Import dEQP.

Import drawElements Quality Program from an internal repository.

Bug: 17388917
Change-Id: Ic109fe4a57e31b2a816113d90fbdf51a43e7abeb
diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt
new file mode 100644
index 0000000..42b8061
--- /dev/null
+++ b/modules/CMakeLists.txt
@@ -0,0 +1,13 @@
+# dEQP Modules
+include(glshared/glshared.cmake			OPTIONAL)
+include(gles2/gles2.cmake				OPTIONAL)
+include(gles3/gles3.cmake				OPTIONAL)
+include(gles31/gles31.cmake				OPTIONAL)
+include(egl/egl.cmake					OPTIONAL)
+
+# Misc
+include(internal/internal.cmake			OPTIONAL)
+
+# Pass DEQP_MODULE_LIBRARIES and DEQP_MODULE_ENTRY_POINTS
+set(DEQP_MODULE_LIBRARIES ${DEQP_MODULE_LIBRARIES} PARENT_SCOPE)
+set(DEQP_MODULE_ENTRY_POINTS ${DEQP_MODULE_ENTRY_POINTS} PARENT_SCOPE)
diff --git a/modules/egl/CMakeLists.txt b/modules/egl/CMakeLists.txt
new file mode 100644
index 0000000..e5779df
--- /dev/null
+++ b/modules/egl/CMakeLists.txt
@@ -0,0 +1,104 @@
+# dEQP-EGL
+
+set(DEQP_EGL_SRCS
+	teglApiCase.cpp
+	teglApiCase.hpp
+	teglChooseConfigReference.cpp
+	teglChooseConfigReference.hpp
+	teglChooseConfigTests.cpp
+	teglChooseConfigTests.hpp
+	teglQueryConfigTests.cpp
+	teglQueryConfigTests.hpp
+	teglColorClearCase.cpp
+	teglColorClearCase.hpp
+	teglColorClearTests.cpp
+	teglColorClearTests.hpp
+	teglConfigList.cpp
+	teglConfigList.hpp
+	teglCreateContextTests.cpp
+	teglCreateContextTests.hpp
+	teglQueryContextTests.cpp
+	teglQueryContextTests.hpp
+	teglCreateSurfaceTests.cpp
+	teglCreateSurfaceTests.hpp
+	teglQuerySurfaceTests.cpp
+	teglQuerySurfaceTests.hpp
+	teglGetProcAddressTests.cpp
+	teglGetProcAddressTests.hpp
+	teglGLES1RenderUtil.cpp
+	teglGLES1RenderUtil.hpp
+	teglGLES2RenderUtil.cpp
+	teglGLES2RenderUtil.hpp
+	teglImageTests.cpp
+	teglImageTests.hpp
+	teglInfoTests.cpp
+	teglInfoTests.hpp
+	teglNegativeApiTests.cpp
+	teglNegativeApiTests.hpp
+	teglRenderCase.cpp
+	teglRenderCase.hpp
+	teglRenderTests.cpp
+	teglRenderTests.hpp
+	teglSimpleConfigCase.cpp
+	teglSimpleConfigCase.hpp
+	teglTestCase.cpp
+	teglTestCase.hpp
+	teglTestPackage.cpp
+	teglTestPackage.hpp
+	teglVGRenderUtil.cpp
+	teglVGRenderUtil.hpp
+	teglImageFormatTests.hpp
+	teglImageFormatTests.cpp
+	teglGLES2SharingTests.hpp
+	teglGLES2SharingTests.cpp
+	teglGLES2SharingThreadedTests.hpp
+	teglGLES2SharingThreadedTests.cpp
+	teglSyncTests.hpp
+	teglSyncTests.cpp
+	teglMultiThreadTests.hpp
+	teglMultiThreadTests.cpp
+	teglMemoryStressTests.hpp
+	teglMemoryStressTests.cpp
+	teglMakeCurrentPerfTests.hpp
+	teglMakeCurrentPerfTests.cpp
+	teglGLES2SharedRenderingPerfTests.hpp
+	teglGLES2SharedRenderingPerfTests.cpp
+	teglPreservingSwapTests.hpp
+	teglPreservingSwapTests.cpp
+	teglClientExtensionTests.hpp
+	teglClientExtensionTests.cpp
+	teglCreateContextExtTests.hpp
+	teglCreateContextExtTests.cpp
+	teglSurfacelessContextTests.hpp
+	teglSurfacelessContextTests.cpp
+	teglSwapBuffersTests.hpp
+	teglSwapBuffersTests.cpp
+	teglNativeColorMappingTests.hpp
+	teglNativeColorMappingTests.cpp
+	teglNativeCoordMappingTests.hpp
+	teglNativeCoordMappingTests.cpp
+	teglResizeTests.hpp
+	teglResizeTests.cpp
+	)
+
+set(DEQP_EGL_LIBS
+	tcutil
+	eglutil
+	referencerenderer
+	${DEQP_EGL_LIBRARIES}
+	)
+
+
+if (DEQP_SUPPORT_GLES2)
+	set(DEQP_EGL_LIBS ${DEQP_EGL_LIBS} glutil glutil-sglr ${DEQP_GLES2_LIBRARIES})
+endif ()
+
+if (DEQP_SUPPORT_GLES1)
+	set(DEQP_EGL_LIBS ${DEQP_EGL_LIBS} ${DEQP_GLES1_LIBRARIES})
+endif ()
+
+if (DEQP_SUPPORT_VG)
+	set(DEQP_EGL_LIBS ${DEQP_EGL_LIBS} ${DEQP_VG_LIBRARIES})
+endif ()
+
+add_deqp_module(deqp-egl "${DEQP_EGL_SRCS}" "${DEQP_EGL_LIBS}" teglTestPackageEntry.cpp)
diff --git a/modules/egl/egl.cmake b/modules/egl/egl.cmake
new file mode 100644
index 0000000..ed6c386
--- /dev/null
+++ b/modules/egl/egl.cmake
@@ -0,0 +1,4 @@
+if (DEQP_SUPPORT_EGL)
+	add_subdirectory(egl)
+endif ()
+
diff --git a/modules/egl/teglApiCase.cpp b/modules/egl/teglApiCase.cpp
new file mode 100644
index 0000000..02ec975
--- /dev/null
+++ b/modules/egl/teglApiCase.cpp
@@ -0,0 +1,158 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief API test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglApiCase.hpp"
+#include "egluStrUtil.hpp"
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+
+ApiCase::ApiCase (EglTestContext& eglTestCtx, const char* name, const char* description)
+	: TestCase		(eglTestCtx, name, description)
+	, CallLogWrapper(eglTestCtx.getTestContext().getLog())
+{
+}
+
+ApiCase::~ApiCase (void)
+{
+}
+
+ApiCase::IterateResult ApiCase::iterate (void)
+{
+	// Initialize result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Enable call logging.
+	enableLogging(true);
+
+	// Run test.
+	test();
+
+	return STOP;
+}
+
+void ApiCase::expectError (EGLenum expected)
+{
+	EGLenum err = eglGetError();
+	if (err != expected)
+	{
+		m_testCtx.getLog() << TestLog::Message << "// ERROR: expected " << eglu::getErrorStr(expected) << TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid error");
+	}
+}
+
+void ApiCase::expectBoolean (EGLBoolean expected, EGLBoolean got)
+{
+	if (expected != got)
+	{
+		m_testCtx.getLog() << TestLog::Message << "// ERROR: expected " << (expected ? "EGL_TRUE" : "EGL_FALSE") << TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	}
+}
+
+void ApiCase::expectNoContext (EGLContext got)
+{
+	if (got != EGL_NO_CONTEXT)
+	{
+		m_testCtx.getLog() << TestLog::Message << "// ERROR: expected EGL_NO_CONTEXT" << TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+		eglDestroyContext(getDisplay(), got);
+	}
+}
+
+void ApiCase::expectNoSurface (EGLSurface got)
+{
+	if (got != EGL_NO_CONTEXT)
+	{
+		m_testCtx.getLog() << TestLog::Message << "// ERROR: expected EGL_NO_SURFACE" << TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+		eglDestroySurface(getDisplay(), got);
+	}
+}
+
+void ApiCase::expectNoDisplay (EGLDisplay got)
+{
+	if (got != EGL_NO_CONTEXT)
+	{
+		m_testCtx.getLog() << TestLog::Message << "// ERROR: expected EGL_NO_DISPLAY" << TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	}
+}
+
+void ApiCase::expectNull (const void* got)
+{
+	if (got != EGL_NO_CONTEXT)
+	{
+		m_testCtx.getLog() << TestLog::Message << "// ERROR: expected NULL" << TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	}
+}
+
+bool ApiCase::getConfig (EGLConfig* config,const eglu::FilterList& filters)
+{
+	for (std::vector<eglu::ConfigInfo>::const_iterator cfgIter = m_eglTestCtx.getConfigs().begin(); cfgIter != m_eglTestCtx.getConfigs().end(); ++cfgIter)
+	{
+		if (filters.match(*cfgIter))
+		{
+			EGLint		numCfgs;
+			EGLBoolean	ok;
+			EGLint		attribs[] =
+			{
+				EGL_CONFIG_ID,			cfgIter->configId,
+				EGL_TRANSPARENT_TYPE,	EGL_DONT_CARE,
+				EGL_COLOR_BUFFER_TYPE,	EGL_DONT_CARE,
+				EGL_RENDERABLE_TYPE,	EGL_DONT_CARE,
+				EGL_SURFACE_TYPE,		EGL_DONT_CARE,
+				EGL_NONE
+			};
+
+			ok = eglChooseConfig(getDisplay(), &attribs[0], config, 1, &numCfgs);
+			expectTrue(ok);
+
+			if (ok && numCfgs >= 1)
+				return true;
+			else
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: expected at least one config with id " << cfgIter->configId << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+				return 0;
+			}
+		}
+	}
+
+	return DE_NULL;
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglApiCase.hpp b/modules/egl/teglApiCase.hpp
new file mode 100644
index 0000000..e80fc65
--- /dev/null
+++ b/modules/egl/teglApiCase.hpp
@@ -0,0 +1,79 @@
+#ifndef _TEGLAPICASE_HPP
+#define _TEGLAPICASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief API test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+#include "egluCallLogWrapper.hpp"
+#include "tcuTestLog.hpp"
+#include "egluConfigFilter.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class ApiCase : public TestCase, protected eglu::CallLogWrapper
+{
+public:
+						ApiCase					(EglTestContext& eglTestCtx, const char* name, const char* description);
+	virtual				~ApiCase				(void);
+
+	IterateResult		iterate					(void);
+
+protected:
+	virtual void		test					(void) = DE_NULL;
+
+	void				expectError				(EGLenum error);
+	void				expectBoolean			(EGLBoolean expected, EGLBoolean got);
+
+	void				expectNoContext			(EGLContext got);
+	void				expectNoSurface			(EGLSurface got);
+	void				expectNoDisplay			(EGLDisplay got);
+	void				expectNull				(const void* got);
+
+	inline void			expectTrue				(EGLBoolean got) { expectBoolean(EGL_TRUE, got); }
+	inline void			expectFalse				(EGLBoolean got) { expectBoolean(EGL_FALSE, got); }
+
+	bool				isAPISupported			(EGLenum api) const	{ return m_eglTestCtx.isAPISupported(api);			}
+	EGLDisplay			getDisplay				(void)				{ return m_eglTestCtx.getDisplay().getEGLDisplay(); }
+	bool				getConfig				(EGLConfig* cfg, const eglu::FilterList& filters);
+};
+
+} // egl
+} // deqp
+
+// Helper macro for declaring ApiCases.
+#define TEGL_ADD_API_CASE(NAME, DESCRIPTION, TEST_FUNC_BODY)									\
+	do {																						\
+		class ApiCase_##NAME : public deqp::egl::ApiCase {										\
+		public:																					\
+			ApiCase_##NAME (EglTestContext& context) : ApiCase(context, #NAME, DESCRIPTION) {}	\
+		protected:																				\
+			void test (void) TEST_FUNC_BODY														\
+		};																						\
+		addChild(new ApiCase_##NAME(m_eglTestCtx));												\
+	} while (deGetFalse())
+
+#endif // _TEGLAPICASE_HPP
diff --git a/modules/egl/teglChooseConfigReference.cpp b/modules/egl/teglChooseConfigReference.cpp
new file mode 100644
index 0000000..10f2530
--- /dev/null
+++ b/modules/egl/teglChooseConfigReference.cpp
@@ -0,0 +1,392 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Choose config reference implementation.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglChooseConfigReference.hpp"
+
+#include <algorithm>
+#include <vector>
+#include <map>
+
+namespace deqp
+{
+namespace egl
+{
+
+using eglu::ConfigInfo;
+
+enum Criteria
+{
+	CRITERIA_AT_LEAST = 0,
+	CRITERIA_EXACT,
+	CRITERIA_MASK,
+	CRITERIA_SPECIAL,
+
+	CRITERIA_LAST
+};
+
+enum SortOrder
+{
+	SORTORDER_NONE	= 0,
+	SORTORDER_SMALLER,
+	SORTORDER_SPECIAL,
+
+	SORTORDER_LAST
+};
+
+struct AttribRule
+{
+	EGLenum		name;
+	EGLint		value;
+	Criteria	criteria;
+	SortOrder	sortOrder;
+
+	AttribRule (void)
+		: name			(EGL_NONE)
+		, value			(EGL_NONE)
+		, criteria		(CRITERIA_LAST)
+		, sortOrder		(SORTORDER_LAST)
+	{
+	}
+
+	AttribRule (EGLenum name_, EGLint value_, Criteria criteria_, SortOrder sortOrder_)
+		: name			(name_)
+		, value			(value_)
+		, criteria		(criteria_)
+		, sortOrder		(sortOrder_)
+	{
+	}
+};
+
+class SurfaceConfig
+{
+private:
+	static int getCaveatRank (EGLenum caveat)
+	{
+		switch (caveat)
+		{
+			case EGL_NONE:					return 0;
+			case EGL_SLOW_CONFIG:			return 1;
+			case EGL_NON_CONFORMANT_CONFIG:	return 2;
+			default: DE_ASSERT(DE_FALSE);	return 3;
+		}
+	}
+
+	static int getColorBufferTypeRank (EGLenum type)
+	{
+		switch (type)
+		{
+			case EGL_RGB_BUFFER:			return 0;
+			case EGL_LUMINANCE_BUFFER:		return 1;
+			default: DE_ASSERT(DE_FALSE);	return 2;
+		}
+	}
+
+	typedef bool (*CompareFunc) (const SurfaceConfig& a, const SurfaceConfig& b);
+
+	static bool compareCaveat (const SurfaceConfig& a, const SurfaceConfig& b)
+	{
+		return getCaveatRank((EGLenum)a.m_info.configCaveat) < getCaveatRank((EGLenum)b.m_info.configCaveat);
+	}
+
+	static bool compareColorBufferType (const SurfaceConfig& a, const SurfaceConfig& b)
+	{
+		return getColorBufferTypeRank((EGLenum)a.m_info.colorBufferType) < getColorBufferTypeRank((EGLenum)b.m_info.colorBufferType);
+	}
+
+	static bool compareColorBufferBits (const SurfaceConfig& a, const SurfaceConfig& b)
+	{
+		DE_ASSERT(a.m_info.colorBufferType == b.m_info.colorBufferType);
+		switch (a.m_info.colorBufferType)
+		{
+			case EGL_RGB_BUFFER:
+				return (a.m_info.redSize + a.m_info.greenSize + a.m_info.blueSize + a.m_info.alphaSize)
+						> (b.m_info.redSize + b.m_info.greenSize + b.m_info.blueSize + b.m_info.alphaSize);
+
+			case EGL_LUMINANCE_BUFFER:
+				return (a.m_info.luminanceSize + a.m_info.alphaSize) > (b.m_info.luminanceSize + b.m_info.alphaSize);
+
+			default:
+				DE_ASSERT(DE_FALSE);
+				return true;
+		}
+	}
+
+	template <EGLenum Attribute>
+	static bool compareAttributeSmaller (const SurfaceConfig& a, const SurfaceConfig& b)
+	{
+		return a.getAttribute(Attribute) < b.getAttribute(Attribute);
+	}
+public:
+	SurfaceConfig (EGLConfig config, ConfigInfo &info)
+		: m_config(config)
+		, m_info(info)
+	{
+	}
+
+	EGLConfig getEglConfig (void) const
+	{
+		return m_config;
+	}
+
+	EGLint getAttribute (const EGLenum attribute) const
+	{
+		return m_info.getAttribute(attribute);
+	}
+
+	friend bool operator== (const SurfaceConfig& a, const SurfaceConfig& b)
+	{
+		for (std::map<EGLenum, AttribRule>::const_iterator iter = SurfaceConfig::defaultRules.begin(); iter != SurfaceConfig::defaultRules.end(); iter++)
+		{
+			const EGLenum attribute = iter->first;
+
+			if (a.getAttribute(attribute) != b.getAttribute(attribute)) return false;
+		}
+		return true;
+	}
+
+	bool compareTo (const SurfaceConfig& b, bool skipColorBufferBits=false) const
+	{
+		static const SurfaceConfig::CompareFunc compareFuncs[] =
+		{
+			SurfaceConfig::compareCaveat,
+			SurfaceConfig::compareColorBufferType,
+			SurfaceConfig::compareColorBufferBits,
+			SurfaceConfig::compareAttributeSmaller<EGL_BUFFER_SIZE>,
+			SurfaceConfig::compareAttributeSmaller<EGL_SAMPLE_BUFFERS>,
+			SurfaceConfig::compareAttributeSmaller<EGL_SAMPLES>,
+			SurfaceConfig::compareAttributeSmaller<EGL_DEPTH_SIZE>,
+			SurfaceConfig::compareAttributeSmaller<EGL_STENCIL_SIZE>,
+			SurfaceConfig::compareAttributeSmaller<EGL_ALPHA_MASK_SIZE>,
+			SurfaceConfig::compareAttributeSmaller<EGL_CONFIG_ID>
+		};
+
+		if (*this == b)
+			return false; // std::sort() can compare object to itself.
+
+		for (int ndx = 0; ndx < (int)DE_LENGTH_OF_ARRAY(compareFuncs); ndx++)
+		{
+			if (skipColorBufferBits && (compareFuncs[ndx] == SurfaceConfig::compareColorBufferBits))
+				continue;
+
+			if (compareFuncs[ndx](*this, b))
+				return true;
+			else if (compareFuncs[ndx](b, *this))
+				return false;
+		}
+
+		TCU_FAIL("Unable to compare configs - duplicate ID?");
+	}
+
+	static const std::map<EGLenum, AttribRule> defaultRules;
+
+	static std::map<EGLenum, AttribRule> initAttribRules (void)
+	{
+		// \todo [2011-03-24 pyry] From EGL 1.4 spec - check that this is valid for other versions as well
+		std::map<EGLenum, AttribRule> rules;
+
+		//									Attribute									Default				Selection Criteria	Sort Order			Sort Priority
+		rules[EGL_BUFFER_SIZE]				= AttribRule(EGL_BUFFER_SIZE,				0,					CRITERIA_AT_LEAST,	SORTORDER_SMALLER);	//	4
+		rules[EGL_RED_SIZE]					= AttribRule(EGL_RED_SIZE,					0,					CRITERIA_AT_LEAST,	SORTORDER_SPECIAL);	//	3
+		rules[EGL_GREEN_SIZE]				= AttribRule(EGL_GREEN_SIZE,				0,					CRITERIA_AT_LEAST,	SORTORDER_SPECIAL);	//	3
+		rules[EGL_BLUE_SIZE]				= AttribRule(EGL_BLUE_SIZE,					0,					CRITERIA_AT_LEAST,	SORTORDER_SPECIAL);	//	3
+		rules[EGL_LUMINANCE_SIZE]			= AttribRule(EGL_LUMINANCE_SIZE,			0,					CRITERIA_AT_LEAST,	SORTORDER_SPECIAL);	//	3
+		rules[EGL_ALPHA_SIZE]				= AttribRule(EGL_ALPHA_SIZE,				0,					CRITERIA_AT_LEAST,	SORTORDER_SPECIAL);	//	3
+		rules[EGL_ALPHA_MASK_SIZE]			= AttribRule(EGL_ALPHA_MASK_SIZE,			0,					CRITERIA_AT_LEAST,	SORTORDER_SMALLER);	//	9
+		rules[EGL_BIND_TO_TEXTURE_RGB]		= AttribRule(EGL_BIND_TO_TEXTURE_RGB,		EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_NONE);
+		rules[EGL_BIND_TO_TEXTURE_RGBA]		= AttribRule(EGL_BIND_TO_TEXTURE_RGBA,		EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_NONE);
+		rules[EGL_COLOR_BUFFER_TYPE]		= AttribRule(EGL_COLOR_BUFFER_TYPE,			EGL_RGB_BUFFER,		CRITERIA_EXACT,		SORTORDER_NONE);	//	2
+		rules[EGL_CONFIG_CAVEAT]			= AttribRule(EGL_CONFIG_CAVEAT,				EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_SPECIAL);	//	1
+		rules[EGL_CONFIG_ID]				= AttribRule(EGL_CONFIG_ID,					EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_SMALLER);	//	11
+		rules[EGL_CONFORMANT]				= AttribRule(EGL_CONFORMANT,				0,					CRITERIA_MASK,		SORTORDER_NONE);
+		rules[EGL_DEPTH_SIZE]				= AttribRule(EGL_DEPTH_SIZE,				0,					CRITERIA_AT_LEAST,	SORTORDER_SMALLER);	//	7
+		rules[EGL_LEVEL]					= AttribRule(EGL_LEVEL,						0,					CRITERIA_EXACT,		SORTORDER_NONE);
+		rules[EGL_MATCH_NATIVE_PIXMAP]		= AttribRule(EGL_MATCH_NATIVE_PIXMAP,		EGL_NONE,			CRITERIA_SPECIAL,	SORTORDER_NONE);
+		rules[EGL_MAX_SWAP_INTERVAL]		= AttribRule(EGL_MAX_SWAP_INTERVAL,			EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_NONE);
+		rules[EGL_MIN_SWAP_INTERVAL]		= AttribRule(EGL_MIN_SWAP_INTERVAL,			EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_NONE);
+		rules[EGL_NATIVE_RENDERABLE]		= AttribRule(EGL_NATIVE_RENDERABLE,			EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_NONE);
+		rules[EGL_NATIVE_VISUAL_TYPE]		= AttribRule(EGL_NATIVE_VISUAL_TYPE,		EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_SPECIAL);	//	10
+		rules[EGL_RENDERABLE_TYPE]			= AttribRule(EGL_RENDERABLE_TYPE,			EGL_OPENGL_ES_BIT,	CRITERIA_MASK,		SORTORDER_NONE);
+		rules[EGL_SAMPLE_BUFFERS]			= AttribRule(EGL_SAMPLE_BUFFERS,			0,					CRITERIA_AT_LEAST,	SORTORDER_SMALLER);	//	5
+		rules[EGL_SAMPLES]					= AttribRule(EGL_SAMPLES,					0,					CRITERIA_AT_LEAST,	SORTORDER_SMALLER);	//	6
+		rules[EGL_STENCIL_SIZE]				= AttribRule(EGL_STENCIL_SIZE,				0,					CRITERIA_AT_LEAST,	SORTORDER_SMALLER);	//	8
+		rules[EGL_SURFACE_TYPE]				= AttribRule(EGL_SURFACE_TYPE,				EGL_WINDOW_BIT,		CRITERIA_MASK,		SORTORDER_NONE);
+		rules[EGL_TRANSPARENT_TYPE]			= AttribRule(EGL_TRANSPARENT_TYPE,			EGL_NONE,			CRITERIA_EXACT,		SORTORDER_NONE);
+		rules[EGL_TRANSPARENT_RED_VALUE]	= AttribRule(EGL_TRANSPARENT_RED_VALUE,		EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_NONE);
+		rules[EGL_TRANSPARENT_GREEN_VALUE]	= AttribRule(EGL_TRANSPARENT_GREEN_VALUE,	EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_NONE);
+		rules[EGL_TRANSPARENT_BLUE_VALUE]	= AttribRule(EGL_TRANSPARENT_BLUE_VALUE,	EGL_DONT_CARE,		CRITERIA_EXACT,		SORTORDER_NONE);
+
+		return rules;
+	}
+private:
+	EGLConfig m_config;
+	ConfigInfo m_info;
+};
+
+const std::map<EGLenum, AttribRule> SurfaceConfig::defaultRules = SurfaceConfig::initAttribRules();
+
+class CompareConfigs
+{
+public:
+	CompareConfigs (bool skipColorBufferBits)
+		: m_skipColorBufferBits(skipColorBufferBits)
+	{
+	}
+
+	bool operator() (const SurfaceConfig& a, const SurfaceConfig& b)
+	{
+		return a.compareTo(b, m_skipColorBufferBits);
+	}
+
+private:
+	bool m_skipColorBufferBits;
+};
+
+class ConfigFilter
+{
+private:
+	std::map<EGLenum, AttribRule> m_rules;
+public:
+	ConfigFilter ()
+		: m_rules(SurfaceConfig::defaultRules)
+	{
+	}
+
+	void setValue (EGLenum name, EGLint value)
+	{
+		DE_ASSERT(SurfaceConfig::defaultRules.find(name) != SurfaceConfig::defaultRules.end());
+		m_rules[name].value = value;
+	}
+
+	void setValues (std::vector<std::pair<EGLenum, EGLint> > values)
+	{
+		for (size_t ndx = 0; ndx < values.size(); ndx++)
+		{
+			const EGLenum	name	= values[ndx].first;
+			const EGLint	value	= values[ndx].second;
+
+			setValue(name, value);
+		}
+	}
+
+	AttribRule getAttribute (EGLenum name)
+	{
+		DE_ASSERT(SurfaceConfig::defaultRules.find(name) != SurfaceConfig::defaultRules.end());
+		return m_rules[name];
+	}
+
+	bool isMatch (const SurfaceConfig& config)
+	{
+		bool result = true;
+		for (std::map<EGLenum, AttribRule>::const_iterator iter = m_rules.begin(); iter != m_rules.end(); iter++)
+		{
+			const AttribRule rule = iter->second;
+
+			if (rule.value == EGL_DONT_CARE)
+				continue;
+			else if (rule.name == EGL_MATCH_NATIVE_PIXMAP)
+				TCU_CHECK(rule.value == EGL_NONE); // Not supported
+			else if (rule.name == EGL_TRANSPARENT_RED_VALUE || rule.name == EGL_TRANSPARENT_GREEN_VALUE || rule.name == EGL_TRANSPARENT_BLUE_VALUE)
+				continue;
+			else
+			{
+				const EGLint cfgValue = config.getAttribute(rule.name);
+
+				switch (rule.criteria)
+				{
+					case CRITERIA_EXACT:	result = rule.value == cfgValue;				break;
+					case CRITERIA_AT_LEAST:	result = rule.value <= cfgValue;				break;
+					case CRITERIA_MASK:		result = (rule.value & cfgValue) == rule.value;	break;
+					default:				TCU_FAIL("Unknown criteria");
+				}
+			}
+
+			if (result == false) return false;
+		}
+
+		return true;
+	}
+
+	bool isColorBitsUnspecified (void)
+	{
+		const EGLenum	bitAttribs[]	= { EGL_RED_SIZE, EGL_GREEN_SIZE, EGL_BLUE_SIZE, EGL_LUMINANCE_SIZE };
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(bitAttribs); ndx++)
+		{
+			const EGLenum	attrib	= bitAttribs[ndx];
+			const EGLint	value	= getAttribute(attrib).value;
+
+			if (value != 0 && value != EGL_DONT_CARE) return false;
+		}
+
+		return true;
+	}
+
+	std::vector<SurfaceConfig> filter (const std::vector<SurfaceConfig>& configs)
+	{
+		std::vector<SurfaceConfig> out;
+
+		for (std::vector<SurfaceConfig>::const_iterator iter = configs.begin(); iter != configs.end(); iter++)
+		{
+			if (isMatch(*iter)) out.push_back(*iter);
+		}
+
+		return out;
+	}
+};
+
+void chooseConfigReference (const tcu::egl::Display& display, std::vector<EGLConfig>& dst, const std::vector<std::pair<EGLenum, EGLint> >& attributes)
+{
+	// Get all configs
+	std::vector<EGLConfig> eglConfigs;
+	display.getConfigs(eglConfigs);
+
+	// Config infos
+	std::vector<ConfigInfo> configInfos;
+	configInfos.resize(eglConfigs.size());
+	for (size_t ndx = 0; ndx < eglConfigs.size(); ndx++)
+		display.describeConfig(eglConfigs[ndx], configInfos[ndx]);
+
+	TCU_CHECK_EGL_MSG("Config query failed");
+
+	// Pair configs with info
+	std::vector<SurfaceConfig> configs;
+	for (size_t ndx = 0; ndx < eglConfigs.size(); ndx++)
+		configs.push_back(SurfaceConfig(eglConfigs[ndx], configInfos[ndx]));
+
+	// Filter configs
+	ConfigFilter configFilter;
+	configFilter.setValues(attributes);
+
+	std::vector<SurfaceConfig> filteredConfigs = configFilter.filter(configs);
+
+	// Sort configs
+	std::sort(filteredConfigs.begin(), filteredConfigs.end(), CompareConfigs(configFilter.isColorBitsUnspecified()));
+
+	// Write to dst list
+	dst.resize(filteredConfigs.size());
+	for (size_t ndx = 0; ndx < filteredConfigs.size(); ndx++)
+		dst[ndx] = filteredConfigs[ndx].getEglConfig();
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglChooseConfigReference.hpp b/modules/egl/teglChooseConfigReference.hpp
new file mode 100644
index 0000000..bb93df4
--- /dev/null
+++ b/modules/egl/teglChooseConfigReference.hpp
@@ -0,0 +1,39 @@
+#ifndef _TEGLCHOOSECONFIGREFERENCE_HPP
+#define _TEGLCHOOSECONFIGREFERENCE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Choose config reference implementation.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+void chooseConfigReference (const tcu::egl::Display& display, std::vector<EGLConfig>& dst, const std::vector<std::pair<EGLenum, EGLint> >& attributes);
+
+} // egl
+} // deqp
+
+#endif // _TEGLCHOOSECONFIGREFERENCE_HPP
diff --git a/modules/egl/teglChooseConfigTests.cpp b/modules/egl/teglChooseConfigTests.cpp
new file mode 100644
index 0000000..cfa8899
--- /dev/null
+++ b/modules/egl/teglChooseConfigTests.cpp
@@ -0,0 +1,596 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Choose config tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglChooseConfigTests.hpp"
+#include "teglChooseConfigReference.hpp"
+#include "tcuTestLog.hpp"
+#include "egluStrUtil.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+#include <vector>
+#include <algorithm>
+#include <string>
+#include <set>
+#include <map>
+
+using std::set;
+using std::vector;
+using std::pair;
+using std::string;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+
+using eglu::ConfigInfo;
+
+namespace
+{
+
+string configListToString (const tcu::egl::Display& display, const vector<EGLConfig>& configs)
+{
+	string str = "";
+	for (vector<EGLConfig>::const_iterator cfgIter = configs.begin(); cfgIter != configs.end(); cfgIter++)
+	{
+		EGLConfig	config		= *cfgIter;
+		EGLint		configId	= display.getConfigAttrib(config, EGL_CONFIG_ID);
+
+		if (str.length() != 0)
+			str += " ";
+
+		str += de::toString(configId);
+	}
+	return str;
+}
+
+void logConfigAttrib (TestLog& log, EGLenum attrib, EGLint value)
+{
+	const std::string	attribStr	= eglu::getConfigAttribName(attrib);
+
+	if (value == EGL_DONT_CARE)
+	{
+		log << TestLog::Message << "  " << attribStr << ": EGL_DONT_CARE" << TestLog::EndMessage;
+		return;
+	}
+
+	log << TestLog::Message << "  " << attribStr << ": " << eglu::getConfigAttribValueStr(attrib, value) << TestLog::EndMessage;
+}
+
+} // anonymous
+
+class ChooseConfigCase : public TestCase
+{
+public:
+	ChooseConfigCase (EglTestContext& eglTestCtx, const char* name, const char* description, bool checkOrder, const EGLint* attributes)
+		: TestCase		(eglTestCtx, name, description)
+		, m_checkOrder	(checkOrder)
+	{
+		// Parse attributes
+		while (attributes[0] != EGL_NONE)
+		{
+			m_attributes.push_back(std::make_pair((EGLenum)attributes[0], (EGLint)attributes[1]));
+			attributes += 2;
+		}
+	}
+
+	ChooseConfigCase (EglTestContext& eglTestCtx, const char* name, const char* description, bool checkOrder, const std::vector<std::pair<EGLenum, EGLint> >& attributes)
+		: TestCase		(eglTestCtx, name, description)
+		, m_checkOrder	(checkOrder)
+		, m_attributes	(attributes)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		executeTest(m_attributes, m_checkOrder);
+		return STOP;
+	}
+protected:
+	ChooseConfigCase (EglTestContext& eglTestCtx, const char* name, const char* description, bool checkOrder)
+		: TestCase		(eglTestCtx, name, description)
+		, m_checkOrder	(checkOrder)
+	{
+	}
+
+	void executeTest (const std::vector<std::pair<EGLenum, EGLint> >& attributes, bool checkOrder)
+	{
+		TestLog&					log		= m_testCtx.getLog();
+		const tcu::egl::Display&	display	= m_eglTestCtx.getDisplay();
+
+		// Build attributes for EGL
+		vector<EGLint> attribList;
+		for (vector<pair<EGLenum, EGLint> >::const_iterator i = attributes.begin(); i != attributes.end(); i++)
+		{
+			attribList.push_back(i->first);
+			attribList.push_back(i->second);
+		}
+		attribList.push_back(EGL_NONE);
+
+		// Print attribList to log
+		log << TestLog::Message << "Attributes:" << TestLog::EndMessage;
+		for (vector<pair<EGLenum, EGLint> >::const_iterator i = attributes.begin(); i != attributes.end(); i++)
+			logConfigAttrib(log, i->first, i->second);
+
+		std::vector<EGLConfig>	resultConfigs;
+		std::vector<EGLConfig>	referenceConfigs;
+
+		// Query from EGL implementation
+		{
+			EGLint numConfigs = 0;
+			TCU_CHECK_EGL_CALL(eglChooseConfig(display.getEGLDisplay(), &attribList[0], DE_NULL, 0, &numConfigs));
+			resultConfigs.resize(numConfigs);
+
+			if (numConfigs > 0)
+				TCU_CHECK_EGL_CALL(eglChooseConfig(display.getEGLDisplay(), &attribList[0], &resultConfigs[0], (EGLint)resultConfigs.size(), &numConfigs));
+		}
+
+		// Build reference
+		chooseConfigReference(display, referenceConfigs, attributes);
+
+		log << TestLog::Message << "Expected:\n  " << configListToString(display, referenceConfigs) << TestLog::EndMessage;
+		log << TestLog::Message << "Got:\n  " << configListToString(display, resultConfigs) << TestLog::EndMessage;
+
+		bool isSetMatch		= (set<EGLConfig>(resultConfigs.begin(), resultConfigs.end()) == set<EGLConfig>(referenceConfigs.begin(), referenceConfigs.end()));
+		bool isExactMatch	= (resultConfigs == referenceConfigs);
+		bool isMatch		= isSetMatch && (checkOrder ? isExactMatch : true);
+
+		if (isMatch)
+			log << TestLog::Message << "Pass" << TestLog::EndMessage;
+		else if (!isSetMatch)
+			log << TestLog::Message << "Fail, configs don't match" << TestLog::EndMessage;
+		else if (!isExactMatch)
+			log << TestLog::Message << "Fail, got correct configs but in invalid order" << TestLog::EndMessage;
+
+		if (!isMatch)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	}
+
+	void fillDontCare (std::vector<std::pair<EGLenum, EGLint> >& attributes)
+	{
+		static const EGLenum dontCareAttributes[] =
+		{
+			EGL_TRANSPARENT_TYPE,
+			EGL_COLOR_BUFFER_TYPE,
+			EGL_RENDERABLE_TYPE,
+			EGL_SURFACE_TYPE
+		};
+
+		// Fill appropriate unused attributes with EGL_DONT_CARE
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(dontCareAttributes); ndx++)
+		{
+			bool found = false;
+			for (size_t findNdx = 0; findNdx < attributes.size(); findNdx++)
+				if (attributes[findNdx].first == dontCareAttributes[ndx]) found = true;
+
+			if (!found) attributes.push_back(std::make_pair(dontCareAttributes[ndx], EGL_DONT_CARE));
+		}
+	}
+
+	bool							m_checkOrder;
+	vector<pair<EGLenum, EGLint> >	m_attributes;
+};
+
+class ChooseConfigSimpleCase : public ChooseConfigCase
+{
+protected:
+	EGLint getValue (EGLenum name)
+	{
+		static const struct
+		{
+			EGLenum		name;
+			EGLint		value;
+		} attributes[] = {
+			{ EGL_BUFFER_SIZE,				0					},
+			{ EGL_RED_SIZE,					0					},
+			{ EGL_GREEN_SIZE,				0					},
+			{ EGL_BLUE_SIZE,				0					},
+			{ EGL_LUMINANCE_SIZE,			0					},
+			{ EGL_ALPHA_SIZE,				0					},
+			{ EGL_ALPHA_MASK_SIZE,			0					},
+			{ EGL_BIND_TO_TEXTURE_RGB,		EGL_DONT_CARE		},
+			{ EGL_BIND_TO_TEXTURE_RGBA,		EGL_DONT_CARE		},
+			{ EGL_COLOR_BUFFER_TYPE,		EGL_DONT_CARE		},
+			{ EGL_CONFIG_CAVEAT,			EGL_DONT_CARE		},
+			//{ EGL_CONFIG_ID,				EGL_DONT_CARE		},
+			{ EGL_DEPTH_SIZE,				0					},
+			{ EGL_LEVEL,					0					},
+			{ EGL_MAX_SWAP_INTERVAL,		EGL_DONT_CARE		},
+			{ EGL_MIN_SWAP_INTERVAL,		EGL_DONT_CARE		},
+			{ EGL_NATIVE_RENDERABLE,		EGL_DONT_CARE		},
+			{ EGL_NATIVE_VISUAL_TYPE,		EGL_DONT_CARE		},
+			{ EGL_SAMPLE_BUFFERS,			0					},
+			{ EGL_SAMPLES,					0					},
+			{ EGL_STENCIL_SIZE,				0					},
+			{ EGL_TRANSPARENT_TYPE,			EGL_TRANSPARENT_RGB	},
+			{ EGL_TRANSPARENT_RED_VALUE,	0					},
+			{ EGL_TRANSPARENT_GREEN_VALUE,	0					},
+			{ EGL_TRANSPARENT_BLUE_VALUE,	0					},
+			{ EGL_CONFORMANT,				EGL_OPENGL_ES_BIT	},
+			{ EGL_RENDERABLE_TYPE,			EGL_OPENGL_ES_BIT	},
+			{ EGL_SURFACE_TYPE,				EGL_WINDOW_BIT		}
+			//{ EGL_CONFORMANT,				EGL_OPENGL_BIT | EGL_OPENGL_ES_BIT | EGL_OPENGL_ES2_BIT | EGL_OPENVG_BIT	},
+			//{ EGL_RENDERABLE_TYPE,			EGL_OPENGL_BIT | EGL_OPENGL_ES_BIT | EGL_OPENGL_ES2_BIT | EGL_OPENVG_BIT	},
+			//{ EGL_SURFACE_TYPE,				EGL_WINDOW_BIT
+			//								| EGL_PIXMAP_BIT
+			//								| EGL_PBUFFER_BIT
+			//								| EGL_MULTISAMPLE_RESOLVE_BOX_BIT
+			//								| EGL_VG_ALPHA_FORMAT_PRE_BIT
+			//								| EGL_SWAP_BEHAVIOR_PRESERVED_BIT
+			//								| EGL_VG_COLORSPACE_LINEAR_BIT
+			//								}
+		};
+
+		if (name == EGL_CONFIG_ID)
+		{
+			de::Random rnd(0);
+			return m_eglTestCtx.getConfigs()[rnd.getInt(0, (int)(m_eglTestCtx.getConfigs().size()-1))].configId;
+		}
+		else
+		{
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(attributes); ndx++)
+			{
+				if (attributes[ndx].name == name) return attributes[ndx].value;
+			}
+		}
+
+		DE_ASSERT(DE_FALSE);
+		return EGL_NONE;
+	}
+public:
+	ChooseConfigSimpleCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLenum attribute, bool checkOrder)
+		: ChooseConfigCase(eglTestCtx, name, description, checkOrder)
+		, m_attribute(attribute)
+	{
+	}
+
+	void init (void)
+	{
+	}
+
+	TestCase::IterateResult iterate (void)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		std::vector<std::pair<EGLenum, EGLint> > attributes;
+		attributes.push_back(std::pair<EGLenum, EGLint>(m_attribute, getValue(m_attribute)));
+
+		fillDontCare(attributes);
+		executeTest(attributes, m_checkOrder);
+
+		return STOP;
+	}
+private:
+	EGLenum	m_attribute;
+};
+
+class ChooseConfigRandomCase : public ChooseConfigCase
+{
+public:
+	ChooseConfigRandomCase (EglTestContext& eglTestCtx, const char* name, const char* description, const set<EGLenum>& attribSet)
+		: ChooseConfigCase	(eglTestCtx, name, description, true)
+		, m_attribSet		(attribSet)
+		, m_numIters		(10)
+		, m_iterNdx			(0)
+	{
+	}
+
+	void init (void)
+	{
+		m_iterNdx = 0;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+
+	TestCase::IterateResult iterate (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Iteration :" << m_iterNdx << TestLog::EndMessage;
+		m_iterNdx += 1;
+
+		// Build random list of attributes
+		de::Random									rnd(m_iterNdx);
+		const int									numAttribs	= rnd.getInt(0, (int)m_attribSet.size()*2);
+
+		std::vector<std::pair<EGLenum, EGLint> >	attributes	= genRandomAttributes(m_attribSet, numAttribs, rnd);
+
+		fillDontCare(attributes);
+		executeTest(attributes, m_checkOrder);
+
+		return m_iterNdx < m_numIters ? CONTINUE : STOP;
+	}
+
+	template <int MinVal, int MaxVal> static EGLint getInt (de::Random& rnd)
+	{
+		return rnd.getInt(MinVal, MaxVal);
+	}
+
+	static EGLint getBool (de::Random& rnd)
+	{
+		return rnd.getBool() ? EGL_TRUE : EGL_FALSE;
+	}
+
+	static EGLint getBufferType (de::Random& rnd)
+	{
+		static const EGLint types[] = { EGL_RGB_BUFFER, EGL_LUMINANCE_BUFFER };
+		return rnd.choose<EGLint>(types, types+DE_LENGTH_OF_ARRAY(types));
+	}
+
+	static EGLint getConfigCaveat (de::Random& rnd)
+	{
+		static const EGLint caveats[] = { EGL_SLOW_CONFIG, EGL_NON_CONFORMANT_CONFIG };
+		return rnd.choose<EGLint>(caveats, caveats+DE_LENGTH_OF_ARRAY(caveats));
+	}
+
+	static EGLint getApiBits (de::Random& rnd)
+	{
+		EGLint api = 0;
+		api |= rnd.getBool() ? EGL_OPENGL_BIT		: 0;
+		api |= rnd.getBool() ? EGL_OPENGL_ES_BIT	: 0;
+		api |= rnd.getBool() ? EGL_OPENGL_ES2_BIT	: 0;
+		api |= rnd.getBool() ? EGL_OPENVG_BIT		: 0;
+		return api;
+	}
+
+	static EGLint getSurfaceType (de::Random& rnd)
+	{
+		EGLint bits = 0;
+		bits |= rnd.getBool() ? EGL_WINDOW_BIT	: 0;
+		bits |= rnd.getBool() ? EGL_PIXMAP_BIT	: 0;
+		bits |= rnd.getBool() ? EGL_PBUFFER_BIT	: 0;
+		return bits;
+	}
+
+	struct AttribSpec
+	{
+		EGLenum			attribute;
+		EGLint			(*getValue)(de::Random& rnd);
+	};
+
+	std::vector<std::pair<EGLenum, EGLint> > genRandomAttributes (const std::set<EGLenum>& attribSet, int numAttribs, de::Random& rnd)
+	{
+		static const struct AttribSpec attributes[] =
+		{
+			{ EGL_BUFFER_SIZE,				ChooseConfigRandomCase::getInt<0, 32>,		},
+			{ EGL_RED_SIZE,					ChooseConfigRandomCase::getInt<0, 8>,		},
+			{ EGL_GREEN_SIZE,				ChooseConfigRandomCase::getInt<0, 8>,		},
+			{ EGL_BLUE_SIZE,				ChooseConfigRandomCase::getInt<0, 8>,		},
+			{ EGL_LUMINANCE_SIZE,			ChooseConfigRandomCase::getInt<0, 1>,		},
+			{ EGL_ALPHA_SIZE,				ChooseConfigRandomCase::getInt<0, 8>,		},
+			{ EGL_ALPHA_MASK_SIZE,			ChooseConfigRandomCase::getInt<0, 1>,		},
+			{ EGL_BIND_TO_TEXTURE_RGB,		ChooseConfigRandomCase::getBool,			},
+			{ EGL_BIND_TO_TEXTURE_RGBA,		ChooseConfigRandomCase::getBool,			},
+			{ EGL_COLOR_BUFFER_TYPE,		ChooseConfigRandomCase::getBufferType,		},
+			{ EGL_CONFIG_CAVEAT,			ChooseConfigRandomCase::getConfigCaveat,	},
+//			{ EGL_CONFIG_ID,				0/*special*/,		},
+			{ EGL_CONFORMANT,				ChooseConfigRandomCase::getApiBits,			},
+			{ EGL_DEPTH_SIZE,				ChooseConfigRandomCase::getInt<0, 32>,		},
+			{ EGL_LEVEL,					ChooseConfigRandomCase::getInt<0, 1>,		},
+//			{ EGL_MATCH_NATIVE_PIXMAP,		EGL_NONE,			},
+			{ EGL_MAX_SWAP_INTERVAL,		ChooseConfigRandomCase::getInt<0, 2>,		},
+			{ EGL_MIN_SWAP_INTERVAL,		ChooseConfigRandomCase::getInt<0, 1>,		},
+			{ EGL_NATIVE_RENDERABLE,		ChooseConfigRandomCase::getBool,			},
+//			{ EGL_NATIVE_VISUAL_TYPE,		EGL_DONT_CARE,		},
+			{ EGL_RENDERABLE_TYPE,			ChooseConfigRandomCase::getApiBits,			},
+			{ EGL_SAMPLE_BUFFERS,			ChooseConfigRandomCase::getInt<0, 1>,		},
+			{ EGL_SAMPLES,					ChooseConfigRandomCase::getInt<0, 1>,		},
+			{ EGL_STENCIL_SIZE,				ChooseConfigRandomCase::getInt<0, 1>,		},
+			{ EGL_SURFACE_TYPE,				ChooseConfigRandomCase::getSurfaceType,		},
+//			{ EGL_TRANSPARENT_TYPE,			EGL_TRANSPARENT_RGB,},
+//			{ EGL_TRANSPARENT_RED_VALUE,	ChooseConfigRandomCase::getInt<0, 255>,		},
+//			{ EGL_TRANSPARENT_GREEN_VALUE,	ChooseConfigRandomCase::getInt<0, 255>,		},
+//			{ EGL_TRANSPARENT_BLUE_VALUE,	ChooseConfigRandomCase::getInt<0, 255>,		}
+		};
+
+		std::vector<std::pair<EGLenum, EGLint> > out;
+
+		// Build list to select from
+		std::vector<AttribSpec> candidates;
+		for (int ndx = 0; ndx < (int)DE_LENGTH_OF_ARRAY(attributes); ndx++)
+		{
+			if (attribSet.find(attributes[ndx].attribute) != attribSet.end())
+				candidates.push_back(attributes[ndx]);
+		}
+
+		for (int attribNdx = 0; attribNdx < numAttribs; attribNdx++)
+		{
+			AttribSpec spec = rnd.choose<AttribSpec>(candidates.begin(), candidates.end());
+			out.push_back(std::make_pair(spec.attribute, spec.getValue(rnd)));
+		}
+
+		return out;
+	}
+private:
+	std::set<EGLenum>	m_attribSet;
+	int					m_numIters;
+	int					m_iterNdx;
+};
+
+ChooseConfigTests::ChooseConfigTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "choose_config", "eglChooseConfig() tests")
+{
+}
+
+ChooseConfigTests::~ChooseConfigTests (void)
+{
+}
+
+namespace
+{
+
+template <typename T, size_t N>
+std::set<T> toSet (const T (&arr)[N])
+{
+	std::set<T> set;
+	for (size_t i = 0; i < N; i++)
+		set.insert(arr[i]);
+	return set;
+}
+
+} // anonymous
+
+void ChooseConfigTests::init (void)
+{
+	// Single attributes
+	{
+		static const struct
+		{
+			EGLenum			attribute;
+			const char*		testName;
+		} attributes[] =
+		{
+			{ EGL_BUFFER_SIZE,				"buffer_size"				},
+			{ EGL_RED_SIZE,					"red_size"					},
+			{ EGL_GREEN_SIZE,				"green_size"				},
+			{ EGL_BLUE_SIZE,				"blue_size"					},
+			{ EGL_LUMINANCE_SIZE,			"luminance_size"			},
+			{ EGL_ALPHA_SIZE,				"alpha_size"				},
+			{ EGL_ALPHA_MASK_SIZE,			"alpha_mask_size"			},
+			{ EGL_BIND_TO_TEXTURE_RGB,		"bind_to_texture_rgb"		},
+			{ EGL_BIND_TO_TEXTURE_RGBA,		"bind_to_texture_rgba"		},
+			{ EGL_COLOR_BUFFER_TYPE,		"color_buffer_type"			},
+			{ EGL_CONFIG_CAVEAT,			"config_caveat"				},
+			{ EGL_CONFIG_ID,				"config_id"					},
+			{ EGL_CONFORMANT,				"conformant"				},
+			{ EGL_DEPTH_SIZE,				"depth_size"				},
+			{ EGL_LEVEL,					"level"						},
+			{ EGL_MAX_SWAP_INTERVAL,		"max_swap_interval"			},
+			{ EGL_MIN_SWAP_INTERVAL,		"min_swap_interval"			},
+			{ EGL_NATIVE_RENDERABLE,		"native_renderable"			},
+			{ EGL_NATIVE_VISUAL_TYPE,		"native_visual_type"		},
+			{ EGL_RENDERABLE_TYPE,			"renderable_type"			},
+			{ EGL_SAMPLE_BUFFERS,			"sample_buffers"			},
+			{ EGL_SAMPLES,					"samples"					},
+			{ EGL_STENCIL_SIZE,				"stencil_size"				},
+			{ EGL_SURFACE_TYPE,				"surface_type"				},
+			{ EGL_TRANSPARENT_TYPE,			"transparent_type"			},
+			{ EGL_TRANSPARENT_RED_VALUE,	"transparent_red_value"		},
+			{ EGL_TRANSPARENT_GREEN_VALUE,	"transparent_green_value"	},
+			{ EGL_TRANSPARENT_BLUE_VALUE,	"transparent_blue_value"	}
+		};
+
+		tcu::TestCaseGroup* simpleGroup = new tcu::TestCaseGroup(m_testCtx, "simple", "Simple tests");
+		addChild(simpleGroup);
+
+		tcu::TestCaseGroup* selectionGroup = new tcu::TestCaseGroup(m_testCtx, "selection_only", "Selection tests, order ignored");
+		simpleGroup->addChild(selectionGroup);
+
+		tcu::TestCaseGroup* sortGroup = new tcu::TestCaseGroup(m_testCtx, "selection_and_sort", "Selection and ordering tests");
+		simpleGroup->addChild(sortGroup);
+
+		for (int ndx = 0; ndx < (int)DE_LENGTH_OF_ARRAY(attributes); ndx++)
+		{
+			selectionGroup->addChild(new ChooseConfigSimpleCase(m_eglTestCtx, attributes[ndx].testName, "Simple config selection case", attributes[ndx].attribute, false));
+			sortGroup->addChild(new ChooseConfigSimpleCase(m_eglTestCtx, attributes[ndx].testName, "Simple config selection and sort case", attributes[ndx].attribute, true));
+		}
+	}
+
+	// Random
+	{
+		tcu::TestCaseGroup* randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Random eglChooseConfig() usage");
+		addChild(randomGroup);
+
+		static const EGLenum rgbaSizes[] =
+		{
+			EGL_RED_SIZE,
+			EGL_GREEN_SIZE,
+			EGL_BLUE_SIZE,
+			EGL_ALPHA_SIZE
+		};
+		randomGroup->addChild(new ChooseConfigRandomCase(m_eglTestCtx, "color_sizes", "Random color size rules", toSet(rgbaSizes)));
+
+		static const EGLenum colorDepthStencilSizes[] =
+		{
+			EGL_RED_SIZE,
+			EGL_GREEN_SIZE,
+			EGL_BLUE_SIZE,
+			EGL_ALPHA_SIZE,
+			EGL_DEPTH_SIZE,
+			EGL_STENCIL_SIZE
+		};
+		randomGroup->addChild(new ChooseConfigRandomCase(m_eglTestCtx, "color_depth_stencil_sizes", "Random color, depth and stencil size rules", toSet(colorDepthStencilSizes)));
+
+		static const EGLenum bufferSizes[] =
+		{
+			EGL_BUFFER_SIZE,
+			EGL_LUMINANCE_SIZE,
+			EGL_ALPHA_MASK_SIZE,
+			EGL_DEPTH_SIZE,
+			EGL_STENCIL_SIZE
+		};
+		randomGroup->addChild(new ChooseConfigRandomCase(m_eglTestCtx, "buffer_sizes", "Various buffer size rules", toSet(bufferSizes)));
+
+		static const EGLenum surfaceType[] =
+		{
+			EGL_NATIVE_RENDERABLE,
+			EGL_SURFACE_TYPE
+		};
+		randomGroup->addChild(new ChooseConfigRandomCase(m_eglTestCtx, "surface_type", "Surface type rules", toSet(surfaceType)));
+
+		static const EGLenum sampleBuffers[] =
+		{
+			EGL_SAMPLE_BUFFERS,
+			EGL_SAMPLES
+		};
+		randomGroup->addChild(new ChooseConfigRandomCase(m_eglTestCtx, "sample_buffers", "Sample buffer rules", toSet(sampleBuffers)));
+
+		// \note Not every attribute is supported at the moment
+		static const EGLenum allAttribs[] =
+		{
+			EGL_BUFFER_SIZE,
+			EGL_RED_SIZE,
+			EGL_GREEN_SIZE,
+			EGL_BLUE_SIZE,
+			EGL_ALPHA_SIZE,
+			EGL_ALPHA_MASK_SIZE,
+			EGL_BIND_TO_TEXTURE_RGB,
+			EGL_BIND_TO_TEXTURE_RGBA,
+			EGL_COLOR_BUFFER_TYPE,
+			EGL_CONFIG_CAVEAT,
+			EGL_CONFIG_ID,
+			EGL_CONFORMANT,
+			EGL_DEPTH_SIZE,
+			EGL_LEVEL,
+//			EGL_MATCH_NATIVE_PIXMAP,
+			EGL_MAX_SWAP_INTERVAL,
+			EGL_MIN_SWAP_INTERVAL,
+			EGL_NATIVE_RENDERABLE,
+			EGL_NATIVE_VISUAL_TYPE,
+			EGL_RENDERABLE_TYPE,
+			EGL_SAMPLE_BUFFERS,
+			EGL_SAMPLES,
+			EGL_STENCIL_SIZE,
+			EGL_SURFACE_TYPE,
+			EGL_TRANSPARENT_TYPE,
+//			EGL_TRANSPARENT_RED_VALUE,
+//			EGL_TRANSPARENT_GREEN_VALUE,
+//			EGL_TRANSPARENT_BLUE_VALUE
+		};
+		randomGroup->addChild(new ChooseConfigRandomCase(m_eglTestCtx, "all", "All attributes", toSet(allAttribs)));
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglChooseConfigTests.hpp b/modules/egl/teglChooseConfigTests.hpp
new file mode 100644
index 0000000..9201fa9
--- /dev/null
+++ b/modules/egl/teglChooseConfigTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLCHOOSECONFIGTESTS_HPP
+#define _TEGLCHOOSECONFIGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Choose config tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class ChooseConfigTests : public TestCaseGroup
+{
+public:
+							ChooseConfigTests			(EglTestContext& eglTestCtx);
+	virtual					~ChooseConfigTests			(void);
+
+	void					init						(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLCHOOSECONFIGTESTS_HPP
diff --git a/modules/egl/teglClientExtensionTests.cpp b/modules/egl/teglClientExtensionTests.cpp
new file mode 100644
index 0000000..e089cac
--- /dev/null
+++ b/modules/egl/teglClientExtensionTests.cpp
@@ -0,0 +1,321 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL_EXT_client_extensions tests
+ *//*--------------------------------------------------------------------*/
+
+#include "teglClientExtensionTests.hpp"
+
+#include "tcuTestLog.hpp"
+
+#include <EGL/egl.h>
+
+#include <vector>
+#include <set>
+#include <string>
+#include <sstream>
+
+using std::string;
+using std::vector;
+using std::set;
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+namespace
+{
+
+static const char* const s_displayExtensionList[] =
+{
+	"EGL_KHR_config_attribs",
+	"EGL_KHR_lock_surface",
+	"EGL_KHR_image",
+	"EGL_KHR_vg_parent_image",
+	"EGL_KHR_gl_texture_2D_image",
+	"EGL_KHR_gl_texture_cubemap_image",
+	"EGL_KHR_gl_texture_3D_image",
+	"EGL_KHR_gl_renderbuffer_image",
+	"EGL_KHR_reusable_sync",
+	"EGL_KHR_image_base",
+	"EGL_KHR_image_pixmap",
+	"EGL_IMG_context_priority",
+	"EGL_KHR_lock_surface2",
+	"EGL_NV_coverage_sample",
+	"EGL_NV_depth_nonlinear",
+	"EGL_NV_sync",
+	"EGL_KHR_fence_sync",
+	"EGL_HI_clientpixmap",
+	"EGL_HI_colorformats",
+	"EGL_MESA_drm_image",
+	"EGL_NV_post_sub_buffer",
+	"EGL_ANGLE_query_surface_pointer",
+	"EGL_ANGLE_surface_d3d_texture_2d_share_handle",
+	"EGL_NV_coverage_sample_resolve",
+//	"EGL_NV_system_time",	\todo [mika] Unclear which one this is
+	"EGL_KHR_stream",
+	"EGL_KHR_stream_consumer_gltexture",
+	"EGL_KHR_stream_producer_eglsurface",
+	"EGL_KHR_stream_producer_aldatalocator",
+	"EGL_KHR_stream_fifo",
+	"EGL_EXT_create_context_robustness",
+	"EGL_ANGLE_d3d_share_handle_client_buffer",
+	"EGL_KHR_create_context",
+	"EGL_KHR_surfaceless_context",
+	"EGL_KHR_stream_cross_process_fd",
+	"EGL_EXT_multiview_window",
+	"EGL_KHR_wait_sync",
+	"EGL_NV_post_convert_rounding",
+	"EGL_NV_native_query",
+	"EGL_NV_3dvision_surface",
+	"EGL_ANDROID_framebuffer_target",
+	"EGL_ANDROID_blob_cache",
+	"EGL_ANDROID_image_native_buffer",
+	"EGL_ANDROID_native_fence_sync",
+	"EGL_ANDROID_recordable",
+	"EGL_EXT_buffer_age",
+	"EGL_EXT_image_dma_buf_import",
+	"EGL_ARM_pixmap_multisample_discard",
+	"EGL_EXT_swap_buffers_with_damage",
+	"EGL_NV_stream_sync",
+	"EGL_KHR_cl_event",
+	"EGL_KHR_get_all_proc_addresses"
+};
+
+static const char* const s_clientExtensionList[] =
+{
+	"EGL_EXT_platform_base",
+	"EGL_EXT_client_extensions",
+	"EGL_EXT_platform_x11",
+	"EGL_KHR_client_get_all_proc_addresses",
+	"EGL_MESA_platform_gbm",
+	"EGL_EXT_platform_wayland"
+};
+
+void splitExtensions (vector<string>& extensions, const char* str)
+{
+	std::istringstream	stream(str);
+	string				extension;
+
+	extensions.clear();
+
+	while (std::getline(stream, extension, ' '))
+		extensions.push_back(extension);
+}
+
+class BaseTest : public TestCase
+{
+public:
+					BaseTest	(EglTestContext& eglTestCtx);
+	IterateResult	iterate		(void);
+};
+
+BaseTest::BaseTest (EglTestContext& eglTestCtx)
+	: TestCase(eglTestCtx, "base", "Basic tests for EGL_EXT_client_extensions")
+{
+}
+
+TestCase::IterateResult BaseTest::iterate (void)
+{
+	const char* const	clientExtesionsStr	= eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+	const EGLint		eglError			= eglGetError();
+
+	if (eglError == EGL_BAD_DISPLAY)
+		throw tcu::NotSupportedError("EGL_EXT_client_extensions not supported", "", __FILE__, __LINE__);
+	else if (eglError != EGL_SUCCESS)
+		throw eglu::Error(eglError, "eglQueryString()", "", __FILE__, __LINE__);
+
+	TCU_CHECK(clientExtesionsStr);
+
+	{
+		bool				found = false;
+		std::istringstream	stream(clientExtesionsStr);
+		string				extension;
+
+		while (std::getline(stream, extension, ' '))
+		{
+			if (extension == "EGL_EXT_client_extensions")
+			{
+				found = true;
+				break;
+			}
+		}
+
+		if (found)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+		{
+			m_testCtx.getLog() << TestLog::Message << "eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS) didn't fail, but extension string doesn't contain EGL_EXT_client_extensions" <<TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		}
+	}
+
+	return STOP;
+}
+
+class CheckExtensionsTest : public TestCase
+{
+public:
+					CheckExtensionsTest	(EglTestContext& eglTestCtx);
+	IterateResult	iterate				(void);
+};
+
+CheckExtensionsTest::CheckExtensionsTest (EglTestContext& eglTestCtx)
+	: TestCase(eglTestCtx, "extensions", "Check that returned extensions are client or display extensions")
+{
+}
+
+TestCase::IterateResult CheckExtensionsTest::iterate (void)
+{
+	bool				isOk				= true;
+	const char* const	clientExtesionsStr	= eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+	const EGLint		eglQueryError		= eglGetError();
+
+	set<string>		knownClientExtensions(s_clientExtensionList, s_clientExtensionList + DE_LENGTH_OF_ARRAY(s_clientExtensionList));
+	set<string>		knownDisplayExtensions(s_displayExtensionList, s_displayExtensionList + DE_LENGTH_OF_ARRAY(s_displayExtensionList));
+
+	vector<string>	displayExtensions;
+	vector<string>	clientExtensions;
+
+	if (eglQueryError == EGL_BAD_DISPLAY)
+		throw tcu::NotSupportedError("EGL_EXT_client_extensions not supported", "", __FILE__, __LINE__);
+	else if (eglQueryError != EGL_SUCCESS)
+		throw eglu::Error(eglQueryError, "eglQueryString()", "", __FILE__, __LINE__);
+
+	TCU_CHECK(clientExtesionsStr);
+
+	splitExtensions(clientExtensions, clientExtesionsStr);
+	m_eglTestCtx.getDisplay().getExtensions(displayExtensions);
+
+	for (int extNdx = 0; extNdx < (int)clientExtensions.size(); extNdx++)
+	{
+		if (knownDisplayExtensions.find(clientExtensions[extNdx]) != knownDisplayExtensions.end())
+		{
+			m_testCtx.getLog() << TestLog::Message << "'" << clientExtensions[extNdx] << "' is not client extension" << TestLog::EndMessage;
+			isOk = false;
+		}
+	}
+
+	for (int extNdx = 0; extNdx < (int)displayExtensions.size(); extNdx++)
+	{
+		if (knownClientExtensions.find(displayExtensions[extNdx]) != knownClientExtensions.end())
+		{
+			m_testCtx.getLog() << TestLog::Message << "'" << displayExtensions[extNdx] << "' is not display extension" << TestLog::EndMessage;
+			isOk = false;
+		}
+	}
+
+	if (isOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	return STOP;
+}
+
+
+class DisjointTest : public TestCase
+{
+public:
+					DisjointTest	(EglTestContext& eglTestCtx);
+	IterateResult	iterate			(void);
+};
+
+DisjointTest::DisjointTest (EglTestContext& eglTestCtx)
+	: TestCase(eglTestCtx, "disjoint", "Check that client and display extensions are disjoint")
+{
+}
+
+TestCase::IterateResult DisjointTest::iterate (void)
+{
+	const char*	const	clientExtesionsStr	= eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+	const EGLint		eglQueryError		= eglGetError();
+
+	if (eglQueryError == EGL_BAD_DISPLAY)
+		throw tcu::NotSupportedError("EGL_EXT_client_extensions not supported", "", __FILE__, __LINE__);
+	else if (eglQueryError != EGL_SUCCESS)
+		throw eglu::Error(eglQueryError, "eglQueryString()", "", __FILE__, __LINE__);
+
+	vector<string> displayExtensions;
+	vector<string> clientExtensions;
+
+	splitExtensions(clientExtensions, clientExtesionsStr);
+	m_eglTestCtx.getDisplay().getExtensions(displayExtensions);
+
+	// Log client extensions
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "Client extensions", "Client extensions");
+
+		for (int extNdx = 0; extNdx < (int)clientExtensions.size(); extNdx++)
+			m_testCtx.getLog() << TestLog::Message << clientExtensions[extNdx] << TestLog::EndMessage;
+	}
+
+	// Log display extensions
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "Display extensions", "Display extensions");
+
+		for (int extNdx = 0; extNdx < (int)displayExtensions.size(); extNdx++)
+			m_testCtx.getLog() << TestLog::Message << displayExtensions[extNdx] << TestLog::EndMessage;
+	}
+
+	// Check that sets are disjoint
+	{
+		set<string>			commonExtensionSet;
+		const set<string>	clientExtensionSet(clientExtensions.begin(), clientExtensions.end());
+		const set<string>	displayExtensionSet(displayExtensions.begin(), displayExtensions.end());
+
+		for (set<string>::const_iterator iter = clientExtensionSet.begin(); iter != clientExtensionSet.end(); ++iter)
+		{
+			if (displayExtensionSet.find(*iter) != displayExtensionSet.end())
+				commonExtensionSet.insert(*iter);
+		}
+
+		for (set<string>::const_iterator iter = commonExtensionSet.begin(); iter != commonExtensionSet.end(); ++iter)
+			m_testCtx.getLog() << TestLog::Message << "Extension '" << *iter << "' exists in client and display extension sets." << TestLog::EndMessage;
+
+		if (commonExtensionSet.empty())
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+		{
+			m_testCtx.getLog() << TestLog::Message << "Extension sets are not disjoint" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		}
+	}
+
+	return STOP;
+}
+
+} // anonymous
+
+ClientExtensionTests::ClientExtensionTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "client_extensions", "Test for EGL_EXT_client_extensions")
+{
+}
+
+void ClientExtensionTests::init (void)
+{
+	addChild(new BaseTest(m_eglTestCtx));
+	addChild(new DisjointTest(m_eglTestCtx));
+	addChild(new CheckExtensionsTest(m_eglTestCtx));
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglClientExtensionTests.hpp b/modules/egl/teglClientExtensionTests.hpp
new file mode 100644
index 0000000..75ca923
--- /dev/null
+++ b/modules/egl/teglClientExtensionTests.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLCLIENTEXTENSIONTESTS_HPP
+#define _TEGLCLIENTEXTENSIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL_EXT_client_extensions tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class ClientExtensionTests : public TestCaseGroup
+{
+public:
+			ClientExtensionTests	(EglTestContext& eglTestCtx);
+	void	init					(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLCLIENTEXTENSIONTESTS_HPP
diff --git a/modules/egl/teglColorClearCase.cpp b/modules/egl/teglColorClearCase.cpp
new file mode 100644
index 0000000..e280997
--- /dev/null
+++ b/modules/egl/teglColorClearCase.cpp
@@ -0,0 +1,393 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Color clear case.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglColorClearCase.hpp"
+#include "tcuTestLog.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+#include "tcuImageCompare.hpp"
+#include "tcuVector.hpp"
+#include "tcuTextureUtil.hpp"
+#include "deThread.hpp"
+#include "deSemaphore.hpp"
+#include "deSharedPtr.hpp"
+#include "teglGLES1RenderUtil.hpp"
+#include "teglGLES2RenderUtil.hpp"
+#include "teglVGRenderUtil.hpp"
+
+#include <memory>
+#include <iterator>
+
+#include <EGL/eglext.h>
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#	define EGL_OPENGL_ES3_BIT_KHR	0x0040
+#endif
+#if !defined(EGL_CONTEXT_MAJOR_VERSION_KHR)
+#	define EGL_CONTEXT_MAJOR_VERSION_KHR EGL_CONTEXT_CLIENT_VERSION
+#endif
+
+using tcu::TestLog;
+using tcu::RGBA;
+
+using std::vector;
+
+namespace deqp
+{
+namespace egl
+{
+
+// Utilities.
+
+struct ClearOp
+{
+	ClearOp (int x_, int y_, int width_, int height_, const tcu::RGBA& color_)
+		: x			(x_)
+		, y			(y_)
+		, width		(width_)
+		, height	(height_)
+		, color		(color_)
+	{
+	}
+
+	ClearOp (void)
+		: x			(0)
+		, y			(0)
+		, width		(0)
+		, height	(0)
+		, color		(0)
+	{
+	}
+
+	int			x;
+	int			y;
+	int			width;
+	int			height;
+	tcu::RGBA	color;
+};
+
+static ClearOp computeRandomClear (de::Random& rnd, int width, int height)
+{
+	int			w		= rnd.getInt(1, width);
+	int			h		= rnd.getInt(1, height);
+	int			x		= rnd.getInt(0, width-w);
+	int			y		= rnd.getInt(0, height-h);
+	tcu::RGBA	col		(rnd.getUint32());
+
+	return ClearOp(x, y, w, h, col);
+}
+
+static void renderReference (tcu::Surface& dst, const vector<ClearOp>& clears, const tcu::PixelFormat& pixelFormat)
+{
+	for (vector<ClearOp>::const_iterator clearIter = clears.begin(); clearIter != clears.end(); clearIter++)
+	{
+		tcu::PixelBufferAccess access = tcu::getSubregion(dst.getAccess(), clearIter->x, clearIter->y, 0, clearIter->width, clearIter->height, 1);
+		tcu::clear(access, pixelFormat.convertColor(clearIter->color).toIVec());
+	}
+}
+
+static void renderClear (EGLint api, const ClearOp& clear)
+{
+	switch (api)
+	{
+		case EGL_OPENGL_ES_BIT:			gles1::clear(clear.x, clear.y, clear.width, clear.height, clear.color.toVec());	break;
+		case EGL_OPENGL_ES2_BIT:		gles2::clear(clear.x, clear.y, clear.width, clear.height, clear.color.toVec());	break;
+		case EGL_OPENGL_ES3_BIT_KHR:	gles2::clear(clear.x, clear.y, clear.width, clear.height, clear.color.toVec());	break;
+		case EGL_OPENVG_BIT:			vg::clear	(clear.x, clear.y, clear.width, clear.height, clear.color.toVec());	break;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+static void readPixels (EGLint api, tcu::Surface& dst)
+{
+	switch (api)
+	{
+		case EGL_OPENGL_ES_BIT:			gles1::readPixels	(dst, 0, 0, dst.getWidth(), dst.getHeight());	break;
+		case EGL_OPENGL_ES2_BIT:		gles2::readPixels	(dst, 0, 0, dst.getWidth(), dst.getHeight());	break;
+		case EGL_OPENGL_ES3_BIT_KHR:	gles2::readPixels	(dst, 0, 0, dst.getWidth(), dst.getHeight());	break;
+		case EGL_OPENVG_BIT:			vg::readPixels		(dst, 0, 0, dst.getWidth(), dst.getHeight());	break;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+// SingleThreadColorClearCase
+
+SingleThreadColorClearCase::SingleThreadColorClearCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const std::vector<EGLint>& configIds, int numContextsPerApi)
+	: MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, configIds, numContextsPerApi)
+{
+}
+
+void SingleThreadColorClearCase::executeForContexts (tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config, const std::vector<std::pair<EGLint, tcu::egl::Context*> >& contexts)
+{
+	int					width		= surface.getWidth();
+	int					height		= surface.getHeight();
+
+	TestLog&			log			= m_testCtx.getLog();
+
+	tcu::Surface		refFrame	(width, height);
+	tcu::Surface		frame		(width, height);
+	tcu::PixelFormat	pixelFmt;
+
+	de::Random			rnd			(deStringHash(getName()));
+	vector<ClearOp>		clears;
+	const int			ctxClears	= 2;
+	const int			numIters	= 3;
+
+	// Query pixel format.
+	display.describeConfig(config, pixelFmt);
+
+	// Clear to black using first context.
+	{
+		EGLint				api			= contexts[0].first;
+		tcu::egl::Context*	context		= contexts[0].second;
+		ClearOp				clear		(0, 0, width, height, RGBA::black);
+
+		eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+		TCU_CHECK_EGL();
+
+		renderClear(api, clear);
+		clears.push_back(clear);
+	}
+
+	// Render.
+	for (int iterNdx = 0; iterNdx < numIters; iterNdx++)
+	{
+		for (vector<std::pair<EGLint, tcu::egl::Context*> >::const_iterator ctxIter = contexts.begin(); ctxIter != contexts.end(); ctxIter++)
+		{
+			EGLint				api			= ctxIter->first;
+			tcu::egl::Context*	context		= ctxIter->second;
+
+			eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+			TCU_CHECK_EGL();
+
+			for (int clearNdx = 0; clearNdx < ctxClears; clearNdx++)
+			{
+				ClearOp clear = computeRandomClear(rnd, width, height);
+
+				renderClear(api, clear);
+				clears.push_back(clear);
+			}
+		}
+	}
+
+	// Read pixels using first context. \todo [pyry] Randomize?
+	{
+		EGLint				api		= contexts[0].first;
+		tcu::egl::Context*	context	= contexts[0].second;
+
+		eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+		TCU_CHECK_EGL();
+
+		readPixels(api, frame);
+	}
+
+	// Render reference.
+	renderReference(refFrame, clears, pixelFmt);
+
+	// Compare images
+	{
+		bool imagesOk = tcu::pixelThresholdCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, RGBA(1,1,1,1) + pixelFmt.getColorThreshold(), tcu::COMPARE_LOG_RESULT);
+
+		if (!imagesOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+	}
+}
+
+// MultiThreadColorClearCase
+
+enum
+{
+	NUM_CLEARS_PER_PACKET	= 2 //!< Number of clears performed in one context activation in one thread.
+};
+
+class ColorClearThread;
+
+typedef de::SharedPtr<ColorClearThread>	ColorClearThreadSp;
+typedef de::SharedPtr<de::Semaphore>	SemaphoreSp;
+
+struct ClearPacket
+{
+	ClearPacket (void)
+	{
+	}
+
+	ClearOp			clears[NUM_CLEARS_PER_PACKET];
+	SemaphoreSp		wait;
+	SemaphoreSp		signal;
+};
+
+class ColorClearThread : public de::Thread
+{
+public:
+	ColorClearThread (tcu::egl::Display& display, tcu::egl::Surface& surface, tcu::egl::Context& context, EGLint api, const std::vector<ClearPacket>& packets)
+		: m_display	(display)
+		, m_surface	(surface)
+		, m_context	(context)
+		, m_api		(api)
+		, m_packets	(packets)
+	{
+	}
+
+	void run (void)
+	{
+		for (std::vector<ClearPacket>::const_iterator packetIter = m_packets.begin(); packetIter != m_packets.end(); packetIter++)
+		{
+			// Wait until it is our turn.
+			packetIter->wait->decrement();
+
+			// Acquire context.
+			eglMakeCurrent(m_display.getEGLDisplay(), m_surface.getEGLSurface(), m_surface.getEGLSurface(), m_context.getEGLContext());
+
+			// Execute clears.
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(packetIter->clears); ndx++)
+				renderClear(m_api, packetIter->clears[ndx]);
+
+			// Release context.
+			eglMakeCurrent(m_display.getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+			// Signal completion.
+			packetIter->signal->increment();
+		}
+	}
+
+private:
+	tcu::egl::Display&				m_display;
+	tcu::egl::Surface&				m_surface;
+	tcu::egl::Context&				m_context;
+	EGLint							m_api;
+	const std::vector<ClearPacket>&	m_packets;
+};
+
+MultiThreadColorClearCase::MultiThreadColorClearCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const std::vector<EGLint>& configIds, int numContextsPerApi)
+	: MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, configIds, numContextsPerApi)
+{
+}
+
+void MultiThreadColorClearCase::executeForContexts (tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config, const std::vector<std::pair<EGLint, tcu::egl::Context*> >& contexts)
+{
+	int					width		= surface.getWidth();
+	int					height		= surface.getHeight();
+
+	TestLog&			log			= m_testCtx.getLog();
+
+	tcu::Surface		refFrame	(width, height);
+	tcu::Surface		frame		(width, height);
+	tcu::PixelFormat	pixelFmt;
+
+	de::Random			rnd			(deStringHash(getName()));
+
+	// Query pixel format.
+	display.describeConfig(config, pixelFmt);
+
+	// Create clear packets.
+	const int						numPacketsPerThread		= 2;
+	int								numThreads				= (int)contexts.size();
+	int								numPackets				= numThreads * numPacketsPerThread;
+
+	vector<SemaphoreSp>				semaphores				(numPackets+1);
+	vector<vector<ClearPacket> >	packets					(numThreads);
+	vector<ColorClearThreadSp>		threads					(numThreads);
+
+	// Initialize semaphores.
+	for (vector<SemaphoreSp>::iterator sem = semaphores.begin(); sem != semaphores.end(); ++sem)
+		*sem = SemaphoreSp(new de::Semaphore(0));
+
+	// Create packets.
+	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
+	{
+		packets[threadNdx].resize(numPacketsPerThread);
+
+		for (int packetNdx = 0; packetNdx < numPacketsPerThread; packetNdx++)
+		{
+			ClearPacket& packet = packets[threadNdx][packetNdx];
+
+			// Threads take turns with packets.
+			packet.wait		= semaphores[packetNdx*numThreads + threadNdx];
+			packet.signal	= semaphores[packetNdx*numThreads + threadNdx + 1];
+
+			for (int clearNdx = 0; clearNdx < DE_LENGTH_OF_ARRAY(packet.clears); clearNdx++)
+			{
+				// First clear is always full-screen black.
+				if (threadNdx == 0 && packetNdx == 0 && clearNdx == 0)
+					packet.clears[clearNdx] = ClearOp(0, 0, width, height, RGBA::black);
+				else
+					packet.clears[clearNdx] = computeRandomClear(rnd, width, height);
+			}
+		}
+	}
+
+	// Create and launch threads (actual rendering starts once first semaphore is signaled).
+	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
+	{
+		threads[threadNdx] = ColorClearThreadSp(new ColorClearThread(display, surface, *contexts[threadNdx].second, contexts[threadNdx].first, packets[threadNdx]));
+		threads[threadNdx]->start();
+	}
+
+	// Signal start and wait until complete.
+	semaphores.front()->increment();
+	semaphores.back()->decrement();
+
+	// Read pixels using first context. \todo [pyry] Randomize?
+	{
+		EGLint				api		= contexts[0].first;
+		tcu::egl::Context*	context	= contexts[0].second;
+
+		eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+		TCU_CHECK_EGL();
+
+		readPixels(api, frame);
+	}
+
+	// Join threads.
+	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
+		threads[threadNdx]->join();
+
+	// Render reference.
+	for (int packetNdx = 0; packetNdx < numPacketsPerThread; packetNdx++)
+	{
+		for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
+		{
+			const ClearPacket& packet = packets[threadNdx][packetNdx];
+			for (int clearNdx = 0; clearNdx < DE_LENGTH_OF_ARRAY(packet.clears); clearNdx++)
+			{
+				tcu::PixelBufferAccess access = tcu::getSubregion(refFrame.getAccess(),
+																  packet.clears[clearNdx].x, packet.clears[clearNdx].y, 0,
+																  packet.clears[clearNdx].width, packet.clears[clearNdx].height, 1);
+				tcu::clear(access, pixelFmt.convertColor(packet.clears[clearNdx].color).toIVec());
+			}
+		}
+	}
+
+	// Compare images
+	{
+		bool imagesOk = tcu::pixelThresholdCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, RGBA(1,1,1,1) + pixelFmt.getColorThreshold(), tcu::COMPARE_LOG_RESULT);
+
+		if (!imagesOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglColorClearCase.hpp b/modules/egl/teglColorClearCase.hpp
new file mode 100644
index 0000000..08b7bf4
--- /dev/null
+++ b/modules/egl/teglColorClearCase.hpp
@@ -0,0 +1,58 @@
+#ifndef _TEGLCOLORCLEARCASE_HPP
+#define _TEGLCOLORCLEARCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Color clear case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+#include "teglRenderCase.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace egl
+{
+
+class SingleThreadColorClearCase : public MultiContextRenderCase
+{
+public:
+						SingleThreadColorClearCase		(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const std::vector<EGLint>& configIds, int numContextsPerApi);
+
+private:
+	virtual void		executeForContexts				(tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config, const std::vector<std::pair<EGLint, tcu::egl::Context*> >& contexts);
+};
+
+class MultiThreadColorClearCase : public MultiContextRenderCase
+{
+public:
+						MultiThreadColorClearCase		(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const std::vector<EGLint>& configIds, int numContextsPerApi);
+
+private:
+	virtual void		executeForContexts				(tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config, const std::vector<std::pair<EGLint, tcu::egl::Context*> >& contexts);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLCOLORCLEARCASE_HPP
diff --git a/modules/egl/teglColorClearTests.cpp b/modules/egl/teglColorClearTests.cpp
new file mode 100644
index 0000000..e8c424f
--- /dev/null
+++ b/modules/egl/teglColorClearTests.cpp
@@ -0,0 +1,117 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Color clear tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglColorClearTests.hpp"
+#include "teglColorClearCase.hpp"
+
+#include <EGL/eglext.h>
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#	define EGL_OPENGL_ES3_BIT_KHR	0x0040
+#endif
+#if !defined(EGL_CONTEXT_MAJOR_VERSION_KHR)
+#	define EGL_CONTEXT_MAJOR_VERSION_KHR EGL_CONTEXT_CLIENT_VERSION
+#endif
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace egl
+{
+
+ColorClearTests::ColorClearTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "color_clears", "Color clears with different client APIs")
+{
+}
+
+ColorClearTests::~ColorClearTests (void)
+{
+}
+
+struct ColorClearGroupSpec
+{
+	const char*		name;
+	const char*		desc;
+	EGLint			apiBits;
+	int				numContextsPerApi;
+};
+
+template <class ClearClass>
+static void createColorClearGroups (EglTestContext& eglTestCtx, tcu::TestCaseGroup* group, const ColorClearGroupSpec* first, const ColorClearGroupSpec* last)
+{
+	for (const ColorClearGroupSpec* groupIter = first; groupIter != last; groupIter++)
+	{
+		tcu::TestCaseGroup* configGroup = new tcu::TestCaseGroup(eglTestCtx.getTestContext(), groupIter->name, groupIter->desc);
+		group->addChild(configGroup);
+
+		vector<RenderConfigIdSet>	configSets;
+		eglu::FilterList			filters;
+		filters << (eglu::ConfigRenderableType() & groupIter->apiBits);
+		getDefaultRenderConfigIdSets(configSets, eglTestCtx.getConfigs(), filters);
+
+		for (vector<RenderConfigIdSet>::const_iterator setIter = configSets.begin(); setIter != configSets.end(); setIter++)
+			configGroup->addChild(new ClearClass(eglTestCtx, setIter->getName(), "", groupIter->apiBits, setIter->getSurfaceTypeMask(), setIter->getConfigIds(), groupIter->numContextsPerApi));
+	}
+}
+
+void ColorClearTests::init (void)
+{
+	static const ColorClearGroupSpec singleContextCases[] =
+	{
+		{ "gles1",			"Color clears using GLES1",											EGL_OPENGL_ES_BIT,										1 },
+		{ "gles2",			"Color clears using GLES2",											EGL_OPENGL_ES2_BIT,										1 },
+		{ "gles3",			"Color clears using GLES3",											EGL_OPENGL_ES3_BIT_KHR,									1 },
+		{ "vg",				"Color clears using OpenVG",										EGL_OPENVG_BIT,											1 }
+	};
+
+	static const ColorClearGroupSpec multiContextCases[] =
+	{
+		{ "gles1",				"Color clears using multiple GLES1 contexts to shared surface",		EGL_OPENGL_ES_BIT,												3 },
+		{ "gles2",				"Color clears using multiple GLES2 contexts to shared surface",		EGL_OPENGL_ES2_BIT,												3 },
+		{ "gles3",				"Color clears using multiple GLES3 contexts to shared surface",		EGL_OPENGL_ES3_BIT_KHR,											3 },
+		{ "vg",					"Color clears using multiple OpenVG contexts to shared surface",	EGL_OPENVG_BIT,													3 },
+		{ "gles1_gles2",		"Color clears using multiple APIs to shared surface",				EGL_OPENGL_ES_BIT|EGL_OPENGL_ES2_BIT,							1 },
+		{ "gles1_gles2_gles3",	"Color clears using multiple APIs to shared surface",				EGL_OPENGL_ES_BIT|EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT_KHR,	1 },
+		{ "gles1_vg",			"Color clears using multiple APIs to shared surface",				EGL_OPENGL_ES_BIT|EGL_OPENVG_BIT,								1 },
+		{ "gles2_vg",			"Color clears using multiple APIs to shared surface",				EGL_OPENGL_ES2_BIT|EGL_OPENVG_BIT,								1 },
+		{ "gles3_vg",			"Color clears using multiple APIs to shared surface",				EGL_OPENGL_ES3_BIT_KHR|EGL_OPENVG_BIT,							1 },
+		{ "gles1_gles2_vg",		"Color clears using multiple APIs to shared surface",				EGL_OPENGL_ES_BIT|EGL_OPENGL_ES2_BIT|EGL_OPENVG_BIT,			1 }
+	};
+
+	tcu::TestCaseGroup* singleContextGroup = new tcu::TestCaseGroup(m_testCtx, "single_context", "Single-context color clears");
+	addChild(singleContextGroup);
+	createColorClearGroups<SingleThreadColorClearCase>(m_eglTestCtx, singleContextGroup, &singleContextCases[0], &singleContextCases[DE_LENGTH_OF_ARRAY(singleContextCases)]);
+
+	tcu::TestCaseGroup* multiContextGroup = new tcu::TestCaseGroup(m_testCtx, "multi_context", "Multi-context color clears with shared surface");
+	addChild(multiContextGroup);
+	createColorClearGroups<SingleThreadColorClearCase>(m_eglTestCtx, multiContextGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]);
+
+	tcu::TestCaseGroup* multiThreadGroup = new tcu::TestCaseGroup(m_testCtx, "multi_thread", "Multi-thread color clears with shared surface");
+	addChild(multiThreadGroup);
+	createColorClearGroups<MultiThreadColorClearCase>(m_eglTestCtx, multiThreadGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]);
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglColorClearTests.hpp b/modules/egl/teglColorClearTests.hpp
new file mode 100644
index 0000000..8c41770
--- /dev/null
+++ b/modules/egl/teglColorClearTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLCOLORCLEARTESTS_HPP
+#define _TEGLCOLORCLEARTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Color clear tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class ColorClearTests : public TestCaseGroup
+{
+public:
+							ColorClearTests				(EglTestContext& eglTestCtx);
+	virtual					~ColorClearTests			(void);
+
+	void					init						(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLCOLORCLEARTESTS_HPP
diff --git a/modules/egl/teglConfigList.cpp b/modules/egl/teglConfigList.cpp
new file mode 100644
index 0000000..2308234
--- /dev/null
+++ b/modules/egl/teglConfigList.cpp
@@ -0,0 +1,185 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL tests
+ *//*--------------------------------------------------------------------*/
+
+#include "teglConfigList.hpp"
+#include "tcuEgl.hpp"
+#include "tcuTestLog.hpp"
+#include "egluStrUtil.hpp"
+#include "deStringUtil.hpp"
+
+#include <vector>
+
+using std::vector;
+
+namespace deqp
+{
+namespace egl
+{
+
+ConfigList::ConfigList (EglTestContext& eglTestCtx)
+	: TestCase(eglTestCtx, "configs", "Output the list of configs from EGL")
+
+{
+}
+
+ConfigList::~ConfigList (void)
+{
+}
+
+void ConfigList::init (void)
+{
+}
+
+void ConfigList::deinit (void)
+{
+}
+
+tcu::TestNode::IterateResult ConfigList::iterate (void)
+{
+	tcu::TestLog&		log			= m_testCtx.getLog();
+	EGLDisplay			display		= m_eglTestCtx.getDisplay().getEGLDisplay();
+	vector<EGLConfig>	configs;
+
+	// \todo [2011-03-23 pyry] Check error codes!
+
+	// \todo [kalle 10/08/2010] Get EGL version.
+
+	m_eglTestCtx.getDisplay().getConfigs(configs);
+
+	log.startEglConfigSet("EGL-configs", "List of all EGL configs");
+
+	// \todo [kalle 10/08/2010] Add validity checks for the values?
+	// \todo [kalle 10/08/2010] Adapt for different EGL versions
+
+	for (int i = 0; i < (int)configs.size(); i++)
+	{
+		qpEglConfigInfo info;
+		EGLint val = 0;
+
+		eglGetConfigAttrib(display, configs[i], EGL_BUFFER_SIZE, &val);
+		info.bufferSize = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_RED_SIZE, &val);
+		info.redSize = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_GREEN_SIZE, &val);
+		info.greenSize = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_BLUE_SIZE, &val);
+		info.blueSize = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_LUMINANCE_SIZE, &val);
+		info.luminanceSize = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_ALPHA_SIZE, &val);
+		info.alphaSize = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_ALPHA_MASK_SIZE, &val);
+		info.alphaMaskSize = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_BIND_TO_TEXTURE_RGB, &val);
+		info.bindToTextureRGB = val == EGL_TRUE ? DE_TRUE : DE_FALSE;
+
+		eglGetConfigAttrib(display, configs[i], EGL_BIND_TO_TEXTURE_RGBA, &val);
+		info.bindToTextureRGBA = val == EGL_TRUE ? DE_TRUE : DE_FALSE;
+
+		eglGetConfigAttrib(display, configs[i], EGL_COLOR_BUFFER_TYPE, &val);
+		std::string colorBufferType = de::toString(eglu::getColorBufferTypeStr(val));
+		info.colorBufferType = colorBufferType.c_str();
+
+		eglGetConfigAttrib(display, configs[i], EGL_CONFIG_CAVEAT, &val);
+		std::string caveat = de::toString(eglu::getConfigCaveatStr(val));
+		info.configCaveat = caveat.c_str();
+
+		eglGetConfigAttrib(display, configs[i], EGL_CONFIG_ID, &val);
+		info.configID = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_CONFORMANT, &val);
+		std::string conformant = de::toString(eglu::getAPIBitsStr(val));
+		info.conformant = conformant.c_str();
+
+		eglGetConfigAttrib(display, configs[i], EGL_DEPTH_SIZE, &val);
+		info.depthSize = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_LEVEL, &val);
+		info.level = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_MAX_PBUFFER_WIDTH, &val);
+		info.maxPBufferWidth = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_MAX_PBUFFER_HEIGHT, &val);
+		info.maxPBufferHeight = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_MAX_PBUFFER_PIXELS, &val);
+		info.maxPBufferPixels = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_MAX_SWAP_INTERVAL, &val);
+		info.maxSwapInterval = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_MIN_SWAP_INTERVAL, &val);
+		info.minSwapInterval = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_NATIVE_RENDERABLE, &val);
+		info.nativeRenderable = val == EGL_TRUE ? DE_TRUE : DE_FALSE;
+
+		eglGetConfigAttrib(display, configs[i], EGL_RENDERABLE_TYPE, &val);
+		std::string renderableTypes = de::toString(eglu::getAPIBitsStr(val));
+		info.renderableType = renderableTypes.c_str();
+
+		eglGetConfigAttrib(display, configs[i], EGL_SAMPLE_BUFFERS, &val);
+		info.sampleBuffers = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_SAMPLES, &val);
+		info.samples = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_STENCIL_SIZE, &val);
+		info.stencilSize = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_SURFACE_TYPE, &val);
+		std::string surfaceTypes = de::toString(eglu::getSurfaceBitsStr(val));
+		info.surfaceTypes = surfaceTypes.c_str();
+
+		eglGetConfigAttrib(display, configs[i], EGL_TRANSPARENT_TYPE, &val);
+		std::string transparentType = de::toString(eglu::getTransparentTypeStr(val));
+		info.transparentType = transparentType.c_str();
+
+		eglGetConfigAttrib(display, configs[i], EGL_TRANSPARENT_RED_VALUE, &val);
+		info.transparentRedValue = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_TRANSPARENT_GREEN_VALUE, &val);
+		info.transparentGreenValue = val;
+
+		eglGetConfigAttrib(display, configs[i], EGL_TRANSPARENT_BLUE_VALUE, &val);
+		info.transparentBlueValue = val;
+
+		log.writeEglConfig(&info);
+	}
+	log.endEglConfigSet();
+
+	getTestContext().setTestResult(QP_TEST_RESULT_PASS, "");
+
+	return TestNode::STOP;
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglConfigList.hpp b/modules/egl/teglConfigList.hpp
new file mode 100644
index 0000000..88924de
--- /dev/null
+++ b/modules/egl/teglConfigList.hpp
@@ -0,0 +1,52 @@
+#ifndef _TEGLCONFIGLIST_HPP
+#define _TEGLCONFIGLIST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL tests
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class ConfigList : public TestCase
+{
+public:
+							ConfigList		(EglTestContext& eglTestCtx);
+	virtual					~ConfigList		(void);
+
+	virtual void			init			(void);
+	virtual void			deinit			(void);
+	virtual IterateResult	iterate			(void);
+
+private:
+							ConfigList		(const ConfigList&);		// not allowed!
+	ConfigList&				operator=		(const ConfigList&);		// not allowed!
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLCONFIGLIST_HPP
diff --git a/modules/egl/teglCreateContextExtTests.cpp b/modules/egl/teglCreateContextExtTests.cpp
new file mode 100644
index 0000000..542890e
--- /dev/null
+++ b/modules/egl/teglCreateContextExtTests.cpp
@@ -0,0 +1,1298 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Simple context construction test for EGL_KHR_create_context.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglCreateContextExtTests.hpp"
+
+#include "tcuTestLog.hpp"
+
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluConfigFilter.hpp"
+#include "egluStrUtil.hpp"
+#include "egluUtil.hpp"
+
+#include "gluDefs.hpp"
+#include "gluRenderConfig.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+
+#include <EGL/eglext.h>
+
+#include <string>
+#include <vector>
+#include <set>
+#include <sstream>
+
+#include <cstring>
+
+// \note Taken from official EGL/eglext.h. EGL_EGLEXT_VERSION 20131028
+#ifndef EGL_KHR_create_context
+#define EGL_KHR_create_context 1
+#define EGL_CONTEXT_MAJOR_VERSION_KHR     0x3098
+#define EGL_CONTEXT_MINOR_VERSION_KHR     0x30FB
+#define EGL_CONTEXT_FLAGS_KHR             0x30FC
+#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30FD
+#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR 0x31BD
+#define EGL_NO_RESET_NOTIFICATION_KHR     0x31BE
+#define EGL_LOSE_CONTEXT_ON_RESET_KHR     0x31BF
+#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR  0x00000001
+#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002
+#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004
+#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001
+#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002
+#define EGL_OPENGL_ES3_BIT_KHR            0x00000040
+#endif /* EGL_KHR_create_context */
+
+#ifndef EGL_EXT_create_context_robustness
+#define EGL_EXT_create_context_robustness 1
+#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT 0x30BF
+#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT 0x3138
+#define EGL_NO_RESET_NOTIFICATION_EXT     0x31BE
+#define EGL_LOSE_CONTEXT_ON_RESET_EXT     0x31BF
+#endif /* EGL_EXT_create_context_robustness */
+
+// \note Taken from official GLES2/gl2ext.h. Generated on date 20131202.
+#ifndef GL_EXT_robustness
+#define GL_EXT_robustness 1
+#define GL_GUILTY_CONTEXT_RESET_EXT       0x8253
+#define GL_INNOCENT_CONTEXT_RESET_EXT     0x8254
+#define GL_UNKNOWN_CONTEXT_RESET_EXT      0x8255
+#define GL_CONTEXT_ROBUST_ACCESS_EXT      0x90F3
+#define GL_RESET_NOTIFICATION_STRATEGY_EXT 0x8256
+#define GL_LOSE_CONTEXT_ON_RESET_EXT      0x8252
+#define GL_NO_RESET_NOTIFICATION_EXT      0x8261
+#endif /* GL_EXT_robustness */
+
+#ifndef GL_ARB_robustness
+/* reuse GL_NO_ERROR */
+#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB 0x00000004
+#define GL_LOSE_CONTEXT_ON_RESET_ARB      0x8252
+#define GL_GUILTY_CONTEXT_RESET_ARB       0x8253
+#define GL_INNOCENT_CONTEXT_RESET_ARB     0x8254
+#define GL_UNKNOWN_CONTEXT_RESET_ARB      0x8255
+#define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256
+#define GL_NO_RESET_NOTIFICATION_ARB      0x8261
+#endif
+
+using std::set;
+using std::string;
+using std::vector;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace
+{
+
+size_t getAttribListLength (const EGLint* attribList)
+{
+	size_t size = 0;
+
+	while (attribList[size] != EGL_NONE)
+		size++;
+
+	return size + 1;
+}
+
+string eglContextFlagsToString (EGLint flags)
+{
+	std::ostringstream	stream;
+
+	if (flags == 0)
+		stream << "<None>";
+	else
+	{
+		bool first = true;
+
+		if ((flags & EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR) != 0)
+		{
+			if (!first)
+				stream << "|";
+
+			first = false;
+
+			stream << "EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR";
+		}
+
+		if ((flags & EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR) != 0)
+		{
+			if (!first)
+				stream << "|";
+
+			first = false;
+
+			stream << "EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR";
+		}
+
+		if ((flags & EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR) != 0)
+		{
+			if (!first)
+				stream << "|";
+
+			stream << "EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR";
+		}
+	}
+
+	return stream.str();
+}
+
+string eglProfileMaskToString (EGLint mask)
+{
+	std::ostringstream	stream;
+
+	if (mask == 0)
+		stream << "<None>";
+	else
+	{
+		bool first = true;
+
+		if ((mask & EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR) != 0)
+		{
+			if (!first)
+				stream << "|";
+
+			first = false;
+
+			stream << "EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR";
+		}
+
+		if ((mask & EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR) != 0)
+		{
+			if (!first)
+				stream << "|";
+
+			stream << "EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR";
+		}
+	}
+
+	return stream.str();
+}
+
+const char* eglResetNotificationStrategyToString (EGLint strategy)
+{
+	switch (strategy)
+	{
+		case EGL_NO_RESET_NOTIFICATION_KHR:		return "EGL_NO_RESET_NOTIFICATION_KHR";
+		case EGL_LOSE_CONTEXT_ON_RESET_KHR:		return "EGL_LOSE_CONTEXT_ON_RESET_KHR";
+		default:
+			return "<Unknown>";
+	}
+}
+
+class CreateContextExtCase : public TestCase
+{
+public:
+								CreateContextExtCase	(EglTestContext& eglTestCtx, EGLenum api, const EGLint* attribList, const eglu::FilterList& filter, const char* name, const char* description);
+								~CreateContextExtCase	(void);
+
+	void						executeForConfig		(tcu::egl::Display& display, EGLConfig config, tcu::egl::Surface& surface);
+
+	void						init					(void);
+	void						deinit					(void);
+
+	IterateResult				iterate					(void);
+	void						checkRequiredExtensions	(void);
+	void						logAttribList			(void);
+	bool						validateCurrentContext	(const glw::Functions& gl);
+
+private:
+	bool						m_isOk;
+	int							m_iteration;
+
+	const eglu::FilterList		m_filter;
+	vector<EGLint>				m_attribList;
+	const EGLenum				m_api;
+
+	vector<EGLConfig>			m_configs;
+	glu::ContextType			m_glContextType;
+};
+
+glu::ContextType attribListToContextType (EGLenum api, const EGLint* attribList)
+{
+	EGLint				majorVersion	= 1;
+	EGLint				minorVersion	= 0;
+	glu::ContextFlags	flags			= glu::ContextFlags(0);
+	glu::Profile		profile			= api == EGL_OPENGL_ES_API ? glu::PROFILE_ES : glu::PROFILE_CORE;
+	const EGLint*		iter			= attribList;
+
+	while((*iter) != EGL_NONE)
+	{
+		switch (*iter)
+		{
+			case EGL_CONTEXT_MAJOR_VERSION_KHR:
+				iter++;
+				majorVersion = (*iter);
+				iter++;
+				break;
+
+			case EGL_CONTEXT_MINOR_VERSION_KHR:
+				iter++;
+				minorVersion = (*iter);
+				iter++;
+				break;
+
+			case EGL_CONTEXT_FLAGS_KHR:
+				iter++;
+
+				if ((*iter & EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR) != 0)
+					flags = flags | glu::CONTEXT_ROBUST;
+
+				if ((*iter & EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR) != 0)
+					flags = flags | glu::CONTEXT_DEBUG;
+
+				if ((*iter & EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR) != 0)
+					flags = flags | glu::CONTEXT_FORWARD_COMPATIBLE;
+
+				iter++;
+				break;
+
+			case EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR:
+				iter++;
+
+				if (*iter == EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR)
+					profile = glu::PROFILE_COMPATIBILITY;
+				else if (*iter != EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR)
+					throw tcu::InternalError("Indeterminate OpenGL profile");
+
+				iter++;
+				break;
+
+			case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR:
+				iter += 2;
+				break;
+
+			case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT:
+				iter += 2;
+				break;
+
+			case EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT:
+				iter += 2;
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+
+	return glu::ContextType(majorVersion, minorVersion, profile, flags);
+}
+
+CreateContextExtCase::CreateContextExtCase (EglTestContext& eglTestCtx, EGLenum api, const EGLint* attribList, const eglu::FilterList& filter, const char* name, const char* description)
+	: TestCase			(eglTestCtx, name, description)
+	, m_isOk			(true)
+	, m_iteration		(0)
+	, m_filter			(filter)
+	, m_attribList		(attribList, attribList + getAttribListLength(attribList))
+	, m_api				(api)
+	, m_glContextType	(attribListToContextType(api, attribList))
+{
+}
+
+CreateContextExtCase::~CreateContextExtCase (void)
+{
+	deinit();
+}
+
+void CreateContextExtCase::init (void)
+{
+	vector<EGLConfig> configs;
+	m_eglTestCtx.getDisplay().getConfigs(configs);
+
+	for (int configNdx = 0; configNdx < (int)configs.size(); configNdx++)
+	{
+		if (m_filter.match(m_eglTestCtx.getDisplay().getEGLDisplay(), configs[configNdx]))
+			m_configs.push_back(configs[configNdx]);
+	}
+}
+
+void CreateContextExtCase::deinit (void)
+{
+	m_attribList	= vector<EGLint>();
+	m_configs		= vector<EGLConfig>();
+}
+
+void CreateContextExtCase::logAttribList (void)
+{
+	const EGLint*		iter = &(m_attribList[0]);
+	std::ostringstream	attribListString;
+
+	while ((*iter) != EGL_NONE)
+	{
+		switch (*iter)
+		{
+			case EGL_CONTEXT_MAJOR_VERSION_KHR:
+				iter++;
+				attribListString << "EGL_CONTEXT_MAJOR_VERSION_KHR(EGL_CONTEXT_CLIENT_VERSION), " << (*iter) << ", ";
+				iter++;
+				break;
+
+			case EGL_CONTEXT_MINOR_VERSION_KHR:
+				iter++;
+				attribListString << "EGL_CONTEXT_MINOR_VERSION_KHR, " << (*iter) << ", ";
+				iter++;
+				break;
+
+			case EGL_CONTEXT_FLAGS_KHR:
+				iter++;
+				attribListString << "EGL_CONTEXT_FLAGS_KHR, " << eglContextFlagsToString(*iter) << ", ";
+				iter++;
+				break;
+
+			case EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR:
+				iter++;
+				attribListString << "EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, " << eglProfileMaskToString(*iter) << ", ";
+				iter++;
+				break;
+
+			case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR:
+				iter++;
+				attribListString << "EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, " << eglResetNotificationStrategyToString(*iter) << ", ";
+				iter++;
+				break;
+
+			case EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT:
+				iter++;
+				attribListString << "EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, ";
+
+				if (*iter == EGL_FALSE && *iter == EGL_TRUE)
+					attribListString << (*iter ? "EGL_TRUE" : "EGL_FALSE");
+				else
+					attribListString << (*iter);
+				iter++;
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+
+	attribListString << "EGL_NONE";
+	m_testCtx.getLog() << TestLog::Message << "EGL attrib list: { " << attribListString.str() << " }" << TestLog::EndMessage;
+}
+
+void CreateContextExtCase::checkRequiredExtensions (void)
+{
+	bool			isOk = true;
+	set<string>		requiredExtensions;
+	vector<string>	extensions;
+
+	m_eglTestCtx.getDisplay().getExtensions(extensions);
+
+	{
+		const EGLint* iter = &(m_attribList[0]);
+
+		while ((*iter) != EGL_NONE)
+		{
+			switch (*iter)
+			{
+				case EGL_CONTEXT_MAJOR_VERSION_KHR:
+					iter++;
+					iter++;
+					break;
+
+				case EGL_CONTEXT_MINOR_VERSION_KHR:
+					iter++;
+					requiredExtensions.insert("EGL_KHR_create_context");
+					iter++;
+					break;
+
+				case EGL_CONTEXT_FLAGS_KHR:
+					iter++;
+					requiredExtensions.insert("EGL_KHR_create_context");
+					iter++;
+					break;
+
+				case EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR:
+					iter++;
+					requiredExtensions.insert("EGL_KHR_create_context");
+					iter++;
+					break;
+
+				case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR:
+					iter++;
+					requiredExtensions.insert("EGL_KHR_create_context");
+					iter++;
+					break;
+
+				case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT:
+					iter++;
+					requiredExtensions.insert("EGL_EXT_create_context_robustness");
+					iter++;
+					break;
+
+				case EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT:
+					iter++;
+					requiredExtensions.insert("EGL_EXT_create_context_robustness");
+					iter++;
+					break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+		}
+	}
+
+	for (std::set<string>::const_iterator reqExt = requiredExtensions.begin(); reqExt != requiredExtensions.end(); ++reqExt)
+	{
+		bool found = false;
+
+		for (int extNdx = 0; extNdx < (int)extensions.size(); extNdx++)
+		{
+			if (*reqExt == extensions[extNdx])
+				found = true;
+		}
+
+		if (!found)
+		{
+			m_testCtx.getLog() << TestLog::Message << "Required extension '" << (*reqExt) << "' not supported" << TestLog::EndMessage;
+			isOk = false;
+		}
+	}
+
+	if (!isOk)
+		throw tcu::NotSupportedError("Required extensions not supported", "", __FILE__, __LINE__);
+}
+
+bool hasExtension (const glw::Functions& gl, const char* extension)
+{
+	std::istringstream	stream((const char*)gl.getString(GL_EXTENSIONS));
+	string				ext;
+
+	while (std::getline(stream, ext, ' '))
+	{
+		if (ext == extension)
+			return true;
+	}
+
+	return false;
+}
+
+bool checkVersionString (TestLog& log, const glw::Functions& gl, bool desktop, int major, int minor)
+{
+	const char* const	versionStr	= (const char*)gl.getString(GL_VERSION);
+	const char*			iter		= versionStr;
+
+	int majorVersion = 0;
+	int minorVersion = 0;
+
+	// Check embedded version prefixes
+	if (!desktop)
+	{
+		const char* prefix		= NULL;
+		const char* prefixIter	= NULL;
+
+		if (major == 1)
+			prefix = "OpenGL ES-CM ";
+		else
+			prefix = "OpenGL ES ";
+
+		prefixIter = prefix;
+
+		while (*prefixIter)
+		{
+			if ((*prefixIter) != (*iter))
+			{
+				log << TestLog::Message << "Invalid version string prefix. Expected '" << prefix << "'." << TestLog::EndMessage;
+				return false;
+			}
+
+			prefixIter++;
+			iter++;
+		}
+	}
+
+	while ((*iter) && (*iter) != '.')
+	{
+		const int val = (*iter) - '0';
+
+		// Not a number
+		if (val < 0 || val > 9)
+		{
+			log << TestLog::Message << "Failed to parse major version number. Not a number." << TestLog::EndMessage;
+			return false;
+		}
+
+		// Leading zero
+		if (majorVersion == 0 && val == 0)
+		{
+			log << TestLog::Message << "Failed to parse major version number. Begins with zero." << TestLog::EndMessage;
+			return false;
+		}
+
+		majorVersion = majorVersion * 10 + val;
+
+		iter++;
+	}
+
+	// Invalid format
+	if ((*iter) != '.')
+	{
+		log << TestLog::Message << "Failed to parse version. Expected '.' after major version number." << TestLog::EndMessage;
+		return false;
+	}
+
+	iter++;
+
+	while ((*iter) && (*iter) != ' ' && (*iter) != '.')
+	{
+		const int val = (*iter) - '0';
+
+		// Not a number
+		if (val < 0 || val > 9)
+		{
+			log << TestLog::Message << "Failed to parse minor version number. Not a number." << TestLog::EndMessage;
+			return false;
+		}
+
+		// Leading zero
+		if (minorVersion == 0 && val == 0)
+		{
+			// Leading zeros in minor version
+			if ((*(iter + 1)) != ' ' && (*(iter + 1)) != '.' && (*(iter + 1)) != '\0')
+			{
+				log << TestLog::Message << "Failed to parse minor version number. Leading zeros." << TestLog::EndMessage;
+				return false;
+			}
+		}
+
+		minorVersion = minorVersion * 10 + val;
+
+		iter++;
+	}
+
+	// Invalid format
+	if ((*iter) != ' ' && (*iter) != '.' && (*iter) != '\0')
+		return false;
+
+	if (desktop)
+	{
+		if (majorVersion < major)
+		{
+			log << TestLog::Message << "Major version is less than required." << TestLog::EndMessage;
+			return false;
+		}
+		else if (majorVersion == major && minorVersion < minor)
+		{
+			log << TestLog::Message << "Minor version is less than required." << TestLog::EndMessage;
+			return false;
+		}
+		else if (majorVersion == major && minorVersion == minor)
+			return true;
+
+		if (major < 3 || (major == 3 && minor == 0))
+		{
+			if (majorVersion == 3 && minorVersion == 1)
+			{
+				if (hasExtension(gl, "GL_ARB_compatibility"))
+					return true;
+				else
+				{
+					log << TestLog::Message << "Required OpenGL 3.0 or earlier. Got OpenGL 3.1 without GL_ARB_compatibility." << TestLog::EndMessage;
+					return false;
+				}
+			}
+			else if (majorVersion > 3 || (majorVersion == 3 && minorVersion >= minor))
+			{
+				deInt32 profile = 0;
+
+				gl.getIntegerv(GL_CONTEXT_PROFILE_MASK, &profile);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+
+				if (profile == GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
+					return true;
+				else
+				{
+					log << TestLog::Message << "Required OpenGL 3.0 or earlier. Got later version without compatibility profile." << TestLog::EndMessage;
+					return false;
+				}
+			}
+			else
+				DE_ASSERT(false);
+
+			return false;
+		}
+		else if (major == 3 && minor == 1)
+		{
+			if (majorVersion > 3 || (majorVersion == 3 && minorVersion >= minor))
+			{
+				deInt32 profile = 0;
+
+				gl.getIntegerv(GL_CONTEXT_PROFILE_MASK, &profile);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+
+				if (profile == GL_CONTEXT_CORE_PROFILE_BIT)
+					return true;
+				else
+				{
+					log << TestLog::Message << "Required OpenGL 3.1. Got later version without core profile." << TestLog::EndMessage;
+					return false;
+				}
+			}
+			else
+				DE_ASSERT(false);
+
+			return false;
+		}
+		else
+		{
+			log << TestLog::Message << "Couldn't do any further compatibilyt checks." << TestLog::EndMessage;
+			return true;
+		}
+	}
+	else
+	{
+		if (majorVersion < major)
+		{
+			log << TestLog::Message << "Major version is less than required." << TestLog::EndMessage;
+			return false;
+		}
+		else if (majorVersion == major && minorVersion < minor)
+		{
+			log << TestLog::Message << "Minor version is less than required." << TestLog::EndMessage;
+			return false;
+		}
+		else
+			return true;
+	}
+}
+
+bool checkVersionQueries (TestLog& log, const glw::Functions& gl, int major, int minor)
+{
+	deInt32 majorVersion = 0;
+	deInt32	minorVersion = 0;
+
+	gl.getIntegerv(GL_MAJOR_VERSION, &majorVersion);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+
+	gl.getIntegerv(GL_MINOR_VERSION, &minorVersion);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+
+	if (majorVersion < major || (majorVersion == major && minorVersion < minor))
+	{
+		if (majorVersion < major)
+			log << TestLog::Message << "glGetIntegerv(GL_MAJOR_VERSION) returned '" << majorVersion << "' expected at least '" << major << "'" << TestLog::EndMessage;
+		else if (majorVersion == major && minorVersion < minor)
+			log << TestLog::Message << "glGetIntegerv(GL_MINOR_VERSION) returned '" << minorVersion << "' expected '" << minor << "'" << TestLog::EndMessage;
+		else
+			DE_ASSERT(false);
+
+		return false;
+	}
+	else
+		return true;
+}
+
+bool CreateContextExtCase::validateCurrentContext (const glw::Functions& gl)
+{
+	bool				isOk					= true;
+	TestLog&			log						= m_testCtx.getLog();
+	const EGLint*		iter					= &(m_attribList[0]);
+
+	EGLint				majorVersion			= -1;
+	EGLint				minorVersion			= -1;
+	EGLint				contextFlags			= -1;
+	EGLint				profileMask				= -1;
+	EGLint				notificationStrategy	= -1;
+	EGLint				robustAccessExt			= -1;
+	EGLint				notificationStrategyExt	= -1;
+
+	while ((*iter) != EGL_NONE)
+	{
+		switch (*iter)
+		{
+			case EGL_CONTEXT_MAJOR_VERSION_KHR:
+				iter++;
+				majorVersion = (*iter);
+				iter++;
+				break;
+
+			case EGL_CONTEXT_MINOR_VERSION_KHR:
+				iter++;
+				minorVersion = (*iter);
+				iter++;
+				break;
+
+			case EGL_CONTEXT_FLAGS_KHR:
+				iter++;
+				contextFlags = (*iter);
+				iter++;
+				break;
+
+			case EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR:
+				iter++;
+				profileMask = (*iter);
+				iter++;
+				break;
+
+			case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR:
+				iter++;
+				notificationStrategy = (*iter);
+				iter++;
+				break;
+
+			case EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT:
+				iter++;
+				robustAccessExt = *iter;
+				iter++;
+				break;
+
+			case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT:
+				iter++;
+				notificationStrategyExt = *iter;
+				iter++;
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+
+	const string version = (const char*)gl.getString(GL_VERSION);
+
+	log << TestLog::Message << "GL_VERSION: '" << version << "'" << TestLog::EndMessage;
+
+	if (majorVersion == -1)
+		majorVersion = 1;
+
+	if (minorVersion == -1)
+		minorVersion = 0;
+
+	if (m_api == EGL_OPENGL_ES_API)
+	{
+		if (!checkVersionString(log, gl, false, majorVersion, minorVersion))
+			isOk = false;
+
+		if (majorVersion == 3)
+		{
+			if (!checkVersionQueries(log, gl, majorVersion, minorVersion))
+				isOk = false;
+		}
+	}
+	else if (m_api == EGL_OPENGL_API)
+	{
+		if (!checkVersionString(log, gl, true, majorVersion, minorVersion))
+			isOk = false;
+
+		if (majorVersion >= 3)
+		{
+			if (!checkVersionQueries(log, gl, majorVersion, minorVersion))
+				isOk = false;
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+
+	if (contextFlags != -1)
+	{
+		if (m_api == EGL_OPENGL_API && (majorVersion > 3 || (majorVersion == 3 && minorVersion >= 1)))
+		{
+			deInt32 contextFlagsGL;
+
+			DE_ASSERT(m_api == EGL_OPENGL_API);
+
+			if (contextFlags == -1)
+				contextFlags = 0;
+
+			gl.getIntegerv(GL_CONTEXT_FLAGS, &contextFlagsGL);
+
+			if (contextFlags != contextFlagsGL)
+			{
+				log << TestLog::Message << "Invalid GL_CONTEXT_FLAGS. Expected '" << eglContextFlagsToString(contextFlags) << "' got '" << eglContextFlagsToString(contextFlagsGL) << "'" << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+	}
+
+	if (profileMask != -1 || (m_api == EGL_OPENGL_API && (majorVersion >= 3)))
+	{
+		if (profileMask == -1)
+			profileMask = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR;
+
+		DE_ASSERT(m_api == EGL_OPENGL_API);
+
+		if (majorVersion < 3 || (majorVersion == 3 && minorVersion < 2))
+		{
+			// \note Ignore profile masks. This is not an error
+		}
+		else
+		{
+			deInt32 profileMaskGL = 0;
+
+			gl.getIntegerv(GL_CONTEXT_PROFILE_MASK, &profileMask);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+
+			if (profileMask != profileMaskGL)
+			{
+				log << TestLog::Message << "Invalid GL_CONTEXT_PROFILE_MASK. Expected '" << eglProfileMaskToString(profileMask) << "' got '" << eglProfileMaskToString(profileMaskGL) << "'" << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+	}
+
+	if (notificationStrategy != -1)
+	{
+		if (m_api == EGL_OPENGL_API)
+		{
+			deInt32 strategy;
+
+			gl.getIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB, &strategy);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+
+			if (notificationStrategy == EGL_NO_RESET_NOTIFICATION_KHR && strategy != GL_NO_RESET_NOTIFICATION_ARB)
+			{
+				log << TestLog::Message << "glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB) returned '" << strategy << "', expected 'GL_NO_RESET_NOTIFICATION_ARB'" << TestLog::EndMessage;
+				isOk = false;
+			}
+			else if (notificationStrategy == EGL_LOSE_CONTEXT_ON_RESET_KHR && strategy != GL_LOSE_CONTEXT_ON_RESET_ARB)
+			{
+				log << TestLog::Message << "glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB) returned '" << strategy << "', expected 'GL_LOSE_CONTEXT_ON_RESET_ARB'" << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+		else if (m_api == EGL_OPENGL_ES_API)
+		{
+			deInt32 strategy;
+
+			gl.getIntegerv(GL_RESET_NOTIFICATION_STRATEGY_EXT, &strategy);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+
+			if (notificationStrategy == EGL_NO_RESET_NOTIFICATION_KHR && strategy != GL_NO_RESET_NOTIFICATION_EXT)
+			{
+				log << TestLog::Message << "glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_EXT) returned '" << strategy << "', expected 'GL_NO_RESET_NOTIFICATION_EXT'" << TestLog::EndMessage;
+				isOk = false;
+			}
+			else if (notificationStrategy == EGL_LOSE_CONTEXT_ON_RESET_KHR && strategy != GL_LOSE_CONTEXT_ON_RESET_EXT)
+			{
+				log << TestLog::Message << "glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_EXT) returned '" << strategy << "', expected 'GL_LOSE_CONTEXT_ON_RESET_EXT'" << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+	}
+
+	if (notificationStrategyExt != -1)
+	{
+		if (m_api == EGL_OPENGL_API)
+		{
+			deInt32 strategy;
+
+			gl.getIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB, &strategy);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+
+			if (notificationStrategyExt == EGL_NO_RESET_NOTIFICATION_KHR && strategy != GL_NO_RESET_NOTIFICATION_ARB)
+			{
+				log << TestLog::Message << "glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB) returned '" << strategy << "', expected 'GL_NO_RESET_NOTIFICATION_ARB'" << TestLog::EndMessage;
+				isOk = false;
+			}
+			else if (notificationStrategyExt == EGL_LOSE_CONTEXT_ON_RESET_KHR && strategy != GL_LOSE_CONTEXT_ON_RESET_ARB)
+			{
+				log << TestLog::Message << "glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB) returned '" << strategy << "', expected 'GL_LOSE_CONTEXT_ON_RESET_ARB'" << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+		else if (m_api == EGL_OPENGL_ES_API)
+		{
+			deInt32 strategy;
+
+			gl.getIntegerv(GL_RESET_NOTIFICATION_STRATEGY_EXT, &strategy);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+
+			if (notificationStrategyExt == EGL_NO_RESET_NOTIFICATION_KHR && strategy != GL_NO_RESET_NOTIFICATION_EXT)
+			{
+				log << TestLog::Message << "glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_EXT) returned '" << strategy << "', expected 'GL_NO_RESET_NOTIFICATION_EXT'" << TestLog::EndMessage;
+				isOk = false;
+			}
+			else if (notificationStrategyExt == EGL_LOSE_CONTEXT_ON_RESET_KHR && strategy != GL_LOSE_CONTEXT_ON_RESET_EXT)
+			{
+				log << TestLog::Message << "glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_EXT) returned '" << strategy << "', expected 'GL_LOSE_CONTEXT_ON_RESET_EXT'" << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+	}
+	
+
+	if (robustAccessExt == EGL_TRUE)
+	{
+		if (m_api == EGL_OPENGL_API)
+		{
+			if (!hasExtension(gl, "GL_ARB_robustness"))
+			{
+				log << TestLog::Message << "Created robustness context but it doesn't support GL_ARB_robustness." << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+		else if (m_api == EGL_OPENGL_ES_API)
+		{
+			if (!hasExtension(gl, "GL_EXT_robustness"))
+			{
+				log << TestLog::Message << "Created robustness context but it doesn't support GL_EXT_robustness." << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+
+		if (m_api == EGL_OPENGL_API && (majorVersion > 3 || (majorVersion == 3 && minorVersion >= 1)))
+		{
+			deInt32 contextFlagsGL;
+
+			DE_ASSERT(m_api == EGL_OPENGL_API);
+
+			gl.getIntegerv(GL_CONTEXT_FLAGS, &contextFlagsGL);
+
+			if ((contextFlagsGL & GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB) != 0)
+			{
+				log << TestLog::Message << "Invalid GL_CONTEXT_FLAGS. GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB to be set, got '" << eglContextFlagsToString(contextFlagsGL) << "'" << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+		else if (m_api == EGL_OPENGL_ES_API)
+		{
+			deUint8 robustAccessGL;
+
+			gl.getBooleanv(GL_CONTEXT_ROBUST_ACCESS_EXT, &robustAccessGL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetBooleanv()");
+
+			if (robustAccessGL != GL_TRUE)
+			{
+				log << TestLog::Message << "Invalid GL_CONTEXT_ROBUST_ACCESS_EXT returned by glGetBooleanv(). Got '" << robustAccessGL << "' expected GL_TRUE." << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+		
+	}
+
+	return isOk;
+}
+
+TestCase::IterateResult CreateContextExtCase::iterate (void)
+{
+	if (m_iteration == 0)
+	{
+		logAttribList();
+		checkRequiredExtensions();
+	}
+
+	if (m_iteration < (int)m_configs.size())
+	{
+		const EGLConfig		config			= m_configs[m_iteration];
+		tcu::egl::Display&	display			= m_eglTestCtx.getDisplay();
+		const EGLint		surfaceTypes	= display.getConfigAttrib(config, EGL_SURFACE_TYPE);
+		const EGLint		configId		= display.getConfigAttrib(config, EGL_CONFIG_ID);
+
+		if ((surfaceTypes & EGL_PBUFFER_BIT) != 0)
+		{
+			tcu::ScopedLogSection section(m_testCtx.getLog(), ("EGLConfig ID: " + de::toString(configId) + " with PBuffer").c_str(), ("EGLConfig ID: " + de::toString(configId)).c_str());
+			const EGLint attribList[] =
+			{
+				EGL_WIDTH,	64,
+				EGL_HEIGHT,	64,
+				EGL_NONE
+			};
+
+			tcu::egl::PbufferSurface pbuffer(display, config, attribList);
+			executeForConfig(display, config, pbuffer);
+		}
+		else if ((surfaceTypes & EGL_WINDOW_BIT) != 0)
+		{
+			de::UniquePtr<eglu::NativeWindow>	window	(m_eglTestCtx.createNativeWindow(display.getEGLDisplay(), config, DE_NULL, 256, 256, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
+			tcu::egl::WindowSurface				surface	(display, eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, display.getEGLDisplay(), config, DE_NULL));
+
+			executeForConfig(display, config, surface);
+		}
+		else if ((surfaceTypes & EGL_PIXMAP_BIT) != 0)
+		{
+			de::UniquePtr<eglu::NativePixmap>	pixmap	(m_eglTestCtx.createNativePixmap(display.getEGLDisplay(), config, DE_NULL, 256, 256));
+			tcu::egl::PixmapSurface				surface	(display, eglu::createPixmapSurface(m_eglTestCtx.getNativeDisplay(), *pixmap, display.getEGLDisplay(), config, DE_NULL));
+
+			executeForConfig(display, config, surface);
+		}
+		else // No supported surface type
+			TCU_CHECK(false);
+
+		m_iteration++;
+		return CONTINUE;
+	}
+	else
+	{
+		if (m_configs.size() == 0)
+		{
+			m_testCtx.getLog() << TestLog::Message << "No supported configs found" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "No supported configs found");
+		}
+		else if (m_isOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+		return STOP;
+	}
+}
+
+void CreateContextExtCase::executeForConfig (tcu::egl::Display& display, EGLConfig config, tcu::egl::Surface& surface)
+{
+	tcu::egl::Context*		context		= DE_NULL;
+
+	TCU_CHECK_EGL_CALL(eglBindAPI(m_api));
+
+	try
+	{
+		glw::Functions	gl;
+
+		context = new tcu::egl::Context(display, config, &(m_attribList[0]), m_api);
+		context->makeCurrent(surface, surface);
+
+		m_eglTestCtx.getGLFunctions(gl, m_glContextType.getAPI());
+
+		if (!validateCurrentContext(gl))
+			m_isOk = false;
+
+		delete context;
+	}
+	catch (const eglu::Error& error)
+	{
+		delete context;
+
+		if (error.getError() == EGL_BAD_MATCH)
+			m_testCtx.getLog() << TestLog::Message << "Context creation failed with error EGL_BAD_CONTEXT. Config doesn't support api version." << TestLog::EndMessage;
+		else if (error.getError() == EGL_BAD_CONFIG)
+			m_testCtx.getLog() << TestLog::Message << "Context creation failed with error EGL_BAD_MATCH. Context attribute compination not supported." << TestLog::EndMessage;
+		else
+		{
+			m_testCtx.getLog() << TestLog::Message << "Context creation failed with error " << eglu::getErrorStr(error.getError()) << ". Error is not result of unsupported api etc." << TestLog::EndMessage;
+			m_isOk = false;
+		}
+	}
+	catch (...)
+	{
+		delete context;
+		throw;
+	}
+}
+
+class CreateContextExtGroup : public TestCaseGroup
+{
+public:
+						CreateContextExtGroup	(EglTestContext& eglTestCtx, EGLenum api, EGLint apiBit, const EGLint* attribList, const char* name, const char* description);
+	virtual				~CreateContextExtGroup	(void);
+
+	void				init					(void);
+
+private:
+	const EGLenum		m_api;
+	const EGLint		m_apiBit;
+	vector<EGLint>		m_attribList;
+};
+
+CreateContextExtGroup::CreateContextExtGroup (EglTestContext& eglTestCtx, EGLenum api, EGLint apiBit, const EGLint* attribList, const char* name, const char* description)
+	: TestCaseGroup (eglTestCtx, name, description)
+	, m_api			(api)
+	, m_apiBit		(apiBit)
+	, m_attribList	(attribList, attribList + getAttribListLength(attribList))
+{
+}
+
+CreateContextExtGroup::~CreateContextExtGroup (void)
+{
+}
+
+void CreateContextExtGroup::init (void)
+{
+	const struct
+	{
+		const char*				name;
+		const char*				description;
+
+		EGLint					redSize;
+		EGLint					greenSize;
+		EGLint					blueSize;
+		EGLint					alphaSize;
+
+		bool					hasDepth;
+		bool					hasStencil;
+	} groups[] =
+	{
+		{ "rgb565_no_depth_no_stencil",		"RGB565 configs without depth or stencil",		5, 6, 5, 0, false,	false	},
+		{ "rgb565_no_depth_stencil",		"RGB565 configs with stencil and no depth",		5, 6, 5, 0, false,	true	},
+		{ "rgb565_depth_no_stencil",		"RGB565 configs with depth and no stencil",		5, 6, 5, 0, true,	false	},
+		{ "rgb565_depth_stencil",			"RGB565 configs with depth and stencil",		5, 6, 5, 0, true,	true	},
+
+		{ "rgb888_no_depth_no_stencil",		"RGB888 configs without depth or stencil",		8, 8, 8, 0, false,	false	},
+		{ "rgb888_no_depth_stencil",		"RGB888 configs with stencil and no depth",		8, 8, 8, 0, false,	true	},
+		{ "rgb888_depth_no_stencil",		"RGB888 configs with depth and no stencil",		8, 8, 8, 0, true,	false	},
+		{ "rgb888_depth_stencil",			"RGB888 configs with depth and stencil",		8, 8, 8, 0, true,	true	},
+
+		{ "rgba4444_no_depth_no_stencil",	"RGBA4444 configs without depth or stencil",	4, 4, 4, 4, false,	false	},
+		{ "rgba4444_no_depth_stencil",		"RGBA4444 configs with stencil and no depth",	4, 4, 4, 4, false,	true	},
+		{ "rgba4444_depth_no_stencil",		"RGBA4444 configs with depth and no stencil",	4, 4, 4, 4, false,	false	},
+		{ "rgba4444_depth_stencil",			"RGBA4444 configs with depth and stencil",		4, 4, 4, 4, true,	true	},
+
+		{ "rgba5551_no_depth_no_stencil",	"RGBA5551 configs without depth or stencil",	5, 5, 5, 1, false,	false	},
+		{ "rgba5551_no_depth_stencil",		"RGBA5551 configs with stencil and no depth",	5, 5, 5, 1, false,	true	},
+		{ "rgba5551_depth_no_stencil",		"RGBA5551 configs with depth and no stencil",	5, 5, 5, 1, true,	false	},
+		{ "rgba5551_depth_stencil",			"RGBA5551 configs with depth and stencil",		5, 5, 5, 1, true,	true	},
+
+		{ "rgba8888_no_depth_no_stencil",	"RGBA8888 configs without depth or stencil",	8, 8, 8, 8, false,	false	},
+		{ "rgba8888_no_depth_stencil",		"RGBA8888 configs with stencil and no depth",	8, 8, 8, 8, false,	true	},
+		{ "rgba8888_depth_no_stencil",		"RGBA8888 configs with depth and no stencil",	8, 8, 8, 8, true,	false	},
+		{ "rgba8888_depth_stencil",			"RGBA8888 configs with depth and stencil",		8, 8, 8, 8, true,	true	}
+	};
+
+	for (int groupNdx = 0; groupNdx < DE_LENGTH_OF_ARRAY(groups); groupNdx++)
+	{
+		eglu::FilterList filter;
+
+		filter
+		<< (eglu::ConfigRedSize()	== groups[groupNdx].redSize)
+		<< (eglu::ConfigGreenSize()	== groups[groupNdx].greenSize)
+		<< (eglu::ConfigBlueSize()	== groups[groupNdx].blueSize)
+		<< (eglu::ConfigAlphaSize()	== groups[groupNdx].alphaSize);
+
+		if (groups[groupNdx].hasDepth)
+			filter << (eglu::ConfigDepthSize() >= 1);
+
+		if (groups[groupNdx].hasStencil)
+			filter << (eglu::ConfigStencilSize() >= 1);
+
+		filter << (eglu::ConfigRenderableType() & m_apiBit);
+
+		addChild(new CreateContextExtCase(m_eglTestCtx, m_api, &(m_attribList[0]), filter, groups[groupNdx].name, groups[groupNdx].description));
+	}
+	// \todo [mika] Add other group
+}
+
+} // anonymous
+
+CreateContextExtTests::CreateContextExtTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "create_context_ext", "EGL_KHR_create_context tests.")
+{
+}
+
+CreateContextExtTests::~CreateContextExtTests (void)
+{
+}
+
+void CreateContextExtTests::init (void)
+{
+	const size_t	maxAttributeCount = 10;
+	const struct
+	{
+		const char*	name;
+		const char*	description;
+		EGLenum		api;
+		EGLint		apiBit;
+		EGLint		attribList[maxAttributeCount];
+	} groupList[] =
+	{
+#if 0
+		// \todo [mika] Not supported by glw
+		// OpenGL ES 1.x
+		{ "gles_10", "Create OpenGL ES 1.0 context", EGL_OPENGL_ES_API, EGL_OPENGL_ES_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 1, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_NONE} },
+		{ "gles_11", "Create OpenGL ES 1.1 context", EGL_OPENGL_ES_API, EGL_OPENGL_ES_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 1, EGL_CONTEXT_MINOR_VERSION_KHR, 1, EGL_NONE } },
+#endif
+		// OpenGL ES 2.x
+		{ "gles_20", "Create OpenGL ES 2.0 context", EGL_OPENGL_ES_API, EGL_OPENGL_ES2_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 2, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_NONE } },
+		{ "robust_gles_20", "Create robust OpenGL ES 2.0 context", EGL_OPENGL_ES_API, EGL_OPENGL_ES2_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 2, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE } },
+		// OpenGL ES 3.x
+		{ "gles_30", "Create OpenGL ES 3.0 context", EGL_OPENGL_ES_API, EGL_OPENGL_ES3_BIT_KHR,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_NONE} },
+		{ "robust_gles_30", "Create OpenGL ES 3.0 context", EGL_OPENGL_ES_API, EGL_OPENGL_ES3_BIT_KHR,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE } },
+#if 0
+		// \todo [mika] Not supported by glw
+		// \note [mika] Should we really test 1.x?
+		{ "gl_10", "Create OpenGL 1.0 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 1, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_NONE} },
+		{ "gl_11", "Create OpenGL 1.1 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 1, EGL_CONTEXT_MINOR_VERSION_KHR, 1, EGL_NONE } },
+
+		// OpenGL 2.x
+		{ "gl_20", "Create OpenGL 2.0 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 2, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_NONE } },
+		{ "gl_21", "Create OpenGL 2.1 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 2, EGL_CONTEXT_MINOR_VERSION_KHR, 1, EGL_NONE } },
+#endif
+		// OpenGL 3.x
+		{ "gl_30", "Create OpenGL 3.0 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_NONE } },
+		{ "robust_gl_30", "Create robust OpenGL 3.0 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE } },
+		{ "gl_31", "Create OpenGL 3.1 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 1, EGL_NONE } },
+		{ "robust_gl_31", "Create robust OpenGL 3.1 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 1, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE } },
+		{ "gl_32", "Create OpenGL 3.2 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 2, EGL_NONE } },
+		{ "robust_gl_32", "Create robust OpenGL 3.2 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 2, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE } },
+		{ "gl_33", "Create OpenGL 3.3 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 3, EGL_NONE } },
+		{ "robust_gl_33", "Create robust OpenGL 3.3 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 3, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE } },
+
+		// OpenGL 4.x
+		{ "gl_40", "Create OpenGL 4.0 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_NONE } },
+		{ "robust_gl_40", "Create robust OpenGL 4.0 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE } },
+		{ "gl_41", "Create OpenGL 4.1 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 1, EGL_NONE } },
+		{ "robust_gl_41", "Create robust OpenGL 4.1 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 1, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE } },
+		{ "gl_42", "Create OpenGL 4.2 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 2, EGL_NONE } },
+		{ "robust_gl_42", "Create robust OpenGL 4.2 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 2, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE } },
+		{ "gl_43", "Create OpenGL 4.3 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 3, EGL_NONE } },
+		{ "robust_gl_43", "Create robust OpenGL 4.3 context", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 3, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE } },
+
+		// Robust contexts with EGL_EXT_create_context_robustness
+		{ "robust_gles_2_ext", "Create robust OpenGL ES 2.0 context with EGL_EXT_create_context_robustness.", EGL_OPENGL_ES_API, EGL_OPENGL_ES2_BIT,
+			{ EGL_CONTEXT_CLIENT_VERSION, 2, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_TRUE, EGL_NONE } },
+		{ "robust_gles_3_ext", "Create robust OpenGL ES 3.0 context with EGL_EXT_create_context_robustness.", EGL_OPENGL_ES_API, EGL_OPENGL_ES3_BIT_KHR,
+			{ EGL_CONTEXT_CLIENT_VERSION, 3, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_TRUE, EGL_NONE } },
+#if 0
+	// glu/glw doesn't support any version of OpenGL and EGL doesn't allow use of EGL_CONTEXT_CLIENT_VERSION with OpenGL and doesn't define which OpenGL version should be returned.
+		{ "robust_gl_ext", "Create robust OpenGL context with EGL_EXT_create_context_robustness.", EGL_OPENGL_API, EGL_OPENGL_BIT,
+			{ EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_TRUE, EGL_NONE } }
+#endif
+	};
+
+	for (int groupNdx = 0; groupNdx < DE_LENGTH_OF_ARRAY(groupList); groupNdx++)
+		addChild(new CreateContextExtGroup(m_eglTestCtx, groupList[groupNdx].api, groupList[groupNdx].apiBit, groupList[groupNdx].attribList, groupList[groupNdx].name, groupList[groupNdx].description));
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglCreateContextExtTests.hpp b/modules/egl/teglCreateContextExtTests.hpp
new file mode 100644
index 0000000..d82f953
--- /dev/null
+++ b/modules/egl/teglCreateContextExtTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLCREATECONTEXTEXTTESTS_HPP
+#define _TEGLCREATECONTEXTEXTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Simple context construction test for EGL_KHR_create_context.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class CreateContextExtTests : public TestCaseGroup
+{
+public:
+				CreateContextExtTests		(EglTestContext& eglTestCtx);
+	virtual		~CreateContextExtTests		(void);
+
+	void		init						(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLCREATECONTEXTEXTTESTS_HPP
diff --git a/modules/egl/teglCreateContextTests.cpp b/modules/egl/teglCreateContextTests.cpp
new file mode 100644
index 0000000..4f48728
--- /dev/null
+++ b/modules/egl/teglCreateContextTests.cpp
@@ -0,0 +1,137 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Simple Context construction test.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglCreateContextTests.hpp"
+#include "teglSimpleConfigCase.hpp"
+#include "egluStrUtil.hpp"
+#include "tcuTestLog.hpp"
+
+#include <EGL/eglext.h>
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#	define EGL_OPENGL_ES3_BIT_KHR	0x0040
+#endif
+#if !defined(EGL_CONTEXT_MAJOR_VERSION_KHR)
+#	define EGL_CONTEXT_MAJOR_VERSION_KHR EGL_CONTEXT_CLIENT_VERSION
+#endif
+
+using std::vector;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+
+class CreateContextCase : public SimpleConfigCase
+{
+public:
+						CreateContextCase			(EglTestContext& eglTestCtx, const char* name, const char* description, const vector<EGLint>& configIds);
+						~CreateContextCase			(void);
+
+	void				executeForConfig			(tcu::egl::Display& display, EGLConfig config);
+};
+
+CreateContextCase::CreateContextCase (EglTestContext& eglTestCtx, const char* name, const char* description, const vector<EGLint>& configIds)
+	: SimpleConfigCase(eglTestCtx, name, description, configIds)
+{
+}
+
+CreateContextCase::~CreateContextCase (void)
+{
+}
+
+void CreateContextCase::executeForConfig (tcu::egl::Display& display, EGLConfig config)
+{
+	TestLog&	log		= m_testCtx.getLog();
+	EGLint		id		= display.getConfigAttrib(config, EGL_CONFIG_ID);
+	EGLint		apiBits	= display.getConfigAttrib(config, EGL_RENDERABLE_TYPE);
+
+	static const EGLint es1Attrs[] = { EGL_CONTEXT_CLIENT_VERSION,		1, EGL_NONE };
+	static const EGLint es2Attrs[] = { EGL_CONTEXT_CLIENT_VERSION,		2, EGL_NONE };
+	static const EGLint es3Attrs[] = { EGL_CONTEXT_MAJOR_VERSION_KHR,	3, EGL_NONE };
+
+	static const struct
+	{
+		const char*		name;
+		EGLenum			api;
+		EGLint			apiBit;
+		const EGLint*	ctxAttrs;
+	} apis[] =
+	{
+		{ "OpenGL",			EGL_OPENGL_API,		EGL_OPENGL_BIT,			DE_NULL		},
+		{ "OpenGL ES 1",	EGL_OPENGL_ES_API,	EGL_OPENGL_ES_BIT,		es1Attrs	},
+		{ "OpenGL ES 2",	EGL_OPENGL_ES_API,	EGL_OPENGL_ES2_BIT,		es2Attrs	},
+		{ "OpenGL ES 3",	EGL_OPENGL_ES_API,	EGL_OPENGL_ES3_BIT_KHR,	es3Attrs	},
+		{ "OpenVG",			EGL_OPENVG_API,		EGL_OPENVG_BIT,			DE_NULL		}
+	};
+
+	for (int apiNdx = 0; apiNdx < (int)DE_LENGTH_OF_ARRAY(apis); apiNdx++)
+	{
+		if ((apiBits & apis[apiNdx].apiBit) == 0)
+			continue; // Not supported API
+
+		log << TestLog::Message << "Creating " << apis[apiNdx].name << " context with config ID " << id << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		TCU_CHECK_EGL_CALL(eglBindAPI(apis[apiNdx].api));
+
+		EGLContext	context = eglCreateContext(display.getEGLDisplay(), config, EGL_NO_CONTEXT, apis[apiNdx].ctxAttrs);
+		EGLenum		err		= eglGetError();
+
+		if (context == EGL_NO_CONTEXT || err != EGL_SUCCESS)
+		{
+			log << TestLog::Message << "  Fail, context: " << tcu::toHex(context) << ", error: " << eglu::getErrorName(err) << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to create context");
+		}
+		else
+		{
+			// Destroy
+			TCU_CHECK_EGL_CALL(eglDestroyContext(display.getEGLDisplay(), context));
+			log << TestLog::Message << "  Pass" << TestLog::EndMessage;
+		}
+	}
+}
+
+
+CreateContextTests::CreateContextTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "create_context", "Basic eglCreateContext() tests")
+{
+}
+
+CreateContextTests::~CreateContextTests (void)
+{
+}
+
+void CreateContextTests::init (void)
+{
+	vector<NamedConfigIdSet>	configIdSets;
+	eglu::FilterList			filters;
+	NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+	for (vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+		addChild(new CreateContextCase(m_eglTestCtx, i->getName(), i->getDescription(), i->getConfigIds()));
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglCreateContextTests.hpp b/modules/egl/teglCreateContextTests.hpp
new file mode 100644
index 0000000..fda9514
--- /dev/null
+++ b/modules/egl/teglCreateContextTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLCREATECONTEXTTESTS_HPP
+#define _TEGLCREATECONTEXTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Simple context construction test.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class CreateContextTests : public TestCaseGroup
+{
+public:
+							CreateContextTests			(EglTestContext& eglTestCtx);
+	virtual					~CreateContextTests			(void);
+
+	void					init						(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLCREATECONTEXTTESTS_HPP
diff --git a/modules/egl/teglCreateSurfaceTests.cpp b/modules/egl/teglCreateSurfaceTests.cpp
new file mode 100644
index 0000000..584c2a9
--- /dev/null
+++ b/modules/egl/teglCreateSurfaceTests.cpp
@@ -0,0 +1,363 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Simple surface construction test.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglCreateSurfaceTests.hpp"
+
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluUtil.hpp"
+
+#include "teglSimpleConfigCase.hpp"
+#include "tcuTestContext.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuTestLog.hpp"
+
+#include "deSTLUtil.hpp"
+#include "deUniquePtr.hpp"
+
+#include <memory>
+
+#if !defined(EGL_EXT_platform_base)
+#	define EGL_EXT_platform_base 1
+	typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list);
+	typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list);
+	typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list);
+#endif // EGL_EXT_platform_base
+
+using std::vector;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+namespace
+{
+
+void checkEGLPlatformSupport (const char* platformExt)
+{
+	std::vector<std::string> extensions = eglu::getPlatformExtensions();
+
+	if (!de::contains(extensions.begin(), extensions.end(), platformExt))
+		throw tcu::NotSupportedError((std::string("Platform extension '") + platformExt + "' not supported").c_str(), "", __FILE__, __LINE__);
+}
+
+EGLSurface createWindowSurface (EGLDisplay display, EGLConfig config, eglu::NativeDisplay& nativeDisplay, eglu::NativeWindow& window, bool useLegacyCreate)
+{
+	EGLSurface surface = EGL_NO_SURFACE;
+
+	if (useLegacyCreate)
+	{
+		surface = eglCreateWindowSurface(display, config, window.getLegacyNative(), DE_NULL);
+		TCU_CHECK_EGL_MSG("eglCreateWindowSurface() failed");
+	}
+	else
+	{
+		checkEGLPlatformSupport(nativeDisplay.getPlatformExtensionName());
+
+		PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
+		TCU_CHECK_EGL_MSG("eglGetProcAddress() failed");
+
+		surface = createPlatformWindowSurfaceEXT(display, config, window.getPlatformNative(), DE_NULL);
+		TCU_CHECK_EGL_MSG("eglCreatePlatformWindowSurfaceEXT() failed");
+	}
+
+	return surface;
+}
+
+EGLSurface createPixmapSurface (EGLDisplay display, EGLConfig config, eglu::NativeDisplay& nativeDisplay, eglu::NativePixmap& pixmap, bool useLegacyCreate)
+{
+	EGLSurface surface = EGL_NO_SURFACE;
+
+	if (useLegacyCreate)
+	{
+		surface = eglCreatePixmapSurface(display, config, pixmap.getLegacyNative(), DE_NULL);
+		TCU_CHECK_EGL_MSG("eglCreatePixmapSurface() failed");
+	}
+	else
+	{
+		checkEGLPlatformSupport(nativeDisplay.getPlatformExtensionName());
+
+		PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC createPlatformPixmapSurfaceEXT = (PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformPixmapSurfaceEXT");
+		TCU_CHECK_EGL_MSG("eglGetProcAddress() failed");
+
+		surface = createPlatformPixmapSurfaceEXT(display, config, pixmap.getPlatformNative(), DE_NULL);
+		TCU_CHECK_EGL_MSG("eglCreatePlatformPixmapSurfaceEXT() failed");
+	}
+
+	return surface;
+}
+
+class CreateWindowSurfaceCase : public SimpleConfigCase
+{
+public:
+	CreateWindowSurfaceCase (EglTestContext& eglTestCtx, const char* name, const char* description, bool useLegacyCreate, const vector<EGLint>& configIds)
+		: SimpleConfigCase	(eglTestCtx, name, description, configIds)
+		, m_useLegacyCreate	(useLegacyCreate)
+	{
+	}
+
+	void executeForConfig (tcu::egl::Display& display, EGLConfig config)
+	{
+		TestLog&	log		= m_testCtx.getLog();
+		EGLint		id		= display.getConfigAttrib(config, EGL_CONFIG_ID);
+
+		// \todo [2011-03-23 pyry] Iterate thru all possible combinations of EGL_RENDER_BUFFER, EGL_VG_COLORSPACE and EGL_VG_ALPHA_FORMAT
+
+		if (m_useLegacyCreate)
+		{
+			if ((m_eglTestCtx.getNativeWindowFactory().getCapabilities() & eglu::NativeWindow::CAPABILITY_CREATE_SURFACE_LEGACY) == 0)
+				throw tcu::NotSupportedError("Native window doesn't support legacy eglCreateWindowSurface()", "", __FILE__, __LINE__);
+		}
+		else
+		{
+			if ((m_eglTestCtx.getNativeWindowFactory().getCapabilities() & eglu::NativeWindow::CAPABILITY_CREATE_SURFACE_PLATFORM) == 0)
+				throw tcu::NotSupportedError("Native window doesn't support eglCreatePlatformWindowSurfaceEXT()", "", __FILE__, __LINE__);
+		}
+
+		log << TestLog::Message << "Creating window surface with config ID " << id << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		{
+			const int							width			= 64;
+			const int							height			= 64;
+			de::UniquePtr<eglu::NativeWindow>	window			(m_eglTestCtx.createNativeWindow(display.getEGLDisplay(), config, DE_NULL, width, height, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
+			tcu::egl::WindowSurface				surface			(display, createWindowSurface(display.getEGLDisplay(), config, m_eglTestCtx.getNativeDisplay(), *window, m_useLegacyCreate));
+
+			EGLint								windowWidth		= 0;
+			EGLint								windowHeight	= 0;
+
+			TCU_CHECK_EGL_CALL(eglQuerySurface(display.getEGLDisplay(), surface.getEGLSurface(), EGL_WIDTH,		&windowWidth));
+			TCU_CHECK_EGL_CALL(eglQuerySurface(display.getEGLDisplay(), surface.getEGLSurface(), EGL_HEIGHT,	&windowHeight));
+
+			if (windowWidth <= 0 || windowHeight <= 0)
+			{
+				log << TestLog::Message << "  Fail, invalid surface size " << windowWidth << "x" << windowHeight << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid surface size");
+			}
+			else
+				log << TestLog::Message << "  Pass" << TestLog::EndMessage;
+		}
+	}
+
+private:
+	bool	m_useLegacyCreate;
+};
+
+class CreatePixmapSurfaceCase : public SimpleConfigCase
+{
+public:
+	CreatePixmapSurfaceCase (EglTestContext& eglTestCtx, const char* name, const char* description, bool useLegacyCreate, const vector<EGLint>& configIds)
+		: SimpleConfigCase(eglTestCtx, name, description, configIds)
+		, m_useLegacyCreate	(useLegacyCreate)
+	{
+	}
+
+	void executeForConfig (tcu::egl::Display& display, EGLConfig config)
+	{
+		TestLog&	log		= m_testCtx.getLog();
+		EGLint		id		= display.getConfigAttrib(config, EGL_CONFIG_ID);
+
+		// \todo [2011-03-23 pyry] Iterate thru all possible combinations of EGL_RENDER_BUFFER, EGL_VG_COLORSPACE and EGL_VG_ALPHA_FORMAT
+
+		if (m_useLegacyCreate)
+		{
+			if ((m_eglTestCtx.getNativePixmapFactory().getCapabilities() & eglu::NativePixmap::CAPABILITY_CREATE_SURFACE_LEGACY) == 0)
+				throw tcu::NotSupportedError("Native pixmap doesn't support legacy eglCreatePixmapSurface()", "", __FILE__, __LINE__);
+		}
+		else
+		{
+			if ((m_eglTestCtx.getNativePixmapFactory().getCapabilities() & eglu::NativePixmap::CAPABILITY_CREATE_SURFACE_PLATFORM) == 0)
+				throw tcu::NotSupportedError("Native pixmap doesn't support eglCreatePlatformPixmapSurfaceEXT()", "", __FILE__, __LINE__);
+		}
+
+		log << TestLog::Message << "Creating pixmap surface with config ID " << id << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		{
+			const int							width			= 64;
+			const int							height			= 64;
+			de::UniquePtr<eglu::NativePixmap>	pixmap			(m_eglTestCtx.createNativePixmap(display.getEGLDisplay(), config, DE_NULL, width, height));
+			tcu::egl::PixmapSurface				surface			(display, createPixmapSurface(display.getEGLDisplay(), config, m_eglTestCtx.getNativeDisplay(), *pixmap, m_useLegacyCreate));
+			EGLint								pixmapWidth		= 0;
+			EGLint								pixmapHeight	= 0;
+
+			TCU_CHECK_EGL_CALL(eglQuerySurface(display.getEGLDisplay(), surface.getEGLSurface(), EGL_WIDTH,		&pixmapWidth));
+			TCU_CHECK_EGL_CALL(eglQuerySurface(display.getEGLDisplay(), surface.getEGLSurface(), EGL_HEIGHT,	&pixmapHeight));
+
+			if (pixmapWidth <= 0 || pixmapHeight <= 0)
+			{
+				log << TestLog::Message << "  Fail, invalid surface size " << pixmapWidth << "x" << pixmapHeight << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid surface size");
+			}
+			else
+				log << TestLog::Message << "  Pass" << TestLog::EndMessage;
+		}
+	}
+
+private:
+	bool	m_useLegacyCreate;
+};
+
+class CreatePbufferSurfaceCase : public SimpleConfigCase
+{
+public:
+	CreatePbufferSurfaceCase (EglTestContext& eglTestCtx, const char* name, const char* description, const vector<EGLint>& configIds)
+		: SimpleConfigCase(eglTestCtx, name, description, configIds)
+	{
+	}
+
+	void executeForConfig (tcu::egl::Display& display, EGLConfig config)
+	{
+		TestLog&	log		= m_testCtx.getLog();
+		EGLint		id		= display.getConfigAttrib(config, EGL_CONFIG_ID);
+		int			width	= 64;
+		int			height	= 64;
+
+		// \todo [2011-03-23 pyry] Iterate thru all possible combinations of EGL_RENDER_BUFFER, EGL_VG_COLORSPACE and EGL_VG_ALPHA_FORMAT
+
+		log << TestLog::Message << "Creating pbuffer surface with config ID " << id << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		// Clamp to maximums reported by implementation
+		width	= deMin32(width, display.getConfigAttrib(config, EGL_MAX_PBUFFER_WIDTH));
+		height	= deMin32(height, display.getConfigAttrib(config, EGL_MAX_PBUFFER_HEIGHT));
+
+		if (width == 0 || height == 0)
+		{
+			log << TestLog::Message << "  Fail, maximum pbuffer size of " << width << "x" << height << " reported" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid maximum pbuffer size");
+			return;
+		}
+
+		// \todo [2011-03-23 pyry] Texture-backed variants!
+
+		EGLint attribs[] =
+		{
+			EGL_WIDTH,			width,
+			EGL_HEIGHT,			height,
+			EGL_TEXTURE_FORMAT,	EGL_NO_TEXTURE,
+			EGL_NONE
+		};
+
+		EGLSurface surface = eglCreatePbufferSurface(display.getEGLDisplay(), config, attribs);
+		TCU_CHECK_EGL_MSG("Failed to create pbuffer");
+		TCU_CHECK(surface != EGL_NO_SURFACE);
+		eglDestroySurface(display.getEGLDisplay(), surface);
+
+		log << TestLog::Message << "  Pass" << TestLog::EndMessage;
+	}
+};
+
+} // anonymous
+
+CreateSurfaceTests::CreateSurfaceTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "create_surface", "Basic surface construction tests")
+{
+}
+
+CreateSurfaceTests::~CreateSurfaceTests (void)
+{
+}
+
+void CreateSurfaceTests::init (void)
+{
+	// Window surfaces
+	{
+		tcu::TestCaseGroup* windowGroup = new tcu::TestCaseGroup(m_testCtx, "window", "Window surfaces");
+		addChild(windowGroup);
+
+		eglu::FilterList filters;
+		filters << (eglu::ConfigSurfaceType() & EGL_WINDOW_BIT);
+
+		vector<NamedConfigIdSet> configIdSets;
+		NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+		for (vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+			windowGroup->addChild(new CreateWindowSurfaceCase(m_eglTestCtx, i->getName(), i->getDescription(), true, i->getConfigIds()));
+	}
+
+	// Pixmap surfaces
+	{
+		tcu::TestCaseGroup* pixmapGroup = new tcu::TestCaseGroup(m_testCtx, "pixmap", "Pixmap surfaces");
+		addChild(pixmapGroup);
+
+		eglu::FilterList filters;
+		filters << (eglu::ConfigSurfaceType() & EGL_PIXMAP_BIT);
+
+		vector<NamedConfigIdSet> configIdSets;
+		NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+		for (vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+			pixmapGroup->addChild(new CreatePixmapSurfaceCase(m_eglTestCtx, i->getName(), i->getDescription(), true, i->getConfigIds()));
+	}
+
+	// Pbuffer surfaces
+	{
+		tcu::TestCaseGroup* pbufferGroup = new tcu::TestCaseGroup(m_testCtx, "pbuffer", "Pbuffer surfaces");
+		addChild(pbufferGroup);
+
+		eglu::FilterList filters;
+		filters << (eglu::ConfigSurfaceType() & EGL_PBUFFER_BIT);
+
+		vector<NamedConfigIdSet> configIdSets;
+		NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+		for (vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+			pbufferGroup->addChild(new CreatePbufferSurfaceCase(m_eglTestCtx, i->getName(), i->getDescription(), i->getConfigIds()));
+	}
+
+	// Window surfaces with new platform extension
+	{
+		tcu::TestCaseGroup* windowGroup = new tcu::TestCaseGroup(m_testCtx, "platform_window", "Window surfaces with platform extension");
+		addChild(windowGroup);
+
+		eglu::FilterList filters;
+		filters << (eglu::ConfigSurfaceType() & EGL_WINDOW_BIT);
+
+		vector<NamedConfigIdSet> configIdSets;
+		NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+		for (vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+			windowGroup->addChild(new CreateWindowSurfaceCase(m_eglTestCtx, i->getName(), i->getDescription(), false, i->getConfigIds()));
+	}
+
+	// Pixmap surfaces with new platform extension
+	{
+		tcu::TestCaseGroup* pixmapGroup = new tcu::TestCaseGroup(m_testCtx, "platform_pixmap", "Pixmap surfaces with platform extension");
+		addChild(pixmapGroup);
+
+		eglu::FilterList filters;
+		filters << (eglu::ConfigSurfaceType() & EGL_PIXMAP_BIT);
+
+		vector<NamedConfigIdSet> configIdSets;
+		NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+		for (vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+			pixmapGroup->addChild(new CreatePixmapSurfaceCase(m_eglTestCtx, i->getName(), i->getDescription(), false, i->getConfigIds()));
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglCreateSurfaceTests.hpp b/modules/egl/teglCreateSurfaceTests.hpp
new file mode 100644
index 0000000..b2fec47
--- /dev/null
+++ b/modules/egl/teglCreateSurfaceTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLCREATESURFACETESTS_HPP
+#define _TEGLCREATESURFACETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Simple surface construction test.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class CreateSurfaceTests : public TestCaseGroup
+{
+public:
+							CreateSurfaceTests			(EglTestContext& eglTestCtx);
+	virtual					~CreateSurfaceTests			(void);
+
+	void					init						(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLCREATESURFACETESTS_HPP
diff --git a/modules/egl/teglGLES1RenderUtil.cpp b/modules/egl/teglGLES1RenderUtil.cpp
new file mode 100644
index 0000000..fe96aa9
--- /dev/null
+++ b/modules/egl/teglGLES1RenderUtil.cpp
@@ -0,0 +1,76 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES1 render utils.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglGLES1RenderUtil.hpp"
+
+#if defined(DEQP_SUPPORT_GLES1)
+#	include <GLES/gl.h>
+#endif
+
+using std::vector;
+
+namespace deqp
+{
+namespace egl
+{
+namespace gles1
+{
+
+#if defined(DEQP_SUPPORT_GLES1)
+
+void clear (int x, int y, int width, int height, const tcu::Vec4& color)
+{
+	glEnable(GL_SCISSOR_TEST);
+	glScissor(x, y, width, height);
+	glClearColor(color.x(), color.y(), color.z(), color.w());
+	glClear(GL_COLOR_BUFFER_BIT);
+	glDisable(GL_SCISSOR_TEST);
+}
+
+void readPixels (tcu::Surface& dst, int x, int y, int width, int height)
+{
+	dst.setSize(width, height);
+	glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr());
+}
+
+#else // DEQP_SUPPORT_GLES1
+
+void clear (int x, int y, int width, int height, const tcu::Vec4& color)
+{
+	DE_UNREF(x && y && width && height);
+	DE_UNREF(color);
+	throw tcu::NotSupportedError("OpenGL ES 1.x is not supported", "", __FILE__, __LINE__);
+}
+
+void readPixels (tcu::Surface& dst, int x, int y, int width, int height)
+{
+	DE_UNREF(x && y && width && height);
+	DE_UNREF(dst);
+	throw tcu::NotSupportedError("OpenGL ES 1.x is not supported", "", __FILE__, __LINE__);
+}
+
+#endif // DEQP_SUPPORT_GLES1
+
+} // gles1
+} // egl
+} // deqp
diff --git a/modules/egl/teglGLES1RenderUtil.hpp b/modules/egl/teglGLES1RenderUtil.hpp
new file mode 100644
index 0000000..3254331
--- /dev/null
+++ b/modules/egl/teglGLES1RenderUtil.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLGLES1RENDERUTIL_HPP
+#define _TEGLGLES1RENDERUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES1 render utils.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuSurface.hpp"
+#include "tcuVector.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+namespace gles1
+{
+
+void	clear		(int x, int y, int width, int height, const tcu::Vec4& color);
+void	readPixels	(tcu::Surface& dst, int x, int y, int width, int height);
+
+} // gles1
+} // egl
+} // deqp
+
+#endif // _TEGLGLES1RENDERUTIL_HPP
diff --git a/modules/egl/teglGLES2RenderUtil.cpp b/modules/egl/teglGLES2RenderUtil.cpp
new file mode 100644
index 0000000..3cc7de9
--- /dev/null
+++ b/modules/egl/teglGLES2RenderUtil.cpp
@@ -0,0 +1,80 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES2 render utils.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglGLES2RenderUtil.hpp"
+
+#if defined(DEQP_SUPPORT_GLES2) || defined(DEQP_SUPPORT_GLES3)
+#	include "gluDefs.hpp"
+#	include "gluPixelTransfer.hpp"
+#	if !defined(DEQP_SUPPORT_GLES2)
+#		include <GLES3/gl3.h>
+#	else
+#		include <GLES2/gl2.h>
+#	endif
+#endif
+
+namespace deqp
+{
+namespace egl
+{
+namespace gles2
+{
+
+#if defined(DEQP_SUPPORT_GLES2) || defined(DEQP_SUPPORT_GLES3)
+
+void clear (int x, int y, int width, int height, const tcu::Vec4& color)
+{
+	glEnable(GL_SCISSOR_TEST);
+	glScissor(x, y, width, height);
+	glClearColor(color.x(), color.y(), color.z(), color.w());
+	glClear(GL_COLOR_BUFFER_BIT);
+	glDisable(GL_SCISSOR_TEST);
+}
+
+void readPixels (tcu::Surface& dst, int x, int y, int width, int height)
+{
+	dst.setSize(width, height);
+	glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr());
+}
+
+#else // DEQP_SUPPORT_GLES2 || DEQP_SUPPORT_GLES3
+
+void clear (int x, int y, int width, int height, const tcu::Vec4& color)
+{
+	DE_UNREF(x && y && width && height);
+	DE_UNREF(color);
+	throw tcu::NotSupportedError("OpenGL ES 2 is not supported", "", __FILE__, __LINE__);
+}
+
+void readPixels (tcu::Surface& dst, int x, int y, int width, int height)
+{
+	DE_UNREF(x && y && width && height);
+	DE_UNREF(dst);
+	throw tcu::NotSupportedError("OpenGL ES 2 is not supported", "", __FILE__, __LINE__);
+}
+
+#endif // DEQP_SUPPORT_GLES2 || DEQP_SUPPORT_GLES3
+
+} // gles2
+} // egl
+} // deqp
diff --git a/modules/egl/teglGLES2RenderUtil.hpp b/modules/egl/teglGLES2RenderUtil.hpp
new file mode 100644
index 0000000..7425bbe
--- /dev/null
+++ b/modules/egl/teglGLES2RenderUtil.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLGLES2RENDERUTIL_HPP
+#define _TEGLGLES2RENDERUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES2 render utils.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuSurface.hpp"
+#include "tcuVector.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+namespace gles2
+{
+
+void	clear		(int x, int y, int width, int height, const tcu::Vec4& color);
+void	readPixels	(tcu::Surface& dst, int x, int y, int width, int height);
+
+} // gles2
+} // egl
+} // deqp
+
+#endif // _TEGLGLES2RENDERUTIL_HPP
diff --git a/modules/egl/teglGLES2SharedRenderingPerfTests.cpp b/modules/egl/teglGLES2SharedRenderingPerfTests.cpp
new file mode 100644
index 0000000..90ac7ad
--- /dev/null
+++ b/modules/egl/teglGLES2SharedRenderingPerfTests.cpp
@@ -0,0 +1,1631 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES2 resource sharing performnace tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglGLES2SharedRenderingPerfTests.hpp"
+
+#include "tcuTestLog.hpp"
+
+#include "gluDefs.hpp"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include "deThread.hpp"
+#include "deClock.h"
+#include "deStringUtil.hpp"
+
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <cmath>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace
+{
+
+struct TestConfig
+{
+	enum TextureType
+	{
+		TEXTURETYPE_TEXTURE = 0,
+		TEXTURETYPE_SHARED_TEXTURE,
+		TEXTURETYPE_IMAGE,
+		TEXTURETYPE_SHARED_IMAGE,
+		TEXTURETYPE_SHARED_IMAGE_TEXTURE
+	};
+
+	int			threadCount;
+	int			perThreadContextCount;
+
+	int			frameCount;
+	int			drawCallCount;
+	int			triangleCount;
+
+	bool		sharedContexts;
+
+	bool		useCoordBuffer;
+	bool		sharedCoordBuffer;
+
+	bool		useIndices;
+	bool		useIndexBuffer;
+	bool		sharedIndexBuffer;
+
+	bool		useTexture;
+	TextureType	textureType;
+
+	bool		sharedProgram;
+
+	int			textureWidth;
+	int			textureHeight;
+
+	int			surfaceWidth;
+	int			surfaceHeight;
+};
+
+class TestContext
+{
+public:
+						TestContext		(EglTestContext& eglTestCtx, EGLConfig eglConfig, const TestConfig& config, bool share, TestContext* parent);
+						~TestContext	(void);
+
+	void				render			(void);
+
+	EGLContext			getEGLContext	(void) { return m_eglContext; }
+
+	GLuint				getCoordBuffer	(void) const { return m_coordBuffer;	}
+	GLuint				getIndexBuffer	(void) const { return m_indexBuffer;	}
+	GLuint				getTexture		(void) const { return m_texture;		}
+	GLuint				getProgram		(void) const { return m_program;		}
+	EGLImageKHR			getEGLImage		(void) const { return m_eglImage;		}
+
+private:
+	TestContext*				m_parent;
+	EglTestContext&				m_testCtx;
+	TestConfig					m_config;
+	EGLContext					m_eglContext;
+	EGLSurface					m_eglSurface;
+
+	glw::Functions				m_gl;
+
+	PFNEGLCREATEIMAGEKHRPROC	m_eglCreateImageKHR;
+	PFNEGLDESTROYIMAGEKHRPROC	m_eglDestroyImageKHR;
+
+	PFNGLEGLIMAGETARGETTEXTURE2DOESPROC	m_glEGLImageTargetTexture2DOES;
+
+	GLuint						m_coordBuffer;
+	GLuint						m_indexBuffer;
+	GLuint						m_texture;
+	GLuint						m_program;
+
+	EGLImageKHR					m_eglImage;
+
+	GLuint						m_coordLoc;
+	GLuint						m_textureLoc;
+
+	vector<float>				m_coordData;
+	vector<deUint16>			m_indexData;
+
+	EGLImageKHR					createEGLImage			(void);
+	GLuint						createTextureFromImage	(EGLImageKHR image);
+
+	// Not supported
+	TestContext&	operator=		(const TestContext&);
+					TestContext		(const TestContext&);
+};
+
+namespace
+{
+
+bool checkExtension (const char* extensions, const char* extension)
+{
+	TCU_CHECK(extensions);
+
+	std::istringstream	stream(extensions);
+	string				ext;
+
+	while (std::getline(stream, ext, ' '))
+	{
+		if (ext == extension)
+			return true;
+	}
+
+	return false;
+}
+
+void createCoordData (vector<float>& data, const TestConfig& config)
+{
+	if (config.useIndices)
+	{
+		for (int triangleNdx = 0; triangleNdx < 2; triangleNdx++)
+		{
+			const float x1 = -1.0f;
+			const float y1 = -1.0f;
+
+			const float x2 = 1.0f;
+			const float y2 = 1.0f;
+
+			const float side = ((triangleNdx % 2) == 0 ? 1.0f : -1.0f);
+
+			data.push_back(side * x1);
+			data.push_back(side * y1);
+
+			data.push_back(side * x2);
+			data.push_back(side * y1);
+
+			data.push_back(side * x2);
+			data.push_back(side * y2);
+		}
+	}
+	else
+	{
+		data.reserve(config.triangleCount * 3 * 2);
+
+		for (int triangleNdx = 0; triangleNdx < config.triangleCount; triangleNdx++)
+		{
+			const float x1 = -1.0f;
+			const float y1 = -1.0f;
+
+			const float x2 = 1.0f;
+			const float y2 = 1.0f;
+
+			const float side = ((triangleNdx % 2) == 0 ? 1.0f : -1.0f);
+
+			data.push_back(side * x1);
+			data.push_back(side * y1);
+
+			data.push_back(side * x2);
+			data.push_back(side * y1);
+
+			data.push_back(side * x2);
+			data.push_back(side * y2);
+		}
+	}
+}
+
+GLuint createCoordBuffer (const glw::Functions& gl, const TestConfig& config)
+{
+	GLuint			buffer;
+	vector<float>	data;
+
+	createCoordData(data, config);
+
+	gl.genBuffers(1, &buffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenBuffers()");
+	gl.bindBuffer(GL_ARRAY_BUFFER, buffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+	gl.bufferData(GL_ARRAY_BUFFER, (GLsizei)(data.size() * sizeof(float)), &(data[0]), GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBufferData()");
+	gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+
+	return buffer;
+}
+
+void createIndexData (vector<deUint16>& data, const TestConfig& config)
+{
+	for (int triangleNdx = 0; triangleNdx < config.triangleCount; triangleNdx++)
+	{
+		if ((triangleNdx % 2) == 0)
+		{
+			data.push_back(0);
+			data.push_back(1);
+			data.push_back(2);
+		}
+		else
+		{
+			data.push_back(2);
+			data.push_back(3);
+			data.push_back(0);
+		}
+	}
+}
+
+GLuint createIndexBuffer (const glw::Functions& gl, const TestConfig& config)
+{
+	GLuint				buffer;
+	vector<deUint16>	data;
+
+	createIndexData(data, config);
+
+	gl.genBuffers(1, &buffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenBuffers()");
+	gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+	gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizei)(data.size() * sizeof(deUint16)), &(data[0]), GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBufferData()");
+	gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+
+	return buffer;
+}
+
+void createTextureData (vector<deUint8>& data, const TestConfig& config)
+{
+	for (int x = 0; x < config.textureWidth; x++)
+	{
+		for (int y = 0; y < config.textureHeight; y++)
+		{
+			data.push_back((255*x)/255);
+			data.push_back((255*y)/255);
+			data.push_back((255*x*y)/(255*255));
+			data.push_back(255);
+		}
+	}
+}
+
+GLuint createTexture (const glw::Functions& gl, const TestConfig& config)
+{
+	GLuint			texture;
+	vector<deUint8>	data;
+
+	createTextureData(data, config);
+
+	gl.genTextures(1, &texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures()");
+	gl.bindTexture(GL_TEXTURE_2D, texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
+	gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, config.textureWidth, config.textureWidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, &(data[0]));
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage2D()");
+	gl.bindTexture(GL_TEXTURE_2D, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+	return texture;
+}
+
+GLuint createProgram (const glw::Functions& gl, const TestConfig& config)
+{
+	GLuint	vertexShader	= gl.createShader(GL_VERTEX_SHADER);
+	GLuint	fragmentShader	= gl.createShader(GL_FRAGMENT_SHADER);
+
+	if (config.useTexture)
+	{
+		const char* vertexShaderSource =
+		"attribute mediump vec2 a_coord;\n"
+		"varying mediump vec2 v_texCoord;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tv_texCoord = 0.5 * a_coord + vec2(0.5);\n"
+		"\tgl_Position = vec4(a_coord, 0.0, 1.0);\n"
+		"}\n";
+
+		const char* fragmentShaderSource =
+		"uniform sampler2D u_sampler;\n"
+		"varying mediump vec2 v_texCoord;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tgl_FragColor = texture2D(u_sampler, v_texCoord);\n"
+		"}\n";
+
+		gl.shaderSource(vertexShader, 1, &vertexShaderSource, NULL);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glShaderSource()");
+		gl.shaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glShaderSource()");
+	}
+	else
+	{
+		const char* vertexShaderSource =
+		"attribute mediump vec2 a_coord;\n"
+		"varying mediump vec4 v_color;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tv_color = vec4(0.5 * a_coord + vec2(0.5), 0.5, 1.0);\n"
+		"\tgl_Position = vec4(a_coord, 0.0, 1.0);\n"
+		"}\n";
+
+		const char* fragmentShaderSource =
+		"varying mediump vec4 v_color;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tgl_FragColor = v_color;\n"
+		"}\n";
+
+		gl.shaderSource(vertexShader, 1, &vertexShaderSource, NULL);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glShaderSource()");
+		gl.shaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glShaderSource()");
+	}
+
+	gl.compileShader(vertexShader);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glCompileShader()");
+	gl.compileShader(fragmentShader);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glCompileShader()");
+
+	{
+		GLint status;
+
+		gl.getShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv()");
+
+		if (!status)
+		{
+			string	log;
+			GLint length;
+
+			gl.getShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &length);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv()");
+			log.resize(length, 0);
+
+			gl.getShaderInfoLog(vertexShader, (GLsizei)log.size(), &length, &(log[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderInfoLog()");
+
+			throw std::runtime_error(log.c_str());
+		}
+	}
+
+	{
+		GLint status;
+
+		gl.getShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv()");
+
+		if (!status)
+		{
+			string	log;
+			GLint length;
+
+			gl.getShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &length);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv()");
+			log.resize(length, 0);
+
+			gl.getShaderInfoLog(fragmentShader, (GLsizei)log.size(), &length, &(log[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderInfoLog()");
+
+			throw std::runtime_error(log.c_str());
+		}
+	}
+
+	{
+		GLuint program = gl.createProgram();
+
+		gl.attachShader(program, vertexShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glAttachShader()");
+		gl.attachShader(program, fragmentShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glAttachShader()");
+
+		gl.linkProgram(program);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glLinkProgram()");
+
+		{
+			GLint status;
+
+			gl.getProgramiv(program, GL_LINK_STATUS, &status);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramiv()");
+
+			if (!status)
+			{
+				string	log;
+				GLsizei	length;
+
+				gl.getProgramInfoLog(program, 0, &length, NULL);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramInfoLog()");
+				log.resize(length, 0);
+
+				gl.getProgramInfoLog(program, (GLsizei)log.size(), &length, &(log[0]));
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramInfoLog()");
+
+				throw std::runtime_error(log.c_str());
+			}
+		}
+
+		gl.deleteShader(vertexShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteShader()");
+		gl.deleteShader(fragmentShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteShader()");
+
+		return program;
+	}
+}
+
+EGLContext createEGLContext (EglTestContext& testCtx, EGLConfig eglConfig, EGLContext share)
+{
+	const EGLint attribList[] = {
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+	TCU_CHECK_EGL_CALL(eglBindAPI(EGL_OPENGL_ES_API));
+
+	EGLContext context = eglCreateContext(testCtx.getDisplay().getEGLDisplay(), eglConfig, share, attribList);
+	TCU_CHECK_EGL_MSG("eglCreateContext()");
+
+	return context;
+}
+
+EGLSurface createEGLSurface (EglTestContext& testCtx, EGLConfig eglConfig, const TestConfig& config)
+{
+	const EGLint attribList[] = {
+		EGL_WIDTH,	config.surfaceWidth,
+		EGL_HEIGHT, config.surfaceHeight,
+		EGL_NONE
+	};
+
+	EGLSurface surface = eglCreatePbufferSurface(testCtx.getDisplay().getEGLDisplay(), eglConfig, attribList);
+	TCU_CHECK_EGL_MSG("eglCreatePbufferSurface()");
+
+	return surface;
+}
+
+} // anonymous
+
+TestContext::TestContext (EglTestContext& testCtx, EGLConfig eglConfig, const TestConfig& config, bool share, TestContext* parent)
+	: m_parent				(parent)
+	, m_testCtx				(testCtx)
+	, m_config				(config)
+	, m_eglContext			(EGL_NO_CONTEXT)
+	, m_eglSurface			(EGL_NO_SURFACE)
+
+	, m_eglCreateImageKHR	(NULL)
+	, m_eglDestroyImageKHR	(NULL)
+
+	, m_glEGLImageTargetTexture2DOES			(NULL)
+
+	, m_coordBuffer			(0)
+	, m_indexBuffer			(0)
+	, m_texture				(0)
+	, m_program				(0)
+	, m_eglImage			(EGL_NO_IMAGE_KHR)
+{
+	if (m_config.textureType == TestConfig::TEXTURETYPE_IMAGE
+		|| m_config.textureType == TestConfig::TEXTURETYPE_SHARED_IMAGE
+		|| m_config.textureType == TestConfig::TEXTURETYPE_SHARED_IMAGE_TEXTURE)
+	{
+		if (	!checkExtension(eglQueryString(m_testCtx.getDisplay().getEGLDisplay(), EGL_EXTENSIONS), "EGL_KHR_image_base")
+			||	!checkExtension(eglQueryString(m_testCtx.getDisplay().getEGLDisplay(), EGL_EXTENSIONS), "EGL_KHR_gl_texture_2D_image"))
+			throw tcu::NotSupportedError("EGL_KHR_image_base extensions not supported", "", __FILE__, __LINE__);
+
+		m_eglCreateImageKHR		= (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
+		m_eglDestroyImageKHR	= (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
+
+		TCU_CHECK(m_eglCreateImageKHR);
+		TCU_CHECK(m_eglDestroyImageKHR);
+	}
+
+	m_eglContext = createEGLContext(m_testCtx, eglConfig, (share && parent ? parent->getEGLContext() : EGL_NO_CONTEXT));
+	m_eglSurface = createEGLSurface(m_testCtx, eglConfig, config);
+
+	TCU_CHECK_EGL_CALL(eglMakeCurrent(m_testCtx.getDisplay().getEGLDisplay(), m_eglSurface, m_eglSurface, m_eglContext));
+
+	m_testCtx.getGLFunctions(m_gl, glu::ApiType::es(2,0));
+
+	if (m_config.textureType == TestConfig::TEXTURETYPE_IMAGE
+		|| m_config.textureType == TestConfig::TEXTURETYPE_SHARED_IMAGE
+		|| m_config.textureType == TestConfig::TEXTURETYPE_SHARED_IMAGE_TEXTURE)
+	{
+		if (!checkExtension((const char*)m_gl.getString(GL_EXTENSIONS), "GL_OES_EGL_image"))
+			throw tcu::NotSupportedError("GL_OES_EGL_image extensions not supported", "", __FILE__, __LINE__);
+
+		m_glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
+
+		TCU_CHECK(m_glEGLImageTargetTexture2DOES);
+	}
+
+	if (m_config.useCoordBuffer && (!m_config.sharedCoordBuffer || !parent))
+		m_coordBuffer = createCoordBuffer(m_gl, m_config);
+	else if (m_config.useCoordBuffer && m_config.sharedCoordBuffer)
+		m_coordBuffer = parent->getCoordBuffer();
+	else
+		createCoordData(m_coordData, m_config);
+
+	if (m_config.useIndexBuffer && (!m_config.sharedIndexBuffer || !parent))
+		m_indexBuffer = createIndexBuffer(m_gl, m_config);
+	else if (m_config.useIndexBuffer && m_config.sharedIndexBuffer)
+		m_indexBuffer = parent->getIndexBuffer();
+	else if (m_config.useIndices)
+		createIndexData(m_indexData, m_config);
+
+	if (m_config.useTexture)
+	{
+		if (m_config.textureType == TestConfig::TEXTURETYPE_TEXTURE)
+			m_texture = createTexture(m_gl, m_config);
+		else if (m_config.textureType == TestConfig::TEXTURETYPE_SHARED_TEXTURE)
+		{
+			if (parent)
+				m_texture = parent->getTexture();
+			else
+				m_texture = createTexture(m_gl, m_config);
+		}
+		else if (m_config.textureType == TestConfig::TEXTURETYPE_IMAGE)
+		{
+			m_eglImage	= createEGLImage();
+			m_texture	= createTextureFromImage(m_eglImage);
+		}
+		else if (m_config.textureType == TestConfig::TEXTURETYPE_SHARED_IMAGE)
+		{
+			if (parent)
+				m_eglImage = parent->getEGLImage();
+			else
+				m_eglImage = createEGLImage();
+
+			m_texture = createTextureFromImage(m_eglImage);
+		}
+		else if (m_config.textureType == TestConfig::TEXTURETYPE_SHARED_IMAGE_TEXTURE)
+		{
+			if (parent)
+				m_texture = parent->getTexture();
+			else
+			{
+				m_eglImage	= createEGLImage();
+				m_texture	= createTextureFromImage(m_eglImage);
+			}
+		}
+	}
+
+	if (!m_config.sharedProgram || !parent)
+		m_program = createProgram(m_gl, m_config);
+	else if (m_config.sharedProgram)
+		m_program = parent->getProgram();
+
+	m_coordLoc = m_gl.getAttribLocation(m_program, "a_coord");
+
+	if (m_config.useTexture)
+		m_textureLoc = m_gl.getUniformLocation(m_program, "u_sampler");
+
+	TCU_CHECK_EGL_CALL(eglMakeCurrent(m_testCtx.getDisplay().getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+}
+
+EGLImageKHR TestContext::createEGLImage (void)
+{
+	GLuint sourceTexture = createTexture(m_gl, m_config);
+
+	try
+	{
+		const EGLint attribList[] = {
+			EGL_GL_TEXTURE_LEVEL_KHR, 0,
+			EGL_NONE
+		};
+
+		EGLImageKHR image = m_eglCreateImageKHR(m_testCtx.getDisplay().getEGLDisplay(), m_eglContext, EGL_GL_TEXTURE_2D_KHR, (EGLClientBuffer)(deUintptr)sourceTexture, attribList);
+		TCU_CHECK_EGL_MSG("eglCreateImageKHR()");
+
+		m_gl.deleteTextures(1, &sourceTexture);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "eglCreateImageKHR()");
+	
+		return image;
+	}
+	catch (...)
+	{
+		m_gl.deleteTextures(1, &sourceTexture);
+		throw;
+	}
+}
+
+GLuint TestContext::createTextureFromImage (EGLImageKHR image)
+{
+	GLuint texture = 0;
+
+	try
+	{
+		m_gl.genTextures(1, &texture);
+		m_gl.bindTexture(GL_TEXTURE_2D, texture);
+		m_glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+		m_gl.bindTexture(GL_TEXTURE_2D, 0);
+
+		return texture;
+	}
+	catch (...)
+	{
+		m_gl.deleteTextures(1, &texture);
+		throw;
+	}
+}
+
+
+TestContext::~TestContext (void)
+{
+	EGLDisplay display = m_testCtx.getDisplay().getEGLDisplay();
+
+	TCU_CHECK_EGL_CALL(eglMakeCurrent(display, m_eglSurface, m_eglSurface, m_eglContext));
+
+	if (m_parent == NULL && m_eglImage)
+		TCU_CHECK_EGL_CALL(m_eglDestroyImageKHR(display, m_eglImage));
+
+	TCU_CHECK_EGL_CALL(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+	TCU_CHECK_EGL_CALL(eglDestroyContext(display, m_eglContext));
+	TCU_CHECK_EGL_CALL(eglDestroySurface(display, m_eglSurface));
+}
+
+void TestContext::render (void)
+{
+	EGLDisplay display = m_testCtx.getDisplay().getEGLDisplay();
+
+	eglMakeCurrent(display, m_eglSurface, m_eglSurface, m_eglContext);
+
+	for (int frameNdx = 0; frameNdx < m_config.frameCount; frameNdx++)
+	{
+		m_gl.clearColor(0.75f, 0.6f, 0.5f, 1.0f);
+		m_gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+
+		for (int callNdx = 0; callNdx < m_config.drawCallCount; callNdx++)
+		{
+			m_gl.useProgram(m_program);
+			m_gl.enableVertexAttribArray(m_coordLoc);
+
+			if (m_config.useCoordBuffer)
+			{
+				m_gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffer);
+				m_gl.vertexAttribPointer(m_coordLoc, 2, GL_FLOAT, GL_FALSE, 0, 0);
+				m_gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+			}
+			else
+				m_gl.vertexAttribPointer(m_coordLoc, 2, GL_FLOAT, GL_FALSE, 0, &(m_coordData[0]));
+
+			if (m_config.useTexture)
+			{
+				m_gl.bindTexture(GL_TEXTURE_2D, m_texture);
+				m_gl.uniform1i(m_textureLoc, 0);
+			}
+
+			if (m_config.useIndices)
+			{
+				if (m_config.useIndexBuffer)
+				{
+					m_gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
+					m_gl.drawElements(GL_TRIANGLES, m_config.triangleCount, GL_UNSIGNED_SHORT, 0);
+				}
+				else
+					m_gl.drawElements(GL_TRIANGLES, m_config.triangleCount, GL_UNSIGNED_SHORT, &(m_indexData[0]));
+			}
+			else
+				m_gl.drawArrays(GL_TRIANGLES, 0, m_config.triangleCount);
+
+
+			if (m_config.useTexture)
+				m_gl.bindTexture(GL_TEXTURE_2D, 0);
+
+			m_gl.disableVertexAttribArray(m_coordLoc);
+
+			m_gl.useProgram(0);
+		}
+
+
+		eglSwapBuffers(display, m_eglSurface);
+	}
+
+	m_gl.finish();
+	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glFinish()");
+	TCU_CHECK_EGL_CALL(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+}
+
+class TestThread : de::Thread
+{
+public:
+					TestThread		(const vector<TestContext*> contexts);
+					~TestThread		(void);
+
+	void			start			(void);
+	void			join			(void);
+	void			log				(TestLog& log);
+
+	bool			resultOk		(void) { return m_isOk; }
+
+private:
+	vector<TestContext*>	m_contexts;
+	bool					m_isOk;
+	string					m_errorString;
+
+	deUint64				m_beginTimeUs;
+	deUint64				m_endTimeUs;
+
+	deUint64				m_joinBeginUs;
+	deUint64				m_joinEndUs;
+
+	deUint64				m_startBeginUs;
+	deUint64				m_startEndUs;
+
+
+	virtual void	run			(void);
+
+	TestThread&		operator=	(const TestThread&);
+					TestThread	(const TestThread&);
+};
+
+TestThread::TestThread (const vector<TestContext*> contexts)
+	: m_contexts		(contexts)
+	, m_isOk			(false)
+	, m_errorString		("")
+	, m_beginTimeUs		(0)
+	, m_endTimeUs		(0)
+	, m_joinBeginUs		(0)
+	, m_joinEndUs		(0)
+	, m_startBeginUs	(0)
+	, m_startEndUs		(0)
+{
+}
+
+TestThread::~TestThread (void)
+{
+	m_contexts.clear();
+}
+
+void TestThread::log (TestLog& testLog)
+{
+	if (!m_isOk)
+		testLog << TestLog::Message << "Thread failed: " << m_errorString << TestLog::EndMessage;
+}
+
+void TestThread::start (void)
+{
+	m_startBeginUs = deGetMicroseconds();
+	de::Thread::start();
+	m_startEndUs = deGetMicroseconds();
+}
+
+void TestThread::join (void)
+{
+	m_joinBeginUs = deGetMicroseconds();
+	de::Thread::join();
+	m_joinEndUs = deGetMicroseconds();
+}
+
+void TestThread::run (void)
+{
+	try
+	{
+		m_beginTimeUs = deGetMicroseconds();
+
+		for (int contextNdx = 0; contextNdx < (int)m_contexts.size(); contextNdx++)
+			m_contexts[contextNdx]->render();
+
+		m_isOk		= true;
+		m_endTimeUs = deGetMicroseconds();
+	}
+	catch (const std::runtime_error& error)
+	{
+		m_isOk			= false;
+		m_errorString	= error.what();
+	}
+	catch (...)
+	{
+		m_isOk			= false;
+		m_errorString	= "Got unknown exception";
+	}
+}
+
+class SharedRenderingPerfCase : public TestCase
+{
+public:
+								SharedRenderingPerfCase		(EglTestContext& eglTestCtx, const TestConfig& config, const char* name, const char* description);
+								~SharedRenderingPerfCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+	TestConfig					m_config;
+	const int					m_iterationCount;
+	vector<TestContext*>		m_contexts;
+	vector<deUint64>			m_results;
+
+	SharedRenderingPerfCase&	operator=					(const SharedRenderingPerfCase&);
+								SharedRenderingPerfCase		(const SharedRenderingPerfCase&);
+};
+
+SharedRenderingPerfCase::SharedRenderingPerfCase (EglTestContext& eglTestCtx, const TestConfig& config, const char* name, const char* description)
+	: TestCase			(eglTestCtx, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_config			(config)
+	, m_iterationCount	(30)
+{
+}
+
+SharedRenderingPerfCase::~SharedRenderingPerfCase (void)
+{
+	deinit();
+}
+
+void SharedRenderingPerfCase::init (void)
+{
+	EGLConfig eglConfig;
+
+	{
+		const EGLint	attribList[] = {
+			EGL_SURFACE_TYPE,		EGL_PBUFFER_BIT,
+			EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
+			EGL_NONE
+		};
+
+		EGLint		configCount = 0;
+		EGLDisplay	display	= m_eglTestCtx.getDisplay().getEGLDisplay();
+
+		TCU_CHECK_EGL_CALL(eglChooseConfig(display, attribList, &eglConfig, 1, &configCount));
+
+		TCU_CHECK(configCount != 0);
+	}
+
+	// Create contexts and resources
+	for (int threadNdx = 0; threadNdx < m_config.threadCount * m_config.perThreadContextCount; threadNdx++)
+		m_contexts.push_back(new TestContext(m_eglTestCtx, eglConfig, m_config, m_config.sharedContexts, (threadNdx == 0 ? NULL : m_contexts[threadNdx-1])));
+}
+
+void SharedRenderingPerfCase::deinit (void)
+{
+	// Destroy resources and contexts
+	for (int threadNdx = 0; threadNdx < (int)m_contexts.size(); threadNdx++)
+	{
+		delete m_contexts[threadNdx];
+		m_contexts[threadNdx] = NULL;
+	}
+
+	m_contexts.clear();
+	m_results.clear();
+}
+
+namespace
+{
+
+void createThreads (vector<TestThread*>& threads, int threadCount, int perThreadContextCount, vector<TestContext*>& contexts)
+{
+	DE_ASSERT(threadCount * perThreadContextCount == (int)contexts.size());
+	DE_ASSERT(threads.empty());
+
+	vector<TestContext*> threadContexts;
+
+	for (int threadNdx = 0; threadNdx < threadCount; threadNdx++)
+	{
+		for (int contextNdx = 0; contextNdx < perThreadContextCount; contextNdx++)
+			threadContexts.push_back(contexts[threadNdx * perThreadContextCount + contextNdx]);
+
+		threads.push_back(new TestThread(threadContexts));
+
+		threadContexts.clear();
+	}
+}
+
+void destroyThreads (vector<TestThread*>& threads)
+{
+	for (int threadNdx = 0; threadNdx < (int)threads.size(); threadNdx++)
+	{
+		delete threads[threadNdx];
+		threads[threadNdx] = NULL;
+	}
+
+	threads.clear();
+}
+
+void startThreads (vector<TestThread*>& threads)
+{
+	for (int threadNdx = 0; threadNdx < (int)threads.size(); threadNdx++)
+		threads[threadNdx]->start();
+}
+
+void joinThreads (vector<TestThread*>& threads)
+{
+	for (int threadNdx = 0; threadNdx < (int)threads.size(); threadNdx++)
+		threads[threadNdx]->join();
+}
+
+bool threadResultsOk (const vector<TestThread*>& threads)
+{
+	for (int threadNdx = 0; threadNdx < (int)threads.size(); threadNdx++)
+	{
+		if (!threads[threadNdx]->resultOk())
+			return false;
+	}
+
+	return true;
+}
+
+void logAndSetResults (tcu::TestContext& testCtx, const vector<deUint64>& r)
+{
+	TestLog& log		= testCtx.getLog();
+	vector<deUint64>	resultsUs = r;
+	deUint64			sum = 0;
+	deUint64			average;
+	deUint64			median;
+	double				deviation;
+
+	log << TestLog::SampleList("Result", "Result")
+		<< TestLog::SampleInfo << TestLog::ValueInfo("Time", "Time", "us", QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< TestLog::EndSampleInfo;
+
+	for (int resultNdx = 0; resultNdx < (int)resultsUs.size(); resultNdx++)
+		log << TestLog::Sample << deInt64(resultsUs[resultNdx]) << TestLog::EndSample;
+
+	log << TestLog::EndSampleList;
+
+	std::sort(resultsUs.begin(), resultsUs.end());
+
+	for (int resultNdx = 0; resultNdx < (int)resultsUs.size(); resultNdx++)
+		sum += resultsUs[resultNdx];
+
+	average	= sum / resultsUs.size();
+	median	= resultsUs[resultsUs.size() / 2];
+
+	deviation = 0.0;
+	for (int resultNdx = 0; resultNdx < (int)resultsUs.size(); resultNdx++)
+		deviation += (resultsUs[resultNdx] - average) * (resultsUs[resultNdx] - average);
+
+	deviation = std::sqrt((double)(deviation/resultsUs.size()));
+
+	{
+		tcu::ScopedLogSection	section(log, "Statistics from results", "Statistics from results");
+
+		log << TestLog::Message
+		<< "Average: "					<< (average/1000.0)											<< "ms\n"
+		<< "Standart deviation: "		<< (deviation/1000.0)										<< "ms\n"
+		<< "Standart error of mean: "	<< ((deviation/std::sqrt((double)resultsUs.size()))/1000.0)	<< "ms\n"
+		<< "Median: "					<< (median/1000.0)											<< "ms\n"
+		<< TestLog::EndMessage;
+	}
+
+	testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)(average/1000.0), 2).c_str());
+}
+
+void logTestConfig (TestLog& log, const TestConfig& config)
+{
+	tcu::ScopedLogSection threadSection(log, "Test info", "Test information");
+
+	log << TestLog::Message << "Total triangles rendered: : "							<< config.triangleCount * config.drawCallCount * config.frameCount * config.perThreadContextCount * config.threadCount << TestLog::EndMessage;
+	log << TestLog::Message << "Number of threads: "									<< config.threadCount << TestLog::EndMessage;
+	log << TestLog::Message << "Number of contexts used to render with each thread: "	<< config.perThreadContextCount << TestLog::EndMessage;
+	log << TestLog::Message << "Number of frames rendered with each context: "			<< config.frameCount << TestLog::EndMessage;
+	log << TestLog::Message << "Number of draw calls performed by each frame: "			<< config.drawCallCount << TestLog::EndMessage;
+	log << TestLog::Message << "Number of triangles rendered by each draw call: "		<< config.triangleCount << TestLog::EndMessage;
+
+	if (config.sharedContexts)
+		log << TestLog::Message << "Shared contexts." << TestLog::EndMessage;
+	else
+		log << TestLog::Message << "No shared contexts." << TestLog::EndMessage;
+
+	if (config.useCoordBuffer)
+		log << TestLog::Message << (config.sharedCoordBuffer ? "Shared " : "") << "Coordinate buffer" << TestLog::EndMessage;
+	else
+		log << TestLog::Message << "Coordinates from pointer" << TestLog::EndMessage;
+
+	if (config.useIndices)
+		log << TestLog::Message << "Using glDrawElements with indices from " << (config.sharedIndexBuffer ? "shared " : "") << (config.useIndexBuffer ? "buffer." : "pointer.") << TestLog::EndMessage;
+
+	if (config.useTexture)
+	{
+		if (config.textureType == TestConfig::TEXTURETYPE_TEXTURE)
+			log << TestLog::Message << "Use texture." << TestLog::EndMessage;
+		else if (config.textureType == TestConfig::TEXTURETYPE_SHARED_TEXTURE)
+			log << TestLog::Message << "Use shared texture." << TestLog::EndMessage;
+		else if (config.textureType == TestConfig::TEXTURETYPE_IMAGE)
+			log << TestLog::Message << "Use texture created from EGLImage." << TestLog::EndMessage;
+		else if (config.textureType == TestConfig::TEXTURETYPE_SHARED_IMAGE)
+			log << TestLog::Message << "Use texture created from shared EGLImage." << TestLog::EndMessage;
+		else if (config.textureType == TestConfig::TEXTURETYPE_SHARED_IMAGE_TEXTURE)
+			log << TestLog::Message << "Use shared texture created from EGLImage." << TestLog::EndMessage;
+		else
+			DE_ASSERT(false);
+
+		log << TestLog::Message << "Texture size: " << config.textureWidth << "x" << config.textureHeight << TestLog::EndMessage;
+	}
+
+	if (config.sharedProgram)
+		log << TestLog::Message << "Shared program." << TestLog::EndMessage;
+
+	log << TestLog::Message << "Surface size: " << config.surfaceWidth << "x" << config.surfaceHeight << TestLog::EndMessage;
+}
+
+} // anonymous
+
+TestCase::IterateResult SharedRenderingPerfCase::iterate (void)
+{
+	deUint64			beginTimeUs;
+	deUint64			endTimeUs;
+	vector<TestThread*>	threads;
+
+	if (m_results.empty())
+		logTestConfig(m_testCtx.getLog(), m_config);
+
+	createThreads(threads, m_config.threadCount, m_config.perThreadContextCount, m_contexts);
+
+	beginTimeUs = deGetMicroseconds();
+
+	startThreads(threads);
+	joinThreads(threads);
+
+	endTimeUs = deGetMicroseconds();
+
+	if (!threadResultsOk(threads))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		return STOP;
+	}
+
+	destroyThreads(threads);
+
+	m_results.push_back(endTimeUs - beginTimeUs);
+
+	if ((int)m_results.size() == m_iterationCount)
+	{
+		logAndSetResults(m_testCtx, m_results);
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+string createTestName(int threads, int perThreadContextCount)
+{
+	std::ostringstream stream;
+
+	stream << threads << (threads == 1 ? "_thread_" : "_threads_") << perThreadContextCount << (perThreadContextCount == 1 ? "_context" : "_contexts");
+
+	return stream.str();
+}
+
+} // anonymous
+
+GLES2SharedRenderingPerfTests::GLES2SharedRenderingPerfTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "gles2_shared_render", "")
+{
+}
+
+void GLES2SharedRenderingPerfTests::init (void)
+{
+	TestConfig basicConfig;
+
+	basicConfig.threadCount					= 1;
+	basicConfig.perThreadContextCount		= 1;
+
+	basicConfig.sharedContexts				= true;
+	basicConfig.frameCount					= 10;
+	basicConfig.drawCallCount				= 10;
+	basicConfig.triangleCount				= 100;
+
+	basicConfig.useCoordBuffer				= true;
+	basicConfig.sharedCoordBuffer			= false;
+
+	basicConfig.useIndices					= true;
+	basicConfig.useIndexBuffer				= true;
+	basicConfig.sharedIndexBuffer			= false;
+
+	basicConfig.useTexture					= true;
+	basicConfig.textureType					= TestConfig::TEXTURETYPE_TEXTURE;
+
+	basicConfig.sharedProgram				= false;
+
+	basicConfig.textureWidth				= 128;
+	basicConfig.textureHeight				= 128;
+
+	basicConfig.surfaceWidth				= 256;
+	basicConfig.surfaceHeight				= 256;
+
+	const int	threadCounts[]				= { 1, 2, 4 };
+	const int	perThreadContextCounts[]	= { 1, 2, 4 };
+
+	// Add no sharing tests
+	{
+		TestCaseGroup* sharedNoneGroup = new TestCaseGroup(m_eglTestCtx, "no_shared_context", "Tests without sharing contexts.");
+
+		for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+		{
+			int threadCount = threadCounts[threadCountNdx];
+
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(perThreadContextCounts); contextCountNdx++)
+			{
+				int contextCount = perThreadContextCounts[contextCountNdx];
+
+				if (threadCount * contextCount != 4 && threadCount * contextCount != 1)
+					continue;
+
+				TestConfig config 				= basicConfig;
+				config.threadCount				= threadCount;
+				config.perThreadContextCount	= contextCount;
+				config.sharedContexts			= false;
+
+				{
+					TestConfig smallConfig		= config;
+					smallConfig.triangleCount	= 1;
+					smallConfig.drawCallCount	= 1000;
+					smallConfig.frameCount		= 10;
+
+					if (threadCount * contextCount == 1)
+						smallConfig.frameCount *= 4;
+
+					sharedNoneGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, smallConfig, (createTestName(threadCount, contextCount) + "_small_call").c_str(), ""));
+				}
+
+				{
+					TestConfig bigConfig	= config;
+					bigConfig.triangleCount	= 1000;
+					bigConfig.drawCallCount	= 1;
+					bigConfig.frameCount	= 10;
+
+					if (threadCount * contextCount == 1)
+						bigConfig.frameCount *= 4;
+
+					sharedNoneGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, bigConfig, (createTestName(threadCount, contextCount) + "_big_call").c_str(), ""));
+				}
+			}
+		}
+
+		addChild(sharedNoneGroup);
+	}
+
+	// Add no resource sharing tests
+	{
+		TestCaseGroup* sharedNoneGroup = new TestCaseGroup(m_eglTestCtx, "no_shared_resource", "Tests without shared resources.");
+
+		for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+		{
+			int threadCount = threadCounts[threadCountNdx];
+
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(perThreadContextCounts); contextCountNdx++)
+			{
+				int contextCount = perThreadContextCounts[contextCountNdx];
+
+				if (threadCount * contextCount != 4 && threadCount * contextCount != 1)
+					continue;
+
+				TestConfig config				= basicConfig;
+				config.threadCount				= threadCount;
+				config.perThreadContextCount	= contextCount;
+
+				{
+					TestConfig smallConfig		= config;
+					smallConfig.triangleCount	= 1;
+					smallConfig.drawCallCount	= 1000;
+					smallConfig.frameCount		= 10;
+
+					if (threadCount * contextCount == 1)
+						smallConfig.frameCount *= 4;
+
+					sharedNoneGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, smallConfig, (createTestName(threadCount, contextCount) + "_small_call").c_str(), ""));
+				}
+
+				{
+					TestConfig bigConfig	= config;
+					bigConfig.triangleCount	= 1000;
+					bigConfig.drawCallCount	= 1;
+					bigConfig.frameCount	= 10;
+
+					if (threadCount * contextCount == 1)
+						bigConfig.frameCount *= 4;
+
+					sharedNoneGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, bigConfig, (createTestName(threadCount, contextCount) + "_big_call").c_str(), ""));
+				}
+			}
+		}
+
+		addChild(sharedNoneGroup);
+	}
+
+	// Add shared coord buffer tests
+	{
+		TestCaseGroup* sharedCoordBufferGroup = new TestCaseGroup(m_eglTestCtx, "shared_coord_buffer", "Shared coordinate bufffer");
+
+		for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+		{
+			int threadCount = threadCounts[threadCountNdx];
+
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(perThreadContextCounts); contextCountNdx++)
+			{
+				int contextCount = perThreadContextCounts[contextCountNdx];
+
+				if (threadCount * contextCount != 4 && threadCount * contextCount != 1)
+					continue;
+
+				TestConfig config				= basicConfig;
+				config.sharedCoordBuffer		= true;
+				config.threadCount				= threadCount;
+				config.perThreadContextCount	= contextCount;
+
+				{
+					TestConfig smallConfig		= config;
+					smallConfig.triangleCount	= 1;
+					smallConfig.drawCallCount	= 1000;
+					smallConfig.frameCount		= 10;
+
+					if (threadCount * contextCount == 1)
+						smallConfig.frameCount *= 4;
+
+					sharedCoordBufferGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, smallConfig, (createTestName(threadCount, contextCount) + "_small_call").c_str(), ""));
+				}
+
+				{
+					TestConfig bigConfig	= config;
+					bigConfig.triangleCount	= 1000;
+					bigConfig.drawCallCount	= 1;
+					bigConfig.frameCount	= 10;
+
+					if (threadCount * contextCount == 1)
+						bigConfig.frameCount *= 4;
+
+					sharedCoordBufferGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, bigConfig, (createTestName(threadCount, contextCount) + "_big_call").c_str(), ""));
+				}
+			}
+		}
+
+		addChild(sharedCoordBufferGroup);
+	}
+
+	// Add shared index buffer tests
+	{
+		TestCaseGroup* sharedIndexBufferGroup = new TestCaseGroup(m_eglTestCtx, "shared_index_buffer", "Shared index bufffer");
+
+		for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+		{
+			int threadCount = threadCounts[threadCountNdx];
+
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(perThreadContextCounts); contextCountNdx++)
+			{
+				int contextCount = perThreadContextCounts[contextCountNdx];
+
+				if (threadCount * contextCount != 4 && threadCount * contextCount != 1)
+					continue;
+
+				TestConfig config				= basicConfig;
+				config.sharedIndexBuffer		= true;
+				config.threadCount				= threadCount;
+				config.perThreadContextCount	= contextCount;
+
+				{
+					TestConfig smallConfig		= config;
+					smallConfig.triangleCount	= 1;
+					smallConfig.drawCallCount	= 1000;
+					smallConfig.frameCount		= 10;
+
+					if (threadCount * contextCount == 1)
+						smallConfig.frameCount *= 4;
+
+					sharedIndexBufferGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, smallConfig, (createTestName(threadCount, contextCount) + "_small_call").c_str(), ""));
+				}
+
+				{
+					TestConfig bigConfig	= config;
+					bigConfig.triangleCount	= 1000;
+					bigConfig.drawCallCount	= 1;
+					bigConfig.frameCount	= 10;
+
+					if (threadCount * contextCount == 1)
+						bigConfig.frameCount *= 4;
+
+					sharedIndexBufferGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, bigConfig, (createTestName(threadCount, contextCount) + "_big_call").c_str(), ""));
+				}
+			}
+		}
+
+		addChild(sharedIndexBufferGroup);
+	}
+
+	// Add shared texture tests
+	{
+		TestCaseGroup* sharedTextureGroup = new TestCaseGroup(m_eglTestCtx, "shared_texture", "Shared texture tests.");
+
+		for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+		{
+			int threadCount = threadCounts[threadCountNdx];
+
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(perThreadContextCounts); contextCountNdx++)
+			{
+				int contextCount = perThreadContextCounts[contextCountNdx];
+
+				if (threadCount * contextCount != 4 && threadCount * contextCount != 1)
+					continue;
+
+				TestConfig config				= basicConfig;
+				config.textureType				= TestConfig::TEXTURETYPE_SHARED_TEXTURE;
+				config.threadCount				= threadCount;
+				config.perThreadContextCount	= contextCount;
+
+				{
+					TestConfig smallConfig		= config;
+					smallConfig.triangleCount	= 1;
+					smallConfig.drawCallCount	= 1000;
+					smallConfig.frameCount		= 10;
+
+					if (threadCount * contextCount == 1)
+						smallConfig.frameCount *= 4;
+
+					sharedTextureGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, smallConfig, (createTestName(threadCount, contextCount) + "_small_call").c_str(), ""));
+				}
+
+				{
+					TestConfig bigConfig	= config;
+					bigConfig.triangleCount	= 1000;
+					bigConfig.drawCallCount	= 1;
+					bigConfig.frameCount	= 10;
+
+					if (threadCount * contextCount == 1)
+						bigConfig.frameCount *= 4;
+
+					sharedTextureGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, bigConfig, (createTestName(threadCount, contextCount) + "_big_call").c_str(), ""));
+				}
+			}
+		}
+
+		addChild(sharedTextureGroup);
+	}
+
+	// Add shared program tests
+	{
+		TestCaseGroup* sharedProgramGroup = new TestCaseGroup(m_eglTestCtx, "shared_program", "Shared program tests.");
+
+		for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+		{
+			int threadCount = threadCounts[threadCountNdx];
+
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(perThreadContextCounts); contextCountNdx++)
+			{
+				int contextCount = perThreadContextCounts[contextCountNdx];
+
+				if (threadCount * contextCount != 4 && threadCount * contextCount != 1)
+					continue;
+
+				TestConfig config				= basicConfig;
+				config.sharedProgram			= true;
+				config.threadCount				= threadCount;
+				config.perThreadContextCount	= contextCount;
+
+				{
+					TestConfig smallConfig		= config;
+					smallConfig.triangleCount	= 1;
+					smallConfig.drawCallCount	= 1000;
+					smallConfig.frameCount		= 10;
+
+					if (threadCount * contextCount == 1)
+						smallConfig.frameCount *= 4;
+
+					sharedProgramGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, smallConfig, (createTestName(threadCount, contextCount) + "_small_call").c_str(), ""));
+				}
+
+				{
+					TestConfig bigConfig	= config;
+					bigConfig.triangleCount	= 1000;
+					bigConfig.drawCallCount	= 1;
+					bigConfig.frameCount	= 10;
+
+					if (threadCount * contextCount == 1)
+						bigConfig.frameCount *= 4;
+
+					sharedProgramGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, bigConfig, (createTestName(threadCount, contextCount) + "_big_call").c_str(), ""));
+				}
+			}
+		}
+
+		addChild(sharedProgramGroup);
+	}
+
+	// Add shared all tests
+	{
+		TestCaseGroup* sharedallGroup = new TestCaseGroup(m_eglTestCtx, "shared_all", "Share all possible resources.");
+
+		for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+		{
+			int threadCount = threadCounts[threadCountNdx];
+
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(perThreadContextCounts); contextCountNdx++)
+			{
+				int contextCount = perThreadContextCounts[contextCountNdx];
+
+				if (threadCount * contextCount != 4 && threadCount * contextCount != 1)
+					continue;
+
+				TestConfig config				= basicConfig;
+				config.sharedCoordBuffer		= true;
+				config.sharedIndexBuffer		= true;
+				config.sharedProgram			= true;
+				config.textureType				= TestConfig::TEXTURETYPE_SHARED_TEXTURE;
+				config.threadCount				= threadCount;
+				config.perThreadContextCount	= contextCount;
+
+				{
+					TestConfig smallConfig		= config;
+					smallConfig.triangleCount	= 1;
+					smallConfig.drawCallCount	= 1000;
+					smallConfig.frameCount		= 10;
+
+					if (threadCount * contextCount == 1)
+						smallConfig.frameCount *= 4;
+
+					sharedallGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, smallConfig, (createTestName(threadCount, contextCount) + "_small_call").c_str(), ""));
+				}
+
+				{
+					TestConfig bigConfig	= config;
+					bigConfig.triangleCount	= 1000;
+					bigConfig.drawCallCount	= 1;
+					bigConfig.frameCount	= 10;
+
+					if (threadCount * contextCount == 1)
+						bigConfig.frameCount *= 4;
+
+					sharedallGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, bigConfig, (createTestName(threadCount, contextCount) + "_big_call").c_str(), ""));
+				}
+			}
+		}
+
+		addChild(sharedallGroup);
+	}
+
+	// Add EGLImage tests
+	{
+		TestCaseGroup* sharedTextureGroup = new TestCaseGroup(m_eglTestCtx, "egl_image", "EGL image tests.");
+
+		for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+		{
+			int threadCount = threadCounts[threadCountNdx];
+
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(perThreadContextCounts); contextCountNdx++)
+			{
+				int contextCount = perThreadContextCounts[contextCountNdx];
+
+				if (threadCount * contextCount != 4 && threadCount * contextCount != 1)
+					continue;
+
+				TestConfig config = basicConfig;
+
+				config.textureType				= TestConfig::TEXTURETYPE_IMAGE;
+				config.threadCount				= threadCount;
+				config.perThreadContextCount	= contextCount;
+				config.sharedContexts			= false;
+
+				{
+					TestConfig smallConfig		= config;
+					smallConfig.triangleCount	= 1;
+					smallConfig.drawCallCount	= 1000;
+					smallConfig.frameCount		= 10;
+
+					if (threadCount * contextCount == 1)
+						smallConfig.frameCount *= 4;
+
+					sharedTextureGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, smallConfig, (createTestName(threadCount, contextCount) + "_small_call").c_str(), ""));
+				}
+
+				{
+					TestConfig bigConfig	= config;
+					bigConfig.triangleCount	= 1000;
+					bigConfig.drawCallCount	= 1;
+					bigConfig.frameCount	= 10;
+
+					if (threadCount * contextCount == 1)
+						bigConfig.frameCount *= 4;
+
+					sharedTextureGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, bigConfig, (createTestName(threadCount, contextCount) + "_big_call").c_str(), ""));
+				}
+			}
+		}
+
+		addChild(sharedTextureGroup);
+	}
+
+	// Add shared EGLImage tests
+	{
+		TestCaseGroup* sharedTextureGroup = new TestCaseGroup(m_eglTestCtx, "shared_egl_image", "Shared EGLImage tests.");
+
+		for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+		{
+			int threadCount = threadCounts[threadCountNdx];
+
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(perThreadContextCounts); contextCountNdx++)
+			{
+				int contextCount = perThreadContextCounts[contextCountNdx];
+
+				if (threadCount * contextCount != 4 && threadCount * contextCount != 1)
+					continue;
+
+				TestConfig config				= basicConfig;
+
+				config.textureType				= TestConfig::TEXTURETYPE_SHARED_IMAGE;
+				config.threadCount				= threadCount;
+				config.perThreadContextCount	= contextCount;
+				config.sharedContexts			= false;
+
+				{
+					TestConfig smallConfig		= config;
+					smallConfig.triangleCount	= 1;
+					smallConfig.drawCallCount	= 1000;
+					smallConfig.frameCount		= 10;
+
+					if (threadCount * contextCount == 1)
+						smallConfig.frameCount *= 4;
+
+					sharedTextureGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, smallConfig, (createTestName(threadCount, contextCount) + "_small_call").c_str(), ""));
+				}
+
+				{
+					TestConfig bigConfig	= config;
+					bigConfig.triangleCount	= 1000;
+					bigConfig.drawCallCount	= 1;
+					bigConfig.frameCount	= 10;
+
+					if (threadCount * contextCount == 1)
+						bigConfig.frameCount *= 4;
+
+					sharedTextureGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, bigConfig, (createTestName(threadCount, contextCount) + "_big_call").c_str(), ""));
+				}
+			}
+		}
+
+		addChild(sharedTextureGroup);
+	}
+
+	// Shared EGLImage texture test
+	{
+		TestCaseGroup* sharedTextureGroup = new TestCaseGroup(m_eglTestCtx, "shared_egl_image_texture", "Shared EGLImage texture tests.");
+
+		for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+		{
+			int threadCount = threadCounts[threadCountNdx];
+
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(perThreadContextCounts); contextCountNdx++)
+			{
+				int contextCount = perThreadContextCounts[contextCountNdx];
+
+				if (threadCount * contextCount != 4 && threadCount * contextCount != 1)
+					continue;
+
+				TestConfig config				= basicConfig;
+				config.textureType				= TestConfig::TEXTURETYPE_SHARED_IMAGE_TEXTURE;
+				config.threadCount				= threadCount;
+				config.perThreadContextCount	= contextCount;
+
+				{
+					TestConfig smallConfig		= config;
+					smallConfig.triangleCount	= 1;
+					smallConfig.drawCallCount	= 1000;
+					smallConfig.frameCount		= 10;
+
+					if (threadCount * contextCount == 1)
+						smallConfig.frameCount *= 4;
+
+					sharedTextureGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, smallConfig, (createTestName(threadCount, contextCount) + "_small_call").c_str(), ""));
+				}
+
+				{
+					TestConfig bigConfig	= config;
+					bigConfig.triangleCount	= 1000;
+					bigConfig.drawCallCount	= 1;
+					bigConfig.frameCount	= 10;
+
+					if (threadCount * contextCount == 1)
+						bigConfig.frameCount *= 4;
+
+					sharedTextureGroup->addChild(new SharedRenderingPerfCase(m_eglTestCtx, bigConfig, (createTestName(threadCount, contextCount) + "_big_call").c_str(), ""));
+				}
+			}
+		}
+
+		addChild(sharedTextureGroup);
+	}
+
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglGLES2SharedRenderingPerfTests.hpp b/modules/egl/teglGLES2SharedRenderingPerfTests.hpp
new file mode 100644
index 0000000..185383a
--- /dev/null
+++ b/modules/egl/teglGLES2SharedRenderingPerfTests.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLGLES2SHAREDRENDERINGPERFTESTS_HPP
+#define _TEGLGLES2SHAREDRENDERINGPERFTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES2 resource sharing performnace tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class GLES2SharedRenderingPerfTests : public TestCaseGroup
+{
+public:
+			GLES2SharedRenderingPerfTests	(EglTestContext& eglTestCtx);
+	void	init							(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLGLES2SHAREDRENDERINGPERFTESTS_HPP
diff --git a/modules/egl/teglGLES2SharingTests.cpp b/modules/egl/teglGLES2SharingTests.cpp
new file mode 100644
index 0000000..5267c60
--- /dev/null
+++ b/modules/egl/teglGLES2SharingTests.cpp
@@ -0,0 +1,1372 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL gles2 sharing tests
+ *//*--------------------------------------------------------------------*/
+
+#include "teglGLES2SharingTests.hpp"
+
+#include "teglGLES2SharingThreadedTests.hpp"
+
+#include "egluNativeWindow.hpp"
+#include "egluUtil.hpp"
+
+#include "tcuCommandLine.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+
+#include "deUniquePtr.hpp"
+#include "deRandom.hpp"
+
+#include "deMath.h"
+#include "deMemory.h"
+#include "deString.h"
+
+#include "gluDefs.hpp"
+
+#include <GLES2/gl2.h>
+
+#include <memory>
+#include <sstream>
+#include <vector>
+
+using std::vector;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace
+{
+
+// \todo [2013-04-09 pyry] Use glu::Program
+class Program
+{
+public:
+	Program (const char* vertexSource, const char* fragmentSource)
+		: m_program			(0)
+		, m_vertexShader	(0)
+		, m_fragmentShader	(0)
+		, m_isOk			(false)
+	{
+		m_program			= glCreateProgram();
+		m_vertexShader		= glCreateShader(GL_VERTEX_SHADER);
+		m_fragmentShader	= glCreateShader(GL_FRAGMENT_SHADER);
+
+		try
+		{
+			bool	vertexCompileOk		= false;
+			bool	fragmentCompileOk	= false;
+			bool	linkOk				= false;
+
+			for (int ndx = 0; ndx < 2; ndx++)
+			{
+				const char*		source			= ndx ? fragmentSource		: vertexSource;
+				const deUint32	shader			= ndx ? m_fragmentShader	: m_vertexShader;
+				int				compileStatus	= 0;
+				bool&			compileOk		= ndx ? fragmentCompileOk	: vertexCompileOk;
+
+				glShaderSource(shader, 1, &source, DE_NULL);
+				glCompileShader(shader);
+				glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
+
+				compileOk = (compileStatus == GL_TRUE);
+			}
+
+			if (vertexCompileOk && fragmentCompileOk)
+			{
+				int linkStatus = 0;
+
+				glAttachShader(m_program, m_vertexShader);
+				glAttachShader(m_program, m_fragmentShader);
+				glLinkProgram(m_program);
+				glGetProgramiv(m_program, GL_LINK_STATUS, &linkStatus);
+
+				linkOk = (linkStatus == GL_TRUE);
+			}
+
+			m_isOk = linkOk;
+		}
+		catch (const std::exception&)
+		{
+			glDeleteShader(m_vertexShader);
+			glDeleteShader(m_fragmentShader);
+			glDeleteProgram(m_program);
+			throw;
+		}
+	}
+
+	~Program (void)
+	{
+		glDeleteShader(m_vertexShader);
+		glDeleteShader(m_fragmentShader);
+		glDeleteProgram(m_program);
+	}
+
+	bool			isOk			(void) const { return m_isOk;	}
+	deUint32		getProgram		(void) const {return m_program;	}
+
+private:
+	deUint32		m_program;
+	deUint32		m_vertexShader;
+	deUint32		m_fragmentShader;
+	bool			m_isOk;
+};
+
+} // anonymous
+
+class GLES2SharingTest : public TestCase
+{
+public:
+	enum ResourceType
+	{
+		BUFFER = 0,
+		TEXTURE,
+		RENDERBUFFER,
+		SHADER_PROGRAM
+	};
+
+	struct TestSpec
+	{
+		ResourceType	type;
+		bool			destroyContextBFirst;
+		bool			useResource;
+		bool			destroyOnContexB;
+		bool			initializeData;
+		bool			renderOnContexA;
+		bool			renderOnContexB;
+		bool			verifyOnContexA;
+		bool			verifyOnContexB;
+	};
+
+					GLES2SharingTest	(EglTestContext& eglTestCtx, const char* name , const char* desc, const TestSpec& spec);
+	IterateResult	iterate				(void);
+
+private:
+	TestSpec		m_spec;
+
+	EGLContext		createContext		(EGLDisplay display, EGLContext share, EGLConfig config);
+	void			destroyContext		(EGLDisplay display, EGLContext context);
+	void			makeCurrent			(EGLDisplay display, EGLContext context, EGLSurface surafec);
+
+protected:
+	de::Random		m_random;
+	tcu::TestLog&	m_log;
+	virtual void	createResource		(void)  { DE_ASSERT(false); }
+	virtual void 	destroyResource		(void)	{ DE_ASSERT(false); }
+	virtual void	renderResource		(tcu::Surface* screen, tcu::Surface* reference) { DE_UNREF(screen); DE_UNREF(reference); DE_ASSERT(false); }
+};
+
+GLES2SharingTest::GLES2SharingTest (EglTestContext& eglTestCtx, const char* name , const char* desc, const TestSpec& spec)
+	: TestCase	(eglTestCtx, name, desc)
+	, m_spec	(spec)
+	, m_random	(deStringHash(name))
+	, m_log		(eglTestCtx.getTestContext().getLog())
+{
+}
+
+EGLContext GLES2SharingTest::createContext (EGLDisplay display, EGLContext share, EGLConfig config)
+{
+	EGLContext context = EGL_NO_CONTEXT;
+	EGLint attriblist[] =
+	{
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+	EGLint configId = -1;
+	eglGetConfigAttrib(display, config, EGL_CONFIG_ID, &configId);
+
+	TCU_CHECK_EGL_CALL(eglBindAPI(EGL_OPENGL_ES_API));
+
+	context = eglCreateContext(display, config, share, attriblist);
+	TCU_CHECK_EGL_MSG("Failed to create GLES2 context");
+	TCU_CHECK(context != EGL_NO_CONTEXT);
+
+	return context;
+}
+
+void GLES2SharingTest::destroyContext (EGLDisplay display, EGLContext context)
+{
+	TCU_CHECK_EGL_CALL(eglDestroyContext(display, context));
+}
+
+void GLES2SharingTest::makeCurrent (EGLDisplay display, EGLContext context, EGLSurface surface)
+{
+	TCU_CHECK_EGL_CALL(eglMakeCurrent(display, surface, surface, context));
+}
+
+TestCase::IterateResult GLES2SharingTest::iterate (void)
+{
+	tcu::TestLog&		log		= m_testCtx.getLog();
+	vector<EGLConfig>	configs;
+
+	EGLint attribList[] =
+	{
+		EGL_RENDERABLE_TYPE, 	EGL_OPENGL_ES2_BIT,
+		EGL_SURFACE_TYPE,	 	EGL_WINDOW_BIT,
+		EGL_ALPHA_SIZE,			1,
+		EGL_NONE
+	};
+
+	tcu::egl::Display& display = m_eglTestCtx.getDisplay();
+	display.chooseConfig(attribList, configs);
+	EGLConfig config = configs[0];
+
+	de::UniquePtr<eglu::NativeWindow>	window	(m_eglTestCtx.createNativeWindow(display.getEGLDisplay(), config, DE_NULL, 480, 480, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
+	tcu::egl::WindowSurface				surface	(display, eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, display.getEGLDisplay(), config, DE_NULL));
+
+	m_log << tcu::TestLog::Message << "Create context A" << tcu::TestLog::EndMessage;
+	EGLContext		contextA	= createContext(display.getEGLDisplay(), EGL_NO_CONTEXT, config);
+	m_log << tcu::TestLog::Message << "Create context B" << tcu::TestLog::EndMessage;
+	EGLContext		contextB	= createContext(display.getEGLDisplay(), contextA, config);
+	bool			isOk		= true;
+
+	if (m_spec.useResource)
+	{
+		m_log << tcu::TestLog::Message << "Make current context A" << tcu::TestLog::EndMessage;
+		makeCurrent(display.getEGLDisplay(), contextA, surface.getEGLSurface());
+		m_log << tcu::TestLog::Message << "Creating resource" << tcu::TestLog::EndMessage;
+		createResource();
+
+		int		width	= 240;
+		int		height	= 240;
+
+		if (m_spec.renderOnContexA)
+		{
+			m_log << tcu::TestLog::Message << "Render resource" << tcu::TestLog::EndMessage;
+			if (m_spec.verifyOnContexA)
+			{
+				tcu::Surface screen	(width, height);
+				tcu::Surface ref	(width, height);
+				renderResource(&screen, &ref);
+
+				if (!fuzzyCompare(log, "Rendered image", "Rendering result comparision", ref, screen, 0.05f, tcu::COMPARE_LOG_RESULT))
+					isOk = false;
+			}
+			else
+			{
+				renderResource(NULL, NULL);
+			}
+		}
+
+		if (m_spec.renderOnContexB)
+		{
+			m_log << tcu::TestLog::Message << "Make current context B" << tcu::TestLog::EndMessage;
+			makeCurrent(display.getEGLDisplay(), contextB, surface.getEGLSurface());
+			m_log << tcu::TestLog::Message << "Render resource" << tcu::TestLog::EndMessage;
+			if (m_spec.verifyOnContexB)
+			{
+				tcu::Surface screen	(width, height);
+				tcu::Surface ref	(width, height);
+				renderResource(&screen, &ref);
+
+				if (!fuzzyCompare(log, "Rendered image", "Rendering result comparision", ref, screen, 0.05f, tcu::COMPARE_LOG_RESULT))
+					isOk = false;
+			}
+			else
+			{
+				renderResource(NULL, NULL);
+			}
+		}
+
+		if (m_spec.destroyOnContexB)
+		{
+			m_log << tcu::TestLog::Message << "Make current context B" << tcu::TestLog::EndMessage;
+			makeCurrent(display.getEGLDisplay(), contextB, surface.getEGLSurface());
+			m_log << tcu::TestLog::Message << "Destroy resource" << tcu::TestLog::EndMessage;
+			destroyResource();
+		}
+		else
+		{
+			m_log << tcu::TestLog::Message << "Make current context A" << tcu::TestLog::EndMessage;
+			makeCurrent(display.getEGLDisplay(), contextA, surface.getEGLSurface());
+			m_log << tcu::TestLog::Message << "Destroy resource" << tcu::TestLog::EndMessage;
+			destroyResource();
+		}
+	}
+
+	if (m_spec.destroyContextBFirst)
+	{
+		m_log << tcu::TestLog::Message << "Destroy context B" << tcu::TestLog::EndMessage;
+		destroyContext(display.getEGLDisplay(), contextB);
+		m_log << tcu::TestLog::Message << "Destroy context A" << tcu::TestLog::EndMessage;
+		destroyContext(display.getEGLDisplay(), contextA);
+	}
+	else
+	{
+		m_log << tcu::TestLog::Message << "Destroy context A" << tcu::TestLog::EndMessage;
+		destroyContext(display.getEGLDisplay(), contextA);
+		m_log << tcu::TestLog::Message << "Destroy context B" << tcu::TestLog::EndMessage;
+		destroyContext(display.getEGLDisplay(), contextB);
+	}
+
+	if (isOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+	return STOP;
+}
+
+class GLES2BufferSharingTest : public GLES2SharingTest
+{
+public:
+							GLES2BufferSharingTest	(EglTestContext& eglTestCtx, const char* name, const char* desc, const GLES2SharingTest::TestSpec& spec);
+
+private:
+	GLuint					m_glBuffer;
+	std::vector<GLubyte>	m_buffer;
+
+	virtual void	createResource		(void);
+	virtual void 	destroyResource		(void);
+	virtual void	renderResource		(tcu::Surface* screen, tcu::Surface* reference);
+
+};
+
+GLES2BufferSharingTest::GLES2BufferSharingTest (EglTestContext& eglTestCtx, const char* name, const char* desc, const GLES2SharingTest::TestSpec& spec)
+	: GLES2SharingTest	(eglTestCtx, name, desc, spec)
+	, m_glBuffer		(0)
+{
+}
+
+void GLES2BufferSharingTest::createResource (void)
+{
+	int						size	= 16*16*4;
+
+	m_buffer.reserve(size);
+
+	for (int i = 0; i < size; i++)
+		m_buffer.push_back((GLubyte)m_random.getInt(0, 255));
+
+	GLU_CHECK_CALL(glGenBuffers(1, &m_glBuffer));
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_glBuffer));
+	GLU_CHECK_CALL(glBufferData(GL_ARRAY_BUFFER, (GLsizei)(m_buffer.size() * sizeof(GLubyte)), &(m_buffer[0]), GL_DYNAMIC_DRAW));
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
+}
+
+void GLES2BufferSharingTest::destroyResource (void)
+{
+	GLU_CHECK_CALL(glDeleteBuffers(1, &m_glBuffer));
+	m_buffer.clear();
+}
+
+void GLES2BufferSharingTest::renderResource (tcu::Surface* screen, tcu::Surface* reference)
+{
+	DE_ASSERT((screen && reference) || (!screen && !reference));
+
+	const char* vertexShader = ""
+	"attribute mediump vec2 a_pos;\n"
+	"attribute mediump float a_color;\n"
+	"varying mediump float v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tv_color = a_color;\n"
+	"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+	"}\n";
+
+	const char* fragmentShader = ""
+	"varying mediump float v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tgl_FragColor = vec4(v_color, v_color, v_color, 1.0);\n"
+	"}\n";
+
+	Program program(vertexShader, fragmentShader);
+
+	if (!program.isOk())
+		TCU_FAIL("Failed to compile shader program");
+
+	std::vector<deUint16>	indices;
+	std::vector<float>		coords;
+
+	DE_ASSERT(m_buffer.size() % 4 == 0);
+
+	for (int i = 0; i < (int)m_buffer.size() / 4; i++)
+	{
+		indices.push_back(i*4);
+		indices.push_back(i*4 + 1);
+		indices.push_back(i*4 + 2);
+		indices.push_back(i*4 + 2);
+		indices.push_back(i*4 + 3);
+		indices.push_back(i*4);
+
+		coords.push_back(0.125f * (i % 16) - 1.0f);
+		coords.push_back(0.125f * ((int)(i / 16.0f)) - 1.0f);
+
+		coords.push_back(0.125f * (i % 16) - 1.0f);
+		coords.push_back(0.125f * ((int)(i / 16.0f) + 1) - 1.0f);
+
+		coords.push_back(0.125f * ((i % 16) + 1) - 1.0f);
+		coords.push_back(0.125f * ((int)(i / 16.0f) + 1) - 1.0f);
+
+		coords.push_back(0.125f * ((i % 16) + 1) - 1.0f);
+		coords.push_back(0.125f * ((int)(i / 16.0f)) - 1.0f);
+	}
+
+	int width = 240;
+	int height = 240;
+
+	if (screen)
+	{
+		width = screen->getWidth();
+		height = screen->getHeight();
+	}
+
+	GLU_CHECK_CALL(glViewport(0, 0, width, height));
+
+	GLU_CHECK_CALL(glClearColor(1.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	GLU_CHECK_CALL(glUseProgram(program.getProgram()));
+
+	GLuint gridLocation = glGetAttribLocation(program.getProgram(), "a_pos");
+	GLU_CHECK_MSG("glGetAttribLocation()");
+	TCU_CHECK(gridLocation != (GLuint)-1);
+
+	GLuint colorLocation = glGetAttribLocation(program.getProgram(), "a_color");
+	GLU_CHECK_MSG("glGetAttribLocation()");
+	TCU_CHECK(colorLocation != (GLuint)-1);
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(colorLocation));
+	GLU_CHECK_CALL(glEnableVertexAttribArray(gridLocation));
+
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_glBuffer));
+	GLU_CHECK_CALL(glVertexAttribPointer(colorLocation, 1, GL_UNSIGNED_BYTE, GL_TRUE, 0, NULL));
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
+
+	GLU_CHECK_CALL(glVertexAttribPointer(gridLocation, 2, GL_FLOAT, GL_FALSE, 0, &(coords[0])));
+
+	GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)indices.size(), GL_UNSIGNED_SHORT, &(indices[0])));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(colorLocation));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(gridLocation));
+
+	GLU_CHECK_CALL(glUseProgram(0));
+
+	if (screen)
+	{
+		tcu::clear(reference->getAccess(), tcu::IVec4(0xff, 0, 0, 0xff));
+		glReadPixels(0, 0, screen->getWidth(), screen->getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, screen->getAccess().getDataPtr());
+		for (int i = 0; i < (int)m_buffer.size() / 4; i++)
+		{
+			float fx1 = 0.125f * (i % 16) - 1.0f;
+			float fy1 = 0.125f * ((int)(i / 16.0f)) - 1.0f;
+			float fx2 = 0.125f * ((i % 16) + 1) - 1.0f;
+			float fy2 = 0.125f * ((int)((i / 16.0f) + 1)) - 1.0f;
+
+			int ox = deRoundFloatToInt32(width		/ 2.0f);
+			int oy = deRoundFloatToInt32(height		/ 2.0f);
+			int x1 = deRoundFloatToInt32((width		 * fx1 / 2.0f) + ox);
+			int y1 = deRoundFloatToInt32((height	 * fy1 / 2.0f) + oy);
+			int x2 = deRoundFloatToInt32((width		 * fx2 / 2.0f) + ox);
+			int y2 = deRoundFloatToInt32((height	 * fy2 / 2.0f) + oy);
+
+			for (int x = x1; x < x2; x++)
+			{
+				for (int y = y1; y < y2; y++)
+				{
+					float		xf		= ((float)(x-x1) + 0.5f) / (float)(x2 - x1);
+					float		yf		= ((float)(y-y1) + 0.5f) / (float)(y2 - y1);
+					bool		tri		= yf >= xf;
+					deUint8		a		= m_buffer[i*4 + (tri ? 1 : 3)];
+					deUint8		b		= m_buffer[i*4 + (tri ? 2 : 0)];
+					deUint8		c		= m_buffer[i*4 + (tri ? 0 : 2)];
+					float		s		= tri ? xf : 1.0f-xf;
+					float		t		= tri ? 1.0f-yf : yf;
+					float		val		= (float)a + (float)(b-a)*s + (float)(c-a)*t;
+
+					reference->setPixel(x, y, tcu::RGBA((deUint8)val, (deUint8)val, (deUint8)val, 255));
+				}
+			}
+		}
+	}
+}
+
+class GLES2TextureSharingTest : public GLES2SharingTest
+{
+public:
+							GLES2TextureSharingTest	(EglTestContext& eglTestCtx, const char* name, const char* desc, const GLES2SharingTest::TestSpec& spec);
+
+private:
+	GLuint					m_glTexture;
+	tcu::Texture2D			m_texture;
+
+	virtual void	createResource		(void);
+	virtual void 	destroyResource		(void);
+	virtual void	renderResource		(tcu::Surface* screen, tcu::Surface* reference);
+
+};
+
+GLES2TextureSharingTest::GLES2TextureSharingTest (EglTestContext& eglTestCtx, const char* name, const char* desc, const GLES2SharingTest::TestSpec& spec)
+	: GLES2SharingTest	(eglTestCtx, name, desc, spec)
+	, m_glTexture		(0)
+	, m_texture			(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 1, 1)
+{
+}
+
+void GLES2TextureSharingTest::createResource (void)
+{
+	int width	= 128;
+	int	height	= 128;
+	m_texture = tcu::Texture2D(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), width, height);
+	m_texture.allocLevel(0);
+
+	tcu::fillWithComponentGradients(m_texture.getLevel(0), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+	GLU_CHECK_CALL(glGenTextures(1, &m_glTexture));
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, m_glTexture));
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+	GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_texture.getLevel(0).getDataPtr()));
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+}
+
+void GLES2TextureSharingTest::destroyResource (void)
+{
+	GLU_CHECK_CALL(glDeleteTextures(1, &m_glTexture));
+}
+
+void GLES2TextureSharingTest::renderResource (tcu::Surface* screen, tcu::Surface* reference)
+{
+	DE_ASSERT((screen && reference) || (!screen && !reference));
+
+	const char* vertexShader = ""
+	"attribute mediump vec2 a_pos;\n"
+	"attribute mediump vec2 a_texCorod;\n"
+	"varying mediump vec2 v_texCoord;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tv_texCoord = a_texCorod;\n"
+	"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+	"}\n";
+
+	const char* fragmentShader = ""
+	"varying mediump vec2 v_texCoord;\n"
+	"uniform sampler2D u_sampler;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tgl_FragColor = texture2D(u_sampler, v_texCoord);\n"
+	"}\n";
+
+	Program program(vertexShader, fragmentShader);
+
+	if (!program.isOk())
+		TCU_FAIL("Failed to compile shader program");
+
+	int width = 240;
+	int height = 240;
+
+	if (screen)
+	{
+		width = screen->getWidth();
+		height = screen->getHeight();
+	}
+
+	static const GLfloat coords[] = {
+		-1.0f, -1.0f,
+		 1.0f, -1.0f,
+		 1.0f,  1.0f,
+		-1.0f,  1.0f
+	};
+
+	static const GLfloat texCoords[] = {
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		1.0f, 1.0f,
+		0.0f, 1.0f
+	};
+
+	static const GLushort indices[] = {
+		0, 1, 2,
+		2, 3, 0
+	};
+
+	GLU_CHECK_CALL(glViewport(0, 0, width, height));
+
+	GLU_CHECK_CALL(glClearColor(1.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	GLU_CHECK_CALL(glUseProgram(program.getProgram()));
+
+	GLuint coordLocation = glGetAttribLocation(program.getProgram(), "a_pos");
+	GLU_CHECK_MSG("glGetAttribLocation()");
+	TCU_CHECK(coordLocation != (GLuint)-1);
+
+	GLuint texCoordLocation = glGetAttribLocation(program.getProgram(), "a_texCorod");
+	GLU_CHECK_MSG("glGetAttribLocation()");
+	TCU_CHECK(texCoordLocation != (GLuint)-1);
+
+
+	GLuint samplerLocation = glGetUniformLocation(program.getProgram(), "u_sampler");
+	GLU_CHECK_MSG("glGetUniformLocation()");
+	TCU_CHECK(samplerLocation != (GLuint)-1);
+
+	GLU_CHECK_CALL(glActiveTexture(GL_TEXTURE0));
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, m_glTexture));
+
+	GLU_CHECK_CALL(glUniform1i(samplerLocation, 0));
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(texCoordLocation));
+	GLU_CHECK_CALL(glEnableVertexAttribArray(coordLocation));
+
+	GLU_CHECK_CALL(glVertexAttribPointer(texCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, texCoords));
+	GLU_CHECK_CALL(glVertexAttribPointer(coordLocation, 2, GL_FLOAT, GL_FALSE, 0, coords));
+
+	GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(coordLocation));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(texCoordLocation));
+
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+	GLU_CHECK_CALL(glUseProgram(0));
+
+	if (screen)
+	{
+		glReadPixels(0, 0, screen->getWidth(), screen->getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, screen->getAccess().getDataPtr());
+
+		for (int x = 0; x < width; x++)
+		{
+			for (int y = 0; y < height; y++)
+			{
+				float t = ((float)x / (width - 1.0f));
+				float s = ((float)y / (height - 1.0f));
+				float lod = 0.0f;
+
+				tcu::Vec4 color = m_texture.sample(tcu::Sampler(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::LINEAR, tcu::Sampler::LINEAR), t, s, lod);
+
+				int r = deClamp32((int)(255.0f * color.x()), 0, 255);
+				int g = deClamp32((int)(255.0f * color.y()), 0, 255);
+				int b = deClamp32((int)(255.0f * color.z()), 0, 255);
+				int a = deClamp32((int)(255.0f * color.w()), 0, 255);
+
+				reference->setPixel(x, y, tcu::RGBA(r, g, b, a));
+			}
+		}
+	}
+}
+
+class GLES2ProgramSharingTest : public GLES2SharingTest
+{
+public:
+					GLES2ProgramSharingTest	(EglTestContext& eglTestCtx, const char* name, const char* desc, const GLES2SharingTest::TestSpec& spec);
+
+private:
+	Program*		m_program;
+
+	virtual void	createResource		(void);
+	virtual void 	destroyResource		(void);
+	virtual void	renderResource		(tcu::Surface* screen, tcu::Surface* reference);
+
+};
+
+GLES2ProgramSharingTest::GLES2ProgramSharingTest (EglTestContext& eglTestCtx, const char* name, const char* desc, const GLES2SharingTest::TestSpec& spec)
+	: GLES2SharingTest	(eglTestCtx, name, desc, spec)
+	, m_program			(NULL)
+{
+}
+
+void GLES2ProgramSharingTest::createResource (void)
+{
+	const char* vertexShader = ""
+	"attribute mediump vec2 a_pos;\n"
+	"attribute mediump vec4 a_color;\n"
+	"varying mediump vec4 v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tv_color = a_color;\n"
+	"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+	"}\n";
+
+	const char* fragmentShader = ""
+	"varying mediump vec4 v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tgl_FragColor = v_color;\n"
+	"}\n";
+
+	m_program = new Program(vertexShader, fragmentShader);
+
+	if (!m_program->isOk())
+		TCU_FAIL("Failed to compile shader program");
+}
+
+void GLES2ProgramSharingTest::destroyResource (void)
+{
+	delete m_program;
+}
+
+void GLES2ProgramSharingTest::renderResource (tcu::Surface* screen, tcu::Surface* reference)
+{
+	DE_ASSERT((screen && reference) || (!screen && !reference));
+
+	int width = 240;
+	int height = 240;
+
+	if (screen)
+	{
+		width = screen->getWidth();
+		height = screen->getHeight();
+	}
+
+	static const GLfloat coords[] = {
+		-0.9f, -0.9f,
+		 0.9f, -0.9f,
+		 0.9f,  0.9f,
+		-0.9f,  0.9f
+	};
+
+	static const GLfloat colors [] = {
+		0.0f, 0.0f, 0.0f, 1.0f,
+		1.0f, 0.0f, 0.0f, 1.0f,
+		0.0f, 1.0f, 0.0f, 1.0f,
+		0.0f, 0.0f, 1.0f, 1.0f
+	};
+
+	static const GLushort indices[] = {
+		0, 1, 2,
+		2, 3, 0
+	};
+
+	GLU_CHECK_CALL(glViewport(0, 0, width, height));
+
+	GLU_CHECK_CALL(glClearColor(1.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	GLU_CHECK_CALL(glUseProgram(m_program->getProgram()));
+
+	GLuint coordLocation = glGetAttribLocation(m_program->getProgram(), "a_pos");
+	GLU_CHECK_MSG("glGetAttribLocation()");
+	TCU_CHECK(coordLocation != (GLuint)-1);
+
+	GLuint colorLocation = glGetAttribLocation(m_program->getProgram(), "a_color");
+	GLU_CHECK_MSG("glGetAttribLocation()");
+	TCU_CHECK(colorLocation != (GLuint)-1);
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(colorLocation));
+	GLU_CHECK_CALL(glEnableVertexAttribArray(coordLocation));
+
+	GLU_CHECK_CALL(glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, colors));
+	GLU_CHECK_CALL(glVertexAttribPointer(coordLocation, 2, GL_FLOAT, GL_FALSE, 0, coords));
+
+	GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(coordLocation));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(colorLocation));
+	GLU_CHECK_CALL(glUseProgram(0));
+
+	if (screen)
+	{
+		glReadPixels(0, 0, screen->getWidth(), screen->getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, screen->getAccess().getDataPtr());
+
+		tcu::clear(reference->getAccess(), tcu::IVec4(0xff, 0, 0, 0xff));
+
+		int x1 = (int)((width/2.0f) * (-0.9f) + (width/2.0f));
+		int x2 = (int)((width/2.0f) * 0.9f + (width/2.0f));
+		int y1 = (int)((height/2.0f) * (-0.9f) + (height/2.0f));
+		int y2 = (int)((height/2.0f) * 0.9f + (height/2.0f));
+
+		for (int x = x1; x <= x2; x++)
+		{
+			for (int y = y1; y <= y2; y++)
+			{
+				float t = ((float)(x-x1) / (x2 - x1));
+				float s = ((float)(y-y1) / (y2-y1));
+				bool isUpper = t > s;
+
+				tcu::Vec4 a(colors[0],		colors[1],		colors[2],		colors[3]);
+				tcu::Vec4 b(colors[4 + 0],	colors[4 + 1],	colors[4 + 2],	colors[4 + 3]);
+				tcu::Vec4 c(colors[8 + 0],	colors[8 + 1],	colors[8 + 2],	colors[8 + 3]);
+				tcu::Vec4 d(colors[12 + 0],	colors[12 + 1],	colors[12 + 2],	colors[12 + 3]);
+
+
+				tcu::Vec4 color;
+
+				if (isUpper)
+					color = a * (1.0f - t)  + b * (t - s) + s * c;
+				else
+					color = a * (1.0f - s)  + d * (s - t) + t * c;
+
+				int red		= deClamp32((int)(255.0f * color.x()), 0, 255);
+				int green	= deClamp32((int)(255.0f * color.y()), 0, 255);
+				int blue	= deClamp32((int)(255.0f * color.z()), 0, 255);
+				int alpha	= deClamp32((int)(255.0f * color.w()), 0, 255);
+
+				reference->setPixel(x, y, tcu::RGBA(red, green, blue, alpha));
+			}
+		}
+	}
+}
+
+class GLES2ShaderSharingTest : public GLES2SharingTest
+{
+public:
+					GLES2ShaderSharingTest	(EglTestContext& eglTestCtx, const char* name, const char* desc, GLenum shaderType, const GLES2SharingTest::TestSpec& spec);
+
+private:
+	GLuint			m_shader;
+	GLenum			m_shaderType;
+
+	virtual void	createResource		(void);
+	virtual void 	destroyResource		(void);
+	virtual void	renderResource		(tcu::Surface* screen, tcu::Surface* reference);
+
+};
+
+GLES2ShaderSharingTest::GLES2ShaderSharingTest (EglTestContext& eglTestCtx, const char* name, const char* desc, GLenum shaderType, const GLES2SharingTest::TestSpec& spec)
+	: GLES2SharingTest	(eglTestCtx, name, desc, spec)
+	, m_shader			(0)
+	, m_shaderType		(shaderType)
+{
+}
+
+void GLES2ShaderSharingTest::createResource (void)
+{
+	const char* vertexShader = ""
+	"attribute mediump vec2 a_pos;\n"
+	"attribute mediump vec4 a_color;\n"
+	"varying mediump vec4 v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tv_color = a_color;\n"
+	"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+	"}\n";
+
+	const char* fragmentShader = ""
+	"varying mediump vec4 v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tgl_FragColor = v_color;\n"
+	"}\n";
+
+
+	m_shader = glCreateShader(m_shaderType);
+	GLU_CHECK_MSG("glCreateShader()");
+
+	switch (m_shaderType)
+	{
+		case GL_VERTEX_SHADER:
+			GLU_CHECK_CALL(glShaderSource(m_shader, 1, &vertexShader, NULL));
+			break;
+
+		case GL_FRAGMENT_SHADER:
+			GLU_CHECK_CALL(glShaderSource(m_shader, 1, &fragmentShader, NULL));
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	GLU_CHECK_CALL(glCompileShader(m_shader));
+
+	GLint status = 0;
+	GLU_CHECK_CALL(glGetShaderiv(m_shader, GL_COMPILE_STATUS, &status));
+
+	if (!status)
+	{
+		char buffer[256];
+		GLU_CHECK_CALL(glGetShaderInfoLog(m_shader, 256, NULL, buffer));
+
+		m_log << tcu::TestLog::Message << "Failed to compile shader" << tcu::TestLog::EndMessage;
+
+		switch (m_shaderType)
+		{
+			case GL_VERTEX_SHADER:
+				m_log << tcu::TestLog::Message << vertexShader << tcu::TestLog::EndMessage;
+				break;
+
+			case GL_FRAGMENT_SHADER:
+				m_log << tcu::TestLog::Message << fragmentShader << tcu::TestLog::EndMessage;
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		m_log << tcu::TestLog::Message << buffer << tcu::TestLog::EndMessage;
+		TCU_FAIL("Failed to compile shader");
+	}
+}
+
+void GLES2ShaderSharingTest::destroyResource (void)
+{
+	GLU_CHECK_CALL(glDeleteShader(m_shader));
+}
+
+void GLES2ShaderSharingTest::renderResource (tcu::Surface* screen, tcu::Surface* reference)
+{
+	DE_ASSERT((screen && reference) || (!screen && !reference));
+
+	int width = 240;
+	int height = 240;
+
+	const char* vertexShader = ""
+	"attribute mediump vec2 a_pos;\n"
+	"attribute mediump vec4 a_color;\n"
+	"varying mediump vec4 v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tv_color = a_color;\n"
+	"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+	"}\n";
+
+	const char* fragmentShader = ""
+	"varying mediump vec4 v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tgl_FragColor = v_color;\n"
+	"}\n";
+
+
+	GLuint otherShader = (GLuint)-1;
+
+	switch (m_shaderType)
+	{
+		case GL_VERTEX_SHADER:
+			otherShader = glCreateShader(GL_FRAGMENT_SHADER);
+			GLU_CHECK_MSG("glCreateShader()");
+			GLU_CHECK_CALL(glShaderSource(otherShader, 1, &fragmentShader, NULL));
+			break;
+
+		case GL_FRAGMENT_SHADER:
+			otherShader = glCreateShader(GL_VERTEX_SHADER);
+			GLU_CHECK_MSG("glCreateShader()");
+			GLU_CHECK_CALL(glShaderSource(otherShader, 1, &vertexShader, NULL));
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	GLU_CHECK_CALL(glCompileShader(otherShader));
+
+	GLint status = 0;
+	GLU_CHECK_CALL(glGetShaderiv(otherShader, GL_COMPILE_STATUS, &status));
+
+	if (!status)
+	{
+		char buffer[256];
+		GLU_CHECK_CALL(glGetShaderInfoLog(otherShader, 256, NULL, buffer));
+
+		m_log << tcu::TestLog::Message << "Failed to compile shader" << tcu::TestLog::EndMessage;
+
+		switch (m_shaderType)
+		{
+			case GL_FRAGMENT_SHADER:
+				m_log << tcu::TestLog::Message << vertexShader << tcu::TestLog::EndMessage;
+				break;
+
+			case GL_VERTEX_SHADER:
+				m_log << tcu::TestLog::Message << fragmentShader << tcu::TestLog::EndMessage;
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		m_log << tcu::TestLog::Message << buffer << tcu::TestLog::EndMessage;
+		TCU_FAIL("Failed to compile shader");
+	}
+
+	GLuint program = glCreateProgram();
+	GLU_CHECK_MSG("glCreateProgram()");
+
+	GLU_CHECK_CALL(glAttachShader(program, m_shader));
+	GLU_CHECK_CALL(glAttachShader(program, otherShader));
+
+	GLU_CHECK_CALL(glLinkProgram(program));
+	GLU_CHECK_CALL(glDeleteShader(otherShader));
+
+	status = 0;
+	GLU_CHECK_CALL(glGetProgramiv(program, GL_LINK_STATUS, &status));
+
+	if (!status)
+	{
+		char buffer[256];
+		GLU_CHECK_CALL(glGetProgramInfoLog(program, 256, NULL, buffer));
+
+		m_log << tcu::TestLog::Message << "Failed to link program" << tcu::TestLog::EndMessage;
+
+		m_log << tcu::TestLog::Message << vertexShader << tcu::TestLog::EndMessage;
+		m_log << tcu::TestLog::Message << fragmentShader << tcu::TestLog::EndMessage;
+		m_log << tcu::TestLog::Message << buffer << tcu::TestLog::EndMessage;
+		TCU_FAIL("Failed to link program");
+	}
+
+	if (screen)
+	{
+		width = screen->getWidth();
+		height = screen->getHeight();
+	}
+
+	static const GLfloat coords[] = {
+		-0.9f, -0.9f,
+		 0.9f, -0.9f,
+		 0.9f,  0.9f,
+		-0.9f,  0.9f
+	};
+
+	static const GLfloat colors [] = {
+		0.0f, 0.0f, 0.0f, 1.0f,
+		1.0f, 0.0f, 0.0f, 1.0f,
+		0.0f, 1.0f, 0.0f, 1.0f,
+		0.0f, 0.0f, 1.0f, 1.0f
+	};
+
+	static const GLushort indices[] = {
+		0, 1, 2,
+		2, 3, 0
+	};
+
+	GLU_CHECK_CALL(glViewport(0, 0, width, height));
+
+	GLU_CHECK_CALL(glClearColor(1.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	GLU_CHECK_CALL(glUseProgram(program));
+
+	GLuint coordLocation = glGetAttribLocation(program, "a_pos");
+	GLU_CHECK_MSG("glGetAttribLocation()");
+	TCU_CHECK(coordLocation != (GLuint)-1);
+
+	GLuint colorLocation = glGetAttribLocation(program, "a_color");
+	GLU_CHECK_MSG("glGetAttribLocation()");
+	TCU_CHECK(colorLocation != (GLuint)-1);
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(colorLocation));
+	GLU_CHECK_CALL(glEnableVertexAttribArray(coordLocation));
+
+	GLU_CHECK_CALL(glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, colors));
+	GLU_CHECK_CALL(glVertexAttribPointer(coordLocation, 2, GL_FLOAT, GL_FALSE, 0, coords));
+
+	GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(coordLocation));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(colorLocation));
+	GLU_CHECK_CALL(glUseProgram(0));
+
+	if (screen)
+	{
+		glReadPixels(0, 0, screen->getWidth(), screen->getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, screen->getAccess().getDataPtr());
+
+		tcu::clear(reference->getAccess(), tcu::IVec4(0xff, 0, 0, 0xff));
+
+		int x1 = (int)((width/2.0f) * (-0.9f) + (width/2.0f));
+		int x2 = (int)((width/2.0f) * 0.9f + (width/2.0f));
+		int y1 = (int)((height/2.0f) * (-0.9f) + (height/2.0f));
+		int y2 = (int)((height/2.0f) * 0.9f + (height/2.0f));
+
+		for (int x = x1; x <= x2; x++)
+		{
+			for (int y = y1; y <= y2; y++)
+			{
+				float t = ((float)(x-x1) / (x2 - x1));
+				float s = ((float)(y-y1) / (y2-y1));
+				bool isUpper = t > s;
+
+				tcu::Vec4 a(colors[0],		colors[1],		colors[2],		colors[3]);
+				tcu::Vec4 b(colors[4 + 0],	colors[4 + 1],	colors[4 + 2],	colors[4 + 3]);
+				tcu::Vec4 c(colors[8 + 0],	colors[8 + 1],	colors[8 + 2],	colors[8 + 3]);
+				tcu::Vec4 d(colors[12 + 0],	colors[12 + 1],	colors[12 + 2],	colors[12 + 3]);
+
+
+				tcu::Vec4 color;
+
+				if (isUpper)
+					color = a * (1.0f - t)  + b * (t - s) + s * c;
+				else
+					color = a * (1.0f - s)  + d * (s - t) + t * c;
+
+				int red		= deClamp32((int)(255.0f * color.x()), 0, 255);
+				int green	= deClamp32((int)(255.0f * color.y()), 0, 255);
+				int blue	= deClamp32((int)(255.0f * color.z()), 0, 255);
+				int alpha	= deClamp32((int)(255.0f * color.w()), 0, 255);
+
+				reference->setPixel(x, y, tcu::RGBA(red, green, blue, alpha));
+			}
+		}
+	}
+}
+
+SharingTests::SharingTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup	(eglTestCtx, "sharing", "Sharing test cases")
+{
+}
+
+void SharingTests::init (void)
+{
+	TestCaseGroup* gles2 = new TestCaseGroup(m_eglTestCtx, "gles2", "OpenGL ES 2 sharing test");
+
+	TestCaseGroup* context = new TestCaseGroup(m_eglTestCtx, "context", "Context creation and destruction tests");
+
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= false;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= true;
+		spec.renderOnContexB		= true;
+		spec.verifyOnContexA		= true;
+		spec.verifyOnContexB		= true;
+
+		context->addChild(new GLES2SharingTest(m_eglTestCtx, "create_destroy", "Simple context creation and destruction", spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= true;
+		spec.useResource			= false;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= false;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		context->addChild(new GLES2SharingTest(m_eglTestCtx, "create_destroy_mixed", "Simple context creation and destruction test with different destruction order", spec));
+	}
+
+	gles2->addChild(context);
+
+	TestCaseGroup* buffer = new TestCaseGroup(m_eglTestCtx, "buffer", "Buffer creation, destruction and rendering test");
+
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		buffer->addChild(new GLES2BufferSharingTest(m_eglTestCtx, "create_delete", "Create and delete on shared context", spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= true;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		buffer->addChild(new GLES2BufferSharingTest(m_eglTestCtx, "create_delete_mixed", "Create and delet on different contexts", spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= true;
+		spec.renderOnContexB		= true;
+		spec.verifyOnContexA		= true;
+		spec.verifyOnContexB		= true;
+
+		buffer->addChild(new GLES2BufferSharingTest(m_eglTestCtx, "render", "Create, rendering on two different contexts and delete", spec));
+	}
+
+	gles2->addChild(buffer);
+
+	TestCaseGroup* texture = new TestCaseGroup(m_eglTestCtx, "texture", "Texture creation, destruction and rendering tests");
+
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		texture->addChild(new GLES2TextureSharingTest(m_eglTestCtx, "create_delete", "Create and delete on shared context", spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= true;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		texture->addChild(new GLES2TextureSharingTest(m_eglTestCtx, "create_delete_mixed", "Create and delete on different contexts", spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= true;
+		spec.renderOnContexB		= true;
+		spec.verifyOnContexA		= true;
+		spec.verifyOnContexB		= true;
+
+		texture->addChild(new GLES2TextureSharingTest(m_eglTestCtx, "render", "Create, render in two contexts and delete", spec));
+	}
+
+	gles2->addChild(texture);
+
+	TestCaseGroup* program = new TestCaseGroup(m_eglTestCtx, "program", "Program creation, destruction and rendering test");
+
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		program->addChild(new GLES2ProgramSharingTest(m_eglTestCtx, "create_delete", "Create and delete on shared context", spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= true;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		program->addChild(new GLES2ProgramSharingTest(m_eglTestCtx, "create_delete_mixed", "Create and delete on different contexts", spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= true;
+		spec.renderOnContexB		= true;
+		spec.verifyOnContexA		= true;
+		spec.verifyOnContexB		= true;
+
+		program->addChild(new GLES2ProgramSharingTest(m_eglTestCtx, "render", "Create, render in two contexts and delete", spec));
+	}
+
+	gles2->addChild(program);
+
+	TestCaseGroup* shader = new TestCaseGroup(m_eglTestCtx, "shader", "Shader creation, destruction and rendering test");
+
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		shader->addChild(new GLES2ShaderSharingTest(m_eglTestCtx, "create_delete_vert", "Create and delete on shared context", GL_VERTEX_SHADER, spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= true;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		shader->addChild(new GLES2ShaderSharingTest(m_eglTestCtx, "create_delete_mixed_vert", "Create and delete on different contexts", GL_VERTEX_SHADER, spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= true;
+		spec.renderOnContexB		= true;
+		spec.verifyOnContexA		= true;
+		spec.verifyOnContexB		= true;
+
+		shader->addChild(new GLES2ShaderSharingTest(m_eglTestCtx, "render_vert", "Create, render on two contexts and delete", GL_VERTEX_SHADER, spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		shader->addChild(new GLES2ShaderSharingTest(m_eglTestCtx, "create_delete_frag", "Create and delete on shared context", GL_FRAGMENT_SHADER, spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= true;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= false;
+		spec.renderOnContexB		= false;
+		spec.verifyOnContexA		= false;
+		spec.verifyOnContexB		= false;
+
+		shader->addChild(new GLES2ShaderSharingTest(m_eglTestCtx, "create_delete_mixed_frag", "Create and delete on different contexts", GL_FRAGMENT_SHADER, spec));
+	}
+	{
+		GLES2SharingTest::TestSpec spec;
+		spec.destroyContextBFirst	= false;
+		spec.useResource			= true;
+		spec.destroyOnContexB		= false;
+		spec.initializeData			= true;
+		spec.renderOnContexA		= true;
+		spec.renderOnContexB		= true;
+		spec.verifyOnContexA		= true;
+		spec.verifyOnContexB		= true;
+
+		shader->addChild(new GLES2ShaderSharingTest(m_eglTestCtx, "render_frag", "Create, render on two contexts and delete", GL_FRAGMENT_SHADER, spec));
+	}
+
+
+	gles2->addChild(shader);
+
+
+	gles2->addChild(new GLES2SharingThreadedTests(m_eglTestCtx));
+
+	addChild(gles2);
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglGLES2SharingTests.hpp b/modules/egl/teglGLES2SharingTests.hpp
new file mode 100644
index 0000000..45070d2
--- /dev/null
+++ b/modules/egl/teglGLES2SharingTests.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLGLES2SHARINGTESTS_HPP
+#define _TEGLGLES2SHARINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL gles2 sharing tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class SharingTests : public TestCaseGroup
+{
+public:
+			SharingTests		(EglTestContext& eglTestCtx);
+	void	init				(void);
+};
+
+
+} // egl
+} // deqp
+#endif // _TEGLGLES2SHARINGTESTS_HPP
diff --git a/modules/egl/teglGLES2SharingThreadedTests.cpp b/modules/egl/teglGLES2SharingThreadedTests.cpp
new file mode 100644
index 0000000..9ec510f
--- /dev/null
+++ b/modules/egl/teglGLES2SharingThreadedTests.cpp
@@ -0,0 +1,5238 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL gles2 sharing threaded tests
+ *//*--------------------------------------------------------------------*/
+
+#include "teglGLES2SharingThreadedTests.hpp"
+
+#include "tcuTestLog.hpp"
+
+#include "deRandom.hpp"
+#include "deThread.hpp"
+#include "deSharedPtr.hpp"
+#include "deMutex.hpp"
+#include "deSemaphore.hpp"
+#include "deStringUtil.hpp"
+
+#include "deClock.h"
+#include "deString.h"
+#include "deMemory.h"
+#include "deMath.h"
+
+#include "gluDefs.hpp"
+
+#include "tcuThreadUtil.hpp"
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#ifndef EGL_KHR_wait_sync
+#define EGL_KHR_wait_sync 1
+typedef EGLint (EGLAPIENTRYP PFNEGLWAITSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags);
+#ifdef EGL_EGLEXT_PROTOTYPES
+EGLAPI EGLint EGLAPIENTRY eglWaitSyncKHR (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags);
+#endif
+#endif /* EGL_KHR_wait_sync */
+
+#include <vector>
+#include <string>
+#include <memory>
+#include <sstream>
+
+using std::vector;
+using std::string;
+using de::SharedPtr;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace GLES2ThreadTest
+{
+
+class Texture;
+class Buffer;
+class Shader;
+class Program;
+class GLES2ResourceManager
+{
+public:
+
+	SharedPtr<Texture>			popTexture			(int index);
+	const SharedPtr<Texture>	getTexture			(int index) const { return m_textures[index]; }
+	void						addTexture			(SharedPtr<Texture> texture) { m_textures.push_back(texture); }
+	int							getTextureCount		(void) const { return (int)m_textures.size(); }
+
+	SharedPtr<Buffer>			popBuffer			(int index);
+	const SharedPtr<Buffer>		getBuffer			(int index) const { return m_buffers[index]; }
+	void						addBuffer			(SharedPtr<Buffer> buffer) { m_buffers.push_back(buffer); }
+	int							getBufferCount		(void) const { return (int)m_buffers.size(); }
+
+	SharedPtr<Shader>			popShader			(int index);
+	const SharedPtr<Shader>		getShader			(int index) const { return m_shaders[index]; }
+	void						addShader			(SharedPtr<Shader> shader) { m_shaders.push_back(shader); }
+	int							getShaderCount		(void) const { return (int)m_shaders.size(); }
+
+	SharedPtr<Program>			popProgram			(int index);
+	const SharedPtr<Program>	getProgram			(int index) const { return m_programs[index]; }
+	void						addProgram			(SharedPtr<Program> program) { m_programs.push_back(program); }
+	int							getProgramCount		(void) const { return (int)m_programs.size(); }
+
+private:
+	std::vector<SharedPtr<Texture> >	m_textures;
+	std::vector<SharedPtr<Buffer> >		m_buffers;
+	std::vector<SharedPtr<Shader> >		m_shaders;
+	std::vector<SharedPtr<Program> >	m_programs;
+};
+
+SharedPtr<Texture> GLES2ResourceManager::popTexture (int index)
+{
+	SharedPtr<Texture> texture = m_textures[index];
+
+	m_textures.erase(m_textures.begin() + index);
+
+	return texture;
+}
+
+SharedPtr<Buffer> GLES2ResourceManager::popBuffer (int index)
+{
+	SharedPtr<Buffer> buffer = m_buffers[index];
+
+	m_buffers.erase(m_buffers.begin() + index);
+
+	return buffer;
+}
+
+SharedPtr<Shader> GLES2ResourceManager::popShader (int index)
+{
+	SharedPtr<Shader> shader = m_shaders[index];
+
+	m_shaders.erase(m_shaders.begin() + index);
+
+	return shader;
+}
+
+SharedPtr<Program> GLES2ResourceManager::popProgram (int index)
+{
+	SharedPtr<Program> program = m_programs[index];
+
+	m_programs.erase(m_programs.begin() + index);
+
+	return program;
+}
+
+class GLES2Context : public tcu::ThreadUtil::Object
+{
+public:
+				GLES2Context		(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<GLES2ResourceManager> resourceManager);
+				~GLES2Context		(void);
+
+	// Call generation time attributes
+	SharedPtr<GLES2ResourceManager>	resourceManager;
+
+	// Run time attributes
+	EGLDisplay		display;
+	EGLContext		context;
+
+	struct
+	{
+		PFNGLEGLIMAGETARGETTEXTURE2DOESPROC	imageTargetTexture2D;
+	} glExtensions;
+private:
+					GLES2Context		(const GLES2Context&);
+	GLES2Context&	operator=			(const GLES2Context&);
+};
+
+GLES2Context::GLES2Context (SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<GLES2ResourceManager> resourceManager_)
+	: tcu::ThreadUtil::Object	("Context", event)
+	, resourceManager			(resourceManager_)
+	, display					(EGL_NO_DISPLAY)
+	, context					(EGL_NO_CONTEXT)
+{
+	glExtensions.imageTargetTexture2D = NULL;
+}
+
+GLES2Context::~GLES2Context (void)
+{
+}
+
+class Surface : public tcu::ThreadUtil::Object
+{
+public:
+				Surface		(SharedPtr<tcu::ThreadUtil::Event> event);
+				~Surface	(void);
+
+	// Run time attributes
+	EGLSurface	surface;
+
+private:
+	Surface		(const Surface&);
+	Surface&	operator=	(const Surface&);
+};
+
+Surface::Surface (SharedPtr<tcu::ThreadUtil::Event> event)
+	: tcu::ThreadUtil::Object	("Surface", event)
+	, surface					(EGL_NO_SURFACE)
+{
+}
+
+Surface::~Surface (void)
+{
+}
+
+// EGL thread with thread specifig state
+class EGLThread : public tcu::ThreadUtil::Thread
+{
+public:
+								EGLThread	(int seed);
+								~EGLThread	(void);
+	virtual void				deinit		(void);
+
+	// Generation time attributes
+	SharedPtr<GLES2Context>		context;
+	SharedPtr<Surface>			surface;
+
+	// Runtime attributes
+
+	SharedPtr<GLES2Context>		runtimeContext;
+	EGLSurface					eglSurface;
+
+	struct
+	{
+		PFNEGLCREATESYNCKHRPROC		createSync;
+		PFNEGLDESTROYSYNCKHRPROC	destroySync;
+		PFNEGLCLIENTWAITSYNCKHRPROC	clientWaitSync;
+
+		PFNEGLWAITSYNCKHRPROC		waitSync;
+
+		PFNEGLCREATEIMAGEKHRPROC	createImage;
+		PFNEGLDESTROYIMAGEKHRPROC	destroyImage;
+	} eglExtensions;
+
+private:
+};
+
+EGLThread::EGLThread (int seed)
+	: tcu::ThreadUtil::Thread	(seed)
+	, eglSurface				(EGL_NO_SURFACE)
+{
+	eglExtensions.createSync		= NULL;
+	eglExtensions.destroySync		= NULL;
+	eglExtensions.clientWaitSync	= NULL;
+
+	eglExtensions.createImage		= NULL;
+	eglExtensions.destroyImage		= NULL;
+
+	eglExtensions.createSync		= (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR");
+	eglExtensions.destroySync		= (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress("eglDestroySyncKHR");
+	eglExtensions.clientWaitSync	= (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress("eglClientWaitSyncKHR");
+
+	eglExtensions.waitSync			= (PFNEGLWAITSYNCKHRPROC)eglGetProcAddress("eglWaitSyncKHR");
+
+	eglExtensions.createImage		= (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
+	eglExtensions.destroyImage		= (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
+}
+
+void EGLThread::deinit (void)
+{
+	if (runtimeContext)
+	{
+		if (runtimeContext->context != EGL_NO_CONTEXT)
+			eglMakeCurrent(runtimeContext->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+		eglDestroyContext(runtimeContext->display, runtimeContext->context);
+		eglDestroySurface(runtimeContext->display, eglSurface);
+
+		runtimeContext->context	= EGL_NO_CONTEXT;
+		eglSurface	= EGL_NO_SURFACE;
+	}
+
+	eglReleaseThread();
+}
+
+EGLThread::~EGLThread (void)
+{
+}
+
+class FenceSync
+{
+public:
+				FenceSync	(void);
+				~FenceSync	(void);
+
+	void		init		(EGLThread& thread, bool serverSync);
+	bool		waitReady	(EGLThread& thread);
+
+	void		addWaiter	(void);
+
+private:
+	EGLDisplay	m_display;
+	EGLSyncKHR	m_sync;
+	de::Mutex	m_lock;
+	int			m_waiterCount;
+	bool		m_serverSync;
+};
+
+FenceSync::FenceSync (void)
+	: m_display		(EGL_NO_DISPLAY)
+	, m_sync		(NULL)
+	, m_waiterCount	(0)
+	, m_serverSync	(false)
+{
+}
+
+FenceSync::~FenceSync (void)
+{
+}
+
+void FenceSync::addWaiter (void)
+{
+	m_lock.lock();
+	m_waiterCount++;
+	m_lock.unlock();
+}
+
+void FenceSync::init (EGLThread& thread, bool serverSync)
+{
+	m_display		= thread.runtimeContext->display;
+	m_serverSync	= serverSync;
+
+	// Use sync only if somebody will actualy depend on it
+	m_lock.lock();
+	if (m_waiterCount > 0)
+	{
+		thread.newMessage() << "Begin -- eglCreateSyncKHR(" << ((size_t)m_display) << ", EGL_SYNC_FENCE_KHR, NULL)" << tcu::ThreadUtil::Message::End;
+		m_sync = thread.eglExtensions.createSync(m_display, EGL_SYNC_FENCE_KHR, NULL);
+		thread.newMessage() << "End -- " << ((size_t)m_sync) << " = eglCreateSyncKHR()" << tcu::ThreadUtil::Message::End;
+		TCU_CHECK(m_sync);
+	}
+	m_lock.unlock();
+}
+
+bool FenceSync::waitReady (EGLThread& thread)
+{
+	bool ok = true;
+	if (m_serverSync)
+	{
+		thread.newMessage() << "Begin -- eglWaitSyncKHR(" << ((size_t)m_display) << ", " << ((size_t)m_sync) << ", 0)" << tcu::ThreadUtil::Message::End;
+		EGLint result = thread.eglExtensions.waitSync(m_display, m_sync, 0);
+		thread.newMessage() << "End -- " << result << " = eglWaitSyncKHR()" << tcu::ThreadUtil::Message::End;
+		ok = result == EGL_TRUE;
+	}
+	else
+	{
+		thread.newMessage() << "Begin -- eglClientWaitSyncKHR(" << ((size_t)m_display) << ", " << ((size_t)m_sync) << ", EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 1000 000 000)" << tcu::ThreadUtil::Message::End;
+		EGLint result = thread.eglExtensions.clientWaitSync(m_display, m_sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 1000000000);
+		thread.newMessage() << "End -- " << result << " = eglClientWaitSyncKHR()" << tcu::ThreadUtil::Message::End;
+		ok = result == EGL_CONDITION_SATISFIED_KHR;
+	}
+
+	m_lock.lock();
+	m_waiterCount--;
+	DE_ASSERT(m_waiterCount >= 0);
+
+	if (m_waiterCount == 0)
+	{
+		// \note [mika] This is no longer deterministic and eglDestroySyncKHR might happen in different places and in different threads
+		thread.newMessage() << "Begin -- eglDestroySyncKHR(" << ((size_t)m_display) << ", " << ((size_t)m_sync) << ")" << tcu::ThreadUtil::Message::End;
+		EGLint destroyResult = thread.eglExtensions.destroySync(m_display, m_sync);
+		thread.newMessage() << "End -- " << destroyResult << " = eglDestroySyncKHR()" << tcu::ThreadUtil::Message::End;
+		m_sync = NULL;
+	}
+
+	m_lock.unlock();
+
+	return ok;
+}
+
+class Object : public tcu::ThreadUtil::Object
+{
+public:
+							Object			(const char* type, SharedPtr<tcu::ThreadUtil::Event> e, SharedPtr<FenceSync> sync);
+							~Object			(void);
+
+	void					readGL			(SharedPtr<FenceSync> sync, std::vector<SharedPtr<FenceSync> >& deps);
+	void					modifyGL		(SharedPtr<FenceSync> sync, std::vector<SharedPtr<FenceSync> >& deps);
+
+private:
+	SharedPtr<FenceSync>			m_modifySync;
+	vector<SharedPtr<FenceSync> >	m_readSyncs;
+};
+
+Object::Object (const char* type, SharedPtr<tcu::ThreadUtil::Event> e, SharedPtr<FenceSync> sync)
+	: tcu::ThreadUtil::Object	(type, e)
+	, m_modifySync				(sync)
+{
+}
+
+Object::~Object	(void)
+{
+}
+
+void Object::readGL (SharedPtr<FenceSync> sync, std::vector<SharedPtr<FenceSync> >& deps)
+{
+	if (m_modifySync)
+		m_modifySync->addWaiter();
+
+	// Make call depend on last modifying call
+	deps.push_back(m_modifySync);
+
+	// Add read dependency
+	m_readSyncs.push_back(sync);
+}
+
+void Object::modifyGL (SharedPtr<FenceSync> sync, std::vector<SharedPtr<FenceSync> >& deps)
+{
+	// Make call depend on all reads
+	for (int readNdx = 0; readNdx < (int)m_readSyncs.size(); readNdx++)
+	{
+		if (m_readSyncs[readNdx])
+			m_readSyncs[readNdx]->addWaiter();
+
+		deps.push_back(m_readSyncs[readNdx]);
+	}
+
+	if (m_modifySync)
+		m_modifySync->addWaiter();
+
+	deps.push_back(m_modifySync);
+
+	// Update last modifying call
+	m_modifySync = sync;
+
+	// Clear read dependencies of last "version" of this object
+	m_readSyncs.clear();
+}
+
+class Operation : public tcu::ThreadUtil::Operation
+{
+public:
+							Operation		(const char* name, bool useSync, bool serverSync);
+	virtual					~Operation		(void);
+
+	SharedPtr<FenceSync>	getSync			(void) { return m_sync; }
+	void					readGLObject	(SharedPtr<Object> object);
+	void					modifyGLObject	(SharedPtr<Object> object);
+
+	virtual void			execute			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	bool								m_useSync;
+	bool								m_serverSync;
+	std::vector<SharedPtr<FenceSync> >	m_syncDeps;
+	SharedPtr<FenceSync>				m_sync;
+};
+
+Operation::Operation (const char* name, bool useSync, bool serverSync)
+	: tcu::ThreadUtil::Operation	(name)
+	, m_useSync						(useSync)
+	, m_serverSync					(serverSync)
+	, m_sync						(useSync ? SharedPtr<FenceSync>(new FenceSync()) : SharedPtr<FenceSync>())
+{
+}
+
+Operation::~Operation (void)
+{
+}
+
+void Operation::readGLObject (SharedPtr<Object> object)
+{
+	object->read(m_event, m_deps);
+	object->readGL(m_sync, m_syncDeps);
+}
+
+void Operation::modifyGLObject (SharedPtr<Object> object)
+{
+	object->modify(m_event, m_deps);
+	object->modifyGL(m_sync, m_syncDeps);
+}
+
+void Operation::execute (tcu::ThreadUtil::Thread& thread)
+{
+	bool success = true;
+
+	// Wait for dependencies and check that they succeeded
+	for (int depNdx = 0; depNdx < (int)m_deps.size(); depNdx++)
+	{
+		if (!m_deps[depNdx]->waitReady())
+			success = false;
+	}
+
+	// Try execute operation
+	if (success)
+	{
+		try
+		{
+			if (m_useSync)
+			{
+				for (int depNdx = 0; depNdx < (int)m_syncDeps.size(); depNdx++)
+				{
+					EGLThread* eglThread = dynamic_cast<EGLThread*>(&thread);
+					DE_ASSERT(eglThread);
+					if (m_syncDeps[depNdx]->waitReady(*eglThread) != tcu::ThreadUtil::Event::RESULT_OK)
+					{
+						success = false;
+						break;
+					}
+				}
+			}
+	
+			if (success)
+			{
+				exec(thread);
+				if (m_useSync)
+				{
+					EGLThread* eglThread = dynamic_cast<EGLThread*>(&thread);
+					DE_ASSERT(eglThread);
+					m_sync->init(*eglThread, m_serverSync);
+					thread.newMessage() << "Begin -- glFlush()" << tcu::ThreadUtil::Message::End;
+					GLU_CHECK_CALL(glFlush());
+					thread.newMessage() << "End -- glFlush()" << tcu::ThreadUtil::Message::End;
+				}
+				else
+				{
+					thread.newMessage() << "Begin -- glFinish()" << tcu::ThreadUtil::Message::End;
+					GLU_CHECK_CALL(glFinish());
+					thread.newMessage() << "End -- glFinish()" << tcu::ThreadUtil::Message::End;
+				}
+			}
+		}
+		catch (...)
+		{
+			// Got exception event failed
+			m_event->setResult(tcu::ThreadUtil::Event::RESULT_FAILED);
+			throw;
+		}
+
+		m_event->setResult(tcu::ThreadUtil::Event::RESULT_OK);
+	}
+
+	if (!success)
+		m_event->setResult(tcu::ThreadUtil::Event::RESULT_FAILED);
+
+	m_deps.clear();
+	m_event = SharedPtr<tcu::ThreadUtil::Event>();
+	m_syncDeps.clear();
+	m_sync = SharedPtr<FenceSync>();
+}
+
+class EGLImage : public Object
+{
+public:
+				EGLImage	(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync);
+	virtual		~EGLImage	(void) {}
+
+	EGLImageKHR	image;
+};
+
+// EGLResource manager
+class EGLResourceManager
+{
+public:
+
+	void					addContext		(SharedPtr<GLES2Context> context) { m_contexts.push_back(context); }
+	void					addSurface		(SharedPtr<Surface> surface) { m_surfaces.push_back(surface); }
+	void					addImage		(SharedPtr<EGLImage> image) { m_images.push_back(image); }
+
+	SharedPtr<Surface>		popSurface		(int index);
+	SharedPtr<GLES2Context>	popContext		(int index);
+	SharedPtr<EGLImage>		popImage		(int index);
+
+	int						getContextCount	(void) const { return (int)m_contexts.size(); }
+	int						getSurfaceCount	(void) const { return (int)m_surfaces.size(); }
+	int						getImageCount	(void) const { return (int)m_images.size(); }
+
+private:
+	std::vector<SharedPtr<GLES2Context> >	m_contexts;
+	std::vector<SharedPtr<Surface> >		m_surfaces;
+	std::vector<SharedPtr<EGLImage> >		m_images;
+};
+
+SharedPtr<Surface> EGLResourceManager::popSurface (int index)
+{
+	SharedPtr<Surface> surface = m_surfaces[index];
+	m_surfaces.erase(m_surfaces.begin() + index);
+	return surface;
+}
+
+SharedPtr<GLES2Context> EGLResourceManager::popContext (int index)
+{
+	SharedPtr<GLES2Context> context = m_contexts[index];
+	m_contexts.erase(m_contexts.begin() + index);
+	return context;
+}
+
+SharedPtr<EGLImage> EGLResourceManager::popImage (int index)
+{
+	SharedPtr<EGLImage> image = m_images[index];
+	m_images.erase(m_images.begin() + index);
+	return image;
+}
+
+class CreateContext : public tcu::ThreadUtil::Operation
+{
+public:
+				CreateContext	(EGLDisplay display, EGLConfig config, SharedPtr<GLES2Context> shared, SharedPtr<GLES2Context>& context);
+
+	void		exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	EGLDisplay					m_display;
+	EGLConfig					m_config;
+	SharedPtr<GLES2Context>		m_shared;
+	SharedPtr<GLES2Context>		m_context;
+};
+
+CreateContext::CreateContext (EGLDisplay display, EGLConfig config, SharedPtr<GLES2Context> shared, SharedPtr<GLES2Context>& context)
+	: tcu::ThreadUtil::Operation	("CreateContext")
+	, m_display					(display)
+	, m_config					(config)
+	, m_shared					(shared)
+{
+	if (shared)
+		modifyObject(SharedPtr<tcu::ThreadUtil::Object>(shared));
+
+	context = SharedPtr<GLES2Context>(new GLES2Context(getEvent(), (shared ? shared->resourceManager : SharedPtr<GLES2ResourceManager>(new GLES2ResourceManager))));
+	m_context = context;
+}
+
+void CreateContext::exec (tcu::ThreadUtil::Thread& thread)
+{
+	DE_UNREF(thread);
+	m_context->display = m_display;
+
+	EGLint attriblist[] =
+	{
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+	thread.newMessage() << "Begin -- eglBindAPI(EGL_OPENGL_ES_API)" << tcu::ThreadUtil::Message::End;
+	TCU_CHECK_EGL_CALL(eglBindAPI(EGL_OPENGL_ES_API));
+	thread.newMessage() << "End -- eglBindAPI()" << tcu::ThreadUtil::Message::End;
+
+	if (m_shared)
+	{
+		DE_ASSERT(m_shared->context != EGL_NO_CONTEXT);
+		DE_ASSERT(m_shared->display != EGL_NO_DISPLAY);
+		DE_ASSERT(m_shared->display == m_display);
+
+		thread.newMessage() << "Begin -- eglCreateContext(" << m_display << ", " << m_config << ", " << m_shared->context << ", { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE })" << tcu::ThreadUtil::Message::End;
+		m_context->context = eglCreateContext(m_display, m_config, m_shared->context, attriblist);
+		thread.newMessage() << "End -- " << m_context->context << " = eglCreateContext()" << tcu::ThreadUtil::Message::End;
+	}
+	else
+	{
+		thread.newMessage() << "Begin -- eglCreateContext(" << m_display << ", " << m_config << ", EGL_NO_CONTEXT, { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE })" << tcu::ThreadUtil::Message::End;
+		m_context->context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, attriblist);
+		thread.newMessage() << "End -- " << m_context->context << " = eglCreateContext()" << tcu::ThreadUtil::Message::End;
+	}
+
+	TCU_CHECK_EGL_MSG("Failed to create GLES2 context");
+	TCU_CHECK(m_context->context != EGL_NO_CONTEXT);
+}
+
+class DestroyContext : public tcu::ThreadUtil::Operation
+{
+public:
+							DestroyContext	(SharedPtr<GLES2Context> contex);
+	void					exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<GLES2Context>	m_context;
+};
+
+DestroyContext::DestroyContext (SharedPtr<GLES2Context> contex)
+	: tcu::ThreadUtil::Operation	("DestroyContext")
+	, m_context					(contex)
+{
+	modifyObject(SharedPtr<tcu::ThreadUtil::Object>(m_context));
+}
+
+void DestroyContext::exec (tcu::ThreadUtil::Thread& thread)
+{
+	DE_UNREF(thread);
+	thread.newMessage() << "Begin -- eglDestroyContext(" << m_context->display << ", " << m_context->context << ")" << tcu::ThreadUtil::Message::End;
+	TCU_CHECK_EGL_CALL(eglDestroyContext(m_context->display, m_context->context));
+	thread.newMessage() << "End -- eglDestroyContext()" << tcu::ThreadUtil::Message::End;
+	m_context->display	= EGL_NO_DISPLAY;
+	m_context->context	= EGL_NO_CONTEXT;
+}
+
+class MakeCurrent : public tcu::ThreadUtil::Operation
+{
+public:
+			MakeCurrent	(EGLThread& thread, EGLDisplay display, SharedPtr<Surface> surface, SharedPtr<GLES2Context> context);
+
+	void	exec		(tcu::ThreadUtil::Thread& thread);
+
+private:
+	EGLDisplay				m_display;
+	SharedPtr<Surface>		m_surface;
+	SharedPtr<GLES2Context>	m_context;
+};
+
+MakeCurrent::MakeCurrent (EGLThread& thread, EGLDisplay display, SharedPtr<Surface> surface, SharedPtr<GLES2Context> context)
+	: tcu::ThreadUtil::Operation	("MakeCurrent")
+	, m_display					(display)
+	, m_surface					(surface)
+	, m_context					(context)
+{
+	if (m_context)
+		modifyObject(SharedPtr<tcu::ThreadUtil::Object>(m_context));
+
+	if (m_surface)
+		modifyObject(SharedPtr<tcu::ThreadUtil::Object>(m_surface));
+
+	// Release old contexts
+	if (thread.context)
+	{
+		modifyObject(SharedPtr<tcu::ThreadUtil::Object>(thread.context));
+	}
+
+	// Release old surface
+	if (thread.surface)
+	{
+		modifyObject(SharedPtr<tcu::ThreadUtil::Object>(thread.surface));
+	}
+
+	thread.context	= m_context;
+	thread.surface	= m_surface;
+}
+
+void MakeCurrent::exec (tcu::ThreadUtil::Thread& t)
+{
+	EGLThread& thread = *(dynamic_cast<EGLThread*>(&t));
+	DE_ASSERT(&thread);
+
+	if (m_context)
+	{
+		thread.eglSurface = m_surface->surface;
+		thread.runtimeContext = m_context;
+
+		DE_ASSERT(m_surface);
+		thread.newMessage() << "Begin -- eglMakeCurrent(" << m_display << ", " << m_surface->surface << ", " << m_surface->surface << ", " << m_context->context << ")" << tcu::ThreadUtil::Message::End;
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(m_display, m_surface->surface, m_surface->surface, m_context->context));
+		thread.newMessage() << "End -- eglMakeCurrent()" << tcu::ThreadUtil::Message::End;
+	}
+	else
+	{
+		thread.runtimeContext = m_context;
+
+		thread.newMessage() << "Begin -- eglMakeCurrent(" << m_display << ", EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)" << tcu::ThreadUtil::Message::End;
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+		thread.newMessage() << "End -- eglMakeCurrent()" << tcu::ThreadUtil::Message::End;
+	}
+}
+
+class InitGLExtension : public tcu::ThreadUtil::Operation
+{
+public:
+			InitGLExtension		(const char* extension);
+
+	void	exec				(tcu::ThreadUtil::Thread& thread);
+
+private:
+	std::string					m_extension;
+};
+
+InitGLExtension::InitGLExtension (const char* extension)
+	: tcu::ThreadUtil::Operation	("InitGLExtension")
+	, m_extension					(extension)
+{
+}
+
+void InitGLExtension::exec (tcu::ThreadUtil::Thread& t)
+{
+	EGLThread& thread = *(dynamic_cast<EGLThread*>(&t));
+
+	// Check extensions
+	bool found = false;
+
+	thread.newMessage() << "Begin -- glGetString(GL_EXTENSIONS)" << tcu::ThreadUtil::Message::End;
+	std::string extensions = (const char*)glGetString(GL_EXTENSIONS);
+	thread.newMessage() << "End -- glGetString()" << tcu::ThreadUtil::Message::End;
+
+	std::string::size_type pos = extensions.find(" ");
+
+	do
+	{
+		std::string extension;
+		if (pos != std::string::npos)
+		{
+			extension = extensions.substr(0, pos);
+			extensions = extensions.substr(pos+1);
+		}
+		else
+		{
+			extension = extensions;
+			extensions = "";
+		}
+
+		if (extension == m_extension)
+		{
+			found = true;
+			break;
+		}
+		pos = extensions.find(" ");
+	} while (pos != std::string::npos);
+
+	if (!found)
+		throw tcu::NotSupportedError((m_extension + " not supported").c_str(), "", __FILE__, __LINE__);
+
+
+	// Query function pointers
+	if (m_extension == "GL_OES_EGL_image")
+	{
+		thread.newMessage() << "Begin -- eglGetProcAddress(\"glEGLImageTargetTexture2DOES\")" << tcu::ThreadUtil::Message::End;
+		thread.runtimeContext->glExtensions.imageTargetTexture2D = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
+		thread.newMessage() << "End --  " << ((void*)thread.runtimeContext->glExtensions.imageTargetTexture2D) << " = eglGetProcAddress()"<< tcu::ThreadUtil::Message::End;
+	}
+}
+
+class CreatePBufferSurface : public tcu::ThreadUtil::Operation
+{
+public:
+				CreatePBufferSurface	(EGLDisplay display, EGLConfig config, EGLint width, EGLint height, SharedPtr<Surface>& surface);
+	void		exec					(tcu::ThreadUtil::Thread& thread);
+
+private:
+	EGLDisplay			m_display;
+	EGLConfig			m_config;
+	EGLint				m_width;
+	EGLint				m_height;
+	SharedPtr<Surface>	m_surface;
+};
+
+CreatePBufferSurface::CreatePBufferSurface (EGLDisplay display, EGLConfig config, EGLint width, EGLint height, SharedPtr<Surface>& surface)
+	: tcu::ThreadUtil::Operation	("CreatePBufferSurface")
+	, m_display					(display)
+	, m_config					(config)
+	, m_width					(width)
+	, m_height					(height)
+{
+	surface = SharedPtr<Surface>(new Surface(getEvent()));
+	m_surface = surface;
+}
+
+void CreatePBufferSurface::exec (tcu::ThreadUtil::Thread& thread)
+{
+	EGLint attriblist[] = {
+		EGL_WIDTH, m_width,
+		EGL_HEIGHT, m_height,
+		EGL_NONE
+	};
+
+	thread.newMessage() << "Begin -- eglCreatePbufferSurface(" << m_display << ", " << m_config << ", { EGL_WIDTH, " << m_width << ", EGL_HEIGHT, " << m_height << ", EGL_NONE })" << tcu::ThreadUtil::Message::End;
+	m_surface->surface = eglCreatePbufferSurface(m_display, m_config, attriblist);
+	thread.newMessage() << "End -- " << m_surface->surface << "= eglCreatePbufferSurface()" << tcu::ThreadUtil::Message::End;
+	TCU_CHECK_EGL_MSG("eglCreatePbufferSurface()");
+}
+
+class DestroySurface : public tcu::ThreadUtil::Operation
+{
+public:
+			DestroySurface	(EGLDisplay display, SharedPtr<Surface> surface);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	EGLDisplay			m_display;
+	SharedPtr<Surface>	m_surface;
+};
+
+DestroySurface::DestroySurface (EGLDisplay display, SharedPtr<Surface> surface)
+	: tcu::ThreadUtil::Operation	("DestroySurface")
+	, m_display					(display)
+	, m_surface					(surface)
+{
+	modifyObject(SharedPtr<tcu::ThreadUtil::Object>(m_surface));
+}
+
+void DestroySurface::exec (tcu::ThreadUtil::Thread& thread)
+{
+	thread.newMessage() << "Begin -- eglDestroySurface(" << m_display << ",  " << m_surface->surface << ")" << tcu::ThreadUtil::Message::End;
+	TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, m_surface->surface));
+	thread.newMessage() << "End -- eglDestroySurface()" << tcu::ThreadUtil::Message::End;
+}
+
+EGLImage::EGLImage (SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync)
+	: Object	("EGLImage", event, sync)
+	, image		(EGL_NO_IMAGE_KHR)
+{
+}
+
+class Texture : public Object
+{
+public:
+			Texture (SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync);
+
+	// Runtime parameters
+	GLuint	texture;
+
+	// Call generation time parameters
+	bool	isDefined;
+
+	SharedPtr<EGLImage>	sourceImage;
+};
+
+Texture::Texture (SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync)
+	: Object					("Texture", event, sync)
+	, texture					(0)
+	, isDefined					(false)
+{
+}
+
+class CreateTexture : public Operation
+{
+public:
+			CreateTexture	(SharedPtr<Texture>& texture, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Texture> m_texture;
+};
+
+CreateTexture::CreateTexture (SharedPtr<Texture>& texture, bool useSync, bool serverSync)
+	: Operation	("CreateTexture", useSync, serverSync)
+{
+	texture = SharedPtr<Texture>(new Texture(getEvent(), getSync()));
+	m_texture = texture;
+}
+
+void CreateTexture::exec (tcu::ThreadUtil::Thread& thread)
+{
+	GLuint tex = 0;
+
+	thread.newMessage() << "Begin -- glGenTextures(1, { 0 })" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glGenTextures(1, &tex));
+	thread.newMessage() << "End -- glGenTextures(1, { " << tex << " })" << tcu::ThreadUtil::Message::End;
+
+	m_texture->texture = tex;
+}
+
+class DeleteTexture : public Operation
+{
+public:
+			DeleteTexture	(SharedPtr<Texture> texture, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Texture> m_texture;
+};
+
+DeleteTexture::DeleteTexture (SharedPtr<Texture> texture, bool useSync, bool serverSync)
+	: Operation		("DeleteTexture", useSync, serverSync)
+	, m_texture		(texture)
+{
+	modifyGLObject(SharedPtr<Object>(m_texture));
+}
+
+void DeleteTexture::exec (tcu::ThreadUtil::Thread& thread)
+{
+	GLuint tex = m_texture->texture;
+
+	thread.newMessage() << "Begin -- glDeleteTextures(1, { " << tex << " })" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glDeleteTextures(1, &tex));
+	thread.newMessage() << "End -- glDeleteTextures()" << tcu::ThreadUtil::Message::End;
+
+	m_texture->texture = 0;
+}
+
+class TexImage2D : public Operation
+{
+public:
+			TexImage2D	(SharedPtr<Texture> texture, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, bool useSync, bool serverSync);
+	void	exec		(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Texture>	m_texture;
+	GLint				m_level;
+	GLint				m_internalFormat;
+	GLsizei				m_width;
+	GLsizei				m_height;
+	GLenum				m_format;
+	GLenum				m_type;
+};
+
+TexImage2D::TexImage2D (SharedPtr<Texture> texture, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, bool useSync, bool serverSync)
+	: Operation			("TexImage2D", useSync, serverSync)
+	, m_texture			(texture)
+	, m_level			(level)
+	, m_internalFormat	(internalFormat)
+	, m_width			(width)
+	, m_height			(height)
+	, m_format			(format)
+	, m_type			(type)
+{
+	modifyGLObject(SharedPtr<Object>(m_texture));
+	m_texture->isDefined = true;
+
+	// Orphang texture
+	texture->sourceImage = SharedPtr<EGLImage>();
+}
+
+void TexImage2D::exec (tcu::ThreadUtil::Thread& thread)
+{
+	void* dummyData = thread.getDummyData(m_width*m_height*4);
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, m_texture->texture));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glTexImage2D(GL_TEXTURE_2D, " << m_level << ", " << m_internalFormat << ", " << m_width << ", " << m_height << ", 0, " << m_format << ", " << m_type << ", data)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_2D, m_level, m_internalFormat, m_width, m_height, 0, m_format, m_type, dummyData));
+	thread.newMessage() << "End -- glTexImage2D()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+}
+
+class TexSubImage2D : public Operation
+{
+public:
+			TexSubImage2D	(SharedPtr<Texture> texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Texture>	m_texture;
+	GLint				m_level;
+	GLint				m_xoffset;
+	GLint				m_yoffset;
+	GLsizei				m_width;
+	GLsizei				m_height;
+	GLenum				m_format;
+	GLenum				m_type;
+};
+
+TexSubImage2D::TexSubImage2D (SharedPtr<Texture> texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, bool useSync, bool serverSync)
+	: Operation		("TexSubImage2D", useSync, serverSync)
+	, m_texture		(texture)
+	, m_level		(level)
+	, m_xoffset		(xoffset)
+	, m_yoffset		(yoffset)
+	, m_width		(width)
+	, m_height		(height)
+	, m_format		(format)
+	, m_type		(type)
+{
+	modifyGLObject(SharedPtr<Object>(m_texture));
+
+	if (m_texture->sourceImage)
+		modifyGLObject(SharedPtr<Object>(m_texture->sourceImage));
+}
+
+void TexSubImage2D::exec (tcu::ThreadUtil::Thread& thread)
+{
+	void* dummyData = thread.getDummyData(m_width*m_height*4);
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, m_texture->texture));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glTexSubImage2D(GL_TEXTURE_2D, " << m_level << ", " << m_xoffset << ", " << m_yoffset << ", " << m_width << ", " << m_height << ", 0, " << m_format << ", " << m_type << ", <data>)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glTexSubImage2D(GL_TEXTURE_2D, m_level, m_xoffset, m_yoffset, m_width, m_height, m_format, m_type, dummyData));
+	thread.newMessage() << "End -- glSubTexImage2D()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+}
+
+class CopyTexImage2D : public Operation
+{
+public:
+			CopyTexImage2D	(SharedPtr<Texture> texture, GLint level, GLint internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Texture>	m_texture;
+	GLint				m_level;
+	GLint				m_internalFormat;
+	GLint				m_x;
+	GLint				m_y;
+	GLsizei				m_width;
+	GLsizei				m_height;
+	GLint				m_border;
+};
+
+CopyTexImage2D::CopyTexImage2D (SharedPtr<Texture> texture, GLint level, GLint internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border, bool useSync, bool serverSync)
+	: Operation			("CopyTexImage2D", useSync, serverSync)
+	, m_texture			(texture)
+	, m_level			(level)
+	, m_internalFormat	(internalFormat)
+	, m_x				(x)
+	, m_y				(y)
+	, m_width			(width)
+	, m_height			(height)
+	, m_border			(border)
+{
+	modifyGLObject(SharedPtr<Object>(m_texture));
+	texture->isDefined = true;
+
+	// Orphang texture
+	texture->sourceImage = SharedPtr<EGLImage>();
+}
+
+void CopyTexImage2D::exec (tcu::ThreadUtil::Thread& thread)
+{
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, m_texture->texture));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glCopyTexImage2D(GL_TEXTURE_2D, " << m_level << ", " << m_internalFormat << ", " << m_x << ", " << m_y << ", " << m_width << ", " << m_height << ", " << m_border << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glCopyTexImage2D(GL_TEXTURE_2D, m_level, m_internalFormat, m_x, m_y, m_width, m_height, m_border));
+	thread.newMessage() << "End -- glCopyTexImage2D()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+}
+
+class CopyTexSubImage2D : public Operation
+{
+public:
+			CopyTexSubImage2D		(SharedPtr<Texture> texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height, bool useSync, bool serverSync);
+	void	exec					(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Texture>	m_texture;
+	GLint				m_level;
+	GLint				m_xoffset;
+	GLint				m_yoffset;
+	GLint				m_x;
+	GLint				m_y;
+	GLsizei				m_width;
+	GLsizei				m_height;
+};
+
+CopyTexSubImage2D::CopyTexSubImage2D (SharedPtr<Texture> texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height, bool useSync, bool serverSync)
+	: Operation		("CopyTexSubImage2D", useSync, serverSync)
+	, m_texture		(texture)
+	, m_level		(level)
+	, m_xoffset		(xoffset)
+	, m_yoffset		(yoffset)
+	, m_x			(x)
+	, m_y			(y)
+	, m_width		(width)
+	, m_height		(height)
+{
+	modifyGLObject(SharedPtr<Object>(m_texture));
+
+	if (m_texture->sourceImage)
+		modifyGLObject(SharedPtr<Object>(m_texture->sourceImage));
+}
+
+void CopyTexSubImage2D::exec (tcu::ThreadUtil::Thread& thread)
+{
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, m_texture->texture));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glCopyTexSubImage2D(GL_TEXTURE_2D, " << m_level << ", " << m_xoffset << ", " << m_yoffset << ", " << m_x << ", " << m_y << ", " << m_width << ", " << m_height << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glCopyTexSubImage2D(GL_TEXTURE_2D, m_level, m_xoffset, m_yoffset, m_x, m_y, m_width, m_height));
+	thread.newMessage() << "End -- glCopyTexSubImage2D()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+}
+
+class Buffer : public Object
+{
+public:
+				Buffer		(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync);
+
+	// Runtime attributes
+	GLuint		buffer;
+	GLsizeiptr	size;
+
+	// Call generation time parameters
+	bool		isDefined;
+};
+
+Buffer::Buffer (SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync)
+	: Object		("Buffer", event, sync)
+	, buffer		(0)
+	, size			(0)
+	, isDefined		(false)
+{
+}
+
+class CreateBuffer : public Operation
+{
+public:
+			CreateBuffer	(SharedPtr<Buffer>& buffer, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Buffer> m_buffer;
+};
+
+CreateBuffer::CreateBuffer (SharedPtr<Buffer>& buffer, bool useSync, bool serverSync)
+	: Operation	("CreateBuffer", useSync, serverSync)
+{
+	buffer = SharedPtr<Buffer>(new Buffer(getEvent(), getSync()));
+	m_buffer = buffer;
+}
+
+void CreateBuffer::exec (tcu::ThreadUtil::Thread& thread)
+{
+	GLuint buffer = 0;
+
+	thread.newMessage() << "Begin -- glGenBuffers(1, { 0 })" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glGenBuffers(1, &buffer));
+	thread.newMessage() << "End -- glGenBuffers(1, { " << buffer << " })" << tcu::ThreadUtil::Message::End;
+
+	m_buffer->buffer = buffer;
+}
+
+class DeleteBuffer : public Operation
+{
+public:
+			DeleteBuffer	(SharedPtr<Buffer> buffer, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Buffer> m_buffer;
+};
+
+DeleteBuffer::DeleteBuffer (SharedPtr<Buffer> buffer, bool useSync, bool serverSync)
+	: Operation	("DeleteBuffer", useSync, serverSync)
+	, m_buffer	(buffer)
+{
+	modifyGLObject(SharedPtr<Object>(m_buffer));
+}
+
+void DeleteBuffer::exec (tcu::ThreadUtil::Thread& thread)
+{
+	GLuint buffer = m_buffer->buffer;
+
+	thread.newMessage() << "Begin -- glDeleteBuffers(1, { " << buffer << " })" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glDeleteBuffers(1, &buffer));
+	thread.newMessage() << "End -- glDeleteBuffers()" << tcu::ThreadUtil::Message::End;
+
+	m_buffer->buffer = 0;
+}
+
+class BufferData : public Operation
+{
+public:
+			BufferData	(SharedPtr<Buffer> buffer, GLenum target, GLsizeiptr size, GLenum usage, bool useSync, bool serverSync);
+	void	exec		(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Buffer>	m_buffer;
+	GLenum				m_target;
+	GLsizeiptr			m_size;
+	GLenum				m_usage;
+};
+
+BufferData::BufferData (SharedPtr<Buffer> buffer, GLenum target, GLsizeiptr size, GLenum usage, bool useSync, bool serverSync)
+	: Operation	("BufferData", useSync, serverSync)
+	, m_buffer	(buffer)
+	, m_target	(target)
+	, m_size	(size)
+	, m_usage	(usage)
+{
+	modifyGLObject(SharedPtr<Object>(m_buffer));
+	buffer->isDefined	= true;
+	buffer->size		= size;
+}
+
+void BufferData::exec (tcu::ThreadUtil::Thread& thread)
+{
+	void* dummyData = thread.getDummyData(m_size);
+
+	thread.newMessage() << "Begin -- glBindBuffer(" << m_target << ", " << m_buffer->buffer << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindBuffer(m_target, m_buffer->buffer));
+	thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBufferData(" << m_target << ", " << m_size << ", <DATA>, " << m_usage << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBufferData(m_target, m_size, dummyData, m_usage));
+	thread.newMessage() << "End -- glBufferData()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindBuffer(" << m_target << ", 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindBuffer(m_target, 0));
+	thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;
+}
+
+class BufferSubData : public Operation
+{
+public:
+			BufferSubData	(SharedPtr<Buffer> buffer, GLenum target, GLintptr offset, GLsizeiptr size, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Buffer>	m_buffer;
+	GLenum				m_target;
+	GLintptr			m_offset;
+	GLsizeiptr			m_size;
+};
+
+BufferSubData::BufferSubData (SharedPtr<Buffer> buffer, GLenum target, GLintptr offset, GLsizeiptr size, bool useSync, bool serverSync)
+	: Operation	("BufferSubData", useSync, serverSync)
+	, m_buffer	(buffer)
+	, m_target	(target)
+	, m_offset	(offset)
+	, m_size	(size)
+{
+	modifyGLObject(SharedPtr<Object>(m_buffer));
+}
+
+void BufferSubData::exec (tcu::ThreadUtil::Thread& thread)
+{
+	void* dummyData = thread.getDummyData(m_size);
+
+	thread.newMessage() << "Begin -- glBindBuffer(" << m_target << ", " << m_buffer->buffer << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindBuffer(m_target, m_buffer->buffer));
+	thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBufferSubData(" << m_target << ", " << m_offset << ", " << m_size << ", <DATA>)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBufferSubData(m_target, m_offset, m_size, dummyData));
+	thread.newMessage() << "End -- glBufferSubData()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindBuffer(" << m_target << ", 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindBuffer(m_target, 0));
+	thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;
+}
+
+class Shader : public Object
+{
+public:
+				Shader		(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync);
+
+	GLuint		shader;
+	GLenum		type;
+	bool		isDefined;
+	bool		compiled;
+};
+
+Shader::Shader (SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync)
+	: Object		("Shader", event, sync)
+	, shader		(0)
+	, type			(GL_NONE)
+	, isDefined		(false)
+	, compiled		(false)
+{
+}
+
+class CreateShader : public Operation
+{
+public:
+			CreateShader	(GLenum type, SharedPtr<Shader>& shader, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Shader>	m_shader;
+	GLenum				m_type;
+};
+
+CreateShader::CreateShader (GLenum type, SharedPtr<Shader>& shader, bool useSync, bool serverSync)
+	: Operation	("CreateShader", useSync, serverSync)
+	, m_type	(type)
+{
+	shader = SharedPtr<Shader>(new Shader(getEvent(), getSync()));
+	shader->type = type;
+
+	m_shader = shader;
+}
+
+void CreateShader::exec (tcu::ThreadUtil::Thread& thread)
+{
+	GLuint shader = 0;
+
+	thread.newMessage() << "Begin -- glCreateShader(" << m_type << ")" << tcu::ThreadUtil::Message::End;
+	shader = glCreateShader(m_type);
+	GLU_CHECK_MSG("glCreateShader()");
+	thread.newMessage() << "End -- " << shader  << " = glCreateShader(" << m_type << ")" << tcu::ThreadUtil::Message::End;
+
+	m_shader->shader	= shader;
+}
+
+class DeleteShader : public Operation
+{
+public:
+			DeleteShader	(SharedPtr<Shader> shader, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Shader> m_shader;
+};
+
+DeleteShader::DeleteShader (SharedPtr<Shader> shader, bool useSync, bool serverSync)
+	: Operation	("DeleteShader", useSync, serverSync)
+	, m_shader	(shader)
+{
+	modifyGLObject(SharedPtr<Object>(m_shader));
+}
+
+void DeleteShader::exec (tcu::ThreadUtil::Thread& thread)
+{
+	GLuint shader = m_shader->shader;
+
+	thread.newMessage() << "Begin -- glDeleteShader(" << shader << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glDeleteShader(shader));
+	thread.newMessage() << "End -- glDeleteShader()" << tcu::ThreadUtil::Message::End;
+
+	m_shader->shader = 0;
+}
+
+class ShaderSource : public Operation
+{
+public:
+			ShaderSource	(SharedPtr<Shader> sharder, const char* source, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Shader>	m_shader;
+	string				m_source;
+};
+
+ShaderSource::ShaderSource (SharedPtr<Shader> shader, const char* source, bool useSync, bool serverSync)
+	: Operation	("ShaderSource", useSync, serverSync)
+	, m_shader	(shader)
+	, m_source	(source)
+{
+	modifyGLObject(SharedPtr<Object>(m_shader));
+	m_shader->isDefined = true;
+}
+
+void ShaderSource::exec (tcu::ThreadUtil::Thread& thread)
+{
+	const char* shaderSource = m_source.c_str();
+
+	thread.newMessage() << "Begin -- glShaderSource(" << m_shader->shader << ", 1, \"" << shaderSource << "\", NULL)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glShaderSource(m_shader->shader, 1, &shaderSource, NULL));
+	thread.newMessage() << "End -- glShaderSource()" << tcu::ThreadUtil::Message::End;
+}
+
+class ShaderCompile : public Operation
+{
+public:
+			ShaderCompile	(SharedPtr<Shader> sharder, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Shader> m_shader;
+};
+
+ShaderCompile::ShaderCompile (SharedPtr<Shader> shader, bool useSync, bool serverSync)
+	: Operation	("ShaderCompile", useSync, serverSync)
+	, m_shader	(shader)
+{
+	m_shader->compiled = true;
+	modifyGLObject(SharedPtr<Object>(m_shader));
+}
+
+void ShaderCompile::exec (tcu::ThreadUtil::Thread& thread)
+{
+	thread.newMessage() << "Begin -- glCompileShader(" << m_shader->shader << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glCompileShader(m_shader->shader));
+	thread.newMessage() << "End -- glCompileShader()" << tcu::ThreadUtil::Message::End;
+}
+
+class Program : public Object
+{
+public:
+						Program		(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync);
+
+	// Generation time attributes
+	SharedPtr<Shader>	vertexShader;
+	SharedPtr<Shader>	fragmentShader;
+	bool				linked;
+
+	// Runtime attributes
+	GLuint				program;
+	GLuint				runtimeVertexShader;
+	GLuint				runtimeFragmentShader;
+};
+
+Program::Program (SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync)
+	: Object					("Program", event, sync)
+	, linked					(false)
+	, program					(0)
+	, runtimeVertexShader		(0)
+	, runtimeFragmentShader		(0)
+{
+}
+
+class CreateProgram : public Operation
+{
+public:
+			CreateProgram	(SharedPtr<Program>& program, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Program> m_program;
+};
+
+CreateProgram::CreateProgram (SharedPtr<Program>& program, bool useSync, bool serverSync)
+	: Operation	("CreateProgram", useSync, serverSync)
+{
+	program = SharedPtr<Program>(new Program(getEvent(), getSync()));
+	m_program = program;
+}
+
+void CreateProgram::exec (tcu::ThreadUtil::Thread& thread)
+{
+	GLuint program = 0;
+
+	thread.newMessage() << "Begin -- glCreateProgram()" << tcu::ThreadUtil::Message::End;
+	program = glCreateProgram();
+	GLU_CHECK_MSG("glCreateProgram()");
+	thread.newMessage() << "End -- " << program  << " = glCreateProgram()" << tcu::ThreadUtil::Message::End;
+
+	m_program->program	= program;
+}
+
+class DeleteProgram : public Operation
+{
+public:
+			DeleteProgram	(SharedPtr<Program> program, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Program> m_program;
+};
+
+DeleteProgram::DeleteProgram (SharedPtr<Program> program, bool useSync, bool serverSync)
+	: Operation	("DeleteProgram", useSync, serverSync)
+	, m_program	(program)
+{
+	modifyGLObject(SharedPtr<Object>(m_program));
+}
+
+void DeleteProgram::exec (tcu::ThreadUtil::Thread& thread)
+{
+	GLuint program = m_program->program;
+
+	thread.newMessage() << "Begin -- glDeleteProgram(" << program << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glDeleteProgram(program));
+	thread.newMessage() << "End -- glDeleteProgram()" << tcu::ThreadUtil::Message::End;
+
+	m_program->program = 0;
+}
+
+class AttachShader : public Operation
+{
+public:
+			AttachShader	(SharedPtr<Program> sharder, SharedPtr<Shader> shader, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Program>	m_program;
+	SharedPtr<Shader>	m_shader;
+};
+
+AttachShader::AttachShader (SharedPtr<Program> program, SharedPtr<Shader> shader, bool useSync, bool serverSync)
+	: Operation	("AttachShader", useSync, serverSync)
+	, m_program	(program)
+	, m_shader	(shader)
+{
+	modifyGLObject(SharedPtr<Object>(m_program));
+	readGLObject(SharedPtr<Object>(m_shader));
+
+	if (m_shader->type == GL_VERTEX_SHADER)
+		m_program->vertexShader = shader;
+	else if (m_shader->type == GL_FRAGMENT_SHADER)
+		m_program->fragmentShader = shader;
+	else
+		DE_ASSERT(false);
+}
+
+void AttachShader::exec (tcu::ThreadUtil::Thread& thread)
+{
+	thread.newMessage() << "Begin -- glAttachShader(" << m_program->program << ", " << m_shader->shader << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glAttachShader(m_program->program, m_shader->shader));
+	thread.newMessage() << "End -- glAttachShader()" << tcu::ThreadUtil::Message::End;
+
+	if (m_shader->type == GL_VERTEX_SHADER)
+		m_program->runtimeVertexShader = m_shader->shader;
+	else if (m_shader->type == GL_FRAGMENT_SHADER)
+		m_program->runtimeFragmentShader = m_shader->shader;
+	else
+		DE_ASSERT(false);
+}
+
+class DetachShader : public Operation
+{
+public:
+			DetachShader	(SharedPtr<Program> sharder, GLenum type, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Program>	m_program;
+	GLenum				m_type;
+};
+
+DetachShader::DetachShader (SharedPtr<Program> program, GLenum type, bool useSync, bool serverSync)
+	: Operation	("DetachShader", useSync, serverSync)
+	, m_program	(program)
+	, m_type	(type)
+{
+	modifyGLObject(SharedPtr<Object>(m_program));
+
+	if (m_type == GL_VERTEX_SHADER)
+	{
+		DE_ASSERT(m_program->vertexShader);
+		m_program->vertexShader = SharedPtr<Shader>();
+	}
+	else if (m_type == GL_FRAGMENT_SHADER)
+	{
+		DE_ASSERT(m_program->fragmentShader);
+		m_program->fragmentShader = SharedPtr<Shader>();
+	}
+	else
+		DE_ASSERT(false);
+}
+
+void DetachShader::exec (tcu::ThreadUtil::Thread& thread)
+{
+	if (m_type == GL_VERTEX_SHADER)
+	{
+		thread.newMessage() << "Begin -- glDetachShader(" << m_program->program << ", " << m_program->runtimeVertexShader << ")" << tcu::ThreadUtil::Message::End;
+		GLU_CHECK_CALL(glDetachShader(m_program->program, m_program->runtimeVertexShader));
+		thread.newMessage() << "End -- glDetachShader()" << tcu::ThreadUtil::Message::End;
+		m_program->runtimeVertexShader = 0;
+	}
+	else if (m_type == GL_FRAGMENT_SHADER)
+	{
+		thread.newMessage() << "Begin -- glDetachShader(" << m_program->program << ", " << m_program->runtimeFragmentShader << ")" << tcu::ThreadUtil::Message::End;
+		GLU_CHECK_CALL(glDetachShader(m_program->program, m_program->runtimeFragmentShader));
+		thread.newMessage() << "End -- glDetachShader()" << tcu::ThreadUtil::Message::End;
+		m_program->runtimeFragmentShader = 0;
+	}
+	else
+		DE_ASSERT(false);
+}
+
+class LinkProgram : public Operation
+{
+public:
+			LinkProgram	(SharedPtr<Program> program, bool useSync, bool serverSync);
+	void	exec		(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Program> m_program;
+};
+
+LinkProgram::LinkProgram (SharedPtr<Program> program, bool useSync, bool serverSync)
+	: Operation	("LinkProgram", useSync, serverSync)
+	, m_program	(program)
+{
+	modifyGLObject(SharedPtr<Object>(m_program));
+	program->linked = true;
+}
+
+void LinkProgram::exec (tcu::ThreadUtil::Thread& thread)
+{
+	GLuint program = m_program->program;
+
+	thread.newMessage() << "Begin -- glLinkProgram(" << program << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glLinkProgram(program));
+	thread.newMessage() << "End -- glLinkProgram()" << tcu::ThreadUtil::Message::End;
+}
+
+class RenderBuffer : public Operation
+{
+public:
+			RenderBuffer	(SharedPtr<Program> program, SharedPtr<Buffer> buffer, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Program>	m_program;
+	SharedPtr<Buffer>	m_buffer;
+};
+
+RenderBuffer::RenderBuffer (SharedPtr<Program> program, SharedPtr<Buffer> buffer, bool useSync, bool serverSync)
+	: Operation	("RenderBuffer", useSync, serverSync)
+	, m_program	(program)
+	, m_buffer	(buffer)
+{
+	readGLObject(SharedPtr<Object>(program));
+	readGLObject(SharedPtr<Object>(buffer));
+}
+
+void RenderBuffer::exec (tcu::ThreadUtil::Thread& thread)
+{
+	thread.newMessage() << "Begin -- glClearColor(0.5f, 0.5f, 0.5f, 1.0f)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glClearColor(0.5f, 0.5f, 0.5f, 1.0f));
+	thread.newMessage() << "End -- glClearColor()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glClear(GL_COLOR_BUFFER_BIT)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+	thread.newMessage() << "End -- glClear()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glUseProgram(" << m_program->program << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glUseProgram(m_program->program));
+	thread.newMessage() << "End -- glUseProgram()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glGetAttribLocation(" << m_program->program << ", \"a_pos\")" << tcu::ThreadUtil::Message::End;
+	GLint posLoc = glGetAttribLocation(m_program->program, "a_pos");
+	GLU_CHECK_MSG("glGetAttribLocation()");
+	thread.newMessage() << "End -- " << posLoc << " = glGetAttribLocation()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glEnableVertexAttribArray(" << posLoc << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glEnableVertexAttribArray(posLoc));
+	thread.newMessage() << "End -- glEnableVertexAttribArray()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindBuffer(GL_ARRAY_BUFFER, " << m_buffer->buffer << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_buffer->buffer));
+	thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glVertexAttribPointer(" << posLoc << ", GL_BYTE, GL_TRUE, 0, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glVertexAttribPointer(posLoc, 2, GL_BYTE, GL_TRUE, 0, 0));
+	thread.newMessage() << "End -- glVertexAttribPointer()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glDrawArrays(GL_TRIANGLES, 0, " << (m_buffer->size / 2) << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_buffer->size / 2));
+	thread.newMessage() << "End -- glDrawArrays()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindBuffer(GL_ARRAY_BUFFER, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
+	thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glDisableVertexAttribArray(" << posLoc << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glDisableVertexAttribArray(posLoc));
+	thread.newMessage() << "End -- glDisableVertexAttribArray()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glUseProgram(0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glUseProgram(0));
+	thread.newMessage() << "End -- glUseProgram()" << tcu::ThreadUtil::Message::End;
+}
+
+class RenderTexture : public Operation
+{
+public:
+			RenderTexture	(SharedPtr<Program> program, SharedPtr<Texture> texture, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Program>	m_program;
+	SharedPtr<Texture>	m_texture;
+};
+
+RenderTexture::RenderTexture (SharedPtr<Program> program, SharedPtr<Texture> texture, bool useSync, bool serverSync)
+	: Operation	("RenderTexture", useSync, serverSync)
+	, m_program	(program)
+	, m_texture	(texture)
+{
+	readGLObject(SharedPtr<Object>(program));
+	readGLObject(SharedPtr<Object>(texture));
+}
+
+void RenderTexture::exec (tcu::ThreadUtil::Thread& thread)
+{
+	thread.newMessage() << "Begin -- glClearColor(0.5f, 0.5f, 0.5f, 1.0f)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glClearColor(0.5f, 0.5f, 0.5f, 1.0f));
+	thread.newMessage() << "End -- glClearColor()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glClear(GL_COLOR_BUFFER_BIT)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+	thread.newMessage() << "End -- glClear()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glUseProgram(" << m_program->program << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glUseProgram(m_program->program));
+	thread.newMessage() << "End -- glUseProgram()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, m_texture->texture));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glGetUniformLocation(" << m_program->program << ", \"u_sampler\")" << tcu::ThreadUtil::Message::End;
+	GLint samplerPos = glGetUniformLocation(m_program->program, "u_sampler");
+	GLU_CHECK_MSG("glGetUniformLocation()");
+	thread.newMessage() << "End -- glGetUniformLocation()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glUniform1i(" << samplerPos << ", 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glUniform1i(samplerPos, 0));
+	thread.newMessage() << "End -- glUniform1i()" << tcu::ThreadUtil::Message::End;
+
+
+	thread.newMessage() << "Begin -- glGetAttribLocation(" << m_program->program << ", \"a_pos\")" << tcu::ThreadUtil::Message::End;
+	GLint posLoc = glGetAttribLocation(m_program->program, "a_pos");
+	GLU_CHECK_MSG("glGetAttribLocation()");
+	thread.newMessage() << "End -- " << posLoc << " = glGetAttribLocation()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glEnableVertexAttribArray(" << posLoc << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glEnableVertexAttribArray(posLoc));
+	thread.newMessage() << "End -- glEnableVertexAttribArray()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindBuffer(GL_ARRAY_BUFFER, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
+	thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;
+
+
+	float coords[] = {
+		-1.0, -1.0,
+		 1.0, -1.0,
+		 1.0,  1.0,
+
+		 1.0,  1.0,
+		-1.0,  1.0,
+		-1.0, -1.0
+	};
+
+	thread.newMessage() << "Begin -- glVertexAttribPointer(" << posLoc << ", GL_FLOAT, GL_FALSE, 0, <data>)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, coords));
+	thread.newMessage() << "End -- glVertexAttribPointer()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glDrawArrays(GL_TRIANGLES, 0, 6)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 6));
+	thread.newMessage() << "End -- glDrawArrays()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindBuffer(GL_ARRAY_BUFFER, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
+	thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glDisableVertexAttribArray(" << posLoc << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glDisableVertexAttribArray(posLoc));
+	thread.newMessage() << "End -- glDisableVertexAttribArray()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glUseProgram(0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glUseProgram(0));
+	thread.newMessage() << "End -- glUseProgram()" << tcu::ThreadUtil::Message::End;
+}
+
+class ReadPixels : public Operation
+{
+public:
+			ReadPixels		(int x, int y, int width, int height, GLenum format, GLenum type, SharedPtr<tcu::ThreadUtil::DataBlock>& data, bool useSync, bool serverSync);
+	void	exec			(tcu::ThreadUtil::Thread& thread);
+
+private:
+	int										m_x;
+	int										m_y;
+	int										m_width;
+	int										m_height;
+	GLenum									m_format;
+	GLenum									m_type;
+	SharedPtr<tcu::ThreadUtil::DataBlock>	m_data;
+};
+
+ReadPixels::ReadPixels (int x, int y, int width, int height, GLenum format, GLenum type, SharedPtr<tcu::ThreadUtil::DataBlock>& data, bool useSync, bool serverSync)
+	: Operation	("ReadPixels", useSync, serverSync)
+	, m_x		(x)
+	, m_y		(y)
+	, m_width	(width)
+	, m_height	(height)
+	, m_format	(format)
+	, m_type	(type)
+{
+	data = SharedPtr<tcu::ThreadUtil::DataBlock>(new tcu::ThreadUtil::DataBlock(getEvent()));
+	m_data = data;
+}
+
+void ReadPixels::exec (tcu::ThreadUtil::Thread& thread)
+{
+	DE_ASSERT(m_type == GL_UNSIGNED_BYTE);
+	DE_ASSERT(m_format == GL_RGBA);
+
+	std::vector<deUint8> data((m_width-m_x)*(m_height-m_y)*4);
+
+	thread.newMessage() << "Begin -- glReadPixels(" << m_x << ", " << m_y << ", " << m_width << ", " << m_height << ", " << m_format << ", " << m_type << ", <data>)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glReadPixels(m_x, m_y, m_width, m_height, m_format, m_type, &(data[0])));
+	thread.newMessage() << "End -- glReadPixels()" << tcu::ThreadUtil::Message::End;
+
+	m_data->setData(data.size(), &(data[0]));
+}
+
+class CreateImageFromTexture : public Operation
+{
+public:
+	// \note [mika] Unlike eglCreateImageKHR this operation requires current context and uses it for creating EGLImage
+	//				Current context is required to support EGL sync objects in current tests system
+			CreateImageFromTexture	(SharedPtr<EGLImage>& image, SharedPtr<Texture> texture, bool useSync, bool serverSync);
+	void	exec					(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Texture>		m_texture;
+	SharedPtr<EGLImage>		m_image;
+};
+
+CreateImageFromTexture::CreateImageFromTexture (SharedPtr<EGLImage>& image, SharedPtr<Texture> texture, bool useSync, bool serverSync)
+	: Operation	("CreateImageFromTexture", useSync, serverSync)
+{
+	modifyGLObject(SharedPtr<Object>(texture));
+	image = SharedPtr<EGLImage>(new EGLImage(getEvent(), getSync()));
+
+	m_image					= image;
+	m_texture				= texture;
+	m_texture->sourceImage	= m_image;
+}
+
+void CreateImageFromTexture::exec (tcu::ThreadUtil::Thread& t)
+{
+	EGLThread& thread = *(dynamic_cast<EGLThread*>(&t));
+
+	EGLint attribList[] = {
+		EGL_GL_TEXTURE_LEVEL_KHR, 0,
+		EGL_NONE
+	};
+
+	TCU_CHECK(thread.eglExtensions.createImage);
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, m_texture->texture));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+
+	// Make texture image complete...
+	thread.newMessage() << "Begin -- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+	thread.newMessage() << "End -- glTexParameteri()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+	thread.newMessage() << "End -- glTexParameteri()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+	thread.newMessage() << "End -- glTexParameteri()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+	thread.newMessage() << "End -- glTexParameteri()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- eglCreateImageKHR(" << thread.runtimeContext->display << ", " << thread.runtimeContext->context << ", EGL_GL_TEXTURE_2D_KHR, " << m_texture->texture << ", { EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_NONE })" << tcu::ThreadUtil::Message::End;
+	m_image->image = thread.eglExtensions.createImage(thread.runtimeContext->display, thread.runtimeContext->context, EGL_GL_TEXTURE_2D_KHR, (EGLClientBuffer)(deUintptr)m_texture->texture, attribList);
+	TCU_CHECK_EGL_MSG("eglCreateImageKHR()");
+	thread.newMessage() << "End -- " << m_image->image << " = eglCreateImageKHR()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+}
+
+class DestroyImage : public Operation
+{
+public:
+	// \note [mika] Unlike eglDestroyImageKHR this operation requires current context and uses it for creating EGLImage
+	//				Current context is required to support EGL sync objects in current tests system
+			DestroyImage		(SharedPtr<EGLImage> image, bool useSync, bool serverSync);
+	void	exec				(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<EGLImage>		m_image;
+};
+
+DestroyImage::DestroyImage (SharedPtr<EGLImage> image, bool useSync, bool serverSync)
+	: Operation	("CreateImageFromTexture", useSync, serverSync)
+	, m_image	(image)
+{
+	modifyGLObject(SharedPtr<Object>(image));
+}
+
+void DestroyImage::exec (tcu::ThreadUtil::Thread& t)
+{
+	EGLThread& thread = *(dynamic_cast<EGLThread*>(&t));
+
+	TCU_CHECK(thread.eglExtensions.destroyImage);
+
+	thread.newMessage() << "Begin -- eglDestroyImageKHR(" << thread.runtimeContext->display << ", " << m_image->image << ")" << tcu::ThreadUtil::Message::End;
+	thread.eglExtensions.destroyImage(thread.runtimeContext->display, m_image->image);
+	m_image->image = EGL_NO_IMAGE_KHR;
+	TCU_CHECK_EGL_MSG("eglDestroyImageKHR()");
+	thread.newMessage() << "End -- eglDestroyImageKHR()" << tcu::ThreadUtil::Message::End;
+}
+
+class DefineTextureFromImage : public Operation
+{
+public:
+			DefineTextureFromImage	(SharedPtr<Texture> texture, SharedPtr<EGLImage> image, bool useSync, bool serverSync);
+	void	exec					(tcu::ThreadUtil::Thread& thread);
+
+private:
+	SharedPtr<Texture>	m_texture;
+	SharedPtr<EGLImage>	m_image;
+};
+
+DefineTextureFromImage::DefineTextureFromImage (SharedPtr<Texture> texture, SharedPtr<EGLImage> image, bool useSync, bool serverSync)
+	: Operation	("DefineTextureFromImage", useSync, serverSync)
+{
+	readGLObject(SharedPtr<Object>(image));
+	modifyGLObject(SharedPtr<Object>(texture));
+
+	texture->isDefined		= true;
+	texture->sourceImage	= image;
+
+	m_image		= image;
+	m_texture	= texture;
+}
+
+void DefineTextureFromImage::exec (tcu::ThreadUtil::Thread& t)
+{
+	EGLThread& thread = *(dynamic_cast<EGLThread*>(&t));
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, m_texture->texture));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, " << m_image->image << ")" << tcu::ThreadUtil::Message::End;
+	thread.runtimeContext->glExtensions.imageTargetTexture2D(GL_TEXTURE_2D, m_image->image);
+	GLU_CHECK_MSG("glEGLImageTargetTexture2DOES()");
+	thread.newMessage() << "End -- glEGLImageTargetTexture2DOES()" << tcu::ThreadUtil::Message::End;
+
+	thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+	thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
+}
+
+} // GLES2ThreadTest
+
+static void requireEGLExtension (EGLDisplay eglDisplay, const char* requiredExtension)
+{
+	// Check extensions
+	bool found = false;
+
+	std::string extensions = eglQueryString(eglDisplay, EGL_EXTENSIONS);
+
+	std::string::size_type pos = extensions.find(" ");
+	do
+	{
+		std::string extension;
+		if (pos != std::string::npos)
+		{
+			extension = extensions.substr(0, pos);
+			extensions = extensions.substr(pos+1);
+		}
+		else
+		{
+			extension = extensions;
+			extensions = "";
+		}
+
+		if (extension == requiredExtension)
+		{
+			found = true;
+			break;
+		}
+		pos = extensions.find(" ");
+	} while (pos != std::string::npos);
+
+	if (!found)
+		throw tcu::NotSupportedError((string(requiredExtension) + " not supported").c_str(), "", __FILE__, __LINE__);
+}
+
+enum OperationId
+{
+	THREADOPERATIONID_NONE = 0,
+
+	THREADOPERATIONID_CREATE_BUFFER,
+	THREADOPERATIONID_DESTROY_BUFFER,
+	THREADOPERATIONID_BUFFER_DATA,
+	THREADOPERATIONID_BUFFER_SUBDATA,
+
+	THREADOPERATIONID_CREATE_TEXTURE,
+	THREADOPERATIONID_DESTROY_TEXTURE,
+	THREADOPERATIONID_TEXIMAGE2D,
+	THREADOPERATIONID_TEXSUBIMAGE2D,
+	THREADOPERATIONID_COPYTEXIMAGE2D,
+	THREADOPERATIONID_COPYTEXSUBIMAGE2D,
+
+	THREADOPERATIONID_CREATE_VERTEX_SHADER,
+	THREADOPERATIONID_CREATE_FRAGMENT_SHADER,
+	THREADOPERATIONID_DESTROY_SHADER,
+	THREADOPERATIONID_SHADER_SOURCE,
+	THREADOPERATIONID_SHADER_COMPILE,
+
+	THREADOPERATIONID_ATTACH_SHADER,
+	THREADOPERATIONID_DETACH_SHADER,
+
+	THREADOPERATIONID_CREATE_PROGRAM,
+	THREADOPERATIONID_DESTROY_PROGRAM,
+	THREADOPERATIONID_LINK_PROGRAM,
+
+	THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE,
+	THREADOPERATIONID_DESTROY_IMAGE,
+	THREADOPERATIONID_TEXTURE_FROM_IMAGE,
+
+	THREADOPERATIONID_LAST
+};
+
+class GLES2SharingRandomTest : public TestCase
+{
+public:
+	struct TestConfig
+	{
+				TestConfig		(void);
+		int		threadCount;
+		int		operationCount;
+		bool	serverSync;
+		bool	useFenceSync;
+		bool	useImages;
+
+		float probabilities[THREADOPERATIONID_LAST][THREADOPERATIONID_LAST];
+	};
+						GLES2SharingRandomTest		(EglTestContext& context, const TestConfig& config, const char* name, const char* description);
+						~GLES2SharingRandomTest		(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+	void				addRandomOperation			(GLES2ThreadTest::EGLResourceManager& resourceManager);
+
+private:
+	TestConfig				m_config;
+	int						m_seed;
+	de::Random				m_random;
+	tcu::TestLog&			m_log;
+	bool					m_threadsStarted;
+	bool					m_threadsRunning;
+	bool					m_executionReady;
+	bool					m_requiresRestart;
+	deUint64				m_beginTimeUs;
+	deUint64				m_timeOutUs;
+	deUint32				m_sleepTimeMs;
+	deUint64				m_timeOutTimeUs;
+
+	std::vector<GLES2ThreadTest::EGLThread*>	m_threads;
+
+	EGLDisplay				m_eglDisplay;
+	EGLConfig				m_eglConfig;
+	OperationId				m_lastOperation;
+};
+
+GLES2SharingRandomTest::TestConfig::TestConfig (void)
+	: threadCount		(0)
+	, operationCount	(0)
+	, serverSync		(false)
+	, useFenceSync		(false)
+	, useImages			(false)
+{
+	deMemset(probabilities, 0, sizeof(probabilities));
+}
+
+GLES2SharingRandomTest::GLES2SharingRandomTest (EglTestContext& context, const TestConfig& config, const char* name, const char* description)
+	: TestCase			(context, name, description)
+	, m_config			(config)
+	, m_seed			(deStringHash(name))
+	, m_random			(deStringHash(name))
+	, m_log				(m_testCtx.getLog())
+	, m_threadsStarted	(false)
+	, m_threadsRunning	(false)
+	, m_executionReady	(false)
+	, m_requiresRestart	(false)
+	, m_beginTimeUs		(0)
+	, m_timeOutUs		(10000000)	// 10 seconds
+	, m_sleepTimeMs		(1)		// 1 milliseconds
+	, m_timeOutTimeUs	(0)
+	, m_eglDisplay		(EGL_NO_DISPLAY)
+	, m_eglConfig		(0)
+	, m_lastOperation	(THREADOPERATIONID_NONE)
+{
+}
+
+GLES2SharingRandomTest::~GLES2SharingRandomTest (void)
+{
+	GLES2SharingRandomTest::deinit();
+}
+
+void GLES2SharingRandomTest::init (void)
+{
+	tcu::egl::Display& display = m_eglTestCtx.getDisplay();
+
+	vector<EGLConfig>	configs;
+
+	EGLint attribList[] =
+	{
+		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
+		EGL_SURFACE_TYPE,		EGL_WINDOW_BIT,
+		EGL_ALPHA_SIZE,			1,
+		EGL_NONE
+	};
+
+	display.chooseConfig(attribList, configs);
+	m_eglDisplay	= display.getEGLDisplay();
+	m_eglConfig 	= configs[0];
+
+	// Check extensions
+	if (m_config.useFenceSync)
+		requireEGLExtension(m_eglDisplay, "EGL_KHR_fence_sync");
+
+	if (m_config.serverSync)
+		requireEGLExtension(m_eglDisplay, "EGL_KHR_wait_sync");
+
+	if (m_config.useImages)
+	{
+		requireEGLExtension(m_eglDisplay, "EGL_KHR_image_base");
+		requireEGLExtension(m_eglDisplay, "EGL_KHR_gl_texture_2D_image");
+	}
+
+	GLES2ThreadTest::EGLResourceManager resourceManager;
+	// Create contexts
+	for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
+	{
+		m_threads.push_back(new GLES2ThreadTest::EGLThread(deInt32Hash(m_seed+threadNdx)));
+		SharedPtr<GLES2ThreadTest::GLES2Context> context;
+		SharedPtr<GLES2ThreadTest::GLES2Context> shared = (threadNdx > 0 ? resourceManager.popContext(0) : SharedPtr<GLES2ThreadTest::GLES2Context>());
+		m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CreateContext(m_eglDisplay, m_eglConfig, shared, context));
+
+		resourceManager.addContext(context);
+
+		if (shared)
+			resourceManager.addContext(shared);
+	}
+
+	// Create surfaces
+	for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
+	{
+		SharedPtr<GLES2ThreadTest::Surface> surface;
+		m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CreatePBufferSurface(m_eglDisplay, m_eglConfig, 400, 400, surface));
+		resourceManager.addSurface(surface);
+	}
+
+	// Make contexts current
+	for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
+	{
+		m_threads[threadNdx]->addOperation(new GLES2ThreadTest::MakeCurrent(*m_threads[threadNdx], m_eglDisplay, resourceManager.popSurface(0), resourceManager.popContext(0)));
+	}
+
+	// Operations to check fence sync support
+	if (m_config.useFenceSync)
+	{
+		for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
+		{
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_sync"));
+		}
+	}
+
+	// Init EGLimage support
+	if (m_config.useImages)
+	{
+		for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
+		{
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_image"));
+		}
+	}
+
+	// Add random operations
+	for (int operationNdx = 0; operationNdx < m_config.operationCount; operationNdx++)
+		addRandomOperation(resourceManager);
+
+	// Release contexts
+	for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
+	{
+		SharedPtr<GLES2ThreadTest::GLES2Context>	context = m_threads[threadNdx]->context;
+		SharedPtr<GLES2ThreadTest::Surface>			surface = m_threads[threadNdx]->surface;
+
+		m_threads[threadNdx]->addOperation(new GLES2ThreadTest::MakeCurrent(*m_threads[threadNdx], m_eglDisplay, SharedPtr<GLES2ThreadTest::Surface>(), SharedPtr<GLES2ThreadTest::GLES2Context>()));
+
+		resourceManager.addSurface(surface);
+		resourceManager.addContext(context);
+	}
+
+	// Destroy contexts
+	for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DestroyContext(resourceManager.popContext(0)));
+
+	// Destroy surfaces
+	for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
+		m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DestroySurface(m_eglDisplay, resourceManager.popSurface(0)));
+}
+
+void GLES2SharingRandomTest::deinit (void)
+{
+	for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		delete m_threads[threadNdx];
+
+	m_threads.clear();
+
+	TCU_CHECK(!m_requiresRestart);
+}
+
+void GLES2SharingRandomTest::addRandomOperation (GLES2ThreadTest::EGLResourceManager& resourceManager)
+{
+	int threadNdx	= m_random.getUint32() % m_threads.size();
+
+	std::vector<OperationId>	operations;
+	std::vector<float>			weights;
+
+	operations.push_back(THREADOPERATIONID_CREATE_BUFFER);
+	weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_BUFFER]);
+
+	operations.push_back(THREADOPERATIONID_CREATE_TEXTURE);
+	weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_TEXTURE]);
+
+	operations.push_back(THREADOPERATIONID_CREATE_VERTEX_SHADER);
+	weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_VERTEX_SHADER]);
+
+	operations.push_back(THREADOPERATIONID_CREATE_FRAGMENT_SHADER);
+	weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]);
+
+	operations.push_back(THREADOPERATIONID_CREATE_PROGRAM);
+	weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_PROGRAM]);
+
+	int destroyableBufferNdx				= -1;
+	int destroyableTextureNdx				= -1;
+	int destroyableShaderNdx				= -1;
+	int destroyableProgramNdx				= -1;
+
+	int vertexShaderNdx						= -1;
+	int fragmentShaderNdx					= -1;
+
+	int definedTextureNdx					= -1;
+
+	int definedBufferNdx					= -1;
+
+	int definedShaderNdx					= -1;
+
+	int detachableProgramNdx				= -1;
+	GLenum detachShaderType					= GL_VERTEX_SHADER;
+
+	int unusedVertexAttachmentProgramNdx	= -1;
+	int unusedFragmentAttachmentProgramNdx	= -1;
+
+	int linkableProgramNdx					= -1;
+
+	int attachProgramNdx					= -1;
+	int attachShaderNdx						= -1;
+
+	int nonSiblingTextureNdx				= -1;
+
+	if (m_threads[threadNdx]->context->resourceManager->getBufferCount() > 0)
+		destroyableBufferNdx = m_random.getUint32() % m_threads[threadNdx]->context->resourceManager->getBufferCount();
+
+	if (m_threads[threadNdx]->context->resourceManager->getTextureCount() > 0)
+		destroyableTextureNdx = m_random.getUint32() % m_threads[threadNdx]->context->resourceManager->getTextureCount();
+
+	if (m_threads[threadNdx]->context->resourceManager->getShaderCount() > 0)
+		destroyableShaderNdx = m_random.getUint32() % m_threads[threadNdx]->context->resourceManager->getShaderCount();
+
+	if (m_threads[threadNdx]->context->resourceManager->getProgramCount() > 0)
+		destroyableProgramNdx = m_random.getUint32() % m_threads[threadNdx]->context->resourceManager->getProgramCount();
+
+	// Check what kind of buffers we have
+	for (int bufferNdx = 0; bufferNdx < m_threads[threadNdx]->context->resourceManager->getBufferCount(); bufferNdx++)
+	{
+		SharedPtr<GLES2ThreadTest::Buffer> buffer = m_threads[threadNdx]->context->resourceManager->getBuffer(bufferNdx);
+
+		if (buffer->isDefined)
+		{
+			if (definedBufferNdx == -1)
+				definedBufferNdx = bufferNdx;
+			else if (m_random.getBool())
+				definedBufferNdx = bufferNdx;
+		}
+	}
+
+	// Check what kind of textures we have
+	for (int textureNdx = 0; textureNdx < m_threads[threadNdx]->context->resourceManager->getTextureCount(); textureNdx++)
+	{
+		SharedPtr<GLES2ThreadTest::Texture> texture = m_threads[threadNdx]->context->resourceManager->getTexture(textureNdx);
+
+		if (texture->isDefined)
+		{
+			if (definedTextureNdx == -1)
+				definedTextureNdx = textureNdx;
+			else if (m_random.getBool())
+				definedTextureNdx = textureNdx;
+
+			if (!texture->sourceImage)
+			{
+				if (nonSiblingTextureNdx == -1)
+					nonSiblingTextureNdx = textureNdx;
+				else if (m_random.getBool())
+					nonSiblingTextureNdx = textureNdx;
+			}
+		}
+
+	}
+
+	// Check what kind of shaders we have
+	for (int shaderNdx = 0; shaderNdx < m_threads[threadNdx]->context->resourceManager->getShaderCount(); shaderNdx++)
+	{
+		SharedPtr<GLES2ThreadTest::Shader> shader = m_threads[threadNdx]->context->resourceManager->getShader(shaderNdx);
+
+		// Defined shader found
+		if (shader->isDefined)
+		{
+			if (definedShaderNdx == -1)
+				definedShaderNdx = shaderNdx;
+			else if (m_random.getBool())
+				definedShaderNdx = shaderNdx;
+		}
+
+		// Vertex shader found
+		if (shader->type == GL_VERTEX_SHADER)
+		{
+			if (vertexShaderNdx == -1)
+				vertexShaderNdx = shaderNdx;
+			else if (m_random.getBool())
+				vertexShaderNdx = shaderNdx;
+		}
+
+		// Fragmet shader found
+		if (shader->type == GL_FRAGMENT_SHADER)
+		{
+			if (fragmentShaderNdx == -1)
+				fragmentShaderNdx = shaderNdx;
+			else if (m_random.getBool())
+				fragmentShaderNdx = shaderNdx;
+		}
+	}
+
+	// Check what kind of programs we have
+	for (int programNdx = 0; programNdx < m_threads[threadNdx]->context->resourceManager->getProgramCount(); programNdx++)
+	{
+		SharedPtr<GLES2ThreadTest::Program> program = m_threads[threadNdx]->context->resourceManager->getProgram(programNdx);
+
+		// Program that can be detached
+		if (program->vertexShader || program->fragmentShader)
+		{
+			if (detachableProgramNdx == -1)
+			{
+				detachableProgramNdx = programNdx;
+
+				if (program->vertexShader)
+					detachShaderType = GL_VERTEX_SHADER;
+				else if (program->fragmentShader)
+					detachShaderType = GL_FRAGMENT_SHADER;
+				else
+					DE_ASSERT(false);
+			}
+			else if (m_random.getBool())
+			{
+				detachableProgramNdx = programNdx;
+
+				if (program->vertexShader)
+					detachShaderType = GL_VERTEX_SHADER;
+				else if (program->fragmentShader)
+					detachShaderType = GL_FRAGMENT_SHADER;
+				else
+					DE_ASSERT(false);
+			}
+		}
+
+		// Program that can be attached vertex shader
+		if (!program->vertexShader)
+		{
+			if (unusedVertexAttachmentProgramNdx == -1)
+				unusedVertexAttachmentProgramNdx = programNdx;
+			else if (m_random.getBool())
+				unusedVertexAttachmentProgramNdx = programNdx;
+		}
+
+		// Program that can be attached fragment shader
+		if (!program->fragmentShader)
+		{
+			if (unusedFragmentAttachmentProgramNdx == -1)
+				unusedFragmentAttachmentProgramNdx = programNdx;
+			else if (m_random.getBool())
+				unusedFragmentAttachmentProgramNdx = programNdx;
+		}
+
+		// Program that can be linked
+		if (program->vertexShader && program->fragmentShader)
+		{
+			if (linkableProgramNdx == -1)
+				linkableProgramNdx = programNdx;
+			else if (m_random.getBool())
+				linkableProgramNdx = programNdx;
+		}
+	}
+
+	// Has images
+	if (resourceManager.getImageCount() > 0)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DESTROY_IMAGE]);
+		operations.push_back(THREADOPERATIONID_DESTROY_IMAGE);
+
+		if (m_threads[threadNdx]->context->resourceManager->getTextureCount() > 0)
+		{
+			weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_TEXTURE_FROM_IMAGE]);
+			operations.push_back(THREADOPERATIONID_TEXTURE_FROM_IMAGE);
+		}
+	}
+
+	// Has buffer
+	if (destroyableBufferNdx != -1)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DESTROY_BUFFER]);
+		operations.push_back(THREADOPERATIONID_DESTROY_BUFFER);
+
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_BUFFER_DATA]);
+		operations.push_back(THREADOPERATIONID_BUFFER_DATA);
+	}
+
+	// Has buffer with defined data
+	if (definedBufferNdx != -1)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_BUFFER_SUBDATA]);
+		operations.push_back(THREADOPERATIONID_BUFFER_SUBDATA);
+	}
+
+	// Has texture
+	if (destroyableTextureNdx != -1)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DESTROY_TEXTURE]);
+		operations.push_back(THREADOPERATIONID_DESTROY_TEXTURE);
+
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_TEXIMAGE2D]);
+		operations.push_back(THREADOPERATIONID_TEXIMAGE2D);
+
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_COPYTEXIMAGE2D]);
+		operations.push_back(THREADOPERATIONID_COPYTEXIMAGE2D);
+	}
+
+	// Has texture with data
+	if (definedTextureNdx != -1)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_TEXSUBIMAGE2D]);
+		operations.push_back(THREADOPERATIONID_TEXSUBIMAGE2D);
+
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_COPYTEXSUBIMAGE2D]);
+		operations.push_back(THREADOPERATIONID_COPYTEXSUBIMAGE2D);
+	}
+
+	// Has texture that can be used as EGLimage source
+	if (nonSiblingTextureNdx != -1)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]);
+		operations.push_back(THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE);
+	}
+
+	// Has shader
+	if (destroyableShaderNdx != -1)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DESTROY_SHADER]);
+		operations.push_back(THREADOPERATIONID_DESTROY_SHADER);
+
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_SHADER_SOURCE]);
+		operations.push_back(THREADOPERATIONID_SHADER_SOURCE);
+	}
+
+	// Has shader with defined source
+	if (definedShaderNdx != -1)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_SHADER_COMPILE]);
+		operations.push_back(THREADOPERATIONID_SHADER_COMPILE);
+	}
+
+	// Has program
+	if (destroyableProgramNdx != -1)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DESTROY_PROGRAM]);
+		operations.push_back(THREADOPERATIONID_DESTROY_PROGRAM);
+	}
+
+	// Has program that can be linked
+	if (linkableProgramNdx != -1)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_LINK_PROGRAM]);
+		operations.push_back(THREADOPERATIONID_LINK_PROGRAM);
+	}
+
+	// has program with attachments
+	if (detachableProgramNdx != -1)
+	{
+		weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DETACH_SHADER]);
+		operations.push_back(THREADOPERATIONID_DETACH_SHADER);
+	}
+
+	// Has program and shader pair that can be attached
+	if (fragmentShaderNdx != -1 && unusedFragmentAttachmentProgramNdx != -1)
+	{
+		if (attachProgramNdx == -1)
+		{
+			DE_ASSERT(attachShaderNdx == -1);
+			attachProgramNdx = unusedFragmentAttachmentProgramNdx;
+			attachShaderNdx = fragmentShaderNdx;
+
+			weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_ATTACH_SHADER]);
+			operations.push_back(THREADOPERATIONID_ATTACH_SHADER);
+		}
+		else if (m_random.getBool())
+		{
+			attachProgramNdx = unusedFragmentAttachmentProgramNdx;
+			attachShaderNdx = fragmentShaderNdx;
+		}
+	}
+
+	if (vertexShaderNdx != -1 && unusedVertexAttachmentProgramNdx != -1)
+	{
+		if (attachProgramNdx == -1)
+		{
+			DE_ASSERT(attachShaderNdx == -1);
+			attachProgramNdx = unusedVertexAttachmentProgramNdx;
+			attachShaderNdx = vertexShaderNdx;
+
+			weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_ATTACH_SHADER]);
+			operations.push_back(THREADOPERATIONID_ATTACH_SHADER);
+		}
+		else if (m_random.getBool())
+		{
+			attachProgramNdx = unusedVertexAttachmentProgramNdx;
+			attachShaderNdx = vertexShaderNdx;
+		}
+	}
+
+	OperationId op = m_random.chooseWeighted<OperationId, std::vector<OperationId> ::iterator>(operations.begin(), operations.end(), weights.begin());
+
+	switch (op)
+	{
+		case THREADOPERATIONID_CREATE_BUFFER:
+		{
+			SharedPtr<GLES2ThreadTest::Buffer> buffer;
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CreateBuffer(buffer, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addBuffer(buffer);
+			break;
+		}
+
+		case THREADOPERATIONID_DESTROY_BUFFER:
+		{
+			SharedPtr<GLES2ThreadTest::Buffer> buffer = m_threads[threadNdx]->context->resourceManager->popBuffer(destroyableBufferNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DeleteBuffer(buffer, m_config.useFenceSync, m_config.serverSync));
+			break;
+		}
+
+		case THREADOPERATIONID_BUFFER_DATA:
+		{
+			SharedPtr<GLES2ThreadTest::Buffer> buffer = m_threads[threadNdx]->context->resourceManager->popBuffer(destroyableBufferNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::BufferData(buffer, GL_ARRAY_BUFFER, 1024, GL_DYNAMIC_DRAW, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addBuffer(buffer);
+			break;
+		}
+
+		case THREADOPERATIONID_BUFFER_SUBDATA:
+		{
+			SharedPtr<GLES2ThreadTest::Buffer> buffer = m_threads[threadNdx]->context->resourceManager->popBuffer(definedBufferNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::BufferSubData(buffer, GL_ARRAY_BUFFER, 1, 20, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addBuffer(buffer);
+			break;
+		}
+
+		case THREADOPERATIONID_CREATE_TEXTURE:
+		{
+			SharedPtr<GLES2ThreadTest::Texture> texture;
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CreateTexture(texture, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addTexture(texture);
+			break;
+		}
+
+		case THREADOPERATIONID_DESTROY_TEXTURE:
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DeleteTexture(m_threads[threadNdx]->context->resourceManager->popTexture(destroyableTextureNdx), m_config.useFenceSync, m_config.serverSync));
+			break;
+
+		case THREADOPERATIONID_TEXIMAGE2D:
+		{
+			SharedPtr<GLES2ThreadTest::Texture> texture = m_threads[threadNdx]->context->resourceManager->popTexture(destroyableTextureNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::TexImage2D(texture, 0, GL_RGBA, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addTexture(texture);
+			break;
+		}
+
+		case THREADOPERATIONID_TEXSUBIMAGE2D:
+		{
+			SharedPtr<GLES2ThreadTest::Texture> texture = m_threads[threadNdx]->context->resourceManager->popTexture(definedTextureNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::TexSubImage2D(texture, 0, 30, 30, 50, 50, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addTexture(texture);
+			break;
+		}
+
+		case THREADOPERATIONID_COPYTEXIMAGE2D:
+		{
+			SharedPtr<GLES2ThreadTest::Texture> texture = m_threads[threadNdx]->context->resourceManager->popTexture(destroyableTextureNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CopyTexImage2D(texture, 0, GL_RGBA, 20, 20, 300, 300, 0, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addTexture(texture);
+			break;
+		}
+
+		case THREADOPERATIONID_COPYTEXSUBIMAGE2D:
+		{
+			SharedPtr<GLES2ThreadTest::Texture> texture = m_threads[threadNdx]->context->resourceManager->popTexture(definedTextureNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CopyTexSubImage2D(texture, 0, 10, 10, 30, 30, 50, 50, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addTexture(texture);
+			break;
+		}
+
+		case THREADOPERATIONID_CREATE_VERTEX_SHADER:
+		{
+			SharedPtr<GLES2ThreadTest::Shader> shader;
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CreateShader(GL_VERTEX_SHADER, shader, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addShader(shader);
+			break;
+		}
+
+		case THREADOPERATIONID_CREATE_FRAGMENT_SHADER:
+		{
+			SharedPtr<GLES2ThreadTest::Shader> shader;
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CreateShader(GL_FRAGMENT_SHADER, shader, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addShader(shader);
+			break;
+		}
+
+		case THREADOPERATIONID_DESTROY_SHADER:
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DeleteShader(m_threads[threadNdx]->context->resourceManager->popShader(destroyableShaderNdx), m_config.useFenceSync, m_config.serverSync));
+			break;
+
+		case THREADOPERATIONID_SHADER_SOURCE:
+		{
+			const char* vertexShaderSource =
+				"attribute mediump vec4 a_pos;\n"
+				"varying mediump vec4 v_pos;\n"
+				"void main (void)\n"
+				"{\n"
+				"\tv_pos = a_pos;\n"
+				"\tgl_Position = a_pos;\n"
+				"}\n";
+
+			const char* fragmentShaderSource =
+				"varying mediump vec4 v_pos;\n"
+				"void main (void)\n"
+				"{\n"
+				"\tgl_FragColor = v_pos;\n"
+				"}\n";
+			SharedPtr<GLES2ThreadTest::Shader> shader = m_threads[threadNdx]->context->resourceManager->popShader(destroyableShaderNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::ShaderSource(shader, (shader->type == GL_VERTEX_SHADER ? vertexShaderSource : fragmentShaderSource), m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addShader(shader);
+			break;
+		}
+
+		case THREADOPERATIONID_SHADER_COMPILE:
+		{
+			SharedPtr<GLES2ThreadTest::Shader> shader = m_threads[threadNdx]->context->resourceManager->popShader(definedShaderNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::ShaderCompile(shader, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addShader(shader);
+			break;
+		}
+
+		case THREADOPERATIONID_CREATE_PROGRAM:
+		{
+			SharedPtr<GLES2ThreadTest::Program> program;
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CreateProgram(program, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addProgram(program);
+			break;
+		}
+
+		case THREADOPERATIONID_DESTROY_PROGRAM:
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DeleteProgram(m_threads[threadNdx]->context->resourceManager->popProgram(destroyableProgramNdx), m_config.useFenceSync, m_config.serverSync));
+			break;
+
+		case THREADOPERATIONID_ATTACH_SHADER:
+		{
+			SharedPtr<GLES2ThreadTest::Program>	program = m_threads[threadNdx]->context->resourceManager->popProgram(attachProgramNdx);
+			SharedPtr<GLES2ThreadTest::Shader>	shader	= m_threads[threadNdx]->context->resourceManager->popShader(attachShaderNdx);
+
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::AttachShader(program, shader, m_config.useFenceSync, m_config.serverSync));
+
+			m_threads[threadNdx]->context->resourceManager->addProgram(program);
+			m_threads[threadNdx]->context->resourceManager->addShader(shader);
+			break;
+		}
+
+		case THREADOPERATIONID_DETACH_SHADER:
+		{
+			SharedPtr<GLES2ThreadTest::Program>	program = m_threads[threadNdx]->context->resourceManager->popProgram(detachableProgramNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DetachShader(program, detachShaderType, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addProgram(program);
+			break;
+		}
+
+		case THREADOPERATIONID_LINK_PROGRAM:
+		{
+			SharedPtr<GLES2ThreadTest::Program>	program = m_threads[threadNdx]->context->resourceManager->popProgram(linkableProgramNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addProgram(program);
+			break;
+		}
+
+		case THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE:
+		{
+			SharedPtr<GLES2ThreadTest::EGLImage> image;
+			SharedPtr<GLES2ThreadTest::Texture> texture = m_threads[threadNdx]->context->resourceManager->popTexture(nonSiblingTextureNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CreateImageFromTexture(image, texture, m_config.useFenceSync, m_config.serverSync));
+			// \note [mika] Can source be added back to resourceManager?
+			m_threads[threadNdx]->context->resourceManager->addTexture(texture);
+			resourceManager.addImage(image);
+			break;
+		}
+
+		case THREADOPERATIONID_DESTROY_IMAGE:
+		{
+			int imageNdx = m_random.getInt(0, resourceManager.getImageCount()-1);
+			SharedPtr<GLES2ThreadTest::EGLImage> image = resourceManager.popImage(imageNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DestroyImage(image, m_config.useFenceSync, m_config.serverSync));
+			break;
+		}
+
+		case THREADOPERATIONID_TEXTURE_FROM_IMAGE:
+		{
+			int imageNdx = m_random.getInt(0, resourceManager.getImageCount()-1);
+			SharedPtr<GLES2ThreadTest::Texture> texture = m_threads[threadNdx]->context->resourceManager->popTexture(destroyableTextureNdx);
+			SharedPtr<GLES2ThreadTest::EGLImage> image = resourceManager.popImage(imageNdx);
+			m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DefineTextureFromImage(texture, image, m_config.useFenceSync, m_config.serverSync));
+			m_threads[threadNdx]->context->resourceManager->addTexture(texture);
+			resourceManager.addImage(image);
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	m_lastOperation = op;
+}
+
+tcu::TestCase::IterateResult GLES2SharingRandomTest::iterate (void)
+{
+	if (!m_threadsStarted)
+	{
+		m_beginTimeUs = deGetMicroseconds();
+
+		// Execute threads
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			m_threads[threadNdx]->exec();
+
+		m_threadsStarted = true;
+		m_threadsRunning = true;
+	}
+
+	if (m_threadsRunning)
+	{
+		// Wait threads to finish
+		int readyThreads = 0;
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		{
+			if (m_threads[threadNdx]->getStatus() != tcu::ThreadUtil::Thread::THREADSTATUS_RUNNING)
+				readyThreads++;
+		}
+
+		if (readyThreads == (int)m_threads.size())
+		{
+			for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+				m_threads[threadNdx]->join();
+
+			m_executionReady	= true;
+			m_requiresRestart	= false;
+		}
+
+		if (deGetMicroseconds() - m_beginTimeUs > m_timeOutUs)
+		{
+			for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			{
+				if (m_threads[threadNdx]->getStatus() != tcu::ThreadUtil::Thread::THREADSTATUS_RUNNING)
+				{
+					if (m_threads[threadNdx]->isStarted())
+						m_threads[threadNdx]->join();
+				}
+			}
+			m_executionReady	= true;
+			m_requiresRestart	= true;
+			m_timeOutTimeUs		= deGetMicroseconds();
+		}
+		else
+		{
+			deSleep(m_sleepTimeMs);
+		}
+	}
+
+	if (m_executionReady)
+	{
+		std::vector<int> indices(m_threads.size(), 0);
+		while (true)
+		{
+			int 		firstThread = -1;
+
+			// Find first thread with messages
+			for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			{
+				if (m_threads[threadNdx]->getMessageCount() > indices[threadNdx])
+				{
+					firstThread = threadNdx;
+					break;
+				}
+			}
+
+			// No more messages
+			if (firstThread == -1)
+				break;
+
+			for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			{
+				// No more messages in this thread
+				if (m_threads[threadNdx]->getMessageCount() <= indices[threadNdx])
+					continue;
+
+				if ((m_threads[threadNdx]->getMessage(indices[threadNdx]).getTime() - m_beginTimeUs) < (m_threads[firstThread]->getMessage(indices[firstThread]).getTime() - m_beginTimeUs))
+					firstThread = threadNdx;
+			}
+
+			deUint64	time	= m_threads[firstThread]->getMessage(indices[firstThread]).getTime();
+			std::string	message	= m_threads[firstThread]->getMessage(indices[firstThread]).getMessage();
+			m_log << tcu::TestLog::Message << "[" << (time - m_beginTimeUs) << "] (" << firstThread << ") " << message << tcu::TestLog::EndMessage;
+			indices[firstThread]++;
+		}
+
+		bool isOk = true;
+		bool notSupported = false;
+
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		{
+			if (m_threads[threadNdx]->getStatus() == tcu::ThreadUtil::Thread::THREADSTATUS_FAILED)
+				isOk = false;
+			else if (m_threads[threadNdx]->getStatus() == tcu::ThreadUtil::Thread::THREADSTATUS_READY)
+				isOk &= true;
+			else if (m_threads[threadNdx]->getStatus() == tcu::ThreadUtil::Thread::THREADSTATUS_NOT_SUPPORTED)
+				notSupported = true;
+			else
+			{
+				isOk = false;
+				DE_ASSERT(false);
+			}
+
+		}
+
+		if (m_timeOutTimeUs != 0)
+		{
+			m_log << tcu::TestLog::Message << "[" << (m_timeOutTimeUs - m_beginTimeUs) << "] Execution timeout limit reached" << tcu::TestLog::EndMessage;
+		}
+
+		if (notSupported)
+			throw tcu::NotSupportedError("Thread threw tcu::NotSupportedError", "", __FILE__, __LINE__);
+
+		if (isOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+		return STOP;
+	}
+
+	return CONTINUE;
+}
+
+class GLES2ThreadedSharingTest : public TestCase
+{
+public:
+	struct TestConfig
+	{
+		enum ResourceType
+		{
+			RESOURCETYPE_BUFFER = 0,
+			RESOURCETYPE_TEXTURE,
+			RESOURCETYPE_VERTEX_SHADER,
+			RESOURCETYPE_FRAGMENT_SHADER,
+			RESOURCETYPE_PROGRAM,
+			RESOURCETYPE_IMAGE
+		};
+
+		ResourceType	resourceType;
+		bool			singleContext;
+		int				define;
+		int				modify;
+		bool			useFenceSync;
+		bool			serverSync;
+		bool			render;
+	};
+						GLES2ThreadedSharingTest	(EglTestContext& context, const TestConfig& config, const char* name, const char* description);
+						~GLES2ThreadedSharingTest	(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+	void				addBufferOperations			(void);
+	void				addTextureOperations		(void);
+	void				addImageOperations			(void);
+	void				addShaderOperations			(GLenum type);
+	void				addProgramOperations		(void);
+
+private:
+	TestConfig				m_config;
+	tcu::TestLog&			m_log;
+	int						m_seed;
+	bool					m_threadsStarted;
+	bool					m_threadsRunning;
+	bool					m_executionReady;
+	bool					m_requiresRestart;
+	deUint64				m_beginTimeUs;
+	deUint64				m_timeOutUs;
+	deUint32				m_sleepTimeMs;
+	deUint64				m_timeOutTimeUs;
+
+	std::vector<GLES2ThreadTest::EGLThread*>	m_threads;
+
+	EGLDisplay				m_eglDisplay;
+	EGLConfig				m_eglConfig;
+};
+
+GLES2ThreadedSharingTest::GLES2ThreadedSharingTest (EglTestContext& context, const TestConfig& config, const char* name, const char* description)
+	: TestCase			(context, name, description)
+	, m_config			(config)
+	, m_log				(m_testCtx.getLog())
+	, m_seed			(deStringHash(name))
+	, m_threadsStarted	(false)
+	, m_threadsRunning	(false)
+	, m_executionReady	(false)
+	, m_requiresRestart	(false)
+	, m_beginTimeUs		(0)
+	, m_timeOutUs		(10000000)	// 10 seconds
+	, m_sleepTimeMs		(1)			// 1 milliseconds
+	, m_timeOutTimeUs	(0)
+	, m_eglDisplay		(EGL_NO_DISPLAY)
+	, m_eglConfig		(0)
+{
+}
+
+GLES2ThreadedSharingTest::~GLES2ThreadedSharingTest (void)
+{
+	GLES2ThreadedSharingTest::deinit();
+}
+
+void GLES2ThreadedSharingTest::init (void)
+{
+	tcu::egl::Display& display = m_eglTestCtx.getDisplay();
+
+	vector<EGLConfig>	configs;
+
+	EGLint attribList[] =
+	{
+		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
+		EGL_SURFACE_TYPE,		EGL_WINDOW_BIT,
+		EGL_ALPHA_SIZE,			1,
+		EGL_NONE
+	};
+
+	display.chooseConfig(attribList, configs);
+	m_eglDisplay	= display.getEGLDisplay();
+	m_eglConfig 	= configs[0];
+
+	// Check extensions
+	if (m_config.useFenceSync)
+		requireEGLExtension(m_eglDisplay, "EGL_KHR_fence_sync");
+
+	if (m_config.serverSync)
+		requireEGLExtension(m_eglDisplay, "EGL_KHR_wait_sync");
+
+	if (m_config.resourceType == TestConfig::RESOURCETYPE_IMAGE)
+	{
+		requireEGLExtension(m_eglDisplay, "EGL_KHR_image_base");
+		requireEGLExtension(m_eglDisplay, "EGL_KHR_gl_texture_2D_image");
+	}
+
+	// Create threads
+	m_threads.push_back(new GLES2ThreadTest::EGLThread(deInt32Hash(m_seed)));
+	m_threads.push_back(new GLES2ThreadTest::EGLThread(deInt32Hash(m_seed*200)));
+
+	SharedPtr<GLES2ThreadTest::GLES2Context> contex1;
+	SharedPtr<GLES2ThreadTest::GLES2Context> contex2;
+
+	SharedPtr<GLES2ThreadTest::Surface> surface1;
+	SharedPtr<GLES2ThreadTest::Surface> surface2;
+
+	// Create contexts
+	m_threads[0]->addOperation(new GLES2ThreadTest::CreateContext(m_eglDisplay, m_eglConfig, SharedPtr<GLES2ThreadTest::GLES2Context>(), contex1));
+	m_threads[1]->addOperation(new GLES2ThreadTest::CreateContext(m_eglDisplay, m_eglConfig, contex1, contex2));
+
+	// Create surfaces
+	m_threads[0]->addOperation(new GLES2ThreadTest::CreatePBufferSurface(m_eglDisplay, m_eglConfig, 400, 400, surface1));
+	m_threads[1]->addOperation(new GLES2ThreadTest::CreatePBufferSurface(m_eglDisplay, m_eglConfig, 400, 400, surface2));
+
+	// Make current contexts
+	m_threads[0]->addOperation(new GLES2ThreadTest::MakeCurrent(*m_threads[0], m_eglDisplay, surface1, contex1));
+	m_threads[1]->addOperation(new GLES2ThreadTest::MakeCurrent(*m_threads[1], m_eglDisplay, surface2, contex2));
+	// Operations to check fence sync support
+	if (m_config.useFenceSync)
+	{
+		m_threads[0]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_sync"));
+		m_threads[1]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_sync"));
+	}
+
+
+	switch (m_config.resourceType)
+	{
+		case TestConfig::RESOURCETYPE_BUFFER:
+			addBufferOperations();
+			break;
+
+		case TestConfig::RESOURCETYPE_TEXTURE:
+			addTextureOperations();
+			break;
+
+		case TestConfig::RESOURCETYPE_IMAGE:
+			addImageOperations();
+			break;
+
+		case TestConfig::RESOURCETYPE_VERTEX_SHADER:
+			addShaderOperations(GL_VERTEX_SHADER);
+			break;
+
+		case TestConfig::RESOURCETYPE_FRAGMENT_SHADER:
+			addShaderOperations(GL_FRAGMENT_SHADER);
+			break;
+
+		case TestConfig::RESOURCETYPE_PROGRAM:
+			addProgramOperations();
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	// Relaese contexts
+	m_threads[0]->addOperation(new GLES2ThreadTest::MakeCurrent(*m_threads[0], m_eglDisplay, SharedPtr<GLES2ThreadTest::Surface>(), SharedPtr<GLES2ThreadTest::GLES2Context>()));
+	m_threads[1]->addOperation(new GLES2ThreadTest::MakeCurrent(*m_threads[0], m_eglDisplay, SharedPtr<GLES2ThreadTest::Surface>(), SharedPtr<GLES2ThreadTest::GLES2Context>()));
+
+	// Destory context
+	m_threads[0]->addOperation(new GLES2ThreadTest::DestroyContext(contex1));
+	m_threads[1]->addOperation(new GLES2ThreadTest::DestroyContext(contex2));
+
+	// Destroy surfaces
+	m_threads[0]->addOperation(new GLES2ThreadTest::DestroySurface(m_eglDisplay, surface1));
+	m_threads[1]->addOperation(new GLES2ThreadTest::DestroySurface(m_eglDisplay, surface2));
+}
+
+void GLES2ThreadedSharingTest::addBufferOperations (void)
+{
+	// Add operations for verify
+	SharedPtr<GLES2ThreadTest::Shader>	vertexShader;
+	SharedPtr<GLES2ThreadTest::Shader>	fragmentShader;
+	SharedPtr<GLES2ThreadTest::Program>	program;
+
+	if (m_config.render)
+	{
+		const char* vertexShaderSource =
+		"attribute highp vec2 a_pos;\n"
+		"varying mediump vec2 v_pos;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tv_pos = a_pos;\n"
+		"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+		"}\n";
+
+		const char* fragmentShaderSource =
+		"varying mediump vec2 v_pos;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tgl_FragColor = vec4(v_pos, 0.5, 1.0);\n"
+		"}\n";
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_VERTEX_SHADER, vertexShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(vertexShader, vertexShaderSource, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderCompile(vertexShader, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_FRAGMENT_SHADER, fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(fragmentShader, fragmentShaderSource, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderCompile(fragmentShader, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateProgram(program, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::AttachShader(program, fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::AttachShader(program, vertexShader, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	SharedPtr<GLES2ThreadTest::Buffer> buffer;
+
+	m_threads[0]->addOperation(new GLES2ThreadTest::CreateBuffer(buffer, m_config.useFenceSync, m_config.serverSync));
+
+	if (m_config.define)
+	{
+		if (m_config.modify || m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::BufferData(buffer, GL_ARRAY_BUFFER, 1024, GL_DYNAMIC_DRAW, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::BufferData(buffer, GL_ARRAY_BUFFER, 1024, GL_DYNAMIC_DRAW, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.modify)
+	{
+		if (m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::BufferSubData(buffer, GL_ARRAY_BUFFER, 17, 17, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::BufferSubData(buffer, GL_ARRAY_BUFFER, 17, 17, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.render)
+	{
+		m_threads[0]->addOperation(new GLES2ThreadTest::RenderBuffer(program, buffer, m_config.useFenceSync, m_config.serverSync));
+		m_threads[1]->addOperation(new GLES2ThreadTest::RenderBuffer(program, buffer, m_config.useFenceSync, m_config.serverSync));
+
+		SharedPtr<tcu::ThreadUtil::DataBlock> pixels1;
+		SharedPtr<tcu::ThreadUtil::DataBlock> pixels2;
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels1, m_config.useFenceSync, m_config.serverSync));
+		m_threads[1]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels2, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new tcu::ThreadUtil::CompareData(pixels1, pixels2));
+	}
+
+	if (m_config.modify || m_config.render)
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteBuffer(buffer, m_config.useFenceSync, m_config.serverSync));
+	else
+		m_threads[1]->addOperation(new GLES2ThreadTest::DeleteBuffer(buffer, m_config.useFenceSync, m_config.serverSync));
+
+	if (m_config.render)
+	{
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteShader(vertexShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteShader(fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteProgram(program, m_config.useFenceSync, m_config.serverSync));
+	}
+}
+
+void GLES2ThreadedSharingTest::addTextureOperations (void)
+{
+	// Add operations for verify
+	SharedPtr<GLES2ThreadTest::Shader>	vertexShader;
+	SharedPtr<GLES2ThreadTest::Shader>	fragmentShader;
+	SharedPtr<GLES2ThreadTest::Program>	program;
+
+	if (m_config.render)
+	{
+		const char* vertexShaderSource =
+		"attribute highp vec2 a_pos;\n"
+		"varying mediump vec2 v_pos;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tv_pos = a_pos;\n"
+		"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+		"}\n";
+
+		const char* fragmentShaderSource =
+		"varying mediump vec2 v_pos;\n"
+		"uniform sampler2D u_sampler;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tgl_FragColor = texture2D(u_sampler, v_pos);\n"
+		"}\n";
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_VERTEX_SHADER, vertexShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(vertexShader, vertexShaderSource, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderCompile(vertexShader, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_FRAGMENT_SHADER, fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(fragmentShader, fragmentShaderSource, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderCompile(fragmentShader, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateProgram(program, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::AttachShader(program, fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::AttachShader(program, vertexShader, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	SharedPtr<GLES2ThreadTest::Texture> texture;
+
+	m_threads[0]->addOperation(new GLES2ThreadTest::CreateTexture(texture, m_config.useFenceSync, m_config.serverSync));
+
+	if (m_config.define == 1)
+	{
+		if (m_config.modify || m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::TexImage2D(texture, 0, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::TexImage2D(texture, 0, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.define == 2)
+	{
+		if (m_config.modify || m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::CopyTexImage2D(texture, 0, GL_RGBA, 17, 17, 256, 256, 0, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::CopyTexImage2D(texture, 0, GL_RGBA, 17, 17, 256, 256, 0, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.modify == 1)
+	{
+		if (m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::TexSubImage2D(texture, 0, 17, 17, 29, 29, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::TexSubImage2D(texture, 0, 17, 17, 29, 29, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.modify == 2)
+	{
+		if (m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::CopyTexSubImage2D(texture, 0, 7, 7, 17, 17, 29, 29, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::CopyTexSubImage2D(texture, 0, 7, 7, 17, 17, 29, 29, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.render)
+	{
+		SharedPtr<tcu::ThreadUtil::DataBlock> pixels1;
+		SharedPtr<tcu::ThreadUtil::DataBlock> pixels2;
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::RenderTexture(program, texture, m_config.useFenceSync, m_config.serverSync));
+		m_threads[1]->addOperation(new GLES2ThreadTest::RenderTexture(program, texture, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels1, m_config.useFenceSync, m_config.serverSync));
+		m_threads[1]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels2, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new tcu::ThreadUtil::CompareData(pixels1, pixels2));
+	}
+
+	if (m_config.modify || m_config.render)
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteTexture(texture, m_config.useFenceSync, m_config.serverSync));
+	else
+		m_threads[1]->addOperation(new GLES2ThreadTest::DeleteTexture(texture, m_config.useFenceSync, m_config.serverSync));
+
+	if (m_config.render)
+	{
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteShader(vertexShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteShader(fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteProgram(program, m_config.useFenceSync, m_config.serverSync));
+	}
+}
+
+void GLES2ThreadedSharingTest::addImageOperations (void)
+{
+	// Add operations for verify
+	SharedPtr<GLES2ThreadTest::Shader>	vertexShader;
+	SharedPtr<GLES2ThreadTest::Shader>	fragmentShader;
+	SharedPtr<GLES2ThreadTest::Program>	program;
+
+	m_threads[0]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_image"));
+	m_threads[1]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_image"));
+
+	if (m_config.render)
+	{
+		const char* vertexShaderSource =
+		"attribute highp vec2 a_pos;\n"
+		"varying mediump vec2 v_pos;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tv_pos = a_pos;\n"
+		"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+		"}\n";
+
+		const char* fragmentShaderSource =
+		"varying mediump vec2 v_pos;\n"
+		"uniform sampler2D u_sampler;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tgl_FragColor = texture2D(u_sampler, v_pos);\n"
+		"}\n";
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_VERTEX_SHADER, vertexShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(vertexShader, vertexShaderSource, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderCompile(vertexShader, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_FRAGMENT_SHADER, fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(fragmentShader, fragmentShaderSource, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderCompile(fragmentShader, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateProgram(program, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::AttachShader(program, fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::AttachShader(program, vertexShader, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	SharedPtr<GLES2ThreadTest::Texture>		sourceTexture;
+	SharedPtr<GLES2ThreadTest::Texture>		texture;
+	SharedPtr<GLES2ThreadTest::EGLImage>	image;
+
+	m_threads[0]->addOperation(new GLES2ThreadTest::CreateTexture(sourceTexture, m_config.useFenceSync, m_config.serverSync));
+	m_threads[0]->addOperation(new GLES2ThreadTest::TexImage2D(sourceTexture, 0, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+
+	if (m_config.define == 1)
+	{
+		if (m_config.modify || m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::CreateImageFromTexture(image, sourceTexture, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::CreateImageFromTexture(image, sourceTexture, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.define == 2)
+	{
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateImageFromTexture(image, sourceTexture, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateTexture(texture, m_config.useFenceSync, m_config.serverSync));
+
+		if (m_config.modify || m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::DefineTextureFromImage(texture, image, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::DefineTextureFromImage(texture, image, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	m_threads[0]->addOperation(new GLES2ThreadTest::DeleteTexture(sourceTexture, m_config.useFenceSync, m_config.serverSync));
+
+	if (m_config.modify == 1)
+	{
+		DE_ASSERT(m_config.define != 1);
+
+		if (m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::TexSubImage2D(texture, 0, 17, 17, 29, 29, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::TexSubImage2D(texture, 0, 17, 17, 29, 29, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.modify == 2)
+	{
+		DE_ASSERT(m_config.define != 1);
+
+		if (m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::CopyTexSubImage2D(texture, 0, 7, 7, 17, 17, 29, 29, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::CopyTexSubImage2D(texture, 0, 7, 7, 17, 17, 29, 29, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.modify == 3)
+	{
+		DE_ASSERT(m_config.define != 1);
+
+		if (m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::TexImage2D(texture, 0, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::TexImage2D(texture, 0, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.modify == 4)
+	{
+		DE_ASSERT(m_config.define != 1);
+
+		if (m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::CopyTexImage2D(texture, 0, GL_RGBA, 7, 7, 256, 256, 0, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::CopyTexImage2D(texture, 0, GL_RGBA, 7, 7, 256, 256, 0, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.render)
+	{
+		DE_ASSERT(m_config.define != 1);
+
+		SharedPtr<tcu::ThreadUtil::DataBlock> pixels1;
+		SharedPtr<tcu::ThreadUtil::DataBlock> pixels2;
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::RenderTexture(program, texture, m_config.useFenceSync, m_config.serverSync));
+		m_threads[1]->addOperation(new GLES2ThreadTest::RenderTexture(program, texture, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels1, m_config.useFenceSync, m_config.serverSync));
+		m_threads[1]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels2, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new tcu::ThreadUtil::CompareData(pixels1, pixels2));
+	}
+
+	if (texture)
+	{
+		if (m_config.modify || m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::DeleteTexture(texture, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::DeleteTexture(texture, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.modify || m_config.render)
+		m_threads[0]->addOperation(new GLES2ThreadTest::DestroyImage(image, m_config.useFenceSync, m_config.serverSync));
+	else
+		m_threads[1]->addOperation(new GLES2ThreadTest::DestroyImage(image, m_config.useFenceSync, m_config.serverSync));
+
+	if (m_config.render)
+	{
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteShader(vertexShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteShader(fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteProgram(program, m_config.useFenceSync, m_config.serverSync));
+	}
+}
+
+void GLES2ThreadedSharingTest::addShaderOperations (GLenum type)
+{
+	SharedPtr<GLES2ThreadTest::Shader> shader;
+
+	m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(type, shader, m_config.useFenceSync, m_config.serverSync));
+
+	if (m_config.define)
+	{
+		const char* vertexShaderSource =
+		"attribute mediump vec4 a_pos;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tgl_Position = a_pos;\n"
+		"}";
+
+		const char* fragmentShaderSource =
+		"void main(void)\n"
+		"{\n"
+		"\tgl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+		"}";
+
+		if (m_config.modify || m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(shader, (type == GL_VERTEX_SHADER ? vertexShaderSource : fragmentShaderSource), m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::ShaderSource(shader, (type == GL_VERTEX_SHADER ? vertexShaderSource : fragmentShaderSource), m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.modify)
+	{
+		if (m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::ShaderCompile(shader, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::ShaderCompile(shader, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	DE_ASSERT(!m_config.render);
+
+	if (m_config.modify || m_config.render)
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteShader(shader, m_config.useFenceSync, m_config.serverSync));
+	else
+		m_threads[1]->addOperation(new GLES2ThreadTest::DeleteShader(shader, m_config.useFenceSync, m_config.serverSync));
+}
+
+void GLES2ThreadedSharingTest::addProgramOperations (void)
+{
+	// Add operations for verify
+	SharedPtr<GLES2ThreadTest::Shader>	vertexShader;
+	SharedPtr<GLES2ThreadTest::Shader>	fragmentShader;
+
+	if (m_config.define)
+	{
+		const char* vertexShaderSource =
+		"attribute highp vec2 a_pos;\n"
+		"varying mediump vec2 v_pos;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tv_pos = a_pos;\n"
+		"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+		"}\n";
+
+		const char* fragmentShaderSource =
+		"varying mediump vec2 v_pos;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tgl_FragColor = vec4(v_pos, 0.5, 1.0);\n"
+		"}\n";
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_VERTEX_SHADER, vertexShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(vertexShader, vertexShaderSource, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderCompile(vertexShader, m_config.useFenceSync, m_config.serverSync));
+
+		m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_FRAGMENT_SHADER, fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(fragmentShader, fragmentShaderSource, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::ShaderCompile(fragmentShader, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	SharedPtr<GLES2ThreadTest::Program> program;
+
+	m_threads[0]->addOperation(new GLES2ThreadTest::CreateProgram(program, m_config.useFenceSync, m_config.serverSync));
+
+	if (m_config.define)
+	{
+		// Attach shaders
+		if (m_config.modify || m_config.render)
+		{
+			m_threads[0]->addOperation(new GLES2ThreadTest::AttachShader(program, vertexShader, m_config.useFenceSync, m_config.serverSync));
+			m_threads[0]->addOperation(new GLES2ThreadTest::AttachShader(program, fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		}
+		else
+		{
+			m_threads[1]->addOperation(new GLES2ThreadTest::AttachShader(program, vertexShader, m_config.useFenceSync, m_config.serverSync));
+			m_threads[1]->addOperation(new GLES2ThreadTest::AttachShader(program, fragmentShader, m_config.useFenceSync, m_config.serverSync));
+		}
+	}
+
+	if (m_config.modify == 1)
+	{
+		// Link program
+		if (m_config.render)
+			m_threads[0]->addOperation(new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
+		else
+			m_threads[1]->addOperation(new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
+	}
+
+	if (m_config.modify == 2)
+	{
+		// Link program
+		if (m_config.render)
+		{
+			m_threads[0]->addOperation(new GLES2ThreadTest::DetachShader(program, GL_VERTEX_SHADER, m_config.useFenceSync, m_config.serverSync));
+			m_threads[0]->addOperation(new GLES2ThreadTest::DetachShader(program, GL_FRAGMENT_SHADER, m_config.useFenceSync, m_config.serverSync));
+		}
+		else
+		{
+			m_threads[1]->addOperation(new GLES2ThreadTest::DetachShader(program, GL_VERTEX_SHADER, m_config.useFenceSync, m_config.serverSync));
+			m_threads[1]->addOperation(new GLES2ThreadTest::DetachShader(program, GL_FRAGMENT_SHADER, m_config.useFenceSync, m_config.serverSync));
+		}
+	}
+
+	if (m_config.render)
+	{
+		DE_ASSERT(false);
+	}
+
+	if (m_config.modify || m_config.render)
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteProgram(program, m_config.useFenceSync, m_config.serverSync));
+	else
+		m_threads[1]->addOperation(new GLES2ThreadTest::DeleteProgram(program, m_config.useFenceSync, m_config.serverSync));
+
+	if (m_config.render)
+	{
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteShader(vertexShader, m_config.useFenceSync, m_config.serverSync));
+		m_threads[0]->addOperation(new GLES2ThreadTest::DeleteShader(fragmentShader, m_config.useFenceSync, m_config.serverSync));
+	}
+}
+
+void GLES2ThreadedSharingTest::deinit (void)
+{
+	for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		delete m_threads[threadNdx];
+
+	m_threads.clear();
+
+	TCU_CHECK(!m_requiresRestart);
+}
+
+tcu::TestCase::IterateResult GLES2ThreadedSharingTest::iterate (void)
+{
+	if (!m_threadsStarted)
+	{
+		m_beginTimeUs = deGetMicroseconds();
+
+		// Execute threads
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			m_threads[threadNdx]->exec();
+
+		m_threadsStarted = true;
+		m_threadsRunning = true;
+	}
+
+	if (m_threadsRunning)
+	{
+		// Wait threads to finish
+		int readyThreads = 0;
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		{
+			if (m_threads[threadNdx]->getStatus() != tcu::ThreadUtil::Thread::THREADSTATUS_RUNNING)
+				readyThreads++;
+		}
+
+		if (readyThreads == (int)m_threads.size())
+		{
+			for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+				m_threads[threadNdx]->join();
+
+			m_executionReady	= true;
+			m_requiresRestart	= false;
+		}
+
+		if (deGetMicroseconds() - m_beginTimeUs > m_timeOutUs)
+		{
+			for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			{
+				if (m_threads[threadNdx]->getStatus() != tcu::ThreadUtil::Thread::THREADSTATUS_RUNNING)
+					m_threads[threadNdx]->join();
+			}
+			m_executionReady	= true;
+			m_requiresRestart	= true;
+			m_timeOutTimeUs		= deGetMicroseconds();
+		}
+		else
+		{
+			deSleep(m_sleepTimeMs);
+		}
+	}
+
+	if (m_executionReady)
+	{
+		std::vector<int> indices(m_threads.size(), 0);
+		while (true)
+		{
+			int firstThread = -1;
+
+			// Find first thread with messages
+			for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			{
+				if (m_threads[threadNdx]->getMessageCount() > indices[threadNdx])
+				{
+					firstThread = threadNdx;
+					break;
+				}
+			}
+
+			// No more messages
+			if (firstThread == -1)
+				break;
+
+			for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			{
+				// No more messages in this thread
+				if (m_threads[threadNdx]->getMessageCount() <= indices[threadNdx])
+					continue;
+
+				if ((m_threads[threadNdx]->getMessage(indices[threadNdx]).getTime() - m_beginTimeUs) < (m_threads[firstThread]->getMessage(indices[firstThread]).getTime() - m_beginTimeUs))
+					firstThread = threadNdx;
+			}
+
+			deUint64	time	= m_threads[firstThread]->getMessage(indices[firstThread]).getTime();
+			std::string	message	= m_threads[firstThread]->getMessage(indices[firstThread]).getMessage();
+			m_log << tcu::TestLog::Message << "[" << (time - m_beginTimeUs) << "] (" << firstThread << ") " << message << tcu::TestLog::EndMessage;
+			indices[firstThread]++;
+		}
+
+		bool isOk = true;
+		bool notSupported = false;
+
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		{
+			if (m_threads[threadNdx]->getStatus() == tcu::ThreadUtil::Thread::THREADSTATUS_FAILED)
+				isOk = false;
+			else if (m_threads[threadNdx]->getStatus() == tcu::ThreadUtil::Thread::THREADSTATUS_READY)
+				isOk &= true;
+			else if (m_threads[threadNdx]->getStatus() == tcu::ThreadUtil::Thread::THREADSTATUS_NOT_SUPPORTED)
+				notSupported = true;
+			else
+			{
+				isOk = false;
+				DE_ASSERT(false);
+			}
+		}
+
+		if (m_timeOutTimeUs != 0)
+		{
+			m_log << tcu::TestLog::Message << "[" << (m_timeOutTimeUs - m_beginTimeUs) << "] Execution timeout limit reached" << tcu::TestLog::EndMessage;
+		}
+
+		if (notSupported)
+			throw tcu::NotSupportedError("Thread threw tcu::NotSupportedError", "", __FILE__, __LINE__);
+
+		if (isOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+		return STOP;
+	}
+
+	return CONTINUE;
+}
+
+static void addSimpleTests (EglTestContext& ctx, tcu::TestCaseGroup* group, bool useSync, bool serverSync)
+{
+	{
+		TestCaseGroup* bufferTests = new TestCaseGroup(ctx, "buffers", "Buffer management tests");
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_BUFFER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 0;
+			config.modify = 0;
+			config.render = false;
+			bufferTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "gen_delete", "Generate and delete buffer"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_BUFFER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 0;
+			config.render = false;
+			bufferTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "bufferdata", "Generate, set data and delete buffer"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_BUFFER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 1;
+			config.render = false;
+			bufferTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "buffersubdata", "Generate, set data, update data and delete buffer"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_BUFFER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 0;
+			config.render = true;
+			bufferTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "bufferdata_render", "Generate, set data, render and delete buffer"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_BUFFER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 1;
+			config.render = true;
+			bufferTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "buffersubdata_render", "Generate, set data, update data, render and delete buffer"));
+		}
+
+		group->addChild(bufferTests);
+	}
+
+	{
+		TestCaseGroup* textureTests = new TestCaseGroup(ctx, "textures", "Texture management tests");
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 0;
+			config.modify = 0;
+			config.render = false;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "gen_delete", "Generate and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 0;
+			config.render = false;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d", "Generate, set data and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 1;
+			config.render = false;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d_texsubimage2d", "Generate, set data, update data and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 2;
+			config.render = false;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d_copytexsubimage2d", "Generate, set data, update data and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 0;
+			config.render = true;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d_render", "Generate, set data, render and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 1;
+			config.render = true;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d_texsubimage2d_render", "Generate, set data, update data, render and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 2;
+			config.render = true;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d_copytexsubimage2d_render", "Generate, set data, update data, render and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 0;
+			config.render = false;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d", "Generate, set data and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 1;
+			config.render = false;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_texsubimage2d", "Generate, set data, update data and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 2;
+			config.render = false;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_copytexsubimage2d", "Generate, set data, update data and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 0;
+			config.render = true;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_render", "Generate, set data, render and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 1;
+			config.render = true;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_texsubimage2d_render", "Generate, set data, update data, render and delete texture"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 2;
+			config.render = true;
+			textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_copytexsubimage2d_render", "Generate, set data, update data, render and delete texture"));
+		}
+
+		group->addChild(textureTests);
+	}
+
+	{
+		TestCaseGroup* shaderTests = new TestCaseGroup(ctx, "shaders", "Shader management tests");
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_VERTEX_SHADER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 0;
+			config.modify = 0;
+			config.render = false;
+			shaderTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "vtx_create_destroy", "Create and delete shader"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_VERTEX_SHADER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 0;
+			config.render = false;
+			shaderTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "vtx_shadersource", "Create, set source and delete shader"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_VERTEX_SHADER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 1;
+			config.render = false;
+			shaderTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "vtx_compile", "Create, set source, compile and delete shader"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_FRAGMENT_SHADER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 0;
+			config.modify = 0;
+			config.render = false;
+			shaderTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "frag_create_destroy", "Create and delete shader"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_FRAGMENT_SHADER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 0;
+			config.render = false;
+			shaderTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "frag_shadersource", "Create, set source and delete shader"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_FRAGMENT_SHADER;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 1;
+			config.render = false;
+			shaderTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "frag_compile", "Create, set source, compile and delete shader"));
+		}
+
+		group->addChild(shaderTests);
+	}
+
+	{
+		TestCaseGroup* programTests = new TestCaseGroup(ctx, "programs", "Program management tests");
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_PROGRAM;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 0;
+			config.modify = 0;
+			config.render = false;
+			programTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "create_destroy", "Create and delete program"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_PROGRAM;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 0;
+			config.render = false;
+			programTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "attach", "Create, attach shaders and delete program"));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_PROGRAM;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 1;
+			config.render = false;
+			programTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "link", "Create, attach shaders, link and delete program"));
+		}
+
+		group->addChild(programTests);
+	}
+
+	{
+		TestCaseGroup* imageTests = new TestCaseGroup(ctx, "images", "Image management tests");
+
+		TestCaseGroup* textureSourceTests = new TestCaseGroup(ctx, "texture_source", "Image management tests with texture source.");
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 1;
+			config.modify = 0;
+			config.render = false;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "create_destroy", "Create and destroy EGLImage."));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 0;
+			config.render = false;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "create_texture", "Create texture from image."));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 1;
+			config.render = false;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "texsubimage2d", "Modify texture created from image with glTexSubImage2D."));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 2;
+			config.render = false;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copytexsubimage2d", "Modify texture created from image with glCopyTexSubImage2D."));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 3;
+			config.render = false;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d", "Modify texture created from image with glTexImage2D."));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 4;
+			config.render = false;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d", "Modify texture created from image with glCopyTexImage2D."));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 0;
+			config.render = true;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "create_texture_render", "Create texture from image and render."));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 1;
+			config.render = true;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "texsubimage2d_render", "Modify texture created from image and render."));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 2;
+			config.render = true;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copytexsubimage2d_render", "Modify texture created from image and render."));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 3;
+			config.render = true;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d_render", "Modify texture created from image and render."));
+		}
+
+		{
+			GLES2ThreadedSharingTest::TestConfig config;
+
+			config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
+			config.useFenceSync = useSync;
+			config.serverSync	= serverSync;
+			config.define = 2;
+			config.modify = 4;
+			config.render = true;
+			textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_render", "Modify texture created from image and render."));
+		}
+
+		imageTests->addChild(textureSourceTests);
+
+		group->addChild(imageTests);
+	}
+
+}
+
+static void addRandomTests (EglTestContext& ctx, tcu::TestCaseGroup* group, bool useSync, bool serverSync)
+{
+	{
+		TestCaseGroup* textureTests = new TestCaseGroup(ctx, "textures", "Texture management tests");
+
+		{
+			TestCaseGroup* genTextureTests = new TestCaseGroup(ctx, "gen_delete", "Texture gen and delete tests");
+
+			for (int textureTestNdx = 0; textureTestNdx < 20; textureTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + textureTestNdx % 5;
+				config.operationCount	= 30 + textureTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE]				= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]	= 0.75f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.5f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]	= 0.5f;
+
+				std::string	name = de::toString(textureTestNdx);
+				genTextureTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			textureTests->addChild(genTextureTests);
+		}
+
+		{
+			TestCaseGroup* texImage2DTests = new TestCaseGroup(ctx, "teximage2d", "Texture gen, delete and teximage2D tests");
+
+			for (int textureTestNdx = 0; textureTestNdx < 20; textureTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + textureTestNdx % 5;
+				config.operationCount	= 40 + textureTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE]				= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]	= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]		= 0.80f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.30f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]	= 0.40f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]		= 0.30f;
+
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]		= 0.40f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]		= 0.40f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]			= 0.20f;
+
+				std::string	name = de::toString(textureTestNdx);
+				texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			textureTests->addChild(texImage2DTests);
+		}
+
+		{
+			TestCaseGroup* texSubImage2DTests = new TestCaseGroup(ctx, "texsubimage2d", "Texture gen, delete, teximage2D and texsubimage2d tests");
+
+			for (int textureTestNdx = 0; textureTestNdx < 20; textureTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + textureTestNdx % 5;
+				config.operationCount	= 50 + textureTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE]				= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]	= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]		= 0.80f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXSUBIMAGE2D]		= 0.05f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.30f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]	= 0.40f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]		= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXSUBIMAGE2D]	= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]		= 0.20f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]		= 0.20f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]			= 0.10f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXSUBIMAGE2D]			= 0.50f;
+
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.20f;
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]		= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_TEXSUBIMAGE2D]		= 0.30f;
+
+				std::string	name = de::toString(textureTestNdx);
+				texSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			textureTests->addChild(texSubImage2DTests);
+		}
+
+		{
+			TestCaseGroup* copyTexImage2DTests = new TestCaseGroup(ctx, "copyteximage2d", "Texture gen, delete and copyteximage2d tests");
+
+			for (int textureTestNdx = 0; textureTestNdx < 20; textureTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + textureTestNdx % 5;
+				config.operationCount	= 40 + textureTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE]				= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]	= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_COPYTEXIMAGE2D]	= 0.80f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.30f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]	= 0.40f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_COPYTEXIMAGE2D]	= 0.30f;
+
+				config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.40f;
+				config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]	= 0.40f;
+				config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_COPYTEXIMAGE2D]	= 0.20f;
+
+
+				std::string	name = de::toString(textureTestNdx);
+				copyTexImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			textureTests->addChild(copyTexImage2DTests);
+		}
+
+		{
+			TestCaseGroup* copyTexSubImage2DTests = new TestCaseGroup(ctx, "copytexsubimage2d", "Texture gen, delete, teximage2D and copytexsubimage2d tests");
+
+			for (int textureTestNdx = 0; textureTestNdx < 20; textureTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + textureTestNdx % 5;
+				config.operationCount	= 50 + textureTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE]					= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]		= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]		= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]			= 0.80f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_COPYTEXSUBIMAGE2D]		= 0.05f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]		= 0.30f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]		= 0.40f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]			= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_COPYTEXSUBIMAGE2D]	= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]			= 0.20f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]			= 0.20f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_COPYTEXSUBIMAGE2D]			= 0.50f;
+
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]	= 0.20f;
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]		= 0.25f;
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_COPYTEXSUBIMAGE2D]	= 0.30f;
+
+
+				std::string	name = de::toString(textureTestNdx);
+				copyTexSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			textureTests->addChild(copyTexSubImage2DTests);
+		}
+
+		group->addChild(textureTests);
+
+		TestCaseGroup* bufferTests = new TestCaseGroup(ctx, "buffers", "Buffer management tests");
+
+		{
+			TestCaseGroup* genBufferTests = new TestCaseGroup(ctx, "gen_delete", "Buffer gen and delete tests");
+
+			for (int bufferTestNdx = 0; bufferTestNdx < 20; bufferTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + bufferTestNdx % 5;
+				config.operationCount	= 30 + bufferTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_BUFFER]				= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_DESTROY_BUFFER]		= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_CREATE_BUFFER]		= 0.75f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_DESTROY_BUFFER]	= 0.5f;
+				config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_CREATE_BUFFER]		= 0.5f;
+
+				std::string	name = de::toString(bufferTestNdx);
+				genBufferTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			bufferTests->addChild(genBufferTests);
+		}
+
+		{
+			TestCaseGroup* texImage2DTests = new TestCaseGroup(ctx, "bufferdata", "Buffer gen, delete and bufferdata tests");
+
+			for (int bufferTestNdx = 0; bufferTestNdx < 20; bufferTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + bufferTestNdx % 5;
+				config.operationCount	= 40 + bufferTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_BUFFER]				= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_DESTROY_BUFFER]		= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_CREATE_BUFFER]		= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_BUFFER_DATA]		= 0.80f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_DESTROY_BUFFER]	= 0.30f;
+				config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_CREATE_BUFFER]		= 0.40f;
+				config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_BUFFER_DATA]		= 0.30f;
+
+				config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_DESTROY_BUFFER]		= 0.40f;
+				config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_CREATE_BUFFER]		= 0.40f;
+				config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_BUFFER_DATA]			= 0.20f;
+
+				std::string	name = de::toString(bufferTestNdx);
+				texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			bufferTests->addChild(texImage2DTests);
+		}
+
+		{
+			TestCaseGroup* texSubImage2DTests = new TestCaseGroup(ctx, "buffersubdata", "Buffer gen, delete, bufferdata and bufferdata tests");
+
+			for (int bufferTestNdx = 0; bufferTestNdx < 20; bufferTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + bufferTestNdx % 5;
+				config.operationCount	= 50 + bufferTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_BUFFER]				= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_DESTROY_BUFFER]		= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_CREATE_BUFFER]		= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_BUFFER_DATA]		= 0.80f;
+				config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_BUFFER_SUBDATA]		= 0.05f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_DESTROY_BUFFER]	= 0.30f;
+				config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_CREATE_BUFFER]		= 0.40f;
+				config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_BUFFER_DATA]		= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_BUFFER_SUBDATA]	= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_DESTROY_BUFFER]		= 0.20f;
+				config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_CREATE_BUFFER]		= 0.20f;
+				config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_BUFFER_DATA]			= 0.10f;
+				config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_BUFFER_SUBDATA]		= 0.50f;
+
+				config.probabilities[THREADOPERATIONID_BUFFER_SUBDATA][THREADOPERATIONID_DESTROY_BUFFER]	= 0.20f;
+				config.probabilities[THREADOPERATIONID_BUFFER_SUBDATA][THREADOPERATIONID_CREATE_BUFFER]		= 0.25f;
+				config.probabilities[THREADOPERATIONID_BUFFER_SUBDATA][THREADOPERATIONID_BUFFER_DATA]		= 0.25f;
+				config.probabilities[THREADOPERATIONID_BUFFER_SUBDATA][THREADOPERATIONID_BUFFER_SUBDATA]	= 0.30f;
+
+				std::string	name = de::toString(bufferTestNdx);
+				texSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			bufferTests->addChild(texSubImage2DTests);
+		}
+
+		group->addChild(bufferTests);
+
+		TestCaseGroup* shaderTests = new TestCaseGroup(ctx, "shaders", "Shader management tests");
+
+		{
+			TestCaseGroup* createShaderTests = new TestCaseGroup(ctx, "create_destroy", "Shader create and destroy tests");
+
+			for (int shaderTestNdx = 0; shaderTestNdx < 20; shaderTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + shaderTestNdx % 5;
+				config.operationCount	= 30 + shaderTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_VERTEX_SHADER]						= 0.5f;
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]						= 0.5f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]		= 0.40f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]		= 0.40f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_SHADER]			= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]		= 0.40f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]	= 0.40f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_SHADER]					= 0.5f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.25f;
+
+				std::string	name = de::toString(shaderTestNdx);
+				createShaderTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			shaderTests->addChild(createShaderTests);
+		}
+
+		{
+			TestCaseGroup* texImage2DTests = new TestCaseGroup(ctx, "source", "Shader create, destroy and source tests");
+
+			for (int shaderTestNdx = 0; shaderTestNdx < 20; shaderTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + shaderTestNdx % 5;
+				config.operationCount	= 40 + shaderTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_VERTEX_SHADER]						= 0.5f;
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]						= 0.5f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_SHADER]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]		= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]		= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_SOURCE]				= 0.50f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_SHADER]			= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]		= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]	= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_SOURCE]				= 0.50f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_SHADER]					= 0.30f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.30f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.30f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_SOURCE]						= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_SHADER]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_SOURCE]						= 0.40f;
+
+				std::string	name = de::toString(shaderTestNdx);
+				texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			shaderTests->addChild(texImage2DTests);
+		}
+
+		{
+			TestCaseGroup* texSubImage2DTests = new TestCaseGroup(ctx, "compile", "Shader create, destroy, source and compile tests");
+
+			for (int shaderTestNdx = 0; shaderTestNdx < 20; shaderTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + shaderTestNdx % 5;
+				config.operationCount	= 50 + shaderTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_VERTEX_SHADER]						= 0.5f;
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]						= 0.5f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_SHADER]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]		= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]		= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_SOURCE]				= 0.50f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_COMPILE]				= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_SHADER]			= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]		= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]	= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_SOURCE]				= 0.50f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_COMPILE]			= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_SHADER]					= 0.30f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_SOURCE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_COMPILE]					= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_SHADER]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_SOURCE]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_COMPILE]						= 0.50f;
+
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_DESTROY_SHADER]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.15f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.15f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_SOURCE]						= 0.30f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_COMPILE]					= 0.30f;
+
+				std::string	name = de::toString(shaderTestNdx);
+				texSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			shaderTests->addChild(texSubImage2DTests);
+		}
+
+		group->addChild(shaderTests);
+
+		TestCaseGroup* programTests = new TestCaseGroup(ctx, "programs", "Program management tests");
+
+		{
+			TestCaseGroup* createProgramTests = new TestCaseGroup(ctx, "create_destroy", "Program create and destroy tests");
+
+			for (int programTestNdx = 0; programTestNdx < 20; programTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + programTestNdx % 5;
+				config.operationCount	= 30 + programTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_PROGRAM]				= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM]	= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]	= 0.75f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM]	= 0.5f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]	= 0.5f;
+
+				std::string	name = de::toString(programTestNdx);
+				createProgramTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			programTests->addChild(createProgramTests);
+		}
+
+		{
+			TestCaseGroup* texImage2DTests = new TestCaseGroup(ctx, "attach_detach", "Program create, destroy, attach and detach tests");
+
+			for (int programTestNdx = 0; programTestNdx < 20; programTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + programTestNdx % 5;
+				config.operationCount	= 60 + programTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_VERTEX_SHADER]						= 0.35f;
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]						= 0.35f;
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_PROGRAM]								= 0.30f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_SHADER]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]		= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]		= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_SOURCE]				= 0.30f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_COMPILE]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_PROGRAM]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_PROGRAM]				= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_ATTACH_SHADER]				= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_SHADER]			= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]		= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]	= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_SOURCE]				= 0.30f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_COMPILE]			= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_PROGRAM]			= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_PROGRAM]			= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_ATTACH_SHADER]				= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_SHADER]					= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_SOURCE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_COMPILE]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_PROGRAM]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_ATTACH_SHADER]						= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_SHADER]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_SOURCE]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_COMPILE]						= 0.50f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_PROGRAM]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_ATTACH_SHADER]						= 0.25f;
+
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_DESTROY_SHADER]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.15f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.15f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_SOURCE]						= 0.30f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_COMPILE]					= 0.30f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_PROGRAM]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_ATTACH_SHADER]						= 0.35f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_DESTROY_SHADER]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_SHADER_SOURCE]						= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_SHADER_COMPILE]					= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_ATTACH_SHADER]						= 0.40f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_DESTROY_SHADER]					= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_SHADER_SOURCE]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_SHADER_COMPILE]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]					= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_ATTACH_SHADER]					= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_DESTROY_SHADER]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_SHADER_SOURCE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_SHADER_COMPILE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_PROGRAM]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_ATTACH_SHADER]						= 0.30f;
+
+				std::string	name = de::toString(programTestNdx);
+				texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			programTests->addChild(texImage2DTests);
+		}
+
+		{
+			TestCaseGroup* texSubImage2DTests = new TestCaseGroup(ctx, "link", "Program create, destroy, attach and link tests");
+
+			for (int programTestNdx = 0; programTestNdx < 20; programTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + programTestNdx % 5;
+				config.operationCount	= 70 + programTestNdx;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_VERTEX_SHADER]						= 0.35f;
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]						= 0.35f;
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_PROGRAM]								= 0.30f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_SHADER]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]		= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]		= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_SOURCE]				= 0.30f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_COMPILE]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_PROGRAM]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_PROGRAM]				= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_ATTACH_SHADER]				= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_LINK_PROGRAM]				= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_SHADER]			= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]		= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]	= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_SOURCE]				= 0.30f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_COMPILE]			= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_PROGRAM]			= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_PROGRAM]			= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_ATTACH_SHADER]				= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_LINK_PROGRAM]				= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_SHADER]					= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_SOURCE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_COMPILE]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_PROGRAM]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_ATTACH_SHADER]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_LINK_PROGRAM]						= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_SHADER]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]				= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_SOURCE]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_COMPILE]						= 0.50f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_PROGRAM]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_ATTACH_SHADER]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_LINK_PROGRAM]						= 0.20f;
+
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_DESTROY_SHADER]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.15f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.15f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_SOURCE]						= 0.30f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_COMPILE]					= 0.30f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_PROGRAM]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_ATTACH_SHADER]						= 0.35f;
+				config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_LINK_PROGRAM]						= 0.20f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_DESTROY_SHADER]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_SHADER_SOURCE]						= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_SHADER_COMPILE]					= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.05f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_ATTACH_SHADER]						= 0.40f;
+				config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_LINK_PROGRAM]						= 0.05f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_DESTROY_SHADER]					= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]			= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_SHADER_SOURCE]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_SHADER_COMPILE]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]					= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_ATTACH_SHADER]					= 0.10f;
+				config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_LINK_PROGRAM]						= 0.05f;
+
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_DESTROY_SHADER]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_SHADER_SOURCE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_SHADER_COMPILE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_PROGRAM]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_DESTROY_PROGRAM]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_ATTACH_SHADER]						= 0.30f;
+				config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_LINK_PROGRAM]						= 0.30f;
+
+				config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_DESTROY_SHADER]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_CREATE_VERTEX_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_SHADER_SOURCE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_SHADER_COMPILE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_ATTACH_SHADER]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_LINK_PROGRAM]						= 0.05f;
+
+				std::string	name = de::toString(programTestNdx);
+				texSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			programTests->addChild(texSubImage2DTests);
+		}
+
+		group->addChild(programTests);
+
+		TestCaseGroup* imageTests = new TestCaseGroup(ctx, "images", "Image management tests");
+
+		{
+			TestCaseGroup* texImage2DTests = new TestCaseGroup(ctx, "create_destroy", "Image gen, delete and teximage2D tests");
+
+			for (int imageTestNdx = 0; imageTestNdx < 20; imageTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + imageTestNdx % 5;
+				config.operationCount	= 70 + imageTestNdx;
+				config.useImages		= true;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE]									= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.40f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]							= 0.35f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]							= 0.30f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.40f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]						= 0.35f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]							= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]	= 0.40f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]				= 0.35f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]					= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.40f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]							= 0.35f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXIMAGE2D]								= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]					= 0.40f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]								= 0.35f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]								= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.30f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXIMAGE2D]						= 0.15f;
+
+				std::string	name = de::toString(imageTestNdx);
+				texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			imageTests->addChild(texImage2DTests);
+		}
+
+		{
+			TestCaseGroup* texImage2DTests = new TestCaseGroup(ctx, "teximage2d", "Image gen, delete and teximage2D tests");
+
+			for (int imageTestNdx = 0; imageTestNdx < 20; imageTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + imageTestNdx % 5;
+				config.operationCount	= 70 + imageTestNdx;
+				config.useImages		= true;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE]									= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]							= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]							= 0.30f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]					= 0.20f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]							= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]					= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]	= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]			= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXIMAGE2D]								= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]						= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]								= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]								= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE]						= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXIMAGE2D]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]				= 0.15f;
+
+				std::string	name = de::toString(imageTestNdx);
+				texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			imageTests->addChild(texImage2DTests);
+		}
+
+		{
+			TestCaseGroup* texSubImage2DTests = new TestCaseGroup(ctx, "texsubimage2d", "Image gen, delete, teximage2D and texsubimage2d tests");
+
+			for (int imageTestNdx = 0; imageTestNdx < 20; imageTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + imageTestNdx % 5;
+				config.operationCount	= 70 + imageTestNdx;
+				config.useImages		= true;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE]									= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]							= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]							= 0.30f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]					= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXSUBIMAGE2D]							= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]							= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXSUBIMAGE2D]						= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]	= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]			= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXSUBIMAGE2D]				= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXIMAGE2D]								= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXSUBIMAGE2D]							= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]								= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]								= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXSUBIMAGE2D]								= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXIMAGE2D]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]				= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXSUBIMAGE2D]						= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]								= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_TEXSUBIMAGE2D]							= 0.10f;
+
+				std::string	name = de::toString(imageTestNdx);
+				texSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			imageTests->addChild(texSubImage2DTests);
+		}
+
+		{
+			TestCaseGroup* copyTexImage2DTests = new TestCaseGroup(ctx, "copyteximage2d", "Image gen, delete and copyteximage2d tests");
+
+			for (int imageTestNdx = 0; imageTestNdx < 20; imageTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + imageTestNdx % 5;
+				config.operationCount	= 70 + imageTestNdx;
+				config.useImages		= true;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE]									= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]							= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_COPYTEXIMAGE2D]						= 0.30f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]					= 0.20f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_COPYTEXIMAGE2D]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]					= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]	= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_COPYTEXIMAGE2D]				= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]			= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_COPYTEXIMAGE2D]							= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]						= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_COPYTEXIMAGE2D]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE]					= 0.15f;
+
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_COPYTEXIMAGE2D]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]				= 0.15f;
+
+				std::string	name = de::toString(imageTestNdx);
+				copyTexImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			imageTests->addChild(copyTexImage2DTests);
+		}
+
+		{
+			TestCaseGroup* copyTexSubImage2DTests = new TestCaseGroup(ctx, "copytexsubimage2d", "Image gen, delete, teximage2D and copytexsubimage2d tests");
+
+			for (int imageTestNdx = 0; imageTestNdx < 20; imageTestNdx++)
+			{
+				GLES2SharingRandomTest::TestConfig config;
+				config.useFenceSync		= useSync;
+				config.serverSync		= serverSync;
+				config.threadCount		= 2 + imageTestNdx % 5;
+				config.operationCount	= 70 + imageTestNdx;
+				config.useImages		= true;
+
+				config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE]									= 1.0f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.10f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]							= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]							= 0.30f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]					= 0.20f;
+				config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_COPYTEXSUBIMAGE2D]						= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]						= 0.20f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]							= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_COPYTEXSUBIMAGE2D]					= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]	= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]			= 0.15f;
+				config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_COPYTEXSUBIMAGE2D]			= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]				= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXIMAGE2D]								= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_COPYTEXSUBIMAGE2D]						= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]							= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]								= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]								= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_COPYTEXSUBIMAGE2D]							= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXIMAGE2D]						= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE]				= 0.15f;
+				config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_COPYTEXSUBIMAGE2D]					= 0.10f;
+
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]					= 0.25f;
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]			= 0.25f;
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]						= 0.25f;
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]							= 0.15f;
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE]					= 0.15f;
+				config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_COPYTEXSUBIMAGE2D]					= 0.10f;
+
+
+				std::string	name = de::toString(imageTestNdx);
+				copyTexSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
+			}
+
+			imageTests->addChild(copyTexSubImage2DTests);
+		}
+
+		group->addChild(imageTests);
+	}
+}
+
+GLES2SharingThreadedTests::GLES2SharingThreadedTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "multithread", "EGL GLES2 sharing multithread tests")
+{
+}
+
+void GLES2SharingThreadedTests::init (void)
+{
+	tcu::TestCaseGroup* simpleTests = new TestCaseGroup(m_eglTestCtx, "simple", "Simple multithreaded tests");
+	addSimpleTests(m_eglTestCtx, simpleTests, false, false);
+	addChild(simpleTests);
+
+	TestCaseGroup* randomTests = new TestCaseGroup(m_eglTestCtx, "random", "Random tests");
+	addRandomTests(m_eglTestCtx, randomTests, false, false);
+	addChild(randomTests);
+
+	tcu::TestCaseGroup* simpleTestsSync = new TestCaseGroup(m_eglTestCtx, "simple_egl_sync", "Simple multithreaded tests with EGL_KHR_fence_sync");
+	addSimpleTests(m_eglTestCtx, simpleTestsSync, true, false);
+	addChild(simpleTestsSync);
+
+	TestCaseGroup* randomTestsSync = new TestCaseGroup(m_eglTestCtx, "random_egl_sync", "Random tests with EGL_KHR_fence_sync");
+	addRandomTests(m_eglTestCtx, randomTestsSync, true, false);
+	addChild(randomTestsSync);
+
+	tcu::TestCaseGroup* simpleTestsServerSync = new TestCaseGroup(m_eglTestCtx, "simple_egl_server_sync", "Simple multithreaded tests with EGL_KHR_fence_sync and EGL_KHR_wait_sync");
+	addSimpleTests(m_eglTestCtx, simpleTestsServerSync, true, true);
+	addChild(simpleTestsServerSync);
+
+	TestCaseGroup* randomTestsServerSync = new TestCaseGroup(m_eglTestCtx, "random_egl_server_sync", "Random tests with EGL_KHR_fence_sync and EGL_KHR_wait_sync");
+	addRandomTests(m_eglTestCtx, randomTestsServerSync, true, true);
+	addChild(randomTestsServerSync);
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglGLES2SharingThreadedTests.hpp b/modules/egl/teglGLES2SharingThreadedTests.hpp
new file mode 100644
index 0000000..92ea958
--- /dev/null
+++ b/modules/egl/teglGLES2SharingThreadedTests.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLGLES2SHARINGTHREADEDTESTS_HPP
+#define _TEGLGLES2SHARINGTHREADEDTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL gles2 sharing threaded tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+
+namespace deqp
+{
+namespace egl
+{
+
+class GLES2SharingThreadedTests : public TestCaseGroup
+{
+public:
+			GLES2SharingThreadedTests	(EglTestContext& eglTestCtx);
+	void	init						(void);
+};
+
+} // egl
+} // deqp
+#endif // _TEGLGLES2SHARINGTHREADEDTESTS_HPP
diff --git a/modules/egl/teglGetProcAddressTests.cpp b/modules/egl/teglGetProcAddressTests.cpp
new file mode 100644
index 0000000..b341289
--- /dev/null
+++ b/modules/egl/teglGetProcAddressTests.cpp
@@ -0,0 +1,316 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Extension function pointer query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglGetProcAddressTests.hpp"
+#include "teglTestCase.hpp"
+#include "egluCallLogWrapper.hpp"
+#include "egluStrUtil.hpp"
+#include "tcuTestLog.hpp"
+
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <cctype>
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#	define EGL_OPENGL_ES3_BIT_KHR	0x0040
+#endif
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace
+{
+
+// Function name strings generated from API headers
+
+#include "teglGetProcAddressTests.inl"
+
+std::vector<std::string> makeStringVector (const char** cStrArray)
+{
+	std::vector<std::string>	out;
+
+	for (int ndx = 0; cStrArray[ndx] != DE_NULL; ndx++)
+	{
+		out.push_back(std::string(cStrArray[ndx]));
+	}
+
+	return out;
+}
+
+std::vector<std::string> getExtensionNames (void)
+{
+	return makeStringVector(getExtensionStrs());
+}
+
+std::vector<std::string> getExtFunctionNames (const std::string& extName)
+{
+	const char** names_raw = getExtensionFuncStrs(extName);
+
+	return (names_raw != 0) ? makeStringVector(names_raw) : std::vector<std::string>();
+}
+
+std::vector<std::string> getCoreFunctionNames (EGLint apiBit)
+{
+	switch (apiBit)
+	{
+		case 0:							return makeStringVector(getCoreFunctionStrs());
+		case EGL_OPENGL_ES_BIT:			return makeStringVector(getGlesFunctionStrs());
+		case EGL_OPENGL_ES2_BIT:		return makeStringVector(getGles2FunctionStrs());
+		case EGL_OPENGL_ES3_BIT_KHR:	return makeStringVector(getGles3FunctionStrs());
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return std::vector<std::string>();
+}
+
+} // anonymous
+
+// Base class for eglGetProcAddress() test cases
+
+class GetProcAddressCase : public TestCase, protected eglu::CallLogWrapper
+{
+public:
+								GetProcAddressCase		(EglTestContext& eglTestCtx, const char* name, const char* description);
+	virtual						~GetProcAddressCase		(void);
+
+	void						init					(void);
+	IterateResult				iterate					(void);
+
+	bool						isSupported				(const std::string& extName);
+
+	virtual void				executeTest				(void) = 0;
+
+private:
+	std::vector<std::string>	m_supported;
+};
+
+GetProcAddressCase::GetProcAddressCase (EglTestContext& eglTestCtx, const char* name, const char* description)
+	: TestCase			(eglTestCtx, name, description)
+	, CallLogWrapper	(eglTestCtx.getTestContext().getLog())
+{
+}
+
+GetProcAddressCase::~GetProcAddressCase (void)
+{
+}
+
+void GetProcAddressCase::init (void)
+{
+	const tcu::egl::Display&	display		= m_eglTestCtx.getDisplay();
+	display.getExtensions(m_supported);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+tcu::TestNode::IterateResult GetProcAddressCase::iterate (void)
+{
+	enableLogging(true);
+
+	executeTest();
+
+	enableLogging(false);
+
+	return STOP;
+}
+
+bool GetProcAddressCase::isSupported (const std::string& extName)
+{
+	return std::find(m_supported.begin(), m_supported.end(), extName) != m_supported.end();
+}
+
+// Test by extension
+
+class GetProcAddressExtensionCase : public GetProcAddressCase
+{
+public:
+	GetProcAddressExtensionCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::string& extName)
+		: GetProcAddressCase	(eglTestCtx, name, description)
+		, m_extName				(extName)
+	{
+	}
+
+	virtual ~GetProcAddressExtensionCase (void)
+	{
+	}
+
+	void executeTest (void)
+	{
+		TestLog&						log			= m_testCtx.getLog();
+		bool							supported	= isSupported(m_extName);
+		const std::vector<std::string>	funcNames	= getExtFunctionNames(m_extName);
+
+		DE_ASSERT(!funcNames.empty());
+
+		log << TestLog::Message << m_extName << ": " << (supported ? "supported" : "not supported") << TestLog::EndMessage;
+		log << TestLog::Message << TestLog::EndMessage;
+
+		for (std::vector<std::string>::const_iterator funcIter = funcNames.begin(); funcIter != funcNames.end(); funcIter++)
+		{
+			const std::string&	funcName			= *funcIter;
+			void				(*funcPtr)(void);
+
+			funcPtr = eglGetProcAddress(funcName.c_str());
+			TCU_CHECK_EGL();
+
+			if (supported && funcPtr == 0)
+			{
+				log << TestLog::Message << "Fail, received null pointer for supported extension function: " << funcName << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected null pointer");
+			}
+		}
+	}
+
+private:
+	std::string	m_extName;
+};
+
+// Test core functions
+
+class GetProcAddressCoreFunctionsCase : public GetProcAddressCase
+{
+public:
+	GetProcAddressCoreFunctionsCase (EglTestContext& eglTestCtx, const char* name, const char* description, const EGLint apiBit = 0)
+		: GetProcAddressCase	(eglTestCtx, name, description)
+		, m_funcNames			(getCoreFunctionNames(apiBit))
+		, m_apiBit				(apiBit)
+	{
+	}
+
+	virtual ~GetProcAddressCoreFunctionsCase (void)
+	{
+	}
+
+	bool isApiSupported (void)
+	{
+		const std::vector<eglu::ConfigInfo>	configs	= m_eglTestCtx.getConfigs();
+
+		if (m_apiBit == 0)
+			return true;
+
+		for (std::vector<eglu::ConfigInfo>::const_iterator configIter = configs.begin(); configIter != configs.end(); configIter++)
+		{
+			const eglu::ConfigInfo&	config	= *configIter;
+
+			if ((config.renderableType & m_apiBit) != 0)
+				return true;
+		}
+
+		return false;
+	}
+
+	void executeTest (void)
+	{
+		TestLog&	log					= m_testCtx.getLog();
+		const bool	funcPtrSupported	= isSupported("EGL_KHR_get_all_proc_addresses");
+		const bool	apiSupported		= isApiSupported();
+
+		log << TestLog::Message << "EGL_KHR_get_all_proc_addresses: " << (funcPtrSupported ? "supported" : "not supported") << TestLog::EndMessage;
+		log << TestLog::Message << TestLog::EndMessage;
+
+		if (!apiSupported)
+		{
+			log << TestLog::Message << eglu::getConfigAttribValueStr(EGL_RENDERABLE_TYPE, m_apiBit) << " not supported by any available configuration." << TestLog::EndMessage;
+			log << TestLog::Message << TestLog::EndMessage;
+		}
+
+		for (std::vector<std::string>::const_iterator funcIter = m_funcNames.begin(); funcIter != m_funcNames.end(); funcIter++)
+		{
+			const std::string&	funcName			= *funcIter;
+			void				(*funcPtr)(void);
+
+			funcPtr = eglGetProcAddress(funcName.c_str());
+			TCU_CHECK_EGL();
+
+			if (apiSupported && funcPtrSupported && (funcPtr == 0))
+			{
+				log << TestLog::Message << "Fail, received null pointer for supported function: " << funcName << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected null pointer");
+			}
+			else if (!apiSupported && (funcPtr != 0))
+			{
+				log << TestLog::Message << "Warning, received non-null value for unsupported function: " << funcName << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Non-null value for unsupported function");
+			}
+		}
+	}
+
+private:
+	const std::vector<std::string>	m_funcNames;
+	const EGLint					m_apiBit;
+};
+
+GetProcAddressTests::GetProcAddressTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "get_proc_address", "eglGetProcAddress() tests")
+{
+}
+
+GetProcAddressTests::~GetProcAddressTests (void)
+{
+}
+
+void GetProcAddressTests::init (void)
+{
+	// extensions
+	{
+		tcu::TestCaseGroup* extensionsGroup = new tcu::TestCaseGroup(m_testCtx, "extension", "Test EGL extensions");
+		addChild(extensionsGroup);
+
+		const std::vector<std::string>	extNames	= getExtensionNames();
+
+		for (std::vector<std::string>::const_iterator extIter = extNames.begin(); extIter != extNames.end(); extIter++)
+		{
+			const std::string&				extName		= *extIter;
+			const std::vector<std::string>	funcNames	= getExtFunctionNames(extName);
+
+			std::string						testName	(extName);
+
+			if (funcNames.empty())
+				continue;
+
+			for (size_t ndx = 0; ndx < extName.length(); ndx++)
+				testName[ndx] = std::tolower(extName[ndx]);
+
+			extensionsGroup->addChild(new GetProcAddressExtensionCase(m_eglTestCtx, testName.c_str(), ("Test " + extName).c_str(), extName));
+		}
+	}
+
+	// core functions
+	{
+		tcu::TestCaseGroup* coreFuncGroup = new tcu::TestCaseGroup(m_testCtx, "core", "Test core functions");
+		addChild(coreFuncGroup);
+
+		coreFuncGroup->addChild(new GetProcAddressCoreFunctionsCase	(m_eglTestCtx,	"egl",		"Test EGL core functions"));
+		coreFuncGroup->addChild(new GetProcAddressCoreFunctionsCase	(m_eglTestCtx,	"gles",		"Test OpenGL ES core functions",	EGL_OPENGL_ES_BIT));
+		coreFuncGroup->addChild(new GetProcAddressCoreFunctionsCase	(m_eglTestCtx,	"gles2",	"Test OpenGL ES 2 core functions",	EGL_OPENGL_ES2_BIT));
+		coreFuncGroup->addChild(new GetProcAddressCoreFunctionsCase	(m_eglTestCtx,	"gles3",	"Test OpenGL ES 3 core functions",	EGL_OPENGL_ES3_BIT_KHR));
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglGetProcAddressTests.hpp b/modules/egl/teglGetProcAddressTests.hpp
new file mode 100644
index 0000000..34eb5be
--- /dev/null
+++ b/modules/egl/teglGetProcAddressTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLGETPROCADDRESSTESTS_HPP
+#define _TEGLGETPROCADDRESSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Extension function pointer query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class GetProcAddressTests : public TestCaseGroup
+{
+public:
+							GetProcAddressTests			(EglTestContext& eglTestCtx);
+	virtual					~GetProcAddressTests		(void);
+
+	void					init						(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLGETPROCADDRESSTESTS_HPP
diff --git a/modules/egl/teglGetProcAddressTests.inl b/modules/egl/teglGetProcAddressTests.inl
new file mode 100644
index 0000000..7f0cda2
--- /dev/null
+++ b/modules/egl/teglGetProcAddressTests.inl
@@ -0,0 +1,1722 @@
+/* WARNING! THIS IS A PROGRAMMATICALLY GENERATED CODE. DO NOT MODIFY THE CODE,
+ * SINCE THE CHANGES WILL BE LOST! MODIFY THE GENERATING PYTHON INSTEAD.
+ */
+
+const char** getCoreFunctionStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglGetError",
+		"eglGetDisplay",
+		"eglInitialize",
+		"eglTerminate",
+		"eglQueryString",
+		"eglGetConfigs",
+		"eglChooseConfig",
+		"eglGetConfigAttrib",
+		"eglCreateWindowSurface",
+		"eglCreatePbufferSurface",
+		"eglCreatePixmapSurface",
+		"eglDestroySurface",
+		"eglQuerySurface",
+		"eglBindAPI",
+		"eglQueryAPI",
+		"eglWaitClient",
+		"eglReleaseThread",
+		"eglCreatePbufferFromClientBuffer",
+		"eglSurfaceAttrib",
+		"eglBindTexImage",
+		"eglReleaseTexImage",
+		"eglSwapInterval",
+		"eglCreateContext",
+		"eglDestroyContext",
+		"eglMakeCurrent",
+		"eglGetCurrentContext",
+		"eglGetCurrentSurface",
+		"eglGetCurrentDisplay",
+		"eglQueryContext",
+		"eglWaitGL",
+		"eglWaitNative",
+		"eglSwapBuffers",
+		"eglCopyBuffers",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getGlesFunctionStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glActiveTexture",
+		"glAlphaFunc",
+		"glAlphaFuncx",
+		"glBindTexture",
+		"glBlendFunc",
+		"glClear",
+		"glClearColor",
+		"glClearColorx",
+		"glClearDepthf",
+		"glClearDepthx",
+		"glClearStencil",
+		"glClientActiveTexture",
+		"glColor4f",
+		"glColor4x",
+		"glColorMask",
+		"glColorPointer",
+		"glCompressedTexImage2D",
+		"glCompressedTexSubImage2D",
+		"glCopyTexImage2D",
+		"glCopyTexSubImage2D",
+		"glCullFace",
+		"glDeleteTextures",
+		"glDepthFunc",
+		"glDepthMask",
+		"glDepthRangef",
+		"glDepthRangex",
+		"glDisable",
+		"glDisableClientState",
+		"glDrawArrays",
+		"glDrawElements",
+		"glEnable",
+		"glEnableClientState",
+		"glFinish",
+		"glFlush",
+		"glFogf",
+		"glFogfv",
+		"glFogx",
+		"glFogxv",
+		"glFrontFace",
+		"glFrustumf",
+		"glFrustumx",
+		"glGenTextures",
+		"glGetError",
+		"glGetIntegerv",
+		"glGetString",
+		"glHint",
+		"glLightModelf",
+		"glLightModelfv",
+		"glLightModelx",
+		"glLightModelxv",
+		"glLightf",
+		"glLightfv",
+		"glLightx",
+		"glLightxv",
+		"glLineWidth",
+		"glLineWidthx",
+		"glLoadIdentity",
+		"glLoadMatrixf",
+		"glLoadMatrixx",
+		"glLogicOp",
+		"glMaterialf",
+		"glMaterialfv",
+		"glMaterialx",
+		"glMaterialxv",
+		"glMatrixMode",
+		"glMultMatrixf",
+		"glMultMatrixx",
+		"glMultiTexCoord4f",
+		"glMultiTexCoord4x",
+		"glNormal3f",
+		"glNormal3x",
+		"glNormalPointer",
+		"glOrthof",
+		"glOrthox",
+		"glPixelStorei",
+		"glPointSize",
+		"glPointSizex",
+		"glPolygonOffset",
+		"glPolygonOffsetx",
+		"glPopMatrix",
+		"glPushMatrix",
+		"glReadPixels",
+		"glRotatef",
+		"glRotatex",
+		"glSampleCoverage",
+		"glSampleCoveragex",
+		"glScalef",
+		"glScalex",
+		"glScissor",
+		"glShadeModel",
+		"glStencilFunc",
+		"glStencilMask",
+		"glStencilOp",
+		"glTexCoordPointer",
+		"glTexEnvf",
+		"glTexEnvfv",
+		"glTexEnvx",
+		"glTexEnvxv",
+		"glTexImage2D",
+		"glTexParameterf",
+		"glTexParameterx",
+		"glTexSubImage2D",
+		"glTranslatef",
+		"glTranslatex",
+		"glVertexPointer",
+		"glViewport",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getGles2FunctionStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glAttachShader",
+		"glBindAttribLocation",
+		"glBindBuffer",
+		"glBindFramebuffer",
+		"glBindRenderbuffer",
+		"glBlendColor",
+		"glBlendEquation",
+		"glBlendEquationSeparate",
+		"glBlendFuncSeparate",
+		"glBufferData",
+		"glBufferSubData",
+		"glCheckFramebufferStatus",
+		"glCompileShader",
+		"glCreateProgram",
+		"glCreateShader",
+		"glDeleteBuffers",
+		"glDeleteFramebuffers",
+		"glDeleteProgram",
+		"glDeleteRenderbuffers",
+		"glDeleteShader",
+		"glDetachShader",
+		"glDisableVertexAttribArray",
+		"glEnableVertexAttribArray",
+		"glFramebufferRenderbuffer",
+		"glFramebufferTexture2D",
+		"glGenBuffers",
+		"glGenerateMipmap",
+		"glGenFramebuffers",
+		"glGenRenderbuffers",
+		"glGetActiveAttrib",
+		"glGetActiveUniform",
+		"glGetAttachedShaders",
+		"glGetAttribLocation",
+		"glGetBooleanv",
+		"glGetBufferParameteriv",
+		"glGetFloatv",
+		"glGetFramebufferAttachmentParameteriv",
+		"glGetProgramiv",
+		"glGetProgramInfoLog",
+		"glGetRenderbufferParameteriv",
+		"glGetShaderiv",
+		"glGetShaderInfoLog",
+		"glGetShaderPrecisionFormat",
+		"glGetShaderSource",
+		"glGetTexParameterfv",
+		"glGetTexParameteriv",
+		"glGetUniformfv",
+		"glGetUniformiv",
+		"glGetUniformLocation",
+		"glGetVertexAttribfv",
+		"glGetVertexAttribiv",
+		"glGetVertexAttribPointerv",
+		"glIsBuffer",
+		"glIsEnabled",
+		"glIsFramebuffer",
+		"glIsProgram",
+		"glIsRenderbuffer",
+		"glIsShader",
+		"glIsTexture",
+		"glLinkProgram",
+		"glReleaseShaderCompiler",
+		"glRenderbufferStorage",
+		"glShaderBinary",
+		"glShaderSource",
+		"glStencilFuncSeparate",
+		"glStencilMaskSeparate",
+		"glStencilOpSeparate",
+		"glTexParameterfv",
+		"glTexParameteri",
+		"glTexParameteriv",
+		"glUniform1f",
+		"glUniform1fv",
+		"glUniform1i",
+		"glUniform1iv",
+		"glUniform2f",
+		"glUniform2fv",
+		"glUniform2i",
+		"glUniform2iv",
+		"glUniform3f",
+		"glUniform3fv",
+		"glUniform3i",
+		"glUniform3iv",
+		"glUniform4f",
+		"glUniform4fv",
+		"glUniform4i",
+		"glUniform4iv",
+		"glUniformMatrix2fv",
+		"glUniformMatrix3fv",
+		"glUniformMatrix4fv",
+		"glUseProgram",
+		"glValidateProgram",
+		"glVertexAttrib1f",
+		"glVertexAttrib1fv",
+		"glVertexAttrib2f",
+		"glVertexAttrib2fv",
+		"glVertexAttrib3f",
+		"glVertexAttrib3fv",
+		"glVertexAttrib4f",
+		"glVertexAttrib4fv",
+		"glVertexAttribPointer",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getGles3FunctionStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glReadBuffer",
+		"glDrawRangeElements",
+		"glTexImage3D",
+		"glTexSubImage3D",
+		"glCopyTexSubImage3D",
+		"glCompressedTexImage3D",
+		"glCompressedTexSubImage3D",
+		"glGenQueries",
+		"glDeleteQueries",
+		"glIsQuery",
+		"glBeginQuery",
+		"glEndQuery",
+		"glGetQueryiv",
+		"glGetQueryObjectuiv",
+		"glUnmapBuffer",
+		"glGetBufferPointerv",
+		"glDrawBuffers",
+		"glUniformMatrix2x3fv",
+		"glUniformMatrix3x2fv",
+		"glUniformMatrix2x4fv",
+		"glUniformMatrix4x2fv",
+		"glUniformMatrix3x4fv",
+		"glUniformMatrix4x3fv",
+		"glBlitFramebuffer",
+		"glRenderbufferStorageMultisample",
+		"glFramebufferTextureLayer",
+		"glMapBufferRange",
+		"glFlushMappedBufferRange",
+		"glBindVertexArray",
+		"glDeleteVertexArrays",
+		"glGenVertexArrays",
+		"glIsVertexArray",
+		"glGetIntegeri_v",
+		"glBeginTransformFeedback",
+		"glEndTransformFeedback",
+		"glBindBufferRange",
+		"glBindBufferBase",
+		"glTransformFeedbackVaryings",
+		"glGetTransformFeedbackVarying",
+		"glVertexAttribIPointer",
+		"glGetVertexAttribIiv",
+		"glGetVertexAttribIuiv",
+		"glVertexAttribI4i",
+		"glVertexAttribI4ui",
+		"glVertexAttribI4iv",
+		"glVertexAttribI4uiv",
+		"glGetUniformuiv",
+		"glGetFragDataLocation",
+		"glUniform1ui",
+		"glUniform2ui",
+		"glUniform3ui",
+		"glUniform4ui",
+		"glUniform1uiv",
+		"glUniform2uiv",
+		"glUniform3uiv",
+		"glUniform4uiv",
+		"glClearBufferiv",
+		"glClearBufferuiv",
+		"glClearBufferfv",
+		"glClearBufferfi",
+		"glGetStringi",
+		"glCopyBufferSubData",
+		"glGetUniformIndices",
+		"glGetActiveUniformsiv",
+		"glGetUniformBlockIndex",
+		"glGetActiveUniformBlockiv",
+		"glGetActiveUniformBlockName",
+		"glUniformBlockBinding",
+		"glDrawArraysInstanced",
+		"glDrawElementsInstanced",
+		"glFenceSync",
+		"glIsSync",
+		"glDeleteSync",
+		"glClientWaitSync",
+		"glWaitSync",
+		"glGetInteger64v",
+		"glGetSynciv",
+		"glGetInteger64i_v",
+		"glGetBufferParameteri64v",
+		"glGenSamplers",
+		"glDeleteSamplers",
+		"glIsSampler",
+		"glBindSampler",
+		"glSamplerParameteri",
+		"glSamplerParameteriv",
+		"glSamplerParameterf",
+		"glSamplerParameterfv",
+		"glGetSamplerParameteriv",
+		"glGetSamplerParameterfv",
+		"glVertexAttribDivisor",
+		"glBindTransformFeedback",
+		"glDeleteTransformFeedbacks",
+		"glGenTransformFeedbacks",
+		"glIsTransformFeedback",
+		"glPauseTransformFeedback",
+		"glResumeTransformFeedback",
+		"glGetProgramBinary",
+		"glProgramBinary",
+		"glProgramParameteri",
+		"glInvalidateFramebuffer",
+		"glInvalidateSubFramebuffer",
+		"glTexStorage2D",
+		"glTexStorage3D",
+		"glGetInternalformativ",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getKhrLockSurfaceFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglLockSurfaceKHR",
+		"eglUnlockSurfaceKHR",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesSinglePrecisionFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glDepthRangefOES",
+		"glFrustumfOES",
+		"glOrthofOES",
+		"glClipPlanefOES",
+		"glGetClipPlanefOES",
+		"glClearDepthfOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getQcomTiledRenderingFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glStartTilingQCOM",
+		"glEndTilingQCOM",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvNativeQueryFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglQueryNativeDisplayNV",
+		"eglQueryNativeWindowNV",
+		"eglQueryNativePixmapNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getMesaDrmImageFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglCreateDRMImageMESA",
+		"eglExportDRMImageMESA",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvFenceFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glDeleteFencesNV",
+		"glGenFencesNV",
+		"glIsFenceNV",
+		"glTestFenceNV",
+		"glGetFenceivNV",
+		"glFinishFenceNV",
+		"glSetFenceNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesBlendSubtractFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glBlendEquationOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getKhrStreamConsumerGltextureFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglStreamConsumerGLTextureExternalKHR",
+		"eglStreamConsumerAcquireKHR",
+		"eglStreamConsumerReleaseKHR",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getQcomExtendedGet2FuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glExtGetShadersQCOM",
+		"glExtGetProgramsQCOM",
+		"glExtIsProgramBinaryQCOM",
+		"glExtGetProgramBinarySourceQCOM",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtMultiDrawArraysFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glMultiDrawArraysEXT",
+		"glMultiDrawElementsEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesMatrixPaletteFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glCurrentPaletteMatrixOES",
+		"glLoadPaletteFromModelViewMatrixOES",
+		"glMatrixIndexPointerOES",
+		"glWeightPointerOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtSeparateShaderObjectsFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glUseProgramStagesEXT",
+		"glActiveShaderProgramEXT",
+		"glCreateShaderProgramvEXT",
+		"glBindProgramPipelineEXT",
+		"glDeleteProgramPipelinesEXT",
+		"glGenProgramPipelinesEXT",
+		"glIsProgramPipelineEXT",
+		"glProgramParameteriEXT",
+		"glGetProgramPipelineivEXT",
+		"glProgramUniform1iEXT",
+		"glProgramUniform2iEXT",
+		"glProgramUniform3iEXT",
+		"glProgramUniform4iEXT",
+		"glProgramUniform1fEXT",
+		"glProgramUniform2fEXT",
+		"glProgramUniform3fEXT",
+		"glProgramUniform4fEXT",
+		"glProgramUniform1ivEXT",
+		"glProgramUniform2ivEXT",
+		"glProgramUniform3ivEXT",
+		"glProgramUniform4ivEXT",
+		"glProgramUniform1fvEXT",
+		"glProgramUniform2fvEXT",
+		"glProgramUniform3fvEXT",
+		"glProgramUniform4fvEXT",
+		"glProgramUniformMatrix2fvEXT",
+		"glProgramUniformMatrix3fvEXT",
+		"glProgramUniformMatrix4fvEXT",
+		"glValidateProgramPipelineEXT",
+		"glGetProgramPipelineInfoLogEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAngleTranslatedShaderSourceFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glGetTranslatedShaderSourceANGLE",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvFramebufferMultisampleFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glRenderbufferStorageMultisampleNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvDrawBuffersFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glDrawBuffersNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesEglImageFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glEGLImageTargetTexture2DOES",
+		"glEGLImageTargetRenderbufferStorageOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getKhrWaitSyncFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglWaitSyncKHR",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvSystemTimeFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglGetSystemTimeFrequencyNV",
+		"eglGetSystemTimeNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtRobustnessFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glGetGraphicsResetStatusEXT",
+		"glReadnPixelsEXT",
+		"glGetnUniformfvEXT",
+		"glGetnUniformivEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getKhrImageFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglCreateImageKHR",
+		"eglDestroyImageKHR",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvDrawInstancedFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glDrawArraysInstancedNV",
+		"glDrawElementsInstancedNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getImgUserClipPlaneFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glClipPlanefIMG",
+		"glClipPlanexIMG",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesQueryMatrixFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glQueryMatrixxOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvCoverageSampleFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glCoverageMaskNV",
+		"glCoverageOperationNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtDisjointTimerQueryFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glGenQueriesEXT",
+		"glDeleteQueriesEXT",
+		"glIsQueryEXT",
+		"glBeginQueryEXT",
+		"glEndQueryEXT",
+		"glQueryCounterEXT",
+		"glGetQueryivEXT",
+		"glGetQueryObjectivEXT",
+		"glGetQueryObjectuivEXT",
+		"glGetQueryObjecti64vEXT",
+		"glGetQueryObjectui64vEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesVertexArrayObjectFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glBindVertexArrayOES",
+		"glDeleteVertexArraysOES",
+		"glGenVertexArraysOES",
+		"glIsVertexArrayOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getKhrReusableSyncFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglCreateSyncKHR",
+		"eglDestroySyncKHR",
+		"eglClientWaitSyncKHR",
+		"eglSignalSyncKHR",
+		"eglGetSyncAttribKHR",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtDebugLabelFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glLabelObjectEXT",
+		"glGetObjectLabelEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtMapBufferRangeFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glMapBufferRangeEXT",
+		"glFlushMappedBufferRangeEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAngleFramebufferMultisampleFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glRenderbufferStorageMultisampleANGLE",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getKhrStreamCrossProcessFdFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglGetStreamFileDescriptorKHR",
+		"eglCreateStreamFromFileDescriptorKHR",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtTextureStorageFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glTexStorage1DEXT",
+		"glTexStorage2DEXT",
+		"glTexStorage3DEXT",
+		"glTextureStorage1DEXT",
+		"glTextureStorage2DEXT",
+		"glTextureStorage3DEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getKhrStreamFifoFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglQueryStreamTimeKHR",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesMapbufferFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glMapBufferOES",
+		"glUnmapBufferOES",
+		"glGetBufferPointervOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvPostSubBufferFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglPostSubBufferNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesTextureCubeMapFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glTexGenfOES",
+		"glTexGenfvOES",
+		"glTexGeniOES",
+		"glTexGenivOES",
+		"glTexGenxOES",
+		"glTexGenxvOES",
+		"glGetTexGenfvOES",
+		"glGetTexGenivOES",
+		"glGetTexGenxvOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getKhrStreamProducerEglsurfaceFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglCreateStreamProducerSurfaceKHR",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesBlendEquationSeparateFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glBlendEquationSeparateOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtDrawBuffersFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glDrawBuffersEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesFramebufferObjectFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glIsRenderbufferOES",
+		"glBindRenderbufferOES",
+		"glDeleteRenderbuffersOES",
+		"glGenRenderbuffersOES",
+		"glRenderbufferStorageOES",
+		"glGetRenderbufferParameterivOES",
+		"glIsFramebufferOES",
+		"glBindFramebufferOES",
+		"glDeleteFramebuffersOES",
+		"glGenFramebuffersOES",
+		"glCheckFramebufferStatusOES",
+		"glFramebufferRenderbufferOES",
+		"glFramebufferTexture2DOES",
+		"glGetFramebufferAttachmentParameterivOES",
+		"glGenerateMipmapOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtDebugMarkerFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glInsertEventMarkerEXT",
+		"glPushGroupMarkerEXT",
+		"glPopGroupMarkerEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtMultisampledRenderToTextureFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glRenderbufferStorageMultisampleEXT",
+		"glFramebufferTexture2DMultisampleEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtDiscardFramebufferFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glDiscardFramebufferEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesFixedPointFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glAlphaFuncxOES",
+		"glClearColorxOES",
+		"glClearDepthxOES",
+		"glClipPlanexOES",
+		"glColor4xOES",
+		"glDepthRangexOES",
+		"glFogxOES",
+		"glFogxvOES",
+		"glFrustumxOES",
+		"glGetClipPlanexOES",
+		"glGetFixedvOES",
+		"glGetLightxvOES",
+		"glGetMaterialxvOES",
+		"glGetTexEnvxvOES",
+		"glGetTexParameterxvOES",
+		"glLightModelxOES",
+		"glLightModelxvOES",
+		"glLightxOES",
+		"glLightxvOES",
+		"glLineWidthxOES",
+		"glLoadMatrixxOES",
+		"glMaterialxOES",
+		"glMaterialxvOES",
+		"glMultMatrixxOES",
+		"glMultiTexCoord4xOES",
+		"glNormal3xOES",
+		"glOrthoxOES",
+		"glPointParameterxOES",
+		"glPointParameterxvOES",
+		"glPointSizexOES",
+		"glPolygonOffsetxOES",
+		"glRotatexOES",
+		"glSampleCoveragexOES",
+		"glScalexOES",
+		"glTexEnvxOES",
+		"glTexEnvxvOES",
+		"glTexParameterxOES",
+		"glTexParameterxvOES",
+		"glTranslatexOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getImgMultisampledRenderToTextureFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glRenderbufferStorageMultisampleIMG",
+		"glFramebufferTexture2DMultisampleIMG",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesGetProgramBinaryFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glGetProgramBinaryOES",
+		"glProgramBinaryOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAppleFramebufferMultisampleFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glRenderbufferStorageMultisampleAPPLE",
+		"glResolveMultisampleFramebufferAPPLE",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAppleCopyTextureLevelsFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glCopyTextureLevelsAPPLE",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtMultiviewDrawBuffersFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glReadBufferIndexedEXT",
+		"glDrawBuffersIndexedEXT",
+		"glGetIntegeri_vEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesBlendFuncSeparateFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glBlendFuncSeparateOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getQcomExtendedGetFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glExtGetTexturesQCOM",
+		"glExtGetBuffersQCOM",
+		"glExtGetRenderbuffersQCOM",
+		"glExtGetFramebuffersQCOM",
+		"glExtGetTexLevelParameterivQCOM",
+		"glExtTexObjectStateOverrideiQCOM",
+		"glExtGetTexSubImageQCOM",
+		"glExtGetBufferPointervQCOM",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvFramebufferBlitFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glBlitFramebufferNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAndroidBlobCacheFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglSetBlobCacheFuncsANDROID",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAngleFramebufferBlitFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glBlitFramebufferANGLE",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesTexture3DFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glTexImage3DOES",
+		"glTexSubImage3DOES",
+		"glCopyTexSubImage3DOES",
+		"glCompressedTexImage3DOES",
+		"glCompressedTexSubImage3DOES",
+		"glFramebufferTexture3DOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getKhrDebugFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glDebugMessageControlKHR",
+		"glDebugMessageInsertKHR",
+		"glDebugMessageCallbackKHR",
+		"glGetDebugMessageLogKHR",
+		"glPushDebugGroupKHR",
+		"glPopDebugGroupKHR",
+		"glObjectLabelKHR",
+		"glGetObjectLabelKHR",
+		"glObjectPtrLabelKHR",
+		"glGetObjectPtrLabelKHR",
+		"glGetPointervKHR",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getQcomAlphaTestFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glAlphaFuncQCOM",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getHiClientpixmapFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglCreatePixmapSurfaceHI",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getKhrStreamFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglCreateStreamKHR",
+		"eglDestroyStreamKHR",
+		"eglStreamAttribKHR",
+		"eglQueryStreamKHR",
+		"eglQueryStreamu64KHR",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getOesDrawTextureFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glDrawTexsOES",
+		"glDrawTexiOES",
+		"glDrawTexxOES",
+		"glDrawTexsvOES",
+		"glDrawTexivOES",
+		"glDrawTexxvOES",
+		"glDrawTexfOES",
+		"glDrawTexfvOES",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtOcclusionQueryBooleanFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glGenQueriesEXT",
+		"glDeleteQueriesEXT",
+		"glIsQueryEXT",
+		"glBeginQueryEXT",
+		"glEndQueryEXT",
+		"glGetQueryivEXT",
+		"glGetQueryObjectuivEXT",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAngleQuerySurfacePointerFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglQuerySurfacePointerANGLE",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvReadBufferFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glReadBufferNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvInstancedArraysFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glVertexAttribDivisorNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAngleInstancedArraysFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glDrawArraysInstancedANGLE",
+		"glDrawElementsInstancedANGLE",
+		"glVertexAttribDivisorANGLE",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAmdPerformanceMonitorFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glGetPerfMonitorGroupsAMD",
+		"glGetPerfMonitorCountersAMD",
+		"glGetPerfMonitorGroupStringAMD",
+		"glGetPerfMonitorCounterStringAMD",
+		"glGetPerfMonitorCounterInfoAMD",
+		"glGenPerfMonitorsAMD",
+		"glDeletePerfMonitorsAMD",
+		"glSelectPerfMonitorCountersAMD",
+		"glBeginPerfMonitorAMD",
+		"glEndPerfMonitorAMD",
+		"glGetPerfMonitorCounterDataAMD",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAndroidNativeFenceSyncFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglDupNativeFenceFDANDROID",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getAppleSyncFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glFenceSyncAPPLE",
+		"glIsSyncAPPLE",
+		"glDeleteSyncAPPLE",
+		"glClientWaitSyncAPPLE",
+		"glWaitSyncAPPLE",
+		"glGetInteger64vAPPLE",
+		"glGetSyncivAPPLE",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getNvSyncFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"eglCreateFenceSyncNV",
+		"eglDestroySyncNV",
+		"eglFenceNV",
+		"eglClientWaitSyncNV",
+		"eglSignalSyncNV",
+		"eglGetSyncAttribNV",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getQcomDriverControlFuncStrs (void)
+{
+	static const char* funcs[] =
+	{
+		"glGetDriverControlsQCOM",
+		"glGetDriverControlStringQCOM",
+		"glEnableDriverControlQCOM",
+		"glDisableDriverControlQCOM",
+		DE_NULL
+	};
+
+	return funcs;
+}
+
+const char** getExtensionFuncStrs (const std::string& extensionName)
+{
+	if (extensionName == "EGL_KHR_lock_surface")
+		return getKhrLockSurfaceFuncStrs();
+	else if (extensionName == "GL_OES_single_precision")
+		return getOesSinglePrecisionFuncStrs();
+	else if (extensionName == "GL_QCOM_tiled_rendering")
+		return getQcomTiledRenderingFuncStrs();
+	else if (extensionName == "EGL_NV_native_query")
+		return getNvNativeQueryFuncStrs();
+	else if (extensionName == "EGL_MESA_drm_image")
+		return getMesaDrmImageFuncStrs();
+	else if (extensionName == "GL_NV_fence")
+		return getNvFenceFuncStrs();
+	else if (extensionName == "GL_OES_blend_subtract")
+		return getOesBlendSubtractFuncStrs();
+	else if (extensionName == "EGL_KHR_stream_consumer_gltexture")
+		return getKhrStreamConsumerGltextureFuncStrs();
+	else if (extensionName == "GL_QCOM_extended_get2")
+		return getQcomExtendedGet2FuncStrs();
+	else if (extensionName == "GL_EXT_multi_draw_arrays")
+		return getExtMultiDrawArraysFuncStrs();
+	else if (extensionName == "GL_OES_matrix_palette")
+		return getOesMatrixPaletteFuncStrs();
+	else if (extensionName == "GL_EXT_separate_shader_objects")
+		return getExtSeparateShaderObjectsFuncStrs();
+	else if (extensionName == "GL_ANGLE_translated_shader_source")
+		return getAngleTranslatedShaderSourceFuncStrs();
+	else if (extensionName == "GL_NV_framebuffer_multisample")
+		return getNvFramebufferMultisampleFuncStrs();
+	else if (extensionName == "GL_NV_draw_buffers")
+		return getNvDrawBuffersFuncStrs();
+	else if (extensionName == "GL_OES_EGL_image")
+		return getOesEglImageFuncStrs();
+	else if (extensionName == "EGL_KHR_wait_sync")
+		return getKhrWaitSyncFuncStrs();
+	else if (extensionName == "EGL_NV_system_time")
+		return getNvSystemTimeFuncStrs();
+	else if (extensionName == "GL_EXT_robustness")
+		return getExtRobustnessFuncStrs();
+	else if (extensionName == "EGL_KHR_image")
+		return getKhrImageFuncStrs();
+	else if (extensionName == "GL_NV_draw_instanced")
+		return getNvDrawInstancedFuncStrs();
+	else if (extensionName == "GL_IMG_user_clip_plane")
+		return getImgUserClipPlaneFuncStrs();
+	else if (extensionName == "GL_OES_query_matrix")
+		return getOesQueryMatrixFuncStrs();
+	else if (extensionName == "GL_NV_coverage_sample")
+		return getNvCoverageSampleFuncStrs();
+	else if (extensionName == "GL_EXT_disjoint_timer_query")
+		return getExtDisjointTimerQueryFuncStrs();
+	else if (extensionName == "GL_OES_vertex_array_object")
+		return getOesVertexArrayObjectFuncStrs();
+	else if (extensionName == "EGL_KHR_reusable_sync")
+		return getKhrReusableSyncFuncStrs();
+	else if (extensionName == "GL_EXT_debug_label")
+		return getExtDebugLabelFuncStrs();
+	else if (extensionName == "GL_EXT_map_buffer_range")
+		return getExtMapBufferRangeFuncStrs();
+	else if (extensionName == "GL_ANGLE_framebuffer_multisample")
+		return getAngleFramebufferMultisampleFuncStrs();
+	else if (extensionName == "EGL_KHR_stream_cross_process_fd")
+		return getKhrStreamCrossProcessFdFuncStrs();
+	else if (extensionName == "GL_EXT_texture_storage")
+		return getExtTextureStorageFuncStrs();
+	else if (extensionName == "EGL_KHR_stream_fifo")
+		return getKhrStreamFifoFuncStrs();
+	else if (extensionName == "GL_OES_mapbuffer")
+		return getOesMapbufferFuncStrs();
+	else if (extensionName == "EGL_NV_post_sub_buffer")
+		return getNvPostSubBufferFuncStrs();
+	else if (extensionName == "GL_OES_texture_cube_map")
+		return getOesTextureCubeMapFuncStrs();
+	else if (extensionName == "EGL_KHR_stream_producer_eglsurface")
+		return getKhrStreamProducerEglsurfaceFuncStrs();
+	else if (extensionName == "GL_OES_blend_equation_separate")
+		return getOesBlendEquationSeparateFuncStrs();
+	else if (extensionName == "GL_EXT_draw_buffers")
+		return getExtDrawBuffersFuncStrs();
+	else if (extensionName == "GL_OES_framebuffer_object")
+		return getOesFramebufferObjectFuncStrs();
+	else if (extensionName == "GL_EXT_debug_marker")
+		return getExtDebugMarkerFuncStrs();
+	else if (extensionName == "GL_EXT_multisampled_render_to_texture")
+		return getExtMultisampledRenderToTextureFuncStrs();
+	else if (extensionName == "GL_EXT_discard_framebuffer")
+		return getExtDiscardFramebufferFuncStrs();
+	else if (extensionName == "GL_OES_fixed_point")
+		return getOesFixedPointFuncStrs();
+	else if (extensionName == "GL_IMG_multisampled_render_to_texture")
+		return getImgMultisampledRenderToTextureFuncStrs();
+	else if (extensionName == "GL_OES_get_program_binary")
+		return getOesGetProgramBinaryFuncStrs();
+	else if (extensionName == "GL_APPLE_framebuffer_multisample")
+		return getAppleFramebufferMultisampleFuncStrs();
+	else if (extensionName == "GL_APPLE_copy_texture_levels")
+		return getAppleCopyTextureLevelsFuncStrs();
+	else if (extensionName == "GL_EXT_multiview_draw_buffers")
+		return getExtMultiviewDrawBuffersFuncStrs();
+	else if (extensionName == "GL_OES_blend_func_separate")
+		return getOesBlendFuncSeparateFuncStrs();
+	else if (extensionName == "GL_QCOM_extended_get")
+		return getQcomExtendedGetFuncStrs();
+	else if (extensionName == "GL_NV_framebuffer_blit")
+		return getNvFramebufferBlitFuncStrs();
+	else if (extensionName == "EGL_ANDROID_blob_cache")
+		return getAndroidBlobCacheFuncStrs();
+	else if (extensionName == "GL_ANGLE_framebuffer_blit")
+		return getAngleFramebufferBlitFuncStrs();
+	else if (extensionName == "GL_OES_texture_3D")
+		return getOesTexture3DFuncStrs();
+	else if (extensionName == "GL_KHR_debug")
+		return getKhrDebugFuncStrs();
+	else if (extensionName == "GL_QCOM_alpha_test")
+		return getQcomAlphaTestFuncStrs();
+	else if (extensionName == "EGL_HI_clientpixmap")
+		return getHiClientpixmapFuncStrs();
+	else if (extensionName == "EGL_KHR_stream")
+		return getKhrStreamFuncStrs();
+	else if (extensionName == "GL_OES_draw_texture")
+		return getOesDrawTextureFuncStrs();
+	else if (extensionName == "GL_EXT_occlusion_query_boolean")
+		return getExtOcclusionQueryBooleanFuncStrs();
+	else if (extensionName == "EGL_ANGLE_query_surface_pointer")
+		return getAngleQuerySurfacePointerFuncStrs();
+	else if (extensionName == "GL_NV_read_buffer")
+		return getNvReadBufferFuncStrs();
+	else if (extensionName == "GL_NV_instanced_arrays")
+		return getNvInstancedArraysFuncStrs();
+	else if (extensionName == "GL_ANGLE_instanced_arrays")
+		return getAngleInstancedArraysFuncStrs();
+	else if (extensionName == "GL_AMD_performance_monitor")
+		return getAmdPerformanceMonitorFuncStrs();
+	else if (extensionName == "EGL_ANDROID_native_fence_sync")
+		return getAndroidNativeFenceSyncFuncStrs();
+	else if (extensionName == "GL_APPLE_sync")
+		return getAppleSyncFuncStrs();
+	else if (extensionName == "EGL_NV_sync")
+		return getNvSyncFuncStrs();
+	else if (extensionName == "GL_QCOM_driver_control")
+		return getQcomDriverControlFuncStrs();
+	else
+		return 0;
+}
+
+const char** getExtensionStrs (void)
+{
+	static const char* extensions[] =
+	{
+		"EGL_KHR_config_attribs",
+		"EGL_KHR_lock_surface",
+		"EGL_KHR_image",
+		"EGL_KHR_vg_parent_image",
+		"EGL_KHR_gl_texture_2D_image",
+		"EGL_KHR_gl_texture_cubemap_image",
+		"EGL_KHR_gl_texture_3D_image",
+		"EGL_KHR_gl_renderbuffer_image",
+		"EGL_KHR_reusable_sync",
+		"EGL_KHR_image_base",
+		"EGL_KHR_image_pixmap",
+		"EGL_IMG_context_priority",
+		"EGL_KHR_lock_surface2",
+		"EGL_NV_coverage_sample",
+		"EGL_NV_depth_nonlinear",
+		"EGL_NV_sync",
+		"EGL_KHR_fence_sync",
+		"EGL_HI_clientpixmap",
+		"EGL_HI_colorformats",
+		"EGL_MESA_drm_image",
+		"EGL_NV_post_sub_buffer",
+		"EGL_ANGLE_query_surface_pointer",
+		"EGL_ANGLE_surface_d3d_texture_2d_share_handle",
+		"EGL_NV_coverage_sample_resolve",
+		"EGL_NV_system_time",
+		"EGL_KHR_stream",
+		"EGL_KHR_stream_consumer_gltexture",
+		"EGL_KHR_stream_producer_eglsurface",
+		"EGL_KHR_stream_producer_aldatalocator",
+		"EGL_KHR_stream_fifo",
+		"EGL_EXT_create_context_robustness",
+		"EGL_ANGLE_d3d_share_handle_client_buffer",
+		"EGL_KHR_create_context",
+		"EGL_KHR_surfaceless_context",
+		"EGL_KHR_stream_cross_process_fd",
+		"EGL_EXT_multiview_window",
+		"EGL_KHR_wait_sync",
+		"EGL_NV_post_convert_rounding",
+		"EGL_NV_native_query",
+		"EGL_NV_3dvision_surface",
+		"EGL_ANDROID_framebuffer_target",
+		"EGL_ANDROID_blob_cache",
+		"EGL_ANDROID_image_native_buffer",
+		"EGL_ANDROID_native_fence_sync",
+		"EGL_ANDROID_recordable",
+		"EGL_EXT_buffer_age",
+		"EGL_EXT_image_dma_buf_import",
+		"EGL_ARM_pixmap_multisample_discard",
+		"GL_APIENTRYP",
+		"GL_OES_blend_equation_separate",
+		"GL_OES_blend_func_separate",
+		"GL_OES_blend_subtract",
+		"GL_OES_compressed_ETC1_RGB8_texture",
+		"GL_OES_depth24",
+		"GL_OES_depth32",
+		"GL_OES_draw_texture",
+		"GL_OES_EGL_image",
+		"GL_OES_EGL_image_external",
+		"GL_OES_element_index_uint",
+		"GL_OES_fixed_point",
+		"GL_OES_framebuffer_object",
+		"GL_OES_mapbuffer",
+		"GL_OES_matrix_get",
+		"GL_OES_matrix_palette",
+		"GL_OES_packed_depth_stencil",
+		"GL_OES_rgb8_rgba8",
+		"GL_OES_stencil1",
+		"GL_OES_stencil4",
+		"GL_OES_stencil8",
+		"GL_OES_stencil_wrap",
+		"GL_OES_texture_cube_map",
+		"GL_OES_texture_mirrored_repeat",
+		"GL_OES_vertex_array_object",
+		"GL_AMD_compressed_3DC_texture",
+		"GL_AMD_compressed_ATC_texture",
+		"GL_APPLE_framebuffer_multisample",
+		"GL_APPLE_sync",
+		"GL_APPLE_texture_format_BGRA8888",
+		"GL_APPLE_texture_max_level",
+		"GL_EXT_blend_minmax",
+		"GL_EXT_discard_framebuffer",
+		"GL_EXT_map_buffer_range",
+		"GL_EXT_multisampled_render_to_texture",
+		"GL_EXT_read_format_bgra",
+		"GL_EXT_robustness",
+		"GL_EXT_sRGB",
+		"GL_EXT_texture_compression_dxt1",
+		"GL_EXT_texture_filter_anisotropic",
+		"GL_EXT_texture_format_BGRA8888",
+		"GL_EXT_texture_lod_bias",
+		"GL_EXT_texture_storage",
+		"GL_IMG_read_format",
+		"GL_IMG_texture_compression_pvrtc",
+		"GL_IMG_texture_env_enhanced_fixed_function",
+		"GL_IMG_user_clip_plane",
+		"GL_IMG_multisampled_render_to_texture",
+		"GL_NV_fence",
+		"GL_QCOM_extended_get",
+		"GL_QCOM_perfmon_global_mode",
+		"GL_QCOM_writeonly_rendering",
+		"GL_QCOM_tiled_rendering",
+		"GL_OES_byte_coordinates",
+		"GL_OES_extended_matrix_palette",
+		"GL_OES_fbo_render_mipmap",
+		"GL_OES_required_internalformat",
+		"GL_OES_query_matrix",
+		"GL_OES_single_precision",
+		"GL_OES_texture_env_crossbar",
+		"GL_APPLE_copy_texture_levels",
+		"GL_APPLE_texture_2D_limited_npot",
+		"GL_ARM_rgba8",
+		"GL_EXT_multi_draw_arrays",
+		"GL_QCOM_driver_control",
+		"GL_QCOM_extended_get2",
+		"GL_OES_compressed_paletted_texture",
+		"GL_OES_get_program_binary",
+		"GL_OES_standard_derivatives",
+		"GL_OES_surfaceless_context",
+		"GL_OES_texture_3D",
+		"GL_OES_texture_half_float",
+		"GL_OES_vertex_type_10_10_10_2",
+		"GL_KHR_debug",
+		"GL_KHR_texture_compression_astc_ldr",
+		"GL_AMD_performance_monitor",
+		"GL_AMD_program_binary_Z400",
+		"GL_ANGLE_depth_texture",
+		"GL_ANGLE_framebuffer_blit",
+		"GL_ANGLE_framebuffer_multisample",
+		"GL_ANGLE_instanced_arrays",
+		"GL_ANGLE_pack_reverse_row_order",
+		"GL_ANGLE_program_binary",
+		"GL_ANGLE_texture_compression_dxt3",
+		"GL_ANGLE_texture_compression_dxt5",
+		"GL_ANGLE_texture_usage",
+		"GL_ANGLE_translated_shader_source",
+		"GL_APPLE_rgb_422",
+		"GL_ARM_mali_program_binary",
+		"GL_ARM_mali_shader_binary",
+		"GL_EXT_color_buffer_half_float",
+		"GL_EXT_debug_label",
+		"GL_EXT_disjoint_timer_query",
+		"GL_EXT_draw_buffers",
+		"GL_EXT_multiview_draw_buffers",
+		"GL_EXT_occlusion_query_boolean",
+		"GL_EXT_separate_shader_objects",
+		"GL_EXT_shader_framebuffer_fetch",
+		"GL_EXT_shadow_samplers",
+		"GL_EXT_texture_rg",
+		"GL_EXT_texture_type_2_10_10_10_REV",
+		"GL_EXT_unpack_subimage",
+		"GL_DMP_shader_binary",
+		"GL_FJ_shader_binary_GCCSO",
+		"GL_IMG_program_binary",
+		"GL_IMG_shader_binary",
+		"GL_IMG_texture_compression_pvrtc2",
+		"GL_NV_coverage_sample",
+		"GL_NV_depth_nonlinear",
+		"GL_NV_draw_buffers",
+		"GL_NV_fbo_color_attachments",
+		"GL_NV_framebuffer_blit",
+		"GL_NV_framebuffer_multisample",
+		"GL_NV_instanced_arrays",
+		"GL_NV_read_buffer",
+		"GL_NV_shadow_samplers_array",
+		"GL_NV_shadow_samplers_cube",
+		"GL_NV_sRGB_formats",
+		"GL_NV_texture_border_clamp",
+		"GL_QCOM_alpha_test",
+		"GL_QCOM_binning_control",
+		"GL_VIV_shader_binary",
+		"GL_OES_depth_texture",
+		"GL_OES_fragment_precision_high",
+		"GL_OES_texture_float",
+		"GL_OES_texture_float_linear",
+		"GL_OES_texture_half_float_linear",
+		"GL_OES_texture_npot",
+		"GL_OES_vertex_half_float",
+		"GL_EXT_debug_marker",
+		"GL_EXT_shader_texture_lod",
+		"GL_NV_draw_instanced",
+		"GL_NV_generate_mipmap_sRGB",
+		"GL_NV_read_buffer_front",
+		"GL_NV_read_depth",
+		"GL_NV_read_depth_stencil",
+		"GL_NV_read_stencil",
+		"GL_NV_texture_compression_s3tc_update",
+		"GL_NV_texture_npot_2D_mipmap",
+		DE_NULL
+	};
+
+	return extensions;
+}
diff --git a/modules/egl/teglImageFormatTests.cpp b/modules/egl/teglImageFormatTests.cpp
new file mode 100644
index 0000000..b00e327
--- /dev/null
+++ b/modules/egl/teglImageFormatTests.cpp
@@ -0,0 +1,2130 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL image tests.
+ *//*--------------------------------------------------------------------*/
+#include "teglImageFormatTests.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuCommandLine.hpp"
+
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluConfigFilter.hpp"
+#include "egluUtil.hpp"
+
+#include "gluTexture.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <EGL/eglext.h>
+
+#include <vector>
+#include <string>
+#include <set>
+
+using std::vector;
+using std::set;
+using std::string;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace
+{
+
+// \todo [2013-04-09 pyry] Use glu::Program
+class Program
+{
+public:
+	Program (const char* vertexSource, const char* fragmentSource)
+		: m_program			(0)
+		, m_vertexShader	(0)
+		, m_fragmentShader	(0)
+		, m_isOk			(false)
+	{
+		m_program			= glCreateProgram();
+		m_vertexShader		= glCreateShader(GL_VERTEX_SHADER);
+		m_fragmentShader	= glCreateShader(GL_FRAGMENT_SHADER);
+
+		try
+		{
+			bool	vertexCompileOk		= false;
+			bool	fragmentCompileOk	= false;
+			bool	linkOk				= false;
+
+			for (int ndx = 0; ndx < 2; ndx++)
+			{
+				const char*		source			= ndx ? fragmentSource		: vertexSource;
+				const deUint32	shader			= ndx ? m_fragmentShader	: m_vertexShader;
+				int				compileStatus	= 0;
+				bool&			compileOk		= ndx ? fragmentCompileOk	: vertexCompileOk;
+
+				glShaderSource(shader, 1, &source, DE_NULL);
+				glCompileShader(shader);
+				glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
+
+				compileOk = (compileStatus == GL_TRUE);
+			}
+
+			if (vertexCompileOk && fragmentCompileOk)
+			{
+				int linkStatus = 0;
+
+				glAttachShader(m_program, m_vertexShader);
+				glAttachShader(m_program, m_fragmentShader);
+				glLinkProgram(m_program);
+				glGetProgramiv(m_program, GL_LINK_STATUS, &linkStatus);
+
+				linkOk = (linkStatus == GL_TRUE);
+			}
+
+			m_isOk = linkOk;
+		}
+		catch (...)
+		{
+			glDeleteShader(m_vertexShader);
+			glDeleteShader(m_fragmentShader);
+			glDeleteProgram(m_program);
+			throw;
+		}
+	}
+
+	~Program (void)
+	{
+		glDeleteShader(m_vertexShader);
+		glDeleteShader(m_fragmentShader);
+		glDeleteProgram(m_program);
+	}
+
+	bool			isOk			(void) const { return m_isOk;	}
+	deUint32		getProgram		(void) const {return m_program;	}
+
+private:
+	deUint32		m_program;
+	deUint32		m_vertexShader;
+	deUint32		m_fragmentShader;
+	bool			m_isOk;
+};
+
+} // anonymous
+
+namespace Image
+{
+
+class EglExt
+{
+public:
+	EglExt (void)
+	{
+		eglCreateImageKHR						= (PFNEGLCREATEIMAGEKHRPROC)						eglGetProcAddress("eglCreateImageKHR");
+		eglDestroyImageKHR						= (PFNEGLDESTROYIMAGEKHRPROC)						eglGetProcAddress("eglDestroyImageKHR");
+
+		glEGLImageTargetTexture2DOES			= (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)				eglGetProcAddress("glEGLImageTargetTexture2DOES");
+		glEGLImageTargetRenderbufferStorageOES	= (PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC)	eglGetProcAddress("glEGLImageTargetRenderbufferStorageOES");
+	}
+
+	PFNEGLCREATEIMAGEKHRPROC						eglCreateImageKHR;
+	PFNEGLDESTROYIMAGEKHRPROC						eglDestroyImageKHR;
+
+	PFNGLEGLIMAGETARGETTEXTURE2DOESPROC				glEGLImageTargetTexture2DOES;
+	PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC	glEGLImageTargetRenderbufferStorageOES;
+
+private:
+};
+
+struct TestSpec
+{
+	std::string name;
+	std::string desc;
+
+	enum ApiContext
+	{
+		API_GLES2 = 0,
+		//API_VG
+		//API_GLES1
+
+		API_LAST
+	};
+
+	struct Operation
+	{
+		enum Type
+		{
+				TYPE_CREATE = 0,
+				TYPE_RENDER,
+				TYPE_MODIFY,
+
+				TYPE_LAST
+		};
+
+		ApiContext	requiredApi;
+		int			apiIndex;
+		Type		type;
+		int			operationIndex;
+	};
+
+	vector<ApiContext>	contexts;
+	vector<Operation>	operations;
+
+};
+
+class ImageApi
+{
+public:
+							ImageApi				(int contextId, tcu::TestLog& log, tcu::egl::Display& display, tcu::egl::Surface* surface);
+	virtual 				~ImageApi				(void) {}
+
+	virtual EGLImageKHR		create					(int operationNdx, tcu::Texture2D& ref) = 0;
+	virtual bool			render					(int operationNdx, EGLImageKHR img, const tcu::Texture2D& reference) = 0;
+	virtual void			modify					(int operationNdx, EGLImageKHR img, tcu::Texture2D& reference) = 0;
+
+	virtual void			checkRequiredExtensions	(set<string>& extensions, TestSpec::Operation::Type type, int operationNdx) = 0;
+
+protected:
+	int						m_contextId;
+	tcu::TestLog&			m_log;
+	tcu::egl::Display&		m_display;
+	tcu::egl::Surface*		m_surface;
+};
+
+ImageApi::ImageApi (int contextId, tcu::TestLog& log, tcu::egl::Display& display, tcu::egl::Surface* surface)
+	: m_contextId		(contextId)
+	, m_log				(log)
+	, m_display			(display)
+	, m_surface			(surface)
+{
+}
+
+class GLES2ImageApi : public ImageApi
+{
+public:
+	enum Create
+	{
+		CREATE_TEXTURE2D_RGB8 = 0,
+		CREATE_TEXTURE2D_RGB565,
+		CREATE_TEXTURE2D_RGBA8,
+		CREATE_TEXTURE2D_RGBA5_A1,
+		CREATE_TEXTURE2D_RGBA4,
+
+		CREATE_CUBE_MAP_POSITIVE_X_RGBA8,
+		CREATE_CUBE_MAP_NEGATIVE_X_RGBA8,
+		CREATE_CUBE_MAP_POSITIVE_Y_RGBA8,
+		CREATE_CUBE_MAP_NEGATIVE_Y_RGBA8,
+		CREATE_CUBE_MAP_POSITIVE_Z_RGBA8,
+		CREATE_CUBE_MAP_NEGATIVE_Z_RGBA8,
+
+		CREATE_CUBE_MAP_POSITIVE_X_RGB8,
+		CREATE_CUBE_MAP_NEGATIVE_X_RGB8,
+		CREATE_CUBE_MAP_POSITIVE_Y_RGB8,
+		CREATE_CUBE_MAP_NEGATIVE_Y_RGB8,
+		CREATE_CUBE_MAP_POSITIVE_Z_RGB8,
+		CREATE_CUBE_MAP_NEGATIVE_Z_RGB8,
+
+		CREATE_RENDER_BUFFER_DEPTH16,
+		CREATE_RENDER_BUFFER_RGBA4,
+		CREATE_RENDER_BUFFER_RGB5_A1,
+		CREATE_RENDER_BUFFER_RGB565,
+		CREATE_RENDER_BUFFER_STENCIL,
+
+		CREATE_LAST
+	};
+
+	enum Render
+	{
+		RENDER_TEXTURE2D = 0,
+
+		// \note Not supported
+		RENDER_CUBE_MAP_POSITIVE_X,
+		RENDER_CUBE_MAP_NEGATIVE_X,
+		RENDER_CUBE_MAP_POSITIVE_Y,
+		RENDER_CUBE_MAP_NEGATIVE_Y,
+		RENDER_CUBE_MAP_POSITIVE_Z,
+		RENDER_CUBE_MAP_NEGATIVE_Z,
+
+		RENDER_READ_PIXELS_RENDERBUFFER,
+		RENDER_DEPTHBUFFER,
+
+		RENDER_TRY_ALL,
+
+		RENDER_LAST
+	};
+
+	enum Modify
+	{
+		MODIFY_TEXSUBIMAGE_RGBA8,
+		MODIFY_TEXSUBIMAGE_RGBA5_A1,
+		MODIFY_TEXSUBIMAGE_RGBA4,
+		MODIFY_TEXSUBIMAGE_RGB8,
+		MODIFY_TEXSUBIMAGE_RGB565,
+		MODIFY_RENDERBUFFER_CLEAR_COLOR,
+		MODIFY_RENDERBUFFER_CLEAR_DEPTH,
+		MODIFY_RENDERBUFFER_CLEAR_STENCIL,
+
+		MODIFY_LAST
+	};
+
+					GLES2ImageApi					(int contextId, tcu::TestLog& log, tcu::egl::Display& display, tcu::egl::Surface* surface, EGLConfig config);
+					~GLES2ImageApi					(void);
+
+	EGLImageKHR		create							(int operationNdx, tcu::Texture2D& ref);
+	EGLImageKHR		createTexture2D					(tcu::Texture2D& ref, GLenum target, GLenum format, GLenum type);
+	EGLImageKHR		createRenderBuffer				(tcu::Texture2D& ref, GLenum type);
+
+	bool			render							(int operationNdx, EGLImageKHR img, const tcu::Texture2D& reference);
+	bool			renderTexture2D					(EGLImageKHR img, const tcu::Texture2D& reference);
+	bool			renderDepth						(EGLImageKHR img, const tcu::Texture2D& reference);
+	bool			renderReadPixelsRenderBuffer	(EGLImageKHR img, const tcu::Texture2D& reference);
+	bool			renderTryAll					(EGLImageKHR img, const tcu::Texture2D& reference);
+
+	// \note Not supported
+	bool			renderCubeMap					(EGLImageKHR img, const tcu::Surface& reference, GLenum face);
+
+	void			modify							(int operationNdx, EGLImageKHR img, tcu::Texture2D& reference);
+	void			modifyTexSubImage				(EGLImageKHR img, tcu::Texture2D& reference, GLenum format, GLenum type);
+	void			modifyRenderbufferClearColor	(EGLImageKHR img, tcu::Texture2D& reference);
+	void			modifyRenderbufferClearDepth	(EGLImageKHR img, tcu::Texture2D& reference);
+	void			modifyRenderbufferClearStencil	(EGLImageKHR img, tcu::Texture2D& reference);
+
+	void			checkRequiredExtensions			(set<string>& extensions, TestSpec::Operation::Type type, int operationNdx);
+
+private:
+	tcu::egl::Context*	m_context;
+	EglExt				m_eglExt;
+};
+
+GLES2ImageApi::GLES2ImageApi (int contextId, tcu::TestLog& log, tcu::egl::Display& display, tcu::egl::Surface* surface, EGLConfig config)
+	: ImageApi	(contextId, log, display, surface)
+	, m_context	(DE_NULL)
+{
+	EGLint attriblist[] =
+	{
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+	EGLint configId = -1;
+	TCU_CHECK_EGL_CALL(eglGetConfigAttrib(m_display.getEGLDisplay(), config, EGL_CONFIG_ID, &configId));
+	m_log << tcu::TestLog::Message << "Creating gles2 context with config id: " << configId << " context: " << m_contextId << tcu::TestLog::EndMessage;
+	m_context = new tcu::egl::Context(m_display, config, attriblist, EGL_OPENGL_ES_API);
+	TCU_CHECK_EGL_MSG("Failed to create GLES2 context");
+
+	m_context->makeCurrent(*m_surface, *m_surface);
+	TCU_CHECK_EGL_MSG("Failed to make context current");
+}
+
+GLES2ImageApi::~GLES2ImageApi (void)
+{
+	delete m_context;
+}
+
+EGLImageKHR GLES2ImageApi::create (int operationNdx, tcu::Texture2D& ref)
+{
+	m_context->makeCurrent(*m_surface, *m_surface);
+	EGLImageKHR	img = EGL_NO_IMAGE_KHR;
+	switch (operationNdx)
+	{
+		case CREATE_TEXTURE2D_RGB8:				img = createTexture2D(ref, GL_TEXTURE_2D, GL_RGB,	GL_UNSIGNED_BYTE);			break;
+		case CREATE_TEXTURE2D_RGB565:			img = createTexture2D(ref, GL_TEXTURE_2D, GL_RGB,	GL_UNSIGNED_SHORT_5_6_5);	break;
+		case CREATE_TEXTURE2D_RGBA8:			img = createTexture2D(ref, GL_TEXTURE_2D, GL_RGBA,	GL_UNSIGNED_BYTE);			break;
+		case CREATE_TEXTURE2D_RGBA4:			img = createTexture2D(ref, GL_TEXTURE_2D, GL_RGBA,	GL_UNSIGNED_SHORT_4_4_4_4);	break;
+		case CREATE_TEXTURE2D_RGBA5_A1:			img = createTexture2D(ref, GL_TEXTURE_2D, GL_RGBA,	GL_UNSIGNED_SHORT_5_5_5_1);	break;
+
+		case CREATE_CUBE_MAP_POSITIVE_X_RGBA8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_RGBA, GL_UNSIGNED_BYTE); break;
+		case CREATE_CUBE_MAP_NEGATIVE_X_RGBA8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_RGBA, GL_UNSIGNED_BYTE); break;
+		case CREATE_CUBE_MAP_POSITIVE_Y_RGBA8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_RGBA, GL_UNSIGNED_BYTE); break;
+		case CREATE_CUBE_MAP_NEGATIVE_Y_RGBA8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_RGBA, GL_UNSIGNED_BYTE); break;
+		case CREATE_CUBE_MAP_POSITIVE_Z_RGBA8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_RGBA, GL_UNSIGNED_BYTE); break;
+		case CREATE_CUBE_MAP_NEGATIVE_Z_RGBA8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, GL_RGBA, GL_UNSIGNED_BYTE); break;
+
+		case CREATE_CUBE_MAP_POSITIVE_X_RGB8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_RGB, GL_UNSIGNED_BYTE); break;
+		case CREATE_CUBE_MAP_NEGATIVE_X_RGB8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_RGB, GL_UNSIGNED_BYTE); break;
+		case CREATE_CUBE_MAP_POSITIVE_Y_RGB8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_RGB, GL_UNSIGNED_BYTE); break;
+		case CREATE_CUBE_MAP_NEGATIVE_Y_RGB8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_RGB, GL_UNSIGNED_BYTE); break;
+		case CREATE_CUBE_MAP_POSITIVE_Z_RGB8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_RGB, GL_UNSIGNED_BYTE); break;
+		case CREATE_CUBE_MAP_NEGATIVE_Z_RGB8:	img = createTexture2D(ref, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, GL_RGB, GL_UNSIGNED_BYTE); break;
+
+		case CREATE_RENDER_BUFFER_DEPTH16:		img = createRenderBuffer(ref, GL_DEPTH_COMPONENT16);	break;
+		case CREATE_RENDER_BUFFER_RGBA4:		img = createRenderBuffer(ref, GL_RGBA4);				break;
+		case CREATE_RENDER_BUFFER_RGB5_A1:		img = createRenderBuffer(ref, GL_RGB5_A1);				break;
+		case CREATE_RENDER_BUFFER_RGB565:		img = createRenderBuffer(ref, GL_RGB565);				break;
+		case CREATE_RENDER_BUFFER_STENCIL:		img = createRenderBuffer(ref, GL_STENCIL_INDEX8);		break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	return img;
+}
+
+namespace
+{
+
+const char* glTargetToString (GLenum target)
+{
+	switch (target)
+	{
+		case GL_TEXTURE_2D: return "GL_TEXTURE_2D";
+		break;
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_X: return "GL_TEXTURE_CUBE_MAP_POSITIVE_X";
+		break;
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: return "GL_TEXTURE_CUBE_MAP_NEGATIVE_X";
+		break;
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: return "GL_TEXTURE_CUBE_MAP_POSITIVE_Y";
+		break;
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: return "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y";
+		break;
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: return "GL_TEXTURE_CUBE_MAP_POSITIVE_Z";
+		break;
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: return "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z";
+		break;
+		default:
+			DE_ASSERT(false);
+		break;
+	};
+	return "";
+}
+
+const char* glFormatToString (GLenum format)
+{
+	switch (format)
+	{
+		case GL_RGB:
+			return "GL_RGB";
+
+		case GL_RGBA:
+			return "GL_RGBA";
+
+		default:
+			DE_ASSERT(false);
+			return "";
+	}
+}
+
+const char* glTypeToString (GLenum type)
+{
+	switch (type)
+	{
+		case GL_UNSIGNED_BYTE:
+			return "GL_UNSIGNED_BYTE";
+
+		case GL_UNSIGNED_SHORT_5_6_5:
+			return "GL_UNSIGNED_SHORT_5_6_5";
+
+		case GL_UNSIGNED_SHORT_4_4_4_4:
+			return "GL_UNSIGNED_SHORT_4_4_4_4";
+
+		case GL_UNSIGNED_SHORT_5_5_5_1:
+			return "GL_UNSIGNED_SHORT_5_5_5_1";
+
+		default:
+			DE_ASSERT(false);
+			return "";
+	}
+}
+
+} // anonymous
+
+EGLImageKHR	GLES2ImageApi::createTexture2D (tcu::Texture2D& reference, GLenum target, GLenum format, GLenum type)
+{
+	tcu::Texture2D src(glu::mapGLTransferFormat(format, type), 64, 64);
+	src.allocLevel(0);
+
+	tcu::fillWithComponentGradients(src.getLevel(0), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+	m_log << tcu::TestLog::Message << "Creating EGLImage from " << glTargetToString(target) << " " << glFormatToString(format) << " " << glTypeToString(type) << " in context: " << m_contextId << tcu::TestLog::EndMessage;
+
+	deUint32 srcTex = 0;
+	glGenTextures(1, &srcTex);
+	TCU_CHECK(srcTex != 0);
+	if (GL_TEXTURE_2D == target)
+	{
+		GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, srcTex));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_2D, 0, format, src.getWidth(), src.getHeight(), 0, format, type, src.getLevel(0).getDataPtr()));
+	}
+	else
+	{
+		GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, srcTex));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+
+		// First fill all faces, required by eglCreateImageKHR
+		GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format, src.getWidth(), src.getHeight(), 0, format, type, 0));
+		GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format, src.getWidth(), src.getHeight(), 0, format, type, 0));
+		GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format, src.getWidth(), src.getHeight(), 0, format, type, 0));
+		GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format, src.getWidth(), src.getHeight(), 0, format, type, 0));
+		GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format, src.getWidth(), src.getHeight(), 0, format, type, 0));
+		GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format, src.getWidth(), src.getHeight(), 0, format, type, 0));
+		GLU_CHECK_CALL(glTexImage2D(target, 0, format, src.getWidth(), src.getHeight(), 0, format, type, src.getLevel(0).getDataPtr()));
+	}
+
+	EGLint attrib[] = {
+		EGL_GL_TEXTURE_LEVEL_KHR, 0,
+		EGL_NONE
+	};
+
+	EGLImageKHR img = EGL_NO_IMAGE_KHR;
+
+	if (GL_TEXTURE_2D == target)
+	{
+		img = m_eglExt.eglCreateImageKHR(m_display.getEGLDisplay(), m_context->getEGLContext(), EGL_GL_TEXTURE_2D_KHR, (EGLClientBuffer)(deUintptr)srcTex, attrib);
+	}
+	else
+	{
+		switch (target)
+		{
+			case GL_TEXTURE_CUBE_MAP_POSITIVE_X: img = m_eglExt.eglCreateImageKHR(m_display.getEGLDisplay(), m_context->getEGLContext(), EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X_KHR, (EGLClientBuffer)(deUintptr)srcTex, attrib); break;
+			case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: img = m_eglExt.eglCreateImageKHR(m_display.getEGLDisplay(), m_context->getEGLContext(), EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X_KHR, (EGLClientBuffer)(deUintptr)srcTex, attrib); break;
+			case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: img = m_eglExt.eglCreateImageKHR(m_display.getEGLDisplay(), m_context->getEGLContext(), EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y_KHR, (EGLClientBuffer)(deUintptr)srcTex, attrib); break;
+			case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: img = m_eglExt.eglCreateImageKHR(m_display.getEGLDisplay(), m_context->getEGLContext(), EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_KHR, (EGLClientBuffer)(deUintptr)srcTex, attrib); break;
+			case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: img = m_eglExt.eglCreateImageKHR(m_display.getEGLDisplay(), m_context->getEGLContext(), EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z_KHR, (EGLClientBuffer)(deUintptr)srcTex, attrib); break;
+			case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: img = m_eglExt.eglCreateImageKHR(m_display.getEGLDisplay(), m_context->getEGLContext(), EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_KHR, (EGLClientBuffer)(deUintptr)srcTex, attrib); break;
+
+			default:
+				DE_ASSERT(false);
+				break;
+		}
+	}
+
+	GLU_CHECK_CALL(glDeleteTextures(1, &srcTex));
+	TCU_CHECK_EGL_MSG("Failed to create EGLImage");
+	TCU_CHECK_MSG(img != EGL_NO_IMAGE_KHR, "Failed to create EGLImage, got EGL_NO_IMAGE_KHR");
+	glBindTexture(GL_TEXTURE_2D, 0);
+
+	reference = src;
+
+	return img;
+}
+
+static std::string glRenderbufferTargetToString (GLenum format)
+{
+	switch (format)
+	{
+		case GL_RGBA4:
+			return "GL_RGBA4";
+			break;
+
+		case GL_RGB5_A1:
+			return "GL_RGB5_A1";
+			break;
+
+		case GL_RGB565:
+			return "GL_RGB565";
+			break;
+
+		case GL_DEPTH_COMPONENT16:
+			return "GL_DEPTH_COMPONENT16";
+			break;
+
+		case GL_STENCIL_INDEX8:
+			return "GL_STENCIL_INDEX8";
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	DE_ASSERT(false);
+	return "";
+}
+
+EGLImageKHR GLES2ImageApi::createRenderBuffer (tcu::Texture2D& ref, GLenum format)
+{
+	m_log << tcu::TestLog::Message << "Creating EGLImage from GL_RENDERBUFFER " << glRenderbufferTargetToString(format) << " " << " in context: " << m_contextId << tcu::TestLog::EndMessage;
+	GLuint renderBuffer = 1;
+
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer));
+	GLU_CHECK_CALL(glRenderbufferStorage(GL_RENDERBUFFER, format, 64, 64));
+
+	GLuint frameBuffer = 1;
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer));
+
+	switch (format)
+	{
+		case GL_STENCIL_INDEX8:
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderBuffer));
+			TCU_CHECK(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+			GLU_CHECK_CALL(glClearStencil(235));
+			GLU_CHECK_CALL(glClear(GL_STENCIL_BUFFER_BIT));
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0));
+			ref = tcu::Texture2D(tcu::TextureFormat(tcu::TextureFormat::I, tcu::TextureFormat::UNORM_INT8),  64, 64);
+			ref.allocLevel(0);
+
+			for (int x = 0; x < 64; x++)
+			{
+				for (int y = 0; y < 64; y++)
+				{
+					ref.getLevel(0).setPixel(tcu::IVec4(235, 235, 235, 235), x, y);
+				}
+			}
+			break;
+
+		case GL_DEPTH_COMPONENT16:
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderBuffer));
+			TCU_CHECK(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+			GLU_CHECK_CALL(glClearDepthf(0.5f));
+			GLU_CHECK_CALL(glClear(GL_DEPTH_BUFFER_BIT));
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0));
+			ref = tcu::Texture2D(tcu::TextureFormat(tcu::TextureFormat::I, tcu::TextureFormat::UNORM_INT16),  64, 64);
+			ref.allocLevel(0);
+
+			for (int x = 0; x < 64; x++)
+			{
+				for (int y = 0; y < 64; y++)
+				{
+					ref.getLevel(0).setPixel(tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f), x, y);
+				}
+			}
+			break;
+
+		case GL_RGBA4:
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer));
+			TCU_CHECK(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+			GLU_CHECK_CALL(glClearColor(0.9f, 0.5f, 0.65f, 1.0f));
+			GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0));
+			ref = tcu::Texture2D(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_SHORT_4444),  64, 64);
+			ref.allocLevel(0);
+
+			for (int x = 0; x < 64; x++)
+			{
+				for (int y = 0; y < 64; y++)
+				{
+					ref.getLevel(0).setPixel(tcu::Vec4(0.9f, 0.5f, 0.65f, 1.0f), x, y);
+				}
+			}
+			break;
+
+		case GL_RGB5_A1:
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer));
+			TCU_CHECK(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+			GLU_CHECK_CALL(glClearColor(0.5f, 0.7f, 0.65f, 1.0f));
+			GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0));
+			ref = tcu::Texture2D(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_SHORT_5551),  64, 64);
+			ref.allocLevel(0);
+
+			for (int x = 0; x < 64; x++)
+			{
+				for (int y = 0; y < 64; y++)
+				{
+					ref.getLevel(0).setPixel(tcu::Vec4(0.5f, 0.7f, 0.65f, 1.0f), x, y);
+				}
+			}
+			break;
+
+		case GL_RGB565:
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer));
+			TCU_CHECK(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+			GLU_CHECK_CALL(glClearColor(0.2f, 0.5f, 0.65f, 1.0f));
+			GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0));
+			ref = tcu::Texture2D(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_SHORT_565),  64, 64);
+			ref.allocLevel(0);
+
+			for (int x = 0; x < 64; x++)
+			{
+				for (int y = 0; y < 64; y++)
+				{
+					ref.getLevel(0).setPixel(tcu::Vec4(0.2f, 0.5f, 0.65f, 1.0f), x, y);
+				}
+			}
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
+	GLU_CHECK_CALL(glDeleteFramebuffers(1, &frameBuffer));
+
+	EGLint attrib[] = {
+		EGL_NONE
+	};
+
+	EGLImageKHR img = m_eglExt.eglCreateImageKHR(m_display.getEGLDisplay(), m_context->getEGLContext(), EGL_GL_RENDERBUFFER_KHR, (EGLClientBuffer)(deUintptr)renderBuffer, attrib);
+
+	GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderBuffer));
+	return img;
+}
+
+bool GLES2ImageApi::render (int operationNdx, EGLImageKHR img, const tcu::Texture2D& reference)
+{
+	m_context->makeCurrent(*m_surface, *m_surface);
+	switch (operationNdx)
+	{
+		case RENDER_TEXTURE2D:
+			return renderTexture2D(img, reference);
+
+		case RENDER_READ_PIXELS_RENDERBUFFER:
+			return renderReadPixelsRenderBuffer(img, reference);
+
+		case RENDER_DEPTHBUFFER:
+			return renderDepth(img, reference);
+
+		case RENDER_TRY_ALL:
+			return renderTryAll(img, reference);
+
+		default:
+			DE_ASSERT(false);
+			break;
+	};
+	return false;
+}
+
+bool GLES2ImageApi::renderTexture2D (EGLImageKHR img, const tcu::Texture2D& reference)
+{
+	glClearColor(0.0, 0.0, 0.0, 0.0);
+	glViewport(0, 0, reference.getWidth(), reference.getHeight());
+	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+	glDisable(GL_DEPTH_TEST);
+
+	m_log << tcu::TestLog::Message << "Rendering EGLImage as GL_TEXTURE_2D in context: " << m_contextId << tcu::TestLog::EndMessage;
+	TCU_CHECK(img != EGL_NO_IMAGE_KHR);
+
+	deUint32 srcTex = 0;
+	glGenTextures(1, &srcTex);
+	TCU_CHECK(srcTex != 0);
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, srcTex));
+	m_eglExt.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)img);
+	GLenum error = glGetError();
+
+	if (error == GL_INVALID_OPERATION)
+	{
+		GLU_CHECK_CALL(glDeleteTextures(1, &srcTex));
+		throw tcu::NotSupportedError("Creating texture2D from EGLImage type not supported", "glEGLImageTargetTexture2DOES", __FILE__, __LINE__);
+	}
+
+	TCU_CHECK(error == GL_NONE);
+
+	TCU_CHECK_EGL_MSG("glEGLImageTargetTexture2DOES() failed");
+
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+
+	const char* vertexShader =
+		"attribute highp vec2 a_coord;\n"
+		"varying mediump vec2 v_texCoord;\n"
+		"void main(void) {\n"
+		"\tv_texCoord = vec2((a_coord.x + 1.0) * 0.5, (a_coord.y + 1.0) * 0.5);\n"
+		"\tgl_Position = vec4(a_coord, -0.1, 1.0);\n"
+		"}\n";
+
+	const char* fragmentShader =
+		"varying mediump vec2 v_texCoord;\n"
+		"uniform sampler2D u_sampler;\n"
+		"void main(void) {\n"
+		"\tmediump vec4 texColor = texture2D(u_sampler, v_texCoord);\n"
+		"\tgl_FragColor = vec4(texColor);\n"
+		"}";
+
+	Program program(vertexShader, fragmentShader);
+	TCU_CHECK(program.isOk());
+
+	GLuint glProgram = program.getProgram();
+	GLU_CHECK_CALL(glUseProgram(glProgram));
+
+	GLuint coordLoc = glGetAttribLocation(glProgram, "a_coord");
+	TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute a_coord");
+
+	GLuint samplerLoc = glGetUniformLocation(glProgram, "u_sampler");
+	TCU_CHECK_MSG((int)samplerLoc != (int)-1, "Couldn't find uniform u_sampler");
+
+	float coords[] =
+	{
+		-1.0, -1.0,
+		1.0, -1.0,
+		1.0,  1.0,
+
+		1.0,  1.0,
+		-1.0,  1.0,
+		-1.0, -1.0
+	};
+
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, srcTex));
+	GLU_CHECK_CALL(glUniform1i(samplerLoc, 0));
+	GLU_CHECK_CALL(glEnableVertexAttribArray(coordLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, coords));
+
+	GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 6));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(coordLoc));
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+	GLU_CHECK_CALL(glDeleteTextures(1, &srcTex));
+
+	tcu::Surface screen(reference.getWidth(), reference.getHeight());
+	glReadPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, screen.getAccess().getDataPtr());
+	GLU_CHECK_MSG("glReadPixels()");
+
+	tcu::Surface referenceScreen(reference.getWidth(), reference.getHeight());
+
+	for (int y = 0; y < referenceScreen.getHeight(); y++)
+	{
+		for (int x = 0; x < referenceScreen.getWidth(); x++)
+		{
+			tcu::Vec4 src = reference.getLevel(0).getPixel(x, y);
+			referenceScreen.setPixel(x, y, tcu::RGBA(src));
+		}
+	}
+
+	float	threshold	= 0.05f;
+	bool	match		= tcu::fuzzyCompare(m_log, "ComparisonResult", "Image comparison result", referenceScreen, screen, threshold, tcu::COMPARE_LOG_RESULT);
+
+	return match;
+}
+
+bool GLES2ImageApi::renderDepth (EGLImageKHR img, const tcu::Texture2D& reference)
+{
+	m_log << tcu::TestLog::Message << "Rendering with depth buffer" << tcu::TestLog::EndMessage;
+
+	deUint32 framebuffer;
+	glGenFramebuffers(1, &framebuffer);
+	TCU_CHECK(framebuffer != (GLuint)-1);
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
+
+	deUint32 renderbufferColor = 0;
+	glGenRenderbuffers(1, &renderbufferColor);
+	TCU_CHECK(renderbufferColor != (GLuint)-1);
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbufferColor));
+	GLU_CHECK_CALL(glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, reference.getWidth(), reference.getHeight()));
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+
+	deUint32 renderbufferDepth = 0;
+	glGenRenderbuffers(1, &renderbufferDepth);
+	TCU_CHECK(renderbufferDepth != (GLuint)-1);
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbufferDepth));
+
+	m_eglExt.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)img);
+
+	GLenum error = glGetError();
+
+	if (error == GL_INVALID_OPERATION)
+	{
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbufferDepth));
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbufferColor));
+		throw tcu::NotSupportedError("Creating renderbuffer from EGLImage type not supported", "glEGLImageTargetRenderbufferStorageOES", __FILE__, __LINE__);
+	}
+
+	TCU_CHECK(error == GL_NONE);
+
+	TCU_CHECK_EGL_MSG("glEGLImageTargetRenderbufferStorageOES() failed");
+
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbufferDepth));
+	GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbufferDepth));
+
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbufferColor));
+	GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbufferColor));
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+
+	GLU_CHECK_CALL(glViewport(0, 0, reference.getWidth(), reference.getHeight()));
+	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbufferDepth));
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbufferColor));
+		throw tcu::NotSupportedError("EGLImage as depth attachment not supported", "", __FILE__, __LINE__);
+	}
+
+	// Render
+	const char* vertexShader =
+		"attribute highp vec2 a_coord;\n"
+		"uniform highp float u_depth;\n"
+		"void main(void) {\n"
+		"\tgl_Position = vec4(a_coord, u_depth, 1.0);\n"
+		"}\n";
+
+	const char* fragmentShader =
+		"uniform mediump vec4 u_color;\n"
+		"void main(void) {\n"
+		"\tgl_FragColor = u_color;\n"
+		"}";
+
+	Program program(vertexShader, fragmentShader);
+	TCU_CHECK(program.isOk());
+
+	GLuint glProgram = program.getProgram();
+	GLU_CHECK_CALL(glUseProgram(glProgram));
+
+	GLuint coordLoc = glGetAttribLocation(glProgram, "a_coord");
+	TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute a_coord");
+
+	GLuint colorLoc = glGetUniformLocation(glProgram, "u_color");
+	TCU_CHECK_MSG((int)colorLoc != (int)-1, "Couldn't find uniform u_color");
+
+	GLuint depthLoc = glGetUniformLocation(glProgram, "u_depth");
+	TCU_CHECK_MSG((int)depthLoc != (int)-1, "Couldn't find uniform u_depth");
+
+	float coords[] =
+	{
+		-1.0, -1.0,
+		1.0, -1.0,
+		1.0,  1.0,
+
+		1.0,  1.0,
+		-1.0,  1.0,
+		-1.0, -1.0
+	};
+
+	float depthLevels[] = {
+		0.1f,
+		0.2f,
+		0.3f,
+		0.4f,
+		0.5f,
+		0.6f,
+		0.7f,
+		0.8f,
+		0.9f,
+		1.0f
+	};
+
+	tcu::Vec4 depthLevelColors[] = {
+		tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f),
+		tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f),
+		tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f),
+
+		tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f),
+		tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+		tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 0.5f, 0.0f, 1.0f),
+		tcu::Vec4(0.5f, 0.5f, 0.0f, 1.0f)
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(depthLevels) == DE_LENGTH_OF_ARRAY(depthLevelColors));
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(coordLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, coords));
+
+	GLU_CHECK_CALL(glEnable(GL_DEPTH_TEST));
+	GLU_CHECK_CALL(glDepthFunc(GL_LESS));
+
+	for (int level = 0; level < DE_LENGTH_OF_ARRAY(depthLevels); level++)
+	{
+		tcu::Vec4 color = depthLevelColors[level];
+		GLU_CHECK_CALL(glUniform4f(colorLoc, color.x(), color.y(), color.z(), color.w()));
+		GLU_CHECK_CALL(glUniform1f(depthLoc, depthLevels[level]));
+		GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 6));
+	}
+
+	GLU_CHECK_CALL(glDisable(GL_DEPTH_TEST));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(coordLoc));
+
+	tcu::Surface screen(reference.getWidth(), reference.getHeight());
+	tcu::Surface referenceScreen(reference.getWidth(), reference.getHeight());
+
+	glReadPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, screen.getAccess().getDataPtr());
+
+	for (int y = 0; y < reference.getHeight(); y++)
+	{
+		for (int x = 0; x < reference.getWidth(); x++)
+		{
+			tcu::RGBA result;
+			for (int level = 0; level < DE_LENGTH_OF_ARRAY(depthLevels); level++)
+			{
+				tcu::Vec4 src = reference.getLevel(0).getPixel(x, y);
+
+				if (src.x() < depthLevels[level])
+				{
+					result = tcu::RGBA((int)(depthLevelColors[level].x() * 255.0f), (int)(depthLevelColors[level].y() * 255.0f), (int)(depthLevelColors[level].z() * 255.0f), (int)(depthLevelColors[level].w() * 255.0f));
+				}
+			}
+
+			referenceScreen.setPixel(x, reference.getHeight(), result);
+		}
+	}
+
+	bool isOk = tcu::pixelThresholdCompare(m_log, "Depth buffer rendering result", "Result from rendering with depth buffer", referenceScreen, screen, tcu::RGBA(1,1,1,1), tcu::COMPARE_LOG_RESULT);
+
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+	GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbufferDepth));
+	GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbufferColor));
+	GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+	GLU_CHECK_CALL(glFinish());
+
+	return isOk;
+}
+
+bool GLES2ImageApi::renderReadPixelsRenderBuffer (EGLImageKHR img, const tcu::Texture2D& reference)
+{
+	m_log << tcu::TestLog::Message << "Reading with ReadPixels from renderbuffer" << tcu::TestLog::EndMessage;
+
+	deUint32 framebuffer;
+	glGenFramebuffers(1, &framebuffer);
+	TCU_CHECK(framebuffer != (GLuint)-1);
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
+
+	deUint32 renderbuffer = 0;
+	glGenRenderbuffers(1, &renderbuffer);
+	TCU_CHECK(renderbuffer != (GLuint)-1);
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer));
+
+	m_eglExt.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)img);
+
+	GLenum error = glGetError();
+
+	if (error == GL_INVALID_OPERATION)
+	{
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+		throw tcu::NotSupportedError("Creating renderbuffer from EGLImage type not supported", "glEGLImageTargetRenderbufferStorageOES", __FILE__, __LINE__);
+	}
+
+	TCU_CHECK(error == GL_NONE);
+
+	TCU_CHECK_EGL_MSG("glEGLImageTargetRenderbufferStorageOES() failed");
+
+	GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer));
+
+	GLU_CHECK_CALL(glViewport(0, 0, reference.getWidth(), reference.getHeight()));
+	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+		throw tcu::NotSupportedError("EGLImage as color attachment not supported", "", __FILE__, __LINE__);
+	}
+
+	tcu::Surface screen(reference.getWidth(), reference.getHeight());
+	tcu::Surface referenceScreen(reference.getWidth(), reference.getHeight());
+
+	glReadPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, screen.getAccess().getDataPtr());
+
+	for (int y = 0; y < reference.getHeight(); y++)
+	{
+		for (int x = 0; x < reference.getWidth(); x++)
+		{
+			tcu::Vec4 src = reference.getLevel(0).getPixel(x, y);
+			referenceScreen.setPixel(x, y, tcu::RGBA(src));
+		}
+	}
+
+	bool isOk = tcu::pixelThresholdCompare(m_log, "Renderbuffer read", "Result from reading renderbuffer", referenceScreen, screen, tcu::RGBA(1,1,1,1), tcu::COMPARE_LOG_RESULT);
+
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+	GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+	GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+	GLU_CHECK_CALL(glFinish());
+
+	return isOk;
+}
+
+bool GLES2ImageApi::renderTryAll (EGLImageKHR img, const tcu::Texture2D& reference)
+{
+	bool isOk = true;
+	bool foundSupportedRendering = false;
+
+	try
+	{
+		if (!renderTexture2D(img, reference))
+			isOk = false;
+
+		foundSupportedRendering = true;
+	}
+	catch (const tcu::NotSupportedError& error)
+	{
+		m_log << tcu::TestLog::Message << error.what() << tcu::TestLog::EndMessage;
+	}
+
+	if (!isOk)
+		return false;
+
+	try
+	{
+		if (!renderReadPixelsRenderBuffer(img, reference))
+			isOk = false;
+
+		foundSupportedRendering = true;
+	}
+	catch (const tcu::NotSupportedError& error)
+	{
+		m_log << tcu::TestLog::Message << error.what() << tcu::TestLog::EndMessage;
+	}
+
+	if (!isOk)
+		return false;
+
+	try
+	{
+		if (!renderDepth(img, reference))
+			isOk = false;
+
+		foundSupportedRendering = true;
+	}
+	catch (const tcu::NotSupportedError& error)
+	{
+		m_log << tcu::TestLog::Message << error.what() << tcu::TestLog::EndMessage;
+	}
+
+	if (!foundSupportedRendering)
+		throw tcu::NotSupportedError("Rendering not supported", "", __FILE__, __LINE__);
+
+	return isOk;
+}
+
+bool GLES2ImageApi::renderCubeMap (EGLImageKHR img, const tcu::Surface& reference, GLenum face)
+{
+	// \note This is not supported by EGLImage
+	DE_ASSERT(false);
+
+	glClearColor(0.5, 0.5, 0.5, 1.0);
+	glViewport(0, 0, reference.getWidth(), reference.getHeight());
+	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+	glDisable(GL_DEPTH_TEST);
+
+	m_log << tcu::TestLog::Message << "Rendering EGLImage as " <<  glTargetToString(face) << " in context: " << m_contextId << tcu::TestLog::EndMessage;
+	DE_ASSERT(img != EGL_NO_IMAGE_KHR);
+
+	deUint32 srcTex = 0;
+	glGenTextures(1, &srcTex);
+	DE_ASSERT(srcTex != 0);
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, srcTex));
+	GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, reference.getWidth(), reference.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
+	GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA, reference.getWidth(), reference.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
+	GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA, reference.getWidth(), reference.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
+	GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA, reference.getWidth(), reference.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
+	GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA, reference.getWidth(), reference.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
+	GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, reference.getWidth(), reference.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
+
+	m_eglExt.glEGLImageTargetTexture2DOES(face, (GLeglImageOES)img);
+	GLenum error = glGetError();
+
+	if (error == GL_INVALID_OPERATION)
+	{
+		GLU_CHECK_CALL(glDeleteTextures(1, &srcTex));
+		throw tcu::NotSupportedError("Creating texture cubemap from EGLImage type not supported", "glEGLImageTargetTexture2DOES", __FILE__, __LINE__);
+	}
+
+	TCU_CHECK(error == GL_NONE);
+
+	TCU_CHECK_EGL_MSG("glEGLImageTargetTexture2DOES() failed");
+
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+	GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+
+	const char* vertexShader =
+		"attribute highp vec3 a_coord;\n"
+		"attribute highp vec3 a_texCoord;\n"
+		"varying mediump vec3 v_texCoord;\n"
+		"void main(void) {\n"
+		"\tv_texCoord = a_texCoord;\n"
+		"\tgl_Position = vec4(a_coord.xy, -0.1, 1.0);\n"
+		"}\n";
+
+	const char* fragmentShader =
+		"varying mediump vec3 v_texCoord;\n"
+		"uniform samplerCube u_sampler;\n"
+		"void main(void) {\n"
+		"\tmediump vec4 texColor = textureCube(u_sampler, v_texCoord);\n"
+		"\tgl_FragColor = vec4(texColor.rgb, 1.0);\n"
+		"}";
+
+	Program program(vertexShader, fragmentShader);
+	DE_ASSERT(program.isOk());
+
+	GLuint glProgram = program.getProgram();
+	GLU_CHECK_CALL(glUseProgram(glProgram));
+
+	GLint coordLoc = glGetAttribLocation(glProgram, "a_coord");
+	DE_ASSERT(coordLoc != -1);
+
+	GLint texCoordLoc = glGetAttribLocation(glProgram, "a_texCoord");
+	DE_ASSERT(texCoordLoc != -1);
+
+	GLint samplerLoc = glGetUniformLocation(glProgram, "u_sampler");
+	DE_ASSERT(samplerLoc != -1);
+
+	float coords[] =
+	{
+		-1.0, -1.0,
+		1.0, -1.0,
+		1.0,  1.0,
+
+		1.0,  1.0,
+		-1.0,  1.0,
+		-1.0, -1.0,
+	};
+
+	float sampleTexCoords[] =
+	{
+		10.0, -1.0, -1.0,
+		10.0,  1.0, -1.0,
+		10.0,  1.0,  1.0,
+
+		10.0,  1.0,  1.0,
+		10.0, -1.0,  1.0,
+		10.0, -1.0, -1.0,
+	};
+
+	vector<float> texCoords;
+	float	sign	= 0.0f;
+	int		dir		= -1;
+
+	switch (face)
+	{
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_X: sign =  1.0; dir = 0; break;
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: sign = -1.0; dir = 0; break;
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: sign =  1.0; dir = 1; break;
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: sign = -1.0; dir = 1; break;
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: sign =  1.0; dir = 2; break;
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: sign = -1.0; dir = 2; break;
+		default:
+				DE_ASSERT(false);
+	}
+
+	for (int i = 0; i < 6; i++)
+	{
+		texCoords.push_back(sign * sampleTexCoords[i*3 + (dir % 3)]);
+		texCoords.push_back(sampleTexCoords[i*3 + ((dir + 1) % 3)]);
+		texCoords.push_back(sampleTexCoords[i*3 + ((dir + 2) % 3)]);
+	}
+
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, srcTex));
+	GLU_CHECK_CALL(glUniform1i(samplerLoc, 0));
+	GLU_CHECK_CALL(glEnableVertexAttribArray(coordLoc));
+	GLU_CHECK_CALL(glEnableVertexAttribArray(texCoordLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, coords));
+	GLU_CHECK_CALL(glVertexAttribPointer(texCoordLoc, 3, GL_FLOAT, GL_FALSE, 0, coords));
+
+	GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 6));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(coordLoc));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(texCoordLoc));
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, 0));
+	GLU_CHECK_CALL(glDeleteTextures(1, &srcTex));
+
+	tcu::Surface screen(reference.getWidth(), reference.getHeight());
+	glReadPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, screen.getAccess().getDataPtr());
+	GLU_CHECK_MSG("glReadPixels()");
+
+	float	threshold	= 0.05f;
+	bool	match		= tcu::fuzzyCompare(m_log, "ComparisonResult", "Image comparison result", reference, screen, threshold, tcu::COMPARE_LOG_RESULT);
+
+	return match;
+}
+
+void GLES2ImageApi::modify (int operationNdx, EGLImageKHR img, tcu::Texture2D& reference)
+{
+	switch (operationNdx)
+	{
+		case MODIFY_TEXSUBIMAGE_RGBA8:
+			modifyTexSubImage(img, reference, GL_RGBA, GL_UNSIGNED_BYTE);
+			break;
+
+		case MODIFY_TEXSUBIMAGE_RGBA5_A1:
+			modifyTexSubImage(img, reference, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1);
+			break;
+
+		case MODIFY_TEXSUBIMAGE_RGBA4:
+			modifyTexSubImage(img, reference, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4);
+			break;
+
+		case MODIFY_TEXSUBIMAGE_RGB8:
+			modifyTexSubImage(img, reference, GL_RGB, GL_UNSIGNED_BYTE);
+			break;
+
+		case MODIFY_TEXSUBIMAGE_RGB565:
+			modifyTexSubImage(img, reference, GL_RGB, GL_UNSIGNED_SHORT_5_6_5);
+			break;
+
+		case MODIFY_RENDERBUFFER_CLEAR_COLOR:
+			modifyRenderbufferClearColor(img, reference);
+			break;
+
+		case MODIFY_RENDERBUFFER_CLEAR_DEPTH:
+			modifyRenderbufferClearDepth(img, reference);
+			break;
+
+		case MODIFY_RENDERBUFFER_CLEAR_STENCIL:
+			modifyRenderbufferClearStencil(img, reference);
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+}
+
+void GLES2ImageApi::modifyTexSubImage (EGLImageKHR img, tcu::Texture2D& reference, GLenum format, GLenum type)
+{
+	m_log << tcu::TestLog::Message << "Modifying EGLImage with glTexSubImage2D" << tcu::TestLog::EndMessage;
+
+	deUint32 srcTex = 0;
+	glGenTextures(1, &srcTex);
+	TCU_CHECK(srcTex != 0);
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, srcTex));
+
+	m_eglExt.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)img);
+
+	GLenum error = glGetError();
+
+	if (error == GL_INVALID_OPERATION)
+	{
+		GLU_CHECK_CALL(glDeleteTextures(1, &srcTex));
+		throw tcu::NotSupportedError("Creating texture2D from EGLImage type not supported", "glEGLImageTargetTexture2DOES", __FILE__, __LINE__);
+	}
+	TCU_CHECK(error == GL_NONE);
+
+	TCU_CHECK_EGL_MSG("glEGLImageTargetTexture2DOES() failed");
+
+	int xOffset = 8;
+	int yOffset = 16;
+
+	tcu::Texture2D src(glu::mapGLTransferFormat(format, type), 16, 16);
+	src.allocLevel(0);
+	tcu::fillWithComponentGradients(src.getLevel(0), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, srcTex));
+	GLU_CHECK_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, src.getWidth(), src.getHeight(), format, type, src.getLevel(0).getDataPtr()));
+
+	for (int x = 0; x < src.getWidth(); x++)
+	{
+		if (x + xOffset >= reference.getWidth())
+			continue;
+
+		for (int y = 0; y < src.getHeight(); y++)
+		{
+			if (y + yOffset >= reference.getHeight())
+				continue;
+
+			reference.getLevel(0).setPixel(src.getLevel(0).getPixel(x, y), x+xOffset, y+yOffset);
+		}
+	}
+
+	GLU_CHECK_CALL(glDeleteTextures(1, &srcTex));
+	GLU_CHECK_CALL(glFinish());
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+}
+
+void GLES2ImageApi::modifyRenderbufferClearColor (EGLImageKHR img, tcu::Texture2D& reference)
+{
+	m_log << tcu::TestLog::Message << "Modifying EGLImage with glClear to renderbuffer" << tcu::TestLog::EndMessage;
+
+	deUint32 framebuffer;
+	glGenFramebuffers(1, &framebuffer);
+	TCU_CHECK(framebuffer != (GLuint)-1);
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
+
+	deUint32 renderbuffer = 0;
+	glGenRenderbuffers(1, &renderbuffer);
+	TCU_CHECK(renderbuffer != (GLuint)-1);
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer));
+
+	m_eglExt.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)img);
+
+	GLenum error = glGetError();
+
+	if (error == GL_INVALID_OPERATION)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+		throw tcu::NotSupportedError("Creating renderbuffer from EGLImage type not supported", "glEGLImageTargetRenderbufferStorageOES", __FILE__, __LINE__);
+	}
+	TCU_CHECK(error == GL_NONE);
+
+	TCU_CHECK_EGL_MSG("glEGLImageTargetRenderbufferStorageOES() failed");
+
+	float red	= 0.3f;
+	float green	= 0.5f;
+	float blue	= 0.3f;
+	float alpha	= 1.0f;
+
+	GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer));
+
+	GLU_CHECK_CALL(glViewport(0, 0, reference.getWidth(), reference.getHeight()));
+	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+		throw tcu::NotSupportedError("EGLImage type as color attachment not supported", "", __FILE__, __LINE__);
+	}
+
+	GLU_CHECK_CALL(glClearColor(red, green, blue, alpha));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	for (int x = 0; x < reference.getWidth(); x++)
+	{
+		for (int y = 0; y < reference.getHeight(); y++)
+		{
+			tcu::Vec4 color = tcu::Vec4(red, green, blue, alpha);
+			reference.getLevel(0).setPixel(color, x, y);
+		}
+	}
+
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+	GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+	GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+	GLU_CHECK_CALL(glFinish());
+}
+
+void GLES2ImageApi::modifyRenderbufferClearDepth (EGLImageKHR img, tcu::Texture2D& reference)
+{
+	m_log << tcu::TestLog::Message << "Modifying EGLImage with glClear to renderbuffer" << tcu::TestLog::EndMessage;
+
+	deUint32 framebuffer;
+	glGenFramebuffers(1, &framebuffer);
+	TCU_CHECK(framebuffer != (GLuint)-1);
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
+
+	deUint32 renderbuffer = 0;
+	glGenRenderbuffers(1, &renderbuffer);
+	TCU_CHECK(renderbuffer != 0);
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer));
+
+	m_eglExt.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)img);
+
+	GLenum error = glGetError();
+
+	if (error == GL_INVALID_OPERATION)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+		throw tcu::NotSupportedError("Creating renderbuffer from EGLImage type not supported", "glEGLImageTargetRenderbufferStorageOES", __FILE__, __LINE__);
+	}
+	TCU_CHECK(error == GL_NONE);
+
+	TCU_CHECK_EGL_MSG("glEGLImageTargetRenderbufferStorageOES() failed");
+
+	float depth = 0.7f;
+
+	GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer));
+
+	GLU_CHECK_CALL(glViewport(0, 0, reference.getWidth(), reference.getHeight()));
+	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+		throw tcu::NotSupportedError("EGLImage type as depth attachment not supported", "", __FILE__, __LINE__);
+	}
+
+	GLU_CHECK_CALL(glClearDepthf(depth));
+	GLU_CHECK_CALL(glClear(GL_DEPTH_BUFFER_BIT));
+
+	for (int x = 0; x < reference.getWidth(); x++)
+	{
+		for (int y = 0; y < reference.getHeight(); y++)
+		{
+			tcu::Vec4 color = tcu::Vec4(depth, depth, depth, depth);
+			reference.getLevel(0).setPixel(color, x, y);
+		}
+	}
+
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+	GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+	GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+	GLU_CHECK_CALL(glFinish());
+}
+
+void GLES2ImageApi::modifyRenderbufferClearStencil (EGLImageKHR img, tcu::Texture2D& reference)
+{
+	m_log << tcu::TestLog::Message << "Modifying EGLImage with glClear to renderbuffer" << tcu::TestLog::EndMessage;
+
+	deUint32 framebuffer;
+	glGenFramebuffers(1, &framebuffer);
+	TCU_CHECK(framebuffer != (GLuint)-1);
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
+
+	deUint32 renderbuffer = 0;
+	glGenRenderbuffers(1, &renderbuffer);
+	TCU_CHECK(renderbuffer != 0);
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer));
+
+	m_eglExt.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)img);
+	GLenum error = glGetError();
+
+	if (error == GL_INVALID_OPERATION)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+		throw tcu::NotSupportedError("Creating renderbuffer from EGLImage type not supported", "glEGLImageTargetRenderbufferStorageOES", __FILE__, __LINE__);
+	}
+	TCU_CHECK(error == GL_NONE);
+
+	TCU_CHECK_EGL_MSG("glEGLImageTargetRenderbufferStorageOES() failed");
+
+	int stencilValue = 78;
+
+	GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderbuffer));
+
+	GLU_CHECK_CALL(glViewport(0, 0, reference.getWidth(), reference.getHeight()));
+	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+		throw tcu::NotSupportedError("EGLImage type as stencil attachment not supported", "", __FILE__, __LINE__);
+	}
+
+	GLU_CHECK_CALL(glClearStencil(stencilValue));
+	GLU_CHECK_CALL(glClear(GL_STENCIL_BUFFER_BIT));
+
+	for (int x = 0; x < reference.getWidth(); x++)
+	{
+		for (int y = 0; y < reference.getHeight(); y++)
+		{
+			tcu::IVec4 color = tcu::IVec4(stencilValue, stencilValue, stencilValue, stencilValue);
+			reference.getLevel(0).setPixel(color, x, y);
+		}
+	}
+
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+	GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+	GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+	GLU_CHECK_CALL(glFinish());
+}
+
+
+void GLES2ImageApi::checkRequiredExtensions (set<string>& extensions, TestSpec::Operation::Type type, int operationNdx)
+{
+	switch (type)
+	{
+		case TestSpec::Operation::TYPE_CREATE:
+			switch (operationNdx)
+			{
+				case CREATE_TEXTURE2D_RGB8:
+				case CREATE_TEXTURE2D_RGB565:
+				case CREATE_TEXTURE2D_RGBA8:
+				case CREATE_TEXTURE2D_RGBA5_A1:
+				case CREATE_TEXTURE2D_RGBA4:
+					extensions.insert("EGL_KHR_gl_texture_2D_image");
+					break;
+
+				case CREATE_CUBE_MAP_POSITIVE_X_RGB8:
+				case CREATE_CUBE_MAP_NEGATIVE_X_RGB8:
+				case CREATE_CUBE_MAP_POSITIVE_Y_RGB8:
+				case CREATE_CUBE_MAP_NEGATIVE_Y_RGB8:
+				case CREATE_CUBE_MAP_POSITIVE_Z_RGB8:
+				case CREATE_CUBE_MAP_NEGATIVE_Z_RGB8:
+				case CREATE_CUBE_MAP_POSITIVE_X_RGBA8:
+				case CREATE_CUBE_MAP_NEGATIVE_X_RGBA8:
+				case CREATE_CUBE_MAP_POSITIVE_Y_RGBA8:
+				case CREATE_CUBE_MAP_NEGATIVE_Y_RGBA8:
+				case CREATE_CUBE_MAP_POSITIVE_Z_RGBA8:
+				case CREATE_CUBE_MAP_NEGATIVE_Z_RGBA8:
+					extensions.insert("EGL_KHR_gl_texture_cubemap_image");
+					break;
+
+				case CREATE_RENDER_BUFFER_RGBA4:
+				case CREATE_RENDER_BUFFER_RGB5_A1:
+				case CREATE_RENDER_BUFFER_RGB565:
+				case CREATE_RENDER_BUFFER_DEPTH16:
+				case CREATE_RENDER_BUFFER_STENCIL:
+					extensions.insert("EGL_KHR_gl_renderbuffer_image");
+					break;
+
+				default:
+					DE_ASSERT(false);
+			}
+			break;
+
+		case TestSpec::Operation::TYPE_RENDER:
+			switch (operationNdx)
+			{
+				case RENDER_TEXTURE2D:
+				case RENDER_READ_PIXELS_RENDERBUFFER:
+				case RENDER_DEPTHBUFFER:
+				case RENDER_TRY_ALL:
+					extensions.insert("GL_OES_EGL_image");
+					break;
+
+				default:
+					DE_ASSERT(false);
+					break;
+			}
+			break;
+
+		case TestSpec::Operation::TYPE_MODIFY:
+			switch (operationNdx)
+			{
+				case MODIFY_TEXSUBIMAGE_RGB565:
+				case MODIFY_TEXSUBIMAGE_RGB8:
+				case MODIFY_TEXSUBIMAGE_RGBA8:
+				case MODIFY_TEXSUBIMAGE_RGBA5_A1:
+				case MODIFY_TEXSUBIMAGE_RGBA4:
+				case MODIFY_RENDERBUFFER_CLEAR_COLOR:
+				case MODIFY_RENDERBUFFER_CLEAR_DEPTH:
+				case MODIFY_RENDERBUFFER_CLEAR_STENCIL:
+					extensions.insert("GL_OES_EGL_image");
+					break;
+
+				default:
+					DE_ASSERT(false);
+					break;
+			};
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+}
+
+class ImageFormatCase : public TestCase
+{
+public:
+						ImageFormatCase		(EglTestContext& eglTestCtx, const TestSpec& spec);
+						~ImageFormatCase	(void);
+
+	void				init				(void);
+	void				deinit				(void);
+	IterateResult		iterate				(void);
+	void				checkExtensions		(void);
+
+private:
+	EGLConfig			getConfig			(void);
+
+	const TestSpec		m_spec;
+	tcu::TestLog&		m_log;
+
+	vector<ImageApi*>	m_apiContexts;
+
+	tcu::egl::Display*	m_display;
+	eglu::NativeWindow*	m_window;
+	tcu::egl::Surface*	m_surface;
+	EGLConfig			m_config;
+	int					m_curIter;
+	EGLImageKHR			m_img;
+	tcu::Texture2D		m_refImg;
+	EglExt				m_eglExt;
+};
+
+EGLConfig ImageFormatCase::getConfig (void)
+{
+	vector<EGLConfig>	configs;
+	eglu::FilterList	filter;
+
+	EGLint attribList[] =
+	{
+		EGL_RENDERABLE_TYPE, 	EGL_OPENGL_ES2_BIT,
+		EGL_SURFACE_TYPE,	 	EGL_WINDOW_BIT,
+		EGL_ALPHA_SIZE,			1,
+		EGL_DEPTH_SIZE,			8,
+		EGL_NONE
+	};
+	m_display->chooseConfig(attribList, configs);
+
+	return configs[0];
+}
+
+ImageFormatCase::ImageFormatCase (EglTestContext& eglTestCtx, const TestSpec& spec)
+	: TestCase			(eglTestCtx, spec.name.c_str(), spec.desc.c_str())
+	, m_spec			(spec)
+	, m_log				(eglTestCtx.getTestContext().getLog())
+	, m_display			(DE_NULL)
+	, m_window			(DE_NULL)
+	, m_surface			(DE_NULL)
+	, m_config			(0)
+	, m_curIter			(0)
+	, m_img				(EGL_NO_IMAGE_KHR)
+	, m_refImg			(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 1, 1)
+{
+}
+
+ImageFormatCase::~ImageFormatCase (void)
+{
+	deinit();
+}
+
+void ImageFormatCase::checkExtensions (void)
+{
+	vector<string> extensions;
+	m_display->getExtensions(extensions);
+
+	set<string> extSet(extensions.begin(), extensions.end());
+
+	const char* glExt = (const char*)glGetString(GL_EXTENSIONS);
+
+	for (const char* c = glExt; true; c++)
+	{
+		if (*c == '\0')
+		{
+			extSet.insert(string(glExt));
+			break;
+		}
+
+		if (*c == ' ')
+		{
+			extSet.insert(string(glExt, c));
+			glExt = (c+1);
+		}
+	}
+
+	if (extSet.find("EGL_KHR_image_base") == extSet.end()
+			&& extSet.find("EGL_KHR_image") == extSet.end())
+	{
+		m_log << tcu::TestLog::Message
+			<< "EGL_KHR_image and EGL_KHR_image_base not supported."
+			<< "One should be supported."
+			<< tcu::TestLog::EndMessage;
+		throw tcu::NotSupportedError("Extension not supported", "EGL_KHR_image_base", __FILE__, __LINE__);
+	}
+
+	set<string> requiredExtensions;
+	for (int operationNdx = 0; operationNdx < (int)m_spec.operations.size(); operationNdx++)
+		m_apiContexts[m_spec.operations[m_curIter].apiIndex]->checkRequiredExtensions(requiredExtensions, m_spec.operations[operationNdx].type, m_spec.operations[operationNdx].operationIndex);
+
+	std::set<string>::iterator extIter = requiredExtensions.begin();
+	for (; extIter != requiredExtensions.end(); extIter++)
+	{
+		if (extSet.find(*extIter) == extSet.end())
+			throw tcu::NotSupportedError("Extension not supported", (*extIter).c_str(), __FILE__, __LINE__);
+	}
+}
+
+void ImageFormatCase::init (void)
+{
+	m_display	= &m_eglTestCtx.getDisplay();
+	m_config	= getConfig();
+	m_window	= m_eglTestCtx.createNativeWindow(m_display->getEGLDisplay(), m_config, DE_NULL, 480, 480, eglu::parseWindowVisibility(m_testCtx.getCommandLine()));
+	m_surface	= new tcu::egl::WindowSurface(*m_display, eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *m_window, m_display->getEGLDisplay(), m_config, DE_NULL));
+
+	for (int contextNdx = 0; contextNdx < (int)m_spec.contexts.size(); contextNdx++)
+	{
+		ImageApi* api = DE_NULL;
+		switch (m_spec.contexts[contextNdx])
+		{
+			case TestSpec::API_GLES2:
+			{
+				api = new GLES2ImageApi(contextNdx, m_log, *m_display, m_surface, m_config);
+				break;
+			}
+
+			default:
+				DE_ASSERT(false);
+				break;
+		}
+		m_apiContexts.push_back(api);
+	}
+	checkExtensions();
+}
+
+void ImageFormatCase::deinit (void)
+{
+	for (int contexNdx = 0 ; contexNdx < (int)m_apiContexts.size(); contexNdx++)
+		delete m_apiContexts[contexNdx];
+
+	m_apiContexts.clear();
+	delete m_surface;
+	m_surface = DE_NULL;
+	delete m_window;
+	m_window = DE_NULL;
+}
+
+TestCase::IterateResult ImageFormatCase::iterate (void)
+{
+	bool isOk = true;
+
+	switch (m_spec.operations[m_curIter].type)
+	{
+		case TestSpec::Operation::TYPE_CREATE:
+		{
+			// Delete old image if exists
+			if (m_img != EGL_NO_IMAGE_KHR)
+			{
+				m_log << tcu::TestLog::Message << "Destroying old EGLImage" << tcu::TestLog::EndMessage;
+				TCU_CHECK_EGL_CALL(m_eglExt.eglDestroyImageKHR(m_display->getEGLDisplay(), m_img));
+
+				m_img = EGL_NO_IMAGE_KHR;
+			}
+
+			m_img = m_apiContexts[m_spec.operations[m_curIter].apiIndex]->create(m_spec.operations[m_curIter].operationIndex, m_refImg);
+			break;
+		}
+
+		case TestSpec::Operation::TYPE_RENDER:
+		{
+			DE_ASSERT(m_apiContexts[m_spec.operations[m_curIter].apiIndex]);
+			isOk = m_apiContexts[m_spec.operations[m_curIter].apiIndex]->render(m_spec.operations[m_curIter].operationIndex, m_img, m_refImg);
+			break;
+		}
+
+		case TestSpec::Operation::TYPE_MODIFY:
+		{
+			m_apiContexts[m_spec.operations[m_curIter].apiIndex]->modify(m_spec.operations[m_curIter].operationIndex, m_img, m_refImg);
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	if (isOk && ++m_curIter < (int)m_spec.operations.size())
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return CONTINUE;
+	}
+	else if (!isOk)
+	{
+		if (m_img != EGL_NO_IMAGE_KHR)
+		{
+			m_log << tcu::TestLog::Message << "Destroying EGLImage" << tcu::TestLog::EndMessage;
+			TCU_CHECK_EGL_CALL(m_eglExt.eglDestroyImageKHR(m_display->getEGLDisplay(), m_img));
+			m_img = EGL_NO_IMAGE_KHR;
+		}
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		return STOP;
+	}
+	else
+	{
+		if (m_img != EGL_NO_IMAGE_KHR)
+		{
+			m_log << tcu::TestLog::Message << "Destroying EGLImage" << tcu::TestLog::EndMessage;
+			TCU_CHECK_EGL_CALL(m_eglExt.eglDestroyImageKHR(m_display->getEGLDisplay(), m_img));
+			m_img = EGL_NO_IMAGE_KHR;
+		}
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+}
+
+SimpleCreationTests::SimpleCreationTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup	(eglTestCtx, "create", "EGLImage creation tests")
+{
+}
+
+#define PUSH_VALUES_TO_VECTOR(type, vector, ...)\
+do {\
+	type array[] = __VA_ARGS__;\
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(array); i++)\
+	{\
+		vector.push_back(array[i]);\
+	}\
+} while(false);
+
+void SimpleCreationTests::init (void)
+{
+	GLES2ImageApi::Create createOperations[] = {
+		GLES2ImageApi::CREATE_TEXTURE2D_RGB8,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGB565,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGBA8,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGBA5_A1,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGBA4,
+
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_X_RGBA8,
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_Y_RGBA8,
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_Z_RGBA8,
+
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_X_RGBA8,
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_Y_RGBA8,
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_Z_RGBA8,
+
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_X_RGB8,
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_Y_RGB8,
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_Z_RGB8,
+
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_X_RGB8,
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_Y_RGB8,
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_Z_RGB8,
+
+		GLES2ImageApi::CREATE_RENDER_BUFFER_RGBA4,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_RGB5_A1,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_RGB565,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_DEPTH16,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_STENCIL
+	};
+
+	const char* createOperationsStr[] = {
+		"texture_rgb8",
+		"texture_rgb565",
+		"texture_rgba8",
+		"texture_rgba5_a1",
+		"texture_rgba4",
+
+		"cubemap_positive_x_rgba",
+		"cubemap_positive_y_rgba",
+		"cubemap_positive_z_rgba",
+
+		"cubemap_negative_x_rgba",
+		"cubemap_negative_y_rgba",
+		"cubemap_negative_z_rgba",
+
+		"cubemap_positive_x_rgb",
+		"cubemap_positive_y_rgb",
+		"cubemap_positive_z_rgb",
+
+		"cubemap_negative_x_rgb",
+		"cubemap_negative_y_rgb",
+		"cubemap_negative_z_rgb",
+
+		"renderbuffer_rgba4",
+		"renderbuffer_rgb5_a1",
+		"renderbuffer_rgb565",
+		"renderbuffer_depth16",
+		"renderbuffer_stencil"
+	};
+
+	GLES2ImageApi::Render renderOperations[] = {
+			GLES2ImageApi::RENDER_TEXTURE2D,
+			GLES2ImageApi::RENDER_READ_PIXELS_RENDERBUFFER,
+			GLES2ImageApi::RENDER_DEPTHBUFFER
+	};
+	const char* renderOperationsStr[] = {
+			"texture",
+			"read_pixels",
+			"depth_buffer"
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(createOperations) == DE_LENGTH_OF_ARRAY(createOperationsStr));
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(renderOperations) == DE_LENGTH_OF_ARRAY(renderOperationsStr));
+
+	for (int createNdx = 0; createNdx < DE_LENGTH_OF_ARRAY(createOperations); createNdx++)
+	{
+		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renderOperations); renderNdx++)
+		{
+			TestSpec spec;
+			spec.name = std::string("gles2_") + createOperationsStr[createNdx] + "_" + renderOperationsStr[renderNdx];
+			spec.desc = spec.name;
+
+			PUSH_VALUES_TO_VECTOR(TestSpec::ApiContext, spec.contexts,
+			{
+				TestSpec::API_GLES2
+			});
+			PUSH_VALUES_TO_VECTOR(TestSpec::Operation, spec.operations,
+			{
+				{ TestSpec::API_GLES2, 0, TestSpec::Operation::TYPE_CREATE, createOperations[createNdx] },
+				{ TestSpec::API_GLES2, 0, TestSpec::Operation::TYPE_RENDER, renderOperations[renderNdx] },
+			});
+
+			addChild(new ImageFormatCase(m_eglTestCtx, spec));
+		}
+	}
+}
+
+MultiContextRenderTests::MultiContextRenderTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup	(eglTestCtx, "render_multiple_contexts", "EGLImage render tests on multiple contexts")
+{
+}
+
+void MultiContextRenderTests::init (void)
+{
+	GLES2ImageApi::Create createOperations[] = {
+		GLES2ImageApi::CREATE_TEXTURE2D_RGB8,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGB565,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGBA8,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGBA5_A1,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGBA4,
+
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_X_RGBA8,
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_Y_RGBA8,
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_Z_RGBA8,
+
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_X_RGBA8,
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_Y_RGBA8,
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_Z_RGBA8,
+
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_X_RGB8,
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_Y_RGB8,
+		GLES2ImageApi::CREATE_CUBE_MAP_POSITIVE_Z_RGB8,
+
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_X_RGB8,
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_Y_RGB8,
+		GLES2ImageApi::CREATE_CUBE_MAP_NEGATIVE_Z_RGB8,
+
+		GLES2ImageApi::CREATE_RENDER_BUFFER_RGBA4,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_RGB5_A1,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_RGB565,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_DEPTH16,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_STENCIL
+	};
+
+	const char* createOperationsStr[] = {
+		"texture_rgb8",
+		"texture_rgb565",
+		"texture_rgba8",
+		"texture_rgba5_a1",
+		"texture_rgba4",
+
+		"cubemap_positive_x_rgba8",
+		"cubemap_positive_y_rgba8",
+		"cubemap_positive_z_rgba8",
+
+		"cubemap_negative_x_rgba8",
+		"cubemap_negative_y_rgba8",
+		"cubemap_negative_z_rgba8",
+
+		"cubemap_positive_x_rgb8",
+		"cubemap_positive_y_rgb8",
+		"cubemap_positive_z_rgb8",
+
+		"cubemap_negative_x_rgb8",
+		"cubemap_negative_y_rgb8",
+		"cubemap_negative_z_rgb8",
+
+		"renderbuffer_rgba4",
+		"renderbuffer_rgb5_a1",
+		"renderbuffer_rgb565",
+		"renderbuffer_depth16",
+		"renderbuffer_stencil"
+	};
+
+	GLES2ImageApi::Render renderOperations[] = {
+			GLES2ImageApi::RENDER_TEXTURE2D,
+			GLES2ImageApi::RENDER_READ_PIXELS_RENDERBUFFER,
+			GLES2ImageApi::RENDER_DEPTHBUFFER
+	};
+	const char* renderOperationsStr[] = {
+			"texture",
+			"read_pixels",
+			"depth_buffer"
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(createOperations) == DE_LENGTH_OF_ARRAY(createOperationsStr));
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(renderOperations) == DE_LENGTH_OF_ARRAY(renderOperationsStr));
+
+	for (int createNdx = 0; createNdx < DE_LENGTH_OF_ARRAY(createOperations); createNdx++)
+	{
+		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renderOperations); renderNdx++)
+		{
+			TestSpec spec;
+			spec.name = std::string("gles2_") + createOperationsStr[createNdx] + "_" + renderOperationsStr[renderNdx];
+			spec.desc = spec.name;
+
+			PUSH_VALUES_TO_VECTOR(TestSpec::ApiContext, spec.contexts,
+			{
+				TestSpec::API_GLES2,
+				TestSpec::API_GLES2
+			});
+			PUSH_VALUES_TO_VECTOR(TestSpec::Operation, spec.operations,
+			{
+				{ TestSpec::API_GLES2, 0, TestSpec::Operation::TYPE_CREATE, createOperations[createNdx] },
+				{ TestSpec::API_GLES2, 1, TestSpec::Operation::TYPE_RENDER, renderOperations[renderNdx] },
+				{ TestSpec::API_GLES2, 0, TestSpec::Operation::TYPE_RENDER, renderOperations[renderNdx] },
+				{ TestSpec::API_GLES2, 1, TestSpec::Operation::TYPE_CREATE, createOperations[createNdx] },
+				{ TestSpec::API_GLES2, 1, TestSpec::Operation::TYPE_RENDER, renderOperations[renderNdx] },
+				{ TestSpec::API_GLES2, 0, TestSpec::Operation::TYPE_RENDER, renderOperations[renderNdx] }
+			});
+			addChild(new ImageFormatCase(m_eglTestCtx, spec));
+		}
+	}
+}
+
+ModifyTests::ModifyTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup	(eglTestCtx, "modify", "EGLImage modifying tests")
+{
+}
+
+void ModifyTests::init (void)
+{
+	GLES2ImageApi::Create createOperations[] = {
+		GLES2ImageApi::CREATE_TEXTURE2D_RGB8,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGB565,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGBA8,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGBA5_A1,
+		GLES2ImageApi::CREATE_TEXTURE2D_RGBA4,
+
+		GLES2ImageApi::CREATE_RENDER_BUFFER_RGBA4,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_RGB5_A1,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_RGB565,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_DEPTH16,
+		GLES2ImageApi::CREATE_RENDER_BUFFER_STENCIL
+	};
+
+	const char* createOperationsStr[] = {
+		"tex_rgb8",
+		"tex_rgb565",
+		"tex_rgba8",
+		"tex_rgba5_a1",
+		"tex_rgba4",
+
+		"renderbuffer_rgba4",
+		"renderbuffer_rgb5_a1",
+		"renderbuffer_rgb565",
+		"renderbuffer_depth16",
+		"renderbuffer_stencil"
+	};
+
+	GLES2ImageApi::Modify modifyOperations[] = {
+		GLES2ImageApi::MODIFY_TEXSUBIMAGE_RGB8,
+		GLES2ImageApi::MODIFY_TEXSUBIMAGE_RGB565,
+		GLES2ImageApi::MODIFY_TEXSUBIMAGE_RGBA8,
+		GLES2ImageApi::MODIFY_TEXSUBIMAGE_RGBA5_A1,
+		GLES2ImageApi::MODIFY_TEXSUBIMAGE_RGBA4,
+
+		GLES2ImageApi::MODIFY_RENDERBUFFER_CLEAR_COLOR,
+		GLES2ImageApi::MODIFY_RENDERBUFFER_CLEAR_DEPTH,
+		GLES2ImageApi::MODIFY_RENDERBUFFER_CLEAR_STENCIL,
+	};
+
+	const char* modifyOperationsStr[] = {
+		"tex_subimage_rgb8",
+		"tex_subimage_rgb565",
+		"tex_subimage_rgba8",
+		"tex_subimage_rgba5_a1",
+		"tex_subimage_rgba4",
+
+		"renderbuffer_clear_color",
+		"renderbuffer_clear_depth",
+		"renderbuffer_clear_stencil",
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(modifyOperations) == DE_LENGTH_OF_ARRAY(modifyOperationsStr));
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(createOperations) == DE_LENGTH_OF_ARRAY(createOperationsStr));
+
+	for (int createNdx = 0; createNdx < DE_LENGTH_OF_ARRAY(createOperations); createNdx++)
+	{
+		for (int modifyNdx = 0; modifyNdx < DE_LENGTH_OF_ARRAY(modifyOperations); modifyNdx++)
+		{
+			TestSpec spec;
+			spec.name = "gles2_tex_sub_image";
+			spec.desc = spec.name;
+
+			PUSH_VALUES_TO_VECTOR(TestSpec::ApiContext, spec.contexts,
+			{
+				TestSpec::API_GLES2
+			});
+			PUSH_VALUES_TO_VECTOR(TestSpec::Operation, spec.operations,
+			{
+				{ TestSpec::API_GLES2, 0, TestSpec::Operation::TYPE_CREATE, createOperations[createNdx] },
+				{ TestSpec::API_GLES2, 0, TestSpec::Operation::TYPE_RENDER, GLES2ImageApi::RENDER_TRY_ALL },
+				{ TestSpec::API_GLES2, 0, TestSpec::Operation::TYPE_MODIFY, modifyOperations[modifyNdx] },
+				{ TestSpec::API_GLES2, 0, TestSpec::Operation::TYPE_RENDER, GLES2ImageApi::RENDER_TRY_ALL }
+			});
+
+			spec.name = std::string(createOperationsStr[createNdx]) + "_" + modifyOperationsStr[modifyNdx];
+			addChild(new ImageFormatCase(m_eglTestCtx, spec));
+		}
+	}
+}
+
+} // Image
+} // egl
+} // deqp
diff --git a/modules/egl/teglImageFormatTests.hpp b/modules/egl/teglImageFormatTests.hpp
new file mode 100644
index 0000000..2a26ff5
--- /dev/null
+++ b/modules/egl/teglImageFormatTests.hpp
@@ -0,0 +1,60 @@
+#ifndef _TEGLIMAGEFORMATTESTS_HPP
+#define _TEGLIMAGEFORMATTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL image tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+namespace Image
+{
+
+class SimpleCreationTests : public TestCaseGroup
+{
+public:
+			SimpleCreationTests		(EglTestContext& eglTestCtx);
+	void	init						(void);
+};
+
+class ModifyTests : public TestCaseGroup
+{
+public:
+			ModifyTests		(EglTestContext& eglTestCtx);
+	void	init			(void);
+};
+
+class MultiContextRenderTests : public TestCaseGroup
+{
+public:
+			MultiContextRenderTests		(EglTestContext& eglTestCtx);
+	void	init						(void);
+};
+
+} // Image
+} // egl
+} // deqp
+#endif // _TEGLIMAGEFORMATTESTS_HPP
diff --git a/modules/egl/teglImageTests.cpp b/modules/egl/teglImageTests.cpp
new file mode 100644
index 0000000..6656d1e
--- /dev/null
+++ b/modules/egl/teglImageTests.cpp
@@ -0,0 +1,714 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL image tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglImageTests.hpp"
+
+#include "teglImageFormatTests.hpp"
+
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluStrUtil.hpp"
+#include "egluUtil.hpp"
+
+#include "gluDefs.hpp"
+#include "gluStrUtil.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuCommandLine.hpp"
+
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <set>
+
+#include <EGL/eglext.h>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+using tcu::TestLog;
+
+using std::string;
+using std::vector;
+using std::set;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace Image
+{
+
+bool checkExtensions (const tcu::egl::Display& dpy, const char** first, const char** last, vector<const char*>& unsupported)
+{
+	vector<string> extensions;
+	dpy.getExtensions(extensions);
+
+	set<string> extSet(extensions.begin(), extensions.end());
+
+	unsupported.clear();
+
+	for (const char** extIter = first; extIter != last; extIter++)
+	{
+		const char* ext = *extIter;
+
+		if (extSet.find(ext) == extSet.end())
+			unsupported.push_back(ext);
+	}
+
+	return unsupported.size() == 0;
+}
+
+string join (const vector<const char*>& parts, const char* separator)
+{
+	std::ostringstream str;
+	for (std::vector<const char*>::const_iterator i = parts.begin(); i != parts.end(); i++)
+	{
+		if (i != parts.begin())
+			str << separator;
+		str << *i;
+	}
+	return str.str();
+}
+
+void checkExtensions (const tcu::egl::Display& dpy, const char** first, const char** last)
+{
+	vector<const char*> unsupported;
+	if (!checkExtensions(dpy, first, last, unsupported))
+		throw tcu::NotSupportedError("Extension not supported", join(unsupported, " ").c_str(), __FILE__, __LINE__);
+}
+
+template <size_t N>
+void checkExtensions (const tcu::egl::Display& dpy, const char* (&extensions)[N])
+{
+	checkExtensions(dpy, &extensions[0], &extensions[N]);
+}
+
+#define CHECK_EXTENSIONS(EXTENSIONS) do { static const char* ext[] = EXTENSIONS; checkExtensions(m_eglTestCtx.getDisplay(), ext); } while (deGetFalse())
+
+template <typename RetVal>
+RetVal checkCallError (tcu::TestContext& testCtx, const char* call, RetVal returnValue, EGLint expectError)
+{
+	TestLog& log = testCtx.getLog();
+	log << TestLog::Message << call << TestLog::EndMessage;
+
+	EGLint error = eglGetError();
+
+	if (error != expectError)
+	{
+		log << TestLog::Message << "  Fail: Error code mismatch! Expected " << eglu::getErrorStr(expectError) << ", got " << eglu::getErrorStr(error) << TestLog::EndMessage;
+		log << TestLog::Message << "  " << returnValue << " was returned" << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid error code");
+	}
+
+	return returnValue;
+}
+
+template <typename RetVal>
+void checkCallReturn (tcu::TestContext& testCtx, const char* call, RetVal returnValue, RetVal expectReturnValue, EGLint expectError)
+{
+	TestLog& log = testCtx.getLog();
+	log << TestLog::Message << call << TestLog::EndMessage;
+
+	EGLint error = eglGetError();
+
+	if (returnValue != expectReturnValue)
+	{
+		log << TestLog::Message << "  Fail: Return value mismatch! Expected " << expectReturnValue << ", got " << returnValue << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid return value");
+	}
+
+	if (error != expectError)
+	{
+		log << TestLog::Message << "  Fail: Error code mismatch! Expected " << eglu::getErrorStr(expectError) << ", got " << eglu::getErrorStr(error) << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid error code");
+	}
+}
+
+void checkGLCall (tcu::TestContext& testCtx, const char* call, GLenum expectError)
+{
+	TestLog& log = testCtx.getLog();
+	log << TestLog::Message << call << TestLog::EndMessage;
+
+	GLenum error = glGetError();
+
+	if (error != expectError)
+	{
+		log << TestLog::Message << "  Fail: Error code mismatch! Expected " << glu::getErrorStr(expectError) << ", got " << glu::getErrorStr(error) << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid error code");
+	}
+}
+
+// \note These macros expect "TestContext m_testCtx" and "ExtFuncTable efTable" variables to be defined.
+#define CHECK_EXT_CALL_RET(CALL, EXPECT_RETURN_VALUE, EXPECT_ERROR)	checkCallReturn(m_testCtx, #CALL, efTable.CALL, (EXPECT_RETURN_VALUE), (EXPECT_ERROR))
+#define CHECK_EXT_CALL_ERR(CALL, EXPECT_ERROR)						checkCallError(m_testCtx, #CALL, efTable.CALL, (EXPECT_ERROR))
+#define CHECK_GL_EXT_CALL(CALL, EXPECT_ERROR)						do { efTable.CALL; checkGLCall(m_testCtx, #CALL, (EXPECT_ERROR)); } while (deGetFalse())
+
+class ExtFuncTable
+{
+public:
+	PFNEGLCREATEIMAGEKHRPROC						eglCreateImageKHR;
+	PFNEGLDESTROYIMAGEKHRPROC						eglDestroyImageKHR;
+
+	PFNGLEGLIMAGETARGETTEXTURE2DOESPROC				glEGLImageTargetTexture2DOES;
+	PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC	glEGLImageTargetRenderbufferStorageOES;
+
+	ExtFuncTable (void)
+	{
+		// EGL_KHR_image_base
+		eglCreateImageKHR						= (PFNEGLCREATEIMAGEKHRPROC)						eglGetProcAddress("eglCreateImageKHR");
+		eglDestroyImageKHR						= (PFNEGLDESTROYIMAGEKHRPROC)						eglGetProcAddress("eglDestroyImageKHR");
+
+		// OES_EGL_image
+		glEGLImageTargetTexture2DOES			= (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)				eglGetProcAddress("glEGLImageTargetTexture2DOES");
+		glEGLImageTargetRenderbufferStorageOES	= (PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC)	eglGetProcAddress("glEGLImageTargetRenderbufferStorageOES");
+	}
+};
+
+class InvalidCreateImage : public TestCase
+{
+public:
+	InvalidCreateImage (EglTestContext& eglTestCtx)
+		: TestCase(eglTestCtx, "invalid_create_image", "eglCreateImageKHR() with invalid arguments")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		EGLDisplay		dpy = m_eglTestCtx.getDisplay().getEGLDisplay();
+		TestLog&		log	= m_testCtx.getLog();
+		ExtFuncTable	efTable;
+
+		CHECK_EXTENSIONS({ "EGL_KHR_image_base" });
+
+		// Initialize result to pass.
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		log << TestLog::Message << "Testing bad display (-1)..." << TestLog::EndMessage;
+		CHECK_EXT_CALL_RET(eglCreateImageKHR((EGLDisplay)-1, EGL_NO_CONTEXT, EGL_NONE, 0, DE_NULL),
+						   EGL_NO_IMAGE_KHR, EGL_BAD_DISPLAY);
+
+		log << TestLog::Message << "Testing bad context (-1)..." << TestLog::EndMessage;
+		CHECK_EXT_CALL_RET(eglCreateImageKHR(dpy, (EGLContext)-1, EGL_NONE, 0, DE_NULL),
+						   EGL_NO_IMAGE_KHR, EGL_BAD_CONTEXT);
+
+		log << TestLog::Message << "Testing bad parameter (-1).." << TestLog::EndMessage;
+		CHECK_EXT_CALL_RET(eglCreateImageKHR(dpy, EGL_NO_CONTEXT, (EGLenum)-1, 0, DE_NULL),
+						   EGL_NO_IMAGE_KHR, EGL_BAD_PARAMETER);
+
+		return STOP;
+	}
+};
+
+class GLES2Context
+{
+public:
+	GLES2Context (EglTestContext& eglTestCtx, EGLint configId, int width, int height)
+		: m_eglTestCtx	(eglTestCtx)
+		, m_config		(getConfigById(eglTestCtx.getDisplay(), configId))
+		, m_context		(eglTestCtx.getDisplay(), m_config, m_ctxAttrs, EGL_OPENGL_ES_API)
+		, m_window		(DE_NULL)
+		, m_pixmap		(DE_NULL)
+		, m_surface		(DE_NULL)
+	{
+		tcu::egl::Display&	dpy				= eglTestCtx.getDisplay();
+		EGLint				surfaceTypeBits	= dpy.getConfigAttrib(m_config, EGL_SURFACE_TYPE);
+
+		if (surfaceTypeBits & EGL_PBUFFER_BIT)
+		{
+			EGLint pbufferAttrs[] =
+			{
+				EGL_WIDTH,		width,
+				EGL_HEIGHT,		height,
+				EGL_NONE
+			};
+
+			m_surface = new tcu::egl::PbufferSurface(dpy, m_config, pbufferAttrs);
+		}
+		else if (surfaceTypeBits & EGL_WINDOW_BIT)
+		{
+			m_window	= eglTestCtx.createNativeWindow(dpy.getEGLDisplay(), m_config, DE_NULL, width, height, eglu::parseWindowVisibility(eglTestCtx.getTestContext().getCommandLine()));
+			m_surface	= new tcu::egl::WindowSurface(dpy, eglu::createWindowSurface(eglTestCtx.getNativeDisplay(), *m_window, dpy.getEGLDisplay(), m_config, DE_NULL));
+		}
+		else if (surfaceTypeBits & EGL_PIXMAP_BIT)
+		{
+			m_pixmap	= eglTestCtx.createNativePixmap(dpy.getEGLDisplay(), m_config, DE_NULL, width, height);
+			m_surface	= new tcu::egl::PixmapSurface(dpy, eglu::createPixmapSurface(eglTestCtx.getNativeDisplay(), *m_pixmap, dpy.getEGLDisplay(), m_config, DE_NULL));
+		}
+		else
+			TCU_FAIL("No valid surface types supported in config");
+
+		m_context.makeCurrent(*m_surface, *m_surface);
+	}
+
+	~GLES2Context (void)
+	{
+		eglMakeCurrent(m_eglTestCtx.getDisplay().getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+		delete m_window;
+		delete m_pixmap;
+		delete m_surface;
+	}
+
+	EGLDisplay getEglDisplay (void)
+	{
+		return m_eglTestCtx.getDisplay().getEGLDisplay();
+	}
+
+	EGLContext getEglContext (void)
+	{
+		return m_context.getEGLContext();
+	}
+
+	// Helper for selecting config.
+	static EGLint getConfigIdForApi (const vector<eglu::ConfigInfo>& configInfos, EGLint apiBits)
+	{
+		EGLint	windowCfg	= 0;
+		EGLint	pixmapCfg	= 0;
+		EGLint	pbufferCfg	= 0;
+
+		for (vector<eglu::ConfigInfo>::const_iterator cfgIter = configInfos.begin(); cfgIter != configInfos.end(); cfgIter++)
+		{
+			if ((cfgIter->renderableType & apiBits) == 0)
+				continue;
+
+			if (windowCfg == 0 && (cfgIter->surfaceType & EGL_WINDOW_BIT) != 0)
+				windowCfg = cfgIter->configId;
+
+			if (pixmapCfg == 0 && (cfgIter->surfaceType & EGL_PIXMAP_BIT) != 0)
+				pixmapCfg = cfgIter->configId;
+
+			if (pbufferCfg == 0 && (cfgIter->surfaceType & EGL_PBUFFER_BIT) != 0)
+				pbufferCfg = cfgIter->configId;
+
+			if (windowCfg && pixmapCfg && pbufferCfg)
+				break;
+		}
+
+		// Prefer configs in order: pbuffer, window, pixmap
+		if (pbufferCfg)
+			return pbufferCfg;
+		else if (windowCfg)
+			return windowCfg;
+		else if (pixmapCfg)
+			return pixmapCfg;
+		else
+			throw tcu::NotSupportedError("No compatible EGL configs found", "", __FILE__, __LINE__);
+	}
+
+private:
+	static EGLConfig getConfigById (const tcu::egl::Display& dpy, EGLint configId)
+	{
+		EGLint attributes[] = { EGL_CONFIG_ID, configId, EGL_NONE };
+		vector<EGLConfig> configs;
+		dpy.chooseConfig(attributes, configs);
+		TCU_CHECK(configs.size() == 1);
+		return configs[0];
+	}
+
+	static const EGLint			m_ctxAttrs[];
+
+	EglTestContext&				m_eglTestCtx;
+	EGLConfig					m_config;
+	tcu::egl::Context			m_context;
+	eglu::NativeWindow*			m_window;
+	eglu::NativePixmap*			m_pixmap;
+	tcu::egl::Surface*			m_surface;
+
+								GLES2Context	(const GLES2Context&);
+	GLES2Context&				operator=		(const GLES2Context&);
+};
+
+const EGLint GLES2Context::m_ctxAttrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+
+class CreateImageGLES2 : public TestCase
+{
+public:
+	static const char* getTargetName (EGLint target)
+	{
+		switch (target)
+		{
+			case EGL_GL_TEXTURE_2D_KHR:						return "tex2d";
+			case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X_KHR:	return "cubemap_pos_x";
+			case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X_KHR:	return "cubemap_neg_x";
+			case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y_KHR:	return "cubemap_pos_y";
+			case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_KHR:	return "cubemap_neg_y";
+			case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z_KHR:	return "cubemap_pos_z";
+			case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_KHR:	return "cubemap_neg_z";
+			case EGL_GL_RENDERBUFFER_KHR:					return "renderbuffer";
+			default:		DE_ASSERT(DE_FALSE);			return "";
+		}
+	}
+
+	static const char* getStorageName (GLenum storage)
+	{
+		switch (storage)
+		{
+			case GL_RGB:				return "rgb";
+			case GL_RGBA:				return "rgba";
+			case GL_DEPTH_COMPONENT16:	return "depth_component_16";
+			case GL_RGBA4:				return "rgba4";
+			case GL_RGB5_A1:			return "rgb5_a1";
+			case GL_RGB565:				return "rgb565";
+			case GL_STENCIL_INDEX8:		return "stencil_index8";
+			default:
+				DE_ASSERT(DE_FALSE);
+				return "";
+		}
+	}
+
+	CreateImageGLES2 (EglTestContext& eglTestCtx, EGLint target, GLenum storage, bool useTexLevel0 = false)
+		: TestCase			(eglTestCtx, (string("create_image_gles2_") + getTargetName(target) + "_" + getStorageName(storage) + (useTexLevel0 ? "_level0_only" : "")).c_str(), "Create EGLImage from GLES2 object")
+		, m_target			(target)
+		, m_storage			(storage)
+		, m_useTexLevel0	(useTexLevel0)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog&		log	= m_testCtx.getLog();
+		ExtFuncTable	efTable;
+
+		if (m_target == EGL_GL_TEXTURE_2D_KHR)
+			CHECK_EXTENSIONS({"EGL_KHR_gl_texture_2D_image"});
+		else if (m_target == EGL_GL_RENDERBUFFER_KHR)
+			CHECK_EXTENSIONS({"EGL_KHR_gl_renderbuffer_image"});
+		else
+			CHECK_EXTENSIONS({"EGL_KHR_gl_texture_cubemap_image"});
+
+		// Initialize result.
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		// Create GLES2 context
+		EGLint configId = GLES2Context::getConfigIdForApi(m_eglTestCtx.getConfigs(), EGL_OPENGL_ES2_BIT);
+		log << TestLog::Message << "Using EGL config " << configId << TestLog::EndMessage;
+
+		GLES2Context context(m_eglTestCtx, configId, 64, 64);
+
+		switch (m_target)
+		{
+			case EGL_GL_TEXTURE_2D_KHR:
+			{
+				deUint32 tex = 1;
+				GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, tex));
+
+				// Specify mipmap level 0
+				GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_2D, 0, m_storage, 64, 64, 0, m_storage, GL_UNSIGNED_BYTE, DE_NULL));
+
+				if (!m_useTexLevel0)
+				{
+					// Set minification filter to linear. This makes the texture complete.
+					GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+				}
+				// Else spec allows using incomplete texture when miplevel 0 is only used and specified.
+
+				// Create EGL image
+				EGLint		attribs[]	= { EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_NONE };
+				EGLImageKHR	image		= CHECK_EXT_CALL_ERR(eglCreateImageKHR(context.getEglDisplay(), context.getEglContext(), EGL_GL_TEXTURE_2D_KHR, (EGLClientBuffer)(deUintptr)tex, attribs), EGL_SUCCESS);
+				if (image == EGL_NO_IMAGE_KHR)
+				{
+					log << TestLog::Message << "  Fail: Got EGL_NO_IMAGE_KHR!" << TestLog::EndMessage;
+
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got EGL_NO_IMAGE_KHR");
+				}
+
+				// Destroy image
+				CHECK_EXT_CALL_RET(eglDestroyImageKHR(context.getEglDisplay(), image), (EGLBoolean)EGL_TRUE, EGL_SUCCESS);
+
+				// Destroy texture object
+				GLU_CHECK_CALL(glDeleteTextures(1, &tex));
+
+				break;
+			}
+
+			case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X_KHR:
+			case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X_KHR:
+			case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y_KHR:
+			case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_KHR:
+			case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z_KHR:
+			case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_KHR:
+			{
+				deUint32 tex = 1;
+				GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, tex));
+
+				// Specify mipmap level 0 for all faces
+				GLenum faces[] =
+				{
+					GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+					GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+					GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+					GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+					GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+					GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
+				};
+				for (int faceNdx = 0; faceNdx < DE_LENGTH_OF_ARRAY(faces); faceNdx++)
+					GLU_CHECK_CALL(glTexImage2D(faces[faceNdx], 0, m_storage, 64, 64, 0, m_storage, GL_UNSIGNED_BYTE, DE_NULL));
+
+				if (!m_useTexLevel0)
+				{
+					// Set minification filter to linear.
+					GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+				}
+
+				// Create EGL image
+				EGLint		attribs[]	= { EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_NONE };
+				EGLImageKHR	image		= CHECK_EXT_CALL_ERR(eglCreateImageKHR(context.getEglDisplay(), context.getEglContext(), m_target, (EGLClientBuffer)(deUintptr)tex, attribs), EGL_SUCCESS);
+				if (image == EGL_NO_IMAGE_KHR)
+				{
+					log << TestLog::Message << "  Fail: Got EGL_NO_IMAGE_KHR!" << TestLog::EndMessage;
+
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got EGL_NO_IMAGE_KHR");
+				}
+
+				// Destroy image
+				CHECK_EXT_CALL_RET(eglDestroyImageKHR(context.getEglDisplay(), image), (EGLBoolean)EGL_TRUE, EGL_SUCCESS);
+
+				// Destroy texture object
+				GLU_CHECK_CALL(glDeleteTextures(1, &tex));
+
+				break;
+			}
+
+			case EGL_GL_RENDERBUFFER_KHR:
+			{
+				// Create renderbuffer.
+				deUint32 rbo = 1;
+				GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, rbo));
+
+				// Specify storage.
+				GLU_CHECK_CALL(glRenderbufferStorage(GL_RENDERBUFFER, m_storage, 64, 64));
+
+				// Create EGL image
+				EGLImageKHR image = CHECK_EXT_CALL_ERR(eglCreateImageKHR(context.getEglDisplay(), context.getEglContext(), EGL_GL_RENDERBUFFER_KHR, (EGLClientBuffer)(deUintptr)rbo, DE_NULL), EGL_SUCCESS);
+				if (image == EGL_NO_IMAGE_KHR)
+				{
+					log << TestLog::Message << "  Fail: Got EGL_NO_IMAGE_KHR!" << TestLog::EndMessage;
+
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got EGL_NO_IMAGE_KHR");
+				}
+
+				// Destroy image
+				CHECK_EXT_CALL_RET(eglDestroyImageKHR(context.getEglDisplay(), image), (EGLBoolean)EGL_TRUE, EGL_SUCCESS);
+
+				// Destroy texture object
+				GLU_CHECK_CALL(glDeleteRenderbuffers(1, &rbo));
+
+				break;
+			}
+
+			default:
+				DE_ASSERT(DE_FALSE);
+				break;
+		}
+
+		return STOP;
+	}
+
+private:
+	EGLint	m_target;
+	GLenum	m_storage;
+	bool	m_useTexLevel0;
+};
+
+class ImageTargetGLES2 : public TestCase
+{
+public:
+	static const char* getTargetName (GLenum target)
+	{
+		switch (target)
+		{
+			case GL_TEXTURE_2D:		return "tex2d";
+			case GL_RENDERBUFFER:	return "renderbuffer";
+			default:
+				DE_ASSERT(DE_FALSE);
+				return "";
+		}
+	}
+
+	ImageTargetGLES2 (EglTestContext& eglTestCtx, GLenum target)
+		: TestCase	(eglTestCtx, (string("image_target_gles2_") + getTargetName(target)).c_str(), "Use EGLImage as GLES2 object")
+		, m_target	(target)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog&		log	= m_testCtx.getLog();
+		ExtFuncTable	efTable;
+
+		// \todo [2011-07-21 pyry] Try all possible EGLImage sources
+		CHECK_EXTENSIONS({"EGL_KHR_gl_texture_2D_image"});
+
+		// Initialize result.
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		// Create GLES2 context
+		EGLint configId = GLES2Context::getConfigIdForApi(m_eglTestCtx.getConfigs(), EGL_OPENGL_ES2_BIT);
+		log << TestLog::Message << "Using EGL config " << configId << TestLog::EndMessage;
+
+		GLES2Context context(m_eglTestCtx, configId, 64, 64);
+
+		// Check for OES_EGL_image
+		{
+			const char* glExt = (const char*)glGetString(GL_EXTENSIONS);
+
+			if (string(glExt).find("GL_OES_EGL_image") == string::npos)
+				throw tcu::NotSupportedError("Extension not supported", "GL_OES_EGL_image", __FILE__, __LINE__);
+
+			TCU_CHECK(efTable.glEGLImageTargetTexture2DOES);
+			TCU_CHECK(efTable.glEGLImageTargetRenderbufferStorageOES);
+		}
+
+		// Create GL_TEXTURE_2D and EGLImage from it.
+		log << TestLog::Message << "Creating EGLImage using GL_TEXTURE_2D with GL_RGBA storage" << TestLog::EndMessage;
+
+		deUint32 srcTex = 1;
+		GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, srcTex));
+		GLU_CHECK_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+
+		// Create EGL image
+		EGLint		attribs[]	= { EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_NONE };
+		EGLImageKHR	image		= CHECK_EXT_CALL_ERR(eglCreateImageKHR(context.getEglDisplay(), context.getEglContext(), EGL_GL_TEXTURE_2D_KHR, (EGLClientBuffer)(deUintptr)srcTex, attribs), EGL_SUCCESS);
+		if (image == EGL_NO_IMAGE_KHR)
+		{
+			log << TestLog::Message << "  Fail: Got EGL_NO_IMAGE_KHR!" << TestLog::EndMessage;
+
+			if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got EGL_NO_IMAGE_KHR");
+		}
+
+		// Create texture or renderbuffer
+		if (m_target == GL_TEXTURE_2D)
+		{
+			log << TestLog::Message << "Creating GL_TEXTURE_2D from EGLimage" << TestLog::EndMessage;
+
+			deUint32 dstTex = 2;
+			GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, dstTex));
+			CHECK_GL_EXT_CALL(glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image), GL_NO_ERROR);
+			GLU_CHECK_CALL(glDeleteTextures(1, &dstTex));
+		}
+		else
+		{
+			DE_ASSERT(m_target == GL_RENDERBUFFER);
+
+			log << TestLog::Message << "Creating GL_RENDERBUFFER from EGLimage" << TestLog::EndMessage;
+
+			deUint32 dstRbo = 2;
+			GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, dstRbo));
+			CHECK_GL_EXT_CALL(glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)image), GL_NO_ERROR);
+			GLU_CHECK_CALL(glDeleteRenderbuffers(1, &dstRbo));
+		}
+
+		// Destroy image
+		CHECK_EXT_CALL_RET(eglDestroyImageKHR(context.getEglDisplay(), image), (EGLBoolean)EGL_TRUE, EGL_SUCCESS);
+
+		// Destroy source texture object
+		GLU_CHECK_CALL(glDeleteTextures(1, &srcTex));
+
+		return STOP;
+	}
+
+private:
+	GLenum	m_target;
+};
+
+class ApiTests : public TestCaseGroup
+{
+public:
+	ApiTests (EglTestContext& eglTestCtx)
+		: TestCaseGroup(eglTestCtx, "api", "EGLImage API tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new Image::InvalidCreateImage(m_eglTestCtx));
+
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_2D_KHR, GL_RGB));
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_2D_KHR, GL_RGBA));
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_2D_KHR, GL_RGBA, true));
+
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X_KHR, GL_RGB));
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X_KHR, GL_RGBA));
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X_KHR, GL_RGBA, true));
+
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X_KHR, GL_RGBA));
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y_KHR, GL_RGBA));
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_KHR, GL_RGBA));
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z_KHR, GL_RGBA));
+		addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_KHR, GL_RGBA));
+
+		static const GLenum rboStorages[] =
+		{
+			GL_DEPTH_COMPONENT16,
+			GL_RGBA4,
+			GL_RGB5_A1,
+			GL_RGB565,
+			GL_STENCIL_INDEX8
+		};
+		for (int storageNdx = 0; storageNdx < DE_LENGTH_OF_ARRAY(rboStorages); storageNdx++)
+			addChild(new Image::CreateImageGLES2(m_eglTestCtx, EGL_GL_RENDERBUFFER_KHR, rboStorages[storageNdx]));
+
+		addChild(new Image::ImageTargetGLES2(m_eglTestCtx, GL_TEXTURE_2D));
+		addChild(new Image::ImageTargetGLES2(m_eglTestCtx, GL_RENDERBUFFER));
+	}
+};
+
+} // Image
+
+ImageTests::ImageTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "image", "EGLImage Tests")
+{
+}
+
+ImageTests::~ImageTests (void)
+{
+}
+
+void ImageTests::init (void)
+{
+	addChild(new Image::ApiTests(m_eglTestCtx));
+	addChild(new Image::SimpleCreationTests(m_eglTestCtx));
+	addChild(new Image::ModifyTests(m_eglTestCtx));
+	addChild(new Image::MultiContextRenderTests(m_eglTestCtx));
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglImageTests.hpp b/modules/egl/teglImageTests.hpp
new file mode 100644
index 0000000..3058f45
--- /dev/null
+++ b/modules/egl/teglImageTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLIMAGETESTS_HPP
+#define _TEGLIMAGETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL image tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class ImageTests : public TestCaseGroup
+{
+public:
+					ImageTests			(EglTestContext& eglTestCtx);
+	virtual			~ImageTests			(void);
+
+	void			init				(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLIMAGETESTS_HPP
diff --git a/modules/egl/teglInfoTests.cpp b/modules/egl/teglInfoTests.cpp
new file mode 100644
index 0000000..7cd71f6
--- /dev/null
+++ b/modules/egl/teglInfoTests.cpp
@@ -0,0 +1,173 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL Implementation Information Tests
+ *//*--------------------------------------------------------------------*/
+
+#include "teglInfoTests.hpp"
+#include "teglConfigList.hpp"
+#include "tcuTestLog.hpp"
+
+#include <vector>
+#include <string>
+#include <sstream>
+
+#include <EGL/egl.h>
+
+using std::vector;
+using std::string;
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+
+static std::vector<std::string> split(const std::string& str, const std::string& delim = " ")
+{
+	std::vector<std::string>	out;
+	if (str.length() == 0) return out;
+
+	size_t	start	= 0;
+	size_t	end		= string::npos;
+
+	while ((end = str.find(delim, start)) != string::npos)
+	{
+		out.push_back(str.substr(start, end-start));
+		start = end + delim.length();
+	}
+
+	if (start < end)
+		out.push_back(str.substr(start, end-start));
+
+	return out;
+}
+
+static int toInt(std::string str)
+{
+	std::istringstream strStream(str);
+
+	int out;
+	strStream >> out;
+	return out;
+}
+
+class QueryStringCase : public TestCase
+{
+public:
+	QueryStringCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint query)
+		: TestCase	(eglTestCtx, name, description)
+		, m_query	(query)
+	{
+	}
+
+	void validateString (const std::string& result)
+	{
+		tcu::TestLog&				log		= m_testCtx.getLog();
+		std::vector<std::string>	tokens	= split(result);
+
+		if (m_query == EGL_VERSION)
+		{
+			const tcu::egl::Display&		display			= m_eglTestCtx.getDisplay();
+			const int						dispMajor		= display.getEGLMajorVersion();
+			const int						dispMinor		= display.getEGLMinorVersion();
+
+			const std::vector<std::string>	versionTokens	= split(tokens[0], ".");
+
+			if (versionTokens.size() < 2)
+			{
+				log << TestLog::Message << "  Fail, first part of the string must be in the format <major_version.minor_version>" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid version string");
+			}
+			else
+			{
+				const	int	stringMajor	= toInt(versionTokens[0]);
+				const	int	stringMinor	= toInt(versionTokens[1]);
+
+				if (stringMajor != dispMajor || stringMinor != dispMinor)
+				{
+					log << TestLog::Message << "  Fail, version numer (" << stringMajor << "." << stringMinor
+						<< ") does not match the one reported by eglInitialize (" << dispMajor << "." << dispMinor << ")" << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Version number mismatch");
+				}
+			}
+		}
+	}
+
+	IterateResult iterate (void)
+	{
+		const char* result = eglQueryString(m_eglTestCtx.getDisplay().getEGLDisplay(), m_query);
+		TCU_CHECK_EGL_MSG("eglQueryString() failed");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << result << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		validateString(result);
+
+		return STOP;
+	}
+
+private:
+	EGLint m_query;
+};
+
+class QueryExtensionsCase : public TestCase
+{
+public:
+	QueryExtensionsCase (EglTestContext& eglTestCtx)
+		: TestCase	(eglTestCtx, "extensions", "Supported Extensions")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		vector<string> extensions;
+		m_eglTestCtx.getDisplay().getExtensions(extensions);
+
+		for (vector<string>::const_iterator i = extensions.begin(); i != extensions.end(); i++)
+			m_testCtx.getLog() << tcu::TestLog::Message << *i << tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		return STOP;
+	}
+};
+
+InfoTests::InfoTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "info", "Platform Information")
+{
+}
+
+InfoTests::~InfoTests (void)
+{
+}
+
+void InfoTests::init (void)
+{
+	addChild(new QueryStringCase(m_eglTestCtx, "version",		"EGL Version",				EGL_VERSION));
+	addChild(new QueryStringCase(m_eglTestCtx, "vendor",		"EGL Vendor",				EGL_VENDOR));
+	addChild(new QueryStringCase(m_eglTestCtx, "client_apis",	"Supported client APIs",	EGL_CLIENT_APIS));
+	addChild(new QueryExtensionsCase(m_eglTestCtx));
+	addChild(new ConfigList(m_eglTestCtx));
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglInfoTests.hpp b/modules/egl/teglInfoTests.hpp
new file mode 100644
index 0000000..d90a482
--- /dev/null
+++ b/modules/egl/teglInfoTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLINFOTESTS_HPP
+#define _TEGLINFOTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL Implementation Information Tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class InfoTests : public TestCaseGroup
+{
+public:
+				InfoTests		(EglTestContext& eglTestCtx);
+				~InfoTests		(void);
+
+	void		init			(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLINFOTESTS_HPP
diff --git a/modules/egl/teglMakeCurrentPerfTests.cpp b/modules/egl/teglMakeCurrentPerfTests.cpp
new file mode 100644
index 0000000..d7d5ff0
--- /dev/null
+++ b/modules/egl/teglMakeCurrentPerfTests.cpp
@@ -0,0 +1,689 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief eglMakeCurrent performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglMakeCurrentPerfTests.hpp"
+
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluUtil.hpp"
+
+#include "tcuTestLog.hpp"
+
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+#include "deClock.h"
+#include "deString.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <vector>
+
+using std::ostringstream;
+using std::string;
+using std::vector;
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+
+class MakeCurrentPerfCase : public TestCase
+{
+public:
+	enum SurfaceType
+	{
+		SURFACETYPE_PBUFFER	= (1<<0),
+		SURFACETYPE_WINDOW	= (1<<1),
+		SURFACETYPE_PIXMAP	= (1<<2)
+	};
+
+	struct Spec
+	{
+		SurfaceType	surfaceTypes;
+		int			contextCount;
+		int			surfaceCount;
+
+		bool		release;
+
+		int			iterationCount;
+		int			sampleCount;
+
+		string		toName			(void) const;
+		string		toDescription	(void) const;
+	};
+					MakeCurrentPerfCase		(EglTestContext& eglTestCtx, const Spec& spec, const char* name, const char* description);
+					~MakeCurrentPerfCase	(void);
+
+	void			init					(void);
+	void			deinit					(void);
+	IterateResult	iterate					(void);
+
+private:
+	Spec						m_spec;
+	de::Random					m_rnd;
+
+	EGLConfig					m_config;
+	vector<EGLContext>			m_contexts;
+	vector<EGLSurface>			m_surfaces;
+
+	vector<eglu::NativeWindow*>	m_windows;
+	vector<eglu::NativePixmap*>	m_pixmaps;
+
+	vector<deUint64>			m_samples;
+
+	void					chooseConfig	(void);
+	void					createSurfaces	(void);
+	void					createContexts	(void);
+
+	void					destroySurfaces	(void);
+	void					destroyContexts	(void);
+
+	void					createPBuffer	(void);
+	void					createWindow	(void);
+	void					createPixmap	(void);
+
+	void					logTestInfo		(void);
+	void					logResults		(void);
+	// Disabled
+							MakeCurrentPerfCase	(const MakeCurrentPerfCase&);
+	MakeCurrentPerfCase&	operator=			(const MakeCurrentPerfCase&);
+};
+
+string MakeCurrentPerfCase::Spec::toName (void) const
+{
+	ostringstream name;
+
+	name << "context";
+
+	if (contextCount > 1)
+		name << "s_" << contextCount;
+
+	if ((surfaceTypes & SURFACETYPE_WINDOW) != 0)
+		name << "_window" << (surfaceCount > 1 ? "s" : "");
+
+	if ((surfaceTypes & SURFACETYPE_PIXMAP) != 0)
+		name << "_pixmap" << (surfaceCount > 1 ? "s" : "");
+
+	if ((surfaceTypes & SURFACETYPE_PBUFFER) != 0)
+		name << "_pbuffer" << (surfaceCount > 1 ? "s" : "");
+
+	if (surfaceCount > 1)
+		name << "_" << surfaceCount;
+
+	if (release)
+		name << "_release";
+
+	return name.str();
+}
+
+string MakeCurrentPerfCase::Spec::toDescription (void) const
+{
+	// \todo [mika] Generate descrpition
+	return toName();
+}
+
+MakeCurrentPerfCase::MakeCurrentPerfCase (EglTestContext& eglTestCtx, const Spec& spec, const char* name, const char* description)
+	: TestCase		(eglTestCtx, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_spec		(spec)
+	, m_rnd			(deStringHash(name))
+	, m_config		(DE_NULL)
+{
+}
+
+MakeCurrentPerfCase::~MakeCurrentPerfCase (void)
+{
+	deinit();
+}
+
+void MakeCurrentPerfCase::init (void)
+{
+	chooseConfig();
+	createContexts();
+	createSurfaces();
+}
+
+void MakeCurrentPerfCase::deinit (void)
+{
+	destroyContexts();
+	destroySurfaces();
+}
+
+void MakeCurrentPerfCase::chooseConfig (void)
+{
+	const EGLint	surfaceBits	= ((m_spec.surfaceTypes & SURFACETYPE_WINDOW) != 0 ? EGL_WINDOW_BIT : 0)
+									| ((m_spec.surfaceTypes & SURFACETYPE_PIXMAP) != 0 ? EGL_PIXMAP_BIT : 0)
+									| ((m_spec.surfaceTypes & SURFACETYPE_PBUFFER) != 0 ? EGL_PBUFFER_BIT : 0);
+
+	const EGLint	attribList[] = {
+		EGL_SURFACE_TYPE,		surfaceBits,
+		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
+		EGL_NONE
+	};
+
+	EGLint		configCount = 0;
+	EGLDisplay	display	= m_eglTestCtx.getDisplay().getEGLDisplay();
+
+	TCU_CHECK_EGL_CALL(eglChooseConfig(display, attribList, &m_config, 1, &configCount));
+
+	if (configCount <= 0)
+		throw tcu::NotSupportedError("No compatible configs found");
+}
+
+void MakeCurrentPerfCase::createSurfaces (void)
+{
+	vector<SurfaceType> types;
+
+	if ((m_spec.surfaceTypes & SURFACETYPE_WINDOW) != 0)
+		types.push_back(SURFACETYPE_WINDOW);
+
+	if ((m_spec.surfaceTypes & SURFACETYPE_PIXMAP) != 0)
+		types.push_back(SURFACETYPE_PIXMAP);
+
+	if ((m_spec.surfaceTypes & SURFACETYPE_PBUFFER) != 0)
+		types.push_back(SURFACETYPE_PBUFFER);
+
+	DE_ASSERT((int)types.size() <= m_spec.surfaceCount);
+
+	// Create surfaces
+	for (int surfaceNdx = 0; surfaceNdx < m_spec.surfaceCount; surfaceNdx++)
+	{
+		SurfaceType type = types[surfaceNdx % types.size()];
+
+		switch (type)
+		{
+			case SURFACETYPE_PBUFFER:
+				createPBuffer();
+				break;
+
+			case SURFACETYPE_WINDOW:
+				createWindow();
+				break;
+
+			case SURFACETYPE_PIXMAP:
+				createPixmap();
+				break;
+
+			default:
+				DE_ASSERT(false);
+		};
+	}
+}
+
+void MakeCurrentPerfCase::createPBuffer (void)
+{
+	const EGLint	width	= 256;
+	const EGLint	height	= 256;
+
+	const EGLint attribList[] = {
+		EGL_WIDTH,	width,
+		EGL_HEIGHT, height,
+		EGL_NONE
+	};
+
+	EGLDisplay	display	= m_eglTestCtx.getDisplay().getEGLDisplay();
+	EGLSurface	surface = eglCreatePbufferSurface(display, m_config, attribList);
+
+	TCU_CHECK_EGL_MSG("eglCreatePbufferSurface()");
+
+	m_surfaces.push_back(surface);
+}
+
+void MakeCurrentPerfCase::createWindow (void)
+{
+	const EGLint	width	= 256;
+	const EGLint	height	= 256;
+
+	eglu::NativeWindow* window	= DE_NULL;
+	EGLSurface			surface	= EGL_NO_SURFACE;
+
+	try
+	{
+		window	= m_eglTestCtx.createNativeWindow(m_eglTestCtx.getDisplay().getEGLDisplay(), m_config, DE_NULL, width, height, eglu::parseWindowVisibility(m_eglTestCtx.getTestContext().getCommandLine()));
+		surface	= eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, m_eglTestCtx.getDisplay().getEGLDisplay(), m_config, DE_NULL);
+	}
+	catch (const std::exception&)
+	{
+		if (surface != EGL_NO_SURFACE)
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_eglTestCtx.getDisplay().getEGLDisplay(), surface));
+
+		delete window;
+		throw;
+	}
+
+	m_windows.push_back(window);
+	m_surfaces.push_back(surface);
+}
+
+void MakeCurrentPerfCase::createPixmap (void)
+{
+	const EGLint	width	= 256;
+	const EGLint	height	= 256;
+
+	eglu::NativePixmap*	pixmap	= DE_NULL;
+	EGLSurface			surface	= EGL_NO_SURFACE;
+
+	try
+	{
+		pixmap	= m_eglTestCtx.createNativePixmap(m_eglTestCtx.getDisplay().getEGLDisplay(), m_config, DE_NULL, width, height);
+		surface	= eglu::createPixmapSurface(m_eglTestCtx.getNativeDisplay(), *pixmap, m_eglTestCtx.getDisplay().getEGLDisplay(), m_config, DE_NULL);
+	}
+	catch (const std::exception&)
+	{
+		if (surface != EGL_NO_SURFACE)
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_eglTestCtx.getDisplay().getEGLDisplay(), surface));
+
+		delete pixmap;
+		throw;
+	}
+
+	m_pixmaps.push_back(pixmap);
+	m_surfaces.push_back(surface);
+}
+
+void MakeCurrentPerfCase::destroySurfaces (void)
+{
+	if (m_surfaces.size() > 0)
+	{
+		EGLDisplay display = m_eglTestCtx.getDisplay().getEGLDisplay();
+
+		// Destroy surfaces
+		for (vector<EGLSurface>::iterator iter = m_surfaces.begin(); iter != m_surfaces.end(); ++iter)
+		{
+			if (*iter != EGL_NO_SURFACE)
+				TCU_CHECK_EGL_CALL(eglDestroySurface(display, *iter));
+			*iter = EGL_NO_SURFACE;
+		}
+
+		m_surfaces.clear();
+
+		// Destroy pixmaps
+		for (vector<eglu::NativePixmap*>::iterator iter = m_pixmaps.begin(); iter != m_pixmaps.end(); ++iter)
+		{
+			delete *iter;
+			*iter = NULL;
+		}
+
+		m_pixmaps.clear();
+
+		// Destroy windows
+		for (vector<eglu::NativeWindow*>::iterator iter = m_windows.begin(); iter != m_windows.end(); ++iter)
+		{
+			delete *iter;
+			*iter = NULL;
+		}
+
+		m_windows.clear();
+
+		// Clear all surface handles
+		m_surfaces.clear();
+	}
+}
+
+void MakeCurrentPerfCase::createContexts (void)
+{
+	EGLDisplay display = m_eglTestCtx.getDisplay().getEGLDisplay();
+
+	for (int contextNdx = 0; contextNdx < m_spec.contextCount; contextNdx++)
+	{
+		const EGLint attribList[] = {
+			EGL_CONTEXT_CLIENT_VERSION, 2,
+			EGL_NONE
+		};
+
+		TCU_CHECK_EGL_CALL(eglBindAPI(EGL_OPENGL_ES_API));
+		EGLContext context = eglCreateContext(display, m_config, EGL_NO_CONTEXT, attribList);
+		TCU_CHECK_EGL_MSG("eglCreateContext()");
+
+		m_contexts.push_back(context);
+	}
+}
+
+void MakeCurrentPerfCase::destroyContexts (void)
+{
+	if (m_contexts.size() > 0)
+	{
+		EGLDisplay display = m_eglTestCtx.getDisplay().getEGLDisplay();
+
+		for (vector<EGLContext>::iterator iter = m_contexts.begin(); iter != m_contexts.end(); ++iter)
+		{
+			if (*iter != EGL_NO_CONTEXT)
+				TCU_CHECK_EGL_CALL(eglDestroyContext(display, *iter));
+			*iter = EGL_NO_CONTEXT;
+		}
+
+		m_contexts.clear();
+	}
+}
+
+void MakeCurrentPerfCase::logTestInfo (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	{
+		tcu::ScopedLogSection	section(log, "Test Info", "Test case information.");
+
+		log << TestLog::Message << "Context count: "	<< m_contexts.size()											<< TestLog::EndMessage;
+		log << TestLog::Message << "Surfaces count: "	<< m_surfaces.size()											<< TestLog::EndMessage;
+		log << TestLog::Message << "Sample count: "	<< m_spec.sampleCount												<< TestLog::EndMessage;
+		log << TestLog::Message << "Iteration count: "	<< m_spec.iterationCount										<< TestLog::EndMessage;
+		log << TestLog::Message << "Window count: "	<< m_windows.size()													<< TestLog::EndMessage;
+		log << TestLog::Message << "Pixmap count: "	<< m_pixmaps.size()													<< TestLog::EndMessage;
+		log << TestLog::Message << "PBuffer count: "	<< (m_surfaces.size() - m_windows.size() - m_pixmaps.size())	<< TestLog::EndMessage;
+
+		if (m_spec.release)
+			log << TestLog::Message << "Context is released after each use. Both binding and releasing context are included in result time." << TestLog::EndMessage;
+	}
+}
+
+void MakeCurrentPerfCase::logResults (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	log << TestLog::SampleList("Result", "Result")
+		<< TestLog::SampleInfo << TestLog::ValueInfo("Time", "Time", "us", QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)m_samples.size(); sampleNdx++)
+		log << TestLog::Sample << deInt64(m_samples[sampleNdx]) << TestLog::EndSample;
+
+	log << TestLog::EndSampleList;
+
+	// Log stats
+	{
+		deUint64	totalTimeUs				= 0;
+		deUint64	totalIterationCount		= 0;
+
+		float		iterationTimeMeanUs		= 0.0f;
+		float		iterationTimeMedianUs	= 0.0f;
+		float		iterationTimeVarianceUs	= 0.0f;
+		float		iterationTimeSkewnessUs	= 0.0f;
+		float		iterationTimeMinUs		= std::numeric_limits<float>::max();
+		float		iterationTimeMaxUs		= 0.0f;
+
+		std::sort(m_samples.begin(), m_samples.end());
+
+		// Calculate totals
+		for (int sampleNdx = 0; sampleNdx < (int)m_samples.size(); sampleNdx++)
+		{
+			totalTimeUs			+= m_samples[sampleNdx];
+			totalIterationCount	+= m_spec.iterationCount;
+		}
+
+		// Calculate mean and median
+		iterationTimeMeanUs		= ((float)(((double)totalTimeUs) / totalIterationCount));
+		iterationTimeMedianUs	= ((float)(((double)m_samples[m_samples.size() / 2]) / m_spec.iterationCount));
+
+		// Calculate variance
+		for (int sampleNdx = 0; sampleNdx < (int)m_samples.size(); sampleNdx++)
+		{
+			float iterationTimeUs	= (float)(((double)m_samples[sampleNdx]) / m_spec.iterationCount);
+			iterationTimeVarianceUs	+= std::pow(iterationTimeUs - iterationTimeMedianUs, 2.0f);
+		}
+
+		// Calculate min and max
+		for (int sampleNdx = 0; sampleNdx < (int)m_samples.size(); sampleNdx++)
+		{
+			float iterationTimeUs	= (float)(((double)m_samples[sampleNdx]) / m_spec.iterationCount);
+			iterationTimeMinUs		= std::min<float>(iterationTimeMinUs, iterationTimeUs);
+			iterationTimeMaxUs		= std::max<float>(iterationTimeMaxUs, iterationTimeUs);
+		}
+
+		iterationTimeVarianceUs /= m_samples.size();
+
+		// Calculate skewness
+		for (int sampleNdx = 0; sampleNdx < (int)m_samples.size(); sampleNdx++)
+		{
+			float iterationTimeUs	= (float)(((double)m_samples[sampleNdx]) / m_spec.iterationCount);
+			iterationTimeSkewnessUs	= std::pow((iterationTimeUs - iterationTimeMedianUs) / iterationTimeVarianceUs, 2.0f);
+		}
+
+		iterationTimeSkewnessUs /= (float)m_samples.size();
+
+		{
+			tcu::ScopedLogSection	section(log, "Result", "Statistics from results.");
+
+			log << TestLog::Message << "Total time: "	<< totalTimeUs				<< "us" << TestLog::EndMessage;
+			log << TestLog::Message << "Mean: "			<< iterationTimeMeanUs		<< "us" << TestLog::EndMessage;
+			log << TestLog::Message << "Median: "		<< iterationTimeMedianUs	<< "us" << TestLog::EndMessage;
+			log << TestLog::Message << "Variance: "		<< iterationTimeVarianceUs	<< "us" << TestLog::EndMessage;
+			log << TestLog::Message << "Skewness: "		<< iterationTimeSkewnessUs	<< "us" << TestLog::EndMessage;
+			log << TestLog::Message << "Min: "			<< iterationTimeMinUs		<< "us" << TestLog::EndMessage;
+			log << TestLog::Message << "Max: "			<< iterationTimeMaxUs		<< "us" << TestLog::EndMessage;
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)(((double)totalTimeUs)/totalIterationCount), 2).c_str());
+	}
+}
+
+TestCase::IterateResult MakeCurrentPerfCase::iterate (void)
+{
+	if (m_samples.size() == 0)
+		logTestInfo();
+
+	{
+		EGLDisplay	display		= m_eglTestCtx.getDisplay().getEGLDisplay();
+		deUint64	beginTimeUs	= deGetMicroseconds();
+
+		for (int iteration = 0; iteration < m_spec.iterationCount; iteration++)
+		{
+			EGLContext	context = m_contexts[m_rnd.getUint32() % m_contexts.size()];
+			EGLSurface	surface	= m_surfaces[m_rnd.getUint32() % m_surfaces.size()];
+
+			TCU_CHECK_EGL_CALL(eglMakeCurrent(display, surface, surface, context));
+
+			if (m_spec.release)
+				TCU_CHECK_EGL_CALL(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+		}
+
+		m_samples.push_back(deGetMicroseconds() - beginTimeUs);
+	}
+
+	if ((int)m_samples.size() == m_spec.sampleCount)
+	{
+		logResults();
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+MakeCurrentPerfTests::MakeCurrentPerfTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "make_current", "eglMakeCurrent performance tests")
+{
+}
+
+void MakeCurrentPerfTests::init (void)
+{
+	const int iterationCount	= 100;
+	const int sampleCount		= 100;
+
+	// Add simple test group
+	{
+		TestCaseGroup* simple = new TestCaseGroup(m_eglTestCtx, "simple", "Simple eglMakeCurrent performance tests using single context and surface");
+
+		const MakeCurrentPerfCase::SurfaceType types[] = {
+			MakeCurrentPerfCase::SURFACETYPE_PBUFFER,
+			MakeCurrentPerfCase::SURFACETYPE_PIXMAP,
+			MakeCurrentPerfCase::SURFACETYPE_WINDOW
+		};
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			for (int releaseNdx = 0; releaseNdx < 2; releaseNdx++)
+			{
+				MakeCurrentPerfCase::Spec spec;
+
+				spec.surfaceTypes	= types[typeNdx];
+				spec.contextCount	= 1;
+				spec.surfaceCount	= 1;
+				spec.release		= (releaseNdx == 1);
+				spec.iterationCount	= iterationCount;
+				spec.sampleCount	= sampleCount;
+
+				simple->addChild(new MakeCurrentPerfCase(m_eglTestCtx, spec, spec.toName().c_str(), spec.toDescription().c_str()));
+			}
+		}
+
+		addChild(simple);
+	}
+
+	// Add multi context test group
+	{
+		TestCaseGroup* multiContext = new TestCaseGroup(m_eglTestCtx, "multi_context", "eglMakeCurrent performance tests using multiple contexts and single surface");
+
+		const MakeCurrentPerfCase::SurfaceType types[] = {
+			MakeCurrentPerfCase::SURFACETYPE_PBUFFER,
+			MakeCurrentPerfCase::SURFACETYPE_PIXMAP,
+			MakeCurrentPerfCase::SURFACETYPE_WINDOW
+		};
+
+		const int contextCounts[] = {
+			10, 100
+		};
+
+		for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(contextCounts); contextCountNdx++)
+		{
+			for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+			{
+				for (int releaseNdx = 0; releaseNdx < 2; releaseNdx++)
+				{
+					MakeCurrentPerfCase::Spec spec;
+
+					spec.surfaceTypes	= types[typeNdx];
+					spec.contextCount	= contextCounts[contextCountNdx];
+					spec.surfaceCount	= 1;
+					spec.release		= (releaseNdx == 1);
+					spec.iterationCount	= iterationCount;
+					spec.sampleCount	= sampleCount;
+
+					multiContext->addChild(new MakeCurrentPerfCase(m_eglTestCtx, spec, spec.toName().c_str(), spec.toDescription().c_str()));
+				}
+			}
+		}
+
+		addChild(multiContext);
+	}
+
+	// Add multi surface test group
+	{
+		TestCaseGroup* multiSurface = new TestCaseGroup(m_eglTestCtx, "multi_surface", "eglMakeCurrent performance tests using single context and multiple surfaces");
+
+		const MakeCurrentPerfCase::SurfaceType types[] = {
+			MakeCurrentPerfCase::SURFACETYPE_PBUFFER,
+			MakeCurrentPerfCase::SURFACETYPE_PIXMAP,
+			MakeCurrentPerfCase::SURFACETYPE_WINDOW,
+
+			(MakeCurrentPerfCase::SurfaceType)(MakeCurrentPerfCase::SURFACETYPE_PBUFFER	|MakeCurrentPerfCase::SURFACETYPE_PIXMAP),
+			(MakeCurrentPerfCase::SurfaceType)(MakeCurrentPerfCase::SURFACETYPE_PBUFFER	|MakeCurrentPerfCase::SURFACETYPE_WINDOW),
+			(MakeCurrentPerfCase::SurfaceType)(MakeCurrentPerfCase::SURFACETYPE_PIXMAP	|MakeCurrentPerfCase::SURFACETYPE_WINDOW),
+
+			(MakeCurrentPerfCase::SurfaceType)(MakeCurrentPerfCase::SURFACETYPE_PBUFFER|MakeCurrentPerfCase::SURFACETYPE_PIXMAP|MakeCurrentPerfCase::SURFACETYPE_WINDOW)
+		};
+
+		const int surfaceCounts[] = {
+			10, 100
+		};
+
+		for (int surfaceCountNdx = 0; surfaceCountNdx < DE_LENGTH_OF_ARRAY(surfaceCounts); surfaceCountNdx++)
+		{
+			for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+			{
+				for (int releaseNdx = 0; releaseNdx < 2; releaseNdx++)
+				{
+					MakeCurrentPerfCase::Spec spec;
+
+					spec.surfaceTypes	= types[typeNdx];
+					spec.surfaceCount	= surfaceCounts[surfaceCountNdx];
+					spec.contextCount	= 1;
+					spec.release		= (releaseNdx == 1);
+					spec.iterationCount	= iterationCount;
+					spec.sampleCount	= sampleCount;
+
+					multiSurface->addChild(new MakeCurrentPerfCase(m_eglTestCtx, spec, spec.toName().c_str(), spec.toDescription().c_str()));
+				}
+			}
+		}
+
+		addChild(multiSurface);
+	}
+
+	// Add Complex? test group
+	{
+		TestCaseGroup* multi = new TestCaseGroup(m_eglTestCtx, "complex", "eglMakeCurrent performance tests using multiple contexts and multiple surfaces");
+
+		const MakeCurrentPerfCase::SurfaceType types[] = {
+			MakeCurrentPerfCase::SURFACETYPE_PBUFFER,
+			MakeCurrentPerfCase::SURFACETYPE_PIXMAP,
+			MakeCurrentPerfCase::SURFACETYPE_WINDOW,
+
+			(MakeCurrentPerfCase::SurfaceType)(MakeCurrentPerfCase::SURFACETYPE_PBUFFER	|MakeCurrentPerfCase::SURFACETYPE_PIXMAP),
+			(MakeCurrentPerfCase::SurfaceType)(MakeCurrentPerfCase::SURFACETYPE_PBUFFER	|MakeCurrentPerfCase::SURFACETYPE_WINDOW),
+			(MakeCurrentPerfCase::SurfaceType)(MakeCurrentPerfCase::SURFACETYPE_PIXMAP	|MakeCurrentPerfCase::SURFACETYPE_WINDOW),
+
+			(MakeCurrentPerfCase::SurfaceType)(MakeCurrentPerfCase::SURFACETYPE_PBUFFER|MakeCurrentPerfCase::SURFACETYPE_PIXMAP|MakeCurrentPerfCase::SURFACETYPE_WINDOW)
+		};
+
+		const int surfaceCounts[] = {
+			10, 100
+		};
+
+
+		const int contextCounts[] = {
+			10, 100
+		};
+
+		for (int surfaceCountNdx = 0; surfaceCountNdx < DE_LENGTH_OF_ARRAY(surfaceCounts); surfaceCountNdx++)
+		{
+			for (int contextCountNdx = 0; contextCountNdx < DE_LENGTH_OF_ARRAY(contextCounts); contextCountNdx++)
+			{
+				for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+				{
+					for (int releaseNdx = 0; releaseNdx < 2; releaseNdx++)
+					{
+						MakeCurrentPerfCase::Spec spec;
+
+						spec.surfaceTypes	= types[typeNdx];
+						spec.contextCount	= contextCounts[contextCountNdx];
+						spec.surfaceCount	= surfaceCounts[surfaceCountNdx];
+						spec.release		= (releaseNdx == 1);
+						spec.iterationCount	= iterationCount;
+						spec.sampleCount	= sampleCount;
+
+						multi->addChild(new MakeCurrentPerfCase(m_eglTestCtx, spec, spec.toName().c_str(), spec.toDescription().c_str()));
+					}
+				}
+			}
+		}
+
+		addChild(multi);
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglMakeCurrentPerfTests.hpp b/modules/egl/teglMakeCurrentPerfTests.hpp
new file mode 100644
index 0000000..c7df074
--- /dev/null
+++ b/modules/egl/teglMakeCurrentPerfTests.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLMAKECURRENTPERFTESTS_HPP
+#define _TEGLMAKECURRENTPERFTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief eglMakeCurrent performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class MakeCurrentPerfTests : public TestCaseGroup
+{
+public:
+			MakeCurrentPerfTests	(EglTestContext& eglTestCtx);
+	void	init					(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLMAKECURRENTPERFTESTS_HPP
diff --git a/modules/egl/teglMemoryStressTests.cpp b/modules/egl/teglMemoryStressTests.cpp
new file mode 100644
index 0000000..e9ba817
--- /dev/null
+++ b/modules/egl/teglMemoryStressTests.cpp
@@ -0,0 +1,599 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Memory object allocation stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "teglMemoryStressTests.hpp"
+
+#include "gluDefs.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuCommandLine.hpp"
+
+#include "deRandom.hpp"
+
+#include "deClock.h"
+#include "deString.h"
+
+#include "glwFunctions.hpp"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+#include <vector>
+#include <string>
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace
+{
+
+enum ObjectType
+{
+	OBJECTTYPE_PBUFFER = (1<<0),
+	OBJECTTYPE_CONTEXT = (1<<1),
+
+//	OBJECTTYPE_WINDOW,
+//	OBJECTTYPE_PIXMAP,
+};
+
+class MemoryAllocator
+{
+public:
+					MemoryAllocator			(EglTestContext& eglTestCtx, EGLDisplay display, EGLConfig config, int seed, ObjectType types, int minWidth, int minHeight, int maxWidth, int maxHeight, bool use);
+					~MemoryAllocator		(void);
+
+	bool			allocateUntilFailure	(void);
+	int				getAllocationCount		(void) const { return (int)(m_pbuffers.size() + m_contexts.size());	}
+	int				getContextCount			(void) const { return (int)m_contexts.size();						}
+	int				getPBufferCount			(void) const { return (int)m_pbuffers.size();						}
+	const string&	getErrorString			(void) const { return m_errorString;							}
+
+private:
+	void			allocatePBuffer			(void);
+	void			allocateContext			(void);
+
+	EglTestContext&			m_eglTestCtx;
+	EGLDisplay				m_display;
+	EGLConfig				m_config;
+	glw::Functions			m_gl;
+
+	de::Random				m_rnd;
+	bool					m_failed;
+	string					m_errorString;
+
+	ObjectType				m_types;
+	int						m_minWidth;
+	int						m_minHeight;
+	int						m_maxWidth;
+	int						m_maxHeight;
+	bool					m_use;
+
+	vector<EGLSurface>		m_pbuffers;
+	vector<EGLContext>		m_contexts;
+};
+
+MemoryAllocator::MemoryAllocator (EglTestContext& eglTestCtx, EGLDisplay display, EGLConfig config, int seed, ObjectType types, int minWidth, int minHeight, int maxWidth, int maxHeight, bool use)
+	: m_eglTestCtx	(eglTestCtx)
+	, m_display		(display)
+	, m_config		(config)
+
+	, m_rnd			(seed)
+	, m_failed		(false)
+
+	, m_types		(types)
+	, m_minWidth	(minWidth)
+	, m_minHeight	(minHeight)
+	, m_maxWidth	(maxWidth)
+	, m_maxHeight	(maxHeight)
+	, m_use			(use)
+{
+	m_eglTestCtx.getGLFunctions(m_gl, glu::ApiType::es(2,0));
+}
+
+MemoryAllocator::~MemoryAllocator (void)
+{
+	for (vector<EGLSurface>::const_iterator iter = m_pbuffers.begin(); iter != m_pbuffers.end(); ++iter)
+		TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, *iter));
+
+	m_pbuffers.clear();
+
+	for (vector<EGLContext>::const_iterator iter = m_contexts.begin(); iter != m_contexts.end(); ++iter)
+	{
+		TCU_CHECK_EGL_CALL(eglDestroyContext(m_display, *iter));
+	}
+
+	m_contexts.clear();
+}
+
+bool MemoryAllocator::allocateUntilFailure (void)
+{
+	const deUint64		timeLimitUs		= 10000000; // 10s
+	deUint64			beginTimeUs		= deGetMicroseconds();
+	vector<ObjectType>	types;
+
+	if ((m_types & OBJECTTYPE_CONTEXT) != 0)
+		types.push_back(OBJECTTYPE_CONTEXT);
+
+	if ((m_types & OBJECTTYPE_PBUFFER) != 0)
+		types.push_back(OBJECTTYPE_PBUFFER);
+
+	// If objects should be used. Create one of both at beginning to allow using them.
+	if (m_contexts.size() == 0 && m_pbuffers.size() == 0 && m_use)
+	{
+		allocateContext();
+		allocatePBuffer();
+	}
+
+	while (!m_failed)
+	{
+		ObjectType type = m_rnd.choose<ObjectType>(types.begin(), types.end());
+
+		switch (type)
+		{
+			case OBJECTTYPE_PBUFFER:
+				allocatePBuffer();
+				break;
+
+			case OBJECTTYPE_CONTEXT:
+				allocateContext();
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		if (deGetMicroseconds() - beginTimeUs > timeLimitUs)
+			return true;
+	}
+
+	return false;
+}
+
+void MemoryAllocator::allocatePBuffer (void)
+{
+	// Reserve space for new allocations
+	try
+	{
+		m_pbuffers.reserve(m_pbuffers.size() + 1);
+	}
+	catch (const std::bad_alloc&)
+	{
+		m_errorString 	= "std::bad_alloc when allocating more space for testcase. Out of host memory.";
+		m_failed		= true;
+		return;
+	}
+
+	// Allocate pbuffer
+	try
+	{
+		const EGLint	width	= m_rnd.getInt(m_minWidth, m_maxWidth);
+		const EGLint	height	= m_rnd.getInt(m_minHeight, m_maxHeight);
+
+		const EGLint attribList[] = {
+			EGL_WIDTH,	width,
+			EGL_HEIGHT, height,
+			EGL_NONE
+		};
+
+		EGLSurface surface = eglCreatePbufferSurface(m_display, m_config, attribList);
+		TCU_CHECK_EGL_MSG("eglCreatePbufferSurface");
+
+		DE_ASSERT(surface != EGL_NO_SURFACE);
+
+		m_pbuffers.push_back(surface);
+
+		if (m_use && m_contexts.size() > 0)
+		{
+			EGLContext				context		= m_rnd.choose<EGLContext>(m_contexts.begin(), m_contexts.end());
+			const float				red			= m_rnd.getFloat();
+			const float				green		= m_rnd.getFloat();
+			const float				blue		= m_rnd.getFloat();
+			const float				alpha		= m_rnd.getFloat();
+
+			TCU_CHECK_EGL_CALL(eglMakeCurrent(m_display, surface, surface, context));
+
+			m_gl.clearColor(red, green, blue, alpha);
+			GLU_EXPECT_NO_ERROR(m_gl.getError(), "glClearColor()");
+
+			m_gl.clear(GL_COLOR_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(m_gl.getError(), "glClear()");
+
+			TCU_CHECK_EGL_CALL(eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+		}
+	}
+	catch (const eglu::Error& error)
+	{
+		if (error.getError() == EGL_BAD_ALLOC)
+		{
+			m_errorString	= "eglCreatePbufferSurface returned EGL_BAD_ALLOC";
+			m_failed		= true;
+			return;
+		}
+		else
+			throw;
+	}
+}
+
+void MemoryAllocator::allocateContext (void)
+{
+	// Reserve space for new allocations
+	try
+	{
+		m_contexts.reserve(m_contexts.size() + 1);
+	}
+	catch (const std::bad_alloc&)
+	{
+		m_errorString 	= "std::bad_alloc when allocating more space for testcase. Out of host memory.";
+		m_failed		= true;
+		return;
+	}
+
+	// Allocate context
+	try
+	{
+		const EGLint attribList[] = {
+			EGL_CONTEXT_CLIENT_VERSION, 2,
+			EGL_NONE
+		};
+
+		TCU_CHECK_EGL_CALL(eglBindAPI(EGL_OPENGL_ES_API));
+		EGLContext context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, attribList);
+		TCU_CHECK_EGL_MSG("eglCreateContext");
+
+		DE_ASSERT(context != EGL_NO_CONTEXT);
+
+		m_contexts.push_back(context);
+
+		if (m_use && m_pbuffers.size() > 0)
+		{
+			EGLSurface				surface		= m_rnd.choose<EGLSurface>(m_pbuffers.begin(), m_pbuffers.end());
+			const float				red			= m_rnd.getFloat();
+			const float				green		= m_rnd.getFloat();
+			const float				blue		= m_rnd.getFloat();
+			const float				alpha		= m_rnd.getFloat();
+
+			TCU_CHECK_EGL_CALL(eglMakeCurrent(m_display, surface, surface, context));
+
+			m_gl.clearColor(red, green, blue, alpha);
+			GLU_EXPECT_NO_ERROR(m_gl.getError(), "glClearColor()");
+
+			m_gl.clear(GL_COLOR_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(m_gl.getError(), "glClear()");
+
+			TCU_CHECK_EGL_CALL(eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+		}
+	}
+	catch (const eglu::Error& error)
+	{
+		if (error.getError() == EGL_BAD_ALLOC)
+		{
+			m_errorString	= "eglCreateContext returned EGL_BAD_ALLOC";
+			m_failed		= true;
+			return;
+		}
+		else
+			throw;
+	}
+}
+
+} // anonymous
+
+class MemoryStressCase : public TestCase
+{
+public:
+	struct Spec
+	{
+		ObjectType	types;
+		int			minWidth;
+		int			minHeight;
+		int			maxWidth;
+		int			maxHeight;
+		bool		use;
+	};
+
+					MemoryStressCase	(EglTestContext& eglTestCtx, Spec spec, const char* name, const char* description);
+	void			init				(void);
+	void			deinit				(void);
+	IterateResult	iterate				(void);
+
+private:
+	Spec				m_spec;
+	vector<int>			m_allocationCounts;
+	MemoryAllocator*	m_allocator;
+
+	int					m_iteration;
+	int					m_iterationCount;
+	int					m_seed;
+	EGLDisplay			m_display;
+	EGLConfig			m_config;
+};
+
+MemoryStressCase::MemoryStressCase (EglTestContext& eglTestCtx, Spec spec, const char* name, const char* description)
+	: TestCase			(eglTestCtx, name, description)
+	, m_spec			(spec)
+	, m_allocator		(NULL)
+	, m_iteration		(0)
+	, m_iterationCount	(10)
+	, m_seed			(deStringHash(name))
+	, m_display			(EGL_NO_DISPLAY)
+	, m_config			(DE_NULL)
+{
+}
+
+void MemoryStressCase::init (void)
+{
+	EGLint			configCount = 0;
+	const EGLint	attribList[] = {
+		EGL_SURFACE_TYPE,		EGL_PBUFFER_BIT,
+		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
+		EGL_NONE
+	};
+
+	if (!m_testCtx.getCommandLine().isOutOfMemoryTestEnabled())
+	{
+		m_testCtx.getLog() << TestLog::Message << "Tests that exhaust memory are disabled, use --deqp-test-oom=enable command line option to enable." << TestLog::EndMessage;
+		throw tcu::NotSupportedError("OOM tests disabled");
+	}
+
+	m_display = m_eglTestCtx.getDisplay().getEGLDisplay();
+
+	TCU_CHECK_EGL_CALL(eglChooseConfig(m_display, attribList, &m_config, 1, &configCount));
+
+	TCU_CHECK(configCount != 0);
+}
+
+void MemoryStressCase::deinit (void)
+{
+	delete m_allocator;
+	m_allocator = DE_NULL;
+}
+
+TestCase::IterateResult MemoryStressCase::iterate (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (m_iteration < m_iterationCount)
+	{
+		try
+		{
+			if (!m_allocator)
+				m_allocator = new MemoryAllocator(m_eglTestCtx, m_display, m_config, m_seed, m_spec.types, m_spec.minWidth, m_spec.minHeight, m_spec.maxWidth, m_spec.maxHeight, m_spec.use);
+
+			if (m_allocator->allocateUntilFailure())
+			{
+				log << TestLog::Message << "Couldn't exhaust memory before timeout. Allocated " << m_allocator->getAllocationCount() << " objects." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+				delete m_allocator;
+				m_allocator = NULL;
+
+				return STOP;
+			}
+
+			log << TestLog::Message << "Iteration " << m_iteration << ": Allocated " << m_allocator->getAllocationCount() << " objects; " << m_allocator->getContextCount() << " contexts, " << m_allocator->getPBufferCount() << " PBuffers." << TestLog::EndMessage;
+			log << TestLog::Message << "Got expected error: " << m_allocator->getErrorString() << TestLog::EndMessage;
+			m_allocationCounts.push_back(m_allocator->getAllocationCount());
+
+			delete m_allocator;
+			m_allocator = NULL;
+
+			m_iteration++;
+
+			return CONTINUE;
+		} catch (...)
+		{
+			log << TestLog::Message << "Iteration " << m_iteration << ": Allocated " << m_allocator->getAllocationCount() << " objects; " << m_allocator->getContextCount() << " contexts, " << m_allocator->getPBufferCount() << " PBuffers." << TestLog::EndMessage;
+			log << TestLog::Message << "Unexpected error" << TestLog::EndMessage;
+			throw;
+		}
+	}
+	else
+	{
+		// Analyze number of passed allocations.
+		int min = m_allocationCounts[0];
+		int max = m_allocationCounts[0];
+
+		float threshold = 50.0f;
+
+		for (int allocNdx = 0; allocNdx < (int)m_allocationCounts.size(); allocNdx++)
+		{
+			min = deMin32(m_allocationCounts[allocNdx], min);
+			max = deMax32(m_allocationCounts[allocNdx], max);
+		}
+
+		if (min == 0 && max != 0)
+		{
+			log << TestLog::Message << "Allocation count zero" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		}
+		else
+		{
+			float change = (min - max) / ((float)(max));
+
+			if (change > threshold)
+			{
+				log << TestLog::Message << "Allocated objects max: " << max << ", min: " << min << ", difference: " << change << "% threshold: " << threshold << "%" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Allocation count variation");
+			}
+			else
+				m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		}
+
+		return STOP;
+	}
+}
+
+MemoryStressTests::MemoryStressTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "memory", "Memory allocation stress tests")
+{
+}
+
+void MemoryStressTests::init (void)
+{
+	// Check small pbuffers 256x256
+	{
+		MemoryStressCase::Spec spec;
+
+		spec.types		= OBJECTTYPE_PBUFFER;
+		spec.minWidth	= 256;
+		spec.minHeight	= 256;
+		spec.maxWidth	= 256;
+		spec.maxHeight	= 256;
+		spec.use		= false;
+
+		addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_256x256", "PBuffer allocation stress tests"));
+	}
+
+	// Check small pbuffers 256x256 and use them
+	{
+		MemoryStressCase::Spec spec;
+
+		spec.types		= OBJECTTYPE_PBUFFER;
+		spec.minWidth	= 256;
+		spec.minHeight	= 256;
+		spec.maxWidth	= 256;
+		spec.maxHeight	= 256;
+		spec.use		= true;
+
+		addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_256x256_use", "PBuffer allocation stress tests"));
+	}
+
+	// Check big pbuffers 1024x1024
+	{
+		MemoryStressCase::Spec spec;
+
+		spec.types		= OBJECTTYPE_PBUFFER;
+		spec.minWidth	= 1024;
+		spec.minHeight	= 1024;
+		spec.maxWidth	= 1024;
+		spec.maxHeight	= 1024;
+		spec.use		= false;
+
+		addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_1024x1024", "PBuffer allocation stress tests"));
+	}
+
+	// Check big pbuffers 1024x1024 and use them
+	{
+		MemoryStressCase::Spec spec;
+
+		spec.types		= OBJECTTYPE_PBUFFER;
+		spec.minWidth	= 1024;
+		spec.minHeight	= 1024;
+		spec.maxWidth	= 1024;
+		spec.maxHeight	= 1024;
+		spec.use		= true;
+
+		addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_1024x1024_use", "PBuffer allocation stress tests"));
+	}
+
+	// Check different sized pbuffers
+	{
+		MemoryStressCase::Spec spec;
+
+		spec.types		= OBJECTTYPE_PBUFFER;
+		spec.minWidth	= 64;
+		spec.minHeight	= 64;
+		spec.maxWidth	= 1024;
+		spec.maxHeight	= 1024;
+		spec.use		= false;
+
+		addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer", "PBuffer allocation stress tests"));
+	}
+
+	// Check different sized pbuffers and use them
+	{
+		MemoryStressCase::Spec spec;
+
+		spec.types		= OBJECTTYPE_PBUFFER;
+		spec.minWidth	= 64;
+		spec.minHeight	= 64;
+		spec.maxWidth	= 1024;
+		spec.maxHeight	= 1024;
+		spec.use		= true;
+
+		addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_use", "PBuffer allocation stress tests"));
+	}
+
+	// Check contexts
+	{
+		MemoryStressCase::Spec spec;
+
+		spec.types		= OBJECTTYPE_CONTEXT;
+		spec.minWidth	= 1024;
+		spec.minHeight	= 1024;
+		spec.maxWidth	= 1024;
+		spec.maxHeight	= 1024;
+		spec.use		= false;
+
+		addChild(new MemoryStressCase(m_eglTestCtx, spec, "context", "Context allocation stress tests"));
+	}
+
+	// Check contexts and use them
+	{
+		MemoryStressCase::Spec spec;
+
+		spec.types		= OBJECTTYPE_CONTEXT;
+		spec.minWidth	= 1024;
+		spec.minHeight	= 1024;
+		spec.maxWidth	= 1024;
+		spec.maxHeight	= 1024;
+		spec.use		= true;
+
+		addChild(new MemoryStressCase(m_eglTestCtx, spec, "context_use", "Context allocation stress tests"));
+	}
+
+	// Check contexts and pbuffers
+	{
+		MemoryStressCase::Spec spec;
+
+		spec.types		= (ObjectType)(OBJECTTYPE_PBUFFER|OBJECTTYPE_CONTEXT);
+		spec.minWidth	= 64;
+		spec.minHeight	= 64;
+		spec.maxWidth	= 1024;
+		spec.maxHeight	= 1024;
+		spec.use		= false;
+
+		addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_context", "PBuffer and context allocation stress tests"));
+	}
+
+	// Check contexts and pbuffers and use
+	{
+		MemoryStressCase::Spec spec;
+
+		spec.types		= (ObjectType)(OBJECTTYPE_PBUFFER|OBJECTTYPE_CONTEXT);
+		spec.minWidth	= 64;
+		spec.minHeight	= 64;
+		spec.maxWidth	= 1024;
+		spec.maxHeight	= 1024;
+		spec.use		= true;
+
+		addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_context_use", "PBuffer and context allocation stress tests"));
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglMemoryStressTests.hpp b/modules/egl/teglMemoryStressTests.hpp
new file mode 100644
index 0000000..e517454
--- /dev/null
+++ b/modules/egl/teglMemoryStressTests.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLMEMORYSTRESSTESTS_HPP
+#define _TEGLMEMORYSTRESSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Memory object allocation stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class MemoryStressTests : public TestCaseGroup
+{
+public:
+			MemoryStressTests	(EglTestContext& eglTestCtx);
+	void	init				(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLMEMORYSTRESSTESTS_HPP
diff --git a/modules/egl/teglMultiThreadTests.cpp b/modules/egl/teglMultiThreadTests.cpp
new file mode 100644
index 0000000..8ccffbc
--- /dev/null
+++ b/modules/egl/teglMultiThreadTests.cpp
@@ -0,0 +1,1502 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multi threaded EGL tests
+ *//*--------------------------------------------------------------------*/
+#include "teglMultiThreadTests.hpp"
+
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluUtil.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuCommandLine.hpp"
+
+#include "deRandom.hpp"
+
+#include "deThread.hpp"
+#include "deMutex.hpp"
+#include "deSemaphore.hpp"
+
+#include "deAtomic.h"
+#include "deClock.h"
+
+#include <vector>
+#include <string>
+#include <sstream>
+
+using std::vector;
+using std::string;
+using std::pair;
+using std::ostringstream;
+
+namespace deqp
+{
+namespace egl
+{
+
+class ThreadLog
+{
+public:
+	class BeginMessageToken	{};
+	class EndMessageToken	{};
+
+	struct Message
+	{
+					Message	(deUint64 timeUs_, const char* msg_) : timeUs(timeUs_), msg(msg_) {}
+
+		deUint64	timeUs;
+		string		msg;
+	};
+
+								ThreadLog	(void)						{ m_messages.reserve(100); }
+
+	ThreadLog&					operator<<	(const BeginMessageToken&)	{ return *this; }
+	ThreadLog&					operator<<	(const EndMessageToken&);
+
+	template<class T>
+	ThreadLog&					operator<< 	(const T& t)				{ m_message << t; return *this; }
+	const vector<Message>&		getMessages (void) const				{ return m_messages; }
+
+	static BeginMessageToken	BeginMessage;
+	static EndMessageToken		EndMessage;
+
+private:
+	ostringstream		m_message;
+	vector<Message>		m_messages;
+};
+
+ThreadLog& ThreadLog::operator<< (const EndMessageToken&)
+{
+	m_messages.push_back(Message(deGetMicroseconds(), m_message.str().c_str()));
+	m_message.str("");
+	return *this;
+}
+
+ThreadLog::BeginMessageToken	ThreadLog::BeginMessage;
+ThreadLog::EndMessageToken		ThreadLog::EndMessage;
+
+class MultiThreadedTest;
+
+class TestThread : public de::Thread
+{
+public:
+	enum ThreadStatus
+	{
+		THREADSTATUS_NOT_STARTED = 0,
+		THREADSTATUS_RUNNING,
+		THREADSTATUS_READY,
+
+		THREADSTATUS_NOT_SUPPORTED,
+		THREADSTATUS_ERROR
+	};
+
+					TestThread	(MultiThreadedTest& test, int id);
+	void			run			(void);
+
+	ThreadStatus	getStatus	(void) const	{ return m_status; }
+	ThreadLog&		getLog		(void)			{ return m_log; }
+
+	int				getId		(void) const	{ return m_id; }
+
+	void			setStatus	(ThreadStatus status)	{ m_status = status; }
+
+
+	// Test has stopped
+	class TestStop {};
+
+
+private:
+	MultiThreadedTest&	m_test;
+	const int			m_id;
+	ThreadStatus		m_status;
+	ThreadLog			m_log;
+};
+
+class MultiThreadedTest : public TestCase
+{
+public:
+							MultiThreadedTest	(EglTestContext& eglTestCtx, const char* name, const char* description, int threadCount, deUint64 timeoutUs);
+	virtual					~MultiThreadedTest	(void);
+	virtual void			deinit				(void) {}
+
+	virtual bool			runThread			(TestThread& thread) = 0;
+	virtual IterateResult	iterate				(void);
+	bool					execTest			(TestThread& thread);
+
+protected:
+	void					barrier				(TestThread& thread);
+
+private:
+	int						m_threadCount;
+	bool					m_initialized;
+	deUint64				m_startTimeUs;
+	const deUint64			m_timeoutUs;
+	vector<TestThread*>		m_threads;
+
+	volatile deInt32		m_barrierWaiters;
+	de::Semaphore			m_barrierSemaphore1;
+	de::Semaphore			m_barrierSemaphore2;
+};
+
+TestThread::TestThread (MultiThreadedTest& test, int id)
+	: m_test	(test)
+	, m_id		(id)
+	, m_status	(THREADSTATUS_NOT_STARTED)
+{
+}
+
+void TestThread::run (void)
+{
+	m_status = THREADSTATUS_RUNNING;
+
+	try
+	{
+		if (m_test.execTest(*this))
+			m_status = THREADSTATUS_READY;
+		else
+			m_status = THREADSTATUS_ERROR;
+	}
+	catch (const TestThread::TestStop&)
+	{
+		getLog() << ThreadLog::BeginMessage << "Thread stopped" << ThreadLog::EndMessage;
+	}
+	catch (const tcu::NotSupportedError& e)
+	{
+		getLog() << ThreadLog::BeginMessage << "Not supported: '" << e.what() << "'" << ThreadLog::EndMessage;
+	}
+	catch (const std::exception& e)
+	{
+		getLog() << ThreadLog::BeginMessage << "Got exception: '" << e.what() << "'" << ThreadLog::EndMessage;
+	}
+	catch (...)
+	{
+		getLog() << ThreadLog::BeginMessage << "Unknown exception" << ThreadLog::EndMessage;
+	}
+}
+
+bool MultiThreadedTest::execTest (TestThread& thread)
+{
+	bool isOk = false;
+
+	try
+	{
+		isOk = runThread(thread);
+	}
+	catch (const TestThread::TestStop&)
+	{
+		// Thread exited due to error in other thread
+		throw;
+	}
+	catch (const tcu::NotSupportedError&)
+	{
+		// Set status of each thread
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			m_threads[threadNdx]->setStatus(TestThread::THREADSTATUS_NOT_SUPPORTED);
+
+		// Release barriers
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		{
+			m_barrierSemaphore1.increment();
+			m_barrierSemaphore2.increment();
+		}
+
+		throw;
+	}
+	catch(...)
+	{
+		// Set status of each thread
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			m_threads[threadNdx]->setStatus(TestThread::THREADSTATUS_ERROR);
+
+		// Release barriers
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		{
+			m_barrierSemaphore1.increment();
+			m_barrierSemaphore2.increment();
+		}
+
+		throw;
+	}
+
+	return isOk;
+}
+
+MultiThreadedTest::MultiThreadedTest (EglTestContext& eglTestCtx, const char* name, const char* description, int threadCount, deUint64 timeoutUs)
+	: TestCase				(eglTestCtx, name, description)
+	, m_threadCount			(threadCount)
+	, m_initialized			(false)
+	, m_startTimeUs			(0)
+	, m_timeoutUs			(timeoutUs)
+
+	, m_barrierWaiters		(0)
+	, m_barrierSemaphore1	(0, 0)
+	, m_barrierSemaphore2	(1, 0)
+{
+}
+
+MultiThreadedTest::~MultiThreadedTest (void)
+{
+	for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		delete m_threads[threadNdx];
+	m_threads.clear();
+}
+
+void MultiThreadedTest::barrier (TestThread& thread)
+{
+	{
+		const deInt32 waiters = deAtomicIncrement32(&m_barrierWaiters);
+
+		if (waiters == m_threadCount)
+		{
+			m_barrierSemaphore2.decrement();
+			m_barrierSemaphore1.increment();
+		}
+		else
+		{
+			m_barrierSemaphore1.decrement();
+			m_barrierSemaphore1.increment();
+		}
+	}
+
+	{
+		const deInt32 waiters = deAtomicDecrement32(&m_barrierWaiters);
+
+		if (waiters == 0)
+		{
+			m_barrierSemaphore1.decrement();
+			m_barrierSemaphore2.increment();
+		}
+		else
+		{
+			m_barrierSemaphore2.decrement();
+			m_barrierSemaphore2.increment();
+		}
+	}
+
+	// Barrier was released due an error in other thread
+	if (thread.getStatus() != TestThread::THREADSTATUS_RUNNING)
+		throw TestThread::TestStop();
+}
+
+TestCase::IterateResult MultiThreadedTest::iterate (void)
+{
+	if (!m_initialized)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Thread timeout limit: " << m_timeoutUs << "us" << tcu::TestLog::EndMessage;
+
+		// Create threads
+		m_threads.reserve(m_threadCount);
+
+		for (int threadNdx = 0; threadNdx < m_threadCount; threadNdx++)
+			m_threads.push_back(new TestThread(*this, threadNdx));
+
+		m_startTimeUs = deGetMicroseconds();
+
+		// Run threads
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			m_threads[threadNdx]->start();
+
+		m_initialized = true;
+	}
+
+	int readyCount = 0;
+	for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+	{
+		if (m_threads[threadNdx]->getStatus() != TestThread::THREADSTATUS_RUNNING)
+			readyCount++;
+	}
+
+	if (readyCount == m_threadCount)
+	{
+		// Join threads
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			m_threads[threadNdx]->join();
+
+		bool isOk			= true;
+		bool notSupported	= false;
+
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+		{
+			if (m_threads[threadNdx]->getStatus() == TestThread::THREADSTATUS_ERROR)
+				isOk = false;
+
+			if (m_threads[threadNdx]->getStatus() == TestThread::THREADSTATUS_NOT_SUPPORTED)
+				notSupported = true;
+		}
+
+		// Get logs
+		{
+			vector<int> messageNdx;
+
+			messageNdx.resize(m_threads.size(), 0);
+
+			while (true)
+			{
+				int			nextThreadNdx		= -1;
+				deUint64	nextThreadTimeUs	= 0;
+
+				for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+				{
+					if (messageNdx[threadNdx] >= (int)m_threads[threadNdx]->getLog().getMessages().size())
+						continue;
+
+					if (nextThreadNdx == -1 || nextThreadTimeUs > m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs)
+					{
+						nextThreadNdx		= threadNdx;
+						nextThreadTimeUs	= m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs;
+					}
+				}
+
+				if (nextThreadNdx == -1)
+					break;
+
+				m_testCtx.getLog() << tcu::TestLog::Message << "[" << (nextThreadTimeUs - m_startTimeUs) << "] (" << nextThreadNdx << ") " << m_threads[nextThreadNdx]->getLog().getMessages()[messageNdx[nextThreadNdx]].msg << tcu::TestLog::EndMessage;
+
+				messageNdx[nextThreadNdx]++;
+			}
+		}
+
+		// Destroy threads
+		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+			delete m_threads[threadNdx];
+
+		m_threads.clear();
+
+		// Set result
+		if (isOk)
+		{
+			if (notSupported)
+				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not Supported");
+			else
+				m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		}
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+		return STOP;
+	}
+	else
+	{
+		// Check for timeout
+		const deUint64 currentTimeUs = deGetMicroseconds();
+
+		if (currentTimeUs - m_startTimeUs > m_timeoutUs)
+		{
+			// Get logs
+			{
+				vector<int> messageNdx;
+
+				messageNdx.resize(m_threads.size(), 0);
+
+				while (true)
+				{
+					int			nextThreadNdx		= -1;
+					deUint64	nextThreadTimeUs	= 0;
+
+					for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
+					{
+						if (messageNdx[threadNdx] >= (int)m_threads[threadNdx]->getLog().getMessages().size())
+							continue;
+
+						if (nextThreadNdx == -1 || nextThreadTimeUs > m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs)
+						{
+							nextThreadNdx		= threadNdx;
+							nextThreadTimeUs	= m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs;
+						}
+					}
+
+					if (nextThreadNdx == -1)
+						break;
+
+					m_testCtx.getLog() << tcu::TestLog::Message << "[" << (nextThreadTimeUs - m_startTimeUs) << "] (" << nextThreadNdx << ") " << m_threads[nextThreadNdx]->getLog().getMessages()[messageNdx[nextThreadNdx]].msg << tcu::TestLog::EndMessage;
+
+					messageNdx[nextThreadNdx]++;
+				}
+			}
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "[" << (currentTimeUs - m_startTimeUs) << "] (-) Timeout, Limit: " << m_timeoutUs << "us" << tcu::TestLog::EndMessage;
+			m_testCtx.getLog() << tcu::TestLog::Message << "[" << (currentTimeUs - m_startTimeUs) << "] (-) Trying to perform resource cleanup..." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		// Sleep
+		deSleep(10);
+	}
+
+	return CONTINUE;
+}
+
+namespace
+{
+
+const char* configAttributeToString (EGLint e)
+{
+	switch (e)
+	{
+		case EGL_BUFFER_SIZE:				return "EGL_BUFFER_SIZE";
+		case EGL_RED_SIZE:					return "EGL_RED_SIZE";
+		case EGL_GREEN_SIZE:				return "EGL_GREEN_SIZE";
+		case EGL_BLUE_SIZE:					return "EGL_BLUE_SIZE";
+		case EGL_LUMINANCE_SIZE:			return "EGL_LUMINANCE_SIZE";
+		case EGL_ALPHA_SIZE:				return "EGL_ALPHA_SIZE";
+		case EGL_ALPHA_MASK_SIZE:			return "EGL_ALPHA_MASK_SIZE";
+		case EGL_BIND_TO_TEXTURE_RGB:		return "EGL_BIND_TO_TEXTURE_RGB";
+		case EGL_BIND_TO_TEXTURE_RGBA:		return "EGL_BIND_TO_TEXTURE_RGBA";
+		case EGL_COLOR_BUFFER_TYPE:			return "EGL_COLOR_BUFFER_TYPE";
+		case EGL_CONFIG_CAVEAT:				return "EGL_CONFIG_CAVEAT";
+		case EGL_CONFIG_ID:					return "EGL_CONFIG_ID";
+		case EGL_CONFORMANT:				return "EGL_CONFORMANT";
+		case EGL_DEPTH_SIZE:				return "EGL_DEPTH_SIZE";
+		case EGL_LEVEL:						return "EGL_LEVEL";
+		case EGL_MAX_PBUFFER_WIDTH:			return "EGL_MAX_PBUFFER_WIDTH";
+		case EGL_MAX_PBUFFER_HEIGHT:		return "EGL_MAX_PBUFFER_HEIGHT";
+		case EGL_MAX_PBUFFER_PIXELS:		return "EGL_MAX_PBUFFER_PIXELS";
+		case EGL_MAX_SWAP_INTERVAL:			return "EGL_MAX_SWAP_INTERVAL";
+		case EGL_MIN_SWAP_INTERVAL:			return "EGL_MIN_SWAP_INTERVAL";
+		case EGL_NATIVE_RENDERABLE:			return "EGL_NATIVE_RENDERABLE";
+		case EGL_NATIVE_VISUAL_ID:			return "EGL_NATIVE_VISUAL_ID";
+		case EGL_NATIVE_VISUAL_TYPE:		return "EGL_NATIVE_VISUAL_TYPE";
+		case EGL_RENDERABLE_TYPE:			return "EGL_RENDERABLE_TYPE";
+		case EGL_SAMPLE_BUFFERS:			return "EGL_SAMPLE_BUFFERS";
+		case EGL_SAMPLES:					return "EGL_SAMPLES";
+		case EGL_STENCIL_SIZE:				return "EGL_STENCIL_SIZE";
+		case EGL_SURFACE_TYPE:				return "EGL_SURFACE_TYPE";
+		case EGL_TRANSPARENT_TYPE:			return "EGL_TRANSPARENT_TYPE";
+		case EGL_TRANSPARENT_RED_VALUE:		return "EGL_TRANSPARENT_RED_VALUE";
+		case EGL_TRANSPARENT_GREEN_VALUE:	return "EGL_TRANSPARENT_GREEN_VALUE";
+		case EGL_TRANSPARENT_BLUE_VALUE:	return "EGL_TRANSPARENT_BLUE_VALUE";
+		default:							return "<Unknown>";
+	}
+}
+
+} // anonymous
+
+class MultiThreadedConfigTest : public MultiThreadedTest
+{
+public:
+				MultiThreadedConfigTest		(EglTestContext& context, const char* name, const char* description, int getConfigs, int chooseConfigs, int query);
+	bool		runThread					(TestThread& thread);
+
+private:
+	EGLDisplay	m_display;
+	const int	m_getConfigs;
+	const int	m_chooseConfigs;
+	const int	m_query;
+};
+
+MultiThreadedConfigTest::MultiThreadedConfigTest (EglTestContext& context, const char* name, const char* description, int getConfigs, int chooseConfigs, int query)
+	: MultiThreadedTest (context, name, description, 2, 20000000/*us = 20s*/) // \todo [mika] Set timeout to something relevant to frameworks timeout?
+	, m_display			(EGL_NO_DISPLAY)
+	, m_getConfigs		(getConfigs)
+	, m_chooseConfigs	(chooseConfigs)
+	, m_query			(query)
+{
+}
+
+bool MultiThreadedConfigTest::runThread (TestThread& thread)
+{
+	de::Random			rnd(deInt32Hash(thread.getId() + 10435));
+	vector<EGLConfig>	configs;
+
+	if (thread.getId() == 0)
+		m_display = m_eglTestCtx.getDisplay().getEGLDisplay();
+
+	barrier(thread);
+
+	for (int getConfigsNdx = 0; getConfigsNdx < m_getConfigs; getConfigsNdx++)
+	{
+		EGLint configCount;
+
+		// Get number of configs
+		{
+			EGLBoolean result;
+
+			result = eglGetConfigs(m_display, NULL, 0, &configCount);
+			thread.getLog() << ThreadLog::BeginMessage << result << " = eglGetConfigs(" << m_display << ", NULL, 0, " << configCount << ")" <<  ThreadLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglGetConfigs()");
+
+			if (!result)
+				return false;
+		}
+
+		configs.resize(configs.size() + configCount);
+
+		// Get configs
+		if (configCount != 0)
+		{
+			EGLBoolean result;
+
+			result = eglGetConfigs(m_display, &(configs[configs.size() - configCount]), configCount, &configCount);
+			thread.getLog() << ThreadLog::BeginMessage << result << " = eglGetConfigs(" << m_display << ", &configs' " << configCount << ", " << configCount << ")" <<  ThreadLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglGetConfigs()");
+
+			if (!result)
+				return false;
+		}
+
+		// Pop configs to stop config list growing
+		if (configs.size() > 40)
+		{
+			configs.erase(configs.begin() + 40, configs.end());
+		}
+		else
+		{
+			const int popCount = rnd.getInt(0, (int)(configs.size()-2));
+
+			configs.erase(configs.begin() + (configs.size() - popCount), configs.end());
+		}
+	}
+
+	for (int chooseConfigsNdx = 0; chooseConfigsNdx < m_chooseConfigs; chooseConfigsNdx++)
+	{
+		EGLint configCount;
+
+		static const EGLint attribList[] = {
+			EGL_NONE
+		};
+
+		// Get number of configs
+		{
+			EGLBoolean result;
+
+			result = eglChooseConfig(m_display, attribList, NULL, 0, &configCount);
+			thread.getLog() << ThreadLog::BeginMessage << result << " = eglChooseConfig(" << m_display << ", { EGL_NONE }, NULL, 0, " << configCount << ")" <<  ThreadLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglChooseConfig()");
+
+			if (!result)
+				return false;
+		}
+
+		configs.resize(configs.size() + configCount);
+
+		// Get configs
+		if (configCount != 0)
+		{
+			EGLBoolean result;
+
+			result = eglChooseConfig(m_display, attribList, &(configs[configs.size() - configCount]), configCount, &configCount);
+			thread.getLog() << ThreadLog::BeginMessage << result << " = eglChooseConfig(" << m_display << ", { EGL_NONE }, &configs, " << configCount << ", " << configCount << ")" <<  ThreadLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglChooseConfig()");
+
+			if (!result)
+				return false;
+		}
+
+		// Pop configs to stop config list growing
+		if (configs.size() > 40)
+		{
+			configs.erase(configs.begin() + 40, configs.end());
+		}
+		else
+		{
+			const int popCount = rnd.getInt(0, (int)(configs.size()-2));
+
+			configs.erase(configs.begin() + (configs.size() - popCount), configs.end());
+		}
+	}
+
+	{
+		// Perform queries on configs
+		static const EGLint attributes[] =
+		{
+			EGL_BUFFER_SIZE,
+			EGL_RED_SIZE,
+			EGL_GREEN_SIZE,
+			EGL_BLUE_SIZE,
+			EGL_LUMINANCE_SIZE,
+			EGL_ALPHA_SIZE,
+			EGL_ALPHA_MASK_SIZE,
+			EGL_BIND_TO_TEXTURE_RGB,
+			EGL_BIND_TO_TEXTURE_RGBA,
+			EGL_COLOR_BUFFER_TYPE,
+			EGL_CONFIG_CAVEAT,
+			EGL_CONFIG_ID,
+			EGL_CONFORMANT,
+			EGL_DEPTH_SIZE,
+			EGL_LEVEL,
+			EGL_MAX_PBUFFER_WIDTH,
+			EGL_MAX_PBUFFER_HEIGHT,
+			EGL_MAX_PBUFFER_PIXELS,
+			EGL_MAX_SWAP_INTERVAL,
+			EGL_MIN_SWAP_INTERVAL,
+			EGL_NATIVE_RENDERABLE,
+			EGL_NATIVE_VISUAL_ID,
+			EGL_NATIVE_VISUAL_TYPE,
+			EGL_RENDERABLE_TYPE,
+			EGL_SAMPLE_BUFFERS,
+			EGL_SAMPLES,
+			EGL_STENCIL_SIZE,
+			EGL_SURFACE_TYPE,
+			EGL_TRANSPARENT_TYPE,
+			EGL_TRANSPARENT_RED_VALUE,
+			EGL_TRANSPARENT_GREEN_VALUE,
+			EGL_TRANSPARENT_BLUE_VALUE
+		};
+
+		for (int queryNdx = 0; queryNdx < m_query; queryNdx++)
+		{
+			const EGLint	attribute	= attributes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(attributes)-1)];
+			EGLConfig		config		= configs[rnd.getInt(0, (int)(configs.size()-1))];
+			EGLint			value;
+			EGLBoolean		result;
+
+			result = eglGetConfigAttrib(m_display, config, attribute, &value);
+			thread.getLog() << ThreadLog::BeginMessage << result << " = eglGetConfigAttrib(" << m_display << ", " << config << ", " << configAttributeToString(attribute) << ", " << value << ")" <<  ThreadLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglGetConfigAttrib()");
+
+			if (!result)
+				return false;
+		}
+	}
+
+	return true;
+}
+
+class MultiThreadedObjectTest : public MultiThreadedTest
+{
+public:
+	enum Type
+	{
+		TYPE_PBUFFER			= (1<<0),
+		TYPE_PIXMAP				= (1<<1),
+		TYPE_WINDOW				= (1<<2),
+		TYPE_SINGLE_WINDOW		= (1<<3),
+		TYPE_CONTEXT			= (1<<4)
+	};
+
+					MultiThreadedObjectTest			(EglTestContext& context, const char* name, const char* description, deUint32 types);
+					~MultiThreadedObjectTest		(void);
+
+	virtual void	deinit							(void);
+
+	bool			runThread						(TestThread& thread);
+
+	void			createDestroyObjects			(TestThread& thread, int count);
+	void			pushObjectsToShared				(TestThread& thread);
+	void			pullObjectsFromShared			(TestThread& thread, int pbufferCount, int pixmapCount, int windowCount, int contextCount);
+	void			querySetSharedObjects			(TestThread& thread, int count);
+	void			destroyObjects					(TestThread& thread);
+
+private:
+	EGLDisplay			m_display;
+	EGLConfig			m_config;
+	de::Random			m_rnd0;
+	de::Random			m_rnd1;
+	Type				m_types;
+
+	volatile deUint32	m_hasWindow;
+
+	vector<pair<eglu::NativePixmap*, EGLSurface> >	m_sharedNativePixmaps;
+	vector<pair<eglu::NativePixmap*, EGLSurface> >	m_nativePixmaps0;
+	vector<pair<eglu::NativePixmap*, EGLSurface> >	m_nativePixmaps1;
+
+	vector<pair<eglu::NativeWindow*, EGLSurface> >	m_sharedNativeWindows;
+	vector<pair<eglu::NativeWindow*, EGLSurface> >	m_nativeWindows0;
+	vector<pair<eglu::NativeWindow*, EGLSurface> >	m_nativeWindows1;
+
+	vector<EGLSurface>								m_sharedPbuffers;
+	vector<EGLSurface>								m_pbuffers0;
+	vector<EGLSurface>								m_pbuffers1;
+
+	vector<EGLContext>								m_sharedContexts;
+	vector<EGLContext>								m_contexts0;
+	vector<EGLContext>								m_contexts1;
+};
+
+MultiThreadedObjectTest::MultiThreadedObjectTest (EglTestContext& context, const char* name, const char* description, deUint32 type)
+	: MultiThreadedTest (context, name, description, 2, 20000000/*us = 20s*/) // \todo [mika] Set timeout to something relevant to frameworks timeout?
+	, m_display			(EGL_NO_DISPLAY)
+	, m_config			(DE_NULL)
+	, m_rnd0			(58204327)
+	, m_rnd1			(230983)
+	, m_types			((Type)type)
+	, m_hasWindow		(0)
+{
+}
+
+MultiThreadedObjectTest::~MultiThreadedObjectTest (void)
+{
+	deinit();
+}
+
+void MultiThreadedObjectTest::deinit (void)
+{
+	// Clear pbuffers
+	for (int pbufferNdx = 0; pbufferNdx < (int)m_pbuffers0.size(); pbufferNdx++)
+	{
+		if (m_pbuffers0[pbufferNdx] != EGL_NO_SURFACE)
+		{
+			eglDestroySurface(m_display, m_pbuffers0[pbufferNdx]);
+			TCU_CHECK_EGL_MSG("eglDestroySurface()");
+			m_pbuffers0[pbufferNdx] = EGL_NO_SURFACE;
+		}
+	}
+	m_pbuffers0.clear();
+
+	for (int pbufferNdx = 0; pbufferNdx < (int)m_pbuffers1.size(); pbufferNdx++)
+	{
+		if (m_pbuffers1[pbufferNdx] != EGL_NO_SURFACE)
+		{
+			eglDestroySurface(m_display, m_pbuffers1[pbufferNdx]);
+			TCU_CHECK_EGL_MSG("eglDestroySurface()");
+			m_pbuffers1[pbufferNdx] = EGL_NO_SURFACE;
+		}
+	}
+	m_pbuffers1.clear();
+
+	for (int pbufferNdx = 0; pbufferNdx < (int)m_sharedPbuffers.size(); pbufferNdx++)
+	{
+		if (m_sharedPbuffers[pbufferNdx] != EGL_NO_SURFACE)
+		{
+			eglDestroySurface(m_display, m_sharedPbuffers[pbufferNdx]);
+			TCU_CHECK_EGL_MSG("eglDestroySurface()");
+			m_sharedPbuffers[pbufferNdx] = EGL_NO_SURFACE;
+		}
+	}
+	m_sharedPbuffers.clear();
+
+	for (int contextNdx = 0; contextNdx < (int)m_sharedContexts.size(); contextNdx++)
+	{
+		if (m_sharedContexts[contextNdx] != EGL_NO_CONTEXT)
+		{
+			eglDestroyContext(m_display, m_sharedContexts[contextNdx]);
+			TCU_CHECK_EGL_MSG("eglDestroyContext()");
+			m_sharedContexts[contextNdx] =  EGL_NO_CONTEXT;
+		}
+	}
+	m_sharedContexts.clear();
+
+	for (int contextNdx = 0; contextNdx < (int)m_contexts0.size(); contextNdx++)
+	{
+		if (m_contexts0[contextNdx] != EGL_NO_CONTEXT)
+		{
+			eglDestroyContext(m_display, m_contexts0[contextNdx]);
+			TCU_CHECK_EGL_MSG("eglDestroyContext()");
+			m_contexts0[contextNdx] =  EGL_NO_CONTEXT;
+		}
+	}
+	m_contexts0.clear();
+
+	for (int contextNdx = 0; contextNdx < (int)m_contexts1.size(); contextNdx++)
+	{
+		if (m_contexts1[contextNdx] != EGL_NO_CONTEXT)
+		{
+			eglDestroyContext(m_display, m_contexts1[contextNdx]);
+			TCU_CHECK_EGL_MSG("eglDestroyContext()");
+			m_contexts1[contextNdx] =  EGL_NO_CONTEXT;
+		}
+	}
+	m_contexts1.clear();
+
+	// Clear pixmaps
+	for (int pixmapNdx = 0; pixmapNdx < (int)m_nativePixmaps0.size(); pixmapNdx++)	
+	{
+		if (m_nativePixmaps0[pixmapNdx].second != EGL_NO_SURFACE)
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, m_nativePixmaps0[pixmapNdx].second));
+
+		m_nativePixmaps0[pixmapNdx].second = EGL_NO_SURFACE;
+		delete m_nativePixmaps0[pixmapNdx].first;
+		m_nativePixmaps0[pixmapNdx].first = NULL;
+	}
+	m_nativePixmaps0.clear();
+
+	for (int pixmapNdx = 0; pixmapNdx < (int)m_nativePixmaps1.size(); pixmapNdx++)
+	{
+		if (m_nativePixmaps1[pixmapNdx].second != EGL_NO_SURFACE)
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, m_nativePixmaps1[pixmapNdx].second));
+
+		m_nativePixmaps1[pixmapNdx].second = EGL_NO_SURFACE;
+		delete m_nativePixmaps1[pixmapNdx].first;
+		m_nativePixmaps1[pixmapNdx].first = NULL;
+	}
+	m_nativePixmaps1.clear();
+
+	for (int pixmapNdx = 0; pixmapNdx < (int)m_sharedNativePixmaps.size(); pixmapNdx++)
+	{
+		if (m_sharedNativePixmaps[pixmapNdx].second != EGL_NO_SURFACE)
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, m_sharedNativePixmaps[pixmapNdx].second));
+
+		m_sharedNativePixmaps[pixmapNdx].second = EGL_NO_SURFACE;
+		delete m_sharedNativePixmaps[pixmapNdx].first;
+		m_sharedNativePixmaps[pixmapNdx].first = NULL;
+	}
+	m_sharedNativePixmaps.clear();
+
+	// Clear windows
+	for (int windowNdx = 0; windowNdx < (int)m_nativeWindows1.size(); windowNdx++)
+	{
+		if (m_nativeWindows1[windowNdx].second != EGL_NO_SURFACE)
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, m_nativeWindows1[windowNdx].second));
+
+		m_nativeWindows1[windowNdx].second = EGL_NO_SURFACE;
+		delete m_nativeWindows1[windowNdx].first;
+		m_nativeWindows1[windowNdx].first = NULL;
+	}
+	m_nativeWindows1.clear();
+
+	for (int windowNdx = 0; windowNdx < (int)m_nativeWindows0.size(); windowNdx++)
+	{
+		if (m_nativeWindows0[windowNdx].second != EGL_NO_SURFACE)
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, m_nativeWindows0[windowNdx].second));
+
+		m_nativeWindows0[windowNdx].second = EGL_NO_SURFACE;
+		delete m_nativeWindows0[windowNdx].first;
+		m_nativeWindows0[windowNdx].first = NULL;
+	}
+	m_nativeWindows0.clear();
+
+	for (int windowNdx = 0; windowNdx < (int)m_sharedNativeWindows.size(); windowNdx++)
+	{
+		if (m_sharedNativeWindows[windowNdx].second != EGL_NO_SURFACE)
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, m_sharedNativeWindows[windowNdx].second));
+
+		m_sharedNativeWindows[windowNdx].second = EGL_NO_SURFACE;
+		delete m_sharedNativeWindows[windowNdx].first;
+		m_sharedNativeWindows[windowNdx].first = NULL;
+	}
+	m_sharedNativeWindows.clear();
+}
+
+bool MultiThreadedObjectTest::runThread (TestThread& thread)
+{
+	if (thread.getId() == 0)
+	{
+		m_display = m_eglTestCtx.getDisplay().getEGLDisplay();
+
+		EGLint surfaceTypes = 0;
+
+		if ((m_types & TYPE_WINDOW) != 0)
+			surfaceTypes |= EGL_WINDOW_BIT;
+
+		if ((m_types & TYPE_PBUFFER) != 0)
+			surfaceTypes |= EGL_PBUFFER_BIT;
+
+		if ((m_types & TYPE_PIXMAP) != 0)
+			surfaceTypes |= EGL_PIXMAP_BIT;
+
+		EGLint configCount;
+		EGLint attribList[] =
+		{
+			EGL_SURFACE_TYPE, surfaceTypes,
+			EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+			EGL_NONE
+		};
+
+		TCU_CHECK_EGL_CALL(eglChooseConfig(m_display, attribList, &m_config, 1, &configCount));
+
+		if (configCount == 0)
+			throw tcu::NotSupportedError("No usable config found", "", __FILE__, __LINE__);
+	}
+
+	barrier(thread);
+
+	// Create / Destroy Objects
+	if ((m_types & TYPE_SINGLE_WINDOW) != 0 && (m_types & TYPE_PBUFFER) == 0 && (m_types & TYPE_PIXMAP) == 0 && (m_types & TYPE_CONTEXT) == 0)
+	{
+		if (thread.getId() == 0)
+			createDestroyObjects(thread, 1);
+	}
+	else
+		createDestroyObjects(thread, 100);
+
+	// Push first threads objects to shared
+	if (thread.getId() == 0)
+		pushObjectsToShared(thread);
+
+	barrier(thread);
+
+	// Push second threads objects to shared
+	if (thread.getId() == 1)
+		pushObjectsToShared(thread);
+
+	barrier(thread);
+
+	// Make queries from shared surfaces
+	querySetSharedObjects(thread, 100);
+
+	barrier(thread);
+
+	// Pull surfaces for first thread from shared surfaces
+	if (thread.getId() == 0)
+		pullObjectsFromShared(thread, (int)(m_sharedPbuffers.size()/2), (int)(m_sharedNativePixmaps.size()/2), (int)(m_sharedNativeWindows.size()/2), (int)(m_sharedContexts.size()/2));
+
+	barrier(thread);
+
+	// Pull surfaces for second thread from shared surfaces
+	if (thread.getId() == 1)
+		pullObjectsFromShared(thread, (int)m_sharedPbuffers.size(), (int)m_sharedNativePixmaps.size(), (int)m_sharedNativeWindows.size(), (int)m_sharedContexts.size());
+
+	barrier(thread);
+
+	// Create / Destroy Objects
+	if ((m_types & TYPE_SINGLE_WINDOW) == 0)
+		createDestroyObjects(thread, 100);
+
+	// Destroy surfaces
+	destroyObjects(thread);
+
+	return true;
+}
+
+void MultiThreadedObjectTest::createDestroyObjects (TestThread& thread, int count)
+{
+	de::Random&										rnd			= (thread.getId() == 0 ? m_rnd0 : m_rnd1);
+	vector<EGLSurface>&								pbuffers	= (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
+	vector<pair<eglu::NativeWindow*, EGLSurface> >&	windows		= (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
+	vector<pair<eglu::NativePixmap*, EGLSurface> >&	pixmaps		= (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
+	vector<EGLContext>&								contexts	= (thread.getId() == 0 ? m_contexts0 : m_contexts1);
+
+	vector<Type>		objectTypes;
+
+	if ((m_types & TYPE_PBUFFER) != 0)
+		objectTypes.push_back(TYPE_PBUFFER);
+
+	if ((m_types & TYPE_PIXMAP) != 0)
+		objectTypes.push_back(TYPE_PIXMAP);
+
+	if ((m_types & TYPE_WINDOW) != 0)
+		objectTypes.push_back(TYPE_WINDOW);
+
+	if ((m_types & TYPE_CONTEXT) != 0)
+		objectTypes.push_back(TYPE_CONTEXT);
+
+	for (int createDestroyNdx = 0; createDestroyNdx < count; createDestroyNdx++)
+	{
+		bool create;
+		Type type;
+
+		if (pbuffers.size() > 5 && ((m_types & TYPE_PBUFFER) != 0))
+		{
+			create	= false;
+			type	= TYPE_PBUFFER;
+		}
+		else if (windows.size() > 5 && ((m_types & TYPE_WINDOW) != 0))
+		{
+			create	= false;
+			type	= TYPE_WINDOW;
+		}
+		else if (pixmaps.size() > 5 && ((m_types & TYPE_PIXMAP) != 0))
+		{
+			create	= false;
+			type	= TYPE_PIXMAP;
+		}
+		else if (contexts.size() > 5 && ((m_types & TYPE_CONTEXT) != 0))
+		{
+			create	= false;
+			type	= TYPE_CONTEXT;
+		}
+		else if (pbuffers.size() < 3 && ((m_types & TYPE_PBUFFER) != 0))
+		{
+			create	= true;
+			type	= TYPE_PBUFFER;
+		}
+		else if (pixmaps.size() < 3 && ((m_types & TYPE_PIXMAP) != 0))
+		{
+			create	= true;
+			type	= TYPE_PIXMAP;
+		}
+		else if (contexts.size() < 3 && ((m_types & TYPE_CONTEXT) != 0))
+		{
+			create	= true;
+			type	= TYPE_CONTEXT;
+		}
+		else if (windows.size() < 3 && ((m_types & TYPE_WINDOW) != 0) && ((m_types & TYPE_SINGLE_WINDOW) == 0))
+		{
+			create	= true;
+			type	= TYPE_WINDOW;
+		}
+		else if (windows.empty() && ((m_types & TYPE_WINDOW) != 0) && ((m_types & TYPE_SINGLE_WINDOW) != 0))
+		{
+			create	= true;
+			type	= TYPE_WINDOW;
+		}
+		else
+		{
+			create	= rnd.getBool();
+			type	= rnd.choose<Type>(objectTypes.begin(), objectTypes.end());
+		}
+
+		if (create)
+		{
+			switch (type)
+			{
+				case TYPE_PBUFFER:
+				{
+					EGLSurface surface;
+
+					const EGLint attributes[] =
+					{
+						EGL_WIDTH,	64,
+						EGL_HEIGHT,	64,
+
+						EGL_NONE
+					};
+
+					surface = eglCreatePbufferSurface(m_display, m_config, attributes);
+					thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreatePbufferSurface(" << m_display << ", " << m_config << ", { EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE })" << ThreadLog::EndMessage;
+					TCU_CHECK_EGL_MSG("eglCreatePbufferSurface()");
+
+					pbuffers.push_back(surface);
+
+					break;
+				}
+
+				case TYPE_WINDOW:
+				{
+					if ((m_types & TYPE_SINGLE_WINDOW) != 0)
+					{
+						if (deAtomicCompareExchange32(&m_hasWindow, 0, 1) == 0)
+						{
+							eglu::NativeWindow* window	= DE_NULL;
+							EGLSurface			surface = EGL_NO_SURFACE;
+
+							try
+							{
+								window = m_eglTestCtx.createNativeWindow(m_eglTestCtx.getDisplay().getEGLDisplay(), m_config, DE_NULL, 64, 64, eglu::parseWindowVisibility(m_testCtx.getCommandLine()));
+								surface = eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, m_eglTestCtx.getDisplay().getEGLDisplay(), m_config, DE_NULL);
+
+								thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreateWindowSurface()" << ThreadLog::EndMessage;
+								windows.push_back(std::make_pair(window, surface));
+							}
+							catch (const std::exception&)
+							{
+								if (surface != EGL_NO_SURFACE)
+									TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, surface));
+								delete window;
+								throw;
+							}
+						}
+						else
+						{
+							createDestroyNdx--;
+						}
+					}
+					else
+					{
+						eglu::NativeWindow* window	= DE_NULL;
+						EGLSurface			surface = EGL_NO_SURFACE;
+
+						try
+						{
+							window	= m_eglTestCtx.createNativeWindow(m_eglTestCtx.getDisplay().getEGLDisplay(), m_config, DE_NULL, 64, 64, eglu::parseWindowVisibility(m_testCtx.getCommandLine()));
+							surface	= eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, m_eglTestCtx.getDisplay().getEGLDisplay(), m_config, DE_NULL);
+
+							thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreateWindowSurface()" << ThreadLog::EndMessage;
+							windows.push_back(std::make_pair(window, surface));
+						}
+						catch (const std::exception&)
+						{
+							if (surface != EGL_NO_SURFACE)
+								TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, surface));
+							delete window;
+							throw;
+						}
+					}
+					break;
+				}
+
+				case TYPE_PIXMAP:
+				{
+					eglu::NativePixmap* pixmap	= DE_NULL;
+					EGLSurface			surface	= EGL_NO_SURFACE;
+
+					try
+					{
+						pixmap	= m_eglTestCtx.createNativePixmap(m_eglTestCtx.getDisplay().getEGLDisplay(), m_config, DE_NULL, 64, 64);
+						surface	= eglu::createPixmapSurface(m_eglTestCtx.getNativeDisplay(), *pixmap, m_eglTestCtx.getDisplay().getEGLDisplay(), m_config, DE_NULL);
+
+						thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreatePixmapSurface()" << ThreadLog::EndMessage;
+						pixmaps.push_back(std::make_pair(pixmap, surface));
+					}
+					catch (const std::exception&)
+					{
+						if (surface != EGL_NO_SURFACE)
+							TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, surface));
+						delete pixmap;
+						throw;
+					}
+					break;
+				}
+
+				case TYPE_CONTEXT:
+				{
+					EGLContext context;
+
+					TCU_CHECK_EGL_CALL(eglBindAPI(EGL_OPENGL_ES_API));
+					thread.getLog() << ThreadLog::BeginMessage << "eglBindAPI(EGL_OPENGL_ES_API)" << ThreadLog::EndMessage;
+
+					const EGLint attributes[] =
+					{
+						EGL_CONTEXT_CLIENT_VERSION, 2,
+						EGL_NONE
+					};
+
+					context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, attributes);
+					thread.getLog() << ThreadLog::BeginMessage << context << " = eglCreateContext(" << m_display << ", " << m_config << ", EGL_NO_CONTEXT, { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE })" << ThreadLog::EndMessage;
+					TCU_CHECK_EGL_MSG("eglCreateContext()");
+					contexts.push_back(context);
+					break;
+				}
+
+				default:
+					DE_ASSERT(false);
+			};
+		}
+		else
+		{
+			switch (type)
+			{
+				case TYPE_PBUFFER:
+				{
+					const int pbufferNdx = rnd.getInt(0, (int)(pbuffers.size()-1));
+					EGLBoolean result;
+
+					result = eglDestroySurface(m_display, pbuffers[pbufferNdx]);
+					thread.getLog() << ThreadLog::BeginMessage << result << " = eglDestroySurface(" << m_display << ", " << pbuffers[pbufferNdx] << ")" << ThreadLog::EndMessage;
+					TCU_CHECK_EGL_MSG("eglDestroySurface()");
+
+					pbuffers.erase(pbuffers.begin() + pbufferNdx);
+
+					break;
+				}
+
+				case TYPE_WINDOW:
+				{
+					const int windowNdx = rnd.getInt(0, (int)(windows.size()-1));
+
+					thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", " << windows[windowNdx].second << ")" << ThreadLog::EndMessage;
+
+					TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, windows[windowNdx].second));
+					windows[windowNdx].second = EGL_NO_SURFACE;
+					delete windows[windowNdx].first;
+					windows[windowNdx].first = DE_NULL;
+					windows.erase(windows.begin() + windowNdx);
+
+					if ((m_types & TYPE_SINGLE_WINDOW) != 0)
+						m_hasWindow = 0;
+
+					break;
+				}
+
+				case TYPE_PIXMAP:
+				{
+					const int pixmapNdx = rnd.getInt(0, (int)(pixmaps.size()-1));
+
+					thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", " << pixmaps[pixmapNdx].second << ")" << ThreadLog::EndMessage;
+					TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, pixmaps[pixmapNdx].second));
+					pixmaps[pixmapNdx].second = EGL_NO_SURFACE;
+					delete pixmaps[pixmapNdx].first;
+					pixmaps[pixmapNdx].first = DE_NULL;
+					pixmaps.erase(pixmaps.begin() + pixmapNdx);
+
+					break;
+				}
+
+				case TYPE_CONTEXT:
+				{
+					const int contextNdx = rnd.getInt(0, (int)(contexts.size()-1));
+
+					TCU_CHECK_EGL_CALL(eglDestroyContext(m_display, contexts[contextNdx]));
+					thread.getLog() << ThreadLog::BeginMessage << "eglDestroyContext(" << m_display << ", " << contexts[contextNdx]  << ")" << ThreadLog::EndMessage;
+					contexts.erase(contexts.begin() + contextNdx);
+
+					break;
+				}
+
+				default:
+					DE_ASSERT(false);
+			}
+
+		}
+	}
+}
+
+void MultiThreadedObjectTest::pushObjectsToShared (TestThread& thread)
+{
+	vector<EGLSurface>&									pbuffers	= (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
+	vector<pair<eglu::NativeWindow*, EGLSurface> >&		windows		= (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
+	vector<pair<eglu::NativePixmap*, EGLSurface> >&		pixmaps		= (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
+	vector<EGLContext>&									contexts	= (thread.getId() == 0 ? m_contexts0 : m_contexts1);
+
+	for (int pbufferNdx = 0; pbufferNdx < (int)pbuffers.size(); pbufferNdx++)
+		m_sharedPbuffers.push_back(pbuffers[pbufferNdx]);
+
+	pbuffers.clear();
+
+	for (int windowNdx = 0; windowNdx < (int)windows.size(); windowNdx++)
+		m_sharedNativeWindows.push_back(windows[windowNdx]);
+
+	windows.clear();
+
+	for (int pixmapNdx = 0; pixmapNdx < (int)pixmaps.size(); pixmapNdx++)
+		m_sharedNativePixmaps.push_back(pixmaps[pixmapNdx]);
+
+	pixmaps.clear();
+
+	for (int contextNdx = 0; contextNdx < (int)contexts.size(); contextNdx++)
+		m_sharedContexts.push_back(contexts[contextNdx]);
+
+	contexts.clear();
+}
+
+void MultiThreadedObjectTest::pullObjectsFromShared (TestThread& thread, int pbufferCount, int pixmapCount, int windowCount, int contextCount)
+{
+	de::Random&											rnd			= (thread.getId() == 0 ? m_rnd0 : m_rnd1);
+	vector<EGLSurface>&									pbuffers	= (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
+	vector<pair<eglu::NativeWindow*, EGLSurface> >&		windows		= (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
+	vector<pair<eglu::NativePixmap*, EGLSurface> >&		pixmaps		= (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
+	vector<EGLContext>&									contexts	= (thread.getId() == 0 ? m_contexts0 : m_contexts1);
+
+	for (int pbufferNdx = 0; pbufferNdx < pbufferCount; pbufferNdx++)
+	{
+		const int ndx = rnd.getInt(0, (int)(m_sharedPbuffers.size()-1));
+
+		pbuffers.push_back(m_sharedPbuffers[ndx]);
+		m_sharedPbuffers.erase(m_sharedPbuffers.begin() + ndx);
+	}
+
+	for (int pixmapNdx = 0; pixmapNdx < pixmapCount; pixmapNdx++)
+	{
+		const int ndx = rnd.getInt(0, (int)(m_sharedNativePixmaps.size()-1));
+
+		pixmaps.push_back(m_sharedNativePixmaps[ndx]);
+		m_sharedNativePixmaps.erase(m_sharedNativePixmaps.begin() + ndx);
+	}
+
+	for (int windowNdx = 0; windowNdx < windowCount; windowNdx++)
+	{
+		const int ndx = rnd.getInt(0, (int)(m_sharedNativeWindows.size()-1));
+
+		windows.push_back(m_sharedNativeWindows[ndx]);
+		m_sharedNativeWindows.erase(m_sharedNativeWindows.begin() + ndx);
+	}
+
+	for (int contextNdx = 0; contextNdx < contextCount; contextNdx++)
+	{
+		const int ndx = rnd.getInt(0, (int)(m_sharedContexts.size()-1));
+
+		contexts.push_back(m_sharedContexts[ndx]);
+		m_sharedContexts.erase(m_sharedContexts.begin() + ndx);
+	}
+}
+
+void MultiThreadedObjectTest::querySetSharedObjects (TestThread& thread, int count)
+{
+	de::Random& rnd = (thread.getId() == 0 ? m_rnd0 : m_rnd1);
+	vector<Type> objectTypes;
+
+	if ((m_types & TYPE_PBUFFER) != 0)
+		objectTypes.push_back(TYPE_PBUFFER);
+
+	if ((m_types & TYPE_PIXMAP) != 0)
+		objectTypes.push_back(TYPE_PIXMAP);
+
+	if ((m_types & TYPE_WINDOW) != 0)
+		objectTypes.push_back(TYPE_WINDOW);
+
+	if ((m_types & TYPE_CONTEXT) != 0)
+		objectTypes.push_back(TYPE_CONTEXT);
+
+	for (int queryNdx = 0; queryNdx < count; queryNdx++)
+	{
+		const Type	type		= rnd.choose<Type>(objectTypes.begin(), objectTypes.end());
+		EGLSurface	surface		= EGL_NO_SURFACE;
+		EGLContext	context		= EGL_NO_CONTEXT;
+
+		switch (type)
+		{
+			case TYPE_PBUFFER:
+				surface = m_sharedPbuffers[rnd.getInt(0, (int)(m_sharedPbuffers.size()-1))];
+				break;
+
+			case TYPE_PIXMAP:
+				surface = m_sharedNativePixmaps[rnd.getInt(0, (int)(m_sharedNativePixmaps.size()-1))].second;
+				break;
+
+			case TYPE_WINDOW:
+				surface = m_sharedNativeWindows[rnd.getInt(0, (int)(m_sharedNativeWindows.size()-1))].second;
+				break;
+
+			case TYPE_CONTEXT:
+				context = m_sharedContexts[rnd.getInt(0, (int)(m_sharedContexts.size()-1))];
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		if (surface != EGL_NO_SURFACE)
+		{
+			static const EGLint queryAttributes[] =
+			{
+				EGL_LARGEST_PBUFFER,
+				EGL_HEIGHT,
+				EGL_WIDTH
+			};
+
+			const EGLint	attribute	= queryAttributes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(queryAttributes) - 1)];
+			EGLBoolean		result;
+			EGLint			value;
+
+			result = eglQuerySurface(m_display, surface, attribute, &value);
+			thread.getLog() << ThreadLog::BeginMessage << result << " = eglQuerySurface(" << m_display << ", " << surface << ", " << attribute << ", " << value << ")" << ThreadLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglQuerySurface()");
+
+		}
+		else if (context != EGL_NO_CONTEXT)
+		{
+			static const EGLint attributes[] =
+			{
+				EGL_CONFIG_ID,
+				EGL_CONTEXT_CLIENT_TYPE,
+				EGL_CONTEXT_CLIENT_VERSION,
+				EGL_RENDER_BUFFER
+			};
+
+			const EGLint	attribute = attributes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(attributes)-1)];
+			EGLint			value;
+			EGLBoolean		result;
+
+			result = eglQueryContext(m_display, context, attribute, &value);
+			thread.getLog() << ThreadLog::BeginMessage << result << " = eglQueryContext(" << m_display << ", " << context << ", " << attribute << ", " << value << ")" << ThreadLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglQueryContext()");
+
+		}
+		else
+			DE_ASSERT(false);
+	}
+}
+
+void MultiThreadedObjectTest::destroyObjects (TestThread& thread)
+{
+	vector<EGLSurface>&									pbuffers	= (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
+	vector<pair<eglu::NativeWindow*, EGLSurface> >&		windows		= (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
+	vector<pair<eglu::NativePixmap*, EGLSurface> >&		pixmaps		= (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
+	vector<EGLContext>&									contexts	= (thread.getId() == 0 ? m_contexts0 : m_contexts1);
+
+	for (int pbufferNdx = 0; pbufferNdx < (int)pbuffers.size(); pbufferNdx++)
+	{
+		if (pbuffers[pbufferNdx] != EGL_NO_SURFACE)
+		{
+			// Destroy EGLSurface
+			EGLBoolean result;
+
+			result = eglDestroySurface(m_display, pbuffers[pbufferNdx]);
+			thread.getLog() << ThreadLog::BeginMessage << result << " = eglDestroySurface(" << m_display << ", " << pbuffers[pbufferNdx] << ")" << ThreadLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglDestroySurface()");
+			pbuffers[pbufferNdx] = EGL_NO_SURFACE;
+		}
+	}
+	pbuffers.clear();
+
+	for (int windowNdx = 0; windowNdx < (int)windows.size(); windowNdx++)
+	{
+		if (windows[windowNdx].second != EGL_NO_SURFACE)
+		{
+			thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", " << windows[windowNdx].second << ")" << ThreadLog::EndMessage;
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, windows[windowNdx].second));
+			windows[windowNdx].second = EGL_NO_SURFACE;
+		}
+
+		if (windows[windowNdx].first)
+		{
+			delete windows[windowNdx].first;
+			windows[windowNdx].first = NULL;
+		}
+	}
+	windows.clear();
+
+	for (int pixmapNdx = 0; pixmapNdx < (int)pixmaps.size(); pixmapNdx++)
+	{
+		if (pixmaps[pixmapNdx].first != EGL_NO_SURFACE)
+		{
+			thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", " << pixmaps[pixmapNdx].second << ")" << ThreadLog::EndMessage;
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_display, pixmaps[pixmapNdx].second));
+			pixmaps[pixmapNdx].second = EGL_NO_SURFACE;
+		}
+
+		if (pixmaps[pixmapNdx].first)
+		{
+			delete pixmaps[pixmapNdx].first;
+			pixmaps[pixmapNdx].first = NULL;
+		}
+	}
+	pixmaps.clear();
+
+	for (int contextNdx = 0; contextNdx < (int)contexts.size(); contextNdx++)
+	{
+		if (contexts[contextNdx] != EGL_NO_CONTEXT)
+		{
+			TCU_CHECK_EGL_CALL(eglDestroyContext(m_display, contexts[contextNdx]));
+			thread.getLog() << ThreadLog::BeginMessage << "eglDestroyContext(" << m_display << ", " << contexts[contextNdx]  << ")" << ThreadLog::EndMessage;
+			contexts[contextNdx] = EGL_NO_CONTEXT;
+		}
+	}
+	contexts.clear();
+}
+
+MultiThreadedTests::MultiThreadedTests (EglTestContext& context)
+	: TestCaseGroup(context, "multithread", "Multithreaded EGL tests")
+{
+}
+
+void MultiThreadedTests::init (void)
+{
+	// Config tests
+	addChild(new MultiThreadedConfigTest(m_eglTestCtx,	"config",	"",	30,	30,	30));
+
+	// Object tests
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer",								"", MultiThreadedObjectTest::TYPE_PBUFFER));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap",								"", MultiThreadedObjectTest::TYPE_PIXMAP));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"window",								"", MultiThreadedObjectTest::TYPE_WINDOW));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"single_window",						"", MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"context",								"", MultiThreadedObjectTest::TYPE_CONTEXT));
+
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap",						"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_window",						"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_WINDOW));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_single_window",				"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_context",						"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_CONTEXT));
+
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap_window",						"", MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap_single_window",					"", MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap_context",						"", MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_CONTEXT));
+
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"window_context",						"", MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"single_window_context",				"", MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
+
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap_window",				"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap_single_window",			"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap_context",				"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_CONTEXT));
+
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_window_context",				"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_single_window_context",		"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
+
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap_window_context",				"", MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap_single_window_context",			"", MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
+
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap_window_context",		"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
+	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap_single_window_context",	"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglMultiThreadTests.hpp b/modules/egl/teglMultiThreadTests.hpp
new file mode 100644
index 0000000..3755c46
--- /dev/null
+++ b/modules/egl/teglMultiThreadTests.hpp
@@ -0,0 +1,43 @@
+#ifndef _TEGLMULTITHREADTESTS_HPP
+#define _TEGLMULTITHREADTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multi threaded EGL tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class MultiThreadedTests : public TestCaseGroup
+{
+public:
+			MultiThreadedTests	(EglTestContext& eglTestCtx);
+	void	init				(void);
+};
+
+} // egl
+} // deqp
+#endif // _TEGLMULTITHREADTESTS_HPP
diff --git a/modules/egl/teglNativeColorMappingTests.cpp b/modules/egl/teglNativeColorMappingTests.cpp
new file mode 100644
index 0000000..1ea7f76
--- /dev/null
+++ b/modules/egl/teglNativeColorMappingTests.cpp
@@ -0,0 +1,600 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test for mapping client color values to native surface colors
+ *//*--------------------------------------------------------------------*/
+
+#include "teglNativeColorMappingTests.hpp"
+
+#include "teglSimpleConfigCase.hpp"
+
+#include "tcuTexture.hpp"
+
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluUnique.hpp"
+#include "egluUtil.hpp"
+
+#include "gluDefs.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+
+#include "deUniquePtr.hpp"
+#include "deStringUtil.hpp"
+
+#include "deThread.hpp"
+
+#include <vector>
+#include <string>
+#include <limits>
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+namespace egl
+{
+namespace
+{
+
+EGLContext createGLES2Context (EGLDisplay display, EGLConfig config)
+{
+	EGLContext		context = EGL_NO_CONTEXT;
+	const EGLint	attribList[] =
+	{
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+	TCU_CHECK_EGL_CALL(eglBindAPI(EGL_OPENGL_ES_API));
+
+	context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribList);
+	TCU_CHECK_EGL_MSG("eglCreateContext() failed");
+	TCU_CHECK(context);
+
+	return context;
+}
+
+deUint32 createGLES2Program (const glw::Functions& gl, TestLog& log)
+{
+	const char* const vertexShaderSource =
+	"attribute highp vec2 a_pos;\n"
+	"void main (void)\n"
+	"{\n"
+	"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+	"}";
+
+	const char* const fragmentShaderSource =
+	"uniform mediump vec4 u_color;\n"
+	"void main (void)\n"
+	"{\n"
+	"\tgl_FragColor = u_color;\n"
+	"}";
+
+	deUint32	program			= 0;
+	deUint32	vertexShader	= 0;
+	deUint32	fragmentShader	= 0;
+
+	deInt32		vertexCompileStatus;
+	string		vertexInfoLog;
+	deInt32		fragmentCompileStatus;
+	string		fragmentInfoLog;
+	deInt32		linkStatus;
+	string		programInfoLog;
+
+	try
+	{
+		program			= gl.createProgram();
+		vertexShader	= gl.createShader(GL_VERTEX_SHADER);
+		fragmentShader	= gl.createShader(GL_FRAGMENT_SHADER);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create shaders and program");
+
+		gl.shaderSource(vertexShader, 1, &vertexShaderSource, DE_NULL);
+		gl.compileShader(vertexShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup vertex shader");
+
+		gl.shaderSource(fragmentShader, 1, &fragmentShaderSource, DE_NULL);
+		gl.compileShader(fragmentShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup fragment shader");
+
+		{
+			deInt32		infoLogLength = 0;
+
+			gl.getShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexCompileStatus);
+			gl.getShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+			vertexInfoLog.resize(infoLogLength, '\0');
+
+			gl.getShaderInfoLog(vertexShader, (glw::GLsizei)vertexInfoLog.length(), &infoLogLength, &(vertexInfoLog[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get vertex shader compile info");
+
+			vertexInfoLog.resize(infoLogLength);
+		}
+
+		{
+			deInt32		infoLogLength = 0;
+
+			gl.getShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentCompileStatus);
+			gl.getShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+			fragmentInfoLog.resize(infoLogLength, '\0');
+
+			gl.getShaderInfoLog(fragmentShader, (glw::GLsizei)fragmentInfoLog.length(), &infoLogLength, &(fragmentInfoLog[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get fragment shader compile info");
+
+			fragmentInfoLog.resize(infoLogLength);
+		}
+
+		gl.attachShader(program, vertexShader);
+		gl.attachShader(program, fragmentShader);
+		gl.linkProgram(program);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup program");
+
+		{
+			deInt32		infoLogLength = 0;
+
+			gl.getProgramiv(program, GL_LINK_STATUS, &linkStatus);
+			gl.getProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+			programInfoLog.resize(infoLogLength, '\0');
+
+			gl.getProgramInfoLog(program, (glw::GLsizei)programInfoLog.length(), &infoLogLength, &(programInfoLog[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get program link info");
+
+			programInfoLog.resize(infoLogLength);
+		}
+
+		if (linkStatus == 0 || vertexCompileStatus == 0 || fragmentCompileStatus == 0)
+		{
+
+			log.startShaderProgram(linkStatus != 0, programInfoLog.c_str());
+
+			log << TestLog::Shader(QP_SHADER_TYPE_VERTEX, vertexShaderSource, vertexCompileStatus != 0, vertexInfoLog);
+			log << TestLog::Shader(QP_SHADER_TYPE_FRAGMENT, fragmentShaderSource, fragmentCompileStatus != 0, fragmentInfoLog);
+
+			log.endShaderProgram();
+		}
+
+		gl.deleteShader(vertexShader);
+		gl.deleteShader(fragmentShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to delete shaders");
+
+		TCU_CHECK(linkStatus != 0 && vertexCompileStatus != 0 && fragmentCompileStatus != 0);
+	}
+	catch (...)
+	{
+		if (program)
+			gl.deleteProgram(program);
+
+		if (vertexShader)
+			gl.deleteShader(vertexShader);
+
+		if (fragmentShader)
+			gl.deleteShader(fragmentShader);
+
+		throw;
+	}
+
+	return program;
+}
+
+void clear (const glw::Functions& gl, const tcu::Vec4& color)
+{
+	gl.clearColor(color.x(), color.y(), color.z(), color.w());
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Color clear failed");
+}
+
+void render (const glw::Functions& gl, deUint32 program, const tcu::Vec4& color)
+{
+	const float positions[] =
+	{
+		-1.0f, -1.0f,
+		 1.0f, -1.0f,
+		 1.0f,  1.0f,
+
+		 1.0f,  1.0f,
+		-1.0f,  1.0f,
+		-1.0f, -1.0f
+	};
+
+	deUint32 posLocation;
+	deUint32 colorLocation;
+
+	gl.useProgram(program);
+	posLocation	= gl.getAttribLocation(program, "a_pos");
+	gl.enableVertexAttribArray(posLocation);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup shader program for rendering");
+
+	colorLocation = gl.getUniformLocation(program, "u_color");
+	gl.uniform4fv(colorLocation, 1, color.getPtr());
+
+	gl.vertexAttribPointer(posLocation, 2, GL_FLOAT, GL_FALSE, 0, positions);
+	gl.drawArrays(GL_TRIANGLES, 0, 6);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to render");
+}
+
+bool validate (TestLog& log, EGLDisplay display, EGLConfig config, const tcu::TextureLevel& result, const tcu::Vec4& color)
+{
+	const tcu::UVec4 eglBitDepth((deUint32)eglu::getConfigAttribInt(display, config, EGL_RED_SIZE),
+								 (deUint32)eglu::getConfigAttribInt(display, config, EGL_GREEN_SIZE),
+								 (deUint32)eglu::getConfigAttribInt(display, config, EGL_BLUE_SIZE),
+								 (deUint32)eglu::getConfigAttribInt(display, config, EGL_ALPHA_SIZE));
+
+	const tcu::UVec4 nativeBitDepth(tcu::getTextureFormatBitDepth(result.getFormat()).asUint());
+	const tcu::UVec4 bitDepth(deMinu32(nativeBitDepth.x(), eglBitDepth.x()),
+							  deMinu32(nativeBitDepth.y(), eglBitDepth.y()),
+							  deMinu32(nativeBitDepth.z(), eglBitDepth.z()),
+							  deMinu32(nativeBitDepth.w(), eglBitDepth.w()));
+
+	const tcu::UVec4 uColor = tcu::UVec4((deUint32)(((1u << bitDepth.x()) - 1u) * color.x()),
+										 (deUint32)(((1u << bitDepth.y()) - 1u) * color.y()),
+										 (deUint32)(((1u << bitDepth.z()) - 1u) * color.z()),
+										 (deUint32)(((1u << bitDepth.w()) - 1u) * color.w()));
+
+	tcu::TextureLevel reference(result.getFormat(), result.getWidth(), result.getHeight());
+
+	for (int y = 0; y < result.getHeight(); y++)
+	{
+		for (int x = 0; x < result.getWidth(); x++)
+			reference.getAccess().setPixel(uColor, x, y);
+	}
+
+	return tcu::intThresholdCompare(log, "Result compare", "Compare results", reference.getAccess(), result.getAccess(), tcu::UVec4(1u, 1u, 1u, (bitDepth.w() > 0 ? 1u : std::numeric_limits<deUint32>::max())), tcu::COMPARE_LOG_RESULT);
+}
+
+class NativeColorMappingCase : public SimpleConfigCase
+{
+public:
+	enum NativeType
+	{
+		NATIVETYPE_WINDOW = 0,
+		NATIVETYPE_PIXMAP,
+		NATIVETYPE_PBUFFER_COPY_TO_PIXMAP
+	};
+
+				NativeColorMappingCase	(EglTestContext& eglTestCtx, const char* name, const char* description, bool render, NativeType nativeType, const vector<EGLint>& configIds);
+				~NativeColorMappingCase	(void);
+
+private:
+	void		executeForConfig		(tcu::egl::Display& display, EGLConfig config);
+
+	NativeType	m_nativeType;
+	bool		m_render;
+};
+
+NativeColorMappingCase::NativeColorMappingCase (EglTestContext& eglTestCtx, const char* name, const char* description, bool render, NativeType nativeType, const vector<EGLint>& configIds)
+	: SimpleConfigCase	(eglTestCtx, name, description, configIds)
+	, m_nativeType		(nativeType)
+	, m_render			(render)
+{
+}
+
+NativeColorMappingCase::~NativeColorMappingCase	(void)
+{
+	deinit();
+}
+
+void logConfigInfo (TestLog& log, EGLDisplay display, EGLConfig config, NativeColorMappingCase::NativeType nativeType, int waitFrames)
+{
+	log << TestLog::Message << "EGL_RED_SIZE: "		<< eglu::getConfigAttribInt(display, config, EGL_RED_SIZE)		<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_GREEN_SIZE: "	<< eglu::getConfigAttribInt(display, config, EGL_GREEN_SIZE)	<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_BLUE_SIZE: "	<< eglu::getConfigAttribInt(display, config, EGL_BLUE_SIZE)		<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_ALPHA_SIZE: "	<< eglu::getConfigAttribInt(display, config, EGL_ALPHA_SIZE)	<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_DEPTH_SIZE: "	<< eglu::getConfigAttribInt(display, config, EGL_DEPTH_SIZE)	<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_STENCIL_SIZE: "	<< eglu::getConfigAttribInt(display, config, EGL_STENCIL_SIZE)	<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_SAMPLES: "		<< eglu::getConfigAttribInt(display, config, EGL_SAMPLES)		<< TestLog::EndMessage;
+
+	if (nativeType == NativeColorMappingCase::NATIVETYPE_WINDOW)
+		log << TestLog::Message << "Waiting " << waitFrames * 16 << "ms after eglSwapBuffers() and glFinish() for frame to become visible" << TestLog::EndMessage;
+}
+
+bool testNativeWindow (TestLog& log, eglu::NativeDisplay& nativeDisplay, eglu::NativeWindow& nativeWindow, EGLDisplay display, EGLContext context, EGLConfig config, const glw::Functions& gl, bool renderColor, int waitFrames, size_t colorCount, const tcu::Vec4* colors)
+{
+	eglu::UniqueSurface	surface(display, eglu::createWindowSurface(nativeDisplay, nativeWindow, display, config, DE_NULL));
+	tcu::TextureLevel	result;
+	deUint32			program	= 0;
+	bool				isOk	= true;
+
+	try
+	{
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, *surface, *surface, context));
+
+		if (renderColor)
+			program = createGLES2Program(gl, log);
+
+		for (int colorNdx = 0; colorNdx < (int)colorCount; colorNdx++)
+		{
+			if (renderColor)
+				render(gl, program, colors[colorNdx]);
+			else
+				clear(gl, colors[colorNdx]);
+
+			TCU_CHECK_EGL_CALL(eglSwapBuffers(display, *surface));
+			TCU_CHECK_EGL_CALL(eglWaitClient());
+			deSleep(waitFrames*16);
+			nativeWindow.readScreenPixels(&result);
+
+			if (!validate(log, display, config, result, colors[colorNdx]))
+				isOk = false;
+		}
+
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+	}
+	catch (...)
+	{
+		if (program)
+			gl.deleteProgram(program);
+		throw;
+	}
+
+	return isOk;
+}
+
+bool testNativePixmap (TestLog& log, eglu::NativeDisplay& nativeDisplay, eglu::NativePixmap& nativePixmap, EGLDisplay display, EGLContext context, EGLConfig config, const glw::Functions& gl, bool renderColor, size_t colorCount, const tcu::Vec4* colors)
+{
+	eglu::UniqueSurface	surface(display, eglu::createPixmapSurface(nativeDisplay, nativePixmap, display, config, DE_NULL));
+	tcu::TextureLevel	result;
+	deUint32			program	= 0;
+	bool				isOk	= true;
+
+	try
+	{
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, *surface, *surface, context));
+
+		if (renderColor)
+			program = createGLES2Program(gl, log);
+
+		for (int colorNdx = 0; colorNdx < (int)colorCount; colorNdx++)
+		{
+			if (renderColor)
+				render(gl, program, colors[colorNdx]);
+			else
+				clear(gl, colors[colorNdx]);
+
+			TCU_CHECK_EGL_CALL(eglWaitClient());
+			nativePixmap.readPixels(&result);
+
+			if (!validate(log, display, config, result, colors[colorNdx]))
+				isOk = false;
+		}
+
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+	}
+	catch (...)
+	{
+		if (program)
+			gl.deleteProgram(program);
+		throw;
+	}
+
+	return isOk;
+}
+
+bool testNativePixmapCopy (TestLog& log, eglu::NativePixmap& nativePixmap, EGLDisplay display, EGLContext context, EGLConfig config, const glw::Functions& gl, bool renderColor, size_t colorCount, const tcu::Vec4* colors)
+{
+	eglu::UniqueSurface	surface(display, eglCreatePbufferSurface(display, config, DE_NULL));
+	tcu::TextureLevel	result;
+	deUint32			program	= 0;
+	bool				isOk	= true;
+
+	try
+	{
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, *surface, *surface, context));
+
+		if (renderColor)
+			program = createGLES2Program(gl, log);
+
+		for (int colorNdx = 0; colorNdx < (int)colorCount; colorNdx++)
+		{
+			if (renderColor)
+				render(gl, program, colors[colorNdx]);
+			else
+				clear(gl, colors[colorNdx]);
+
+			TCU_CHECK_EGL_CALL(eglCopyBuffers(display, *surface, nativePixmap.getLegacyNative()));
+			TCU_CHECK_EGL_CALL(eglWaitClient());
+			nativePixmap.readPixels(&result);
+
+			if (!validate(log, display, config, result, colors[colorNdx]))
+				isOk = false;
+		}
+
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+	}
+	catch (...)
+	{
+		if (program)
+			gl.deleteProgram(program);
+		throw;
+	}
+
+	return isOk;
+}
+
+void checkSupport (EglTestContext& eglTestCtx, NativeColorMappingCase::NativeType nativeType)
+{
+	switch (nativeType)
+	{
+		case NativeColorMappingCase::NATIVETYPE_WINDOW:
+			if ((eglTestCtx.getNativeWindowFactory().getCapabilities() & eglu::NativeWindow::CAPABILITY_READ_SCREEN_PIXELS) == 0)
+				throw tcu::NotSupportedError("Native window doesn't support readPixels()", "", __FILE__, __LINE__);
+			break;
+
+		case NativeColorMappingCase::NATIVETYPE_PIXMAP:
+			if ((eglTestCtx.getNativePixmapFactory().getCapabilities() & eglu::NativePixmap::CAPABILITY_READ_PIXELS) == 0)
+				throw tcu::NotSupportedError("Native pixmap doesn't support readPixels()", "", __FILE__, __LINE__);
+			break;
+
+		case NativeColorMappingCase::NATIVETYPE_PBUFFER_COPY_TO_PIXMAP:
+			if ((eglTestCtx.getNativePixmapFactory().getCapabilities() & eglu::NativePixmap::CAPABILITY_READ_PIXELS) == 0 ||
+				(eglTestCtx.getNativePixmapFactory().getCapabilities() & eglu::NativePixmap::CAPABILITY_CREATE_SURFACE_LEGACY) == 0)
+				throw tcu::NotSupportedError("Native pixmap doesn't support readPixels() or legacy create surface", "", __FILE__, __LINE__);
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+void NativeColorMappingCase::executeForConfig (tcu::egl::Display& display, EGLConfig config)
+{
+	const int width		= 128;
+	const int height	= 128;
+
+	const tcu::Vec4 colors[] =
+	{
+		tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f),
+		tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 1.0f, 1.0f, 1.0f),
+		tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f),
+		tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f),
+		tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+
+		tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 0.0f, 0.5f, 1.0f),
+		tcu::Vec4(0.0f, 0.5f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 0.5f, 0.5f, 1.0f),
+		tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.5f, 0.0f, 0.5f, 1.0f),
+		tcu::Vec4(0.5f, 0.5f, 0.0f, 1.0f),
+		tcu::Vec4(0.5f, 0.5f, 0.5f, 1.0f)
+	};
+
+	const string			configIdStr	(de::toString(eglu::getConfigAttribInt(display.getEGLDisplay(), config, EGL_CONFIG_ID)));
+	tcu::ScopedLogSection	logSection	(m_testCtx.getLog(), ("Config ID " + configIdStr).c_str(), ("Config ID " + configIdStr).c_str());
+	const int				waitFrames	= 5;
+
+	logConfigInfo(m_testCtx.getLog(), display.getEGLDisplay(), config, m_nativeType, waitFrames);
+
+	checkSupport(m_eglTestCtx, m_nativeType);
+
+	eglu::UniqueContext context(display.getEGLDisplay(), createGLES2Context(display.getEGLDisplay(), config));
+	glw::Functions		gl;
+
+	m_eglTestCtx.getGLFunctions(gl, glu::ApiType::es(2,0));
+
+	switch (m_nativeType)
+	{
+		case NATIVETYPE_WINDOW:
+		{
+			de::UniquePtr<eglu::NativeWindow> nativeWindow(m_eglTestCtx.createNativeWindow(display.getEGLDisplay(), config, DE_NULL, width, height, eglu::WindowParams::VISIBILITY_VISIBLE));
+
+			if (!testNativeWindow(m_testCtx.getLog(), m_eglTestCtx.getNativeDisplay(), *nativeWindow, display.getEGLDisplay(), *context, config, gl, m_render, waitFrames, DE_LENGTH_OF_ARRAY(colors), colors))
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid color rendered");
+			break;
+		}
+
+		case NATIVETYPE_PIXMAP:
+		{
+			de::UniquePtr<eglu::NativePixmap> nativePixmap(m_eglTestCtx.createNativePixmap(display.getEGLDisplay(), config, DE_NULL, width, height));
+
+			if (!testNativePixmap(m_testCtx.getLog(), m_eglTestCtx.getNativeDisplay(), *nativePixmap, display.getEGLDisplay(), *context, config, gl, m_render, DE_LENGTH_OF_ARRAY(colors), colors))
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid color rendered");
+			break;
+		}
+
+		case NATIVETYPE_PBUFFER_COPY_TO_PIXMAP:
+		{
+			de::UniquePtr<eglu::NativePixmap> nativePixmap(m_eglTestCtx.createNativePixmap(display.getEGLDisplay(), config, DE_NULL, width, height));
+
+			if (!testNativePixmapCopy(m_testCtx.getLog(), *nativePixmap, display.getEGLDisplay(), *context, config, gl, m_render, DE_LENGTH_OF_ARRAY(colors), colors))
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid color rendered");
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+void addTestGroups (EglTestContext& eglTestCtx, TestCaseGroup* group, NativeColorMappingCase::NativeType type)
+{
+	eglu::FilterList filters;
+
+	switch (type)
+	{
+		case NativeColorMappingCase::NATIVETYPE_WINDOW:
+			filters << (eglu::ConfigSurfaceType() & EGL_WINDOW_BIT);
+			break;
+
+		case NativeColorMappingCase::NATIVETYPE_PIXMAP:
+			filters << (eglu::ConfigSurfaceType() & EGL_PIXMAP_BIT);
+			break;
+
+		case NativeColorMappingCase::NATIVETYPE_PBUFFER_COPY_TO_PIXMAP:
+			filters << (eglu::ConfigSurfaceType() & EGL_PBUFFER_BIT);
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	vector<NamedConfigIdSet> configIdSets;
+	NamedConfigIdSet::getDefaultSets(configIdSets, eglTestCtx.getConfigs(), filters);
+
+	for (vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+	{
+		group->addChild(new NativeColorMappingCase(eglTestCtx, (string(i->getName()) + "_clear").c_str(), i->getDescription(), false, type, i->getConfigIds()));
+		group->addChild(new NativeColorMappingCase(eglTestCtx, (string(i->getName()) + "_render").c_str(), i->getDescription(), true, type, i->getConfigIds()));
+	}
+}
+
+} // anonymous
+
+NativeColorMappingTests::NativeColorMappingTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "native_color_mapping", "Tests for mapping client colors to native surface")
+{
+}
+
+void NativeColorMappingTests::init (void)
+{
+	{
+		TestCaseGroup* windowGroup = new TestCaseGroup(m_eglTestCtx, "native_window", "Tests for mapping client color to native window");
+		addTestGroups(m_eglTestCtx, windowGroup, NativeColorMappingCase::NATIVETYPE_WINDOW);
+		addChild(windowGroup);
+	}
+
+	{
+		TestCaseGroup* pixmapGroup = new TestCaseGroup(m_eglTestCtx, "native_pixmap", "Tests for mapping client color to native pixmap");
+		addTestGroups(m_eglTestCtx, pixmapGroup, NativeColorMappingCase::NATIVETYPE_PIXMAP);
+		addChild(pixmapGroup);
+	}
+
+	{
+		TestCaseGroup* pbufferGroup = new TestCaseGroup(m_eglTestCtx, "pbuffer_to_native_pixmap", "Tests for mapping client color to native pixmap with eglCopyBuffers()");
+		addTestGroups(m_eglTestCtx, pbufferGroup, NativeColorMappingCase::NATIVETYPE_PBUFFER_COPY_TO_PIXMAP);
+		addChild(pbufferGroup);
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglNativeColorMappingTests.hpp b/modules/egl/teglNativeColorMappingTests.hpp
new file mode 100644
index 0000000..83e4120
--- /dev/null
+++ b/modules/egl/teglNativeColorMappingTests.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLNATIVECOLORMAPPINGTESTS_HPP
+#define _TEGLNATIVECOLORMAPPINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test for mapping client color values to native surface colors
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class NativeColorMappingTests : public TestCaseGroup
+{
+public:
+			NativeColorMappingTests	(EglTestContext& eglTestCtx);
+	void	init					(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLNATIVECOLORMAPPINGTESTS_HPP
diff --git a/modules/egl/teglNativeCoordMappingTests.cpp b/modules/egl/teglNativeCoordMappingTests.cpp
new file mode 100644
index 0000000..a79aebb
--- /dev/null
+++ b/modules/egl/teglNativeCoordMappingTests.cpp
@@ -0,0 +1,636 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tests for mapping client coordinates to native surface coordinates
+ *//*--------------------------------------------------------------------*/
+
+#include "teglNativeCoordMappingTests.hpp"
+
+#include "teglSimpleConfigCase.hpp"
+
+#include "tcuSurface.hpp"
+#include "tcuTexture.hpp"
+
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluUnique.hpp"
+#include "egluUtil.hpp"
+
+#include "gluDefs.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+
+#include "deUniquePtr.hpp"
+#include "deStringUtil.hpp"
+
+#include "deThread.hpp"
+#include "deMath.h"
+
+#include <vector>
+#include <string>
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+namespace egl
+{
+namespace
+{
+
+EGLContext createGLES2Context (EGLDisplay display, EGLConfig config)
+{
+	EGLContext		context = EGL_NO_CONTEXT;
+	const EGLint	attribList[] =
+	{
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+	TCU_CHECK_EGL_CALL(eglBindAPI(EGL_OPENGL_ES_API));
+
+	context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribList);
+	TCU_CHECK_EGL_MSG("eglCreateContext() failed");
+	TCU_CHECK(context);
+
+	return context;
+}
+
+deUint32 createGLES2Program (const glw::Functions& gl, TestLog& log)
+{
+	const char* const vertexShaderSource =
+	"attribute highp vec2 a_pos;\n"
+	"void main (void)\n"
+	"{\n"
+	"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+	"}";
+
+	const char* const fragmentShaderSource =
+	"void main (void)\n"
+	"{\n"
+	"\tgl_FragColor = vec4(1.0);\n"
+	"}";
+
+	deUint32	program			= 0;
+	deUint32	vertexShader	= 0;
+	deUint32	fragmentShader	= 0;
+
+	deInt32		vertexCompileStatus;
+	string		vertexInfoLog;
+	deInt32		fragmentCompileStatus;
+	string		fragmentInfoLog;
+	deInt32		linkStatus;
+	string		programInfoLog;
+
+	try
+	{
+		program			= gl.createProgram();
+		vertexShader	= gl.createShader(GL_VERTEX_SHADER);
+		fragmentShader	= gl.createShader(GL_FRAGMENT_SHADER);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create shaders and program");
+
+		gl.shaderSource(vertexShader, 1, &vertexShaderSource, DE_NULL);
+		gl.compileShader(vertexShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup vertex shader");
+
+		gl.shaderSource(fragmentShader, 1, &fragmentShaderSource, DE_NULL);
+		gl.compileShader(fragmentShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup fragment shader");
+
+		{
+			deInt32		infoLogLength = 0;
+
+			gl.getShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexCompileStatus);
+			gl.getShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+			vertexInfoLog.resize(infoLogLength, '\0');
+
+			gl.getShaderInfoLog(vertexShader, (glw::GLsizei)vertexInfoLog.length(), &infoLogLength, &(vertexInfoLog[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get vertex shader compile info");
+
+			vertexInfoLog.resize(infoLogLength);
+		}
+
+		{
+			deInt32		infoLogLength = 0;
+
+			gl.getShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentCompileStatus);
+			gl.getShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+			fragmentInfoLog.resize(infoLogLength, '\0');
+
+			gl.getShaderInfoLog(fragmentShader, (glw::GLsizei)fragmentInfoLog.length(), &infoLogLength, &(fragmentInfoLog[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get fragment shader compile info");
+
+			fragmentInfoLog.resize(infoLogLength);
+		}
+
+		gl.attachShader(program, vertexShader);
+		gl.attachShader(program, fragmentShader);
+		gl.linkProgram(program);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup program");
+
+		{
+			deInt32		infoLogLength = 0;
+
+			gl.getProgramiv(program, GL_LINK_STATUS, &linkStatus);
+			gl.getProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+			programInfoLog.resize(infoLogLength, '\0');
+
+			gl.getProgramInfoLog(program, (glw::GLsizei)programInfoLog.length(), &infoLogLength, &(programInfoLog[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get program link info");
+
+			programInfoLog.resize(infoLogLength);
+		}
+
+		if (linkStatus == 0 || vertexCompileStatus == 0 || fragmentCompileStatus == 0)
+		{
+
+			log.startShaderProgram(linkStatus != 0, programInfoLog.c_str());
+
+			log << TestLog::Shader(QP_SHADER_TYPE_VERTEX, vertexShaderSource, vertexCompileStatus != 0, vertexInfoLog);
+			log << TestLog::Shader(QP_SHADER_TYPE_FRAGMENT, fragmentShaderSource, fragmentCompileStatus != 0, fragmentInfoLog);
+
+			log.endShaderProgram();
+		}
+
+		gl.deleteShader(vertexShader);
+		gl.deleteShader(fragmentShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to delete shaders");
+
+		TCU_CHECK(linkStatus != 0 && vertexCompileStatus != 0 && fragmentCompileStatus != 0);
+	}
+	catch (...)
+	{
+		if (program)
+			gl.deleteProgram(program);
+
+		if (vertexShader)
+			gl.deleteShader(vertexShader);
+
+		if (fragmentShader)
+			gl.deleteShader(fragmentShader);
+
+		throw;
+	}
+
+	return program;
+}
+
+void clear (const glw::Functions& gl, const tcu::Vec4& color, int x, int y, int width, int height)
+{
+	gl.enable(GL_SCISSOR_TEST);
+	gl.scissor(x, y, width, height);
+	gl.clearColor(color.x(), color.y(), color.z(), color.w());
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Color clear failed");
+}
+
+tcu::Vec2 toGLCoord (int width, int height, int x, int y)
+{
+	const float xf = (float(2.0f * x) / width) - 1.0f;
+	const float yf = (float(2.0f * y) / height) -  1.0f;
+
+	return tcu::Vec2(xf, yf);
+}
+
+void render (const glw::Functions& gl, deUint32 program, int targetWidth, int targetHeight, int x, int y, int width, int height)
+{
+	const tcu::Vec2 positions[] =
+	{
+		toGLCoord(targetWidth, targetHeight, x,			y),
+		toGLCoord(targetWidth, targetHeight, x+width,	y),
+		toGLCoord(targetWidth, targetHeight, x+width,	y+height),
+
+		toGLCoord(targetWidth, targetHeight, x+width,	y+height),
+		toGLCoord(targetWidth, targetHeight, x,			y+height),
+		toGLCoord(targetWidth, targetHeight, x,			y)
+	};
+
+	deUint32 posLocation;
+
+	gl.useProgram(program);
+	posLocation	= gl.getAttribLocation(program, "a_pos");
+	gl.enableVertexAttribArray(posLocation);
+	gl.vertexAttribPointer(posLocation, 2, GL_FLOAT, GL_FALSE, 0, positions);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup shader program for rendering");
+
+	gl.viewport(0, 0, targetWidth, targetHeight);
+	gl.drawArrays(GL_TRIANGLES, 0, 6);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to render");
+}
+
+bool compareColor (const tcu::Vec4& a, const tcu::Vec4& b)
+{
+	const float threshold = 0.005f;
+
+	return deFloatAbs(a.x() - b.x()) < threshold &&  deFloatAbs(a.y() - b.y()) < threshold && deFloatAbs(a.z() - b.z()) < threshold && deFloatAbs(a.w() - b.w()) < threshold;
+}
+
+bool validate (TestLog& log, const tcu::TextureLevel& result, int rectX, int rectY, int rectW, int rectH)
+{
+	const tcu::Vec4		black		(0.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4		white		(1.0f, 1.0f, 1.0f, 1.0f);
+	tcu::Surface		errorMask	(result.getWidth(), result.getHeight());
+	bool				isOk		= true;
+
+	for (int y = 0; y < result.getHeight(); y++)
+	{
+		for (int x = 0; x < result.getWidth(); x++)
+		{
+			const tcu::Vec4 resultColor = result.getAccess().getPixel(x, y);
+
+			if (x > rectX && x < rectX + rectW - 1 && y > rectY && y < rectY + rectH - 1)
+			{
+				if (!compareColor(resultColor, white))
+				{
+					errorMask.setPixel(x, y, tcu::RGBA(255, 0, 0, 255));
+					isOk = false;
+				}
+				else
+					errorMask.setPixel(x, y, tcu::RGBA(0, 255, 0, 255));
+			}
+			else if (x < rectX-1 || x > rectX + rectW || y < rectY-1 || y > rectY + rectH)
+			{
+				if (!compareColor(resultColor, black))
+				{
+					errorMask.setPixel(x, y, tcu::RGBA(255, 0, 0, 255));
+					isOk = false;
+				}
+				else
+					errorMask.setPixel(x, y, tcu::RGBA(0, 255, 0, 255));
+			}
+			else
+			{
+				// Pixel is close to edge of reference rectangle
+
+				if (!compareColor(resultColor, black) && !compareColor(resultColor, white))
+				{
+					errorMask.setPixel(x, y, tcu::RGBA(255, 0, 0, 255));
+					isOk = false;
+				}
+				else
+					errorMask.setPixel(x, y, tcu::RGBA(0, 255, 0, 255));
+			}
+		}
+	}
+
+	log << TestLog::Image("Result", "Result of rendering", result.getAccess());
+
+	if (!isOk)
+		log << TestLog::Image("Error Mask", "Error Mask", errorMask.getAccess());
+
+	return isOk;
+}
+
+class NativeCoordMappingCase : public SimpleConfigCase
+{
+public:
+	enum NativeType
+	{
+		NATIVETYPE_WINDOW = 0,
+		NATIVETYPE_PIXMAP,
+		NATIVETYPE_PBUFFER_COPY_TO_PIXMAP
+	};
+
+				NativeCoordMappingCase	(EglTestContext& eglTestCtx, const char* name, const char* description, bool render, NativeType nativeType, const vector<EGLint>& configIds);
+				~NativeCoordMappingCase	(void);
+
+private:
+	void		executeForConfig		(tcu::egl::Display& display, EGLConfig config);
+
+	NativeType	m_nativeType;
+	bool		m_render;
+};
+
+NativeCoordMappingCase::NativeCoordMappingCase (EglTestContext& eglTestCtx, const char* name, const char* description, bool render, NativeType nativeType, const vector<EGLint>& configIds)
+	: SimpleConfigCase	(eglTestCtx, name, description, configIds)
+	, m_nativeType		(nativeType)
+	, m_render			(render)
+{
+}
+
+NativeCoordMappingCase::~NativeCoordMappingCase	(void)
+{
+	deinit();
+}
+
+void logConfigInfo (TestLog& log, EGLDisplay display, EGLConfig config, NativeCoordMappingCase::NativeType nativeType, int waitFrames)
+{
+	log << TestLog::Message << "EGL_RED_SIZE: "		<< eglu::getConfigAttribInt(display, config, EGL_RED_SIZE)		<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_GREEN_SIZE: "	<< eglu::getConfigAttribInt(display, config, EGL_GREEN_SIZE)	<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_BLUE_SIZE: "	<< eglu::getConfigAttribInt(display, config, EGL_BLUE_SIZE)		<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_ALPHA_SIZE: "	<< eglu::getConfigAttribInt(display, config, EGL_ALPHA_SIZE)	<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_DEPTH_SIZE: "	<< eglu::getConfigAttribInt(display, config, EGL_DEPTH_SIZE)	<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_STENCIL_SIZE: "	<< eglu::getConfigAttribInt(display, config, EGL_STENCIL_SIZE)	<< TestLog::EndMessage;
+	log << TestLog::Message << "EGL_SAMPLES: "		<< eglu::getConfigAttribInt(display, config, EGL_SAMPLES)		<< TestLog::EndMessage;
+
+	if (nativeType == NativeCoordMappingCase::NATIVETYPE_WINDOW)
+		log << TestLog::Message << "Waiting " << waitFrames * 16 << "ms after eglSwapBuffers() and glFinish() for frame to become visible" << TestLog::EndMessage;
+}
+
+bool testNativeWindow (TestLog& log, eglu::NativeDisplay& nativeDisplay, eglu::NativeWindow& nativeWindow, EGLDisplay display, EGLContext context, EGLConfig config, const glw::Functions& gl, bool renderColor, int waitFrames)
+{
+	const int			rectX		= 8;
+	const int			rectY		= 16;
+	const int			rectW		= 64;
+	const int			rectH		= 72;
+
+	const tcu::IVec2	screenSize	= nativeWindow.getScreenSize();
+	eglu::UniqueSurface	surface		(display, eglu::createWindowSurface(nativeDisplay, nativeWindow, display, config, DE_NULL));
+	const tcu::IVec2	surfaceSize = eglu::getSurfaceSize(display, *surface);
+	deUint32			program		= 0;
+	bool				isOk		= true;
+	tcu::TextureLevel	result;
+
+	try
+	{
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, *surface, *surface, context));
+
+		if (renderColor)
+			program = createGLES2Program(gl, log);
+
+		clear(gl, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0, 0, surfaceSize.x(), surfaceSize.y());
+
+		if (renderColor)
+			render(gl, program, surfaceSize.x(), surfaceSize.y(), rectX, rectY, rectW, rectH);
+		else
+			clear(gl, tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f), rectX, rectY, rectW, rectH);
+
+		TCU_CHECK_EGL_CALL(eglSwapBuffers(display, *surface));
+		TCU_CHECK_EGL_CALL(eglWaitClient());
+		deSleep(waitFrames*16);
+		nativeWindow.readScreenPixels(&result);
+
+		if (!validate(log, result, rectX, screenSize.y() - rectY - rectH, rectW, rectH))
+			isOk = false;
+
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+	}
+	catch (...)
+	{
+		if (program)
+			gl.deleteProgram(program);
+		throw;
+	}
+
+	return isOk;
+}
+
+bool testNativePixmap (TestLog& log, eglu::NativeDisplay& nativeDisplay, eglu::NativePixmap& nativePixmap, int width, int height, EGLDisplay display, EGLContext context, EGLConfig config, const glw::Functions& gl, bool renderColor)
+{
+	const int			rectX		= 8;
+	const int			rectY		= 16;
+	const int			rectW		= 64;
+	const int			rectH		= 72;
+
+	eglu::UniqueSurface	surface(display, eglu::createPixmapSurface(nativeDisplay, nativePixmap, display, config, DE_NULL));
+	deUint32			program	= 0;
+	bool				isOk	= true;
+	tcu::TextureLevel	result;
+
+	try
+	{
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, *surface, *surface, context));
+
+		if (renderColor)
+			program = createGLES2Program(gl, log);
+
+		clear(gl, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0, 0, width, height);
+
+		if (renderColor)
+			render(gl, program, width, height, rectX, rectY, rectW, rectH);
+		else
+			clear(gl, tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f), rectX, rectY, rectW, rectH);
+
+		TCU_CHECK_EGL_CALL(eglWaitClient());
+		nativePixmap.readPixels(&result);
+
+		if (!validate(log, result, rectX, height - 1 - rectY - rectH, rectW, rectH))
+			isOk = false;
+
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+	}
+	catch (...)
+	{
+		if (program)
+			gl.deleteProgram(program);
+		throw;
+	}
+
+	return isOk;
+}
+
+bool testNativePixmapCopy (TestLog& log, eglu::NativePixmap& nativePixmap, int width, int height, EGLDisplay display, EGLContext context, EGLConfig config, const glw::Functions& gl, bool renderColor)
+{
+	const int			rectX		= 8;
+	const int			rectY		= 16;
+	const int			rectW		= 64;
+	const int			rectH		= 72;
+
+	eglu::UniqueSurface	surface(display, eglCreatePbufferSurface(display, config, DE_NULL));
+	deUint32			program	= 0;
+	bool				isOk	= true;
+	tcu::TextureLevel	result;
+
+	try
+	{
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, *surface, *surface, context));
+
+		if (renderColor)
+			program = createGLES2Program(gl, log);
+
+		clear(gl, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0, 0, width, height);
+
+		if (renderColor)
+			render(gl, program, width, height, rectX, rectY, rectW, rectH);
+		else
+			clear(gl, tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f), rectX, rectY, rectW, rectH);
+
+		TCU_CHECK_EGL_CALL(eglCopyBuffers(display, *surface, nativePixmap.getLegacyNative()));
+		TCU_CHECK_EGL_CALL(eglWaitClient());
+		nativePixmap.readPixels(&result);
+
+		if (!validate(log, result, rectX, height - 1 - rectY, rectW, rectH))
+			isOk = false;
+
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+	}
+	catch (...)
+	{
+		if (program)
+			gl.deleteProgram(program);
+		throw;
+	}
+
+	return isOk;
+}
+
+void checkSupport (EglTestContext& eglTestCtx, NativeCoordMappingCase::NativeType nativeType)
+{
+	switch (nativeType)
+	{
+		case NativeCoordMappingCase::NATIVETYPE_WINDOW:
+			if ((eglTestCtx.getNativeWindowFactory().getCapabilities() & eglu::NativeWindow::CAPABILITY_READ_SCREEN_PIXELS) == 0)
+				throw tcu::NotSupportedError("Native window doesn't support readPixels()", "", __FILE__, __LINE__);
+			break;
+
+		case NativeCoordMappingCase::NATIVETYPE_PIXMAP:
+			if ((eglTestCtx.getNativePixmapFactory().getCapabilities() & eglu::NativePixmap::CAPABILITY_READ_PIXELS) == 0)
+				throw tcu::NotSupportedError("Native pixmap doesn't support readPixels()", "", __FILE__, __LINE__);
+			break;
+
+		case NativeCoordMappingCase::NATIVETYPE_PBUFFER_COPY_TO_PIXMAP:
+			if ((eglTestCtx.getNativePixmapFactory().getCapabilities() & eglu::NativePixmap::CAPABILITY_READ_PIXELS) == 0 ||
+				(eglTestCtx.getNativePixmapFactory().getCapabilities() & eglu::NativePixmap::CAPABILITY_CREATE_SURFACE_LEGACY) == 0)
+				throw tcu::NotSupportedError("Native pixmap doesn't support readPixels() or legacy create surface", "", __FILE__, __LINE__);
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+void NativeCoordMappingCase::executeForConfig (tcu::egl::Display& display, EGLConfig config)
+{
+	const int				width		= 128;
+	const int				height		= 128;
+	const string			configIdStr	(de::toString(eglu::getConfigAttribInt(display.getEGLDisplay(), config, EGL_CONFIG_ID)));
+	tcu::ScopedLogSection	logSection	(m_testCtx.getLog(), ("Config ID " + configIdStr).c_str(), ("Config ID " + configIdStr).c_str());
+	const int				waitFrames	= 5;
+
+	logConfigInfo(m_testCtx.getLog(), display.getEGLDisplay(), config, m_nativeType, waitFrames);
+
+	checkSupport(m_eglTestCtx, m_nativeType);
+
+	eglu::UniqueContext	context(display.getEGLDisplay(), createGLES2Context(display.getEGLDisplay(), config));
+	glw::Functions		gl;
+
+	m_eglTestCtx.getGLFunctions(gl, glu::ApiType::es(2,0));
+
+	switch (m_nativeType)
+	{
+		case NATIVETYPE_WINDOW:
+		{
+			de::UniquePtr<eglu::NativeWindow> nativeWindow(m_eglTestCtx.createNativeWindow(display.getEGLDisplay(), config, DE_NULL, width, height, eglu::WindowParams::VISIBILITY_VISIBLE));
+
+			if (!testNativeWindow(m_testCtx.getLog(), m_eglTestCtx.getNativeDisplay(), *nativeWindow, display.getEGLDisplay(), *context, config, gl, m_render, waitFrames))
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid color rendered");
+
+			break;
+		}
+
+		case NATIVETYPE_PIXMAP:
+		{
+			de::UniquePtr<eglu::NativePixmap> nativePixmap(m_eglTestCtx.createNativePixmap(display.getEGLDisplay(), config, DE_NULL, width, height));
+
+			if (!testNativePixmap(m_testCtx.getLog(), m_eglTestCtx.getNativeDisplay(), *nativePixmap, width, height, display.getEGLDisplay(), *context, config, gl, m_render))
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid color rendered");
+
+			break;
+		}
+
+		case NATIVETYPE_PBUFFER_COPY_TO_PIXMAP:
+		{
+			de::UniquePtr<eglu::NativePixmap> nativePixmap(m_eglTestCtx.createNativePixmap(display.getEGLDisplay(), config, DE_NULL, width, height));
+
+			if (!testNativePixmapCopy(m_testCtx.getLog(), *nativePixmap, width, height, display.getEGLDisplay(), *context, config, gl, m_render))
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid color rendered");
+
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+void addTestGroups (EglTestContext& eglTestCtx, TestCaseGroup* group, NativeCoordMappingCase::NativeType type)
+{
+	eglu::FilterList filters;
+
+	switch (type)
+	{
+		case NativeCoordMappingCase::NATIVETYPE_WINDOW:
+			filters << (eglu::ConfigSurfaceType() & EGL_WINDOW_BIT);
+			break;
+
+		case NativeCoordMappingCase::NATIVETYPE_PIXMAP:
+			filters << (eglu::ConfigSurfaceType() & EGL_PIXMAP_BIT);
+			break;
+
+		case NativeCoordMappingCase::NATIVETYPE_PBUFFER_COPY_TO_PIXMAP:
+			filters << (eglu::ConfigSurfaceType() & EGL_PBUFFER_BIT);
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	vector<NamedConfigIdSet> configIdSets;
+	NamedConfigIdSet::getDefaultSets(configIdSets, eglTestCtx.getConfigs(), filters);
+
+	for (vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+	{
+		group->addChild(new NativeCoordMappingCase(eglTestCtx, (string(i->getName()) + "_clear").c_str(), i->getDescription(), false, type, i->getConfigIds()));
+		group->addChild(new NativeCoordMappingCase(eglTestCtx, (string(i->getName()) + "_render").c_str(), i->getDescription(), true, type, i->getConfigIds()));
+	}
+}
+
+} // anonymous
+
+NativeCoordMappingTests::NativeCoordMappingTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "native_coord_mapping", "Tests for mapping client coordinates to native surface")
+{
+}
+
+void NativeCoordMappingTests::init (void)
+{
+	{
+		TestCaseGroup* windowGroup = new TestCaseGroup(m_eglTestCtx, "native_window", "Tests for mapping client color to native window");
+		addTestGroups(m_eglTestCtx, windowGroup, NativeCoordMappingCase::NATIVETYPE_WINDOW);
+		addChild(windowGroup);
+	}
+
+	{
+		TestCaseGroup* pixmapGroup = new TestCaseGroup(m_eglTestCtx, "native_pixmap", "Tests for mapping client color to native pixmap");
+		addTestGroups(m_eglTestCtx, pixmapGroup, NativeCoordMappingCase::NATIVETYPE_PIXMAP);
+		addChild(pixmapGroup);
+	}
+
+	{
+		TestCaseGroup* pbufferGroup = new TestCaseGroup(m_eglTestCtx, "pbuffer_to_native_pixmap", "Tests for mapping client color to native pixmap with eglCopyBuffers()");
+		addTestGroups(m_eglTestCtx, pbufferGroup, NativeCoordMappingCase::NATIVETYPE_PBUFFER_COPY_TO_PIXMAP);
+		addChild(pbufferGroup);
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglNativeCoordMappingTests.hpp b/modules/egl/teglNativeCoordMappingTests.hpp
new file mode 100644
index 0000000..7e3321c
--- /dev/null
+++ b/modules/egl/teglNativeCoordMappingTests.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLNATIVECOORDMAPPINGTESTS_HPP
+#define _TEGLNATIVECOORDMAPPINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tests for mapping client coordinates to native surface coordinates
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class NativeCoordMappingTests : public TestCaseGroup
+{
+public:
+			NativeCoordMappingTests	(EglTestContext& eglTestCtx);
+	void	init					(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLNATIVECOORDMAPPINGTESTS_HPP
diff --git a/modules/egl/teglNegativeApiTests.cpp b/modules/egl/teglNegativeApiTests.cpp
new file mode 100644
index 0000000..cd91ca9
--- /dev/null
+++ b/modules/egl/teglNegativeApiTests.cpp
@@ -0,0 +1,1260 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative API Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglNegativeApiTests.hpp"
+#include "teglApiCase.hpp"
+
+#include <memory>
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+
+NegativeApiTests::NegativeApiTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "negative_api", "Negative API Tests")
+{
+}
+
+NegativeApiTests::~NegativeApiTests (void)
+{
+}
+
+void NegativeApiTests::init (void)
+{
+	// \todo [2012-10-02 pyry] Add tests for EGL_NOT_INITIALIZED to all functions taking in EGLDisplay
+	// \todo [2012-10-02 pyry] Implement negative cases for following non-trivial cases:
+	//  * eglBindTexImage()
+	//    - EGL_BAD_ACCESS is generated if buffer is already bound to a texture
+	//    - EGL_BAD_MATCH is generated if the surface attribute EGL_TEXTURE_FORMAT is set to EGL_NO_TEXTURE
+	//    - EGL_BAD_MATCH is generated if buffer is not a valid buffer (currently only EGL_BACK_BUFFER may be specified)
+	//    - EGL_BAD_SURFACE is generated if surface is not a pbuffer surface supporting texture binding
+	//  * eglCopyBuffers()
+	//    - EGL_BAD_NATIVE_PIXMAP is generated if the implementation does not support native pixmaps
+	//    - EGL_BAD_NATIVE_PIXMAP may be generated if native_pixmap is not a valid native pixmap
+	//    - EGL_BAD_MATCH is generated if the format of native_pixmap is not compatible with the color buffer of surface
+	//  * eglCreateContext()
+	//    - EGL_BAD_MATCH is generated if the current rendering API is EGL_NONE
+	//	  - EGL_BAD_MATCH is generated if the server context state for share_context exists in an address space which cannot be shared with the newly created context
+	//	  - EGL_BAD_CONTEXT is generated if share_context is not an EGL rendering context of the same client API type as the newly created context and is not EGL_NO_CONTEXT
+	//  * eglCreatePbufferFromClientBuffer()
+	//    - various BAD_MATCH, BAD_ACCESS etc. conditions
+	//  * eglCreatePbufferSurface()
+	//    - EGL_BAD_MATCH is generated if the EGL_TEXTURE_FORMAT attribute is not EGL_NO_TEXTURE, and EGL_WIDTH and/or EGL_HEIGHT specify an invalid size
+	//  * eglCreatePixmapSurface()
+	//    - EGL_BAD_ATTRIBUTE is generated if attrib_list contains an invalid pixmap attribute
+	//    - EGL_BAD_MATCH is generated if the attributes of native_pixmap do not correspond to config or if config does not support rendering to pixmaps
+	//    - EGL_BAD_MATCH is generated if config does not support the specified OpenVG alpha format attribute or colorspace attribute
+	//  * eglCreateWindowSurface()
+	//    - EGL_BAD_ATTRIBUTE is generated if attrib_list contains an invalid window attribute
+	//    - EGL_BAD_MATCH is generated if the attributes of native_window do not correspond to config or if config does not support rendering to windows
+	//    - EGL_BAD_MATCH is generated if config does not support the specified OpenVG alpha format attribute or colorspace attribute
+	//  * eglMakeCurrent()
+	//    - EGL_BAD_MATCH is generated if draw or read are not compatible with context
+	//    - EGL_BAD_MATCH is generated if context is set to EGL_NO_CONTEXT and draw or read are not set to EGL_NO_SURFACE
+	//    - EGL_BAD_MATCH is generated if draw or read are set to EGL_NO_SURFACE and context is not set to EGL_NO_CONTEXT
+	//    - EGL_BAD_ACCESS is generated if context is current to some other thread
+	//    - EGL_BAD_NATIVE_PIXMAP may be generated if a native pixmap underlying either draw or read is no longer valid
+	//    - EGL_BAD_NATIVE_WINDOW may be generated if a native window underlying either draw or read is no longer valid
+	//  * eglReleaseTexImage()
+	//    - EGL_BAD_MATCH is generated if buffer is not a valid buffer (currently only EGL_BACK_BUFFER may be specified)
+	//  * eglSwapInterval()
+	//    - EGL_BAD_SURFACE is generated if there is no surface bound to the current context
+	//  * eglWaitNative()
+	//    - EGL_BAD_CURRENT_SURFACE is generated if the surface associated with the current context has a native window or pixmap, and that window or pixmap is no longer valid
+
+	using namespace tcu::egl;
+	using namespace eglu;
+
+	static const EGLint s_emptyAttribList[]			= { EGL_NONE };
+	static const EGLint s_es1ContextAttribList[]	= { EGL_CONTEXT_CLIENT_VERSION, 1, EGL_NONE };
+	static const EGLint s_es2ContextAttribList[]	= { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+
+	static const EGLenum s_renderAPIs[]				= { EGL_OPENGL_API, EGL_OPENGL_ES_API, EGL_OPENVG_API };
+
+	TEGL_ADD_API_CASE(bind_api, "eglBindAPI() negative tests",
+		{
+			TestLog& log = m_testCtx.getLog();
+			log << TestLog::Section("Test1", "EGL_BAD_PARAMETER is generated if api is not one of the accepted tokens");
+
+			expectFalse(eglBindAPI(0));
+			expectError(EGL_BAD_PARAMETER);
+
+			expectFalse(eglBindAPI(0xfdfdfdfd));
+			expectError(EGL_BAD_PARAMETER);
+
+			expectFalse(eglBindAPI((EGLenum)0xffffffff));
+			expectError(EGL_BAD_PARAMETER);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_PARAMETER is generated if the specified client API is not supported by the EGL implementation");
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_renderAPIs); ndx++)
+			{
+				if (!isAPISupported(s_renderAPIs[ndx]))
+				{
+					expectFalse(eglBindAPI(s_renderAPIs[ndx]));
+					expectError(EGL_BAD_PARAMETER);
+				}
+			}
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(bind_tex_image, "eglBindTexImage() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglBindTexImage(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_BACK_BUFFER));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglBindTexImage((EGLDisplay)-1, EGL_NO_SURFACE, EGL_BACK_BUFFER));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_SURFACE is generated if surface is not an EGL surface");
+
+			expectFalse(eglBindTexImage(display, EGL_NO_SURFACE, EGL_BACK_BUFFER));
+			expectError(EGL_BAD_SURFACE);
+
+			expectFalse(eglBindTexImage(display, (EGLSurface)-1, EGL_BACK_BUFFER));
+			expectError(EGL_BAD_SURFACE);
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(copy_buffers, "eglCopyBuffers() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglCopyBuffers(EGL_NO_DISPLAY, EGL_NO_SURFACE, (NativePixmapType)0));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglCopyBuffers((EGLDisplay)-1, EGL_NO_SURFACE, (NativePixmapType)0));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_SURFACE is generated if surface is not an EGL surface");
+
+			expectFalse(eglCopyBuffers(display, EGL_NO_SURFACE, (NativePixmapType)0));
+			expectError(EGL_BAD_SURFACE);
+
+			expectFalse(eglCopyBuffers(display, (EGLSurface)-1, (NativePixmapType)0));
+			expectError(EGL_BAD_SURFACE);
+
+			log << TestLog::EndSection;
+		});
+
+	static const EGLint s_invalidChooseConfigAttribList0[]	= { 0, EGL_NONE };
+	static const EGLint s_invalidChooseConfigAttribList1[]	= { (EGLint)0xffffffff };
+	static const EGLint s_invalidChooseConfigAttribList2[]	= { EGL_BIND_TO_TEXTURE_RGB, 4, EGL_NONE };
+	static const EGLint s_invalidChooseConfigAttribList3[]	= { EGL_BIND_TO_TEXTURE_RGBA, 5, EGL_NONE };
+	static const EGLint s_invalidChooseConfigAttribList4[]	= { EGL_COLOR_BUFFER_TYPE, 0, EGL_NONE };
+	static const EGLint s_invalidChooseConfigAttribList5[]	= { EGL_MATCH_NATIVE_PIXMAP, -1, EGL_NONE };
+	static const EGLint s_invalidChooseConfigAttribList6[]	= { EGL_NATIVE_RENDERABLE, 6, EGL_NONE };
+	static const EGLint s_invalidChooseConfigAttribList7[]	= { EGL_TRANSPARENT_TYPE, 6, EGL_NONE };
+	static const EGLint* s_invalidChooseConfigAttribLists[] =
+	{
+		&s_invalidChooseConfigAttribList0[0],
+		&s_invalidChooseConfigAttribList1[0],
+		&s_invalidChooseConfigAttribList2[0],
+		&s_invalidChooseConfigAttribList3[0],
+		&s_invalidChooseConfigAttribList4[0],
+		&s_invalidChooseConfigAttribList5[0],
+		&s_invalidChooseConfigAttribList6[0],
+		&s_invalidChooseConfigAttribList7[0]
+	};
+
+	TEGL_ADD_API_CASE(choose_config, "eglChooseConfig() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+			EGLConfig	configs[1];
+			EGLint		numConfigs;
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglChooseConfig(EGL_NO_DISPLAY, s_emptyAttribList, &configs[0], DE_LENGTH_OF_ARRAY(configs), &numConfigs));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglChooseConfig((EGLDisplay)-1, s_emptyAttribList, &configs[0], DE_LENGTH_OF_ARRAY(configs), &numConfigs));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_ATTRIBUTE is generated if attribute_list contains an invalid frame buffer configuration attribute");
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_invalidChooseConfigAttribLists); ndx++)
+			{
+				expectFalse(eglChooseConfig(display, s_invalidChooseConfigAttribLists[ndx], &configs[0], DE_LENGTH_OF_ARRAY(configs), &numConfigs));
+				expectError(EGL_BAD_ATTRIBUTE);
+			}
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test3", "EGL_BAD_PARAMETER is generated if num_config is NULL");
+
+			expectFalse(eglChooseConfig(display, s_emptyAttribList, &configs[0], DE_LENGTH_OF_ARRAY(configs), DE_NULL));
+			expectError(EGL_BAD_PARAMETER);
+
+			log << TestLog::EndSection;
+		});
+
+	static const EGLint s_invalidCreateContextAttribList0[] = { 0, EGL_NONE };
+	static const EGLint s_invalidCreateContextAttribList1[] = { (EGLint)0xffffffff };
+
+	TEGL_ADD_API_CASE(create_context, "eglCreateContext() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectNoContext(eglCreateContext(EGL_NO_DISPLAY, DE_NULL, EGL_NO_CONTEXT, s_emptyAttribList));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectNoContext(eglCreateContext((EGLDisplay)-1, DE_NULL, EGL_NO_CONTEXT, s_emptyAttribList));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_CONFIG is generated if config is not an EGL frame buffer configuration");
+
+			expectNoContext(eglCreateContext(display, (EGLConfig)-1, EGL_NO_CONTEXT, s_emptyAttribList));
+			expectError(EGL_BAD_CONFIG);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test3", "EGL_BAD_CONFIG is generated if config does not support the current rendering API");
+
+			if (isAPISupported(EGL_OPENGL_API))
+			{
+				EGLConfig es1OnlyConfig;
+				if (getConfig(&es1OnlyConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_ES_BIT) << (ConfigRenderableType() ^ EGL_OPENGL_BIT)))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_API));
+					expectNoContext(eglCreateContext(display, es1OnlyConfig, EGL_NO_CONTEXT, s_es1ContextAttribList));
+					expectError(EGL_BAD_CONFIG);
+				}
+
+				EGLConfig es2OnlyConfig;
+				if (getConfig(&es2OnlyConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_ES2_BIT) << (ConfigRenderableType() ^ EGL_OPENGL_BIT)))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_API));
+					expectNoContext(eglCreateContext(display, es2OnlyConfig, EGL_NO_CONTEXT, s_es2ContextAttribList));
+					expectError(EGL_BAD_CONFIG);
+				}
+
+				EGLConfig vgOnlyConfig;
+				if (getConfig(&vgOnlyConfig, FilterList() << (ConfigRenderableType() & EGL_OPENVG_BIT) << (ConfigRenderableType() ^ EGL_OPENGL_BIT)))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_API));
+					expectNoContext(eglCreateContext(display, vgOnlyConfig, EGL_NO_CONTEXT, s_emptyAttribList));
+					expectError(EGL_BAD_CONFIG);
+				}
+			}
+
+			if (isAPISupported(EGL_OPENGL_ES_API))
+			{
+				EGLConfig glOnlyConfig;
+				if (getConfig(&glOnlyConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_BIT) << (ConfigRenderableType() ^ (EGL_OPENGL_ES_BIT|EGL_OPENGL_ES2_BIT))))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_ES_API));
+					expectNoContext(eglCreateContext(display, glOnlyConfig, EGL_NO_CONTEXT, s_emptyAttribList));
+					expectError(EGL_BAD_CONFIG);
+				}
+
+				EGLConfig vgOnlyConfig;
+				if (getConfig(&vgOnlyConfig, FilterList() << (ConfigRenderableType() & EGL_OPENVG_BIT) << (ConfigRenderableType() ^ (EGL_OPENGL_ES_BIT|EGL_OPENGL_ES2_BIT))))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_ES_API));
+					expectNoContext(eglCreateContext(display, vgOnlyConfig, EGL_NO_CONTEXT, s_emptyAttribList));
+					expectError(EGL_BAD_CONFIG);
+				}
+			}
+
+			if (isAPISupported(EGL_OPENVG_API))
+			{
+				EGLConfig glOnlyConfig;
+				if (getConfig(&glOnlyConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_BIT) << (ConfigRenderableType() ^ EGL_OPENVG_BIT)))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_ES_API));
+					expectNoContext(eglCreateContext(display, glOnlyConfig, EGL_NO_CONTEXT, s_emptyAttribList));
+					expectError(EGL_BAD_CONFIG);
+				}
+
+				EGLConfig es1OnlyConfig;
+				if (getConfig(&es1OnlyConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_ES_BIT) << (ConfigRenderableType() ^ EGL_OPENVG_BIT)))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_API));
+					expectNoContext(eglCreateContext(display, es1OnlyConfig, EGL_NO_CONTEXT, s_es1ContextAttribList));
+					expectError(EGL_BAD_CONFIG);
+				}
+
+				EGLConfig es2OnlyConfig;
+				if (getConfig(&es2OnlyConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_ES2_BIT) << (ConfigRenderableType() ^ EGL_OPENVG_BIT)))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_API));
+					expectNoContext(eglCreateContext(display, es2OnlyConfig, EGL_NO_CONTEXT, s_es2ContextAttribList));
+					expectError(EGL_BAD_CONFIG);
+				}
+			}
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test4", "EGL_BAD_CONFIG is generated if OpenGL ES 1.x context is requested and EGL_RENDERABLE_TYPE attribute of config does not contain EGL_OPENGL_ES_BIT");
+
+			if (isAPISupported(EGL_OPENGL_ES_API))
+			{
+				EGLConfig notES1Config;
+				if (getConfig(&notES1Config, FilterList() << (ConfigRenderableType() ^ EGL_OPENGL_ES_BIT)))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_ES_API));
+					expectNoContext(eglCreateContext(display, notES1Config, EGL_NO_CONTEXT, s_es1ContextAttribList));
+					expectError(EGL_BAD_CONFIG);
+				}
+			}
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test5", "EGL_BAD_CONFIG is generated if OpenGL ES 2.x context is requested and EGL_RENDERABLE_TYPE attribute of config does not contain EGL_OPENGL_ES2_BIT");
+
+			if (isAPISupported(EGL_OPENGL_ES_API))
+			{
+				EGLConfig notES2Config;
+				if (getConfig(&notES2Config, FilterList() << (ConfigRenderableType() ^ EGL_OPENGL_ES2_BIT)))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_ES_API));
+					expectNoContext(eglCreateContext(display, notES2Config, EGL_NO_CONTEXT, s_es2ContextAttribList));
+					expectError(EGL_BAD_CONFIG);
+				}
+			}
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test6", "EGL_BAD_ATTRIBUTE is generated if attrib_list contains an invalid context attribute");
+
+			if (isAPISupported(EGL_OPENGL_API))
+			{
+				EGLConfig glConfig;
+				if (getConfig(&glConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_BIT)))
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_API));
+					expectNoContext(eglCreateContext(display, glConfig, EGL_NO_CONTEXT, s_es1ContextAttribList));
+					expectError(EGL_BAD_ATTRIBUTE);
+				}
+			}
+
+			if (isAPISupported(EGL_OPENVG_API))
+			{
+				EGLConfig vgConfig;
+				if (getConfig(&vgConfig, FilterList() << (ConfigRenderableType() & EGL_OPENVG_BIT)))
+				{
+					expectTrue(eglBindAPI(EGL_OPENVG_API));
+					expectNoContext(eglCreateContext(display, vgConfig, EGL_NO_CONTEXT, s_es1ContextAttribList));
+					expectError(EGL_BAD_ATTRIBUTE);
+				}
+			}
+
+			if (isAPISupported(EGL_OPENGL_ES_API))
+			{
+				bool		gotConfig	= false;
+				EGLConfig	esConfig;
+
+				gotConfig = getConfig(&esConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_ES_BIT)) ||
+							getConfig(&esConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_ES2_BIT));
+
+				if (gotConfig)
+				{
+					expectTrue(eglBindAPI(EGL_OPENGL_ES_API));
+					expectNoContext(eglCreateContext(display, esConfig, EGL_NO_CONTEXT, s_invalidCreateContextAttribList0));
+					expectError(EGL_BAD_ATTRIBUTE);
+					expectNoContext(eglCreateContext(display, esConfig, EGL_NO_CONTEXT, s_invalidCreateContextAttribList1));
+					expectError(EGL_BAD_ATTRIBUTE);
+				}
+			}
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(create_pbuffer_from_client_buffer, "eglCreatePbufferFromClientBuffer() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+			EGLConfig	anyConfig;
+			EGLint		unused		= 0;
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectNoSurface(eglCreatePbufferFromClientBuffer(EGL_NO_DISPLAY, EGL_OPENVG_IMAGE, 0, (EGLConfig)0, DE_NULL));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectNoSurface(eglCreatePbufferFromClientBuffer((EGLDisplay)-1, EGL_OPENVG_IMAGE, 0, (EGLConfig)0, DE_NULL));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_CONFIG is generated if config is not an EGL frame buffer configuration");
+
+			expectNoSurface(eglCreatePbufferFromClientBuffer(display, EGL_OPENVG_IMAGE, 0, (EGLConfig)-1, DE_NULL));
+			expectError(EGL_BAD_CONFIG);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test3", "EGL_BAD_PARAMETER is generated if buftype is not EGL_OPENVG_IMAGE");
+
+			expectTrue(eglGetConfigs(display, &anyConfig, 1, &unused));
+
+			expectNoSurface(eglCreatePbufferFromClientBuffer(display, EGL_OPENVG_IMAGE, (EGLClientBuffer)-1, anyConfig, DE_NULL));
+			expectError(EGL_BAD_CONFIG);
+
+			log << TestLog::EndSection;
+		});
+
+	static const EGLint s_validGenericPbufferAttrib[] = { EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE };
+
+	static const EGLint s_invalidGenericPbufferAttrib0[] = { 0, EGL_NONE };
+	static const EGLint s_invalidGenericPbufferAttrib1[] = { (EGLint)0xffffffff };
+	static const EGLint s_invalidGenericPbufferAttrib2[] = { EGL_WIDTH, 64, EGL_HEIGHT, -1, EGL_NONE };
+	static const EGLint s_invalidGenericPbufferAttrib3[] = { EGL_WIDTH, -1, EGL_HEIGHT, 64, EGL_NONE };
+	static const EGLint* s_invalidGenericPbufferAttribs[] =
+	{
+		s_invalidGenericPbufferAttrib0,
+		s_invalidGenericPbufferAttrib1,
+		s_invalidGenericPbufferAttrib2,
+		s_invalidGenericPbufferAttrib3
+	};
+
+	static const EGLint s_invalidNoEsPbufferAttrib0[] = { EGL_MIPMAP_TEXTURE, EGL_TRUE, EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE };
+	static const EGLint s_invalidNoEsPbufferAttrib1[] = { EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA, EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE };
+	static const EGLint s_invalidNoEsPbufferAttrib2[] = { EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE };
+	static const EGLint* s_invalidNoEsPbufferAttribs[] =
+	{
+		s_invalidNoEsPbufferAttrib0,
+		s_invalidNoEsPbufferAttrib1,
+		s_invalidNoEsPbufferAttrib2
+	};
+
+	static const EGLint s_invalidEsPbufferAttrib0[] = { EGL_TEXTURE_FORMAT, EGL_NO_TEXTURE, EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE };
+	static const EGLint s_invalidEsPbufferAttrib1[] = { EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA, EGL_TEXTURE_TARGET, EGL_NO_TEXTURE, EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE };
+	static const EGLint* s_invalidEsPbufferAttribs[] =
+	{
+		s_invalidEsPbufferAttrib0,
+		s_invalidEsPbufferAttrib1
+	};
+
+	static const EGLint s_vgPreMultAlphaPbufferAttrib[] = { EGL_VG_ALPHA_FORMAT, EGL_VG_ALPHA_FORMAT_PRE, EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE };
+	static const EGLint s_vgLinearColorspacePbufferAttrib[] = { EGL_VG_COLORSPACE, EGL_VG_COLORSPACE_LINEAR, EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE };
+
+	TEGL_ADD_API_CASE(create_pbuffer_surface, "eglCreatePbufferSurface() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectNoSurface(eglCreatePbufferSurface(EGL_NO_DISPLAY, DE_NULL, s_emptyAttribList));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectNoSurface(eglCreatePbufferSurface((EGLDisplay)-1, DE_NULL, s_emptyAttribList));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_CONFIG is generated if config is not an EGL frame buffer configuration");
+
+			expectNoSurface(eglCreatePbufferSurface(display, (EGLConfig)-1, s_emptyAttribList));
+			expectError(EGL_BAD_CONFIG);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test3", "EGL_BAD_ATTRIBUTE is generated if attrib_list contains an invalid pixel buffer attribute");
+
+			// Generic pbuffer-capable config
+			EGLConfig genericConfig;
+			if (getConfig(&genericConfig, FilterList() << (ConfigSurfaceType() & EGL_PBUFFER_BIT)))
+			{
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_invalidGenericPbufferAttribs); ndx++)
+				{
+					expectNoSurface(eglCreatePbufferSurface(display, genericConfig, s_invalidGenericPbufferAttribs[ndx]));
+					expectError(EGL_BAD_ATTRIBUTE);
+				}
+			}
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test4", "EGL_BAD_MATCH is generated if config does not support rendering to pixel buffers");
+
+			EGLConfig noPbufferConfig;
+			if (getConfig(&noPbufferConfig, FilterList() << (ConfigSurfaceType() ^ EGL_PBUFFER_BIT)))
+			{
+				expectNoSurface(eglCreatePbufferSurface(display, noPbufferConfig, s_validGenericPbufferAttrib));
+				expectError(EGL_BAD_MATCH);
+			}
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test5", "EGL_BAD_ATTRIBUTE is generated if attrib_list contains any of the attributes EGL_MIPMAP_TEXTURE, EGL_TEXTURE_FORMAT, or EGL_TEXTURE_TARGET, and config does not support OpenGL ES rendering");
+
+			EGLConfig noEsConfig;
+			if (getConfig(&noEsConfig, FilterList() << (ConfigRenderableType() ^ (EGL_OPENGL_ES_BIT|EGL_OPENGL_ES2_BIT))))
+			{
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_invalidNoEsPbufferAttribs); ndx++)
+				{
+					expectNoSurface(eglCreatePbufferSurface(display, noEsConfig, s_invalidNoEsPbufferAttribs[ndx]));
+					expectError(EGL_BAD_MATCH);
+				}
+			}
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test6", "EGL_BAD_MATCH is generated if the EGL_TEXTURE_FORMAT attribute is EGL_NO_TEXTURE, and EGL_TEXTURE_TARGET is something other than EGL_NO_TEXTURE; or, EGL_TEXTURE_FORMAT is something other than EGL_NO_TEXTURE, and EGL_TEXTURE_TARGET is EGL_NO_TEXTURE");
+
+			// ES1 or ES2 config.
+			EGLConfig	esConfig;
+			bool		gotEsConfig	= getConfig(&esConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_ES_BIT)) ||
+									  getConfig(&esConfig, FilterList() << (ConfigRenderableType() & EGL_OPENGL_ES2_BIT));
+			if (gotEsConfig)
+			{
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_invalidEsPbufferAttribs); ndx++)
+				{
+					expectNoSurface(eglCreatePbufferSurface(display, esConfig, s_invalidEsPbufferAttribs[ndx]));
+					expectError(EGL_BAD_MATCH);
+				}
+			}
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test7", "EGL_BAD_MATCH is generated if config does not support the specified OpenVG alpha format attribute or colorspace attribute");
+
+			EGLConfig vgNoPreConfig;
+			if (getConfig(&vgNoPreConfig, FilterList() << (ConfigRenderableType() & EGL_OPENVG_BIT) << (ConfigSurfaceType() ^ EGL_VG_ALPHA_FORMAT_PRE)))
+			{
+				expectNoSurface(eglCreatePbufferSurface(display, vgNoPreConfig, s_vgPreMultAlphaPbufferAttrib));
+				expectError(EGL_BAD_MATCH);
+			}
+
+			EGLConfig vgNoLinearConfig;
+			if (getConfig(&vgNoLinearConfig, FilterList() << (ConfigRenderableType() & EGL_OPENVG_BIT) << (ConfigSurfaceType() ^ EGL_VG_COLORSPACE_LINEAR_BIT)))
+			{
+				expectNoSurface(eglCreatePbufferSurface(display, vgNoLinearConfig, s_vgLinearColorspacePbufferAttrib));
+				expectError(EGL_BAD_MATCH);
+			}
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(create_pixmap_surface, "eglCreatePixmapSurface() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectNoSurface(eglCreatePixmapSurface(EGL_NO_DISPLAY, DE_NULL, DE_NULL, s_emptyAttribList));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectNoSurface(eglCreatePixmapSurface((EGLDisplay)-1, DE_NULL, DE_NULL, s_emptyAttribList));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_CONFIG is generated if config is not an EGL frame buffer configuration");
+
+			expectNoSurface(eglCreatePixmapSurface(display, (EGLConfig)-1, DE_NULL, s_emptyAttribList));
+			expectError(EGL_BAD_CONFIG);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test3", "EGL_BAD_NATIVE_PIXMAP may be generated if native_pixmap is not a valid native pixmap");
+
+			// Any pixmap-capable config.
+			EGLConfig pixmapConfig;
+			if (getConfig(&pixmapConfig, FilterList() << (ConfigSurfaceType() & EGL_PIXMAP_BIT)))
+			{
+				expectNoSurface(eglCreatePixmapSurface(display, pixmapConfig, DE_NULL, s_emptyAttribList));
+				expectError(EGL_BAD_NATIVE_PIXMAP);
+
+				expectNoSurface(eglCreatePixmapSurface(display, pixmapConfig, (EGLNativePixmapType)-1, s_emptyAttribList));
+				expectError(EGL_BAD_NATIVE_PIXMAP);
+			}
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(create_window_surface, "eglCreateWindowSurface() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectNoSurface(eglCreateWindowSurface(EGL_NO_DISPLAY, DE_NULL, DE_NULL, s_emptyAttribList));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectNoSurface(eglCreateWindowSurface((EGLDisplay)-1, DE_NULL, DE_NULL, s_emptyAttribList));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_CONFIG is generated if config is not an EGL frame buffer configuration");
+
+			expectNoSurface(eglCreateWindowSurface(display, (EGLConfig)-1, DE_NULL, s_emptyAttribList));
+			expectError(EGL_BAD_CONFIG);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test3", "EGL_BAD_NATIVE_WINDOW may be generated if native_window is not a valid native window");
+
+			// Any window-capable config.
+			EGLConfig windowConfig;
+			if (getConfig(&windowConfig, FilterList() << (ConfigSurfaceType() & EGL_WINDOW_BIT)))
+			{
+				expectNoSurface(eglCreateWindowSurface(display, windowConfig, DE_NULL, s_emptyAttribList));
+				expectError(EGL_BAD_NATIVE_PIXMAP);
+
+				expectNoSurface(eglCreateWindowSurface(display, windowConfig, (EGLNativeWindowType)-1, s_emptyAttribList));
+				expectError(EGL_BAD_NATIVE_PIXMAP);
+			}
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(destroy_context, "eglDestroyContext() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglDestroyContext(EGL_NO_DISPLAY, DE_NULL));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglDestroyContext((EGLDisplay)-1, DE_NULL));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_CONTEXT is generated if context is not an EGL rendering context");
+
+			expectFalse(eglDestroyContext(display, DE_NULL));
+			expectError(EGL_BAD_CONTEXT);
+
+			expectFalse(eglDestroyContext(display, (EGLContext)-1));
+			expectError(EGL_BAD_CONTEXT);
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(destroy_surface, "eglDestroySurface() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglDestroySurface(EGL_NO_DISPLAY, DE_NULL));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglDestroySurface((EGLDisplay)-1, DE_NULL));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_SURFACE is generated if surface is not an EGL surface");
+
+			expectFalse(eglDestroySurface(display, DE_NULL));
+			expectError(EGL_BAD_SURFACE);
+
+			expectFalse(eglDestroySurface(display, (EGLSurface)-1));
+			expectError(EGL_BAD_SURFACE);
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(get_config_attrib, "eglGetConfigAttrib() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+			EGLint		value		= 0;
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglGetConfigAttrib(EGL_NO_DISPLAY, DE_NULL, EGL_RED_SIZE, &value));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglGetConfigAttrib((EGLDisplay)-1, DE_NULL, EGL_RED_SIZE, &value));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_CONFIG is generated if config is not an EGL frame buffer configuration");
+
+			expectFalse(eglGetConfigAttrib(display, (EGLConfig)-1, EGL_RED_SIZE, &value));
+			expectError(EGL_BAD_CONFIG);
+
+			log << TestLog::EndSection;
+
+			// Any config.
+			EGLConfig	config		= DE_NULL;
+			bool		hasConfig	= getConfig(&config, FilterList());
+
+			log << TestLog::Section("Test3", "EGL_BAD_ATTRIBUTE is generated if attribute is not a valid frame buffer configuration attribute");
+
+			if (hasConfig)
+			{
+				expectFalse(eglGetConfigAttrib(display, config, 0, &value));
+				expectError(EGL_BAD_ATTRIBUTE);
+
+				expectFalse(eglGetConfigAttrib(display, config, -1, &value));
+				expectError(EGL_BAD_ATTRIBUTE);
+			}
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(get_configs, "eglGetConfigs() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+			EGLConfig	cfgs[1];
+			EGLint		numCfgs		= 0;
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglGetConfigs(EGL_NO_DISPLAY, &cfgs[0], DE_LENGTH_OF_ARRAY(cfgs), &numCfgs));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglGetConfigs((EGLDisplay)-1, &cfgs[0], DE_LENGTH_OF_ARRAY(cfgs), &numCfgs));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_PARAMETER is generated if num_config is NULL");
+
+			expectFalse(eglGetConfigs(display, &cfgs[0], DE_LENGTH_OF_ARRAY(cfgs), DE_NULL));
+			expectError(EGL_BAD_PARAMETER);
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(get_display, "eglGetDisplay() negative tests",
+		{
+			expectNoDisplay(eglGetDisplay((EGLNativeDisplayType)-1));
+			expectError(EGL_SUCCESS);
+		});
+
+	TEGL_ADD_API_CASE(initialize, "eglInitialize() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLint		major		= 0;
+			EGLint		minor		= 0;
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglInitialize(EGL_NO_DISPLAY, &major, &minor));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglInitialize((EGLDisplay)-1, &major, &minor));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(make_current, "eglMakeCurrent() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglMakeCurrent(EGL_NO_DISPLAY, DE_NULL, DE_NULL, DE_NULL));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglMakeCurrent((EGLDisplay)-1, DE_NULL, DE_NULL, DE_NULL));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			// Create simple pbuffer surface.
+			EGLSurface surface = EGL_NO_SURFACE;
+			{
+				EGLConfig config;
+				if (getConfig(&config, FilterList() << (ConfigSurfaceType() & EGL_PBUFFER_BIT)))
+				{
+					surface = eglCreatePbufferSurface(display, config, s_validGenericPbufferAttrib);
+					expectError(EGL_SUCCESS);
+				}
+			}
+
+			log << TestLog::Section("Test2", "EGL_BAD_SURFACE is generated if surface is not an EGL surface");
+
+			expectFalse(eglMakeCurrent(display, (EGLSurface)-1, (EGLSurface)-1, DE_NULL));
+			expectError(EGL_BAD_SURFACE);
+
+			if (surface)
+			{
+				expectFalse(eglMakeCurrent(display, surface, (EGLSurface)-1, DE_NULL));
+				expectError(EGL_BAD_SURFACE);
+
+				expectFalse(eglMakeCurrent(display, (EGLSurface)-1, surface, DE_NULL));
+				expectError(EGL_BAD_SURFACE);
+			}
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test3", "EGL_BAD_CONTEXT is generated if context is not an EGL rendering context");
+
+			if (surface)
+			{
+				expectFalse(eglMakeCurrent(display, surface, surface, DE_NULL));
+				expectError(EGL_BAD_CONTEXT);
+
+				expectFalse(eglMakeCurrent(display, surface, surface, (EGLContext)-1));
+				expectError(EGL_BAD_CONTEXT);
+			}
+
+			log << TestLog::EndSection;
+
+			if (surface)
+			{
+				eglDestroySurface(display, surface);
+				expectError(EGL_SUCCESS);
+			}
+		});
+
+	TEGL_ADD_API_CASE(get_current_context, "eglGetCurrentContext() negative tests",
+		{
+			expectNoContext(eglGetCurrentContext());
+
+			if (isAPISupported(EGL_OPENGL_ES_API))
+			{
+				expectTrue(eglBindAPI(EGL_OPENGL_ES_API));
+				expectError(EGL_SUCCESS);
+
+				expectNoContext(eglGetCurrentContext());
+				expectError(EGL_SUCCESS);
+			}
+		});
+
+	TEGL_ADD_API_CASE(get_current_surface, "eglGetCurrentSurface() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+
+			log << TestLog::Section("Test1", "EGL_BAD_PARAMETER is generated if readdraw is neither EGL_READ nor EGL_DRAW");
+
+			expectNoSurface(eglGetCurrentSurface(EGL_NONE));
+			expectError(EGL_BAD_PARAMETER);
+
+			log << TestLog::EndSection;
+
+			expectNoSurface(eglGetCurrentSurface(EGL_READ));
+			expectError(EGL_SUCCESS);
+
+			expectNoSurface(eglGetCurrentSurface(EGL_DRAW));
+			expectError(EGL_SUCCESS);
+		});
+
+	TEGL_ADD_API_CASE(query_context, "eglQueryContext() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+			EGLint		value		= 0;
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglQueryContext(EGL_NO_DISPLAY, DE_NULL, EGL_CONFIG_ID, &value));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglQueryContext((EGLDisplay)-1, DE_NULL, EGL_CONFIG_ID, &value));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_CONTEXT is generated if context is not an EGL rendering context");
+
+			expectFalse(eglQueryContext(display, DE_NULL, EGL_CONFIG_ID, &value));
+			expectError(EGL_BAD_CONTEXT);
+
+			expectFalse(eglQueryContext(display, DE_NULL, EGL_CONFIG_ID, &value));
+			expectError(EGL_BAD_CONTEXT);
+
+			log << TestLog::EndSection;
+
+			// Create ES2 context.
+			EGLConfig	config		= DE_NULL;
+			EGLContext	context		= DE_NULL;
+			bool		gotConfig	= getConfig(&config, FilterList() << (ConfigRenderableType() & EGL_OPENGL_ES2_BIT));
+
+			if (gotConfig)
+			{
+				expectTrue(eglBindAPI(EGL_OPENGL_ES_API));
+				expectError(EGL_SUCCESS);
+
+				context = eglCreateContext(display, config, EGL_NO_CONTEXT, s_es2ContextAttribList);
+				expectError(EGL_SUCCESS);
+			}
+
+			log << TestLog::Section("Test3", "EGL_BAD_ATTRIBUTE is generated if attribute is not a valid context attribute");
+
+			if (context)
+			{
+				expectFalse(eglQueryContext(display, context, 0, &value));
+				expectError(EGL_BAD_ATTRIBUTE);
+				expectFalse(eglQueryContext(display, context, -1, &value));
+				expectError(EGL_BAD_ATTRIBUTE);
+				expectFalse(eglQueryContext(display, context, EGL_RED_SIZE, &value));
+				expectError(EGL_BAD_ATTRIBUTE);
+			}
+
+			log << TestLog::EndSection;
+
+			if (context)
+			{
+				expectTrue(eglDestroyContext(display, context));
+				expectError(EGL_SUCCESS);
+			}
+		});
+
+	TEGL_ADD_API_CASE(query_string, "eglQueryString() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectNull(eglQueryString(EGL_NO_DISPLAY, EGL_VENDOR));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectNull(eglQueryString((EGLDisplay)-1, EGL_VENDOR));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_PARAMETER is generated if name is not an accepted value");
+
+			expectNull(eglQueryString(display, 0));
+			expectError(EGL_BAD_PARAMETER);
+			expectNull(eglQueryString(display, -1));
+			expectError(EGL_BAD_PARAMETER);
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(query_surface, "eglQuerySurface() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+			EGLint		value		= 0;
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglQuerySurface(EGL_NO_DISPLAY, DE_NULL, EGL_CONFIG_ID, &value));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglQuerySurface((EGLDisplay)-1, DE_NULL, EGL_CONFIG_ID, &value));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_SURFACE is generated if surface is not an EGL surface");
+
+			expectFalse(eglQuerySurface(display, DE_NULL, EGL_CONFIG_ID, &value));
+			expectError(EGL_BAD_SURFACE);
+
+			expectFalse(eglQuerySurface(display, (EGLSurface)-1, EGL_CONFIG_ID, &value));
+			expectError(EGL_BAD_SURFACE);
+
+			log << TestLog::EndSection;
+
+			// Create pbuffer surface.
+			EGLSurface surface = EGL_NO_SURFACE;
+			{
+				EGLConfig config;
+				if (getConfig(&config, FilterList() << (ConfigSurfaceType() & EGL_PBUFFER_BIT)))
+				{
+					surface = eglCreatePbufferSurface(display, config, s_validGenericPbufferAttrib);
+					expectError(EGL_SUCCESS);
+				}
+				else
+					log << TestLog::Message << "// WARNING: No suitable config found, testing will be incomplete" << TestLog::EndMessage;
+			}
+
+			log << TestLog::Section("Test3", "EGL_BAD_ATTRIBUTE is generated if attribute is not a valid surface attribute");
+
+			if (surface)
+			{
+				expectFalse(eglQuerySurface(display, surface, 0, &value));
+				expectError(EGL_BAD_ATTRIBUTE);
+
+				expectFalse(eglQuerySurface(display, surface, -1, &value));
+				expectError(EGL_BAD_ATTRIBUTE);
+			}
+
+			log << TestLog::EndSection;
+
+			if (surface)
+			{
+				eglDestroySurface(display, surface);
+				expectError(EGL_SUCCESS);
+			}
+		});
+
+	TEGL_ADD_API_CASE(release_tex_image, "eglReleaseTexImage() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglReleaseTexImage(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_BACK_BUFFER));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglReleaseTexImage((EGLDisplay)-1, EGL_NO_SURFACE, EGL_BACK_BUFFER));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_SURFACE is generated if surface is not an EGL surface");
+
+			expectFalse(eglReleaseTexImage(display, EGL_NO_SURFACE, EGL_BACK_BUFFER));
+			expectError(EGL_BAD_SURFACE);
+
+			expectFalse(eglReleaseTexImage(display, (EGLSurface)-1, EGL_BACK_BUFFER));
+			expectError(EGL_BAD_SURFACE);
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(surface_attrib, "eglSurfaceAttrib() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglSurfaceAttrib(EGL_NO_DISPLAY, DE_NULL, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglSurfaceAttrib((EGLDisplay)-1, DE_NULL, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_SURFACE is generated if surface is not an EGL surface");
+
+			expectFalse(eglSurfaceAttrib(display, DE_NULL, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED));
+			expectError(EGL_BAD_SURFACE);
+
+			expectFalse(eglSurfaceAttrib(display, (EGLSurface)-1, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED));
+			expectError(EGL_BAD_SURFACE);
+
+			log << TestLog::EndSection;
+
+			{
+				// Create pbuffer surface.
+				EGLSurface surface = EGL_NO_SURFACE;
+				{
+					EGLConfig config;
+					if (getConfig(&config, FilterList() << (ConfigSurfaceType() & EGL_PBUFFER_BIT)))
+					{
+						surface = eglCreatePbufferSurface(display, config, s_validGenericPbufferAttrib);
+						expectError(EGL_SUCCESS);
+					}
+					else
+						log << TestLog::Message << "// WARNING: No suitable config found, testing will be incomplete" << TestLog::EndMessage;
+				}
+
+				log << TestLog::Section("Test3", "EGL_BAD_ATTRIBUTE is generated if attribute is not a valid surface attribute");
+
+				if (surface)
+				{
+					expectFalse(eglSurfaceAttrib(display, surface, 0, 0));
+					expectError(EGL_BAD_ATTRIBUTE);
+
+					expectFalse(eglSurfaceAttrib(display, surface, -1, 0));
+					expectError(EGL_BAD_ATTRIBUTE);
+				}
+
+				log << TestLog::EndSection;
+
+				if (surface)
+				{
+					eglDestroySurface(display, surface);
+					expectError(EGL_SUCCESS);
+				}
+			}
+
+			{
+				// Create pbuffer surface without EGL_MULTISAMPLE_RESOLVE_BOX_BIT.
+				EGLSurface surface = EGL_NO_SURFACE;
+				{
+					EGLConfig config;
+					if (getConfig(&config, FilterList() << (ConfigSurfaceType() & EGL_PBUFFER_BIT) << (ConfigSurfaceType() ^ EGL_MULTISAMPLE_RESOLVE_BOX_BIT)))
+					{
+						surface = eglCreatePbufferSurface(display, config, s_validGenericPbufferAttrib);
+						expectError(EGL_SUCCESS);
+					}
+					else
+						log << TestLog::Message << "// WARNING: No suitable config found, testing will be incomplete" << TestLog::EndMessage;
+				}
+
+				log << TestLog::Section("Test4", "EGL_BAD_MATCH is generated if attribute is EGL_MULTISAMPLE_RESOLVE, value is EGL_MULTISAMPLE_RESOLVE_BOX, and the EGL_SURFACE_TYPE attribute of the EGLConfig used to create surface does not contain EGL_MULTISAMPLE_RESOLVE_BOX_BIT");
+
+				if (surface)
+				{
+					expectFalse(eglSurfaceAttrib(display, surface, EGL_MULTISAMPLE_RESOLVE, EGL_MULTISAMPLE_RESOLVE_BOX));
+					expectError(EGL_BAD_MATCH);
+				}
+
+				log << TestLog::EndSection;
+
+				if (surface)
+				{
+					eglDestroySurface(display, surface);
+					expectError(EGL_SUCCESS);
+				}
+			}
+
+			{
+				// Create pbuffer surface without EGL_SWAP_BEHAVIOR_PRESERVED_BIT.
+				EGLSurface surface = EGL_NO_SURFACE;
+				{
+					EGLConfig config;
+					if (getConfig(&config, FilterList() << (ConfigSurfaceType() & EGL_PBUFFER_BIT) << (ConfigSurfaceType() ^ EGL_SWAP_BEHAVIOR_PRESERVED_BIT)))
+					{
+						surface = eglCreatePbufferSurface(display, config, s_validGenericPbufferAttrib);
+						expectError(EGL_SUCCESS);
+					}
+					else
+						log << TestLog::Message << "// WARNING: No suitable config found, testing will be incomplete" << TestLog::EndMessage;
+				}
+
+				log << TestLog::Section("Test5", "EGL_BAD_MATCH is generated if attribute is EGL_SWAP_BEHAVIOR, value is EGL_BUFFER_PRESERVED, and the EGL_SURFACE_TYPE attribute of the EGLConfig used to create surface does not contain EGL_SWAP_BEHAVIOR_PRESERVED_BIT");
+
+				if (surface)
+				{
+					expectFalse(eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED));
+					expectError(EGL_BAD_MATCH);
+				}
+
+				log << TestLog::EndSection;
+
+				if (surface)
+				{
+					eglDestroySurface(display, surface);
+					expectError(EGL_SUCCESS);
+				}
+			}
+		});
+
+	TEGL_ADD_API_CASE(swap_buffers, "eglSwapBuffers() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglSwapBuffers(EGL_NO_DISPLAY, DE_NULL));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglSwapBuffers((EGLDisplay)-1, DE_NULL));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_SURFACE is generated if surface is not an EGL surface");
+
+			expectFalse(eglSwapBuffers(display, DE_NULL));
+			expectError(EGL_BAD_SURFACE);
+
+			expectFalse(eglSwapBuffers(display, (EGLSurface)-1));
+			expectError(EGL_BAD_SURFACE);
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(swap_interval, "eglSwapInterval() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+			EGLDisplay	display		= getDisplay();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglSwapInterval(EGL_NO_DISPLAY, 0));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglSwapInterval((EGLDisplay)-1, 0));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+
+			log << TestLog::Section("Test2", "EGL_BAD_CONTEXT is generated if there is no current context on the calling thread");
+
+			expectFalse(eglSwapInterval(display, 0));
+			expectError(EGL_BAD_CONTEXT);
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(terminate, "eglTerminate() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+
+			log << TestLog::Section("Test1", "EGL_BAD_DISPLAY is generated if display is not an EGL display connection");
+
+			expectFalse(eglTerminate(EGL_NO_DISPLAY));
+			expectError(EGL_BAD_DISPLAY);
+
+			expectFalse(eglTerminate((EGLDisplay)-1));
+			expectError(EGL_BAD_DISPLAY);
+
+			log << TestLog::EndSection;
+		});
+
+	TEGL_ADD_API_CASE(wait_native, "eglWaitNative() negative tests",
+		{
+			TestLog&	log			= m_testCtx.getLog();
+
+			log << TestLog::Section("Test1", "EGL_BAD_PARAMETER is generated if engine is not a recognized marking engine");
+
+			expectFalse(eglWaitNative(-1));
+			expectError(EGL_BAD_PARAMETER);
+
+			log << TestLog::EndSection;
+		});
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglNegativeApiTests.hpp b/modules/egl/teglNegativeApiTests.hpp
new file mode 100644
index 0000000..4d7f02e
--- /dev/null
+++ b/modules/egl/teglNegativeApiTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLNEGATIVEAPITESTS_HPP
+#define _TEGLNEGATIVEAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative API Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class NegativeApiTests : public TestCaseGroup
+{
+public:
+				NegativeApiTests		(EglTestContext& eglTestCtx);
+				~NegativeApiTests		(void);
+
+	void		init					(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLNEGATIVEAPITESTS_HPP
diff --git a/modules/egl/teglPreservingSwapTests.cpp b/modules/egl/teglPreservingSwapTests.cpp
new file mode 100644
index 0000000..1e335b7
--- /dev/null
+++ b/modules/egl/teglPreservingSwapTests.cpp
@@ -0,0 +1,657 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test EGL_SWAP_BEHAVIOR_PRESERVED_BIT.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglPreservingSwapTests.hpp"
+
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuEgl.hpp"
+
+#include "egluNativeWindow.hpp"
+#include "egluUtil.hpp"
+
+#include "gluDefs.hpp"
+#include "gluRenderContext.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include "deRandom.hpp"
+
+#include "deString.h"
+
+#include <vector>
+#include <string>
+
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace
+{
+class GLES2Program;
+class ReferenceProgram;
+
+class PreservingSwapTest : public TestCase
+{
+public:
+	enum DrawType
+	{
+		DRAWTYPE_NONE = 0,
+		DRAWTYPE_GLES2_CLEAR,
+		DRAWTYPE_GLES2_RENDER
+	};
+
+					PreservingSwapTest	(EglTestContext& eglTestCtx, bool preserveColorbuffer, bool readPixelsBeforeSwap, DrawType preSwapDrawType, DrawType postSwapDrawType, const char* name, const char* description);
+					~PreservingSwapTest	(void);
+
+	void			init				(void);
+	void			deinit				(void);
+	IterateResult	iterate				(void);
+
+private:
+	const int					m_seed;
+	const bool					m_preserveColorbuffer;
+	const bool					m_readPixelsBeforeSwap;
+	const DrawType				m_preSwapDrawType;
+	const DrawType				m_postSwapDrawType;
+
+	eglu::NativeWindow*			m_window;
+	tcu::egl::WindowSurface*	m_eglSurface;
+	EGLConfig					m_eglConfig;
+	tcu::egl::Context*			m_eglContext;
+	glw::Functions				m_gl;
+
+	GLES2Program*				m_gles2Program;
+	ReferenceProgram*			m_refProgram;
+
+	void initEGLSurface	(EGLConfig config);
+	void initEGLContext (EGLConfig config);
+};
+
+class GLES2Program
+{
+public:
+					GLES2Program	(const glw::Functions& gl);
+					~GLES2Program	(void);
+
+	void			render			(int width, int height, float x1, float y1, float x2, float y2, PreservingSwapTest::DrawType drawType);
+
+private:
+	const glw::Functions&	m_gl;
+	glw::GLuint				m_glProgram;
+	glw::GLuint				m_coordLoc;
+	glw::GLuint				m_colorLoc;
+
+	GLES2Program&			operator=		(const GLES2Program&);
+							GLES2Program	(const GLES2Program&);
+};
+
+GLES2Program::GLES2Program (const glw::Functions& gl)
+	: m_gl			(gl)
+	, m_glProgram	(0)
+	, m_coordLoc	((glw::GLuint)-1)
+	, m_colorLoc	((glw::GLuint)-1)
+{
+	const char* const vertexShaderSource =
+	"attribute mediump vec4 a_pos;\n"
+	"attribute mediump vec4 a_color;\n"
+	"varying mediump vec4 v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tv_color = a_color;\n"
+	"\tgl_Position = a_pos;\n"
+	"}";
+
+	const char* const fragmentShaderSource =
+	"varying mediump vec4 v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tgl_FragColor = v_color;\n"
+	"}";
+
+	glw::GLuint	vtxShader	= (glw::GLuint)-1;
+	glw::GLuint	fragShader	= (glw::GLuint)-1;
+
+	try
+	{
+		vtxShader = m_gl.createShader(GL_VERTEX_SHADER);
+		fragShader = m_gl.createShader(GL_FRAGMENT_SHADER);
+
+		m_glProgram	= m_gl.createProgram();
+
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to create resources for shader program");
+
+		m_gl.shaderSource(vtxShader, 1, &vertexShaderSource, DE_NULL);
+		m_gl.shaderSource(fragShader, 1, &fragmentShaderSource, DE_NULL);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to set shader sources");
+
+		m_gl.compileShader(vtxShader);
+		m_gl.compileShader(fragShader);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "Shader compilation failed");
+
+		m_gl.attachShader(m_glProgram, vtxShader);
+		m_gl.attachShader(m_glProgram, fragShader);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to attach shaders to program");
+
+		m_gl.linkProgram(m_glProgram);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to link program");
+
+		m_gl.deleteShader(fragShader);
+		fragShader = (glw::GLuint)-1;
+		m_gl.deleteShader(vtxShader);
+		vtxShader = (glw::GLuint)-1;
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to delete shaders");
+
+		m_colorLoc = m_gl.getAttribLocation(m_glProgram, "a_color");
+		m_coordLoc = m_gl.getAttribLocation(m_glProgram, "a_pos");
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to get attribute locations");
+
+		TCU_CHECK(m_colorLoc != (glw::GLuint)-1);
+		TCU_CHECK(m_coordLoc != (glw::GLuint)-1);
+
+	}
+	catch (...)
+	{
+		if (vtxShader != (glw::GLuint)-1)
+			m_gl.deleteShader(vtxShader);
+
+		if (fragShader != (glw::GLuint)-1)
+			m_gl.deleteShader(fragShader);
+
+		if (m_glProgram != (glw::GLuint)-1)
+			m_gl.deleteProgram(m_glProgram);
+
+		m_glProgram = (glw::GLuint)-1;
+
+		throw;
+	}
+}
+
+GLES2Program::~GLES2Program (void)
+{
+	if (m_glProgram != (glw::GLuint)-1)
+		m_gl.deleteProgram(m_glProgram);
+}
+
+void GLES2Program::render (int width, int height, float x1, float y1, float x2, float y2, PreservingSwapTest::DrawType drawType)
+{
+	if (drawType == PreservingSwapTest::DRAWTYPE_GLES2_RENDER)
+	{
+		const glw::GLfloat coords[] =
+		{
+			x1, y1, 0.0f, 1.0f,
+			x1, y2, 0.0f, 1.0f,
+			x2, y2, 0.0f, 1.0f,
+
+			x2, y2, 0.0f, 1.0f,
+			x2, y1, 0.0f, 1.0f,
+			x1, y1, 0.0f, 1.0f
+		};
+
+		const glw::GLubyte colors[] =
+		{
+			127,	127,	127,	255,
+			127,	127,	127,	255,
+			127,	127,	127,	255,
+
+			127,	127,	127,	255,
+			127,	127,	127,	255,
+			127,	127,	127,	255
+		};
+
+		m_gl.useProgram(m_glProgram);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUseProgram() failed");
+
+		m_gl.enableVertexAttribArray(m_coordLoc);
+		m_gl.enableVertexAttribArray(m_colorLoc);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to enable attributes");
+
+		m_gl.vertexAttribPointer(m_coordLoc, 4, GL_FLOAT, GL_FALSE, 0, coords);
+		m_gl.vertexAttribPointer(m_colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, colors);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to set attribute pointers");
+
+		m_gl.drawArrays(GL_TRIANGLES, 0, 6);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "glDrawArrays() failed");
+
+		m_gl.disableVertexAttribArray(m_coordLoc);
+		m_gl.disableVertexAttribArray(m_colorLoc);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to disable attributes");
+
+		m_gl.useProgram(0);
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUseProgram() failed");
+	}
+	else if (drawType == PreservingSwapTest::DRAWTYPE_GLES2_CLEAR)
+	{
+		const int ox	= width/2;
+		const int oy	= height/2;
+
+		const int px	= width;
+		const int py	= height;
+
+		const int x1i	= (int)((px/2.0f) * x1 + ox);
+		const int y1i	= (int)((py/2.0f) * y1 + oy);
+
+		const int x2i	= (int)((px/2.0f) * x2 + ox);
+		const int y2i	= (int)((py/2.0f) * y2 + oy);
+
+		m_gl.enable(GL_SCISSOR_TEST);
+		m_gl.scissor(x1i, y1i, x2i-x1i, y2i-y1i);
+		m_gl.clearColor(0.5f, 0.5f, 0.5f, 1.0f);
+		m_gl.clear(GL_COLOR_BUFFER_BIT);
+		m_gl.disable(GL_SCISSOR_TEST);
+	}
+	else
+		DE_ASSERT(false);
+}
+
+class ReferenceProgram
+{
+public:
+			ReferenceProgram	(void);
+			~ReferenceProgram	(void);
+
+	void	render				(tcu::Surface* target, float x1, float y1, float x2, float y2, PreservingSwapTest::DrawType drawType);
+
+private:
+						ReferenceProgram	(const ReferenceProgram&);
+	ReferenceProgram&	operator=			(const ReferenceProgram&);
+};
+
+ReferenceProgram::ReferenceProgram (void)
+{
+}
+
+ReferenceProgram::~ReferenceProgram (void)
+{
+}
+
+void ReferenceProgram::render (tcu::Surface* target, float x1, float y1, float x2, float y2, PreservingSwapTest::DrawType drawType)
+{
+	if (drawType == PreservingSwapTest::DRAWTYPE_GLES2_RENDER || drawType == PreservingSwapTest::DRAWTYPE_GLES2_CLEAR)
+	{
+		const int ox	= target->getWidth()/2;
+		const int oy	= target->getHeight()/2;
+
+		const int px	= target->getWidth();
+		const int py	= target->getHeight();
+
+		const int x1i	= (int)((px/2.0) * x1 + ox);
+		const int y1i	= (int)((py/2.0) * y1 + oy);
+
+		const int x2i	= (int)((px/2.0) * x2 + ox);
+		const int y2i	= (int)((py/2.0) * y2 + oy);
+
+		const tcu::RGBA	color(127, 127, 127, 255);
+
+		for (int y = y1i; y <= y2i; y++)
+		{
+			for (int x = x1i; x <= x2i; x++)
+				target->setPixel(x, y, color);
+		}
+	}
+	else
+		DE_ASSERT(false);
+}
+
+PreservingSwapTest::PreservingSwapTest (EglTestContext& eglTestCtx, bool preserveColorbuffer, bool readPixelsBeforeSwap, DrawType preSwapDrawType, DrawType postSwapDrawType, const char* name, const char* description)
+	: TestCase					(eglTestCtx, name, description)
+	, m_seed					(deStringHash(name))
+	, m_preserveColorbuffer		(preserveColorbuffer)
+	, m_readPixelsBeforeSwap	(readPixelsBeforeSwap)
+	, m_preSwapDrawType			(preSwapDrawType)
+	, m_postSwapDrawType		(postSwapDrawType)
+	, m_window					(DE_NULL)
+	, m_eglSurface				(DE_NULL)
+	, m_eglContext				(DE_NULL)
+	, m_gles2Program			(DE_NULL)
+	, m_refProgram				(DE_NULL)
+{
+}
+
+PreservingSwapTest::~PreservingSwapTest (void)
+{
+	deinit();
+}
+
+EGLConfig getEGLConfig (tcu::egl::Display& eglDisplay, bool preserveColorbuffer)
+{
+	vector<EGLConfig>	configs;
+	const EGLint		attribList[] =
+	{
+		EGL_SURFACE_TYPE,		EGL_WINDOW_BIT | (preserveColorbuffer ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
+		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
+		EGL_NONE
+	};
+
+	eglDisplay.chooseConfig(attribList, configs);
+
+	if (configs.size() == 0)
+		return DE_NULL;
+	else
+		return configs[0];
+}
+
+void clearColorScreen(const glw::Functions& gl, float red, float green, float blue, float alpha)
+{
+	gl.clearColor(red, green, blue, alpha);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+}
+
+void clearColorReference(tcu::Surface* ref, float red, float green, float blue, float alpha)
+{
+	tcu::clear(ref->getAccess(), tcu::Vec4(red, green, blue, alpha));
+}
+
+void readPixels (const glw::Functions& gl, tcu::Surface* screen)
+{
+	gl.readPixels(0, 0, screen->getWidth(), screen->getHeight(),  GL_RGBA, GL_UNSIGNED_BYTE, screen->getAccess().getDataPtr());
+}
+
+void PreservingSwapTest::initEGLSurface (EGLConfig config)
+{
+	m_window		= m_eglTestCtx.createNativeWindow(m_eglTestCtx.getDisplay().getEGLDisplay(), config, DE_NULL, 480, 480, eglu::parseWindowVisibility(m_testCtx.getCommandLine()));
+	m_eglSurface	= new tcu::egl::WindowSurface(m_eglTestCtx.getDisplay(), eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *m_window, m_eglTestCtx.getDisplay().getEGLDisplay(), config, DE_NULL));
+}
+
+void PreservingSwapTest::initEGLContext (EGLConfig config)
+{
+	const EGLint attribList[] =
+	{
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+	m_eglContext = new tcu::egl::Context(m_eglTestCtx.getDisplay(), config, attribList, EGL_OPENGL_ES_API);
+}
+
+void PreservingSwapTest::init (void)
+{
+	m_eglConfig = getEGLConfig(m_eglTestCtx.getDisplay(), m_preserveColorbuffer);
+
+	if (m_eglConfig == DE_NULL)
+		throw tcu::NotSupportedError("No supported config found", "", __FILE__, __LINE__);
+
+	initEGLSurface(m_eglConfig);
+	initEGLContext(m_eglConfig);
+
+	m_eglContext->makeCurrent(*m_eglSurface, *m_eglSurface);
+
+	m_eglTestCtx.getGLFunctions(m_gl, glu::ApiType::es(2,0));
+
+	m_gles2Program	= new GLES2Program(m_gl);
+	m_refProgram	= new ReferenceProgram();
+}
+
+void PreservingSwapTest::deinit (void)
+{
+	delete m_refProgram;
+	m_refProgram = DE_NULL;
+
+	delete m_gles2Program;
+	m_gles2Program = DE_NULL;
+
+	delete m_eglContext;
+	m_eglContext = DE_NULL;
+
+	delete m_eglSurface;
+	m_eglSurface = DE_NULL;
+
+	delete m_window;
+	m_window = DE_NULL;
+}
+
+bool compareToReference (tcu::TestLog& log, const char* name, const char* description, const tcu::Surface& reference, const tcu::Surface& screen, int x, int y, int width, int height)
+{
+	return tcu::fuzzyCompare(log, name, description, reference.getSubAccess(x, y, width, height), screen.getSubAccess(x, y, width, height), 0.05f, tcu::COMPARE_LOG_RESULT);
+}
+
+bool comparePreAndPostSwapFramebuffers (tcu::TestLog& log, const tcu::Surface& preSwap, const tcu::Surface& postSwap)
+{
+	return tcu::pixelThresholdCompare(log, "Pre- / Post framebuffer compare", "Compare pre- and post-swap framebuffers", preSwap, postSwap, tcu::RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);
+}
+
+TestCase::IterateResult PreservingSwapTest::iterate (void)
+{
+	tcu::TestLog&	log				= m_testCtx.getLog();
+	de::Random		rnd(m_seed);
+
+	const int		width			= m_eglSurface->getWidth();
+	const int		height			= m_eglSurface->getHeight();
+
+	const float		clearRed		= rnd.getFloat();
+	const float		clearGreen		= rnd.getFloat();
+	const float		clearBlue		= rnd.getFloat();
+	const float		clearAlpha		= 1.0f;
+
+	const float		preSwapX1		= -0.9f * rnd.getFloat();
+	const float		preSwapY1		= -0.9f * rnd.getFloat();
+	const float		preSwapX2		= 0.9f * rnd.getFloat();
+	const float		preSwapY2		= 0.9f * rnd.getFloat();
+
+	const float		postSwapX1		= -0.9f * rnd.getFloat();
+	const float		postSwapY1		= -0.9f * rnd.getFloat();
+	const float		postSwapX2		= 0.9f * rnd.getFloat();
+	const float		postSwapY2		= 0.9f * rnd.getFloat();
+
+	tcu::Surface	postSwapFramebufferReference(width, height);
+	tcu::Surface	preSwapFramebufferReference(width, height);
+
+	tcu::Surface	postSwapFramebuffer(width, height);
+	tcu::Surface	preSwapFramebuffer(width, height);
+
+	if (m_preserveColorbuffer)
+		m_eglSurface->setAttribute(EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
+
+	m_eglContext->makeCurrent(*m_eglSurface, *m_eglSurface);
+
+	clearColorScreen(m_gl, clearRed, clearGreen, clearBlue, clearAlpha);
+
+	if (m_readPixelsBeforeSwap)
+		clearColorReference(&preSwapFramebufferReference, clearRed, clearGreen, clearBlue, clearAlpha);
+
+	clearColorReference(&postSwapFramebufferReference, clearRed, clearGreen, clearBlue, clearAlpha);
+
+	if (m_preSwapDrawType != DRAWTYPE_NONE)
+	{
+		m_gles2Program->render(width, height, preSwapX1, preSwapY1, preSwapX2, preSwapY2, m_preSwapDrawType);
+		m_refProgram->render(&postSwapFramebufferReference, preSwapX1, preSwapY1, preSwapX2, preSwapY2, m_preSwapDrawType);
+	}
+
+	if (m_readPixelsBeforeSwap)
+	{
+		if (m_preSwapDrawType != DRAWTYPE_NONE)
+			m_refProgram->render(&preSwapFramebufferReference, preSwapX1, preSwapY1, preSwapX2, preSwapY2, m_preSwapDrawType);
+
+		readPixels(m_gl, &preSwapFramebuffer);
+	}
+
+	m_eglSurface->swapBuffers();
+
+	if (m_postSwapDrawType != DRAWTYPE_NONE)
+	{
+		m_refProgram->render(&postSwapFramebufferReference, postSwapX1, postSwapY1, postSwapX2, postSwapY2, m_postSwapDrawType);
+		m_gles2Program->render(width, height, postSwapX1, postSwapY1, postSwapX2, postSwapY2, m_postSwapDrawType);
+	}
+
+	readPixels(m_gl, &postSwapFramebuffer);
+
+	bool isOk = true;
+
+	if (m_preserveColorbuffer)
+	{
+		if (m_readPixelsBeforeSwap)
+			isOk = isOk && compareToReference(log, "Compare pre-swap framebuffer to reference", "Compare pre-swap framebuffer to reference", preSwapFramebufferReference, preSwapFramebuffer, 0, 0, width, height);
+
+		isOk = isOk && compareToReference(log, "Compare post-swap framebuffer to reference", "Compare post-swap framebuffer to reference", postSwapFramebufferReference, postSwapFramebuffer, 0, 0, width, height);
+
+		if (m_readPixelsBeforeSwap && m_postSwapDrawType == PreservingSwapTest::DRAWTYPE_NONE)
+			isOk = isOk && comparePreAndPostSwapFramebuffers(log, preSwapFramebuffer, postSwapFramebuffer);
+	}
+	else
+	{
+		const int ox	= width/2;
+		const int oy	= height/2;
+
+		const int px	= width;
+		const int py	= height;
+
+		const int x1i	= (int)((px/2.0f) * postSwapX1 + ox);
+		const int y1i	= (int)((py/2.0f) * postSwapY1 + oy);
+
+		const int x2i	= (int)((px/2.0f) * postSwapX2 + ox);
+		const int y2i	= (int)((py/2.0f) * postSwapY2 + oy);
+
+		if (m_readPixelsBeforeSwap)
+			isOk = isOk && compareToReference(log, "Compare pre-swap framebuffer to reference", "Compare pre-swap framebuffer to reference", preSwapFramebufferReference, preSwapFramebuffer, 0, 0, width, height);
+
+		DE_ASSERT(m_postSwapDrawType != DRAWTYPE_NONE);
+		isOk = isOk && compareToReference(log, "Compare valid are of post-swap framebuffer to reference", "Compare valid area of post-swap framebuffer to reference", postSwapFramebufferReference, postSwapFramebuffer, x1i, y1i, x2i - x1i, y2i - y1i);
+	}
+
+	if (isOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+	return STOP;
+}
+
+string generateTestName (PreservingSwapTest::DrawType preSwapDrawType, PreservingSwapTest::DrawType postSwapDrawType)
+{
+	std::ostringstream stream;
+
+	if (preSwapDrawType == PreservingSwapTest::DRAWTYPE_NONE && postSwapDrawType == PreservingSwapTest::DRAWTYPE_NONE)
+		stream << "no_draw";
+	else
+	{
+		switch (preSwapDrawType)
+		{
+			case PreservingSwapTest::DRAWTYPE_NONE:
+				// Do nothing
+				break;
+
+			case PreservingSwapTest::DRAWTYPE_GLES2_RENDER:
+				stream << "pre_render";
+				break;
+
+			case PreservingSwapTest::DRAWTYPE_GLES2_CLEAR:
+				stream << "pre_clear";
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		if (preSwapDrawType != PreservingSwapTest::DRAWTYPE_NONE && postSwapDrawType != PreservingSwapTest::DRAWTYPE_NONE)
+			stream << "_";
+
+		switch (postSwapDrawType)
+		{
+			case PreservingSwapTest::DRAWTYPE_NONE:
+				// Do nothing
+				break;
+
+			case PreservingSwapTest::DRAWTYPE_GLES2_RENDER:
+				stream << "post_render";
+				break;
+
+			case PreservingSwapTest::DRAWTYPE_GLES2_CLEAR:
+				stream << "post_clear";
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	return stream.str();
+}
+
+} // anonymous
+
+PreservingSwapTests::PreservingSwapTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "preserve_swap", "Color buffer preserving swap tests")
+{
+}
+
+void PreservingSwapTests::init (void)
+{
+	const PreservingSwapTest::DrawType preSwapDrawTypes[] =
+	{
+		PreservingSwapTest::DRAWTYPE_NONE,
+		PreservingSwapTest::DRAWTYPE_GLES2_CLEAR,
+		PreservingSwapTest::DRAWTYPE_GLES2_RENDER
+	};
+
+	const PreservingSwapTest::DrawType postSwapDrawTypes[] =
+	{
+		PreservingSwapTest::DRAWTYPE_NONE,
+		PreservingSwapTest::DRAWTYPE_GLES2_CLEAR,
+		PreservingSwapTest::DRAWTYPE_GLES2_RENDER
+	};
+
+	for (int preserveNdx = 0; preserveNdx < 2; preserveNdx++)
+	{
+		const bool				preserve		= (preserveNdx == 0);
+		TestCaseGroup* const	preserveGroup	= new TestCaseGroup(m_eglTestCtx, (preserve ? "preserve" : "no_preserve"), "");
+
+		for (int readPixelsNdx = 0; readPixelsNdx < 2; readPixelsNdx++)
+		{
+			const bool				readPixelsBeforeSwap		= (readPixelsNdx == 1);
+			TestCaseGroup* const	readPixelsBeforeSwapGroup	= new TestCaseGroup(m_eglTestCtx, (readPixelsBeforeSwap ? "read_before_swap" : "no_read_before_swap"), "");
+
+			for (int preSwapDrawTypeNdx = 0; preSwapDrawTypeNdx < DE_LENGTH_OF_ARRAY(preSwapDrawTypes); preSwapDrawTypeNdx++)
+			{
+				const PreservingSwapTest::DrawType preSwapDrawType = preSwapDrawTypes[preSwapDrawTypeNdx];
+
+				for (int postSwapDrawTypeNdx = 0; postSwapDrawTypeNdx < DE_LENGTH_OF_ARRAY(postSwapDrawTypes); postSwapDrawTypeNdx++)
+				{
+					const PreservingSwapTest::DrawType postSwapDrawType = postSwapDrawTypes[postSwapDrawTypeNdx];
+
+					// If not preserving and rendering after swap, then there is nothing to verify
+					if (!preserve && postSwapDrawType == PreservingSwapTest::DRAWTYPE_NONE)
+						continue;
+
+					const std::string name = generateTestName(preSwapDrawType, postSwapDrawType);
+
+					readPixelsBeforeSwapGroup->addChild(new PreservingSwapTest(m_eglTestCtx, preserve, readPixelsBeforeSwap, preSwapDrawType, postSwapDrawType, name.c_str(), ""));
+				}
+			}
+
+			preserveGroup->addChild(readPixelsBeforeSwapGroup);
+		}
+
+		addChild(preserveGroup);
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglPreservingSwapTests.hpp b/modules/egl/teglPreservingSwapTests.hpp
new file mode 100644
index 0000000..5a8ec2d
--- /dev/null
+++ b/modules/egl/teglPreservingSwapTests.hpp
@@ -0,0 +1,48 @@
+#ifndef _TEGLPRESERVINGSWAPTESTS_HPP
+#define _TEGLPRESERVINGSWAPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test EGL_SWAP_BEHAVIOR_PRESERVED_BIT.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class PreservingSwapTests : public TestCaseGroup
+{
+public:
+			PreservingSwapTests		(EglTestContext& eglTestCtx);
+	void	init					(void);
+
+private:
+							PreservingSwapTests	(const PreservingSwapTests&);
+	PreservingSwapTests&	operator=			(const PreservingSwapTests&);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLPRESERVINGSWAPTESTS_HPP
diff --git a/modules/egl/teglQueryConfigTests.cpp b/modules/egl/teglQueryConfigTests.cpp
new file mode 100644
index 0000000..aa08c22
--- /dev/null
+++ b/modules/egl/teglQueryConfigTests.cpp
@@ -0,0 +1,580 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Config query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglQueryConfigTests.hpp"
+#include "teglSimpleConfigCase.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTestContext.hpp"
+#include "tcuCommandLine.hpp"
+#include "egluCallLogWrapper.hpp"
+#include "egluStrUtil.hpp"
+#include "deRandom.hpp"
+
+#include <string>
+#include <vector>
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#	define EGL_OPENGL_ES3_BIT_KHR	0x0040
+#endif
+
+namespace deqp
+{
+namespace egl
+{
+
+using eglu::ConfigInfo;
+using tcu::TestLog;
+
+static void logConfigAttribute (TestLog& log, EGLenum attrib, EGLint value)
+{
+	log << TestLog::Message << "  " << eglu::getConfigAttribName(attrib) << ": " << eglu::getConfigAttribValueStr(attrib, value) << TestLog::EndMessage;
+}
+
+static bool isAttributePresent (const eglu::Version& version, EGLenum attribute)
+{
+	switch (attribute)
+	{
+		case EGL_CONFORMANT:
+			if (version < eglu::Version(1, 3)) return false;
+			break;
+		case EGL_LUMINANCE_SIZE:
+		case EGL_ALPHA_MASK_SIZE:
+		case EGL_COLOR_BUFFER_TYPE:
+		case EGL_MATCH_NATIVE_PIXMAP:
+			if (version < eglu::Version(1, 2)) return false;
+			break;
+		case EGL_BIND_TO_TEXTURE_RGB:
+		case EGL_BIND_TO_TEXTURE_RGBA:
+		case EGL_MAX_SWAP_INTERVAL:
+		case EGL_MIN_SWAP_INTERVAL:
+		case EGL_RENDERABLE_TYPE:
+			if (version < eglu::Version(1, 1)) return false;
+			break;
+		default:
+			break;
+	}
+
+	return true;
+}
+
+class GetConfigsBoundsCase : public TestCase, protected eglu::CallLogWrapper
+{
+public:
+	GetConfigsBoundsCase (EglTestContext& eglTestCtx, const char* name, const char* description)
+		: TestCase	(eglTestCtx, name, description)
+		, CallLogWrapper(eglTestCtx.getTestContext().getLog())
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+
+	void checkGetConfigsBounds(const tcu::egl::Display& display, de::Random& rnd, const int numConfigAll, const int numConfigRequested)
+	{
+		tcu::TestLog&			log				= m_testCtx.getLog();
+		std::vector<EGLConfig>	buffer			(numConfigAll + 10);
+
+		std::vector<deUint32>	magicBuffer		((buffer.size() * sizeof(EGLConfig)) / sizeof(deUint32) + 1);
+		const EGLConfig*		magicConfigs	= reinterpret_cast<EGLConfig*>(&magicBuffer[0]);
+
+		int						numConfigReturned;
+
+		// Fill buffers with magic
+		for (size_t ndx = 0; ndx < magicBuffer.size(); ndx++)	magicBuffer[ndx]	= rnd.getUint32();
+		for (size_t ndx = 0; ndx < buffer.size(); ndx++)		buffer[ndx]			= magicConfigs[ndx];
+
+		eglGetConfigs(display.getEGLDisplay(), &buffer[0], numConfigRequested, &numConfigReturned);
+		TCU_CHECK_EGL();
+
+		log << TestLog::Message << numConfigReturned << " configs returned" << TestLog::EndMessage;
+
+		// Compare results with stored magic
+		{
+			int	numOverwritten	= 0;
+
+			for (size_t ndx = 0; ndx < buffer.size(); ndx++)
+			{
+				if (buffer[ndx] == magicConfigs[ndx])
+				{
+					numOverwritten = (int)ndx;
+					break;
+				}
+			}
+
+			log << TestLog::Message << numOverwritten << " values actually written" << TestLog::EndMessage;
+
+			if (numConfigReturned > deMax32(numConfigRequested, 0))
+			{
+				log << TestLog::Message << "Fail, more configs returned than requested." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Too many configs returned");
+			}
+
+			if (numOverwritten > deMax32(numConfigReturned, 0))
+			{
+				log << TestLog::Message << "Fail, buffer overflow detected." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer overflow");
+			}
+			else if (numOverwritten != numConfigReturned)
+			{
+				log << TestLog::Message << "Fail, reported number of returned configs differs from number of values written." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect size");
+			}
+		}
+	}
+
+	IterateResult iterate (void)
+	{
+		tcu::TestLog&		log			= m_testCtx.getLog();
+		tcu::egl::Display&	display		= m_eglTestCtx.getDisplay();
+		EGLint				numConfigAll;
+
+		enableLogging(true);
+
+		eglGetConfigs(display.getEGLDisplay(), 0, 0, &numConfigAll);
+
+		log << TestLog::Message << numConfigAll << " configs available" << TestLog::EndMessage;
+		log << TestLog::Message << TestLog::EndMessage;
+
+		if (numConfigAll > 0)
+		{
+			de::Random		rnd					(123);
+
+			for (int i = 0; i < 5; i++)
+			{
+				checkGetConfigsBounds(display, rnd, numConfigAll, rnd.getInt(0, numConfigAll));
+				log << TestLog::Message << TestLog::EndMessage;
+			}
+
+			checkGetConfigsBounds(display, rnd, numConfigAll, -1);
+		}
+		else
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "No configs");
+		}
+
+		enableLogging(false);
+
+		return STOP;
+	}
+};
+
+class GetConfigAttribCase : public TestCase, protected eglu::CallLogWrapper
+{
+public:
+	GetConfigAttribCase (EglTestContext& eglTestCtx, const char* name, const char* description);
+
+	void			init		();
+	IterateResult	iterate		(void);
+
+	EGLint			getValue	(EGLConfig config, EGLenum attrib, bool logValue=true);
+
+	virtual void	executeTest	(EGLConfig config) = 0;
+private:
+	std::vector<EGLConfig>					m_configs;
+	std::vector<EGLConfig>::const_iterator	m_configsIter;
+};
+
+GetConfigAttribCase::GetConfigAttribCase (EglTestContext& eglTestCtx, const char* name, const char* description)
+	: TestCase(eglTestCtx, name, description)
+	, CallLogWrapper(eglTestCtx.getTestContext().getLog())
+{
+}
+
+void GetConfigAttribCase::init (void)
+{
+	const tcu::egl::Display&	display	= m_eglTestCtx.getDisplay();
+
+	display.getConfigs(m_configs);
+	m_configsIter = m_configs.begin();
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+tcu::TestNode::IterateResult GetConfigAttribCase::iterate (void)
+{
+	tcu::TestLog&		log			= m_testCtx.getLog();
+	tcu::egl::Display&	display		= m_eglTestCtx.getDisplay();
+
+	if (m_configsIter == m_configs.end())
+	{
+		log << TestLog::Message << "No configs available." << TestLog::EndMessage;
+		return STOP;
+	}
+
+	{
+		const EGLConfig	config	= *m_configsIter;
+		EGLint			id;
+
+		TCU_CHECK_EGL_CALL(eglGetConfigAttrib(display.getEGLDisplay(), config, EGL_CONFIG_ID, &id));
+		log << TestLog::Message << "Config ID " << id << TestLog::EndMessage;
+
+		executeTest(config);
+	}
+
+	log << TestLog::Message << TestLog::EndMessage;
+
+	m_configsIter++;
+
+	if (m_configsIter == m_configs.end())
+		return STOP;
+	else
+		return CONTINUE;
+}
+
+EGLint GetConfigAttribCase::getValue (EGLConfig config, EGLenum attrib, bool logValue)
+{
+	TestLog&					log		= m_testCtx.getLog();
+	const tcu::egl::Display&	display	= m_eglTestCtx.getDisplay();
+	EGLint			value;
+
+	eglGetConfigAttrib(display.getEGLDisplay(), config, attrib, &value);
+	TCU_CHECK_EGL();
+
+	if (logValue) logConfigAttribute(log, attrib, value);
+
+	return value;
+}
+
+class GetConfigAttribSimpleCase : public GetConfigAttribCase
+{
+public:
+	GetConfigAttribSimpleCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLenum attribute)
+		: GetConfigAttribCase(eglTestCtx, name, description)
+		, m_attrib(attribute)
+	{
+	}
+
+	void checkColorBufferType (EGLint value)
+	{
+		if (!(value == EGL_RGB_BUFFER || value == EGL_LUMINANCE_BUFFER))
+		{
+			TestLog&	log	= m_testCtx.getLog();
+
+			log << TestLog::Message << "Fail, invalid EGL_COLOR_BUFFER_TYPE value" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid value");
+		}
+	}
+
+	void checkCaveat (EGLint value)
+	{
+		if (!(value == EGL_NONE || value == EGL_SLOW_CONFIG || value == EGL_NON_CONFORMANT_CONFIG))
+		{
+			TestLog&	log	= m_testCtx.getLog();
+
+			log << TestLog::Message << "Fail, invalid EGL_CONFIG_CAVEAT value" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid value");
+		}
+	}
+
+	void checkTransparentType (EGLint value)
+	{
+		if (!(value == EGL_NONE || value == EGL_TRANSPARENT_RGB))
+		{
+			TestLog&	log	= m_testCtx.getLog();
+
+			log << TestLog::Message << "Fail, invalid EGL_TRANSPARENT_TYPE value" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid value");
+		}
+	}
+
+	void checkBoolean (EGLenum attrib, EGLint value)
+	{
+		if (!(value == EGL_FALSE || value == EGL_TRUE))
+		{
+			TestLog&	log	= m_testCtx.getLog();
+
+			log << TestLog::Message << "Fail, " << eglu::getConfigAttribStr(attrib) << " should be a boolean value." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid value");
+		}
+	}
+
+	void checkInteger (EGLenum attrib, EGLint value)
+	{
+		if (attrib == EGL_NATIVE_VISUAL_ID || attrib == EGL_NATIVE_VISUAL_TYPE) // Implementation-defined
+			return;
+
+		if (attrib == EGL_CONFIG_ID && value < 1)
+		{
+			TestLog&	log	= m_testCtx.getLog();
+
+			log << TestLog::Message << "Fail, config IDs should be positive integer values beginning from 1." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid value");
+		}
+	}
+
+	void checkSurfaceTypeMask (EGLint value)
+	{
+		const EGLint	wantedBits	= EGL_WINDOW_BIT | EGL_PIXMAP_BIT | EGL_PBUFFER_BIT;
+
+		if ((value & wantedBits) == 0)
+		{
+			TestLog&	log	= m_testCtx.getLog();
+
+			log << TestLog::Message << "Fail, config does not actually support creation of any surface type?" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid value");
+		}
+	}
+
+	void checkAttribute (EGLenum attrib, EGLint value)
+	{
+		switch (attrib)
+		{
+			case EGL_COLOR_BUFFER_TYPE:
+				checkColorBufferType(value);
+				break;
+			case EGL_CONFIG_CAVEAT:
+				checkCaveat(value);
+				break;
+			case EGL_TRANSPARENT_TYPE:
+				checkTransparentType(value);
+				break;
+			case EGL_CONFORMANT:
+			case EGL_RENDERABLE_TYPE:
+				// Just print what we know
+				break;
+			case EGL_SURFACE_TYPE:
+				checkSurfaceTypeMask(value);
+				break;
+			case EGL_BIND_TO_TEXTURE_RGB:
+			case EGL_BIND_TO_TEXTURE_RGBA:
+			case EGL_NATIVE_RENDERABLE:
+				checkBoolean(attrib, value);
+				break;
+			default:
+				checkInteger(attrib, value);
+		}
+	}
+
+	void executeTest (EGLConfig config)
+	{
+		TestLog&					log		= m_testCtx.getLog();
+		const tcu::egl::Display&	display	= m_eglTestCtx.getDisplay();
+
+		if (!isAttributePresent(display.getVersion(), m_attrib))
+		{
+			log << TestLog::Message << eglu::getConfigAttribStr(m_attrib) << " not supported by this EGL version";
+		}
+		else
+		{
+			EGLint			value;
+
+			enableLogging(true);
+
+			eglGetConfigAttrib(display.getEGLDisplay(), config, m_attrib, &value);
+			TCU_CHECK_EGL();
+
+			logConfigAttribute(log, m_attrib, value);
+			checkAttribute(m_attrib, value);
+
+			enableLogging(false);
+		}
+	}
+
+private:
+	EGLenum	m_attrib;
+};
+
+class GetConfigAttribBufferSizeCase : public GetConfigAttribCase
+{
+public:
+	GetConfigAttribBufferSizeCase (EglTestContext& eglTestCtx, const char* name, const char* description)
+		: GetConfigAttribCase(eglTestCtx, name, description)
+	{
+	}
+
+	void executeTest (EGLConfig config)
+	{
+		TestLog&		log				= m_testCtx.getLog();
+
+		const EGLint	colorBufferType	= getValue(config, EGL_COLOR_BUFFER_TYPE);
+
+		const EGLint	bufferSize		= getValue(config, EGL_BUFFER_SIZE);
+		const EGLint	redSize			= getValue(config, EGL_RED_SIZE);
+		const EGLint	greenSize		= getValue(config, EGL_GREEN_SIZE);
+		const EGLint	blueSize		= getValue(config, EGL_BLUE_SIZE);
+		const EGLint	luminanceSize	= getValue(config, EGL_LUMINANCE_SIZE);
+		const EGLint	alphaSize		= getValue(config, EGL_ALPHA_SIZE);
+
+		if (alphaSize < 0)
+		{
+			log << TestLog::Message << "Fail, alpha size must be zero or positive." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid alpha size");
+		}
+
+		if (colorBufferType == EGL_RGB_BUFFER)
+		{
+			if (luminanceSize != 0)
+			{
+				log << TestLog::Message << "Fail, luminance size must be zero for an RGB buffer." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid luminance size");
+			}
+
+			if (redSize <= 0 || greenSize <= 0  || blueSize <= 0)
+			{
+				log << TestLog::Message << "Fail, RGB component sizes must be positive for an RGB buffer." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid color component size");
+			}
+
+			if (bufferSize != (redSize + greenSize + blueSize + alphaSize))
+			{
+				log << TestLog::Message << "Fail, buffer size must be equal to the sum of RGB component sizes and alpha size." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid buffer size");
+			}
+		}
+		else if (colorBufferType == EGL_LUMINANCE_BUFFER)
+		{
+			if (luminanceSize <= 0)
+			{
+				log << TestLog::Message << "Fail, luminance size must be positive for a luminance buffer." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid luminance size");
+			}
+
+			if (redSize != 0 || greenSize != 0  || blueSize != 0)
+			{
+				log << TestLog::Message << "Fail, RGB component sizes must be zero for a luminance buffer." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid color component size");
+			}
+
+			if (bufferSize != (luminanceSize + alphaSize))
+			{
+				log << TestLog::Message << "Fail, buffer size must be equal to the sum of luminance size and alpha size." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid buffer size");
+			}
+		}
+	}
+};
+
+class GetConfigAttribTransparentValueCase : public GetConfigAttribCase
+{
+public:
+	GetConfigAttribTransparentValueCase (EglTestContext& eglTestCtx, const char* name, const char* description)
+		: GetConfigAttribCase(eglTestCtx, name, description)
+	{
+	}
+
+	void executeTest (EGLConfig config)
+	{
+		TestLog&		log	= m_testCtx.getLog();
+
+		const EGLint	transparentType	= getValue(config, EGL_TRANSPARENT_TYPE);
+		const EGLint	redValue		= getValue(config, EGL_TRANSPARENT_RED_VALUE);
+		const EGLint	greenValue		= getValue(config, EGL_TRANSPARENT_GREEN_VALUE);
+		const EGLint	blueValue		= getValue(config, EGL_TRANSPARENT_BLUE_VALUE);
+
+		const EGLint	redSize			= getValue(config, EGL_RED_SIZE);
+		const EGLint	greenSize		= getValue(config, EGL_GREEN_SIZE);
+		const EGLint	blueSize		= getValue(config, EGL_BLUE_SIZE);
+
+		if (transparentType == EGL_TRANSPARENT_RGB)
+		{
+			if (   (redValue	< 0	|| redValue		>= (1 << redSize))
+				|| (greenValue	< 0	|| greenValue	>= (1 << greenSize))
+				|| (blueValue	< 0	|| blueValue	>= (1 << blueSize))	)
+			{
+				log << TestLog::Message << "Fail, transparent color values must lie between 0 and the maximum component value." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid transparent color value");
+			}
+		}
+	}
+};
+
+QueryConfigTests::QueryConfigTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "query_config", "Surface config query tests")
+{
+}
+
+QueryConfigTests::~QueryConfigTests (void)
+{
+}
+
+void QueryConfigTests::init (void)
+{
+	// eglGetGonfigs
+	{
+		tcu::TestCaseGroup* getConfigsGroup = new tcu::TestCaseGroup(m_testCtx, "get_configs", "eglGetConfigs tests");
+		addChild(getConfigsGroup);
+
+		getConfigsGroup->addChild(new GetConfigsBoundsCase(m_eglTestCtx, "get_configs_bounds", "eglGetConfigs bounds checking test"));
+	}
+
+	// eglGetConfigAttrib
+	{
+		static const struct
+		{
+			EGLenum			attribute;
+			const char*		testName;
+		} attributes[] =
+		{
+			{ EGL_BUFFER_SIZE,				"buffer_size"				},
+			{ EGL_RED_SIZE,					"red_size"					},
+			{ EGL_GREEN_SIZE,				"green_size"				},
+			{ EGL_BLUE_SIZE,				"blue_size"					},
+			{ EGL_LUMINANCE_SIZE,			"luminance_size"			},
+			{ EGL_ALPHA_SIZE,				"alpha_size"				},
+			{ EGL_ALPHA_MASK_SIZE,			"alpha_mask_size"			},
+			{ EGL_BIND_TO_TEXTURE_RGB,		"bind_to_texture_rgb"		},
+			{ EGL_BIND_TO_TEXTURE_RGBA,		"bind_to_texture_rgba"		},
+			{ EGL_COLOR_BUFFER_TYPE,		"color_buffer_type"			},
+			{ EGL_CONFIG_CAVEAT,			"config_caveat"				},
+			{ EGL_CONFIG_ID,				"config_id"					},
+			{ EGL_CONFORMANT,				"conformant"				},
+			{ EGL_DEPTH_SIZE,				"depth_size"				},
+			{ EGL_LEVEL,					"level"						},
+			{ EGL_MAX_SWAP_INTERVAL,		"max_swap_interval"			},
+			{ EGL_MIN_SWAP_INTERVAL,		"min_swap_interval"			},
+			{ EGL_NATIVE_RENDERABLE,		"native_renderable"			},
+			{ EGL_NATIVE_VISUAL_TYPE,		"native_visual_type"		},
+			{ EGL_RENDERABLE_TYPE,			"renderable_type"			},
+			{ EGL_SAMPLE_BUFFERS,			"sample_buffers"			},
+			{ EGL_SAMPLES,					"samples"					},
+			{ EGL_STENCIL_SIZE,				"stencil_size"				},
+			{ EGL_SURFACE_TYPE,				"surface_type"				},
+			{ EGL_TRANSPARENT_TYPE,			"transparent_type"			},
+			{ EGL_TRANSPARENT_RED_VALUE,	"transparent_red_value"		},
+			{ EGL_TRANSPARENT_GREEN_VALUE,	"transparent_green_value"	},
+			{ EGL_TRANSPARENT_BLUE_VALUE,	"transparent_blue_value"	}
+		};
+
+		tcu::TestCaseGroup* simpleGroup = new tcu::TestCaseGroup(m_testCtx, "get_config_attrib", "eglGetConfigAttrib() tests");
+		addChild(simpleGroup);
+
+		for (int ndx = 0; ndx < (int)DE_LENGTH_OF_ARRAY(attributes); ndx++)
+		{
+			simpleGroup->addChild(new GetConfigAttribSimpleCase(m_eglTestCtx, attributes[ndx].testName, "Simple attribute query case", attributes[ndx].attribute));
+		}
+	}
+
+	// Attribute constraints
+	{
+		tcu::TestCaseGroup* constraintsGroup = new tcu::TestCaseGroup(m_testCtx, "constraints", "Attribute constraint tests");
+		addChild(constraintsGroup);
+
+		constraintsGroup->addChild(new GetConfigAttribBufferSizeCase(m_eglTestCtx,			"color_buffer_size",	"Color buffer component sizes"));
+		constraintsGroup->addChild(new GetConfigAttribTransparentValueCase(m_eglTestCtx,	"transparent_value",	"Transparent color value"));
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglQueryConfigTests.hpp b/modules/egl/teglQueryConfigTests.hpp
new file mode 100644
index 0000000..35d754c
--- /dev/null
+++ b/modules/egl/teglQueryConfigTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLQUERYCONFIGTESTS_HPP
+#define _TEGLQUERYCONFIGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Surface query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class QueryConfigTests : public TestCaseGroup
+{
+public:
+							QueryConfigTests			(EglTestContext& eglTestCtx);
+	virtual					~QueryConfigTests			(void);
+
+	void					init						(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLQUERYCONFIGTESTS_HPP
diff --git a/modules/egl/teglQueryContextTests.cpp b/modules/egl/teglQueryContextTests.cpp
new file mode 100644
index 0000000..c6edb9d
--- /dev/null
+++ b/modules/egl/teglQueryContextTests.cpp
@@ -0,0 +1,569 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Config query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglQueryContextTests.hpp"
+#include "teglSimpleConfigCase.hpp"
+#include "teglRenderCase.hpp"
+#include "egluCallLogWrapper.hpp"
+#include "egluStrUtil.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTestContext.hpp"
+
+#include "egluUtil.hpp"
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+
+#include "deUniquePtr.hpp"
+
+#include <vector>
+
+#include <EGL/eglext.h>
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#	define EGL_OPENGL_ES3_BIT_KHR	0x0040
+#endif
+#if !defined(EGL_CONTEXT_MAJOR_VERSION_KHR)
+#	define EGL_CONTEXT_MAJOR_VERSION_KHR EGL_CONTEXT_CLIENT_VERSION
+#endif
+
+namespace deqp
+{
+namespace egl
+{
+
+using eglu::ConfigInfo;
+using tcu::TestLog;
+
+struct ContextCaseInfo
+{
+	EGLint	surfaceType;
+	EGLint	clientType;
+	EGLint	clientVersion;
+};
+
+class ContextCase : public SimpleConfigCase, protected eglu::CallLogWrapper
+{
+public:
+						ContextCase			(EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds, EGLint surfaceTypeMask);
+	virtual				~ContextCase		(void);
+
+	void				executeForConfig	(tcu::egl::Display& display, EGLConfig config);
+	void				executeForSurface	(tcu::egl::Display& display, EGLConfig config, EGLSurface surface, ContextCaseInfo& info);
+
+	virtual void		executeForContext	(tcu::egl::Display& display, EGLConfig config, EGLSurface surface, EGLContext context, ContextCaseInfo& info) = 0;
+
+private:
+	EGLint				m_surfaceTypeMask;
+};
+
+ContextCase::ContextCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds, EGLint surfaceTypeMask)
+	: SimpleConfigCase	(eglTestCtx, name, description, configIds)
+	, CallLogWrapper	(eglTestCtx.getTestContext().getLog())
+	, m_surfaceTypeMask	(surfaceTypeMask)
+{
+}
+
+ContextCase::~ContextCase (void)
+{
+}
+
+void ContextCase::executeForConfig (tcu::egl::Display& display, EGLConfig config)
+{
+	tcu::TestLog&			log				= m_testCtx.getLog();
+	const int				width			= 64;
+	const int				height			= 64;
+	const EGLint			configId		= display.getConfigAttrib(config, EGL_CONFIG_ID);
+	eglu::NativeDisplay&	nativeDisplay	= m_eglTestCtx.getNativeDisplay();
+	bool					isOk			= true;
+	std::string				failReason		= "";
+
+	if (m_surfaceTypeMask & EGL_WINDOW_BIT)
+	{
+		log << TestLog::Message << "Creating window surface with config ID " << configId << TestLog::EndMessage;
+
+		try
+		{
+			de::UniquePtr<eglu::NativeWindow>	window		(m_eglTestCtx.createNativeWindow(display.getEGLDisplay(), config, DE_NULL, width, height, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
+			tcu::egl::WindowSurface				surface		(display, eglu::createWindowSurface(nativeDisplay, *window, display.getEGLDisplay(), config, DE_NULL));
+
+			ContextCaseInfo info;
+			info.surfaceType	= EGL_WINDOW_BIT;
+
+			executeForSurface(m_eglTestCtx.getDisplay(), config, surface.getEGLSurface(), info);
+		}
+		catch (const tcu::TestError& e)
+		{
+			log << e;
+			isOk = false;
+			failReason = e.what();
+		}
+
+		log << TestLog::Message << TestLog::EndMessage;
+	}
+
+	if (m_surfaceTypeMask & EGL_PIXMAP_BIT)
+	{
+		log << TestLog::Message << "Creating pixmap surface with config ID " << configId << TestLog::EndMessage;
+
+		try
+		{
+			de::UniquePtr<eglu::NativePixmap>	pixmap		(m_eglTestCtx.createNativePixmap(display.getEGLDisplay(), config, DE_NULL, width, height));
+			tcu::egl::PixmapSurface				surface		(display, eglu::createPixmapSurface(nativeDisplay, *pixmap, display.getEGLDisplay(), config, DE_NULL));
+
+			ContextCaseInfo info;
+			info.surfaceType	= EGL_PIXMAP_BIT;
+
+			executeForSurface(display, config, surface.getEGLSurface(), info);
+		}
+		catch (const tcu::TestError& e)
+		{
+			log << e;
+			isOk = false;
+			failReason = e.what();
+		}
+
+		log << TestLog::Message << TestLog::EndMessage;
+	}
+
+	if (m_surfaceTypeMask & EGL_PBUFFER_BIT)
+	{
+		log << TestLog::Message << "Creating pbuffer surface with config ID " << configId << TestLog::EndMessage;
+
+		try
+		{
+			const EGLint surfaceAttribs[] =
+			{
+				EGL_WIDTH,	width,
+				EGL_HEIGHT,	height,
+				EGL_NONE
+			};
+
+			tcu::egl::PbufferSurface surface(display, config, surfaceAttribs);
+
+			ContextCaseInfo info;
+			info.surfaceType	= EGL_PBUFFER_BIT;
+
+			executeForSurface(display, config, surface.getEGLSurface(), info);
+		}
+		catch (const tcu::TestError& e)
+		{
+			log << e;
+			isOk = false;
+			failReason = e.what();
+		}
+
+		log << TestLog::Message << TestLog::EndMessage;
+	}
+
+	if (!isOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, failReason.c_str());
+}
+
+void ContextCase::executeForSurface (tcu::egl::Display& display, EGLConfig config, EGLSurface surface, ContextCaseInfo& info)
+{
+	TestLog&	log		= m_testCtx.getLog();
+	EGLint		apiBits	= display.getConfigAttrib(config, EGL_RENDERABLE_TYPE);
+
+	static const EGLint es1Attrs[] = { EGL_CONTEXT_CLIENT_VERSION,		1, EGL_NONE };
+	static const EGLint es2Attrs[] = { EGL_CONTEXT_CLIENT_VERSION,		2, EGL_NONE };
+	static const EGLint es3Attrs[] = { EGL_CONTEXT_MAJOR_VERSION_KHR,	3, EGL_NONE };
+
+	static const struct
+	{
+		const char*		name;
+		EGLenum			api;
+		EGLint			apiBit;
+		const EGLint*	ctxAttrs;
+		EGLint			apiVersion;
+	} apis[] =
+	{
+		{ "OpenGL",			EGL_OPENGL_API,		EGL_OPENGL_BIT,			DE_NULL,	0	},
+		{ "OpenGL ES 1",	EGL_OPENGL_ES_API,	EGL_OPENGL_ES_BIT,		es1Attrs,	1	},
+		{ "OpenGL ES 2",	EGL_OPENGL_ES_API,	EGL_OPENGL_ES2_BIT,		es2Attrs,	2	},
+		{ "OpenGL ES 3",	EGL_OPENGL_ES_API,	EGL_OPENGL_ES3_BIT_KHR,	es3Attrs,	3	},
+		{ "OpenVG",			EGL_OPENVG_API,		EGL_OPENVG_BIT,			DE_NULL,	0	}
+	};
+
+	for (int apiNdx = 0; apiNdx < (int)DE_LENGTH_OF_ARRAY(apis); apiNdx++)
+	{
+		if ((apiBits & apis[apiNdx].apiBit) == 0)
+			continue; // Not supported API
+
+		TCU_CHECK_EGL_CALL(eglBindAPI(apis[apiNdx].api));
+
+		log << TestLog::Message << "Creating " << apis[apiNdx].name << " context" << TestLog::EndMessage;
+
+		const EGLContext	context = eglCreateContext(display.getEGLDisplay(), config, EGL_NO_CONTEXT, apis[apiNdx].ctxAttrs);
+		TCU_CHECK_EGL();
+		TCU_CHECK(context != EGL_NO_CONTEXT);
+
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display.getEGLDisplay(), surface, surface, context));
+
+		info.clientType		= apis[apiNdx].api;
+		info.clientVersion	= apis[apiNdx].apiVersion;
+
+		executeForContext(display, config, surface, context, info);
+
+		// Destroy
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display.getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+		TCU_CHECK_EGL_CALL(eglDestroyContext(display.getEGLDisplay(), context));
+	}
+}
+
+class GetCurrentContextCase : public ContextCase
+{
+public:
+	GetCurrentContextCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds, EGLint surfaceTypeMask)
+		: ContextCase(eglTestCtx, name, description, configIds, surfaceTypeMask)
+	{
+	}
+
+	void executeForContext (tcu::egl::Display& display, EGLConfig config, EGLSurface surface, EGLContext context, ContextCaseInfo& info)
+	{
+		TestLog&	log	= m_testCtx.getLog();
+
+		DE_UNREF(display);
+		DE_UNREF(config && surface);
+		DE_UNREF(info);
+
+		enableLogging(true);
+
+		const EGLContext	gotContext	= eglGetCurrentContext();
+		TCU_CHECK_EGL();
+
+		if (gotContext == context)
+		{
+			log << TestLog::Message << "  Pass" << TestLog::EndMessage;
+		}
+		else if (gotContext == EGL_NO_CONTEXT)
+		{
+			log << TestLog::Message << "  Fail, got EGL_NO_CONTEXT" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected EGL_NO_CONTEXT");
+		}
+		else if (gotContext != context)
+		{
+			log << TestLog::Message << "  Fail, call returned the wrong context. Expected: " << tcu::toHex(context) << ", got: " << tcu::toHex(gotContext) << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid context");
+		}
+
+		enableLogging(false);
+	}
+};
+
+class GetCurrentSurfaceCase : public ContextCase
+{
+public:
+	GetCurrentSurfaceCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds, EGLint surfaceTypeMask)
+		: ContextCase(eglTestCtx, name, description, configIds, surfaceTypeMask)
+	{
+	}
+
+	void executeForContext (tcu::egl::Display& display, EGLConfig config, EGLSurface surface, EGLContext context, ContextCaseInfo& info)
+	{
+		TestLog&	log	= m_testCtx.getLog();
+
+		DE_UNREF(display);
+		DE_UNREF(config && context);
+		DE_UNREF(info);
+
+		enableLogging(true);
+
+		const EGLContext	gotReadSurface	= eglGetCurrentSurface(EGL_READ);
+		TCU_CHECK_EGL();
+
+		const EGLContext	gotDrawSurface	= eglGetCurrentSurface(EGL_DRAW);
+		TCU_CHECK_EGL();
+
+		if (gotReadSurface == surface && gotDrawSurface == surface)
+		{
+			log << TestLog::Message << "  Pass" << TestLog::EndMessage;
+		}
+		else
+		{
+			log << TestLog::Message << "  Fail, read surface: " << tcu::toHex(gotReadSurface)
+									<< ", draw surface: " << tcu::toHex(gotDrawSurface)
+									<< ", expected: " << tcu::toHex(surface) << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid surface");
+		}
+
+		enableLogging(false);
+	}
+};
+
+class GetCurrentDisplayCase : public ContextCase
+{
+public:
+	GetCurrentDisplayCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds, EGLint surfaceTypeMask)
+		: ContextCase(eglTestCtx, name, description, configIds, surfaceTypeMask)
+	{
+	}
+
+	void executeForContext (tcu::egl::Display& display, EGLConfig config, EGLSurface surface, EGLContext context, ContextCaseInfo& info)
+	{
+		TestLog&	log	= m_testCtx.getLog();
+
+		DE_UNREF(config && surface && context);
+		DE_UNREF(info);
+
+		enableLogging(true);
+
+		const EGLDisplay	gotDisplay	= eglGetCurrentDisplay();
+		TCU_CHECK_EGL();
+
+		if (gotDisplay == display.getEGLDisplay())
+		{
+			log << TestLog::Message << "  Pass" << TestLog::EndMessage;
+		}
+		else if (gotDisplay == EGL_NO_DISPLAY)
+		{
+			log << TestLog::Message << "  Fail, got EGL_NO_DISPLAY" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected EGL_NO_DISPLAY");
+		}
+		else if (gotDisplay != display.getEGLDisplay())
+		{
+			log << TestLog::Message << "  Fail, call returned the wrong display. Expected: " << tcu::toHex(display.getEGLDisplay()) << ", got: " << tcu::toHex(gotDisplay) << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid display");
+		}
+
+		enableLogging(false);
+	}
+};
+
+class QueryContextCase : public ContextCase
+{
+public:
+	QueryContextCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds, EGLint surfaceTypeMask)
+		: ContextCase(eglTestCtx, name, description, configIds, surfaceTypeMask)
+	{
+	}
+
+	EGLint getContextAttrib (tcu::egl::Display& display, EGLContext context, EGLint attrib)
+	{
+		EGLint	value;
+		TCU_CHECK_EGL_CALL(eglQueryContext(display.getEGLDisplay(), context, attrib, &value));
+
+		return value;
+	}
+
+	void executeForContext (tcu::egl::Display& display, EGLConfig config, EGLSurface surface, EGLContext context, ContextCaseInfo& info)
+	{
+		TestLog&			log		= m_testCtx.getLog();
+		const eglu::Version	version	(display.getEGLMajorVersion(), display.getEGLMinorVersion());
+
+		DE_UNREF(surface);
+		enableLogging(true);
+
+		// Config ID
+		{
+			const EGLint	configID		= getContextAttrib(display, context, EGL_CONFIG_ID);
+			const EGLint	surfaceConfigID	= display.getConfigAttrib(config, EGL_CONFIG_ID);
+
+			if (configID != surfaceConfigID)
+			{
+				log << TestLog::Message << "  Fail, config ID doesn't match the one used to create the context." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid config ID");
+			}
+		}
+
+		// Client API type
+		if (version >= eglu::Version(1, 2))
+		{
+			const EGLint	clientType		= getContextAttrib(display, context, EGL_CONTEXT_CLIENT_TYPE);
+
+			if (clientType != info.clientType)
+			{
+				log << TestLog::Message << "  Fail, client API type doesn't match." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid client API type");
+			}
+		}
+
+		// Client API version
+		if (version >= eglu::Version(1, 3))
+		{
+			const EGLint	clientVersion	= getContextAttrib(display, context, EGL_CONTEXT_CLIENT_VERSION);
+
+			if (info.clientType == EGL_OPENGL_ES_API && clientVersion != info.clientVersion)
+			{
+				log << TestLog::Message << "  Fail, client API version doesn't match." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid client API version");
+			}
+		}
+
+		// Render buffer
+		if (version >= eglu::Version(1, 2))
+		{
+			const EGLint	renderBuffer	= getContextAttrib(display, context, EGL_RENDER_BUFFER);
+
+			if (info.surfaceType == EGL_PIXMAP_BIT && renderBuffer != EGL_SINGLE_BUFFER)
+			{
+				log << TestLog::Message << "  Fail, render buffer should be EGL_SINGLE_BUFFER for a pixmap surface." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid render buffer");
+			}
+			else if (info.surfaceType == EGL_PBUFFER_BIT && renderBuffer != EGL_BACK_BUFFER)
+			{
+				log << TestLog::Message << "  Fail, render buffer should be EGL_BACK_BUFFER for a pbuffer surface." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid render buffer");
+			}
+			else if (info.surfaceType == EGL_WINDOW_BIT && renderBuffer != EGL_SINGLE_BUFFER && renderBuffer != EGL_BACK_BUFFER)
+			{
+				log << TestLog::Message << "  Fail, render buffer should be either EGL_SINGLE_BUFFER or EGL_BACK_BUFFER for a window surface." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid render buffer");
+			}
+		}
+
+		enableLogging(false);
+
+		log << TestLog::Message << "  Pass" << TestLog::EndMessage;
+	}
+};
+
+class QueryAPICase : public TestCase, protected eglu::CallLogWrapper
+{
+public:
+	QueryAPICase (EglTestContext& eglTestCtx, const char* name, const char* description)
+		: TestCase(eglTestCtx, name, description)
+		, CallLogWrapper(eglTestCtx.getTestContext().getLog())
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+
+	IterateResult iterate (void)
+	{
+		tcu::TestLog&	log		= m_testCtx.getLog();
+		const EGLenum	apis[]	= { EGL_OPENGL_API, EGL_OPENGL_ES_API, EGL_OPENVG_API };
+
+		enableLogging(true);
+
+		{
+			const EGLenum	api	= eglQueryAPI();
+
+			if (api != EGL_OPENGL_ES_API && m_eglTestCtx.isAPISupported(EGL_OPENGL_ES_API))
+			{
+				log << TestLog::Message << "  Fail, initial value should be EGL_OPENGL_ES_API if OpenGL ES is supported." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid default value");
+			}
+			else if(api != EGL_NONE && !m_eglTestCtx.isAPISupported(EGL_OPENGL_ES_API))
+			{
+				log << TestLog::Message << "  Fail, initial value should be EGL_NONE if OpenGL ES is not supported." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid default value");
+			}
+		}
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(apis); ndx++)
+		{
+			const EGLenum	api	= apis[ndx];
+
+			log << TestLog::Message << TestLog::EndMessage;
+
+			if (m_eglTestCtx.isAPISupported(api))
+			{
+				eglBindAPI(api);
+
+				if (api != eglQueryAPI())
+				{
+					log << TestLog::Message << "  Fail, return value does not match previously bound API." << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid return value");
+				}
+			}
+			else
+			{
+				log << TestLog::Message << eglu::getAPIStr(api) << " not supported." << TestLog::EndMessage;
+			}
+		}
+
+		enableLogging(false);
+		return STOP;
+	}
+};
+
+QueryContextTests::QueryContextTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "query_context", "Rendering context query tests")
+{
+}
+
+QueryContextTests::~QueryContextTests (void)
+{
+}
+
+template<class QueryContextClass>
+void createQueryContextGroups (EglTestContext& eglTestCtx, tcu::TestCaseGroup* group)
+{
+	std::vector<RenderConfigIdSet>	configSets;
+	eglu::FilterList				filters;
+
+	getDefaultRenderConfigIdSets(configSets, eglTestCtx.getConfigs(), filters);
+
+	for (std::vector<RenderConfigIdSet>::const_iterator setIter = configSets.begin(); setIter != configSets.end(); setIter++)
+		group->addChild(new QueryContextClass(eglTestCtx, setIter->getName(), "", setIter->getConfigIds(), setIter->getSurfaceTypeMask()));
+}
+
+void QueryContextTests::init (void)
+{
+	{
+		tcu::TestCaseGroup* simpleGroup = new tcu::TestCaseGroup(m_testCtx, "simple", "Simple API tests");
+		addChild(simpleGroup);
+
+		simpleGroup->addChild(new QueryAPICase(m_eglTestCtx, "query_api", "eglQueryAPI() test"));
+	}
+
+	// eglGetCurrentContext
+	{
+		tcu::TestCaseGroup* getCurrentContextGroup = new tcu::TestCaseGroup(m_testCtx, "get_current_context", "eglGetCurrentContext() tests");
+		addChild(getCurrentContextGroup);
+
+		createQueryContextGroups<GetCurrentContextCase>(m_eglTestCtx, getCurrentContextGroup);
+	}
+
+	// eglGetCurrentSurface
+	{
+		tcu::TestCaseGroup* getCurrentSurfaceGroup = new tcu::TestCaseGroup(m_testCtx, "get_current_surface", "eglGetCurrentSurface() tests");
+		addChild(getCurrentSurfaceGroup);
+
+		createQueryContextGroups<GetCurrentSurfaceCase>(m_eglTestCtx, getCurrentSurfaceGroup);
+	}
+
+	// eglGetCurrentDisplay
+	{
+		tcu::TestCaseGroup* getCurrentDisplayGroup = new tcu::TestCaseGroup(m_testCtx, "get_current_display", "eglGetCurrentDisplay() tests");
+		addChild(getCurrentDisplayGroup);
+
+		createQueryContextGroups<GetCurrentDisplayCase>(m_eglTestCtx, getCurrentDisplayGroup);
+	}
+
+	// eglQueryContext
+	{
+		tcu::TestCaseGroup* queryContextGroup = new tcu::TestCaseGroup(m_testCtx, "query_context", "eglQueryContext() tests");
+		addChild(queryContextGroup);
+
+		createQueryContextGroups<QueryContextCase>(m_eglTestCtx, queryContextGroup);
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglQueryContextTests.hpp b/modules/egl/teglQueryContextTests.hpp
new file mode 100644
index 0000000..e5ac396
--- /dev/null
+++ b/modules/egl/teglQueryContextTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLQUERYCONTEXTTESTS_HPP
+#define _TEGLQUERYCONTEXTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Context query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class QueryContextTests : public TestCaseGroup
+{
+public:
+			QueryContextTests	(EglTestContext& eglTestCtx);
+	virtual	~QueryContextTests	(void);
+
+	void	init				(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLQUERYCONTEXTTESTS_HPP
diff --git a/modules/egl/teglQuerySurfaceTests.cpp b/modules/egl/teglQuerySurfaceTests.cpp
new file mode 100644
index 0000000..4a993aa
--- /dev/null
+++ b/modules/egl/teglQuerySurfaceTests.cpp
@@ -0,0 +1,787 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Surface query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglQuerySurfaceTests.hpp"
+
+#include "teglSimpleConfigCase.hpp"
+
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluStrUtil.hpp"
+#include "egluUtil.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuTestContext.hpp"
+#include "tcuCommandLine.hpp"
+
+#include "deUniquePtr.hpp"
+#include "deRandom.hpp"
+
+#include <string>
+#include <vector>
+
+namespace deqp
+{
+namespace egl
+{
+
+using eglu::ConfigInfo;
+using tcu::TestLog;
+
+static void logSurfaceAttribute (tcu::TestLog& log, EGLint attribute, EGLint value)
+{
+	const char*								name		= eglu::getSurfaceAttribName(attribute);
+	const eglu::SurfaceAttribValueFmt		valueFmt	(attribute, value);
+
+	log << TestLog::Message << "  " << name << ": " << valueFmt << TestLog::EndMessage;
+}
+
+static void logSurfaceAttributes (tcu::TestLog& log, const tcu::egl::Surface& surface, const EGLint* attributes, int num)
+{
+	for (int ndx = 0; ndx < num; ndx++)
+	{
+		const EGLint	attrib	= attributes[ndx];
+
+		logSurfaceAttribute(log, attrib, surface.getAttribute(attrib));
+	}
+}
+
+static void logCommonSurfaceAttributes (tcu::TestLog& log, const tcu::egl::Surface& surface)
+{
+	static const EGLint	attributes[] =
+	{
+		EGL_CONFIG_ID,
+		EGL_WIDTH,
+		EGL_HEIGHT,
+		EGL_HORIZONTAL_RESOLUTION,
+		EGL_VERTICAL_RESOLUTION,
+		EGL_MULTISAMPLE_RESOLVE,
+		EGL_PIXEL_ASPECT_RATIO,
+		EGL_RENDER_BUFFER,
+		EGL_SWAP_BEHAVIOR,
+		EGL_VG_ALPHA_FORMAT,
+		EGL_VG_COLORSPACE
+	};
+
+	logSurfaceAttributes(log, surface, attributes, DE_LENGTH_OF_ARRAY(attributes));
+}
+
+static void logPbufferSurfaceAttributes (tcu::TestLog& log, const tcu::egl::Surface& surface)
+{
+	static const EGLint	attributes[] = {
+		EGL_LARGEST_PBUFFER,
+		EGL_TEXTURE_FORMAT,
+		EGL_TEXTURE_TARGET,
+		EGL_MIPMAP_TEXTURE,
+		EGL_MIPMAP_LEVEL,
+	};
+
+	logSurfaceAttributes(log, surface, attributes, DE_LENGTH_OF_ARRAY(attributes));
+}
+
+class QuerySurfaceCase : public SimpleConfigCase
+{
+public:
+	QuerySurfaceCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds);
+
+	void checkCommonAttributes (const tcu::egl::Surface& surface, const ConfigInfo& info);
+	void checkNonPbufferAttributes (EGLDisplay display, const tcu::egl::Surface& surface);
+};
+
+QuerySurfaceCase::QuerySurfaceCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds)
+	: SimpleConfigCase(eglTestCtx, name, description, configIds)
+{
+}
+
+void QuerySurfaceCase::checkCommonAttributes (const tcu::egl::Surface& surface, const ConfigInfo& info)
+{
+	tcu::TestLog&	log		= m_testCtx.getLog();
+
+	// Attributes which are common to all surface types
+
+	// Config ID
+	{
+		const EGLint	id	= surface.getAttribute(EGL_CONFIG_ID);
+
+		if (id != info.configId)
+		{
+			log << TestLog::Message << "    Fail, config ID " << id << " does not match the one used to create the surface" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Config ID mismatch");
+		}
+	}
+
+	// Width and height
+	{
+		const EGLint	width	= surface.getWidth();
+		const EGLint	height	= surface.getHeight();
+
+		if (width <= 0 || height <= 0)
+		{
+			log << TestLog::Message << "    Fail, invalid surface size " << width << "x" << height << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid surface size");
+		}
+	}
+
+	// Horizontal and vertical resolution
+	{
+		const EGLint	hRes	= surface.getAttribute(EGL_HORIZONTAL_RESOLUTION);
+		const EGLint	vRes	= surface.getAttribute(EGL_VERTICAL_RESOLUTION);
+
+		if ((hRes <= 0 || vRes <= 0) && (hRes != EGL_UNKNOWN && vRes != EGL_UNKNOWN))
+		{
+			log << TestLog::Message << "    Fail, invalid surface resolution " << hRes << "x" << vRes << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid surface resolution");
+		}
+	}
+
+	// Pixel aspect ratio
+	{
+		const EGLint	pixelRatio	= surface.getAttribute(EGL_PIXEL_ASPECT_RATIO);
+
+		if (pixelRatio <= 0 && pixelRatio != EGL_UNKNOWN)
+		{
+			log << TestLog::Message << "    Fail, invalid pixel aspect ratio " << surface.getAttribute(EGL_PIXEL_ASPECT_RATIO) << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixel aspect ratio");
+		}
+	}
+
+	// Render buffer
+	{
+		const EGLint	renderBuffer	= surface.getAttribute(EGL_RENDER_BUFFER);
+
+		if (renderBuffer != EGL_BACK_BUFFER && renderBuffer != EGL_SINGLE_BUFFER)
+		{
+			log << TestLog::Message << "    Fail, invalid render buffer value " << renderBuffer << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid render buffer");
+		}
+	}
+
+	// Multisample resolve
+	{
+		const EGLint	multisampleResolve	= surface.getAttribute(EGL_MULTISAMPLE_RESOLVE);
+
+		if (multisampleResolve != EGL_MULTISAMPLE_RESOLVE_DEFAULT && multisampleResolve != EGL_MULTISAMPLE_RESOLVE_BOX)
+		{
+			log << TestLog::Message << "    Fail, invalid multisample resolve value " << multisampleResolve << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid multisample resolve");
+		}
+
+		if (multisampleResolve == EGL_MULTISAMPLE_RESOLVE_BOX && !(info.surfaceType & EGL_MULTISAMPLE_RESOLVE_BOX_BIT))
+		{
+			log << TestLog::Message << "    Fail, multisample resolve is reported as box filter but configuration does not support it." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid multisample resolve");
+		}
+	}
+
+	// Swap behavior
+	{
+		const EGLint	swapBehavior	= surface.getAttribute(EGL_SWAP_BEHAVIOR);
+
+		if (swapBehavior != EGL_BUFFER_DESTROYED && swapBehavior != EGL_BUFFER_PRESERVED)
+		{
+			log << TestLog::Message << "    Fail, invalid swap behavior value " << swapBehavior << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid swap behavior");
+		}
+
+		if (swapBehavior == EGL_BUFFER_PRESERVED && !(info.surfaceType & EGL_SWAP_BEHAVIOR_PRESERVED_BIT))
+		{
+			log << TestLog::Message << "    Fail, swap behavior is reported as preserve but configuration does not support it." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid swap behavior");
+		}
+	}
+
+	// OpenVG alpha format
+	{
+		const EGLint	vgAlphaFormat	= surface.getAttribute(EGL_VG_ALPHA_FORMAT);
+
+		if (vgAlphaFormat != EGL_VG_ALPHA_FORMAT_NONPRE && vgAlphaFormat != EGL_VG_ALPHA_FORMAT_PRE)
+		{
+			log << TestLog::Message << "    Fail, invalid OpenVG alpha format value " << vgAlphaFormat << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid OpenVG alpha format");
+		}
+
+		if (vgAlphaFormat == EGL_VG_ALPHA_FORMAT_PRE && !(info.surfaceType & EGL_VG_ALPHA_FORMAT_PRE_BIT))
+		{
+			log << TestLog::Message << "    Fail, OpenVG is set to use premultiplied alpha but configuration does not support it." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid OpenVG alpha format");
+		}
+	}
+
+	// OpenVG color space
+	{
+		const EGLint	vgColorspace	= surface.getAttribute(EGL_VG_COLORSPACE);
+
+		if (vgColorspace != EGL_VG_COLORSPACE_sRGB && vgColorspace != EGL_VG_COLORSPACE_LINEAR)
+		{
+			log << TestLog::Message << "    Fail, invalid OpenVG color space value " << vgColorspace << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid OpenVG color space");
+		}
+
+		if (vgColorspace == EGL_VG_COLORSPACE_LINEAR && !(info.surfaceType & EGL_VG_COLORSPACE_LINEAR_BIT))
+		{
+			log << TestLog::Message << "    Fail, OpenVG is set to use a linear color space but configuration does not support it." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid OpenVG color space");
+		}
+	}
+}
+
+void QuerySurfaceCase::checkNonPbufferAttributes (EGLDisplay display, const tcu::egl::Surface& surface)
+{
+	const EGLint	uninitializedMagicValue	= -42;
+	tcu::TestLog&	log						= m_testCtx.getLog();
+	EGLint			value					= uninitializedMagicValue;
+
+	static const EGLint pbufferAttribs[] = {
+		EGL_LARGEST_PBUFFER,
+		EGL_TEXTURE_FORMAT,
+		EGL_TEXTURE_TARGET,
+		EGL_MIPMAP_TEXTURE,
+		EGL_MIPMAP_LEVEL,
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pbufferAttribs); ndx++)
+	{
+		const EGLint		attribute	= pbufferAttribs[ndx];
+		const std::string	name		= eglu::getSurfaceAttribName(pbufferAttribs[ndx]);
+
+		eglQuerySurface(display, surface.getEGLSurface(), attribute, &value);
+
+		{
+			const EGLint	error	= eglGetError();
+
+			if (error != EGL_SUCCESS)
+			{
+				log << TestLog::Message << "    Fail, querying " << name << " from a non-pbuffer surface should not result in an error, received "
+					<< eglu::getErrorStr(error) << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Illegal error condition");
+			}
+
+			break;
+		}
+
+		// "For a window or pixmap surface, the contents of value are not modified."
+		if (value != uninitializedMagicValue)
+		{
+			log << TestLog::Message << "    Fail, return value contents were modified when querying " << name << " from a non-pbuffer surface." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Illegal modification of return value");
+		}
+	}
+}
+
+class QuerySurfaceSimpleWindowCase : public QuerySurfaceCase
+{
+public:
+	QuerySurfaceSimpleWindowCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds)
+		: QuerySurfaceCase(eglTestCtx, name, description, configIds)
+	{
+	}
+
+	void executeForConfig (tcu::egl::Display& display, EGLConfig config)
+	{
+		tcu::TestLog&	log		= m_testCtx.getLog();
+		const int		width	= 64;
+		const int		height	= 64;
+
+		ConfigInfo		info;
+		display.describeConfig(config, info);
+
+		log << TestLog::Message << "Creating window surface with config ID " << info.configId << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		de::UniquePtr<eglu::NativeWindow>	window(m_eglTestCtx.createNativeWindow(display.getEGLDisplay(), config, DE_NULL, width, height, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
+		tcu::egl::WindowSurface				surface(display, eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, display.getEGLDisplay(), config, DE_NULL));
+
+		logCommonSurfaceAttributes(log, surface);
+
+		checkCommonAttributes(surface, info);
+		checkNonPbufferAttributes(display.getEGLDisplay(), surface);
+	}
+};
+
+class QuerySurfaceSimplePixmapCase : public QuerySurfaceCase
+{
+public:
+	QuerySurfaceSimplePixmapCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds)
+		: QuerySurfaceCase(eglTestCtx, name, description, configIds)
+	{
+	}
+
+	void executeForConfig (tcu::egl::Display& display, EGLConfig config)
+	{
+		tcu::TestLog&	log		= m_testCtx.getLog();
+		const int		width	= 64;
+		const int		height	= 64;
+
+		ConfigInfo		info;
+		display.describeConfig(config, info);
+
+		log << TestLog::Message << "Creating pixmap surface with config ID " << info.configId << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		de::UniquePtr<eglu::NativePixmap>	pixmap	(m_eglTestCtx.createNativePixmap(display.getEGLDisplay(), config, DE_NULL, width, height));
+		tcu::egl::PixmapSurface				surface	(display, eglu::createPixmapSurface(m_eglTestCtx.getNativeDisplay(), *pixmap, display.getEGLDisplay(), config, DE_NULL));
+
+		logCommonSurfaceAttributes(log, surface);
+
+		checkCommonAttributes(surface, info);
+		checkNonPbufferAttributes(display.getEGLDisplay(), surface);
+	}
+};
+
+class QuerySurfaceSimplePbufferCase : public QuerySurfaceCase
+{
+public:
+	QuerySurfaceSimplePbufferCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds)
+		: QuerySurfaceCase(eglTestCtx, name, description, configIds)
+	{
+	}
+
+	void executeForConfig (tcu::egl::Display& display, EGLConfig config)
+	{
+		tcu::TestLog&	log		= m_testCtx.getLog();
+		int				width	= 64;
+		int				height	= 64;
+
+		ConfigInfo		info;
+		display.describeConfig(config, info);
+
+		log << TestLog::Message << "Creating pbuffer surface with config ID " << info.configId << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		// Clamp to maximums reported by implementation
+		width	= deMin32(width, display.getConfigAttrib(config, EGL_MAX_PBUFFER_WIDTH));
+		height	= deMin32(height, display.getConfigAttrib(config, EGL_MAX_PBUFFER_HEIGHT));
+
+		if (width == 0 || height == 0)
+		{
+			log << TestLog::Message << "    Fail, maximum pbuffer size of " << width << "x" << height << " reported" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid maximum pbuffer size");
+			return;
+		}
+
+		const EGLint attribs[] =
+		{
+			EGL_WIDTH,			width,
+			EGL_HEIGHT,			height,
+			EGL_TEXTURE_FORMAT,	EGL_NO_TEXTURE,
+			EGL_NONE
+		};
+
+		{
+			tcu::egl::PbufferSurface surface(display, config, attribs);
+
+			logCommonSurfaceAttributes(log, surface);
+			logPbufferSurfaceAttributes(log, surface);
+
+			checkCommonAttributes(surface, info);
+
+			// Pbuffer-specific attributes
+
+			// Largest pbuffer
+			{
+				const EGLint	largestPbuffer	= surface.getAttribute(EGL_LARGEST_PBUFFER);
+
+				if (largestPbuffer != EGL_FALSE && largestPbuffer != EGL_TRUE)
+				{
+					log << TestLog::Message << "    Fail, invalid largest pbuffer value " << largestPbuffer << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid largest pbuffer");
+				}
+			}
+
+			// Texture format
+			{
+				const EGLint	textureFormat	= surface.getAttribute(EGL_TEXTURE_FORMAT);
+
+				if (textureFormat != EGL_NO_TEXTURE && textureFormat != EGL_TEXTURE_RGB && textureFormat != EGL_TEXTURE_RGBA)
+				{
+					log << TestLog::Message << "    Fail, invalid texture format value " << textureFormat << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid texture format");
+				}
+			}
+
+			// Texture target
+			{
+				const EGLint	textureTarget	= surface.getAttribute(EGL_TEXTURE_TARGET);
+
+				if (textureTarget != EGL_NO_TEXTURE && textureTarget != EGL_TEXTURE_2D)
+				{
+					log << TestLog::Message << "    Fail, invalid texture target value " << textureTarget << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid texture target");
+				}
+			}
+
+			// Mipmap texture
+			{
+				const EGLint	mipmapTexture	= surface.getAttribute(EGL_MIPMAP_TEXTURE);
+
+				if (mipmapTexture != EGL_FALSE && mipmapTexture != EGL_TRUE)
+				{
+					log << TestLog::Message << "    Fail, invalid mipmap texture value " << mipmapTexture << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid mipmap texture");
+				}
+			}
+		}
+	}
+};
+
+class SurfaceAttribCase : public SimpleConfigCase
+{
+public:
+			SurfaceAttribCase	(EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds);
+	virtual	~SurfaceAttribCase	(void) {}
+
+	void	testAttributes		(tcu::egl::Surface& surface, const ConfigInfo& info);
+};
+
+SurfaceAttribCase::SurfaceAttribCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds)
+		: SimpleConfigCase(eglTestCtx, name, description, configIds)
+{
+}
+
+void SurfaceAttribCase::testAttributes (tcu::egl::Surface& surface, const ConfigInfo& info)
+{
+	const tcu::egl::Display&	display			= surface.getDisplay();
+	tcu::TestLog&				log				= m_testCtx.getLog();
+	const int					majorVersion	= display.getEGLMajorVersion();
+	const int					minorVersion	= display.getEGLMinorVersion();
+	de::Random					rnd				(deStringHash(m_name.c_str()) ^ 0xf215918f);
+
+	if (majorVersion == 1 && minorVersion == 0)
+	{
+		log << TestLog::Message << "No attributes can be set in EGL 1.0" << TestLog::EndMessage;
+		return;
+	}
+
+	// Mipmap level
+	if (info.renderableType & EGL_OPENGL_ES_BIT || info.renderableType & EGL_OPENGL_ES2_BIT)
+	{
+		const EGLint	value	= surface.getAttribute(EGL_MIPMAP_LEVEL);
+
+		logSurfaceAttribute(log, EGL_MIPMAP_LEVEL, value);
+
+		if (value != 0)
+		{
+			log << TestLog::Message << "    Fail, initial mipmap level value should be 0, is " << value << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid default mipmap level");
+		}
+
+		eglSurfaceAttrib(display.getEGLDisplay(), surface.getEGLSurface(), EGL_MIPMAP_LEVEL, 1);
+
+		{
+			const EGLint	error	= eglGetError();
+
+			if (error != EGL_SUCCESS)
+			{
+				log << TestLog::Message << "    Fail, setting EGL_MIPMAP_LEVEL should not result in an error, received " << eglu::getErrorStr(error) << TestLog::EndMessage;
+
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Illegal error condition");
+			}
+		}
+	}
+
+	// Only mipmap level can be set in EGL 1.3 and lower
+	if (majorVersion == 1 && minorVersion <= 3) return;
+
+	// Multisample resolve
+	{
+		const EGLint	value	= surface.getAttribute(EGL_MULTISAMPLE_RESOLVE);
+
+		logSurfaceAttribute(log, EGL_MULTISAMPLE_RESOLVE, value);
+
+		if (value != EGL_MULTISAMPLE_RESOLVE_DEFAULT)
+		{
+			log << TestLog::Message << "    Fail, initial multisample resolve value should be EGL_MULTISAMPLE_RESOLVE_DEFAULT, is "
+				<< eglu::getSurfaceAttribValueStr(EGL_MULTISAMPLE_RESOLVE, value) << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid default multisample resolve");
+		}
+
+		if (info.renderableType & EGL_MULTISAMPLE_RESOLVE_BOX_BIT)
+		{
+			log << TestLog::Message << "    Box filter is supported by surface, trying to set." << TestLog::EndMessage;
+
+			surface.setAttribute(EGL_MULTISAMPLE_RESOLVE, EGL_MULTISAMPLE_RESOLVE_BOX);
+
+			if (surface.getAttribute(EGL_MULTISAMPLE_RESOLVE) != EGL_MULTISAMPLE_RESOLVE_BOX)
+			{
+				log << TestLog::Message << "    Fail, tried to enable box filter but value did not change.";
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to set multisample resolve");
+			}
+		}
+	}
+
+	// Swap behavior
+	{
+		const EGLint	value	= surface.getAttribute(EGL_SWAP_BEHAVIOR);
+
+		logSurfaceAttribute(log, EGL_SWAP_BEHAVIOR, value);
+
+		if (info.renderableType & EGL_SWAP_BEHAVIOR_PRESERVED_BIT)
+		{
+			const EGLint	nextValue	= (value == EGL_BUFFER_DESTROYED) ? EGL_BUFFER_PRESERVED : EGL_BUFFER_DESTROYED;
+
+			surface.setAttribute(EGL_SWAP_BEHAVIOR, nextValue);
+
+			if (surface.getAttribute(EGL_SWAP_BEHAVIOR) != nextValue)
+			{
+				log << TestLog::Message << "  Fail, tried to set swap behavior to " << eglu::getSurfaceAttribStr(nextValue) << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to set swap behavior");
+			}
+		}
+	}
+}
+
+class SurfaceAttribWindowCase : public SurfaceAttribCase
+{
+public:
+	SurfaceAttribWindowCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds)
+		: SurfaceAttribCase(eglTestCtx, name, description, configIds)
+	{
+	}
+
+	void executeForConfig (tcu::egl::Display& display, EGLConfig config)
+	{
+		tcu::TestLog&	log		= m_testCtx.getLog();
+		const int		width	= 64;
+		const int		height	= 64;
+
+		ConfigInfo		info;
+		display.describeConfig(config, info);
+
+		log << TestLog::Message << "Creating window surface with config ID " << info.configId << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		de::UniquePtr<eglu::NativeWindow>	window(m_eglTestCtx.createNativeWindow(display.getEGLDisplay(), config, DE_NULL, width, height, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
+		tcu::egl::WindowSurface				surface(display, eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, display.getEGLDisplay(), config, DE_NULL));
+
+		testAttributes(surface, info);
+	}
+};
+
+class SurfaceAttribPixmapCase : public SurfaceAttribCase
+{
+public:
+	SurfaceAttribPixmapCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds)
+		: SurfaceAttribCase(eglTestCtx, name, description, configIds)
+	{
+	}
+
+	void executeForConfig (tcu::egl::Display& display, EGLConfig config)
+	{
+		tcu::TestLog&	log		= m_testCtx.getLog();
+		const int		width	= 64;
+		const int		height	= 64;
+
+		ConfigInfo		info;
+		display.describeConfig(config, info);
+
+		log << TestLog::Message << "Creating pixmap surface with config ID " << info.configId << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		de::UniquePtr<eglu::NativePixmap>	pixmap	(m_eglTestCtx.createNativePixmap(display.getEGLDisplay(), config, DE_NULL, width, height));
+		tcu::egl::PixmapSurface				surface	(display, eglu::createPixmapSurface(m_eglTestCtx.getNativeDisplay(), *pixmap, display.getEGLDisplay(), config, DE_NULL));
+
+		testAttributes(surface, info);
+	}
+};
+
+class SurfaceAttribPbufferCase : public SurfaceAttribCase
+{
+public:
+	SurfaceAttribPbufferCase (EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds)
+		: SurfaceAttribCase(eglTestCtx, name, description, configIds)
+	{
+	}
+
+	void executeForConfig (tcu::egl::Display& display, EGLConfig config)
+	{
+		tcu::TestLog&	log		= m_testCtx.getLog();
+		int				width	= 64;
+		int				height	= 64;
+
+		ConfigInfo		info;
+		display.describeConfig(config, info);
+
+		log << TestLog::Message << "Creating pbuffer surface with config ID " << info.configId << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		// Clamp to maximums reported by implementation
+		width	= deMin32(width, display.getConfigAttrib(config, EGL_MAX_PBUFFER_WIDTH));
+		height	= deMin32(height, display.getConfigAttrib(config, EGL_MAX_PBUFFER_HEIGHT));
+
+		if (width == 0 || height == 0)
+		{
+			log << TestLog::Message << "    Fail, maximum pbuffer size of " << width << "x" << height << " reported" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid maximum pbuffer size");
+			return;
+		}
+
+		const EGLint attribs[] =
+		{
+			EGL_WIDTH,			width,
+			EGL_HEIGHT,			height,
+			EGL_TEXTURE_FORMAT,	EGL_NO_TEXTURE,
+			EGL_NONE
+		};
+
+		tcu::egl::PbufferSurface surface(display, config, attribs);
+
+		testAttributes(surface, info);
+	}
+};
+
+QuerySurfaceTests::QuerySurfaceTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "query_surface", "Surface Query Tests")
+{
+}
+
+QuerySurfaceTests::~QuerySurfaceTests (void)
+{
+}
+
+std::vector<EGLint> getConfigs (const tcu::egl::Display& display, EGLint surfaceType)
+{
+	std::vector<EGLint>			out;
+
+	std::vector<EGLConfig> eglConfigs;
+	display.getConfigs(eglConfigs);
+
+	for (size_t ndx = 0; ndx < eglConfigs.size(); ndx++)
+	{
+		ConfigInfo info;
+		display.describeConfig(eglConfigs[ndx], info);
+
+		if (info.surfaceType & surfaceType)
+			out.push_back(info.configId);
+	}
+
+	return out;
+}
+
+void QuerySurfaceTests::init (void)
+{
+	// Simple queries
+	{
+		tcu::TestCaseGroup* simpleGroup = new tcu::TestCaseGroup(m_testCtx, "simple", "Simple queries");
+		addChild(simpleGroup);
+
+		// Window
+		{
+			tcu::TestCaseGroup* windowGroup = new tcu::TestCaseGroup(m_testCtx, "window", "Window surfaces");
+			simpleGroup->addChild(windowGroup);
+
+			eglu::FilterList filters;
+			filters << (eglu::ConfigSurfaceType() & EGL_WINDOW_BIT);
+
+			std::vector<NamedConfigIdSet> configIdSets;
+			NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+			for (std::vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+				windowGroup->addChild(new QuerySurfaceSimpleWindowCase(m_eglTestCtx, i->getName(), i->getDescription(), i->getConfigIds()));
+		}
+
+		// Pixmap
+		{
+			tcu::TestCaseGroup* pixmapGroup = new tcu::TestCaseGroup(m_testCtx, "pixmap", "Pixmap surfaces");
+			simpleGroup->addChild(pixmapGroup);
+
+			eglu::FilterList filters;
+			filters << (eglu::ConfigSurfaceType() & EGL_PIXMAP_BIT);
+
+			std::vector<NamedConfigIdSet> configIdSets;
+			NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+			for (std::vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+				pixmapGroup->addChild(new QuerySurfaceSimplePixmapCase(m_eglTestCtx, i->getName(), i->getDescription(), i->getConfigIds()));
+		}
+
+		// Pbuffer
+		{
+			tcu::TestCaseGroup* pbufferGroup = new tcu::TestCaseGroup(m_testCtx, "pbuffer", "Pbuffer surfaces");
+			simpleGroup->addChild(pbufferGroup);
+
+			eglu::FilterList filters;
+			filters << (eglu::ConfigSurfaceType() & EGL_PBUFFER_BIT);
+
+			std::vector<NamedConfigIdSet> configIdSets;
+			NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+			for (std::vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+				pbufferGroup->addChild(new QuerySurfaceSimplePbufferCase(m_eglTestCtx, i->getName(), i->getDescription(), i->getConfigIds()));
+		}
+	}
+
+	// Set surface attributes
+	{
+		tcu::TestCaseGroup* setAttributeGroup = new tcu::TestCaseGroup(m_testCtx, "set_attribute", "Setting attributes");
+		addChild(setAttributeGroup);
+
+		// Window
+		{
+			tcu::TestCaseGroup* windowGroup = new tcu::TestCaseGroup(m_testCtx, "window", "Window surfaces");
+			setAttributeGroup->addChild(windowGroup);
+
+			eglu::FilterList filters;
+			filters << (eglu::ConfigSurfaceType() & EGL_WINDOW_BIT);
+
+			std::vector<NamedConfigIdSet> configIdSets;
+			NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+			for (std::vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+				windowGroup->addChild(new SurfaceAttribWindowCase(m_eglTestCtx, i->getName(), i->getDescription(), i->getConfigIds()));
+		}
+
+		// Pixmap
+		{
+			tcu::TestCaseGroup* pixmapGroup = new tcu::TestCaseGroup(m_testCtx, "pixmap", "Pixmap surfaces");
+			setAttributeGroup->addChild(pixmapGroup);
+
+			eglu::FilterList filters;
+			filters << (eglu::ConfigSurfaceType() & EGL_PIXMAP_BIT);
+
+			std::vector<NamedConfigIdSet> configIdSets;
+			NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+			for (std::vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+				pixmapGroup->addChild(new SurfaceAttribPixmapCase(m_eglTestCtx, i->getName(), i->getDescription(), i->getConfigIds()));
+		}
+
+		// Pbuffer
+		{
+			tcu::TestCaseGroup* pbufferGroup = new tcu::TestCaseGroup(m_testCtx, "pbuffer", "Pbuffer surfaces");
+			setAttributeGroup->addChild(pbufferGroup);
+
+			eglu::FilterList filters;
+			filters << (eglu::ConfigSurfaceType() & EGL_PBUFFER_BIT);
+
+			std::vector<NamedConfigIdSet> configIdSets;
+			NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+			for (std::vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+				pbufferGroup->addChild(new SurfaceAttribPbufferCase(m_eglTestCtx, i->getName(), i->getDescription(), i->getConfigIds()));
+		}
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglQuerySurfaceTests.hpp b/modules/egl/teglQuerySurfaceTests.hpp
new file mode 100644
index 0000000..8074ac0
--- /dev/null
+++ b/modules/egl/teglQuerySurfaceTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLQUERYSURFACETESTS_HPP
+#define _TEGLQUERYSURFACETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Surface query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class QuerySurfaceTests : public TestCaseGroup
+{
+public:
+							QuerySurfaceTests			(EglTestContext& eglTestCtx);
+	virtual					~QuerySurfaceTests			(void);
+
+	void					init						(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLQUERYSURFACETESTS_HPP
diff --git a/modules/egl/teglRenderCase.cpp b/modules/egl/teglRenderCase.cpp
new file mode 100644
index 0000000..d4e2f05
--- /dev/null
+++ b/modules/egl/teglRenderCase.cpp
@@ -0,0 +1,433 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Base class for rendering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglRenderCase.hpp"
+
+#include "teglSimpleConfigCase.hpp"
+
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluUtil.hpp"
+
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuCommandLine.hpp"
+
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <set>
+
+#include <EGL/eglext.h>
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#	define EGL_OPENGL_ES3_BIT_KHR	0x0040
+#endif
+#if !defined(EGL_CONTEXT_MAJOR_VERSION_KHR)
+#	define EGL_CONTEXT_MAJOR_VERSION_KHR EGL_CONTEXT_CLIENT_VERSION
+#endif
+
+using std::string;
+using std::vector;
+using std::set;
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+
+// \todo [2013-04-24 pyry] Should we instead store surface bit somewhere?
+template<class Derived, class Base>
+inline bool instanceOf (Base& obj)
+{
+	return dynamic_cast<Derived*>(&obj) != DE_NULL;
+}
+
+static void postSurface (tcu::egl::Surface& surface)
+{
+	const bool	isWindow	= instanceOf<tcu::egl::WindowSurface>(surface);
+	const bool	isPixmap	= instanceOf<tcu::egl::PixmapSurface>(surface);
+	const bool	isPbuffer	= instanceOf<tcu::egl::PbufferSurface>(surface);
+
+	DE_ASSERT((isWindow?1:0) + (isPixmap?1:0) + (isPbuffer?1:0) == 1);
+
+	if (isWindow)
+	{
+		tcu::egl::WindowSurface& window = static_cast<tcu::egl::WindowSurface&>(surface);
+		window.swapBuffers();
+	}
+	else if (isPixmap)
+	{
+		TCU_CHECK_EGL_CALL(eglWaitClient());
+	}
+	else
+	{
+		DE_ASSERT(isPbuffer);
+		DE_UNREF(isPbuffer);
+		TCU_CHECK_EGL_CALL(eglWaitClient());
+	}
+}
+
+// RenderCase
+
+RenderCase::RenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint apiMask, EGLint surfaceTypeMask, const vector<EGLint>& configIds)
+	: SimpleConfigCase	(eglTestCtx, name, description, configIds)
+	, m_apiMask			(apiMask)
+	, m_surfaceTypeMask	(surfaceTypeMask)
+{
+}
+
+RenderCase::~RenderCase (void)
+{
+}
+
+EGLint RenderCase::getSupportedApis (void)
+{
+	EGLint apiMask = 0;
+
+#if defined(DEQP_SUPPORT_GLES2)
+	apiMask |= EGL_OPENGL_ES2_BIT;
+#endif
+
+#if defined(DEQP_SUPPORT_GLES3)
+	apiMask |= EGL_OPENGL_ES3_BIT_KHR;
+#endif
+
+#if defined(DEQP_SUPPORT_GLES1)
+	apiMask |= EGL_OPENGL_ES_BIT;
+#endif
+
+#if defined(DEQP_SUPPORT_VG)
+	apiMask |= EGL_OPENVG_BIT;
+#endif
+
+	return apiMask;
+}
+
+void RenderCase::executeForConfig (tcu::egl::Display& defaultDisplay, EGLConfig config)
+{
+	tcu::TestLog&			log				= m_testCtx.getLog();
+	int						width			= 128;
+	int						height			= 128;
+	EGLint					configId		= defaultDisplay.getConfigAttrib(config, EGL_CONFIG_ID);
+	bool					isOk			= true;
+	string					failReason		= "";
+
+	if (m_surfaceTypeMask & EGL_WINDOW_BIT)
+	{
+		tcu::ScopedLogSection(log, (string("Config") + de::toString(configId) + "-Window").c_str(),
+										(string("Config ID ") + de::toString(configId) + ", window surface").c_str());
+
+		try
+		{
+			tcu::egl::Display&					display		= m_eglTestCtx.getDisplay();
+			de::UniquePtr<eglu::NativeWindow>	window		(m_eglTestCtx.createNativeWindow(display.getEGLDisplay(), config, DE_NULL, width, height, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
+			EGLSurface							eglSurface	= createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, display.getEGLDisplay(), config, DE_NULL);
+			tcu::egl::WindowSurface				surface		(display, eglSurface);
+
+			executeForSurface(display, surface, config);
+		}
+		catch (const tcu::TestError& e)
+		{
+			log << e;
+			isOk = false;
+			failReason = e.what();
+		}
+	}
+
+	if (m_surfaceTypeMask & EGL_PIXMAP_BIT)
+	{
+		tcu::ScopedLogSection(log, (string("Config") + de::toString(configId) + "-Pixmap").c_str(),
+										(string("Config ID ") + de::toString(configId) + ", pixmap surface").c_str());
+
+		try
+		{
+			tcu::egl::Display&					display		= m_eglTestCtx.getDisplay();
+			std::auto_ptr<eglu::NativePixmap>	pixmap		(m_eglTestCtx.createNativePixmap(display.getEGLDisplay(), config, DE_NULL, width, height));
+			EGLSurface							eglSurface	= createPixmapSurface(m_eglTestCtx.getNativeDisplay(), *pixmap, display.getEGLDisplay(), config, DE_NULL);
+			tcu::egl::PixmapSurface				surface		(display, eglSurface);
+
+			executeForSurface(display, surface, config);
+		}
+		catch (const tcu::TestError& e)
+		{
+			log << e;
+			isOk = false;
+			failReason = e.what();
+		}
+	}
+
+	if (m_surfaceTypeMask & EGL_PBUFFER_BIT)
+	{
+		tcu::ScopedLogSection(log, (string("Config") + de::toString(configId) + "-Pbuffer").c_str(),
+										(string("Config ID ") + de::toString(configId) + ", pbuffer surface").c_str());
+		try
+		{
+			EGLint surfaceAttribs[] =
+			{
+				EGL_WIDTH,	width,
+				EGL_HEIGHT,	height,
+				EGL_NONE
+			};
+
+			tcu::egl::PbufferSurface surface(defaultDisplay, config, surfaceAttribs);
+
+			executeForSurface(defaultDisplay, surface, config);
+		}
+		catch (const tcu::TestError& e)
+		{
+			log << e;
+			isOk = false;
+			failReason = e.what();
+		}
+	}
+
+	if (!isOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, failReason.c_str());
+}
+
+// SingleContextRenderCase
+
+SingleContextRenderCase::SingleContextRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint apiMask, EGLint surfaceTypeMask, const std::vector<EGLint>& configIds)
+	: RenderCase(eglTestCtx, name, description, apiMask, surfaceTypeMask, configIds)
+{
+}
+
+SingleContextRenderCase::~SingleContextRenderCase (void)
+{
+}
+
+void SingleContextRenderCase::executeForSurface (tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config)
+{
+	EGLint				supportedApis	= getSupportedApis();
+	const EGLint		apis[]			= { EGL_OPENGL_ES2_BIT, EGL_OPENGL_ES3_BIT_KHR, EGL_OPENGL_ES_BIT, EGL_OPENVG_BIT };
+	tcu::TestLog&		log				= m_testCtx.getLog();
+
+	// Check if case is supported
+	if ((m_apiMask & supportedApis) != m_apiMask)
+		throw tcu::NotSupportedError("Client APIs not supported", "", __FILE__, __LINE__);
+
+	for (int apiNdx = 0; apiNdx < DE_LENGTH_OF_ARRAY(apis); apiNdx++)
+	{
+		EGLint apiBit = apis[apiNdx];
+
+		if ((apiBit & m_apiMask) == 0)
+			continue; // Skip this api.
+
+		EGLint			api		= EGL_NONE;
+		const char*		apiName	= DE_NULL;
+		vector<EGLint>	contextAttribs;
+
+		// Select api enum and build context attributes.
+		switch (apiBit)
+		{
+			case EGL_OPENGL_ES2_BIT:
+				api		= EGL_OPENGL_ES_API;
+				apiName	= "OpenGL ES 2.x";
+				contextAttribs.push_back(EGL_CONTEXT_CLIENT_VERSION);
+				contextAttribs.push_back(2);
+				break;
+
+			case EGL_OPENGL_ES3_BIT_KHR:
+				api		= EGL_OPENGL_ES_API;
+				apiName	= "OpenGL ES 3.x";
+				contextAttribs.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
+				contextAttribs.push_back(3);
+				break;
+
+			case EGL_OPENGL_ES_BIT:
+				api		= EGL_OPENGL_ES_API;
+				apiName	= "OpenGL ES 1.x";
+				contextAttribs.push_back(EGL_CONTEXT_CLIENT_VERSION);
+				contextAttribs.push_back(1);
+				break;
+
+			case EGL_OPENVG_BIT:
+				api		= EGL_OPENVG_API;
+				apiName	= "OpenVG";
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+
+		contextAttribs.push_back(EGL_NONE);
+
+		log << TestLog::Message << apiName << TestLog::EndMessage;
+
+		tcu::egl::Context context(display, config, &contextAttribs[0], api);
+
+		context.makeCurrent(surface, surface);
+		executeForContext(display, context, surface, apiBit);
+
+		// Call SwapBuffers() / WaitClient() to finish rendering
+		postSurface(surface);
+	}
+}
+
+// MultiContextRenderCase
+
+MultiContextRenderCase::MultiContextRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const vector<EGLint>& configIds, int numContextsPerApi)
+	: RenderCase			(eglTestCtx, name, description, api, surfaceType, configIds)
+	, m_numContextsPerApi	(numContextsPerApi)
+{
+}
+
+MultiContextRenderCase::~MultiContextRenderCase (void)
+{
+}
+
+void MultiContextRenderCase::executeForSurface (tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config)
+{
+	vector<std::pair<EGLint, tcu::egl::Context*> > contexts;
+	contexts.reserve(3*m_numContextsPerApi); // 3 types of contexts at maximum.
+
+	try
+	{
+		// Create contexts that will participate in rendering.
+		for (int ndx = 0; ndx < m_numContextsPerApi; ndx++)
+		{
+			if (m_apiMask & EGL_OPENGL_ES2_BIT)
+			{
+				static const EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+				contexts.push_back(std::make_pair(EGL_OPENGL_ES2_BIT, new tcu::egl::Context(display, config, &attribs[0], EGL_OPENGL_ES_API)));
+			}
+
+			if (m_apiMask & EGL_OPENGL_ES3_BIT_KHR)
+			{
+				static const EGLint attribs[] = { EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_NONE };
+				contexts.push_back(std::make_pair(EGL_OPENGL_ES3_BIT_KHR, new tcu::egl::Context(display, config, &attribs[0], EGL_OPENGL_ES_API)));
+			}
+
+			if (m_apiMask & EGL_OPENGL_ES_BIT)
+			{
+				static const EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 1, EGL_NONE };
+				contexts.push_back(std::make_pair(EGL_OPENGL_ES_BIT, new tcu::egl::Context(display, config, &attribs[0], EGL_OPENGL_ES_API)));
+			}
+
+			if (m_apiMask & EGL_OPENVG_BIT)
+			{
+				static const EGLint attribs[] = { EGL_NONE };
+				contexts.push_back(std::make_pair(EGL_OPENVG_BIT, new tcu::egl::Context(display, config, &attribs[0], EGL_OPENVG_API)));
+			}
+		}
+
+		// Execute for contexts.
+		executeForContexts(display, surface, config, contexts);
+	}
+	catch (const std::exception&)
+	{
+		// Make sure all contexts have been destroyed.
+		for (vector<std::pair<EGLint, tcu::egl::Context*> >::iterator i = contexts.begin(); i != contexts.end(); i++)
+			delete i->second;
+		throw;
+	}
+
+	// Destroy contexts.
+	for (vector<std::pair<EGLint, tcu::egl::Context*> >::iterator i = contexts.begin(); i != contexts.end(); i++)
+		delete i->second;
+}
+
+// Utilities
+
+void addRenderConfigIdSet (
+	vector<RenderConfigIdSet>&			configSets,
+	const vector<eglu::ConfigInfo>&		configInfos,
+	const eglu::FilterList&				baseFilters,
+	const char*							name,
+	tcu::RGBA							colorBits,
+	EGLint								surfaceType)
+{
+	eglu::FilterList filters = baseFilters;
+	filters << (eglu::ConfigColorBits() == colorBits) << (eglu::ConfigSurfaceType() & surfaceType);
+
+	vector<EGLint> matchingConfigs;
+
+	for (vector<eglu::ConfigInfo>::const_iterator configIter = configInfos.begin(); configIter != configInfos.end(); configIter++)
+	{
+		if (!filters.match(*configIter))
+			continue;
+
+		matchingConfigs.push_back(configIter->configId);
+	}
+
+	configSets.push_back(RenderConfigIdSet(name, "", matchingConfigs, surfaceType));
+}
+
+void addRenderConfigIdSet (
+	vector<RenderConfigIdSet>&			configSets,
+	const vector<eglu::ConfigInfo>&		configInfos,
+	const eglu::FilterList&				baseFilters,
+	const char*							name,
+	tcu::RGBA							colorBits)
+{
+	addRenderConfigIdSet(configSets, configInfos, baseFilters, (string(name) + "_window").c_str(),	colorBits, EGL_WINDOW_BIT);
+	addRenderConfigIdSet(configSets, configInfos, baseFilters, (string(name) + "_pixmap").c_str(),	colorBits, EGL_PIXMAP_BIT);
+	addRenderConfigIdSet(configSets, configInfos, baseFilters, (string(name) + "_pbuffer").c_str(),	colorBits, EGL_PBUFFER_BIT);
+}
+
+void getDefaultRenderConfigIdSets (vector<RenderConfigIdSet>& configSets, const vector<eglu::ConfigInfo>& configInfos, const eglu::FilterList& baseFilters)
+{
+	using tcu::RGBA;
+
+	addRenderConfigIdSet(configSets, configInfos, baseFilters, "rgb565",	RGBA(5, 6, 5, 0));
+	addRenderConfigIdSet(configSets, configInfos, baseFilters, "rgb888",	RGBA(8, 8, 8, 0));
+	addRenderConfigIdSet(configSets, configInfos, baseFilters, "rgba4444",	RGBA(4, 4, 4, 4));
+	addRenderConfigIdSet(configSets, configInfos, baseFilters, "rgba5551",	RGBA(5, 5, 5, 1));
+	addRenderConfigIdSet(configSets, configInfos, baseFilters, "rgba8888",	RGBA(8, 8, 8, 8));
+
+	// Add other config ids to "other" set
+	{
+		set<EGLint>		usedConfigs;
+		vector<EGLint>	otherCfgSet;
+
+		for (vector<RenderConfigIdSet>::const_iterator setIter = configSets.begin(); setIter != configSets.end(); setIter++)
+		{
+			const vector<EGLint>& setCfgs = setIter->getConfigIds();
+			for (vector<EGLint>::const_iterator i = setCfgs.begin(); i != setCfgs.end(); i++)
+				usedConfigs.insert(*i);
+		}
+
+		for (vector<eglu::ConfigInfo>::const_iterator cfgIter = configInfos.begin(); cfgIter != configInfos.end(); cfgIter++)
+		{
+			if (!baseFilters.match(*cfgIter))
+				continue;
+
+			EGLint id = cfgIter->configId;
+
+			if (usedConfigs.find(id) == usedConfigs.end())
+				otherCfgSet.push_back(id);
+		}
+
+		configSets.push_back(RenderConfigIdSet("other", "", otherCfgSet, EGL_WINDOW_BIT|EGL_PIXMAP_BIT|EGL_PBUFFER_BIT));
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglRenderCase.hpp b/modules/egl/teglRenderCase.hpp
new file mode 100644
index 0000000..69a374b
--- /dev/null
+++ b/modules/egl/teglRenderCase.hpp
@@ -0,0 +1,104 @@
+#ifndef _TEGLRENDERCASE_HPP
+#define _TEGLRENDERCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Base class for rendering tests.
+ * \todo [2011-07-18 pyry] Uses currently gles2-specific RenderContext.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+#include "teglSimpleConfigCase.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace egl
+{
+
+class RenderCase : public SimpleConfigCase
+{
+public:
+					RenderCase				(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint apiMask, EGLint surfaceTypeMask, const std::vector<EGLint>& configIds);
+	virtual			~RenderCase				(void);
+
+	static EGLint	getSupportedApis		(void);
+
+protected:
+	virtual void	executeForConfig		(tcu::egl::Display& display, EGLConfig config);
+
+	virtual void	executeForSurface		(tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config) = DE_NULL;
+
+	EGLint			m_apiMask;
+	EGLint			m_surfaceTypeMask;
+};
+
+class SingleContextRenderCase : public RenderCase
+{
+public:
+					SingleContextRenderCase		(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint apiMask, EGLint surfaceTypeMask, const std::vector<EGLint>& configIds);
+	virtual			~SingleContextRenderCase	(void);
+
+protected:
+	virtual void	executeForSurface			(tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config);
+
+	virtual void	executeForContext			(const tcu::egl::Display& display, tcu::egl::Context& context, tcu::egl::Surface& surface, EGLint api) = DE_NULL;
+};
+
+class MultiContextRenderCase : public RenderCase
+{
+public:
+						MultiContextRenderCase		(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const std::vector<EGLint>& configIds, int numContextsPerApi);
+	virtual				~MultiContextRenderCase		(void);
+
+protected:
+	void				executeForSurface			(tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config);
+
+	virtual void		executeForContexts			(tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config, const std::vector<std::pair<EGLint, tcu::egl::Context*> >& contexts) = DE_NULL;
+
+	int					m_numContextsPerApi;
+};
+
+class RenderConfigIdSet : public NamedConfigIdSet
+{
+public:
+	RenderConfigIdSet (const char* name, const char* description, std::vector<EGLint> configIds, EGLint surfaceTypeMask)
+		: NamedConfigIdSet	(name, description, configIds)
+		, m_surfaceTypeMask	(surfaceTypeMask)
+	{
+	}
+
+	EGLint getSurfaceTypeMask (void) const
+	{
+		return m_surfaceTypeMask;
+	}
+
+private:
+	EGLint m_surfaceTypeMask;
+};
+
+void getDefaultRenderConfigIdSets (std::vector<RenderConfigIdSet>& configSets, const std::vector<eglu::ConfigInfo>& configInfos, const eglu::FilterList& baseFilters);
+
+} // egl
+} // deqp
+
+#endif // _TEGLRENDERCASE_HPP
diff --git a/modules/egl/teglRenderTests.cpp b/modules/egl/teglRenderTests.cpp
new file mode 100644
index 0000000..654004e
--- /dev/null
+++ b/modules/egl/teglRenderTests.cpp
@@ -0,0 +1,1081 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rendering tests for different config and api combinations.
+ * \todo [2013-03-19 pyry] GLES1 and VG support.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglRenderTests.hpp"
+#include "teglRenderCase.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuSurface.hpp"
+
+#include "deRandom.hpp"
+#include "deSharedPtr.hpp"
+#include "deSemaphore.hpp"
+#include "deThread.hpp"
+#include "deString.h"
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <set>
+
+#include <EGL/eglext.h>
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#	define EGL_OPENGL_ES3_BIT_KHR	0x0040
+#endif
+#if !defined(EGL_CONTEXT_MAJOR_VERSION_KHR)
+#	define EGL_CONTEXT_MAJOR_VERSION_KHR EGL_CONTEXT_CLIENT_VERSION
+#endif
+
+#if defined(DEQP_SUPPORT_GLES2)
+#	include <GLES2/gl2.h>
+#elif defined(DEQP_SUPPORT_GLES3)
+#	include <GLES3/gl3.h>
+#endif
+
+#include "rrRenderer.hpp"
+#include "rrFragmentOperations.hpp"
+
+#if defined(DEQP_SUPPORT_GLES2) || defined(DEQP_SUPPORT_GLES3)
+#      include "gluDefs.hpp"
+#else
+       // \todo [pyry] Move renderer to common utils
+       // \note [jarkko] gluDefs is required for GLU_CHECK_MSG
+#      error "Reference renderer requires GLES2 or GLES3 support"
+#endif
+
+namespace deqp
+{
+namespace egl
+{
+
+using std::string;
+using std::vector;
+using std::set;
+
+using tcu::Vec4;
+
+using tcu::TestLog;
+
+static const tcu::Vec4	CLEAR_COLOR		= tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
+static const float		CLEAR_DEPTH		= 1.0f;
+static const int		CLEAR_STENCIL	= 0;
+
+namespace
+{
+
+enum PrimitiveType
+{
+	PRIMITIVETYPE_TRIANGLE = 0,	//!< Triangles, requires 3 coordinates per primitive
+//	PRIMITIVETYPE_POINT,		//!< Points, requires 1 coordinate per primitive (w is used as size)
+//	PRIMITIVETYPE_LINE,			//!< Lines, requires 2 coordinates per primitive
+
+	PRIMITIVETYPE_LAST
+};
+
+enum BlendMode
+{
+	BLENDMODE_NONE = 0,			//!< No blending
+	BLENDMODE_ADDITIVE,			//!< Blending with ONE, ONE
+	BLENDMODE_SRC_OVER,			//!< Blending with SRC_ALPHA, ONE_MINUS_SRC_ALPHA
+
+	BLENDMODE_LAST
+};
+
+enum DepthMode
+{
+	DEPTHMODE_NONE = 0,			//!< No depth test or depth writes
+	DEPTHMODE_LESS,				//!< Depth test with less & depth write
+
+	DEPTHMODE_LAST
+};
+
+enum StencilMode
+{
+	STENCILMODE_NONE = 0,		//!< No stencil test or write
+	STENCILMODE_LEQUAL_INC,		//!< Stencil test with LEQUAL, increment on pass
+
+	STENCILMODE_LAST
+};
+
+struct DrawPrimitiveOp
+{
+	PrimitiveType	type;
+	int				count;
+	vector<Vec4>	positions;
+	vector<Vec4>	colors;
+	BlendMode		blend;
+	DepthMode		depth;
+	StencilMode		stencil;
+	int				stencilRef;
+};
+
+void randomizeDrawOp (de::Random& rnd, DrawPrimitiveOp& drawOp)
+{
+	const int	minStencilRef	= 0;
+	const int	maxStencilRef	= 8;
+	const int	minPrimitives	= 2;
+	const int	maxPrimitives	= 4;
+
+	const float	maxTriOffset	= 1.0f;
+	const float	minDepth		= -1.0f; // \todo [pyry] Reference doesn't support Z clipping yet
+	const float	maxDepth		= 1.0f;
+
+	const float	minRGB			= 0.2f;
+	const float	maxRGB			= 0.9f;
+	const float	minAlpha		= 0.3f;
+	const float	maxAlpha		= 1.0f;
+
+	drawOp.type			= (PrimitiveType)rnd.getInt(0, PRIMITIVETYPE_LAST-1);
+	drawOp.count		= rnd.getInt(minPrimitives, maxPrimitives);
+	drawOp.blend		= (BlendMode)rnd.getInt(0, BLENDMODE_LAST-1);
+	drawOp.depth		= (DepthMode)rnd.getInt(0, DEPTHMODE_LAST-1);
+	drawOp.stencil		= (StencilMode)rnd.getInt(0, STENCILMODE_LAST-1);
+	drawOp.stencilRef	= rnd.getInt(minStencilRef, maxStencilRef);
+
+	if (drawOp.type == PRIMITIVETYPE_TRIANGLE)
+	{
+		drawOp.positions.resize(drawOp.count*3);
+		drawOp.colors.resize(drawOp.count*3);
+
+		for (int triNdx = 0; triNdx < drawOp.count; triNdx++)
+		{
+			const float		cx		= rnd.getFloat(-1.0f, 1.0f);
+			const float		cy		= rnd.getFloat(-1.0f, 1.0f);
+
+			for (int coordNdx = 0; coordNdx < 3; coordNdx++)
+			{
+				tcu::Vec4&	position	= drawOp.positions[triNdx*3 + coordNdx];
+				tcu::Vec4&	color		= drawOp.colors[triNdx*3 + coordNdx];
+
+				position.x()	= cx + rnd.getFloat(-maxTriOffset, maxTriOffset);
+				position.y()	= cy + rnd.getFloat(-maxTriOffset, maxTriOffset);
+				position.z()	= rnd.getFloat(minDepth, maxDepth);
+				position.w()	= 1.0f;
+
+				color.x()		= rnd.getFloat(minRGB, maxRGB);
+				color.y()		= rnd.getFloat(minRGB, maxRGB);
+				color.z()		= rnd.getFloat(minRGB, maxRGB);
+				color.w()		= rnd.getFloat(minAlpha, maxAlpha);
+			}
+		}
+	}
+	else
+		DE_ASSERT(false);
+}
+
+// Reference rendering code
+
+class ReferenceShader : public rr::VertexShader, public rr::FragmentShader
+{
+public:
+	enum
+	{
+		VaryingLoc_Color = 0
+	};
+
+	ReferenceShader ()
+		: rr::VertexShader	(2, 1)		// color and pos in => color out
+		, rr::FragmentShader(1, 1)		// color in => color out
+	{
+		this->rr::VertexShader::m_inputs[0].type		= rr::GENERICVECTYPE_FLOAT;
+		this->rr::VertexShader::m_inputs[1].type		= rr::GENERICVECTYPE_FLOAT;
+
+		this->rr::VertexShader::m_outputs[0].type		= rr::GENERICVECTYPE_FLOAT;
+		this->rr::VertexShader::m_outputs[0].flatshade	= false;
+
+		this->rr::FragmentShader::m_inputs[0].type		= rr::GENERICVECTYPE_FLOAT;
+		this->rr::FragmentShader::m_inputs[0].flatshade	= false;
+
+		this->rr::FragmentShader::m_outputs[0].type		= rr::GENERICVECTYPE_FLOAT;
+	}
+
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			const int positionAttrLoc = 0;
+			const int colorAttrLoc = 1;
+
+			rr::VertexPacket& packet = *packets[packetNdx];
+
+			// Transform to position
+			packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx);
+
+			// Pass color to FS
+			packet.outputs[VaryingLoc_Color] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx);
+		}
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			rr::FragmentPacket& packet = packets[packetNdx];
+
+			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+				rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, VaryingLoc_Color, fragNdx));
+		}
+	}
+};
+
+void toReferenceRenderState (rr::RenderState& state, const DrawPrimitiveOp& drawOp)
+{
+	state.cullMode	= rr::CULLMODE_NONE;
+
+	if (drawOp.blend != BLENDMODE_NONE)
+	{
+		state.fragOps.blendMode = rr::BLENDMODE_STANDARD;
+
+		switch (drawOp.blend)
+		{
+			case BLENDMODE_ADDITIVE:
+				state.fragOps.blendRGBState.srcFunc		= rr::BLENDFUNC_ONE;
+				state.fragOps.blendRGBState.dstFunc		= rr::BLENDFUNC_ONE;
+				state.fragOps.blendRGBState.equation	= rr::BLENDEQUATION_ADD;
+				state.fragOps.blendAState				= state.fragOps.blendRGBState;
+				break;
+
+			case BLENDMODE_SRC_OVER:
+				state.fragOps.blendRGBState.srcFunc		= rr::BLENDFUNC_SRC_ALPHA;
+				state.fragOps.blendRGBState.dstFunc		= rr::BLENDFUNC_ONE_MINUS_SRC_ALPHA;
+				state.fragOps.blendRGBState.equation	= rr::BLENDEQUATION_ADD;
+				state.fragOps.blendAState				= state.fragOps.blendRGBState;
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	if (drawOp.depth != DEPTHMODE_NONE)
+	{
+		state.fragOps.depthTestEnabled = true;
+
+		DE_ASSERT(drawOp.depth == DEPTHMODE_LESS);
+		state.fragOps.depthFunc = rr::TESTFUNC_LESS;
+	}
+
+	if (drawOp.stencil != STENCILMODE_NONE)
+	{
+		state.fragOps.stencilTestEnabled = true;
+
+		DE_ASSERT(drawOp.stencil == STENCILMODE_LEQUAL_INC);
+		state.fragOps.stencilStates[0].func		= rr::TESTFUNC_LEQUAL;
+		state.fragOps.stencilStates[0].sFail	= rr::STENCILOP_KEEP;
+		state.fragOps.stencilStates[0].dpFail	= rr::STENCILOP_INCR;
+		state.fragOps.stencilStates[0].dpPass	= rr::STENCILOP_INCR;
+		state.fragOps.stencilStates[0].ref		= drawOp.stencilRef;
+		state.fragOps.stencilStates[1]			= state.fragOps.stencilStates[0];
+	}
+}
+
+tcu::TextureFormat getColorFormat (const tcu::PixelFormat& colorBits)
+{
+	using tcu::TextureFormat;
+
+	DE_ASSERT(de::inBounds(colorBits.redBits,	0, 0xff) &&
+			  de::inBounds(colorBits.greenBits,	0, 0xff) &&
+			  de::inBounds(colorBits.blueBits,	0, 0xff) &&
+			  de::inBounds(colorBits.alphaBits,	0, 0xff));
+
+#define PACK_FMT(R, G, B, A) (((R) << 24) | ((G) << 16) | ((B) << 8) | (A))
+
+	// \note [pyry] This may not hold true on some implementations - best effort guess only.
+	switch (PACK_FMT(colorBits.redBits, colorBits.greenBits, colorBits.blueBits, colorBits.alphaBits))
+	{
+		case PACK_FMT(8,8,8,8):		return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8);
+		case PACK_FMT(8,8,8,0):		return TextureFormat(TextureFormat::RGB,	TextureFormat::UNORM_INT8);
+		case PACK_FMT(4,4,4,4):		return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_SHORT_4444);
+		case PACK_FMT(5,5,5,1):		return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_SHORT_5551);
+		case PACK_FMT(5,6,5,0):		return TextureFormat(TextureFormat::RGB,	TextureFormat::UNORM_SHORT_565);
+
+		// \note Defaults to RGBA8
+		default:					return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8);
+	}
+
+#undef PACK_FMT
+}
+
+tcu::TextureFormat getDepthFormat (const int depthBits)
+{
+	switch (depthBits)
+	{
+		case 0:		return tcu::TextureFormat();
+		case 8:		return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT8);
+		case 16:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16);
+		case 24:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNSIGNED_INT_24_8);
+		case 32:
+		default:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT);
+	}
+}
+
+tcu::TextureFormat getStencilFormat (int stencilBits)
+{
+	switch (stencilBits)
+	{
+		case 0:		return tcu::TextureFormat();
+		case 8:
+		default:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT8);
+	}
+}
+
+void renderReference (const tcu::PixelBufferAccess& dst, const vector<DrawPrimitiveOp>& drawOps, const tcu::PixelFormat& colorBits, const int depthBits, const int stencilBits, const int numSamples)
+{
+	const int						width			= dst.getWidth();
+	const int						height			= dst.getHeight();
+
+	tcu::TextureLevel				colorBuffer;
+	tcu::TextureLevel				depthBuffer;
+	tcu::TextureLevel				stencilBuffer;
+
+	rr::Renderer					referenceRenderer;
+	rr::VertexAttrib				attributes[2];
+	const ReferenceShader			shader;
+
+	attributes[0].type				= rr::VERTEXATTRIBTYPE_FLOAT;
+	attributes[0].size				= 4;
+	attributes[0].stride			= 0;
+	attributes[0].instanceDivisor	= 0;
+
+	attributes[1].type				= rr::VERTEXATTRIBTYPE_FLOAT;
+	attributes[1].size				= 4;
+	attributes[1].stride			= 0;
+	attributes[1].instanceDivisor	= 0;
+
+	// Initialize buffers.
+	colorBuffer.setStorage(getColorFormat(colorBits), numSamples, width, height);
+	rr::clearMultisampleColorBuffer(colorBuffer, CLEAR_COLOR, rr::WindowRectangle(0, 0, width, height));
+
+	if (depthBits > 0)
+	{
+		depthBuffer.setStorage(getDepthFormat(depthBits), numSamples, width, height);
+		rr::clearMultisampleDepthBuffer(depthBuffer, CLEAR_DEPTH, rr::WindowRectangle(0, 0, width, height));
+	}
+
+	if (stencilBits > 0)
+	{
+		stencilBuffer.setStorage(getStencilFormat(stencilBits), numSamples, width, height);
+		rr::clearMultisampleStencilBuffer(stencilBuffer, CLEAR_STENCIL, rr::WindowRectangle(0, 0, width, height));
+	}
+
+	const rr::RenderTarget renderTarget(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess()),
+										rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthBuffer.getAccess()),
+										rr::MultisamplePixelBufferAccess::fromMultisampleAccess(stencilBuffer.getAccess()));
+
+	for (vector<DrawPrimitiveOp>::const_iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); drawOp++)
+	{
+		// Translate state
+		rr::RenderState renderState((rr::ViewportState)(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess())));
+		toReferenceRenderState(renderState, *drawOp);
+
+		DE_ASSERT(drawOp->type == PRIMITIVETYPE_TRIANGLE);
+
+		attributes[0].pointer = &drawOp->positions[0];
+		attributes[1].pointer = &drawOp->colors[0];
+
+		referenceRenderer.draw(
+			rr::DrawCommand(
+				renderState,
+				renderTarget,
+				rr::Program(static_cast<const rr::VertexShader*>(&shader), static_cast<const rr::FragmentShader*>(&shader)),
+				2,
+				attributes,
+				rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, drawOp->count * 3, 0)));
+	}
+
+	rr::resolveMultisampleColorBuffer(dst, rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess()));
+}
+
+// API rendering code
+
+class Program
+{
+public:
+					Program				(void) {}
+	virtual			~Program			(void) {}
+
+	virtual void	setup				(void) const = DE_NULL;
+};
+
+typedef de::SharedPtr<Program> ProgramSp;
+
+#if defined(DEQP_SUPPORT_GLES2) || defined(DEQP_SUPPORT_GLES3)
+
+static const char* s_vertexSrc =
+	"attribute highp vec4 a_position;\n"
+	"attribute mediump vec4 a_color;\n"
+	"varying mediump vec4 v_color;\n"
+	"void main (void)\n"
+	"{\n"
+	"	gl_Position = a_position;\n"
+	"	v_color = a_color;\n"
+	"}\n";
+
+static const char* s_fragmentSrc =
+	"varying mediump vec4 v_color;\n"
+	"void main (void)\n"
+	"{\n"
+	"	gl_FragColor = v_color;\n"
+	"}\n";
+
+static deUint32 createShader (deUint32 shaderType, const char* source)
+{
+	deUint32 shader = glCreateShader(shaderType);
+	glShaderSource(shader, 1, &source, DE_NULL);
+	glCompileShader(shader);
+
+	int compileStatus = 0;
+	glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
+
+	if (!compileStatus)
+	{
+		glDeleteShader(shader);
+		return 0;
+	}
+
+	return shader;
+}
+
+static deUint32 createProgram (deUint32 vertexShader, deUint32 fragmentShader)
+{
+	deUint32 program = glCreateProgram();
+	glAttachShader(program, vertexShader);
+	glAttachShader(program, fragmentShader);
+	glLinkProgram(program);
+
+	int linkStatus = 0;
+	glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+
+	if (!linkStatus)
+	{
+		glDeleteProgram(program);
+		return 0;
+	}
+
+	return program;
+}
+
+class GLES2Program : public Program
+{
+public:
+	GLES2Program (void)
+		: m_program			(0)
+		, m_vertexShader	(0)
+		, m_fragmentShader	(0)
+		, m_positionLoc		(0)
+		, m_colorLoc		(0)
+	{
+		m_vertexShader		= createShader(GL_VERTEX_SHADER, s_vertexSrc);
+		m_fragmentShader	= createShader(GL_FRAGMENT_SHADER, s_fragmentSrc);
+
+		if (!m_vertexShader || !m_fragmentShader)
+		{
+			glDeleteShader(m_vertexShader);
+			glDeleteShader(m_fragmentShader);
+			throw tcu::TestError("Failed to compile shaders");
+		}
+
+		m_program = createProgram(m_vertexShader, m_fragmentShader);
+		if (!m_program)
+		{
+			glDeleteShader(m_vertexShader);
+			glDeleteShader(m_fragmentShader);
+			throw tcu::TestError("Failed to link program");
+		}
+
+		m_positionLoc	= glGetAttribLocation(m_program, "a_position");
+		m_colorLoc		= glGetAttribLocation(m_program, "a_color");
+	}
+
+	~GLES2Program (void)
+	{
+	}
+
+	void setup (void) const
+	{
+		glUseProgram(m_program);
+		glEnableVertexAttribArray(m_positionLoc);
+		glEnableVertexAttribArray(m_colorLoc);
+		GLU_CHECK_MSG("Program setup failed");
+	}
+
+	int			getPositionLoc		(void) const { return m_positionLoc;	}
+	int			getColorLoc			(void) const { return m_colorLoc;		}
+
+private:
+	deUint32	m_program;
+	deUint32	m_vertexShader;
+	deUint32	m_fragmentShader;
+	int			m_positionLoc;
+	int			m_colorLoc;
+};
+
+void clearGLES2 (const tcu::Vec4& color, const float depth, const int stencil)
+{
+	glClearColor(color.x(), color.y(), color.z(), color.w());
+	glClearDepthf(depth);
+	glClearStencil(stencil);
+	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+}
+
+void drawGLES2 (const Program& program, const DrawPrimitiveOp& drawOp)
+{
+	const GLES2Program& gles2Program = dynamic_cast<const GLES2Program&>(program);
+
+	switch (drawOp.blend)
+	{
+		case BLENDMODE_NONE:
+			glDisable(GL_BLEND);
+			break;
+
+		case BLENDMODE_ADDITIVE:
+			glEnable(GL_BLEND);
+			glBlendFunc(GL_ONE, GL_ONE);
+			break;
+
+		case BLENDMODE_SRC_OVER:
+			glEnable(GL_BLEND);
+			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	switch (drawOp.depth)
+	{
+		case DEPTHMODE_NONE:
+			glDisable(GL_DEPTH_TEST);
+			break;
+
+		case DEPTHMODE_LESS:
+			glEnable(GL_DEPTH_TEST);
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	switch (drawOp.stencil)
+	{
+		case STENCILMODE_NONE:
+			glDisable(GL_STENCIL_TEST);
+			break;
+
+		case STENCILMODE_LEQUAL_INC:
+			glEnable(GL_STENCIL_TEST);
+			glStencilFunc(GL_LEQUAL, drawOp.stencilRef, ~0u);
+			glStencilOp(GL_KEEP, GL_INCR, GL_INCR);
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	glVertexAttribPointer(gles2Program.getPositionLoc(), 4, GL_FLOAT, GL_FALSE, 0, &drawOp.positions[0]);
+	glVertexAttribPointer(gles2Program.getColorLoc(), 4, GL_FLOAT, GL_FALSE, 0, &drawOp.colors[0]);
+
+	DE_ASSERT(drawOp.type == PRIMITIVETYPE_TRIANGLE);
+	glDrawArrays(GL_TRIANGLES, 0, drawOp.count*3);
+}
+
+static void readPixelsGLES2 (tcu::Surface& dst)
+{
+	glReadPixels(0, 0, dst.getWidth(), dst.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr());
+}
+
+#endif
+
+Program* createProgram (EGLint api)
+{
+	switch (api)
+	{
+#if defined(DEQP_SUPPORT_GLES2) || defined(DEQP_SUPPORT_GLES3)
+		case EGL_OPENGL_ES2_BIT:		return new GLES2Program();
+		case EGL_OPENGL_ES3_BIT_KHR:	return new GLES2Program();
+#endif
+		default:
+			throw tcu::NotSupportedError("Unsupported API");
+	}
+}
+
+void draw (EGLint api, const Program& program, const DrawPrimitiveOp& drawOp)
+{
+	switch (api)
+	{
+#if defined(DEQP_SUPPORT_GLES2) || defined(DEQP_SUPPORT_GLES3)
+		case EGL_OPENGL_ES2_BIT:		drawGLES2(program, drawOp);		break;
+		case EGL_OPENGL_ES3_BIT_KHR:	drawGLES2(program, drawOp);		break;
+#endif
+		default:
+			throw tcu::NotSupportedError("Unsupported API");
+	}
+}
+
+void clear (EGLint api, const tcu::Vec4& color, const float depth, const int stencil)
+{
+	switch (api)
+	{
+#if defined(DEQP_SUPPORT_GLES2) || defined(DEQP_SUPPORT_GLES3)
+		case EGL_OPENGL_ES2_BIT:		clearGLES2(color, depth, stencil);		break;
+		case EGL_OPENGL_ES3_BIT_KHR:	clearGLES2(color, depth, stencil);		break;
+#endif
+		default:
+			throw tcu::NotSupportedError("Unsupported API");
+	}
+}
+
+static void readPixels (EGLint api, tcu::Surface& dst)
+{
+	switch (api)
+	{
+#if defined(DEQP_SUPPORT_GLES2) || defined(DEQP_SUPPORT_GLES3)
+		case EGL_OPENGL_ES2_BIT:		readPixelsGLES2(dst);		break;
+		case EGL_OPENGL_ES3_BIT_KHR:	readPixelsGLES2(dst);		break;
+#endif
+		default:
+			throw tcu::NotSupportedError("Unsupported API");
+	}
+}
+
+tcu::PixelFormat getPixelFormat (const tcu::egl::Display& display, EGLConfig config)
+{
+	tcu::PixelFormat fmt;
+	display.describeConfig(config, fmt);
+	return fmt;
+}
+
+} // anonymous
+
+// SingleThreadRenderCase
+
+class SingleThreadRenderCase : public MultiContextRenderCase
+{
+public:
+						SingleThreadRenderCase		(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const std::vector<EGLint>& configIds, int numContextsPerApi);
+
+private:
+	virtual void		executeForContexts			(tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config, const std::vector<std::pair<EGLint, tcu::egl::Context*> >& contexts);
+};
+
+// SingleThreadColorClearCase
+
+SingleThreadRenderCase::SingleThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const std::vector<EGLint>& configIds, int numContextsPerApi)
+	: MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, configIds, numContextsPerApi)
+{
+}
+
+void SingleThreadRenderCase::executeForContexts (tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config, const std::vector<std::pair<EGLint, tcu::egl::Context*> >& contexts)
+{
+	const int				width		= surface.getWidth();
+	const int				height		= surface.getHeight();
+	const int				numContexts	= (int)contexts.size();
+	const int				drawsPerCtx	= 2;
+	const int				numIters	= 2;
+	const float				threshold	= 0.02f;
+
+	const tcu::PixelFormat	pixelFmt	= getPixelFormat(display, config);
+	const int				depthBits	= display.getConfigAttrib(config, EGL_DEPTH_SIZE);
+	const int				stencilBits	= display.getConfigAttrib(config, EGL_STENCIL_SIZE);
+	const int				numSamples	= display.getConfigAttrib(config, EGL_SAMPLES);
+
+	TestLog&				log			= m_testCtx.getLog();
+
+	tcu::Surface			refFrame	(width, height);
+	tcu::Surface			frame		(width, height);
+
+	de::Random				rnd			(deStringHash(getName()) ^ deInt32Hash(numContexts));
+	vector<ProgramSp>		programs	(contexts.size());
+	vector<DrawPrimitiveOp>	drawOps;
+
+	// Log basic information about config.
+	log << TestLog::Message << "EGL_RED_SIZE = "		<< pixelFmt.redBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_GREEN_SIZE = "		<< pixelFmt.greenBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_BLUE_SIZE = "		<< pixelFmt.blueBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_ALPHA_SIZE = "		<< pixelFmt.alphaBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_DEPTH_SIZE = "		<< depthBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_STENCIL_SIZE = "	<< stencilBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_SAMPLES = "			<< numSamples << TestLog::EndMessage;
+
+	// Generate draw ops.
+	drawOps.resize(numContexts*drawsPerCtx*numIters);
+	for (vector<DrawPrimitiveOp>::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp)
+		randomizeDrawOp(rnd, *drawOp);
+
+	// Create and setup programs per context
+	for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++)
+	{
+		EGLint				api			= contexts[ctxNdx].first;
+		tcu::egl::Context*	context		= contexts[ctxNdx].second;
+
+		eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+		TCU_CHECK_EGL();
+
+		programs[ctxNdx] = ProgramSp(createProgram(api));
+		programs[ctxNdx]->setup();
+	}
+
+	// Clear to black using first context.
+	{
+		EGLint				api			= contexts[0].first;
+		tcu::egl::Context*	context		= contexts[0].second;
+
+		eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+		TCU_CHECK_EGL();
+
+		clear(api, CLEAR_COLOR, CLEAR_DEPTH, CLEAR_STENCIL);
+	}
+
+	// Render.
+	for (int iterNdx = 0; iterNdx < numIters; iterNdx++)
+	{
+		for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++)
+		{
+			EGLint				api			= contexts[ctxNdx].first;
+			tcu::egl::Context*	context		= contexts[ctxNdx].second;
+
+			eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+			TCU_CHECK_EGL();
+
+			for (int drawNdx = 0; drawNdx < drawsPerCtx; drawNdx++)
+			{
+				const DrawPrimitiveOp& drawOp = drawOps[iterNdx*numContexts*drawsPerCtx + ctxNdx*drawsPerCtx + drawNdx];
+				draw(api, *programs[ctxNdx], drawOp);
+			}
+		}
+	}
+
+	// Read pixels using first context. \todo [pyry] Randomize?
+	{
+		EGLint				api		= contexts[0].first;
+		tcu::egl::Context*	context	= contexts[0].second;
+
+		eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+		TCU_CHECK_EGL();
+
+		readPixels(api, frame);
+	}
+
+	// Render reference.
+	// \note Reference image is always generated using single-sampling.
+	renderReference(refFrame.getAccess(), drawOps, pixelFmt, depthBits, stencilBits, 1);
+
+	// Compare images
+	{
+		bool imagesOk = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, threshold, tcu::COMPARE_LOG_RESULT);
+
+		if (!imagesOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+	}
+}
+
+// MultiThreadRenderCase
+
+class MultiThreadRenderCase : public MultiContextRenderCase
+{
+public:
+						MultiThreadRenderCase		(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const std::vector<EGLint>& configIds, int numContextsPerApi);
+
+private:
+	virtual void		executeForContexts			(tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config, const std::vector<std::pair<EGLint, tcu::egl::Context*> >& contexts);
+};
+
+class RenderTestThread;
+
+typedef de::SharedPtr<RenderTestThread>	RenderTestThreadSp;
+typedef de::SharedPtr<de::Semaphore>	SemaphoreSp;
+
+struct DrawOpPacket
+{
+	DrawOpPacket (void)
+		: drawOps	(DE_NULL)
+		, numOps	(0)
+	{
+	}
+
+	const DrawPrimitiveOp*	drawOps;
+	int						numOps;
+	SemaphoreSp				wait;
+	SemaphoreSp				signal;
+};
+
+class RenderTestThread : public de::Thread
+{
+public:
+	RenderTestThread (tcu::egl::Display& display, tcu::egl::Surface& surface, tcu::egl::Context& context, EGLint api, const Program& program, const std::vector<DrawOpPacket>& packets)
+		: m_display	(display)
+		, m_surface	(surface)
+		, m_context	(context)
+		, m_api		(api)
+		, m_program	(program)
+		, m_packets	(packets)
+	{
+	}
+
+	void run (void)
+	{
+		for (std::vector<DrawOpPacket>::const_iterator packetIter = m_packets.begin(); packetIter != m_packets.end(); packetIter++)
+		{
+			// Wait until it is our turn.
+			packetIter->wait->decrement();
+
+			// Acquire context.
+			eglMakeCurrent(m_display.getEGLDisplay(), m_surface.getEGLSurface(), m_surface.getEGLSurface(), m_context.getEGLContext());
+
+			// Execute rendering.
+			for (int ndx = 0; ndx < packetIter->numOps; ndx++)
+				draw(m_api, m_program, packetIter->drawOps[ndx]);
+
+			// Release context.
+			eglMakeCurrent(m_display.getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+			// Signal completion.
+			packetIter->signal->increment();
+		}
+	}
+
+private:
+	tcu::egl::Display&					m_display;
+	tcu::egl::Surface&					m_surface;
+	tcu::egl::Context&					m_context;
+	EGLint								m_api;
+	const Program&						m_program;
+	const std::vector<DrawOpPacket>&	m_packets;
+};
+
+MultiThreadRenderCase::MultiThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const std::vector<EGLint>& configIds, int numContextsPerApi)
+	: MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, configIds, numContextsPerApi)
+{
+}
+
+void MultiThreadRenderCase::executeForContexts (tcu::egl::Display& display, tcu::egl::Surface& surface, EGLConfig config, const std::vector<std::pair<EGLint, tcu::egl::Context*> >& contexts)
+{
+	const int				width				= surface.getWidth();
+	const int				height				= surface.getHeight();
+	const int				numContexts			= (int)contexts.size();
+	const int				opsPerPacket		= 2;
+	const int				packetsPerThread	= 2;
+	const int				numThreads			= numContexts;
+	const int				numPackets			= numThreads * packetsPerThread;
+	const float				threshold			= 0.02f;
+
+	const tcu::PixelFormat	pixelFmt			= getPixelFormat(display, config);
+	const int				depthBits			= display.getConfigAttrib(config, EGL_DEPTH_SIZE);
+	const int				stencilBits			= display.getConfigAttrib(config, EGL_STENCIL_SIZE);
+	const int				numSamples			= display.getConfigAttrib(config, EGL_SAMPLES);
+
+	TestLog&				log					= m_testCtx.getLog();
+
+	tcu::Surface			refFrame			(width, height);
+	tcu::Surface			frame				(width, height);
+
+	de::Random				rnd					(deStringHash(getName()) ^ deInt32Hash(numContexts));
+
+	// Resources that need cleanup
+	vector<ProgramSp>				programs	(numContexts);
+	vector<SemaphoreSp>				semaphores	(numPackets+1);
+	vector<DrawPrimitiveOp>			drawOps		(numPackets*opsPerPacket);
+	vector<vector<DrawOpPacket> >	packets		(numThreads);
+	vector<RenderTestThreadSp>		threads		(numThreads);
+
+	// Log basic information about config.
+	log << TestLog::Message << "EGL_RED_SIZE = "		<< pixelFmt.redBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_GREEN_SIZE = "		<< pixelFmt.greenBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_BLUE_SIZE = "		<< pixelFmt.blueBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_ALPHA_SIZE = "		<< pixelFmt.alphaBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_DEPTH_SIZE = "		<< depthBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_STENCIL_SIZE = "	<< stencilBits << TestLog::EndMessage;
+	log << TestLog::Message << "EGL_SAMPLES = "			<< numSamples << TestLog::EndMessage;
+
+	// Initialize semaphores.
+	for (vector<SemaphoreSp>::iterator sem = semaphores.begin(); sem != semaphores.end(); ++sem)
+		*sem = SemaphoreSp(new de::Semaphore(0));
+
+	// Create draw ops.
+	for (vector<DrawPrimitiveOp>::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp)
+		randomizeDrawOp(rnd, *drawOp);
+
+	// Create packets.
+	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
+	{
+		packets[threadNdx].resize(packetsPerThread);
+
+		for (int packetNdx = 0; packetNdx < packetsPerThread; packetNdx++)
+		{
+			DrawOpPacket& packet = packets[threadNdx][packetNdx];
+
+			// Threads take turns with packets.
+			packet.wait		= semaphores[packetNdx*numThreads + threadNdx];
+			packet.signal	= semaphores[packetNdx*numThreads + threadNdx + 1];
+			packet.numOps	= opsPerPacket;
+			packet.drawOps	= &drawOps[(packetNdx*numThreads + threadNdx)*opsPerPacket];
+		}
+	}
+
+	// Create and setup programs per context
+	for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++)
+	{
+		EGLint				api			= contexts[ctxNdx].first;
+		tcu::egl::Context*	context		= contexts[ctxNdx].second;
+
+		eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+		TCU_CHECK_EGL();
+
+		programs[ctxNdx] = ProgramSp(createProgram(api));
+		programs[ctxNdx]->setup();
+
+		// Release context
+		eglMakeCurrent(display.getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+	}
+
+	// Clear to black using first context.
+	{
+		EGLint				api			= contexts[0].first;
+		tcu::egl::Context*	context		= contexts[0].second;
+
+		eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+		TCU_CHECK_EGL();
+
+		clear(api, CLEAR_COLOR, CLEAR_DEPTH, CLEAR_STENCIL);
+
+		// Release context
+		eglMakeCurrent(display.getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+	}
+
+	// Create and launch threads (actual rendering starts once first semaphore is signaled).
+	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
+	{
+		threads[threadNdx] = RenderTestThreadSp(new RenderTestThread(display, surface, *contexts[threadNdx].second, contexts[threadNdx].first, *programs[threadNdx], packets[threadNdx]));
+		threads[threadNdx]->start();
+	}
+
+	// Signal start and wait until complete.
+	semaphores.front()->increment();
+	semaphores.back()->decrement();
+
+	// Read pixels using first context. \todo [pyry] Randomize?
+	{
+		EGLint				api		= contexts[0].first;
+		tcu::egl::Context*	context	= contexts[0].second;
+
+		eglMakeCurrent(display.getEGLDisplay(), surface.getEGLSurface(), surface.getEGLSurface(), context->getEGLContext());
+		TCU_CHECK_EGL();
+
+		readPixels(api, frame);
+	}
+
+	// Join threads.
+	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
+		threads[threadNdx]->join();
+
+	// Render reference.
+	renderReference(refFrame.getAccess(), drawOps, pixelFmt, depthBits, stencilBits, 1);
+
+	// Compare images
+	{
+		bool imagesOk = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, threshold, tcu::COMPARE_LOG_RESULT);
+
+		if (!imagesOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+	}
+}
+
+RenderTests::RenderTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "render", "Basic rendering with different client APIs")
+{
+}
+
+RenderTests::~RenderTests (void)
+{
+}
+
+struct RenderGroupSpec
+{
+	const char*		name;
+	const char*		desc;
+	EGLint			apiBits;
+	int				numContextsPerApi;
+};
+
+template <class RenderClass>
+static void createRenderGroups (EglTestContext& eglTestCtx, tcu::TestCaseGroup* group, const RenderGroupSpec* first, const RenderGroupSpec* last)
+{
+	for (const RenderGroupSpec* groupIter = first; groupIter != last; groupIter++)
+	{
+		tcu::TestCaseGroup* configGroup = new tcu::TestCaseGroup(eglTestCtx.getTestContext(), groupIter->name, groupIter->desc);
+		group->addChild(configGroup);
+
+		vector<RenderConfigIdSet>	configSets;
+		eglu::FilterList			filters;
+		filters << (eglu::ConfigRenderableType() & groupIter->apiBits);
+		getDefaultRenderConfigIdSets(configSets, eglTestCtx.getConfigs(), filters);
+
+		for (vector<RenderConfigIdSet>::const_iterator setIter = configSets.begin(); setIter != configSets.end(); setIter++)
+			configGroup->addChild(new RenderClass(eglTestCtx, setIter->getName(), "", groupIter->apiBits, setIter->getSurfaceTypeMask(), setIter->getConfigIds(), groupIter->numContextsPerApi));
+	}
+}
+
+void RenderTests::init (void)
+{
+	static const RenderGroupSpec singleContextCases[] =
+	{
+//		{ "gles1",			"Primitive rendering using GLES1",												EGL_OPENGL_ES_BIT,										1 },
+		{ "gles2",			"Primitive rendering using GLES2",												EGL_OPENGL_ES2_BIT,										1 },
+		{ "gles3",			"Primitive rendering using GLES3",												EGL_OPENGL_ES3_BIT_KHR,									1 },
+//		{ "vg",				"Primitive rendering using OpenVG",												EGL_OPENVG_BIT,											1 }
+	};
+
+	static const RenderGroupSpec multiContextCases[] =
+	{
+//		{ "gles1",				"Primitive rendering using multiple GLES1 contexts to shared surface",		EGL_OPENGL_ES_BIT,												3 },
+		{ "gles2",				"Primitive rendering using multiple GLES2 contexts to shared surface",		EGL_OPENGL_ES2_BIT,												3 },
+		{ "gles3",				"Primitive rendering using multiple GLES3 contexts to shared surface",		EGL_OPENGL_ES3_BIT_KHR,											3 },
+//		{ "vg",					"Primitive rendering using multiple OpenVG contexts to shared surface",		EGL_OPENVG_BIT,													3 },
+//		{ "gles1_gles2",		"Primitive rendering using multiple APIs to shared surface",				EGL_OPENGL_ES_BIT|EGL_OPENGL_ES2_BIT,							1 },
+//		{ "gles1_gles2_gles3",	"Primitive rendering using multiple APIs to shared surface",				EGL_OPENGL_ES_BIT|EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT_KHR,	1 },
+		{ "gles2_gles3",		"Primitive rendering using multiple APIs to shared surface",				EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT_KHR,						1 },
+//		{ "gles1_vg",			"Primitive rendering using multiple APIs to shared surface",				EGL_OPENGL_ES_BIT|EGL_OPENVG_BIT,								1 },
+//		{ "gles2_vg",			"Primitive rendering using multiple APIs to shared surface",				EGL_OPENGL_ES2_BIT|EGL_OPENVG_BIT,								1 },
+//		{ "gles3_vg",			"Primitive rendering using multiple APIs to shared surface",				EGL_OPENGL_ES3_BIT_KHR|EGL_OPENVG_BIT,							1 },
+//		{ "gles1_gles2_vg",		"Primitive rendering using multiple APIs to shared surface",				EGL_OPENGL_ES_BIT|EGL_OPENGL_ES2_BIT|EGL_OPENVG_BIT,			1 }
+	};
+
+	tcu::TestCaseGroup* singleContextGroup = new tcu::TestCaseGroup(m_testCtx, "single_context", "Single-context rendering");
+	addChild(singleContextGroup);
+	createRenderGroups<SingleThreadRenderCase>(m_eglTestCtx, singleContextGroup, &singleContextCases[0], &singleContextCases[DE_LENGTH_OF_ARRAY(singleContextCases)]);
+
+	tcu::TestCaseGroup* multiContextGroup = new tcu::TestCaseGroup(m_testCtx, "multi_context", "Multi-context rendering with shared surface");
+	addChild(multiContextGroup);
+	createRenderGroups<SingleThreadRenderCase>(m_eglTestCtx, multiContextGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]);
+
+	tcu::TestCaseGroup* multiThreadGroup = new tcu::TestCaseGroup(m_testCtx, "multi_thread", "Multi-thread rendering with shared surface");
+	addChild(multiThreadGroup);
+	createRenderGroups<MultiThreadRenderCase>(m_eglTestCtx, multiThreadGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]);
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglRenderTests.hpp b/modules/egl/teglRenderTests.hpp
new file mode 100644
index 0000000..7750851
--- /dev/null
+++ b/modules/egl/teglRenderTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TEGLRENDERTESTS_HPP
+#define _TEGLRENDERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rendering tests for different config and api combinations.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class RenderTests : public TestCaseGroup
+{
+public:
+							RenderTests					(EglTestContext& eglTestCtx);
+	virtual					~RenderTests				(void);
+
+	void					init						(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLRENDERTESTS_HPP
diff --git a/modules/egl/teglResizeTests.cpp b/modules/egl/teglResizeTests.cpp
new file mode 100644
index 0000000..1fb1070
--- /dev/null
+++ b/modules/egl/teglResizeTests.cpp
@@ -0,0 +1,538 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tests for resizing the native window of a surface.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglResizeTests.hpp"
+
+#include "teglSimpleConfigCase.hpp"
+
+#include "tcuImageCompare.hpp"
+#include "tcuSurface.hpp"
+#include "tcuPlatform.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuInterval.hpp"
+
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+#include "egluUnique.hpp"
+#include "egluUtil.hpp"
+
+#include "gluDefs.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+
+#include "deThread.h"
+#include "deUniquePtr.hpp"
+
+#include <sstream>
+
+namespace deqp
+{
+namespace egl
+{
+
+using	std::vector;
+using	std::string;
+using	std::ostringstream;
+using	de::MovePtr;
+using	tcu::CommandLine;
+using	tcu::ConstPixelBufferAccess;
+using	tcu::Interval;
+using	tcu::IVec2;
+using	tcu::Vec3;
+using	tcu::Vec4;
+using	tcu::UVec4;
+using	tcu::ResultCollector;
+using	tcu::Surface;
+using	tcu::TestLog;
+using	tcu::egl::Display;
+using	eglu::AttribMap;
+using	eglu::NativeDisplay;
+using	eglu::NativeWindow;
+using	eglu::ScopedCurrentContext;
+using	eglu::UniqueSurface;
+using	eglu::UniqueContext;
+
+typedef	eglu::WindowParams::Visibility	Visibility;
+typedef	TestCase::IterateResult			IterateResult;
+
+struct ResizeParams
+{
+	string	name;
+	string	description;
+	IVec2	oldSize;
+	IVec2	newSize;
+};
+
+class ResizeTest : public TestCase
+{
+public:
+								ResizeTest	(EglTestContext&		eglTestCtx,
+											 const ResizeParams&	params)
+									: TestCase	(eglTestCtx,
+												 params.name.c_str(),
+												 params.description.c_str())
+									, m_oldSize	(params.oldSize)
+									, m_newSize	(params.newSize)
+									, m_display	(EGL_NO_DISPLAY)
+									, m_config	(DE_NULL)
+									, m_log		(m_testCtx.getLog())
+									, m_status	(m_log) {}
+
+	void						init		(void);
+	void						deinit		(void);
+
+protected:
+	virtual EGLenum				surfaceType	(void) const { return EGL_WINDOW_BIT; }
+	void						resize		(IVec2 size);
+
+	const IVec2					m_oldSize;
+	const IVec2					m_newSize;
+	EGLDisplay					m_display;
+	EGLConfig					m_config;
+	MovePtr<NativeWindow>		m_nativeWindow;
+	MovePtr<UniqueSurface>		m_surface;
+	MovePtr<UniqueContext>		m_context;
+	TestLog&					m_log;
+	ResultCollector				m_status;
+	glw::Functions				m_gl;
+};
+
+EGLConfig getEGLConfig (const EGLDisplay eglDisplay, EGLenum surfaceType)
+{
+	AttribMap attribMap;
+
+	attribMap[EGL_SURFACE_TYPE]		= surfaceType;
+	attribMap[EGL_RENDERABLE_TYPE]	= EGL_OPENGL_ES2_BIT;
+
+	return eglu::chooseSingleConfig(eglDisplay, attribMap);
+}
+
+void ResizeTest::init (void)
+{
+	TestCase::init();
+
+	const CommandLine&		cmdLine			= m_testCtx.getCommandLine();
+	const EGLDisplay		eglDisplay		= m_eglTestCtx.getDisplay().getEGLDisplay();
+	const EGLConfig			eglConfig		= getEGLConfig(eglDisplay, surfaceType());
+	const EGLint			ctxAttribs[]	=
+	{
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+	EGLContext				eglContext		= eglCreateContext(eglDisplay,
+															   eglConfig,
+															   EGL_NO_CONTEXT,
+															   ctxAttribs);
+	EGLU_CHECK_MSG("eglCreateContext()");
+	MovePtr<UniqueContext>	context			(new UniqueContext(eglDisplay, eglContext));
+	const EGLint			configId		= eglu::getConfigAttribInt(eglDisplay,
+																	   eglConfig,
+																	   EGL_CONFIG_ID);
+	const Visibility		visibility		= eglu::parseWindowVisibility(cmdLine);
+	NativeDisplay&			nativeDisplay	= m_eglTestCtx.getNativeDisplay();
+	MovePtr<NativeWindow>	nativeWindow	(m_eglTestCtx.createNativeWindow(eglDisplay,
+																			 eglConfig,
+																			 DE_NULL,
+																			 m_oldSize.x(),
+																			 m_oldSize.y(),
+																			 visibility));
+	const EGLSurface		eglSurface		= eglu::createWindowSurface(nativeDisplay,
+																		*nativeWindow,
+																		eglDisplay,
+																		eglConfig,
+																		DE_NULL);
+	MovePtr<UniqueSurface>	surface			(new UniqueSurface(eglDisplay, eglSurface));
+
+	m_log << TestLog::Message
+		  << "Chose EGLConfig with id " << configId << ".\n"
+		  << "Created initial surface with size " << m_oldSize
+		  << TestLog::EndMessage;
+
+	m_eglTestCtx.getGLFunctions(m_gl, glu::ApiType::es(2, 0));
+	m_config		= eglConfig;
+	m_surface		= surface;
+	m_context		= context;
+	m_display		= eglDisplay;
+	m_nativeWindow	= nativeWindow;
+	EGLU_CHECK();
+}
+
+void ResizeTest::deinit (void)
+{
+	m_config		= DE_NULL;
+	m_display		= EGL_NO_DISPLAY;
+	m_context.clear();
+	m_surface.clear();
+	m_nativeWindow.clear();
+}
+
+void ResizeTest::resize (IVec2 size)
+{
+	m_nativeWindow->setSurfaceSize(size);
+	m_testCtx.getPlatform().processEvents();
+	m_log << TestLog::Message
+		  << "Resized surface to size " << size
+		  << TestLog::EndMessage;
+}
+
+class ChangeSurfaceSizeCase : public ResizeTest
+{
+public:
+					ChangeSurfaceSizeCase	(EglTestContext&		eglTestCtx,
+											 const ResizeParams&	params)
+						: ResizeTest(eglTestCtx, params) {}
+
+	IterateResult	iterate					(void);
+};
+
+void drawRectangle (const glw::Functions& gl, IVec2 pos, IVec2 size, Vec3 color)
+{
+	gl.clearColor(color.x(), color.y(), color.z(), 1.0);
+	gl.scissor(pos.x(), pos.y(), size.x(), size.y());
+	gl.enable(GL_SCISSOR_TEST);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	gl.disable(GL_SCISSOR_TEST);
+	GLU_EXPECT_NO_ERROR(gl.getError(),
+						"Rectangle drawing with glScissor and glClear failed.");
+}
+
+void initSurface (const glw::Functions& gl, IVec2 oldSize)
+{
+	const Vec3	frameColor	(0.0f, 0.0f, 1.0f);
+	const Vec3	fillColor	(1.0f, 0.0f, 0.0f);
+	const Vec3	markColor	(0.0f, 1.0f, 0.0f);
+
+	drawRectangle(gl, IVec2(0, 0), oldSize, frameColor);
+	drawRectangle(gl, IVec2(2, 2), oldSize - IVec2(4, 4), fillColor);
+
+	drawRectangle(gl, IVec2(0, 0), IVec2(8, 4), markColor);
+	drawRectangle(gl, oldSize - IVec2(16, 16), IVec2(8, 4), markColor);
+	drawRectangle(gl, IVec2(0, oldSize.y() - 16), IVec2(8, 4), markColor);
+	drawRectangle(gl, IVec2(oldSize.x() - 16, 0), IVec2(8, 4), markColor);
+}
+
+bool compareRectangles (const ConstPixelBufferAccess& rectA,
+						const ConstPixelBufferAccess& rectB)
+{
+	const int width		= rectA.getWidth();
+	const int height	= rectA.getHeight();
+	const int depth		= rectA.getDepth();
+
+	if (rectB.getWidth() != width || rectB.getHeight() != height || rectB.getDepth() != depth)
+		return false;
+
+	for (int z = 0; z < depth; ++z)
+		for (int y = 0; y < height; ++y)
+			for (int x = 0; x < width; ++x)
+				if (rectA.getPixel(x, y, z) != rectB.getPixel(x, y, z))
+					return false;
+
+	return true;
+}
+
+// Check whether `oldSurface` and `newSurface` share a common corner.
+bool compareCorners (const Surface& oldSurface, const Surface& newSurface)
+{
+	const int	oldWidth	= oldSurface.getWidth();
+	const int	oldHeight	= oldSurface.getHeight();
+	const int	newWidth	= newSurface.getWidth();
+	const int	newHeight	= newSurface.getHeight();
+	const int	minWidth	= de::min(oldWidth, newWidth);
+	const int	minHeight	= de::min(oldHeight, newHeight);
+
+	for (int xCorner = 0; xCorner < 2; ++xCorner)
+	{
+		const int oldX = xCorner == 0 ? 0 : oldWidth - minWidth;
+		const int newX = xCorner == 0 ? 0 : newWidth - minWidth;
+
+		for (int yCorner = 0; yCorner < 2; ++yCorner)
+		{
+			const int				oldY		= yCorner == 0 ? 0 : oldHeight - minHeight;
+			const int				newY		= yCorner == 0 ? 0 : newHeight - minHeight;
+			ConstPixelBufferAccess	oldAccess	=
+				oldSurface.getSubAccess(oldX, oldY, minWidth, minHeight);
+			ConstPixelBufferAccess	newAccess	=
+				newSurface.getSubAccess(newX, newY, minWidth, minHeight);
+
+			if (compareRectangles(oldAccess, newAccess))
+				return true;
+		}
+	}
+
+	return false;
+}
+
+Surface readSurface (const glw::Functions& gl, IVec2 size)
+{
+	Surface ret (size.x(), size.y());
+	gl.readPixels(0, 0, size.x(), size.y(), GL_RGBA, GL_UNSIGNED_BYTE,
+				  ret.getAccess().getDataPtr());
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels() failed");
+	return ret;
+}
+
+template <typename T>
+inline bool hasBits (T bitSet, T requiredBits)
+{
+	return (bitSet & requiredBits) == requiredBits;
+}
+
+IVec2 getNativeSurfaceSize (const NativeWindow& nativeWindow,
+							IVec2				reqSize)
+{
+	if (hasBits(nativeWindow.getCapabilities(), NativeWindow::CAPABILITY_GET_SURFACE_SIZE))
+		return nativeWindow.getSurfaceSize();
+	return reqSize; // assume we got the requested size
+}
+
+IVec2 checkSurfaceSize (EGLDisplay			eglDisplay,
+						EGLSurface			eglSurface,
+						const NativeWindow&	nativeWindow,
+						IVec2				reqSize,
+						ResultCollector&	status)
+{
+	const IVec2		nativeSize	= getNativeSurfaceSize(nativeWindow, reqSize);
+	IVec2			eglSize		= eglu::getSurfaceSize(eglDisplay, eglSurface);
+	ostringstream	oss;
+
+	oss << "Size of EGL surface " << eglSize
+		<< " differs from size of native window " << nativeSize;
+	status.check(eglSize == nativeSize, oss.str());
+
+	return eglSize;
+}
+
+IterateResult ChangeSurfaceSizeCase::iterate (void)
+{
+	EGLU_CHECK();
+	Surface					oldSurface;
+	Surface					newSurface;
+	ScopedCurrentContext	currentCtx	(m_display, **m_surface, **m_surface, **m_context);
+	IVec2					oldEglSize	= checkSurfaceSize(m_display,
+														   **m_surface,
+														   *m_nativeWindow,
+														   m_oldSize,
+														   m_status);
+
+	initSurface(m_gl, oldEglSize);
+
+	this->resize(m_newSize);
+
+	eglSwapBuffers(m_display, **m_surface);
+	checkSurfaceSize(m_display, **m_surface, *m_nativeWindow, m_newSize, m_status);
+
+	m_status.setTestContextResult(m_testCtx);
+	return STOP;
+}
+
+class PreserveBackBufferCase : public ResizeTest
+{
+public:
+					PreserveBackBufferCase	(EglTestContext&		eglTestCtx,
+											 const ResizeParams&	params)
+						: ResizeTest(eglTestCtx, params) {}
+
+	IterateResult	iterate					(void);
+	EGLenum			surfaceType				(void) const;
+};
+
+EGLenum PreserveBackBufferCase::surfaceType (void) const
+{
+	return EGL_WINDOW_BIT | EGL_SWAP_BEHAVIOR_PRESERVED_BIT;
+}
+
+IterateResult PreserveBackBufferCase::iterate (void)
+{
+	ScopedCurrentContext	currentCtx	(m_display, **m_surface, **m_surface, **m_context);
+
+	EGLU_CHECK_CALL(
+		eglSurfaceAttrib(m_display, **m_surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED));
+
+	GLU_EXPECT_NO_ERROR(m_gl.getError(), "GL state erroneous upon initialization!");
+
+	{
+		const IVec2 oldEglSize = eglu::getSurfaceSize(m_display, **m_surface);
+		initSurface(m_gl, oldEglSize);
+
+		m_gl.finish();
+		GLU_EXPECT_NO_ERROR(m_gl.getError(), "glFinish() failed");
+
+		{
+			const Surface oldSurface = readSurface(m_gl, oldEglSize);
+
+			eglSwapBuffers(m_display, **m_surface);
+			this->resize(m_newSize);
+			eglSwapBuffers(m_display, **m_surface);
+
+			{
+				const IVec2		newEglSize	= eglu::getSurfaceSize(m_display, **m_surface);
+				const Surface	newSurface	= readSurface(m_gl, newEglSize);
+
+				m_log << TestLog::ImageSet("Corner comparison",
+										   "Comparing old and new surfaces at all corners")
+					  << TestLog::Image("Before resizing", "Before resizing", oldSurface)
+					  << TestLog::Image("After resizing", "After resizing", newSurface)
+					  << TestLog::EndImageSet;
+
+				m_status.check(compareCorners(oldSurface, newSurface),
+							   "Resizing the native window changed the contents of "
+							   "the EGL surface");
+			}
+		}
+	}
+
+	m_status.setTestContextResult(m_testCtx);
+	return STOP;
+}
+
+typedef tcu::Vector<Interval, 2> IvVec2;
+
+class UpdateResolutionCase : public ResizeTest
+{
+public:
+					UpdateResolutionCase	(EglTestContext&		eglTestCtx,
+											 const ResizeParams&	params)
+						: ResizeTest(eglTestCtx, params) {}
+
+	IterateResult	iterate					(void);
+
+private:
+	IvVec2			getNativePixelsPerInch	(void);
+};
+
+IvVec2 ivVec2 (const IVec2& vec)
+{
+	return IvVec2(double(vec.x()), double(vec.y()));
+}
+
+Interval approximateInt (int i)
+{
+	return (Interval(i) + Interval(-1.0, 1.0)) & Interval(0.0, TCU_INFINITY);
+}
+
+IvVec2 UpdateResolutionCase::getNativePixelsPerInch	(void)
+{
+	const int		inchPer10km	= 254 * EGL_DISPLAY_SCALING;
+	const IVec2		bufSize		= eglu::getSurfaceSize(m_display, **m_surface);
+	const IVec2		winSize		= m_nativeWindow->getScreenSize();
+	const IVec2		bufPp10km	= eglu::getSurfaceResolution(m_display, **m_surface);
+	const Interval	margin		(-1.0, 1.0); // The resolution may be rounded
+	const IvVec2	bufPpiI		= (IvVec2(approximateInt(bufPp10km.x()),
+										  approximateInt(bufPp10km.y()))
+								   / Interval(inchPer10km));
+	const IvVec2	winPpiI		= ivVec2(winSize) * bufPpiI / ivVec2(bufSize);
+	const IVec2		winPpi		(int(winPpiI.x().midpoint()), int(winPpiI.y().midpoint()));
+
+	m_log << TestLog::Message
+		  << "EGL surface size: "							<< bufSize		<< "\n"
+		  << "EGL surface pixel density (pixels / 10 km): "	<< bufPp10km	<< "\n"
+		  << "Native window size: "							<< winSize		<< "\n"
+		  << "Native pixel density (ppi): "					<< winPpi		<< "\n"
+		  << TestLog::EndMessage;
+
+	m_status.checkResult(bufPp10km.x() >= 1 && bufPp10km.y() >= 1,
+						 QP_TEST_RESULT_QUALITY_WARNING,
+						 "Surface pixel density is less than one pixel per 10 km. "
+						 "Is the surface really visible from space?");
+
+	return winPpiI;
+}
+
+IterateResult UpdateResolutionCase::iterate (void)
+{
+	ScopedCurrentContext	currentCtx	(m_display, **m_surface, **m_surface, **m_context);
+
+	if (!hasBits(m_nativeWindow->getCapabilities(),
+				 NativeWindow::CAPABILITY_GET_SCREEN_SIZE))
+		TCU_THROW(NotSupportedError, "Unable to determine surface size in screen pixels");
+
+	{
+		const IVec2 oldEglSize = eglu::getSurfaceSize(m_display, **m_surface);
+		initSurface(m_gl, oldEglSize);
+	}
+	{
+		const IvVec2 oldPpi = this->getNativePixelsPerInch();
+		this->resize(m_newSize);
+		eglSwapBuffers(m_display, **m_surface);
+		{
+			const IvVec2 newPpi = this->getNativePixelsPerInch();
+			m_status.check(oldPpi.x().intersects(newPpi.x()) &&
+						   oldPpi.y().intersects(newPpi.y()),
+						   "Window PPI differs after resizing");
+		}
+	}
+
+	m_status.setTestContextResult(m_testCtx);
+	return STOP;
+}
+
+ResizeTests::ResizeTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "resize", "Tests resizing the native surface")
+{
+}
+
+template <class Case>
+TestCaseGroup* createCaseGroup(EglTestContext&	eglTestCtx,
+							   const string&	name,
+							   const string&	desc)
+{
+	const ResizeParams		params[]	=
+	{
+		{ "shrink",			"Shrink in both dimensions",
+		  IVec2(128, 128),	IVec2(32, 32) },
+		{ "grow",			"Grow in both dimensions",
+		  IVec2(32, 32),	IVec2(128, 128) },
+		{ "stretch_width",	"Grow horizontally, shrink vertically",
+		  IVec2(32, 128),	IVec2(128, 32) },
+		{ "stretch_height",	"Grow vertically, shrink horizontally",
+		  IVec2(128, 32),	IVec2(32, 128) },
+	};
+	TestCaseGroup* const	group		=
+		new TestCaseGroup(eglTestCtx, name.c_str(), desc.c_str());
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(params); ++ndx)
+		group->addChild(new Case(eglTestCtx, params[ndx]));
+
+	return group;
+}
+
+void ResizeTests::init (void)
+{
+	addChild(createCaseGroup<ChangeSurfaceSizeCase>(m_eglTestCtx,
+													"surface_size",
+													"EGL surface size update"));
+	addChild(createCaseGroup<PreserveBackBufferCase>(m_eglTestCtx,
+													 "back_buffer",
+													 "Back buffer contents"));
+	addChild(createCaseGroup<UpdateResolutionCase>(m_eglTestCtx,
+												   "pixel_density",
+												   "Pixel density"));
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglResizeTests.hpp b/modules/egl/teglResizeTests.hpp
new file mode 100644
index 0000000..48b86e8
--- /dev/null
+++ b/modules/egl/teglResizeTests.hpp
@@ -0,0 +1,48 @@
+#ifndef _TEGLRESIZETESTS_HPP
+#define _TEGLRESIZETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tests for resizing the native window of a surface.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class ResizeTests : public TestCaseGroup
+{
+public:
+					ResizeTests		(EglTestContext& eglTestCtx);
+	void			init			(void);
+
+private:
+					ResizeTests		(const ResizeTests&);
+	ResizeTests&	operator=		(const ResizeTests&);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLRESIZETESTS_HPP
diff --git a/modules/egl/teglSimpleConfigCase.cpp b/modules/egl/teglSimpleConfigCase.cpp
new file mode 100644
index 0000000..d11232f
--- /dev/null
+++ b/modules/egl/teglSimpleConfigCase.cpp
@@ -0,0 +1,229 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Simple Context construction test.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglSimpleConfigCase.hpp"
+#include "deStringUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuFormatUtil.hpp"
+
+#include <map>
+#include <set>
+#include <algorithm>
+
+namespace deqp
+{
+namespace egl
+{
+
+using std::map;
+using std::set;
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using eglu::ConfigInfo;
+
+SimpleConfigCase::SimpleConfigCase (EglTestContext& eglTestCtx, const char* name, const char* description, const vector<EGLint>& configIds)
+	: TestCase		(eglTestCtx, name, description)
+	, m_configIds	(configIds)
+{
+}
+
+SimpleConfigCase::~SimpleConfigCase (void)
+{
+}
+
+void SimpleConfigCase::init (void)
+{
+	const tcu::egl::Display& display = m_eglTestCtx.getDisplay();
+
+	// Log matching configs.
+	m_testCtx.getLog() << TestLog::Message << "Matching configs: " << tcu::formatArray(m_configIds.begin(), m_configIds.end()) << TestLog::EndMessage;
+
+	// Config id set.
+	set<EGLint> idSet(m_configIds.begin(), m_configIds.end());
+
+	if (idSet.size() != m_configIds.size())
+	{
+		DE_ASSERT(idSet.size() < m_configIds.size());
+		m_testCtx.getLog() << tcu::TestLog::Message << "Warning: Duplicate config IDs in list" << TestLog::EndMessage;
+	}
+
+	// Get all configs
+	vector<EGLConfig> allConfigs;
+	display.getConfigs(allConfigs);
+
+	// Collect list of configs with matching IDs
+	m_configs.clear();
+	for (vector<EGLConfig>::const_iterator cfgIter = allConfigs.begin(); cfgIter != allConfigs.end(); ++cfgIter)
+	{
+		const EGLint	configId	= display.getConfigAttrib(*cfgIter, EGL_CONFIG_ID);
+		const bool		isInSet		= idSet.find(configId) != idSet.end();
+
+		if (isInSet)
+			m_configs.push_back(*cfgIter);
+	}
+
+	if (m_configs.empty())
+	{
+		// If no compatible configs are found, it is reported as NotSupported
+		throw tcu::NotSupportedError("No compatible configs found");
+	}
+
+	// Init config iter
+	m_configIter = m_configs.begin();
+
+	// Init test case result to Pass
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+SimpleConfigCase::IterateResult SimpleConfigCase::iterate (void)
+{
+	DE_ASSERT(m_configIter != m_configs.end());
+
+	tcu::egl::Display&	display	= m_eglTestCtx.getDisplay();
+	EGLConfig			config	= *m_configIter++;
+
+	try
+	{
+		executeForConfig(display, config);
+	}
+	catch (const tcu::TestError& e)
+	{
+		m_testCtx.getLog() << e;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	}
+	// \note Other errors are handled by framework (resource / internal errors).
+
+	return (m_configIter != m_configs.end()) ? CONTINUE : STOP;
+}
+
+namespace
+{
+
+void addConfigId (map<string, NamedConfigIdSet*>& setMap, const char* name, const ConfigInfo& info)
+{
+	DE_ASSERT(setMap.find(name) != setMap.end());
+	setMap[name]->getConfigIds().push_back(info.configId);
+}
+
+bool filterConfigStencil (map<string, NamedConfigIdSet*>& setMap, const char* namePrefix, const ConfigInfo& info)
+{
+	if (info.stencilSize > 0)
+		addConfigId(setMap, (string(namePrefix) + "stencil").c_str(), info);
+	else
+		addConfigId(setMap, (string(namePrefix) + "no_stencil").c_str(), info);
+	return true;
+}
+
+bool filterConfigDepth (map<string, NamedConfigIdSet*>& setMap, const char* namePrefix, const ConfigInfo& info)
+{
+	if (info.depthSize > 0)
+		return filterConfigStencil(setMap, (string(namePrefix) + "depth_").c_str(), info);
+	else
+		return filterConfigStencil(setMap, (string(namePrefix) + "no_depth_").c_str(), info);
+}
+
+bool filterConfigColor (map<string, NamedConfigIdSet*>& setMap, const char* namePrefix, const ConfigInfo& info)
+{
+	static const struct
+	{
+		const char*	name;
+		int			red, green, blue, alpha;
+	}
+	colorRules[] =
+	{
+		{ "rgb565",		5, 6, 5, 0 },
+		{ "rgb888",		8, 8, 8, 0 },
+		{ "rgba4444",	4, 4, 4, 4 },
+		{ "rgba5551",	5, 5, 5, 1 },
+		{ "rgba8888",	8, 8, 8, 8 }
+	};
+	for (int ndx = 0; ndx < (int)DE_LENGTH_OF_ARRAY(colorRules); ndx++)
+	{
+		if (info.redSize	== colorRules[ndx].red		&&
+			info.greenSize	== colorRules[ndx].green	&&
+			info.blueSize	== colorRules[ndx].blue		&&
+			info.alphaSize	== colorRules[ndx].alpha)
+			return filterConfigDepth(setMap, (string(namePrefix) + colorRules[ndx].name + "_").c_str(), info);
+	}
+
+	return false; // Didn't match any
+}
+
+} // anonymous
+
+void NamedConfigIdSet::getDefaultSets (vector<NamedConfigIdSet>& configSets, const vector<ConfigInfo>& configInfos, const eglu::FilterList& baseFilters)
+{
+	// Set list
+	configSets.push_back(NamedConfigIdSet("rgb565_no_depth_no_stencil",		"RGB565 configs without depth or stencil"));
+	configSets.push_back(NamedConfigIdSet("rgb565_no_depth_stencil",		"RGB565 configs with stencil and no depth"));
+	configSets.push_back(NamedConfigIdSet("rgb565_depth_no_stencil",		"RGB565 configs with depth and no stencil"));
+	configSets.push_back(NamedConfigIdSet("rgb565_depth_stencil",			"RGB565 configs with depth and stencil"));
+	configSets.push_back(NamedConfigIdSet("rgb888_no_depth_no_stencil",		"RGB888 configs without depth or stencil"));
+	configSets.push_back(NamedConfigIdSet("rgb888_no_depth_stencil",		"RGB888 configs with stencil and no depth"));
+	configSets.push_back(NamedConfigIdSet("rgb888_depth_no_stencil",		"RGB888 configs with depth and no stencil"));
+	configSets.push_back(NamedConfigIdSet("rgb888_depth_stencil",			"RGB888 configs with depth and stencil"));
+	configSets.push_back(NamedConfigIdSet("rgba4444_no_depth_no_stencil",	"RGBA4444 configs without depth or stencil"));
+	configSets.push_back(NamedConfigIdSet("rgba4444_no_depth_stencil",		"RGBA4444 configs with stencil and no depth"));
+	configSets.push_back(NamedConfigIdSet("rgba4444_depth_no_stencil",		"RGBA4444 configs with depth and no stencil"));
+	configSets.push_back(NamedConfigIdSet("rgba4444_depth_stencil",			"RGBA4444 configs with depth and stencil"));
+	configSets.push_back(NamedConfigIdSet("rgba5551_no_depth_no_stencil",	"RGBA5551 configs without depth or stencil"));
+	configSets.push_back(NamedConfigIdSet("rgba5551_no_depth_stencil",		"RGBA5551 configs with stencil and no depth"));
+	configSets.push_back(NamedConfigIdSet("rgba5551_depth_no_stencil",		"RGBA5551 configs with depth and no stencil"));
+	configSets.push_back(NamedConfigIdSet("rgba5551_depth_stencil",			"RGBA5551 configs with depth and stencil"));
+	configSets.push_back(NamedConfigIdSet("rgba8888_no_depth_no_stencil",	"RGBA8888 configs without depth or stencil"));
+	configSets.push_back(NamedConfigIdSet("rgba8888_no_depth_stencil",		"RGBA8888 configs with stencil and no depth"));
+	configSets.push_back(NamedConfigIdSet("rgba8888_depth_no_stencil",		"RGBA8888 configs with depth and no stencil"));
+	configSets.push_back(NamedConfigIdSet("rgba8888_depth_stencil",			"RGBA8888 configs with depth and stencil"));
+	configSets.push_back(NamedConfigIdSet("other",							"All other configs"));
+
+	// Build set map
+	map<string, NamedConfigIdSet*> setMap;
+	for (int ndx = 0; ndx < (int)configSets.size(); ndx++)
+		setMap[configSets[ndx].getName()] = &configSets[ndx];
+
+	// Filter configs
+	for (vector<ConfigInfo>::const_iterator cfgIter = configInfos.begin(); cfgIter != configInfos.end(); cfgIter++)
+	{
+		const ConfigInfo& info = *cfgIter;
+
+		if (!baseFilters.match(info))
+			continue;
+
+		if (!filterConfigColor(setMap, "", info))
+		{
+			// Add to "other" set
+			addConfigId(setMap, "other", info);
+		}
+	}
+
+	// Sort config ids
+	for (vector<NamedConfigIdSet>::iterator i = configSets.begin(); i != configSets.end(); i++)
+	{
+		vector<EGLint>& ids = i->getConfigIds();
+		std::sort(ids.begin(), ids.end());
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglSimpleConfigCase.hpp b/modules/egl/teglSimpleConfigCase.hpp
new file mode 100644
index 0000000..5e959d4
--- /dev/null
+++ b/modules/egl/teglSimpleConfigCase.hpp
@@ -0,0 +1,83 @@
+#ifndef _TEGLSIMPLECONFIGCASE_HPP
+#define _TEGLSIMPLECONFIGCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Simple Context construction test.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+#include "tcuEgl.hpp"
+#include "egluConfigFilter.hpp"
+
+#include <vector>
+#include <string>
+
+namespace deqp
+{
+namespace egl
+{
+
+class SimpleConfigCase : public TestCase
+{
+public:
+							SimpleConfigCase			(EglTestContext& eglTestCtx, const char* name, const char* description, const std::vector<EGLint>& configIds);
+	virtual					~SimpleConfigCase			(void);
+
+	void					init						(void);
+	IterateResult			iterate						(void);
+
+private:
+	virtual void			executeForConfig			(tcu::egl::Display& display, EGLConfig config)	= DE_NULL;
+
+							SimpleConfigCase			(const SimpleConfigCase& other);
+	SimpleConfigCase&		operator=					(const SimpleConfigCase& other);
+
+	std::vector<EGLint>					m_configIds;
+	std::vector<EGLConfig>				m_configs;
+	std::vector<EGLConfig>::iterator	m_configIter;
+};
+
+class NamedConfigIdSet
+{
+public:
+								NamedConfigIdSet		(void) {}
+								NamedConfigIdSet		(const char* name, const char* description) : m_name(name), m_description(description) {}
+								NamedConfigIdSet		(const char* name, const char* description, const std::vector<EGLint>& configIds) : m_name(name), m_description(description), m_configIds(configIds) {}
+
+	const char*					getName					(void) const	{ return m_name.c_str();		}
+	const char*					getDescription			(void) const	{ return m_description.c_str();	}
+	const std::vector<EGLint>	getConfigIds			(void) const	{ return m_configIds;			}
+
+	std::vector<EGLint>&		getConfigIds			(void)			{ return m_configIds;			}
+
+	static void					getDefaultSets			(std::vector<NamedConfigIdSet>& configSets, const std::vector<eglu::ConfigInfo>& configInfos, const eglu::FilterList& baseFilters);
+
+private:
+	std::string					m_name;
+	std::string					m_description;
+	std::vector<EGLint>			m_configIds;
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLSIMPLECONFIGCASE_HPP
diff --git a/modules/egl/teglSurfacelessContextTests.cpp b/modules/egl/teglSurfacelessContextTests.cpp
new file mode 100644
index 0000000..650f125
--- /dev/null
+++ b/modules/egl/teglSurfacelessContextTests.cpp
@@ -0,0 +1,163 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL_KHR_surfaceless_context extension tests
+ *//*--------------------------------------------------------------------*/
+
+#include "teglSurfacelessContextTests.hpp"
+#include "teglSimpleConfigCase.hpp"
+#include "egluStrUtil.hpp"
+#include "tcuTestLog.hpp"
+
+#include <EGL/eglext.h>
+
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#	define EGL_OPENGL_ES3_BIT_KHR	0x0040
+#endif
+#if !defined(EGL_CONTEXT_MAJOR_VERSION_KHR)
+#	define EGL_CONTEXT_MAJOR_VERSION_KHR EGL_CONTEXT_CLIENT_VERSION
+#endif
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+namespace
+{
+
+class SurfacelessContextCase : public SimpleConfigCase
+{
+public:
+						SurfacelessContextCase			(EglTestContext& eglTestCtx, const char* name, const char* description, const vector<EGLint>& configIds);
+						~SurfacelessContextCase			(void);
+
+	void				executeForConfig				(tcu::egl::Display& display, EGLConfig config);
+};
+
+SurfacelessContextCase::SurfacelessContextCase (EglTestContext& eglTestCtx, const char* name, const char* description, const vector<EGLint>& configIds)
+	: SimpleConfigCase(eglTestCtx, name, description, configIds)
+{
+}
+
+SurfacelessContextCase::~SurfacelessContextCase (void)
+{
+}
+
+void SurfacelessContextCase::executeForConfig (tcu::egl::Display& display, EGLConfig config)
+{
+	TestLog&		log		= m_testCtx.getLog();
+	const EGLint	id		= display.getConfigAttrib(config, EGL_CONFIG_ID);
+	const EGLint	apiBits	= display.getConfigAttrib(config, EGL_RENDERABLE_TYPE);
+
+	static const EGLint es1Attrs[] = { EGL_CONTEXT_CLIENT_VERSION,		1, EGL_NONE };
+	static const EGLint es2Attrs[] = { EGL_CONTEXT_CLIENT_VERSION,		2, EGL_NONE };
+	static const EGLint es3Attrs[] = { EGL_CONTEXT_MAJOR_VERSION_KHR,	3, EGL_NONE };
+
+	static const struct
+	{
+		const char*		name;
+		EGLenum			api;
+		EGLint			apiBit;
+		const EGLint*	ctxAttrs;
+	} apis[] =
+	{
+		{ "OpenGL",			EGL_OPENGL_API,		EGL_OPENGL_BIT,			DE_NULL		},
+		{ "OpenGL ES 1",	EGL_OPENGL_ES_API,	EGL_OPENGL_ES_BIT,		es1Attrs	},
+		{ "OpenGL ES 2",	EGL_OPENGL_ES_API,	EGL_OPENGL_ES2_BIT,		es2Attrs	},
+		{ "OpenGL ES 3",	EGL_OPENGL_ES_API,	EGL_OPENGL_ES3_BIT_KHR,	es3Attrs	},
+		{ "OpenVG",			EGL_OPENVG_API,		EGL_OPENVG_BIT,			DE_NULL		}
+	};
+
+	{
+		vector<string> extensions;
+		display.getExtensions(extensions);
+
+		if (std::find(extensions.begin(), extensions.end(), string("EGL_KHR_surfaceless_context")) == extensions.end())
+			throw tcu::NotSupportedError("EGL_KHR_surfaceless_context not supported", "", __FILE__, __LINE__);
+	}
+
+	for (int apiNdx = 0; apiNdx < (int)DE_LENGTH_OF_ARRAY(apis); apiNdx++)
+	{
+		if ((apiBits & apis[apiNdx].apiBit) == 0)
+			continue; // Not supported API
+
+		log << TestLog::Message << "Creating " << apis[apiNdx].name << " context with config ID " << id << TestLog::EndMessage;
+		TCU_CHECK_EGL();
+
+		TCU_CHECK_EGL_CALL(eglBindAPI(apis[apiNdx].api));
+
+		EGLContext context = eglCreateContext(display.getEGLDisplay(), config, EGL_NO_CONTEXT, apis[apiNdx].ctxAttrs);
+		TCU_CHECK_EGL();
+
+		if (!eglMakeCurrent(display.getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, context))
+		{
+			const EGLenum err = eglGetError();
+
+			if (err == EGL_BAD_MATCH)
+			{
+				log << TestLog::Message << "  eglMakeCurrent() failed with EGL_BAD_MATCH. Context doesn't support surfaceless mode." << TestLog::EndMessage;
+				TCU_CHECK_EGL_CALL(eglDestroyContext(display.getEGLDisplay(), context));
+				continue;
+			}
+			else
+			{
+				log << TestLog::Message << "  Fail, context: " << tcu::toHex(context) << ", error: " << eglu::getErrorName(err) << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to make context current");
+				continue;
+			}
+		}
+
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display.getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+
+		// Destroy
+		TCU_CHECK_EGL_CALL(eglDestroyContext(display.getEGLDisplay(), context));
+		log << TestLog::Message << "  Pass" << TestLog::EndMessage;
+	}
+}
+
+
+
+} // anonymous
+
+SurfacelessContextTests::SurfacelessContextTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup (eglTestCtx, "surfaceless_context", "EGL_KHR_surfaceless_context extension tests")
+{
+}
+
+void SurfacelessContextTests::init (void)
+{
+	vector<NamedConfigIdSet>	configIdSets;
+	eglu::FilterList			filters;
+	NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+	for (vector<NamedConfigIdSet>::const_iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+		addChild(new SurfacelessContextCase(m_eglTestCtx, i->getName(), i->getDescription(), i->getConfigIds()));
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglSurfacelessContextTests.hpp b/modules/egl/teglSurfacelessContextTests.hpp
new file mode 100644
index 0000000..9d39a2d
--- /dev/null
+++ b/modules/egl/teglSurfacelessContextTests.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLSURFACELESSCONTEXTTESTS_HPP
+#define _TEGLSURFACELESSCONTEXTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL_KHR_surfaceless_context extension tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class SurfacelessContextTests : public TestCaseGroup
+{
+public:
+			SurfacelessContextTests	(EglTestContext& eglTestCtx);
+	void	init					(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLSURFACELESSCONTEXTTESTS_HPP
diff --git a/modules/egl/teglSwapBuffersTests.cpp b/modules/egl/teglSwapBuffersTests.cpp
new file mode 100644
index 0000000..9a353bc
--- /dev/null
+++ b/modules/egl/teglSwapBuffersTests.cpp
@@ -0,0 +1,447 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test eglSwapBuffers() interaction with native window.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglSwapBuffersTests.hpp"
+
+#include "teglSimpleConfigCase.hpp"
+
+#include "egluNativeWindow.hpp"
+#include "egluUtil.hpp"
+#include "egluUnique.hpp"
+
+#include "gluDefs.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVector.hpp"
+#include "tcuVectorUtil.hpp"
+
+#include "deUniquePtr.hpp"
+#include "deThread.hpp"
+
+#include <string>
+#include <vector>
+#include <sstream>
+
+using tcu::TestLog;
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace
+{
+
+EGLContext createGLES2Context (EGLDisplay display, EGLConfig config)
+{
+	EGLContext		context = EGL_NO_CONTEXT;
+	const EGLint	attribList[] =
+	{
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+
+	TCU_CHECK_EGL_CALL(eglBindAPI(EGL_OPENGL_ES_API));
+
+	context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribList);
+	TCU_CHECK_EGL_MSG("eglCreateContext() failed");
+	TCU_CHECK(context);
+
+	return context;
+}
+
+class SwapBuffersTest : public SimpleConfigCase
+{
+public:
+						SwapBuffersTest		(EglTestContext& eglTestCtx, const char* name, const char* description, const vector<EGLint>& configIds);
+						~SwapBuffersTest	(void);
+
+private:
+	void				executeForConfig	(tcu::egl::Display& display, EGLConfig config);
+
+	// Not allowed
+						SwapBuffersTest		(const SwapBuffersTest&);
+	SwapBuffersTest&	operator=			(const SwapBuffersTest&);
+};
+
+
+SwapBuffersTest::SwapBuffersTest (EglTestContext& eglTestCtx, const char* name, const char* description, const vector<EGLint>& configIds)
+	: SimpleConfigCase			(eglTestCtx, name, description, configIds)
+{
+}
+
+SwapBuffersTest::~SwapBuffersTest (void)
+{
+}
+
+string getConfigIdString (EGLDisplay display, EGLConfig config)
+{
+	std::ostringstream	stream;
+	EGLint				id;
+
+	TCU_CHECK_EGL_CALL(eglGetConfigAttrib(display, config , EGL_CONFIG_ID, &id));
+
+	stream << id;
+
+	return stream.str();
+}
+
+deUint32 createGLES2Program (const glw::Functions& gl, TestLog& log)
+{
+	const char* const vertexShaderSource =
+	"attribute highp vec2 a_pos;\n"
+	"void main (void)\n"
+	"{\n"
+	"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
+	"}";
+
+	const char* const fragmentShaderSource =
+	"void main (void)\n"
+	"{\n"
+	"\tgl_FragColor = vec4(0.9, 0.1, 0.4, 1.0);\n"
+	"}";
+
+	deUint32	program			= 0;
+	deUint32	vertexShader	= 0;
+	deUint32	fragmentShader	= 0;
+
+	deInt32		vertexCompileStatus;
+	string		vertexInfoLog;
+	deInt32		fragmentCompileStatus;
+	string		fragmentInfoLog;
+	deInt32		linkStatus;
+	string		programInfoLog;
+
+	try
+	{
+		program			= gl.createProgram();
+		vertexShader	= gl.createShader(GL_VERTEX_SHADER);
+		fragmentShader	= gl.createShader(GL_FRAGMENT_SHADER);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create shaders and program");
+
+		gl.shaderSource(vertexShader, 1, &vertexShaderSource, DE_NULL);
+		gl.compileShader(vertexShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup vertex shader");
+
+		gl.shaderSource(fragmentShader, 1, &fragmentShaderSource, DE_NULL);
+		gl.compileShader(fragmentShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup fragment shader");
+
+		{
+			deInt32		infoLogLength = 0;
+
+			gl.getShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexCompileStatus);
+			gl.getShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+			vertexInfoLog.resize(infoLogLength, '\0');
+
+			gl.getShaderInfoLog(vertexShader, (glw::GLsizei)vertexInfoLog.length(), &infoLogLength, &(vertexInfoLog[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get vertex shader compile info");
+
+			vertexInfoLog.resize(infoLogLength);
+		}
+
+		{
+			deInt32		infoLogLength = 0;
+
+			gl.getShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentCompileStatus);
+			gl.getShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+			fragmentInfoLog.resize(infoLogLength, '\0');
+
+			gl.getShaderInfoLog(fragmentShader, (glw::GLsizei)fragmentInfoLog.length(), &infoLogLength, &(fragmentInfoLog[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get fragment shader compile info");
+
+			fragmentInfoLog.resize(infoLogLength);
+		}
+
+		gl.attachShader(program, vertexShader);
+		gl.attachShader(program, fragmentShader);
+		gl.linkProgram(program);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup program");
+
+		{
+			deInt32		infoLogLength = 0;
+
+			gl.getProgramiv(program, GL_LINK_STATUS, &linkStatus);
+			gl.getProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+			programInfoLog.resize(infoLogLength, '\0');
+
+			gl.getProgramInfoLog(program, (glw::GLsizei)programInfoLog.length(), &infoLogLength, &(programInfoLog[0]));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get program link info");
+
+			programInfoLog.resize(infoLogLength);
+		}
+
+		if (linkStatus == 0 || vertexCompileStatus == 0 || fragmentCompileStatus == 0)
+		{
+
+			log.startShaderProgram(linkStatus != 0, programInfoLog.c_str());
+
+			log << TestLog::Shader(QP_SHADER_TYPE_VERTEX, vertexShaderSource, vertexCompileStatus != 0, vertexInfoLog);
+			log << TestLog::Shader(QP_SHADER_TYPE_FRAGMENT, fragmentShaderSource, fragmentCompileStatus != 0, fragmentInfoLog);
+
+			log.endShaderProgram();
+		}
+
+		gl.deleteShader(vertexShader);
+		gl.deleteShader(fragmentShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to delete shaders");
+
+		TCU_CHECK(linkStatus != 0 && vertexCompileStatus != 0 && fragmentCompileStatus != 0);
+	}
+	catch (...)
+	{
+		if (program)
+			gl.deleteProgram(program);
+
+		if (vertexShader)
+			gl.deleteShader(vertexShader);
+
+		if (fragmentShader)
+			gl.deleteShader(fragmentShader);
+
+		throw;
+	}
+
+	return program;
+}
+
+bool checkColor (tcu::TestLog& log, const tcu::TextureLevel& screen, const tcu::Vec4& color)
+{
+	const tcu::Vec4 threshold(0.01f, 0.01f, 0.01f, 1.00f);
+
+	for (int y = 0; y < screen.getHeight(); y++)
+	{
+		for (int x = 0; x < screen.getWidth(); x++)
+		{
+			const tcu::Vec4	pixel(screen.getAccess().getPixel(x, y));
+			const tcu::Vec4	diff(abs(pixel - color));
+
+			if (!boolAll(lessThanEqual(diff, threshold)))
+			{
+				log << TestLog::Message << "Unexpected color values read from screen expected: " << color << TestLog::EndMessage;
+				log << TestLog::Image("Screen", "Screen", screen.getAccess());
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+void SwapBuffersTest::executeForConfig (tcu::egl::Display& display, EGLConfig config)
+{
+	const string			configIdStr	(getConfigIdString(display.getEGLDisplay(), config));
+	tcu::ScopedLogSection	logSection	(m_testCtx.getLog(), ("Config ID " + configIdStr).c_str(), ("Config ID " + configIdStr).c_str());
+	const int				waitFrames	= 5;
+
+	{
+		TestLog& log = m_testCtx.getLog();
+
+		log << TestLog::Message << "EGL_RED_SIZE: " << eglu::getConfigAttribInt(display.getEGLDisplay(), config, EGL_RED_SIZE) << TestLog::EndMessage;
+		log << TestLog::Message << "EGL_GREEN_SIZE: " << eglu::getConfigAttribInt(display.getEGLDisplay(), config, EGL_GREEN_SIZE) << TestLog::EndMessage;
+		log << TestLog::Message << "EGL_BLUE_SIZE: " << eglu::getConfigAttribInt(display.getEGLDisplay(), config, EGL_BLUE_SIZE) << TestLog::EndMessage;
+		log << TestLog::Message << "EGL_ALPHA_SIZE: " << eglu::getConfigAttribInt(display.getEGLDisplay(), config, EGL_ALPHA_SIZE) << TestLog::EndMessage;
+		log << TestLog::Message << "EGL_DEPTH_SIZE: " << eglu::getConfigAttribInt(display.getEGLDisplay(), config, EGL_DEPTH_SIZE) << TestLog::EndMessage;
+		log << TestLog::Message << "EGL_STENCIL_SIZE: " << eglu::getConfigAttribInt(display.getEGLDisplay(), config, EGL_STENCIL_SIZE) << TestLog::EndMessage;
+		log << TestLog::Message << "EGL_SAMPLES: " << eglu::getConfigAttribInt(display.getEGLDisplay(), config, EGL_SAMPLES) << TestLog::EndMessage;
+
+		log << TestLog::Message << "Waiting " << waitFrames * 16 << "ms after eglSwapBuffers() and glFinish() for frame to become visible" << TestLog::EndMessage;
+	}
+
+	if ((m_eglTestCtx.getNativeWindowFactory().getCapabilities() & eglu::NativeWindow::CAPABILITY_READ_SCREEN_PIXELS) == 0)
+		throw tcu::NotSupportedError("eglu::NativeWindow doesn't support readScreenPixels()", "", __FILE__, __LINE__);
+
+	de::UniquePtr<eglu::NativeWindow>	window	(m_eglTestCtx.createNativeWindow(m_eglTestCtx.getDisplay().getEGLDisplay(), config, DE_NULL, 128, 128, eglu::WindowParams::VISIBILITY_VISIBLE));
+
+	eglu::UniqueSurface					surface	(display.getEGLDisplay(), eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, display.getEGLDisplay(), config, DE_NULL));
+	eglu::UniqueContext					context	(display.getEGLDisplay(), createGLES2Context(display.getEGLDisplay(), config));
+	glw::Functions						gl;
+	deUint32							program = 0;
+
+	tcu::TextureLevel					whiteFrame;
+	tcu::TextureLevel					blackFrame;
+	tcu::TextureLevel					frameBegin;
+	tcu::TextureLevel					frameEnd;
+
+	m_eglTestCtx.getGLFunctions(gl, glu::ApiType::es(2,0));
+	TCU_CHECK_EGL_CALL(eglMakeCurrent(display.getEGLDisplay(), *surface, *surface, *context));
+
+	try
+	{
+		const float positions1[] = {
+			 0.00f,  0.00f,
+			 0.75f,  0.00f,
+			 0.75f,  0.75f,
+
+			 0.75f,  0.75f,
+			 0.00f,  0.75f,
+			 0.00f,  0.00f
+		};
+
+		const float positions2[] = {
+			-0.75f, -0.75f,
+			 0.00f, -0.75f,
+			 0.00f,  0.00f,
+
+			 0.00f,  0.00f,
+			-0.75f,  0.00f,
+			-0.75f, -0.75f
+		};
+
+		deUint32 posLocation;
+
+		program	= createGLES2Program(gl, m_testCtx.getLog());
+
+		gl.useProgram(program);
+		posLocation	= gl.getAttribLocation(program, "a_pos");
+		gl.enableVertexAttribArray(posLocation);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup shader program for rendering");
+
+		// Clear screen to white and check that sceen is white
+		gl.clearColor(1.0f, 1.0f, 1.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to clear surface");
+
+		TCU_CHECK_EGL_CALL(eglSwapBuffers(display.getEGLDisplay(), *surface));
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish() failed");
+		deSleep(waitFrames * 16);
+		window->processEvents();
+		window->readScreenPixels(&whiteFrame);
+
+		if (!checkColor(m_testCtx.getLog(), whiteFrame, tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f)))
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Couldn't reliably read pixels from screen");
+			return;
+		}
+
+		// Clear screen to black and check that sceen is black
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to clear surface");
+
+		TCU_CHECK_EGL_CALL(eglSwapBuffers(display.getEGLDisplay(), *surface));
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish() failed");
+		deSleep(waitFrames * 16);
+		window->processEvents();
+		window->readScreenPixels(&blackFrame);
+
+		if (!checkColor(m_testCtx.getLog(), blackFrame, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)))
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Couldn't reliably read pixels from screen");
+			return;
+		}
+
+		gl.clearColor(0.7f, 1.0f, 0.3f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to clear surface");
+
+		gl.vertexAttribPointer(posLocation, 2, GL_FLOAT, GL_FALSE, 0, positions1);
+		gl.drawArrays(GL_TRIANGLES, 0, 6);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to render");
+
+		TCU_CHECK_EGL_CALL(eglSwapBuffers(display.getEGLDisplay(), *surface));
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish() failed");
+		deSleep(waitFrames * 16);
+		window->processEvents();
+		window->readScreenPixels(&frameBegin);
+
+		gl.clearColor(0.7f, 0.7f, 1.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to clear surface");
+
+		gl.vertexAttribPointer(posLocation, 2, GL_FLOAT, GL_FALSE, 0, positions2);
+		gl.drawArrays(GL_TRIANGLES, 0, 6);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to render");
+
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish() failed");
+		deSleep(waitFrames * 16);
+		window->readScreenPixels(&frameEnd);
+
+		TCU_CHECK_EGL_CALL(eglSwapBuffers(display.getEGLDisplay(), *surface));
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish() failed");
+		deSleep(waitFrames * 16);
+		window->processEvents();
+
+		gl.disableVertexAttribArray(posLocation);
+		gl.useProgram(0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to release program state");
+
+		gl.deleteProgram(program);
+		program = 0;
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteProgram()");
+
+		if (!tcu::intThresholdCompare(m_testCtx.getLog(), "Compare end of frame against beginning of frame" , "Compare end of frame against beginning of frame", frameBegin.getAccess(), frameEnd.getAccess(), tcu::UVec4(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Screen pixels changed during frame");
+	}
+	catch (...)
+	{
+		if (program != 0)
+			gl.deleteProgram(program);
+
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(display.getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+		throw;
+	}
+
+	TCU_CHECK_EGL_CALL(eglMakeCurrent(display.getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+}
+
+} // anonymous
+
+SwapBuffersTests::SwapBuffersTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "swap_buffers", "Swap buffers tests")
+{
+}
+
+void SwapBuffersTests::init (void)
+{
+	eglu::FilterList filters;
+	filters << (eglu::ConfigSurfaceType() & EGL_WINDOW_BIT);
+
+	vector<NamedConfigIdSet> configIdSets;
+	NamedConfigIdSet::getDefaultSets(configIdSets, m_eglTestCtx.getConfigs(), filters);
+
+	for (vector<NamedConfigIdSet>::iterator i = configIdSets.begin(); i != configIdSets.end(); i++)
+		addChild(new SwapBuffersTest(m_eglTestCtx, i->getName(), i->getDescription(), i->getConfigIds()));
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglSwapBuffersTests.hpp b/modules/egl/teglSwapBuffersTests.hpp
new file mode 100644
index 0000000..eec6ab9
--- /dev/null
+++ b/modules/egl/teglSwapBuffersTests.hpp
@@ -0,0 +1,48 @@
+#ifndef _TEGLSWAPBUFFERSTESTS_HPP
+#define _TEGLSWAPBUFFERSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test eglSwapBuffers() interaction with native window.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class SwapBuffersTests : public TestCaseGroup
+{
+public:
+						SwapBuffersTests	(EglTestContext& eglTestCtx);
+	void				init				(void);
+
+private:
+						SwapBuffersTests	(const SwapBuffersTests&);
+	SwapBuffersTests&	operator=			(const SwapBuffersTests&);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLSWAPBUFFERSTESTS_HPP
diff --git a/modules/egl/teglSyncTests.cpp b/modules/egl/teglSyncTests.cpp
new file mode 100644
index 0000000..3d90403
--- /dev/null
+++ b/modules/egl/teglSyncTests.cpp
@@ -0,0 +1,1323 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL EGL_KHR_fence_sync and EGL_KHR_reusable_sync tests
+ *//*--------------------------------------------------------------------*/
+
+#include "teglSyncTests.hpp"
+
+#include "egluNativeWindow.hpp"
+#include "egluStrUtil.hpp"
+#include "egluUtil.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuCommandLine.hpp"
+
+#include "gluDefs.hpp"
+
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+
+#ifndef EGL_KHR_wait_sync
+#define EGL_KHR_wait_sync 1
+typedef EGLint (EGLAPIENTRYP PFNEGLWAITSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags);
+#ifdef EGL_EGLEXT_PROTOTYPES
+EGLAPI EGLint EGLAPIENTRY eglWaitSyncKHR (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags);
+#endif
+#endif /* EGL_KHR_wait_sync */
+
+#include <vector>
+#include <string>
+#include <sstream>
+#include <set>
+
+using std::vector;
+using std::string;
+using std::set;
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace egl
+{
+namespace
+{
+
+const char* getSyncTypeName (EGLenum syncType)
+{
+	switch (syncType)
+	{
+		case EGL_SYNC_FENCE_KHR:	return "EGL_SYNC_FENCE_KHR";
+		case EGL_SYNC_REUSABLE_KHR:	return "EGL_SYNC_REUSABLE_KHR";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return "<Unknown>";
+	}
+}
+
+class SyncTest : public TestCase
+{
+public:
+	enum Extension
+	{
+		EXTENSION_NONE				= 0,
+		EXTENSION_WAIT_SYNC			= (0x1 << 0),
+		EXTENSION_FENCE_SYNC		= (0x1 << 1),
+		EXTENSION_REUSABLE_SYNC		= (0x1 << 2)
+	};
+									SyncTest	(EglTestContext& eglTestCtx, EGLenum syncType, Extension extensions, const char* name, const char* description);
+									~SyncTest	(void);
+
+	void							init		(void);
+	void							deinit		(void);
+
+protected:
+	const EGLenum					m_syncType;
+	const Extension					m_extensions;
+
+	EGLDisplay						m_eglDisplay;
+	EGLConfig						m_eglConfig;
+	EGLSurface						m_eglSurface;
+	eglu::NativeWindow*				m_nativeWindow;
+	EGLContext						m_eglContext;
+	EGLSyncKHR						m_sync;
+
+	struct
+	{
+		PFNEGLCREATESYNCKHRPROC		createSync;
+		PFNEGLDESTROYSYNCKHRPROC	destroySync;
+		PFNEGLCLIENTWAITSYNCKHRPROC	clientWaitSync;
+		PFNEGLGETSYNCATTRIBKHRPROC	getSyncAttrib;
+
+		PFNEGLWAITSYNCKHRPROC		waitSync;
+		PFNEGLSIGNALSYNCKHRPROC		signalSync;
+	} m_ext;
+};
+
+SyncTest::SyncTest (EglTestContext& eglTestCtx, EGLenum syncType, Extension extensions, const char* name, const char* description)
+	: TestCase			(eglTestCtx, name, description)
+	, m_syncType		(syncType)
+	, m_extensions		(extensions)
+	, m_eglDisplay		(EGL_NO_DISPLAY)
+	, m_eglSurface		(EGL_NO_SURFACE)
+	, m_nativeWindow	(DE_NULL)
+	, m_eglContext		(EGL_NO_CONTEXT)
+	, m_sync			(EGL_NO_SYNC_KHR)
+{
+	m_ext.createSync		= DE_NULL;
+	m_ext.destroySync		= DE_NULL;
+	m_ext.clientWaitSync	= DE_NULL;
+	m_ext.getSyncAttrib		= DE_NULL;
+	m_ext.waitSync			= DE_NULL;
+}
+
+SyncTest::~SyncTest (void)
+{
+	SyncTest::deinit();
+}
+
+void requiredEGLExtensions (EGLDisplay display, SyncTest::Extension requiredExtensions)
+{
+	SyncTest::Extension foundExtensions = SyncTest::EXTENSION_NONE;
+	std::istringstream	extensionStream(eglQueryString(display, EGL_EXTENSIONS));
+	string				extension;
+
+	TCU_CHECK_EGL_MSG("eglQueryString(display, EGL_EXTENSIONS)");
+
+	while (std::getline(extensionStream, extension, ' '))
+	{
+		if (extension == "EGL_KHR_fence_sync")
+			foundExtensions = (SyncTest::Extension)(foundExtensions | SyncTest::EXTENSION_FENCE_SYNC);
+		else if (extension == "EGL_KHR_reusable_sync")
+			foundExtensions = (SyncTest::Extension)(foundExtensions | SyncTest::EXTENSION_REUSABLE_SYNC);
+		else if (extension == "EGL_KHR_wait_sync")
+			foundExtensions = (SyncTest::Extension)(foundExtensions | SyncTest::EXTENSION_WAIT_SYNC);
+	}
+
+	{
+		const SyncTest::Extension missingExtensions = (SyncTest::Extension)((foundExtensions & requiredExtensions) ^ requiredExtensions);
+
+		if ((missingExtensions & SyncTest::EXTENSION_FENCE_SYNC) != 0)
+			throw tcu::NotSupportedError("EGL_KHR_fence_sync not supported", "", __FILE__, __LINE__);
+
+		if ((missingExtensions & SyncTest::EXTENSION_REUSABLE_SYNC) != 0)
+			throw tcu::NotSupportedError("EGL_KHR_reusable_sync not supported", "", __FILE__, __LINE__);
+
+		if ((missingExtensions & SyncTest::EXTENSION_WAIT_SYNC) != 0)
+			throw tcu::NotSupportedError("EGL_KHR_wait_sync not supported", "", __FILE__, __LINE__);
+	}
+}
+
+void requiredGLESExtensions (void)
+{
+	bool				found = false;
+	std::istringstream	extensionStream((const char*)glGetString(GL_EXTENSIONS));
+	string				extension;
+
+	GLU_CHECK_MSG("glGetString(GL_EXTENSIONS)");
+
+	while (std::getline(extensionStream, extension, ' '))
+	{
+		if (extension == "GL_OES_EGL_sync")
+			found = true;
+	}
+
+	if (!found)
+		throw tcu::NotSupportedError("GL_OES_EGL_sync not supported", "", __FILE__, __LINE__);
+}
+
+SyncTest::Extension getSyncTypeExtension (EGLenum syncType)
+{
+	switch (syncType)
+	{
+		case EGL_SYNC_FENCE_KHR:	return SyncTest::EXTENSION_FENCE_SYNC;
+		case EGL_SYNC_REUSABLE_KHR:	return SyncTest::EXTENSION_REUSABLE_SYNC;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return SyncTest::EXTENSION_NONE;
+	}
+}
+
+void SyncTest::init (void)
+{
+	const EGLint displayAttribList[] =
+	{
+		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
+		EGL_SURFACE_TYPE,		EGL_WINDOW_BIT,
+		EGL_ALPHA_SIZE,			1,
+		EGL_NONE
+	};
+
+	const EGLint contextAttribList[] =
+	{
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+	tcu::egl::Display&	display = m_eglTestCtx.getDisplay();
+	vector<EGLConfig>	configs;
+
+	display.chooseConfig(displayAttribList, configs);
+	m_eglDisplay	= display.getEGLDisplay();
+	m_eglConfig 	= configs[0];
+
+	{
+		const Extension syncTypeExtension = getSyncTypeExtension(m_syncType);
+		requiredEGLExtensions(m_eglDisplay, (Extension)(m_extensions | syncTypeExtension));
+	}
+
+	m_ext.createSync		= (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR");
+	m_ext.destroySync		= (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress("eglDestroySyncKHR");
+	m_ext.clientWaitSync	= (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress("eglClientWaitSyncKHR");
+	m_ext.getSyncAttrib		= (PFNEGLGETSYNCATTRIBKHRPROC)eglGetProcAddress("eglGetSyncAttribKHR");
+	m_ext.waitSync			= (PFNEGLWAITSYNCKHRPROC)eglGetProcAddress("eglWaitSyncKHR");
+	m_ext.signalSync		= (PFNEGLSIGNALSYNCKHRPROC)eglGetProcAddress("eglSignalSyncKHR");
+
+	// Create context
+	TCU_CHECK_EGL_CALL(eglBindAPI(EGL_OPENGL_ES_API));
+	m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttribList);
+	TCU_CHECK_EGL_MSG("Failed to create GLES2 context");
+
+	// Create surface
+	m_nativeWindow = m_eglTestCtx.createNativeWindow(m_eglDisplay, m_eglConfig, DE_NULL, 480, 480, eglu::parseWindowVisibility(m_testCtx.getCommandLine()));
+	m_eglSurface = eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *m_nativeWindow, m_eglDisplay, m_eglConfig, DE_NULL);
+
+	TCU_CHECK_EGL_CALL(eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext));
+
+	requiredGLESExtensions();
+}
+
+void SyncTest::deinit (void)
+{
+	if (m_eglDisplay != EGL_NO_DISPLAY)
+	{
+		if (m_sync != EGL_NO_SYNC_KHR)
+		{
+			TCU_CHECK_EGL_CALL(m_ext.destroySync(m_eglDisplay, m_sync));
+			m_sync = EGL_NO_SYNC_KHR;
+		}
+
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+
+		if (m_eglContext != EGL_NO_CONTEXT)
+		{
+			TCU_CHECK_EGL_CALL(eglDestroyContext(m_eglDisplay, m_eglContext));
+			m_eglContext = EGL_NO_CONTEXT;
+		}
+
+		if (m_eglSurface != EGL_NO_SURFACE)
+		{
+			TCU_CHECK_EGL_CALL(eglDestroySurface(m_eglDisplay, m_eglSurface));
+			m_eglSurface = EGL_NO_SURFACE;
+		}
+
+		delete m_nativeWindow;
+		m_nativeWindow = DE_NULL;
+
+		m_eglDisplay = EGL_NO_DISPLAY;
+	}
+}
+
+class CreateNullAttribsTest : public SyncTest
+{
+public:
+					CreateNullAttribsTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "create_null_attribs", "create_null_attribs") {}
+
+	IterateResult	iterate					(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class CreateEmptyAttribsTest : public SyncTest
+{
+public:
+					CreateEmptyAttribsTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "create_empty_attribs", "create_empty_attribs") {}
+
+	IterateResult	iterate					(void)
+	{
+
+		const EGLint attribList[] = {
+			EGL_NONE
+		};
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, attribList);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", { EGL_NONE })" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class CreateInvalidDisplayTest : public SyncTest
+{
+public:
+					CreateInvalidDisplayTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "create_invalid_display", "create_invalid_display") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+
+		m_sync = m_ext.createSync(EGL_NO_DISPLAY, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(EGL_NO_DISPLAY, " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_DISPLAY)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_DISPLAY" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(m_sync == EGL_NO_SYNC_KHR);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class CreateInvalidTypeTest : public SyncTest
+{
+public:
+					CreateInvalidTypeTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "create_invalid_type", "create_invalid_type") {}
+
+	IterateResult	iterate					(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, EGL_NONE, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", EGL_NONE, NULL)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_ATTRIBUTE)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_ATTRIBUTE" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(m_sync == EGL_NO_SYNC_KHR);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class CreateInvalidAttribsTest : public SyncTest
+{
+public:
+					CreateInvalidAttribsTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "create_invalid_attribs", "create_invalid_attribs") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+
+		EGLint attribs[] = {
+			2, 3, 4, 5,
+			EGL_NONE
+		};
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, attribs);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", { 2, 3, 4, 5, EGL_NONE })" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_ATTRIBUTE)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_ATTRIBUTE" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(m_sync == EGL_NO_SYNC_KHR);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class CreateInvalidContextTest : public SyncTest
+{
+public:
+					CreateInvalidContextTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "create_invalid_context", "create_invalid_context") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+
+		log << TestLog::Message << "eglMakeCurrent(" << m_eglDisplay << ", EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_MATCH)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_MATCH" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(m_sync == EGL_NO_SYNC_KHR);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class ClientWaitNoTimeoutTest : public SyncTest
+{
+public:
+					ClientWaitNoTimeoutTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "wait_no_timeout", "wait_no_timeout") {}
+
+	IterateResult	iterate					(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.clientWaitSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint status = m_ext.clientWaitSync(m_eglDisplay, m_sync, 0, 0);
+		log << TestLog::Message << status << " = eglClientWaitSyncKHR(" << m_eglDisplay << ", " << m_sync << ", 0, 0)" << TestLog::EndMessage;
+
+		if (m_syncType == EGL_SYNC_FENCE_KHR)
+			TCU_CHECK(status == EGL_CONDITION_SATISFIED_KHR || status == EGL_TIMEOUT_EXPIRED_KHR);
+		else if (m_syncType == EGL_SYNC_REUSABLE_KHR)
+			TCU_CHECK(status == EGL_TIMEOUT_EXPIRED_KHR);
+		else
+			DE_ASSERT(DE_FALSE);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+};
+
+class ClientWaitForeverTest : public SyncTest
+{
+public:
+					ClientWaitForeverTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "wait_forever", "wait_forever") {}
+
+	IterateResult	iterate					(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.clientWaitSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		if (m_syncType == EGL_SYNC_REUSABLE_KHR)
+		{
+			EGLBoolean ret = m_ext.signalSync(m_eglDisplay, m_sync, EGL_SIGNALED_KHR);
+			log << TestLog::Message << ret << " = eglSignalSyncKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SIGNALED_KHR)" << TestLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglSignalSyncKHR()");
+		}
+		else if (m_syncType == EGL_SYNC_FENCE_KHR)
+		{
+			GLU_CHECK_CALL(glFlush());
+			log << TestLog::Message << "glFlush()" << TestLog::EndMessage;
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		EGLint status = m_ext.clientWaitSync(m_eglDisplay, m_sync, 0, EGL_FOREVER_KHR);
+		log << TestLog::Message << status << " = eglClientWaitSyncKHR(" << m_eglDisplay << ", " << m_sync << ", 0, EGL_FOREVER_KHR)" << TestLog::EndMessage;
+
+		TCU_CHECK(status == EGL_CONDITION_SATISFIED_KHR);
+		TCU_CHECK_EGL_MSG("eglClientWaitSyncKHR()");
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class ClientWaitNoContextTest : public SyncTest
+{
+public:
+					ClientWaitNoContextTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "wait_no_context", "wait_no_Context") {}
+
+	IterateResult	iterate					(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.clientWaitSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+
+		if (m_syncType == EGL_SYNC_REUSABLE_KHR)
+		{
+			EGLBoolean ret = m_ext.signalSync(m_eglDisplay, m_sync, EGL_SIGNALED_KHR);
+			log << TestLog::Message << ret << " = eglSignalSyncKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SIGNALED_KHR)" << TestLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglSignalSyncKHR()");
+		}
+		else if (m_syncType == EGL_SYNC_FENCE_KHR)
+		{
+			GLU_CHECK_CALL(glFlush());
+			log << TestLog::Message << "glFlush()" << TestLog::EndMessage;
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		log << TestLog::Message << "eglMakeCurrent(" << m_eglDisplay << ", EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+
+		EGLint result = m_ext.clientWaitSync(m_eglDisplay, m_sync, 0, EGL_FOREVER_KHR);
+		log << TestLog::Message << result << " = eglClientWaitSyncKHR(" << m_eglDisplay << ", " << m_sync << ", 0, EGL_FOREVER_KHR)" << TestLog::EndMessage;
+
+		TCU_CHECK(result == EGL_CONDITION_SATISFIED_KHR);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class ClientWaitForeverFlushTest : public SyncTest
+{
+public:
+					ClientWaitForeverFlushTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "wait_forever_flush", "wait_forever_flush") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.clientWaitSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		if (m_syncType == EGL_SYNC_REUSABLE_KHR)
+		{
+			EGLBoolean ret = m_ext.signalSync(m_eglDisplay, m_sync, EGL_SIGNALED_KHR);
+			log << TestLog::Message << ret << " = eglSignalSyncKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SIGNALED_KHR)" << TestLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglSignalSyncKHR()");
+		}
+
+		EGLint status = m_ext.clientWaitSync(m_eglDisplay, m_sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR);
+		log << TestLog::Message << status << " = eglClientWaitSyncKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR)" << TestLog::EndMessage;
+
+		TCU_CHECK(status == EGL_CONDITION_SATISFIED_KHR);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class ClientWaitInvalidDisplayTest : public SyncTest
+{
+public:
+					ClientWaitInvalidDisplayTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "wait_invalid_display", "wait_invalid_display") {}
+
+	IterateResult	iterate							(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.clientWaitSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint status = m_ext.clientWaitSync(EGL_NO_DISPLAY, m_sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR);
+		log << TestLog::Message << status << " = eglClientWaitSyncKHR(EGL_NO_DISPLAY, " << m_sync << ", EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_DISPLAY)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_DISPLAY" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(status == EGL_FALSE);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class ClientWaitInvalidSyncTest : public SyncTest
+{
+public:
+					ClientWaitInvalidSyncTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "wait_invalid_sync", "wait_invalid_sync") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.clientWaitSync);
+
+		EGLint status = m_ext.clientWaitSync(m_eglDisplay, EGL_NO_SYNC_KHR, 0, EGL_FOREVER_KHR);
+		log << TestLog::Message << status << " = eglClientWaitSyncKHR(" << m_eglDisplay << ", EGL_NO_SYNC_KHR, 0, EGL_FOREVER_KHR)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_PARAMETER)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_PARAMETER" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(status == EGL_FALSE);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class ClientWaitInvalidFlagTest : public SyncTest
+{
+public:
+					ClientWaitInvalidFlagTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "wait_invalid_flag", "wait_invalid_flag") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.clientWaitSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint status = m_ext.clientWaitSync(m_eglDisplay, m_sync, 0xFFFFFFFF, EGL_FOREVER_KHR);
+		log << TestLog::Message << status << " = eglClientWaitSyncKHR(" << m_eglDisplay << ", " << m_sync << ", 0xFFFFFFFF, EGL_FOREVER_KHR)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_PARAMETER)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_PARAMETER" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(status == EGL_FALSE);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class GetSyncTypeTest : public SyncTest
+{
+public:
+					GetSyncTypeTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "get_type", "get_type") {}
+
+	IterateResult	iterate			(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.getSyncAttrib);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint type = 0;
+		TCU_CHECK_EGL_CALL(m_ext.getSyncAttrib(m_eglDisplay, m_sync, EGL_SYNC_TYPE_KHR, &type));
+		log << TestLog::Message << "eglGetSyncAttribKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SYNC_TYPE_KHR, {" << type << "})" << TestLog::EndMessage;
+
+		TCU_CHECK(type == ((EGLint)m_syncType));
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class GetSyncStatusTest : public SyncTest
+{
+public:
+					GetSyncStatusTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "get_status", "get_status") {}
+
+	IterateResult	iterate				(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.getSyncAttrib);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint status = 0;
+		TCU_CHECK_EGL_CALL(m_ext.getSyncAttrib(m_eglDisplay, m_sync, EGL_SYNC_STATUS_KHR, &status));
+		log << TestLog::Message << "eglGetSyncAttribKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SYNC_STATUS_KHR, {" << status << "})" << TestLog::EndMessage;
+
+		if (m_syncType == EGL_SYNC_FENCE_KHR)
+			TCU_CHECK(status == EGL_SIGNALED_KHR || status == EGL_UNSIGNALED_KHR);
+		else if (m_syncType == EGL_SYNC_REUSABLE_KHR)
+			TCU_CHECK(status == EGL_UNSIGNALED_KHR);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class GetSyncStatusSignaledTest : public SyncTest
+{
+public:
+					GetSyncStatusSignaledTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "get_status_signaled", "get_status_signaled") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.clientWaitSync);
+		TCU_CHECK(m_ext.getSyncAttrib);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		if (m_syncType == EGL_SYNC_REUSABLE_KHR)
+		{
+			EGLBoolean ret = m_ext.signalSync(m_eglDisplay, m_sync, EGL_SIGNALED_KHR);
+			log << TestLog::Message << ret << " = eglSignalSyncKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SIGNALED_KHR)" << TestLog::EndMessage;
+			TCU_CHECK_EGL_MSG("eglSignalSyncKHR()");
+		}
+		else if (m_syncType == EGL_SYNC_FENCE_KHR)
+		{
+			GLU_CHECK_CALL(glFinish());
+			log << TestLog::Message << "glFinish()" << TestLog::EndMessage;
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		{
+			EGLint status = m_ext.clientWaitSync(m_eglDisplay, m_sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR);
+			log << TestLog::Message << status << " = eglClientWaitSyncKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR)" << TestLog::EndMessage;
+			TCU_CHECK(status == EGL_CONDITION_SATISFIED_KHR);
+		}
+
+		EGLint status = 0;
+		TCU_CHECK_EGL_CALL(m_ext.getSyncAttrib(m_eglDisplay, m_sync, EGL_SYNC_STATUS_KHR, &status));
+		log << TestLog::Message << "eglGetSyncAttribKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SYNC_STATUS_KHR, {" << status << "})" << TestLog::EndMessage;
+
+		TCU_CHECK(status == EGL_SIGNALED_KHR);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class GetSyncConditionTest : public SyncTest
+{
+public:
+					GetSyncConditionTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "get_condition", "get_condition") {}
+
+	IterateResult	iterate					(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.getSyncAttrib);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint condition = 0;
+		TCU_CHECK_EGL_CALL(m_ext.getSyncAttrib(m_eglDisplay, m_sync, EGL_SYNC_CONDITION_KHR, &condition));
+		log << TestLog::Message << "eglGetSyncAttribKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SYNC_CONDITION_KHR, {" << condition << "})" << TestLog::EndMessage;
+
+		TCU_CHECK(condition == EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class GetSyncInvalidDisplayTest : public SyncTest
+{
+public:
+					GetSyncInvalidDisplayTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "get_invalid_display", "get_invalid_display") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.getSyncAttrib);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint condition = 0xF0F0F;
+		EGLBoolean result = m_ext.getSyncAttrib(EGL_NO_DISPLAY, m_sync, EGL_SYNC_CONDITION_KHR, &condition);
+		log << TestLog::Message << result << " = eglGetSyncAttribKHR(EGL_NO_DISPLAY, " << m_sync << ", EGL_SYNC_CONDITION_KHR, {" << condition << "})" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_DISPLAY)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_DISPLAY" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(result == EGL_FALSE);
+		TCU_CHECK(condition == 0xF0F0F);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class GetSyncInvalidSyncTest : public SyncTest
+{
+public:
+					GetSyncInvalidSyncTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "get_invalid_sync", "get_invalid_sync") {}
+
+	IterateResult	iterate					(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.getSyncAttrib);
+
+		EGLint condition = 0xF0F0F;
+		EGLBoolean result = m_ext.getSyncAttrib(m_eglDisplay, EGL_NO_SYNC_KHR, EGL_SYNC_CONDITION_KHR, &condition);
+		log << TestLog::Message << result << " = eglGetSyncAttribKHR(" << m_eglDisplay << ", EGL_NO_SYNC_KHR, EGL_SYNC_CONDITION_KHR, {" << condition << "})" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_PARAMETER)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_PARAMETER" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(result == EGL_FALSE);
+		TCU_CHECK(condition == 0xF0F0F);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class GetSyncInvalidAttributeTest : public SyncTest
+{
+public:
+					GetSyncInvalidAttributeTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "get_invalid_attribute", "get_invalid_attribute") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.getSyncAttrib);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint condition = 0xF0F0F;
+		EGLBoolean result = m_ext.getSyncAttrib(m_eglDisplay, m_sync, EGL_NONE, &condition);
+		log << TestLog::Message << result << " = eglGetSyncAttribKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_NONE, {" << condition << "})" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_ATTRIBUTE)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_ATTRIBUTE" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(result == EGL_FALSE);
+		TCU_CHECK(condition == 0xF0F0F);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class GetSyncInvalidValueTest : public SyncTest
+{
+public:
+					GetSyncInvalidValueTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "get_invalid_value", "get_invalid_value") {}
+
+	IterateResult	iterate					(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.getSyncAttrib);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLBoolean result = m_ext.getSyncAttrib(m_eglDisplay, m_sync, EGL_SYNC_TYPE_KHR, NULL);
+		log << TestLog::Message << result << " = eglGetSyncAttribKHR(" << m_eglDisplay << ", " << m_sync << ", EGL_SYNC_CONDITION_KHR, NULL)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_PARAMETER)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_PARAMETER" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(result == EGL_FALSE);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class DestroySyncTest : public SyncTest
+{
+public:
+					DestroySyncTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "destroy", "destroy") {}
+
+	IterateResult	iterate			(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.destroySync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << "eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		log << TestLog::Message << "eglDestroySyncKHR(" << m_eglDisplay << ", " << m_sync << ")" << TestLog::EndMessage;
+		TCU_CHECK_EGL_CALL(m_ext.destroySync(m_eglDisplay, m_sync));
+		m_sync = EGL_NO_SYNC_KHR;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class DestroySyncInvalidDislayTest : public SyncTest
+{
+public:
+					DestroySyncInvalidDislayTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "destroy_invalid_display", "destroy_invalid_display") {}
+
+	IterateResult	iterate							(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.destroySync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << "eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLBoolean result = m_ext.destroySync(EGL_NO_DISPLAY, m_sync);
+		log << TestLog::Message << result << " = eglDestroySyncKHR(EGL_NO_DISPLAY, " << m_sync << ")" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_DISPLAY)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_DISPLAY" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(result == EGL_FALSE);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class DestroySyncInvalidSyncTest : public SyncTest
+{
+public:
+					DestroySyncInvalidSyncTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_NONE, "destroy_invalid_sync", "destroy_invalid_sync") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.destroySync);
+
+		EGLBoolean result = m_ext.destroySync(m_eglDisplay, EGL_NO_SYNC_KHR);
+		log << TestLog::Message << result << " = eglDestroySyncKHR(" << m_eglDisplay << ", EGL_NO_SYNC_KHR)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_PARAMETER)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_PARAMETER" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(result == EGL_FALSE);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class WaitSyncTest : public SyncTest
+{
+public:
+					WaitSyncTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_WAIT_SYNC, "wait_server", "wait_server") {}
+
+	IterateResult	iterate			(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.waitSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint status = m_ext.waitSync(m_eglDisplay, m_sync, 0);
+		log << TestLog::Message << status << " = eglWaitSyncKHR(" << m_eglDisplay << ", " << m_sync << ", 0, 0)" << TestLog::EndMessage;
+
+		TCU_CHECK(status == EGL_TRUE);
+
+		GLU_CHECK_CALL(glFinish());
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+};
+
+class WaitSyncInvalidDisplayTest : public SyncTest
+{
+public:
+					WaitSyncInvalidDisplayTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_WAIT_SYNC, "wait_server_invalid_display", "wait_server_invalid_display") {}
+
+	IterateResult	iterate						(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.waitSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint status = m_ext.waitSync(EGL_NO_DISPLAY, m_sync, 0);
+		log << TestLog::Message << status << " = eglWaitSyncKHR(EGL_NO_DISPLAY, " << m_sync << ", 0)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_DISPLAY)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_DISPLAY" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(status == EGL_FALSE);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class WaitSyncInvalidSyncTest : public SyncTest
+{
+public:
+					WaitSyncInvalidSyncTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_WAIT_SYNC, "wait_server_invalid_sync", "wait_server_invalid_sync") {}
+
+	IterateResult	iterate					(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.waitSync);
+
+		EGLint status = m_ext.waitSync(m_eglDisplay, EGL_NO_SYNC_KHR, 0);
+		log << TestLog::Message << status << " = eglWaitSyncKHR(" << m_eglDisplay << ", EGL_NO_SYNC_KHR, 0)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_PARAMETER)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_PARAMETER" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(status == EGL_FALSE);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class WaitSyncInvalidFlagTest : public SyncTest
+{
+public:
+					WaitSyncInvalidFlagTest	(EglTestContext& eglTestCtx, EGLenum syncType) : SyncTest(eglTestCtx, syncType, SyncTest::EXTENSION_WAIT_SYNC, "wait_server_invalid_flag", "wait_server_invalid_flag") {}
+
+	IterateResult	iterate					(void)
+	{
+		TestLog& log = m_testCtx.getLog();
+		TCU_CHECK(m_ext.createSync);
+		TCU_CHECK(m_ext.waitSync);
+
+		m_sync = m_ext.createSync(m_eglDisplay, m_syncType, NULL);
+		log << TestLog::Message << m_sync << " = eglCreateSyncKHR(" << m_eglDisplay << ", " << getSyncTypeName(m_syncType) << ", NULL)" << TestLog::EndMessage;
+		TCU_CHECK_EGL_MSG("eglCreateSyncKHR()");
+
+		EGLint status = m_ext.waitSync(m_eglDisplay, m_sync, 0xFFFFFFFF);
+		log << TestLog::Message << status << " = eglWaitSyncKHR(" << m_eglDisplay << ", " << m_sync << ", 0xFFFFFFFF)" << TestLog::EndMessage;
+
+		EGLint error = eglGetError();
+		log << TestLog::Message << error << " = eglGetError()" << TestLog::EndMessage;
+
+		if (error != EGL_BAD_PARAMETER)
+		{
+			log << TestLog::Message << "Unexpected error '" << eglu::getErrorStr(error) << "' expected EGL_BAD_PARAMETER" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		TCU_CHECK(status == EGL_FALSE);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+} // anonymous
+
+FenceSyncTests::FenceSyncTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup	(eglTestCtx, "fence_sync", "EGL_KHR_fence_sync extension tests")
+{
+}
+
+void FenceSyncTests::init (void)
+{
+	// Add valid API test
+	{
+		TestCaseGroup* const valid = new TestCaseGroup(m_eglTestCtx, "valid", "Valid function calls");
+
+		// eglCreateSyncKHR tests
+		valid->addChild(new CreateNullAttribsTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		valid->addChild(new CreateEmptyAttribsTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+
+		// eglClientWaitSyncKHR tests
+		valid->addChild(new ClientWaitNoTimeoutTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		valid->addChild(new ClientWaitForeverTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		valid->addChild(new ClientWaitNoContextTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		valid->addChild(new ClientWaitForeverFlushTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+
+		// eglGetSyncAttribKHR tests
+		valid->addChild(new GetSyncTypeTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		valid->addChild(new GetSyncStatusTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		valid->addChild(new GetSyncStatusSignaledTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		valid->addChild(new GetSyncConditionTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+
+		// eglDestroySyncKHR tests
+		valid->addChild(new DestroySyncTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+
+		// eglWaitSyncKHR tests
+		valid->addChild(new WaitSyncTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+
+		addChild(valid);
+	}
+
+	// Add negative API tests
+	{
+		TestCaseGroup* const invalid = new TestCaseGroup(m_eglTestCtx, "invalid", "Invalid function calls");
+
+		// eglCreateSyncKHR tests
+		invalid->addChild(new CreateInvalidDisplayTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new CreateInvalidTypeTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new CreateInvalidAttribsTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new CreateInvalidContextTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+
+		// eglClientWaitSyncKHR tests
+		invalid->addChild(new ClientWaitInvalidDisplayTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new ClientWaitInvalidSyncTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new ClientWaitInvalidFlagTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+
+		// eglGetSyncAttribKHR tests
+		invalid->addChild(new GetSyncInvalidDisplayTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new GetSyncInvalidSyncTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new GetSyncInvalidAttributeTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new GetSyncInvalidValueTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+
+		// eglDestroySyncKHR tests
+		invalid->addChild(new DestroySyncInvalidDislayTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new DestroySyncInvalidSyncTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+
+		// eglWaitSyncKHR tests
+		invalid->addChild(new WaitSyncInvalidDisplayTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new WaitSyncInvalidSyncTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+		invalid->addChild(new WaitSyncInvalidFlagTest(m_eglTestCtx, EGL_SYNC_FENCE_KHR));
+
+		addChild(invalid);
+	}
+}
+
+ReusableSyncTests::ReusableSyncTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup	(eglTestCtx, "reusable_sync", "EGL_KHR_reusable_sync extension tests")
+{
+}
+
+void ReusableSyncTests::init (void)
+{
+	// Add valid API test
+	{
+		TestCaseGroup* const valid = new TestCaseGroup(m_eglTestCtx, "valid", "Valid function calls");
+
+		// eglCreateSyncKHR tests
+		valid->addChild(new CreateNullAttribsTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		valid->addChild(new CreateEmptyAttribsTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+
+		// eglClientWaitSyncKHR tests
+		valid->addChild(new ClientWaitNoTimeoutTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		valid->addChild(new ClientWaitForeverTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		valid->addChild(new ClientWaitNoContextTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		valid->addChild(new ClientWaitForeverFlushTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+
+		// eglGetSyncAttribKHR tests
+		valid->addChild(new GetSyncTypeTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		valid->addChild(new GetSyncStatusTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		valid->addChild(new GetSyncStatusSignaledTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+
+		// eglDestroySyncKHR tests
+		valid->addChild(new DestroySyncTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+
+		// eglWaitSyncKHR tests
+		valid->addChild(new WaitSyncTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+
+		addChild(valid);
+	}
+
+	// Add negative API tests
+	{
+		TestCaseGroup* const invalid = new TestCaseGroup(m_eglTestCtx, "invalid", "Invalid function calls");
+
+		// eglCreateSyncKHR tests
+		invalid->addChild(new CreateInvalidDisplayTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new CreateInvalidTypeTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new CreateInvalidAttribsTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new CreateInvalidContextTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+
+		// eglClientWaitSyncKHR tests
+		invalid->addChild(new ClientWaitInvalidDisplayTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new ClientWaitInvalidSyncTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new ClientWaitInvalidFlagTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+
+		// eglGetSyncAttribKHR tests
+		invalid->addChild(new GetSyncInvalidDisplayTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new GetSyncInvalidSyncTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new GetSyncInvalidAttributeTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new GetSyncInvalidValueTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+
+		// eglDestroySyncKHR tests
+		invalid->addChild(new DestroySyncInvalidDislayTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new DestroySyncInvalidSyncTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+
+		// eglWaitSyncKHR tests
+		invalid->addChild(new WaitSyncInvalidDisplayTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new WaitSyncInvalidSyncTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+		invalid->addChild(new WaitSyncInvalidFlagTest(m_eglTestCtx, EGL_SYNC_REUSABLE_KHR));
+
+		addChild(invalid);
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglSyncTests.hpp b/modules/egl/teglSyncTests.hpp
new file mode 100644
index 0000000..2ac6ab3
--- /dev/null
+++ b/modules/egl/teglSyncTests.hpp
@@ -0,0 +1,51 @@
+#ifndef _TEGLSYNCTESTS_HPP
+#define _TEGLSYNCTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL EGL_KHR_fence_sync and EGL_KHR_reusable_sync tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class FenceSyncTests : public TestCaseGroup
+{
+public:
+			FenceSyncTests	(EglTestContext& eglTestCtx);
+	void	init			(void);
+};
+
+class ReusableSyncTests : public TestCaseGroup
+{
+public:
+			ReusableSyncTests	(EglTestContext& eglTestCtx);
+	void	init				(void);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLSYNCTESTS_HPP
diff --git a/modules/egl/teglTestCase.cpp b/modules/egl/teglTestCase.cpp
new file mode 100644
index 0000000..7214936
--- /dev/null
+++ b/modules/egl/teglTestCase.cpp
@@ -0,0 +1,298 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL Test Case
+ *//*--------------------------------------------------------------------*/
+
+#include "teglTestCase.hpp"
+
+#include "tcuPlatform.hpp"
+
+#include "egluUtil.hpp"
+#include "egluGLFunctionLoader.hpp"
+#include "egluPlatform.hpp"
+
+#include "gluRenderContext.hpp"
+#include "glwInitFunctions.hpp"
+
+#include <set>
+
+using std::vector;
+using std::set;
+
+namespace deqp
+{
+namespace egl
+{
+
+namespace
+{
+
+void split (std::vector<std::string>& dst, const std::string& src)
+{
+	size_t start = 0;
+	size_t end	 = std::string::npos;
+
+	while ((end = src.find(' ', start)) != std::string::npos)
+	{
+		dst.push_back(src.substr(start, end-start));
+		start = end+1;
+	}
+
+	if (start < end)
+		dst.push_back(src.substr(start, end-start));
+}
+
+EGLint parseAPI (const std::string& api)
+{
+	if (api == "OpenGL")
+		return EGL_OPENGL_API;
+	else if (api == "OpenGL_ES")
+		return EGL_OPENGL_ES_API;
+	else if (api == "OpenVG")
+		return EGL_OPENVG_API;
+	else
+	{
+		tcu::print("Warning: Unknown API '%s'", api.c_str());
+		return 0;
+	}
+}
+
+} // anonymous
+
+EglTestContext::EglTestContext (tcu::TestContext& testCtx, const eglu::NativeDisplayFactory& displayFactory, const eglu::NativeWindowFactory* windowFactory, const eglu::NativePixmapFactory* pixmapFactory)
+	: m_testCtx					(testCtx)
+	, m_displayFactory			(displayFactory)
+	, m_windowFactory			(windowFactory)
+	, m_pixmapFactory			(pixmapFactory)
+	, m_defaultNativeDisplay	(DE_NULL)
+	, m_defaultEGLDisplay		(DE_NULL)
+{
+	// Temporarily allocate default display for storing config list
+	try
+	{
+		EGLDisplay	eglDisplay	= EGL_NO_DISPLAY;
+		EGLint		majorVersion;
+		EGLint		minorVersion;
+
+		m_defaultNativeDisplay	= m_displayFactory.createDisplay();
+
+		eglDisplay = eglu::getDisplay(*m_defaultNativeDisplay);
+		TCU_CHECK_EGL_CALL(eglInitialize(eglDisplay, &majorVersion, &minorVersion));
+
+		m_defaultEGLDisplay = new tcu::egl::Display(eglDisplay, majorVersion, minorVersion);
+
+		// Create config list
+		{
+			vector<EGLConfig>	configs;
+			set<EGLint>			idSet; // For checking for duplicate config IDs
+
+			m_defaultEGLDisplay->getConfigs(configs);
+
+			m_configs.resize(configs.size());
+			for (int ndx = 0; ndx < (int)configs.size(); ndx++)
+			{
+				m_defaultEGLDisplay->describeConfig(configs[ndx], m_configs[ndx]);
+
+				EGLint id = m_configs[ndx].configId;
+				if (idSet.find(id) != idSet.end())
+					tcu::print("Warning: Duplicate config ID %d\n", id);
+				idSet.insert(id);
+			}
+		}
+
+		// Query supported APIs
+		{
+			const char*					clientAPIs	= eglQueryString(eglDisplay, EGL_CLIENT_APIS);
+			std::vector<std::string>	apis;
+			TCU_CHECK(clientAPIs);
+
+			split(apis, clientAPIs);
+			for (std::vector<std::string>::const_iterator apiIter = apis.begin(); apiIter != apis.end(); apiIter++)
+			{
+				EGLint parsedAPI = parseAPI(*apiIter);
+				if (parsedAPI != 0)
+					m_supportedAPIs.insert(parsedAPI);
+			}
+		}
+
+		delete m_defaultEGLDisplay;
+		m_defaultEGLDisplay = DE_NULL;
+		delete m_defaultNativeDisplay;
+		m_defaultNativeDisplay = DE_NULL;
+	}
+	catch (...)
+	{
+		delete m_defaultEGLDisplay;
+		m_defaultEGLDisplay = DE_NULL;
+		delete m_defaultNativeDisplay;
+		m_defaultNativeDisplay = DE_NULL;
+		throw;
+	}
+}
+
+EglTestContext::~EglTestContext (void)
+{
+	for (GLLibraryMap::iterator iter = m_glLibraries.begin(); iter != m_glLibraries.end(); ++iter)
+		delete iter->second;
+
+	delete m_defaultEGLDisplay;
+	delete m_defaultNativeDisplay;
+}
+
+void EglTestContext::createDefaultDisplay (void)
+{
+	EGLDisplay	eglDisplay	= EGL_NO_DISPLAY;
+	EGLint		majorVersion;
+	EGLint		minorVersion;
+
+	DE_ASSERT(!m_defaultEGLDisplay);
+	DE_ASSERT(!m_defaultNativeDisplay);
+
+	try
+	{
+		m_defaultNativeDisplay	= m_displayFactory.createDisplay();
+
+		eglDisplay = eglu::getDisplay(*m_defaultNativeDisplay);
+		TCU_CHECK_EGL_CALL(eglInitialize(eglDisplay, &majorVersion, &minorVersion));
+
+		m_defaultEGLDisplay = new tcu::egl::Display(eglDisplay, majorVersion, minorVersion);
+	}
+	catch (const std::exception&)
+	{
+		delete m_defaultEGLDisplay;
+		m_defaultEGLDisplay = DE_NULL;
+		delete m_defaultNativeDisplay;
+		m_defaultNativeDisplay = DE_NULL;
+		throw;
+	}
+}
+
+const eglu::NativeWindowFactory& EglTestContext::getNativeWindowFactory (void) const
+{
+	if (m_windowFactory)
+		return *m_windowFactory;
+	else
+		throw tcu::NotSupportedError("No default native window factory available", "", __FILE__, __LINE__);
+}
+
+const eglu::NativePixmapFactory& EglTestContext::getNativePixmapFactory (void) const
+{
+	if (m_pixmapFactory)
+		return *m_pixmapFactory;
+	else
+		throw tcu::NotSupportedError("No default native pixmap factory available", "", __FILE__, __LINE__);
+}
+
+void EglTestContext::destroyDefaultDisplay (void)
+{
+	DE_ASSERT(m_defaultEGLDisplay);
+	DE_ASSERT(m_defaultNativeDisplay);
+
+	delete m_defaultEGLDisplay;
+	m_defaultEGLDisplay = DE_NULL;
+
+	delete m_defaultNativeDisplay;
+	m_defaultNativeDisplay = DE_NULL;
+}
+
+eglu::NativeWindow* EglTestContext::createNativeWindow (EGLDisplay display, EGLConfig config, const EGLAttrib* attribList, int width, int height, eglu::WindowParams::Visibility visibility)
+{
+	if (!m_windowFactory)
+		throw tcu::NotSupportedError("Windows not supported", "", __FILE__, __LINE__);
+
+	return m_windowFactory->createWindow(m_defaultNativeDisplay, display, config, attribList, eglu::WindowParams(width, height, visibility));
+}
+
+eglu::NativePixmap* EglTestContext::createNativePixmap (EGLDisplay display, EGLConfig config, const EGLAttrib* attribList, int width, int height)
+{
+	if (!m_pixmapFactory)
+		throw tcu::NotSupportedError("Pixmaps not supported", "", __FILE__, __LINE__);
+
+	return m_pixmapFactory->createPixmap(m_defaultNativeDisplay, display, config, attribList, width, height);
+}
+
+// \todo [2014-10-06 pyry] Quite hacky, expose ApiType internals?
+static deUint32 makeKey (glu::ApiType apiType)
+{
+	return (apiType.getMajorVersion() << 8) | (apiType.getMinorVersion() << 4) | apiType.getProfile();
+}
+
+const tcu::FunctionLibrary* EglTestContext::getGLLibrary (glu::ApiType apiType) const
+{
+	tcu::FunctionLibrary*		library	= DE_NULL;
+	const deUint32				key		= makeKey(apiType);
+	GLLibraryMap::iterator		iter	= m_glLibraries.find(key);
+
+	if (iter == m_glLibraries.end())
+	{
+		library = m_testCtx.getPlatform().getEGLPlatform().createDefaultGLFunctionLibrary(apiType, m_testCtx.getCommandLine());
+		m_glLibraries.insert(std::make_pair(key, library));
+	}
+	else
+		library = iter->second;
+
+	return library;
+}
+
+deFunctionPtr EglTestContext::getGLFunction (glu::ApiType apiType, const char* name) const
+{
+	// \todo [2014-03-11 pyry] This requires fall-back to eglGetProcAddress(), right?
+	const tcu::FunctionLibrary* const	library	= getGLLibrary(apiType);
+	return library->getFunction(name);
+}
+
+void EglTestContext::getGLFunctions (glw::Functions& gl, glu::ApiType apiType) const
+{
+	const tcu::FunctionLibrary* const	library		= getGLLibrary(apiType);
+	const eglu::GLFunctionLoader		loader		(library);
+
+	// \note There may not be current context, so we can't use initFunctions().
+	glu::initCoreFunctions(&gl, &loader, apiType);
+}
+
+TestCaseGroup::TestCaseGroup (EglTestContext& eglTestCtx, const char* name, const char* description)
+	: tcu::TestCaseGroup	(eglTestCtx.getTestContext(), name, description)
+	, m_eglTestCtx			(eglTestCtx)
+{
+}
+
+TestCaseGroup::~TestCaseGroup (void)
+{
+}
+
+TestCase::TestCase (EglTestContext& eglTestCtx, const char* name, const char* description)
+	: tcu::TestCase		(eglTestCtx.getTestContext(), name, description)
+	, m_eglTestCtx		(eglTestCtx)
+{
+}
+
+TestCase::TestCase (EglTestContext& eglTestCtx, tcu::TestNodeType type,  const char* name, const char* description)
+	: tcu::TestCase		(eglTestCtx.getTestContext(), type, name, description)
+	, m_eglTestCtx		(eglTestCtx)
+{
+}
+
+TestCase::~TestCase (void)
+{
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglTestCase.hpp b/modules/egl/teglTestCase.hpp
new file mode 100644
index 0000000..494c67d
--- /dev/null
+++ b/modules/egl/teglTestCase.hpp
@@ -0,0 +1,122 @@
+#ifndef _TEGLTESTCASE_HPP
+#define _TEGLTESTCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL Test Case
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuEgl.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluConfigInfo.hpp"
+#include "tcuFunctionLibrary.hpp"
+#include "gluRenderContext.hpp"
+
+#include <set>
+#include <map>
+
+namespace eglu
+{
+class NativeDisplay;
+class NativeWindow;
+class NativePixmap;
+class NativeDisplayFactory;
+class NativeWindowFactory;
+class NativePixmapFactory;
+} // eglu
+
+namespace deqp
+{
+namespace egl
+{
+
+class EglTestContext
+{
+public:
+												EglTestContext			(tcu::TestContext& testCtx, const eglu::NativeDisplayFactory& displayFactory, const eglu::NativeWindowFactory* windowFactory, const eglu::NativePixmapFactory* pixmapFactory);
+												~EglTestContext			(void);
+
+	tcu::TestContext&							getTestContext			(void) 			{ return m_testCtx;													}
+	eglu::NativeDisplay&						getNativeDisplay		(void)			{ return *m_defaultNativeDisplay;									}
+	tcu::egl::Display&							getDisplay				(void)			{ return *m_defaultEGLDisplay;										}
+	const std::vector<eglu::ConfigInfo>&		getConfigs				(void) const	{ return m_configs;													}
+
+	const eglu::NativeWindowFactory&			getNativeWindowFactory	(void) const;
+	const eglu::NativePixmapFactory&			getNativePixmapFactory	(void) const;
+
+	eglu::NativeWindow*							createNativeWindow		(EGLDisplay display, EGLConfig config, const EGLAttrib* attribList, int width, int height, eglu::WindowParams::Visibility visibility);
+	eglu::NativePixmap*							createNativePixmap		(EGLDisplay display, EGLConfig config, const EGLAttrib* attribList, int width, int height);
+
+	deFunctionPtr								getGLFunction			(glu::ApiType apiType, const char* name) const;
+	void										getGLFunctions			(glw::Functions& gl, glu::ApiType apiType) const;
+
+	bool										isAPISupported			(EGLint api)	{ return m_supportedAPIs.find(api) != m_supportedAPIs.end();		}
+
+	// Test case wrapper will instruct test context to create display upon case init and destroy it in deinit
+	void										createDefaultDisplay	(void);
+	void										destroyDefaultDisplay	(void);
+
+private:
+												EglTestContext			(const EglTestContext&);
+	EglTestContext&								operator=				(const EglTestContext&);
+
+	const tcu::FunctionLibrary*					getGLLibrary			(glu::ApiType apiType) const;
+
+	tcu::TestContext&							m_testCtx;
+	const eglu::NativeDisplayFactory&			m_displayFactory;
+	const eglu::NativeWindowFactory*			m_windowFactory;
+	const eglu::NativePixmapFactory*			m_pixmapFactory;
+
+	typedef std::map<deUint32, tcu::FunctionLibrary*> GLLibraryMap;
+	mutable GLLibraryMap						m_glLibraries;			//!< GL library cache.
+
+	eglu::NativeDisplay*						m_defaultNativeDisplay;
+	tcu::egl::Display*							m_defaultEGLDisplay;
+	std::vector<eglu::ConfigInfo>				m_configs;
+	std::set<EGLint>							m_supportedAPIs;
+};
+
+class TestCaseGroup : public tcu::TestCaseGroup
+{
+public:
+						TestCaseGroup	(EglTestContext& eglTestCtx, const char* name, const char* description);
+	virtual				~TestCaseGroup	(void);
+
+protected:
+	EglTestContext&		m_eglTestCtx;
+};
+
+class TestCase : public tcu::TestCase
+{
+public:
+						TestCase		(EglTestContext& eglTestCtx, const char* name, const char* description);
+						TestCase		(EglTestContext& eglTestCtx, tcu::TestNodeType type, const char* name, const char* description);
+	virtual				~TestCase		(void);
+
+protected:
+	EglTestContext&		m_eglTestCtx;
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLTESTCASE_HPP
diff --git a/modules/egl/teglTestPackage.cpp b/modules/egl/teglTestPackage.cpp
new file mode 100644
index 0000000..f1ff130
--- /dev/null
+++ b/modules/egl/teglTestPackage.cpp
@@ -0,0 +1,354 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL Test Package
+ *//*--------------------------------------------------------------------*/
+
+#include "teglTestPackage.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuPlatform.hpp"
+#include "tcuCommandLine.hpp"
+
+#include "egluPlatform.hpp"
+#include "egluNativeDisplay.hpp"
+#include "egluNativeWindow.hpp"
+#include "egluNativePixmap.hpp"
+
+#include "teglInfoTests.hpp"
+#include "teglCreateContextTests.hpp"
+#include "teglQueryContextTests.hpp"
+#include "teglCreateSurfaceTests.hpp"
+#include "teglQuerySurfaceTests.hpp"
+#include "teglChooseConfigTests.hpp"
+#include "teglQueryConfigTests.hpp"
+#include "teglColorClearTests.hpp"
+#include "teglRenderTests.hpp"
+#include "teglImageTests.hpp"
+#include "teglGLES2SharingTests.hpp"
+#include "teglNegativeApiTests.hpp"
+#include "teglSyncTests.hpp"
+#include "teglMultiThreadTests.hpp"
+#include "teglGetProcAddressTests.hpp"
+#include "teglMemoryStressTests.hpp"
+#include "teglMakeCurrentPerfTests.hpp"
+#include "teglGLES2SharedRenderingPerfTests.hpp"
+#include "teglPreservingSwapTests.hpp"
+#include "teglClientExtensionTests.hpp"
+#include "teglCreateContextExtTests.hpp"
+#include "teglSurfacelessContextTests.hpp"
+#include "teglSwapBuffersTests.hpp"
+#include "teglNativeColorMappingTests.hpp"
+#include "teglNativeCoordMappingTests.hpp"
+#include "teglResizeTests.hpp"
+
+#include <typeinfo>
+
+using std::vector;
+
+namespace deqp
+{
+namespace egl
+{
+
+class StressTests : public TestCaseGroup
+{
+public:
+	StressTests (EglTestContext& eglTestCtx)
+		: TestCaseGroup(eglTestCtx, "stress", "EGL stress tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new MemoryStressTests(m_eglTestCtx));
+	}
+};
+
+class PerformanceTests : public TestCaseGroup
+{
+public:
+	PerformanceTests (EglTestContext& eglTestCtx)
+		: TestCaseGroup(eglTestCtx, "performance", "EGL performance tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new MakeCurrentPerfTests			(m_eglTestCtx));
+		addChild(new GLES2SharedRenderingPerfTests	(m_eglTestCtx));
+	}
+};
+
+class FunctionalTests : public TestCaseGroup
+{
+public:
+	FunctionalTests (EglTestContext& eglTestCtx)
+		: TestCaseGroup(eglTestCtx, "functional", "EGL functional tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new CreateContextTests			(m_eglTestCtx));
+		addChild(new QueryContextTests			(m_eglTestCtx));
+		addChild(new CreateSurfaceTests			(m_eglTestCtx));
+		addChild(new QuerySurfaceTests			(m_eglTestCtx));
+		addChild(new QueryConfigTests			(m_eglTestCtx));
+		addChild(new ChooseConfigTests			(m_eglTestCtx));
+		addChild(new ColorClearTests			(m_eglTestCtx));
+		addChild(new RenderTests				(m_eglTestCtx));
+		addChild(new ImageTests					(m_eglTestCtx));
+		addChild(new SharingTests				(m_eglTestCtx));
+		addChild(new NegativeApiTests			(m_eglTestCtx));
+		addChild(new FenceSyncTests				(m_eglTestCtx));
+		addChild(new MultiThreadedTests			(m_eglTestCtx));
+		addChild(new GetProcAddressTests		(m_eglTestCtx));
+		addChild(new PreservingSwapTests		(m_eglTestCtx));
+		addChild(new ClientExtensionTests		(m_eglTestCtx));
+		addChild(new CreateContextExtTests		(m_eglTestCtx));
+		addChild(new SurfacelessContextTests	(m_eglTestCtx));
+		addChild(new SwapBuffersTests			(m_eglTestCtx));
+		addChild(new NativeColorMappingTests	(m_eglTestCtx));
+		addChild(new NativeCoordMappingTests	(m_eglTestCtx));
+		addChild(new ReusableSyncTests			(m_eglTestCtx));
+		addChild(new ResizeTests				(m_eglTestCtx));
+	}
+};
+
+TestCaseWrapper::TestCaseWrapper (EglTestContext& eglTestCtx)
+	: tcu::TestCaseWrapper	(eglTestCtx.getTestContext())
+	, m_eglTestCtx			(eglTestCtx)
+{
+}
+
+TestCaseWrapper::~TestCaseWrapper (void)
+{
+}
+
+bool TestCaseWrapper::initTestCase (tcu::TestCase* testCase)
+{
+	tcu::TestLog& log = m_eglTestCtx.getTestContext().getLog();
+
+	// Create display
+	try
+	{
+		m_eglTestCtx.createDefaultDisplay();
+	}
+	catch (const std::exception& e)
+	{
+		log << e;
+		m_eglTestCtx.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed to initialize EGL for default display");
+		return false;
+	}
+
+	return tcu::TestCaseWrapper::initTestCase(testCase);
+}
+
+bool TestCaseWrapper::deinitTestCase (tcu::TestCase* testCase)
+{
+	tcu::TestLog& log = m_eglTestCtx.getTestContext().getLog();
+
+	bool deinitOk = tcu::TestCaseWrapper::deinitTestCase(testCase);
+
+	// Destroy display
+	try
+	{
+		TCU_CHECK_EGL_CALL(eglMakeCurrent(m_eglTestCtx.getDisplay().getEGLDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+		m_eglTestCtx.destroyDefaultDisplay();
+	}
+	catch (const std::exception& e)
+	{
+		log << e;
+		log << tcu::TestLog::Message << "Error in EGL deinit, test program will teminate." << tcu::TestLog::EndMessage;
+		return false;
+	}
+
+	return deinitOk;
+}
+
+tcu::TestNode::IterateResult TestCaseWrapper::iterateTestCase (tcu::TestCase* testCase)
+{
+	return tcu::TestCaseWrapper::iterateTestCase(testCase);
+}
+
+PackageContext::PackageContext (tcu::TestContext& testCtx)
+	: m_eglTestCtx	(DE_NULL)
+	, m_caseWrapper	(DE_NULL)
+{
+	const eglu::NativeDisplayFactoryRegistry&	dpyFactoryRegistry	= testCtx.getPlatform().getEGLPlatform().getNativeDisplayFactoryRegistry();
+	const std::string							displayFactoryName	= testCtx.getCommandLine().getEGLDisplayType();
+	const std::string							windowFactoryName	= testCtx.getCommandLine().getEGLWindowType();
+	const std::string							pixmapFactoryName	= testCtx.getCommandLine().getEGLPixmapType();
+
+	const eglu::NativeDisplayFactory*			displayFactory		= DE_NULL;
+	const eglu::NativeWindowFactory*			windowFactory		= DE_NULL;
+	const eglu::NativePixmapFactory*			pixmapFactory		= DE_NULL;
+
+	if (dpyFactoryRegistry.empty())
+	{
+		tcu::print("ERROR: Platform doesn't support any EGL native display types!\n");
+		throw tcu::NotSupportedError("Platform doesn't have EGL any native display factories", DE_NULL, __FILE__, __LINE__);
+	}
+
+	if (displayFactoryName.empty())
+		displayFactory = dpyFactoryRegistry.getDefaultFactory();
+	else
+	{
+		displayFactory = dpyFactoryRegistry.getFactoryByName(displayFactoryName.c_str());
+
+		if (!displayFactory)
+		{
+			tcu::print("ERROR: Unknown/unsupported EGL native display type '%s'\n", displayFactoryName.c_str());
+			tcu::print("Supported EGL native display types:\n");
+
+			for (int factoryNdx = 0; factoryNdx < (int)dpyFactoryRegistry.getFactoryCount(); factoryNdx++)
+			{
+				const char* name = dpyFactoryRegistry.getFactoryByIndex(factoryNdx)->getName();
+				const char* desc = dpyFactoryRegistry.getFactoryByIndex(factoryNdx)->getDescription();
+
+				tcu::print("  %s: %s\n", name, desc);
+			}
+
+			throw tcu::NotSupportedError(("Unknown EGL native display type '" + displayFactoryName + "'.").c_str(), DE_NULL, __FILE__, __LINE__);
+		}
+	}
+
+	tcu::print("Using EGL native display type '%s'\n", displayFactory->getName());
+
+	if (!displayFactory->getNativeWindowRegistry().empty())
+	{
+		windowFactory = windowFactoryName.empty() ? displayFactory->getNativeWindowRegistry().getDefaultFactory()
+												  : displayFactory->getNativeWindowRegistry().getFactoryByName(windowFactoryName.c_str());
+
+		if (!windowFactory)
+		{
+			DE_ASSERT(!windowFactoryName.empty());
+			tcu::print("ERROR: Unknown/unsupported EGL native window type '%s'\n", windowFactoryName.c_str());
+			tcu::print("Supported EGL native window types for native display '%s':\n", displayFactory->getName());
+
+			for (int factoryNdx = 0; factoryNdx < (int)displayFactory->getNativeWindowRegistry().getFactoryCount(); factoryNdx++)
+			{
+				const char* name = displayFactory->getNativeWindowRegistry().getFactoryByIndex(factoryNdx)->getName();
+				const char* desc = displayFactory->getNativeWindowRegistry().getFactoryByIndex(factoryNdx)->getDescription();
+
+				tcu::print("  %s: %s\n", name, desc);
+			}
+
+			throw tcu::NotSupportedError(("Unknown EGL native window type '" + windowFactoryName + "'").c_str(), DE_NULL, __FILE__, __LINE__);
+		}
+	}
+	else
+		tcu::print("Warning: EGL native display doesn't have any native window types.\n");
+
+	if (!displayFactory->getNativePixmapRegistry().empty())
+	{
+		pixmapFactory = pixmapFactoryName.empty() ? displayFactory->getNativePixmapRegistry().getDefaultFactory()
+												  : displayFactory->getNativePixmapRegistry().getFactoryByName(pixmapFactoryName.c_str());
+
+		if (!pixmapFactory)
+		{
+			DE_ASSERT(!pixmapFactoryName.empty());
+			tcu::print("ERROR: Unknown/unsupported EGL native pixmap type '%s'\n", pixmapFactoryName.c_str());
+			tcu::print("Supported EGL native pixmap types for native display '%s':\n", displayFactory->getName());
+
+			for (int factoryNdx = 0; factoryNdx < (int)displayFactory->getNativePixmapRegistry().getFactoryCount(); factoryNdx++)
+			{
+				const char* name = displayFactory->getNativePixmapRegistry().getFactoryByIndex(factoryNdx)->getName();
+				const char* desc = displayFactory->getNativePixmapRegistry().getFactoryByIndex(factoryNdx)->getDescription();
+
+				tcu::print("  %s: %s\n", name, desc);
+			}
+
+			throw tcu::NotSupportedError(("Unknown EGL native pixmap type '" + pixmapFactoryName + "'").c_str(), DE_NULL, __FILE__, __LINE__);
+		}
+	}
+	else
+		tcu::print("Warning: EGL native display doesn't have any native pixmap types.\n");
+
+	if (windowFactory)
+		tcu::print("Using EGL native window type '%s'\n", windowFactory->getName());
+	if (pixmapFactory)
+		tcu::print("Using EGL native pixmap type '%s'\n", pixmapFactory->getName());
+
+	try
+	{
+		m_eglTestCtx	= new EglTestContext(testCtx, *displayFactory, windowFactory, pixmapFactory);
+		m_caseWrapper	= new TestCaseWrapper(*m_eglTestCtx);
+	}
+	catch (...)
+	{
+		delete m_caseWrapper;
+		delete m_eglTestCtx;
+
+		throw;
+	}
+}
+
+PackageContext::~PackageContext (void)
+{
+	delete m_caseWrapper;
+	delete m_eglTestCtx;
+}
+
+TestPackage::TestPackage (tcu::TestContext& testCtx)
+	: tcu::TestPackage	(testCtx, "dEQP-EGL", "dEQP EGL Tests")
+	, m_packageCtx		(DE_NULL)
+	, m_archive			(testCtx.getRootArchive(), "egl/")
+{
+}
+
+TestPackage::~TestPackage (void)
+{
+	// Destroy children first since destructors may access context.
+	TestNode::deinit();
+	delete m_packageCtx;
+}
+
+void TestPackage::init (void)
+{
+	DE_ASSERT(!m_packageCtx);
+	m_packageCtx = new PackageContext(m_testCtx);
+
+	try
+	{
+		addChild(new InfoTests				(m_packageCtx->getEglTestContext()));
+		addChild(new FunctionalTests		(m_packageCtx->getEglTestContext()));
+		addChild(new PerformanceTests		(m_packageCtx->getEglTestContext()));
+		addChild(new StressTests			(m_packageCtx->getEglTestContext()));
+	}
+	catch (...)
+	{
+		delete m_packageCtx;
+		m_packageCtx = DE_NULL;
+
+		throw;
+	}
+}
+
+void TestPackage::deinit (void)
+{
+	tcu::TestNode::deinit();
+	delete m_packageCtx;
+	m_packageCtx = DE_NULL;
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglTestPackage.hpp b/modules/egl/teglTestPackage.hpp
new file mode 100644
index 0000000..fa5bd5f
--- /dev/null
+++ b/modules/egl/teglTestPackage.hpp
@@ -0,0 +1,85 @@
+#ifndef _TEGLTESTPACKAGE_HPP
+#define _TEGLTESTPACKAGE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL Test Package
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestPackage.hpp"
+#include "teglTestCase.hpp"
+#include "tcuResource.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class TestCaseWrapper : public tcu::TestCaseWrapper
+{
+public:
+									TestCaseWrapper			(EglTestContext& eglTestContext);
+									~TestCaseWrapper		(void);
+
+	bool							initTestCase			(tcu::TestCase* testCase);
+	bool							deinitTestCase			(tcu::TestCase* testCase);
+
+	tcu::TestNode::IterateResult	iterateTestCase			(tcu::TestCase* testCase);
+
+private:
+	EglTestContext&					m_eglTestCtx;
+};
+
+class PackageContext
+{
+public:
+									PackageContext			(tcu::TestContext& testCtx);
+									~PackageContext			(void);
+
+	EglTestContext&					getEglTestContext		(void) { return *m_eglTestCtx;	}
+	tcu::TestCaseWrapper&			getTestCaseWrapper		(void) { return *m_caseWrapper;	}
+
+private:
+	EglTestContext*					m_eglTestCtx;
+	TestCaseWrapper*				m_caseWrapper;
+};
+
+class TestPackage : public tcu::TestPackage
+{
+public:
+									TestPackage				(tcu::TestContext& testCtx);
+	virtual							~TestPackage			(void);
+
+	virtual void					init					(void);
+	virtual void					deinit					(void);
+
+	tcu::TestCaseWrapper&			getTestCaseWrapper		(void) { return m_packageCtx->getTestCaseWrapper(); }
+	tcu::ResourcePrefix&			getArchive				(void) { return m_archive; }
+
+private:
+	PackageContext*					m_packageCtx;
+	tcu::ResourcePrefix				m_archive;
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLTESTPACKAGE_HPP
diff --git a/modules/egl/teglTestPackageEntry.cpp b/modules/egl/teglTestPackageEntry.cpp
new file mode 100644
index 0000000..1ffc623
--- /dev/null
+++ b/modules/egl/teglTestPackageEntry.cpp
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief EGL Test Package Entry Point.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglTestPackage.hpp"
+
+// Register package to test executor.
+
+static tcu::TestPackage* createTestPackage (tcu::TestContext& testCtx)
+{
+	return new deqp::egl::TestPackage(testCtx);
+}
+
+tcu::TestPackageDescriptor g_eglPackageDescriptor("dEQP-EGL", createTestPackage);
diff --git a/modules/egl/teglVGRenderUtil.cpp b/modules/egl/teglVGRenderUtil.cpp
new file mode 100644
index 0000000..afb68f6
--- /dev/null
+++ b/modules/egl/teglVGRenderUtil.cpp
@@ -0,0 +1,74 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenVG render utils.
+ *//*--------------------------------------------------------------------*/
+
+#include "teglVGRenderUtil.hpp"
+
+#if defined(DEQP_SUPPORT_VG)
+#	include <VG/openvg.h>
+#endif
+
+using std::vector;
+
+namespace deqp
+{
+namespace egl
+{
+namespace vg
+{
+
+#if defined(DEQP_SUPPORT_VG)
+
+void clear (int x, int y, int width, int height, const tcu::Vec4& color)
+{
+	vgSetfv(VG_CLEAR_COLOR, 4, color.getPtr());
+	vgClear(x, y, width, height);
+	TCU_CHECK(vgGetError() == VG_NO_ERROR);
+}
+
+void readPixels (tcu::Surface& dst, int x, int y, int width, int height)
+{
+	dst.setSize(width, height);
+	vgReadPixels(dst.getAccess().getDataPtr(), width*4, VG_sRGBA_8888, 0, 0, width, height);
+}
+
+#else // DEQP_SUPPORT_VG
+
+void clear (int x, int y, int width, int height, const tcu::Vec4& color)
+{
+	DE_UNREF(x && y && width && height);
+	DE_UNREF(color);
+	throw tcu::NotSupportedError("OpenVG is not supported", "", __FILE__, __LINE__);
+}
+
+void readPixels (tcu::Surface& dst, int x, int y, int width, int height)
+{
+	DE_UNREF(x && y && width && height);
+	DE_UNREF(dst);
+	throw tcu::NotSupportedError("OpenVG is not supported", "", __FILE__, __LINE__);
+}
+
+#endif // DEQP_SUPPORT_VG
+
+} // vg
+} // egl
+} // deqp
diff --git a/modules/egl/teglVGRenderUtil.hpp b/modules/egl/teglVGRenderUtil.hpp
new file mode 100644
index 0000000..b9a9054
--- /dev/null
+++ b/modules/egl/teglVGRenderUtil.hpp
@@ -0,0 +1,44 @@
+#ifndef _TEGLVGRENDERUTIL_HPP
+#define _TEGLVGRENDERUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenVG render utils.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuSurface.hpp"
+#include "tcuVector.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+namespace vg
+{
+
+void	clear		(int x, int y, int width, int height, const tcu::Vec4& color);
+void	readPixels	(tcu::Surface& dst, int x, int y, int width, int height);
+
+} // vg
+} // egl
+} // deqp
+
+#endif // _TEGLVGRENDERUTIL_HPP
diff --git a/modules/gles2/CMakeLists.txt b/modules/gles2/CMakeLists.txt
new file mode 100644
index 0000000..053f03b
--- /dev/null
+++ b/modules/gles2/CMakeLists.txt
@@ -0,0 +1,47 @@
+# dEQP-GLES2
+
+include_directories(
+	accuracy
+	functional
+	performance
+	randomshaders
+	stress
+	../glshared
+	${CMAKE_CURRENT_SOURCE_DIR}
+	)
+
+add_subdirectory(accuracy)
+add_subdirectory(functional)
+add_subdirectory(performance)
+add_subdirectory(stress)
+
+set(DEQP_GLES2_SRCS
+	tes2CapabilityTests.cpp
+	tes2CapabilityTests.hpp
+	tes2Context.cpp
+	tes2Context.hpp
+	tes2InfoTests.cpp
+	tes2InfoTests.hpp
+	tes2TestCase.cpp
+	tes2TestCase.hpp
+	tes2TestCaseWrapper.cpp
+	tes2TestCaseWrapper.hpp
+	tes2TestPackage.cpp
+	tes2TestPackage.hpp
+	)
+
+set(DEQP_GLES2_LIBS
+	deqp-gles2-accuracy
+	deqp-gles2-functional
+	deqp-gles2-performance
+	deqp-gles2-stress
+	tcutil
+	glutil
+	${DEQP_GLES2_LIBRARIES}
+	)
+
+add_deqp_module(deqp-gles2 "${DEQP_GLES2_SRCS}" "${DEQP_GLES2_LIBS}" tes2TestPackageEntry.cpp)
+
+# Data directories
+add_data_dir(deqp-gles2 ../../data/gles2/data		gles2/data)
+add_data_dir(deqp-gles2 ../../data/gles2/shaders	gles2/shaders)
diff --git a/modules/gles2/accuracy/CMakeLists.txt b/modules/gles2/accuracy/CMakeLists.txt
new file mode 100644
index 0000000..6fd69fc
--- /dev/null
+++ b/modules/gles2/accuracy/CMakeLists.txt
@@ -0,0 +1,15 @@
+# dEQP-GLES2.accuracy
+
+set(DEQP_GLES2_ACCURACY_SRCS
+	es2aAccuracyTests.cpp
+	es2aAccuracyTests.hpp
+	es2aTextureFilteringTests.cpp
+	es2aTextureFilteringTests.hpp
+	es2aTextureMipmapTests.cpp
+	es2aTextureMipmapTests.hpp
+	es2aVaryingInterpolationTests.cpp
+	es2aVaryingInterpolationTests.hpp
+	)
+
+add_library(deqp-gles2-accuracy STATIC ${DEQP_GLES2_ACCURACY_SRCS})
+target_link_libraries(deqp-gles2-accuracy deqp-gl-shared glutil tcutil ${DEQP_GLES2_LIBRARIES})
diff --git a/modules/gles2/accuracy/es2aAccuracyTests.cpp b/modules/gles2/accuracy/es2aAccuracyTests.cpp
new file mode 100644
index 0000000..2b1738d
--- /dev/null
+++ b/modules/gles2/accuracy/es2aAccuracyTests.cpp
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2aAccuracyTests.hpp"
+#include "es2aTextureFilteringTests.hpp"
+#include "es2aTextureMipmapTests.hpp"
+#include "es2aVaryingInterpolationTests.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Accuracy
+{
+
+class TextureTests : public TestCaseGroup
+{
+public:
+	TextureTests (Context& context)
+		: TestCaseGroup(context, "texture", "Texturing Accuracy Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new TextureFilteringTests	(m_context));
+		addChild(new TextureMipmapTests		(m_context));
+	}
+};
+
+AccuracyTests::AccuracyTests (Context& context)
+	: TestCaseGroup(context, "accuracy", "Accuracy Tests")
+{
+}
+
+AccuracyTests::~AccuracyTests (void)
+{
+}
+
+void AccuracyTests::init (void)
+{
+	addChild(new VaryingInterpolationTests	(m_context));
+	addChild(new TextureTests				(m_context));
+}
+
+} // Accuracy
+} // gles2
+} // deqp
diff --git a/modules/gles2/accuracy/es2aAccuracyTests.hpp b/modules/gles2/accuracy/es2aAccuracyTests.hpp
new file mode 100644
index 0000000..188b013
--- /dev/null
+++ b/modules/gles2/accuracy/es2aAccuracyTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2AACCURACYTESTS_HPP
+#define _ES2AACCURACYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Accuracy
+{
+
+class AccuracyTests : public TestCaseGroup
+{
+public:
+						AccuracyTests		(Context& context);
+						~AccuracyTests		(void);
+
+	void				init				(void);
+
+private:
+						AccuracyTests		(const AccuracyTests& other);
+	AccuracyTests&		operator=			(const AccuracyTests& other);
+};
+
+} // Accuracy
+} // gles2
+} // deqp
+
+#endif // _ES2AACCURACYTESTS_HPP
diff --git a/modules/gles2/accuracy/es2aTextureFilteringTests.cpp b/modules/gles2/accuracy/es2aTextureFilteringTests.cpp
new file mode 100644
index 0000000..2321491
--- /dev/null
+++ b/modules/gles2/accuracy/es2aTextureFilteringTests.cpp
@@ -0,0 +1,785 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2aTextureFilteringTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "deStringUtil.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+using std::string;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Accuracy
+{
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using tcu::Sampler;
+using namespace glu;
+using namespace gls::TextureTestUtil;
+
+class Texture2DFilteringCase : public tcu::TestCase
+{
+public:
+								Texture2DFilteringCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, deUint32 dataType, int width, int height);
+								Texture2DFilteringCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames);
+								~Texture2DFilteringCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Texture2DFilteringCase		(const Texture2DFilteringCase& other);
+	Texture2DFilteringCase&		operator=					(const Texture2DFilteringCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	deUint32					m_minFilter;
+	deUint32					m_magFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+
+	deUint32					m_format;
+	deUint32					m_dataType;
+	int							m_width;
+	int							m_height;
+
+	std::vector<std::string>	m_filenames;
+
+	std::vector<glu::Texture2D*>	m_textures;
+	TextureRenderer					m_renderer;
+};
+
+
+Texture2DFilteringCase::Texture2DFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, deUint32 dataType, int width, int height)
+	: TestCase			(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(ctxInfo)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_width			(width)
+	, m_height			(height)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+Texture2DFilteringCase::Texture2DFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames)
+	: TestCase			(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(ctxInfo)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(GL_NONE)
+	, m_dataType		(GL_NONE)
+	, m_width			(0)
+	, m_height			(0)
+	, m_filenames		(filenames)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+Texture2DFilteringCase::~Texture2DFilteringCase (void)
+{
+	deinit();
+}
+
+void Texture2DFilteringCase::init (void)
+{
+	try
+	{
+		if (!m_filenames.empty())
+		{
+			m_textures.reserve(1);
+			m_textures.push_back(glu::Texture2D::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size(), m_filenames));
+		}
+		else
+		{
+			// Create 2 textures.
+			m_textures.reserve(2);
+			for (int ndx = 0; ndx < 2; ndx++)
+				m_textures.push_back(new glu::Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height));
+
+			const bool				mipmaps		= deIsPowerOfTwo32(m_width) && deIsPowerOfTwo32(m_height);
+			const int				numLevels	= mipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;
+			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+			tcu::Vec4				cBias		= fmtInfo.valueMin;
+			tcu::Vec4				cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+			// Fill first gradient texture.
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				tcu::Vec4 gMin = tcu::Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
+				tcu::Vec4 gMax = tcu::Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;
+
+				m_textures[0]->getRefTexture().allocLevel(levelNdx);
+				tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
+			}
+
+			// Fill second with grid texture.
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				deUint32	step	= 0x00ffffff / numLevels;
+				deUint32	rgb		= step*levelNdx;
+				deUint32	colorA	= 0xff000000 | rgb;
+				deUint32	colorB	= 0xff000000 | ~rgb;
+
+				m_textures[1]->getRefTexture().allocLevel(levelNdx);
+				tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+			}
+
+			// Upload.
+			for (std::vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+				(*i)->upload();
+		}
+	}
+	catch (...)
+	{
+		// Clean up to save memory.
+		Texture2DFilteringCase::deinit();
+		throw;
+	}
+}
+
+void Texture2DFilteringCase::deinit (void)
+{
+	for (std::vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	m_renderer.clear();
+}
+
+Texture2DFilteringCase::IterateResult Texture2DFilteringCase::iterate (void)
+{
+	const glw::Functions&		gl					= m_renderCtx.getFunctions();
+	TestLog&					log					= m_testCtx.getLog();
+	const int					defViewportWidth	= 256;
+	const int					defViewportHeight	= 256;
+	RandomViewport				viewport			(m_renderCtx.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+	tcu::Surface				renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface				referenceFrame		(viewport.width, viewport.height);
+	const tcu::TextureFormat&	texFmt				= m_textures[0]->getRefTexture().getFormat();
+	tcu::TextureFormatInfo		fmtInfo				= tcu::getTextureFormatInfo(texFmt);
+	ReferenceParams				refParams			(TEXTURETYPE_2D);
+	vector<float>				texCoord;
+
+	// Accuracy measurements are off unless viewport size is 256x256
+	if (viewport.width < defViewportWidth || viewport.height < defViewportHeight)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Viewport is divided into 4 sections.
+	int				leftWidth			= viewport.width / 2;
+	int				rightWidth			= viewport.width - leftWidth;
+	int				bottomHeight		= viewport.height / 2;
+	int				topHeight			= viewport.height - bottomHeight;
+
+	int				curTexNdx			= 0;
+
+	// Use unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture(GL_TEXTURE_2D, m_textures[curTexNdx]->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	// Setup params for reference.
+	refParams.sampler		= mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	refParams.samplerType	= getSamplerType(texFmt);
+	refParams.lodMode		= LODMODE_EXACT;
+	refParams.colorBias		= fmtInfo.lookupBias;
+	refParams.colorScale	= fmtInfo.lookupScale;
+
+	// Bottom left: Minification
+	{
+		gl.viewport(viewport.x, viewport.y, leftWidth, bottomHeight);
+
+		computeQuadTexCoord2D(texCoord, tcu::Vec2(-4.0f, -4.5f), tcu::Vec2(4.0f, 2.5f));
+
+		m_renderer.renderQuad(0, &texCoord[0], refParams);
+		sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), 0, 0, leftWidth, bottomHeight),
+					  m_textures[curTexNdx]->getRefTexture(), &texCoord[0], refParams);
+	}
+
+	// Bottom right: Magnification
+	{
+		gl.viewport(viewport.x+leftWidth, viewport.y, rightWidth, bottomHeight);
+
+		computeQuadTexCoord2D(texCoord, tcu::Vec2(-0.5f, 0.75f), tcu::Vec2(0.25f, 1.25f));
+
+		m_renderer.renderQuad(0, &texCoord[0], refParams);
+		sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), leftWidth, 0, rightWidth, bottomHeight),
+					  m_textures[curTexNdx]->getRefTexture(), &texCoord[0], refParams);
+	}
+
+	if (m_textures.size() >= 2)
+	{
+		curTexNdx += 1;
+
+		// Setup second texture.
+		gl.bindTexture(GL_TEXTURE_2D, m_textures[curTexNdx]->getGLTexture());
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	}
+
+	// Top left: Minification
+	// \note Minification is chosen so that 0.0 < lod <= 0.5. This way special minification threshold rule will be triggered.
+	{
+		gl.viewport(viewport.x, viewport.y+bottomHeight, leftWidth, topHeight);
+
+		float	sMin		= -0.5f;
+		float	tMin		= -0.2f;
+		float	sRange		= ((float)leftWidth * 1.2f) / (float)m_textures[curTexNdx]->getRefTexture().getWidth();
+		float	tRange		= ((float)topHeight * 1.1f) / (float)m_textures[curTexNdx]->getRefTexture().getHeight();
+
+		computeQuadTexCoord2D(texCoord, tcu::Vec2(sMin, tMin), tcu::Vec2(sMin+sRange, tMin+tRange));
+
+		m_renderer.renderQuad(0, &texCoord[0], refParams);
+		sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), 0, bottomHeight, leftWidth, topHeight),
+					  m_textures[curTexNdx]->getRefTexture(), &texCoord[0], refParams);
+	}
+
+	// Top right: Magnification
+	{
+		gl.viewport(viewport.x+leftWidth, viewport.y+bottomHeight, rightWidth, topHeight);
+
+		computeQuadTexCoord2D(texCoord, tcu::Vec2(-0.5f, 0.75f), tcu::Vec2(0.25f, 1.25f));
+
+		m_renderer.renderQuad(0, &texCoord[0], refParams);
+		sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), leftWidth, bottomHeight, rightWidth, topHeight),
+					  m_textures[curTexNdx]->getRefTexture(), &texCoord[0], refParams);
+	}
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log.
+	{
+		DE_ASSERT(getNodeType() == tcu::NODETYPE_ACCURACY);
+
+		const int	bestScoreDiff	= 16;
+		const int	worstScoreDiff	= 3200;
+
+		int score = measureAccuracy(log, referenceFrame, renderedFrame, bestScoreDiff, worstScoreDiff);
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(score).c_str());
+	}
+
+	return STOP;
+}
+
+class TextureCubeFilteringCase : public tcu::TestCase
+{
+public:
+								TextureCubeFilteringCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, deUint32 dataType, int width, int height);
+								TextureCubeFilteringCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames);
+								~TextureCubeFilteringCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								TextureCubeFilteringCase	(const TextureCubeFilteringCase& other);
+	TextureCubeFilteringCase&	operator=					(const TextureCubeFilteringCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	deUint32					m_minFilter;
+	deUint32					m_magFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+
+	deUint32					m_format;
+	deUint32					m_dataType;
+	int							m_width;
+	int							m_height;
+
+	std::vector<std::string>	m_filenames;
+
+	std::vector<glu::TextureCube*>	m_textures;
+	TextureRenderer					m_renderer;
+};
+
+
+TextureCubeFilteringCase::TextureCubeFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, deUint32 dataType, int width, int height)
+	: TestCase					(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx				(renderCtx)
+	, m_renderCtxInfo			(ctxInfo)
+	, m_minFilter				(minFilter)
+	, m_magFilter				(magFilter)
+	, m_wrapS					(wrapS)
+	, m_wrapT					(wrapT)
+	, m_format					(format)
+	, m_dataType				(dataType)
+	, m_width					(width)
+	, m_height					(height)
+	, m_renderer				(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+TextureCubeFilteringCase::TextureCubeFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames)
+	: TestCase					(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx				(renderCtx)
+	, m_renderCtxInfo			(ctxInfo)
+	, m_minFilter				(minFilter)
+	, m_magFilter				(magFilter)
+	, m_wrapS					(wrapS)
+	, m_wrapT					(wrapT)
+	, m_format					(GL_NONE)
+	, m_dataType				(GL_NONE)
+	, m_width					(0)
+	, m_height					(0)
+	, m_filenames				(filenames)
+	, m_renderer				(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+TextureCubeFilteringCase::~TextureCubeFilteringCase (void)
+{
+	deinit();
+}
+
+void TextureCubeFilteringCase::init (void)
+{
+	try
+	{
+		if (!m_filenames.empty())
+		{
+			m_textures.reserve(1);
+			m_textures.push_back(glu::TextureCube::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size() / 6, m_filenames));
+		}
+		else
+		{
+			m_textures.reserve(2);
+			DE_ASSERT(m_width == m_height);
+			for (int ndx = 0; ndx < 2; ndx++)
+				m_textures.push_back(new glu::TextureCube(m_renderCtx, m_format, m_dataType, m_width));
+
+			const bool				mipmaps		= deIsPowerOfTwo32(m_width) && deIsPowerOfTwo32(m_height);
+			const int				numLevels	= mipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;
+			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+			tcu::Vec4				cBias		= fmtInfo.valueMin;
+			tcu::Vec4				cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+			// Fill first with gradient texture.
+			static const tcu::Vec4 gradients[tcu::CUBEFACE_LAST][2] =
+			{
+				{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
+				{ tcu::Vec4( 0.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
+				{ tcu::Vec4(-1.0f,  0.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
+				{ tcu::Vec4(-1.0f, -1.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
+				{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
+				{ tcu::Vec4( 0.0f,  0.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
+			};
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			{
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					m_textures[0]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+					tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0]*cScale + cBias, gradients[face][1]*cScale + cBias);
+				}
+			}
+
+			// Fill second with grid texture.
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			{
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					deUint32	step	= 0x00ffffff / (numLevels*tcu::CUBEFACE_LAST);
+					deUint32	rgb		= step*levelNdx*face;
+					deUint32	colorA	= 0xff000000 | rgb;
+					deUint32	colorB	= 0xff000000 | ~rgb;
+
+					m_textures[1]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+					tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+				}
+			}
+
+			// Upload.
+			for (std::vector<glu::TextureCube*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+				(*i)->upload();
+		}
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		TextureCubeFilteringCase::deinit();
+		throw;
+	}
+}
+
+void TextureCubeFilteringCase::deinit (void)
+{
+	for (std::vector<glu::TextureCube*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	m_renderer.clear();
+}
+
+static void renderFaces (
+	const glw::Functions&		gl,
+	const SurfaceAccess&		dstRef,
+	const tcu::TextureCube&		refTexture,
+	const ReferenceParams&		params,
+	TextureRenderer&			renderer,
+	int							x,
+	int							y,
+	int							width,
+	int							height,
+	const tcu::Vec2&			bottomLeft,
+	const tcu::Vec2&			topRight,
+	const tcu::Vec2&			texCoordTopRightFactor)
+{
+	DE_ASSERT(width == dstRef.getWidth() && height == dstRef.getHeight());
+
+	vector<float> texCoord;
+
+	DE_STATIC_ASSERT(tcu::CUBEFACE_LAST == 6);
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+	{
+		bool	isRightmost		= (face == 2) || (face == 5);
+		bool	isTop			= face >= 3;
+		int		curX			= (face % 3) * (width  / 3);
+		int		curY			= (face / 3) * (height / 2);
+		int		curW			= isRightmost	? (width-curX)	: (width	/ 3);
+		int		curH			= isTop			? (height-curY)	: (height	/ 2);
+
+		computeQuadTexCoordCube(texCoord, (tcu::CubeFace)face, bottomLeft, topRight);
+
+		{
+			// Move the top and right edges of the texture coord quad. This is useful when we want a cube edge visible.
+			int texCoordSRow = face == tcu::CUBEFACE_NEGATIVE_X || face == tcu::CUBEFACE_POSITIVE_X ? 2 : 0;
+			int texCoordTRow = face == tcu::CUBEFACE_NEGATIVE_Y || face == tcu::CUBEFACE_POSITIVE_Y ? 2 : 1;
+			texCoord[6 + texCoordSRow] *= texCoordTopRightFactor.x();
+			texCoord[9 + texCoordSRow] *= texCoordTopRightFactor.x();
+			texCoord[3 + texCoordTRow] *= texCoordTopRightFactor.y();
+			texCoord[9 + texCoordTRow] *= texCoordTopRightFactor.y();
+		}
+
+		gl.viewport(x+curX, y+curY, curW, curH);
+
+		renderer.renderQuad(0, &texCoord[0], params);
+
+		sampleTexture(SurfaceAccess(dstRef, curX, curY, curW, curH), refTexture, &texCoord[0], params);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Post render");
+}
+
+TextureCubeFilteringCase::IterateResult TextureCubeFilteringCase::iterate (void)
+{
+	const glw::Functions&		gl					= m_renderCtx.getFunctions();
+	TestLog&					log					= m_testCtx.getLog();
+	const int					cellSize			= 28;
+	const int					defViewportWidth	= cellSize*6;
+	const int					defViewportHeight	= cellSize*4;
+	RandomViewport				viewport			(m_renderCtx.getRenderTarget(), cellSize*6, cellSize*4, deStringHash(getName()));
+	tcu::Surface				renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface				referenceFrame		(viewport.width, viewport.height);
+	ReferenceParams				sampleParams		(TEXTURETYPE_CUBE);
+	const tcu::TextureFormat&	texFmt				= m_textures[0]->getRefTexture().getFormat();
+	tcu::TextureFormatInfo		fmtInfo				= tcu::getTextureFormatInfo(texFmt);
+
+	// Accuracy measurements are off unless viewport size is exactly as expected.
+	if (viewport.width < defViewportWidth || viewport.height < defViewportHeight)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Viewport is divided into 4 sections.
+	int				leftWidth			= viewport.width / 2;
+	int				rightWidth			= viewport.width - leftWidth;
+	int				bottomHeight		= viewport.height / 2;
+	int				topHeight			= viewport.height - bottomHeight;
+
+	int				curTexNdx			= 0;
+
+	// Sampling parameters.
+	sampleParams.sampler					= mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	sampleParams.sampler.seamlessCubeMap	= false;
+	sampleParams.samplerType				= getSamplerType(texFmt);
+	sampleParams.colorBias					= fmtInfo.lookupBias;
+	sampleParams.colorScale					= fmtInfo.lookupScale;
+	sampleParams.lodMode					= LODMODE_EXACT;
+
+	// Use unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+
+	// Setup gradient texture.
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_textures[curTexNdx]->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	// Bottom left: Minification
+	renderFaces(gl,
+				SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), 0, 0, leftWidth, bottomHeight),
+				m_textures[curTexNdx]->getRefTexture(), sampleParams,
+				m_renderer,
+				viewport.x, viewport.y, leftWidth, bottomHeight,
+				tcu::Vec2(-0.81f, -0.81f),
+				tcu::Vec2( 0.8f,  0.8f),
+				tcu::Vec2(1.0f, 1.0f));
+
+	// Bottom right: Magnification
+	renderFaces(gl,
+				SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), leftWidth, 0, rightWidth, bottomHeight),
+				m_textures[curTexNdx]->getRefTexture(), sampleParams,
+				m_renderer,
+				viewport.x+leftWidth, viewport.y, rightWidth, bottomHeight,
+				tcu::Vec2(0.5f, 0.65f), tcu::Vec2(0.8f, 0.8f),
+				tcu::Vec2(1.0f, 1.0f));
+
+	if (m_textures.size() >= 2)
+	{
+		curTexNdx += 1;
+
+		// Setup second texture.
+		gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_textures[curTexNdx]->getGLTexture());
+		gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+		gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+		gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+		gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	}
+
+	// Top left: Minification
+	renderFaces(gl,
+				SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), 0, bottomHeight, leftWidth, topHeight),
+				m_textures[curTexNdx]->getRefTexture(), sampleParams,
+				m_renderer,
+				viewport.x, viewport.y+bottomHeight, leftWidth, topHeight,
+				tcu::Vec2(-0.81f, -0.81f),
+				tcu::Vec2( 0.8f,  0.8f),
+				tcu::Vec2(1.0f, 1.0f));
+
+	// Top right: Magnification
+	renderFaces(gl,
+				SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), leftWidth, bottomHeight, rightWidth, topHeight),
+				m_textures[curTexNdx]->getRefTexture(), sampleParams,
+				m_renderer,
+				viewport.x+leftWidth, viewport.y+bottomHeight, rightWidth, topHeight,
+				tcu::Vec2(0.5f, -0.65f), tcu::Vec2(0.8f, -0.8f),
+				tcu::Vec2(1.0f, 1.0f));
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log.
+	{
+		DE_ASSERT(getNodeType() == tcu::NODETYPE_ACCURACY);
+
+		const int	bestScoreDiff	= 16;
+		const int	worstScoreDiff	= 10000;
+
+		int score = measureAccuracy(log, referenceFrame, renderedFrame, bestScoreDiff, worstScoreDiff);
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(score).c_str());
+	}
+
+	return STOP;
+}
+
+TextureFilteringTests::TextureFilteringTests (Context& context)
+	: TestCaseGroup(context, "filter", "Texture Filtering Accuracy Tests")
+{
+}
+
+TextureFilteringTests::~TextureFilteringTests (void)
+{
+}
+
+void TextureFilteringTests::init (void)
+{
+	tcu::TestCaseGroup* group2D		= new tcu::TestCaseGroup(m_testCtx, "2d",	"2D Texture Filtering");
+	tcu::TestCaseGroup*	groupCube	= new tcu::TestCaseGroup(m_testCtx, "cube",	"Cube Map Filtering");
+	addChild(group2D);
+	addChild(groupCube);
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE },
+		{ "repeat",		GL_REPEAT },
+		{ "mirror",		GL_MIRRORED_REPEAT }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} minFilterModes[] =
+	{
+		{ "nearest",				GL_NEAREST					},
+		{ "linear",					GL_LINEAR					},
+		{ "nearest_mipmap_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
+		{ "linear_mipmap_nearest",	GL_LINEAR_MIPMAP_NEAREST	},
+		{ "nearest_mipmap_linear",	GL_NEAREST_MIPMAP_LINEAR	},
+		{ "linear_mipmap_linear",	GL_LINEAR_MIPMAP_LINEAR		}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} magFilterModes[] =
+	{
+		{ "nearest",	GL_NEAREST },
+		{ "linear",		GL_LINEAR }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		int				width;
+		int				height;
+	} sizes2D[] =
+	{
+		{ "pot",		32, 64 },
+		{ "npot",		31, 55 }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		int				width;
+		int				height;
+	} sizesCube[] =
+	{
+		{ "pot",		64, 64 },
+		{ "npot",		63, 63 }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		format;
+		deUint32		dataType;
+	} formats[] =
+	{
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE			},
+		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4	}
+	};
+
+#define FOR_EACH(ITERATOR, ARRAY, BODY)	\
+	for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++)	\
+		BODY
+
+	// 2D cases.
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+	FOR_EACH(format,		formats,
+	FOR_EACH(size,			sizes2D,
+		{
+			bool isMipmap		= minFilterModes[minFilter].mode != GL_NEAREST && minFilterModes[minFilter].mode != GL_LINEAR;
+			bool isClamp		= wrapModes[wrapMode].mode == GL_CLAMP_TO_EDGE;
+			bool isRepeat		= wrapModes[wrapMode].mode == GL_REPEAT;
+			bool isMagNearest	= magFilterModes[magFilter].mode == GL_NEAREST;
+			bool isPotSize		= deIsPowerOfTwo32(sizes2D[size].width) && deIsPowerOfTwo32(sizes2D[size].height);
+
+			if ((isMipmap || !isClamp) && !isPotSize)
+				continue; // Not supported.
+
+			if ((format != 0) && !(!isMipmap || (isRepeat && isMagNearest)))
+				continue; // Skip.
+
+			string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name + "_" + formats[format].name;
+
+			if (!isMipmap)
+				name += string("_") + sizes2D[size].name;
+
+			group2D->addChild(new Texture2DFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+														 name.c_str(), "",
+														 minFilterModes[minFilter].mode,
+														 magFilterModes[magFilter].mode,
+														 wrapModes[wrapMode].mode,
+														 wrapModes[wrapMode].mode,
+														 formats[format].format, formats[format].dataType,
+														 sizes2D[size].width, sizes2D[size].height));
+		})))));
+
+	// Cubemap cases.
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+	FOR_EACH(format,		formats,
+	FOR_EACH(size,			sizesCube,
+		{
+			bool isMipmap		= minFilterModes[minFilter].mode != GL_NEAREST && minFilterModes[minFilter].mode != GL_LINEAR;
+			bool isClamp		= wrapModes[wrapMode].mode == GL_CLAMP_TO_EDGE;
+			bool isRepeat		= wrapModes[wrapMode].mode == GL_REPEAT;
+			bool isMagNearest	= magFilterModes[magFilter].mode == GL_NEAREST;
+			bool isPotSize		= deIsPowerOfTwo32(sizesCube[size].width) && deIsPowerOfTwo32(sizesCube[size].height);
+
+			if ((isMipmap || !isClamp) && !isPotSize)
+				continue; // Not supported.
+
+			if (format != 0 && !(!isMipmap || (isRepeat && isMagNearest)))
+				continue; // Skip.
+
+			string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name + "_" + formats[format].name;
+
+			if (!isMipmap)
+				name += string("_") + sizesCube[size].name;
+
+			groupCube->addChild(new TextureCubeFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+															 name.c_str(), "",
+															 minFilterModes[minFilter].mode,
+															 magFilterModes[magFilter].mode,
+															 wrapModes[wrapMode].mode,
+															 wrapModes[wrapMode].mode,
+															 formats[format].format, formats[format].dataType,
+															 sizesCube[size].width, sizesCube[size].height));
+		})))));
+}
+
+} // Accuracy
+} // gles2
+} // deqp
diff --git a/modules/gles2/accuracy/es2aTextureFilteringTests.hpp b/modules/gles2/accuracy/es2aTextureFilteringTests.hpp
new file mode 100644
index 0000000..70535a0
--- /dev/null
+++ b/modules/gles2/accuracy/es2aTextureFilteringTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2ATEXTUREFILTERINGTESTS_HPP
+#define _ES2ATEXTUREFILTERINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Accuracy
+{
+
+class TextureFilteringTests : public TestCaseGroup
+{
+public:
+								TextureFilteringTests		(Context& context);
+								~TextureFilteringTests		(void);
+
+	void						init						(void);
+
+private:
+								TextureFilteringTests		(const TextureFilteringTests& other);
+	TextureFilteringTests&		operator=					(const TextureFilteringTests& other);
+};
+
+} // Accuracy
+} // gles2
+} // deqp
+
+#endif // _ES2ATEXTUREFILTERINGTESTS_HPP
diff --git a/modules/gles2/accuracy/es2aTextureMipmapTests.cpp b/modules/gles2/accuracy/es2aTextureMipmapTests.cpp
new file mode 100644
index 0000000..6e47861
--- /dev/null
+++ b/modules/gles2/accuracy/es2aTextureMipmapTests.cpp
@@ -0,0 +1,752 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Mipmapping accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2aTextureMipmapTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuMatrixUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Accuracy
+{
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using tcu::Sampler;
+using tcu::Vec2;
+using tcu::Mat2;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec4;
+using namespace glu;
+using namespace gls::TextureTestUtil;
+
+enum CoordType
+{
+	COORDTYPE_BASIC,		//!< texCoord = translateScale(position).
+	COORDTYPE_BASIC_BIAS,	//!< Like basic, but with bias values.
+	COORDTYPE_AFFINE,		//!< texCoord = translateScaleRotateShear(position).
+	COORDTYPE_PROJECTED,	//!< Projected coordinates, w != 1
+
+	COORDTYPE_LAST
+};
+
+// Texture2DMipmapCase
+
+class Texture2DMipmapCase : public tcu::TestCase
+{
+public:
+
+								Texture2DMipmapCase			(tcu::TestContext&			testCtx,
+															 glu::RenderContext&		renderCtx,
+															 const glu::ContextInfo&	renderCtxInfo,
+															 const char*				name,
+															 const char*				desc,
+															 CoordType					coordType,
+															 deUint32					minFilter,
+															 deUint32					wrapS,
+															 deUint32					wrapT,
+															 deUint32					format,
+															 deUint32					dataType,
+															 int						width,
+															 int						height);
+								~Texture2DMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Texture2DMipmapCase			(const Texture2DMipmapCase& other);
+	Texture2DMipmapCase&		operator=					(const Texture2DMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	CoordType					m_coordType;
+	deUint32					m_minFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	deUint32					m_format;
+	deUint32					m_dataType;
+	int							m_width;
+	int							m_height;
+
+	glu::Texture2D*				m_texture;
+	TextureRenderer				m_renderer;
+};
+
+Texture2DMipmapCase::Texture2DMipmapCase (tcu::TestContext&			testCtx,
+										  glu::RenderContext&		renderCtx,
+										  const glu::ContextInfo&	renderCtxInfo,
+										  const char*				name,
+										  const char*				desc,
+										  CoordType					coordType,
+										  deUint32					minFilter,
+										  deUint32					wrapS,
+										  deUint32					wrapT,
+										  deUint32					format,
+										  deUint32					dataType,
+										  int						width,
+										  int						height)
+	: TestCase			(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_coordType		(coordType)
+	, m_minFilter		(minFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_width			(width)
+	, m_height			(height)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES,
+						 renderCtxInfo.isFragmentHighPrecisionSupported() ? glu::PRECISION_HIGHP // Use highp if available.
+																		  : glu::PRECISION_MEDIUMP)
+{
+}
+
+Texture2DMipmapCase::~Texture2DMipmapCase (void)
+{
+	deinit();
+}
+
+void Texture2DMipmapCase::init (void)
+{
+	if (!m_renderCtxInfo.isFragmentHighPrecisionSupported())
+		m_testCtx.getLog() << TestLog::Message << "Warning: High precision not supported in fragment shaders." << TestLog::EndMessage;
+
+	m_texture = new Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+
+	int numLevels = deLog2Floor32(de::max(m_width, m_height))+1;
+
+	// Fill texture with colored grid.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		deUint32	step		= 0xff / (numLevels-1);
+		deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+		deUint32	dec			= 0xff - inc;
+		deUint32	rgb			= (inc << 16) | (dec << 8) | 0xff;
+		deUint32	color		= 0xff000000 | rgb;
+
+		m_texture->getRefTexture().allocLevel(levelNdx);
+		tcu::clear(m_texture->getRefTexture().getLevel(levelNdx), toVec4(tcu::RGBA(color)));
+	}
+}
+
+void Texture2DMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+static void getBasicTexCoord2D (std::vector<float>& dst, int cellNdx)
+{
+	static const struct
+	{
+		Vec2 bottomLeft;
+		Vec2 topRight;
+	} s_basicCoords[] =
+	{
+		{ Vec2(-0.1f,  0.1f), Vec2( 0.8f,  1.0f) },
+		{ Vec2(-0.3f, -0.6f), Vec2( 0.7f,  0.4f) },
+		{ Vec2(-0.3f,  0.6f), Vec2( 0.7f, -0.9f) },
+		{ Vec2(-0.8f,  0.6f), Vec2( 0.7f, -0.9f) },
+
+		{ Vec2(-0.5f, -0.5f), Vec2( 1.5f,  1.5f) },
+		{ Vec2( 1.0f, -1.0f), Vec2(-1.3f,  1.0f) },
+		{ Vec2( 1.2f, -1.0f), Vec2(-1.3f,  1.6f) },
+		{ Vec2( 2.2f, -1.1f), Vec2(-1.3f,  0.8f) },
+
+		{ Vec2(-1.5f,  1.6f), Vec2( 1.7f, -1.4f) },
+		{ Vec2( 2.0f,  1.6f), Vec2( 2.3f, -1.4f) },
+		{ Vec2( 1.3f, -2.6f), Vec2(-2.7f,  2.9f) },
+		{ Vec2(-0.8f, -6.6f), Vec2( 6.0f, -0.9f) },
+
+		{ Vec2( -8.0f,   9.0f), Vec2(  8.3f,  -7.0f) },
+		{ Vec2(-16.0f,  10.0f), Vec2( 18.3f,  24.0f) },
+		{ Vec2( 30.2f,  55.0f), Vec2(-24.3f,  -1.6f) },
+		{ Vec2(-33.2f,  64.1f), Vec2( 32.1f, -64.1f) },
+	};
+
+	DE_ASSERT(de::inBounds(cellNdx, 0, DE_LENGTH_OF_ARRAY(s_basicCoords)));
+
+	const Vec2& bottomLeft	= s_basicCoords[cellNdx].bottomLeft;
+	const Vec2& topRight	= s_basicCoords[cellNdx].topRight;
+
+	computeQuadTexCoord2D(dst, bottomLeft, topRight);
+}
+
+static void getAffineTexCoord2D (std::vector<float>& dst, int cellNdx)
+{
+	// Use basic coords as base.
+	getBasicTexCoord2D(dst, cellNdx);
+
+	// Rotate based on cell index.
+	float		angle		= 2.0f*DE_PI * ((float)cellNdx / 16.0f);
+	tcu::Mat2	rotMatrix	= tcu::rotationMatrix(angle);
+
+	// Second and third row are sheared.
+	float		shearX		= de::inRange(cellNdx, 4, 11) ? (float)(15-cellNdx) / 16.0f : 0.0f;
+	tcu::Mat2	shearMatrix	= tcu::shearMatrix(tcu::Vec2(shearX, 0.0f));
+
+	tcu::Mat2	transform	= rotMatrix * shearMatrix;
+	Vec2		p0			= transform * Vec2(dst[0], dst[1]);
+	Vec2		p1			= transform * Vec2(dst[2], dst[3]);
+	Vec2		p2			= transform * Vec2(dst[4], dst[5]);
+	Vec2		p3			= transform * Vec2(dst[6], dst[7]);
+
+	dst[0] = p0.x();	dst[1] = p0.y();
+	dst[2] = p1.x();	dst[3] = p1.y();
+	dst[4] = p2.x();	dst[5] = p2.y();
+	dst[6] = p3.x();	dst[7] = p3.y();
+}
+
+Texture2DMipmapCase::IterateResult Texture2DMipmapCase::iterate (void)
+{
+	// Constants.
+	const deUint32				magFilter			= GL_NEAREST;
+
+	const glw::Functions&		gl					= m_renderCtx.getFunctions();
+	TestLog&					log					= m_testCtx.getLog();
+
+	const tcu::Texture2D&		refTexture			= m_texture->getRefTexture();
+	const tcu::TextureFormat&	texFmt				= refTexture.getFormat();
+	tcu::TextureFormatInfo		fmtInfo				= tcu::getTextureFormatInfo(texFmt);
+
+	int							texWidth			= refTexture.getWidth();
+	int							texHeight			= refTexture.getHeight();
+	int							defViewportWidth	= texWidth*4;
+	int							defViewportHeight	= texHeight*4;
+
+	RandomViewport				viewport			(m_renderCtx.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+	ReferenceParams				sampleParams		(TEXTURETYPE_2D);
+	vector<float>				texCoord;
+	bool						isProjected			= m_coordType == COORDTYPE_PROJECTED;
+	bool						useLodBias			= m_coordType == COORDTYPE_BASIC_BIAS;
+
+	tcu::Surface				renderedFrame		(viewport.width, viewport.height);
+
+	// Accuracy cases test against ideal lod computation.
+	tcu::Surface				idealFrame			(viewport.width, viewport.height);
+
+	// Viewport is divided into 4x4 grid.
+	int							gridWidth			= 4;
+	int							gridHeight			= 4;
+	int							cellWidth			= viewport.width / gridWidth;
+	int							cellHeight			= viewport.height / gridHeight;
+
+	// Accuracy measurements are off unless we get the expected viewport size.
+	if (viewport.width < defViewportWidth || viewport.height < defViewportHeight)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Sampling parameters.
+	sampleParams.sampler		= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, magFilter);
+	sampleParams.samplerType	= gls::TextureTestUtil::getSamplerType(m_texture->getRefTexture().getFormat());
+	sampleParams.colorBias		= fmtInfo.lookupBias;
+	sampleParams.colorScale		= fmtInfo.lookupScale;
+	sampleParams.flags			= (isProjected ? ReferenceParams::PROJECTED : 0) | (useLodBias ? ReferenceParams::USE_BIAS : 0);
+
+	// Upload texture data.
+	m_texture->upload();
+
+	// Use unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Bias values.
+	static const float s_bias[] = { 1.0f, -2.0f, 0.8f, -0.5f, 1.5f, 0.9f, 2.0f, 4.0f };
+
+	// Projection values.
+	static const Vec4 s_projections[] =
+	{
+		Vec4(1.2f, 1.0f, 0.7f, 1.0f),
+		Vec4(1.3f, 0.8f, 0.6f, 2.0f),
+		Vec4(0.8f, 1.0f, 1.7f, 0.6f),
+		Vec4(1.2f, 1.0f, 1.7f, 1.5f)
+	};
+
+	// Render cells.
+	for (int gridY = 0; gridY < gridHeight; gridY++)
+	{
+		for (int gridX = 0; gridX < gridWidth; gridX++)
+		{
+			int				curX		= cellWidth*gridX;
+			int				curY		= cellHeight*gridY;
+			int				curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+			int				curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+			int				cellNdx		= gridY*gridWidth + gridX;
+
+			// Compute texcoord.
+			switch (m_coordType)
+			{
+				case COORDTYPE_BASIC_BIAS:	// Fall-through.
+				case COORDTYPE_PROJECTED:
+				case COORDTYPE_BASIC:		getBasicTexCoord2D	(texCoord, cellNdx);	break;
+				case COORDTYPE_AFFINE:		getAffineTexCoord2D	(texCoord, cellNdx);	break;
+				default:					DE_ASSERT(DE_FALSE);
+			}
+
+			if (isProjected)
+				sampleParams.w = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+
+			if (useLodBias)
+				sampleParams.bias = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+
+			// Render with GL.
+			gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+			m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+
+			// Render reference(s).
+			{
+				SurfaceAccess idealDst(idealFrame, m_renderCtx.getRenderTarget().getPixelFormat(), curX, curY, curW, curH);
+				sampleParams.lodMode = LODMODE_EXACT;
+				sampleTexture(idealDst, m_texture->getRefTexture(), &texCoord[0], sampleParams);
+			}
+		}
+	}
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log.
+	{
+		const int	bestScoreDiff	= (texWidth/16)*(texHeight/16);
+		const int	worstScoreDiff	= texWidth*texHeight;
+
+		int score = measureAccuracy(log, idealFrame, renderedFrame, bestScoreDiff, worstScoreDiff);
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(score).c_str());
+	}
+
+	return STOP;
+}
+
+// TextureCubeMipmapCase
+
+class TextureCubeMipmapCase : public tcu::TestCase
+{
+public:
+
+								TextureCubeMipmapCase		(tcu::TestContext&			testCtx,
+															 glu::RenderContext&		renderCtx,
+															 const glu::ContextInfo&	renderCtxInfo,
+															 const char*				name,
+															 const char*				desc,
+															 CoordType					coordType,
+															 deUint32					minFilter,
+															 deUint32					wrapS,
+															 deUint32					wrapT,
+															 deUint32					format,
+															 deUint32					dataType,
+															 int						size);
+								~TextureCubeMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								TextureCubeMipmapCase		(const TextureCubeMipmapCase& other);
+	TextureCubeMipmapCase&		operator=					(const TextureCubeMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	CoordType					m_coordType;
+	deUint32					m_minFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	deUint32					m_format;
+	deUint32					m_dataType;
+	int							m_size;
+
+	glu::TextureCube*			m_texture;
+	TextureRenderer				m_renderer;
+};
+
+TextureCubeMipmapCase::TextureCubeMipmapCase (tcu::TestContext&			testCtx,
+											  glu::RenderContext&		renderCtx,
+											  const glu::ContextInfo&	renderCtxInfo,
+											  const char*				name,
+											  const char*				desc,
+											  CoordType					coordType,
+											  deUint32					minFilter,
+											  deUint32					wrapS,
+											  deUint32					wrapT,
+											  deUint32					format,
+											  deUint32					dataType,
+											  int						size)
+	: TestCase			(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_coordType		(coordType)
+	, m_minFilter		(minFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_size			(size)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES,
+						 renderCtxInfo.isFragmentHighPrecisionSupported() ? glu::PRECISION_HIGHP // Use highp if available.
+																		  : glu::PRECISION_MEDIUMP)
+{
+}
+
+TextureCubeMipmapCase::~TextureCubeMipmapCase (void)
+{
+	deinit();
+}
+
+void TextureCubeMipmapCase::init (void)
+{
+	if (!m_renderCtxInfo.isFragmentHighPrecisionSupported())
+		m_testCtx.getLog() << TestLog::Message << "Warning: High precision not supported in fragment shaders." << TestLog::EndMessage;
+
+	m_texture = new TextureCube(m_renderCtx, m_format, m_dataType, m_size);
+
+	int numLevels = deLog2Floor32(m_size)+1;
+
+	// Fill texture with colored grid.
+	for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+	{
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			deUint32	step		= 0xff / (numLevels-1);
+			deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+			deUint32	dec			= 0xff - inc;
+			deUint32	rgb			= 0;
+
+			switch (faceNdx)
+			{
+				case 0: rgb = (inc << 16) | (dec << 8) | 255; break;
+				case 1: rgb = (255 << 16) | (inc << 8) | dec; break;
+				case 2: rgb = (dec << 16) | (255 << 8) | inc; break;
+				case 3: rgb = (dec << 16) | (inc << 8) | 255; break;
+				case 4: rgb = (255 << 16) | (dec << 8) | inc; break;
+				case 5: rgb = (inc << 16) | (255 << 8) | dec; break;
+			}
+
+			deUint32	color		= 0xff000000 | rgb;
+
+			m_texture->getRefTexture().allocLevel((tcu::CubeFace)faceNdx, levelNdx);
+			tcu::clear(m_texture->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)faceNdx), toVec4(tcu::RGBA(color)));
+		}
+	}
+}
+
+void TextureCubeMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+static void randomPartition (vector<IVec4>& dst, de::Random& rnd, int x, int y, int width, int height)
+{
+	const int minWidth	= 8;
+	const int minHeight	= 8;
+
+	bool	partition		= rnd.getFloat() > 0.4f;
+	bool	partitionX		= partition && width > minWidth && rnd.getBool();
+	bool	partitionY		= partition && height > minHeight && !partitionX;
+
+	if (partitionX)
+	{
+		int split = width/2 + rnd.getInt(-width/4, +width/4);
+		randomPartition(dst, rnd, x, y, split, height);
+		randomPartition(dst, rnd, x+split, y, width-split, height);
+	}
+	else if (partitionY)
+	{
+		int split = height/2 + rnd.getInt(-height/4, +height/4);
+		randomPartition(dst, rnd, x, y, width, split);
+		randomPartition(dst, rnd, x, y+split, width, height-split);
+	}
+	else
+		dst.push_back(IVec4(x, y, width, height));
+}
+
+static void computeGridLayout (vector<IVec4>& dst, int width, int height)
+{
+	de::Random rnd(7);
+	randomPartition(dst, rnd, 0, 0, width, height);
+}
+
+TextureCubeMipmapCase::IterateResult TextureCubeMipmapCase::iterate (void)
+{
+	// Constants.
+	const deUint32			magFilter			= GL_NEAREST;
+
+	int						texWidth			= m_texture->getRefTexture().getSize();
+	int						texHeight			= m_texture->getRefTexture().getSize();
+
+	int						defViewportWidth	= texWidth*2;
+	int						defViewportHeight	= texHeight*2;
+
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+	tcu::Sampler			sampler				= mapGLSampler(m_wrapS, m_wrapT, m_minFilter, magFilter);
+
+	vector<float>			texCoord;
+
+	bool					isProjected			= m_coordType == COORDTYPE_PROJECTED;
+	bool					useLodBias			= m_coordType == COORDTYPE_BASIC_BIAS;
+
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+
+	// Accuracy cases test against ideal lod computation.
+	tcu::Surface			idealFrame			(viewport.width, viewport.height);
+
+	// Accuracy measurements are off unless we get the expected viewport size.
+	if (viewport.width < defViewportWidth || viewport.height < defViewportHeight)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Upload texture data.
+	m_texture->upload();
+
+	// Use unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Compute grid.
+	vector<IVec4> gridLayout;
+	computeGridLayout(gridLayout, viewport.width, viewport.height);
+
+	// Bias values.
+	static const float s_bias[] = { 1.0f, -2.0f, 0.8f, -0.5f, 1.5f, 0.9f, 2.0f, 4.0f };
+
+	// Projection values \note Less agressive than in 2D case due to smaller quads.
+	static const Vec4 s_projections[] =
+	{
+		Vec4(1.2f, 1.0f, 0.7f, 1.0f),
+		Vec4(1.3f, 0.8f, 0.6f, 1.1f),
+		Vec4(0.8f, 1.0f, 1.2f, 0.8f),
+		Vec4(1.2f, 1.0f, 1.3f, 0.9f)
+	};
+
+	for (int cellNdx = 0; cellNdx < (int)gridLayout.size(); cellNdx++)
+	{
+		int				curX		= gridLayout[cellNdx].x();
+		int				curY		= gridLayout[cellNdx].y();
+		int				curW		= gridLayout[cellNdx].z();
+		int				curH		= gridLayout[cellNdx].w();
+		tcu::CubeFace	cubeFace	= (tcu::CubeFace)(cellNdx % tcu::CUBEFACE_LAST);
+		ReferenceParams	params		(TEXTURETYPE_CUBE);
+
+		params.sampler = sampler;
+
+		DE_ASSERT(m_coordType != COORDTYPE_AFFINE); // Not supported.
+		computeQuadTexCoordCube(texCoord, cubeFace);
+
+		if (isProjected)
+		{
+			params.flags	|= ReferenceParams::PROJECTED;
+			params.w		 = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+		}
+
+		if (useLodBias)
+		{
+			params.flags	|= ReferenceParams::USE_BIAS;
+			params.bias		 = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+		}
+
+		// Render with GL.
+		gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+		m_renderer.renderQuad(0, &texCoord[0], params);
+
+		// Render reference(s).
+		{
+			SurfaceAccess idealDst(idealFrame, m_renderCtx.getRenderTarget().getPixelFormat(), curX, curY, curW, curH);
+			params.lodMode = LODMODE_EXACT;
+			sampleTexture(idealDst, m_texture->getRefTexture(), &texCoord[0], params);
+		}
+	}
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log.
+	{
+		const int	bestScoreDiff	= (texWidth/16)*(texHeight/16);
+		const int	worstScoreDiff	= texWidth*texHeight;
+
+		int score = measureAccuracy(log, idealFrame, renderedFrame, bestScoreDiff, worstScoreDiff);
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(score).c_str());
+	}
+
+	return STOP;
+}
+
+TextureMipmapTests::TextureMipmapTests (Context& context)
+	: TestCaseGroup(context, "mipmap", "Mipmapping accuracy tests")
+{
+}
+
+TextureMipmapTests::~TextureMipmapTests (void)
+{
+}
+
+void TextureMipmapTests::init (void)
+{
+	tcu::TestCaseGroup* group2D		= new tcu::TestCaseGroup(m_testCtx, "2d",	"2D Texture Mipmapping");
+	tcu::TestCaseGroup*	groupCube	= new tcu::TestCaseGroup(m_testCtx, "cube",	"Cube Map Filtering");
+	addChild(group2D);
+	addChild(groupCube);
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE },
+		{ "repeat",		GL_REPEAT },
+		{ "mirror",		GL_MIRRORED_REPEAT }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} minFilterModes[] =
+	{
+		{ "nearest_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
+		{ "linear_nearest",		GL_LINEAR_MIPMAP_NEAREST	},
+		{ "nearest_linear",		GL_NEAREST_MIPMAP_LINEAR	},
+		{ "linear_linear",		GL_LINEAR_MIPMAP_LINEAR		}
+	};
+
+	static const struct
+	{
+		CoordType		type;
+		const char*		name;
+		const char*		desc;
+	} coordTypes[] =
+	{
+		{ COORDTYPE_BASIC,		"basic",		"Mipmapping with translated and scaled coordinates" },
+		{ COORDTYPE_AFFINE,		"affine",		"Mipmapping with affine coordinate transform"		},
+		{ COORDTYPE_PROJECTED,	"projected",	"Mipmapping with perspective projection"			}
+	};
+
+	const int tex2DWidth	= 64;
+	const int tex2DHeight	= 64;
+
+	// 2D cases.
+	for (int coordType = 0; coordType < DE_LENGTH_OF_ARRAY(coordTypes); coordType++)
+	{
+		tcu::TestCaseGroup* coordTypeGroup = new tcu::TestCaseGroup(m_testCtx, coordTypes[coordType].name, coordTypes[coordType].desc);
+		group2D->addChild(coordTypeGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+		{
+			for (int wrapMode = 0; wrapMode < DE_LENGTH_OF_ARRAY(wrapModes); wrapMode++)
+			{
+				std::ostringstream name;
+				name << minFilterModes[minFilter].name
+						<< "_" << wrapModes[wrapMode].name;
+
+				coordTypeGroup->addChild(new Texture2DMipmapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																	name.str().c_str(), "",
+																	coordTypes[coordType].type,
+																	minFilterModes[minFilter].mode,
+																	wrapModes[wrapMode].mode,
+																	wrapModes[wrapMode].mode,
+																	GL_RGBA, GL_UNSIGNED_BYTE,
+																	tex2DWidth, tex2DHeight));
+			}
+		}
+	}
+
+	const int cubeMapSize = 64;
+
+	static const struct
+	{
+		CoordType		type;
+		const char*		name;
+		const char*		desc;
+	} cubeCoordTypes[] =
+	{
+		{ COORDTYPE_BASIC,		"basic",		"Mipmapping with translated and scaled coordinates" },
+		{ COORDTYPE_PROJECTED,	"projected",	"Mipmapping with perspective projection"			}
+	};
+
+	// Cubemap cases.
+	for (int coordType = 0; coordType < DE_LENGTH_OF_ARRAY(cubeCoordTypes); coordType++)
+	{
+		tcu::TestCaseGroup* coordTypeGroup = new tcu::TestCaseGroup(m_testCtx, cubeCoordTypes[coordType].name, cubeCoordTypes[coordType].desc);
+		groupCube->addChild(coordTypeGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+		{
+			coordTypeGroup->addChild(new TextureCubeMipmapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+															   minFilterModes[minFilter].name, "",
+															   cubeCoordTypes[coordType].type,
+															   minFilterModes[minFilter].mode,
+															   GL_CLAMP_TO_EDGE,
+															   GL_CLAMP_TO_EDGE,
+															   GL_RGBA, GL_UNSIGNED_BYTE, cubeMapSize));
+		}
+	}
+}
+
+} // Accuracy
+} // gles2
+} // deqp
diff --git a/modules/gles2/accuracy/es2aTextureMipmapTests.hpp b/modules/gles2/accuracy/es2aTextureMipmapTests.hpp
new file mode 100644
index 0000000..0ab6cf4
--- /dev/null
+++ b/modules/gles2/accuracy/es2aTextureMipmapTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2ATEXTUREMIPMAPTESTS_HPP
+#define _ES2ATEXTUREMIPMAPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Mipmapping accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Accuracy
+{
+
+class TextureMipmapTests : public TestCaseGroup
+{
+public:
+							TextureMipmapTests		(Context& context);
+							~TextureMipmapTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureMipmapTests		(const TextureMipmapTests& other);
+	TextureMipmapTests&		operator=				(const TextureMipmapTests& other);
+};
+
+} // Accuracy
+} // gles2
+} // deqp
+
+#endif // _ES2ATEXTUREMIPMAPTESTS_HPP
diff --git a/modules/gles2/accuracy/es2aVaryingInterpolationTests.cpp b/modules/gles2/accuracy/es2aVaryingInterpolationTests.cpp
new file mode 100644
index 0000000..6c808ea
--- /dev/null
+++ b/modules/gles2/accuracy/es2aVaryingInterpolationTests.cpp
@@ -0,0 +1,331 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Varying interpolation accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2aVaryingInterpolationTests.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluContextInfo.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuFloat.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+#include "glw.h"
+
+using tcu::TestLog;
+using tcu::Vec3;
+using tcu::Vec4;
+using std::string;
+using std::vector;
+using std::map;
+using deqp::gls::TextureTestUtil::SurfaceAccess;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Accuracy
+{
+
+static inline float projectedTriInterpolate (const tcu::Vec3& s, const tcu::Vec3& w, float nx, float ny)
+{
+	return (s[0]*(1.0f-nx-ny)/w[0] + s[1]*ny/w[1] + s[2]*nx/w[2]) / ((1.0f-nx-ny)/w[0] + ny/w[1] + nx/w[2]);
+}
+
+static void renderReference (const SurfaceAccess& dst, const float coords[4*3], const Vec4& wCoord, const Vec3& scale, const Vec3& bias)
+{
+	float		dstW		= (float)dst.getWidth();
+	float		dstH		= (float)dst.getHeight();
+
+	Vec3		triR[2]		= { Vec3(coords[0*3+0], coords[1*3+0], coords[2*3+0]), Vec3(coords[3*3+0], coords[2*3+0], coords[1*3+0]) };
+	Vec3		triG[2]		= { Vec3(coords[0*3+1], coords[1*3+1], coords[2*3+1]), Vec3(coords[3*3+1], coords[2*3+1], coords[1*3+1]) };
+	Vec3		triB[2]		= { Vec3(coords[0*3+2], coords[1*3+2], coords[2*3+2]), Vec3(coords[3*3+2], coords[2*3+2], coords[1*3+2]) };
+	tcu::Vec3	triW[2]		= { wCoord.swizzle(0, 1, 2), wCoord.swizzle(3, 2, 1) };
+
+	for (int py = 0; py < dst.getHeight(); py++)
+	{
+		for (int px = 0; px < dst.getWidth(); px++)
+		{
+			float	wx		= (float)px + 0.5f;
+			float	wy		= (float)py + 0.5f;
+			float	nx		= wx / dstW;
+			float	ny		= wy / dstH;
+
+			int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+			float	triNx	= triNdx ? 1.0f - nx : nx;
+			float	triNy	= triNdx ? 1.0f - ny : ny;
+
+			float	r		= projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy) * scale[0] + bias[0];
+			float	g		= projectedTriInterpolate(triG[triNdx], triW[triNdx], triNx, triNy) * scale[1] + bias[1];
+			float	b		= projectedTriInterpolate(triB[triNdx], triW[triNdx], triNx, triNy) * scale[2] + bias[2];
+
+			Vec4	color	= Vec4(r, g, b, 1.0f);
+
+			dst.setPixel(color, px, py);
+		}
+	}
+}
+
+class InterpolationCase : public TestCase
+{
+public:
+					InterpolationCase			(Context& context, const char* name, const char* desc, glu::Precision precision, const tcu::Vec3& minVal, const tcu::Vec3& maxVal, bool projective);
+					~InterpolationCase			(void);
+
+	IterateResult	iterate						(void);
+
+private:
+	glu::Precision	m_precision;
+	tcu::Vec3		m_min;
+	tcu::Vec3		m_max;
+	bool			m_projective;
+};
+
+InterpolationCase::InterpolationCase (Context& context, const char* name, const char* desc, glu::Precision precision, const tcu::Vec3& minVal, const tcu::Vec3& maxVal, bool projective)
+	: TestCase		(context, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_precision	(precision)
+	, m_min			(minVal)
+	, m_max			(maxVal)
+	, m_projective	(projective)
+{
+}
+
+InterpolationCase::~InterpolationCase (void)
+{
+}
+
+static bool isValidFloat (glu::Precision precision, float val)
+{
+	if (precision == glu::PRECISION_MEDIUMP)
+	{
+		tcu::Float16 fp16(val);
+		return !fp16.isDenorm() && !fp16.isInf() && !fp16.isNaN();
+	}
+	else
+	{
+		tcu::Float32 fp32(val);
+		return !fp32.isDenorm() && !fp32.isInf() && !fp32.isNaN();
+	}
+}
+
+template <int Size>
+static bool isValidFloatVec (glu::Precision precision, const tcu::Vector<float, Size>& vec)
+{
+	for (int ndx = 0; ndx < Size; ndx++)
+	{
+		if (!isValidFloat(precision, vec[ndx]))
+			return false;
+	}
+	return true;
+}
+
+InterpolationCase::IterateResult InterpolationCase::iterate (void)
+{
+	TestLog&		log				= m_testCtx.getLog();
+	de::Random		rnd				(deStringHash(getName()));
+	int				viewportWidth	= 128;
+	int				viewportHeight	= 128;
+
+	if (m_context.getRenderTarget().getWidth() < viewportWidth ||
+		m_context.getRenderTarget().getHeight() < viewportHeight)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	int				viewportX		= rnd.getInt(0, m_context.getRenderTarget().getWidth()	- viewportWidth);
+	int				viewportY		= rnd.getInt(0, m_context.getRenderTarget().getHeight()	- viewportHeight);
+
+	static const char* s_vertShaderTemplate =
+		"attribute highp vec4 a_position;\n"
+		"attribute ${PRECISION} vec3 a_coords;\n"
+		"varying ${PRECISION} vec3 v_coords;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_coords = a_coords;\n"
+		"}\n";
+	static const char* s_fragShaderTemplate =
+		"varying ${PRECISION} vec3 v_coords;\n"
+		"uniform ${PRECISION} vec3 u_scale;\n"
+		"uniform ${PRECISION} vec3 u_bias;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_FragColor = vec4(v_coords * u_scale + u_bias, 1.0);\n"
+		"}\n";
+
+	map<string, string> templateParams;
+	templateParams["PRECISION"] = glu::getPrecisionName(m_precision);
+
+	glu::ShaderProgram program(m_context.getRenderContext(),
+							   glu::makeVtxFragSources(tcu::StringTemplate(s_vertShaderTemplate).specialize(templateParams),
+													   tcu::StringTemplate(s_fragShaderTemplate).specialize(templateParams)));
+	log << program;
+	if (!program.isOk())
+	{
+		if (m_precision == glu::PRECISION_HIGHP && !m_context.getContextInfo().isFragmentHighPrecisionSupported())
+			m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Fragment highp not supported");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		return STOP;
+	}
+
+	// Position coordinates.
+	Vec4 wCoord = m_projective ? Vec4(1.3f, 0.8f, 0.6f, 2.0f) : Vec4(1.0f, 1.0f, 1.0f, 1.0f);
+	float positions[] =
+	{
+		-1.0f*wCoord.x(), -1.0f*wCoord.x(), 0.0f, wCoord.x(),
+		-1.0f*wCoord.y(), +1.0f*wCoord.y(), 0.0f, wCoord.y(),
+		+1.0f*wCoord.z(), -1.0f*wCoord.z(), 0.0f, wCoord.z(),
+		+1.0f*wCoord.w(), +1.0f*wCoord.w(), 0.0f, wCoord.w()
+	};
+
+	// Coordinates for interpolation.
+	tcu::Vec3 scale	= 1.0f / (m_max - m_min);
+	tcu::Vec3 bias	= -1.0f*m_min*scale;
+	float coords[] =
+	{
+		(0.0f - bias[0])/scale[0], (0.5f - bias[1])/scale[1], (1.0f - bias[2])/scale[2],
+		(0.5f - bias[0])/scale[0], (1.0f - bias[1])/scale[1], (0.5f - bias[2])/scale[2],
+		(0.5f - bias[0])/scale[0], (0.0f - bias[1])/scale[1], (0.5f - bias[2])/scale[2],
+		(1.0f - bias[0])/scale[0], (0.5f - bias[1])/scale[1], (0.0f - bias[2])/scale[2]
+	};
+
+	log << TestLog::Message << "a_coords = " << ((tcu::Vec3(0.0f) - bias)/scale) << " -> " << ((tcu::Vec3(1.0f) - bias)/scale) << TestLog::EndMessage;
+	log << TestLog::Message << "u_scale = " << scale << TestLog::EndMessage;
+	log << TestLog::Message << "u_bias = " << bias << TestLog::EndMessage;
+
+	// Verify that none of the inputs are denormalized / inf / nan.
+	TCU_CHECK(isValidFloatVec(m_precision, scale));
+	TCU_CHECK(isValidFloatVec(m_precision, bias));
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(coords); ndx++)
+	{
+		TCU_CHECK(isValidFloat(m_precision, coords[ndx]));
+		TCU_CHECK(isValidFloat(m_precision, coords[ndx] * scale[ndx % 3] + bias[ndx % 3]));
+	}
+
+	// Indices.
+	static const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+	{
+		const int	posLoc		= glGetAttribLocation(program.getProgram(), "a_position");
+		const int	coordLoc	= glGetAttribLocation(program.getProgram(), "a_coords");
+
+		glEnableVertexAttribArray(posLoc);
+		glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &positions[0]);
+
+		glEnableVertexAttribArray(coordLoc);
+		glVertexAttribPointer(coordLoc, 3, GL_FLOAT, GL_FALSE, 0, &coords[0]);
+	}
+
+	glUseProgram(program.getProgram());
+	glUniform3f(glGetUniformLocation(program.getProgram(), "u_scale"), scale.x(), scale.y(), scale.z());
+	glUniform3f(glGetUniformLocation(program.getProgram(), "u_bias"), bias.x(), bias.y(), bias.z());
+
+	GLU_CHECK_MSG("After program setup");
+
+	// Frames.
+	tcu::Surface	rendered		(viewportWidth, viewportHeight);
+	tcu::Surface	reference		(viewportWidth, viewportHeight);
+	tcu::Surface	diffMask		(viewportWidth, viewportHeight);
+
+	// Render with GL.
+	glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
+	glDrawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_SHORT, &indices[0]);
+
+	// Render reference \note While GPU is hopefully doing our draw call.
+	renderReference(SurfaceAccess(reference, m_context.getRenderTarget().getPixelFormat()), coords, wCoord, scale, bias);
+
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, rendered.getAccess());
+
+	// Compute difference.
+	const int		bestScoreDiff	= 16;
+	const int		worstScoreDiff	= 300;
+	int				score			= tcu::measurePixelDiffAccuracy(log, "Result", "Image comparison result", reference, rendered, bestScoreDiff, worstScoreDiff, tcu::COMPARE_LOG_EVERYTHING);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(score).c_str());
+	return STOP;
+}
+
+VaryingInterpolationTests::VaryingInterpolationTests (Context& context)
+	: TestCaseGroup(context, "interpolation", "Varying Interpolation Accuracy Tests")
+{
+}
+
+VaryingInterpolationTests::~VaryingInterpolationTests (void)
+{
+}
+
+void VaryingInterpolationTests::init (void)
+{
+	DE_STATIC_ASSERT(glu::PRECISION_LOWP+1		== glu::PRECISION_MEDIUMP);
+	DE_STATIC_ASSERT(glu::PRECISION_MEDIUMP+1	== glu::PRECISION_HIGHP);
+
+	// Exp = Emax-3, Mantissa = 0
+	float minF32 = tcu::Float32((0u<<31) | (0xfcu<<23) | 0x0u).asFloat();
+	float maxF32 = tcu::Float32((1u<<31) | (0xfcu<<23) | 0x0u).asFloat();
+	float minF16 = tcu::Float16((deUint16)((0u<<15) | (0x1cu<<10) | 0x0u)).asFloat();
+	float maxF16 = tcu::Float16((deUint16)((1u<<15) | (0x1cu<<10) | 0x0u)).asFloat();
+
+	static const struct
+	{
+		const char*		name;
+		Vec3			minVal;
+		Vec3			maxVal;
+		glu::Precision	minPrecision;
+	} coordRanges[] =
+	{
+		{ "zero_to_one",		Vec3(  0.0f,   0.0f,   0.0f), Vec3(  1.0f,   1.0f,   1.0f), glu::PRECISION_LOWP		},
+		{ "zero_to_minus_one",	Vec3(  0.0f,   0.0f,   0.0f), Vec3( -1.0f,  -1.0f,  -1.0f), glu::PRECISION_LOWP		},
+		{ "minus_one_to_one",	Vec3( -1.0f,  -1.0f,  -1.0f), Vec3(  1.0f,   1.0f,   1.0f), glu::PRECISION_LOWP		},
+		{ "minus_ten_to_ten",	Vec3(-10.0f, -10.0f, -10.0f), Vec3( 10.0f,  10.0f,  10.0f), glu::PRECISION_MEDIUMP	},
+		{ "thousands",			Vec3( -5e3f,   1e3f,   1e3f), Vec3(  3e3f,  -1e3f,   7e3f), glu::PRECISION_MEDIUMP	},
+		{ "full_mediump",		Vec3(minF16, minF16, minF16), Vec3(maxF16, maxF16, maxF16), glu::PRECISION_MEDIUMP	},
+		{ "full_highp",			Vec3(minF32, minF32, minF32), Vec3(maxF32, maxF32, maxF32), glu::PRECISION_HIGHP	},
+	};
+
+	for (int precision = glu::PRECISION_LOWP; precision <= glu::PRECISION_HIGHP; precision++)
+	{
+		for (int coordNdx = 0; coordNdx < DE_LENGTH_OF_ARRAY(coordRanges); coordNdx++)
+		{
+			if (precision < (int)coordRanges[coordNdx].minPrecision)
+				continue;
+
+			string baseName = string(glu::getPrecisionName((glu::Precision)precision)) + "_" + coordRanges[coordNdx].name;
+
+			addChild(new InterpolationCase(m_context, baseName.c_str(),				"",	(glu::Precision)precision, coordRanges[coordNdx].minVal, coordRanges[coordNdx].maxVal, false));
+			addChild(new InterpolationCase(m_context, (baseName + "_proj").c_str(),	"",	(glu::Precision)precision, coordRanges[coordNdx].minVal, coordRanges[coordNdx].maxVal, true));
+		}
+	}
+}
+
+} // Accuracy
+} // gles2
+} // deqp
diff --git a/modules/gles2/accuracy/es2aVaryingInterpolationTests.hpp b/modules/gles2/accuracy/es2aVaryingInterpolationTests.hpp
new file mode 100644
index 0000000..b648566
--- /dev/null
+++ b/modules/gles2/accuracy/es2aVaryingInterpolationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2AVARYINGINTERPOLATIONTESTS_HPP
+#define _ES2AVARYINGINTERPOLATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Varying interpolation accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Accuracy
+{
+
+class VaryingInterpolationTests : public TestCaseGroup
+{
+public:
+									VaryingInterpolationTests		(Context& context);
+									~VaryingInterpolationTests		(void);
+
+	void							init							(void);
+
+private:
+									VaryingInterpolationTests		(const VaryingInterpolationTests& other);
+	VaryingInterpolationTests&		operator=						(const VaryingInterpolationTests& other);
+};
+
+} // Accuracy
+} // gles2
+} // deqp
+
+#endif // _ES2AVARYINGINTERPOLATIONTESTS_HPP
diff --git a/modules/gles2/functional/CMakeLists.txt b/modules/gles2/functional/CMakeLists.txt
new file mode 100644
index 0000000..4119bd4
--- /dev/null
+++ b/modules/gles2/functional/CMakeLists.txt
@@ -0,0 +1,165 @@
+# dEQP-GLES2.functional
+
+set(DEQP_GLES2_FUNCTIONAL_SRCS
+	es2fApiCase.cpp
+	es2fApiCase.hpp
+	es2fAttribLocationTests.cpp
+	es2fAttribLocationTests.hpp
+	es2fColorClearTest.cpp
+	es2fColorClearTest.hpp
+	es2fDefaultVertexAttributeTests.cpp
+	es2fDefaultVertexAttributeTests.hpp
+	es2fDepthStencilClearTests.cpp
+	es2fDepthStencilClearTests.hpp
+	es2fDepthStencilTests.cpp
+	es2fDepthStencilTests.hpp
+	es2fDepthTests.cpp
+	es2fDepthTests.hpp
+	es2fFboApiTest.cpp
+	es2fFboApiTest.hpp
+	es2fFboCompletenessTests.cpp
+	es2fFboCompletenessTests.hpp
+	es2fFboRenderTest.cpp
+	es2fFboRenderTest.hpp
+	es2fFunctionalTests.cpp
+	es2fFunctionalTests.hpp
+	es2fLifetimeTests.cpp
+	es2fLifetimeTests.hpp
+	es2fLightAmountTest.cpp
+	es2fLightAmountTest.hpp
+	es2fNegativeBufferApiTests.cpp
+	es2fNegativeBufferApiTests.hpp
+	es2fNegativeFragmentApiTests.cpp
+	es2fNegativeFragmentApiTests.hpp
+	es2fNegativeShaderApiTests.cpp
+	es2fNegativeShaderApiTests.hpp
+	es2fNegativeStateApiTests.cpp
+	es2fNegativeStateApiTests.hpp
+	es2fNegativeTextureApiTests.cpp
+	es2fNegativeTextureApiTests.hpp
+	es2fNegativeVertexArrayApiTests.cpp
+	es2fNegativeVertexArrayApiTests.hpp
+	es2fPrerequisiteTests.cpp
+	es2fPrerequisiteTests.hpp
+	es2fRandomShaderTests.cpp
+	es2fRandomShaderTests.hpp
+	es2fShaderApiTests.cpp
+	es2fShaderApiTests.hpp
+	es2fShaderAlgorithmTests.cpp
+	es2fShaderAlgorithmTests.hpp
+	es2fShaderBuiltinVarTests.cpp
+	es2fShaderBuiltinVarTests.hpp
+	es2fShaderConstExprTests.cpp
+	es2fShaderConstExprTests.hpp
+	es2fShaderDiscardTests.cpp
+	es2fShaderDiscardTests.hpp
+	es2fShaderExecuteTest.cpp
+	es2fShaderExecuteTest.hpp
+	es2fShaderIndexingTests.cpp
+	es2fShaderIndexingTests.hpp
+	es2fShaderLoopTests.cpp
+	es2fShaderLoopTests.hpp
+	es2fShaderMatrixTests.cpp
+	es2fShaderMatrixTests.hpp
+	es2fShaderOperatorTests.cpp
+	es2fShaderOperatorTests.hpp
+	es2fShaderReturnTests.cpp
+	es2fShaderReturnTests.hpp
+	es2fShaderStructTests.cpp
+	es2fShaderStructTests.hpp
+	es2fShaderTextureFunctionTests.cpp
+	es2fShaderTextureFunctionTests.hpp
+	es2fScissorTests.cpp
+	es2fScissorTests.hpp
+	es2fStencilTests.cpp
+	es2fStencilTests.hpp
+	es2fTextureCompletenessTests.cpp
+	es2fTextureCompletenessTests.hpp
+	es2fTextureFilteringTests.cpp
+	es2fTextureFilteringTests.hpp
+	es2fTextureFormatTests.cpp
+	es2fTextureFormatTests.hpp
+	es2fTextureMipmapTests.cpp
+	es2fTextureMipmapTests.hpp
+	es2fTextureSizeTests.cpp
+	es2fTextureSizeTests.hpp
+	es2fTextureSpecificationTests.cpp
+	es2fTextureSpecificationTests.hpp
+	es2fTextureWrapTests.cpp
+	es2fTextureWrapTests.hpp
+	es2fVertexArrayTest.cpp
+	es2fVertexArrayTest.hpp
+	es2fNegativeTextureApiTests.hpp
+	es2fNegativeTextureApiTests.cpp
+	es2fNegativeVertexArrayApiTests.hpp
+	es2fNegativeVertexArrayApiTests.cpp
+	es2fNegativeFragmentApiTests.hpp
+	es2fNegativeFragmentApiTests.cpp
+	es2fNegativeBufferApiTests.hpp
+	es2fNegativeBufferApiTests.cpp
+	es2fNegativeShaderApiTests.hpp
+	es2fNegativeShaderApiTests.cpp
+	es2fTextureCompletenessTests.hpp
+	es2fTextureCompletenessTests.cpp
+	es2fRasterizationTests.hpp
+	es2fRasterizationTests.cpp
+	es2fVertexTextureTests.cpp
+	es2fVertexTextureTests.hpp
+	es2fTextureUnitTests.cpp
+	es2fTextureUnitTests.hpp
+	es2fBlendTests.cpp
+	es2fBlendTests.hpp
+	es2fRandomFragmentOpTests.cpp
+	es2fRandomFragmentOpTests.hpp
+	es2fMultisampleTests.cpp
+	es2fMultisampleTests.hpp
+	es2fUniformApiTests.cpp
+	es2fUniformApiTests.hpp
+	es2fBufferTestUtil.cpp
+	es2fBufferTestUtil.hpp
+	es2fBufferWriteTests.cpp
+	es2fBufferWriteTests.hpp
+	es2fImplementationLimitTests.cpp
+	es2fImplementationLimitTests.hpp
+	es2fReadPixelsTests.cpp
+	es2fReadPixelsTests.hpp
+	es2fDepthRangeTests.cpp
+	es2fDepthRangeTests.hpp
+	es2fDitheringTests.hpp
+	es2fDitheringTests.cpp
+	es2fBooleanStateQueryTests.hpp
+	es2fBooleanStateQueryTests.cpp
+	es2fIntegerStateQueryTests.hpp
+	es2fIntegerStateQueryTests.cpp
+	es2fFloatStateQueryTests.hpp
+	es2fFloatStateQueryTests.cpp
+	es2fTextureStateQueryTests.hpp
+	es2fTextureStateQueryTests.cpp
+	es2fStringQueryTests.hpp
+	es2fStringQueryTests.cpp
+	es2fBufferObjectQueryTests.hpp
+	es2fBufferObjectQueryTests.cpp
+	es2fFboStateQueryTests.hpp
+	es2fFboStateQueryTests.cpp
+	es2fRboStateQueryTests.hpp
+	es2fRboStateQueryTests.cpp
+	es2fShaderStateQueryTests.hpp
+	es2fShaderStateQueryTests.cpp
+	es2fClippingTests.hpp
+	es2fClippingTests.cpp
+	es2fPolygonOffsetTests.hpp
+	es2fPolygonOffsetTests.cpp
+	es2fDrawTests.hpp
+	es2fDrawTests.cpp
+	es2fShaderInvarianceTests.hpp
+	es2fShaderInvarianceTests.cpp
+	es2fFragOpInteractionTests.cpp
+	es2fFragOpInteractionTests.hpp
+	es2fFlushFinishTests.cpp
+	es2fFlushFinishTests.hpp
+	es2fShaderFragDataTests.cpp
+	es2fShaderFragDataTests.hpp
+	)
+
+add_library(deqp-gles2-functional STATIC ${DEQP_GLES2_FUNCTIONAL_SRCS})
+target_link_libraries(deqp-gles2-functional deqp-gl-shared glutil glutil-sglr tcutil referencerenderer ${DEQP_GLES2_LIBRARIES})
diff --git a/modules/gles2/functional/es2fApiCase.cpp b/modules/gles2/functional/es2fApiCase.cpp
new file mode 100644
index 0000000..ee5ba3c
--- /dev/null
+++ b/modules/gles2/functional/es2fApiCase.cpp
@@ -0,0 +1,112 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief API test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fApiCase.hpp"
+#include "gluStrUtil.hpp"
+#include "gluRenderContext.hpp"
+
+#include <algorithm>
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+ApiCase::ApiCase (Context& context, const char* name, const char* description)
+	: TestCase		(context, name, description)
+	, CallLogWrapper(context.getRenderContext().getFunctions(), context.getTestContext().getLog())
+	, m_log			(context.getTestContext().getLog())
+{
+}
+
+ApiCase::~ApiCase (void)
+{
+}
+
+ApiCase::IterateResult ApiCase::iterate (void)
+{
+	// Initialize result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Enable call logging.
+	enableLogging(true);
+
+	// Run test.
+	test();
+
+	return STOP;
+}
+
+void ApiCase::expectError (deUint32 expected)
+{
+	deUint32 err = glGetError();
+	if (err != expected)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: expected " << glu::getErrorStr(expected) << tcu::TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid error");
+	}
+}
+
+void ApiCase::expectError (deUint32 expected0, deUint32 expected1)
+{
+	deUint32 err = glGetError();
+	if (err != expected0 && err != expected1)
+	{
+		m_log << tcu::TestLog::Message << "// ERROR: expected " << glu::getErrorStr(expected0) << " or " << glu::getErrorStr(expected1) << tcu::TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid error");
+	}
+}
+
+void ApiCase::checkBooleans (deUint8 value, deUint8 expected)
+{
+	if (value != expected)
+	{
+		m_log << tcu::TestLog::Message << "// ERROR: expected " << (expected	? "GL_TRUE" : "GL_FALSE") << tcu::TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void ApiCase::getSupportedExtensions (const deUint32 numSupportedValues, const deUint32 extension, std::vector<int>& values)
+{
+	deInt32 numFormats;
+	GLU_CHECK_CALL(glGetIntegerv(numSupportedValues, &numFormats));
+	if (numFormats == 0)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "// No supported extensions available." << tcu::TestLog::EndMessage;
+		return;
+	}
+	values.resize(numFormats);
+	GLU_CHECK_CALL(glGetIntegerv(extension, &values[0]));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fApiCase.hpp b/modules/gles2/functional/es2fApiCase.hpp
new file mode 100644
index 0000000..9bbfd90
--- /dev/null
+++ b/modules/gles2/functional/es2fApiCase.hpp
@@ -0,0 +1,73 @@
+#ifndef _ES2FAPICASE_HPP
+#define _ES2FAPICASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief API test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "tcuTestLog.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ApiCase : public TestCase, protected glu::CallLogWrapper
+{
+public:
+						ApiCase					(Context& context, const char* name, const char* description);
+	virtual				~ApiCase				(void);
+
+	IterateResult		iterate					(void);
+
+protected:
+	virtual void		test					(void) = DE_NULL;
+
+	void				expectError				(deUint32 error);
+	void				expectError				(deUint32 error0, deUint32 error1);
+	void				getSupportedExtensions	(const deUint32 numSupportedValues, const deUint32 extension, std::vector<int>& values);
+	void				checkBooleans			(deUint8 value, deUint8 expected);
+
+	tcu::TestLog&		m_log;
+};
+
+// Helper macro for declaring ApiCases.
+#define ES2F_ADD_API_CASE(NAME, DESCRIPTION, TEST_FUNC_BODY)							\
+	do {																				\
+		class ApiCase_##NAME : public ApiCase {											\
+		public:																			\
+			ApiCase_##NAME (Context& context) : ApiCase(context, #NAME, DESCRIPTION) {}	\
+		protected:																		\
+			void test (void) TEST_FUNC_BODY												\
+		};																				\
+		addChild(new ApiCase_##NAME(m_context));										\
+	} while (deGetFalse())
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FAPICASE_HPP
diff --git a/modules/gles2/functional/es2fAttribLocationTests.cpp b/modules/gles2/functional/es2fAttribLocationTests.cpp
new file mode 100644
index 0000000..3ded798
--- /dev/null
+++ b/modules/gles2/functional/es2fAttribLocationTests.cpp
@@ -0,0 +1,161 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Attribute location test
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fAttribLocationTests.hpp"
+
+#include "glsAttributeLocationTests.hpp"
+
+#include "deStringUtil.hpp"
+#include "gluDefs.hpp"
+#include "gluRenderContext.hpp"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+#include "tcuTestLog.hpp"
+
+#include <vector>
+
+using namespace deqp::gls::AttributeLocationTestUtil;
+using std::vector;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+TestCaseGroup* createAttributeLocationTests (Context& context)
+{
+	const AttribType	types[] =
+	{
+		AttribType("float",	1,  GL_FLOAT),
+		AttribType("vec2",	1,  GL_FLOAT_VEC2),
+		AttribType("vec3",	1,  GL_FLOAT_VEC3),
+		AttribType("vec4",	1,  GL_FLOAT_VEC4),
+
+		AttribType("mat2",	2,  GL_FLOAT_MAT2),
+		AttribType("mat3",	3,  GL_FLOAT_MAT3),
+		AttribType("mat4",	4,  GL_FLOAT_MAT4)
+	};
+
+	TestCaseGroup* const root = new TestCaseGroup (context, "attribute_location", "Attribute location tests");
+
+	// Basic bind attribute tests
+	{
+		TestCaseGroup* const bindAttributeGroup = new TestCaseGroup(context, "bind", "Basic attribute binding tests.");
+
+		root->addChild(bindAttributeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+			bindAttributeGroup->addChild(new gls::BindAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Bind max number of attributes
+	{
+		TestCaseGroup* const bindMaxAttributeGroup = new TestCaseGroup(context, "bind_max_attributes", "Test using maximum attributes with bind.");
+
+		root->addChild(bindMaxAttributeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+			bindMaxAttributeGroup->addChild(new gls::BindMaxAttributesTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test aliasing
+	{
+		TestCaseGroup* const aliasingGroup = new TestCaseGroup(context, "bind_aliasing", "Test attribute location aliasing with bind.");
+
+		root->addChild(aliasingGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+
+			// Simple aliasing cases
+			aliasingGroup->addChild(new gls::BindAliasingAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+
+			// For types which occupy more than one location. Alias second location.
+			if (type.getLocationSize() > 1)
+				aliasingGroup->addChild(new gls::BindAliasingAttributeTest(context.getTestContext(), context.getRenderContext(), type, 1));
+
+			// Use more than maximum attributes with conditional aliasing
+			aliasingGroup->addChild(new gls::BindMaxAliasingAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+
+			// Use more than maximum attributes with inactive attributes
+			aliasingGroup->addChild(new gls::BindInactiveAliasingAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test filling holes in attribute location
+	{
+		TestCaseGroup* const holeGroup = new TestCaseGroup(context, "bind_hole", "Bind all, but one attribute and leave hole in location space for it.");
+
+		root->addChild(holeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+
+			// Bind first location, leave hole size of type and fill rest of locations
+			holeGroup->addChild(new gls::BindHoleAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test binding at different times
+	{
+		TestCaseGroup* const bindTimeGroup = new TestCaseGroup(context, "bind_time", "Bind time tests. Test binding at different stages.");
+
+		root->addChild(bindTimeGroup);
+
+		bindTimeGroup->addChild(new gls::PreAttachBindAttributeTest(context.getTestContext(), context.getRenderContext()));
+		bindTimeGroup->addChild(new gls::PreLinkBindAttributeTest(context.getTestContext(), context.getRenderContext()));
+		bindTimeGroup->addChild(new gls::PostLinkBindAttributeTest(context.getTestContext(), context.getRenderContext()));
+		bindTimeGroup->addChild(new gls::BindRelinkAttributeTest(context.getTestContext(), context.getRenderContext()));
+		bindTimeGroup->addChild(new gls::BindReattachAttributeTest(context.getTestContext(), context.getRenderContext()));
+	}
+
+	// Test relinking program
+	{
+		TestCaseGroup* const relinkHoleGroup = new TestCaseGroup(context, "bind_relink_hole", "Test relinking with moving hole in attribute location space.");
+
+		root->addChild(relinkHoleGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+
+			relinkHoleGroup->addChild(new gls::BindRelinkHoleAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	return root;
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fAttribLocationTests.hpp b/modules/gles2/functional/es2fAttribLocationTests.hpp
new file mode 100644
index 0000000..5d414aa
--- /dev/null
+++ b/modules/gles2/functional/es2fAttribLocationTests.hpp
@@ -0,0 +1,42 @@
+#ifndef _ES2FATTRIBLOCATIONTESTS_HPP
+#define _ES2FATTRIBLOCATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Attribute location tests
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+TestCaseGroup* createAttributeLocationTests (Context& context);
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FATTRIBLOCATIONTESTS_HPP
diff --git a/modules/gles2/functional/es2fBlendTests.cpp b/modules/gles2/functional/es2fBlendTests.cpp
new file mode 100644
index 0000000..029970e
--- /dev/null
+++ b/modules/gles2/functional/es2fBlendTests.cpp
@@ -0,0 +1,447 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Blend tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fBlendTests.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "deRandom.hpp"
+#include "rrFragmentOperations.hpp"
+#include "sglrReferenceUtils.hpp"
+
+#include "glw.h"
+
+#include <string>
+#include <vector>
+
+namespace deqp
+{
+
+using gls::FragmentOpUtil::Quad;
+using gls::FragmentOpUtil::IntegerQuad;
+using gls::FragmentOpUtil::QuadRenderer;
+using gls::FragmentOpUtil::ReferenceQuadRenderer;
+using glu::getBlendEquationName;
+using glu::getBlendFactorName;
+using tcu::Vec4;
+using tcu::UVec4;
+using tcu::TestLog;
+using tcu::Surface;
+using tcu::TextureFormat;
+using tcu::TextureLevel;
+using std::string;
+using std::vector;
+
+namespace gles2
+{
+namespace Functional
+{
+
+static const int MAX_VIEWPORT_WIDTH		= 64;
+static const int MAX_VIEWPORT_HEIGHT	= 64;
+
+struct BlendParams
+{
+	GLenum	equationRGB;
+	GLenum	srcFuncRGB;
+	GLenum	dstFuncRGB;
+	GLenum	equationAlpha;
+	GLenum	srcFuncAlpha;
+	GLenum	dstFuncAlpha;
+	Vec4	blendColor;
+
+	BlendParams (GLenum		equationRGB_,
+				 GLenum		srcFuncRGB_,
+				 GLenum		dstFuncRGB_,
+				 GLenum		equationAlpha_,
+				 GLenum		srcFuncAlpha_,
+				 GLenum		dstFuncAlpha_,
+				 Vec4		blendColor_)
+	: equationRGB	(equationRGB_)
+	, srcFuncRGB	(srcFuncRGB_)
+	, dstFuncRGB	(dstFuncRGB_)
+	, equationAlpha	(equationAlpha_)
+	, srcFuncAlpha	(srcFuncAlpha_)
+	, dstFuncAlpha	(dstFuncAlpha_)
+	, blendColor	(blendColor_)
+	{
+	}
+};
+
+class BlendCase : public TestCase
+{
+public:
+							BlendCase	(Context&						context,
+										 const char*					name,
+										 const char*					desc,
+										 const vector<BlendParams>&		paramSets);
+
+							~BlendCase	(void);
+
+	void					init		(void);
+	void					deinit		(void);
+
+	IterateResult			iterate		(void);
+
+private:
+							BlendCase	(const BlendCase& other);
+	BlendCase&				operator=	(const BlendCase& other);
+
+	vector<BlendParams>		m_paramSets;
+	int						m_curParamSetNdx;
+
+	QuadRenderer*			m_renderer;
+	ReferenceQuadRenderer*	m_referenceRenderer;
+	TextureLevel*			m_refColorBuffer;
+	Quad					m_firstQuad;
+	Quad					m_secondQuad;
+	IntegerQuad				m_firstQuadInt;
+	IntegerQuad				m_secondQuadInt;
+
+	int						m_viewportW;
+	int						m_viewportH;
+};
+
+BlendCase::BlendCase (Context&						context,
+					  const char*					name,
+					  const char*					desc,
+					  const vector<BlendParams>&	paramSets)
+	: TestCase				(context, name, desc)
+	, m_paramSets			(paramSets)
+	, m_curParamSetNdx		(0)
+	, m_renderer			(DE_NULL)
+	, m_referenceRenderer	(DE_NULL)
+	, m_refColorBuffer		(DE_NULL)
+	, m_viewportW			(0)
+	, m_viewportH			(0)
+{
+	DE_ASSERT(!m_paramSets.empty());
+	for (int i = 0; i < (int)m_paramSets.size(); i++)
+		DE_ASSERT(m_paramSets[i].dstFuncRGB != GL_SRC_ALPHA_SATURATE && m_paramSets[i].dstFuncAlpha != GL_SRC_ALPHA_SATURATE);
+}
+
+void BlendCase::init (void)
+{
+	bool useRGB = m_context.getRenderTarget().getPixelFormat().alphaBits == 0;
+
+	static const Vec4 baseGradientColors[4] =
+	{
+		Vec4(0.0f, 0.5f, 1.0f, 0.5f),
+		Vec4(0.5f, 0.0f, 0.5f, 1.0f),
+		Vec4(0.5f, 1.0f, 0.5f, 0.0f),
+		Vec4(1.0f, 0.5f, 0.0f, 0.5f)
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(m_firstQuad.color) == DE_LENGTH_OF_ARRAY(m_firstQuadInt.color));
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(m_firstQuad.color); i++)
+	{
+		m_firstQuad.color[i]		= (baseGradientColors[i] - 0.5f) * 0.2f + 0.5f;
+		m_firstQuadInt.color[i]		= m_firstQuad.color[i];
+
+		m_secondQuad.color[i]		= (Vec4(1.0f) - baseGradientColors[i] - 0.5f) * 1.0f + 0.5f;
+		m_secondQuadInt.color[i]	= m_secondQuad.color[i];
+	}
+
+	m_viewportW = de::min<int>(m_context.getRenderTarget().getWidth(),	MAX_VIEWPORT_WIDTH);
+	m_viewportH = de::min<int>(m_context.getRenderTarget().getHeight(),	MAX_VIEWPORT_HEIGHT);
+
+	m_firstQuadInt.posA		= tcu::IVec2(0,					0);
+	m_secondQuadInt.posA	= tcu::IVec2(0,					0);
+	m_firstQuadInt.posB		= tcu::IVec2(m_viewportW-1,		m_viewportH-1);
+	m_secondQuadInt.posB	= tcu::IVec2(m_viewportW-1,		m_viewportH-1);
+
+	DE_ASSERT(!m_renderer);
+	DE_ASSERT(!m_referenceRenderer);
+	DE_ASSERT(!m_refColorBuffer);
+
+	m_renderer				= new QuadRenderer(m_context.getRenderContext(), glu::GLSL_VERSION_100_ES);
+	m_referenceRenderer		= new ReferenceQuadRenderer;
+	m_refColorBuffer		= new TextureLevel(TextureFormat(useRGB ? TextureFormat::RGB : TextureFormat::RGBA, TextureFormat::UNORM_INT8),
+											   m_viewportW, m_viewportH);
+
+	m_curParamSetNdx = 0;
+}
+
+BlendCase::~BlendCase (void)
+{
+	delete m_renderer;
+	delete m_referenceRenderer;
+	delete m_refColorBuffer;
+}
+
+void BlendCase::deinit (void)
+{
+	delete m_renderer;
+	delete m_referenceRenderer;
+	delete m_refColorBuffer;
+
+	m_renderer			= DE_NULL;
+	m_referenceRenderer	= DE_NULL;
+	m_refColorBuffer	= DE_NULL;
+}
+
+BlendCase::IterateResult BlendCase::iterate (void)
+{
+	de::Random						rnd				(deStringHash(getName()) ^ deInt32Hash(m_curParamSetNdx));
+	int								viewportX		= rnd.getInt(0, m_context.getRenderTarget().getWidth() - m_viewportW);
+	int								viewportY		= rnd.getInt(0, m_context.getRenderTarget().getHeight() - m_viewportH);
+	tcu::Surface					renderedImg		(m_viewportW, m_viewportH);
+	TestLog&						log				(m_testCtx.getLog());
+	const BlendParams&				paramSet		= m_paramSets[m_curParamSetNdx];
+	rr::FragmentOperationState		referenceState;
+
+	// Log the blend parameters.
+
+	log << TestLog::Message << "RGB equation = " << getBlendEquationName(paramSet.equationRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "RGB src func = " << getBlendFactorName(paramSet.srcFuncRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "RGB dst func = " << getBlendFactorName(paramSet.dstFuncRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "Alpha equation = " << getBlendEquationName(paramSet.equationAlpha) << TestLog::EndMessage;
+	log << TestLog::Message << "Alpha src func = " << getBlendFactorName(paramSet.srcFuncAlpha) << TestLog::EndMessage;
+	log << TestLog::Message << "Alpha dst func = " << getBlendFactorName(paramSet.dstFuncAlpha) << TestLog::EndMessage;
+	log << TestLog::Message << "Blend color = (" << paramSet.blendColor.x() << ", " << paramSet.blendColor.y() << ", " << paramSet.blendColor.z() << ", " << paramSet.blendColor.w() << ")" << TestLog::EndMessage;
+
+	// Set GL state.
+
+	GLU_CHECK_CALL(glBlendEquationSeparate(paramSet.equationRGB, paramSet.equationAlpha));
+	GLU_CHECK_CALL(glBlendFuncSeparate(paramSet.srcFuncRGB, paramSet.dstFuncRGB, paramSet.srcFuncAlpha, paramSet.dstFuncAlpha));
+	GLU_CHECK_CALL(glBlendColor(paramSet.blendColor.x(), paramSet.blendColor.y(), paramSet.blendColor.z(), paramSet.blendColor.w()));
+
+	// Set reference state.
+
+	referenceState.blendRGBState.equation	= sglr::rr_util::mapGLBlendEquation(paramSet.equationRGB);
+	referenceState.blendRGBState.srcFunc	= sglr::rr_util::mapGLBlendFunc(paramSet.srcFuncRGB);
+	referenceState.blendRGBState.dstFunc	= sglr::rr_util::mapGLBlendFunc(paramSet.dstFuncRGB);
+	referenceState.blendAState.equation		= sglr::rr_util::mapGLBlendEquation(paramSet.equationAlpha);
+	referenceState.blendAState.srcFunc		= sglr::rr_util::mapGLBlendFunc(paramSet.srcFuncAlpha);
+	referenceState.blendAState.dstFunc		= sglr::rr_util::mapGLBlendFunc(paramSet.dstFuncAlpha);
+	referenceState.blendColor				= paramSet.blendColor;
+
+	// Render with GL.
+
+	glDisable(GL_BLEND);
+	glViewport(viewportX, viewportY, m_viewportW, m_viewportH);
+	m_renderer->render(m_firstQuad);
+	glEnable(GL_BLEND);
+	m_renderer->render(m_secondQuad);
+	glFlush();
+
+	// Render reference.
+
+	const tcu::PixelBufferAccess nullAccess(TextureFormat(), 0, 0, 0, DE_NULL);
+
+	referenceState.blendMode = rr::BLENDMODE_NONE;
+	m_referenceRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()), nullAccess /* no depth */, nullAccess /* no stencil */, m_firstQuadInt, referenceState);
+	referenceState.blendMode = rr::BLENDMODE_STANDARD;
+	m_referenceRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()), nullAccess /* no depth */, nullAccess /* no stencil */, m_secondQuadInt, referenceState);
+
+	// Read GL image.
+
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedImg.getAccess());
+
+	// Compare images.
+
+	UVec4 compareThreshold = m_context.getRenderTarget().getPixelFormat().getColorThreshold().toIVec().asUint()
+							 * UVec4(5) / UVec4(2) + UVec4(2); // \note Non-scientific ad hoc formula. Need big threshold when few color bits; blending brings extra inaccuracy.
+
+	bool comparePass = tcu::intThresholdCompare(m_testCtx.getLog(), "CompareResult", "Image Comparison Result", m_refColorBuffer->getAccess(), renderedImg.getAccess(), compareThreshold, tcu::COMPARE_LOG_RESULT);
+
+	// Fail now if images don't match.
+
+	if (!comparePass)
+	{
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Image compare failed");
+		return STOP;
+	}
+
+	// Continue if param sets still remain in m_paramSets; otherwise stop.
+
+	m_curParamSetNdx++;
+
+	if (m_curParamSetNdx < (int)m_paramSets.size())
+		return CONTINUE;
+	else
+	{
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+		return STOP;
+	}
+}
+
+BlendTests::BlendTests (Context& context)
+	: TestCaseGroup(context, "blend", "Blend tests")
+{
+}
+
+BlendTests::~BlendTests (void)
+{
+}
+
+void BlendTests::init (void)
+{
+	struct EnumGL
+	{
+		GLenum			glValue;
+		const char*		nameStr;
+	};
+
+	static const EnumGL blendEquations[] =
+	{
+		{ GL_FUNC_ADD,					"add"					},
+		{ GL_FUNC_SUBTRACT,				"subtract"				},
+		{ GL_FUNC_REVERSE_SUBTRACT,		"reverse_subtract"		}
+	};
+
+	static const EnumGL blendFunctions[] =
+	{
+		{ GL_ZERO,							"zero"						},
+		{ GL_ONE,							"one"						},
+		{ GL_SRC_COLOR,						"src_color"					},
+		{ GL_ONE_MINUS_SRC_COLOR,			"one_minus_src_color"		},
+		{ GL_DST_COLOR,						"dst_color"					},
+		{ GL_ONE_MINUS_DST_COLOR,			"one_minus_dst_color"		},
+		{ GL_SRC_ALPHA,						"src_alpha"					},
+		{ GL_ONE_MINUS_SRC_ALPHA,			"one_minus_src_alpha"		},
+		{ GL_DST_ALPHA,						"dst_alpha"					},
+		{ GL_ONE_MINUS_DST_ALPHA,			"one_minus_dst_alpha"		},
+		{ GL_CONSTANT_COLOR,				"constant_color"			},
+		{ GL_ONE_MINUS_CONSTANT_COLOR,		"one_minus_constant_color"	},
+		{ GL_CONSTANT_ALPHA,				"constant_alpha"			},
+		{ GL_ONE_MINUS_CONSTANT_ALPHA,		"one_minus_constant_alpha"	},
+		{ GL_SRC_ALPHA_SATURATE,			"src_alpha_saturate"		}
+	};
+
+	const Vec4 defaultBlendColor(0.2f, 0.4f, 0.6f, 0.8f);
+
+	// Test all blend equation, src blend function, dst blend function combinations. RGB and alpha modes are the same.
+
+	{
+		TestCaseGroup* group = new TestCaseGroup(m_context, "equation_src_func_dst_func", "Combinations of Blend Equations and Functions");
+		addChild(group);
+
+		for (int equationNdx = 0;	equationNdx < DE_LENGTH_OF_ARRAY(blendEquations);	equationNdx++)
+		for (int srcFuncNdx = 0;	srcFuncNdx < DE_LENGTH_OF_ARRAY(blendFunctions);	srcFuncNdx++)
+		for (int dstFuncNdx = 0;	dstFuncNdx < DE_LENGTH_OF_ARRAY(blendFunctions);	dstFuncNdx++)
+		{
+			const EnumGL& eq	= blendEquations[equationNdx];
+			const EnumGL& src	= blendFunctions[srcFuncNdx];
+			const EnumGL& dst	= blendFunctions[dstFuncNdx];
+
+			if (dst.glValue == GL_SRC_ALPHA_SATURATE) // SRC_ALPHA_SATURATE is only valid for src func.
+				continue;
+
+			string name			= string("") + eq.nameStr + "_" + src.nameStr + "_" + dst.nameStr;
+			string description	= string("") +
+								  "Equations "		+ getBlendEquationName(eq.glValue) +
+								  ", src funcs "	+ getBlendFactorName(src.glValue) +
+								  ", dst funcs "	+ getBlendFactorName(dst.glValue);
+
+			vector<BlendParams> paramSets;
+			paramSets.push_back(BlendParams(eq.glValue, src.glValue, dst.glValue, eq.glValue, src.glValue, dst.glValue, defaultBlendColor));
+
+			group->addChild(new BlendCase(m_context, name.c_str(), description.c_str(), paramSets));
+		}
+	}
+
+	// Test all RGB src, alpha src and RGB dst, alpha dst combinations. Equations are ADD.
+	// \note For all RGB src, alpha src combinations, also test a couple of different RGBA dst functions, and vice versa.
+
+	{
+		TestCaseGroup* mainGroup = new TestCaseGroup(m_context, "rgb_func_alpha_func", "Combinations of RGB and Alpha Functions");
+		addChild(mainGroup);
+		TestCaseGroup* srcGroup = new TestCaseGroup(m_context, "src", "Source functions");
+		TestCaseGroup* dstGroup = new TestCaseGroup(m_context, "dst", "Destination functions");
+		mainGroup->addChild(srcGroup);
+		mainGroup->addChild(dstGroup);
+
+		for (int isDstI = 0;		isDstI <= 1;										isDstI++)
+		for (int rgbFuncNdx = 0;	rgbFuncNdx < DE_LENGTH_OF_ARRAY(blendFunctions);	rgbFuncNdx++)
+		for (int alphaFuncNdx = 0;	alphaFuncNdx < DE_LENGTH_OF_ARRAY(blendFunctions);	alphaFuncNdx++)
+		{
+			bool			isSrc			= isDstI == 0;
+			TestCaseGroup*	curGroup		= isSrc ? srcGroup : dstGroup;
+			const EnumGL&	funcRGB			= blendFunctions[rgbFuncNdx];
+			const EnumGL&	funcAlpha		= blendFunctions[alphaFuncNdx];
+			const char*		dstOrSrcStr		= isSrc ? "src" : "dst";
+
+			if (!isSrc && (funcRGB.glValue == GL_SRC_ALPHA_SATURATE || funcAlpha.glValue == GL_SRC_ALPHA_SATURATE)) // SRC_ALPHA_SATURATE is only valid for src func.
+				continue;
+
+			string name			= string("") + funcRGB.nameStr + "_" + funcAlpha.nameStr;
+			string description	= string("") +
+								  "RGB "		+ dstOrSrcStr + " func " + getBlendFactorName(funcRGB.glValue) +
+								  ", alpha "	+ dstOrSrcStr + " func " + getBlendFactorName(funcAlpha.glValue);
+
+			// First, make param sets as if this was a src case.
+
+			vector<BlendParams> paramSets;
+			paramSets.push_back(BlendParams(GL_FUNC_ADD, funcRGB.glValue, GL_ONE,			GL_FUNC_ADD, funcAlpha.glValue, GL_ONE,			defaultBlendColor));
+			paramSets.push_back(BlendParams(GL_FUNC_ADD, funcRGB.glValue, GL_ZERO,			GL_FUNC_ADD, funcAlpha.glValue, GL_ZERO,		defaultBlendColor));
+			paramSets.push_back(BlendParams(GL_FUNC_ADD, funcRGB.glValue, GL_SRC_COLOR,		GL_FUNC_ADD, funcAlpha.glValue, GL_SRC_COLOR,	defaultBlendColor));
+			paramSets.push_back(BlendParams(GL_FUNC_ADD, funcRGB.glValue, GL_DST_COLOR,		GL_FUNC_ADD, funcAlpha.glValue, GL_DST_COLOR,	defaultBlendColor));
+
+			// Swap src and dst params if this is a dst case.
+
+			if (!isSrc)
+			{
+				for (int i = 0; i < (int)paramSets.size(); i++)
+				{
+					std::swap(paramSets[i].srcFuncRGB,		paramSets[i].dstFuncRGB);
+					std::swap(paramSets[i].srcFuncAlpha,	paramSets[i].dstFuncAlpha);
+				}
+			}
+
+			curGroup->addChild(new BlendCase(m_context, name.c_str(), description.c_str(), paramSets));
+		}
+	}
+
+	// Test all RGB and alpha equation combinations. Src and dst funcs are ONE for both.
+
+	{
+		TestCaseGroup* group = new TestCaseGroup(m_context, "rgb_equation_alpha_equation", "Combinations of RGB and Alpha Equation Combinations");
+		addChild(group);
+
+		for (int equationRGBNdx = 0;	equationRGBNdx < DE_LENGTH_OF_ARRAY(blendEquations);	equationRGBNdx++)
+		for (int equationAlphaNdx = 0;	equationAlphaNdx < DE_LENGTH_OF_ARRAY(blendEquations);	equationAlphaNdx++)
+		{
+			const EnumGL& eqRGB			= blendEquations[equationRGBNdx];
+			const EnumGL& eqAlpha		= blendEquations[equationAlphaNdx];
+
+			string name			= string("") + eqRGB.nameStr + "_" + eqAlpha.nameStr;
+			string description	= string("") +
+								  "RGB equation "		+ getBlendEquationName(eqRGB.glValue) +
+								  ", alpha equation "	+ getBlendEquationName(eqAlpha.glValue);
+
+			vector<BlendParams> paramSets;
+			paramSets.push_back(BlendParams(eqRGB.glValue, GL_ONE, GL_ONE, eqAlpha.glValue, GL_ONE, GL_ONE, defaultBlendColor));
+
+			group->addChild(new BlendCase(m_context, name.c_str(), description.c_str(), paramSets));
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fBlendTests.hpp b/modules/gles2/functional/es2fBlendTests.hpp
new file mode 100644
index 0000000..419f82b
--- /dev/null
+++ b/modules/gles2/functional/es2fBlendTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FBLENDTESTS_HPP
+#define _ES2FBLENDTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Blend tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class BlendTests : public TestCaseGroup
+{
+public:
+					BlendTests			(Context& context);
+					~BlendTests			(void);
+
+	void			init				(void);
+
+private:
+					BlendTests			(const BlendTests& other);
+	BlendTests&		operator=			(const BlendTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FBLENDTESTS_HPP
diff --git a/modules/gles2/functional/es2fBooleanStateQueryTests.cpp b/modules/gles2/functional/es2fBooleanStateQueryTests.cpp
new file mode 100644
index 0000000..67c8406
--- /dev/null
+++ b/modules/gles2/functional/es2fBooleanStateQueryTests.cpp
@@ -0,0 +1,622 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Boolean State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fBooleanStateQueryTests.hpp"
+#include "es2fApiCase.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "tcuRenderTarget.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace BooleanStateQueryVerifiers
+{
+
+// StateVerifier
+
+class StateVerifier : protected glu::CallLogWrapper
+{
+public:
+						StateVerifier					(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~StateVerifier					(); // make GCC happy
+
+	const char*			getTestNamePostfix				(void) const;
+
+	virtual void		verifyBoolean					(tcu::TestContext& testCtx, GLenum name, bool reference)														= DE_NULL;
+	virtual void		verifyBoolean4					(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)	= DE_NULL;
+	virtual void		verifyBooleanAnything			(tcu::TestContext& testCtx, GLenum name)																		= DE_NULL;
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+StateVerifier::StateVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+
+StateVerifier::~StateVerifier ()
+{
+}
+
+const char* StateVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+// IsEnabledVerifier
+
+class IsEnabledVerifier : public StateVerifier
+{
+public:
+			IsEnabledVerifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyBoolean			(tcu::TestContext& testCtx, GLenum name, bool reference);
+	void	verifyBoolean4			(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3);
+	void	verifyBooleanAnything	(tcu::TestContext& testCtx, GLenum name);
+};
+
+IsEnabledVerifier::IsEnabledVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_isenabled")
+{
+}
+
+void IsEnabledVerifier::verifyBoolean (tcu::TestContext& testCtx, GLenum name, bool reference)
+{
+	using tcu::TestLog;
+
+	const GLboolean state = glIsEnabled(name);
+	const GLboolean expectedGLState = reference ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << (reference ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void IsEnabledVerifier::verifyBoolean4 (tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)
+{
+	DE_UNREF(testCtx);
+	DE_UNREF(name);
+	DE_UNREF(reference0);
+	DE_UNREF(reference1);
+	DE_UNREF(reference2);
+	DE_UNREF(reference3);
+	DE_ASSERT(false && "not supported");
+}
+
+void IsEnabledVerifier::verifyBooleanAnything (tcu::TestContext& testCtx, GLenum name)
+{
+	DE_UNREF(testCtx);
+	DE_UNREF(name);
+	DE_ASSERT(false && "not supported");
+}
+// GetBooleanVerifier
+
+class GetBooleanVerifier : public StateVerifier
+{
+public:
+			GetBooleanVerifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyBoolean			(tcu::TestContext& testCtx, GLenum name, bool reference);
+	void	verifyBoolean4			(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3);
+	void	verifyBooleanAnything	(tcu::TestContext& testCtx, GLenum name);
+};
+
+GetBooleanVerifier::GetBooleanVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getboolean")
+{
+}
+
+void GetBooleanVerifier::verifyBoolean (tcu::TestContext& testCtx, GLenum name, bool reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLboolean expectedGLState = reference ? GL_TRUE : GL_FALSE;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << (reference ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyBoolean4 (tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean[4]> boolVector4;
+	glGetBooleanv(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	const GLboolean referenceAsGLBoolean[] =
+	{
+		reference0 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference1 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference2 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference3 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+	};
+
+	if (boolVector4[0] != referenceAsGLBoolean[0] ||
+		boolVector4[1] != referenceAsGLBoolean[1] ||
+		boolVector4[2] != referenceAsGLBoolean[2] ||
+		boolVector4[3] != referenceAsGLBoolean[3])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (referenceAsGLBoolean[0] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[1] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[2] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[3] ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyBooleanAnything (tcu::TestContext& testCtx, GLenum name)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	state.verifyValidity(testCtx);
+}
+
+//GetIntegerVerifier
+
+class GetIntegerVerifier : public StateVerifier
+{
+public:
+			GetIntegerVerifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyBoolean			(tcu::TestContext& testCtx, GLenum name, bool reference);
+	void	verifyBoolean4			(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3);
+	void	verifyBooleanAnything	(tcu::TestContext& testCtx, GLenum name);
+
+};
+
+GetIntegerVerifier::GetIntegerVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger")
+{
+}
+
+void GetIntegerVerifier::verifyBoolean (tcu::TestContext& testCtx, GLenum name, bool reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint expectedGLState = reference ? 1 : 0;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << expectedGLState << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyBoolean4 (tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[4]> boolVector4;
+	glGetIntegerv(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	const GLint referenceAsGLint[] =
+	{
+		reference0 ? 1 : 0,
+		reference1 ? 1 : 0,
+		reference2 ? 1 : 0,
+		reference3 ? 1 : 0,
+	};
+
+	if (boolVector4[0] != referenceAsGLint[0] ||
+		boolVector4[1] != referenceAsGLint[1] ||
+		boolVector4[2] != referenceAsGLint[2] ||
+		boolVector4[3] != referenceAsGLint[3])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< referenceAsGLint[0] << " "
+			<< referenceAsGLint[1] << " "
+			<< referenceAsGLint[2] << " "
+			<< referenceAsGLint[3] << " " << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyBooleanAnything (tcu::TestContext& testCtx, GLenum name)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	state.verifyValidity(testCtx);
+}
+
+//GetFloatVerifier
+
+class GetFloatVerifier : public StateVerifier
+{
+public:
+			GetFloatVerifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyBoolean			(tcu::TestContext& testCtx, GLenum name, bool reference);
+	void	verifyBoolean4			(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3);
+	void	verifyBooleanAnything	(tcu::TestContext& testCtx, GLenum name);
+};
+
+GetFloatVerifier::GetFloatVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getfloat")
+{
+}
+
+void GetFloatVerifier::verifyBoolean (tcu::TestContext& testCtx, GLenum name, bool reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLfloat expectedGLState = reference ? 1.0f : 0.0f;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << expectedGLState << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyBoolean4 (tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[4]> boolVector4;
+	glGetFloatv(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	const GLfloat referenceAsGLfloat[] =
+	{
+		reference0 ? 1.0f : 0.0f,
+		reference1 ? 1.0f : 0.0f,
+		reference2 ? 1.0f : 0.0f,
+		reference3 ? 1.0f : 0.0f,
+	};
+
+	if (boolVector4[0] != referenceAsGLfloat[0] ||
+		boolVector4[1] != referenceAsGLfloat[1] ||
+		boolVector4[2] != referenceAsGLfloat[2] ||
+		boolVector4[3] != referenceAsGLfloat[3])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< referenceAsGLfloat[0] << " "
+			<< referenceAsGLfloat[1] << " "
+			<< referenceAsGLfloat[2] << " "
+			<< referenceAsGLfloat[3] << " " << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyBooleanAnything (tcu::TestContext& testCtx, GLenum name)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	state.verifyValidity(testCtx);
+}
+
+} // BooleanStateQueryVerifiers
+
+namespace
+{
+
+using namespace BooleanStateQueryVerifiers;
+
+class IsEnabledStateTestCase : public ApiCase
+{
+public:
+	IsEnabledStateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName, bool initial)
+		: ApiCase		(context, name, description)
+		, m_targetName	(targetName)
+		, m_initial		(initial)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		// check inital value
+		m_verifier->verifyBoolean(m_testCtx, m_targetName, m_initial);
+		expectError(GL_NO_ERROR);
+
+		// check toggle
+
+		glEnable(m_targetName);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, m_targetName, true);
+		expectError(GL_NO_ERROR);
+
+		glDisable(m_targetName);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, m_targetName, false);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	GLenum			m_targetName;
+	bool			m_initial;
+	StateVerifier*	m_verifier;
+};
+
+class DepthWriteMaskTestCase : public ApiCase
+{
+public:
+	DepthWriteMaskTestCase	(Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyBoolean(m_testCtx, GL_DEPTH_WRITEMASK, true);
+		expectError(GL_NO_ERROR);
+
+		glDepthMask(GL_FALSE);
+		m_verifier->verifyBoolean(m_testCtx, GL_DEPTH_WRITEMASK, false);
+		expectError(GL_NO_ERROR);
+
+		glDepthMask(GL_TRUE);
+		m_verifier->verifyBoolean(m_testCtx, GL_DEPTH_WRITEMASK, true);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class SampleCoverageInvertTestCase : public ApiCase
+{
+public:
+	SampleCoverageInvertTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyBoolean(m_testCtx, GL_SAMPLE_COVERAGE_INVERT, false);
+		expectError(GL_NO_ERROR);
+
+		glSampleCoverage(1.0f, GL_TRUE);
+		m_verifier->verifyBoolean(m_testCtx, GL_SAMPLE_COVERAGE_INVERT, true);
+		expectError(GL_NO_ERROR);
+
+		glSampleCoverage(1.0f, GL_FALSE);
+		m_verifier->verifyBoolean(m_testCtx, GL_SAMPLE_COVERAGE_INVERT, false);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ShaderCompilerTestCase : public ApiCase
+{
+public:
+	ShaderCompilerTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyBooleanAnything(m_testCtx, GL_SHADER_COMPILER);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ColorMaskTestCase : public ApiCase
+{
+public:
+	ColorMaskTestCase	(Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+	void test (void)
+	{
+		m_verifier->verifyBoolean4(m_testCtx, GL_COLOR_WRITEMASK, true, true, true, true);
+		expectError(GL_NO_ERROR);
+
+		const struct ColorMask
+		{
+			GLboolean r, g, b, a;
+		} testMasks[] =
+		{
+			{ GL_TRUE,	GL_TRUE,	GL_TRUE,	GL_TRUE  },
+			{ GL_TRUE,	GL_TRUE,	GL_TRUE,	GL_FALSE },
+			{ GL_TRUE,	GL_TRUE,	GL_FALSE,	GL_TRUE  },
+			{ GL_TRUE,	GL_TRUE,	GL_FALSE,	GL_FALSE },
+			{ GL_TRUE,	GL_FALSE,	GL_TRUE,	GL_TRUE  },
+			{ GL_TRUE,	GL_FALSE,	GL_TRUE,	GL_FALSE },
+			{ GL_TRUE,	GL_FALSE,	GL_FALSE,	GL_TRUE  },
+			{ GL_TRUE,	GL_FALSE,	GL_FALSE,	GL_FALSE },
+			{ GL_FALSE,	GL_TRUE,	GL_TRUE,	GL_TRUE  },
+			{ GL_FALSE,	GL_TRUE,	GL_TRUE,	GL_FALSE },
+			{ GL_FALSE,	GL_TRUE,	GL_FALSE,	GL_TRUE  },
+			{ GL_FALSE,	GL_TRUE,	GL_FALSE,	GL_FALSE },
+			{ GL_FALSE,	GL_FALSE,	GL_TRUE,	GL_TRUE  },
+			{ GL_FALSE,	GL_FALSE,	GL_TRUE,	GL_FALSE },
+			{ GL_FALSE,	GL_FALSE,	GL_FALSE,	GL_TRUE  },
+			{ GL_FALSE,	GL_FALSE,	GL_FALSE,	GL_FALSE },
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(testMasks); ndx++)
+		{
+			glColorMask(testMasks[ndx].r, testMasks[ndx].g, testMasks[ndx].b, testMasks[ndx].a);
+			m_verifier->verifyBoolean4(m_testCtx, GL_COLOR_WRITEMASK, testMasks[ndx].r==GL_TRUE, testMasks[ndx].g==GL_TRUE, testMasks[ndx].b==GL_TRUE, testMasks[ndx].a==GL_TRUE);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		StateVerifier* verifier = VERIFIERS[_verifierNdx];										\
+		CODE_BLOCK;																				\
+	}
+
+} // anonymous
+
+BooleanStateQueryTests::BooleanStateQueryTests (Context& context)
+	: TestCaseGroup				(context, "boolean", "Boolean State Query tests")
+	, m_verifierIsEnabled		(DE_NULL)
+	, m_verifierBoolean			(DE_NULL)
+	, m_verifierInteger			(DE_NULL)
+	, m_verifierFloat			(DE_NULL)
+{
+}
+
+BooleanStateQueryTests::~BooleanStateQueryTests (void)
+{
+	deinit();
+}
+
+void BooleanStateQueryTests::init (void)
+{
+	DE_ASSERT(m_verifierIsEnabled == DE_NULL);
+	DE_ASSERT(m_verifierBoolean == DE_NULL);
+	DE_ASSERT(m_verifierInteger == DE_NULL);
+	DE_ASSERT(m_verifierFloat == DE_NULL);
+
+	m_verifierIsEnabled		= new IsEnabledVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierBoolean		= new GetBooleanVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInteger		= new GetIntegerVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierFloat			= new GetFloatVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+
+	StateVerifier* isEnabledVerifiers[] = {m_verifierIsEnabled, m_verifierBoolean, m_verifierInteger, m_verifierFloat};
+	StateVerifier* normalVerifiers[]	= {						m_verifierBoolean, m_verifierInteger, m_verifierFloat};
+
+	struct StateBoolean
+	{
+		const char*		name;
+		const char*		description;
+		GLenum			targetName;
+		bool			value;
+	};
+	const StateBoolean isEnableds[] =
+	{
+		{ "cull_face",						"CULL_FACE",						GL_CULL_FACE,						false},
+		{ "polygon_offset_fill",			"POLYGON_OFFSET_FILL",				GL_POLYGON_OFFSET_FILL,				false},
+		{ "sample_alpha_to_coverage",		"SAMPLE_ALPHA_TO_COVERAGE",			GL_SAMPLE_ALPHA_TO_COVERAGE,		false},
+		{ "sample_coverage",				"SAMPLE_COVERAGE",					GL_SAMPLE_COVERAGE,					false},
+		{ "scissor_test",					"SCISSOR_TEST",						GL_SCISSOR_TEST,					false},
+		{ "stencil_test",					"STENCIL_TEST",						GL_STENCIL_TEST,					false},
+		{ "depth_test",						"DEPTH_TEST",						GL_DEPTH_TEST,						false},
+		{ "blend",							"BLEND",							GL_BLEND,							false},
+		{ "dither",							"DITHER",							GL_DITHER,							true },
+	};
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(isEnableds); testNdx++)
+		FOR_EACH_VERIFIER(isEnabledVerifiers, addChild(new IsEnabledStateTestCase(m_context, verifier, (std::string(isEnableds[testNdx].name) + verifier->getTestNamePostfix()).c_str(), isEnableds[testNdx].description, isEnableds[testNdx].targetName, isEnableds[testNdx].value)));
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new SampleCoverageInvertTestCase				(m_context, verifier, (std::string("sample_coverage_invert")				+ verifier->getTestNamePostfix()).c_str(), "SAMPLE_COVERAGE_INVERT")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ColorMaskTestCase							(m_context, verifier, (std::string("color_writemask")						+ verifier->getTestNamePostfix()).c_str(), "COLOR_WRITEMASK")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new DepthWriteMaskTestCase						(m_context, verifier, (std::string("depth_writemask")						+ verifier->getTestNamePostfix()).c_str(), "DEPTH_WRITEMASK")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ShaderCompilerTestCase						(m_context, verifier, (std::string("shader_compiler")						+ verifier->getTestNamePostfix()).c_str(), "SHADER_COMPILER")));
+}
+
+void BooleanStateQueryTests::deinit (void)
+{
+	if (m_verifierIsEnabled)
+	{
+		delete m_verifierIsEnabled;
+		m_verifierIsEnabled = DE_NULL;
+	}
+	if (m_verifierBoolean)
+	{
+		delete m_verifierBoolean;
+		m_verifierBoolean = DE_NULL;
+	}
+	if (m_verifierInteger)
+	{
+		delete m_verifierInteger;
+		m_verifierInteger = DE_NULL;
+	}
+	if (m_verifierFloat)
+	{
+		delete m_verifierFloat;
+		m_verifierFloat = DE_NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fBooleanStateQueryTests.hpp b/modules/gles2/functional/es2fBooleanStateQueryTests.hpp
new file mode 100644
index 0000000..6093475
--- /dev/null
+++ b/modules/gles2/functional/es2fBooleanStateQueryTests.hpp
@@ -0,0 +1,68 @@
+#ifndef _ES2FBOOLEANSTATEQUERYTESTS_HPP
+#define _ES2FBOOLEANSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Boolean State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace BooleanStateQueryVerifiers
+{
+
+class IsEnabledVerifier;
+class GetBooleanVerifier;
+class GetIntegerVerifier;
+class GetFloatVerifier;
+
+} // BooleanStateQueryVerifiers
+
+class BooleanStateQueryTests : public TestCaseGroup
+{
+public:
+																BooleanStateQueryTests	(Context& context);
+																~BooleanStateQueryTests	(void);
+
+	void														init					(void);
+	void														deinit					(void);
+
+private:
+																BooleanStateQueryTests	(const BooleanStateQueryTests& other);
+	BooleanStateQueryTests&										operator=				(const BooleanStateQueryTests& other);
+
+	BooleanStateQueryVerifiers::IsEnabledVerifier*				m_verifierIsEnabled;
+	BooleanStateQueryVerifiers::GetBooleanVerifier*				m_verifierBoolean;
+	BooleanStateQueryVerifiers::GetIntegerVerifier*				m_verifierInteger;
+	BooleanStateQueryVerifiers::GetFloatVerifier*				m_verifierFloat;
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FBOOLEANSTATEQUERYTESTS_HPP
diff --git a/modules/gles2/functional/es2fBufferObjectQueryTests.cpp b/modules/gles2/functional/es2fBufferObjectQueryTests.cpp
new file mode 100644
index 0000000..7995ac2
--- /dev/null
+++ b/modules/gles2/functional/es2fBufferObjectQueryTests.cpp
@@ -0,0 +1,284 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer Object Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fBufferObjectQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es2fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+
+#include <limits>
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace BufferParamVerifiers
+{
+
+void checkIntEquals (tcu::TestContext& testCtx, GLint got, GLint expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void checkPointerEquals (tcu::TestContext& testCtx, const void* got, const void* expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+class BufferParamVerifier : protected glu::CallLogWrapper
+{
+public:
+						BufferParamVerifier		(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~BufferParamVerifier	(); // make GCC happy
+
+	const char*			getTestNamePostfix		(void) const;
+
+	virtual void		verifyInteger			(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)	= DE_NULL;
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+BufferParamVerifier::BufferParamVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+
+BufferParamVerifier::~BufferParamVerifier ()
+{
+}
+
+const char* BufferParamVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+class GetBufferParameterIVerifier : public BufferParamVerifier
+{
+public:
+			GetBufferParameterIVerifier					(const glw::Functions& gl, tcu::TestLog& log);
+
+	void	verifyInteger								(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference);
+};
+
+GetBufferParameterIVerifier::GetBufferParameterIVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: BufferParamVerifier(gl, log, "_getbufferparameteri")
+{
+}
+
+void GetBufferParameterIVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetBufferParameteriv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	}
+}
+
+} // BufferParamVerifiers
+
+namespace
+{
+
+using namespace BufferParamVerifiers;
+
+// Tests
+
+class BufferCase : public ApiCase
+{
+public:
+	BufferCase (Context& context, BufferParamVerifier* verifier, const char* name, const char* description)
+		: ApiCase			(context, name, description)
+		, m_bufferTarget	(0)
+		, m_verifier		(verifier)
+	{
+	}
+
+	virtual void testBuffer (void) = DE_NULL;
+
+	void test (void)
+	{
+		const GLenum bufferTargets[] =
+		{
+			GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER
+		};
+		const int targets = DE_LENGTH_OF_ARRAY(bufferTargets);
+
+		for (int ndx = 0; ndx < targets; ++ndx)
+		{
+			m_bufferTarget = bufferTargets[ndx];
+
+			GLuint bufferId = 0;
+			glGenBuffers(1, &bufferId);
+			glBindBuffer(m_bufferTarget, bufferId);
+			expectError(GL_NO_ERROR);
+
+			testBuffer();
+
+			glDeleteBuffers(1, &bufferId);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+protected:
+	GLenum					m_bufferTarget;
+	BufferParamVerifier*	m_verifier;
+};
+
+class BufferSizeCase : public BufferCase
+{
+public:
+	BufferSizeCase (Context& context, BufferParamVerifier* verifier, const char* name, const char* description)
+		: BufferCase(context, verifier, name, description)
+	{
+	}
+
+	void testBuffer (void)
+	{
+		const int numIteration = 16;
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_SIZE, 0);
+
+		for (int i = 0; i < numIteration; ++i)
+		{
+			const GLint len = rnd.getInt(0, 1024);
+			glBufferData(m_bufferTarget, len, DE_NULL, GL_STREAM_DRAW);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_SIZE, len);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class BufferUsageCase : public BufferCase
+{
+public:
+	BufferUsageCase (Context& context, BufferParamVerifier* verifier, const char* name, const char* description)
+		: BufferCase(context, verifier, name, description)
+	{
+	}
+
+	void testBuffer (void)
+	{
+		const GLenum usages[] =
+		{
+			GL_STATIC_DRAW, GL_DYNAMIC_DRAW, GL_STREAM_DRAW
+		};
+
+		m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_USAGE, GL_STATIC_DRAW);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(usages); ++ndx)
+		{
+			glBufferData(m_bufferTarget, 16, DE_NULL, usages[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_USAGE, usages[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+} // anonymous
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		BufferParamVerifier* verifier = VERIFIERS[_verifierNdx];								\
+		CODE_BLOCK;																				\
+	}
+
+BufferObjectQueryTests::BufferObjectQueryTests (Context& context)
+	: TestCaseGroup		(context, "buffer_object", "Buffer Object Query tests")
+	, m_verifierInt		(DE_NULL)
+{
+}
+
+BufferObjectQueryTests::~BufferObjectQueryTests (void)
+{
+	deinit();
+}
+
+void BufferObjectQueryTests::init (void)
+{
+	using namespace BufferParamVerifiers;
+
+	DE_ASSERT(m_verifierInt == DE_NULL);
+
+	m_verifierInt		= new GetBufferParameterIVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	BufferParamVerifier* verifiers[] = {m_verifierInt};
+
+	FOR_EACH_VERIFIER(verifiers, addChild(new BufferSizeCase		(m_context, verifier,	(std::string("buffer_size")				+ verifier->getTestNamePostfix()).c_str(), "BUFFER_SIZE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new BufferUsageCase		(m_context, verifier,	(std::string("buffer_usage")			+ verifier->getTestNamePostfix()).c_str(), "BUFFER_USAGE")));
+}
+
+void BufferObjectQueryTests::deinit (void)
+{
+	if (m_verifierInt)
+	{
+		delete m_verifierInt;
+		m_verifierInt = NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fBufferObjectQueryTests.hpp b/modules/gles2/functional/es2fBufferObjectQueryTests.hpp
new file mode 100644
index 0000000..34447f1
--- /dev/null
+++ b/modules/gles2/functional/es2fBufferObjectQueryTests.hpp
@@ -0,0 +1,62 @@
+#ifndef _ES2FBUFFEROBJECTQUERYTESTS_HPP
+#define _ES2FBUFFEROBJECTQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer Object Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace BufferParamVerifiers
+{
+
+class GetBufferParameterIVerifier;
+
+} // BufferParamVerifiers
+
+class BufferObjectQueryTests : public TestCaseGroup
+{
+public:
+																		BufferObjectQueryTests	(Context& context);
+																		~BufferObjectQueryTests	(void);
+
+	void																init					(void);
+	void																deinit					(void);
+
+private:
+																		BufferObjectQueryTests	(const BufferObjectQueryTests& other);
+	BufferObjectQueryTests&												operator=				(const BufferObjectQueryTests& other);
+
+	BufferParamVerifiers::GetBufferParameterIVerifier*					m_verifierInt;
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FBUFFEROBJECTQUERYTESTS_HPP
diff --git a/modules/gles2/functional/es2fBufferTestUtil.cpp b/modules/gles2/functional/es2fBufferTestUtil.cpp
new file mode 100644
index 0000000..a94025b
--- /dev/null
+++ b/modules/gles2/functional/es2fBufferTestUtil.cpp
@@ -0,0 +1,648 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fBufferTestUtil.hpp"
+#include "tcuRandomValueIterator.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVector.hpp"
+#include "tcuFormatUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+#include "gluStrUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "deMemory.h"
+#include "deStringUtil.hpp"
+
+#include <algorithm>
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace BufferTestUtil
+{
+
+enum
+{
+	VERIFY_QUAD_SIZE					= 8,		//!< Quad size in VertexArrayVerifier
+	MAX_LINES_PER_INDEX_ARRAY_DRAW		= 128,		//!< Maximum number of lines per one draw in IndexArrayVerifier
+	INDEX_ARRAY_DRAW_VIEWPORT_WIDTH		= 128,
+	INDEX_ARRAY_DRAW_VIEWPORT_HEIGHT	= 128
+};
+
+static const bool LOG_VERIFIER_CALLS = false; //! \note Especially array verifier generates a lot of calls.
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using std::set;
+
+// Helper functions.
+
+void fillWithRandomBytes (deUint8* ptr, int numBytes, deUint32 seed)
+{
+	std::copy(tcu::RandomValueIterator<deUint8>::begin(seed, numBytes), tcu::RandomValueIterator<deUint8>::end(), ptr);
+}
+
+bool compareByteArrays (tcu::TestLog& log, const deUint8* resPtr, const deUint8* refPtr, int numBytes)
+{
+	bool			isOk			= true;
+	const int		maxSpanLen		= 8;
+	const int		maxDiffSpans	= 4;
+	int				numDiffSpans	= 0;
+	int				diffSpanStart	= -1;
+	int				ndx				= 0;
+
+	log << TestLog::Section("Verify", "Verification result");
+
+	for (;ndx < numBytes; ndx++)
+	{
+		if (resPtr[ndx] != refPtr[ndx])
+		{
+			if (diffSpanStart < 0)
+				diffSpanStart = ndx;
+
+			isOk = false;
+		}
+		else if (diffSpanStart >= 0)
+		{
+			if (numDiffSpans < maxDiffSpans)
+			{
+				int len			= ndx-diffSpanStart;
+				int	printLen	= de::min(len, maxSpanLen);
+
+				log << TestLog::Message << len << " byte difference at offset " << diffSpanStart << "\n"
+										<< "  expected "	<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart+printLen)) << "\n"
+										<< "  got "			<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart+printLen))
+					<< TestLog::EndMessage;
+			}
+			else
+				log << TestLog::Message << "(output too long, truncated)" << TestLog::EndMessage;
+
+			numDiffSpans	+= 1;
+			diffSpanStart	 = -1;
+		}
+	}
+
+	if (diffSpanStart >= 0)
+	{
+		if (numDiffSpans < maxDiffSpans)
+		{
+				int len			= ndx-diffSpanStart;
+				int	printLen	= de::min(len, maxSpanLen);
+
+				log << TestLog::Message << len << " byte difference at offset " << diffSpanStart << "\n"
+										<< "  expected "	<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart+printLen)) << "\n"
+										<< "  got "			<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart+printLen))
+					<< TestLog::EndMessage;
+		}
+		else
+			log << TestLog::Message << "(output too long, truncated)" << TestLog::EndMessage;
+	}
+
+	log << TestLog::Message << (isOk ? "Verification passed." : "Verification FAILED!") << TestLog::EndMessage;
+	log << TestLog::EndSection;
+
+	return isOk;
+}
+
+const char* getBufferTargetName (deUint32 target)
+{
+	switch (target)
+	{
+		case GL_ARRAY_BUFFER:				return "array";
+		case GL_ELEMENT_ARRAY_BUFFER:		return "element_array";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+const char* getUsageHintName (deUint32 hint)
+{
+	switch (hint)
+	{
+		case GL_STREAM_DRAW:	return "stream_draw";
+		case GL_STATIC_DRAW:	return "static_draw";
+		case GL_DYNAMIC_DRAW:	return "dynamic_draw";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+// BufferCase
+
+BufferCase::BufferCase (Context& context, const char* name, const char* description)
+	: TestCase			(context, name, description)
+	, CallLogWrapper	(context.getRenderContext().getFunctions(), m_context.getTestContext().getLog())
+{
+}
+
+BufferCase::~BufferCase (void)
+{
+	enableLogging(false);
+	BufferCase::deinit();
+}
+
+void BufferCase::init (void)
+{
+	enableLogging(true);
+}
+
+void BufferCase::deinit (void)
+{
+	for (set<deUint32>::const_iterator bufIter = m_allocatedBuffers.begin(); bufIter != m_allocatedBuffers.end(); bufIter++)
+		glDeleteBuffers(1, &(*bufIter));
+}
+
+deUint32 BufferCase::genBuffer (void)
+{
+	deUint32 buf = 0;
+	glGenBuffers(1, &buf);
+	if (buf != 0)
+	{
+		try
+		{
+			m_allocatedBuffers.insert(buf);
+		}
+		catch (const std::exception&)
+		{
+			glDeleteBuffers(1, &buf);
+			throw;
+		}
+	}
+	return buf;
+}
+
+void BufferCase::deleteBuffer (deUint32 buffer)
+{
+	glDeleteBuffers(1, &buffer);
+	m_allocatedBuffers.erase(buffer);
+}
+
+void BufferCase::checkError (void)
+{
+	glw::GLenum err = glGetError();
+	if (err != GL_NO_ERROR)
+		throw tcu::TestError(string("Got ") + glu::getErrorStr(err).toString());
+}
+
+// ReferenceBuffer
+
+void ReferenceBuffer::setSize (int numBytes)
+{
+	m_data.resize(numBytes);
+}
+
+void ReferenceBuffer::setData (int numBytes, const deUint8* bytes)
+{
+	m_data.resize(numBytes);
+	std::copy(bytes, bytes+numBytes, m_data.begin());
+}
+
+void ReferenceBuffer::setSubData (int offset, int numBytes, const deUint8* bytes)
+{
+	DE_ASSERT(de::inBounds(offset, 0, (int)m_data.size()) && de::inRange(offset+numBytes, offset, (int)m_data.size()));
+	std::copy(bytes, bytes+numBytes, m_data.begin()+offset);
+}
+
+// BufferVerifierBase
+
+BufferVerifierBase::BufferVerifierBase (Context& context)
+	: CallLogWrapper	(context.getRenderContext().getFunctions(), context.getTestContext().getLog())
+	, m_context			(context)
+{
+	enableLogging(LOG_VERIFIER_CALLS);
+}
+
+// BufferVerifier
+
+BufferVerifier::BufferVerifier (Context& context, VerifyType verifyType)
+	: m_verifier(DE_NULL)
+{
+	switch (verifyType)
+	{
+		case VERIFY_AS_VERTEX_ARRAY:	m_verifier = new VertexArrayVerifier(context);	break;
+		case VERIFY_AS_INDEX_ARRAY:		m_verifier = new IndexArrayVerifier	(context);	break;
+		default:
+			TCU_FAIL("Unsupported verifier");
+	}
+}
+
+BufferVerifier::~BufferVerifier (void)
+{
+	delete m_verifier;
+}
+
+bool BufferVerifier::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes)
+{
+	DE_ASSERT(numBytes >= getMinSize());
+	DE_ASSERT(offset%getAlignment() == 0);
+	DE_ASSERT((offset+numBytes)%getAlignment() == 0);
+	return m_verifier->verify(buffer, reference, offset, numBytes);
+}
+
+// VertexArrayVerifier
+
+VertexArrayVerifier::VertexArrayVerifier (Context& context)
+	: BufferVerifierBase	(context)
+	, m_program				(DE_NULL)
+	, m_posLoc				(0)
+	, m_byteVecLoc			(0)
+{
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(
+		"attribute highp vec2 a_position;\n"
+		"attribute mediump vec3 a_byteVec;\n"
+		"varying mediump vec3 v_byteVec;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		"	v_byteVec = a_byteVec;\n"
+		"}\n",
+
+		"varying mediump vec3 v_byteVec;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_FragColor = vec4(v_byteVec, 1.0);\n"
+		"}\n"));
+
+	if (!m_program->isOk())
+	{
+		m_context.getTestContext().getLog() << *m_program;
+		delete m_program;
+		TCU_FAIL("Compile failed");
+	}
+
+	const glw::Functions& funcs = context.getRenderContext().getFunctions();
+	m_posLoc		= funcs.getAttribLocation(m_program->getProgram(), "a_position");
+	m_byteVecLoc	= funcs.getAttribLocation(m_program->getProgram(), "a_byteVec");
+}
+
+VertexArrayVerifier::~VertexArrayVerifier (void)
+{
+	delete m_program;
+}
+
+static void computePositions (vector<tcu::Vec2>& positions, int gridSizeX, int gridSizeY)
+{
+	positions.resize(gridSizeX*gridSizeY*4);
+
+	for (int y = 0; y < gridSizeY; y++)
+	for (int x = 0; x < gridSizeX; x++)
+	{
+		float	sx0			= (x+0) / (float)gridSizeX;
+		float	sy0			= (y+0) / (float)gridSizeY;
+		float	sx1			= (x+1) / (float)gridSizeX;
+		float	sy1			= (y+1) / (float)gridSizeY;
+		float	fx0			= 2.0f * sx0 - 1.0f;
+		float	fy0			= 2.0f * sy0 - 1.0f;
+		float	fx1			= 2.0f * sx1 - 1.0f;
+		float	fy1			= 2.0f * sy1 - 1.0f;
+		int		baseNdx		= (y * gridSizeX + x)*4;
+
+		positions[baseNdx+0] = tcu::Vec2(fx0, fy0);
+		positions[baseNdx+1] = tcu::Vec2(fx0, fy1);
+		positions[baseNdx+2] = tcu::Vec2(fx1, fy0);
+		positions[baseNdx+3] = tcu::Vec2(fx1, fy1);
+	}
+}
+
+static void computeIndices (vector<deUint16>& indices, int gridSizeX, int gridSizeY)
+{
+	indices.resize(3 * 2 * gridSizeX * gridSizeY);
+
+	for (int quadNdx = 0; quadNdx < gridSizeX*gridSizeY; quadNdx++)
+	{
+		int v00 = quadNdx*4 + 0;
+		int v01 = quadNdx*4 + 1;
+		int v10 = quadNdx*4 + 2;
+		int v11 = quadNdx*4 + 3;
+
+		DE_ASSERT(v11 < (1<<16));
+
+		indices[quadNdx*6 + 0] = (deUint16)v10;
+		indices[quadNdx*6 + 1] = (deUint16)v00;
+		indices[quadNdx*6 + 2] = (deUint16)v01;
+
+		indices[quadNdx*6 + 3] = (deUint16)v10;
+		indices[quadNdx*6 + 4] = (deUint16)v01;
+		indices[quadNdx*6 + 5] = (deUint16)v11;
+	}
+}
+
+static inline tcu::Vec4 fetchVtxColor (const deUint8* ptr, int vtxNdx)
+{
+	return tcu::RGBA(*(ptr + vtxNdx*3 + 0),
+					 *(ptr + vtxNdx*3 + 1),
+					 *(ptr + vtxNdx*3 + 2),
+					 255).toVec();
+}
+
+static void renderQuadGridReference (tcu::Surface& dst, int numQuads, int rowLength, const deUint8* inPtr)
+{
+	using tcu::Vec4;
+
+	dst.setSize(rowLength*VERIFY_QUAD_SIZE, (numQuads/rowLength + (numQuads%rowLength != 0 ? 1 : 0))*VERIFY_QUAD_SIZE);
+
+	tcu::PixelBufferAccess dstAccess = dst.getAccess();
+	tcu::clear(dstAccess, tcu::IVec4(0, 0, 0, 0xff));
+
+	for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
+	{
+		int		x0		= (quadNdx%rowLength)*VERIFY_QUAD_SIZE;
+		int		y0		= (quadNdx/rowLength)*VERIFY_QUAD_SIZE;
+		Vec4	v00		= fetchVtxColor(inPtr, quadNdx*4 + 0);
+		Vec4	v10		= fetchVtxColor(inPtr, quadNdx*4 + 1);
+		Vec4	v01		= fetchVtxColor(inPtr, quadNdx*4 + 2);
+		Vec4	v11		= fetchVtxColor(inPtr, quadNdx*4 + 3);
+
+		for (int y = 0; y < VERIFY_QUAD_SIZE; y++)
+		for (int x = 0; x < VERIFY_QUAD_SIZE; x++)
+		{
+			float		fx		= (float)(x+0.5f) / (float)VERIFY_QUAD_SIZE;
+			float		fy		= (float)(y+0.5f) / (float)VERIFY_QUAD_SIZE;
+
+			bool		tri		= fx + fy <= 1.0f;
+			float		tx		= tri ? fx : (1.0f-fx);
+			float		ty		= tri ? fy : (1.0f-fy);
+			const Vec4&	t0		= tri ? v00 : v11;
+			const Vec4&	t1		= tri ? v01 : v10;
+			const Vec4&	t2		= tri ? v10 : v01;
+			Vec4		color	= t0 + (t1-t0)*tx + (t2-t0)*ty;
+
+			dstAccess.setPixel(color, x0+x, y0+y);
+		}
+	}
+}
+
+bool VertexArrayVerifier::verify (deUint32 buffer, const deUint8* refPtr, int offset, int numBytes)
+{
+	const tcu::RenderTarget&	renderTarget		= m_context.getRenderContext().getRenderTarget();
+	const int					numBytesInVtx		= 3;
+	const int					numBytesInQuad		= numBytesInVtx*4;
+	int							maxQuadsX			= de::min(128, renderTarget.getWidth()	/ VERIFY_QUAD_SIZE);
+	int							maxQuadsY			= de::min(128, renderTarget.getHeight()	/ VERIFY_QUAD_SIZE);
+	int							maxQuadsPerBatch	= maxQuadsX*maxQuadsY;
+	int							numVerified			= 0;
+	deUint32					program				= m_program->getProgram();
+	tcu::RGBA					threshold			= renderTarget.getPixelFormat().getColorThreshold() + tcu::RGBA(3,3,3,3);
+	bool						isOk				= true;
+
+	vector<tcu::Vec2>			positions;
+	vector<deUint16>			indices;
+
+	tcu::Surface				rendered;
+	tcu::Surface				reference;
+
+	DE_ASSERT(numBytes >= numBytesInQuad); // Can't render full quad with smaller buffers.
+
+	computePositions(positions, maxQuadsX, maxQuadsY);
+	computeIndices(indices, maxQuadsX, maxQuadsY);
+
+	// Reset buffer bindings.
+	glBindBuffer				(GL_ELEMENT_ARRAY_BUFFER,	0);
+	glBindBuffer				(GL_ARRAY_BUFFER,			0);
+
+	// Setup rendering state.
+	glViewport					(0, 0, maxQuadsX*VERIFY_QUAD_SIZE, maxQuadsY*VERIFY_QUAD_SIZE);
+	glClearColor				(0.0f, 0.0f, 0.0f, 1.0f);
+	glUseProgram				(program);
+	glEnableVertexAttribArray	(m_posLoc);
+	glVertexAttribPointer		(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, &positions[0]);
+	glEnableVertexAttribArray	(m_byteVecLoc);
+	glBindBuffer				(GL_ARRAY_BUFFER, buffer);
+
+	while (numVerified < numBytes)
+	{
+		int		numRemaining		= numBytes-numVerified;
+		bool	isLeftoverBatch		= numRemaining < numBytesInQuad;
+		int		numBytesToVerify	= isLeftoverBatch ? numBytesInQuad				: de::min(maxQuadsPerBatch*numBytesInQuad, numRemaining - numRemaining%numBytesInQuad);
+		int		curOffset			= isLeftoverBatch ? (numBytes-numBytesInQuad)	: numVerified;
+		int		numQuads			= numBytesToVerify/numBytesInQuad;
+		int		numCols				= de::min(maxQuadsX, numQuads);
+		int		numRows				= numQuads/maxQuadsX + (numQuads%maxQuadsX != 0 ? 1 : 0);
+		string	imageSetDesc		= string("Bytes ") + de::toString(offset+curOffset) + " to " + de::toString(offset+curOffset+numBytesToVerify-1);
+
+		DE_ASSERT(numBytesToVerify > 0 && numBytesToVerify%numBytesInQuad == 0);
+		DE_ASSERT(de::inBounds(curOffset, 0, numBytes));
+		DE_ASSERT(de::inRange(curOffset+numBytesToVerify, curOffset, numBytes));
+
+		// Render batch.
+		glClear					(GL_COLOR_BUFFER_BIT);
+		glVertexAttribPointer	(m_byteVecLoc, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0, (const glw::GLvoid*)(deUintptr)(offset + curOffset));
+		glDrawElements			(GL_TRIANGLES, numQuads*6, GL_UNSIGNED_SHORT, &indices[0]);
+
+		renderQuadGridReference(reference,  numQuads, numCols, refPtr + offset + curOffset);
+
+		rendered.setSize(numCols*VERIFY_QUAD_SIZE, numRows*VERIFY_QUAD_SIZE);
+		glu::readPixels(m_context.getRenderContext(), 0, 0, rendered.getAccess());
+
+		if (!tcu::pixelThresholdCompare(m_context.getTestContext().getLog(), "RenderResult", imageSetDesc.c_str(), reference, rendered, threshold, tcu::COMPARE_LOG_RESULT))
+		{
+			isOk = false;
+			break;
+		}
+
+		numVerified += isLeftoverBatch ? numRemaining : numBytesToVerify;
+	}
+
+	glDisableVertexAttribArray	(m_posLoc);
+	glDisableVertexAttribArray	(m_byteVecLoc);
+
+	return isOk;
+}
+
+// IndexArrayVerifier
+
+IndexArrayVerifier::IndexArrayVerifier (Context& context)
+	: BufferVerifierBase	(context)
+	, m_program				(DE_NULL)
+	, m_posLoc				(0)
+	, m_colorLoc			(0)
+{
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(
+		"attribute highp vec2 a_position;\n"
+		"attribute mediump vec3 a_color;\n"
+		"varying mediump vec3 v_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		"	v_color = a_color;\n"
+		"}\n",
+
+		"varying mediump vec3 v_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_FragColor = vec4(v_color, 1.0);\n"
+		"}\n"));
+
+	if (!m_program->isOk())
+	{
+		m_context.getTestContext().getLog() << *m_program;
+		delete m_program;
+		TCU_FAIL("Compile failed");
+	}
+
+	const glw::Functions& funcs = context.getRenderContext().getFunctions();
+	m_posLoc	= funcs.getAttribLocation(m_program->getProgram(), "a_position");
+	m_colorLoc	= funcs.getAttribLocation(m_program->getProgram(), "a_color");
+}
+
+IndexArrayVerifier::~IndexArrayVerifier (void)
+{
+	delete m_program;
+}
+
+static void computeIndexVerifierPositions (std::vector<tcu::Vec2>& dst)
+{
+	const int	numPosX		= 16;
+	const int	numPosY		= 16;
+
+	dst.resize(numPosX*numPosY);
+
+	for (int y = 0; y < numPosY; y++)
+	{
+		for (int x = 0; x < numPosX; x++)
+		{
+			float	xf	= float(x) / float(numPosX-1);
+			float	yf	= float(y) / float(numPosY-1);
+
+			dst[y*numPosX + x] = tcu::Vec2(2.0f*xf - 1.0f, 2.0f*yf - 1.0f);
+		}
+	}
+}
+
+static void computeIndexVerifierColors (std::vector<tcu::Vec3>& dst)
+{
+	const int	numColors	= 256;
+	const float	minVal		= 0.1f;
+	const float maxVal		= 0.5f;
+	de::Random	rnd			(0xabc231);
+
+	dst.resize(numColors);
+
+	for (std::vector<tcu::Vec3>::iterator i = dst.begin(); i != dst.end(); ++i)
+	{
+		i->x()	= rnd.getFloat(minVal, maxVal);
+		i->y()	= rnd.getFloat(minVal, maxVal);
+		i->z()	= rnd.getFloat(minVal, maxVal);
+	}
+}
+
+template<typename T>
+static void execVertexFetch (T* dst, const T* src, const deUint8* indices, int numIndices)
+{
+	for (int i = 0; i < numIndices; ++i)
+		dst[i] = src[indices[i]];
+}
+
+bool IndexArrayVerifier::verify (deUint32 buffer, const deUint8* refPtr, int offset, int numBytes)
+{
+	const tcu::RenderTarget&	renderTarget		= m_context.getRenderContext().getRenderTarget();
+	const int					viewportW			= de::min<int>(INDEX_ARRAY_DRAW_VIEWPORT_WIDTH, renderTarget.getWidth());
+	const int					viewportH			= de::min<int>(INDEX_ARRAY_DRAW_VIEWPORT_HEIGHT, renderTarget.getHeight());
+	const int					minBytesPerBatch	= 2;
+	const tcu::RGBA				threshold			(0,0,0,0);
+
+	std::vector<tcu::Vec2>		positions;
+	std::vector<tcu::Vec3>		colors;
+
+	std::vector<tcu::Vec2>		fetchedPos			(MAX_LINES_PER_INDEX_ARRAY_DRAW+1);
+	std::vector<tcu::Vec3>		fetchedColor		(MAX_LINES_PER_INDEX_ARRAY_DRAW+1);
+
+	tcu::Surface				indexBufferImg		(viewportW, viewportH);
+	tcu::Surface				referenceImg		(viewportW, viewportH);
+
+	int							numVerified			= 0;
+	bool						isOk				= true;
+
+	DE_STATIC_ASSERT(sizeof(tcu::Vec2) == sizeof(float)*2);
+	DE_STATIC_ASSERT(sizeof(tcu::Vec3) == sizeof(float)*3);
+
+	computeIndexVerifierPositions(positions);
+	computeIndexVerifierColors(colors);
+
+	// Reset buffer bindings.
+	glBindBuffer				(GL_ARRAY_BUFFER,			0);
+	glBindBuffer				(GL_ELEMENT_ARRAY_BUFFER,	buffer);
+
+	// Setup rendering state.
+	glViewport					(0, 0, viewportW, viewportH);
+	glClearColor				(0.0f, 0.0f, 0.0f, 1.0f);
+	glUseProgram				(m_program->getProgram());
+	glEnableVertexAttribArray	(m_posLoc);
+	glEnableVertexAttribArray	(m_colorLoc);
+	glEnable					(GL_BLEND);
+	glBlendFunc					(GL_ONE, GL_ONE);
+	glBlendEquation				(GL_FUNC_ADD);
+
+	while (numVerified < numBytes)
+	{
+		int		numRemaining		= numBytes-numVerified;
+		bool	isLeftoverBatch		= numRemaining < minBytesPerBatch;
+		int		numBytesToVerify	= isLeftoverBatch ? minBytesPerBatch			: de::min(MAX_LINES_PER_INDEX_ARRAY_DRAW+1, numRemaining);
+		int		curOffset			= isLeftoverBatch ? (numBytes-minBytesPerBatch)	: numVerified;
+		string	imageSetDesc		= string("Bytes ") + de::toString(offset+curOffset) + " to " + de::toString(offset+curOffset+numBytesToVerify-1);
+
+		// Step 1: Render using index buffer.
+		glClear					(GL_COLOR_BUFFER_BIT);
+		glVertexAttribPointer	(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, &positions[0]);
+		glVertexAttribPointer	(m_colorLoc, 3, GL_FLOAT, GL_FALSE, 0, &colors[0]);
+		glDrawElements			(GL_LINE_STRIP, numBytesToVerify, GL_UNSIGNED_BYTE, (void*)(deUintptr)(offset+curOffset));
+		glu::readPixels			(m_context.getRenderContext(), 0, 0, indexBufferImg.getAccess());
+
+		// Step 2: Do manual fetch and render without index buffer.
+		execVertexFetch(&fetchedPos[0], &positions[0], refPtr+offset+curOffset, numBytesToVerify);
+		execVertexFetch(&fetchedColor[0], &colors[0], refPtr+offset+curOffset, numBytesToVerify);
+
+		glClear					(GL_COLOR_BUFFER_BIT);
+		glVertexAttribPointer	(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, &fetchedPos[0]);
+		glVertexAttribPointer	(m_colorLoc, 3, GL_FLOAT, GL_FALSE, 0, &fetchedColor[0]);
+		glDrawArrays			(GL_LINE_STRIP, 0, numBytesToVerify);
+		glu::readPixels			(m_context.getRenderContext(), 0, 0, referenceImg.getAccess());
+
+		if (!tcu::pixelThresholdCompare(m_context.getTestContext().getLog(), "RenderResult", imageSetDesc.c_str(), referenceImg, indexBufferImg, threshold, tcu::COMPARE_LOG_RESULT))
+		{
+			isOk = false;
+			break;
+		}
+
+		numVerified += isLeftoverBatch ? numRemaining : numBytesToVerify;
+	}
+
+	return isOk;
+}
+
+} // BufferTestUtil
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fBufferTestUtil.hpp b/modules/gles2/functional/es2fBufferTestUtil.hpp
new file mode 100644
index 0000000..ba59562
--- /dev/null
+++ b/modules/gles2/functional/es2fBufferTestUtil.hpp
@@ -0,0 +1,178 @@
+#ifndef _ES2FBUFFERTESTUTIL_HPP
+#define _ES2FBUFFERTESTUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestLog.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "tes2TestCase.hpp"
+
+#include <vector>
+#include <set>
+
+namespace glu
+{
+class ShaderProgram;
+}
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace BufferTestUtil
+{
+
+// Helper functions.
+
+void			fillWithRandomBytes		(deUint8* ptr, int numBytes, deUint32 seed);
+bool			compareByteArrays		(tcu::TestLog& log, const deUint8* resPtr, const deUint8* refPtr, int numBytes);
+const char*		getBufferTargetName		(deUint32 target);
+const char*		getUsageHintName		(deUint32 hint);
+
+// Base class for buffer cases.
+
+class BufferCase : public TestCase, public glu::CallLogWrapper
+{
+public:
+							BufferCase							(Context& context, const char* name, const char* description);
+	virtual					~BufferCase							(void);
+
+	void					init								(void);
+	void					deinit								(void);
+
+	deUint32				genBuffer							(void);
+	void					deleteBuffer						(deUint32 buffer);
+	void					checkError							(void);
+
+private:
+	// Resource handles for cleanup in case of unexpected iterate() termination.
+	std::set<deUint32>		m_allocatedBuffers;
+};
+
+// Reference buffer.
+
+class ReferenceBuffer
+{
+public:
+							ReferenceBuffer			(void) {}
+							~ReferenceBuffer		(void) {}
+
+	void					setSize					(int numBytes);
+	void					setData					(int numBytes, const deUint8* bytes);
+	void					setSubData				(int offset, int numBytes, const deUint8* bytes);
+
+	deUint8*				getPtr					(int offset = 0)		{ return &m_data[offset]; }
+	const deUint8*			getPtr					(int offset = 0) const	{ return &m_data[offset]; }
+
+private:
+	std::vector<deUint8>	m_data;
+};
+
+// Buffer verifier system.
+
+enum VerifyType
+{
+	VERIFY_AS_VERTEX_ARRAY	= 0,
+	VERIFY_AS_INDEX_ARRAY,
+
+	VERIFY_LAST
+};
+
+class BufferVerifierBase : public glu::CallLogWrapper
+{
+public:
+							BufferVerifierBase		(Context& context);
+	virtual					~BufferVerifierBase		(void) {}
+
+	virtual int				getMinSize				(void) const = DE_NULL;
+	virtual int				getAlignment			(void) const = DE_NULL;
+	virtual bool			verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes) = DE_NULL;
+
+protected:
+	Context&				m_context;
+
+private:
+							BufferVerifierBase		(const BufferVerifierBase& other);
+	BufferVerifierBase&		operator=				(const BufferVerifierBase& other);
+};
+
+class BufferVerifier
+{
+public:
+							BufferVerifier			(Context& context, VerifyType verifyType);
+							~BufferVerifier			(void);
+
+	int						getMinSize				(void) const { return m_verifier->getMinSize();		}
+	int						getAlignment			(void) const { return m_verifier->getAlignment();	}
+
+	// \note Offset is applied to reference pointer as well.
+	bool					verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes);
+
+private:
+							BufferVerifier			(const BufferVerifier& other);
+	BufferVerifier&			operator=				(const BufferVerifier& other);
+
+	BufferVerifierBase*		m_verifier;
+};
+
+class VertexArrayVerifier : public BufferVerifierBase
+{
+public:
+						VertexArrayVerifier		(Context& context);
+						~VertexArrayVerifier	(void);
+
+	int					getMinSize				(void) const { return 3*4; }
+	int					getAlignment			(void) const { return 1; }
+	bool				verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes);
+
+private:
+	glu::ShaderProgram*	m_program;
+	deUint32			m_posLoc;
+	deUint32			m_byteVecLoc;
+};
+
+class IndexArrayVerifier : public BufferVerifierBase
+{
+public:
+						IndexArrayVerifier		(Context& context);
+						~IndexArrayVerifier		(void);
+
+	int					getMinSize				(void) const { return 2; }
+	int					getAlignment			(void) const { return 1; }
+	bool				verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes);
+
+private:
+	glu::ShaderProgram*	m_program;
+	deUint32			m_posLoc;
+	deUint32			m_colorLoc;
+};
+
+} // BufferTestUtil
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FBUFFERTESTUTIL_HPP
diff --git a/modules/gles2/functional/es2fBufferWriteTests.cpp b/modules/gles2/functional/es2fBufferWriteTests.cpp
new file mode 100644
index 0000000..b4ba4c7
--- /dev/null
+++ b/modules/gles2/functional/es2fBufferWriteTests.cpp
@@ -0,0 +1,774 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer data upload tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fBufferWriteTests.hpp"
+#include "es2fBufferTestUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "gluStrUtil.hpp"
+#include "deMemory.h"
+#include "deString.h"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <algorithm>
+#include <list>
+
+using std::set;
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using tcu::IVec2;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using namespace BufferTestUtil;
+
+struct DataStoreSpec
+{
+	DataStoreSpec (void)
+		: target	(0)
+		, usage		(0)
+		, size		(0)
+	{
+	}
+
+	DataStoreSpec (deUint32 target_, deUint32 usage_, int size_)
+		: target	(target_)
+		, usage		(usage_)
+		, size		(size_)
+	{
+	}
+
+	deUint32	target;
+	deUint32	usage;
+	int			size;
+};
+
+struct DataStoreSpecVecBuilder
+{
+	std::vector<DataStoreSpec>& list;
+
+	DataStoreSpecVecBuilder (std::vector<DataStoreSpec>& list_)
+		: list(list_)
+	{
+	}
+
+	DataStoreSpecVecBuilder& operator<< (const DataStoreSpec& spec)
+	{
+		list.push_back(spec);
+		return *this;
+	}
+};
+
+struct RangeVecBuilder
+{
+	std::vector<tcu::IVec2>& list;
+
+	RangeVecBuilder (std::vector<tcu::IVec2>& list_)
+		: list(list_)
+	{
+	}
+
+	RangeVecBuilder& operator<< (const tcu::IVec2& vec)
+	{
+		list.push_back(vec);
+		return *this;
+	}
+};
+
+template<typename Iterator>
+static bool isRangeListValid (Iterator begin, Iterator end)
+{
+	if (begin != end)
+	{
+		// Fetch first.
+		tcu::IVec2 prev = *begin;
+		++begin;
+
+		for (; begin != end; ++begin)
+		{
+			tcu::IVec2 cur = *begin;
+			if (cur.x() <= prev.x() || cur.x() <= prev.x()+prev.y())
+				return false;
+			prev = cur;
+		}
+	}
+
+	return true;
+}
+
+inline bool rangesIntersect (const tcu::IVec2& a, const tcu::IVec2& b)
+{
+	return de::inRange(a.x(), b.x(), b.x()+b.y()) || de::inRange(a.x()+a.y(), b.x(), b.x()+b.y()) ||
+		   de::inRange(b.x(), a.x(), a.x()+a.y()) || de::inRange(b.x()+b.y(), a.x(), a.x()+a.y());
+}
+
+inline tcu::IVec2 unionRanges (const tcu::IVec2& a, const tcu::IVec2& b)
+{
+	DE_ASSERT(rangesIntersect(a, b));
+
+	int start	= de::min(a.x(), b.x());
+	int end		= de::max(a.x()+a.y(), b.x()+b.y());
+
+	return tcu::IVec2(start, end-start);
+}
+
+//! Updates range list (start, len) with a new range.
+std::vector<tcu::IVec2> addRangeToList (const std::vector<tcu::IVec2>& oldList, const tcu::IVec2& newRange)
+{
+	DE_ASSERT(newRange.y() > 0);
+
+	std::vector<tcu::IVec2>					newList;
+	std::vector<tcu::IVec2>::const_iterator	oldListIter	= oldList.begin();
+
+	// Append ranges that end before the new range.
+	for (; oldListIter != oldList.end() && oldListIter->x()+oldListIter->y() < newRange.x(); ++oldListIter)
+		newList.push_back(*oldListIter);
+
+	// Join any ranges that intersect new range
+	{
+		tcu::IVec2 curRange = newRange;
+		while (oldListIter != oldList.end() && rangesIntersect(curRange, *oldListIter))
+		{
+			curRange = unionRanges(curRange, *oldListIter);
+			++oldListIter;
+		}
+
+		newList.push_back(curRange);
+	}
+
+	// Append remaining ranges.
+	for (; oldListIter != oldList.end(); oldListIter++)
+		newList.push_back(*oldListIter);
+
+	DE_ASSERT(isRangeListValid(newList.begin(), newList.end()));
+
+	return newList;
+}
+
+class BasicBufferDataCase : public BufferCase
+{
+public:
+	BasicBufferDataCase (Context& context, const char* name, const char* desc, deUint32 target, deUint32 usage, int size, VerifyType verify)
+		: BufferCase	(context, name, desc)
+		, m_target		(target)
+		, m_usage		(usage)
+		, m_size		(size)
+		, m_verify		(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32			dataSeed	= deStringHash(getName()) ^ 0x125;
+		BufferVerifier			verifier	(m_context, m_verify);
+		ReferenceBuffer			refBuf;
+		bool					isOk		= false;
+
+		refBuf.setSize(m_size);
+		fillWithRandomBytes(refBuf.getPtr(), m_size, dataSeed);
+
+		deUint32 buf = genBuffer();
+		glBindBuffer(m_target, buf);
+		glBufferData(m_target, m_size, refBuf.getPtr(), m_usage);
+
+		checkError();
+
+		isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_size);
+
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_target;
+	deUint32		m_usage;
+	int				m_size;
+	VerifyType		m_verify;
+};
+
+class RecreateBufferDataStoreCase : public BufferCase
+{
+public:
+	RecreateBufferDataStoreCase (Context& context, const char* name, const char* desc, const DataStoreSpec* specs, int numSpecs, VerifyType verify)
+		: BufferCase(context, name, desc)
+		, m_specs	(specs, specs+numSpecs)
+		, m_verify	(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32			baseSeed	= deStringHash(getName()) ^ 0xbeef;
+		BufferVerifier			verifier	(m_context, m_verify);
+		ReferenceBuffer			refBuf;
+		const deUint32			buf			= genBuffer();
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		for (vector<DataStoreSpec>::const_iterator spec = m_specs.begin(); spec != m_specs.end(); spec++)
+		{
+			bool iterOk = false;
+
+			refBuf.setSize(spec->size);
+			fillWithRandomBytes(refBuf.getPtr(), spec->size, baseSeed ^ deInt32Hash(spec->size+spec->target+spec->usage));
+
+			glBindBuffer(spec->target, buf);
+			glBufferData(spec->target, spec->size, refBuf.getPtr(), spec->usage);
+
+			checkError();
+
+			iterOk = verifier.verify(buf, refBuf.getPtr(), 0, spec->size);
+
+			if (!iterOk)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+				break;
+			}
+		}
+
+		deleteBuffer(buf);
+		return STOP;
+	}
+
+private:
+	std::vector<DataStoreSpec>	m_specs;
+	VerifyType					m_verify;
+};
+
+class BasicBufferSubDataCase : public BufferCase
+{
+public:
+	BasicBufferSubDataCase (Context& context, const char* name, const char* desc, deUint32 target, deUint32 usage, int size, int subDataOffs, int subDataSize, VerifyType verify)
+		: BufferCase	(context, name, desc)
+		, m_target		(target)
+		, m_usage		(usage)
+		, m_size		(size)
+		, m_subDataOffs	(subDataOffs)
+		, m_subDataSize	(subDataSize)
+		, m_verify		(verify)
+	{
+		DE_ASSERT(de::inBounds(subDataOffs, 0, size) && de::inRange(subDataOffs+subDataSize, 0, size));
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32			dataSeed	= deStringHash(getName());
+		BufferVerifier			verifier	(m_context, m_verify);
+		ReferenceBuffer			refBuf;
+		bool					isOk		= false;
+
+		refBuf.setSize(m_size);
+
+		deUint32 buf = genBuffer();
+		glBindBuffer(m_target, buf);
+
+		// Initialize with glBufferData()
+		fillWithRandomBytes(refBuf.getPtr(), m_size, dataSeed ^ 0x80354f);
+		glBufferData(m_target, m_size, refBuf.getPtr(), m_usage);
+		checkError();
+
+		// Re-specify part of buffer
+		fillWithRandomBytes(refBuf.getPtr()+m_subDataOffs, m_subDataSize, dataSeed ^ 0xfac425c);
+		glBufferSubData(m_target, m_subDataOffs, m_subDataSize, refBuf.getPtr()+m_subDataOffs);
+
+		isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_size);
+
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_target;
+	deUint32		m_usage;
+	int				m_size;
+	int				m_subDataOffs;
+	int				m_subDataSize;
+	VerifyType		m_verify;
+};
+
+class SubDataToUndefinedCase : public BufferCase
+{
+public:
+	SubDataToUndefinedCase (Context& context, const char* name, const char* desc, deUint32 target, deUint32 usage, int size, const tcu::IVec2* ranges, int numRanges, VerifyType verify)
+		: BufferCase	(context, name, desc)
+		, m_target		(target)
+		, m_usage		(usage)
+		, m_size		(size)
+		, m_ranges		(ranges, ranges+numRanges)
+		, m_verify		(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32			dataSeed	= deStringHash(getName());
+		BufferVerifier			verifier	(m_context, m_verify);
+		ReferenceBuffer			refBuf;
+		bool					isOk		= true;
+		std::vector<tcu::IVec2>	definedRanges;
+
+		refBuf.setSize(m_size);
+
+		deUint32 buf = genBuffer();
+		glBindBuffer(m_target, buf);
+
+		// Initialize storage with glBufferData()
+		glBufferData(m_target, m_size, DE_NULL, m_usage);
+		checkError();
+
+		// Fill specified ranges with glBufferSubData()
+		for (vector<tcu::IVec2>::const_iterator range = m_ranges.begin(); range != m_ranges.end(); range++)
+		{
+			fillWithRandomBytes(refBuf.getPtr()+range->x(), range->y(), dataSeed ^ deInt32Hash(range->x()+range->y()));
+			glBufferSubData(m_target, range->x(), range->y(), refBuf.getPtr()+range->x());
+
+			// Mark range as defined
+			definedRanges = addRangeToList(definedRanges, *range);
+		}
+
+		// Verify defined parts
+		for (vector<tcu::IVec2>::const_iterator range = definedRanges.begin(); range != definedRanges.end(); range++)
+		{
+			if (!verifier.verify(buf, refBuf.getPtr(), range->x(), range->y()))
+				isOk = false;
+		}
+
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32				m_target;
+	deUint32				m_usage;
+	int						m_size;
+	std::vector<tcu::IVec2>	m_ranges;
+	VerifyType				m_verify;
+};
+
+class RandomBufferWriteCase : public BufferCase
+{
+public:
+	RandomBufferWriteCase (Context& context, const char* name, const char* desc, deUint32 seed)
+		: BufferCase(context, name, desc)
+		, m_seed		(seed)
+		, m_verifier	(DE_NULL)
+		, m_buffer		(0)
+		, m_curSize		(0)
+		, m_iterNdx		(0)
+	{
+	}
+
+	~RandomBufferWriteCase (void)
+	{
+		delete m_verifier;
+	}
+
+	void init (void)
+	{
+		BufferCase::init();
+
+		m_iterNdx	= 0;
+		m_buffer	= genBuffer();
+		m_curSize	= 0;
+		m_verifier	= new BufferVerifier(m_context, VERIFY_AS_VERTEX_ARRAY);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+
+	void deinit (void)
+	{
+		deleteBuffer(m_buffer);
+		m_refBuffer.setSize(0);
+
+		delete m_verifier;
+		m_verifier = DE_NULL;
+
+		BufferCase::deinit();
+	}
+
+	IterateResult iterate (void)
+	{
+		// Parameters.
+		const int	numIterations				= 5;
+		const int	uploadsPerIteration			= 7;
+		const int	minSize						= 12;
+		const int	maxSize						= 32*1024;
+		const float	respecifyProbability		= 0.07f;
+		const float	respecifyDataProbability	= 0.2f;
+
+		static const deUint32 bufferTargets[] =
+		{
+			GL_ARRAY_BUFFER,
+			GL_ELEMENT_ARRAY_BUFFER
+		};
+
+		static const deUint32 usageHints[] =
+		{
+			GL_STREAM_DRAW,
+			GL_STATIC_DRAW,
+			GL_DYNAMIC_DRAW,
+		};
+
+		bool		iterOk					= true;
+		deUint32	curBoundTarget			= GL_NONE;
+		de::Random	rnd						(m_seed ^ deInt32Hash(m_iterNdx) ^ 0xacf92e);
+
+		m_testCtx.getLog() << TestLog::Section(string("Iteration") + de::toString(m_iterNdx+1), string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(numIterations));
+
+		for (int uploadNdx = 0; uploadNdx < uploadsPerIteration; uploadNdx++)
+		{
+			const deUint32	target		= bufferTargets[rnd.getInt(0, DE_LENGTH_OF_ARRAY(bufferTargets)-1)];
+			const bool		respecify	= m_curSize == 0 || rnd.getFloat() < respecifyProbability;
+
+			if (target != curBoundTarget)
+			{
+				glBindBuffer(target, m_buffer);
+				curBoundTarget = target;
+			}
+
+			if (respecify)
+			{
+				const int		size			= rnd.getInt(minSize, maxSize);
+				const deUint32	hint			= usageHints[rnd.getInt(0, DE_LENGTH_OF_ARRAY(usageHints)-1)];
+				const bool		fillWithData	= rnd.getFloat() < respecifyDataProbability;
+
+				m_refBuffer.setSize(size);
+				if (fillWithData)
+					fillWithRandomBytes(m_refBuffer.getPtr(), size, rnd.getUint32());
+
+				glBufferData(target, size, fillWithData ? m_refBuffer.getPtr() : DE_NULL, hint);
+
+				m_validRanges.clear();
+				if (fillWithData)
+					m_validRanges.push_back(tcu::IVec2(0, size));
+
+				m_curSize = size;
+			}
+			else
+			{
+				// \note Non-uniform size distribution.
+				const int	size	= de::clamp(deRoundFloatToInt32((float)m_curSize * deFloatPow(rnd.getFloat(0.0f, 0.7f), 3.0f)), minSize, m_curSize);
+				const int	offset	= rnd.getInt(0, m_curSize-size);
+
+				fillWithRandomBytes(m_refBuffer.getPtr()+offset, size, rnd.getUint32());
+				glBufferSubData(target, offset, size, m_refBuffer.getPtr()+offset);
+
+				m_validRanges = addRangeToList(m_validRanges, tcu::IVec2(offset, size));
+			}
+		}
+
+		// Check error.
+		{
+			deUint32 err = glGetError();
+			if (err != GL_NO_ERROR)
+				throw tcu::TestError(string("Got ") + glu::getErrorStr(err).toString());
+		}
+
+		// Verify valid ranges.
+		for (vector<IVec2>::const_iterator range = m_validRanges.begin(); range != m_validRanges.end(); range++)
+		{
+			if (!m_verifier->verify(m_buffer, m_refBuffer.getPtr(), range->x(), range->y()))
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer verification failed");
+				iterOk = false;
+				break;
+			}
+		}
+
+		m_testCtx.getLog() << TestLog::EndSection;
+
+		DE_ASSERT(iterOk || m_testCtx.getTestResult() != QP_TEST_RESULT_PASS);
+
+		m_iterNdx += 1;
+		return (iterOk && m_iterNdx < numIterations) ? CONTINUE : STOP;
+	}
+
+private:
+	deUint32				m_seed;
+
+	BufferVerifier*			m_verifier;
+	deUint32				m_buffer;
+	ReferenceBuffer			m_refBuffer;
+	std::vector<tcu::IVec2>	m_validRanges;
+	int						m_curSize;
+	int						m_iterNdx;
+};
+
+BufferWriteTests::BufferWriteTests (Context& context)
+	: TestCaseGroup(context, "write", "Buffer data upload tests")
+{
+}
+
+BufferWriteTests::~BufferWriteTests (void)
+{
+}
+
+void BufferWriteTests::init (void)
+{
+	static const deUint32 bufferTargets[] =
+	{
+		GL_ARRAY_BUFFER,
+		GL_ELEMENT_ARRAY_BUFFER
+	};
+
+	static const deUint32 usageHints[] =
+	{
+		GL_STREAM_DRAW,
+		GL_STATIC_DRAW,
+		GL_DYNAMIC_DRAW
+	};
+
+	static const struct
+	{
+		const char*	name;
+		VerifyType	verify;
+	} verifyTypes[] =
+	{
+		{ "vertex_array",	VERIFY_AS_VERTEX_ARRAY	},
+		{ "index_array",	VERIFY_AS_INDEX_ARRAY	}
+	};
+
+	// .basic
+	{
+		tcu::TestCaseGroup* const basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic upload with glBufferData()");
+		addChild(basicGroup);
+
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+		{
+			for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usageHints); usageNdx++)
+			{
+				const deUint32		target	= bufferTargets[targetNdx];
+				const deUint32		usage	= usageHints[usageNdx];
+				const int			size	= 1020;
+				const VerifyType	verify	= VERIFY_AS_VERTEX_ARRAY;
+				const string		name	= string(getBufferTargetName(target)) + "_" + getUsageHintName(usage);
+
+				basicGroup->addChild(new BasicBufferDataCase(m_context, name.c_str(), "", target, usage, size, verify));
+			}
+		}
+	}
+
+	// .use
+	{
+		tcu::TestCaseGroup* const useGroup = new tcu::TestCaseGroup(m_testCtx, "use", "Buffer uses");
+		addChild(useGroup);
+
+		for (int verifyNdx = 0; verifyNdx < DE_LENGTH_OF_ARRAY(verifyTypes); verifyNdx++)
+		{
+			tcu::TestCaseGroup* const verifyGroup = new tcu::TestCaseGroup(m_testCtx, verifyTypes[verifyNdx].name, "");
+			useGroup->addChild(verifyGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				const deUint32		target	= bufferTargets[targetNdx];
+				const deUint32		usage	= GL_STATIC_DRAW;
+				const int			size	= 763;
+				const VerifyType	verify	= verifyTypes[verifyNdx].verify;
+				const string		name	= getBufferTargetName(target);
+
+				verifyGroup->addChild(new BasicBufferDataCase(m_context, name.c_str(), "", target, usage, size, verify));
+			}
+		}
+	}
+
+	// .recreate_store
+	{
+		tcu::TestCaseGroup* const recreateStoreGroup = new tcu::TestCaseGroup(m_testCtx, "recreate_store", "Data store recreate using glBufferData()");
+		addChild(recreateStoreGroup);
+
+#define RECREATE_STORE_CASE(NAME, DESC, SPECLIST)																											\
+		do {																																				\
+			std::vector<DataStoreSpec> specs;																												\
+			DataStoreSpecVecBuilder builder(specs);																											\
+			builder SPECLIST;																																\
+			recreateStoreGroup->addChild(new RecreateBufferDataStoreCase(m_context, #NAME, DESC, &specs[0], (int)specs.size(), VERIFY_AS_VERTEX_ARRAY));	\
+		} while (deGetFalse())
+
+		RECREATE_STORE_CASE(identical_1, "Recreate with identical parameters",
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996));
+
+		RECREATE_STORE_CASE(identical_2, "Recreate with identical parameters",
+			<< DataStoreSpec(GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, 72)
+			<< DataStoreSpec(GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, 72)
+			<< DataStoreSpec(GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, 72));
+
+		RECREATE_STORE_CASE(different_target_1, "Recreate with different target",
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_DRAW, 504)
+			<< DataStoreSpec(GL_ELEMENT_ARRAY_BUFFER,		GL_STATIC_DRAW, 504));
+
+		RECREATE_STORE_CASE(different_target_2, "Recreate with different target",
+			<< DataStoreSpec(GL_ELEMENT_ARRAY_BUFFER,		GL_STATIC_DRAW, 716)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_DRAW, 716)
+			<< DataStoreSpec(GL_ELEMENT_ARRAY_BUFFER,		GL_STATIC_DRAW, 716));
+
+		RECREATE_STORE_CASE(different_usage, "Recreate with different usage",
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STREAM_DRAW,	1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW,	1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STATIC_DRAW,	1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STREAM_DRAW,	1644));
+
+		RECREATE_STORE_CASE(different_size, "Recreate with different size",
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STREAM_DRAW,	1024)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STREAM_DRAW,	12)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STREAM_DRAW,	3327)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STREAM_DRAW,	92)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STREAM_DRAW,	12379)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STREAM_DRAW,	571));
+
+#undef RECREATE_STORE_CASE
+
+		// Random cases.
+		{
+			const int			numRandomCases		= 4;
+			const int			numUploadsPerCase	= 10;
+			const int			minSize				= 12;
+			const int			maxSize				= 65536;
+			const VerifyType	verify				= VERIFY_AS_VERTEX_ARRAY;
+			de::Random			rnd					(23921);
+
+			for (int caseNdx = 0; caseNdx < numRandomCases; caseNdx++)
+			{
+				vector<DataStoreSpec> specs(numUploadsPerCase);
+
+				for (vector<DataStoreSpec>::iterator spec = specs.begin(); spec != specs.end(); spec++)
+				{
+					spec->target	= bufferTargets[rnd.getInt(0, DE_LENGTH_OF_ARRAY(bufferTargets)-1)];
+					spec->usage		= usageHints[rnd.getInt(0, DE_LENGTH_OF_ARRAY(usageHints)-1)];
+					spec->size		= rnd.getInt(minSize, maxSize);
+				}
+
+				recreateStoreGroup->addChild(new RecreateBufferDataStoreCase(m_context, (string("random_") + de::toString(caseNdx+1)).c_str(), "", &specs[0], (int)specs.size(), verify));
+			}
+		}
+	}
+
+	// .basic_subdata
+	{
+		tcu::TestCaseGroup* const basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic_subdata", "Basic glBufferSubData() usage");
+		addChild(basicGroup);
+
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+		{
+			for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usageHints); usageNdx++)
+			{
+				const deUint32		target	= bufferTargets[targetNdx];
+				const deUint32		usage	= usageHints[usageNdx];
+				const int			size	= 1020;
+				const VerifyType	verify	= VERIFY_AS_VERTEX_ARRAY;
+				const string		name	= string(getBufferTargetName(target)) + "_" + getUsageHintName(usage);
+
+				basicGroup->addChild(new BasicBufferDataCase(m_context, name.c_str(), "", target, usage, size, verify));
+			}
+		}
+	}
+
+	// .partial_specify
+	{
+		tcu::TestCaseGroup* const partialSpecifyGroup = new tcu::TestCaseGroup(m_testCtx, "partial_specify", "Partial buffer data specification with glBufferSubData()");
+		addChild(partialSpecifyGroup);
+
+#define PARTIAL_SPECIFY_CASE(NAME, DESC, TARGET, USAGE, SIZE, RANGELIST)																									\
+		do {																																								\
+			std::vector<tcu::IVec2> ranges;																																	\
+			RangeVecBuilder builder(ranges);																																\
+			builder RANGELIST;																																				\
+			partialSpecifyGroup->addChild(new SubDataToUndefinedCase(m_context, #NAME, DESC, TARGET, USAGE, SIZE, &ranges[0], (int)ranges.size(), VERIFY_AS_VERTEX_ARRAY));	\
+		} while (deGetFalse())
+
+		PARTIAL_SPECIFY_CASE(whole_1, "Whole buffer specification with single glBufferSubData()", GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996,
+			<< IVec2(0, 996));
+		PARTIAL_SPECIFY_CASE(whole_2, "Whole buffer specification with two calls", GL_ELEMENT_ARRAY_BUFFER, GL_DYNAMIC_DRAW, 1728,
+			<< IVec2(729, 999)
+			<< IVec2(0, 729));
+		PARTIAL_SPECIFY_CASE(whole_3, "Whole buffer specification with three calls", GL_ARRAY_BUFFER, GL_STREAM_DRAW, 1944,
+			<< IVec2(0, 421)
+			<< IVec2(1421, 523)
+			<< IVec2(421, 1000));
+		PARTIAL_SPECIFY_CASE(whole_4, "Whole buffer specification with three calls", GL_ELEMENT_ARRAY_BUFFER, GL_STREAM_DRAW, 1200,
+			<< IVec2(0, 500)
+			<< IVec2(429, 200)
+			<< IVec2(513, 687));
+
+		PARTIAL_SPECIFY_CASE(low_1, "Low part of buffer specified with single call", GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, 1000,
+			<< IVec2(0, 513));
+		PARTIAL_SPECIFY_CASE(low_2, "Low part of buffer specified with two calls", GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996,
+			<< IVec2(0, 98)
+			<< IVec2(98, 511));
+		PARTIAL_SPECIFY_CASE(low_3, "Low part of buffer specified with two calls", GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, 1200,
+			<< IVec2(0, 591)
+			<< IVec2(371, 400));
+
+		PARTIAL_SPECIFY_CASE(high_1, "High part of buffer specified with single call", GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, 1000,
+			<< IVec2(500, 500));
+		PARTIAL_SPECIFY_CASE(high_2, "High part of buffer specified with two calls", GL_ARRAY_BUFFER, GL_STREAM_DRAW, 1200,
+			<< IVec2(600, 123)
+			<< IVec2(723, 477));
+		PARTIAL_SPECIFY_CASE(high_3, "High part of buffer specified with two calls", GL_ARRAY_BUFFER, GL_STREAM_DRAW, 1200,
+			<< IVec2(600, 200)
+			<< IVec2(601, 599));
+
+		PARTIAL_SPECIFY_CASE(middle_1, "Middle part of buffer specified with single call", GL_ELEMENT_ARRAY_BUFFER, GL_STREAM_DRAW, 2500,
+			<< IVec2(1000, 799));
+		PARTIAL_SPECIFY_CASE(middle_2, "Middle part of buffer specified with two calls", GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, 2500,
+			<< IVec2(780, 220)
+			<< IVec2(1000, 500));
+		PARTIAL_SPECIFY_CASE(middle_3, "Middle part of buffer specified with two calls", GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, 2500,
+			<< IVec2(780, 321)
+			<< IVec2(1000, 501));
+
+#undef PARTIAL_SPECIFY_CASE
+	}
+
+	// .random
+	{
+		tcu::TestCaseGroup* const randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Randomized buffer data cases");
+		addChild(randomGroup);
+
+		for (int i = 0; i < 10; i++)
+			randomGroup->addChild(new RandomBufferWriteCase(m_context, de::toString(i).c_str(), "", deInt32Hash(i)));
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fBufferWriteTests.hpp b/modules/gles2/functional/es2fBufferWriteTests.hpp
new file mode 100644
index 0000000..d774799
--- /dev/null
+++ b/modules/gles2/functional/es2fBufferWriteTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FBUFFERWRITETESTS_HPP
+#define _ES2FBUFFERWRITETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer data upload tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class BufferWriteTests : public TestCaseGroup
+{
+public:
+						BufferWriteTests	(Context& context);
+						~BufferWriteTests	(void);
+
+	void				init				(void);
+
+private:
+						BufferWriteTests	(const BufferWriteTests& other);
+	BufferWriteTests&	operator=			(const BufferWriteTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FBUFFERWRITETESTS_HPP
diff --git a/modules/gles2/functional/es2fClippingTests.cpp b/modules/gles2/functional/es2fClippingTests.cpp
new file mode 100644
index 0000000..74afc01
--- /dev/null
+++ b/modules/gles2/functional/es2fClippingTests.cpp
@@ -0,0 +1,2062 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Clipping tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fClippingTests.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+
+#include "sglrReferenceContext.hpp"
+#include "sglrGLContext.hpp"
+
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+#include "glwFunctions.hpp"
+
+using namespace glw; // GLint and other GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace
+{
+
+using tcu::PixelBufferAccess;
+using tcu::ConstPixelBufferAccess;
+using tcu::TestLog;
+
+static const tcu::Vec4	MASK_COLOR_OK			 = tcu::Vec4(0.0f, 0.1f, 0.0f, 1.0f);
+static const tcu::Vec4	MASK_COLOR_DEV			 = tcu::Vec4(0.8f, 0.5f, 0.0f, 1.0f);
+static const tcu::Vec4	MASK_COLOR_FAIL			 = tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f);
+
+const int					TEST_CANVAS_SIZE  = 200;
+const rr::WindowRectangle	VIEWPORT_WHOLE		(0,						0,					TEST_CANVAS_SIZE,		TEST_CANVAS_SIZE);
+const rr::WindowRectangle	VIEWPORT_CENTER		(TEST_CANVAS_SIZE/4,	TEST_CANVAS_SIZE/4,	TEST_CANVAS_SIZE/2,		TEST_CANVAS_SIZE/2);
+const rr::WindowRectangle	VIEWPORT_CORNER		(TEST_CANVAS_SIZE/2,	TEST_CANVAS_SIZE/2,	TEST_CANVAS_SIZE/2,		TEST_CANVAS_SIZE/2);
+
+
+const char* shaderSourceVertex =	"attribute highp vec4 a_position;\n"
+									"attribute highp vec4 a_color;\n"
+									"attribute highp float a_pointSize;\n"
+									"varying mediump vec4 varFragColor;\n"
+									"void main (void)\n"
+									"{\n"
+									"	gl_Position = a_position;\n"
+									"	gl_PointSize = a_pointSize;\n"
+									"	varFragColor = a_color;\n"
+									"}\n";
+const char* shaderSourceFragment =	"varying mediump vec4 varFragColor;\n"
+									"void main (void)\n"
+									"{\n"
+									"	gl_FragColor = varFragColor;\n"
+									"}\n";
+
+inline bool isBlack (const tcu::IVec4& a)
+{
+	return a.x() == 0 && a.y() == 0 && a.z() == 0;
+}
+
+inline bool isHalfFilled (const tcu::IVec4& a)
+{
+	const tcu::IVec4 halfFilled	(127, 0, 0, 0);
+	const tcu::IVec4 threshold	(20, 256, 256, 256);
+
+	return tcu::boolAll(tcu::lessThanEqual(tcu::abs(a - halfFilled), threshold));
+}
+
+inline bool isLessThanHalfFilled (const tcu::IVec4& a)
+{
+	const int halfFilled = 127;
+	const int threshold	 = 20;
+
+	return a.x() + threshold < halfFilled;
+}
+
+inline bool compareBlackNonBlackPixels (const tcu::IVec4& a, const tcu::IVec4& b)
+{
+	return isBlack(a) == isBlack(b);
+}
+
+inline bool compareColoredPixels (const tcu::IVec4& a, const tcu::IVec4& b)
+{
+	const bool aIsBlack = isBlack(a);
+	const bool bIsBlack = isBlack(b);
+	const tcu::IVec4 threshold(20, 20, 20, 0);
+
+	if (aIsBlack && bIsBlack)
+		return true;
+	if (aIsBlack != bIsBlack)
+		return false;
+
+	return tcu::boolAll(tcu::lessThanEqual(tcu::abs(a - b), threshold));
+}
+
+void blitImageOnBlackSurface(const ConstPixelBufferAccess& src, const PixelBufferAccess& dst)
+{
+	const int			height	= src.getHeight();
+	const int			width	= src.getWidth();
+	const tcu::IVec4	black	= tcu::IVec4(0, 0, 0, 255);
+
+	for (int y = 0; y < height; y++)
+	for (int x = 0; x < width; x++)
+	{
+		const tcu::IVec4 cSrc = src.getPixelInt(x, y);
+		const tcu::IVec4 cDst = tcu::IVec4(cSrc.x(), cSrc.y(), cSrc.z(), 255);
+
+		dst.setPixel(cDst, x, y);
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Pixelwise comparison of two images.
+ * \note copied & modified from glsRasterizationTests
+ *
+ * Kernel radius defines maximum allowed distance. If radius is 0, only
+ * perfect match is allowed. Radius of 1 gives a 3x3 kernel. Pixels are
+ * equal if pixelCmp returns true..
+ *
+ * Return values:  -1 = Perfect match
+ *					0 = Deviation within kernel
+ *				   >0 = Number of faulty pixels
+ *//*--------------------------------------------------------------------*/
+inline int compareImages (tcu::TestLog& log, const ConstPixelBufferAccess& test, const ConstPixelBufferAccess& ref, const PixelBufferAccess& diffMask, int kernelRadius, bool (*pixelCmp)(const tcu::IVec4& a, const tcu::IVec4& b))
+{
+	const int			height				= test.getHeight();
+	const int			width				= test.getWidth();
+	int					deviatingPixels		= 0;
+	int					faultyPixels		= 0;
+	int					compareFailed		= -1;
+
+	tcu::clear(diffMask, MASK_COLOR_OK);
+
+	for (int y = 0; y < height; y++)
+	{
+		for (int x = 0; x < width; x++)
+		{
+			const tcu::IVec4 cRef	= ref.getPixelInt(x, y);
+			const tcu::IVec4 cTest	= test.getPixelInt(x, y);
+
+			// Pixelwise match, no deviation or fault
+			if ((*pixelCmp)(cRef, cTest))
+				continue;
+
+			// Deviation
+			{
+				const int radius	= kernelRadius;
+				bool foundRef		= false;
+				bool foundTest		= false;
+
+				// edges are considered a "deviation" too. The suitable pixel could be "behind" the edge
+				if (y < radius || x < radius || y + radius >= height || x + radius >= width)
+				{
+					foundRef	= true;
+					foundTest	= true;
+				}
+				else
+				{
+					// find ref
+					for (int kY = y - radius; kY <= y + radius; kY++)
+					for (int kX = x - radius; kX <= x + radius; kX++)
+					{
+						if ((*pixelCmp)(cRef, test.getPixelInt(kX, kY)))
+						{
+							foundRef = true;
+							break;
+						}
+					}
+
+					// find result
+					for (int kY = y - radius; kY <= y + radius; kY++)
+					for (int kX = x - radius; kX <= x + radius; kX++)
+					{
+						if ((*pixelCmp)(cTest, ref.getPixelInt(kX, kY)))
+						{
+							foundTest = true;
+							break;
+						}
+					}
+				}
+
+				// A pixel is deviating if the reference color is found inside the kernel and (~= every pixel reference draws must be drawn by the gl too)
+				// the result color is found in the reference image inside the kernel         (~= every pixel gl draws must be drawn by the reference too)
+				if (foundRef && foundTest)
+				{
+					diffMask.setPixel(MASK_COLOR_DEV, x, y);
+					if (compareFailed == -1)
+						compareFailed = 0;
+					deviatingPixels++;
+					continue;
+				}
+			}
+
+			diffMask.setPixel(MASK_COLOR_FAIL, x, y);
+			faultyPixels++;									// The pixel is faulty if the color is not found
+			compareFailed = 1;
+		}
+	}
+
+	log << TestLog::Message << deviatingPixels	<< " deviating pixel(s) found." << TestLog::EndMessage;
+	log << TestLog::Message << faultyPixels		<< " faulty pixel(s) found." << TestLog::EndMessage;
+
+	return (compareFailed == 1 ? faultyPixels : compareFailed);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Pixelwise comparison of two images.
+ *
+ * Kernel radius defines maximum allowed distance. If radius is 0, only
+ * perfect match is allowed. Radius of 1 gives a 3x3 kernel. Pixels are
+ * equal if they both are black, or both are non-black.
+ *
+ * Return values:  -1 = Perfect match
+ *					0 = Deviation within kernel
+ *				   >0 = Number of faulty pixels
+ *//*--------------------------------------------------------------------*/
+int compareBlackNonBlackImages (tcu::TestLog& log, const ConstPixelBufferAccess& test, const ConstPixelBufferAccess& ref, const PixelBufferAccess& diffMask, int kernelRadius)
+{
+	return compareImages(log, test, ref, diffMask, kernelRadius, compareBlackNonBlackPixels);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Pixelwise comparison of two images.
+ *
+ * Kernel radius defines maximum allowed distance. If radius is 0, only
+ * perfect match is allowed. Radius of 1 gives a 3x3 kernel. Pixels are
+ * equal if they both are black, or both are non-black with color values
+ * close to each other.
+ *
+ * Return values:  -1 = Perfect match
+ *					0 = Deviation within kernel
+ *				   >0 = Number of faulty pixels
+ *//*--------------------------------------------------------------------*/
+int compareColoredImages (tcu::TestLog& log, const ConstPixelBufferAccess& test, const ConstPixelBufferAccess& ref, const PixelBufferAccess& diffMask, int kernelRadius)
+{
+	return compareImages(log, test, ref, diffMask, kernelRadius, compareColoredPixels);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Overdraw check verification
+ *
+ * Check that image does not have at any point a
+ * pixel with red component value > 0.5
+ *
+ * Return values:  false = area not filled, or leaking
+ *//*--------------------------------------------------------------------*/
+bool checkHalfFilledImageOverdraw (tcu::TestLog& log, const tcu::RenderTarget& m_renderTarget, const ConstPixelBufferAccess& image, const PixelBufferAccess& output)
+{
+	const int			height				= image.getHeight();
+	const int			width				= image.getWidth();
+
+	bool				faulty				= false;
+
+	tcu::clear(output, MASK_COLOR_OK);
+
+	for (int y = 0; y < height; y++)
+	{
+		for (int x = 0; x < width; x++)
+		{
+			const tcu::IVec4	cTest	= image.getPixelInt(x, y);
+
+			const bool pixelValid = isBlack(cTest) || isHalfFilled(cTest) || (m_renderTarget.getNumSamples() > 1 && isLessThanHalfFilled(cTest));
+
+			if (!pixelValid)
+			{
+				output.setPixel(MASK_COLOR_FAIL, x, y);
+				faulty = true;
+			}
+		}
+	}
+
+	if (faulty)
+		log << TestLog::Message << "Faulty pixel(s) found." << TestLog::EndMessage;
+
+	return !faulty;
+}
+
+void checkPointSize (const glw::Functions& gl, float pointSize)
+{
+	GLfloat pointSizeRange[2] = {0,0};
+	gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
+	if (pointSizeRange[1] < pointSize)
+		throw tcu::NotSupportedError("Maximum point size is too low for this test");
+}
+
+void checkLineWidth (const glw::Functions& gl, float lineWidth)
+{
+	GLfloat lineWidthRange[2] = {0,0};
+	gl.getFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);
+	if (lineWidthRange[1] < lineWidth)
+		throw tcu::NotSupportedError("Maximum line width is too low for this test");
+}
+
+tcu::Vec3 IVec3ToVec3 (const tcu::IVec3& v)
+{
+	return tcu::Vec3((float)v.x(), (float)v.y(), (float)v.z());
+}
+
+bool pointOnTriangle (const tcu::IVec3& p, const tcu::IVec3& t0, const tcu::IVec3& t1, const tcu::IVec3& t2)
+{
+	// Must be on the plane
+	const tcu::IVec3 n = tcu::cross(t1 - t0, t2 - t0);
+	const tcu::IVec3 d = (p - t0);
+
+	if (tcu::dot(n, d))
+		return false;
+
+	// Must be within the triangle area
+	if (deSign32(tcu::dot(n, tcu::cross(t1 - t0, p - t0))) == deSign32(tcu::dot(n, tcu::cross(t2 - t0, p - t0))))
+		return false;
+	if (deSign32(tcu::dot(n, tcu::cross(t2 - t1, p - t1))) == deSign32(tcu::dot(n, tcu::cross(t0 - t1, p - t1))))
+		return false;
+	if (deSign32(tcu::dot(n, tcu::cross(t0 - t2, p - t2))) == deSign32(tcu::dot(n, tcu::cross(t1 - t2, p - t2))))
+		return false;
+
+	return true;
+}
+
+bool pointsOnLine (const tcu::IVec2& t0, const tcu::IVec2& t1, const tcu::IVec2& t2)
+{
+	return (t1 - t0).x() * (t2 - t0).y() - (t2 - t0).x() * (t1 - t0).y() == 0;
+}
+
+// returns true for cases where polygon is (almost) along xz or yz planes (normal.z < 0.1)
+// \note[jarkko] Doesn't have to be accurate, just to detect some obviously bad cases
+bool twoPointClippedTriangleInvisible(const tcu::Vec3& p, const tcu::IVec3& dir1, const tcu::IVec3& dir2)
+{
+	// fixed-point-like coords
+	const deInt64					fixedScale	= 64;
+	const deInt64					farValue	= 1024;
+	const tcu::Vector<deInt64, 3>	d1			= tcu::Vector<deInt64, 3>(dir1.x(), dir1.y(), dir1.z());
+	const tcu::Vector<deInt64, 3>	d2			= tcu::Vector<deInt64, 3>(dir2.x(), dir2.y(), dir2.z());
+	const tcu::Vector<deInt64, 3>	pfixed		= tcu::Vector<deInt64, 3>(deFloorFloatToInt32(p.x() * fixedScale), deFloorFloatToInt32(p.y() * fixedScale), deFloorFloatToInt32(p.z() * fixedScale));
+	const tcu::Vector<deInt64, 3>	normalDir	= tcu::cross(d1*farValue - pfixed, d2*farValue - pfixed);
+	const deInt64					normalLen2	= tcu::lengthSquared(normalDir);
+
+	return (normalDir.z() * normalDir.z() - normalLen2/100) < 0;
+}
+
+std::string genClippingPointInfoString(const tcu::Vec4& p)
+{
+	std::ostringstream msg;
+
+	if (p.x() < -p.w())		msg << "\t(-X clip)";
+	if (p.x() >  p.w())		msg << "\t(+X clip)";
+	if (p.y() < -p.w())		msg << "\t(-Y clip)";
+	if (p.y() >  p.w())		msg << "\t(+Y clip)";
+	if (p.z() < -p.w())		msg << "\t(-Z clip)";
+	if (p.z() >  p.w())		msg << "\t(+Z clip)";
+
+	return msg.str();
+}
+
+std::string genColorString(const tcu::Vec4& p)
+{
+	const tcu::Vec4 white	(1.0f, 1.0f, 1.0f, 1.0f);
+	const tcu::Vec4 red		(1.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4 yellow	(1.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 blue	(0.0f, 0.0f, 1.0f, 1.0f);
+
+	if (p == white)		return "(white)";
+	if (p == red)		return "(red)";
+	if (p == yellow)	return "(yellow)";
+	if (p == blue)		return "(blue)";
+	return "";
+}
+
+class PositionColorShader : public sglr::ShaderProgram
+{
+public:
+	enum
+	{
+		VARYINGLOC_COLOR = 0
+	};
+
+			PositionColorShader (void);
+
+	void	shadeVertices		(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void	shadeFragments		(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+};
+
+PositionColorShader::PositionColorShader (void)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_pointSize", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(shaderSourceVertex)
+							<< sglr::pdec::FragmentSource(shaderSourceFragment))
+{
+}
+
+void PositionColorShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const int positionAttrLoc = 0;
+		const int colorAttrLoc = 1;
+		const int pointSizeAttrLoc = 2;
+
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		// Transform to position
+		packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx);
+
+		// output point size
+		packet.pointSize = rr::readVertexAttribFloat(inputs[pointSizeAttrLoc], packet.instanceNdx, packet.vertexNdx).x();
+
+		// Pass color to FS
+		packet.outputs[VARYINGLOC_COLOR] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void PositionColorShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::FragmentPacket& packet = packets[packetNdx];
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, VARYINGLOC_COLOR, fragNdx));
+	}
+}
+
+class RenderTestCase : public TestCase
+{
+public:
+					RenderTestCase	(Context& context, const char* name, const char* description);
+
+	virtual void	testRender		(void) = DE_NULL;
+	virtual void	init			(void) { }
+
+	IterateResult	iterate			(void);
+};
+
+RenderTestCase::RenderTestCase (Context& context, const char* name, const char* description)
+	: TestCase	(context, name, description)
+{
+}
+
+RenderTestCase::IterateResult RenderTestCase::iterate (void)
+{
+	const int width	 = m_context.getRenderTarget().getWidth();
+	const int height = m_context.getRenderTarget().getHeight();
+
+	m_testCtx.getLog() << TestLog::Message << "Render target size: " << width << "x" << height << TestLog::EndMessage;
+	if (width < TEST_CANVAS_SIZE || height < TEST_CANVAS_SIZE)
+		throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(TEST_CANVAS_SIZE) + "x" + de::toString(TEST_CANVAS_SIZE));
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); // success by default
+	testRender();
+
+	return STOP;
+}
+
+class PointCase : public RenderTestCase
+{
+public:
+									PointCase	(Context& context, const char* name, const char* description, const tcu::Vec4* pointsBegin, const tcu::Vec4* pointsEnd, float pointSize, const rr::WindowRectangle& viewport);
+
+	void							init		(void);
+	void							testRender	(void);
+
+private:
+	const std::vector<tcu::Vec4>	m_points;
+	const float						m_pointSize;
+	const rr::WindowRectangle		m_viewport;
+};
+
+PointCase::PointCase (Context& context, const char* name, const char* description, const tcu::Vec4* pointsBegin, const tcu::Vec4* pointsEnd, float pointSize, const rr::WindowRectangle& viewport)
+	: RenderTestCase(context, name, description)
+	, m_points		(pointsBegin, pointsEnd)
+	, m_pointSize	(pointSize)
+	, m_viewport	(viewport)
+{
+}
+
+void PointCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	checkPointSize (gl, m_pointSize);
+}
+
+void PointCase::testRender (void)
+{
+	using tcu::TestLog;
+
+	const int numSamples			= de::max(m_context.getRenderTarget().getNumSamples(), 1);
+
+	tcu::TestLog&					log			= m_testCtx.getLog();
+	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
+	sglr::ReferenceContextLimits	limits;
+	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
+	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+	PositionColorShader				program;
+	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	sglr::Context*					contexts[2] = {&glesContext, &refContext};
+	tcu::Surface*					surfaces[2] = {&testSurface, &refSurface};
+
+	// log the purpose of the test
+	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
+	log << TestLog::Message << "Rendering points with point size " << m_pointSize << ". Coordinates:" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < m_points.size(); ++ndx)
+		log << TestLog::Message
+				<< "\tx=" << m_points[ndx].x()
+				<< "\ty=" << m_points[ndx].y()
+				<< "\tz=" << m_points[ndx].z()
+				<< "\tw=" << m_points[ndx].w()
+				<< "\t" << genClippingPointInfoString(m_points[ndx])
+				<< TestLog::EndMessage;
+
+	for (int contextNdx = 0; contextNdx < 2; ++contextNdx)
+	{
+		sglr::Context&	ctx				= *contexts[contextNdx];
+		tcu::Surface&	dstSurface		= *surfaces[contextNdx];
+		const deUint32	programId		= ctx.createProgram(&program);
+		const GLint		positionLoc		= ctx.getAttribLocation(programId, "a_position");
+		const GLint		pointSizeLoc	= ctx.getAttribLocation(programId, "a_pointSize");
+		const GLint		colorLoc		= ctx.getAttribLocation(programId, "a_color");
+
+		ctx.clearColor					(0, 0, 0, 1);
+		ctx.clearDepthf					(1.0f);
+		ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
+		ctx.useProgram					(programId);
+		ctx.enableVertexAttribArray		(positionLoc);
+		ctx.vertexAttribPointer			(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &m_points[0]);
+		ctx.vertexAttrib1f				(pointSizeLoc, m_pointSize);
+		ctx.vertexAttrib4f				(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+		ctx.drawArrays					(GL_POINTS, 0, (glw::GLsizei)m_points.size());
+		ctx.disableVertexAttribArray	(positionLoc);
+		ctx.useProgram					(0);
+		ctx.deleteProgram				(programId);
+		ctx.finish						();
+
+		ctx.readPixels(dstSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	}
+
+	// do the comparison
+	{
+		tcu::Surface		diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		const int			kernelRadius	= 1;
+		int					faultyPixels;
+
+		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
+
+		faultyPixels = compareBlackNonBlackImages(log, testSurface.getAccess(), refSurface.getAccess(), diffMask.getAccess(), kernelRadius);
+
+		if (faultyPixels > 0)
+		{
+			log << TestLog::ImageSet("Images", "Image comparison")
+				<< TestLog::Image("TestImage", "Test image", testSurface.getAccess())
+				<< TestLog::Image("ReferenceImage", "Reference image", refSurface.getAccess())
+				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+				<< TestLog::EndImageSet
+				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+		}
+	}
+}
+
+class LineRenderTestCase : public RenderTestCase
+{
+public:
+	struct ColoredLineData
+	{
+		tcu::Vec4 p0;
+		tcu::Vec4 c0;
+		tcu::Vec4 p1;
+		tcu::Vec4 c1;
+	};
+
+	struct ColorlessLineData
+	{
+		tcu::Vec4 p0;
+		tcu::Vec4 p1;
+	};
+										LineRenderTestCase		(Context& context, const char* name, const char* description, const ColoredLineData*   linesBegin, const ColoredLineData*   linesEnd, float lineWidth, const rr::WindowRectangle& viewport);
+										LineRenderTestCase		(Context& context, const char* name, const char* description, const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport);
+
+	virtual void						verifyImage				(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess) = DE_NULL;
+	void								init					(void);
+	void								testRender				(void);
+
+private:
+	std::vector<ColoredLineData>		convertToColoredLines	(const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd);
+
+	const std::vector<ColoredLineData>	m_lines;
+	const float							m_lineWidth;
+	const rr::WindowRectangle			m_viewport;
+};
+
+LineRenderTestCase::LineRenderTestCase (Context& context, const char* name, const char* description, const ColoredLineData* linesBegin, const ColoredLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport)
+	: RenderTestCase	(context, name, description)
+	, m_lines			(linesBegin, linesEnd)
+	, m_lineWidth		(lineWidth)
+	, m_viewport		(viewport)
+{
+}
+
+LineRenderTestCase::LineRenderTestCase (Context& context, const char* name, const char* description, const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport)
+	: RenderTestCase	(context, name, description)
+	, m_lines			(convertToColoredLines(linesBegin, linesEnd))
+	, m_lineWidth		(lineWidth)
+	, m_viewport		(viewport)
+{
+}
+
+void LineRenderTestCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	checkLineWidth (gl, m_lineWidth);
+}
+
+void LineRenderTestCase::testRender (void)
+{
+	using tcu::TestLog;
+
+	const int numSamples			= de::max(m_context.getRenderTarget().getNumSamples(), 1);
+	const int verticesPerLine		= 2;
+
+	tcu::TestLog&					log			= m_testCtx.getLog();
+	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
+	sglr::ReferenceContextLimits	limits;
+	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
+	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+	PositionColorShader				program;
+	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	sglr::Context*					contexts[2] = {&glesContext, &refContext};
+	tcu::Surface*					surfaces[2] = {&testSurface, &refSurface};
+
+	// log the purpose of the test
+	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
+	log << TestLog::Message << "Rendering lines with line width " << m_lineWidth << ". Coordinates:" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < m_lines.size(); ++ndx)
+	{
+		const std::string fromProperties = genClippingPointInfoString(m_lines[ndx].p0);
+		const std::string toProperties   = genClippingPointInfoString(m_lines[ndx].p1);
+
+		log << TestLog::Message << "\tfrom (x=" << m_lines[ndx].p0.x() << "\ty=" << m_lines[ndx].p0.y() << "\tz=" << m_lines[ndx].p0.z() << "\tw=" << m_lines[ndx].p0.w() << ")\t" << fromProperties << TestLog::EndMessage;
+		log << TestLog::Message << "\tto   (x=" << m_lines[ndx].p1.x() << "\ty=" << m_lines[ndx].p1.y() << "\tz=" << m_lines[ndx].p1.z() << "\tw=" << m_lines[ndx].p1.w() << ")\t" << toProperties   << TestLog::EndMessage;
+		log << TestLog::Message << TestLog::EndMessage;
+	}
+
+	// render test image
+	for (int contextNdx = 0; contextNdx < 2; ++contextNdx)
+	{
+		sglr::Context&	ctx				= *contexts[contextNdx];
+		tcu::Surface&	dstSurface		= *surfaces[contextNdx];
+		const deUint32	programId		= ctx.createProgram(&program);
+		const GLint		positionLoc		= ctx.getAttribLocation(programId, "a_position");
+		const GLint		colorLoc		= ctx.getAttribLocation(programId, "a_color");
+
+		ctx.clearColor					(0, 0, 0, 1);
+		ctx.clearDepthf					(1.0f);
+		ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
+		ctx.useProgram					(programId);
+		ctx.enableVertexAttribArray		(positionLoc);
+		ctx.enableVertexAttribArray		(colorLoc);
+		ctx.vertexAttribPointer			(positionLoc,	4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_lines[0].p0);
+		ctx.vertexAttribPointer			(colorLoc,		4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_lines[0].c0);
+		ctx.lineWidth					(m_lineWidth);
+		ctx.drawArrays					(GL_LINES, 0, verticesPerLine * (glw::GLsizei)m_lines.size());
+		ctx.disableVertexAttribArray	(positionLoc);
+		ctx.disableVertexAttribArray	(colorLoc);
+		ctx.useProgram					(0);
+		ctx.deleteProgram				(programId);
+		ctx.finish						();
+
+		ctx.readPixels(dstSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	}
+
+	// compare
+	verifyImage(testSurface.getAccess(), refSurface.getAccess());
+}
+
+std::vector<LineRenderTestCase::ColoredLineData> LineRenderTestCase::convertToColoredLines(const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd)
+{
+	std::vector<ColoredLineData> ret;
+
+	for (const ColorlessLineData* it = linesBegin; it != linesEnd; ++it)
+	{
+		ColoredLineData r;
+
+		r.p0 = (*it).p0;
+		r.c0 = tcu::Vec4(1, 1, 1, 1);
+		r.p1 = (*it).p1;
+		r.c1 = tcu::Vec4(1, 1, 1, 1);
+
+		ret.push_back(r);
+	}
+
+	return ret;
+}
+
+class LineCase : public LineRenderTestCase
+{
+public:
+				LineCase			(Context& context, const char* name, const char* description, const LineRenderTestCase::ColorlessLineData* linesBegin, const LineRenderTestCase::ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport, int searchKernelSize = 1);
+
+	void		verifyImage			(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
+
+private:
+	const int	m_searchKernelSize;
+};
+
+LineCase::LineCase (Context& context, const char* name, const char* description, const LineRenderTestCase::ColorlessLineData* linesBegin, const LineRenderTestCase::ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport, int searchKernelSize)
+	: LineRenderTestCase	(context, name, description, linesBegin, linesEnd, lineWidth, viewport)
+	, m_searchKernelSize	(searchKernelSize)
+{
+}
+
+void LineCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
+{
+	const int	faultyLimit = 6;
+	int			faultyPixels;
+
+	tcu::TestLog&		log			= m_testCtx.getLog();
+	tcu::Surface		diffMask	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+	log << TestLog::Message << "Deviation within radius of " << m_searchKernelSize << " is allowed." << TestLog::EndMessage;
+	log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;
+
+	faultyPixels = compareBlackNonBlackImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), m_searchKernelSize);
+
+	if (faultyPixels > faultyLimit)
+	{
+		log << TestLog::ImageSet("Images", "Image comparison")
+			<< TestLog::Image("TestImage", "Test image", testImageAccess)
+			<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
+			<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+			<< TestLog::EndImageSet
+			<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+	}
+}
+
+class ColoredLineCase : public LineRenderTestCase
+{
+public:
+	ColoredLineCase (Context& context, const char* name, const char* description, const LineRenderTestCase::ColoredLineData* linesBegin, const LineRenderTestCase::ColoredLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport);
+
+	void verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
+};
+
+ColoredLineCase::ColoredLineCase (Context& context, const char* name, const char* description, const LineRenderTestCase::ColoredLineData* linesBegin, const LineRenderTestCase::ColoredLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport)
+	: LineRenderTestCase (context, name, description, linesBegin, linesEnd, lineWidth, viewport)
+{
+}
+
+void ColoredLineCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
+{
+	const bool		msaa	= m_context.getRenderTarget().getNumSamples() > 1;
+	tcu::TestLog&	log		= m_testCtx.getLog();
+
+	if (!msaa)
+	{
+		const int		kernelRadius	= 1;
+		const int		faultyLimit		= 6;
+		int				faultyPixels;
+		tcu::Surface	diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
+		log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;
+
+		faultyPixels = compareColoredImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), kernelRadius);
+
+		if (faultyPixels > faultyLimit)
+		{
+			log << TestLog::ImageSet("Images", "Image comparison")
+				<< TestLog::Image("TestImage", "Test image", testImageAccess)
+				<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
+				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+				<< TestLog::EndImageSet
+				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+		}
+	}
+	else
+	{
+		const float threshold = 0.3f;
+		if (!tcu::fuzzyCompare(log, "Images", "", referenceImageAccess, testImageAccess, threshold, tcu::COMPARE_LOG_ON_ERROR))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+	}
+}
+
+class TriangleCaseBase : public RenderTestCase
+{
+public:
+	struct TriangleData
+	{
+		tcu::Vec4 p0;
+		tcu::Vec4 c0;
+		tcu::Vec4 p1;
+		tcu::Vec4 c1;
+		tcu::Vec4 p2;
+		tcu::Vec4 c2;
+	};
+
+										TriangleCaseBase	(Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport);
+
+	virtual void						verifyImage			(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess) = DE_NULL;
+	void								testRender			(void);
+
+private:
+	const std::vector<TriangleData>		m_polys;
+	const rr::WindowRectangle			m_viewport;
+};
+
+TriangleCaseBase::TriangleCaseBase (Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport)
+	: RenderTestCase(context, name, description)
+	, m_polys		(polysBegin, polysEnd)
+	, m_viewport	(viewport)
+{
+}
+
+void TriangleCaseBase::testRender (void)
+{
+	using tcu::TestLog;
+
+	const int numSamples			= de::max(m_context.getRenderTarget().getNumSamples(), 1);
+	const int verticesPerTriangle	= 3;
+
+	tcu::TestLog&					log			= m_testCtx.getLog();
+	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
+	sglr::ReferenceContextLimits	limits;
+	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
+	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+	PositionColorShader				program;
+	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	sglr::Context*					contexts[2] = {&glesContext, &refContext};
+	tcu::Surface*					surfaces[2] = {&testSurface, &refSurface};
+
+	// log the purpose of the test
+	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
+	log << TestLog::Message << "Rendering triangles. Coordinates:" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < m_polys.size(); ++ndx)
+	{
+		const std::string v0Properties = genClippingPointInfoString(m_polys[ndx].p0);
+		const std::string v1Properties = genClippingPointInfoString(m_polys[ndx].p1);
+		const std::string v2Properties = genClippingPointInfoString(m_polys[ndx].p2);
+		const std::string c0Properties = genColorString(m_polys[ndx].c0);
+		const std::string c1Properties = genColorString(m_polys[ndx].c1);
+		const std::string c2Properties = genColorString(m_polys[ndx].c2);
+
+		log << TestLog::Message << "\tv0 (x=" << m_polys[ndx].p0.x() << "\ty=" << m_polys[ndx].p0.y() << "\tz=" << m_polys[ndx].p0.z() << "\tw=" << m_polys[ndx].p0.w() << ")\t" << v0Properties << "\t" << c0Properties << TestLog::EndMessage;
+		log << TestLog::Message << "\tv1 (x=" << m_polys[ndx].p1.x() << "\ty=" << m_polys[ndx].p1.y() << "\tz=" << m_polys[ndx].p1.z() << "\tw=" << m_polys[ndx].p1.w() << ")\t" << v1Properties << "\t" << c1Properties << TestLog::EndMessage;
+		log << TestLog::Message << "\tv2 (x=" << m_polys[ndx].p2.x() << "\ty=" << m_polys[ndx].p2.y() << "\tz=" << m_polys[ndx].p2.z() << "\tw=" << m_polys[ndx].p2.w() << ")\t" << v2Properties << "\t" << c2Properties << TestLog::EndMessage;
+		log << TestLog::Message << TestLog::EndMessage;
+	}
+
+	// render test image
+	for (int contextNdx = 0; contextNdx < 2; ++contextNdx)
+	{
+		sglr::Context&	ctx				= *contexts[contextNdx];
+		tcu::Surface&	dstSurface		= *surfaces[contextNdx];
+		const deUint32	programId		= ctx.createProgram(&program);
+		const GLint		positionLoc		= ctx.getAttribLocation(programId, "a_position");
+		const GLint		colorLoc		= ctx.getAttribLocation(programId, "a_color");
+
+		ctx.clearColor					(0, 0, 0, 1);
+		ctx.clearDepthf					(1.0f);
+		ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
+		ctx.useProgram					(programId);
+		ctx.enableVertexAttribArray		(positionLoc);
+		ctx.enableVertexAttribArray		(colorLoc);
+		ctx.vertexAttribPointer			(positionLoc,	4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_polys[0].p0);
+		ctx.vertexAttribPointer			(colorLoc,		4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_polys[0].c0);
+		ctx.drawArrays					(GL_TRIANGLES, 0, verticesPerTriangle * (glw::GLsizei)m_polys.size());
+		ctx.disableVertexAttribArray	(positionLoc);
+		ctx.disableVertexAttribArray	(colorLoc);
+		ctx.useProgram					(0);
+		ctx.deleteProgram				(programId);
+		ctx.finish						();
+
+		ctx.readPixels(dstSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	}
+
+	verifyImage(testSurface.getAccess(), refSurface.getAccess());
+}
+
+class TriangleCase : public TriangleCaseBase
+{
+public:
+			TriangleCase	(Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport);
+
+	void	verifyImage		(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
+};
+
+TriangleCase::TriangleCase (Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport)
+	: TriangleCaseBase(context, name, description, polysBegin, polysEnd, viewport)
+{
+}
+
+void TriangleCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
+{
+	const int			kernelRadius	= 1;
+	const int			faultyLimit		= 6;
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	int					faultyPixels;
+
+	log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+	log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
+	log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;
+
+	faultyPixels = compareBlackNonBlackImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), kernelRadius);
+
+	if (faultyPixels > faultyLimit)
+	{
+		log << TestLog::ImageSet("Images", "Image comparison")
+			<< TestLog::Image("TestImage", "Test image", testImageAccess)
+			<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
+			<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+			<< TestLog::EndImageSet
+			<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+	}
+}
+
+class TriangleAttributeCase : public TriangleCaseBase
+{
+public:
+			TriangleAttributeCase	(Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport);
+
+	void	verifyImage				(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
+};
+
+TriangleAttributeCase::TriangleAttributeCase (Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport)
+	: TriangleCaseBase(context, name, description, polysBegin, polysEnd, viewport)
+{
+}
+
+void TriangleAttributeCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
+{
+	const bool		msaa	= m_context.getRenderTarget().getNumSamples() > 1;
+	tcu::TestLog&	log		= m_testCtx.getLog();
+
+	if (!msaa)
+	{
+		const int		kernelRadius	= 1;
+		const int		faultyLimit		= 6;
+		int				faultyPixels;
+		tcu::Surface	diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
+		log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;
+		faultyPixels = compareColoredImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), kernelRadius);
+
+		if (faultyPixels > faultyLimit)
+		{
+			log << TestLog::ImageSet("Images", "Image comparison")
+				<< TestLog::Image("TestImage", "Test image", testImageAccess)
+				<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
+				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+				<< TestLog::EndImageSet
+				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+		}
+	}
+	else
+	{
+		const float threshold = 0.3f;
+		if (!tcu::fuzzyCompare(log, "Images", "", referenceImageAccess, testImageAccess, threshold, tcu::COMPARE_LOG_ON_ERROR))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+	}
+}
+
+class FillTest : public RenderTestCase
+{
+public:
+								FillTest	(Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport);
+
+	virtual void				render		(sglr::Context& ctx) = DE_NULL;
+	void						testRender	(void);
+
+protected:
+	const rr::WindowRectangle	m_viewport;
+};
+
+FillTest::FillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport)
+	: RenderTestCase(context, name, description)
+	, m_viewport	(viewport)
+{
+}
+
+void FillTest::testRender (void)
+{
+	using tcu::TestLog;
+
+	const int						numSamples	= 1;
+
+	tcu::TestLog&					log			= m_testCtx.getLog();
+	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
+	sglr::ReferenceContextLimits	limits;
+	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
+	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	render(glesContext);
+	glesContext.readPixels(testSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	render(refContext);
+	refContext.readPixels(refSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// check overdraw
+	{
+		bool				overdrawOk;
+		tcu::Surface		outputImage(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+		log << TestLog::Message << "Checking for overdraw " << TestLog::EndMessage;
+		overdrawOk = checkHalfFilledImageOverdraw(log, m_context.getRenderTarget(), testSurface.getAccess(), outputImage.getAccess());
+
+		if (!overdrawOk)
+		{
+			log << TestLog::ImageSet("Images", "Image comparison")
+				<< TestLog::Image("TestImage", "Test image", testSurface.getAccess())
+				<< TestLog::Image("InvalidPixels", "Invalid pixels", outputImage.getAccess())
+				<< TestLog::EndImageSet
+				<< tcu::TestLog::Message << "Got overdraw." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got overdraw");
+		}
+	}
+
+	// compare & check missing pixels
+	{
+		const int			kernelRadius	= 1;
+		tcu::Surface		diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		int					faultyPixels;
+
+		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
+
+		blitImageOnBlackSurface(refSurface.getAccess(), refSurface.getAccess()); // makes images look right in Candy
+
+		faultyPixels = compareBlackNonBlackImages(log, testSurface.getAccess(), refSurface.getAccess(), diffMask.getAccess(), kernelRadius);
+
+		if (faultyPixels > 0)
+		{
+			log << TestLog::ImageSet("Images", "Image comparison")
+				<< TestLog::Image("TestImage", "Test image", testSurface.getAccess())
+				<< TestLog::Image("ReferenceImage", "Reference image", refSurface.getAccess())
+				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+				<< TestLog::EndImageSet
+				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+		}
+	}
+}
+
+class TriangleFillTest : public FillTest
+{
+public:
+	struct FillTriangle
+	{
+		tcu::Vec4 v0;
+		tcu::Vec4 c0;
+		tcu::Vec4 v1;
+		tcu::Vec4 c1;
+		tcu::Vec4 v2;
+		tcu::Vec4 c2;
+	};
+
+								TriangleFillTest	(Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport);
+
+	void						render				(sglr::Context& ctx);
+
+protected:
+	std::vector<FillTriangle>	m_triangles;
+};
+
+TriangleFillTest::TriangleFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport)
+	: FillTest(context, name, description, viewport)
+{
+}
+
+void TriangleFillTest::render (sglr::Context& ctx)
+{
+	const int			verticesPerTriangle		= 3;
+	PositionColorShader program;
+	const deUint32		programId				= ctx.createProgram(&program);
+	const GLint			positionLoc				= ctx.getAttribLocation(programId, "a_position");
+	const GLint			colorLoc				= ctx.getAttribLocation(programId, "a_color");
+	tcu::TestLog&		log						= m_testCtx.getLog();
+
+	// log the purpose of the test
+	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
+	log << TestLog::Message << "Rendering triangles. Coordinates:" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < m_triangles.size(); ++ndx)
+	{
+		const std::string v0Properties = genClippingPointInfoString(m_triangles[ndx].v0);
+		const std::string v1Properties = genClippingPointInfoString(m_triangles[ndx].v1);
+		const std::string v2Properties = genClippingPointInfoString(m_triangles[ndx].v2);
+
+		log << TestLog::Message << "\tv0 (x=" << m_triangles[ndx].v0.x() << "\ty=" << m_triangles[ndx].v0.y() << "\tz=" << m_triangles[ndx].v0.z() << "\tw=" << m_triangles[ndx].v0.w() << ")\t" << v0Properties << TestLog::EndMessage;
+		log << TestLog::Message << "\tv1 (x=" << m_triangles[ndx].v1.x() << "\ty=" << m_triangles[ndx].v1.y() << "\tz=" << m_triangles[ndx].v1.z() << "\tw=" << m_triangles[ndx].v1.w() << ")\t" << v1Properties << TestLog::EndMessage;
+		log << TestLog::Message << "\tv2 (x=" << m_triangles[ndx].v2.x() << "\ty=" << m_triangles[ndx].v2.y() << "\tz=" << m_triangles[ndx].v2.z() << "\tw=" << m_triangles[ndx].v2.w() << ")\t" << v2Properties << TestLog::EndMessage;
+		log << TestLog::Message << TestLog::EndMessage;
+	}
+
+	ctx.clearColor					(0, 0, 0, 1);
+	ctx.clearDepthf					(1.0f);
+	ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
+	ctx.useProgram					(programId);
+	ctx.blendFunc					(GL_ONE, GL_ONE);
+	ctx.enable						(GL_BLEND);
+	ctx.enableVertexAttribArray		(positionLoc);
+	ctx.enableVertexAttribArray		(colorLoc);
+	ctx.vertexAttribPointer			(positionLoc,	4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_triangles[0].v0);
+	ctx.vertexAttribPointer			(colorLoc,		4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_triangles[0].c0);
+	ctx.drawArrays					(GL_TRIANGLES, 0, verticesPerTriangle * (glw::GLsizei)m_triangles.size());
+	ctx.disableVertexAttribArray	(positionLoc);
+	ctx.disableVertexAttribArray	(colorLoc);
+	ctx.useProgram					(0);
+	ctx.deleteProgram				(programId);
+	ctx.finish						();
+}
+
+class QuadFillTest : public TriangleFillTest
+{
+public:
+	QuadFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport, const tcu::Vec3& d1, const tcu::Vec3& d2, const tcu::Vec3& center_ = tcu::Vec3(0, 0, 0));
+};
+
+QuadFillTest::QuadFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport, const tcu::Vec3& d1, const tcu::Vec3& d2, const tcu::Vec3& center_)
+	: TriangleFillTest(context, name, description, viewport)
+{
+	const float		radius		= 40000.0f;
+	const tcu::Vec4 center		= tcu::Vec4(center_.x(), center_.y(), center_.z(), 1.0f);
+	const tcu::Vec4 halfWhite	= tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f);
+	const tcu::Vec4 halfRed		= tcu::Vec4(0.5f, 0.0f, 0.0f, 0.5f);
+	const tcu::Vec4	e1			= radius * tcu::Vec4(d1.x(), d1.y(), d1.z(), 0.0f);
+	const tcu::Vec4	e2			= radius * tcu::Vec4(d2.x(), d2.y(), d2.z(), 0.0f);
+
+	FillTriangle triangle1;
+	FillTriangle triangle2;
+
+	triangle1.c0 = halfWhite;
+	triangle1.c1 = halfWhite;
+	triangle1.c2 = halfWhite;
+	triangle1.v0 = center + e1 + e2;
+	triangle1.v1 = center + e1 - e2;
+	triangle1.v2 = center - e1 - e2;
+	m_triangles.push_back(triangle1);
+
+	triangle2.c0 = halfRed;
+	triangle2.c1 = halfRed;
+	triangle2.c2 = halfRed;
+	triangle2.v0 = center + e1 + e2;
+	triangle2.v1 = center - e1 - e2;
+	triangle2.v2 = center - e1 + e2;
+	m_triangles.push_back(triangle2);
+}
+
+class TriangleFanFillTest : public TriangleFillTest
+{
+public:
+	TriangleFanFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport);
+};
+
+TriangleFanFillTest::TriangleFanFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport)
+	: TriangleFillTest(context, name, description, viewport)
+{
+	const float		radius				= 70000.0f;
+	const int		trianglesPerVisit	= 40;
+	const tcu::Vec4 center				= tcu::Vec4(0, 0, 0, 1.0f);
+	const tcu::Vec4 halfWhite			= tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f);
+	const tcu::Vec4 oddSliceColor		= tcu::Vec4(0.0f, 0.0f, 0.5f, 0.0f);
+
+	// create a continuous surface that goes through all 6 clip planes
+
+	/*
+		*   /           /
+		*  /_ _ _ _ _  /x
+		* |           |  |
+		* |           | /
+		* |       / --xe /
+		* |      |    | /
+		* |_ _ _ e _ _|/
+		*
+		* e = enter
+		* x = exit
+		*/
+	const struct ClipPlaneVisit
+	{
+		const tcu::Vec3 corner;
+		const tcu::Vec3 entryPoint;
+		const tcu::Vec3 exitPoint;
+	} visits[] =
+	{
+		{ tcu::Vec3( 1, 1, 1),	tcu::Vec3( 0, 1, 1),	tcu::Vec3( 1, 0, 1) },
+		{ tcu::Vec3( 1,-1, 1),	tcu::Vec3( 1, 0, 1),	tcu::Vec3( 1,-1, 0) },
+		{ tcu::Vec3( 1,-1,-1),	tcu::Vec3( 1,-1, 0),	tcu::Vec3( 0,-1,-1) },
+		{ tcu::Vec3(-1,-1,-1),	tcu::Vec3( 0,-1,-1),	tcu::Vec3(-1, 0,-1) },
+		{ tcu::Vec3(-1, 1,-1),	tcu::Vec3(-1, 0,-1),	tcu::Vec3(-1, 1, 0) },
+		{ tcu::Vec3(-1, 1, 1),	tcu::Vec3(-1, 1, 0),	tcu::Vec3( 0, 1, 1) },
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(visits); ++ndx)
+	{
+		const ClipPlaneVisit& visit = visits[ndx];
+
+		for (int tri = 0; tri < trianglesPerVisit; ++tri)
+		{
+			tcu::Vec3 vertex0;
+			tcu::Vec3 vertex1;
+
+			if (tri == 0) // first vertex is magic
+			{
+				vertex0 = visit.entryPoint;
+			}
+			else
+			{
+				const tcu::Vec3 v1 = visit.entryPoint - visit.corner;
+				const tcu::Vec3 v2 = visit.exitPoint  - visit.corner;
+
+				vertex0 = visit.corner + tcu::normalize(tcu::mix(v1, v2, tcu::Vec3(float(tri)/trianglesPerVisit)));
+			}
+
+			if (tri == trianglesPerVisit-1) // last vertex is magic
+			{
+				vertex1 = visit.exitPoint;
+			}
+			else
+			{
+				const tcu::Vec3 v1 = visit.entryPoint - visit.corner;
+				const tcu::Vec3 v2 = visit.exitPoint  - visit.corner;
+
+				vertex1 = visit.corner + tcu::normalize(tcu::mix(v1, v2, tcu::Vec3(float(tri+1)/trianglesPerVisit)));
+			}
+
+			// write vec out
+			{
+				FillTriangle triangle;
+
+				triangle.c0 = (tri % 2) ? halfWhite : halfWhite + oddSliceColor;
+				triangle.c1 = (tri % 2) ? halfWhite : halfWhite + oddSliceColor;
+				triangle.c2 = (tri % 2) ? halfWhite : halfWhite + oddSliceColor;
+				triangle.v0 = center;
+				triangle.v1 = tcu::Vec4(vertex0.x() * radius, vertex0.y() * radius, vertex0.z() * radius, 1.0f);
+				triangle.v2 = tcu::Vec4(vertex1.x() * radius, vertex1.y() * radius, vertex1.z() * radius, 1.0f);
+
+				m_triangles.push_back(triangle);
+			}
+
+		}
+	}
+}
+
+class PointsTestGroup : public TestCaseGroup
+{
+public:
+			PointsTestGroup	(Context& context);
+
+	void	init			(void);
+};
+
+PointsTestGroup::PointsTestGroup (Context& context)
+	: TestCaseGroup(context, "point", "Point clipping tests")
+{
+}
+
+void PointsTestGroup::init (void)
+{
+	const float littleOverViewport = 1.0f + (2.0f / (TEST_CANVAS_SIZE)); // one pixel over the viewport edge in VIEWPORT_WHOLE, half pixels over in the reduced viewport.
+
+	const tcu::Vec4 viewportTestPoints[] =
+	{
+		// in clip volume
+		tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),
+		tcu::Vec4( 0.1f,  0.1f,  0.1f,  1.0f),
+		tcu::Vec4(-0.1f,  0.1f, -0.1f,  1.0f),
+		tcu::Vec4(-0.1f, -0.1f,  0.1f,  1.0f),
+		tcu::Vec4( 0.1f, -0.1f, -0.1f,  1.0f),
+
+		// in clip volume with w != 1
+		tcu::Vec4( 2.0f,  2.0f,  2.0f,  3.0f),
+		tcu::Vec4(-2.0f, -2.0f,  2.0f,  3.0f),
+		tcu::Vec4( 0.5f, -0.5f,  0.5f,  0.7f),
+		tcu::Vec4(-0.5f,  0.5f, -0.5f,  0.7f),
+
+		// near the edge
+		tcu::Vec4(-2.0f, -2.0f,  0.0f,  2.2f),
+		tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.1f),
+		tcu::Vec4(-1.0f,  1.0f,  0.0f,  1.1f),
+
+		// not in the volume but still between near and far planes
+		tcu::Vec4( 1.3f,  0.0f,  0.0f,  1.0f),
+		tcu::Vec4(-1.3f,  0.0f,  0.0f,  1.0f),
+		tcu::Vec4( 0.0f,  1.3f,  0.0f,  1.0f),
+		tcu::Vec4( 0.0f, -1.3f,  0.0f,  1.0f),
+
+		tcu::Vec4(-1.3f, -1.3f,  0.0f,  1.0f),
+		tcu::Vec4(-1.3f,  1.3f,  0.0f,  1.0f),
+		tcu::Vec4( 1.3f,  1.3f,  0.0f,  1.0f),
+		tcu::Vec4( 1.3f, -1.3f,  0.0f,  1.0f),
+
+		// outside the viewport, wide points have fragments in the viewport
+		tcu::Vec4( littleOverViewport,  littleOverViewport,  0.0f,  1.0f),
+		tcu::Vec4(               0.0f,  littleOverViewport,  0.0f,  1.0f),
+		tcu::Vec4( littleOverViewport,                0.0f,  0.0f,  1.0f),
+	};
+	const tcu::Vec4 depthTestPoints[] =
+	{
+		// in clip volume
+		tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),
+		tcu::Vec4( 0.1f,  0.1f,  0.1f,  1.0f),
+		tcu::Vec4(-0.1f,  0.1f, -0.1f,  1.0f),
+		tcu::Vec4(-0.1f, -0.1f,  0.1f,  1.0f),
+		tcu::Vec4( 0.1f, -0.1f, -0.1f,  1.0f),
+
+		// not between the near and the far planes. These should be clipped
+		tcu::Vec4( 0.1f,  0.0f,  1.1f,  1.0f),
+		tcu::Vec4(-0.1f,  0.0f, -1.1f,  1.0f),
+		tcu::Vec4(-0.0f, -0.1f,  1.1f,  1.0f),
+		tcu::Vec4( 0.0f,  0.1f, -1.1f,  1.0f)
+	};
+
+	addChild(new PointCase(m_context, "point_z_clip",						"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		1.0f,	VIEWPORT_WHOLE));
+	addChild(new PointCase(m_context, "point_z_clip_viewport_center",		"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		1.0f,	VIEWPORT_CENTER));
+	addChild(new PointCase(m_context, "point_z_clip_viewport_corner",		"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		1.0f,	VIEWPORT_CORNER));
+
+	addChild(new PointCase(m_context, "point_clip_viewport_center",			"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	1.0f,	VIEWPORT_CENTER));
+	addChild(new PointCase(m_context, "point_clip_viewport_corner",			"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	1.0f,	VIEWPORT_CORNER));
+
+	addChild(new PointCase(m_context, "wide_point_z_clip",					"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		5.0f,	VIEWPORT_WHOLE));
+	addChild(new PointCase(m_context, "wide_point_z_clip_viewport_center",	"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		5.0f,	VIEWPORT_CENTER));
+	addChild(new PointCase(m_context, "wide_point_z_clip_viewport_corner",	"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		5.0f,	VIEWPORT_CORNER));
+
+	addChild(new PointCase(m_context, "wide_point_clip",					"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	5.0f,	VIEWPORT_WHOLE));
+	addChild(new PointCase(m_context, "wide_point_clip_viewport_center",	"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	5.0f,	VIEWPORT_CENTER));
+	addChild(new PointCase(m_context, "wide_point_clip_viewport_corner",	"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	5.0f,	VIEWPORT_CORNER));
+}
+
+class LinesTestGroup : public TestCaseGroup
+{
+public:
+			LinesTestGroup	(Context& context);
+
+	void	init			(void);
+};
+
+LinesTestGroup::LinesTestGroup (Context& context)
+	: TestCaseGroup(context, "line", "Line clipping tests")
+{
+}
+
+void LinesTestGroup::init (void)
+{
+	const float littleOverViewport = 1.0f + (2.0f / (TEST_CANVAS_SIZE)); // one pixel over the viewport edge in VIEWPORT_WHOLE, half pixels over in the reduced viewport.
+
+	// lines
+	const LineRenderTestCase::ColorlessLineData viewportTestLines[] =
+	{
+		// from center to outside of viewport
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 0.0f,  1.5f,  0.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.5f,  1.0f,  0.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.5f,  0.0f,  0.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 0.2f,  0.4f,  1.5f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-2.0f, -1.0f,  0.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.0f,  0.1f,  0.0f,  0.6f)},
+
+		// from outside to inside of viewport
+		{tcu::Vec4( 1.5f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 0.8f, -0.2f,  0.0f,  1.0f)},
+		{tcu::Vec4( 0.0f, -1.5f,  0.0f,  1.0f),		tcu::Vec4( 0.9f, -0.7f,  0.0f,  1.0f)},
+
+		// from outside to outside
+		{tcu::Vec4( 0.0f, -1.3f,  0.0f,  1.0f),		tcu::Vec4( 1.3f,  0.0f,  0.0f,  1.0f)},
+
+		// outside the viewport, wide lines have fragments in the viewport
+		{tcu::Vec4(-0.8f,                      -littleOverViewport,  0.0f,  1.0f),	tcu::Vec4( 0.0f, -littleOverViewport,         0.0f,  1.0f)},
+		{tcu::Vec4(-littleOverViewport - 1.0f,  0.0f,                0.0f,  1.0f),	tcu::Vec4( 0.0f, -littleOverViewport - 1.0f,  0.0f,  1.0f)},
+	};
+	const LineRenderTestCase::ColorlessLineData depthTestLines[] =
+	{
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.3f,  1.0f,  2.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.3f, -1.0f,  2.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.0f, -1.1f, -2.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.0f,  1.1f, -2.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.0f,  0.1f,  2.0f,  0.6f)},
+	};
+	const LineRenderTestCase::ColorlessLineData longTestLines[] =
+	{
+		{tcu::Vec4( -41000.0f,		-40000.0f,		-1000000.0f,	1.0f),	tcu::Vec4( 41000.0f,		40000.0f,		1000000.0f,	1.0f)},
+		{tcu::Vec4(  41000.0f,		-40000.0f,		 1000000.0f,	1.0f),	tcu::Vec4(-41000.0f,		40000.0f,	   -1000000.0f,	1.0f)},
+		{tcu::Vec4(  0.5f,			-40000.0f,		 100000.0f,		1.0f),	tcu::Vec4( 0.5f,			40000.0f,	   -100000.0f,	1.0f)},
+		{tcu::Vec4( -0.5f,			 40000.0f,		 100000.0f,		1.0f),	tcu::Vec4(-0.5f,		   -40000.0f,	   -100000.0f,	1.0f)},
+	};
+
+	// line attribute clipping
+	const tcu::Vec4 red			(1.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4 yellow		(1.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 lightBlue	(0.3f, 0.3f, 1.0f, 1.0f);
+	const LineRenderTestCase::ColoredLineData colorTestLines[] =
+	{
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4( 1.3f,  1.0f,  2.0f,  1.0f),	yellow		},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4( 1.3f, -1.0f,  2.0f,  1.0f),	lightBlue	},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4(-1.0f, -1.0f, -2.0f,  1.0f),	yellow		},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4(-1.0f,  1.0f, -2.0f,  1.0f),	lightBlue	},
+	};
+
+	// line clipping
+	addChild(new LineCase(m_context, "line_z_clip",							"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		1.0f,	VIEWPORT_WHOLE));
+	addChild(new LineCase(m_context, "line_z_clip_viewport_center",			"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		1.0f,	VIEWPORT_CENTER));
+	addChild(new LineCase(m_context, "line_z_clip_viewport_corner",			"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		1.0f,	VIEWPORT_CORNER));
+
+	addChild(new LineCase(m_context, "line_clip_viewport_center",			"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	1.0f,	VIEWPORT_CENTER));
+	addChild(new LineCase(m_context, "line_clip_viewport_corner",			"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	1.0f,	VIEWPORT_CORNER));
+
+	addChild(new LineCase(m_context, "wide_line_z_clip",					"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		5.0f,	VIEWPORT_WHOLE));
+	addChild(new LineCase(m_context, "wide_line_z_clip_viewport_center",	"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		5.0f,	VIEWPORT_CENTER));
+	addChild(new LineCase(m_context, "wide_line_z_clip_viewport_corner",	"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		5.0f,	VIEWPORT_CORNER));
+
+	addChild(new LineCase(m_context, "wide_line_clip",						"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	5.0f,	VIEWPORT_WHOLE));
+	addChild(new LineCase(m_context, "wide_line_clip_viewport_center",		"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	5.0f,	VIEWPORT_CENTER));
+	addChild(new LineCase(m_context, "wide_line_clip_viewport_corner",		"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	5.0f,	VIEWPORT_CORNER));
+
+	addChild(new LineCase(m_context, "long_line_clip",						"line viewport clipping",		DE_ARRAY_BEGIN(longTestLines),		DE_ARRAY_END(longTestLines),		1.0f,	VIEWPORT_WHOLE, 2));
+	addChild(new LineCase(m_context, "long_wide_line_clip",					"line viewport clipping",		DE_ARRAY_BEGIN(longTestLines),		DE_ARRAY_END(longTestLines),		5.0f,	VIEWPORT_WHOLE, 2));
+
+	// line attribute clipping
+	addChild(new ColoredLineCase(m_context, "line_attrib_clip",				"line attribute clipping",		DE_ARRAY_BEGIN(colorTestLines),		DE_ARRAY_END(colorTestLines),		1.0f,	VIEWPORT_WHOLE));
+	addChild(new ColoredLineCase(m_context, "wide_line_attrib_clip",		"line attribute clipping",		DE_ARRAY_BEGIN(colorTestLines),		DE_ARRAY_END(colorTestLines),		5.0f,	VIEWPORT_WHOLE));
+}
+
+class PolysTestGroup : public TestCaseGroup
+{
+public:
+			PolysTestGroup	(Context& context);
+
+	void	init			(void);
+};
+
+PolysTestGroup::PolysTestGroup (Context& context)
+	: TestCaseGroup(context, "polygon", "Polygon clipping tests")
+{
+}
+
+void PolysTestGroup::init (void)
+{
+	const float		large = 100000.0f;
+	const float		offset = 0.9f;
+	const tcu::Vec4 white	(1.0f, 1.0f, 1.0f, 1.0f);
+	const tcu::Vec4 red		(1.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4 yellow	(1.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 blue	(0.0f, 0.0f, 1.0f, 1.0f);
+
+	// basic cases
+	{
+		const TriangleCase::TriangleData viewportPolys[] =
+		{
+			// one vertex clipped
+			{tcu::Vec4(-0.8f, -0.2f,  0.0f,  1.0f), white, tcu::Vec4(-0.8f,  0.2f,  0.0f,  1.0f), white, tcu::Vec4(-1.3f,  0.05f,  0.0f,  1.0f), white},
+
+			// two vertices clipped
+			{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), white, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), white, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), white},
+
+			// three vertices clipped
+			{tcu::Vec4(-1.1f,  0.6f,  0.0f,  1.0f), white, tcu::Vec4(-1.1f,  1.1f,  0.0f,  1.0f), white, tcu::Vec4(-0.6f,  1.1f,  0.0f,  1.0f), white},
+			{tcu::Vec4( 0.8f,  1.1f,  0.0f,  1.0f), white, tcu::Vec4( 0.95f,-1.1f,  0.0f,  1.0f), white, tcu::Vec4( 3.0f,  0.0f,  0.0f,  1.0f), white},
+		};
+		const TriangleCase::TriangleData depthPolys[] =
+		{
+			// one vertex clipped to Z+
+			{tcu::Vec4(-0.2f,  0.7f,  0.0f,  1.0f), white, tcu::Vec4( 0.2f,  0.7f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f,  0.9f,  2.0f,  1.0f), white},
+
+			// two vertices clipped to Z-
+			{tcu::Vec4( 0.9f, 0.4f,  -1.5f,  1.0f), white, tcu::Vec4( 0.9f, -0.4f, -1.5f,  1.0f), white, tcu::Vec4( 0.6f,  0.0f,  0.0f,  1.0f), white},
+
+			// three vertices clipped
+			{tcu::Vec4(-0.9f, 0.6f,  -2.0f,  1.0f), white, tcu::Vec4(-0.9f, -0.6f, -2.0f,  1.0f), white, tcu::Vec4(-0.4f,  0.0f,  2.0f,  1.0f), white},
+
+			// three vertices clipped by X, Y and Z
+			{tcu::Vec4( 0.0f, -1.2f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f,  0.5f,  -1.5f, 1.0f), white, tcu::Vec4( 1.2f, -0.9f,  0.0f,  1.0f), white},
+		};
+		const TriangleCase::TriangleData largePolys[] =
+		{
+			// one vertex clipped
+			{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f, -large,  2.0f,  1.0f), white},
+
+			// two vertices clipped
+			{tcu::Vec4( 0.5f, 0.5f,  0.0f,  1.0f), white, tcu::Vec4( large, 0.5f, 0.0f,  1.0f), white, tcu::Vec4( 0.5f,  large,  0.0f,  1.0f), white},
+
+			// three vertices clipped
+			{tcu::Vec4(-0.9f, -large, 0.0f,  1.0f), white, tcu::Vec4(-1.1f, -large, 0.0f,  1.0f), white, tcu::Vec4(-0.9f,  large,  0.0f,  1.0f), white},
+		};
+		const TriangleCase::TriangleData largeDepthPolys[] =
+		{
+			// one vertex clipped
+			{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f, -large, large,  1.0f), white},
+
+			// two vertices clipped
+			{tcu::Vec4( 0.5f, 0.5f,  0.0f,  1.0f), white, tcu::Vec4( 0.9f, large/2, -large,  1.0f), white, tcu::Vec4( large/4, 0.0f, -large,  1.0f), white},
+
+			// three vertices clipped
+			{tcu::Vec4(-0.9f, large/4, large,  1.0f), white, tcu::Vec4(-0.5f, -large/4, -large,  1.0f), white, tcu::Vec4(-0.2f, large/4, large,  1.0f), white},
+		};
+		const TriangleCase::TriangleData attribPolys[] =
+		{
+			// one vertex clipped to edge, large
+			{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -large,  2.0f,  1.0f), blue},
+
+			// two vertices clipped to edges
+			{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+
+			// two vertices clipped to edges, with non-uniform w
+			{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+
+			// three vertices clipped, large, Z
+			{tcu::Vec4(-0.9f, large/4, large,  1.0f), red, tcu::Vec4(-0.5f, -large/4, -large,  1.0f), yellow, tcu::Vec4(-0.2f, large/4, large,  1.0f), blue},
+		};
+
+		addChild(new TriangleCase(m_context, "poly_clip_viewport_center",			"polygon viewport clipping",	DE_ARRAY_BEGIN(viewportPolys),		DE_ARRAY_END(viewportPolys),	VIEWPORT_CENTER));
+		addChild(new TriangleCase(m_context, "poly_clip_viewport_corner",			"polygon viewport clipping",	DE_ARRAY_BEGIN(viewportPolys),		DE_ARRAY_END(viewportPolys),	VIEWPORT_CORNER));
+
+		addChild(new TriangleCase(m_context, "poly_z_clip",							"polygon z clipping",			DE_ARRAY_BEGIN(depthPolys),			DE_ARRAY_END(depthPolys),		VIEWPORT_WHOLE));
+		addChild(new TriangleCase(m_context, "poly_z_clip_viewport_center",			"polygon z clipping",			DE_ARRAY_BEGIN(depthPolys),			DE_ARRAY_END(depthPolys),		VIEWPORT_CENTER));
+		addChild(new TriangleCase(m_context, "poly_z_clip_viewport_corner",			"polygon z clipping",			DE_ARRAY_BEGIN(depthPolys),			DE_ARRAY_END(depthPolys),		VIEWPORT_CORNER));
+
+		addChild(new TriangleCase(m_context, "large_poly_clip_viewport_center",		"polygon viewport clipping",	DE_ARRAY_BEGIN(largePolys),			DE_ARRAY_END(largePolys),		VIEWPORT_CENTER));
+		addChild(new TriangleCase(m_context, "large_poly_clip_viewport_corner",		"polygon viewport clipping",	DE_ARRAY_BEGIN(largePolys),			DE_ARRAY_END(largePolys),		VIEWPORT_CORNER));
+
+		addChild(new TriangleCase(m_context, "large_poly_z_clip",					"polygon z clipping",			DE_ARRAY_BEGIN(largeDepthPolys),	DE_ARRAY_END(largeDepthPolys),	VIEWPORT_WHOLE));
+		addChild(new TriangleCase(m_context, "large_poly_z_clip_viewport_center",	"polygon z clipping",			DE_ARRAY_BEGIN(largeDepthPolys),	DE_ARRAY_END(largeDepthPolys),	VIEWPORT_CENTER));
+		addChild(new TriangleCase(m_context, "large_poly_z_clip_viewport_corner",	"polygon z clipping",			DE_ARRAY_BEGIN(largeDepthPolys),	DE_ARRAY_END(largeDepthPolys),	VIEWPORT_CORNER));
+
+		addChild(new TriangleAttributeCase(m_context, "poly_attrib_clip",					"polygon clipping",		DE_ARRAY_BEGIN(attribPolys),		DE_ARRAY_END(attribPolys),		VIEWPORT_WHOLE));
+		addChild(new TriangleAttributeCase(m_context, "poly_attrib_clip_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(attribPolys),		DE_ARRAY_END(attribPolys),		VIEWPORT_CENTER));
+		addChild(new TriangleAttributeCase(m_context, "poly_attrib_clip_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(attribPolys),		DE_ARRAY_END(attribPolys),		VIEWPORT_CORNER));
+	}
+
+	// multiple polygons
+	{
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to edge
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+
+				// two vertices clipped to edges, with non-uniform w
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+
+				// three vertices clipped, Z
+				{tcu::Vec4(-0.9f, offset/4, offset,  1.0f), red, tcu::Vec4(-0.5f, -offset/4, -offset,  1.0f), yellow, tcu::Vec4(-0.2f, offset/4, offset,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_0",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_0_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_0_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+
+				// two vertices clipped to edges, with non-uniform w
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_1",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_1_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_1_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_2",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_2_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_2_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset, -2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_3",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_3_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_3_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(0.3f,  0.2f,  0.0f,  1.0f), red, tcu::Vec4( 0.3f, -0.2f,  0.0f,  1.0f), yellow, tcu::Vec4( offset, 0.0f,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_4",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_4_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_4_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.3f,  0.2f,  0.0f,  1.0f), red, tcu::Vec4(-0.3f, -0.2f,  0.0f,  1.0f), yellow, tcu::Vec4(-offset, 0.0f,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_5",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_5_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_5_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, 0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, offset,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_6",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_6_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_6_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_7",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_7_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_7_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// fill
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), white},
+				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), blue,	tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue, tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_8",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_8_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_8_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// fill
+				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), red,  tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), red,  tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), red},
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), blue, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue, tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_9",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_9_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_9_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// fill
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), white},
+				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), red,   tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), red,   tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), red},
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), blue,  tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue,  tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_10",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_10_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_10_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// fill
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), white,  tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), white,  tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), white},
+				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), red,    tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), red,    tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), red},
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), blue,   tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue,   tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), yellow, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), yellow, tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), yellow},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_11",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_11_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_11_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+	}
+}
+
+class PolyEdgesTestGroup : public TestCaseGroup
+{
+public:
+			PolyEdgesTestGroup	(Context& context);
+
+	void	init				(void);
+};
+
+PolyEdgesTestGroup::PolyEdgesTestGroup (Context& context)
+	: TestCaseGroup(context, "polygon_edge", "Polygon clipping edge tests")
+{
+}
+
+void PolyEdgesTestGroup::init (void)
+{
+	// Quads via origin
+	const struct Quad
+	{
+		tcu::Vec3 d1; // tangent
+		tcu::Vec3 d2; // bi-tangent
+	} quads[] =
+	{
+		{ tcu::Vec3( 1, 1, 1),	tcu::Vec3( 1,   -1, 1) },
+		{ tcu::Vec3( 1, 1, 1),	tcu::Vec3(-1, 1.1f, 1) },
+		{ tcu::Vec3( 1, 1, 0),	tcu::Vec3(-1,    1, 0) },
+		{ tcu::Vec3( 0, 1, 0),	tcu::Vec3( 1,    0, 0) },
+		{ tcu::Vec3( 0, 1, 0),	tcu::Vec3( 1, 0.1f, 0) },
+	};
+
+	// Quad near edge
+	const struct EdgeQuad
+	{
+		tcu::Vec3 d1;		// tangent
+		tcu::Vec3 d2;		// bi-tangent
+		tcu::Vec3 center;	// center
+	} edgeQuads[] =
+	{
+		{ tcu::Vec3( 1,     0.01f, 0    ),	tcu::Vec3( 0,      0.01f,  0),  tcu::Vec3( 0,     0.99f, 0    ) }, // edge near x-plane
+		{ tcu::Vec3( 0.01f, 1,     0    ),	tcu::Vec3( 0.01f,  0,      0),  tcu::Vec3( 0.99f, 0,     0    ) }, // edge near y-plane
+		{ tcu::Vec3( 1,     1,     0.01f),	tcu::Vec3( 0.01f,  -0.01f, 0),  tcu::Vec3( 0,     0,     0.99f) }, // edge near z-plane
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(quads); ++ndx)
+		addChild(new QuadFillTest(m_context, (std::string("quad_at_origin_") + de::toString(ndx)).c_str(), "polygon edge clipping", VIEWPORT_CENTER, quads[ndx].d1, quads[ndx].d2));
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(edgeQuads); ++ndx)
+		addChild(new QuadFillTest(m_context, (std::string("quad_near_edge_") + de::toString(ndx)).c_str(), "polygon edge clipping", VIEWPORT_CENTER, edgeQuads[ndx].d1, edgeQuads[ndx].d2, edgeQuads[ndx].center));
+
+	// Polyfan
+	addChild(new TriangleFanFillTest(m_context, "poly_fan", "polygon edge clipping", VIEWPORT_CENTER));
+}
+
+class PolyVertexClipTestGroup : public TestCaseGroup
+{
+public:
+			PolyVertexClipTestGroup	(Context& context);
+
+	void	init					(void);
+};
+
+PolyVertexClipTestGroup::PolyVertexClipTestGroup (Context& context)
+	: TestCaseGroup(context, "triangle_vertex", "Clip n vertices")
+{
+}
+
+void PolyVertexClipTestGroup::init (void)
+{
+	const float far = 30000.0f;
+	const tcu::IVec3 outside[] =
+	{
+		// outside one clipping plane
+		tcu::IVec3(-1,  0,  0),
+		tcu::IVec3( 1,  0,  0),
+		tcu::IVec3( 0,  1,  0),
+		tcu::IVec3( 0, -1,  0),
+		tcu::IVec3( 0,  0,  1),
+		tcu::IVec3( 0,  0, -1),
+
+		// outside two clipping planes
+		tcu::IVec3(-1, -1,  0),
+		tcu::IVec3( 1, -1,  0),
+		tcu::IVec3( 1,  1,  0),
+		tcu::IVec3(-1,  1,  0),
+
+		tcu::IVec3(-1,  0, -1),
+		tcu::IVec3( 1,  0, -1),
+		tcu::IVec3( 1,  0,  1),
+		tcu::IVec3(-1,  0,  1),
+
+		tcu::IVec3( 0, -1, -1),
+		tcu::IVec3( 0,  1, -1),
+		tcu::IVec3( 0,  1,  1),
+		tcu::IVec3( 0, -1,  1),
+
+		// outside three clipping planes
+		tcu::IVec3(-1, -1,  1),
+		tcu::IVec3( 1, -1,  1),
+		tcu::IVec3( 1,  1,  1),
+		tcu::IVec3(-1,  1,  1),
+
+		tcu::IVec3(-1, -1, -1),
+		tcu::IVec3( 1, -1, -1),
+		tcu::IVec3( 1,  1, -1),
+		tcu::IVec3(-1,  1, -1),
+	};
+
+	de::Random rnd(0xabcdef);
+
+	TestCaseGroup* clipOne		= new TestCaseGroup(m_context, "clip_one",		"Clip one vertex");
+	TestCaseGroup* clipTwo		= new TestCaseGroup(m_context, "clip_two",		"Clip two vertices");
+	TestCaseGroup* clipThree	= new TestCaseGroup(m_context, "clip_three",	"Clip three vertices");
+
+	addChild(clipOne);
+	addChild(clipTwo);
+	addChild(clipThree);
+
+	// Test 1 point clipped
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(outside); ++ndx)
+	{
+		const float		w0		= rnd.getFloat(0.2f, 16.0f);
+		const float		w1		= rnd.getFloat(0.2f, 16.0f);
+		const float		w2		= rnd.getFloat(0.2f, 16.0f);
+		const tcu::Vec4 white	= tcu::Vec4(    1,	    1,	1,	1);
+		const tcu::Vec3 r0		= tcu::Vec3( 0.2f,	 0.3f,	0);
+		const tcu::Vec3 r1		= tcu::Vec3(-0.3f,	-0.4f,	0);
+		const tcu::Vec3 r2		= IVec3ToVec3(outside[ndx]) * far;
+		const tcu::Vec4 p0		= tcu::Vec4(r0.x() * w0, r0.y() * w0, r0.z() * w0, w0);
+		const tcu::Vec4 p1		= tcu::Vec4(r1.x() * w1, r1.y() * w1, r1.z() * w1, w1);
+		const tcu::Vec4 p2		= tcu::Vec4(r2.x() * w2, r2.y() * w2, r2.z() * w2, w2);
+
+		const std::string name	= std::string("clip") +
+			(outside[ndx].x() > 0 ? "_pos_x" : (outside[ndx].x() < 0 ? "_neg_x" : "")) +
+			(outside[ndx].y() > 0 ? "_pos_y" : (outside[ndx].y() < 0 ? "_neg_y" : "")) +
+			(outside[ndx].z() > 0 ? "_pos_z" : (outside[ndx].z() < 0 ? "_neg_z" : ""));
+
+		const TriangleCase::TriangleData triangle =	{p0, white, p1, white, p2, white};
+
+		// don't try to test with degenerate (or almost degenerate) triangles
+		if (outside[ndx].x() == 0 && outside[ndx].y() == 0)
+			continue;
+
+		clipOne->addChild(new TriangleCase(m_context, name.c_str(), "clip one vertex", &triangle, &triangle + 1, VIEWPORT_CENTER));
+	}
+
+	// Special triangles for "clip_z" cases, default triangles is not good, since it has very small visible area => problems with MSAA
+	{
+		const tcu::Vec4 white = tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);
+
+		const TriangleCase::TriangleData posZTriangle =
+		{
+			tcu::Vec4( 0.0f, -0.7f, -0.9f, 1.0f), white,
+			tcu::Vec4( 0.8f,  0.0f, -0.7f, 1.0f), white,
+			tcu::Vec4(-0.9f,  0.9f,  3.0f, 1.0f), white
+		};
+		const TriangleCase::TriangleData negZTriangle =
+		{
+			tcu::Vec4( 0.0f, -0.7f,  0.9f, 1.0f), white,
+			tcu::Vec4( 0.4f,  0.0f,  0.7f, 1.0f), white,
+			tcu::Vec4(-0.9f,  0.9f, -3.0f, 1.0f), white
+		};
+
+		clipOne->addChild(new TriangleCase(m_context, "clip_pos_z", "clip one vertex", &posZTriangle, &posZTriangle + 1, VIEWPORT_CENTER));
+		clipOne->addChild(new TriangleCase(m_context, "clip_neg_z", "clip one vertex", &negZTriangle, &negZTriangle + 1, VIEWPORT_CENTER));
+	}
+
+	// Test 2 points clipped
+	for (int ndx1 = 0; ndx1 < DE_LENGTH_OF_ARRAY(outside); ++ndx1)
+	for (int ndx2 = ndx1 + 1; ndx2 < DE_LENGTH_OF_ARRAY(outside); ++ndx2)
+	{
+		const float		w0		= rnd.getFloat(0.2f, 16.0f);
+		const float		w1		= rnd.getFloat(0.2f, 16.0f);
+		const float		w2		= rnd.getFloat(0.2f, 16.0f);
+		const tcu::Vec4 white	= tcu::Vec4(    1,	    1,	1,	1);
+		const tcu::Vec3 r0		= tcu::Vec3( 0.2f,	 0.3f,	0);
+		const tcu::IVec3 r1		= outside[ndx1];
+		const tcu::IVec3 r2		= outside[ndx2];
+		const tcu::Vec4 p0		= tcu::Vec4(r0.x() * w0, r0.y() * w0, r0.z() * w0, w0);
+		const tcu::Vec4 p1		= tcu::Vec4(r1.x() * far * w1, r1.y() * far * w1, r1.z() * far * w1, w1);
+		const tcu::Vec4 p2		= tcu::Vec4(r2.x() * far * w2, r2.y() * far * w2, r2.z() * far * w2, w2);
+
+		const std::string name	= std::string("clip") +
+			(outside[ndx1].x() > 0 ? "_pos_x" : (outside[ndx1].x() < 0 ? "_neg_x" : "")) +
+			(outside[ndx1].y() > 0 ? "_pos_y" : (outside[ndx1].y() < 0 ? "_neg_y" : "")) +
+			(outside[ndx1].z() > 0 ? "_pos_z" : (outside[ndx1].z() < 0 ? "_neg_z" : "")) +
+			"_and" +
+			(outside[ndx2].x() > 0 ? "_pos_x" : (outside[ndx2].x() < 0 ? "_neg_x" : "")) +
+			(outside[ndx2].y() > 0 ? "_pos_y" : (outside[ndx2].y() < 0 ? "_neg_y" : "")) +
+			(outside[ndx2].z() > 0 ? "_pos_z" : (outside[ndx2].z() < 0 ? "_neg_z" : ""));
+
+		const TriangleCase::TriangleData triangle =	{p0, white, p1, white, p2, white};
+
+		if (twoPointClippedTriangleInvisible(r0, r1, r2))
+			continue;
+
+		clipTwo->addChild(new TriangleCase(m_context, name.c_str(), "clip two vertices", &triangle, &triangle + 1, VIEWPORT_CENTER));
+	}
+
+	// Test 3 points clipped
+	for (int ndx1 = 0; ndx1 < DE_LENGTH_OF_ARRAY(outside); ++ndx1)
+	for (int ndx2 = ndx1 + 1; ndx2 < DE_LENGTH_OF_ARRAY(outside); ++ndx2)
+	for (int ndx3 = ndx2 + 1; ndx3 < DE_LENGTH_OF_ARRAY(outside); ++ndx3)
+	{
+		const float		w0		= rnd.getFloat(0.2f, 16.0f);
+		const float		w1		= rnd.getFloat(0.2f, 16.0f);
+		const float		w2		= rnd.getFloat(0.2f, 16.0f);
+		const tcu::Vec4 white	= tcu::Vec4(1, 1, 1, 1);
+		const tcu::IVec3 r0		= outside[ndx1];
+		const tcu::IVec3 r1		= outside[ndx2];
+		const tcu::IVec3 r2		= outside[ndx3];
+		const tcu::Vec4 p0		= tcu::Vec4(r0.x() * far * w0, r0.y() * far * w0, r0.z() * far * w0, w0);
+		const tcu::Vec4 p1		= tcu::Vec4(r1.x() * far * w1, r1.y() * far * w1, r1.z() * far * w1, w1);
+		const tcu::Vec4 p2		= tcu::Vec4(r2.x() * far * w2, r2.y() * far * w2, r2.z() * far * w2, w2);
+
+		// ignore cases where polygon is along xz or yz planes
+		if (pointsOnLine(r0.swizzle(0, 1), r1.swizzle(0, 1), r2.swizzle(0, 1)))
+			continue;
+
+		// triangle is visible only if it intersects the origin
+		if (pointOnTriangle(tcu::IVec3(0, 0, 0), r0, r1, r2))
+		{
+			const TriangleCase::TriangleData triangle =	{p0, white, p1, white, p2, white};
+			const std::string name	= std::string("clip") +
+				(outside[ndx1].x() > 0 ? "_pos_x" : (outside[ndx1].x() < 0 ? "_neg_x" : "")) +
+				(outside[ndx1].y() > 0 ? "_pos_y" : (outside[ndx1].y() < 0 ? "_neg_y" : "")) +
+				(outside[ndx1].z() > 0 ? "_pos_z" : (outside[ndx1].z() < 0 ? "_neg_z" : "")) +
+				"_and" +
+				(outside[ndx2].x() > 0 ? "_pos_x" : (outside[ndx2].x() < 0 ? "_neg_x" : "")) +
+				(outside[ndx2].y() > 0 ? "_pos_y" : (outside[ndx2].y() < 0 ? "_neg_y" : "")) +
+				(outside[ndx2].z() > 0 ? "_pos_z" : (outside[ndx2].z() < 0 ? "_neg_z" : "")) +
+				"_and" +
+				(outside[ndx3].x() > 0 ? "_pos_x" : (outside[ndx3].x() < 0 ? "_neg_x" : "")) +
+				(outside[ndx3].y() > 0 ? "_pos_y" : (outside[ndx3].y() < 0 ? "_neg_y" : "")) +
+				(outside[ndx3].z() > 0 ? "_pos_z" : (outside[ndx3].z() < 0 ? "_neg_z" : ""));
+
+			clipThree->addChild(new TriangleCase(m_context, name.c_str(), "clip three vertices", &triangle, &triangle + 1, VIEWPORT_CENTER));
+		}
+	}
+}
+
+} // anonymous
+
+ClippingTests::ClippingTests (Context& context)
+	: TestCaseGroup(context, "clipping", "Clipping tests")
+{
+}
+
+ClippingTests::~ClippingTests (void)
+{
+}
+
+void ClippingTests::init (void)
+{
+	addChild(new PointsTestGroup		(m_context));
+	addChild(new LinesTestGroup			(m_context));
+	addChild(new PolysTestGroup			(m_context));
+	addChild(new PolyEdgesTestGroup		(m_context));
+	addChild(new PolyVertexClipTestGroup(m_context));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fClippingTests.hpp b/modules/gles2/functional/es2fClippingTests.hpp
new file mode 100644
index 0000000..b6231b2
--- /dev/null
+++ b/modules/gles2/functional/es2fClippingTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FCLIPPINGTESTS_HPP
+#define _ES2FCLIPPINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Clipping tests
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ClippingTests : public TestCaseGroup
+{
+public:
+						ClippingTests	(Context& context);
+						~ClippingTests	(void);
+	void				init			(void);
+
+private:
+	ClippingTests&		operator=		(const ClippingTests&);
+						ClippingTests	(const ClippingTests&);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FCLIPPINGTESTS_HPP
diff --git a/modules/gles2/functional/es2fColorClearTest.cpp b/modules/gles2/functional/es2fColorClearTest.cpp
new file mode 100644
index 0000000..3a86e61
--- /dev/null
+++ b/modules/gles2/functional/es2fColorClearTest.cpp
@@ -0,0 +1,331 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Screen clearing test.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fColorClearTest.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "gluRenderContext.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuVector.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "deInt32.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <vector>
+
+using tcu::RGBA;
+using tcu::Surface;
+using tcu::TestLog;
+
+using namespace std;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ColorClearCase : public TestCase
+{
+public:
+	ColorClearCase(Context& context, const char* name, int numIters, int numClearsMin, int numClearsMax, bool testAlpha, bool testScissoring, bool testColorMasks, bool firstClearFull)
+		: TestCase			(context, name, name)
+		, m_numIters		(numIters)
+		, m_numClearsMin	(numClearsMin)
+		, m_numClearsMax	(numClearsMax)
+		, m_testAlpha		(testAlpha)
+		, m_testScissoring	(testScissoring)
+		, m_testColorMasks	(testColorMasks)
+		, m_firstClearFull	(firstClearFull)
+	{
+		m_curIter = 0;
+	}
+
+	virtual ~ColorClearCase (void)
+	{
+	}
+
+	virtual IterateResult iterate (void);
+
+private:
+	const int		m_numIters;
+	const int		m_numClearsMin;
+	const int		m_numClearsMax;
+	const bool		m_testAlpha;
+	const bool		m_testScissoring;
+	const bool		m_testColorMasks;
+	const bool		m_firstClearFull;
+
+	int				m_curIter;
+};
+
+class ClearInfo
+{
+public:
+	ClearInfo (const tcu::IVec4& rect, deUint8 colorMask, tcu::RGBA color)
+		: m_rect(rect), m_colorMask(colorMask), m_color(color)
+	{
+	}
+
+	tcu::IVec4		m_rect;
+	deUint8			m_colorMask;
+	tcu::RGBA		m_color;
+};
+
+TestCase::IterateResult ColorClearCase::iterate (void)
+{
+	TestLog&					log						= m_testCtx.getLog();
+	const glw::Functions&		gl						= m_context.getRenderContext().getFunctions();
+	const tcu::RenderTarget&	renderTarget			= m_context.getRenderTarget();
+	const tcu::PixelFormat&		pixelFormat				= renderTarget.getPixelFormat();
+	const int					targetWidth				= renderTarget.getWidth();
+	const int					targetHeight			= renderTarget.getHeight();
+	const int					numPixels				= targetWidth * targetHeight;
+
+	de::Random					rnd						(deInt32Hash(m_curIter));
+	vector<deUint8>				pixelKnownChannelMask	(numPixels, 0);
+	Surface						refImage				(targetWidth, targetHeight);
+	Surface						resImage				(targetWidth, targetHeight);
+	Surface						diffImage				(targetWidth, targetHeight);
+	int							numClears				= rnd.getUint32() % (m_numClearsMax + 1 - m_numClearsMin) + m_numClearsMin;
+	std::vector<ClearInfo>		clearOps;
+
+	if (m_testScissoring)
+		gl.enable(GL_SCISSOR_TEST);
+
+	for (int clearNdx = 0; clearNdx < numClears; clearNdx++)
+	{
+		// Rectangle.
+		int clearX;
+		int clearY;
+		int clearWidth;
+		int clearHeight;
+		if (!m_testScissoring || (clearNdx == 0 && m_firstClearFull))
+		{
+			clearX		= 0;
+			clearY		= 0;
+			clearWidth	= targetWidth;
+			clearHeight	= targetHeight;
+		}
+		else
+		{
+			clearX		= (rnd.getUint32() % (2*targetWidth)) - targetWidth;
+			clearY		= (rnd.getUint32() % (2*targetHeight)) - targetHeight;
+			clearWidth	= (rnd.getUint32() % targetWidth);
+			clearHeight	= (rnd.getUint32() % targetHeight);
+		}
+		gl.scissor(clearX, clearY, clearWidth, clearHeight);
+
+		// Color.
+		int		r = (int)(rnd.getUint32() & 0xFF);
+		int		g = (int)(rnd.getUint32() & 0xFF);
+		int		b = (int)(rnd.getUint32() & 0xFF);
+		int		a = m_testAlpha ? (int)(rnd.getUint32() & 0xFF) : 0xFF;
+		RGBA	clearCol(r, g, b, a);
+		gl.clearColor(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
+
+		// Mask.
+		deUint8	clearMask;
+		if (!m_testColorMasks || (clearNdx == 0 && m_firstClearFull))
+			clearMask = 0xF;
+		else
+			clearMask = (rnd.getUint32() & 0xF);
+		gl.colorMask((clearMask&0x1) != 0, (clearMask&0x2) != 0, (clearMask&0x4) != 0, (clearMask&0x8) != 0);
+
+		// Clear & store op.
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		clearOps.push_back(ClearInfo(tcu::IVec4(clearX, clearY, clearWidth, clearHeight), clearMask, clearCol));
+
+		// Let watchdog know we're alive.
+		m_testCtx.touchWatchdog();
+	}
+
+	// Compute reference image.
+	{
+		for (int y = 0; y < targetHeight; y++)
+		{
+			std::vector<ClearInfo>	scanlineClearOps;
+
+			// Find all rectangles affecting this scanline.
+			for (int opNdx = 0; opNdx < (int)clearOps.size(); opNdx++)
+			{
+				ClearInfo& op = clearOps[opNdx];
+				if (de::inBounds(y, op.m_rect.y(), op.m_rect.y()+op.m_rect.w()))
+					scanlineClearOps.push_back(op);
+			}
+
+			// Compute reference for scanline.
+			int x = 0;
+			while (x < targetWidth)
+			{
+				tcu::RGBA	spanColor;
+				deUint8		spanKnownMask	= 0;
+				int			spanLength		= (targetWidth - x);
+
+				for (int opNdx = (int)scanlineClearOps.size() - 1; opNdx >= 0; opNdx--)
+				{
+					const ClearInfo& op = scanlineClearOps[opNdx];
+
+					if (de::inBounds(x, op.m_rect.x(), op.m_rect.x()+op.m_rect.z()) &&
+						de::inBounds(y, op.m_rect.y(), op.m_rect.y()+op.m_rect.w()))
+					{
+						// Compute span length until end of given rectangle.
+						spanLength = deMin32(spanLength, op.m_rect.x() + op.m_rect.z() - x);
+
+						tcu::RGBA	clearCol	= op.m_color;
+						deUint8		clearMask	= op.m_colorMask;
+						if ((clearMask & 0x1) && !(spanKnownMask & 0x1)) spanColor.setRed(clearCol.getRed());
+						if ((clearMask & 0x2) && !(spanKnownMask & 0x2)) spanColor.setGreen(clearCol.getGreen());
+						if ((clearMask & 0x4) && !(spanKnownMask & 0x4)) spanColor.setBlue(clearCol.getBlue());
+						if ((clearMask & 0x8) && !(spanKnownMask & 0x8)) spanColor.setAlpha(clearCol.getAlpha());
+						spanKnownMask |= clearMask;
+
+						// Break if have all colors.
+						if (spanKnownMask == 0xF)
+							break;
+					}
+					else if (op.m_rect.x() > x)
+						spanLength = deMin32(spanLength, op.m_rect.x() - x);
+				}
+
+				// Set reference alpha channel to 0xFF, if no alpha in target.
+				if (pixelFormat.alphaBits == 0)
+					spanColor.setAlpha(0xFF);
+
+				// Fill the span.
+				for (int ndx = 0; ndx < spanLength; ndx++)
+				{
+					refImage.setPixel(x + ndx, y, spanColor);
+					pixelKnownChannelMask[y*targetWidth + x + ndx] |= spanKnownMask;
+				}
+
+				x += spanLength;
+			}
+		}
+	}
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, resImage.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels");
+
+	// Compute difference image.
+	RGBA colorThreshold = pixelFormat.getColorThreshold();
+	RGBA matchColor(0, 255, 0, 255);
+	RGBA diffColor(255, 0, 0, 255);
+	RGBA maxDiff(0, 0, 0, 0);
+	bool isImageOk = true;
+
+	if (gl.isEnabled(GL_DITHER))
+	{
+		colorThreshold.setRed(colorThreshold.getRed() + 1);
+		colorThreshold.setGreen(colorThreshold.getGreen() + 1);
+		colorThreshold.setBlue(colorThreshold.getBlue() + 1);
+		colorThreshold.setAlpha(colorThreshold.getAlpha() + 1);
+	}
+
+	for (int y = 0; y < targetHeight; y++)
+	for (int x = 0; x < targetWidth; x++)
+	{
+		int			offset		= (y*targetWidth + x);
+		RGBA		refRGBA		= refImage.getPixel(x, y);
+		RGBA		resRGBA		= resImage.getPixel(x, y);
+		deUint8		colMask		= pixelKnownChannelMask[offset];
+		RGBA		diff		= computeAbsDiffMasked(refRGBA, resRGBA, colMask);
+		bool		isPixelOk	= diff.isBelowThreshold(colorThreshold);
+
+		diffImage.setPixel(x, y, isPixelOk ? matchColor : diffColor);
+
+		isImageOk	= isImageOk && isPixelOk;
+		maxDiff		= max(maxDiff, diff);
+	}
+
+	if (isImageOk)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Image comparison failed, max diff = " << maxDiff << ", threshold = " << colorThreshold << tcu::TestLog::EndMessage;
+
+		log << TestLog::ImageSet("Result", "Resulting framebuffer")
+			<< TestLog::Image("Result",		"Resulting framebuffer",	resImage)
+			<< TestLog::Image("Reference",	"Reference image",			refImage)
+			<< TestLog::Image("DiffMask",	"Failing pixels",			diffImage)
+			<< TestLog::EndImageSet;
+		return TestCase::STOP;
+	}
+
+	bool isFinal = (++m_curIter == m_numIters);
+
+	// On final frame, dump images.
+	if (isFinal)
+	{
+		log << TestLog::ImageSet("Result", "Resulting framebuffer")
+			<< TestLog::Image("Result",		"Resulting framebuffer",	resImage)
+			<< TestLog::EndImageSet;
+	}
+
+	return isFinal ? TestCase::STOP : TestCase::CONTINUE;
+}
+
+ColorClearTest::ColorClearTest (Context& context) : TestCaseGroup(context, "color_clear", "Color Clear Tests")
+{
+}
+
+ColorClearTest::~ColorClearTest (void)
+{
+}
+
+void ColorClearTest::init (void)
+{
+	//										name					iters,	#..#,		alpha?,	scissor,masks,	1stfull?
+	addChild(new ColorClearCase(m_context, "single_rgb",			30,		1,3,		false,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "single_rgba",			30,		1,3,		true,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "multiple_rgb",			15,		4,20,		false,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "multiple_rgba",			15,		4,20,		true,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "long_rgb",				2,		100,500,	false,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "long_rgba",				2,		100,500,	true,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "subclears_rgb",			15,		4,30,		false,	false,	false,	false	));
+	addChild(new ColorClearCase(m_context, "subclears_rgba",		15,		4,30,		true,	false,	false,	false	));
+	addChild(new ColorClearCase(m_context, "short_scissored_rgb",	30,		2,4,		false,	true,	false,	true	));
+	addChild(new ColorClearCase(m_context, "scissored_rgb",			15,		4,30,		false,	true,	false,	true	));
+	addChild(new ColorClearCase(m_context, "scissored_rgba",		15,		4,30,		true,	true,	false,	true	));
+	addChild(new ColorClearCase(m_context, "masked_rgb",			15,		4,30,		false,	true,	false,	true	));
+	addChild(new ColorClearCase(m_context, "masked_rgba",			15,		4,30,		true,	true,	false,	true	));
+	addChild(new ColorClearCase(m_context, "masked_scissored_rgb",	15,		4,30,		false,	true,	true,	true	));
+	addChild(new ColorClearCase(m_context, "masked_scissored_rgba",	15,		4,30,		true,	true,	true,	true	));
+	addChild(new ColorClearCase(m_context, "complex_rgb",			15,		5,50,		false,	true,	true,	false	));
+	addChild(new ColorClearCase(m_context, "complex_rgba",			15,		5,50,		true,	true,	true,	false	));
+	addChild(new ColorClearCase(m_context, "long_masked_rgb",		2,		100,500,	false,	true,	true,	true	));
+	addChild(new ColorClearCase(m_context, "long_masked_rgba",		2,		100,500,	true,	true,	true,	true	));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fColorClearTest.hpp b/modules/gles2/functional/es2fColorClearTest.hpp
new file mode 100644
index 0000000..276c6fe
--- /dev/null
+++ b/modules/gles2/functional/es2fColorClearTest.hpp
@@ -0,0 +1,55 @@
+#ifndef _ES2FCOLORCLEARTEST_HPP
+#define _ES2FCOLORCLEARTEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Color buffer clear test.
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+struct ClearTestCaseInfo;
+
+class ColorClearTest : public TestCaseGroup
+{
+public:
+							ColorClearTest			(Context& context);
+	virtual					~ColorClearTest			(void);
+
+	virtual void			init					(void);
+
+private:
+							ColorClearTest			(const ColorClearTest&);		// not allowed!
+	ColorClearTest&			operator=				(const ColorClearTest&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FCOLORCLEARTEST_HPP
diff --git a/modules/gles2/functional/es2fDefaultVertexAttributeTests.cpp b/modules/gles2/functional/es2fDefaultVertexAttributeTests.cpp
new file mode 100644
index 0000000..b2672bb
--- /dev/null
+++ b/modules/gles2/functional/es2fDefaultVertexAttributeTests.cpp
@@ -0,0 +1,534 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Default vertex attribute test
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fDefaultVertexAttributeTests.hpp"
+#include "tcuVector.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deMath.h"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+#include <limits>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace
+{
+
+static const int s_valueRange = 10;
+
+static const char* const s_passThroughFragmentShaderSource =	"varying mediump vec4 v_color;\n"
+																"void main (void)\n"
+																"{\n"
+																"	gl_FragColor = v_color;\n"
+																"}\n";
+
+template <typename T1, int S1, typename T2, int S2>
+tcu::Vector<T1, S1> convertToTypeVec (const tcu::Vector<T2, S2>& v)
+{
+	tcu::Vector<T1, S1> retVal;
+
+	for (int ndx = 0; ndx < S1; ++ndx)
+		retVal[ndx] = T1(0);
+
+	if (S1 == 4)
+		retVal[3] = T1(1);
+
+	for (int ndx = 0; ndx < de::min(S1, S2); ++ndx)
+		retVal[ndx] = T1(v[ndx]);
+
+	return retVal;
+}
+
+class FloatLoader
+{
+public:
+	virtual				~FloatLoader	(void) {};
+
+	// returns the value loaded
+	virtual tcu::Vec4	load			(glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const = 0;
+};
+
+#define GEN_DIRECT_FLOAT_LOADER(TYPE, COMPS, TYPECODE, CASENAME, VALUES)				\
+	class LoaderVertexAttrib##COMPS##TYPECODE : public FloatLoader						\
+	{																					\
+	public:																				\
+		enum																			\
+		{																				\
+			NORMALIZING = 0,															\
+		};																				\
+		enum																			\
+		{																				\
+			COMPONENTS = COMPS															\
+		};																				\
+		typedef TYPE Type;																\
+																						\
+		tcu::Vec4 load (glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const	\
+		{																				\
+			tcu::Vector<TYPE, COMPONENTS> value;										\
+			value = convertToTypeVec<Type, COMPONENTS>(v);								\
+																						\
+			gl.glVertexAttrib ##COMPS ##TYPECODE VALUES;								\
+			return convertToTypeVec<float, 4>(value);									\
+		}																				\
+																						\
+		static const char* getCaseName (void)											\
+		{																				\
+			return CASENAME;															\
+		}																				\
+																						\
+		static const char* getName (void)												\
+		{																				\
+			return "VertexAttrib" #COMPS #TYPECODE;										\
+		}																				\
+	}
+
+#define GEN_INDIRECT_FLOAT_LOADER(TYPE, COMPS, TYPECODE, CASENAME)						\
+	class LoaderVertexAttrib##COMPS##TYPECODE : public FloatLoader						\
+	{																					\
+	public:																				\
+		enum																			\
+		{																				\
+			NORMALIZING = 0,															\
+		};																				\
+		enum																			\
+		{																				\
+			COMPONENTS = COMPS															\
+		};																				\
+		typedef TYPE Type;																\
+																						\
+		tcu::Vec4 load (glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const	\
+		{																				\
+			tcu::Vector<TYPE, COMPONENTS> value;										\
+			value = convertToTypeVec<Type, COMPONENTS>(v);								\
+																						\
+			gl.glVertexAttrib ##COMPS ##TYPECODE (index, value.getPtr());				\
+			return convertToTypeVec<float, 4>(value);									\
+		}																				\
+																						\
+		static const char* getCaseName (void)											\
+		{																				\
+			return CASENAME;															\
+		}																				\
+																						\
+		static const char* getName (void)												\
+		{																				\
+			return "VertexAttrib" #COMPS #TYPECODE;										\
+		}																				\
+	}
+
+GEN_DIRECT_FLOAT_LOADER(float, 1, f, "vertex_attrib_1f", (index, value.x()));
+GEN_DIRECT_FLOAT_LOADER(float, 2, f, "vertex_attrib_2f", (index, value.x(), value.y()));
+GEN_DIRECT_FLOAT_LOADER(float, 3, f, "vertex_attrib_3f", (index, value.x(), value.y(), value.z()));
+GEN_DIRECT_FLOAT_LOADER(float, 4, f, "vertex_attrib_4f", (index, value.x(), value.y(), value.z(), value.w()));
+
+GEN_INDIRECT_FLOAT_LOADER(float, 1, fv, "vertex_attrib_1fv");
+GEN_INDIRECT_FLOAT_LOADER(float, 2, fv, "vertex_attrib_2fv");
+GEN_INDIRECT_FLOAT_LOADER(float, 3, fv, "vertex_attrib_3fv");
+GEN_INDIRECT_FLOAT_LOADER(float, 4, fv, "vertex_attrib_4fv");
+
+class AttributeCase : public TestCase
+{
+									AttributeCase			(Context& ctx, const char* name, const char* desc, const char* funcName, bool normalizing, bool useNegative, glu::DataType dataType);
+public:
+	template<typename LoaderType>
+	static AttributeCase*			create					(Context& ctx, glu::DataType dataType);
+									~AttributeCase			(void);
+
+private:
+	void							init					(void);
+	void							deinit					(void);
+	IterateResult					iterate					(void);
+
+	glu::DataType					getTargetType			(void) const;
+	std::string						genVertexSource			(void) const;
+	bool							renderWithValue			(const tcu::Vec4& v);
+	tcu::Vec4						computeColor			(const tcu::Vec4& value);
+	bool							verifyUnicoloredBuffer	(const tcu::Surface& scene, const tcu::Vec4& refValue);
+
+	const bool						m_normalizing;
+	const bool						m_useNegativeValues;
+	const char* const				m_funcName;
+	const glu::DataType				m_dataType;
+	const FloatLoader*				m_loader;
+	glu::ShaderProgram*				m_program;
+	deUint32						m_bufID;
+	bool							m_allIterationsPassed;
+	int								m_iteration;
+
+	enum
+	{
+		RENDER_SIZE = 32
+	};
+};
+
+AttributeCase::AttributeCase (Context& ctx, const char* name, const char* desc, const char* funcName, bool normalizing, bool useNegative, glu::DataType dataType)
+	: TestCase				(ctx, name, desc)
+	, m_normalizing			(normalizing)
+	, m_useNegativeValues	(useNegative)
+	, m_funcName			(funcName)
+	, m_dataType			(dataType)
+	, m_loader				(DE_NULL)
+	, m_program				(DE_NULL)
+	, m_bufID				(0)
+	, m_allIterationsPassed	(true)
+	, m_iteration			(0)
+{
+}
+
+template<typename LoaderType>
+AttributeCase* AttributeCase::create (Context& ctx, glu::DataType dataType)
+{
+	AttributeCase* retVal = new AttributeCase(ctx,
+											  LoaderType::getCaseName(),
+											  (std::string("Test ") + LoaderType::getName()).c_str(),
+											  LoaderType::getName(),
+											  LoaderType::NORMALIZING != 0,
+											  std::numeric_limits<typename LoaderType::Type>::is_signed,
+											  dataType);
+	retVal->m_loader = new LoaderType();
+	return retVal;
+}
+
+AttributeCase::~AttributeCase (void)
+{
+	deinit();
+}
+
+void AttributeCase::init (void)
+{
+	if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE)
+		throw tcu::NotSupportedError("Render target must be at least " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE));
+
+	// log test info
+
+	{
+		const float			maxRange		= (m_normalizing) ? (1.0f) : (s_valueRange);
+		const float			minRange		= (m_useNegativeValues) ? (-maxRange) : (0.0f);
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Loading attribute values using " << m_funcName << "\n"
+			<< "Attribute type: " << glu::getDataTypeName(m_dataType) << "\n"
+			<< "Attribute value range: [" << minRange << ", " << maxRange << "]"
+			<< tcu::TestLog::EndMessage;
+	}
+
+	// gen shader and base quad
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource()) << glu::FragmentSource(s_passThroughFragmentShaderSource));
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build program");
+
+	{
+		const tcu::Vec4 fullscreenQuad[] =
+		{
+			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),
+		};
+
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.genBuffers(1, &m_bufID);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_bufID);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "fill buffer");
+	}
+}
+
+void AttributeCase::deinit (void)
+{
+	delete m_loader;
+	m_loader = DE_NULL;
+
+	delete m_program;
+	m_program = DE_NULL;
+
+	if (m_bufID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_bufID);
+		m_bufID = 0;
+	}
+}
+
+AttributeCase::IterateResult AttributeCase::iterate (void)
+{
+	static const tcu::Vec4 testValues[] =
+	{
+		tcu::Vec4(0.0f, 0.5f, 0.2f, 1.0f),
+		tcu::Vec4(0.1f, 0.7f, 1.0f, 0.6f),
+		tcu::Vec4(0.4f, 0.2f, 0.0f, 0.5f),
+		tcu::Vec4(0.5f, 0.0f, 0.9f, 0.1f),
+		tcu::Vec4(0.6f, 0.2f, 0.2f, 0.9f),
+		tcu::Vec4(0.9f, 1.0f, 0.0f, 0.0f),
+		tcu::Vec4(1.0f, 0.5f, 0.3f, 0.8f),
+	};
+
+	const tcu::ScopedLogSection section(m_testCtx.getLog(), "Iteration", "Iteration " + de::toString(m_iteration+1) + "/" + de::toString(DE_LENGTH_OF_ARRAY(testValues)));
+
+	// Test normalizing transfers with whole range, non-normalizing with up to s_valueRange
+	const tcu::Vec4 testValue = ((m_useNegativeValues) ? (testValues[m_iteration] * 2.0f - tcu::Vec4(1.0f)) : (testValues[m_iteration])) * ((m_normalizing) ? (1.0f) : ((float)s_valueRange));
+
+	if (!renderWithValue(testValue))
+		m_allIterationsPassed = false;
+
+	// continue
+
+	if (++m_iteration < DE_LENGTH_OF_ARRAY(testValues))
+		return CONTINUE;
+
+	if (m_allIterationsPassed)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected values");
+
+	return STOP;
+}
+
+std::string AttributeCase::genVertexSource (void) const
+{
+	const int			vectorSize	= (glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeMatrixNumRows(m_dataType)) : (glu::isDataTypeVector(m_dataType)) ? (glu::getDataTypeScalarSize(m_dataType)) : (-1);
+	const char* const	vectorType	= glu::getDataTypeName((glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeVector(glu::TYPE_FLOAT, vectorSize)) : (glu::isDataTypeVector(m_dataType)) ? (glu::getDataTypeVector(glu::TYPE_FLOAT, vectorSize)) : (glu::TYPE_FLOAT));
+	const int			components	= (glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeMatrixNumRows(m_dataType)) : (glu::getDataTypeScalarSize(m_dataType));
+	std::ostringstream	buf;
+
+	buf <<	"attribute highp vec4 a_position;\n"
+			"attribute highp " << glu::getDataTypeName(m_dataType) << " a_value;\n"
+			"varying highp vec4 v_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"\n";
+
+	if (m_normalizing)
+		buf << "	highp " << vectorType << " normalizedValue = " << ((glu::getDataTypeScalarType(m_dataType) == glu::TYPE_FLOAT) ? ("") : (vectorType)) << "(a_value" << ((glu::isDataTypeMatrix(m_dataType)) ? ("[1]") : ("")) << ");\n";
+	else
+		buf << "	highp " << vectorType << " normalizedValue = " << ((glu::getDataTypeScalarType(m_dataType) == glu::TYPE_FLOAT) ? ("") : (vectorType)) << "(a_value" << ((glu::isDataTypeMatrix(m_dataType)) ? ("[1]") : ("")) << ") / float(" << s_valueRange << ");\n";
+
+	if (m_useNegativeValues)
+		buf << "	highp " << vectorType << " positiveNormalizedValue = (normalizedValue + " << vectorType << "(1.0)) / 2.0;\n";
+	else
+		buf << "	highp " << vectorType << " positiveNormalizedValue = normalizedValue;\n";
+
+	if (components == 1)
+		buf << "	v_color = vec4(positiveNormalizedValue, 0.0, 0.0, 1.0);\n";
+	else if (components == 2)
+		buf << "	v_color = vec4(positiveNormalizedValue.xy, 0.0, 1.0);\n";
+	else if (components == 3)
+		buf << "	v_color = vec4(positiveNormalizedValue.xyz, 1.0);\n";
+	else if (components == 4)
+		buf << "	v_color = vec4((positiveNormalizedValue.xy + positiveNormalizedValue.zz) / 2.0, positiveNormalizedValue.w, 1.0);\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "}\n";
+
+	return buf.str();
+}
+
+bool AttributeCase::renderWithValue (const tcu::Vec4& v)
+{
+	glu::CallLogWrapper	gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	gl.enableLogging(true);
+
+	const int			positionIndex	= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
+	const int			valueIndex		= gl.glGetAttribLocation(m_program->getProgram(), "a_value");
+	tcu::Surface		dest			(RENDER_SIZE, RENDER_SIZE);
+	tcu::Vec4			loadedValue;
+
+	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	gl.glClear(GL_COLOR_BUFFER_BIT);
+	gl.glViewport(0, 0, RENDER_SIZE, RENDER_SIZE);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup");
+
+	gl.glBindBuffer(GL_ARRAY_BUFFER, m_bufID);
+	gl.glVertexAttribPointer(positionIndex, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.glEnableVertexAttribArray(positionIndex);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "position va");
+
+	// transfer test value. Load to the second column in the matrix case
+	loadedValue = m_loader->load(gl, (glu::isDataTypeMatrix(m_dataType)) ? (valueIndex + 1) : (valueIndex), v);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "default va");
+
+	gl.glUseProgram(m_program->getProgram());
+	gl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+	gl.glUseProgram(0);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dest.getAccess());
+
+	// check whole result is colored correctly
+	return verifyUnicoloredBuffer(dest, computeColor(loadedValue));
+}
+
+tcu::Vec4 AttributeCase::computeColor (const tcu::Vec4& value)
+{
+	const tcu::Vec4 normalizedValue			= value / ((m_normalizing) ? (1.0f) : ((float)s_valueRange));
+	const tcu::Vec4 positiveNormalizedValue = ((m_useNegativeValues) ? ((normalizedValue + tcu::Vec4(1.0f)) / 2.0f) : (normalizedValue));
+	const int		components				= (glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeMatrixNumRows(m_dataType)) : (glu::getDataTypeScalarSize(m_dataType));
+
+	if (components == 1)
+		return tcu::Vec4(positiveNormalizedValue.x(), 0.0f, 0.0f, 1.0f);
+	else if (components == 2)
+		return tcu::Vec4(positiveNormalizedValue.x(), positiveNormalizedValue.y(), 0.0f, 1.0f);
+	else if (components == 3)
+		return tcu::Vec4(positiveNormalizedValue.x(), positiveNormalizedValue.y(), positiveNormalizedValue.z(), 1.0f);
+	else if (components == 4)
+		return tcu::Vec4((positiveNormalizedValue.x() + positiveNormalizedValue.z()) / 2.0f, (positiveNormalizedValue.y() + positiveNormalizedValue.z()) / 2.0f, positiveNormalizedValue.w(), 1.0f);
+	else
+		DE_ASSERT(DE_FALSE);
+
+	return tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+bool AttributeCase::verifyUnicoloredBuffer (const tcu::Surface& scene, const tcu::Vec4& refValue)
+{
+	tcu::Surface	errorMask		(RENDER_SIZE, RENDER_SIZE);
+	const tcu::RGBA	refColor		(refValue);
+	const int		resultThreshold	= 2;
+	const tcu::RGBA	colorThreshold	= m_context.getRenderTarget().getPixelFormat().getColorThreshold() * resultThreshold;
+	bool			error			= false;
+
+	tcu::RGBA		exampleColor;
+	tcu::IVec2		examplePos;
+
+	tcu::clear(errorMask.getAccess(), tcu::RGBA::green.toIVec());
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying rendered image. Expecting color " << refColor << ", threshold " << colorThreshold << tcu::TestLog::EndMessage;
+
+	for (int y = 0; y < RENDER_SIZE; ++y)
+	for (int x = 0; x < RENDER_SIZE; ++x)
+	{
+		const tcu::RGBA color = scene.getPixel(x, y);
+
+		if (de::abs(color.getRed()   - refColor.getRed())   > colorThreshold.getRed()   ||
+			de::abs(color.getGreen() - refColor.getGreen()) > colorThreshold.getGreen() ||
+			de::abs(color.getBlue()  - refColor.getBlue())  > colorThreshold.getBlue())
+		{
+			// first error
+			if (!error)
+			{
+				exampleColor = color;
+				examplePos = tcu::IVec2(x, y);
+			}
+
+			error = true;
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+		}
+	}
+
+	if (!error)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Rendered image is valid." << tcu::TestLog::EndMessage;
+	else
+	{
+		m_testCtx.getLog()	<< tcu::TestLog::Message
+							<< "Found invalid pixel(s).\n"
+							<< "Pixel at (" << examplePos.x() << ", " << examplePos.y() << ") color: " << exampleColor
+							<< tcu::TestLog::EndMessage
+							<< tcu::TestLog::ImageSet("Result", "Render result")
+							<< tcu::TestLog::Image("Result", "Result", scene)
+							<< tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask)
+							<< tcu::TestLog::EndImageSet;
+	}
+
+	return !error;
+}
+
+} // anonymous
+
+DefaultVertexAttributeTests::DefaultVertexAttributeTests (Context& context)
+	: TestCaseGroup(context, "default_vertex_attrib", "Test default vertex attributes")
+{
+}
+
+DefaultVertexAttributeTests::~DefaultVertexAttributeTests (void)
+{
+}
+
+void DefaultVertexAttributeTests::init (void)
+{
+	struct Target
+	{
+		const char*		name;
+		glu::DataType	dataType;
+		bool			reducedTestSets;	// !< use reduced coverage
+	};
+
+	static const Target floatTargets[] =
+	{
+		{ "float",	glu::TYPE_FLOAT,		false	},
+		{ "vec2",	glu::TYPE_FLOAT_VEC2,	true	},
+		{ "vec3",	glu::TYPE_FLOAT_VEC3,	true	},
+		{ "vec4",	glu::TYPE_FLOAT_VEC4,	false	},
+		{ "mat2",	glu::TYPE_FLOAT_MAT2,	true	},
+		{ "mat3",	glu::TYPE_FLOAT_MAT3,	true	},
+		{ "mat4",	glu::TYPE_FLOAT_MAT4,	false	},
+	};
+
+	// float targets
+
+	for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(floatTargets); ++targetNdx)
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(m_testCtx, floatTargets[targetNdx].name, (std::string("test with ") + floatTargets[targetNdx].name).c_str());
+		const bool					fullSet	= !floatTargets[targetNdx].reducedTestSets;
+
+#define ADD_CASE(X) group->addChild(AttributeCase::create<X>(m_context, floatTargets[targetNdx].dataType))
+#define ADD_REDUCED_CASE(X)	if (fullSet) ADD_CASE(X)
+
+		ADD_CASE		(LoaderVertexAttrib1f);
+		ADD_REDUCED_CASE(LoaderVertexAttrib2f);
+		ADD_REDUCED_CASE(LoaderVertexAttrib3f);
+		ADD_CASE		(LoaderVertexAttrib4f);
+
+		ADD_CASE		(LoaderVertexAttrib1fv);
+		ADD_REDUCED_CASE(LoaderVertexAttrib2fv);
+		ADD_REDUCED_CASE(LoaderVertexAttrib3fv);
+		ADD_CASE		(LoaderVertexAttrib4fv);
+
+#undef ADD_CASE
+#undef ADD_REDUCED_CASE
+
+		addChild(group);
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fDefaultVertexAttributeTests.hpp b/modules/gles2/functional/es2fDefaultVertexAttributeTests.hpp
new file mode 100644
index 0000000..5ca0fc0
--- /dev/null
+++ b/modules/gles2/functional/es2fDefaultVertexAttributeTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FDEFAULTVERTEXATTRIBUTETESTS_HPP
+#define _ES2FDEFAULTVERTEXATTRIBUTETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Default vertex attribute test
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class DefaultVertexAttributeTests : public TestCaseGroup
+{
+public:
+									DefaultVertexAttributeTests		(Context& context);
+									~DefaultVertexAttributeTests	(void);
+
+	void							init							(void);
+
+private:
+									DefaultVertexAttributeTests		(const DefaultVertexAttributeTests& other);
+	DefaultVertexAttributeTests&	operator=						(const DefaultVertexAttributeTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FDEFAULTVERTEXATTRIBUTETESTS_HPP
diff --git a/modules/gles2/functional/es2fDepthRangeTests.cpp b/modules/gles2/functional/es2fDepthRangeTests.cpp
new file mode 100644
index 0000000..4668d17
--- /dev/null
+++ b/modules/gles2/functional/es2fDepthRangeTests.cpp
@@ -0,0 +1,447 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief glDepthRangef() tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fDepthRangeTests.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+#include "glw.h"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+enum
+{
+	VISUALIZE_DEPTH_STEPS	= 32 //!< Number of depth steps in visualization
+};
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::TestLog;
+using std::string;
+using std::vector;
+
+static const char* s_vertexShaderSrc =
+	"attribute highp vec4 a_position;\n"
+	"attribute highp vec2 a_coord;\n"
+	"void main (void)\n"
+	"{\n"
+	"	gl_Position = a_position;\n"
+	"}\n";
+static const char* s_fragmentShaderSrc =
+	"uniform mediump vec4 u_color;\n"
+	"void main (void)\n"
+	"{\n"
+	"	gl_FragColor = u_color;\n"
+	"}\n";
+
+template <typename T>
+static inline bool compare (deUint32 func, T a, T b)
+{
+	switch (func)
+	{
+		case GL_NEVER:		return false;
+		case GL_ALWAYS:		return true;
+		case GL_LESS:		return a < b;
+		case GL_LEQUAL:		return a <= b;
+		case GL_EQUAL:		return a == b;
+		case GL_NOTEQUAL:	return a != b;
+		case GL_GEQUAL:		return a >= b;
+		case GL_GREATER:	return a > b;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return false;
+	}
+}
+
+inline float triangleInterpolate (const float v0, const float v1, const float v2, const float x, const float y)
+{
+	return v0 + (v2-v0)*x + (v1-v0)*y;
+}
+
+inline float triQuadInterpolate (const float x, const float y, const tcu::Vec4& quad)
+{
+	// \note Top left fill rule.
+	if (x + y < 1.0f)
+		return triangleInterpolate(quad.x(), quad.y(), quad.z(), x, y);
+	else
+		return triangleInterpolate(quad.w(), quad.z(), quad.y(), 1.0f-x, 1.0f-y);
+}
+
+inline float depthRangeTransform (const float zd, const float zNear, const float zFar)
+{
+	const float	cNear	= de::clamp(zNear, 0.0f, 1.0f);
+	const float	cFar	= de::clamp(zFar, 0.0f, 1.0f);
+	return ((cFar - cNear)/2.0f) * zd + (cNear + cFar)/2.0f;
+}
+
+class DepthRangeCompareCase : public TestCase
+{
+public:
+							DepthRangeCompareCase	(Context& context, const char* name, const char* desc, const tcu::Vec4& depthCoord, const float zNear, const float zFar, const deUint32 compareFunc);
+							~DepthRangeCompareCase	(void);
+
+	IterateResult			iterate					(void);
+
+private:
+	const tcu::Vec4			m_depthCoord;
+	const float				m_zNear;
+	const float				m_zFar;
+	const deUint32			m_compareFunc;
+};
+
+DepthRangeCompareCase::DepthRangeCompareCase (Context& context, const char* name, const char* desc, const tcu::Vec4& depthCoord, const float zNear, const float zFar, const deUint32 compareFunc)
+	: TestCase			(context, name, desc)
+	, m_depthCoord		(depthCoord)
+	, m_zNear			(zNear)
+	, m_zFar			(zFar)
+	, m_compareFunc		(compareFunc)
+{
+}
+
+DepthRangeCompareCase::~DepthRangeCompareCase (void)
+{
+}
+
+DepthRangeCompareCase::IterateResult DepthRangeCompareCase::iterate (void)
+{
+	TestLog&					log					= m_testCtx.getLog();
+	de::Random					rnd					(deStringHash(getName()));
+	const tcu::RenderTarget&	renderTarget		= m_context.getRenderContext().getRenderTarget();
+	const int					viewportW			= de::min(128, renderTarget.getWidth());
+	const int					viewportH			= de::min(128, renderTarget.getHeight());
+	const int					viewportX			= rnd.getInt(0, renderTarget.getWidth()-viewportW);
+	const int					viewportY			= rnd.getInt(0, renderTarget.getHeight()-viewportH);
+	tcu::Surface				renderedFrame		(viewportW, viewportH);
+	tcu::Surface				referenceFrame		(viewportW, viewportH);
+	const float					constDepth			= 0.1f;
+
+	if (renderTarget.getDepthBits() == 0)
+		throw tcu::NotSupportedError("Depth buffer is required", "", __FILE__, __LINE__);
+
+	const glu::ShaderProgram	program				(m_context.getRenderContext(), glu::makeVtxFragSources(s_vertexShaderSrc, s_fragmentShaderSrc));
+
+	if (!program.isOk())
+	{
+		log << program;
+		TCU_FAIL("Compile failed");
+	}
+
+	const int					colorLoc			= glGetUniformLocation(program.getProgram(), "u_color");
+	const int					posLoc				= glGetAttribLocation(program.getProgram(), "a_position");
+
+	m_testCtx.getLog() << TestLog::Message << "glDepthRangef(" << m_zNear << ", " << m_zFar << ")" << TestLog::EndMessage;
+
+	glViewport(viewportX, viewportY, viewportW, viewportH);
+	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	glEnable(GL_DEPTH_TEST);
+	glUseProgram(program.getProgram());
+	glEnableVertexAttribArray(posLoc);
+
+	static const deUint16 quadIndices[] = { 0, 1, 2, 2, 1, 3 };
+
+	// Fill viewport with 2 quads - one with constant depth and another with d = [-1..1]
+	{
+		static const float constDepthCoord[] =
+		{
+			-1.0f, -1.0f, constDepth, 1.0f,
+			-1.0f, +1.0f, constDepth, 1.0f,
+			 0.0f, -1.0f, constDepth, 1.0f,
+			 0.0f, +1.0f, constDepth, 1.0f
+		};
+		static const float varyingDepthCoord[] =
+		{
+			 0.0f, -1.0f, +1.0f, 1.0f,
+			 0.0f, +1.0f,  0.0f, 1.0f,
+			+1.0f, -1.0f,  0.0f, 1.0f,
+			+1.0f, +1.0f, -1.0f, 1.0f
+		};
+
+		glUniform4f(colorLoc, 0.0f, 0.0f, 1.0f, 1.0f);
+		glDepthFunc(GL_ALWAYS);
+
+		glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &constDepthCoord);
+		glDrawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(quadIndices), GL_UNSIGNED_SHORT, &quadIndices[0]);
+
+		glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &varyingDepthCoord);
+		glDrawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(quadIndices), GL_UNSIGNED_SHORT, &quadIndices[0]);
+
+		GLU_CHECK();
+	}
+
+	// Render with depth test.
+	{
+		const float position[] =
+		{
+			-1.0f, -1.0f, m_depthCoord[0], 1.0f,
+			-1.0f, +1.0f, m_depthCoord[1], 1.0f,
+			+1.0f, -1.0f, m_depthCoord[2], 1.0f,
+			+1.0f, +1.0f, m_depthCoord[3], 1.0f
+		};
+
+		glDepthRangef(m_zNear, m_zFar);
+		glDepthFunc(m_compareFunc);
+		glUniform4f(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+
+		glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &position[0]);
+		glDrawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(quadIndices), GL_UNSIGNED_SHORT, &quadIndices[0]);
+
+		GLU_CHECK();
+	}
+
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedFrame.getAccess());
+
+	// Render reference.
+	for (int y = 0; y < referenceFrame.getHeight(); y++)
+	{
+		float	yf		= ((float)y + 0.5f) / referenceFrame.getHeight();
+		int		half	= de::clamp((int)((float)referenceFrame.getWidth()*0.5f + 0.5f), 0, referenceFrame.getWidth());
+
+		// Fill left half - comparison to constant 0.5
+		for (int x = 0; x < half; x++)
+		{
+			float	xf		= ((float)x + 0.5f) / (float)referenceFrame.getWidth();
+			float	d		= depthRangeTransform(triQuadInterpolate(xf, yf, m_depthCoord), m_zNear, m_zFar);
+			bool	dpass	= compare(m_compareFunc, d, constDepth*0.5f + 0.5f);
+
+			referenceFrame.setPixel(x, y, dpass ? tcu::RGBA::green : tcu::RGBA::blue);
+		}
+
+		// Fill right half - comparison to interpolated depth
+		for (int x = half; x < referenceFrame.getWidth(); x++)
+		{
+			float	xf		= ((float)x + 0.5f) / (float)referenceFrame.getWidth();
+			float	xh		= ((float)x - half + 0.5f) / (float)(referenceFrame.getWidth()-half);
+			float	rd		= 1.0f - (xh + yf) * 0.5f;
+			float	d		= depthRangeTransform(triQuadInterpolate(xf, yf, m_depthCoord), m_zNear, m_zFar);
+			bool	dpass	= compare(m_compareFunc, d, rd);
+
+			referenceFrame.setPixel(x, y, dpass ? tcu::RGBA::green : tcu::RGBA::blue);
+		}
+	}
+
+	bool isOk = tcu::fuzzyCompare(log, "Result", "Image comparison result", referenceFrame, renderedFrame, 0.05f, tcu::COMPARE_LOG_RESULT);
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Fail");
+	return STOP;
+}
+
+class DepthRangeWriteCase : public TestCase
+{
+public:
+							DepthRangeWriteCase		(Context& context, const char* name, const char* desc, const tcu::Vec4& depthCoord, const float zNear, const float zFar);
+							~DepthRangeWriteCase	(void);
+
+	IterateResult			iterate					(void);
+
+private:
+	const tcu::Vec4&		m_depthCoord;
+	const float				m_zNear;
+	const float				m_zFar;
+};
+
+DepthRangeWriteCase::DepthRangeWriteCase (Context& context, const char* name, const char* desc, const tcu::Vec4& depthCoord, const float zNear, const float zFar)
+	: TestCase			(context, name, desc)
+	, m_depthCoord		(depthCoord)
+	, m_zNear			(zNear)
+	, m_zFar			(zFar)
+{
+}
+
+DepthRangeWriteCase::~DepthRangeWriteCase (void)
+{
+}
+
+DepthRangeWriteCase::IterateResult DepthRangeWriteCase::iterate (void)
+{
+	TestLog&					log				= m_testCtx.getLog();
+	de::Random					rnd				(deStringHash(getName()));
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderContext().getRenderTarget();
+	const int					viewportW		= de::min(128, renderTarget.getWidth());
+	const int					viewportH		= de::min(128, renderTarget.getHeight());
+	const int					viewportX		= rnd.getInt(0, renderTarget.getWidth()-viewportW);
+	const int					viewportY		= rnd.getInt(0, renderTarget.getHeight()-viewportH);
+	tcu::Surface				renderedFrame	(viewportW, viewportH);
+	tcu::Surface				referenceFrame	(viewportW, viewportH);
+	const int					numDepthSteps	= VISUALIZE_DEPTH_STEPS;
+	const float					depthStep		= 1.0f/(float)(numDepthSteps-1);
+
+	if (renderTarget.getDepthBits() == 0)
+		throw tcu::NotSupportedError("Depth buffer is required", "", __FILE__, __LINE__);
+
+	const glu::ShaderProgram	program			(m_context.getRenderContext(), glu::makeVtxFragSources(s_vertexShaderSrc, s_fragmentShaderSrc));
+
+	if (!program.isOk())
+	{
+		log << program;
+		TCU_FAIL("Compile failed");
+	}
+
+	const int					colorLoc		= glGetUniformLocation(program.getProgram(), "u_color");
+	const int					posLoc			= glGetAttribLocation(program.getProgram(), "a_position");
+
+	m_testCtx.getLog() << TestLog::Message << "glDepthRangef(" << m_zNear << ", " << m_zFar << ")" << TestLog::EndMessage;
+
+	glViewport(viewportX, viewportY, viewportW, viewportH);
+	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	glEnable(GL_DEPTH_TEST);
+	glUseProgram(program.getProgram());
+	glEnableVertexAttribArray(posLoc);
+
+	static const deUint16 quadIndices[] = { 0, 1, 2, 2, 1, 3 };
+
+	// Render with depth range.
+	{
+		const float position[] =
+		{
+			-1.0f, -1.0f, m_depthCoord[0], 1.0f,
+			-1.0f, +1.0f, m_depthCoord[1], 1.0f,
+			+1.0f, -1.0f, m_depthCoord[2], 1.0f,
+			+1.0f, +1.0f, m_depthCoord[3], 1.0f
+		};
+
+		glDepthFunc(GL_ALWAYS);
+		glDepthRangef(m_zNear, m_zFar);
+		glUniform4f(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+		glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &position[0]);
+		glDrawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(quadIndices), GL_UNSIGNED_SHORT, &quadIndices[0]);
+		GLU_CHECK();
+	}
+
+	// Visualize by rendering full-screen quads with increasing depth and color.
+	{
+		glDepthFunc(GL_LEQUAL);
+		glDepthMask(GL_FALSE);
+		glDepthRangef(0.0f, 1.0f);
+
+		for (int stepNdx = 0; stepNdx < numDepthSteps; stepNdx++)
+		{
+			float	f		= (float)stepNdx*depthStep;
+			float	depth	= f*2.0f - 1.0f;
+			Vec4	color	= Vec4(f, f, f, 1.0f);
+
+			float position[] =
+			{
+				-1.0f, -1.0f, depth, 1.0f,
+				-1.0f, +1.0f, depth, 1.0f,
+				+1.0f, -1.0f, depth, 1.0f,
+				+1.0f, +1.0f, depth, 1.0f
+			};
+
+			glUniform4fv(colorLoc, 1, color.getPtr());
+			glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &position[0]);
+			glDrawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(quadIndices), GL_UNSIGNED_SHORT, &quadIndices[0]);
+		}
+
+		GLU_CHECK();
+	}
+
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedFrame.getAccess());
+
+	// Render reference.
+	for (int y = 0; y < referenceFrame.getHeight(); y++)
+	{
+		for (int x = 0; x < referenceFrame.getWidth(); x++)
+		{
+			float	xf		= ((float)x + 0.5f) / (float)referenceFrame.getWidth();
+			float	yf		= ((float)y + 0.5f) / (float)referenceFrame.getHeight();
+			float	d		= depthRangeTransform(triQuadInterpolate(xf, yf, m_depthCoord), m_zNear, m_zFar);
+			int		step	= (int)deFloatFloor(d / depthStep);
+			int		col		= de::clamp(deRoundFloatToInt32((float)step*depthStep*255.0f), 0, 255);
+
+			referenceFrame.setPixel(x, y, tcu::RGBA(col, col, col, 0xff));
+		}
+	}
+
+	bool isOk = tcu::fuzzyCompare(log, "Result", "Image comparison result", referenceFrame, renderedFrame, 0.05f, tcu::COMPARE_LOG_RESULT);
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Fail");
+	return STOP;
+}
+
+DepthRangeTests::DepthRangeTests (Context& context)
+	: TestCaseGroup(context, "depth_range", "glDepthRangef() tests")
+{
+}
+
+DepthRangeTests::~DepthRangeTests (void)
+{
+}
+
+void DepthRangeTests::init (void)
+{
+	static const struct
+	{
+		const char*			name;
+		const char*			desc;
+		const tcu::Vec4		depthCoord;
+		const float			zNear;
+		const float			zFar;
+	} cases[] =
+	{
+		{ "default",		"Default depth range",		tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	0.0f,		1.0f },
+		{ "reverse",		"Reversed default range",	tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	1.0f,		0.0f },
+		{ "zero_to_half",	"From 0 to 0.5",			tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	0.0f,		0.5f },
+		{ "half_to_one",	"From 0.5 to 1",			tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	0.5f,		1.0f },
+		{ "half_to_zero",	"From 0.5 to 0",			tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	0.5f,		0.0f },
+		{ "one_to_half",	"From 1 to 0.5",			tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	1.0f,		0.5f },
+		{ "third_to_0_8",	"From 1/3 to 0.8",			tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	1.0f/3.0f,	0.8f },
+		{ "0_8_to_third",	"From 0.8 to 1/3",			tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	0.8f,		1.0f/3.0f },
+		{ "zero_to_zero",	"From 0 to 0",				tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	0.0f,		0.0f },
+		{ "half_to_half",	"From 0.5 to 0.5",			tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	0.5f,		0.5f },
+		{ "one_to_one",		"From 1 to 1",				tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	1.0f,		1.0f },
+		{ "clamp_near",		"From -1 to 1",				tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	-1.0f,		1.0f },
+		{ "clamp_far",		"From 0 to 2",				tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	0.0f,		2.0 },
+		{ "clamp_both",		"From -1 to 2",				tcu::Vec4(-1.0f, 0.2f, -0.3f, 1.0f),	-1.0,		2.0 }
+	};
+
+	// .write
+	tcu::TestCaseGroup* writeGroup = new tcu::TestCaseGroup(m_testCtx, "write", "gl_FragDepth write tests");
+	addChild(writeGroup);
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
+		writeGroup->addChild(new DepthRangeWriteCase(m_context, cases[ndx].name, cases[ndx].desc, cases[ndx].depthCoord, cases[ndx].zNear, cases[ndx].zFar));
+
+	// .compare
+	tcu::TestCaseGroup* compareGroup = new tcu::TestCaseGroup(m_testCtx, "compare", "gl_FragDepth used with depth comparison");
+	addChild(compareGroup);
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
+		compareGroup->addChild(new DepthRangeCompareCase(m_context, cases[ndx].name, cases[ndx].desc, cases[ndx].depthCoord, cases[ndx].zNear, cases[ndx].zFar, GL_LESS));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles2/functional/es2fDepthRangeTests.hpp b/modules/gles2/functional/es2fDepthRangeTests.hpp
new file mode 100644
index 0000000..1f84b68
--- /dev/null
+++ b/modules/gles2/functional/es2fDepthRangeTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FDEPTHRANGETESTS_HPP
+#define _ES2FDEPTHRANGETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief glDepthRangef() tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class DepthRangeTests : public TestCaseGroup
+{
+public:
+						DepthRangeTests		(Context& context);
+						~DepthRangeTests	(void);
+
+	void				init				(void);
+
+private:
+						DepthRangeTests		(const DepthRangeTests& other);
+	DepthRangeTests&	operator=			(const DepthRangeTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FDEPTHRANGETESTS_HPP
diff --git a/modules/gles2/functional/es2fDepthStencilClearTests.cpp b/modules/gles2/functional/es2fDepthStencilClearTests.cpp
new file mode 100644
index 0000000..d8867af
--- /dev/null
+++ b/modules/gles2/functional/es2fDepthStencilClearTests.cpp
@@ -0,0 +1,521 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth and stencil clear tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fDepthStencilClearTests.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::TestLog;
+using std::string;
+using std::vector;
+
+namespace
+{
+
+enum
+{
+	STENCIL_STEPS	= 32,
+	DEPTH_STEPS		= 32
+};
+
+struct Clear
+{
+	Clear (void)
+		: clearMask			(0)
+		, clearDepth		(0.0f)
+		, clearStencil		(0)
+		, useScissor		(false)
+		, scissor			(0, 0, 0, 0)
+		, depthMask			(false)
+		, stencilMask		(0)
+	{
+	}
+
+	deUint32	clearMask;
+	float		clearDepth;
+	int			clearStencil;
+
+	bool		useScissor;
+	tcu::IVec4	scissor;
+
+	bool		depthMask;
+	deUint32	stencilMask;
+};
+
+tcu::TextureFormat getDepthFormat (int depthBits)
+{
+	switch (depthBits)
+	{
+		case 8:		return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT8);
+		case 16:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16);
+		case 24:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNSIGNED_INT_24_8);
+		case 32:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT);
+		default:
+			TCU_FAIL("Can't map depth buffer format");
+	}
+}
+
+tcu::TextureFormat getStencilFormat (int stencilBits)
+{
+	switch (stencilBits)
+	{
+		case 8:		return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT8);
+		case 16:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT16);
+		case 24:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT_24_8);
+		case 32:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT32);
+		default:
+			TCU_FAIL("Can't map depth buffer format");
+	}
+}
+
+} // anonymous.
+
+class DepthStencilClearCase : public TestCase
+{
+public:
+								DepthStencilClearCase	(Context& context, const char* name, const char* description, int numIters, int numClears, bool depth, bool stencil, bool scissor, bool masked);
+								~DepthStencilClearCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+
+	IterateResult				iterate					(void);
+
+private:
+	void						generateClears			(vector<Clear>& dst, deUint32 seed);
+	void						renderGL				(tcu::Surface& dst, const vector<Clear>& clears);
+	void						renderReference			(tcu::Surface& dst, const vector<Clear>& clears);
+
+	bool						m_testDepth;
+	bool						m_testStencil;
+	bool						m_testScissor;
+	bool						m_masked;
+	int							m_numIters;
+	int							m_numClears;
+	int							m_curIter;
+
+	glu::ShaderProgram*			m_visProgram;
+};
+
+DepthStencilClearCase::DepthStencilClearCase (Context& context, const char* name, const char* description, int numIters, int numClears, bool depth, bool stencil, bool scissor, bool masked)
+	: TestCase			(context, name, description)
+	, m_testDepth		(depth)
+	, m_testStencil		(stencil)
+	, m_testScissor		(scissor)
+	, m_masked			(masked)
+	, m_numIters		(numIters)
+	, m_numClears		(numClears)
+	, m_curIter			(0)
+	, m_visProgram		(DE_NULL)
+{
+}
+
+DepthStencilClearCase::~DepthStencilClearCase (void)
+{
+	DepthStencilClearCase::deinit();
+}
+
+void DepthStencilClearCase::init (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	m_visProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(
+			// Vertex shader.
+			"attribute highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n",
+
+			// Fragment shader.
+			"uniform mediump vec4 u_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = u_color;\n"
+			"}\n"));
+
+	if (!m_visProgram->isOk())
+	{
+		log << *m_visProgram;
+		delete m_visProgram;
+		m_visProgram = DE_NULL;
+		TCU_FAIL("Compile failed");
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void DepthStencilClearCase::deinit (void)
+{
+	delete m_visProgram;
+	m_visProgram = DE_NULL;
+}
+
+DepthStencilClearCase::IterateResult DepthStencilClearCase::iterate (void)
+{
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderTarget();
+	int							width			= renderTarget.getWidth();
+	int							height			= renderTarget.getHeight();
+	tcu::Surface				result			(width, height);
+	tcu::Surface				reference		(width, height);
+	tcu::RGBA					threshold		= renderTarget.getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<Clear>				clears;
+
+	if ((m_testDepth && renderTarget.getDepthBits() == 0) ||
+		(m_testStencil && renderTarget.getStencilBits() == 0))
+		throw tcu::NotSupportedError("No depth/stencil buffers", "", __FILE__, __LINE__);
+
+	generateClears(clears, deStringHash(getName())^deInt32Hash(m_curIter));
+	renderGL(result, clears);
+	renderReference(reference, clears);
+
+	bool	isLastIter		= m_curIter+1 == m_numIters;
+	bool	isOk			= tcu::pixelThresholdCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference, result, threshold, isLastIter ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);
+
+	if (!isOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+	m_curIter += 1;
+	return isLastIter || !isOk ? STOP : CONTINUE;
+}
+
+void DepthStencilClearCase::generateClears (vector<Clear>& clears, deUint32 seed)
+{
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderContext().getRenderTarget();
+	int							width			= renderTarget.getWidth();
+	int							height			= renderTarget.getHeight();
+	de::Random					rnd				(seed);
+
+	clears.resize(m_numClears);
+
+	for (vector<Clear>::iterator clear = clears.begin(); clear != clears.end(); clear++)
+	{
+		if (m_testScissor)
+		{
+			int w = rnd.getInt(1, width);
+			int h = rnd.getInt(1, height);
+			int x = rnd.getInt(0, width-w);
+			int y = rnd.getInt(0, height-h);
+
+			clear->useScissor	= true; // \todo [pyry] Should we randomize?
+			clear->scissor		= tcu::IVec4(x, y, w, h);
+		}
+		else
+			clear->useScissor = false;
+
+		clear->clearDepth	= rnd.getFloat(-0.2f, 1.2f);
+		clear->clearStencil	= rnd.getUint32();
+
+		clear->depthMask	= m_masked ? rnd.getBool()		: true;
+		clear->stencilMask	= m_masked ? rnd.getUint32()	: 0xffffffffu;
+
+		if (m_testDepth && m_testStencil)
+		{
+			switch (rnd.getInt(0, 2))
+			{
+				case 0: clear->clearMask = GL_DEPTH_BUFFER_BIT;							break;
+				case 1: clear->clearMask = GL_STENCIL_BUFFER_BIT;						break;
+				case 2: clear->clearMask = GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT;	break;
+			}
+		}
+		else if (m_testDepth)
+			clear->clearMask = GL_DEPTH_BUFFER_BIT;
+		else
+		{
+			DE_ASSERT(m_testStencil);
+			clear->clearMask = GL_STENCIL_BUFFER_BIT;
+		}
+	}
+}
+
+void DepthStencilClearCase::renderGL (tcu::Surface& dst, const vector<Clear>& clears)
+{
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	int							colorLoc		= gl.getUniformLocation(m_visProgram->getProgram(), "u_color");
+	int							positionLoc		= gl.getAttribLocation(m_visProgram->getProgram(), "a_position");
+	static const deUint8		indices[]		= { 0, 1, 2, 2, 1, 3 };
+
+	// Clear with default values.
+	gl.clearDepthf	(1.0f);
+	gl.clearStencil	(0);
+	gl.clearColor	(1.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear		(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Before clears");
+
+	for (vector<Clear>::const_iterator clear = clears.begin(); clear != clears.end(); clear++)
+	{
+		if (clear->useScissor)
+		{
+			gl.enable(GL_SCISSOR_TEST);
+			gl.scissor(clear->scissor.x(), clear->scissor.y(), clear->scissor.z(), clear->scissor.w());
+		}
+
+		// Clear values.
+		gl.clearDepthf	(clear->clearDepth);
+		gl.clearStencil	(clear->clearStencil);
+
+		// Masks.
+		gl.depthMask	(clear->depthMask ? GL_TRUE : GL_FALSE);
+		gl.stencilMask	(clear->stencilMask);
+
+		// Execute clear.
+		gl.clear		(clear->clearMask);
+
+		if (clear->useScissor)
+			gl.disable(GL_SCISSOR_TEST);
+	}
+
+	// Restore default masks.
+	gl.depthMask	(GL_TRUE);
+	gl.stencilMask	(0xffffffffu);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After clears");
+
+	gl.useProgram				(m_visProgram->getProgram());
+	gl.enableVertexAttribArray	(positionLoc);
+
+	// Visualize depth / stencil buffers.
+	if (m_testDepth)
+	{
+		int		numSteps	= DEPTH_STEPS;
+		float	step		= 2.0f / numSteps;
+
+		gl.enable	(GL_DEPTH_TEST);
+		gl.depthFunc(GL_LESS);
+		gl.depthMask(GL_FALSE);
+		gl.colorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
+
+		for (int ndx = 0; ndx < numSteps; ndx++)
+		{
+			float	d		= -1.0f + step*ndx;
+			float	c		= (float)ndx / (float)(numSteps-1);
+			float	pos[]	=
+			{
+				-1.0f, -1.0f, d,
+				-1.0f,  1.0f, d,
+				 1.0f, -1.0f, d,
+				 1.0f,  1.0f, d
+			};
+
+			gl.uniform4f			(colorLoc, 0.0f, 0.0f, c, 1.0f);
+			gl.vertexAttribPointer	(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, &pos[0]);
+			gl.drawElements			(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, &indices[0]);
+		}
+
+		gl.disable	(GL_DEPTH_TEST);
+		gl.depthMask(GL_TRUE);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "After depth visualization");
+	}
+
+	if (m_testStencil)
+	{
+		int		numSteps	= STENCIL_STEPS;
+		int		numValues	= (1 << TestCase::m_context.getRenderContext().getRenderTarget().getStencilBits()); // 2^bits
+		int		step		= numValues / numSteps;
+
+		gl.enable		(GL_STENCIL_TEST);
+		gl.stencilOp	(GL_KEEP, GL_KEEP, GL_KEEP);
+		gl.colorMask	(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
+
+		static const float pos[] =
+		{
+			-1.0f, -1.0f,
+			-1.0f,  1.0f,
+			 1.0f, -1.0f,
+			 1.0f,  1.0f
+		};
+		gl.vertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, &pos[0]);
+
+		for (int ndx = 0; ndx < numSteps; ndx++)
+		{
+			int		s	= step*ndx;
+			float	c	= (float)ndx / (float)(numSteps-1);
+
+			gl.stencilFunc	(GL_LEQUAL, s, 0xffu);
+			gl.uniform4f	(colorLoc, 0.0f, c, 0.0f, 1.0f);
+			gl.drawElements	(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, &indices[0]);
+		}
+
+		gl.disable(GL_STENCIL_TEST);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "After stencil visualization");
+	}
+
+	// Restore color mask (changed by visualization).
+	gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+}
+
+void DepthStencilClearCase::renderReference (tcu::Surface& dst, const vector<Clear>& clears)
+{
+	glu::RenderContext&			renderCtx		= TestCase::m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget	= renderCtx.getRenderTarget();
+
+	// Clear surface to red.
+	tcu::clear(dst.getAccess(), tcu::RGBA::red.toVec());
+
+	if (m_testDepth)
+	{
+		// Simulated depth buffer span.
+		tcu::TextureLevel		depthBufRow		(getDepthFormat(renderTarget.getDepthBits()), dst.getWidth(), 1, 1);
+		tcu::PixelBufferAccess	rowAccess		= depthBufRow.getAccess();
+
+		for (int y = 0; y < dst.getHeight(); y++)
+		{
+			// Clear to default value.
+			for (int x = 0; x < rowAccess.getWidth(); x++)
+				rowAccess.setPixel(Vec4(1.0f), x, 0);
+
+			// Execute clears.
+			for (vector<Clear>::const_iterator clear = clears.begin(); clear != clears.end(); clear++)
+			{
+				// Clear / mask test.
+				if ((clear->clearMask & GL_DEPTH_BUFFER_BIT) == 0 || !clear->depthMask)
+					continue;
+
+				tcu::IVec4 clearRect = clear->useScissor ? clear->scissor : tcu::IVec4(0, 0, dst.getWidth(), dst.getHeight());
+
+				// Intersection test.
+				if (!de::inBounds(y, clearRect.y(), clearRect.y()+clearRect.w()))
+					continue;
+
+				for (int x = clearRect.x(); x < clearRect.x()+clearRect.z(); x++)
+					rowAccess.setPixel(Vec4(de::clamp(clear->clearDepth, 0.0f, 1.0f)), x, 0);
+			}
+
+			// Map to colors.
+			for (int x = 0; x < dst.getWidth(); x++)
+			{
+				float		depth		= rowAccess.getPixel(x, 0).x();
+				float		step		= deFloatFloor(depth * (float)DEPTH_STEPS) / (float)(DEPTH_STEPS-1);
+				tcu::RGBA	oldColor	= dst.getPixel(x, y);
+				tcu::RGBA	newColor	= tcu::RGBA(oldColor.getRed(), oldColor.getGreen(), deClamp32(deRoundFloatToInt32(step * 255.0f), 0, 255), oldColor.getAlpha());
+
+				dst.setPixel(x, y, newColor);
+			}
+		}
+	}
+
+	if (m_testStencil)
+	{
+		// Simulated stencil buffer span.
+		int						stencilBits		= renderTarget.getStencilBits();
+		tcu::TextureLevel		depthBufRow		(getStencilFormat(stencilBits), dst.getWidth(), 1, 1);
+		tcu::PixelBufferAccess	rowAccess		= depthBufRow.getAccess();
+		deUint32				bufMask			= (1u<<stencilBits)-1;
+
+		for (int y = 0; y < dst.getHeight(); y++)
+		{
+			// Clear to default value.
+			for (int x = 0; x < rowAccess.getWidth(); x++)
+				rowAccess.setPixel(tcu::UVec4(0), x, 0);
+
+			// Execute clears.
+			for (vector<Clear>::const_iterator clear = clears.begin(); clear != clears.end(); clear++)
+			{
+				// Clear / mask test.
+				if ((clear->clearMask & GL_STENCIL_BUFFER_BIT) == 0 || clear->stencilMask == 0)
+					continue;
+
+				tcu::IVec4 clearRect = clear->useScissor ? clear->scissor : tcu::IVec4(0, 0, dst.getWidth(), dst.getHeight());
+
+				// Intersection test.
+				if (!de::inBounds(y, clearRect.y(), clearRect.y()+clearRect.w()))
+					continue;
+
+				for (int x = clearRect.x(); x < clearRect.x()+clearRect.z(); x++)
+				{
+					deUint32	oldVal	= rowAccess.getPixelUint(x, 0).w();
+					deUint32	newVal	= ((oldVal & ~clear->stencilMask) | (clear->clearStencil & clear->stencilMask)) & bufMask;
+					rowAccess.setPixel(tcu::UVec4(newVal), x, 0);
+				}
+			}
+
+			// Map to colors.
+			for (int x = 0; x < dst.getWidth(); x++)
+			{
+				deUint32	stencil		= rowAccess.getPixelUint(x, 0).w();
+				float		step		= (float)(stencil / ((1u<<stencilBits) / (deUint32)STENCIL_STEPS)) / (float)(STENCIL_STEPS-1);
+				tcu::RGBA	oldColor	= dst.getPixel(x, y);
+				tcu::RGBA	newColor	= tcu::RGBA(oldColor.getRed(), deClamp32(deRoundFloatToInt32(step * 255.0f), 0, 255), oldColor.getBlue(), oldColor.getAlpha());
+
+				dst.setPixel(x, y, newColor);
+			}
+		}
+	}
+}
+
+DepthStencilClearTests::DepthStencilClearTests (Context& context)
+	: TestCaseGroup(context, "depth_stencil_clear", "Depth and stencil clear tests")
+{
+}
+
+void DepthStencilClearTests::init (void)
+{
+	//																					iters	clears	depth	stencil	scissor	masked
+	addChild(new DepthStencilClearCase(m_context, "depth",							"",	4,		2,		true,	false,	false,	false));
+	addChild(new DepthStencilClearCase(m_context, "depth_scissored",				"",	4,		16,		true,	false,	true,	false));
+	addChild(new DepthStencilClearCase(m_context, "depth_scissored_masked",			"",	4,		16,		true,	false,	true,	true));
+
+	addChild(new DepthStencilClearCase(m_context, "stencil",						"",	4,		2,		false,	true,	false,	false));
+	addChild(new DepthStencilClearCase(m_context, "stencil_masked",					"",	4,		8,		false,	true,	false,	true));
+	addChild(new DepthStencilClearCase(m_context, "stencil_scissored",				"",	4,		16,		false,	true,	true,	false));
+	addChild(new DepthStencilClearCase(m_context, "stencil_scissored_masked",		"",	4,		16,		false,	true,	true,	true));
+
+	addChild(new DepthStencilClearCase(m_context, "depth_stencil",					"",	4,		2,		true,	true,	false,	false));
+	addChild(new DepthStencilClearCase(m_context, "depth_stencil_masked",			"",	4,		8,		true,	true,	false,	true));
+	addChild(new DepthStencilClearCase(m_context, "depth_stencil_scissored",		"",	4,		16,		true,	true,	true,	false));
+	addChild(new DepthStencilClearCase(m_context, "depth_stencil_scissored_masked",	"",	4,		16,		true,	true,	true,	true));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fDepthStencilClearTests.hpp b/modules/gles2/functional/es2fDepthStencilClearTests.hpp
new file mode 100644
index 0000000..35f7133
--- /dev/null
+++ b/modules/gles2/functional/es2fDepthStencilClearTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FDEPTHSTENCILCLEARTESTS_HPP
+#define _ES2FDEPTHSTENCILCLEARTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth and stencil buffer clear tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class DepthStencilClearTests : public TestCaseGroup
+{
+public:
+								DepthStencilClearTests		(Context& context);
+	virtual						~DepthStencilClearTests		(void) {}
+
+	virtual void				init						(void);
+
+private:
+								DepthStencilClearTests		(const DepthStencilClearTests&);
+	DepthStencilClearTests&		operator=					(const DepthStencilClearTests&);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FDEPTHSTENCILCLEARTESTS_HPP
diff --git a/modules/gles2/functional/es2fDepthStencilTests.cpp b/modules/gles2/functional/es2fDepthStencilTests.cpp
new file mode 100644
index 0000000..c7cb9ca
--- /dev/null
+++ b/modules/gles2/functional/es2fDepthStencilTests.cpp
@@ -0,0 +1,1158 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth & stencil tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fDepthStencilTests.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuTextureUtil.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMemory.h"
+#include "deString.h"
+#include "rrFragmentOperations.hpp"
+#include "sglrReferenceUtils.hpp"
+
+#include <algorithm>
+#include <sstream>
+
+#include "glw.h"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using std::vector;
+using tcu::IVec2;
+using tcu::Vec2;
+using tcu::Vec4;
+using tcu::TestLog;
+using std::ostringstream;
+
+enum
+{
+	VIEWPORT_WIDTH			= 4*3*4,
+	VIEWPORT_HEIGHT			= 4*12,
+
+	NUM_RANDOM_CASES		= 25,
+	NUM_RANDOM_SUB_CASES	= 10
+};
+
+namespace DepthStencilCaseUtil
+{
+
+struct StencilParams
+{
+	deUint32	function;
+	int			reference;
+	deUint32	compareMask;
+
+	deUint32	stencilFailOp;
+	deUint32	depthFailOp;
+	deUint32	depthPassOp;
+
+	deUint32	writeMask;
+
+	StencilParams (void)
+		: function		(0)
+		, reference		(0)
+		, compareMask	(0)
+		, stencilFailOp	(0)
+		, depthFailOp	(0)
+		, depthPassOp	(0)
+		, writeMask		(0)
+	{
+	}
+};
+
+struct DepthStencilParams
+{
+	rr::FaceType	visibleFace;			//!< Quad visible face.
+
+	bool			stencilTestEnabled;
+	StencilParams	stencil[rr::FACETYPE_LAST];
+
+	bool			depthTestEnabled;
+	deUint32		depthFunc;
+	float			depth;
+	bool			depthWriteMask;
+
+	DepthStencilParams (void)
+		: visibleFace			(rr::FACETYPE_LAST)
+		, stencilTestEnabled	(false)
+		, depthTestEnabled		(false)
+		, depthFunc				(0)
+		, depth					(0.0f)
+		, depthWriteMask		(false)
+	{
+	}
+};
+
+tcu::TestLog& operator<< (tcu::TestLog& log, const StencilParams& params)
+{
+	log << TestLog::Message << "  func = " << glu::getCompareFuncStr(params.function) << "\n"
+							<< "  ref = " << params.reference << "\n"
+							<< "  compare mask = " << tcu::toHex(params.compareMask) << "\n"
+							<< "  stencil fail = " << glu::getStencilOpStr(params.stencilFailOp) << "\n"
+							<< "  depth fail = " << glu::getStencilOpStr(params.depthFailOp) << "\n"
+							<< "  depth pass = " << glu::getStencilOpStr(params.depthPassOp) << "\n"
+							<< "  write mask = " << tcu::toHex(params.writeMask) << "\n"
+		<< TestLog::EndMessage;
+	return log;
+}
+
+tcu::TestLog& operator<< (tcu::TestLog& log, const DepthStencilParams& params)
+{
+	log << TestLog::Message << "Stencil test: " << (params.stencilTestEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
+	if (params.stencilTestEnabled)
+	{
+		log << TestLog::Message << "Front-face stencil state: " << TestLog::EndMessage;
+		log << params.stencil[rr::FACETYPE_FRONT];
+
+		log << TestLog::Message << "Back-face stencil state: " << TestLog::EndMessage;
+		log << params.stencil[rr::FACETYPE_BACK];
+	}
+
+	log << TestLog::Message << "Depth test: " << (params.depthTestEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
+	if (params.depthTestEnabled)
+	{
+		log << TestLog::Message << "  func = " << glu::getCompareFuncStr(params.depthFunc) << "\n"
+								   "  depth value = " << params.depth << "\n"
+								   "  write mask = " << (params.depthWriteMask ? "true" : "false") << "\n"
+			<< TestLog::EndMessage;
+	}
+
+	log << TestLog::Message << "Triangles are " << (params.visibleFace == rr::FACETYPE_FRONT ? "front" : "back") << "-facing" << TestLog::EndMessage;
+
+	return log;
+}
+
+struct ClearCommand
+{
+	rr::WindowRectangle	rect;
+	deUint32			buffers;
+	tcu::Vec4			color;
+	int					stencil;
+	// \note No depth here - don't use clears for setting depth values; use quad rendering instead. Cleared depths are in [0, 1] to begin with,
+	//		 whereas rendered depths are given in [-1, 1] and then mapped to [0, 1]; this discrepancy could cause precision issues in depth tests.
+
+	ClearCommand (void)
+		: rect		(0, 0, 0, 0)
+		, buffers	(0)
+		, stencil	(0)
+	{
+	}
+
+	ClearCommand (const rr::WindowRectangle&	rect_,
+				  deUint32					buffers_,
+				  const tcu::Vec4&			color_,
+				  int						stencil_)
+		: rect		(rect_)
+		, buffers	(buffers_)
+		, color		(color_)
+		, stencil	(stencil_)
+	{
+	}
+};
+
+struct RenderCommand
+{
+	DepthStencilParams		params;
+	rr::WindowRectangle		rect;
+	tcu::Vec4				color;
+	tcu::BVec4				colorMask;
+
+	RenderCommand (void)
+		: rect(0, 0, 0, 0)
+	{
+	}
+};
+
+struct RefRenderCommand
+{
+	gls::FragmentOpUtil::IntegerQuad	quad;
+	rr::FragmentOperationState			state;
+};
+
+struct TestRenderTarget
+{
+	int		width;
+	int		height;
+	int		depthBits;
+	int		stencilBits;
+
+	TestRenderTarget (int width_,
+					  int height_,
+					  int depthBits_,
+					  int stencilBits_)
+		: width			(width_)
+		, height		(height_)
+		, depthBits		(depthBits_)
+		, stencilBits	(stencilBits_)
+	{
+	}
+
+	TestRenderTarget (void)
+		: width			(0)
+		, height		(0)
+		, depthBits		(0)
+		, stencilBits	(0)
+	{
+	}
+};
+
+void getStencilTestValues (int stencilBits, int numValues, int* values)
+{
+	int numLowest		= numValues/2;
+	int	numHighest		= numValues-numLowest;
+	int	maxVal			= (1<<stencilBits)-1;
+
+	for (int ndx = 0; ndx < numLowest; ndx++)
+		values[ndx] = ndx;
+
+	for (int ndx = 0; ndx < numHighest; ndx++)
+		values[numValues-ndx-1] = maxVal-ndx;
+}
+
+void generateBaseClearAndDepthCommands (const TestRenderTarget& target, vector<ClearCommand>& clearCommands, vector<RenderCommand>& renderCommands)
+{
+	DE_ASSERT(clearCommands.empty());
+	DE_ASSERT(renderCommands.empty());
+
+	const int		numL0CellsX		= 4;
+	const int		numL0CellsY		= 4;
+	const int		numL1CellsX		= 3;
+	const int		numL1CellsY		= 1;
+	int				cellL0Width		= target.width/numL0CellsX;
+	int				cellL0Height	= target.height/numL0CellsY;
+	int				cellL1Width		= cellL0Width/numL1CellsX;
+	int				cellL1Height	= cellL0Height/numL1CellsY;
+
+	int				stencilValues[numL0CellsX*numL0CellsY];
+	float			depthValues[numL1CellsX*numL1CellsY];
+
+	if (cellL0Width <= 0 || cellL1Width <= 0 || cellL0Height <= 0 || cellL1Height <= 0)
+		throw tcu::NotSupportedError("Too small render target");
+
+	// Fullscreen clear to black.
+	clearCommands.push_back(ClearCommand(rr::WindowRectangle(0, 0, target.width, target.height), GL_COLOR_BUFFER_BIT, Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0));
+
+	// Compute stencil values: numL0CellsX*numL0CellsY combinations of lowest and highest bits.
+	getStencilTestValues(target.stencilBits, numL0CellsX*numL0CellsY, &stencilValues[0]);
+
+	// Compute depth values
+	{
+		int		numValues		= DE_LENGTH_OF_ARRAY(depthValues);
+		float	depthStep		= 2.0f/(numValues-1);
+
+		for (int ndx = 0; ndx < numValues; ndx++)
+			depthValues[ndx] = -1.0f + depthStep*ndx;
+	}
+
+	for (int y0 = 0; y0 < numL0CellsY; y0++)
+	{
+		for (int x0 = 0; x0 < numL0CellsX; x0++)
+		{
+			int stencilValue = stencilValues[y0*numL0CellsX + x0];
+
+			for (int y1 = 0; y1 < numL1CellsY; y1++)
+			{
+				for (int x1 = 0; x1 < numL1CellsX; x1++)
+				{
+					int					x			= x0*cellL0Width + x1*cellL1Width;
+					int					y			= y0*cellL0Height + y1*cellL1Height;
+					rr::WindowRectangle	cellL1Rect	(x, y, cellL1Width, cellL1Height);
+
+					clearCommands.push_back(ClearCommand(cellL1Rect, GL_STENCIL_BUFFER_BIT, Vec4(0), stencilValue));
+
+					RenderCommand renderCmd;
+					renderCmd.params.visibleFace		= rr::FACETYPE_FRONT;
+					renderCmd.params.depth				= depthValues[y1*numL1CellsX + x1];;
+					renderCmd.params.depthTestEnabled	= true;
+					renderCmd.params.depthFunc			= GL_ALWAYS;
+					renderCmd.params.depthWriteMask		= true;
+					renderCmd.colorMask					= tcu::BVec4(false);
+					renderCmd.rect						= cellL1Rect;
+
+					renderCommands.push_back(renderCmd);
+				}
+			}
+		}
+	}
+}
+
+void generateDepthVisualizeCommands (const TestRenderTarget& target, vector<RenderCommand>& commands)
+{
+	const float			epsilon			= -0.05f;
+	static const float	depthSteps[]	= {-1.0f, -0.5f, 0.0f, 0.5f, 1.0f};
+	int					numSteps		= DE_LENGTH_OF_ARRAY(depthSteps);
+	const float			colorStep		= 1.0f / (float)(numSteps-1);
+
+	for (int ndx = 0; ndx < numSteps; ndx++)
+	{
+		RenderCommand cmd;
+
+		cmd.params.visibleFace		= rr::FACETYPE_FRONT;
+		cmd.rect					= rr::WindowRectangle(0, 0, target.width, target.height);
+		cmd.color					= Vec4(0.0f, 0.0f, colorStep*ndx, 0.0f);
+		cmd.colorMask				= tcu::BVec4(false, false, true, false);
+		cmd.params.depth			= depthSteps[ndx]+epsilon;
+		cmd.params.depthTestEnabled	= true;
+		cmd.params.depthFunc		= GL_LESS;
+		cmd.params.depthWriteMask	= false;
+
+		commands.push_back(cmd);
+	}
+}
+
+void generateStencilVisualizeCommands (const TestRenderTarget& target, vector<RenderCommand>& commands)
+{
+	const int	numValues		= 4*4;
+	float		colorStep		= 1.0f / numValues; // 0 is reserved for non-matching.
+	int			stencilValues[numValues];
+
+	getStencilTestValues(target.stencilBits, numValues, &stencilValues[0]);
+
+	for (int ndx = 0; ndx < numValues; ndx++)
+	{
+		RenderCommand cmd;
+
+		cmd.params.visibleFace							= rr::FACETYPE_FRONT;
+		cmd.rect										= rr::WindowRectangle(0, 0, target.width, target.height);
+		cmd.color										= Vec4(0.0f, colorStep*(ndx+1), 0.0f, 0.0f);
+		cmd.colorMask									= tcu::BVec4(false, true, false, false);
+		cmd.params.stencilTestEnabled					= true;
+
+		cmd.params.stencil[rr::FACETYPE_FRONT].function			= GL_EQUAL;
+		cmd.params.stencil[rr::FACETYPE_FRONT].reference		= stencilValues[ndx];
+		cmd.params.stencil[rr::FACETYPE_FRONT].compareMask		= ~0u;
+		cmd.params.stencil[rr::FACETYPE_FRONT].stencilFailOp	= GL_KEEP;
+		cmd.params.stencil[rr::FACETYPE_FRONT].depthFailOp		= GL_KEEP;
+		cmd.params.stencil[rr::FACETYPE_FRONT].depthPassOp		= GL_KEEP;
+		cmd.params.stencil[rr::FACETYPE_FRONT].writeMask		= 0u;
+
+		cmd.params.stencil[rr::FACETYPE_BACK] = cmd.params.stencil[rr::FACETYPE_FRONT];
+
+		commands.push_back(cmd);
+	}
+}
+
+void translateStencilState (const StencilParams& src, rr::StencilState& dst)
+{
+	dst.func		= sglr::rr_util::mapGLTestFunc(src.function);
+	dst.ref			= src.reference;
+	dst.compMask	= src.compareMask;
+	dst.sFail		= sglr::rr_util::mapGLStencilOp(src.stencilFailOp);
+	dst.dpFail		= sglr::rr_util::mapGLStencilOp(src.depthFailOp);
+	dst.dpPass		= sglr::rr_util::mapGLStencilOp(src.depthPassOp);
+	dst.writeMask	= src.writeMask;
+}
+
+void translateCommand (const RenderCommand& src, RefRenderCommand& dst, const TestRenderTarget& renderTarget)
+{
+	const float		far				= 1.0f;
+	const float		near			= 0.0f;
+	bool			hasDepth		= renderTarget.depthBits > 0;
+	bool			hasStencil		= renderTarget.stencilBits > 0;
+	bool			isFrontFacing	= src.params.visibleFace == rr::FACETYPE_FRONT;
+
+	dst.quad.posA = IVec2(isFrontFacing ? src.rect.left : (src.rect.left+src.rect.width-1), src.rect.bottom);
+	dst.quad.posB = IVec2(isFrontFacing ? (src.rect.left+src.rect.width-1) : src.rect.left, src.rect.bottom+src.rect.height-1);
+
+	std::fill(DE_ARRAY_BEGIN(dst.quad.color), DE_ARRAY_END(dst.quad.color), src.color);
+	std::fill(DE_ARRAY_BEGIN(dst.quad.depth), DE_ARRAY_END(dst.quad.depth), ((far-near)/2.0f) * src.params.depth + (near+far)/2.0f);
+
+	dst.state.colorMask = src.colorMask;
+
+	dst.state.scissorTestEnabled		= false;
+	dst.state.stencilTestEnabled		= hasStencil && src.params.stencilTestEnabled;
+	dst.state.depthTestEnabled			= hasDepth && src.params.depthTestEnabled;
+	dst.state.blendMode					= rr::BLENDMODE_NONE;
+	dst.state.numStencilBits			= renderTarget.stencilBits;
+
+	if (dst.state.depthTestEnabled)
+	{
+		dst.state.depthFunc					= sglr::rr_util::mapGLTestFunc(src.params.depthFunc);
+		dst.state.depthMask					= src.params.depthWriteMask;
+	}
+
+	if (dst.state.stencilTestEnabled)
+	{
+		translateStencilState(src.params.stencil[rr::FACETYPE_BACK],	dst.state.stencilStates[rr::FACETYPE_BACK]);
+		translateStencilState(src.params.stencil[rr::FACETYPE_FRONT],	dst.state.stencilStates[rr::FACETYPE_FRONT]);
+	}
+}
+
+void render (const vector<ClearCommand>& clears, int viewportX, int viewportY)
+{
+	glEnable(GL_SCISSOR_TEST);
+
+	for (int ndx = 0; ndx < (int)clears.size(); ndx++)
+	{
+		const ClearCommand& clear = clears[ndx];
+
+		if (clear.buffers & GL_COLOR_BUFFER_BIT)	glClearColor(clear.color.x(), clear.color.y(), clear.color.z(), clear.color.w());
+		if (clear.buffers & GL_STENCIL_BUFFER_BIT)	glClearStencil(clear.stencil);
+
+		DE_ASSERT(clear.buffers == (clear.buffers & (GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT))); // \note Don't use clear for depths.
+
+		glScissor(clear.rect.left+viewportX, clear.rect.bottom+viewportY, clear.rect.width, clear.rect.height);
+		glClear(clear.buffers);
+	}
+
+	glDisable(GL_SCISSOR_TEST);
+}
+
+void render (gls::FragmentOpUtil::QuadRenderer& renderer, const RenderCommand& command, int viewportX, int viewportY)
+{
+	if (command.params.stencilTestEnabled)
+	{
+		glEnable(GL_STENCIL_TEST);
+
+		for (int face = 0; face < rr::FACETYPE_LAST; face++)
+		{
+			deUint32				glFace	= face == rr::FACETYPE_BACK ? GL_BACK : GL_FRONT;
+			const StencilParams&	sParams	= command.params.stencil[face];
+
+			glStencilFuncSeparate(glFace, sParams.function, sParams.reference, sParams.compareMask);
+			glStencilOpSeparate(glFace, sParams.stencilFailOp, sParams.depthFailOp, sParams.depthPassOp);
+			glStencilMaskSeparate(glFace, sParams.writeMask);
+		}
+	}
+	else
+		glDisable(GL_STENCIL_TEST);
+
+	if (command.params.depthTestEnabled)
+	{
+		glEnable(GL_DEPTH_TEST);
+		glDepthFunc(command.params.depthFunc);
+		glDepthMask(command.params.depthWriteMask ? GL_TRUE : GL_FALSE);
+	}
+	else
+		glDisable(GL_DEPTH_TEST);
+
+	glColorMask(command.colorMask[0] ? GL_TRUE : GL_FALSE,
+				command.colorMask[1] ? GL_TRUE : GL_FALSE,
+				command.colorMask[2] ? GL_TRUE : GL_FALSE,
+				command.colorMask[3] ? GL_TRUE : GL_FALSE);
+	glViewport(command.rect.left+viewportX, command.rect.bottom+viewportY, command.rect.width, command.rect.height);
+
+	gls::FragmentOpUtil::Quad quad;
+
+	bool isFrontFacing = command.params.visibleFace == rr::FACETYPE_FRONT;
+	quad.posA = Vec2(isFrontFacing ? -1.0f :  1.0f, -1.0f);
+	quad.posB = Vec2(isFrontFacing ?  1.0f : -1.0f,  1.0f);
+
+	std::fill(DE_ARRAY_BEGIN(quad.color), DE_ARRAY_END(quad.color), command.color);
+	std::fill(DE_ARRAY_BEGIN(quad.depth), DE_ARRAY_END(quad.depth), command.params.depth);
+
+	renderer.render(quad);
+	GLU_CHECK();
+}
+
+void renderReference (const vector<ClearCommand>& clears, const tcu::PixelBufferAccess& dstColor, const tcu::PixelBufferAccess& dstStencil, int stencilBits)
+{
+	for (int ndx = 0; ndx < (int)clears.size(); ndx++)
+	{
+		const ClearCommand& clear = clears[ndx];
+
+		if (clear.buffers & GL_COLOR_BUFFER_BIT)
+			tcu::clear(tcu::getSubregion(dstColor, clear.rect.left, clear.rect.bottom, clear.rect.width, clear.rect.height), clear.color);
+
+		if (clear.buffers & GL_STENCIL_BUFFER_BIT && stencilBits > 0)
+		{
+			int maskedVal = clear.stencil & ((1<<stencilBits)-1);
+			tcu::clearStencil(tcu::getSubregion(dstStencil, clear.rect.left, clear.rect.bottom, clear.rect.width, clear.rect.height), maskedVal);
+		}
+
+		DE_ASSERT(clear.buffers == (clear.buffers & (GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT))); // \note Don't use clear for depths.
+	}
+}
+
+} // DepthStencilCaseUtil
+
+using namespace DepthStencilCaseUtil;
+
+class DepthStencilCase : public TestCase
+{
+public:
+							DepthStencilCase		(Context& context, const char* name, const char* desc, const std::vector<DepthStencilParams>& cases);
+							~DepthStencilCase		(void);
+
+	void					init					(void);
+	void					deinit					(void);
+
+	IterateResult			iterate					(void);
+
+private:
+							DepthStencilCase		(const DepthStencilCase& other);
+	DepthStencilCase&		operator=				(const DepthStencilCase& other);
+
+	std::vector<DepthStencilParams>					m_cases;
+
+	TestRenderTarget								m_renderTarget;
+	std::vector<ClearCommand>						m_baseClears;
+	std::vector<RenderCommand>						m_baseDepthRenders;
+	std::vector<RenderCommand>						m_visualizeCommands;
+	std::vector<RefRenderCommand>					m_refBaseDepthRenders;
+	std::vector<RefRenderCommand>					m_refVisualizeCommands;
+
+	gls::FragmentOpUtil::QuadRenderer*				m_renderer;
+	tcu::Surface*									m_refColorBuffer;
+	tcu::TextureLevel*								m_refDepthBuffer;
+	tcu::TextureLevel*								m_refStencilBuffer;
+	gls::FragmentOpUtil::ReferenceQuadRenderer*		m_refRenderer;
+
+	int												m_iterNdx;
+};
+
+DepthStencilCase::DepthStencilCase (Context& context, const char* name, const char* desc, const std::vector<DepthStencilParams>& cases)
+	: TestCase				(context, name, desc)
+	, m_cases				(cases)
+	, m_renderer			(DE_NULL)
+	, m_refColorBuffer		(DE_NULL)
+	, m_refDepthBuffer		(DE_NULL)
+	, m_refStencilBuffer	(DE_NULL)
+	, m_refRenderer			(DE_NULL)
+	, m_iterNdx				(0)
+{
+}
+
+DepthStencilCase::~DepthStencilCase (void)
+{
+	delete m_renderer;
+	delete m_refColorBuffer;
+	delete m_refDepthBuffer;
+	delete m_refStencilBuffer;
+	delete m_refRenderer;
+}
+
+void DepthStencilCase::init (void)
+{
+	DE_ASSERT(!m_renderer && !m_refColorBuffer && !m_refDepthBuffer && !m_refStencilBuffer && !m_refRenderer);
+
+	// Compute render target.
+	int viewportW	= de::min<int>(m_context.getRenderTarget().getWidth(), VIEWPORT_WIDTH);
+	int viewportH	= de::min<int>(m_context.getRenderTarget().getHeight(), VIEWPORT_HEIGHT);
+	m_renderTarget	= TestRenderTarget(viewportW, viewportH, m_context.getRenderTarget().getDepthBits(), m_context.getRenderTarget().getStencilBits());
+
+	// Compute base clears & visualization commands.
+	generateBaseClearAndDepthCommands(m_renderTarget, m_baseClears, m_baseDepthRenders);
+	generateDepthVisualizeCommands(m_renderTarget, m_visualizeCommands);
+	generateStencilVisualizeCommands(m_renderTarget, m_visualizeCommands);
+
+	// Translate to ref commands.
+	m_refBaseDepthRenders.resize(m_baseDepthRenders.size());
+	for (int ndx = 0; ndx < (int)m_baseDepthRenders.size(); ndx++)
+		translateCommand(m_baseDepthRenders[ndx], m_refBaseDepthRenders[ndx], m_renderTarget);
+
+	m_refVisualizeCommands.resize(m_visualizeCommands.size());
+	for (int ndx = 0; ndx < (int)m_visualizeCommands.size(); ndx++)
+		translateCommand(m_visualizeCommands[ndx], m_refVisualizeCommands[ndx], m_renderTarget);
+
+	m_renderer			= new gls::FragmentOpUtil::QuadRenderer(m_context.getRenderContext(), glu::GLSL_VERSION_100_ES);
+	m_refColorBuffer	= new tcu::Surface(viewportW, viewportH);
+	m_refDepthBuffer	= new tcu::TextureLevel(tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT),			viewportW, viewportH);
+	m_refStencilBuffer	= new tcu::TextureLevel(tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT32),	viewportW, viewportH);
+	m_refRenderer		= new gls::FragmentOpUtil::ReferenceQuadRenderer();
+
+	m_iterNdx			= 0;
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void DepthStencilCase::deinit (void)
+{
+	delete m_renderer;
+	delete m_refColorBuffer;
+	delete m_refDepthBuffer;
+	delete m_refStencilBuffer;
+	delete m_refRenderer;
+
+	m_renderer			= DE_NULL;
+	m_refColorBuffer	= DE_NULL;
+	m_refDepthBuffer	= DE_NULL;
+	m_refStencilBuffer	= DE_NULL;
+	m_refRenderer		= DE_NULL;
+
+	m_baseClears.clear();
+	m_baseDepthRenders.clear();
+	m_visualizeCommands.clear();
+	m_refBaseDepthRenders.clear();
+	m_refVisualizeCommands.clear();
+}
+
+DepthStencilCase::IterateResult DepthStencilCase::iterate (void)
+{
+	de::Random				rnd				(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
+	int						viewportX		= rnd.getInt(0, m_context.getRenderTarget().getWidth()-m_renderTarget.width);
+	int						viewportY		= rnd.getInt(0, m_context.getRenderTarget().getHeight()-m_renderTarget.height);
+	RenderCommand			testCmd;
+
+	tcu::Surface			renderedImg		(m_renderTarget.width, m_renderTarget.height);
+	tcu::RGBA				threshold		= m_context.getRenderTarget().getPixelFormat().getColorThreshold();
+
+	// Fill in test command for this iteration.
+	testCmd.color		= Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+	testCmd.colorMask	= tcu::BVec4(true);
+	testCmd.rect		= rr::WindowRectangle(0, 0, m_renderTarget.width, m_renderTarget.height);
+	testCmd.params		= m_cases[m_iterNdx];
+
+	if (m_iterNdx == 0)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Channels:\n"
+													"  RED: passing pixels\n"
+													"  GREEN: stencil values\n"
+													"  BLUE: depth values"
+							<< TestLog::EndMessage;
+	}
+
+	if (m_cases.size() > 1)
+		m_testCtx.getLog() << TestLog::Message << "Iteration " << m_iterNdx << "..." << TestLog::EndMessage;
+
+	m_testCtx.getLog() << m_cases[m_iterNdx];
+
+	// Submit render commands to gl GL.
+
+	// Base clears.
+	render(m_baseClears, viewportX, viewportY);
+
+	// Base depths.
+	for (vector<RenderCommand>::const_iterator cmd = m_baseDepthRenders.begin(); cmd != m_baseDepthRenders.end(); ++cmd)
+		render(*m_renderer, *cmd, viewportX, viewportY);
+
+	// Test command.
+	render(*m_renderer, testCmd, viewportX, viewportY);
+
+	// Visualization commands.
+	for (vector<RenderCommand>::const_iterator cmd = m_visualizeCommands.begin(); cmd != m_visualizeCommands.end(); ++cmd)
+		render(*m_renderer, *cmd, viewportX, viewportY);
+
+	// Re-enable all write masks.
+	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+	glDepthMask(GL_TRUE);
+	glStencilMask(~0u);
+
+	// Ask GPU to start rendering.
+	glFlush();
+
+	// Render reference while GPU is doing work.
+	{
+		RefRenderCommand refTestCmd;
+		translateCommand(testCmd, refTestCmd, m_renderTarget);
+
+		// Base clears.
+		renderReference(m_baseClears, m_refColorBuffer->getAccess(), m_refStencilBuffer->getAccess(), m_renderTarget.depthBits);
+
+		// Base depths.
+		for (vector<RefRenderCommand>::const_iterator cmd = m_refBaseDepthRenders.begin(); cmd != m_refBaseDepthRenders.end(); ++cmd)
+			m_refRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()),
+								  gls::FragmentOpUtil::getMultisampleAccess(m_refDepthBuffer->getAccess()),
+								  gls::FragmentOpUtil::getMultisampleAccess(m_refStencilBuffer->getAccess()),
+								  cmd->quad, cmd->state);
+
+		// Test command.
+		m_refRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()),
+							  gls::FragmentOpUtil::getMultisampleAccess(m_refDepthBuffer->getAccess()),
+							  gls::FragmentOpUtil::getMultisampleAccess(m_refStencilBuffer->getAccess()),
+							  refTestCmd.quad, refTestCmd.state);
+
+		// Visualization commands.
+		for (vector<RefRenderCommand>::const_iterator cmd = m_refVisualizeCommands.begin(); cmd != m_refVisualizeCommands.end(); ++cmd)
+			m_refRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()),
+								  gls::FragmentOpUtil::getMultisampleAccess(m_refDepthBuffer->getAccess()),
+								  gls::FragmentOpUtil::getMultisampleAccess(m_refStencilBuffer->getAccess()),
+								  cmd->quad, cmd->state);
+	}
+
+	// Read rendered image.
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedImg.getAccess());
+
+	m_iterNdx += 1;
+
+	// Compare to reference.
+	bool	isLastIter	= m_iterNdx >= (int)m_cases.size();
+	bool	compareOk	= tcu::pixelThresholdCompare(m_testCtx.getLog(), "CompareResult", "Image Comparison Result", *m_refColorBuffer, renderedImg, threshold,
+													 tcu::COMPARE_LOG_RESULT);
+
+	m_testCtx.getLog() << TestLog::Message << (compareOk ? "  Passed." : "  FAILED!") << TestLog::EndMessage;
+	if (!compareOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+	if (compareOk && !isLastIter)
+		return CONTINUE;
+	else
+		return STOP;
+}
+
+DepthStencilTests::DepthStencilTests (Context& context)
+	: TestCaseGroup(context, "depth_stencil", "Depth and Stencil Op Tests")
+{
+}
+
+DepthStencilTests::~DepthStencilTests (void)
+{
+}
+
+static void randomDepthStencilState (de::Random& rnd, DepthStencilParams& params)
+{
+	const float stencilTestProbability	= 0.8f;
+	const float depthTestProbability	= 0.7f;
+
+	static const deUint32 compareFuncs[] =
+	{
+		GL_NEVER,
+		GL_ALWAYS,
+		GL_LESS,
+		GL_LEQUAL,
+		GL_EQUAL,
+		GL_GEQUAL,
+		GL_GREATER,
+		GL_NOTEQUAL
+	};
+
+	static const deUint32 stencilOps[] =
+	{
+		GL_KEEP,
+		GL_ZERO,
+		GL_REPLACE,
+		GL_INCR,
+		GL_DECR,
+		GL_INVERT,
+		GL_INCR_WRAP,
+		GL_DECR_WRAP
+	};
+
+	static const float depthValues[] = { -1.0f, -0.8f, -0.6f, -0.4f, -0.2f, 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f };
+
+	params.visibleFace			= rnd.getBool() ? rr::FACETYPE_FRONT : rr::FACETYPE_BACK;
+	params.stencilTestEnabled	= rnd.getFloat() < stencilTestProbability;
+	params.depthTestEnabled		= !params.stencilTestEnabled || (rnd.getFloat() < depthTestProbability);
+
+	if (params.stencilTestEnabled)
+	{
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			params.stencil[ndx].function		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(compareFuncs), DE_ARRAY_END(compareFuncs));
+			params.stencil[ndx].reference		= rnd.getInt(-2, 260);
+			params.stencil[ndx].compareMask		= rnd.getUint32();
+			params.stencil[ndx].stencilFailOp	= rnd.choose<deUint32>(DE_ARRAY_BEGIN(stencilOps), DE_ARRAY_END(stencilOps));
+			params.stencil[ndx].depthFailOp		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(stencilOps), DE_ARRAY_END(stencilOps));
+			params.stencil[ndx].depthPassOp		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(stencilOps), DE_ARRAY_END(stencilOps));
+			params.stencil[ndx].writeMask		= rnd.getUint32();
+		}
+	}
+
+	if (params.depthTestEnabled)
+	{
+		params.depthFunc		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(compareFuncs), DE_ARRAY_END(compareFuncs));
+		params.depth			= rnd.choose<float>(DE_ARRAY_BEGIN(depthValues), DE_ARRAY_END(depthValues));
+		params.depthWriteMask	= rnd.getBool();
+	}
+}
+
+void DepthStencilTests::init (void)
+{
+	static const struct
+	{
+		const char*	name;
+		deUint32	func;
+	} compareFuncs[] =
+	{
+		{ "never",		GL_NEVER	},
+		{ "always",		GL_ALWAYS	},
+		{ "less",		GL_LESS		},
+		{ "lequal",		GL_LEQUAL	},
+		{ "equal",		GL_EQUAL	},
+		{ "gequal",		GL_GEQUAL	},
+		{ "greater",	GL_GREATER	},
+		{ "notequal",	GL_NOTEQUAL	}
+	};
+
+	static const struct
+	{
+		const char*	name;
+		deUint32	op;
+	} stencilOps[] =
+	{
+		{ "keep",		GL_KEEP			},
+		{ "zero",		GL_ZERO			},
+		{ "replace",	GL_REPLACE		},
+		{ "incr",		GL_INCR			},
+		{ "decr",		GL_DECR			},
+		{ "invert",		GL_INVERT		},
+		{ "incr_wrap",	GL_INCR_WRAP	},
+		{ "decr_wrap",	GL_DECR_WRAP	}
+	};
+
+	static const struct
+	{
+		rr::FaceType	visibleFace;
+		deUint32		sFail;
+		deUint32		dFail;
+		deUint32		dPass;
+		int				stencilRef;
+		deUint32		compareMask;
+		deUint32		writeMask;
+		float			depth;
+	} functionCases[] =
+	{
+		{ rr::FACETYPE_BACK,	GL_DECR,		GL_INCR,	GL_INVERT,		4,	~0u, ~0u,	-0.7f },
+		{ rr::FACETYPE_FRONT,	GL_DECR,		GL_INCR,	GL_INVERT,		2,	~0u, ~0u,	0.0f },
+		{ rr::FACETYPE_BACK,	GL_DECR,		GL_INCR,	GL_INVERT,		1,	~0u, ~0u,	0.2f },
+		{ rr::FACETYPE_FRONT,	GL_DECR_WRAP,	GL_INVERT,	GL_REPLACE,		4,	~0u, ~0u,	1.0f }
+	};
+
+	// All combinations of depth stencil functions.
+	{
+		tcu::TestCaseGroup* functionsGroup = new tcu::TestCaseGroup(m_testCtx, "stencil_depth_funcs", "Combinations of Depth and Stencil Functions");
+		addChild(functionsGroup);
+
+		for (int stencilFunc = 0; stencilFunc < DE_LENGTH_OF_ARRAY(compareFuncs)+1; stencilFunc++)
+		{
+			// One extra: depth test disabled.
+			for (int depthFunc = 0; depthFunc < DE_LENGTH_OF_ARRAY(compareFuncs)+1; depthFunc++)
+			{
+				DepthStencilParams	params;
+				ostringstream		name;
+				bool				hasStencilFunc	= de::inBounds(stencilFunc, 0, DE_LENGTH_OF_ARRAY(compareFuncs));
+				bool				hasDepthFunc	= de::inBounds(depthFunc, 0, DE_LENGTH_OF_ARRAY(compareFuncs));
+
+				if (hasStencilFunc)
+					name << "stencil_" << compareFuncs[stencilFunc].name << "_";
+				else
+					name << "no_stencil_";
+
+				if (hasDepthFunc)
+					name << "depth_" << compareFuncs[depthFunc].name;
+				else
+					name << "no_depth";
+
+				params.depthFunc			= hasDepthFunc ? compareFuncs[depthFunc].func : 0;
+				params.depthTestEnabled		= hasDepthFunc;
+				params.depthWriteMask		= true;
+
+				params.stencilTestEnabled	= hasStencilFunc;
+
+				vector<DepthStencilParams> cases;
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(functionCases); ndx++)
+				{
+					rr::FaceType	visible		= functionCases[ndx].visibleFace;
+					rr::FaceType	notVisible	= visible == rr::FACETYPE_FRONT ? rr::FACETYPE_BACK : rr::FACETYPE_FRONT;
+
+					params.depth								= functionCases[ndx].depth;
+					params.visibleFace							= visible;
+
+					params.stencil[visible].function			= hasStencilFunc ? compareFuncs[stencilFunc].func : 0;
+					params.stencil[visible].reference			= functionCases[ndx].stencilRef;
+					params.stencil[visible].stencilFailOp		= functionCases[ndx].sFail;
+					params.stencil[visible].depthFailOp			= functionCases[ndx].dFail;
+					params.stencil[visible].depthPassOp			= functionCases[ndx].dPass;
+					params.stencil[visible].compareMask			= functionCases[ndx].compareMask;
+					params.stencil[visible].writeMask			= functionCases[ndx].writeMask;
+
+					params.stencil[notVisible].function			= GL_ALWAYS;
+					params.stencil[notVisible].reference		= 0;
+					params.stencil[notVisible].stencilFailOp	= GL_REPLACE;
+					params.stencil[notVisible].depthFailOp		= GL_REPLACE;
+					params.stencil[notVisible].depthPassOp		= GL_REPLACE;
+					params.stencil[notVisible].compareMask		= 0u;
+					params.stencil[notVisible].writeMask		= ~0u;
+
+
+					cases.push_back(params);
+				}
+
+				functionsGroup->addChild(new DepthStencilCase(m_context, name.str().c_str(), "", cases));
+			}
+		}
+	}
+
+	static const struct
+	{
+		rr::FaceType	visibleFace;
+		deUint32		func;
+		int				ref;
+		deUint32		compareMask;
+		deUint32		writeMask;
+	} opCombinationCases[] =
+	{
+		{ rr::FACETYPE_BACK,	GL_LESS,		4,		~0u,	~0u	},
+		{ rr::FACETYPE_FRONT,	GL_GREATER,		2,		~0u,	~0u	},
+		{ rr::FACETYPE_BACK,	GL_EQUAL,		3,		~2u,	~0u	},
+		{ rr::FACETYPE_FRONT,	GL_NOTEQUAL,	1,		~0u,	~1u	}
+	};
+
+	// All combinations of stencil ops.
+	{
+		tcu::TestCaseGroup* opCombinationGroup = new tcu::TestCaseGroup(m_testCtx, "stencil_ops", "Stencil Op Combinations");
+		addChild(opCombinationGroup);
+
+		for (int sFail = 0; sFail < DE_LENGTH_OF_ARRAY(stencilOps); sFail++)
+		{
+			for (int dFail = 0; dFail < DE_LENGTH_OF_ARRAY(stencilOps); dFail++)
+			{
+				for (int dPass = 0; dPass < DE_LENGTH_OF_ARRAY(stencilOps); dPass++)
+				{
+					DepthStencilParams	params;
+					ostringstream		name;
+
+					name << stencilOps[sFail].name << "_" << stencilOps[dFail].name << "_" << stencilOps[dPass].name;
+
+					params.depthFunc			= GL_LEQUAL;
+					params.depth				= 0.0f;
+					params.depthTestEnabled		= true;
+					params.depthWriteMask		= true;
+
+					params.stencilTestEnabled	= true;
+
+					vector<DepthStencilParams> cases;
+					for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(opCombinationCases); ndx++)
+					{
+						rr::FaceType	visible		= opCombinationCases[ndx].visibleFace;
+						rr::FaceType	notVisible	= visible == rr::FACETYPE_FRONT ? rr::FACETYPE_BACK : rr::FACETYPE_FRONT;
+
+						params.visibleFace							= visible;
+
+						params.stencil[visible].function			= opCombinationCases[ndx].func;
+						params.stencil[visible].reference			= opCombinationCases[ndx].ref;
+						params.stencil[visible].stencilFailOp		= stencilOps[sFail].op;
+						params.stencil[visible].depthFailOp			= stencilOps[dFail].op;
+						params.stencil[visible].depthPassOp			= stencilOps[dPass].op;
+						params.stencil[visible].compareMask			= opCombinationCases[ndx].compareMask;
+						params.stencil[visible].writeMask			= opCombinationCases[ndx].writeMask;
+
+						params.stencil[notVisible].function			= GL_ALWAYS;
+						params.stencil[notVisible].reference		= 0;
+						params.stencil[notVisible].stencilFailOp	= GL_REPLACE;
+						params.stencil[notVisible].depthFailOp		= GL_REPLACE;
+						params.stencil[notVisible].depthPassOp		= GL_REPLACE;
+						params.stencil[notVisible].compareMask		= 0u;
+						params.stencil[notVisible].writeMask		= ~0u;
+
+
+						cases.push_back(params);
+					}
+
+					opCombinationGroup->addChild(new DepthStencilCase(m_context, name.str().c_str(), "", cases));
+				}
+			}
+		}
+	}
+
+	// Write masks
+	{
+		tcu::TestCaseGroup* writeMaskGroup = new tcu::TestCaseGroup(m_testCtx, "write_mask", "Depth and Stencil Write Masks");
+		addChild(writeMaskGroup);
+
+		// Depth mask
+		{
+			DepthStencilParams params;
+
+			params.depthFunc			= GL_LEQUAL;
+			params.depth				= 0.0f;
+			params.depthTestEnabled		= true;
+			params.stencilTestEnabled	= true;
+
+			params.stencil[rr::FACETYPE_FRONT].function			= GL_NOTEQUAL;
+			params.stencil[rr::FACETYPE_FRONT].reference		= 1;
+			params.stencil[rr::FACETYPE_FRONT].stencilFailOp	= GL_INVERT;
+			params.stencil[rr::FACETYPE_FRONT].depthFailOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_FRONT].depthPassOp		= GL_DECR;
+			params.stencil[rr::FACETYPE_FRONT].compareMask		= ~0u;
+			params.stencil[rr::FACETYPE_FRONT].writeMask		= ~0u;
+
+			params.stencil[rr::FACETYPE_BACK].function			= GL_ALWAYS;
+			params.stencil[rr::FACETYPE_BACK].reference			= 0;
+			params.stencil[rr::FACETYPE_BACK].stencilFailOp		= GL_REPLACE;
+			params.stencil[rr::FACETYPE_BACK].depthFailOp		= GL_INVERT;
+			params.stencil[rr::FACETYPE_BACK].depthPassOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_BACK].compareMask		= ~0u;
+			params.stencil[rr::FACETYPE_BACK].writeMask			= ~0u;
+
+			vector<DepthStencilParams> cases;
+
+			// Case 1: front, depth write enabled
+			params.visibleFace		= rr::FACETYPE_FRONT;
+			params.depthWriteMask	= true;
+			cases.push_back(params);
+
+			// Case 2: front, depth write disabled
+			params.visibleFace		= rr::FACETYPE_FRONT;
+			params.depthWriteMask	= false;
+			cases.push_back(params);
+
+			// Case 3: back, depth write enabled
+			params.visibleFace		= rr::FACETYPE_BACK;
+			params.depthWriteMask	= true;
+			cases.push_back(params);
+
+			// Case 4: back, depth write disabled
+			params.visibleFace		= rr::FACETYPE_BACK;
+			params.depthWriteMask	= false;
+			cases.push_back(params);
+
+			writeMaskGroup->addChild(new DepthStencilCase(m_context, "depth", "Depth Write Mask", cases));
+		}
+
+		// Stencil write masks.
+		{
+			static const struct
+			{
+				rr::FaceType	visibleFace;
+				deUint32		frontWriteMask;
+				deUint32		backWriteMask;
+			} stencilWmaskCases[] =
+			{
+				{ rr::FACETYPE_FRONT,	~0u,	0u		},
+				{ rr::FACETYPE_FRONT,	0u,		~0u		},
+				{ rr::FACETYPE_FRONT,	0xfu,	0xf0u	},
+				{ rr::FACETYPE_FRONT,	0x2u,	0x4u	},
+				{ rr::FACETYPE_BACK,	0u,		~0u		},
+				{ rr::FACETYPE_BACK,	~0u,	0u		},
+				{ rr::FACETYPE_BACK,	0xf0u,	0xfu	},
+				{ rr::FACETYPE_BACK,	0x4u,	0x2u	}
+			};
+
+			DepthStencilParams params;
+
+			params.depthFunc			= GL_LEQUAL;
+			params.depth				= 0.0f;
+			params.depthTestEnabled		= true;
+			params.depthWriteMask		= true;
+			params.stencilTestEnabled	= true;
+
+			params.stencil[rr::FACETYPE_FRONT].function			= GL_NOTEQUAL;
+			params.stencil[rr::FACETYPE_FRONT].reference		= 1;
+			params.stencil[rr::FACETYPE_FRONT].stencilFailOp	= GL_INVERT;
+			params.stencil[rr::FACETYPE_FRONT].depthFailOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_FRONT].depthPassOp		= GL_DECR;
+			params.stencil[rr::FACETYPE_FRONT].compareMask		= ~0u;
+
+			params.stencil[rr::FACETYPE_BACK].function			= GL_ALWAYS;
+			params.stencil[rr::FACETYPE_BACK].reference			= 0;
+			params.stencil[rr::FACETYPE_BACK].stencilFailOp		= GL_REPLACE;
+			params.stencil[rr::FACETYPE_BACK].depthFailOp		= GL_INVERT;
+			params.stencil[rr::FACETYPE_BACK].depthPassOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_BACK].compareMask		= ~0u;
+
+			vector<DepthStencilParams> cases;
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(stencilWmaskCases); ndx++)
+			{
+				params.visibleFace								= stencilWmaskCases[ndx].visibleFace;
+				params.stencil[rr::FACETYPE_FRONT].writeMask	= stencilWmaskCases[ndx].frontWriteMask;
+				params.stencil[rr::FACETYPE_BACK].writeMask		= stencilWmaskCases[ndx].backWriteMask;
+				cases.push_back(params);
+			}
+
+			writeMaskGroup->addChild(new DepthStencilCase(m_context, "stencil", "Stencil Write Mask", cases));
+		}
+
+		// Depth & stencil write masks.
+		{
+			static const struct
+			{
+				bool			depthWriteMask;
+				rr::FaceType	visibleFace;
+				deUint32		frontWriteMask;
+				deUint32		backWriteMask;
+			} depthStencilWmaskCases[] =
+			{
+				{ false,	rr::FACETYPE_FRONT,		~0u,	0u		},
+				{ false,	rr::FACETYPE_FRONT,		0u,		~0u		},
+				{ false,	rr::FACETYPE_FRONT,		0xfu,	0xf0u	},
+				{ true,		rr::FACETYPE_FRONT,		~0u,	0u		},
+				{ true,		rr::FACETYPE_FRONT,		0u,		~0u		},
+				{ true,		rr::FACETYPE_FRONT,		0xfu,	0xf0u	},
+				{ false,	rr::FACETYPE_BACK,		0u,		~0u		},
+				{ false,	rr::FACETYPE_BACK,		~0u,	0u		},
+				{ false,	rr::FACETYPE_BACK,		0xf0u,	0xfu	},
+				{ true,		rr::FACETYPE_BACK,		0u,		~0u		},
+				{ true,		rr::FACETYPE_BACK,		~0u,	0u		},
+				{ true,		rr::FACETYPE_BACK,		0xf0u,	0xfu	}
+			};
+
+			DepthStencilParams params;
+
+			params.depthFunc			= GL_LEQUAL;
+			params.depth				= 0.0f;
+			params.depthTestEnabled		= true;
+			params.depthWriteMask		= true;
+			params.stencilTestEnabled	= true;
+
+			params.stencil[rr::FACETYPE_FRONT].function			= GL_NOTEQUAL;
+			params.stencil[rr::FACETYPE_FRONT].reference		= 1;
+			params.stencil[rr::FACETYPE_FRONT].stencilFailOp	= GL_INVERT;
+			params.stencil[rr::FACETYPE_FRONT].depthFailOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_FRONT].depthPassOp		= GL_DECR;
+			params.stencil[rr::FACETYPE_FRONT].compareMask		= ~0u;
+
+			params.stencil[rr::FACETYPE_BACK].function			= GL_ALWAYS;
+			params.stencil[rr::FACETYPE_BACK].reference			= 0;
+			params.stencil[rr::FACETYPE_BACK].stencilFailOp		= GL_REPLACE;
+			params.stencil[rr::FACETYPE_BACK].depthFailOp		= GL_INVERT;
+			params.stencil[rr::FACETYPE_BACK].depthPassOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_BACK].compareMask		= ~0u;
+
+			vector<DepthStencilParams> cases;
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilWmaskCases); ndx++)
+			{
+				params.depthWriteMask							= depthStencilWmaskCases[ndx].depthWriteMask;
+				params.visibleFace								= depthStencilWmaskCases[ndx].visibleFace;
+				params.stencil[rr::FACETYPE_FRONT].writeMask	= depthStencilWmaskCases[ndx].frontWriteMask;
+				params.stencil[rr::FACETYPE_BACK].writeMask		= depthStencilWmaskCases[ndx].backWriteMask;
+				cases.push_back(params);
+			}
+
+			writeMaskGroup->addChild(new DepthStencilCase(m_context, "both", "Depth and Stencil Write Masks", cases));
+		}
+	}
+
+	// Randomized cases
+	{
+		tcu::TestCaseGroup* randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Randomized Depth and Stencil Test Cases");
+		addChild(randomGroup);
+
+		for (int caseNdx = 0; caseNdx < NUM_RANDOM_CASES; caseNdx++)
+		{
+			vector<DepthStencilParams>	subCases	(NUM_RANDOM_SUB_CASES);
+			de::Random					rnd			(deInt32Hash(caseNdx) ^ deInt32Hash(m_testCtx.getCommandLine().getBaseSeed()));
+
+			for (vector<DepthStencilParams>::iterator iter = subCases.begin(); iter != subCases.end(); ++iter)
+				randomDepthStencilState(rnd, *iter);
+
+			randomGroup->addChild(new DepthStencilCase(m_context, de::toString(caseNdx).c_str(), "", subCases));
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fDepthStencilTests.hpp b/modules/gles2/functional/es2fDepthStencilTests.hpp
new file mode 100644
index 0000000..0f3e911
--- /dev/null
+++ b/modules/gles2/functional/es2fDepthStencilTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FDEPTHSTENCILTESTS_HPP
+#define _ES2FDEPTHSTENCILTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth & stencil tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class DepthStencilTests : public TestCaseGroup
+{
+public:
+						DepthStencilTests	(Context& context);
+						~DepthStencilTests	(void);
+
+	void				init				(void);
+
+private:
+						DepthStencilTests	(const DepthStencilTests& other);
+	DepthStencilTests&	operator=			(const DepthStencilTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FDEPTHSTENCILTESTS_HPP
diff --git a/modules/gles2/functional/es2fDepthTests.cpp b/modules/gles2/functional/es2fDepthTests.cpp
new file mode 100644
index 0000000..0f04598
--- /dev/null
+++ b/modules/gles2/functional/es2fDepthTests.cpp
@@ -0,0 +1,277 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fDepthTests.hpp"
+
+#include "tcuTestLog.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluStrUtil.hpp"
+
+#include "sglrContextUtil.hpp"
+#include "sglrReferenceContext.hpp"
+#include "sglrGLContext.hpp"
+
+#include "deRandom.hpp"
+
+#include "glwEnums.hpp"
+
+using tcu::RGBA;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class DepthShader : public sglr::ShaderProgram
+{
+public:
+								DepthShader		(void);
+
+	void						setColor		(sglr::Context& ctx, deUint32 programID, const tcu::Vec4& color);
+
+private:
+	void						shadeVertices	(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void						shadeFragments	(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+	const sglr::UniformSlot&	u_color;
+};
+
+DepthShader::DepthShader (void)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = a_position;\n"
+														"}\n")
+							<< sglr::pdec::FragmentSource("uniform mediump vec4 u_color;\n"
+														  "void main (void)\n"
+														  "{\n"
+														  "	gl_FragColor = u_color;\n"
+														  "}\n"))
+	, u_color(getUniformByName("u_color"))
+{
+}
+
+void DepthShader::setColor (sglr::Context& ctx, deUint32 programID, const tcu::Vec4& color)
+{
+	ctx.useProgram(programID);
+	ctx.uniform4fv(ctx.getUniformLocation(programID, "u_color"), 1, color.getPtr());
+}
+
+void DepthShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		packets[packetNdx]->position = rr::readVertexAttribFloat(inputs[0], packets[packetNdx]->instanceNdx, packets[packetNdx]->vertexNdx);
+}
+
+void DepthShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4 color(u_color.value.f4);
+
+	DE_UNREF(packets);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+}
+
+// \todo [2011-07-11 pyry] This code is duplicated in a quite many places. Move it to some utility?
+class DepthCase : public TestCase
+{
+public:
+								DepthCase				(Context& context, const char* name, const char* description);
+	virtual						~DepthCase				(void) {}
+
+	virtual IterateResult		iterate					(void);
+	virtual void				render					(sglr::Context& context) = DE_NULL;
+};
+
+DepthCase::DepthCase (Context& context, const char* name, const char* description)
+	: TestCase(context, name, description)
+{
+}
+
+TestCase::IterateResult DepthCase::iterate (void)
+{
+	tcu::Vec4					clearColor				= tcu::Vec4(0.125f, 0.25f, 0.5f, 1.0f);
+	glu::RenderContext&			renderCtx				= m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget			= renderCtx.getRenderTarget();
+	tcu::TestLog&				log						= m_testCtx.getLog();
+	const char*					failReason				= DE_NULL;
+
+	// Position & size for context
+	de::Random rnd(deStringHash(getName()));
+
+	int		width	= deMin32(renderTarget.getWidth(),	128);
+	int		height	= deMin32(renderTarget.getHeight(),	128);
+	int		x		= rnd.getInt(0, renderTarget.getWidth()		- width);
+	int		y		= rnd.getInt(0, renderTarget.getHeight()	- height);
+
+	tcu::Surface	gles2Frame	(width, height);
+	tcu::Surface	refFrame	(width, height);
+	deUint32		gles2Error;
+	deUint32		refError;
+
+	// Render using GLES2
+	{
+		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(x, y, width, height));
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		render(context); // Call actual render func
+		context.readPixels(gles2Frame, 0, 0, width, height);
+		gles2Error = context.getError();
+	}
+
+	// Render reference image
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), renderTarget.getDepthBits(), renderTarget.getStencilBits(), width, height);
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		render(context);
+		context.readPixels(refFrame, 0, 0, width, height);
+		refError = context.getError();
+	}
+
+	// Compare error codes
+	bool errorCodesOk = (gles2Error == refError);
+
+	if (!errorCodesOk)
+	{
+		log << tcu::TestLog::Message << "Error code mismatch: got " << glu::getErrorStr(gles2Error) << ", expected " << glu::getErrorStr(refError) << tcu::TestLog::EndMessage;
+		failReason = "Got unexpected error";
+	}
+
+	// Compare images
+	const float		threshold	= 0.02f;
+	bool			imagesOk	= tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, gles2Frame, threshold, tcu::COMPARE_LOG_RESULT);
+
+	if (!imagesOk && !failReason)
+		failReason = "Image comparison failed";
+
+	// Store test result
+	bool isOk = errorCodesOk && imagesOk;
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: failReason);
+
+	return STOP;
+}
+
+class DepthCompareCase : public DepthCase
+{
+public:
+	DepthCompareCase (Context& context, const char* name, const char* description, deUint32 compareOp)
+		: DepthCase		(context, name, description)
+		, m_compareOp	(compareOp)
+	{
+	}
+
+	void render (sglr::Context& context)
+	{
+		using tcu::Vec3;
+
+		DepthShader	shader;
+		deUint32	shaderID = context.createProgram(&shader);
+
+		tcu::Vec4	red		(1.0f, 0.0f, 0.0f, 1.0);
+		tcu::Vec4	green	(0.0f, 1.0f, 0.0f, 1.0f);
+
+		// Clear depth to 1
+		context.clearDepthf(1.0f);
+		context.clear(GL_DEPTH_BUFFER_BIT);
+
+		// Enable depth test.
+		context.enable(GL_DEPTH_TEST);
+
+		// Upper left: two quads with same depth
+		context.depthFunc(GL_ALWAYS);
+		shader.setColor(context, shaderID, red);
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.2f),	Vec3(0.0f, 0.0f, 0.2f));
+		context.depthFunc(m_compareOp);
+		shader.setColor(context, shaderID, green);
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.2f),	Vec3(0.0f, 0.0f, 0.2f));
+
+		// Lower left: two quads, d1 < d2
+		context.depthFunc(GL_ALWAYS);
+		shader.setColor(context, shaderID, red);
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, 0.0f, -0.4f),	Vec3(0.0f, 1.0f, -0.4f));
+		context.depthFunc(m_compareOp);
+		shader.setColor(context, shaderID, green);
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, 0.0f, -0.1f),	Vec3(0.0f, 1.0f, -0.1f));
+
+		// Upper right: two quads, d1 > d2
+		context.depthFunc(GL_ALWAYS);
+		shader.setColor(context, shaderID, red);
+		sglr::drawQuad(context, shaderID, Vec3(0.0f, -1.0f, 0.5f),	Vec3(1.0f, 0.0f, 0.5f));
+		context.depthFunc(m_compareOp);
+		shader.setColor(context, shaderID, green);
+		sglr::drawQuad(context, shaderID, Vec3(0.0f, -1.0f, 0.3f),	Vec3(1.0f, 0.0f, 0.3f));
+
+		// Lower right: two quads, d1 = 0, d2 = [-1..1]
+		context.depthFunc(GL_ALWAYS);
+		shader.setColor(context, shaderID, red);
+		sglr::drawQuad(context, shaderID, Vec3(0.0f, 0.0f, 0.0f),	Vec3(1.0f, 1.0f, 0.0f));
+		context.depthFunc(m_compareOp);
+		shader.setColor(context, shaderID, green);
+		sglr::drawQuad(context, shaderID, Vec3(0.0f, 0.0f, -1.0f),	Vec3(1.0f, 1.0f, 1.0f));
+	}
+
+private:
+	deUint32	m_compareOp;
+};
+
+DepthTests::DepthTests (Context& context)
+	: TestCaseGroup(context, "depth", "Depth Tests")
+{
+}
+
+DepthTests::~DepthTests (void)
+{
+}
+
+void DepthTests::init (void)
+{
+	addChild(new DepthCompareCase(m_context, "cmp_always",				"Always pass depth test",				GL_ALWAYS));
+	addChild(new DepthCompareCase(m_context, "cmp_never",				"Never pass depth test",				GL_NEVER));
+	addChild(new DepthCompareCase(m_context, "cmp_equal",				"Depth compare: equal",					GL_EQUAL));
+	addChild(new DepthCompareCase(m_context, "cmp_not_equal",			"Depth compare: not equal",				GL_NOTEQUAL));
+	addChild(new DepthCompareCase(m_context, "cmp_less_than",			"Depth compare: less than",				GL_LESS));
+	addChild(new DepthCompareCase(m_context, "cmp_less_or_equal",		"Depth compare: less than or equal",	GL_LEQUAL));
+	addChild(new DepthCompareCase(m_context, "cmp_greater_than",		"Depth compare: greater than",			GL_GREATER));
+	addChild(new DepthCompareCase(m_context, "cmp_greater_or_equal",	"Depth compare: greater than or equal",	GL_GEQUAL));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fDepthTests.hpp b/modules/gles2/functional/es2fDepthTests.hpp
new file mode 100644
index 0000000..1770d45
--- /dev/null
+++ b/modules/gles2/functional/es2fDepthTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FDEPTHTESTS_HPP
+#define _ES2FDEPTHTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class DepthTests : public TestCaseGroup
+{
+public:
+						DepthTests			(Context& context);
+						~DepthTests			(void);
+
+	void				init				(void);
+
+private:
+						DepthTests			(const DepthTests& other);
+	DepthTests&			operator=			(const DepthTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FDEPTHTESTS_HPP
diff --git a/modules/gles2/functional/es2fDitheringTests.cpp b/modules/gles2/functional/es2fDitheringTests.cpp
new file mode 100644
index 0000000..6fa1e27
--- /dev/null
+++ b/modules/gles2/functional/es2fDitheringTests.cpp
@@ -0,0 +1,541 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Dithering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fDitheringTests.hpp"
+#include "gluRenderContext.hpp"
+#include "gluDefs.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuVector.hpp"
+#include "tcuPixelFormat.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuCommandLine.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+#include "deMath.h"
+
+#include "glw.h"
+
+#include <string>
+#include <algorithm>
+
+namespace deqp
+{
+
+using tcu::Vec4;
+using tcu::IVec4;
+using tcu::TestLog;
+using gls::FragmentOpUtil::QuadRenderer;
+using gls::FragmentOpUtil::Quad;
+using tcu::PixelFormat;
+using tcu::Surface;
+using de::Random;
+using std::vector;
+using std::string;
+
+namespace gles2
+{
+namespace Functional
+{
+
+static const char* const s_channelNames[4] = { "red", "green", "blue", "alpha" };
+
+static inline IVec4 pixelFormatToIVec4 (const PixelFormat& format)
+{
+	return IVec4(format.redBits, format.greenBits, format.blueBits, format.alphaBits);
+}
+
+template<typename T>
+static inline string choiceListStr (const vector<T>& choices)
+{
+	string result;
+	for (int i = 0; i < (int)choices.size(); i++)
+	{
+		if (i == (int)choices.size()-1)
+			result += " or ";
+		else if (i > 0)
+			result += ", ";
+		result += de::toString(choices[i]);
+	}
+	return result;
+}
+
+class DitheringCase : public tcu::TestCase
+{
+public:
+	enum PatternType
+	{
+		PATTERNTYPE_GRADIENT = 0,
+		PATTERNTYPE_UNICOLORED_QUAD,
+
+		PATTERNTYPE_LAST
+	};
+
+											DitheringCase				(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, bool isEnabled, PatternType patternType, const tcu::Vec4& color);
+											~DitheringCase				(void);
+
+	IterateResult							iterate						(void);
+	void									init						(void);
+	void									deinit						(void);
+
+	static const char*						getPatternTypeName			(PatternType type);
+
+private:
+	bool									checkColor					(const tcu::Vec4& inputClr, const tcu::RGBA& renderedClr, bool logErrors) const;
+
+	bool									drawAndCheckGradient		(bool isVerticallyIncreasing, const tcu::Vec4& highColor) const;
+	bool									drawAndCheckUnicoloredQuad	(const tcu::Vec4& color) const;
+
+	const glu::RenderContext&				m_renderCtx;
+
+	const bool								m_ditheringEnabled;
+	const PatternType						m_patternType;
+	const tcu::Vec4							m_color;
+
+	const tcu::PixelFormat					m_renderFormat;
+
+	const QuadRenderer*						m_renderer;
+	int										m_iteration;
+};
+
+const char* DitheringCase::getPatternTypeName (const PatternType type)
+{
+	switch (type)
+	{
+		case PATTERNTYPE_GRADIENT:			return "gradient";
+		case PATTERNTYPE_UNICOLORED_QUAD:	return "unicolored_quad";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+
+DitheringCase::DitheringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* const name, const char* const description, const bool ditheringEnabled, const PatternType patternType, const Vec4& color)
+	: TestCase				(testCtx, name, description)
+	, m_renderCtx			(renderCtx)
+	, m_ditheringEnabled	(ditheringEnabled)
+	, m_patternType			(patternType)
+	, m_color				(color)
+	, m_renderFormat		(renderCtx.getRenderTarget().getPixelFormat())
+	, m_renderer			(DE_NULL)
+	, m_iteration			(0)
+{
+}
+
+DitheringCase::~DitheringCase (void)
+{
+	DitheringCase::deinit();
+}
+
+void DitheringCase::init (void)
+{
+	DE_ASSERT(!m_renderer);
+	m_renderer = new QuadRenderer(m_renderCtx, glu::GLSL_VERSION_100_ES);
+	m_iteration = 0;
+}
+
+void DitheringCase::deinit (void)
+{
+	delete m_renderer;
+	m_renderer = DE_NULL;
+}
+
+bool DitheringCase::checkColor (const Vec4& inputClr, const tcu::RGBA& renderedClr, const bool logErrors) const
+{
+	const IVec4		channelBits		= pixelFormatToIVec4(m_renderFormat);
+	bool			allChannelsOk	= true;
+
+	for (int chanNdx = 0; chanNdx < 4; chanNdx++)
+	{
+		if (channelBits[chanNdx] == 0)
+			continue;
+
+		const int		channelMax			= (1 << channelBits[chanNdx]) - 1;
+		const float		scaledInput			= inputClr[chanNdx] * (float)channelMax;
+		const bool		useRoundingMargin	= deFloatAbs(scaledInput - deFloatRound(scaledInput)) < 0.0001f;
+		vector<int>		channelChoices;
+
+		channelChoices.push_back(de::min(channelMax,	(int)deFloatCeil(scaledInput)));
+		channelChoices.push_back(de::max(0,				(int)deFloatCeil(scaledInput) - 1));
+
+		// If the input color results in a scaled value that is very close to an integer, account for a little bit of possible inaccuracy.
+		if (useRoundingMargin)
+		{
+			if (scaledInput > deFloatRound(scaledInput))
+				channelChoices.push_back((int)deFloatCeil(scaledInput) - 2);
+			else
+				channelChoices.push_back((int)deFloatCeil(scaledInput) + 1);
+		}
+
+		std::sort(channelChoices.begin(), channelChoices.end());
+
+		{
+			const int		renderedClrInFormat	= (int)deFloatRound((float)(renderedClr.toIVec()[chanNdx] * channelMax) / 255.0f);
+			bool			goodChannel			= false;
+
+			for (int i = 0; i < (int)channelChoices.size(); i++)
+			{
+				if (renderedClrInFormat == channelChoices[i])
+				{
+					goodChannel = true;
+					break;
+				}
+			}
+
+			if (!goodChannel)
+			{
+				if (logErrors)
+				{
+					m_testCtx.getLog() << TestLog::Message
+									   << "Failure: " << channelBits[chanNdx] << "-bit " << s_channelNames[chanNdx] << " channel is " << renderedClrInFormat
+									   << ", should be " << choiceListStr(channelChoices)
+									   << " (corresponding fragment color channel is " << inputClr[chanNdx] << ")"
+									   << TestLog::EndMessage
+									   << TestLog::Message
+									   << "Note: " << inputClr[chanNdx] << " * (" << channelMax + 1 << "-1) = " << scaledInput
+									   << TestLog::EndMessage;
+
+					if (useRoundingMargin)
+					{
+						m_testCtx.getLog() << TestLog::Message
+										   << "Note: one extra color candidate was allowed because fragmentColorChannel * (2^bits-1) is close to an integer"
+										   << TestLog::EndMessage;
+					}
+				}
+
+				allChannelsOk = false;
+			}
+		}
+	}
+
+	return allChannelsOk;
+}
+
+bool DitheringCase::drawAndCheckGradient (const bool isVerticallyIncreasing, const Vec4& highColor) const
+{
+	TestLog&					log					= m_testCtx.getLog();
+	Random						rnd					(deStringHash(getName()));
+	const int					maxViewportWid		= 256;
+	const int					maxViewportHei		= 256;
+	const int					viewportWid			= de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid);
+	const int					viewportHei			= de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei);
+	const int					viewportX			= rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid);
+	const int					viewportY			= rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei);
+	const Vec4					quadClr0			(0.0f, 0.0f, 0.0f, 0.0f);
+	const Vec4&					quadClr1			= highColor;
+	Quad						quad;
+	Surface						renderedImg			(viewportWid, viewportHei);
+
+	GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei));
+
+	log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
+
+	if (m_ditheringEnabled)
+		GLU_CHECK_CALL(glEnable(GL_DITHER));
+	else
+		GLU_CHECK_CALL(glDisable(GL_DITHER));
+
+	log << TestLog::Message << "Drawing a " << (isVerticallyIncreasing ? "vertically" : "horizontally") << " increasing gradient" << TestLog::EndMessage;
+
+	quad.color[0] = quadClr0;
+	quad.color[1] = isVerticallyIncreasing ? quadClr1 : quadClr0;
+	quad.color[2] = isVerticallyIncreasing ? quadClr0 : quadClr1;
+	quad.color[3] = quadClr1;
+
+	m_renderer->render(quad);
+
+	glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess());
+	GLU_CHECK_MSG("glReadPixels()");
+
+	log << TestLog::Image(isVerticallyIncreasing ? "VerGradient"		: "HorGradient",
+						  isVerticallyIncreasing ? "Vertical gradient"	: "Horizontal gradient",
+						  renderedImg);
+
+	// Validate, at each pixel, that each color channel is one of its two allowed values.
+
+	{
+		Surface		errorMask		(viewportWid, viewportHei);
+		bool		colorChoicesOk	= true;
+
+		for (int y = 0; y < renderedImg.getHeight(); y++)
+		{
+			for (int x = 0; x < renderedImg.getWidth(); x++)
+			{
+				const float		inputF		= ((float)(isVerticallyIncreasing ? y : x) + 0.5f) / (float)(isVerticallyIncreasing ? renderedImg.getHeight() : renderedImg.getWidth());
+				const Vec4		inputClr	= (1.0f-inputF)*quadClr0 + inputF*quadClr1;
+
+				if (!checkColor(inputClr, renderedImg.getPixel(x, y), colorChoicesOk))
+				{
+					errorMask.setPixel(x, y, tcu::RGBA::red);
+
+					if (colorChoicesOk)
+					{
+						log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage;
+						colorChoicesOk = false;
+					}
+				}
+				else
+					errorMask.setPixel(x, y, tcu::RGBA::green);
+			}
+		}
+
+		if (!colorChoicesOk)
+		{
+			log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask);
+			return false;
+		}
+	}
+
+	// When dithering is disabled, the color selection must be coordinate-independent - i.e. the colors must be constant in the gradient's constant direction.
+
+	if (!m_ditheringEnabled)
+	{
+		const int increasingDirectionSize	= isVerticallyIncreasing ? renderedImg.getHeight() : renderedImg.getWidth();
+		const int constantDirectionSize		= isVerticallyIncreasing ? renderedImg.getWidth() : renderedImg.getHeight();
+
+		for (int incrPos = 0; incrPos < increasingDirectionSize; incrPos++)
+		{
+			tcu::RGBA prevConstantDirectionPix;
+			for (int constPos = 0; constPos < constantDirectionSize; constPos++)
+			{
+				const int			x		= isVerticallyIncreasing ? constPos : incrPos;
+				const int			y		= isVerticallyIncreasing ? incrPos : constPos;
+				const tcu::RGBA		clr		= renderedImg.getPixel(x, y);
+
+				if (constPos > 0 && clr != prevConstantDirectionPix)
+				{
+					log << TestLog::Message
+						<< "Failure: colors should be constant per " << (isVerticallyIncreasing ? "row" : "column")
+						<< " (since dithering is disabled), but the color at position (" << x << ", " << y << ") is " << clr
+						<< " and does not equal the color at (" << (isVerticallyIncreasing ? x-1 : x) << ", " << (isVerticallyIncreasing ? y : y-1) << "), which is " << prevConstantDirectionPix
+						<< TestLog::EndMessage;
+
+					return false;
+				}
+
+				prevConstantDirectionPix = clr;
+			}
+		}
+	}
+
+	return true;
+}
+
+bool DitheringCase::drawAndCheckUnicoloredQuad (const Vec4& quadColor) const
+{
+	TestLog&					log					= m_testCtx.getLog();
+	Random						rnd					(deStringHash(getName()));
+	const int					maxViewportWid		= 32;
+	const int					maxViewportHei		= 32;
+	const int					viewportWid			= de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid);
+	const int					viewportHei			= de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei);
+	const int					viewportX			= rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid);
+	const int					viewportY			= rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei);
+	Quad						quad;
+	Surface						renderedImg			(viewportWid, viewportHei);
+
+	GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei));
+
+	log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
+
+	if (m_ditheringEnabled)
+		GLU_CHECK_CALL(glEnable(GL_DITHER));
+	else
+		GLU_CHECK_CALL(glDisable(GL_DITHER));
+
+	log << TestLog::Message << "Drawing an unicolored quad with color " << quadColor << TestLog::EndMessage;
+
+	quad.color[0] = quadColor;
+	quad.color[1] = quadColor;
+	quad.color[2] = quadColor;
+	quad.color[3] = quadColor;
+
+	m_renderer->render(quad);
+
+	glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess());
+	GLU_CHECK_MSG("glReadPixels()");
+
+	log << TestLog::Image(("Quad" + de::toString(m_iteration)).c_str(), ("Quad " + de::toString(m_iteration)).c_str(), renderedImg);
+
+	// Validate, at each pixel, that each color channel is one of its two allowed values.
+
+	{
+		Surface		errorMask		(viewportWid, viewportHei);
+		bool		colorChoicesOk	= true;
+
+		for (int y = 0; y < renderedImg.getHeight(); y++)
+		{
+			for (int x = 0; x < renderedImg.getWidth(); x++)
+			{
+				if (!checkColor(quadColor, renderedImg.getPixel(x, y), colorChoicesOk))
+				{
+					errorMask.setPixel(x, y, tcu::RGBA::red);
+
+					if (colorChoicesOk)
+					{
+						log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage;
+						colorChoicesOk = false;
+					}
+				}
+				else
+					errorMask.setPixel(x, y, tcu::RGBA::green);
+			}
+		}
+
+		if (!colorChoicesOk)
+		{
+			log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask);
+			return false;
+		}
+	}
+
+	// When dithering is disabled, the color selection must be coordinate-independent - i.e. the entire rendered image must be unicolored.
+
+	if (!m_ditheringEnabled)
+	{
+		const tcu::RGBA renderedClr00 = renderedImg.getPixel(0, 0);
+
+		for (int y = 0; y < renderedImg.getHeight(); y++)
+		{
+			for (int x = 0; x < renderedImg.getWidth(); x++)
+			{
+				const tcu::RGBA curClr = renderedImg.getPixel(x, y);
+
+				if (curClr != renderedClr00)
+				{
+					log << TestLog::Message
+						<< "Failure: color at (" << x << ", " << y << ") is " << curClr
+						<< " and does not equal the color at (0, 0), which is " << renderedClr00
+						<< TestLog::EndMessage;
+
+					return false;
+				}
+			}
+		}
+	}
+
+	return true;
+}
+
+DitheringCase::IterateResult DitheringCase::iterate (void)
+{
+	if (m_patternType == PATTERNTYPE_GRADIENT)
+	{
+		// Draw horizontal and vertical gradients.
+
+		DE_ASSERT(m_iteration < 2);
+
+		const bool success = drawAndCheckGradient(m_iteration == 1, m_color);
+
+		if (!success)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		if (m_iteration == 1)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+			return STOP;
+		}
+	}
+	else if (m_patternType == PATTERNTYPE_UNICOLORED_QUAD)
+	{
+		const int numQuads = m_testCtx.getCommandLine().getTestIterationCount() > 0 ? m_testCtx.getCommandLine().getTestIterationCount() : 30;
+
+		DE_ASSERT(m_iteration < numQuads);
+
+		const Vec4 quadColor	= (float)m_iteration / (float)(numQuads-1) * m_color;
+		const bool success		=  drawAndCheckUnicoloredQuad(quadColor);
+
+		if (!success)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		if (m_iteration == numQuads - 1)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+			return STOP;
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+	m_iteration++;
+
+	return CONTINUE;
+}
+
+DitheringTests::DitheringTests (Context& context)
+	: TestCaseGroup(context, "dither", "Dithering tests")
+{
+}
+
+DitheringTests::~DitheringTests (void)
+{
+}
+
+void DitheringTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		Vec4			color;
+	} caseColors[] =
+	{
+		{ "white",		Vec4(1.0f, 1.0f, 1.0f, 1.0f) },
+		{ "red",		Vec4(1.0f, 0.0f, 0.0f, 1.0f) },
+		{ "green",		Vec4(0.0f, 1.0f, 0.0f, 1.0f) },
+		{ "blue",		Vec4(0.0f, 0.0f, 1.0f, 1.0f) },
+		{ "alpha",		Vec4(0.0f, 0.0f, 0.0f, 1.0f) }
+	};
+
+	for (int ditheringEnabledI = 0; ditheringEnabledI <= 1; ditheringEnabledI++)
+	{
+		const bool				ditheringEnabled	= ditheringEnabledI != 0;
+		TestCaseGroup* const	group				= new TestCaseGroup(m_context, ditheringEnabled ? "enabled" : "disabled", "");
+		addChild(group);
+
+		for (int patternTypeI = 0; patternTypeI < DitheringCase::PATTERNTYPE_LAST; patternTypeI++)
+		{
+			for (int caseColorNdx = 0; caseColorNdx < DE_LENGTH_OF_ARRAY(caseColors); caseColorNdx++)
+			{
+				const DitheringCase::PatternType	patternType		= (DitheringCase::PatternType)patternTypeI;
+				const string						caseName		= string("") + DitheringCase::getPatternTypeName(patternType) + "_" + caseColors[caseColorNdx].name;
+
+				group->addChild(new DitheringCase(m_context.getTestContext(), m_context.getRenderContext(), caseName.c_str(), "", ditheringEnabled, patternType, caseColors[caseColorNdx].color));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fDitheringTests.hpp b/modules/gles2/functional/es2fDitheringTests.hpp
new file mode 100644
index 0000000..dc6b390
--- /dev/null
+++ b/modules/gles2/functional/es2fDitheringTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FDITHERINGTESTS_HPP
+#define _ES2FDITHERINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Dithering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class DitheringTests : public TestCaseGroup
+{
+public:
+						DitheringTests		(Context& context);
+						~DitheringTests		(void);
+
+	void				init				(void);
+
+private:
+						DitheringTests		(const DitheringTests& other);
+	DitheringTests&		operator=			(const DitheringTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FDITHERINGTESTS_HPP
diff --git a/modules/gles2/functional/es2fDrawTests.cpp b/modules/gles2/functional/es2fDrawTests.cpp
new file mode 100644
index 0000000..8219f0c
--- /dev/null
+++ b/modules/gles2/functional/es2fDrawTests.cpp
@@ -0,0 +1,728 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Drawing tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fDrawTests.hpp"
+#include "glsDrawTest.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+#include "deSTLUtil.hpp"
+
+#include "glwEnums.hpp"
+
+#include <set>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace
+{
+
+enum TestIterationType
+{
+	TYPE_DRAW_COUNT,		// !< test with 1, 5, and 25 primitives
+
+	TYPE_LAST
+};
+
+static void addTestIterations (gls::DrawTest* test, const gls::DrawTestSpec& baseSpec, TestIterationType type)
+{
+	gls::DrawTestSpec spec(baseSpec);
+
+	if (type == TYPE_DRAW_COUNT)
+	{
+		spec.primitiveCount = 1;
+		test->addIteration(spec, "draw count = 1");
+
+		spec.primitiveCount = 5;
+		test->addIteration(spec, "draw count = 5");
+
+		spec.primitiveCount = 25;
+		test->addIteration(spec, "draw count = 25");
+	}
+	else
+		DE_ASSERT(false);
+}
+
+static void genBasicSpec (gls::DrawTestSpec& spec, gls::DrawTestSpec::DrawMethod method)
+{
+	spec.apiType							= glu::ApiType::es(2,0);
+	spec.primitive							= gls::DrawTestSpec::PRIMITIVE_TRIANGLES;
+	spec.primitiveCount						= 5;
+	spec.drawMethod							= method;
+	spec.indexType							= gls::DrawTestSpec::INDEXTYPE_LAST;
+	spec.indexPointerOffset					= 0;
+	spec.indexStorage						= gls::DrawTestSpec::STORAGE_LAST;
+	spec.first								= 0;
+	spec.indexMin							= 0;
+	spec.indexMax							= 0;
+	spec.instanceCount						= 1;
+
+	spec.attribs.resize(2);
+
+	spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+	spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+	spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+	spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+	spec.attribs[0].componentCount			= 4;
+	spec.attribs[0].offset					= 0;
+	spec.attribs[0].stride					= 0;
+	spec.attribs[0].normalize				= false;
+	spec.attribs[0].instanceDivisor			= 0;
+	spec.attribs[0].useDefaultAttribute		= false;
+
+	spec.attribs[1].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+	spec.attribs[1].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+	spec.attribs[1].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+	spec.attribs[1].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+	spec.attribs[1].componentCount			= 2;
+	spec.attribs[1].offset					= 0;
+	spec.attribs[1].stride					= 0;
+	spec.attribs[1].normalize				= false;
+	spec.attribs[1].instanceDivisor			= 0;
+	spec.attribs[1].useDefaultAttribute		= false;
+}
+
+
+class AttributeGroup : public TestCaseGroup
+{
+public:
+									AttributeGroup	(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod, gls::DrawTestSpec::Primitive primitive, gls::DrawTestSpec::IndexType indexType, gls::DrawTestSpec::Storage indexStorage);
+									~AttributeGroup	(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+	gls::DrawTestSpec::Primitive	m_primitive;
+	gls::DrawTestSpec::IndexType	m_indexType;
+	gls::DrawTestSpec::Storage		m_indexStorage;
+};
+
+AttributeGroup::AttributeGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod, gls::DrawTestSpec::Primitive primitive, gls::DrawTestSpec::IndexType indexType, gls::DrawTestSpec::Storage indexStorage)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+	, m_primitive		(primitive)
+	, m_indexType		(indexType)
+	, m_indexStorage	(indexStorage)
+{
+}
+
+AttributeGroup::~AttributeGroup (void)
+{
+}
+
+void AttributeGroup::init (void)
+{
+	// Single attribute
+	{
+		gls::DrawTest*		test				= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "single_attribute", "Single attribute array.");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType							= glu::ApiType::es(2,0);
+		spec.primitive							= m_primitive;
+		spec.primitiveCount						= 0;
+		spec.drawMethod							= m_method;
+		spec.indexType							= m_indexType;
+		spec.indexPointerOffset					= 0;
+		spec.indexStorage						= m_indexStorage;
+		spec.first								= 0;
+		spec.indexMin							= 0;
+		spec.indexMax							= 0;
+		spec.instanceCount						= 1;
+
+		spec.attribs.resize(1);
+
+		spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount			= 2;
+		spec.attribs[0].offset					= 0;
+		spec.attribs[0].stride					= 0;
+		spec.attribs[0].normalize				= false;
+		spec.attribs[0].instanceDivisor			= 0;
+		spec.attribs[0].useDefaultAttribute		= false;
+
+		addTestIterations(test, spec, TYPE_DRAW_COUNT);
+
+		this->addChild(test);
+	}
+
+	// Multiple attribute
+	{
+		gls::DrawTest*		test				= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "multiple_attributes", "Multiple attribute arrays");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType							= glu::ApiType::es(2,0);
+		spec.primitive							= m_primitive;
+		spec.primitiveCount						= 0;
+		spec.drawMethod							= m_method;
+		spec.indexType							= m_indexType;
+		spec.indexPointerOffset					= 0;
+		spec.indexStorage						= m_indexStorage;
+		spec.first								= 0;
+		spec.indexMin							= 0;
+		spec.indexMax							= 0;
+		spec.instanceCount						= 1;
+
+		spec.attribs.resize(2);
+
+		spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount			= 4;
+		spec.attribs[0].offset					= 0;
+		spec.attribs[0].stride					= 0;
+		spec.attribs[0].normalize				= false;
+		spec.attribs[0].instanceDivisor			= 0;
+		spec.attribs[0].useDefaultAttribute		= false;
+
+		spec.attribs[1].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[1].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[1].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[1].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[1].componentCount			= 2;
+		spec.attribs[1].offset					= 0;
+		spec.attribs[1].stride					= 0;
+		spec.attribs[1].normalize				= false;
+		spec.attribs[1].instanceDivisor			= 0;
+		spec.attribs[1].useDefaultAttribute		= false;
+
+		addTestIterations(test, spec, TYPE_DRAW_COUNT);
+
+		this->addChild(test);
+	}
+
+	// Multiple attribute, second one default.
+	{
+		gls::DrawTest*		test				= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "default_attribute", "Attribute specified with glVertexAttrib*.");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType							= glu::ApiType::es(2,0);
+		spec.primitive							= m_primitive;
+		spec.primitiveCount						= 5;
+		spec.drawMethod							= m_method;
+		spec.indexType							= m_indexType;
+		spec.indexPointerOffset					= 0;
+		spec.indexStorage						= m_indexStorage;
+		spec.first								= 0;
+		spec.indexMin							= 0;
+		spec.indexMax							= 0;
+		spec.instanceCount						= 1;
+
+		spec.attribs.resize(2);
+
+		spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount			= 2;
+		spec.attribs[0].offset					= 0;
+		spec.attribs[0].stride					= 0;
+		spec.attribs[0].normalize				= false;
+		spec.attribs[0].instanceDivisor			= 0;
+		spec.attribs[0].useDefaultAttribute		= false;
+
+		struct IOPair
+		{
+			gls::DrawTestSpec::InputType  input;
+			gls::DrawTestSpec::OutputType output;
+			int							  componentCount;
+		} iopairs[] =
+		{
+			{ gls::DrawTestSpec::INPUTTYPE_FLOAT,        gls::DrawTestSpec::OUTPUTTYPE_VEC2,  4 },
+			{ gls::DrawTestSpec::INPUTTYPE_FLOAT,        gls::DrawTestSpec::OUTPUTTYPE_VEC4,  2 },
+		};
+
+		for (int ioNdx = 0; ioNdx < DE_LENGTH_OF_ARRAY(iopairs); ++ioNdx)
+		{
+			const std::string desc = gls::DrawTestSpec::inputTypeToString(iopairs[ioNdx].input) + de::toString(iopairs[ioNdx].componentCount) + " to " + gls::DrawTestSpec::outputTypeToString(iopairs[ioNdx].output);
+
+			spec.attribs[1].inputType			= iopairs[ioNdx].input;
+			spec.attribs[1].outputType			= iopairs[ioNdx].output;
+			spec.attribs[1].storage				= gls::DrawTestSpec::STORAGE_BUFFER;
+			spec.attribs[1].usage				= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+			spec.attribs[1].componentCount		= iopairs[ioNdx].componentCount;
+			spec.attribs[1].offset				= 0;
+			spec.attribs[1].stride				= 0;
+			spec.attribs[1].normalize			= false;
+			spec.attribs[1].instanceDivisor		= 0;
+			spec.attribs[1].useDefaultAttribute	= true;
+
+			test->addIteration(spec, desc.c_str());
+		}
+
+		this->addChild(test);
+	}
+}
+
+class IndexGroup : public TestCaseGroup
+{
+public:
+									IndexGroup		(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~IndexGroup		(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+IndexGroup::IndexGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+IndexGroup::~IndexGroup (void)
+{
+}
+
+void IndexGroup::init (void)
+{
+	struct IndexTest
+	{
+		gls::DrawTestSpec::Storage		storage;
+		gls::DrawTestSpec::IndexType	type;
+		bool							aligned;
+		int								offsets[3];
+	};
+
+	const IndexTest tests[] =
+	{
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_BYTE,	true,	{ 0, 1, -1 } },
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_SHORT,	true,	{ 0, 2, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_SHORT,	false,	{ 1, 3, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_BYTE,	true,	{ 0, 1, -1 } },
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_SHORT,	true,	{ 0, 2, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_SHORT,	false,	{ 1, 3, -1 } },
+	};
+
+	gls::DrawTestSpec spec;
+
+	tcu::TestCaseGroup* userPtrGroup			= new tcu::TestCaseGroup(m_testCtx, "user_ptr", "user pointer");
+	tcu::TestCaseGroup* unalignedUserPtrGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_user_ptr", "unaligned user pointer");
+	tcu::TestCaseGroup* bufferGroup				= new tcu::TestCaseGroup(m_testCtx, "buffer", "buffer");
+	tcu::TestCaseGroup* unalignedBufferGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_buffer", "unaligned buffer");
+
+	genBasicSpec(spec, m_method);
+
+	this->addChild(userPtrGroup);
+	this->addChild(unalignedUserPtrGroup);
+	this->addChild(bufferGroup);
+	this->addChild(unalignedBufferGroup);
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(tests); ++testNdx)
+	{
+		const IndexTest&				indexTest	= tests[testNdx];
+		tcu::TestCaseGroup*				group		= (indexTest.storage == gls::DrawTestSpec::STORAGE_USER) ? ((indexTest.aligned) ? (userPtrGroup) : (unalignedUserPtrGroup)) : ((indexTest.aligned) ? (bufferGroup) : (unalignedBufferGroup));
+
+		const std::string				name		= std::string("index_") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
+		const std::string				desc		= std::string("index ") + gls::DrawTestSpec::indexTypeToString(indexTest.type) + " in " + gls::DrawTestSpec::storageToString(indexTest.storage);
+		de::MovePtr<gls::DrawTest>		test		(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str()));
+
+		spec.indexType			= indexTest.type;
+		spec.indexStorage		= indexTest.storage;
+
+		for (int iterationNdx = 0; iterationNdx < DE_LENGTH_OF_ARRAY(indexTest.offsets) && indexTest.offsets[iterationNdx] != -1; ++iterationNdx)
+		{
+			const std::string iterationDesc = std::string("offset ") + de::toString(indexTest.offsets[iterationNdx]);
+			spec.indexPointerOffset	= indexTest.offsets[iterationNdx];
+			test->addIteration(spec, iterationDesc.c_str());
+		}
+
+		if (spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET &&
+			spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+			group->addChild(test.release());
+	}
+}
+
+
+class FirstGroup : public TestCaseGroup
+{
+public:
+									FirstGroup		(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~FirstGroup		(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+FirstGroup::FirstGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+FirstGroup::~FirstGroup (void)
+{
+}
+
+void FirstGroup::init (void)
+{
+	const int firsts[] =
+	{
+		0, 1, 17
+	};
+
+	gls::DrawTestSpec spec;
+	genBasicSpec(spec, m_method);
+
+	for (int firstNdx = 0; firstNdx < DE_LENGTH_OF_ARRAY(firsts); ++firstNdx)
+	{
+		const std::string	name = std::string("first_") + de::toString(firsts[firstNdx]);
+		const std::string	desc = std::string("first ") + de::toString(firsts[firstNdx]);
+		gls::DrawTest*		test = new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str());
+
+		spec.first = firsts[firstNdx];
+
+		addTestIterations(test, spec, TYPE_DRAW_COUNT);
+
+		this->addChild(test);
+	}
+}
+
+class MethodGroup : public TestCaseGroup
+{
+public:
+									MethodGroup			(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~MethodGroup		(void);
+
+	void							init				(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+MethodGroup::MethodGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+MethodGroup::~MethodGroup (void)
+{
+}
+
+void MethodGroup::init (void)
+{
+	const bool indexed		= (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS) || (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED) || (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED);
+	const bool hasFirst		= (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS) || (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INSTANCED);
+
+	const gls::DrawTestSpec::Primitive primitive[] =
+	{
+		gls::DrawTestSpec::PRIMITIVE_POINTS,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINES,
+		gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINE_LOOP
+	};
+
+	if (hasFirst)
+	{
+		// First-tests
+		this->addChild(new FirstGroup(m_context, "first", "First tests", m_method));
+	}
+
+	if (indexed)
+	{
+		// Index-tests
+		if (m_method != gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED)
+			this->addChild(new IndexGroup(m_context, "indices", "Index tests", m_method));
+	}
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(primitive); ++ndx)
+	{
+		const std::string name = gls::DrawTestSpec::primitiveToString(primitive[ndx]);
+		const std::string desc = gls::DrawTestSpec::primitiveToString(primitive[ndx]);
+
+		this->addChild(new AttributeGroup(m_context, name.c_str(), desc.c_str(), m_method, primitive[ndx], gls::DrawTestSpec::INDEXTYPE_SHORT, gls::DrawTestSpec::STORAGE_BUFFER));
+	}
+}
+
+class RandomGroup : public TestCaseGroup
+{
+public:
+									RandomGroup		(Context& context, const char* name, const char* descr);
+									~RandomGroup	(void);
+
+	void							init			(void);
+};
+
+template <int SIZE>
+struct UniformWeightArray
+{
+	float weights[SIZE];
+
+	UniformWeightArray (void)
+	{
+		for (int i=0; i<SIZE; ++i)
+			weights[i] = 1.0f;
+	}
+};
+
+RandomGroup::RandomGroup (Context& context, const char* name, const char* descr)
+	: TestCaseGroup	(context, name, descr)
+{
+}
+
+RandomGroup::~RandomGroup (void)
+{
+}
+
+void RandomGroup::init (void)
+{
+	const int			numAttempts				= 100;
+
+	static const int	attribCounts[]			= { 1, 2, 5 };
+	static const float	attribWeights[]			= { 30, 10, 1 };
+	static const int	primitiveCounts[]		= { 1, 5, 64 };
+	static const float	primitiveCountWeights[]	= { 20, 10, 1 };
+	static const int	indexOffsets[]			= { 0, 7, 13 };
+	static const float	indexOffsetWeights[]	= { 20, 20, 1 };
+	static const int	firsts[]				= { 0, 7, 13 };
+	static const float	firstWeights[]			= { 20, 20, 1 };
+	static const int	offsets[]				= { 0, 1, 5, 12 };
+	static const float	offsetWeights[]			= { 50, 10, 10, 10 };
+	static const int	strides[]				= { 0, 7, 16, 17 };
+	static const float	strideWeights[]			= { 50, 10, 10, 10 };
+
+	static const gls::DrawTestSpec::Primitive primitives[] =
+	{
+		gls::DrawTestSpec::PRIMITIVE_POINTS,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINES,
+		gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINE_LOOP
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(primitives)> primitiveWeights;
+
+	static const gls::DrawTestSpec::DrawMethod drawMethods[] =
+	{
+		gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(drawMethods)> drawMethodWeights;
+
+	static const gls::DrawTestSpec::IndexType indexTypes[] =
+	{
+		gls::DrawTestSpec::INDEXTYPE_BYTE,
+		gls::DrawTestSpec::INDEXTYPE_SHORT,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(indexTypes)> indexTypeWeights;
+
+	static const gls::DrawTestSpec::Storage storages[] =
+	{
+		gls::DrawTestSpec::STORAGE_USER,
+		gls::DrawTestSpec::STORAGE_BUFFER,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(storages)> storageWeights;
+
+	static const gls::DrawTestSpec::InputType inputTypes[] =
+	{
+		gls::DrawTestSpec::INPUTTYPE_FLOAT,
+		gls::DrawTestSpec::INPUTTYPE_FIXED,
+		gls::DrawTestSpec::INPUTTYPE_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_SHORT,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(inputTypes)> inputTypeWeights;
+
+	static const gls::DrawTestSpec::OutputType outputTypes[] =
+	{
+		gls::DrawTestSpec::OUTPUTTYPE_FLOAT,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC4,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(outputTypes)> outputTypeWeights;
+
+	static const gls::DrawTestSpec::Usage usages[] =
+	{
+		gls::DrawTestSpec::USAGE_STATIC_DRAW,
+		gls::DrawTestSpec::USAGE_DYNAMIC_DRAW,
+		gls::DrawTestSpec::USAGE_STREAM_DRAW,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(usages)> usageWeights;
+
+	static const deUint32 blacklistedCases[]=
+	{
+		3153,	//!< extremely narrow triangle, results depend on sample positions
+	};
+
+	std::set<deUint32>	insertedHashes;
+	size_t				insertedCount = 0;
+
+	for (int ndx = 0; ndx < numAttempts; ++ndx)
+	{
+		de::Random random(0xc551393 + ndx); // random does not depend on previous cases
+
+		int					attributeCount = random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(attribCounts), DE_ARRAY_END(attribCounts), attribWeights);
+		gls::DrawTestSpec	spec;
+
+		spec.apiType				= glu::ApiType::es(2,0);
+		spec.primitive				= random.chooseWeighted<gls::DrawTestSpec::Primitive>	(DE_ARRAY_BEGIN(primitives),		DE_ARRAY_END(primitives),		primitiveWeights.weights);
+		spec.primitiveCount			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(primitiveCounts),	DE_ARRAY_END(primitiveCounts),	primitiveCountWeights);
+		spec.drawMethod				= random.chooseWeighted<gls::DrawTestSpec::DrawMethod>	(DE_ARRAY_BEGIN(drawMethods),		DE_ARRAY_END(drawMethods),		drawMethodWeights.weights);
+		spec.indexType				= random.chooseWeighted<gls::DrawTestSpec::IndexType>	(DE_ARRAY_BEGIN(indexTypes),		DE_ARRAY_END(indexTypes),		indexTypeWeights.weights);
+		spec.indexPointerOffset		= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexOffsets),		DE_ARRAY_END(indexOffsets),		indexOffsetWeights);
+		spec.indexStorage			= random.chooseWeighted<gls::DrawTestSpec::Storage>		(DE_ARRAY_BEGIN(storages),			DE_ARRAY_END(storages),			storageWeights.weights);
+		spec.first					= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(firsts),			DE_ARRAY_END(firsts),			firstWeights);
+		spec.indexMin				= 0;
+		spec.indexMax				= 0;
+		spec.instanceCount			= 0;
+
+		// check spec is legal
+		if (!spec.valid())
+			continue;
+
+		for (int attrNdx = 0; attrNdx < attributeCount;)
+		{
+			bool valid;
+			gls::DrawTestSpec::AttributeSpec attribSpec;
+
+			attribSpec.inputType			= random.chooseWeighted<gls::DrawTestSpec::InputType>	(DE_ARRAY_BEGIN(inputTypes),		DE_ARRAY_END(inputTypes),		inputTypeWeights.weights);
+			attribSpec.outputType			= random.chooseWeighted<gls::DrawTestSpec::OutputType>	(DE_ARRAY_BEGIN(outputTypes),		DE_ARRAY_END(outputTypes),		outputTypeWeights.weights);
+			attribSpec.storage				= random.chooseWeighted<gls::DrawTestSpec::Storage>		(DE_ARRAY_BEGIN(storages),			DE_ARRAY_END(storages),			storageWeights.weights);
+			attribSpec.usage				= random.chooseWeighted<gls::DrawTestSpec::Usage>		(DE_ARRAY_BEGIN(usages),			DE_ARRAY_END(usages),			usageWeights.weights);
+			attribSpec.componentCount		= random.getInt(1, 4);
+			attribSpec.offset				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), offsetWeights);
+			attribSpec.stride				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(strides), DE_ARRAY_END(strides), strideWeights);
+			attribSpec.normalize			= random.getBool();
+			attribSpec.instanceDivisor		= 0;
+			attribSpec.useDefaultAttribute	= random.getBool();
+
+			// check spec is legal
+			valid = attribSpec.valid(spec.apiType);
+
+			// we do not want interleaved elements. (Might result in some weird floating point values)
+			if (attribSpec.stride && attribSpec.componentCount * gls::DrawTestSpec::inputTypeSize(attribSpec.inputType) > attribSpec.stride)
+				valid = false;
+
+			// try again if not valid
+			if (valid)
+			{
+				spec.attribs.push_back(attribSpec);
+				++attrNdx;
+			}
+		}
+
+		// Do not collapse all vertex positions to a single positions
+		if (spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+			spec.attribs[0].instanceDivisor = 0;
+
+		// Is render result meaningful?
+		{
+			// Only one vertex
+			if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && spec.indexMin == spec.indexMax && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+			if (spec.attribs[0].useDefaultAttribute && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+
+			// Triangle only on one axis
+			if (spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLES || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP)
+			{
+				if (spec.attribs[0].componentCount == 1)
+					continue;
+				if (spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_FLOAT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_INT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_UINT)
+					continue;
+				if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && (spec.indexMax - spec.indexMin) < 2)
+					continue;
+			}
+		}
+
+		// Add case
+		{
+			deUint32 hash = spec.hash();
+			for (int attrNdx = 0; attrNdx < attributeCount; ++attrNdx)
+				hash = (hash << 2) ^ (deUint32)spec.attribs[attrNdx].hash();
+
+			if (insertedHashes.find(hash) == insertedHashes.end() &&
+				!de::contains(DE_ARRAY_BEGIN(blacklistedCases), DE_ARRAY_END(blacklistedCases), hash))
+			{
+				// Only aligned cases
+				if (spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET &&
+					spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+					this->addChild(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), spec, de::toString(insertedCount).c_str(), spec.getDesc().c_str()));
+				insertedHashes.insert(hash);
+
+				++insertedCount;
+			}
+		}
+	}
+}
+
+} // anonymous
+
+DrawTests::DrawTests (Context& context)
+	: TestCaseGroup(context, "draw", "Drawing tests")
+{
+}
+
+DrawTests::~DrawTests (void)
+{
+}
+
+void DrawTests::init (void)
+{
+	// Basic
+	{
+		const gls::DrawTestSpec::DrawMethod basicMethods[] =
+		{
+			gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS,
+			gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS,
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(basicMethods); ++ndx)
+		{
+			std::string name = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
+			std::string desc = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
+
+			this->addChild(new MethodGroup(m_context, name.c_str(), desc.c_str(), basicMethods[ndx]));
+		}
+	}
+
+	// Random
+
+	this->addChild(new RandomGroup(m_context, "random", "random draw commands."));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fDrawTests.hpp b/modules/gles2/functional/es2fDrawTests.hpp
new file mode 100644
index 0000000..b9d3bf1
--- /dev/null
+++ b/modules/gles2/functional/es2fDrawTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FDRAWTESTS_HPP
+#define _ES2FDRAWTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Drawing tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class DrawTests : public TestCaseGroup
+{
+public:
+						DrawTests		(Context& context);
+						~DrawTests		(void);
+
+	void				init			(void);
+
+private:
+						DrawTests		(const DrawTests& other);
+	DrawTests&			operator=		(const DrawTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FDRAWTESTS_HPP
diff --git a/modules/gles2/functional/es2fFboApiTest.cpp b/modules/gles2/functional/es2fFboApiTest.cpp
new file mode 100644
index 0000000..9a9660a
--- /dev/null
+++ b/modules/gles2/functional/es2fFboApiTest.cpp
@@ -0,0 +1,636 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer Object API Tests.
+ *
+ * Notes:
+ *   All gl calls are passed thru sgl2::Context class. Reasons:
+ *    + Name, object allocation is tracked and live resources are freed
+ *      when Context is destroyed.
+ *    + Makes it possible to easily log all relevant calls into test log.
+ *      \todo [pyry] This is not implemented yet
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fFboApiTest.hpp"
+#include "sglrGLContext.hpp"
+#include "gluDefs.hpp"
+#include "gluContextInfo.hpp"
+#include "gluStrUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deString.h"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <iterator>
+#include <algorithm>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+
+using glw::GLenum;
+using glw::GLint;
+
+static void logComment (tcu::TestContext& testCtx, const char* comment)
+{
+	testCtx.getLog() << TestLog::Message << "// " << comment << TestLog::EndMessage;
+}
+
+static void checkError (tcu::TestContext& testCtx, sglr::Context& ctx, GLenum expect)
+{
+	GLenum result = ctx.getError();
+	testCtx.getLog() << TestLog::Message << "// " << (result == expect ? "Pass" : "Fail") << ", expected " << glu::getErrorStr(expect) << TestLog::EndMessage;
+
+	if (result != expect)
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Error code mismatch");
+}
+
+static const char* getAttachmentName (GLenum attachment)
+{
+	switch (attachment)
+	{
+		case GL_COLOR_ATTACHMENT0:	return "GL_COLOR_ATTACHMENT0";
+		case GL_DEPTH_ATTACHMENT:	return "GL_DEPTH_ATTACHMENT";
+		case GL_STENCIL_ATTACHMENT:	return "GL_STENCIL_ATTACHMENT";
+		default: throw tcu::InternalError("Unknown attachment", "", __FILE__, __LINE__);
+	}
+}
+
+static const char* getAttachmentParameterName (GLenum pname)
+{
+	switch (pname)
+	{
+		case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:				return "GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE";
+		case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:				return "GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME";
+		case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL:			return "GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL";
+		case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE:	return "GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE";
+		default: throw tcu::InternalError("Unknown parameter", "", __FILE__, __LINE__);
+	}
+}
+
+static string getAttachmentParameterValueName (GLint value)
+{
+	switch (value)
+	{
+		case 0:									return "GL_NONE(0)";
+		case GL_TEXTURE:						return "GL_TEXTURE";
+		case GL_RENDERBUFFER:					return "GL_RENDERBUFFER";
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_X:	return "GL_TEXTURE_CUBE_MAP_POSITIVE_X";
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:	return "GL_TEXTURE_CUBE_MAP_NEGATIVE_X";
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:	return "GL_TEXTURE_CUBE_MAP_POSITIVE_Y";
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:	return "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y";
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:	return "GL_TEXTURE_CUBE_MAP_POSITIVE_Z";
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:	return "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z";
+		default:
+		{
+			char tmp[64];
+			deSprintf(tmp, sizeof(tmp), "0x%x", value);
+			return string(tmp);
+		}
+	}
+}
+
+static void checkFboAttachmentParam (tcu::TestContext& testCtx, sglr::Context& ctx, GLenum attachment, GLenum pname, GLint expectedValue)
+{
+	TestLog& log = testCtx.getLog();
+	log << TestLog::Message << "// Querying " << getAttachmentName(attachment) << " " << getAttachmentParameterName(pname) << TestLog::EndMessage;
+
+	GLint value = 0xcdcdcdcd;
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, attachment, pname, &value);
+
+	GLenum err = ctx.getError();
+
+	if (value == expectedValue && err == GL_NO_ERROR)
+		log << TestLog::Message << "// Pass" << TestLog::EndMessage;
+	else
+	{
+		log << TestLog::Message << "// Fail, expected " << getAttachmentParameterValueName(expectedValue) << " without error" << TestLog::EndMessage;
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid result for attachment param query");
+	}
+}
+
+static void notSupportedTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	DE_UNREF(testCtx);
+	DE_UNREF(context);
+	throw tcu::NotSupportedError("Not supported", "", __FILE__, __LINE__);
+}
+
+static void textureLevelsTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	deUint32	tex		= 1;
+	deUint32	fbo		= 1;
+
+	context.bindTexture(GL_TEXTURE_2D, tex);
+	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256);
+	context.texImage2D(GL_TEXTURE_2D, 1, GL_RGB, 128, 128);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+	static int levels[] = { 2, 1, 0, -1, 0x7fffffff, 0, 1 };
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(levels); ndx++)
+	{
+		context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, levels[ndx]);
+		checkError(testCtx, context, levels[ndx] == 0 ? GL_NO_ERROR : GL_INVALID_VALUE);
+	}
+}
+
+static void textureLevelsWithRenderToMipmapTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	deUint32	tex		= 1;
+	deUint32	fbo		= 1;
+
+	context.bindTexture(GL_TEXTURE_2D, tex);
+	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256);
+	context.texImage2D(GL_TEXTURE_2D, 1, GL_RGB, 128, 128);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+	static int levels[] = { 2, 1, 0, -1, 0x7fffffff, 0, 1 };
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(levels); ndx++)
+	{
+		context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, levels[ndx]);
+		checkError(testCtx, context, de::inBounds(levels[ndx], 0, 16) ? GL_NO_ERROR : GL_INVALID_VALUE);
+	}
+}
+
+static void validTex2DAttachmentsTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+	static const GLenum attachmentPoints[] =
+	{
+		GL_COLOR_ATTACHMENT0,
+		GL_DEPTH_ATTACHMENT,
+		GL_STENCIL_ATTACHMENT
+	};
+
+	// Texture2D
+	deUint32 tex2D = 1;
+	context.bindTexture(GL_TEXTURE_2D, tex2D);
+	for (int pointNdx = 0; pointNdx < DE_LENGTH_OF_ARRAY(attachmentPoints); pointNdx++)
+	{
+		context.framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoints[pointNdx], GL_TEXTURE_2D, tex2D, 0);
+		checkError(testCtx, context, GL_NO_ERROR);
+	}
+}
+
+static void validTexCubeAttachmentsTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	static const GLenum attachmentPoints[] =
+	{
+		GL_COLOR_ATTACHMENT0,
+		GL_DEPTH_ATTACHMENT,
+		GL_STENCIL_ATTACHMENT
+	};
+	static const GLenum cubeTargets[] =
+	{
+		GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+		GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+		GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+		GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+		GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+		GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
+	};
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	// TextureCube
+	deUint32 texCube = 2;
+	context.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+	for (int pointNdx = 0; pointNdx < DE_LENGTH_OF_ARRAY(attachmentPoints); pointNdx++)
+	{
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(cubeTargets); targetNdx++)
+		{
+			context.framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoints[pointNdx], cubeTargets[targetNdx], texCube, 0);
+			checkError(testCtx, context, GL_NO_ERROR);
+		}
+	}
+}
+
+static void validRboAttachmentsTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	static const GLenum attachmentPoints[] =
+	{
+		GL_COLOR_ATTACHMENT0,
+		GL_DEPTH_ATTACHMENT,
+		GL_STENCIL_ATTACHMENT
+	};
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	// Renderbuffer
+	deUint32 rbo = 3;
+	context.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+	for (int pointNdx = 0; pointNdx < DE_LENGTH_OF_ARRAY(attachmentPoints); pointNdx++)
+	{
+		context.framebufferRenderbuffer(GL_FRAMEBUFFER, attachmentPoints[pointNdx], GL_RENDERBUFFER, rbo);
+		checkError(testCtx, context, GL_NO_ERROR);
+	}
+}
+
+static void attachToDefaultFramebufferTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	logComment(testCtx, "Attaching 2D texture to default framebuffer");
+
+	deUint32 tex2D = 1;
+	context.bindTexture(GL_TEXTURE_2D, tex2D);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+
+	logComment(testCtx, "Attaching renderbuffer to default framebuffer");
+
+	deUint32 rbo = 1;
+	context.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+}
+
+static void invalidTex2DAttachmentTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	logComment(testCtx, "Attaching 2D texture using GL_TEXTURE_CUBE_MAP_NEGATIVE_X texture target");
+
+	deUint32 tex2D = 1;
+	context.bindTexture(GL_TEXTURE_2D, tex2D);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, tex2D, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+
+	logComment(testCtx, "Attaching deleted 2D texture object");
+	context.deleteTextures(1, &tex2D);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+}
+
+static void invalidTexCubeAttachmentTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	logComment(testCtx, "Attaching cube texture using GL_TEXTURE_2D texture target");
+	deUint32 texCube = 2;
+	context.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texCube, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+
+	logComment(testCtx, "Attaching deleted cube texture object");
+	context.deleteTextures(1, &texCube);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+}
+
+static void invalidRboAttachmentTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	logComment(testCtx, "Attaching renderbuffer using GL_FRAMEBUFFER renderbuffer target");
+	deUint32 rbo = 3;
+	context.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER, rbo);
+	checkError(testCtx, context, GL_INVALID_ENUM);
+
+	logComment(testCtx, "Attaching deleted renderbuffer object");
+	context.deleteRenderbuffers(1, &rbo);
+	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+}
+
+static void attachNamesTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	// Just allocate some names, don't bind for storage
+	deUint32 reservedTexName;
+	context.genTextures(1, &reservedTexName);
+
+	logComment(testCtx, "Attaching allocated texture name to 2D target");
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, reservedTexName, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+
+	logComment(testCtx, "Attaching allocated texture name to cube target");
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, reservedTexName, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+
+	deUint32 reservedRboName;
+	context.genRenderbuffers(1, &reservedRboName);
+
+	logComment(testCtx, "Attaching allocated renderbuffer name");
+	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, reservedRboName);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+}
+
+static void attachmentQueryDefaultFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	// Check that proper error codes are returned
+	GLint unused = 1;
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &unused);
+	checkError(testCtx, ctx, GL_INVALID_OPERATION);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &unused);
+	checkError(testCtx, ctx, GL_INVALID_OPERATION);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &unused);
+	checkError(testCtx, ctx, GL_INVALID_OPERATION);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &unused);
+	checkError(testCtx, ctx, GL_INVALID_OPERATION);
+}
+
+static void attachmentQueryEmptyFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	static const GLenum attachmentPoints[] =
+	{
+		GL_COLOR_ATTACHMENT0,
+		GL_DEPTH_ATTACHMENT,
+		GL_STENCIL_ATTACHMENT
+	};
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(attachmentPoints); ndx++)
+		checkFboAttachmentParam(testCtx, ctx, attachmentPoints[ndx], GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
+
+	// Check that proper error codes are returned
+	GLint unused = -1;
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+}
+
+static void attachmentQueryTex2DTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	ctx.bindTexture(GL_TEXTURE_2D, 1);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 1, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 1);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, 0);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, 0);
+}
+
+static void attachmentQueryTexCubeTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	ctx.bindTexture(GL_TEXTURE_CUBE_MAP, 2);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 2, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 2);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, 0);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y);
+}
+
+static void attachmentQueryRboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	ctx.bindRenderbuffer(GL_RENDERBUFFER, 3);
+	ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 3);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 3);
+
+	GLint unused = 0;
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+}
+
+static void deleteTex2DAttachedToBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 tex2D = 1;
+	ctx.bindTexture(GL_TEXTURE_2D, tex2D);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, tex2D);
+
+	ctx.deleteTextures(1, &tex2D);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
+}
+
+static void deleteTexCubeAttachedToBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 texCube = 1;
+	ctx.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, texCube);
+
+	ctx.deleteTextures(1, &texCube);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
+}
+
+static void deleteRboAttachedToBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 rbo = 1;
+	ctx.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+	ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, rbo);
+
+	ctx.deleteRenderbuffers(1, &rbo);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
+}
+
+static void deleteTex2DAttachedToNotBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 tex2D = 1;
+	ctx.bindTexture(GL_TEXTURE_2D, tex2D);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, tex2D);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	ctx.deleteTextures(1, &tex2D);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, tex2D);
+}
+
+static void deleteTexCubeAttachedToNotBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 texCube = 1;
+	ctx.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, texCube);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	ctx.deleteTextures(1, &texCube);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, texCube);
+}
+
+static void deleteRboAttachedToNotBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 rbo = 1;
+	ctx.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+	ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, rbo);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	ctx.deleteRenderbuffers(1, &rbo);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, rbo);
+}
+
+class FboApiCase : public TestCase
+{
+public:
+	typedef void (*TestFunc) (tcu::TestContext& testCtx, sglr::Context& context);
+
+								FboApiCase				(Context& context, const char* name, const char* description, TestFunc test);
+	virtual						~FboApiCase				(void);
+
+	virtual IterateResult		iterate					(void);
+
+private:
+								FboApiCase				(const FboApiCase& other);
+	FboApiCase&					operator=				(const FboApiCase& other);
+
+	TestFunc					m_testFunc;
+};
+
+FboApiCase::FboApiCase (Context& context, const char* name, const char* description, TestFunc test)
+	: TestCase		(context, name, description)
+	, m_testFunc	(test)
+{
+}
+
+FboApiCase::~FboApiCase (void)
+{
+}
+
+TestCase::IterateResult FboApiCase::iterate (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Before test case");
+
+	// Initialize result to PASS
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Execute test case
+	{
+		sglr::GLContext context(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(0, 0, m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight()));
+		m_testFunc(m_testCtx, context);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After test case");
+
+	return STOP;
+}
+
+FboApiTestGroup::FboApiTestGroup (Context& context)
+	: TestCaseGroup(context, "api", "API Tests")
+{
+}
+
+FboApiTestGroup::~FboApiTestGroup (void)
+{
+}
+
+void FboApiTestGroup::init (void)
+{
+	std::set<std::string> extensions;
+	std::copy(m_context.getContextInfo().getExtensions().begin(), m_context.getContextInfo().getExtensions().end(), std::inserter(extensions, extensions.begin()));
+
+	bool	defaultFboIsZero	= m_context.getRenderContext().getDefaultFramebuffer() == 0;
+	bool	hasRenderToMipmap	= extensions.find("GL_OES_fbo_render_mipmap") != extensions.end();
+
+	// Valid attachments
+	addChild(new FboApiCase(m_context, "valid_tex2d_attachments",					"Valid 2D texture attachments",							validTex2DAttachmentsTest));
+	addChild(new FboApiCase(m_context, "valid_texcube_attachments",					"Valid cubemap attachments",							validTexCubeAttachmentsTest));
+	addChild(new FboApiCase(m_context, "valid_rbo_attachments",						"Valid renderbuffer attachments",						validRboAttachmentsTest));
+
+	// Invalid attachments
+	addChild(new FboApiCase(m_context, "attach_to_default_fbo",						"Invalid usage: attaching to default FBO",				defaultFboIsZero ? attachToDefaultFramebufferTest : notSupportedTest));
+	addChild(new FboApiCase(m_context, "invalid_tex2d_attachments",					"Invalid 2D texture attachments",						invalidTex2DAttachmentTest));
+	addChild(new FboApiCase(m_context, "invalid_texcube_attachments",				"Invalid cubemap attachments",							invalidTexCubeAttachmentTest));
+	addChild(new FboApiCase(m_context, "invalid_rbo_attachments",					"Invalid renderbuffer attachments",						invalidRboAttachmentTest));
+	addChild(new FboApiCase(m_context, "attach_names",								"Attach allocated names without objects",				attachNamesTest));
+
+	addChild(new FboApiCase(m_context, "texture_levels",							"Valid and invalid texturel levels",					hasRenderToMipmap ? textureLevelsWithRenderToMipmapTest : textureLevelsTest));
+
+	// Attachment queries
+	addChild(new FboApiCase(m_context, "attachment_query_default_fbo",				"Query attachments from default FBO",					defaultFboIsZero ? attachmentQueryDefaultFboTest : notSupportedTest));
+	addChild(new FboApiCase(m_context, "attachment_query_empty_fbo",				"Query attachments from empty FBO",						attachmentQueryEmptyFboTest));
+	addChild(new FboApiCase(m_context, "attachment_query_tex2d",					"Query 2d texture attachment properties",				attachmentQueryTex2DTest));
+	addChild(new FboApiCase(m_context, "attachment_query_texcube",					"Query cubemap attachment properties",					attachmentQueryTexCubeTest));
+	addChild(new FboApiCase(m_context, "attachment_query_rbo",						"Query renderbuffer attachment properties",				attachmentQueryRboTest));
+
+	// Delete attachments
+	addChild(new FboApiCase(m_context, "delete_tex_2d_attached_to_bound_fbo",		"Delete 2d texture attached to currently bound FBO",	deleteTex2DAttachedToBoundFboTest));
+	addChild(new FboApiCase(m_context, "delete_tex_cube_attached_to_bound_fbo",		"Delete cubemap attached to currently bound FBO",		deleteTexCubeAttachedToBoundFboTest));
+	addChild(new FboApiCase(m_context, "delete_rbo_attached_to_bound_fbo",			"Delete renderbuffer attached to currently bound FBO",	deleteRboAttachedToBoundFboTest));
+
+	addChild(new FboApiCase(m_context, "delete_tex_2d_attached_to_not_bound_fbo",	"Delete 2d texture attached to FBO",					deleteTex2DAttachedToNotBoundFboTest));
+	addChild(new FboApiCase(m_context, "delete_tex_cube_attached_to_not_bound_fbo",	"Delete cubemap attached to FBO",						deleteTexCubeAttachedToNotBoundFboTest));
+	addChild(new FboApiCase(m_context, "delete_rbo_attached_to_not_bound_fbo",		"Delete renderbuffer attached to FBO",					deleteRboAttachedToNotBoundFboTest));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fFboApiTest.hpp b/modules/gles2/functional/es2fFboApiTest.hpp
new file mode 100644
index 0000000..c77295b
--- /dev/null
+++ b/modules/gles2/functional/es2fFboApiTest.hpp
@@ -0,0 +1,49 @@
+#ifndef _ES2FFBOAPITEST_HPP
+#define _ES2FFBOAPITEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer Object API Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class FboApiTestGroup : public TestCaseGroup
+{
+public:
+					FboApiTestGroup			(Context& context);
+	virtual			~FboApiTestGroup		(void);
+
+	virtual void	init					(void);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FFBOAPITEST_HPP
diff --git a/modules/gles2/functional/es2fFboCompletenessTests.cpp b/modules/gles2/functional/es2fFboCompletenessTests.cpp
new file mode 100644
index 0000000..24882aa
--- /dev/null
+++ b/modules/gles2/functional/es2fFboCompletenessTests.cpp
@@ -0,0 +1,292 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer completeness tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fFboCompletenessTests.hpp"
+
+#include "glsFboCompletenessTests.hpp"
+#include "gluObjectWrapper.hpp"
+
+using namespace glw;
+using deqp::gls::Range;
+using namespace deqp::gls::FboUtil;
+using namespace deqp::gls::FboUtil::config;
+namespace fboc = deqp::gls::fboc;
+typedef tcu::TestCase::IterateResult IterateResult;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+static const FormatKey s_es2ColorRenderables[] =
+{
+	GL_RGBA4, GL_RGB5_A1, GL_RGB565,
+};
+
+// GLES2 does not strictly allow these, but this seems to be a bug in the
+// specification. For now, let's assume the unsized formats corresponding to
+// the color-renderable sized formats are allowed.
+// See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=7333
+
+static const FormatKey s_es2UnsizedColorRenderables[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4),
+	GLS_UNSIZED_FORMATKEY(GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1),
+	GLS_UNSIZED_FORMATKEY(GL_RGB, GL_UNSIGNED_SHORT_5_6_5)
+};
+
+static const FormatKey s_es2DepthRenderables[] =
+{
+	GL_DEPTH_COMPONENT16,
+};
+
+static const FormatKey s_es2StencilRenderables[] =
+{
+	GL_STENCIL_INDEX8,
+};
+
+static const FormatEntry s_es2Formats[] =
+{
+	{ COLOR_RENDERABLE | TEXTURE_VALID,
+	  GLS_ARRAY_RANGE(s_es2UnsizedColorRenderables) },
+	{ REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
+	  GLS_ARRAY_RANGE(s_es2ColorRenderables) },
+	{ REQUIRED_RENDERABLE | DEPTH_RENDERABLE | RENDERBUFFER_VALID,
+	  GLS_ARRAY_RANGE(s_es2DepthRenderables) },
+	{ REQUIRED_RENDERABLE | STENCIL_RENDERABLE | RENDERBUFFER_VALID,
+	  GLS_ARRAY_RANGE(s_es2StencilRenderables) },
+};
+
+// We have here only the extensions that are redundant in vanilla GLES3. Those
+// that are applicable both to GLES2 and GLES3 are in glsFboCompletenessTests.cpp.
+
+// GL_OES_texture_float
+static const FormatKey s_oesTextureFloatFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_FLOAT),
+	GLS_UNSIZED_FORMATKEY(GL_RGB,	GL_FLOAT),
+};
+
+// GL_OES_texture_half_float
+static const FormatKey s_oesTextureHalfFloatFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_HALF_FLOAT_OES),
+	GLS_UNSIZED_FORMATKEY(GL_RGB,	GL_HALF_FLOAT_OES),
+};
+
+static const FormatExtEntry s_es2ExtFormats[] =
+{
+	// The extension does not specify these to be color-renderable.
+	{
+		"GL_OES_texture_float",
+		TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesTextureFloatFormats)
+	},
+	{
+		"GL_OES_texture_half_float",
+		TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesTextureHalfFloatFormats)
+	},
+};
+
+class ES2Checker : public Checker
+{
+public:
+			ES2Checker				(void) : m_width(-1), m_height(-1) {}
+	void	check					(GLenum attPoint, const Attachment& att,
+									 const Image* image);
+private:
+	GLsizei	m_width;	//< The common width of images
+	GLsizei	m_height;	//< The common height of images
+};
+
+void ES2Checker::check(GLenum attPoint, const Attachment& att, const Image* image)
+{
+	DE_UNREF(attPoint);
+	DE_UNREF(att);
+	// GLES2: "All attached images have the same width and height."
+	if (m_width == -1)
+	{
+		m_width = image->width;
+		m_height = image->height;
+	}
+	else
+	{
+		require(image->width == m_width && image->height == m_height,
+				GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
+	}
+	// GLES2, 4.4.5: "some implementations may not support rendering to
+	// particular combinations of internal formats. If the combination of
+	// formats of the images attached to a framebuffer object are not
+	// supported by the implementation, then the framebuffer is not complete
+	// under the clause labeled FRAMEBUFFER_UNSUPPORTED."
+	//
+	// Hence it is _always_ allowed to report FRAMEBUFFER_UNSUPPORTED.
+	canRequire(false, GL_FRAMEBUFFER_UNSUPPORTED);
+}
+
+struct FormatCombination
+{
+	GLenum			colorKind;
+	ImageFormat		colorFmt;
+	GLenum			depthKind;
+	ImageFormat		depthFmt;
+	GLenum			stencilKind;
+	ImageFormat		stencilFmt;
+};
+
+class SupportedCombinationTest : public fboc::TestBase
+{
+public:
+					SupportedCombinationTest	(fboc::Context& ctx,
+												 const char* name, const char* desc)
+						: TestBase		(ctx, name, desc) {}
+
+	IterateResult	iterate						(void);
+	bool			tryCombination				(const FormatCombination& comb);
+	GLenum			formatKind					(ImageFormat fmt);
+};
+
+bool SupportedCombinationTest::tryCombination (const FormatCombination& comb)
+{
+	glu::Framebuffer fbo(m_ctx.getRenderContext());
+	FboBuilder builder(*fbo, GL_FRAMEBUFFER, fboc::gl(*this));
+
+	attachTargetToNew(GL_COLOR_ATTACHMENT0,		comb.colorKind,		comb.colorFmt,
+					  64, 						64,					builder);
+	attachTargetToNew(GL_DEPTH_ATTACHMENT,		comb.depthKind,		comb.depthFmt,
+					  64,						64,					builder);
+	attachTargetToNew(GL_STENCIL_ATTACHMENT,	comb.stencilKind,	comb.stencilFmt,
+					  64,						64,					builder);
+
+	const GLenum glStatus = fboc::gl(*this).checkFramebufferStatus(GL_FRAMEBUFFER);
+
+	return (glStatus == GL_FRAMEBUFFER_COMPLETE);
+}
+
+GLenum SupportedCombinationTest::formatKind (ImageFormat fmt)
+{
+	if (fmt.format == GL_NONE)
+		return GL_NONE;
+
+	const FormatFlags flags = m_ctx.getMinFormats().getFormatInfo(fmt, ANY_FORMAT);
+	const bool rbo = (flags & RENDERBUFFER_VALID) != 0;
+	// exactly one of renderbuffer and texture is supported by vanilla GLES2 formats
+	DE_ASSERT(rbo != ((flags & TEXTURE_VALID) != 0));
+
+	return rbo ? GL_RENDERBUFFER : GL_TEXTURE;
+}
+
+IterateResult SupportedCombinationTest::iterate (void)
+{
+	const FormatDB& db		= m_ctx.getMinFormats();
+	const ImageFormat none	= ImageFormat::none();
+	Formats colorFmts		= db.getFormats(COLOR_RENDERABLE);
+	Formats depthFmts		= db.getFormats(DEPTH_RENDERABLE);
+	Formats stencilFmts		= db.getFormats(STENCIL_RENDERABLE);
+	FormatCombination comb;
+	bool succ = false;
+
+	colorFmts.insert(none);
+	depthFmts.insert(none);
+	stencilFmts.insert(none);
+
+	for (Formats::const_iterator col = colorFmts.begin(); col != colorFmts.end(); col++)
+	{
+		comb.colorFmt = *col;
+		comb.colorKind = formatKind(*col);
+		for (Formats::const_iterator dep = depthFmts.begin(); dep != depthFmts.end(); dep++)
+		{
+			comb.depthFmt = *dep;
+			comb.depthKind = formatKind(*dep);
+			for (Formats::const_iterator stc = stencilFmts.begin();
+				 stc != stencilFmts.end(); stc++)
+			{
+				comb.stencilFmt = *stc;
+				comb.stencilKind = formatKind(*stc);
+				succ = tryCombination(comb);
+				if (succ)
+					break;
+			}
+		}
+	}
+
+	if (succ)
+		pass();
+	else
+		fail("No supported format combination found");
+
+	return STOP;
+}
+
+class ES2CheckerFactory : public CheckerFactory {
+public:
+	Checker*			createChecker	(void) { return new ES2Checker(); }
+};
+
+class TestGroup : public TestCaseGroup
+{
+public:
+						TestGroup		(Context& ctx);
+	void				init			(void);
+private:
+	ES2CheckerFactory	m_checkerFactory;
+	fboc::Context		m_fboc;
+};
+
+TestGroup::TestGroup (Context& ctx)
+	: TestCaseGroup		(ctx, "completeness", "Completeness tests")
+	, m_checkerFactory	()
+	, m_fboc			(ctx.getTestContext(), ctx.getRenderContext(), m_checkerFactory)
+{
+	const FormatEntries stdRange = GLS_ARRAY_RANGE(s_es2Formats);
+	const FormatExtEntries extRange = GLS_ARRAY_RANGE(s_es2ExtFormats);
+
+	m_fboc.addFormats(stdRange);
+	m_fboc.addExtFormats(extRange);
+	m_fboc.setHaveMulticolorAtts(
+		ctx.getContextInfo().isExtensionSupported("GL_NV_fbo_color_attachments"));
+}
+
+void TestGroup::init (void)
+{
+	tcu::TestCaseGroup* attCombTests = m_fboc.createAttachmentTests();
+	addChild(m_fboc.createRenderableTests());
+	attCombTests->addChild(new SupportedCombinationTest(
+							   m_fboc,
+							   "exists_supported",
+							   "Test for existence of a supported combination of formats"));
+	addChild(attCombTests);
+	addChild(m_fboc.createSizeTests());
+}
+
+tcu::TestCaseGroup* createFboCompletenessTests (Context& context)
+{
+	return new TestGroup(context);
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fFboCompletenessTests.hpp b/modules/gles2/functional/es2fFboCompletenessTests.hpp
new file mode 100644
index 0000000..0512f5b
--- /dev/null
+++ b/modules/gles2/functional/es2fFboCompletenessTests.hpp
@@ -0,0 +1,41 @@
+#ifndef _ES2FFBOCOMPLETENESSTESTS_HPP
+#define _ES2FFBOCOMPLETENESSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer completeness tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+tcu::TestCaseGroup* createFboCompletenessTests (Context& context);
+
+} // Functioal
+} // gles2
+} // deqp
+
+#endif // _ES2FFBOCOMPLETENESSTESTS_HPP
diff --git a/modules/gles2/functional/es2fFboRenderTest.cpp b/modules/gles2/functional/es2fFboRenderTest.cpp
new file mode 100644
index 0000000..42173bf
--- /dev/null
+++ b/modules/gles2/functional/es2fFboRenderTest.cpp
@@ -0,0 +1,2112 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer Object Tests.
+ *
+ * Notes:
+ *   + Like in API tests, tcu::sgl2s::Context class is used.
+ *   + ReferenceContext is used to generate reference images.
+ *   + API calls can be logged \todo [pyry] Implement.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fFboRenderTest.hpp"
+#include "sglrContextUtil.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "deRandom.h"
+#include "deString.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+using std::vector;
+using std::string;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::RGBA;
+using tcu::Surface;
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+// Shaders.
+
+class FlatColorShader : public sglr::ShaderProgram
+{
+public:
+	FlatColorShader (void)
+		: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+								<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+								<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+								<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
+								<< sglr::pdec::VertexSource(
+										"attribute highp vec4 a_position;\n"
+										"void main (void)\n"
+										"{\n"
+										"	gl_Position = a_position;\n"
+										"}\n")
+								<< sglr::pdec::FragmentSource(
+										"uniform mediump vec4 u_color;\n"
+										"void main (void)\n"
+										"{\n"
+										"	gl_FragColor = u_color;\n"
+										"}\n"))
+	{
+	}
+
+	void setColor (sglr::Context& gl, deUint32 program, const tcu::Vec4& color)
+	{
+		gl.useProgram(program);
+		gl.uniform4fv(gl.getUniformLocation(program, "u_color"), 1, color.getPtr());
+	}
+
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+			packets[packetNdx]->position = rr::readVertexAttribFloat(inputs[0], packets[packetNdx]->instanceNdx, packets[packetNdx]->vertexNdx);
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		const tcu::Vec4 color(m_uniforms[0].value.f4);
+
+		DE_UNREF(packets);
+
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+	}
+};
+
+class SingleTex2DShader : public sglr::ShaderProgram
+{
+public:
+	SingleTex2DShader (void)
+		: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+								<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+								<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+								<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+								<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+								<< sglr::pdec::Uniform("u_sampler0", glu::TYPE_SAMPLER_2D)
+								<< sglr::pdec::VertexSource(
+										"attribute highp vec4 a_position;\n"
+										"attribute mediump vec2 a_coord;\n"
+										"varying mediump vec2 v_coord;\n"
+										"void main (void)\n"
+										"{\n"
+										"	gl_Position = a_position;\n"
+										"	v_coord = a_coord;\n"
+										"}\n")
+								<< sglr::pdec::FragmentSource(
+										"uniform sampler2D u_sampler0;\n"
+										"varying mediump vec2 v_coord;\n"
+										"void main (void)\n"
+										"{\n"
+										"	gl_FragColor = texture2D(u_sampler0, v_coord);\n"
+										"}\n"))
+	{
+	}
+
+	void setUnit (sglr::Context& gl, deUint32 program, int unitNdx)
+	{
+		gl.useProgram(program);
+		gl.uniform1i(gl.getUniformLocation(program, "u_sampler0"), unitNdx);
+	}
+
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			rr::VertexPacket& packet = *packets[packetNdx];
+
+			packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+			packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+		}
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4 v_coord = rr::readVarying<float>(packets[packetNdx], context, 0, fragNdx);
+			const float		lod		= 0.0f;
+
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, this->m_uniforms[0].sampler.tex2D->sample(v_coord.x(), v_coord.y(), lod));
+		}
+	}
+
+};
+
+class MixTexturesShader : public sglr::ShaderProgram
+{
+public:
+	MixTexturesShader (void)
+		: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+								<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+								<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+								<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+								<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+								<< sglr::pdec::Uniform("u_sampler0", glu::TYPE_SAMPLER_2D)
+								<< sglr::pdec::Uniform("u_sampler1", glu::TYPE_SAMPLER_2D)
+								<< sglr::pdec::VertexSource(
+										"attribute highp vec4 a_position;\n"
+										"attribute mediump vec2 a_coord;\n"
+										"varying mediump vec2 v_coord;\n"
+										"void main (void)\n"
+										"{\n"
+										"	gl_Position = a_position;\n"
+										"	v_coord = a_coord;\n"
+										"}\n")
+								<< sglr::pdec::FragmentSource(
+										"uniform sampler2D u_sampler0;\n"
+										"uniform sampler2D u_sampler1;\n"
+										"varying mediump vec2 v_coord;\n"
+										"void main (void)\n"
+										"{\n"
+										"	gl_FragColor = texture2D(u_sampler0, v_coord)*0.5 + texture2D(u_sampler1, v_coord)*0.5;\n"
+										"}\n"))
+	{
+	}
+
+	void setUnits (sglr::Context& gl, deUint32 program, int unit0, int unit1)
+	{
+		gl.useProgram(program);
+		gl.uniform1i(gl.getUniformLocation(program, "u_sampler0"), unit0);
+		gl.uniform1i(gl.getUniformLocation(program, "u_sampler1"), unit1);
+	}
+
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			rr::VertexPacket& packet = *packets[packetNdx];
+
+			packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+			packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+		}
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4 v_coord = rr::readVarying<float>(packets[packetNdx], context, 0, fragNdx);
+			const float		lod		= 0.0f;
+
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0,   this->m_uniforms[0].sampler.tex2D->sample(v_coord.x(), v_coord.y(), lod) * 0.5f
+			                                                        + this->m_uniforms[1].sampler.tex2D->sample(v_coord.x(), v_coord.y(), lod) * 0.5f);
+		}
+	}
+};
+
+// Framebuffer config.
+
+class FboConfig
+{
+public:
+	FboConfig (void)
+		: colorbufferType		(GL_NONE)
+		, colorbufferFormat		(GL_NONE)
+		, depthbufferType		(GL_NONE)
+		, depthbufferFormat		(GL_NONE)
+		, stencilbufferType		(GL_NONE)
+		, stencilbufferFormat	(GL_NONE)
+	{
+	}
+
+	std::string				getName			(void) const;
+
+	GLenum					colorbufferType;		//!< GL_TEXTURE_2D, GL_TEXTURE_CUBE_MAP, GL_RENDERBUFFER
+	GLenum					colorbufferFormat;		//!< Internal format for color buffer texture or renderbuffer
+
+	GLenum					depthbufferType;		//!< GL_RENDERBUFFER
+	GLenum					depthbufferFormat;
+
+	GLenum					stencilbufferType;		//!< GL_RENDERBUFFER
+	GLenum					stencilbufferFormat;
+
+private:
+	static const char*		getFormatName	(GLenum format);
+};
+
+const char* FboConfig::getFormatName (GLenum format)
+{
+	switch (format)
+	{
+		case GL_RGB:				return "rgb";
+		case GL_RGBA:				return "rgba";
+		case GL_ALPHA:				return "alpha";
+		case GL_LUMINANCE:			return "luminance";
+		case GL_LUMINANCE_ALPHA:	return "luminance_alpha";
+		case GL_RGB565:				return "rgb565";
+		case GL_RGB5_A1:			return "rgb5_a1";
+		case GL_RGBA4:				return "rgba4";
+		case GL_RGBA16F:			return "rgba16f";
+		case GL_RGB16F:				return "rgb16f";
+		case GL_DEPTH_COMPONENT16:	return "depth_component16";
+		case GL_STENCIL_INDEX8:		return "stencil_index8";
+		default:					DE_ASSERT(false); return DE_NULL;
+	}
+}
+
+std::string FboConfig::getName (void) const
+{
+	std::string name = "";
+
+	if (colorbufferType != GL_NONE)
+	{
+		switch (colorbufferType)
+		{
+			case GL_TEXTURE_2D:			name += "tex2d_";	break;
+			case GL_TEXTURE_CUBE_MAP:	name += "texcube_";	break;
+			case GL_RENDERBUFFER:		name += "rbo_";		break;
+			default:					DE_ASSERT(false);	break;
+		}
+		name += getFormatName(colorbufferFormat);
+	}
+
+	if (depthbufferType != GL_NONE)
+	{
+		DE_ASSERT(depthbufferType == GL_RENDERBUFFER);
+		if (name.length() > 0)
+			name += "_";
+		name += getFormatName(depthbufferFormat);
+	}
+
+	if (stencilbufferType != GL_NONE)
+	{
+		DE_ASSERT(stencilbufferType == GL_RENDERBUFFER);
+		if (name.length() > 0)
+			name += "_";
+		name += getFormatName(stencilbufferFormat);
+	}
+
+	return name;
+}
+
+class FboIncompleteException : public tcu::TestError
+{
+public:
+						FboIncompleteException		(const FboConfig& config, GLenum reason, const char* file, int line);
+	virtual				~FboIncompleteException		(void) throw() {}
+
+	const FboConfig&	getConfig					(void) const { return m_config; }
+	GLenum				getReason					(void) const { return m_reason; }
+
+private:
+	FboConfig			m_config;
+	GLenum				m_reason;
+};
+
+static const char* getFboIncompleteReasonName (GLenum reason)
+{
+	switch (reason)
+	{
+		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:			return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
+		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:	return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
+		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:			return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+		case GL_FRAMEBUFFER_UNSUPPORTED:					return "GL_FRAMEBUFFER_UNSUPPORTED";
+		case GL_FRAMEBUFFER_COMPLETE:						return "GL_FRAMEBUFFER_COMPLETE";
+		default:											return "UNKNOWN";
+	}
+}
+
+FboIncompleteException::FboIncompleteException (const FboConfig& config, GLenum reason, const char* file, int line)
+	: TestError("Framebuffer is not complete", getFboIncompleteReasonName(reason), file, line)
+	, m_config(config)
+	, m_reason(reason)
+{
+}
+
+class Framebuffer
+{
+public:
+						Framebuffer			(sglr::Context& context, const FboConfig& config, int width, int height, deUint32 fbo = 0, deUint32 colorbuffer = 0, deUint32 depthbuffer = 0, deUint32 stencilbuffer = 0);
+						~Framebuffer		(void);
+
+	const FboConfig&	getConfig			(void) const { return m_config; }
+	deUint32			getFramebuffer		(void) const { return m_framebuffer; }
+	deUint32			getColorbuffer		(void) const { return m_colorbuffer; }
+	deUint32			getDepthbuffer		(void) const { return m_depthbuffer; }
+	deUint32			getStencilbuffer	(void) const { return m_stencilbuffer; }
+
+	void				checkCompleteness	(void);
+
+private:
+	void				createRbo			(deUint32& name, GLenum format, int width, int height);
+	void				destroyBuffer		(deUint32 name, GLenum type);
+
+	FboConfig			m_config;
+	sglr::Context&		m_context;
+	deUint32			m_framebuffer;
+	deUint32			m_colorbuffer;
+	deUint32			m_depthbuffer;
+	deUint32			m_stencilbuffer;
+};
+
+static bool isExtensionSupported (sglr::Context& context, const char* name)
+{
+	std::istringstream extensions(context.getString(GL_EXTENSIONS));
+	std::string extension;
+
+	while (std::getline(extensions, extension, ' '))
+	{
+		if (extension == name)
+			return true;
+	}
+
+	return false;
+}
+
+static void checkColorFormatSupport (sglr::Context& context, deUint32 sizedFormat)
+{
+	switch (sizedFormat)
+	{
+		case GL_RGBA16F:
+		case GL_RGB16F:
+		case GL_RG16F:
+		case GL_R16F:
+			if (!isExtensionSupported(context, "GL_EXT_color_buffer_half_float"))
+				throw tcu::NotSupportedError("GL_EXT_color_buffer_half_float is not supported");
+
+		default:
+			break;
+	}
+}
+
+Framebuffer::Framebuffer (sglr::Context& context, const FboConfig& config, int width, int height, deUint32 fbo, deUint32 colorbuffer, deUint32 depthbuffer, deUint32 stencilbuffer)
+	: m_config			(config)
+	, m_context			(context)
+	, m_framebuffer		(fbo)
+	, m_colorbuffer		(colorbuffer)
+	, m_depthbuffer		(depthbuffer)
+	, m_stencilbuffer	(stencilbuffer)
+{
+	// Verify that color format is supported
+	checkColorFormatSupport(context, config.colorbufferFormat);
+
+	if (m_framebuffer == 0)
+		context.genFramebuffers(1, &m_framebuffer);
+	context.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+
+	switch (m_config.colorbufferType)
+	{
+		case GL_TEXTURE_2D:
+			if (m_colorbuffer == 0)
+				context.genTextures(1, &m_colorbuffer);
+			context.bindTexture(GL_TEXTURE_2D, m_colorbuffer);
+			context.texImage2D(GL_TEXTURE_2D, 0, m_config.colorbufferFormat, width, height);
+			context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+			if (!deIsPowerOfTwo32(width) || !deIsPowerOfTwo32(height))
+			{
+				// Set wrap mode to clamp for NPOT FBOs
+				context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+				context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+			}
+
+			context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorbuffer, 0);
+			break;
+
+		case GL_TEXTURE_CUBE_MAP:
+			DE_ASSERT(!"TODO");
+			break;
+
+		case GL_RENDERBUFFER:
+			createRbo(m_colorbuffer, m_config.colorbufferFormat, width, height);
+			context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorbuffer);
+			break;
+
+		default:
+			DE_ASSERT(m_config.colorbufferType == GL_NONE);
+			break;
+	}
+
+	if (m_config.depthbufferType == GL_RENDERBUFFER)
+	{
+		createRbo(m_depthbuffer, m_config.depthbufferFormat, width, height);
+		context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthbuffer);
+	}
+	else
+		DE_ASSERT(m_config.depthbufferType == GL_NONE);
+
+	if (m_config.stencilbufferType == GL_RENDERBUFFER)
+	{
+		createRbo(m_stencilbuffer, m_config.stencilbufferFormat, width, height);
+		context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_stencilbuffer);
+	}
+	else
+		DE_ASSERT(m_config.stencilbufferType == GL_NONE);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+Framebuffer::~Framebuffer (void)
+{
+	m_context.deleteFramebuffers(1, &m_framebuffer);
+	destroyBuffer(m_colorbuffer, m_config.colorbufferType);
+	destroyBuffer(m_depthbuffer, m_config.depthbufferType);
+	destroyBuffer(m_stencilbuffer, m_config.stencilbufferType);
+}
+
+void Framebuffer::checkCompleteness (void)
+{
+	m_context.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+	GLenum status = m_context.checkFramebufferStatus(GL_FRAMEBUFFER);
+	m_context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	if (status != GL_FRAMEBUFFER_COMPLETE)
+		throw FboIncompleteException(m_config, status, __FILE__, __LINE__);
+}
+
+void Framebuffer::createRbo (deUint32& name, GLenum format, int width, int height)
+{
+	if (name == 0)
+		m_context.genRenderbuffers(1, &name);
+	m_context.bindRenderbuffer(GL_RENDERBUFFER, name);
+	m_context.renderbufferStorage(GL_RENDERBUFFER, format, width, height);
+}
+
+void Framebuffer::destroyBuffer (deUint32 name, GLenum type)
+{
+	if (type == GL_TEXTURE_2D || type == GL_TEXTURE_CUBE_MAP)
+		m_context.deleteTextures(1, &name);
+	else if (type == GL_RENDERBUFFER)
+		m_context.deleteRenderbuffers(1, &name);
+	else
+		DE_ASSERT(type == GL_NONE);
+}
+
+static void createMetaballsTex2D (sglr::Context& context, deUint32 name, GLenum format, GLenum dataType, int width, int height)
+{
+	tcu::TextureFormat	texFormat	= glu::mapGLTransferFormat(format, dataType);
+	tcu::TextureLevel	level		(texFormat, width, height);
+
+	tcu::fillWithMetaballs(level.getAccess(), 5, name ^ width ^ height);
+
+	context.bindTexture(GL_TEXTURE_2D, name);
+	context.texImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, level.getAccess().getDataPtr());
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+}
+
+static void createQuadsTex2D (sglr::Context& context, deUint32 name, GLenum format, GLenum dataType, int width, int height)
+{
+	tcu::TextureFormat	texFormat	= glu::mapGLTransferFormat(format, dataType);
+	tcu::TextureLevel	level		(texFormat, width, height);
+
+	tcu::fillWithRGBAQuads(level.getAccess());
+
+	context.bindTexture(GL_TEXTURE_2D, name);
+	context.texImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, level.getAccess().getDataPtr());
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+}
+
+class FboRenderCase : public TestCase
+{
+public:
+								FboRenderCase			(Context& context, const char* name, const char* description, const FboConfig& config);
+	virtual						~FboRenderCase			(void) {}
+
+	virtual IterateResult		iterate					(void);
+	virtual void				render					(sglr::Context& fboContext, Surface& dst) = DE_NULL;
+
+	const FboConfig&			getConfig				(void) const { return m_config; }
+
+	static bool					isConfigSupported		(const FboConfig& config) { DE_UNREF(config); return true; }
+
+private:
+	FboConfig					m_config;
+};
+
+FboRenderCase::FboRenderCase (Context& context, const char* name, const char* description, const FboConfig& config)
+	: TestCase(context, name, description)
+	, m_config(config)
+{
+}
+
+TestCase::IterateResult FboRenderCase::iterate (void)
+{
+	Vec4						clearColor				(0.125f, 0.25f, 0.5f, 1.0f);
+	glu::RenderContext&			renderCtx				= m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget			= m_context.getRenderTarget();
+	tcu::TestLog&				log						= m_testCtx.getLog();
+	const char*					failReason				= DE_NULL;
+
+	// Position & size for context
+	deRandom rnd;
+	deRandom_init(&rnd, deStringHash(getName()));
+
+	int		width	= deMin32(renderTarget.getWidth(), 128);
+	int		height	= deMin32(renderTarget.getHeight(), 128);
+	int		xMax	= renderTarget.getWidth()-width+1;
+	int		yMax	= renderTarget.getHeight()-height+1;
+	int		x		= deRandom_getUint32(&rnd) % xMax;
+	int		y		= deRandom_getUint32(&rnd) % yMax;
+
+	tcu::Surface	gles2Frame	(width, height);
+	tcu::Surface	refFrame	(width, height);
+	GLenum			gles2Error;
+	GLenum			refError;
+
+	// Render using GLES2
+	try
+	{
+		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(x, y, width, height));
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		render(context, gles2Frame); // Call actual render func
+		gles2Error = context.getError();
+	}
+	catch (const FboIncompleteException& e)
+	{
+		if (e.getReason() == GL_FRAMEBUFFER_UNSUPPORTED)
+		{
+			// Mark test case as unsupported
+			log << e;
+			m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not supported");
+			return STOP;
+		}
+		else
+			throw; // Propagate error
+	}
+
+	// Render reference image
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), renderTarget.getDepthBits(), renderTarget.getStencilBits(), width, height);
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		render(context, refFrame);
+		refError = context.getError();
+	}
+
+	// Compare error codes
+	bool errorCodesOk = (gles2Error == refError);
+
+	if (!errorCodesOk)
+	{
+		log << tcu::TestLog::Message << "Error code mismatch: got " << glu::getErrorStr(gles2Error) << ", expected " << glu::getErrorStr(refError) << tcu::TestLog::EndMessage;
+		failReason = "Got unexpected error";
+	}
+
+	// Compare images
+	const float		threshold	= 0.02f;
+	bool			imagesOk	= tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, gles2Frame, threshold, tcu::COMPARE_LOG_RESULT);
+
+	if (!imagesOk && !failReason)
+		failReason = "Image comparison failed";
+
+	// Store test result
+	bool isOk = errorCodesOk && imagesOk;
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: failReason);
+
+	return STOP;
+}
+
+namespace FboCases
+{
+
+class ColorClearsTest : public FboRenderCase
+{
+public:
+						ColorClearsTest				(Context& context, const FboConfig& config);
+						~ColorClearsTest			(void) {}
+
+	void				render						(sglr::Context& context, Surface& dst);
+};
+
+ColorClearsTest::ColorClearsTest (Context& context, const FboConfig& config)
+	: FboRenderCase(context, config.getName().c_str(), "Color buffer clears", config)
+{
+}
+
+void ColorClearsTest::render (sglr::Context& context, Surface& dst)
+{
+	int			width	= 128;
+	int			height	= 128;
+	deRandom	rnd;
+
+	deRandom_init(&rnd, 0);
+
+	// Create framebuffer
+	Framebuffer fbo(context, getConfig(), width, height);
+	fbo.checkCompleteness();
+
+	// Clear fbo
+	context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	context.viewport(0, 0, width, height);
+	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	// Enable scissor test.
+	context.enable(GL_SCISSOR_TEST);
+
+	// Do 10 random color clears
+	for (int i = 0; i < 15; i++)
+	{
+		int		cX		= (int)(deRandom_getUint32(&rnd) & 0x7fffffff) % width;
+		int		cY		= (int)(deRandom_getUint32(&rnd) & 0x7fffffff) % height;
+		int		cWidth	= (int)(deRandom_getUint32(&rnd) & 0x7fffffff) % (width-cX);
+		int		cHeight	= (int)(deRandom_getUint32(&rnd) & 0x7fffffff) % (height-cY);
+		Vec4	color	= RGBA(deRandom_getUint32(&rnd)).toVec();
+
+		context.scissor(cX, cY, cWidth, cHeight);
+		context.clearColor(color.x(), color.y(), color.z(), color.w());
+		context.clear(GL_COLOR_BUFFER_BIT);
+	}
+
+	// Disable scissor.
+	context.disable(GL_SCISSOR_TEST);
+
+	if (fbo.getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		// Unbind fbo
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+		// Draw to screen
+		SingleTex2DShader	shader;
+		deUint32			shaderID = context.createProgram(&shader);
+
+		shader.setUnit(context, shaderID, 0);
+
+		context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		// Read from screen
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+	{
+		// Read from fbo
+		context.readPixels(dst, 0, 0, width, height);
+	}
+}
+
+class IntersectingQuadsTest : public FboRenderCase
+{
+public:
+					IntersectingQuadsTest			(Context& context, const FboConfig& config, bool npot = false);
+	virtual			~IntersectingQuadsTest			(void) {}
+
+	virtual void	render							(sglr::Context& context, Surface& dst);
+
+	static bool		isConfigSupported				(const FboConfig& config);
+
+private:
+	int				m_fboWidth;
+	int				m_fboHeight;
+};
+
+class IntersectingQuadsNpotTest : public IntersectingQuadsTest
+{
+public:
+	IntersectingQuadsNpotTest (Context& context, const FboConfig& config)
+		: IntersectingQuadsTest(context, config, true)
+	{
+	}
+};
+
+IntersectingQuadsTest::IntersectingQuadsTest (Context& context, const FboConfig& config, bool npot)
+	: FboRenderCase	(context, (string(npot ? "npot_" : "") + config.getName()).c_str(), "Intersecting textured quads", config)
+	, m_fboWidth	(npot ? 127 : 128)
+	, m_fboHeight	(npot ?  95 : 128)
+{
+}
+
+bool IntersectingQuadsTest::isConfigSupported (const FboConfig& config)
+{
+	// \note Disabled for stencil configurations since doesn't exercise stencil buffer
+	return config.depthbufferType	!= GL_NONE &&
+		   config.stencilbufferType	== GL_NONE;
+}
+
+void IntersectingQuadsTest::render (sglr::Context& ctx, Surface& dst)
+{
+	SingleTex2DShader	texShader;
+	deUint32			texShaderID = ctx.createProgram(&texShader);
+
+	deUint32 metaballsTex	= 1;
+	deUint32 quadsTex		= 2;
+
+	createMetaballsTex2D(ctx, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+	createQuadsTex2D(ctx, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+
+	int width	= m_fboWidth;
+	int height	= m_fboHeight;
+	Framebuffer fbo(ctx, getConfig(), width, height);
+	fbo.checkCompleteness();
+
+	// Setup shaders
+	texShader.setUnit(ctx, texShaderID, 0);
+
+	// Draw scene
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	ctx.viewport(0, 0, width, height);
+	ctx.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+	ctx.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	ctx.enable(GL_DEPTH_TEST);
+
+	ctx.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+	ctx.bindTexture(GL_TEXTURE_2D, quadsTex);
+	sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, 1.0f), Vec3(1.0f, 1.0f, -1.0f));
+
+	ctx.disable(GL_DEPTH_TEST);
+
+	if (fbo.getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		// Unbind fbo
+		ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+		// Draw to screen
+		ctx.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
+		ctx.viewport(0, 0, ctx.getWidth(), ctx.getHeight());
+		sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		// Read from screen
+		ctx.readPixels(dst, 0, 0, ctx.getWidth(), ctx.getHeight());
+	}
+	else
+	{
+		// Read from fbo
+		ctx.readPixels(dst, 0, 0, width, height);
+	}
+}
+
+class MixTest : public FboRenderCase
+{
+public:
+						MixTest				(Context& context, const FboConfig& config, bool npot = false);
+	virtual				~MixTest			(void) {}
+
+	void				render				(sglr::Context& context, Surface& dst);
+
+	static bool			isConfigSupported	(const FboConfig& config);
+
+private:
+	int					m_fboAWidth;
+	int					m_fboAHeight;
+	int					m_fboBWidth;
+	int					m_fboBHeight;
+};
+
+class MixNpotTest : public MixTest
+{
+public:
+	MixNpotTest (Context& context, const FboConfig& config)
+		: MixTest(context, config, true)
+	{
+	}
+};
+
+MixTest::MixTest (Context& context, const FboConfig& config, bool npot)
+	: FboRenderCase	(context, (string(npot ? "mix_npot_" : "mix_") + config.getName()).c_str(), "Use two fbos as sources in draw operation", config)
+	, m_fboAWidth	(npot ? 127 : 128)
+	, m_fboAHeight	(npot ?  95 : 128)
+	, m_fboBWidth	(npot ?  55 :  64)
+	, m_fboBHeight	(npot ?  63 :  64)
+{
+}
+
+bool MixTest::isConfigSupported (const FboConfig& config)
+{
+	// \note Disabled for stencil configurations since doesn't exercise stencil buffer
+	return config.colorbufferType	== GL_TEXTURE_2D &&
+		   config.stencilbufferType	== GL_NONE;
+}
+
+void MixTest::render (sglr::Context& context, Surface& dst)
+{
+	SingleTex2DShader	singleTexShader;
+	MixTexturesShader	mixShader;
+
+	deUint32			singleTexShaderID	= context.createProgram(&singleTexShader);
+	deUint32			mixShaderID			= context.createProgram(&mixShader);
+
+	// Texture with metaballs
+	deUint32 metaballsTex = 1;
+	context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
+	createMetaballsTex2D(context, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+
+	// Setup shaders
+	singleTexShader.setUnit(context, singleTexShaderID, 0);
+	mixShader.setUnits(context, mixShaderID, 0, 1);
+
+	// Fbo, quad with metaballs texture
+	Framebuffer fboA(context, getConfig(), m_fboAWidth, m_fboAHeight);
+	fboA.checkCompleteness();
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboA.getFramebuffer());
+	context.viewport(0, 0, m_fboAWidth, m_fboAHeight);
+	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	sglr::drawQuad(context, singleTexShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	// Fbo, color clears
+	Framebuffer fboB(context, getConfig(), m_fboBWidth, m_fboBHeight);
+	fboB.checkCompleteness();
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
+	context.viewport(0, 0, m_fboBWidth, m_fboBHeight);
+	context.enable(GL_SCISSOR_TEST);
+	context.scissor(0, 0, 32, 64);
+	context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT);
+	context.scissor(32, 0, 32, 64);
+	context.clearColor(0.0f, 1.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT);
+	context.disable(GL_SCISSOR_TEST);
+
+	// Final mix op
+	context.activeTexture(GL_TEXTURE0);
+	context.bindTexture(GL_TEXTURE_2D, fboA.getColorbuffer());
+	context.activeTexture(GL_TEXTURE1);
+	context.bindTexture(GL_TEXTURE_2D, fboB.getColorbuffer());
+	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	context.viewport(0, 0, context.getWidth(), context.getHeight());
+	sglr::drawQuad(context, mixShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+}
+
+class BlendTest : public FboRenderCase
+{
+public:
+						BlendTest			(Context& context, const FboConfig& config, bool npot = false);
+	virtual				~BlendTest			(void) {}
+
+	void				render				(sglr::Context& context, Surface& dst);
+
+	static bool			isConfigSupported	(const FboConfig& config);
+
+private:
+	int					m_fboWidth;
+	int					m_fboHeight;
+};
+
+class BlendNpotTest : public BlendTest
+{
+public:
+	BlendNpotTest (Context& context, const FboConfig& config)
+		: BlendTest(context, config, true)
+	{
+	}
+};
+
+BlendTest::BlendTest (Context& context, const FboConfig& config, bool npot)
+	: FboRenderCase	(context, (string(npot ? "blend_npot_" : "blend_") + config.getName()).c_str(), "Blend to fbo", config)
+	, m_fboWidth	(npot ? 111 : 128)
+	, m_fboHeight	(npot ? 122 : 128)
+{
+}
+
+bool BlendTest::isConfigSupported (const FboConfig& config)
+{
+	// \note Disabled for stencil configurations since doesn't exercise stencil buffer
+	return config.stencilbufferType	== GL_NONE;
+}
+
+void BlendTest::render (sglr::Context& context, Surface& dst)
+{
+	SingleTex2DShader	shader;
+	deUint32			shaderID		= context.createProgram(&shader);
+	int					width			= m_fboWidth;
+	int					height			= m_fboHeight;
+	deUint32			metaballsTex	= 1;
+
+	createMetaballsTex2D(context, metaballsTex, GL_RGBA, GL_UNSIGNED_BYTE, 64, 64);
+
+	Framebuffer fbo(context, getConfig(), width, height);
+	fbo.checkCompleteness();
+
+	shader.setUnit(context, shaderID, 0);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	context.viewport(0, 0, width, height);
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	context.clearColor(0.6f, 0.0f, 0.6f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	context.enable(GL_BLEND);
+	context.blendEquation(GL_FUNC_ADD);
+	context.blendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+	context.disable(GL_BLEND);
+
+	if (fbo.getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+		context.readPixels(dst, 0, 0, width, height);
+}
+
+class StencilClearsTest : public FboRenderCase
+{
+public:
+						StencilClearsTest		(Context& context, const FboConfig& config);
+	virtual				~StencilClearsTest		(void) {};
+
+	void				render					(sglr::Context& context, Surface& dst);
+
+	static bool			isConfigSupported		(const FboConfig& config);
+};
+
+StencilClearsTest::StencilClearsTest (Context& context, const FboConfig& config)
+	: FboRenderCase(context, config.getName().c_str(), "Stencil clears", config)
+{
+}
+
+void StencilClearsTest::render (sglr::Context& context, Surface& dst)
+{
+	SingleTex2DShader	shader;
+	deUint32			shaderID		= context.createProgram(&shader);
+	int					width			= 128;
+	int					height			= 128;
+	deUint32			quadsTex		= 1;
+	deUint32			metaballsTex	= 2;
+
+	createQuadsTex2D(context, quadsTex, GL_RGBA, GL_UNSIGNED_BYTE, width, height);
+	createMetaballsTex2D(context, metaballsTex, GL_RGBA, GL_UNSIGNED_BYTE, width, height);
+
+	Framebuffer fbo(context, getConfig(), width, height);
+	fbo.checkCompleteness();
+
+	// Bind framebuffer and clear
+	context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	context.viewport(0, 0, width, height);
+	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	// Do stencil clears
+	context.enable(GL_SCISSOR_TEST);
+	context.scissor(10, 16, 32, 120);
+	context.clearStencil(1);
+	context.clear(GL_STENCIL_BUFFER_BIT);
+	context.scissor(16, 32, 100, 64);
+	context.clearStencil(2);
+	context.clear(GL_STENCIL_BUFFER_BIT);
+	context.disable(GL_SCISSOR_TEST);
+
+	// Draw 2 textures with stecil tests
+	context.activeTexture(GL_TEXTURE0);
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	context.activeTexture(GL_TEXTURE1);
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+
+	context.enable(GL_STENCIL_TEST);
+	context.stencilFunc(GL_EQUAL, 1, 0xffffffffu);
+	shader.setUnit(context, shaderID, 0);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	context.stencilFunc(GL_EQUAL, 2, 0xffffffffu);
+	shader.setUnit(context, shaderID, 1);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	context.disable(GL_STENCIL_TEST);
+
+	if (fbo.getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.activeTexture(GL_TEXTURE0);
+		context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		shader.setUnit(context, shaderID, 0);
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+		context.readPixels(dst, 0, 0, width, height);
+}
+
+bool StencilClearsTest::isConfigSupported (const FboConfig& config)
+{
+	return config.stencilbufferType != GL_NONE;
+}
+
+class StencilTest : public FboRenderCase
+{
+public:
+						StencilTest				(Context& context, const FboConfig& config, bool npot = false);
+	virtual				~StencilTest			(void) {};
+
+	void				render					(sglr::Context& context, Surface& dst);
+
+	static bool			isConfigSupported		(const FboConfig& config);
+
+private:
+	int					m_fboWidth;
+	int					m_fboHeight;
+};
+
+class StencilNpotTest : public StencilTest
+{
+public:
+	StencilNpotTest (Context& context, const FboConfig& config)
+		: StencilTest(context, config, true)
+	{
+	}
+};
+
+StencilTest::StencilTest (Context& context, const FboConfig& config, bool npot)
+	: FboRenderCase	(context, (string(npot ? "npot_" : "") + config.getName()).c_str(), "Stencil ops", config)
+	, m_fboWidth	(npot ?  99 : 128)
+	, m_fboHeight	(npot ? 110 : 128)
+{
+}
+
+bool StencilTest::isConfigSupported (const FboConfig& config)
+{
+	return config.stencilbufferType != GL_NONE;
+}
+
+void StencilTest::render (sglr::Context& ctx, Surface& dst)
+{
+	FlatColorShader		colorShader;
+	SingleTex2DShader	texShader;
+	deUint32			colorShaderID	= ctx.createProgram(&colorShader);
+	deUint32			texShaderID		= ctx.createProgram(&texShader);
+	int					width			= m_fboWidth;
+	int					height			= m_fboHeight;
+	int					texWidth		= 64;
+	int					texHeight		= 64;
+	deUint32			quadsTex		= 1;
+	deUint32			metaballsTex	= 2;
+	bool				depth			= getConfig().depthbufferType != GL_NONE;
+
+	createQuadsTex2D(ctx, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight);
+	createMetaballsTex2D(ctx, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight);
+
+	Framebuffer fbo(ctx, getConfig(), width, height);
+	fbo.checkCompleteness();
+
+	// Bind framebuffer and clear
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	ctx.viewport(0, 0, width, height);
+	ctx.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	ctx.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	// Render intersecting quads - increment stencil on depth pass
+	ctx.enable(GL_DEPTH_TEST);
+	ctx.enable(GL_STENCIL_TEST);
+	ctx.stencilFunc(GL_ALWAYS, 0, 0xffu);
+	ctx.stencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+
+	colorShader.setColor(ctx, colorShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+	sglr::drawQuad(ctx, colorShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+
+	ctx.bindTexture(GL_TEXTURE_2D, quadsTex);
+	texShader.setUnit(ctx, texShaderID, 0);
+	sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(+1.0f, +1.0f, +1.0f));
+
+	// Draw quad with stencil test (stencil == 1 or 2 depending on depth) - decrement on stencil failure
+	ctx.disable(GL_DEPTH_TEST);
+	ctx.stencilFunc(GL_EQUAL, depth ? 2 : 1, 0xffu);
+	ctx.stencilOp(GL_DECR, GL_KEEP, GL_KEEP);
+	colorShader.setColor(ctx, colorShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+	sglr::drawQuad(ctx, colorShaderID, Vec3(-0.5f, -0.5f, 0.0f), Vec3(+0.5f, +0.5f, 0.0f));
+
+	// Draw metaballs with stencil test where stencil > 1 or 2 depending on depth buffer
+	ctx.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	ctx.stencilFunc(GL_GREATER, depth ? 1 : 2, 0xffu);
+	ctx.stencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+	sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+
+	ctx.disable(GL_STENCIL_TEST);
+
+	if (fbo.getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		ctx.activeTexture(GL_TEXTURE0);
+		ctx.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
+		ctx.viewport(0, 0, ctx.getWidth(), ctx.getHeight());
+		sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		ctx.readPixels(dst, 0, 0, ctx.getWidth(), ctx.getHeight());
+	}
+	else
+		ctx.readPixels(dst, 0, 0, width, height);
+}
+
+class SharedColorbufferTest : public FboRenderCase
+{
+public:
+						SharedColorbufferTest			(Context& context, const FboConfig& config);
+	virtual				~SharedColorbufferTest			(void) {};
+
+	void				render							(sglr::Context& context, Surface& dst);
+};
+
+SharedColorbufferTest::SharedColorbufferTest (Context& context, const FboConfig& config)
+	: FboRenderCase(context, config.getName().c_str(), "Shared colorbuffer", config)
+{
+}
+
+void SharedColorbufferTest::render (sglr::Context& context, Surface& dst)
+{
+	SingleTex2DShader	shader;
+	deUint32			shaderID		= context.createProgram(&shader);
+	int					width			= 128;
+	int					height			= 128;
+//	bool				depth			= getConfig().depthbufferFormat		!= GL_NONE;
+	bool				stencil			= getConfig().stencilbufferFormat	!= GL_NONE;
+
+	// Textures
+	deUint32	quadsTex		= 1;
+	deUint32	metaballsTex	= 2;
+	createQuadsTex2D(context, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+	createMetaballsTex2D(context, metaballsTex, GL_RGBA, GL_UNSIGNED_BYTE, 64, 64);
+
+	context.viewport(0, 0, width, height);
+
+	shader.setUnit(context, shaderID, 0);
+
+	// Fbo A
+	Framebuffer fboA(context, getConfig(), width, height);
+	fboA.checkCompleteness();
+
+	// Fbo B - don't create colorbuffer
+	FboConfig cfg = getConfig();
+	cfg.colorbufferType		= GL_NONE;
+	cfg.colorbufferFormat	= GL_NONE;
+	Framebuffer fboB(context, cfg, width, height);
+
+	// Attach color buffer from fbo A
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
+	switch (getConfig().colorbufferType)
+	{
+		case GL_TEXTURE_2D:
+			context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboA.getColorbuffer(), 0);
+			break;
+
+		case GL_RENDERBUFFER:
+			context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fboA.getColorbuffer());
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	// Clear depth and stencil in fbo B
+	context.clear(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	// Render quads to fbo 1, with depth 0.0
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboA.getFramebuffer());
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	if (stencil)
+	{
+		// Stencil to 1 in fbo A
+		context.clearStencil(1);
+		context.clear(GL_STENCIL_BUFFER_BIT);
+	}
+
+	context.enable(GL_DEPTH_TEST);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+	context.disable(GL_DEPTH_TEST);
+
+	// Blend metaballs to fbo 2
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	context.enable(GL_BLEND);
+	context.blendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	// Render small quad that is only visible if depth buffer is not shared with fbo A - or there is no depth bits
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	context.enable(GL_DEPTH_TEST);
+	sglr::drawQuad(context, shaderID, Vec3(0.5f, 0.5f, 0.5f), Vec3(1.0f, 1.0f, 0.5f));
+	context.disable(GL_DEPTH_TEST);
+
+	if (stencil)
+	{
+		FlatColorShader flatShader;
+		deUint32		flatShaderID = context.createProgram(&flatShader);
+
+		flatShader.setColor(context, flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+		// Clear subset of stencil buffer to 1
+		context.enable(GL_SCISSOR_TEST);
+		context.scissor(10, 10, 12, 25);
+		context.clearStencil(1);
+		context.clear(GL_STENCIL_BUFFER_BIT);
+		context.disable(GL_SCISSOR_TEST);
+
+		// Render quad with stencil mask == 1
+		context.enable(GL_STENCIL_TEST);
+		context.stencilFunc(GL_EQUAL, 1, 0xffu);
+		sglr::drawQuad(context, flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		context.disable(GL_STENCIL_TEST);
+	}
+
+	// Get results
+	if (fboA.getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.bindTexture(GL_TEXTURE_2D, fboA.getColorbuffer());
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+		context.readPixels(dst, 0, 0, width, height);
+}
+
+class SharedColorbufferClearsTest : public FboRenderCase
+{
+public:
+					SharedColorbufferClearsTest		(Context& context, const FboConfig& config);
+	virtual			~SharedColorbufferClearsTest	(void) {}
+
+	static bool		isConfigSupported				(const FboConfig& config);
+	void			render							(sglr::Context& context, Surface& dst);
+};
+
+SharedColorbufferClearsTest::SharedColorbufferClearsTest (Context& context, const FboConfig& config)
+	: FboRenderCase(context, config.getName().c_str(), "Shared colorbuffer clears", config)
+{
+}
+
+bool SharedColorbufferClearsTest::isConfigSupported (const FboConfig& config)
+{
+	return config.colorbufferType	!= GL_NONE &&
+		   config.depthbufferType	== GL_NONE &&
+		   config.stencilbufferType	== GL_NONE;
+}
+
+void SharedColorbufferClearsTest::render (sglr::Context& context, Surface& dst)
+{
+	int			width			= 128;
+	int			height			= 128;
+	deUint32	colorbuffer		= 1;
+
+	checkColorFormatSupport(context, getConfig().colorbufferFormat);
+
+	// Single colorbuffer
+	if (getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		context.bindTexture(GL_TEXTURE_2D, colorbuffer);
+		context.texImage2D(GL_TEXTURE_2D, 0, getConfig().colorbufferFormat, width, height);
+		context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	}
+	else
+	{
+		DE_ASSERT(getConfig().colorbufferType == GL_RENDERBUFFER);
+		context.bindRenderbuffer(GL_RENDERBUFFER, colorbuffer);
+		context.renderbufferStorage(GL_RENDERBUFFER, getConfig().colorbufferFormat, width, height);
+	}
+
+	// Multiple framebuffers sharing the colorbuffer
+	for (int fbo = 1; fbo <= 3; fbo++)
+	{
+		context.bindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+		if (getConfig().colorbufferType == GL_TEXTURE_2D)
+			context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0);
+		else
+			context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuffer);
+	}
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	// Check completeness
+	{
+		GLenum status = context.checkFramebufferStatus(GL_FRAMEBUFFER);
+		if (status != GL_FRAMEBUFFER_COMPLETE)
+			throw FboIncompleteException(getConfig(), status, __FILE__, __LINE__);
+	}
+
+	// Render to them
+	context.viewport(0, 0, width, height);
+	context.clearColor(0.0f, 0.0f, 1.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT);
+
+	context.enable(GL_SCISSOR_TEST);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 2);
+	context.clearColor(0.6f, 0.0f, 0.0f, 1.0f);
+	context.scissor(10, 10, 64, 64);
+	context.clear(GL_COLOR_BUFFER_BIT);
+	context.clearColor(0.0f, 0.6f, 0.0f, 1.0f);
+	context.scissor(60, 60, 40, 20);
+	context.clear(GL_COLOR_BUFFER_BIT);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 3);
+	context.clearColor(0.0f, 0.0f, 0.6f, 1.0f);
+	context.scissor(20, 20, 100, 10);
+	context.clear(GL_COLOR_BUFFER_BIT);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+	context.clearColor(0.6f, 0.0f, 0.6f, 1.0f);
+	context.scissor(20, 20, 5, 100);
+	context.clear(GL_COLOR_BUFFER_BIT);
+
+	context.disable(GL_SCISSOR_TEST);
+
+	if (getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		SingleTex2DShader	shader;
+		deUint32			shaderID = context.createProgram(&shader);
+
+		shader.setUnit(context, shaderID, 0);
+
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		sglr::drawQuad(context, shaderID, Vec3(-0.9f, -0.9f, 0.0f), Vec3(0.9f, 0.9f, 0.0f));
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+		context.readPixels(dst, 0, 0, width, height);
+}
+
+class SharedDepthbufferTest : public FboRenderCase
+{
+public:
+					SharedDepthbufferTest		(Context& context, const FboConfig& config);
+	virtual			~SharedDepthbufferTest		(void) {};
+
+	static bool		isConfigSupported			(const FboConfig& config);
+	void			render						(sglr::Context& context, Surface& dst);
+};
+
+SharedDepthbufferTest::SharedDepthbufferTest (Context& context, const FboConfig& config)
+	: FboRenderCase(context, config.getName().c_str(), "Shared depthbuffer", config)
+{
+}
+
+bool SharedDepthbufferTest::isConfigSupported (const FboConfig& config)
+{
+	return config.depthbufferType == GL_RENDERBUFFER;
+}
+
+void SharedDepthbufferTest::render (sglr::Context& context, Surface& dst)
+{
+	SingleTex2DShader	texShader;
+	FlatColorShader		colorShader;
+	deUint32			texShaderID		= context.createProgram(&texShader);
+	deUint32			colorShaderID	= context.createProgram(&colorShader);
+	int					width			= 128;
+	int					height			= 128;
+	bool				stencil			= getConfig().stencilbufferType != GL_NONE;
+
+	// Setup shaders
+	texShader.setUnit(context, texShaderID, 0);
+	colorShader.setColor(context, colorShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+	// Textures
+	deUint32 metaballsTex	= 5;
+	deUint32 quadsTex		= 6;
+	createMetaballsTex2D(context, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+	createQuadsTex2D(context, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+
+	context.viewport(0, 0, width, height);
+
+	// Fbo A
+	Framebuffer fboA(context, getConfig(), width, height);
+	fboA.checkCompleteness();
+
+	// Fbo B
+	FboConfig cfg = getConfig();
+	cfg.depthbufferType		= GL_NONE;
+	cfg.depthbufferFormat	= GL_NONE;
+	Framebuffer fboB(context, cfg, width, height);
+
+	// Bind depth buffer from fbo A to fbo B
+	DE_ASSERT(fboA.getConfig().depthbufferType == GL_RENDERBUFFER);
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
+	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboA.getDepthbuffer());
+
+	// Clear fbo B color to red and stencil to 1
+	context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+	context.clearStencil(1);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	// Enable depth test.
+	context.enable(GL_DEPTH_TEST);
+
+	// Render quad to fbo A
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboA.getFramebuffer());
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	// Render metaballs to fbo B
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+	context.disable(GL_DEPTH_TEST);
+
+	if (stencil)
+	{
+		// Clear subset of stencil buffer to 0
+		context.enable(GL_SCISSOR_TEST);
+		context.scissor(10, 10, 12, 25);
+		context.clearStencil(0);
+		context.clear(GL_STENCIL_BUFFER_BIT);
+		context.disable(GL_SCISSOR_TEST);
+
+		// Render quad with stencil mask == 0
+		context.enable(GL_STENCIL_TEST);
+		context.stencilFunc(GL_EQUAL, 0, 0xffu);
+		sglr::drawQuad(context, colorShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		context.disable(GL_STENCIL_TEST);
+	}
+
+	if (getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		// Render both to screen
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		context.bindTexture(GL_TEXTURE_2D, fboA.getColorbuffer());
+		sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f));
+		context.bindTexture(GL_TEXTURE_2D, fboB.getColorbuffer());
+		sglr::drawQuad(context, texShaderID, Vec3(0.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+	{
+		// Read results from fbo B
+		context.readPixels(dst, 0, 0, width, height);
+	}
+}
+
+class TexSubImageAfterRenderTest : public FboRenderCase
+{
+public:
+					TexSubImageAfterRenderTest		(Context& context, const FboConfig& config);
+	virtual			~TexSubImageAfterRenderTest		(void) {}
+
+	static bool		isConfigSupported				(const FboConfig& config);
+	void			render							(sglr::Context& context, Surface& dst);
+};
+
+TexSubImageAfterRenderTest::TexSubImageAfterRenderTest (Context& context, const FboConfig& config)
+	: FboRenderCase(context, (string("after_render_") + config.getName()).c_str(), "TexSubImage after rendering to texture", config)
+{
+}
+
+bool TexSubImageAfterRenderTest::isConfigSupported (const FboConfig& config)
+{
+	return config.colorbufferType == GL_TEXTURE_2D &&
+		   (config.colorbufferFormat == GL_RGB || config.colorbufferFormat == GL_RGBA) &&
+		   config.depthbufferType == GL_NONE &&
+		   config.stencilbufferType == GL_NONE;
+}
+
+void TexSubImageAfterRenderTest::render (sglr::Context& context, Surface& dst)
+{
+	SingleTex2DShader	shader;
+	deUint32			shaderID	= context.createProgram(&shader);
+	bool				isRGBA		= getConfig().colorbufferFormat == GL_RGBA;
+
+	tcu::TextureLevel fourQuads(tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), 64, 64);
+	tcu::fillWithRGBAQuads(fourQuads.getAccess());
+
+	tcu::TextureLevel metaballs(tcu::TextureFormat(isRGBA ? tcu::TextureFormat::RGBA : tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), 64, 64);
+	tcu::fillWithMetaballs(metaballs.getAccess(), 5, 3);
+
+	shader.setUnit(context, shaderID, 0);
+
+	deUint32 fourQuadsTex = 1;
+	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, 64, 64, 0, GL_RGB, GL_UNSIGNED_BYTE, fourQuads.getAccess().getDataPtr());
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 fboTex = 2;
+	context.bindTexture(GL_TEXTURE_2D, fboTex);
+	context.texImage2D(GL_TEXTURE_2D, 0, isRGBA ? GL_RGBA : GL_RGB, 128, 128);
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTex, 0);
+
+	// Render to fbo
+	context.viewport(0, 0, 128, 128);
+	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	// Update texture using TexSubImage2D
+	context.bindTexture(GL_TEXTURE_2D, fboTex);
+	context.texSubImage2D(GL_TEXTURE_2D, 0, 32, 32, 64, 64, isRGBA ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, metaballs.getAccess().getDataPtr());
+
+	// Draw to screen
+	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	context.viewport(0, 0, context.getWidth(), context.getHeight());
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+	context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+}
+
+class TexSubImageBetweenRenderTest : public FboRenderCase
+{
+public:
+					TexSubImageBetweenRenderTest		(Context& context, const FboConfig& config);
+	virtual			~TexSubImageBetweenRenderTest		(void) {}
+
+	static bool		isConfigSupported					(const FboConfig& config);
+	void			render								(sglr::Context& context, Surface& dst);
+};
+
+TexSubImageBetweenRenderTest::TexSubImageBetweenRenderTest (Context& context, const FboConfig& config)
+	: FboRenderCase(context, (string("between_render_") + config.getName()).c_str(), "TexSubImage between rendering calls", config)
+{
+}
+
+bool TexSubImageBetweenRenderTest::isConfigSupported (const FboConfig& config)
+{
+	return config.colorbufferType == GL_TEXTURE_2D &&
+		   (config.colorbufferFormat == GL_RGB || config.colorbufferFormat == GL_RGBA) &&
+		   config.depthbufferType == GL_NONE &&
+		   config.stencilbufferType == GL_NONE;
+}
+
+void TexSubImageBetweenRenderTest::render (sglr::Context& context, Surface& dst)
+{
+	SingleTex2DShader	shader;
+	deUint32			shaderID	= context.createProgram(&shader);
+	bool				isRGBA		= getConfig().colorbufferFormat == GL_RGBA;
+
+	tcu::TextureLevel fourQuads(tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), 64, 64);
+	tcu::fillWithRGBAQuads(fourQuads.getAccess());
+
+	tcu::TextureLevel metaballs(tcu::TextureFormat(isRGBA ? tcu::TextureFormat::RGBA : tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), 64, 64);
+	tcu::fillWithMetaballs(metaballs.getAccess(), 5, 3);
+
+	tcu::TextureLevel metaballs2(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 64, 64);
+	tcu::fillWithMetaballs(metaballs2.getAccess(), 5, 4);
+
+	deUint32 metaballsTex = 3;
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, metaballs2.getAccess().getDataPtr());
+
+	deUint32 fourQuadsTex = 1;
+	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, 64, 64, 0, GL_RGB, GL_UNSIGNED_BYTE, fourQuads.getAccess().getDataPtr());
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 fboTex = 2;
+	context.bindTexture(GL_TEXTURE_2D, fboTex);
+	context.texImage2D(GL_TEXTURE_2D, 0, isRGBA ? GL_RGBA : GL_RGB, 128, 128);
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTex, 0);
+
+	shader.setUnit(context, shaderID, 0);
+
+	// Render to fbo
+	context.viewport(0, 0, 128, 128);
+	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	// Update texture using TexSubImage2D
+	context.bindTexture(GL_TEXTURE_2D, fboTex);
+	context.texSubImage2D(GL_TEXTURE_2D, 0, 32, 32, 64, 64, isRGBA ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, metaballs.getAccess().getDataPtr());
+
+	// Render again to fbo
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	context.enable(GL_BLEND);
+	context.blendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+	context.disable(GL_BLEND);
+
+	// Draw to screen
+	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	context.viewport(0, 0, context.getWidth(), context.getHeight());
+	context.bindTexture(GL_TEXTURE_2D, fboTex);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+}
+
+class ResizeTest : public FboRenderCase
+{
+public:
+					ResizeTest				(Context& context, const FboConfig& config);
+	virtual			~ResizeTest				(void) {}
+
+	void			render					(sglr::Context& context, Surface& dst);
+};
+
+ResizeTest::ResizeTest (Context& context, const FboConfig& config)
+	: FboRenderCase(context, config.getName().c_str(), "Resize framebuffer", config)
+{
+}
+
+void ResizeTest::render (sglr::Context& context, Surface& dst)
+{
+	SingleTex2DShader	texShader;
+	FlatColorShader		colorShader;
+	deUint32			texShaderID		= context.createProgram(&texShader);
+	deUint32			colorShaderID	= context.createProgram(&colorShader);
+	deUint32			quadsTex		= 1;
+	deUint32			metaballsTex	= 2;
+	bool				depth			= getConfig().depthbufferType	 != GL_NONE;
+	bool				stencil			= getConfig().stencilbufferType	 != GL_NONE;
+
+	createQuadsTex2D(context, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+	createMetaballsTex2D(context, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 32, 32);
+
+	Framebuffer fbo(context, getConfig(), 128, 128);
+	fbo.checkCompleteness();
+
+	// Setup shaders
+	texShader.setUnit(context, texShaderID, 0);
+	colorShader.setColor(context, colorShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+	// Render quads
+	context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	context.viewport(0, 0, 128, 128);
+	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	if (fbo.getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		// Render fbo to screen
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
+		sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		// Restore binding
+		context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	}
+
+	int newWidth	= 64;
+	int newHeight	= 32;
+
+	// Resize buffers
+	switch (fbo.getConfig().colorbufferType)
+	{
+		case GL_TEXTURE_2D:
+			context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
+			context.texImage2D(GL_TEXTURE_2D, 0, fbo.getConfig().colorbufferFormat, newWidth, newHeight);
+			break;
+
+		case GL_RENDERBUFFER:
+			context.bindRenderbuffer(GL_RENDERBUFFER, fbo.getColorbuffer());
+			context.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().colorbufferFormat, newWidth, newHeight);
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	if (depth)
+	{
+		DE_ASSERT(fbo.getConfig().depthbufferType == GL_RENDERBUFFER);
+		context.bindRenderbuffer(GL_RENDERBUFFER, fbo.getDepthbuffer());
+		context.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().depthbufferFormat, newWidth, newHeight);
+	}
+
+	if (stencil)
+	{
+		DE_ASSERT(fbo.getConfig().stencilbufferType == GL_RENDERBUFFER);
+		context.bindRenderbuffer(GL_RENDERBUFFER, fbo.getStencilbuffer());
+		context.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().stencilbufferFormat, newWidth, newHeight);
+	}
+
+	// Render to resized fbo
+	context.viewport(0, 0, newWidth, newHeight);
+	context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	context.enable(GL_DEPTH_TEST);
+
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	sglr::drawQuad(context, texShaderID, Vec3(0.0f, 0.0f, -1.0f), Vec3(+1.0f, +1.0f, 1.0f));
+
+	context.disable(GL_DEPTH_TEST);
+
+	if (stencil)
+	{
+		context.enable(GL_SCISSOR_TEST);
+		context.scissor(10, 10, 5, 15);
+		context.clearStencil(1);
+		context.clear(GL_STENCIL_BUFFER_BIT);
+		context.disable(GL_SCISSOR_TEST);
+
+		context.enable(GL_STENCIL_TEST);
+		context.stencilFunc(GL_EQUAL, 1, 0xffu);
+		sglr::drawQuad(context, colorShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+		context.disable(GL_STENCIL_TEST);
+	}
+
+	if (getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
+		sglr::drawQuad(context, texShaderID, Vec3(-0.5f, -0.5f, 0.0f), Vec3(0.5f, 0.5f, 0.0f));
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+		context.readPixels(dst, 0, 0, newWidth, newHeight);
+}
+
+template <GLenum Buffers>
+class RecreateBuffersTest : public FboRenderCase
+{
+public:
+					RecreateBuffersTest			(Context& context, const FboConfig& config, bool rebind);
+	virtual			~RecreateBuffersTest		(void) {}
+
+	static bool		isConfigSupported			(const FboConfig& config);
+	void			render						(sglr::Context& context, Surface& dst);
+
+private:
+	bool			m_rebind;
+};
+
+template <GLenum Buffers>
+class RecreateBuffersNoRebindTest : public RecreateBuffersTest<Buffers>
+{
+public:
+	RecreateBuffersNoRebindTest (Context& context, const FboConfig& config)
+		: RecreateBuffersTest<Buffers>(context, config, false)
+	{
+	}
+};
+
+template <GLenum Buffers>
+class RecreateBuffersRebindTest : public RecreateBuffersTest<Buffers>
+{
+public:
+	RecreateBuffersRebindTest (Context& context, const FboConfig& config)
+		: RecreateBuffersTest<Buffers>(context, config, true)
+	{
+	}
+};
+
+template <GLenum Buffers>
+RecreateBuffersTest<Buffers>::RecreateBuffersTest (Context& context, const FboConfig& config, bool rebind)
+	: FboRenderCase		(context, (string(rebind ? "rebind_" : "no_rebind_") + config.getName()).c_str(), "Recreate buffers", config)
+	, m_rebind			(rebind)
+{
+}
+
+template <GLenum Buffers>
+bool RecreateBuffersTest<Buffers>::isConfigSupported (const FboConfig& config)
+{
+	if ((Buffers & GL_COLOR_BUFFER_BIT) && config.colorbufferType == GL_NONE)
+		return false;
+	if ((Buffers & GL_DEPTH_BUFFER_BIT) && config.depthbufferType == GL_NONE)
+		return false;
+	if ((Buffers & GL_STENCIL_BUFFER_BIT) && config.stencilbufferType == GL_NONE)
+		return false;
+	return true;
+}
+
+template <GLenum Buffers>
+void RecreateBuffersTest<Buffers>::render (sglr::Context& ctx, Surface& dst)
+{
+	SingleTex2DShader	texShader;
+	deUint32			texShaderID		= ctx.createProgram(&texShader);
+	int					width			= 128;
+	int					height			= 128;
+	deUint32			metaballsTex	= 1;
+	deUint32			quadsTex		= 2;
+	bool				stencil			= getConfig().stencilbufferType != GL_NONE;
+
+	createQuadsTex2D(ctx, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+	createMetaballsTex2D(ctx, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+
+	Framebuffer fbo(ctx, getConfig(), width, height);
+	fbo.checkCompleteness();
+
+	// Setup shader
+	texShader.setUnit(ctx, texShaderID, 0);
+
+	// Draw scene
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	ctx.viewport(0, 0, width, height);
+	ctx.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+	ctx.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	ctx.enable(GL_DEPTH_TEST);
+
+	ctx.bindTexture(GL_TEXTURE_2D, quadsTex);
+	sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	if (stencil)
+	{
+		ctx.enable(GL_SCISSOR_TEST);
+		ctx.scissor(width/4, height/4, width/2, height/2);
+		ctx.clearStencil(1);
+		ctx.clear(GL_STENCIL_BUFFER_BIT);
+		ctx.disable(GL_SCISSOR_TEST);
+	}
+
+	// Recreate buffers
+	if (!m_rebind)
+		ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	if (Buffers & GL_COLOR_BUFFER_BIT)
+	{
+		deUint32 colorbuf = fbo.getColorbuffer();
+		switch (fbo.getConfig().colorbufferType)
+		{
+			case GL_TEXTURE_2D:
+				ctx.deleteTextures(1, &colorbuf);
+				ctx.bindTexture(GL_TEXTURE_2D, colorbuf);
+				ctx.texImage2D(GL_TEXTURE_2D, 0, fbo.getConfig().colorbufferFormat, width, height);
+				ctx.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+				if (m_rebind)
+					ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuf, 0);
+				break;
+
+			case GL_RENDERBUFFER:
+				ctx.deleteRenderbuffers(1, &colorbuf);
+				ctx.bindRenderbuffer(GL_RENDERBUFFER, colorbuf);
+				ctx.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().colorbufferFormat, width, height);
+
+				if (m_rebind)
+					ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuf);
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	if (Buffers & GL_DEPTH_BUFFER_BIT)
+	{
+		deUint32 depthbuf = fbo.getDepthbuffer();
+		DE_ASSERT(fbo.getConfig().depthbufferType == GL_RENDERBUFFER);
+
+		ctx.deleteRenderbuffers(1, &depthbuf);
+		ctx.bindRenderbuffer(GL_RENDERBUFFER, depthbuf);
+		ctx.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().depthbufferFormat, width, height);
+
+		if (m_rebind)
+			ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthbuf);
+	}
+
+	if (Buffers & GL_STENCIL_BUFFER_BIT)
+	{
+		deUint32 stencilbuf = fbo.getStencilbuffer();
+		DE_ASSERT(fbo.getConfig().stencilbufferType == GL_RENDERBUFFER);
+
+		ctx.deleteRenderbuffers(1, &stencilbuf);
+		ctx.bindRenderbuffer(GL_RENDERBUFFER, stencilbuf);
+		ctx.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().stencilbufferFormat, width, height);
+
+		if (m_rebind)
+			ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, stencilbuf);
+	}
+
+	if (!m_rebind)
+		ctx.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+
+	ctx.clearColor(0.0f, 0.0f, 1.0f, 0.0f);
+	ctx.clearStencil(0);
+	ctx.clear(Buffers); // \note Clear only buffers that were re-created
+
+	if (stencil)
+	{
+		// \note Stencil test enabled only if we have stencil buffer
+		ctx.enable(GL_STENCIL_TEST);
+		ctx.stencilFunc(GL_EQUAL, 0, 0xffu);
+	}
+	ctx.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, 1.0f), Vec3(1.0f, 1.0f, -1.0f));
+	if (stencil)
+		ctx.disable(GL_STENCIL_TEST);
+
+	ctx.disable(GL_DEPTH_TEST);
+
+	if (fbo.getConfig().colorbufferType == GL_TEXTURE_2D)
+	{
+		// Unbind fbo
+		ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+		// Draw to screen
+		ctx.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
+		ctx.viewport(0, 0, ctx.getWidth(), ctx.getHeight());
+		sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		// Read from screen
+		ctx.readPixels(dst, 0, 0, ctx.getWidth(), ctx.getHeight());
+	}
+	else
+	{
+		// Read from fbo
+		ctx.readPixels(dst, 0, 0, width, height);
+	}
+}
+
+} // FboCases
+
+FboRenderTestGroup::FboRenderTestGroup (Context& context)
+	: TestCaseGroup(context, "render", "Rendering Tests")
+{
+}
+
+FboRenderTestGroup::~FboRenderTestGroup (void)
+{
+}
+
+namespace
+{
+
+struct TypeFormatPair
+{
+	GLenum		type;
+	GLenum		format;
+};
+
+template <typename CaseType>
+void addChildVariants (deqp::gles2::TestCaseGroup* group)
+{
+	TypeFormatPair colorbufferConfigs[] =
+	{
+//		{ GL_TEXTURE_2D,	GL_ALPHA },
+//		{ GL_TEXTURE_2D,	GL_LUMINANCE },
+//		{ GL_TEXTURE_2D,	GL_LUMINANCE_ALPHA },
+		{ GL_TEXTURE_2D,	GL_RGB },
+		{ GL_TEXTURE_2D,	GL_RGBA },
+		{ GL_RENDERBUFFER,	GL_RGB565 },
+		{ GL_RENDERBUFFER,	GL_RGB5_A1 },
+		{ GL_RENDERBUFFER,	GL_RGBA4 },
+//		{ GL_RENDERBUFFER,	GL_RGBA16F },
+//		{ GL_RENDERBUFFER,	GL_RGB16F }
+	};
+	TypeFormatPair depthbufferConfigs[] =
+	{
+		{ GL_NONE,			GL_NONE },
+		{ GL_RENDERBUFFER,	GL_DEPTH_COMPONENT16 }
+	};
+	TypeFormatPair stencilbufferConfigs[] =
+	{
+		{ GL_NONE,			GL_NONE },
+		{ GL_RENDERBUFFER,	GL_STENCIL_INDEX8 }
+	};
+
+	for (int colorbufferNdx = 0; colorbufferNdx < DE_LENGTH_OF_ARRAY(colorbufferConfigs); colorbufferNdx++)
+	for (int depthbufferNdx = 0; depthbufferNdx < DE_LENGTH_OF_ARRAY(depthbufferConfigs); depthbufferNdx++)
+	for (int stencilbufferNdx = 0; stencilbufferNdx < DE_LENGTH_OF_ARRAY(stencilbufferConfigs); stencilbufferNdx++)
+	{
+		FboConfig config;
+		config.colorbufferType		= colorbufferConfigs[colorbufferNdx].type;
+		config.colorbufferFormat	= colorbufferConfigs[colorbufferNdx].format;
+		config.depthbufferType		= depthbufferConfigs[depthbufferNdx].type;
+		config.depthbufferFormat	= depthbufferConfigs[depthbufferNdx].format;
+		config.stencilbufferType	= stencilbufferConfigs[stencilbufferNdx].type;
+		config.stencilbufferFormat	= stencilbufferConfigs[stencilbufferNdx].format;
+
+		if (CaseType::isConfigSupported(config))
+			group->addChild(new CaseType(group->getContext(), config));
+	}
+}
+
+template <typename CaseType>
+void createChildGroup (deqp::gles2::TestCaseGroup* parent, const char* name, const char* description)
+{
+	deqp::gles2::TestCaseGroup* tmpGroup = new deqp::gles2::TestCaseGroup(parent->getContext(), name, description);
+	parent->addChild(tmpGroup);
+	addChildVariants<CaseType>(tmpGroup);
+}
+
+template <GLbitfield Buffers>
+void createRecreateBuffersGroup (deqp::gles2::TestCaseGroup* parent, const char* name, const char* description)
+{
+	deqp::gles2::TestCaseGroup* tmpGroup = new deqp::gles2::TestCaseGroup(parent->getContext(), name, description);
+	parent->addChild(tmpGroup);
+	addChildVariants<FboCases::RecreateBuffersRebindTest<Buffers> >		(tmpGroup);
+	addChildVariants<FboCases::RecreateBuffersNoRebindTest<Buffers> >	(tmpGroup);
+}
+
+} // anonymous
+
+void FboRenderTestGroup::init (void)
+{
+	createChildGroup<FboCases::ColorClearsTest>					(this, "color_clear",		"Color buffer clears");
+	createChildGroup<FboCases::StencilClearsTest>				(this, "stencil_clear",		"Stencil buffer clears");
+
+	deqp::gles2::TestCaseGroup* colorGroup = new deqp::gles2::TestCaseGroup(m_context, "color", "Color buffer tests");
+	addChild(colorGroup);
+	addChildVariants<FboCases::MixTest>			(colorGroup);
+	addChildVariants<FboCases::MixNpotTest>		(colorGroup);
+	addChildVariants<FboCases::BlendTest>		(colorGroup);
+	addChildVariants<FboCases::BlendNpotTest>	(colorGroup);
+
+	deqp::gles2::TestCaseGroup* depthGroup = new deqp::gles2::TestCaseGroup(m_context, "depth", "Depth bufer tests");
+	addChild(depthGroup);
+	addChildVariants<FboCases::IntersectingQuadsTest>		(depthGroup);
+	addChildVariants<FboCases::IntersectingQuadsNpotTest>	(depthGroup);
+
+	deqp::gles2::TestCaseGroup* stencilGroup = new deqp::gles2::TestCaseGroup(m_context, "stencil", "Stencil buffer tests");
+	addChild(stencilGroup);
+	addChildVariants<FboCases::StencilTest>		(stencilGroup);
+	addChildVariants<FboCases::StencilNpotTest>	(stencilGroup);
+
+	createChildGroup<FboCases::SharedColorbufferClearsTest>		(this, "shared_colorbuffer_clear",	"Shared colorbuffer clears");
+	createChildGroup<FboCases::SharedColorbufferTest>			(this, "shared_colorbuffer",		"Shared colorbuffer tests");
+	createChildGroup<FboCases::SharedDepthbufferTest>			(this, "shared_depthbuffer",		"Shared depthbuffer tests");
+	createChildGroup<FboCases::ResizeTest>						(this, "resize",					"FBO resize tests");
+
+	createRecreateBuffersGroup<GL_COLOR_BUFFER_BIT>				(this, "recreate_colorbuffer",		"Recreate colorbuffer tests");
+	createRecreateBuffersGroup<GL_DEPTH_BUFFER_BIT>				(this, "recreate_depthbuffer",		"Recreate depthbuffer tests");
+	createRecreateBuffersGroup<GL_STENCIL_BUFFER_BIT>			(this, "recreate_stencilbuffer",	"Recreate stencilbuffer tests");
+
+	deqp::gles2::TestCaseGroup* texSubImageGroup = new deqp::gles2::TestCaseGroup(m_context, "texsubimage", "TexSubImage interop with FBO colorbuffer texture");
+	addChild(texSubImageGroup);
+	addChildVariants<FboCases::TexSubImageAfterRenderTest>		(texSubImageGroup);
+	addChildVariants<FboCases::TexSubImageBetweenRenderTest>	(texSubImageGroup);
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fFboRenderTest.hpp b/modules/gles2/functional/es2fFboRenderTest.hpp
new file mode 100644
index 0000000..27d5365
--- /dev/null
+++ b/modules/gles2/functional/es2fFboRenderTest.hpp
@@ -0,0 +1,49 @@
+#ifndef _ES2FFBORENDERTEST_HPP
+#define _ES2FFBORENDERTEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer Object Rendering Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class FboRenderTestGroup : public TestCaseGroup
+{
+public:
+					FboRenderTestGroup			(Context& context);
+	virtual			~FboRenderTestGroup			(void);
+
+	virtual void	init						(void);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FFBORENDERTEST_HPP
diff --git a/modules/gles2/functional/es2fFboStateQueryTests.cpp b/modules/gles2/functional/es2fFboStateQueryTests.cpp
new file mode 100644
index 0000000..aec3d3f
--- /dev/null
+++ b/modules/gles2/functional/es2fFboStateQueryTests.cpp
@@ -0,0 +1,230 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Fbo state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fFboStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es2fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deMath.h"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace
+{
+
+void checkIntEquals (tcu::TestContext& testCtx, GLint got, GLint expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void checkAttachmentParam(tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLenum target, GLenum attachment, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetFramebufferAttachmentParameteriv(target, attachment, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntEquals(testCtx, state, reference);
+}
+
+void checkColorAttachmentParam(tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLenum target, GLenum pname, GLenum reference)
+{
+	checkAttachmentParam(testCtx, gl, target, GL_COLOR_ATTACHMENT0, pname, reference);
+}
+
+class AttachmentObjectCase : public ApiCase
+{
+public:
+	AttachmentObjectCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		// texture
+		{
+			GLuint textureID = 0;
+			glGenTextures(1, &textureID);
+			glBindTexture(GL_TEXTURE_2D, textureID);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, textureID);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			glDeleteTextures(1, &textureID);
+		}
+
+		// rb
+		{
+			GLuint renderbufferID = 0;
+			glGenRenderbuffers(1, &renderbufferID);
+			glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbufferID);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, renderbufferID);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			glDeleteRenderbuffers(1, &renderbufferID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class AttachmentTextureLevelCase : public ApiCase
+{
+public:
+	AttachmentTextureLevelCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		// GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL can only be 0
+		{
+			GLuint textureID = 0;
+
+			glGenTextures(1, &textureID);
+			glBindTexture(GL_TEXTURE_2D, textureID);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 64, 64, 0, GL_RGB, GL_UNSIGNED_BYTE, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, 0);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			glDeleteTextures(1, &textureID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class AttachmentTextureCubeMapFaceCase : public ApiCase
+{
+public:
+	AttachmentTextureCubeMapFaceCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		{
+			GLuint textureID = 0;
+			glGenTextures(1, &textureID);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
+			expectError(GL_NO_ERROR);
+
+			const GLenum faces[] =
+			{
+				GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
+			};
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(faces); ++ndx)
+				glTexImage2D(faces[ndx], 0, GL_RGB, 64, 64, 0, GL_RGB, GL_UNSIGNED_BYTE, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(faces); ++ndx)
+			{
+				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, faces[ndx], textureID, 0);
+				checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, faces[ndx]);
+			}
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			glDeleteTextures(1, &textureID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+} // anonymous
+
+
+FboStateQueryTests::FboStateQueryTests (Context& context)
+	: TestCaseGroup(context, "fbo", "Fbo State Query tests")
+{
+}
+
+void FboStateQueryTests::init (void)
+{
+	addChild(new AttachmentObjectCase				(m_context, "framebuffer_attachment_object",				"FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE and FRAMEBUFFER_ATTACHMENT_OBJECT_NAME"));
+	addChild(new AttachmentTextureLevelCase			(m_context, "framebuffer_attachment_texture_level",			"FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL"));
+	addChild(new AttachmentTextureCubeMapFaceCase	(m_context, "framebuffer_attachment_texture_cube_map_face",	"FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE"));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fFboStateQueryTests.hpp b/modules/gles2/functional/es2fFboStateQueryTests.hpp
new file mode 100644
index 0000000..1a3259b
--- /dev/null
+++ b/modules/gles2/functional/es2fFboStateQueryTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FFBOSTATEQUERYTESTS_HPP
+#define _ES2FFBOSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Fbo state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class FboStateQueryTests : public TestCaseGroup
+{
+public:
+																		FboStateQueryTests	(Context& context);
+
+	void																init				(void);
+
+private:
+																		FboStateQueryTests	(const FboStateQueryTests& other);
+	FboStateQueryTests&													operator=			(const FboStateQueryTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FFBOSTATEQUERYTESTS_HPP
diff --git a/modules/gles2/functional/es2fFloatStateQueryTests.cpp b/modules/gles2/functional/es2fFloatStateQueryTests.cpp
new file mode 100644
index 0000000..8d8c1b3
--- /dev/null
+++ b/modules/gles2/functional/es2fFloatStateQueryTests.cpp
@@ -0,0 +1,1161 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Float State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fFloatStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es2fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuFormatUtil.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "glwEnums.hpp"
+
+#include <limits>
+
+using namespace glw; // GLint and other GL types
+using namespace deqp::gls;
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace FloatStateQueryVerifiers
+{
+namespace
+{
+
+const int FLOAT_EXPANSION_E = 0x3FFF;
+
+GLint64 expandGLFloatToInteger (GLfloat f)
+{
+	const GLuint64 referenceValue = (GLint64)((f * double(0xFFFFFFFFULL) - 1) / 2);
+	return referenceValue;
+}
+
+GLint clampToGLint (GLint64 val)
+{
+	return (GLint)de::clamp<GLint64>(val, std::numeric_limits<GLint>::min(), std::numeric_limits<GLint>::max());
+}
+
+} // anonymous
+
+// StateVerifier
+
+class StateVerifier : protected glu::CallLogWrapper
+{
+public:
+						StateVerifier					(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~StateVerifier					(); // make GCC happy
+
+	const char*			getTestNamePostfix				(void) const;
+
+	virtual void		verifyFloat						(tcu::TestContext& testCtx, GLenum name, GLfloat reference)																																		= DE_NULL;
+
+	// "Expanded" == Float to int conversion converts from [-1.0 to 1.0] -> [MIN_INT MAX_INT]
+	virtual void		verifyFloatExpanded				(tcu::TestContext& testCtx, GLenum name, GLfloat reference)																																		= DE_NULL;
+	virtual void		verifyFloat2Expanded			(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1)																												= DE_NULL;
+	virtual void		verifyFloat4Color				(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3)																		= DE_NULL;
+
+	// verify that the given range is completely whitin the GL state range
+	virtual void		verifyFloatRange				(tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max)																																= DE_NULL;
+
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+StateVerifier::StateVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+
+StateVerifier::~StateVerifier ()
+{
+}
+
+const char* StateVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+// GetBooleanVerifier
+
+class GetBooleanVerifier : public StateVerifier
+{
+public:
+			GetBooleanVerifier				(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyFloat						(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloatExpanded				(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloat2Expanded			(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1);
+	void	verifyFloat4Color				(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3);
+	void	verifyFloatRange				(tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max);
+};
+
+GetBooleanVerifier::GetBooleanVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getboolean")
+{
+}
+
+void GetBooleanVerifier::verifyFloat (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLboolean expectedGLState = reference ? GL_TRUE : GL_FALSE;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << (expectedGLState==GL_TRUE ? "GL_TRUE" : "GL_FALSE") << "; got " << (state == GL_TRUE ? "GL_TRUE" : (state == GL_FALSE ? "GL_FALSE" : "non-boolean")) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyFloatExpanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	DE_ASSERT(de::inRange(reference, -1.0f, 1.0f));
+	verifyFloat(testCtx, name, reference);
+}
+
+void GetBooleanVerifier::verifyFloat2Expanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1)
+{
+	DE_ASSERT(de::inRange(reference0, -1.0f, 1.0f));
+	DE_ASSERT(de::inRange(reference1, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+
+	const GLboolean referenceAsGLBoolean[] =
+	{
+		reference0 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference1 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+	};
+
+	StateQueryMemoryWriteGuard<GLboolean[2]> boolVector2;
+	glGetBooleanv(name, boolVector2);
+
+	if (!boolVector2.verifyValidity(testCtx))
+		return;
+
+	if (boolVector2[0] != referenceAsGLBoolean[0] ||
+		boolVector2[1] != referenceAsGLBoolean[1])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (boolVector2[0] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (boolVector2[1] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyFloat4Color (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3)
+{
+	using tcu::TestLog;
+
+	const GLboolean referenceAsGLBoolean[] =
+	{
+		reference0 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference1 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference2 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference3 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+	};
+
+	StateQueryMemoryWriteGuard<GLboolean[4]> boolVector4;
+	glGetBooleanv(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	if (boolVector4[0] != referenceAsGLBoolean[0] ||
+		boolVector4[1] != referenceAsGLBoolean[1] ||
+		boolVector4[2] != referenceAsGLBoolean[2] ||
+		boolVector4[3] != referenceAsGLBoolean[3])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (referenceAsGLBoolean[0] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[1] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[2] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[3] ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyFloatRange (tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean[2]> range;
+	glGetBooleanv(name, range);
+
+	if (!range.verifyValidity(testCtx))
+		return;
+
+	if (range[0] == GL_FALSE)
+	{
+		if (max < 0 || min < 0)
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: range [" << min << ", " << max << "] is not in range [" << (range[0] == GL_TRUE ? "GL_TRUE" : (range[0] == GL_FALSE ? "GL_FALSE" : "non-boolean")) << ", " << (range[1] == GL_TRUE ? "GL_TRUE" : (range[1] == GL_FALSE ? "GL_FALSE" : "non-boolean")) << "]"  << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean range");
+			return;
+		}
+	}
+	if (range[1] == GL_FALSE)
+	{
+		if (max > 0 || min > 0)
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: range [" << min << ", " << max << "] is not in range [" << (range[0] == GL_TRUE ? "GL_TRUE" : (range[0] == GL_FALSE ? "GL_FALSE" : "non-boolean")) << ", " << (range[1] == GL_TRUE ? "GL_TRUE" : (range[1] == GL_FALSE ? "GL_FALSE" : "non-boolean")) << "]"  << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean range");
+			return;
+		}
+	}
+}
+
+//GetIntegerVerifier
+
+class GetIntegerVerifier : public StateVerifier
+{
+public:
+			GetIntegerVerifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyFloat						(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloatExpanded				(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloat2Expanded			(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1);
+	void	verifyFloat4Color				(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3);
+	void	verifyFloatRange				(tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max);
+};
+
+GetIntegerVerifier::GetIntegerVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger")
+{
+}
+
+void GetIntegerVerifier::verifyFloat (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	const GLint expectedGLStateMax = StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint>(reference);
+	const GLint expectedGLStateMin = StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint>(reference);
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected rounding to the nearest integer, valid range [" << expectedGLStateMin << "," << expectedGLStateMax << "]; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyFloatExpanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	DE_ASSERT(de::inRange(reference, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	const GLint expectedGLStateMax = clampToGLint(expandGLFloatToInteger(reference) + FLOAT_EXPANSION_E);
+	const GLint expectedGLStateMin = clampToGLint(expandGLFloatToInteger(reference) - FLOAT_EXPANSION_E);
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected in range [" << toHex(expectedGLStateMin) << "," << toHex(expectedGLStateMax) << "]; got " << toHex((GLint)state) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyFloat2Expanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1)
+{
+	DE_ASSERT(de::inRange(reference0, -1.0f, 1.0f));
+	DE_ASSERT(de::inRange(reference1, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	const GLint referenceAsGLintMin[] =
+	{
+		clampToGLint(expandGLFloatToInteger(reference0) - FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference1) - FLOAT_EXPANSION_E)
+	};
+	const GLint referenceAsGLintMax[] =
+	{
+		clampToGLint(expandGLFloatToInteger(reference0) + FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference1) + FLOAT_EXPANSION_E)
+	};
+
+	StateQueryMemoryWriteGuard<GLint[2]> floatVector2;
+	glGetIntegerv(name, floatVector2);
+
+	if (!floatVector2.verifyValidity(testCtx))
+		return;
+
+	if (floatVector2[0] < referenceAsGLintMin[0] || floatVector2[0] > referenceAsGLintMax[0] ||
+		floatVector2[1] < referenceAsGLintMin[1] || floatVector2[1] > referenceAsGLintMax[1])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected in ranges "
+			<< "[" << toHex(referenceAsGLintMin[0]) << " " << toHex(referenceAsGLintMax[0]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[1]) << " " << toHex(referenceAsGLintMax[1]) << "]"
+			<< "; got "
+			<< toHex(floatVector2[0]) << ", "
+			<< toHex(floatVector2[1]) << " "<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyFloat4Color (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3)
+{
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	const GLint referenceAsGLintMin[] =
+	{
+		clampToGLint(expandGLFloatToInteger(reference0) - FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference1) - FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference2) - FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference3) - FLOAT_EXPANSION_E)
+	};
+	const GLint referenceAsGLintMax[] =
+	{
+		clampToGLint(expandGLFloatToInteger(reference0) + FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference1) + FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference2) + FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference3) + FLOAT_EXPANSION_E)
+	};
+
+	StateQueryMemoryWriteGuard<GLint[4]> floatVector4;
+	glGetIntegerv(name, floatVector4);
+
+	if (!floatVector4.verifyValidity(testCtx))
+		return;
+
+	if (floatVector4[0] < referenceAsGLintMin[0] || floatVector4[0] > referenceAsGLintMax[0] ||
+		floatVector4[1] < referenceAsGLintMin[1] || floatVector4[1] > referenceAsGLintMax[1] ||
+		floatVector4[2] < referenceAsGLintMin[2] || floatVector4[2] > referenceAsGLintMax[2] ||
+		floatVector4[3] < referenceAsGLintMin[3] || floatVector4[3] > referenceAsGLintMax[3])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected in ranges "
+			<< "[" << toHex(referenceAsGLintMin[0]) << " " << toHex(referenceAsGLintMax[0]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[1]) << " " << toHex(referenceAsGLintMax[1]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[2]) << " " << toHex(referenceAsGLintMax[2]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[3]) << " " << toHex(referenceAsGLintMax[3]) << "]"
+			<< "; got "
+			<< toHex(floatVector4[0]) << ", "
+			<< toHex(floatVector4[1]) << ", "
+			<< toHex(floatVector4[2]) << ", "
+			<< toHex(floatVector4[3]) << " "<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyFloatRange (tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max)
+{
+	using tcu::TestLog;
+
+	const GLint testRangeAsGLint[] =
+	{
+		StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint>(min),
+		StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint>(max)
+	};
+
+	StateQueryMemoryWriteGuard<GLint[2]> range;
+	glGetIntegerv(name, range);
+
+	if (!range.verifyValidity(testCtx))
+		return;
+
+	// check if test range outside of gl state range
+	if (testRangeAsGLint[0] < range[0] ||
+		testRangeAsGLint[1] > range[1])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: range ["
+			<< testRangeAsGLint[0] << ", "
+			<< testRangeAsGLint[1] << "]"
+			<< " is not in range ["
+			<< range[0] << ", "
+			<< range[1] << "]" << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer range");
+	}
+}
+
+//GetInteger64Verifier
+
+class GetInteger64Verifier : public StateVerifier
+{
+public:
+			GetInteger64Verifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyFloat						(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloatExpanded				(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloat2Expanded			(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1);
+	void	verifyFloat4Color				(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3);
+	void	verifyFloatRange				(tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max);
+};
+
+GetInteger64Verifier::GetInteger64Verifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger64")
+{
+}
+
+void GetInteger64Verifier::verifyFloat (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	const GLint64 expectedGLStateMax = StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint64>(reference);
+	const GLint64 expectedGLStateMin = StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint64>(reference);
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected rounding to the nearest integer, valid range [" << expectedGLStateMin << "," << expectedGLStateMax << "]; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyFloatExpanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	DE_ASSERT(de::inRange(reference, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	const GLint64 expectedGLStateMax = expandGLFloatToInteger(reference) + FLOAT_EXPANSION_E;
+	const GLint64 expectedGLStateMin = expandGLFloatToInteger(reference) - FLOAT_EXPANSION_E;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected in range [" << toHex(expectedGLStateMin) << "," << toHex(expectedGLStateMax) << "]; got " << toHex((GLint64)state) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyFloat2Expanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1)
+{
+	DE_ASSERT(de::inRange(reference0, -1.0f, 1.0f));
+	DE_ASSERT(de::inRange(reference1, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	const GLint64 referenceAsGLintMin[] =
+	{
+		expandGLFloatToInteger(reference0) - FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference1) - FLOAT_EXPANSION_E
+	};
+	const GLint64 referenceAsGLintMax[] =
+	{
+		expandGLFloatToInteger(reference0) + FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference1) + FLOAT_EXPANSION_E
+	};
+
+	StateQueryMemoryWriteGuard<GLint64[2]> floatVector2;
+	glGetInteger64v(name, floatVector2);
+
+	if (!floatVector2.verifyValidity(testCtx))
+		return;
+
+	if (floatVector2[0] < referenceAsGLintMin[0] || floatVector2[0] > referenceAsGLintMax[0] ||
+		floatVector2[1] < referenceAsGLintMin[1] || floatVector2[1] > referenceAsGLintMax[1])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected in ranges "
+			<< "[" << toHex(referenceAsGLintMin[0]) << " " << toHex(referenceAsGLintMax[0]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[1]) << " " << toHex(referenceAsGLintMax[1]) << "]"
+			<< "; got "
+			<< toHex(floatVector2[0]) << ", "
+			<< toHex(floatVector2[1]) << " "<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyFloat4Color (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3)
+{
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	const GLint64 referenceAsGLintMin[] =
+	{
+		expandGLFloatToInteger(reference0) - FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference1) - FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference2) - FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference3) - FLOAT_EXPANSION_E
+	};
+	const GLint64 referenceAsGLintMax[] =
+	{
+		expandGLFloatToInteger(reference0) + FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference1) + FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference2) + FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference3) + FLOAT_EXPANSION_E
+	};
+
+	StateQueryMemoryWriteGuard<GLint64[4]> floatVector4;
+	glGetInteger64v(name, floatVector4);
+
+	if (!floatVector4.verifyValidity(testCtx))
+		return;
+
+	if (floatVector4[0] < referenceAsGLintMin[0] || floatVector4[0] > referenceAsGLintMax[0] ||
+		floatVector4[1] < referenceAsGLintMin[1] || floatVector4[1] > referenceAsGLintMax[1] ||
+		floatVector4[2] < referenceAsGLintMin[2] || floatVector4[2] > referenceAsGLintMax[2] ||
+		floatVector4[3] < referenceAsGLintMin[3] || floatVector4[3] > referenceAsGLintMax[3])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected in ranges "
+			<< "[" << toHex(referenceAsGLintMin[0]) << " " << toHex(referenceAsGLintMax[0]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[1]) << " " << toHex(referenceAsGLintMax[1]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[2]) << " " << toHex(referenceAsGLintMax[2]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[3]) << " " << toHex(referenceAsGLintMax[3]) << "]"
+			<< "; got "
+			<< toHex(floatVector4[0]) << ", "
+			<< toHex(floatVector4[1]) << ", "
+			<< toHex(floatVector4[2]) << ", "
+			<< toHex(floatVector4[3]) << " "<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyFloatRange (tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max)
+{
+	using tcu::TestLog;
+
+	const GLint64 testRangeAsGLint[] =
+	{
+		StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint64>(min),
+		StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint64>(max)
+	};
+
+	StateQueryMemoryWriteGuard<GLint64[2]> range;
+	glGetInteger64v(name, range);
+
+	if (!range.verifyValidity(testCtx))
+		return;
+
+	// check if test range outside of gl state range
+	if (testRangeAsGLint[0] < range[0] ||
+		testRangeAsGLint[1] > range[1])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: range ["
+			<< testRangeAsGLint[0] << ", "
+			<< testRangeAsGLint[1] << "]"
+			<< " is not in range ["
+			<< range[0] << ", "
+			<< range[1] << "]" << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer range");
+	}
+}
+
+//GetFloatVerifier
+
+class GetFloatVerifier : public StateVerifier
+{
+public:
+			GetFloatVerifier			(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyFloat						(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloatExpanded				(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloat2Expanded			(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1);
+	void	verifyFloat4Color				(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3);
+	void	verifyFloatRange				(tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max);
+};
+
+GetFloatVerifier::GetFloatVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getfloat")
+{
+}
+
+void GetFloatVerifier::verifyFloat (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyFloatExpanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	DE_ASSERT(de::inRange(reference, -1.0f, 1.0f));
+	verifyFloat(testCtx, name, reference);
+}
+
+void GetFloatVerifier::verifyFloat2Expanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1)
+{
+	DE_ASSERT(de::inRange(reference0, -1.0f, 1.0f));
+	DE_ASSERT(de::inRange(reference1, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[2]> floatVector2;
+	glGetFloatv(name, floatVector2);
+
+	if (!floatVector2.verifyValidity(testCtx))
+		return;
+
+	if (floatVector2[0] != reference0 ||
+		floatVector2[1] != reference1)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference0 << ", " << reference1 << "; got " << floatVector2[0] << " " << floatVector2[1] << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyFloat4Color (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[4]> floatVector4;
+	glGetFloatv(name, floatVector4);
+
+	if (!floatVector4.verifyValidity(testCtx))
+		return;
+
+	if (floatVector4[0] != reference0 ||
+		floatVector4[1] != reference1 ||
+		floatVector4[2] != reference2 ||
+		floatVector4[3] != reference3)
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected "<< reference0 << ", " << reference1 << ", " << reference2 << ", " << reference3
+			<< "; got " << floatVector4[0] << ", " << floatVector4[1] << ", " << floatVector4[2] << ", " << floatVector4[3]
+			<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyFloatRange (tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[2]> floatVector2;
+	glGetFloatv(name, floatVector2);
+
+	if (!floatVector2.verifyValidity(testCtx))
+		return;
+
+	if (floatVector2[0] > min ||
+		floatVector2[1] < max)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected in range [" << min << ", " << max << "]; got [" << floatVector2[0] << " " << floatVector2[1]  << "]" << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float range");
+	}
+}
+
+} // FloatStateQueryVerifiers
+
+namespace
+{
+
+using namespace FloatStateQueryVerifiers;
+
+class DepthRangeCase : public ApiCase
+{
+public:
+	DepthRangeCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloat2Expanded(m_testCtx, GL_DEPTH_RANGE, 0.0f, 1.0f);
+		expectError(GL_NO_ERROR);
+
+		{
+			const struct FixedTest
+			{
+				float n, f;
+			} fixedTests[] =
+			{
+				{ 0.5f, 1.0f },
+				{ 0.0f, 0.5f },
+				{ 0.0f, 0.0f },
+				{ 1.0f, 1.0f }
+			};
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+			{
+				glDepthRangef(fixedTests[ndx].n, fixedTests[ndx].f);
+
+				m_verifier->verifyFloat2Expanded(m_testCtx, GL_DEPTH_RANGE, fixedTests[ndx].n, fixedTests[ndx].f);
+				expectError(GL_NO_ERROR);
+			}
+		}
+
+		{
+			const int numIterations = 120;
+			for (int i = 0; i < numIterations; ++i)
+			{
+				GLfloat n	= rnd.getFloat(0, 1);
+				GLfloat f	= rnd.getFloat(0, 1);
+
+				glDepthRangef(n, f);
+				m_verifier->verifyFloat2Expanded(m_testCtx, GL_DEPTH_RANGE, n, f);
+				expectError(GL_NO_ERROR);
+			}
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class LineWidthCase : public ApiCase
+{
+public:
+	LineWidthCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		GLfloat range[2] = {1};
+		glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, range);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyFloat(m_testCtx, GL_LINE_WIDTH, 1.0f);
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLfloat reference = rnd.getFloat(range[0], range[1]);
+
+			glLineWidth(reference);
+			m_verifier->verifyFloat(m_testCtx, GL_LINE_WIDTH, reference);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class PolygonOffsetFactorCase : public ApiCase
+{
+public:
+	PolygonOffsetFactorCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_FACTOR, 0.0f);
+		expectError(GL_NO_ERROR);
+
+		{
+			const float fixedTests[] =
+			{
+				0.0f, 0.5f, -0.5f, 1.5f
+			};
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+			{
+				glPolygonOffset(fixedTests[ndx], 0);
+				m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_FACTOR, fixedTests[ndx]);
+				expectError(GL_NO_ERROR);
+			}
+		}
+
+		{
+			const int numIterations = 120;
+			for (int i = 0; i < numIterations; ++i)
+			{
+				const GLfloat reference = rnd.getFloat(-64000, 64000);
+
+				glPolygonOffset(reference, 0);
+				m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_FACTOR, reference);
+				expectError(GL_NO_ERROR);
+			}
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class PolygonOffsetUnitsCase : public ApiCase
+{
+public:
+	PolygonOffsetUnitsCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_UNITS, 0.0f);
+		expectError(GL_NO_ERROR);
+
+		{
+			const float fixedTests[] =
+			{
+				0.0f, 0.5f, -0.5f, 1.5f
+			};
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+			{
+				glPolygonOffset(0, fixedTests[ndx]);
+				m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_UNITS, fixedTests[ndx]);
+				expectError(GL_NO_ERROR);
+			}
+		}
+
+		{
+			const int numIterations = 120;
+			for (int i = 0; i < numIterations; ++i)
+			{
+				const GLfloat reference = rnd.getFloat(-64000, 64000);
+
+				glPolygonOffset(0, reference);
+				m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_UNITS, reference);
+				expectError(GL_NO_ERROR);
+			}
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class SampleCoverageCase : public ApiCase
+{
+public:
+	SampleCoverageCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloat(m_testCtx, GL_SAMPLE_COVERAGE_VALUE, 1.0f);
+		expectError(GL_NO_ERROR);
+
+		{
+			const float fixedTests[] =
+			{
+				0.0f, 0.5f, 0.45f, 0.55f
+			};
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+			{
+				glSampleCoverage(fixedTests[ndx], GL_FALSE);
+				m_verifier->verifyFloat(m_testCtx, GL_SAMPLE_COVERAGE_VALUE, fixedTests[ndx]);
+				expectError(GL_NO_ERROR);
+			}
+		}
+
+		{
+			const float clampTests[] =
+			{
+				-1.0f, -1.5f, 1.45f, 3.55f
+			};
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(clampTests); ++ndx)
+			{
+				glSampleCoverage(clampTests[ndx], GL_FALSE);
+				m_verifier->verifyFloat(m_testCtx, GL_SAMPLE_COVERAGE_VALUE, de::clamp(clampTests[ndx], 0.0f, 1.0f));
+				expectError(GL_NO_ERROR);
+			}
+		}
+
+		{
+			const int numIterations = 120;
+			for (int i = 0; i < numIterations; ++i)
+			{
+				GLfloat		reference	= rnd.getFloat(0, 1);
+				GLboolean	invert		= rnd.getBool() ? GL_TRUE : GL_FALSE;
+
+				glSampleCoverage(reference, invert);
+				m_verifier->verifyFloat(m_testCtx, GL_SAMPLE_COVERAGE_VALUE, reference);
+				expectError(GL_NO_ERROR);
+			}
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ColorClearCase : public ApiCase
+{
+public:
+	ColorClearCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		// \note Initial color clear value check is temorarily removed. (until the framework does not alter it)
+		//m_verifier->verifyFloat4Color(m_testCtx, GL_COLOR_CLEAR_VALUE, 0, 0, 0, 0);
+		//expectError(GL_NO_ERROR);
+
+		{
+			const struct FixedTest
+			{
+				float r, g, b, a;
+			} fixedTests[] =
+			{
+				{ 0.5f, 1.0f, 0.5f, 1.0f },
+				{ 0.0f, 0.5f, 0.0f, 0.5f },
+				{ 0.0f, 0.0f, 0.0f, 0.0f },
+				{ 1.0f, 1.0f, 1.0f, 1.0f },
+			};
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+			{
+				glClearColor(fixedTests[ndx].r, fixedTests[ndx].g, fixedTests[ndx].b, fixedTests[ndx].a);
+				m_verifier->verifyFloat4Color(m_testCtx, GL_COLOR_CLEAR_VALUE, fixedTests[ndx].r, fixedTests[ndx].g, fixedTests[ndx].b, fixedTests[ndx].a);
+				expectError(GL_NO_ERROR);
+			}
+		}
+
+		{
+			const int numIterations = 120;
+			for (int i = 0; i < numIterations; ++i)
+			{
+				const GLfloat r = rnd.getFloat(0, 1);
+				const GLfloat g = rnd.getFloat(0, 1);
+				const GLfloat b = rnd.getFloat(0, 1);
+				const GLfloat a = rnd.getFloat(0, 1);
+
+				glClearColor(r, g, b, a);
+				m_verifier->verifyFloat4Color(m_testCtx, GL_COLOR_CLEAR_VALUE, r, g, b, a);
+				expectError(GL_NO_ERROR);
+			}
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class DepthClearCase : public ApiCase
+{
+public:
+	DepthClearCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		const int numIterations = 120;
+
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloatExpanded(m_testCtx, GL_DEPTH_CLEAR_VALUE, 1);
+		expectError(GL_NO_ERROR);
+
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLfloat ref = rnd.getFloat(0, 1);
+
+			glClearDepthf(ref);
+			m_verifier->verifyFloatExpanded(m_testCtx, GL_DEPTH_CLEAR_VALUE, ref);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class AliasedPointSizeRangeCase : public ApiCase
+{
+public:
+	AliasedPointSizeRangeCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyFloatRange(m_testCtx, GL_ALIASED_POINT_SIZE_RANGE, 1, 1);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class AliasedLineWidthRangeCase : public ApiCase
+{
+public:
+	AliasedLineWidthRangeCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyFloatRange(m_testCtx, GL_ALIASED_LINE_WIDTH_RANGE, 1, 1);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		StateVerifier* verifier = VERIFIERS[_verifierNdx];										\
+		CODE_BLOCK;																				\
+	}
+
+} // anonymous
+
+FloatStateQueryTests::FloatStateQueryTests (Context& context)
+	: TestCaseGroup			(context, "floats", "Float Values")
+	, m_verifierBoolean		(DE_NULL)
+	, m_verifierInteger		(DE_NULL)
+	, m_verifierFloat		(DE_NULL)
+{
+}
+
+FloatStateQueryTests::~FloatStateQueryTests (void)
+{
+	deinit();
+}
+
+void FloatStateQueryTests::init (void)
+{
+	DE_ASSERT(m_verifierBoolean == DE_NULL);
+	DE_ASSERT(m_verifierInteger == DE_NULL);
+	DE_ASSERT(m_verifierFloat == DE_NULL);
+
+	m_verifierBoolean		= new GetBooleanVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInteger		= new GetIntegerVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierFloat			= new GetFloatVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+
+	StateVerifier* verifiers[] = {m_verifierBoolean, m_verifierInteger, m_verifierFloat};
+
+	FOR_EACH_VERIFIER(verifiers, addChild(new DepthRangeCase				(m_context, verifier,	(std::string("depth_range")					+ verifier->getTestNamePostfix()).c_str(),	"DEPTH_RANGE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new LineWidthCase					(m_context, verifier,	(std::string("line_width")					+ verifier->getTestNamePostfix()).c_str(),	"LINE_WIDTH")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new PolygonOffsetFactorCase		(m_context, verifier,	(std::string("polygon_offset_factor")		+ verifier->getTestNamePostfix()).c_str(),	"POLYGON_OFFSET_FACTOR")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new PolygonOffsetUnitsCase		(m_context, verifier,	(std::string("polygon_offset_units")		+ verifier->getTestNamePostfix()).c_str(),	"POLYGON_OFFSET_UNITS")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new SampleCoverageCase			(m_context, verifier,	(std::string("sample_coverage_value")		+ verifier->getTestNamePostfix()).c_str(),	"SAMPLE_COVERAGE_VALUE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new ColorClearCase				(m_context, verifier,	(std::string("color_clear_value")			+ verifier->getTestNamePostfix()).c_str(),	"COLOR_CLEAR_VALUE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new DepthClearCase				(m_context, verifier,	(std::string("depth_clear_value")			+ verifier->getTestNamePostfix()).c_str(),	"DEPTH_CLEAR_VALUE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new AliasedPointSizeRangeCase		(m_context, verifier,	(std::string("aliased_point_size_range")	+ verifier->getTestNamePostfix()).c_str(),	"ALIASED_POINT_SIZE_RANGE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new AliasedLineWidthRangeCase		(m_context, verifier,	(std::string("aliased_line_width_range")	+ verifier->getTestNamePostfix()).c_str(),	"ALIASED_LINE_WIDTH_RANGE")));
+}
+
+void FloatStateQueryTests::deinit (void)
+{
+	if (m_verifierBoolean)
+	{
+		delete m_verifierBoolean;
+		m_verifierBoolean = DE_NULL;
+	}
+	if (m_verifierInteger)
+	{
+		delete m_verifierInteger;
+		m_verifierInteger = DE_NULL;
+	}
+	if (m_verifierFloat)
+	{
+		delete m_verifierFloat;
+		m_verifierFloat = DE_NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fFloatStateQueryTests.hpp b/modules/gles2/functional/es2fFloatStateQueryTests.hpp
new file mode 100644
index 0000000..2438108
--- /dev/null
+++ b/modules/gles2/functional/es2fFloatStateQueryTests.hpp
@@ -0,0 +1,66 @@
+#ifndef _ES2FFLOATSTATEQUERYTESTS_HPP
+#define _ES2FFLOATSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Float State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace FloatStateQueryVerifiers
+{
+
+class GetBooleanVerifier;
+class GetIntegerVerifier;
+class GetFloatVerifier;
+
+} // FloatStateQueryVerifiers
+
+class FloatStateQueryTests : public TestCaseGroup
+{
+public:
+															FloatStateQueryTests				(Context& context);
+															~FloatStateQueryTests				(void);
+
+	void													init								(void);
+	void													deinit								(void);
+
+private:
+															FloatStateQueryTests				(const FloatStateQueryTests& other);
+	FloatStateQueryTests&									operator=							(const FloatStateQueryTests& other);
+
+	FloatStateQueryVerifiers::GetBooleanVerifier*			m_verifierBoolean;
+	FloatStateQueryVerifiers::GetIntegerVerifier*			m_verifierInteger;
+	FloatStateQueryVerifiers::GetFloatVerifier*				m_verifierFloat;
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FFLOATSTATEQUERYTESTS_HPP
diff --git a/modules/gles2/functional/es2fFlushFinishTests.cpp b/modules/gles2/functional/es2fFlushFinishTests.cpp
new file mode 100644
index 0000000..49ebd96
--- /dev/null
+++ b/modules/gles2/functional/es2fFlushFinishTests.cpp
@@ -0,0 +1,610 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Flush and finish tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fFlushFinishTests.hpp"
+
+#include "gluRenderContext.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluDrawUtil.hpp"
+
+#include "glsCalibration.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deClock.h"
+#include "deThread.h"
+#include "deMath.h"
+
+#include <algorithm>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using tcu::Vec2;
+using deqp::gls::theilSenEstimator;
+using deqp::gls::LineParameters;
+
+namespace
+{
+
+enum
+{
+	MAX_VIEWPORT_SIZE		= 128,
+	MAX_SAMPLE_DURATION_US	= 1000*1000,
+	WAIT_TIME_MS			= 1200,
+	NUM_SAMPLES				= 25,
+	MIN_DRAW_CALL_COUNT		= 10,
+	MAX_DRAW_CALL_COUNT		= 1<<20,
+	NUM_ITERS_IN_SHADER		= 10
+};
+
+const float		NO_CORR_COEF_THRESHOLD		= 0.1f;
+const float		FLUSH_COEF_THRESHOLD		= 0.2f;
+const float		CORRELATED_COEF_THRESHOLD	= 0.5f;
+
+static void busyWait (int milliseconds)
+{
+	const deUint64	startTime	= deGetMicroseconds();
+	float			v			= 2.0f;
+
+	for (;;)
+	{
+		for (int i = 0; i < 10; i++)
+			v = deFloatSin(v);
+
+		if (deGetMicroseconds()-startTime >= deUint64(1000*milliseconds))
+			break;
+	}
+}
+
+class CalibrationFailedException : public std::runtime_error
+{
+public:
+	CalibrationFailedException (const std::string& reason) : std::runtime_error(reason) {}
+};
+
+class FlushFinishCase : public TestCase
+{
+public:
+	enum ExpectedBehavior
+	{
+		EXPECT_COEF_LESS_THAN = 0,
+		EXPECT_COEF_GREATER_THAN,
+	};
+
+							FlushFinishCase		(Context&			context,
+												 const char*		name,
+												 const char*		description,
+												 ExpectedBehavior	waitBehavior,
+												 float				waitThreshold,
+												 ExpectedBehavior	readBehavior,
+												 float				readThreshold);
+							~FlushFinishCase	(void);
+
+	void					init				(void);
+	void					deinit				(void);
+	IterateResult			iterate				(void);
+
+	struct Sample
+	{
+		int			numDrawCalls;
+		deUint64	waitTime;
+		deUint64	readPixelsTime;
+	};
+
+	struct CalibrationParams
+	{
+		int			maxDrawCalls;
+	};
+
+protected:
+	virtual void			waitForGL			(void) = 0;
+
+private:
+							FlushFinishCase		(const FlushFinishCase&);
+	FlushFinishCase&		operator=			(const FlushFinishCase&);
+
+	CalibrationParams		calibrate			(void);
+	void					analyzeResults		(const std::vector<Sample>& samples, const CalibrationParams& calibrationParams);
+
+	void					setupRenderState	(void);
+	void					render				(int numDrawCalls);
+	void					readPixels			(void);
+
+	const ExpectedBehavior	m_waitBehavior;
+	const float				m_waitThreshold;
+	const ExpectedBehavior	m_readBehavior;
+	const float				m_readThreshold;
+
+	glu::ShaderProgram*		m_program;
+};
+
+FlushFinishCase::FlushFinishCase (Context& context, const char* name, const char* description, ExpectedBehavior waitBehavior, float waitThreshold, ExpectedBehavior readBehavior, float readThreshold)
+	: TestCase			(context, name, description)
+	, m_waitBehavior	(waitBehavior)
+	, m_waitThreshold	(waitThreshold)
+	, m_readBehavior	(readBehavior)
+	, m_readThreshold	(readThreshold)
+	, m_program			(DE_NULL)
+{
+}
+
+FlushFinishCase::~FlushFinishCase (void)
+{
+	FlushFinishCase::deinit();
+}
+
+void FlushFinishCase::init (void)
+{
+	DE_ASSERT(!m_program);
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(),
+		glu::ProgramSources()
+			<< glu::VertexSource(
+				"attribute highp vec4 a_position;\n"
+				"varying highp vec4 v_coord;\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = a_position;\n"
+				"	v_coord = a_position;\n"
+				"}\n")
+			<< glu::FragmentSource(
+				"uniform mediump int u_numIters;\n"
+				"varying mediump vec4 v_coord;\n"
+				"void main (void)\n"
+				"{\n"
+				"	highp vec4 color = v_coord;\n"
+				"	for (int i = 0; i < " + de::toString(int(NUM_ITERS_IN_SHADER)) + "; i++)\n"
+				"		color = sin(color);\n"
+				"	gl_FragColor = color;\n"
+				"}\n"));
+
+	if (!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+		delete m_program;
+		m_program = DE_NULL;
+		TCU_FAIL("Compile failed");
+	}
+}
+
+void FlushFinishCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+tcu::TestLog& operator<< (tcu::TestLog& log, const FlushFinishCase::Sample& sample)
+{
+	log << TestLog::Message << sample.numDrawCalls << " calls:\t" << sample.waitTime << " us wait,\t" << sample.readPixelsTime << " us read" << TestLog::EndMessage;
+	return log;
+}
+
+void FlushFinishCase::setupRenderState (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const int				posLoc			= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	const int				viewportW		= de::min<int>(m_context.getRenderTarget().getWidth(), MAX_VIEWPORT_SIZE);
+	const int				viewportH		= de::min<int>(m_context.getRenderTarget().getHeight(), MAX_VIEWPORT_SIZE);
+
+	static const float s_positions[] =
+	{
+		-1.0f, -1.0f,
+		+1.0f, -1.0f,
+		-1.0f, +1.0f,
+		+1.0f, +1.0f
+	};
+
+	TCU_CHECK(posLoc >= 0);
+
+	gl.viewport(0, 0, viewportW, viewportH);
+	gl.useProgram(m_program->getProgram());
+	gl.enableVertexAttribArray(posLoc);
+	gl.vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, &s_positions[0]);
+	gl.enable(GL_BLEND);
+	gl.blendFunc(GL_ONE, GL_ONE);
+	gl.blendEquation(GL_FUNC_ADD);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set up render state");
+}
+
+void FlushFinishCase::render (int numDrawCalls)
+{
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+
+	const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	for (int ndx = 0; ndx < numDrawCalls; ndx++)
+		gl.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, &indices[0]);
+}
+
+void FlushFinishCase::readPixels (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	deUint8					tmp[4];
+
+	gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &tmp);
+}
+
+FlushFinishCase::CalibrationParams FlushFinishCase::calibrate (void)
+{
+	tcu::ScopedLogSection		section				(m_testCtx.getLog(), "CalibrationInfo", "Calibration info");
+	CalibrationParams			params;
+
+	// Find draw call count that results in desired maximum time.
+	{
+		deUint64			prevDuration			= 0;
+		int					prevDrawCount			= 1;
+		int					curDrawCount			= 1;
+
+		m_testCtx.getLog() << TestLog::Message << "Calibrating maximum draw call count, target duration = " << int(MAX_SAMPLE_DURATION_US) << " us" << TestLog::EndMessage;
+
+		for (;;)
+		{
+			deUint64 curDuration;
+
+			{
+				const deUint64	startTime	= deGetMicroseconds();
+				render(curDrawCount);
+				readPixels();
+				curDuration = deGetMicroseconds()-startTime;
+			}
+
+			m_testCtx.getLog() << TestLog::Message << "Duration with " << curDrawCount << " draw calls = " << curDuration << " us" << TestLog::EndMessage;
+
+			if (curDuration > MAX_SAMPLE_DURATION_US)
+			{
+				if (curDrawCount > 1)
+				{
+					// Compute final count by using linear estimation.
+					const float		a		= float(curDuration - prevDuration) / float(curDrawCount - prevDrawCount);
+					const float		b		= float(prevDuration) - a*float(prevDrawCount);
+					const float		est		= (float(MAX_SAMPLE_DURATION_US) - b) / a;
+
+					curDrawCount = de::clamp(deFloorFloatToInt32(est), 1, int(MAX_DRAW_CALL_COUNT));
+				}
+				// else: Settle on 1.
+
+				break;
+			}
+			else if (curDrawCount >= MAX_DRAW_CALL_COUNT)
+				break; // Settle on maximum.
+			else
+			{
+				prevDrawCount	= curDrawCount;
+				prevDuration	= curDuration;
+				curDrawCount	= curDrawCount*2;
+			}
+		}
+
+		params.maxDrawCalls = curDrawCount;
+
+		m_testCtx.getLog() << TestLog::Integer("MaxDrawCalls", "Maximum number of draw calls", "", QP_KEY_TAG_NONE, params.maxDrawCalls);
+	}
+
+	// Sanity check.
+	if (params.maxDrawCalls < MIN_DRAW_CALL_COUNT)
+		throw CalibrationFailedException("Calibration failed, maximum draw call count is too low");
+
+	return params;
+}
+
+struct CompareSampleDrawCount
+{
+	bool operator() (const FlushFinishCase::Sample& a, const FlushFinishCase::Sample& b) const { return a.numDrawCalls < b.numDrawCalls; }
+};
+
+std::vector<Vec2> getPointsFromSamples (const std::vector<FlushFinishCase::Sample>& samples, const deUint64 FlushFinishCase::Sample::*field)
+{
+	vector<Vec2> points(samples.size());
+
+	for (size_t ndx = 0; ndx < samples.size(); ndx++)
+		points[ndx] = Vec2(float(samples[ndx].numDrawCalls), float(samples[ndx].*field));
+
+	return points;
+}
+
+template<typename T>
+T getMaximumValue (const std::vector<FlushFinishCase::Sample>& samples, const T FlushFinishCase::Sample::*field)
+{
+	DE_ASSERT(!samples.empty());
+
+	T maxVal = samples[0].*field;
+
+	for (size_t ndx = 1; ndx < samples.size(); ndx++)
+		maxVal = de::max(maxVal, samples[ndx].*field);
+
+	return maxVal;
+}
+
+void FlushFinishCase::analyzeResults (const std::vector<Sample>& samples, const CalibrationParams& calibrationParams)
+{
+	const vector<Vec2>		waitTimes		= getPointsFromSamples(samples, &Sample::waitTime);
+	const vector<Vec2>		readTimes		= getPointsFromSamples(samples, &Sample::readPixelsTime);
+	const LineParameters	waitLine		= theilSenEstimator(waitTimes);
+	const LineParameters	readLine		= theilSenEstimator(readTimes);
+	const float				normWaitCoef	= waitLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US);
+	const float				normReadCoef	= readLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US);
+	bool					allOk			= true;
+
+	{
+		tcu::ScopedLogSection	section			(m_testCtx.getLog(), "Samples", "Samples");
+		vector<Sample>			sortedSamples	(samples.begin(), samples.end());
+
+		std::sort(sortedSamples.begin(), sortedSamples.end(), CompareSampleDrawCount());
+
+		for (vector<Sample>::const_iterator iter = sortedSamples.begin(); iter != sortedSamples.end(); ++iter)
+			m_testCtx.getLog() << *iter;
+	}
+
+	m_testCtx.getLog() << TestLog::Float("WaitCoefficient",				"Wait coefficient", "", QP_KEY_TAG_NONE, waitLine.coefficient)
+					   << TestLog::Float("ReadCoefficient",				"Read coefficient", "", QP_KEY_TAG_NONE, readLine.coefficient)
+					   << TestLog::Float("NormalizedWaitCoefficient",	"Normalized wait coefficient", "", QP_KEY_TAG_NONE, normWaitCoef)
+					   << TestLog::Float("NormalizedReadCoefficient",	"Normalized read coefficient", "", QP_KEY_TAG_NONE, normReadCoef);
+
+	{
+		const bool		waitCorrelated		= normWaitCoef > CORRELATED_COEF_THRESHOLD;
+		const bool		readCorrelated		= normReadCoef > CORRELATED_COEF_THRESHOLD;
+		const bool		waitNotCorr			= normWaitCoef < NO_CORR_COEF_THRESHOLD;
+		const bool		readNotCorr			= normReadCoef < NO_CORR_COEF_THRESHOLD;
+
+		if (waitCorrelated || waitNotCorr)
+			m_testCtx.getLog() << TestLog::Message << "Wait time is" << (waitCorrelated ? "" : " NOT") << " correlated to rendering workload size." << TestLog::EndMessage;
+		else
+			m_testCtx.getLog() << TestLog::Message << "Warning: Wait time correlation to rendering workload size is unclear." << TestLog::EndMessage;
+
+		if (readCorrelated || readNotCorr)
+			m_testCtx.getLog() << TestLog::Message << "Read time is" << (readCorrelated ? "" : " NOT") << " correlated to rendering workload size." << TestLog::EndMessage;
+		else
+			m_testCtx.getLog() << TestLog::Message << "Warning: Read time correlation to rendering workload size is unclear." << TestLog::EndMessage;
+	}
+
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		const float				coef		= ndx == 0 ? normWaitCoef : normReadCoef;
+		const char*				name		= ndx == 0 ? "wait" : "read";
+		const ExpectedBehavior	behavior	= ndx == 0 ? m_waitBehavior : m_readBehavior;
+		const float				threshold	= ndx == 0 ? m_waitThreshold : m_readThreshold;
+		const bool				isOk		= behavior == EXPECT_COEF_GREATER_THAN	? coef > threshold :
+											  behavior == EXPECT_COEF_LESS_THAN		? coef < threshold : false;
+		const char*				cmpName		= behavior == EXPECT_COEF_GREATER_THAN	? "greater than" :
+											  behavior == EXPECT_COEF_LESS_THAN		? "less than" : DE_NULL;
+
+		if (!isOk)
+		{
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << name << " coefficient to be " << cmpName << " " << threshold << TestLog::EndMessage;
+			allOk = false;
+		}
+	}
+
+	m_testCtx.setTestResult(allOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							allOk ? "Pass"				: "Suspicious performance behavior");
+}
+
+FlushFinishCase::IterateResult FlushFinishCase::iterate (void)
+{
+	vector<Sample>		samples		(NUM_SAMPLES);
+	CalibrationParams	params;
+
+	// Try to poke CPU into full speed. \todo [2013-12-26 pyry] Use more robust method.
+	busyWait(10);
+
+	setupRenderState();
+
+	// Do one full render cycle.
+	{
+		render(1);
+		readPixels();
+	}
+
+	// Calibrate.
+	try
+	{
+		params = calibrate();
+	}
+	catch (const CalibrationFailedException& e)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, e.what());
+		return STOP;
+	}
+
+	// Do measurement.
+	{
+		de::Random	rnd		(123);
+
+		for (size_t ndx = 0; ndx < samples.size(); ndx++)
+		{
+			const int	drawCallCount	= rnd.getInt(1, params.maxDrawCalls);
+			deUint64	waitStartTime;
+			deUint64	readStartTime;
+			deUint64	readFinishTime;
+
+			render(drawCallCount);
+
+			waitStartTime = deGetMicroseconds();
+			waitForGL();
+
+			readStartTime = deGetMicroseconds();
+			readPixels();
+			readFinishTime = deGetMicroseconds();
+
+			samples[ndx].numDrawCalls	= drawCallCount;
+			samples[ndx].waitTime		= readStartTime-waitStartTime;
+			samples[ndx].readPixelsTime	= readFinishTime-readStartTime;
+
+			if (m_testCtx.getWatchDog())
+				qpWatchDog_touch(m_testCtx.getWatchDog());
+		}
+	}
+
+	// Analyze - sets test case result.
+	analyzeResults(samples, params);
+
+	return STOP;
+}
+
+class WaitOnlyCase : public FlushFinishCase
+{
+public:
+	WaitOnlyCase (Context& context)
+		: FlushFinishCase(context, "wait", "Wait only", EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD, EXPECT_COEF_GREATER_THAN, -1000.0f /* practically nothing is expected */)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
+		FlushFinishCase::init();
+	}
+
+protected:
+	void waitForGL (void)
+	{
+		busyWait(WAIT_TIME_MS);
+	}
+};
+
+class FlushOnlyCase : public FlushFinishCase
+{
+public:
+	FlushOnlyCase (Context& context)
+		: FlushFinishCase(context, "flush", "Flush only", EXPECT_COEF_LESS_THAN, FLUSH_COEF_THRESHOLD, EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Single call to glFlush()" << TestLog::EndMessage;
+		FlushFinishCase::init();
+	}
+
+protected:
+	void waitForGL (void)
+	{
+		m_context.getRenderContext().getFunctions().flush();
+	}
+};
+
+class FlushWaitCase : public FlushFinishCase
+{
+public:
+	FlushWaitCase (Context& context)
+		: FlushFinishCase(context, "flush_wait", "Wait after flushing", EXPECT_COEF_LESS_THAN, FLUSH_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "glFlush() followed by " << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
+		FlushFinishCase::init();
+	}
+
+protected:
+	void waitForGL (void)
+	{
+		m_context.getRenderContext().getFunctions().flush();
+		busyWait(WAIT_TIME_MS);
+	}
+};
+
+class FinishOnlyCase : public FlushFinishCase
+{
+public:
+	FinishOnlyCase (Context& context)
+		: FlushFinishCase(context, "finish", "Finish only", EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Single call to glFinish()" << TestLog::EndMessage;
+		FlushFinishCase::init();
+	}
+
+protected:
+	void waitForGL (void)
+	{
+		m_context.getRenderContext().getFunctions().finish();
+	}
+};
+
+class FinishWaitCase : public FlushFinishCase
+{
+public:
+	FinishWaitCase (Context& context)
+		: FlushFinishCase(context, "finish_wait", "Finish and wait", EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "glFinish() followed by " << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
+		FlushFinishCase::init();
+	}
+
+protected:
+	void waitForGL (void)
+	{
+		m_context.getRenderContext().getFunctions().finish();
+		busyWait(WAIT_TIME_MS);
+	}
+};
+
+} // anonymous
+
+FlushFinishTests::FlushFinishTests (Context& context)
+	: TestCaseGroup(context, "flush_finish", "Flush and Finish tests")
+{
+}
+
+FlushFinishTests::~FlushFinishTests (void)
+{
+}
+
+void FlushFinishTests::init (void)
+{
+	addChild(new WaitOnlyCase	(m_context));
+	addChild(new FlushOnlyCase	(m_context));
+	addChild(new FlushWaitCase	(m_context));
+	addChild(new FinishOnlyCase	(m_context));
+	addChild(new FinishWaitCase	(m_context));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fFlushFinishTests.hpp b/modules/gles2/functional/es2fFlushFinishTests.hpp
new file mode 100644
index 0000000..940dadd
--- /dev/null
+++ b/modules/gles2/functional/es2fFlushFinishTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FFLUSHFINISHTESTS_HPP
+#define _ES2FFLUSHFINISHTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Flush and finish tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class FlushFinishTests : public TestCaseGroup
+{
+public:
+						FlushFinishTests	(Context& context);
+						~FlushFinishTests	(void);
+
+	void				init				(void);
+
+private:
+						FlushFinishTests	(const FlushFinishTests&);
+	FlushFinishTests&	operator=			(const FlushFinishTests&);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FFLUSHFINISHTESTS_HPP
diff --git a/modules/gles2/functional/es2fFragOpInteractionTests.cpp b/modules/gles2/functional/es2fFragOpInteractionTests.cpp
new file mode 100644
index 0000000..5340cfa
--- /dev/null
+++ b/modules/gles2/functional/es2fFragOpInteractionTests.cpp
@@ -0,0 +1,86 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader - render state interaction tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fFragOpInteractionTests.hpp"
+#include "glsFragOpInteractionCase.hpp"
+#include "deStringUtil.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using gls::FragOpInteractionCase;
+
+FragOpInteractionTests::FragOpInteractionTests (Context& context)
+	: TestCaseGroup(context, "interaction", "Shader - Render State Interaction Tests")
+{
+}
+
+FragOpInteractionTests::~FragOpInteractionTests (void)
+{
+}
+
+void FragOpInteractionTests::init (void)
+{
+	// .basic
+	{
+		tcu::TestCaseGroup* const	basicGroup	= new tcu::TestCaseGroup(m_testCtx, "basic_shader", "Basic shaders");
+		const deUint32				baseSeed	= 0x667eacfd;
+		const int					numCases	= 100;
+		rsg::ProgramParameters		params;
+
+		addChild(basicGroup);
+
+		params.version					= rsg::VERSION_100;
+
+		params.useScalarConversions		= true;
+		params.useSwizzle				= true;
+		params.useComparisonOps			= true;
+		params.useConditionals			= true;
+
+		params.vertexParameters.randomize						= true;
+		params.vertexParameters.maxStatementDepth				= 3;
+		params.vertexParameters.maxStatementsPerBlock			= 4;
+		params.vertexParameters.maxExpressionDepth				= 4;
+		params.vertexParameters.maxCombinedVariableScalars		= 64;
+
+		params.fragmentParameters.randomize						= true;
+		params.fragmentParameters.maxStatementDepth				= 3;
+		params.fragmentParameters.maxStatementsPerBlock			= 4;
+		params.fragmentParameters.maxExpressionDepth			= 4;
+		params.fragmentParameters.maxCombinedVariableScalars	= 64;
+
+		for (int ndx = 0; ndx < numCases; ndx++)
+		{
+			params.seed = baseSeed ^ deInt32Hash(ndx);
+			basicGroup->addChild(new FragOpInteractionCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), de::toString(ndx).c_str(), params));
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fFragOpInteractionTests.hpp b/modules/gles2/functional/es2fFragOpInteractionTests.hpp
new file mode 100644
index 0000000..73db702
--- /dev/null
+++ b/modules/gles2/functional/es2fFragOpInteractionTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FFRAGOPINTERACTIONTESTS_HPP
+#define _ES2FFRAGOPINTERACTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader - render state interaction tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class FragOpInteractionTests : public TestCaseGroup
+{
+public:
+								FragOpInteractionTests		(Context& context);
+								~FragOpInteractionTests		(void);
+
+	void						init						(void);
+
+private:
+								FragOpInteractionTests		(const FragOpInteractionTests&);
+	FragOpInteractionTests&		operator=					(const FragOpInteractionTests&);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FFRAGOPINTERACTIONTESTS_HPP
diff --git a/modules/gles2/functional/es2fFunctionalTests.cpp b/modules/gles2/functional/es2fFunctionalTests.cpp
new file mode 100644
index 0000000..af34ef4
--- /dev/null
+++ b/modules/gles2/functional/es2fFunctionalTests.cpp
@@ -0,0 +1,339 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Base class for a test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fFunctionalTests.hpp"
+
+#include "es2fColorClearTest.hpp"
+#include "es2fLightAmountTest.hpp"
+#include "es2fShaderExecuteTest.hpp"
+#include "es2fFboApiTest.hpp"
+#include "es2fFboRenderTest.hpp"
+#include "es2fFboCompletenessTests.hpp"
+#include "es2fRandomShaderTests.hpp"
+#include "es2fPrerequisiteTests.hpp"
+#include "es2fDepthTests.hpp"
+#include "es2fStencilTests.hpp"
+#include "es2fScissorTests.hpp"
+#include "es2fVertexArrayTest.hpp"
+#include "es2fRasterizationTests.hpp"
+#include "es2fDepthStencilClearTests.hpp"
+#include "es2fDepthStencilTests.hpp"
+#include "es2fBlendTests.hpp"
+#include "es2fRandomFragmentOpTests.hpp"
+#include "es2fMultisampleTests.hpp"
+#include "es2fUniformApiTests.hpp"
+#include "es2fBufferWriteTests.hpp"
+#include "es2fImplementationLimitTests.hpp"
+#include "es2fDepthRangeTests.hpp"
+#include "es2fDitheringTests.hpp"
+#include "es2fClippingTests.hpp"
+#include "es2fPolygonOffsetTests.hpp"
+#include "es2fDrawTests.hpp"
+#include "es2fFragOpInteractionTests.hpp"
+#include "es2fFlushFinishTests.hpp"
+#include "es2fDefaultVertexAttributeTests.hpp"
+#include "es2fLifetimeTests.hpp"
+
+#include "es2fTextureFormatTests.hpp"
+#include "es2fTextureWrapTests.hpp"
+#include "es2fTextureFilteringTests.hpp"
+#include "es2fTextureMipmapTests.hpp"
+#include "es2fTextureSizeTests.hpp"
+#include "es2fTextureSpecificationTests.hpp"
+#include "es2fTextureCompletenessTests.hpp"
+#include "es2fNegativeVertexArrayApiTests.hpp"
+#include "es2fNegativeTextureApiTests.hpp"
+#include "es2fNegativeFragmentApiTests.hpp"
+#include "es2fNegativeBufferApiTests.hpp"
+#include "es2fNegativeShaderApiTests.hpp"
+#include "es2fNegativeStateApiTests.hpp"
+#include "es2fVertexTextureTests.hpp"
+#include "es2fTextureUnitTests.hpp"
+
+#include "es2fShaderApiTests.hpp"
+#include "es2fShaderAlgorithmTests.hpp"
+#include "es2fShaderBuiltinVarTests.hpp"
+#include "es2fShaderConstExprTests.hpp"
+#include "es2fShaderDiscardTests.hpp"
+#include "es2fShaderIndexingTests.hpp"
+#include "es2fShaderLoopTests.hpp"
+#include "es2fShaderOperatorTests.hpp"
+#include "es2fShaderReturnTests.hpp"
+#include "es2fShaderStructTests.hpp"
+#include "es2fShaderMatrixTests.hpp"
+#include "es2fShaderTextureFunctionTests.hpp"
+#include "es2fAttribLocationTests.hpp"
+#include "es2fShaderInvarianceTests.hpp"
+#include "es2fShaderFragDataTests.hpp"
+
+// State query tests
+#include "es2fBooleanStateQueryTests.hpp"
+#include "es2fIntegerStateQueryTests.hpp"
+#include "es2fFloatStateQueryTests.hpp"
+#include "es2fTextureStateQueryTests.hpp"
+#include "es2fStringQueryTests.hpp"
+#include "es2fBufferObjectQueryTests.hpp"
+#include "es2fFboStateQueryTests.hpp"
+#include "es2fRboStateQueryTests.hpp"
+#include "es2fShaderStateQueryTests.hpp"
+
+#include "es2fReadPixelsTests.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+// ShadersTestGroup
+
+class ShadersTestGroup : public TestCaseGroup
+{
+public:
+	ShadersTestGroup (Context& context)
+		: TestCaseGroup(context, "shaders", "Shader Tests")
+	{
+	}
+
+	virtual ~ShadersTestGroup (void)
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new ShaderExecuteTest(m_context, "preprocessor",			"Preprocessor Tests"));
+		addChild(new ShaderExecuteTest(m_context, "constants",				"Constant Literal Tests"));
+		addChild(new ShaderExecuteTest(m_context, "linkage",				"Linkage Tests"));
+		addChild(new ShaderExecuteTest(m_context, "conversions",			"Type Conversion Tests"));
+		addChild(new ShaderExecuteTest(m_context, "conditionals",			"Conditionals Tests"));
+		addChild(new ShaderExecuteTest(m_context, "declarations",			"Declarations Tests"));
+		addChild(new ShaderExecuteTest(m_context, "swizzles",				"Swizzle Tests"));
+		addChild(new ShaderExecuteTest(m_context, "functions",				"Function Tests"));
+		addChild(new ShaderExecuteTest(m_context, "keywords",				"Keyword Tests"));
+		addChild(new ShaderExecuteTest(m_context, "reserved_operators",		"Reserved Operator Tests"));
+		addChild(new ShaderExecuteTest(m_context, "qualification_order",	"Order of Qualification Tests"));
+		addChild(new ShaderExecuteTest(m_context, "scoping",				"Scoping of Declarations"));
+
+		addChild(new ShaderIndexingTests		(m_context));
+		addChild(new ShaderLoopTests			(m_context));
+		addChild(new ShaderOperatorTests		(m_context));
+		addChild(new ShaderMatrixTests			(m_context));
+		addChild(new ShaderReturnTests			(m_context));
+		addChild(new ShaderDiscardTests			(m_context));
+		addChild(new ShaderStructTests			(m_context));
+		addChild(new ShaderBuiltinVarTests		(m_context));
+		addChild(new ShaderTextureFunctionTests	(m_context));
+		addChild(new ShaderInvarianceTests		(m_context));
+		addChild(new ShaderFragDataTests		(m_context));
+		addChild(new ShaderAlgorithmTests		(m_context));
+		addChild(new ShaderConstExprTests		(m_context));
+
+		addChild(new RandomShaderTests(m_context));
+	}
+};
+
+// TextureTestGroup
+
+class TextureTestGroup : public TestCaseGroup
+{
+public:
+	TextureTestGroup (Context& context)
+		: TestCaseGroup(context, "texture", "Texture Tests")
+	{
+	}
+
+	virtual ~TextureTestGroup (void)
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new TextureFormatTests			(m_context));
+		addChild(new TextureSizeTests			(m_context));
+		addChild(new TextureWrapTests			(m_context));
+		addChild(new TextureFilteringTests		(m_context));
+		addChild(new TextureMipmapTests			(m_context));
+		addChild(new TextureSpecificationTests	(m_context));
+		addChild(new TextureCompletenessTests	(m_context));
+		addChild(new VertexTextureTests			(m_context));
+		addChild(new TextureUnitTests			(m_context));
+	}
+
+	virtual void deinit (void)
+	{
+	}
+};
+
+class BufferTests : public TestCaseGroup
+{
+public:
+	BufferTests (Context& context)
+		: TestCaseGroup(context, "buffer", "Buffer object tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new BufferWriteTests(m_context));
+	}
+};
+
+// FboTestGroup
+
+class FboTestGroup : public TestCaseGroup
+{
+public:
+	FboTestGroup (Context& context)
+		: TestCaseGroup(context, "fbo", "Framebuffer Object Tests")
+	{
+	}
+
+	virtual ~FboTestGroup (void)
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new FboApiTestGroup(m_context));
+		addChild(new FboRenderTestGroup(m_context));
+		addChild(createFboCompletenessTests(m_context));
+	}
+};
+
+// NegativeApiTestGroup
+
+class NegativeApiTestGroup : public TestCaseGroup
+{
+public:
+	NegativeApiTestGroup (Context& context)
+		: TestCaseGroup(context, "negative_api", "Negative API Tests")
+	{
+	}
+
+	virtual ~NegativeApiTestGroup (void)
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new NegativeBufferApiTests			(m_context));
+		addChild(new NegativeFragmentApiTests		(m_context));
+		addChild(new NegativeShaderApiTests			(m_context));
+		addChild(new NegativeStateApiTests			(m_context));
+		addChild(new NegativeTextureApiTests		(m_context));
+		addChild(new NegativeVertexArrayApiTests	(m_context));
+	}
+};
+
+// FragmentOpTests
+
+class FragmentOpTests : public TestCaseGroup
+{
+public:
+	FragmentOpTests (Context& context)
+		: TestCaseGroup(context, "fragment_ops", "Per-Fragment Operation Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new DepthTests				(m_context));
+		addChild(new StencilTests			(m_context));
+		addChild(new DepthStencilTests		(m_context));
+		addChild(new ScissorTests			(m_context));
+		addChild(new BlendTests				(m_context));
+		addChild(new RandomFragmentOpTests	(m_context));
+		addChild(new FragOpInteractionTests	(m_context));
+	}
+};
+
+// StateQueryTests
+
+class StateQueryTests : public TestCaseGroup
+{
+public:
+	StateQueryTests (Context& context)
+		: TestCaseGroup(context, "state_query", "State Query Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new BooleanStateQueryTests		(m_context));
+		addChild(new IntegerStateQueryTests		(m_context));
+		addChild(new FloatStateQueryTests		(m_context));
+		addChild(new TextureStateQueryTests		(m_context));
+		addChild(new StringQueryTests			(m_context));
+		addChild(new BufferObjectQueryTests		(m_context));
+		addChild(new FboStateQueryTests			(m_context));
+		addChild(new RboStateQueryTests			(m_context));
+		addChild(new ShaderStateQueryTests		(m_context));
+	}
+};
+
+// FunctionalTestGroup
+
+FunctionalTests::FunctionalTests (Context& context)
+	: TestCaseGroup(context, "functional", "Functionality Tests")
+{
+}
+
+FunctionalTests::~FunctionalTests (void)
+{
+}
+
+void FunctionalTests::init (void)
+{
+	addChild(new PrerequisiteTests			(m_context));
+	addChild(new ImplementationLimitTests	(m_context));
+	addChild(new ColorClearTest				(m_context));
+	addChild(new DepthStencilClearTests		(m_context));
+	addChild(new BufferTests				(m_context));
+	addChild(new LightAmountTest			(m_context));
+	addChild(new ShadersTestGroup			(m_context));
+	addChild(new TextureTestGroup			(m_context));
+	addChild(new FragmentOpTests			(m_context));
+	addChild(new FboTestGroup				(m_context));
+	addChild(new VertexArrayTestGroup		(m_context));
+	addChild(new ShaderApiTests				(m_context));
+	addChild(new NegativeApiTestGroup		(m_context));
+	addChild(new RasterizationTests			(m_context));
+	addChild(createAttributeLocationTests	(m_context));
+	addChild(new MultisampleTests			(m_context));
+	addChild(new UniformApiTests			(m_context));
+	addChild(new ReadPixelsTests			(m_context));
+	addChild(new DepthRangeTests			(m_context));
+	addChild(new DitheringTests				(m_context));
+	addChild(new StateQueryTests			(m_context));
+	addChild(new ClippingTests				(m_context));
+	addChild(new PolygonOffsetTests			(m_context));
+	addChild(new DrawTests					(m_context));
+	addChild(new FlushFinishTests			(m_context));
+	addChild(new DefaultVertexAttributeTests(m_context));
+	addChild(createLifetimeTests			(m_context));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fFunctionalTests.hpp b/modules/gles2/functional/es2fFunctionalTests.hpp
new file mode 100644
index 0000000..81a3485
--- /dev/null
+++ b/modules/gles2/functional/es2fFunctionalTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FFUNCTIONALTESTS_HPP
+#define _ES2FFUNCTIONALTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Functional tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class FunctionalTests : public TestCaseGroup
+{
+public:
+						FunctionalTests		(Context& context);
+						~FunctionalTests	(void);
+
+	void				init				(void);
+
+private:
+						FunctionalTests		(const FunctionalTests& other);
+	FunctionalTests&	operator=			(const FunctionalTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FFUNCTIONALTESTS_HPP
diff --git a/modules/gles2/functional/es2fImplementationLimitTests.cpp b/modules/gles2/functional/es2fImplementationLimitTests.cpp
new file mode 100644
index 0000000..b2813b1
--- /dev/null
+++ b/modules/gles2/functional/es2fImplementationLimitTests.cpp
@@ -0,0 +1,218 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Implementation-defined limit tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fImplementationLimitTests.hpp"
+#include "tcuTestLog.hpp"
+#include "gluDefs.hpp"
+#include "gluStrUtil.hpp"
+#include "gluRenderContext.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using namespace glw; // GL types
+
+namespace LimitQuery
+{
+
+// Query function template.
+template<typename T>
+T query (const glw::Functions& gl, deUint32 param);
+
+// Compare template.
+template<typename T>
+inline bool compare (const T& min, const T& reported) { return min <= reported; }
+
+// Types for queries
+
+struct NegInt
+{
+	GLint value;
+	NegInt (GLint value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const NegInt& v) { return str << v.value; }
+
+struct FloatRange
+{
+	float min;
+	float max;
+	FloatRange (float min_, float max_) : min(min_), max(max_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const FloatRange& range) { return str << range.min << ", " << range.max; }
+
+// For custom formatting
+struct Boolean
+{
+	GLboolean value;
+	Boolean (GLboolean value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const Boolean& boolean) { return str << (boolean.value ? "GL_TRUE" : "GL_FALSE"); }
+
+// Query function implementations.
+template<>
+GLint query<GLint> (const glw::Functions& gl, deUint32 param)
+{
+	GLint val = -1;
+	gl.getIntegerv(param, &val);
+	return val;
+}
+
+template<>
+GLfloat query<GLfloat> (const glw::Functions& gl, deUint32 param)
+{
+	GLfloat val = -1000.f;
+	gl.getFloatv(param, &val);
+	return val;
+}
+
+template<>
+NegInt query<NegInt> (const glw::Functions& gl, deUint32 param)
+{
+	return NegInt(query<GLint>(gl, param));
+}
+
+template<>
+Boolean query<Boolean> (const glw::Functions& gl, deUint32 param)
+{
+	GLboolean val = GL_FALSE;
+	gl.getBooleanv(param, &val);
+	return Boolean(val);
+}
+
+template<>
+FloatRange query<FloatRange> (const glw::Functions& gl, deUint32 param)
+{
+	float v[2] = { -1.0f, -1.0f };
+	gl.getFloatv(param, &v[0]);
+	return FloatRange(v[0], v[1]);
+}
+
+// Special comparison operators
+template<>
+bool compare<Boolean> (const Boolean& min, const Boolean& reported)
+{
+	return !min.value || (min.value && reported.value);
+}
+
+template<>
+bool compare<NegInt> (const NegInt& min, const NegInt& reported)
+{
+	// Reverse comparison.
+	return reported.value <= min.value;
+}
+
+template<>
+bool compare<FloatRange> (const FloatRange& min, const FloatRange& reported)
+{
+	return reported.min <= min.min && min.max <= reported.max;
+}
+
+} // LimitQuery
+
+using namespace LimitQuery;
+using tcu::TestLog;
+
+template<typename T>
+class LimitQueryCase : public TestCase
+{
+public:
+	LimitQueryCase (Context& context, const char* name, const char* description, deUint32 limit, const T& minRequiredValue)
+		: TestCase				(context, name, description)
+		, m_limit				(limit)
+		, m_minRequiredValue	(minRequiredValue)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+		const T					value	= query<T>(m_context.getRenderContext().getFunctions(), m_limit);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Query failed");
+
+		const bool isOk = compare<T>(m_minRequiredValue, value);
+
+		m_testCtx.getLog() << TestLog::Message << "Reported: " << value << TestLog::EndMessage;
+		m_testCtx.getLog() << TestLog::Message << "Minimum required: " << m_minRequiredValue << TestLog::EndMessage;
+
+		if (!isOk)
+			m_testCtx.getLog() << TestLog::Message << "FAIL: reported value is less than minimum required value!" << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Requirement not satisfied");
+		return STOP;
+	}
+
+private:
+	deUint32	m_limit;
+	T			m_minRequiredValue;
+};
+
+ImplementationLimitTests::ImplementationLimitTests (Context& context)
+	: TestCaseGroup(context, "implementation_limits", "Implementation-defined limits")
+{
+}
+
+ImplementationLimitTests::~ImplementationLimitTests (void)
+{
+}
+
+void ImplementationLimitTests::init (void)
+{
+#define LIMIT_CASE(NAME, PARAM, TYPE, MIN_VAL)	\
+	addChild(new LimitQueryCase<TYPE>(m_context, #NAME, #PARAM, PARAM, MIN_VAL))
+
+	LIMIT_CASE(subpixel_bits,						GL_SUBPIXEL_BITS,						GLint,		4);
+	LIMIT_CASE(max_texture_size,					GL_MAX_TEXTURE_SIZE,					GLint,		64);
+	LIMIT_CASE(max_cube_map_texture_size,			GL_MAX_CUBE_MAP_TEXTURE_SIZE,			GLint,		16);
+	// GL_MAX_VIEWPORT_DIMS
+	LIMIT_CASE(aliased_point_size_range,			GL_ALIASED_POINT_SIZE_RANGE,			FloatRange,	FloatRange(1,1));
+	LIMIT_CASE(aliased_line_width_range,			GL_ALIASED_LINE_WIDTH_RANGE,			FloatRange,	FloatRange(1,1));
+//	LIMIT_CASE(sample_buffers,						GL_SAMPLE_BUFFERS,						GLint,		0);
+//	LIMIT_CASE(samples,								GL_SAMPLES,								GLint,		0);
+	LIMIT_CASE(num_compressed_texture_formats,		GL_NUM_COMPRESSED_TEXTURE_FORMATS,		GLint,		0);
+	LIMIT_CASE(num_shader_binary_formats,			GL_NUM_SHADER_BINARY_FORMATS,			GLint,		0);
+	LIMIT_CASE(shader_compiler,						GL_SHADER_COMPILER,						Boolean,	GL_FALSE);
+	// Shader precision format
+	LIMIT_CASE(max_vertex_attribs,					GL_MAX_VERTEX_ATTRIBS,					GLint,		8);
+	LIMIT_CASE(max_vertex_uniform_vectors,			GL_MAX_VERTEX_UNIFORM_VECTORS,			GLint,		128);
+	LIMIT_CASE(max_varying_vectors,					GL_MAX_VARYING_VECTORS,					GLint,		8);
+	LIMIT_CASE(max_combined_texture_image_units,	GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,	GLint,		8);
+	LIMIT_CASE(max_vertex_texture_image_units,		GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,		GLint,		0);
+	LIMIT_CASE(max_texture_image_units,				GL_MAX_TEXTURE_IMAGE_UNITS,				GLint,		8);
+	LIMIT_CASE(max_fragment_uniform_vectors,		GL_MAX_FRAGMENT_UNIFORM_VECTORS,		GLint,		16);
+	LIMIT_CASE(max_renderbuffer_size,				GL_MAX_RENDERBUFFER_SIZE,				GLint,		1);
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fImplementationLimitTests.hpp b/modules/gles2/functional/es2fImplementationLimitTests.hpp
new file mode 100644
index 0000000..331dc66
--- /dev/null
+++ b/modules/gles2/functional/es2fImplementationLimitTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FIMPLEMENTATIONLIMITTESTS_HPP
+#define _ES2FIMPLEMENTATIONLIMITTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Implementation-defined limit tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ImplementationLimitTests : public TestCaseGroup
+{
+public:
+									ImplementationLimitTests			(Context& context);
+									~ImplementationLimitTests			(void);
+
+	void							init								(void);
+
+private:
+									ImplementationLimitTests			(const ImplementationLimitTests& other);
+	ImplementationLimitTests&		operator=							(const ImplementationLimitTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FIMPLEMENTATIONLIMITTESTS_HPP
diff --git a/modules/gles2/functional/es2fIntegerStateQueryTests.cpp b/modules/gles2/functional/es2fIntegerStateQueryTests.cpp
new file mode 100644
index 0000000..e84de93
--- /dev/null
+++ b/modules/gles2/functional/es2fIntegerStateQueryTests.cpp
@@ -0,0 +1,2145 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fIntegerStateQueryTests.hpp"
+#include "es2fApiCase.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluContextInfo.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+#ifndef GL_SLUMINANCE_NV
+#define	GL_SLUMINANCE_NV 0x8C46
+#endif
+#ifndef GL_SLUMINANCE_ALPHA_NV
+#define	GL_SLUMINANCE_ALPHA_NV 0x8C44
+#endif
+#ifndef GL_BGR_NV
+#define GL_BGR_NV 0x80E0
+#endif
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace IntegerStateQueryVerifiers
+{
+
+// StateVerifier
+
+class StateVerifier : protected glu::CallLogWrapper
+{
+public:
+						StateVerifier						(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~StateVerifier						(); // make GCC happy
+
+	const char*			getTestNamePostfix					(void) const;
+
+	virtual void		verifyInteger						(tcu::TestContext& testCtx, GLenum name, GLint reference)																																= DE_NULL;
+	virtual void		verifyInteger4						(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3)																		= DE_NULL;
+	virtual void		verifyInteger4Mask					(tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3)	= DE_NULL;
+	virtual void		verifyIntegerGreaterOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference)																																= DE_NULL;
+	virtual void		verifyUnsignedIntegerGreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint reference)																																= DE_NULL;
+	virtual void		verifyIntegerGreaterOrEqual2		(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1)																											= DE_NULL;
+	virtual void		verifyIntegerAnyOf					(tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength)																								= DE_NULL;
+	virtual void		verifyStencilMaskInitial			(tcu::TestContext& testCtx, GLenum name, int stencilBits)																																= DE_NULL;
+
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+StateVerifier::StateVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+
+StateVerifier::~StateVerifier ()
+{
+}
+
+const char* StateVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+// GetBooleanVerifier
+
+class GetBooleanVerifier : public StateVerifier
+{
+public:
+			GetBooleanVerifier					(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyInteger						(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyInteger4						(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3);
+	void	verifyInteger4Mask					(tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3);
+	void	verifyIntegerGreaterOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyUnsignedIntegerGreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint reference);
+	void	verifyIntegerGreaterOrEqual2		(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1);
+	void	verifyIntegerAnyOf					(tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength);
+	void	verifyStencilMaskInitial			(tcu::TestContext& testCtx, GLenum name, int stencilBits);
+};
+
+GetBooleanVerifier::GetBooleanVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getboolean")
+{
+}
+
+void GetBooleanVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLboolean expectedGLState = reference ? GL_TRUE : GL_FALSE;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << (expectedGLState==GL_TRUE ? "GL_TRUE" : "GL_FALSE") << "; got " << (state == GL_TRUE ? "GL_TRUE" : (state == GL_FALSE ? "GL_FALSE" : "non-boolean")) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyInteger4 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3)
+{
+	verifyInteger4Mask(testCtx, name, reference0, true, reference1, true, reference2, true, reference3, true);
+}
+
+void GetBooleanVerifier::verifyInteger4Mask (tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean[4]> boolVector4;
+	glGetBooleanv(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	const GLboolean referenceAsGLBoolean[] =
+	{
+		reference0 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE,
+		reference1 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE,
+		reference2 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE,
+		reference3 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE,
+	};
+
+	if ((enableRef0 && (boolVector4[0] != referenceAsGLBoolean[0])) ||
+		(enableRef1 && (boolVector4[1] != referenceAsGLBoolean[1])) ||
+		(enableRef2 && (boolVector4[2] != referenceAsGLBoolean[2])) ||
+		(enableRef3 && (boolVector4[3] != referenceAsGLBoolean[3])))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (enableRef0 ? (referenceAsGLBoolean[0] ? "GL_TRUE" : "GL_FALSE") : " - ") << ", "
+			<< (enableRef1 ? (referenceAsGLBoolean[1] ? "GL_TRUE" : "GL_FALSE") : " - ") << ", "
+			<< (enableRef2 ? (referenceAsGLBoolean[2] ? "GL_TRUE" : "GL_FALSE") : " - ") << ", "
+			<< (enableRef3 ? (referenceAsGLBoolean[3] ? "GL_TRUE" : "GL_FALSE") : " - ") << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state == GL_TRUE) // state is non-zero, could be greater than reference (correct)
+		return;
+
+	if (state == GL_FALSE) // state is zero
+	{
+		if (reference > 0) // and reference is greater than zero?
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+		}
+	}
+	else
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE or GL_FALSE" << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyUnsignedIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state == GL_TRUE) // state is non-zero, could be greater than reference (correct)
+		return;
+
+	if (state == GL_FALSE) // state is zero
+	{
+		if (reference > 0) // and reference is greater than zero?
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+		}
+	}
+	else
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE or GL_FALSE" << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyIntegerGreaterOrEqual2 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean[2]> boolVector;
+	glGetBooleanv(name, boolVector);
+
+	if (!boolVector.verifyValidity(testCtx))
+		return;
+
+	const GLboolean referenceAsGLBoolean[2] =
+	{
+		reference0 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE,
+		reference1 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(referenceAsGLBoolean); ++ndx)
+	{
+		if (boolVector[ndx] == GL_TRUE) // state is non-zero, could be greater than any integer
+		{
+			continue;
+		}
+		else if (boolVector[ndx] == GL_FALSE) // state is zero
+		{
+			if (referenceAsGLBoolean[ndx] > 0) // and reference is greater than zero?
+			{
+				testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+				if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+			}
+		}
+		else
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE or GL_FALSE" << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+		}
+	}
+}
+
+void GetBooleanVerifier::verifyIntegerAnyOf (tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (size_t ndx = 0; ndx < referencesLength; ++ndx)
+	{
+		const GLboolean expectedGLState = references[ndx] ? GL_TRUE : GL_FALSE;
+
+		if (state == expectedGLState)
+			return;
+	}
+
+	testCtx.getLog() << TestLog::Message << "// ERROR: got " << (state==GL_TRUE ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+	if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+}
+
+void GetBooleanVerifier::verifyStencilMaskInitial (tcu::TestContext& testCtx, GLenum name, int stencilBits)
+{
+	// if stencilBits == 0, the mask is allowed to be either GL_TRUE or GL_FALSE
+	// otherwise it must be GL_TRUE
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (stencilBits > 0 && state != GL_TRUE)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+//GetIntegerVerifier
+
+class GetIntegerVerifier : public StateVerifier
+{
+public:
+			GetIntegerVerifier			(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyInteger						(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyInteger4						(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3);
+	void	verifyInteger4Mask					(tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3);
+	void	verifyIntegerGreaterOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyUnsignedIntegerGreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint reference);
+	void	verifyIntegerGreaterOrEqual2		(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1);
+	void	verifyIntegerAnyOf					(tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength);
+	void	verifyStencilMaskInitial			(tcu::TestContext& testCtx, GLenum name, int stencilBits);
+};
+
+GetIntegerVerifier::GetIntegerVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger")
+{
+}
+
+void GetIntegerVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyInteger4 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3)
+{
+	verifyInteger4Mask(testCtx, name, reference0, true, reference1, true, reference2, true, reference3, true);
+}
+
+void GetIntegerVerifier::verifyInteger4Mask (tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[4]> intVector4;
+	glGetIntegerv(name, intVector4);
+
+	if (!intVector4.verifyValidity(testCtx))
+		return;
+
+	if ((enableRef0 && (intVector4[0] != reference0)) ||
+		(enableRef1 && (intVector4[1] != reference1)) ||
+		(enableRef2 && (intVector4[2] != reference2)) ||
+		(enableRef3 && (intVector4[3] != reference3)))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (enableRef0?"":"(") << reference0 << (enableRef0?"":")") << ", "
+			<< (enableRef1?"":"(") << reference1 << (enableRef1?"":")") << ", "
+			<< (enableRef2?"":"(") << reference2 << (enableRef2?"":")") << ", "
+			<< (enableRef3?"":"(") << reference3 << (enableRef3?"":")")	<< TestLog::EndMessage;
+
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyUnsignedIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (GLuint(state) < reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << reference << "; got " << GLuint(state) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyIntegerGreaterOrEqual2 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[2]> intVector2;
+	glGetIntegerv(name, intVector2);
+
+	if (!intVector2.verifyValidity(testCtx))
+		return;
+
+	if (intVector2[0] < reference0 || intVector2[1] < reference1)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << reference0 << ", " << reference1 << "; got " << intVector2[0] << ", " << intVector2[0] << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyIntegerAnyOf (tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (size_t ndx = 0; ndx < referencesLength; ++ndx)
+	{
+		const GLint expectedGLState = references[ndx];
+
+		if (state == expectedGLState)
+			return;
+	}
+
+	testCtx.getLog() << TestLog::Message << "// ERROR: got " << state << TestLog::EndMessage;
+	if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+}
+
+void GetIntegerVerifier::verifyStencilMaskInitial (tcu::TestContext& testCtx, GLenum name, int stencilBits)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint reference = (1 << stencilBits) - 1;
+
+	if ((state & reference) != reference) // the least significant stencilBits bits should be on
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected minimum mask of " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid mask value");
+	}
+}
+
+//GetFloatVerifier
+
+class GetFloatVerifier : public StateVerifier
+{
+public:
+			GetFloatVerifier					(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyInteger						(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyInteger4						(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3);
+	void	verifyInteger4Mask					(tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3);
+	void	verifyIntegerGreaterOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyUnsignedIntegerGreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint reference);
+	void	verifyIntegerGreaterOrEqual2		(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1);
+	void	verifyIntegerAnyOf					(tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength);
+	void	verifyStencilMaskInitial			(tcu::TestContext& testCtx, GLenum name, int stencilBits);
+};
+
+GetFloatVerifier::GetFloatVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getfloat")
+{
+}
+
+void GetFloatVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	const GLfloat referenceAsFloat = GLfloat(reference);
+	DE_ASSERT(reference == GLint(referenceAsFloat)); // reference integer must have 1:1 mapping to float for this to work. Reference value is always such value in these tests
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != referenceAsFloat)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << referenceAsFloat << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyInteger4 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3)
+{
+	verifyInteger4Mask(testCtx, name, reference0, true, reference1, true, reference2, true, reference3, true);
+}
+
+void GetFloatVerifier::verifyInteger4Mask (tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[4]> floatVector4;
+	glGetFloatv(name, floatVector4);
+
+	if (!floatVector4.verifyValidity(testCtx))
+		return;
+
+	if ((enableRef0 && (floatVector4[0] != GLfloat(reference0))) ||
+		(enableRef1 && (floatVector4[1] != GLfloat(reference1))) ||
+		(enableRef2 && (floatVector4[2] != GLfloat(reference2))) ||
+		(enableRef3 && (floatVector4[3] != GLfloat(reference3))))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (enableRef0?"":"(") << GLfloat(reference0) << (enableRef0?"":")") << ", "
+			<< (enableRef1?"":"(") << GLfloat(reference1) << (enableRef1?"":")") << ", "
+			<< (enableRef2?"":"(") << GLfloat(reference2) << (enableRef2?"":")") << ", "
+			<< (enableRef3?"":"(") << GLfloat(reference3) << (enableRef3?"":")") << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < GLfloat(reference))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLfloat(reference) << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyUnsignedIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (GLuint(state) < GLfloat(reference))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLfloat(reference) << "; got " << GLuint(state) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyIntegerGreaterOrEqual2 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[2]> floatVector2;
+	glGetFloatv(name, floatVector2);
+
+	if (!floatVector2.verifyValidity(testCtx))
+		return;
+
+	if (floatVector2[0] < GLfloat(reference0) || floatVector2[1] < GLfloat(reference1))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLfloat(reference0) << ", " << GLfloat(reference1) << "; got " << floatVector2[0] << ", " << floatVector2[1] << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyIntegerAnyOf (tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (size_t ndx = 0; ndx < referencesLength; ++ndx)
+	{
+		const GLfloat expectedGLState = GLfloat(references[ndx]);
+		DE_ASSERT(references[ndx] == GLint(expectedGLState)); // reference integer must have 1:1 mapping to float for this to work. Reference value is always such value in these tests
+
+		if (state == expectedGLState)
+			return;
+	}
+
+	testCtx.getLog() << TestLog::Message << "// ERROR: got " << state << TestLog::EndMessage;
+	if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+}
+
+void GetFloatVerifier::verifyStencilMaskInitial (tcu::TestContext& testCtx, GLenum name, int stencilBits)
+{
+	// checking the mask bits with float doesn't make much sense because of conversion errors
+	// just verify that the value is greater or equal to the minimum value
+	const GLint reference = (1 << stencilBits) - 1;
+	verifyIntegerGreaterOrEqual(testCtx, name, reference);
+}
+
+} // IntegerStateQueryVerifiers
+
+namespace
+{
+
+using namespace IntegerStateQueryVerifiers;
+
+static const char* testVertSource					=	"void main (void)\n"
+														"{\n"
+														"	gl_Position = vec4(0.0);\n"
+														"}\n";
+static const char* testFragSource					=	"void main (void)\n"
+														"{\n"
+														"	gl_FragColor = vec4(0.0);\n"
+														"}\n";
+
+class ConstantMinimumValueTestCase : public ApiCase
+{
+public:
+	ConstantMinimumValueTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName, GLint minValue)
+		: ApiCase		(context, name, description)
+		, m_targetName	(targetName)
+		, m_minValue	(minValue)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyUnsignedIntegerGreaterOrEqual(m_testCtx, m_targetName, m_minValue);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	GLenum			m_targetName;
+	GLint			m_minValue;
+	StateVerifier*	m_verifier;
+};
+
+class SampleBuffersTestCase : public ApiCase
+{
+public:
+	SampleBuffersTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		const int expectedSampleBuffers = (m_context.getRenderTarget().getNumSamples() > 1) ? 1 : 0;
+
+		m_log << tcu::TestLog::Message << "Sample count is " << (m_context.getRenderTarget().getNumSamples()) << ", expecting GL_SAMPLE_BUFFERS to be " << expectedSampleBuffers << tcu::TestLog::EndMessage;
+
+		m_verifier->verifyInteger(m_testCtx, GL_SAMPLE_BUFFERS, expectedSampleBuffers);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class SamplesTestCase : public ApiCase
+{
+public:
+	SamplesTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		// MSAA?
+		if (m_context.getRenderTarget().getNumSamples() > 1)
+		{
+			m_log << tcu::TestLog::Message << "Sample count is " << (m_context.getRenderTarget().getNumSamples()) << tcu::TestLog::EndMessage;
+
+			m_verifier->verifyInteger(m_testCtx, GL_SAMPLES, m_context.getRenderTarget().getNumSamples());
+			expectError(GL_NO_ERROR);
+		}
+		else
+		{
+			const glw::GLint validSamples[] = {0, 1};
+
+			m_log << tcu::TestLog::Message << "Expecting GL_SAMPLES to be 0 or 1" << tcu::TestLog::EndMessage;
+
+			m_verifier->verifyIntegerAnyOf(m_testCtx, GL_SAMPLES, validSamples, DE_LENGTH_OF_ARRAY(validSamples));
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class HintTestCase : public ApiCase
+{
+public:
+	HintTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName)
+		: ApiCase		(context, name, description)
+		, m_targetName	(targetName)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_targetName, GL_DONT_CARE);
+		expectError(GL_NO_ERROR);
+
+		glHint(m_targetName, GL_NICEST);
+		m_verifier->verifyInteger(m_testCtx, m_targetName, GL_NICEST);
+		expectError(GL_NO_ERROR);
+
+		glHint(m_targetName, GL_FASTEST);
+		m_verifier->verifyInteger(m_testCtx, m_targetName, GL_FASTEST);
+		expectError(GL_NO_ERROR);
+
+		glHint(m_targetName, GL_DONT_CARE);
+		m_verifier->verifyInteger(m_testCtx, m_targetName, GL_DONT_CARE);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	GLenum		m_targetName;
+	StateVerifier*	m_verifier;
+};
+
+class DepthFuncTestCase : public ApiCase
+{
+public:
+	DepthFuncTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_DEPTH_FUNC, GL_LESS);
+		expectError(GL_NO_ERROR);
+
+		const GLenum depthFunctions[] = {GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GREATER, GL_GEQUAL, GL_NOTEQUAL};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthFunctions); ndx++)
+		{
+			glDepthFunc(depthFunctions[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_DEPTH_FUNC, depthFunctions[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class CullFaceTestCase : public ApiCase
+{
+public:
+	CullFaceTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_CULL_FACE_MODE, GL_BACK);
+		expectError(GL_NO_ERROR);
+
+		const GLenum cullFaces[] = {GL_FRONT, GL_BACK, GL_FRONT_AND_BACK};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cullFaces); ndx++)
+		{
+			glCullFace(cullFaces[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_CULL_FACE_MODE, cullFaces[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class FrontFaceTestCase : public ApiCase
+{
+public:
+	FrontFaceTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_FRONT_FACE, GL_CCW);
+		expectError(GL_NO_ERROR);
+
+		const GLenum frontFaces[] = {GL_CW, GL_CCW};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(frontFaces); ndx++)
+		{
+			glFrontFace(frontFaces[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_FRONT_FACE, frontFaces[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ViewPortTestCase : public ApiCase
+{
+public:
+	ViewPortTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		GLint maxViewportDimensions[2] = {0};
+		glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDimensions);
+
+		// verify initial value of first two values
+		m_verifier->verifyInteger4(m_testCtx, GL_VIEWPORT, 0, 0, m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			GLint x			= rnd.getInt(-64000, 64000);
+			GLint y			= rnd.getInt(-64000, 64000);
+			GLsizei width	= rnd.getInt(0, maxViewportDimensions[0]);
+			GLsizei height	= rnd.getInt(0, maxViewportDimensions[1]);
+
+			glViewport(x, y, width, height);
+			m_verifier->verifyInteger4(m_testCtx, GL_VIEWPORT, x, y, width, height);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ScissorBoxTestCase : public ApiCase
+{
+public:
+	ScissorBoxTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		// verify initial value
+		m_verifier->verifyInteger4(m_testCtx, GL_SCISSOR_BOX, 0, 0, m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			GLint left		= rnd.getInt(-64000, 64000);
+			GLint bottom	= rnd.getInt(-64000, 64000);
+			GLsizei width	= rnd.getInt(0, 64000);
+			GLsizei height	= rnd.getInt(0, 64000);
+
+			glScissor(left, bottom, width, height);
+			m_verifier->verifyInteger4(m_testCtx, GL_SCISSOR_BOX, left, bottom, width, height);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class MaxViewportDimsTestCase : public ApiCase
+{
+public:
+	MaxViewportDimsTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyIntegerGreaterOrEqual2(m_testCtx, GL_MAX_VIEWPORT_DIMS, m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class StencilRefTestCase : public ApiCase
+{
+public:
+	StencilRefTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, 0);
+		expectError(GL_NO_ERROR);
+
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int ref = 1 << stencilBit;
+
+			glStencilFunc(GL_ALWAYS, ref, 0); // mask should not affect the REF
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, ref);
+			expectError(GL_NO_ERROR);
+
+			glStencilFunc(GL_ALWAYS, ref, ref);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, ref);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+};
+
+class StencilRefSeparateTestCase : public ApiCase
+{
+public:
+	StencilRefSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, GLenum stencilFuncTargetFace)
+		: ApiCase					(context, name, description)
+		, m_verifier				(verifier)
+		, m_testTargetName			(testTargetName)
+		, m_stencilFuncTargetFace	(stencilFuncTargetFace)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, 0);
+		expectError(GL_NO_ERROR);
+
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int ref = 1 << stencilBit;
+
+			glStencilFuncSeparate(m_stencilFuncTargetFace, GL_ALWAYS, ref, 0);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, ref);
+			expectError(GL_NO_ERROR);
+
+			glStencilFuncSeparate(m_stencilFuncTargetFace, GL_ALWAYS, ref, ref);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, ref);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+	GLenum		m_stencilFuncTargetFace;
+};
+
+class StencilOpTestCase : public ApiCase
+{
+public:
+	StencilOpTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum stencilOpName)
+		: ApiCase					(context, name, description)
+		, m_verifier				(verifier)
+		, m_stencilOpName			(stencilOpName)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_stencilOpName, GL_KEEP);
+		expectError(GL_NO_ERROR);
+
+		const GLenum stencilOpValues[] = {GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(stencilOpValues); ++ndx)
+		{
+			SetStencilOp(stencilOpValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_stencilOpName, stencilOpValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+protected:
+	virtual void SetStencilOp (GLenum stencilOpValue)
+	{
+		switch (m_stencilOpName)
+		{
+		case GL_STENCIL_FAIL:
+		case GL_STENCIL_BACK_FAIL:
+			glStencilOp(stencilOpValue, GL_KEEP, GL_KEEP);
+			break;
+
+		case GL_STENCIL_PASS_DEPTH_FAIL:
+		case GL_STENCIL_BACK_PASS_DEPTH_FAIL:
+			glStencilOp(GL_KEEP, stencilOpValue, GL_KEEP);
+			break;
+
+		case GL_STENCIL_PASS_DEPTH_PASS:
+		case GL_STENCIL_BACK_PASS_DEPTH_PASS:
+			glStencilOp(GL_KEEP, GL_KEEP, stencilOpValue);
+			break;
+
+		default:
+			DE_ASSERT(false && "should not happen");
+			break;
+		}
+	}
+
+	StateVerifier*				m_verifier;
+	GLenum					m_stencilOpName;
+};
+
+class StencilOpSeparateTestCase : public StencilOpTestCase
+{
+public:
+	StencilOpSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum stencilOpName, GLenum stencilOpFace)
+		: StencilOpTestCase		(context, verifier, name, description, stencilOpName)
+		, m_stencilOpFace		(stencilOpFace)
+	{
+	}
+
+private:
+	void SetStencilOp (GLenum stencilOpValue)
+	{
+		switch (m_stencilOpName)
+		{
+		case GL_STENCIL_FAIL:
+		case GL_STENCIL_BACK_FAIL:
+			glStencilOpSeparate(m_stencilOpFace, stencilOpValue, GL_KEEP, GL_KEEP);
+			break;
+
+		case GL_STENCIL_PASS_DEPTH_FAIL:
+		case GL_STENCIL_BACK_PASS_DEPTH_FAIL:
+			glStencilOpSeparate(m_stencilOpFace, GL_KEEP, stencilOpValue, GL_KEEP);
+			break;
+
+		case GL_STENCIL_PASS_DEPTH_PASS:
+		case GL_STENCIL_BACK_PASS_DEPTH_PASS:
+			glStencilOpSeparate(m_stencilOpFace, GL_KEEP, GL_KEEP, stencilOpValue);
+			break;
+
+		default:
+			DE_ASSERT(false && "should not happen");
+			break;
+		}
+	}
+
+	GLenum		m_stencilOpFace;
+};
+
+class StencilFuncTestCase : public ApiCase
+{
+public:
+	StencilFuncTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_STENCIL_FUNC, GL_ALWAYS);
+		expectError(GL_NO_ERROR);
+
+		const GLenum stencilfuncValues[] = {GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(stencilfuncValues); ++ndx)
+		{
+			glStencilFunc(stencilfuncValues[ndx], 0, 0);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_STENCIL_FUNC, stencilfuncValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_STENCIL_BACK_FUNC, stencilfuncValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class StencilFuncSeparateTestCase : public ApiCase
+{
+public:
+	StencilFuncSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum stencilFuncName, GLenum stencilFuncFace)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_stencilFuncName	(stencilFuncName)
+		, m_stencilFuncFace	(stencilFuncFace)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_stencilFuncName, GL_ALWAYS);
+		expectError(GL_NO_ERROR);
+
+		const GLenum stencilfuncValues[] = {GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(stencilfuncValues); ++ndx)
+		{
+			glStencilFuncSeparate(m_stencilFuncFace, stencilfuncValues[ndx], 0, 0);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_stencilFuncName, stencilfuncValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_stencilFuncName;
+	GLenum		m_stencilFuncFace;
+};
+
+class StencilMaskTestCase : public ApiCase
+{
+public:
+	StencilMaskTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+	{
+	}
+
+	void test (void)
+	{
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		m_verifier->verifyStencilMaskInitial(m_testCtx, m_testTargetName, stencilBits);
+		expectError(GL_NO_ERROR);
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int mask = 1 << stencilBit;
+
+			glStencilFunc(GL_ALWAYS, 0, mask);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, mask);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+};
+
+class StencilMaskSeparateTestCase : public ApiCase
+{
+public:
+	StencilMaskSeparateTestCase	(Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, GLenum stencilFuncTargetFace)
+		: ApiCase					(context, name, description)
+		, m_verifier				(verifier)
+		, m_testTargetName			(testTargetName)
+		, m_stencilFuncTargetFace	(stencilFuncTargetFace)
+	{
+	}
+
+	void test (void)
+	{
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		m_verifier->verifyStencilMaskInitial(m_testCtx, m_testTargetName, stencilBits);
+		expectError(GL_NO_ERROR);
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int mask = 1 << stencilBit;
+
+			glStencilFuncSeparate(m_stencilFuncTargetFace, GL_ALWAYS, 0, mask);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, mask);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+	GLenum		m_stencilFuncTargetFace;
+};
+
+class StencilWriteMaskTestCase : public ApiCase
+{
+public:
+	StencilWriteMaskTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+	{
+	}
+
+	void test (void)
+	{
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int mask = 1 << stencilBit;
+
+			glStencilMask(mask);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, mask);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+};
+
+class StencilWriteMaskSeparateTestCase : public ApiCase
+{
+public:
+	StencilWriteMaskSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, GLenum stencilTargetFace)
+		: ApiCase				(context, name, description)
+		, m_verifier			(verifier)
+		, m_testTargetName		(testTargetName)
+		, m_stencilTargetFace	(stencilTargetFace)
+	{
+	}
+
+	void test (void)
+	{
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int mask = 1 << stencilBit;
+
+			glStencilMaskSeparate(m_stencilTargetFace, mask);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, mask);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+	GLenum		m_stencilTargetFace;
+};
+
+class PixelStoreAlignTestCase : public ApiCase
+{
+public:
+	PixelStoreAlignTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, 4);
+		expectError(GL_NO_ERROR);
+
+		const int alignments[] = {1, 2, 4, 8};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(alignments); ++ndx)
+		{
+			const int referenceValue = alignments[ndx];
+
+			glPixelStorei(m_testTargetName, referenceValue);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, referenceValue);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+};
+
+class BlendFuncTestCase : public ApiCase
+{
+public:
+	BlendFuncTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, int initialValue)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+		, m_initialValue	(initialValue)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, m_initialValue);
+		expectError(GL_NO_ERROR);
+
+		const GLenum blendFuncValues[] =
+		{
+			GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA,
+			GL_SRC_ALPHA_SATURATE
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(blendFuncValues); ++ndx)
+		{
+			const GLenum referenceValue = blendFuncValues[ndx];
+
+			//GL_SRC_ALPHA_SATURATE is ony allowed for srcRGB or srcA
+			if (referenceValue == GL_SRC_ALPHA_SATURATE &&
+				!(m_testTargetName == GL_BLEND_SRC_RGB || m_testTargetName == GL_BLEND_SRC_ALPHA))
+				continue;
+
+			SetBlendFunc(referenceValue);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, referenceValue);
+			expectError(GL_NO_ERROR);
+		}
+	}
+protected:
+	virtual void SetBlendFunc (GLenum func)
+	{
+		switch (m_testTargetName)
+		{
+		case GL_BLEND_SRC_RGB:
+		case GL_BLEND_SRC_ALPHA:
+			glBlendFunc(func, GL_ZERO);
+			break;
+
+		case GL_BLEND_DST_RGB:
+		case GL_BLEND_DST_ALPHA:
+			glBlendFunc(GL_ZERO, func);
+			break;
+
+		default:
+			DE_ASSERT(false && "should not happen");
+			break;
+		}
+	}
+
+	StateVerifier*		m_verifier;
+	GLenum			m_testTargetName;
+	int				m_initialValue;
+};
+
+class BlendFuncSeparateTestCase : public BlendFuncTestCase
+{
+public:
+	BlendFuncSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, int initialValue)
+		: BlendFuncTestCase	(context, verifier, name, description, testTargetName, initialValue)
+	{
+	}
+
+	void SetBlendFunc (GLenum func)
+	{
+		switch (m_testTargetName)
+		{
+		case GL_BLEND_SRC_RGB:
+			glBlendFuncSeparate(func, GL_ZERO, GL_ZERO, GL_ZERO);
+			break;
+
+		case GL_BLEND_DST_RGB:
+			glBlendFuncSeparate(GL_ZERO, func, GL_ZERO, GL_ZERO);
+			break;
+
+		case GL_BLEND_SRC_ALPHA:
+			glBlendFuncSeparate(GL_ZERO, GL_ZERO, func, GL_ZERO);
+			break;
+
+		case GL_BLEND_DST_ALPHA:
+			glBlendFuncSeparate(GL_ZERO, GL_ZERO, GL_ZERO, func);
+			break;
+
+		default:
+			DE_ASSERT(false && "should not happen");
+			break;
+		}
+	}
+};
+
+class BlendEquationTestCase : public ApiCase
+{
+public:
+	BlendEquationTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, int initialValue)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+		, m_initialValue	(initialValue)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, m_initialValue);
+		expectError(GL_NO_ERROR);
+
+		const GLenum blendFuncValues[] =
+		{
+			GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(blendFuncValues); ++ndx)
+		{
+			const GLenum referenceValue = blendFuncValues[ndx];
+
+			SetBlendEquation(referenceValue);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, referenceValue);
+			expectError(GL_NO_ERROR);
+		}
+	}
+protected:
+	virtual void SetBlendEquation (GLenum equation)
+	{
+		glBlendEquation(equation);
+	}
+
+	StateVerifier*		m_verifier;
+	GLenum			m_testTargetName;
+	int				m_initialValue;
+};
+
+class BlendEquationSeparateTestCase : public BlendEquationTestCase
+{
+public:
+	BlendEquationSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, int initialValue)
+		: BlendEquationTestCase	(context, verifier, name, description, testTargetName, initialValue)
+	{
+	}
+
+protected:
+	void SetBlendEquation (GLenum equation)
+	{
+		switch (m_testTargetName)
+		{
+		case GL_BLEND_EQUATION_RGB:
+			glBlendEquationSeparate(equation, GL_FUNC_ADD);
+			break;
+
+		case GL_BLEND_EQUATION_ALPHA:
+			glBlendEquationSeparate(GL_FUNC_ADD, equation);
+			break;
+
+		default:
+			DE_ASSERT(false && "should not happen");
+			break;
+		}
+	}
+};
+
+class ImplementationArrayTestCase : public ApiCase
+{
+public:
+	ImplementationArrayTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, GLenum testTargetLengthTargetName, int minValue)
+		: ApiCase						(context, name, description)
+		, m_verifier					(verifier)
+		, m_testTargetName				(testTargetName)
+		, m_testTargetLengthTargetName	(testTargetLengthTargetName)
+		, m_minValue					(minValue)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyIntegerGreaterOrEqual(m_testCtx, m_testTargetLengthTargetName, m_minValue);
+		expectError(GL_NO_ERROR);
+
+		GLint targetArrayLength = 0;
+		glGetIntegerv(m_testTargetLengthTargetName, &targetArrayLength);
+		expectError(GL_NO_ERROR);
+
+		if (targetArrayLength)
+		{
+			std::vector<GLint> queryResult;
+			queryResult.resize(targetArrayLength, 0);
+
+			glGetIntegerv(m_testTargetName, &queryResult[0]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*		m_verifier;
+	GLenum			m_testTargetName;
+	GLenum			m_testTargetLengthTargetName;
+	int				m_minValue;
+};
+
+class CurrentProgramBindingTestCase : public ApiCase
+{
+public:
+	CurrentProgramBindingTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_CURRENT_PROGRAM, 0);
+		expectError(GL_NO_ERROR);
+
+		{
+			GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+			glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+			glCompileShader(shaderVert);
+			expectError(GL_NO_ERROR);
+			GLint compileStatus;
+			glGetShaderiv(shaderVert, GL_COMPILE_STATUS, &compileStatus);
+			checkBooleans(compileStatus, GL_TRUE);
+
+			GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+			glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+			glCompileShader(shaderFrag);
+			expectError(GL_NO_ERROR);
+			glGetShaderiv(shaderFrag, GL_COMPILE_STATUS, &compileStatus);
+			checkBooleans(compileStatus, GL_TRUE);
+
+			GLuint shaderProg = glCreateProgram();
+			glAttachShader(shaderProg, shaderVert);
+			glAttachShader(shaderProg, shaderFrag);
+			glLinkProgram(shaderProg);
+			expectError(GL_NO_ERROR);
+			GLint linkStatus;
+			glGetProgramiv(shaderProg, GL_LINK_STATUS, &linkStatus);
+			checkBooleans(linkStatus, GL_TRUE);
+
+			glUseProgram(shaderProg);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_CURRENT_PROGRAM, shaderProg);
+			expectError(GL_NO_ERROR);
+
+			glDeleteShader(shaderVert);
+			glDeleteShader(shaderFrag);
+			glDeleteProgram(shaderProg);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_CURRENT_PROGRAM, shaderProg);
+			expectError(GL_NO_ERROR);
+		}
+
+		glUseProgram(0);
+		expectError(GL_NO_ERROR);
+		m_verifier->verifyInteger(m_testCtx, GL_CURRENT_PROGRAM, 0);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*		m_verifier;
+};
+
+class BufferBindingTestCase : public ApiCase
+{
+public:
+	BufferBindingTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum bufferBindingName, GLenum bufferType)
+		: ApiCase				(context, name, description)
+		, m_verifier			(verifier)
+		, m_bufferBindingName	(bufferBindingName)
+		, m_bufferType			(bufferType)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_bufferBindingName, 0);
+		expectError(GL_NO_ERROR);
+
+		GLuint bufferObject = 0;
+		glGenBuffers(1, &bufferObject);
+		expectError(GL_NO_ERROR);
+
+		glBindBuffer(m_bufferType, bufferObject);
+		m_verifier->verifyInteger(m_testCtx, m_bufferBindingName, bufferObject);
+		expectError(GL_NO_ERROR);
+
+		glDeleteBuffers(1, &bufferObject);
+		m_verifier->verifyInteger(m_testCtx, m_bufferBindingName, 0);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*		m_verifier;
+	GLenum			m_bufferBindingName;
+	GLenum			m_bufferType;
+};
+
+class StencilClearValueTestCase : public ApiCase
+{
+public:
+	StencilClearValueTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_STENCIL_CLEAR_VALUE, 0);
+		expectError(GL_NO_ERROR);
+
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int ref = 1 << stencilBit;
+
+			glClearStencil(ref);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_STENCIL_CLEAR_VALUE, ref);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ActiveTextureTestCase : public ApiCase
+{
+public:
+	ActiveTextureTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_ACTIVE_TEXTURE, GL_TEXTURE0);
+		expectError(GL_NO_ERROR);
+
+		GLint textureUnits = 0;
+		glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &textureUnits);
+		expectError(GL_NO_ERROR);
+
+		for (int ndx = 0; ndx < textureUnits; ++ndx)
+		{
+			glActiveTexture(GL_TEXTURE0 + ndx);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_ACTIVE_TEXTURE, GL_TEXTURE0 + ndx);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*		m_verifier;
+};
+
+class RenderbufferBindingTestCase : public ApiCase
+{
+public:
+	RenderbufferBindingTestCase	(Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_RENDERBUFFER_BINDING, 0);
+		expectError(GL_NO_ERROR);
+
+		GLuint renderBuffer = 0;
+		glGenRenderbuffers(1, &renderBuffer);
+		expectError(GL_NO_ERROR);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_RENDERBUFFER_BINDING, renderBuffer);
+		expectError(GL_NO_ERROR);
+
+		glDeleteRenderbuffers(1, &renderBuffer);
+		m_verifier->verifyInteger(m_testCtx, GL_RENDERBUFFER_BINDING, 0);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*		m_verifier;
+};
+
+class TextureBindingTestCase : public ApiCase
+{
+public:
+	TextureBindingTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testBindingName, GLenum textureType)
+		: ApiCase				(context, name, description)
+		, m_verifier			(verifier)
+		, m_testBindingName		(testBindingName)
+		, m_textureType			(textureType)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testBindingName, 0);
+		expectError(GL_NO_ERROR);
+
+		GLuint texture = 0;
+		glGenTextures(1, &texture);
+		expectError(GL_NO_ERROR);
+
+		glBindTexture(m_textureType, texture);
+		m_verifier->verifyInteger(m_testCtx, m_testBindingName, texture);
+
+		glDeleteTextures(1, &texture);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, m_testBindingName, 0);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testBindingName;
+	GLenum		m_textureType;
+};
+
+class FrameBufferBindingTestCase : public ApiCase
+{
+public:
+	FrameBufferBindingTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_FRAMEBUFFER_BINDING, 0);
+		expectError(GL_NO_ERROR);
+
+		GLuint framebufferId = 0;
+		glGenFramebuffers(1, &framebufferId);
+		expectError(GL_NO_ERROR);
+
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferId);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_FRAMEBUFFER_BINDING, framebufferId);
+		expectError(GL_NO_ERROR);
+
+		glBindFramebuffer(GL_FRAMEBUFFER, 0);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_FRAMEBUFFER_BINDING, 0);
+		expectError(GL_NO_ERROR);
+
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferId);
+		glDeleteFramebuffers(1, &framebufferId);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_FRAMEBUFFER_BINDING, 0);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ImplementationColorReadTestCase : public ApiCase
+{
+public:
+	ImplementationColorReadTestCase	(Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		const GLint defaultColorTypes[] =
+		{
+			GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1, GL_UNSIGNED_SHORT_5_6_5
+		};
+		const GLint defaultColorFormats[] =
+		{
+			GL_RGBA, GL_RGB, GL_ALPHA
+		};
+
+		std::vector<GLint> validColorTypes;
+		std::vector<GLint> validColorFormats;
+
+		// Defined by the spec
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(defaultColorTypes); ++ndx)
+			validColorTypes.push_back(defaultColorTypes[ndx]);
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(defaultColorFormats); ++ndx)
+			validColorFormats.push_back(defaultColorFormats[ndx]);
+
+		// Extensions
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_format_BGRA8888") ||
+			m_context.getContextInfo().isExtensionSupported("GL_APPLE_texture_format_BGRA8888"))
+			validColorFormats.push_back(GL_BGRA);
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_EXT_read_format_bgra"))
+		{
+			validColorFormats.push_back(GL_BGRA);
+			validColorTypes.push_back(GL_UNSIGNED_SHORT_4_4_4_4_REV);
+			validColorTypes.push_back(GL_UNSIGNED_SHORT_1_5_5_5_REV);
+		}
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_IMG_read_format"))
+		{
+			validColorFormats.push_back(GL_BGRA);
+			validColorTypes.push_back(GL_UNSIGNED_SHORT_4_4_4_4_REV);
+		}
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_NV_sRGB_formats"))
+		{
+			validColorFormats.push_back(GL_SLUMINANCE_NV);
+			validColorFormats.push_back(GL_SLUMINANCE_ALPHA_NV);
+		}
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_NV_bgr"))
+		{
+			validColorFormats.push_back(GL_BGR_NV);
+		}
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_rg"))
+		{
+			validColorFormats.push_back(GL_RED);
+			validColorFormats.push_back(GL_RG);
+		}
+
+		m_verifier->verifyIntegerAnyOf(m_testCtx, GL_IMPLEMENTATION_COLOR_READ_TYPE,	&validColorTypes[0],	validColorTypes.size());
+		m_verifier->verifyIntegerAnyOf(m_testCtx, GL_IMPLEMENTATION_COLOR_READ_FORMAT,	&validColorFormats[0],	validColorFormats.size());
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class BufferComponentSizeCase : public ApiCase
+{
+public:
+	BufferComponentSizeCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyIntegerGreaterOrEqual(m_testCtx, GL_RED_BITS,		m_context.getRenderTarget().getPixelFormat().redBits);
+		m_verifier->verifyIntegerGreaterOrEqual(m_testCtx, GL_BLUE_BITS,	m_context.getRenderTarget().getPixelFormat().blueBits);
+		m_verifier->verifyIntegerGreaterOrEqual(m_testCtx, GL_GREEN_BITS,	m_context.getRenderTarget().getPixelFormat().greenBits);
+		m_verifier->verifyIntegerGreaterOrEqual(m_testCtx, GL_ALPHA_BITS,	m_context.getRenderTarget().getPixelFormat().alphaBits);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyIntegerGreaterOrEqual(m_testCtx, GL_DEPTH_BITS,	m_context.getRenderTarget().getDepthBits());
+		m_verifier->verifyIntegerGreaterOrEqual(m_testCtx, GL_STENCIL_BITS,	m_context.getRenderTarget().getStencilBits());
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		StateVerifier* verifier = VERIFIERS[_verifierNdx];										\
+		CODE_BLOCK;																				\
+	}
+
+} // anonymous
+
+IntegerStateQueryTests::IntegerStateQueryTests (Context& context)
+	: TestCaseGroup			(context, "integers", "Integer Values")
+	, m_verifierBoolean		(DE_NULL)
+	, m_verifierInteger		(DE_NULL)
+	, m_verifierFloat		(DE_NULL)
+{
+}
+
+IntegerStateQueryTests::~IntegerStateQueryTests (void)
+{
+	deinit();
+}
+
+void IntegerStateQueryTests::init (void)
+{
+	DE_ASSERT(m_verifierBoolean == DE_NULL);
+	DE_ASSERT(m_verifierInteger == DE_NULL);
+	DE_ASSERT(m_verifierFloat == DE_NULL);
+
+	m_verifierBoolean		= new GetBooleanVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInteger		= new GetIntegerVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierFloat			= new GetFloatVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+
+	const struct LimitedStateInteger
+	{
+		const char*		name;
+		const char*		description;
+		GLenum			targetName;
+		GLint			value;
+	} implementationMinLimits[] =
+	{
+		{ "subpixel_bits",						"SUBPIXEL_BITS has  a minimum value of 4",						GL_SUBPIXEL_BITS,						4	},
+		{ "max_texture_size",					"MAX_TEXTURE_SIZE has  a minimum value of 64",					GL_MAX_TEXTURE_SIZE,					64	},
+		{ "max_cube_map_texture_size",			"MAX_CUBE_MAP_TEXTURE_SIZE has  a minimum value of 16",			GL_MAX_CUBE_MAP_TEXTURE_SIZE,			16	},
+		{ "max_vertex_attribs",					"MAX_VERTEX_ATTRIBS has  a minimum value of 8",					GL_MAX_VERTEX_ATTRIBS,					8	},
+		{ "max_vertex_uniform_vectors",			"MAX_VERTEX_UNIFORM_VECTORS has  a minimum value of 128",		GL_MAX_VERTEX_UNIFORM_VECTORS,			128	},
+		{ "max_varying_vectors",				"MAX_VARYING_VECTORS has  a minimum value of 8",				GL_MAX_VARYING_VECTORS,					8	},
+		{ "max_combined_texture_image_units",	"MAX_COMBINED_TEXTURE_IMAGE_UNITS has  a minimum value of 8",	GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,	8	},
+		{ "max_vertex_texture_image_units",		"MAX_VERTEX_TEXTURE_IMAGE_UNITS has  a minimum value of 0",		GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,		0	},
+		{ "max_texture_image_units",			"MAX_TEXTURE_IMAGE_UNITS has  a minimum value of 8",			GL_MAX_TEXTURE_IMAGE_UNITS,				8	},
+		{ "max_fragment_uniform_vectors",		"MAX_FRAGMENT_UNIFORM_VECTORS has  a minimum value of 16",		GL_MAX_FRAGMENT_UNIFORM_VECTORS,		16	},
+		{ "max_renderbuffer_size",				"MAX_RENDERBUFFER_SIZE has  a minimum value of 1",				GL_MAX_RENDERBUFFER_SIZE,				1	},
+	};
+
+	// \note implementation defined limits have their own tests so just check the conversions to boolean and float
+	StateVerifier* implementationLimitVerifiers[]	= {m_verifierBoolean,						m_verifierFloat};
+	StateVerifier* normalVerifiers[]				= {m_verifierBoolean,	m_verifierInteger,	m_verifierFloat};
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(implementationMinLimits); testNdx++)
+		FOR_EACH_VERIFIER(implementationLimitVerifiers, addChild(new ConstantMinimumValueTestCase(m_context, verifier, (std::string(implementationMinLimits[testNdx].name) + verifier->getTestNamePostfix()).c_str(), implementationMinLimits[testNdx].description, implementationMinLimits[testNdx].targetName, implementationMinLimits[testNdx].value)));
+
+	FOR_EACH_VERIFIER(implementationLimitVerifiers, addChild(new SampleBuffersTestCase		(m_context,	 verifier, (std::string("sample_buffers")						+ verifier->getTestNamePostfix()).c_str(),		"SAMPLE_BUFFERS")));
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new SamplesTestCase				(m_context,	 verifier, (std::string("samples")								+ verifier->getTestNamePostfix()).c_str(),		"SAMPLES")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new HintTestCase				(m_context,	 verifier, (std::string("generate_mipmap_hint")					+ verifier->getTestNamePostfix()).c_str(),		"GENERATE_MIPMAP_HINT",				GL_GENERATE_MIPMAP_HINT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new DepthFuncTestCase			(m_context,	 verifier, (std::string("depth_func")							+ verifier->getTestNamePostfix()).c_str(),		"DEPTH_FUNC")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new CullFaceTestCase			(m_context,	 verifier, (std::string("cull_face_mode")						+ verifier->getTestNamePostfix()).c_str(),		"CULL_FACE_MODE")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new FrontFaceTestCase			(m_context,	 verifier, (std::string("front_face_mode")						+ verifier->getTestNamePostfix()).c_str(),		"FRONT_FACE")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ViewPortTestCase			(m_context,	 verifier, (std::string("viewport")								+ verifier->getTestNamePostfix()).c_str(),		"VIEWPORT")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ScissorBoxTestCase			(m_context,	 verifier, (std::string("scissor_box")							+ verifier->getTestNamePostfix()).c_str(),		"SCISSOR_BOX")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new MaxViewportDimsTestCase		(m_context,	 verifier, (std::string("max_viewport_dims")					+ verifier->getTestNamePostfix()).c_str(),		"MAX_VIEWPORT_DIMS")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new BufferComponentSizeCase		(m_context,	 verifier, (std::string("buffer_component_size")				+ verifier->getTestNamePostfix()).c_str(),		"x BITS")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefTestCase			(m_context,	 verifier, (std::string("stencil_ref")							+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_REF",						GL_STENCIL_REF)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefTestCase			(m_context,	 verifier, (std::string("stencil_back_ref")						+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_BACK_REF",					GL_STENCIL_BACK_REF)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefSeparateTestCase	(m_context,	 verifier, (std::string("stencil_ref_separate")					+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_REF (separate)",			GL_STENCIL_REF,			GL_FRONT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefSeparateTestCase	(m_context,	 verifier, (std::string("stencil_ref_separate_both")			+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_REF (separate)",			GL_STENCIL_REF,			GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefSeparateTestCase	(m_context,	 verifier, (std::string("stencil_back_ref_separate")			+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_BACK_REF (separate)",		GL_STENCIL_BACK_REF,	GL_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefSeparateTestCase	(m_context,	 verifier, (std::string("stencil_back_ref_separate_both")		+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_BACK_REF (separate)",		GL_STENCIL_BACK_REF,	GL_FRONT_AND_BACK)));
+
+	const struct NamedStencilOp
+	{
+		const char*		name;
+
+		const char*		frontDescription;
+		GLenum			frontTarget;
+		const char*		backDescription;
+		GLenum			backTarget;
+	} stencilOps[] =
+	{
+		{ "fail",		"STENCIL_FAIL",				GL_STENCIL_FAIL,			"STENCIL_BACK_FAIL",			GL_STENCIL_BACK_FAIL			},
+		{ "depth_fail",	"STENCIL_PASS_DEPTH_FAIL",	GL_STENCIL_PASS_DEPTH_FAIL,	"STENCIL_BACK_PASS_DEPTH_FAIL",	GL_STENCIL_BACK_PASS_DEPTH_FAIL	},
+		{ "depth_pass",	"STENCIL_PASS_DEPTH_PASS",	GL_STENCIL_PASS_DEPTH_PASS,	"STENCIL_BACK_PASS_DEPTH_PASS",	GL_STENCIL_BACK_PASS_DEPTH_PASS	}
+	};
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(stencilOps); testNdx++)
+	{
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpTestCase			(m_context, verifier, (std::string("stencil_")		+ stencilOps[testNdx].name + verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].frontDescription,	stencilOps[testNdx].frontTarget)));
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpTestCase			(m_context, verifier, (std::string("stencil_back_")	+ stencilOps[testNdx].name + verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].backDescription,	stencilOps[testNdx].backTarget)));
+
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpSeparateTestCase	(m_context, verifier, (std::string("stencil_")		+ stencilOps[testNdx].name + "_separate_both"	+ verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].frontDescription,	stencilOps[testNdx].frontTarget,	GL_FRONT_AND_BACK)));
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpSeparateTestCase	(m_context, verifier, (std::string("stencil_back_")	+ stencilOps[testNdx].name + "_separate_both"	+ verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].backDescription,		stencilOps[testNdx].backTarget,		GL_FRONT_AND_BACK)));
+
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpSeparateTestCase	(m_context, verifier, (std::string("stencil_")		+ stencilOps[testNdx].name + "_separate"		+ verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].frontDescription,	stencilOps[testNdx].frontTarget,	GL_FRONT)));
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpSeparateTestCase	(m_context, verifier, (std::string("stencil_back_")	+ stencilOps[testNdx].name + "_separate"		+ verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].backDescription,		stencilOps[testNdx].backTarget,		GL_BACK)));
+	}
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilFuncTestCase					(m_context, verifier,	(std::string("stencil_func")								+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_FUNC")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilFuncSeparateTestCase			(m_context, verifier,	(std::string("stencil_func_separate")						+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_FUNC (separate)",				GL_STENCIL_FUNC,				GL_FRONT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilFuncSeparateTestCase			(m_context, verifier,	(std::string("stencil_func_separate_both")					+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_FUNC (separate)",				GL_STENCIL_FUNC,				GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilFuncSeparateTestCase			(m_context, verifier,	(std::string("stencil_back_func_separate")					+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_FUNC (separate)",				GL_STENCIL_BACK_FUNC,			GL_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilFuncSeparateTestCase			(m_context, verifier,	(std::string("stencil_back_func_separate_both")				+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_FUNC (separate)",				GL_STENCIL_BACK_FUNC,			GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskTestCase					(m_context, verifier,	(std::string("stencil_value_mask")							+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_VALUE_MASK",					GL_STENCIL_VALUE_MASK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskTestCase					(m_context, verifier,	(std::string("stencil_back_value_mask")						+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_VALUE_MASK",				GL_STENCIL_BACK_VALUE_MASK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskSeparateTestCase			(m_context, verifier,	(std::string("stencil_value_mask_separate")					+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_VALUE_MASK (separate)",		GL_STENCIL_VALUE_MASK,			GL_FRONT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskSeparateTestCase			(m_context, verifier,	(std::string("stencil_value_mask_separate_both")			+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_VALUE_MASK (separate)",		GL_STENCIL_VALUE_MASK,			GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskSeparateTestCase			(m_context, verifier,	(std::string("stencil_back_value_mask_separate")			+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_VALUE_MASK (separate)",	GL_STENCIL_BACK_VALUE_MASK,		GL_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskSeparateTestCase			(m_context, verifier,	(std::string("stencil_back_value_mask_separate_both")		+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_VALUE_MASK (separate)",	GL_STENCIL_BACK_VALUE_MASK,		GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskTestCase			(m_context, verifier,	(std::string("stencil_writemask")							+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_WRITEMASK",					GL_STENCIL_WRITEMASK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskTestCase			(m_context, verifier,	(std::string("stencil_back_writemask")						+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_WRITEMASK",				GL_STENCIL_BACK_WRITEMASK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskSeparateTestCase	(m_context, verifier,	(std::string("stencil_writemask_separate")					+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_WRITEMASK (separate)",			GL_STENCIL_WRITEMASK,			GL_FRONT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskSeparateTestCase	(m_context, verifier,	(std::string("stencil_writemask_separate_both")				+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_WRITEMASK (separate)",			GL_STENCIL_WRITEMASK,			GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskSeparateTestCase	(m_context, verifier,	(std::string("stencil_back_writemask_separate")				+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_WRITEMASK (separate)",	GL_STENCIL_BACK_WRITEMASK,		GL_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskSeparateTestCase	(m_context, verifier,	(std::string("stencil_back_writemask_separate_both")		+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_WRITEMASK (separate)",	GL_STENCIL_BACK_WRITEMASK,		GL_FRONT_AND_BACK)));
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new PixelStoreAlignTestCase(m_context, verifier, (std::string("unpack_alignment")	+ verifier->getTestNamePostfix()).c_str(),	"UNPACK_ALIGNMENT",	GL_UNPACK_ALIGNMENT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new PixelStoreAlignTestCase(m_context, verifier, (std::string("pack_alignment")		+ verifier->getTestNamePostfix()).c_str(),	"PACK_ALIGNMENT",	GL_PACK_ALIGNMENT)));
+
+	{
+		const struct BlendColorState
+		{
+			const char*	name;
+			const char*	description;
+			GLenum		target;
+			int			initialValue;
+		} blendColorStates[] =
+		{
+			{ "blend_src_rgb",		"BLEND_SRC_RGB",	GL_BLEND_SRC_RGB,		GL_ONE	},
+			{ "blend_src_alpha",	"BLEND_SRC_ALPHA",	GL_BLEND_SRC_ALPHA,		GL_ONE	},
+			{ "blend_dst_rgb",		"BLEND_DST_RGB",	GL_BLEND_DST_RGB,		GL_ZERO	},
+			{ "blend_dst_alpha",	"BLEND_DST_ALPHA",	GL_BLEND_DST_ALPHA,		GL_ZERO	}
+		};
+		for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(blendColorStates); testNdx++)
+		{
+			FOR_EACH_VERIFIER(normalVerifiers, addChild(new BlendFuncTestCase			(m_context, verifier, (std::string(blendColorStates[testNdx].name)					+ verifier->getTestNamePostfix()).c_str(),	blendColorStates[testNdx].description,	blendColorStates[testNdx].target,	blendColorStates[testNdx].initialValue)));
+			FOR_EACH_VERIFIER(normalVerifiers, addChild(new BlendFuncSeparateTestCase	(m_context, verifier, (std::string(blendColorStates[testNdx].name) + "_separate"	+ verifier->getTestNamePostfix()).c_str(),	blendColorStates[testNdx].description,	blendColorStates[testNdx].target,	blendColorStates[testNdx].initialValue)));
+		}
+	}
+
+	{
+		const struct BlendEquationState
+		{
+			const char*	name;
+			const char*	description;
+			GLenum		target;
+			int			initialValue;
+		} blendEquationStates[] =
+		{
+			{ "blend_equation_rgb",		"BLEND_EQUATION_RGB",	GL_BLEND_EQUATION_RGB,		GL_FUNC_ADD	},
+			{ "blend_equation_alpha",	"BLEND_EQUATION_ALPHA",	GL_BLEND_EQUATION_ALPHA,	GL_FUNC_ADD	}
+		};
+		for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(blendEquationStates); testNdx++)
+		{
+			FOR_EACH_VERIFIER(normalVerifiers, addChild(new BlendEquationTestCase			(m_context, verifier, (std::string(blendEquationStates[testNdx].name) +				+ verifier->getTestNamePostfix()).c_str(),		blendEquationStates[testNdx].description,	blendEquationStates[testNdx].target,	blendEquationStates[testNdx].initialValue)));
+			FOR_EACH_VERIFIER(normalVerifiers, addChild(new BlendEquationSeparateTestCase	(m_context, verifier, (std::string(blendEquationStates[testNdx].name) + "_separate"	+ verifier->getTestNamePostfix()).c_str(),		blendEquationStates[testNdx].description,	blendEquationStates[testNdx].target,	blendEquationStates[testNdx].initialValue)));
+		}
+	}
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ImplementationArrayTestCase			(m_context, verifier, (std::string("compressed_texture_formats")	+ verifier->getTestNamePostfix()).c_str(),	"COMPRESSED_TEXTURE_FORMATS",	GL_COMPRESSED_TEXTURE_FORMATS,		GL_NUM_COMPRESSED_TEXTURE_FORMATS,	0)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ImplementationArrayTestCase			(m_context, verifier, (std::string("shader_binary_formats")			+ verifier->getTestNamePostfix()).c_str(),	"SHADER_BINARY_FORMATS",		GL_SHADER_BINARY_FORMATS,			GL_NUM_SHADER_BINARY_FORMATS,		0)));
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new BufferBindingTestCase				(m_context, verifier, (std::string("array_buffer_binding")			+ verifier->getTestNamePostfix()).c_str(),	"ARRAY_BUFFER_BINDING",			GL_ARRAY_BUFFER_BINDING,			GL_ARRAY_BUFFER)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new BufferBindingTestCase				(m_context, verifier, (std::string("element_array_buffer_binding")	+ verifier->getTestNamePostfix()).c_str(),	"ELEMENT_ARRAY_BUFFER_BINDING",	GL_ELEMENT_ARRAY_BUFFER_BINDING,	GL_ELEMENT_ARRAY_BUFFER)));
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new CurrentProgramBindingTestCase		(m_context, verifier, (std::string("current_program_binding")		+ verifier->getTestNamePostfix()).c_str(),	"CURRENT_PROGRAM")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilClearValueTestCase			(m_context, verifier, (std::string("stencil_clear_value")			+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_CLEAR_VALUE")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ActiveTextureTestCase				(m_context, verifier, (std::string("active_texture")				+ verifier->getTestNamePostfix()).c_str(),	"ACTIVE_TEXTURE")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new RenderbufferBindingTestCase			(m_context, verifier, (std::string("renderbuffer_binding")			+ verifier->getTestNamePostfix()).c_str(),	"RENDERBUFFER_BINDING")));
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new TextureBindingTestCase				(m_context, verifier, (std::string("texture_binding_2d")			+ verifier->getTestNamePostfix()).c_str(),	"TEXTURE_BINDING_2D",			GL_TEXTURE_BINDING_2D,			GL_TEXTURE_2D)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new TextureBindingTestCase				(m_context, verifier, (std::string("texture_binding_cube_map")		+ verifier->getTestNamePostfix()).c_str(),	"TEXTURE_BINDING_CUBE_MAP",		GL_TEXTURE_BINDING_CUBE_MAP,	GL_TEXTURE_CUBE_MAP)));
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new FrameBufferBindingTestCase			(m_context, verifier, (std::string("framebuffer_binding")			+ verifier->getTestNamePostfix()).c_str(),	"DRAW_FRAMEBUFFER_BINDING and READ_FRAMEBUFFER_BINDING")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ImplementationColorReadTestCase		(m_context, verifier, (std::string("implementation_color_read")		+ verifier->getTestNamePostfix()).c_str(),	"IMPLEMENTATION_COLOR_READ_TYPE and IMPLEMENTATION_COLOR_READ_FORMAT")));
+}
+
+void IntegerStateQueryTests::deinit (void)
+{
+	if (m_verifierBoolean)
+	{
+		delete m_verifierBoolean;
+		m_verifierBoolean = DE_NULL;
+	}
+	if (m_verifierInteger)
+	{
+		delete m_verifierInteger;
+		m_verifierInteger = DE_NULL;
+	}
+	if (m_verifierFloat)
+	{
+		delete m_verifierFloat;
+		m_verifierFloat = DE_NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fIntegerStateQueryTests.hpp b/modules/gles2/functional/es2fIntegerStateQueryTests.hpp
new file mode 100644
index 0000000..17859c6
--- /dev/null
+++ b/modules/gles2/functional/es2fIntegerStateQueryTests.hpp
@@ -0,0 +1,66 @@
+#ifndef _ES2FINTEGERSTATEQUERYTESTS_HPP
+#define _ES2FINTEGERSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Integer State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace IntegerStateQueryVerifiers
+{
+
+class GetBooleanVerifier;
+class GetIntegerVerifier;
+class GetFloatVerifier;
+
+} // IntegerStateQueryVerifiers
+
+class IntegerStateQueryTests : public TestCaseGroup
+{
+public:
+																IntegerStateQueryTests	(Context& context);
+																~IntegerStateQueryTests	(void);
+
+	void														init					(void);
+	void														deinit					(void);
+
+private:
+																IntegerStateQueryTests	(const IntegerStateQueryTests& other);
+	IntegerStateQueryTests&										operator=				(const IntegerStateQueryTests& other);
+
+	IntegerStateQueryVerifiers::GetBooleanVerifier*				m_verifierBoolean;
+	IntegerStateQueryVerifiers::GetIntegerVerifier*				m_verifierInteger;
+	IntegerStateQueryVerifiers::GetFloatVerifier*				m_verifierFloat;
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FINTEGERSTATEQUERYTESTS_HPP
diff --git a/modules/gles2/functional/es2fLifetimeTests.cpp b/modules/gles2/functional/es2fLifetimeTests.cpp
new file mode 100644
index 0000000..80b858a
--- /dev/null
+++ b/modules/gles2/functional/es2fLifetimeTests.cpp
@@ -0,0 +1,74 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Object lifetime tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fLifetimeTests.hpp"
+
+#include "deUniquePtr.hpp"
+#include "glsLifetimeTests.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace
+{
+
+using de::MovePtr;
+using glu::RenderContext;
+namespace lt = gls::LifetimeTests;
+using namespace lt;
+using namespace glw;
+
+class TestGroup : public TestCaseGroup
+{
+public:
+							TestGroup		(Context& context)
+								: TestCaseGroup	(context, "lifetime", "Object lifetime tests")
+							{}
+	void					init			(void);
+
+private:
+	MovePtr<Types>			m_types;
+};
+
+void TestGroup::init (void)
+{
+	gles2::Context&	ctx		= getContext();
+	lt::Context		ltCtx	(ctx.getRenderContext(), ctx.getTestContext());
+
+	m_types = MovePtr<Types>(new ES2Types(ltCtx));
+	addTestCases(*this, *m_types);
+}
+
+} // anonymous
+
+TestCaseGroup* createLifetimeTests (Context& context)
+{
+	return new TestGroup(context);
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fLifetimeTests.hpp b/modules/gles2/functional/es2fLifetimeTests.hpp
new file mode 100644
index 0000000..e4e1790
--- /dev/null
+++ b/modules/gles2/functional/es2fLifetimeTests.hpp
@@ -0,0 +1,41 @@
+#ifndef _ES2FLIFETIMETESTS_HPP
+#define _ES2FLIFETIMETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Object lifetime tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+TestCaseGroup* createLifetimeTests (Context& context);
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FLIFETIMETESTS_HPP
diff --git a/modules/gles2/functional/es2fLightAmountTest.cpp b/modules/gles2/functional/es2fLightAmountTest.cpp
new file mode 100644
index 0000000..0a07c06
--- /dev/null
+++ b/modules/gles2/functional/es2fLightAmountTest.cpp
@@ -0,0 +1,240 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Light amount test.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fLightAmountTest.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluDefs.hpp"
+#include "gluShaderProgram.hpp"
+#include "tcuTestLog.hpp"
+#include "deStringUtil.hpp"
+#include "deInt32.h"
+#include "deRandom.h"
+
+#include <stdio.h>
+#include <vector>
+
+#include "glw.h"
+
+using namespace std;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+const char* s_noLightsVertexShader =
+	"uniform mat4 u_modelviewMatrix;\n"
+	"uniform mat4 u_modelviewProjectionMatrix;\n"
+	"uniform mat3 u_normalMatrix;\n"
+	"\n"
+	"attribute vec4 a_position;\n"
+	"attribute vec3 a_normal;\n"
+	"\n"
+	"varying vec3 v_color;\n"
+	"\n"
+	"void main()\n"
+	"{\n"
+	"	v_color = vec3(0.0);\n"
+	"	gl_Position = u_modelviewProjectionMatrix * a_position;\n"
+	"}\n"
+;
+
+const char* s_vertexShaderTemplate =
+	"struct Light\n"
+	"{\n"
+	"	vec3	position;\n"
+	"	vec3	diffuse;\n"
+	"	vec3	specular;\n"
+	"	vec3	attenuation;\n"
+	"};\n"
+	"uniform Light u_lights[${NUM_DIR_LIGHTS} + ${NUM_OMNI_LIGHTS}];\n"
+	"uniform mat4 u_modelviewMatrix;\n"
+	"uniform mat4 u_modelviewProjectionMatrix;\n"
+	"uniform mat3 u_normalMatrix;\n"
+	"\n"
+	"attribute vec4 a_position;\n"
+	"attribute vec3 a_normal;\n"
+	"\n"
+	"varying vec3 v_color;\n"
+	"\n"
+	"float computeAttenuation(vec3 dirToLight, vec3 attenuation)\n"
+	"{\n"
+	"	float dist = length(dirToLight);\n"
+	"	return 1.0 / (attenuation.x + attenuation.y*dist + attenuation.z*dist*dist);\n"
+	"}\n"
+	"\n"
+	"vec3 computeDirLight(int ndx, vec3 position, vec3 normal)\n"
+	"{\n"
+	"	Light light = u_lights[ndx];\n"
+	"	float cosAngle = dot(light.position, normal);\n"
+	"	return cosAngle * light.diffuse;\n"
+	"}\n"
+	"\n"
+	"vec3 computeOmniLight(int ndx, vec3 position, vec3 normal)\n"
+	"{\n"
+	"	Light light = u_lights[ndx];\n"
+	"	vec3 dirToLight = light.position - position;\n"
+	"	float cosAngle = dot(normalize(dirToLight), normal);\n"
+	"	float atten = computeAttenuation(dirToLight, light.attenuation);\n"
+	"	return atten * cosAngle * light.diffuse;\n"
+	"}\n"
+	"\n"
+	"void main()\n"
+	"{\n"
+	"	vec3 lightSpacePos = vec3(u_modelviewMatrix * a_position);\n"
+	"	vec3 lightNormal = normalize(u_normalMatrix * a_normal);\n"
+	"	vec3 color = vec3(0.0);\n"
+	"	for (int i = 0; i < ${NUM_DIR_LIGHTS}; i++)\n"
+	"		color += computeDirLight(i, lightSpacePos, lightNormal);\n"
+	"	for (int i = 0; i < ${NUM_OMNI_LIGHTS}; i++)\n"
+	"		color += computeOmniLight(${NUM_DIR_LIGHTS}+i, lightSpacePos, lightNormal);\n"
+	"	v_color = color;\n"
+	"	gl_Position = u_modelviewProjectionMatrix * a_position;\n"
+	"}\n"
+;
+
+const char* s_fragmentShaderTemplate =
+	"varying highp vec3 v_color;\n"
+	"\n"
+	"void main()\n"
+	"{\n"
+	"	gl_FragColor = vec4(v_color, 1.0);\n"
+	"}\n"
+	;
+
+class LightAmountCase : public TestCase
+{
+public:
+	LightAmountCase(Context&  context, const char* name, int numDirectionalLights, int numOmniLights, int numSpotLights)
+		: TestCase(context, name, name)
+		, m_numDirectionalLights	(numDirectionalLights)
+		, m_numOmniLights			(numOmniLights)
+		, m_numSpotLights			(numSpotLights)
+	{
+	}
+
+	virtual IterateResult	iterate		(void);
+
+private:
+	int				m_numDirectionalLights;
+	int				m_numOmniLights;
+	int				m_numSpotLights;
+};
+
+TestCase::IterateResult LightAmountCase::iterate (void)
+{
+	GLU_CHECK_MSG("LightAmountTest::iterate() begin");
+
+	string vertexShaderSource;
+	string fragmentShaderSource;
+
+	// Fill in shader template parameters.
+	{
+		bool hasAnyLights = ((m_numDirectionalLights + m_numOmniLights + m_numSpotLights) != 0);
+
+		tcu::StringTemplate vertexTemplate(hasAnyLights ? s_vertexShaderTemplate : s_noLightsVertexShader);
+		tcu::StringTemplate fragmentTemplate(s_fragmentShaderTemplate);
+
+		map<string, string> params;
+		params.insert(pair<string, string>("NUM_DIR_LIGHTS", de::toString(m_numDirectionalLights)));
+		params.insert(pair<string, string>("NUM_OMNI_LIGHTS", de::toString(m_numOmniLights)));
+		params.insert(pair<string, string>("NUM_SPOT_LIGHTS", de::toString(m_numSpotLights)));
+
+		vertexShaderSource		= vertexTemplate.specialize(params);
+		fragmentShaderSource	= fragmentTemplate.specialize(params);
+	}
+
+	// Create shader and program objects.
+	glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	m_testCtx.getLog() << program;
+
+	// Draw something? Check results?
+	glUseProgram(program.getProgram());
+
+	bool testOk = program.isOk();
+
+	GLU_CHECK_MSG("LightAmountTest::iterate() end");
+
+	m_testCtx.setTestResult(testOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, testOk ? "Pass" : "Fail");
+	return TestCase::STOP;
+}
+
+//
+
+LightAmountTest::LightAmountTest (Context& context) : TestCaseGroup(context, "light_amount", "Light Amount Stress Tests")
+{
+}
+
+LightAmountTest::~LightAmountTest (void)
+{
+}
+
+void LightAmountTest::init (void)
+{
+	//										name				dir,	omni,	spot
+	addChild(new LightAmountCase(m_context, "none",				0,		0,		0	));
+	addChild(new LightAmountCase(m_context, "1dir",				1,		0,		0	));
+	addChild(new LightAmountCase(m_context, "2dir",				2,		0,		0	));
+	addChild(new LightAmountCase(m_context, "4dir",				4,		0,		0	));
+	addChild(new LightAmountCase(m_context, "6dir",				6,		0,		0	));
+	addChild(new LightAmountCase(m_context, "8dir",				8,		0,		0	));
+	addChild(new LightAmountCase(m_context, "10dir",			10,		0,		0	));
+	addChild(new LightAmountCase(m_context, "12dir",			12,		0,		0	));
+	addChild(new LightAmountCase(m_context, "14dir",			14,		0,		0	));
+	addChild(new LightAmountCase(m_context, "16dir",			16,		0,		0	));
+	addChild(new LightAmountCase(m_context, "1omni",			0,		1,		0	));
+	addChild(new LightAmountCase(m_context, "2omni",			0,		2,		0	));
+	addChild(new LightAmountCase(m_context, "4omni",			0,		4,		0	));
+	addChild(new LightAmountCase(m_context, "6omni",			0,		6,		0	));
+	addChild(new LightAmountCase(m_context, "8omni",			0,		8,		0	));
+	addChild(new LightAmountCase(m_context, "10omni",			0,		10,		0	));
+	addChild(new LightAmountCase(m_context, "12omni",			0,		12,		0	));
+	addChild(new LightAmountCase(m_context, "14omni",			0,		14,		0	));
+	addChild(new LightAmountCase(m_context, "16omni",			0,		16,		0	));
+// 	addChild(new LightAmountCase(m_context, "1spot",			0,		0,		1	));
+// 	addChild(new LightAmountCase(m_context, "2spot",			0,		0,		2	));
+// 	addChild(new LightAmountCase(m_context, "4spot",			0,		0,		4	));
+// 	addChild(new LightAmountCase(m_context, "6spot",			0,		0,		6	));
+// 	addChild(new LightAmountCase(m_context, "8spot",			0,		0,		8	));
+// 	addChild(new LightAmountCase(m_context, "1dir_1omni",		1,		1,		0	));
+// 	addChild(new LightAmountCase(m_context, "2dir_2omni",		2,		2,		0	));
+// 	addChild(new LightAmountCase(m_context, "4dir_4omni",		4,		4,		0	));
+// 	addChild(new LightAmountCase(m_context, "1dir_1spot",		1,		0,		1	));
+// 	addChild(new LightAmountCase(m_context, "2dir_2spot",		2,		0,		2	));
+// 	addChild(new LightAmountCase(m_context, "4dir_4spot",		4,		0,		4	));
+// 	addChild(new LightAmountCase(m_context, "1omni_1spot",		0,		1,		1	));
+// 	addChild(new LightAmountCase(m_context, "2omni_2spot",		0,		2,		2	));
+// 	addChild(new LightAmountCase(m_context, "4omni_4spot",		0,		4,		4	));
+// 	addChild(new LightAmountCase(m_context, "1dir_1omni_1spot",	1,		1,		1	));
+// 	addChild(new LightAmountCase(m_context, "2dir_2omni_2spot",	2,		2,		2	));
+// 	addChild(new LightAmountCase(m_context, "4dir_2omni_2spot",	4,		2,		2	));
+// 	addChild(new LightAmountCase(m_context, "2dir_4omni_2spot",	2,		4,		2	));
+// 	addChild(new LightAmountCase(m_context, "2dir_2omni_4spot",	2,		2,		4	));
+// 	addChild(new LightAmountCase(m_context, "4dir_4omni_4spot",	4,		4,		4	));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fLightAmountTest.hpp b/modules/gles2/functional/es2fLightAmountTest.hpp
new file mode 100644
index 0000000..1d04c41
--- /dev/null
+++ b/modules/gles2/functional/es2fLightAmountTest.hpp
@@ -0,0 +1,57 @@
+#ifndef _ES2FLIGHTAMOUNTTEST_HPP
+#define _ES2FLIGHTAMOUNTTEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Light amount test.
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes2TestCase.hpp"
+
+struct LightAmountSubCase;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class LightAmountTest : public TestCaseGroup
+{
+public:
+							LightAmountTest		(Context& context);
+	virtual					~LightAmountTest	(void);
+
+	virtual void			init				(void);
+
+private:
+							LightAmountTest		(const LightAmountTest&);		// not allowed!
+	LightAmountTest&		operator=			(const LightAmountTest&);		// not allowed!
+
+	// Member variables.
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FLIGHTAMOUNTTEST_HPP
diff --git a/modules/gles2/functional/es2fMultisampleTests.cpp b/modules/gles2/functional/es2fMultisampleTests.cpp
new file mode 100644
index 0000000..e12972c
--- /dev/null
+++ b/modules/gles2/functional/es2fMultisampleTests.cpp
@@ -0,0 +1,1492 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisampling tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fMultisampleTests.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuCommandLine.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+#include "glw.h"
+
+#include <string>
+#include <vector>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec4;
+using tcu::TestLog;
+using std::vector;
+
+static const float SQRT_HALF = 0.707107f;
+
+namespace
+{
+
+struct QuadCorners
+{
+	Vec2 p0;
+	Vec2 p1;
+	Vec2 p2;
+	Vec2 p3;
+
+	QuadCorners(const Vec2& p0_, const Vec2& p1_, const Vec2& p2_, const Vec2& p3_) : p0(p0_), p1(p1_), p2(p2_), p3(p3_) {}
+};
+
+} // anonymous
+
+static inline int getIterationCount (const tcu::TestContext& ctx, int defaultCount)
+{
+	int cmdLineValue = ctx.getCommandLine().getTestIterationCount();
+	return cmdLineValue > 0 ? cmdLineValue : defaultCount;
+}
+
+static inline int getGLInteger (GLenum name)
+{
+	int result;
+	GLU_CHECK_CALL(glGetIntegerv(name, &result));
+	return result;
+}
+
+template<typename T>
+static inline T min4 (T a, T b, T c, T d)
+{
+	return de::min(de::min(de::min(a, b), c), d);
+}
+
+template<typename T>
+static inline T max4 (T a, T b, T c, T d)
+{
+	return de::max(de::max(de::max(a, b), c), d);
+}
+
+static inline bool isInsideQuad (const IVec2& point, const IVec2& p0, const IVec2& p1, const IVec2& p2, const IVec2& p3)
+{
+	int dot0 = (point.x()-p0.x()) * (p1.y()-p0.y()) + (point.y()-p0.y()) * (p0.x()-p1.x());
+	int dot1 = (point.x()-p1.x()) * (p2.y()-p1.y()) + (point.y()-p1.y()) * (p1.x()-p2.x());
+	int dot2 = (point.x()-p2.x()) * (p3.y()-p2.y()) + (point.y()-p2.y()) * (p2.x()-p3.x());
+	int dot3 = (point.x()-p3.x()) * (p0.y()-p3.y()) + (point.y()-p3.y()) * (p3.x()-p0.x());
+
+	return (dot0 > 0) == (dot1 > 0) && (dot1 > 0) == (dot2 > 0) && (dot2 > 0) == (dot3 > 0);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Check if a region in an image is unicolored.
+ *
+ * Checks if the pixels in img inside the convex quadilateral defined by
+ * p0, p1, p2 and p3 are all (approximately) of the same color.
+ *//*--------------------------------------------------------------------*/
+static bool isPixelRegionUnicolored (const tcu::Surface& img, const IVec2& p0, const IVec2& p1, const IVec2& p2, const IVec2& p3)
+{
+	int			xMin				= de::clamp(min4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1);
+	int			yMin				= de::clamp(min4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1);
+	int			xMax				= de::clamp(max4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1);
+	int			yMax				= de::clamp(max4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1);
+	bool		insideEncountered	= false;	//!< Whether we have already seen at least one pixel inside the region.
+	tcu::RGBA	insideColor;					//!< Color of the first pixel inside the region.
+
+	for (int y = yMin; y <= yMax; y++)
+	for (int x = xMin; x <= xMax; x++)
+	{
+		if (isInsideQuad(IVec2(x, y), p0, p1, p2, p3))
+		{
+			tcu::RGBA pixColor = img.getPixel(x, y);
+
+			if (insideEncountered)
+			{
+				if (!tcu::compareThreshold(pixColor, insideColor, tcu::RGBA(3, 3, 3, 3))) // Pixel color differs from already-detected color inside same region - region not unicolored.
+					return false;
+			}
+			else
+			{
+				insideEncountered = true;
+				insideColor = pixColor;
+			}
+		}
+	}
+
+	return true;
+}
+
+static bool drawUnicolorTestErrors (tcu::Surface& img, const tcu::PixelBufferAccess& errorImg, const IVec2& p0, const IVec2& p1, const IVec2& p2, const IVec2& p3)
+{
+	int			xMin		= de::clamp(min4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1);
+	int			yMin		= de::clamp(min4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1);
+	int			xMax		= de::clamp(max4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1);
+	int			yMax		= de::clamp(max4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1);
+	tcu::RGBA	refColor	= img.getPixel((xMin + xMax) / 2, (yMin + yMax) / 2);
+
+	for (int y = yMin; y <= yMax; y++)
+	for (int x = xMin; x <= xMax; x++)
+	{
+		if (isInsideQuad(IVec2(x, y), p0, p1, p2, p3))
+		{
+			if (!tcu::compareThreshold(img.getPixel(x, y), refColor, tcu::RGBA(3, 3, 3, 3)))
+			{
+				img.setPixel(x, y, tcu::RGBA::red);
+				errorImg.setPixel(Vec4(1.0f, 0.0f, 0.0f, 1.0f), x, y);
+			}
+		}
+	}
+
+	return true;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Abstract base class handling common stuff for multisample cases.
+ *//*--------------------------------------------------------------------*/
+class MultisampleCase : public TestCase
+{
+public:
+						MultisampleCase			(Context& context, const char* name, const char* desc);
+	virtual				~MultisampleCase		(void);
+
+	virtual void		init					(void);
+	virtual void		deinit					(void);
+
+protected:
+	virtual int			getDesiredViewportSize	(void) const = 0;
+
+	void				renderTriangle			(const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const;
+	void				renderTriangle			(const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& color) const;
+	void				renderTriangle			(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const;
+	void				renderTriangle			(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const;
+	void				renderQuad				(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& c0, const Vec4& c1, const Vec4& c2, const Vec4& c3) const;
+	void				renderQuad				(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& color) const;
+	void				renderLine				(const Vec2& p0, const Vec2& p1, const Vec4& color) const;
+
+	void				randomizeViewport		(void);
+	void				readImage				(tcu::Surface& dst) const;
+
+	int					m_numSamples;
+
+	int					m_viewportSize;
+
+private:
+						MultisampleCase			(const MultisampleCase& other);
+	MultisampleCase&	operator=				(const MultisampleCase& other);
+
+	glu::ShaderProgram*	m_program;
+	int					m_attrPositionLoc;
+	int					m_attrColorLoc;
+
+	int					m_viewportX;
+	int					m_viewportY;
+	de::Random			m_rnd;
+};
+
+MultisampleCase::MultisampleCase (Context& context, const char* name, const char* desc)
+	: TestCase			(context, name, desc)
+	, m_numSamples		(0)
+	, m_viewportSize	(0)
+	, m_program			(DE_NULL)
+	, m_attrPositionLoc	(-1)
+	, m_attrColorLoc	(-1)
+	, m_viewportX		(0)
+	, m_viewportY		(0)
+	, m_rnd				(deStringHash(name))
+{
+}
+
+void MultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const
+{
+	float vertexPositions[] =
+	{
+		p0.x(), p0.y(), p0.z(), 1.0f,
+		p1.x(), p1.y(), p1.z(), 1.0f,
+		p2.x(), p2.y(), p2.z(), 1.0f
+	};
+	float vertexColors[] =
+	{
+		c0.x(), c0.y(), c0.z(), c0.w(),
+		c1.x(), c1.y(), c1.z(), c1.w(),
+		c2.x(), c2.y(), c2.z(), c2.w(),
+	};
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrPositionLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(m_attrPositionLoc, 4, GL_FLOAT, false, 0, &vertexPositions[0]));
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrColorLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(m_attrColorLoc, 4, GL_FLOAT, false, 0, &vertexColors[0]));
+
+	GLU_CHECK_CALL(glUseProgram(m_program->getProgram()));
+	GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 3));
+}
+
+void MultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& color) const
+{
+	renderTriangle(p0, p1, p2, color, color, color);
+}
+
+void MultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const
+{
+	renderTriangle(Vec3(p0.x(), p0.y(), 0.0f),
+				   Vec3(p1.x(), p1.y(), 0.0f),
+				   Vec3(p2.x(), p2.y(), 0.0f),
+				   c0, c1, c2);
+}
+
+void MultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const
+{
+	renderTriangle(p0, p1, p2, color, color, color);
+}
+
+void MultisampleCase::renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& c0, const Vec4& c1, const Vec4& c2, const Vec4& c3) const
+{
+	renderTriangle(p0, p1, p2, c0, c1, c2);
+	renderTriangle(p2, p1, p3, c2, c1, c3);
+}
+
+void MultisampleCase::renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& color) const
+{
+	renderQuad(p0, p1, p2, p3, color, color, color, color);
+}
+
+void MultisampleCase::renderLine (const Vec2& p0, const Vec2& p1, const Vec4& color) const
+{
+	float vertexPositions[] =
+	{
+		p0.x(), p0.y(), 0.0f, 1.0f,
+		p1.x(), p1.y(), 0.0f, 1.0f
+	};
+	float vertexColors[] =
+	{
+		color.x(), color.y(), color.z(), color.w(),
+		color.x(), color.y(), color.z(), color.w()
+	};
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrPositionLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(m_attrPositionLoc, 4, GL_FLOAT, false, 0, &vertexPositions[0]));
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrColorLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(m_attrColorLoc, 4, GL_FLOAT, false, 0, &vertexColors[0]));
+
+	GLU_CHECK_CALL(glUseProgram(m_program->getProgram()));
+	GLU_CHECK_CALL(glDrawArrays(GL_LINES, 0, 2));
+}
+
+void MultisampleCase::randomizeViewport (void)
+{
+	m_viewportX = m_rnd.getInt(0, m_context.getRenderTarget().getWidth() - m_viewportSize);
+	m_viewportY = m_rnd.getInt(0, m_context.getRenderTarget().getHeight() - m_viewportSize);
+
+	GLU_CHECK_CALL(glViewport(m_viewportX, m_viewportY, m_viewportSize, m_viewportSize));
+}
+
+void MultisampleCase::readImage (tcu::Surface& dst) const
+{
+	glu::readPixels(m_context.getRenderContext(), m_viewportX, m_viewportY, dst.getAccess());
+}
+
+void MultisampleCase::init (void)
+{
+	static const char* vertShaderSource =
+		"attribute highp vec4 a_position;\n"
+		"attribute mediump vec4 a_color;\n"
+		"varying mediump vec4 v_color;\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_color = a_color;\n"
+		"}\n";
+
+	static const char* fragShaderSource =
+		"varying mediump vec4 v_color;\n"
+		"void main()\n"
+		"{\n"
+		"	gl_FragColor = v_color;\n"
+		"}\n";
+
+	// Check multisample support.
+
+	if (m_context.getRenderTarget().getNumSamples() <= 1)
+		throw tcu::NotSupportedError("No multisample buffers");
+
+	// Prepare program.
+
+	DE_ASSERT(!m_program);
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource));
+	if (!m_program->isOk())
+		throw tcu::TestError("Failed to compile program", DE_NULL, __FILE__, __LINE__);
+
+	GLU_CHECK_CALL(m_attrPositionLoc	= glGetAttribLocation(m_program->getProgram(), "a_position"));
+	GLU_CHECK_CALL(m_attrColorLoc		= glGetAttribLocation(m_program->getProgram(), "a_color"));
+
+	if (m_attrPositionLoc < 0 || m_attrColorLoc < 0)
+	{
+		delete m_program;
+		throw tcu::TestError("Invalid attribute locations", DE_NULL, __FILE__, __LINE__);
+	}
+
+	// Get suitable viewport size.
+
+	m_viewportSize = de::min<int>(getDesiredViewportSize(), de::min(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight()));
+	randomizeViewport();
+
+	// Query and log number of samples per pixel.
+
+	m_numSamples = getGLInteger(GL_SAMPLES);
+	m_testCtx.getLog() << TestLog::Message << "GL_SAMPLES = " << m_numSamples << TestLog::EndMessage;
+}
+
+MultisampleCase::~MultisampleCase (void)
+{
+	delete m_program;
+}
+
+void MultisampleCase::deinit (void)
+{
+	delete m_program;
+
+	m_program = DE_NULL;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Base class for cases testing the value of GL_SAMPLES.
+ *
+ * Draws a test pattern (defined by renderPattern() of an inheriting class)
+ * and counts the number of distinct colors in the resulting image. That
+ * number should be at least the value of GL_SAMPLES plus one. This is
+ * repeated with increased values of m_currentIteration until this correct
+ * number of colors is detected or m_currentIteration reaches
+ * m_maxNumIterations.
+ *//*--------------------------------------------------------------------*/
+class NumSamplesCase : public MultisampleCase
+{
+public:
+						NumSamplesCase			(Context& context, const char* name, const char* description);
+						~NumSamplesCase			(void) {}
+
+	IterateResult		iterate					(void);
+
+protected:
+	int					getDesiredViewportSize	(void) const { return 256; }
+	virtual void		renderPattern			(void) const = 0;
+
+	int					m_currentIteration;
+
+private:
+	enum { DEFAULT_MAX_NUM_ITERATIONS = 16 };
+
+	const int			m_maxNumIterations;
+	vector<tcu::RGBA>	m_detectedColors;
+};
+
+NumSamplesCase::NumSamplesCase (Context& context, const char* name, const char* description)
+	: MultisampleCase		(context, name, description)
+	, m_currentIteration	(0)
+	, m_maxNumIterations	(getIterationCount(m_testCtx, DEFAULT_MAX_NUM_ITERATIONS))
+{
+}
+
+NumSamplesCase::IterateResult NumSamplesCase::iterate (void)
+{
+	TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface	renderedImg		(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	renderPattern();
+
+	// Read and log rendered image.
+
+	readImage(renderedImg);
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	// Detect new, previously unseen colors from image.
+
+	int requiredNumDistinctColors = m_numSamples + 1;
+
+	for (int y = 0; y < renderedImg.getHeight() && (int)m_detectedColors.size() < requiredNumDistinctColors; y++)
+	for (int x = 0; x < renderedImg.getWidth() && (int)m_detectedColors.size() < requiredNumDistinctColors; x++)
+	{
+		tcu::RGBA color = renderedImg.getPixel(x, y);
+
+		int i;
+		for (i = 0; i < (int)m_detectedColors.size(); i++)
+		{
+			if (tcu::compareThreshold(color, m_detectedColors[i], tcu::RGBA(3, 3, 3, 3)))
+				break;
+		}
+
+		if (i == (int)m_detectedColors.size())
+			m_detectedColors.push_back(color); // Color not previously detected.
+	}
+
+	// Log results.
+
+	log << TestLog::Message
+		<< "Number of distinct colors detected so far: "
+		<< ((int)m_detectedColors.size() >= requiredNumDistinctColors ? "at least " : "")
+		<< de::toString(m_detectedColors.size())
+		<< TestLog::EndMessage;
+
+	if ((int)m_detectedColors.size() < requiredNumDistinctColors)
+	{
+		// Haven't detected enough different colors yet.
+
+		m_currentIteration++;
+
+		if (m_currentIteration >= m_maxNumIterations)
+		{
+			log << TestLog::Message << "Failure: Number of distinct colors detected is lower than GL_SAMPLES+1" << TestLog::EndMessage;
+			m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+			return STOP;
+		}
+		else
+		{
+			log << TestLog::Message << "The number of distinct colors detected is lower than GL_SAMPLES+1 - trying again with a slightly altered pattern" << TestLog::EndMessage;
+			return CONTINUE;
+		}
+	}
+	else
+	{
+		log << TestLog::Message << "Success: The number of distinct colors detected is at least GL_SAMPLES+1" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+		return STOP;
+	}
+}
+
+class PolygonNumSamplesCase : public NumSamplesCase
+{
+public:
+			PolygonNumSamplesCase	(Context& context, const char* name, const char* description);
+			~PolygonNumSamplesCase	(void) {}
+
+protected:
+	void	renderPattern			(void) const;
+};
+
+PolygonNumSamplesCase::PolygonNumSamplesCase (Context& context, const char* name, const char* description)
+	: NumSamplesCase(context, name, description)
+{
+}
+
+void PolygonNumSamplesCase::renderPattern (void) const
+{
+	// The test pattern consists of several triangles with edges at different angles.
+
+	const int numTriangles = 25;
+	for (int i = 0; i < numTriangles; i++)
+	{
+		float angle0 = 2.0f*DE_PI * (float)i			/ (float)numTriangles + 0.001f*(float)m_currentIteration;
+		float angle1 = 2.0f*DE_PI * (float)(i + 0.5f)	/ (float)numTriangles + 0.001f*(float)m_currentIteration;
+
+		renderTriangle(Vec2(0.0f, 0.0f),
+					   Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f),
+					   Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f),
+					   Vec4(1.0f));
+	}
+}
+
+class LineNumSamplesCase : public NumSamplesCase
+{
+public:
+			LineNumSamplesCase		(Context& context, const char* name, const char* description);
+			~LineNumSamplesCase		(void) {}
+
+protected:
+	void	renderPattern			(void) const;
+};
+
+LineNumSamplesCase::LineNumSamplesCase (Context& context, const char* name, const char* description)
+	: NumSamplesCase (context, name, description)
+{
+}
+
+void LineNumSamplesCase::renderPattern (void) const
+{
+	// The test pattern consists of several lines at different angles.
+
+	// We scale the number of lines based on the viewport size. This is because a gl line's thickness is
+	// constant in pixel units, i.e. they get relatively thicker as viewport size decreases. Thus we must
+	// decrease the number of lines in order to decrease the extent of overlap among the lines in the
+	// center of the pattern.
+	const int numLines = (int)(100.0f * deFloatSqrt((float)m_viewportSize / 256.0f));
+
+	for (int i = 0; i < numLines; i++)
+	{
+		float angle = 2.0f*DE_PI * (float)i / (float)numLines + 0.001f*(float)m_currentIteration;
+		renderLine(Vec2(0.0f, 0.0f), Vec2(deFloatCos(angle)*0.95f, deFloatSin(angle)*0.95f), Vec4(1.0f));
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Case testing behaviour of common edges when multisampling.
+ *
+ * Draws a number of test patterns, each with a number of quads, each made
+ * of two triangles, rotated at different angles. The inner edge inside the
+ * quad (i.e. the common edge of the two triangles) still should not be
+ * visible, despite multisampling - i.e. the two triangles forming the quad
+ * should never get any common coverage bits in any pixel.
+ *//*--------------------------------------------------------------------*/
+class CommonEdgeCase : public MultisampleCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_SMALL_QUADS = 0,				//!< Draw several small quads per iteration.
+		CASETYPE_BIGGER_THAN_VIEWPORT_QUAD,		//!< Draw one bigger-than-viewport quad per iteration.
+		CASETYPE_FIT_VIEWPORT_QUAD,				//!< Draw one exactly viewport-sized, axis aligned quad per iteration.
+
+		CASETYPE_LAST
+	};
+
+					CommonEdgeCase			(Context& context, const char* name, const char* description, CaseType caseType);
+					~CommonEdgeCase			(void) {}
+
+	void			init					(void);
+
+	IterateResult	iterate					(void);
+
+protected:
+	int				getDesiredViewportSize	(void) const { return m_caseType == CASETYPE_SMALL_QUADS ? 128 : 32; }
+
+private:
+	enum
+	{
+		DEFAULT_SMALL_QUADS_ITERATIONS					= 16,
+		DEFAULT_BIGGER_THAN_VIEWPORT_QUAD_ITERATIONS	= 8*8
+		// \note With CASETYPE_FIT_VIEWPORT_QUAD, we don't do rotations other than multiples of 90 deg -> constant number of iterations.
+	};
+
+	const CaseType	m_caseType;
+
+	const int		m_numIterations;
+	int				m_currentIteration;
+};
+
+CommonEdgeCase::CommonEdgeCase (Context& context, const char* name, const char* description, CaseType caseType)
+	: MultisampleCase		(context, name, description)
+	, m_caseType			(caseType)
+	, m_numIterations		(caseType == CASETYPE_SMALL_QUADS					? getIterationCount(m_testCtx, DEFAULT_SMALL_QUADS_ITERATIONS)
+							: caseType == CASETYPE_BIGGER_THAN_VIEWPORT_QUAD	? getIterationCount(m_testCtx, DEFAULT_BIGGER_THAN_VIEWPORT_QUAD_ITERATIONS)
+							: 8)
+	, m_currentIteration	(0)
+{
+}
+
+void CommonEdgeCase::init (void)
+{
+	MultisampleCase::init();
+
+	if (m_caseType == CASETYPE_SMALL_QUADS)
+	{
+		// Check for a big enough viewport. With too small viewports the test case can't analyze the resulting image well enough.
+
+		const int minViewportSize = 32;
+
+		DE_ASSERT(minViewportSize <= getDesiredViewportSize());
+
+		if (m_viewportSize < minViewportSize)
+			throw tcu::InternalError("Render target width or height too low (is " + de::toString(m_viewportSize) + ", should be at least " + de::toString(minViewportSize) + ")");
+	}
+
+	GLU_CHECK_CALL(glEnable(GL_BLEND));
+	GLU_CHECK_CALL(glBlendEquation(GL_FUNC_ADD));
+	GLU_CHECK_CALL(glBlendFunc(GL_ONE, GL_ONE));
+
+	m_testCtx.getLog() << TestLog::Message << "Additive blending enabled in order to detect (erroneously) overlapping samples" << TestLog::EndMessage;
+}
+
+CommonEdgeCase::IterateResult CommonEdgeCase::iterate (void)
+{
+	TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface	renderedImg		(m_viewportSize, m_viewportSize);
+	tcu::Surface	errorImg		(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	// Draw test pattern. Test patterns consist of quads formed with two triangles.
+	// After drawing the pattern, we check that the interior pixels of each quad are
+	// all the same color - this is meant to verify that there are no artifacts on the inner edge.
+
+	vector<QuadCorners> unicoloredRegions;
+
+	if (m_caseType == CASETYPE_SMALL_QUADS)
+	{
+		// Draw several quads, rotated at different angles.
+
+		const float		quadDiagLen = 2.0f / 3.0f * 0.9f; // \note Fit 3 quads in both x and y directions.
+		float			angleCos;
+		float			angleSin;
+
+		// \note First and second iteration get exact 0 (and 90, 180, 270) and 45 (and 135, 225, 315) angle quads, as they are kind of a special case.
+
+		if (m_currentIteration == 0)
+		{
+			angleCos = 1.0f;
+			angleSin = 0.0f;
+		}
+		else if (m_currentIteration == 1)
+		{
+			angleCos = SQRT_HALF;
+			angleSin = SQRT_HALF;
+		}
+		else
+		{
+			float angle = 0.5f * DE_PI * (float)(m_currentIteration-1) / (float)(m_numIterations-1);
+			angleCos = deFloatCos(angle);
+			angleSin = deFloatSin(angle);
+		}
+
+		Vec2 corners[4] =
+		{
+			0.5f * quadDiagLen * Vec2( angleCos,  angleSin),
+			0.5f * quadDiagLen * Vec2(-angleSin,  angleCos),
+			0.5f * quadDiagLen * Vec2(-angleCos, -angleSin),
+			0.5f * quadDiagLen * Vec2( angleSin, -angleCos)
+		};
+
+		unicoloredRegions.reserve(8);
+
+		// Draw 8 quads.
+		// First four are rotated at angles angle+0, angle+90, angle+180 and angle+270.
+		// Last four are rotated the same angles as the first four, but the ordering of the last triangle's vertices is reversed.
+
+		for (int quadNdx = 0; quadNdx < 8; quadNdx++)
+		{
+			Vec2 center = (2.0f-quadDiagLen) * Vec2((float)(quadNdx%3), (float)(quadNdx/3)) / 2.0f - 0.5f*(2.0f-quadDiagLen);
+
+			renderTriangle(corners[(0+quadNdx) % 4] + center,
+						   corners[(1+quadNdx) % 4] + center,
+						   corners[(2+quadNdx) % 4] + center,
+						   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+
+			if (quadNdx >= 4)
+			{
+				renderTriangle(corners[(3+quadNdx) % 4] + center,
+							   corners[(2+quadNdx) % 4] + center,
+							   corners[(0+quadNdx) % 4] + center,
+							   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+			}
+			else
+			{
+				renderTriangle(corners[(0+quadNdx) % 4] + center,
+							   corners[(2+quadNdx) % 4] + center,
+							   corners[(3+quadNdx) % 4] + center,
+							   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+			}
+
+			// The size of the "interior" of a quad is assumed to be approximately unicolorRegionScale*<actual size of quad>.
+			// By "interior" we here mean the region of non-boundary pixels of the rendered quad for which we can safely assume
+			// that it has all coverage bits set to 1, for every pixel.
+			float unicolorRegionScale = 1.0f - 6.0f*2.0f / (float)m_viewportSize / quadDiagLen;
+			unicoloredRegions.push_back(QuadCorners((center + corners[0]*unicolorRegionScale),
+													(center + corners[1]*unicolorRegionScale),
+													(center + corners[2]*unicolorRegionScale),
+													(center + corners[3]*unicolorRegionScale)));
+		}
+	}
+	else if (m_caseType == CASETYPE_BIGGER_THAN_VIEWPORT_QUAD)
+	{
+		// Draw a bigger-than-viewport quad, rotated at an angle depending on m_currentIteration.
+
+		int				quadBaseAngleNdx		= m_currentIteration / 8;
+		int				quadSubAngleNdx			= m_currentIteration % 8;
+		float			angleCos;
+		float			angleSin;
+
+		if (quadBaseAngleNdx == 0)
+		{
+			angleCos = 1.0f;
+			angleSin = 0.0f;
+		}
+		else if (quadBaseAngleNdx == 1)
+		{
+			angleCos = SQRT_HALF;
+			angleSin = SQRT_HALF;
+		}
+		else
+		{
+			float angle = 0.5f * DE_PI * (float)(m_currentIteration-1) / (float)(m_numIterations-1);
+			angleCos = deFloatCos(angle);
+			angleSin = deFloatSin(angle);
+		}
+
+		float quadDiagLen = 2.5f / de::max(angleCos, angleSin);
+
+		Vec2 corners[4] =
+		{
+			0.5f * quadDiagLen * Vec2( angleCos,  angleSin),
+			0.5f * quadDiagLen * Vec2(-angleSin,  angleCos),
+			0.5f * quadDiagLen * Vec2(-angleCos, -angleSin),
+			0.5f * quadDiagLen * Vec2( angleSin, -angleCos)
+		};
+
+		renderTriangle(corners[(0+quadSubAngleNdx) % 4],
+					   corners[(1+quadSubAngleNdx) % 4],
+					   corners[(2+quadSubAngleNdx) % 4],
+					   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+
+		if (quadSubAngleNdx >= 4)
+		{
+			renderTriangle(corners[(3+quadSubAngleNdx) % 4],
+						   corners[(2+quadSubAngleNdx) % 4],
+						   corners[(0+quadSubAngleNdx) % 4],
+						   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+		}
+		else
+		{
+			renderTriangle(corners[(0+quadSubAngleNdx) % 4],
+						   corners[(2+quadSubAngleNdx) % 4],
+						   corners[(3+quadSubAngleNdx) % 4],
+						   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+		}
+
+		float unicolorRegionScale = 1.0f - 6.0f*2.0f / (float)m_viewportSize / quadDiagLen;
+		unicoloredRegions.push_back(QuadCorners((corners[0]*unicolorRegionScale),
+												(corners[1]*unicolorRegionScale),
+												(corners[2]*unicolorRegionScale),
+												(corners[3]*unicolorRegionScale)));
+	}
+	else if (m_caseType == CASETYPE_FIT_VIEWPORT_QUAD)
+	{
+		// Draw an exactly viewport-sized quad, rotated by multiples of 90 degrees angle depending on m_currentIteration.
+
+		int quadSubAngleNdx = m_currentIteration % 8;
+
+		Vec2 corners[4] =
+		{
+			Vec2( 1.0f,  1.0f),
+			Vec2(-1.0f,  1.0f),
+			Vec2(-1.0f, -1.0f),
+			Vec2( 1.0f, -1.0f)
+		};
+
+		renderTriangle(corners[(0+quadSubAngleNdx) % 4],
+					   corners[(1+quadSubAngleNdx) % 4],
+					   corners[(2+quadSubAngleNdx) % 4],
+					   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+
+		if (quadSubAngleNdx >= 4)
+		{
+			renderTriangle(corners[(3+quadSubAngleNdx) % 4],
+						   corners[(2+quadSubAngleNdx) % 4],
+						   corners[(0+quadSubAngleNdx) % 4],
+						   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+		}
+		else
+		{
+			renderTriangle(corners[(0+quadSubAngleNdx) % 4],
+						   corners[(2+quadSubAngleNdx) % 4],
+						   corners[(3+quadSubAngleNdx) % 4],
+						   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+		}
+
+		unicoloredRegions.push_back(QuadCorners(corners[0], corners[1], corners[2], corners[3]));
+	}
+	else
+		DE_ASSERT(false);
+
+	// Read pixels and check unicolored regions.
+
+	readImage(renderedImg);
+
+	tcu::clear(errorImg.getAccess(), Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	bool errorsDetected = false;
+	for (int i = 0; i < (int)unicoloredRegions.size(); i++)
+	{
+		const QuadCorners&	region					= unicoloredRegions[i];
+		IVec2				p0Win					= ((region.p0+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt();
+		IVec2				p1Win					= ((region.p1+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt();
+		IVec2				p2Win					= ((region.p2+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt();
+		IVec2				p3Win					= ((region.p3+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt();
+		bool				errorsInCurrentRegion	= !isPixelRegionUnicolored(renderedImg, p0Win, p1Win, p2Win, p3Win);
+
+		if (errorsInCurrentRegion)
+			drawUnicolorTestErrors(renderedImg, errorImg.getAccess(), p0Win, p1Win, p2Win, p3Win);
+
+		errorsDetected = errorsDetected || errorsInCurrentRegion;
+	}
+
+	m_currentIteration++;
+
+	if (errorsDetected)
+	{
+		log << TestLog::Message << "Failure: Not all quad interiors seem unicolored - common-edge artifacts?" << TestLog::EndMessage;
+		log << TestLog::Message << "Erroneous pixels are drawn red in the following image" << TestLog::EndMessage;
+		log << TestLog::Image("RenderedImageWithErrors",	"Rendered image with errors marked",	renderedImg,	QP_IMAGE_COMPRESSION_MODE_PNG);
+		log << TestLog::Image("ErrorsOnly",					"Image with error pixels only",			errorImg,		QP_IMAGE_COMPRESSION_MODE_PNG);
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+	else if (m_currentIteration < m_numIterations)
+	{
+		log << TestLog::Message << "Quads seem OK - moving on to next pattern" << TestLog::EndMessage;
+		return CONTINUE;
+	}
+	else
+	{
+		log << TestLog::Message << "Success: All quad interiors seem unicolored (no common-edge artifacts)" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+		return STOP;
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test that depth values are per-sample.
+ *
+ * Draws intersecting, differently-colored polygons and checks that there
+ * are at least GL_SAMPLES+1 distinct colors present, due to some of the
+ * samples at the intersection line belonging to one and some to another
+ * polygon.
+ *//*--------------------------------------------------------------------*/
+class SampleDepthCase : public NumSamplesCase
+{
+public:
+						SampleDepthCase			(Context& context, const char* name, const char* description);
+						~SampleDepthCase		(void) {}
+
+	void				init					(void);
+
+protected:
+	void				renderPattern			(void) const;
+};
+
+SampleDepthCase::SampleDepthCase (Context& context, const char* name, const char* description)
+	: NumSamplesCase (context, name, description)
+{
+}
+
+void SampleDepthCase::init (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	MultisampleCase::init();
+
+	GLU_CHECK_CALL(glEnable(GL_DEPTH_TEST));
+	GLU_CHECK_CALL(glDepthFunc(GL_LESS));
+
+	log << TestLog::Message << "Depth test enabled, depth func is GL_LESS" << TestLog::EndMessage;
+	log << TestLog::Message << "Drawing several bigger-than-viewport black or white polygons intersecting each other" << TestLog::EndMessage;
+}
+
+void SampleDepthCase::renderPattern (void) const
+{
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
+	GLU_CHECK_CALL(glClearDepthf(1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
+
+	{
+		const int numPolygons = 50;
+
+		for (int i = 0; i < numPolygons; i++)
+		{
+			Vec4	color	= i % 2 == 0 ? Vec4(1.0f, 1.0f, 1.0f, 1.0f) : Vec4(0.0f, 0.0f, 0.0f, 1.0f);
+			float	angle	= 2.0f * DE_PI * (float)i / (float)numPolygons + 0.001f*(float)m_currentIteration;
+			Vec3	pt0		(3.0f*deFloatCos(angle + 2.0f*DE_PI*0.0f/3.0f), 3.0f*deFloatSin(angle + 2.0f*DE_PI*0.0f/3.0f), 1.0f);
+			Vec3	pt1		(3.0f*deFloatCos(angle + 2.0f*DE_PI*1.0f/3.0f), 3.0f*deFloatSin(angle + 2.0f*DE_PI*1.0f/3.0f), 0.0f);
+			Vec3	pt2		(3.0f*deFloatCos(angle + 2.0f*DE_PI*2.0f/3.0f), 3.0f*deFloatSin(angle + 2.0f*DE_PI*2.0f/3.0f), 0.0f);
+
+			renderTriangle(pt0, pt1, pt2, color);
+		}
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test that stencil buffer values are per-sample.
+ *
+ * Draws a unicolored pattern and marks drawn samples in stencil buffer;
+ * then clears and draws a viewport-size quad with that color and with
+ * proper stencil test such that the resulting image should be exactly the
+ * same as after the pattern was first drawn.
+ *//*--------------------------------------------------------------------*/
+class SampleStencilCase : public MultisampleCase
+{
+public:
+						SampleStencilCase		(Context& context, const char* name, const char* description);
+						~SampleStencilCase		(void) {}
+
+	IterateResult		iterate					(void);
+
+protected:
+	int					getDesiredViewportSize	(void) const { return 256; }
+};
+
+SampleStencilCase::SampleStencilCase (Context& context, const char* name, const char* description)
+	: MultisampleCase (context, name, description)
+{
+}
+
+SampleStencilCase::IterateResult SampleStencilCase::iterate (void)
+{
+	TestLog&		log					= m_testCtx.getLog();
+	tcu::Surface	renderedImgFirst	(m_viewportSize, m_viewportSize);
+	tcu::Surface	renderedImgSecond	(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClearStencil(0));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
+	GLU_CHECK_CALL(glEnable(GL_STENCIL_TEST));
+	GLU_CHECK_CALL(glStencilFunc(GL_ALWAYS, 1, 1));
+	GLU_CHECK_CALL(glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE));
+
+	log << TestLog::Message << "Drawing a pattern with glStencilFunc(GL_ALWAYS, 1, 1) and glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)" << TestLog::EndMessage;
+
+	{
+		const int numTriangles = 25;
+		for (int i = 0; i < numTriangles; i++)
+		{
+			float angle0 = 2.0f*DE_PI * (float)i			/ (float)numTriangles;
+			float angle1 = 2.0f*DE_PI * (float)(i + 0.5f)	/ (float)numTriangles;
+
+			renderTriangle(Vec2(0.0f, 0.0f),
+						   Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f),
+						   Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f),
+						   Vec4(1.0f));
+		}
+	}
+
+	readImage(renderedImgFirst);
+	log << TestLog::Image("RenderedImgFirst", "First image rendered", renderedImgFirst, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	log << TestLog::Message << "Clearing color buffer to black" << TestLog::EndMessage;
+
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+	GLU_CHECK_CALL(glStencilFunc(GL_EQUAL, 1, 1));
+	GLU_CHECK_CALL(glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP));
+
+	{
+		log << TestLog::Message << "Checking that color buffer was actually cleared to black" << TestLog::EndMessage;
+
+		tcu::Surface clearedImg(m_viewportSize, m_viewportSize);
+		readImage(clearedImg);
+
+		for (int y = 0; y < clearedImg.getHeight(); y++)
+		for (int x = 0; x < clearedImg.getWidth(); x++)
+		{
+			const tcu::RGBA& clr = clearedImg.getPixel(x, y);
+			if (clr != tcu::RGBA::black)
+			{
+				log << TestLog::Message << "Failure: first non-black pixel, color " << clr << ", detected at coordinates (" << x << ", " << y << ")" << TestLog::EndMessage;
+				log << TestLog::Image("ClearedImg", "Image after clearing, erroneously non-black", clearedImg);
+				m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+				return STOP;
+			}
+		}
+	}
+
+	log << TestLog::Message << "Drawing a viewport-sized quad with glStencilFunc(GL_EQUAL, 1, 1) and glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) - should result in same image as the first" << TestLog::EndMessage;
+
+	renderQuad(Vec2(-1.0f, -1.0f),
+			   Vec2( 1.0f, -1.0f),
+			   Vec2(-1.0f,  1.0f),
+			   Vec2( 1.0f,  1.0f),
+			   Vec4(1.0f));
+
+	readImage(renderedImgSecond);
+	log << TestLog::Image("RenderedImgSecond", "Second image rendered", renderedImgSecond, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	bool passed = tcu::pixelThresholdCompare(log,
+											 "ImageCompare",
+											 "Image comparison",
+											 renderedImgFirst,
+											 renderedImgSecond,
+											 tcu::RGBA(0),
+											 tcu::COMPARE_LOG_ON_ERROR);
+
+	if (passed)
+		log << TestLog::Message << "Success: The two images rendered are identical" << TestLog::EndMessage;
+
+	m_context.getTestContext().setTestResult(passed ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+											 passed ? "Passed"				: "Failed");
+
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests coverage mask generation proportionality property.
+ *
+ * Tests that the number of coverage bits in a coverage mask created by
+ * GL_SAMPLE_ALPHA_TO_COVERAGE or GL_SAMPLE_COVERAGE is, on average,
+ * proportional to the alpha or coverage value, respectively. Draws
+ * multiple frames, each time increasing the alpha or coverage value used,
+ * and checks that the average color is changing appropriately.
+ *//*--------------------------------------------------------------------*/
+class MaskProportionalityCase : public MultisampleCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_ALPHA_TO_COVERAGE = 0,
+		CASETYPE_SAMPLE_COVERAGE,
+		CASETYPE_SAMPLE_COVERAGE_INVERTED,
+
+		CASETYPE_LAST
+	};
+
+					MaskProportionalityCase				(Context& context, const char* name, const char* description, CaseType type);
+					~MaskProportionalityCase			(void) {}
+
+	void			init								(void);
+
+	IterateResult	iterate								(void);
+
+protected:
+	int				getDesiredViewportSize				(void) const { return 32; }
+
+private:
+	const CaseType	m_type;
+
+	int				m_numIterations;
+	int				m_currentIteration;
+
+	deInt32			m_previousIterationColorSum;
+};
+
+MaskProportionalityCase::MaskProportionalityCase (Context& context, const char* name, const char* description, CaseType type)
+	: MultisampleCase				(context, name, description)
+	, m_type						(type)
+	, m_currentIteration			(0)
+	, m_previousIterationColorSum	(-1)
+{
+}
+
+void MaskProportionalityCase::init (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	MultisampleCase::init();
+
+	if (m_type == CASETYPE_ALPHA_TO_COVERAGE)
+	{
+		GLU_CHECK_CALL(glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE));
+		log << TestLog::Message << "GL_SAMPLE_ALPHA_TO_COVERAGE is enabled" << TestLog::EndMessage;
+	}
+	else
+	{
+		DE_ASSERT(m_type == CASETYPE_SAMPLE_COVERAGE || m_type == CASETYPE_SAMPLE_COVERAGE_INVERTED);
+
+		GLU_CHECK_CALL(glEnable(GL_SAMPLE_COVERAGE));
+		log << TestLog::Message << "GL_SAMPLE_COVERAGE is enabled" << TestLog::EndMessage;
+	}
+
+	m_numIterations = de::max(2, getIterationCount(m_testCtx, m_numSamples * 5));
+
+	randomizeViewport(); // \note Using the same viewport for every iteration since coverage mask may depend on window-relative pixel coordinate.
+}
+
+MaskProportionalityCase::IterateResult MaskProportionalityCase::iterate (void)
+{
+	TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface	renderedImg		(m_viewportSize, m_viewportSize);
+	deInt32			numPixels		= (deInt32)renderedImg.getWidth()*(deInt32)renderedImg.getHeight();
+
+	log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
+	GLU_CHECK_CALL(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE));
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	if (m_type == CASETYPE_ALPHA_TO_COVERAGE)
+	{
+		GLU_CHECK_CALL(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE));
+		log << TestLog::Message << "Using color mask TRUE, TRUE, TRUE, FALSE" << TestLog::EndMessage;
+	}
+
+	// Draw quad.
+
+	{
+		const Vec2		pt0						(-1.0f, -1.0f);
+		const Vec2		pt1						( 1.0f, -1.0f);
+		const Vec2		pt2						(-1.0f,  1.0f);
+		const Vec2		pt3						( 1.0f,  1.0f);
+		Vec4			quadColor				(1.0f, 0.0f, 0.0f, 1.0f);
+		float			alphaOrCoverageValue	= (float)m_currentIteration / (float)(m_numIterations-1);
+
+		if (m_type == CASETYPE_ALPHA_TO_COVERAGE)
+		{
+			log << TestLog::Message << "Drawing a red quad using alpha value " + de::floatToString(alphaOrCoverageValue, 2) << TestLog::EndMessage;
+			quadColor.w() = alphaOrCoverageValue;
+		}
+		else
+		{
+			DE_ASSERT(m_type == CASETYPE_SAMPLE_COVERAGE || m_type == CASETYPE_SAMPLE_COVERAGE_INVERTED);
+
+			bool	isInverted		= m_type == CASETYPE_SAMPLE_COVERAGE_INVERTED;
+			float	coverageValue	= isInverted ? 1.0f - alphaOrCoverageValue : alphaOrCoverageValue;
+			log << TestLog::Message << "Drawing a red quad using sample coverage value " + de::floatToString(coverageValue, 2) << (isInverted ? " (inverted)" : "") << TestLog::EndMessage;
+			GLU_CHECK_CALL(glSampleCoverage(coverageValue, isInverted ? GL_TRUE : GL_FALSE));
+		}
+
+		renderQuad(pt0, pt1, pt2, pt3, quadColor);
+	}
+
+	// Read ang log image.
+
+	readImage(renderedImg);
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	// Compute average red component in rendered image.
+
+	deInt32 sumRed = 0;
+
+	for (int y = 0; y < renderedImg.getHeight(); y++)
+	for (int x = 0; x < renderedImg.getWidth(); x++)
+		sumRed += renderedImg.getPixel(x, y).getRed();
+
+	log << TestLog::Message << "Average red color component: " << de::floatToString((float)sumRed / 255.0f / (float)numPixels, 2) << TestLog::EndMessage;
+
+	// Check if average color has decreased from previous frame's color.
+
+	if (sumRed < m_previousIterationColorSum)
+	{
+		log << TestLog::Message << "Failure: Current average red color component is lower than previous" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+
+	// Check if coverage mask is not all-zeros if alpha or coverage value is 0 (or 1, if inverted).
+
+	if (m_currentIteration == 0 && sumRed != 0)
+	{
+		log << TestLog::Message << "Failure: Image should be completely black" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+
+	if (m_currentIteration == m_numIterations-1 && sumRed != 0xff*numPixels)
+	{
+		log << TestLog::Message << "Failure: Image should be completely red" << TestLog::EndMessage;
+
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+
+	m_previousIterationColorSum = sumRed;
+
+	m_currentIteration++;
+
+	if (m_currentIteration >= m_numIterations)
+	{
+		log << TestLog::Message
+			<< "Success: Number of coverage mask bits set appears to be, on average, proportional to "
+			<< (m_type == CASETYPE_ALPHA_TO_COVERAGE ? "alpha" : m_type == CASETYPE_SAMPLE_COVERAGE ? "sample coverage value" : "inverted sample coverage value")
+			<< TestLog::EndMessage;
+
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests coverage mask generation constancy property.
+ *
+ * Tests that the coverage mask created by GL_SAMPLE_ALPHA_TO_COVERAGE or
+ * GL_SAMPLE_COVERAGE is constant at given pixel coordinates, with a given
+ * alpha component or coverage value, respectively. Draws two quads, with
+ * the second one fully overlapping the first one such that at any given
+ * pixel, both quads have the same alpha or coverage value. This way, if
+ * the constancy property is fulfilled, only the second quad should be
+ * visible.
+ *//*--------------------------------------------------------------------*/
+class MaskConstancyCase : public MultisampleCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_ALPHA_TO_COVERAGE = 0,		//!< Use only alpha-to-coverage.
+		CASETYPE_SAMPLE_COVERAGE,			//!< Use only sample coverage.
+		CASETYPE_SAMPLE_COVERAGE_INVERTED,	//!< Use only inverted sample coverage.
+		CASETYPE_BOTH,						//!< Use both alpha-to-coverage and sample coverage.
+		CASETYPE_BOTH_INVERTED,				//!< Use both alpha-to-coverage and inverted sample coverage.
+
+		CASETYPE_LAST
+	};
+
+					MaskConstancyCase			(Context& context, const char* name, const char* description, CaseType type);
+					~MaskConstancyCase			(void) {}
+
+	IterateResult	iterate						(void);
+
+protected:
+	int				getDesiredViewportSize		(void) const { return 256; }
+
+private:
+	const bool		m_isAlphaToCoverageCase;
+	const bool		m_isSampleCoverageCase;
+	const bool		m_isInvertedSampleCoverageCase;
+};
+
+MaskConstancyCase::MaskConstancyCase (Context& context, const char* name, const char* description, CaseType type)
+	: MultisampleCase					(context, name, description)
+	, m_isAlphaToCoverageCase			(type == CASETYPE_ALPHA_TO_COVERAGE			|| type == CASETYPE_BOTH						|| type == CASETYPE_BOTH_INVERTED)
+	, m_isSampleCoverageCase			(type == CASETYPE_SAMPLE_COVERAGE			|| type == CASETYPE_SAMPLE_COVERAGE_INVERTED	|| type == CASETYPE_BOTH			|| type == CASETYPE_BOTH_INVERTED)
+	, m_isInvertedSampleCoverageCase	(type == CASETYPE_SAMPLE_COVERAGE_INVERTED	|| type == CASETYPE_BOTH_INVERTED)
+{
+}
+
+MaskConstancyCase::IterateResult MaskConstancyCase::iterate (void)
+{
+	TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface	renderedImg		(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	if (m_isAlphaToCoverageCase)
+	{
+		GLU_CHECK_CALL(glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE));
+		GLU_CHECK_CALL(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE));
+		log << TestLog::Message << "GL_SAMPLE_ALPHA_TO_COVERAGE is enabled" << TestLog::EndMessage;
+		log << TestLog::Message << "Color mask is TRUE, TRUE, TRUE, FALSE" << TestLog::EndMessage;
+	}
+
+	if (m_isSampleCoverageCase)
+	{
+		GLU_CHECK_CALL(glEnable(GL_SAMPLE_COVERAGE));
+		log << TestLog::Message << "GL_SAMPLE_COVERAGE is enabled" << TestLog::EndMessage;
+	}
+
+	log << TestLog::Message
+		<< "Drawing several green quads, each fully overlapped by a red quad with the same "
+		<< (m_isAlphaToCoverageCase ? "alpha" : "")
+		<< (m_isAlphaToCoverageCase && m_isSampleCoverageCase ? " and " : "")
+		<< (m_isInvertedSampleCoverageCase ? "inverted " : "")
+		<< (m_isSampleCoverageCase ? "sample coverage" : "")
+		<< " values"
+		<< TestLog::EndMessage;
+
+	const int numQuadRowsCols = m_numSamples*4;
+
+	for (int row = 0; row < numQuadRowsCols; row++)
+	{
+		for (int col = 0; col < numQuadRowsCols; col++)
+		{
+			float		x0			= (float)(col+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float		x1			= (float)(col+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float		y0			= (float)(row+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float		y1			= (float)(row+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			const Vec4	baseGreen	(0.0f, 1.0f, 0.0f, 0.0f);
+			const Vec4	baseRed		(1.0f, 0.0f, 0.0f, 0.0f);
+			Vec4		alpha0		(0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)col / (float)(numQuadRowsCols-1) : 1.0f);
+			Vec4		alpha1		(0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)row / (float)(numQuadRowsCols-1) : 1.0f);
+
+			if (m_isSampleCoverageCase)
+			{
+				float value = (float)(row*numQuadRowsCols + col) / (float)(numQuadRowsCols*numQuadRowsCols-1);
+				GLU_CHECK_CALL(glSampleCoverage(m_isInvertedSampleCoverageCase ? 1.0f - value : value, m_isInvertedSampleCoverageCase ? GL_TRUE : GL_FALSE));
+			}
+
+			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseGreen + alpha0,	baseGreen + alpha1,	baseGreen + alpha0,	baseGreen + alpha1);
+			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseRed + alpha0,	baseRed + alpha1,	baseRed + alpha0,	baseRed + alpha1);
+		}
+	}
+
+	readImage(renderedImg);
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	for (int y = 0; y < renderedImg.getHeight(); y++)
+	for (int x = 0; x < renderedImg.getWidth(); x++)
+	{
+		if (renderedImg.getPixel(x, y).getGreen() > 0)
+		{
+			log << TestLog::Message << "Failure: Non-zero green color component detected - should have been completely overwritten by red quad" << TestLog::EndMessage;
+			m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+			return STOP;
+		}
+	}
+
+	log << TestLog::Message
+		<< "Success: Coverage mask appears to be constant at a given pixel coordinate with a given "
+		<< (m_isAlphaToCoverageCase ? "alpha" : "")
+		<< (m_isAlphaToCoverageCase && m_isSampleCoverageCase ? " and " : "")
+		<< (m_isSampleCoverageCase ? "coverage value" : "")
+		<< TestLog::EndMessage;
+
+	m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests coverage mask inversion validity.
+ *
+ * Tests that the coverage masks obtained by glSampleCoverage(..., GL_TRUE)
+ * and glSampleCoverage(..., GL_FALSE) are indeed each others' inverses.
+ * This is done by drawing a pattern, with varying coverage values,
+ * overlapped by a pattern that has inverted masks and is otherwise
+ * identical. The resulting image is compared to one obtained by drawing
+ * the same pattern but with all-ones coverage masks.
+ *//*--------------------------------------------------------------------*/
+class CoverageMaskInvertCase : public MultisampleCase
+{
+public:
+					CoverageMaskInvertCase		(Context& context, const char* name, const char* description);
+					~CoverageMaskInvertCase		(void) {}
+
+	IterateResult	iterate						(void);
+
+protected:
+	int				getDesiredViewportSize		(void) const { return 256; }
+
+private:
+	void			drawPattern					(bool invertSampleCoverage) const;
+};
+
+CoverageMaskInvertCase::CoverageMaskInvertCase (Context& context, const char* name, const char* description)
+	: MultisampleCase (context, name, description)
+{
+}
+
+void CoverageMaskInvertCase::drawPattern (bool invertSampleCoverage) const
+{
+	const int numTriangles = 25;
+	for (int i = 0; i < numTriangles; i++)
+	{
+		GLU_CHECK_CALL(glSampleCoverage((float)i / (float)(numTriangles-1), invertSampleCoverage ? GL_TRUE : GL_FALSE));
+
+		float angle0 = 2.0f*DE_PI * (float)i			/ (float)numTriangles;
+		float angle1 = 2.0f*DE_PI * (float)(i + 0.5f)	/ (float)numTriangles;
+
+		renderTriangle(Vec2(0.0f, 0.0f),
+					   Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f),
+					   Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f),
+					   Vec4(0.4f + (float)i/(float)numTriangles*0.6f,
+							0.5f + (float)i/(float)numTriangles*0.3f,
+							0.6f - (float)i/(float)numTriangles*0.5f,
+							0.7f - (float)i/(float)numTriangles*0.7f));
+	}
+}
+
+CoverageMaskInvertCase::IterateResult CoverageMaskInvertCase::iterate (void)
+{
+	TestLog&		log								= m_testCtx.getLog();
+	tcu::Surface	renderedImgNoSampleCoverage		(m_viewportSize, m_viewportSize);
+	tcu::Surface	renderedImgSampleCoverage		(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	GLU_CHECK_CALL(glEnable(GL_BLEND));
+	GLU_CHECK_CALL(glBlendEquation(GL_FUNC_ADD));
+	GLU_CHECK_CALL(glBlendFunc(GL_ONE, GL_ONE));
+	log << TestLog::Message << "Additive blending enabled in order to detect (erroneously) overlapping samples" << TestLog::EndMessage;
+
+	log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage;
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_COVERAGE disabled" << TestLog::EndMessage;
+	drawPattern(false);
+	readImage(renderedImgNoSampleCoverage);
+
+	log << TestLog::Image("RenderedImageNoSampleCoverage", "Rendered image with GL_SAMPLE_COVERAGE disabled", renderedImgNoSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage;
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+	GLU_CHECK_CALL(glEnable(GL_SAMPLE_COVERAGE));
+	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_COVERAGE enabled, using non-inverted masks" << TestLog::EndMessage;
+	drawPattern(false);
+	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_COVERAGE enabled, using same sample coverage values but inverted masks" << TestLog::EndMessage;
+	drawPattern(true);
+	readImage(renderedImgSampleCoverage);
+
+	log << TestLog::Image("RenderedImageSampleCoverage", "Rendered image with GL_SAMPLE_COVERAGE enabled", renderedImgSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	bool passed = tcu::pixelThresholdCompare(log,
+											 "CoverageVsNoCoverage",
+											 "Comparison of same pattern with GL_SAMPLE_COVERAGE disabled and enabled",
+											 renderedImgNoSampleCoverage,
+											 renderedImgSampleCoverage,
+											 tcu::RGBA(0),
+											 tcu::COMPARE_LOG_ON_ERROR);
+
+	if (passed)
+		log << TestLog::Message << "Success: The two images rendered are identical" << TestLog::EndMessage;
+
+	m_context.getTestContext().setTestResult(passed ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+											 passed ? "Passed"				: "Failed");
+
+	return STOP;
+}
+
+MultisampleTests::MultisampleTests (Context& context)
+	: TestCaseGroup(context, "multisample", "Multisampling tests")
+{
+}
+
+MultisampleTests::~MultisampleTests (void)
+{
+}
+
+void MultisampleTests::init (void)
+{
+	addChild(new PolygonNumSamplesCase		(m_context, "num_samples_polygon",			"Test sanity of the value of GL_SAMPLES, with polygons"));
+	addChild(new LineNumSamplesCase			(m_context, "num_samples_line",				"Test sanity of the value of GL_SAMPLES, with lines"));
+	addChild(new CommonEdgeCase				(m_context, "common_edge_small_quads",		"Test polygons' common edges with small quads",						CommonEdgeCase::CASETYPE_SMALL_QUADS));
+	addChild(new CommonEdgeCase				(m_context, "common_edge_big_quad",			"Test polygons' common edges with bigger-than-viewport quads",		CommonEdgeCase::CASETYPE_BIGGER_THAN_VIEWPORT_QUAD));
+	addChild(new CommonEdgeCase				(m_context, "common_edge_viewport_quad",	"Test polygons' common edges with exactly viewport-sized quads",	CommonEdgeCase::CASETYPE_FIT_VIEWPORT_QUAD));
+	addChild(new SampleDepthCase			(m_context, "depth",						"Test that depth values are per-sample"));
+	addChild(new SampleStencilCase			(m_context, "stencil",						"Test that stencil values are per-sample"));
+	addChild(new CoverageMaskInvertCase		(m_context, "sample_coverage_invert",		"Test that non-inverted and inverted sample coverage masks are each other's negations"));
+
+	addChild(new MaskProportionalityCase(m_context, "proportionality_alpha_to_coverage",			"Test the proportionality property of GL_SAMPLE_ALPHA_TO_COVERAGE",			MaskProportionalityCase::CASETYPE_ALPHA_TO_COVERAGE));
+	addChild(new MaskProportionalityCase(m_context, "proportionality_sample_coverage",				"Test the proportionality property of GL_SAMPLE_COVERAGE",					MaskProportionalityCase::CASETYPE_SAMPLE_COVERAGE));
+	addChild(new MaskProportionalityCase(m_context, "proportionality_sample_coverage_inverted",		"Test the proportionality property of inverted-mask GL_SAMPLE_COVERAGE",	MaskProportionalityCase::CASETYPE_SAMPLE_COVERAGE_INVERTED));
+
+	addChild(new MaskConstancyCase(m_context, "constancy_alpha_to_coverage",			"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE",											MaskConstancyCase::CASETYPE_ALPHA_TO_COVERAGE));
+	addChild(new MaskConstancyCase(m_context, "constancy_sample_coverage",				"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_COVERAGE",													MaskConstancyCase::CASETYPE_SAMPLE_COVERAGE));
+	addChild(new MaskConstancyCase(m_context, "constancy_sample_coverage_inverted",		"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using inverted-mask GL_SAMPLE_COVERAGE",									MaskConstancyCase::CASETYPE_SAMPLE_COVERAGE_INVERTED));
+	addChild(new MaskConstancyCase(m_context, "constancy_both",							"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE and GL_SAMPLE_COVERAGE",					MaskConstancyCase::CASETYPE_BOTH));
+	addChild(new MaskConstancyCase(m_context, "constancy_both_inverted",				"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE and inverted-mask GL_SAMPLE_COVERAGE",	MaskConstancyCase::CASETYPE_BOTH_INVERTED));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fMultisampleTests.hpp b/modules/gles2/functional/es2fMultisampleTests.hpp
new file mode 100644
index 0000000..0cd2c36
--- /dev/null
+++ b/modules/gles2/functional/es2fMultisampleTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FMULTISAMPLETESTS_HPP
+#define _ES2FMULTISAMPLETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisampling tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class MultisampleTests : public TestCaseGroup
+{
+public:
+						MultisampleTests	(Context& context);
+						~MultisampleTests	(void);
+
+	void				init				(void);
+
+private:
+						MultisampleTests	(const MultisampleTests& other);
+	MultisampleTests&	operator=			(const MultisampleTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FMULTISAMPLETESTS_HPP
diff --git a/modules/gles2/functional/es2fNegativeBufferApiTests.cpp b/modules/gles2/functional/es2fNegativeBufferApiTests.cpp
new file mode 100644
index 0000000..911ea33
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeBufferApiTests.cpp
@@ -0,0 +1,426 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Buffer API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fNegativeBufferApiTests.hpp"
+#include "es2fApiCase.hpp"
+
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+
+NegativeBufferApiTests::NegativeBufferApiTests (Context& context)
+	: TestCaseGroup(context, "buffer", "Negative Buffer API Cases")
+{
+}
+
+NegativeBufferApiTests::~NegativeBufferApiTests (void)
+{
+}
+
+void NegativeBufferApiTests::init (void)
+{
+	ES2F_ADD_API_CASE(bind_buffer, "Invalid glBindBuffer() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the allowable values.");
+			glBindBuffer(-1, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(delete_buffers, "Invalid glDeleteBuffers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteBuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(gen_buffers, "Invalid glGenBuffers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glGenBuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(buffer_data, "Invalid glBufferData() usage",
+		{
+			GLuint buffer;
+			glGenBuffers(1, &buffer);
+			glBindBuffer(GL_ARRAY_BUFFER, buffer);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER.");
+			glBufferData(-1, 0, NULL, GL_STREAM_DRAW);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if usage is not GL_STREAM_DRAW, GL_STATIC_DRAW, or GL_DYNAMIC_DRAW.");
+			glBufferData(GL_ARRAY_BUFFER, 0, NULL, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if size is negative.");
+			glBufferData(GL_ARRAY_BUFFER, -1, NULL, GL_STREAM_DRAW);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &buffer);
+		});
+	ES2F_ADD_API_CASE(buffer_sub_data, "Invalid glBufferSubData() usage",
+		{
+			GLuint buffer;
+			glGenBuffers(1, &buffer);
+			glBindBuffer(GL_ARRAY_BUFFER, buffer);
+			glBufferData(GL_ARRAY_BUFFER, 10, 0, GL_STREAM_DRAW);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER.");
+			glBufferSubData(-1, 1, 1, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			glBufferSubData(GL_ARRAY_BUFFER, 1, 1, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &buffer);
+		});
+	ES2F_ADD_API_CASE(buffer_sub_data_size_offset, "Invalid glBufferSubData() usage",
+		{
+			GLuint buffer;
+			glGenBuffers(1, &buffer);
+			glBindBuffer(GL_ARRAY_BUFFER, buffer);
+			glBufferData(GL_ARRAY_BUFFER, 10, 0, GL_STREAM_DRAW);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if offset or size is negative, or if together they define a region of memory that extends beyond the buffer object's allocated data store.");
+			glBufferSubData(GL_ARRAY_BUFFER, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glBufferSubData(GL_ARRAY_BUFFER, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glBufferSubData(GL_ARRAY_BUFFER, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glBufferSubData(GL_ARRAY_BUFFER, 15, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glBufferSubData(GL_ARRAY_BUFFER, 1, 15, 0);
+			expectError(GL_INVALID_VALUE);
+			glBufferSubData(GL_ARRAY_BUFFER, 8, 8, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &buffer);
+		});
+	ES2F_ADD_API_CASE(clear, "Invalid glClear() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if any bit other than the three defined bits is set in mask.");
+			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+			expectError(GL_NO_ERROR);
+			glClear(0x00000200);
+			expectError(GL_INVALID_VALUE);
+			glClear(0x00001000);
+			expectError(GL_INVALID_VALUE);
+			glClear(0x00000010);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(read_pixels, "Invalid glReadPixels() usage",
+		{
+			std::vector<GLubyte> ubyteData(4);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the combination of format and type is unsupported.");
+			glReadPixels(0, 0, 1, 1, GL_LUMINANCE_ALPHA, GL_UNSIGNED_SHORT_4_4_4_4, &ubyteData[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either width or height is negative.");
+			glReadPixels(0, 0, -1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+			expectError(GL_INVALID_VALUE);
+			glReadPixels(0, 0, 1, -1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+			expectError(GL_INVALID_VALUE);
+			glReadPixels(0, 0, -1, -1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(read_pixels_format_mismatch, "Invalid glReadPixels() usage",
+		{
+			std::vector<GLubyte> ubyteData(4);
+			std::vector<GLushort> ushortData(4);
+
+			m_log << TestLog::Section("", "Unsupported combinations of format and type will generate an INVALID_OPERATION error.");
+			glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_SHORT_5_6_5, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			glReadPixels(0, 0, 1, 1, GL_ALPHA, GL_UNSIGNED_SHORT_5_6_5, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			glReadPixels(0, 0, 1, 1, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			glReadPixels(0, 0, 1, 1, GL_ALPHA, GL_UNSIGNED_SHORT_4_4_4_4, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			glReadPixels(0, 0, 1, 1, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			glReadPixels(0, 0, 1, 1, GL_ALPHA, GL_UNSIGNED_SHORT_5_5_5_1, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_RGBA/GL_UNSIGNED_BYTE is always accepted and the other acceptable pair can be discovered by querying GL_IMPLEMENTATION_COLOR_READ_FORMAT and GL_IMPLEMENTATION_COLOR_READ_TYPE.");
+			glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+			expectError(GL_NO_ERROR);
+			GLint readFormat;
+			GLint readType;
+			glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
+			glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
+			glReadPixels(0, 0, 1, 1, readFormat, readType, &ubyteData[0]);
+			expectError(GL_NO_ERROR);
+			m_log << TestLog::EndSection;
+		});
+
+	// Framebuffer Objects
+
+	ES2F_ADD_API_CASE(bind_framebuffer, "Invalid glBindFramebuffer() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER.");
+			glBindFramebuffer(-1, 0);
+			expectError(GL_INVALID_ENUM);
+			glBindFramebuffer(GL_RENDERBUFFER, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(bind_renderbuffer, "Invalid glBindRenderbuffer() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+			glBindRenderbuffer(-1, 0);
+			expectError(GL_INVALID_ENUM);
+			glBindRenderbuffer(GL_FRAMEBUFFER, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(check_framebuffer_status, "Invalid glCheckFramebufferStatus() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER.");
+			glCheckFramebufferStatus(-1);
+			expectError(GL_INVALID_ENUM);
+			glCheckFramebufferStatus(GL_RENDERBUFFER);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(gen_framebuffers, "Invalid glGenFramebuffers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glGenFramebuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(gen_renderbuffers, "Invalid glGenRenderbuffers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glGenRenderbuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(delete_framebuffers, "Invalid glDeleteFramebuffers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteFramebuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(delete_renderbuffers, "Invalid glDeleteRenderbuffers() usage",
+		{;
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteRenderbuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(framebuffer_renderbuffer, "Invalid glFramebufferRenderbuffer() usage",
+		{
+			GLuint fbo;
+			GLuint rbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glGenRenderbuffers(1, &rbo);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER.");
+			glFramebufferRenderbuffer(-1, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if attachment is not an accepted attachment point.");
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, -1, GL_RENDERBUFFER, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if renderbuffertarget is not GL_RENDERBUFFER.");
+			glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, -1, rbo);
+			expectError(GL_INVALID_ENUM);
+			glBindRenderbuffer(GL_RENDERBUFFER, 0);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if renderbuffer is neither 0 nor the name of an existing renderbuffer object.");
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, -1);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the default framebuffer object name 0 is bound.");
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteRenderbuffers(1, &rbo);
+			glDeleteFramebuffers(1, &fbo);
+		});
+	ES2F_ADD_API_CASE(framebuffer_texture2d, "Invalid glFramebufferTexture2D() usage",
+		{
+			GLuint fbo;
+			GLuint tex2D;
+			GLuint texCube;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glGenTextures(1, &tex2D);
+			glBindTexture(GL_TEXTURE_2D, tex2D);
+			glGenTextures(1, &texCube);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+			expectError(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER.");
+			glFramebufferTexture2D(-1, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if textarget is not an accepted texture target.");
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, -1, tex2D, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if attachment is not an accepted attachment point.");
+			glFramebufferTexture2D(GL_FRAMEBUFFER, -1, GL_TEXTURE_2D, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is not 0.");
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 3);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if texture is neither 0 nor the name of an existing texture object.");
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, -1, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if texture is the name of an existing two-dimensional texture object but textarget is not GL_TEXTURE_2D.");
+
+			expectError(GL_NO_ERROR);
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, tex2D, 0);
+			expectError(GL_INVALID_OPERATION);
+			glDeleteTextures(1, &tex2D);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if texture is the name of an existing cube map texture object but textarget is GL_TEXTURE_2D.");
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texCube, 0);
+			expectError(GL_INVALID_OPERATION);
+			glDeleteTextures(1, &texCube);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the default framebuffer object name 0 is bound.");
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteFramebuffers(1, &fbo);
+		});
+	ES2F_ADD_API_CASE(renderbuffer_storage, "Invalid glRenderbufferStorage() usage",
+		{
+			GLuint rbo;
+			glGenRenderbuffers(1, &rbo);
+			glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+			glRenderbufferStorage(-1, GL_RGBA4, 1, 1);
+			expectError(GL_INVALID_ENUM);
+			glRenderbufferStorage(GL_FRAMEBUFFER, GL_RGBA4, 1, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if internalformat is not an accepted format.");
+			glRenderbufferStorage(GL_RENDERBUFFER, -1, 1, 1);
+			expectError(GL_INVALID_ENUM);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, 1, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than zero.");
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, -1, 1);
+			expectError(GL_INVALID_VALUE);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 1, -1);
+			expectError(GL_INVALID_VALUE);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, -1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_RENDERBUFFER_SIZE.");
+			GLint maxSize;
+			glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxSize);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 1, maxSize+1);
+			expectError(GL_INVALID_VALUE);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, maxSize+1, 1);
+			expectError(GL_INVALID_VALUE);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, maxSize+1, maxSize+1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the reserved renderbuffer object name 0 is bound.");
+			glBindRenderbuffer(GL_RENDERBUFFER, 0);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 1, 1);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteRenderbuffers(1, &rbo);
+		});
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fNegativeBufferApiTests.hpp b/modules/gles2/functional/es2fNegativeBufferApiTests.hpp
new file mode 100644
index 0000000..7967f19
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeBufferApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FNEGATIVEBUFFERAPITESTS_HPP
+#define _ES2FNEGATIVEBUFFERAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Buffer API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class NegativeBufferApiTests : public TestCaseGroup
+{
+public:
+									NegativeBufferApiTests		(Context& context);
+									~NegativeBufferApiTests		(void);
+
+	void							init						(void);
+
+private:
+									NegativeBufferApiTests		(const NegativeBufferApiTests& other);
+	NegativeBufferApiTests&			operator=					(const NegativeBufferApiTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FNEGATIVEBUFFERAPITESTS_HPP
diff --git a/modules/gles2/functional/es2fNegativeFragmentApiTests.cpp b/modules/gles2/functional/es2fNegativeFragmentApiTests.cpp
new file mode 100644
index 0000000..46dcfa1
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeFragmentApiTests.cpp
@@ -0,0 +1,210 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Fragment Pipe API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fNegativeFragmentApiTests.hpp"
+#include "es2fApiCase.hpp"
+
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+
+NegativeFragmentApiTests::NegativeFragmentApiTests (Context& context)
+	: TestCaseGroup(context, "fragment", "Negative Fragment API Cases")
+{
+}
+
+NegativeFragmentApiTests::~NegativeFragmentApiTests (void)
+{
+}
+
+void NegativeFragmentApiTests::init (void)
+{
+	ES2F_ADD_API_CASE(scissor, "Invalid glScissor() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either width or height is negative.");
+			glScissor(0, 0, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glScissor(0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glScissor(0, 0, -1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(depth_func, "Invalid glDepthFunc() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if func is not an accepted value.");
+			glDepthFunc(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(viewport, "Invalid glViewport() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either width or height is negative.");
+			glViewport(0, 0, -1, 1);
+			expectError(GL_INVALID_VALUE);
+			glViewport(0, 0, 1, -1);
+			expectError(GL_INVALID_VALUE);
+			glViewport(0, 0, -1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+
+	// Stencil functions
+
+	ES2F_ADD_API_CASE(stencil_func, "Invalid glStencilFunc() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if func is not one of the eight accepted values.");
+			glStencilFunc(-1, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(stencil_func_separate, "Invalid glStencilFuncSeparate() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if face is not GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.");
+			glStencilFuncSeparate(-1, GL_NEVER, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if func is not one of the eight accepted values.");
+			glStencilFuncSeparate(GL_FRONT, -1, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(stencil_op, "Invalid glStencilOp() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if sfail, dpfail, or dppass is any value other than the eight defined symbolic constant values.");
+			glStencilOp(-1, GL_ZERO, GL_REPLACE);
+			expectError(GL_INVALID_ENUM);
+			glStencilOp(GL_KEEP, -1, GL_REPLACE);
+			expectError(GL_INVALID_ENUM);
+			glStencilOp(GL_KEEP, GL_ZERO, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(stencil_op_separate, "Invalid glStencilOpSeparate() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if face is any value other than GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.");
+			glStencilOpSeparate(-1, GL_KEEP, GL_ZERO, GL_REPLACE);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if sfail, dpfail, or dppass is any value other than the eight defined symbolic constant values.");
+			glStencilOpSeparate(GL_FRONT, -1, GL_ZERO, GL_REPLACE);
+			expectError(GL_INVALID_ENUM);
+			glStencilOpSeparate(GL_FRONT, GL_KEEP, -1, GL_REPLACE);
+			expectError(GL_INVALID_ENUM);
+			glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_ZERO, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(stencil_mask_separate, "Invalid glStencilMaskSeparate() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if face is not GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.");
+			glStencilMaskSeparate(-1, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Blend functions
+
+	ES2F_ADD_API_CASE(blend_equation, "Invalid glBlendEquation() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not one of GL_FUNC_ADD, GL_FUNC_SUBTRACT, or GL_FUNC_REVERSE_SUBTRACT.");
+			glBlendEquation(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(blend_equation_separate, "Invalid glBlendEquationSeparate() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if either modeRGB or modeAlpha is not one of GL_FUNC_ADD, GL_FUNC_SUBTRACT, or GL_FUNC_REVERSE_SUBTRACT.");
+			glBlendEquationSeparate(-1, GL_FUNC_ADD);
+			expectError(GL_INVALID_ENUM);
+			glBlendEquationSeparate(GL_FUNC_ADD, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(blend_func_separate, "Invalid glBlendFuncSeparate() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if srcRGB, dstRGB, srcAlpha, or dstAlpha is not an accepted value.");
+			glBlendFuncSeparate(-1, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
+			expectError(GL_INVALID_ENUM);
+			glBlendFuncSeparate(GL_ZERO, -1, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
+			expectError(GL_INVALID_ENUM);
+			glBlendFuncSeparate(GL_ZERO, GL_ONE, -1, GL_ONE_MINUS_SRC_COLOR);
+			expectError(GL_INVALID_ENUM);
+			glBlendFuncSeparate(GL_ZERO, GL_ONE, GL_SRC_COLOR, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(blend_func, "Invalid glBlendFunc() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if either sfactor or dfactor is not an accepted value.");
+			glBlendFunc(-1, GL_ONE);
+			expectError(GL_INVALID_ENUM);
+			glBlendFunc(GL_ONE, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Rasterization API functions
+
+	ES2F_ADD_API_CASE(cull_face, "Invalid glCullFace() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glCullFace(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	ES2F_ADD_API_CASE(front_face, "Invalid glFrontFace() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glFrontFace(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	ES2F_ADD_API_CASE(line_width, "Invalid glLineWidth() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width is less than or equal to 0.");
+			glLineWidth(0);
+			expectError(GL_INVALID_VALUE);
+			glLineWidth(-1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fNegativeFragmentApiTests.hpp b/modules/gles2/functional/es2fNegativeFragmentApiTests.hpp
new file mode 100644
index 0000000..5bb91e9
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeFragmentApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FNEGATIVEFRAGMENTAPITESTS_HPP
+#define _ES2FNEGATIVEFRAGMENTAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Fragment Pipe API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class NegativeFragmentApiTests : public TestCaseGroup
+{
+public:
+								NegativeFragmentApiTests		(Context& context);
+								~NegativeFragmentApiTests		(void);
+
+	void						init							(void);
+
+private:
+								NegativeFragmentApiTests		(const NegativeFragmentApiTests& other);
+	NegativeFragmentApiTests&	operator=						(const NegativeFragmentApiTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FNEGATIVEFRAGMENTAPITESTS_HPP
diff --git a/modules/gles2/functional/es2fNegativeShaderApiTests.cpp b/modules/gles2/functional/es2fNegativeShaderApiTests.cpp
new file mode 100644
index 0000000..a273bca
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeShaderApiTests.cpp
@@ -0,0 +1,1004 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Shader API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fNegativeShaderApiTests.hpp"
+#include "es2fApiCase.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluContextInfo.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+static const char* vertexShaderSource	= "void main (void) { gl_Position = vec4(0.0); }\n\0";
+static const char* fragmentShaderSource	= "void main (void) { gl_FragColor = vec4(0.0); }\n\0";
+
+static const char* uniformTestVertSource	=	"uniform mediump vec4 vTest;\n"
+												"uniform mediump mat4 vMatrix;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vMatrix * vTest;\n"
+												"}\n\0";
+static const char* uniformTestFragSource	=	"uniform mediump ivec4 fTest;\n"
+												"uniform sampler2D fSampler;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_FragColor.xy = vec4(fTest).xy;\n"
+												"	gl_FragColor.zw = texture2D(fSampler, vec2(0.0, 0.0)).zw;\n"
+												"}\n\0";
+
+using tcu::TestLog;
+
+NegativeShaderApiTests::NegativeShaderApiTests (Context& context)
+	: TestCaseGroup(context, "shader", "Negative Shader API Cases")
+{
+}
+
+NegativeShaderApiTests::~NegativeShaderApiTests (void)
+{
+}
+
+void NegativeShaderApiTests::init (void)
+{
+	ES2F_ADD_API_CASE(create_shader, "Invalid glCreateShader() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if shaderType is not an accepted value.");
+			glCreateShader(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(shader_source, "Invalid glShaderSource() usage",
+		{
+			GLboolean shaderCompilerSupported;
+			glGetBooleanv(GL_SHADER_COMPILER, &shaderCompilerSupported);
+			if (!shaderCompilerSupported)
+				m_log << TestLog::Message << "// Shader compiler not supported, always expect GL_INVALID_OPERATION" << TestLog::EndMessage;
+			else
+				m_log << TestLog::Message << "// Shader compiler supported" << TestLog::EndMessage;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glShaderSource(1, 0, 0, 0);
+			expectError(shaderCompilerSupported ? GL_INVALID_VALUE : GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if count is less than 0.");
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+			glShaderSource(shader, -1, 0, 0);
+			expectError(shaderCompilerSupported ? GL_INVALID_VALUE : GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			GLuint program = glCreateProgram();
+			glShaderSource(program, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+	ES2F_ADD_API_CASE(compile_shader, "Invalid glCompileShader() usage",
+		{
+			GLboolean shaderCompilerSupported;
+			glGetBooleanv(GL_SHADER_COMPILER, &shaderCompilerSupported);
+			if (!shaderCompilerSupported)
+				m_log << TestLog::Message << "// Shader compiler not supported, always expect GL_INVALID_OPERATION" << TestLog::EndMessage;
+			else
+				m_log << TestLog::Message << "// Shader compiler supported" << TestLog::EndMessage;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glCompileShader(9);
+			expectError(shaderCompilerSupported ? GL_INVALID_VALUE : GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			GLuint program = glCreateProgram();
+			glCompileShader(program);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+		});
+	ES2F_ADD_API_CASE(delete_shader, "Invalid glDeleteShader() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glDeleteShader(9);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(shader_binary, "Invalid glShaderBinary() usage",
+		{
+			std::vector<deInt32> binaryFormats;
+			getSupportedExtensions(GL_NUM_SHADER_BINARY_FORMATS, GL_SHADER_BINARY_FORMATS, binaryFormats);
+			deBool shaderBinarySupported = !binaryFormats.empty();
+			if (!shaderBinarySupported)
+				m_log << TestLog::Message << "// Shader binaries not supported." << TestLog::EndMessage;
+			else
+				m_log << TestLog::Message << "// Shader binaries supported" << TestLog::EndMessage;
+
+			GLuint shaders[2];
+
+			shaders[0] = glCreateShader(GL_VERTEX_SHADER);
+			shaders[1] = glCreateShader(GL_VERTEX_SHADER);
+			GLuint program = glCreateProgram();
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if binaryformat is not a supported format returned in GL_SHADER_BINARY_FORMATS.");
+			glShaderBinary(1, &shaders[0], -1, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			if (shaderBinarySupported)
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if any value in shaders is not a value generated by OpenGL.");
+				shaders[0] = 137;
+				glShaderBinary(1, &shaders[0], binaryFormats[0], 0, 0);
+				expectError(shaderBinarySupported ? GL_INVALID_VALUE : GL_INVALID_OPERATION);
+				m_log << TestLog::EndSection;
+
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n or length is negative.");
+				shaders[0] = glCreateShader(GL_VERTEX_SHADER);
+				glShaderBinary(-1, &shaders[0], binaryFormats[0], 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glShaderBinary(1, &shaders[0], binaryFormats[0], 0, -1);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+
+				m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if any value in shaders is not a shader object.");
+				glShaderBinary(1, &program, binaryFormats[0], 0, 0);
+				expectError(GL_INVALID_OPERATION);
+				m_log << TestLog::EndSection;
+
+				m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if there is more than one vertex shader object handle or more than one fragment shader object handle in shaders.");
+				shaders[0] = glCreateShader(GL_VERTEX_SHADER);
+				shaders[1] = glCreateShader(GL_VERTEX_SHADER);
+				glShaderBinary(2, &shaders[0], binaryFormats[0], 0, 1);
+				expectError(GL_INVALID_OPERATION);
+				m_log << TestLog::EndSection;
+			}
+
+			glDeleteShader(shaders[0]);
+			glDeleteShader(shaders[1]);
+			glDeleteProgram(program);
+
+			// \note: The format of the data pointed to by binary does not match binaryformat.
+		});
+	ES2F_ADD_API_CASE(attach_shader, "Invalid glAttachShader() usage",
+		{
+			GLuint shader1 = glCreateShader(GL_VERTEX_SHADER);
+			GLuint shader2 = glCreateShader(GL_VERTEX_SHADER);
+			GLuint program = glCreateProgram();
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glAttachShader(shader1, shader1);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			glAttachShader(program, program);
+			expectError(GL_INVALID_OPERATION);
+			glAttachShader(shader1, program);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either program or shader is not a value generated by OpenGL.");
+			glAttachShader(program, -1);
+			expectError(GL_INVALID_VALUE);
+			glAttachShader(-1, shader1);
+			expectError(GL_INVALID_VALUE);
+			glAttachShader(-1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is already attached to program, or if another shader object of the same type as shader is already attached to program.");
+			glAttachShader(program, shader1);
+			expectError(GL_NO_ERROR);
+			glAttachShader(program, shader1);
+			expectError(GL_INVALID_OPERATION);
+			glAttachShader(program, shader2);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader1);
+			glDeleteShader(shader2);
+		});
+	ES2F_ADD_API_CASE(detach_shader, "Invalid glDetachShader() usage",
+		{
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+			GLuint program = glCreateProgram();
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either program or shader is not a value generated by OpenGL.");
+			glDetachShader(-1, shader);
+			expectError(GL_INVALID_VALUE);
+			glDetachShader(program, -1);
+			expectError(GL_INVALID_VALUE);
+			glDetachShader(-1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glDetachShader(shader, shader);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			glDetachShader(program, program);
+			expectError(GL_INVALID_OPERATION);
+			glDetachShader(shader, program);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not attached to program.");
+			glDetachShader(program, shader);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+	ES2F_ADD_API_CASE(link_program, "Invalid glLinkProgram() usage",
+		{
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glLinkProgram(-1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glLinkProgram(shader);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+		});
+	ES2F_ADD_API_CASE(use_program, "Invalid glUseProgram() usage",
+		{
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is neither 0 nor a value generated by OpenGL.");
+			glUseProgram(-1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glUseProgram(shader);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteShader(shader);
+		});
+	ES2F_ADD_API_CASE(delete_program, "Invalid glDeleteProgram() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glDeleteProgram(-1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(get_active_attrib, "Invalid glGetActiveAttrib() usage",
+		{
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetActiveAttrib(-1, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetActiveAttrib(shader, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to the number of active attribute variables in program.");
+			glGetActiveAttrib(program.getProgram(), 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if bufSize is less than 0.");
+			glGetActiveAttrib(program.getProgram(), 0, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteShader(shader);
+		});
+	ES2F_ADD_API_CASE(get_attrib_location, "Invalid glGetAttribLocation() usage",
+		{
+			GLuint programEmpty = glCreateProgram();
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+			glBindAttribLocation(programEmpty, 0, "test");
+			glGetAttribLocation(programEmpty, "test");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a program or shader object.");
+			glUseProgram(program.getProgram());
+			glBindAttribLocation(program.getProgram(), 0, "test");
+			expectError(GL_NO_ERROR);
+			glGetAttribLocation(program.getProgram(), "test");
+			expectError(GL_NO_ERROR);
+			glGetAttribLocation(-2, "test");
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetAttribLocation(shader, "test");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteShader(shader);
+			glDeleteProgram(programEmpty);
+		});
+	ES2F_ADD_API_CASE(get_uniform_location, "Invalid glGetUniformLocation() usage",
+		{
+			GLuint programEmpty = glCreateProgram();
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+			glGetUniformLocation(programEmpty, "test");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glUseProgram(program.getProgram());
+			glGetUniformLocation(-2, "test");
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetAttribLocation(shader, "test");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteProgram(programEmpty);
+			glDeleteShader(shader);
+		});
+	ES2F_ADD_API_CASE(bind_attrib_location, "Invalid glBindAttribLocation() usage",
+		{
+			GLuint program = glCreateProgram();
+			GLuint maxIndex = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			glBindAttribLocation(program, maxIndex, "test");
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if name starts with the reserved prefix \"gl_\".");
+			glBindAttribLocation(program, maxIndex-1, "gl_test");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glBindAttribLocation(-1, maxIndex-1, "test");
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glBindAttribLocation(shader, maxIndex-1, "test");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+	ES2F_ADD_API_CASE(get_active_uniform, "Invalid glGetActiveUniform() usage",
+		{
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetActiveUniform(-1, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetActiveUniform(shader, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to the number of active attribute variables in program.");
+			glUseProgram(program.getProgram());
+			glGetActiveUniform(program.getProgram(), 5, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if bufSize is less than 0.");
+			glGetActiveUniform(program.getProgram(), 0, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteShader(shader);
+		});
+	ES2F_ADD_API_CASE(validate_program, "Invalid glValidateProgram() usage",
+		{
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glValidateProgram(-1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glValidateProgram(shader);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+		});
+
+	ES2F_ADD_API_CASE(release_shader_compiler, "Invalid glReleaseShaderCompiler() usage",
+		{
+			GLboolean shaderCompilerSupported;
+			glGetBooleanv(GL_SHADER_COMPILER, &shaderCompilerSupported);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a shader compiler is not supported.");
+			glReleaseShaderCompiler();
+			expectError(shaderCompilerSupported ? GL_NONE : GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+		});
+
+	// glUniform*f
+
+	ES2F_ADD_API_CASE(uniformf_invalid_program, "Invalid glUniform{1234}f() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniform1f(-1, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2f(-1, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3f(-1, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4f(-1, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(uniformf_incompatible_type, "Invalid glUniform{1234}f() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+			GLint vUnif		= glGetUniformLocation(program.getProgram(), "vTest");		// vec4
+			GLint fUnif		= glGetUniformLocation(program.getProgram(), "fTest");		// ivec4
+			GLint fSampler	= glGetUniformLocation(program.getProgram(), "fSampler");	// sampler2D
+
+			if (vUnif == -1 || fUnif == -1 || fSampler == -1)
+				{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniform1f(vUnif, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2f(vUnif, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3f(vUnif, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4f(vUnif, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if one of the floating-point variants of this function is used to load a uniform variable of type int, ivec2, ivec3, or ivec4.");
+			glUseProgram(program.getProgram());
+			glUniform4f(fUnif, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than glUniform1i and glUniform1iv.");
+			glUseProgram(program.getProgram());
+			glUniform1f(fSampler, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(uniformf_invalid_location, "Invalid glUniform{1234}f() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniform1f(-2, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2f(-2, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3f(-2, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4f(-2, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniform1f(-1, 0.0f);
+			expectError(GL_NO_ERROR);
+			glUniform2f(-1, 0.0f, 0.0f);
+			expectError(GL_NO_ERROR);
+			glUniform3f(-1, 0.0f, 0.0f, 0.0f);
+			expectError(GL_NO_ERROR);
+			glUniform4f(-1, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+	// glUniform*fv
+
+	ES2F_ADD_API_CASE(uniformfv_invalid_program, "Invalid glUniform{1234}fv() usage",
+		{
+			std::vector<GLfloat> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniform1fv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2fv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3fv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4fv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(uniformfv_incompatible_type, "Invalid glUniform{1234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+			GLint vUnif		= glGetUniformLocation(program.getProgram(), "vTest");		// vec4
+			GLint fUnif		= glGetUniformLocation(program.getProgram(), "fTest");		// ivec4
+			GLint fSampler	= glGetUniformLocation(program.getProgram(), "fSampler");	// sampler2D
+
+			if (vUnif == -1 || fUnif == -1 || fSampler == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLfloat> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniform1fv(vUnif, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2fv(vUnif, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3fv(vUnif, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4fv(vUnif, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if one of the floating-point variants of this function is used to load a uniform variable of type int, ivec2, ivec3, or ivec4.");
+			glUseProgram(program.getProgram());
+			glUniform4fv(fUnif, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than glUniform1i and glUniform1iv.");
+			glUseProgram(program.getProgram());
+			glUniform1fv(fSampler, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(uniformfv_invalid_location, "Invalid glUniform{1234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			std::vector<GLfloat> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniform1fv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2fv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3fv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4fv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniform1fv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform2fv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform3fv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform4fv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(uniformfv_invalid_count, "Invalid glUniform{1234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+			GLint vUnif		= glGetUniformLocation(program.getProgram(), "vTest");		// vec4
+
+			if (vUnif == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLfloat> data(8);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+			glUseProgram(program.getProgram());
+			glUniform1fv(vUnif, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2fv(vUnif, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3fv(vUnif, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4fv(vUnif, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+	// glUniform*i
+
+	ES2F_ADD_API_CASE(uniformi_invalid_program, "Invalid glUniform{1234}i() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniform1i(-1, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2i(-1, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3i(-1, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4i(-1, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(uniformi_incompatible_type, "Invalid glUniform{1234}i() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+			GLint vUnif		= glGetUniformLocation(program.getProgram(), "vTest");		// vec4
+			GLint fUnif		= glGetUniformLocation(program.getProgram(), "fTest");		// ivec4
+			GLint fSampler	= glGetUniformLocation(program.getProgram(), "fSampler");	// sampler2D
+
+			if (vUnif == -1 || fUnif == -1 || fSampler == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniform1i(fUnif, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2i(fUnif, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3i(fUnif, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4i(fUnif, 0, 0, 0, 0);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if one of the integer variants of this function is used to load a uniform variable of type float, vec2, vec3, or vec4.");
+			glUseProgram(program.getProgram());
+			glUniform4i(vUnif, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(uniformi_invalid_location, "Invalid glUniform{1234}i() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniform1i(-2, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2i(-2, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3i(-2, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4i(-2, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniform1i(-1, 0);
+			expectError(GL_NO_ERROR);
+			glUniform2i(-1, 0, 0);
+			expectError(GL_NO_ERROR);
+			glUniform3i(-1, 0, 0, 0);
+			expectError(GL_NO_ERROR);
+			glUniform4i(-1, 0, 0, 0, 0);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+	// glUniform*iv
+
+	ES2F_ADD_API_CASE(uniformiv_invalid_program, "Invalid glUniform{1234}iv() usage",
+		{
+			std::vector<GLint> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniform1iv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2iv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3iv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4iv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(uniformiv_incompatible_type, "Invalid glUniform{1234}iv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+			GLint vUnif		= glGetUniformLocation(program.getProgram(), "vTest");		// vec4
+			GLint fUnif		= glGetUniformLocation(program.getProgram(), "fTest");		// ivec4
+			GLint fSampler	= glGetUniformLocation(program.getProgram(), "fSampler");	// sampler2D
+
+			if (vUnif == -1 || fUnif == -1 || fSampler == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLint> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniform1iv(fUnif, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2iv(fUnif, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3iv(fUnif, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4iv(fUnif, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if one of the integer variants of this function is used to load a uniform variable of type float, vec2, vec3, or vec4.");
+			glUseProgram(program.getProgram());
+			glUniform4iv(vUnif, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(uniformiv_invalid_location, "Invalid glUniform{1234}iv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			std::vector<GLint> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniform1iv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2iv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3iv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4iv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniform1iv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform2iv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform3iv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform4iv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(uniformiv_invalid_count, "Invalid glUniform{1234}iv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+			GLint fUnif		= glGetUniformLocation(program.getProgram(), "fTest");		// ivec4
+
+			if (fUnif == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLint> data(8);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+			glUseProgram(program.getProgram());
+			glUniform1iv(fUnif, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2iv(fUnif, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3iv(fUnif, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4iv(fUnif, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+	// glUniformMatrix*fv
+
+	ES2F_ADD_API_CASE(uniform_matrixfv_invalid_program, "Invalid glUniformMatrix{234}fv() usage",
+		{
+			std::vector<GLfloat> data(16);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniformMatrix2fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(uniform_matrixfv_incompatible_type, "Invalid glUniformMatrix{234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+			GLint vMatUnif		= glGetUniformLocation(program.getProgram(), "vMatrix");	// mat4
+			GLint fSamplerUnif	= glGetUniformLocation(program.getProgram(), "fSampler");	// sampler2D
+
+			if (vMatUnif == -1 || fSamplerUnif == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLfloat> data(16);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniformMatrix2fv(vMatUnif, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3fv(vMatUnif, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4fv(vMatUnif, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than glUniform1i and glUniform1iv.");
+			glUseProgram(program.getProgram());
+			glUniformMatrix4fv(fSamplerUnif, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(uniform_matrixfv_invalid_location, "Invalid glUniformMatrix{234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			std::vector<GLfloat> data(16);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniformMatrix2fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniformMatrix2fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniformMatrix3fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniformMatrix4fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(uniform_matrixfv_invalid_count, "Invalid glUniformMatrix{234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+			GLint vMatUnif		= glGetUniformLocation(program.getProgram(), "vMatrix");		// mat4
+
+			if (vMatUnif == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+
+			std::vector<GLfloat> data(32);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+			glUseProgram(program.getProgram());
+			glUniformMatrix2fv(vMatUnif, 2, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3fv(vMatUnif, 2, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4fv(vMatUnif, 2, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(uniform_matrixfv_invalid_transpose, "Invalid glUniformMatrix{234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			std::vector<GLfloat> data(16);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if transpose is not GL_FALSE.");
+			glUseProgram(program.getProgram());
+			glUniformMatrix2fv(-1, 1, GL_TRUE, &data[0]);
+			expectError(GL_INVALID_VALUE);
+			glUniformMatrix3fv(-1, 1, GL_TRUE, &data[0]);
+			expectError(GL_INVALID_VALUE);
+			glUniformMatrix4fv(-1, 1, GL_TRUE, &data[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fNegativeShaderApiTests.hpp b/modules/gles2/functional/es2fNegativeShaderApiTests.hpp
new file mode 100644
index 0000000..21feb93
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeShaderApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FNEGATIVESHADERAPITESTS_HPP
+#define _ES2FNEGATIVESHADERAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Shader API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class NegativeShaderApiTests : public TestCaseGroup
+{
+public:
+									NegativeShaderApiTests		(Context& context);
+									~NegativeShaderApiTests		(void);
+
+	void							init						(void);
+
+private:
+									NegativeShaderApiTests		(const NegativeShaderApiTests& other);
+	NegativeShaderApiTests&			operator=					(const NegativeShaderApiTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FNEGATIVESHADERAPITESTS_HPP
diff --git a/modules/gles2/functional/es2fNegativeStateApiTests.cpp b/modules/gles2/functional/es2fNegativeStateApiTests.cpp
new file mode 100644
index 0000000..22979fb
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeStateApiTests.cpp
@@ -0,0 +1,745 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative GL State API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fNegativeStateApiTests.hpp"
+#include "es2fApiCase.hpp"
+#include "gluShaderProgram.hpp"
+#include "deMemory.h"
+
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+
+static const char* uniformTestVertSource	=	"uniform mediump vec4 vTest;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vTest;\n"
+												"}\n\0";
+static const char* uniformTestFragSource	=	"uniform mediump ivec4 fTest;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_FragColor = vec4(fTest);\n"
+												"}\n\0";
+
+NegativeStateApiTests::NegativeStateApiTests (Context& context)
+	: TestCaseGroup(context, "state", "Negative GL State API Cases")
+{
+}
+
+NegativeStateApiTests::~NegativeStateApiTests (void)
+{
+}
+
+void NegativeStateApiTests::init (void)
+{
+	// Enabling & disabling states
+
+	ES2F_ADD_API_CASE(enable, "Invalid glEnable() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if cap is not one of the allowed values.");
+			glEnable(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(disable, "Invalid glDisable() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if cap is not one of the allowed values.");
+			glDisable(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Simple state queries
+
+	ES2F_ADD_API_CASE(get_booleanv, "Invalid glGetBooleanv() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+			GLboolean params[1] = { GL_FALSE };
+			glGetBooleanv(-1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(get_floatv, "Invalid glGetFloatv() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+			GLfloat params[1] = { 0.0f };
+			glGetFloatv(-1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(get_integerv, "Invalid glGetIntegerv() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+			GLint params[1] = { 0 };
+			glGetIntegerv(-1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(get_string, "Invalid glGetString() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if name is not an accepted value.");
+			glGetString(0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Enumerated state queries: Shaders
+
+	ES2F_ADD_API_CASE(get_attached_shaders, "Invalid glGetAttachedShaders() usage",
+		{
+			GLuint shaders[1]	= { 0 };
+			GLuint shaderObject = glCreateShader(GL_VERTEX_SHADER);
+			GLuint program		= glCreateProgram();
+			GLsizei count[1]	= { -1 };
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetAttachedShaders(-1, 1, &count[0], &shaders[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetAttachedShaders(shaderObject, 1, &count[0], &shaders[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if maxCount is less than 0.");
+			glGetAttachedShaders(program, -1, &count[0], &shaders[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shaderObject);
+			glDeleteProgram(program);
+		});
+	ES2F_ADD_API_CASE(get_shaderiv, "Invalid glGetShaderiv() usage",
+		{
+			GLboolean shaderCompilerSupported;
+			glGetBooleanv(GL_SHADER_COMPILER, &shaderCompilerSupported);
+			if (!shaderCompilerSupported)
+				m_log << TestLog::Message << "// Shader compiler not supported, always expect GL_INVALID_OPERATION" << TestLog::EndMessage;
+			else
+				m_log << TestLog::Message << "// Shader compiler supported" << TestLog::EndMessage;
+
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+			GLuint program	= glCreateProgram();
+			GLint param[1]	= { -1 };
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetShaderiv(shader, -1, &param[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glGetShaderiv(-1, GL_SHADER_TYPE, &param[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if pname is GL_COMPILE_STATUS, GL_INFO_LOG_LENGTH, or GL_SHADER_SOURCE_LENGTH but a shader compiler is not supported.");
+			glGetShaderiv(shader, GL_COMPILE_STATUS, &param[0]);
+			expectError(shaderCompilerSupported ? GL_NO_ERROR : GL_INVALID_OPERATION);
+			glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &param[0]);
+			expectError(shaderCompilerSupported ? GL_NO_ERROR : GL_INVALID_OPERATION);
+			glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &param[0]);
+			expectError(shaderCompilerSupported ? GL_NO_ERROR : GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader does not refer to a shader object.");
+			glGetShaderiv(program, GL_SHADER_TYPE, &param[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+			glDeleteProgram(program);
+		});
+	ES2F_ADD_API_CASE(get_shader_info_log, "Invalid glGetShaderInfoLog() usage",
+		{
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+			GLuint program	= glCreateProgram();
+			GLsizei length[1] = { -1 };
+			char infoLog[128];
+
+			deMemset(&infoLog[0], 0, sizeof(infoLog));
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glGetShaderInfoLog(-1, 128, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			glGetShaderInfoLog(program, 128, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if maxLength is less than 0.");
+			glGetShaderInfoLog(shader, -1, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+			glDeleteProgram(program);
+		});
+	ES2F_ADD_API_CASE(get_shader_precision_format, "Invalid glGetShaderPrecisionFormat() usage",
+		{
+			GLboolean shaderCompilerSupported;
+			glGetBooleanv(GL_SHADER_COMPILER, &shaderCompilerSupported);
+			if (!shaderCompilerSupported)
+				m_log << TestLog::Message << "// Shader compiler not supported, always expect GL_INVALID_OPERATION" << TestLog::EndMessage;
+			else
+				m_log << TestLog::Message << "// Shader compiler supported" << TestLog::EndMessage;
+
+			GLint range[2];
+			range[0] = -1;
+			range[1] = -1;
+			GLint precision[1] = { -1 };
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if shaderType or precisionType is not an accepted value.");
+			glGetShaderPrecisionFormat (-1, GL_MEDIUM_FLOAT, &range[0], &precision[0]);
+			expectError(shaderCompilerSupported ? GL_INVALID_ENUM : GL_INVALID_OPERATION);
+			glGetShaderPrecisionFormat (GL_VERTEX_SHADER, -1, &range[0], &precision[0]);
+			expectError(shaderCompilerSupported ? GL_INVALID_ENUM : GL_INVALID_OPERATION);
+			glGetShaderPrecisionFormat (-1, -1, &range[0], &precision[0]);
+			expectError(shaderCompilerSupported ? GL_INVALID_ENUM : GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a shader compiler is not supported.");
+			glGetShaderPrecisionFormat (GL_VERTEX_SHADER, GL_MEDIUM_FLOAT, &range[0], &precision[0]);
+			expectError(shaderCompilerSupported ? GL_NO_ERROR : GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(get_shader_source, "Invalid glGetShaderSource() usage",
+		{
+			GLsizei length[1] = { -1 };
+			char source[1] = { 0 };
+			GLuint program	= glCreateProgram();
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glGetShaderSource(-1, 1, &length[0], &source[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			glGetShaderSource(program, 1, &length[0], &source[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if bufSize is less than 0.");
+			glGetShaderSource(shader, -1, &length[0], &source[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+
+	// Enumerated state queries: Programs
+
+	ES2F_ADD_API_CASE(get_programiv, "Invalid glGetProgramiv() usage",
+		{
+			GLuint program	= glCreateProgram();
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+			GLint params[1] = { -1 };
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetProgramiv(program, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetProgramiv(-1, GL_LINK_STATUS, &params[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program does not refer to a program object.");
+			glGetProgramiv(shader, GL_LINK_STATUS, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+	ES2F_ADD_API_CASE(get_program_info_log, "Invalid glGetProgramInfoLog() usage",
+		{
+			GLuint program	= glCreateProgram();
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+			GLsizei length[1] = { -1 };
+			char infoLog[1] = { 0 };
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetProgramInfoLog (-1, 1, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetProgramInfoLog (shader, 1, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if maxLength is less than 0.");
+			glGetProgramInfoLog (program, -1, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+
+	// Enumerated state queries: Shader variables
+
+	ES2F_ADD_API_CASE(get_tex_parameterfv, "Invalid glGetTexParameterfv() usage",
+		{
+			GLfloat params[1] = { 0.0f };
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not an accepted value.");
+			glGetTexParameterfv (-1, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetTexParameterfv (GL_TEXTURE_2D, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetTexParameterfv (-1, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(get_tex_parameteriv, "Invalid glGetTexParameteriv() usage",
+		{
+			GLint params[1] = { -1 };
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not an accepted value.");
+			glGetTexParameteriv (-1, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetTexParameteriv (GL_TEXTURE_2D, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetTexParameteriv (-1, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(get_uniformfv, "Invalid glGetUniformfv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			GLint vUnif = glGetUniformLocation(program.getProgram(), "vTest");	// vec4
+			GLint fUnif = glGetUniformLocation(program.getProgram(), "fTest");	// ivec4
+			if (vUnif == -1 || fUnif)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+
+			GLuint shader		= glCreateShader(GL_VERTEX_SHADER);
+			GLuint programEmpty = glCreateProgram();
+			GLfloat params[4];
+
+			deMemset(&params[0], 0, sizeof(params));
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetUniformfv (-1, vUnif, &params[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetUniformfv (shader, vUnif, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+			glGetUniformfv (programEmpty, vUnif, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if location does not correspond to a valid uniform variable location for the specified program object.");
+			glGetUniformfv (program.getProgram(), de::max(vUnif, fUnif)+1, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+			glDeleteProgram(programEmpty);
+		});
+	ES2F_ADD_API_CASE(get_uniformiv, "Invalid glGetUniformiv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			GLint vUnif = glGetUniformLocation(program.getProgram(), "vTest");	// vec4
+			GLint fUnif = glGetUniformLocation(program.getProgram(), "fTest");	// ivec4
+			if (vUnif == -1 || fUnif == -1)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+
+			GLuint shader		= glCreateShader(GL_VERTEX_SHADER);
+			GLuint programEmpty = glCreateProgram();
+			GLint params[4];
+
+			deMemset(&params[0], 0, sizeof(params));
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetUniformiv (-1, vUnif, &params[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetUniformiv (shader, vUnif, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+			glGetUniformiv (programEmpty, vUnif, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if location does not correspond to a valid uniform variable location for the specified program object.");
+			glGetUniformiv (program.getProgram(), de::max(vUnif, fUnif)+1, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+			glDeleteProgram(programEmpty);
+		});
+	ES2F_ADD_API_CASE(get_vertex_attribfv, "Invalid glGetVertexAttribfv() usage",
+		{
+			GLfloat params[1];
+
+			deMemset(&params[0], 0, sizeof(params));
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetVertexAttribfv(0, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			GLint maxVertexAttribs;
+			glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+			glGetVertexAttribfv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &params[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(get_vertex_attribiv, "Invalid glGetVertexAttribiv() usage",
+		{
+			GLint params[1];
+
+			deMemset(&params[0], 0, sizeof(params));
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetVertexAttribiv(0, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			GLint maxVertexAttribs;
+			glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+			glGetVertexAttribiv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &params[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(get_vertex_attrib_pointerv, "Invalid glGetVertexAttribPointerv() usage",
+		{
+			GLvoid* ptr[1] = { DE_NULL };
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetVertexAttribPointerv(0, -1, &ptr[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			GLint maxVertexAttribs;
+			glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+			glGetVertexAttribPointerv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_POINTER, &ptr[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+
+	// Enumerated state queries: Buffers
+
+	ES2F_ADD_API_CASE(get_buffer_parameteriv, "Invalid glGetBufferParameteriv() usage",
+		{
+			GLint params[1];
+			GLuint buf;
+			glGenBuffers(1, &buf);
+			glBindBuffer(GL_ARRAY_BUFFER, buf);
+
+			deMemset(&params[0], 0, sizeof(params));
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or value is not an accepted value.");
+			glGetBufferParameteriv(-1, GL_BUFFER_SIZE, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetBufferParameteriv(GL_ARRAY_BUFFER , -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetBufferParameteriv(-1, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &buf);
+		});
+	ES2F_ADD_API_CASE(get_framebuffer_attachment_parameteriv, "Invalid glGetFramebufferAttachmentParameteriv() usage",
+		{
+			GLint params[1] = { -1 };
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER.");
+			glGetFramebufferAttachmentParameteriv(-1, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if there is no attached object at the named attachment point and pname is not GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE.");
+			glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if the attached object at the named attachment point is incompatible with pname.");
+			GLint attachmentObjectType = -1;
+			glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &attachmentObjectType);
+			expectError(GL_NO_ERROR);
+
+			if (attachmentObjectType == GL_RENDERBUFFER)
+				glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &params[0]);
+			else if (attachmentObjectType == GL_TEXTURE)
+				glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, -1, &params[0]);
+			else if (attachmentObjectType == GL_NONE)
+				glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);
+			else
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid return value from glGetFramebufferAttachmentParameteriv()");
+
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the default framebuffer object name 0 is bound.");
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteFramebuffers(1, &fbo);
+		});
+	ES2F_ADD_API_CASE(get_renderbuffer_parameteriv, "Invalid glGetRenderbufferParameteriv() usage",
+		{
+			GLint params[1];
+			GLuint rbo;
+			glGenRenderbuffers(1, &rbo);
+			glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+
+			deMemset(&params[0], 0, sizeof(params));
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+			glGetRenderbufferParameteriv(-1, GL_RENDERBUFFER_WIDTH, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+			glGetRenderbufferParameteriv(GL_RENDERBUFFER, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the reserved renderbuffer object name 0 is bound.");
+			glBindRenderbuffer(GL_RENDERBUFFER, 0);
+			glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteRenderbuffers(1, &rbo);
+		});
+
+	// Enumerated boolean state queries
+
+	ES2F_ADD_API_CASE(get_is_enabled, "Invalid glIsEnabled() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if cap is not an accepted value.");
+			glIsEnabled(-1);
+			expectError(GL_INVALID_ENUM);
+			glIsEnabled(GL_TRIANGLES);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Hints
+
+	ES2F_ADD_API_CASE(hint, "Invalid glHint() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if either target or mode is not an accepted value.");
+			glHint(GL_GENERATE_MIPMAP_HINT, -1);
+			expectError(GL_INVALID_ENUM);
+			glHint(-1, GL_FASTEST);
+			expectError(GL_INVALID_ENUM);
+			glHint(-1, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Named object usage
+
+	ES2F_ADD_API_CASE(is_buffer, "Invalid glIsBuffer() usage",
+		{
+			GLuint		buffer = 0;
+			GLboolean	isBuffer;
+
+			m_log << TestLog::Section("", "A name returned by glGenBuffers, but not yet associated with a buffer object by calling glBindBuffer, is not the name of a buffer object.");
+			isBuffer		= glIsBuffer(buffer);
+			checkBooleans	(isBuffer, GL_FALSE);
+
+			glGenBuffers	(1, &buffer);
+			isBuffer		= glIsBuffer(buffer);
+			checkBooleans	(isBuffer, GL_FALSE);
+
+			glBindBuffer	(GL_ARRAY_BUFFER, buffer);
+			isBuffer		= glIsBuffer(buffer);
+			checkBooleans	(isBuffer, GL_TRUE);
+
+			glBindBuffer	(GL_ARRAY_BUFFER, 0);
+			glDeleteBuffers	(1, &buffer);
+			isBuffer		= glIsBuffer(buffer);
+			checkBooleans	(isBuffer, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES2F_ADD_API_CASE(is_framebuffer, "Invalid glIsFramebuffer() usage",
+		{
+			GLuint		fbo = 0;
+			GLboolean	isFbo;
+
+			m_log << TestLog::Section("", "A name returned by glGenFramebuffers, but not yet bound through a call to glBindFramebuffer is not the name of a framebuffer object.");
+			isFbo				= glIsFramebuffer(fbo);
+			checkBooleans		(isFbo, GL_FALSE);
+
+			glGenFramebuffers	(1, &fbo);
+			isFbo				= glIsFramebuffer(fbo);
+			checkBooleans		(isFbo, GL_FALSE);
+
+			glBindFramebuffer	(GL_FRAMEBUFFER, fbo);
+			isFbo				= glIsFramebuffer(fbo);
+			checkBooleans		(isFbo, GL_TRUE);
+
+			glBindFramebuffer	(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			isFbo				= glIsFramebuffer(fbo);
+			checkBooleans		(isFbo, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES2F_ADD_API_CASE(is_program, "Invalid glIsProgram() usage",
+		{
+			GLuint		program = 0;
+			GLboolean	isProgram;
+
+			m_log << TestLog::Section("", "A name created with glCreateProgram, and not yet deleted with glDeleteProgram is a name of a program object.");
+			isProgram			= glIsProgram(program);
+			checkBooleans		(isProgram, GL_FALSE);
+
+			program				= glCreateProgram();
+			isProgram			= glIsProgram(program);
+			checkBooleans		(isProgram, GL_TRUE);
+
+			glDeleteProgram		(program);
+			isProgram			= glIsProgram(program);
+			checkBooleans		(isProgram, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES2F_ADD_API_CASE(is_renderbuffer, "Invalid glIsRenderbuffer() usage",
+		{
+			GLuint		rbo = 0;
+			GLboolean	isRbo;
+
+			m_log << TestLog::Section("", "A name returned by glGenRenderbuffers, but not yet bound through a call to glBindRenderbuffer or glFramebufferRenderbuffer is not the name of a renderbuffer object.");
+			isRbo					= glIsRenderbuffer(rbo);
+			checkBooleans			(isRbo, GL_FALSE);
+
+			glGenRenderbuffers		(1, &rbo);
+			isRbo					= glIsRenderbuffer(rbo);
+			checkBooleans			(isRbo, GL_FALSE);
+
+			glBindRenderbuffer		(GL_RENDERBUFFER, rbo);
+			isRbo					= glIsRenderbuffer(rbo);
+			checkBooleans			(isRbo, GL_TRUE);
+
+			glBindRenderbuffer		(GL_RENDERBUFFER, 0);
+			glDeleteRenderbuffers	(1, &rbo);
+			isRbo					= glIsRenderbuffer(rbo);
+			checkBooleans			(isRbo, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES2F_ADD_API_CASE(is_shader, "Invalid glIsShader() usage",
+		{
+			GLuint		shader = 0;
+			GLboolean	isShader;
+
+			m_log << TestLog::Section("", "A name created with glCreateShader, and not yet deleted with glDeleteShader is a name of a shader object.");
+			isShader			= glIsProgram(shader);
+			checkBooleans		(isShader, GL_FALSE);
+
+			shader				= glCreateShader(GL_VERTEX_SHADER);
+			isShader			= glIsShader(shader);
+			checkBooleans		(isShader, GL_TRUE);
+
+			glDeleteShader		(shader);
+			isShader			= glIsShader(shader);
+			checkBooleans		(isShader, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES2F_ADD_API_CASE(is_texture, "Invalid glIsTexture() usage",
+		{
+			GLuint		texture = 0;
+			GLboolean	isTexture;
+
+			m_log << TestLog::Section("", "A name returned by glGenTextures, but not yet bound through a call to glBindTexture is not the name of a texture.");
+			isTexture			= glIsTexture(texture);
+			checkBooleans		(isTexture, GL_FALSE);
+
+			glGenTextures		(1, &texture);
+			isTexture			= glIsTexture(texture);
+			checkBooleans		(isTexture, GL_FALSE);
+
+			glBindTexture		(GL_TEXTURE_2D, texture);
+			isTexture			= glIsTexture(texture);
+			checkBooleans		(isTexture, GL_TRUE);
+
+			glBindTexture		(GL_TEXTURE_2D, 0);
+			glDeleteTextures	(1, &texture);
+			isTexture			= glIsTexture(texture);
+			checkBooleans		(isTexture, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fNegativeStateApiTests.hpp b/modules/gles2/functional/es2fNegativeStateApiTests.hpp
new file mode 100644
index 0000000..252851c
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeStateApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FNEGATIVESTATEAPITESTS_HPP
+#define _ES2FNEGATIVESTATEAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative GL State API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class NegativeStateApiTests : public TestCaseGroup
+{
+public:
+								NegativeStateApiTests		(Context& context);
+								~NegativeStateApiTests		(void);
+
+	void						init						(void);
+
+private:
+								NegativeStateApiTests		(const NegativeStateApiTests& other);
+	NegativeStateApiTests&		operator=					(const NegativeStateApiTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FNEGATIVESTATEAPITESTS_HPP
diff --git a/modules/gles2/functional/es2fNegativeTextureApiTests.cpp b/modules/gles2/functional/es2fNegativeTextureApiTests.cpp
new file mode 100644
index 0000000..b4b1dc3
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeTextureApiTests.cpp
@@ -0,0 +1,2298 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Texture API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fNegativeTextureApiTests.hpp"
+#include "es2fApiCase.hpp"
+#include "tcuFormatUtil.hpp"
+#include "gluContextInfo.hpp"
+
+#include <vector>
+#include <algorithm>
+
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+using std::vector;
+
+static deUint32 cubeFaceToGLFace (tcu::CubeFace face)
+{
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
+		case tcu::CUBEFACE_POSITIVE_X: return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
+		case tcu::CUBEFACE_NEGATIVE_Y: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
+		case tcu::CUBEFACE_POSITIVE_Y: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
+		case tcu::CUBEFACE_NEGATIVE_Z: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
+		case tcu::CUBEFACE_POSITIVE_Z: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return GL_NONE;
+	}
+}
+
+#define FOR_CUBE_FACES(FACE_GL_VAR, BODY)												\
+	do																					\
+	{																					\
+		for (int faceIterTcu = 0; faceIterTcu < tcu::CUBEFACE_LAST; faceIterTcu++)		\
+		{																				\
+			const GLenum FACE_GL_VAR = cubeFaceToGLFace((tcu::CubeFace)faceIterTcu);	\
+			BODY																		\
+		}																				\
+	} while (false)
+
+static void getCompressedTexSubImage2DFormat(const vector<deInt32>& supported, vector<deInt32>& accepted)
+{
+	// Find a supported compressed texture format that is accepted by compressedTexSubImage2D()
+
+	static const GLuint compressedTexSubImage2DFormats[] =
+	{
+		0x83F0,	// GL_COMPRESSED_RGB_S3TC_DXT1_EXT
+		0x83F1,	// GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
+		0x8C00,	// GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG
+		0x8C01,	// GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG
+		0x8C02,	// GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
+		0x8C03	// GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
+	};
+
+	for (int i = 0; i < (int)supported.size(); i++)
+	{
+		vector<deInt32>::const_iterator fmt = std::find(supported.begin(), supported.end(), compressedTexSubImage2DFormats[i]);
+		if (fmt != supported.end())
+			accepted.push_back(*fmt);
+	}
+}
+
+NegativeTextureApiTests::NegativeTextureApiTests (Context& context)
+	: TestCaseGroup(context, "texture", "Negative Texture API Cases")
+{
+}
+
+NegativeTextureApiTests::~NegativeTextureApiTests (void)
+{
+}
+
+void NegativeTextureApiTests::init (void)
+{
+	// glActiveTexture
+
+	ES2F_ADD_API_CASE(activetexture_invalid_texture, "Invalid glActiveTexture() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if texture is not one of GL_TEXTUREi, where i ranges from 0 to (GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1).");
+			glActiveTexture(-1);
+			expectError(GL_INVALID_ENUM);
+			int numMaxTextureUnits = m_context.getContextInfo().getInt(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+			glActiveTexture(GL_TEXTURE0 + numMaxTextureUnits);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// glBindTexture
+
+	ES2F_ADD_API_CASE(bindtexture_invalid_target, "Invalid glBindTexture() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the allowable values.");
+			glBindTexture(0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(bindtexture_type_mismatch, "Invalid glBindTexture() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if texture was previously created with a target that doesn't match that of target.");
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
+			expectError(GL_INVALID_OPERATION);
+			glDeleteTextures(1, &texture);
+			m_log << TestLog::EndSection;
+		});
+
+	// glCompressedTexImage2D
+
+	ES2F_ADD_API_CASE(compressedteximage_2d_invalid_target, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+				glCompressedTexImage2D(0, 0, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_ENUM);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage_2d_invalid_format_tex2d, "Invalid glCompressedTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if internalformat is not a supported format returned in GL_COMPRESSED_TEXTURE_FORMATS.");
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(compressedteximage_2d_invalid_format_cube, "Invalid glCompressedTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if internalformat is not a supported format returned in GL_COMPRESSED_TEXTURE_FORMATS.");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_neg_level_tex2d, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+				glCompressedTexImage2D(GL_TEXTURE_2D, -1, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_neg_level_cube, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_level_max_tex2d, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+				deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+				glCompressedTexImage2D(GL_TEXTURE_2D, log2MaxTextureSize, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_level_max_cube_pos, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+				deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxTextureSize, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxTextureSize, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxTextureSize, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxTextureSize, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxTextureSize, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxTextureSize, compressedFormats[0], 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_neg_width_height_tex2d, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, compressedFormats[0], -1, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, compressedFormats[0], 0, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, compressedFormats[0], -1, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_neg_width_height_cube_pos_x, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, compressedFormats[0], -1, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, compressedFormats[0], 0, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, compressedFormats[0], -1, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_neg_width_height_cube_pos_y, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, compressedFormats[0], -1, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, compressedFormats[0], 0, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, compressedFormats[0], -1, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_neg_width_height_cube_pos_z, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, compressedFormats[0], -1, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, compressedFormats[0], 0, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, compressedFormats[0], -1, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_neg_width_height_cube_neg_x, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, compressedFormats[0], -1, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, compressedFormats[0], 0, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, compressedFormats[0], -1, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_neg_width_height_cube_neg_y, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, compressedFormats[0], -1, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, compressedFormats[0], 0, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, compressedFormats[0], -1, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_neg_width_height_cube_neg_z, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, compressedFormats[0], -1, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, compressedFormats[0], 0, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, compressedFormats[0], -1, -1, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_width_height_max_tex2d, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_TEXTURE_SIZE.");
+				int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE) + 1;
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, compressedFormats[0], maxTextureSize, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, compressedFormats[0], 0, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, compressedFormats[0], maxTextureSize, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_width_height_max_cube_pos_x, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+				int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, compressedFormats[0], maxTextureSize, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, compressedFormats[0], 0, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, compressedFormats[0], maxTextureSize, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_width_height_max_cube_pos_y, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+				int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, compressedFormats[0], maxTextureSize, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, compressedFormats[0], 0, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, compressedFormats[0], maxTextureSize, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_width_height_max_cube_pos_z, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+				int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, compressedFormats[0], maxTextureSize, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, compressedFormats[0], 0, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, compressedFormats[0], maxTextureSize, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_width_height_max_cube_neg_x, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+				int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, compressedFormats[0], maxTextureSize, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, compressedFormats[0], 0, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, compressedFormats[0], maxTextureSize, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_width_height_max_cube_neg_y, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+				int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, compressedFormats[0], maxTextureSize, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, compressedFormats[0], 0, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, compressedFormats[0], maxTextureSize, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_width_height_max_cube_neg_z, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+				int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, compressedFormats[0], maxTextureSize, 0, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, compressedFormats[0], 0, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, compressedFormats[0], maxTextureSize, maxTextureSize, 0, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_invalid_border, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, compressedFormats[0], 0, 0, 1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, compressedFormats[0], 0, 0, -1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+
+	ES2F_ADD_API_CASE(compressedteximage2d_invalid_border_cube_pos_x, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, compressedFormats[0], 0, 0, 1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, compressedFormats[0], 0, 0, -1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_invalid_border_cube_pos_y, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, compressedFormats[0], 0, 0, 1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, compressedFormats[0], 0, 0, -1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_invalid_border_cube_pos_z, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, compressedFormats[0], 0, 0, 1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, compressedFormats[0], 0, 0, -1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_invalid_border_cube_neg_x, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, compressedFormats[0], 0, 0, 1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, compressedFormats[0], 0, 0, -1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_invalid_border_cube_neg_y, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, compressedFormats[0], 0, 0, 1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, compressedFormats[0], 0, 0, -1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_invalid_border_cube_neg_z, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, compressedFormats[0], 0, 0, 1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, compressedFormats[0], 0, 0, -1, 0, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(compressedteximage2d_invalid_size, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if imageSize is not consistent with the format, dimensions, and contents of the specified compressed image data.");
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, compressedFormats[0], 0, 0, 0, -1, 0);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+		});
+
+	// glCopyTexImage2D
+
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_target, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glCopyTexImage2D(0, 0, GL_RGB, 0, 0, 64, 64, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_format_tex2d, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM or GL_INVALID_VALUE is generated if internalformat is not an accepted format.");
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 64, 64, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_format_cube, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM or GL_INVALID_VALUE is generated if internalformat is not an accepted format.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_inequal_width_height_cube, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if target is one of the six cube map 2D image targets and the width and height parameters are not equal.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_neg_level_tex2d, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "");
+			glCopyTexImage2D(GL_TEXTURE_2D, -1, GL_RGB, 0, 0, 64, 64, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_neg_level_cube, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_level_max_tex2d, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glCopyTexImage2D(GL_TEXTURE_2D, log2MaxTextureSize, GL_RGB, 0, 0, 64, 64, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_level_max_cube, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxTextureSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxTextureSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxTextureSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxTextureSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxTextureSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxTextureSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_width_height_tex2d, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_width_height_cube_pos_x, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_width_height_cube_pos_y, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_width_height_cube_pos_z, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_width_height_cube_neg_x, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_width_height_cube_neg_y, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_width_height_cube_neg_z, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_width_height_max_tex2d, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE) + 1;
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, maxTextureSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 1, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, maxTextureSize, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_width_height_max_cube_pos_x, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 1, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, maxTextureSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, maxTextureSize, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_width_height_max_cube_pos_y, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 1, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, maxTextureSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, maxTextureSize, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_width_height_max_cube_pos_z, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 1, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, maxTextureSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, maxTextureSize, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_width_height_max_cube_neg_x, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 1, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, maxTextureSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, maxTextureSize, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_width_height_max_cube_neg_y, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 1, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, maxTextureSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, maxTextureSize, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_width_height_max_cube_neg_z, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 1, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, maxTextureSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, maxTextureSize, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_border_tex2d, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 64, 64, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 64, 64, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_border_cube_pos_x, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 16, 16, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 16, 16, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_border_cube_pos_y, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 16, 16, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 16, 16, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_border_cube_pos_z, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 16, 16, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 16, 16, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_border_cube_neg_x, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 16, 16, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 16, 16, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_border_cube_neg_y, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 16, 16, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 16, 16, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copyteximage2d_invalid_border_cube_neg_z, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 16, 16, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 16, 16, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+
+	ES2F_ADD_API_CASE(copyteximage2d_incomplete_framebuffer, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 64, 64, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+		});
+
+	// glCopyTexSubImage2D
+
+	ES2F_ADD_API_CASE(copytexsubimage2d_invalid_target, "Invalid glCopyTexSubImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glCopyTexSubImage2D(0, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(copytexsubimage2d_neg_level_tex2d, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glCopyTexSubImage2D(GL_TEXTURE_2D, -1, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(copytexsubimage2d_neg_level_cube, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
+			FOR_CUBE_FACES(faceGL, glTexImage2D(faceGL, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL););
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(copytexsubimage2d_level_max_tex2d, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glCopyTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(copytexsubimage2d_level_max_cube_pos, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
+			FOR_CUBE_FACES(faceGL, glTexImage2D(faceGL, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL););
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxTextureSize, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxTextureSize, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxTextureSize, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxTextureSize, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxTextureSize, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxTextureSize, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(copytexsubimage2d_neg_offset, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset < 0 or yoffset < 0.");
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, -1, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, -1, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(copytexsubimage2d_offset_allowed, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 8, 4, 0, 0, 10, 10);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 4, 8, 0, 0, 10, 10);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 8, 8, 0, 0, 10, 10);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(copytexsubimage2d_neg_wdt_hgt, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, -1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(copytexsubimage2d_incomplete_framebuffer, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glDeleteTextures
+
+	ES2F_ADD_API_CASE(deletetextures_invalid_number, "Invalid glDeleteTextures() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteTextures(-1,0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(deletetextures_invalid_number_bind, "Invalid glDeleteTextures() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glDeleteTextures(-1,0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glGenerateMipmap
+
+	ES2F_ADD_API_CASE(generatemipmap_invalid_target, "Invalid glGenerateMipmap() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP.");
+			glGenerateMipmap(0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(generatemipmap_invalid_target_bind, "Invalid glGenerateMipmap() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+
+			m_log << TestLog::Section("", "INVALID_OPERATION is generated if the texture bound to target is not cube complete.");
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(generatemipmap_npot_wdt_hgt, "Invalid glGenerateMipmap() usage",
+		{
+			GLuint texture;
+			glActiveTexture(GL_TEXTURE0);
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if either the width or height of the zero level array is not a power of two.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 257, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glGenerateMipmap(GL_TEXTURE_2D);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 257, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glGenerateMipmap(GL_TEXTURE_2D);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 257, 257, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glGenerateMipmap(GL_TEXTURE_2D);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(generatemipmap_zero_level_array_compressed, "Invalid glGenerateMipmap() usage",
+		{
+			vector<deInt32> compressedFormats;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats);
+			if (!compressedFormats.empty())
+			{
+				m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the zero level array is stored in a compressed internal format.");
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, compressedFormats[0], 0, 0, 0, 0, 0);
+				glGenerateMipmap(GL_TEXTURE_2D);
+				expectError(GL_INVALID_OPERATION);
+				m_log << TestLog::EndSection;
+			}
+		});
+	ES2F_ADD_API_CASE(generatemipmap_incomplete_cube, "Invalid glGenerateMipmap() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
+
+			m_log << TestLog::Section("", "INVALID_OPERATION is generated if the texture bound to target is not cube complete.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glGenTextures
+
+	ES2F_ADD_API_CASE(gentextures_invalid_size, "Invalid glGenTextures() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glGenTextures(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+
+	// glPixelStorei
+
+	ES2F_ADD_API_CASE(pixelstorei_invalid_pname, "Invalid glPixelStorei() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glPixelStorei(0,1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(pixelstorei_invalid_param, "Invalid glPixelStorei() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if alignment is specified as other than 1, 2, 4, or 8.");
+			glPixelStorei(GL_PACK_ALIGNMENT, 0);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_UNPACK_ALIGNMENT, 0);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_PACK_ALIGNMENT, 16);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_UNPACK_ALIGNMENT, 16);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+
+	// glTexImage2D
+
+	ES2F_ADD_API_CASE(teximage2d_invalid_target, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glTexImage2D(0, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_invalid_format, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if format or type is not an accepted value.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, 0, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_invalid_type, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if format or type is not an accepted value.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_inequal_width_height_cube, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if target is one of the six cube map 2D image targets and the width and height parameters are not equal.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_neg_level_tex2d, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glTexImage2D(GL_TEXTURE_2D, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_neg_level_cube, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_level_max_tex2d, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glTexImage2D(GL_TEXTURE_2D, log2MaxTextureSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_level_max_cube, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxTextureSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxTextureSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxTextureSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxTextureSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxTextureSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxTextureSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_invalid_internalformat, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if internalformat is not an accepted format.");
+			glTexImage2D(GL_TEXTURE_2D, 0, 0, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_neg_width_height_tex2d, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_neg_width_height_cube_pos_x, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_neg_width_height_cube_pos_y, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_neg_width_height_cube_pos_z, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_neg_width_height_cube_neg_x, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_neg_width_height_cube_neg_y, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_neg_width_height_cube_neg_z, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_width_height_max_tex2d, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE) + 1;
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, maxTextureSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, maxTextureSize, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_width_height_max_cube_pos_x, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, maxTextureSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, maxTextureSize, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_width_height_max_cube_pos_y, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, maxTextureSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, maxTextureSize, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_width_height_max_cube_pos_z, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, maxTextureSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, maxTextureSize, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_width_height_max_cube_neg_x, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, maxTextureSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, maxTextureSize, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_width_height_max_cube_neg_y, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, maxTextureSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, maxTextureSize, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_width_height_max_cube_neg_z, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, maxTextureSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, maxTextureSize, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_invalid_border, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, -1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_format_mismatch, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if format does not match internalformat.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(teximage2d_type_format_mismatch, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if type is GL_UNSIGNED_SHORT_4_4_4_4 or GL_UNSIGNED_SHORT_5_5_5_1 and format is not GL_RGBA.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+		});
+
+	// glTexSubImage2D
+
+	ES2F_ADD_API_CASE(texsubimage2d_invalid_target, "Invalid glTexSubImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glTexSubImage2D(0, 0, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(texsubimage2d_invalid_format, "Invalid glTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if format or type is not an accepted value.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(texsubimage2d_invalid_type, "Invalid glTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if format or type is not an accepted value.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_RGB, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(texsubimage2d_neg_level_tex2d, "Invalid glTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glTexSubImage2D(GL_TEXTURE_2D, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(texsubimage2d_neg_level_cube, "Invalid glTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
+			FOR_CUBE_FACES(faceGL, glTexImage2D(faceGL, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL););
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(texsubimage2d_level_max_tex2d, "Invalid glTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(texsubimage2d_level_max_cube, "Invalid glTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
+			FOR_CUBE_FACES(faceGL, glTexImage2D(faceGL, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL););
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			glTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxTextureSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxTextureSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxTextureSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxTextureSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxTextureSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(texsubimage2d_neg_offset, "Invalid glTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset or yoffset are negative.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, -1, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, -1, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, -1, -1, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(texsubimage2d_offset_allowed, "Invalid glTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 8, 4, 10, 10, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 4, 8, 10, 10, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 8, 8, 10, 10, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(texsubimage2d_neg_wdt_hgt, "Invalid glTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, -1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -1, -1, -GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES2F_ADD_API_CASE(texsubimage2d_type_format_mismatch, "Invalid glTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if type is GL_UNSIGNED_SHORT_5_6_5 and format is not GL_RGB");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_6_5, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if type is GL_UNSIGNED_SHORT_4_4_4_4 or GL_UNSIGNED_SHORT_5_5_5_1 and format is not GL_RGBA.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glTexParameteri
+
+	ES2F_ADD_API_CASE(texparameteri, "Invalid glTexParameteri() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			glTexParameteri(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(0, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(texparameteri_bind, "Invalid glTexParameteri() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			glTexParameteri(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(0, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glTexParameterf
+
+	ES2F_ADD_API_CASE(texparameterf, "Invalid glTexParameterf() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			glTexParameterf(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(0, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(texparameterf_bind, "Invalid glTexParameterf() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			glTexParameterf(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(0, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glTexParameteriv
+
+	ES2F_ADD_API_CASE(texparameteriv, "Invalid glTexParameteriv() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			GLint params[1] = {GL_LINEAR};
+			glTexParameteriv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteriv(GL_TEXTURE_2D, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteriv(0, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			params[0] = 0;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_REPEAT;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = 0;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_NEAREST;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(texparameteriv_bind, "Invalid glTexParameteriv() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			GLint params[1] = {GL_LINEAR};
+			glTexParameteriv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteriv(GL_TEXTURE_2D, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteriv(0, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			params[0] = 0;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_REPEAT;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = 0;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_NEAREST;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glTexParameterfv
+
+	ES2F_ADD_API_CASE(texparameterfv, "Invalid glTexParameterfv() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			GLfloat params[1] = {GL_LINEAR};
+			glTexParameterfv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterfv(GL_TEXTURE_2D, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterfv(0, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			params[0] = 0.0f;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_REPEAT;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = 0.0f;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_NEAREST;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(texparameterfv_bind, "Invalid glTexParameterfv() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			GLfloat params[1] = {GL_LINEAR};
+			glTexParameterfv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterfv(GL_TEXTURE_2D, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterfv(0, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			params[0] = 0.0f;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_REPEAT;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = 0.0f;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_NEAREST;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glCompressedTexSubImage2D
+
+	ES2F_ADD_API_CASE(compressedtexsubimage2d_invalid_target, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			vector<deInt32> supported;
+			vector<deInt32> accepted;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, supported);
+			getCompressedTexSubImage2DFormat(supported, accepted);
+
+			if (accepted.empty())
+			{
+				m_log << TestLog::Message << "// No suitable compressed formats found, cannot evaluate test." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "No suitable compressed formats found");
+			}
+			else
+			{
+				for (int i = 0; i < (int)accepted.size(); i++)
+				{
+					m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+					m_log << TestLog::Message << "// Using texture format " << tcu::toHex(accepted[i]) << TestLog::EndMessage;
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					glCompressedTexSubImage2D(0, 0, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_ENUM);
+					m_log << TestLog::EndSection;
+				}
+			}
+		});
+	ES2F_ADD_API_CASE(compressedtexsubimage2d_invalid_format, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			vector<deInt32> supported;
+			vector<deInt32> accepted;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, supported);
+			getCompressedTexSubImage2DFormat(supported, accepted);
+
+			if (accepted.empty())
+			{
+				m_log << TestLog::Message << "// No suitable compressed formats found, expect GL_INVALID_ENUM." << TestLog::EndMessage;
+				glCompressedTexImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, 0);
+				expectError(GL_INVALID_ENUM);
+			}
+			else
+			{
+				for (int i = 0; i < (int)accepted.size(); i++)
+				{
+					m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if format is not a supported format returned in GL_COMPRESSED_TEXTURE_FORMATS.");
+					m_log << TestLog::Message << "// Using texture format " << tcu::toHex(accepted[i]) << TestLog::EndMessage;
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_ENUM);
+					m_log << TestLog::EndSection;
+				}
+			}
+		});
+	ES2F_ADD_API_CASE(compressedtexsubimage2d_neg_level_tex2d, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			vector<deInt32> supported;
+			vector<deInt32> accepted;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, supported);
+			getCompressedTexSubImage2DFormat(supported, accepted);
+
+			if (accepted.empty())
+			{
+				m_log << TestLog::Message << "// No suitable compressed formats found, cannot evaluate test." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "No suitable compressed formats found");
+			}
+			else
+			{
+				for (int i = 0; i < (int)accepted.size(); i++)
+				{
+					m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+					m_log << TestLog::Message << "// Using texture format " << tcu::toHex(accepted[i]) << TestLog::EndMessage;
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, -1, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					m_log << TestLog::EndSection;
+				}
+			}
+		});
+	ES2F_ADD_API_CASE(compressedtexsubimage2d_neg_level_cube, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			vector<deInt32> supported;
+			vector<deInt32> accepted;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, supported);
+			getCompressedTexSubImage2DFormat(supported, accepted);
+
+			if (accepted.empty())
+			{
+				m_log << TestLog::Message << "// No suitable compressed formats found, cannot evaluate test." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "No suitable compressed formats found");
+			}
+			else
+			{
+				for (int i = 0; i < (int)accepted.size(); i++)
+				{
+					m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+					m_log << TestLog::Message << "// Using texture format " << tcu::toHex(accepted[i]) << TestLog::EndMessage;
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					m_log << TestLog::EndSection;
+				}
+			}
+		});
+	ES2F_ADD_API_CASE(compressedtexsubimage2d_level_max_tex2d, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			vector<deInt32> supported;
+			vector<deInt32> accepted;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, supported);
+			getCompressedTexSubImage2DFormat(supported, accepted);
+
+			if (accepted.empty())
+			{
+				m_log << TestLog::Message << "// No suitable compressed formats found, cannot evaluate test." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "No suitable compressed formats found");
+			}
+			else
+			{
+				for (int i = 0; i < (int)accepted.size(); i++)
+				{
+					m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+					m_log << TestLog::Message << "// Using texture format " << tcu::toHex(accepted[i]) << TestLog::EndMessage;
+					deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					m_log << TestLog::EndSection;
+				}
+			}
+		});
+	ES2F_ADD_API_CASE(compressedtexsubimage2d_level_max_cube, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			vector<deInt32> supported;
+			vector<deInt32> accepted;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, supported);
+			getCompressedTexSubImage2DFormat(supported, accepted);
+
+			if (accepted.empty())
+			{
+				m_log << TestLog::Message << "// No suitable compressed formats found, cannot evaluate test." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "No suitable compressed formats found");
+			}
+			else
+			{
+				for (int i = 0; i < (int)accepted.size(); i++)
+				{
+					m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+					m_log << TestLog::Message << "// Using texture format " << tcu::toHex(accepted[i]) << TestLog::EndMessage;
+					deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxTextureSize, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxTextureSize, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxTextureSize, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxTextureSize, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxTextureSize, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxTextureSize, 0, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					m_log << TestLog::EndSection;
+				}
+			}
+		});
+		ES2F_ADD_API_CASE(compressedtexsubimage2d_neg_offset, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			vector<deInt32> supported;
+			vector<deInt32> accepted;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, supported);
+			getCompressedTexSubImage2DFormat(supported, accepted);
+
+			if (accepted.empty())
+			{
+				m_log << TestLog::Message << "// No suitable compressed formats found, cannot evaluate test." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "No suitable compressed formats found");
+			}
+			else
+			{
+				for (int i = 0; i < (int)accepted.size(); i++)
+				{
+					m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset or yoffset are negative.");
+					m_log << TestLog::Message << "// Using texture format " << tcu::toHex(accepted[i]) << TestLog::EndMessage;
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, -1, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, -1, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, -1, -1, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					m_log << TestLog::EndSection;
+				}
+			}
+		});
+	ES2F_ADD_API_CASE(compressedtexsubimage2d_offset_allowed, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			vector<deInt32> supported;
+			vector<deInt32> accepted;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, supported);
+			getCompressedTexSubImage2DFormat(supported, accepted);
+
+			if (accepted.empty())
+			{
+				m_log << TestLog::Message << "// No suitable compressed formats found, cannot evaluate test." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "No suitable compressed formats found");
+			}
+			else
+			{
+				for (int i = 0; i < (int)accepted.size(); i++)
+				{
+					deUint32 maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+					m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+					m_log << TestLog::Message << "// Using texture format " << tcu::toHex(accepted[i]) << TestLog::EndMessage;
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, maxTextureSize, 0, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, maxTextureSize, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, maxTextureSize, maxTextureSize, 0, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					m_log << TestLog::EndSection;
+				}
+			}
+		});
+	ES2F_ADD_API_CASE(compressedtexsubimage2d_neg_wdt_hgt, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			vector<deInt32> supported;
+			vector<deInt32> accepted;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, supported);
+			getCompressedTexSubImage2DFormat(supported, accepted);
+
+			if (accepted.empty())
+			{
+				m_log << TestLog::Message << "// No suitable compressed formats found, cannot evaluate test." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "No suitable compressed formats found");
+			}
+			else
+			{
+				for (int i = 0; i < (int)accepted.size(); i++)
+				{
+					m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+					m_log << TestLog::Message << "// Using texture format " << tcu::toHex(accepted[i]) << TestLog::EndMessage;
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -1, 0, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, -1, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -1, -1, accepted[i], 0, 0);
+					expectError(GL_INVALID_VALUE);
+					m_log << TestLog::EndSection;
+				}
+			}
+		});
+	ES2F_ADD_API_CASE(compressedtexsubimage2d_invalid_size, "Invalid glCompressedTexImage2D() usage",
+		{
+			vector<deInt32> supported;
+			vector<deInt32> accepted;
+			getSupportedExtensions(GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_COMPRESSED_TEXTURE_FORMATS, supported);
+			getCompressedTexSubImage2DFormat(supported, accepted);
+
+			if (accepted.empty())
+			{
+				m_log << TestLog::Message << "// No suitable compressed formats found, cannot evaluate test." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "No suitable compressed formats found");
+			}
+			else
+			{
+				for (int i = 0; i < (int)accepted.size(); i++)
+				{
+					m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if imageSize is not consistent with the format, dimensions, and contents of the specified compressed image data.");
+					m_log << TestLog::Message << "// Using texture format " << tcu::toHex(accepted[i]) << TestLog::EndMessage;
+					//glCompressedTexImage2D(GL_TEXTURE_2D, 0, accepted[i], 0, 0, 0, 0, 0);
+					//expectError(GL_NO_ERROR);
+					glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, accepted[i], -1, 0);
+					expectError(GL_INVALID_VALUE);
+					m_log << TestLog::EndSection;
+				}
+			}
+		});
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fNegativeTextureApiTests.hpp b/modules/gles2/functional/es2fNegativeTextureApiTests.hpp
new file mode 100644
index 0000000..092c77c
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeTextureApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FNEGATIVETEXTUREAPITESTS_HPP
+#define _ES2FNEGATIVETEXTUREAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Texture API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class NegativeTextureApiTests : public TestCaseGroup
+{
+public:
+								NegativeTextureApiTests		(Context& context);
+								~NegativeTextureApiTests	(void);
+
+	void						init						(void);
+
+private:
+								NegativeTextureApiTests		(const NegativeTextureApiTests& other);
+	NegativeTextureApiTests&	operator=					(const NegativeTextureApiTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FNEGATIVETEXTUREAPITESTS_HPP
diff --git a/modules/gles2/functional/es2fNegativeVertexArrayApiTests.cpp b/modules/gles2/functional/es2fNegativeVertexArrayApiTests.cpp
new file mode 100644
index 0000000..b62be41
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeVertexArrayApiTests.cpp
@@ -0,0 +1,310 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Vertex Array API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fNegativeVertexArrayApiTests.hpp"
+#include "es2fApiCase.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluContextInfo.hpp"
+#include "deString.h"
+
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+static const char* vertexShaderSource	= "void main (void) { gl_Position = vec4(0.0); }\n\0";
+static const char* fragmentShaderSource	= "void main (void) { gl_FragColor = vec4(0.0); }\n\0";
+
+using tcu::TestLog;
+
+NegativeVertexArrayApiTests::NegativeVertexArrayApiTests (Context& context)
+	: TestCaseGroup(context, "vertex_array", "Negative Vertex Array API Cases")
+{
+}
+
+NegativeVertexArrayApiTests::~NegativeVertexArrayApiTests (void)
+{
+}
+
+void NegativeVertexArrayApiTests::init (void)
+{
+	ES2F_ADD_API_CASE(vertex_attrib, "Invalid glVertexAttrib() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			glVertexAttrib1f(maxVertexAttribs, 0.0f);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib2f(maxVertexAttribs, 0.0f, 0.0f);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib3f(maxVertexAttribs, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib4f(maxVertexAttribs, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(vertex_attribv, "Invalid glVertexAttribv() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			float v[4] = {0.0f};
+			glVertexAttrib1fv(maxVertexAttribs, &v[0]);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib2fv(maxVertexAttribs, &v[0]);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib3fv(maxVertexAttribs, &v[0]);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib4fv(maxVertexAttribs, &v[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(vertex_attrib_pointer, "Invalid glVertexAttribPointer() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not an accepted value.");
+			glVertexAttribPointer(0, 1, 0, GL_TRUE, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			glVertexAttribPointer(maxVertexAttribs, 1, GL_BYTE, GL_TRUE, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if size is not 1, 2, 3, or 4.");
+			glVertexAttribPointer(0, 0, GL_BYTE, GL_TRUE, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if stride is negative.");
+			glVertexAttribPointer(0, 1, GL_BYTE, GL_TRUE, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(enable_vertex_attrib_array, "Invalid glEnableVertexAttribArray() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			glEnableVertexAttribArray(maxVertexAttribs);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(disable_vertex_attrib_array, "Invalid glDisableVertexAttribArray() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			glDisableVertexAttribArray(maxVertexAttribs);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(draw_arrays, "Invalid glDrawArrays() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawArrays(-1, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawArrays(GL_POINTS, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawArrays(GL_POINTS, 0, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(draw_arrays_invalid_program, "Invalid glDrawArrays() usage",
+		{
+			glUseProgram(0);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawArrays(-1, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawArrays(GL_POINTS, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawArrays(GL_POINTS, 0, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(draw_arrays_incomplete_primitive, "Invalid glDrawArrays() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawArrays(-1, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawArrays(GL_TRIANGLES, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawArrays(GL_TRIANGLES, 0, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(draw_elements, "Invalid glDrawElements() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLfloat vertices[1] = { 0.0f };
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawElements(-1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not GL_UNSIGNED_BYTE or GL_UNSIGNED_SHORT.");
+			glDrawElements(GL_POINTS, 1, -1, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawElements(GL_POINTS, -1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawElements(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES2F_ADD_API_CASE(draw_elements_invalid_program, "Invalid glDrawElements() usage",
+		{
+			glUseProgram(0);
+			GLfloat vertices[1] = { 0.0f };
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawElements(-1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not GL_UNSIGNED_BYTE or GL_UNSIGNED_SHORT.");
+			glDrawElements(GL_POINTS, 1, -1, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawElements(GL_POINTS, -1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawElements(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES2F_ADD_API_CASE(draw_elements_incomplete_primitive, "Invalid glDrawElements() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLfloat vertices[1] = { 0.0f };
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawElements(-1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not GL_UNSIGNED_BYTE or GL_UNSIGNED_SHORT.");
+			glDrawElements(GL_TRIANGLES, 1, -1, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawElements(GL_TRIANGLES, -1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawElements(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fNegativeVertexArrayApiTests.hpp b/modules/gles2/functional/es2fNegativeVertexArrayApiTests.hpp
new file mode 100644
index 0000000..4f040f9
--- /dev/null
+++ b/modules/gles2/functional/es2fNegativeVertexArrayApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FNEGATIVEVERTEXARRAYAPITESTS_HPP
+#define _ES2FNEGATIVEVERTEXARRAYAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Vertex Array API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class NegativeVertexArrayApiTests : public TestCaseGroup
+{
+public:
+									NegativeVertexArrayApiTests		(Context& context);
+									~NegativeVertexArrayApiTests	(void);
+
+	void							init							(void);
+
+private:
+									NegativeVertexArrayApiTests		(const NegativeVertexArrayApiTests& other);
+	NegativeVertexArrayApiTests&	operator=						(const NegativeVertexArrayApiTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FNEGATIVEVERTEXARRAYAPITESTS_HPP
diff --git a/modules/gles2/functional/es2fPolygonOffsetTests.cpp b/modules/gles2/functional/es2fPolygonOffsetTests.cpp
new file mode 100644
index 0000000..4a933d8
--- /dev/null
+++ b/modules/gles2/functional/es2fPolygonOffsetTests.cpp
@@ -0,0 +1,1247 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Polygon offset tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fPolygonOffsetTests.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+#include "glwFunctions.hpp"
+#include "tcuTestContext.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuVectorUtil.hpp"
+#include "rrRenderer.hpp"
+#include "rrFragmentOperations.hpp"
+
+#include "sglrReferenceContext.hpp"
+
+#include <string>
+#include <limits>
+
+using namespace glw; // GLint and other GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace
+{
+
+const char* s_shaderSourceVertex	= "attribute highp vec4 a_position;\n"
+									  "attribute highp vec4 a_color;\n"
+									  "varying mediump vec4 v_color;\n"
+									  "void main (void)\n"
+									  "{\n"
+									  "	gl_Position = a_position;\n"
+									  "	v_color = a_color;\n"
+									  "}\n";
+const char* s_shaderSourceFragment	= "varying mediump vec4 v_color;\n"
+									  "void main (void)\n"
+									  "{\n"
+									  "	gl_FragColor = v_color;\n"
+									  "}\n";
+
+static const tcu::Vec4	MASK_COLOR_OK	= tcu::Vec4(0.0f, 0.1f, 0.0f, 1.0f);
+static const tcu::Vec4	MASK_COLOR_DEV	= tcu::Vec4(0.8f, 0.5f, 0.0f, 1.0f);
+static const tcu::Vec4	MASK_COLOR_FAIL	= tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f);
+
+inline bool compareThreshold (const tcu::IVec4& a, const tcu::IVec4& b, const tcu::IVec4& threshold)
+{
+	return tcu::boolAll(tcu::lessThanEqual(tcu::abs(a - b), threshold));
+}
+
+/*--------------------------------------------------------------------*//*!
+* \brief Pixelwise comparison of two images.
+* \note copied & modified from glsRasterizationTests
+*
+* Kernel radius defines maximum allowed distance. If radius is 0, only
+* perfect match is allowed. Radius of 1 gives a 3x3 kernel.
+*
+* Return values: -1 = Perfect match
+* 0 = Deviation within kernel
+* >0 = Number of faulty pixels
+*//*--------------------------------------------------------------------*/
+int compareImages (tcu::TestLog& log, glu::RenderContext& renderCtx, const tcu::ConstPixelBufferAccess& test, const tcu::ConstPixelBufferAccess& ref, const tcu::PixelBufferAccess& diffMask, int radius)
+{
+	const int			height			= test.getHeight();
+	const int			width			= test.getWidth();
+	const int			colorThreshold	= 128;
+	const tcu::RGBA		formatThreshold	= renderCtx.getRenderTarget().getPixelFormat().getColorThreshold();
+	const tcu::IVec4	threshold		= tcu::IVec4(colorThreshold, colorThreshold, colorThreshold, formatThreshold.getAlpha() > 0 ? colorThreshold : 0)
+										+ tcu::IVec4(formatThreshold.getRed(), formatThreshold.getGreen(), formatThreshold.getBlue(), formatThreshold.getAlpha());
+
+	int			deviatingPixels = 0;
+	int			faultyPixels	= 0;
+	int			compareFailed	= -1;
+
+	tcu::clear(diffMask, MASK_COLOR_OK);
+
+	for (int y = 0; y < height; y++)
+	{
+		for (int x = 0; x < width; x++)
+		{
+			const tcu::IVec4 cRef = ref.getPixelInt(x, y);
+
+			// Pixelwise match, no deviation or fault
+			{
+				const tcu::IVec4 cTest = test.getPixelInt(x, y);
+				if (compareThreshold(cRef, cTest, threshold))
+					continue;
+			}
+
+			// If not, search within kernel radius
+			{
+				const int kYmin = deMax32(y - radius, 0);
+				const int kYmax = deMin32(y + radius, height-1);
+				const int kXmin = deMax32(x - radius, 0);
+				const int kXmax = deMin32(x + radius, width-1);
+				bool found = false;
+
+				for (int kY = kYmin; kY <= kYmax; kY++)
+				for (int kX = kXmin; kX <= kXmax; kX++)
+				{
+					const tcu::IVec4 cTest = test.getPixelInt(kX, kY);
+					if (compareThreshold(cRef, cTest, threshold))
+						found = true;
+				}
+
+				if (found)	// The pixel is deviating if the color is found inside the kernel
+				{
+					diffMask.setPixel(MASK_COLOR_DEV, x, y);
+					if (compareFailed == -1)
+						compareFailed = 0;
+					deviatingPixels++;
+					continue;
+				}
+			}
+
+			diffMask.setPixel(MASK_COLOR_FAIL, x, y);
+			faultyPixels++;										// The pixel is faulty if the color is not found
+			compareFailed = 1;
+		}
+	}
+
+	log << tcu::TestLog::Message << faultyPixels << " faulty pixel(s) found." << tcu::TestLog::EndMessage;
+
+	return (compareFailed == 1 ? faultyPixels : compareFailed);
+}
+
+void verifyImages (tcu::TestLog& log, tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const tcu::ConstPixelBufferAccess& testImage, const tcu::ConstPixelBufferAccess& referenceImage)
+{
+	using tcu::TestLog;
+
+	const int			kernelRadius		= 1;
+	const int			faultyPixelLimit	= 20;
+	int					faultyPixels;
+	tcu::Surface		diffMask			(testImage.getWidth(), testImage.getHeight());
+
+	faultyPixels = compareImages(log, renderCtx, referenceImage, testImage, diffMask.getAccess(), kernelRadius);
+
+	if (faultyPixels > faultyPixelLimit)
+	{
+		log << TestLog::ImageSet("Images", "Image comparison");
+		log << TestLog::Image("Test image", "Test image", testImage);
+		log << TestLog::Image("Reference image", "Reference image", referenceImage);
+		log << TestLog::Image("Difference mask", "Difference mask", diffMask.getAccess());
+		log << TestLog::EndImageSet;
+
+		log << tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+	}
+}
+
+void verifyError (tcu::TestContext& testCtx, const glw::Functions& gl, GLenum expected)
+{
+	deUint32 got = gl.getError();
+	if (got != expected)
+	{
+		testCtx.getLog() << tcu::TestLog::Message << "// ERROR: expected " << glu::getErrorStr(expected) << "; got " << glu::getErrorStr(got) << tcu::TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid error");
+	}
+}
+
+void checkCanvasSize (int width, int height, int minWidth, int minHeight)
+{
+	if (width < minWidth || height < minHeight)
+		throw tcu::NotSupportedError(std::string("Render context size must be at least ") + de::toString(minWidth) + "x" + de::toString(minWidth));
+}
+
+class PositionColorShader : public sglr::ShaderProgram
+{
+public:
+	enum
+	{
+		VARYINGLOC_COLOR = 0
+	};
+
+			PositionColorShader (void);
+	void	shadeVertices		(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void	shadeFragments		(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+};
+
+PositionColorShader::PositionColorShader (void)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(s_shaderSourceVertex)
+							<< sglr::pdec::FragmentSource(s_shaderSourceFragment))
+{
+}
+
+void PositionColorShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const int positionAttrLoc = 0;
+		const int colorAttrLoc = 1;
+
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		// Transform to position
+		packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx);
+
+		// Pass color to FS
+		packet.outputs[VARYINGLOC_COLOR] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void PositionColorShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::FragmentPacket& packet = packets[packetNdx];
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readTriangleVarying<float>(packet, context, VARYINGLOC_COLOR, fragNdx));
+	}
+}
+
+// PolygonOffsetTestCase
+
+class PolygonOffsetTestCase : public TestCase
+{
+public:
+					PolygonOffsetTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName, int canvasSize);
+
+	virtual void	testPolygonOffset		(void) = DE_NULL;
+	IterateResult	iterate					(void);
+
+protected:
+	const GLenum	m_internalFormat;
+	const char*		m_internalFormatName;
+	const int		m_targetSize;
+};
+
+PolygonOffsetTestCase::PolygonOffsetTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName, int canvasSize)
+	: TestCase				(context, name, description)
+	, m_internalFormat		(internalFormat)
+	, m_internalFormatName	(internalFormatName)
+	, m_targetSize			(canvasSize)
+{
+}
+
+PolygonOffsetTestCase::IterateResult PolygonOffsetTestCase::iterate (void)
+{
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	m_testCtx.getLog() << tcu::TestLog::Message << "Testing PolygonOffset with " << m_internalFormatName << " depth buffer." << tcu::TestLog::EndMessage;
+
+	if (m_internalFormat == 0)
+	{
+		// default framebuffer
+		const int width		= m_context.getRenderTarget().getWidth();
+		const int height	= m_context.getRenderTarget().getHeight();
+
+		checkCanvasSize(width, height, m_targetSize, m_targetSize);
+
+		if (m_context.getRenderTarget().getDepthBits() == 0)
+			throw tcu::NotSupportedError("polygon offset tests require depth buffer");
+
+		testPolygonOffset();
+	}
+	else
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		// framebuffer object
+		GLuint	colorRboId	= 0;
+		GLuint	depthRboId	= 0;
+		GLuint	fboId		= 0;
+		bool	fboComplete;
+
+		gl.genRenderbuffers(1, &colorRboId);
+		gl.bindRenderbuffer(GL_RENDERBUFFER, colorRboId);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, m_targetSize, m_targetSize);
+		verifyError(m_testCtx, gl, GL_NO_ERROR);
+
+		gl.genRenderbuffers(1, &depthRboId);
+		gl.bindRenderbuffer(GL_RENDERBUFFER, depthRboId);
+		gl.renderbufferStorage(GL_RENDERBUFFER, m_internalFormat, m_targetSize, m_targetSize);
+		verifyError(m_testCtx, gl, GL_NO_ERROR);
+
+		gl.genFramebuffers(1, &fboId);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, fboId);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRboId);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,	GL_RENDERBUFFER, depthRboId);
+		verifyError(m_testCtx, gl, GL_NO_ERROR);
+
+		fboComplete = gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
+
+		if (fboComplete)
+			testPolygonOffset();
+
+		gl.deleteFramebuffers(1, &fboId);
+		gl.deleteRenderbuffers(1, &depthRboId);
+		gl.deleteRenderbuffers(1, &colorRboId);
+
+		if (!fboComplete)
+			throw tcu::NotSupportedError("could not create fbo for testing.");
+	}
+
+	return STOP;
+}
+
+// UsageTestCase
+
+class UsageTestCase : public PolygonOffsetTestCase
+{
+public:
+			UsageTestCase		(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset	(void);
+};
+
+UsageTestCase::UsageTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void UsageTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangle[] =
+	{
+		tcu::Vec4(-1,  1,  0,  1),
+		tcu::Vec4( 1,  1,  0,  1),
+		tcu::Vec4( 1, -1,  0,  1),
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// render test image
+	{
+		const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program		(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc = gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc	= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.depthFunc				(GL_LEQUAL);	// make test pass if polygon offset doesn't do anything. It has its own test case. This test is only for to detect always-on cases.
+
+		log << TestLog::Message << "DepthFunc = GL_LEQUAL" << TestLog::EndMessage;
+
+		gl.enableVertexAttribArray	(positionLoc);
+		gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangle);
+
+		//draw back (offset disabled)
+
+		log << TestLog::Message << "Draw bottom-right. Color = White.\tState: PolygonOffset(0, -2), POLYGON_OFFSET_FILL disabled." << TestLog::EndMessage;
+
+		gl.polygonOffset			(0, -2);
+		gl.disable					(GL_POLYGON_OFFSET_FILL);
+		gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+		gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+		//draw front
+
+		log << TestLog::Message << "Draw bottom-right. Color = Red.\tState: PolygonOffset(0, -1), POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		gl.polygonOffset			(0, -1);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.vertexAttrib4f			(colorLoc, 1.0f, 0.0f, 0.0f, 1.0f);
+		gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	{
+		rr::Renderer		referenceRenderer;
+		rr::VertexAttrib	attribs[2];
+		rr::RenderState		state((rr::ViewportState)(rr::WindowRectangle(0, 0, m_targetSize, m_targetSize)));
+
+		PositionColorShader program;
+
+		attribs[0].type				= rr::VERTEXATTRIBTYPE_FLOAT;
+		attribs[0].size				= 4;
+		attribs[0].stride			= 0;
+		attribs[0].instanceDivisor	= 0;
+		attribs[0].pointer			= triangle;
+
+		attribs[1].type				= rr::VERTEXATTRIBTYPE_DONT_CARE;
+		attribs[1].generic			= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+
+		tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+		log << TestLog::Message << "Expecting: Bottom-right = Red." << TestLog::EndMessage;
+
+		referenceRenderer.draw(
+			rr::DrawCommand(
+				state,
+				rr::RenderTarget(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(referenceImage.getAccess())),
+				rr::Program(program.getVertexShader(), program.getFragmentShader()),
+				2,
+				attribs,
+				rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, 3, 0)));
+	}
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// UsageDisplacementTestCase
+
+class UsageDisplacementTestCase : public PolygonOffsetTestCase
+{
+public:
+				UsageDisplacementTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+private:
+	tcu::Vec4	genRandomVec4				(de::Random& rnd) const;
+	void		testPolygonOffset			(void);
+};
+
+UsageDisplacementTestCase::UsageDisplacementTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+tcu::Vec4 UsageDisplacementTestCase::genRandomVec4 (de::Random& rnd) const
+{
+	// generater triangle endpoint with following properties
+	//	1) it will not be clipped
+	//	2) it is not near either far or near plane to prevent possible problems related to depth clamping
+	// => w >= 1.0 and z in (-0.9, 0.9) range
+	tcu::Vec4 retVal;
+
+	retVal.x() = rnd.getFloat(-1, 1);
+	retVal.y() = rnd.getFloat(-1, 1);
+	retVal.z() = rnd.getFloat(-0.9f, 0.9f);
+	retVal.w() = 1.0f + rnd.getFloat();
+
+	return retVal;
+}
+
+void UsageDisplacementTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	de::Random			rnd				(0xdec0de);
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// render test image
+	{
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program			(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc		= gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc		= gl.getAttribLocation(program.getProgram(), "a_color");
+		const int					numIterations	= 40;
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+		gl.vertexAttrib4f			(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+
+		log << TestLog::Message << "Framebuffer cleared, clear color = Black." << TestLog::EndMessage;
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		// draw colorless (mask = 0,0,0) triangle at random* location, set offset and render green triangle with depthfunc = equal
+		// *) w >= 1.0 and z in (-1, 1) range
+		for (int iterationNdx = 0; iterationNdx < numIterations; ++iterationNdx)
+		{
+			const bool		offsetDirection = rnd.getBool();
+			const float		offset = offsetDirection ? -1.0f : 1.0f;
+			tcu::Vec4		triangle[3];
+
+			for (int vertexNdx = 0; vertexNdx < DE_LENGTH_OF_ARRAY(triangle); ++vertexNdx)
+				triangle[vertexNdx] = genRandomVec4(rnd);
+
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangle);
+
+			log << TestLog::Message << "Setup triangle with random coordinates:" << TestLog::EndMessage;
+			for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(triangle); ++ndx)
+				log << TestLog::Message
+						<< "\tx=" << triangle[ndx].x()
+						<< "\ty=" << triangle[ndx].y()
+						<< "\tz=" << triangle[ndx].z()
+						<< "\tw=" << triangle[ndx].w()
+						<< TestLog::EndMessage;
+
+			log << TestLog::Message << "Draw colorless triangle.\tState: DepthFunc = GL_ALWAYS, PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, 0);
+			gl.colorMask				(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			// all fragments should have different Z => DepthFunc == GL_EQUAL fails with every fragment
+
+			log << TestLog::Message << "Draw green triangle.\tState: DepthFunc = GL_EQUAL, PolygonOffset(0, " << offset << ")." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_EQUAL);
+			gl.polygonOffset			(0, offset);
+			gl.colorMask				(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << TestLog::EndMessage; // empty line for clarity
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	log << TestLog::Message << "Expecting black framebuffer." << TestLog::EndMessage;
+	tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// UsagePositiveNegativeTestCase
+
+class UsagePositiveNegativeTestCase : public PolygonOffsetTestCase
+{
+public:
+			UsagePositiveNegativeTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset				(void);
+};
+
+UsagePositiveNegativeTestCase::UsagePositiveNegativeTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void UsagePositiveNegativeTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangleBottomRight[] =
+	{
+		tcu::Vec4(-1,  1,  0,  1),
+		tcu::Vec4( 1,  1,  0,  1),
+		tcu::Vec4( 1, -1,  0,  1),
+	};
+	const tcu::Vec4 triangleTopLeft[] =
+	{
+		tcu::Vec4(-1, -1,  0,  1),
+		tcu::Vec4(-1,  1,  0,  1),
+		tcu::Vec4( 1, -1,  0,  1),
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// render test image
+	{
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program			(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc		= gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc		= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.depthFunc				(GL_LESS);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+
+		log << TestLog::Message << "DepthFunc = GL_LESS." << TestLog::EndMessage;
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		//draw top left (negative offset test)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleTopLeft);
+
+			log << TestLog::Message << "Draw top-left. Color = White.\tState: PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.polygonOffset			(0, 0);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw top-left. Color = Green.\tState: PolygonOffset(0, -1)." << TestLog::EndMessage;
+
+			gl.polygonOffset			(0, -1);
+			gl.vertexAttrib4f			(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		//draw bottom right (positive offset test)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleBottomRight);
+
+			log << TestLog::Message << "Draw bottom-right. Color = White.\tState: PolygonOffset(0, 1)." << TestLog::EndMessage;
+
+			gl.polygonOffset			(0, 1);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw bottom-right. Color = Yellow.\tState: PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.polygonOffset			(0, 0);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	{
+		rr::Renderer		referenceRenderer;
+		rr::VertexAttrib	attribs[2];
+		rr::RenderState		state((rr::ViewportState)(rr::WindowRectangle(0, 0, m_targetSize, m_targetSize)));
+
+		PositionColorShader program;
+
+		attribs[0].type				= rr::VERTEXATTRIBTYPE_FLOAT;
+		attribs[0].size				= 4;
+		attribs[0].stride			= 0;
+		attribs[0].instanceDivisor	= 0;
+		attribs[0].pointer			= triangleTopLeft;
+
+		attribs[1].type				= rr::VERTEXATTRIBTYPE_DONT_CARE;
+		attribs[1].generic			= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+
+		tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+		log << TestLog::Message << "Expecting: Top-left = Green, Bottom-right = Yellow." << TestLog::EndMessage;
+
+		referenceRenderer.draw(
+			rr::DrawCommand(
+				state,
+				rr::RenderTarget(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(referenceImage.getAccess())),
+				rr::Program(program.getVertexShader(), program.getFragmentShader()),
+				2,
+				attribs,
+				rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, 3, 0)));
+
+		attribs[0].pointer = triangleBottomRight;
+		attribs[1].generic = tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
+
+		referenceRenderer.draw(
+			rr::DrawCommand(
+				state,
+				rr::RenderTarget(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(referenceImage.getAccess())),
+				rr::Program(program.getVertexShader(), program.getFragmentShader()),
+				2,
+				attribs,
+				rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, 3, 0)));
+	}
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// ResultClampingTestCase
+
+class ResultClampingTestCase : public PolygonOffsetTestCase
+{
+public:
+			ResultClampingTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset		(void);
+};
+
+ResultClampingTestCase::ResultClampingTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void ResultClampingTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangleBottomRight[] =
+	{
+		tcu::Vec4(-1,  1,  1,  1),
+		tcu::Vec4( 1,  1,  1,  1),
+		tcu::Vec4( 1, -1,  1,  1),
+	};
+	const tcu::Vec4 triangleTopLeft[] =
+	{
+		tcu::Vec4(-1, -1, -1,  1),
+		tcu::Vec4(-1,  1, -1,  1),
+		tcu::Vec4( 1, -1, -1,  1),
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// render test image
+	{
+		const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program		(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc = gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc	= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		//draw bottom right (far)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleBottomRight);
+
+			log << TestLog::Message << "Draw bottom-right. Color = White.\tState: DepthFunc = ALWAYS, PolygonOffset(0, 8), Polygon Z = 1.0. (Result depth should clamp to 1.0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, 8);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw bottom-right. Color = Red.\tState: DepthFunc = GREATER, PolygonOffset(0, 9), Polygon Z = 1.0. (Result depth should clamp to 1.0 too)" << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_GREATER);
+			gl.polygonOffset			(0, 9);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 0.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		//draw top left (near)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleTopLeft);
+
+			log << TestLog::Message << "Draw top-left. Color = White.\tState: DepthFunc = ALWAYS, PolygonOffset(0, -8), Polygon Z = -1.0. (Result depth should clamp to -1.0)" << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, -8);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw top-left. Color = Yellow.\tState: DepthFunc = LESS, PolygonOffset(0, -9), Polygon Z = -1.0. (Result depth should clamp to -1.0 too)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_LESS);
+			gl.polygonOffset			(0, -9);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	log << TestLog::Message << "Expecting: Top-left = White, Bottom-right = White." << TestLog::EndMessage;
+	tcu::clear(referenceImage.getAccess(), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// UsageSlopeTestCase
+
+class UsageSlopeTestCase  : public PolygonOffsetTestCase
+{
+public:
+			UsageSlopeTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset	(void);
+};
+
+UsageSlopeTestCase::UsageSlopeTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void UsageSlopeTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangleBottomRight[] =
+	{
+		tcu::Vec4(-1,  1,  0.0f,  1),
+		tcu::Vec4( 1,  1,  0.9f,  1),
+		tcu::Vec4( 1, -1,  0.9f,  1),
+	};
+	const tcu::Vec4 triangleTopLeft[] =
+	{
+		tcu::Vec4(-1, -1,  -0.9f,  1),
+		tcu::Vec4(-1,  1,   0.9f,  1),
+		tcu::Vec4( 1, -1,   0.0f,  1),
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// render test image
+	{
+		const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program		(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc = gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc	= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		//draw top left (negative offset test)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleTopLeft);
+
+			log << TestLog::Message << "Draw top-left. Color = White.\tState: DepthFunc = ALWAYS, PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, 0);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw top-left. Color = Green.\tState: DepthFunc = LESS, PolygonOffset(-1, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_LESS);
+			gl.polygonOffset			(-1, 0);
+			gl.vertexAttrib4f			(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		//draw bottom right (positive offset test)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleBottomRight);
+
+			log << TestLog::Message << "Draw bottom-right. Color = White.\tState: DepthFunc = ALWAYS, PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, 0);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw bottom-right. Color = Green.\tState: DepthFunc = GREATER, PolygonOffset(1, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_GREATER);
+			gl.polygonOffset			(1, 0);
+			gl.vertexAttrib4f			(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	log << TestLog::Message << "Expecting: Top-left = Green, Bottom-right = Green." << TestLog::EndMessage;
+	tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// ZeroSlopeTestCase
+
+class ZeroSlopeTestCase : public PolygonOffsetTestCase
+{
+public:
+			ZeroSlopeTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset	(void);
+};
+
+ZeroSlopeTestCase::ZeroSlopeTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void ZeroSlopeTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangle[] =
+	{
+		tcu::Vec4(-0.4f,  0.4f, 0.0f, 1.0f),
+		tcu::Vec4(-0.8f, -0.5f, 0.0f, 1.0f),
+		tcu::Vec4( 0.7f,  0.2f, 0.0f, 1.0f),
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// log the triangle
+	log << TestLog::Message << "Setup triangle with coordinates:" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(triangle); ++ndx)
+		log << TestLog::Message
+				<< "\tx=" << triangle[ndx].x()
+				<< "\ty=" << triangle[ndx].y()
+				<< "\tz=" << triangle[ndx].z()
+				<< "\tw=" << triangle[ndx].w()
+				<< TestLog::EndMessage;
+
+	// render test image
+	{
+		const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program		(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc = gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc	= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangle);
+
+			log << TestLog::Message << "Draw triangle. Color = Red.\tState: DepthFunc = ALWAYS, PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, 0);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 0.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw triangle. Color = Black.\tState: DepthFunc = EQUAL, PolygonOffset(4, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_EQUAL);
+			gl.polygonOffset			(4, 0);	// triangle slope == 0
+			gl.vertexAttrib4f			(colorLoc, 0.0f, 0.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	log << TestLog::Message << "Expecting black triangle." << TestLog::EndMessage;
+	tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// OneSlopeTestCase
+
+class OneSlopeTestCase : public PolygonOffsetTestCase
+{
+public:
+			OneSlopeTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset	(void);
+};
+
+OneSlopeTestCase::OneSlopeTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void OneSlopeTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	/*
+		* setup vertices subject to following properties
+		*   dz_w / dx_w == 1
+		*   dz_w / dy_w == 0
+		* or
+		*   dz_w / dx_w == 0
+		*   dz_w / dy_w == 1
+		* ==> m == 1
+		*/
+	const float cornerDepth = float(m_targetSize);
+	const tcu::Vec4 triangles[2][3] =
+	{
+		{
+			tcu::Vec4(-1, -1, -cornerDepth, 1),
+			tcu::Vec4(-1,  1, -cornerDepth, 1),
+			tcu::Vec4( 1, -1,  cornerDepth, 1),
+		},
+		{
+			tcu::Vec4(-1,  1,  cornerDepth, 1),
+			tcu::Vec4( 1,  1,  cornerDepth, 1),
+			tcu::Vec4( 1, -1, -cornerDepth, 1),
+		},
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// log triangle info
+	log << TestLog::Message << "Setup triangle0 coordinates: (slope in window coordinates = 1.0)" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(triangles[0]); ++ndx)
+		log << TestLog::Message
+				<< "\tx=" << triangles[0][ndx].x()
+				<< "\ty=" << triangles[0][ndx].y()
+				<< "\tz=" << triangles[0][ndx].z()
+				<< "\tw=" << triangles[0][ndx].w()
+				<< TestLog::EndMessage;
+	log << TestLog::Message << "Setup triangle1 coordinates: (slope in window coordinates = 1.0)" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(triangles[1]); ++ndx)
+		log << TestLog::Message
+				<< "\tx=" << triangles[1][ndx].x()
+				<< "\ty=" << triangles[1][ndx].y()
+				<< "\tz=" << triangles[1][ndx].z()
+				<< "\tw=" << triangles[1][ndx].w()
+				<< TestLog::EndMessage;
+
+	// render test image
+	{
+		const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program		(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc = gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc	= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clear					(GL_COLOR_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+
+		log << TestLog::Message << "Framebuffer cleared, clear color = Black." << TestLog::EndMessage;
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		// top left (positive offset)
+		{
+			log << TestLog::Message << "Clear depth to 1.0." << TestLog::EndMessage;
+
+			gl.clearDepthf			(1.0f); // far
+			gl.clear				(GL_DEPTH_BUFFER_BIT);
+
+			gl.vertexAttribPointer	(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangles[0]);
+
+			log << TestLog::Message << "Draw triangle0. Color = Red.\tState: DepthFunc = NOTEQUAL, PolygonOffset(10, 0). (Result depth should clamp to 1.0)." << TestLog::EndMessage;
+
+			gl.polygonOffset		(10, 0);		// clamps any depth on the triangle to 1
+			gl.depthFunc			(GL_NOTEQUAL);
+			gl.vertexAttrib4f		(colorLoc, 1.0f, 0.0f, 0.0f, 1.0f);
+			gl.drawArrays			(GL_TRIANGLES, 0, 3);
+		}
+		// bottom right (negative offset)
+		{
+			log << TestLog::Message << "Clear depth to 0.0." << TestLog::EndMessage;
+
+			gl.clearDepthf			(0.0f); // far
+			gl.clear				(GL_DEPTH_BUFFER_BIT);
+
+			gl.vertexAttribPointer	(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangles[1]);
+
+			log << TestLog::Message << "Draw triangle1. Color = Green.\tState: DepthFunc = NOTEQUAL, PolygonOffset(-10, 0). (Result depth should clamp to 0.0)." << TestLog::EndMessage;
+
+			gl.polygonOffset		(-10, 0); // clamps depth to 0
+			gl.depthFunc			(GL_NOTEQUAL);
+			gl.vertexAttrib4f		(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays			(GL_TRIANGLES, 0, 3);
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	log << TestLog::Message << "Expecting black framebuffer." << TestLog::EndMessage;
+	tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+} // anonymous
+
+PolygonOffsetTests::PolygonOffsetTests (Context& context)
+	: TestCaseGroup(context, "polygon_offset", "Polygon offset tests")
+{
+}
+
+PolygonOffsetTests::~PolygonOffsetTests (void)
+{
+}
+
+void PolygonOffsetTests::init (void)
+{
+	const struct DepthBufferFormat
+	{
+		enum BufferType
+		{
+			TYPE_FIXED_POINT,
+			TYPE_FLOATING_POINT,
+			TYPE_UNKNOWN
+		};
+
+		GLenum		internalFormat;
+		int			bits;
+		BufferType	floatingPoint;
+		const char* name;
+	} depthFormats[]=
+	{
+		{ 0,						0,		DepthBufferFormat::TYPE_UNKNOWN,		"default" },
+		{ GL_DEPTH_COMPONENT16,		16,		DepthBufferFormat::TYPE_FIXED_POINT,	"fixed16" },
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthFormats); ++ndx)
+	{
+		const DepthBufferFormat& format = depthFormats[ndx];
+
+		// enable works?
+		addChild(new UsageTestCase(m_context, (std::string(format.name) + "_enable").c_str(), "test enable GL_POLYGON_OFFSET_FILL", format.internalFormat, format.name));
+
+		// Really moves the polygons ?
+		addChild(new UsageDisplacementTestCase(m_context, (std::string(format.name) + "_displacement_with_units").c_str(), "test polygon offset", format.internalFormat, format.name));
+
+		// Really moves the polygons to right direction ?
+		addChild(new UsagePositiveNegativeTestCase(m_context, (std::string(format.name) + "_render_with_units").c_str(), "test polygon offset", format.internalFormat, format.name));
+
+		// Is total result clamped to [0,1] like promised?
+		addChild(new ResultClampingTestCase(m_context, (std::string(format.name) + "_result_depth_clamp").c_str(), "test polygon offset clamping", format.internalFormat, format.name));
+
+		// Slope really moves the polygon?
+		addChild(new UsageSlopeTestCase(m_context, (std::string(format.name) + "_render_with_factor").c_str(), "test polygon offset factor", format.internalFormat, format.name));
+
+		// Factor with zero slope
+		addChild(new ZeroSlopeTestCase(m_context, (std::string(format.name) + "_factor_0_slope").c_str(), "test polygon offset factor", format.internalFormat, format.name));
+
+		// Factor with 1.0 slope
+		addChild(new OneSlopeTestCase(m_context, (std::string(format.name) + "_factor_1_slope").c_str(), "test polygon offset factor", format.internalFormat, format.name));
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fPolygonOffsetTests.hpp b/modules/gles2/functional/es2fPolygonOffsetTests.hpp
new file mode 100644
index 0000000..94f1f85
--- /dev/null
+++ b/modules/gles2/functional/es2fPolygonOffsetTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FPOLYGONOFFSETTESTS_HPP
+#define _ES2FPOLYGONOFFSETTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Polygon offset tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class PolygonOffsetTests : public TestCaseGroup
+{
+public:
+						PolygonOffsetTests		(Context& context);
+	virtual				~PolygonOffsetTests		(void);
+	virtual void		init					(void);
+
+private:
+						PolygonOffsetTests		(const PolygonOffsetTests&);
+	PolygonOffsetTests&	operator=				(const PolygonOffsetTests&);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FPOLYGONOFFSETTESTS_HPP
diff --git a/modules/gles2/functional/es2fPrerequisiteTests.cpp b/modules/gles2/functional/es2fPrerequisiteTests.cpp
new file mode 100644
index 0000000..b2bf495
--- /dev/null
+++ b/modules/gles2/functional/es2fPrerequisiteTests.cpp
@@ -0,0 +1,287 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Prerequisite tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fPrerequisiteTests.hpp"
+#include "deRandom.h"
+#include "tcuRGBA.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStateReset.hpp"
+
+#include "glw.h"
+
+using tcu::RGBA;
+using tcu::Surface;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class StateResetCase : public TestCase
+{
+public:
+										StateResetCase	(Context& context);
+	virtual								~StateResetCase	(void);
+	virtual TestCase::IterateResult		iterate			(void);
+};
+
+StateResetCase::StateResetCase (Context& context)
+	: TestCase(context, "state_reset", "State Reset Test")
+{
+}
+
+StateResetCase::~StateResetCase (void)
+{
+}
+
+TestCase::IterateResult StateResetCase::iterate (void)
+{
+	try
+	{
+		glu::resetState(m_context.getRenderContext());
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	catch (const tcu::TestError& e)
+	{
+		m_testCtx.getLog() << e;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	}
+
+	return TestCase::STOP;
+}
+
+class ClearColorCase : public TestCase
+{
+public:
+										ClearColorCase		(Context& context);
+	virtual								~ClearColorCase		(void);
+	virtual TestCase::IterateResult		iterate				(void);
+
+private:
+	RGBA		m_clearColor;
+	int			m_numIters;
+	int			m_curIter;
+};
+
+ClearColorCase::ClearColorCase (Context& context)
+	: TestCase		(context, "clear_color", "glClearColor test")
+	, m_numIters	(10)
+	, m_curIter		(0)
+{
+}
+
+ClearColorCase::~ClearColorCase (void)
+{
+}
+
+TestCase::IterateResult ClearColorCase::iterate (void)
+{
+	int r = 0;
+	int g = 0;
+	int b = 0;
+	int a = 255;
+
+	switch (m_curIter)
+	{
+		case 0:
+			// Black, skip
+			break;
+		case 1:
+			r = 255;
+			g = 255;
+			b = 255;
+			break;
+		case 2:
+			r = 255;
+			break;
+		case 3:
+			g = 255;
+			break;
+		case 4:
+			b = 255;
+			break;
+		default:
+			deRandom rnd;
+			deRandom_init(&rnd, deInt32Hash(m_curIter));
+			r = (int)(deRandom_getUint32(&rnd) & 0xFF);
+			g = (int)(deRandom_getUint32(&rnd) & 0xFF);
+			b = (int)(deRandom_getUint32(&rnd) & 0xFF);
+			a = (int)(deRandom_getUint32(&rnd) & 0xFF);
+			break;
+
+	};
+
+	glClearColor(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	GLU_CHECK_MSG("CLES2 ClearColor failed.");
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	return (++m_curIter < m_numIters) ? CONTINUE : STOP;
+}
+
+class ReadPixelsCase : public TestCase
+{
+public:
+										ReadPixelsCase		(Context& context);
+	virtual								~ReadPixelsCase		(void);
+	virtual TestCase::IterateResult		iterate				(void);
+
+private:
+	int m_numIters;
+	int m_curIter;
+};
+
+ReadPixelsCase::ReadPixelsCase (Context& context)
+	: TestCase(context, "read_pixels", "Read pixels test")
+	, m_numIters(20)
+	, m_curIter(0)
+{
+}
+
+ReadPixelsCase::~ReadPixelsCase (void)
+{
+}
+
+TestCase::IterateResult ReadPixelsCase::iterate (void)
+{
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderTarget();
+	tcu::PixelFormat			pixelFormat		= renderTarget.getPixelFormat();
+	int							targetWidth		= renderTarget.getWidth();
+	int							targetHeight	= renderTarget.getHeight();
+	int							x				= 0;
+	int							y				= 0;
+	int							imageWidth		= 0;
+	int							imageHeight		= 0;
+
+	deRandom rnd;
+	deRandom_init(&rnd, deInt32Hash(m_curIter));
+
+	switch (m_curIter)
+	{
+		case 0:
+			// Fullscreen
+			x = 0;
+			y = 0;
+			imageWidth  = targetWidth;
+			imageHeight = targetHeight;
+			break;
+		case 1:
+			// Upper left corner
+			x = 0;
+			y = 0;
+			imageWidth = targetWidth / 2;
+			imageHeight = targetHeight / 2;
+			break;
+		case 2:
+			// Lower right corner
+			x = targetWidth / 2;
+			y = targetHeight / 2;
+			imageWidth = targetWidth - x;
+			imageHeight = targetHeight - y;
+			break;
+		default:
+			x = deRandom_getUint32(&rnd) % (targetWidth - 1);
+			y = deRandom_getUint32(&rnd) % (targetHeight - 1);
+			imageWidth = 1 + (deRandom_getUint32(&rnd) % (targetWidth - x - 1));
+			imageHeight = 1 + (deRandom_getUint32(&rnd) % (targetHeight - y - 1));
+			break;
+	}
+
+	Surface	resImage(imageWidth, imageHeight);
+	Surface	refImage(imageWidth, imageHeight);
+	Surface	diffImage(imageWidth, imageHeight);
+
+	int r = (int)(deRandom_getUint32(&rnd) & 0xFF);
+	int g = (int)(deRandom_getUint32(&rnd) & 0xFF);
+	int b = (int)(deRandom_getUint32(&rnd) & 0xFF);
+
+	tcu::clear(refImage.getAccess(), tcu::IVec4(r, g, b, 255));
+	glClearColor(r/255.0f, g/255.0f, b/255.0f, 1.0f);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	glu::readPixels(m_context.getRenderContext(), x, y, resImage.getAccess());
+	GLU_CHECK_MSG("glReadPixels() failed.");
+
+	RGBA colorThreshold = pixelFormat.getColorThreshold();
+	RGBA matchColor(0, 255, 0, 255);
+	RGBA diffColor(255, 0, 0, 255);
+	bool isImageOk = true;
+
+	for (int j = 0; j < imageHeight; j++)
+	{
+		for (int i = 0; i < imageWidth; i++)
+		{
+			RGBA		resRGBA		= resImage.getPixel(i, j);
+			RGBA		refRGBA		= refImage.getPixel(i, j);
+			bool		isPixelOk	= compareThreshold(refRGBA, resRGBA, colorThreshold);
+			diffImage.setPixel(i, j, isPixelOk ? matchColor : diffColor);
+
+			isImageOk = isImageOk && isPixelOk;
+		}
+	}
+
+	if (isImageOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+		TestLog& log = m_testCtx.getLog();
+		log << TestLog::ImageSet("Result", "Resulting framebuffer")
+			<< TestLog::Image("Result",		"Resulting framebuffer",	resImage)
+			<< TestLog::Image("Reference",	"Reference image",			refImage)
+			<< TestLog::Image("DiffMask",	"Failing pixels",			diffImage)
+			<< TestLog::EndImageSet;
+	}
+
+	return (++m_curIter < m_numIters) ? CONTINUE : STOP;
+}
+
+PrerequisiteTests::PrerequisiteTests (Context& context)
+	: TestCaseGroup(context, "prerequisite", "Prerequisite Test Cases")
+{
+}
+
+PrerequisiteTests::~PrerequisiteTests (void)
+{
+}
+
+void PrerequisiteTests::init (void)
+{
+	addChild(new StateResetCase(m_context));
+	addChild(new ClearColorCase(m_context));
+	addChild(new ReadPixelsCase(m_context));
+}
+
+} // Functional
+} // gles
+} // deqp
diff --git a/modules/gles2/functional/es2fPrerequisiteTests.hpp b/modules/gles2/functional/es2fPrerequisiteTests.hpp
new file mode 100644
index 0000000..913c9ca
--- /dev/null
+++ b/modules/gles2/functional/es2fPrerequisiteTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FPREREQUISITETESTS_HPP
+#define _ES2FPREREQUISITETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Prerequisite tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class PrerequisiteTests : public TestCaseGroup
+{
+public:
+						PrerequisiteTests		(Context& context);
+	virtual				~PrerequisiteTests		(void);
+	virtual void		init					(void);
+
+private:
+						PrerequisiteTests		(const PrerequisiteTests&);
+	PrerequisiteTests&	operator=				(const PrerequisiteTests&);
+};
+
+} // Functional
+} // gles
+} // deqp
+
+#endif // _ES2FPREREQUISITETESTS_HPP
diff --git a/modules/gles2/functional/es2fRandomFragmentOpTests.cpp b/modules/gles2/functional/es2fRandomFragmentOpTests.cpp
new file mode 100644
index 0000000..343278b
--- /dev/null
+++ b/modules/gles2/functional/es2fRandomFragmentOpTests.cpp
@@ -0,0 +1,416 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Randomized per-fragment operation tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fRandomFragmentOpTests.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "glsInteractionTestUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluRenderContext.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "rrFragmentOperations.hpp"
+#include "sglrReferenceUtils.hpp"
+
+#include <algorithm>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using std::vector;
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::BVec4;
+
+enum
+{
+	VIEWPORT_WIDTH				= 64,
+	VIEWPORT_HEIGHT				= 64,
+	NUM_CALLS_PER_ITERATION		= 3,
+	NUM_ITERATIONS_PER_CASE		= 10
+};
+
+static const tcu::Vec4		CLEAR_COLOR			(0.25f, 0.5f, 0.75f, 1.0f);
+static const float			CLEAR_DEPTH			= 1.0f;
+static const int			CLEAR_STENCIL		= 0;
+static const bool			ENABLE_CALL_LOG		= true;
+
+using namespace gls::FragmentOpUtil;
+using namespace gls::InteractionTestUtil;
+
+void translateStencilState (const StencilState& src, rr::StencilState& dst)
+{
+	dst.func		= sglr::rr_util::mapGLTestFunc(src.function);
+	dst.ref			= src.reference;
+	dst.compMask	= src.compareMask;
+	dst.sFail		= sglr::rr_util::mapGLStencilOp(src.stencilFailOp);
+	dst.dpFail		= sglr::rr_util::mapGLStencilOp(src.depthFailOp);
+	dst.dpPass		= sglr::rr_util::mapGLStencilOp(src.depthPassOp);
+	dst.writeMask	= src.writeMask;
+}
+
+void translateBlendState (const BlendState& src, rr::BlendState& dst)
+{
+	dst.equation	= sglr::rr_util::mapGLBlendEquation(src.equation);
+	dst.srcFunc		= sglr::rr_util::mapGLBlendFunc(src.srcFunc);
+	dst.dstFunc		= sglr::rr_util::mapGLBlendFunc(src.dstFunc);
+}
+
+void translateState (const RenderState& src, rr::FragmentOperationState& dst, const tcu::RenderTarget& renderTarget)
+{
+	bool hasDepth		= renderTarget.getDepthBits() > 0;
+	bool hasStencil		= renderTarget.getStencilBits() > 0;
+
+	dst.scissorTestEnabled		= src.scissorTestEnabled;
+	dst.scissorRectangle		= src.scissorRectangle;
+	dst.stencilTestEnabled		= hasStencil && src.stencilTestEnabled;
+	dst.depthTestEnabled		= hasDepth && src.depthTestEnabled;
+	dst.blendMode				= src.blendEnabled ? rr::BLENDMODE_STANDARD : rr::BLENDMODE_NONE;
+	dst.numStencilBits			= renderTarget.getStencilBits();
+
+	dst.colorMask = src.colorMask;
+
+	if (dst.depthTestEnabled)
+	{
+		dst.depthFunc	= sglr::rr_util::mapGLTestFunc(src.depthFunc);
+		dst.depthMask	= src.depthWriteMask;
+	}
+
+	if (dst.stencilTestEnabled)
+	{
+		translateStencilState(src.stencil[rr::FACETYPE_BACK],	dst.stencilStates[rr::FACETYPE_BACK]);
+		translateStencilState(src.stencil[rr::FACETYPE_FRONT],	dst.stencilStates[rr::FACETYPE_FRONT]);
+	}
+
+	if (src.blendEnabled)
+	{
+		translateBlendState(src.blendRGBState, dst.blendRGBState);
+		translateBlendState(src.blendAState, dst.blendAState);
+		dst.blendColor = tcu::clamp(src.blendColor, Vec4(0.0f), Vec4(1.0f));
+	}
+}
+
+static void renderQuad (const glw::Functions& gl, gls::FragmentOpUtil::QuadRenderer& renderer, const gls::FragmentOpUtil::IntegerQuad& quad, int baseX, int baseY)
+{
+	gls::FragmentOpUtil::Quad translated;
+
+	std::copy(DE_ARRAY_BEGIN(quad.color), DE_ARRAY_END(quad.color), DE_ARRAY_BEGIN(translated.color));
+
+	bool	flipX		= quad.posB.x() < quad.posA.x();
+	bool	flipY		= quad.posB.y() < quad.posA.y();
+	int		viewportX	= de::min(quad.posA.x(), quad.posB.x());
+	int		viewportY	= de::min(quad.posA.y(), quad.posB.y());
+	int		viewportW	= de::abs(quad.posA.x()-quad.posB.x())+1;
+	int		viewportH	= de::abs(quad.posA.y()-quad.posB.y())+1;
+
+	translated.posA = Vec2(flipX ? 1.0f : -1.0f, flipY ? 1.0f : -1.0f);
+	translated.posB = Vec2(flipX ? -1.0f : 1.0f, flipY ? -1.0f : 1.0f);
+
+	// \todo [2012-12-18 pyry] Pass in DepthRange parameters.
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(quad.depth); ndx++)
+		translated.depth[ndx] = quad.depth[ndx]*2.0f - 1.0f;
+
+	gl.viewport(baseX+viewportX, baseY+viewportY, viewportW, viewportH);
+	renderer.render(translated);
+}
+
+static void setGLState (glu::CallLogWrapper& wrapper, const RenderState& state, int viewportX, int viewportY)
+{
+	if (state.scissorTestEnabled)
+	{
+		wrapper.glEnable(GL_SCISSOR_TEST);
+		wrapper.glScissor(viewportX+state.scissorRectangle.left, viewportY+state.scissorRectangle.bottom,
+						  state.scissorRectangle.width, state.scissorRectangle.height);
+	}
+	else
+		wrapper.glDisable(GL_SCISSOR_TEST);
+
+	if (state.stencilTestEnabled)
+	{
+		wrapper.glEnable(GL_STENCIL_TEST);
+
+		for (int face = 0; face < rr::FACETYPE_LAST; face++)
+		{
+			deUint32				glFace	= face == rr::FACETYPE_BACK ? GL_BACK : GL_FRONT;
+			const StencilState&		sParams	= state.stencil[face];
+
+			wrapper.glStencilFuncSeparate(glFace, sParams.function, sParams.reference, sParams.compareMask);
+			wrapper.glStencilOpSeparate(glFace, sParams.stencilFailOp, sParams.depthFailOp, sParams.depthPassOp);
+			wrapper.glStencilMaskSeparate(glFace, sParams.writeMask);
+		}
+	}
+	else
+		wrapper.glDisable(GL_STENCIL_TEST);
+
+	if (state.depthTestEnabled)
+	{
+		wrapper.glEnable(GL_DEPTH_TEST);
+		wrapper.glDepthFunc(state.depthFunc);
+		wrapper.glDepthMask(state.depthWriteMask ? GL_TRUE : GL_FALSE);
+	}
+	else
+		wrapper.glDisable(GL_DEPTH_TEST);
+
+	if (state.blendEnabled)
+	{
+		wrapper.glEnable(GL_BLEND);
+		wrapper.glBlendEquationSeparate(state.blendRGBState.equation, state.blendAState.equation);
+		wrapper.glBlendFuncSeparate(state.blendRGBState.srcFunc, state.blendRGBState.dstFunc, state.blendAState.srcFunc, state.blendAState.dstFunc);
+		wrapper.glBlendColor(state.blendColor.x(), state.blendColor.y(), state.blendColor.z(), state.blendColor.w());
+	}
+	else
+		wrapper.glDisable(GL_BLEND);
+
+	if (state.ditherEnabled)
+		wrapper.glEnable(GL_DITHER);
+	else
+		wrapper.glDisable(GL_DITHER);
+
+	wrapper.glColorMask(state.colorMask[0] ? GL_TRUE : GL_FALSE,
+						state.colorMask[1] ? GL_TRUE : GL_FALSE,
+						state.colorMask[2] ? GL_TRUE : GL_FALSE,
+						state.colorMask[3] ? GL_TRUE : GL_FALSE);
+}
+
+class RandomFragmentOpCase : public TestCase
+{
+public:
+						RandomFragmentOpCase		(Context& context, const char* name, const char* desc, deUint32 seed);
+						~RandomFragmentOpCase		(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+private:
+	tcu::UVec4			getCompareThreshold			(void) const;
+
+	deUint32										m_seed;
+
+	glu::CallLogWrapper						m_callLogWrapper;
+
+	gls::FragmentOpUtil::QuadRenderer*				m_renderer;
+	tcu::TextureLevel*								m_refColorBuffer;
+	tcu::TextureLevel*								m_refDepthBuffer;
+	tcu::TextureLevel*								m_refStencilBuffer;
+	gls::FragmentOpUtil::ReferenceQuadRenderer*		m_refRenderer;
+
+	int												m_iterNdx;
+};
+
+RandomFragmentOpCase::RandomFragmentOpCase (Context& context, const char* name, const char* desc, deUint32 seed)
+	: TestCase				(context, name, desc)
+	, m_seed				(seed)
+	, m_callLogWrapper		(context.getRenderContext().getFunctions(), context.getTestContext().getLog())
+	, m_renderer			(DE_NULL)
+	, m_refColorBuffer		(DE_NULL)
+	, m_refDepthBuffer		(DE_NULL)
+	, m_refStencilBuffer	(DE_NULL)
+	, m_refRenderer			(DE_NULL)
+	, m_iterNdx				(0)
+{
+	m_callLogWrapper.enableLogging(ENABLE_CALL_LOG);
+}
+
+RandomFragmentOpCase::~RandomFragmentOpCase (void)
+{
+	delete m_renderer;
+	delete m_refColorBuffer;
+	delete m_refDepthBuffer;
+	delete m_refStencilBuffer;
+	delete m_refRenderer;
+}
+
+void RandomFragmentOpCase::init (void)
+{
+	DE_ASSERT(!m_renderer && !m_refColorBuffer && !m_refDepthBuffer && !m_refStencilBuffer && !m_refRenderer);
+
+	int		width	= de::min<int>(m_context.getRenderTarget().getWidth(), VIEWPORT_WIDTH);
+	int		height	= de::min<int>(m_context.getRenderTarget().getHeight(), VIEWPORT_HEIGHT);
+	bool	useRGB	= m_context.getRenderTarget().getPixelFormat().alphaBits == 0;
+
+	m_renderer			= new gls::FragmentOpUtil::QuadRenderer(m_context.getRenderContext(), glu::GLSL_VERSION_100_ES);
+	m_refColorBuffer	= new tcu::TextureLevel(tcu::TextureFormat(useRGB ? tcu::TextureFormat::RGB : tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), width, height);
+	m_refDepthBuffer	= new tcu::TextureLevel(tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT),			width, height);
+	m_refStencilBuffer	= new tcu::TextureLevel(tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT32),	width, height);
+	m_refRenderer		= new gls::FragmentOpUtil::ReferenceQuadRenderer();
+	m_iterNdx			= 0;
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void RandomFragmentOpCase::deinit (void)
+{
+	delete m_renderer;
+	delete m_refColorBuffer;
+	delete m_refDepthBuffer;
+	delete m_refStencilBuffer;
+	delete m_refRenderer;
+
+	m_renderer			= DE_NULL;
+	m_refColorBuffer	= DE_NULL;
+	m_refDepthBuffer	= DE_NULL;
+	m_refStencilBuffer	= DE_NULL;
+	m_refRenderer		= DE_NULL;
+}
+
+RandomFragmentOpCase::IterateResult RandomFragmentOpCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const deUint32			iterSeed		= deUint32Hash(m_seed) ^ deInt32Hash(m_iterNdx) ^ deInt32Hash(m_testCtx.getCommandLine().getBaseSeed());
+	de::Random				rnd				(iterSeed);
+
+	const int				width			= m_refColorBuffer->getWidth();
+	const int				height			= m_refColorBuffer->getHeight();
+	const int				viewportX		= rnd.getInt(0, m_context.getRenderTarget().getWidth()-width);
+	const int				viewportY		= rnd.getInt(0, m_context.getRenderTarget().getHeight()-height);
+
+	tcu::Surface			renderedImg		(width, height);
+
+	const Vec4				clearColor		= CLEAR_COLOR;
+	const float				clearDepth		= CLEAR_DEPTH;
+	const int				clearStencil	= CLEAR_STENCIL;
+
+	bool					gotError		= false;
+
+	const tcu::ScopedLogSection	iterSection	(m_testCtx.getLog(), std::string("Iteration") + de::toString(m_iterNdx), std::string("Iteration ") + de::toString(m_iterNdx));
+
+	// Compute randomized rendering commands.
+	vector<RenderCommand> commands;
+	computeRandomRenderCommands(rnd, glu::ApiType::es(2,0), NUM_CALLS_PER_ITERATION, width, height, commands);
+
+	// Reset default fragment state.
+	gl.disable(GL_SCISSOR_TEST);
+	gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+	gl.depthMask(GL_TRUE);
+	gl.stencilMask(~0u);
+
+	// Render using GL.
+	m_callLogWrapper.glClearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+	m_callLogWrapper.glClearDepthf(clearDepth);
+	m_callLogWrapper.glClearStencil(clearStencil);
+	m_callLogWrapper.glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	m_callLogWrapper.glViewport(viewportX, viewportY, width, height);
+
+	for (vector<RenderCommand>::const_iterator cmd = commands.begin(); cmd != commands.end(); cmd++)
+	{
+		setGLState(m_callLogWrapper, cmd->state, viewportX, viewportY);
+
+		if (ENABLE_CALL_LOG)
+			m_testCtx.getLog() << TestLog::Message << "// Quad: " << cmd->quad.posA << " -> " << cmd->quad.posB
+												   << ", color: [" << cmd->quad.color[0] << ", " << cmd->quad.color[1] << ", " << cmd->quad.color[2] << ", " << cmd->quad.color[3] << "]"
+												   << ", depth: [" << cmd->quad.depth[0] << ", " << cmd->quad.depth[1] << ", " << cmd->quad.depth[2] << ", " << cmd->quad.depth[3] << "]"
+								<< TestLog::EndMessage;
+
+		renderQuad(gl, *m_renderer, cmd->quad, viewportX, viewportY);
+	}
+
+	// Check error.
+	if (m_callLogWrapper.glGetError() != GL_NO_ERROR)
+		gotError = true;
+
+	gl.flush();
+
+	// Render reference while GPU is doing work.
+	tcu::clear			(m_refColorBuffer->getAccess(),		clearColor);
+	tcu::clearDepth		(m_refDepthBuffer->getAccess(),		clearDepth);
+	tcu::clearStencil	(m_refStencilBuffer->getAccess(),	clearStencil);
+
+	for (vector<RenderCommand>::const_iterator cmd = commands.begin(); cmd != commands.end(); cmd++)
+	{
+		rr::FragmentOperationState refState;
+		translateState(cmd->state, refState, m_context.getRenderTarget());
+		m_refRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()),
+							  gls::FragmentOpUtil::getMultisampleAccess(m_refDepthBuffer->getAccess()),
+							  gls::FragmentOpUtil::getMultisampleAccess(m_refStencilBuffer->getAccess()),
+							  cmd->quad, refState);
+	}
+
+	// Read rendered image.
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedImg.getAccess());
+
+	m_iterNdx += 1;
+
+	// Compare to reference.
+	bool	isLastIter	= m_iterNdx >= NUM_ITERATIONS_PER_CASE;
+	bool	compareOk	= tcu::intThresholdCompare(m_testCtx.getLog(), "CompareResult", "Image Comparison Result", m_refColorBuffer->getAccess(), renderedImg.getAccess(), getCompareThreshold(),
+													 tcu::COMPARE_LOG_RESULT);
+
+	m_testCtx.getLog() << TestLog::Message << (compareOk ? "  Passed." : "  FAILED!") << TestLog::EndMessage;
+
+	if (!compareOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+	else if (gotError)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "GL error");
+
+	if (compareOk && !gotError && !isLastIter)
+		return CONTINUE;
+	else
+		return STOP;
+}
+
+tcu::UVec4 RandomFragmentOpCase::getCompareThreshold (void) const
+{
+	tcu::PixelFormat format = m_context.getRenderTarget().getPixelFormat();
+
+	if (format == tcu::PixelFormat(8, 8, 8, 8) || format == tcu::PixelFormat(8, 8, 8, 0))
+		return format.getColorThreshold().toIVec().asUint() + tcu::UVec4(2); // Default threshold.
+	else
+		return format.getColorThreshold().toIVec().asUint()
+			   * tcu::UVec4(5) + tcu::UVec4(2); // \note Non-scientific ad hoc formula. Need big threshold when few color bits; especially multiple blendings bring extra inaccuracy.
+}
+
+RandomFragmentOpTests::RandomFragmentOpTests (Context& context)
+	: TestCaseGroup(context, "random", "Randomized Per-Fragment Operation Tests")
+{
+}
+
+RandomFragmentOpTests::~RandomFragmentOpTests (void)
+{
+}
+
+void RandomFragmentOpTests::init (void)
+{
+	for (int ndx = 0; ndx < 100; ndx++)
+		addChild(new RandomFragmentOpCase(m_context, de::toString(ndx).c_str(), "", (deUint32)(ndx*NUM_ITERATIONS_PER_CASE)));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fRandomFragmentOpTests.hpp b/modules/gles2/functional/es2fRandomFragmentOpTests.hpp
new file mode 100644
index 0000000..ef02698
--- /dev/null
+++ b/modules/gles2/functional/es2fRandomFragmentOpTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FRANDOMFRAGMENTOPTESTS_HPP
+#define _ES2FRANDOMFRAGMENTOPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Randomized per-fragment operation tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class RandomFragmentOpTests : public TestCaseGroup
+{
+public:
+							RandomFragmentOpTests	(Context& context);
+							~RandomFragmentOpTests	(void);
+
+	void					init					(void);
+
+private:
+							RandomFragmentOpTests	(const RandomFragmentOpTests& other);
+	RandomFragmentOpTests&	operator=				(const RandomFragmentOpTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FRANDOMFRAGMENTOPTESTS_HPP
diff --git a/modules/gles2/functional/es2fRandomShaderTests.cpp b/modules/gles2/functional/es2fRandomShaderTests.cpp
new file mode 100644
index 0000000..c39ba19
--- /dev/null
+++ b/modules/gles2/functional/es2fRandomShaderTests.cpp
@@ -0,0 +1,373 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Random shader tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fRandomShaderTests.hpp"
+#include "glsRandomShaderCase.hpp"
+#include "deStringUtil.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+namespace
+{
+
+gls::RandomShaderCase* createRandomShaderCase (Context& context, const char* description, const rsg::ProgramParameters& baseParams, deUint32 seed, bool vertex, bool fragment)
+{
+	rsg::ProgramParameters params = baseParams;
+
+	params.seed							= seed;
+	params.vertexParameters.randomize	= vertex;
+	params.fragmentParameters.randomize	= fragment;
+
+	return new gls::RandomShaderCase(context.getTestContext(), context.getRenderContext(), de::toString(seed).c_str(), description, params);
+}
+
+class BasicExpressionGroup : public TestCaseGroup
+{
+public:
+	BasicExpressionGroup (Context& context)
+		: TestCaseGroup(context, "basic_expression", "Basic arithmetic expressions")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		tcu::TestCaseGroup* combinedGroup = new tcu::TestCaseGroup(m_testCtx, "combined", "Combined tests");
+		addChild(combinedGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Random expressions in vertex shader",					params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Random expressions in fragment shader",				params, seed, false, true));
+			combinedGroup->addChild(createRandomShaderCase(m_context,	"Random expressions in vertex and fragment shaders",	params, seed, true, true));
+		}
+	}
+};
+
+class ScalarConversionGroup : public TestCaseGroup
+{
+public:
+	ScalarConversionGroup (Context& context)
+		: TestCaseGroup(context, "scalar_conversion", "Scalar conversions")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions = true;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		tcu::TestCaseGroup* combinedGroup = new tcu::TestCaseGroup(m_testCtx, "combined", "Combined tests");
+		addChild(combinedGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Scalar conversions in vertex shader",					params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Scalar conversions in fragment shader",				params, seed, false, true));
+			combinedGroup->addChild(createRandomShaderCase(m_context,	"Scalar conversions in vertex and fragment shaders",	params, seed, true, true));
+		}
+	}
+};
+
+class SwizzleGroup : public TestCaseGroup
+{
+public:
+	SwizzleGroup (Context& context)
+		: TestCaseGroup(context, "swizzle", "Vector swizzles")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions = true;
+		params.useSwizzle			= true;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		for (int seed = 0; seed < 50; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Vector swizzles in vertex shader",		params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Vector swizzles in fragment shader",	params, seed, false, true));
+		}
+	}
+};
+
+class ComparisonOpsGroup : public TestCaseGroup
+{
+public:
+	ComparisonOpsGroup (Context& context)
+		: TestCaseGroup(context, "comparison_ops", "Comparison operators")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions = true;
+		params.useComparisonOps		= true;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		for (int seed = 0; seed < 50; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Comparison operators in vertex shader",		params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Comparison operators in fragment shader",		params, seed, false, true));
+		}
+	}
+};
+
+class ConditionalsGroup : public TestCaseGroup
+{
+public:
+	ConditionalsGroup (Context& context)
+		: TestCaseGroup(context, "conditionals", "Conditional control flow (if-else)")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions = true;
+		params.useSwizzle			= true;
+		params.useComparisonOps		= true;
+		params.useConditionals		= true;
+		params.vertexParameters.maxStatementDepth		= 4;
+		params.vertexParameters.maxStatementsPerBlock	= 5;
+		params.fragmentParameters.maxStatementDepth		= 4;
+		params.fragmentParameters.maxStatementsPerBlock	= 5;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		tcu::TestCaseGroup* combinedGroup = new tcu::TestCaseGroup(m_testCtx, "combined", "Combined tests");
+		addChild(combinedGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Conditional control flow in vertex shader",				params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Conditional control flow in fragment shader",				params, seed, false, true));
+			combinedGroup->addChild(createRandomShaderCase(m_context,	"Conditional control flow in vertex and fragment shaders",	params, seed, true, true));
+		}
+	}
+};
+
+class TrigonometricGroup : public TestCaseGroup
+{
+public:
+	TrigonometricGroup (Context& context)
+		: TestCaseGroup(context, "trigonometric", "Trigonometric built-in functions")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions		= true;
+		params.useSwizzle				= true;
+		params.trigonometricBaseWeight	= 4.0f;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Trigonometric ops in vertex shader",	params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Trigonometric ops in fragment shader",	params, seed, false, true));
+		}
+	}
+};
+
+class ExponentialGroup : public TestCaseGroup
+{
+public:
+	ExponentialGroup (Context& context)
+		: TestCaseGroup(context, "exponential", "Exponential built-in functions")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions		= true;
+		params.useSwizzle				= true;
+		params.exponentialBaseWeight	= 4.0f;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Exponential ops in vertex shader",		params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Exponential ops in fragment shader",	params, seed, false, true));
+		}
+	}
+};
+
+class TextureGroup : public TestCaseGroup
+{
+public:
+	TextureGroup (Context& context)
+		: TestCaseGroup(context, "texture", "Texture lookups")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions						= true;
+		params.useSwizzle								= true;
+		params.vertexParameters.texLookupBaseWeight		= 10.0f;
+		params.vertexParameters.useTexture2D			= true;
+		params.vertexParameters.useTextureCube			= true;
+		params.fragmentParameters.texLookupBaseWeight	= 10.0f;
+		params.fragmentParameters.useTexture2D			= true;
+		params.fragmentParameters.useTextureCube		= true;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		// Do only 50 vertex cases and 150 fragment cases.
+		for (int seed = 0; seed < 50; seed++)
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Texture lookups in vertex shader",		params, seed, true, false));
+
+		for (int seed = 0; seed < 150; seed++)
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Texture lookups in fragment shader",	params, seed, false, true));
+	}
+};
+
+class AllFeaturesGroup : public TestCaseGroup
+{
+public:
+	AllFeaturesGroup (Context& context)
+		: TestCaseGroup(context, "all_features", "All features enabled")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions		= true;
+		params.useSwizzle				= true;
+		params.useComparisonOps			= true;
+		params.useConditionals			= true;
+		params.trigonometricBaseWeight	= 1.0f;
+		params.exponentialBaseWeight	= 1.0f;
+
+		params.vertexParameters.maxStatementDepth				= 4;
+		params.vertexParameters.maxStatementsPerBlock			= 7;
+		params.vertexParameters.maxExpressionDepth				= 7;
+		params.vertexParameters.maxCombinedVariableScalars		= 64;
+		params.fragmentParameters.maxStatementDepth				= 4;
+		params.fragmentParameters.maxStatementsPerBlock			= 7;
+		params.fragmentParameters.maxExpressionDepth			= 7;
+		params.fragmentParameters.maxCombinedVariableScalars	= 64;
+
+		params.fragmentParameters.texLookupBaseWeight		= 4.0f; // \note Texture lookups are enabled for fragment shaders only.
+		params.fragmentParameters.useTexture2D				= true;
+		params.fragmentParameters.useTextureCube			= true;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Texture lookups in vertex shader",		params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Texture lookups in fragment shader",	params, seed, false, true));
+		}
+	}
+};
+
+} // anonymous
+
+RandomShaderTests::RandomShaderTests (Context& context)
+	: TestCaseGroup(context, "random", "Random shaders")
+{
+}
+
+RandomShaderTests::~RandomShaderTests (void)
+{
+}
+
+namespace
+{
+
+} // anonymous
+
+void RandomShaderTests::init (void)
+{
+	addChild(new BasicExpressionGroup	(m_context));
+	addChild(new ScalarConversionGroup	(m_context));
+	addChild(new SwizzleGroup			(m_context));
+	addChild(new ComparisonOpsGroup		(m_context));
+	addChild(new ConditionalsGroup		(m_context));
+	addChild(new TrigonometricGroup		(m_context));
+	addChild(new ExponentialGroup		(m_context));
+	addChild(new TextureGroup			(m_context));
+	addChild(new AllFeaturesGroup		(m_context));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fRandomShaderTests.hpp b/modules/gles2/functional/es2fRandomShaderTests.hpp
new file mode 100644
index 0000000..a40c1f8
--- /dev/null
+++ b/modules/gles2/functional/es2fRandomShaderTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FRANDOMSHADERTESTS_HPP
+#define _ES2FRANDOMSHADERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Random shader tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class RandomShaderTests : public TestCaseGroup
+{
+public:
+							RandomShaderTests			(Context& context);
+							~RandomShaderTests			(void);
+
+	void					init						(void);
+
+private:
+							RandomShaderTests			(const RandomShaderTests& other);
+	RandomShaderTests&		operator=					(const RandomShaderTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FRANDOMSHADERTESTS_HPP
diff --git a/modules/gles2/functional/es2fRasterizationTests.cpp b/modules/gles2/functional/es2fRasterizationTests.cpp
new file mode 100644
index 0000000..c78aad1
--- /dev/null
+++ b/modules/gles2/functional/es2fRasterizationTests.cpp
@@ -0,0 +1,1901 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Functional rasterization tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fRasterizationTests.hpp"
+#include "glsRasterizationTestUtil.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace
+{
+
+using namespace gls::RasterizationTestUtil;
+
+static const char* const s_shaderVertexTemplate =	"attribute highp vec4 a_position;\n"
+													"attribute highp vec4 a_color;\n"
+													"varying highp vec4 v_color;\n"
+													"uniform highp float u_pointSize;\n"
+													"void main ()\n"
+													"{\n"
+													"	gl_Position = a_position;\n"
+													"	gl_PointSize = u_pointSize;\n"
+													"	v_color = a_color;\n"
+													"}\n";
+static const char* const s_shaderFragmentTemplate =	"varying mediump vec4 v_color;\n"
+													"void main ()\n"
+													"{\n"
+													"	gl_FragColor = v_color;\n"
+													"}\n";
+enum InterpolationCaseFlags
+{
+	INTERPOLATIONFLAGS_NONE = 0,
+	INTERPOLATIONFLAGS_PROJECTED = (1 << 1),
+};
+
+enum PrimitiveWideness
+{
+	PRIMITIVEWIDENESS_NARROW = 0,
+	PRIMITIVEWIDENESS_WIDE,
+
+	PRIMITIVEWIDENESS_LAST
+};
+
+class BaseRenderingCase : public TestCase
+{
+public:
+							BaseRenderingCase	(Context& context, const char* name, const char* desc, int renderSize = 256);
+							~BaseRenderingCase	(void);
+	virtual void			init				(void);
+	void					deinit				(void);
+
+protected:
+	void					drawPrimitives		(tcu::Surface& result, const std::vector<tcu::Vec4>& vertexData, glw::GLenum primitiveType);
+	void					drawPrimitives		(tcu::Surface& result, const std::vector<tcu::Vec4>& vertexData, const std::vector<tcu::Vec4>& coloDrata, glw::GLenum primitiveType);
+
+	const int				m_renderSize;
+	int						m_numSamples;
+	int						m_subpixelBits;
+	float					m_pointSize;
+	float					m_lineWidth;
+
+private:
+	glu::ShaderProgram*		m_shader;
+};
+
+BaseRenderingCase::BaseRenderingCase (Context& context, const char* name, const char* desc, int renderSize)
+	: TestCase				(context, name, desc)
+	, m_renderSize			(renderSize)
+	, m_numSamples			(-1)
+	, m_subpixelBits		(-1)
+	, m_pointSize			(1.0f)
+	, m_lineWidth			(1.0f)
+	, m_shader				(DE_NULL)
+{
+}
+
+BaseRenderingCase::~BaseRenderingCase (void)
+{
+	deinit();
+}
+
+void BaseRenderingCase::init (void)
+{
+	const int width	 = m_context.getRenderTarget().getWidth();
+	const int height = m_context.getRenderTarget().getHeight();
+
+	// Requirements
+
+	if (width < m_renderSize || height < m_renderSize)
+		throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(m_renderSize) + "x" + de::toString(m_renderSize));
+
+	if (m_lineWidth != 1.0f)
+	{
+		float range[2] = { 0.0f, 0.0f };
+		m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_LINE_WIDTH_RANGE, range);
+
+		if (m_lineWidth < range[0] || m_lineWidth > range[1])
+			throw tcu::NotSupportedError(std::string("Support for line width ") + de::toString(m_lineWidth) + " is required.");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "ALIASED_LINE_WIDTH_RANGE = [" << range[0] << ", " << range[1] << "]" << tcu::TestLog::EndMessage;
+	}
+
+	if (m_pointSize != 1.0f)
+	{
+		float range[2] = { 0.0f, 0.0f };
+		m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_POINT_SIZE_RANGE, range);
+
+		if (m_pointSize < range[0] || m_pointSize > range[1])
+			throw tcu::NotSupportedError(std::string("Support for point size ") + de::toString(m_pointSize) + " is required.");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "ALIASED_POINT_SIZE_RANGE = [" << range[0] << ", " << range[1] << "]" << tcu::TestLog::EndMessage;
+	}
+
+	// Query info
+
+	m_numSamples = m_context.getRenderTarget().getNumSamples();
+	m_context.getRenderContext().getFunctions().getIntegerv(GL_SUBPIXEL_BITS, &m_subpixelBits);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Sample count = " << m_numSamples << tcu::TestLog::EndMessage;
+	m_testCtx.getLog() << tcu::TestLog::Message << "SUBPIXEL_BITS = " << m_subpixelBits << tcu::TestLog::EndMessage;
+
+	// Gen shader
+
+	{
+		tcu::StringTemplate					vertexSource	(s_shaderVertexTemplate);
+		tcu::StringTemplate					fragmentSource	(s_shaderFragmentTemplate);
+		std::map<std::string, std::string>	params;
+
+		m_shader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexSource.specialize(params)) << glu::FragmentSource(fragmentSource.specialize(params)));
+		if (!m_shader->isOk())
+			throw tcu::TestError("could not create shader");
+	}
+}
+
+void BaseRenderingCase::deinit (void)
+{
+	if (m_shader)
+	{
+		delete m_shader;
+		m_shader = DE_NULL;
+	}
+}
+
+void BaseRenderingCase::drawPrimitives (tcu::Surface& result, const std::vector<tcu::Vec4>& vertexData, glw::GLenum primitiveType)
+{
+	// default to color white
+	const std::vector<tcu::Vec4> colorData(vertexData.size(), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+	drawPrimitives(result, vertexData, colorData, primitiveType);
+}
+
+void BaseRenderingCase::drawPrimitives (tcu::Surface& result, const std::vector<tcu::Vec4>& vertexData, const std::vector<tcu::Vec4>& colorData, glw::GLenum primitiveType)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const glw::GLint		positionLoc		= gl.getAttribLocation(m_shader->getProgram(), "a_position");
+	const glw::GLint		colorLoc		= gl.getAttribLocation(m_shader->getProgram(), "a_color");
+	const glw::GLint		pointSizeLoc	= gl.getUniformLocation(m_shader->getProgram(), "u_pointSize");
+
+	gl.clearColor					(0, 0, 0, 1);
+	gl.clear						(GL_COLOR_BUFFER_BIT);
+	gl.viewport						(0, 0, m_renderSize, m_renderSize);
+	gl.useProgram					(m_shader->getProgram());
+	gl.enableVertexAttribArray		(positionLoc);
+	gl.vertexAttribPointer			(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &vertexData[0]);
+	gl.enableVertexAttribArray		(colorLoc);
+	gl.vertexAttribPointer			(colorLoc, 4, GL_FLOAT, GL_FALSE, 0, &colorData[0]);
+	gl.uniform1f					(pointSizeLoc, m_pointSize);
+	gl.lineWidth					(m_lineWidth);
+	gl.drawArrays					(primitiveType, 0, (glw::GLsizei)vertexData.size());
+	gl.disableVertexAttribArray		(colorLoc);
+	gl.disableVertexAttribArray		(positionLoc);
+	gl.useProgram					(0);
+	gl.finish						();
+	GLU_EXPECT_NO_ERROR				(gl.getError(), "draw primitives");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
+}
+
+class BaseTriangleCase : public BaseRenderingCase
+{
+public:
+							BaseTriangleCase	(Context& context, const char* name, const char* desc, glw::GLenum primitiveDrawType);
+							~BaseTriangleCase	(void);
+	IterateResult			iterate				(void);
+
+private:
+	virtual void			generateTriangles	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles) = DE_NULL;
+
+	int						m_iteration;
+	const int				m_iterationCount;
+	const glw::GLenum		m_primitiveDrawType;
+	bool					m_allIterationsPassed;
+};
+
+BaseTriangleCase::BaseTriangleCase (Context& context, const char* name, const char* desc, glw::GLenum primitiveDrawType)
+	: BaseRenderingCase		(context, name, desc)
+	, m_iteration			(0)
+	, m_iterationCount		(3)
+	, m_primitiveDrawType	(primitiveDrawType)
+	, m_allIterationsPassed	(true)
+{
+}
+
+BaseTriangleCase::~BaseTriangleCase (void)
+{
+}
+
+BaseTriangleCase::IterateResult BaseTriangleCase::iterate (void)
+{
+	const std::string								iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection						section					(m_testCtx.getLog(), iterationDescription, iterationDescription);
+	tcu::Surface									resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>							drawBuffer;
+	std::vector<TriangleSceneSpec::SceneTriangle>	triangles;
+
+	generateTriangles(m_iteration, drawBuffer, triangles);
+
+	// draw image
+	drawPrimitives(resultImage, drawBuffer, m_primitiveDrawType);
+
+	// compare
+	{
+		bool					compareOk;
+		RasterizationArguments	args;
+		TriangleSceneSpec		scene;
+
+		args.numSamples		= m_numSamples;
+		args.subpixelBits	= m_subpixelBits;
+		args.redBits		= m_context.getRenderTarget().getPixelFormat().redBits;
+		args.greenBits		= m_context.getRenderTarget().getPixelFormat().greenBits;
+		args.blueBits		= m_context.getRenderTarget().getPixelFormat().blueBits;
+
+		scene.triangles.swap(triangles);
+
+		compareOk = verifyTriangleGroupRasterization(resultImage, scene, args, m_testCtx.getLog());
+
+		if (!compareOk)
+			m_allIterationsPassed = false;
+	}
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect rasterization");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+class BaseLineCase : public BaseRenderingCase
+{
+public:
+							BaseLineCase		(Context& context, const char* name, const char* desc, glw::GLenum primitiveDrawType, PrimitiveWideness wideness);
+							~BaseLineCase		(void);
+	IterateResult			iterate				(void);
+
+private:
+	virtual void			generateLines		(int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines) = DE_NULL;
+
+	int						m_iteration;
+	const int				m_iterationCount;
+	const glw::GLenum		m_primitiveDrawType;
+	const PrimitiveWideness	m_primitiveWideness;
+	bool					m_allIterationsPassed;
+
+	static const float		s_wideSize;
+};
+
+const float BaseLineCase::s_wideSize = 5.0f;
+
+BaseLineCase::BaseLineCase (Context& context, const char* name, const char* desc, glw::GLenum primitiveDrawType, PrimitiveWideness wideness)
+	: BaseRenderingCase		(context, name, desc)
+	, m_iteration			(0)
+	, m_iterationCount		(3)
+	, m_primitiveDrawType	(primitiveDrawType)
+	, m_primitiveWideness	(wideness)
+	, m_allIterationsPassed	(true)
+{
+	DE_ASSERT(m_primitiveWideness < PRIMITIVEWIDENESS_LAST);
+	m_lineWidth = (m_primitiveWideness == PRIMITIVEWIDENESS_WIDE) ? (s_wideSize) : (1.0f);
+}
+
+BaseLineCase::~BaseLineCase (void)
+{
+}
+
+BaseLineCase::IterateResult BaseLineCase::iterate (void)
+{
+	const std::string						iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection				section					(m_testCtx.getLog(), iterationDescription, iterationDescription);
+	tcu::Surface							resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>					drawBuffer;
+	std::vector<LineSceneSpec::SceneLine>	lines;
+
+	// last iteration, max out size
+	if (m_primitiveWideness == PRIMITIVEWIDENESS_WIDE &&
+		m_iteration+1 == m_iterationCount)
+	{
+		float range[2] = { 0.0f, 0.0f };
+		m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_LINE_WIDTH_RANGE, range);
+
+		m_lineWidth = range[1];
+	}
+
+	// gen data
+	generateLines(m_iteration, drawBuffer, lines);
+
+	// draw image
+	drawPrimitives(resultImage, drawBuffer, m_primitiveDrawType);
+
+	// compare
+	{
+		bool					compareOk;
+		RasterizationArguments	args;
+		LineSceneSpec			scene;
+
+		args.numSamples		= m_numSamples;
+		args.subpixelBits	= m_subpixelBits;
+		args.redBits		= m_context.getRenderTarget().getPixelFormat().redBits;
+		args.greenBits		= m_context.getRenderTarget().getPixelFormat().greenBits;
+		args.blueBits		= m_context.getRenderTarget().getPixelFormat().blueBits;
+
+		scene.lines.swap(lines);
+		scene.lineWidth = m_lineWidth;
+
+		compareOk = verifyLineGroupRasterization(resultImage, scene, args, m_testCtx.getLog());
+
+		if (!compareOk)
+			m_allIterationsPassed = false;
+	}
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect rasterization");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+class PointCase : public BaseRenderingCase
+{
+public:
+							PointCase		(Context& context, const char* name, const char* desc, PrimitiveWideness wideness);
+							~PointCase		(void);
+	IterateResult			iterate			(void);
+
+private:
+	void					generatePoints	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<PointSceneSpec::ScenePoint>& outPoints);
+
+	int						m_iteration;
+	const int				m_iterationCount;
+	const PrimitiveWideness	m_primitiveWideness;
+	bool					m_allIterationsPassed;
+
+	static const float		s_wideSize;
+};
+
+const float PointCase::s_wideSize = 10.0f;
+
+PointCase::PointCase (Context& context, const char* name, const char* desc, PrimitiveWideness wideness)
+	: BaseRenderingCase		(context, name, desc)
+	, m_iteration			(0)
+	, m_iterationCount		(3)
+	, m_primitiveWideness	(wideness)
+	, m_allIterationsPassed	(true)
+{
+	m_pointSize = (m_primitiveWideness == PRIMITIVEWIDENESS_WIDE) ? (s_wideSize) : (1.0f);
+}
+
+PointCase::~PointCase (void)
+{
+}
+
+PointCase::IterateResult PointCase::iterate (void)
+{
+	const std::string						iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection				section					(m_testCtx.getLog(), iterationDescription, iterationDescription);
+	tcu::Surface							resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>					drawBuffer;
+	std::vector<PointSceneSpec::ScenePoint>	points;
+
+	// last iteration, max out size
+	if (m_primitiveWideness == PRIMITIVEWIDENESS_WIDE &&
+		m_iteration+1 == m_iterationCount)
+	{
+		float range[2] = { 0.0f, 0.0f };
+		m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_POINT_SIZE_RANGE, range);
+
+		m_pointSize = range[1];
+	}
+
+	// gen data
+	generatePoints(m_iteration, drawBuffer, points);
+
+	// draw image
+	drawPrimitives(resultImage, drawBuffer, GL_POINTS);
+
+	// compare
+	{
+		bool					compareOk;
+		RasterizationArguments	args;
+		PointSceneSpec			scene;
+
+		args.numSamples		= m_numSamples;
+		args.subpixelBits	= m_subpixelBits;
+		args.redBits		= m_context.getRenderTarget().getPixelFormat().redBits;
+		args.greenBits		= m_context.getRenderTarget().getPixelFormat().greenBits;
+		args.blueBits		= m_context.getRenderTarget().getPixelFormat().blueBits;
+
+		scene.points.swap(points);
+
+		compareOk = verifyPointGroupRasterization(resultImage, scene, args, m_testCtx.getLog());
+
+		if (!compareOk)
+			m_allIterationsPassed = false;
+	}
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect rasterization");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+void PointCase::generatePoints	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<PointSceneSpec::ScenePoint>& outPoints)
+{
+	outData.resize(6);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.2f,  0.8f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,  0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.5f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.5f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(-0.2f, -0.4f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(-0.4f,  0.2f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(  0.88f,   0.9f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(   0.4f,   1.2f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  0.3f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( -0.4f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.11f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 0.88f,  0.7f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4( -0.4f,  0.4f, 0.0f, 1.0f);
+			break;
+	}
+
+	outPoints.resize(outData.size());
+	for (int pointNdx = 0; pointNdx < (int)outPoints.size(); ++pointNdx)
+	{
+		outPoints[pointNdx].position = outData[pointNdx];
+		outPoints[pointNdx].pointSize = m_pointSize;
+	}
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << outPoints.size() << " point(s): (point size = " << m_pointSize << ")" << tcu::TestLog::EndMessage;
+	for (int pointNdx = 0; pointNdx < (int)outPoints.size(); ++pointNdx)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Point " << (pointNdx+1) << ":\t" << outPoints[pointNdx].position << tcu::TestLog::EndMessage;
+}
+
+class TrianglesCase : public BaseTriangleCase
+{
+public:
+			TrianglesCase		(Context& context, const char* name, const char* desc);
+			~TrianglesCase		(void);
+
+	void	generateTriangles	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles);
+};
+
+TrianglesCase::TrianglesCase (Context& context, const char* name, const char* desc)
+	: BaseTriangleCase(context, name, desc, GL_TRIANGLES)
+{
+}
+
+TrianglesCase::~TrianglesCase (void)
+{
+
+}
+
+void TrianglesCase::generateTriangles (int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles)
+{
+	outData.resize(6);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.2f,  0.8f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,  0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.5f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.5f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(-1.5f, -0.4f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(-0.4f,  0.2f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(  0.88f,   0.9f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(   0.4f,   1.2f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( -1.1f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.11f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 0.88f,  0.7f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4( -0.4f,  0.4f, 0.0f, 1.0f);
+			break;
+	}
+
+	outTriangles.resize(2);
+	outTriangles[0].positions[0] = outData[0];	outTriangles[0].sharedEdge[0] = false;
+	outTriangles[0].positions[1] = outData[1];	outTriangles[0].sharedEdge[1] = false;
+	outTriangles[0].positions[2] = outData[2];	outTriangles[0].sharedEdge[2] = false;
+
+	outTriangles[1].positions[0] = outData[3];	outTriangles[1].sharedEdge[0] = false;
+	outTriangles[1].positions[1] = outData[4];	outTriangles[1].sharedEdge[1] = false;
+	outTriangles[1].positions[2] = outData[5];	outTriangles[1].sharedEdge[2] = false;
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << outTriangles.size() << " triangle(s):" << tcu::TestLog::EndMessage;
+	for (int triangleNdx = 0; triangleNdx < (int)outTriangles.size(); ++triangleNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Triangle " << (triangleNdx+1) << ":"
+			<< "\n\t" << outTriangles[triangleNdx].positions[0]
+			<< "\n\t" << outTriangles[triangleNdx].positions[1]
+			<< "\n\t" << outTriangles[triangleNdx].positions[2]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class TriangleStripCase : public BaseTriangleCase
+{
+public:
+			TriangleStripCase	(Context& context, const char* name, const char* desc);
+
+	void	generateTriangles	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles);
+};
+
+TriangleStripCase::TriangleStripCase (Context& context, const char* name, const char* desc)
+	: BaseTriangleCase(context, name, desc, GL_TRIANGLE_STRIP)
+{
+}
+
+void TriangleStripCase::generateTriangles (int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles)
+{
+	outData.resize(5);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4(-0.504f,  0.8f,   0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.2f,   -0.2f,   0.0f, 1.0f);
+			outData[2] = tcu::Vec4(-0.2f,    0.199f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4( 0.5f,    0.201f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 1.5f,    0.4f,   0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.129f,  0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f,  0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f,  0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,  -0.31f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(  0.88f,   0.9f,  0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f,  0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f,  0.0f, 1.0f);
+			outData[2] = tcu::Vec4(-0.87f, -0.1f,  0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.11f,  0.19f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 0.88f,  0.7f,  0.0f, 1.0f);
+			break;
+	}
+
+	outTriangles.resize(3);
+	outTriangles[0].positions[0] = outData[0];	outTriangles[0].sharedEdge[0] = false;
+	outTriangles[0].positions[1] = outData[1];	outTriangles[0].sharedEdge[1] = true;
+	outTriangles[0].positions[2] = outData[2];	outTriangles[0].sharedEdge[2] = false;
+
+	outTriangles[1].positions[0] = outData[2];	outTriangles[1].sharedEdge[0] = true;
+	outTriangles[1].positions[1] = outData[1];	outTriangles[1].sharedEdge[1] = false;
+	outTriangles[1].positions[2] = outData[3];	outTriangles[1].sharedEdge[2] = true;
+
+	outTriangles[2].positions[0] = outData[2];	outTriangles[2].sharedEdge[0] = true;
+	outTriangles[2].positions[1] = outData[3];	outTriangles[2].sharedEdge[1] = false;
+	outTriangles[2].positions[2] = outData[4];	outTriangles[2].sharedEdge[2] = false;
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering triangle strip, " << outData.size() << " vertices." << tcu::TestLog::EndMessage;
+	for (int vtxNdx = 0; vtxNdx < (int)outData.size(); ++vtxNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "\t" << outData[vtxNdx]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class TriangleFanCase : public BaseTriangleCase
+{
+public:
+			TriangleFanCase		(Context& context, const char* name, const char* desc);
+
+	void	generateTriangles	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles);
+};
+
+TriangleFanCase::TriangleFanCase (Context& context, const char* name, const char* desc)
+	: BaseTriangleCase(context, name, desc, GL_TRIANGLE_FAN)
+{
+}
+
+void TriangleFanCase::generateTriangles (int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles)
+{
+	outData.resize(5);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.01f,  0.0f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,   0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.46f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.5f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(-1.5f,  -0.4f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(  0.88f,   0.9f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.7f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4( 0.11f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 0.88f,  0.7f, 0.0f, 1.0f);
+			break;
+	}
+
+	outTriangles.resize(3);
+	outTriangles[0].positions[0] = outData[0];	outTriangles[0].sharedEdge[0] = false;
+	outTriangles[0].positions[1] = outData[1];	outTriangles[0].sharedEdge[1] = false;
+	outTriangles[0].positions[2] = outData[2];	outTriangles[0].sharedEdge[2] = true;
+
+	outTriangles[1].positions[0] = outData[0];	outTriangles[1].sharedEdge[0] = true;
+	outTriangles[1].positions[1] = outData[2];	outTriangles[1].sharedEdge[1] = false;
+	outTriangles[1].positions[2] = outData[3];	outTriangles[1].sharedEdge[2] = true;
+
+	outTriangles[2].positions[0] = outData[0];	outTriangles[2].sharedEdge[0] = true;
+	outTriangles[2].positions[1] = outData[3];	outTriangles[2].sharedEdge[1] = false;
+	outTriangles[2].positions[2] = outData[4];	outTriangles[2].sharedEdge[2] = false;
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering triangle fan, " << outData.size() << " vertices." << tcu::TestLog::EndMessage;
+	for (int vtxNdx = 0; vtxNdx < (int)outData.size(); ++vtxNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "\t" << outData[vtxNdx]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class LinesCase : public BaseLineCase
+{
+public:
+			LinesCase		(Context& context, const char* name, const char* desc, PrimitiveWideness wideness);
+
+	void	generateLines	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines);
+};
+
+LinesCase::LinesCase (Context& context, const char* name, const char* desc, PrimitiveWideness wideness)
+	: BaseLineCase(context, name, desc, GL_LINES, wideness)
+{
+}
+
+void LinesCase::generateLines (int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines)
+{
+	outData.resize(6);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.01f,  0.0f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,   0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.46f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.3f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(-1.5f,  -0.4f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4( 0.1f,   0.5f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(  0.88f,   0.9f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(  0.18f,  -0.2f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.7f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4( 0.11f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 0.88f,  0.7f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(  0.8f, -0.7f, 0.0f, 1.0f);
+			break;
+	}
+
+	outLines.resize(3);
+	outLines[0].positions[0] = outData[0];
+	outLines[0].positions[1] = outData[1];
+	outLines[1].positions[0] = outData[2];
+	outLines[1].positions[1] = outData[3];
+	outLines[2].positions[0] = outData[4];
+	outLines[2].positions[1] = outData[5];
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << outLines.size() << " lines(s): (width = " << m_lineWidth << ")" << tcu::TestLog::EndMessage;
+	for (int lineNdx = 0; lineNdx < (int)outLines.size(); ++lineNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Line " << (lineNdx+1) << ":"
+			<< "\n\t" << outLines[lineNdx].positions[0]
+			<< "\n\t" << outLines[lineNdx].positions[1]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class LineStripCase : public BaseLineCase
+{
+public:
+			LineStripCase	(Context& context, const char* name, const char* desc, PrimitiveWideness wideness);
+
+	void	generateLines	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines);
+};
+
+LineStripCase::LineStripCase (Context& context, const char* name, const char* desc, PrimitiveWideness wideness)
+	: BaseLineCase(context, name, desc, GL_LINE_STRIP, wideness)
+{
+}
+
+void LineStripCase::generateLines (int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines)
+{
+	outData.resize(4);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.01f,  0.0f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,   0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.46f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.5f,   0.2f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.7f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4( 0.11f,  0.2f, 0.0f, 1.0f);
+			break;
+	}
+
+	outLines.resize(3);
+	outLines[0].positions[0] = outData[0];
+	outLines[0].positions[1] = outData[1];
+	outLines[1].positions[0] = outData[1];
+	outLines[1].positions[1] = outData[2];
+	outLines[2].positions[0] = outData[2];
+	outLines[2].positions[1] = outData[3];
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering line strip, width = " << m_lineWidth << ", " << outData.size() << " vertices." << tcu::TestLog::EndMessage;
+	for (int vtxNdx = 0; vtxNdx < (int)outData.size(); ++vtxNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "\t" << outData[vtxNdx]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class LineLoopCase : public BaseLineCase
+{
+public:
+			LineLoopCase	(Context& context, const char* name, const char* desc, PrimitiveWideness wideness);
+
+	void	generateLines	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines);
+};
+
+LineLoopCase::LineLoopCase (Context& context, const char* name, const char* desc, PrimitiveWideness wideness)
+	: BaseLineCase(context, name, desc, GL_LINE_LOOP, wideness)
+{
+}
+
+void LineLoopCase::generateLines (int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines)
+{
+	outData.resize(4);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.01f,  0.0f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,   0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.46f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.5f,   0.2f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.7f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4( 0.11f,  0.2f, 0.0f, 1.0f);
+			break;
+	}
+
+	outLines.resize(4);
+	outLines[0].positions[0] = outData[0];
+	outLines[0].positions[1] = outData[1];
+	outLines[1].positions[0] = outData[1];
+	outLines[1].positions[1] = outData[2];
+	outLines[2].positions[0] = outData[2];
+	outLines[2].positions[1] = outData[3];
+	outLines[3].positions[0] = outData[3];
+	outLines[3].positions[1] = outData[0];
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering line loop, width = " << m_lineWidth << ", " << outData.size() << " vertices." << tcu::TestLog::EndMessage;
+	for (int vtxNdx = 0; vtxNdx < (int)outData.size(); ++vtxNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "\t" << outData[vtxNdx]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class FillRuleCase : public BaseRenderingCase
+{
+public:
+	enum FillRuleCaseType
+	{
+		FILLRULECASE_BASIC = 0,
+		FILLRULECASE_REVERSED,
+		FILLRULECASE_CLIPPED_FULL,
+		FILLRULECASE_CLIPPED_PARTIAL,
+		FILLRULECASE_PROJECTED,
+
+		FILLRULECASE_LAST
+	};
+
+							FillRuleCase		(Context& ctx, const char* name, const char* desc, FillRuleCaseType type);
+							~FillRuleCase		(void);
+	IterateResult			iterate				(void);
+
+private:
+	int						getRenderSize		(FillRuleCase::FillRuleCaseType type) const;
+	int						getNumIterations	(FillRuleCase::FillRuleCaseType type) const;
+	void					generateTriangles	(int iteration, std::vector<tcu::Vec4>& outData) const;
+
+	const FillRuleCaseType	m_caseType;
+	int						m_iteration;
+	const int				m_iterationCount;
+	bool					m_allIterationsPassed;
+
+};
+
+FillRuleCase::FillRuleCase (Context& ctx, const char* name, const char* desc, FillRuleCaseType type)
+	: BaseRenderingCase		(ctx, name, desc, getRenderSize(type))
+	, m_caseType			(type)
+	, m_iteration			(0)
+	, m_iterationCount		(getNumIterations(type))
+	, m_allIterationsPassed	(true)
+{
+	DE_ASSERT(type < FILLRULECASE_LAST);
+}
+
+FillRuleCase::~FillRuleCase (void)
+{
+	deinit();
+}
+
+FillRuleCase::IterateResult FillRuleCase::iterate (void)
+{
+	const std::string						iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection				section					(m_testCtx.getLog(), iterationDescription, iterationDescription);
+	const int								thresholdRed			= 1 << (8 - m_context.getRenderTarget().getPixelFormat().redBits);
+	const int								thresholdGreen			= 1 << (8 - m_context.getRenderTarget().getPixelFormat().greenBits);
+	const int								thresholdBlue			= 1 << (8 - m_context.getRenderTarget().getPixelFormat().blueBits);
+	tcu::Surface							resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>					drawBuffer;
+	bool									imageShown				= false;
+
+	generateTriangles(m_iteration, drawBuffer);
+
+	// draw image
+	{
+		const glw::Functions&			gl				= m_context.getRenderContext().getFunctions();
+		const std::vector<tcu::Vec4>	colorBuffer		(drawBuffer.size(), tcu::Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing gray triangles with shared edges.\nEnabling additive blending to detect overlapping fragments." << tcu::TestLog::EndMessage;
+
+		gl.enable(GL_BLEND);
+		gl.blendEquation(GL_FUNC_ADD);
+		gl.blendFunc(GL_ONE, GL_ONE);
+		drawPrimitives(resultImage, drawBuffer, colorBuffer, GL_TRIANGLES);
+	}
+
+	// verify no overdraw
+	{
+		const tcu::RGBA	triangleColor	= tcu::RGBA(127, 127, 127, 255);
+		bool			overdraw		= false;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying result." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < resultImage.getHeight(); ++y)
+		for (int x = 0; x < resultImage.getWidth();  ++x)
+		{
+			const tcu::RGBA color = resultImage.getPixel(x, y);
+
+			// color values are greater than triangle color? Allow lower values for multisampled edges and background.
+			if ((color.getRed()   - triangleColor.getRed())   > thresholdRed   ||
+				(color.getGreen() - triangleColor.getGreen()) > thresholdGreen ||
+				(color.getBlue()  - triangleColor.getBlue())  > thresholdBlue)
+				overdraw = true;
+		}
+
+		// results
+		if (!overdraw)
+			m_testCtx.getLog() << tcu::TestLog::Message << "No overlapping fragments detected." << tcu::TestLog::EndMessage;
+		else
+		{
+			m_testCtx.getLog()	<< tcu::TestLog::Message << "Overlapping fragments detected, image is not valid." << tcu::TestLog::EndMessage;
+			m_testCtx.getLog()	<< tcu::TestLog::ImageSet("Result of rendering", "Result of rendering")
+								<< tcu::TestLog::Image("Result", "Result", resultImage)
+								<< tcu::TestLog::EndImageSet;
+
+			imageShown = true;
+			m_allIterationsPassed = false;
+		}
+	}
+
+	// verify no missing fragments in the full viewport case
+	if (m_caseType == FILLRULECASE_CLIPPED_FULL)
+	{
+		bool missingFragments = false;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Searching missing fragments." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < resultImage.getHeight(); ++y)
+		for (int x = 0; x < resultImage.getWidth();  ++x)
+		{
+			const tcu::RGBA color = resultImage.getPixel(x, y);
+
+			// black? (background)
+			if (color.getRed()   <= thresholdRed   ||
+				color.getGreen() <= thresholdGreen ||
+				color.getBlue()  <= thresholdBlue)
+				missingFragments = true;
+		}
+
+		// results
+		if (!missingFragments)
+			m_testCtx.getLog() << tcu::TestLog::Message << "No missing fragments detected." << tcu::TestLog::EndMessage;
+		else
+		{
+			m_testCtx.getLog()	<< tcu::TestLog::Message << "Missing fragments detected, image is not valid." << tcu::TestLog::EndMessage;
+
+			if (!imageShown)
+			{
+				m_testCtx.getLog()	<< tcu::TestLog::ImageSet("Result of rendering", "Result of rendering")
+									<< tcu::TestLog::Image("Result", "Result", resultImage)
+									<< tcu::TestLog::EndImageSet;
+			}
+
+			m_allIterationsPassed = false;
+		}
+	}
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Found invalid pixels");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+int FillRuleCase::getRenderSize (FillRuleCase::FillRuleCaseType type) const
+{
+	if (type == FILLRULECASE_CLIPPED_FULL || type == FILLRULECASE_CLIPPED_PARTIAL)
+		return 64;
+	else
+		return 256;
+}
+
+int FillRuleCase::getNumIterations (FillRuleCase::FillRuleCaseType type) const
+{
+	if (type == FILLRULECASE_CLIPPED_FULL || type == FILLRULECASE_CLIPPED_PARTIAL)
+		return 15;
+	else
+		return 2;
+}
+
+void FillRuleCase::generateTriangles (int iteration, std::vector<tcu::Vec4>& outData) const
+{
+	switch (m_caseType)
+	{
+		case FILLRULECASE_BASIC:
+		case FILLRULECASE_REVERSED:
+		case FILLRULECASE_PROJECTED:
+		{
+			const int	numRows		= 4;
+			const int	numColumns	= 4;
+			const float	quadSide	= 0.15f;
+			de::Random	rnd			(0xabcd);
+
+			outData.resize(6 * numRows * numColumns);
+
+			for (int col = 0; col < numColumns; ++col)
+			for (int row = 0; row < numRows;    ++row)
+			{
+				const tcu::Vec2 center		= tcu::Vec2((row + 0.5f) / numRows * 2.0f - 1.0f, (col + 0.5f) / numColumns * 2.0f - 1.0f);
+				const float		rotation	= (iteration * numColumns * numRows + col * numRows + row) / (float)(m_iterationCount * numColumns * numRows) * DE_PI / 2.0f;
+				const tcu::Vec2 sideH		= quadSide * tcu::Vec2(deFloatCos(rotation), deFloatSin(rotation));
+				const tcu::Vec2 sideV		= tcu::Vec2(sideH.y(), -sideH.x());
+				const tcu::Vec2 quad[4]		=
+				{
+					center + sideH + sideV,
+					center + sideH - sideV,
+					center - sideH - sideV,
+					center - sideH + sideV,
+				};
+
+				if (m_caseType == FILLRULECASE_BASIC)
+				{
+					outData[6 * (col * numRows + row) + 0] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 1] = tcu::Vec4(quad[1].x(), quad[1].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 2] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 3] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 4] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 5] = tcu::Vec4(quad[3].x(), quad[3].y(), 0.0f, 1.0f);
+				}
+				else if (m_caseType == FILLRULECASE_REVERSED)
+				{
+					outData[6 * (col * numRows + row) + 0] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 1] = tcu::Vec4(quad[1].x(), quad[1].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 2] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 3] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 4] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 5] = tcu::Vec4(quad[3].x(), quad[3].y(), 0.0f, 1.0f);
+				}
+				else if (m_caseType == FILLRULECASE_PROJECTED)
+				{
+					const float w0 = rnd.getFloat(0.1f, 4.0f);
+					const float w1 = rnd.getFloat(0.1f, 4.0f);
+					const float w2 = rnd.getFloat(0.1f, 4.0f);
+					const float w3 = rnd.getFloat(0.1f, 4.0f);
+
+					outData[6 * (col * numRows + row) + 0] = tcu::Vec4(quad[0].x() * w0, quad[0].y() * w0, 0.0f, w0);
+					outData[6 * (col * numRows + row) + 1] = tcu::Vec4(quad[1].x() * w1, quad[1].y() * w1, 0.0f, w1);
+					outData[6 * (col * numRows + row) + 2] = tcu::Vec4(quad[2].x() * w2, quad[2].y() * w2, 0.0f, w2);
+					outData[6 * (col * numRows + row) + 3] = tcu::Vec4(quad[2].x() * w2, quad[2].y() * w2, 0.0f, w2);
+					outData[6 * (col * numRows + row) + 4] = tcu::Vec4(quad[0].x() * w0, quad[0].y() * w0, 0.0f, w0);
+					outData[6 * (col * numRows + row) + 5] = tcu::Vec4(quad[3].x() * w3, quad[3].y() * w3, 0.0f, w3);
+				}
+				else
+					DE_ASSERT(DE_FALSE);
+			}
+
+			break;
+		}
+
+		case FILLRULECASE_CLIPPED_PARTIAL:
+		case FILLRULECASE_CLIPPED_FULL:
+		{
+			const float		quadSide	= (m_caseType == FILLRULECASE_CLIPPED_PARTIAL) ? (1.0f) : (2.0f);
+			const tcu::Vec2 center		= (m_caseType == FILLRULECASE_CLIPPED_PARTIAL) ? (tcu::Vec2(0.5f, 0.5f)) : (tcu::Vec2(0.0f, 0.0f));
+			const float		rotation	= (iteration) / (float)(m_iterationCount - 1) * DE_PI / 2.0f;
+			const tcu::Vec2 sideH		= quadSide * tcu::Vec2(deFloatCos(rotation), deFloatSin(rotation));
+			const tcu::Vec2 sideV		= tcu::Vec2(sideH.y(), -sideH.x());
+			const tcu::Vec2 quad[4]		=
+			{
+				center + sideH + sideV,
+				center + sideH - sideV,
+				center - sideH - sideV,
+				center - sideH + sideV,
+			};
+
+			outData.resize(6);
+			outData[0] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(quad[1].x(), quad[1].y(), 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(quad[3].x(), quad[3].y(), 0.0f, 1.0f);
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+class CullingTest : public BaseRenderingCase
+{
+public:
+						CullingTest			(Context& ctx, const char* name, const char* desc, glw::GLenum cullMode, glw::GLenum primitive, glw::GLenum faceOrder);
+						~CullingTest		(void);
+	IterateResult		iterate				(void);
+
+private:
+	void				generateVertices	(std::vector<tcu::Vec4>& outData) const;
+	void				extractTriangles	(std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles, const std::vector<tcu::Vec4>& vertices) const;
+	bool				triangleOrder		(const tcu::Vec4& v0, const tcu::Vec4& v1, const tcu::Vec4& v2) const;
+
+	const glw::GLenum	m_cullMode;
+	const glw::GLenum	m_primitive;
+	const glw::GLenum	m_faceOrder;
+};
+
+CullingTest::CullingTest (Context& ctx, const char* name, const char* desc, glw::GLenum cullMode, glw::GLenum primitive, glw::GLenum faceOrder)
+	: BaseRenderingCase	(ctx, name, desc)
+	, m_cullMode		(cullMode)
+	, m_primitive		(primitive)
+	, m_faceOrder		(faceOrder)
+{
+}
+
+CullingTest::~CullingTest (void)
+{
+}
+
+CullingTest::IterateResult CullingTest::iterate (void)
+{
+	tcu::Surface									resultImage(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>							drawBuffer;
+	std::vector<TriangleSceneSpec::SceneTriangle>	triangles;
+
+	// generate scene
+	generateVertices(drawBuffer);
+	extractTriangles(triangles, drawBuffer);
+
+	// draw image
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.enable(GL_CULL_FACE);
+		gl.cullFace(m_cullMode);
+		gl.frontFace(m_faceOrder);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting front face to " << glu::getWindingName(m_faceOrder) << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting cull face to " << glu::getFaceName(m_cullMode) << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing test pattern (" << glu::getPrimitiveTypeName(m_primitive) << ")" << tcu::TestLog::EndMessage;
+
+		drawPrimitives(resultImage, drawBuffer, m_primitive);
+	}
+
+	// compare
+	{
+		RasterizationArguments	args;
+		TriangleSceneSpec		scene;
+
+		args.numSamples		= m_numSamples;
+		args.subpixelBits	= m_subpixelBits;
+		args.redBits		= m_context.getRenderTarget().getPixelFormat().redBits;
+		args.greenBits		= m_context.getRenderTarget().getPixelFormat().greenBits;
+		args.blueBits		= m_context.getRenderTarget().getPixelFormat().blueBits;
+
+		scene.triangles.swap(triangles);
+
+		if (verifyTriangleGroupRasterization(resultImage, scene, args, m_testCtx.getLog(), VERIFICATIONMODE_WEAK))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect rendering");
+	}
+
+	return STOP;
+}
+
+void CullingTest::generateVertices (std::vector<tcu::Vec4>& outData) const
+{
+	de::Random rnd(543210);
+
+	outData.resize(6);
+	for (int vtxNdx = 0; vtxNdx < (int)outData.size(); ++vtxNdx)
+	{
+		outData[vtxNdx].x() = rnd.getFloat(-0.9f, 0.9f);
+		outData[vtxNdx].y() = rnd.getFloat(-0.9f, 0.9f);
+		outData[vtxNdx].z() = 0.0f;
+		outData[vtxNdx].w() = 1.0f;
+	}
+}
+
+void CullingTest::extractTriangles (std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles, const std::vector<tcu::Vec4>& vertices) const
+{
+	const bool cullDirection = (m_cullMode == GL_FRONT) ^ (m_faceOrder == GL_CCW);
+
+	// No triangles
+	if (m_cullMode == GL_FRONT_AND_BACK)
+		return;
+
+	switch (m_primitive)
+	{
+		case GL_TRIANGLES:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 2; vtxNdx += 3)
+			{
+				const tcu::Vec4& v0 = vertices[vtxNdx + 0];
+				const tcu::Vec4& v1 = vertices[vtxNdx + 1];
+				const tcu::Vec4& v2 = vertices[vtxNdx + 2];
+
+				if (triangleOrder(v0, v1, v2) != cullDirection)
+				{
+					TriangleSceneSpec::SceneTriangle tri;
+					tri.positions[0] = v0;	tri.sharedEdge[0] = false;
+					tri.positions[1] = v1;	tri.sharedEdge[1] = false;
+					tri.positions[2] = v2;	tri.sharedEdge[2] = false;
+
+					outTriangles.push_back(tri);
+				}
+			}
+			break;
+		}
+
+		case GL_TRIANGLE_STRIP:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 2; ++vtxNdx)
+			{
+				const tcu::Vec4& v0 = vertices[vtxNdx + 0];
+				const tcu::Vec4& v1 = vertices[vtxNdx + 1];
+				const tcu::Vec4& v2 = vertices[vtxNdx + 2];
+
+				if (triangleOrder(v0, v1, v2) != (cullDirection ^ (vtxNdx % 2 != 0)))
+				{
+					TriangleSceneSpec::SceneTriangle tri;
+					tri.positions[0] = v0;	tri.sharedEdge[0] = false;
+					tri.positions[1] = v1;	tri.sharedEdge[1] = false;
+					tri.positions[2] = v2;	tri.sharedEdge[2] = false;
+
+					outTriangles.push_back(tri);
+				}
+			}
+			break;
+		}
+
+		case GL_TRIANGLE_FAN:
+		{
+			for (int vtxNdx = 1; vtxNdx < (int)vertices.size() - 1; ++vtxNdx)
+			{
+				const tcu::Vec4& v0 = vertices[0];
+				const tcu::Vec4& v1 = vertices[vtxNdx + 0];
+				const tcu::Vec4& v2 = vertices[vtxNdx + 1];
+
+				if (triangleOrder(v0, v1, v2) != cullDirection)
+				{
+					TriangleSceneSpec::SceneTriangle tri;
+					tri.positions[0] = v0;	tri.sharedEdge[0] = false;
+					tri.positions[1] = v1;	tri.sharedEdge[1] = false;
+					tri.positions[2] = v2;	tri.sharedEdge[2] = false;
+
+					outTriangles.push_back(tri);
+				}
+			}
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+bool CullingTest::triangleOrder (const tcu::Vec4& v0, const tcu::Vec4& v1, const tcu::Vec4& v2) const
+{
+	const tcu::Vec2 s0 = v0.swizzle(0, 1) / v0.w();
+	const tcu::Vec2 s1 = v1.swizzle(0, 1) / v1.w();
+	const tcu::Vec2 s2 = v2.swizzle(0, 1) / v2.w();
+
+	// cross
+	return ((s1.x() - s0.x()) * (s2.y() - s0.y()) - (s2.x() - s0.x()) * (s1.y() - s0.y())) < 0;
+}
+
+class TriangleInterpolationTest : public BaseRenderingCase
+{
+public:
+						TriangleInterpolationTest	(Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags);
+						~TriangleInterpolationTest	(void);
+	IterateResult		iterate						(void);
+
+private:
+	void				generateVertices			(int iteration, std::vector<tcu::Vec4>& outVertices, std::vector<tcu::Vec4>& outColors) const;
+	void				extractTriangles			(std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles, const std::vector<tcu::Vec4>& vertices, const std::vector<tcu::Vec4>& colors) const;
+
+	const glw::GLenum	m_primitive;
+	const bool			m_projective;
+	const int			m_iterationCount;
+
+	int					m_iteration;
+	bool				m_allIterationsPassed;
+};
+
+TriangleInterpolationTest::TriangleInterpolationTest (Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags)
+	: BaseRenderingCase		(ctx, name, desc)
+	, m_primitive			(primitive)
+	, m_projective			((flags & INTERPOLATIONFLAGS_PROJECTED) != 0)
+	, m_iterationCount		(3)
+	, m_iteration			(0)
+	, m_allIterationsPassed	(true)
+{
+}
+
+TriangleInterpolationTest::~TriangleInterpolationTest (void)
+{
+	deinit();
+}
+
+TriangleInterpolationTest::IterateResult TriangleInterpolationTest::iterate (void)
+{
+	const std::string								iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection						section					(m_testCtx.getLog(), "Iteration" + de::toString(m_iteration+1), iterationDescription);
+	tcu::Surface									resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>							drawBuffer;
+	std::vector<tcu::Vec4>							colorBuffer;
+	std::vector<TriangleSceneSpec::SceneTriangle>	triangles;
+
+	// generate scene
+	generateVertices(m_iteration, drawBuffer, colorBuffer);
+	extractTriangles(triangles, drawBuffer, colorBuffer);
+
+	// log
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Generated vertices:" << tcu::TestLog::EndMessage;
+		for (int vtxNdx = 0; vtxNdx < (int)drawBuffer.size(); ++vtxNdx)
+			m_testCtx.getLog() << tcu::TestLog::Message << "\t" << drawBuffer[vtxNdx] << ",\tcolor= " << colorBuffer[vtxNdx] << tcu::TestLog::EndMessage;
+	}
+
+	// draw image
+	drawPrimitives(resultImage, drawBuffer, colorBuffer, m_primitive);
+
+	// compare
+	{
+		RasterizationArguments	args;
+		TriangleSceneSpec		scene;
+
+		args.numSamples		= m_numSamples;
+		args.subpixelBits	= m_subpixelBits;
+		args.redBits		= m_context.getRenderTarget().getPixelFormat().redBits;
+		args.greenBits		= m_context.getRenderTarget().getPixelFormat().greenBits;
+		args.blueBits		= m_context.getRenderTarget().getPixelFormat().blueBits;
+
+		scene.triangles.swap(triangles);
+
+		if (!verifyTriangleGroupInterpolation(resultImage, scene, args, m_testCtx.getLog()))
+			m_allIterationsPassed = false;
+	}
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Found invalid pixel values");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+void TriangleInterpolationTest::generateVertices (int iteration, std::vector<tcu::Vec4>& outVertices, std::vector<tcu::Vec4>& outColors) const
+{
+	// use only red, green and blue
+	const tcu::Vec4 colors[] =
+	{
+		tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f),
+	};
+
+	de::Random rnd(123 + iteration * 1000 + (int)m_primitive);
+
+	outVertices.resize(6);
+	outColors.resize(6);
+
+	for (int vtxNdx = 0; vtxNdx < (int)outVertices.size(); ++vtxNdx)
+	{
+		outVertices[vtxNdx].x() = rnd.getFloat(-0.9f, 0.9f);
+		outVertices[vtxNdx].y() = rnd.getFloat(-0.9f, 0.9f);
+		outVertices[vtxNdx].z() = 0.0f;
+
+		if (!m_projective)
+			outVertices[vtxNdx].w() = 1.0f;
+		else
+		{
+			const float w = rnd.getFloat(0.2f, 4.0f);
+
+			outVertices[vtxNdx].x() *= w;
+			outVertices[vtxNdx].y() *= w;
+			outVertices[vtxNdx].z() *= w;
+			outVertices[vtxNdx].w() = w;
+		}
+
+		outColors[vtxNdx] = colors[vtxNdx % DE_LENGTH_OF_ARRAY(colors)];
+	}
+}
+
+void TriangleInterpolationTest::extractTriangles (std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles, const std::vector<tcu::Vec4>& vertices, const std::vector<tcu::Vec4>& colors) const
+{
+	switch (m_primitive)
+	{
+		case GL_TRIANGLES:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 2; vtxNdx += 3)
+			{
+				TriangleSceneSpec::SceneTriangle tri;
+				tri.positions[0]	= vertices[vtxNdx + 0];
+				tri.positions[1]	= vertices[vtxNdx + 1];
+				tri.positions[2]	= vertices[vtxNdx + 2];
+				tri.sharedEdge[0]	= false;
+				tri.sharedEdge[1]	= false;
+				tri.sharedEdge[2]	= false;
+
+				tri.colors[0] = colors[vtxNdx + 0];
+				tri.colors[1] = colors[vtxNdx + 1];
+				tri.colors[2] = colors[vtxNdx + 2];
+
+				outTriangles.push_back(tri);
+			}
+			break;
+		}
+
+		case GL_TRIANGLE_STRIP:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 2; ++vtxNdx)
+			{
+				TriangleSceneSpec::SceneTriangle tri;
+				tri.positions[0]	= vertices[vtxNdx + 0];
+				tri.positions[1]	= vertices[vtxNdx + 1];
+				tri.positions[2]	= vertices[vtxNdx + 2];
+				tri.sharedEdge[0]	= false;
+				tri.sharedEdge[1]	= false;
+				tri.sharedEdge[2]	= false;
+
+				tri.colors[0] = colors[vtxNdx + 0];
+				tri.colors[1] = colors[vtxNdx + 1];
+				tri.colors[2] = colors[vtxNdx + 2];
+
+				outTriangles.push_back(tri);
+			}
+			break;
+		}
+
+		case GL_TRIANGLE_FAN:
+		{
+			for (int vtxNdx = 1; vtxNdx < (int)vertices.size() - 1; ++vtxNdx)
+			{
+				TriangleSceneSpec::SceneTriangle tri;
+				tri.positions[0]	= vertices[0];
+				tri.positions[1]	= vertices[vtxNdx + 0];
+				tri.positions[2]	= vertices[vtxNdx + 1];
+				tri.sharedEdge[0]	= false;
+				tri.sharedEdge[1]	= false;
+				tri.sharedEdge[2]	= false;
+
+				tri.colors[0] = colors[0];
+				tri.colors[1] = colors[vtxNdx + 0];
+				tri.colors[2] = colors[vtxNdx + 1];
+
+				outTriangles.push_back(tri);
+			}
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+class LineInterpolationTest : public BaseRenderingCase
+{
+public:
+						LineInterpolationTest	(Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags, float lineWidth);
+						~LineInterpolationTest	(void);
+	IterateResult		iterate					(void);
+
+private:
+	void				generateVertices		(int iteration, std::vector<tcu::Vec4>& outVertices, std::vector<tcu::Vec4>& outColors) const;
+	void				extractLines			(std::vector<LineSceneSpec::SceneLine>& outLines, const std::vector<tcu::Vec4>& vertices, const std::vector<tcu::Vec4>& colors) const;
+
+	const glw::GLenum	m_primitive;
+	const bool			m_projective;
+	const int			m_iterationCount;
+
+	int					m_iteration;
+	bool				m_allIterationsPassed;
+};
+
+LineInterpolationTest::LineInterpolationTest (Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags, float lineWidth)
+	: BaseRenderingCase		(ctx, name, desc)
+	, m_primitive			(primitive)
+	, m_projective			((flags & INTERPOLATIONFLAGS_PROJECTED) != 0)
+	, m_iterationCount		(3)
+	, m_iteration			(0)
+	, m_allIterationsPassed	(true)
+{
+	m_lineWidth = lineWidth;
+}
+
+LineInterpolationTest::~LineInterpolationTest (void)
+{
+	deinit();
+}
+
+LineInterpolationTest::IterateResult LineInterpolationTest::iterate (void)
+{
+	const std::string						iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection				section					(m_testCtx.getLog(), "Iteration" + de::toString(m_iteration+1), iterationDescription);
+	tcu::Surface							resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>					drawBuffer;
+	std::vector<tcu::Vec4>					colorBuffer;
+	std::vector<LineSceneSpec::SceneLine>	lines;
+
+	// generate scene
+	generateVertices(m_iteration, drawBuffer, colorBuffer);
+	extractLines(lines, drawBuffer, colorBuffer);
+
+	// log
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Generated vertices:" << tcu::TestLog::EndMessage;
+		for (int vtxNdx = 0; vtxNdx < (int)drawBuffer.size(); ++vtxNdx)
+			m_testCtx.getLog() << tcu::TestLog::Message << "\t" << drawBuffer[vtxNdx] << ",\tcolor= " << colorBuffer[vtxNdx] << tcu::TestLog::EndMessage;
+	}
+
+	// draw image
+	drawPrimitives(resultImage, drawBuffer, colorBuffer, m_primitive);
+
+	// compare
+	{
+		RasterizationArguments	args;
+		LineSceneSpec			scene;
+
+		args.numSamples		= m_numSamples;
+		args.subpixelBits	= m_subpixelBits;
+		args.redBits		= m_context.getRenderTarget().getPixelFormat().redBits;
+		args.greenBits		= m_context.getRenderTarget().getPixelFormat().greenBits;
+		args.blueBits		= m_context.getRenderTarget().getPixelFormat().blueBits;
+
+		scene.lines.swap(lines);
+		scene.lineWidth = m_lineWidth;
+
+		if (!verifyLineGroupInterpolation(resultImage, scene, args, m_testCtx.getLog()))
+			m_allIterationsPassed = false;
+	}
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Found invalid pixel values");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+void LineInterpolationTest::generateVertices (int iteration, std::vector<tcu::Vec4>& outVertices, std::vector<tcu::Vec4>& outColors) const
+{
+	// use only red, green and blue
+	const tcu::Vec4 colors[] =
+	{
+		tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f),
+	};
+
+	de::Random rnd(123 + iteration * 1000 + (int)m_primitive);
+
+	outVertices.resize(6);
+	outColors.resize(6);
+
+	for (int vtxNdx = 0; vtxNdx < (int)outVertices.size(); ++vtxNdx)
+	{
+		outVertices[vtxNdx].x() = rnd.getFloat(-0.9f, 0.9f);
+		outVertices[vtxNdx].y() = rnd.getFloat(-0.9f, 0.9f);
+		outVertices[vtxNdx].z() = 0.0f;
+
+		if (!m_projective)
+			outVertices[vtxNdx].w() = 1.0f;
+		else
+		{
+			const float w = rnd.getFloat(0.2f, 4.0f);
+
+			outVertices[vtxNdx].x() *= w;
+			outVertices[vtxNdx].y() *= w;
+			outVertices[vtxNdx].z() *= w;
+			outVertices[vtxNdx].w() = w;
+		}
+
+		outColors[vtxNdx] = colors[vtxNdx % DE_LENGTH_OF_ARRAY(colors)];
+	}
+}
+
+void LineInterpolationTest::extractLines (std::vector<LineSceneSpec::SceneLine>& outLines, const std::vector<tcu::Vec4>& vertices, const std::vector<tcu::Vec4>& colors) const
+{
+	switch (m_primitive)
+	{
+		case GL_LINES:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 1; vtxNdx += 2)
+			{
+				LineSceneSpec::SceneLine line;
+				line.positions[0] = vertices[vtxNdx + 0];
+				line.positions[1] = vertices[vtxNdx + 1];
+
+				line.colors[0] = colors[vtxNdx + 0];
+				line.colors[1] = colors[vtxNdx + 1];
+
+				outLines.push_back(line);
+			}
+			break;
+		}
+
+		case GL_LINE_STRIP:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 1; ++vtxNdx)
+			{
+				LineSceneSpec::SceneLine line;
+				line.positions[0] = vertices[vtxNdx + 0];
+				line.positions[1] = vertices[vtxNdx + 1];
+
+				line.colors[0] = colors[vtxNdx + 0];
+				line.colors[1] = colors[vtxNdx + 1];
+
+				outLines.push_back(line);
+			}
+			break;
+		}
+
+		case GL_LINE_LOOP:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size(); ++vtxNdx)
+			{
+				LineSceneSpec::SceneLine line;
+				line.positions[0] = vertices[(vtxNdx + 0) % (int)vertices.size()];
+				line.positions[1] = vertices[(vtxNdx + 1) % (int)vertices.size()];
+
+				line.colors[0] = colors[(vtxNdx + 0) % (int)vertices.size()];
+				line.colors[1] = colors[(vtxNdx + 1) % (int)vertices.size()];
+
+				outLines.push_back(line);
+			}
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+} // anonymous
+
+RasterizationTests::RasterizationTests (Context& context)
+	: TestCaseGroup(context, "rasterization", "Rasterization Tests")
+{
+}
+
+RasterizationTests::~RasterizationTests (void)
+{
+}
+
+void RasterizationTests::init (void)
+{
+	// .primitives
+	{
+		tcu::TestCaseGroup* const primitives = new tcu::TestCaseGroup(m_testCtx, "primitives", "Primitive rasterization");
+
+		addChild(primitives);
+
+		primitives->addChild(new TrianglesCase		(m_context, "triangles",		"Render primitives as GL_TRIANGLES, verify rasterization result"));
+		primitives->addChild(new TriangleStripCase	(m_context, "triangle_strip",	"Render primitives as GL_TRIANGLE_STRIP, verify rasterization result"));
+		primitives->addChild(new TriangleFanCase	(m_context, "triangle_fan",		"Render primitives as GL_TRIANGLE_FAN, verify rasterization result"));
+		primitives->addChild(new LinesCase			(m_context, "lines",			"Render primitives as GL_LINES, verify rasterization result",							PRIMITIVEWIDENESS_NARROW));
+		primitives->addChild(new LineStripCase		(m_context, "line_strip",		"Render primitives as GL_LINE_STRIP, verify rasterization result",						PRIMITIVEWIDENESS_NARROW));
+		primitives->addChild(new LineLoopCase		(m_context, "line_loop",		"Render primitives as GL_LINE_LOOP, verify rasterization result",						PRIMITIVEWIDENESS_NARROW));
+		primitives->addChild(new LinesCase			(m_context, "lines_wide",		"Render primitives as GL_LINES with wide lines, verify rasterization result",			PRIMITIVEWIDENESS_WIDE));
+		primitives->addChild(new LineStripCase		(m_context, "line_strip_wide",	"Render primitives as GL_LINE_STRIP with wide lines, verify rasterization result",		PRIMITIVEWIDENESS_WIDE));
+		primitives->addChild(new LineLoopCase		(m_context, "line_loop_wide",	"Render primitives as GL_LINE_LOOP with wide lines, verify rasterization result",		PRIMITIVEWIDENESS_WIDE));
+		primitives->addChild(new PointCase			(m_context, "points",			"Render primitives as GL_POINTS, verify rasterization result",							PRIMITIVEWIDENESS_WIDE));
+	}
+
+	// .fill_rules
+	{
+		tcu::TestCaseGroup* const fillRules = new tcu::TestCaseGroup(m_testCtx, "fill_rules", "Primitive fill rules");
+
+		addChild(fillRules);
+
+		fillRules->addChild(new FillRuleCase(m_context,	"basic_quad",			"Verify fill rules",	FillRuleCase::FILLRULECASE_BASIC));
+		fillRules->addChild(new FillRuleCase(m_context,	"basic_quad_reverse",	"Verify fill rules",	FillRuleCase::FILLRULECASE_REVERSED));
+		fillRules->addChild(new FillRuleCase(m_context,	"clipped_full",			"Verify fill rules",	FillRuleCase::FILLRULECASE_CLIPPED_FULL));
+		fillRules->addChild(new FillRuleCase(m_context,	"clipped_partly",		"Verify fill rules",	FillRuleCase::FILLRULECASE_CLIPPED_PARTIAL));
+		fillRules->addChild(new FillRuleCase(m_context,	"projected",			"Verify fill rules",	FillRuleCase::FILLRULECASE_PROJECTED));
+	}
+
+	// .culling
+	{
+		static const struct CullMode
+		{
+			glw::GLenum	mode;
+			const char*	prefix;
+		} cullModes[] =
+		{
+			{ GL_FRONT,				"front_"	},
+			{ GL_BACK,				"back_"		},
+			{ GL_FRONT_AND_BACK,	"both_"		},
+		};
+		static const struct PrimitiveType
+		{
+			glw::GLenum	type;
+			const char*	name;
+		} primitiveTypes[] =
+		{
+			{ GL_TRIANGLES,			"triangles"			},
+			{ GL_TRIANGLE_STRIP,	"triangle_strip"	},
+			{ GL_TRIANGLE_FAN,		"triangle_fan"		},
+		};
+		static const struct FrontFaceOrder
+		{
+			glw::GLenum	mode;
+			const char*	postfix;
+		} frontOrders[] =
+		{
+			{ GL_CCW,	""			},
+			{ GL_CW,	"_reverse"	},
+		};
+
+		tcu::TestCaseGroup* const culling = new tcu::TestCaseGroup(m_testCtx, "culling", "Culling");
+
+		addChild(culling);
+
+		for (int cullModeNdx   = 0; cullModeNdx   < DE_LENGTH_OF_ARRAY(cullModes);      ++cullModeNdx)
+		for (int primitiveNdx  = 0; primitiveNdx  < DE_LENGTH_OF_ARRAY(primitiveTypes); ++primitiveNdx)
+		for (int frontOrderNdx = 0; frontOrderNdx < DE_LENGTH_OF_ARRAY(frontOrders);    ++frontOrderNdx)
+		{
+			const std::string name = std::string(cullModes[cullModeNdx].prefix) + primitiveTypes[primitiveNdx].name + frontOrders[frontOrderNdx].postfix;
+
+			culling->addChild(new CullingTest(m_context, name.c_str(), "Test primitive culling.", cullModes[cullModeNdx].mode, primitiveTypes[primitiveNdx].type, frontOrders[frontOrderNdx].mode));
+		}
+	}
+
+	// .interpolation
+	{
+		tcu::TestCaseGroup* const interpolation = new tcu::TestCaseGroup(m_testCtx, "interpolation", "Test interpolation");
+
+		addChild(interpolation);
+
+		// .basic
+		{
+			tcu::TestCaseGroup* const basic = new tcu::TestCaseGroup(m_testCtx, "basic", "Non-projective interpolation");
+
+			interpolation->addChild(basic);
+
+			basic->addChild(new TriangleInterpolationTest		(m_context, "triangles",		"Verify triangle interpolation",		GL_TRIANGLES,		INTERPOLATIONFLAGS_NONE));
+			basic->addChild(new TriangleInterpolationTest		(m_context, "triangle_strip",	"Verify triangle strip interpolation",	GL_TRIANGLE_STRIP,	INTERPOLATIONFLAGS_NONE));
+			basic->addChild(new TriangleInterpolationTest		(m_context, "triangle_fan",		"Verify triangle fan interpolation",	GL_TRIANGLE_FAN,	INTERPOLATIONFLAGS_NONE));
+			basic->addChild(new LineInterpolationTest			(m_context, "lines",			"Verify line interpolation",			GL_LINES,			INTERPOLATIONFLAGS_NONE,	1.0f));
+			basic->addChild(new LineInterpolationTest			(m_context, "line_strip",		"Verify line strip interpolation",		GL_LINE_STRIP,		INTERPOLATIONFLAGS_NONE,	1.0f));
+			basic->addChild(new LineInterpolationTest			(m_context, "line_loop",		"Verify line loop interpolation",		GL_LINE_LOOP,		INTERPOLATIONFLAGS_NONE,	1.0f));
+			basic->addChild(new LineInterpolationTest			(m_context, "lines_wide",		"Verify wide line interpolation",		GL_LINES,			INTERPOLATIONFLAGS_NONE,	5.0f));
+			basic->addChild(new LineInterpolationTest			(m_context, "line_strip_wide",	"Verify wide line strip interpolation",	GL_LINE_STRIP,		INTERPOLATIONFLAGS_NONE,	5.0f));
+			basic->addChild(new LineInterpolationTest			(m_context, "line_loop_wide",	"Verify wide line loop interpolation",	GL_LINE_LOOP,		INTERPOLATIONFLAGS_NONE,	5.0f));
+		}
+
+		// .projected
+		{
+			tcu::TestCaseGroup* const projected = new tcu::TestCaseGroup(m_testCtx, "projected", "Projective interpolation");
+
+			interpolation->addChild(projected);
+
+			projected->addChild(new TriangleInterpolationTest	(m_context, "triangles",		"Verify triangle interpolation",		GL_TRIANGLES,		INTERPOLATIONFLAGS_PROJECTED));
+			projected->addChild(new TriangleInterpolationTest	(m_context, "triangle_strip",	"Verify triangle strip interpolation",	GL_TRIANGLE_STRIP,	INTERPOLATIONFLAGS_PROJECTED));
+			projected->addChild(new TriangleInterpolationTest	(m_context, "triangle_fan",		"Verify triangle fan interpolation",	GL_TRIANGLE_FAN,	INTERPOLATIONFLAGS_PROJECTED));
+			projected->addChild(new LineInterpolationTest		(m_context, "lines",			"Verify line interpolation",			GL_LINES,			INTERPOLATIONFLAGS_PROJECTED,	1.0f));
+			projected->addChild(new LineInterpolationTest		(m_context, "line_strip",		"Verify line strip interpolation",		GL_LINE_STRIP,		INTERPOLATIONFLAGS_PROJECTED,	1.0f));
+			projected->addChild(new LineInterpolationTest		(m_context, "line_loop",		"Verify line loop interpolation",		GL_LINE_LOOP,		INTERPOLATIONFLAGS_PROJECTED,	1.0f));
+			projected->addChild(new LineInterpolationTest		(m_context, "lines_wide",		"Verify wide line interpolation",		GL_LINES,			INTERPOLATIONFLAGS_PROJECTED,	5.0f));
+			projected->addChild(new LineInterpolationTest		(m_context, "line_strip_wide",	"Verify wide line strip interpolation",	GL_LINE_STRIP,		INTERPOLATIONFLAGS_PROJECTED,	5.0f));
+			projected->addChild(new LineInterpolationTest		(m_context, "line_loop_wide",	"Verify wide line loop interpolation",	GL_LINE_LOOP,		INTERPOLATIONFLAGS_PROJECTED,	5.0f));
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fRasterizationTests.hpp b/modules/gles2/functional/es2fRasterizationTests.hpp
new file mode 100644
index 0000000..ef6ac1d
--- /dev/null
+++ b/modules/gles2/functional/es2fRasterizationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FRASTERIZATIONTESTS_HPP
+#define _ES2FRASTERIZATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rasterization tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class RasterizationTests : public TestCaseGroup
+{
+public:
+							RasterizationTests	(Context& context);
+							~RasterizationTests	(void);
+
+	void					init				(void);
+
+private:
+							RasterizationTests	(const RasterizationTests& other);
+	RasterizationTests&		operator=			(const RasterizationTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FRASTERIZATIONTESTS_HPP
diff --git a/modules/gles2/functional/es2fRboStateQueryTests.cpp b/modules/gles2/functional/es2fRboStateQueryTests.cpp
new file mode 100644
index 0000000..b49bbf5
--- /dev/null
+++ b/modules/gles2/functional/es2fRboStateQueryTests.cpp
@@ -0,0 +1,296 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rbo state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fRboStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es2fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace
+{
+
+void checkRenderbufferComponentSize (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, int r, int g, int b, int a, int d, int s)
+{
+	using tcu::TestLog;
+
+	const int referenceSizes[] = {r, g, b, a, d, s};
+	const GLenum paramNames[] =
+	{
+		GL_RENDERBUFFER_RED_SIZE,
+		GL_RENDERBUFFER_GREEN_SIZE,
+		GL_RENDERBUFFER_BLUE_SIZE,
+		GL_RENDERBUFFER_ALPHA_SIZE,
+		GL_RENDERBUFFER_DEPTH_SIZE,
+		GL_RENDERBUFFER_STENCIL_SIZE
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(referenceSizes) == DE_LENGTH_OF_ARRAY(paramNames));
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(referenceSizes); ++ndx)
+	{
+		if (referenceSizes[ndx] == -1)
+			continue;
+
+		StateQueryMemoryWriteGuard<GLint> state;
+		gl.glGetRenderbufferParameteriv(GL_RENDERBUFFER, paramNames[ndx], &state);
+
+		if (!state.verifyValidity(testCtx))
+			return;
+
+		if (state < referenceSizes[ndx])
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: Expected greater or equal to " << referenceSizes[ndx] << "; got " << state << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+		}
+	}
+}
+
+void checkIntEquals (tcu::TestContext& testCtx, GLint got, GLint expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void checkRenderbufferParam (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetRenderbufferParameteriv(GL_RENDERBUFFER, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntEquals(testCtx, state, reference);
+}
+
+class RboSizeCase : public ApiCase
+{
+public:
+	RboSizeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_WIDTH,		0);
+		checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_HEIGHT,	0);
+		expectError(GL_NO_ERROR);
+
+		int maxRenderbufferSize = 1;
+		glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxRenderbufferSize);
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 30;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLint w = rnd.getInt(0, maxRenderbufferSize);
+			const GLint h = rnd.getInt(0, maxRenderbufferSize);
+
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, w, h);
+			expectError(GL_NO_ERROR);
+
+			checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_WIDTH,		w);
+			checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_HEIGHT,	h);
+		}
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+class RboInternalFormatCase : public ApiCase
+{
+public:
+	RboInternalFormatCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_INTERNAL_FORMAT, GL_RGBA4);
+		expectError(GL_NO_ERROR);
+
+		const GLenum requiredColorformats[] =
+		{
+			GL_RGBA4, GL_RGB5_A1, GL_RGB565
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requiredColorformats); ++ndx)
+		{
+			glRenderbufferStorage(GL_RENDERBUFFER, requiredColorformats[ndx], 1, 1);
+			expectError(GL_NO_ERROR);
+
+			checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_INTERNAL_FORMAT, requiredColorformats[ndx]);
+		}
+
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+class RboComponentSizeColorCase : public ApiCase
+{
+public:
+	RboComponentSizeColorCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkRenderbufferComponentSize(m_testCtx, *this, 0, 0, 0, 0, 0, 0);
+		expectError(GL_NO_ERROR);
+
+		const struct ColorFormat
+		{
+			GLenum	internalFormat;
+			int		bitsR, bitsG, bitsB, bitsA;
+		} requiredColorFormats[] =
+		{
+			{ GL_RGBA4,			4,	4,	4,	4	},
+			{ GL_RGB5_A1,		5,	5,	5,	1	},
+			{ GL_RGB565,		5,	6,	5,	0	},
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requiredColorFormats); ++ndx)
+		{
+			glRenderbufferStorage(GL_RENDERBUFFER, requiredColorFormats[ndx].internalFormat, 1, 1);
+			expectError(GL_NO_ERROR);
+
+			checkRenderbufferComponentSize(m_testCtx, *this, requiredColorFormats[ndx].bitsR, requiredColorFormats[ndx].bitsG, requiredColorFormats[ndx].bitsB, requiredColorFormats[ndx].bitsA, -1, -1);
+		}
+
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+class RboComponentSizeDepthCase : public ApiCase
+{
+public:
+	RboComponentSizeDepthCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		const struct DepthFormat
+		{
+			GLenum	internalFormat;
+			int		dbits;
+			int		sbits;
+		} requiredDepthFormats[] =
+		{
+			{ GL_DEPTH_COMPONENT16,		16, 0 },
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requiredDepthFormats); ++ndx)
+		{
+			glRenderbufferStorage(GL_RENDERBUFFER, requiredDepthFormats[ndx].internalFormat, 1, 1);
+			expectError(GL_NO_ERROR);
+
+			checkRenderbufferComponentSize(m_testCtx, *this, -1, -1, -1, -1, requiredDepthFormats[ndx].dbits, requiredDepthFormats[ndx].sbits);
+		}
+
+		// STENCIL_INDEX8 is required, in that case sBits >= 8
+		{
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, 1, 1);
+			expectError(GL_NO_ERROR);
+
+			StateQueryMemoryWriteGuard<GLint> state;
+			glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_STENCIL_SIZE, &state);
+
+			if (state.verifyValidity(m_testCtx) && state < 8)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected greater or equal to 8; got " << state << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+			}
+		}
+
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+} // anonymous
+
+
+RboStateQueryTests::RboStateQueryTests (Context& context)
+	: TestCaseGroup(context, "rbo", "Rbo State Query tests")
+{
+}
+
+void RboStateQueryTests::init (void)
+{
+	addChild(new RboSizeCase				(m_context, "renderbuffer_size",					"RENDERBUFFER_WIDTH and RENDERBUFFER_HEIGHT"));
+	addChild(new RboInternalFormatCase		(m_context, "renderbuffer_internal_format",			"RENDERBUFFER_INTERNAL_FORMAT"));
+	addChild(new RboComponentSizeColorCase	(m_context, "renderbuffer_component_size_color",	"RENDERBUFFER_x_SIZE"));
+	addChild(new RboComponentSizeDepthCase	(m_context, "renderbuffer_component_size_depth",	"RENDERBUFFER_x_SIZE"));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fRboStateQueryTests.hpp b/modules/gles2/functional/es2fRboStateQueryTests.hpp
new file mode 100644
index 0000000..a595af9
--- /dev/null
+++ b/modules/gles2/functional/es2fRboStateQueryTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FRBOSTATEQUERYTESTS_HPP
+#define _ES2FRBOSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rbo state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class RboStateQueryTests : public TestCaseGroup
+{
+public:
+							RboStateQueryTests	(Context& context);
+
+	void					init				(void);
+
+private:
+							RboStateQueryTests	(const RboStateQueryTests& other);
+	RboStateQueryTests&		operator=			(const RboStateQueryTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FRBOSTATEQUERYTESTS_HPP
diff --git a/modules/gles2/functional/es2fReadPixelsTests.cpp b/modules/gles2/functional/es2fReadPixelsTests.cpp
new file mode 100644
index 0000000..cabc677
--- /dev/null
+++ b/modules/gles2/functional/es2fReadPixelsTests.cpp
@@ -0,0 +1,295 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Read pixels tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fReadPixelsTests.hpp"
+
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+#include "gluDefs.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+
+#include "glw.h"
+
+#include <cstring>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ReadPixelsTest : public TestCase
+{
+public:
+					ReadPixelsTest	(Context& context, const char* name, const char* description, bool chooseFormat, int alignment);
+
+	IterateResult	iterate			(void);
+	void			render			(tcu::Texture2D& reference);
+
+private:
+	bool			m_chooseFormat;
+	int				m_alignment;
+	int				m_seed;
+
+	void			getFormatInfo	(tcu::TextureFormat& format, GLint& glFormat, GLint& glType, int& pixelSize, bool& align);
+};
+
+ReadPixelsTest::ReadPixelsTest	(Context& context, const char* name, const char* description, bool chooseFormat, int alignment)
+	: TestCase			(context, name, description)
+	, m_chooseFormat	(chooseFormat)
+	, m_alignment		(alignment)
+	, m_seed			(deStringHash(name))
+{
+}
+
+void ReadPixelsTest::render (tcu::Texture2D& reference)
+{
+	// Create program
+	const char* vertexSource =
+	"attribute mediump vec2 a_coord;\n"
+	"void main (void)\n"
+	"{\n"
+	"\tgl_Position = vec4(a_coord, 0.0, 1.0);\n"
+	"}\n";
+
+	const char* fragmentSource =
+	"void main (void)\n"
+	"{\n"
+	"\tgl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n"
+	"}\n";
+
+	glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexSource, fragmentSource));
+
+	m_testCtx.getLog() << program;
+	TCU_CHECK(program.isOk());
+	GLU_CHECK_CALL(glUseProgram(program.getProgram()));
+
+	// Render
+	{
+		const float coords[] =
+		{
+			-0.5f, -0.5f,
+			 0.5f, -0.5f,
+			 0.5f,  0.5f,
+
+			 0.5f,  0.5f,
+			-0.5f,  0.5f,
+			-0.5f, -0.5f
+		};
+		GLuint coordLoc;
+
+		coordLoc = glGetAttribLocation(program.getProgram(), "a_coord");
+		GLU_CHECK_MSG("glGetAttribLocation()");
+
+		GLU_CHECK_CALL(glEnableVertexAttribArray(coordLoc));
+
+		GLU_CHECK_CALL(glVertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, coords));
+
+		GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 6));
+		GLU_CHECK_CALL(glDisableVertexAttribArray(coordLoc));
+	}
+
+	// Render reference
+
+	const int coordX1 = (int)((-0.5f * reference.getWidth()		/ 2.0f) + reference.getWidth() / 2.0f);
+	const int coordY1 = (int)((-0.5f * reference.getHeight()	/ 2.0f) + reference.getHeight() / 2.0f);
+	const int coordX2 = (int)(( 0.5f * reference.getWidth()		/ 2.0f) + reference.getWidth() / 2.0f);
+	const int coordY2 = (int)(( 0.5f * reference.getHeight()	/ 2.0f) + reference.getHeight() / 2.0f);
+
+	for (int x = 0; x < reference.getWidth(); x++)
+	{
+		if (x < coordX1 || x > coordX2)
+			continue;
+
+		for (int y = 0; y < reference.getHeight(); y++)
+		{
+			if (y >= coordY1 && y <= coordY2)
+				reference.getLevel(0).setPixel(tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f), x, y);
+		}
+	}
+}
+
+void ReadPixelsTest::getFormatInfo (tcu::TextureFormat& format, GLint& glFormat, GLint& glType, int& pixelSize, bool& align)
+{
+	if (m_chooseFormat)
+	{
+		GLU_CHECK_CALL(glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &glFormat));
+		GLU_CHECK_CALL(glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &glType));
+
+		format = glu::mapGLTransferFormat(glFormat, glType);
+
+		// Check if aligment is allowed
+		switch (glType)
+		{
+			case GL_BYTE:
+			case GL_UNSIGNED_BYTE:
+			case GL_SHORT:
+			case GL_UNSIGNED_SHORT:
+			case GL_INT:
+			case GL_UNSIGNED_INT:
+			case GL_FLOAT:
+			case GL_HALF_FLOAT:
+				align = true;
+				break;
+
+			case GL_UNSIGNED_SHORT_5_6_5:
+			case GL_UNSIGNED_SHORT_4_4_4_4:
+			case GL_UNSIGNED_SHORT_5_5_5_1:
+			case GL_UNSIGNED_INT_2_10_10_10_REV:
+			case GL_UNSIGNED_INT_10F_11F_11F_REV:
+			case GL_UNSIGNED_INT_24_8:
+			case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
+			case GL_UNSIGNED_INT_5_9_9_9_REV:
+				align = false;
+				break;
+
+			default:
+				throw tcu::InternalError("Unsupported format", "", __FILE__, __LINE__);
+		}
+
+		pixelSize	= format.getPixelSize();
+	}
+	else
+	{
+		format		= tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);
+		pixelSize	= 1 * 4;
+		align		= true;
+		glFormat	= GL_RGBA;
+		glType		= GL_UNSIGNED_BYTE;
+	}
+}
+
+TestCase::IterateResult ReadPixelsTest::iterate (void)
+{
+	// Create reference
+	const int					width	= 13;
+	const int					height	= 13;
+
+	de::Random					rnd(m_seed);
+
+	tcu::TextureFormat			format(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);
+	int							pixelSize;
+	GLint						glFormat;
+	GLint						glType;
+	bool						align;
+
+	getFormatInfo(format, glFormat, glType, pixelSize, align);
+	m_testCtx.getLog() << tcu::TestLog::Message << "Format: " << glu::getPixelFormatStr(glFormat) << ", Type: " << glu::getTypeStr(glType) << tcu::TestLog::EndMessage;
+
+	tcu::Texture2D reference(format, width, height);
+	reference.allocLevel(0);
+
+	GLU_CHECK_CALL(glViewport(0, 0, width, height));
+
+	// Clear color
+	{
+		const float red		= rnd.getFloat();
+		const float green	= rnd.getFloat();
+		const float blue	= rnd.getFloat();
+		const float alpha	= 1.0f;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Clear color: (" << red << ", " << green << ", " << blue << ", " << alpha << ")" << tcu::TestLog::EndMessage;
+
+		// Clear target
+		GLU_CHECK_CALL(glClearColor(red, green, blue, alpha));
+		GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+		// Clear reference
+		for (int x = 0; x < reference.getWidth(); x++)
+			for (int y = 0; y < reference.getHeight(); y++)
+					reference.getLevel(0).setPixel(tcu::Vec4(red, green, blue, alpha), x, y);
+	}
+
+	render(reference);
+
+	std::vector<deUint8> pixelData;
+	const int rowPitch = (align ? m_alignment * deCeilFloatToInt32(pixelSize * width / (float)m_alignment) : width * pixelSize);
+
+	pixelData.resize(rowPitch * height, 0);
+
+	GLU_CHECK_CALL(glPixelStorei(GL_PACK_ALIGNMENT, m_alignment));
+	GLU_CHECK_CALL(glReadPixels(0, 0, width, height, glFormat, glType, &(pixelData[0])));
+
+	if (m_context.getRenderTarget().getNumSamples() > 1)
+	{
+		const tcu::IVec4	formatBitDepths	= tcu::getTextureFormatBitDepth(format);
+		const deUint8		redThreshold	= (deUint8)deCeilFloatToInt32(256.0f * (2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().redBits,		formatBitDepths.x()))));
+		const deUint8		greenThreshold	= (deUint8)deCeilFloatToInt32(256.0f * (2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().greenBits,	formatBitDepths.y()))));
+		const deUint8		blueThreshold	= (deUint8)deCeilFloatToInt32(256.0f * (2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().blueBits,		formatBitDepths.z()))));
+		const deUint8		alphaThreshold	= (deUint8)deCeilFloatToInt32(256.0f * (2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().alphaBits,	formatBitDepths.w()))));
+
+		if (tcu::bilinearCompare(m_testCtx.getLog(), "Result", "Result", reference.getLevel(0), tcu::PixelBufferAccess(format, width, height, 1, rowPitch, 0, &(pixelData[0])), tcu::RGBA(redThreshold, greenThreshold, blueThreshold, alphaThreshold), tcu::COMPARE_LOG_RESULT))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	}
+	else
+	{
+		const tcu::IVec4	formatBitDepths	= tcu::getTextureFormatBitDepth(format);
+		const float			redThreshold	= 2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().redBits,	formatBitDepths.x()));
+		const float			greenThreshold	= 2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().greenBits,	formatBitDepths.y()));
+		const float			blueThreshold	= 2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().blueBits,	formatBitDepths.z()));
+		const float			alphaThreshold	= 2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().alphaBits,	formatBitDepths.w()));
+
+		// Compare
+		if (tcu::floatThresholdCompare(m_testCtx.getLog(), "Result", "Result", reference.getLevel(0), tcu::PixelBufferAccess(format, width, height, 1, rowPitch, 0, &(pixelData[0])), tcu::Vec4(redThreshold, greenThreshold, blueThreshold, alphaThreshold), tcu::COMPARE_LOG_RESULT))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	}
+
+	return STOP;
+}
+
+ReadPixelsTests::ReadPixelsTests (Context& context)
+	: TestCaseGroup(context, "read_pixels", "ReadPixel tests")
+{
+}
+
+void ReadPixelsTests::init (void)
+{
+	addChild(new ReadPixelsTest(m_context, "rgba_ubyte_align_1", "", false, 1));
+	addChild(new ReadPixelsTest(m_context, "rgba_ubyte_align_2", "", false, 2));
+	addChild(new ReadPixelsTest(m_context, "rgba_ubyte_align_4", "", false, 4));
+	addChild(new ReadPixelsTest(m_context, "rgba_ubyte_align_8", "", false, 8));
+
+	addChild(new ReadPixelsTest(m_context, "choose_align_1", "", true, 1));
+	addChild(new ReadPixelsTest(m_context, "choose_align_2", "", true, 2));
+	addChild(new ReadPixelsTest(m_context, "choose_align_4", "", true, 4));
+	addChild(new ReadPixelsTest(m_context, "choose_align_8", "", true, 8));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fReadPixelsTests.hpp b/modules/gles2/functional/es2fReadPixelsTests.hpp
new file mode 100644
index 0000000..57de5fc
--- /dev/null
+++ b/modules/gles2/functional/es2fReadPixelsTests.hpp
@@ -0,0 +1,47 @@
+#ifndef _ES2FREADPIXELSTESTS_HPP
+#define _ES2FREADPIXELSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Read pixels tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ReadPixelsTests : public TestCaseGroup
+{
+public:
+			ReadPixelsTests	(Context& context);
+	void	init			(void);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FREADPIXELSTESTS_HPP
diff --git a/modules/gles2/functional/es2fScissorTests.cpp b/modules/gles2/functional/es2fScissorTests.cpp
new file mode 100644
index 0000000..f102b87
--- /dev/null
+++ b/modules/gles2/functional/es2fScissorTests.cpp
@@ -0,0 +1,101 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES2 Scissor tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fScissorTests.hpp"
+
+#include "glsScissorTests.hpp"
+
+#include "tcuVector.hpp"
+
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+ScissorTests::ScissorTests (Context& context)
+	: TestCaseGroup	(context, "scissor", "Scissor Tests")
+{
+}
+
+ScissorTests::~ScissorTests (void)
+{
+}
+
+void ScissorTests::init (void)
+{
+	using tcu::Vec4;
+	typedef gls::Functional::ScissorCase SC;
+
+	glu::RenderContext&		rc = m_context.getRenderContext();
+	tcu::TestContext&		tc = m_context.getTestContext();
+
+	const struct
+	{
+		const char*			name;
+		const char*			description;
+		const tcu::Vec4		scissor;
+		const tcu::Vec4		render;
+		SC::PrimitiveType	type;
+		const int			primitives;
+	} cases[] =
+	{
+		{ "contained_tris",			"Triangles fully inside scissor area (single call)",		Vec4(0.1f, 0.1f, 0.8f, 0.8f), Vec4(0.2f, 0.2f, 0.6f, 0.6f), SC::TRIANGLE,	30 },
+		{ "partial_tris",			"Triangles partially inside scissor area (single call)",	Vec4(0.3f, 0.3f, 0.4f, 0.4f), Vec4(0.2f, 0.2f, 0.6f, 0.6f), SC::TRIANGLE,	30 },
+		{ "contained_tri",			"Triangle fully inside scissor area",						Vec4(0.1f, 0.1f, 0.8f, 0.8f), Vec4(0.2f, 0.2f, 0.6f, 0.6f), SC::TRIANGLE,	1  },
+		{ "enclosing_tri",			"Triangle fully covering scissor area",						Vec4(0.4f, 0.4f, 0.2f, 0.2f), Vec4(0.2f, 0.2f, 0.6f, 0.6f), SC::TRIANGLE,	1  },
+		{ "partial_tri",			"Triangle partially inside scissor area",					Vec4(0.4f, 0.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 1.0f, 1.0f), SC::TRIANGLE,	1  },
+		{ "outside_render_tri",		"Triangle with scissor area outside render target",			Vec4(1.4f, 1.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 0.6f, 0.6f), SC::TRIANGLE,	1  },
+		{ "partial_lines",			"Linse partially inside scissor area",						Vec4(0.4f, 0.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 1.0f, 1.0f), SC::LINE,		30 },
+		{ "contained_line",			"Line fully inside scissor area",							Vec4(0.1f, 0.1f, 0.8f, 0.8f), Vec4(0.2f, 0.2f, 0.6f, 0.6f), SC::LINE,		1  },
+		{ "partial_line",			"Line partially inside scissor area",						Vec4(0.4f, 0.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 1.0f, 1.0f), SC::LINE,		1  },
+		{ "outside_render_line",	"Line with scissor area outside render target",				Vec4(1.4f, 1.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 0.6f, 0.6f), SC::LINE,		1  },
+		{ "contained_point",		"Point fully inside scissor area",							Vec4(0.1f, 0.1f, 0.8f, 0.8f), Vec4(0.5f, 0.5f, 0.0f, 0.0f), SC::POINT,		1  },
+		{ "partial_points",			"Points partially inside scissor area",						Vec4(0.4f, 0.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 1.0f, 1.0f), SC::POINT,		30 },
+		{ "outside_point",			"Point fully outside scissor area",							Vec4(0.4f, 0.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 0.0f, 0.0f), SC::POINT,		1  },
+		{ "outside_render_point",	"Point with scissor area outside render target",			Vec4(1.4f, 1.4f, 0.6f, 0.6f), Vec4(0.5f, 0.5f, 0.0f, 0.0f),	SC::POINT,		1  }
+	};
+
+	for(int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+	{
+		addChild(SC::createPrimitiveTest(rc,
+										 tc,
+										 cases[caseNdx].scissor,
+										 cases[caseNdx].render,
+										 cases[caseNdx].type,
+										 cases[caseNdx].primitives,
+										 cases[caseNdx].name,
+										 cases[caseNdx].description));
+	}
+
+	addChild(SC::createClearTest(rc, tc, Vec4(0.1f, 0.1f, 0.8f, 0.8f), GL_DEPTH_BUFFER_BIT,		"clear_depth",		"Depth buffer clear"));
+	addChild(SC::createClearTest(rc, tc, Vec4(0.1f, 0.1f, 0.8f, 0.8f), GL_STENCIL_BUFFER_BIT,	"clear_stencil",	"Stencil buffer clear"));
+	addChild(SC::createClearTest(rc, tc, Vec4(0.1f, 0.1f, 0.8f, 0.8f), GL_COLOR_BUFFER_BIT,		"clear_color",		"Color buffer clear"));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fScissorTests.hpp b/modules/gles2/functional/es2fScissorTests.hpp
new file mode 100644
index 0000000..739bfed
--- /dev/null
+++ b/modules/gles2/functional/es2fScissorTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FSCISSORTESTS_HPP
+#define _ES2FSCISSORTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES2 Scissor tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ScissorTests : public TestCaseGroup
+{
+public:
+						ScissorTests		(Context& context);
+	virtual				~ScissorTests		(void);
+
+	void				init				(void);
+
+private:
+						ScissorTests		(const ScissorTests& other);
+	ScissorTests&		operator=			(const ScissorTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSCISSORTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderAlgorithmTests.cpp b/modules/gles2/functional/es2fShaderAlgorithmTests.cpp
new file mode 100644
index 0000000..162aa2a
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderAlgorithmTests.cpp
@@ -0,0 +1,276 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Algorithm implementation tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderAlgorithmTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuStringTemplate.hpp"
+
+#include "deInt32.h"
+#include "deMemory.h"
+
+#include <map>
+#include <algorithm>
+
+using namespace std;
+using namespace tcu;
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+// ShaderAlgorithmCase
+
+class ShaderAlgorithmCase : public ShaderRenderCase
+{
+public:
+								ShaderAlgorithmCase			(Context& context, const char* name, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, const char* vertShaderSource, const char* fragShaderSource);
+	virtual						~ShaderAlgorithmCase		(void);
+
+private:
+								ShaderAlgorithmCase			(const ShaderAlgorithmCase&);	// not allowed!
+	ShaderAlgorithmCase&		operator=					(const ShaderAlgorithmCase&);	// not allowed!
+};
+
+ShaderAlgorithmCase::ShaderAlgorithmCase (Context& context, const char* name, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, const char* vertShaderSource, const char* fragShaderSource)
+	: ShaderRenderCase(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
+{
+	m_vertShaderSource	= vertShaderSource;
+	m_fragShaderSource	= fragShaderSource;
+}
+
+ShaderAlgorithmCase::~ShaderAlgorithmCase (void)
+{
+}
+
+// Helpers.
+
+static ShaderAlgorithmCase* createExpressionCase (Context& context, const char* caseName, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, LineStream& shaderBody)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	vtx << "attribute highp vec4 a_position;\n";
+	vtx << "attribute highp vec4 a_unitCoords;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec3 v_color;\n";
+		frag << "varying mediump vec3 v_color;\n";
+	}
+	else
+	{
+		vtx << "varying mediump vec4 v_coords;\n";
+		frag << "varying mediump vec4 v_coords;\n";
+	}
+
+//	op << "uniform mediump sampler2D ut_brick;\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Write matrix.
+	if (isVertexCase)
+		op << "	${PRECISION} vec4 coords = a_unitCoords;\n";
+	else
+		op << "	${PRECISION} vec4 coords = v_coords;\n";
+
+	op << "	${PRECISION} vec3 res = vec3(0.0);\n";
+	op << shaderBody.str();
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = res;\n";
+		frag << "	gl_FragColor = vec4(v_color, 1.0);\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_unitCoords;\n";
+		frag << "	gl_FragColor = vec4(res, 1.0);\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	return new ShaderAlgorithmCase(context, caseName, description, isVertexCase, evalFunc, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+// ShaderAlgorithmTests.
+
+ShaderAlgorithmTests::ShaderAlgorithmTests(Context& context)
+	: TestCaseGroup(context, "algorithm", "Miscellaneous algorithm implementations using shaders.")
+{
+}
+
+ShaderAlgorithmTests::~ShaderAlgorithmTests (void)
+{
+}
+
+void ShaderAlgorithmTests::init (void)
+{
+//	TestCaseGroup* colorGroup = new TestCaseGroup(m_testCtx, "color", "Miscellaneous color related algorithm tests.");
+//	addChild(colorGroup);
+
+	#define SHADER_OP_CASE(NAME, DESCRIPTION, SHADER_OP, EVAL_FUNC_BODY)														\
+		do {																													\
+			struct Eval_##NAME { static void eval (ShaderEvalContext& c) EVAL_FUNC_BODY };										\
+			addChild(createExpressionCase(m_context, #NAME "_vertex", DESCRIPTION, true, &Eval_##NAME::eval, SHADER_OP));		\
+			addChild(createExpressionCase(m_context, #NAME "_fragment", DESCRIPTION, false, &Eval_##NAME::eval, SHADER_OP));	\
+		} while (deGetFalse())
+
+	SHADER_OP_CASE(hsl_to_rgb, "Conversion from HSL color space into RGB.",
+		LineStream(1)
+		<< "mediump float H = coords.x, S = coords.y, L = coords.z;"
+		<< "mediump float v = (L <= 0.5) ? (L * (1.0 + S)) : (L + S - L * S);"
+		<< "res = vec3(L); // default to gray"
+		<< "if (v > 0.0)"
+		<< "{"
+		<< "	mediump float m = L + L - v;"
+		<< "	mediump float sv = (v - m) / v;"
+		<< "	H *= 6.0;"
+		<< "	mediump int sextant = int(H);"
+		<< "	mediump float fract = H - float(sextant);"
+		<< "	mediump float vsf = v * sv * fract;"
+		<< "	mediump float mid1 = m + vsf;"
+		<< "	mediump float mid2 = m - vsf;"
+		<< "	if (sextant == 0)      res = vec3(v, mid1, m);"
+		<< "	else if (sextant == 1) res = vec3(mid2, v, m);"
+		<< "	else if (sextant == 2) res = vec3(m, v, mid1);"
+		<< "	else if (sextant == 3) res = vec3(m, mid2, v);"
+		<< "	else if (sextant == 4) res = vec3(mid1, m, v);"
+		<< "	else                   res = vec3(v, m, mid2);"
+		<< "}",
+		{
+			float H = c.unitCoords.x();
+			float S = c.unitCoords.y();
+			float L = c.unitCoords.z();
+			Vec3 rgb = Vec3(L);
+			float v = (L <= 0.5f) ? (L * (1.0f + S)) : (L + S - L * S);
+			if (v > 0.0f)
+			{
+				float m = L + L - v;
+				float sv = (v - m) / v;
+				H *= 6.0f;
+				int sextant = int(H);
+				float fract = H - float(sextant);
+				float vsf = v * sv * fract;
+				float mid1 = m + vsf;
+				float mid2 = m - vsf;
+				if (sextant == 0)		rgb = Vec3(v, mid1, m);
+				else if (sextant == 1)	rgb = Vec3(mid2, v, m);
+				else if (sextant == 2)	rgb = Vec3(m, v, mid1);
+				else if (sextant == 3)	rgb = Vec3(m, mid2, v);
+				else if (sextant == 4)	rgb = Vec3(mid1, m, v);
+				else					rgb = Vec3(v, m, mid2);
+			}
+			c.color.xyz() = rgb;
+		});
+
+	SHADER_OP_CASE(rgb_to_hsl, "Conversion from RGB color space into HSL.",
+		LineStream(1)
+		<< "mediump float r = coords.x, g = coords.y, b = coords.z;"
+		<< "mediump float minVal = min(min(r, g), b);"
+		<< "mediump float maxVal = max(max(r, g), b);"
+		<< "mediump float L = (minVal + maxVal) * 0.5;"
+		<< "if (minVal == maxVal)"
+		<< "	res = vec3(0.0, 0.0, L);"
+		<< "else"
+		<< "{"
+		<< "	mediump float H;"
+		<< "	mediump float S;"
+		<< "	if (L < 0.5)"
+		<< "		S = (maxVal - minVal) / (maxVal + minVal);"
+		<< "	else"
+		<< "		S = (maxVal - minVal) / (2.0 - maxVal - minVal);"
+		<< ""
+		<< "	mediump float ooDiff = 1.0 / (maxVal - minVal);"
+		<< "	if (r == maxVal)      H = (g - b) * ooDiff;"
+		<< "	else if (g == maxVal) H = 2.0 + (b - r) * ooDiff;"
+		<< "	else                  H = 4.0 + (r - g) * ooDiff;"
+		<< "	H /= 6.0;"
+		<< ""
+		<< "	res = vec3(H, S, L);"
+		<< "}",
+		{
+			float r = c.unitCoords.x();
+			float g = c.unitCoords.y();
+			float b = c.unitCoords.z();
+			float minVal = min(min(r, g), b);
+			float maxVal = max(max(r, g), b);
+			float L = (minVal + maxVal) * 0.5f;
+			Vec3 hsl;
+
+			if (minVal == maxVal)
+				hsl = Vec3(0.0f, 0.0f, L);
+			else
+			{
+				float H;
+				float S;
+				if (L < 0.5f)
+					S = (maxVal - minVal) / (maxVal + minVal);
+				else
+					S = (maxVal - minVal) / (2.0f - maxVal - minVal);
+
+				float ooDiff = 1.0f / (maxVal - minVal);
+				if (r == maxVal)		H = (g - b) * ooDiff;
+				else if (g == maxVal)	H = 2.0f + (b - r) * ooDiff;
+				else					H = 4.0f + (r - g) * ooDiff;
+				H /= 6.0f;
+
+				hsl = Vec3(H, S, L);
+			}
+			c.color.xyz() = hsl;
+		});
+
+/*	SHADER_OP_CASE(image_to_grayscale, "Convert image to grayscale.",
+		LineStream(1)
+		<< "res = texture2D(ut_brick, coords.xy).rgb;",
+		{
+			c.color.xyz() = Vec3(0.5f);
+		});*/
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderAlgorithmTests.hpp b/modules/gles2/functional/es2fShaderAlgorithmTests.hpp
new file mode 100644
index 0000000..ccc7f22
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderAlgorithmTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERALGORITHMTESTS_HPP
+#define _ES2FSHADERALGORITHMTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Algorithm implementation tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderAlgorithmTests : public TestCaseGroup
+{
+public:
+							ShaderAlgorithmTests		(Context& context);
+	virtual					~ShaderAlgorithmTests		(void);
+
+	virtual void			init						(void);
+
+private:
+							ShaderAlgorithmTests		(const ShaderAlgorithmTests&);		// not allowed!
+	ShaderAlgorithmTests&	operator=					(const ShaderAlgorithmTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERALGORITHMTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderApiTests.cpp b/modules/gles2/functional/es2fShaderApiTests.cpp
new file mode 100644
index 0000000..b580c52
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderApiTests.cpp
@@ -0,0 +1,1111 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderApiTests.hpp"
+#include "es2fApiCase.hpp"
+#include "tcuTestLog.hpp"
+
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluContextInfo.hpp"
+#include "glwFunctions.hpp"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+#include "deString.h"
+
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+#include <string>
+#include <vector>
+#include <map>
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+
+namespace
+{
+
+enum ShaderSourceCaseFlags
+{
+	CASE_EXPLICIT_SOURCE_LENGTHS	= 1,
+	CASE_RANDOM_NULL_TERMINATED		= 2
+};
+
+struct ShaderSources
+{
+	std::vector<std::string>	strings;
+	std::vector<int>			lengths;
+};
+
+// Simple shaders
+
+const char* getSimpleShaderSource (const glu::ShaderType shaderType)
+{
+	const char* simpleVertexShaderSource	= "void main (void) { gl_Position = vec4(0.0); }\n";
+	const char* simpleFragmentShaderSource	= "void main (void) { gl_FragColor = vec4(0.0); }\n";
+
+	switch (shaderType)
+	{
+		case glu::SHADERTYPE_VERTEX:
+			return simpleVertexShaderSource;
+		case glu::SHADERTYPE_FRAGMENT:
+			return simpleFragmentShaderSource;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return 0;
+}
+
+void setShaderSources (glu::Shader& shader, const ShaderSources& sources)
+{
+	std::vector<const char*> cStrings (sources.strings.size(), 0);
+
+	for (size_t ndx = 0; ndx < sources.strings.size(); ndx++)
+		cStrings[ndx] = sources.strings[ndx].c_str();
+
+	if (sources.lengths.size() > 0)
+		shader.setSources((int)cStrings.size(), &cStrings[0], &sources.lengths[0]);
+	else
+		shader.setSources((int)cStrings.size(), &cStrings[0], 0);
+}
+
+void sliceSourceString (const std::string& in, ShaderSources& out, const int numSlices, const size_t paddingLength = 0)
+{
+	DE_ASSERT(numSlices > 0);
+
+	const size_t		sliceSize			= in.length() / numSlices;
+	const size_t		sliceSizeRemainder	= in.length() - (sliceSize * numSlices);
+	const std::string	padding				(paddingLength, 'E');
+
+	for (int i = 0; i < numSlices; i++)
+	{
+		out.strings.push_back(in.substr(i * sliceSize, sliceSize) + padding);
+
+		if (paddingLength > 0)
+			out.lengths.push_back((int)sliceSize);
+	}
+
+	if (sliceSizeRemainder > 0)
+	{
+		const std::string	lastString			= in.substr(numSlices * sliceSize);
+		const int			lastStringLength	= (int)lastString.length();
+
+		out.strings.push_back(lastString + padding);
+
+		if (paddingLength > 0)
+			out.lengths.push_back(lastStringLength);
+	}
+}
+
+void queryShaderInfo (glu::RenderContext& renderCtx, deUint32 shader, glu::ShaderInfo& info)
+{
+	const glw::Functions& gl = renderCtx.getFunctions();
+
+	info.compileOk		= false;
+	info.compileTimeUs	= 0;
+	info.infoLog.clear();
+
+	// Query source, status & log.
+	{
+		int	compileStatus	= 0;
+		int sourceLen		= 0;
+		int	infoLogLen		= 0;
+		int	unusedLen;
+
+		gl.getShaderiv(shader, GL_COMPILE_STATUS,			&compileStatus);
+		gl.getShaderiv(shader, GL_SHADER_SOURCE_LENGTH,	&sourceLen);
+		gl.getShaderiv(shader, GL_INFO_LOG_LENGTH,		&infoLogLen);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv()");
+
+		info.compileOk = compileStatus != GL_FALSE;
+
+		if (sourceLen > 0)
+		{
+			std::vector<char> source(sourceLen);
+			gl.getShaderSource(shader, (int)source.size(), &unusedLen, &source[0]);
+			info.source = std::string(&source[0], sourceLen);
+		}
+
+		if (infoLogLen > 0)
+		{
+			std::vector<char> infoLog(infoLogLen);
+			gl.getShaderInfoLog(shader, (int)infoLog.size(), &unusedLen, &infoLog[0]);
+			info.infoLog = std::string(&infoLog[0], infoLogLen);
+		}
+	}
+}
+
+// Shader source generator
+
+class SourceGenerator
+{
+public:
+	virtual				~SourceGenerator	(void)	{}
+
+	virtual std::string	next				(const glu::ShaderType shaderType)			= 0;
+	virtual bool		finished			(const glu::ShaderType shaderType) const	= 0;
+};
+
+class ConstantShaderGenerator : public SourceGenerator
+{
+public:
+				ConstantShaderGenerator		(de::Random& rnd)	: m_rnd(rnd)	{}
+				~ConstantShaderGenerator	(void)								{}
+
+	bool		finished					(const glu::ShaderType shaderType) const	{ DE_UNREF(shaderType); return false; }
+
+	std::string	next						(const glu::ShaderType shaderType);
+
+private:
+	de::Random	m_rnd;
+};
+
+std::string ConstantShaderGenerator::next (const glu::ShaderType shaderType)
+{
+	DE_ASSERT(shaderType == glu::SHADERTYPE_VERTEX || shaderType == glu::SHADERTYPE_FRAGMENT);
+
+	const float			value		= m_rnd.getFloat(0.0f, 1.0f);
+	const std::string	valueString	= de::toString(value);
+	const std::string	outputName	= (shaderType == glu::SHADERTYPE_VERTEX) ? "gl_Position" : "gl_FragColor";
+
+	std::string source =
+		"#version 100\n"
+		"void main (void) { " + outputName + " = vec4(" + valueString + "); }\n";
+
+	return source;
+}
+
+// Shader allocation utility
+
+class ShaderAllocator
+{
+public:
+					ShaderAllocator		(glu::RenderContext& context, SourceGenerator& generator);
+					~ShaderAllocator	(void);
+
+	bool			hasShader			(const glu::ShaderType shaderType);
+
+	void			setSource			(const glu::ShaderType shaderType);
+
+	glu::Shader&	createShader		(const glu::ShaderType shaderType);
+	void			deleteShader		(const glu::ShaderType shaderType);
+
+	glu::Shader&	get					(const glu::ShaderType shaderType)	{ DE_ASSERT(hasShader(shaderType)); return *m_shaders[shaderType]; }
+
+private:
+	const glu::RenderContext&				m_context;
+	SourceGenerator&						m_srcGen;
+	std::map<glu::ShaderType, glu::Shader*>	m_shaders;
+};
+
+ShaderAllocator::ShaderAllocator (glu::RenderContext& context, SourceGenerator& generator)
+	: m_context	(context)
+	, m_srcGen	(generator)
+{
+}
+
+ShaderAllocator::~ShaderAllocator (void)
+{
+	for (std::map<glu::ShaderType, glu::Shader*>::iterator shaderIter = m_shaders.begin(); shaderIter != m_shaders.end(); shaderIter++)
+		delete shaderIter->second;
+	m_shaders.clear();
+}
+
+bool ShaderAllocator::hasShader (const glu::ShaderType shaderType)
+{
+	if (m_shaders.find(shaderType) != m_shaders.end())
+		return true;
+	else
+		return false;
+}
+
+glu::Shader& ShaderAllocator::createShader (const glu::ShaderType shaderType)
+{
+	DE_ASSERT(!this->hasShader(shaderType));
+
+	glu::Shader* const	shader	= new glu::Shader(m_context, shaderType);
+
+	m_shaders[shaderType] = shader;
+	this->setSource(shaderType);
+
+	return *shader;
+}
+
+void ShaderAllocator::deleteShader (const glu::ShaderType shaderType)
+{
+	DE_ASSERT(this->hasShader(shaderType));
+
+	delete m_shaders[shaderType];
+	m_shaders.erase(shaderType);
+}
+
+void ShaderAllocator::setSource (const glu::ShaderType shaderType)
+{
+	DE_ASSERT(this->hasShader(shaderType));
+	DE_ASSERT(!m_srcGen.finished(shaderType));
+
+	const std::string	source	= m_srcGen.next(shaderType);
+	const char* const	cSource	= source.c_str();
+
+	m_shaders[shaderType]->setSources(1, &cSource, 0);
+}
+
+// Logging utilities
+
+void logShader (TestLog& log, glu::RenderContext& renderCtx, glu::Shader& shader)
+{
+	glu::ShaderInfo info;
+	queryShaderInfo(renderCtx, shader.getShader(), info);
+
+	log << TestLog::Shader(getLogShaderType(shader.getType()), info.source, info.compileOk, info.infoLog);
+}
+
+void logProgram (TestLog& log, glu::RenderContext& renderCtx, glu::Program& program, ShaderAllocator& shaders)
+{
+	log << TestLog::ShaderProgram(program.getLinkStatus(), program.getInfoLog());
+
+	for (int shaderTypeInt = 0; shaderTypeInt < glu::SHADERTYPE_LAST; shaderTypeInt++)
+	{
+		const glu::ShaderType shaderType = (glu::ShaderType)shaderTypeInt;
+
+		if (shaders.hasShader(shaderType))
+			logShader(log, renderCtx, shaders.get(shaderType));
+	}
+
+	log << TestLog::EndShaderProgram;
+}
+
+void logVertexFragmentProgram (TestLog& log, glu::RenderContext& renderCtx, glu::Program& program, glu::Shader& vertShader, glu::Shader& fragShader)
+{
+	DE_ASSERT(vertShader.getType() == glu::SHADERTYPE_VERTEX && fragShader.getType() == glu::SHADERTYPE_FRAGMENT);
+
+	log << TestLog::ShaderProgram(program.getLinkStatus(), program.getInfoLog());
+
+	logShader(log, renderCtx, vertShader);
+	logShader(log, renderCtx, fragShader);
+
+	log << TestLog::EndShaderProgram;
+}
+
+} // anonymous
+
+// Simple glCreateShader() case
+
+class CreateShaderCase : public ApiCase
+{
+public:
+	CreateShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ApiCase		(context, name, desc)
+		, m_shaderType	(shaderType)
+	{
+	}
+
+	void test (void)
+	{
+		const GLuint shaderObject = glCreateShader(glu::getGLShaderType(m_shaderType));
+
+		TCU_CHECK(shaderObject != 0);
+
+		glDeleteShader(shaderObject);
+	}
+
+private:
+	const glu::ShaderType m_shaderType;
+};
+
+// Simple glCompileShader() case
+
+class CompileShaderCase : public ApiCase
+{
+public:
+	CompileShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ApiCase		(context, name, desc)
+		, m_shaderType	(shaderType)
+	{
+	}
+
+	bool checkCompileStatus (const GLuint shaderObject)
+	{
+		GLint compileStatus = -1;
+		glGetShaderiv(shaderObject, GL_COMPILE_STATUS, &compileStatus);
+		GLU_CHECK();
+
+		return (compileStatus == GL_TRUE);
+	}
+
+	void test (void)
+	{
+		const char*		shaderSource	= getSimpleShaderSource(m_shaderType);
+		const GLuint	shaderObject	= glCreateShader(glu::getGLShaderType(m_shaderType));
+
+		TCU_CHECK(shaderObject != 0);
+
+		glShaderSource(shaderObject, 1, &shaderSource, 0);
+		glCompileShader(shaderObject);
+
+		TCU_CHECK(checkCompileStatus(shaderObject));
+
+		glDeleteShader(shaderObject);
+	}
+
+private:
+	const glu::ShaderType m_shaderType;
+};
+
+// Base class for simple program API tests
+
+class SimpleProgramCase : public ApiCase
+{
+public:
+	SimpleProgramCase (Context& context, const char* name, const char* desc)
+		: ApiCase 		(context, name, desc)
+		, m_vertShader	(0)
+		, m_fragShader	(0)
+		, m_program		(0)
+	{
+	}
+
+	virtual ~SimpleProgramCase (void)
+	{
+	}
+
+	virtual void compileShaders (void)
+	{
+		const char*		vertSource	= getSimpleShaderSource(glu::SHADERTYPE_VERTEX);
+		const char*		fragSource	= getSimpleShaderSource(glu::SHADERTYPE_FRAGMENT);
+
+		const GLuint	vertShader	= glCreateShader(GL_VERTEX_SHADER);
+		const GLuint	fragShader	= glCreateShader(GL_FRAGMENT_SHADER);
+
+		TCU_CHECK(vertShader != 0);
+		TCU_CHECK(fragShader != 0);
+
+		glShaderSource(vertShader, 1, &vertSource, 0);
+		glCompileShader(vertShader);
+
+		glShaderSource(fragShader, 1, &fragSource, 0);
+		glCompileShader(fragShader);
+
+		GLU_CHECK();
+
+		m_vertShader = vertShader;
+		m_fragShader = fragShader;
+	}
+
+	void linkProgram (void)
+	{
+		const GLuint program = glCreateProgram();
+
+		TCU_CHECK(program != 0);
+
+		glAttachShader(program, m_vertShader);
+		glAttachShader(program, m_fragShader);
+		GLU_CHECK();
+
+		glLinkProgram(program);
+
+		m_program = program;
+	}
+
+	void cleanup (void)
+	{
+		glDeleteShader(m_vertShader);
+		glDeleteShader(m_fragShader);
+		glDeleteProgram(m_program);
+	}
+
+protected:
+	GLuint	m_vertShader;
+	GLuint	m_fragShader;
+	GLuint	m_program;
+};
+
+// glDeleteShader() case
+
+class DeleteShaderCase : public SimpleProgramCase
+{
+public:
+	DeleteShaderCase (Context& context, const char* name, const char* desc)
+		: SimpleProgramCase (context, name, desc)
+	{
+	}
+
+	bool checkDeleteStatus(GLuint shader)
+	{
+		GLint deleteStatus = -1;
+		glGetShaderiv(shader, GL_DELETE_STATUS, &deleteStatus);
+		GLU_CHECK();
+
+		return (deleteStatus == GL_TRUE);
+	}
+
+	void deleteShaders (void)
+	{
+		glDeleteShader(m_vertShader);
+		glDeleteShader(m_fragShader);
+		GLU_CHECK();
+	}
+
+	void test (void)
+	{
+		compileShaders();
+		linkProgram();
+		GLU_CHECK();
+
+		deleteShaders();
+
+		TCU_CHECK(checkDeleteStatus(m_vertShader) && checkDeleteStatus(m_fragShader));
+
+		glDeleteProgram(m_program);
+
+		TCU_CHECK(!(glIsShader(m_vertShader) || glIsShader(m_fragShader)));
+	}
+};
+
+// Simple glLinkProgram() case
+
+class LinkVertexFragmentCase : public SimpleProgramCase
+{
+public:
+	LinkVertexFragmentCase (Context& context, const char* name, const char* desc)
+		: SimpleProgramCase (context, name, desc)
+	{
+	}
+
+	bool checkLinkStatus (const GLuint programObject)
+	{
+		GLint linkStatus = -1;
+		glGetProgramiv(programObject, GL_LINK_STATUS, &linkStatus);
+		GLU_CHECK();
+
+		return (linkStatus == GL_TRUE);
+	}
+
+	void test (void)
+	{
+		compileShaders();
+		linkProgram();
+
+		GLU_CHECK_MSG("Linking failed.");
+		TCU_CHECK_MSG(checkLinkStatus(m_program), "Fail, expected LINK_STATUS to be TRUE.");
+
+		cleanup();
+	}
+};
+
+class ShaderSourceReplaceCase : public ApiCase
+{
+public:
+	ShaderSourceReplaceCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ApiCase		(context, name, desc)
+		, m_shaderType	(shaderType)
+	{
+	}
+
+	std::string generateFirstSource (void)
+	{
+		return getSimpleShaderSource(m_shaderType);
+	}
+
+	std::string generateSecondSource (void)
+	{
+		std::string str;
+
+		str  = "#version 100\n";
+		str += "precision highp float;\n\n";
+
+		str += "void main()\n";
+		str += "{\n";
+		str += "	float variable = 1.0;\n";
+
+		if		(m_shaderType == glu::SHADERTYPE_VERTEX)	str += "	gl_Position = vec4(variable);\n";
+		else if	(m_shaderType == glu::SHADERTYPE_FRAGMENT)	str += "	gl_FragColor = vec4(variable);\n";
+
+		str += "}\n";
+
+		return str;
+	}
+
+	GLint getSourceLength (glu::Shader& shader)
+	{
+		GLint sourceLength = 0;
+		glGetShaderiv(shader.getShader(), GL_SHADER_SOURCE_LENGTH, &sourceLength);
+		GLU_CHECK();
+
+		return sourceLength;
+	}
+
+	std::string readSource (glu::Shader& shader)
+	{
+		const GLint			sourceLength	= getSourceLength(shader);
+		std::vector<char>	sourceBuffer	(sourceLength + 1);
+
+		glGetShaderSource(shader.getShader(), (GLsizei)sourceBuffer.size(), 0, &sourceBuffer[0]);
+
+		return std::string(&sourceBuffer[0]);
+	}
+
+	void verifyShaderSourceReplaced (glu::Shader& shader, const std::string& firstSource, const std::string& secondSource)
+	{
+		TestLog&			log		= m_testCtx.getLog();
+		const std::string	result	= readSource(shader);
+
+		if (result == firstSource)
+		{
+			log << TestLog::Message << "Fail, source was not replaced." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader source nor replaced");
+		}
+		else if (result != secondSource)
+		{
+			log << TestLog::Message << "Fail, invalid shader source." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid source");
+		}
+	}
+
+	void test (void)
+	{
+		TestLog&			log				= m_testCtx.getLog();
+
+		glu::Shader			shader			(m_context.getRenderContext(), m_shaderType);
+
+		const std::string	firstSourceStr	= generateFirstSource();
+		const std::string	secondSourceStr	= generateSecondSource();
+
+		const char*			firstSource		= firstSourceStr.c_str();
+		const char*			secondSource	= secondSourceStr.c_str();
+
+		log << TestLog::Message << "Setting shader source." << TestLog::EndMessage;
+
+		shader.setSources(1, &firstSource, 0);
+		GLU_CHECK();
+
+		log << TestLog::Message << "Replacing shader source." << TestLog::EndMessage;
+
+		shader.setSources(1, &secondSource, 0);
+		GLU_CHECK();
+
+		verifyShaderSourceReplaced(shader, firstSourceStr, secondSourceStr);
+	}
+
+private:
+	glu::ShaderType	m_shaderType;
+};
+
+// glShaderSource() split source case
+
+class ShaderSourceSplitCase : public ApiCase
+{
+public:
+	ShaderSourceSplitCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType, const int numSlices, const deUint32 flags = 0)
+		: ApiCase			(context, name, desc)
+		, m_rnd				(deStringHash(getName()) ^ 0x4fb2337d)
+		, m_shaderType		(shaderType)
+		, m_numSlices		(numSlices)
+		, m_explicitLengths	((flags & CASE_EXPLICIT_SOURCE_LENGTHS)	!= 0)
+		, m_randomNullTerm	((flags & CASE_RANDOM_NULL_TERMINATED)	!= 0)
+	{
+		DE_ASSERT(m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT);
+	}
+
+	virtual ~ShaderSourceSplitCase (void)
+	{
+	}
+
+	std::string generateFullSource (void)
+	{
+		std::string str;
+
+		str  = "#version 100\n";
+		str += "precision highp float;\n\n";
+
+		str += "void main()\n";
+		str += "{\n";
+		str += "	float variable = 1.0;\n";
+
+		if		(m_shaderType == glu::SHADERTYPE_VERTEX)	str += "	gl_Position = vec4(variable);\n";
+		else if	(m_shaderType == glu::SHADERTYPE_FRAGMENT)	str += "	gl_FragColor = vec4(variable);\n";
+
+		str += "}\n";
+
+		return str;
+	}
+
+	void insertRandomNullTermStrings (ShaderSources& sources)
+	{
+		const int			numInserts	= de::max(m_numSlices >> 2, 1);
+		std::vector<int>	indices		(sources.strings.size(), 0);
+
+		DE_ASSERT(sources.lengths.size() > 0);
+		DE_ASSERT(sources.lengths.size() == sources.strings.size());
+
+		for (int i = 0; i < (int)sources.strings.size(); i++)
+			indices[i] = i;
+
+		m_rnd.shuffle(indices.begin(), indices.end());
+
+		for (int i = 0; i < numInserts; i++)
+		{
+			const int			ndx				= indices[i];
+			const int			unpaddedLength	= sources.lengths[ndx];
+			const std::string	unpaddedString	= sources.strings[ndx].substr(0, unpaddedLength);
+
+			sources.strings[ndx] = unpaddedString;
+			sources.lengths[ndx] = m_rnd.getInt(-10, -1);
+		}
+	}
+
+	void generateSources (ShaderSources& sources)
+	{
+		const size_t	paddingLength	= (m_explicitLengths ? 10 : 0);
+		std::string		str				= generateFullSource();
+
+		sliceSourceString(str, sources, m_numSlices, paddingLength);
+
+		if (m_randomNullTerm)
+			insertRandomNullTermStrings(sources);
+	}
+
+	void buildProgram (glu::Shader& shader)
+	{
+		TestLog&				log					= m_testCtx.getLog();
+		glu::RenderContext&		renderCtx			= m_context.getRenderContext();
+
+		const glu::ShaderType	supportShaderType	= (m_shaderType == glu::SHADERTYPE_FRAGMENT ? glu::SHADERTYPE_VERTEX : glu::SHADERTYPE_FRAGMENT);
+		const char*				supportShaderSource	= getSimpleShaderSource(supportShaderType);
+		glu::Shader				supportShader		(renderCtx, supportShaderType);
+
+		glu::Program			program				(renderCtx);
+
+		supportShader.setSources(1, &supportShaderSource, 0);
+		supportShader.compile();
+
+		program.attachShader(shader.getShader());
+		program.attachShader(supportShader.getShader());
+
+		program.link();
+
+		if (m_shaderType == glu::SHADERTYPE_VERTEX)
+			logVertexFragmentProgram(log, renderCtx, program, shader, supportShader);
+		else
+			logVertexFragmentProgram(log, renderCtx, program, supportShader, shader);
+	}
+
+	void test (void)
+	{
+		TestLog&			log			= m_testCtx.getLog();
+		glu::RenderContext&	renderCtx	= m_context.getRenderContext();
+
+		ShaderSources		sources;
+		glu::Shader			shader		(renderCtx, m_shaderType);
+
+		generateSources(sources);
+		setShaderSources(shader, sources);
+		shader.compile();
+
+		buildProgram(shader);
+
+		if (!shader.getCompileStatus())
+		{
+			log << TestLog::Message << "Compilation failed." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		}
+	}
+
+private:
+	de::Random		m_rnd;
+
+	glu::ShaderType	m_shaderType;
+	const int		m_numSlices;
+
+	const bool		m_explicitLengths;
+	const bool		m_randomNullTerm;
+};
+
+// Base class for program state persistence cases
+
+class ProgramStateCase : public ApiCase
+{
+public:
+					ProgramStateCase	(Context& context, const char* name, const char* desc, glu::ShaderType shaderType);
+	virtual			~ProgramStateCase	(void)	{}
+
+	void			buildProgram		(glu::Program& program, ShaderAllocator& shaders);
+	void			verify				(glu::Program& program, const glu::ProgramInfo& reference);
+
+	void			test				(void);
+
+	virtual void	executeForProgram	(glu::Program& program, ShaderAllocator& shaders)	= 0;
+
+protected:
+	de::Random					m_rnd;
+	const glu::ShaderType		m_shaderType;
+};
+
+ProgramStateCase::ProgramStateCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+	: ApiCase		(context, name, desc)
+	, m_rnd			(deStringHash(name) ^ 0x713de0ca)
+	, m_shaderType	(shaderType)
+{
+	DE_ASSERT(m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT);
+}
+
+void ProgramStateCase::buildProgram (glu::Program& program, ShaderAllocator& shaders)
+{
+	TestLog&		log			= m_testCtx.getLog();
+
+	glu::Shader&	vertShader	= shaders.createShader(glu::SHADERTYPE_VERTEX);
+	glu::Shader&	fragShader	= shaders.createShader(glu::SHADERTYPE_FRAGMENT);
+
+	vertShader.compile();
+	fragShader.compile();
+
+	program.attachShader(vertShader.getShader());
+	program.attachShader(fragShader.getShader());
+	program.link();
+
+	logProgram(log, m_context.getRenderContext(), program, shaders);
+}
+
+void ProgramStateCase::verify (glu::Program& program, const glu::ProgramInfo& reference)
+{
+	TestLog&				log			= m_testCtx.getLog();
+	const glu::ProgramInfo&	programInfo	= program.getInfo();
+
+	if (!programInfo.linkOk)
+	{
+		log << TestLog::Message << "Fail, link status may only change as a result of linking or loading a program binary." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Link status changed");
+	}
+
+	if (programInfo.linkTimeUs != reference.linkTimeUs)
+	{
+		log << TestLog::Message << "Fail, reported link time changed." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Link time changed");
+	}
+
+	if (programInfo.infoLog != reference.infoLog)
+	{
+		log << TestLog::Message << "Fail, program infolog changed." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Infolog changed");
+	}
+}
+
+void ProgramStateCase::test (void)
+{
+	TestLog&				log			= m_testCtx.getLog();
+	glu::RenderContext&		renderCtx	= m_context.getRenderContext();
+
+	ConstantShaderGenerator	sourceGen	(m_rnd);
+
+	ShaderAllocator			shaders		(renderCtx, sourceGen);
+	glu::Program			program		(renderCtx);
+
+	buildProgram(program, shaders);
+
+	if (program.getLinkStatus())
+	{
+		glu::ProgramInfo programInfo = program.getInfo();
+
+		executeForProgram(program, shaders);
+
+		verify(program, programInfo);
+
+		logProgram(log, renderCtx, program, shaders);
+	}
+	else
+	{
+		log << TestLog::Message << "Fail, couldn't link program." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Linking failed");
+	}
+}
+
+// Program state case utilities
+
+namespace
+{
+
+template<class T>
+void addProgramStateCase (TestCaseGroup* group, Context& context, const std::string& name, const std::string& desc)
+{
+	for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
+	{
+		const glu::ShaderType	shaderType		= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
+		const std::string		shaderTypeName	= getShaderTypeName(shaderType);
+
+		const std::string		caseName		= name + "_" + shaderTypeName;
+		const std::string		caseDesc		= "Build program, " + desc + ", for " + shaderTypeName + " shader.";
+
+		group->addChild(new T(context, caseName.c_str(), caseDesc.c_str(), shaderType));
+	}
+}
+
+} // anonymous
+
+// Specialized program state cases
+
+class ProgramStateDetachShaderCase : public ProgramStateCase
+{
+public:
+	ProgramStateDetachShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateDetachShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Detaching " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+	}
+};
+
+class ProgramStateReattachShaderCase : public ProgramStateCase
+{
+public:
+	ProgramStateReattachShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateReattachShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Reattaching " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+		program.attachShader(caseShader.getShader());
+	}
+};
+
+class ProgramStateDeleteShaderCase : public ProgramStateCase
+{
+public:
+	ProgramStateDeleteShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateDeleteShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Deleting " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+		shaders.deleteShader(m_shaderType);
+	}
+};
+
+class ProgramStateReplaceShaderCase : public ProgramStateCase
+{
+public:
+	ProgramStateReplaceShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateReplaceShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Deleting and replacing " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+		shaders.deleteShader(m_shaderType);
+		program.attachShader(shaders.createShader(m_shaderType).getShader());
+	}
+};
+
+class ProgramStateRecompileShaderCase : public ProgramStateCase
+{
+public:
+	ProgramStateRecompileShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateRecompileShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Recompiling " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		caseShader.compile();
+		DE_UNREF(program);
+	}
+};
+
+class ProgramStateReplaceSourceCase : public ProgramStateCase
+{
+public:
+	ProgramStateReplaceSourceCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateReplaceSourceCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Replacing " + std::string(getShaderTypeName(m_shaderType)) + " shader source and recompiling" << TestLog::EndMessage;
+		shaders.setSource(m_shaderType);
+		caseShader.compile();
+		DE_UNREF(program);
+	}
+};
+
+// Test group
+
+ShaderApiTests::ShaderApiTests (Context& context)
+	: TestCaseGroup(context, "shader_api", "Shader API Cases")
+{
+}
+
+ShaderApiTests::~ShaderApiTests (void)
+{
+}
+
+void ShaderApiTests::init (void)
+{
+	// create and delete shaders
+	{
+		TestCaseGroup* createDeleteGroup = new TestCaseGroup(m_context, "create_delete", "glCreateShader() tests");
+		addChild(createDeleteGroup);
+
+		createDeleteGroup->addChild(new CreateShaderCase(m_context,	"create_vertex_shader",		"Create vertex shader object",		glu::SHADERTYPE_VERTEX));
+		createDeleteGroup->addChild(new CreateShaderCase(m_context,	"create_fragment_shader",	"Create fragment shader object",	glu::SHADERTYPE_FRAGMENT));
+
+		createDeleteGroup->addChild(new DeleteShaderCase(m_context,	"delete_vertex_fragment",	"Delete vertex shader and fragment shader"));
+	}
+
+	// compile and link
+	{
+		TestCaseGroup* compileLinkGroup = new TestCaseGroup(m_context, "compile_link", "Compile and link tests");
+		addChild(compileLinkGroup);
+
+		compileLinkGroup->addChild(new CompileShaderCase(m_context,	"compile_vertex_shader",	"Compile vertex shader",	glu::SHADERTYPE_VERTEX));
+		compileLinkGroup->addChild(new CompileShaderCase(m_context,	"compile_fragment_shader",	"Compile fragment shader",	glu::SHADERTYPE_FRAGMENT));
+
+		compileLinkGroup->addChild(new LinkVertexFragmentCase(m_context,	"link_vertex_fragment",	"Link vertex and fragment shaders"));
+	}
+
+	// shader source
+	{
+		TestCaseGroup* shaderSourceGroup = new TestCaseGroup(m_context, "shader_source", "glShaderSource() tests");
+		addChild(shaderSourceGroup);
+
+		for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
+		{
+			const glu::ShaderType	shaderType	= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
+
+			const std::string		caseName	= std::string("replace_source") + ((shaderType == glu::SHADERTYPE_FRAGMENT) ? "_fragment" : "_vertex");
+			const std::string		caseDesc	= std::string("Replace source code of ") + ((shaderType == glu::SHADERTYPE_FRAGMENT) ? "fragment" : "vertex") + " shader.";
+
+			shaderSourceGroup->addChild(new ShaderSourceReplaceCase(m_context, caseName.c_str(), caseDesc.c_str(), shaderType));
+		}
+
+		for (int stringLengthsInt	= 0; stringLengthsInt < 3; stringLengthsInt++)
+		for (int caseNdx = 1; caseNdx <= 3; caseNdx++)
+		for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
+		{
+			const int				numSlices		= 1 << caseNdx;
+			const glu::ShaderType	shaderType		= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
+
+			const bool				explicitLengths	= (stringLengthsInt != 0);
+			const bool				randomNullTerm	= (stringLengthsInt == 2);
+
+			const deUint32			flags			= (explicitLengths	? CASE_EXPLICIT_SOURCE_LENGTHS	: 0)
+													| (randomNullTerm	? CASE_RANDOM_NULL_TERMINATED	: 0);
+
+			const std::string		caseName		= "split_source_"
+													+ de::toString(numSlices)
+													+ (randomNullTerm ? "_random_negative_length" : (explicitLengths ? "_specify_lengths" : "_null_terminated"))
+													+ ((shaderType == glu::SHADERTYPE_FRAGMENT) ? "_fragment" : "_vertex");
+
+			const std::string		caseDesc		= std::string((shaderType == glu::SHADERTYPE_FRAGMENT) ? "Fragment" : "Vertex")
+													+ " shader source split into "
+													+ de::toString(numSlices)
+													+ " pieces"
+													+ (explicitLengths ? ", using explicitly specified string lengths" : "")
+													+ (randomNullTerm ? " with random negative length values" : "");
+
+			shaderSourceGroup->addChild(new ShaderSourceSplitCase(m_context, caseName.c_str(), caseDesc.c_str(), shaderType, numSlices, flags));
+		}
+	}
+
+	// link status and infolog
+	{
+		TestCaseGroup* linkStatusGroup = new TestCaseGroup(m_context, "program_state", "Program state persistence tests");
+		addChild(linkStatusGroup);
+
+		addProgramStateCase<ProgramStateDetachShaderCase>		(linkStatusGroup,	m_context,	"detach_shader",	"detach shader");
+		addProgramStateCase<ProgramStateReattachShaderCase>		(linkStatusGroup,	m_context,	"reattach_shader",	"reattach shader");
+		addProgramStateCase<ProgramStateDeleteShaderCase>		(linkStatusGroup,	m_context,	"delete_shader",	"delete shader");
+		addProgramStateCase<ProgramStateReplaceShaderCase>		(linkStatusGroup,	m_context,	"replace_shader",	"replace shader object");
+		addProgramStateCase<ProgramStateRecompileShaderCase>	(linkStatusGroup,	m_context,	"recompile_shader",	"recompile shader");
+		addProgramStateCase<ProgramStateReplaceSourceCase>		(linkStatusGroup,	m_context,	"replace_source",	"replace shader source");
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderApiTests.hpp b/modules/gles2/functional/es2fShaderApiTests.hpp
new file mode 100644
index 0000000..3f53e62
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FSHADERAPITESTS_HPP
+#define _ES2FSHADERAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderApiTests : public TestCaseGroup
+{
+public:
+						ShaderApiTests		(Context& context);
+						~ShaderApiTests		(void);
+
+	void				init				(void);
+
+private:
+						ShaderApiTests		(const ShaderApiTests& other);
+	ShaderApiTests&		operator=			(const ShaderApiTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERAPITESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderBuiltinVarTests.cpp b/modules/gles2/functional/es2fShaderBuiltinVarTests.cpp
new file mode 100644
index 0000000..fd33737
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderBuiltinVarTests.cpp
@@ -0,0 +1,708 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader built-in variable tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderBuiltinVarTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+#include "deMath.h"
+#include "deStringUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuImageCompare.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluDrawUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+const float builtinConstScale = 4.0f;
+
+void evalBuiltinConstant (gls::ShaderEvalContext& c)
+{
+	bool isOk = 0 == (int)(deFloatFloor(c.coords.x() * builtinConstScale) + 0.05f);
+	c.color = isOk ? tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f) : tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+}
+
+class ShaderBuiltinConstantCase : public gls::ShaderRenderCase
+{
+public:
+						ShaderBuiltinConstantCase				(Context& context, const char* name, const char* desc, const char* varName, deUint32 paramName, bool isVertexCase);
+						~ShaderBuiltinConstantCase				(void);
+
+	void				init									(void);
+
+private:
+	std::string			m_varName;
+	deUint32			m_paramName;
+};
+
+ShaderBuiltinConstantCase::ShaderBuiltinConstantCase (Context& context, const char* name, const char* desc, const char* varName, deUint32 paramName, bool isVertexCase)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, desc, isVertexCase, evalBuiltinConstant)
+	, m_varName			(varName)
+	, m_paramName		(paramName)
+{
+}
+
+ShaderBuiltinConstantCase::~ShaderBuiltinConstantCase (void)
+{
+}
+
+static int getConstRefValue (const char* varName)
+{
+	if (deStringEqual(varName, "gl_MaxDrawBuffers"))
+		return 1;
+	else
+	{
+		DE_ASSERT(DE_FALSE);
+		return 0;
+	}
+}
+
+void ShaderBuiltinConstantCase::init (void)
+{
+	int refValue = m_paramName != GL_NONE ? m_ctxInfo.getInt(m_paramName) : getConstRefValue(m_varName.c_str());
+	m_testCtx.getLog() << tcu::TestLog::Message << m_varName << " = " << refValue << tcu::TestLog::EndMessage;
+
+	static const char* defaultVertSrc =
+		"attribute highp vec4 a_position;\n"
+		"attribute highp vec4 a_coords;\n"
+		"varying mediump vec4 v_coords;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"	v_coords = a_coords;\n"
+		"	gl_Position = a_position;\n"
+		"}\n";
+	static const char* defaultFragSrc =
+		"varying mediump vec4 v_color;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_FragColor = v_color;\n"
+		"}\n";
+
+	// Construct shader.
+	std::ostringstream src;
+	if (m_isVertexCase)
+	{
+		src << "attribute highp vec4 a_position;\n"
+			<< "attribute highp vec4 a_coords;\n"
+			<< "varying mediump vec4 v_color;\n";
+	}
+	else
+		src << "varying mediump vec4 v_coords;\n";
+
+	src << "void main (void)\n{\n";
+
+	src << "\tbool isOk = " << m_varName << " == (" << refValue << " + int(floor(" << (m_isVertexCase ? "a_coords" : "v_coords") << ".x * " << de::floatToString(builtinConstScale, 1) << ") + 0.05));\n";
+	src << "\t" << (m_isVertexCase ? "v_color" : "gl_FragColor") << " = isOk ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);\n";
+
+	if (m_isVertexCase)
+		src << "\tgl_Position = a_position;\n";
+
+	src << "}\n";
+
+	m_vertShaderSource		= m_isVertexCase ? src.str()		: defaultVertSrc;
+	m_fragShaderSource		= m_isVertexCase ? defaultFragSrc	: src.str();
+
+	gls::ShaderRenderCase::init();
+}
+
+namespace
+{
+
+struct DepthRangeParams
+{
+	DepthRangeParams (void)
+		: zNear	(0.0f)
+		, zFar	(1.0f)
+	{
+	}
+
+	DepthRangeParams (float zNear_, float zFar_)
+		: zNear	(zNear_)
+		, zFar	(zFar_)
+	{
+	}
+
+	float	zNear;
+	float	zFar;
+};
+
+class DepthRangeEvaluator : public gls::ShaderEvaluator
+{
+public:
+	DepthRangeEvaluator (const DepthRangeParams& params)
+		: m_params(params)
+	{
+	}
+
+	void evaluate (gls::ShaderEvalContext& c)
+	{
+		float zNear	= deFloatClamp(m_params.zNear, 0.0f, 1.0f);
+		float zFar	= deFloatClamp(m_params.zFar, 0.0f, 1.0f);
+		float diff	= zFar - zNear;
+		c.color.xyz() = tcu::Vec3(zNear, zFar, diff*0.5f + 0.5f);
+	}
+
+private:
+	const DepthRangeParams& m_params;
+};
+
+} // anonymous
+
+class ShaderDepthRangeTest : public gls::ShaderRenderCase
+{
+public:
+	ShaderDepthRangeTest (Context& context, const char* name, const char* desc, bool isVertexCase)
+		: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, desc, isVertexCase, m_evaluator)
+		, m_evaluator		(m_depthRange)
+		, m_iterNdx			(0)
+	{
+	}
+
+	void init (void)
+	{
+		static const char* defaultVertSrc =
+			"attribute highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n";
+		static const char* defaultFragSrc =
+			"varying mediump vec4 v_color;\n\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = v_color;\n"
+			"}\n";
+
+		// Construct shader.
+		std::ostringstream src;
+		if (m_isVertexCase)
+			src << "attribute highp vec4 a_position;\n"
+				<< "varying mediump vec4 v_color;\n";
+
+		src << "void main (void)\n{\n";
+		src << "\t" << (m_isVertexCase ? "v_color" : "gl_FragColor") << " = vec4(gl_DepthRange.near, gl_DepthRange.far, gl_DepthRange.diff*0.5 + 0.5, 1.0);\n";
+
+		if (m_isVertexCase)
+			src << "\tgl_Position = a_position;\n";
+
+		src << "}\n";
+
+		m_vertShaderSource		= m_isVertexCase ? src.str()		: defaultVertSrc;
+		m_fragShaderSource		= m_isVertexCase ? defaultFragSrc	: src.str();
+
+		gls::ShaderRenderCase::init();
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions& gl = m_renderCtx.getFunctions();
+
+		const DepthRangeParams cases[] =
+		{
+			DepthRangeParams(0.0f,  1.0f),
+			DepthRangeParams(1.5f, -1.0f),
+			DepthRangeParams(0.7f,  0.3f)
+		};
+
+		m_depthRange = cases[m_iterNdx];
+		m_testCtx.getLog() << tcu::TestLog::Message << "glDepthRangef(" << m_depthRange.zNear << ", " << m_depthRange.zFar << ")" << tcu::TestLog::EndMessage;
+		gl.depthRangef(m_depthRange.zNear, m_depthRange.zFar);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthRangef()");
+
+		gls::ShaderRenderCase::iterate();
+		m_iterNdx += 1;
+
+		if (m_iterNdx == DE_LENGTH_OF_ARRAY(cases) || m_testCtx.getTestResult() != QP_TEST_RESULT_PASS)
+			return STOP;
+		else
+			return CONTINUE;
+	}
+
+private:
+	DepthRangeParams		m_depthRange;
+	DepthRangeEvaluator		m_evaluator;
+	int						m_iterNdx;
+};
+
+class FragCoordXYZCase : public TestCase
+{
+public:
+	FragCoordXYZCase (Context& context)
+		: TestCase(context, "fragcoord_xyz", "gl_FragCoord.xyz Test")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog&				log			= m_testCtx.getLog();
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const int				width		= m_context.getRenderTarget().getWidth();
+		const int				height		= m_context.getRenderTarget().getHeight();
+		const tcu::RGBA			threshold	= tcu::RGBA(1,1,1,1) + m_context.getRenderTarget().getPixelFormat().getColorThreshold();
+		const tcu::Vec3			scale		(1.f / float(width), 1.f / float(height), 1.0f);
+
+		tcu::Surface			testImg		(width, height);
+		tcu::Surface			refImg		(width, height);
+
+		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
+			"attribute highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n",
+
+			"uniform mediump vec3 u_scale;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(gl_FragCoord.xyz*u_scale, 1.0);\n"
+			"}\n"));
+
+		log << program;
+
+		if (!program.isOk())
+			throw tcu::TestError("Compile failed");
+
+		// Draw with GL.
+		{
+			const float positions[] =
+			{
+				-1.0f,  1.0f, -1.0f, 1.0f,
+				-1.0f, -1.0f,  0.0f, 1.0f,
+				 1.0f,  1.0f,  0.0f, 1.0f,
+				 1.0f, -1.0f,  1.0f, 1.0f
+			};
+			const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+			const int				scaleLoc	= gl.getUniformLocation(program.getProgram(), "u_scale");
+			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_position", 4, 4, 0, &positions[0]);
+
+			gl.useProgram(program.getProgram());
+			gl.uniform3fv(scaleLoc, 1, scale.getPtr());
+
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+
+			glu::readPixels(m_context.getRenderContext(), 0, 0, testImg.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+		}
+
+		// Draw reference
+		for (int y = 0; y < refImg.getHeight(); y++)
+		{
+			for (int x = 0; x < refImg.getWidth(); x++)
+			{
+				const float			xf			= (float(x)+.5f) / float(refImg.getWidth());
+				const float			yf			= (float(refImg.getHeight()-y-1)+.5f) / float(refImg.getHeight());
+				const float			z			= (xf + yf) / 2.0f;
+				const tcu::Vec3		fragCoord	(float(x)+.5f, float(y)+.5f, z);
+				const tcu::Vec3		scaledFC	= fragCoord*scale;
+				const tcu::Vec4		color		(scaledFC.x(), scaledFC.y(), scaledFC.z(), 1.0f);
+
+				refImg.setPixel(x, y, tcu::RGBA(color));
+			}
+		}
+
+		// Compare
+		{
+			bool isOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+static inline float projectedTriInterpolate (const tcu::Vec3& s, const tcu::Vec3& w, float nx, float ny)
+{
+	return (s[0]*(1.0f-nx-ny)/w[0] + s[1]*ny/w[1] + s[2]*nx/w[2]) / ((1.0f-nx-ny)/w[0] + ny/w[1] + nx/w[2]);
+}
+
+class FragCoordWCase : public TestCase
+{
+public:
+	FragCoordWCase (Context& context)
+		: TestCase(context, "fragcoord_w", "gl_FragCoord.w Test")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog&				log			= m_testCtx.getLog();
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const int				width		= m_context.getRenderTarget().getWidth();
+		const int				height		= m_context.getRenderTarget().getHeight();
+		const tcu::RGBA			threshold	= tcu::RGBA(1,1,1,1) + m_context.getRenderTarget().getPixelFormat().getColorThreshold();
+
+		tcu::Surface			testImg		(width, height);
+		tcu::Surface			refImg		(width, height);
+
+		const float				w[4]		= { 1.7f, 2.0f, 1.2f, 1.0f };
+
+		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
+			"attribute highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n",
+
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(0.0, 1.0/gl_FragCoord.w - 1.0, 0.0, 1.0);\n"
+			"}\n"));
+
+		log << program;
+
+		if (!program.isOk())
+			throw tcu::TestError("Compile failed");
+
+		// Draw with GL.
+		{
+			const float positions[] =
+			{
+				-w[0],  w[0], 0.0f, w[0],
+				-w[1], -w[1], 0.0f, w[1],
+				 w[2],  w[2], 0.0f, w[2],
+				 w[3], -w[3], 0.0f, w[3]
+			};
+			const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_position", 4, 4, 0, &positions[0]);
+
+			gl.useProgram(program.getProgram());
+
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+
+			glu::readPixels(m_context.getRenderContext(), 0, 0, testImg.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+		}
+
+		// Draw reference
+		for (int y = 0; y < refImg.getHeight(); y++)
+		{
+			for (int x = 0; x < refImg.getWidth(); x++)
+			{
+				const float			xf			= (float(x)+.5f) / float(refImg.getWidth());
+				const float			yf			= (float(refImg.getHeight()-y-1)+.5f) / float(refImg.getHeight());
+				const float			oow			= ((xf + yf) < 1.0f)
+												? projectedTriInterpolate(tcu::Vec3(w[0], w[1], w[2]), tcu::Vec3(w[0], w[1], w[2]), xf, yf)
+												: projectedTriInterpolate(tcu::Vec3(w[3], w[2], w[1]), tcu::Vec3(w[3], w[2], w[1]), 1.0f-xf, 1.0f-yf);
+				const tcu::Vec4		color		(0.0f, oow - 1.0f, 0.0f, 1.0f);
+
+				refImg.setPixel(x, y, tcu::RGBA(color));
+			}
+		}
+
+		// Compare
+		{
+			bool isOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class PointCoordCase : public TestCase
+{
+public:
+	PointCoordCase (Context& context)
+		: TestCase(context, "pointcoord", "gl_PointCoord Test")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog&				log			= m_testCtx.getLog();
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const int				width		= de::min(256, m_context.getRenderTarget().getWidth());
+		const int				height		= de::min(256, m_context.getRenderTarget().getHeight());
+		const float				threshold	= 0.02f;
+
+		const int				numPoints	= 8;
+
+		vector<tcu::Vec3>		coords		(numPoints);
+		float					pointSizeRange[2]	= { 0.0f, 0.0f };
+
+		de::Random				rnd			(0x145fa);
+		tcu::Surface			testImg		(width, height);
+		tcu::Surface			refImg		(width, height);
+
+		gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, &pointSizeRange[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE)");
+
+		if (pointSizeRange[0] <= 0.0f || pointSizeRange[1] <= 0.0f || pointSizeRange[1] < pointSizeRange[0])
+			throw tcu::TestError("Invalid GL_ALIASED_POINT_SIZE_RANGE");
+
+		// Compute coordinates.
+		{
+
+			for (vector<tcu::Vec3>::iterator coord = coords.begin(); coord != coords.end(); ++coord)
+			{
+				coord->x() = rnd.getFloat(-0.9f, 0.9f);
+				coord->y() = rnd.getFloat(-0.9f, 0.9f);
+				coord->z() = rnd.getFloat(pointSizeRange[0], pointSizeRange[1]);
+			}
+		}
+
+		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
+			"attribute highp vec3 a_positionSize;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(a_positionSize.xy, 0.0, 1.0);\n"
+			"	gl_PointSize = a_positionSize.z;\n"
+			"}\n",
+
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(gl_PointCoord, 0.0, 1.0);\n"
+			"}\n"));
+
+		log << program;
+
+		if (!program.isOk())
+			throw tcu::TestError("Compile failed");
+
+		// Draw with GL.
+		{
+			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_positionSize", 3, (int)coords.size(), 0, (const float*)&coords[0]);
+			const int				viewportX	= rnd.getInt(0, m_context.getRenderTarget().getWidth()-width);
+			const int				viewportY	= rnd.getInt(0, m_context.getRenderTarget().getHeight()-height);
+
+			gl.viewport(viewportX, viewportY, width, height);
+			gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+			gl.clear(GL_COLOR_BUFFER_BIT);
+
+			gl.useProgram(program.getProgram());
+
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Points((int)coords.size()));
+
+			glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+		}
+
+		// Draw reference
+		tcu::clear(refImg.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+		for (vector<tcu::Vec3>::const_iterator pointIter = coords.begin(); pointIter != coords.end(); ++pointIter)
+		{
+			const int	x0		= deRoundFloatToInt32(float(width) *(pointIter->x()*0.5f + 0.5f) - pointIter->z()*0.5f);
+			const int	y0		= deRoundFloatToInt32(float(height)*(pointIter->y()*0.5f + 0.5f) - pointIter->z()*0.5f);
+			const int	x1		= deRoundFloatToInt32(float(width) *(pointIter->x()*0.5f + 0.5f) + pointIter->z()*0.5f);
+			const int	y1		= deRoundFloatToInt32(float(height)*(pointIter->y()*0.5f + 0.5f) + pointIter->z()*0.5f);
+			const int	w		= x1-x0;
+			const int	h		= y1-y0;
+
+			for (int yo = 0; yo < h; yo++)
+			{
+				for (int xo = 0; xo < w; xo++)
+				{
+					const float			xf		= float(xo+0.5f) / float(w);
+					const float			yf		= float((h-yo-1)+0.5f) / float(h);
+					const tcu::Vec4		color	(xf, yf, 0.0f, 1.0f);
+					const int			dx		= x0+xo;
+					const int			dy		= y0+yo;
+
+					if (de::inBounds(dx, 0, refImg.getWidth()) && de::inBounds(dy, 0, refImg.getHeight()))
+						refImg.setPixel(dx, dy, tcu::RGBA(color));
+				}
+			}
+		}
+
+		// Compare
+		{
+			bool isOk = tcu::fuzzyCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class FrontFacingCase : public TestCase
+{
+public:
+	FrontFacingCase (Context& context)
+		: TestCase(context, "frontfacing", "gl_FrontFacing Test")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		// Test case renders two adjecent quads, where left is has front-facing
+		// triagles and right back-facing. Color is selected based on gl_FrontFacing
+		// value.
+
+		TestLog&				log			= m_testCtx.getLog();
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		de::Random				rnd			(0x89f2c);
+		const int				width		= de::min(64, m_context.getRenderTarget().getWidth());
+		const int				height		= de::min(64, m_context.getRenderTarget().getHeight());
+		const int				viewportX	= rnd.getInt(0, m_context.getRenderTarget().getWidth()-width);
+		const int				viewportY	= rnd.getInt(0, m_context.getRenderTarget().getHeight()-height);
+		const tcu::RGBA			threshold	= tcu::RGBA(1,1,1,1) + m_context.getRenderTarget().getPixelFormat().getColorThreshold();
+
+		tcu::Surface			testImg		(width, height);
+		tcu::Surface			refImg		(width, height);
+
+		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
+			"attribute highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n",
+
+			"void main (void)\n"
+			"{\n"
+			"	if (gl_FrontFacing)\n"
+			"		gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+			"	else\n"
+			"		gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);\n"
+			"}\n"));
+
+		log << program;
+
+		if (!program.isOk())
+			throw tcu::TestError("Compile failed");
+
+		// Draw with GL.
+		{
+			const float positions[] =
+			{
+				-1.0f,  1.0f, 0.0f, 1.0f,
+				-1.0f, -1.0f, 0.0f, 1.0f,
+				 1.0f,  1.0f, 0.0f, 1.0f,
+				 1.0f, -1.0f, 0.0f, 1.0f
+			};
+			const deUint16 indicesCCW[]	= { 0, 1, 2, 2, 1, 3 };
+			const deUint16 indicesCW[]	= { 2, 1, 0, 3, 1, 2 };
+
+			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_position", 4, 4, 0, &positions[0]);
+
+			gl.useProgram(program.getProgram());
+
+			gl.viewport(viewportX, viewportY, width/2, height);
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indicesCCW), &indicesCCW[0]));
+
+			gl.viewport(viewportX + width/2, viewportY, width-width/2, height);
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indicesCW), &indicesCW[0]));
+
+			glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+		}
+
+		// Draw reference
+		for (int y = 0; y < refImg.getHeight(); y++)
+		{
+			for (int x = 0; x < refImg.getWidth()/2; x++)
+				refImg.setPixel(x, y, tcu::RGBA::green);
+
+			for (int x = refImg.getWidth()/2; x < refImg.getWidth(); x++)
+				refImg.setPixel(x, y, tcu::RGBA::blue);
+		}
+
+		// Compare
+		{
+			bool isOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+ShaderBuiltinVarTests::ShaderBuiltinVarTests (Context& context)
+	: TestCaseGroup(context, "builtin_variable", "Built-in Variable Tests")
+{
+}
+
+ShaderBuiltinVarTests::~ShaderBuiltinVarTests (void)
+{
+}
+
+void ShaderBuiltinVarTests::init (void)
+{
+	// Builtin constants.
+
+	static const struct
+	{
+		const char*		caseName;
+		const char*		varName;
+		deUint32		paramName;
+	} builtinConstants[] =
+	{
+		{ "max_vertex_attribs",					"gl_MaxVertexAttribs",				GL_MAX_VERTEX_ATTRIBS },
+		{ "max_vertex_uniform_vectors",			"gl_MaxVertexUniformVectors",		GL_MAX_VERTEX_UNIFORM_VECTORS },
+		{ "max_fragment_uniform_vectors",		"gl_MaxFragmentUniformVectors",		GL_MAX_FRAGMENT_UNIFORM_VECTORS },
+		{ "max_varying_vectors",				"gl_MaxVaryingVectors",				GL_MAX_VARYING_VECTORS },
+		{ "max_texture_image_units",			"gl_MaxTextureImageUnits",			GL_MAX_TEXTURE_IMAGE_UNITS },
+		{ "max_vertex_texture_image_units",		"gl_MaxVertexTextureImageUnits",	GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS },
+		{ "max_combined_texture_image_units",	"gl_MaxCombinedTextureImageUnits",	GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS },
+		{ "max_draw_buffers",					"gl_MaxDrawBuffers",				GL_NONE }
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(builtinConstants); ndx++)
+	{
+		const char*		caseName	= builtinConstants[ndx].caseName;
+		const char*		varName		= builtinConstants[ndx].varName;
+		deUint32		paramName	= builtinConstants[ndx].paramName;
+
+		addChild(new ShaderBuiltinConstantCase(m_context, (string(caseName) + "_vertex").c_str(),	varName, varName, paramName, true));
+		addChild(new ShaderBuiltinConstantCase(m_context, (string(caseName) + "_fragment").c_str(),	varName, varName, paramName, false));
+	}
+
+	addChild(new ShaderDepthRangeTest(m_context, "depth_range_vertex",		"gl_DepthRange", true));
+	addChild(new ShaderDepthRangeTest(m_context, "depth_range_fragment",	"gl_DepthRange", false));
+
+	// Fragment shader builtin variables.
+
+	addChild(new FragCoordXYZCase	(m_context));
+	addChild(new FragCoordWCase		(m_context));
+	addChild(new PointCoordCase		(m_context));
+	addChild(new FrontFacingCase	(m_context));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderBuiltinVarTests.hpp b/modules/gles2/functional/es2fShaderBuiltinVarTests.hpp
new file mode 100644
index 0000000..ac72b66
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderBuiltinVarTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERBUILTINVARTESTS_HPP
+#define _ES2FSHADERBUILTINVARTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader built-in variable tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderBuiltinVarTests : public TestCaseGroup
+{
+public:
+								ShaderBuiltinVarTests		(Context& context);
+	virtual						~ShaderBuiltinVarTests		(void);
+
+	virtual void				init						(void);
+
+private:
+								ShaderBuiltinVarTests		(const ShaderBuiltinVarTests&);		// not allowed!
+	ShaderBuiltinVarTests&		operator=					(const ShaderBuiltinVarTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERBUILTINVARTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderConstExprTests.cpp b/modules/gles2/functional/es2fShaderConstExprTests.cpp
new file mode 100644
index 0000000..442d943
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderConstExprTests.cpp
@@ -0,0 +1,232 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES2 shader constant expression tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderConstExprTests.hpp"
+
+#include "glsShaderLibrary.hpp"
+#include "glsShaderConstExprTests.hpp"
+
+#include "tcuStringTemplate.hpp"
+
+#include "deStringUtil.hpp"
+#include "deMath.h"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+// builtins
+class ShaderConstExprBuiltinTests : public TestCaseGroup
+{
+public:
+				ShaderConstExprBuiltinTests		(Context& context) : TestCaseGroup (context, "builtin_functions", "Builtin functions") {}
+	virtual		~ShaderConstExprBuiltinTests	(void) {}
+
+	void		init							(void);
+
+	void		addChildGroup					(const char* name, const char* desc, const gls::ShaderConstExpr::TestParams* cases, int numCases);
+};
+
+void ShaderConstExprBuiltinTests::addChildGroup (const char* name, const char* desc, const gls::ShaderConstExpr::TestParams* cases, int numCases)
+{
+	const std::vector<tcu::TestNode*>	children = createTests(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), cases, numCases, glu::GLSL_VERSION_100_ES);
+	tcu::TestCaseGroup*					group	 = new tcu::TestCaseGroup(m_testCtx, name, desc);
+
+	addChild(group);
+
+	for (int i = 0; i < (int)children.size(); i++)
+		group->addChild(children[i]);
+}
+
+void ShaderConstExprBuiltinTests::init (void)
+{
+	using namespace gls::ShaderConstExpr;
+
+	// ${T} => final type, ${MT} => final type but with scalar version usable even when T is a vector
+
+	// Trigonometry
+	{
+		const TestParams cases[] =
+		{
+			{"radians",			"radians(${T} (90.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatRadians(90.0f)		},
+			{"degrees",			"degrees(${T} (2.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatDegrees(2.0f)		},
+			{"sin",				"sin(${T} (3.0))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatSin(3.0f)			},
+			{"cos",				"cos(${T} (3.2))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatCos(3.2f)			},
+			{"tan",				"tan(${T} (1.5))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatTan(1.5f)			},
+			{"asin",			"asin(${T} (0.0))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAsin(0.0f)			},
+			{"acos",			"acos(${T} (1.0))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAcos(1.0f)			},
+			{"atan_separate",	"atan(${T} (-1.0), ${T} (-1.0))",						glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAtan2(-1.0f, -1.0f)	},
+			{"atan_combined",	"atan(${T} (2.0))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAtanOver(2.0f)		},
+		};
+
+		addChildGroup("trigonometry", "Trigonometry", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Exponential
+	{
+		const TestParams cases[] =
+		{
+			{"pow",				"pow(${T} (1.7), ${T} (3.5))",							glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatPow(1.7f, 3.5f)		},
+			{"exp",				"exp(${T} (4.2))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatExp(4.2f)			},
+			{"log",				"log(${T} (42.12))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatLog(42.12f)			},
+			{"exp2",			"exp2(${T} (6.7))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatExp2(6.7f)			},
+			{"log2",			"log2(${T} (100.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatLog2(100.0f)			},
+			{"sqrt",			"sqrt(${T} (10.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatSqrt(10.0f)			},
+			{"inversesqrt",		"inversesqrt(${T} (10.0))",								glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatRsq(10.0f)			},
+		};
+
+		addChildGroup("exponential", "Exponential", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Common
+	{
+		const TestParams cases[] =
+		{
+			{"abs",				"abs(${T} (-42.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 42.0f						},
+			{"sign",			"sign(${T} (-18.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, -1.0f						},
+			{"floor",			"floor(${T} (37.3))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatFloor(37.3f)			},
+			{"ceil",			"ceil(${T} (82.2))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatCeil(82.2f)			},
+			{"fract",			"fract(${T} (17.75))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatFrac(17.75f)			},
+			{"mod",				"mod(${T} (87.65), ${MT} (3.7))",						glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatMod(87.65f, 3.7f)	},
+			{"min",				"min(${T} (12.3), ${MT} (32.1))",						glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 12.3f						},
+			{"max",				"max(${T} (12.3), ${MT} (32.1))",						glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 32.1f						},
+			{"clamp",			"clamp(${T} (42.1),	${MT} (10.0), ${MT} (15.0))",		glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 15.0f						},
+			{"mix",				"mix(${T} (10.0), ${T} (20.0), ${MT}(0.75))",			glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 17.5f						},
+			{"step",			"step(${MT} (3.2), ${T} (4.2))",						glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 1.0f						},
+			{"smoothstep",		"smoothstep(${MT} (3.0), ${MT} (5.0), ${T} (4.0))",		glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 0.5f						},
+		};
+
+		addChildGroup("common", "Common", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Geometric
+	{
+		const TestParams cases[] =
+		{
+			{"length_float",	"length(1.0)",											glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"length_vec2",		"length(vec2(1.0))",									glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(2.0f)			},
+			{"length_vec3",		"length(vec3(1.0))",									glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(3.0f)			},
+			{"length_vec4",		"length(vec4(1.0))",									glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(4.0f)			},
+
+			{"distance_float",	"distance(1.0, 2.0)",									glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"distance_vec2",	"distance(vec2(1.0), vec2(2.0))",						glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(2.0f)			},
+			{"distance_vec3",	"distance(vec3(1.0), vec3(2.0))",						glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(3.0f)			},
+			{"distance_vec4",	"distance(vec4(1.0), vec4(2.0))",						glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(4.0f)			},
+
+			{"dot_float",		"dot(1.0, 1.0)",										glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"dot_vec2",		"dot(vec2(1.0), vec2(1.0))",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.0f						},
+			{"dot_vec3",		"dot(vec3(1.0), vec3(1.0))",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 3.0f						},
+			{"dot_vec4",		"dot(vec4(1.0), vec4(1.0))",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 4.0f						},
+
+			{"normalize_float",	"normalize(1.0)",										glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"normalize_vec2",	"normalize(vec2(1.0)).x",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatRsq(2.0f)			},
+			{"normalize_vec3",	"normalize(vec3(1.0)).x",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatRsq(3.0f)			},
+			{"normalize_vec4",	"normalize(vec4(1.0)).x",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatRsq(4.0f)			},
+
+			{"faceforward",		"faceforward(${T} (1.0), ${T} (1.0), ${T} (1.0))",		glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, -1.0f						},
+
+			// reflect(I, N) => I - 2*dot(N, I)*N
+			{"reflect_float",	"reflect(1.0, 1.0)",									glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"reflect_vec2",	"reflect(vec2(1.0), vec2(1.0, 0.0)).x",					glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"reflect_vec3",	"reflect(vec3(1.0), vec3(1.0, 0.0, 0.0)).x",			glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"reflect_vec4",	"reflect(vec4(1.0), vec4(1.0, 0.0, 0.0, 0.0)).x",		glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+
+			/*
+			genType refract(genType I, genType N, float eta) =>
+				k = 1.0 - (eta^2)*(1.0-dot(N,I)^2)
+				if k < 0 return 0.0
+				else return eta*I - (eta*dot(N,I) + sqrt(k))*N
+			*/
+			{"refract_float",	"refract(1.0, 1.0, 0.5)",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"refract_vec2",	"refract(vec2(1.0), vec2(1.0, 0.0), 0.5).x",			glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"refract_vec3",	"refract(vec3(1.0), vec3(1.0, 0.0, 0.0), 0.5).x",		glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"refract_vec4",	"refract(vec4(1.0), vec4(1.0, 0.0, 0.0, 0.0), 0.5).x",	glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+		};
+
+		addChildGroup("geometric", "Geometric", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Matrix
+	{
+		const TestParams cases[] =
+		{
+			{"compMult_mat2",	"matrixCompMult(mat2(1.0), mat2(1.0))[0][0]",			glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"compMult_mat3",	"matrixCompMult(mat3(1.0), mat3(1.0))[0][0]",			glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"compMult_mat4",	"matrixCompMult(mat4(1.0), mat4(1.0))[0][0]",			glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+		};
+
+		addChildGroup("matrix", "Matrix", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Vector relational
+	{
+		const TestParams cases[] =
+		{
+			{"lessThan",		"lessThan(${T} (1.0), ${T} (2.0))",						glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"lessThan",		"lessThan(${T} (1), ${T} (2))",							glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"lessThanEqual",	"lessThanEqual(${T} (1.0), ${T} (1.0))",				glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"lessThanEqual",	"lessThanEqual(${T} (1), ${T} (1))",					glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"greaterThan",		"greaterThan(${T} (1.0), ${T} (2.0))",					glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"greaterThan",		"greaterThan(${T} (1), ${T} (2))",						glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"greaterThanEqual","greaterThanEqual(${T} (1.0), ${T} (2.0))",				glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"greaterThanEqual","greaterThanEqual(${T} (1), ${T} (2))",					glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"equal",			"equal(${T} (1.0), ${T} (1.2))",						glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"equal",			"equal(${T} (1), ${T} (2))",							glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"equal",			"equal(${T} (true), ${T} (false))",						glu::TYPE_BOOL,  2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"notEqual",		"notEqual(${T} (1.0), ${T} (1.2))",						glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"notEqual",		"notEqual(${T} (1), ${T} (2))",							glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"notEqual",		"notEqual(${T} (true), ${T} (false))",					glu::TYPE_BOOL,  2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"any_bvec2",		"any(bvec2(true, false))",								glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  1.0f						},
+			{"any_bvec3",		"any(bvec3(true, false, false))",						glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  1.0f						},
+			{"any_bvec4",		"any(bvec4(true, false, false, false))",				glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  1.0f						},
+			{"all_bvec2",		"all(bvec2(true, false))",								glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  0.0f						},
+			{"all_bvec3",		"all(bvec3(true, false, false))",						glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  0.0f						},
+			{"all_bvec4",		"all(bvec4(true, false, false, false))",				glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  0.0f						},
+			{"not",				"not(${T} (false))",									glu::TYPE_BOOL,  2, 4, glu::TYPE_BOOL,  1.0f						}
+		};
+
+		addChildGroup("vector_relational", "Vector relational", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+}
+
+ShaderConstExprTests::ShaderConstExprTests (Context& context)
+	: TestCaseGroup	(context, "constant_expressions", "Constant expressions")
+{
+}
+
+ShaderConstExprTests::~ShaderConstExprTests (void)
+{
+}
+
+// all
+void ShaderConstExprTests::init (void)
+{
+	const std::vector<tcu::TestNode*> children = gls::ShaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo()).loadShaderFile("shaders/constant_expressions.test");
+
+	for (int i = 0; i < (int)children.size(); i++)
+		addChild(children[i]);
+
+	addChild(new ShaderConstExprBuiltinTests(m_context));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderConstExprTests.hpp b/modules/gles2/functional/es2fShaderConstExprTests.hpp
new file mode 100644
index 0000000..a2d2dd9
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderConstExprTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FSHADERCONSTEXPRTESTS_HPP
+#define _ES2FSHADERCONSTEXPRTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES2 shader constant expression tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderConstExprTests : public TestCaseGroup
+{
+public:
+							ShaderConstExprTests		(Context& context);
+	virtual					~ShaderConstExprTests		(void);
+
+	void					init						(void);
+
+private:
+							ShaderConstExprTests		(const ShaderConstExprTests& other);
+	ShaderConstExprTests&	operator=					(const ShaderConstExprTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERCONSTEXPRTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderDiscardTests.cpp b/modules/gles2/functional/es2fShaderDiscardTests.cpp
new file mode 100644
index 0000000..92316e2
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderDiscardTests.cpp
@@ -0,0 +1,376 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader discard statement tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderDiscardTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluTexture.hpp"
+
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using tcu::StringTemplate;
+
+using std::map;
+using std::string;
+using std::ostringstream;
+
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+enum CaseFlags
+{
+	FLAG_USES_TEXTURES				= (1<<0),
+	FLAG_REQUIRES_DYNAMIC_LOOPS		= (1<<1),
+};
+
+class ShaderDiscardCase : public ShaderRenderCase
+{
+public:
+						ShaderDiscardCase			(Context& context, const char* name, const char* description, const char* shaderSource, ShaderEvalFunc evalFunc, deUint32 flags);
+	virtual				~ShaderDiscardCase			(void);
+
+	void				init						(void);
+	void				deinit						(void);
+
+	void				setupUniforms				(int programID, const tcu::Vec4& constCoords);
+
+private:
+	const deUint32		m_flags;
+	glu::Texture2D*		m_brickTexture;
+};
+
+ShaderDiscardCase::ShaderDiscardCase (Context& context, const char* name, const char* description, const char* shaderSource, ShaderEvalFunc evalFunc, deUint32 flags)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, false, evalFunc)
+	, m_flags			(flags)
+	, m_brickTexture	(DE_NULL)
+{
+	m_fragShaderSource	= shaderSource;
+	m_vertShaderSource	=
+		"attribute highp   vec4 a_position;\n"
+		"attribute highp   vec4 a_coords;\n"
+		"varying   mediump vec4 v_color;\n"
+		"varying   mediump vec4 v_coords;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_Position = a_position;\n"
+		"    v_color = vec4(a_coords.xyz, 1.0);\n"
+		"    v_coords = a_coords;\n"
+		"}\n";
+}
+
+ShaderDiscardCase::~ShaderDiscardCase (void)
+{
+	delete m_brickTexture;
+}
+
+void ShaderDiscardCase::init (void)
+{
+	try
+	{
+		gls::ShaderRenderCase::init();
+	}
+	catch (const CompileFailed&)
+	{
+		if (m_flags & FLAG_REQUIRES_DYNAMIC_LOOPS)
+		{
+			const bool isSupported = m_isVertexCase ? m_ctxInfo.isVertexDynamicLoopSupported() : m_ctxInfo.isFragmentDynamicLoopSupported();
+			if (!isSupported)
+				throw tcu::NotSupportedError("Dynamic loops not supported");
+		}
+
+		throw;
+	}
+
+	if (m_flags & FLAG_USES_TEXTURES)
+	{
+		m_brickTexture = glu::Texture2D::create(m_renderCtx, m_ctxInfo, m_testCtx.getArchive(), "data/brick.png");
+		m_textures.push_back(TextureBinding(m_brickTexture, tcu::Sampler(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
+																		 tcu::Sampler::LINEAR, tcu::Sampler::LINEAR)));
+	}
+}
+
+void ShaderDiscardCase::deinit (void)
+{
+	gls::ShaderRenderCase::deinit();
+	delete m_brickTexture;
+	m_brickTexture = DE_NULL;
+}
+
+void ShaderDiscardCase::setupUniforms (int programID, const tcu::Vec4&)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.uniform1i(gl.getUniformLocation(programID, "ut_brick"), 0);
+}
+
+ShaderDiscardTests::ShaderDiscardTests (Context& context)
+	: TestCaseGroup(context, "discard", "Discard statement tests")
+{
+}
+
+ShaderDiscardTests::~ShaderDiscardTests (void)
+{
+}
+
+enum DiscardMode
+{
+	DISCARDMODE_ALWAYS = 0,
+	DISCARDMODE_NEVER,
+	DISCARDMODE_UNIFORM,
+	DISCARDMODE_DYNAMIC,
+	DISCARDMODE_TEXTURE,
+
+	DISCARDMODE_LAST
+};
+
+enum DiscardTemplate
+{
+	DISCARDTEMPLATE_MAIN_BASIC = 0,
+	DISCARDTEMPLATE_FUNCTION_BASIC,
+	DISCARDTEMPLATE_MAIN_STATIC_LOOP,
+	DISCARDTEMPLATE_MAIN_DYNAMIC_LOOP,
+	DISCARDTEMPLATE_FUNCTION_STATIC_LOOP,
+
+	DISCARDTEMPLATE_LAST
+};
+
+// Evaluation functions
+inline void evalDiscardAlways	(ShaderEvalContext& c) { c.discard(); }
+inline void evalDiscardNever	(ShaderEvalContext& c) { c.color.xyz() = c.coords.swizzle(0,1,2); }
+inline void evalDiscardDynamic	(ShaderEvalContext& c) { c.color.xyz() = c.coords.swizzle(0,1,2); if (c.coords.x()+c.coords.y() > 0.0f) c.discard(); }
+
+inline void evalDiscardTexture (ShaderEvalContext& c)
+{
+	c.color.xyz() = c.coords.swizzle(0,1,2);
+	if (c.texture2D(0, c.coords.swizzle(0,1) * 0.25f + 0.5f).x() < 0.7f)
+		c.discard();
+}
+
+static ShaderEvalFunc getEvalFunc (DiscardMode mode)
+{
+	switch (mode)
+	{
+		case DISCARDMODE_ALWAYS:	return evalDiscardAlways;
+		case DISCARDMODE_NEVER:		return evalDiscardNever;
+		case DISCARDMODE_UNIFORM:	return evalDiscardAlways;
+		case DISCARDMODE_DYNAMIC:	return evalDiscardDynamic;
+		case DISCARDMODE_TEXTURE:	return evalDiscardTexture;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return evalDiscardAlways;
+	}
+}
+
+static const char* getTemplate (DiscardTemplate variant)
+{
+	switch (variant)
+	{
+		case DISCARDTEMPLATE_MAIN_BASIC:
+			return "varying mediump vec4 v_color;\n"
+				   "varying mediump vec4 v_coords;\n"
+				   "uniform sampler2D    ut_brick;\n"
+				   "uniform mediump int  ui_one;\n\n"
+				   "void main (void)\n"
+				   "{\n"
+				   "    gl_FragColor = v_color;\n"
+				   "    ${DISCARD};\n"
+				   "}\n";
+
+		case DISCARDTEMPLATE_FUNCTION_BASIC:
+			return "varying mediump vec4 v_color;\n"
+				   "varying mediump vec4 v_coords;\n"
+				   "uniform sampler2D    ut_brick;\n"
+				   "uniform mediump int  ui_one;\n\n"
+				   "void myfunc (void)\n"
+				   "{\n"
+				   "    ${DISCARD};\n"
+				   "}\n\n"
+				   "void main (void)\n"
+				   "{\n"
+				   "    gl_FragColor = v_color;\n"
+				   "    myfunc();\n"
+				   "}\n";
+
+		case DISCARDTEMPLATE_MAIN_STATIC_LOOP:
+			return "varying mediump vec4 v_color;\n"
+				   "varying mediump vec4 v_coords;\n"
+				   "uniform sampler2D    ut_brick;\n"
+				   "uniform mediump int  ui_one;\n\n"
+				   "void main (void)\n"
+				   "{\n"
+				   "    gl_FragColor = v_color;\n"
+				   "    for (int i = 0; i < 2; i++)\n"
+				   "    {\n"
+				   "        if (i > 0)\n"
+				   "            ${DISCARD};\n"
+				   "    }\n"
+				   "}\n";
+
+		case DISCARDTEMPLATE_MAIN_DYNAMIC_LOOP:
+			return "varying mediump vec4 v_color;\n"
+				   "varying mediump vec4 v_coords;\n"
+				   "uniform sampler2D    ut_brick;\n"
+				   "uniform mediump int  ui_one;\n"
+				   "uniform mediump int  ui_two;\n\n"
+				   "void main (void)\n"
+				   "{\n"
+				   "    gl_FragColor = v_color;\n"
+				   "    for (int i = 0; i < ui_two; i++)\n"
+				   "    {\n"
+				   "        if (i > 0)\n"
+				   "            ${DISCARD};\n"
+				   "    }\n"
+				   "}\n";
+
+		case DISCARDTEMPLATE_FUNCTION_STATIC_LOOP:
+			return "varying mediump vec4 v_color;\n"
+				   "varying mediump vec4 v_coords;\n"
+				   "uniform sampler2D    ut_brick;\n"
+				   "uniform mediump int  ui_one;\n\n"
+				   "void myfunc (void)\n"
+				   "{\n"
+				   "    for (int i = 0; i < 2; i++)\n"
+				   "    {\n"
+				   "        if (i > 0)\n"
+				   "            ${DISCARD};\n"
+				   "    }\n"
+				   "}\n\n"
+				   "void main (void)\n"
+				   "{\n"
+				   "    gl_FragColor = v_color;\n"
+				   "    myfunc();\n"
+				   "}\n";
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static const char* getTemplateName (DiscardTemplate variant)
+{
+	switch (variant)
+	{
+		case DISCARDTEMPLATE_MAIN_BASIC:			return "basic";
+		case DISCARDTEMPLATE_FUNCTION_BASIC:		return "function";
+		case DISCARDTEMPLATE_MAIN_STATIC_LOOP:		return "static_loop";
+		case DISCARDTEMPLATE_MAIN_DYNAMIC_LOOP:		return "dynamic_loop";
+		case DISCARDTEMPLATE_FUNCTION_STATIC_LOOP:	return "function_static_loop";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static const char* getModeName (DiscardMode mode)
+{
+	switch (mode)
+	{
+		case DISCARDMODE_ALWAYS:	return "always";
+		case DISCARDMODE_NEVER:		return "never";
+		case DISCARDMODE_UNIFORM:	return "uniform";
+		case DISCARDMODE_DYNAMIC:	return "dynamic";
+		case DISCARDMODE_TEXTURE:	return "texture";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static const char* getTemplateDesc (DiscardTemplate variant)
+{
+	switch (variant)
+	{
+		case DISCARDTEMPLATE_MAIN_BASIC:			return "main";
+		case DISCARDTEMPLATE_FUNCTION_BASIC:		return "function";
+		case DISCARDTEMPLATE_MAIN_STATIC_LOOP:		return "static loop";
+		case DISCARDTEMPLATE_MAIN_DYNAMIC_LOOP:		return "dynamic loop";
+		case DISCARDTEMPLATE_FUNCTION_STATIC_LOOP:	return "static loop in function";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static const char* getModeDesc (DiscardMode mode)
+{
+	switch (mode)
+	{
+		case DISCARDMODE_ALWAYS:	return "Always discard";
+		case DISCARDMODE_NEVER:		return "Never discard";
+		case DISCARDMODE_UNIFORM:	return "Discard based on uniform value";
+		case DISCARDMODE_DYNAMIC:	return "Discard based on varying values";
+		case DISCARDMODE_TEXTURE:	return "Discard based on texture value";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+ShaderDiscardCase* makeDiscardCase (Context& context, DiscardTemplate tmpl, DiscardMode mode)
+{
+	StringTemplate shaderTemplate(getTemplate(tmpl));
+
+	map<string, string> params;
+
+	switch (mode)
+	{
+		case DISCARDMODE_ALWAYS:	params["DISCARD"] = "discard";										break;
+		case DISCARDMODE_NEVER:		params["DISCARD"] = "if (false) discard";							break;
+		case DISCARDMODE_UNIFORM:	params["DISCARD"] = "if (ui_one > 0) discard";						break;
+		case DISCARDMODE_DYNAMIC:	params["DISCARD"] = "if (v_coords.x+v_coords.y > 0.0) discard";		break;
+		case DISCARDMODE_TEXTURE:	params["DISCARD"] = "if (texture2D(ut_brick, v_coords.xy*0.25+0.5).x < 0.7) discard";	break;
+		default:
+			DE_ASSERT(DE_FALSE);
+			break;
+	}
+
+	string		name			= string(getTemplateName(tmpl)) + "_" + getModeName(mode);
+	string		description		= string(getModeDesc(mode)) + " in " + getTemplateDesc(tmpl);
+	deUint32	flags			= (mode == DISCARDMODE_TEXTURE ? FLAG_USES_TEXTURES : 0)
+								| (tmpl == DISCARDTEMPLATE_MAIN_DYNAMIC_LOOP ? FLAG_REQUIRES_DYNAMIC_LOOPS : 0);
+
+	return new ShaderDiscardCase(context, name.c_str(), description.c_str(), shaderTemplate.specialize(params).c_str(), getEvalFunc(mode), flags);
+}
+
+void ShaderDiscardTests::init (void)
+{
+	for (int tmpl = 0; tmpl < DISCARDTEMPLATE_LAST; tmpl++)
+		for (int mode = 0; mode < DISCARDMODE_LAST; mode++)
+			addChild(makeDiscardCase(m_context, (DiscardTemplate)tmpl, (DiscardMode)mode));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderDiscardTests.hpp b/modules/gles2/functional/es2fShaderDiscardTests.hpp
new file mode 100644
index 0000000..5519311
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderDiscardTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERDISCARDTESTS_HPP
+#define _ES2FSHADERDISCARDTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader discard statement tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderDiscardTests : public TestCaseGroup
+{
+public:
+							ShaderDiscardTests		(Context& context);
+	virtual					~ShaderDiscardTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderDiscardTests		(const ShaderDiscardTests&);		// not allowed!
+	ShaderDiscardTests&		operator=				(const ShaderDiscardTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERDISCARDTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderExecuteTest.cpp b/modules/gles2/functional/es2fShaderExecuteTest.cpp
new file mode 100644
index 0000000..4cd9cc3
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderExecuteTest.cpp
@@ -0,0 +1,66 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader execute test.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderExecuteTest.hpp"
+#include "glsShaderLibrary.hpp"
+
+#include "deMemory.h"
+
+#include <stdio.h>
+#include <vector>
+#include <string>
+
+using namespace std;
+using namespace tcu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+ShaderExecuteTest::ShaderExecuteTest (Context& context, const char* groupName, const char* description)
+	: TestCaseGroup(context, groupName, description)
+{
+}
+
+ShaderExecuteTest::~ShaderExecuteTest (void)
+{
+}
+
+void ShaderExecuteTest::init (void)
+{
+	// Test code.
+	gls::ShaderLibrary	shaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
+	string				fileName	= string("shaders/") + getName() + ".test";
+	vector<TestNode*>	children	= shaderLibrary.loadShaderFile(fileName.c_str());
+
+	for (int i = 0; i < (int)children.size(); i++)
+		addChild(children[i]);
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderExecuteTest.hpp b/modules/gles2/functional/es2fShaderExecuteTest.hpp
new file mode 100644
index 0000000..e0be178
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderExecuteTest.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FSHADEREXECUTETEST_HPP
+#define _ES2FSHADEREXECUTETEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader execute test.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderExecuteTest : public TestCaseGroup
+{
+public:
+							ShaderExecuteTest	(Context& context, const char* groupName, const char* description);
+	virtual					~ShaderExecuteTest	(void);
+
+	virtual void			init				(void);
+
+private:
+							ShaderExecuteTest	(const ShaderExecuteTest&);		// not allowed!
+	ShaderExecuteTest&		operator=			(const ShaderExecuteTest&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADEREXECUTETEST_HPP
diff --git a/modules/gles2/functional/es2fShaderFragDataTests.cpp b/modules/gles2/functional/es2fShaderFragDataTests.cpp
new file mode 100644
index 0000000..f1ef8f0
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderFragDataTests.cpp
@@ -0,0 +1,249 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief gl_FragData[] tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderFragDataTests.hpp"
+
+#include "glsShaderLibrary.hpp"
+
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluObjectWrapper.hpp"
+
+#include "tcuRenderTarget.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using std::string;
+using tcu::TestLog;
+
+namespace
+{
+
+enum IndexExprType
+{
+	INDEX_EXPR_STATIC	= 0,
+	INDEX_EXPR_UNIFORM,
+	INDEX_EXPR_DYNAMIC,
+
+	INDEX_EXPR_TYPE_LAST
+};
+
+static bool compareSingleColor (tcu::TestLog& log, const tcu::Surface& surface, tcu::RGBA expectedColor, tcu::RGBA threshold)
+{
+	const int	maxPrints			= 10;
+	int			numFailedPixels		= 0;
+
+	log << TestLog::Message << "Expecting " << expectedColor << " with threshold " << threshold << TestLog::EndMessage;
+
+	for (int y = 0; y < surface.getHeight(); y++)
+	{
+		for (int x = 0; x < surface.getWidth(); x++)
+		{
+			const tcu::RGBA		resultColor		= surface.getPixel(x, y);
+			const bool			isOk			= compareThreshold(resultColor, expectedColor, threshold);
+
+			if (!isOk)
+			{
+				if (numFailedPixels < maxPrints)
+					log << TestLog::Message << "ERROR: Got " << resultColor << " at (" << x << ", " << y << ")!" << TestLog::EndMessage;
+				else if (numFailedPixels == maxPrints)
+					log << TestLog::Message << "..." << TestLog::EndMessage;
+
+				numFailedPixels += 1;
+			}
+		}
+	}
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Message << "Found " << numFailedPixels << " invalid pixels, comparison FAILED!" << TestLog::EndMessage;
+		log << TestLog::Image("ResultImage", "Result Image", surface);
+		return false;
+	}
+	else
+	{
+		log << TestLog::Message << "Image comparison passed." << TestLog::EndMessage;
+		return true;
+	}
+}
+
+class FragDataIndexingCase : public TestCase
+{
+public:
+	FragDataIndexingCase (Context& context, const char* name, const char* description, IndexExprType indexExprType)
+		: TestCase			(context, name, description)
+		, m_indexExprType	(indexExprType)
+	{
+	}
+
+	static glu::ProgramSources genSources (const IndexExprType indexExprType)
+	{
+		const char* const	fragIndexExpr	= indexExprType == INDEX_EXPR_STATIC	? "0"				:
+											  indexExprType == INDEX_EXPR_UNIFORM	? "u_index"			:
+											  indexExprType == INDEX_EXPR_DYNAMIC	? "int(v_index)"	: DE_NULL;
+		glu::ProgramSources	sources;
+
+		DE_ASSERT(fragIndexExpr);
+
+		sources << glu::VertexSource(
+			"attribute highp vec4 a_position;\n"
+			"attribute highp float a_index;\n"
+			"attribute highp vec4 a_color;\n"
+			"varying mediump float v_index;\n"
+			"varying mediump vec4 v_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_color = a_color;\n"
+			"	v_index = a_index;\n"
+			"}\n");
+
+		sources << glu::FragmentSource(string(
+			"varying mediump vec4 v_color;\n"
+			"varying mediump float v_index;\n"
+			"uniform mediump int u_index;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragData[") + fragIndexExpr + "] = v_color;\n"
+			"}\n");
+
+		return sources;
+	}
+
+	IterateResult iterate (void)
+	{
+		const glu::RenderContext&		renderCtx		= m_context.getRenderContext();
+		const glw::Functions&			gl				= renderCtx.getFunctions();
+		const glu::ShaderProgram		program			(renderCtx, genSources(m_indexExprType));
+		const int						viewportW		= de::min(renderCtx.getRenderTarget().getWidth(), 128);
+		const int						viewportH		= de::min(renderCtx.getRenderTarget().getHeight(), 128);
+
+		const float positions[] =
+		{
+			-1.0f, -1.0f,
+			+1.0f, -1.0f,
+			-1.0f, +1.0f,
+			+1.0f, +1.0f
+		};
+		const float colors[] =
+		{
+			0.0f, 1.0f, 0.0f, 1.0f,
+			0.0f, 1.0f, 0.0f, 1.0f,
+			0.0f, 1.0f, 0.0f, 1.0f,
+			0.0f, 1.0f, 0.0f, 1.0f
+		};
+		const float		indexValues[]	= { 0.0f, 0.0f, 0.0f, 0.0f };
+		const deUint8	indices[]		= { 0, 1, 2, 2, 1, 3 };
+
+		const glu::VertexArrayBinding vertexArrays[] =
+		{
+			glu::va::Float("a_position",	2, 4, 0, &positions[0]),
+			glu::va::Float("a_color",		4, 4, 0, &colors[0]),
+			glu::va::Float("a_index",		1, 4, 0, &indexValues[0])
+		};
+
+		m_testCtx.getLog() << program;
+
+		if (!program.isOk())
+		{
+			if (m_indexExprType == INDEX_EXPR_STATIC)
+				TCU_FAIL("Compile failed");
+			else
+				throw tcu::NotSupportedError("Dynamic indexing of gl_FragData[] not supported");
+		}
+
+		gl.clearColor	(1.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear		(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		gl.viewport		(0, 0, viewportW, viewportH);
+		gl.useProgram	(program.getProgram());
+		gl.uniform1i	(gl.getUniformLocation(program.getProgram(), "u_index"), 0);
+
+		glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
+				  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Rendering failed");
+
+		{
+			tcu::Surface		result		(viewportW, viewportH);
+			const tcu::RGBA		threshold	= renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+			bool				isOk;
+
+			glu::readPixels(renderCtx, 0, 0, result.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");
+
+			isOk = compareSingleColor(m_testCtx.getLog(), result, tcu::RGBA::green, threshold);
+
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	const IndexExprType m_indexExprType;
+};
+
+} // anonymous
+
+ShaderFragDataTests::ShaderFragDataTests (Context& context)
+	: TestCaseGroup(context, "fragdata", "gl_FragData[] Tests")
+{
+}
+
+ShaderFragDataTests::~ShaderFragDataTests (void)
+{
+}
+
+void ShaderFragDataTests::init (void)
+{
+	addChild(new FragDataIndexingCase(m_context, "valid_static_index",	"Valid gl_FragData[] assignment using static index",	INDEX_EXPR_STATIC));
+	addChild(new FragDataIndexingCase(m_context, "valid_uniform_index",	"Valid gl_FragData[] assignment using uniform index",	INDEX_EXPR_UNIFORM));
+	addChild(new FragDataIndexingCase(m_context, "valid_dynamic_index",	"Valid gl_FragData[] assignment using dynamic index",	INDEX_EXPR_DYNAMIC));
+
+	// Negative cases.
+	{
+		gls::ShaderLibrary library(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
+		std::vector<tcu::TestNode*> negativeCases = library.loadShaderFile("shaders/fragdata.test");
+
+		for (std::vector<tcu::TestNode*>::iterator i = negativeCases.begin(); i != negativeCases.end(); i++)
+			addChild(*i);
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderFragDataTests.hpp b/modules/gles2/functional/es2fShaderFragDataTests.hpp
new file mode 100644
index 0000000..00efe19
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderFragDataTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERFRAGDATATESTS_HPP
+#define _ES2FSHADERFRAGDATATESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief gl_FragData[] tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderFragDataTests : public TestCaseGroup
+{
+public:
+							ShaderFragDataTests		(Context& context);
+	virtual					~ShaderFragDataTests	(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderFragDataTests		(const ShaderFragDataTests&);		// not allowed!
+	ShaderFragDataTests&	operator=				(const ShaderFragDataTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERFRAGDATATESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderIndexingTests.cpp b/modules/gles2/functional/es2fShaderIndexingTests.cpp
new file mode 100644
index 0000000..634b864
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderIndexingTests.cpp
@@ -0,0 +1,1167 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader indexing (arrays, vector, matrices) tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderIndexingTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuStringTemplate.hpp"
+
+#include "deInt32.h"
+#include "deMemory.h"
+
+#include <map>
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using namespace std;
+using namespace tcu;
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+enum IndexAccessType
+{
+	INDEXACCESS_STATIC = 0,
+	INDEXACCESS_DYNAMIC,
+	INDEXACCESS_STATIC_LOOP,
+	INDEXACCESS_DYNAMIC_LOOP,
+
+	INDEXACCESS_LAST
+};
+
+static const char* getIndexAccessTypeName (IndexAccessType accessType)
+{
+	static const char* s_names[INDEXACCESS_LAST] =
+	{
+		"static",
+		"dynamic",
+		"static_loop",
+		"dynamic_loop"
+	};
+
+	DE_ASSERT(deInBounds32((int)accessType, 0, INDEXACCESS_LAST));
+	return s_names[(int)accessType];
+}
+
+enum VectorAccessType
+{
+	DIRECT = 0,
+	COMPONENT,
+	SUBSCRIPT_STATIC,
+	SUBSCRIPT_DYNAMIC,
+	SUBSCRIPT_STATIC_LOOP,
+	SUBSCRIPT_DYNAMIC_LOOP,
+
+	VECTORACCESS_LAST
+};
+
+static const char* getVectorAccessTypeName (VectorAccessType accessType)
+{
+	static const char* s_names[VECTORACCESS_LAST] =
+	{
+		"direct",
+		"component",
+		"static_subscript",
+		"dynamic_subscript",
+		"static_loop_subscript",
+		"dynamic_loop_subscript"
+	};
+
+	DE_ASSERT(deInBounds32((int)accessType, 0, VECTORACCESS_LAST));
+	return s_names[(int)accessType];
+}
+
+enum RequirementFlags
+{
+	REQUIREMENT_UNIFORM_INDEXING		= (1<<0),
+	REQUIREMENT_VERTEX_UNIFORM_LOOPS	= (1<<1),
+	REQUIREMENT_FRAGMENT_UNIFORM_LOOPS	= (1<<2),
+};
+
+void evalArrayCoordsFloat		(ShaderEvalContext& c) { c.color.x()	= 1.875f * c.coords.x(); }
+void evalArrayCoordsVec2		(ShaderEvalContext& c) { c.color.xy()	= 1.875f * c.coords.swizzle(0,1); }
+void evalArrayCoordsVec3		(ShaderEvalContext& c) { c.color.xyz()	= 1.875f * c.coords.swizzle(0,1,2); }
+void evalArrayCoordsVec4		(ShaderEvalContext& c) { c.color		= 1.875f * c.coords; }
+
+static ShaderEvalFunc getArrayCoordsEvalFunc (DataType dataType)
+{
+	if (dataType == TYPE_FLOAT)				return evalArrayCoordsFloat;
+	else if (dataType == TYPE_FLOAT_VEC2)	return evalArrayCoordsVec2;
+	else if (dataType == TYPE_FLOAT_VEC3)	return evalArrayCoordsVec3;
+	else if (dataType == TYPE_FLOAT_VEC4)	return evalArrayCoordsVec4;
+
+	DE_ASSERT(!"Invalid data type.");
+	return NULL;
+}
+
+void evalArrayUniformFloat		(ShaderEvalContext& c) { c.color.x()	= 1.875f * c.constCoords.x(); }
+void evalArrayUniformVec2		(ShaderEvalContext& c) { c.color.xy()	= 1.875f * c.constCoords.swizzle(0,1); }
+void evalArrayUniformVec3		(ShaderEvalContext& c) { c.color.xyz()	= 1.875f * c.constCoords.swizzle(0,1,2); }
+void evalArrayUniformVec4		(ShaderEvalContext& c) { c.color		= 1.875f * c.constCoords; }
+
+static ShaderEvalFunc getArrayUniformEvalFunc (DataType dataType)
+{
+	if (dataType == TYPE_FLOAT)				return evalArrayUniformFloat;
+	else if (dataType == TYPE_FLOAT_VEC2)	return evalArrayUniformVec2;
+	else if (dataType == TYPE_FLOAT_VEC3)	return evalArrayUniformVec3;
+	else if (dataType == TYPE_FLOAT_VEC4)	return evalArrayUniformVec4;
+
+	DE_ASSERT(!"Invalid data type.");
+	return NULL;
+}
+
+// ShaderIndexingCase
+
+class ShaderIndexingCase : public ShaderRenderCase
+{
+public:
+								ShaderIndexingCase		(Context& context, const char* name, const char* description, bool isVertexCase, DataType varType, ShaderEvalFunc evalFunc, deUint32 requirements, const char* vertShaderSource, const char* fragShaderSource);
+	virtual						~ShaderIndexingCase		(void);
+
+	virtual void				init					(void);
+
+private:
+								ShaderIndexingCase		(const ShaderIndexingCase&);	// not allowed!
+	ShaderIndexingCase&			operator=				(const ShaderIndexingCase&);	// not allowed!
+
+	virtual void				setup					(int programID);
+	virtual void				setupUniforms			(int programID, const Vec4& constCoords);
+
+	DataType					m_varType;
+	deUint32					m_requirements;
+};
+
+ShaderIndexingCase::ShaderIndexingCase (Context& context, const char* name, const char* description, bool isVertexCase, DataType varType, ShaderEvalFunc evalFunc, deUint32 requirements, const char* vertShaderSource, const char* fragShaderSource)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
+	, m_requirements	(requirements)
+{
+	m_varType			= varType;
+	m_vertShaderSource	= vertShaderSource;
+	m_fragShaderSource	= fragShaderSource;
+}
+
+ShaderIndexingCase::~ShaderIndexingCase (void)
+{
+}
+
+void ShaderIndexingCase::init (void)
+{
+	const bool isSupported = !(m_requirements & REQUIREMENT_UNIFORM_INDEXING) &&
+							 (!(m_requirements & REQUIREMENT_VERTEX_UNIFORM_LOOPS) || m_ctxInfo.isVertexUniformLoopSupported()) &&
+							 (!(m_requirements & REQUIREMENT_FRAGMENT_UNIFORM_LOOPS) || m_ctxInfo.isFragmentUniformLoopSupported());
+
+	try
+	{
+		ShaderRenderCase::init();
+	}
+	catch (const CompileFailed&)
+	{
+		if (!isSupported)
+			throw tcu::NotSupportedError("Shader is not supported");
+		else
+			throw;
+	}
+}
+
+void ShaderIndexingCase::setup (int programID)
+{
+	DE_UNREF(programID);
+}
+
+void ShaderIndexingCase::setupUniforms (int programID, const Vec4& constCoords)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	DE_UNREF(constCoords);
+
+	int arrLoc = gl.getUniformLocation(programID, "u_arr");
+	if (arrLoc != -1)
+	{
+		//int scalarSize = getDataTypeScalarSize(m_varType);
+		if (m_varType == TYPE_FLOAT)
+		{
+			float arr[4];
+			arr[0] = constCoords.x();
+			arr[1] = constCoords.x() * 0.5f;
+			arr[2] = constCoords.x() * 0.25f;
+			arr[3] = constCoords.x() * 0.125f;
+			gl.uniform1fv(arrLoc, 4, &arr[0]);
+		}
+		else if (m_varType == TYPE_FLOAT_VEC2)
+		{
+			Vec2 arr[4];
+			arr[0] = constCoords.swizzle(0,1);
+			arr[1] = constCoords.swizzle(0,1) * 0.5f;
+			arr[2] = constCoords.swizzle(0,1) * 0.25f;
+			arr[3] = constCoords.swizzle(0,1) * 0.125f;
+			gl.uniform2fv(arrLoc, 4, arr[0].getPtr());
+		}
+		else if (m_varType == TYPE_FLOAT_VEC3)
+		{
+			Vec3 arr[4];
+			arr[0] = constCoords.swizzle(0,1,2);
+			arr[1] = constCoords.swizzle(0,1,2) * 0.5f;
+			arr[2] = constCoords.swizzle(0,1,2) * 0.25f;
+			arr[3] = constCoords.swizzle(0,1,2) * 0.125f;
+			gl.uniform3fv(arrLoc, 4, arr[0].getPtr());
+		}
+		else if (m_varType == TYPE_FLOAT_VEC4)
+		{
+			Vec4 arr[4];
+			arr[0] = constCoords.swizzle(0,1,2,3);
+			arr[1] = constCoords.swizzle(0,1,2,3) * 0.5f;
+			arr[2] = constCoords.swizzle(0,1,2,3) * 0.25f;
+			arr[3] = constCoords.swizzle(0,1,2,3) * 0.125f;
+			gl.uniform4fv(arrLoc, 4, arr[0].getPtr());
+		}
+		else
+			throw tcu::TestError("u_arr should not have location assigned in this test case");
+	}
+}
+
+// Helpers.
+
+static ShaderIndexingCase* createVaryingArrayCase (Context& context, const char* caseName, const char* description, DataType varType, IndexAccessType vertAccess, IndexAccessType fragAccess)
+{
+	std::ostringstream vtx;
+	vtx << "attribute highp vec4 a_position;\n";
+	vtx << "attribute highp vec4 a_coords;\n";
+	if (vertAccess == INDEXACCESS_DYNAMIC)
+		vtx << "uniform mediump int ui_zero, ui_one, ui_two, ui_three;\n";
+	else if (vertAccess == INDEXACCESS_DYNAMIC_LOOP)
+		vtx << "uniform mediump int ui_four;\n";
+	vtx << "varying ${PRECISION} ${VAR_TYPE} var[${ARRAY_LEN}];\n";
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+	if (vertAccess == INDEXACCESS_STATIC)
+	{
+		vtx << "	var[0] = ${VAR_TYPE}(a_coords);\n";
+		vtx << "	var[1] = ${VAR_TYPE}(a_coords) * 0.5;\n";
+		vtx << "	var[2] = ${VAR_TYPE}(a_coords) * 0.25;\n";
+		vtx << "	var[3] = ${VAR_TYPE}(a_coords) * 0.125;\n";
+	}
+	else if (vertAccess == INDEXACCESS_DYNAMIC)
+	{
+		vtx << "	var[ui_zero]  = ${VAR_TYPE}(a_coords);\n";
+		vtx << "	var[ui_one]   = ${VAR_TYPE}(a_coords) * 0.5;\n";
+		vtx << "	var[ui_two]   = ${VAR_TYPE}(a_coords) * 0.25;\n";
+		vtx << "	var[ui_three] = ${VAR_TYPE}(a_coords) * 0.125;\n";
+	}
+	else if (vertAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		vtx << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(a_coords);\n";
+		vtx << "	for (int i = 0; i < 4; i++)\n";
+		vtx << "	{\n";
+		vtx << "		var[i] = ${VAR_TYPE}(coords);\n";
+		vtx << "		coords = coords * 0.5;\n";
+		vtx << "	}\n";
+	}
+	else
+	{
+		DE_ASSERT(vertAccess == INDEXACCESS_DYNAMIC_LOOP);
+		vtx << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(a_coords);\n";
+		vtx << "	for (int i = 0; i < ui_four; i++)\n";
+		vtx << "	{\n";
+		vtx << "		var[i] = ${VAR_TYPE}(coords);\n";
+		vtx << "		coords = coords * 0.5;\n";
+		vtx << "	}\n";
+	}
+	vtx << "}\n";
+
+	std::ostringstream frag;
+	frag << "precision mediump int;\n";
+	if (fragAccess == INDEXACCESS_DYNAMIC)
+		frag << "uniform mediump int ui_zero, ui_one, ui_two, ui_three;\n";
+	else if (fragAccess == INDEXACCESS_DYNAMIC_LOOP)
+		frag << "uniform int ui_four;\n";
+	frag << "varying ${PRECISION} ${VAR_TYPE} var[${ARRAY_LEN}];\n";
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+	frag << "	${PRECISION} ${VAR_TYPE} res = ${VAR_TYPE}(0.0);\n";
+	if (fragAccess == INDEXACCESS_STATIC)
+	{
+		frag << "	res += var[0];\n";
+		frag << "	res += var[1];\n";
+		frag << "	res += var[2];\n";
+		frag << "	res += var[3];\n";
+	}
+	else if (fragAccess == INDEXACCESS_DYNAMIC)
+	{
+		frag << "	res += var[ui_zero];\n";
+		frag << "	res += var[ui_one];\n";
+		frag << "	res += var[ui_two];\n";
+		frag << "	res += var[ui_three];\n";
+	}
+	else if (fragAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		frag << "	for (int i = 0; i < 4; i++)\n";
+		frag << "		res += var[i];\n";
+	}
+	else
+	{
+		DE_ASSERT(fragAccess == INDEXACCESS_DYNAMIC_LOOP);
+		frag << "	for (int i = 0; i < ui_four; i++)\n";
+		frag << "		res += var[i];\n";
+	}
+	frag << "	gl_FragColor = vec4(res${PADDING});\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("VAR_TYPE", getDataTypeName(varType)));
+	params.insert(pair<string, string>("ARRAY_LEN", "4"));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+
+	if (varType == TYPE_FLOAT)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC2)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC3)
+		params.insert(pair<string, string>("PADDING", ", 1.0"));
+	else
+		params.insert(pair<string, string>("PADDING", ""));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	ShaderEvalFunc evalFunc = getArrayCoordsEvalFunc(varType);
+	deUint32 requirements = 0;
+
+	if (vertAccess == INDEXACCESS_DYNAMIC || fragAccess == INDEXACCESS_DYNAMIC)
+		requirements |= REQUIREMENT_UNIFORM_INDEXING;
+
+	if (vertAccess == INDEXACCESS_DYNAMIC_LOOP)
+		requirements |= REQUIREMENT_VERTEX_UNIFORM_LOOPS|REQUIREMENT_UNIFORM_INDEXING;
+
+	if (fragAccess == INDEXACCESS_DYNAMIC_LOOP)
+		requirements |= REQUIREMENT_FRAGMENT_UNIFORM_LOOPS|REQUIREMENT_UNIFORM_INDEXING;
+
+	return new ShaderIndexingCase(context, caseName, description, true, varType, evalFunc, requirements, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+static ShaderIndexingCase* createUniformArrayCase (Context& context, const char* caseName, const char* description, bool isVertexCase, DataType varType, IndexAccessType readAccess)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	vtx << "attribute highp vec4 a_position;\n";
+	vtx << "attribute highp vec4 a_coords;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "varying mediump vec4 v_coords;\n";
+		frag << "varying mediump vec4 v_coords;\n";
+	}
+
+	if (readAccess == INDEXACCESS_DYNAMIC)
+		op << "uniform mediump int ui_zero, ui_one, ui_two, ui_three;\n";
+	else if (readAccess == INDEXACCESS_DYNAMIC_LOOP)
+		op << "uniform mediump int ui_four;\n";
+
+	op << "uniform ${PRECISION} ${VAR_TYPE} u_arr[${ARRAY_LEN}];\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Read array.
+	op << "	${PRECISION} ${VAR_TYPE} res = ${VAR_TYPE}(0.0);\n";
+	if (readAccess == INDEXACCESS_STATIC)
+	{
+		op << "	res += u_arr[0];\n";
+		op << "	res += u_arr[1];\n";
+		op << "	res += u_arr[2];\n";
+		op << "	res += u_arr[3];\n";
+	}
+	else if (readAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "	res += u_arr[ui_zero];\n";
+		op << "	res += u_arr[ui_one];\n";
+		op << "	res += u_arr[ui_two];\n";
+		op << "	res += u_arr[ui_three];\n";
+	}
+	else if (readAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < 4; i++)\n";
+		op << "		res += u_arr[i];\n";
+	}
+	else
+	{
+		DE_ASSERT(readAccess == INDEXACCESS_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < ui_four; i++)\n";
+		op << "		res += u_arr[i];\n";
+	}
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = vec4(res${PADDING});\n";
+		frag << "	gl_FragColor = v_color;\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	gl_FragColor = vec4(res${PADDING});\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("VAR_TYPE", getDataTypeName(varType)));
+	params.insert(pair<string, string>("ARRAY_LEN", "4"));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+
+	if (varType == TYPE_FLOAT)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC2)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC3)
+		params.insert(pair<string, string>("PADDING", ", 1.0"));
+	else
+		params.insert(pair<string, string>("PADDING", ""));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	ShaderEvalFunc evalFunc = getArrayUniformEvalFunc(varType);
+	deUint32 requirements = 0;
+
+	if (readAccess == INDEXACCESS_DYNAMIC)
+		requirements |= REQUIREMENT_UNIFORM_INDEXING;
+
+	if (readAccess == INDEXACCESS_DYNAMIC_LOOP)
+		requirements |= (isVertexCase ? REQUIREMENT_VERTEX_UNIFORM_LOOPS : REQUIREMENT_FRAGMENT_UNIFORM_LOOPS) | REQUIREMENT_UNIFORM_INDEXING;
+
+	return new ShaderIndexingCase(context, caseName, description, isVertexCase, varType, evalFunc, requirements, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+static ShaderIndexingCase* createTmpArrayCase (Context& context, const char* caseName, const char* description, bool isVertexCase, DataType varType, IndexAccessType writeAccess, IndexAccessType readAccess)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	vtx << "attribute highp vec4 a_position;\n";
+	vtx << "attribute highp vec4 a_coords;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "varying mediump vec4 v_coords;\n";
+		frag << "varying mediump vec4 v_coords;\n";
+	}
+
+	if (writeAccess == INDEXACCESS_DYNAMIC || readAccess == INDEXACCESS_DYNAMIC)
+		op << "uniform mediump int ui_zero, ui_one, ui_two, ui_three;\n";
+
+	if (writeAccess == INDEXACCESS_DYNAMIC_LOOP || readAccess == INDEXACCESS_DYNAMIC_LOOP)
+		op << "uniform mediump int ui_four;\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Write array.
+	if (isVertexCase)
+		op << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(a_coords);\n";
+	else
+		op << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(v_coords);\n";
+
+	op << "	${PRECISION} ${VAR_TYPE} arr[${ARRAY_LEN}];\n";
+	if (writeAccess == INDEXACCESS_STATIC)
+	{
+		op << "	arr[0] = ${VAR_TYPE}(coords);\n";
+		op << "	arr[1] = ${VAR_TYPE}(coords) * 0.5;\n";
+		op << "	arr[2] = ${VAR_TYPE}(coords) * 0.25;\n";
+		op << "	arr[3] = ${VAR_TYPE}(coords) * 0.125;\n";
+	}
+	else if (writeAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "	arr[ui_zero]  = ${VAR_TYPE}(coords);\n";
+		op << "	arr[ui_one]   = ${VAR_TYPE}(coords) * 0.5;\n";
+		op << "	arr[ui_two]   = ${VAR_TYPE}(coords) * 0.25;\n";
+		op << "	arr[ui_three] = ${VAR_TYPE}(coords) * 0.125;\n";
+	}
+	else if (writeAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < 4; i++)\n";
+		op << "	{\n";
+		op << "		arr[i] = ${VAR_TYPE}(coords);\n";
+		op << "		coords = coords * 0.5;\n";
+		op << "	}\n";
+	}
+	else
+	{
+		DE_ASSERT(writeAccess == INDEXACCESS_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < ui_four; i++)\n";
+		op << "	{\n";
+		op << "		arr[i] = ${VAR_TYPE}(coords);\n";
+		op << "		coords = coords * 0.5;\n";
+		op << "	}\n";
+	}
+
+	// Read array.
+	op << "	${PRECISION} ${VAR_TYPE} res = ${VAR_TYPE}(0.0);\n";
+	if (readAccess == INDEXACCESS_STATIC)
+	{
+		op << "	res += arr[0];\n";
+		op << "	res += arr[1];\n";
+		op << "	res += arr[2];\n";
+		op << "	res += arr[3];\n";
+	}
+	else if (readAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "	res += arr[ui_zero];\n";
+		op << "	res += arr[ui_one];\n";
+		op << "	res += arr[ui_two];\n";
+		op << "	res += arr[ui_three];\n";
+	}
+	else if (readAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < 4; i++)\n";
+		op << "		res += arr[i];\n";
+	}
+	else
+	{
+		DE_ASSERT(readAccess == INDEXACCESS_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < ui_four; i++)\n";
+		op << "		res += arr[i];\n";
+	}
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = vec4(res${PADDING});\n";
+		frag << "	gl_FragColor = v_color;\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	gl_FragColor = vec4(res${PADDING});\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("VAR_TYPE", getDataTypeName(varType)));
+	params.insert(pair<string, string>("ARRAY_LEN", "4"));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+
+	if (varType == TYPE_FLOAT)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC2)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC3)
+		params.insert(pair<string, string>("PADDING", ", 1.0"));
+	else
+		params.insert(pair<string, string>("PADDING", ""));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	ShaderEvalFunc evalFunc = getArrayCoordsEvalFunc(varType);
+	deUint32 requirements = 0;
+
+	if (readAccess == INDEXACCESS_DYNAMIC || writeAccess == INDEXACCESS_DYNAMIC)
+		requirements |= REQUIREMENT_UNIFORM_INDEXING;
+
+	if (readAccess == INDEXACCESS_DYNAMIC_LOOP || writeAccess == INDEXACCESS_DYNAMIC_LOOP)
+		requirements |= (isVertexCase ? REQUIREMENT_VERTEX_UNIFORM_LOOPS : REQUIREMENT_FRAGMENT_UNIFORM_LOOPS) | REQUIREMENT_UNIFORM_INDEXING;
+
+	return new ShaderIndexingCase(context, caseName, description, isVertexCase, varType, evalFunc, requirements, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+// VECTOR SUBSCRIPT.
+
+void evalSubscriptVec2 (ShaderEvalContext& c) { c.color.xyz() = Vec3(c.coords.x() + 0.5f*c.coords.y()); }
+void evalSubscriptVec3 (ShaderEvalContext& c) { c.color.xyz() = Vec3(c.coords.x() + 0.5f*c.coords.y() + 0.25f*c.coords.z()); }
+void evalSubscriptVec4 (ShaderEvalContext& c) { c.color.xyz() = Vec3(c.coords.x() + 0.5f*c.coords.y() + 0.25f*c.coords.z() + 0.125f*c.coords.w()); }
+
+static ShaderEvalFunc getVectorSubscriptEvalFunc (DataType dataType)
+{
+	if (dataType == TYPE_FLOAT_VEC2)		return evalSubscriptVec2;
+	else if (dataType == TYPE_FLOAT_VEC3)	return evalSubscriptVec3;
+	else if (dataType == TYPE_FLOAT_VEC4)	return evalSubscriptVec4;
+
+	DE_ASSERT(!"Invalid data type.");
+	return NULL;
+}
+
+static ShaderIndexingCase* createVectorSubscriptCase (Context& context, const char* caseName, const char* description, bool isVertexCase, DataType varType, VectorAccessType writeAccess, VectorAccessType readAccess)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	int			vecLen		= getDataTypeScalarSize(varType);
+	const char*	vecLenName	= getIntUniformName(vecLen);
+
+	vtx << "attribute highp vec4 a_position;\n";
+	vtx << "attribute highp vec4 a_coords;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec3 v_color;\n";
+		frag << "varying mediump vec3 v_color;\n";
+	}
+	else
+	{
+		vtx << "varying mediump vec4 v_coords;\n";
+		frag << "varying mediump vec4 v_coords;\n";
+	}
+
+	if (writeAccess == SUBSCRIPT_DYNAMIC || readAccess == SUBSCRIPT_DYNAMIC)
+	{
+		op << "uniform mediump int ui_zero";
+		if (vecLen >= 2) op << ", ui_one";
+		if (vecLen >= 3) op << ", ui_two";
+		if (vecLen >= 4) op << ", ui_three";
+		op << ";\n";
+	}
+
+	if (writeAccess == SUBSCRIPT_DYNAMIC_LOOP || readAccess == SUBSCRIPT_DYNAMIC_LOOP)
+		op << "uniform mediump int " << vecLenName << ";\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Write vector.
+	if (isVertexCase)
+		op << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(a_coords);\n";
+	else
+		op << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(v_coords);\n";
+
+	op << "	${PRECISION} ${VAR_TYPE} tmp;\n";
+	if (writeAccess == DIRECT)
+		op << "	tmp = coords.${SWIZZLE} * vec4(1.0, 0.5, 0.25, 0.125).${SWIZZLE};\n";
+	else if (writeAccess == COMPONENT)
+	{
+		op << "	tmp.x = coords.x;\n";
+		if (vecLen >= 2) op << "	tmp.y = coords.y * 0.5;\n";
+		if (vecLen >= 3) op << "	tmp.z = coords.z * 0.25;\n";
+		if (vecLen >= 4) op << "	tmp.w = coords.w * 0.125;\n";
+	}
+	else if (writeAccess == SUBSCRIPT_STATIC)
+	{
+		op << "	tmp[0] = coords.x;\n";
+		if (vecLen >= 2) op << "	tmp[1] = coords.y * 0.5;\n";
+		if (vecLen >= 3) op << "	tmp[2] = coords.z * 0.25;\n";
+		if (vecLen >= 4) op << "	tmp[3] = coords.w * 0.125;\n";
+	}
+	else if (writeAccess == SUBSCRIPT_DYNAMIC)
+	{
+		op << "	tmp[ui_zero]  = coords.x;\n";
+		if (vecLen >= 2) op << "	tmp[ui_one]   = coords.y * 0.5;\n";
+		if (vecLen >= 3) op << "	tmp[ui_two]   = coords.z * 0.25;\n";
+		if (vecLen >= 4) op << "	tmp[ui_three] = coords.w * 0.125;\n";
+	}
+	else if (writeAccess == SUBSCRIPT_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < " << vecLen << "; i++)\n";
+		op << "	{\n";
+		op << "		tmp[i] = coords.x;\n";
+		op << "		coords = coords.${ROT_SWIZZLE} * 0.5;\n";
+		op << "	}\n";
+	}
+	else
+	{
+		DE_ASSERT(writeAccess == SUBSCRIPT_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < " << vecLenName << "; i++)\n";
+		op << "	{\n";
+		op << "		tmp[i] = coords.x;\n";
+		op << "		coords = coords.${ROT_SWIZZLE} * 0.5;\n";
+		op << "	}\n";
+	}
+
+	// Read vector.
+	op << "	${PRECISION} float res = 0.0;\n";
+	if (readAccess == DIRECT)
+		op << "	res = dot(tmp, ${VAR_TYPE}(1.0));\n";
+	else if (readAccess == COMPONENT)
+	{
+		op << "	res += tmp.x;\n";
+		if (vecLen >= 2) op << "	res += tmp.y;\n";
+		if (vecLen >= 3) op << "	res += tmp.z;\n";
+		if (vecLen >= 4) op << "	res += tmp.w;\n";
+	}
+	else if (readAccess == SUBSCRIPT_STATIC)
+	{
+		op << "	res += tmp[0];\n";
+		if (vecLen >= 2) op << "	res += tmp[1];\n";
+		if (vecLen >= 3) op << "	res += tmp[2];\n";
+		if (vecLen >= 4) op << "	res += tmp[3];\n";
+	}
+	else if (readAccess == SUBSCRIPT_DYNAMIC)
+	{
+		op << "	res += tmp[ui_zero];\n";
+		if (vecLen >= 2) op << "	res += tmp[ui_one];\n";
+		if (vecLen >= 3) op << "	res += tmp[ui_two];\n";
+		if (vecLen >= 4) op << "	res += tmp[ui_three];\n";
+	}
+	else if (readAccess == SUBSCRIPT_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < " << vecLen << "; i++)\n";
+		op << "		res += tmp[i];\n";
+	}
+	else
+	{
+		DE_ASSERT(readAccess == SUBSCRIPT_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < " << vecLenName << "; i++)\n";
+		op << "		res += tmp[i];\n";
+	}
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = vec3(res);\n";
+		frag << "	gl_FragColor = vec4(v_color, 1.0);\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	gl_FragColor = vec4(vec3(res), 1.0);\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	static const char* s_swizzles[5]	= { "", "x", "xy", "xyz", "xyzw" };
+	static const char* s_rotSwizzles[5]	= { "", "x", "yx", "yzx", "yzwx" };
+
+	map<string, string> params;
+	params.insert(pair<string, string>("VAR_TYPE", getDataTypeName(varType)));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+	params.insert(pair<string, string>("SWIZZLE", s_swizzles[vecLen]));
+	params.insert(pair<string, string>("ROT_SWIZZLE", s_rotSwizzles[vecLen]));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	ShaderEvalFunc evalFunc = getVectorSubscriptEvalFunc(varType);
+	deUint32 requirements = 0;
+
+	if (readAccess == SUBSCRIPT_DYNAMIC || writeAccess == SUBSCRIPT_DYNAMIC)
+		requirements |= REQUIREMENT_UNIFORM_INDEXING;
+
+	if (readAccess == SUBSCRIPT_DYNAMIC_LOOP || writeAccess == SUBSCRIPT_DYNAMIC_LOOP)
+		requirements |= (isVertexCase ? REQUIREMENT_VERTEX_UNIFORM_LOOPS : REQUIREMENT_FRAGMENT_UNIFORM_LOOPS) | REQUIREMENT_UNIFORM_INDEXING;
+
+	return new ShaderIndexingCase(context, caseName, description, isVertexCase, varType, evalFunc, requirements, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+// MATRIX SUBSCRIPT.
+
+void evalSubscriptMat2 (ShaderEvalContext& c) { c.color.xy() = c.coords.swizzle(0,1) + 0.5f*c.coords.swizzle(1,2); }
+void evalSubscriptMat3 (ShaderEvalContext& c) { c.color.xyz() = c.coords.swizzle(0,1,2) + 0.5f*c.coords.swizzle(1,2,3) + 0.25f*c.coords.swizzle(2,3,0); }
+void evalSubscriptMat4 (ShaderEvalContext& c) { c.color = c.coords + 0.5f*c.coords.swizzle(1,2,3,0) + 0.25f*c.coords.swizzle(2,3,0,1) + 0.125f*c.coords.swizzle(3,0,1,2); }
+
+static ShaderEvalFunc getMatrixSubscriptEvalFunc (DataType dataType)
+{
+	if (dataType == TYPE_FLOAT_MAT2)		return evalSubscriptMat2;
+	else if (dataType == TYPE_FLOAT_MAT3)	return evalSubscriptMat3;
+	else if (dataType == TYPE_FLOAT_MAT4)	return evalSubscriptMat4;
+
+	DE_ASSERT(!"Invalid data type.");
+	return NULL;
+}
+
+static ShaderIndexingCase* createMatrixSubscriptCase (Context& context, const char* caseName, const char* description, bool isVertexCase, DataType varType, IndexAccessType writeAccess, IndexAccessType readAccess)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	int			matSize		= getDataTypeMatrixNumRows(varType);
+	const char*	matSizeName	= getIntUniformName(matSize);
+	DataType	vecType		= getDataTypeFloatVec(matSize);
+
+	vtx << "attribute highp vec4 a_position;\n";
+	vtx << "attribute highp vec4 a_coords;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "varying mediump vec4 v_coords;\n";
+		frag << "varying mediump vec4 v_coords;\n";
+	}
+
+	if (writeAccess == INDEXACCESS_DYNAMIC || readAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "uniform mediump int ui_zero";
+		if (matSize >= 2) op << ", ui_one";
+		if (matSize >= 3) op << ", ui_two";
+		if (matSize >= 4) op << ", ui_three";
+		op << ";\n";
+	}
+
+	if (writeAccess == INDEXACCESS_DYNAMIC_LOOP || readAccess == INDEXACCESS_DYNAMIC_LOOP)
+		op << "uniform mediump int " << matSizeName << ";\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Write matrix.
+	if (isVertexCase)
+		op << "	${PRECISION} vec4 coords = a_coords;\n";
+	else
+		op << "	${PRECISION} vec4 coords = v_coords;\n";
+
+	op << "	${PRECISION} ${MAT_TYPE} tmp;\n";
+	if (writeAccess == INDEXACCESS_STATIC)
+	{
+		op << "	tmp[0] = ${VEC_TYPE}(coords);\n";
+		if (matSize >= 2) op << "	tmp[1] = ${VEC_TYPE}(coords.yzwx) * 0.5;\n";
+		if (matSize >= 3) op << "	tmp[2] = ${VEC_TYPE}(coords.zwxy) * 0.25;\n";
+		if (matSize >= 4) op << "	tmp[3] = ${VEC_TYPE}(coords.wxyz) * 0.125;\n";
+	}
+	else if (writeAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "	tmp[ui_zero]  = ${VEC_TYPE}(coords);\n";
+		if (matSize >= 2) op << "	tmp[ui_one]   = ${VEC_TYPE}(coords.yzwx) * 0.5;\n";
+		if (matSize >= 3) op << "	tmp[ui_two]   = ${VEC_TYPE}(coords.zwxy) * 0.25;\n";
+		if (matSize >= 4) op << "	tmp[ui_three] = ${VEC_TYPE}(coords.wxyz) * 0.125;\n";
+	}
+	else if (writeAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < " << matSize << "; i++)\n";
+		op << "	{\n";
+		op << "		tmp[i] = ${VEC_TYPE}(coords);\n";
+		op << "		coords = coords.yzwx * 0.5;\n";
+		op << "	}\n";
+	}
+	else
+	{
+		DE_ASSERT(writeAccess == INDEXACCESS_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < " << matSizeName << "; i++)\n";
+		op << "	{\n";
+		op << "		tmp[i] = ${VEC_TYPE}(coords);\n";
+		op << "		coords = coords.yzwx * 0.5;\n";
+		op << "	}\n";
+	}
+
+	// Read matrix.
+	op << "	${PRECISION} ${VEC_TYPE} res = ${VEC_TYPE}(0.0);\n";
+	if (readAccess == INDEXACCESS_STATIC)
+	{
+		op << "	res += tmp[0];\n";
+		if (matSize >= 2) op << "	res += tmp[1];\n";
+		if (matSize >= 3) op << "	res += tmp[2];\n";
+		if (matSize >= 4) op << "	res += tmp[3];\n";
+	}
+	else if (readAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "	res += tmp[ui_zero];\n";
+		if (matSize >= 2) op << "	res += tmp[ui_one];\n";
+		if (matSize >= 3) op << "	res += tmp[ui_two];\n";
+		if (matSize >= 4) op << "	res += tmp[ui_three];\n";
+	}
+	else if (readAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < " << matSize << "; i++)\n";
+		op << "		res += tmp[i];\n";
+	}
+	else
+	{
+		DE_ASSERT(readAccess == INDEXACCESS_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < " << matSizeName << "; i++)\n";
+		op << "		res += tmp[i];\n";
+	}
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = vec4(res${PADDING});\n";
+		frag << "	gl_FragColor = v_color;\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	gl_FragColor = vec4(res${PADDING});\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("MAT_TYPE", getDataTypeName(varType)));
+	params.insert(pair<string, string>("VEC_TYPE", getDataTypeName(vecType)));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+
+	if (matSize == 2)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 1.0"));
+	else if (matSize == 3)
+		params.insert(pair<string, string>("PADDING", ", 1.0"));
+	else
+		params.insert(pair<string, string>("PADDING", ""));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	ShaderEvalFunc evalFunc = getMatrixSubscriptEvalFunc(varType);
+	deUint32 requirements = 0;
+
+	if (readAccess == INDEXACCESS_DYNAMIC || writeAccess == INDEXACCESS_DYNAMIC)
+		requirements |= REQUIREMENT_UNIFORM_INDEXING;
+
+	if (readAccess == INDEXACCESS_DYNAMIC_LOOP || writeAccess == INDEXACCESS_DYNAMIC_LOOP)
+		requirements |= (isVertexCase ? REQUIREMENT_VERTEX_UNIFORM_LOOPS : REQUIREMENT_FRAGMENT_UNIFORM_LOOPS) | REQUIREMENT_UNIFORM_INDEXING;
+
+	return new ShaderIndexingCase(context, caseName, description, isVertexCase, varType, evalFunc, requirements, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+// ShaderIndexingTests.
+
+ShaderIndexingTests::ShaderIndexingTests(Context& context)
+	: TestCaseGroup(context, "indexing", "Indexing Tests")
+{
+}
+
+ShaderIndexingTests::~ShaderIndexingTests (void)
+{
+}
+
+void ShaderIndexingTests::init (void)
+{
+	static const ShaderType s_shaderTypes[] =
+	{
+		SHADERTYPE_VERTEX,
+		SHADERTYPE_FRAGMENT
+	};
+
+	static const DataType s_floatAndVecTypes[] =
+	{
+		TYPE_FLOAT,
+		TYPE_FLOAT_VEC2,
+		TYPE_FLOAT_VEC3,
+		TYPE_FLOAT_VEC4
+	};
+
+	// Varying array access cases.
+	{
+		TestCaseGroup* varyingGroup = new TestCaseGroup(m_context, "varying_array", "Varying array access tests.");
+		addChild(varyingGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_floatAndVecTypes); typeNdx++)
+		{
+			DataType varType = s_floatAndVecTypes[typeNdx];
+			for (int vertAccess = 0; vertAccess < INDEXACCESS_LAST; vertAccess++)
+			{
+				for (int fragAccess = 0; fragAccess < INDEXACCESS_LAST; fragAccess++)
+				{
+					const char* vertAccessName = getIndexAccessTypeName((IndexAccessType)vertAccess);
+					const char* fragAccessName = getIndexAccessTypeName((IndexAccessType)fragAccess);
+					string name = string(getDataTypeName(varType)) + "_" + vertAccessName + "_write_" + fragAccessName + "_read";
+					string desc = string("Varying array with ") + vertAccessName + " write in vertex shader and " + fragAccessName + " read in fragment shader.";
+					varyingGroup->addChild(createVaryingArrayCase(m_context, name.c_str(), desc.c_str(), varType, (IndexAccessType)vertAccess, (IndexAccessType)fragAccess));
+				}
+			}
+		}
+	}
+
+	// Uniform array access cases.
+	{
+		TestCaseGroup* uniformGroup = new TestCaseGroup(m_context, "uniform_array", "Uniform array access tests.");
+		addChild(uniformGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_floatAndVecTypes); typeNdx++)
+		{
+			DataType varType = s_floatAndVecTypes[typeNdx];
+			for (int readAccess = 0; readAccess < INDEXACCESS_LAST; readAccess++)
+			{
+				const char* readAccessName = getIndexAccessTypeName((IndexAccessType)readAccess);
+				for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+				{
+					ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+					const char*	shaderTypeName	= getShaderTypeName(shaderType);
+					string		name			= string(getDataTypeName(varType)) + "_" + readAccessName + "_read_" + shaderTypeName;
+					string		desc			= string("Uniform array with ") + readAccessName + " read in " + shaderTypeName + " shader.";
+					bool isVertexCase = ((ShaderType)shaderType == SHADERTYPE_VERTEX);
+					uniformGroup->addChild(createUniformArrayCase(m_context, name.c_str(), desc.c_str(), isVertexCase, varType, (IndexAccessType)readAccess));
+				}
+			}
+		}
+	}
+
+	// Temporary array access cases.
+	{
+		TestCaseGroup* tmpGroup = new TestCaseGroup(m_context, "tmp_array", "Temporary array access tests.");
+		addChild(tmpGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_floatAndVecTypes); typeNdx++)
+		{
+			DataType varType = s_floatAndVecTypes[typeNdx];
+			for (int writeAccess = 0; writeAccess < INDEXACCESS_LAST; writeAccess++)
+			{
+				for (int readAccess = 0; readAccess < INDEXACCESS_LAST; readAccess++)
+				{
+					const char* writeAccessName = getIndexAccessTypeName((IndexAccessType)writeAccess);
+					const char* readAccessName = getIndexAccessTypeName((IndexAccessType)readAccess);
+
+					for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+					{
+						ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+						const char* shaderTypeName	= getShaderTypeName(shaderType);
+						string		name			= string(getDataTypeName(varType)) + "_" + writeAccessName + "_write_" + readAccessName + "_read_" + shaderTypeName;
+						string		desc			= string("Temporary array with ") + writeAccessName + " write and " + readAccessName + " read in " + shaderTypeName + " shader.";
+						bool		isVertexCase	= ((ShaderType)shaderType == SHADERTYPE_VERTEX);
+						tmpGroup->addChild(createTmpArrayCase(m_context, name.c_str(), desc.c_str(), isVertexCase, varType, (IndexAccessType)writeAccess, (IndexAccessType)readAccess));
+					}
+				}
+			}
+		}
+	}
+
+	// Vector indexing with subscripts.
+	{
+		TestCaseGroup* vecGroup = new TestCaseGroup(m_context, "vector_subscript", "Vector subscript indexing.");
+		addChild(vecGroup);
+
+		static const DataType s_vectorTypes[] =
+		{
+			TYPE_FLOAT_VEC2,
+			TYPE_FLOAT_VEC3,
+			TYPE_FLOAT_VEC4
+		};
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_vectorTypes); typeNdx++)
+		{
+			DataType varType = s_vectorTypes[typeNdx];
+			for (int writeAccess = 0; writeAccess < VECTORACCESS_LAST; writeAccess++)
+			{
+				for (int readAccess = 0; readAccess < VECTORACCESS_LAST; readAccess++)
+				{
+					const char* writeAccessName = getVectorAccessTypeName((VectorAccessType)writeAccess);
+					const char* readAccessName = getVectorAccessTypeName((VectorAccessType)readAccess);
+
+					for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+					{
+						ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+						const char* shaderTypeName	= getShaderTypeName(shaderType);
+						string		name			= string(getDataTypeName(varType)) + "_" + writeAccessName + "_write_" + readAccessName + "_read_" + shaderTypeName;
+						string		desc			= string("Vector subscript access with ") + writeAccessName + " write and " + readAccessName + " read in " + shaderTypeName + " shader.";
+						bool		isVertexCase	= ((ShaderType)shaderType == SHADERTYPE_VERTEX);
+						vecGroup->addChild(createVectorSubscriptCase(m_context, name.c_str(), desc.c_str(), isVertexCase, varType, (VectorAccessType)writeAccess, (VectorAccessType)readAccess));
+					}
+				}
+			}
+		}
+	}
+
+	// Matrix indexing with subscripts.
+	{
+		TestCaseGroup* matGroup = new TestCaseGroup(m_context, "matrix_subscript", "Matrix subscript indexing.");
+		addChild(matGroup);
+
+		static const DataType s_matrixTypes[] =
+		{
+			TYPE_FLOAT_MAT2,
+			TYPE_FLOAT_MAT3,
+			TYPE_FLOAT_MAT4
+		};
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_matrixTypes); typeNdx++)
+		{
+			DataType varType = s_matrixTypes[typeNdx];
+			for (int writeAccess = 0; writeAccess < INDEXACCESS_LAST; writeAccess++)
+			{
+				for (int readAccess = 0; readAccess < INDEXACCESS_LAST; readAccess++)
+				{
+					const char* writeAccessName = getIndexAccessTypeName((IndexAccessType)writeAccess);
+					const char* readAccessName = getIndexAccessTypeName((IndexAccessType)readAccess);
+
+					for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+					{
+						ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+						const char* shaderTypeName	= getShaderTypeName(shaderType);
+						string		name			= string(getDataTypeName(varType)) + "_" + writeAccessName + "_write_" + readAccessName + "_read_" + shaderTypeName;
+						string		desc			= string("Vector subscript access with ") + writeAccessName + " write and " + readAccessName + " read in " + shaderTypeName + " shader.";
+						bool		isVertexCase	= ((ShaderType)shaderType == SHADERTYPE_VERTEX);
+						matGroup->addChild(createMatrixSubscriptCase(m_context, name.c_str(), desc.c_str(), isVertexCase, varType, (IndexAccessType)writeAccess, (IndexAccessType)readAccess));
+					}
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderIndexingTests.hpp b/modules/gles2/functional/es2fShaderIndexingTests.hpp
new file mode 100644
index 0000000..12405f8
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderIndexingTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERINDEXINGTESTS_HPP
+#define _ES2FSHADERINDEXINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader indexing (arrays, vector, matrices) tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderIndexingTests : public TestCaseGroup
+{
+public:
+							ShaderIndexingTests		(Context& context);
+	virtual					~ShaderIndexingTests	(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderIndexingTests		(const ShaderIndexingTests&);		// not allowed!
+	ShaderIndexingTests&	operator=				(const ShaderIndexingTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERINDEXINGTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderInvarianceTests.cpp b/modules/gles2/functional/es2fShaderInvarianceTests.cpp
new file mode 100644
index 0000000..d404ba6
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderInvarianceTests.cpp
@@ -0,0 +1,859 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Invariance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderInvarianceTests.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuStringTemplate.hpp"
+
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace
+{
+
+class FormatArgumentList;
+
+static tcu::Vec4 genRandomVector (de::Random& rnd)
+{
+	tcu::Vec4 retVal;
+
+	retVal.x() = rnd.getFloat(-1.0f, 1.0f);
+	retVal.y() = rnd.getFloat(-1.0f, 1.0f);
+	retVal.z() = rnd.getFloat(-1.0f, 1.0f);
+	retVal.w() = rnd.getFloat( 0.2f, 1.0f);
+
+	return retVal;
+}
+
+class FormatArgument
+{
+public:
+						FormatArgument (const char* name, const std::string& value);
+
+private:
+	friend class FormatArgumentList;
+
+	const char* const	m_name;
+	const std::string	m_value;
+};
+
+FormatArgument::FormatArgument (const char* name, const std::string& value)
+	: m_name	(name)
+	, m_value	(value)
+{
+}
+
+class FormatArgumentList
+{
+public:
+												FormatArgumentList	(void);
+
+	FormatArgumentList&							operator<<			(const FormatArgument&);
+	const std::map<std::string, std::string>&	getArguments		(void) const;
+
+private:
+	std::map<std::string, std::string>			m_formatArguments;
+};
+
+FormatArgumentList::FormatArgumentList (void)
+{
+}
+
+FormatArgumentList&	FormatArgumentList::operator<< (const FormatArgument& arg)
+{
+	m_formatArguments[arg.m_name] = arg.m_value;
+	return *this;
+}
+
+const std::map<std::string, std::string>& FormatArgumentList::getArguments (void) const
+{
+	return m_formatArguments;
+}
+
+static std::string formatGLSL (const char* templateString, const FormatArgumentList& args)
+{
+	const std::map<std::string, std::string>& params = args.getArguments();
+
+	return tcu::StringTemplate(std::string(templateString)).specialize(params);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Vertex shader invariance test
+ *
+ * Test vertex shader invariance by drawing a test pattern two times, each
+ * time with a different shader. Shaders have set identical values to
+ * invariant gl_Position using identical expressions. No fragments from the
+ * first pass using should remain visible.
+ *//*--------------------------------------------------------------------*/
+class InvarianceTest : public TestCase
+{
+public:
+	struct ShaderPair
+	{
+		std::string vertexShaderSource0;
+		std::string fragmentShaderSource0;
+		std::string vertexShaderSource1;
+		std::string fragmentShaderSource1;
+	};
+
+							InvarianceTest		(Context& ctx, const char* name, const char* desc);
+							~InvarianceTest		(void);
+
+	void					init				(void);
+	void					deinit				(void);
+	IterateResult			iterate				(void);
+
+private:
+	virtual ShaderPair		genShaders			(void) const = DE_NULL;
+	bool					checkImage			(const tcu::Surface&) const;
+
+	glu::ShaderProgram*		m_shader0;
+	glu::ShaderProgram*		m_shader1;
+	glw::GLuint				m_arrayBuf;
+	int						m_verticesInPattern;
+
+	const int				m_renderSize;
+};
+
+InvarianceTest::InvarianceTest (Context& ctx, const char* name, const char* desc)
+	: TestCase				(ctx, name, desc)
+	, m_shader0				(DE_NULL)
+	, m_shader1				(DE_NULL)
+	, m_arrayBuf			(0)
+	, m_verticesInPattern	(0)
+	, m_renderSize			(256)
+{
+}
+
+InvarianceTest::~InvarianceTest (void)
+{
+	deinit();
+}
+
+void InvarianceTest::init (void)
+{
+	// Invariance tests require drawing to the screen and reading back results.
+	// Tests results are not reliable if the resolution is too small
+	{
+		if (m_context.getRenderTarget().getWidth()  < m_renderSize ||
+			m_context.getRenderTarget().getHeight() < m_renderSize)
+			throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(m_renderSize) + "x" + de::toString(m_renderSize));
+	}
+
+	// Gen shaders
+	{
+		ShaderPair vertexShaders = genShaders();
+
+		m_shader0 = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaders.vertexShaderSource0) << glu::FragmentSource(vertexShaders.fragmentShaderSource0));
+		if (!m_shader0->isOk())
+		{
+			m_testCtx.getLog() << *m_shader0;
+			throw tcu::TestError("Test shader compile failed.");
+		}
+
+		m_shader1 = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaders.vertexShaderSource1) << glu::FragmentSource(vertexShaders.fragmentShaderSource1));
+		if (!m_shader1->isOk())
+		{
+			m_testCtx.getLog() << *m_shader1;
+			throw tcu::TestError("Test shader compile failed.");
+		}
+
+		// log
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message << "Shader 1:" << tcu::TestLog::EndMessage
+			<< *m_shader0
+			<< tcu::TestLog::Message << "Shader 2:" << tcu::TestLog::EndMessage
+			<< *m_shader1;
+	}
+
+	// Gen test pattern
+	{
+		const int				numTriangles	= 72;
+		de::Random				rnd				(123);
+		std::vector<tcu::Vec4>	triangles		(numTriangles * 3 * 2);
+		const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+
+		// Narrow triangle pattern
+		for (int triNdx = 0; triNdx < numTriangles; ++triNdx)
+		{
+			const tcu::Vec4 vertex1 = genRandomVector(rnd);
+			const tcu::Vec4 vertex2 = genRandomVector(rnd);
+			const tcu::Vec4 vertex3 = vertex2 + genRandomVector(rnd) * 0.01f; // generate narrow triangles
+
+			triangles[triNdx*3 + 0] = vertex1;
+			triangles[triNdx*3 + 1] = vertex2;
+			triangles[triNdx*3 + 2] = vertex3;
+		}
+
+		// Normal triangle pattern
+		for (int triNdx = 0; triNdx < numTriangles; ++triNdx)
+		{
+			triangles[(numTriangles + triNdx)*3 + 0] = genRandomVector(rnd);
+			triangles[(numTriangles + triNdx)*3 + 1] = genRandomVector(rnd);
+			triangles[(numTriangles + triNdx)*3 + 2] = genRandomVector(rnd);
+		}
+
+		// upload
+		gl.genBuffers(1, &m_arrayBuf);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuf);
+		gl.bufferData(GL_ARRAY_BUFFER, (int)(triangles.size() * sizeof(tcu::Vec4)), &triangles[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "buffer gen");
+
+		m_verticesInPattern = numTriangles * 3;
+	}
+}
+
+void InvarianceTest::deinit (void)
+{
+	delete m_shader0;
+	delete m_shader1;
+
+	m_shader0 = DE_NULL;
+	m_shader1 = DE_NULL;
+
+	if (m_arrayBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_arrayBuf);
+		m_arrayBuf = 0;
+	}
+}
+
+InvarianceTest::IterateResult InvarianceTest::iterate (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const bool				depthBufferExists	= m_context.getRenderTarget().getDepthBits() != 0;
+	tcu::Surface			resultSurface		(m_renderSize, m_renderSize);
+	bool					error				= false;
+
+	// Prepare draw
+	gl.clearColor		(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear			(GL_COLOR_BUFFER_BIT);
+	gl.viewport			(0, 0, m_renderSize, m_renderSize);
+	gl.bindBuffer		(GL_ARRAY_BUFFER, m_arrayBuf);
+	GLU_EXPECT_NO_ERROR	(gl.getError(), "setup draw");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Testing position invariance." << tcu::TestLog::EndMessage;
+
+	// Draw position check passes
+	for (int passNdx = 0; passNdx < 2; ++passNdx)
+	{
+		const glu::ShaderProgram&	shader		= (passNdx == 0) ? (*m_shader0) : (*m_shader1);
+		const glw::GLint			positionLoc = gl.getAttribLocation(shader.getProgram(), "a_input");
+		const glw::GLint			colorLoc	= gl.getUniformLocation(shader.getProgram(), "u_color");
+		const tcu::Vec4				red			= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+		const tcu::Vec4				green		= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+		const tcu::Vec4				color		= (passNdx == 0) ? (red) : (green);
+		const char* const			colorStr	= (passNdx == 0) ? ("red - purple") : ("green");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing position test pattern using shader " << (passNdx+1) << ". Primitive color: " << colorStr << "." << tcu::TestLog::EndMessage;
+
+		gl.useProgram				(shader.getProgram());
+		gl.uniform4fv				(colorLoc, 1, color.getPtr());
+		gl.enableVertexAttribArray	(positionLoc);
+		gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, sizeof(tcu::Vec4), DE_NULL);
+		gl.drawArrays				(GL_TRIANGLES, 0, m_verticesInPattern);
+		gl.disableVertexAttribArray	(positionLoc);
+		GLU_EXPECT_NO_ERROR			(gl.getError(), "draw pass");
+	}
+
+	// Read result
+	glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+
+	// Check there are no red pixels
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output. Expecting only green or background colored pixels." << tcu::TestLog::EndMessage;
+	error |= !checkImage(resultSurface);
+
+	if (!depthBufferExists)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Depth buffer not available, skipping z-test." << tcu::TestLog::EndMessage;
+	}
+	else
+	{
+		// Test with Z-test
+		gl.clearDepthf		(1.0f);
+		gl.clear			(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.enable			(GL_DEPTH_TEST);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Testing position invariance with z-test. Enabling GL_DEPTH_TEST." << tcu::TestLog::EndMessage;
+
+		// Draw position check passes
+		for (int passNdx = 0; passNdx < 2; ++passNdx)
+		{
+			const glu::ShaderProgram&	shader			= (passNdx == 0) ? (*m_shader0) : (*m_shader1);
+			const glw::GLint			positionLoc		= gl.getAttribLocation(shader.getProgram(), "a_input");
+			const glw::GLint			colorLoc		= gl.getUniformLocation(shader.getProgram(), "u_color");
+			const tcu::Vec4				red				= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+			const tcu::Vec4				green			= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+			const tcu::Vec4				color			= (passNdx == 0) ? (red) : (green);
+			const glw::GLenum			depthFunc		= (passNdx == 0) ? (GL_ALWAYS) : (GL_EQUAL);
+			const char* const			depthFuncStr	= (passNdx == 0) ? ("GL_ALWAYS") : ("GL_EQUAL");
+			const char* const			colorStr		= (passNdx == 0) ? ("red - purple") : ("green");
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Drawing Z-test pattern using shader " << (passNdx+1) << ". Primitive color: " << colorStr << ". DepthFunc: " << depthFuncStr << tcu::TestLog::EndMessage;
+
+			gl.useProgram				(shader.getProgram());
+			gl.uniform4fv				(colorLoc, 1, color.getPtr());
+			gl.depthFunc				(depthFunc);
+			gl.enableVertexAttribArray	(positionLoc);
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, sizeof(tcu::Vec4), DE_NULL);
+			gl.drawArrays				(GL_TRIANGLES, m_verticesInPattern, m_verticesInPattern); // !< buffer contains 2 m_verticesInPattern-sized patterns
+			gl.disableVertexAttribArray	(positionLoc);
+			GLU_EXPECT_NO_ERROR			(gl.getError(), "draw pass");
+		}
+
+		// Read result
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+
+		// Check there are no red pixels
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output. Expecting only green or background colored pixels." << tcu::TestLog::EndMessage;
+		error |= !checkImage(resultSurface);
+	}
+
+	// Report result
+	if (error)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Detected variance between two invariant values");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	return STOP;
+}
+
+bool InvarianceTest::checkImage (const tcu::Surface& surface) const
+{
+	const tcu::IVec4	okColor		= tcu::IVec4(0, 255, 0, 255);
+	const tcu::RGBA		errColor	= tcu::RGBA(255, 0, 0, 255);
+	bool				error		= false;
+	tcu::Surface		errorMask	(m_renderSize, m_renderSize);
+
+	tcu::clear(errorMask.getAccess(), okColor);
+
+	for (int y = 0; y < m_renderSize; ++y)
+	for (int x = 0; x < m_renderSize; ++x)
+	{
+		const tcu::RGBA col = surface.getPixel(x, y);
+
+		if (col.getRed() != 0)
+		{
+			errorMask.setPixel(x, y, errColor);
+			error = true;
+		}
+	}
+
+	// report error
+	if (error)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Invalid pixels found (fragments from first render pass found). Variance detected." << tcu::TestLog::EndMessage;
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Results", "Result verification")
+			<< tcu::TestLog::Image("Result",		"Result",		surface)
+			<< tcu::TestLog::Image("Error mask",	"Error mask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+
+		return false;
+	}
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "No variance found." << tcu::TestLog::EndMessage;
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Results", "Result verification")
+			<< tcu::TestLog::Image("Result", "Result", surface)
+			<< tcu::TestLog::EndImageSet;
+
+		return true;
+	}
+}
+
+class BasicInvarianceTest : public InvarianceTest
+{
+public:
+								BasicInvarianceTest		(Context& ctx, const char* name, const char* desc, const std::string& vertexShader1, const std::string& vertexShader2);
+								BasicInvarianceTest		(Context& ctx, const char* name, const char* desc, const std::string& vertexShader1, const std::string& vertexShader2, const std::string& fragmentShader);
+	ShaderPair					genShaders				(void) const;
+
+private:
+	const std::string			m_vertexShader1;
+	const std::string			m_vertexShader2;
+	const std::string			m_fragmentShader;
+	static const char* const	s_basicFragmentShader;
+};
+
+const char* const BasicInvarianceTest::s_basicFragmentShader =	"uniform mediump vec4 u_color;\n"
+																"varying mediump vec4 v_unrelated;\n"
+																"void main ()\n"
+																"{\n"
+																"	mediump float blue = dot(v_unrelated, vec4(1.0, 1.0, 1.0, 1.0));\n"
+																"	gl_FragColor = vec4(u_color.r, u_color.g, blue, u_color.a);\n"
+																"}\n";
+
+BasicInvarianceTest::BasicInvarianceTest (Context& ctx, const char* name, const char* desc, const std::string& vertexShader1, const std::string& vertexShader2)
+	: InvarianceTest	(ctx, name, desc)
+	, m_vertexShader1	(vertexShader1)
+	, m_vertexShader2	(vertexShader2)
+	, m_fragmentShader	(s_basicFragmentShader)
+{
+}
+
+BasicInvarianceTest::BasicInvarianceTest (Context& ctx, const char* name, const char* desc, const std::string& vertexShader1, const std::string& vertexShader2, const std::string& fragmentShader)
+	: InvarianceTest	(ctx, name, desc)
+	, m_vertexShader1	(vertexShader1)
+	, m_vertexShader2	(vertexShader2)
+	, m_fragmentShader	(fragmentShader)
+{
+}
+
+BasicInvarianceTest::ShaderPair BasicInvarianceTest::genShaders (void) const
+{
+	ShaderPair retVal;
+
+	retVal.vertexShaderSource0 = m_vertexShader1;
+	retVal.vertexShaderSource1 = m_vertexShader2;
+	retVal.fragmentShaderSource0 = m_fragmentShader;
+	retVal.fragmentShaderSource1 = m_fragmentShader;
+
+	return retVal;
+}
+
+} // anonymous
+
+ShaderInvarianceTests::ShaderInvarianceTests (Context& context)
+	: TestCaseGroup(context, "invariance", "Invariance tests")
+{
+}
+
+ShaderInvarianceTests::~ShaderInvarianceTests (void)
+{
+}
+
+void ShaderInvarianceTests::init (void)
+{
+	static const struct PrecisionCase
+	{
+		glu::Precision	prec;
+		const char*		name;
+
+		// set literals in the glsl to be in the representable range
+		const char*		highValue;		// !< highValue < maxValue
+		const char*		invHighValue;
+		const char*		mediumValue;	// !< mediumValue^2 < maxValue
+		const char*		lowValue;		// !< lowValue^4 < maxValue
+		const char*		invlowValue;
+		int				loopIterations;
+		int				loopPartialIterations;
+		int				loopNormalizationExponent;
+		const char*		loopNormalizationConstantLiteral;
+		const char*		loopMultiplier;
+		const char*		sumLoopNormalizationConstantLiteral;
+	} precisions[] =
+	{
+		{ glu::PRECISION_HIGHP,		"highp",	"1.0e20",	"1.0e-20",	"1.0e14",	"1.0e9",	"1.0e-9",	14,	11,	2,	"1.0e4",	"1.9",	"1.0e3"	},
+		{ glu::PRECISION_MEDIUMP,	"mediump",	"1.0e4",	"1.0e-4",	"1.0e2",	"1.0e1",	"1.0e-1",	13,	11,	2,	"1.0e4",	"1.9",	"1.0e3"	},
+		{ glu::PRECISION_LOWP,		"lowp",		"0.9",		"1.1",		"1.1",		"1.15",		"0.87",		6,	2,	0,	"2.0",		"1.1",	"1.0"	},
+	};
+
+	for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); ++precNdx)
+	{
+		const char* const			precisionName	= precisions[precNdx].name;
+		const glu::Precision		precision		= precisions[precNdx].prec;
+		tcu::TestCaseGroup* const	group			= new tcu::TestCaseGroup(m_testCtx, precisionName, "Invariance tests using the given precision.");
+
+		const FormatArgumentList	args			= FormatArgumentList()
+														<< FormatArgument("VERSION",				"")
+														<< FormatArgument("IN",						"attribute")
+														<< FormatArgument("OUT",					"varying")
+														<< FormatArgument("IN_PREC",				precisionName)
+														<< FormatArgument("HIGH_VALUE",				de::toString(precisions[precNdx].highValue))
+														<< FormatArgument("HIGH_VALUE_INV",			de::toString(precisions[precNdx].invHighValue))
+														<< FormatArgument("MEDIUM_VALUE",			de::toString(precisions[precNdx].mediumValue))
+														<< FormatArgument("LOW_VALUE",				de::toString(precisions[precNdx].lowValue))
+														<< FormatArgument("LOW_VALUE_INV",			de::toString(precisions[precNdx].invlowValue))
+														<< FormatArgument("LOOP_ITERS",				de::toString(precisions[precNdx].loopIterations))
+														<< FormatArgument("LOOP_ITERS_PARTIAL",		de::toString(precisions[precNdx].loopPartialIterations))
+														<< FormatArgument("LOOP_NORM_FRACT_EXP",	de::toString(precisions[precNdx].loopNormalizationExponent))
+														<< FormatArgument("LOOP_NORM_LITERAL",		precisions[precNdx].loopNormalizationConstantLiteral)
+														<< FormatArgument("LOOP_MULTIPLIER",		precisions[precNdx].loopMultiplier)
+														<< FormatArgument("SUM_LOOP_NORM_LITERAL",	precisions[precNdx].sumLoopNormalizationConstantLiteral);
+
+		addChild(group);
+
+		// subexpression cases
+		{
+			// First shader shares "${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy" with unrelated output variable. Reordering might result in accuracy loss
+			// due to the high exponent. In the second shader, the high exponent may be removed during compilation.
+
+			group->addChild(new BasicInvarianceTest(m_context, "common_subexpression_0", "Shader shares a subexpression with an unrelated variable.",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	v_unrelated = a_input.xzxz + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * (1.08 * a_input.zyzy * a_input.xzxz) * ${HIGH_VALUE_INV} * (a_input.z * a_input.zzxz - a_input.z * a_input.zzxz) + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) / ${HIGH_VALUE};\n"
+							"	gl_Position = a_input + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * ${HIGH_VALUE_INV};\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	gl_Position = a_input + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * ${HIGH_VALUE_INV};\n"
+							"}\n", args)));
+
+			// In the first shader, the unrelated variable "d" has mathematically the same expression as "e", but the different
+			// order of calculation might cause different results.
+
+			group->addChild(new BasicInvarianceTest(m_context, "common_subexpression_1", "Shader shares a subexpression with an unrelated variable.",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 a = ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy - ${HIGH_VALUE} * a_input.zzxx;\n"
+							"	${IN_PREC} vec4 b = ${HIGH_VALUE} * a_input.zzxx;\n"
+							"	${IN_PREC} vec4 c = b - ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy;\n"
+							"	${IN_PREC} vec4 d = (${LOW_VALUE} * a_input.yzxx) * (${LOW_VALUE} * a_input.yzzw) * (1.1*${LOW_VALUE_INV} * a_input.yzxx) * (${LOW_VALUE_INV} * a_input.xzzy);\n"
+							"	${IN_PREC} vec4 e = ((${LOW_VALUE} * a_input.yzxx) * (1.1*${LOW_VALUE_INV} * a_input.yzxx)) * ((${LOW_VALUE_INV} * a_input.xzzy) * (${LOW_VALUE} * a_input.yzzw));\n"
+							"	v_unrelated = a + b + c + d + e;\n"
+							"	gl_Position = a_input + fract(c) + e;\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 b = ${HIGH_VALUE} * a_input.zzxx;\n"
+							"	${IN_PREC} vec4 c = b - ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy;\n"
+							"	${IN_PREC} vec4 e = ((${LOW_VALUE} * a_input.yzxx) * (1.1*${LOW_VALUE_INV} * a_input.yzxx)) * ((${LOW_VALUE_INV} * a_input.xzzy) * (${LOW_VALUE} * a_input.yzzw));\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	gl_Position = a_input + fract(c) + e;\n"
+							"}\n", args)));
+
+			// Intermediate values used by an unrelated output variable
+
+			group->addChild(new BasicInvarianceTest(m_context, "common_subexpression_2", "Shader shares a subexpression with an unrelated variable.",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 a = ${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy);\n"
+							"	${IN_PREC} vec4 b = (${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy)) * (${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy)) / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n"
+							"	${IN_PREC} vec4 c = a * a;\n"
+							"	${IN_PREC} vec4 d = c / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n"
+							"	v_unrelated = a + b + c + d;\n"
+							"	gl_Position = a_input + d;\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 a = ${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy);\n"
+							"	${IN_PREC} vec4 c = a * a;\n"
+							"	${IN_PREC} vec4 d = c / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	gl_Position = a_input + d;\n"
+							"}\n", args)));
+
+			// Invariant value can be calculated using unrelated value
+
+			group->addChild(new BasicInvarianceTest(m_context, "common_subexpression_3", "Shader shares a subexpression with an unrelated variable.",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} float x = a_input.x * 0.2;\n"
+							"	${IN_PREC} vec4 a = a_input.xxyx * 0.7;\n"
+							"	${IN_PREC} vec4 b = a_input.yxyz * 0.7;\n"
+							"	${IN_PREC} vec4 c = a_input.zxyx * 0.5;\n"
+							"	${IN_PREC} vec4 f = x*a + x*b + x*c;\n"
+							"	v_unrelated = f;\n"
+							"	${IN_PREC} vec4 g = x * (a + b + c);\n"
+							"	gl_Position = a_input + g;\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} float x = a_input.x * 0.2;\n"
+							"	${IN_PREC} vec4 a = a_input.xxyx * 0.7;\n"
+							"	${IN_PREC} vec4 b = a_input.yxyz * 0.7;\n"
+							"	${IN_PREC} vec4 c = a_input.zxyx * 0.5;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	${IN_PREC} vec4 g = x * (a + b + c);\n"
+							"	gl_Position = a_input + g;\n"
+							"}\n", args)));
+		}
+
+		// shared subexpression of different precision
+		{
+			for (int precisionOther = glu::PRECISION_LOWP; precisionOther != glu::PRECISION_LAST; ++precisionOther)
+			{
+				const char* const		unrelatedPrec				= glu::getPrecisionName((glu::Precision)precisionOther);
+				const glu::Precision	minPrecision				= (precisionOther < (int)precision) ? ((glu::Precision)precisionOther) : (precision);
+				const char* const		multiplierStr				= (minPrecision == glu::PRECISION_LOWP) ? ("0.8, 0.4, -0.2, 0.3") : ("1.0e1, 5.0e2, 2.0e2, 1.0");
+				const char* const		normalizationStrUsed		= (minPrecision == glu::PRECISION_LOWP) ? ("vec4(fract(used2).xyz, 0.0)") : ("vec4(fract(used2 / 1.0e2).xyz - fract(used2 / 1.0e3).xyz, 0.0)");
+				const char* const		normalizationStrUnrelated	= (minPrecision == glu::PRECISION_LOWP) ? ("vec4(fract(unrelated2).xyz, 0.0)") : ("vec4(fract(unrelated2 / 1.0e2).xyz - fract(unrelated2 / 1.0e3).xyz, 0.0)");
+
+				group->addChild(new BasicInvarianceTest(m_context, ("subexpression_precision_" + std::string(unrelatedPrec)).c_str(), "Shader shares subexpression of different precision with an unrelated variable.",
+					formatGLSL(	"${VERSION}"
+								"${IN} ${IN_PREC} vec4 a_input;\n"
+								"${OUT} ${UNRELATED_PREC} vec4 v_unrelated;\n"
+								"invariant gl_Position;\n"
+								"void main ()\n"
+								"{\n"
+								"	${UNRELATED_PREC} vec4 unrelated0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n"
+								"	${UNRELATED_PREC} vec4 unrelated1 = vec4(${MULTIPLIER}) * unrelated0.xywz + unrelated0;\n"
+								"	${UNRELATED_PREC} vec4 unrelated2 = refract(unrelated1, unrelated0, distance(unrelated0, unrelated1));\n"
+								"	v_unrelated = a_input + 0.02 * ${NORMALIZE_UNRELATED};\n"
+								"	${IN_PREC} vec4 used0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n"
+								"	${IN_PREC} vec4 used1 = vec4(${MULTIPLIER}) * used0.xywz + used0;\n"
+								"	${IN_PREC} vec4 used2 = refract(used1, used0, distance(used0, used1));\n"
+								"	gl_Position = a_input + 0.02 * ${NORMALIZE_USED};\n"
+								"}\n", FormatArgumentList(args)
+											<< FormatArgument("UNRELATED_PREC",			unrelatedPrec)
+											<< FormatArgument("MULTIPLIER",				multiplierStr)
+											<< FormatArgument("NORMALIZE_USED",			normalizationStrUsed)
+											<< FormatArgument("NORMALIZE_UNRELATED",	normalizationStrUnrelated)),
+					formatGLSL(	"${VERSION}"
+								"${IN} ${IN_PREC} vec4 a_input;\n"
+								"${OUT} ${UNRELATED_PREC} vec4 v_unrelated;\n"
+								"invariant gl_Position;\n"
+								"void main ()\n"
+								"{\n"
+								"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+								"	${IN_PREC} vec4 used0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n"
+								"	${IN_PREC} vec4 used1 = vec4(${MULTIPLIER}) * used0.xywz + used0;\n"
+								"	${IN_PREC} vec4 used2 = refract(used1, used0, distance(used0, used1));\n"
+								"	gl_Position = a_input + 0.02 * ${NORMALIZE_USED};\n"
+								"}\n", FormatArgumentList(args)
+											<< FormatArgument("UNRELATED_PREC",			unrelatedPrec)
+											<< FormatArgument("MULTIPLIER",				multiplierStr)
+											<< FormatArgument("NORMALIZE_USED",			normalizationStrUsed)
+											<< FormatArgument("NORMALIZE_UNRELATED",	normalizationStrUnrelated))));
+			}
+		}
+
+		// loops
+		{
+			group->addChild(new BasicInvarianceTest(m_context, "loop_0", "Invariant value set using a loop",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} highp vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		v_unrelated += value;\n"
+							"	}\n"
+							"	gl_Position = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} highp vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"	}\n"
+							"	gl_Position = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"}\n", args)));
+
+			group->addChild(new BasicInvarianceTest(m_context, "loop_1", "Invariant value set using a loop",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		if (i == ${LOOP_ITERS_PARTIAL})\n"
+							"			v_unrelated = value;\n"
+							"	}\n"
+							"	gl_Position = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"	}\n"
+							"	gl_Position = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"}\n", args)));
+
+			group->addChild(new BasicInvarianceTest(m_context, "loop_2", "Invariant value set using a loop",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, -1.0, 1.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		if (i == ${LOOP_ITERS_PARTIAL})\n"
+							"			gl_Position = a_input + 0.05 * vec4(fract(value.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
+							"		else\n"
+							"			v_unrelated = value + a_input;\n"
+							"	}\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, -1.0, 1.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		if (i == ${LOOP_ITERS_PARTIAL})\n"
+							"			gl_Position = a_input + 0.05 * vec4(fract(value.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
+							"		else\n"
+							"			v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	}\n"
+							"}\n", args)));
+
+			group->addChild(new BasicInvarianceTest(m_context, "loop_3", "Invariant value set using a loop",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		gl_Position += vec4(value.xyz / ${SUM_LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"		v_unrelated = gl_Position.xyzx * a_input;\n"
+							"	}\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		gl_Position += vec4(value.xyz / ${SUM_LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"	}\n"
+							"}\n", args)));
+
+			group->addChild(new BasicInvarianceTest(m_context, "loop_4", "Invariant value set using a loop",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	${IN_PREC} vec4 value1 = a_input;\n"
+							"	${IN_PREC} vec4 value2 = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value1 *= ${LOOP_MULTIPLIER};\n"
+							"		v_unrelated = v_unrelated*1.3 + a_input.xyzx * value1.xyxw;\n"
+							"	}\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value2 *= ${LOOP_MULTIPLIER};\n"
+							"		position = position*1.3 + a_input.xyzx * value2.xyxw;\n"
+							"	}\n"
+							"	gl_Position = a_input + 0.05 * vec4(fract(position.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	${IN_PREC} vec4 value2 = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value2 *= ${LOOP_MULTIPLIER};\n"
+							"		position = position*1.3 + a_input.xyzx * value2.xyxw;\n"
+							"	}\n"
+							"	gl_Position = a_input + 0.05 * vec4(fract(position.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
+							"}\n", args)));
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderInvarianceTests.hpp b/modules/gles2/functional/es2fShaderInvarianceTests.hpp
new file mode 100644
index 0000000..e214db9
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderInvarianceTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FSHADERINVARIANCETESTS_HPP
+#define _ES2FSHADERINVARIANCETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Invariance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderInvarianceTests : public TestCaseGroup
+{
+public:
+							ShaderInvarianceTests	(Context& context);
+							~ShaderInvarianceTests	(void);
+
+	void					init					(void);
+
+private:
+							ShaderInvarianceTests	(const ShaderInvarianceTests& other);
+	ShaderInvarianceTests&	operator=				(const ShaderInvarianceTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERINVARIANCETESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderLoopTests.cpp b/modules/gles2/functional/es2fShaderLoopTests.cpp
new file mode 100644
index 0000000..8da0b44
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderLoopTests.cpp
@@ -0,0 +1,1343 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader loop tests.
+ *
+ * \todo [petri]
+ * - loop body cases (do different operations inside the loops)
+ * - more complex nested loops
+ *   * random generated?
+ *   * dataflow variations
+ *   * mixed loop types
+ * -
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderLoopTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuStringTemplate.hpp"
+
+#include "deStringUtil.hpp"
+#include "deInt32.h"
+#include "deMemory.h"
+
+#include <map>
+
+using namespace std;
+using namespace tcu;
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+// Repeated with for, while, do-while. Examples given as 'for' loops.
+// Repeated for const, uniform, dynamic loops.
+enum LoopCase
+{
+	LOOPCASE_EMPTY_BODY = 0,								// for (...) { }
+	LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST,		// for (...) { break; <body>; }
+	LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST,		// for (...) { <body>; break; }
+	LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK,				// for (...) { <body>; if (cond) break; }
+	LOOPCASE_SINGLE_STATEMENT,								// for (...) statement;
+	LOOPCASE_COMPOUND_STATEMENT,							// for (...) { statement; statement; }
+	LOOPCASE_SEQUENCE_STATEMENT,							// for (...) statement, statement;
+	LOOPCASE_NO_ITERATIONS,									// for (i=0; i<0; i++) ...
+	LOOPCASE_SINGLE_ITERATION,								// for (i=0; i<1; i++) ...
+	LOOPCASE_SELECT_ITERATION_COUNT,						// for (i=0; i<a?b:c; i++) ...
+	LOOPCASE_CONDITIONAL_CONTINUE,							// for (...) { if (cond) continue; }
+	LOOPCASE_UNCONDITIONAL_CONTINUE,						// for (...) { <body>; continue; }
+	LOOPCASE_ONLY_CONTINUE,									// for (...) { continue; }
+	LOOPCASE_DOUBLE_CONTINUE,								// for (...) { if (cond) continue; <body>; continue; }
+	LOOPCASE_CONDITIONAL_BREAK,								// for (...) { if (cond) break; }
+	LOOPCASE_UNCONDITIONAL_BREAK,							// for (...) { <body>; break; }
+	LOOPCASE_PRE_INCREMENT,									// for (...; ++i) { <body>; }
+	LOOPCASE_POST_INCREMENT,								// for (...; i++) { <body>; }
+	LOOPCASE_MIXED_BREAK_CONTINUE,
+	LOOPCASE_VECTOR_COUNTER,								// for (ivec3 ndx = ...; ndx.x < ndx.y; ndx.x += ndx.z) { ... }
+	LOOPCASE_101_ITERATIONS,								// loop for 101 iterations
+	LOOPCASE_SEQUENCE,										// two loops in sequence
+	LOOPCASE_NESTED,										// two nested loops
+	LOOPCASE_NESTED_SEQUENCE,								// two loops in sequence nested inside a third
+	LOOPCASE_NESTED_TRICKY_DATAFLOW_1,						// nested loops with tricky data flow
+	LOOPCASE_NESTED_TRICKY_DATAFLOW_2,						// nested loops with tricky data flow
+	LOOPCASE_CONDITIONAL_BODY,								// conditional body in loop
+	LOOPCASE_FUNCTION_CALL_RETURN,							// function call in loop with return value usage
+	LOOPCASE_FUNCTION_CALL_INOUT,							// function call with inout parameter usage
+
+	LOOPCASE_LAST
+};
+
+enum LoopRequirement
+{
+	LOOPREQUIREMENT_STANDARD = 0,		//!< Minimum requirements by standard (constant for loop with simple iterator).
+	LOOPREQUIREMENT_UNIFORM,
+	LOOPREQUIREMENT_DYNAMIC,
+
+	LOOPREQUIREMENT_LAST
+};
+
+static const char* getLoopCaseName (LoopCase loopCase)
+{
+	static const char* s_names[] =
+	{
+		"empty_body",
+		"infinite_with_unconditional_break_first",
+		"infinite_with_unconditional_break_last",
+		"infinite_with_conditional_break",
+		"single_statement",
+		"compound_statement",
+		"sequence_statement",
+		"no_iterations",
+		"single_iteration",
+		"select_iteration_count",
+		"conditional_continue",
+		"unconditional_continue",
+		"only_continue",
+		"double_continue",
+		"conditional_break",
+		"unconditional_break",
+		"pre_increment",
+		"post_increment",
+		"mixed_break_continue",
+		"vector_counter",
+		"101_iterations",
+		"sequence",
+		"nested",
+		"nested_sequence",
+		"nested_tricky_dataflow_1",
+		"nested_tricky_dataflow_2",
+		"conditional_body",
+		"function_call_return",
+		"function_call_inout"
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == LOOPCASE_LAST);
+	DE_ASSERT(deInBounds32((int)loopCase, 0, LOOPCASE_LAST));
+	return s_names[(int)loopCase];
+}
+
+enum LoopType
+{
+	LOOPTYPE_FOR = 0,
+	LOOPTYPE_WHILE,
+	LOOPTYPE_DO_WHILE,
+
+	LOOPTYPE_LAST
+};
+
+static const char* getLoopTypeName (LoopType loopType)
+{
+	static const char* s_names[] =
+	{
+		"for",
+		"while",
+		"do_while"
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == LOOPTYPE_LAST);
+	DE_ASSERT(deInBounds32((int)loopType, 0, LOOPTYPE_LAST));
+	return s_names[(int)loopType];
+}
+
+enum LoopCountType
+{
+	LOOPCOUNT_CONSTANT = 0,
+	LOOPCOUNT_UNIFORM,
+	LOOPCOUNT_DYNAMIC,
+
+	LOOPCOUNT_LAST
+};
+
+static const char* getLoopCountTypeName (LoopCountType countType)
+{
+	static const char* s_names[] =
+	{
+		"constant",
+		"uniform",
+		"dynamic"
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == LOOPCOUNT_LAST);
+	DE_ASSERT(deInBounds32((int)countType, 0, LOOPCOUNT_LAST));
+	return s_names[(int)countType];
+}
+
+static void evalLoop0Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(0,1,2); }
+static void evalLoop1Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(1,2,3); }
+static void evalLoop2Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(2,3,0); }
+static void evalLoop3Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(3,0,1); }
+
+static ShaderEvalFunc getLoopEvalFunc (int numIters)
+{
+	switch (numIters % 4)
+	{
+		case 0: return evalLoop0Iters;
+		case 1:	return evalLoop1Iters;
+		case 2:	return evalLoop2Iters;
+		case 3:	return evalLoop3Iters;
+	}
+
+	DE_ASSERT(!"Invalid loop iteration count.");
+	return NULL;
+}
+
+// ShaderLoopCase
+
+class ShaderLoopCase : public ShaderRenderCase
+{
+public:
+								ShaderLoopCase			(Context& context, const char* name, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, LoopRequirement requirement, const char* vertShaderSource, const char* fragShaderSource);
+	virtual						~ShaderLoopCase			(void);
+
+	void						init					(void);
+
+private:
+								ShaderLoopCase			(const ShaderLoopCase&);	// not allowed!
+	ShaderLoopCase&				operator=				(const ShaderLoopCase&);	// not allowed!
+
+	virtual void				setup					(int programID);
+	virtual void				setupUniforms			(int programID, const Vec4& constCoords);
+
+	LoopRequirement				m_requirement;
+};
+
+ShaderLoopCase::ShaderLoopCase (Context& context, const char* name, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, LoopRequirement requirement, const char* vertShaderSource, const char* fragShaderSource)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
+	, m_requirement		(requirement)
+{
+	m_vertShaderSource	= vertShaderSource;
+	m_fragShaderSource	= fragShaderSource;
+}
+
+ShaderLoopCase::~ShaderLoopCase (void)
+{
+}
+
+void ShaderLoopCase::init (void)
+{
+	bool isSupported = true;
+
+	if (m_requirement == LOOPREQUIREMENT_UNIFORM)
+		isSupported = m_isVertexCase ? m_ctxInfo.isVertexUniformLoopSupported()
+									 : m_ctxInfo.isFragmentUniformLoopSupported();
+	else if (m_requirement == LOOPREQUIREMENT_DYNAMIC)
+		isSupported = m_isVertexCase ? m_ctxInfo.isVertexDynamicLoopSupported()
+									 : m_ctxInfo.isFragmentDynamicLoopSupported();
+
+	try
+	{
+		ShaderRenderCase::init();
+	}
+	catch (const CompileFailed&)
+	{
+		if (!isSupported)
+			throw tcu::NotSupportedError("Loop type is not supported");
+		else
+			throw;
+	}
+}
+
+void ShaderLoopCase::setup (int programID)
+{
+	DE_UNREF(programID);
+}
+
+void ShaderLoopCase::setupUniforms (int programID, const Vec4& constCoords)
+{
+	DE_UNREF(programID);
+	DE_UNREF(constCoords);
+}
+
+// Test case creation.
+
+static ShaderLoopCase* createGenericLoopCase (Context& context, const char* caseName, const char* description, bool isVertexCase, LoopType loopType, LoopCountType loopCountType, Precision loopCountPrecision, DataType loopCountDataType)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	vtx << "attribute highp vec4 a_position;\n";
+	vtx << "attribute highp vec4 a_coords;\n";
+
+	if (loopCountType == LOOPCOUNT_DYNAMIC)
+		vtx << "attribute mediump float a_one;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec3 v_color;\n";
+		frag << "varying mediump vec3 v_color;\n";
+	}
+	else
+	{
+		vtx << "varying mediump vec4 v_coords;\n";
+		frag << "varying mediump vec4 v_coords;\n";
+
+		if (loopCountType == LOOPCOUNT_DYNAMIC)
+		{
+			vtx << "varying mediump float v_one;\n";
+			frag << "varying mediump float v_one;\n";
+		}
+	}
+
+	// \todo [petri] Pass numLoopIters from outside?
+	int		numLoopIters = 3;
+	bool	isIntCounter = isDataTypeIntOrIVec(loopCountDataType);
+
+	if (isIntCounter)
+	{
+		if (loopCountType == LOOPCOUNT_UNIFORM || loopCountType == LOOPCOUNT_DYNAMIC)
+			op << "uniform ${COUNTER_PRECISION} int " << getIntUniformName(numLoopIters) << ";\n";
+	}
+	else
+	{
+		if (loopCountType == LOOPCOUNT_UNIFORM || loopCountType == LOOPCOUNT_DYNAMIC)
+			op << "uniform ${COUNTER_PRECISION} float " << getFloatFractionUniformName(numLoopIters) << ";\n";
+
+		if (numLoopIters != 1)
+			op << "uniform ${COUNTER_PRECISION} float uf_one;\n";
+	}
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	if (isVertexCase)
+		vtx << "	${PRECISION} vec4 coords = a_coords;\n";
+	else
+		frag << "	${PRECISION} vec4 coords = v_coords;\n";
+
+	if (loopCountType == LOOPCOUNT_DYNAMIC)
+	{
+		if (isIntCounter)
+		{
+			if (isVertexCase)
+				vtx << "	${COUNTER_PRECISION} int one = int(a_one + 0.5);\n";
+			else
+				frag << "	${COUNTER_PRECISION} int one = int(v_one + 0.5);\n";
+		}
+		else
+		{
+			if (isVertexCase)
+				vtx << "	${COUNTER_PRECISION} float one = a_one;\n";
+			else
+				frag << "	${COUNTER_PRECISION} float one = v_one;\n";
+		}
+	}
+
+	// Read array.
+	op << "	${PRECISION} vec4 res = coords;\n";
+
+	// Loop iteration count.
+	string	iterMaxStr;
+
+	if (isIntCounter)
+	{
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			iterMaxStr = de::toString(numLoopIters);
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			iterMaxStr = getIntUniformName(numLoopIters);
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			iterMaxStr = string(getIntUniformName(numLoopIters)) + "*one";
+		else
+			DE_ASSERT(false);
+	}
+	else
+	{
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			iterMaxStr = "1.0";
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			iterMaxStr = "uf_one";
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			iterMaxStr = "uf_one*one";
+		else
+			DE_ASSERT(false);
+	}
+
+	// Loop operations.
+	string initValue		= isIntCounter ? "0" : "0.05";
+	string loopCountDeclStr	= "${COUNTER_PRECISION} ${LOOP_VAR_TYPE} ndx = " + initValue;
+	string loopCmpStr		= ("ndx < " + iterMaxStr);
+	string incrementStr;
+	if (isIntCounter)
+		incrementStr = "ndx++";
+	else
+	{
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			incrementStr = string("ndx += ") + de::toString(1.0f / numLoopIters);
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			incrementStr = string("ndx += ") + getFloatFractionUniformName(numLoopIters);
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			incrementStr = string("ndx += ") + getFloatFractionUniformName(numLoopIters) + "*one";
+		else
+			DE_ASSERT(false);
+	}
+
+	// Loop body.
+	string loopBody;
+
+	loopBody = "		res = res.yzwx;\n";
+
+	if (loopType == LOOPTYPE_FOR)
+	{
+		op << "	for (" + loopCountDeclStr + "; " + loopCmpStr + "; " + incrementStr + ")\n";
+		op << "	{\n";
+		op << loopBody;
+		op << "	}\n";
+	}
+	else if (loopType == LOOPTYPE_WHILE)
+	{
+		op << "\t" << loopCountDeclStr + ";\n";
+		op << "	while (" + loopCmpStr + ")\n";
+		op << "	{\n";
+		op << loopBody;
+		op << "\t\t" + incrementStr + ";\n";
+		op << "	}\n";
+	}
+	else if (loopType == LOOPTYPE_DO_WHILE)
+	{
+		op << "\t" << loopCountDeclStr + ";\n";
+		op << "	do\n";
+		op << "	{\n";
+		op << loopBody;
+		op << "\t\t" + incrementStr + ";\n";
+		op << "	} while (" + loopCmpStr + ");\n";
+	}
+	else
+		DE_ASSERT(false);
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = res.rgb;\n";
+		frag << "	gl_FragColor = vec4(v_color.rgb, 1.0);\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	gl_FragColor = vec4(res.rgb, 1.0);\n";
+
+		if (loopCountType == LOOPCOUNT_DYNAMIC)
+			vtx << "	v_one = a_one;\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("LOOP_VAR_TYPE", getDataTypeName(loopCountDataType)));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+	params.insert(pair<string, string>("COUNTER_PRECISION", getPrecisionName(loopCountPrecision)));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	// Create the case.
+	ShaderEvalFunc evalFunc = getLoopEvalFunc(numLoopIters);
+	LoopRequirement requirement;
+
+	if (loopType == LOOPTYPE_FOR)
+	{
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			requirement = LOOPREQUIREMENT_STANDARD;
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			requirement = LOOPREQUIREMENT_UNIFORM;
+		else
+			requirement = LOOPREQUIREMENT_DYNAMIC;
+	}
+	else
+		requirement = LOOPREQUIREMENT_DYNAMIC;
+
+	return new ShaderLoopCase(context, caseName, description, isVertexCase, evalFunc, requirement, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+// \todo [petri] Generalize to float as well?
+static ShaderLoopCase* createSpecialLoopCase (Context& context, const char* caseName, const char* description, bool isVertexCase, LoopCase loopCase, LoopType loopType, LoopCountType loopCountType)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	vtx << "attribute highp vec4 a_position;\n";
+	vtx << "attribute highp vec4 a_coords;\n";
+
+	if (loopCountType == LOOPCOUNT_DYNAMIC)
+		vtx << "attribute mediump float a_one;\n";
+
+	// Attribute and varyings.
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec3 v_color;\n";
+		frag << "varying mediump vec3 v_color;\n";
+	}
+	else
+	{
+		vtx << "varying mediump vec4 v_coords;\n";
+		frag << "varying mediump vec4 v_coords;\n";
+
+		if (loopCountType == LOOPCOUNT_DYNAMIC)
+		{
+			vtx << "varying mediump float v_one;\n";
+			frag << "varying mediump float v_one;\n";
+		}
+	}
+
+	if (loopCase == LOOPCASE_SELECT_ITERATION_COUNT)
+		op << "uniform bool ub_true;\n";
+
+	op << "uniform ${COUNTER_PRECISION} int ui_zero, ui_one, ui_two, ui_three, ui_four, ui_five, ui_six;\n";
+	if (loopCase == LOOPCASE_101_ITERATIONS)
+		op << "uniform ${COUNTER_PRECISION} int ui_oneHundredOne;\n";
+
+	int iterCount	= 3;	// value to use in loop
+	int numIters	= 3;	// actual number of iterations
+
+	// Generate helpers if necessary.
+	if (loopCase == LOOPCASE_FUNCTION_CALL_RETURN)
+		op << "\n${PRECISION} vec4 func (in ${PRECISION} vec4 coords) { return coords.yzwx; }\n";
+	else if (loopCase == LOOPCASE_FUNCTION_CALL_INOUT)
+		op << "\nvoid func (inout ${PRECISION} vec4 coords) { coords = coords.yzwx; }\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	if (loopCountType == LOOPCOUNT_DYNAMIC)
+	{
+		if (isVertexCase)
+			vtx << "	${COUNTER_PRECISION} int one = int(a_one + 0.5);\n";
+		else
+			frag << "	${COUNTER_PRECISION} int one = int(v_one + 0.5);\n";
+	}
+
+	if (isVertexCase)
+		vtx << "	${PRECISION} vec4 coords = a_coords;\n";
+	else
+		frag << "	${PRECISION} vec4 coords = v_coords;\n";
+
+	// Read array.
+	op << "	${PRECISION} vec4 res = coords;\n";
+
+	// Handle all loop types.
+	string counterPrecisionStr = "mediump";
+	string forLoopStr;
+	string whileLoopStr;
+	string doWhileLoopPreStr;
+	string doWhileLoopPostStr;
+
+	if (loopType == LOOPTYPE_FOR)
+	{
+		switch (loopCase)
+		{
+			case LOOPCASE_EMPTY_BODY:
+				numIters = 0;
+				op << "	${FOR_LOOP} {}\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST:
+				numIters = 0;
+				op << "	for (;;) { break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST:
+				numIters = 1;
+				op << "	for (;;) { res = res.yzwx; break; }\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	for (;;) { res = res.yzwx; if (i == ${ONE}) break; i++; }\n";
+				break;
+
+			case LOOPCASE_SINGLE_STATEMENT:
+				op << "	${FOR_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_COMPOUND_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${FOR_LOOP} { res = res.yzwx; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_SEQUENCE_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${FOR_LOOP} res = res.yzwx, res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_NO_ITERATIONS:
+				iterCount	= 0;
+				numIters	= 0;
+				op << "	${FOR_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SINGLE_ITERATION:
+				iterCount	= 1;
+				numIters	= 1;
+				op << "	${FOR_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SELECT_ITERATION_COUNT:
+				op << "	for (int i = 0; i < (ub_true ? ${ITER_COUNT} : 0); i++) res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${FOR_LOOP} { if (i == ${TWO}) continue; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_CONTINUE:
+				op << "	${FOR_LOOP} { res = res.yzwx; continue; }\n";
+				break;
+
+			case LOOPCASE_ONLY_CONTINUE:
+				numIters = 0;
+				op << "	${FOR_LOOP} { continue; }\n";
+				break;
+
+			case LOOPCASE_DOUBLE_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${FOR_LOOP} { if (i == ${TWO}) continue; res = res.yzwx; continue; }\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${FOR_LOOP} { if (i == ${TWO}) break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_BREAK:
+				numIters = 1;
+				op << "	${FOR_LOOP} { res = res.yzwx; break; }\n";
+				break;
+
+			case LOOPCASE_PRE_INCREMENT:
+				op << "	for (int i = 0; i < ${ITER_COUNT}; ++i) { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_POST_INCREMENT:
+				op << "	${FOR_LOOP} { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_MIXED_BREAK_CONTINUE:
+				numIters	= 2;
+				iterCount	= 5;
+				op << "	${FOR_LOOP} { if (i == 0) continue; else if (i == 3) break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_VECTOR_COUNTER:
+				op << "	for (${COUNTER_PRECISION} ivec4 i = ivec4(0, 1, ${ITER_COUNT}, 0); i.x < i.z; i.x += i.y) { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_101_ITERATIONS:
+				numIters = iterCount = 101;
+				op << "	${FOR_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SEQUENCE:
+				iterCount	= 5;
+				numIters	= 5;
+				op << "	${COUNTER_PRECISION} int i;\n";
+				op << "	for (i = 0; i < ${TWO}; i++) { res = res.yzwx; }\n";
+				op << "	for (; i < ${ITER_COUNT}; i++) { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_NESTED:
+				numIters = 2 * iterCount;
+				op << "	for (${COUNTER_PRECISION} int i = 0; i < ${TWO}; i++)\n";
+				op << "	{\n";
+				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${ITER_COUNT}; j++)\n";
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_SEQUENCE:
+				numIters = 3 * iterCount;
+				op << "	for (${COUNTER_PRECISION} int i = 0; i < ${ITER_COUNT}; i++)\n";
+				op << "	{\n";
+				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${TWO}; j++)\n";
+				op << "			res = res.yzwx;\n";
+				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${ONE}; j++)\n";
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_1:
+				numIters = 2;
+				op << "	${FOR_LOOP}\n";
+				op << "	{\n";
+				op << "		res = coords; // ignore outer loop effect \n";
+				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${TWO}; j++)\n";
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_2:
+				numIters = iterCount;
+				op << "	${FOR_LOOP}\n";
+				op << "	{\n";
+				op << "		res = coords.wxyz;\n";
+				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${TWO}; j++)\n";
+				op << "			res = res.yzwx;\n";
+				op << "		coords = res;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_BODY:
+				numIters = de::min(2, iterCount);
+				op << "	${FOR_LOOP} if (i < 2) res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_FUNCTION_CALL_RETURN:
+				numIters = iterCount;
+				op << "	${FOR_LOOP}\n";
+				op << "	{\n";
+				op << "		res = func(res);\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_FUNCTION_CALL_INOUT:
+				numIters = iterCount;
+				op << "	${FOR_LOOP}\n";
+				op << "	{\n";
+				op << "		func(res);\n";
+				op << "	}\n";
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			forLoopStr = string("for (") + counterPrecisionStr + " int i = 0; i < " + de::toString(iterCount) + "; i++)";
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			forLoopStr = string("for (") + counterPrecisionStr + " int i = 0; i < " + getIntUniformName(iterCount) + "; i++)";
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			forLoopStr = string("for (") + counterPrecisionStr + " int i = 0; i < one*" + getIntUniformName(iterCount) + "; i++)";
+		else
+			DE_ASSERT(false);
+	}
+	else if (loopType == LOOPTYPE_WHILE)
+	{
+		switch (loopCase)
+		{
+			case LOOPCASE_EMPTY_BODY:
+				numIters = 0;
+				op << "	${WHILE_LOOP} {}\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST:
+				numIters = 0;
+				op << "	while (true) { break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST:
+				numIters = 1;
+				op << "	while (true) { res = res.yzwx; break; }\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (true) { res = res.yzwx; if (i == ${ONE}) break; i++; }\n";
+				break;
+
+			case LOOPCASE_SINGLE_STATEMENT:
+				op << "	${WHILE_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_COMPOUND_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${WHILE_LOOP} { res = res.yzwx; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_SEQUENCE_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${WHILE_LOOP} res = res.yzwx, res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_NO_ITERATIONS:
+				iterCount	= 0;
+				numIters	= 0;
+				op << "	${WHILE_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SINGLE_ITERATION:
+				iterCount	= 1;
+				numIters	= 1;
+				op << "	${WHILE_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SELECT_ITERATION_COUNT:
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (i < (ub_true ? ${ITER_COUNT} : 0)) { res = res.yzwx; i++; }\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${WHILE_LOOP} { if (i == ${TWO}) continue; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_CONTINUE:
+				op << "	${WHILE_LOOP} { res = res.yzwx; continue; }\n";
+				break;
+
+			case LOOPCASE_ONLY_CONTINUE:
+				numIters = 0;
+				op << "	${WHILE_LOOP} { continue; }\n";
+				break;
+
+			case LOOPCASE_DOUBLE_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${WHILE_LOOP} { if (i == ${ONE}) continue; res = res.yzwx; continue; }\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${WHILE_LOOP} { if (i == ${THREE}) break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_BREAK:
+				numIters = 1;
+				op << "	${WHILE_LOOP} { res = res.yzwx; break; }\n";
+				break;
+
+			case LOOPCASE_PRE_INCREMENT:
+				numIters = iterCount - 1;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (++i < ${ITER_COUNT}) { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_POST_INCREMENT:
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (i++ < ${ITER_COUNT}) { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_MIXED_BREAK_CONTINUE:
+				numIters	= 2;
+				iterCount	= 5;
+				op << "	${WHILE_LOOP} { if (i == 0) continue; else if (i == 3) break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_VECTOR_COUNTER:
+				op << "	${COUNTER_PRECISION} ivec4 i = ivec4(0, 1, ${ITER_COUNT}, 0);\n";
+				op << "	while (i.x < i.z) { res = res.yzwx; i.x += i.y; }\n";
+				break;
+
+			case LOOPCASE_101_ITERATIONS:
+				numIters = iterCount = 101;
+				op << "	${WHILE_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SEQUENCE:
+				iterCount	= 6;
+				numIters	= iterCount - 1;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (i++ < ${TWO}) { res = res.yzwx; }\n";
+				op << "	while (i++ < ${ITER_COUNT}) { res = res.yzwx; }\n"; // \note skips one iteration
+				break;
+
+			case LOOPCASE_NESTED:
+				numIters = 2 * iterCount;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (i++ < ${TWO})\n";
+				op << "	{\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		while (j++ < ${ITER_COUNT})\n";
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_SEQUENCE:
+				numIters = 2 * iterCount;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (i++ < ${ITER_COUNT})\n";
+				op << "	{\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		while (j++ < ${ONE})\n";
+				op << "			res = res.yzwx;\n";
+				op << "		while (j++ < ${THREE})\n"; // \note skips one iteration
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_1:
+				numIters = 2;
+				op << "	${WHILE_LOOP}\n";
+				op << "	{\n";
+				op << "		res = coords; // ignore outer loop effect \n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		while (j++ < ${TWO})\n";
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_2:
+				numIters = iterCount;
+				op << "	${WHILE_LOOP}\n";
+				op << "	{\n";
+				op << "		res = coords.wxyz;\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		while (j++ < ${TWO})\n";
+				op << "			res = res.yzwx;\n";
+				op << "		coords = res;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_BODY:
+				numIters = de::min(1, iterCount);
+				op << "	${WHILE_LOOP} if (i < 2) res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_FUNCTION_CALL_RETURN:
+				numIters = iterCount;
+				op << "	${WHILE_LOOP}\n";
+				op << "	{\n";
+				op << "		res = func(res);\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_FUNCTION_CALL_INOUT:
+				numIters = iterCount;
+				op << "	${WHILE_LOOP}\n";
+				op << "	{\n";
+				op << "		func(res);\n";
+				op << "	}\n";
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			whileLoopStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "	while(i++ < " + de::toString(iterCount) + ")";
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			whileLoopStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "	while(i++ < " + getIntUniformName(iterCount) + ")";
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			whileLoopStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "	while(i++ < one*" + getIntUniformName(iterCount) + ")";
+		else
+			DE_ASSERT(false);
+	}
+	else
+	{
+		DE_ASSERT(loopType == LOOPTYPE_DO_WHILE);
+
+		switch (loopCase)
+		{
+			case LOOPCASE_EMPTY_BODY:
+				numIters = 0;
+				op << "	${DO_WHILE_PRE} {} ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST:
+				numIters = 0;
+				op << "	do { break; res = res.yzwx; } while (true);\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST:
+				numIters = 1;
+				op << "	do { res = res.yzwx; break; } while (true);\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do { res = res.yzwx; if (i == ${ONE}) break; i++; } while (true);\n";
+				break;
+
+			case LOOPCASE_SINGLE_STATEMENT:
+				op << "	${DO_WHILE_PRE} res = res.yzwx; ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_COMPOUND_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${DO_WHILE_PRE} { res = res.yzwx; res = res.yzwx; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_SEQUENCE_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${DO_WHILE_PRE} res = res.yzwx, res = res.yzwx; ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_NO_ITERATIONS:
+				DE_ASSERT(false);
+				break;
+
+			case LOOPCASE_SINGLE_ITERATION:
+				iterCount	= 1;
+				numIters	= 1;
+				op << "	${DO_WHILE_PRE} res = res.yzwx; ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_SELECT_ITERATION_COUNT:
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do { res = res.yzwx; } while (++i < (ub_true ? ${ITER_COUNT} : 0));\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${DO_WHILE_PRE} { if (i == ${TWO}) continue; res = res.yzwx; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_CONTINUE:
+				op << "	${DO_WHILE_PRE} { res = res.yzwx; continue; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_ONLY_CONTINUE:
+				numIters = 0;
+				op << "	${DO_WHILE_PRE} { continue; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_DOUBLE_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${DO_WHILE_PRE} { if (i == ${TWO}) continue; res = res.yzwx; continue; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${DO_WHILE_PRE} { res = res.yzwx; if (i == ${ONE}) break; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_BREAK:
+				numIters = 1;
+				op << "	${DO_WHILE_PRE} { res = res.yzwx; break; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_PRE_INCREMENT:
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do { res = res.yzwx; } while (++i < ${ITER_COUNT});\n";
+				break;
+
+			case LOOPCASE_POST_INCREMENT:
+				numIters = iterCount + 1;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do { res = res.yzwx; } while (i++ < ${ITER_COUNT});\n";
+				break;
+
+			case LOOPCASE_MIXED_BREAK_CONTINUE:
+				numIters	= 2;
+				iterCount	= 5;
+				op << "	${DO_WHILE_PRE} { if (i == 0) continue; else if (i == 3) break; res = res.yzwx; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_VECTOR_COUNTER:
+				op << "	${COUNTER_PRECISION} ivec4 i = ivec4(0, 1, ${ITER_COUNT}, 0);\n";
+				op << "	do { res = res.yzwx; } while ((i.x += i.y) < i.z);\n";
+				break;
+
+			case LOOPCASE_101_ITERATIONS:
+				numIters = iterCount = 101;
+				op << "	${DO_WHILE_PRE} res = res.yzwx; ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_SEQUENCE:
+				iterCount	= 5;
+				numIters	= 5;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do { res = res.yzwx; } while (++i < ${TWO});\n";
+				op << "	do { res = res.yzwx; } while (++i < ${ITER_COUNT});\n";
+				break;
+
+			case LOOPCASE_NESTED:
+				numIters = 2 * iterCount;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do\n";
+				op << "	{\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		do\n";
+				op << "			res = res.yzwx;\n";
+				op << "		while (++j < ${ITER_COUNT});\n";
+				op << "	} while (++i < ${TWO});\n";
+				break;
+
+			case LOOPCASE_NESTED_SEQUENCE:
+				numIters = 3 * iterCount;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do\n";
+				op << "	{\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		do\n";
+				op << "			res = res.yzwx;\n";
+				op << "		while (++j < ${TWO});\n";
+				op << "		do\n";
+				op << "			res = res.yzwx;\n";
+				op << "		while (++j < ${THREE});\n";
+				op << "	} while (++i < ${ITER_COUNT});\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_1:
+				numIters = 2;
+				op << "	${DO_WHILE_PRE}\n";
+				op << "	{\n";
+				op << "		res = coords; // ignore outer loop effect \n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		do\n";
+				op << "			res = res.yzwx;\n";
+				op << "		while (++j < ${TWO});\n";
+				op << "	} ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_2:
+				numIters = iterCount;
+				op << "	${DO_WHILE_PRE}\n";
+				op << "	{\n";
+				op << "		res = coords.wxyz;\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		while (j++ < ${TWO})\n";
+				op << "			res = res.yzwx;\n";
+				op << "		coords = res;\n";
+				op << "	} ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_BODY:
+				numIters = de::min(2, iterCount);
+				op << "	${DO_WHILE_PRE} if (i < 2) res = res.yzwx; ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_FUNCTION_CALL_RETURN:
+				numIters = iterCount;
+				op << "	${DO_WHILE_PRE}\n";
+				op << "	{\n";
+				op << "		res = func(res);\n";
+				op << "	} ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_FUNCTION_CALL_INOUT:
+				numIters = iterCount;
+				op << "	${DO_WHILE_PRE}\n";
+				op << "	{\n";
+				op << "		func(res);\n";
+				op << "	} ${DO_WHILE_POST}\n";
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		doWhileLoopPreStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "\tdo ";
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			doWhileLoopPostStr = string(" while (++i < ") + de::toString(iterCount) + ");\n";
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			doWhileLoopPostStr = string(" while (++i < ") + getIntUniformName(iterCount) + ");\n";
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			doWhileLoopPostStr = string(" while (++i < one*") + getIntUniformName(iterCount) + ");\n";
+		else
+			DE_ASSERT(false);
+	}
+
+	// Shader footers.
+	if (isVertexCase)
+	{
+		vtx << "	v_color = res.rgb;\n";
+		frag << "	gl_FragColor = vec4(v_color.rgb, 1.0);\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	gl_FragColor = vec4(res.rgb, 1.0);\n";
+
+		if (loopCountType == LOOPCOUNT_DYNAMIC)
+			vtx << "	v_one = a_one;\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Constants.
+	string oneStr;
+	string twoStr;
+	string threeStr;
+	string iterCountStr;
+
+	if (loopCountType == LOOPCOUNT_CONSTANT)
+	{
+		oneStr			= "1";
+		twoStr			= "2";
+		threeStr		= "3";
+		iterCountStr	= de::toString(iterCount);
+	}
+	else if (loopCountType == LOOPCOUNT_UNIFORM)
+	{
+		oneStr			= "ui_one";
+		twoStr			= "ui_two";
+		threeStr		= "ui_three";
+		iterCountStr	= getIntUniformName(iterCount);
+	}
+	else if (loopCountType == LOOPCOUNT_DYNAMIC)
+	{
+		oneStr			= "one*ui_one";
+		twoStr			= "one*ui_two";
+		threeStr		= "one*ui_three";
+		iterCountStr	= string("one*") + getIntUniformName(iterCount);
+	}
+	else DE_ASSERT(false);
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+	params.insert(pair<string, string>("ITER_COUNT", iterCountStr));
+	params.insert(pair<string, string>("COUNTER_PRECISION", counterPrecisionStr));
+	params.insert(pair<string, string>("FOR_LOOP", forLoopStr));
+	params.insert(pair<string, string>("WHILE_LOOP", whileLoopStr));
+	params.insert(pair<string, string>("DO_WHILE_PRE", doWhileLoopPreStr));
+	params.insert(pair<string, string>("DO_WHILE_POST", doWhileLoopPostStr));
+	params.insert(pair<string, string>("ONE", oneStr));
+	params.insert(pair<string, string>("TWO", twoStr));
+	params.insert(pair<string, string>("THREE", threeStr));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	// Create the case.
+	ShaderEvalFunc evalFunc = getLoopEvalFunc(numIters);
+	LoopRequirement requirement;
+
+	if (loopType == LOOPTYPE_FOR && loopCountType == LOOPCOUNT_CONSTANT)
+	{
+		if (loopCase == LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK			||
+			loopCase == LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST	||
+			loopCase == LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST		||
+			loopCase == LOOPCASE_SELECT_ITERATION_COUNT						||
+			loopCase == LOOPCASE_VECTOR_COUNTER								||
+			loopCase == LOOPCASE_SEQUENCE)
+			requirement = LOOPREQUIREMENT_DYNAMIC;
+		else
+			requirement = LOOPREQUIREMENT_STANDARD;
+	}
+	else
+		requirement = LOOPREQUIREMENT_DYNAMIC;
+
+	return new ShaderLoopCase(context, caseName, description, isVertexCase, evalFunc, requirement, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+};
+
+// ShaderLoopTests.
+
+ShaderLoopTests::ShaderLoopTests(Context& context)
+	: TestCaseGroup(context, "loops", "Loop Tests")
+{
+}
+
+ShaderLoopTests::~ShaderLoopTests (void)
+{
+}
+
+void ShaderLoopTests::init (void)
+{
+	// Loop cases.
+
+	static const ShaderType s_shaderTypes[] =
+	{
+		SHADERTYPE_VERTEX,
+		SHADERTYPE_FRAGMENT
+	};
+
+	static const DataType s_countDataType[] =
+	{
+		TYPE_INT,
+		TYPE_FLOAT
+	};
+
+	for (int loopType = 0; loopType < LOOPTYPE_LAST; loopType++)
+	{
+		const char* loopTypeName = getLoopTypeName((LoopType)loopType);
+
+		for (int loopCountType = 0; loopCountType < LOOPCOUNT_LAST; loopCountType++)
+		{
+			const char* loopCountName = getLoopCountTypeName((LoopCountType)loopCountType);
+
+			string groupName = string(loopTypeName) + "_" + string(loopCountName) + "_iterations";
+			string groupDesc = string("Loop tests with ") + loopCountName + " loop counter.";
+			TestCaseGroup* group = new TestCaseGroup(m_context, groupName.c_str(), groupDesc.c_str());
+			addChild(group);
+
+			// Generic cases.
+
+			for (int precision = 0; precision < PRECISION_LAST; precision++)
+			{
+				const char* precisionName = getPrecisionName((Precision)precision);
+
+				for (int dataTypeNdx = 0; dataTypeNdx < DE_LENGTH_OF_ARRAY(s_countDataType); dataTypeNdx++)
+				{
+					DataType loopDataType = s_countDataType[dataTypeNdx];
+					const char* dataTypeName = getDataTypeName(loopDataType);
+
+					for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+					{
+						ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+						const char*	shaderTypeName	= getShaderTypeName(shaderType);
+						bool		isVertexCase	= (shaderType == SHADERTYPE_VERTEX);
+
+						string name = string("basic_") + precisionName + "_" + dataTypeName + "_" + shaderTypeName;
+						string desc = string(loopTypeName) + " loop with " + precisionName + dataTypeName + " " + loopCountName + " iteration count in " + shaderTypeName + " shader.";
+						group->addChild(createGenericLoopCase(m_context, name.c_str(), desc.c_str(), isVertexCase, (LoopType)loopType, (LoopCountType)loopCountType, (Precision)precision, loopDataType));
+					}
+				}
+			}
+
+			// Special cases.
+
+			for (int loopCase = 0; loopCase < LOOPCASE_LAST; loopCase++)
+			{
+				const char* loopCaseName = getLoopCaseName((LoopCase)loopCase);
+
+				// no-iterations not possible with do-while.
+				if ((loopCase == LOOPCASE_NO_ITERATIONS) && (loopType == LOOPTYPE_DO_WHILE))
+					continue;
+
+				for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+				{
+					ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+					const char*	shaderTypeName	= getShaderTypeName(shaderType);
+					bool		isVertexCase	= (shaderType == SHADERTYPE_VERTEX);
+
+					string name = string(loopCaseName) + "_" + shaderTypeName;
+					string desc = string(loopCaseName) + " loop with " + loopTypeName + " iteration count in " + shaderTypeName + " shader.";
+					group->addChild(createSpecialLoopCase(m_context, name.c_str(), desc.c_str(), isVertexCase, (LoopCase)loopCase, (LoopType)loopType, (LoopCountType)loopCountType));
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderLoopTests.hpp b/modules/gles2/functional/es2fShaderLoopTests.hpp
new file mode 100644
index 0000000..6b43485
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderLoopTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERLOOPTESTS_HPP
+#define _ES2FSHADERLOOPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader loop tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderLoopTests : public TestCaseGroup
+{
+public:
+							ShaderLoopTests			(Context& context);
+	virtual					~ShaderLoopTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderLoopTests			(const ShaderLoopTests&);		// not allowed!
+	ShaderLoopTests&		operator=				(const ShaderLoopTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERLOOPTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderMatrixTests.cpp b/modules/gles2/functional/es2fShaderMatrixTests.cpp
new file mode 100644
index 0000000..4b6a8fd
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderMatrixTests.cpp
@@ -0,0 +1,1223 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader matrix arithmetic tests.
+ *
+ * Variables:
+ *  + operation
+ *    - mat OP mat
+ *    - mat OP vec
+ *    - vec OP mat
+ *    - mat OP scalar
+ *    - OP mat
+ *  + matrix source
+ *    - constant (ctor)
+ *    - uniform
+ *    - vertex input
+ *    - fragment input
+ *  + other operand: always dynamic data?
+ *  + how to reduce to vec3?
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderMatrixTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuMatrixUtil.hpp"
+#include "deStringUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using namespace glu;
+using namespace deqp::gls;
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::Mat2;
+using tcu::Mat3;
+using tcu::Mat4;
+
+// Uniform / constant values for tests.
+// \note Input1 should not contain 0 components as it is used as divisor in div cases.
+// \todo [2012-02-14 pyry] Make these dynamic.
+static const float	s_constInFloat[2]	= { 0.5f, -0.2f };
+static const Vec2	s_constInVec2[2]	= { Vec2(1.2f, 0.5f), Vec2(0.5f, 1.0f) };
+static const Vec3	s_constInVec3[2]	= { Vec3(1.1f, 0.1f, 0.5f), Vec3(-0.2f, 0.5f, 0.8f) };
+static const Vec4	s_constInVec4[2]	= { Vec4(1.4f, 0.2f, -0.5f, 0.7f), Vec4(0.2f, -1.0f, 0.5f, 0.8f) };
+
+static const float s_constInMat20[] = { 0.6f, -1.0f, 0.7f, 0.4f };
+static const float s_constInMat21[] = { -0.5f, -0.4f, 0.7f, -0.8f };
+
+static const float s_constInMat31[] =
+{
+	1.2f,  0.1f, -0.1f,
+	0.1f,  0.9f,  0.2f,
+	0.2f, -0.1f,  0.7f
+};
+static const float s_constInMat41[] =
+{
+	 1.2f, -0.2f,  0.4f,  0.1f,
+	 0.1f,  0.8f, -0.1f, -0.2f,
+	-0.2f,  0.1f, -1.1f,  0.3f,
+	 0.1f,  0.2f,  0.3f,  0.9f
+};
+
+static const Mat2	s_constInMat2[2]	= { tcu::Mat2(s_constInMat20), tcu::Mat2(s_constInMat21) };
+static const Mat3	s_constInMat3[2]	= { tcu::translationMatrix(tcu::Vec2(0.2f, -0.3f)), tcu::Mat3(s_constInMat31) };
+static const Mat4	s_constInMat4[2]	= { tcu::translationMatrix(tcu::Vec3(0.2f, -0.3f, 0.15f)), tcu::Mat4(s_constInMat41) };
+
+namespace MatrixCaseUtils
+{
+
+enum InputType
+{
+	INPUTTYPE_CONST = 0,
+	INPUTTYPE_UNIFORM,
+	INPUTTYPE_DYNAMIC,
+
+	INPUTTYPE_LAST
+};
+
+struct ShaderInput
+{
+	ShaderInput (InputType inputType_, DataType dataType_, Precision precision_)
+		: inputType	(inputType_)
+		, dataType	(dataType_)
+		, precision	(precision_)
+	{
+	}
+
+	InputType		inputType;
+	DataType		dataType;
+	Precision		precision;
+};
+
+enum MatrixOp
+{
+	OP_ADD = 0,
+	OP_SUB,
+	OP_MUL,
+	OP_DIV,
+	OP_COMP_MUL,
+	OP_UNARY_PLUS,
+	OP_NEGATION,
+	OP_PRE_INCREMENT,
+	OP_PRE_DECREMENT,
+	OP_POST_INCREMENT,
+	OP_POST_DECREMENT,
+	OP_ADD_INTO,
+	OP_SUBTRACT_FROM,
+	OP_MULTIPLY_INTO,
+	OP_DIVIDE_INTO,
+
+	OP_LAST
+};
+
+// Type traits.
+
+template <int DataT>
+struct TypeTraits;
+
+#define DECLARE_TYPE_TRAIT(DATATYPE, TYPE)	\
+template<>									\
+struct TypeTraits<DATATYPE> {				\
+	typedef TYPE Type;						\
+}
+
+DECLARE_TYPE_TRAIT(TYPE_FLOAT,		float);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_VEC2,	tcu::Vec2);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_VEC3,	tcu::Vec3);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_VEC4,	tcu::Vec4);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT2, tcu::Mat2);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT3, tcu::Mat3);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT4, tcu::Mat4);
+
+// Operation info
+
+enum OperationType
+{
+	OPERATIONTYPE_BINARY_OPERATOR = 0,
+	OPERATIONTYPE_BINARY_FUNCTION,
+	OPERATIONTYPE_UNARY_PREFIX_OPERATOR,
+	OPERATIONTYPE_UNARY_POSTFIX_OPERATOR,
+	OPERATIONTYPE_ASSIGNMENT,
+
+	OPERATIONTYPE_LAST
+};
+
+static const char* getOperationName (MatrixOp op)
+{
+	switch (op)
+	{
+		case OP_ADD:			return "+";
+		case OP_SUB:			return "-";
+		case OP_MUL:			return "*";
+		case OP_DIV:			return "/";
+		case OP_COMP_MUL:		return "matrixCompMult";
+		case OP_UNARY_PLUS:		return "+";
+		case OP_NEGATION:		return "-";
+		case OP_PRE_INCREMENT:	return "++";
+		case OP_PRE_DECREMENT:	return "--";
+		case OP_POST_INCREMENT:	return "++";
+		case OP_POST_DECREMENT:	return "--";
+		case OP_ADD_INTO:		return "+=";
+		case OP_SUBTRACT_FROM:	return "-=";
+		case OP_MULTIPLY_INTO:	return "*=";
+		case OP_DIVIDE_INTO:	return "/=";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return "";
+	}
+}
+
+static OperationType getOperationType (MatrixOp op)
+{
+	switch (op)
+	{
+		case OP_ADD:			return OPERATIONTYPE_BINARY_OPERATOR;
+		case OP_SUB:			return OPERATIONTYPE_BINARY_OPERATOR;
+		case OP_MUL:			return OPERATIONTYPE_BINARY_OPERATOR;
+		case OP_DIV:			return OPERATIONTYPE_BINARY_OPERATOR;
+		case OP_COMP_MUL:		return OPERATIONTYPE_BINARY_FUNCTION;
+		case OP_UNARY_PLUS:		return OPERATIONTYPE_UNARY_PREFIX_OPERATOR;
+		case OP_NEGATION:		return OPERATIONTYPE_UNARY_PREFIX_OPERATOR;
+		case OP_PRE_INCREMENT:	return OPERATIONTYPE_UNARY_PREFIX_OPERATOR;
+		case OP_PRE_DECREMENT:	return OPERATIONTYPE_UNARY_PREFIX_OPERATOR;
+		case OP_POST_INCREMENT:	return OPERATIONTYPE_UNARY_POSTFIX_OPERATOR;
+		case OP_POST_DECREMENT:	return OPERATIONTYPE_UNARY_POSTFIX_OPERATOR;
+		case OP_ADD_INTO:		return OPERATIONTYPE_ASSIGNMENT;
+		case OP_SUBTRACT_FROM:	return OPERATIONTYPE_ASSIGNMENT;
+		case OP_MULTIPLY_INTO:	return OPERATIONTYPE_ASSIGNMENT;
+		case OP_DIVIDE_INTO:	return OPERATIONTYPE_ASSIGNMENT;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return OPERATIONTYPE_LAST;
+	}
+}
+
+enum TestMatrixType
+{
+	TESTMATRIXTYPE_DEFAULT = 0,
+	TESTMATRIXTYPE_NEGATED,
+	TESTMATRIXTYPE_INCREMENTED,
+	TESTMATRIXTYPE_DECREMENTED,
+
+	TESTMATRIXTYPE_LAST
+};
+
+static TestMatrixType getOperationTestMatrixType (MatrixOp op)
+{
+	switch(op)
+	{
+		case OP_ADD:			return TESTMATRIXTYPE_DEFAULT;
+		case OP_SUB:			return TESTMATRIXTYPE_DEFAULT;
+		case OP_MUL:			return TESTMATRIXTYPE_DEFAULT;
+		case OP_DIV:			return TESTMATRIXTYPE_DEFAULT;
+		case OP_COMP_MUL:		return TESTMATRIXTYPE_DEFAULT;
+		case OP_UNARY_PLUS:		return TESTMATRIXTYPE_DEFAULT;
+		case OP_NEGATION:		return TESTMATRIXTYPE_NEGATED;
+		case OP_PRE_INCREMENT:	return TESTMATRIXTYPE_NEGATED;
+		case OP_PRE_DECREMENT:	return TESTMATRIXTYPE_INCREMENTED;
+		case OP_POST_INCREMENT:	return TESTMATRIXTYPE_NEGATED;
+		case OP_POST_DECREMENT:	return TESTMATRIXTYPE_DEFAULT;
+		case OP_ADD_INTO:		return TESTMATRIXTYPE_DECREMENTED;
+		case OP_SUBTRACT_FROM:	return TESTMATRIXTYPE_DEFAULT;
+		case OP_MULTIPLY_INTO:	return TESTMATRIXTYPE_DEFAULT;
+		case OP_DIVIDE_INTO:	return TESTMATRIXTYPE_DEFAULT;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return TESTMATRIXTYPE_LAST;
+	}
+}
+
+static bool isOperationBinary (MatrixOp op)
+{
+	return getOperationType(op) == OPERATIONTYPE_BINARY_OPERATOR ||
+	       getOperationType(op) == OPERATIONTYPE_BINARY_FUNCTION ||
+	       getOperationType(op) == OPERATIONTYPE_ASSIGNMENT;
+}
+
+static bool isOperationMatrixScalar (MatrixOp op)
+{
+	return op == OP_ADD || op == OP_SUB || op == OP_MUL || op == OP_DIV;
+}
+
+static bool isOperationMatrixVector (MatrixOp op)
+{
+	return op == OP_MUL;
+}
+
+static bool isOperationMatrixMatrix (MatrixOp op)
+{
+	return op == OP_ADD || op == OP_SUB || op == OP_MUL || op == OP_DIV || op == OP_COMP_MUL;
+}
+
+static bool isOperationUnary (MatrixOp op)
+{
+	return  op == OP_UNARY_PLUS			||
+			op == OP_NEGATION			||
+			op == OP_PRE_INCREMENT		||
+			op == OP_PRE_DECREMENT		||
+			op == OP_POST_INCREMENT		||
+			op == OP_POST_DECREMENT;
+}
+
+static bool isOperationValueModifying (MatrixOp op)
+{
+	return  op == OP_PRE_INCREMENT		||
+			op == OP_PRE_DECREMENT		||
+			op == OP_POST_INCREMENT		||
+			op == OP_POST_DECREMENT;
+}
+
+static bool isOperationAssignment (MatrixOp op)
+{
+	return  op == OP_ADD_INTO		 ||
+			op == OP_SUBTRACT_FROM	 ||
+			op == OP_MULTIPLY_INTO	 ||
+			op == OP_DIVIDE_INTO;
+}
+
+// Operation nature
+
+enum OperationNature
+{
+	OPERATIONNATURE_PURE = 0,
+	OPERATIONNATURE_MUTATING,
+	OPERATIONNATURE_ASSIGNMENT,
+
+	OPERATIONNATURE_LAST
+};
+
+static OperationNature getOperationNature (MatrixOp op)
+{
+	if (isOperationAssignment(op))
+		return OPERATIONNATURE_ASSIGNMENT;
+
+	if (isOperationValueModifying(op))
+		return OPERATIONNATURE_MUTATING;
+
+	return OPERATIONNATURE_PURE;
+}
+
+// Input value loader.
+
+template <int InputT, int DataT>
+typename TypeTraits<DataT>::Type getInputValue (const ShaderEvalContext& evalCtx, int inputNdx);
+
+template <> inline float		getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT>			(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInFloat[inputNdx];	}
+template <> inline tcu::Vec2	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_VEC2>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInVec2[inputNdx];	}
+template <> inline tcu::Vec3	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_VEC3>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInVec3[inputNdx];	}
+template <> inline tcu::Vec4	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_VEC4>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInVec4[inputNdx];	}
+template <> inline tcu::Mat2	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT2>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInMat2[inputNdx];	}
+template <> inline tcu::Mat3	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT3>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInMat3[inputNdx];	}
+template <> inline tcu::Mat4	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT4>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInMat4[inputNdx];	}
+
+template <> inline float		getInputValue<INPUTTYPE_DYNAMIC,	TYPE_FLOAT>			(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(inputNdx); return evalCtx.coords.x();					}
+template <> inline tcu::Vec2	getInputValue<INPUTTYPE_DYNAMIC,	TYPE_FLOAT_VEC2>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(inputNdx); return evalCtx.coords.swizzle(0, 1);			}
+template <> inline tcu::Vec3	getInputValue<INPUTTYPE_DYNAMIC,	TYPE_FLOAT_VEC3>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(inputNdx); return evalCtx.coords.swizzle(0, 1, 2);		}
+template <> inline tcu::Vec4	getInputValue<INPUTTYPE_DYNAMIC,	TYPE_FLOAT_VEC4>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(inputNdx); return evalCtx.coords.swizzle(0, 1, 2, 3);	}
+
+template <> inline tcu::Mat2 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT2> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat2 m;
+	m.setColumn(0, evalCtx.in[0].swizzle(0,1));
+	m.setColumn(1, evalCtx.in[1].swizzle(0,1));
+	return m;
+}
+
+template <> inline tcu::Mat3 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT3> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat3 m;
+	m.setColumn(0, evalCtx.in[0].swizzle(0,1,2));
+	m.setColumn(1, evalCtx.in[1].swizzle(0,1,2));
+	m.setColumn(2, evalCtx.in[2].swizzle(0,1,2));
+	return m;
+}
+
+template <> inline tcu::Mat4 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT4> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat4 m;
+	m.setColumn(0, evalCtx.in[0]);
+	m.setColumn(1, evalCtx.in[1]);
+	m.setColumn(2, evalCtx.in[2]);
+	m.setColumn(3, evalCtx.in[3]);
+	return m;
+}
+
+// Reduction from expression result to vec3.
+
+inline tcu::Vec3 reduceToVec3 (const tcu::Vec2& value) { return value.swizzle(0,1,0); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Vec3& value) { return value; }
+inline tcu::Vec3 reduceToVec3 (const tcu::Vec4& value) { return tcu::Vec3(value.x(), value.y(), value.z()+value.w()); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat2& value) { return tcu::Vec3(value(0, 0), value(0, 1), value(1, 0)+value(1, 1)); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat3& value) { return value.getColumn(0) + value.getColumn(1) + value.getColumn(2); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat4& value) { return value.getColumn(0).swizzle(0,1,2) + value.getColumn(1).swizzle(1,2,3) + value.getColumn(2).swizzle(2,3,0) + value.getColumn(3).swizzle(3,0,1); }
+
+// matrixCompMult
+
+template <typename T, int Rows, int Cols>
+tcu::Matrix<T, Rows, Cols> matrixCompMult (const tcu::Matrix<T, Rows, Cols>& a, const tcu::Matrix<T, Rows, Cols>& b)
+{
+	tcu::Matrix<T, Rows, Cols> retVal;
+
+	for (int r = 0; r < Rows; ++r)
+		for (int c = 0; c < Cols; ++c)
+			retVal(r,c) = a(r,c) * b(r, c);
+
+	return retVal;
+}
+
+// negate
+
+template <typename T, int Rows, int Cols>
+tcu::Matrix<T, Rows, Cols> negate (const tcu::Matrix<T, Rows, Cols>& mat)
+{
+	tcu::Matrix<T, Rows, Cols> retVal;
+
+	for (int r = 0; r < Rows; ++r)
+		for (int c = 0; c < Cols; ++c)
+			retVal(r,c) = -mat(r, c);
+
+	return retVal;
+}
+
+// increment/decrement
+
+template <typename T, int Rows, int Cols>
+tcu::Matrix<T, Rows, Cols> increment (const tcu::Matrix<T, Rows, Cols>& mat)
+{
+	tcu::Matrix<T, Rows, Cols> retVal;
+
+	for (int r = 0; r < Rows; ++r)
+		for (int c = 0; c < Cols; ++c)
+			retVal(r,c) = mat(r, c) + 1.0f;
+
+	return retVal;
+}
+
+template <typename T, int Rows, int Cols>
+tcu::Matrix<T, Rows, Cols> decrement (const tcu::Matrix<T, Rows, Cols>& mat)
+{
+	tcu::Matrix<T, Rows, Cols> retVal;
+
+	for (int r = 0; r < Rows; ++r)
+		for (int c = 0; c < Cols; ++c)
+			retVal(r,c) = mat(r, c) - 1.0f;
+
+	return retVal;
+}
+
+// Evaluator template.
+
+template <int Op, int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator;
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_ADD, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0) + getInputValue<In1Type, In1DataType>(evalCtx, 1));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_SUB, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0) - getInputValue<In1Type, In1DataType>(evalCtx, 1));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_MUL, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0) * getInputValue<In1Type, In1DataType>(evalCtx, 1));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_DIV, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0) / getInputValue<In1Type, In1DataType>(evalCtx, 1));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_COMP_MUL, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(matrixCompMult(getInputValue<In0Type, In0DataType>(evalCtx, 0), getInputValue<In1Type, In1DataType>(evalCtx, 1)));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_UNARY_PLUS, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_NEGATION, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(negate(getInputValue<In0Type, In0DataType>(evalCtx, 0)));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_PRE_INCREMENT, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		// modifying reduction: sum modified value too
+		evalCtx.color.xyz() = reduceToVec3(increment(getInputValue<In0Type, In0DataType>(evalCtx, 0))) + reduceToVec3(increment(getInputValue<In0Type, In0DataType>(evalCtx, 0)));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_PRE_DECREMENT, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		// modifying reduction: sum modified value too
+		evalCtx.color.xyz() = reduceToVec3(decrement(getInputValue<In0Type, In0DataType>(evalCtx, 0))) + reduceToVec3(decrement(getInputValue<In0Type, In0DataType>(evalCtx, 0)));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_POST_INCREMENT, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		// modifying reduction: sum modified value too
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0)) + reduceToVec3(increment(getInputValue<In0Type, In0DataType>(evalCtx, 0)));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_POST_DECREMENT, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		// modifying reduction: sum modified value too
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0)) + reduceToVec3(decrement(getInputValue<In0Type, In0DataType>(evalCtx, 0)));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_ADD_INTO, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0) + getInputValue<In1Type, In1DataType>(evalCtx, 1));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_SUBTRACT_FROM, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0) - getInputValue<In1Type, In1DataType>(evalCtx, 1));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_MULTIPLY_INTO, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0) * getInputValue<In1Type, In1DataType>(evalCtx, 1));
+	}
+};
+
+template <int In0Type, int In0DataType, int In1Type, int In1DataType>
+struct Evaluator<OP_DIVIDE_INTO, In0Type, In0DataType, In1Type, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx)
+	{
+		evalCtx.color.xyz() = reduceToVec3(getInputValue<In0Type, In0DataType>(evalCtx, 0) / getInputValue<In1Type, In1DataType>(evalCtx, 1));
+	}
+};
+
+ShaderEvalFunc getEvalFunc (const ShaderInput& in0, const ShaderInput& in1, MatrixOp op)
+{
+	DE_STATIC_ASSERT(TYPE_LAST		<= (1<<7));
+	DE_STATIC_ASSERT(OP_LAST		<= (1<<4));
+	DE_STATIC_ASSERT(INPUTTYPE_LAST	<= (1<<2));
+
+#define PACK_EVAL_CASE(OP, IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE)	(((OP) << 18) | ((IN0TYPE) << 16) | ((IN0DATATYPE) << 9) | ((IN1TYPE) << 7) | (IN1DATATYPE))
+
+#define MAKE_EVAL_CASE(OP, IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE)		\
+	case PACK_EVAL_CASE(OP, IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE):	\
+		return Evaluator<OP, IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE>::evaluate
+
+#define SCALAR_OPS(IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE)	\
+	MAKE_EVAL_CASE(OP_ADD,		IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_SUB,		IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_MUL,		IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_DIV,		IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE)
+
+#define ALL_OPS(IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE)	\
+	MAKE_EVAL_CASE(OP_ADD,			IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_SUB,			IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_MUL,			IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_DIV,			IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_COMP_MUL,		IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);
+
+#define MUL_OP(IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE)	\
+	MAKE_EVAL_CASE(OP_MUL, IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE)
+
+#define MAKE_MAT_SCALAR_VEC_CASES(OP, TYPE0, TYPE1)				\
+	OP(INPUTTYPE_CONST,		TYPE0, INPUTTYPE_CONST,		TYPE1);	\
+	OP(INPUTTYPE_DYNAMIC,	TYPE0, INPUTTYPE_CONST,		TYPE1);	\
+	OP(INPUTTYPE_CONST,		TYPE0, INPUTTYPE_DYNAMIC,	TYPE1);	\
+	OP(INPUTTYPE_DYNAMIC,	TYPE0, INPUTTYPE_DYNAMIC,	TYPE1)
+
+#define MAKE_MAT_MAT_CASES(OP, MATTYPE)								\
+	OP(INPUTTYPE_CONST,		MATTYPE, INPUTTYPE_CONST,	MATTYPE);	\
+	OP(INPUTTYPE_DYNAMIC,	MATTYPE, INPUTTYPE_CONST,	MATTYPE)
+
+#define UNARY_OP(IN0TYPE, IN0DATATYPE)														\
+	MAKE_EVAL_CASE(OP_UNARY_PLUS,		IN0TYPE, IN0DATATYPE, INPUTTYPE_CONST, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_NEGATION,			IN0TYPE, IN0DATATYPE, INPUTTYPE_CONST, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_PRE_INCREMENT,	IN0TYPE, IN0DATATYPE, INPUTTYPE_CONST, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_PRE_DECREMENT,	IN0TYPE, IN0DATATYPE, INPUTTYPE_CONST, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_POST_INCREMENT,	IN0TYPE, IN0DATATYPE, INPUTTYPE_CONST, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_POST_DECREMENT,	IN0TYPE, IN0DATATYPE, INPUTTYPE_CONST, TYPE_LAST)
+
+#define MAKE_UNARY_CASES(OP, MATTYPE)	\
+	OP(INPUTTYPE_CONST,		MATTYPE);	\
+	OP(INPUTTYPE_DYNAMIC,	MATTYPE)
+
+#define ASSIGN_OP(IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE)							\
+	MAKE_EVAL_CASE(OP_ADD_INTO,			IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_SUBTRACT_FROM,	IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_MULTIPLY_INTO,	IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_DIVIDE_INTO,		IN0TYPE, IN0DATATYPE, IN1TYPE, IN1DATATYPE)
+
+#define MAKE_ASSIGNMENT_CASES(OP, MATTYPE)						\
+	OP(INPUTTYPE_CONST,		MATTYPE, INPUTTYPE_CONST,	MATTYPE);	\
+	OP(INPUTTYPE_DYNAMIC,	MATTYPE, INPUTTYPE_CONST,	MATTYPE);	\
+	OP(INPUTTYPE_CONST,		MATTYPE, INPUTTYPE_DYNAMIC,	MATTYPE);	\
+	OP(INPUTTYPE_DYNAMIC,	MATTYPE, INPUTTYPE_DYNAMIC,	MATTYPE)
+
+	// \note At the moment there is no difference between uniform and const inputs. This saves binary size.
+	InputType in0Type = in0.inputType == INPUTTYPE_DYNAMIC ? INPUTTYPE_DYNAMIC : INPUTTYPE_CONST;
+	InputType in1Type = in1.inputType == INPUTTYPE_DYNAMIC ? INPUTTYPE_DYNAMIC : INPUTTYPE_CONST;
+
+	switch (PACK_EVAL_CASE(op, in0Type, in0.dataType, in1Type, in1.dataType))
+	{
+		// Matrix-scalar.
+		MAKE_MAT_SCALAR_VEC_CASES(SCALAR_OPS,	TYPE_FLOAT_MAT2, TYPE_FLOAT);
+		MAKE_MAT_SCALAR_VEC_CASES(SCALAR_OPS,	TYPE_FLOAT_MAT3, TYPE_FLOAT);
+		MAKE_MAT_SCALAR_VEC_CASES(SCALAR_OPS,	TYPE_FLOAT_MAT4, TYPE_FLOAT);
+
+		// Matrix-vector.
+		MAKE_MAT_SCALAR_VEC_CASES(MUL_OP,		TYPE_FLOAT_MAT2, TYPE_FLOAT_VEC2);
+		MAKE_MAT_SCALAR_VEC_CASES(MUL_OP,		TYPE_FLOAT_MAT3, TYPE_FLOAT_VEC3);
+		MAKE_MAT_SCALAR_VEC_CASES(MUL_OP,		TYPE_FLOAT_MAT4, TYPE_FLOAT_VEC4);
+
+		// Vector-matrix.
+		MAKE_MAT_SCALAR_VEC_CASES(MUL_OP,		TYPE_FLOAT_VEC2, TYPE_FLOAT_MAT2);
+		MAKE_MAT_SCALAR_VEC_CASES(MUL_OP,		TYPE_FLOAT_VEC3, TYPE_FLOAT_MAT3);
+		MAKE_MAT_SCALAR_VEC_CASES(MUL_OP,		TYPE_FLOAT_VEC4, TYPE_FLOAT_MAT4);
+
+		// Matrix-matrix.
+		MAKE_MAT_MAT_CASES(ALL_OPS,	TYPE_FLOAT_MAT2);
+		MAKE_MAT_MAT_CASES(ALL_OPS,	TYPE_FLOAT_MAT3);
+		MAKE_MAT_MAT_CASES(ALL_OPS,	TYPE_FLOAT_MAT4);
+
+		// Unary matrix
+		MAKE_UNARY_CASES(UNARY_OP, TYPE_FLOAT_MAT2);
+		MAKE_UNARY_CASES(UNARY_OP, TYPE_FLOAT_MAT3);
+		MAKE_UNARY_CASES(UNARY_OP, TYPE_FLOAT_MAT4);
+
+		// Assignment matrix
+		MAKE_ASSIGNMENT_CASES(ASSIGN_OP, TYPE_FLOAT_MAT2);
+		MAKE_ASSIGNMENT_CASES(ASSIGN_OP, TYPE_FLOAT_MAT3);
+		MAKE_ASSIGNMENT_CASES(ASSIGN_OP, TYPE_FLOAT_MAT4);
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+
+#undef PACK_EVAL_CASE
+#undef MAKE_EVAL_CASE
+#undef MUL_OP
+#undef ALL_OPS
+#undef MAKE_MAT_SCALAR_VEC_CASES
+#undef MAKE_MAT_MAT_CASES
+}
+
+// Shader source format utilities.
+
+template <int Size>
+void writeVectorConstructor (std::ostream& str, const tcu::Vector<float, Size>& v)
+{
+	str << "vec" << Size << "(";
+	for (int ndx = 0; ndx < Size; ndx++)
+	{
+		if (ndx != 0)
+			str << ", ";
+		str << de::floatToString(v[ndx], 1);
+	}
+	str << ")";
+}
+
+template <int Cols, int Rows>
+void writeMatrixConstructor (std::ostream& str, const tcu::Matrix<float, Rows, Cols>& m)
+{
+	if (Rows == Cols)
+		str << "mat" << Cols;
+	else
+		str << "mat" << Cols << "x" << Rows;
+
+	str << "(";
+	for (int colNdx = 0; colNdx < Cols; colNdx++)
+	{
+		for (int rowNdx = 0; rowNdx < Rows; rowNdx++)
+		{
+			if (rowNdx > 0 || colNdx > 0)
+				str << ", ";
+			str << de::floatToString(m(rowNdx, colNdx), 1);
+		}
+	}
+	str << ")";
+}
+
+} // MatrixCaseUtils
+
+using namespace MatrixCaseUtils;
+
+class ShaderMatrixCase : public ShaderRenderCase
+{
+public:
+					ShaderMatrixCase			(Context& context, const char* name, const char* desc, const ShaderInput& in0, const ShaderInput& in1, MatrixOp op, bool isVertexCase);
+					~ShaderMatrixCase			(void);
+
+	void			init						(void);
+
+protected:
+	std::string		genGLSLMatToVec3Reduction	(const glu::DataType& matType, const char* varName);
+	void			setupUniforms				(int programID, const tcu::Vec4& constCoords);
+
+private:
+	ShaderInput		m_in0;
+	ShaderInput		m_in1;
+	MatrixOp		m_op;
+};
+
+ShaderMatrixCase::ShaderMatrixCase (Context& context, const char* name, const char* desc, const ShaderInput& in0, const ShaderInput& in1, MatrixOp op, bool isVertexCase)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, desc, isVertexCase, getEvalFunc(in0, in1, op))
+	, m_in0				(in0)
+	, m_in1				(in1)
+	, m_op				(op)
+{
+}
+
+ShaderMatrixCase::~ShaderMatrixCase (void)
+{
+}
+
+void ShaderMatrixCase::init (void)
+{
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op				= m_isVertexCase ? vtx : frag;
+
+	bool				isInDynMat0		= isDataTypeMatrix(m_in0.dataType) && m_in0.inputType == INPUTTYPE_DYNAMIC;
+	bool				isInDynMat1		= isDataTypeMatrix(m_in1.dataType) && m_in1.inputType == INPUTTYPE_DYNAMIC;
+	string				inValue0;
+	string				inValue1;
+	DataType			resultType		= TYPE_LAST;
+	Precision			resultPrec		= m_in0.precision;
+	vector<string>		passVars;
+	int					numInputs		= (isOperationBinary(m_op)) ? (2) : (1);
+
+	std::string			operationValue0;
+	std::string			operationValue1;
+
+	DE_ASSERT(!isInDynMat0 || !isInDynMat1); // Only single dynamic matrix input is allowed.
+	DE_UNREF(isInDynMat0 && isInDynMat1);
+
+	// Compute result type.
+	if (isDataTypeMatrix(m_in0.dataType) && isDataTypeMatrix(m_in1.dataType))
+	{
+		DE_ASSERT(m_in0.dataType == m_in1.dataType);
+		resultType = m_in0.dataType;
+	}
+	else if (getOperationType(m_op) == OPERATIONTYPE_UNARY_PREFIX_OPERATOR ||
+			 getOperationType(m_op) == OPERATIONTYPE_UNARY_POSTFIX_OPERATOR)
+	{
+		resultType = m_in0.dataType;
+	}
+	else
+	{
+		int			matNdx		= isDataTypeMatrix(m_in0.dataType) ? 0 : 1;
+		DataType	matrixType	= matNdx == 0 ? m_in0.dataType : m_in1.dataType;
+		DataType	otherType	= matNdx == 0 ? m_in1.dataType : m_in0.dataType;
+
+		if (otherType == TYPE_FLOAT)
+			resultType = matrixType;
+		else
+		{
+			DE_ASSERT(isDataTypeVector(otherType));
+			resultType = otherType;
+		}
+	}
+
+	vtx << "attribute highp vec4 a_position;\n";
+	if (m_isVertexCase)
+	{
+		vtx << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+
+	// Input declarations.
+	for (int inNdx = 0; inNdx < numInputs; inNdx++)
+	{
+		const ShaderInput&	in			= inNdx > 0 ? m_in1 : m_in0;
+		const char*			precName	= getPrecisionName(in.precision);
+		const char*			typeName	= getDataTypeName(in.dataType);
+		string&				inValue		= inNdx > 0 ? inValue1 : inValue0;
+
+		if (in.inputType == INPUTTYPE_DYNAMIC)
+		{
+			vtx << "attribute " << precName << " " << typeName << " a_";
+
+			if (isDataTypeMatrix(in.dataType))
+			{
+				// a_matN, v_matN
+				vtx << typeName << ";\n";
+				if (!m_isVertexCase)
+				{
+					vtx << "varying " << precName << " " << typeName << " v_" << typeName << ";\n";
+					frag << "varying " << precName << " " << typeName << " v_" << typeName << ";\n";
+					passVars.push_back(typeName);
+				}
+
+				inValue = string(m_isVertexCase ? "a_" : "v_") + getDataTypeName(in.dataType);
+			}
+			else
+			{
+				// a_coords, v_coords
+				vtx << "coords;\n";
+				if (!m_isVertexCase)
+				{
+					vtx << "varying " << precName << " " << typeName << " v_coords;\n";
+					frag << "varying " << precName << " " << typeName << " v_coords;\n";
+					passVars.push_back("coords");
+				}
+
+				inValue = m_isVertexCase ? "a_coords" : "v_coords";
+			}
+		}
+		else if (in.inputType == INPUTTYPE_UNIFORM)
+		{
+			op << "uniform " << precName << " " << typeName << " u_in" << inNdx << ";\n";
+			inValue = string("u_in") + de::toString(inNdx);
+		}
+		else if (in.inputType == INPUTTYPE_CONST)
+		{
+			op << "const " << precName << " " << typeName << " in" << inNdx << " = ";
+
+			// Generate declaration.
+			switch (in.dataType)
+			{
+				case TYPE_FLOAT:		op << de::floatToString(s_constInFloat[inNdx], 1);					break;
+				case TYPE_FLOAT_VEC2:	writeVectorConstructor<2>(op, s_constInVec2[inNdx]);				break;
+				case TYPE_FLOAT_VEC3:	writeVectorConstructor<3>(op, s_constInVec3[inNdx]);				break;
+				case TYPE_FLOAT_VEC4:	writeVectorConstructor<4>(op, s_constInVec4[inNdx]);				break;
+				case TYPE_FLOAT_MAT2:	writeMatrixConstructor<2, 2>(op, Mat2(s_constInMat2[inNdx]));		break;
+				case TYPE_FLOAT_MAT3:	writeMatrixConstructor<3, 3>(op, Mat3(s_constInMat3[inNdx]));		break;
+				case TYPE_FLOAT_MAT4:	writeMatrixConstructor<4, 4>(op, Mat4(s_constInMat4[inNdx]));		break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+
+			op << ";\n";
+
+			inValue = string("in") + de::toString(inNdx);
+		}
+	}
+
+	vtx << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	gl_Position = a_position;\n";
+	frag << "\n"
+		 << "void main (void)\n"
+		 << "{\n";
+
+	if (m_isVertexCase)
+	{
+		frag << "	gl_FragColor = v_color;\n";
+	}
+	else
+	{
+		for (vector<string>::const_iterator copyIter = passVars.begin(); copyIter != passVars.end(); copyIter++)
+			vtx << "	v_" << *copyIter << " = " << "a_" << *copyIter << ";\n";
+	}
+
+	// Operation.
+
+	switch (getOperationNature(m_op))
+	{
+		case OPERATIONNATURE_PURE:
+			DE_ASSERT(getOperationType(m_op) != OPERATIONTYPE_ASSIGNMENT);
+
+			operationValue0 = inValue0;
+			operationValue1 = inValue1;
+			break;
+
+		case OPERATIONNATURE_MUTATING:
+			DE_ASSERT(getOperationType(m_op) != OPERATIONTYPE_ASSIGNMENT);
+
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " tmpValue = " << inValue0 << ";\n";
+
+			operationValue0 = "tmpValue";
+			operationValue1 = inValue1;
+			break;
+
+		case OPERATIONNATURE_ASSIGNMENT:
+			DE_ASSERT(getOperationType(m_op) == OPERATIONTYPE_ASSIGNMENT);
+
+			operationValue0 = inValue0;
+			operationValue1 = inValue1;
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	switch (getOperationType(m_op))
+	{
+		case OPERATIONTYPE_BINARY_OPERATOR:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << operationValue0 << " " << getOperationName(m_op) << " " << operationValue1 << ";\n";
+			break;
+
+		case OPERATIONTYPE_UNARY_PREFIX_OPERATOR:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << getOperationName(m_op) << operationValue0 << ";\n";
+			break;
+
+		case OPERATIONTYPE_UNARY_POSTFIX_OPERATOR:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << operationValue0 << getOperationName(m_op) << ";\n";
+			break;
+
+		case OPERATIONTYPE_BINARY_FUNCTION:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << getOperationName(m_op) << "(" << operationValue0 << ", " << operationValue1 << ");\n";
+			break;
+
+		case OPERATIONTYPE_ASSIGNMENT:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << operationValue0 << ";\n";
+			op << "	res " << getOperationName(m_op) << " " << operationValue1 << ";\n";
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// Reduction to vec3 (rgb). Check the used value too if it was modified.
+	op << "	" << (m_isVertexCase ? "v_color" : "gl_FragColor") << " = ";
+
+	if (isOperationValueModifying(m_op))
+		op << "vec4(" << genGLSLMatToVec3Reduction(resultType, "res") << ", 1.0) + vec4(" << genGLSLMatToVec3Reduction(resultType, "tmpValue") << ", 0.0);\n";
+	else
+		op << "vec4(" << genGLSLMatToVec3Reduction(resultType, "res") << ", 1.0);\n";
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource	= vtx.str();
+	m_fragShaderSource	= frag.str();
+
+	// \todo [2012-02-14 pyry] Compute better values for matrix tests.
+	m_userAttribTransforms.resize(4);
+	for (int attribNdx = 0; attribNdx < 4; attribNdx++)
+	{
+		m_userAttribTransforms[attribNdx] = Mat4(0.0f);
+		m_userAttribTransforms[attribNdx]((0 + attribNdx) % 4, 0) = 1.0f;
+		m_userAttribTransforms[attribNdx]((1 + attribNdx) % 4, 1) = 1.0f;
+		m_userAttribTransforms[attribNdx]((2 + attribNdx) % 4, 2) = 1.0f;
+		m_userAttribTransforms[attribNdx]((3 + attribNdx) % 4, 3) = 1.0f;
+	}
+
+	// prevent bad reference cases such as black result images by fine-tuning used matrices
+	if (getOperationTestMatrixType(m_op) != TESTMATRIXTYPE_DEFAULT)
+	{
+		for (int attribNdx = 0; attribNdx < 4; attribNdx++)
+		{
+			for (int row = 0; row < 4; row++)
+			for (int col = 0; col < 4; col++)
+			{
+				switch (getOperationTestMatrixType(m_op))
+				{
+					case TESTMATRIXTYPE_NEGATED:
+						m_userAttribTransforms[attribNdx](row, col) = -m_userAttribTransforms[attribNdx](row, col);
+						break;
+					case TESTMATRIXTYPE_INCREMENTED:
+						m_userAttribTransforms[attribNdx](row, col) += 0.3f;
+						break;
+					case TESTMATRIXTYPE_DECREMENTED:
+						m_userAttribTransforms[attribNdx](row, col) -= 0.1f;
+						break;
+
+					default:
+						DE_ASSERT(DE_FALSE);
+						break;
+				}
+			}
+		}
+	}
+
+	ShaderRenderCase::init();
+}
+
+std::string ShaderMatrixCase::genGLSLMatToVec3Reduction (const glu::DataType& matType, const char* varName)
+{
+	std::ostringstream op;
+
+	switch (matType)
+	{
+		case TYPE_FLOAT:		op << varName << ", "		<< varName << ", "			<< varName << "";										break;
+		case TYPE_FLOAT_VEC2:	op << varName << ".x, "		<< varName << ".y, "		<< varName << ".x";										break;
+		case TYPE_FLOAT_VEC3:	op << varName << "";																							break;
+		case TYPE_FLOAT_VEC4:	op << varName << ".x, "		<< varName << ".y, "		<< varName << ".z+"			<< varName << ".w";			break;
+		case TYPE_FLOAT_MAT2:	op << varName << "[0][0], "	<< varName << "[1][0], "	<< varName << "[0][1]+"		<< varName << "[1][1]";		break;
+		case TYPE_FLOAT_MAT3:	op << varName << "[0]+"		<< varName << "[1]+"		<< varName << "[2]";									break;
+		case TYPE_FLOAT_MAT4:	op << varName << "[0].xyz+"	<< varName << "[1].yzw+"	<< varName << "[2].zwx+"	<< varName << "[3].wxy";	break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return op.str();
+}
+
+void ShaderMatrixCase::setupUniforms (int programID, const tcu::Vec4& constCoords)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	DE_UNREF(constCoords);
+
+	for (int inNdx = 0; inNdx < 2; inNdx++)
+	{
+		const ShaderInput& in = inNdx > 0 ? m_in1 : m_in0;
+
+		if (in.inputType == INPUTTYPE_UNIFORM)
+		{
+			int loc = gl.getUniformLocation(programID, (string("u_in") + de::toString(inNdx)).c_str());
+
+			if (loc < 0)
+				continue;
+
+			switch (in.dataType)
+			{
+				case TYPE_FLOAT:		gl.uniform1f(loc, s_constInFloat[inNdx]);													break;
+				case TYPE_FLOAT_VEC2:	gl.uniform2fv(loc, 1, s_constInVec2[inNdx].getPtr());										break;
+				case TYPE_FLOAT_VEC3:	gl.uniform3fv(loc, 1, s_constInVec3[inNdx].getPtr());										break;
+				case TYPE_FLOAT_VEC4:	gl.uniform4fv(loc, 1, s_constInVec4[inNdx].getPtr());										break;
+				case TYPE_FLOAT_MAT2:	gl.uniformMatrix2fv(loc, 1, GL_FALSE, s_constInMat2[inNdx].getColumnMajorData().getPtr());	break;
+				case TYPE_FLOAT_MAT3:	gl.uniformMatrix3fv(loc, 1, GL_FALSE, s_constInMat3[inNdx].getColumnMajorData().getPtr());	break;
+				case TYPE_FLOAT_MAT4:	gl.uniformMatrix4fv(loc, 1, GL_FALSE, s_constInMat4[inNdx].getColumnMajorData().getPtr());	break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+	}
+}
+
+ShaderMatrixTests::ShaderMatrixTests (Context& context)
+	: TestCaseGroup(context, "matrix", "Matrix Tests")
+{
+}
+
+ShaderMatrixTests::~ShaderMatrixTests (void)
+{
+}
+
+void ShaderMatrixTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		const char*		desc;
+		MatrixOp		op;
+		bool			extendedInputTypeCases; // !< test with const and uniform types too
+	} ops[] =
+	{
+		{ "add",			"Matrix addition tests",						OP_ADD,				true	},
+		{ "sub",			"Matrix subtraction tests",						OP_SUB,				true	},
+		{ "mul",			"Matrix multiplication tests",					OP_MUL,				true	},
+		{ "div",			"Matrix division tests",						OP_DIV,				true	},
+		{ "matrixcompmult",	"Matrix component-wise multiplication tests",	OP_COMP_MUL,		false	},
+		{ "unary_addition",	"Matrix unary addition tests",					OP_UNARY_PLUS,		false	},
+		{ "negation",		"Matrix negation tests",						OP_NEGATION,		false	},
+		{ "pre_increment",	"Matrix prefix increment tests",				OP_PRE_INCREMENT,	false	},
+		{ "pre_decrement",	"Matrix prefix decrement tests",				OP_PRE_DECREMENT,	false	},
+		{ "post_increment",	"Matrix postfix increment tests",				OP_POST_INCREMENT,	false	},
+		{ "post_decrement",	"Matrix postfix decrement tests",				OP_POST_DECREMENT,	false	},
+		{ "add_assign",		"Matrix add into tests",						OP_ADD_INTO,		false	},
+		{ "sub_assign",		"Matrix subtract from tests",					OP_SUBTRACT_FROM,	false	},
+		{ "mul_assign",		"Matrix multiply into tests",					OP_MULTIPLY_INTO,	false	},
+		{ "div_assign",		"Matrix divide into tests",						OP_DIVIDE_INTO,		false	},
+	};
+
+	struct InputTypeSpec
+	{
+		const char*		name;
+		const char*		desc;
+		InputType		type;
+	};
+	static const InputTypeSpec extendedInputTypes[] =
+	{
+		{ "const",		"Constant matrix input",	INPUTTYPE_CONST		},
+		{ "uniform",	"Uniform matrix input",		INPUTTYPE_UNIFORM	},
+		{ "dynamic",	"Dynamic matrix input",		INPUTTYPE_DYNAMIC	}
+	};
+	static const InputTypeSpec reducedInputTypes[] =
+	{
+		{ "dynamic",	"Dynamic matrix input",		INPUTTYPE_DYNAMIC	}
+	};
+
+	static const DataType matrixTypes[] =
+	{
+		TYPE_FLOAT_MAT2,
+		TYPE_FLOAT_MAT3,
+		TYPE_FLOAT_MAT4
+	};
+
+	static const Precision precisions[] =
+	{
+		PRECISION_LOWP,
+		PRECISION_MEDIUMP,
+		PRECISION_HIGHP
+	};
+
+	for (int opNdx = 0; opNdx < DE_LENGTH_OF_ARRAY(ops); opNdx++)
+	{
+		const InputTypeSpec*	inTypeList		= (ops[opNdx].extendedInputTypeCases) ? (extendedInputTypes) : (reducedInputTypes);
+		const int				inTypeListSize	= (ops[opNdx].extendedInputTypeCases) ? (DE_LENGTH_OF_ARRAY(extendedInputTypes)) : (DE_LENGTH_OF_ARRAY(reducedInputTypes));
+		const MatrixOp			op				= ops[opNdx].op;
+		tcu::TestCaseGroup*		opGroup			= new tcu::TestCaseGroup(m_testCtx, ops[opNdx].name, ops[opNdx].desc);
+
+		addChild(opGroup);
+
+		for (int inTypeNdx = 0; inTypeNdx < inTypeListSize; inTypeNdx++)
+		{
+			const InputType		inputType	= inTypeList[inTypeNdx].type;
+
+			for (int matTypeNdx = 0; matTypeNdx < DE_LENGTH_OF_ARRAY(matrixTypes); matTypeNdx++)
+			{
+				DataType	matType		= matrixTypes[matTypeNdx];
+				const char*	matTypeName	= getDataTypeName(matType);
+
+				for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+				{
+					Precision	precision	= precisions[precNdx];
+					const char*	precName	= getPrecisionName(precision);
+					string		baseName	= string(inTypeList[inTypeNdx].name) + "_" + precName + "_" + matTypeName + "_";
+					ShaderInput	matIn		(inputType, matType, precision);
+
+					if (isOperationMatrixScalar(op))
+					{
+						// Matrix-scalar \note For div cases we use uniform input.
+						ShaderInput scalarIn(op == OP_DIV ? INPUTTYPE_UNIFORM : INPUTTYPE_DYNAMIC, TYPE_FLOAT, precision);
+						opGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "float_vertex").c_str(),		"Matrix-scalar case", matIn, scalarIn, op, true));
+						opGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "float_fragment").c_str(),	"Matrix-scalar case", matIn, scalarIn, op, false));
+					}
+
+					if (isOperationMatrixVector(op))
+					{
+						// Matrix-vector.
+						DataType	vecType	= getDataTypeFloatVec(getDataTypeMatrixNumColumns(matType));
+						ShaderInput vecIn	(op == OP_DIV ? INPUTTYPE_UNIFORM : INPUTTYPE_DYNAMIC, vecType, precision);
+
+						opGroup->addChild(new ShaderMatrixCase(m_context, (baseName + getDataTypeName(vecType) + "_vertex").c_str(),	"Matrix-vector case", matIn, vecIn, op, true));
+						opGroup->addChild(new ShaderMatrixCase(m_context, (baseName + getDataTypeName(vecType) + "_fragment").c_str(),	"Matrix-vector case", matIn, vecIn, op, false));
+
+						// Vector-matrix.
+						string vecMatName = string(inTypeList[inTypeNdx].name) + "_" + precName + "_" + getDataTypeName(vecType) + "_" + matTypeName;
+						opGroup->addChild(new ShaderMatrixCase(m_context, (vecMatName + "_vertex").c_str(),		"Vector-matrix case", vecIn, matIn, op, true));
+						opGroup->addChild(new ShaderMatrixCase(m_context, (vecMatName + "_fragment").c_str(),	"Vector-matrix case", vecIn, matIn, op, false));
+					}
+
+					if (isOperationMatrixMatrix(op))
+					{
+						// Matrix-matrix.
+						ShaderInput otherMatIn(inputType == INPUTTYPE_DYNAMIC ? INPUTTYPE_UNIFORM : inputType, matType, precision);
+						opGroup->addChild(new ShaderMatrixCase(m_context, (baseName + matTypeName + "_vertex").c_str(),		"Matrix-matrix case", matIn, otherMatIn, op, true));
+						opGroup->addChild(new ShaderMatrixCase(m_context, (baseName + matTypeName + "_fragment").c_str(),	"Matrix-matrix case", matIn, otherMatIn, op, false));
+					}
+
+					if (isOperationUnary(op))
+					{
+						// op matrix
+						ShaderInput voidInput(INPUTTYPE_LAST, TYPE_LAST, PRECISION_LAST);
+						opGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "vertex").c_str(),		"Matrix case", matIn, voidInput, op, true));
+						opGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "fragment").c_str(),	"Matrix case", matIn, voidInput, op, false));
+					}
+
+					if (isOperationAssignment(op))
+					{
+						ShaderInput otherMatIn(inputType == INPUTTYPE_DYNAMIC ? INPUTTYPE_UNIFORM : inputType, matType, precision);
+						opGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "vertex").c_str(),		"Matrix assignment case", matIn, otherMatIn, op, true));
+						opGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "fragment").c_str(),	"Matrix assignment case", matIn, otherMatIn, op, false));
+					}
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderMatrixTests.hpp b/modules/gles2/functional/es2fShaderMatrixTests.hpp
new file mode 100644
index 0000000..a58478f
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderMatrixTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERMATRIXTESTS_HPP
+#define _ES2FSHADERMATRIXTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader matrix arithmetic tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderMatrixTests : public TestCaseGroup
+{
+public:
+							ShaderMatrixTests		(Context& context);
+	virtual					~ShaderMatrixTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderMatrixTests		(const ShaderMatrixTests&);		// not allowed!
+	ShaderMatrixTests&		operator=				(const ShaderMatrixTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERMATRIXTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderOperatorTests.cpp b/modules/gles2/functional/es2fShaderOperatorTests.cpp
new file mode 100644
index 0000000..64f5854
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderOperatorTests.cpp
@@ -0,0 +1,1530 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader operators tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderOperatorTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuVectorUtil.hpp"
+
+#include "deStringUtil.hpp"
+#include "deInt32.h"
+#include "deMemory.h"
+
+#include <map>
+
+using namespace tcu;
+using namespace glu;
+using namespace deqp::gls;
+
+using std::map;
+using std::pair;
+using std::vector;
+using std::string;
+using std::ostringstream;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+#if defined(abs)
+#	undef abs
+#endif
+
+using de::min;
+using de::max;
+using de::clamp;
+
+// \note VS2013 gets confused without these
+using tcu::exp2;
+using tcu::log2;
+
+inline float abs		(float v)			{ return deFloatAbs(v); }
+
+inline bool logicalAnd	(bool a, bool b)	{ return (a && b); }
+inline bool logicalOr	(bool a, bool b)	{ return (a || b); }
+inline bool logicalXor	(bool a, bool b)	{ return (a != b); }
+
+#define DEFINE_VEC_FLOAT_FUNCTION(FUNC_NAME, SCALAR_OP_NAME)					\
+template<int Size>																\
+inline Vector<float, Size> FUNC_NAME (const Vector<float, Size>& v, float s)	\
+{																				\
+	Vector<float, Size> res;													\
+	for (int i = 0; i < Size; i++)												\
+		res[i] = SCALAR_OP_NAME(v[i], s);										\
+	return res;																	\
+}
+
+#define DEFINE_FLOAT_VEC_FUNCTION(FUNC_NAME, SCALAR_OP_NAME)					\
+template<int Size>																\
+inline Vector<float, Size> FUNC_NAME (float s, const Vector<float, Size>& v)	\
+{																				\
+	Vector<float, Size> res;													\
+	for (int i = 0; i < Size; i++)												\
+		res[i] = SCALAR_OP_NAME(s, v[i]);										\
+	return res;																	\
+}
+
+#define DEFINE_VEC_VEC_FLOAT_FUNCTION(FUNC_NAME, SCALAR_OP_NAME)												\
+template<int Size>																								\
+inline Vector<float, Size> FUNC_NAME (const Vector<float, Size>& v0, const Vector<float, Size>& v1, float s)	\
+{																												\
+	Vector<float, Size> res;																					\
+	for (int i = 0; i < Size; i++)																				\
+		res[i] = SCALAR_OP_NAME(v0[i], v1[i], s);																\
+	return res;																									\
+}
+
+#define DEFINE_VEC_FLOAT_FLOAT_FUNCTION(FUNC_NAME, SCALAR_OP_NAME)							\
+template<int Size>																			\
+inline Vector<float, Size> FUNC_NAME (const Vector<float, Size>& v, float s0, float s1)		\
+{																							\
+	Vector<float, Size> res;																\
+	for (int i = 0; i < Size; i++)															\
+		res[i] = SCALAR_OP_NAME(v[i], s0, s1);												\
+	return res;																				\
+}
+
+#define DEFINE_FLOAT_FLOAT_VEC_FUNCTION(FUNC_NAME, SCALAR_OP_NAME)							\
+template<int Size>																			\
+inline Vector<float, Size> FUNC_NAME (float s0, float s1, const Vector<float, Size>& v)		\
+{																							\
+	Vector<float, Size> res;																\
+	for (int i = 0; i < Size; i++)															\
+		res[i] = SCALAR_OP_NAME(s0, s1, v[i]);												\
+	return res;																				\
+}
+
+DEFINE_VEC_FLOAT_FUNCTION		(modVecFloat,				mod)
+DEFINE_VEC_FLOAT_FUNCTION		(minVecFloat,				min)
+DEFINE_VEC_FLOAT_FUNCTION		(maxVecFloat,				max)
+DEFINE_VEC_FLOAT_FLOAT_FUNCTION	(clampVecFloatFloat,		clamp)
+DEFINE_VEC_VEC_FLOAT_FUNCTION	(mixVecVecFloat,			mix)
+DEFINE_FLOAT_VEC_FUNCTION		(stepFloatVec,				step)
+DEFINE_FLOAT_FLOAT_VEC_FUNCTION	(smoothStepFloatFloatVec,	smoothStep)
+
+#undef DEFINE_VEC_FLOAT_FUNCTION
+#undef DEFINE_VEC_VEC_FLOAT_FUNCTION
+#undef DEFINE_VEC_FLOAT_FLOAT_FUNCTION
+#undef DEFINE_FLOAT_FLOAT_VEC_FUNCTION
+
+inline float	addOne (float v)	{ return v + 1.0f; };
+inline float	subOne (float v)	{ return v - 1.0f; };
+inline int		addOne (int v)		{ return v + 1; };
+inline int		subOne (int v)		{ return v - 1; };
+
+template<int Size> inline Vector<float, Size>	addOne (const Vector<float, Size>& v)	{ return v + 1.0f; };
+template<int Size> inline Vector<float, Size>	subOne (const Vector<float, Size>& v)	{ return v - 1.0f; };
+template<int Size> inline Vector<int, Size>		addOne (const Vector<int, Size>& v)		{ return v + 1; };
+template<int Size> inline Vector<int, Size>		subOne (const Vector<int, Size>& v)		{ return v - 1; };
+
+template<typename T> inline T selection	(bool cond, T a, T b)	{ return cond ? a : b; };
+
+template<typename T, int Size> inline Vector<T, Size> addVecScalar	(const Vector<T, Size>& v, T s) { return v + s; };
+template<typename T, int Size> inline Vector<T, Size> subVecScalar	(const Vector<T, Size>& v, T s) { return v - s; };
+template<typename T, int Size> inline Vector<T, Size> mulVecScalar	(const Vector<T, Size>& v, T s) { return v * s; };
+template<typename T, int Size> inline Vector<T, Size> divVecScalar	(const Vector<T, Size>& v, T s) { return v / s; };
+
+template<typename T, int Size> inline Vector<T, Size> addScalarVec	(T s, const Vector<T, Size>& v) { return s + v; };
+template<typename T, int Size> inline Vector<T, Size> subScalarVec	(T s, const Vector<T, Size>& v) { return s - v; };
+template<typename T, int Size> inline Vector<T, Size> mulScalarVec	(T s, const Vector<T, Size>& v) { return s * v; };
+template<typename T, int Size> inline Vector<T, Size> divScalarVec	(T s, const Vector<T, Size>& v) { return s / v; };
+
+// Reference functions for specific sequence operations for the sequence operator tests.
+
+// Reference for expression "in0, in2 + in1, in1 + in0"
+inline Vec4		sequenceNoSideEffCase0 (const Vec4& in0, const Vec4& in1, const Vec4& in2)		{ DE_UNREF(in2); return in1 + in0; }
+// Reference for expression "in0, in2 + in1, in1 + in0"
+inline int		sequenceNoSideEffCase1 (float in0, int in1, float in2)							{ DE_UNREF(in0); DE_UNREF(in2); return in1 + in1; }
+// Reference for expression "in0 && in1, in0, ivec2(vec2(in0) + in2)"
+inline IVec2	sequenceNoSideEffCase2 (bool in0, bool in1, const Vec2& in2)					{ DE_UNREF(in1); return IVec2((int)((float)in0 + in2.x()), (int)((float)in0 + in2.y())); }
+// Reference for expression "in0 + vec4(in1), in2, in1"
+inline IVec4	sequenceNoSideEffCase3 (const Vec4& in0, const IVec4& in1, const BVec4& in2)	{ DE_UNREF(in0); DE_UNREF(in2); return in1; }
+// Reference for expression "in0++, in1 = in0 + in2, in2 = in1"
+inline Vec4		sequenceSideEffCase0 (const Vec4& in0, const Vec4& in1, const Vec4& in2)		{ DE_UNREF(in1); return in0 + 1.0f + in2; }
+// Reference for expression "in1++, in0 = float(in1), in1 = int(in0 + in2)"
+inline int		sequenceSideEffCase1 (float in0, int in1, float in2)							{ DE_UNREF(in0); return (int)(in1 + 1.0f + in2); }
+// Reference for expression "in1 = in0, in2++, in2 = in2 + vec2(in1), ivec2(in2)"
+inline IVec2	sequenceSideEffCase2 (bool in0, bool in1, const Vec2& in2)						{ DE_UNREF(in1); return (in2 + Vec2(1.0f) + Vec2((float)in0)).asInt(); }
+// Reference for expression "in0 = in0 + vec4(in2), in1 = in1 + ivec4(in0), in1++"
+inline IVec4	sequenceSideEffCase3 (const Vec4& in0, const IVec4& in1, const BVec4& in2)		{ return in1 + (in0 + Vec4((float)in2.x(), (float)in2.y(), (float)in2.z(), (float)in2.w())).asInt(); }
+
+// ShaderEvalFunc-type wrappers for the above functions.
+void evalSequenceNoSideEffCase0	(ShaderEvalContext& ctx) { ctx.color		= sequenceNoSideEffCase0(ctx.in[0].swizzle(1, 2, 3, 0), ctx.in[1].swizzle(3, 2, 1, 0), ctx.in[2].swizzle(0, 3, 2, 1)); }
+void evalSequenceNoSideEffCase1	(ShaderEvalContext& ctx) { ctx.color.x()	= (float)sequenceNoSideEffCase1(ctx.in[0].z(), (int)ctx.in[1].x(), ctx.in[2].y()); }
+void evalSequenceNoSideEffCase2	(ShaderEvalContext& ctx) { ctx.color.yz()	= sequenceNoSideEffCase2(ctx.in[0].z() > 0.0f, ctx.in[1].x() > 0.0f, ctx.in[2].swizzle(2, 1)).asFloat(); }
+void evalSequenceNoSideEffCase3	(ShaderEvalContext& ctx) { ctx.color		= sequenceNoSideEffCase3(ctx.in[0].swizzle(1, 2, 3, 0), ctx.in[1].swizzle(3, 2, 1, 0).asInt(), greaterThan(ctx.in[2].swizzle(0, 3, 2, 1), Vec4(0.0f, 0.0f, 0.0f, 0.0f))).asFloat(); }
+void evalSequenceSideEffCase0	(ShaderEvalContext& ctx) { ctx.color		= sequenceSideEffCase0(ctx.in[0].swizzle(1, 2, 3, 0), ctx.in[1].swizzle(3, 2, 1, 0), ctx.in[2].swizzle(0, 3, 2, 1)); }
+void evalSequenceSideEffCase1	(ShaderEvalContext& ctx) { ctx.color.x()	= (float)sequenceSideEffCase1(ctx.in[0].z(), (int)ctx.in[1].x(), ctx.in[2].y()); }
+void evalSequenceSideEffCase2	(ShaderEvalContext& ctx) { ctx.color.yz()	= sequenceSideEffCase2(ctx.in[0].z() > 0.0f, ctx.in[1].x() > 0.0f, ctx.in[2].swizzle(2, 1)).asFloat(); }
+void evalSequenceSideEffCase3	(ShaderEvalContext& ctx) { ctx.color		= sequenceSideEffCase3(ctx.in[0].swizzle(1, 2, 3, 0), ctx.in[1].swizzle(3, 2, 1, 0).asInt(), greaterThan(ctx.in[2].swizzle(0, 3, 2, 1), Vec4(0.0f, 0.0f, 0.0f, 0.0f))).asFloat(); }
+
+enum
+{
+	MAX_INPUTS = 3
+};
+
+enum PrecisionMask
+{
+	PRECMASK_NA				= 0,						//!< Precision not applicable (booleans)
+	PRECMASK_LOWP			= (1<<PRECISION_LOWP),
+	PRECMASK_MEDIUMP		= (1<<PRECISION_MEDIUMP),
+	PRECMASK_HIGHP			= (1<<PRECISION_HIGHP),
+
+	PRECMASK_MEDIUMP_HIGHP	= (1<<PRECISION_MEDIUMP) | (1<<PRECISION_HIGHP),
+	PRECMASK_ALL			= (1<<PRECISION_LOWP) | (1<<PRECISION_MEDIUMP) | (1<<PRECISION_HIGHP)
+};
+
+enum ValueType
+{
+	VALUE_NONE			= 0,
+	VALUE_FLOAT			= (1<<0),	// float scalar
+	VALUE_FLOAT_VEC		= (1<<1),	// float vector
+	VALUE_FLOAT_GENTYPE	= (1<<2),	// float scalar/vector
+	VALUE_VEC3			= (1<<3),	// vec3 only
+	VALUE_MATRIX		= (1<<4),	// matrix
+	VALUE_BOOL			= (1<<5),	// boolean scalar
+	VALUE_BOOL_VEC		= (1<<6),	// boolean vector
+	VALUE_BOOL_GENTYPE	= (1<<7),	// boolean scalar/vector
+	VALUE_INT			= (1<<8),	// int scalar
+	VALUE_INT_VEC		= (1<<9),	// int vector
+	VALUE_INT_GENTYPE	= (1<<10),	// int scalar/vector
+
+	// Shorthands.
+	F				= VALUE_FLOAT,
+	FV				= VALUE_FLOAT_VEC,
+	GT				= VALUE_FLOAT_GENTYPE,
+	V3				= VALUE_VEC3,
+	M				= VALUE_MATRIX,
+	B				= VALUE_BOOL,
+	BV				= VALUE_BOOL_VEC,
+	BGT				= VALUE_BOOL_GENTYPE,
+	I				= VALUE_INT,
+	IV				= VALUE_INT_VEC,
+	IGT				= VALUE_INT_GENTYPE
+};
+
+static inline bool isScalarType (ValueType type)
+{
+	return type == VALUE_FLOAT || type == VALUE_BOOL || type == VALUE_INT;
+}
+
+struct Value
+{
+	Value (ValueType valueType_, float rangeMin_, float rangeMax_)
+		: valueType	(valueType_)
+		, rangeMin	(rangeMin_)
+		, rangeMax	(rangeMax_)
+	{
+	}
+
+	ValueType	valueType;
+	float		rangeMin;
+	float		rangeMax;
+};
+
+enum OperationType
+{
+	FUNCTION = 0,
+	OPERATOR,
+	SIDE_EFFECT_OPERATOR // Test the side-effect (as opposed to the result) of a side-effect operator.
+};
+
+struct BuiltinFuncInfo
+{
+	BuiltinFuncInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_, Value input0_, Value input1_, Value input2_, float resultScale_, float resultBias_, deUint32 precisionMask_, ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_, OperationType type_=FUNCTION, bool isUnaryPrefix_=true)
+		: caseName			(caseName_)
+		, shaderFuncName	(shaderFuncName_)
+		, outValue			(outValue_)
+		, input0			(input0_)
+		, input1			(input1_)
+		, input2			(input2_)
+		, resultScale		(resultScale_)
+		, resultBias		(resultBias_)
+		, precisionMask		(precisionMask_)
+		, evalFuncScalar	(evalFuncScalar_)
+		, evalFuncVec2		(evalFuncVec2_)
+		, evalFuncVec3		(evalFuncVec3_)
+		, evalFuncVec4		(evalFuncVec4_)
+		, type				(type_)
+		, isUnaryPrefix		(isUnaryPrefix_)
+	{
+	}
+
+	const char*		caseName;			//!< Name of case.
+	const char*		shaderFuncName;		//!< Name in shading language.
+	ValueType		outValue;
+	Value			input0;
+	Value			input1;
+	Value			input2;
+	float			resultScale;
+	float			resultBias;
+	deUint32		precisionMask;
+	ShaderEvalFunc	evalFuncScalar;
+	ShaderEvalFunc	evalFuncVec2;
+	ShaderEvalFunc	evalFuncVec3;
+	ShaderEvalFunc	evalFuncVec4;
+	OperationType	type;
+	bool			isUnaryPrefix;		//!< Whether a unary operator is a prefix operator; redundant unless unary.
+};
+
+static inline BuiltinFuncInfo BuiltinOperInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_, Value input0_, Value input1_, Value input2_, float resultScale_, float resultBias_, deUint32 precisionMask_, ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_)
+{
+	return BuiltinFuncInfo(caseName_, shaderFuncName_, outValue_, input0_, input1_, input2_, resultScale_, resultBias_, precisionMask_, evalFuncScalar_, evalFuncVec2_, evalFuncVec3_, evalFuncVec4_, OPERATOR);
+}
+
+// For postfix (unary) operators.
+static inline BuiltinFuncInfo BuiltinPostOperInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_, Value input0_, Value input1_, Value input2_, float resultScale_, float resultBias_, deUint32 precisionMask_, ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_)
+{
+	return BuiltinFuncInfo(caseName_, shaderFuncName_, outValue_, input0_, input1_, input2_, resultScale_, resultBias_, precisionMask_, evalFuncScalar_, evalFuncVec2_, evalFuncVec3_, evalFuncVec4_, OPERATOR, false);
+}
+
+static inline BuiltinFuncInfo BuiltinSideEffOperInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_, Value input0_, Value input1_, Value input2_, float resultScale_, float resultBias_, deUint32 precisionMask_, ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_)
+{
+	return BuiltinFuncInfo(caseName_, shaderFuncName_, outValue_, input0_, input1_, input2_, resultScale_, resultBias_, precisionMask_, evalFuncScalar_, evalFuncVec2_, evalFuncVec3_, evalFuncVec4_, SIDE_EFFECT_OPERATOR);
+}
+
+// For postfix (unary) operators, testing side-effect.
+static inline BuiltinFuncInfo BuiltinPostSideEffOperInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_, Value input0_, Value input1_, Value input2_, float resultScale_, float resultBias_, deUint32 precisionMask_, ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_)
+{
+	return BuiltinFuncInfo(caseName_, shaderFuncName_, outValue_, input0_, input1_, input2_, resultScale_, resultBias_, precisionMask_, evalFuncScalar_, evalFuncVec2_, evalFuncVec3_, evalFuncVec4_, SIDE_EFFECT_OPERATOR, false);
+}
+
+// BuiltinFuncGroup
+
+struct BuiltinFuncGroup
+{
+						BuiltinFuncGroup	(const char* name_, const char* description_) : name(name_), description(description_) {}
+	BuiltinFuncGroup&	operator<<			(const BuiltinFuncInfo& info) { funcInfos.push_back(info); return *this; }
+
+	const char*						name;
+	const char*						description;
+	std::vector<BuiltinFuncInfo>	funcInfos;
+};
+
+static const char* s_inSwizzles[MAX_INPUTS][4] =
+{
+	{ "z", "wy", "zxy", "yzwx" },
+	{ "x", "yx", "yzx", "wzyx" },
+	{ "y", "zy", "wyz", "xwzy" }
+};
+
+static const char* s_outSwizzles[]	= { "x", "yz", "xyz", "xyzw" };
+
+// OperatorShaderEvaluator
+
+class OperatorShaderEvaluator : public ShaderEvaluator
+{
+public:
+	OperatorShaderEvaluator (ShaderEvalFunc evalFunc, float scale, float bias)
+	{
+		m_evalFunc	= evalFunc;
+		m_scale		= scale;
+		m_bias		= bias;
+	}
+
+	virtual ~OperatorShaderEvaluator (void)
+	{
+	}
+
+	virtual void evaluate (ShaderEvalContext& ctx)
+	{
+		m_evalFunc(ctx);
+		ctx.color = ctx.color * m_scale + m_bias;
+	}
+
+private:
+	ShaderEvalFunc	m_evalFunc;
+	float			m_scale;
+	float			m_bias;
+};
+
+// Concrete value.
+
+struct ShaderValue
+{
+	ShaderValue (DataType type_, float rangeMin_, float rangeMax_)
+		: type		(type_)
+		, rangeMin	(rangeMin_)
+		, rangeMax	(rangeMax_)
+	{
+	}
+
+	ShaderValue (void)
+		: type		(TYPE_LAST)
+		, rangeMin	(0.0f)
+		, rangeMax	(0.0f)
+	{
+	}
+
+	DataType	type;
+	float		rangeMin;
+	float		rangeMax;
+};
+
+struct ShaderDataSpec
+{
+	ShaderDataSpec (void)
+		: resultScale	(1.0f)
+		, resultBias	(0.0f)
+		, precision		(PRECISION_LAST)
+		, output		(TYPE_LAST)
+		, numInputs		(0)
+	{
+	}
+
+	float			resultScale;
+	float			resultBias;
+	Precision		precision;
+	DataType		output;
+	int				numInputs;
+	ShaderValue		inputs[MAX_INPUTS];
+};
+
+// ShaderOperatorCase
+
+class ShaderOperatorCase : public ShaderRenderCase
+{
+public:
+								ShaderOperatorCase		(Context& context, const char* caseName, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, const char* shaderOp, const ShaderDataSpec& spec);
+	virtual						~ShaderOperatorCase		(void);
+
+private:
+								ShaderOperatorCase		(const ShaderOperatorCase&);	// not allowed!
+	ShaderOperatorCase&			operator=				(const ShaderOperatorCase&);	// not allowed!
+
+	OperatorShaderEvaluator		m_evaluator;
+};
+
+ShaderOperatorCase::ShaderOperatorCase (Context& context, const char* caseName, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, const char* shaderOp, const ShaderDataSpec& spec)
+	: ShaderRenderCase(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), caseName, description, isVertexCase, m_evaluator)
+	, m_evaluator(evalFunc, spec.resultScale, spec.resultBias)
+{
+	const char*		precision	= spec.precision != PRECISION_LAST ? getPrecisionName(spec.precision) : DE_NULL;
+	const char*		inputPrecision[MAX_INPUTS];
+
+	ostringstream	vtx;
+	ostringstream	frag;
+	ostringstream&	op			= isVertexCase ? vtx : frag;
+
+	// Compute precision for inputs.
+	for (int i = 0; i < spec.numInputs; i++)
+	{
+		bool		isBoolVal	= de::inRange<int>(spec.inputs[i].type, TYPE_BOOL, TYPE_BOOL_VEC4);
+		bool		isIntVal	= de::inRange<int>(spec.inputs[i].type, TYPE_INT, TYPE_INT_VEC4);
+		// \note Mediump interpolators are used for booleans and lowp ints.
+		Precision	prec		= isBoolVal || (isIntVal && spec.precision == PRECISION_LOWP) ? PRECISION_MEDIUMP : spec.precision;
+		inputPrecision[i] = getPrecisionName(prec);
+	}
+
+	// Attributes.
+	vtx << "attribute highp vec4 a_position;\n";
+	for (int i = 0; i < spec.numInputs; i++)
+		vtx << "attribute " << inputPrecision[i] << " vec4 a_in" << i << ";\n";
+
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+	else
+	{
+		for (int i = 0; i < spec.numInputs; i++)
+		{
+			vtx << "varying " << inputPrecision[i] << " vec4 v_in" << i << ";\n";
+			frag << "varying " << inputPrecision[i] << " vec4 v_in" << i << ";\n";
+		}
+	}
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Expression inputs.
+	string prefix = isVertexCase ? "a_" : "v_";
+	for (int i = 0; i < spec.numInputs; i++)
+	{
+		DataType		inType		= spec.inputs[i].type;
+		int				inSize		= getDataTypeScalarSize(inType);
+		bool			isInt		= de::inRange<int>(inType, TYPE_INT, TYPE_INT_VEC4);
+		bool			isBool		= de::inRange<int>(inType, TYPE_BOOL, TYPE_BOOL_VEC4);
+		const char*		typeName	= getDataTypeName(inType);
+		const char*		swizzle		= s_inSwizzles[i][inSize-1];
+
+		op << "\t";
+		if (precision && !isBool) op << precision << " ";
+
+		op << typeName << " in" << i << " = ";
+
+		if (isBool)
+		{
+			if (inSize == 1)	op << "(";
+			else				op << "greaterThan(";
+		}
+		else if (isInt)
+			op << typeName << "(";
+
+		op << prefix << "in" << i << "." << swizzle;
+
+		if (isBool)
+		{
+			if (inSize == 1)	op << " > 0.0)";
+			else				op << ", vec" << inSize << "(0.0))";
+		}
+		else if (isInt)
+			op << ")";
+
+		op << ";\n";
+	}
+
+	// Result variable.
+	{
+		const char* outTypeName = getDataTypeName(spec.output);
+		bool		isBoolOut	= de::inRange<int>(spec.output, TYPE_BOOL, TYPE_BOOL_VEC4);
+
+		op << "\t";
+		if (precision && !isBoolOut) op << precision << " ";
+		op << outTypeName << " res = " << outTypeName << "(0.0);\n\n";
+	}
+
+	// Expression.
+	op << "\t" << shaderOp << "\n\n";
+
+	// Convert to color.
+	bool	isResFloatVec	= de::inRange<int>(spec.output, TYPE_FLOAT, TYPE_FLOAT_VEC4);
+	int		outScalarSize	= getDataTypeScalarSize(spec.output);
+
+	op << "\tmediump vec4 color = vec4(0.0, 0.0, 0.0, 1.0);\n";
+	op << "\tcolor." << s_outSwizzles[outScalarSize-1] << " = ";
+
+	if (!isResFloatVec && outScalarSize == 1)
+		op << "float(res)";
+	else if (!isResFloatVec)
+		op << "vec" << outScalarSize << "(res)";
+	else
+		op << "res";
+
+	op << ";\n";
+
+	// Scale & bias.
+	float	resultScale		= spec.resultScale;
+	float	resultBias		= spec.resultBias;
+	if ((resultScale != 1.0f) || (resultBias != 0.0f))
+	{
+		op << "\tcolor = color";
+		if (resultScale != 1.0f) op << " * " << de::floatToString(resultScale, 2);
+		if (resultBias != 0.0f)  op << " + " << de::floatToString(resultBias, 2);
+		op << ";\n";
+	}
+
+	// ..
+	if (isVertexCase)
+	{
+		vtx << "	v_color = color;\n";
+		frag << "	gl_FragColor = v_color;\n";
+	}
+	else
+	{
+		for (int i = 0; i < spec.numInputs; i++)
+		vtx << "	v_in" << i << " = a_in" << i << ";\n";
+		frag << "	gl_FragColor = color;\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource = vtx.str();
+	m_fragShaderSource = frag.str();
+
+	// Setup the user attributes.
+	m_userAttribTransforms.resize(spec.numInputs);
+	for (int inputNdx = 0; inputNdx < spec.numInputs; inputNdx++)
+	{
+		const ShaderValue& v = spec.inputs[inputNdx];
+		DE_ASSERT(v.type != TYPE_LAST);
+
+		float scale		= (v.rangeMax - v.rangeMin);
+		float minBias	= v.rangeMin;
+		float maxBias	= v.rangeMax;
+		Mat4  attribMatrix;
+
+		for (int rowNdx = 0; rowNdx < 4; rowNdx++)
+		{
+			Vec4 row;
+
+			switch ((rowNdx + inputNdx) % 4)
+			{
+				case 0:	row = Vec4(scale, 0.0f, 0.0f, minBias);		break;
+				case 1:	row = Vec4(0.0f, scale, 0.0f, minBias);		break;
+				case 2:	row = Vec4(-scale, 0.0f, 0.0f, maxBias);	break;
+				case 3:	row = Vec4(0.0f, -scale, 0.0f, maxBias);	break;
+				default: DE_ASSERT(false);
+			}
+
+			attribMatrix.setRow(rowNdx, row);
+		}
+
+		m_userAttribTransforms[inputNdx] = attribMatrix;
+	}
+}
+
+ShaderOperatorCase::~ShaderOperatorCase (void)
+{
+}
+
+// ShaderOperatorTests.
+
+ShaderOperatorTests::ShaderOperatorTests(Context& context)
+	: TestCaseGroup(context, "operator", "Operator tests.")
+{
+}
+
+ShaderOperatorTests::~ShaderOperatorTests (void)
+{
+}
+
+// Vector math functions.
+template<typename T> inline T nop (T f) { return f; }
+
+template <typename T, int Size>
+Vector<T, Size> nop (const Vector<T, Size>& v) { return v; }
+
+#define DECLARE_UNARY_GENTYPE_FUNCS(FUNC_NAME)																			\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].z()); }					\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1)); }		\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1)); }	\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0)); }
+
+#define DECLARE_BINARY_GENTYPE_FUNCS(FUNC_NAME)																											\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].z(),                 c.in[1].x()); }					\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0)); }			\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0)); }		\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0)); }
+
+#define DECLARE_TERNARY_GENTYPE_FUNCS(FUNC_NAME)																																	\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].z(),                 c.in[1].x(),                 c.in[2].y()); }					\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0),       c.in[2].swizzle(2, 1)); }			\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0),    c.in[2].swizzle(3, 1, 2)); }		\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0), c.in[2].swizzle(0, 3, 2, 1)); }
+
+#define DECLARE_UNARY_SCALAR_GENTYPE_FUNCS(FUNC_NAME)																	\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].z()); }					\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(3, 1)); }		\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1)); }	\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0)); }
+
+#define DECLARE_BINARY_SCALAR_GENTYPE_FUNCS(FUNC_NAME)																									\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].z(),                 c.in[1].x()); }					\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0)); }			\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0)); }		\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0)); }
+
+#define DECLARE_BINARY_BOOL_FUNCS(FUNC_NAME)																		\
+	void eval_##FUNC_NAME##_bool	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(c.in[0].z() > 0.0f, c.in[1].x() > 0.0f); }
+
+#define DECLARE_UNARY_BOOL_GENTYPE_FUNCS(FUNC_NAME)																											\
+	void eval_##FUNC_NAME##_bool	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(c.in[0].z() > 0.0f); }										\
+	void eval_##FUNC_NAME##_bvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f))).asFloat(); }		\
+	void eval_##FUNC_NAME##_bvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f))).asFloat(); }		\
+	void eval_##FUNC_NAME##_bvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f))).asFloat(); }
+
+#define DECLARE_TERNARY_BOOL_GENTYPE_FUNCS(FUNC_NAME)																																																					\
+	void eval_##FUNC_NAME##_bool	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(c.in[0].z() > 0.0f,                            c.in[1].x() > 0.0f,                                   c.in[2].y() > 0.0f); }												\
+	void eval_##FUNC_NAME##_bvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f)),       greaterThan(c.in[1].swizzle(1, 0), Vec2(0.0f)),       greaterThan(c.in[2].swizzle(2, 1), Vec2(0.0f))).asFloat(); }		\
+	void eval_##FUNC_NAME##_bvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f)),    greaterThan(c.in[1].swizzle(1, 2, 0), Vec3(0.0f)),    greaterThan(c.in[2].swizzle(3, 1, 2), Vec3(0.0f))).asFloat(); }		\
+	void eval_##FUNC_NAME##_bvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f)), greaterThan(c.in[1].swizzle(3, 2, 1, 0), Vec4(0.0f)), greaterThan(c.in[2].swizzle(0, 3, 2, 1), Vec4(0.0f))).asFloat(); }
+
+#define DECLARE_UNARY_INT_GENTYPE_FUNCS(FUNC_NAME)																						\
+	void eval_##FUNC_NAME##_int		(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME((int)c.in[0].z()); }						\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asInt()).asFloat(); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asInt()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asInt()).asFloat(); }
+
+#define DECLARE_BINARY_INT_GENTYPE_FUNCS(FUNC_NAME)																																\
+	void eval_##FUNC_NAME##_int		(ShaderEvalContext& c) { c.color.x()	= (float)tcu::FUNC_NAME((int)c.in[0].z(),				(int)c.in[1].x()); }						\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asInt(),			c.in[1].swizzle(1, 0).asInt()).asFloat(); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asInt(),		c.in[1].swizzle(1, 2, 0).asInt()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asInt(),	c.in[1].swizzle(3, 2, 1, 0).asInt()).asFloat(); }
+
+#define DECLARE_TERNARY_INT_GENTYPE_FUNCS(FUNC_NAME)																																								\
+	void eval_##FUNC_NAME##_int		(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME((int)c.in[0].z(),                 (int)c.in[1].x(),                (int)c.in[2].y()); }								\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asInt(),       c.in[1].swizzle(1, 0).asInt(),       c.in[2].swizzle(2, 1).asInt()).asFloat(); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asInt(),    c.in[1].swizzle(1, 2, 0).asInt(),    c.in[2].swizzle(3, 1, 2).asInt()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asInt(), c.in[1].swizzle(3, 2, 1, 0).asInt(), c.in[2].swizzle(0, 3, 2, 1).asInt()).asFloat(); }
+
+#define DECLARE_VEC_FLOAT_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),			c.in[1].x()); } \
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),		c.in[1].x()); } \
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0),	c.in[1].x()); }
+
+#define DECLARE_VEC_FLOAT_FLOAT_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),			c.in[1].x(), c.in[2].y()); } \
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),		c.in[1].x(), c.in[2].y()); } \
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0),	c.in[1].x(), c.in[2].y()); }
+
+#define DECLARE_VEC_VEC_FLOAT_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),			c.in[1].swizzle(1, 0),			c.in[2].y()); } \
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),		c.in[1].swizzle(1, 2, 0),		c.in[2].y()); } \
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0),	c.in[1].swizzle(3, 2, 1, 0),	c.in[2].y()); }
+
+#define DECLARE_FLOAT_FLOAT_VEC_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].z(), c.in[1].x(), c.in[2].swizzle(2, 1)); }			\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].z(), c.in[1].x(), c.in[2].swizzle(3, 1, 2)); }		\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].z(), c.in[1].x(), c.in[2].swizzle(0, 3, 2, 1)); }
+
+#define DECLARE_FLOAT_VEC_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].z(),					c.in[1].swizzle(1, 0)); } \
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].z(),					c.in[1].swizzle(1, 2, 0)); } \
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].z(),					c.in[1].swizzle(3, 2, 1, 0)); }
+
+#define DECLARE_IVEC_INT_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asInt(),			(int)c.in[1].x()).asFloat(); } \
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asInt(),		(int)c.in[1].x()).asFloat(); } \
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asInt(),	(int)c.in[1].x()).asFloat(); }
+
+#define DECLARE_INT_IVEC_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME((int)c.in[0].z(),					c.in[1].swizzle(1, 0).asInt()).asFloat(); } \
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME((int)c.in[0].z(),					c.in[1].swizzle(1, 2, 0).asInt()).asFloat(); } \
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME((int)c.in[0].z(),					c.in[1].swizzle(3, 2, 1, 0).asInt()).asFloat(); }
+
+// Operators.
+
+DECLARE_UNARY_GENTYPE_FUNCS(nop)
+DECLARE_UNARY_GENTYPE_FUNCS(negate)
+DECLARE_UNARY_GENTYPE_FUNCS(addOne)
+DECLARE_UNARY_GENTYPE_FUNCS(subOne)
+DECLARE_BINARY_GENTYPE_FUNCS(add)
+DECLARE_BINARY_GENTYPE_FUNCS(sub)
+DECLARE_BINARY_GENTYPE_FUNCS(mul)
+DECLARE_BINARY_GENTYPE_FUNCS(div)
+
+void eval_selection_float	(ShaderEvalContext& c) { c.color.x()	= selection(c.in[0].z() > 0.0f,		c.in[1].x(),					c.in[2].y()); }
+void eval_selection_vec2	(ShaderEvalContext& c) { c.color.yz()	= selection(c.in[0].z() > 0.0f,		c.in[1].swizzle(1, 0),			c.in[2].swizzle(2, 1)); }
+void eval_selection_vec3	(ShaderEvalContext& c) { c.color.xyz()	= selection(c.in[0].z() > 0.0f,		c.in[1].swizzle(1, 2, 0),		c.in[2].swizzle(3, 1, 2)); }
+void eval_selection_vec4	(ShaderEvalContext& c) { c.color		= selection(c.in[0].z() > 0.0f,		c.in[1].swizzle(3, 2, 1, 0),	c.in[2].swizzle(0, 3, 2, 1)); }
+
+DECLARE_UNARY_INT_GENTYPE_FUNCS(nop)
+DECLARE_UNARY_INT_GENTYPE_FUNCS(negate)
+DECLARE_UNARY_INT_GENTYPE_FUNCS(addOne)
+DECLARE_UNARY_INT_GENTYPE_FUNCS(subOne)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(add)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(sub)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(mul)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(div)
+
+void eval_selection_int		(ShaderEvalContext& c) { c.color.x()	= (float)selection(c.in[0].z() > 0.0f,	(int)c.in[1].x(),						(int)c.in[2].y()); }
+void eval_selection_ivec2	(ShaderEvalContext& c) { c.color.yz()	= selection(c.in[0].z() > 0.0f,			c.in[1].swizzle(1, 0).asInt(),			c.in[2].swizzle(2, 1).asInt()).asFloat(); }
+void eval_selection_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= selection(c.in[0].z() > 0.0f,			c.in[1].swizzle(1, 2, 0).asInt(),		c.in[2].swizzle(3, 1, 2).asInt()).asFloat(); }
+void eval_selection_ivec4	(ShaderEvalContext& c) { c.color		= selection(c.in[0].z() > 0.0f,			c.in[1].swizzle(3, 2, 1, 0).asInt(),	c.in[2].swizzle(0, 3, 2, 1).asInt()).asFloat(); }
+
+DECLARE_UNARY_BOOL_GENTYPE_FUNCS(boolNot)
+DECLARE_BINARY_BOOL_FUNCS(logicalAnd)
+DECLARE_BINARY_BOOL_FUNCS(logicalOr)
+DECLARE_BINARY_BOOL_FUNCS(logicalXor)
+
+void eval_selection_bool	(ShaderEvalContext& c) { c.color.x()	= (float)selection(c.in[0].z() > 0.0f,	c.in[1].x() > 0.0f,														c.in[2].y() > 0.0f); }
+void eval_selection_bvec2	(ShaderEvalContext& c) { c.color.yz()	= selection(c.in[0].z() > 0.0f,			greaterThan(c.in[1].swizzle(1, 0), Vec2(0.0f, 0.0f)),					greaterThan(c.in[2].swizzle(2, 1), Vec2(0.0f, 0.0f))).asFloat(); }
+void eval_selection_bvec3	(ShaderEvalContext& c) { c.color.xyz()	= selection(c.in[0].z() > 0.0f,			greaterThan(c.in[1].swizzle(1, 2, 0), Vec3(0.0f, 0.0f, 0.0f)),			greaterThan(c.in[2].swizzle(3, 1, 2), Vec3(0.0f, 0.0f, 0.0f))).asFloat(); }
+void eval_selection_bvec4	(ShaderEvalContext& c) { c.color		= selection(c.in[0].z() > 0.0f,			greaterThan(c.in[1].swizzle(3, 2, 1, 0), Vec4(0.0f, 0.0f, 0.0f, 0.0f)),	greaterThan(c.in[2].swizzle(0, 3, 2, 1), Vec4(0.0f, 0.0f, 0.0f, 0.0f))).asFloat(); }
+
+DECLARE_VEC_FLOAT_FUNCS(addVecScalar)
+DECLARE_VEC_FLOAT_FUNCS(subVecScalar)
+DECLARE_VEC_FLOAT_FUNCS(mulVecScalar)
+DECLARE_VEC_FLOAT_FUNCS(divVecScalar)
+
+DECLARE_FLOAT_VEC_FUNCS(addScalarVec)
+DECLARE_FLOAT_VEC_FUNCS(subScalarVec)
+DECLARE_FLOAT_VEC_FUNCS(mulScalarVec)
+DECLARE_FLOAT_VEC_FUNCS(divScalarVec)
+
+DECLARE_IVEC_INT_FUNCS(addVecScalar)
+DECLARE_IVEC_INT_FUNCS(subVecScalar)
+DECLARE_IVEC_INT_FUNCS(mulVecScalar)
+DECLARE_IVEC_INT_FUNCS(divVecScalar)
+
+DECLARE_INT_IVEC_FUNCS(addScalarVec)
+DECLARE_INT_IVEC_FUNCS(subScalarVec)
+DECLARE_INT_IVEC_FUNCS(mulScalarVec)
+DECLARE_INT_IVEC_FUNCS(divScalarVec)
+
+// Built-in functions.
+
+DECLARE_UNARY_GENTYPE_FUNCS(radians)
+DECLARE_UNARY_GENTYPE_FUNCS(degrees)
+DECLARE_UNARY_GENTYPE_FUNCS(sin)
+DECLARE_UNARY_GENTYPE_FUNCS(cos)
+DECLARE_UNARY_GENTYPE_FUNCS(tan)
+DECLARE_UNARY_GENTYPE_FUNCS(asin)
+DECLARE_UNARY_GENTYPE_FUNCS(acos)
+DECLARE_UNARY_GENTYPE_FUNCS(atan)
+DECLARE_BINARY_GENTYPE_FUNCS(atan2)
+
+DECLARE_BINARY_GENTYPE_FUNCS(pow)
+DECLARE_UNARY_GENTYPE_FUNCS(exp)
+DECLARE_UNARY_GENTYPE_FUNCS(log)
+DECLARE_UNARY_GENTYPE_FUNCS(exp2)
+DECLARE_UNARY_GENTYPE_FUNCS(log2)
+DECLARE_UNARY_GENTYPE_FUNCS(sqrt)
+DECLARE_UNARY_GENTYPE_FUNCS(inverseSqrt)
+
+DECLARE_UNARY_GENTYPE_FUNCS(abs)
+DECLARE_UNARY_GENTYPE_FUNCS(sign)
+DECLARE_UNARY_GENTYPE_FUNCS(floor)
+DECLARE_UNARY_GENTYPE_FUNCS(ceil)
+DECLARE_UNARY_GENTYPE_FUNCS(fract)
+DECLARE_BINARY_GENTYPE_FUNCS(mod)
+DECLARE_VEC_FLOAT_FUNCS(modVecFloat)
+DECLARE_BINARY_GENTYPE_FUNCS(min)
+DECLARE_VEC_FLOAT_FUNCS(minVecFloat)
+DECLARE_BINARY_GENTYPE_FUNCS(max)
+DECLARE_VEC_FLOAT_FUNCS(maxVecFloat)
+DECLARE_TERNARY_GENTYPE_FUNCS(clamp)
+DECLARE_VEC_FLOAT_FLOAT_FUNCS(clampVecFloatFloat)
+DECLARE_TERNARY_GENTYPE_FUNCS(mix)
+DECLARE_VEC_VEC_FLOAT_FUNCS(mixVecVecFloat)
+DECLARE_BINARY_GENTYPE_FUNCS(step)
+DECLARE_FLOAT_VEC_FUNCS(stepFloatVec)
+DECLARE_TERNARY_GENTYPE_FUNCS(smoothStep)
+DECLARE_FLOAT_FLOAT_VEC_FUNCS(smoothStepFloatFloatVec)
+
+DECLARE_UNARY_SCALAR_GENTYPE_FUNCS(length)
+DECLARE_BINARY_SCALAR_GENTYPE_FUNCS(distance)
+DECLARE_BINARY_SCALAR_GENTYPE_FUNCS(dot)
+void eval_cross_vec3 (ShaderEvalContext& c) { c.color.xyz()	= cross(c.in[0].swizzle(2, 0, 1), c.in[1].swizzle(1, 2, 0)); }
+
+DECLARE_UNARY_GENTYPE_FUNCS(normalize)
+DECLARE_TERNARY_GENTYPE_FUNCS(faceForward)
+DECLARE_BINARY_GENTYPE_FUNCS(reflect)
+
+void eval_refract_float	(ShaderEvalContext& c) { c.color.x()	= refract(c.in[0].z(),                 c.in[1].x(),                 c.in[2].y()); }
+void eval_refract_vec2	(ShaderEvalContext& c) { c.color.yz()	= refract(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0),       c.in[2].y()); }
+void eval_refract_vec3	(ShaderEvalContext& c) { c.color.xyz()	= refract(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0),    c.in[2].y()); }
+void eval_refract_vec4	(ShaderEvalContext& c) { c.color		= refract(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0), c.in[2].y()); }
+
+// Compare functions.
+
+#define DECLARE_FLOAT_COMPARE_FUNCS(FUNC_NAME)																											\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].z(),          c.in[1].x()); }						\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0)); }		\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0)); }	\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0)); }
+
+#define DECLARE_FLOAT_CWISE_COMPARE_FUNCS(FUNC_NAME)																											\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(c.in[0].z(),          c.in[1].x()); }							\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0)).asFloat(); }		\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0)).asFloat(); }		\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0)).asFloat(); }
+
+#define DECLARE_INT_COMPARE_FUNCS(FUNC_NAME)																																	\
+	void eval_##FUNC_NAME##_int		(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(chopToInt(c.in[0].z()), chopToInt(c.in[1].x())); }									\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(chopToInt(c.in[0].swizzle(3, 1)),       chopToInt(c.in[1].swizzle(1, 0))); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(chopToInt(c.in[0].swizzle(2, 0, 1)),    chopToInt(c.in[1].swizzle(1, 2, 0))); }		\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(chopToInt(c.in[0].swizzle(1, 2, 3, 0)), chopToInt(c.in[1].swizzle(3, 2, 1, 0))); }
+
+#define DECLARE_INT_CWISE_COMPARE_FUNCS(FUNC_NAME)																																	\
+	void eval_##FUNC_NAME##_int		(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(chopToInt(c.in[0].z()), chopToInt(c.in[1].x())); }									\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(chopToInt(c.in[0].swizzle(3, 1)),       chopToInt(c.in[1].swizzle(1, 0))).asFloat(); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(chopToInt(c.in[0].swizzle(2, 0, 1)),    chopToInt(c.in[1].swizzle(1, 2, 0))).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(chopToInt(c.in[0].swizzle(1, 2, 3, 0)), chopToInt(c.in[1].swizzle(3, 2, 1, 0))).asFloat(); }
+
+#define DECLARE_BOOL_COMPARE_FUNCS(FUNC_NAME)																																								\
+	void eval_##FUNC_NAME##_bool	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].z() > 0.0f, c.in[1].x() > 0.0f); }																		\
+	void eval_##FUNC_NAME##_bvec2	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f)),       greaterThan(c.in[1].swizzle(1, 0), Vec2(0.0f))); }		\
+	void eval_##FUNC_NAME##_bvec3	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f)),    greaterThan(c.in[1].swizzle(1, 2, 0), Vec3(0.0f))); }		\
+	void eval_##FUNC_NAME##_bvec4	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f)), greaterThan(c.in[1].swizzle(3, 2, 1, 0), Vec4(0.0f))); }
+
+#define DECLARE_BOOL_CWISE_COMPARE_FUNCS(FUNC_NAME)																																								\
+	void eval_##FUNC_NAME##_bool	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(c.in[0].z() > 0.0f, c.in[1].x() > 0.0f); }																		\
+	void eval_##FUNC_NAME##_bvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f)),       greaterThan(c.in[1].swizzle(1, 0), Vec2(0.0f))).asFloat(); }		\
+	void eval_##FUNC_NAME##_bvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f)),    greaterThan(c.in[1].swizzle(1, 2, 0), Vec3(0.0f))).asFloat(); }	\
+	void eval_##FUNC_NAME##_bvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f)), greaterThan(c.in[1].swizzle(3, 2, 1, 0), Vec4(0.0f))).asFloat(); }
+
+DECLARE_FLOAT_COMPARE_FUNCS(allEqual)
+DECLARE_FLOAT_COMPARE_FUNCS(anyNotEqual)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(lessThan)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(lessThanEqual)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(greaterThan)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(greaterThanEqual)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(equal)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(notEqual)
+
+DECLARE_INT_COMPARE_FUNCS(allEqual)
+DECLARE_INT_COMPARE_FUNCS(anyNotEqual)
+DECLARE_INT_CWISE_COMPARE_FUNCS(lessThan)
+DECLARE_INT_CWISE_COMPARE_FUNCS(lessThanEqual)
+DECLARE_INT_CWISE_COMPARE_FUNCS(greaterThan)
+DECLARE_INT_CWISE_COMPARE_FUNCS(greaterThanEqual)
+DECLARE_INT_CWISE_COMPARE_FUNCS(equal)
+DECLARE_INT_CWISE_COMPARE_FUNCS(notEqual)
+
+DECLARE_BOOL_COMPARE_FUNCS(allEqual)
+DECLARE_BOOL_COMPARE_FUNCS(anyNotEqual)
+DECLARE_BOOL_CWISE_COMPARE_FUNCS(equal)
+DECLARE_BOOL_CWISE_COMPARE_FUNCS(notEqual)
+
+// Boolean functions.
+
+#define DECLARE_UNARY_SCALAR_BVEC_FUNCS(GLSL_NAME, FUNC_NAME)																								\
+	void eval_##GLSL_NAME##_bvec2	(ShaderEvalContext& c) { c.color.x()	= float(FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f)))); }		\
+	void eval_##GLSL_NAME##_bvec3	(ShaderEvalContext& c) { c.color.x()	= float(FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f)))); }		\
+	void eval_##GLSL_NAME##_bvec4	(ShaderEvalContext& c) { c.color.x()	= float(FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f)))); }
+
+#define DECLARE_UNARY_BVEC_BVEC_FUNCS(GLSL_NAME, FUNC_NAME)																									\
+	void eval_##GLSL_NAME##_bvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f))).asFloat(); }		\
+	void eval_##GLSL_NAME##_bvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f))).asFloat(); }	\
+	void eval_##GLSL_NAME##_bvec4	(ShaderEvalContext& c) { c.color.xyzw()	= FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f))).asFloat(); }
+
+DECLARE_UNARY_SCALAR_BVEC_FUNCS(any, boolAny);
+DECLARE_UNARY_SCALAR_BVEC_FUNCS(all, boolAll);
+
+void ShaderOperatorTests::init (void)
+{
+	// Requisites:
+	// - input types (const, uniform, dynamic, mixture)
+	// - data types (bool, int, float, vecs, mats)
+	// -
+	// - complex expressions (\todo [petri] move to expressions?)
+	//   * early-exit from side effects
+	//   * precedence
+
+	// unary plus, minus
+	// add, sub
+	// mul (larger range)
+	// div (div-by-zero)
+	// incr, decr (int only)
+	// relational
+	// equality
+	// logical
+	// selection
+	// assignment
+	// arithmetic assignment
+
+	// parenthesis
+	// sequence
+	// subscript, function call, field selector/swizzler
+
+	// precedence
+	// data types (float, int, bool, vecs, matrices)
+
+//	TestCaseGroup* group = new TestCaseGroup(m_testCtx, "additive", "Additive operator tests.");
+//	addChild(group);
+
+	// * * *
+
+	// Built-in functions
+	// - precision, data types
+
+	#define BOOL_FUNCS(FUNC_NAME)			eval_##FUNC_NAME##_bool, DE_NULL, DE_NULL, DE_NULL
+
+	#define FLOAT_VEC_FUNCS(FUNC_NAME)		DE_NULL, eval_##FUNC_NAME##_vec2, eval_##FUNC_NAME##_vec3, eval_##FUNC_NAME##_vec4
+	#define INT_VEC_FUNCS(FUNC_NAME)		DE_NULL, eval_##FUNC_NAME##_ivec2, eval_##FUNC_NAME##_ivec3, eval_##FUNC_NAME##_ivec4
+	#define BOOL_VEC_FUNCS(FUNC_NAME)		DE_NULL, eval_##FUNC_NAME##_bvec2, eval_##FUNC_NAME##_bvec3, eval_##FUNC_NAME##_bvec4
+
+	#define FLOAT_GENTYPE_FUNCS(FUNC_NAME)	eval_##FUNC_NAME##_float, eval_##FUNC_NAME##_vec2, eval_##FUNC_NAME##_vec3, eval_##FUNC_NAME##_vec4
+	#define INT_GENTYPE_FUNCS(FUNC_NAME)	eval_##FUNC_NAME##_int, eval_##FUNC_NAME##_ivec2, eval_##FUNC_NAME##_ivec3, eval_##FUNC_NAME##_ivec4
+	#define BOOL_GENTYPE_FUNCS(FUNC_NAME)	eval_##FUNC_NAME##_bool, eval_##FUNC_NAME##_bvec2, eval_##FUNC_NAME##_bvec3, eval_##FUNC_NAME##_bvec4
+
+	Value notUsed = Value(VALUE_NONE, 0.0f, 0.0f);
+
+	std::vector<BuiltinFuncGroup> funcInfoGroups;
+
+	// Unary operators.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("unary_operator", "Unary operator tests")
+		<< BuiltinOperInfo("plus",	"+",	GT,		Value(GT,  -1.0f, 1.0f),	notUsed,					notUsed,	0.5f, 0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(nop))
+		<< BuiltinOperInfo("plus",	"+",	IGT,	Value(IGT, -5.0f, 5.0f),	notUsed,					notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(nop))
+		<< BuiltinOperInfo("minus",	"-",	GT,		Value(GT,  -1.0f, 1.0f),	notUsed,					notUsed,	0.5f, 0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(negate))
+		<< BuiltinOperInfo("minus",	"-",	IGT,	Value(IGT, -5.0f, 5.0f),	notUsed,					notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(negate))
+		<< BuiltinOperInfo("not",	"!",	B,		Value(B,   -1.0f, 1.0f),	notUsed,					notUsed,	1.0f, 0.0f,		PRECMASK_NA,	eval_boolNot_bool, DE_NULL, DE_NULL, DE_NULL)
+
+		// Pre/post incr/decr side effect cases.
+		<< BuiltinSideEffOperInfo		("pre_increment_effect",	"++",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(addOne))
+		<< BuiltinSideEffOperInfo		("pre_increment_effect",	"++",	IGT,	Value(IGT,	-6.0f, 4.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(addOne))
+		<< BuiltinSideEffOperInfo		("pre_decrement_effect",	"--",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 1.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(subOne))
+		<< BuiltinSideEffOperInfo		("pre_decrement_effect",	"--",	IGT,	Value(IGT,	-4.0f, 6.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(subOne))
+		<< BuiltinPostSideEffOperInfo	("post_increment_effect",	"++",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(addOne))
+		<< BuiltinPostSideEffOperInfo	("post_increment_effect",	"++",	IGT,	Value(IGT,	-6.0f, 4.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(addOne))
+		<< BuiltinPostSideEffOperInfo	("post_decrement_effect",	"--",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 1.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(subOne))
+		<< BuiltinPostSideEffOperInfo	("post_decrement_effect",	"--",	IGT,	Value(IGT,	-4.0f, 6.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(subOne))
+
+		// Pre/post incr/decr result cases.
+		<< BuiltinOperInfo				("pre_increment_result",	"++",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(addOne))
+		<< BuiltinOperInfo				("pre_increment_result",	"++",	IGT,	Value(IGT,	-6.0f, 4.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(addOne))
+		<< BuiltinOperInfo				("pre_decrement_result",	"--",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 1.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(subOne))
+		<< BuiltinOperInfo				("pre_decrement_result",	"--",	IGT,	Value(IGT,	-4.0f, 6.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(subOne))
+		<< BuiltinPostOperInfo			("post_increment_result",	"++",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(nop))
+		<< BuiltinPostOperInfo			("post_increment_result",	"++",	IGT,	Value(IGT,	-5.0f, 5.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(nop))
+		<< BuiltinPostOperInfo			("post_decrement_result",	"--",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(nop))
+		<< BuiltinPostOperInfo			("post_decrement_result",	"--",	IGT,	Value(IGT,	-5.0f, 5.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(nop))
+	);
+
+	// Binary operators.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("binary_operator", "Binary operator tests")
+		// Arithmetic operators.
+		<< BuiltinOperInfo("add",	"+",	GT,		Value(GT,  -1.0f,   1.0f),	Value(GT,  -1.0f,  1.0f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(add))
+		<< BuiltinOperInfo("add",	"+",	IGT,	Value(IGT, -4.0f,   6.0f),	Value(IGT, -6.0f,  5.0f),	notUsed,	0.1f,		0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(add))
+		<< BuiltinOperInfo("add",	"+",	FV,		Value(FV,  -1.0f,   1.0f),	Value(F,   -1.0f,  1.0f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(addVecScalar))
+		<< BuiltinOperInfo("add",	"+",	IV,		Value(IV,  -4.0f,   6.0f),	Value(I,   -6.0f,  5.0f),	notUsed,	0.1f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(addVecScalar))
+		<< BuiltinOperInfo("add",	"+",	FV,		Value(F,   -1.0f,   1.0f),	Value(FV,  -1.0f,  1.0f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(addScalarVec))
+		<< BuiltinOperInfo("add",	"+",	IV,		Value(I,   -4.0f,   6.0f),	Value(IV,  -6.0f,  5.0f),	notUsed,	0.1f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(addScalarVec))
+		<< BuiltinOperInfo("sub",	"-",	GT,		Value(GT,  -1.0f,   1.0f),	Value(GT,  -1.0f,  1.0f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(sub))
+		<< BuiltinOperInfo("sub",	"-",	IGT,	Value(IGT, -4.0f,   6.0f),	Value(IGT, -6.0f,  5.0f),	notUsed,	0.1f,		0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(sub))
+		<< BuiltinOperInfo("sub",	"-",	FV,		Value(FV,  -1.0f,   1.0f),	Value(F,   -1.0f,  1.0f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(subVecScalar))
+		<< BuiltinOperInfo("sub",	"-",	IV,		Value(IV,  -4.0f,   6.0f),	Value(I,   -6.0f,  5.0f),	notUsed,	0.1f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(subVecScalar))
+		<< BuiltinOperInfo("sub",	"-",	FV,		Value(F,   -1.0f,   1.0f),	Value(FV,  -1.0f,  1.0f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(subScalarVec))
+		<< BuiltinOperInfo("sub",	"-",	IV,		Value(I,   -4.0f,   6.0f),	Value(IV,  -6.0f,  5.0f),	notUsed,	0.1f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(subScalarVec))
+		<< BuiltinOperInfo("mul",	"*",	GT,		Value(GT,  -1.0f,   1.0f),	Value(GT,  -1.0f,  1.0f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(mul))
+		<< BuiltinOperInfo("mul",	"*",	IGT,	Value(IGT, -4.0f,   6.0f),	Value(IGT, -6.0f,  5.0f),	notUsed,	0.1f,		0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(mul))
+		<< BuiltinOperInfo("mul",	"*",	FV,		Value(FV,  -1.0f,   1.0f),	Value(F,   -1.0f,  1.0f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(mulVecScalar))
+		<< BuiltinOperInfo("mul",	"*",	IV,		Value(IV,  -4.0f,   6.0f),	Value(I,   -6.0f,  5.0f),	notUsed,	0.1f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(mulVecScalar))
+		<< BuiltinOperInfo("mul",	"*",	FV,		Value(F,   -1.0f,   1.0f),	Value(FV,  -1.0f,  1.0f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(mulScalarVec))
+		<< BuiltinOperInfo("mul",	"*",	IV,		Value(I,   -4.0f,   6.0f),	Value(IV,  -6.0f,  5.0f),	notUsed,	0.1f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(mulScalarVec))
+		<< BuiltinOperInfo("div",	"/",	GT,		Value(GT,  -1.0f,   1.0f),	Value(GT,  -2.0f, -0.5f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(div))
+		<< BuiltinOperInfo("div",	"/",	IGT,	Value(IGT, 24.0f,  24.0f),	Value(IGT, -4.0f, -1.0f),	notUsed,	0.04f,		1.0f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(div))
+		<< BuiltinOperInfo("div",	"/",	FV,		Value(FV,  -1.0f,   1.0f),	Value(F,   -2.0f, -0.5f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(divVecScalar))
+		<< BuiltinOperInfo("div",	"/",	IV,		Value(IV,  24.0f,  24.0f),	Value(I,   -4.0f, -1.0f),	notUsed,	0.04f,		1.0f,		PRECMASK_ALL,	INT_VEC_FUNCS(divVecScalar))
+		<< BuiltinOperInfo("div",	"/",	FV,		Value(F,   -1.0f,   1.0f),	Value(FV,  -2.0f, -0.5f),	notUsed,	1.0f,		0.0f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(divScalarVec))
+		<< BuiltinOperInfo("div",	"/",	IV,		Value(I,   24.0f,  24.0f),	Value(IV,  -4.0f, -1.0f),	notUsed,	0.04f,		1.0f,		PRECMASK_ALL,	INT_VEC_FUNCS(divScalarVec))
+
+		// Arithmetic assignment side effect cases.
+		<< BuiltinSideEffOperInfo	("add_assign_effect",		"+=",	GT,		Value(GT,	-1.0f,  1.0f),	Value(GT,	-1.0f,  1.0f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(add))
+		<< BuiltinSideEffOperInfo	("add_assign_effect",		"+=",	IGT,	Value(IGT,	-5.0f,  5.0f),	Value(IGT,	-5.0f,  5.0f),	notUsed,	0.05f,		0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(add))
+		<< BuiltinSideEffOperInfo	("add_assign_effect",		"+=",	FV,		Value(FV,	-1.0f,  1.0f),	Value(F,	-1.0f,  1.0f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(addVecScalar))
+		<< BuiltinSideEffOperInfo	("add_assign_effect",		"+=",	IV,		Value(IV,	-5.0f,  5.0f),	Value(I,	-5.0f,  5.0f),	notUsed,	0.05f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(addVecScalar))
+		<< BuiltinSideEffOperInfo	("sub_assign_effect",		"-=",	GT,		Value(GT,	-1.0f,  1.0f),	Value(GT,	-1.0f,  1.0f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(sub))
+		<< BuiltinSideEffOperInfo	("sub_assign_effect",		"-=",	IGT,	Value(IGT,	-5.0f,  5.0f),	Value(IGT,	-5.0f,  5.0f),	notUsed,	0.05f,		0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(sub))
+		<< BuiltinSideEffOperInfo	("sub_assign_effect",		"-=",	FV,		Value(FV,	-1.0f,  1.0f),	Value(F,	-1.0f,  1.0f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(subVecScalar))
+		<< BuiltinSideEffOperInfo	("sub_assign_effect",		"-=",	IV,		Value(IV,	-5.0f,  5.0f),	Value(I,	-5.0f,  5.0f),	notUsed,	0.05f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(subVecScalar))
+		<< BuiltinSideEffOperInfo	("mul_assign_effect",		"*=",	GT,		Value(GT,	-1.0f,  1.0f),	Value(GT,	-1.0f,  1.0f),	notUsed,	0.5f,		0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(mul))
+		<< BuiltinSideEffOperInfo	("mul_assign_effect",		"*=",	IGT,	Value(IGT,	-4.0f,  4.0f),	Value(IGT,	-4.0f,  4.0f),	notUsed,	0.03f,		0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(mul))
+		<< BuiltinSideEffOperInfo	("mul_assign_effect",		"*=",	FV,		Value(FV,	-1.0f,  1.0f),	Value(F,	-1.0f,  1.0f),	notUsed,	0.5f,		0.5f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(mulVecScalar))
+		<< BuiltinSideEffOperInfo	("mul_assign_effect",		"*=",	IV,		Value(IV,	-4.0f,  4.0f),	Value(I,	-4.0f,  4.0f),	notUsed,	0.03f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(mulVecScalar))
+		<< BuiltinSideEffOperInfo	("div_assign_effect",		"/=",	GT,		Value(GT,	-1.0f,  1.0f),	Value(GT,	-2.0f, -0.5f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(div))
+		<< BuiltinSideEffOperInfo	("div_assign_effect",		"/=",	IGT,	Value(IGT,	24.0f, 24.0f),	Value(IGT,	-4.0f, -1.0f),	notUsed,	0.04f,		1.0f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(div))
+		<< BuiltinSideEffOperInfo	("div_assign_effect",		"/=",	FV,		Value(FV,	-1.0f,  1.0f),	Value(F,	-2.0f, -0.5f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(divVecScalar))
+		<< BuiltinSideEffOperInfo	("div_assign_effect",		"/=",	IV,		Value(IV,	24.0f, 24.0f),	Value(I,	-4.0f, -1.0f),	notUsed,	0.04f,		1.0f,		PRECMASK_ALL,	INT_VEC_FUNCS(divVecScalar))
+
+		// Arithmetic assignment result cases.
+		<< BuiltinOperInfo			("add_assign_result",		"+=",	GT,		Value(GT,	-1.0f,  1.0f),	Value(GT,	-1.0f,  1.0f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(add))
+		<< BuiltinOperInfo			("add_assign_result",		"+=",	IGT,	Value(IGT,	-5.0f,  5.0f),	Value(IGT,	-5.0f,  5.0f),	notUsed,	0.05f,		0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(add))
+		<< BuiltinOperInfo			("add_assign_result",		"+=",	FV,		Value(FV,	-1.0f,  1.0f),	Value(F,	-1.0f,  1.0f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(addVecScalar))
+		<< BuiltinOperInfo			("add_assign_result",		"+=",	IV,		Value(IV,	-5.0f,  5.0f),	Value(I,	-5.0f,  5.0f),	notUsed,	0.05f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(addVecScalar))
+		<< BuiltinOperInfo			("sub_assign_result",		"-=",	GT,		Value(GT,	-1.0f,  1.0f),	Value(GT,	-1.0f,  1.0f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(sub))
+		<< BuiltinOperInfo			("sub_assign_result",		"-=",	IGT,	Value(IGT,	-5.0f,  5.0f),	Value(IGT,	-5.0f,  5.0f),	notUsed,	0.05f,		0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(sub))
+		<< BuiltinOperInfo			("sub_assign_result",		"-=",	FV,		Value(FV,	-1.0f,  1.0f),	Value(F,	-1.0f,  1.0f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(subVecScalar))
+		<< BuiltinOperInfo			("sub_assign_result",		"-=",	IV,		Value(IV,	-5.0f,  5.0f),	Value(I,	-5.0f,  5.0f),	notUsed,	0.05f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(subVecScalar))
+		<< BuiltinOperInfo			("mul_assign_result",		"*=",	GT,		Value(GT,	-1.0f,  1.0f),	Value(GT,	-1.0f,  1.0f),	notUsed,	0.5f,		0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(mul))
+		<< BuiltinOperInfo			("mul_assign_result",		"*=",	IGT,	Value(IGT,	-4.0f,  4.0f),	Value(IGT,	-4.0f,  4.0f),	notUsed,	0.03f,		0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(mul))
+		<< BuiltinOperInfo			("mul_assign_result",		"*=",	FV,		Value(FV,	-1.0f,  1.0f),	Value(F,	-1.0f,  1.0f),	notUsed,	0.5f,		0.5f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(mulVecScalar))
+		<< BuiltinOperInfo			("mul_assign_result",		"*=",	IV,		Value(IV,	-4.0f,  4.0f),	Value(I,	-4.0f,  4.0f),	notUsed,	0.03f,		0.5f,		PRECMASK_ALL,	INT_VEC_FUNCS(mulVecScalar))
+		<< BuiltinOperInfo			("div_assign_result",		"/=",	GT,		Value(GT,	-1.0f,  1.0f),	Value(GT,	-2.0f, -0.5f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(div))
+		<< BuiltinOperInfo			("div_assign_result",		"/=",	IGT,	Value(IGT,	24.0f, 24.0f),	Value(IGT,	-4.0f, -1.0f),	notUsed,	0.04f,		1.0f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(div))
+		<< BuiltinOperInfo			("div_assign_result",		"/=",	FV,		Value(FV,	-1.0f,  1.0f),	Value(F,	-2.0f, -0.5f),	notUsed,	0.25f,		0.5f,		PRECMASK_ALL,	FLOAT_VEC_FUNCS(divVecScalar))
+		<< BuiltinOperInfo			("div_assign_result",		"/=",	IV,		Value(IV,	24.0f, 24.0f),	Value(I,	-4.0f, -1.0f),	notUsed,	0.04f,		1.0f,		PRECMASK_ALL,	INT_VEC_FUNCS(divVecScalar))
+
+		// Scalar relational operators.
+		<< BuiltinOperInfo("less",				"<",	B,		Value(F,   -1.0f, 1.0f),	Value(F,   -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_lessThan_float,			DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("less",				"<",	B,		Value(I,   -5.0f, 5.0f),	Value(I,   -5.0f, 5.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_lessThan_int,				DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("less_or_equal",		"<=",	B,		Value(F,   -1.0f, 1.0f),	Value(F,   -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_lessThanEqual_float,		DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("less_or_equal",		"<=",	B,		Value(I,   -5.0f, 5.0f),	Value(I,   -5.0f, 5.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_lessThanEqual_int,			DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("greater",			">",	B,		Value(F,   -1.0f, 1.0f),	Value(F,   -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_greaterThan_float,			DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("greater",			">",	B,		Value(I,   -5.0f, 5.0f),	Value(I,   -5.0f, 5.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_greaterThan_int,			DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("greater_or_equal",	">=",	B,		Value(F,   -1.0f, 1.0f),	Value(F,   -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_greaterThanEqual_float,	DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("greater_or_equal",	">=",	B,		Value(I,   -5.0f, 5.0f),	Value(I,   -5.0f, 5.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_greaterThanEqual_int,		DE_NULL, DE_NULL, DE_NULL)
+
+		// Equality comparison operators.
+		<< BuiltinOperInfo("equal",				"==",	B,		Value(GT,  -1.0f, 1.0f),	Value(GT,  -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(allEqual))
+		<< BuiltinOperInfo("equal",				"==",	B,		Value(IGT, -5.5f, 4.7f),	Value(IGT, -4.9f, 5.8f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(allEqual))
+		<< BuiltinOperInfo("equal",				"==",	B,		Value(BGT, -2.1f, 2.1f),	Value(BGT, -1.1f, 3.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_NA,	BOOL_GENTYPE_FUNCS(allEqual))
+		<< BuiltinOperInfo("not_equal",			"!=",	B,		Value(GT,  -1.0f, 1.0f),	Value(GT,  -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(anyNotEqual))
+		<< BuiltinOperInfo("not_equal",			"!=",	B,		Value(IGT, -5.5f, 4.7f),	Value(IGT, -4.9f, 5.8f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(anyNotEqual))
+		<< BuiltinOperInfo("not_equal",			"!=",	B,		Value(BGT, -2.1f, 2.1f),	Value(BGT, -1.1f, 3.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_NA,	BOOL_GENTYPE_FUNCS(anyNotEqual))
+
+		// Logical operators.
+		<< BuiltinOperInfo("logical_and",	"&&",	B,	Value(B, -1.0f, 1.0f),	Value(B, -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_NA,	BOOL_FUNCS(logicalAnd))
+		<< BuiltinOperInfo("logical_or",	"||",	B,	Value(B, -1.0f, 1.0f),	Value(B, -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_NA,	BOOL_FUNCS(logicalOr))
+		<< BuiltinOperInfo("logical_xor",	"^^",	B,	Value(B, -1.0f, 1.0f),	Value(B, -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_NA,	BOOL_FUNCS(logicalXor))
+	);
+
+	// 8.1 Angle and Trigonometry Functions.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("angle_and_trigonometry", "Angle and trigonometry function tests.")
+		<< BuiltinFuncInfo("radians",		"radians",		GT,	Value(GT, -1.0f, 1.0f),		notUsed,					notUsed,					25.0f, 0.5f,	PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(radians) )
+		<< BuiltinFuncInfo("degrees",		"degrees",		GT,	Value(GT, -1.0f, 1.0f),		notUsed,					notUsed,					0.04f, 0.5f,	PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(degrees) )
+		<< BuiltinFuncInfo("sin",			"sin",			GT,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(sin) )
+		<< BuiltinFuncInfo("sin",			"sin",			GT,	Value(GT, -1.5f, 1.5f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_LOWP,				FLOAT_GENTYPE_FUNCS(sin) )
+		<< BuiltinFuncInfo("cos",			"cos",			GT,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(cos) )
+		<< BuiltinFuncInfo("cos",			"cos",			GT,	Value(GT, -1.5f, 1.5f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_LOWP,				FLOAT_GENTYPE_FUNCS(cos) )
+		<< BuiltinFuncInfo("tan",			"tan",			GT,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(tan) )
+		<< BuiltinFuncInfo("tan",			"tan",			GT,	Value(GT, -1.5f, 5.5f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_LOWP,				FLOAT_GENTYPE_FUNCS(tan) )
+		<< BuiltinFuncInfo("asin",			"asin",			GT,	Value(GT, -1.0f, 1.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(asin) )
+		<< BuiltinFuncInfo("acos",			"acos",			GT,	Value(GT, -1.0f, 1.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(acos) )
+		<< BuiltinFuncInfo("atan",			"atan",			GT,	Value(GT, -4.0f, 4.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(atan) )
+		<< BuiltinFuncInfo("atan2",			"atan",			GT,	Value(GT, -4.0f, 4.0f),		Value(GT, 0.5f, 2.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(atan2) )
+	);
+
+	// 8.2 Exponential Functions.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("exponential", "Exponential function tests")
+		<< BuiltinFuncInfo("pow",			"pow",			GT,	Value(GT, 0.1f, 8.0f),		Value(GT, -4.0f, 2.0f),		notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(pow) )
+		<< BuiltinFuncInfo("exp",			"exp",			GT,	Value(GT, -6.0f, 3.0f),		notUsed,					notUsed,					0.5f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(exp) )
+		<< BuiltinFuncInfo("log",			"log",			GT,	Value(GT, 0.1f, 10.0f),		notUsed,					notUsed,					0.5f, 0.3f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(log) )
+		<< BuiltinFuncInfo("exp2",			"exp2",			GT,	Value(GT, -7.0f, 2.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(exp2) )
+		<< BuiltinFuncInfo("log2",			"log2",			GT,	Value(GT, 0.1f, 10.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(log2) )
+		<< BuiltinFuncInfo("sqrt",			"sqrt",			GT,	Value(GT, 0.0f, 10.0f),		notUsed,					notUsed,					0.3f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(sqrt) )
+		<< BuiltinFuncInfo("inversesqrt",	"inversesqrt",	GT,	Value(GT, 0.5f, 10.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(inverseSqrt) )
+	);
+
+	// 8.3 Common Functions.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("common_functions", "Common function tests.")
+		<< BuiltinFuncInfo("abs",			"abs",			GT,	Value(GT, -2.0f, 2.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(abs) )
+		<< BuiltinFuncInfo("sign",			"sign",			GT,	Value(GT, -1.5f, 1.5f),		notUsed,					notUsed,					0.3f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(sign) )
+		<< BuiltinFuncInfo("floor",			"floor",		GT,	Value(GT, -2.5f, 2.5f),		notUsed,					notUsed,					0.2f, 0.7f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(floor) )
+		<< BuiltinFuncInfo("ceil",			"ceil",			GT,	Value(GT, -2.5f, 2.5f),		notUsed,					notUsed,					0.2f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(ceil) )
+		<< BuiltinFuncInfo("fract",			"fract",		GT,	Value(GT, -1.5f, 1.5f),		notUsed,					notUsed,					0.8f, 0.1f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(fract) )
+		<< BuiltinFuncInfo("mod",			"mod",			GT,	Value(GT, -2.0f, 2.0f),		Value(GT, 0.9f, 6.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(mod) )
+		<< BuiltinFuncInfo("mod",			"mod",			GT,	Value(FV, -2.0f, 2.0f),		Value(F, 0.9f, 6.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_VEC_FUNCS(modVecFloat) )
+		<< BuiltinFuncInfo("min",			"min",			GT,	Value(GT, -1.0f, 1.0f),		Value(GT, -1.0f, 1.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(min) )
+		<< BuiltinFuncInfo("min",			"min",			GT,	Value(FV, -1.0f, 1.0f),		Value(F, -1.0f, 1.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_VEC_FUNCS(minVecFloat) )
+		<< BuiltinFuncInfo("max",			"max",			GT,	Value(GT, -1.0f, 1.0f),		Value(GT, -1.0f, 1.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(max) )
+		<< BuiltinFuncInfo("max",			"max",			GT,	Value(FV, -1.0f, 1.0f),		Value(F, -1.0f, 1.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_VEC_FUNCS(maxVecFloat) )
+		<< BuiltinFuncInfo("clamp",			"clamp",		GT,	Value(GT, -1.0f, 1.0f),		Value(GT, -0.5f, 0.5f),		Value(GT, 0.5f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(clamp) )
+		<< BuiltinFuncInfo("clamp",			"clamp",		GT,	Value(FV, -1.0f, 1.0f),		Value(F, -0.5f, 0.5f),		Value(F, 0.5f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_VEC_FUNCS(clampVecFloatFloat) )
+		<< BuiltinFuncInfo("mix",			"mix",			GT,	Value(GT, -1.0f, 1.0f),		Value(GT, -1.0f, 1.0f),		Value(GT, 0.0f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(mix) )
+		<< BuiltinFuncInfo("mix",			"mix",			GT,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),		Value(F, 0.0f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_VEC_FUNCS(mixVecVecFloat) )
+		<< BuiltinFuncInfo("step",			"step",			GT,	Value(GT, -1.0f, 1.0f),		Value(GT, -1.0f, 0.0f),		notUsed,					0.5f, 0.25f,	PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(step) )
+		<< BuiltinFuncInfo("step",			"step",			GT,	Value(F, -1.0f, 1.0f),		Value(FV, -1.0f, 0.0f),		notUsed,					0.5f, 0.25f,	PRECMASK_ALL,				FLOAT_VEC_FUNCS(stepFloatVec) )
+		<< BuiltinFuncInfo("smoothstep",	"smoothstep",	GT,	Value(GT, -0.5f, 0.0f),		Value(GT, 0.1f, 1.0f),		Value(GT, -1.0f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(smoothStep) )
+		<< BuiltinFuncInfo("smoothstep",	"smoothstep",	GT,	Value(F, -0.5f, 0.0f),		Value(F, 0.1f, 1.0f),		Value(FV, -1.0f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_VEC_FUNCS(smoothStepFloatFloatVec) )
+	);
+
+	// 8.4 Geometric Functions.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("geometric", "Geometric function tests.")
+		<< BuiltinFuncInfo("length",		"length",		F,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.1f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(length) )
+		<< BuiltinFuncInfo("distance",		"distance",		F,	Value(GT, -5.0f, 5.0f),		Value(GT, -5.0f, 5.0f),		notUsed,					0.1f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(distance) )
+		<< BuiltinFuncInfo("dot",			"dot",			F,	Value(GT, -5.0f, 5.0f),		Value(GT, -5.0f, 5.0f),		notUsed,					0.1f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(dot) )
+		<< BuiltinFuncInfo("cross",			"cross",		V3,	Value(GT, -5.0f, 5.0f),		Value(GT, -5.0f, 5.0f),		notUsed,					0.1f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		DE_NULL, DE_NULL, eval_cross_vec3, DE_NULL )
+		<< BuiltinFuncInfo("normalize",		"normalize",	GT,	Value(GT, 0.1f, 4.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(normalize) )
+		<< BuiltinFuncInfo("faceforward",	"faceforward",	GT,	Value(GT, -5.0f, 5.0f),		Value(GT, -5.0f, 5.0f),		Value(GT, -1.0f, 1.0f),		0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(faceForward) )
+		<< BuiltinFuncInfo("reflect",		"reflect",		GT,	Value(GT, -0.8f, -0.5f),	Value(GT, 0.5f, 0.8f),		notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(reflect) )
+		<< BuiltinFuncInfo("refract",		"refract",		GT,	Value(GT, -0.8f, 1.2f),		Value(GT, -1.1f, 0.5f),		Value(F, 0.2f, 1.5f),		0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(refract) )
+	);
+
+	// 8.5 Matrix Functions.
+	// separate matrix tests?
+//	funcInfoGroups.push_back(
+//		BuiltinFuncGroup("matrix", "Matrix function tests.")
+//		<< BuiltinFuncInfo("matrixCompMult",	"matrixCompMult",	M, ... )
+//	);
+
+	// 8.6 Vector Relational Functions.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("float_compare", "Floating point comparison tests.")
+		<< BuiltinFuncInfo("lessThan",			"lessThan",			BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(lessThan) )
+		<< BuiltinFuncInfo("lessThanEqual",		"lessThanEqual",	BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(lessThanEqual) )
+		<< BuiltinFuncInfo("greaterThan",		"greaterThan",		BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(greaterThan) )
+		<< BuiltinFuncInfo("greaterThanEqual",	"greaterThanEqual",	BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(greaterThanEqual) )
+		<< BuiltinFuncInfo("equal",				"equal",			BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(equal) )
+		<< BuiltinFuncInfo("notEqual",			"notEqual",			BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(notEqual) )
+	);
+
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("int_compare", "Integer comparison tests.")
+		<< BuiltinFuncInfo("lessThan",			"lessThan",			BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(lessThan) )
+		<< BuiltinFuncInfo("lessThanEqual",		"lessThanEqual",	BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(lessThanEqual) )
+		<< BuiltinFuncInfo("greaterThan",		"greaterThan",		BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(greaterThan) )
+		<< BuiltinFuncInfo("greaterThanEqual",	"greaterThanEqual",	BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(greaterThanEqual) )
+		<< BuiltinFuncInfo("equal",				"equal",			BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(equal) )
+		<< BuiltinFuncInfo("notEqual",			"notEqual",			BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(notEqual) )
+	);
+
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("bool_compare", "Boolean comparison tests.")
+		<< BuiltinFuncInfo("equal",				"equal",			BV,	Value(BV, -5.2f, 4.9f),		Value(BV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_NA,	BOOL_VEC_FUNCS(equal) )
+		<< BuiltinFuncInfo("notEqual",			"notEqual",			BV,	Value(BV, -5.2f, 4.9f),		Value(BV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_NA,	BOOL_VEC_FUNCS(notEqual) )
+		<< BuiltinFuncInfo("any",				"any",				B,	Value(BV, -1.0f, 0.3f),		notUsed,				notUsed, 1.0f, 0.0f, PRECMASK_NA,	BOOL_VEC_FUNCS(any) )
+		<< BuiltinFuncInfo("all",				"all",				B,	Value(BV, -0.3f, 1.0f),		notUsed,				notUsed, 1.0f, 0.0f, PRECMASK_NA,	BOOL_VEC_FUNCS(all) )
+		<< BuiltinFuncInfo("not",				"not",				BV,	Value(BV, -1.0f, 1.0f),		notUsed,				notUsed, 1.0f, 0.0f, PRECMASK_NA,	BOOL_VEC_FUNCS(boolNot) )
+	);
+
+	// 8.7 Texture Lookup Functions
+	// texture2D (sampler, vec2)
+	// texture2D (sampler, vec2, bias)
+	// texture2DProj (sampler, vec3)
+	// texture2DProj (sampler, vec3, bias)
+	// texture2DProj (sampler, vec4)
+	// texture2DProj (sampler, vec4, bias)
+	// texture2DLod (sampler, vec2, lod)
+	// texture2DProjLod (sampler, vec3, lod)
+	// texture2DProjLod (sampler, vec4, lod)
+	// textureCube (sampler, vec3)
+	// textureCube (sampler, vec3, bias)
+	// textureCubeLod (sampler, vec3, lod)
+
+	static const ShaderType s_shaderTypes[] =
+	{
+		SHADERTYPE_VERTEX,
+		SHADERTYPE_FRAGMENT
+	};
+
+	static const DataType s_floatTypes[] =
+	{
+		TYPE_FLOAT,
+		TYPE_FLOAT_VEC2,
+		TYPE_FLOAT_VEC3,
+		TYPE_FLOAT_VEC4
+	};
+
+	static const DataType s_intTypes[] =
+	{
+		TYPE_INT,
+		TYPE_INT_VEC2,
+		TYPE_INT_VEC3,
+		TYPE_INT_VEC4
+	};
+
+	static const DataType s_boolTypes[] =
+	{
+		TYPE_BOOL,
+		TYPE_BOOL_VEC2,
+		TYPE_BOOL_VEC3,
+		TYPE_BOOL_VEC4
+	};
+
+	for (int outerGroupNdx = 0; outerGroupNdx < (int)funcInfoGroups.size(); outerGroupNdx++)
+	{
+		// Create outer group.
+		const BuiltinFuncGroup& outerGroupInfo = funcInfoGroups[outerGroupNdx];
+		TestCaseGroup* outerGroup = new TestCaseGroup(m_context, outerGroupInfo.name, outerGroupInfo.description);
+		addChild(outerGroup);
+
+		// Only create new group if name differs from previous one.
+		TestCaseGroup* innerGroup = DE_NULL;
+
+		for (int funcInfoNdx = 0; funcInfoNdx < (int)outerGroupInfo.funcInfos.size(); funcInfoNdx++)
+		{
+			const BuiltinFuncInfo&	funcInfo 		= outerGroupInfo.funcInfos[funcInfoNdx];
+			const char*				shaderFuncName	= funcInfo.shaderFuncName;
+			bool					isBoolCase		= (funcInfo.precisionMask == PRECMASK_NA);
+			bool					isIntCase		= (funcInfo.input0.valueType & (VALUE_INT | VALUE_INT_VEC | VALUE_INT_GENTYPE)) != 0;
+			bool					isFloatCase		= !isBoolCase && !isIntCase;	// \todo [petri] Better check.
+			bool					isBoolOut		= (funcInfo.outValue & (VALUE_BOOL | VALUE_BOOL_VEC | VALUE_BOOL_GENTYPE)) != 0;
+			bool					isIntOut		= (funcInfo.outValue & (VALUE_INT | VALUE_INT_VEC | VALUE_INT_GENTYPE)) != 0;
+			bool					isFloatOut		= !isBoolOut && !isIntOut;
+
+			if (!innerGroup || (string(innerGroup->getName()) != funcInfo.caseName))
+			{
+				string groupDesc = string("Built-in function ") + shaderFuncName + "() tests.";
+				innerGroup = new TestCaseGroup(m_context, funcInfo.caseName, groupDesc.c_str());
+				outerGroup->addChild(innerGroup);
+			}
+
+			for (int inScalarSize = 1; inScalarSize <= 4; inScalarSize++)
+			{
+				int			outScalarSize	= ((funcInfo.outValue == VALUE_FLOAT) || (funcInfo.outValue == VALUE_BOOL)) ? 1 : inScalarSize; // \todo [petri] Int.
+				DataType	outDataType		= isFloatOut ? s_floatTypes[outScalarSize - 1]
+											: isIntOut ? s_intTypes[outScalarSize - 1]
+											: isBoolOut ? s_boolTypes[outScalarSize - 1]
+											: TYPE_LAST;
+
+				ShaderEvalFunc evalFunc = DE_NULL;
+				if      (inScalarSize == 1)	evalFunc = funcInfo.evalFuncScalar;
+				else if (inScalarSize == 2)	evalFunc = funcInfo.evalFuncVec2;
+				else if (inScalarSize == 3)	evalFunc = funcInfo.evalFuncVec3;
+				else if (inScalarSize == 4)	evalFunc = funcInfo.evalFuncVec4;
+				else DE_ASSERT(false);
+
+				// Skip if no valid eval func.
+				// \todo [petri] Better check for V3 only etc. cases?
+				if (evalFunc == DE_NULL)
+					continue;
+
+				for (int precision = 0; precision < PRECISION_LAST; precision++)
+				{
+					if ((funcInfo.precisionMask & (1<<precision)) ||
+						(funcInfo.precisionMask == PRECMASK_NA && precision == PRECISION_MEDIUMP)) // use mediump interpolators for booleans
+					{
+						const char*	precisionStr	= getPrecisionName((Precision)precision);
+						string		precisionPrefix	= isBoolCase ? "" : (string(precisionStr) + "_");
+
+						for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+						{
+							ShaderType		shaderType		= s_shaderTypes[shaderTypeNdx];
+							ShaderDataSpec	shaderSpec;
+							const char*		shaderTypeName	= getShaderTypeName(shaderType);
+							bool			isVertexCase	= (ShaderType)shaderType == SHADERTYPE_VERTEX;
+							bool			isUnaryOp		= (funcInfo.input1.valueType == VALUE_NONE);
+
+							// \note Data type names will be added to description and name in a following loop.
+							string desc	= string("Built-in function ") + shaderFuncName + "(";
+							string name = precisionPrefix;
+
+							// Generate shader op.
+							string shaderOp = string("res = ");
+
+							// Setup shader data info.
+							shaderSpec.numInputs	= 0;
+							shaderSpec.precision	= isBoolCase ? PRECISION_LAST : (Precision)precision;
+							shaderSpec.output		= outDataType;
+							shaderSpec.resultScale	= funcInfo.resultScale;
+							shaderSpec.resultBias	= funcInfo.resultBias;
+
+							if (funcInfo.type == OPERATOR)
+							{
+								if (isUnaryOp && funcInfo.isUnaryPrefix)
+									shaderOp += shaderFuncName;
+							}
+							else if (funcInfo.type == FUNCTION)
+								shaderOp += string(shaderFuncName) + "(";
+							else // SIDE_EFFECT_OPERATOR
+								shaderOp += "in0;\n\t";
+
+							for (int inputNdx = 0; inputNdx < MAX_INPUTS; inputNdx++)
+							{
+								const Value&	v				= (inputNdx == 0) ? funcInfo.input0 : (inputNdx == 1) ? funcInfo.input1 : funcInfo.input2;
+								const Value&	prevV			= (inputNdx == 1) ? funcInfo.input0 : (inputNdx == 2) ? funcInfo.input1 : funcInfo.input2;
+
+								if (v.valueType == VALUE_NONE)
+									continue; // Skip unused input.
+
+								int				curInScalarSize	= isScalarType(v.valueType) ? 1 : inScalarSize;
+								DataType		curInDataType	= isFloatCase ? s_floatTypes[curInScalarSize - 1]
+																: isIntCase ? s_intTypes[curInScalarSize - 1]
+																: isBoolCase ? s_boolTypes[curInScalarSize - 1]
+																: TYPE_LAST;
+
+								// Write input type(s) to case description and name.
+
+								if (inputNdx > 0)
+									desc += ", ";
+
+								desc += getDataTypeName(curInDataType);
+
+								if (inputNdx == 0 || isScalarType(prevV.valueType) != isScalarType(v.valueType)) // \note Only write input type to case name if different from previous input type (avoid overly long names).
+									name += string("") + getDataTypeName(curInDataType) + "_";
+
+								// Generate op input source.
+
+								if (funcInfo.type == OPERATOR || funcInfo.type == FUNCTION)
+								{
+									if (inputNdx != 0)
+									{
+										if (funcInfo.type == OPERATOR && !isUnaryOp)
+											shaderOp += " " + string(shaderFuncName) + " ";
+										else
+											shaderOp += ", ";
+									}
+
+									shaderOp += "in" + de::toString(inputNdx);
+
+									if (funcInfo.type == OPERATOR && isUnaryOp && !funcInfo.isUnaryPrefix)
+										shaderOp += string(shaderFuncName);
+								}
+								else
+								{
+									DE_ASSERT(funcInfo.type == SIDE_EFFECT_OPERATOR);
+
+									if (inputNdx != 0 || (isUnaryOp && funcInfo.isUnaryPrefix))
+										shaderOp += string("") + (isUnaryOp ? "" : " ") + shaderFuncName + (isUnaryOp ? "" : " ");
+
+									shaderOp += inputNdx == 0 ? "res" : "in" + de::toString(inputNdx); // \note in0 has already been assigned to res, so start from in1.
+
+									if (isUnaryOp && !funcInfo.isUnaryPrefix)
+										shaderOp += shaderFuncName;
+								}
+
+								// Fill in shader info.
+								shaderSpec.inputs[shaderSpec.numInputs++] = ShaderValue(curInDataType, v.rangeMin, v.rangeMax);
+							}
+
+							if (funcInfo.type == FUNCTION)
+								shaderOp += ")";
+
+							shaderOp += ";";
+
+							desc += ").";
+							name += shaderTypeName;
+
+							// Create the test case.
+							innerGroup->addChild(new ShaderOperatorCase(m_context, name.c_str(), desc.c_str(), isVertexCase, evalFunc, shaderOp.c_str(), shaderSpec));
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// The ?: selection operator.
+
+	static const struct
+	{
+		DataType		type; // The type of "Y" and "Z" operands in "X ? Y : Z" (X is always bool).
+		ShaderEvalFunc	evalFunc;
+	} s_selectionInfo[] =
+	{
+		{ TYPE_FLOAT,		eval_selection_float	},
+		{ TYPE_FLOAT_VEC2,	eval_selection_vec2		},
+		{ TYPE_FLOAT_VEC3,	eval_selection_vec3		},
+		{ TYPE_FLOAT_VEC4,	eval_selection_vec4		},
+		{ TYPE_INT,			eval_selection_int		},
+		{ TYPE_INT_VEC2,	eval_selection_ivec2	},
+		{ TYPE_INT_VEC3,	eval_selection_ivec3	},
+		{ TYPE_INT_VEC4,	eval_selection_ivec4	},
+		{ TYPE_BOOL,		eval_selection_bool		},
+		{ TYPE_BOOL_VEC2,	eval_selection_bvec2	},
+		{ TYPE_BOOL_VEC3,	eval_selection_bvec3	},
+		{ TYPE_BOOL_VEC4,	eval_selection_bvec4	}
+	};
+
+	TestCaseGroup* selectionGroup = new TestCaseGroup(m_context, "selection", "Selection operator tests");
+	addChild(selectionGroup);
+
+	for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_selectionInfo); typeNdx++)
+	{
+		DataType		curType			= s_selectionInfo[typeNdx].type;
+		ShaderEvalFunc	evalFunc		= s_selectionInfo[typeNdx].evalFunc;
+		bool			isBoolCase		= isDataTypeBoolOrBVec(curType);
+		bool			isFloatCase		= isDataTypeFloatOrVec(curType);
+		bool			isIntCase		= isDataTypeIntOrIVec(curType);
+		const char*		dataTypeStr		= getDataTypeName(curType);
+
+		DE_ASSERT(isBoolCase || isFloatCase || isIntCase);
+		DE_UNREF(isIntCase);
+
+		for (int precision = 0; precision < (int)PRECISION_LAST; precision++)
+		{
+			if (isBoolCase && precision != PRECISION_MEDIUMP) // Use mediump interpolators for booleans.
+				continue;
+
+			const char*	precisionStr	= getPrecisionName((Precision)precision);
+			string		precisionPrefix	= isBoolCase ? "" : (string(precisionStr) + "_");
+
+			for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+			{
+				ShaderType		shaderType		= s_shaderTypes[shaderTypeNdx];
+				ShaderDataSpec	shaderSpec;
+				const char*		shaderTypeName	= getShaderTypeName(shaderType);
+				bool			isVertexCase	= (ShaderType)shaderType == SHADERTYPE_VERTEX;
+
+				string name	= precisionPrefix + dataTypeStr + "_" + shaderTypeName;
+
+				shaderSpec.numInputs	= 3;
+				shaderSpec.precision	= isBoolCase ? PRECISION_LAST : (Precision)precision;
+				shaderSpec.output		= curType;
+				shaderSpec.resultScale	= isBoolCase ? 1.0f : isFloatCase ? 0.5f : 0.1f;
+				shaderSpec.resultBias	= isBoolCase ? 0.0f : isFloatCase ? 0.5f : 0.5f;
+
+				float rangeMin = isBoolCase ? -1.0f : isFloatCase ? -1.0f : -5.0f;
+				float rangeMax = isBoolCase ?  1.0f : isFloatCase ?  1.0f :  5.0f;
+
+				shaderSpec.inputs[0] = ShaderValue(TYPE_BOOL, -1.0f, 1.0f);
+				shaderSpec.inputs[1] = ShaderValue(curType, rangeMin, rangeMax);
+				shaderSpec.inputs[2] = ShaderValue(curType, rangeMin, rangeMax);
+
+				selectionGroup->addChild(new ShaderOperatorCase(m_context, name.c_str(), "", isVertexCase, evalFunc, "res = in0 ? in1 : in2;", shaderSpec));
+			}
+		}
+	}
+
+	// The sequence operator (comma).
+
+	TestCaseGroup* sequenceGroup = new TestCaseGroup(m_context, "sequence", "Sequence operator tests");
+	addChild(sequenceGroup);
+
+	TestCaseGroup* sequenceNoSideEffGroup = new TestCaseGroup(m_context, "no_side_effects", "Sequence tests without side-effects");
+	TestCaseGroup* sequenceSideEffGroup = new TestCaseGroup(m_context, "side_effects", "Sequence tests with side-effects");
+	sequenceGroup->addChild(sequenceNoSideEffGroup);
+	sequenceGroup->addChild(sequenceSideEffGroup);
+
+	static const struct
+	{
+		bool			containsSideEffects;
+		const char*		caseName;
+		const char*		expressionStr;
+		int				numInputs;
+		DataType		inputTypes[MAX_INPUTS];
+		DataType		resultType;
+		ShaderEvalFunc	evalFunc;
+	} s_sequenceCases[] =
+	{
+		{ false,	"vec4",					"in0, in2 + in1, in1 + in0",							3,	{ TYPE_FLOAT_VEC4,	TYPE_FLOAT_VEC4,	TYPE_FLOAT_VEC4	},	TYPE_FLOAT_VEC4,	evalSequenceNoSideEffCase0 },
+		{ false,	"float_int",			"in0 + in2, in1 + in1",									3,	{ TYPE_FLOAT,		TYPE_INT,			TYPE_FLOAT		},	TYPE_INT,			evalSequenceNoSideEffCase1 },
+		{ false,	"bool_vec2",			"in0 && in1, in0, ivec2(vec2(in0) + in2)",				3,	{ TYPE_BOOL,		TYPE_BOOL,			TYPE_FLOAT_VEC2	},	TYPE_INT_VEC2,		evalSequenceNoSideEffCase2 },
+		{ false,	"vec4_ivec4_bvec4",		"in0 + vec4(in1), in2, in1",							3,	{ TYPE_FLOAT_VEC4,	TYPE_INT_VEC4,		TYPE_BOOL_VEC4	},	TYPE_INT_VEC4,		evalSequenceNoSideEffCase3 },
+
+		{ true,		"vec4",					"in0++, in1 = in0 + in2, in2 = in1",					3,	{ TYPE_FLOAT_VEC4,	TYPE_FLOAT_VEC4,	TYPE_FLOAT_VEC4	},	TYPE_FLOAT_VEC4,	evalSequenceSideEffCase0 },
+		{ true,		"float_int",			"in1++, in0 = float(in1), in1 = int(in0 + in2)",		3,	{ TYPE_FLOAT,		TYPE_INT,			TYPE_FLOAT		},	TYPE_INT,			evalSequenceSideEffCase1 },
+		{ true,		"bool_vec2",			"in1 = in0, in2++, in2 = in2 + vec2(in1), ivec2(in2)",	3,	{ TYPE_BOOL,		TYPE_BOOL,			TYPE_FLOAT_VEC2	},	TYPE_INT_VEC2,		evalSequenceSideEffCase2 },
+		{ true,		"vec4_ivec4_bvec4",		"in0 = in0 + vec4(in2), in1 = in1 + ivec4(in0), in1++",	3,	{ TYPE_FLOAT_VEC4,	TYPE_INT_VEC4,		TYPE_BOOL_VEC4	},	TYPE_INT_VEC4,		evalSequenceSideEffCase3 }
+	};
+
+	for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(s_sequenceCases); caseNdx++)
+	{
+		for (int precision = 0; precision < (int)PRECISION_LAST; precision++)
+		{
+			for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+			{
+				ShaderType		shaderType		= s_shaderTypes[shaderTypeNdx];
+				ShaderDataSpec	shaderSpec;
+				const char*		shaderTypeName	= getShaderTypeName(shaderType);
+				bool			isVertexCase	= (ShaderType)shaderType == SHADERTYPE_VERTEX;
+
+				string name	= string("") + getPrecisionName((Precision)precision) + "_" + s_sequenceCases[caseNdx].caseName + "_" + shaderTypeName;
+
+				shaderSpec.numInputs	= s_sequenceCases[caseNdx].numInputs;
+				shaderSpec.precision	= (Precision)precision;
+				shaderSpec.output		= s_sequenceCases[caseNdx].resultType;
+				shaderSpec.resultScale	= 0.5f;
+				shaderSpec.resultBias	= 0.0f;
+
+				for (int inputNdx = 0; inputNdx < s_sequenceCases[caseNdx].numInputs; inputNdx++)
+				{
+					DataType	type		= s_sequenceCases[caseNdx].inputTypes[inputNdx];
+					float		rangeMin	= isDataTypeFloatOrVec(type) ? -0.5f : isDataTypeIntOrIVec(type) ? -2.0f : -1.0f;
+					float		rangeMax	= isDataTypeFloatOrVec(type) ?  0.5f : isDataTypeIntOrIVec(type) ?  2.0f :  1.0f;
+
+					shaderSpec.inputs[inputNdx] = ShaderValue(type, rangeMin, rangeMax);
+				}
+
+				string expression = string("") + "res = (" + s_sequenceCases[caseNdx].expressionStr + ");";
+
+				TestCaseGroup* group = s_sequenceCases[caseNdx].containsSideEffects ? sequenceSideEffGroup : sequenceNoSideEffGroup;
+				group->addChild(new ShaderOperatorCase(m_context, name.c_str(), "", isVertexCase, s_sequenceCases[caseNdx].evalFunc, expression.c_str(), shaderSpec));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderOperatorTests.hpp b/modules/gles2/functional/es2fShaderOperatorTests.hpp
new file mode 100644
index 0000000..f3d04a6
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderOperatorTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADEROPERATORTESTS_HPP
+#define _ES2FSHADEROPERATORTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader operators tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderOperatorTests : public TestCaseGroup
+{
+public:
+							ShaderOperatorTests		(Context& context);
+	virtual					~ShaderOperatorTests	(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderOperatorTests		(const ShaderOperatorTests&);		// not allowed!
+	ShaderOperatorTests&	operator=				(const ShaderOperatorTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADEROPERATORTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderReturnTests.cpp b/modules/gles2/functional/es2fShaderReturnTests.cpp
new file mode 100644
index 0000000..ff86ec9
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderReturnTests.cpp
@@ -0,0 +1,469 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader return statement tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderReturnTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "tcuStringTemplate.hpp"
+
+#include <map>
+#include <sstream>
+#include <string>
+
+using tcu::StringTemplate;
+
+using std::map;
+using std::string;
+using std::ostringstream;
+
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+enum ReturnMode
+{
+	RETURNMODE_ALWAYS = 0,
+	RETURNMODE_NEVER,
+	RETURNMODE_DYNAMIC,
+
+	RETURNMODE_LAST
+};
+
+enum RequireFlags
+{
+	REQUIRE_DYNAMIC_LOOPS = (1<<0),
+};
+
+// Evaluation functions
+inline void evalReturnAlways	(ShaderEvalContext& c) { c.color.xyz() = c.coords.swizzle(0,1,2); }
+inline void evalReturnNever		(ShaderEvalContext& c) { c.color.xyz() = c.coords.swizzle(3,2,1); }
+inline void evalReturnDynamic	(ShaderEvalContext& c) { c.color.xyz() = (c.coords.x()+c.coords.y() >= 0.0f) ? c.coords.swizzle(0,1,2) : c.coords.swizzle(3,2,1); }
+
+static ShaderEvalFunc getEvalFunc (ReturnMode mode)
+{
+	switch (mode)
+	{
+		case RETURNMODE_ALWAYS:		return evalReturnAlways;
+		case RETURNMODE_NEVER:		return evalReturnNever;
+		case RETURNMODE_DYNAMIC:	return evalReturnDynamic;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return (ShaderEvalFunc)DE_NULL;
+	}
+}
+
+class ShaderReturnCase : public ShaderRenderCase
+{
+public:
+						ShaderReturnCase			(Context& context, const char* name, const char* description, bool isVertexCase, const char* shaderSource, ShaderEvalFunc evalFunc, deUint32 requirements = 0);
+	virtual				~ShaderReturnCase			(void);
+
+	void				init						(void);
+
+private:
+	const deUint32		m_requirements;
+};
+
+ShaderReturnCase::ShaderReturnCase (Context& context, const char* name, const char* description, bool isVertexCase, const char* shaderSource, ShaderEvalFunc evalFunc, deUint32 requirements)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
+	, m_requirements	(requirements)
+{
+	if (isVertexCase)
+	{
+		m_vertShaderSource = shaderSource;
+		m_fragShaderSource =
+			"varying mediump vec4 v_color;\n\n"
+			"void main (void)\n"
+			"{\n"
+			"    gl_FragColor = v_color;\n"
+			"}\n";
+	}
+	else
+	{
+		m_fragShaderSource = shaderSource;
+		m_vertShaderSource =
+			"attribute highp   vec4 a_position;\n"
+			"attribute highp   vec4 a_coords;\n"
+			"varying   mediump vec4 v_coords;\n\n"
+			"void main (void)\n"
+			"{\n"
+			"    gl_Position = a_position;\n"
+			"    v_coords = a_coords;\n"
+			"}\n";
+	}
+}
+
+ShaderReturnCase::~ShaderReturnCase (void)
+{
+}
+
+void ShaderReturnCase::init (void)
+{
+	try
+	{
+		ShaderRenderCase::init();
+	}
+	catch (const CompileFailed&)
+	{
+		if (m_requirements & REQUIRE_DYNAMIC_LOOPS)
+		{
+			const bool isSupported = m_isVertexCase ? m_ctxInfo.isVertexDynamicLoopSupported() : m_ctxInfo.isFragmentDynamicLoopSupported();
+			if (!isSupported)
+				throw tcu::NotSupportedError("Dynamic loops not supported");
+		}
+
+		throw;
+	}
+}
+
+ShaderReturnTests::ShaderReturnTests (Context& context)
+	: TestCaseGroup(context, "return", "Return Statement Tests")
+{
+}
+
+ShaderReturnTests::~ShaderReturnTests (void)
+{
+}
+
+ShaderReturnCase* makeConditionalReturnInFuncCase (Context& context, const char* name, const char* description, ReturnMode returnMode, bool isVertex)
+{
+	// Template
+	StringTemplate tmpl(
+		"${COORDSTORAGE} ${COORDPREC} vec4 ${COORDS};\n"
+		"${EXTRADECL}\n"
+		"${COORDPREC} vec4 getColor (void)\n"
+		"{\n"
+		"    if (${RETURNCOND})\n"
+		"        return vec4(${COORDS}.xyz, 1.0);\n"
+		"    return vec4(${COORDS}.wzy, 1.0);\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"${POSITIONWRITE}"
+		"    ${OUTPUT} = getColor();\n"
+		"}\n");
+
+	const char* coords = isVertex ? "a_coords" : "v_coords";
+
+	map<string, string> params;
+
+	params["COORDSTORAGE"]	= isVertex ? "attribute"	: "varying";
+	params["COORDPREC"]		= isVertex ? "highp"		: "mediump";
+	params["OUTPUT"]		= isVertex ? "v_color"		: "gl_FragColor";
+	params["COORDS"]		= coords;
+	params["EXTRADECL"]		= isVertex ? "attribute highp vec4 a_position;\nvarying mediump vec4 v_color;\n" : "";
+	params["POSITIONWRITE"]	= isVertex ? "    gl_Position = a_position;\n" : "";
+
+	switch (returnMode)
+	{
+		case RETURNMODE_ALWAYS:		params["RETURNCOND"] = "true";											break;
+		case RETURNMODE_NEVER:		params["RETURNCOND"] = "false";											break;
+		case RETURNMODE_DYNAMIC:	params["RETURNCOND"] = string(coords) + ".x+" + coords + ".y >= 0.0";	break;
+		default:					DE_ASSERT(DE_FALSE);
+	}
+
+	return new ShaderReturnCase(context, name, description, isVertex, tmpl.specialize(params).c_str(), getEvalFunc(returnMode));
+}
+
+ShaderReturnCase* makeOutputWriteReturnCase (Context& context, const char* name, const char* description, bool inFunction, ReturnMode returnMode, bool isVertex)
+{
+	// Template
+	StringTemplate tmpl(
+		inFunction
+		?
+			"${COORDATTRS} vec4 ${COORDS};\n"
+			"${EXTRADECL}\n"
+			"void myfunc (void)\n"
+			"{\n"
+			"    ${OUTPUT} = vec4(${COORDS}.xyz, 1.0);\n"
+			"    if (${RETURNCOND})\n"
+			"        return;\n"
+			"    ${OUTPUT} = vec4(${COORDS}.wzy, 1.0);\n"
+			"}\n\n"
+			"void main (void)\n"
+			"{\n"
+			"${POSITIONWRITE}"
+			"    myfunc();\n"
+			"}\n"
+		:
+			"${COORDATTRS} vec4 ${COORDS};\n"
+			"uniform mediump int ui_one;\n"
+			"${EXTRADECL}\n"
+			"void main ()\n"
+			"{\n"
+			"${POSITIONWRITE}"
+			"    ${OUTPUT} = vec4(${COORDS}.xyz, 1.0);\n"
+			"    if (${RETURNCOND})\n"
+			"        return;\n"
+			"    ${OUTPUT} = vec4(${COORDS}.wzy, 1.0);\n"
+			"}\n");
+
+	const char* coords = isVertex ? "a_coords" : "v_coords";
+
+	map<string, string> params;
+
+	params["COORDATTRS"]	= isVertex ? "attribute highp"	: "varying mediump";
+	params["COORDS"]		= coords;
+	params["OUTPUT"]		= isVertex ? "v_color"			: "gl_FragColor";
+	params["EXTRADECL"]		= isVertex ? "attribute highp vec4 a_position;\nvarying mediump vec4 v_color;\n" : "";
+	params["POSITIONWRITE"]	= isVertex ? "    gl_Position = a_position;\n" : "";
+
+	switch (returnMode)
+	{
+		case RETURNMODE_ALWAYS:		params["RETURNCOND"] = "true";											break;
+		case RETURNMODE_NEVER:		params["RETURNCOND"] = "false";											break;
+		case RETURNMODE_DYNAMIC:	params["RETURNCOND"] = string(coords) + ".x+" + coords + ".y >= 0.0";	break;
+		default:					DE_ASSERT(DE_FALSE);
+	}
+
+	return new ShaderReturnCase(context, name, description, isVertex, tmpl.specialize(params).c_str(), getEvalFunc(returnMode));
+}
+
+ShaderReturnCase* makeReturnInLoopCase (Context& context, const char* name, const char* description, bool isDynamicLoop, ReturnMode returnMode, bool isVertex)
+{
+	// Template
+	StringTemplate tmpl(
+		"${COORDSTORAGE} ${COORDPREC} vec4 ${COORDS};\n"
+		"uniform mediump int ui_one;\n"
+		"${EXTRADECL}\n"
+		"${COORDPREC} vec4 getCoords (void)\n"
+		"{\n"
+		"    ${COORDPREC} vec4 coords = ${COORDS};\n"
+		"    for (int i = 0; i < ${ITERLIMIT}; i++)\n"
+		"    {\n"
+		"        if (${RETURNCOND})\n"
+		"            return coords;\n"
+		"        coords = coords.wzyx;\n"
+		"    }\n"
+		"    return coords;\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"${POSITIONWRITE}"
+		"    ${OUTPUT} = vec4(getCoords().xyz, 1.0);\n"
+		"}\n");
+
+	const char* coords = isVertex ? "a_coords" : "v_coords";
+
+	map<string, string> params;
+
+	params["COORDSTORAGE"]	= isVertex ? "attribute"	: "varying";
+	params["COORDPREC"]		= isVertex ? "highp"		: "mediump";
+	params["OUTPUT"]		= isVertex ? "v_color"		: "gl_FragColor";
+	params["COORDS"]		= coords;
+	params["EXTRADECL"]		= isVertex ? "attribute highp vec4 a_position;\nvarying mediump vec4 v_color;\n" : "";
+	params["POSITIONWRITE"]	= isVertex ? "    gl_Position = a_position;\n" : "";
+	params["ITERLIMIT"]		= isDynamicLoop ? "ui_one" : "1";
+
+	switch (returnMode)
+	{
+		case RETURNMODE_ALWAYS:		params["RETURNCOND"] = "true";											break;
+		case RETURNMODE_NEVER:		params["RETURNCOND"] = "false";											break;
+		case RETURNMODE_DYNAMIC:	params["RETURNCOND"] = string(coords) + ".x+" + coords + ".y >= 0.0";	break;
+		default:					DE_ASSERT(DE_FALSE);
+	}
+
+	return new ShaderReturnCase(context, name, description, isVertex, tmpl.specialize(params).c_str(), getEvalFunc(returnMode), isDynamicLoop ? REQUIRE_DYNAMIC_LOOPS : 0);
+}
+
+static const char* getReturnModeName (ReturnMode mode)
+{
+	switch (mode)
+	{
+		case RETURNMODE_ALWAYS:		return "always";
+		case RETURNMODE_NEVER:		return "never";
+		case RETURNMODE_DYNAMIC:	return "dynamic";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static const char* getReturnModeDesc (ReturnMode mode)
+{
+	switch (mode)
+	{
+		case RETURNMODE_ALWAYS:		return "Always return";
+		case RETURNMODE_NEVER:		return "Never return";
+		case RETURNMODE_DYNAMIC:	return "Return based on coords";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+void ShaderReturnTests::init (void)
+{
+	// Single return statement in function.
+	addChild(new ShaderReturnCase(m_context, "single_return_vertex", "Single return statement in function", true,
+		"attribute highp vec4 a_position;\n"
+		"attribute highp vec4 a_coords;\n"
+		"varying highp vec4 v_color;\n\n"
+		"vec4 getColor (void)\n"
+		"{\n"
+		"    return vec4(a_coords.xyz, 1.0);\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_Position = a_position;\n"
+		"    v_color = getColor();\n"
+		"}\n", evalReturnAlways));
+	addChild(new ShaderReturnCase(m_context, "single_return_fragment", "Single return statement in function", false,
+		"varying mediump vec4 v_coords;\n"
+		"mediump vec4 getColor (void)\n"
+		"{\n"
+		"    return vec4(v_coords.xyz, 1.0);\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_FragColor = getColor();\n"
+		"}\n", evalReturnAlways));
+
+	// Conditional return statement in function.
+	for (int returnMode = 0; returnMode < RETURNMODE_LAST; returnMode++)
+	{
+		for (int isFragment = 0; isFragment < 2; isFragment++)
+		{
+			string name			= string("conditional_return_") + getReturnModeName((ReturnMode)returnMode) + (isFragment ? "_fragment" : "_vertex");
+			string description	= string(getReturnModeDesc((ReturnMode)returnMode)) + " in function";
+			addChild(makeConditionalReturnInFuncCase(m_context, name.c_str(), description.c_str(), (ReturnMode)returnMode, isFragment == 0));
+		}
+	}
+
+	// Unconditional double return in function.
+	addChild(new ShaderReturnCase(m_context, "double_return_vertex", "Unconditional double return in function", true,
+		"attribute highp vec4 a_position;\n"
+		"attribute highp vec4 a_coords;\n"
+		"varying highp vec4 v_color;\n\n"
+		"vec4 getColor (void)\n"
+		"{\n"
+		"    return vec4(a_coords.xyz, 1.0);\n"
+		"    return vec4(a_coords.wzy, 1.0);\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_Position = a_position;\n"
+		"    v_color = getColor();\n"
+		"}\n", evalReturnAlways));
+	addChild(new ShaderReturnCase(m_context, "double_return_fragment", "Unconditional double return in function", false,
+		"varying mediump vec4 v_coords;\n"
+		"mediump vec4 getColor (void)\n"
+		"{\n"
+		"    return vec4(v_coords.xyz, 1.0);\n"
+		"    return vec4(v_coords.wzy, 1.0);\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_FragColor = getColor();\n"
+		"}\n", evalReturnAlways));
+
+	// Last statement in main.
+	addChild(new ShaderReturnCase(m_context, "last_statement_in_main_vertex", "Return as a final statement in main()", true,
+		"attribute highp vec4 a_position;\n"
+		"attribute highp vec4 a_coords;\n"
+		"varying highp vec4 v_color;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_Position = a_position;\n"
+		"    v_color = vec4(a_coords.xyz, 1.0);\n"
+		"    return;\n"
+		"}\n", evalReturnAlways));
+	addChild(new ShaderReturnCase(m_context, "last_statement_in_main_fragment", "Return as a final statement in main()", false,
+		"varying mediump vec4 v_coords;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_FragColor = vec4(v_coords.xyz, 1.0);\n"
+		"    return;\n"
+		"}\n", evalReturnAlways));
+
+	// Return between output variable writes.
+	for (int inFunc = 0; inFunc < 2; inFunc++)
+	{
+		for (int returnMode = 0; returnMode < RETURNMODE_LAST; returnMode++)
+		{
+			for (int isFragment = 0; isFragment < 2; isFragment++)
+			{
+				string name = string("output_write_") + (inFunc ? "in_func_" : "") + getReturnModeName((ReturnMode)returnMode) + (isFragment ? "_fragment" : "_vertex");
+				string desc = string(getReturnModeDesc((ReturnMode)returnMode)) + (inFunc ? " in user-defined function" : " in main()") + " between output writes";
+
+				addChild(makeOutputWriteReturnCase(m_context, name.c_str(), desc.c_str(), inFunc != 0, (ReturnMode)returnMode, isFragment == 0));
+			}
+		}
+	}
+
+	// Conditional return statement in loop.
+	for (int isDynamicLoop = 0; isDynamicLoop < 2; isDynamicLoop++)
+	{
+		for (int returnMode = 0; returnMode < RETURNMODE_LAST; returnMode++)
+		{
+			for (int isFragment = 0; isFragment < 2; isFragment++)
+			{
+				string name			= string("return_in_") + (isDynamicLoop ? "dynamic" : "static") + "_loop_" + getReturnModeName((ReturnMode)returnMode) + (isFragment ? "_fragment" : "_vertex");
+				string description	= string(getReturnModeDesc((ReturnMode)returnMode)) + " in loop";
+				addChild(makeReturnInLoopCase(m_context, name.c_str(), description.c_str(), isDynamicLoop != 0, (ReturnMode)returnMode, isFragment == 0));
+			}
+		}
+	}
+
+	// Unconditional return in infinite loop.
+	addChild(new ShaderReturnCase(m_context, "return_in_infinite_loop_vertex", "Return in infinite loop", true,
+		"attribute highp vec4 a_position;\n"
+		"attribute highp vec4 a_coords;\n"
+		"varying highp vec4 v_color;\n"
+		"uniform int ui_zero;\n\n"
+		"highp vec4 getCoords (void)\n"
+		"{\n"
+		"	for (int i = 1; i < 10; i += ui_zero)\n"
+		"		return a_coords;\n"
+		"	return a_coords.wzyx;\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_Position = a_position;\n"
+		"    v_color = vec4(getCoords().xyz, 1.0);\n"
+		"    return;\n"
+		"}\n", evalReturnAlways, REQUIRE_DYNAMIC_LOOPS));
+	addChild(new ShaderReturnCase(m_context, "return_in_infinite_loop_fragment", "Return in infinite loop", false,
+		"varying mediump vec4 v_coords;\n"
+		"uniform int ui_zero;\n\n"
+		"mediump vec4 getCoords (void)\n"
+		"{\n"
+		"	for (int i = 1; i < 10; i += ui_zero)\n"
+		"		return v_coords;\n"
+		"	return v_coords.wzyx;\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_FragColor = vec4(getCoords().xyz, 1.0);\n"
+		"    return;\n"
+		"}\n", evalReturnAlways, REQUIRE_DYNAMIC_LOOPS));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderReturnTests.hpp b/modules/gles2/functional/es2fShaderReturnTests.hpp
new file mode 100644
index 0000000..2d46439
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderReturnTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERRETURNTESTS_HPP
+#define _ES2FSHADERRETURNTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader return statement tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderReturnTests : public TestCaseGroup
+{
+public:
+							ShaderReturnTests		(Context& context);
+	virtual					~ShaderReturnTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderReturnTests		(const ShaderReturnTests&);		// not allowed!
+	ShaderReturnTests&		operator=				(const ShaderReturnTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERRETURNTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderStateQueryTests.cpp b/modules/gles2/functional/es2fShaderStateQueryTests.cpp
new file mode 100644
index 0000000..ed9b137
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderStateQueryTests.cpp
@@ -0,0 +1,2341 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rbo state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es2fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace
+{
+
+static const char* commonTestVertSource		=	"void main (void)\n"
+												"{\n"
+												"	gl_Position = vec4(0.0);\n"
+												"}\n";
+static const char* commonTestFragSource		=	"void main (void)\n"
+												"{\n"
+												"	gl_FragColor = vec4(0.0);\n"
+												"}\n";
+
+static const char* brokenShader				=	"broken, this should not compile!\n"
+												"\n";
+
+// rounds x.1 to x+1
+template <typename T>
+T roundGLfloatToNearestIntegerUp (GLfloat val)
+{
+	return (T)(ceil(val));
+}
+
+// rounds x.9 to x
+template <typename T>
+T roundGLfloatToNearestIntegerDown (GLfloat val)
+{
+	return (T)(floor(val));
+}
+
+bool checkIntEquals (tcu::TestContext& testCtx, GLint got, GLint expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+		return false;
+	}
+	return true;
+}
+
+void checkPointerEquals (tcu::TestContext& testCtx, const void* got, const void* expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void verifyShaderParam (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint shader, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetShaderiv(shader, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntEquals(testCtx, state, reference);
+}
+
+bool verifyProgramParam (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetProgramiv(program, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		return checkIntEquals(testCtx, state, reference);
+	return false;
+}
+
+void verifyCurrentVertexAttribf (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[4]> attribValue;
+	gl.glGetVertexAttribfv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);
+
+	attribValue.verifyValidity(testCtx);
+
+	if (attribValue[0] != x || attribValue[1] != y || attribValue[2] != z || attribValue[3] != w)
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: Expected [" << x << "," << y << "," << z << "," << w << "];"
+			<< "got [" << attribValue[0] << "," << attribValue[1] << "," << attribValue[2] << "," << attribValue[3] << "]"
+			<< TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid attribute value");
+	}
+}
+
+void verifyCurrentVertexAttribConversion (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[4]> attribValue;
+	gl.glGetVertexAttribiv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);
+
+	attribValue.verifyValidity(testCtx);
+
+	const GLint referenceAsGLintMin[] =
+	{
+		roundGLfloatToNearestIntegerDown<GLint>(x),
+		roundGLfloatToNearestIntegerDown<GLint>(y),
+		roundGLfloatToNearestIntegerDown<GLint>(z),
+		roundGLfloatToNearestIntegerDown<GLint>(w)
+	};
+	const GLint referenceAsGLintMax[] =
+	{
+		roundGLfloatToNearestIntegerUp<GLint>(x),
+		roundGLfloatToNearestIntegerUp<GLint>(y),
+		roundGLfloatToNearestIntegerUp<GLint>(z),
+		roundGLfloatToNearestIntegerUp<GLint>(w)
+	};
+
+	if (attribValue[0] < referenceAsGLintMin[0] || attribValue[0] > referenceAsGLintMax[0] ||
+		attribValue[1] < referenceAsGLintMin[1] || attribValue[1] > referenceAsGLintMax[1] ||
+		attribValue[2] < referenceAsGLintMin[2] || attribValue[2] > referenceAsGLintMax[2] ||
+		attribValue[3] < referenceAsGLintMin[3] || attribValue[3] > referenceAsGLintMax[3])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected in range "
+			<< "[" << referenceAsGLintMin[0] << " " << referenceAsGLintMax[0] << "], "
+			<< "[" << referenceAsGLintMin[1] << " " << referenceAsGLintMax[1] << "], "
+			<< "[" << referenceAsGLintMin[2] << " " << referenceAsGLintMax[2] << "], "
+			<< "[" << referenceAsGLintMin[3] << " " << referenceAsGLintMax[3] << "]"
+			<< "; got "
+			<< attribValue[0] << ", "
+			<< attribValue[1] << ", "
+			<< attribValue[2] << ", "
+			<< attribValue[3] << " "
+			<< "; Input="
+			<< x << "; "
+			<< y << "; "
+			<< z << "; "
+			<< w << " " << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid attribute value");
+	}
+}
+
+void verifyVertexAttrib (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLint index, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetVertexAttribiv(index, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntEquals(testCtx, state, reference);
+}
+
+void verifyUniformValue1f (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, float x)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[1]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x
+		<< "]; got ["
+		<< state[0]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue2f (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, float x, float y)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[2]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue3f (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, float x, float y, float z)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[3]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y ||
+		state[2] != z)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y << ", "
+		<< z
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1] << ", "
+		<< state[2]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue4f (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, float x, float y, float z, float w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[4]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y ||
+		state[2] != z ||
+		state[3] != w)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y << ", "
+		<< z << ", "
+		<< w
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1] << ", "
+		<< state[2] << ", "
+		<< state[3]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue1i (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLint x)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[1]> state;
+	gl.glGetUniformiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x
+		<< "]; got ["
+		<< state[0]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue2i (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLint x, GLint y)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[2]> state;
+	gl.glGetUniformiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue3i (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLint x, GLint y, GLint z)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[3]> state;
+	gl.glGetUniformiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y ||
+		state[2] != z)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y << ", "
+		<< z
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1] << ", "
+		<< state[2]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue4i (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLint x, GLint y, GLint z, GLint w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[4]> state;
+	gl.glGetUniformiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y ||
+		state[2] != z ||
+		state[3] != w)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y << ", "
+		<< z << ", "
+		<< w
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1] << ", "
+		<< state[2] << ", "
+		<< state[3]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+template <int Count>
+void verifyUniformValues (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, const GLfloat* values)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[Count]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (int ndx = 0; ndx < Count; ++ndx)
+	{
+		if (values[ndx] != state[ndx])
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: at index " << ndx << " expected " << values[ndx] << "; got " << state[ndx] << TestLog::EndMessage;
+
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+		}
+	}
+}
+
+template <int N>
+void verifyUniformMatrixValues (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, const GLfloat* values, bool transpose)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[N*N]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (int y = 0; y < N; ++y)
+		for (int x = 0; x < N; ++x)
+		{
+			const int refIndex = y*N + x;
+			const int stateIndex = transpose ? (x*N + y) : (y*N + x);
+
+			if (values[refIndex] != state[stateIndex])
+			{
+				testCtx.getLog() << TestLog::Message << "// ERROR: at index [" << y << "][" << x << "] expected " << values[refIndex] << "; got " << state[stateIndex] << TestLog::EndMessage;
+
+				if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+			}
+		}
+}
+
+void requireShaderCompiler (tcu::TestContext& testCtx, glu::CallLogWrapper& gl)
+{
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	gl.glGetBooleanv(GL_SHADER_COMPILER, &state);
+
+	if (!state.verifyValidity(testCtx) || state != GL_TRUE)
+		throw tcu::NotSupportedError("Test requires SHADER_COMPILER = TRUE");
+}
+
+class ShaderTypeCase : public ApiCase
+{
+public:
+	ShaderTypeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		const GLenum shaderTypes[] = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(shaderTypes); ++ndx)
+		{
+			const GLuint shader = glCreateShader(shaderTypes[ndx]);
+			verifyShaderParam(m_testCtx, *this, shader, GL_SHADER_TYPE, shaderTypes[ndx]);
+			glDeleteShader(shader);
+		}
+	}
+};
+
+class ShaderCompileStatusCase : public ApiCase
+{
+public:
+	ShaderCompileStatusCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		requireShaderCompiler(m_testCtx, *this);
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_FALSE);
+		verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_FALSE);
+
+		glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
+		verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_TRUE);
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ShaderInfoLogCase : public ApiCase
+{
+public:
+	ShaderInfoLogCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		requireShaderCompiler(m_testCtx, *this);
+
+		using tcu::TestLog;
+
+		// INFO_LOG_LENGTH is 0 by default and it includes null-terminator
+		const GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+		verifyShaderParam(m_testCtx, *this, shader, GL_INFO_LOG_LENGTH, 0);
+
+		glShaderSource(shader, 1, &brokenShader, DE_NULL);
+		glCompileShader(shader);
+		expectError(GL_NO_ERROR);
+
+		// check the log length
+		StateQueryMemoryWriteGuard<GLint> logLength;
+		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
+		if (!logLength.verifyValidity(m_testCtx))
+		{
+			glDeleteShader(shader);
+			return;
+		}
+		if (logLength == 0)
+		{
+			glDeleteShader(shader);
+			return;
+		}
+
+		// check normal case
+		{
+			char buffer[2048] = {'x'}; // non-zero initialization
+
+			GLint written = 0; // written does not include null terminator
+			glGetShaderInfoLog(shader, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);
+
+			// check lengths are consistent
+			if (logLength <= DE_LENGTH_OF_ARRAY(buffer))
+			{
+				if (written != logLength-1)
+				{
+					m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length " << logLength-1 << "; got " << written << TestLog::EndMessage;
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
+				}
+			}
+
+			// check null-terminator, either at end of buffer or at buffer[written]
+			const char* terminator = &buffer[DE_LENGTH_OF_ARRAY(buffer) - 1];
+			if (logLength < DE_LENGTH_OF_ARRAY(buffer))
+				terminator = &buffer[written];
+
+			if (*terminator != '\0')
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator, got " << (int)*terminator << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log terminator");
+			}
+		}
+
+		// check with too small buffer
+		{
+			char buffer[2048] = {'x'}; // non-zero initialization
+
+			// check string always ends with \0, even with small buffers
+			GLint written = 0;
+			glGetShaderInfoLog(shader, 1, &written, buffer);
+			if (written != 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length 0; got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
+			}
+			if (buffer[0] != '\0')
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator, got " << (int)buffer[0] << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log terminator");
+			}
+		}
+
+		glDeleteShader(shader);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ShaderSourceCase : public ApiCase
+{
+public:
+	ShaderSourceCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		requireShaderCompiler(m_testCtx, *this);
+
+		using tcu::TestLog;
+
+		// SHADER_SOURCE_LENGTH does include 0-terminator
+		const GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+		verifyShaderParam(m_testCtx, *this, shader, GL_SHADER_SOURCE_LENGTH, 0);
+
+		// check the SHADER_SOURCE_LENGTH
+		{
+			glShaderSource(shader, 1, &brokenShader, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			StateQueryMemoryWriteGuard<GLint> sourceLength;
+			glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &sourceLength);
+
+			sourceLength.verifyValidity(m_testCtx);
+
+			const GLint referenceLength = (GLint)std::string(brokenShader).length() + 1; // including the null terminator
+			if (sourceLength != referenceLength)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length " << referenceLength	<< "; got " << sourceLength << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
+			}
+		}
+
+		// check the concat source SHADER_SOURCE_LENGTH
+		{
+			const char* shaders[] = {brokenShader, brokenShader};
+			glShaderSource(shader, 2, shaders, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			StateQueryMemoryWriteGuard<GLint> sourceLength;
+			glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &sourceLength);
+
+			sourceLength.verifyValidity(m_testCtx);
+
+			const GLint referenceLength = 2 * (GLint)std::string(brokenShader).length() + 1; // including the null terminator
+			if (sourceLength != referenceLength)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length " << referenceLength << "; got " << sourceLength << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
+			}
+		}
+
+		// check the string length
+		{
+			char buffer[2048] = {'x'};
+			DE_ASSERT(DE_LENGTH_OF_ARRAY(buffer) > 2 * (int)std::string(brokenShader).length());
+
+			GLint written = 0; // not inluding null-terminator
+			glGetShaderSource(shader, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);
+
+			const GLint referenceLength = 2 * (GLint)std::string(brokenShader).length();
+			if (written != referenceLength)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected write length " << referenceLength << "; got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
+			}
+			// check null pointer at
+			else
+			{
+				if (buffer[referenceLength] != '\0')
+				{
+					m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator at " << referenceLength << TestLog::EndMessage;
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "did not get a null terminator");
+				}
+			}
+		}
+
+		// check with small buffer
+		{
+			char buffer[2048] = {'x'};
+
+			GLint written = 0;
+			glGetShaderSource(shader, 1, &written, buffer);
+
+			if (written != 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected write length 0; got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
+			}
+			if (buffer[0] != '\0')
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator; got=" << int(buffer[0]) << ", char=" << buffer[0] << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid terminator");
+			}
+		}
+
+		glDeleteShader(shader);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class DeleteStatusCase : public ApiCase
+{
+public:
+	DeleteStatusCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
+		verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_TRUE);
+
+		GLuint shaderProg = glCreateProgram();
+		glAttachShader(shaderProg, shaderVert);
+		glAttachShader(shaderProg, shaderFrag);
+		glLinkProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+
+		verifyProgramParam	(m_testCtx, *this, shaderProg, GL_LINK_STATUS, GL_TRUE);
+
+		verifyShaderParam	(m_testCtx, *this, shaderVert, GL_DELETE_STATUS, GL_FALSE);
+		verifyShaderParam	(m_testCtx, *this, shaderFrag, GL_DELETE_STATUS, GL_FALSE);
+		verifyProgramParam	(m_testCtx, *this, shaderProg, GL_DELETE_STATUS, GL_FALSE);
+		expectError(GL_NO_ERROR);
+
+		glUseProgram(shaderProg);
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+
+		verifyShaderParam	(m_testCtx, *this, shaderVert, GL_DELETE_STATUS, GL_TRUE);
+		verifyShaderParam	(m_testCtx, *this, shaderFrag, GL_DELETE_STATUS, GL_TRUE);
+		verifyProgramParam	(m_testCtx, *this, shaderProg, GL_DELETE_STATUS, GL_TRUE);
+		expectError(GL_NO_ERROR);
+
+		glUseProgram(0);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class CurrentVertexAttribInitialCase : public ApiCase
+{
+public:
+	CurrentVertexAttribInitialCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		int attribute_count = 16;
+		glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);
+
+		// initial
+
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			StateQueryMemoryWriteGuard<GLfloat[4]> attribValue;
+			glGetVertexAttribfv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);
+			attribValue.verifyValidity(m_testCtx);
+
+			if (attribValue[0] != 0.0f || attribValue[1] != 0.0f || attribValue[2] != 0.0f || attribValue[3] != 1.0f)
+			{
+				m_testCtx.getLog() << TestLog::Message
+					<< "// ERROR: Expected [0, 0, 0, 1];"
+					<< "got [" << attribValue[0] << "," << attribValue[1] << "," << attribValue[2] << "," << attribValue[3] << "]"
+					<< TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid attribute value");
+			}
+		}
+	}
+};
+
+class CurrentVertexAttribFloatCase : public ApiCase
+{
+public:
+	CurrentVertexAttribFloatCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		de::Random rnd(0xabcdef);
+
+		int attribute_count = 16;
+		glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);
+
+		// test write float/read float
+
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = rnd.getFloat(-64000, 64000);
+			const GLfloat w = rnd.getFloat(-64000, 64000);
+
+			glVertexAttrib4f(index, x, y, z, w);
+			verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = rnd.getFloat(-64000, 64000);
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib3f(index, x, y, z);
+			verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = 0.0f;
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib2f(index, x, y);
+			verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = 0.0f;
+			const GLfloat z = 0.0f;
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib1f(index, x);
+			verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
+		}
+	}
+};
+
+class CurrentVertexAttribConversionCase : public ApiCase
+{
+public:
+	CurrentVertexAttribConversionCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		de::Random rnd(0xabcdef);
+
+		int attribute_count = 16;
+		glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);
+
+		// test write float/read float
+
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = rnd.getFloat(-64000, 64000);
+			const GLfloat w = rnd.getFloat(-64000, 64000);
+
+			glVertexAttrib4f(index, x, y, z, w);
+			verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = rnd.getFloat(-64000, 64000);
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib3f(index, x, y, z);
+			verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = 0.0f;
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib2f(index, x, y);
+			verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = 0.0f;
+			const GLfloat z = 0.0f;
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib1f(index, x);
+			verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
+		}
+	}
+};
+
+class ProgramInfoLogCase : public ApiCase
+{
+public:
+	ProgramInfoLogCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &brokenShader, DE_NULL);
+		glShaderSource(shaderFrag, 1, &brokenShader, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+
+		// check INFO_LOG_LENGTH == GetProgramInfoLog len
+		{
+			char buffer[2048] = {'x'};
+
+			GLint written = 0;
+			glGetProgramInfoLog(program, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);
+
+			StateQueryMemoryWriteGuard<GLint> logLength;
+			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
+			logLength.verifyValidity(m_testCtx);
+
+			if (logLength != 0 && written+1 != logLength) // INFO_LOG_LENGTH contains 0-terminator
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected INFO_LOG_LENGTH " << written+1 << "; got " << logLength << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
+			}
+		}
+
+		// check GetProgramInfoLog works with too small buffer
+		{
+			char buffer[2048] = {'x'};
+
+			GLint written = 0;
+			glGetProgramInfoLog(program, 1, &written, buffer);
+
+			if (written != 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected write length 0; got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
+			}
+		}
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ProgramValidateStatusCase : public ApiCase
+{
+public:
+	ProgramValidateStatusCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// test validate ok
+		{
+			GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+			GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+			glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
+			glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);
+
+			glCompileShader(shaderVert);
+			glCompileShader(shaderFrag);
+			expectError(GL_NO_ERROR);
+
+			GLuint program = glCreateProgram();
+			glAttachShader(program, shaderVert);
+			glAttachShader(program, shaderFrag);
+			glLinkProgram(program);
+			expectError(GL_NO_ERROR);
+
+			verifyShaderParam	(m_testCtx, *this, shaderVert,	GL_COMPILE_STATUS,	GL_TRUE);
+			verifyShaderParam	(m_testCtx, *this, shaderFrag,	GL_COMPILE_STATUS,	GL_TRUE);
+			verifyProgramParam	(m_testCtx, *this, program,		GL_LINK_STATUS,		GL_TRUE);
+
+			glValidateProgram(program);
+			verifyProgramParam(m_testCtx, *this, program, GL_VALIDATE_STATUS, GL_TRUE);
+
+			glDeleteShader(shaderVert);
+			glDeleteShader(shaderFrag);
+			glDeleteProgram(program);
+			expectError(GL_NO_ERROR);
+		}
+
+		// test with broken shader
+		{
+			GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+			GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+			glShaderSource(shaderVert, 1, &commonTestVertSource,	DE_NULL);
+			glShaderSource(shaderFrag, 1, &brokenShader,			DE_NULL);
+
+			glCompileShader(shaderVert);
+			glCompileShader(shaderFrag);
+			expectError(GL_NO_ERROR);
+
+			GLuint program = glCreateProgram();
+			glAttachShader(program, shaderVert);
+			glAttachShader(program, shaderFrag);
+			glLinkProgram(program);
+			expectError(GL_NO_ERROR);
+
+			verifyShaderParam	(m_testCtx, *this, shaderVert,	GL_COMPILE_STATUS,	GL_TRUE);
+			verifyShaderParam	(m_testCtx, *this, shaderFrag,	GL_COMPILE_STATUS,	GL_FALSE);
+			verifyProgramParam	(m_testCtx, *this, program,		GL_LINK_STATUS,		GL_FALSE);
+
+			glValidateProgram(program);
+			verifyProgramParam(m_testCtx, *this, program, GL_VALIDATE_STATUS, GL_FALSE);
+
+			glDeleteShader(shaderVert);
+			glDeleteShader(shaderFrag);
+			glDeleteProgram(program);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class ProgramAttachedShadersCase : public ApiCase
+{
+public:
+	ProgramAttachedShadersCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		// check ATTACHED_SHADERS
+
+		GLuint program = glCreateProgram();
+		verifyProgramParam(m_testCtx, *this, program, GL_ATTACHED_SHADERS, 0);
+		expectError(GL_NO_ERROR);
+
+		glAttachShader(program, shaderVert);
+		verifyProgramParam(m_testCtx, *this, program, GL_ATTACHED_SHADERS, 1);
+		expectError(GL_NO_ERROR);
+
+		glAttachShader(program, shaderFrag);
+		verifyProgramParam(m_testCtx, *this, program, GL_ATTACHED_SHADERS, 2);
+		expectError(GL_NO_ERROR);
+
+		// check GetAttachedShaders
+		{
+			GLuint shaders[2] = {0, 0};
+			GLint count = 0;
+			glGetAttachedShaders(program, DE_LENGTH_OF_ARRAY(shaders), &count, shaders);
+
+			if (count != 2)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 2; got " << count << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
+			}
+			// shaders are the attached shaders?
+			if (!((shaders[0] == shaderVert && shaders[1] == shaderFrag) ||
+				  (shaders[0] == shaderFrag && shaders[1] == shaderVert)))
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected {" << shaderVert << ", " << shaderFrag << "}; got {" << shaders[0] << ", " << shaders[1] << "}" << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
+			}
+		}
+
+		// check GetAttachedShaders with too small buffer
+		{
+			GLuint shaders[2] = {0, 0};
+			GLint count = 0;
+
+			glGetAttachedShaders(program, 0, &count, shaders);
+			if (count != 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 0; got " << count << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
+			}
+
+			count = 0;
+			glGetAttachedShaders(program, 1, &count, shaders);
+			if (count != 1)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 1; got " << count << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
+			}
+		}
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ProgramActiveUniformNameCase : public ApiCase
+{
+public:
+	ProgramActiveUniformNameCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		static const char* testVertSource =
+			"uniform highp float uniformNameWithLength23;\n"
+			"uniform highp vec2 uniformVec2;\n"
+			"uniform highp mat4 uniformMat4;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(0.0) + vec4(uniformNameWithLength23) + vec4(uniformVec2.x) + vec4(uniformMat4[2][3]);\n"
+			"}\n\0";
+		static const char* testFragSource =
+
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(0.0);\n"
+			"}\n\0";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		expectError(GL_NO_ERROR);
+
+		verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_UNIFORMS, 3);
+		verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_UNIFORM_MAX_LENGTH, (GLint)std::string("uniformNameWithLength23").length() + 1); // including a null terminator
+		expectError(GL_NO_ERROR);
+
+		const char* uniformNames[] =
+		{
+			"uniformNameWithLength23",
+			"uniformVec2",
+			"uniformMat4"
+		};
+
+		// check names
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(uniformNames); ++ndx)
+		{
+			char  buffer[2048]	= {'x'};
+			char* bufferEnd		= (buffer + 1);
+
+			GLint written = 0; // null terminator not included
+			GLint size = 0;
+			GLenum type = 0;
+			glGetActiveUniform(program, ndx, DE_LENGTH_OF_ARRAY(buffer), &written, &size, &type, buffer);
+
+			if (written < DE_LENGTH_OF_ARRAY(buffer))
+				bufferEnd = &buffer[written];
+
+			// find matching uniform
+			{
+				const std::string uniformName(buffer, bufferEnd);
+				bool found = false;
+
+				for (int uniformNdx = 0; uniformNdx < DE_LENGTH_OF_ARRAY(uniformNames); ++uniformNdx)
+				{
+					if (uniformName == uniformNames[uniformNdx])
+					{
+						found = true;
+						break;
+					}
+				}
+
+				if (!found)
+				{
+					m_testCtx.getLog() << TestLog::Message << "// ERROR: Got unknown uniform name: " << uniformName << TestLog::EndMessage;
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform name");
+				}
+			}
+
+			// and with too small buffer
+			written = 0;
+			glGetActiveUniform(program, ndx, 1, &written, &size, &type, buffer);
+
+			if (written != 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 0 got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform name length");
+			}
+		}
+
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ProgramUniformCase : public ApiCase
+{
+public:
+	ProgramUniformCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		const struct UniformType
+		{
+			const char* declaration;
+			const char* postDeclaration;
+			const char* precision;
+			const char* layout;
+			const char* getter;
+			GLenum		type;
+			GLint		size;
+			GLint		isRowMajor;
+		} uniformTypes[] =
+		{
+			{ "float",					"",			"highp",	"",				"uniformValue",													GL_FLOAT,							1, GL_FALSE },
+			{ "float",					"[2]",		"highp",	"",				"uniformValue[1]",												GL_FLOAT,							2, GL_FALSE },
+			{ "vec2",					"",			"highp",	"",				"uniformValue.x",												GL_FLOAT_VEC2,						1, GL_FALSE },
+			{ "vec3",					"",			"highp",	"",				"uniformValue.x",												GL_FLOAT_VEC3,						1, GL_FALSE },
+			{ "vec4",					"",			"highp",	"",				"uniformValue.x",												GL_FLOAT_VEC4,						1, GL_FALSE },
+			{ "int",					"",			"highp",	"",				"float(uniformValue)",											GL_INT,								1, GL_FALSE },
+			{ "ivec2",					"",			"highp",	"",				"float(uniformValue.x)",										GL_INT_VEC2,						1, GL_FALSE },
+			{ "ivec3",					"",			"highp",	"",				"float(uniformValue.x)",										GL_INT_VEC3,						1, GL_FALSE },
+			{ "ivec4",					"",			"highp",	"",				"float(uniformValue.x)",										GL_INT_VEC4,						1, GL_FALSE },
+			{ "bool",					"",			"",			"",				"float(uniformValue)",											GL_BOOL,							1, GL_FALSE },
+			{ "bvec2",					"",			"",			"",				"float(uniformValue.x)",										GL_BOOL_VEC2,						1, GL_FALSE },
+			{ "bvec3",					"",			"",			"",				"float(uniformValue.x)",										GL_BOOL_VEC3,						1, GL_FALSE },
+			{ "bvec4",					"",			"",			"",				"float(uniformValue.x)",										GL_BOOL_VEC4,						1, GL_FALSE },
+			{ "mat2",					"",			"highp",	"",				"float(uniformValue[0][0])",									GL_FLOAT_MAT2,						1, GL_FALSE },
+			{ "mat3",					"",			"highp",	"",				"float(uniformValue[0][0])",									GL_FLOAT_MAT3,						1, GL_FALSE },
+			{ "mat4",					"",			"highp",	"",				"float(uniformValue[0][0])",									GL_FLOAT_MAT4,						1, GL_FALSE },
+			{ "sampler2D",				"",			"highp",	"",				"float(texture2D(uniformValue, vec2(0.0, 0.0)).r)",				GL_SAMPLER_2D,						1, GL_FALSE },
+			{ "samplerCube",			"",			"highp",	"",				"float(textureCube(uniformValue, vec3(0.0, 0.0, 0.0)).r)",		GL_SAMPLER_CUBE,					1, GL_FALSE },
+		};
+
+		static const char* vertSource =
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(0.0);\n"
+			"}\n\0";
+
+		GLuint shaderVert	= glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag	= glCreateShader(GL_FRAGMENT_SHADER);
+		GLuint program		= glCreateProgram();
+
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+
+		glShaderSource(shaderVert, 1, &vertSource, DE_NULL);
+		glCompileShader(shaderVert);
+		expectError(GL_NO_ERROR);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(uniformTypes); ++ndx)
+		{
+			tcu::ScopedLogSection(m_log, uniformTypes[ndx].declaration, std::string("Verify type of ") + uniformTypes[ndx].declaration + " variable" + uniformTypes[ndx].postDeclaration );
+
+			// gen fragment shader
+
+			std::ostringstream frag;
+			frag << uniformTypes[ndx].layout << "uniform " << uniformTypes[ndx].precision << " " << uniformTypes[ndx].declaration << " uniformValue" << uniformTypes[ndx].postDeclaration << ";\n";
+			frag << "void main (void)\n";
+			frag << "{\n";
+			frag << "	gl_FragColor = vec4(" << uniformTypes[ndx].getter << ");\n";
+			frag << "}\n";
+
+			{
+				std::string fragmentSource = frag.str();
+				const char* fragmentSourceCStr = fragmentSource.c_str();
+				glShaderSource(shaderFrag, 1, &fragmentSourceCStr, DE_NULL);
+			}
+
+			// compile & link
+
+			glCompileShader(shaderFrag);
+			glLinkProgram(program);
+
+			// test
+			if (verifyProgramParam(m_testCtx, *this, program, GL_LINK_STATUS, GL_TRUE))
+			{
+				const GLint index = 0; // first and only active uniform
+
+				char buffer[]	= "not written to"; // not written to
+				GLint written	= 0;
+				GLint size		= 0;
+				GLenum type		= 0;
+				glGetActiveUniform(program, index, 0, &written, &size, &type, buffer);
+
+				checkIntEquals(m_testCtx, type, uniformTypes[ndx].type);
+				checkIntEquals(m_testCtx, size, uniformTypes[ndx].size);
+			}
+		}
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ActiveAttributesCase : public ApiCase
+{
+public:
+	ActiveAttributesCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		static const char* testVertSource =
+			"attribute highp vec2 longInputAttributeName;\n"
+			"attribute highp vec2 shortName;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = longInputAttributeName.yxxy + shortName.xyxy;\n"
+			"}\n\0";
+		static const char* testFragSource =
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(0.0);\n"
+			"}\n\0";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		expectError(GL_NO_ERROR);
+
+		verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_ATTRIBUTES, 2);
+		verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, (GLint)std::string("longInputAttributeName").length() + 1); // does include null-terminator
+
+		// check names
+		for (int attributeNdx = 0; attributeNdx < 2; ++attributeNdx)
+		{
+			char buffer[2048] = {'x'};
+
+			GLint written = 0;
+			GLint size = 0;
+			GLenum type = 0;
+			glGetActiveAttrib(program, attributeNdx, DE_LENGTH_OF_ARRAY(buffer), &written, &size, &type, buffer);
+			expectError(GL_NO_ERROR);
+
+			if (deStringBeginsWith(buffer, "longInputAttributeName"))
+			{
+				checkIntEquals(m_testCtx, written, (GLint)std::string("longInputAttributeName").length()); // does NOT include null-terminator
+			}
+			else if (deStringBeginsWith(buffer, "shortName"))
+			{
+				checkIntEquals(m_testCtx, written, (GLint)std::string("shortName").length()); // does NOT include null-terminator
+			}
+			else
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Got unexpected attribute name." << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected name");
+			}
+		}
+
+		// and with too short buffer
+		{
+			char buffer[2048] = {'x'};
+
+			GLint written = 0;
+			GLint size = 0;
+			GLenum type = 0;
+
+			glGetActiveAttrib(program, 0, 1, &written, &size, &type, buffer);
+			expectError(GL_NO_ERROR);
+			checkIntEquals(m_testCtx, written, 0);
+		}
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+struct PointerData
+{
+	GLint		size;
+	GLenum		type;
+	GLint		stride;
+	GLboolean	normalized;
+	void*		pointer;
+};
+
+class VertexAttributeSizeCase : public ApiCase
+{
+public:
+	VertexAttributeSizeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLfloat vertexData[4] = {0.0f}; // never accessed
+
+		// test VertexAttribPointer
+		const PointerData pointers[] =
+		{
+			// size test
+			{ 4, GL_FLOAT,		0,	GL_FALSE, vertexData },
+			{ 3, GL_FLOAT,		0,	GL_FALSE, vertexData },
+			{ 2, GL_FLOAT,		0,	GL_FALSE, vertexData },
+			{ 1, GL_FLOAT,		0,	GL_FALSE, vertexData },
+			{ 4, GL_SHORT,		0,	GL_FALSE, vertexData },
+			{ 3, GL_SHORT,		0,	GL_FALSE, vertexData },
+			{ 2, GL_SHORT,		0,	GL_FALSE, vertexData },
+			{ 1, GL_SHORT,		0,	GL_FALSE, vertexData },
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+		{
+			glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized, pointers[ndx].stride, pointers[ndx].pointer);
+			expectError(GL_NO_ERROR);
+
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_SIZE, pointers[ndx].size);
+		}
+	}
+};
+
+class VertexAttributeTypeCase : public ApiCase
+{
+public:
+	VertexAttributeTypeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLfloat vertexData[4] = {0.0f}; // never accessed
+
+		const PointerData pointers[] =
+		{
+			{ 1, GL_BYTE,								0,	GL_FALSE, vertexData	},
+			{ 1, GL_UNSIGNED_BYTE,						0,	GL_FALSE, vertexData	},
+			{ 1, GL_SHORT,								0,	GL_FALSE, vertexData	},
+			{ 1, GL_UNSIGNED_SHORT,						0,	GL_FALSE, vertexData	},
+			{ 1, GL_FIXED,								0,	GL_FALSE, vertexData	},
+			{ 1, GL_FLOAT,								0,	GL_FALSE, vertexData	},
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+		{
+			glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized, pointers[ndx].stride, pointers[ndx].pointer);
+			expectError(GL_NO_ERROR);
+
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, pointers[ndx].type);
+		}
+	}
+};
+
+class VertexAttributeStrideCase : public ApiCase
+{
+public:
+	VertexAttributeStrideCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLfloat vertexData[4] = {0.0f}; // never accessed
+
+		struct StridePointerData
+		{
+			GLint		size;
+			GLenum		type;
+			GLint		stride;
+			void*		pointer;
+		};
+
+		// test VertexAttribPointer
+		{
+			const StridePointerData pointers[] =
+			{
+				{ 1, GL_FLOAT,				0,	vertexData },
+				{ 1, GL_FLOAT,				1,	vertexData },
+				{ 1, GL_FLOAT,				4,	vertexData },
+				{ 1, GL_SHORT,				0,	vertexData },
+				{ 1, GL_SHORT,				1,	vertexData },
+				{ 1, GL_SHORT,				4,	vertexData },
+				{ 1, GL_FIXED,				0,	vertexData },
+				{ 1, GL_FIXED,				1,	vertexData },
+				{ 1, GL_FIXED,				4,	vertexData },
+				{ 1, GL_BYTE,				0,	vertexData },
+				{ 1, GL_UNSIGNED_SHORT,		1,	vertexData },
+				{ 1, GL_UNSIGNED_SHORT,		4,	vertexData },
+				{ 4, GL_UNSIGNED_BYTE,		0,	vertexData },
+				{ 4, GL_UNSIGNED_BYTE,		1,	vertexData },
+				{ 4, GL_UNSIGNED_BYTE,		4,	vertexData },
+			};
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+			{
+				glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, GL_FALSE, pointers[ndx].stride, pointers[ndx].pointer);
+				expectError(GL_NO_ERROR);
+
+				verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, pointers[ndx].stride);
+			}
+		}
+	}
+};
+
+class VertexAttributeNormalizedCase : public ApiCase
+{
+public:
+	VertexAttributeNormalizedCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLfloat vertexData[4] = {0.0f}; // never accessed
+
+		// test VertexAttribPointer
+		{
+			const PointerData pointers[] =
+			{
+				{ 1, GL_BYTE,								0,	GL_FALSE,	vertexData	},
+				{ 1, GL_SHORT,								0,	GL_FALSE,	vertexData	},
+				{ 1, GL_UNSIGNED_BYTE,						0,	GL_FALSE,	vertexData	},
+				{ 1, GL_UNSIGNED_SHORT,						0,	GL_FALSE,	vertexData	},
+				{ 1, GL_BYTE,								0,	GL_TRUE,	vertexData	},
+				{ 1, GL_SHORT,								0,	GL_TRUE,	vertexData	},
+				{ 1, GL_UNSIGNED_BYTE,						0,	GL_TRUE,	vertexData	},
+				{ 1, GL_UNSIGNED_SHORT,						0,	GL_TRUE,	vertexData	},
+			};
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+			{
+				glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized, pointers[ndx].stride, pointers[ndx].pointer);
+				expectError(GL_NO_ERROR);
+
+				verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, pointers[ndx].normalized);
+			}
+		}
+	}
+};
+
+class VertexAttributeEnabledCase : public ApiCase
+{
+public:
+	VertexAttributeEnabledCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// VERTEX_ATTRIB_ARRAY_ENABLED
+
+		verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_FALSE);
+		glEnableVertexAttribArray(0);
+		verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_TRUE);
+		glDisableVertexAttribArray(0);
+		verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_FALSE);
+	}
+};
+
+class VertexAttributeBufferBindingCase : public ApiCase
+{
+public:
+	VertexAttributeBufferBindingCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// initial
+		verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, 0);
+
+		GLuint bufferID;
+		glGenBuffers(1, &bufferID);
+		glBindBuffer(GL_ARRAY_BUFFER, bufferID);
+		expectError(GL_NO_ERROR);
+
+		glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		expectError(GL_NO_ERROR);
+
+		verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, bufferID);
+
+		glDeleteBuffers(1, &bufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class VertexAttributePointerCase : public ApiCase
+{
+public:
+	VertexAttributePointerCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		StateQueryMemoryWriteGuard<GLvoid*> initialState;
+		glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &initialState);
+		initialState.verifyValidity(m_testCtx);
+		checkPointerEquals(m_testCtx, initialState, 0);
+
+		GLfloat vertexData[4] = {0.0f}; // never accessed
+		const PointerData pointers[] =
+		{
+			{ 1, GL_BYTE,				0,	GL_FALSE, &vertexData[2] },
+			{ 1, GL_SHORT,				0,	GL_FALSE, &vertexData[1] },
+			{ 1, GL_FIXED,				0,	GL_FALSE, &vertexData[2] },
+			{ 1, GL_FIXED,				0,	GL_FALSE, &vertexData[1] },
+			{ 1, GL_FLOAT,				0,	GL_FALSE, &vertexData[0] },
+			{ 1, GL_FLOAT,				0,	GL_FALSE, &vertexData[3] },
+			{ 1, GL_FLOAT,				0,	GL_FALSE, &vertexData[2] },
+			{ 1, GL_UNSIGNED_SHORT,		0,	GL_FALSE, &vertexData[0] },
+			{ 4, GL_UNSIGNED_SHORT,		0,	GL_FALSE, &vertexData[1] },
+			{ 4, GL_UNSIGNED_SHORT,		0,	GL_FALSE, &vertexData[2] },
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+		{
+			glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized, pointers[ndx].stride, pointers[ndx].pointer);
+			expectError(GL_NO_ERROR);
+
+			StateQueryMemoryWriteGuard<GLvoid*> state;
+			glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &state);
+			state.verifyValidity(m_testCtx);
+			checkPointerEquals(m_testCtx, state, pointers[ndx].pointer);
+		}
+	}
+};
+
+class UniformValueFloatCase : public ApiCase
+{
+public:
+	UniformValueFloatCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"uniform highp float floatUniform;\n"
+			"uniform highp vec2 float2Uniform;\n"
+			"uniform highp vec3 float3Uniform;\n"
+			"uniform highp vec4 float4Uniform;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(floatUniform + float2Uniform.x + float3Uniform.x + float4Uniform.x);\n"
+			"}\n";
+		static const char* testFragSource =
+
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(0.0);\n"
+			"}\n";
+
+		glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(testVertSource, testFragSource));
+		if (!program.isOk())
+		{
+			m_log << program;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to compile shader");
+			return;
+		}
+
+		glUseProgram(program.getProgram());
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		location = glGetUniformLocation(program.getProgram(), "floatUniform");
+		glUniform1f(location, 1.0f);
+		verifyUniformValue1f(m_testCtx, *this, program.getProgram(), location, 1.0f);
+
+		location = glGetUniformLocation(program.getProgram(), "float2Uniform");
+		glUniform2f(location, 1.0f, 2.0f);
+		verifyUniformValue2f(m_testCtx, *this, program.getProgram(), location, 1.0f, 2.0f);
+
+		location = glGetUniformLocation(program.getProgram(), "float3Uniform");
+		glUniform3f(location, 1.0f, 2.0f, 3.0f);
+		verifyUniformValue3f(m_testCtx, *this, program.getProgram(), location, 1.0f, 2.0f, 3.0f);
+
+		location = glGetUniformLocation(program.getProgram(), "float4Uniform");
+		glUniform4f(location, 1.0f, 2.0f, 3.0f, 4.0f);
+		verifyUniformValue4f(m_testCtx, *this, program.getProgram(), location, 1.0f, 2.0f, 3.0f, 4.0f);
+
+		glUseProgram(0);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UniformValueIntCase : public ApiCase
+{
+public:
+	UniformValueIntCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"uniform highp int intUniform;\n"
+			"uniform highp ivec2 int2Uniform;\n"
+			"uniform highp ivec3 int3Uniform;\n"
+			"uniform highp ivec4 int4Uniform;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(float(intUniform + int2Uniform.x + int3Uniform.x + int4Uniform.x));\n"
+			"}\n";
+		static const char* testFragSource =
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(0.0);\n"
+			"}\n";
+
+		glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(testVertSource, testFragSource));
+		if (!program.isOk())
+		{
+			m_log << program;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to compile shader");
+			return;
+		}
+
+		glUseProgram(program.getProgram());
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		location = glGetUniformLocation(program.getProgram(), "intUniform");
+		glUniform1i(location, 1);
+		verifyUniformValue1i(m_testCtx, *this, program.getProgram(), location, 1);
+
+		location = glGetUniformLocation(program.getProgram(), "int2Uniform");
+		glUniform2i(location, 1, 2);
+		verifyUniformValue2i(m_testCtx, *this, program.getProgram(), location, 1, 2);
+
+		location = glGetUniformLocation(program.getProgram(), "int3Uniform");
+		glUniform3i(location, 1, 2, 3);
+		verifyUniformValue3i(m_testCtx, *this, program.getProgram(), location, 1, 2, 3);
+
+		location = glGetUniformLocation(program.getProgram(), "int4Uniform");
+		glUniform4i(location, 1, 2, 3, 4);
+		verifyUniformValue4i(m_testCtx, *this, program.getProgram(), location, 1, 2, 3, 4);
+
+		glUseProgram(0);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UniformValueBooleanCase : public ApiCase
+{
+public:
+	UniformValueBooleanCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"uniform bool boolUniform;\n"
+			"uniform bvec2 bool2Uniform;\n"
+			"uniform bvec3 bool3Uniform;\n"
+			"uniform bvec4 bool4Uniform;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(float(boolUniform) + float(bool2Uniform.x) + float(bool3Uniform.x) + float(bool4Uniform.x));\n"
+			"}\n";
+		static const char* testFragSource =
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(0.0);\n"
+			"}\n";
+
+		glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(testVertSource, testFragSource));
+		if (!program.isOk())
+		{
+			m_log << program;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to compile shader");
+			return;
+		}
+
+		glUseProgram(program.getProgram());
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		// int conversion
+
+		location = glGetUniformLocation(program.getProgram(), "boolUniform");
+		glUniform1i(location, 1);
+		verifyUniformValue1i(m_testCtx, *this, program.getProgram(), location, 1);
+
+		location = glGetUniformLocation(program.getProgram(), "bool2Uniform");
+		glUniform2i(location, 1, 2);
+		verifyUniformValue2i(m_testCtx, *this, program.getProgram(), location, 1, 1);
+
+		location = glGetUniformLocation(program.getProgram(), "bool3Uniform");
+		glUniform3i(location, 0, 1, 2);
+		verifyUniformValue3i(m_testCtx, *this, program.getProgram(), location, 0, 1, 1);
+
+		location = glGetUniformLocation(program.getProgram(), "bool4Uniform");
+		glUniform4i(location, 1, 0, 1, -1);
+		verifyUniformValue4i(m_testCtx, *this, program.getProgram(), location, 1, 0, 1, 1);
+
+		// float conversion
+
+		location = glGetUniformLocation(program.getProgram(), "boolUniform");
+		glUniform1f(location, 1.0f);
+		verifyUniformValue1i(m_testCtx, *this, program.getProgram(), location, 1);
+
+		location = glGetUniformLocation(program.getProgram(), "bool2Uniform");
+		glUniform2f(location, 1.0f, 0.1f);
+		verifyUniformValue2i(m_testCtx, *this, program.getProgram(), location, 1, 1);
+
+		location = glGetUniformLocation(program.getProgram(), "bool3Uniform");
+		glUniform3f(location, 0.0f, 0.1f, -0.1f);
+		verifyUniformValue3i(m_testCtx, *this, program.getProgram(), location, 0, 1, 1);
+
+		location = glGetUniformLocation(program.getProgram(), "bool4Uniform");
+		glUniform4f(location, 1.0f, 0.0f, 0.1f, -0.9f);
+		verifyUniformValue4i(m_testCtx, *this, program.getProgram(), location, 1, 0, 1, 1);
+
+		glUseProgram(0);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UniformValueSamplerCase : public ApiCase
+{
+public:
+	UniformValueSamplerCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(0.0);\n"
+			"}\n";
+		static const char* testFragSource =
+			"uniform highp sampler2D uniformSampler;\n"
+
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(texture2D(uniformSampler, vec2(0.0, 0.0)).x);\n"
+			"}\n";
+
+		glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(testVertSource, testFragSource));
+		if (!program.isOk())
+		{
+			m_log << program;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to compile shader");
+			return;
+		}
+
+		glUseProgram(program.getProgram());
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		location = glGetUniformLocation(program.getProgram(), "uniformSampler");
+		glUniform1i(location, 1);
+		verifyUniformValue1i(m_testCtx, *this, program.getProgram(), location, 1);
+
+		glUseProgram(0);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UniformValueArrayCase : public ApiCase
+{
+public:
+	UniformValueArrayCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"uniform highp float arrayUniform[5];"
+			"uniform highp vec2 array2Uniform[5];"
+			"uniform highp vec3 array3Uniform[5];"
+			"uniform highp vec4 array4Uniform[5];"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = \n"
+			"		+ vec4(arrayUniform[0]		+ arrayUniform[1]		+ arrayUniform[2]		+ arrayUniform[3]		+ arrayUniform[4])\n"
+			"		+ vec4(array2Uniform[0].x	+ array2Uniform[1].x	+ array2Uniform[2].x	+ array2Uniform[3].x	+ array2Uniform[4].x)\n"
+			"		+ vec4(array3Uniform[0].x	+ array3Uniform[1].x	+ array3Uniform[2].x	+ array3Uniform[3].x	+ array3Uniform[4].x)\n"
+			"		+ vec4(array4Uniform[0].x	+ array4Uniform[1].x	+ array4Uniform[2].x	+ array4Uniform[3].x	+ array4Uniform[4].x);\n"
+			"}\n";
+		static const char* testFragSource =
+
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(0.0);\n"
+			"}\n";
+
+		glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(testVertSource, testFragSource));
+		if (!program.isOk())
+		{
+			m_log << program;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to compile shader");
+			return;
+		}
+
+		glUseProgram(program.getProgram());
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		float uniformValue[5 * 4] =
+		{
+			-1.0f,	0.1f,	4.0f,	800.0f,
+			13.0f,	55.0f,	12.0f,	91.0f,
+			-55.1f,	1.1f,	98.0f,	19.0f,
+			41.0f,	65.0f,	4.0f,	12.2f,
+			95.0f,	77.0f,	32.0f,	48.0f
+		};
+
+		location = glGetUniformLocation(program.getProgram(), "arrayUniform");
+		glUniform1fv(location, 5, uniformValue);
+		expectError(GL_NO_ERROR);
+
+		verifyUniformValue1f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "arrayUniform[0]"), uniformValue[0]);
+		verifyUniformValue1f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "arrayUniform[1]"), uniformValue[1]);
+		verifyUniformValue1f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "arrayUniform[2]"), uniformValue[2]);
+		verifyUniformValue1f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "arrayUniform[3]"), uniformValue[3]);
+		verifyUniformValue1f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "arrayUniform[4]"), uniformValue[4]);
+		expectError(GL_NO_ERROR);
+
+		location = glGetUniformLocation(program.getProgram(),"array2Uniform");
+		glUniform2fv(location, 5, uniformValue);
+		expectError(GL_NO_ERROR);
+
+		verifyUniformValue2f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array2Uniform[0]"), uniformValue[2 * 0], uniformValue[(2 * 0) + 1]);
+		verifyUniformValue2f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array2Uniform[1]"), uniformValue[2 * 1], uniformValue[(2 * 1) + 1]);
+		verifyUniformValue2f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array2Uniform[2]"), uniformValue[2 * 2], uniformValue[(2 * 2) + 1]);
+		verifyUniformValue2f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array2Uniform[3]"), uniformValue[2 * 3], uniformValue[(2 * 3) + 1]);
+		verifyUniformValue2f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array2Uniform[4]"), uniformValue[2 * 4], uniformValue[(2 * 4) + 1]);
+		expectError(GL_NO_ERROR);
+
+		location = glGetUniformLocation(program.getProgram(),"array3Uniform");
+		glUniform3fv(location, 5, uniformValue);
+		expectError(GL_NO_ERROR);
+
+		verifyUniformValue3f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array3Uniform[0]"), uniformValue[3 * 0], uniformValue[(3 * 0) + 1], uniformValue[(3 * 0) + 2]);
+		verifyUniformValue3f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array3Uniform[1]"), uniformValue[3 * 1], uniformValue[(3 * 1) + 1], uniformValue[(3 * 1) + 2]);
+		verifyUniformValue3f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array3Uniform[2]"), uniformValue[3 * 2], uniformValue[(3 * 2) + 1], uniformValue[(3 * 2) + 2]);
+		verifyUniformValue3f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array3Uniform[3]"), uniformValue[3 * 3], uniformValue[(3 * 3) + 1], uniformValue[(3 * 3) + 2]);
+		verifyUniformValue3f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array3Uniform[4]"), uniformValue[3 * 4], uniformValue[(3 * 4) + 1], uniformValue[(3 * 4) + 2]);
+		expectError(GL_NO_ERROR);
+
+		location = glGetUniformLocation(program.getProgram(),"array4Uniform");
+		glUniform4fv(location, 5, uniformValue);
+		expectError(GL_NO_ERROR);
+
+		verifyUniformValue4f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array4Uniform[0]"), uniformValue[4 * 0], uniformValue[(4 * 0) + 1], uniformValue[(4 * 0) + 2], uniformValue[(4 * 0) + 3]);
+		verifyUniformValue4f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array4Uniform[1]"), uniformValue[4 * 1], uniformValue[(4 * 1) + 1], uniformValue[(4 * 1) + 2], uniformValue[(4 * 1) + 3]);
+		verifyUniformValue4f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array4Uniform[2]"), uniformValue[4 * 2], uniformValue[(4 * 2) + 1], uniformValue[(4 * 2) + 2], uniformValue[(4 * 2) + 3]);
+		verifyUniformValue4f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array4Uniform[3]"), uniformValue[4 * 3], uniformValue[(4 * 3) + 1], uniformValue[(4 * 3) + 2], uniformValue[(4 * 3) + 3]);
+		verifyUniformValue4f(m_testCtx, *this, program.getProgram(), glGetUniformLocation(program.getProgram(), "array4Uniform[4]"), uniformValue[4 * 4], uniformValue[(4 * 4) + 1], uniformValue[(4 * 4) + 2], uniformValue[(4 * 4) + 3]);
+		expectError(GL_NO_ERROR);
+
+		glUseProgram(0);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UniformValueMatrixCase : public ApiCase
+{
+public:
+	UniformValueMatrixCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"uniform highp mat2 mat2Uniform;"
+			"uniform highp mat3 mat3Uniform;"
+			"uniform highp mat4 mat4Uniform;"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(mat2Uniform[0][0] + mat3Uniform[0][0] + mat4Uniform[0][0]);\n"
+			"}\n";
+		static const char* testFragSource =
+
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragColor = vec4(0.0);\n"
+			"}\n";
+
+		glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(testVertSource, testFragSource));
+		if (!program.isOk())
+		{
+			m_log << program;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to compile shader");
+			return;
+		}
+
+		glUseProgram(program.getProgram());
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		float matrixValues[4 * 4] =
+		{
+			-1.0f,	0.1f,	4.0f,	800.0f,
+			13.0f,	55.0f,	12.0f,	91.0f,
+			-55.1f,	1.1f,	98.0f,	19.0f,
+			41.0f,	65.0f,	4.0f,	12.2f,
+		};
+
+		// the values of the matrix are returned in column major order but they can be given in either order
+
+		location = glGetUniformLocation(program.getProgram(), "mat2Uniform");
+		glUniformMatrix2fv(location, 1, GL_FALSE, matrixValues);
+		verifyUniformMatrixValues<2>(m_testCtx, *this, program.getProgram(), location, matrixValues, false);
+
+		location = glGetUniformLocation(program.getProgram(), "mat3Uniform");
+		glUniformMatrix3fv(location, 1, GL_FALSE, matrixValues);
+		verifyUniformMatrixValues<3>(m_testCtx, *this, program.getProgram(), location, matrixValues, false);
+
+		location = glGetUniformLocation(program.getProgram(), "mat4Uniform");
+		glUniformMatrix4fv(location, 1, GL_FALSE, matrixValues);
+		verifyUniformMatrixValues<4>(m_testCtx, *this, program.getProgram(), location, matrixValues, false);
+
+		glUseProgram(0);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class PrecisionFormatCase : public ApiCase
+{
+public:
+	struct RequiredFormat
+	{
+		int negativeRange;
+		int positiveRange;
+		int precision;
+	};
+
+	PrecisionFormatCase (Context& context, const char* name, const char* description, glw::GLenum shaderType, glw::GLenum precisionType)
+		: ApiCase			(context, name, description)
+		, m_shaderType		(shaderType)
+		, m_precisionType	(precisionType)
+	{
+	}
+
+private:
+	void test (void)
+	{
+		const RequiredFormat											expected = getRequiredFormat();
+		bool															error = false;
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLboolean>	shaderCompiler;
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint[2]>	range;
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint>		precision;
+
+		// requires SHADER_COMPILER = true
+		glGetBooleanv(GL_SHADER_COMPILER, &shaderCompiler);
+		expectError(GL_NO_ERROR);
+
+		if (!shaderCompiler.verifyValidity(m_testCtx))
+			return;
+		if (shaderCompiler != GL_TRUE)
+			throw tcu::NotSupportedError("SHADER_COMPILER = TRUE required");
+
+		// query values
+		glGetShaderPrecisionFormat(m_shaderType, m_precisionType, range, &precision);
+		expectError(GL_NO_ERROR);
+
+		if (!range.verifyValidity(m_testCtx))
+			return;
+		if (!precision.verifyValidity(m_testCtx))
+			return;
+
+		m_log
+			<< tcu::TestLog::Message
+			<< "range[0] = " << range[0] << "\n"
+			<< "range[1] = " << range[1] << "\n"
+			<< "precision = " << precision
+			<< tcu::TestLog::EndMessage;
+
+		// special case for highp and fragment shader
+
+		if (m_shaderType == GL_FRAGMENT_SHADER && (m_precisionType == GL_HIGH_FLOAT || m_precisionType == GL_HIGH_INT))
+		{
+			// not supported is a valid return value
+			if (range[0] == 0 && range[1] == 0 && precision == 0)
+				return;
+		}
+
+		// verify the returned values
+
+		if (range[0] < expected.negativeRange)
+		{
+			m_log << tcu::TestLog::Message << "// ERROR: Invalid range[0], expected greater or equal to " << expected.negativeRange << tcu::TestLog::EndMessage;
+			error = true;
+		}
+
+		if (range[1] < expected.positiveRange)
+		{
+			m_log << tcu::TestLog::Message << "// ERROR: Invalid range[1], expected greater or equal to " << expected.positiveRange << tcu::TestLog::EndMessage;
+			error = true;
+		}
+
+		if (precision < expected.precision)
+		{
+			m_log << tcu::TestLog::Message << "// ERROR: Invalid precision, expected greater or equal to " << expected.precision << tcu::TestLog::EndMessage;
+			error = true;
+		}
+
+		if (error)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid precision/range");
+	}
+
+	RequiredFormat getRequiredFormat (void) const
+	{
+		// Precisions for different types.
+		// For example highp float: range: (-2^62, 2^62) => min = -2^62 + e, max = 2^62 - e
+		const RequiredFormat requirements[] =
+		{
+			{  0,  0,  8 }, //!< lowp float
+			{ 13, 13, 10 }, //!< mediump float
+			{ 61, 61, 16 }, //!< highp float
+			{ 7,   7,  0 }, //!< lowp int
+			{ 9,   9,  0 }, //!< mediump int
+			{ 15, 15,  0 }, //!< highp int
+		};
+		const int ndx = (int)m_precisionType - (int)GL_LOW_FLOAT;
+
+		DE_ASSERT(ndx >= 0);
+		DE_ASSERT(ndx < DE_LENGTH_OF_ARRAY(requirements));
+		return requirements[ndx];
+	}
+
+	const glw::GLenum m_shaderType;
+	const glw::GLenum m_precisionType;
+};
+
+} // anonymous
+
+
+ShaderStateQueryTests::ShaderStateQueryTests (Context& context)
+	: TestCaseGroup(context, "shader", "Shader State Query tests")
+{
+}
+
+void ShaderStateQueryTests::init (void)
+{
+	// shader
+	addChild(new ShaderTypeCase						(m_context, "shader_type",							"SHADER_TYPE"));
+	addChild(new ShaderCompileStatusCase			(m_context, "shader_compile_status",				"COMPILE_STATUS"));
+	addChild(new ShaderInfoLogCase					(m_context, "shader_info_log_length",				"INFO_LOG_LENGTH"));
+	addChild(new ShaderSourceCase					(m_context, "shader_source_length",					"SHADER_SOURCE_LENGTH"));
+
+	// shader and program
+	addChild(new DeleteStatusCase					(m_context, "delete_status",						"DELETE_STATUS"));
+
+	// vertex-attrib
+	addChild(new CurrentVertexAttribInitialCase		(m_context, "current_vertex_attrib_initial",		"CURRENT_VERTEX_ATTRIB"));
+	addChild(new CurrentVertexAttribFloatCase		(m_context, "current_vertex_attrib_float",			"CURRENT_VERTEX_ATTRIB"));
+	addChild(new CurrentVertexAttribConversionCase	(m_context, "current_vertex_attrib_float_to_int",	"CURRENT_VERTEX_ATTRIB"));
+
+	// program
+	addChild(new ProgramInfoLogCase					(m_context, "program_info_log_length",				"INFO_LOG_LENGTH"));
+	addChild(new ProgramValidateStatusCase			(m_context, "program_validate_status",				"VALIDATE_STATUS"));
+	addChild(new ProgramAttachedShadersCase			(m_context, "program_attached_shaders",				"ATTACHED_SHADERS"));
+
+	addChild(new ProgramActiveUniformNameCase		(m_context, "program_active_uniform_name",			"ACTIVE_UNIFORMS and ACTIVE_UNIFORM_MAX_LENGTH"));
+	addChild(new ProgramUniformCase					(m_context, "program_active_uniform_types",			"UNIFORM_TYPE and UNIFORM_SIZE"));
+
+	// attribute related
+	addChild(new ActiveAttributesCase				(m_context, "active_attributes",					"ACTIVE_ATTRIBUTES and ACTIVE_ATTRIBUTE_MAX_LENGTH"));
+	addChild(new VertexAttributeSizeCase			(m_context, "vertex_attrib_size",					"VERTEX_ATTRIB_ARRAY_SIZE"));
+	addChild(new VertexAttributeTypeCase			(m_context, "vertex_attrib_type",					"VERTEX_ATTRIB_ARRAY_TYPE"));
+	addChild(new VertexAttributeStrideCase			(m_context, "vertex_attrib_stride",					"VERTEX_ATTRIB_ARRAY_STRIDE"));
+	addChild(new VertexAttributeNormalizedCase		(m_context, "vertex_attrib_normalized",				"VERTEX_ATTRIB_ARRAY_NORMALIZED"));
+	addChild(new VertexAttributeEnabledCase			(m_context, "vertex_attrib_array_enabled",			"VERTEX_ATTRIB_ARRAY_ENABLED"));
+	addChild(new VertexAttributeBufferBindingCase	(m_context, "vertex_attrib_array_buffer_binding",	"VERTEX_ATTRIB_ARRAY_BUFFER_BINDING"));
+	addChild(new VertexAttributePointerCase			(m_context, "vertex_attrib_pointerv",				"GetVertexAttribPointerv"));
+
+	// uniform values
+	addChild(new UniformValueFloatCase				(m_context, "uniform_value_float",					"GetUniform*"));
+	addChild(new UniformValueIntCase				(m_context, "uniform_value_int",					"GetUniform*"));
+	addChild(new UniformValueBooleanCase			(m_context, "uniform_value_boolean",				"GetUniform*"));
+	addChild(new UniformValueSamplerCase			(m_context, "uniform_value_sampler",				"GetUniform*"));
+	addChild(new UniformValueArrayCase				(m_context, "uniform_value_array",					"GetUniform*"));
+	addChild(new UniformValueMatrixCase				(m_context, "uniform_value_matrix",					"GetUniform*"));
+
+	// precision format query
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_lowp_float",			"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_LOW_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_mediump_float",		"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_MEDIUM_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_highp_float",			"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_HIGH_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_lowp_int",			"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_LOW_INT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_mediump_int",			"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_MEDIUM_INT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_highp_int",			"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_HIGH_INT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_lowp_float",		"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_LOW_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_mediump_float",		"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_MEDIUM_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_highp_float",		"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_HIGH_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_lowp_int",			"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_LOW_INT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_mediump_int",		"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_MEDIUM_INT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_highp_int",			"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_HIGH_INT));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderStateQueryTests.hpp b/modules/gles2/functional/es2fShaderStateQueryTests.hpp
new file mode 100644
index 0000000..4bfb040
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderStateQueryTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERSTATEQUERYTESTS_HPP
+#define _ES2FSHADERSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderStateQueryTests : public TestCaseGroup
+{
+public:
+																		ShaderStateQueryTests	(Context& context);
+
+	void																init					(void);
+
+private:
+																		ShaderStateQueryTests	(const ShaderStateQueryTests& other);
+	ShaderStateQueryTests&												operator=				(const ShaderStateQueryTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERSTATEQUERYTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderStructTests.cpp b/modules/gles2/functional/es2fShaderStructTests.cpp
new file mode 100644
index 0000000..fb6f2ed
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderStructTests.cpp
@@ -0,0 +1,1962 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader struct tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderStructTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deMath.h"
+
+using tcu::StringTemplate;
+
+using std::string;
+using std::vector;
+using std::ostringstream;
+
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+enum
+{
+	TEXTURE_BRICK = 0 //!< Unit index for brick texture
+};
+
+enum CaseFlags
+{
+	FLAG_USES_TEXTURES				= (1<<0),
+	FLAG_REQUIRES_DYNAMIC_LOOPS		= (1<<1),
+	FLAG_REQUIRES_DYNAMIC_INDEXING	= (1<<2),
+};
+
+typedef void (*SetupUniformsFunc) (const glw::Functions& gl, deUint32 programID, const tcu::Vec4& constCoords);
+
+class ShaderStructCase : public ShaderRenderCase
+{
+public:
+							ShaderStructCase		(Context& context, const char* name, const char* description, bool isVertexCase, deUint32 flags, ShaderEvalFunc evalFunc, SetupUniformsFunc setupUniformsFunc, const char* vertShaderSource, const char* fragShaderSource);
+							~ShaderStructCase		(void);
+
+	void					init					(void);
+	void					deinit					(void);
+
+	virtual void			setupUniforms			(int programID, const tcu::Vec4& constCoords);
+
+private:
+							ShaderStructCase		(const ShaderStructCase&);
+	ShaderStructCase&		operator=				(const ShaderStructCase&);
+
+	const SetupUniformsFunc	m_setupUniforms;
+	const deUint32			m_flags;
+
+	glu::Texture2D*			m_brickTexture;
+};
+
+ShaderStructCase::ShaderStructCase (Context& context, const char* name, const char* description, bool isVertexCase, deUint32 flags, ShaderEvalFunc evalFunc, SetupUniformsFunc setupUniformsFunc, const char* vertShaderSource, const char* fragShaderSource)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
+	, m_setupUniforms	(setupUniformsFunc)
+	, m_flags			(flags)
+	, m_brickTexture	(DE_NULL)
+{
+	m_vertShaderSource	= vertShaderSource;
+	m_fragShaderSource	= fragShaderSource;
+}
+
+ShaderStructCase::~ShaderStructCase (void)
+{
+	delete m_brickTexture;
+}
+
+void ShaderStructCase::init (void)
+{
+	try
+	{
+		gls::ShaderRenderCase::init();
+	}
+	catch (const CompileFailed&)
+	{
+		if (m_flags & FLAG_REQUIRES_DYNAMIC_LOOPS)
+		{
+			const bool isSupported = m_isVertexCase ? m_ctxInfo.isVertexDynamicLoopSupported() : m_ctxInfo.isFragmentDynamicLoopSupported();
+			if (!isSupported)
+				throw tcu::NotSupportedError("Dynamic loops not supported");
+		}
+
+		if ((m_flags && FLAG_USES_TEXTURES) && m_isVertexCase)
+		{
+			int numTextures = 0;
+			m_renderCtx.getFunctions().getIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &numTextures);
+			if (numTextures == 0)
+				throw tcu::NotSupportedError("Vertex shader texture access not supported");
+		}
+
+		if (m_flags & FLAG_REQUIRES_DYNAMIC_INDEXING)
+			throw tcu::NotSupportedError("Dynamic indexing not supported");
+
+		throw;
+	}
+
+	if (m_flags & FLAG_USES_TEXTURES)
+	{
+		m_brickTexture = glu::Texture2D::create(m_renderCtx, m_ctxInfo, m_testCtx.getArchive(), "data/brick.png");
+		m_textures.push_back(TextureBinding(m_brickTexture, tcu::Sampler(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
+																		 tcu::Sampler::LINEAR, tcu::Sampler::LINEAR)));
+		DE_ASSERT(m_textures.size() == 1);
+	}
+}
+
+void ShaderStructCase::deinit (void)
+{
+	gls::ShaderRenderCase::deinit();
+	delete m_brickTexture;
+	m_brickTexture = DE_NULL;
+}
+
+void ShaderStructCase::setupUniforms (int programID, const tcu::Vec4& constCoords)
+{
+	ShaderRenderCase::setupUniforms(programID, constCoords);
+	if (m_setupUniforms)
+		m_setupUniforms(m_renderCtx.getFunctions(), programID, constCoords);
+}
+
+static ShaderStructCase* createStructCase (Context& context, const char* name, const char* description, bool isVertexCase, deUint32 flags, ShaderEvalFunc evalFunc, SetupUniformsFunc setupUniforms, const LineStream& shaderSrc)
+{
+	static const char* defaultVertSrc =
+		"attribute highp vec4 a_position;\n"
+		"attribute highp vec4 a_coords;\n"
+		"varying mediump vec4 v_coords;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"	v_coords = a_coords;\n"
+		"	gl_Position = a_position;\n"
+		"}\n";
+	static const char* defaultFragSrc =
+		"varying mediump vec4 v_color;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_FragColor = v_color;\n"
+		"}\n";
+
+	// Fill in specialization parameters.
+	std::map<std::string, std::string> spParams;
+	if (isVertexCase)
+	{
+		spParams["DECLARATIONS"] =
+			"attribute highp vec4 a_position;\n"
+			"attribute highp vec4 a_coords;\n"
+			"varying mediump vec4 v_color;";
+		spParams["COORDS"]		= "a_coords";
+		spParams["DST"]			= "v_color";
+		spParams["ASSIGN_POS"]	= "gl_Position = a_position;";
+	}
+	else
+	{
+		spParams["DECLARATIONS"]	= "varying mediump vec4 v_coords;";
+		spParams["COORDS"]			= "v_coords";
+		spParams["DST"]				= "gl_FragColor";
+		spParams["ASSIGN_POS"]		= "";
+	}
+
+	if (isVertexCase)
+		return new ShaderStructCase(context, name, description, isVertexCase, flags, evalFunc, setupUniforms, StringTemplate(shaderSrc.str()).specialize(spParams).c_str(), defaultFragSrc);
+	else
+		return new ShaderStructCase(context, name, description, isVertexCase, flags, evalFunc, setupUniforms, defaultVertSrc, StringTemplate(shaderSrc.str()).specialize(spParams).c_str());
+}
+
+class LocalStructTests : public TestCaseGroup
+{
+public:
+	LocalStructTests (Context& context)
+		: TestCaseGroup(context, "local", "Local structs")
+	{
+	}
+
+	~LocalStructTests (void)
+	{
+	}
+
+	virtual void init (void);
+};
+
+void LocalStructTests::init (void)
+{
+	#define LOCAL_STRUCT_CASE(NAME, DESCRIPTION, FLAGS, SHADER_SRC, EVAL_FUNC_BODY)																\
+		do {																																	\
+			struct Eval_##NAME { static void eval (ShaderEvalContext& c) EVAL_FUNC_BODY };														\
+			addChild(createStructCase(m_context, #NAME "_vertex", DESCRIPTION, true, FLAGS, &Eval_##NAME::eval, DE_NULL, SHADER_SRC));			\
+			addChild(createStructCase(m_context, #NAME "_fragment", DESCRIPTION, false, FLAGS,&Eval_##NAME::eval, DE_NULL, SHADER_SRC));		\
+		} while (deGetFalse())
+
+	LOCAL_STRUCT_CASE(basic, "Basic struct usage", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, vec3(0.0), ui_one);"
+		<< "	s.b = ${COORDS}.yzw;"
+		<< "	${DST} = vec4(s.a, s.b.x, s.b.y, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(nested, "Nested struct", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(0, vec2(0.0)), ui_one);"
+		<< "	s.b = T(ui_zero, ${COORDS}.yz);"
+		<< "	${DST} = vec4(s.a, s.b.b, s.b.a + s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(array_member, "Struct with array member", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump float	b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s;"
+		<< "	s.a = ${COORDS}.w;"
+		<< "	s.c = ui_one;"
+		<< "	s.b[0] = ${COORDS}.z;"
+		<< "	s.b[1] = ${COORDS}.y;"
+		<< "	s.b[2] = ${COORDS}.x;"
+		<< "	${DST} = vec4(s.a, s.b[0], s.b[1], s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(3,2,1);
+		});
+
+	LOCAL_STRUCT_CASE(array_member_dynamic_index, "Struct with array member, dynamic indexing", FLAG_REQUIRES_DYNAMIC_INDEXING,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump float	b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s;"
+		<< "	s.a = ${COORDS}.w;"
+		<< "	s.c = ui_one;"
+		<< "	s.b[0] = ${COORDS}.z;"
+		<< "	s.b[1] = ${COORDS}.y;"
+		<< "	s.b[2] = ${COORDS}.x;"
+		<< "	${DST} = vec4(s.b[ui_one], s.b[ui_zero], s.b[ui_two], s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(1,2,0);
+		});
+
+	LOCAL_STRUCT_CASE(struct_array, "Struct array", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[3];"
+		<< "	s[0] = S(${COORDS}.x, ui_zero);"
+		<< "	s[1].a = ${COORDS}.y;"
+		<< "	s[1].b = ui_one;"
+		<< "	s[2] = S(${COORDS}.z, ui_two);"
+		<< "	${DST} = vec4(s[2].a, s[1].a, s[0].a, s[2].b - s[1].b + s[0].b);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,1,0);
+		});
+
+	LOCAL_STRUCT_CASE(struct_array_dynamic_index, "Struct array with dynamic indexing", FLAG_REQUIRES_DYNAMIC_INDEXING,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[3];"
+		<< "	s[0] = S(${COORDS}.x, ui_zero);"
+		<< "	s[1].a = ${COORDS}.y;"
+		<< "	s[1].b = ui_one;"
+		<< "	s[2] = S(${COORDS}.z, ui_two);"
+		<< "	${DST} = vec4(s[ui_two].a, s[ui_one].a, s[ui_zero].a, s[ui_two].b - s[ui_one].b + s[ui_zero].b);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,1,0);
+		});
+
+	LOCAL_STRUCT_CASE(nested_struct_array, "Nested struct array", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[2];"
+		<< ""
+		<< "	// S[0]"
+		<< "	s[0].a         = ${COORDS}.x;"
+		<< "	s[0].b[0].a    = uf_half;"
+		<< "	s[0].b[0].b[0] = ${COORDS}.xy;"
+		<< "	s[0].b[0].b[1] = ${COORDS}.zw;"
+		<< "	s[0].b[1].a    = uf_third;"
+		<< "	s[0].b[1].b[0] = ${COORDS}.zw;"
+		<< "	s[0].b[1].b[1] = ${COORDS}.xy;"
+		<< "	s[0].b[2].a    = uf_fourth;"
+		<< "	s[0].b[2].b[0] = ${COORDS}.xz;"
+		<< "	s[0].b[2].b[1] = ${COORDS}.yw;"
+		<< "	s[0].c         = ui_zero;"
+		<< ""
+		<< "	// S[1]"
+		<< "	s[1].a         = ${COORDS}.w;"
+		<< "	s[1].b[0].a    = uf_two;"
+		<< "	s[1].b[0].b[0] = ${COORDS}.xx;"
+		<< "	s[1].b[0].b[1] = ${COORDS}.yy;"
+		<< "	s[1].b[1].a    = uf_three;"
+		<< "	s[1].b[1].b[0] = ${COORDS}.zz;"
+		<< "	s[1].b[1].b[1] = ${COORDS}.ww;"
+		<< "	s[1].b[2].a    = uf_four;"
+		<< "	s[1].b[2].b[0] = ${COORDS}.yx;"
+		<< "	s[1].b[2].b[1] = ${COORDS}.wz;"
+		<< "	s[1].c         = ui_one;"
+		<< ""
+		<< "	mediump float r = (s[0].b[1].b[0].x + s[1].b[2].b[1].y) * s[0].b[0].a; // (z + z) * 0.5"
+		<< "	mediump float g = s[1].b[0].b[0].y * s[0].b[2].a * s[1].b[2].a; // x * 0.25 * 4"
+		<< "	mediump float b = (s[0].b[2].b[1].y + s[0].b[1].b[0].y + s[1].a) * s[0].b[1].a; // (w + w + w) * 0.333"
+		<< "	mediump float a = float(s[0].c) + s[1].b[2].a - s[1].b[1].a; // 0 + 4.0 - 3.0"
+		<< "	${DST} = vec4(r, g, b, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,0,3);
+		});
+
+	LOCAL_STRUCT_CASE(nested_struct_array_dynamic_index, "Nested struct array with dynamic indexing", FLAG_REQUIRES_DYNAMIC_INDEXING,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[2];"
+		<< ""
+		<< "	// S[0]"
+		<< "	s[0].a         = ${COORDS}.x;"
+		<< "	s[0].b[0].a    = uf_half;"
+		<< "	s[0].b[0].b[0] = ${COORDS}.xy;"
+		<< "	s[0].b[0].b[1] = ${COORDS}.zw;"
+		<< "	s[0].b[1].a    = uf_third;"
+		<< "	s[0].b[1].b[0] = ${COORDS}.zw;"
+		<< "	s[0].b[1].b[1] = ${COORDS}.xy;"
+		<< "	s[0].b[2].a    = uf_fourth;"
+		<< "	s[0].b[2].b[0] = ${COORDS}.xz;"
+		<< "	s[0].b[2].b[1] = ${COORDS}.yw;"
+		<< "	s[0].c         = ui_zero;"
+		<< ""
+		<< "	// S[1]"
+		<< "	s[1].a         = ${COORDS}.w;"
+		<< "	s[1].b[0].a    = uf_two;"
+		<< "	s[1].b[0].b[0] = ${COORDS}.xx;"
+		<< "	s[1].b[0].b[1] = ${COORDS}.yy;"
+		<< "	s[1].b[1].a    = uf_three;"
+		<< "	s[1].b[1].b[0] = ${COORDS}.zz;"
+		<< "	s[1].b[1].b[1] = ${COORDS}.ww;"
+		<< "	s[1].b[2].a    = uf_four;"
+		<< "	s[1].b[2].b[0] = ${COORDS}.yx;"
+		<< "	s[1].b[2].b[1] = ${COORDS}.wz;"
+		<< "	s[1].c         = ui_one;"
+		<< ""
+		<< "	mediump float r = (s[0].b[ui_one].b[ui_one-1].x + s[ui_one].b[ui_two].b[ui_zero+1].y) * s[0].b[0].a; // (z + z) * 0.5"
+		<< "	mediump float g = s[ui_two-1].b[ui_two-2].b[ui_zero].y * s[0].b[ui_two].a * s[ui_one].b[2].a; // x * 0.25 * 4"
+		<< "	mediump float b = (s[ui_zero].b[ui_one+1].b[1].y + s[0].b[ui_one*ui_one].b[0].y + s[ui_one].a) * s[0].b[ui_two-ui_one].a; // (w + w + w) * 0.333"
+		<< "	mediump float a = float(s[ui_zero].c) + s[ui_one-ui_zero].b[ui_two].a - s[ui_zero+ui_one].b[ui_two-ui_one].a; // 0 + 4.0 - 3.0"
+		<< "	${DST} = vec4(r, g, b, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,0,3);
+		});
+
+	LOCAL_STRUCT_CASE(parameter, "Struct as a function parameter", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "mediump vec4 myFunc (S s)"
+		<< "{"
+		<< "	return vec4(s.a, s.b.x, s.b.y, s.c);"
+		<< "}"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, vec3(0.0), ui_one);"
+		<< "	s.b = ${COORDS}.yzw;"
+		<< "	${DST} = myFunc(s);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(parameter_nested, "Nested struct as a function parameter", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "mediump vec4 myFunc (S s)"
+		<< "{"
+		<< "	return vec4(s.a, s.b.b, s.b.a + s.c);"
+		<< "}"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(0, vec2(0.0)), ui_one);"
+		<< "	s.b = T(ui_zero, ${COORDS}.yz);"
+		<< "	${DST} = myFunc(s);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(return, "Struct as a return value", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "S myFunc (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, vec3(0.0), ui_one);"
+		<< "	s.b = ${COORDS}.yzw;"
+		<< "	return s;"
+		<< "}"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = myFunc();"
+		<< "	${DST} = vec4(s.a, s.b.x, s.b.y, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(return_nested, "Nested struct", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "S myFunc (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(0, vec2(0.0)), ui_one);"
+		<< "	s.b = T(ui_zero, ${COORDS}.yz);"
+		<< "	return s;"
+		<< "}"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = myFunc();"
+		<< "	${DST} = vec4(s.a, s.b.b, s.b.a + s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(conditional_assignment, "Conditional struct assignment", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform mediump float uf_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, ${COORDS}.yzw, ui_zero);"
+		<< "	if (uf_one > 0.0)"
+		<< "		s = S(${COORDS}.w, ${COORDS}.zyx, ui_one);"
+		<< "	${DST} = vec4(s.a, s.b.xy, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(3,2,1);
+		});
+
+	LOCAL_STRUCT_CASE(loop_assignment, "Struct assignment in loop", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, ${COORDS}.yzw, ui_zero);"
+		<< "	for (int i = 0; i < 3; i++)"
+		<< "	{"
+		<< "		if (i == 1)"
+		<< "			s = S(${COORDS}.w, ${COORDS}.zyx, ui_one);"
+		<< "	}"
+		<< "	${DST} = vec4(s.a, s.b.xy, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(3,2,1);
+		});
+
+	LOCAL_STRUCT_CASE(dynamic_loop_assignment, "Struct assignment in loop", FLAG_REQUIRES_DYNAMIC_INDEXING,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_three;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, ${COORDS}.yzw, ui_zero);"
+		<< "	for (int i = 0; i < ui_three; i++)"
+		<< "	{"
+		<< "		if (i == ui_one)"
+		<< "			s = S(${COORDS}.w, ${COORDS}.zyx, ui_one);"
+		<< "	}"
+		<< "	${DST} = vec4(s.a, s.b.xy, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(3,2,1);
+		});
+
+	LOCAL_STRUCT_CASE(nested_conditional_assignment, "Conditional assignment of nested struct", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform mediump float uf_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(ui_one, ${COORDS}.yz), ui_one);"
+		<< "	if (uf_one > 0.0)"
+		<< "		s.b = T(ui_zero, ${COORDS}.zw);"
+		<< "	${DST} = vec4(s.a, s.b.b, s.c - s.b.a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,2,3);
+		});
+
+	LOCAL_STRUCT_CASE(nested_loop_assignment, "Nested struct assignment in loop", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform mediump float uf_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(ui_one, ${COORDS}.yz), ui_one);"
+		<< "	for (int i = 0; i < 3; i++)"
+		<< "	{"
+		<< "		if (i == 1)"
+		<< "			s.b = T(ui_zero, ${COORDS}.zw);"
+		<< "	}"
+		<< "	${DST} = vec4(s.a, s.b.b, s.c - s.b.a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,2,3);
+		});
+
+	LOCAL_STRUCT_CASE(nested_dynamic_loop_assignment, "Nested struct assignment in dynamic loop", FLAG_REQUIRES_DYNAMIC_INDEXING,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_three;"
+		<< "uniform mediump float uf_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(ui_one, ${COORDS}.yz), ui_one);"
+		<< "	for (int i = 0; i < ui_three; i++)"
+		<< "	{"
+		<< "		if (i == ui_one)"
+		<< "			s.b = T(ui_zero, ${COORDS}.zw);"
+		<< "	}"
+		<< "	${DST} = vec4(s.a, s.b.b, s.c - s.b.a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,2,3);
+		});
+
+	LOCAL_STRUCT_CASE(loop_struct_array, "Struct array usage in loop", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[3];"
+		<< "	s[0] = S(${COORDS}.x, ui_zero);"
+		<< "	s[1].a = ${COORDS}.y;"
+		<< "	s[1].b = -ui_one;"
+		<< "	s[2] = S(${COORDS}.z, ui_two);"
+		<< ""
+		<< "	mediump float rgb[3];"
+		<< "	int alpha = 0;"
+		<< "	for (int i = 0; i < 3; i++)"
+		<< "	{"
+		<< "		rgb[i] = s[2-i].a;"
+		<< "		alpha += s[i].b;"
+		<< "	}"
+		<< "	${DST} = vec4(rgb[0], rgb[1], rgb[2], alpha);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,1,0);
+		});
+
+	LOCAL_STRUCT_CASE(loop_nested_struct_array, "Nested struct array usage in loop", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< "uniform mediump float uf_sixth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[2];"
+		<< ""
+		<< "	// S[0]"
+		<< "	s[0].a         = ${COORDS}.x;"
+		<< "	s[0].b[0].a    = uf_half;"
+		<< "	s[0].b[0].b[0] = ${COORDS}.yx;"
+		<< "	s[0].b[0].b[1] = ${COORDS}.zx;"
+		<< "	s[0].b[1].a    = uf_third;"
+		<< "	s[0].b[1].b[0] = ${COORDS}.yy;"
+		<< "	s[0].b[1].b[1] = ${COORDS}.wy;"
+		<< "	s[0].b[2].a    = uf_fourth;"
+		<< "	s[0].b[2].b[0] = ${COORDS}.zx;"
+		<< "	s[0].b[2].b[1] = ${COORDS}.zy;"
+		<< "	s[0].c         = ui_zero;"
+		<< ""
+		<< "	// S[1]"
+		<< "	s[1].a         = ${COORDS}.w;"
+		<< "	s[1].b[0].a    = uf_two;"
+		<< "	s[1].b[0].b[0] = ${COORDS}.zx;"
+		<< "	s[1].b[0].b[1] = ${COORDS}.zy;"
+		<< "	s[1].b[1].a    = uf_three;"
+		<< "	s[1].b[1].b[0] = ${COORDS}.zz;"
+		<< "	s[1].b[1].b[1] = ${COORDS}.ww;"
+		<< "	s[1].b[2].a    = uf_four;"
+		<< "	s[1].b[2].b[0] = ${COORDS}.yx;"
+		<< "	s[1].b[2].b[1] = ${COORDS}.wz;"
+		<< "	s[1].c         = ui_one;"
+		<< ""
+		<< "	mediump float r = 0.0; // (x*3 + y*3) / 6.0"
+		<< "	mediump float g = 0.0; // (y*3 + z*3) / 6.0"
+		<< "	mediump float b = 0.0; // (z*3 + w*3) / 6.0"
+		<< "	mediump float a = 1.0;"
+		<< "	for (int i = 0; i < 2; i++)"
+		<< "	{"
+		<< "		for (int j = 0; j < 3; j++)"
+		<< "		{"
+		<< "			r += s[0].b[j].b[i].y;"
+		<< "			g += s[i].b[j].b[0].x;"
+		<< "			b += s[i].b[j].b[1].x;"
+		<< "			a *= s[i].b[j].a;"
+		<< "		}"
+		<< "	}"
+		<< "	${DST} = vec4(r*uf_sixth, g*uf_sixth, b*uf_sixth, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = (c.coords.swizzle(0,1,2) + c.coords.swizzle(1,2,3)) * 0.5f;
+		});
+
+	LOCAL_STRUCT_CASE(dynamic_loop_struct_array, "Struct array usage in dynamic loop", FLAG_REQUIRES_DYNAMIC_INDEXING|FLAG_REQUIRES_DYNAMIC_LOOPS,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform int ui_three;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[3];"
+		<< "	s[0] = S(${COORDS}.x, ui_zero);"
+		<< "	s[1].a = ${COORDS}.y;"
+		<< "	s[1].b = -ui_one;"
+		<< "	s[2] = S(${COORDS}.z, ui_two);"
+		<< ""
+		<< "	mediump float rgb[3];"
+		<< "	int alpha = 0;"
+		<< "	for (int i = 0; i < ui_three; i++)"
+		<< "	{"
+		<< "		rgb[i] = s[2-i].a;"
+		<< "		alpha += s[i].b;"
+		<< "	}"
+		<< "	${DST} = vec4(rgb[0], rgb[1], rgb[2], alpha);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,1,0);
+		});
+
+	LOCAL_STRUCT_CASE(dynamic_loop_nested_struct_array, "Nested struct array usage in dynamic loop", FLAG_REQUIRES_DYNAMIC_INDEXING|FLAG_REQUIRES_DYNAMIC_LOOPS,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform int ui_three;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< "uniform mediump float uf_sixth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[2];"
+		<< ""
+		<< "	// S[0]"
+		<< "	s[0].a         = ${COORDS}.x;"
+		<< "	s[0].b[0].a    = uf_half;"
+		<< "	s[0].b[0].b[0] = ${COORDS}.yx;"
+		<< "	s[0].b[0].b[1] = ${COORDS}.zx;"
+		<< "	s[0].b[1].a    = uf_third;"
+		<< "	s[0].b[1].b[0] = ${COORDS}.yy;"
+		<< "	s[0].b[1].b[1] = ${COORDS}.wy;"
+		<< "	s[0].b[2].a    = uf_fourth;"
+		<< "	s[0].b[2].b[0] = ${COORDS}.zx;"
+		<< "	s[0].b[2].b[1] = ${COORDS}.zy;"
+		<< "	s[0].c         = ui_zero;"
+		<< ""
+		<< "	// S[1]"
+		<< "	s[1].a         = ${COORDS}.w;"
+		<< "	s[1].b[0].a    = uf_two;"
+		<< "	s[1].b[0].b[0] = ${COORDS}.zx;"
+		<< "	s[1].b[0].b[1] = ${COORDS}.zy;"
+		<< "	s[1].b[1].a    = uf_three;"
+		<< "	s[1].b[1].b[0] = ${COORDS}.zz;"
+		<< "	s[1].b[1].b[1] = ${COORDS}.ww;"
+		<< "	s[1].b[2].a    = uf_four;"
+		<< "	s[1].b[2].b[0] = ${COORDS}.yx;"
+		<< "	s[1].b[2].b[1] = ${COORDS}.wz;"
+		<< "	s[1].c         = ui_one;"
+		<< ""
+		<< "	mediump float r = 0.0; // (x*3 + y*3) / 6.0"
+		<< "	mediump float g = 0.0; // (y*3 + z*3) / 6.0"
+		<< "	mediump float b = 0.0; // (z*3 + w*3) / 6.0"
+		<< "	mediump float a = 1.0;"
+		<< "	for (int i = 0; i < ui_two; i++)"
+		<< "	{"
+		<< "		for (int j = 0; j < ui_three; j++)"
+		<< "		{"
+		<< "			r += s[0].b[j].b[i].y;"
+		<< "			g += s[i].b[j].b[0].x;"
+		<< "			b += s[i].b[j].b[1].x;"
+		<< "			a *= s[i].b[j].a;"
+		<< "		}"
+		<< "	}"
+		<< "	${DST} = vec4(r*uf_sixth, g*uf_sixth, b*uf_sixth, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = (c.coords.swizzle(0,1,2) + c.coords.swizzle(1,2,3)) * 0.5f;
+		});
+
+	LOCAL_STRUCT_CASE(basic_equal, "Basic struct equality", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S a = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y), 2.3), ui_one);"
+		<< "	S b = S(floor(${COORDS}.x+0.5), vec3(0.0, floor(${COORDS}.y), 2.3), ui_one);"
+		<< "	S c = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y+0.5), 2.3), ui_one);"
+		<< "	S d = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y), 2.3), ui_two);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a == b) ${DST}.x = 1.0;"
+		<< "	if (a == c) ${DST}.y = 1.0;"
+		<< "	if (a == d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			if (deFloatFloor(c.coords[0]) == deFloatFloor(c.coords[0]+0.5f))
+				c.color.x() = 1.0f;
+			if (deFloatFloor(c.coords[1]) == deFloatFloor(c.coords[1]+0.5f))
+				c.color.y() = 1.0f;
+		});
+
+	LOCAL_STRUCT_CASE(basic_not_equal, "Basic struct equality", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S a = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y), 2.3), ui_one);"
+		<< "	S b = S(floor(${COORDS}.x+0.5), vec3(0.0, floor(${COORDS}.y), 2.3), ui_one);"
+		<< "	S c = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y+0.5), 2.3), ui_one);"
+		<< "	S d = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y), 2.3), ui_two);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a != b) ${DST}.x = 1.0;"
+		<< "	if (a != c) ${DST}.y = 1.0;"
+		<< "	if (a != d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			if (deFloatFloor(c.coords[0]) != deFloatFloor(c.coords[0]+0.5f))
+				c.color.x() = 1.0f;
+			if (deFloatFloor(c.coords[1]) != deFloatFloor(c.coords[1]+0.5f))
+				c.color.y() = 1.0f;
+			c.color.z() = 1.0f;
+		});
+
+	LOCAL_STRUCT_CASE(nested_equal, "Nested struct struct equality", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump vec3	a;"
+		<< "	int				b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S a = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_one), 1);"
+		<< "	S b = S(floor(${COORDS}.x+0.5), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_one), 1);"
+		<< "	S c = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y+0.5), 2.3), ui_one), 1);"
+		<< "	S d = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_two), 1);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a == b) ${DST}.x = 1.0;"
+		<< "	if (a == c) ${DST}.y = 1.0;"
+		<< "	if (a == d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			if (deFloatFloor(c.coords[0]) == deFloatFloor(c.coords[0]+0.5f))
+				c.color.x() = 1.0f;
+			if (deFloatFloor(c.coords[1]) == deFloatFloor(c.coords[1]+0.5f))
+				c.color.y() = 1.0f;
+		});
+
+	LOCAL_STRUCT_CASE(nested_not_equal, "Nested struct struct equality", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump vec3	a;"
+		<< "	int				b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S a = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_one), 1);"
+		<< "	S b = S(floor(${COORDS}.x+0.5), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_one), 1);"
+		<< "	S c = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y+0.5), 2.3), ui_one), 1);"
+		<< "	S d = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_two), 1);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a != b) ${DST}.x = 1.0;"
+		<< "	if (a != c) ${DST}.y = 1.0;"
+		<< "	if (a != d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			if (deFloatFloor(c.coords[0]) != deFloatFloor(c.coords[0]+0.5f))
+				c.color.x() = 1.0f;
+			if (deFloatFloor(c.coords[1]) != deFloatFloor(c.coords[1]+0.5f))
+				c.color.y() = 1.0f;
+			c.color.z() = 1.0f;
+		});
+}
+
+class UniformStructTests : public TestCaseGroup
+{
+public:
+	UniformStructTests (Context& context)
+		: TestCaseGroup(context, "uniform", "Uniform structs")
+	{
+	}
+
+	~UniformStructTests (void)
+	{
+	}
+
+	virtual void init (void);
+};
+
+namespace
+{
+
+#define CHECK_SET_UNIFORM(NAME) GLU_EXPECT_NO_ERROR(gl.getError(), (string("Failed to set ") + NAME).c_str())
+
+#define MAKE_SET_VEC_UNIFORM(VECTYPE, SETUNIFORM)															\
+void setUniform (const glw::Functions& gl, deUint32 programID, const char* name, const tcu::VECTYPE& vec)	\
+{																											\
+	int loc = gl.getUniformLocation(programID, name);														\
+	SETUNIFORM(loc, 1, vec.getPtr());																		\
+	CHECK_SET_UNIFORM(name);																				\
+}																											\
+struct SetUniform##VECTYPE##Dummy_s { int unused; }
+
+#define MAKE_SET_VEC_UNIFORM_PTR(VECTYPE, SETUNIFORM)																		\
+void setUniform (const glw::Functions& gl, deUint32 programID, const char* name, const tcu::VECTYPE* vec, int arraySize)	\
+{																															\
+	int loc = gl.getUniformLocation(programID, name);																		\
+	SETUNIFORM(loc, arraySize, vec->getPtr());																				\
+	CHECK_SET_UNIFORM(name);																								\
+}																															\
+struct SetUniformPtr##VECTYPE##Dummy_s { int unused; }
+
+MAKE_SET_VEC_UNIFORM	(Vec2,	gl.uniform2fv);
+MAKE_SET_VEC_UNIFORM	(Vec3,	gl.uniform3fv);
+MAKE_SET_VEC_UNIFORM_PTR(Vec2,	gl.uniform2fv);
+
+void setUniform (const glw::Functions& gl, deUint32 programID, const char* name, float value)
+{
+	int loc = gl.getUniformLocation(programID, name);
+	gl.uniform1f(loc, value);
+	CHECK_SET_UNIFORM(name);
+}
+
+void setUniform (const glw::Functions& gl, deUint32 programID, const char* name, int value)
+{
+	int loc = gl.getUniformLocation(programID, name);
+	gl.uniform1i(loc, value);
+	CHECK_SET_UNIFORM(name);
+}
+
+void setUniform (const glw::Functions& gl, deUint32 programID, const char* name, const float* value, int arraySize)
+{
+	int loc = gl.getUniformLocation(programID, name);
+	gl.uniform1fv(loc, arraySize, value);
+	CHECK_SET_UNIFORM(name);
+}
+
+} // anonymous
+
+void UniformStructTests::init (void)
+{
+	#define UNIFORM_STRUCT_CASE(NAME, DESCRIPTION, FLAGS, SHADER_SRC, SET_UNIFORMS_BODY, EVAL_FUNC_BODY)																\
+		do {																																							\
+			struct SetUniforms_##NAME { static void setUniforms (const glw::Functions& gl, deUint32 programID, const tcu::Vec4& constCoords) SET_UNIFORMS_BODY };		\
+			struct Eval_##NAME { static void eval (ShaderEvalContext& c) EVAL_FUNC_BODY };																				\
+			addChild(createStructCase(m_context, #NAME "_vertex", DESCRIPTION, true, FLAGS, Eval_##NAME::eval, SetUniforms_##NAME::setUniforms, SHADER_SRC));			\
+			addChild(createStructCase(m_context, #NAME "_fragment", DESCRIPTION, false, FLAGS, Eval_##NAME::eval, SetUniforms_##NAME::setUniforms, SHADER_SRC));		\
+		} while (deGetFalse())
+
+	UNIFORM_STRUCT_CASE(basic, "Basic struct usage", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s.a, s.b.x, s.b.y, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s.a", constCoords.x());
+			setUniform(gl, programID, "s.b", constCoords.swizzle(1, 2, 3));
+			setUniform(gl, programID, "s.c", 1);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(0,1,2);
+		});
+
+	UNIFORM_STRUCT_CASE(nested, "Nested struct", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s.a, s.b.b, s.b.a + s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s.a",	constCoords.x());
+			setUniform(gl, programID, "s.b.a",	0);
+			setUniform(gl, programID, "s.b.b",	constCoords.swizzle(1,2));
+			setUniform(gl, programID, "s.c",	1);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(0,1,2);
+		});
+
+	UNIFORM_STRUCT_CASE(array_member, "Struct with array member", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump float	b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s.a, s.b[0], s.b[1], s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s.a",	constCoords.w());
+			setUniform(gl, programID, "s.c",	1);
+
+			float b[3];
+			b[0] = constCoords.z();
+			b[1] = constCoords.y();
+			b[2] = constCoords.x();
+			setUniform(gl, programID, "s.b", b, DE_LENGTH_OF_ARRAY(b));
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(3,2,1);
+		});
+
+	UNIFORM_STRUCT_CASE(array_member_dynamic_index, "Struct with array member, dynamic indexing", FLAG_REQUIRES_DYNAMIC_INDEXING,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump float	b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s.b[ui_one], s.b[ui_zero], s.b[ui_two], s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s.a",	constCoords.w());
+			setUniform(gl, programID, "s.c",	1);
+
+			float b[3];
+			b[0] = constCoords.z();
+			b[1] = constCoords.y();
+			b[2] = constCoords.x();
+			setUniform(gl, programID, "s.b", b, DE_LENGTH_OF_ARRAY(b));
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(1,2,0);
+		});
+
+	UNIFORM_STRUCT_CASE(struct_array, "Struct array", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< "uniform S s[3];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s[2].a, s[1].a, s[0].a, s[2].b - s[1].b + s[0].b);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s[0].a",	constCoords.x());
+			setUniform(gl, programID, "s[0].b",	0);
+			setUniform(gl, programID, "s[1].a",	constCoords.y());
+			setUniform(gl, programID, "s[1].b",	1);
+			setUniform(gl, programID, "s[2].a",	constCoords.z());
+			setUniform(gl, programID, "s[2].b",	2);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,1,0);
+		});
+
+	UNIFORM_STRUCT_CASE(struct_array_dynamic_index, "Struct array with dynamic indexing", FLAG_REQUIRES_DYNAMIC_INDEXING,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< "uniform S s[3];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s[ui_two].a, s[ui_one].a, s[ui_zero].a, s[ui_two].b - s[ui_one].b + s[ui_zero].b);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s[0].a",	constCoords.x());
+			setUniform(gl, programID, "s[0].b",	0);
+			setUniform(gl, programID, "s[1].a",	constCoords.y());
+			setUniform(gl, programID, "s[1].b",	1);
+			setUniform(gl, programID, "s[2].a",	constCoords.z());
+			setUniform(gl, programID, "s[2].b",	2);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,1,0);
+		});
+
+	UNIFORM_STRUCT_CASE(nested_struct_array, "Nested struct array", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s[2];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float r = (s[0].b[1].b[0].x + s[1].b[2].b[1].y) * s[0].b[0].a; // (z + z) * 0.5"
+		<< "	mediump float g = s[1].b[0].b[0].y * s[0].b[2].a * s[1].b[2].a; // x * 0.25 * 4"
+		<< "	mediump float b = (s[0].b[2].b[1].y + s[0].b[1].b[0].y + s[1].a) * s[0].b[1].a; // (w + w + w) * 0.333"
+		<< "	mediump float a = float(s[0].c) + s[1].b[2].a - s[1].b[1].a; // 0 + 4.0 - 3.0"
+		<< "	${DST} = vec4(r, g, b, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			tcu::Vec2 arr[2];
+
+			setUniform(gl, programID, "s[0].a",			constCoords.x());
+			arr[0] = constCoords.swizzle(0,1);
+			arr[1] = constCoords.swizzle(2,3);
+			setUniform(gl, programID, "s[0].b[0].a",	0.5f);
+			setUniform(gl, programID, "s[0].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,3);
+			arr[1] = constCoords.swizzle(0,1);
+			setUniform(gl, programID, "s[0].b[1].a",	1.0f/3.0f);
+			setUniform(gl, programID, "s[0].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(0,2);
+			arr[1] = constCoords.swizzle(1,3);
+			setUniform(gl, programID, "s[0].b[2].a",	1.0f/4.0f);
+			setUniform(gl, programID, "s[0].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[0].c",			0);
+
+			setUniform(gl, programID, "s[1].a",			constCoords.w());
+			arr[0] = constCoords.swizzle(0,0);
+			arr[1] = constCoords.swizzle(1,1);
+			setUniform(gl, programID, "s[1].b[0].a",	2.0f);
+			setUniform(gl, programID, "s[1].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,2);
+			arr[1] = constCoords.swizzle(3,3);
+			setUniform(gl, programID, "s[1].b[1].a",	3.0f);
+			setUniform(gl, programID, "s[1].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(3,2);
+			setUniform(gl, programID, "s[1].b[2].a",	4.0f);
+			setUniform(gl, programID, "s[1].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[1].c",			1);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,0,3);
+		});
+
+	UNIFORM_STRUCT_CASE(nested_struct_array_dynamic_index, "Nested struct array with dynamic indexing", FLAG_REQUIRES_DYNAMIC_INDEXING,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s[2];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float r = (s[0].b[ui_one].b[ui_one-1].x + s[ui_one].b[ui_two].b[ui_zero+1].y) * s[0].b[0].a; // (z + z) * 0.5"
+		<< "	mediump float g = s[ui_two-1].b[ui_two-2].b[ui_zero].y * s[0].b[ui_two].a * s[ui_one].b[2].a; // x * 0.25 * 4"
+		<< "	mediump float b = (s[ui_zero].b[ui_one+1].b[1].y + s[0].b[ui_one*ui_one].b[0].y + s[ui_one].a) * s[0].b[ui_two-ui_one].a; // (w + w + w) * 0.333"
+		<< "	mediump float a = float(s[ui_zero].c) + s[ui_one-ui_zero].b[ui_two].a - s[ui_zero+ui_one].b[ui_two-ui_one].a; // 0 + 4.0 - 3.0"
+		<< "	${DST} = vec4(r, g, b, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			tcu::Vec2 arr[2];
+
+			setUniform(gl, programID, "s[0].a",			constCoords.x());
+			arr[0] = constCoords.swizzle(0,1);
+			arr[1] = constCoords.swizzle(2,3);
+			setUniform(gl, programID, "s[0].b[0].a",	0.5f);
+			setUniform(gl, programID, "s[0].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,3);
+			arr[1] = constCoords.swizzle(0,1);
+			setUniform(gl, programID, "s[0].b[1].a",	1.0f/3.0f);
+			setUniform(gl, programID, "s[0].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(0,2);
+			arr[1] = constCoords.swizzle(1,3);
+			setUniform(gl, programID, "s[0].b[2].a",	1.0f/4.0f);
+			setUniform(gl, programID, "s[0].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[0].c",			0);
+
+			setUniform(gl, programID, "s[1].a",			constCoords.w());
+			arr[0] = constCoords.swizzle(0,0);
+			arr[1] = constCoords.swizzle(1,1);
+			setUniform(gl, programID, "s[1].b[0].a",	2.0f);
+			setUniform(gl, programID, "s[1].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,2);
+			arr[1] = constCoords.swizzle(3,3);
+			setUniform(gl, programID, "s[1].b[1].a",	3.0f);
+			setUniform(gl, programID, "s[1].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(3,2);
+			setUniform(gl, programID, "s[1].b[2].a",	4.0f);
+			setUniform(gl, programID, "s[1].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[1].c",			1);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,0,3);
+		});
+
+	UNIFORM_STRUCT_CASE(loop_struct_array, "Struct array usage in loop", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< "uniform S s[3];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float rgb[3];"
+		<< "	int alpha = 0;"
+		<< "	for (int i = 0; i < 3; i++)"
+		<< "	{"
+		<< "		rgb[i] = s[2-i].a;"
+		<< "		alpha += s[i].b;"
+		<< "	}"
+		<< "	${DST} = vec4(rgb[0], rgb[1], rgb[2], alpha);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s[0].a",	constCoords.x());
+			setUniform(gl, programID, "s[0].b",	0);
+			setUniform(gl, programID, "s[1].a",	constCoords.y());
+			setUniform(gl, programID, "s[1].b",	-1);
+			setUniform(gl, programID, "s[2].a",	constCoords.z());
+			setUniform(gl, programID, "s[2].b",	2);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,1,0);
+		});
+
+	UNIFORM_STRUCT_CASE(loop_nested_struct_array, "Nested struct array usage in loop", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< "uniform mediump float uf_sixth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s[2];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float r = 0.0; // (x*3 + y*3) / 6.0"
+		<< "	mediump float g = 0.0; // (y*3 + z*3) / 6.0"
+		<< "	mediump float b = 0.0; // (z*3 + w*3) / 6.0"
+		<< "	mediump float a = 1.0;"
+		<< "	for (int i = 0; i < 2; i++)"
+		<< "	{"
+		<< "		for (int j = 0; j < 3; j++)"
+		<< "		{"
+		<< "			r += s[0].b[j].b[i].y;"
+		<< "			g += s[i].b[j].b[0].x;"
+		<< "			b += s[i].b[j].b[1].x;"
+		<< "			a *= s[i].b[j].a;"
+		<< "		}"
+		<< "	}"
+		<< "	${DST} = vec4(r*uf_sixth, g*uf_sixth, b*uf_sixth, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			tcu::Vec2 arr[2];
+
+			setUniform(gl, programID, "s[0].a",			constCoords.x());
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(2,0);
+			setUniform(gl, programID, "s[0].b[0].a",	0.5f);
+			setUniform(gl, programID, "s[0].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,1);
+			arr[1] = constCoords.swizzle(3,1);
+			setUniform(gl, programID, "s[0].b[1].a",	1.0f/3.0f);
+			setUniform(gl, programID, "s[0].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,1);
+			arr[1] = constCoords.swizzle(2,1);
+			setUniform(gl, programID, "s[0].b[2].a",	1.0f/4.0f);
+			setUniform(gl, programID, "s[0].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[0].c",			0);
+
+			setUniform(gl, programID, "s[1].a",			constCoords.w());
+			arr[0] = constCoords.swizzle(2,0);
+			arr[1] = constCoords.swizzle(2,1);
+			setUniform(gl, programID, "s[1].b[0].a",	2.0f);
+			setUniform(gl, programID, "s[1].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,2);
+			arr[1] = constCoords.swizzle(3,3);
+			setUniform(gl, programID, "s[1].b[1].a",	3.0f);
+			setUniform(gl, programID, "s[1].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(3,2);
+			setUniform(gl, programID, "s[1].b[2].a",	4.0f);
+			setUniform(gl, programID, "s[1].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[1].c",			1);
+		},
+		{
+			c.color.xyz() = (c.constCoords.swizzle(0,1,2) + c.constCoords.swizzle(1,2,3)) * 0.5f;
+		});
+
+	UNIFORM_STRUCT_CASE(dynamic_loop_struct_array, "Struct array usage in dynamic loop", FLAG_REQUIRES_DYNAMIC_INDEXING|FLAG_REQUIRES_DYNAMIC_LOOPS,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform int ui_three;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< "uniform S s[3];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float rgb[3];"
+		<< "	int alpha = 0;"
+		<< "	for (int i = 0; i < ui_three; i++)"
+		<< "	{"
+		<< "		rgb[i] = s[2-i].a;"
+		<< "		alpha += s[i].b;"
+		<< "	}"
+		<< "	${DST} = vec4(rgb[0], rgb[1], rgb[2], alpha);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s[0].a",	constCoords.x());
+			setUniform(gl, programID, "s[0].b",	0);
+			setUniform(gl, programID, "s[1].a",	constCoords.y());
+			setUniform(gl, programID, "s[1].b",	-1);
+			setUniform(gl, programID, "s[2].a",	constCoords.z());
+			setUniform(gl, programID, "s[2].b",	2);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,1,0);
+		});
+
+	UNIFORM_STRUCT_CASE(dynamic_loop_nested_struct_array, "Nested struct array usage in dynamic loop", FLAG_REQUIRES_DYNAMIC_INDEXING|FLAG_REQUIRES_DYNAMIC_LOOPS,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform int ui_three;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< "uniform mediump float uf_sixth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s[2];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float r = 0.0; // (x*3 + y*3) / 6.0"
+		<< "	mediump float g = 0.0; // (y*3 + z*3) / 6.0"
+		<< "	mediump float b = 0.0; // (z*3 + w*3) / 6.0"
+		<< "	mediump float a = 1.0;"
+		<< "	for (int i = 0; i < ui_two; i++)"
+		<< "	{"
+		<< "		for (int j = 0; j < ui_three; j++)"
+		<< "		{"
+		<< "			r += s[0].b[j].b[i].y;"
+		<< "			g += s[i].b[j].b[0].x;"
+		<< "			b += s[i].b[j].b[1].x;"
+		<< "			a *= s[i].b[j].a;"
+		<< "		}"
+		<< "	}"
+		<< "	${DST} = vec4(r*uf_sixth, g*uf_sixth, b*uf_sixth, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			tcu::Vec2 arr[2];
+
+			setUniform(gl, programID, "s[0].a",			constCoords.x());
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(2,0);
+			setUniform(gl, programID, "s[0].b[0].a",	0.5f);
+			setUniform(gl, programID, "s[0].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,1);
+			arr[1] = constCoords.swizzle(3,1);
+			setUniform(gl, programID, "s[0].b[1].a",	1.0f/3.0f);
+			setUniform(gl, programID, "s[0].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,1);
+			arr[1] = constCoords.swizzle(2,1);
+			setUniform(gl, programID, "s[0].b[2].a",	1.0f/4.0f);
+			setUniform(gl, programID, "s[0].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[0].c",			0);
+
+			setUniform(gl, programID, "s[1].a",			constCoords.w());
+			arr[0] = constCoords.swizzle(2,0);
+			arr[1] = constCoords.swizzle(2,1);
+			setUniform(gl, programID, "s[1].b[0].a",	2.0f);
+			setUniform(gl, programID, "s[1].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,2);
+			arr[1] = constCoords.swizzle(3,3);
+			setUniform(gl, programID, "s[1].b[1].a",	3.0f);
+			setUniform(gl, programID, "s[1].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(3,2);
+			setUniform(gl, programID, "s[1].b[2].a",	4.0f);
+			setUniform(gl, programID, "s[1].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[1].c",			1);
+		},
+		{
+			c.color.xyz() = (c.constCoords.swizzle(0,1,2) + c.constCoords.swizzle(1,2,3)) * 0.5f;
+		});
+
+	UNIFORM_STRUCT_CASE(sampler, "Sampler in struct", FLAG_USES_TEXTURES,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	sampler2D		c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(texture2D(s.c, ${COORDS}.xy * s.b.xy + s.b.z).rgb, s.a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			DE_UNREF(constCoords);
+			setUniform(gl, programID, "s.a", 1.0f);
+			setUniform(gl, programID, "s.b", tcu::Vec3(0.25f, 0.25f, 0.5f));
+			setUniform(gl, programID, "s.c", 0);
+		},
+		{
+			c.color.xyz() = c.texture2D(TEXTURE_BRICK, c.coords.swizzle(0,1) * 0.25f + 0.5f).swizzle(0,1,2);
+		});
+
+	UNIFORM_STRUCT_CASE(sampler_nested, "Sampler in nested struct", FLAG_USES_TEXTURES,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct T {"
+		<< "	sampler2D		a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(texture2D(s.b.a, ${COORDS}.xy * s.b.b + s.a).rgb, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			DE_UNREF(constCoords);
+			setUniform(gl, programID, "s.a",	0.5f);
+			setUniform(gl, programID, "s.b.a",	0);
+			setUniform(gl, programID, "s.b.b",	tcu::Vec2(0.25f, 0.25f));
+			setUniform(gl, programID, "s.c",	1);
+		},
+		{
+			c.color.xyz() = c.texture2D(TEXTURE_BRICK, c.coords.swizzle(0,1) * 0.25f + 0.5f).swizzle(0,1,2);
+		});
+
+	UNIFORM_STRUCT_CASE(sampler_array, "Sampler in struct array", FLAG_USES_TEXTURES,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	sampler2D		c;"
+		<< "};"
+		<< "uniform S s[2];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(texture2D(s[1].c, ${COORDS}.xy * s[0].b.xy + s[1].b.z).rgb, s[0].a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			DE_UNREF(constCoords);
+			setUniform(gl, programID, "s[0].a", 1.0f);
+			setUniform(gl, programID, "s[0].b", tcu::Vec3(0.25f, 0.25f, 0.25f));
+			setUniform(gl, programID, "s[0].c", 1);
+			setUniform(gl, programID, "s[1].a", 0.0f);
+			setUniform(gl, programID, "s[1].b", tcu::Vec3(0.5f, 0.5f, 0.5f));
+			setUniform(gl, programID, "s[1].c", 0);
+		},
+		{
+			c.color.xyz() = c.texture2D(TEXTURE_BRICK, c.coords.swizzle(0,1) * 0.25f + 0.5f).swizzle(0,1,2);
+		});
+
+	UNIFORM_STRUCT_CASE(equal, "Struct equality", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform mediump float uf_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S a;"
+		<< "uniform S b;"
+		<< "uniform S c;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S d = S(uf_one, vec3(0.0, floor(${COORDS}.y+1.0), 2.0), ui_two);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a == b) ${DST}.x = 1.0;"
+		<< "	if (a == c) ${DST}.y = 1.0;"
+		<< "	if (a == d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			DE_UNREF(constCoords);
+			setUniform(gl, programID, "a.a", 1.0f);
+			setUniform(gl, programID, "a.b", tcu::Vec3(0.0f, 1.0f, 2.0f));
+			setUniform(gl, programID, "a.c", 2);
+			setUniform(gl, programID, "b.a", 1.0f);
+			setUniform(gl, programID, "b.b", tcu::Vec3(0.0f, 1.0f, 2.0f));
+			setUniform(gl, programID, "b.c", 2);
+			setUniform(gl, programID, "c.a", 1.0f);
+			setUniform(gl, programID, "c.b", tcu::Vec3(0.0f, 1.1f, 2.0f));
+			setUniform(gl, programID, "c.c", 2);
+		},
+		{
+			c.color.xy() = tcu::Vec2(1.0f, 0.0f);
+			if (deFloatFloor(c.coords[1]+1.0f) == deFloatFloor(1.1f))
+				c.color.z() = 1.0f;
+		});
+
+	UNIFORM_STRUCT_CASE(not_equal, "Struct equality", 0,
+		LineStream()
+		<< "${DECLARATIONS}"
+		<< "uniform mediump float uf_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S a;"
+		<< "uniform S b;"
+		<< "uniform S c;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S d = S(uf_one, vec3(0.0, floor(${COORDS}.y+1.0), 2.0), ui_two);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a != b) ${DST}.x = 1.0;"
+		<< "	if (a != c) ${DST}.y = 1.0;"
+		<< "	if (a != d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			DE_UNREF(constCoords);
+			setUniform(gl, programID, "a.a", 1.0f);
+			setUniform(gl, programID, "a.b", tcu::Vec3(0.0f, 1.0f, 2.0f));
+			setUniform(gl, programID, "a.c", 2);
+			setUniform(gl, programID, "b.a", 1.0f);
+			setUniform(gl, programID, "b.b", tcu::Vec3(0.0f, 1.0f, 2.0f));
+			setUniform(gl, programID, "b.c", 2);
+			setUniform(gl, programID, "c.a", 1.0f);
+			setUniform(gl, programID, "c.b", tcu::Vec3(0.0f, 1.1f, 2.0f));
+			setUniform(gl, programID, "c.c", 2);
+		},
+		{
+			c.color.xy() = tcu::Vec2(0.0f, 1.0f);
+			if (deFloatFloor(c.coords[1]+1.0f) != deFloatFloor(1.1f))
+				c.color.z() = 1.0f;
+		});
+}
+
+ShaderStructTests::ShaderStructTests (Context& context)
+	: TestCaseGroup(context, "struct", "Struct Tests")
+{
+}
+
+ShaderStructTests::~ShaderStructTests (void)
+{
+}
+
+void ShaderStructTests::init (void)
+{
+	addChild(new LocalStructTests(m_context));
+	addChild(new UniformStructTests(m_context));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderStructTests.hpp b/modules/gles2/functional/es2fShaderStructTests.hpp
new file mode 100644
index 0000000..fd4c6d1
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderStructTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERSTRUCTTESTS_HPP
+#define _ES2FSHADERSTRUCTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader struct tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderStructTests : public TestCaseGroup
+{
+public:
+							ShaderStructTests		(Context& context);
+	virtual					~ShaderStructTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderStructTests		(const ShaderStructTests&);		// not allowed!
+	ShaderStructTests&		operator=				(const ShaderStructTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERSTRUCTTESTS_HPP
diff --git a/modules/gles2/functional/es2fShaderTextureFunctionTests.cpp b/modules/gles2/functional/es2fShaderTextureFunctionTests.cpp
new file mode 100644
index 0000000..13a4428
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderTextureFunctionTests.cpp
@@ -0,0 +1,636 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture access function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fShaderTextureFunctionTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "glsShaderLibrary.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuMatrixUtil.hpp"
+
+#include <sstream>
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+namespace
+{
+
+enum Function
+{
+	FUNCTION_TEXTURE = 0,		//!< texture(), textureOffset()
+	FUNCTION_TEXTUREPROJ,		//!< textureProj(), textureProjOffset()
+	FUNCTION_TEXTUREPROJ3,		//!< textureProj(sampler2D, vec3)
+	FUNCTION_TEXTURELOD,		// ...
+	FUNCTION_TEXTUREPROJLOD,
+	FUNCTION_TEXTUREPROJLOD3,	//!< textureProjLod(sampler2D, vec3)
+
+	FUNCTION_LAST
+};
+
+inline bool functionHasProj (Function function)
+{
+	return function == FUNCTION_TEXTUREPROJ		||
+		   function == FUNCTION_TEXTUREPROJ3	||
+		   function == FUNCTION_TEXTUREPROJLOD	||
+		   function == FUNCTION_TEXTUREPROJLOD3;
+}
+
+inline bool functionHasLod (Function function)
+{
+	return function == FUNCTION_TEXTURELOD		||
+		   function == FUNCTION_TEXTUREPROJLOD	||
+		   function == FUNCTION_TEXTUREPROJLOD3;
+}
+
+struct TextureLookupSpec
+{
+	Function		function;
+
+	tcu::Vec4		minCoord;
+	tcu::Vec4		maxCoord;
+
+	// Bias
+	bool			useBias;
+
+	// Bias or Lod for *Lod* functions
+	float			minLodBias;
+	float			maxLodBias;
+
+	TextureLookupSpec (void)
+		: function		(FUNCTION_LAST)
+		, minCoord		(0.0f)
+		, maxCoord		(1.0f)
+		, useBias		(false)
+		, minLodBias	(0.0f)
+		, maxLodBias	(0.0f)
+	{
+	}
+
+	TextureLookupSpec (Function				function_,
+					   const tcu::Vec4&		minCoord_,
+					   const tcu::Vec4&		maxCoord_,
+					   bool					useBias_,
+					   float				minLodBias_,
+					   float				maxLodBias_)
+		: function		(function_)
+		, minCoord		(minCoord_)
+		, maxCoord		(maxCoord_)
+		, useBias		(useBias_)
+		, minLodBias	(minLodBias_)
+		, maxLodBias	(maxLodBias_)
+	{
+	}
+};
+
+enum TextureType
+{
+	TEXTURETYPE_2D,
+	TEXTURETYPE_CUBE_MAP,
+
+	TEXTURETYPE_LAST
+};
+
+struct TextureSpec
+{
+	TextureType			type;		//!< Texture type (2D, cubemap, ...)
+	deUint32			format;
+	deUint32			dataType;
+	int					width;
+	int					height;
+	int					numLevels;
+	tcu::Sampler		sampler;
+
+	TextureSpec (void)
+		: type			(TEXTURETYPE_LAST)
+		, format		(GL_NONE)
+		, dataType		(GL_NONE)
+		, width			(0)
+		, height		(0)
+		, numLevels		(0)
+	{
+	}
+
+	TextureSpec (TextureType			type_,
+				 deUint32				format_,
+				 deUint32				dataType_,
+				 int					width_,
+				 int					height_,
+				 int					numLevels_,
+				 const tcu::Sampler&	sampler_)
+		: type			(type_)
+		, format		(format_)
+		, dataType		(dataType_)
+		, width			(width_)
+		, height		(height_)
+		, numLevels		(numLevels_)
+		, sampler		(sampler_)
+	{
+	}
+};
+
+struct TexLookupParams
+{
+	float				lod;
+	tcu::Vec4			scale;
+	tcu::Vec4			bias;
+
+	TexLookupParams (void)
+		: lod		(0.0f)
+		, scale		(1.0f)
+		, bias		(0.0f)
+	{
+	}
+};
+
+} // anonymous
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+
+inline float computeLodFromDerivates (float dudx, float dvdx, float dudy, float dvdy)
+{
+	float p = de::max(deFloatSqrt(dudx*dudx + dvdx*dvdx), deFloatSqrt(dudy*dudy + dvdy*dvdy));
+	return deFloatLog2(p);
+}
+
+typedef void (*TexEvalFunc) (gls::ShaderEvalContext& c, const TexLookupParams& lookupParams);
+
+inline Vec4 texture2D		(const gls::ShaderEvalContext& c, float s, float t, float lod)			{ return c.textures[0].tex2D->sample(c.textures[0].sampler, s, t, lod);			}
+inline Vec4 textureCube		(const gls::ShaderEvalContext& c, float s, float t, float r, float lod)	{ return c.textures[0].texCube->sample(c.textures[0].sampler, s, t, r, lod);	}
+
+// Eval functions.
+static void		evalTexture2D			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x(), c.in[0].y(), p.lod)*p.scale + p.bias; }
+static void		evalTextureCube			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = textureCube(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod)*p.scale + p.bias; }
+
+static void		evalTexture2DBias		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x(), c.in[0].y(), p.lod+c.in[1].x())*p.scale + p.bias; }
+static void		evalTextureCubeBias		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = textureCube(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod+c.in[1].x())*p.scale + p.bias; }
+
+static void		evalTexture2DProj3		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), p.lod)*p.scale + p.bias; }
+static void		evalTexture2DProj3Bias	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), p.lod+c.in[1].x())*p.scale + p.bias; }
+static void		evalTexture2DProj		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), p.lod)*p.scale + p.bias; }
+static void		evalTexture2DProjBias	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), p.lod+c.in[1].x())*p.scale + p.bias; }
+
+static void		evalTexture2DLod		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x(), c.in[0].y(), c.in[1].x())*p.scale + p.bias; }
+static void		evalTextureCubeLod		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = textureCube(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), c.in[1].x())*p.scale + p.bias; }
+
+static void		evalTexture2DProjLod3	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), c.in[1].x())*p.scale + p.bias; }
+static void		evalTexture2DProjLod	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[1].x())*p.scale + p.bias; }
+
+class TexLookupEvaluator : public gls::ShaderEvaluator
+{
+public:
+							TexLookupEvaluator		(TexEvalFunc evalFunc, const TexLookupParams& lookupParams) : m_evalFunc(evalFunc), m_lookupParams(lookupParams) {}
+
+	virtual void			evaluate				(gls::ShaderEvalContext& ctx) { m_evalFunc(ctx, m_lookupParams); }
+
+private:
+	TexEvalFunc				m_evalFunc;
+	const TexLookupParams&	m_lookupParams;
+};
+
+class ShaderTextureFunctionCase : public gls::ShaderRenderCase
+{
+public:
+							ShaderTextureFunctionCase		(Context& context, const char* name, const char* desc, const TextureLookupSpec& lookup, const TextureSpec& texture, TexEvalFunc evalFunc, bool isVertexCase);
+							~ShaderTextureFunctionCase		(void);
+
+	void					init							(void);
+	void					deinit							(void);
+
+protected:
+	void					setupUniforms					(int programID, const tcu::Vec4& constCoords);
+
+private:
+	void					initTexture						(void);
+	void					initShaderSources				(void);
+
+	TextureLookupSpec		m_lookupSpec;
+	TextureSpec				m_textureSpec;
+
+	TexLookupParams			m_lookupParams;
+	TexLookupEvaluator		m_evaluator;
+
+	glu::Texture2D*			m_texture2D;
+	glu::TextureCube*		m_textureCube;
+};
+
+ShaderTextureFunctionCase::ShaderTextureFunctionCase (Context& context, const char* name, const char* desc, const TextureLookupSpec& lookup, const TextureSpec& texture, TexEvalFunc evalFunc, bool isVertexCase)
+	: gls::ShaderRenderCase(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, desc, isVertexCase, m_evaluator)
+	, m_lookupSpec			(lookup)
+	, m_textureSpec			(texture)
+	, m_evaluator			(evalFunc, m_lookupParams)
+	, m_texture2D			(DE_NULL)
+	, m_textureCube			(DE_NULL)
+{
+}
+
+ShaderTextureFunctionCase::~ShaderTextureFunctionCase (void)
+{
+	delete m_texture2D;
+	delete m_textureCube;
+}
+
+void ShaderTextureFunctionCase::init (void)
+{
+	if (m_isVertexCase)
+	{
+		const glw::Functions& gl = m_renderCtx.getFunctions();
+		int numVertexUnits = 0;
+		gl.getIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &numVertexUnits);
+		if (numVertexUnits < 1)
+			throw tcu::NotSupportedError("Vertex shader texture access is not supported");
+	}
+
+	{
+		// Base coord scale & bias
+		Vec4 s = m_lookupSpec.maxCoord-m_lookupSpec.minCoord;
+		Vec4 b = m_lookupSpec.minCoord;
+
+		float baseCoordTrans[] =
+		{
+			s.x(),		0.0f,		0.f,	b.x(),
+			0.f,		s.y(),		0.f,	b.y(),
+			s.z()/2.f,	-s.z()/2.f,	0.f,	s.z()/2.f + b.z(),
+			-s.w()/2.f,	s.w()/2.f,	0.f,	s.w()/2.f + b.w()
+		};
+
+		m_userAttribTransforms.push_back(tcu::Mat4(baseCoordTrans));
+	}
+
+	if (functionHasLod(m_lookupSpec.function) || m_lookupSpec.useBias)
+	{
+		float s = m_lookupSpec.maxLodBias-m_lookupSpec.minLodBias;
+		float b = m_lookupSpec.minLodBias;
+		float lodCoordTrans[] =
+		{
+			s/2.0f,		s/2.0f,		0.f,	b,
+			0.0f,		0.0f,		0.0f,	0.0f,
+			0.0f,		0.0f,		0.0f,	0.0f,
+			0.0f,		0.0f,		0.0f,	0.0f
+		};
+
+		m_userAttribTransforms.push_back(tcu::Mat4(lodCoordTrans));
+	}
+
+	initShaderSources();
+	initTexture();
+
+	gls::ShaderRenderCase::init();
+}
+
+void ShaderTextureFunctionCase::initTexture (void)
+{
+	static const IVec4 texCubeSwz[] =
+	{
+		IVec4(0,0,1,1),
+		IVec4(1,1,0,0),
+		IVec4(0,1,0,1),
+		IVec4(1,0,1,0),
+		IVec4(0,1,1,0),
+		IVec4(1,0,0,1)
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(texCubeSwz) == tcu::CUBEFACE_LAST);
+
+	tcu::TextureFormat		texFmt			= glu::mapGLTransferFormat(m_textureSpec.format, m_textureSpec.dataType);
+	tcu::TextureFormatInfo	fmtInfo			= tcu::getTextureFormatInfo(texFmt);
+	tcu::IVec2				viewportSize	= getViewportSize();
+	bool					isProj			= functionHasProj(m_lookupSpec.function);
+	float					proj			= isProj ? 1.0f/m_lookupSpec.minCoord[m_lookupSpec.function == FUNCTION_TEXTUREPROJ3 ? 2 : 3] : 1.0f;
+
+	switch (m_textureSpec.type)
+	{
+		case TEXTURETYPE_2D:
+		{
+			float	cStep			= 1.0f / (float)de::max(1, m_textureSpec.numLevels-1);
+			Vec4	cScale			= fmtInfo.valueMax-fmtInfo.valueMin;
+			Vec4	cBias			= fmtInfo.valueMin;
+			int		baseCellSize	= de::min(m_textureSpec.width/4, m_textureSpec.height/4);
+
+			m_texture2D = new glu::Texture2D(m_renderCtx, m_textureSpec.format, m_textureSpec.dataType, m_textureSpec.width, m_textureSpec.height);
+			for (int level = 0; level < m_textureSpec.numLevels; level++)
+			{
+				float	fA		= level*cStep;
+				float	fB		= 1.0f-fA;
+				Vec4	colorA	= cBias + cScale*Vec4(fA, fB, fA, fB);
+				Vec4	colorB	= cBias + cScale*Vec4(fB, fA, fB, fA);
+
+				m_texture2D->getRefTexture().allocLevel(level);
+				tcu::fillWithGrid(m_texture2D->getRefTexture().getLevel(level), de::max(1, baseCellSize>>level), colorA, colorB);
+			}
+			m_texture2D->upload();
+
+			// Compute LOD.
+			float dudx = (m_lookupSpec.maxCoord[0]-m_lookupSpec.minCoord[0])*proj*m_textureSpec.width	/ (float)viewportSize[0];
+			float dvdy = (m_lookupSpec.maxCoord[1]-m_lookupSpec.minCoord[1])*proj*m_textureSpec.height	/ (float)viewportSize[1];
+			m_lookupParams.lod = computeLodFromDerivates(dudx, 0.0f, 0.0f, dvdy);
+
+			// Append to texture list.
+			m_textures.push_back(gls::TextureBinding(m_texture2D, m_textureSpec.sampler));
+			break;
+		}
+
+		case TEXTURETYPE_CUBE_MAP:
+		{
+			float	cStep			= 1.0f / (float)de::max(1, m_textureSpec.numLevels-1);
+			Vec4	cScale			= fmtInfo.valueMax-fmtInfo.valueMin;
+			Vec4	cBias			= fmtInfo.valueMin;
+			int		baseCellSize	= de::min(m_textureSpec.width/4, m_textureSpec.height/4);
+
+			DE_ASSERT(m_textureSpec.width == m_textureSpec.height);
+			m_textureCube = new glu::TextureCube(m_renderCtx, m_textureSpec.format, m_textureSpec.dataType, m_textureSpec.width);
+			for (int level = 0; level < m_textureSpec.numLevels; level++)
+			{
+				float	fA		= level*cStep;
+				float	fB		= 1.0f-fA;
+				Vec2	f		(fA, fB);
+
+				for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+				{
+					const IVec4&	swzA	= texCubeSwz[face];
+					IVec4			swzB	= 1-swzA;
+					Vec4			colorA	= cBias + cScale*f.swizzle(swzA[0], swzA[1], swzA[2], swzA[3]);
+					Vec4			colorB	= cBias + cScale*f.swizzle(swzB[0], swzB[1], swzB[2], swzB[3]);
+
+					m_textureCube->getRefTexture().allocLevel((tcu::CubeFace)face, level);
+					tcu::fillWithGrid(m_textureCube->getRefTexture().getLevelFace(level, (tcu::CubeFace)face), de::max(1, baseCellSize>>level), colorA, colorB);
+				}
+			}
+			m_textureCube->upload();
+
+			// Compute LOD \note Assumes that only single side is accessed and R is constant major axis.
+			DE_ASSERT(de::abs(m_lookupSpec.minCoord[2] - m_lookupSpec.maxCoord[2]) < 0.005);
+			DE_ASSERT(de::abs(m_lookupSpec.minCoord[0]) < de::abs(m_lookupSpec.minCoord[2]) && de::abs(m_lookupSpec.maxCoord[0]) < de::abs(m_lookupSpec.minCoord[2]));
+			DE_ASSERT(de::abs(m_lookupSpec.minCoord[1]) < de::abs(m_lookupSpec.minCoord[2]) && de::abs(m_lookupSpec.maxCoord[1]) < de::abs(m_lookupSpec.minCoord[2]));
+
+			tcu::CubeFaceFloatCoords	c00		= tcu::getCubeFaceCoords(Vec3(m_lookupSpec.minCoord[0]*proj, m_lookupSpec.minCoord[1]*proj, m_lookupSpec.minCoord[2]*proj));
+			tcu::CubeFaceFloatCoords	c10		= tcu::getCubeFaceCoords(Vec3(m_lookupSpec.maxCoord[0]*proj, m_lookupSpec.minCoord[1]*proj, m_lookupSpec.minCoord[2]*proj));
+			tcu::CubeFaceFloatCoords	c01		= tcu::getCubeFaceCoords(Vec3(m_lookupSpec.minCoord[0]*proj, m_lookupSpec.maxCoord[1]*proj, m_lookupSpec.minCoord[2]*proj));
+			float						dudx	= (c10.s - c00.s)*m_textureSpec.width	/ (float)viewportSize[0];
+			float						dvdy	= (c01.t - c00.t)*m_textureSpec.height	/ (float)viewportSize[1];
+
+			m_lookupParams.lod = computeLodFromDerivates(dudx, 0.0f, 0.0f, dvdy);
+
+			m_textures.push_back(gls::TextureBinding(m_textureCube, m_textureSpec.sampler));
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// Set lookup scale & bias
+	m_lookupParams.scale	= fmtInfo.lookupScale;
+	m_lookupParams.bias		= fmtInfo.lookupBias;
+}
+
+void ShaderTextureFunctionCase::initShaderSources (void)
+{
+	Function			function			= m_lookupSpec.function;
+	bool				isVtxCase			= m_isVertexCase;
+	bool				isProj				= functionHasProj(function);
+	bool				is2DProj4			= m_textureSpec.type == TEXTURETYPE_2D && (function == FUNCTION_TEXTUREPROJ || function == FUNCTION_TEXTUREPROJLOD);
+	bool				hasLodBias			= functionHasLod(m_lookupSpec.function) || m_lookupSpec.useBias;
+	int					texCoordComps		= m_textureSpec.type == TEXTURETYPE_2D ? 2 : 3;
+	int					extraCoordComps		= isProj ? (is2DProj4 ? 2 : 1) : 0;
+	glu::DataType		coordType			= glu::getDataTypeFloatVec(texCoordComps+extraCoordComps);
+	glu::Precision		coordPrec			= glu::PRECISION_MEDIUMP;
+	const char*			coordTypeName		= glu::getDataTypeName(coordType);
+	const char*			coordPrecName		= glu::getPrecisionName(coordPrec);
+	tcu::TextureFormat	texFmt				= glu::mapGLTransferFormat(m_textureSpec.format, m_textureSpec.dataType);
+	glu::DataType		samplerType			= glu::TYPE_LAST;
+	const char*			baseFuncName		= m_textureSpec.type == TEXTURETYPE_2D ? "texture2D" : "textureCube";
+	const char*			funcExt				= DE_NULL;
+
+	switch (m_textureSpec.type)
+	{
+		case TEXTURETYPE_2D:		samplerType = glu::getSampler2DType(texFmt);		break;
+		case TEXTURETYPE_CUBE_MAP:	samplerType = glu::getSamplerCubeType(texFmt);		break;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	switch (m_lookupSpec.function)
+	{
+		case FUNCTION_TEXTURE:			funcExt = "";			break;
+		case FUNCTION_TEXTUREPROJ:		funcExt = "Proj";		break;
+		case FUNCTION_TEXTUREPROJ3:		funcExt = "Proj";		break;
+		case FUNCTION_TEXTURELOD:		funcExt = "Lod";		break;
+		case FUNCTION_TEXTUREPROJLOD:	funcExt = "ProjLod";	break;
+		case FUNCTION_TEXTUREPROJLOD3:	funcExt = "ProjLod";	break;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	std::ostringstream	vert;
+	std::ostringstream	frag;
+	std::ostringstream&	op		= isVtxCase ? vert : frag;
+
+	vert << "attribute highp vec4 a_position;\n"
+		 << "attribute " << coordPrecName << " " << coordTypeName << " a_in0;\n";
+
+	if (hasLodBias)
+		vert << "attribute " << coordPrecName << " float a_in1;\n";
+
+	if (isVtxCase)
+	{
+		vert << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vert << "varying " << coordPrecName << " " << coordTypeName << " v_texCoord;\n";
+		frag << "varying " << coordPrecName << " " << coordTypeName << " v_texCoord;\n";
+
+		if (hasLodBias)
+		{
+			vert << "varying " << coordPrecName << " float v_lodBias;\n";
+			frag << "varying " << coordPrecName << " float v_lodBias;\n";
+		}
+	}
+
+	// Uniforms
+	op << "uniform lowp " << glu::getDataTypeName(samplerType) << " u_sampler;\n";
+
+	vert << "\nvoid main()\n{\n"
+		 << "\tgl_Position = a_position;\n";
+	frag << "\nvoid main()\n{\n";
+
+	if (isVtxCase)
+		vert << "\tv_color = ";
+	else
+		frag << "\tgl_FragColor = ";
+
+	// Op.
+	{
+		const char*	texCoord	= isVtxCase ? "a_in0" : "v_texCoord";
+		const char*	lodBias		= isVtxCase ? "a_in1" : "v_lodBias";
+
+		op << baseFuncName << funcExt;
+		op << "(u_sampler, " << texCoord;
+
+		if (functionHasLod(function) || m_lookupSpec.useBias)
+			op << ", " << lodBias;
+
+		op << ");\n";
+	}
+
+	if (isVtxCase)
+		frag << "\tgl_FragColor = v_color;\n";
+	else
+	{
+		vert << "\tv_texCoord = a_in0;\n";
+
+		if (hasLodBias)
+			vert << "\tv_lodBias = a_in1;\n";
+	}
+
+	vert << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource = vert.str();
+	m_fragShaderSource = frag.str();
+}
+
+void ShaderTextureFunctionCase::deinit (void)
+{
+	gls::ShaderRenderCase::deinit();
+
+	delete m_texture2D;
+	delete m_textureCube;
+
+	m_texture2D			= DE_NULL;
+	m_textureCube		= DE_NULL;
+}
+
+void ShaderTextureFunctionCase::setupUniforms (int programID, const tcu::Vec4&)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.uniform1i(gl.getUniformLocation(programID, "u_sampler"), 0);
+}
+
+ShaderTextureFunctionTests::ShaderTextureFunctionTests (Context& context)
+	: TestCaseGroup(context, "texture_functions", "Texture Access Function Tests")
+{
+}
+
+ShaderTextureFunctionTests::~ShaderTextureFunctionTests (void)
+{
+}
+
+struct TexFuncCaseSpec
+{
+	const char*			name;
+	TextureLookupSpec	lookupSpec;
+	TextureSpec			texSpec;
+	TexEvalFunc			evalFunc;
+};
+
+#define CASE_SPEC(NAME, FUNC, MINCOORD, MAXCOORD, USEBIAS, MINLOD, MAXLOD, TEXSPEC, EVALFUNC) \
+	{ #NAME, TextureLookupSpec(FUNC, MINCOORD, MAXCOORD, USEBIAS, MINLOD, MAXLOD), TEXSPEC, EVALFUNC }
+
+static void createCaseGroup (TestCaseGroup* parent, const char* groupName, const char* groupDesc, const TexFuncCaseSpec* cases, int numCases, bool isVertex)
+{
+	tcu::TestCaseGroup* group = new tcu::TestCaseGroup(parent->getTestContext(), groupName, groupDesc);
+	parent->addChild(group);
+
+	for (int ndx = 0; ndx < numCases; ndx++)
+		group->addChild(new ShaderTextureFunctionCase(parent->getContext(), cases[ndx].name, "", cases[ndx].lookupSpec, cases[ndx].texSpec, cases[ndx].evalFunc, isVertex));
+}
+
+void ShaderTextureFunctionTests::init (void)
+{
+	// Samplers
+	static const tcu::Sampler	samplerLinearNoMipmap	(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+														 tcu::Sampler::LINEAR, tcu::Sampler::LINEAR);
+	static const tcu::Sampler	samplerLinearMipmap		(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+														 tcu::Sampler::LINEAR_MIPMAP_NEAREST, tcu::Sampler::LINEAR);
+
+	// Default textures.
+	//												Type			Format		DataType			W		H		L	Sampler
+	static const TextureSpec tex2D			(TEXTURETYPE_2D,		GL_RGBA,	GL_UNSIGNED_BYTE,	256,	256,	1,	samplerLinearNoMipmap);
+	static const TextureSpec tex2DMipmap	(TEXTURETYPE_2D,		GL_RGBA,	GL_UNSIGNED_BYTE,	256,	256,	9,	samplerLinearMipmap);
+
+	static const TextureSpec texCube		(TEXTURETYPE_CUBE_MAP,	GL_RGBA,	GL_UNSIGNED_BYTE,	256,	256,	1,	samplerLinearNoMipmap);
+	static const TextureSpec texCubeMipmap	(TEXTURETYPE_CUBE_MAP,	GL_RGBA,	GL_UNSIGNED_BYTE,	256,	256,	9,	samplerLinearMipmap);
+
+	// Vertex cases
+	static const TexFuncCaseSpec vertexCases[] =
+	{
+		//		  Name						Function					MinCoord							MaxCoord							Bias?	MinLod	MaxLod	Texture			EvalFunc
+		CASE_SPEC(texture2d,				FUNCTION_TEXTURE,			Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	tex2D,			evalTexture2D),
+//		CASE_SPEC(texture2d_bias,			FUNCTION_TEXTURE,			Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	true,	-2.0f,	2.0f,	tex2D,			evalTexture2DBias),
+		CASE_SPEC(texture2dproj_vec3,		FUNCTION_TEXTUREPROJ3,		Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	tex2D,			evalTexture2DProj3),
+		CASE_SPEC(texture2dproj_vec4,		FUNCTION_TEXTUREPROJ,		Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	tex2D,			evalTexture2DProj),
+		CASE_SPEC(texture2dlod,				FUNCTION_TEXTURELOD,		Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	-1.0f,	9.0f,	tex2DMipmap,	evalTexture2DLod),
+//		CASE_SPEC(texture2dproj_vec3_bias,	FUNCTION_TEXTUREPROJ3,		Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	true,	-2.0f,	2.0f,	tex2D,			evalTexture2DProj3Bias),
+//		CASE_SPEC(texture2dproj_vec4_bias,	FUNCTION_TEXTUREPROJ,		Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	true,	-2.0f,	2.0f,	tex2D,			evalTexture2DProjBias),
+		CASE_SPEC(texture2dprojlod_vec3,	FUNCTION_TEXTUREPROJLOD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	-1.0f,	9.0f,	tex2D,			evalTexture2DProjLod3),
+		CASE_SPEC(texture2dprojlod_vec4,	FUNCTION_TEXTUREPROJLOD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	-1.0f,	9.0f,	tex2D,			evalTexture2DProjLod),
+		CASE_SPEC(texturecube,				FUNCTION_TEXTURE,			Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	false,	0.0f,	0.0f,	texCube,		evalTextureCube),
+//		CASE_SPEC(texturecube_bias,			FUNCTION_TEXTURE,			Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	true,	-2.0f,	2.0f,	texCube,		evalTextureCubeBias),
+		CASE_SPEC(texturecubelod,			FUNCTION_TEXTURELOD,		Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	false,	-1.0f,	9.0f,	texCubeMipmap,	evalTextureCubeLod),
+	};
+	createCaseGroup(this, "vertex", "Vertex Shader Texture Lookups", &vertexCases[0], DE_LENGTH_OF_ARRAY(vertexCases), true);
+
+	// Fragment cases
+	static const TexFuncCaseSpec fragmentCases[] =
+	{
+		//		  Name						Function				MinCoord							MaxCoord							Bias?	MinLod	MaxLod	Texture			EvalFunc
+		CASE_SPEC(texture2d,				FUNCTION_TEXTURE,		Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	tex2DMipmap,	evalTexture2D),
+		CASE_SPEC(texture2d_bias,			FUNCTION_TEXTURE,		Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	true,	-2.0f,	2.0f,	tex2DMipmap,	evalTexture2DBias),
+		CASE_SPEC(texture2dproj_vec3,		FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	tex2DMipmap,	evalTexture2DProj3),
+		CASE_SPEC(texture2dproj_vec4,		FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	tex2DMipmap,	evalTexture2DProj),
+		CASE_SPEC(texture2dproj_vec3_bias,	FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	true,	-2.0f,	2.0f,	tex2DMipmap,	evalTexture2DProj3Bias),
+		CASE_SPEC(texture2dproj_vec4_bias,	FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	true,	-2.0f,	2.0f,	tex2DMipmap,	evalTexture2DProjBias),
+		CASE_SPEC(texturecube,				FUNCTION_TEXTURE,		Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	false,	0.0f,	0.0f,	texCubeMipmap,	evalTextureCube),
+		CASE_SPEC(texturecube_bias,			FUNCTION_TEXTURE,		Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	true,	-2.0f,	2.0f,	texCubeMipmap,	evalTextureCubeBias)
+	};
+	createCaseGroup(this, "fragment", "Fragment Shader Texture Lookups", &fragmentCases[0], DE_LENGTH_OF_ARRAY(fragmentCases), false);
+
+	// Negative cases.
+	{
+		gls::ShaderLibrary library(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
+		std::vector<tcu::TestNode*> negativeCases = library.loadShaderFile("shaders/invalid_texture_functions.test");
+
+		tcu::TestCaseGroup* group = new tcu::TestCaseGroup(m_testCtx, "invalid", "Invalid texture function usage", negativeCases);
+		addChild(group);
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles2/functional/es2fShaderTextureFunctionTests.hpp b/modules/gles2/functional/es2fShaderTextureFunctionTests.hpp
new file mode 100644
index 0000000..3e5318c
--- /dev/null
+++ b/modules/gles2/functional/es2fShaderTextureFunctionTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FSHADERTEXTUREFUNCTIONTESTS_HPP
+#define _ES2FSHADERTEXTUREFUNCTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture access function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class ShaderTextureFunctionTests : public TestCaseGroup
+{
+public:
+									ShaderTextureFunctionTests		(Context& context);
+	virtual							~ShaderTextureFunctionTests		(void);
+
+	virtual void					init							(void);
+
+private:
+									ShaderTextureFunctionTests		(const ShaderTextureFunctionTests&);		// not allowed!
+	ShaderTextureFunctionTests&		operator=						(const ShaderTextureFunctionTests&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSHADERTEXTUREFUNCTIONTESTS_HPP
diff --git a/modules/gles2/functional/es2fStencilTests.cpp b/modules/gles2/functional/es2fStencilTests.cpp
new file mode 100644
index 0000000..4417127
--- /dev/null
+++ b/modules/gles2/functional/es2fStencilTests.cpp
@@ -0,0 +1,568 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stencil tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fStencilTests.hpp"
+
+#include "tcuSurface.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "sglrContextUtil.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+#include <vector>
+
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+
+using tcu::Vec3;
+using tcu::IVec2;
+using tcu::IVec4;
+using std::vector;
+using namespace glw;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class StencilShader : public sglr::ShaderProgram
+{
+public:
+	StencilShader (void)
+		: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+									<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+									<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+									<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+									<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
+									<< sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
+																"void main (void)\n"
+																"{\n"
+																"	gl_Position = a_position;\n"
+																"}\n")
+									<< sglr::pdec::FragmentSource("uniform mediump vec4 u_color;\n"
+																  "void main (void)\n"
+																  "{\n"
+																  "	gl_FragColor = u_color;\n"
+																  "}\n"))
+		, u_color	(getUniformByName("u_color"))
+	{
+	}
+
+	void setColor (sglr::Context& ctx, deUint32 program, const tcu::Vec4& color)
+	{
+		ctx.useProgram(program);
+		ctx.uniform4fv(ctx.getUniformLocation(program, "u_color"), 1, color.getPtr());
+	}
+
+private:
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			rr::VertexPacket& packet = *packets[packetNdx];
+
+			packet.position = rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		}
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		const tcu::Vec4 color(u_color.value.f4);
+
+		DE_UNREF(packets);
+
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+	}
+
+	const sglr::UniformSlot& u_color;
+};
+
+class StencilOp
+{
+public:
+	enum Type
+	{
+		TYPE_CLEAR_STENCIL = 0,
+		TYPE_CLEAR_DEPTH,
+		TYPE_QUAD,
+
+		TYPE_LAST
+	};
+
+	Type		type;
+	GLenum		stencilTest;
+	int			stencil;	//!< Ref for quad op, clear value for clears
+	deUint32	stencilMask;
+	GLenum		depthTest;
+	float		depth;		//!< Quad depth or clear value
+	GLenum		sFail;
+	GLenum		dFail;
+	GLenum		dPass;
+
+	StencilOp (Type type_, GLenum stencilTest_ = GL_ALWAYS, int stencil_ = 0, GLenum depthTest_ = GL_ALWAYS, float depth_ = 1.0f, GLenum sFail_ = GL_KEEP, GLenum dFail_ = GL_KEEP, GLenum dPass_ = GL_KEEP)
+		: type			(type_)
+		, stencilTest	(stencilTest_)
+		, stencil		(stencil_)
+		, stencilMask	(0xffffffffu)
+		, depthTest		(depthTest_)
+		, depth			(depth_)
+		, sFail			(sFail_)
+		, dFail			(dFail_)
+		, dPass			(dPass_)
+	{
+	}
+
+	static StencilOp clearStencil (int stencil)
+	{
+		StencilOp op(TYPE_CLEAR_STENCIL);
+		op.stencil = stencil;
+		return op;
+	}
+
+	static StencilOp clearDepth (float depth)
+	{
+		StencilOp op(TYPE_CLEAR_DEPTH);
+		op.depth = depth;
+		return op;
+	}
+
+	static StencilOp quad (GLenum stencilTest, int stencil, GLenum depthTest, float depth, GLenum sFail, GLenum dFail, GLenum dPass)
+	{
+		return StencilOp(TYPE_QUAD, stencilTest, stencil, depthTest, depth, sFail, dFail, dPass);
+	}
+};
+
+class StencilCase : public TestCase
+{
+public:
+						StencilCase			(Context& context, const char* name, const char* description);
+	virtual				~StencilCase		(void);
+
+	void				init				(void);
+	void				deinit				(void);
+	IterateResult		iterate				(void);
+
+	virtual void		genOps				(vector<StencilOp>& dst, int stencilBits, int depthBits, int targetStencil) = DE_NULL;
+
+private:
+	void				executeOps			(sglr::Context& context, const IVec4& cell, const vector<StencilOp>& ops);
+	void				visualizeStencil	(sglr::Context& context, int stencilBits, int stencilStep);
+
+	StencilShader		m_shader;
+	deUint32			m_shaderID;
+};
+
+StencilCase::StencilCase (Context& context, const char* name, const char* description)
+	: TestCase		(context, name, description)
+	, m_shaderID	(0)
+{
+}
+
+StencilCase::~StencilCase (void)
+{
+}
+
+void StencilCase::init (void)
+{
+}
+
+void StencilCase::deinit (void)
+{
+}
+
+void StencilCase::executeOps (sglr::Context& context, const IVec4& cell, const vector<StencilOp>& ops)
+{
+	// For quadOps
+	float x0 = 2.0f*((float)cell.x() / (float)context.getWidth())-1.0f;
+	float y0 = 2.0f*((float)cell.y() / (float)context.getHeight())-1.0f;
+	float x1 = x0 + 2.0f*((float)cell.z() / (float)context.getWidth());
+	float y1 = y0 + 2.0f*((float)cell.w() / (float)context.getHeight());
+
+	m_shader.setColor(context, m_shaderID, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	for (vector<StencilOp>::const_iterator i = ops.begin(); i != ops.end(); i++)
+	{
+		const StencilOp& op = *i;
+
+		switch (op.type)
+		{
+			case StencilOp::TYPE_CLEAR_DEPTH:
+				context.enable(GL_SCISSOR_TEST);
+				context.scissor(cell.x(), cell.y(), cell.z(), cell.w());
+				context.clearDepthf(op.depth);
+				context.clear(GL_DEPTH_BUFFER_BIT);
+				context.disable(GL_SCISSOR_TEST);
+				break;
+
+			case StencilOp::TYPE_CLEAR_STENCIL:
+				context.enable(GL_SCISSOR_TEST);
+				context.scissor(cell.x(), cell.y(), cell.z(), cell.w());
+				context.clearStencil(op.stencil);
+				context.clear(GL_STENCIL_BUFFER_BIT);
+				context.disable(GL_SCISSOR_TEST);
+				break;
+
+			case StencilOp::TYPE_QUAD:
+				context.enable(GL_DEPTH_TEST);
+				context.enable(GL_STENCIL_TEST);
+				context.depthFunc(op.depthTest);
+				context.stencilFunc(op.stencilTest, op.stencil, op.stencilMask);
+				context.stencilOp(op.sFail, op.dFail, op.dPass);
+				sglr::drawQuad(context, m_shaderID, Vec3(x0, y0, op.depth), Vec3(x1, y1, op.depth));
+				context.disable(GL_STENCIL_TEST);
+				context.disable(GL_DEPTH_TEST);
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+void StencilCase::visualizeStencil (sglr::Context& context, int stencilBits, int stencilStep)
+{
+	int endVal				= 1<<stencilBits;
+	int numStencilValues	= endVal/stencilStep + 1;
+
+	context.enable(GL_STENCIL_TEST);
+	context.stencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+
+	for (int ndx = 0; ndx < numStencilValues; ndx++)
+	{
+		int			value		= deMin32(ndx*stencilStep, endVal-1);
+		float		colorMix	= (float)value/(float)de::max(1, endVal-1);
+		tcu::Vec4	color		(0.0f, 1.0f-colorMix, colorMix, 1.0f);
+
+		m_shader.setColor(context, m_shaderID, color);
+		context.stencilFunc(GL_EQUAL, value, 0xffffffffu);
+		sglr::drawQuad(context, m_shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+	}
+}
+
+TestCase::IterateResult StencilCase::iterate (void)
+{
+	const tcu::RenderTarget&	renderTarget		= m_context.getRenderContext().getRenderTarget();
+	int							depthBits			= renderTarget.getDepthBits();
+	int							stencilBits			= renderTarget.getStencilBits();
+
+	int							stencilStep			= stencilBits == 8 ? 8 : 1;
+	int							numStencilValues	= (1<<stencilBits)/stencilStep + 1;
+
+	int							gridSize			= (int)deFloatCeil(deFloatSqrt((float)(numStencilValues+2)));
+
+	int							width				= deMin32(128, renderTarget.getWidth());
+	int							height				= deMin32(128, renderTarget.getHeight());
+
+	tcu::TestLog&				log					= m_testCtx.getLog();
+	de::Random					rnd					(deStringHash(m_name.c_str()));
+	int							viewportX			= rnd.getInt(0, renderTarget.getWidth()-width);
+	int							viewportY			= rnd.getInt(0, renderTarget.getHeight()-height);
+	IVec4						viewport			= IVec4(viewportX, viewportY, width, height);
+
+	tcu::Surface				gles2Frame			(width, height);
+	tcu::Surface				refFrame			(width, height);
+	GLenum						gles2Error;
+
+	const char*					failReason			= DE_NULL;
+
+	// Get ops for stencil values
+	vector<vector<StencilOp> >	ops(numStencilValues+2);
+	{
+		// Values from 0 to max
+		for (int ndx = 0; ndx < numStencilValues; ndx++)
+			genOps(ops[ndx], stencilBits, depthBits, deMin32(ndx*stencilStep, (1<<stencilBits)-1));
+
+		// -1 and max+1
+		genOps(ops[numStencilValues+0], stencilBits, depthBits, 1<<stencilBits);
+		genOps(ops[numStencilValues+1], stencilBits, depthBits, -1);
+	}
+
+	// Compute cells: (x, y, w, h)
+	vector<IVec4>				cells;
+	int							cellWidth			= width/gridSize;
+	int							cellHeight			= height/gridSize;
+	for (int y = 0; y < gridSize; y++)
+	for (int x = 0; x < gridSize; x++)
+		cells.push_back(IVec4(x*cellWidth, y*cellHeight, cellWidth, cellHeight));
+
+	DE_ASSERT(ops.size() <= cells.size());
+
+	// Execute for gles2 context
+	{
+		sglr::GLContext context(m_context.getRenderContext(), log, 0 /* don't log calls or program */, viewport);
+
+		m_shaderID = context.createProgram(&m_shader);
+
+		context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		for (int ndx = 0; ndx < (int)ops.size(); ndx++)
+			executeOps(context, cells[ndx], ops[ndx]);
+
+		visualizeStencil(context, stencilBits, stencilStep);
+
+		gles2Error = context.getError();
+		context.readPixels(gles2Frame, 0, 0, width, height);
+	}
+
+	// Execute for reference context
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), renderTarget.getDepthBits(), renderTarget.getStencilBits(), width, height);
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(m_context.getRenderContext()), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		m_shaderID = context.createProgram(&m_shader);
+
+		context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		for (int ndx = 0; ndx < (int)ops.size(); ndx++)
+			executeOps(context, cells[ndx], ops[ndx]);
+
+		visualizeStencil(context, stencilBits, stencilStep);
+
+		context.readPixels(refFrame, 0, 0, width, height);
+	}
+
+	// Check error
+	bool errorCodeOk = (gles2Error == GL_NO_ERROR);
+	if (!errorCodeOk && !failReason)
+		failReason = "Got unexpected error";
+
+	// Compare images
+	const float		threshold	= 0.02f;
+	bool			imagesOk	= tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, gles2Frame, threshold, tcu::COMPARE_LOG_RESULT);
+
+	if (!imagesOk && !failReason)
+		failReason = "Image comparison failed";
+
+	// Store test result
+	bool isOk = errorCodeOk && imagesOk;
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: failReason);
+
+	return STOP;
+}
+
+StencilTests::StencilTests (Context& context)
+	: TestCaseGroup(context, "stencil", "Stencil Tests")
+{
+}
+
+StencilTests::~StencilTests (void)
+{
+}
+
+typedef void (*GenStencilOpsFunc) (vector<StencilOp>& dst, int stencilBits, int depthBits, int targetStencil);
+
+class SimpleStencilCase : public StencilCase
+{
+public:
+	SimpleStencilCase (Context& context, const char* name, const char* description, GenStencilOpsFunc genOpsFunc)
+		: StencilCase	(context, name, description)
+		, m_genOps		(genOpsFunc)
+	{
+	}
+
+	void genOps (vector<StencilOp>& dst, int stencilBits, int depthBits, int targetStencil)
+	{
+		m_genOps(dst, stencilBits, depthBits, targetStencil);
+	}
+
+private:
+	GenStencilOpsFunc	m_genOps;
+};
+
+void StencilTests::init (void)
+{
+#define STENCIL_CASE(NAME, DESCRIPTION, GEN_OPS_BODY)														\
+	do {																									\
+		struct Gen_##NAME {																					\
+			static void genOps (vector<StencilOp>& dst, int stencilBits, int depthBits, int targetStencil)	\
+			{																								\
+				DE_UNREF(stencilBits && depthBits);															\
+				GEN_OPS_BODY																				\
+			}																								\
+		};																									\
+		addChild(new SimpleStencilCase(m_context, #NAME, DESCRIPTION, Gen_##NAME::genOps));					\
+	} while (deGetFalse());
+
+	STENCIL_CASE(clear, "Stencil clear",
+		{
+			// \note Unused bits are set to 1, clear should mask them out
+			int mask = (1<<stencilBits)-1;
+			dst.push_back(StencilOp::clearStencil(targetStencil | ~mask));
+		});
+
+	// Replace in different points
+	STENCIL_CASE(stencil_fail_replace, "Set stencil on stencil fail",
+		{
+			dst.push_back(StencilOp::quad(GL_NEVER, targetStencil, GL_ALWAYS, 0.0f, GL_REPLACE, GL_KEEP, GL_KEEP));
+		});
+	STENCIL_CASE(depth_fail_replace, "Set stencil on depth fail",
+		{
+			dst.push_back(StencilOp::clearDepth(0.0f));
+			dst.push_back(StencilOp::quad(GL_ALWAYS, targetStencil, GL_LESS, 0.5f, GL_KEEP, GL_REPLACE, GL_KEEP));
+		});
+	STENCIL_CASE(depth_pass_replace, "Set stencil on depth pass",
+		{
+			dst.push_back(StencilOp::quad(GL_ALWAYS, targetStencil, GL_LESS, 0.0f, GL_KEEP, GL_KEEP, GL_REPLACE));
+		});
+
+	// Increment, decrement
+	STENCIL_CASE(incr_stencil_fail, "Increment on stencil fail",
+		{
+			if (targetStencil > 0)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil-1));
+				dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_INCR, GL_KEEP, GL_KEEP));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(decr_stencil_fail, "Decrement on stencil fail",
+		{
+			int maxStencil = (1<<stencilBits)-1;
+			if (targetStencil < maxStencil)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil+1));
+				dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_DECR, GL_KEEP, GL_KEEP));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(incr_wrap_stencil_fail, "Increment (wrap) on stencil fail",
+		{
+			int maxStencil = (1<<stencilBits)-1;
+			dst.push_back(StencilOp::clearStencil((targetStencil-1)&maxStencil));
+			dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_INCR_WRAP, GL_KEEP, GL_KEEP));
+		});
+	STENCIL_CASE(decr_wrap_stencil_fail, "Decrement (wrap) on stencil fail",
+		{
+			int maxStencil = (1<<stencilBits)-1;
+			dst.push_back(StencilOp::clearStencil((targetStencil+1)&maxStencil));
+			dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_DECR_WRAP, GL_KEEP, GL_KEEP));
+		});
+
+	// Zero, Invert
+	STENCIL_CASE(zero_stencil_fail, "Zero on stencil fail",
+		{
+			dst.push_back(StencilOp::clearStencil(targetStencil));
+			dst.push_back(StencilOp::quad(GL_NOTEQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_ZERO, GL_KEEP, GL_KEEP));
+			dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_REPLACE, GL_KEEP, GL_KEEP));
+		});
+	STENCIL_CASE(invert_stencil_fail, "Invert on stencil fail",
+		{
+			int mask = (1<<stencilBits)-1;
+			dst.push_back(StencilOp::clearStencil((~targetStencil)&mask));
+			dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_INVERT, GL_KEEP, GL_KEEP));
+		});
+
+	// Comparison modes
+	STENCIL_CASE(cmp_equal, "Equality comparison",
+		{
+			int mask = (1<<stencilBits)-1;
+			int inv  = (~targetStencil)&mask;
+			dst.push_back(StencilOp::clearStencil(inv));
+			dst.push_back(StencilOp::quad(GL_EQUAL, inv, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INVERT));
+			dst.push_back(StencilOp::quad(GL_EQUAL, inv, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INVERT));
+		});
+	STENCIL_CASE(cmp_not_equal, "Equality comparison",
+		{
+			int mask = (1<<stencilBits)-1;
+			int inv  = (~targetStencil)&mask;
+			dst.push_back(StencilOp::clearStencil(inv));
+			dst.push_back(StencilOp::quad(GL_NOTEQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INVERT));
+			dst.push_back(StencilOp::quad(GL_NOTEQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INVERT));
+		});
+	STENCIL_CASE(cmp_less_than, "Less than comparison",
+		{
+			int maxStencil = (1<<stencilBits)-1;
+			if (targetStencil < maxStencil)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil+1));
+				dst.push_back(StencilOp::quad(GL_LESS, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_DECR));
+				dst.push_back(StencilOp::quad(GL_LESS, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_DECR));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(cmp_less_or_equal, "Less or equal comparison",
+		{
+			int maxStencil = (1<<stencilBits)-1;
+			if (targetStencil < maxStencil)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil+1));
+				dst.push_back(StencilOp::quad(GL_LEQUAL, targetStencil+1, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_DECR));
+				dst.push_back(StencilOp::quad(GL_LEQUAL, targetStencil+1, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_DECR));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(cmp_greater_than, "Greater than comparison",
+		{
+			if (targetStencil > 0)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil-1));
+				dst.push_back(StencilOp::quad(GL_GREATER, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INCR));
+				dst.push_back(StencilOp::quad(GL_GREATER, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INCR));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(cmp_greater_or_equal, "Greater or equal comparison",
+		{
+			if (targetStencil > 0)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil-1));
+				dst.push_back(StencilOp::quad(GL_GEQUAL, targetStencil-1, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INCR));
+				dst.push_back(StencilOp::quad(GL_GEQUAL, targetStencil-1, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INCR));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(cmp_mask_equal, "Equality comparison with mask",
+		{
+			int valMask = (1<<stencilBits)-1;
+			int mask	= (1<<7)|(1<<5)|(1<<3)|(1<<1);
+			dst.push_back(StencilOp::clearStencil(~targetStencil));
+			StencilOp op = StencilOp::quad(GL_EQUAL, (~targetStencil | ~mask) & valMask, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INVERT);
+			op.stencilMask = mask;
+			dst.push_back(op);
+		});
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fStencilTests.hpp b/modules/gles2/functional/es2fStencilTests.hpp
new file mode 100644
index 0000000..bb2074e
--- /dev/null
+++ b/modules/gles2/functional/es2fStencilTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FSTENCILTESTS_HPP
+#define _ES2FSTENCILTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stencil tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class StencilTests : public TestCaseGroup
+{
+public:
+						StencilTests			(Context& context);
+						~StencilTests			(void);
+
+	void				init					(void);
+
+private:
+						StencilTests			(const StencilTests& other);
+	StencilTests&		operator=				(const StencilTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSTENCILTESTS_HPP
diff --git a/modules/gles2/functional/es2fStringQueryTests.cpp b/modules/gles2/functional/es2fStringQueryTests.cpp
new file mode 100644
index 0000000..c2b315a
--- /dev/null
+++ b/modules/gles2/functional/es2fStringQueryTests.cpp
@@ -0,0 +1,139 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief String Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fStringQueryTests.hpp"
+#include "es2fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deString.h"
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+
+using namespace glw; // GLint and other GL types
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+StringQueryTests::StringQueryTests (Context& context)
+	: TestCaseGroup (context, "string", "String Query tests")
+{
+}
+
+StringQueryTests::~StringQueryTests (void)
+{
+}
+
+void StringQueryTests::init (void)
+{
+	using tcu::TestLog;
+
+	ES2F_ADD_API_CASE(renderer, "RENDERER",
+	{
+		const GLubyte* string = glGetString(GL_RENDERER);
+		if (string == NULL)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid string");
+	});
+	ES2F_ADD_API_CASE(vendor, "VENDOR",
+	{
+		const GLubyte* string = glGetString(GL_VENDOR);
+		if (string == NULL)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid string");
+	});
+	ES2F_ADD_API_CASE(version, "VERSION",
+	{
+		const char* string				= (const char*)glGetString(GL_VERSION);
+		const char	referenceString[]	= "OpenGL ES ";
+
+		if (string == NULL)
+			TCU_FAIL("Got invalid string");
+
+		if (!deStringBeginsWith(string, referenceString))
+			TCU_FAIL("Got invalid string prefix");
+
+		{
+			std::string tmpString;
+			char		versionDelimiter;
+			int			glMajor				= 0;
+			int			glMinor				= 0;
+
+			std::istringstream versionStream(string);
+			versionStream >> tmpString;			// OpenGL
+			versionStream >> tmpString;			// ES
+			versionStream >> glMajor;			// x
+			versionStream >> std::noskipws;
+			versionStream >> versionDelimiter;	// .
+			versionStream >> glMinor;			// x
+
+			if (!versionStream)
+				TCU_FAIL("Got invalid string format");
+		}
+	});
+	ES2F_ADD_API_CASE(shading_language_version, "SHADING_LANGUAGE_VERSION",
+	{
+		const char* string				= (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
+		const char	referenceString[]	= "OpenGL ES GLSL ES ";
+
+		if (string == NULL)
+			TCU_FAIL("Got invalid string");
+
+		if (!deStringBeginsWith(string, referenceString))
+			TCU_FAIL("Got invalid string prefix");
+
+		{
+			std::string tmpString;
+			char		versionDelimiter;
+			int			glslMajor			= 0;
+			int			glslMinor			= 0;
+
+			std::istringstream versionStream(string);
+			versionStream >> tmpString;			// OpenGL
+			versionStream >> tmpString;			// ES
+			versionStream >> tmpString;			// GLSL
+			versionStream >> tmpString;			// ES
+			versionStream >> glslMajor;			// x
+			versionStream >> std::noskipws;
+			versionStream >> versionDelimiter;	// .
+			versionStream >> glslMinor;			// x
+
+			if (!versionStream)
+				TCU_FAIL("Got invalid string format");
+		}
+	});
+	ES2F_ADD_API_CASE(extensions, "EXTENSIONS",
+	{
+		const char* extensions_cstring = (const char*)glGetString(GL_EXTENSIONS);
+		if (extensions_cstring == NULL)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid string");
+	});
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fStringQueryTests.hpp b/modules/gles2/functional/es2fStringQueryTests.hpp
new file mode 100644
index 0000000..0334f51
--- /dev/null
+++ b/modules/gles2/functional/es2fStringQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FSTRINGQUERYTESTS_HPP
+#define _ES2FSTRINGQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief String Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class StringQueryTests : public TestCaseGroup
+{
+public:
+							StringQueryTests				(Context& context);
+							~StringQueryTests				(void);
+
+	void					init							(void);
+
+private:
+							StringQueryTests				(const StringQueryTests& other);
+	StringQueryTests&		operator=						(const StringQueryTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FSTRINGQUERYTESTS_HPP
diff --git a/modules/gles2/functional/es2fTextureCompletenessTests.cpp b/modules/gles2/functional/es2fTextureCompletenessTests.cpp
new file mode 100644
index 0000000..a4c92db
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureCompletenessTests.cpp
@@ -0,0 +1,991 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture completeness tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fTextureCompletenessTests.hpp"
+#include "glsTextureTestUtil.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVector.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deInt32.h"
+#include "deString.h"
+
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+
+#include "glw.h"
+
+#include <cstdlib>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using tcu::TextureFormat;
+using tcu::Sampler;
+using tcu::IVec2;
+using tcu::RGBA;
+using gls::TextureTestUtil::TextureRenderer;
+using gls::TextureTestUtil::computeQuadTexCoord2D;
+using gls::TextureTestUtil::computeQuadTexCoordCube;
+using gls::TextureTestUtil::clear;
+
+static const GLenum s_cubeTargets[] =
+{
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_Z
+};
+
+static bool isExtensionSupported (const glu::ContextInfo& ctxInfo, const char* extension)
+{
+	vector<string> extensions = ctxInfo.getExtensions();
+
+	for (vector<string>::iterator iter = extensions.begin(); iter != extensions.end(); ++iter)
+		if (iter->compare(extension) == 0)
+			return true;
+
+	return false;
+}
+
+static bool compareToConstantColor (TestLog& log, const char* imageSetName, const char* imageSetDesc, const tcu::Surface& result, tcu::CompareLogMode logMode, RGBA color)
+{
+	bool isOk = true;
+
+	for (int y = 0; y < result.getHeight(); y++)
+	{
+		for (int x = 0; x < result.getWidth(); x++)
+		{
+			if (result.getPixel(x, y).getRed()		!= color.getRed()	||
+				result.getPixel(x, y).getGreen()	!= color.getGreen() ||
+				result.getPixel(x, y).getBlue()		!= color.getBlue()	||
+				result.getPixel(x, y).getAlpha()	!= color.getAlpha())
+			{
+				isOk = false;
+			}
+		}
+	}
+
+	if (!isOk || logMode == tcu::COMPARE_LOG_EVERYTHING)
+	{
+		if (!isOk)
+			log << TestLog::Message << "Image comparison failed" << TestLog::EndMessage;
+
+		log << TestLog::ImageSet(imageSetName, imageSetDesc)
+			<< TestLog::Image("Result",		"Result",		result)
+			<< TestLog::EndImageSet;
+	}
+	else if (logMode == tcu::COMPARE_LOG_RESULT)
+		log << TestLog::ImageSet(imageSetName, imageSetDesc)
+			<< TestLog::Image("Result",		"Result",		result)
+			<< TestLog::EndImageSet;
+
+	return isOk;
+}
+
+// Base classes.
+
+class Tex2DCompletenessCase : public tcu::TestCase
+{
+public:
+							Tex2DCompletenessCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description);
+							~Tex2DCompletenessCase	(void) {};
+
+	IterateResult			iterate					(void);
+
+protected:
+	virtual void			createTexture			(void) = 0;
+
+	tcu::TestContext&		m_testCtx;
+	glu::RenderContext&		m_renderCtx;
+	RGBA					m_compareColor;
+};
+
+Tex2DCompletenessCase::Tex2DCompletenessCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description)
+	: TestCase			(testCtx, name, description)
+	, m_testCtx			(testCtx)
+	, m_renderCtx		(renderCtx)
+	, m_compareColor	(RGBA(0,0,0,255))
+{
+}
+
+Tex2DCompletenessCase::IterateResult Tex2DCompletenessCase::iterate (void)
+{
+	int					viewportWidth	= de::min(64, m_renderCtx.getRenderTarget().getWidth());
+	int					viewportHeight	= de::min(64, m_renderCtx.getRenderTarget().getHeight());
+	TestLog&			log				= m_testCtx.getLog();
+	TextureRenderer		renderer		(m_renderCtx, m_testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP);
+	tcu::Surface		renderedFrame	(viewportWidth, viewportHeight);
+	vector<float>		texCoord;
+
+	de::Random			random			(deStringHash(getName()));
+	int					offsetX			= random.getInt(0, m_renderCtx.getRenderTarget().getWidth()		- viewportWidth	);
+	int					offsetY			= random.getInt(0, m_renderCtx.getRenderTarget().getHeight()	- viewportHeight);
+
+	computeQuadTexCoord2D	(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	glViewport				(offsetX, offsetY, viewportWidth, viewportHeight);
+
+	createTexture			();
+	renderer.renderQuad		(0, &texCoord[0], gls::TextureTestUtil::TEXTURETYPE_2D);
+	glu::readPixels			(m_renderCtx, offsetX, offsetY, renderedFrame.getAccess());
+
+	bool isOk = compareToConstantColor(log, "Result", "Image comparison result", renderedFrame, tcu::COMPARE_LOG_RESULT, m_compareColor);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+	return STOP;
+}
+
+class TexCubeCompletenessCase : public tcu::TestCase
+{
+public:
+							TexCubeCompletenessCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description);
+							~TexCubeCompletenessCase	(void) {};
+
+	IterateResult			iterate						(void);
+
+protected:
+	virtual void			createTexture				(void) = 0;
+
+	tcu::TestContext&		m_testCtx;
+	glu::RenderContext&		m_renderCtx;
+	RGBA					m_compareColor;
+};
+
+TexCubeCompletenessCase::TexCubeCompletenessCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description)
+	: TestCase			(testCtx, name, description)
+	, m_testCtx			(testCtx)
+	, m_renderCtx		(renderCtx)
+	, m_compareColor	(RGBA(0,0,0,255))
+{
+}
+
+TexCubeCompletenessCase::IterateResult TexCubeCompletenessCase::iterate (void)
+{
+	int					viewportWidth	= de::min(64, m_renderCtx.getRenderTarget().getWidth());
+	int					viewportHeight	= de::min(64, m_renderCtx.getRenderTarget().getHeight());
+	bool				allFacesOk		= true;
+	TestLog&			log				= m_testCtx.getLog();
+	TextureRenderer		renderer		(m_renderCtx, m_testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP);
+	tcu::Surface		renderedFrame	(viewportWidth, viewportHeight);
+	vector<float>		texCoord;
+
+	de::Random			random			(deStringHash(getName()));
+	int					offsetX			= random.getInt(0, de::max(0,m_renderCtx.getRenderTarget().getWidth()	- 64));
+	int					offsetY			= random.getInt(0, de::max(0,m_renderCtx.getRenderTarget().getHeight()	- 64));
+
+	createTexture();
+
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+	{
+		computeQuadTexCoordCube	(texCoord, (tcu::CubeFace)face);
+
+		glViewport				(offsetX, offsetY, viewportWidth, viewportHeight);
+
+		renderer.renderQuad		(0, &texCoord[0], gls::TextureTestUtil::TEXTURETYPE_CUBE);
+		glu::readPixels			(m_renderCtx, offsetX, offsetY, renderedFrame.getAccess());
+
+		bool isOk = compareToConstantColor(log, "Result", "Image comparison result", renderedFrame, tcu::COMPARE_LOG_RESULT, m_compareColor);
+
+		if (!isOk)
+			allFacesOk = false;
+	}
+
+	m_testCtx.setTestResult(allFacesOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							allFacesOk ? "Pass"					: "Image comparison failed");
+	return STOP;
+}
+
+// Texture 2D tests.
+
+class Incomplete2DSizeCase : public Tex2DCompletenessCase
+{
+public:
+								Incomplete2DSizeCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, IVec2 invalidLevelSize, int invalidLevelNdx, const glu::ContextInfo& ctxInfo);
+								~Incomplete2DSizeCase	(void) {}
+
+	virtual void				createTexture			(void);
+
+private:
+	int							m_invalidLevelNdx;
+	IVec2						m_invalidLevelSize;
+	const glu::ContextInfo&		m_ctxInfo;
+	IVec2						m_size;
+};
+
+Incomplete2DSizeCase::Incomplete2DSizeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, IVec2 invalidLevelSize, int invalidLevelNdx, const glu::ContextInfo& ctxInfo)
+	: Tex2DCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_invalidLevelNdx			(invalidLevelNdx)
+	, m_invalidLevelSize		(invalidLevelSize)
+	, m_ctxInfo					(ctxInfo)
+	, m_size					(size)
+{
+}
+
+void Incomplete2DSizeCase::createTexture (void)
+{
+	tcu::TextureFormat		fmt				= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel		levelData		(fmt);
+	TestLog&				log				= m_testCtx.getLog();
+
+	GLuint texture;
+	glGenTextures	(1, &texture);
+	glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture	(GL_TEXTURE_2D, texture);
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	int numLevels = 1 + de::max(deLog2Floor32(m_size.x()), deLog2Floor32(m_size.y()));
+
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		int	levelW = (levelNdx == m_invalidLevelNdx) ? m_invalidLevelSize.x() : de::max(1, m_size.x() >> levelNdx);
+		int	levelH = (levelNdx == m_invalidLevelNdx) ? m_invalidLevelSize.y() : de::max(1, m_size.y() >> levelNdx);
+
+		levelData.setSize(m_size.x(), m_size.y());
+		clear(levelData.getAccess(), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+		glTexImage2D(GL_TEXTURE_2D, levelNdx, GL_RGBA, levelW, levelH, 0, GL_RGBA, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+	}
+
+	GLU_CHECK_MSG("Set texturing state");
+
+	const char* extension = "GL_NV_texture_npot_2D_mipmap";
+	if (isExtensionSupported(m_ctxInfo, extension) && !deIsPowerOfTwo32(m_size.x()) && !deIsPowerOfTwo32(m_size.y()))
+	{
+		log << TestLog::Message << extension << " supported, assuming completeness test to pass." << TestLog::EndMessage;
+		m_compareColor = RGBA(0,0,255,255);
+	}
+}
+
+class Incomplete2DFormatCase : public Tex2DCompletenessCase
+{
+public:
+							Incomplete2DFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, deUint32 format, deUint32 invalidFormat, int invalidLevelNdx);
+							~Incomplete2DFormatCase	(void) {}
+
+	virtual void			createTexture			(void);
+
+private:
+	int						m_invalidLevelNdx;
+	deUint32				m_format;
+	deUint32				m_invalidFormat;
+	IVec2					m_size;
+};
+
+Incomplete2DFormatCase::Incomplete2DFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, deUint32 format, deUint32 invalidFormat, int invalidLevelNdx)
+	: Tex2DCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_invalidLevelNdx			(invalidLevelNdx)
+	, m_format					(format)
+	, m_invalidFormat			(invalidFormat)
+	, m_size					(size)
+{
+}
+
+void Incomplete2DFormatCase::createTexture (void)
+{
+	tcu::TextureFormat	fmt			= glu::mapGLTransferFormat(m_format, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel	levelData	(fmt);
+
+	GLuint texture;
+	glGenTextures	(1, &texture);
+	glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture	(GL_TEXTURE_2D, texture);
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	int numLevels = 1 + de::max(deLog2Floor32(m_size.x()), deLog2Floor32(m_size.y()));
+
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		int	levelW = de::max(1, m_size.x() >> levelNdx);
+		int	levelH = de::max(1, m_size.y() >> levelNdx);
+
+		levelData.setSize(m_size.x(), m_size.y());
+		clear(levelData.getAccess(), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+		deUint32 format = levelNdx == m_invalidLevelNdx ? m_invalidFormat : m_format;
+
+		glTexImage2D(GL_TEXTURE_2D, levelNdx, format, levelW, levelH, 0, format, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+	}
+
+	GLU_CHECK_MSG("Set texturing state");
+}
+
+class Incomplete2DMissingLevelCase : public Tex2DCompletenessCase
+{
+public:
+						Incomplete2DMissingLevelCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, int missingLevelNdx);
+						~Incomplete2DMissingLevelCase	(void) {}
+
+	virtual void		createTexture					(void);
+
+private:
+	int					m_missingLevelNdx;
+	IVec2				m_size;
+};
+
+Incomplete2DMissingLevelCase::Incomplete2DMissingLevelCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, int missingLevelNdx)
+	: Tex2DCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_missingLevelNdx			(missingLevelNdx)
+	, m_size					(size)
+{
+}
+
+void Incomplete2DMissingLevelCase::createTexture (void)
+{
+	tcu::TextureFormat	fmt			= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel	levelData	(fmt);
+
+	GLuint texture;
+	glGenTextures	(1, &texture);
+	glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture	(GL_TEXTURE_2D, texture);
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	int numLevels = 1 + de::max(deLog2Floor32(m_size.x()), deLog2Floor32(m_size.y()));
+
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		levelData.setSize(m_size.x(), m_size.y());
+		clear(levelData.getAccess(), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+		int	levelW = de::max(1, m_size.x() >> levelNdx);
+		int	levelH = de::max(1, m_size.y() >> levelNdx);
+
+		// Skip specified level.
+		if (levelNdx != m_missingLevelNdx)
+			glTexImage2D(GL_TEXTURE_2D, levelNdx, GL_RGBA, levelW, levelH, 0, GL_RGBA, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+	}
+
+	GLU_CHECK_MSG("Set texturing state");
+}
+
+class Incomplete2DWrapModeCase : public Tex2DCompletenessCase
+{
+public:
+								Incomplete2DWrapModeCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, deUint32 wrapT, deUint32 wrapS, const glu::ContextInfo& ctxInfo);
+								~Incomplete2DWrapModeCase	(void) {}
+
+	virtual void				createTexture				(void);
+
+private:
+	deUint32					m_wrapT;
+	deUint32					m_wrapS;
+	const glu::ContextInfo&		m_ctxInfo;
+	IVec2						m_size;
+};
+
+Incomplete2DWrapModeCase::Incomplete2DWrapModeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, deUint32 wrapT, deUint32 wrapS, const glu::ContextInfo& ctxInfo)
+	: Tex2DCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_wrapT					(wrapT)
+	, m_wrapS					(wrapS)
+	, m_ctxInfo					(ctxInfo)
+	, m_size					(size)
+{
+}
+
+void Incomplete2DWrapModeCase::createTexture (void)
+{
+	TestLog&			log			= m_testCtx.getLog();
+	tcu::TextureFormat	fmt			= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel	levelData	(fmt);
+
+	GLuint texture;
+	glGenTextures(1, &texture);
+	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture(GL_TEXTURE_2D, texture);
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	levelData.setSize(m_size.x(), m_size.y());
+	clear(levelData.getAccess(), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_size.x(), m_size.y(), 0, GL_RGBA, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+
+	GLU_CHECK_MSG("Set texturing state");
+
+	const char* extension = "GL_OES_texture_npot";
+	if (isExtensionSupported(m_ctxInfo, extension))
+	{
+		log << TestLog::Message << extension << " supported, assuming completeness test to pass." << TestLog::EndMessage;
+		m_compareColor = RGBA(0,0,255,255);
+	}
+}
+
+class Complete2DExtraLevelCase : public Tex2DCompletenessCase
+{
+public:
+						Complete2DExtraLevelCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size);
+						~Complete2DExtraLevelCase	(void) {}
+
+	virtual void		createTexture				(void);
+
+private:
+	IVec2				m_size;
+};
+
+Complete2DExtraLevelCase::Complete2DExtraLevelCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size)
+	: Tex2DCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_size					(size)
+{
+}
+
+void Complete2DExtraLevelCase::createTexture (void)
+{
+	tcu::TextureFormat	fmt			= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel	levelData	(fmt);
+
+	GLuint texture;
+	glGenTextures	(1, &texture);
+	glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture	(GL_TEXTURE_2D, texture);
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	int numLevels = 1 + de::max(deLog2Floor32(m_size.x()), deLog2Floor32(m_size.y()));
+
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		int	levelW = de::max(1, m_size.x() >> levelNdx);
+		int	levelH = de::max(1, m_size.y() >> levelNdx);
+
+		levelData.setSize(m_size.x(), m_size.y());
+		clear(levelData.getAccess(), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+		glTexImage2D(GL_TEXTURE_2D, levelNdx, GL_RGBA, levelW, levelH, 0, GL_RGBA, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+	}
+
+	// Specify extra level.
+	glTexImage2D(GL_TEXTURE_2D, numLevels+1, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+	m_compareColor = RGBA(0,0,255,255);
+
+	GLU_CHECK_MSG("Set texturing state");
+}
+
+class Incomplete2DEmptyObjectCase : public Tex2DCompletenessCase
+{
+public:
+						Incomplete2DEmptyObjectCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size);
+						~Incomplete2DEmptyObjectCase	(void) {}
+
+	virtual void		createTexture					(void);
+
+private:
+	IVec2				m_size;
+};
+
+Incomplete2DEmptyObjectCase::Incomplete2DEmptyObjectCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size)
+	: Tex2DCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_size					(size)
+{
+}
+
+void Incomplete2DEmptyObjectCase::createTexture (void)
+{
+	tcu::TextureFormat	fmt			= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel	levelData	(fmt);
+
+	GLuint texture;
+	glGenTextures	(1, &texture);
+	glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture	(GL_TEXTURE_2D, texture);
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	GLU_CHECK_MSG("Set texturing state");
+}
+
+// Cube texture tests.
+
+class IncompleteCubeSizeCase : public TexCubeCompletenessCase
+{
+public:
+							IncompleteCubeSizeCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, IVec2 invalidLevelSize, int invalidLevelNdx);
+							IncompleteCubeSizeCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, IVec2 invalidLevelSize, int invalidLevelNdx, tcu::CubeFace invalidCubeFace);
+							~IncompleteCubeSizeCase	(void) {}
+
+	virtual void			createTexture			(void);
+
+private:
+	int						m_invalidLevelNdx;
+	IVec2					m_invalidLevelSize;
+	tcu::CubeFace			m_invalidCubeFace;
+	IVec2					m_size;
+};
+
+IncompleteCubeSizeCase::IncompleteCubeSizeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, IVec2 invalidLevelSize, int invalidLevelNdx)
+	: TexCubeCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_invalidLevelNdx				(invalidLevelNdx)
+	, m_invalidLevelSize			(invalidLevelSize)
+	, m_invalidCubeFace				(tcu::CUBEFACE_LAST)
+	, m_size						(size)
+{
+}
+
+IncompleteCubeSizeCase::IncompleteCubeSizeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, IVec2 invalidLevelSize, int invalidLevelNdx, tcu::CubeFace invalidCubeFace)
+	: TexCubeCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_invalidLevelNdx				(invalidLevelNdx)
+	, m_invalidLevelSize			(invalidLevelSize)
+	, m_invalidCubeFace				(invalidCubeFace)
+	, m_size						(size)
+{
+}
+
+void IncompleteCubeSizeCase::createTexture (void)
+{
+	tcu::TextureFormat	fmt			= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel	levelData	(fmt);
+
+	GLuint texture;
+	glGenTextures	(1, &texture);
+	glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture	(GL_TEXTURE_CUBE_MAP, texture);
+
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	int numLevels = 1 + de::max(deLog2Floor32(m_size.x()), deLog2Floor32(m_size.y()));
+
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		levelData.setSize(m_size.x(), m_size.y());
+		clear(levelData.getAccess(), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(s_cubeTargets); targetNdx++)
+		{
+			int levelW = de::max(1, m_size.x() >> levelNdx);
+			int levelH = de::max(1, m_size.y() >> levelNdx);
+			if (levelNdx == m_invalidLevelNdx && (m_invalidCubeFace == tcu::CUBEFACE_LAST || m_invalidCubeFace == targetNdx))
+			{
+				levelW =  m_invalidLevelSize.x();
+				levelH =  m_invalidLevelSize.y();
+			}
+			glTexImage2D(s_cubeTargets[targetNdx], levelNdx, GL_RGBA, levelW, levelH, 0, GL_RGBA, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	GLU_CHECK_MSG("Set texturing state");
+}
+
+class IncompleteCubeFormatCase : public TexCubeCompletenessCase
+{
+public:
+							IncompleteCubeFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, deUint32 format, deUint32 invalidFormat);
+							IncompleteCubeFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, deUint32 format, deUint32 invalidFormat, tcu::CubeFace invalidCubeFace);
+							~IncompleteCubeFormatCase	(void) {}
+
+	virtual void			createTexture				(void);
+
+private:
+	deUint32				m_format;
+	deUint32				m_invalidFormat;
+	tcu::CubeFace			m_invalidCubeFace;
+	IVec2					m_size;
+};
+
+IncompleteCubeFormatCase::IncompleteCubeFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, deUint32 format, deUint32 invalidFormat)
+	: TexCubeCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_format						(format)
+	, m_invalidFormat				(invalidFormat)
+	, m_invalidCubeFace				(tcu::CUBEFACE_LAST)
+	, m_size						(size)
+{
+}
+
+IncompleteCubeFormatCase::IncompleteCubeFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, deUint32 format, deUint32 invalidFormat, tcu::CubeFace invalidCubeFace)
+	: TexCubeCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_format						(format)
+	, m_invalidFormat				(invalidFormat)
+	, m_invalidCubeFace				(invalidCubeFace)
+	, m_size						(size)
+{
+}
+
+void IncompleteCubeFormatCase::createTexture (void)
+{
+	tcu::TextureFormat	fmt			= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel	levelData	(fmt);
+
+	GLuint texture;
+	glGenTextures	(1, &texture);
+	glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture	(GL_TEXTURE_CUBE_MAP, texture);
+
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	int numLevels = 1 + de::max(deLog2Floor32(m_size.x()), deLog2Floor32(m_size.y()));
+
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		levelData.setSize(m_size.x(), m_size.y());
+		clear(levelData.getAccess(), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+		int	levelW = de::max(1, m_size.x() >> levelNdx);
+		int	levelH = de::max(1, m_size.y() >> levelNdx);
+
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(s_cubeTargets); targetNdx++)
+		{
+			deUint32 format = m_format;
+			if (levelNdx == 0 && (m_invalidCubeFace == tcu::CUBEFACE_LAST || m_invalidCubeFace == targetNdx))
+				format = m_invalidFormat;
+
+			glTexImage2D(s_cubeTargets[targetNdx], levelNdx, format, levelW, levelH, 0, format, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	GLU_CHECK_MSG("Set texturing state");
+}
+
+class IncompleteCubeMissingLevelCase : public TexCubeCompletenessCase
+{
+public:
+							IncompleteCubeMissingLevelCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, int invalidLevelNdx);
+							IncompleteCubeMissingLevelCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, int invalidLevelNdx, tcu::CubeFace invalidCubeFace);
+							~IncompleteCubeMissingLevelCase	(void) {}
+
+	virtual void			createTexture					(void);
+
+private:
+	int						m_invalidLevelNdx;
+	tcu::CubeFace			m_invalidCubeFace;
+	IVec2					m_size;
+};
+
+IncompleteCubeMissingLevelCase::IncompleteCubeMissingLevelCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, int invalidLevelNdx)
+	: TexCubeCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_invalidLevelNdx				(invalidLevelNdx)
+	, m_invalidCubeFace				(tcu::CUBEFACE_LAST)
+	, m_size						(size)
+{
+}
+
+IncompleteCubeMissingLevelCase::IncompleteCubeMissingLevelCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, int invalidLevelNdx, tcu::CubeFace invalidCubeFace)
+	: TexCubeCompletenessCase		(testCtx, renderCtx, name, description)
+	, m_invalidLevelNdx				(invalidLevelNdx)
+	, m_invalidCubeFace				(invalidCubeFace)
+	, m_size						(size)
+{
+}
+
+void IncompleteCubeMissingLevelCase::createTexture (void)
+{
+	tcu::TextureFormat	fmt			= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel	levelData	(fmt);
+
+	GLuint texture;
+	glGenTextures	(1, &texture);
+	glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture	(GL_TEXTURE_CUBE_MAP, texture);
+
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+	glTexParameteri	(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	int numLevels = 1 + de::max(deLog2Floor32(m_size.x()), deLog2Floor32(m_size.y()));
+
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		levelData.setSize(m_size.x(), m_size.y());
+		clear(levelData.getAccess(), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+		int	levelW = (levelNdx == m_invalidLevelNdx) ? m_size.x() : de::max(1, m_size.x() >> levelNdx);
+		int	levelH = (levelNdx == m_invalidLevelNdx) ? m_size.y() : de::max(1, m_size.y() >> levelNdx);
+
+		if (levelNdx != m_invalidLevelNdx || m_invalidCubeFace != tcu::CUBEFACE_LAST)
+		{
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(s_cubeTargets); targetNdx++)
+			{
+				// If single cubeface is specified then skip only that one.
+				if (m_invalidCubeFace != targetNdx)
+					glTexImage2D(s_cubeTargets[targetNdx], levelNdx, GL_RGBA, levelW, levelH, 0, GL_RGBA, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+			}
+		}
+	}
+
+	GLU_CHECK_MSG("Set texturing state");
+}
+
+class IncompleteCubeWrapModeCase : public TexCubeCompletenessCase
+{
+public:
+								IncompleteCubeWrapModeCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, deUint32 wrapT, deUint32 wrapS, const glu::ContextInfo& ctxInfo);
+								~IncompleteCubeWrapModeCase	(void) {}
+
+	virtual void				createTexture				(void);
+
+private:
+	deUint32					m_wrapT;
+	deUint32					m_wrapS;
+	const glu::ContextInfo&		m_ctxInfo;
+	IVec2						m_size;
+};
+
+IncompleteCubeWrapModeCase::IncompleteCubeWrapModeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size, deUint32 wrapT, deUint32 wrapS, const glu::ContextInfo& ctxInfo)
+	: TexCubeCompletenessCase	(testCtx, renderCtx, name, description)
+	, m_wrapT					(wrapT)
+	, m_wrapS					(wrapS)
+	, m_ctxInfo					(ctxInfo)
+	, m_size					(size)
+{
+}
+
+void IncompleteCubeWrapModeCase::createTexture (void)
+{
+	TestLog&			log			= m_testCtx.getLog();
+	tcu::TextureFormat	fmt			= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel	levelData	(fmt);
+
+	GLuint texture;
+	glGenTextures(1, &texture);
+	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture(GL_TEXTURE_2D, texture);
+
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	levelData.setSize(m_size.x(), m_size.y());
+	clear(levelData.getAccess(), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+	for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(s_cubeTargets); targetNdx++)
+		glTexImage2D(s_cubeTargets[targetNdx], 0, GL_RGBA, m_size.x(), m_size.y(), 0, GL_RGBA, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+
+	GLU_CHECK_MSG("Set texturing state");
+
+	const char* extension = "GL_OES_texture_npot";
+	if (isExtensionSupported(m_ctxInfo, extension))
+	{
+		log << TestLog::Message << extension << " supported, assuming completeness test to pass." << TestLog::EndMessage;
+		m_compareColor = RGBA(0,0,255,255);
+	}
+}
+
+class CompleteCubeExtraLevelCase : public TexCubeCompletenessCase
+{
+public:
+						CompleteCubeExtraLevelCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size);
+						~CompleteCubeExtraLevelCase	(void) {}
+
+	virtual void		createTexture				(void);
+
+private:
+	IVec2				m_size;
+};
+
+CompleteCubeExtraLevelCase::CompleteCubeExtraLevelCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size)
+	: TexCubeCompletenessCase	(testCtx, renderCtx, name, description)
+	, m_size					(size)
+{
+}
+
+void CompleteCubeExtraLevelCase::createTexture (void)
+{
+	tcu::TextureFormat		fmt				= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel		levelData		(fmt);
+
+	GLuint texture;
+	glGenTextures	(1, &texture);
+	glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture	(GL_TEXTURE_2D, texture);
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	int numLevels = 1 + de::max(deLog2Floor32(m_size.x()), deLog2Floor32(m_size.y()));
+
+	levelData.setSize(m_size.x(), m_size.y());
+	clear(levelData.getAccess(), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		int	levelW = de::max(1, m_size.x() >> levelNdx);
+		int	levelH = de::max(1, m_size.y() >> levelNdx);
+
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(s_cubeTargets); targetNdx++)
+			glTexImage2D(s_cubeTargets[targetNdx], levelNdx, GL_RGBA, levelW, levelH, 0, GL_RGBA, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+	}
+
+	// Specify extra level.
+	for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(s_cubeTargets); targetNdx++)
+		glTexImage2D(s_cubeTargets[targetNdx], numLevels+1, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, levelData.getAccess().getDataPtr());
+
+	m_compareColor = RGBA(0,0,255,255);
+
+	GLU_CHECK_MSG("Set texturing state");
+}
+
+class IncompleteCubeEmptyObjectCase : public TexCubeCompletenessCase
+{
+public:
+							IncompleteCubeEmptyObjectCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size);
+							~IncompleteCubeEmptyObjectCase	(void) {}
+
+	virtual void			createTexture				(void);
+
+private:
+	IVec2					m_size;
+};
+
+IncompleteCubeEmptyObjectCase::IncompleteCubeEmptyObjectCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, IVec2 size)
+	: TexCubeCompletenessCase	(testCtx, renderCtx, name, description)
+	, m_size					(size)
+{
+}
+
+void IncompleteCubeEmptyObjectCase::createTexture (void)
+{
+	tcu::TextureFormat		fmt				= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+	tcu::TextureLevel		levelData		(fmt);
+
+	GLuint texture;
+	glGenTextures	(1, &texture);
+	glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+	glBindTexture	(GL_TEXTURE_2D, texture);
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	GLU_CHECK_MSG("Set texturing state");
+}
+
+// Texture completeness group.
+
+TextureCompletenessTests::TextureCompletenessTests (Context& context)
+	: TestCaseGroup(context, "completeness", "Completeness tests")
+{
+}
+
+void TextureCompletenessTests::init (void)
+{
+	tcu::TestCaseGroup* tex2d = new tcu::TestCaseGroup(m_testCtx, "2d", "2D completeness");
+	addChild(tex2d);
+	tcu::TestCaseGroup* cube = new tcu::TestCaseGroup(m_testCtx, "cube", "Cubemap completeness");
+	addChild(cube);
+
+	// Texture 2D size.
+	tex2d->addChild(new Incomplete2DSizeCase(m_testCtx, m_context.getRenderContext(), "npot_size",				"", IVec2(255, 255), IVec2(255, 255), 0, m_context.getContextInfo()));
+	tex2d->addChild(new Incomplete2DSizeCase(m_testCtx, m_context.getRenderContext(), "npot_size_level_0",		"", IVec2(256, 256), IVec2(255, 255), 0, m_context.getContextInfo()));
+	tex2d->addChild(new Incomplete2DSizeCase(m_testCtx, m_context.getRenderContext(), "npot_size_level_1",		"", IVec2(256, 256), IVec2(127, 127), 1, m_context.getContextInfo()));
+	tex2d->addChild(new Incomplete2DSizeCase(m_testCtx, m_context.getRenderContext(), "not_positive_level_0",	"", IVec2(256, 256), IVec2(0, 0),	  0, m_context.getContextInfo()));
+	// Texture 2D format.
+	tex2d->addChild(new Incomplete2DFormatCase(m_testCtx, m_context.getRenderContext(), "format_mismatch_rgb_rgba",						"", IVec2(128, 128), GL_RGB,				GL_RGBA,			1));
+	tex2d->addChild(new Incomplete2DFormatCase(m_testCtx, m_context.getRenderContext(), "format_mismatch_rgba_rgb",						"", IVec2(128, 128), GL_RGBA,				GL_RGB,				1));
+	tex2d->addChild(new Incomplete2DFormatCase(m_testCtx, m_context.getRenderContext(), "format_mismatch_luminance_luminance_alpha",	"", IVec2(128, 128), GL_LUMINANCE,			GL_LUMINANCE_ALPHA,	1));
+	tex2d->addChild(new Incomplete2DFormatCase(m_testCtx, m_context.getRenderContext(), "format_mismatch_luminance_alpha_luminance",	"", IVec2(128, 128), GL_LUMINANCE_ALPHA,	GL_LUMINANCE,		1));
+	// Texture 2D missing level.
+	tex2d->addChild(new Incomplete2DMissingLevelCase(m_testCtx, m_context.getRenderContext(), "missing_level_1",			"", IVec2(128, 128),	1));
+	tex2d->addChild(new Incomplete2DMissingLevelCase(m_testCtx, m_context.getRenderContext(), "missing_level_3",			"", IVec2(128, 128),	3));
+	tex2d->addChild(new Incomplete2DMissingLevelCase(m_testCtx, m_context.getRenderContext(), "last_level_missing",			"", IVec2(128, 64),		de::max(deLog2Floor32(128), deLog2Floor32(64))));
+	// Texture 2D wrap modes.
+	tex2d->addChild(new Incomplete2DWrapModeCase(m_testCtx, m_context.getRenderContext(), "npot_t_repeat",			"", IVec2(127, 127), GL_CLAMP_TO_EDGE,		GL_REPEAT,				m_context.getContextInfo()));
+	tex2d->addChild(new Incomplete2DWrapModeCase(m_testCtx, m_context.getRenderContext(), "npot_s_repeat",			"", IVec2(127, 127), GL_REPEAT,				GL_CLAMP_TO_EDGE,		m_context.getContextInfo()));
+	tex2d->addChild(new Incomplete2DWrapModeCase(m_testCtx, m_context.getRenderContext(), "npot_all_repeat",		"", IVec2(127, 127), GL_REPEAT,				GL_REPEAT,				m_context.getContextInfo()));
+	tex2d->addChild(new Incomplete2DWrapModeCase(m_testCtx, m_context.getRenderContext(), "npot_mirrored_repeat",	"", IVec2(127, 127), GL_MIRRORED_REPEAT,	GL_MIRRORED_REPEAT,		m_context.getContextInfo()));
+	tex2d->addChild(new Incomplete2DWrapModeCase(m_testCtx, m_context.getRenderContext(), "repeat_width_npot",		"", IVec2(127, 128), GL_REPEAT,				GL_REPEAT,				m_context.getContextInfo()));
+	tex2d->addChild(new Incomplete2DWrapModeCase(m_testCtx, m_context.getRenderContext(), "repeat_height_npot",		"", IVec2(128, 127), GL_REPEAT,				GL_REPEAT,				m_context.getContextInfo()));
+	// Texture 2D extra level.
+	tex2d->addChild(new Complete2DExtraLevelCase(m_testCtx, m_context.getRenderContext(), "extra_level", "", IVec2(64, 64)));
+	// Texture 2D empty object.
+	tex2d->addChild(new Incomplete2DEmptyObjectCase(m_testCtx, m_context.getRenderContext(), "empty_object", "", IVec2(64, 64)));
+
+	// Cube size.
+	cube->addChild(new IncompleteCubeSizeCase(m_testCtx, m_context.getRenderContext(), "npot_size_level_0",			"", IVec2(64, 64), IVec2(63, 63), 0));
+	cube->addChild(new IncompleteCubeSizeCase(m_testCtx, m_context.getRenderContext(), "npot_size_level_1",			"", IVec2(64, 64), IVec2(31, 31), 1));
+	cube->addChild(new IncompleteCubeSizeCase(m_testCtx, m_context.getRenderContext(), "npot_size_level_0_pos_x",	"", IVec2(64, 64), IVec2(63, 63), 0, tcu::CUBEFACE_POSITIVE_X));
+	cube->addChild(new IncompleteCubeSizeCase(m_testCtx, m_context.getRenderContext(), "npot_size_level_1_neg_x",	"", IVec2(64, 64), IVec2(31, 31), 1, tcu::CUBEFACE_NEGATIVE_X));
+	cube->addChild(new IncompleteCubeSizeCase(m_testCtx, m_context.getRenderContext(), "not_positive_level_0",		"", IVec2(64, 64), IVec2(0,0)	, 0));
+	// Cube format.
+	cube->addChild(new IncompleteCubeFormatCase(m_testCtx, m_context.getRenderContext(), "format_mismatch_rgb_rgba_level_0",					"", IVec2(64, 64), GL_RGB,				GL_RGBA));
+	cube->addChild(new IncompleteCubeFormatCase(m_testCtx, m_context.getRenderContext(), "format_mismatch_rgba_rgb_level_0",					"", IVec2(64, 64), GL_RGBA,				GL_RGB));
+	cube->addChild(new IncompleteCubeFormatCase(m_testCtx, m_context.getRenderContext(), "format_mismatch_luminance_luminance_alpha_level_0",	"", IVec2(64, 64), GL_LUMINANCE,		GL_LUMINANCE_ALPHA));
+	cube->addChild(new IncompleteCubeFormatCase(m_testCtx, m_context.getRenderContext(), "format_mismatch_luminance_alpha_luminance_level_0",	"", IVec2(64, 64), GL_LUMINANCE_ALPHA,	GL_LUMINANCE));
+	cube->addChild(new IncompleteCubeFormatCase(m_testCtx, m_context.getRenderContext(), "format_mismatch_rgb_rgba_level_0_pos_z",				"", IVec2(64, 64), GL_RGB,				GL_RGBA,	tcu::CUBEFACE_POSITIVE_Z));
+	cube->addChild(new IncompleteCubeFormatCase(m_testCtx, m_context.getRenderContext(), "format_mismatch_rgba_rgb_level_0_neg_z",				"", IVec2(64, 64), GL_RGBA,				GL_RGB,		tcu::CUBEFACE_NEGATIVE_Z));
+	// Cube missing level.
+	cube->addChild(new IncompleteCubeMissingLevelCase(m_testCtx, m_context.getRenderContext(), "missing_level_1",		"", IVec2(64, 64), 1));
+	cube->addChild(new IncompleteCubeMissingLevelCase(m_testCtx, m_context.getRenderContext(), "missing_level_3",		"", IVec2(64, 64), 3));
+	cube->addChild(new IncompleteCubeMissingLevelCase(m_testCtx, m_context.getRenderContext(), "missing_level_1_pos_y",	"", IVec2(64, 64), 1, tcu::CUBEFACE_POSITIVE_Y));
+	cube->addChild(new IncompleteCubeMissingLevelCase(m_testCtx, m_context.getRenderContext(), "missing_level_3_neg_y",	"", IVec2(64, 64), 3, tcu::CUBEFACE_NEGATIVE_Y));
+	// Cube wrap modes.
+	cube->addChild(new IncompleteCubeWrapModeCase(m_testCtx, m_context.getRenderContext(), "npot_t_repeat",			"", IVec2(127, 127), GL_CLAMP_TO_EDGE,		GL_REPEAT,				m_context.getContextInfo()));
+	cube->addChild(new IncompleteCubeWrapModeCase(m_testCtx, m_context.getRenderContext(), "npot_s_repeat",			"", IVec2(127, 127), GL_REPEAT,				GL_CLAMP_TO_EDGE,		m_context.getContextInfo()));
+	cube->addChild(new IncompleteCubeWrapModeCase(m_testCtx, m_context.getRenderContext(), "npot_all_repeat",		"", IVec2(127, 127), GL_REPEAT,				GL_REPEAT,				m_context.getContextInfo()));
+	cube->addChild(new IncompleteCubeWrapModeCase(m_testCtx, m_context.getRenderContext(), "npot_mirrored_repeat",	"", IVec2(127, 127), GL_MIRRORED_REPEAT,	GL_MIRRORED_REPEAT,		m_context.getContextInfo()));
+	// Cube extra level.
+	cube->addChild(new CompleteCubeExtraLevelCase(m_testCtx, m_context.getRenderContext(), "extra_level", "", IVec2(64, 64)));
+	// Cube extra level.
+	cube->addChild(new IncompleteCubeEmptyObjectCase(m_testCtx, m_context.getRenderContext(), "empty_object", "", IVec2(64, 64)));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fTextureCompletenessTests.hpp b/modules/gles2/functional/es2fTextureCompletenessTests.hpp
new file mode 100644
index 0000000..9d22eb2
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureCompletenessTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FTEXTURECOMPLETENESSTESTS_HPP
+#define _ES2FTEXTURECOMPLETENESSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture completness tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class TextureCompletenessTests : public TestCaseGroup
+{
+public:
+								TextureCompletenessTests	(Context& context);
+								~TextureCompletenessTests	(void) {}
+
+	void						init						(void);
+
+private:
+								TextureCompletenessTests	(const TextureCompletenessTests& other);
+	TextureCompletenessTests&	operator=					(const TextureCompletenessTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FTEXTURECOMPLETENESSTESTS_HPP
diff --git a/modules/gles2/functional/es2fTextureFilteringTests.cpp b/modules/gles2/functional/es2fTextureFilteringTests.cpp
new file mode 100644
index 0000000..784faae
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureFilteringTests.cpp
@@ -0,0 +1,817 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fTextureFilteringTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuTexLookupVerifier.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deStringUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using tcu::Sampler;
+using namespace glu;
+using namespace gls::TextureTestUtil;
+
+enum
+{
+	VIEWPORT_WIDTH		= 64,
+	VIEWPORT_HEIGHT		= 64,
+	MIN_VIEWPORT_WIDTH	= 64,
+	MIN_VIEWPORT_HEIGHT	= 64
+};
+
+class Texture2DFilteringCase : public tcu::TestCase
+{
+public:
+									Texture2DFilteringCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, deUint32 dataType, int width, int height);
+									Texture2DFilteringCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames);
+									~Texture2DFilteringCase		(void);
+
+	void							init						(void);
+	void							deinit						(void);
+	IterateResult					iterate						(void);
+
+private:
+									Texture2DFilteringCase		(const Texture2DFilteringCase& other);
+	Texture2DFilteringCase&			operator=					(const Texture2DFilteringCase& other);
+
+	glu::RenderContext&				m_renderCtx;
+	const glu::ContextInfo&			m_renderCtxInfo;
+
+	const deUint32					m_minFilter;
+	const deUint32					m_magFilter;
+	const deUint32					m_wrapS;
+	const deUint32					m_wrapT;
+
+	const deUint32					m_format;
+	const deUint32					m_dataType;
+	const int						m_width;
+	const int						m_height;
+
+	const std::vector<std::string>	m_filenames;
+
+	struct FilterCase
+	{
+		const glu::Texture2D*	texture;
+		tcu::Vec2				minCoord;
+		tcu::Vec2				maxCoord;
+
+		FilterCase (void)
+			: texture(DE_NULL)
+		{
+		}
+
+		FilterCase (const glu::Texture2D* tex_, const tcu::Vec2& minCoord_, const tcu::Vec2& maxCoord_)
+			: texture	(tex_)
+			, minCoord	(minCoord_)
+			, maxCoord	(maxCoord_)
+		{
+		}
+	};
+
+	std::vector<glu::Texture2D*>	m_textures;
+	std::vector<FilterCase>			m_cases;
+
+	TextureRenderer					m_renderer;
+
+	int								m_caseNdx;
+};
+
+Texture2DFilteringCase::Texture2DFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, deUint32 dataType, int width, int height)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(ctxInfo)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_width			(width)
+	, m_height			(height)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+	, m_caseNdx			(0)
+{
+}
+
+Texture2DFilteringCase::Texture2DFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(ctxInfo)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(GL_NONE)
+	, m_dataType		(GL_NONE)
+	, m_width			(0)
+	, m_height			(0)
+	, m_filenames		(filenames)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+	, m_caseNdx			(0)
+{
+}
+
+Texture2DFilteringCase::~Texture2DFilteringCase (void)
+{
+	deinit();
+}
+
+void Texture2DFilteringCase::init (void)
+{
+	try
+	{
+		if (!m_filenames.empty())
+		{
+			m_textures.reserve(1);
+			m_textures.push_back(glu::Texture2D::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size(), m_filenames));
+		}
+		else
+		{
+			// Create 2 textures.
+			m_textures.reserve(2);
+			for (int ndx = 0; ndx < 2; ndx++)
+				m_textures.push_back(new glu::Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height));
+
+			bool					mipmaps		= deIsPowerOfTwo32(m_width) && deIsPowerOfTwo32(m_height);
+			int						numLevels	= mipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;
+			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+			tcu::Vec4				cBias		= fmtInfo.valueMin;
+			tcu::Vec4				cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+			// Fill first gradient texture.
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				tcu::Vec4 gMin = tcu::Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
+				tcu::Vec4 gMax = tcu::Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;
+
+				m_textures[0]->getRefTexture().allocLevel(levelNdx);
+				tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
+			}
+
+			// Fill second with grid texture.
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				deUint32	step	= 0x00ffffff / numLevels;
+				deUint32	rgb		= step*levelNdx;
+				deUint32	colorA	= 0xff000000 | rgb;
+				deUint32	colorB	= 0xff000000 | ~rgb;
+
+				m_textures[1]->getRefTexture().allocLevel(levelNdx);
+				tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+			}
+
+			// Upload.
+			for (std::vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+				(*i)->upload();
+		}
+
+		// Compute cases.
+		{
+			const struct
+			{
+				int		texNdx;
+				float	lodX;
+				float	lodY;
+				float	oX;
+				float	oY;
+			} cases[] =
+			{
+				{ 0,	1.6f,	2.9f,	-1.0f,	-2.7f	},
+				{ 0,	-2.0f,	-1.35f,	-0.2f,	0.7f	},
+				{ 1,	0.14f,	0.275f,	-1.5f,	-1.1f	},
+				{ 1,	-0.92f,	-2.64f,	0.4f,	-0.1f	},
+			};
+
+			const float	viewportW	= (float)de::min<int>(VIEWPORT_WIDTH, m_renderCtx.getRenderTarget().getWidth());
+			const float	viewportH	= (float)de::min<int>(VIEWPORT_HEIGHT, m_renderCtx.getRenderTarget().getHeight());
+
+			for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+			{
+				const int	texNdx	= de::clamp(cases[caseNdx].texNdx, 0, (int)m_textures.size()-1);
+				const float	lodX	= cases[caseNdx].lodX;
+				const float	lodY	= cases[caseNdx].lodY;
+				const float	oX		= cases[caseNdx].oX;
+				const float	oY		= cases[caseNdx].oY;
+				const float	sX		= deFloatExp2(lodX)*viewportW / float(m_textures[texNdx]->getRefTexture().getWidth());
+				const float	sY		= deFloatExp2(lodY)*viewportH / float(m_textures[texNdx]->getRefTexture().getHeight());
+
+				m_cases.push_back(FilterCase(m_textures[texNdx], tcu::Vec2(oX, oY), tcu::Vec2(oX+sX, oY+sY)));
+			}
+		}
+
+		m_caseNdx = 0;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	catch (...)
+	{
+		// Clean up to save memory.
+		Texture2DFilteringCase::deinit();
+		throw;
+	}
+}
+
+void Texture2DFilteringCase::deinit (void)
+{
+	for (std::vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	m_renderer.clear();
+	m_cases.clear();
+}
+
+Texture2DFilteringCase::IterateResult Texture2DFilteringCase::iterate (void)
+{
+	const glw::Functions&			gl			= m_renderCtx.getFunctions();
+	const RandomViewport			viewport	(m_renderCtx.getRenderTarget(), VIEWPORT_WIDTH, VIEWPORT_HEIGHT, deStringHash(getName()) ^ deInt32Hash(m_caseNdx));
+	const tcu::TextureFormat		texFmt		= m_textures[0]->getRefTexture().getFormat();
+	const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFmt);
+	const FilterCase&				curCase		= m_cases[m_caseNdx];
+	const tcu::ScopedLogSection		section		(m_testCtx.getLog(), string("Test") + de::toString(m_caseNdx), string("Test ") + de::toString(m_caseNdx));
+	ReferenceParams					refParams	(TEXTURETYPE_2D);
+	tcu::Surface					rendered	(viewport.width, viewport.height);
+	vector<float>					texCoord;
+
+	if (viewport.width < MIN_VIEWPORT_WIDTH || viewport.height < MIN_VIEWPORT_HEIGHT)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Setup params for reference.
+	refParams.sampler		= mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	refParams.samplerType	= getSamplerType(texFmt);
+	refParams.lodMode		= LODMODE_EXACT;
+	refParams.colorBias		= fmtInfo.lookupBias;
+	refParams.colorScale	= fmtInfo.lookupScale;
+
+	// Compute texture coordinates.
+	m_testCtx.getLog() << TestLog::Message << "Texture coordinates: " << curCase.minCoord << " -> " << curCase.maxCoord << TestLog::EndMessage;
+	computeQuadTexCoord2D(texCoord, curCase.minCoord, curCase.maxCoord);
+
+	gl.bindTexture	(GL_TEXTURE_2D, curCase.texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+	m_renderer.renderQuad(0, &texCoord[0], refParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, rendered.getAccess());
+
+	{
+		const bool				isNearestOnly	= m_minFilter == GL_NEAREST && m_magFilter == GL_NEAREST;
+		const tcu::PixelFormat	pixelFormat		= m_renderCtx.getRenderTarget().getPixelFormat();
+		const tcu::IVec4		colorBits		= max(getBitsVec(pixelFormat) - (isNearestOnly ? 1 : 2), tcu::IVec4(0)); // 1 inaccurate bit if nearest only, 2 otherwise
+		tcu::LodPrecision		lodPrecision;
+		tcu::LookupPrecision	lookupPrecision;
+
+		lodPrecision.derivateBits		= 8;
+		lodPrecision.lodBits			= 6;
+		lookupPrecision.colorThreshold	= tcu::computeFixedPointThreshold(colorBits) / refParams.colorScale;
+		lookupPrecision.coordBits		= tcu::IVec3(20,20,0);
+		lookupPrecision.uvwBits			= tcu::IVec3(7,7,0);
+		lookupPrecision.colorMask		= getCompareMask(pixelFormat);
+
+		const bool isOk = verifyTextureResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+											  &texCoord[0], refParams, lookupPrecision, lodPrecision, pixelFormat);
+
+		if (!isOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+	}
+
+	m_caseNdx += 1;
+	return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
+}
+
+
+class TextureCubeFilteringCase : public tcu::TestCase
+{
+public:
+									TextureCubeFilteringCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, deUint32 dataType, int width, int height);
+									TextureCubeFilteringCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames);
+									~TextureCubeFilteringCase	(void);
+
+	void							init						(void);
+	void							deinit						(void);
+	IterateResult					iterate						(void);
+
+private:
+									TextureCubeFilteringCase	(const TextureCubeFilteringCase& other);
+	TextureCubeFilteringCase&		operator=					(const TextureCubeFilteringCase& other);
+
+	glu::RenderContext&				m_renderCtx;
+	const glu::ContextInfo&			m_renderCtxInfo;
+
+	const deUint32					m_minFilter;
+	const deUint32					m_magFilter;
+	const deUint32					m_wrapS;
+	const deUint32					m_wrapT;
+
+	const deUint32					m_format;
+	const deUint32					m_dataType;
+	const int						m_width;
+	const int						m_height;
+
+	const std::vector<std::string>	m_filenames;
+
+	struct FilterCase
+	{
+		const glu::TextureCube*	texture;
+		tcu::Vec2				bottomLeft;
+		tcu::Vec2				topRight;
+
+		FilterCase (void)
+			: texture(DE_NULL)
+		{
+		}
+
+		FilterCase (const glu::TextureCube* tex_, const tcu::Vec2& bottomLeft_, const tcu::Vec2& topRight_)
+			: texture	(tex_)
+			, bottomLeft(bottomLeft_)
+			, topRight	(topRight_)
+		{
+		}
+	};
+
+	std::vector<glu::TextureCube*>	m_textures;
+	std::vector<FilterCase>			m_cases;
+
+	TextureRenderer					m_renderer;
+
+	int								m_caseNdx;
+};
+
+TextureCubeFilteringCase::TextureCubeFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, deUint32 dataType, int width, int height)
+	: TestCase					(testCtx, name, desc)
+	, m_renderCtx				(renderCtx)
+	, m_renderCtxInfo			(ctxInfo)
+	, m_minFilter				(minFilter)
+	, m_magFilter				(magFilter)
+	, m_wrapS					(wrapS)
+	, m_wrapT					(wrapT)
+	, m_format					(format)
+	, m_dataType				(dataType)
+	, m_width					(width)
+	, m_height					(height)
+	, m_renderer				(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+	, m_caseNdx					(0)
+{
+}
+
+TextureCubeFilteringCase::TextureCubeFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames)
+	: TestCase					(testCtx, name, desc)
+	, m_renderCtx				(renderCtx)
+	, m_renderCtxInfo			(ctxInfo)
+	, m_minFilter				(minFilter)
+	, m_magFilter				(magFilter)
+	, m_wrapS					(wrapS)
+	, m_wrapT					(wrapT)
+	, m_format					(GL_NONE)
+	, m_dataType				(GL_NONE)
+	, m_width					(0)
+	, m_height					(0)
+	, m_filenames				(filenames)
+	, m_renderer				(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+	, m_caseNdx					(0)
+{
+}
+
+TextureCubeFilteringCase::~TextureCubeFilteringCase (void)
+{
+	deinit();
+}
+
+void TextureCubeFilteringCase::init (void)
+{
+	try
+	{
+		if (!m_filenames.empty())
+		{
+			m_textures.reserve(1);
+			m_textures.push_back(glu::TextureCube::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size() / 6, m_filenames));
+		}
+		else
+		{
+			DE_ASSERT(m_width == m_height);
+			m_textures.reserve(2);
+			for (int ndx = 0; ndx < 2; ndx++)
+				m_textures.push_back(new glu::TextureCube(m_renderCtx, m_format, m_dataType, m_width));
+
+			const bool				mipmaps		= deIsPowerOfTwo32(m_width) && deIsPowerOfTwo32(m_height);
+			const int				numLevels	= mipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;
+			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+			tcu::Vec4				cBias		= fmtInfo.valueMin;
+			tcu::Vec4				cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+			// Fill first with gradient texture.
+			static const tcu::Vec4 gradients[tcu::CUBEFACE_LAST][2] =
+			{
+				{ tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
+				{ tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
+				{ tcu::Vec4(0.0f, 0.5f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
+				{ tcu::Vec4(0.0f, 0.0f, 0.5f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
+				{ tcu::Vec4(0.0f, 0.0f, 0.0f, 0.5f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
+				{ tcu::Vec4(0.5f, 0.5f, 0.5f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
+			};
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			{
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					m_textures[0]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+					tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0]*cScale + cBias, gradients[face][1]*cScale + cBias);
+				}
+			}
+
+			// Fill second with grid texture.
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			{
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					deUint32	step	= 0x00ffffff / (numLevels*tcu::CUBEFACE_LAST);
+					deUint32	rgb		= step*levelNdx*face;
+					deUint32	colorA	= 0xff000000 | rgb;
+					deUint32	colorB	= 0xff000000 | ~rgb;
+
+					m_textures[1]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+					tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+				}
+			}
+
+			// Upload.
+			for (std::vector<glu::TextureCube*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+				(*i)->upload();
+		}
+
+		// Compute cases
+		{
+			const glu::TextureCube*	tex0	= m_textures[0];
+			const glu::TextureCube* tex1	= m_textures.size() > 1 ? m_textures[1] : tex0;
+
+			// \note Coordinates are chosen so that they only sample face interior. ES3 has changed edge sampling behavior
+			//		 and hw is not expected to implement both modes.
+			m_cases.push_back(FilterCase(tex0, tcu::Vec2(-0.8f, -0.8f), tcu::Vec2(0.8f, 0.8f)));	// minification
+			m_cases.push_back(FilterCase(tex0, tcu::Vec2(0.5f, 0.65f), tcu::Vec2(0.8f, 0.8f)));		// magnification
+			m_cases.push_back(FilterCase(tex1, tcu::Vec2(-0.8f, -0.8f), tcu::Vec2(0.8f, 0.8f)));	// minification
+			m_cases.push_back(FilterCase(tex1, tcu::Vec2(0.2f, 0.2f), tcu::Vec2(0.6f, 0.5f)));		// magnification
+		}
+
+		m_caseNdx = 0;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	catch (...)
+	{
+		// Clean up to save memory.
+		TextureCubeFilteringCase::deinit();
+		throw;
+	}
+}
+
+void TextureCubeFilteringCase::deinit (void)
+{
+	for (std::vector<glu::TextureCube*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	m_renderer.clear();
+	m_cases.clear();
+}
+
+static const char* getFaceDesc (const tcu::CubeFace face)
+{
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X:	return "-X";
+		case tcu::CUBEFACE_POSITIVE_X:	return "+X";
+		case tcu::CUBEFACE_NEGATIVE_Y:	return "-Y";
+		case tcu::CUBEFACE_POSITIVE_Y:	return "+Y";
+		case tcu::CUBEFACE_NEGATIVE_Z:	return "-Z";
+		case tcu::CUBEFACE_POSITIVE_Z:	return "+Z";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+TextureCubeFilteringCase::IterateResult TextureCubeFilteringCase::iterate (void)
+{
+	const glw::Functions&			gl				= m_renderCtx.getFunctions();
+	const int						viewportSize	= 28;
+	const RandomViewport			viewport		(m_renderCtx.getRenderTarget(), viewportSize, viewportSize, deStringHash(getName()) ^ deInt32Hash(m_caseNdx));
+	const tcu::ScopedLogSection		iterSection		(m_testCtx.getLog(), string("Test") + de::toString(m_caseNdx), string("Test ") + de::toString(m_caseNdx));
+	const FilterCase&				curCase			= m_cases[m_caseNdx];
+	const tcu::TextureFormat&		texFmt			= curCase.texture->getRefTexture().getFormat();
+	const tcu::TextureFormatInfo	fmtInfo			= tcu::getTextureFormatInfo(texFmt);
+	ReferenceParams					sampleParams	(TEXTURETYPE_CUBE);
+
+	if (viewport.width < viewportSize || viewport.height < viewportSize)
+		throw tcu::NotSupportedError("Too small render target", DE_NULL, __FILE__, __LINE__);
+
+	// Setup texture
+	gl.bindTexture	(GL_TEXTURE_CUBE_MAP, curCase.texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+
+	// Other state
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Params for reference computation.
+	sampleParams.sampler					= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	sampleParams.sampler.seamlessCubeMap	= true;
+	sampleParams.samplerType				= getSamplerType(texFmt);
+	sampleParams.colorBias					= fmtInfo.lookupBias;
+	sampleParams.colorScale					= fmtInfo.lookupScale;
+	sampleParams.lodMode					= LODMODE_EXACT;
+
+	m_testCtx.getLog() << TestLog::Message << "Coordinates: " << curCase.bottomLeft << " -> " << curCase.topRight << TestLog::EndMessage;
+
+	for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+	{
+		const tcu::CubeFace		face		= tcu::CubeFace(faceNdx);
+		tcu::Surface			result		(viewport.width, viewport.height);
+		vector<float>			texCoord;
+
+		computeQuadTexCoordCube(texCoord, face, curCase.bottomLeft, curCase.topRight);
+
+		m_testCtx.getLog() << TestLog::Message << "Face " << getFaceDesc(face) << TestLog::EndMessage;
+
+		// \todo Log texture coordinates.
+
+		m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+
+		glu::readPixels(m_renderCtx, viewport.x, viewport.y, result.getAccess());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels");
+
+		{
+			const bool				isNearestOnly	= m_minFilter == GL_NEAREST && m_magFilter == GL_NEAREST;
+			const tcu::PixelFormat	pixelFormat		= m_renderCtx.getRenderTarget().getPixelFormat();
+			const tcu::IVec4		colorBits		= max(getBitsVec(pixelFormat) - (isNearestOnly ? 1 : 2), tcu::IVec4(0)); // 1 inaccurate bit if nearest only, 2 otherwise
+			tcu::LodPrecision		lodPrecision;
+			tcu::LookupPrecision	lookupPrecision;
+
+			lodPrecision.derivateBits		= 5;
+			lodPrecision.lodBits			= 3;
+			lookupPrecision.colorThreshold	= tcu::computeFixedPointThreshold(colorBits) / sampleParams.colorScale;
+			lookupPrecision.coordBits		= tcu::IVec3(10,10,10);
+			lookupPrecision.uvwBits			= tcu::IVec3(6,6,0);
+			lookupPrecision.colorMask		= getCompareMask(pixelFormat);
+
+			const bool isOk = verifyTextureResult(m_testCtx, result.getAccess(), curCase.texture->getRefTexture(),
+												  &texCoord[0], sampleParams, lookupPrecision, lodPrecision, pixelFormat);
+
+			if (!isOk)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+		}
+	}
+
+	m_caseNdx += 1;
+	return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
+}
+
+TextureFilteringTests::TextureFilteringTests (Context& context)
+	: TestCaseGroup(context, "filtering", "Texture Filtering Tests")
+{
+}
+
+TextureFilteringTests::~TextureFilteringTests (void)
+{
+}
+
+void TextureFilteringTests::init (void)
+{
+	tcu::TestCaseGroup* group2D		= new tcu::TestCaseGroup(m_testCtx, "2d",	"2D Texture Filtering");
+	tcu::TestCaseGroup*	groupCube	= new tcu::TestCaseGroup(m_testCtx, "cube",	"Cube Map Filtering");
+	addChild(group2D);
+	addChild(groupCube);
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE },
+		{ "repeat",		GL_REPEAT },
+		{ "mirror",		GL_MIRRORED_REPEAT }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} minFilterModes[] =
+	{
+		{ "nearest",				GL_NEAREST					},
+		{ "linear",					GL_LINEAR					},
+		{ "nearest_mipmap_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
+		{ "linear_mipmap_nearest",	GL_LINEAR_MIPMAP_NEAREST	},
+		{ "nearest_mipmap_linear",	GL_NEAREST_MIPMAP_LINEAR	},
+		{ "linear_mipmap_linear",	GL_LINEAR_MIPMAP_LINEAR		}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} magFilterModes[] =
+	{
+		{ "nearest",	GL_NEAREST },
+		{ "linear",		GL_LINEAR }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		int				width;
+		int				height;
+	} sizes2D[] =
+	{
+		{ "pot",		32, 64 },
+		{ "npot",		31, 55 }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		int				width;
+		int				height;
+	} sizesCube[] =
+	{
+		{ "pot",		64, 64 },
+		{ "npot",		63, 63 }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		format;
+		deUint32		dataType;
+	} formats[] =
+	{
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE			},
+		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE			},
+		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4	},
+		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE			}
+	};
+
+#define FOR_EACH(ITERATOR, ARRAY, BODY)	\
+	for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++)	\
+		BODY
+
+	// 2D cases.
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+	FOR_EACH(format,		formats,
+	FOR_EACH(size,			sizes2D,
+		{
+			bool isMipmap		= minFilterModes[minFilter].mode != GL_NEAREST && minFilterModes[minFilter].mode != GL_LINEAR;
+			bool isClamp		= wrapModes[wrapMode].mode == GL_CLAMP_TO_EDGE;
+			bool isRepeat		= wrapModes[wrapMode].mode == GL_REPEAT;
+			bool isMagNearest	= magFilterModes[magFilter].mode == GL_NEAREST;
+			bool isPotSize		= deIsPowerOfTwo32(sizes2D[size].width) && deIsPowerOfTwo32(sizes2D[size].height);
+
+			if ((isMipmap || !isClamp) && !isPotSize)
+				continue; // Not supported.
+
+			if ((format != 0) && !(!isMipmap || (isRepeat && isMagNearest)))
+				continue; // Skip.
+
+			string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name + "_" + formats[format].name;
+
+			if (!isMipmap)
+				name += string("_") + sizes2D[size].name;
+
+			group2D->addChild(new Texture2DFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+														 name.c_str(), "",
+														 minFilterModes[minFilter].mode,
+														 magFilterModes[magFilter].mode,
+														 wrapModes[wrapMode].mode,
+														 wrapModes[wrapMode].mode,
+														 formats[format].format, formats[format].dataType,
+														 sizes2D[size].width, sizes2D[size].height));
+		})))));
+
+	// 2D ETC1 texture cases.
+	{
+		std::vector<std::string> filenames;
+		for (int i = 0; i <= 7; i++)
+			filenames.push_back(string("data/etc1/photo_helsinki_mip_") + de::toString(i) + ".pkm");
+
+		FOR_EACH(minFilter,		minFilterModes,
+		FOR_EACH(magFilter,		magFilterModes,
+		FOR_EACH(wrapMode,		wrapModes,
+			{
+				string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name + "_etc1";
+
+				group2D->addChild(new Texture2DFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+															 name.c_str(), "",
+															 minFilterModes[minFilter].mode,
+															 magFilterModes[magFilter].mode,
+															 wrapModes[wrapMode].mode,
+															 wrapModes[wrapMode].mode,
+															 filenames));
+			})));
+	}
+
+	// Cubemap cases.
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+	FOR_EACH(format,		formats,
+	FOR_EACH(size,			sizesCube,
+		{
+			bool isMipmap		= minFilterModes[minFilter].mode != GL_NEAREST && minFilterModes[minFilter].mode != GL_LINEAR;
+			bool isClamp		= wrapModes[wrapMode].mode == GL_CLAMP_TO_EDGE;
+			bool isRepeat		= wrapModes[wrapMode].mode == GL_REPEAT;
+			bool isMagNearest	= magFilterModes[magFilter].mode == GL_NEAREST;
+			bool isPotSize		= deIsPowerOfTwo32(sizesCube[size].width) && deIsPowerOfTwo32(sizesCube[size].height);
+
+			if ((isMipmap || !isClamp) && !isPotSize)
+				continue; // Not supported.
+
+			if (format != 0 && !(!isMipmap || (isRepeat && isMagNearest)))
+				continue; // Skip.
+
+			string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name + "_" + formats[format].name;
+
+			if (!isMipmap)
+				name += string("_") + sizesCube[size].name;
+
+			groupCube->addChild(new TextureCubeFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+															 name.c_str(), "",
+															 minFilterModes[minFilter].mode,
+															 magFilterModes[magFilter].mode,
+															 wrapModes[wrapMode].mode,
+															 wrapModes[wrapMode].mode,
+															 formats[format].format, formats[format].dataType,
+															 sizesCube[size].width, sizesCube[size].height));
+		})))));
+
+	// Cubemap ETC1 cases
+	{
+		static const char* faceExt[] = { "neg_x", "pos_x", "neg_y", "pos_y", "neg_z", "pos_z" };
+
+		const int		numLevels	= 7;
+		vector<string>	filenames;
+		for (int level = 0; level < numLevels; level++)
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+				filenames.push_back(string("data/etc1/skybox_") + faceExt[face] + "_mip_" + de::toString(level) + ".pkm");
+
+		FOR_EACH(minFilter,		minFilterModes,
+		FOR_EACH(magFilter,		magFilterModes,
+			{
+				string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_clamp_etc1";
+
+				groupCube->addChild(new TextureCubeFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																 name.c_str(), "",
+																 minFilterModes[minFilter].mode,
+																 magFilterModes[magFilter].mode,
+																 GL_CLAMP_TO_EDGE,
+																 GL_CLAMP_TO_EDGE,
+																 filenames));
+			}));
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fTextureFilteringTests.hpp b/modules/gles2/functional/es2fTextureFilteringTests.hpp
new file mode 100644
index 0000000..27617b4
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureFilteringTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FTEXTUREFILTERINGTESTS_HPP
+#define _ES2FTEXTUREFILTERINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class TextureFilteringTests : public TestCaseGroup
+{
+public:
+								TextureFilteringTests		(Context& context);
+								~TextureFilteringTests		(void);
+
+	void						init						(void);
+
+private:
+								TextureFilteringTests		(const TextureFilteringTests& other);
+	TextureFilteringTests&		operator=					(const TextureFilteringTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FTEXTUREFILTERINGTESTS_HPP
diff --git a/modules/gles2/functional/es2fTextureFormatTests.cpp b/modules/gles2/functional/es2fTextureFormatTests.cpp
new file mode 100644
index 0000000..a3e422d
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureFormatTests.cpp
@@ -0,0 +1,701 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format tests.
+ *
+ * Constants:
+ *  + nearest-neighbor filtering
+ *  + no mipmaps
+ *  + full texture coordinate range (but not outside) tested
+ *  + accessed from fragment shader
+ *  + texture unit 0
+ *  + named texture object
+ *
+ * Variables:
+ *  + texture format
+ *  + texture type: 2D or cubemap
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fTextureFormatTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+
+#include "deStringUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using tcu::Sampler;
+using namespace glu;
+using namespace gls::TextureTestUtil;
+
+// Texture2DFormatCase
+
+class Texture2DFormatCase : public tcu::TestCase
+{
+public:
+							Texture2DFormatCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height);
+							~Texture2DFormatCase	(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							Texture2DFormatCase		(const Texture2DFormatCase& other);
+	Texture2DFormatCase&	operator=				(const Texture2DFormatCase& other);
+
+	glu::RenderContext&		m_renderCtx;
+
+	const deUint32			m_format;
+	const deUint32			m_dataType;
+	const int				m_width;
+	const int				m_height;
+
+	glu::Texture2D*			m_texture;
+	TextureRenderer			m_renderer;
+};
+
+Texture2DFormatCase::Texture2DFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(format)
+	, m_dataType	(dataType)
+	, m_width		(width)
+	, m_height		(height)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+Texture2DFormatCase::~Texture2DFormatCase (void)
+{
+	deinit();
+}
+
+void Texture2DFormatCase::init (void)
+{
+	TestLog&				log		= m_testCtx.getLog();
+	tcu::TextureFormat		fmt		= glu::mapGLTransferFormat(m_format, m_dataType);
+	tcu::TextureFormatInfo	spec	= tcu::getTextureFormatInfo(fmt);
+	std::ostringstream		fmtName;
+
+	fmtName << getPixelFormatStr(m_format) << ", " << getTypeStr(m_dataType);
+
+	log << TestLog::Message << "2D texture, " << fmtName.str() << ", " << m_width << "x" << m_height
+							<< ",\n  fill with " << formatGradient(&spec.valueMin, &spec.valueMax) << " gradient"
+		<< TestLog::EndMessage;
+
+	m_texture = new Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+
+	// Fill level 0.
+	m_texture->getRefTexture().allocLevel(0);
+	tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), spec.valueMin, spec.valueMax);
+}
+
+void Texture2DFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Texture2DFormatCase::IterateResult Texture2DFormatCase::iterate (void)
+{
+	TestLog&				log					= m_testCtx.getLog();
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_width, m_height, deStringHash(getName()));
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	ReferenceParams			renderParams		(TEXTURETYPE_2D);
+	tcu::TextureFormatInfo	spec				= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+	const deUint32			wrapS				= GL_CLAMP_TO_EDGE;
+	const deUint32			wrapT				= GL_CLAMP_TO_EDGE;
+	const deUint32			minFilter			= GL_NEAREST;
+	const deUint32			magFilter			= GL_NEAREST;
+
+	renderParams.flags			|= RenderParams::LOG_ALL;
+	renderParams.samplerType	= getSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.sampler		= Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	renderParams.colorScale		= spec.lookupScale;
+	renderParams.colorBias		= spec.lookupBias;
+
+	computeQuadTexCoord2D(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	log << TestLog::Message << "Texture parameters:"
+							<< "\n  WRAP_S = " << getTextureParameterValueStr(GL_TEXTURE_WRAP_S, wrapS)
+							<< "\n  WRAP_T = " << getTextureParameterValueStr(GL_TEXTURE_WRAP_T, wrapT)
+							<< "\n  MIN_FILTER = " << getTextureParameterValueStr(GL_TEXTURE_MIN_FILTER, minFilter)
+							<< "\n  MAG_FILTER = " << getTextureParameterValueStr(GL_TEXTURE_MAG_FILTER, magFilter)
+		<< TestLog::EndMessage;
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], renderParams);
+
+	// Compare and log.
+	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+// TextureCubeFormatCase
+
+class TextureCubeFormatCase : public tcu::TestCase
+{
+public:
+							TextureCubeFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height);
+							~TextureCubeFormatCase	(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							TextureCubeFormatCase	(const TextureCubeFormatCase& other);
+	TextureCubeFormatCase&	operator=				(const TextureCubeFormatCase& other);
+
+	bool					testFace				(tcu::CubeFace face);
+
+	glu::RenderContext&		m_renderCtx;
+
+	const deUint32			m_format;
+	const deUint32			m_dataType;
+	const int				m_width;
+	const int				m_height;
+
+	glu::TextureCube*		m_texture;
+	TextureRenderer			m_renderer;
+
+	int						m_curFace;
+	bool					m_isOk;
+};
+
+
+TextureCubeFormatCase::TextureCubeFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(format)
+	, m_dataType	(dataType)
+	, m_width		(width)
+	, m_height		(height)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+	, m_curFace		(0)
+	, m_isOk		(false)
+{
+}
+
+TextureCubeFormatCase::~TextureCubeFormatCase (void)
+{
+	deinit();
+}
+
+void TextureCubeFormatCase::init (void)
+{
+	TestLog&				log		= m_testCtx.getLog();
+	tcu::TextureFormat		fmt		= glu::mapGLTransferFormat(m_format, m_dataType);
+	tcu::TextureFormatInfo	spec	= tcu::getTextureFormatInfo(fmt);
+	std::ostringstream		fmtName;
+
+	if (m_dataType)
+		fmtName << getPixelFormatStr(m_format) << ", " << getTypeStr(m_dataType);
+	else
+		fmtName << getPixelFormatStr(m_format);
+
+	log << TestLog::Message << "Cube map texture, " << fmtName.str() << ", " << m_width << "x" << m_height
+							<< ",\n  fill with " << formatGradient(&spec.valueMin, &spec.valueMax) << " gradient"
+		<< TestLog::EndMessage;
+
+	DE_ASSERT(m_width == m_height);
+	m_texture = m_dataType != GL_NONE
+			  ? new TextureCube(m_renderCtx, m_format, m_dataType, m_width)	// Implicit internal format.
+		      : new TextureCube(m_renderCtx, m_format, m_width);				// Explicit internal format.
+
+	// Fill level 0.
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+	{
+		tcu::Vec4 gMin, gMax;
+
+		switch (face)
+		{
+			case 0: gMin = spec.valueMin.swizzle(0, 1, 2, 3); gMax = spec.valueMax.swizzle(0, 1, 2, 3); break;
+			case 1: gMin = spec.valueMin.swizzle(2, 1, 0, 3); gMax = spec.valueMax.swizzle(2, 1, 0, 3); break;
+			case 2: gMin = spec.valueMin.swizzle(1, 2, 0, 3); gMax = spec.valueMax.swizzle(1, 2, 0, 3); break;
+			case 3: gMin = spec.valueMax.swizzle(0, 1, 2, 3); gMax = spec.valueMin.swizzle(0, 1, 2, 3); break;
+			case 4: gMin = spec.valueMax.swizzle(2, 1, 0, 3); gMax = spec.valueMin.swizzle(2, 1, 0, 3); break;
+			case 5: gMin = spec.valueMax.swizzle(1, 2, 0, 3); gMax = spec.valueMin.swizzle(1, 2, 0, 3); break;
+			default:
+				DE_ASSERT(false);
+		}
+
+		m_texture->getRefTexture().allocLevel((tcu::CubeFace)face, 0);
+		tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevelFace(0, (tcu::CubeFace)face), gMin, gMax);
+	}
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Initialize iteration state.
+	m_curFace	= 0;
+	m_isOk		= true;
+}
+
+void TextureCubeFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+bool TextureCubeFormatCase::testFace (tcu::CubeFace face)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_width, m_height, deStringHash(getName())+(deUint32)face);
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	ReferenceParams			renderParams		(TEXTURETYPE_CUBE);
+	tcu::TextureFormatInfo	spec				= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	renderParams.samplerType				= getSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.sampler					= Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	renderParams.sampler.seamlessCubeMap	= false;
+	renderParams.colorScale					= spec.lookupScale;
+	renderParams.colorBias					= spec.lookupBias;
+
+	// Log render info on first face.
+	if (face == tcu::CUBEFACE_NEGATIVE_X)
+		renderParams.flags |= RenderParams::LOG_ALL;
+
+	computeQuadTexCoordCube(texCoord, face);
+
+	// \todo [2011-10-28 pyry] Image set name / section?
+	log << TestLog::Message << face << TestLog::EndMessage;
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], renderParams);
+
+	// Compare and log.
+	return compareImages(log, referenceFrame, renderedFrame, threshold);
+}
+
+TextureCubeFormatCase::IterateResult TextureCubeFormatCase::iterate (void)
+{
+	// Execute test for all faces.
+	if (!testFace((tcu::CubeFace)m_curFace))
+		m_isOk = false;
+
+	m_curFace += 1;
+
+	if (m_curFace == tcu::CUBEFACE_LAST)
+	{
+		m_testCtx.setTestResult(m_isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								m_isOk ? "Pass"					: "Image comparison failed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+TextureFormatTests::TextureFormatTests (Context& context)
+	: TestCaseGroup(context, "format", "Texture Format Tests")
+{
+}
+
+TextureFormatTests::~TextureFormatTests (void)
+{
+}
+
+// Compressed2DFormatCase
+
+class Compressed2DFormatCase : public tcu::TestCase
+{
+public:
+								Compressed2DFormatCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, const std::vector<std::string>& filenames);
+								~Compressed2DFormatCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Compressed2DFormatCase		(const Compressed2DFormatCase& other);
+	Compressed2DFormatCase&		operator=					(const Compressed2DFormatCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	std::vector<std::string>	m_filenames;
+
+	glu::Texture2D*				m_texture;
+	TextureRenderer				m_renderer;
+};
+
+Compressed2DFormatCase::Compressed2DFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, const std::vector<std::string>& filenames)
+	: TestCase			(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_filenames		(filenames)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+Compressed2DFormatCase::~Compressed2DFormatCase (void)
+{
+	deinit();
+}
+
+void Compressed2DFormatCase::init (void)
+{
+	// Create texture.
+	m_texture = Texture2D::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size(), m_filenames);
+}
+
+void Compressed2DFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Compressed2DFormatCase::IterateResult Compressed2DFormatCase::iterate (void)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_texture->getRefTexture().getWidth(), m_texture->getRefTexture().getHeight(), deStringHash(getName()));
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+
+	computeQuadTexCoord2D(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_2D);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
+
+	// Compute reference.
+	ReferenceParams refParams(TEXTURETYPE_2D);
+	refParams.sampler = Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], refParams);
+
+	// Compare and log.
+	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+// CompressedCubeFormatCase
+
+class CompressedCubeFormatCase : public tcu::TestCase
+{
+public:
+								CompressedCubeFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, const std::vector<std::string>& filenames);
+								~CompressedCubeFormatCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								CompressedCubeFormatCase	(const CompressedCubeFormatCase& other);
+	CompressedCubeFormatCase&	operator=					(const CompressedCubeFormatCase& other);
+
+	bool						testFace					(tcu::CubeFace face);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	std::vector<std::string>	m_filenames;
+
+	glu::TextureCube*			m_texture;
+	TextureRenderer				m_renderer;
+
+	int							m_curFace;
+	bool						m_isOk;
+};
+
+CompressedCubeFormatCase::CompressedCubeFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, const std::vector<std::string>& filenames)
+	: TestCase			(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_filenames		(filenames)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+	, m_curFace			(0)
+	, m_isOk			(false)
+{
+}
+
+CompressedCubeFormatCase::~CompressedCubeFormatCase (void)
+{
+	deinit();
+}
+
+void CompressedCubeFormatCase::init (void)
+{
+	// Create texture.
+	DE_ASSERT(m_filenames.size() % 6 == 0);
+	m_texture = TextureCube::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size()/6, m_filenames);
+
+	m_curFace	= 0;
+	m_isOk		= true;
+}
+
+void CompressedCubeFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+bool CompressedCubeFormatCase::testFace (tcu::CubeFace face)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_texture->getRefTexture().getSize(), m_texture->getRefTexture().getSize(), deStringHash(getName())+(deUint32)face);
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	Sampler					sampler				(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+
+	computeQuadTexCoordCube(texCoord, face);
+
+	// \todo [2011-10-28 pyry] Image set name / section?
+	log << TestLog::Message << face << TestLog::EndMessage;
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_CUBE);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], ReferenceParams(TEXTURETYPE_CUBE, sampler));
+
+	// Compare and log.
+	return compareImages(log, referenceFrame, renderedFrame, threshold);
+}
+
+CompressedCubeFormatCase::IterateResult CompressedCubeFormatCase::iterate (void)
+{
+	// Execute test for all faces.
+	if (!testFace((tcu::CubeFace)m_curFace))
+		m_isOk = false;
+
+	m_curFace += 1;
+
+	if (m_curFace == tcu::CUBEFACE_LAST)
+	{
+		m_testCtx.setTestResult(m_isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								m_isOk ? "Pass"					: "Image comparison failed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+vector<string> toStringVector (const char* const* str, int numStr)
+{
+	vector<string> v;
+	v.resize(numStr);
+	for (int i = 0; i < numStr; i++)
+		v[i] = str[i];
+	return v;
+}
+
+void TextureFormatTests::init (void)
+{
+	struct
+	{
+		const char*	name;
+		deUint32		format;
+		deUint32		dataType;
+	} texFormats[] =
+	{
+		{ "a8",			GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "la88",		GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
+		{ "rgb565",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgba5551",	GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+	{
+		deUint32	format			= texFormats[formatNdx].format;
+		deUint32	dataType		= texFormats[formatNdx].dataType;
+		string	nameBase		= texFormats[formatNdx].name;
+		string	descriptionBase	= string(glu::getPixelFormatName(format)) + ", " + glu::getTypeName(dataType);
+
+		addChild(new Texture2DFormatCase	(m_testCtx, m_context.getRenderContext(),	(nameBase + "_2d_pot").c_str(),		(descriptionBase + ", GL_TEXTURE_2D").c_str(),			format, dataType, 128, 128));
+		addChild(new Texture2DFormatCase	(m_testCtx, m_context.getRenderContext(),	(nameBase + "_2d_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D").c_str(),			format, dataType,  63, 112));
+		addChild(new TextureCubeFormatCase	(m_testCtx, m_context.getRenderContext(),	(nameBase + "_cube_pot").c_str(),	(descriptionBase + ", GL_TEXTURE_CUBE_MAP").c_str(),	format, dataType,  64,  64));
+		addChild(new TextureCubeFormatCase	(m_testCtx, m_context.getRenderContext(),	(nameBase + "_cube_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_CUBE_MAP").c_str(),	format, dataType,  57,  57));
+	}
+
+	// ETC-1 compressed formats.
+	{
+		static const char* filenames[] =
+		{
+			"data/etc1/photo_helsinki_mip_0.pkm",
+			"data/etc1/photo_helsinki_mip_1.pkm",
+			"data/etc1/photo_helsinki_mip_2.pkm",
+			"data/etc1/photo_helsinki_mip_3.pkm",
+			"data/etc1/photo_helsinki_mip_4.pkm",
+			"data/etc1/photo_helsinki_mip_5.pkm",
+			"data/etc1/photo_helsinki_mip_6.pkm",
+			"data/etc1/photo_helsinki_mip_7.pkm"
+		};
+		addChild(new Compressed2DFormatCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), "etc1_2d_pot", "GL_ETC1_RGB8_OES, GL_TEXTURE_2D", toStringVector(filenames, DE_LENGTH_OF_ARRAY(filenames))));
+	}
+
+	{
+		vector<string> filenames;
+		filenames.push_back("data/etc1/photo_helsinki_113x89.pkm");
+		addChild(new Compressed2DFormatCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), "etc1_2d_npot", "GL_ETC1_RGB8_OES, GL_TEXTURE_2D", filenames));
+	}
+
+	{
+		static const char* faceExt[] = { "neg_x", "pos_x", "neg_y", "pos_y", "neg_z", "pos_z" };
+
+		const int		potNumLevels	= 7;
+		vector<string>	potFilenames;
+		for (int level = 0; level < potNumLevels; level++)
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+				potFilenames.push_back(string("data/etc1/skybox_") + faceExt[face] + "_mip_" + de::toString(level) + ".pkm");
+
+		addChild(new CompressedCubeFormatCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), "etc1_cube_pot", "GL_ETC1_RGB8_OES, GL_TEXTURE_CUBE_MAP", potFilenames));
+
+		vector<string> npotFilenames;
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			npotFilenames.push_back(string("data/etc1/skybox_61x61_") + faceExt[face] + ".pkm");
+
+		addChild(new CompressedCubeFormatCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), "etc1_cube_npot", "GL_ETC_RGB8_OES, GL_TEXTURE_CUBE_MAP", npotFilenames));
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fTextureFormatTests.hpp b/modules/gles2/functional/es2fTextureFormatTests.hpp
new file mode 100644
index 0000000..2bef5dc
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureFormatTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FTEXTUREFORMATTESTS_HPP
+#define _ES2FTEXTUREFORMATTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class TextureFormatTests : public TestCaseGroup
+{
+public:
+							TextureFormatTests		(Context& context);
+							~TextureFormatTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureFormatTests		(const TextureFormatTests& other);
+	TextureFormatTests&		operator=				(const TextureFormatTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FTEXTUREFORMATTESTS_HPP
diff --git a/modules/gles2/functional/es2fTextureMipmapTests.cpp b/modules/gles2/functional/es2fTextureMipmapTests.cpp
new file mode 100644
index 0000000..e22ca8f
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureMipmapTests.cpp
@@ -0,0 +1,1257 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Mipmapping tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fTextureMipmapTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuMatrixUtil.hpp"
+#include "tcuTexLookupVerifier.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using tcu::Sampler;
+using tcu::Vec2;
+using tcu::Mat2;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec4;
+using namespace glu;
+using namespace gls::TextureTestUtil;
+
+enum CoordType
+{
+	COORDTYPE_BASIC,		//!< texCoord = translateScale(position).
+	COORDTYPE_BASIC_BIAS,	//!< Like basic, but with bias values.
+	COORDTYPE_AFFINE,		//!< texCoord = translateScaleRotateShear(position).
+	COORDTYPE_PROJECTED,	//!< Projected coordinates, w != 1
+
+	COORDTYPE_LAST
+};
+
+// Texture2DMipmapCase
+
+class Texture2DMipmapCase : public tcu::TestCase
+{
+public:
+
+								Texture2DMipmapCase			(tcu::TestContext&			testCtx,
+															 glu::RenderContext&		renderCtx,
+															 const glu::ContextInfo&	renderCtxInfo,
+															 const char*				name,
+															 const char*				desc,
+															 CoordType					coordType,
+															 deUint32					minFilter,
+															 deUint32					wrapS,
+															 deUint32					wrapT,
+															 deUint32					format,
+															 deUint32					dataType,
+															 int						width,
+															 int						height);
+								~Texture2DMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Texture2DMipmapCase			(const Texture2DMipmapCase& other);
+	Texture2DMipmapCase&		operator=					(const Texture2DMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	CoordType					m_coordType;
+	deUint32					m_minFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	deUint32					m_format;
+	deUint32					m_dataType;
+	int							m_width;
+	int							m_height;
+
+	glu::Texture2D*				m_texture;
+	TextureRenderer				m_renderer;
+};
+
+Texture2DMipmapCase::Texture2DMipmapCase (tcu::TestContext&			testCtx,
+										  glu::RenderContext&		renderCtx,
+										  const glu::ContextInfo&	renderCtxInfo,
+										  const char*				name,
+										  const char*				desc,
+										  CoordType					coordType,
+										  deUint32					minFilter,
+										  deUint32					wrapS,
+										  deUint32					wrapT,
+										  deUint32					format,
+										  deUint32					dataType,
+										  int						width,
+										  int						height)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_coordType		(coordType)
+	, m_minFilter		(minFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_width			(width)
+	, m_height			(height)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES,
+						 renderCtxInfo.isFragmentHighPrecisionSupported() ? glu::PRECISION_HIGHP // Use highp if available.
+																		  : glu::PRECISION_MEDIUMP)
+{
+}
+
+Texture2DMipmapCase::~Texture2DMipmapCase (void)
+{
+	deinit();
+}
+
+void Texture2DMipmapCase::init (void)
+{
+	if (!m_renderCtxInfo.isFragmentHighPrecisionSupported())
+		m_testCtx.getLog() << TestLog::Message << "Warning: High precision not supported in fragment shaders." << TestLog::EndMessage;
+
+	if (m_coordType == COORDTYPE_PROJECTED && m_renderCtx.getRenderTarget().getNumSamples() > 0)
+		throw tcu::NotSupportedError("Projected lookup validation not supported in multisample config");
+
+	m_texture = new Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+
+	int numLevels = deLog2Floor32(de::max(m_width, m_height))+1;
+
+	// Fill texture with colored grid.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		deUint32	step		= 0xff / (numLevels-1);
+		deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+		deUint32	dec			= 0xff - inc;
+		deUint32	rgb			= (inc << 16) | (dec << 8) | 0xff;
+		deUint32	color		= 0xff000000 | rgb;
+
+		m_texture->getRefTexture().allocLevel(levelNdx);
+		tcu::clear(m_texture->getRefTexture().getLevel(levelNdx), toVec4(tcu::RGBA(color)));
+	}
+}
+
+void Texture2DMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+static void getBasicTexCoord2D (std::vector<float>& dst, int cellNdx)
+{
+	static const struct
+	{
+		Vec2 bottomLeft;
+		Vec2 topRight;
+	} s_basicCoords[] =
+	{
+		{ Vec2(-0.1f,  0.1f), Vec2( 0.8f,  1.0f) },
+		{ Vec2(-0.3f, -0.6f), Vec2( 0.7f,  0.4f) },
+		{ Vec2(-0.3f,  0.6f), Vec2( 0.7f, -0.9f) },
+		{ Vec2(-0.8f,  0.6f), Vec2( 0.7f, -0.9f) },
+
+		{ Vec2(-0.5f, -0.5f), Vec2( 1.5f,  1.5f) },
+		{ Vec2( 1.0f, -1.0f), Vec2(-1.3f,  1.0f) },
+		{ Vec2( 1.2f, -1.0f), Vec2(-1.3f,  1.6f) },
+		{ Vec2( 2.2f, -1.1f), Vec2(-1.3f,  0.8f) },
+
+		{ Vec2(-1.5f,  1.6f), Vec2( 1.7f, -1.4f) },
+		{ Vec2( 2.0f,  1.6f), Vec2( 2.3f, -1.4f) },
+		{ Vec2( 1.3f, -2.6f), Vec2(-2.7f,  2.9f) },
+		{ Vec2(-0.8f, -6.6f), Vec2( 6.0f, -0.9f) },
+
+		{ Vec2( -8.0f,   9.0f), Vec2(  8.3f,  -7.0f) },
+		{ Vec2(-16.0f,  10.0f), Vec2( 18.3f,  24.0f) },
+		{ Vec2( 30.2f,  55.0f), Vec2(-24.3f,  -1.6f) },
+		{ Vec2(-33.2f,  64.1f), Vec2( 32.1f, -64.1f) },
+	};
+
+	DE_ASSERT(de::inBounds(cellNdx, 0, DE_LENGTH_OF_ARRAY(s_basicCoords)));
+
+	const Vec2& bottomLeft	= s_basicCoords[cellNdx].bottomLeft;
+	const Vec2& topRight	= s_basicCoords[cellNdx].topRight;
+
+	computeQuadTexCoord2D(dst, bottomLeft, topRight);
+}
+
+static void getAffineTexCoord2D (std::vector<float>& dst, int cellNdx)
+{
+	// Use basic coords as base.
+	getBasicTexCoord2D(dst, cellNdx);
+
+	// Rotate based on cell index.
+	float		angle		= 2.0f*DE_PI * ((float)cellNdx / 16.0f);
+	tcu::Mat2	rotMatrix	= tcu::rotationMatrix(angle);
+
+	// Second and third row are sheared.
+	float		shearX		= de::inRange(cellNdx, 4, 11) ? (float)(15-cellNdx) / 16.0f : 0.0f;
+	tcu::Mat2	shearMatrix	= tcu::shearMatrix(tcu::Vec2(shearX, 0.0f));
+
+	tcu::Mat2	transform	= rotMatrix * shearMatrix;
+	Vec2		p0			= transform * Vec2(dst[0], dst[1]);
+	Vec2		p1			= transform * Vec2(dst[2], dst[3]);
+	Vec2		p2			= transform * Vec2(dst[4], dst[5]);
+	Vec2		p3			= transform * Vec2(dst[6], dst[7]);
+
+	dst[0] = p0.x();	dst[1] = p0.y();
+	dst[2] = p1.x();	dst[3] = p1.y();
+	dst[4] = p2.x();	dst[5] = p2.y();
+	dst[6] = p3.x();	dst[7] = p3.y();
+}
+
+Texture2DMipmapCase::IterateResult Texture2DMipmapCase::iterate (void)
+{
+	const glw::Functions&		gl					= m_renderCtx.getFunctions();
+
+	const tcu::Texture2D&		refTexture			= m_texture->getRefTexture();
+
+	const deUint32				magFilter			= GL_NEAREST;
+	const int					texWidth			= refTexture.getWidth();
+	const int					texHeight			= refTexture.getHeight();
+	const int					defViewportWidth	= texWidth*4;
+	const int					defViewportHeight	= texHeight*4;
+
+	const RandomViewport		viewport			(m_renderCtx.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+	ReferenceParams				sampleParams		(TEXTURETYPE_2D);
+	vector<float>				texCoord;
+
+	const bool					isProjected			= m_coordType == COORDTYPE_PROJECTED;
+	const bool					useLodBias			= m_coordType == COORDTYPE_BASIC_BIAS;
+
+	tcu::Surface				renderedFrame		(viewport.width, viewport.height);
+
+	// Viewport is divided into 4x4 grid.
+	int							gridWidth			= 4;
+	int							gridHeight			= 4;
+	int							cellWidth			= viewport.width / gridWidth;
+	int							cellHeight			= viewport.height / gridHeight;
+
+	// Bail out if rendertarget is too small.
+	if (viewport.width < defViewportWidth/2 || viewport.height < defViewportHeight/2)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Sampling parameters.
+	sampleParams.sampler		= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, magFilter);
+	sampleParams.samplerType	= gls::TextureTestUtil::getSamplerType(m_texture->getRefTexture().getFormat());
+	sampleParams.flags			= (isProjected ? ReferenceParams::PROJECTED : 0) | (useLodBias ? ReferenceParams::USE_BIAS : 0);
+	sampleParams.lodMode		= LODMODE_EXACT; // Use ideal lod.
+
+	// Upload texture data.
+	m_texture->upload();
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture	(GL_TEXTURE_2D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Bias values.
+	static const float s_bias[] = { 1.0f, -2.0f, 0.8f, -0.5f, 1.5f, 0.9f, 2.0f, 4.0f };
+
+	// Projection values.
+	static const Vec4 s_projections[] =
+	{
+		Vec4(1.2f, 1.0f, 0.7f, 1.0f),
+		Vec4(1.3f, 0.8f, 0.6f, 2.0f),
+		Vec4(0.8f, 1.0f, 1.7f, 0.6f),
+		Vec4(1.2f, 1.0f, 1.7f, 1.5f)
+	};
+
+	// Render cells.
+	for (int gridY = 0; gridY < gridHeight; gridY++)
+	{
+		for (int gridX = 0; gridX < gridWidth; gridX++)
+		{
+			const int		curX		= cellWidth*gridX;
+			const int		curY		= cellHeight*gridY;
+			const int		curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+			const int		curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+			const int		cellNdx		= gridY*gridWidth + gridX;
+
+			// Compute texcoord.
+			switch (m_coordType)
+			{
+				case COORDTYPE_BASIC_BIAS:	// Fall-through.
+				case COORDTYPE_PROJECTED:
+				case COORDTYPE_BASIC:		getBasicTexCoord2D	(texCoord, cellNdx);	break;
+				case COORDTYPE_AFFINE:		getAffineTexCoord2D	(texCoord, cellNdx);	break;
+				default:					DE_ASSERT(DE_FALSE);
+			}
+
+			if (isProjected)
+				sampleParams.w = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+
+			if (useLodBias)
+				sampleParams.bias = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+
+			// Render with GL.
+			gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+			m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+		}
+	}
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log.
+	{
+		const tcu::PixelFormat&	pixelFormat		= m_renderCtx.getRenderTarget().getPixelFormat();
+		const bool				isTrilinear		= m_minFilter == GL_NEAREST_MIPMAP_LINEAR || m_minFilter == GL_LINEAR_MIPMAP_LINEAR;
+		tcu::Surface			referenceFrame	(viewport.width, viewport.height);
+		tcu::Surface			errorMask		(viewport.width, viewport.height);
+		tcu::LookupPrecision	lookupPrec;
+		tcu::LodPrecision		lodPrec;
+		int						numFailedPixels	= 0;
+
+		lookupPrec.coordBits		= tcu::IVec3(20, 20, 0);
+		lookupPrec.uvwBits			= tcu::IVec3(16, 16, 0); // Doesn't really matter since pixels are unicolored.
+		lookupPrec.colorThreshold	= tcu::computeFixedPointThreshold(max(getBitsVec(pixelFormat) - (isTrilinear ? 2 : 1), tcu::IVec4(0)));
+		lookupPrec.colorMask		= getCompareMask(pixelFormat);
+		lodPrec.derivateBits		= 10;
+		lodPrec.lodBits				= isProjected ? 6 : 8;
+
+		for (int gridY = 0; gridY < gridHeight; gridY++)
+		{
+			for (int gridX = 0; gridX < gridWidth; gridX++)
+			{
+				const int		curX		= cellWidth*gridX;
+				const int		curY		= cellHeight*gridY;
+				const int		curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+				const int		curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+				const int		cellNdx		= gridY*gridWidth + gridX;
+
+				// Compute texcoord.
+				switch (m_coordType)
+				{
+					case COORDTYPE_BASIC_BIAS:	// Fall-through.
+					case COORDTYPE_PROJECTED:
+					case COORDTYPE_BASIC:		getBasicTexCoord2D	(texCoord, cellNdx);	break;
+					case COORDTYPE_AFFINE:		getAffineTexCoord2D	(texCoord, cellNdx);	break;
+					default:					DE_ASSERT(DE_FALSE);
+				}
+
+				if (isProjected)
+					sampleParams.w = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+
+				if (useLodBias)
+					sampleParams.bias = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+
+				// Render ideal result
+				sampleTexture(SurfaceAccess(referenceFrame, pixelFormat, curX, curY, curW, curH),
+							  refTexture, &texCoord[0], sampleParams);
+
+				// Compare this cell
+				numFailedPixels += computeTextureLookupDiff(tcu::getSubregion(renderedFrame.getAccess(), curX, curY, curW, curH),
+															tcu::getSubregion(referenceFrame.getAccess(), curX, curY, curW, curH),
+															tcu::getSubregion(errorMask.getAccess(), curX, curY, curW, curH),
+															m_texture->getRefTexture(), &texCoord[0], sampleParams,
+															lookupPrec, lodPrec, m_testCtx.getWatchDog());
+			}
+		}
+
+		if (numFailedPixels > 0)
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Image verification failed, found " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+		m_testCtx.getLog() << TestLog::ImageSet("Result", "Verification result")
+							<< TestLog::Image("Rendered", "Rendered image", renderedFrame);
+
+		if (numFailedPixels > 0)
+		{
+			m_testCtx.getLog() << TestLog::Image("Reference", "Ideal reference", referenceFrame)
+								<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+		}
+
+		m_testCtx.getLog() << TestLog::EndImageSet;
+
+		{
+			const bool isOk = numFailedPixels == 0;
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image verification failed");
+		}
+	}
+
+	return STOP;
+}
+
+// TextureCubeMipmapCase
+
+class TextureCubeMipmapCase : public tcu::TestCase
+{
+public:
+
+								TextureCubeMipmapCase		(tcu::TestContext&			testCtx,
+															 glu::RenderContext&		renderCtx,
+															 const glu::ContextInfo&	renderCtxInfo,
+															 const char*				name,
+															 const char*				desc,
+															 CoordType					coordType,
+															 deUint32					minFilter,
+															 deUint32					wrapS,
+															 deUint32					wrapT,
+															 deUint32					format,
+															 deUint32					dataType,
+															 int						size);
+								~TextureCubeMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								TextureCubeMipmapCase		(const TextureCubeMipmapCase& other);
+	TextureCubeMipmapCase&		operator=					(const TextureCubeMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	CoordType					m_coordType;
+	deUint32					m_minFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	deUint32					m_format;
+	deUint32					m_dataType;
+	int							m_size;
+
+	glu::TextureCube*			m_texture;
+	TextureRenderer				m_renderer;
+};
+
+TextureCubeMipmapCase::TextureCubeMipmapCase (tcu::TestContext&			testCtx,
+											  glu::RenderContext&		renderCtx,
+											  const glu::ContextInfo&	renderCtxInfo,
+											  const char*				name,
+											  const char*				desc,
+											  CoordType					coordType,
+											  deUint32					minFilter,
+											  deUint32					wrapS,
+											  deUint32					wrapT,
+											  deUint32					format,
+											  deUint32					dataType,
+											  int						size)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_coordType		(coordType)
+	, m_minFilter		(minFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_size			(size)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES,
+						 renderCtxInfo.isFragmentHighPrecisionSupported() ? glu::PRECISION_HIGHP // Use highp if available.
+																		  : glu::PRECISION_MEDIUMP)
+{
+}
+
+TextureCubeMipmapCase::~TextureCubeMipmapCase (void)
+{
+	deinit();
+}
+
+void TextureCubeMipmapCase::init (void)
+{
+	if (!m_renderCtxInfo.isFragmentHighPrecisionSupported())
+		m_testCtx.getLog() << TestLog::Message << "Warning: High precision not supported in fragment shaders." << TestLog::EndMessage;
+
+	if (m_coordType == COORDTYPE_PROJECTED && m_renderCtx.getRenderTarget().getNumSamples() > 0)
+		throw tcu::NotSupportedError("Projected lookup validation not supported in multisample config");
+
+	m_texture = new TextureCube(m_renderCtx, m_format, m_dataType, m_size);
+
+	int numLevels = deLog2Floor32(m_size)+1;
+
+	// Fill texture with colored grid.
+	for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+	{
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			deUint32	step		= 0xff / (numLevels-1);
+			deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+			deUint32	dec			= 0xff - inc;
+			deUint32	rgb			= 0;
+
+			switch (faceNdx)
+			{
+				case 0: rgb = (inc << 16) | (dec << 8) | 255; break;
+				case 1: rgb = (255 << 16) | (inc << 8) | dec; break;
+				case 2: rgb = (dec << 16) | (255 << 8) | inc; break;
+				case 3: rgb = (dec << 16) | (inc << 8) | 255; break;
+				case 4: rgb = (255 << 16) | (dec << 8) | inc; break;
+				case 5: rgb = (inc << 16) | (255 << 8) | dec; break;
+			}
+
+			deUint32	color		= 0xff000000 | rgb;
+
+			m_texture->getRefTexture().allocLevel((tcu::CubeFace)faceNdx, levelNdx);
+			tcu::clear(m_texture->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)faceNdx), toVec4(tcu::RGBA(color)));
+		}
+	}
+}
+
+void TextureCubeMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+static void randomPartition (vector<IVec4>& dst, de::Random& rnd, int x, int y, int width, int height)
+{
+	const int minWidth	= 8;
+	const int minHeight	= 8;
+
+	bool	partition		= rnd.getFloat() > 0.4f;
+	bool	partitionX		= partition && width > minWidth && rnd.getBool();
+	bool	partitionY		= partition && height > minHeight && !partitionX;
+
+	if (partitionX)
+	{
+		int split = width/2 + rnd.getInt(-width/4, +width/4);
+		randomPartition(dst, rnd, x, y, split, height);
+		randomPartition(dst, rnd, x+split, y, width-split, height);
+	}
+	else if (partitionY)
+	{
+		int split = height/2 + rnd.getInt(-height/4, +height/4);
+		randomPartition(dst, rnd, x, y, width, split);
+		randomPartition(dst, rnd, x, y+split, width, height-split);
+	}
+	else
+		dst.push_back(IVec4(x, y, width, height));
+}
+
+static void computeGridLayout (vector<IVec4>& dst, int width, int height)
+{
+	de::Random rnd(7);
+	randomPartition(dst, rnd, 0, 0, width, height);
+}
+
+TextureCubeMipmapCase::IterateResult TextureCubeMipmapCase::iterate (void)
+{
+	const deUint32			magFilter			= GL_NEAREST;
+	const int				texWidth			= m_texture->getRefTexture().getSize();
+	const int				texHeight			= m_texture->getRefTexture().getSize();
+	const int				defViewportWidth	= texWidth*2;
+	const int				defViewportHeight	= texHeight*2;
+
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	const RandomViewport	viewport			(m_renderCtx.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+
+	const bool				isProjected			= m_coordType == COORDTYPE_PROJECTED;
+	const bool				useLodBias			= m_coordType == COORDTYPE_BASIC_BIAS;
+
+	vector<float>			texCoord;
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+
+	// Bail out if rendertarget is too small.
+	if (viewport.width < defViewportWidth/2 || viewport.height < defViewportHeight/2)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Upload texture data.
+	m_texture->upload();
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture	(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Compute grid.
+	vector<IVec4> gridLayout;
+	computeGridLayout(gridLayout, viewport.width, viewport.height);
+
+	// Bias values.
+	static const float s_bias[] = { 1.0f, -2.0f, 0.8f, -0.5f, 1.5f, 0.9f, 2.0f, 4.0f };
+
+	// Projection values \note Less agressive than in 2D case due to smaller quads.
+	static const Vec4 s_projections[] =
+	{
+		Vec4(1.2f, 1.0f, 0.7f, 1.0f),
+		Vec4(1.3f, 0.8f, 0.6f, 1.1f),
+		Vec4(0.8f, 1.0f, 1.2f, 0.8f),
+		Vec4(1.2f, 1.0f, 1.3f, 0.9f)
+	};
+
+	// Render with GL
+	for (int cellNdx = 0; cellNdx < (int)gridLayout.size(); cellNdx++)
+	{
+		const int			curX		= gridLayout[cellNdx].x();
+		const int			curY		= gridLayout[cellNdx].y();
+		const int			curW		= gridLayout[cellNdx].z();
+		const int			curH		= gridLayout[cellNdx].w();
+		const tcu::CubeFace	cubeFace	= (tcu::CubeFace)(cellNdx % tcu::CUBEFACE_LAST);
+		RenderParams		params		(TEXTURETYPE_CUBE);
+
+		DE_ASSERT(m_coordType != COORDTYPE_AFFINE); // Not supported.
+		computeQuadTexCoordCube(texCoord, cubeFace);
+
+		if (isProjected)
+		{
+			params.flags	|= ReferenceParams::PROJECTED;
+			params.w		 = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+		}
+
+		if (useLodBias)
+		{
+			params.flags	|= ReferenceParams::USE_BIAS;
+			params.bias		 = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+		}
+
+		// Render with GL.
+		gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+		m_renderer.renderQuad(0, &texCoord[0], params);
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels");
+
+	// Render reference and compare
+	{
+		tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+		tcu::Surface			errorMask			(viewport.width, viewport.height);
+		int						numFailedPixels		= 0;
+		ReferenceParams			params				(TEXTURETYPE_CUBE);
+		tcu::LookupPrecision	lookupPrec;
+		tcu::LodPrecision		lodPrec;
+
+		// Params for rendering reference
+		params.sampler					= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, magFilter);
+		params.sampler.seamlessCubeMap	= false;
+		params.lodMode					= LODMODE_EXACT;
+
+		// Comparison parameters
+		lookupPrec.colorMask			= getCompareMask(m_renderCtx.getRenderTarget().getPixelFormat());
+		lookupPrec.colorThreshold		= tcu::computeFixedPointThreshold(max(getBitsVec(m_renderCtx.getRenderTarget().getPixelFormat())-2, IVec4(0)));
+		lookupPrec.coordBits			= isProjected ? tcu::IVec3(8) : tcu::IVec3(10);
+		lookupPrec.uvwBits				= tcu::IVec3(5,5,0);
+		lodPrec.derivateBits			= 10;
+		lodPrec.lodBits					= isProjected ? 4 : 6;
+
+		for (int cellNdx = 0; cellNdx < (int)gridLayout.size(); cellNdx++)
+		{
+			const int				curX		= gridLayout[cellNdx].x();
+			const int				curY		= gridLayout[cellNdx].y();
+			const int				curW		= gridLayout[cellNdx].z();
+			const int				curH		= gridLayout[cellNdx].w();
+			const tcu::CubeFace		cubeFace	= (tcu::CubeFace)(cellNdx % tcu::CUBEFACE_LAST);
+
+			DE_ASSERT(m_coordType != COORDTYPE_AFFINE); // Not supported.
+			computeQuadTexCoordCube(texCoord, cubeFace);
+
+			if (isProjected)
+			{
+				params.flags	|= ReferenceParams::PROJECTED;
+				params.w		 = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+			}
+
+			if (useLodBias)
+			{
+				params.flags	|= ReferenceParams::USE_BIAS;
+				params.bias		 = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+			}
+
+			// Render ideal reference.
+			{
+				SurfaceAccess idealDst(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), curX, curY, curW, curH);
+				sampleTexture(idealDst, m_texture->getRefTexture(), &texCoord[0], params);
+			}
+
+			// Compare this cell
+			numFailedPixels += computeTextureLookupDiff(tcu::getSubregion(renderedFrame.getAccess(), curX, curY, curW, curH),
+														tcu::getSubregion(referenceFrame.getAccess(), curX, curY, curW, curH),
+														tcu::getSubregion(errorMask.getAccess(), curX, curY, curW, curH),
+														m_texture->getRefTexture(), &texCoord[0], params,
+														lookupPrec, lodPrec, m_testCtx.getWatchDog());
+		}
+
+		if (numFailedPixels > 0)
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Image verification failed, found " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+		m_testCtx.getLog() << TestLog::ImageSet("Result", "Verification result")
+						   << TestLog::Image("Rendered", "Rendered image", renderedFrame);
+
+		if (numFailedPixels > 0)
+		{
+			m_testCtx.getLog() << TestLog::Image("Reference", "Ideal reference", referenceFrame)
+							   << TestLog::Image("ErrorMask", "Error mask", errorMask);
+		}
+
+		m_testCtx.getLog() << TestLog::EndImageSet;
+
+		{
+			const bool isOk = numFailedPixels == 0;
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image verification failed");
+		}
+	}
+
+	return STOP;
+}
+
+// Texture2DGenMipmapCase
+
+class Texture2DGenMipmapCase : public tcu::TestCase
+{
+public:
+
+								Texture2DGenMipmapCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 hint, int width, int height);
+								~Texture2DGenMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Texture2DGenMipmapCase		(const Texture2DGenMipmapCase& other);
+	Texture2DGenMipmapCase&		operator=					(const Texture2DGenMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+
+	deUint32					m_format;
+	deUint32					m_dataType;
+	deUint32					m_hint;
+	int							m_width;
+	int							m_height;
+
+	glu::Texture2D*				m_texture;
+	TextureRenderer				m_renderer;
+};
+
+Texture2DGenMipmapCase::Texture2DGenMipmapCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 hint, int width, int height)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_hint			(hint)
+	, m_width			(width)
+	, m_height			(height)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+Texture2DGenMipmapCase::~Texture2DGenMipmapCase (void)
+{
+	deinit();
+}
+
+void Texture2DGenMipmapCase::init (void)
+{
+	DE_ASSERT(!m_texture);
+	m_texture = new Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+}
+
+void Texture2DGenMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Texture2DGenMipmapCase::IterateResult Texture2DGenMipmapCase::iterate (void)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+
+	const deUint32			minFilter			= GL_NEAREST_MIPMAP_NEAREST;
+	const deUint32			magFilter			= GL_NEAREST;
+	const deUint32			wrapS				= GL_CLAMP_TO_EDGE;
+	const deUint32			wrapT				= GL_CLAMP_TO_EDGE;
+
+	const int				numLevels			= deLog2Floor32(de::max(m_width, m_height))+1;
+	const tcu::Sampler		sampler				= glu::mapGLSampler(wrapS, wrapT, minFilter, magFilter);
+
+	tcu::Texture2D			resultTexture		(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), m_texture->getRefTexture().getWidth(), m_texture->getRefTexture().getHeight());
+
+	vector<float>			texCoord;
+
+	// Initialize texture level 0 with colored grid.
+	m_texture->getRefTexture().allocLevel(0);
+	tcu::fillWithGrid(m_texture->getRefTexture().getLevel(0), 8, tcu::Vec4(1.0f, 0.5f, 0.0f, 0.5f), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+	// Upload data and setup params.
+	m_texture->upload();
+
+	gl.bindTexture	(GL_TEXTURE_2D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Generate mipmap.
+	gl.hint(GL_GENERATE_MIPMAP_HINT, m_hint);
+	gl.generateMipmap(GL_TEXTURE_2D);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenerateMipmap()");
+
+	// Use (0, 0) -> (1, 1) texture coordinates.
+	computeQuadTexCoord2D(texCoord, Vec2(0.0f, 0.0f), Vec2(1.0f, 1.0f));
+
+	// Fetch resulting texture by rendering.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		const int				levelWidth		= de::max(1, m_width >> levelNdx);
+		const int				levelHeight		= de::max(1, m_height >> levelNdx);
+		const RandomViewport	viewport		(m_renderCtx.getRenderTarget(), levelWidth, levelHeight, deStringHash(getName()) + levelNdx);
+
+		gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+		m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_2D);
+
+		resultTexture.allocLevel(levelNdx);
+		glu::readPixels(m_renderCtx, viewport.x, viewport.y, resultTexture.getLevel(levelNdx));
+	}
+
+	// Compare results
+	{
+
+		const IVec4			framebufferBits		= max(getBitsVec(m_renderCtx.getRenderTarget().getPixelFormat())-2, IVec4(0));
+		const IVec4			formatBits			= tcu::getTextureFormatBitDepth(glu::mapGLTransferFormat(m_format, m_dataType));
+		const tcu::BVec4	formatMask			= greaterThan(formatBits, IVec4(0));
+		const IVec4			cmpBits				= select(min(framebufferBits, formatBits), framebufferBits, formatMask);
+		GenMipmapPrecision	comparePrec;
+
+		comparePrec.colorMask		= getCompareMask(m_renderCtx.getRenderTarget().getPixelFormat());
+		comparePrec.colorThreshold	= tcu::computeFixedPointThreshold(cmpBits);
+		comparePrec.filterBits		= tcu::IVec3(4, 4, 0);
+
+		const qpTestResult compareResult = compareGenMipmapResult(m_testCtx.getLog(), resultTexture, m_texture->getRefTexture(), comparePrec);
+
+		m_testCtx.setTestResult(compareResult, compareResult == QP_TEST_RESULT_PASS				? "Pass" :
+											   compareResult == QP_TEST_RESULT_QUALITY_WARNING	? "Low-quality method used"	:
+											   compareResult == QP_TEST_RESULT_FAIL				? "Image comparison failed"	: "");
+	}
+
+	return STOP;
+}
+
+// TextureCubeGenMipmapCase
+
+class TextureCubeGenMipmapCase : public tcu::TestCase
+{
+public:
+
+								TextureCubeGenMipmapCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 hint, int size);
+								~TextureCubeGenMipmapCase		(void);
+
+	void						init							(void);
+	void						deinit							(void);
+	IterateResult				iterate							(void);
+
+private:
+								TextureCubeGenMipmapCase		(const TextureCubeGenMipmapCase& other);
+	TextureCubeGenMipmapCase&	operator=						(const TextureCubeGenMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+
+	deUint32					m_format;
+	deUint32					m_dataType;
+	deUint32					m_hint;
+	int							m_size;
+
+	glu::TextureCube*			m_texture;
+	TextureRenderer				m_renderer;
+};
+
+TextureCubeGenMipmapCase::TextureCubeGenMipmapCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 hint, int size)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_hint			(hint)
+	, m_size			(size)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+TextureCubeGenMipmapCase::~TextureCubeGenMipmapCase (void)
+{
+	deinit();
+}
+
+void TextureCubeGenMipmapCase::init (void)
+{
+	if (m_renderCtx.getRenderTarget().getWidth() < 3*m_size || m_renderCtx.getRenderTarget().getHeight() < 2*m_size)
+		throw tcu::NotSupportedError("Render target size must be at least (" + de::toString(3*m_size) + ", " + de::toString(2*m_size) + ")");
+
+	DE_ASSERT(!m_texture);
+	m_texture = new TextureCube(m_renderCtx, m_format, m_dataType, m_size);
+}
+
+void TextureCubeGenMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+TextureCubeGenMipmapCase::IterateResult TextureCubeGenMipmapCase::iterate (void)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+
+	const deUint32			minFilter			= GL_NEAREST_MIPMAP_NEAREST;
+	const deUint32			magFilter			= GL_NEAREST;
+	const deUint32			wrapS				= GL_CLAMP_TO_EDGE;
+	const deUint32			wrapT				= GL_CLAMP_TO_EDGE;
+
+	tcu::TextureCube		resultTexture		(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), m_size);
+
+	const int				numLevels			= deLog2Floor32(m_size)+1;
+	tcu::Sampler			sampler				= glu::mapGLSampler(wrapS, wrapT, minFilter, magFilter);
+	vector<float>			texCoord;
+
+	// Initialize texture level 0 with colored grid.
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+	{
+		Vec4 ca, cb; // Grid colors.
+
+		switch (face)
+		{
+			case 0: ca = Vec4(1.0f, 0.3f, 0.0f, 0.7f); cb = Vec4(0.0f, 0.0f, 1.0f, 1.0f); break;
+			case 1: ca = Vec4(0.0f, 1.0f, 0.5f, 0.5f); cb = Vec4(1.0f, 0.0f, 0.0f, 1.0f); break;
+			case 2: ca = Vec4(0.7f, 0.0f, 1.0f, 0.3f); cb = Vec4(0.0f, 1.0f, 0.0f, 1.0f); break;
+			case 3: ca = Vec4(0.0f, 0.3f, 1.0f, 1.0f); cb = Vec4(1.0f, 0.0f, 0.0f, 0.7f); break;
+			case 4: ca = Vec4(1.0f, 0.0f, 0.5f, 1.0f); cb = Vec4(0.0f, 1.0f, 0.0f, 0.5f); break;
+			case 5: ca = Vec4(0.7f, 1.0f, 0.0f, 1.0f); cb = Vec4(0.0f, 0.0f, 1.0f, 0.3f); break;
+		}
+
+		m_texture->getRefTexture().allocLevel((tcu::CubeFace)face, 0);
+		fillWithGrid(m_texture->getRefTexture().getLevelFace(0, (tcu::CubeFace)face), 8, ca, cb);
+	}
+
+	// Upload data and setup params.
+	m_texture->upload();
+
+	gl.bindTexture	(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Generate mipmap.
+	gl.hint(GL_GENERATE_MIPMAP_HINT, m_hint);
+	gl.generateMipmap(GL_TEXTURE_CUBE_MAP);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenerateMipmap()");
+
+	// Render all levels.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		const int	levelWidth	= de::max(1, m_size >> levelNdx);
+		const int	levelHeight	= de::max(1, m_size >> levelNdx);
+
+		for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+		{
+			const RandomViewport	viewport	(m_renderCtx.getRenderTarget(), levelWidth*3, levelHeight*2, deStringHash(getName()) ^ deInt32Hash(levelNdx + faceNdx));
+			const tcu::CubeFace		face		= tcu::CubeFace(faceNdx);
+
+			computeQuadTexCoordCube(texCoord, face);
+
+			gl.viewport(viewport.x, viewport.y, levelWidth, levelHeight);
+			m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_CUBE);
+
+			resultTexture.allocLevel(face, levelNdx);
+			glu::readPixels(m_renderCtx, viewport.x, viewport.y, resultTexture.getLevelFace(levelNdx, face));
+		}
+	}
+
+	// Compare results
+	{
+		const IVec4			framebufferBits		= max(getBitsVec(m_renderCtx.getRenderTarget().getPixelFormat())-2, IVec4(0));
+		const IVec4			formatBits			= tcu::getTextureFormatBitDepth(glu::mapGLTransferFormat(m_format, m_dataType));
+		const tcu::BVec4	formatMask			= greaterThan(formatBits, IVec4(0));
+		const IVec4			cmpBits				= select(min(framebufferBits, formatBits), framebufferBits, formatMask);
+		GenMipmapPrecision	comparePrec;
+
+		comparePrec.colorMask		= getCompareMask(m_renderCtx.getRenderTarget().getPixelFormat());
+		comparePrec.colorThreshold	= tcu::computeFixedPointThreshold(cmpBits);
+		comparePrec.filterBits		= tcu::IVec3(4, 4, 0);
+
+		const qpTestResult compareResult = compareGenMipmapResult(m_testCtx.getLog(), resultTexture, m_texture->getRefTexture(), comparePrec);
+
+		m_testCtx.setTestResult(compareResult, compareResult == QP_TEST_RESULT_PASS				? "Pass" :
+											   compareResult == QP_TEST_RESULT_QUALITY_WARNING	? "Low-quality method used"	:
+											   compareResult == QP_TEST_RESULT_FAIL				? "Image comparison failed"	: "");
+	}
+
+	return STOP;
+}
+
+TextureMipmapTests::TextureMipmapTests (Context& context)
+	: TestCaseGroup(context, "mipmap", "Mipmapping tests")
+{
+}
+
+TextureMipmapTests::~TextureMipmapTests (void)
+{
+}
+
+void TextureMipmapTests::init (void)
+{
+	tcu::TestCaseGroup* group2D		= new tcu::TestCaseGroup(m_testCtx, "2d",	"2D Texture Mipmapping");
+	tcu::TestCaseGroup*	groupCube	= new tcu::TestCaseGroup(m_testCtx, "cube",	"Cube Map Filtering");
+	addChild(group2D);
+	addChild(groupCube);
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE },
+		{ "repeat",		GL_REPEAT },
+		{ "mirror",		GL_MIRRORED_REPEAT }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} minFilterModes[] =
+	{
+		{ "nearest_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
+		{ "linear_nearest",		GL_LINEAR_MIPMAP_NEAREST	},
+		{ "nearest_linear",		GL_NEAREST_MIPMAP_LINEAR	},
+		{ "linear_linear",		GL_LINEAR_MIPMAP_LINEAR		}
+	};
+
+	static const struct
+	{
+		CoordType		type;
+		const char*		name;
+		const char*		desc;
+	} coordTypes[] =
+	{
+		{ COORDTYPE_BASIC,		"basic",		"Mipmapping with translated and scaled coordinates" },
+		{ COORDTYPE_AFFINE,		"affine",		"Mipmapping with affine coordinate transform"		},
+		{ COORDTYPE_PROJECTED,	"projected",	"Mipmapping with perspective projection"			}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		format;
+		deUint32		dataType;
+	} formats[] =
+	{
+		{ "a8",			GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "la88",		GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
+		{ "rgb565",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgba5551",	GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		hint;
+	} genHints[] =
+	{
+		{ "fastest",	GL_FASTEST },
+		{ "nicest",		GL_NICEST }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		int				width;
+		int				height;
+	} tex2DSizes[] =
+	{
+		{ DE_NULL,		64, 64 }, // Default.
+		{ "non_square",	32, 64 }
+	};
+
+	// 2D cases.
+	for (int coordType = 0; coordType < DE_LENGTH_OF_ARRAY(coordTypes); coordType++)
+	{
+		tcu::TestCaseGroup* coordTypeGroup = new tcu::TestCaseGroup(m_testCtx, coordTypes[coordType].name, coordTypes[coordType].desc);
+		group2D->addChild(coordTypeGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+		{
+			for (int wrapMode = 0; wrapMode < DE_LENGTH_OF_ARRAY(wrapModes); wrapMode++)
+			{
+				// Add non_square variants to basic cases only.
+				int sizeEnd = coordTypes[coordType].type == COORDTYPE_BASIC ? DE_LENGTH_OF_ARRAY(tex2DSizes) : 1;
+
+				for (int size = 0; size < sizeEnd; size++)
+				{
+					std::ostringstream name;
+					name << minFilterModes[minFilter].name
+						 << "_" << wrapModes[wrapMode].name;
+
+					if (tex2DSizes[size].name)
+						name << "_" << tex2DSizes[size].name;
+
+					coordTypeGroup->addChild(new Texture2DMipmapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																	 name.str().c_str(), "",
+																	 coordTypes[coordType].type,
+																	 minFilterModes[minFilter].mode,
+																	 wrapModes[wrapMode].mode,
+																	 wrapModes[wrapMode].mode,
+																	 GL_RGBA, GL_UNSIGNED_BYTE,
+																	 tex2DSizes[size].width, tex2DSizes[size].height));
+				}
+			}
+		}
+	}
+
+	// 2D bias variants.
+	{
+		tcu::TestCaseGroup* biasGroup = new tcu::TestCaseGroup(m_testCtx, "bias", "User-supplied bias value");
+		group2D->addChild(biasGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			biasGroup->addChild(new Texture2DMipmapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+														minFilterModes[minFilter].name, "",
+														COORDTYPE_BASIC_BIAS,
+														minFilterModes[minFilter].mode,
+														GL_REPEAT, GL_REPEAT,
+														GL_RGBA, GL_UNSIGNED_BYTE,
+														tex2DSizes[0].width, tex2DSizes[0].height));
+	}
+
+	// 2D mipmap generation variants.
+	{
+		tcu::TestCaseGroup* genMipmapGroup = new tcu::TestCaseGroup(m_testCtx, "generate", "Mipmap generation tests");
+		group2D->addChild(genMipmapGroup);
+
+		for (int format = 0; format < DE_LENGTH_OF_ARRAY(formats); format++)
+		{
+			for (int size = 0; size < DE_LENGTH_OF_ARRAY(tex2DSizes); size++)
+			{
+				for (int hint = 0; hint < DE_LENGTH_OF_ARRAY(genHints); hint++)
+				{
+					std::ostringstream name;
+					name << formats[format].name;
+
+					if (tex2DSizes[size].name)
+						name << "_" << tex2DSizes[size].name;
+
+					name << "_" << genHints[hint].name;
+
+					genMipmapGroup->addChild(new Texture2DGenMipmapCase(m_testCtx, m_context.getRenderContext(), name.str().c_str(), "",
+																		formats[format].format, formats[format].dataType, genHints[hint].hint,
+																		tex2DSizes[size].width, tex2DSizes[size].height));
+				}
+			}
+		}
+	}
+
+	const int cubeMapSize = 64;
+
+	static const struct
+	{
+		CoordType		type;
+		const char*		name;
+		const char*		desc;
+	} cubeCoordTypes[] =
+	{
+		{ COORDTYPE_BASIC,		"basic",		"Mipmapping with translated and scaled coordinates" },
+		{ COORDTYPE_PROJECTED,	"projected",	"Mipmapping with perspective projection"			},
+		{ COORDTYPE_BASIC_BIAS,	"bias",			"User-supplied bias value"							}
+	};
+
+	// Cubemap cases.
+	for (int coordType = 0; coordType < DE_LENGTH_OF_ARRAY(cubeCoordTypes); coordType++)
+	{
+		tcu::TestCaseGroup* coordTypeGroup = new tcu::TestCaseGroup(m_testCtx, cubeCoordTypes[coordType].name, cubeCoordTypes[coordType].desc);
+		groupCube->addChild(coordTypeGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+		{
+			coordTypeGroup->addChild(new TextureCubeMipmapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+															   minFilterModes[minFilter].name, "",
+															   cubeCoordTypes[coordType].type,
+															   minFilterModes[minFilter].mode,
+															   GL_CLAMP_TO_EDGE,
+															   GL_CLAMP_TO_EDGE,
+															   GL_RGBA, GL_UNSIGNED_BYTE, cubeMapSize));
+		}
+	}
+
+	// Cubemap mipmap generation variants.
+	{
+		tcu::TestCaseGroup* genMipmapGroup = new tcu::TestCaseGroup(m_testCtx, "generate", "Mipmap generation tests");
+		groupCube->addChild(genMipmapGroup);
+
+		for (int format = 0; format < DE_LENGTH_OF_ARRAY(formats); format++)
+		{
+			for (int hint = 0; hint < DE_LENGTH_OF_ARRAY(genHints); hint++)
+			{
+				std::ostringstream name;
+				name << formats[format].name
+					 << "_" << genHints[hint].name;
+
+				genMipmapGroup->addChild(new TextureCubeGenMipmapCase(m_testCtx, m_context.getRenderContext(), name.str().c_str(), "", formats[format].format, formats[format].dataType, genHints[hint].hint, cubeMapSize));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fTextureMipmapTests.hpp b/modules/gles2/functional/es2fTextureMipmapTests.hpp
new file mode 100644
index 0000000..1f66655
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureMipmapTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FTEXTUREMIPMAPTESTS_HPP
+#define _ES2FTEXTUREMIPMAPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Mipmapping tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class TextureMipmapTests : public TestCaseGroup
+{
+public:
+							TextureMipmapTests		(Context& context);
+							~TextureMipmapTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureMipmapTests		(const TextureMipmapTests& other);
+	TextureMipmapTests&		operator=				(const TextureMipmapTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FTEXTUREMIPMAPTESTS_HPP
diff --git a/modules/gles2/functional/es2fTextureSizeTests.cpp b/modules/gles2/functional/es2fTextureSizeTests.cpp
new file mode 100644
index 0000000..5fcd23f
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureSizeTests.cpp
@@ -0,0 +1,422 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture size tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fTextureSizeTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using tcu::Sampler;
+using namespace glu;
+using namespace gls::TextureTestUtil;
+
+class Texture2DSizeCase : public tcu::TestCase
+{
+public:
+							Texture2DSizeCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps);
+							~Texture2DSizeCase		(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							Texture2DSizeCase		(const Texture2DSizeCase& other);
+	Texture2DSizeCase&		operator=				(const Texture2DSizeCase& other);
+
+	glu::RenderContext&		m_renderCtx;
+
+	deUint32				m_format;
+	deUint32				m_dataType;
+	int						m_width;
+	int						m_height;
+	bool					m_useMipmaps;
+
+	glu::Texture2D*			m_texture;
+	TextureRenderer			m_renderer;
+};
+
+Texture2DSizeCase::Texture2DSizeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(format)
+	, m_dataType	(dataType)
+	, m_width		(width)
+	, m_height		(height)
+	, m_useMipmaps	(mipmaps)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+Texture2DSizeCase::~Texture2DSizeCase (void)
+{
+	Texture2DSizeCase::deinit();
+}
+
+void Texture2DSizeCase::init (void)
+{
+	DE_ASSERT(!m_texture);
+	m_texture = new Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+
+	int numLevels = m_useMipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;
+
+	// Fill levels.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		m_texture->getRefTexture().allocLevel(levelNdx);
+		tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(levelNdx), tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f));
+	}
+}
+
+void Texture2DSizeCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Texture2DSizeCase::IterateResult Texture2DSizeCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	TestLog&				log				= m_testCtx.getLog();
+	RandomViewport			viewport		(m_renderCtx.getRenderTarget(), 128, 128, deStringHash(getName()));
+	tcu::Surface			renderedFrame	(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame	(viewport.width, viewport.height);
+	tcu::RGBA				threshold		= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(7,7,7,7);
+	deUint32				wrapS			= GL_CLAMP_TO_EDGE;
+	deUint32				wrapT			= GL_CLAMP_TO_EDGE;
+	deUint32				minFilter		= m_useMipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST;
+	deUint32				magFilter		= GL_NEAREST;
+	vector<float>			texCoord;
+
+	computeQuadTexCoord2D(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_2D);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], ReferenceParams(TEXTURETYPE_2D, mapGLSampler(wrapS, wrapT, minFilter, magFilter)));
+
+	// Compare and log.
+	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+class TextureCubeSizeCase : public tcu::TestCase
+{
+public:
+							TextureCubeSizeCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps);
+							~TextureCubeSizeCase	(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							TextureCubeSizeCase		(const TextureCubeSizeCase& other);
+	TextureCubeSizeCase&	operator=				(const TextureCubeSizeCase& other);
+
+	bool					testFace				(tcu::CubeFace face);
+
+	glu::RenderContext&		m_renderCtx;
+
+	deUint32				m_format;
+	deUint32				m_dataType;
+	int						m_width;
+	int						m_height;
+	bool					m_useMipmaps;
+
+	glu::TextureCube*		m_texture;
+	TextureRenderer			m_renderer;
+
+	int						m_curFace;
+	bool					m_isOk;
+};
+
+TextureCubeSizeCase::TextureCubeSizeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(format)
+	, m_dataType	(dataType)
+	, m_width		(width)
+	, m_height		(height)
+	, m_useMipmaps	(mipmaps)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+	, m_curFace		(0)
+	, m_isOk		(false)
+{
+}
+
+TextureCubeSizeCase::~TextureCubeSizeCase (void)
+{
+	TextureCubeSizeCase::deinit();
+}
+
+void TextureCubeSizeCase::init (void)
+{
+	DE_ASSERT(!m_texture);
+	DE_ASSERT(m_width == m_height);
+	m_texture = new TextureCube(m_renderCtx, m_format, m_dataType, m_width);
+
+	static const tcu::Vec4 gradients[tcu::CUBEFACE_LAST][2] =
+	{
+		{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
+		{ tcu::Vec4( 0.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
+		{ tcu::Vec4(-1.0f,  0.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
+		{ tcu::Vec4(-1.0f, -1.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
+		{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
+		{ tcu::Vec4( 0.0f,  0.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
+	};
+
+	int numLevels = m_useMipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;
+
+	// Fill levels.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			m_texture->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+			fillWithComponentGradients(m_texture->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0], gradients[face][1]);
+		}
+	}
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Initialize iteration state.
+	m_curFace	= 0;
+	m_isOk		= true;
+}
+
+void TextureCubeSizeCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+bool TextureCubeSizeCase::testFace (tcu::CubeFace face)
+{
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	TestLog&				log				= m_testCtx.getLog();
+	RandomViewport			viewport		(m_renderCtx.getRenderTarget(), 128, 128, deStringHash(getName())+(deUint32)face);
+	tcu::Surface			renderedFrame	(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame	(viewport.width, viewport.height);
+	tcu::RGBA				threshold		= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(7,7,7,7);
+	deUint32				wrapS			= GL_CLAMP_TO_EDGE;
+	deUint32				wrapT			= GL_CLAMP_TO_EDGE;
+	deUint32				minFilter		= m_useMipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST;
+	deUint32				magFilter		= GL_NEAREST;
+	vector<float>			texCoord;
+
+	computeQuadTexCoordCube(texCoord, face);
+
+	// \todo [2011-10-28 pyry] Image set name / section?
+	log << TestLog::Message << face << TestLog::EndMessage;
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_CUBE);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compute reference.
+	Sampler sampler = mapGLSampler(wrapS, wrapT, minFilter, magFilter);
+	sampler.seamlessCubeMap = false;
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], ReferenceParams(TEXTURETYPE_CUBE, sampler));
+
+	// Compare and log.
+	return compareImages(log, referenceFrame, renderedFrame, threshold);
+}
+
+TextureCubeSizeCase::IterateResult TextureCubeSizeCase::iterate (void)
+{
+	// Execute test for all faces.
+	if (!testFace((tcu::CubeFace)m_curFace))
+		m_isOk = false;
+
+	m_curFace += 1;
+
+	if (m_curFace == tcu::CUBEFACE_LAST)
+	{
+		m_testCtx.setTestResult(m_isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								m_isOk ? "Pass"					: "Image comparison failed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+TextureSizeTests::TextureSizeTests (Context& context)
+	: TestCaseGroup(context, "size", "Texture Size Tests")
+{
+}
+
+TextureSizeTests::~TextureSizeTests (void)
+{
+}
+
+void TextureSizeTests::init (void)
+{
+	struct
+	{
+		int	width;
+		int	height;
+	} sizes2D[] =
+	{
+		{   64,   64 }, // Spec-mandated minimum.
+		{   65,   63 },
+		{  512,  512 },
+		{ 1024, 1024 },
+		{ 2048, 2048 }
+	};
+
+	struct
+	{
+		int	width;
+		int	height;
+	} sizesCube[] =
+	{
+		{  15,  15 },
+		{  16,  16 }, // Spec-mandated minimum
+		{  64,  64 },
+		{ 128, 128 },
+		{ 256, 256 },
+		{ 512, 512 }
+	};
+
+	struct
+	{
+		const char*	name;
+		deUint32	format;
+		deUint32	dataType;
+	} formats[] =
+	{
+		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	// 2D cases.
+	tcu::TestCaseGroup* group2D = new tcu::TestCaseGroup(m_testCtx, "2d", "2D Texture Size Tests");
+	addChild(group2D);
+	for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizes2D); sizeNdx++)
+	{
+		int		width	= sizes2D[sizeNdx].width;
+		int		height	= sizes2D[sizeNdx].height;
+		bool	isPOT	= deIsPowerOfTwo32(width) && deIsPowerOfTwo32(height);
+
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
+		{
+			for (int mipmap = 0; mipmap < (isPOT ? 2 : 1); mipmap++)
+			{
+				std::ostringstream name;
+				name << width << "x" << height << "_" << formats[formatNdx].name << (mipmap ? "_mipmap" : "");
+
+				group2D->addChild(new Texture2DSizeCase(m_testCtx, m_context.getRenderContext(), name.str().c_str(), "",
+														formats[formatNdx].format, formats[formatNdx].dataType,
+														width, height, mipmap != 0));
+			}
+		}
+	}
+
+	// Cubemap cases.
+	tcu::TestCaseGroup* groupCube = new tcu::TestCaseGroup(m_testCtx, "cube", "Cubemap Texture Size Tests");
+	addChild(groupCube);
+	for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizesCube); sizeNdx++)
+	{
+		int		width	= sizesCube[sizeNdx].width;
+		int		height	= sizesCube[sizeNdx].height;
+		bool	isPOT	= deIsPowerOfTwo32(width) && deIsPowerOfTwo32(height);
+
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
+		{
+			for (int mipmap = 0; mipmap < (isPOT ? 2 : 1); mipmap++)
+			{
+				std::ostringstream name;
+				name << width << "x" << height << "_" << formats[formatNdx].name << (mipmap ? "_mipmap" : "");
+
+				groupCube->addChild(new TextureCubeSizeCase(m_testCtx, m_context.getRenderContext(), name.str().c_str(), "",
+															formats[formatNdx].format, formats[formatNdx].dataType,
+															width, height, mipmap != 0));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fTextureSizeTests.hpp b/modules/gles2/functional/es2fTextureSizeTests.hpp
new file mode 100644
index 0000000..dbaf155
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureSizeTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FTEXTURESIZETESTS_HPP
+#define _ES2FTEXTURESIZETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture size tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class TextureSizeTests : public TestCaseGroup
+{
+public:
+							TextureSizeTests		(Context& context);
+							~TextureSizeTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureSizeTests		(const TextureSizeTests& other);
+	TextureSizeTests&		operator=				(const TextureSizeTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FTEXTURESIZETESTS_HPP
diff --git a/modules/gles2/functional/es2fTextureSpecificationTests.cpp b/modules/gles2/functional/es2fTextureSpecificationTests.cpp
new file mode 100644
index 0000000..9e7d0dd
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureSpecificationTests.cpp
@@ -0,0 +1,1763 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture specification tests.
+ *
+ * \todo [pyry] Following tests are missing:
+ *  - Specify mipmap incomplete texture, use without mipmaps, re-specify
+ *    as complete and render.
+ *  - Randomly re-specify levels to eventually reach mipmap-complete texture.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fTextureSpecificationTests.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "gluTextureUtil.hpp"
+#include "sglrContextUtil.hpp"
+#include "sglrContextWrapper.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuFormatUtil.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using std::pair;
+using tcu::TestLog;
+using tcu::Vec4;
+using tcu::IVec4;
+using tcu::UVec4;
+
+tcu::TextureFormat mapGLUnsizedInternalFormat (deUint32 internalFormat)
+{
+	using tcu::TextureFormat;
+	switch (internalFormat)
+	{
+		case GL_ALPHA:				return TextureFormat(TextureFormat::A,		TextureFormat::UNORM_INT8);
+		case GL_LUMINANCE:			return TextureFormat(TextureFormat::L,		TextureFormat::UNORM_INT8);
+		case GL_LUMINANCE_ALPHA:	return TextureFormat(TextureFormat::LA,		TextureFormat::UNORM_INT8);
+		case GL_RGB:				return TextureFormat(TextureFormat::RGB,	TextureFormat::UNORM_INT8);
+		case GL_RGBA:				return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8);
+		default:
+			throw tcu::InternalError(string("Can't map GL unsized internal format (") + tcu::toHex(internalFormat).toString() + ") to texture format");
+	}
+}
+
+template <int Size>
+static tcu::Vector<float, Size> randomVector (de::Random& rnd, const tcu::Vector<float, Size>& minVal = tcu::Vector<float, Size>(0.0f), const tcu::Vector<float, Size>& maxVal = tcu::Vector<float, Size>(1.0f))
+{
+	tcu::Vector<float, Size> res;
+	for (int ndx = 0; ndx < Size; ndx++)
+		res[ndx] = rnd.getFloat(minVal[ndx], maxVal[ndx]);
+	return res;
+}
+
+static tcu::IVec4 getPixelFormatCompareDepth (const tcu::PixelFormat& pixelFormat, tcu::TextureFormat textureFormat)
+{
+	switch (textureFormat.order)
+	{
+		case tcu::TextureFormat::L:
+		case tcu::TextureFormat::LA:
+			return tcu::IVec4(pixelFormat.redBits, pixelFormat.redBits, pixelFormat.redBits, pixelFormat.alphaBits);
+		default:
+			return tcu::IVec4(pixelFormat.redBits, pixelFormat.greenBits, pixelFormat.blueBits, pixelFormat.alphaBits);
+	}
+}
+
+static tcu::UVec4 computeCompareThreshold (const tcu::PixelFormat& pixelFormat, tcu::TextureFormat textureFormat)
+{
+	const IVec4		texFormatBits		= tcu::getTextureFormatBitDepth(textureFormat);
+	const IVec4		pixelFormatBits		= getPixelFormatCompareDepth(pixelFormat, textureFormat);
+	const IVec4		accurateFmtBits		= min(pixelFormatBits, texFormatBits);
+	const IVec4		compareBits			= select(accurateFmtBits, IVec4(8), greaterThan(accurateFmtBits, IVec4(0))) - 1;
+
+	return (IVec4(1) << (8-compareBits)).asUint();
+}
+
+class GradientShader : public sglr::ShaderProgram
+{
+public:
+	GradientShader (void)
+		: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
+												"attribute mediump vec2 a_coord;\n"
+												"varying mediump vec2 v_coord;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"	v_coord = a_coord;\n"
+												"}\n")
+					<< sglr::pdec::FragmentSource("varying mediump vec2 v_coord;\n"
+												  "void main (void)\n"
+												  "{\n"
+												  "	mediump float x = v_coord.x;\n"
+												  "	mediump float y = v_coord.y;\n"
+												  "	mediump float f0 = (x + y) * 0.5;\n"
+												  "	mediump float f1 = 0.5 + (x - y) * 0.5;\n"
+												  "	gl_FragColor = vec4(f0, f1, 1.0-f0, 1.0-f1);\n"
+												  "}\n"))
+	{
+	}
+
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			rr::VertexPacket& packet = *packets[packetNdx];
+
+			packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+			packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+		}
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4		coord	= rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
+			const float			x		= coord.x();
+			const float			y		= coord.y();
+			const float			f0		= (x + y) * 0.5f;
+			const float			f1		= 0.5f + (x - y) * 0.5f;
+			const tcu::Vec4		fv		= Vec4(f0, f1, 1.0f-f0, 1.0f-f1);
+
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, tcu::Vec4(f0, f1, 1.0f-f0, 1.0f-f1));
+		}
+	}
+};
+
+class Tex2DShader : public sglr::ShaderProgram
+{
+public:
+	Tex2DShader (void)
+		: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::Uniform("u_sampler0", glu::TYPE_SAMPLER_2D)
+					<< sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
+												"attribute mediump vec2 a_coord;\n"
+												"varying mediump vec2 v_coord;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"	v_coord = a_coord;\n"
+												"}\n")
+					<< sglr::pdec::FragmentSource("uniform sampler2D u_sampler0;\n"
+												  "varying mediump vec2 v_coord;\n"
+												  "void main (void)\n"
+												  "{\n"
+												  "	gl_FragColor = texture2D(u_sampler0, v_coord);\n"
+												  "}\n"))
+	{
+	}
+
+	void setUniforms (sglr::Context& ctx, deUint32 program) const
+	{
+		ctx.useProgram(program);
+		ctx.uniform1i(ctx.getUniformLocation(program, "u_sampler0"), 0);
+	}
+
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			rr::VertexPacket& packet = *packets[packetNdx];
+
+			packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+			packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+		}
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		tcu::Vec2 texCoords[4];
+		tcu::Vec4 colors[4];
+
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			// setup tex coords
+			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			{
+				const tcu::Vec4	coord = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
+				texCoords[fragNdx] = tcu::Vec2(coord.x(), coord.y());
+			}
+
+			// Sample
+			m_uniforms[0].sampler.tex2D->sample4(colors, texCoords);
+
+			// Write out
+			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+				rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, colors[fragNdx]);
+		}
+	}
+};
+
+static const char* s_cubeSwizzles[] =
+{
+	"vec3(-1, -y, +x)",
+	"vec3(+1, -y, -x)",
+	"vec3(+x, -1, -y)",
+	"vec3(+x, +1, +y)",
+	"vec3(-x, -y, -1)",
+	"vec3(+x, -y, +1)"
+};
+
+class TexCubeShader : public sglr::ShaderProgram
+{
+public:
+	TexCubeShader (tcu::CubeFace face)
+		: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::Uniform("u_sampler0", glu::TYPE_SAMPLER_CUBE)
+					<< sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
+												"attribute mediump vec2 a_coord;\n"
+												"varying mediump vec2 v_coord;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"	v_coord = a_coord;\n"
+												"}\n")
+					<< sglr::pdec::FragmentSource(string("") +
+												  "uniform samplerCube u_sampler0;\n"
+												  "varying mediump vec2 v_coord;\n"
+												  "void main (void)\n"
+												  "{\n"
+												  "	mediump float x = v_coord.x*2.0 - 1.0;\n"
+												  "	mediump float y = v_coord.y*2.0 - 1.0;\n"
+												  "	gl_FragColor = textureCube(u_sampler0, " + s_cubeSwizzles[face] + ");\n"
+												  "}\n"))
+		, m_face(face)
+	{
+	}
+
+	void setUniforms (sglr::Context& ctx, deUint32 program) const
+	{
+		ctx.useProgram(program);
+		ctx.uniform1i(ctx.getUniformLocation(program, "u_sampler0"), 0);
+	}
+
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			rr::VertexPacket& packet = *packets[packetNdx];
+
+			packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+			packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+		}
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		tcu::Vec3 texCoords[4];
+		tcu::Vec4 colors[4];
+
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			// setup tex coords
+			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			{
+				const tcu::Vec4	coord	= rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
+				const float		x		= coord.x()*2.0f - 1.0f;
+				const float		y		= coord.y()*2.0f - 1.0f;
+
+				// Swizzle tex coords
+				switch (m_face)
+				{
+					case tcu::CUBEFACE_NEGATIVE_X:	texCoords[fragNdx] = tcu::Vec3(-1.0f,    -y,    +x);		break;
+					case tcu::CUBEFACE_POSITIVE_X:	texCoords[fragNdx] = tcu::Vec3(+1.0f,    -y,    -x);		break;
+					case tcu::CUBEFACE_NEGATIVE_Y:	texCoords[fragNdx] = tcu::Vec3(   +x, -1.0f,    -y);		break;
+					case tcu::CUBEFACE_POSITIVE_Y:	texCoords[fragNdx] = tcu::Vec3(   +x, +1.0f,    +y);		break;
+					case tcu::CUBEFACE_NEGATIVE_Z:	texCoords[fragNdx] = tcu::Vec3(   -x,    -y, -1.0f);		break;
+					case tcu::CUBEFACE_POSITIVE_Z:	texCoords[fragNdx] = tcu::Vec3(   +x,    -y, +1.0f);		break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+
+			// Sample
+			m_uniforms[0].sampler.texCube->sample4(colors, texCoords);
+
+			// Write out
+			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+				rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, colors[fragNdx]);
+		}
+	}
+private:
+	tcu::CubeFace m_face;
+};
+
+enum TextureType
+{
+	TEXTURETYPE_2D = 0,
+	TEXTURETYPE_CUBE,
+
+	TEXTURETYPE_LAST
+};
+
+enum Flags
+{
+	MIPMAPS		= (1<<0)
+};
+
+static const deUint32 s_cubeMapFaces[] =
+{
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+};
+
+class TextureSpecCase : public TestCase, public sglr::ContextWrapper
+{
+public:
+								TextureSpecCase		(Context& context, const char* name, const char* desc, const TextureType type, const tcu::TextureFormat format, const deUint32 flags, int width, int height);
+								~TextureSpecCase	(void);
+
+	IterateResult				iterate				(void);
+
+protected:
+	virtual void				createTexture		(void) = DE_NULL;
+
+	const TextureType			m_texType;
+	const tcu::TextureFormat	m_texFormat;
+	const deUint32				m_flags;
+	const int					m_width;
+	const int					m_height;
+
+private:
+								TextureSpecCase		(const TextureSpecCase& other);
+	TextureSpecCase&			operator=			(const TextureSpecCase& other);
+
+	void						verifyTex2D			(sglr::GLContext& gles2Context, sglr::ReferenceContext& refContext);
+	void						verifyTexCube		(sglr::GLContext& gles2Context, sglr::ReferenceContext& refContext);
+
+	void						renderTex2D			(tcu::Surface& dst, int width, int height);
+	void						renderTexCube		(tcu::Surface& dst, int width, int height, tcu::CubeFace face);
+
+	void						readPixels			(tcu::Surface& dst, int x, int y, int width, int height);
+
+	// \todo [2012-03-27 pyry] Renderer should be extended to allow custom attributes, that would clean up this cubemap mess.
+	Tex2DShader					m_tex2DShader;
+	TexCubeShader				m_texCubeNegXShader;
+	TexCubeShader				m_texCubePosXShader;
+	TexCubeShader				m_texCubeNegYShader;
+	TexCubeShader				m_texCubePosYShader;
+	TexCubeShader				m_texCubeNegZShader;
+	TexCubeShader				m_texCubePosZShader;
+};
+
+TextureSpecCase::TextureSpecCase (Context& context, const char* name, const char* desc, const TextureType type, const tcu::TextureFormat format, const deUint32 flags, int width, int height)
+	: TestCase				(context, name, desc)
+	, m_texType				(type)
+	, m_texFormat			(format)
+	, m_flags				(flags)
+	, m_width				(width)
+	, m_height				(height)
+	, m_texCubeNegXShader	(tcu::CUBEFACE_NEGATIVE_X)
+	, m_texCubePosXShader	(tcu::CUBEFACE_POSITIVE_X)
+	, m_texCubeNegYShader	(tcu::CUBEFACE_NEGATIVE_Y)
+	, m_texCubePosYShader	(tcu::CUBEFACE_POSITIVE_Y)
+	, m_texCubeNegZShader	(tcu::CUBEFACE_NEGATIVE_Z)
+	, m_texCubePosZShader	(tcu::CUBEFACE_POSITIVE_Z)
+{
+}
+
+TextureSpecCase::~TextureSpecCase (void)
+{
+}
+
+TextureSpecCase::IterateResult TextureSpecCase::iterate (void)
+{
+	glu::RenderContext&			renderCtx				= TestCase::m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget			= renderCtx.getRenderTarget();
+	tcu::TestLog&				log						= m_testCtx.getLog();
+
+	DE_ASSERT(m_width <= 256 && m_height <= 256);
+	if (renderTarget.getWidth() < m_width || renderTarget.getHeight() < m_height)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Context size, and viewport for GLES2
+	de::Random		rnd			(deStringHash(getName()));
+	int				width		= deMin32(renderTarget.getWidth(),	256);
+	int				height		= deMin32(renderTarget.getHeight(),	256);
+	int				x			= rnd.getInt(0, renderTarget.getWidth()		- width);
+	int				y			= rnd.getInt(0, renderTarget.getHeight()	- height);
+
+	// Contexts.
+	sglr::GLContext					gles2Context	(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(x, y, width, height));
+	sglr::ReferenceContextBuffers	refBuffers		(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), 0 /* depth */, 0 /* stencil */, width, height);
+	sglr::ReferenceContext			refContext		(sglr::ReferenceContextLimits(renderCtx), refBuffers.getColorbuffer(), refBuffers.getDepthbuffer(), refBuffers.getStencilbuffer());
+
+	// Clear color buffer.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles2Context);
+		glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
+		glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	}
+
+	// Construct texture using both GLES2 and reference contexts.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles2Context);
+		createTexture();
+		TCU_CHECK(glGetError() == GL_NO_ERROR);
+	}
+
+	// Setup texture filtering state.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles2Context);
+
+		deUint32 texTarget = m_texType == TEXTURETYPE_2D ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP;
+		glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER,	(m_flags & MIPMAPS) ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST);
+		glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+		glTexParameteri(texTarget, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(texTarget, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+	}
+
+	// Initialize case result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Disable logging.
+	gles2Context.enableLogging(0);
+
+	// Verify results.
+	switch (m_texType)
+	{
+		case TEXTURETYPE_2D:	verifyTex2D		(gles2Context, refContext);	break;
+		case TEXTURETYPE_CUBE:	verifyTexCube	(gles2Context, refContext);	break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	return STOP;
+}
+
+void TextureSpecCase::verifyTex2D (sglr::GLContext& gles2Context, sglr::ReferenceContext& refContext)
+{
+	int numLevels = (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+
+	DE_ASSERT(m_texType == TEXTURETYPE_2D);
+
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		int				levelW		= de::max(1, m_width >> levelNdx);
+		int				levelH		= de::max(1, m_height >> levelNdx);
+		tcu::Surface	reference;
+		tcu::Surface	result;
+
+		if (levelW <= 2 || levelH <= 2)
+			continue; // Don't bother checking.
+
+		// Render with GLES2
+		setContext(&gles2Context);
+		renderTex2D(result, levelW, levelH);
+
+		// Render reference.
+		setContext(&refContext);
+		renderTex2D(reference, levelW, levelH);
+
+		{
+			tcu::UVec4	threshold	= computeCompareThreshold(m_context.getRenderTarget().getPixelFormat(), m_texFormat);
+			bool		isOk		= tcu::intThresholdCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference.getAccess(), result.getAccess(), threshold,
+															   levelNdx == 0 ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);
+
+			if (!isOk)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+				break;
+			}
+		}
+	}
+}
+
+void TextureSpecCase::verifyTexCube (sglr::GLContext& gles2Context, sglr::ReferenceContext& refContext)
+{
+	int numLevels = (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+
+	DE_ASSERT(m_texType == TEXTURETYPE_CUBE);
+
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		int		levelW	= de::max(1, m_width >> levelNdx);
+		int		levelH	= de::max(1, m_height >> levelNdx);
+		bool	isOk	= true;
+
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			tcu::Surface	reference;
+			tcu::Surface	result;
+
+			if (levelW <= 2 || levelH <= 2)
+				continue; // Don't bother checking.
+
+			// Render with GLES2
+			setContext(&gles2Context);
+			renderTexCube(result, levelW, levelH, (tcu::CubeFace)face);
+
+			// Render reference.
+			setContext(&refContext);
+			renderTexCube(reference, levelW, levelH, (tcu::CubeFace)face);
+
+			const float	threshold	= 0.02f;
+			isOk = tcu::fuzzyCompare(m_testCtx.getLog(), "Result", (string("Image comparison result: ") + de::toString((tcu::CubeFace)face)).c_str(), reference, result, threshold,
+									 levelNdx == 0 ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);
+
+			if (!isOk)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+				break;
+			}
+		}
+
+		if (!isOk)
+			break;
+	}
+}
+
+void TextureSpecCase::renderTex2D (tcu::Surface& dst, int width, int height)
+{
+	int			targetW		= getWidth();
+	int			targetH		= getHeight();
+
+	float		w			= (float)width	/ (float)targetW;
+	float		h			= (float)height	/ (float)targetH;
+
+	deUint32	shaderID	= getCurrentContext()->createProgram(&m_tex2DShader);
+
+	m_tex2DShader.setUniforms(*getCurrentContext(), shaderID);
+	sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(-1.0f + w*2.0f, -1.0f + h*2.0f, 0.0f));
+
+	// Read pixels back.
+	readPixels(dst, 0, 0, width, height);
+}
+
+void TextureSpecCase::renderTexCube (tcu::Surface& dst, int width, int height, tcu::CubeFace face)
+{
+	int		targetW		= getWidth();
+	int		targetH		= getHeight();
+
+	float	w			= (float)width	/ (float)targetW;
+	float	h			= (float)height	/ (float)targetH;
+
+	TexCubeShader* shaders[] =
+	{
+		&m_texCubeNegXShader,
+		&m_texCubePosXShader,
+		&m_texCubeNegYShader,
+		&m_texCubePosYShader,
+		&m_texCubeNegZShader,
+		&m_texCubePosZShader
+	};
+
+	deUint32	shaderID	= getCurrentContext()->createProgram(shaders[face]);
+
+	shaders[face]->setUniforms(*getCurrentContext(), shaderID);
+	sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(-1.0f + w*2.0f, -1.0f + h*2.0f, 0.0f));
+
+	// Read pixels back.
+	readPixels(dst, 0, 0, width, height);
+}
+
+void TextureSpecCase::readPixels (tcu::Surface& dst, int x, int y, int width, int height)
+{
+	dst.setSize(width, height);
+	glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr());
+}
+
+// Basic TexImage2D() with 2D texture usage
+class BasicTexImage2DCase : public TextureSpecCase
+{
+public:
+	BasicTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(fmt);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			Vec4	gMin		= randomVector<4>(rnd);
+			Vec4	gMax		= randomVector<4>(rnd);
+
+			levelData.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Basic TexImage2D() with cubemap usage
+class BasicTexImageCubeCase : public TextureSpecCase
+{
+public:
+	BasicTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(fmt);
+		de::Random			rnd			(deStringHash(getName()));
+
+		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			levelData.setSize(levelW, levelH);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				Vec4 gMin = randomVector<4>(rnd);
+				Vec4 gMax = randomVector<4>(rnd);
+
+				tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelW, levelH, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
+			}
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Randomized 2D texture specification using TexImage2D
+class RandomOrderTexImage2DCase : public TextureSpecCase
+{
+public:
+	RandomOrderTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(fmt);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		vector<int>			levels		(numLevels);
+
+		for (int i = 0; i < numLevels; i++)
+			levels[i] = i;
+		rnd.shuffle(levels.begin(), levels.end());
+
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelNdx	= levels[ndx];
+			int		levelW		= de::max(1, m_width	>> levelNdx);
+			int		levelH		= de::max(1, m_height	>> levelNdx);
+			Vec4	gMin		= randomVector<4>(rnd);
+			Vec4	gMax		= randomVector<4>(rnd);
+
+			levelData.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage2D(GL_TEXTURE_2D, levelNdx, m_format, levelW, levelH, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Randomized cubemap texture specification using TexImage2D
+class RandomOrderTexImageCubeCase : public TextureSpecCase
+{
+public:
+	RandomOrderTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(fmt);
+		de::Random			rnd			(deStringHash(getName()));
+
+		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// Level-face pairs.
+		vector<pair<int, tcu::CubeFace> >	images	(numLevels*6);
+
+		for (int ndx = 0; ndx < numLevels; ndx++)
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+				images[ndx*6 + face] = std::make_pair(ndx, (tcu::CubeFace)face);
+
+		rnd.shuffle(images.begin(), images.end());
+
+		for (int ndx = 0; ndx < (int)images.size(); ndx++)
+		{
+			int				levelNdx	= images[ndx].first;
+			tcu::CubeFace	face		= images[ndx].second;
+			int				levelW		= de::max(1, m_width >> levelNdx);
+			int				levelH		= de::max(1, m_height >> levelNdx);
+			Vec4			gMin		= randomVector<4>(rnd);
+			Vec4			gMax		= randomVector<4>(rnd);
+
+			levelData.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage2D(s_cubeMapFaces[face], levelNdx, m_format, levelW, levelH, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+static inline int getRowPitch (const tcu::TextureFormat& transferFmt, int rowLen, int alignment)
+{
+	int basePitch = transferFmt.getPixelSize()*rowLen;
+	return alignment*(basePitch/alignment + ((basePitch % alignment) ? 1 : 0));
+}
+
+// TexImage2D() unpack alignment case.
+class TexImage2DAlignCase : public TextureSpecCase
+{
+public:
+	TexImage2DAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height, int alignment)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+		, m_alignment		(alignment)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32			tex			= 0;
+		vector<deUint8>		data;
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);
+
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			Vec4	colorA		(1.0f, 0.0f, 0.0f, 1.0f);
+			Vec4	colorB		(0.0f, 1.0f, 0.0f, 1.0f);
+			int		rowPitch	= getRowPitch(fmt, levelW, m_alignment);
+			int		cellSize	= de::max(1, de::min(levelW >> 2, levelH >> 2));
+
+			data.resize(rowPitch*levelH);
+			tcu::fillWithGrid(tcu::PixelBufferAccess(fmt, levelW, levelH, 1, rowPitch, 0, &data[0]), cellSize, colorA, colorB);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, &data[0]);
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+	int			m_alignment;
+};
+
+// TexImage2D() unpack alignment case.
+class TexImageCubeAlignCase : public TextureSpecCase
+{
+public:
+	TexImageCubeAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height, int alignment)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+		, m_alignment		(alignment)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32			tex			= 0;
+		vector<deUint8>		data;
+
+		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);
+
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			int		rowPitch	= getRowPitch(fmt, levelW, m_alignment);
+			Vec4	colorA		(1.0f, 0.0f, 0.0f, 1.0f);
+			Vec4	colorB		(0.0f, 1.0f, 0.0f, 1.0f);
+			int		cellSize	= de::max(1, de::min(levelW >> 2, levelH >> 2));
+
+			data.resize(rowPitch*levelH);
+			tcu::fillWithGrid(tcu::PixelBufferAccess(fmt, levelW, levelH, 1, rowPitch, 0, &data[0]), cellSize, colorA, colorB);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelW, levelH, 0, m_format, m_dataType, &data[0]);
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+	int			m_alignment;
+};
+
+// Basic TexSubImage2D() with 2D texture usage
+class BasicTexSubImage2DCase : public TextureSpecCase
+{
+public:
+	BasicTexSubImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32			tex			= 0;
+		tcu::TextureLevel	data		(fmt);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// First specify full texture.
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			Vec4	gMin		= randomVector<4>(rnd);
+			Vec4	gMax		= randomVector<4>(rnd);
+
+			data.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, data.getAccess().getDataPtr());
+		}
+
+		// Re-specify parts of each level.
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			int		w			= rnd.getInt(1, levelW);
+			int		h			= rnd.getInt(1, levelH);
+			int		x			= rnd.getInt(0, levelW-w);
+			int		y			= rnd.getInt(0, levelH-h);
+
+			Vec4	colorA		= randomVector<4>(rnd);
+			Vec4	colorB		= randomVector<4>(rnd);
+			int		cellSize	= rnd.getInt(2, 16);
+
+			data.setSize(w, h);
+			tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);
+
+			glTexSubImage2D(GL_TEXTURE_2D, ndx, x, y, w, h, m_format, m_dataType, data.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Basic TexSubImage2D() with cubemap usage
+class BasicTexSubImageCubeCase : public TextureSpecCase
+{
+public:
+	BasicTexSubImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32			tex			= 0;
+		tcu::TextureLevel	data		(fmt);
+		de::Random			rnd			(deStringHash(getName()));
+
+		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			data.setSize(levelW, levelH);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				Vec4 gMin = randomVector<4>(rnd);
+				Vec4 gMax = randomVector<4>(rnd);
+
+				tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);
+
+				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelW, levelH, 0, m_format, m_dataType, data.getAccess().getDataPtr());
+			}
+		}
+
+		// Re-specify parts of each face and level.
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				int		w			= rnd.getInt(1, levelW);
+				int		h			= rnd.getInt(1, levelH);
+				int		x			= rnd.getInt(0, levelW-w);
+				int		y			= rnd.getInt(0, levelH-h);
+
+				Vec4	colorA		= randomVector<4>(rnd);
+				Vec4	colorB		= randomVector<4>(rnd);
+				int		cellSize	= rnd.getInt(2, 16);
+
+				data.setSize(w, h);
+				tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);
+
+				glTexSubImage2D(s_cubeMapFaces[face], ndx, x, y, w, h, m_format, m_dataType, data.getAccess().getDataPtr());
+			}
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// TexSubImage2D() to texture initialized with empty data
+class TexSubImage2DEmptyTexCase : public TextureSpecCase
+{
+public:
+	TexSubImage2DEmptyTexCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32			tex			= 0;
+		tcu::TextureLevel	data		(fmt);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// First allocate storage for each level.
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, DE_NULL);
+		}
+
+		// Specify pixel data to all levels using glTexSubImage2D()
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			Vec4	gMin		= randomVector<4>(rnd);
+			Vec4	gMax		= randomVector<4>(rnd);
+
+			data.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);
+
+			glTexSubImage2D(GL_TEXTURE_2D, ndx, 0, 0, levelW, levelH, m_format, m_dataType, data.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// TexSubImage2D() to empty cubemap texture
+class TexSubImageCubeEmptyTexCase : public TextureSpecCase
+{
+public:
+	TexSubImageCubeEmptyTexCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32			tex			= 0;
+		tcu::TextureLevel	data		(fmt);
+		de::Random			rnd			(deStringHash(getName()));
+
+		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// Specify storage for each level.
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelW, levelH, 0, m_format, m_dataType, DE_NULL);
+		}
+
+		// Specify data using glTexSubImage2D()
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			data.setSize(levelW, levelH);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				Vec4 gMin = randomVector<4>(rnd);
+				Vec4 gMax = randomVector<4>(rnd);
+
+				tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);
+
+				glTexSubImage2D(s_cubeMapFaces[face], ndx, 0, 0, levelW, levelH, m_format, m_dataType, data.getAccess().getDataPtr());
+			}
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// TexSubImage2D() unpack alignment with 2D texture
+class TexSubImage2DAlignCase : public TextureSpecCase
+{
+public:
+	TexSubImage2DAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height, int subX, int subY, int subW, int subH, int alignment)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), 0 /* Mipmaps are never used */, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+		, m_subX			(subX)
+		, m_subY			(subY)
+		, m_subW			(subW)
+		, m_subH			(subH)
+		, m_alignment		(alignment)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		deUint32			tex			= 0;
+		vector<deUint8>		data;
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+
+		// Specify base level.
+		data.resize(fmt.getPixelSize()*m_width*m_height);
+		tcu::fillWithComponentGradients(tcu::PixelBufferAccess(fmt, m_width, m_height, 1, &data[0]), Vec4(0.0f), Vec4(1.0f));
+
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_format, m_width, m_height, 0, m_format, m_dataType, &data[0]);
+
+		// Re-specify subrectangle.
+		int rowPitch = getRowPitch(fmt, m_subW, m_alignment);
+		data.resize(rowPitch*m_subH);
+		tcu::fillWithGrid(tcu::PixelBufferAccess(fmt, m_subW, m_subH, 1, rowPitch, 0, &data[0]), 4, Vec4(1.0f, 0.0f, 0.0f, 1.0f), Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);
+		glTexSubImage2D(GL_TEXTURE_2D, 0, m_subX, m_subY, m_subW, m_subH, m_format, m_dataType, &data[0]);
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+	int			m_subX;
+	int			m_subY;
+	int			m_subW;
+	int			m_subH;
+	int			m_alignment;
+};
+
+// TexSubImage2D() unpack alignment with cubemap texture
+class TexSubImageCubeAlignCase : public TextureSpecCase
+{
+public:
+	TexSubImageCubeAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height, int subX, int subY, int subW, int subH, int alignment)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), 0 /* Mipmaps are never used */, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+		, m_subX			(subX)
+		, m_subY			(subY)
+		, m_subW			(subW)
+		, m_subH			(subH)
+		, m_alignment		(alignment)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat	fmt			= m_texFormat;
+		deUint32			tex			= 0;
+		vector<deUint8>		data;
+
+		DE_ASSERT(m_width == m_height);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+
+		// Specify base level.
+		data.resize(fmt.getPixelSize()*m_width*m_height);
+		tcu::fillWithComponentGradients(tcu::PixelBufferAccess(fmt, m_width, m_height, 1, &data[0]), Vec4(0.0f), Vec4(1.0f));
+
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			glTexImage2D(s_cubeMapFaces[face], 0, m_format, m_width, m_height, 0, m_format, m_dataType, &data[0]);
+
+		// Re-specify subrectangle.
+		int rowPitch = getRowPitch(fmt, m_subW, m_alignment);
+		data.resize(rowPitch*m_subH);
+		tcu::fillWithGrid(tcu::PixelBufferAccess(fmt, m_subW, m_subH, 1, rowPitch, 0, &data[0]), 4, Vec4(1.0f, 0.0f, 0.0f, 1.0f), Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			glTexSubImage2D(s_cubeMapFaces[face], 0, m_subX, m_subY, m_subW, m_subH, m_format, m_dataType, &data[0]);
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+	int			m_subX;
+	int			m_subY;
+	int			m_subW;
+	int			m_subH;
+	int			m_alignment;
+};
+
+
+
+// Basic CopyTexImage2D() with 2D texture usage
+class BasicCopyTexImage2DCase : public TextureSpecCase
+{
+public:
+	BasicCopyTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, mapGLUnsizedInternalFormat(internalFormat), flags, width, height)
+		, m_internalFormat	(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
+		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
+		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
+		tcu::TextureFormat			fmt				= m_texFormat;
+		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
+		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
+		int							numLevels		= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32					tex				= 0;
+		de::Random					rnd				(deStringHash(getName()));
+		GradientShader				shader;
+		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);
+
+		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
+			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);
+
+		// Fill render target with gradient.
+		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			int		x			= rnd.getInt(0, getWidth()	- levelW);
+			int		y			= rnd.getInt(0, getHeight()	- levelH);
+
+			glCopyTexImage2D(GL_TEXTURE_2D, ndx, m_internalFormat, x, y, levelW, levelH, 0);
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Basic CopyTexImage2D() with cubemap usage
+class BasicCopyTexImageCubeCase : public TextureSpecCase
+{
+public:
+	BasicCopyTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, mapGLUnsizedInternalFormat(internalFormat), flags, width, height)
+		, m_internalFormat	(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
+		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
+		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
+		tcu::TextureFormat			fmt				= m_texFormat;
+		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
+		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
+		int							numLevels		= (m_flags & MIPMAPS) ? deLog2Floor32(m_width)+1 : 1;
+		deUint32					tex				= 0;
+		de::Random					rnd				(deStringHash(getName()));
+		GradientShader				shader;
+		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);
+
+		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.
+
+		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
+			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);
+
+		// Fill render target with gradient.
+		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int levelW = de::max(1, m_width >> ndx);
+			int levelH = de::max(1, m_height >> ndx);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				int x = rnd.getInt(0, getWidth()	- levelW);
+				int y = rnd.getInt(0, getHeight()	- levelH);
+
+				glCopyTexImage2D(s_cubeMapFaces[face], ndx, m_internalFormat, x, y, levelW, levelH, 0);
+			}
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+
+
+// Basic CopyTexSubImage2D() with 2D texture usage
+class BasicCopyTexSubImage2DCase : public TextureSpecCase
+{
+public:
+	BasicCopyTexSubImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
+		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
+		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
+		tcu::TextureFormat			fmt				= m_texFormat;
+		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
+		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
+		int							numLevels		= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32					tex				= 0;
+		tcu::TextureLevel			data			(fmt);
+		de::Random					rnd				(deStringHash(getName()));
+		GradientShader				shader;
+		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);
+
+		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
+			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// First specify full texture.
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			Vec4	colorA		= randomVector<4>(rnd);
+			Vec4	colorB		= randomVector<4>(rnd);
+			int		cellSize	= rnd.getInt(2, 16);
+
+			data.setSize(levelW, levelH);
+			tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, data.getAccess().getDataPtr());
+		}
+
+		// Fill render target with gradient.
+		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));
+
+		// Re-specify parts of each level.
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			int		w			= rnd.getInt(1, levelW);
+			int		h			= rnd.getInt(1, levelH);
+			int		xo			= rnd.getInt(0, levelW-w);
+			int		yo			= rnd.getInt(0, levelH-h);
+
+			int		x			= rnd.getInt(0, getWidth() - w);
+			int		y			= rnd.getInt(0, getHeight() - h);
+
+			glCopyTexSubImage2D(GL_TEXTURE_2D, ndx, xo, yo, x, y, w, h);
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Basic CopyTexSubImage2D() with cubemap usage
+class BasicCopyTexSubImageCubeCase : public TextureSpecCase
+{
+public:
+	BasicCopyTexSubImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
+		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
+		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
+		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
+		tcu::TextureFormat			fmt				= m_texFormat;
+		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
+		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
+		int							numLevels		= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
+		deUint32					tex				= 0;
+		tcu::TextureLevel			data			(fmt);
+		de::Random					rnd				(deStringHash(getName()));
+		GradientShader				shader;
+		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);
+
+		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.
+
+		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
+			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			data.setSize(levelW, levelH);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				Vec4	colorA		= randomVector<4>(rnd);
+				Vec4	colorB		= randomVector<4>(rnd);
+				int		cellSize	= rnd.getInt(2, 16);
+
+				tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);
+				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelW, levelH, 0, m_format, m_dataType, data.getAccess().getDataPtr());
+			}
+		}
+
+		// Fill render target with gradient.
+		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));
+
+		// Re-specify parts of each face and level.
+		for (int ndx = 0; ndx < numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				int		w			= rnd.getInt(1, levelW);
+				int		h			= rnd.getInt(1, levelH);
+				int		xo			= rnd.getInt(0, levelW-w);
+				int		yo			= rnd.getInt(0, levelH-h);
+
+				int		x			= rnd.getInt(0, getWidth() - w);
+				int		y			= rnd.getInt(0, getHeight() - h);
+
+				glCopyTexSubImage2D(s_cubeMapFaces[face], ndx, xo, yo, x, y, w, h);
+			}
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+TextureSpecificationTests::TextureSpecificationTests (Context& context)
+	: TestCaseGroup(context, "specification", "Texture Specification Tests")
+{
+}
+
+TextureSpecificationTests::~TextureSpecificationTests (void)
+{
+}
+
+void TextureSpecificationTests::init (void)
+{
+	struct
+	{
+		const char*	name;
+		deUint32	format;
+		deUint32	dataType;
+	} texFormats[] =
+	{
+		{ "a8",			GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "la88",		GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
+		{ "rgb565",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgba5551",	GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	// Basic TexImage2D usage.
+	{
+		tcu::TestCaseGroup* basicTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_teximage2d", "Basic glTexImage2D() usage");
+		addChild(basicTexImageGroup);
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+		{
+			const char*	fmtName		= texFormats[formatNdx].name;
+			deUint32	format		= texFormats[formatNdx].format;
+			deUint32	dataType	= texFormats[formatNdx].dataType;
+			const int	tex2DWidth	= 64;
+			const int	tex2DHeight	= 128;
+			const int	texCubeSize	= 64;
+
+			basicTexImageGroup->addChild(new BasicTexImage2DCase	(m_context,	(string(fmtName) + "_2d").c_str(),		"",	format, dataType, MIPMAPS, tex2DWidth, tex2DHeight));
+			basicTexImageGroup->addChild(new BasicTexImageCubeCase	(m_context,	(string(fmtName) + "_cube").c_str(),	"",	format, dataType, MIPMAPS, texCubeSize, texCubeSize));
+		}
+	}
+
+	// Randomized TexImage2D order.
+	{
+		tcu::TestCaseGroup* randomTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "random_teximage2d", "Randomized glTexImage2D() usage");
+		addChild(randomTexImageGroup);
+
+		de::Random rnd(9);
+
+		// 2D cases.
+		for (int ndx = 0; ndx < 10; ndx++)
+		{
+			int		formatNdx	= rnd.getInt(0, DE_LENGTH_OF_ARRAY(texFormats)-1);
+			int		width		= 1 << rnd.getInt(2, 8);
+			int		height		= 1 << rnd.getInt(2, 8);
+
+			randomTexImageGroup->addChild(new RandomOrderTexImage2DCase(m_context, (string("2d_") + de::toString(ndx)).c_str(), "", texFormats[formatNdx].format, texFormats[formatNdx].dataType, MIPMAPS, width, height));
+		}
+
+		// Cubemap cases.
+		for (int ndx = 0; ndx < 10; ndx++)
+		{
+			int		formatNdx	= rnd.getInt(0, DE_LENGTH_OF_ARRAY(texFormats)-1);
+			int		size		= 1 << rnd.getInt(2, 8);
+
+			randomTexImageGroup->addChild(new RandomOrderTexImageCubeCase(m_context, (string("cube_") + de::toString(ndx)).c_str(), "", texFormats[formatNdx].format, texFormats[formatNdx].dataType, MIPMAPS, size, size));
+		}
+	}
+
+	// TexImage2D unpack alignment.
+	{
+		tcu::TestCaseGroup* alignGroup = new tcu::TestCaseGroup(m_testCtx, "teximage2d_align", "glTexImage2D() unpack alignment tests");
+		addChild(alignGroup);
+
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_l8_4_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			MIPMAPS,	 4, 8, 8));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_l8_63_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 30, 1));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_l8_63_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 30, 2));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_l8_63_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 30, 4));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_l8_63_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 30, 8));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4444_51_1",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 30, 1));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4444_51_2",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 30, 2));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4444_51_4",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 30, 4));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4444_51_8",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 30, 8));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb888_39_1",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 43, 1));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb888_39_2",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 43, 2));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb888_39_4",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 43, 4));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb888_39_8",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 43, 8));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8888_47_1",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 27, 1));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8888_47_2",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 27, 2));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8888_47_4",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 27, 4));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8888_47_8",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 27, 8));
+
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_l8_4_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			MIPMAPS,	 4, 4, 8));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_l8_63_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 63, 1));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_l8_63_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 63, 2));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_l8_63_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 63, 4));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_l8_63_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 63, 8));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4444_51_1",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 51, 1));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4444_51_2",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 51, 2));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4444_51_4",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 51, 4));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4444_51_8",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 51, 8));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb888_39_1",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 39, 1));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb888_39_2",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 39, 2));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb888_39_4",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 39, 4));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb888_39_8",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 39, 8));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8888_47_1",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 47, 1));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8888_47_2",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 47, 2));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8888_47_4",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 47, 4));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8888_47_8",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 47, 8));
+	}
+
+	// Basic TexSubImage2D usage.
+	{
+		tcu::TestCaseGroup* basicTexSubImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_texsubimage2d", "Basic glTexSubImage2D() usage");
+		addChild(basicTexSubImageGroup);
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+		{
+			const char*	fmtName		= texFormats[formatNdx].name;
+			deUint32	format		= texFormats[formatNdx].format;
+			deUint32	dataType	= texFormats[formatNdx].dataType;
+			const int	tex2DWidth	= 64;
+			const int	tex2DHeight	= 128;
+			const int	texCubeSize	= 64;
+
+			basicTexSubImageGroup->addChild(new BasicTexSubImage2DCase		(m_context,	(string(fmtName) + "_2d").c_str(),		"",	format, dataType, MIPMAPS, tex2DWidth, tex2DHeight));
+			basicTexSubImageGroup->addChild(new BasicTexSubImageCubeCase	(m_context,	(string(fmtName) + "_cube").c_str(),	"",	format, dataType, MIPMAPS, texCubeSize, texCubeSize));
+		}
+	}
+
+	// TexSubImage2D to empty texture.
+	{
+		tcu::TestCaseGroup* texSubImageEmptyTexGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage2d_empty_tex", "glTexSubImage2D() to texture that has storage but no data");
+		addChild(texSubImageEmptyTexGroup);
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+		{
+			const char*	fmtName		= texFormats[formatNdx].name;
+			deUint32	format		= texFormats[formatNdx].format;
+			deUint32	dataType	= texFormats[formatNdx].dataType;
+			const int	tex2DWidth	= 64;
+			const int	tex2DHeight	= 32;
+			const int	texCubeSize	= 32;
+
+			texSubImageEmptyTexGroup->addChild(new TexSubImage2DEmptyTexCase	(m_context,	(string(fmtName) + "_2d").c_str(),		"",	format, dataType, MIPMAPS, tex2DWidth, tex2DHeight));
+			texSubImageEmptyTexGroup->addChild(new TexSubImageCubeEmptyTexCase	(m_context,	(string(fmtName) + "_cube").c_str(),	"",	format, dataType, MIPMAPS, texCubeSize, texCubeSize));
+		}
+	}
+
+	// TexSubImage2D alignment cases.
+	{
+		tcu::TestCaseGroup* alignGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage2d_align", "glTexSubImage2D() unpack alignment tests");
+		addChild(alignGroup);
+
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_1_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 1));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_1_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 2));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_1_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 4));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_1_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 8));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_63_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 1));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_63_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 2));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_63_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 4));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_63_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 8));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4444_51_1",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 1));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4444_51_2",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 2));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4444_51_4",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 4));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4444_51_8",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 8));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb888_39_1",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 1));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb888_39_2",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 2));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb888_39_4",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 4));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb888_39_8",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 8));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8888_47_1",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 1));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8888_47_2",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 2));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8888_47_4",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 4));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8888_47_8",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 8));
+
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_1_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 1));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_1_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 2));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_1_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 4));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_1_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 8));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_63_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 1));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_63_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 2));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_63_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 4));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_63_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 8));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4444_51_1",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 1));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4444_51_2",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 2));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4444_51_4",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 4));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4444_51_8",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 8));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb888_39_1",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 1));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb888_39_2",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 2));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb888_39_4",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 4));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb888_39_8",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 8));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8888_47_1",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 1));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8888_47_2",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 2));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8888_47_4",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 4));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8888_47_8",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 8));
+	}
+
+	// Basic glCopyTexImage2D() cases
+	{
+		tcu::TestCaseGroup* copyTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_copyteximage2d", "Basic glCopyTexImage2D() usage");
+		addChild(copyTexImageGroup);
+
+		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_alpha",				"",	GL_ALPHA,			MIPMAPS,	128, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_luminance",			"",	GL_LUMINANCE,		MIPMAPS,	128, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	MIPMAPS,	128, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_rgb",				"",	GL_RGB,				MIPMAPS,	128, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_rgba",				"",	GL_RGBA,			MIPMAPS,	128, 64));
+
+		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_alpha",			"",	GL_ALPHA,			MIPMAPS,	64, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_luminance",		"",	GL_LUMINANCE,		MIPMAPS,	64, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	MIPMAPS,	64, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_rgb",				"",	GL_RGB,				MIPMAPS,	64, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_rgba",			"",	GL_RGBA,			MIPMAPS,	64, 64));
+	}
+
+	// Basic glCopyTexSubImage2D() cases
+	{
+		tcu::TestCaseGroup* copyTexSubImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_copytexsubimage2d", "Basic glCopyTexSubImage2D() usage");
+		addChild(copyTexSubImageGroup);
+
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_alpha",				"",	GL_ALPHA,			GL_UNSIGNED_BYTE, MIPMAPS, 128, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_luminance",			"",	GL_LUMINANCE,		GL_UNSIGNED_BYTE, MIPMAPS, 128, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE, MIPMAPS, 128, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_rgb",				"",	GL_RGB,				GL_UNSIGNED_BYTE, MIPMAPS, 128, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_rgba",				"",	GL_RGBA,			GL_UNSIGNED_BYTE, MIPMAPS, 128, 64));
+
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_alpha",			"",	GL_ALPHA,			GL_UNSIGNED_BYTE, MIPMAPS, 64, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_luminance",		"",	GL_LUMINANCE,		GL_UNSIGNED_BYTE, MIPMAPS, 64, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE, MIPMAPS, 64, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_rgb",				"",	GL_RGB,				GL_UNSIGNED_BYTE, MIPMAPS, 64, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_rgba",			"",	GL_RGBA,			GL_UNSIGNED_BYTE, MIPMAPS, 64, 64));
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fTextureSpecificationTests.hpp b/modules/gles2/functional/es2fTextureSpecificationTests.hpp
new file mode 100644
index 0000000..599ab8d
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureSpecificationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FTEXTURESPECIFICATIONTESTS_HPP
+#define _ES2FTEXTURESPECIFICATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture specification tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class TextureSpecificationTests : public TestCaseGroup
+{
+public:
+									TextureSpecificationTests		(Context& context);
+									~TextureSpecificationTests		(void);
+
+	void							init							(void);
+
+private:
+									TextureSpecificationTests		(const TextureSpecificationTests& other);
+	TextureSpecificationTests&		operator=						(const TextureSpecificationTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FTEXTURESPECIFICATIONTESTS_HPP
diff --git a/modules/gles2/functional/es2fTextureStateQueryTests.cpp b/modules/gles2/functional/es2fTextureStateQueryTests.cpp
new file mode 100644
index 0000000..4f66f40
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureStateQueryTests.cpp
@@ -0,0 +1,414 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fTextureStateQueryTests.hpp"
+#include "es2fApiCase.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+
+using namespace glw; // GLint and other GL types
+using namespace deqp::gls;
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace TextureParamVerifiers
+{
+
+// TexParamVerifier
+
+class TexParamVerifier : protected glu::CallLogWrapper
+{
+public:
+						TexParamVerifier	(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~TexParamVerifier	(); // make GCC happy
+
+	const char*			getTestNamePostfix	(void) const;
+
+	virtual void		verifyInteger		(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)	= DE_NULL;
+	virtual void		verifyFloat			(tcu::TestContext& testCtx, GLenum target, GLenum name, GLfloat reference)	= DE_NULL;
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+TexParamVerifier::TexParamVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+TexParamVerifier::~TexParamVerifier ()
+{
+}
+
+const char* TexParamVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+class GetTexParameterIVerifier : public TexParamVerifier
+{
+public:
+			GetTexParameterIVerifier	(const glw::Functions& gl, tcu::TestLog& log);
+
+	void	verifyInteger				(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference);
+	void	verifyFloat					(tcu::TestContext& testCtx, GLenum target, GLenum name, GLfloat reference);
+};
+
+GetTexParameterIVerifier::GetTexParameterIVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: TexParamVerifier(gl, log, "_gettexparameteri")
+{
+}
+
+void GetTexParameterIVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetTexParameteriv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid texture param value");
+	}
+}
+
+void GetTexParameterIVerifier::verifyFloat (tcu::TestContext& testCtx, GLenum target, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	const GLint expectedGLStateMax = StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint>(reference);
+	const GLint expectedGLStateMin = StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint>(reference);
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetTexParameteriv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected in range [" << expectedGLStateMin << ", " << expectedGLStateMax << "]; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid texture param value");
+	}
+}
+
+class GetTexParameterFVerifier : public TexParamVerifier
+{
+public:
+			GetTexParameterFVerifier	(const glw::Functions& gl, tcu::TestLog& log);
+
+	void	verifyInteger				(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference);
+	void	verifyFloat					(tcu::TestContext& testCtx, GLenum target, GLenum name, GLfloat reference);
+};
+
+GetTexParameterFVerifier::GetTexParameterFVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: TexParamVerifier(gl, log, "_gettexparameterf")
+{
+}
+
+void GetTexParameterFVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)
+{
+	DE_ASSERT(reference == GLint(GLfloat(reference))); // reference integer must have 1:1 mapping to float for this to work. Reference value is always such value in these tests
+
+	using tcu::TestLog;
+
+	const GLfloat referenceAsFloat = GLfloat(reference);
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetTexParameterfv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != referenceAsFloat)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << referenceAsFloat << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetTexParameterFVerifier::verifyFloat (tcu::TestContext& testCtx, GLenum target, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetTexParameterfv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+} // TextureParamVerifiers
+
+namespace
+{
+
+using namespace TextureParamVerifiers;
+
+// Tests
+
+class TextureCase : public ApiCase
+{
+public:
+	TextureCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget)
+		: ApiCase			(context, name, description)
+		, m_textureTarget	(textureTarget)
+		, m_verifier		(verifier)
+	{
+	}
+
+	virtual void testTexture (void) = DE_NULL;
+
+	void test (void)
+	{
+		GLuint textureId = 0;
+		glGenTextures(1, &textureId);
+		glBindTexture(m_textureTarget, textureId);
+		expectError(GL_NO_ERROR);
+
+		testTexture();
+
+		glDeleteTextures(1, &textureId);
+		expectError(GL_NO_ERROR);
+	}
+
+protected:
+	GLenum				m_textureTarget;
+	TexParamVerifier*	m_verifier;
+};
+
+class TextureWrapCase : public TextureCase
+{
+public:
+	TextureWrapCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget, GLenum valueName)
+		: TextureCase	(context, verifier, name, description, textureTarget)
+		, m_valueName	(valueName)
+	{
+	}
+
+	void testTexture (void)
+	{
+		const GLenum wrapValues[] = {GL_CLAMP_TO_EDGE, GL_REPEAT, GL_MIRRORED_REPEAT};
+
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_valueName, GL_REPEAT);
+		expectError(GL_NO_ERROR);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(wrapValues); ++ndx)
+		{
+			glTexParameteri(m_textureTarget, m_valueName, wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_valueName, wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		//check unit conversions with float
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(wrapValues); ++ndx)
+		{
+			glTexParameterf(m_textureTarget, m_valueName, (GLfloat)wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_valueName, wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	GLenum	m_valueName;
+};
+
+class TextureMagFilterCase : public TextureCase
+{
+public:
+	TextureMagFilterCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget)
+		: TextureCase(context, verifier, name, description, textureTarget)
+	{
+	}
+
+	void testTexture (void)
+	{
+		const GLenum magValues[] = {GL_NEAREST, GL_LINEAR};
+
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		expectError(GL_NO_ERROR);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(magValues); ++ndx)
+		{
+			glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, magValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MAG_FILTER, magValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		//check unit conversions with float
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(magValues); ++ndx)
+		{
+			glTexParameterf(m_textureTarget, GL_TEXTURE_MAG_FILTER, (GLfloat)magValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MAG_FILTER, magValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class TextureMinFilterCase : public TextureCase
+{
+public:
+	TextureMinFilterCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget)
+		: TextureCase(context, verifier, name, description, textureTarget)
+	{
+	}
+
+	void testTexture (void)
+	{
+		const GLenum minValues[] = {GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR};
+
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
+		expectError(GL_NO_ERROR);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(minValues); ++ndx)
+		{
+			glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, minValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MIN_FILTER, minValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		//check unit conversions with float
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(minValues); ++ndx)
+		{
+			glTexParameterf(m_textureTarget, GL_TEXTURE_MIN_FILTER, (GLfloat)minValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MIN_FILTER, minValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+} // anonymous
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		TexParamVerifier* verifier = VERIFIERS[_verifierNdx];									\
+		CODE_BLOCK;																				\
+	}
+
+TextureStateQueryTests::TextureStateQueryTests (Context& context)
+	: TestCaseGroup		(context, "texture", "Texture State Query tests")
+	, m_verifierInt		(DE_NULL)
+	, m_verifierFloat	(DE_NULL)
+{
+}
+
+TextureStateQueryTests::~TextureStateQueryTests (void)
+{
+	deinit();
+}
+
+void TextureStateQueryTests::init (void)
+{
+	using namespace TextureParamVerifiers;
+
+	DE_ASSERT(m_verifierInt == DE_NULL);
+	DE_ASSERT(m_verifierFloat == DE_NULL);
+
+	m_verifierInt		= new GetTexParameterIVerifier(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierFloat		= new GetTexParameterFVerifier(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+
+	TexParamVerifier* verifiers[] = {m_verifierInt, m_verifierFloat};
+
+	const struct
+	{
+		const char*	name;
+		GLenum		textureTarget;
+	} textureTargets[] =
+	{
+		{ "texture_2d",			GL_TEXTURE_2D},
+		{ "texture_cube_map",	GL_TEXTURE_CUBE_MAP}
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(textureTargets); ++ndx)
+	{
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureWrapCase		(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_wrap_s"			+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_WRAP_S",		textureTargets[ndx].textureTarget, GL_TEXTURE_WRAP_S)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureWrapCase		(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_wrap_t"			+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_WRAP_T",		textureTargets[ndx].textureTarget, GL_TEXTURE_WRAP_T)));
+
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureMagFilterCase	(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_mag_filter"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MAG_FILTER",	textureTargets[ndx].textureTarget)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureMinFilterCase	(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_min_filter"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MIN_FILTER",	textureTargets[ndx].textureTarget)));
+	}
+}
+
+void TextureStateQueryTests::deinit (void)
+{
+	if (m_verifierInt)
+	{
+		delete m_verifierInt;
+		m_verifierInt = NULL;
+	}
+	if (m_verifierFloat)
+	{
+		delete m_verifierFloat;
+		m_verifierFloat = NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fTextureStateQueryTests.hpp b/modules/gles2/functional/es2fTextureStateQueryTests.hpp
new file mode 100644
index 0000000..4139dd9
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureStateQueryTests.hpp
@@ -0,0 +1,64 @@
+#ifndef _ES2FTEXTURESTATEQUERYTESTS_HPP
+#define _ES2FTEXTURESTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+namespace TextureParamVerifiers
+{
+
+class GetTexParameterIVerifier;
+class GetTexParameterFVerifier;
+
+} // TextureParamVerifiers
+
+class TextureStateQueryTests : public TestCaseGroup
+{
+public:
+																TextureStateQueryTests	(Context& context);
+																~TextureStateQueryTests	(void);
+
+	void														init					(void);
+	void														deinit					(void);
+
+private:
+																TextureStateQueryTests	(const TextureStateQueryTests& other);
+	TextureStateQueryTests&										operator=				(const TextureStateQueryTests& other);
+
+	TextureParamVerifiers::GetTexParameterIVerifier*			m_verifierInt;
+	TextureParamVerifiers::GetTexParameterFVerifier*			m_verifierFloat;
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FTEXTURESTATEQUERYTESTS_HPP
diff --git a/modules/gles2/functional/es2fTextureUnitTests.cpp b/modules/gles2/functional/es2fTextureUnitTests.cpp
new file mode 100644
index 0000000..65fb90c
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureUnitTests.cpp
@@ -0,0 +1,970 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture unit usage tests.
+ *
+ * \todo [2012-07-12 nuutti] Come up with a good way to make these tests faster.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fTextureUnitTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuRenderTarget.hpp"
+#include "sglrContextUtil.hpp"
+#include "sglrReferenceContext.hpp"
+#include "sglrGLContext.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::Mat3;
+using std::vector;
+using std::string;
+using namespace glw; // GL types
+
+namespace deqp
+{
+
+using namespace gls::TextureTestUtil;
+
+namespace gles2
+{
+namespace Functional
+{
+
+static const int VIEWPORT_WIDTH			= 128;
+static const int VIEWPORT_HEIGHT		= 128;
+
+static const int TEXTURE_WIDTH_2D		= 128;
+static const int TEXTURE_HEIGHT_2D		= 128;
+
+// \note Cube map texture size is larger in order to make minifications possible - otherwise would need to display different faces at same time.
+static const int TEXTURE_WIDTH_CUBE		= 256;
+static const int TEXTURE_HEIGHT_CUBE	= 256;
+
+static const int GRID_CELL_SIZE			= 8;
+
+static const GLenum s_testFormats[] =
+{
+	GL_RGB,
+	GL_RGBA,
+	GL_ALPHA,
+	GL_LUMINANCE,
+	GL_LUMINANCE_ALPHA
+};
+
+static const GLenum s_testDataTypes[] =
+{
+	GL_UNSIGNED_BYTE,
+	GL_UNSIGNED_SHORT_5_6_5,
+	GL_UNSIGNED_SHORT_4_4_4_4,
+	GL_UNSIGNED_SHORT_5_5_5_1,
+};
+
+static const GLenum s_testWrapModes[] =
+{
+	GL_CLAMP_TO_EDGE,
+	GL_REPEAT,
+	GL_MIRRORED_REPEAT,
+};
+
+static const GLenum s_testMinFilters[] =
+{
+	GL_NEAREST,
+	GL_LINEAR,
+	GL_NEAREST_MIPMAP_NEAREST,
+	GL_LINEAR_MIPMAP_NEAREST,
+	GL_NEAREST_MIPMAP_LINEAR,
+	GL_LINEAR_MIPMAP_LINEAR
+};
+
+static const GLenum s_testNonMipmapMinFilters[] =
+{
+	GL_NEAREST,
+	GL_LINEAR
+};
+
+static const GLenum s_testMagFilters[] =
+{
+	GL_NEAREST,
+	GL_LINEAR
+};
+
+static const GLenum s_cubeFaceTargets[] =
+{
+	GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
+};
+
+static string generateMultiTexFragmentShader(int numUnits, const GLenum* unitTypes)
+{
+	// The fragment shader calculates the average of a set of textures.
+
+	string samplersStr;
+	string matricesStr;
+	string lookupsStr;
+
+	string colorMultiplier = "(1.0/" + de::toString(numUnits) + ".0)";
+
+	for (int ndx = 0; ndx < numUnits; ndx++)
+	{
+		string			ndxStr				= de::toString(ndx);
+		string			samplerName			= "u_sampler" + ndxStr;
+		string			transformationName	= "u_trans" + ndxStr;
+		const char*		samplerType			= unitTypes[ndx] == GL_TEXTURE_2D ? "sampler2D" : "samplerCube";
+		const char*		lookupFunc			= unitTypes[ndx] == GL_TEXTURE_2D ? "texture2D" : "textureCube";
+
+		samplersStr += string("") + "uniform mediump " + samplerType + " " + samplerName + ";\n";
+		matricesStr += "uniform mediump mat3 " + transformationName + ";\n";
+
+		string lookupCoord = transformationName + "*vec3(v_coord, 1.0)";
+
+		if (unitTypes[ndx] == GL_TEXTURE_2D)
+			lookupCoord = "vec2(" + lookupCoord + ")";
+
+		lookupsStr += "\tcolor += " + colorMultiplier + "*" + lookupFunc + "(" + samplerName + ", " + lookupCoord + ");\n";
+	}
+
+	return
+		samplersStr +
+		matricesStr +
+		"varying mediump vec2 v_coord;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	mediump vec4 color = vec4(0.0);\n" +
+		lookupsStr +
+		"	gl_FragColor = color;\n"
+		"}\n";
+}
+
+static sglr::pdec::ShaderProgramDeclaration generateShaderProgramDeclaration (int numUnits, const GLenum* unitTypes)
+{
+	sglr::pdec::ShaderProgramDeclaration decl;
+
+	decl << sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT);
+
+	for (int ndx = 0; ndx < numUnits; ++ndx)
+	{
+		string	samplerName			= "u_sampler" + de::toString(ndx);
+		string	transformationName	= "u_trans" + de::toString(ndx);
+
+		decl << sglr::pdec::Uniform(samplerName, (unitTypes[ndx] == GL_TEXTURE_2D) ? (glu::TYPE_SAMPLER_2D) : (glu::TYPE_SAMPLER_CUBE));
+		decl << sglr::pdec::Uniform(transformationName, glu::TYPE_FLOAT_MAT3);
+	}
+
+	decl << sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
+									 "attribute mediump vec2 a_coord;\n"
+									 "varying mediump vec2 v_coord;\n"
+									 "\n"
+									 "void main (void)\n"
+									 "{\n"
+									 "	gl_Position = a_position;\n"
+									 "	v_coord = a_coord;\n"
+									 "}\n");
+	decl << sglr::pdec::FragmentSource(generateMultiTexFragmentShader(numUnits, unitTypes));
+
+	return decl;
+}
+
+// Calculates values to be used in calculateLod().
+static Vec4 calculateLodDerivateParts(const Mat3& transformation)
+{
+	// Calculate transformed coordinates of three corners.
+	Vec2 trans00 = (transformation * Vec3(0.0f, 0.0f, 1.0f)).xy();
+	Vec2 trans01 = (transformation * Vec3(0.0f, 1.0f, 1.0f)).xy();
+	Vec2 trans10 = (transformation * Vec3(1.0f, 0.0f, 1.0f)).xy();
+
+	return Vec4(trans10.x() - trans00.x(),
+				trans01.x() - trans00.x(),
+				trans10.y() - trans00.y(),
+				trans01.y() - trans00.y());
+}
+
+// Calculates the maximum allowed lod from derivates
+static float calculateLodMax(const Vec4& derivateParts, const tcu::IVec2& textureSize, const Vec2& screenDerivate)
+{
+	float dudx = derivateParts.x() * (float)textureSize.x() * screenDerivate.x();
+	float dudy = derivateParts.y() * (float)textureSize.x() * screenDerivate.y();
+	float dvdx = derivateParts.z() * (float)textureSize.y() * screenDerivate.x();
+	float dvdy = derivateParts.w() * (float)textureSize.y() * screenDerivate.y();
+
+	return deFloatLog2(de::max(de::abs(dudx), de::abs(dudy)) + de::max(de::abs(dvdx), de::abs(dvdy)));
+}
+
+// Calculates the minimum allowed lod from derivates
+static float calculateLodMin(const Vec4& derivateParts, const tcu::IVec2& textureSize, const Vec2& screenDerivate)
+{
+	float dudx = derivateParts.x() * (float)textureSize.x() * screenDerivate.x();
+	float dudy = derivateParts.y() * (float)textureSize.x() * screenDerivate.y();
+	float dvdx = derivateParts.z() * (float)textureSize.y() * screenDerivate.x();
+	float dvdy = derivateParts.w() * (float)textureSize.y() * screenDerivate.y();
+
+	return deFloatLog2(de::max(de::max(de::abs(dudx), de::abs(dudy)), de::max(de::abs(dvdx), de::abs(dvdy))));
+}
+
+class MultiTexShader : public sglr::ShaderProgram
+{
+public:
+							MultiTexShader	(deUint32 randSeed, int numUnits, const vector<GLenum>& unitTypes);
+
+	void					setUniforms		(sglr::Context& context, deUint32 program) const;
+	void					makeSafeLods	(const vector<IVec2>& textureSizes, const IVec2& viewportSize); // Modifies texture coordinates so that LODs aren't too close to x.5 or 0.0 .
+
+private:
+	void					shadeVertices	(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void					shadeFragments	(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+	int						m_numUnits;
+	vector<GLenum>			m_unitTypes;		// 2d or cube map.
+	vector<Mat3>			m_transformations;
+	vector<Vec4>			m_lodDerivateParts;	// Parts of lod derivates; computed in init(), used in eval().
+};
+
+MultiTexShader::MultiTexShader (deUint32 randSeed, int numUnits, const vector<GLenum>& unitTypes)
+	: sglr::ShaderProgram	(generateShaderProgramDeclaration(numUnits, &unitTypes[0]))
+	, m_numUnits			(numUnits)
+	, m_unitTypes			(unitTypes)
+{
+	// 2d-to-cube-face transformations.
+	// \note 2d coordinates range from 0 to 1 and cube face coordinates from -1 to 1, so scaling is done as well.
+	static const float s_cubeTransforms[][3*3] =
+	{
+		// Face -X: (x, y, 1) -> (-1, -(2*y-1), +(2*x-1))
+		{  0.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   2.0f,  0.0f, -1.0f },
+		// Face +X: (x, y, 1) -> (+1, -(2*y-1), -(2*x-1))
+		{  0.0f,  0.0f,  1.0f,
+		   0.0f, -2.0f,  1.0f,
+		  -2.0f,  0.0f,  1.0f },
+		// Face -Y: (x, y, 1) -> (+(2*x-1), -1, -(2*y-1))
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f },
+		// Face +Y: (x, y, 1) -> (+(2*x-1), +1, +(2*y-1))
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f,  0.0f,  1.0f,
+		   0.0f,  2.0f, -1.0f },
+		// Face -Z: (x, y, 1) -> (-(2*x-1), -(2*y-1), -1)
+		{ -2.0f,  0.0f,  1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   0.0f,  0.0f, -1.0f },
+		// Face +Z: (x, y, 1) -> (+(2*x-1), -(2*y-1), +1)
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   0.0f,  0.0f,  1.0f }
+	};
+
+	// Generate transformation matrices.
+
+	de::Random rnd(randSeed);
+
+	m_transformations.reserve(m_numUnits);
+	m_lodDerivateParts.reserve(m_numUnits);
+
+	DE_ASSERT((int)m_unitTypes.size() == m_numUnits);
+
+	for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
+	{
+		if (m_unitTypes[unitNdx] == GL_TEXTURE_2D)
+		{
+			float rotAngle				= rnd.getFloat(0.0f, 2.0f*DE_PI);
+			float xScaleFactor			= rnd.getFloat(0.7f, 1.5f);
+			float yScaleFactor			= rnd.getFloat(0.7f, 1.5f);
+			float xShearAmount			= rnd.getFloat(0.0f, 0.5f);
+			float yShearAmount			= rnd.getFloat(0.0f, 0.5f);
+			float xTranslationAmount	= rnd.getFloat(-0.5f, 0.5f);
+			float yTranslationAmount	= rnd.getFloat(-0.5f, 0.5f);
+
+			float tempOffsetData[3*3] = // For temporarily centering the coordinates to get nicer transformations.
+			{
+				1.0f,  0.0f, -0.5f,
+				0.0f,  1.0f, -0.5f,
+				0.0f,  0.0f,  1.0f
+			};
+			float rotTransfData[3*3] =
+			{
+				deFloatCos(rotAngle),	-deFloatSin(rotAngle),	0.0f,
+				deFloatSin(rotAngle),	deFloatCos(rotAngle),	0.0f,
+				0.0f,					0.0f,					1.0f
+			};
+			float scaleTransfData[3*3] =
+			{
+				xScaleFactor,	0.0f,			0.0f,
+				0.0f,			yScaleFactor,	0.0f,
+				0.0f,			0.0f,			1.0f
+			};
+			float xShearTransfData[3*3] =
+			{
+				1.0f,			xShearAmount,	0.0f,
+				0.0f,			1.0f,			0.0f,
+				0.0f,			0.0f,			1.0f
+			};
+			float yShearTransfData[3*3] =
+			{
+				1.0f,			0.0f,			0.0f,
+				yShearAmount,	1.0f,			0.0f,
+				0.0f,			0.0f,			1.0f
+			};
+			float translationTransfData[3*3] =
+			{
+				1.0f,	0.0f,	xTranslationAmount,
+				0.0f,	1.0f,	yTranslationAmount,
+				0.0f,	0.0f,	1.0f
+			};
+
+			Mat3 transformation =
+				Mat3(tempOffsetData) *
+				Mat3(translationTransfData) *
+				Mat3(rotTransfData) *
+				Mat3(scaleTransfData) *
+				Mat3(xShearTransfData) *
+				Mat3(yShearTransfData) *
+				(Mat3(tempOffsetData) * (-1.0f));
+
+			// Calculate parts of lod derivates.
+			m_lodDerivateParts.push_back(calculateLodDerivateParts(transformation));
+
+			m_transformations.push_back(transformation);
+		}
+		else
+		{
+			DE_ASSERT(m_unitTypes[unitNdx] == GL_TEXTURE_CUBE_MAP);
+			DE_STATIC_ASSERT((int)tcu::CUBEFACE_LAST == DE_LENGTH_OF_ARRAY(s_cubeTransforms));
+
+			float planarTransData[3*3];
+
+			// In case of a cube map, we only want to render one face, so the transformation needs to be restricted - only enlarging scaling is done.
+
+			for (int i = 0; i < DE_LENGTH_OF_ARRAY(planarTransData); i++)
+			{
+				if (i == 0 || i == 4)
+					planarTransData[i] = rnd.getFloat(0.1f, 0.9f); // Two first diagonal cells control the scaling.
+				else if (i == 8)
+					planarTransData[i] = 1.0f;
+				else
+					planarTransData[i] = 0.0f;
+			}
+
+			int		faceNdx			= rnd.getInt(0, (int)tcu::CUBEFACE_LAST - 1);
+			Mat3	planarTrans		(planarTransData);									// Planar, face-agnostic transformation.
+			Mat3	finalTrans		= Mat3(s_cubeTransforms[faceNdx]) * planarTrans;	// Final transformation from planar to cube map coordinates, including the transformation just generated.
+
+			// Calculate parts of lod derivates.
+			m_lodDerivateParts.push_back(calculateLodDerivateParts(planarTrans));
+
+			m_transformations.push_back(finalTrans);
+		}
+	}
+}
+
+void MultiTexShader::setUniforms (sglr::Context& ctx, deUint32 program) const
+{
+	ctx.useProgram(program);
+
+	// Sampler and matrix uniforms.
+
+	for (int ndx = 0; ndx < m_numUnits; ndx++)
+	{
+		string			ndxStr		= de::toString(ndx);
+
+		ctx.uniform1i(ctx.getUniformLocation(program, ("u_sampler" + ndxStr).c_str()), ndx);
+		ctx.uniformMatrix3fv(ctx.getUniformLocation(program, ("u_trans" + ndxStr).c_str()), 1, GL_FALSE, (GLfloat*)&m_transformations[ndx].getColumnMajorData()[0]);
+	}
+}
+
+void MultiTexShader::makeSafeLods (const vector<IVec2>& textureSizes, const IVec2& viewportSize)
+{
+	DE_ASSERT((int)textureSizes.size() == m_numUnits);
+
+	static const float shrinkScaleMatData[3*3] =
+	{
+		0.95f,	0.0f,	0.0f,
+		0.0f,	0.95f,	0.0f,
+		0.0f,	0.0f,	1.0f
+	};
+	Mat3 shrinkScaleMat(shrinkScaleMatData);
+
+	Vec2 screenDerivate(1.0f / (float)viewportSize.x(), 1.0f / (float)viewportSize.y());
+
+	for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
+	{
+		// As long as LOD is too close to 0.0 or is positive and too close to a something-and-a-half (0.5, 1.5, 2.5 etc) or allowed lod range could round to different levels, zoom in a little to get a safer LOD.
+		for (;;)
+		{
+			const float threshold = 0.1f;
+			const float epsilon	= 0.01f;
+
+			const float lodMax = calculateLodMax(m_lodDerivateParts[unitNdx], textureSizes[unitNdx], screenDerivate);
+			const float lodMin = calculateLodMin(m_lodDerivateParts[unitNdx], textureSizes[unitNdx], screenDerivate);
+
+			const deInt32 maxLevel = (lodMax + epsilon < 0.5f) ? (0) : (deCeilFloatToInt32(lodMax + epsilon + 0.5f) - 1);
+			const deInt32 minLevel = (lodMin - epsilon < 0.5f) ? (0) : (deCeilFloatToInt32(lodMin - epsilon + 0.5f) - 1);
+
+			if (de::abs(lodMax) < threshold || (lodMax > 0.0f && de::abs(deFloatFrac(lodMax) - 0.5f) < threshold) ||
+				de::abs(lodMin) < threshold || (lodMin > 0.0f && de::abs(deFloatFrac(lodMin) - 0.5f) < threshold) ||
+				maxLevel != minLevel)
+			{
+				m_transformations[unitNdx] = shrinkScaleMat * m_transformations[unitNdx];
+				m_lodDerivateParts[unitNdx] = calculateLodDerivateParts(m_transformations[unitNdx]);
+			}
+			else
+				break;
+		}
+	}
+}
+
+void MultiTexShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::VertexPacket& packet = *(packets[packetNdx]);
+
+		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void MultiTexShader::shadeFragments	(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	DE_ASSERT((int)m_unitTypes.size() == m_numUnits);
+	DE_ASSERT((int)m_transformations.size() == m_numUnits);
+	DE_ASSERT((int)m_lodDerivateParts.size() == m_numUnits);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::FragmentPacket& packet				= packets[packetNdx];
+		const float			colorMultiplier		= 1.0f / (float)m_numUnits;
+		Vec4				outColors[4]		= { Vec4(0.0f), Vec4(0.0f), Vec4(0.0f), Vec4(0.0f) };
+
+		for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
+		{
+			tcu::Vec4 texSamples[4];
+
+			// Read tex coords
+			const tcu::Vec2 texCoords[4] =
+			{
+				rr::readTriangleVarying<float>(packet, context, 0, 0).xy(),
+				rr::readTriangleVarying<float>(packet, context, 0, 1).xy(),
+				rr::readTriangleVarying<float>(packet, context, 0, 2).xy(),
+				rr::readTriangleVarying<float>(packet, context, 0, 3).xy(),
+			};
+
+			if (m_unitTypes[unitNdx] == GL_TEXTURE_2D)
+			{
+				// Transform
+				const tcu::Vec2 transformedTexCoords[4] =
+				{
+					(m_transformations[unitNdx] * Vec3(texCoords[0].x(), texCoords[0].y(), 1.0f)).xy(),
+					(m_transformations[unitNdx] * Vec3(texCoords[1].x(), texCoords[1].y(), 1.0f)).xy(),
+					(m_transformations[unitNdx] * Vec3(texCoords[2].x(), texCoords[2].y(), 1.0f)).xy(),
+					(m_transformations[unitNdx] * Vec3(texCoords[3].x(), texCoords[3].y(), 1.0f)).xy(),
+				};
+
+				// Sample
+				m_uniforms[2*unitNdx].sampler.tex2D->sample4(texSamples, transformedTexCoords);
+			}
+			else
+			{
+				DE_ASSERT(m_unitTypes[unitNdx] == GL_TEXTURE_CUBE_MAP);
+
+				// Transform
+				const tcu::Vec3 transformedTexCoords[4] =
+				{
+					m_transformations[unitNdx] * Vec3(texCoords[0].x(), texCoords[0].y(), 1.0f),
+					m_transformations[unitNdx] * Vec3(texCoords[1].x(), texCoords[1].y(), 1.0f),
+					m_transformations[unitNdx] * Vec3(texCoords[2].x(), texCoords[2].y(), 1.0f),
+					m_transformations[unitNdx] * Vec3(texCoords[3].x(), texCoords[3].y(), 1.0f),
+				};
+
+				// Sample
+				m_uniforms[2*unitNdx].sampler.texCube->sample4(texSamples, transformedTexCoords);
+			}
+
+			// Add to sum
+			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+				outColors[fragNdx] += colorMultiplier * texSamples[fragNdx];
+		}
+
+		// output
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, outColors[fragNdx]);
+	}
+}
+
+class TextureUnitCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASE_ONLY_2D = 0,
+		CASE_ONLY_CUBE,
+		CASE_MIXED,
+
+		CASE_LAST
+	};
+								TextureUnitCase		(Context& context, const char* name, const char* desc, int numUnits /* \note If non-positive, use all units */, CaseType caseType, deUint32 randSeed);
+								~TextureUnitCase	(void);
+
+	void						init				(void);
+	void						deinit				(void);
+	IterateResult				iterate				(void);
+
+private:
+	struct TextureParameters
+	{
+		GLenum format;
+		GLenum dataType;
+		GLenum wrapModeS;
+		GLenum wrapModeT;
+		GLenum minFilter;
+		GLenum magFilter;
+	};
+
+								TextureUnitCase		(const TextureUnitCase& other);
+	TextureUnitCase&			operator=			(const TextureUnitCase& other);
+
+	void						render				(sglr::Context& context);
+
+	const int					m_numUnitsParam;
+	const CaseType				m_caseType;
+	const deUint32				m_randSeed;
+
+	int							m_numTextures;	//!< \note Needed in addition to m_numUnits since same texture may be bound to many texture units.
+	int							m_numUnits;		//!< = m_numUnitsParam > 0 ? m_numUnitsParam : implementationDefinedMaximum
+
+	vector<GLenum>				m_textureTypes;
+	vector<TextureParameters>	m_textureParams;
+	vector<tcu::Texture2D*>		m_textures2d;
+	vector<tcu::TextureCube*>	m_texturesCube;
+	vector<int>					m_unitTextures;	//!< Which texture is used in a particular unit.
+	vector<int>					m_ndx2dOrCube;	//!< Index of a texture in either m_textures2d or m_texturesCube, depending on texture type.
+	MultiTexShader*				m_shader;
+};
+
+TextureUnitCase::TextureUnitCase (Context& context, const char* name, const char* desc, int numUnits, CaseType caseType, deUint32 randSeed)
+	: TestCase			(context, tcu::NODETYPE_SELF_VALIDATE, name, desc)
+	, m_numUnitsParam	(numUnits)
+	, m_caseType		(caseType)
+	, m_randSeed		(randSeed)
+	, m_shader			(DE_NULL)
+{
+}
+
+TextureUnitCase::~TextureUnitCase (void)
+{
+	TextureUnitCase::deinit();
+}
+
+void TextureUnitCase::deinit (void)
+{
+	for (vector<tcu::Texture2D*>::iterator i = m_textures2d.begin(); i != m_textures2d.end(); i++)
+		delete *i;
+	m_textures2d.clear();
+
+	for (vector<tcu::TextureCube*>::iterator i = m_texturesCube.begin(); i != m_texturesCube.end(); i++)
+		delete *i;
+	m_texturesCube.clear();
+
+	delete m_shader;
+	m_shader = DE_NULL;
+}
+
+void TextureUnitCase::init (void)
+{
+	m_numUnits = m_numUnitsParam > 0 ? m_numUnitsParam : m_context.getContextInfo().getInt(GL_MAX_TEXTURE_IMAGE_UNITS);
+
+	// Make the textures.
+
+	try
+	{
+		tcu::TestLog&	log	= m_testCtx.getLog();
+		de::Random		rnd	(m_randSeed);
+
+		if (rnd.getFloat() < 0.7f)
+			m_numTextures = m_numUnits;											// In most cases use one unit per texture.
+		else
+			m_numTextures = rnd.getInt(deMax32(1, m_numUnits - 2), m_numUnits);	// Sometimes assign same texture to multiple units.
+
+		log << tcu::TestLog::Message << ("Using " + de::toString(m_numUnits) + " texture unit(s) and " + de::toString(m_numTextures) + " texture(s)").c_str() << tcu::TestLog::EndMessage;
+
+		m_textureTypes.reserve(m_numTextures);
+		m_textureParams.reserve(m_numTextures);
+		m_ndx2dOrCube.reserve(m_numTextures);
+
+		// Generate textures.
+
+		for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+		{
+			// Either fixed or randomized target types (2d or cube), and randomized parameters for every texture.
+
+			TextureParameters	params;
+			bool				is2d		= m_caseType == CASE_ONLY_2D	? true :
+											  m_caseType == CASE_ONLY_CUBE	? false :
+																			  rnd.getBool();
+
+			GLenum				type		= is2d ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP;
+			const int			texWidth	= is2d ? TEXTURE_WIDTH_2D : TEXTURE_WIDTH_CUBE;
+			const int			texHeight	= is2d ? TEXTURE_HEIGHT_2D : TEXTURE_HEIGHT_CUBE;
+			bool				mipmaps		= (deIsPowerOfTwo32(texWidth) && deIsPowerOfTwo32(texHeight));
+			int					numLevels	= mipmaps ? deLog2Floor32(de::max(texWidth, texHeight))+1 : 1;
+
+			params.wrapModeS	= s_testWrapModes	[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testWrapModes) - 1)];
+			params.wrapModeT	= s_testWrapModes	[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testWrapModes) - 1)];
+			params.magFilter	= s_testMagFilters	[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testMagFilters) - 1)];
+			params.dataType		= s_testDataTypes	[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testDataTypes) - 1)];
+
+			// Certain minification filters are only used when using mipmaps.
+			if (mipmaps)
+				params.minFilter = s_testMinFilters[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testMinFilters) - 1)];
+			else
+				params.minFilter = s_testNonMipmapMinFilters[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testNonMipmapMinFilters) - 1)];
+
+			// Format may depend on data type.
+			if (params.dataType == GL_UNSIGNED_SHORT_5_6_5)
+				params.format = GL_RGB;
+			else if (params.dataType == GL_UNSIGNED_SHORT_4_4_4_4 || params.dataType == GL_UNSIGNED_SHORT_5_5_5_1)
+				params.format = GL_RGBA;
+			else
+				params.format = s_testFormats[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testFormats) - 1)];
+
+			m_textureTypes.push_back(type);
+			m_textureParams.push_back(params);
+
+			// Create new texture.
+
+			if (is2d)
+			{
+				m_ndx2dOrCube.push_back((int)m_textures2d.size()); // Remember the index this texture has in the 2d array.
+				m_textures2d.push_back(new tcu::Texture2D(glu::mapGLTransferFormat(params.format, params.dataType), texWidth, texHeight));
+			}
+			else
+			{
+				m_ndx2dOrCube.push_back((int)m_texturesCube.size()); // Remember the index this texture has in the cube array.
+				DE_ASSERT(texWidth == texHeight);
+				m_texturesCube.push_back(new tcu::TextureCube(glu::mapGLTransferFormat(params.format, params.dataType), texWidth));
+			}
+
+			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(is2d ? m_textures2d.back()->getFormat() : m_texturesCube.back()->getFormat());
+			Vec4					cBias		= fmtInfo.valueMin;
+			Vec4					cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+			// Fill with grid texture.
+
+			int numFaces = is2d ? 1 : (int)tcu::CUBEFACE_LAST;
+
+			for (int face = 0; face < numFaces; face++)
+			{
+				deUint32 rgb	= rnd.getUint32() & 0x00ffffff;
+				deUint32 alpha0	= 0xff000000;
+				deUint32 alpha1	= 0xff000000;
+
+				if (params.format == GL_ALPHA) // \note This needs alpha to be visible.
+				{
+					alpha0 &= rnd.getUint32();
+					alpha1 = ~alpha0;
+				}
+
+				deUint32 colorA = alpha0 | rgb;
+				deUint32 colorB = alpha1 | ~rgb;
+
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					if (is2d)
+						m_textures2d.back()->allocLevel(levelNdx);
+					else
+						m_texturesCube.back()->allocLevel((tcu::CubeFace)face, levelNdx);
+
+					int curCellSize = deMax32(1, GRID_CELL_SIZE >> levelNdx); // \note Scale grid cell size for mipmaps.
+
+					tcu::PixelBufferAccess access = is2d ? m_textures2d.back()->getLevel(levelNdx) : m_texturesCube.back()->getLevelFace(levelNdx, (tcu::CubeFace)face);
+					tcu::fillWithGrid(access, curCellSize, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+				}
+			}
+		}
+
+		// Assign a texture index to each unit.
+
+		m_unitTextures.reserve(m_numUnits);
+
+		// \note Every texture is used at least once.
+		for (int i = 0; i < m_numTextures; i++)
+			m_unitTextures.push_back(i);
+
+		// Assign a random texture to remaining units.
+		while ((int)m_unitTextures.size() < m_numUnits)
+			m_unitTextures.push_back(rnd.getInt(0, m_numTextures - 1));
+
+		rnd.shuffle(m_unitTextures.begin(), m_unitTextures.end());
+
+		// Create shader.
+
+		vector<GLenum> unitTypes;
+		unitTypes.reserve(m_numUnits);
+		for (int i = 0; i < m_numUnits; i++)
+			unitTypes.push_back(m_textureTypes[m_unitTextures[i]]);
+
+		DE_ASSERT(m_shader == DE_NULL);
+		m_shader = new MultiTexShader(rnd.getUint32(), m_numUnits, unitTypes);
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		TextureUnitCase::deinit();
+		throw;
+	}
+}
+
+TextureUnitCase::IterateResult TextureUnitCase::iterate (void)
+{
+	glu::RenderContext&			renderCtx			= m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget		= renderCtx.getRenderTarget();
+	tcu::TestLog&				log					= m_testCtx.getLog();
+	de::Random					rnd					(m_randSeed);
+
+	int							viewportWidth		= deMin32(VIEWPORT_WIDTH, renderTarget.getWidth());
+	int							viewportHeight		= deMin32(VIEWPORT_HEIGHT, renderTarget.getHeight());
+	int							viewportX			= rnd.getInt(0, renderTarget.getWidth() - viewportWidth);
+	int							viewportY			= rnd.getInt(0, renderTarget.getHeight() - viewportHeight);
+
+	tcu::Surface				gles2Frame			(viewportWidth, viewportHeight);
+	tcu::Surface				refFrame			(viewportWidth, viewportHeight);
+
+	{
+		// First we do some tricks to make the LODs safer wrt. precision issues. See MultiTexShader::makeSafeLods().
+
+		vector<IVec2> texSizes;
+		texSizes.reserve(m_numUnits);
+
+		for (int i = 0; i < m_numUnits; i++)
+		{
+			int		texNdx			= m_unitTextures[i];
+			int		texNdxInType	= m_ndx2dOrCube[texNdx];
+			GLenum	type			= m_textureTypes[texNdx];
+
+			switch (type)
+			{
+				case GL_TEXTURE_2D:			texSizes.push_back(IVec2(m_textures2d[texNdxInType]->getWidth(),	m_textures2d[texNdxInType]->getHeight()));	break;
+				case GL_TEXTURE_CUBE_MAP:	texSizes.push_back(IVec2(m_texturesCube[texNdxInType]->getSize(),	m_texturesCube[texNdxInType]->getSize()));	break;
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+		}
+
+		m_shader->makeSafeLods(texSizes, IVec2(viewportWidth, viewportHeight));
+	}
+
+	// Render using GLES2.
+	{
+		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS|sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(viewportX, viewportY, viewportWidth, viewportHeight));
+
+		render(context);
+
+		context.readPixels(gles2Frame, 0, 0, viewportWidth, viewportHeight);
+	}
+
+	// Render reference image.
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), 0 /* depth */, 0 /* stencil */, viewportWidth, viewportHeight);
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		render(context);
+
+		context.readPixels(refFrame, 0, 0, viewportWidth, viewportHeight);
+	}
+
+	// Compare images.
+	const float		threshold	= 0.001f;
+	bool			isOk		= tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, gles2Frame, threshold, tcu::COMPARE_LOG_RESULT);
+
+	// Store test result.
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+void TextureUnitCase::render (sglr::Context& context)
+{
+	// Setup textures.
+
+	vector<deUint32>	textureGLNames;
+	vector<bool>		isTextureSetUp(m_numTextures, false); // \note Same texture may be bound to multiple units, but we only want to set up parameters and data once per texture.
+
+	textureGLNames.resize(m_numTextures);
+	context.genTextures(m_numTextures, &textureGLNames[0]);
+
+	for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
+	{
+		int texNdx = m_unitTextures[unitNdx];
+
+		// Bind texture to unit.
+		context.activeTexture(GL_TEXTURE0 + unitNdx);
+		context.bindTexture(m_textureTypes[texNdx], textureGLNames[texNdx]);
+
+		if (!isTextureSetUp[texNdx])
+		{
+			// Binding this texture for first time, so set parameters and data.
+
+			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_WRAP_S, m_textureParams[texNdx].wrapModeS);
+			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_WRAP_T, m_textureParams[texNdx].wrapModeT);
+			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_MIN_FILTER, m_textureParams[texNdx].minFilter);
+			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_MAG_FILTER, m_textureParams[texNdx].magFilter);
+
+			if (m_textureTypes[texNdx] == GL_TEXTURE_2D)
+			{
+				int						ndx2d		= m_ndx2dOrCube[texNdx];
+				const tcu::Texture2D*	texture		= m_textures2d[ndx2d];
+				bool					mipmaps		= (deIsPowerOfTwo32(texture->getWidth()) && deIsPowerOfTwo32(texture->getHeight()));
+				int						numLevels	= mipmaps ? deLog2Floor32(de::max(texture->getWidth(), texture->getHeight()))+1 : 1;
+
+				context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					tcu::ConstPixelBufferAccess		access	= texture->getLevel(levelNdx);
+					int								width	= access.getWidth();
+					int								height	= access.getHeight();
+
+					DE_ASSERT(access.getRowPitch() == access.getFormat().getPixelSize()*width);
+
+					context.texImage2D(GL_TEXTURE_2D, levelNdx, m_textureParams[texNdx].format, width, height, 0, m_textureParams[texNdx].format, m_textureParams[texNdx].dataType, access.getDataPtr());
+				}
+			}
+			else
+			{
+				DE_ASSERT(m_textureTypes[texNdx] == GL_TEXTURE_CUBE_MAP);
+
+				int							ndxCube		= m_ndx2dOrCube[texNdx];
+				const tcu::TextureCube*		texture		= m_texturesCube[ndxCube];
+				bool						mipmaps		= deIsPowerOfTwo32(texture->getSize()) != DE_FALSE;
+				int							numLevels	= mipmaps ? deLog2Floor32(texture->getSize())+1 : 1;
+
+				context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+				for (int face = 0; face < (int)tcu::CUBEFACE_LAST; face++)
+				{
+					for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+					{
+						tcu::ConstPixelBufferAccess		access	= texture->getLevelFace(levelNdx, (tcu::CubeFace)face);
+						int								width	= access.getWidth();
+						int								height	= access.getHeight();
+
+						DE_ASSERT(access.getRowPitch() == access.getFormat().getPixelSize()*width);
+
+						context.texImage2D(s_cubeFaceTargets[face], levelNdx, m_textureParams[texNdx].format, width, height, 0, m_textureParams[texNdx].format, m_textureParams[texNdx].dataType, access.getDataPtr());
+					}
+				}
+			}
+
+			isTextureSetUp[texNdx] = true; // Don't set up this texture's parameters and data again later.
+		}
+	}
+
+	GLU_EXPECT_NO_ERROR(context.getError(), "Set textures");
+
+	// Setup shader
+
+	deUint32 shaderID = context.createProgram(m_shader);
+
+	// Draw.
+
+	context.clearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	m_shader->setUniforms(context, shaderID);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+	GLU_EXPECT_NO_ERROR(context.getError(), "Draw");
+
+	// Delete previously generated texture names.
+
+	context.deleteTextures(m_numTextures, &textureGLNames[0]);
+	GLU_EXPECT_NO_ERROR(context.getError(), "Delete textures");
+}
+
+TextureUnitTests::TextureUnitTests (Context& context)
+	: TestCaseGroup(context, "units", "Texture Unit Usage Tests")
+{
+}
+
+TextureUnitTests::~TextureUnitTests (void)
+{
+}
+
+void TextureUnitTests::init (void)
+{
+	const int numTestsPerGroup = 10;
+
+	static const int unitCounts[] =
+	{
+		2,
+		4,
+		8,
+		-1 // \note Negative stands for the implementation-specified maximum.
+	};
+
+	for (int unitCountNdx = 0; unitCountNdx < DE_LENGTH_OF_ARRAY(unitCounts); unitCountNdx++)
+	{
+		int numUnits = unitCounts[unitCountNdx];
+
+		string countGroupName = (unitCounts[unitCountNdx] < 0 ? "all" : de::toString(numUnits)) + "_units";
+
+		tcu::TestCaseGroup* countGroup = new tcu::TestCaseGroup(m_testCtx, countGroupName.c_str(), "");
+		addChild(countGroup);
+
+		DE_STATIC_ASSERT((int)TextureUnitCase::CASE_ONLY_2D == 0);
+
+		for (int caseType = (int)TextureUnitCase::CASE_ONLY_2D; caseType < (int)TextureUnitCase::CASE_LAST; caseType++)
+		{
+			const char* caseTypeGroupName = (TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_ONLY_2D	? "only_2d" :
+											(TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_ONLY_CUBE	? "only_cube" :
+											(TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_MIXED		? "mixed" :
+																													  DE_NULL;
+			DE_ASSERT(caseTypeGroupName != DE_NULL);
+
+			tcu::TestCaseGroup* caseTypeGroup = new tcu::TestCaseGroup(m_testCtx, caseTypeGroupName, "");
+			countGroup->addChild(caseTypeGroup);
+
+			for (int testNdx = 0; testNdx < numTestsPerGroup; testNdx++)
+				caseTypeGroup->addChild(new TextureUnitCase(m_context, de::toString(testNdx).c_str(), "", numUnits, (TextureUnitCase::CaseType)caseType, (deUint32)deInt32Hash(testNdx)));
+		}
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fTextureUnitTests.hpp b/modules/gles2/functional/es2fTextureUnitTests.hpp
new file mode 100644
index 0000000..eb4ebc0
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureUnitTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FTEXTUREUNITTESTS_HPP
+#define _ES2FTEXTUREUNITTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture unit usage tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class TextureUnitTests : public TestCaseGroup
+{
+public:
+						TextureUnitTests	(Context& context);
+						~TextureUnitTests	(void);
+
+	void				init				(void);
+
+private:
+						TextureUnitTests	(const TextureUnitTests& other);
+	TextureUnitTests&	operator=			(const TextureUnitTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FTEXTUREUNITTESTS_HPP
diff --git a/modules/gles2/functional/es2fTextureWrapTests.cpp b/modules/gles2/functional/es2fTextureWrapTests.cpp
new file mode 100644
index 0000000..f1a3b80
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureWrapTests.cpp
@@ -0,0 +1,343 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture wrap mode tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fTextureWrapTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using tcu::Sampler;
+using namespace glu;
+using namespace gls::TextureTestUtil;
+
+enum
+{
+	VIEWPORT_WIDTH		= 256,
+	VIEWPORT_HEIGHT		= 256
+};
+
+class TextureWrapCase : public tcu::TestCase
+{
+public:
+								TextureWrapCase			(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, deUint32 format, deUint32 dataType, deUint32 wrapS, deUint32 wrapT, deUint32 minFilter, deUint32 magFilter, int width, int height);
+								TextureWrapCase			(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, deUint32 wrapS, deUint32 wrapT, deUint32 minFilter, deUint32 magFilter, const std::vector<std::string>& filenames);
+								~TextureWrapCase		(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+								TextureWrapCase			(const TextureWrapCase& other);
+	TextureWrapCase&			operator=				(const TextureWrapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	deUint32					m_format;
+	deUint32					m_dataType;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	deUint32					m_minFilter;
+	deUint32					m_magFilter;
+
+	int							m_width;
+	int							m_height;
+	std::vector<std::string>	m_filenames;
+
+	glu::Texture2D*				m_texture;
+	TextureRenderer				m_renderer;
+};
+
+TextureWrapCase::TextureWrapCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, deUint32 format, deUint32 dataType, deUint32 wrapS, deUint32 wrapT, deUint32 minFilter, deUint32 magFilter, int width, int height)
+	: TestCase			(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(ctxInfo)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_width			(width)
+	, m_height			(height)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+TextureWrapCase::TextureWrapCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, deUint32 wrapS, deUint32 wrapT, deUint32 minFilter, deUint32 magFilter, const std::vector<std::string>& filenames)
+	: TestCase			(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(ctxInfo)
+	, m_format			(GL_NONE)
+	, m_dataType		(GL_NONE)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_width			(0)
+	, m_height			(0)
+	, m_filenames		(filenames)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+TextureWrapCase::~TextureWrapCase (void)
+{
+	deinit();
+}
+
+void TextureWrapCase::init (void)
+{
+	if (!m_filenames.empty())
+	{
+		DE_ASSERT(m_width == 0 && m_height == 0 && m_format == GL_NONE && m_dataType == GL_NONE);
+
+		m_texture	= glu::Texture2D::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size(), m_filenames);
+		m_width		= m_texture->getRefTexture().getWidth();
+		m_height	= m_texture->getRefTexture().getHeight();
+	}
+	else
+	{
+		m_texture = new Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+
+		// Fill level 0.
+		m_texture->getRefTexture().allocLevel(0);
+		tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), tcu::Vec4(-0.5f, -0.5f, -0.5f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f));
+
+		m_texture->upload();
+	}
+}
+
+void TextureWrapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+TextureWrapCase::IterateResult TextureWrapCase::iterate (void)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), VIEWPORT_WIDTH, VIEWPORT_HEIGHT, deStringHash(getName()));
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	bool					isCompressedTex		= !m_filenames.empty();
+	ReferenceParams			refParams			(TEXTURETYPE_2D);
+	int						leftWidth			= viewport.width / 2;
+	int						rightWidth			= viewport.width - leftWidth;
+	vector<float>			texCoord;
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold()
+												+ (isCompressedTex ? tcu::RGBA(7,7,7,7) : tcu::RGBA(3,3,3,3));
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+
+	// Setup filtering and wrap modes.
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Parameters for reference images.
+	refParams.sampler	= mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	refParams.lodMode	= LODMODE_EXACT;
+
+	// Left: minification
+	{
+		gl.viewport(viewport.x, viewport.y, leftWidth, viewport.height);
+
+		computeQuadTexCoord2D(texCoord, tcu::Vec2(-1.5f, -3.0f), tcu::Vec2(1.5f, 2.5f));
+
+		m_renderer.renderQuad(0, &texCoord[0], refParams);
+		glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+		sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), 0, 0, leftWidth, viewport.height),
+					  m_texture->getRefTexture(), &texCoord[0], refParams);
+	}
+
+	// Right: magnification
+	{
+		gl.viewport(viewport.x+leftWidth, viewport.y, rightWidth, viewport.height);
+
+		computeQuadTexCoord2D(texCoord, tcu::Vec2(-0.5f, 0.75f), tcu::Vec2(0.25f, 1.25f));
+
+		m_renderer.renderQuad(0, &texCoord[0], refParams);
+		glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+		sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), leftWidth, 0, rightWidth, viewport.height),
+					  m_texture->getRefTexture(), &texCoord[0], refParams);
+	}
+
+	// Compare and log.
+	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+TextureWrapTests::TextureWrapTests (Context& context)
+	: TestCaseGroup(context, "wrap", "Wrap Mode Tests")
+{
+}
+
+TextureWrapTests::~TextureWrapTests (void)
+{
+}
+
+void TextureWrapTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE },
+		{ "repeat",		GL_REPEAT },
+		{ "mirror",		GL_MIRRORED_REPEAT }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} filteringModes[] =
+	{
+		{ "nearest",	GL_NEAREST },
+		{ "linear",		GL_LINEAR }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		int				width;
+		int				height;
+	} sizes[] =
+	{
+		{ "pot",		64, 128 },
+		{ "npot",		63, 112 }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		format;
+		deUint32		dataType;
+	} formats[] =
+	{
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE },
+		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+	};
+
+#define FOR_EACH(ITERATOR, ARRAY, BODY)	\
+	for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++)	\
+		BODY
+
+	FOR_EACH(wrapS,		wrapModes,
+	FOR_EACH(wrapT,		wrapModes,
+	FOR_EACH(filter,	filteringModes,
+	FOR_EACH(size,		sizes,
+	FOR_EACH(format,	formats,
+		{
+			bool is_clamp_clamp		= (wrapModes[wrapS].mode == GL_CLAMP_TO_EDGE	&& wrapModes[wrapT].mode == GL_CLAMP_TO_EDGE);
+			bool is_repeat_mirror	= (wrapModes[wrapS].mode == GL_REPEAT			&& wrapModes[wrapT].mode == GL_MIRRORED_REPEAT);
+
+			if (!is_clamp_clamp && !is_repeat_mirror && format != 0)
+				continue; // Use other format varants with clamp_clamp & repeat_mirror pair only.
+
+			if (!is_clamp_clamp && (!deIsPowerOfTwo32(sizes[size].width) || !deIsPowerOfTwo32(sizes[size].height)))
+				continue; // Not supported as described in Spec section 3.8.2.
+
+			string name = string("") + wrapModes[wrapS].name + "_" + wrapModes[wrapT].name + "_" + filteringModes[filter].name + "_" + sizes[size].name + "_" + formats[format].name;
+			addChild(new TextureWrapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), name.c_str(), "",
+										 formats[format].format, formats[format].dataType,
+										 wrapModes[wrapS].mode,
+										 wrapModes[wrapT].mode,
+										 filteringModes[filter].mode, filteringModes[filter].mode,
+										 sizes[size].width, sizes[size].height));
+
+		})))));
+
+	// Power-of-two ETC1 texture
+	std::vector<std::string> potFilenames;
+	potFilenames.push_back("data/etc1/photo_helsinki_mip_0.pkm");
+
+	FOR_EACH(wrapS,		wrapModes,
+	FOR_EACH(wrapT,		wrapModes,
+	FOR_EACH(filter,	filteringModes,
+		{
+			string name = string("") + wrapModes[wrapS].name + "_" + wrapModes[wrapT].name + "_" + filteringModes[filter].name + "_pot_etc1";
+			addChild(new TextureWrapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), name.c_str(), "",
+										 wrapModes[wrapS].mode,
+										 wrapModes[wrapT].mode,
+										 filteringModes[filter].mode, filteringModes[filter].mode,
+										 potFilenames));
+
+		})));
+
+	std::vector<std::string> npotFilenames;
+	npotFilenames.push_back("data/etc1/photo_helsinki_113x89.pkm");
+
+	// NPOT ETC1 texture
+	for (int filter = 0; filter < DE_LENGTH_OF_ARRAY(filteringModes); filter++)
+	{
+		string name = string("clamp_clamp_") + filteringModes[filter].name + "_npot_etc1";
+		addChild(new TextureWrapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), name.c_str(), "",
+									 GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE,
+									 filteringModes[filter].mode, filteringModes[filter].mode,
+									 npotFilenames));
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fTextureWrapTests.hpp b/modules/gles2/functional/es2fTextureWrapTests.hpp
new file mode 100644
index 0000000..968f4e4
--- /dev/null
+++ b/modules/gles2/functional/es2fTextureWrapTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FTEXTUREWRAPTESTS_HPP
+#define _ES2FTEXTUREWRAPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture wrap mode tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class TextureWrapTests : public TestCaseGroup
+{
+public:
+							TextureWrapTests		(Context& context);
+							~TextureWrapTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureWrapTests		(const TextureWrapTests& other);
+	TextureWrapTests&		operator=				(const TextureWrapTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FTEXTUREWRAPTESTS_HPP
diff --git a/modules/gles2/functional/es2fUniformApiTests.cpp b/modules/gles2/functional/es2fUniformApiTests.cpp
new file mode 100644
index 0000000..4e184c2
--- /dev/null
+++ b/modules/gles2/functional/es2fUniformApiTests.cpp
@@ -0,0 +1,2431 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Uniform API tests.
+ *
+ * \todo [2013-02-26 nuutti] Much duplication between this and ES3.
+ *							 Utilities to glshared?
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fUniformApiTests.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluVarType.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluTexture.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuCommandLine.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deSharedPtr.hpp"
+#include "deString.h"
+#include "deMemory.h"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <set>
+#include <cstring>
+
+using namespace glw;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using tcu::ScopedLogSection;
+using glu::ShaderProgram;
+using glu::StructType;
+using de::Random;
+using de::SharedPtr;
+
+typedef bool (* dataTypePredicate)(glu::DataType);
+
+static const int MAX_RENDER_WIDTH			= 32;
+static const int MAX_RENDER_HEIGHT			= 32;
+static const int MAX_NUM_SAMPLER_UNIFORMS	= 16;
+
+static const glu::DataType s_testDataTypes[] =
+{
+	glu::TYPE_FLOAT,
+	glu::TYPE_FLOAT_VEC2,
+	glu::TYPE_FLOAT_VEC3,
+	glu::TYPE_FLOAT_VEC4,
+	glu::TYPE_FLOAT_MAT2,
+	glu::TYPE_FLOAT_MAT3,
+	glu::TYPE_FLOAT_MAT4,
+
+	glu::TYPE_INT,
+	glu::TYPE_INT_VEC2,
+	glu::TYPE_INT_VEC3,
+	glu::TYPE_INT_VEC4,
+
+	glu::TYPE_BOOL,
+	glu::TYPE_BOOL_VEC2,
+	glu::TYPE_BOOL_VEC3,
+	glu::TYPE_BOOL_VEC4,
+
+	glu::TYPE_SAMPLER_2D,
+	glu::TYPE_SAMPLER_CUBE
+};
+
+static inline int getGLInt (const glw::Functions& funcs, const deUint32 name)
+{
+	int val = -1;
+	funcs.getIntegerv(name, &val);
+	return val;
+}
+
+static inline tcu::Vec4 vec4FromPtr (const float* const ptr)
+{
+	tcu::Vec4 result;
+	for (int i = 0; i < 4; i++)
+		result[i] = ptr[i];
+	return result;
+}
+
+static inline string beforeLast (const string& str, const char c)
+{
+	return str.substr(0, str.find_last_of(c));
+}
+
+static inline void fillWithColor (const tcu::PixelBufferAccess& access, const tcu::Vec4& color)
+{
+	for (int z = 0; z < access.getDepth(); z++)
+	for (int y = 0; y < access.getHeight(); y++)
+	for (int x = 0; x < access.getWidth(); x++)
+		access.setPixel(color, x, y, z);
+}
+
+static inline int getSamplerNumLookupDimensions (const glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_SAMPLER_2D:
+			return 2;
+
+		case glu::TYPE_SAMPLER_CUBE:
+			return 3;
+
+		default: // \note All others than 2d and cube are gles3-only types.
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+template<glu::DataType T>
+static bool dataTypeEquals (const glu::DataType t)
+{
+	return t == T;
+}
+
+template<int N>
+static bool dataTypeIsMatrixWithNRows (const glu::DataType t)
+{
+	return glu::isDataTypeMatrix(t) && glu::getDataTypeMatrixNumRows(t) == N;
+}
+
+static bool typeContainsMatchingBasicType (const glu::VarType& type, const dataTypePredicate predicate)
+{
+	if (type.isBasicType())
+		return predicate(type.getBasicType());
+	else if (type.isArrayType())
+		return typeContainsMatchingBasicType(type.getElementType(), predicate);
+	else
+	{
+		DE_ASSERT(type.isStructType());
+		const StructType& structType = *type.getStructPtr();
+		for (int i = 0; i < structType.getNumMembers(); i++)
+			if (typeContainsMatchingBasicType(structType.getMember(i).getType(), predicate))
+				return true;
+		return false;
+	}
+}
+
+static void getDistinctSamplerTypes (vector<glu::DataType>& dst, const glu::VarType& type)
+{
+	if (type.isBasicType())
+	{
+		const glu::DataType basicType = type.getBasicType();
+		if (glu::isDataTypeSampler(basicType) && std::find(dst.begin(), dst.end(), basicType) == dst.end())
+			dst.push_back(basicType);
+	}
+	else if (type.isArrayType())
+		getDistinctSamplerTypes(dst, type.getElementType());
+	else
+	{
+		DE_ASSERT(type.isStructType());
+		const StructType& structType = *type.getStructPtr();
+		for (int i = 0; i < structType.getNumMembers(); i++)
+			getDistinctSamplerTypes(dst, structType.getMember(i).getType());
+	}
+}
+
+static int getNumSamplersInType (const glu::VarType& type)
+{
+	if (type.isBasicType())
+		return glu::isDataTypeSampler(type.getBasicType()) ? 1 : 0;
+	else if (type.isArrayType())
+		return getNumSamplersInType(type.getElementType()) * type.getArraySize();
+	else
+	{
+		DE_ASSERT(type.isStructType());
+		const StructType& structType = *type.getStructPtr();
+		int sum = 0;
+		for (int i = 0; i < structType.getNumMembers(); i++)
+			sum += getNumSamplersInType(structType.getMember(i).getType());
+		return sum;
+	}
+}
+
+static glu::VarType generateRandomType (const int maxDepth, int& curStructIdx, vector<const StructType*>& structTypesDst, Random& rnd)
+{
+	const bool isStruct		= maxDepth > 0 && rnd.getFloat() < 0.2f;
+	const bool isArray		= rnd.getFloat() < 0.3f;
+
+	if (isStruct)
+	{
+		const int			numMembers = rnd.getInt(1, 5);
+		StructType* const	structType = new StructType(("structType" + de::toString(curStructIdx++)).c_str());
+
+		for (int i = 0; i < numMembers; i++)
+			structType->addMember(("m" + de::toString(i)).c_str(), generateRandomType(maxDepth-1, curStructIdx, structTypesDst, rnd));
+
+		structTypesDst.push_back(structType);
+		return isArray ? glu::VarType(glu::VarType(structType), rnd.getInt(1, 5)) : glu::VarType(structType);
+	}
+	else
+	{
+		const glu::DataType		basicType = (glu::DataType)s_testDataTypes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testDataTypes)-1)];
+		const glu::Precision	precision = glu::isDataTypeBoolOrBVec(basicType) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		return isArray ? glu::VarType(glu::VarType(basicType, precision), rnd.getInt(1, 5)) : glu::VarType(basicType, precision);
+	}
+}
+
+namespace
+{
+
+struct VarValue
+{
+	glu::DataType type;
+
+	union
+	{
+		float		floatV[4*4]; // At most mat4. \note Matrices here are column-major.
+		deInt32		intV[4];
+		bool		boolV[4];
+		struct
+		{
+			int		unit;
+			float	fillColor[4];
+		} samplerV;
+	} val;
+};
+
+enum CaseShaderType
+{
+	CASESHADERTYPE_VERTEX = 0,
+	CASESHADERTYPE_FRAGMENT,
+	CASESHADERTYPE_BOTH,
+
+	CASESHADERTYPE_LAST
+};
+
+struct Uniform
+{
+	string			name;
+	glu::VarType	type;
+
+	Uniform (const char* const name_, const glu::VarType& type_) : name(name_), type(type_) {}
+};
+
+// A set of uniforms, along with related struct types.
+class UniformCollection
+{
+public:
+	int					getNumUniforms		(void) const					{ return (int)m_uniforms.size();	}
+	int					getNumStructTypes	(void) const					{ return (int)m_structTypes.size();	}
+	Uniform&			getUniform			(const int ndx)					{ return m_uniforms[ndx];			}
+	const Uniform&		getUniform			(const int ndx) const			{ return m_uniforms[ndx];			}
+	const StructType*	getStructType		(const int ndx) const			{ return m_structTypes[ndx];		}
+	void				addUniform			(const Uniform& uniform)		{ m_uniforms.push_back(uniform);	}
+	void				addStructType		(const StructType* const type)	{ m_structTypes.push_back(type);	}
+
+	UniformCollection	(void) {}
+	~UniformCollection	(void)
+	{
+		for (int i = 0; i < (int)m_structTypes.size(); i++)
+			delete m_structTypes[i];
+	}
+
+	// Add the contents of m_uniforms and m_structTypes to receiver, and remove them from this one.
+	// \note receiver takes ownership of the struct types.
+	void moveContents (UniformCollection& receiver)
+	{
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			receiver.addUniform(m_uniforms[i]);
+		m_uniforms.clear();
+
+		for (int i = 0; i < (int)m_structTypes.size(); i++)
+			receiver.addStructType(m_structTypes[i]);
+		m_structTypes.clear();
+	}
+
+	bool containsMatchingBasicType (const dataTypePredicate predicate) const
+	{
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			if (typeContainsMatchingBasicType(m_uniforms[i].type, predicate))
+				return true;
+		return false;
+	}
+
+	vector<glu::DataType> getSamplerTypes (void) const
+	{
+		vector<glu::DataType> samplerTypes;
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			getDistinctSamplerTypes(samplerTypes, m_uniforms[i].type);
+		return samplerTypes;
+	}
+
+	bool containsSeveralSamplerTypes (void) const
+	{
+		return getSamplerTypes().size() > 1;
+	}
+
+	int getNumSamplers (void) const
+	{
+		int sum = 0;
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			sum += getNumSamplersInType(m_uniforms[i].type);
+		return sum;
+	}
+
+	static UniformCollection* basic (const glu::DataType type, const char* const nameSuffix = "")
+	{
+		UniformCollection* const	res		= new UniformCollection;
+		const glu::Precision		prec	= glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		res->m_uniforms.push_back(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(type, prec)));
+		return res;
+	}
+
+	static UniformCollection* basicArray (const glu::DataType type, const char* const nameSuffix = "")
+	{
+		UniformCollection* const	res		= new UniformCollection;
+		const glu::Precision		prec	= glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		res->m_uniforms.push_back(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(glu::VarType(type, prec), 3)));
+		return res;
+	}
+
+	static UniformCollection* basicStruct (const glu::DataType type0, const glu::DataType type1, const bool containsArrays, const char* const nameSuffix = "")
+	{
+		UniformCollection* const	res		= new UniformCollection;
+		const glu::Precision		prec0	= glu::isDataTypeBoolOrBVec(type0) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		const glu::Precision		prec1	= glu::isDataTypeBoolOrBVec(type1) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+
+		StructType* const structType = new StructType((string("structType") + nameSuffix).c_str());
+		structType->addMember("m0", glu::VarType(type0, prec0));
+		structType->addMember("m1", glu::VarType(type1, prec1));
+		if (containsArrays)
+		{
+			structType->addMember("m2", glu::VarType(glu::VarType(type0, prec0), 3));
+			structType->addMember("m3", glu::VarType(glu::VarType(type1, prec1), 3));
+		}
+
+		res->addStructType(structType);
+		res->addUniform(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(structType)));
+
+		return res;
+	}
+
+	static UniformCollection* structInArray (const glu::DataType type0, const glu::DataType type1, const bool containsArrays, const char* const nameSuffix = "")
+	{
+		UniformCollection* const res = basicStruct(type0, type1, containsArrays, nameSuffix);
+		res->getUniform(0).type = glu::VarType(res->getUniform(0).type, 3);
+		return res;
+	}
+
+	static UniformCollection* nestedArraysStructs (const glu::DataType type0, const glu::DataType type1, const char* const nameSuffix = "")
+	{
+		UniformCollection* const res		= new UniformCollection;
+		const glu::Precision prec0			= glu::isDataTypeBoolOrBVec(type0) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		const glu::Precision prec1			= glu::isDataTypeBoolOrBVec(type1) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		StructType* const structType		= new StructType((string("structType") + nameSuffix).c_str());
+		StructType* const subStructType		= new StructType((string("subStructType") + nameSuffix).c_str());
+		StructType* const subSubStructType	= new StructType((string("subSubStructType") + nameSuffix).c_str());
+
+		subSubStructType->addMember("mss0", glu::VarType(type0, prec0));
+		subSubStructType->addMember("mss1", glu::VarType(type1, prec1));
+
+		subStructType->addMember("ms0", glu::VarType(type1, prec1));
+		subStructType->addMember("ms1", glu::VarType(glu::VarType(type0, prec0), 2));
+		subStructType->addMember("ms2", glu::VarType(glu::VarType(subSubStructType), 2));
+
+		structType->addMember("m0", glu::VarType(type0, prec0));
+		structType->addMember("m1", glu::VarType(subStructType));
+		structType->addMember("m2", glu::VarType(type1, prec1));
+
+		res->addStructType(subSubStructType);
+		res->addStructType(subStructType);
+		res->addStructType(structType);
+
+		res->addUniform(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(structType)));
+
+		return res;
+	}
+
+	static UniformCollection* multipleBasic (const char* const nameSuffix = "")
+	{
+		static const glu::DataType	types[]	= { glu::TYPE_FLOAT, glu::TYPE_INT_VEC3, glu::TYPE_FLOAT_MAT3, glu::TYPE_BOOL_VEC2 };
+		UniformCollection* const	res		= new UniformCollection;
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(types); i++)
+		{
+			UniformCollection* const sub = basic(types[i], ("_" + de::toString(i) + nameSuffix).c_str());
+			sub->moveContents(*res);
+			delete sub;
+		}
+
+		return res;
+	}
+
+	static UniformCollection* multipleBasicArray (const char* const nameSuffix = "")
+	{
+		static const glu::DataType	types[]	= { glu::TYPE_FLOAT, glu::TYPE_INT_VEC3, glu::TYPE_BOOL_VEC2 };
+		UniformCollection* const	res		= new UniformCollection;
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(types); i++)
+		{
+			UniformCollection* const sub = basicArray(types[i], ("_" + de::toString(i) + nameSuffix).c_str());
+			sub->moveContents(*res);
+			delete sub;
+		}
+
+		return res;
+	}
+
+	static UniformCollection* multipleNestedArraysStructs (const char* const nameSuffix = "")
+	{
+		static const glu::DataType	types0[]	= { glu::TYPE_FLOAT,		glu::TYPE_INT,		glu::TYPE_BOOL_VEC4 };
+		static const glu::DataType	types1[]	= { glu::TYPE_FLOAT_VEC4,	glu::TYPE_INT_VEC4,	glu::TYPE_BOOL };
+		UniformCollection* const	res			= new UniformCollection;
+
+		DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types0) == DE_LENGTH_OF_ARRAY(types1));
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(types0); i++)
+		{
+			UniformCollection* const sub = nestedArraysStructs(types0[i], types1[i], ("_" + de::toString(i) + nameSuffix).c_str());
+			sub->moveContents(*res);
+			delete sub;
+		}
+
+		return res;
+	}
+
+	static UniformCollection* random (const deUint32 seed)
+	{
+		Random						rnd			(seed);
+		const int					numUniforms	= rnd.getInt(1, 5);
+		int							structIdx	= 0;
+		UniformCollection* const	res			= new UniformCollection;
+
+		for (int i = 0; i < numUniforms; i++)
+		{
+			vector<const StructType*>	structTypes;
+			Uniform						uniform(("u_var" + de::toString(i)).c_str(), glu::VarType());
+
+			// \note Discard uniforms that would cause number of samplers to exceed MAX_NUM_SAMPLER_UNIFORMS.
+			do
+			{
+				for (int j = 0; j < (int)structTypes.size(); j++)
+					delete structTypes[j];
+				structTypes.clear();
+				uniform.type = (("u_var" + de::toString(i)).c_str(), generateRandomType(3, structIdx, structTypes, rnd));
+			} while (res->getNumSamplers() + getNumSamplersInType(uniform.type) > MAX_NUM_SAMPLER_UNIFORMS);
+
+			res->addUniform(uniform);
+			for (int j = 0; j < (int)structTypes.size(); j++)
+				res->addStructType(structTypes[j]);
+		}
+
+		return res;
+	}
+
+private:
+	// \note Copying these would be cumbersome, since deep-copying both m_uniforms and m_structTypes
+	// would mean that we'd need to update pointers from uniforms to point to the new structTypes.
+	// When the same UniformCollection is needed in several places, a SharedPtr is used instead.
+								UniformCollection	(const UniformCollection&); // Not allowed.
+	UniformCollection&			operator=			(const UniformCollection&); // Not allowed.
+
+	vector<Uniform>				m_uniforms;
+	vector<const StructType*>	m_structTypes;
+};
+
+}; // anonymous
+
+static VarValue getSamplerFillValue (const VarValue& sampler)
+{
+	DE_ASSERT(glu::isDataTypeSampler(sampler.type));
+
+	VarValue result;
+	result.type = glu::TYPE_FLOAT_VEC4;
+
+	for (int i = 0; i < 4; i++)
+		result.val.floatV[i] = sampler.val.samplerV.fillColor[i];
+
+	return result;
+}
+
+static VarValue getSamplerUnitValue (const VarValue& sampler)
+{
+	DE_ASSERT(glu::isDataTypeSampler(sampler.type));
+
+	VarValue result;
+	result.type = glu::TYPE_INT;
+	result.val.intV[0] = sampler.val.samplerV.unit;
+
+	return result;
+}
+
+static string shaderVarValueStr (const VarValue& value)
+{
+	const int			numElems = glu::getDataTypeScalarSize(value.type);
+	std::ostringstream	result;
+
+	if (numElems > 1)
+		result << glu::getDataTypeName(value.type) << "(";
+
+	for (int i = 0; i < numElems; i++)
+	{
+		if (i > 0)
+			result << ", ";
+
+		if (glu::isDataTypeFloatOrVec(value.type) || glu::isDataTypeMatrix(value.type))
+			result << de::floatToString(value.val.floatV[i], 2);
+		else if (glu::isDataTypeIntOrIVec((value.type)))
+			result << de::toString(value.val.intV[i]);
+		else if (glu::isDataTypeBoolOrBVec((value.type)))
+			result << (value.val.boolV[i] ? "true" : "false");
+		else if (glu::isDataTypeSampler((value.type)))
+			result << shaderVarValueStr(getSamplerFillValue(value));
+		else
+			DE_ASSERT(false);
+	}
+
+	if (numElems > 1)
+		result << ")";
+
+	return result.str();
+}
+
+static string apiVarValueStr (const VarValue& value)
+{
+	const int			numElems = glu::getDataTypeScalarSize(value.type);
+	std::ostringstream	result;
+
+	if (numElems > 1)
+		result << "(";
+
+	for (int i = 0; i < numElems; i++)
+	{
+		if (i > 0)
+			result << ", ";
+
+		if (glu::isDataTypeFloatOrVec(value.type) || glu::isDataTypeMatrix(value.type))
+			result << de::floatToString(value.val.floatV[i], 2);
+		else if (glu::isDataTypeIntOrIVec((value.type)))
+			result << de::toString(value.val.intV[i]);
+		else if (glu::isDataTypeBoolOrBVec((value.type)))
+			result << (value.val.boolV[i] ? "true" : "false");
+		else if (glu::isDataTypeSampler((value.type)))
+			result << value.val.samplerV.unit;
+		else
+			DE_ASSERT(false);
+	}
+
+	if (numElems > 1)
+		result << ")";
+
+	return result.str();
+}
+
+static VarValue generateRandomVarValue (const glu::DataType type, Random& rnd, int samplerUnit = -1 /* Used if type is a sampler type. \note Samplers' unit numbers are not randomized. */)
+{
+	const int	numElems = glu::getDataTypeScalarSize(type);
+	VarValue	result;
+	result.type = type;
+
+	DE_ASSERT((samplerUnit >= 0) == (glu::isDataTypeSampler(type)));
+
+	if (glu::isDataTypeFloatOrVec(type) || glu::isDataTypeMatrix(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.floatV[i] = rnd.getFloat(-10.0f, 10.0f);
+	}
+	else if (glu::isDataTypeIntOrIVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.intV[i] = rnd.getInt(-10, 10);
+	}
+	else if (glu::isDataTypeBoolOrBVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.boolV[i] = rnd.getBool();
+	}
+	else if (glu::isDataTypeSampler(type))
+	{
+		result.val.samplerV.unit = samplerUnit;
+
+		for (int i = 0; i < 4; i++)
+			result.val.samplerV.fillColor[i] = rnd.getFloat(0.0f, 1.0f);
+	}
+	else
+		DE_ASSERT(false);
+
+	return result;
+}
+
+static VarValue generateZeroVarValue (const glu::DataType type)
+{
+	const int	numElems = glu::getDataTypeScalarSize(type);
+	VarValue	result;
+	result.type = type;
+
+	if (glu::isDataTypeFloatOrVec(type) || glu::isDataTypeMatrix(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.floatV[i] = 0.0f;
+	}
+	else if (glu::isDataTypeIntOrIVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.intV[i] = 0;
+	}
+	else if (glu::isDataTypeBoolOrBVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.boolV[i] = false;
+	}
+	else if (glu::isDataTypeSampler(type))
+	{
+		result.val.samplerV.unit = 0;
+
+		for (int i = 0; i < 4; i++)
+			result.val.samplerV.fillColor[i] = 0.12f * (float)i;
+	}
+	else
+		DE_ASSERT(false);
+
+	return result;
+}
+
+static bool apiVarValueEquals (const VarValue& a, const VarValue& b)
+{
+	const int		size			= glu::getDataTypeScalarSize(a.type);
+	const float		floatThreshold	= 0.05f;
+
+	DE_ASSERT(a.type == b.type);
+
+	if (glu::isDataTypeFloatOrVec(a.type) || glu::isDataTypeMatrix(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (de::abs(a.val.floatV[i] - b.val.floatV[i]) >= floatThreshold)
+				return false;
+	}
+	else if (glu::isDataTypeIntOrIVec(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (a.val.intV[i] != b.val.intV[i])
+				return false;
+	}
+	else if (glu::isDataTypeBoolOrBVec(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (a.val.boolV[i] != b.val.boolV[i])
+				return false;
+	}
+	else if (glu::isDataTypeSampler(a.type))
+	{
+		if (a.val.samplerV.unit != b.val.samplerV.unit)
+			return false;
+	}
+	else
+		DE_ASSERT(false);
+
+	return true;
+}
+
+static VarValue getRandomBoolRepresentation (const VarValue& boolValue, const glu::DataType targetScalarType, Random& rnd)
+{
+	DE_ASSERT(glu::isDataTypeBoolOrBVec(boolValue.type));
+
+	const int				size		= glu::getDataTypeScalarSize(boolValue.type);
+	const glu::DataType		targetType	= size == 1 ? targetScalarType : glu::getDataTypeVector(targetScalarType, size);
+	VarValue				result;
+	result.type = targetType;
+
+	switch (targetScalarType)
+	{
+		case glu::TYPE_INT:
+			for (int i = 0; i < size; i++)
+			{
+				if (boolValue.val.boolV[i])
+				{
+					result.val.intV[i] = rnd.getInt(-10, 10);
+					if (result.val.intV[i] == 0)
+						result.val.intV[i] = 1;
+				}
+				else
+					result.val.intV[i] = 0;
+			}
+			break;
+
+		case glu::TYPE_FLOAT:
+			for (int i = 0; i < size; i++)
+			{
+				if (boolValue.val.boolV[i])
+				{
+					result.val.floatV[i] = rnd.getFloat(-10.0f, 10.0f);
+					if (result.val.floatV[i] == 0.0f)
+						result.val.floatV[i] = 1.0f;
+				}
+				else
+					result.val.floatV[i] = 0;
+			}
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	return result;
+}
+
+static const char* getCaseShaderTypeName (const CaseShaderType type)
+{
+	switch (type)
+	{
+		case CASESHADERTYPE_VERTEX:		return "vertex";
+		case CASESHADERTYPE_FRAGMENT:	return "fragment";
+		case CASESHADERTYPE_BOTH:		return "both";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+static CaseShaderType randomCaseShaderType (const deUint32 seed)
+{
+	return (CaseShaderType)Random(seed).getInt(0, CASESHADERTYPE_LAST-1);
+}
+
+class UniformCase : public TestCase, protected glu::CallLogWrapper
+{
+public:
+	enum Feature
+	{
+		// ARRAYUSAGE_ONLY_MIDDLE_INDEX: only middle index of each array is used in shader. If not given, use all indices.
+		FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX	= 1<<0,
+
+		// UNIFORMFUNC_VALUE: use pass-by-value versions of uniform assignment funcs, e.g. glUniform1f(), where possible. If not given, use pass-by-pointer versions.
+		FEATURE_UNIFORMFUNC_VALUE				= 1<<1,
+
+		// ARRAYASSIGN: how basic-type arrays are assigned with glUniform*(). If none given, assign each element of an array separately.
+		FEATURE_ARRAYASSIGN_FULL				= 1<<2, //!< Assign all elements of an array with one glUniform*().
+		FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO		= 1<<3, //!< Assign two elements per one glUniform*().
+
+		// UNIFORMUSAGE_EVERY_OTHER: use about half of the uniforms. If not given, use all uniforms (except that some array indices may be omitted according to ARRAYUSAGE).
+		FEATURE_UNIFORMUSAGE_EVERY_OTHER		= 1<<4,
+
+		// BOOLEANAPITYPE: type used to pass booleans to and from GL api. If none given, use float.
+		FEATURE_BOOLEANAPITYPE_INT				= 1<<5,
+
+		// UNIFORMVALUE_ZERO: use zero-valued uniforms. If not given, use random uniform values.
+		FEATURE_UNIFORMVALUE_ZERO				= 1<<6,
+
+		// ARRAY_FIRST_ELEM_NAME_NO_INDEX: in certain API functions, when referring to the first element of an array, use just the array name without [0] at the end.
+		FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX	= 1<<7
+	};
+
+								UniformCase		(Context& context, const char* name, const char* description, CaseShaderType caseType, const SharedPtr<const UniformCollection>& uniformCollection);
+								UniformCase		(Context& context, const char* name, const char* description, CaseShaderType caseType, const SharedPtr<const UniformCollection>& uniformCollection, deUint32 features);
+								UniformCase		(Context& context, const char* name, const char* description, deUint32 seed); // \note Randomizes caseType, uniformCollection and features.
+	virtual						~UniformCase	(void);
+
+	virtual void				init			(void);
+	virtual void				deinit			(void);
+
+	IterateResult				iterate			(void);
+
+protected:
+	// A basic uniform is a uniform (possibly struct or array member) whose type is a basic type (e.g. float, ivec4, sampler2d).
+	struct BasicUniform
+	{
+		string			name;
+		glu::DataType	type;
+		bool			isUsedInShader;
+		VarValue		finalValue;	//!< The value we ultimately want to set for this uniform.
+
+		string			rootName;	//!< If this is a member of a basic-typed array, rootName is the name of that array with "[0]" appended. Otherwise it equals name.
+		int				elemNdx;	//!< If this is a member of a basic-typed array, elemNdx is the index in that array. Otherwise -1.
+		int				rootSize;	//!< If this is a member of a basic-typed array, rootSize is the size of that array. Otherwise 1.
+
+		BasicUniform (const char* const		name_,
+					  const glu::DataType	type_,
+					  const bool			isUsedInShader_,
+					  const VarValue&		finalValue_,
+					  const char* const		rootName_	= DE_NULL,
+					  const int				elemNdx_	= -1,
+					  const int				rootSize_	= 1)
+					  : name			(name_)
+					  , type			(type_)
+					  , isUsedInShader	(isUsedInShader_)
+					  , finalValue		(finalValue_)
+					  , rootName		(rootName_ == DE_NULL ? name_ : rootName_)
+					  , elemNdx			(elemNdx_)
+					  , rootSize		(rootSize_)
+					 {
+					 }
+
+		static vector<BasicUniform>::const_iterator findWithName (const vector<BasicUniform>& vec, const char* const name)
+		{
+			for (vector<BasicUniform>::const_iterator it = vec.begin(); it != vec.end(); it++)
+			{
+				if (it->name == name)
+					return it;
+			}
+			return vec.end();
+		}
+	};
+
+	// Reference values for info that is expected to be reported by glGetActiveUniform().
+	struct BasicUniformReportRef
+	{
+		string			name;
+		// \note minSize and maxSize are for arrays and can be distinct since implementations are allowed, but not required, to trim the inactive end indices of arrays.
+		int				minSize;
+		int				maxSize;
+		glu::DataType	type;
+		bool			isUsedInShader;
+
+		BasicUniformReportRef (const char* const name_, const int minS, const int maxS, const glu::DataType type_, const bool used)
+			: name(name_), minSize(minS), maxSize(maxS), type(type_), isUsedInShader(used) { DE_ASSERT(minSize <= maxSize); }
+		BasicUniformReportRef (const char* const name_, const glu::DataType type_, const bool used)
+			: name(name_), minSize(1), maxSize(1), type(type_), isUsedInShader(used) {}
+	};
+
+	// Info that is actually reported by glGetActiveUniform().
+	struct BasicUniformReportGL
+	{
+		string			name;
+		int				nameLength;
+		int				size;
+		glu::DataType	type;
+
+		int				index;
+
+		BasicUniformReportGL (const char* const name_, const int nameLength_, const int size_, const glu::DataType type_, const int index_)
+			: name(name_), nameLength(nameLength_), size(size_), type(type_), index(index_) {}
+
+		static vector<BasicUniformReportGL>::const_iterator findWithName (const vector<BasicUniformReportGL>& vec, const char* const name)
+		{
+			for (vector<BasicUniformReportGL>::const_iterator it = vec.begin(); it != vec.end(); it++)
+			{
+				if (it->name == name)
+					return it;
+			}
+			return vec.end();
+		}
+	};
+
+	// Query info with glGetActiveUniform() and check validity.
+	bool						getActiveUniforms						(vector<BasicUniformReportGL>& dst, const vector<BasicUniformReportRef>& ref, deUint32 programGL);
+	// Get uniform values with glGetUniform*() and put to valuesDst. Uniforms that get -1 from glGetUniformLocation() get glu::TYPE_INVALID.
+	bool						getUniforms								(vector<VarValue>& valuesDst, const vector<BasicUniform>& basicUniforms, deUint32 programGL);
+	// Check that every uniform has the default (zero) value.
+	bool						checkUniformDefaultValues				(const vector<VarValue>& values, const vector<BasicUniform>& basicUniforms);
+	// Assign the basicUniforms[].finalValue values for uniforms. \note rnd parameter is for booleans (true can be any nonzero value).
+	void						assignUniforms							(const vector<BasicUniform>& basicUniforms, deUint32 programGL, Random& rnd);
+	// Compare the uniform values given in values (obtained with glGetUniform*()) with the basicUniform.finalValue values.
+	bool						compareUniformValues					(const vector<VarValue>& values, const vector<BasicUniform>& basicUniforms);
+	// Render and check that all pixels are white (i.e. all uniform comparisons passed).
+	bool						renderTest								(const vector<BasicUniform>& basicUniforms, const ShaderProgram& program, Random& rnd);
+
+	virtual bool				test									(const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd) = 0;
+
+	const deUint32								m_features;
+	const SharedPtr<const UniformCollection>	m_uniformCollection;
+
+private:
+	static deUint32				randomFeatures							(deUint32 seed);
+
+	// Generates the basic uniforms, based on the uniform with name varName and type varType, in the same manner as are expected
+	// to be returned by glGetActiveUniform(), e.g. generates a name like var[0] for arrays, and recursively generates struct member names.
+	void						generateBasicUniforms					(vector<BasicUniform>&				basicUniformsDst,
+																		 vector<BasicUniformReportRef>&		basicUniformReportsDst,
+																		 const glu::VarType&				varType,
+																		 const char*						varName,
+																		 bool								isParentActive,
+																		 int&								samplerUnitCounter,
+																		 Random&							rnd) const;
+
+	void						writeUniformDefinitions					(std::ostringstream& dst) const;
+	void						writeUniformCompareExpr					(std::ostringstream& dst, const BasicUniform& uniform) const;
+	void						writeUniformComparisons					(std::ostringstream& dst, const vector<BasicUniform>& basicUniforms, const char* variableName) const;
+
+	string						generateVertexSource					(const vector<BasicUniform>& basicUniforms) const;
+	string						generateFragmentSource					(const vector<BasicUniform>& basicUniforms) const;
+
+	void						setupTexture							(const VarValue& value);
+
+	const CaseShaderType						m_caseShaderType;
+
+	vector<glu::Texture2D*>						m_textures2d;
+	vector<glu::TextureCube*>					m_texturesCube;
+	vector<deUint32>							m_filledTextureUnits;
+};
+
+deUint32 UniformCase::randomFeatures (const deUint32 seed)
+{
+	static const deUint32 arrayUsageChoices[]		= { 0, FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX										};
+	static const deUint32 uniformFuncChoices[]		= { 0, FEATURE_UNIFORMFUNC_VALUE												};
+	static const deUint32 arrayAssignChoices[]		= { 0, FEATURE_ARRAYASSIGN_FULL,			FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO	};
+	static const deUint32 uniformUsageChoices[]		= { 0, FEATURE_UNIFORMUSAGE_EVERY_OTHER											};
+	static const deUint32 booleanApiTypeChoices[]	= { 0, FEATURE_BOOLEANAPITYPE_INT												};
+	static const deUint32 uniformValueChoices[]		= { 0, FEATURE_UNIFORMVALUE_ZERO												};
+
+	Random rnd(seed);
+
+	deUint32 result = 0;
+
+#define ARRAY_CHOICE(ARR) (ARR[rnd.getInt(0, DE_LENGTH_OF_ARRAY(ARR)-1)])
+
+	result |= ARRAY_CHOICE(arrayUsageChoices);
+	result |= ARRAY_CHOICE(uniformFuncChoices);
+	result |= ARRAY_CHOICE(arrayAssignChoices);
+	result |= ARRAY_CHOICE(uniformUsageChoices);
+	result |= ARRAY_CHOICE(booleanApiTypeChoices);
+	result |= ARRAY_CHOICE(uniformValueChoices);
+
+#undef ARRAY_CHOICE
+
+	return result;
+}
+
+UniformCase::UniformCase (Context& context, const char* const name, const char* const description, const CaseShaderType caseShaderType, const SharedPtr<const UniformCollection>& uniformCollection, const deUint32 features)
+	: TestCase				(context, name, description)
+	, CallLogWrapper		(context.getRenderContext().getFunctions(), m_testCtx.getLog())
+	, m_features			(features)
+	, m_uniformCollection	(uniformCollection)
+	, m_caseShaderType		(caseShaderType)
+{
+}
+
+UniformCase::UniformCase (Context& context, const char* const name, const char* const description, const CaseShaderType caseShaderType, const SharedPtr<const UniformCollection>& uniformCollection)
+	: TestCase				(context, name, description)
+	, CallLogWrapper		(context.getRenderContext().getFunctions(), m_testCtx.getLog())
+	, m_features			(0)
+	, m_uniformCollection	(uniformCollection)
+	, m_caseShaderType		(caseShaderType)
+{
+}
+
+UniformCase::UniformCase (Context& context, const char* name, const char* description, const deUint32 seed)
+	: TestCase				(context, name, description)
+	, CallLogWrapper		(context.getRenderContext().getFunctions(), m_testCtx.getLog())
+	, m_features			(randomFeatures(seed))
+	, m_uniformCollection	(UniformCollection::random(seed))
+	, m_caseShaderType		(randomCaseShaderType(seed))
+{
+}
+
+void UniformCase::init (void)
+{
+	{
+		const glw::Functions&	funcs						= m_context.getRenderContext().getFunctions();
+		const int				numSamplerUniforms			= m_uniformCollection->getNumSamplers();
+		const int				vertexTexUnitsRequired		= m_caseShaderType != CASESHADERTYPE_FRAGMENT ? numSamplerUniforms : 0;
+		const int				fragmentTexUnitsRequired	= m_caseShaderType != CASESHADERTYPE_VERTEX ? numSamplerUniforms : 0;
+		const int				combinedTexUnitsRequired	= vertexTexUnitsRequired + fragmentTexUnitsRequired;
+		const int				vertexTexUnitsSupported		= getGLInt(funcs, GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS);
+		const int				fragmentTexUnitsSupported	= getGLInt(funcs, GL_MAX_TEXTURE_IMAGE_UNITS);
+		const int				combinedTexUnitsSupported	= getGLInt(funcs, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+
+		DE_ASSERT(numSamplerUniforms <= MAX_NUM_SAMPLER_UNIFORMS);
+
+		if (vertexTexUnitsRequired > vertexTexUnitsSupported)
+			throw tcu::NotSupportedError(de::toString(vertexTexUnitsRequired) + " vertex texture units required, " + de::toString(vertexTexUnitsSupported) + " supported");
+		if (fragmentTexUnitsRequired > fragmentTexUnitsSupported)
+			throw tcu::NotSupportedError(de::toString(fragmentTexUnitsRequired) + " fragment texture units required, " + de::toString(fragmentTexUnitsSupported) + " supported");
+		if (combinedTexUnitsRequired > combinedTexUnitsSupported)
+			throw tcu::NotSupportedError(de::toString(combinedTexUnitsRequired) + " combined texture units required, " + de::toString(combinedTexUnitsSupported) + " supported");
+	}
+
+	enableLogging(true);
+}
+
+void UniformCase::deinit (void)
+{
+	for (int i = 0; i < (int)m_textures2d.size(); i++)
+		delete m_textures2d[i];
+	m_textures2d.clear();
+
+	for (int i = 0; i < (int)m_texturesCube.size(); i++)
+		delete m_texturesCube[i];
+	m_texturesCube.clear();
+
+	m_filledTextureUnits.clear();
+}
+
+UniformCase::~UniformCase (void)
+{
+	UniformCase::deinit();
+}
+
+void UniformCase::generateBasicUniforms (vector<BasicUniform>& basicUniformsDst, vector<BasicUniformReportRef>& basicUniformReportsDst, const glu::VarType& varType, const char* const varName, const bool isParentActive, int& samplerUnitCounter, Random& rnd) const
+{
+	if (varType.isBasicType())
+	{
+		const bool				isActive	= isParentActive && (m_features & FEATURE_UNIFORMUSAGE_EVERY_OTHER ? basicUniformsDst.size() % 2 == 0 : true);
+		const glu::DataType		type		= varType.getBasicType();
+		const VarValue			value		= m_features & FEATURE_UNIFORMVALUE_ZERO	? generateZeroVarValue(type)
+											: glu::isDataTypeSampler(type)				? generateRandomVarValue(type, rnd, samplerUnitCounter++)
+											: generateRandomVarValue(varType.getBasicType(), rnd);
+
+		basicUniformsDst.push_back(BasicUniform(varName, varType.getBasicType(), isActive, value));
+		basicUniformReportsDst.push_back(BasicUniformReportRef(varName, varType.getBasicType(), isActive));
+	}
+	else if (varType.isArrayType())
+	{
+		const int		size			= varType.getArraySize();
+		const string	arrayRootName	= string("") + varName + "[0]";
+		vector<bool>	isElemActive;
+
+		for (int elemNdx = 0; elemNdx < varType.getArraySize(); elemNdx++)
+		{
+			const string	indexedName		= string("") + varName + "[" + de::toString(elemNdx) + "]";
+			const bool		isCurElemActive	= isParentActive																						&&
+											  (m_features & FEATURE_UNIFORMUSAGE_EVERY_OTHER			? basicUniformsDst.size() % 2 == 0	: true)	&&
+											  (m_features & FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX		? elemNdx == size/2					: true);
+
+			isElemActive.push_back(isCurElemActive);
+
+			if (varType.getElementType().isBasicType())
+			{
+				// \note We don't want separate entries in basicUniformReportsDst for elements of basic-type arrays.
+				const glu::DataType	elemBasicType	= varType.getElementType().getBasicType();
+				const VarValue		value			= m_features & FEATURE_UNIFORMVALUE_ZERO	? generateZeroVarValue(elemBasicType)
+													: glu::isDataTypeSampler(elemBasicType)		? generateRandomVarValue(elemBasicType, rnd, samplerUnitCounter++)
+													: generateRandomVarValue(elemBasicType, rnd);
+
+				basicUniformsDst.push_back(BasicUniform(indexedName.c_str(), elemBasicType, isCurElemActive, value, arrayRootName.c_str(), elemNdx, size));
+			}
+			else
+				generateBasicUniforms(basicUniformsDst, basicUniformReportsDst, varType.getElementType(), indexedName.c_str(), isCurElemActive, samplerUnitCounter, rnd);
+		}
+
+		if (varType.getElementType().isBasicType())
+		{
+			int minSize;
+			for (minSize = varType.getArraySize(); minSize > 0 && !isElemActive[minSize-1]; minSize--);
+
+			basicUniformReportsDst.push_back(BasicUniformReportRef(arrayRootName.c_str(), minSize, size, varType.getElementType().getBasicType(), isParentActive && minSize > 0));
+		}
+	}
+	else
+	{
+		DE_ASSERT(varType.isStructType());
+
+		const StructType& structType = *varType.getStructPtr();
+
+		for (int i = 0; i < structType.getNumMembers(); i++)
+		{
+			const glu::StructMember&	member			= structType.getMember(i);
+			const string				memberFullName	= string("") + varName + "." + member.getName();
+
+			generateBasicUniforms(basicUniformsDst, basicUniformReportsDst, member.getType(), memberFullName.c_str(), isParentActive, samplerUnitCounter, rnd);
+		}
+	}
+}
+
+void UniformCase::writeUniformDefinitions (std::ostringstream& dst) const
+{
+	for (int i = 0; i < (int)m_uniformCollection->getNumStructTypes(); i++)
+		dst << glu::declare(m_uniformCollection->getStructType(i)) << ";\n";
+
+	for (int i = 0; i < (int)m_uniformCollection->getNumUniforms(); i++)
+		dst << "uniform " << glu::declare(m_uniformCollection->getUniform(i).type, m_uniformCollection->getUniform(i).name.c_str()) << ";\n";
+
+	dst << "\n";
+
+	{
+		static const struct
+		{
+			dataTypePredicate	requiringTypes[2];
+			const char*			definition;
+		} compareFuncs[] =
+		{
+			{ { glu::isDataTypeFloatOrVec,				glu::isDataTypeMatrix				}, "mediump float compare_float    (mediump float a, mediump float b)  { return abs(a - b) < 0.05 ? 1.0 : 0.0; }"																		},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_VEC2>,	dataTypeIsMatrixWithNRows<2>		}, "mediump float compare_vec2     (mediump vec2 a, mediump vec2 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y); }"														},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_VEC3>,	dataTypeIsMatrixWithNRows<3>		}, "mediump float compare_vec3     (mediump vec3 a, mediump vec3 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y)*compare_float(a.z, b.z); }"								},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_VEC4>,	dataTypeIsMatrixWithNRows<4>		}, "mediump float compare_vec4     (mediump vec4 a, mediump vec4 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y)*compare_float(a.z, b.z)*compare_float(a.w, b.w); }"		},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat2     (mediump mat2 a, mediump mat2 b)    { return compare_vec2(a[0], b[0])*compare_vec2(a[1], b[1]); }"													},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat3     (mediump mat3 a, mediump mat3 b)    { return compare_vec3(a[0], b[0])*compare_vec3(a[1], b[1])*compare_vec3(a[2], b[2]); }"							},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat4     (mediump mat4 a, mediump mat4 b)    { return compare_vec4(a[0], b[0])*compare_vec4(a[1], b[1])*compare_vec4(a[2], b[2])*compare_vec4(a[3], b[3]); }"	},
+			{ { dataTypeEquals<glu::TYPE_INT>,			dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_int      (mediump int a, mediump int b)      { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_INT_VEC2>,		dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_ivec2    (mediump ivec2 a, mediump ivec2 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_INT_VEC3>,		dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_ivec3    (mediump ivec3 a, mediump ivec3 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_INT_VEC4>,		dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_ivec4    (mediump ivec4 a, mediump ivec4 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL>,			dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bool     (bool a, bool b)                    { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL_VEC2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bvec2    (bvec2 a, bvec2 b)                  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL_VEC3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bvec3    (bvec3 a, bvec3 b)                  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL_VEC4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bvec4    (bvec4 a, bvec4 b)                  { return a == b ? 1.0 : 0.0; }"																					}
+		};
+
+		const bool containsSamplers = !m_uniformCollection->getSamplerTypes().empty();
+
+		for (int compFuncNdx = 0; compFuncNdx < DE_LENGTH_OF_ARRAY(compareFuncs); compFuncNdx++)
+		{
+			const dataTypePredicate		(&typeReq)[2]			= compareFuncs[compFuncNdx].requiringTypes;
+			const bool					containsTypeSampler		= containsSamplers && (typeReq[0](glu::TYPE_FLOAT_VEC4) || typeReq[1](glu::TYPE_FLOAT_VEC4));
+
+			if (containsTypeSampler || m_uniformCollection->containsMatchingBasicType(typeReq[0]) || m_uniformCollection->containsMatchingBasicType(typeReq[1]))
+				dst << compareFuncs[compFuncNdx].definition << "\n";
+		}
+	}
+}
+
+void UniformCase::writeUniformCompareExpr (std::ostringstream& dst, const BasicUniform& uniform) const
+{
+	if (glu::isDataTypeSampler(uniform.type))
+	{
+		dst << "compare_vec4("
+			<< (uniform.type == glu::TYPE_SAMPLER_2D ? "texture2D" : "textureCube")
+			<< "(" << uniform.name << ", vec" << getSamplerNumLookupDimensions(uniform.type) << "(0.0))";
+	}
+	else
+		dst << "compare_" << glu::getDataTypeName(uniform.type) << "(" << uniform.name;
+
+	dst << ", " << shaderVarValueStr(uniform.finalValue) << ")";
+}
+
+void UniformCase::writeUniformComparisons (std::ostringstream& dst, const vector<BasicUniform>& basicUniforms, const char* const variableName) const
+{
+	for (int i = 0; i < (int)basicUniforms.size(); i++)
+	{
+		const BasicUniform& unif = basicUniforms[i];
+
+		if (unif.isUsedInShader)
+		{
+			dst << "\t" << variableName << " *= ";
+			writeUniformCompareExpr(dst, basicUniforms[i]);
+			dst << ";\n";
+		}
+		else
+			dst << "\t// UNUSED: " << basicUniforms[i].name << "\n";
+	}
+}
+
+string UniformCase::generateVertexSource (const vector<BasicUniform>& basicUniforms) const
+{
+	const bool			isVertexCase = m_caseShaderType == CASESHADERTYPE_VERTEX || m_caseShaderType == CASESHADERTYPE_BOTH;
+	std::ostringstream	result;
+
+	result << "attribute highp vec4 a_position;\n"
+			  "varying mediump float v_vtxOut;\n"
+			  "\n";
+
+	if (isVertexCase)
+		writeUniformDefinitions(result);
+
+	result << "\n"
+			  "void main (void)\n"
+			  "{\n"
+			  "	gl_Position = a_position;\n"
+			  "	v_vtxOut = 1.0;\n";
+
+	if (isVertexCase)
+		writeUniformComparisons(result, basicUniforms, "v_vtxOut");
+
+	result << "}\n";
+
+	return result.str();
+}
+
+string UniformCase::generateFragmentSource (const vector<BasicUniform>& basicUniforms) const
+{
+	const bool			isFragmentCase = m_caseShaderType == CASESHADERTYPE_FRAGMENT || m_caseShaderType == CASESHADERTYPE_BOTH;
+	std::ostringstream	result;
+
+	result << "varying mediump float v_vtxOut;\n"
+			  "\n";
+
+	if (isFragmentCase)
+		writeUniformDefinitions(result);
+
+	result << "\n"
+			  "void main (void)\n"
+			  "{\n"
+			  "	mediump float result = v_vtxOut;\n";
+
+	if (isFragmentCase)
+		writeUniformComparisons(result, basicUniforms, "result");
+
+	result << "	gl_FragColor = vec4(result, result, result, 1.0);\n"
+			  "}\n";
+
+	return result.str();
+}
+
+void UniformCase::setupTexture (const VarValue& value)
+{
+	enableLogging(false);
+
+	const int						width			= 32;
+	const int						height			= 32;
+	const tcu::Vec4					color			= vec4FromPtr(&value.val.samplerV.fillColor[0]);
+
+	if (value.type == glu::TYPE_SAMPLER_2D)
+	{
+		glu::Texture2D* texture		= new glu::Texture2D(m_context.getRenderContext(), GL_RGBA, GL_UNSIGNED_BYTE, width, height);
+		tcu::Texture2D& refTexture	= texture->getRefTexture();
+		m_textures2d.push_back(texture);
+
+		refTexture.allocLevel(0);
+		fillWithColor(refTexture.getLevel(0), color);
+
+		GLU_CHECK_CALL(glActiveTexture(GL_TEXTURE0 + value.val.samplerV.unit));
+		m_filledTextureUnits.push_back(value.val.samplerV.unit);
+		texture->upload();
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+	}
+	else if (value.type == glu::TYPE_SAMPLER_CUBE)
+	{
+		DE_ASSERT(width == height);
+
+		glu::TextureCube* texture		= new glu::TextureCube(m_context.getRenderContext(), GL_RGBA, GL_UNSIGNED_BYTE, width);
+		tcu::TextureCube& refTexture	= texture->getRefTexture();
+		m_texturesCube.push_back(texture);
+
+		for (int face = 0; face < (int)tcu::CUBEFACE_LAST; face++)
+		{
+			refTexture.allocLevel((tcu::CubeFace)face, 0);
+			fillWithColor(refTexture.getLevelFace(0, (tcu::CubeFace)face), color);
+		}
+
+		GLU_CHECK_CALL(glActiveTexture(GL_TEXTURE0 + value.val.samplerV.unit));
+		m_filledTextureUnits.push_back(value.val.samplerV.unit);
+		texture->upload();
+
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+
+	}
+	else
+		DE_ASSERT(false);
+
+	enableLogging(true);
+}
+
+bool UniformCase::getActiveUniforms (vector<BasicUniformReportGL>& basicUniformReportsDst, const vector<BasicUniformReportRef>& basicUniformReportsRef, const deUint32 programGL)
+{
+	TestLog&			log						= m_testCtx.getLog();
+	GLint				numActiveUniforms		= 0;
+	GLint				uniformMaxNameLength	= 0;
+	vector<char>		nameBuffer;
+	bool				success					= true;
+
+	GLU_CHECK_CALL(glGetProgramiv(programGL, GL_ACTIVE_UNIFORMS, &numActiveUniforms));
+	log << TestLog::Message << "// Number of active uniforms reported: " << numActiveUniforms << TestLog::EndMessage;
+	GLU_CHECK_CALL(glGetProgramiv(programGL, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformMaxNameLength));
+	log << TestLog::Message << "// Maximum uniform name length reported: " << uniformMaxNameLength << TestLog::EndMessage;
+	nameBuffer.resize(uniformMaxNameLength);
+
+	for (int unifNdx = 0; unifNdx < numActiveUniforms; unifNdx++)
+	{
+		GLsizei					reportedNameLength	= 0;
+		GLint					reportedSize		= -1;
+		GLenum					reportedTypeGL		= GL_NONE;
+
+		GLU_CHECK_CALL(glGetActiveUniform(programGL, (GLuint)unifNdx, (GLsizei)uniformMaxNameLength, &reportedNameLength, &reportedSize, &reportedTypeGL, &nameBuffer[0]));
+
+		const glu::DataType		reportedType		= glu::getDataTypeFromGLType(reportedTypeGL);
+		const string			reportedNameStr		(&nameBuffer[0]);
+
+		TCU_CHECK_MSG(reportedType != glu::TYPE_LAST, "Invalid uniform type");
+
+		log << TestLog::Message << "// Got name = " << reportedNameStr << ", name length = " << reportedNameLength << ", size = " << reportedSize << ", type = " << glu::getDataTypeName(reportedType) << TestLog::EndMessage;
+
+		if ((GLsizei)reportedNameStr.length() != reportedNameLength)
+		{
+			log << TestLog::Message << "// FAILURE: wrong name length reported, should be " << reportedNameStr.length() << TestLog::EndMessage;
+			success = false;
+		}
+
+		if (!deStringBeginsWith(reportedNameStr.c_str(), "gl_")) // Ignore built-in uniforms.
+		{
+			int referenceNdx;
+			for (referenceNdx = 0; referenceNdx < (int)basicUniformReportsRef.size(); referenceNdx++)
+			{
+				if (basicUniformReportsRef[referenceNdx].name == reportedNameStr)
+					break;
+			}
+
+			if (referenceNdx >= (int)basicUniformReportsRef.size())
+			{
+				log << TestLog::Message << "// FAILURE: invalid non-built-in uniform name reported" << TestLog::EndMessage;
+				success = false;
+			}
+			else
+			{
+				const BasicUniformReportRef& reference = basicUniformReportsRef[referenceNdx];
+
+				DE_ASSERT(reference.type != glu::TYPE_LAST);
+				DE_ASSERT(reference.minSize >= 1 || (reference.minSize == 0 && !reference.isUsedInShader));
+				DE_ASSERT(reference.minSize <= reference.maxSize);
+
+				if (BasicUniformReportGL::findWithName(basicUniformReportsDst, reportedNameStr.c_str()) != basicUniformReportsDst.end())
+				{
+					log << TestLog::Message << "// FAILURE: same uniform name reported twice" << TestLog::EndMessage;
+					success = false;
+				}
+
+				basicUniformReportsDst.push_back(BasicUniformReportGL(reportedNameStr.c_str(), reportedNameLength, reportedSize, reportedType, unifNdx));
+
+				if (reportedType != reference.type)
+				{
+					log << TestLog::Message << "// FAILURE: wrong type reported, should be " << glu::getDataTypeName(reference.type) << TestLog::EndMessage;
+					success = false;
+				}
+				if (reportedSize < reference.minSize || reportedSize > reference.maxSize)
+				{
+					log << TestLog::Message
+						<< "// FAILURE: wrong size reported, should be "
+						<< (reference.minSize == reference.maxSize ? de::toString(reference.minSize) : "in the range [" + de::toString(reference.minSize) + ", " + de::toString(reference.maxSize) + "]")
+						<< TestLog::EndMessage;
+
+					success = false;
+				}
+			}
+		}
+	}
+
+	for (int i = 0; i < (int)basicUniformReportsRef.size(); i++)
+	{
+		const BasicUniformReportRef& expected = basicUniformReportsRef[i];
+		if (expected.isUsedInShader && BasicUniformReportGL::findWithName(basicUniformReportsDst, expected.name.c_str()) == basicUniformReportsDst.end())
+		{
+			log << TestLog::Message << "// FAILURE: uniform with name " << expected.name << " was not reported by GL" << TestLog::EndMessage;
+			success = false;
+		}
+	}
+
+	return success;
+}
+
+bool UniformCase::getUniforms (vector<VarValue>& valuesDst, const vector<BasicUniform>& basicUniforms, const deUint32 programGL)
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		success		= true;
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform		= basicUniforms[unifNdx];
+		const string			queryName	= m_features & FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX && uniform.elemNdx == 0 ? beforeLast(uniform.name, '[') : uniform.name;
+		const int				location	= glGetUniformLocation(programGL, queryName.c_str());
+		const int				size		= glu::getDataTypeScalarSize(uniform.type);
+		VarValue				value;
+
+		deMemset(&value, 0xcd, sizeof(value)); // Initialize to known garbage.
+
+		if (location == -1)
+		{
+			value.type = glu::TYPE_INVALID;
+			valuesDst.push_back(value);
+			if (uniform.isUsedInShader)
+			{
+				log << TestLog::Message << "// FAILURE: " << uniform.name << " was used in shader, but has location -1" << TestLog::EndMessage;
+				success = false;
+			}
+			continue;
+		}
+
+		value.type = uniform.type;
+
+		DE_STATIC_ASSERT(sizeof(GLint) == sizeof(value.val.intV[0]));
+		DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(value.val.floatV[0]));
+
+		if (glu::isDataTypeFloatOrVec(uniform.type) || glu::isDataTypeMatrix(uniform.type))
+			GLU_CHECK_CALL(glGetUniformfv(programGL, location, &value.val.floatV[0]));
+		else if (glu::isDataTypeIntOrIVec(uniform.type))
+			GLU_CHECK_CALL(glGetUniformiv(programGL, location, &value.val.intV[0]));
+		else if (glu::isDataTypeBoolOrBVec(uniform.type))
+		{
+			if (m_features & FEATURE_BOOLEANAPITYPE_INT)
+			{
+				GLU_CHECK_CALL(glGetUniformiv(programGL, location, &value.val.intV[0]));
+				for (int i = 0; i < size; i++)
+					value.val.boolV[i] = value.val.intV[i] != 0;
+			}
+			else // Default: use float.
+			{
+				GLU_CHECK_CALL(glGetUniformfv(programGL, location, &value.val.floatV[0]));
+				for (int i = 0; i < size; i++)
+					value.val.boolV[i] = value.val.floatV[i] != 0.0f;
+			}
+		}
+		else if (glu::isDataTypeSampler(uniform.type))
+		{
+			GLint unit = -1;
+			GLU_CHECK_CALL(glGetUniformiv(programGL, location, &unit));
+			value.val.samplerV.unit = unit;
+		}
+		else
+			DE_ASSERT(false);
+
+		valuesDst.push_back(value);
+
+		log << TestLog::Message << "// Got " << uniform.name << " value " << apiVarValueStr(value) << TestLog::EndMessage;
+	}
+
+	return success;
+}
+
+bool UniformCase::checkUniformDefaultValues (const vector<VarValue>& values, const vector<BasicUniform>& basicUniforms)
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		success		= true;
+
+	DE_ASSERT(values.size() == basicUniforms.size());
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform		= basicUniforms[unifNdx];
+		const VarValue&			unifValue	= values[unifNdx];
+		const int				valSize		= glu::getDataTypeScalarSize(uniform.type);
+
+		log << TestLog::Message << "// Checking uniform " << uniform.name << TestLog::EndMessage;
+
+		if (unifValue.type == glu::TYPE_INVALID) // This happens when glGetUniformLocation() returned -1.
+			continue;
+
+#define CHECK_UNIFORM(VAR_VALUE_MEMBER, ZERO)																								\
+	do																																		\
+	{																																		\
+		for (int i = 0; i < valSize; i++)																									\
+		{																																	\
+			if (unifValue.val.VAR_VALUE_MEMBER[i] != ZERO)																					\
+			{																																\
+				log << TestLog::Message << "// FAILURE: uniform " << uniform.name << " has non-zero initial value" << TestLog::EndMessage;	\
+				success = false;																											\
+			}																																\
+		}																																	\
+	} while (false)
+
+		if (glu::isDataTypeFloatOrVec(uniform.type) || glu::isDataTypeMatrix(uniform.type))
+			CHECK_UNIFORM(floatV, 0.0f);
+		else if (glu::isDataTypeIntOrIVec(uniform.type))
+			CHECK_UNIFORM(intV, 0);
+		else if (glu::isDataTypeBoolOrBVec(uniform.type))
+			CHECK_UNIFORM(boolV, false);
+		else if (glu::isDataTypeSampler(uniform.type))
+		{
+			if (unifValue.val.samplerV.unit != 0)
+			{
+				log << TestLog::Message << "// FAILURE: uniform " << uniform.name << " has non-zero initial value" << TestLog::EndMessage;
+				success = false;
+			}
+		}
+		else
+			DE_ASSERT(false);
+
+#undef CHECK_UNIFORM
+	}
+
+	return success;
+}
+
+void UniformCase::assignUniforms (const vector<BasicUniform>& basicUniforms, deUint32 programGL, Random& rnd)
+{
+	TestLog&				log				= m_testCtx.getLog();
+	const glu::DataType		boolApiType		= m_features & FEATURE_BOOLEANAPITYPE_INT	? glu::TYPE_INT
+											:											  glu::TYPE_FLOAT;
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform				= basicUniforms[unifNdx];
+		const bool				isArrayMember		= uniform.elemNdx >= 0;
+		const string			queryName			= m_features & FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX && uniform.elemNdx == 0 ? beforeLast(uniform.name, '[') : uniform.name;
+		const int				numValuesToAssign	= !isArrayMember									? 1
+													: m_features & FEATURE_ARRAYASSIGN_FULL				? (uniform.elemNdx == 0			? uniform.rootSize	: 0)
+													: m_features & FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO	? (uniform.elemNdx % 2 == 0		? 2					: 0)
+													: /* Default: assign array elements separately */	  1;
+
+		DE_ASSERT(numValuesToAssign >= 0);
+		DE_ASSERT(numValuesToAssign == 1 || isArrayMember);
+
+		if (numValuesToAssign == 0)
+		{
+			log << TestLog::Message << "// Uniform " << uniform.name << " is covered by another glUniform*v() call to the same array" << TestLog::EndMessage;
+			continue;
+		}
+
+		const int			location			= glGetUniformLocation(programGL, queryName.c_str());
+		const int			typeSize			= glu::getDataTypeScalarSize(uniform.type);
+		const bool			assignByValue		= m_features & FEATURE_UNIFORMFUNC_VALUE && !glu::isDataTypeMatrix(uniform.type) && numValuesToAssign == 1;
+		vector<VarValue>	valuesToAssign;
+
+		for (int i = 0; i < numValuesToAssign; i++)
+		{
+			const string	curName = isArrayMember ? beforeLast(uniform.rootName, '[') + "[" + de::toString(uniform.elemNdx+i) + "]" : uniform.name;
+			VarValue		unifValue;
+
+			if (isArrayMember)
+			{
+				const vector<BasicUniform>::const_iterator elemUnif = BasicUniform::findWithName(basicUniforms, curName.c_str());
+				if (elemUnif == basicUniforms.end())
+					continue;
+				unifValue = elemUnif->finalValue;
+			}
+			else
+				unifValue = uniform.finalValue;
+
+			const VarValue apiValue = glu::isDataTypeBoolOrBVec(unifValue.type)	? getRandomBoolRepresentation(unifValue, boolApiType, rnd)
+									: glu::isDataTypeSampler(unifValue.type)	? getSamplerUnitValue(unifValue)
+									: unifValue;
+
+			valuesToAssign.push_back(apiValue);
+
+			if (glu::isDataTypeBoolOrBVec(uniform.type))
+				log << TestLog::Message << "// Using type " << glu::getDataTypeName(boolApiType) << " to set boolean value " << apiVarValueStr(unifValue) << " for " << curName << TestLog::EndMessage;
+			else if (glu::isDataTypeSampler(uniform.type))
+				log << TestLog::Message << "// Texture for the sampler uniform " << curName << " will be filled with color " << apiVarValueStr(getSamplerFillValue(uniform.finalValue)) << TestLog::EndMessage;
+		}
+
+		DE_ASSERT(!valuesToAssign.empty());
+
+		if (glu::isDataTypeFloatOrVec(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+			{
+				const float* const ptr = &valuesToAssign[0].val.floatV[0];
+
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glUniform1f(location, ptr[0]));							break;
+					case 2: GLU_CHECK_CALL(glUniform2f(location, ptr[0], ptr[1]));					break;
+					case 3: GLU_CHECK_CALL(glUniform3f(location, ptr[0], ptr[1], ptr[2]));			break;
+					case 4: GLU_CHECK_CALL(glUniform4f(location, ptr[0], ptr[1], ptr[2], ptr[3]));	break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+			else
+			{
+				vector<float> buffer(valuesToAssign.size() * typeSize);
+				for (int i = 0; i < (int)buffer.size(); i++)
+					buffer[i] = valuesToAssign[i / typeSize].val.floatV[i % typeSize];
+
+				DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(buffer[0]));
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glUniform1fv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 2: GLU_CHECK_CALL(glUniform2fv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 3: GLU_CHECK_CALL(glUniform3fv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 4: GLU_CHECK_CALL(glUniform4fv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+		else if (glu::isDataTypeMatrix(valuesToAssign[0].type))
+		{
+			DE_ASSERT(!assignByValue);
+
+			vector<float> buffer(valuesToAssign.size() * typeSize);
+			for (int i = 0; i < (int)buffer.size(); i++)
+				buffer[i] = valuesToAssign[i / typeSize].val.floatV[i % typeSize];
+
+			switch (uniform.type)
+			{
+				case glu::TYPE_FLOAT_MAT2: GLU_CHECK_CALL(glUniformMatrix2fv(location, (GLsizei)valuesToAssign.size(), GL_FALSE, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT3: GLU_CHECK_CALL(glUniformMatrix3fv(location, (GLsizei)valuesToAssign.size(), GL_FALSE, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT4: GLU_CHECK_CALL(glUniformMatrix4fv(location, (GLsizei)valuesToAssign.size(), GL_FALSE, &buffer[0])); break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+		else if (glu::isDataTypeIntOrIVec(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+			{
+				const deInt32* const ptr = &valuesToAssign[0].val.intV[0];
+
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glUniform1i(location, ptr[0]));							break;
+					case 2: GLU_CHECK_CALL(glUniform2i(location, ptr[0], ptr[1]));					break;
+					case 3: GLU_CHECK_CALL(glUniform3i(location, ptr[0], ptr[1], ptr[2]));			break;
+					case 4: GLU_CHECK_CALL(glUniform4i(location, ptr[0], ptr[1], ptr[2], ptr[3]));	break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+			else
+			{
+				vector<deInt32> buffer(valuesToAssign.size() * typeSize);
+				for (int i = 0; i < (int)buffer.size(); i++)
+					buffer[i] = valuesToAssign[i / typeSize].val.intV[i % typeSize];
+
+				DE_STATIC_ASSERT(sizeof(GLint) == sizeof(buffer[0]));
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glUniform1iv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 2: GLU_CHECK_CALL(glUniform2iv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 3: GLU_CHECK_CALL(glUniform3iv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 4: GLU_CHECK_CALL(glUniform4iv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+		else if (glu::isDataTypeSampler(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+				GLU_CHECK_CALL(glUniform1i(location, uniform.finalValue.val.samplerV.unit));
+			else
+			{
+				const GLint unit = uniform.finalValue.val.samplerV.unit;
+				GLU_CHECK_CALL(glUniform1iv(location, (GLsizei)valuesToAssign.size(), &unit));
+			}
+		}
+		else
+			DE_ASSERT(false);
+	}
+}
+
+bool UniformCase::compareUniformValues (const vector<VarValue>& values, const vector<BasicUniform>& basicUniforms)
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		success		= true;
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform		= basicUniforms[unifNdx];
+		const VarValue&			unifValue	= values[unifNdx];
+
+		log << TestLog::Message << "// Checking uniform " << uniform.name << TestLog::EndMessage;
+
+		if (unifValue.type == glu::TYPE_INVALID) // This happens when glGetUniformLocation() returned -1.
+			continue;
+
+		if (!apiVarValueEquals(unifValue, uniform.finalValue))
+		{
+			log << TestLog::Message << "// FAILURE: value obtained with glGetUniform*() for uniform " << uniform.name << " differs from value set with glUniform*()" << TestLog::EndMessage;
+			success = false;
+		}
+	}
+
+	return success;
+}
+
+bool UniformCase::renderTest (const vector<BasicUniform>& basicUniforms, const ShaderProgram& program, Random& rnd)
+{
+	TestLog&					log				= m_testCtx.getLog();
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderTarget();
+	const int					viewportW		= de::min(renderTarget.getWidth(),	MAX_RENDER_WIDTH);
+	const int					viewportH		= de::min(renderTarget.getHeight(),	MAX_RENDER_HEIGHT);
+	const int					viewportX		= rnd.getInt(0, renderTarget.getWidth()		- viewportW);
+	const int					viewportY		= rnd.getInt(0, renderTarget.getHeight()	- viewportH);
+	tcu::Surface				renderedImg		(viewportW, viewportH);
+
+	// Assert that no two samplers of different types have the same texture unit - this is an error in GL.
+	for (int i = 0; i < (int)basicUniforms.size(); i++)
+	{
+		if (glu::isDataTypeSampler(basicUniforms[i].type))
+		{
+			for (int j = 0; j < i; j++)
+			{
+				if (glu::isDataTypeSampler(basicUniforms[j].type) && basicUniforms[i].type != basicUniforms[j].type)
+					DE_ASSERT(basicUniforms[i].finalValue.val.samplerV.unit != basicUniforms[j].finalValue.val.samplerV.unit);
+			}
+		}
+	}
+
+	for (int i = 0; i < (int)basicUniforms.size(); i++)
+	{
+		if (glu::isDataTypeSampler(basicUniforms[i].type) && std::find(m_filledTextureUnits.begin(), m_filledTextureUnits.end(), basicUniforms[i].finalValue.val.samplerV.unit) == m_filledTextureUnits.end())
+		{
+			log << TestLog::Message << "// Filling texture at unit " << apiVarValueStr(basicUniforms[i].finalValue) << " with color " << shaderVarValueStr(basicUniforms[i].finalValue) << TestLog::EndMessage;
+			setupTexture(basicUniforms[i].finalValue);
+		}
+	}
+
+	GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportW, viewportH));
+
+	{
+		static const float position[] =
+		{
+			-1.0f, -1.0f, 0.0f, 1.0f,
+			-1.0f, +1.0f, 0.0f, 1.0f,
+			+1.0f, -1.0f, 0.0f, 1.0f,
+			+1.0f, +1.0f, 0.0f, 1.0f
+		};
+		static const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+		const int posLoc = glGetAttribLocation(program.getProgram(), "a_position");
+
+		glEnableVertexAttribArray(posLoc);
+		glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &position[0]);
+
+		GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_SHORT, &indices[0]));
+	}
+
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedImg.getAccess());
+
+	int numFailedPixels = 0;
+	for (int y = 0; y < renderedImg.getHeight(); y++)
+	{
+		for (int x = 0; x < renderedImg.getWidth(); x++)
+		{
+			if (renderedImg.getPixel(x, y) != tcu::RGBA::white)
+				numFailedPixels += 1;
+		}
+	}
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Image("RenderedImage", "Rendered image", renderedImg);
+		log << TestLog::Message << "FAILURE: image comparison failed, got " << numFailedPixels << " non-white pixels" << TestLog::EndMessage;
+		return false;
+	}
+	else
+	{
+		log << TestLog::Message << "Success: got all-white pixels (all uniforms have correct values)" << TestLog::EndMessage;
+		return true;
+	}
+}
+
+UniformCase::IterateResult UniformCase::iterate (void)
+{
+	Random							rnd				(deStringHash(getName()) ^ (deUint32)m_context.getTestContext().getCommandLine().getBaseSeed());
+	TestLog&						log				= m_testCtx.getLog();
+	vector<BasicUniform>			basicUniforms;
+	vector<BasicUniformReportRef>	basicUniformReportsRef;
+
+	{
+		int samplerUnitCounter = 0;
+		for (int i = 0; i < (int)m_uniformCollection->getNumUniforms(); i++)
+			generateBasicUniforms(basicUniforms, basicUniformReportsRef, m_uniformCollection->getUniform(i).type, m_uniformCollection->getUniform(i).name.c_str(), true, samplerUnitCounter, rnd);
+	}
+
+	const string					vertexSource	= generateVertexSource(basicUniforms);
+	const string					fragmentSource	= generateFragmentSource(basicUniforms);
+	const ShaderProgram				program			(m_context.getRenderContext(), glu::makeVtxFragSources(vertexSource, fragmentSource));
+
+	log << program;
+
+	if (!program.isOk())
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		return STOP;
+	}
+
+	GLU_CHECK_CALL(glUseProgram(program.getProgram()));
+
+	const bool success = test(basicUniforms, basicUniformReportsRef, program, rnd);
+	m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							success ? "Passed"				: "Failed");
+
+	return STOP;
+}
+
+class UniformInfoQueryCase : public UniformCase
+{
+public:
+				UniformInfoQueryCase	(Context& context, const char* name, const char* description, CaseShaderType shaderType, const SharedPtr<const UniformCollection>& uniformCollection, deUint32 additionalFeatures = 0);
+	bool		test					(const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd);
+};
+
+UniformInfoQueryCase::UniformInfoQueryCase (Context& context, const char* const name, const char* const description, const CaseShaderType shaderType, const SharedPtr<const UniformCollection>& uniformCollection, const deUint32 additionalFeatures)
+	: UniformCase	(context, name, description, shaderType, uniformCollection, additionalFeatures)
+{
+}
+
+bool UniformInfoQueryCase::test (const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd)
+{
+	DE_UNREF(basicUniforms);
+	DE_UNREF(rnd);
+
+	const deUint32					programGL	= program.getProgram();
+	TestLog&						log			= m_testCtx.getLog();
+	vector<BasicUniformReportGL>	basicUniformReportsUniform;
+
+	const ScopedLogSection section(log, "InfoGetActiveUniform", "Uniform information queries with glGetActiveUniform()");
+	const bool success = getActiveUniforms(basicUniformReportsUniform, basicUniformReportsRef, programGL);
+
+	if (!success)
+		return false;
+
+	return true;
+}
+
+class UniformValueCase : public UniformCase
+{
+public:
+	enum ValueToCheck
+	{
+		VALUETOCHECK_INITIAL = 0,		//!< Verify the initial values of the uniforms (i.e. check that they're zero).
+		VALUETOCHECK_ASSIGNED,			//!< Assign values to uniforms with glUniform*(), and check those.
+
+		VALUETOCHECK_LAST
+	};
+	enum CheckMethod
+	{
+		CHECKMETHOD_GET_UNIFORM = 0,	//!< Check values with glGetUniform*().
+		CHECKMETHOD_RENDER,				//!< Check values by rendering with the value-checking shader.
+
+		CHECKMETHOD_LAST
+	};
+	enum AssignMethod
+	{
+		ASSIGNMETHOD_POINTER = 0,
+		ASSIGNMETHOD_VALUE,
+
+		ASSIGNMETHOD_LAST
+	};
+
+						UniformValueCase			(Context&									context,
+													 const char*								name,
+													 const char*								description,
+													 CaseShaderType								shaderType,
+													 const SharedPtr<const UniformCollection>&	uniformCollection,
+													 ValueToCheck								valueToCheck,
+													 CheckMethod								checkMethod,
+													 AssignMethod								assignMethod,
+													 deUint32									additionalFeatures = 0);
+
+	bool				test						(const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd);
+
+	static const char*	getValueToCheckName			(ValueToCheck valueToCheck);
+	static const char*	getValueToCheckDescription	(ValueToCheck valueToCheck);
+	static const char*	getCheckMethodName			(CheckMethod checkMethod);
+	static const char*	getCheckMethodDescription	(CheckMethod checkMethod);
+	static const char*	getAssignMethodName			(AssignMethod checkMethod);
+	static const char*	getAssignMethodDescription	(AssignMethod checkMethod);
+
+private:
+	const ValueToCheck	m_valueToCheck;
+	const CheckMethod	m_checkMethod;
+};
+
+const char* UniformValueCase::getValueToCheckName (const ValueToCheck valueToCheck)
+{
+	switch (valueToCheck)
+	{
+		case VALUETOCHECK_INITIAL:	return "initial";
+		case VALUETOCHECK_ASSIGNED:	return "assigned";
+		default: DE_ASSERT(false);	return DE_NULL;
+	}
+}
+
+const char* UniformValueCase::getValueToCheckDescription (const ValueToCheck valueToCheck)
+{
+	switch (valueToCheck)
+{
+		case VALUETOCHECK_INITIAL:	return "Check initial uniform values (zeros)";
+		case VALUETOCHECK_ASSIGNED:	return "Check assigned uniform values";
+		default: DE_ASSERT(false);	return DE_NULL;
+	}
+}
+
+const char* UniformValueCase::getCheckMethodName (const CheckMethod checkMethod)
+{
+	switch (checkMethod)
+	{
+		case CHECKMETHOD_GET_UNIFORM:	return "get_uniform";
+		case CHECKMETHOD_RENDER:		return "render";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+const char* UniformValueCase::getCheckMethodDescription (const CheckMethod checkMethod)
+{
+	switch (checkMethod)
+	{
+		case CHECKMETHOD_GET_UNIFORM:	return "Verify values with glGetUniform*()";
+		case CHECKMETHOD_RENDER:		return "Verify values by rendering";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+const char* UniformValueCase::getAssignMethodName (const AssignMethod assignMethod)
+{
+	switch (assignMethod)
+	{
+		case ASSIGNMETHOD_POINTER:		return "by_pointer";
+		case ASSIGNMETHOD_VALUE:		return "by_value";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+const char* UniformValueCase::getAssignMethodDescription (const AssignMethod assignMethod)
+{
+	switch (assignMethod)
+	{
+		case ASSIGNMETHOD_POINTER:		return "Assign values by-pointer";
+		case ASSIGNMETHOD_VALUE:		return "Assign values by-value";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+UniformValueCase::UniformValueCase (Context&									context,
+									const char* const							name,
+									const char* const							description,
+									const CaseShaderType						shaderType,
+									const SharedPtr<const UniformCollection>&	uniformCollection,
+									const ValueToCheck							valueToCheck,
+									const CheckMethod							checkMethod,
+									const AssignMethod							assignMethod,
+									const deUint32								additionalFeatures)
+	: UniformCase		(context, name, description, shaderType, uniformCollection,
+						 (valueToCheck == VALUETOCHECK_INITIAL ? FEATURE_UNIFORMVALUE_ZERO : 0) | (assignMethod == ASSIGNMETHOD_VALUE ? FEATURE_UNIFORMFUNC_VALUE : 0) | additionalFeatures)
+	, m_valueToCheck	(valueToCheck)
+	, m_checkMethod		(checkMethod)
+{
+	DE_ASSERT(!(assignMethod == ASSIGNMETHOD_LAST && valueToCheck == VALUETOCHECK_ASSIGNED));
+}
+
+bool UniformValueCase::test (const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd)
+{
+	DE_UNREF(basicUniformReportsRef);
+
+	const deUint32	programGL	= program.getProgram();
+	TestLog&		log			= m_testCtx.getLog();
+
+	if (m_valueToCheck == VALUETOCHECK_ASSIGNED)
+	{
+		const ScopedLogSection section(log, "UniformAssign", "Uniform value assignments");
+		assignUniforms(basicUniforms, programGL, rnd);
+	}
+	else
+		DE_ASSERT(m_valueToCheck == VALUETOCHECK_INITIAL);
+
+	if (m_checkMethod == CHECKMETHOD_GET_UNIFORM)
+	{
+		vector<VarValue> values;
+
+		{
+			const ScopedLogSection section(log, "GetUniforms", "Uniform value query");
+			const bool success = getUniforms(values, basicUniforms, program.getProgram());
+
+			if (!success)
+				return false;
+		}
+
+		if (m_valueToCheck == VALUETOCHECK_ASSIGNED)
+		{
+			const ScopedLogSection section(log, "ValueCheck", "Verify that the reported values match the assigned values");
+			const bool success = compareUniformValues(values, basicUniforms);
+
+			if (!success)
+				return false;
+		}
+		else
+		{
+			DE_ASSERT(m_valueToCheck == VALUETOCHECK_INITIAL);
+			const ScopedLogSection section(log, "ValueCheck", "Verify that the uniforms have correct initial values (zeros)");
+			const bool success = checkUniformDefaultValues(values, basicUniforms);
+
+			if (!success)
+				return false;
+		}
+	}
+	else
+	{
+		DE_ASSERT(m_checkMethod == CHECKMETHOD_RENDER);
+
+		const ScopedLogSection section(log, "RenderTest", "Render test");
+		const bool success = renderTest(basicUniforms, program, rnd);
+
+		if (!success)
+			return false;
+	}
+
+	return true;
+}
+
+class RandomUniformCase : public UniformCase
+{
+public:
+						RandomUniformCase		(Context& m_context, const char* name, const char* description, deUint32 seed);
+
+	bool				test					(const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd);
+};
+
+RandomUniformCase::RandomUniformCase (Context& context, const char* const name, const char* const description, const deUint32 seed)
+	: UniformCase (context, name, description, seed ^ (deUint32)context.getTestContext().getCommandLine().getBaseSeed())
+{
+}
+
+bool RandomUniformCase::test (const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd)
+{
+	// \note Different sampler types may not be bound to same unit when rendering.
+	const bool		renderingPossible						= (m_features & FEATURE_UNIFORMVALUE_ZERO) == 0 || !m_uniformCollection->containsSeveralSamplerTypes();
+
+	bool			performGetActiveUniforms			= rnd.getBool();
+	const bool		performGetUniforms					= rnd.getBool();
+	const bool		performCheckUniformDefaultValues	= performGetUniforms && rnd.getBool();
+	const bool		performAssignUniforms				= rnd.getBool();
+	const bool		performCompareUniformValues			= performGetUniforms && performAssignUniforms && rnd.getBool();
+	const bool		performRenderTest					= renderingPossible && performAssignUniforms && rnd.getBool();
+	const deUint32	programGL							= program.getProgram();
+	TestLog&		log									= m_testCtx.getLog();
+
+	if (!(performGetActiveUniforms || performGetUniforms || performCheckUniformDefaultValues || performAssignUniforms || performCompareUniformValues || performRenderTest))
+		performGetActiveUniforms = true; // Do something at least.
+
+#define PERFORM_AND_CHECK(CALL, SECTION_NAME, SECTION_DESCRIPTION)						\
+	do																					\
+	{																					\
+		const ScopedLogSection section(log, (SECTION_NAME), (SECTION_DESCRIPTION));		\
+		const bool success = (CALL);													\
+		if (!success)																	\
+			return false;																\
+	} while (false)
+
+	if (performGetActiveUniforms)
+	{
+		vector<BasicUniformReportGL> reportsUniform;
+		PERFORM_AND_CHECK(getActiveUniforms(reportsUniform, basicUniformReportsRef, programGL), "InfoGetActiveUniform", "Uniform information queries with glGetActiveUniform()");
+	}
+
+	{
+		vector<VarValue> uniformDefaultValues;
+
+		if (performGetUniforms)
+			PERFORM_AND_CHECK(getUniforms(uniformDefaultValues, basicUniforms, programGL), "GetUniformDefaults", "Uniform default value query");
+		if (performCheckUniformDefaultValues)
+			PERFORM_AND_CHECK(checkUniformDefaultValues(uniformDefaultValues, basicUniforms), "DefaultValueCheck", "Verify that the uniforms have correct initial values (zeros)");
+	}
+
+	{
+		vector<VarValue> uniformValues;
+
+		if (performAssignUniforms)
+		{
+			const ScopedLogSection section(log, "UniformAssign", "Uniform value assignments");
+			assignUniforms(basicUniforms, programGL, rnd);
+		}
+		if (performCompareUniformValues)
+		{
+			PERFORM_AND_CHECK(getUniforms(uniformValues, basicUniforms, programGL), "GetUniforms", "Uniform value query");
+			PERFORM_AND_CHECK(compareUniformValues(uniformValues, basicUniforms), "ValueCheck", "Verify that the reported values match the assigned values");
+		}
+	}
+
+	if (performRenderTest)
+		PERFORM_AND_CHECK(renderTest(basicUniforms, program, rnd), "RenderTest", "Render test");
+
+#undef PERFORM_AND_CHECK
+
+	return true;
+}
+
+UniformApiTests::UniformApiTests (Context& context)
+	: TestCaseGroup(context, "uniform_api", "Uniform API Tests")
+{
+}
+
+UniformApiTests::~UniformApiTests (void)
+{
+}
+
+namespace
+{
+
+// \note Although this is only used in UniformApiTest::init, it needs to be defined here as it's used as a template argument.
+struct UniformCollectionCase
+{
+	string								namePrefix;
+	SharedPtr<const UniformCollection>	uniformCollection;
+
+	UniformCollectionCase (const char* const name, const UniformCollection* uniformCollection_)
+		: namePrefix			(name ? name + string("_") : "")
+		, uniformCollection		(uniformCollection_)
+	{
+	}
+};
+
+} // anonymous
+
+void UniformApiTests::init (void)
+{
+	// Generate sets of UniformCollections that are used by several cases.
+
+	enum
+	{
+		UNIFORMCOLLECTIONS_BASIC = 0,
+		UNIFORMCOLLECTIONS_BASIC_ARRAY,
+		UNIFORMCOLLECTIONS_BASIC_STRUCT,
+		UNIFORMCOLLECTIONS_STRUCT_IN_ARRAY,
+		UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT,
+		UNIFORMCOLLECTIONS_NESTED_STRUCTS_ARRAYS,
+		UNIFORMCOLLECTIONS_MULTIPLE_BASIC,
+		UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY,
+		UNIFORMCOLLECTIONS_MULTIPLE_NESTED_STRUCTS_ARRAYS,
+
+		UNIFORMCOLLECTIONS_LAST
+	};
+
+	struct UniformCollectionGroup
+	{
+		string							name;
+		vector<UniformCollectionCase>	cases;
+	} defaultUniformCollections[UNIFORMCOLLECTIONS_LAST];
+
+	defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC].name							= "basic";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_ARRAY].name						= "basic_array";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_STRUCT].name						= "basic_struct";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_STRUCT_IN_ARRAY].name					= "struct_in_array";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT].name					= "array_in_struct";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_NESTED_STRUCTS_ARRAYS].name			= "nested_structs_arrays";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC].name					= "multiple_basic";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY].name				= "multiple_basic_array";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_NESTED_STRUCTS_ARRAYS].name	= "multiple_nested_structs_arrays";
+
+	for (int dataTypeNdx = 0; dataTypeNdx < DE_LENGTH_OF_ARRAY(s_testDataTypes); dataTypeNdx++)
+	{
+		const glu::DataType		dataType	= s_testDataTypes[dataTypeNdx];
+		const char* const		typeName	= glu::getDataTypeName(dataType);
+
+		defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC].cases.push_back(UniformCollectionCase(typeName, UniformCollection::basic(dataType)));
+
+		if (glu::isDataTypeScalar(dataType)													||
+			(glu::isDataTypeVector(dataType) && glu::getDataTypeScalarSize(dataType) == 4)	||
+			dataType == glu::TYPE_FLOAT_MAT4												||
+			dataType == glu::TYPE_SAMPLER_2D)
+			defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_ARRAY].cases.push_back(UniformCollectionCase(typeName, UniformCollection::basicArray(dataType)));
+
+		if (glu::isDataTypeScalar(dataType)		||
+			dataType == glu::TYPE_FLOAT_MAT4	||
+			dataType == glu::TYPE_SAMPLER_2D)
+		{
+			const glu::DataType		secondDataType	= glu::isDataTypeScalar(dataType)	? glu::getDataTypeVector(dataType, 4)
+													: dataType == glu::TYPE_FLOAT_MAT4	? glu::TYPE_FLOAT_MAT2
+													: dataType == glu::TYPE_SAMPLER_2D	? glu::TYPE_SAMPLER_CUBE
+													: glu::TYPE_LAST;
+			DE_ASSERT(secondDataType != glu::TYPE_LAST);
+			const char* const		secondTypeName	= glu::getDataTypeName(secondDataType);
+			const string			name			= string("") + typeName + "_" + secondTypeName;
+
+			defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_STRUCT].cases.push_back			(UniformCollectionCase(name.c_str(), UniformCollection::basicStruct(dataType, secondDataType, false)));
+			defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT].cases.push_back		(UniformCollectionCase(name.c_str(), UniformCollection::basicStruct(dataType, secondDataType, true)));
+			defaultUniformCollections[UNIFORMCOLLECTIONS_STRUCT_IN_ARRAY].cases.push_back		(UniformCollectionCase(name.c_str(), UniformCollection::structInArray(dataType, secondDataType, false)));
+			defaultUniformCollections[UNIFORMCOLLECTIONS_NESTED_STRUCTS_ARRAYS].cases.push_back	(UniformCollectionCase(name.c_str(), UniformCollection::nestedArraysStructs(dataType, secondDataType)));
+		}
+	}
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC].cases.push_back					(UniformCollectionCase(DE_NULL, UniformCollection::multipleBasic()));
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY].cases.push_back				(UniformCollectionCase(DE_NULL, UniformCollection::multipleBasicArray()));
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_NESTED_STRUCTS_ARRAYS].cases.push_back	(UniformCollectionCase(DE_NULL, UniformCollection::multipleNestedArraysStructs()));
+
+	// Info-query cases (check info returned by e.g. glGetActiveUniforms()).
+
+	{
+		TestCaseGroup* const infoQueryGroup = new TestCaseGroup(m_context, "info_query", "Test glGetActiveUniform()");
+		addChild(infoQueryGroup);
+
+		for (int collectionGroupNdx = 0; collectionGroupNdx < (int)UNIFORMCOLLECTIONS_LAST; collectionGroupNdx++)
+		{
+			const UniformCollectionGroup&	collectionGroup		= defaultUniformCollections[collectionGroupNdx];
+			TestCaseGroup* const			collectionTestGroup	= new TestCaseGroup(m_context, collectionGroup.name.c_str(), "");
+			infoQueryGroup->addChild(collectionTestGroup);
+
+			for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+			{
+				const UniformCollectionCase& collectionCase = collectionGroup.cases[collectionNdx];
+
+				for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+				{
+					const string								name				= collectionCase.namePrefix + getCaseShaderTypeName((CaseShaderType)shaderType);
+					const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+
+					collectionTestGroup->addChild(new UniformInfoQueryCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection));
+				}
+			}
+		}
+
+		// Info-querying cases when unused uniforms are present.
+
+		{
+			TestCaseGroup* const unusedUniformsGroup = new TestCaseGroup(m_context, "unused_uniforms", "Test with unused uniforms");
+			infoQueryGroup->addChild(unusedUniformsGroup);
+
+			const UniformCollectionGroup& collectionGroup = defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT];
+
+			for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+			{
+				const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+				const string								collName			= collectionCase.namePrefix;
+				const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+
+				for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+				{
+					const string name = collName + getCaseShaderTypeName((CaseShaderType)shaderType);
+					unusedUniformsGroup->addChild(new UniformInfoQueryCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																			UniformCase::FEATURE_UNIFORMUSAGE_EVERY_OTHER | UniformCase::FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX));
+				}
+			}
+		}
+	}
+
+	// Cases testing uniform values.
+
+	{
+		TestCaseGroup* const valueGroup = new TestCaseGroup(m_context, "value", "Uniform value tests");
+		addChild(valueGroup);
+
+		// Cases checking uniforms' initial values (all must be zeros), with glGetUniform*() or by rendering.
+
+		{
+			TestCaseGroup* const initialValuesGroup = new TestCaseGroup(m_context,
+																		UniformValueCase::getValueToCheckName(UniformValueCase::VALUETOCHECK_INITIAL),
+																		UniformValueCase::getValueToCheckDescription(UniformValueCase::VALUETOCHECK_INITIAL));
+			valueGroup->addChild(initialValuesGroup);
+
+			for (int checkMethodI = 0; checkMethodI < (int)UniformValueCase::CHECKMETHOD_LAST; checkMethodI++)
+			{
+				const UniformValueCase::CheckMethod		checkMethod			= (UniformValueCase::CheckMethod)checkMethodI;
+				TestCaseGroup* const					checkMethodGroup	= new TestCaseGroup(m_context, UniformValueCase::getCheckMethodName(checkMethod), UniformValueCase::getCheckMethodDescription(checkMethod));
+				initialValuesGroup->addChild(checkMethodGroup);
+
+				for (int collectionGroupNdx = 0; collectionGroupNdx < (int)UNIFORMCOLLECTIONS_LAST; collectionGroupNdx++)
+				{
+					const UniformCollectionGroup&	collectionGroup		= defaultUniformCollections[collectionGroupNdx];
+					TestCaseGroup* const			collectionTestGroup	= new TestCaseGroup(m_context, collectionGroup.name.c_str(), "");
+					checkMethodGroup->addChild(collectionTestGroup);
+
+					for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+					{
+						const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+						const string								collName			= collectionCase.namePrefix;
+						const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+						const bool									containsBooleans	= uniformCollection->containsMatchingBasicType(glu::isDataTypeBoolOrBVec);
+						const bool									varyBoolApiType		= checkMethod == UniformValueCase::CHECKMETHOD_GET_UNIFORM && containsBooleans &&
+																						  (collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC || collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY);
+						const int									numBoolVariations	= varyBoolApiType ? 2 : 1;
+
+						if (checkMethod == UniformValueCase::CHECKMETHOD_RENDER && uniformCollection->containsSeveralSamplerTypes())
+							continue; // \note Samplers' initial API values (i.e. their texture units) are 0, and no two samplers of different types shall have same unit when rendering.
+
+						for (int booleanTypeI = 0; booleanTypeI < numBoolVariations; booleanTypeI++)
+						{
+							const deUint32		booleanTypeFeat	= booleanTypeI == 1 ? UniformCase::FEATURE_BOOLEANAPITYPE_INT
+																: 0;
+							const char* const	booleanTypeName	= booleanTypeI == 1 ? "int"
+																: "float";
+							const string		nameWithApiType	= varyBoolApiType ? collName + "api_" + booleanTypeName + "_" : collName;
+
+							for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+							{
+								const string name = nameWithApiType + getCaseShaderTypeName((CaseShaderType)shaderType);
+								collectionTestGroup->addChild(new UniformValueCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																				   UniformValueCase::VALUETOCHECK_INITIAL, checkMethod, UniformValueCase::ASSIGNMETHOD_LAST, booleanTypeFeat));
+							}
+						}
+					}
+				}
+			}
+		}
+
+		// Cases that first assign values to each uniform, then check the values with glGetUniform*() or by rendering.
+
+		{
+			TestCaseGroup* const assignedValuesGroup = new TestCaseGroup(m_context,
+																		UniformValueCase::getValueToCheckName(UniformValueCase::VALUETOCHECK_ASSIGNED),
+																		UniformValueCase::getValueToCheckDescription(UniformValueCase::VALUETOCHECK_ASSIGNED));
+			valueGroup->addChild(assignedValuesGroup);
+
+			for (int assignMethodI = 0; assignMethodI < (int)UniformValueCase::ASSIGNMETHOD_LAST; assignMethodI++)
+			{
+				const UniformValueCase::AssignMethod	assignMethod		= (UniformValueCase::AssignMethod)assignMethodI;
+				TestCaseGroup* const					assignMethodGroup	= new TestCaseGroup(m_context, UniformValueCase::getAssignMethodName(assignMethod), UniformValueCase::getAssignMethodDescription(assignMethod));
+				assignedValuesGroup->addChild(assignMethodGroup);
+
+				for (int checkMethodI = 0; checkMethodI < (int)UniformValueCase::CHECKMETHOD_LAST; checkMethodI++)
+				{
+					const UniformValueCase::CheckMethod		checkMethod			= (UniformValueCase::CheckMethod)checkMethodI;
+					TestCaseGroup* const					checkMethodGroup	= new TestCaseGroup(m_context, UniformValueCase::getCheckMethodName(checkMethod), UniformValueCase::getCheckMethodDescription(checkMethod));
+					assignMethodGroup->addChild(checkMethodGroup);
+
+					for (int collectionGroupNdx = 0; collectionGroupNdx < (int)UNIFORMCOLLECTIONS_LAST; collectionGroupNdx++)
+					{
+						const int numArrayFirstElemNameCases = checkMethod == UniformValueCase::CHECKMETHOD_GET_UNIFORM && collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY ? 2 : 1;
+
+						for (int referToFirstArrayElemWithoutIndexI = 0; referToFirstArrayElemWithoutIndexI < numArrayFirstElemNameCases; referToFirstArrayElemWithoutIndexI++)
+						{
+							const UniformCollectionGroup&	collectionGroup			= defaultUniformCollections[collectionGroupNdx];
+							const string					collectionGroupName		= collectionGroup.name + (referToFirstArrayElemWithoutIndexI == 0 ? "" : "_first_elem_without_brackets");
+							TestCaseGroup* const			collectionTestGroup		= new TestCaseGroup(m_context, collectionGroupName.c_str(), "");
+							checkMethodGroup->addChild(collectionTestGroup);
+
+							for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+							{
+								const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+								const string								collName			= collectionCase.namePrefix;
+								const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+								const bool									containsBooleans	= uniformCollection->containsMatchingBasicType(glu::isDataTypeBoolOrBVec);
+								const bool									varyBoolApiType		= checkMethod == UniformValueCase::CHECKMETHOD_GET_UNIFORM && containsBooleans &&
+																								  (collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC || collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY);
+								const int									numBoolVariations	= varyBoolApiType ? 2 : 1;
+								const bool									containsMatrices	= uniformCollection->containsMatchingBasicType(glu::isDataTypeMatrix);
+
+								if (containsMatrices && assignMethod != UniformValueCase::ASSIGNMETHOD_POINTER)
+									continue;
+
+								for (int booleanTypeI = 0; booleanTypeI < numBoolVariations; booleanTypeI++)
+								{
+									const deUint32		booleanTypeFeat		= booleanTypeI == 1 ? UniformCase::FEATURE_BOOLEANAPITYPE_INT
+																			: 0;
+									const char* const	booleanTypeName		= booleanTypeI == 1 ? "int"
+																			: "float";
+									const string		nameWithBoolType	= varyBoolApiType ? collName + "api_" + booleanTypeName + "_" : collName;
+									const string		nameWithMatrixType	= nameWithBoolType;
+
+									for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+									{
+										const string	name							= nameWithMatrixType + getCaseShaderTypeName((CaseShaderType)shaderType);
+										const deUint32	arrayFirstElemNameNoIndexFeat	= referToFirstArrayElemWithoutIndexI == 0 ? 0 : UniformCase::FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX;
+
+										collectionTestGroup->addChild(new UniformValueCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																							UniformValueCase::VALUETOCHECK_ASSIGNED, checkMethod, assignMethod,
+																							booleanTypeFeat | arrayFirstElemNameNoIndexFeat));
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+
+			// Cases assign multiple basic-array elements with one glUniform*v() (i.e. the count parameter is bigger than 1).
+
+			{
+				static const struct
+				{
+					UniformCase::Feature	arrayAssignMode;
+					const char*				name;
+					const char*				description;
+				} arrayAssignGroups[] =
+				{
+					{ UniformCase::FEATURE_ARRAYASSIGN_FULL,			"basic_array_assign_full",		"Assign entire basic-type arrays per glUniform*v() call"			},
+					{ UniformCase::FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO,	"basic_array_assign_partial",	"Assign two elements of a basic-type array per glUniform*v() call"	}
+				};
+
+				for (int arrayAssignGroupNdx = 0; arrayAssignGroupNdx < DE_LENGTH_OF_ARRAY(arrayAssignGroups); arrayAssignGroupNdx++)
+				{
+					UniformCase::Feature	arrayAssignMode		= arrayAssignGroups[arrayAssignGroupNdx].arrayAssignMode;
+					const char* const		groupName			= arrayAssignGroups[arrayAssignGroupNdx].name;
+					const char* const		groupDesc			= arrayAssignGroups[arrayAssignGroupNdx].description;
+
+					TestCaseGroup* const curArrayAssignGroup = new TestCaseGroup(m_context, groupName, groupDesc);
+					assignedValuesGroup->addChild(curArrayAssignGroup);
+
+					static const int basicArrayCollectionGroups[] = { UNIFORMCOLLECTIONS_BASIC_ARRAY, UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT, UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY };
+
+					for (int collectionGroupNdx = 0; collectionGroupNdx < DE_LENGTH_OF_ARRAY(basicArrayCollectionGroups); collectionGroupNdx++)
+					{
+						const UniformCollectionGroup&	collectionGroup		= defaultUniformCollections[basicArrayCollectionGroups[collectionGroupNdx]];
+						TestCaseGroup* const			collectionTestGroup	= new TestCaseGroup(m_context, collectionGroup.name.c_str(), "");
+						curArrayAssignGroup->addChild(collectionTestGroup);
+
+						for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+						{
+							const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+							const string								collName			= collectionCase.namePrefix;
+							const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+
+							for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+							{
+								const string name = collName + getCaseShaderTypeName((CaseShaderType)shaderType);
+								collectionTestGroup->addChild(new UniformValueCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																				   UniformValueCase::VALUETOCHECK_ASSIGNED, UniformValueCase::CHECKMETHOD_GET_UNIFORM, UniformValueCase::ASSIGNMETHOD_POINTER,
+																				   arrayAssignMode));
+							}
+						}
+					}
+				}
+			}
+
+			// Value checking cases when unused uniforms are present.
+
+			{
+				TestCaseGroup* const unusedUniformsGroup = new TestCaseGroup(m_context, "unused_uniforms", "Test with unused uniforms");
+				assignedValuesGroup->addChild(unusedUniformsGroup);
+
+				const UniformCollectionGroup& collectionGroup = defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT];
+
+				for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+				{
+					const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+					const string								collName			= collectionCase.namePrefix;
+					const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+
+					for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+					{
+						const string name = collName + getCaseShaderTypeName((CaseShaderType)shaderType);
+						unusedUniformsGroup->addChild(new UniformValueCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																		   UniformValueCase::VALUETOCHECK_ASSIGNED, UniformValueCase::CHECKMETHOD_GET_UNIFORM, UniformValueCase::ASSIGNMETHOD_POINTER,
+																		   UniformCase::FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX | UniformCase::FEATURE_UNIFORMUSAGE_EVERY_OTHER));
+					}
+				}
+			}
+		}
+	}
+
+	// Random cases.
+
+	{
+		const int		numRandomCases		= 100;
+		TestCaseGroup*	const randomGroup	= new TestCaseGroup(m_context, "random", "Random cases");
+		addChild(randomGroup);
+
+		for (int ndx = 0; ndx < numRandomCases; ndx++)
+			randomGroup->addChild(new RandomUniformCase(m_context, de::toString(ndx).c_str(), "", (deUint32)ndx));
+	}
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fUniformApiTests.hpp b/modules/gles2/functional/es2fUniformApiTests.hpp
new file mode 100644
index 0000000..3200ab8
--- /dev/null
+++ b/modules/gles2/functional/es2fUniformApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FUNIFORMAPITESTS_HPP
+#define _ES2FUNIFORMAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Uniform API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class UniformApiTests : public TestCaseGroup
+{
+public:
+							UniformApiTests		(Context& context);
+							~UniformApiTests	(void);
+
+	void					init				(void);
+
+private:
+							UniformApiTests		(const UniformApiTests& other);
+	UniformApiTests&		operator=			(const UniformApiTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FUNIFORMAPITESTS_HPP
diff --git a/modules/gles2/functional/es2fVertexArrayTest.cpp b/modules/gles2/functional/es2fVertexArrayTest.cpp
new file mode 100644
index 0000000..d6b75ba
--- /dev/null
+++ b/modules/gles2/functional/es2fVertexArrayTest.cpp
@@ -0,0 +1,882 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array and buffer tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fVertexArrayTest.hpp"
+#include "glsVertexArrayTests.hpp"
+
+#include "glwEnums.hpp"
+
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+template<class T>
+static std::string typeToString (T t)
+{
+	std::stringstream strm;
+	strm << t;
+	return strm.str();
+}
+
+
+class SingleVertexArrayUsageTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayUsageTests		(Context& context);
+	virtual							~SingleVertexArrayUsageTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayUsageTests		(const SingleVertexArrayUsageTests& other);
+	SingleVertexArrayUsageTests&	operator=						(const SingleVertexArrayUsageTests& other);
+};
+
+SingleVertexArrayUsageTests::SingleVertexArrayUsageTests (Context& context)
+	: TestCaseGroup(context, "usages", "Single vertex atribute, usage")
+{
+}
+
+SingleVertexArrayUsageTests::~SingleVertexArrayUsageTests (void)
+{
+}
+
+void SingleVertexArrayUsageTests::init (void)
+{
+	// Test usage
+	Array::Usage		usages[]		= {Array::USAGE_STATIC_DRAW, Array::USAGE_STREAM_DRAW, Array::USAGE_DYNAMIC_DRAW};
+	int					counts[]		= {1, 256};
+	int					strides[]		= {0, -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_FIXED, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE};
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+		{
+			for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+			{
+				for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usages); usageNdx++)
+				{
+					const int	componentCount	= 2;
+					const int	stride			= (strides[strideNdx] < 0 ? Array::inputTypeSize(inputTypes[inputTypeNdx]) * componentCount : strides[strideNdx]);
+					const bool	aligned			= (stride % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0;
+					MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																	Array::OUTPUTTYPE_VEC2,
+																	Array::STORAGE_BUFFER,
+																	usages[usageNdx],
+																	componentCount,
+																	0,
+																	stride,
+																	false,
+																	GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																	GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+					MultiVertexArrayTest::Spec spec;
+					spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+					spec.drawCount	= counts[countNdx];
+					spec.first		= 0;
+					spec.arrays.push_back(arraySpec);
+
+					std::string name = spec.getName();
+
+					if (aligned)
+						addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayStrideTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayStrideTests	(Context& context);
+	virtual							~SingleVertexArrayStrideTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayStrideTests	(const SingleVertexArrayStrideTests& other);
+	SingleVertexArrayStrideTests&	operator=						(const SingleVertexArrayStrideTests& other);
+};
+
+SingleVertexArrayStrideTests::SingleVertexArrayStrideTests (Context& context)
+	: TestCaseGroup(context, "strides", "Single stride vertex atribute")
+{
+}
+
+SingleVertexArrayStrideTests::~SingleVertexArrayStrideTests (void)
+{
+}
+
+void SingleVertexArrayStrideTests::init (void)
+{
+	// Test strides with different input types, component counts and storage, Usage(?)
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE, /*Array::INPUTTYPE_UNSIGNED_SHORT, Array::INPUTTYPE_UNSIGNED_BYTE,*/ Array::INPUTTYPE_FIXED};
+	Array::Storage		storages[]		= {Array::STORAGE_USER, Array::STORAGE_BUFFER};
+	int					counts[]		= {1, 256};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int storageNdx = 0; storageNdx < DE_LENGTH_OF_ARRAY(storages); storageNdx++)
+		{
+			for (int componentCount = 2; componentCount < 5; componentCount++)
+			{
+				for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+				{
+					for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+					{
+						const int	stride			= (strides[strideNdx] < 0 ? Array::inputTypeSize(inputTypes[inputTypeNdx]) * componentCount : strides[strideNdx]);
+						const bool	bufferAligned	= (storages[storageNdx] == Array::STORAGE_BUFFER) && (stride % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0;
+
+						MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																		Array::OUTPUTTYPE_VEC4,
+																		storages[storageNdx],
+																		Array::USAGE_DYNAMIC_DRAW,
+																		componentCount,
+																		0,
+																		stride,
+																		false,
+																		GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																		GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+						MultiVertexArrayTest::Spec spec;
+						spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+						spec.drawCount	= counts[countNdx];
+						spec.first		= 0;
+						spec.arrays.push_back(arraySpec);
+
+						std::string name = spec.getName();
+						if (bufferAligned)
+							addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+					}
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayFirstTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayFirstTests	(Context& context);
+	virtual							~SingleVertexArrayFirstTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayFirstTests	(const SingleVertexArrayFirstTests& other);
+	SingleVertexArrayFirstTests&	operator=						(const SingleVertexArrayFirstTests& other);
+};
+
+SingleVertexArrayFirstTests::SingleVertexArrayFirstTests (Context& context)
+	: TestCaseGroup(context, "first", "Single vertex atribute different first values")
+{
+}
+
+SingleVertexArrayFirstTests::~SingleVertexArrayFirstTests (void)
+{
+}
+
+void SingleVertexArrayFirstTests::init (void)
+{
+	// Test strides with different input types, component counts and storage, Usage(?)
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_FIXED};
+	int					counts[]		= {5, 256};
+	int					firsts[]		= {6, 24};
+	int					offsets[]		= {1, 16, 17};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(offsets); offsetNdx++)
+		{
+			for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+			{
+				for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+				{
+					for (int firstNdx = 0; firstNdx < DE_LENGTH_OF_ARRAY(firsts); firstNdx++)
+					{
+						const int	stride	= (strides[strideNdx] < 0 ? Array::inputTypeSize(inputTypes[inputTypeNdx]) * 2 : strides[strideNdx]);
+						const bool	aligned	= ((stride % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0) && (offsets[offsetNdx] % Array::inputTypeSize(inputTypes[inputTypeNdx]) == 0);
+
+						MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																		Array::OUTPUTTYPE_VEC2,
+																		Array::STORAGE_BUFFER,
+																		Array::USAGE_DYNAMIC_DRAW,
+																		2,
+																		offsets[offsetNdx],
+																		stride,
+																		false,
+																		GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																		GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+						MultiVertexArrayTest::Spec spec;
+						spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+						spec.drawCount	= counts[countNdx];
+						spec.first		= firsts[firstNdx];
+						spec.arrays.push_back(arraySpec);
+
+						std::string name = Array::inputTypeToString(inputTypes[inputTypeNdx]) + "_first" + typeToString(firsts[firstNdx]) + "_offset" + typeToString(offsets[offsetNdx]) + "_stride" + typeToString(stride) + "_quads" + typeToString(counts[countNdx]);
+						if (aligned)
+							addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+					}
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayOffsetTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayOffsetTests	(Context& context);
+	virtual							~SingleVertexArrayOffsetTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayOffsetTests	(const SingleVertexArrayOffsetTests& other);
+	SingleVertexArrayOffsetTests&	operator=						(const SingleVertexArrayOffsetTests& other);
+};
+
+SingleVertexArrayOffsetTests::SingleVertexArrayOffsetTests (Context& context)
+	: TestCaseGroup(context, "offset", "Single vertex atribute offset element")
+{
+}
+
+SingleVertexArrayOffsetTests::~SingleVertexArrayOffsetTests (void)
+{
+}
+
+void SingleVertexArrayOffsetTests::init (void)
+{
+	// Test strides with different input types, component counts and storage, Usage(?)
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_FIXED};
+	int					counts[]		= {1, 256};
+	int					offsets[]		= {1, 4, 17, 32};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(offsets); offsetNdx++)
+		{
+			for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+			{
+				for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+				{
+					const int	stride	= (strides[strideNdx] < 0 ? Array::inputTypeSize(inputTypes[inputTypeNdx]) * 2 : strides[strideNdx]);
+					const bool	aligned	= ((stride % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0) && ((offsets[offsetNdx] % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0);
+
+					MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																	Array::OUTPUTTYPE_VEC2,
+																	Array::STORAGE_BUFFER,
+																	Array::USAGE_DYNAMIC_DRAW,
+																	2,
+																	offsets[offsetNdx],
+																	stride,
+																	false,
+																	GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																	GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+					MultiVertexArrayTest::Spec spec;
+					spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+					spec.drawCount	= counts[countNdx];
+					spec.first		= 0;
+					spec.arrays.push_back(arraySpec);
+
+					std::string name = spec.getName();
+					if (aligned)
+						addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayNormalizeTests : public TestCaseGroup
+{
+public:
+										SingleVertexArrayNormalizeTests		(Context& context);
+	virtual								~SingleVertexArrayNormalizeTests	(void);
+
+	virtual void						init								(void);
+
+private:
+										SingleVertexArrayNormalizeTests		(const SingleVertexArrayNormalizeTests& other);
+	SingleVertexArrayNormalizeTests&	operator=							(const SingleVertexArrayNormalizeTests& other);
+};
+
+SingleVertexArrayNormalizeTests::SingleVertexArrayNormalizeTests (Context& context)
+	: TestCaseGroup(context, "normalize", "Single normalize vertex atribute")
+{
+}
+
+SingleVertexArrayNormalizeTests::~SingleVertexArrayNormalizeTests (void)
+{
+}
+
+void SingleVertexArrayNormalizeTests::init (void)
+{
+	// Test normalization with different input types, component counts and storage
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_UNSIGNED_SHORT, Array::INPUTTYPE_UNSIGNED_BYTE, Array::INPUTTYPE_FIXED};
+	Array::Storage		storages[]		= {Array::STORAGE_USER};
+	int					counts[]		= {1, 256};
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int storageNdx = 0; storageNdx < DE_LENGTH_OF_ARRAY(storages); storageNdx++)
+		{
+			for (int componentCount = 2; componentCount < 5; componentCount++)
+			{
+				for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+				{
+					MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																	Array::OUTPUTTYPE_VEC4,
+																	storages[storageNdx],
+																	Array::USAGE_DYNAMIC_DRAW,
+																	componentCount,
+																	0,
+																	0,
+																	true,
+																	GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																	GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+					MultiVertexArrayTest::Spec spec;
+					spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+					spec.drawCount	= counts[countNdx];
+					spec.first		= 0;
+					spec.arrays.push_back(arraySpec);
+
+					std::string name = spec.getName();
+					addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayOutputTypeTests : public TestCaseGroup
+{
+public:
+											SingleVertexArrayOutputTypeTests	(Context& context);
+	virtual									~SingleVertexArrayOutputTypeTests	(void);
+
+	virtual void							init									(void);
+
+private:
+											SingleVertexArrayOutputTypeTests	(const SingleVertexArrayOutputTypeTests& other);
+	SingleVertexArrayOutputTypeTests&	operator=								(const SingleVertexArrayOutputTypeTests& other);
+};
+
+SingleVertexArrayOutputTypeTests::SingleVertexArrayOutputTypeTests (Context& context)
+	: TestCaseGroup(context, "output_types", "Single output type vertex atribute")
+{
+}
+
+SingleVertexArrayOutputTypeTests::~SingleVertexArrayOutputTypeTests (void)
+{
+}
+
+void SingleVertexArrayOutputTypeTests::init (void)
+{
+	// Test output types with different input types, component counts and storage, Usage?, Precision?, float?
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_UNSIGNED_SHORT, Array::INPUTTYPE_UNSIGNED_BYTE, Array::INPUTTYPE_FIXED};
+	Array::OutputType	outputTypes[]	= {Array::OUTPUTTYPE_VEC2, Array::OUTPUTTYPE_VEC3, Array::OUTPUTTYPE_VEC4};
+	Array::Storage		storages[]		= {Array::STORAGE_USER};
+	int					counts[]		= {1, 256};
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int outputTypeNdx = 0; outputTypeNdx < DE_LENGTH_OF_ARRAY(outputTypes); outputTypeNdx++)
+		{
+			for (int storageNdx = 0; storageNdx < DE_LENGTH_OF_ARRAY(storages); storageNdx++)
+			{
+				for (int componentCount = 2; componentCount < 5; componentCount++)
+				{
+					for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+					{
+						MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																		outputTypes[outputTypeNdx],
+																		storages[storageNdx],
+																		Array::USAGE_DYNAMIC_DRAW,
+																		componentCount,
+																		0,
+																		0,
+																		false,
+																		GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																		GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+						MultiVertexArrayTest::Spec spec;
+						spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+						spec.drawCount	= counts[countNdx];
+						spec.first		= 0;
+						spec.arrays.push_back(arraySpec);
+
+						std::string name = spec.getName();
+						addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+					}
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayTestGroup : public TestCaseGroup
+{
+public:
+									SingleVertexArrayTestGroup	(Context& context);
+	virtual							~SingleVertexArrayTestGroup	(void);
+
+	virtual void					init						(void);
+
+private:
+									SingleVertexArrayTestGroup	(const SingleVertexArrayTestGroup& other);
+	SingleVertexArrayTestGroup&		operator=					(const SingleVertexArrayTestGroup& other);
+};
+
+SingleVertexArrayTestGroup::SingleVertexArrayTestGroup (Context& context)
+	: TestCaseGroup(context, "single_attribute", "Single vertex atribute")
+{
+}
+
+SingleVertexArrayTestGroup::~SingleVertexArrayTestGroup (void)
+{
+}
+
+void SingleVertexArrayTestGroup::init (void)
+{
+	addChild(new SingleVertexArrayStrideTests(m_context));
+	addChild(new SingleVertexArrayNormalizeTests(m_context));
+	addChild(new SingleVertexArrayOutputTypeTests(m_context));
+	addChild(new SingleVertexArrayUsageTests(m_context));
+	addChild(new SingleVertexArrayOffsetTests(m_context));
+	addChild(new SingleVertexArrayFirstTests(m_context));
+}
+
+class MultiVertexArrayCountTests : public TestCaseGroup
+{
+public:
+									MultiVertexArrayCountTests	(Context& context);
+	virtual							~MultiVertexArrayCountTests	(void);
+
+	virtual void					init						(void);
+
+private:
+									MultiVertexArrayCountTests	(const MultiVertexArrayCountTests& other);
+	MultiVertexArrayCountTests&		operator=					(const MultiVertexArrayCountTests& other);
+
+	std::string						getTestName					(const MultiVertexArrayTest::Spec& spec);
+};
+
+MultiVertexArrayCountTests::MultiVertexArrayCountTests (Context& context)
+	: TestCaseGroup(context, "attribute_count", "Attribute counts")
+{
+}
+
+MultiVertexArrayCountTests::~MultiVertexArrayCountTests (void)
+{
+}
+
+std::string MultiVertexArrayCountTests::getTestName (const MultiVertexArrayTest::Spec& spec)
+{
+	std::stringstream name;
+	name
+		<< spec.arrays.size();
+
+	return name.str();
+}
+
+void MultiVertexArrayCountTests::init (void)
+{
+	// Test attribute counts
+	int arrayCounts[] = {2, 3, 4, 5, 6, 7, 8};
+
+	for (int arrayCountNdx = 0; arrayCountNdx < DE_LENGTH_OF_ARRAY(arrayCounts); arrayCountNdx++)
+	{
+		MultiVertexArrayTest::Spec spec;
+
+		spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+		spec.drawCount	= 256;
+		spec.first		= 0;
+
+		for (int arrayNdx = 0; arrayNdx < arrayCounts[arrayCountNdx]; arrayNdx++)
+		{
+			MultiVertexArrayTest::Spec::ArraySpec arraySpec(Array::INPUTTYPE_FLOAT,
+															Array::OUTPUTTYPE_VEC2,
+															Array::STORAGE_USER,
+															Array::USAGE_DYNAMIC_DRAW,
+															2,
+															0,
+															0,
+															false,
+															GLValue::getMinValue(Array::INPUTTYPE_FLOAT),
+															GLValue::getMaxValue(Array::INPUTTYPE_FLOAT));
+
+			spec.arrays.push_back(arraySpec);
+		}
+
+		std::string name = getTestName(spec);
+		std::string desc = getTestName(spec);
+
+		addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), desc.c_str()));
+	}
+}
+
+class MultiVertexArrayStorageTests : public TestCaseGroup
+{
+public:
+									MultiVertexArrayStorageTests	(Context& context);
+	virtual							~MultiVertexArrayStorageTests	(void);
+
+	virtual void					init								(void);
+
+private:
+									MultiVertexArrayStorageTests	(const MultiVertexArrayStorageTests& other);
+	MultiVertexArrayStorageTests&	operator=							(const MultiVertexArrayStorageTests& other);
+
+	void							addStorageCases 					(MultiVertexArrayTest::Spec spec, int depth);
+	std::string						getTestName							(const MultiVertexArrayTest::Spec& spec);
+};
+
+MultiVertexArrayStorageTests::MultiVertexArrayStorageTests (Context& context)
+	: TestCaseGroup(context, "storage", "Attribute storages")
+{
+}
+
+MultiVertexArrayStorageTests::~MultiVertexArrayStorageTests (void)
+{
+}
+
+std::string MultiVertexArrayStorageTests::getTestName (const MultiVertexArrayTest::Spec& spec)
+{
+	std::stringstream name;
+	name
+		<< spec.arrays.size();
+
+	for (int arrayNdx = 0; arrayNdx < (int)spec.arrays.size(); arrayNdx++)
+	{
+		name
+			<< "_"
+			<< Array::storageToString(spec.arrays[arrayNdx].storage);
+	}
+
+	return name.str();
+}
+
+void MultiVertexArrayStorageTests::addStorageCases (MultiVertexArrayTest::Spec spec, int depth)
+{
+	if (depth == 0)
+	{
+		// Skip trivial case, used elsewhere
+		bool ok = false;
+		for (int arrayNdx = 0; arrayNdx < (int)spec.arrays.size(); arrayNdx++)
+		{
+			if (spec.arrays[arrayNdx].storage != Array::STORAGE_USER)
+			{
+				ok = true;
+				break;
+			}
+		}
+
+		if (!ok)
+			return;
+
+		std::string name = getTestName(spec);
+		std::string desc = getTestName(spec);
+
+		addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), desc.c_str()));
+		return;
+	}
+
+	Array::Storage storages[] = {Array::STORAGE_USER, Array::STORAGE_BUFFER};
+	for (int storageNdx = 0; storageNdx < DE_LENGTH_OF_ARRAY(storages); storageNdx++)
+	{
+		MultiVertexArrayTest::Spec::ArraySpec arraySpec(Array::INPUTTYPE_FLOAT,
+														Array::OUTPUTTYPE_VEC2,
+														storages[storageNdx],
+														Array::USAGE_DYNAMIC_DRAW,
+														2,
+														0,
+														0,
+														false,
+														GLValue::getMinValue(Array::INPUTTYPE_FLOAT),
+														GLValue::getMaxValue(Array::INPUTTYPE_FLOAT));
+
+		MultiVertexArrayTest::Spec _spec = spec;
+		_spec.arrays.push_back(arraySpec);
+		addStorageCases(_spec, depth-1);
+	}
+}
+
+
+void MultiVertexArrayStorageTests::init (void)
+{
+	// Test different storages
+	int arrayCounts[] = {3};
+
+	MultiVertexArrayTest::Spec spec;
+
+	spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+	spec.drawCount	= 256;
+	spec.first		= 0;
+
+	for (int arrayCountNdx = 0; arrayCountNdx < DE_LENGTH_OF_ARRAY(arrayCounts); arrayCountNdx++)
+		addStorageCases(spec, arrayCounts[arrayCountNdx]);
+}
+
+class MultiVertexArrayStrideTests : public TestCaseGroup
+{
+public:
+									MultiVertexArrayStrideTests		(Context& context);
+	virtual							~MultiVertexArrayStrideTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									MultiVertexArrayStrideTests		(const MultiVertexArrayStrideTests& other);
+	MultiVertexArrayStrideTests&	operator=						(const MultiVertexArrayStrideTests& other);
+
+	void							addStrideCases 					(MultiVertexArrayTest::Spec spec, int depth);
+	std::string						getTestName						(const MultiVertexArrayTest::Spec& spec);
+};
+
+MultiVertexArrayStrideTests::MultiVertexArrayStrideTests (Context& context)
+	: TestCaseGroup(context, "stride", "Strides")
+{
+}
+
+MultiVertexArrayStrideTests::~MultiVertexArrayStrideTests (void)
+{
+}
+
+std::string MultiVertexArrayStrideTests::getTestName (const MultiVertexArrayTest::Spec& spec)
+{
+	std::stringstream name;
+
+	name
+		<< spec.arrays.size();
+
+	for (int arrayNdx = 0; arrayNdx < (int)spec.arrays.size(); arrayNdx++)
+	{
+		name
+			<< "_"
+			<< Array::inputTypeToString(spec.arrays[arrayNdx].inputType)
+			<< spec.arrays[arrayNdx].componentCount << "_"
+			<< spec.arrays[arrayNdx].stride;
+	}
+
+	return name.str();
+}
+
+void MultiVertexArrayStrideTests::init (void)
+{
+	// Test different strides, with multiple arrays, input types??
+	int arrayCounts[] = {3};
+
+	MultiVertexArrayTest::Spec spec;
+
+	spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+	spec.drawCount	= 256;
+	spec.first		= 0;
+
+	for (int arrayCountNdx = 0; arrayCountNdx < DE_LENGTH_OF_ARRAY(arrayCounts); arrayCountNdx++)
+		addStrideCases(spec, arrayCounts[arrayCountNdx]);
+}
+
+void MultiVertexArrayStrideTests::addStrideCases (MultiVertexArrayTest::Spec spec, int depth)
+{
+	if (depth == 0)
+	{
+		std::string name = getTestName(spec);
+		std::string desc = getTestName(spec);
+		addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), desc.c_str()));
+		return;
+	}
+
+	int strides[]	= {0, -1, 17, 32};
+
+	for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+	{
+		const int componentCount = 2;
+		MultiVertexArrayTest::Spec::ArraySpec arraySpec(Array::INPUTTYPE_FLOAT,
+														Array::OUTPUTTYPE_VEC2,
+														Array::STORAGE_USER,
+														Array::USAGE_DYNAMIC_DRAW,
+														componentCount,
+														0,
+														(strides[strideNdx] >= 0 ? strides[strideNdx] : componentCount * Array::inputTypeSize(Array::INPUTTYPE_FLOAT)),
+														false,
+														GLValue::getMinValue(Array::INPUTTYPE_FLOAT),
+														GLValue::getMaxValue(Array::INPUTTYPE_FLOAT));
+
+		MultiVertexArrayTest::Spec _spec = spec;
+		_spec.arrays.push_back(arraySpec);
+		addStrideCases(_spec, depth-1);
+	}
+}
+
+class MultiVertexArrayOutputTests : public TestCaseGroup
+{
+public:
+										MultiVertexArrayOutputTests		(Context& context);
+	virtual								~MultiVertexArrayOutputTests	(void);
+
+	virtual void						init								(void);
+
+private:
+										MultiVertexArrayOutputTests		(const MultiVertexArrayOutputTests& other);
+	MultiVertexArrayOutputTests&	operator=							(const MultiVertexArrayOutputTests& other);
+
+	void								addInputTypeCases 					(MultiVertexArrayTest::Spec spec, int depth);
+	std::string							getTestName							(const MultiVertexArrayTest::Spec& spec);
+};
+
+MultiVertexArrayOutputTests::MultiVertexArrayOutputTests (Context& context)
+	: TestCaseGroup(context, "input_types", "input types")
+{
+}
+
+MultiVertexArrayOutputTests::~MultiVertexArrayOutputTests (void)
+{
+}
+
+std::string MultiVertexArrayOutputTests::getTestName (const MultiVertexArrayTest::Spec& spec)
+{
+	std::stringstream name;
+
+	name
+		<< spec.arrays.size();
+
+	for (int arrayNdx = 0; arrayNdx < (int)spec.arrays.size(); arrayNdx++)
+	{
+		name
+			<< "_"
+			<< Array::inputTypeToString(spec.arrays[arrayNdx].inputType)
+			<< spec.arrays[arrayNdx].componentCount << "_"
+			<< Array::outputTypeToString(spec.arrays[arrayNdx].outputType);
+	}
+
+	return name.str();
+}
+
+void MultiVertexArrayOutputTests::init (void)
+{
+	// Test different input types, with multiple arrays
+	int arrayCounts[] = {3};
+
+	MultiVertexArrayTest::Spec spec;
+
+	spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+	spec.drawCount	= 256;
+	spec.first		= 0;
+
+	for (int arrayCountNdx = 0; arrayCountNdx < DE_LENGTH_OF_ARRAY(arrayCounts); arrayCountNdx++)
+		addInputTypeCases(spec, arrayCounts[arrayCountNdx]);
+}
+
+void MultiVertexArrayOutputTests::addInputTypeCases (MultiVertexArrayTest::Spec spec, int depth)
+{
+	if (depth == 0)
+	{
+		std::string name = getTestName(spec);
+		std::string desc = getTestName(spec);
+		addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), desc.c_str()));
+		return;
+	}
+
+	Array::InputType inputTypes[] = {Array::INPUTTYPE_FIXED, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_UNSIGNED_BYTE, Array::INPUTTYPE_UNSIGNED_SHORT};
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+														Array::OUTPUTTYPE_VEC2,
+														Array::STORAGE_USER,
+														Array::USAGE_DYNAMIC_DRAW,
+														2,
+														0,
+														0,
+														false,
+														GLValue::getMinValue(inputTypes[inputTypeNdx]),
+														GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+		MultiVertexArrayTest::Spec _spec = spec;
+		_spec.arrays.push_back(arraySpec);
+		addInputTypeCases(_spec, depth-1);
+	}
+}
+
+class MultiVertexArrayTestGroup : public TestCaseGroup
+{
+public:
+									MultiVertexArrayTestGroup	(Context& context);
+	virtual							~MultiVertexArrayTestGroup	(void);
+
+	virtual void					init						(void);
+
+private:
+									MultiVertexArrayTestGroup	(const MultiVertexArrayTestGroup& other);
+	MultiVertexArrayTestGroup&		operator=					(const MultiVertexArrayTestGroup& other);
+};
+
+MultiVertexArrayTestGroup::MultiVertexArrayTestGroup (Context& context)
+	: TestCaseGroup(context, "multiple_attributes", "Multiple vertex atributes")
+{
+}
+
+MultiVertexArrayTestGroup::~MultiVertexArrayTestGroup (void)
+{
+}
+
+void MultiVertexArrayTestGroup::init (void)
+{
+	addChild(new MultiVertexArrayCountTests(m_context));
+	addChild(new MultiVertexArrayStorageTests(m_context));
+	addChild(new MultiVertexArrayStrideTests(m_context));
+	addChild(new MultiVertexArrayOutputTests(m_context));
+}
+
+VertexArrayTestGroup::VertexArrayTestGroup (Context& context)
+	: TestCaseGroup(context, "vertex_arrays", "Vertex array and array tests")
+{
+}
+
+VertexArrayTestGroup::~VertexArrayTestGroup (void)
+{
+}
+
+void VertexArrayTestGroup::init (void)
+{
+	addChild(new SingleVertexArrayTestGroup(m_context));
+	addChild(new MultiVertexArrayTestGroup(m_context));
+}
+
+} // Functional
+} // gles2
+} // deqp
+
diff --git a/modules/gles2/functional/es2fVertexArrayTest.hpp b/modules/gles2/functional/es2fVertexArrayTest.hpp
new file mode 100644
index 0000000..53bccfe
--- /dev/null
+++ b/modules/gles2/functional/es2fVertexArrayTest.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES2FVERTEXARRAYTEST_HPP
+#define _ES2FVERTEXARRAYTEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array and buffer tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class VertexArrayTestGroup : public TestCaseGroup
+{
+public:
+							VertexArrayTestGroup	(Context& context);
+	virtual					~VertexArrayTestGroup	(void);
+
+	virtual void			init					(void);
+
+private:
+							VertexArrayTestGroup	(const VertexArrayTestGroup& other);
+	VertexArrayTestGroup&	operator=				(const VertexArrayTestGroup& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FVERTEXARRAYTEST_HPP
diff --git a/modules/gles2/functional/es2fVertexTextureTests.cpp b/modules/gles2/functional/es2fVertexTextureTests.cpp
new file mode 100644
index 0000000..f86afad
--- /dev/null
+++ b/modules/gles2/functional/es2fVertexTextureTests.cpp
@@ -0,0 +1,1149 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex texture tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2fVertexTextureTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+
+#include <string>
+#include <vector>
+
+#include <limits>
+
+#include "glw.h"
+
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::Mat3;
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+
+using namespace gls::TextureTestUtil;
+
+using gls::TextureTestUtil::TEXTURETYPE_2D;
+using gls::TextureTestUtil::TEXTURETYPE_CUBE;
+
+namespace gles2
+{
+namespace Functional
+{
+
+// The 2D case draws four images.
+static const int MAX_2D_RENDER_WIDTH		= 128*2;
+static const int MAX_2D_RENDER_HEIGHT		= 128*2;
+
+// The cube map case draws four 3-by-2 image groups.
+static const int MAX_CUBE_RENDER_WIDTH		= 28*2*3;
+static const int MAX_CUBE_RENDER_HEIGHT		= 28*2*2;
+
+static const int GRID_SIZE_2D				= 127;
+static const int GRID_SIZE_CUBE				= 63;
+
+// Helpers for making texture coordinates "safe", i.e. move them further from coordinate bounary.
+
+// Moves x towards the closest K+targetFraction, where K is an integer.
+// E.g. moveTowardsFraction(x, 0.5f) moves x away from integer boundaries.
+static inline float moveTowardsFraction (float x, float targetFraction)
+{
+	const float strictness = 0.5f;
+	DE_ASSERT(0.0f < strictness && strictness <= 1.0f);
+	DE_ASSERT(de::inBounds(targetFraction, 0.0f, 1.0f));
+	const float y = x + 0.5f - targetFraction;
+	return deFloatFloor(y) + deFloatFrac(y)*(1.0f-strictness) + strictness*0.5f - 0.5f + targetFraction;
+}
+
+static inline float safeCoord (float raw, int scale, float fraction)
+{
+	const float scaleFloat = (float)scale;
+	return moveTowardsFraction(raw*scaleFloat, fraction) / scaleFloat;
+}
+
+template <int Size>
+static inline tcu::Vector<float, Size> safeCoords (const tcu::Vector<float, Size>& raw, const tcu::Vector<int, Size>& scale, const tcu::Vector<float, Size>& fraction)
+{
+	tcu::Vector<float, Size> result;
+	for (int i = 0; i < Size; i++)
+		result[i] = safeCoord(raw[i], scale[i], fraction[i]);
+	return result;
+}
+
+static inline Vec2 safe2DTexCoords (const Vec2& raw, const IVec2& textureSize)
+{
+	return safeCoords(raw, textureSize, Vec2(0.5f));
+}
+
+namespace
+{
+
+struct Rect
+{
+			Rect	(int x_, int y_, int w_, int h_) : x(x_), y(y_), w(w_), h(h_) {}
+	IVec2	pos		(void) const { return IVec2(x, y); }
+	IVec2	size	(void) const { return IVec2(w, h); }
+
+	int		x;
+	int		y;
+	int		w;
+	int		h;
+};
+
+template <TextureType> struct TexTypeTcuClass;
+template <> struct TexTypeTcuClass<TEXTURETYPE_2D>			{ typedef tcu::Texture2D		t; };
+template <> struct TexTypeTcuClass<TEXTURETYPE_CUBE>		{ typedef tcu::TextureCube		t; };
+
+template <TextureType> struct TexTypeSizeDims;
+template <> struct TexTypeSizeDims<TEXTURETYPE_2D>			{ enum { V = 2 }; };
+template <> struct TexTypeSizeDims<TEXTURETYPE_CUBE>		{ enum { V = 2 }; };
+
+template <TextureType> struct TexTypeCoordDims;
+template <> struct TexTypeCoordDims<TEXTURETYPE_2D>			{ enum { V = 2 }; };
+template <> struct TexTypeCoordDims<TEXTURETYPE_CUBE>		{ enum { V = 3 }; };
+
+template <TextureType TexType> struct TexTypeSizeIVec		{ typedef tcu::Vector<int,		TexTypeSizeDims<TexType>::V>	t; };
+template <TextureType TexType> struct TexTypeCoordVec		{ typedef tcu::Vector<float,	TexTypeCoordDims<TexType>::V>	t; };
+
+template <TextureType> struct TexTypeCoordParams;
+
+template <> struct
+TexTypeCoordParams<TEXTURETYPE_2D>
+{
+	Vec2 scale;
+	Vec2 bias;
+
+	TexTypeCoordParams (const Vec2& scale_, const Vec2& bias_) : scale(scale_), bias(bias_) {}
+};
+
+template <> struct
+TexTypeCoordParams<TEXTURETYPE_CUBE>
+{
+	Vec2			scale;
+	Vec2			bias;
+	tcu::CubeFace	face;
+
+	TexTypeCoordParams (const Vec2& scale_, const Vec2& bias_, tcu::CubeFace face_) : scale(scale_), bias(bias_), face(face_) {}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Quad grid class containing position and texture coordinate data.
+ *
+ * A quad grid of size S means a grid consisting of S*S quads (S rows and
+ * S columns). The quads are rectangles with main axis aligned sides, and
+ * each consists of two triangles. Note that although there are only
+ * (S+1)*(S+1) distinct vertex positions, there are S*S*4 distinct vertices
+ * because we want texture coordinates to be constant across the vertices
+ * of a quad (to avoid interpolation issues), and thus each quad needs its
+ * own 4 vertices.
+ *
+ * Pointers returned by get*Ptr() are suitable for gl calls such as
+ * glVertexAttribPointer() (for position and tex coord) or glDrawElements()
+ * (for indices).
+ *//*--------------------------------------------------------------------*/
+template <TextureType TexType>
+class PosTexCoordQuadGrid
+{
+private:
+	enum { TEX_COORD_DIMS = TexTypeCoordDims <TexType>::V };
+	typedef typename TexTypeCoordVec<TexType>::t	TexCoordVec;
+	typedef typename TexTypeSizeIVec<TexType>::t	TexSizeIVec;
+	typedef TexTypeCoordParams<TexType>				TexCoordParams;
+
+public:
+							PosTexCoordQuadGrid		(int gridSize, const IVec2& renderSize, const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords);
+
+	int						getSize					(void) const { return m_gridSize; }
+	Vec4					getQuadLDRU				(int col, int row) const; //!< Vec4(leftX, downY, rightX, upY)
+	const TexCoordVec&		getQuadTexCoord			(int col, int row) const;
+
+	int						getNumIndices			(void) const { return m_gridSize*m_gridSize*3*2; }
+	const float*			getPositionPtr			(void) const { DE_STATIC_ASSERT(sizeof(Vec2) == 2*sizeof(float)); return (float*)&m_positions[0]; }
+	const float*			getTexCoordPtr			(void) const { DE_STATIC_ASSERT(sizeof(TexCoordVec) == TEX_COORD_DIMS*(int)sizeof(float)); return (float*)&m_texCoords[0]; }
+	const deUint16*			getIndexPtr				(void) const { return &m_indices[0]; }
+
+private:
+	void					initializeTexCoords		(const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords);
+
+	const int				m_gridSize;
+	vector<Vec2>			m_positions;
+	vector<TexCoordVec>		m_texCoords;
+	vector<deUint16>		m_indices;
+};
+
+template <TextureType TexType>
+Vec4 PosTexCoordQuadGrid<TexType>::getQuadLDRU (int col, int row) const
+{
+	int ndx00 = (row*m_gridSize + col) * 4;
+	int ndx11 = ndx00 + 3;
+
+	return Vec4(m_positions[ndx00].x(),
+				m_positions[ndx00].y(),
+				m_positions[ndx11].x(),
+				m_positions[ndx11].y());
+}
+
+template <TextureType TexType>
+const typename TexTypeCoordVec<TexType>::t& PosTexCoordQuadGrid<TexType>::getQuadTexCoord (int col, int row) const
+{
+	return m_texCoords[(row*m_gridSize + col) * 4];
+}
+
+template <TextureType TexType>
+PosTexCoordQuadGrid<TexType>::PosTexCoordQuadGrid (int gridSize, const IVec2& renderSize, const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
+	: m_gridSize(gridSize)
+{
+	DE_ASSERT(m_gridSize > 0 && m_gridSize*m_gridSize <= (int)std::numeric_limits<deUint16>::max() + 1);
+
+	const float gridSizeFloat = (float)m_gridSize;
+
+	m_positions.reserve(m_gridSize*m_gridSize*4);
+	m_indices.reserve(m_gridSize*m_gridSize*3*2);
+
+	for (int y = 0; y < m_gridSize; y++)
+	for (int x = 0; x < m_gridSize; x++)
+	{
+		float fx0 = (float)(x+0) / gridSizeFloat;
+		float fx1 = (float)(x+1) / gridSizeFloat;
+		float fy0 = (float)(y+0) / gridSizeFloat;
+		float fy1 = (float)(y+1) / gridSizeFloat;
+
+		Vec2 quadVertices[4] = { Vec2(fx0, fy0), Vec2(fx1, fy0), Vec2(fx0, fy1), Vec2(fx1, fy1) };
+
+		int firstNdx = (int)m_positions.size();
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(quadVertices); i++)
+			m_positions.push_back(safeCoords(quadVertices[i], renderSize, Vec2(0.0f)) * 2.0f - 1.0f);
+
+		m_indices.push_back(firstNdx + 0);
+		m_indices.push_back(firstNdx + 1);
+		m_indices.push_back(firstNdx + 2);
+
+		m_indices.push_back(firstNdx + 1);
+		m_indices.push_back(firstNdx + 3);
+		m_indices.push_back(firstNdx + 2);
+	}
+
+	m_texCoords.reserve(m_gridSize*m_gridSize*4);
+	initializeTexCoords(textureSize, texCoordParams, useSafeTexCoords);
+
+	DE_ASSERT((int)m_positions.size() == m_gridSize*m_gridSize*4);
+	DE_ASSERT((int)m_indices.size() == m_gridSize*m_gridSize*3*2);
+	DE_ASSERT((int)m_texCoords.size() == m_gridSize*m_gridSize*4);
+}
+
+template <>
+void PosTexCoordQuadGrid<TEXTURETYPE_2D>::initializeTexCoords (const IVec2& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
+{
+	DE_ASSERT(m_texCoords.empty());
+
+	const float gridSizeFloat = (float)m_gridSize;
+
+	for (int y = 0; y < m_gridSize; y++)
+	for (int x = 0; x < m_gridSize; x++)
+	{
+		Vec2 rawCoord = Vec2((float)x / gridSizeFloat, (float)y / gridSizeFloat) * texCoordParams.scale + texCoordParams.bias;
+
+		for (int i = 0; i < 4; i++)
+			m_texCoords.push_back(useSafeTexCoords ? safe2DTexCoords(rawCoord, textureSize) : rawCoord);
+	}
+}
+
+template <>
+void PosTexCoordQuadGrid<TEXTURETYPE_CUBE>::initializeTexCoords (const IVec2& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
+{
+	DE_ASSERT(m_texCoords.empty());
+
+	const float		gridSizeFloat	= (float)m_gridSize;
+	vector<float>	texBoundaries;
+	computeQuadTexCoordCube(texBoundaries, texCoordParams.face);
+	const Vec3		coordA			= Vec3(texBoundaries[0], texBoundaries[1], texBoundaries[2]);
+	const Vec3		coordB			= Vec3(texBoundaries[3], texBoundaries[4], texBoundaries[5]);
+	const Vec3		coordC			= Vec3(texBoundaries[6], texBoundaries[7], texBoundaries[8]);
+	const Vec3		coordAB			= coordB - coordA;
+	const Vec3		coordAC			= coordC - coordA;
+
+	for (int y = 0; y < m_gridSize; y++)
+	for (int x = 0; x < m_gridSize; x++)
+	{
+		const Vec2 rawFaceCoord		= texCoordParams.scale * Vec2((float)x / gridSizeFloat, (float)y / gridSizeFloat) + texCoordParams.bias;
+		const Vec2 safeFaceCoord	= useSafeTexCoords ? safe2DTexCoords(rawFaceCoord, textureSize) : rawFaceCoord;
+		const Vec3 texCoord			= coordA + coordAC*safeFaceCoord.x() + coordAB*safeFaceCoord.y();
+
+		for (int i = 0; i < 4; i++)
+			m_texCoords.push_back(texCoord);
+	}
+}
+
+} // anonymous
+
+static inline bool isLevelNearest (deUint32 filter)
+{
+	return filter == GL_NEAREST || filter == GL_NEAREST_MIPMAP_NEAREST || filter == GL_NEAREST_MIPMAP_LINEAR;
+}
+
+static inline IVec2 getTextureSize (const glu::Texture2D& tex)
+{
+	const tcu::Texture2D& ref = tex.getRefTexture();
+	return IVec2(ref.getWidth(), ref.getHeight());
+}
+
+static inline IVec2 getTextureSize (const glu::TextureCube& tex)
+{
+	const tcu::TextureCube& ref = tex.getRefTexture();
+	return IVec2(ref.getSize(), ref.getSize());
+}
+
+template <TextureType TexType>
+static void setPixelColors (const vector<Vec4>& quadColors, const Rect& region, const PosTexCoordQuadGrid<TexType>& grid, tcu::Surface& dst)
+{
+	const int gridSize = grid.getSize();
+
+	for (int y = 0; y < gridSize; y++)
+	for (int x = 0; x < gridSize; x++)
+	{
+		const Vec4	color	= quadColors[y*gridSize + x];
+		const Vec4	ldru	= grid.getQuadLDRU(x, y) * 0.5f + 0.5f; // [-1, 1] -> [0, 1]
+		const int	ix0		= deCeilFloatToInt32(ldru.x() * (float)region.w - 0.5f);
+		const int	ix1		= deCeilFloatToInt32(ldru.z() * (float)region.w - 0.5f);
+		const int	iy0		= deCeilFloatToInt32(ldru.y() * (float)region.h - 0.5f);
+		const int	iy1		= deCeilFloatToInt32(ldru.w() * (float)region.h - 0.5f);
+
+		for (int iy = iy0; iy < iy1; iy++)
+		for (int ix = ix0; ix < ix1; ix++)
+		{
+			DE_ASSERT(deInBounds32(ix + region.x, 0, dst.getWidth()));
+			DE_ASSERT(deInBounds32(iy + region.y, 0, dst.getHeight()));
+
+			dst.setPixel(ix + region.x, iy + region.y, toRGBA(color));
+		}
+	}
+}
+
+static inline Vec4 sample (const tcu::Texture2D&		tex, const Vec2& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), lod); }
+static inline Vec4 sample (const tcu::TextureCube&		tex, const Vec3& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), coord.z(), lod); }
+
+template <TextureType TexType>
+void computeReference (const typename TexTypeTcuClass<TexType>::t& texture, float lod, const tcu::Sampler& sampler, const PosTexCoordQuadGrid<TexType>& grid, tcu::Surface& dst, const Rect& dstRegion)
+{
+	const int		gridSize	= grid.getSize();
+	vector<Vec4>	quadColors	(gridSize*gridSize);
+
+	for (int y = 0; y < gridSize; y++)
+	for (int x = 0; x < gridSize; x++)
+	{
+		const int										ndx		= y*gridSize + x;
+		const typename TexTypeCoordVec<TexType>::t&		coord	= grid.getQuadTexCoord(x, y);
+
+		quadColors[ndx] = sample(texture, coord, lod, sampler);
+	}
+
+	setPixelColors(quadColors, dstRegion, grid, dst);
+}
+
+static bool compareImages (const glu::RenderContext& renderCtx, tcu::TestLog& log, const tcu::Surface& ref, const tcu::Surface& res)
+{
+	DE_ASSERT(renderCtx.getRenderTarget().getNumSamples() == 0);
+
+	const tcu::RGBA threshold = renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(15,15,15,15);
+	return tcu::pixelThresholdCompare(log, "Result", "Image compare result", ref, res, threshold, tcu::COMPARE_LOG_RESULT);
+}
+
+class Vertex2DTextureCase : public TestCase
+{
+public:
+								Vertex2DTextureCase		(Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT);
+								~Vertex2DTextureCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+	typedef PosTexCoordQuadGrid<TEXTURETYPE_2D> Grid;
+
+								Vertex2DTextureCase		(const Vertex2DTextureCase& other);
+	Vertex2DTextureCase&		operator=				(const Vertex2DTextureCase& other);
+
+	float						calculateLod			(const Vec2& texScale, const Vec2& dstSize, int textureNdx) const;
+	void						setupShaderInputs		(int textureNdx, float lod, const Grid& grid) const;
+	void						renderCell				(int textureNdx, float lod, const Grid& grid) const;
+	void						computeReferenceCell	(int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;
+
+	const deUint32				m_minFilter;
+	const deUint32				m_magFilter;
+	const deUint32				m_wrapS;
+	const deUint32				m_wrapT;
+
+	const glu::ShaderProgram*	m_program;
+	glu::Texture2D*				m_textures[2];	// 2 textures, a gradient texture and a grid texture.
+};
+
+Vertex2DTextureCase::Vertex2DTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT)
+	: TestCase				(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_program				(DE_NULL)
+{
+	m_textures[0] = DE_NULL;
+	m_textures[1] = DE_NULL;
+}
+
+Vertex2DTextureCase::~Vertex2DTextureCase(void)
+{
+	Vertex2DTextureCase::deinit();
+}
+
+void Vertex2DTextureCase::init (void)
+{
+	const char* const vertexShader =
+		"attribute highp vec2 a_position;\n"
+		"attribute highp vec2 a_texCoord;\n"
+		"uniform highp sampler2D u_texture;\n"
+		"uniform highp float u_lod;\n"
+		"varying mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		"	v_color = texture2DLod(u_texture, a_texCoord, u_lod);\n"
+		"}\n";
+
+	const char* const fragmentShader =
+		"varying mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_FragColor = v_color;\n"
+		"}\n";
+
+	if (m_context.getRenderTarget().getNumSamples() != 0)
+		throw tcu::NotSupportedError("MSAA config not supported by this test");
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShader, fragmentShader));
+
+	if(!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+
+		GLint maxVertexTextures;
+		glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);
+
+		if (maxVertexTextures < 1)
+			throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
+		else
+			TCU_FAIL("Failed to compile shader");
+	}
+
+	// Make the textures.
+	try
+	{
+		// Compute suitable power-of-two sizes (for mipmaps).
+		const int texWidth		= 1 << deLog2Ceil32(MAX_2D_RENDER_WIDTH / 2);
+		const int texHeight		= 1 << deLog2Ceil32(MAX_2D_RENDER_HEIGHT / 2);
+
+		for (int i = 0; i < 2; i++)
+		{
+			DE_ASSERT(!m_textures[i]);
+			m_textures[i] = new glu::Texture2D(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight);
+		}
+
+		const bool						mipmaps		= (deIsPowerOfTwo32(texWidth) && deIsPowerOfTwo32(texHeight));
+		const int						numLevels	= mipmaps ? deLog2Floor32(de::max(texWidth, texHeight))+1 : 1;
+		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+		const Vec4						cBias		= fmtInfo.valueMin;
+		const Vec4						cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+		// Fill first with gradient texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			const Vec4 gMin = Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
+			const Vec4 gMax = Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;
+
+			m_textures[0]->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
+		}
+
+		// Fill second with grid texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			const deUint32 step		= 0x00ffffff / numLevels;
+			const deUint32 rgb		= step*levelNdx;
+			const deUint32 colorA	= 0xff000000 | rgb;
+			const deUint32 colorB	= 0xff000000 | ~rgb;
+
+			m_textures[1]->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+		}
+
+		// Upload.
+		for (int i = 0; i < 2; i++)
+			m_textures[i]->upload();
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		Vertex2DTextureCase::deinit();
+		throw;
+	}
+}
+
+void Vertex2DTextureCase::deinit (void)
+{
+	for (int i = 0; i < 2; i++)
+	{
+		delete m_textures[i];
+		m_textures[i] = DE_NULL;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+float Vertex2DTextureCase::calculateLod (const Vec2& texScale, const Vec2& dstSize, int textureNdx) const
+{
+	const tcu::Texture2D&		refTexture	= m_textures[textureNdx]->getRefTexture();
+	const Vec2					srcSize		= Vec2((float)refTexture.getWidth(), (float)refTexture.getHeight());
+	const Vec2					sizeRatio	= texScale*srcSize / dstSize;
+
+	// \note In this particular case dv/dx and du/dy are zero, simplifying the expression.
+	return deFloatLog2(de::max(sizeRatio.x(), sizeRatio.y()));
+}
+
+Vertex2DTextureCase::IterateResult Vertex2DTextureCase::iterate (void)
+{
+	const int	viewportWidth		= deMin32(m_context.getRenderTarget().getWidth(), MAX_2D_RENDER_WIDTH);
+	const int	viewportHeight		= deMin32(m_context.getRenderTarget().getHeight(), MAX_2D_RENDER_HEIGHT);
+
+	const int	viewportXOffsetMax	= m_context.getRenderTarget().getWidth() - viewportWidth;
+	const int	viewportYOffsetMax	= m_context.getRenderTarget().getHeight() - viewportHeight;
+
+	de::Random	rnd					(deStringHash(getName()));
+
+	const int	viewportXOffset		= rnd.getInt(0, viewportXOffsetMax);
+	const int	viewportYOffset		= rnd.getInt(0, viewportYOffsetMax);
+
+	glUseProgram(m_program->getProgram());
+
+	// Divide viewport into 4 cells.
+	const int leftWidth		= viewportWidth / 2;
+	const int rightWidth	= viewportWidth - leftWidth;
+	const int bottomHeight	= viewportHeight / 2;
+	const int topHeight		= viewportHeight - bottomHeight;
+
+	// Clear.
+	glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	// Texture scaling and offsetting vectors.
+	const Vec2 texMinScale		(+1.8f, +1.8f);
+	const Vec2 texMinOffset		(-0.3f, -0.2f);
+	const Vec2 texMagScale		(+0.3f, +0.3f);
+	const Vec2 texMagOffset		(+0.9f, +0.8f);
+
+	// Surface for the reference image.
+	tcu::Surface refImage(viewportWidth, viewportHeight);
+
+	{
+		const struct Render
+		{
+			const Rect	region;
+			int			textureNdx;
+			const Vec2	texCoordScale;
+			const Vec2	texCoordOffset;
+			Render (const Rect& r, int tN, const Vec2& tS, const Vec2& tO) : region(r), textureNdx(tN), texCoordScale(tS), texCoordOffset(tO) {}
+		} renders[] =
+		{
+			Render(Rect(0,				0,				leftWidth,	bottomHeight),	0, texMinScale, texMinOffset),
+			Render(Rect(leftWidth,		0,				rightWidth,	bottomHeight),	0, texMagScale, texMagOffset),
+			Render(Rect(0,				bottomHeight,	leftWidth,	topHeight),		1, texMinScale, texMinOffset),
+			Render(Rect(leftWidth,		bottomHeight,	rightWidth,	topHeight),		1, texMagScale, texMagOffset)
+		};
+
+		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
+		{
+			const Render&	rend				= renders[renderNdx];
+			const float		lod					= calculateLod(rend.texCoordScale, rend.region.size().asFloat(), rend.textureNdx);
+			const bool		useSafeTexCoords	= isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
+			const Grid		grid				(GRID_SIZE_2D, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
+												 TexTypeCoordParams<TEXTURETYPE_2D>(rend.texCoordScale, rend.texCoordOffset), useSafeTexCoords);
+
+			glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
+			renderCell				(rend.textureNdx, lod, grid);
+			computeReferenceCell	(rend.textureNdx, lod, grid, refImage, rend.region);
+		}
+	}
+
+	// Read back rendered results.
+	tcu::Surface resImage(viewportWidth, viewportHeight);
+	glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());
+
+	glUseProgram(0);
+
+	// Compare and log.
+	{
+		const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Image comparison failed");
+	}
+
+	return STOP;
+}
+
+void Vertex2DTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
+{
+	const deUint32 programID = m_program->getProgram();
+
+	// SETUP ATTRIBUTES.
+
+	{
+		const int positionLoc = glGetAttribLocation(programID, "a_position");
+		if (positionLoc != -1)
+		{
+			glEnableVertexAttribArray(positionLoc);
+			glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
+		}
+	}
+
+	{
+		const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
+		if (texCoordLoc != -1)
+		{
+			glEnableVertexAttribArray(texCoordLoc);
+			glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
+		}
+	}
+
+	// SETUP UNIFORMS.
+
+	{
+		const int lodLoc = glGetUniformLocation(programID, "u_lod");
+		if (lodLoc != -1)
+			glUniform1f(lodLoc, lod);
+	}
+
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_2D, m_textures[textureNdx]->getGLTexture());
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	{
+		const int texLoc = glGetUniformLocation(programID, "u_texture");
+		if (texLoc != -1)
+			glUniform1i(texLoc, 0);
+	}
+}
+
+// Renders one sub-image with given parameters.
+void Vertex2DTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
+{
+	setupShaderInputs(textureNdx, lod, grid);
+	glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
+}
+
+void Vertex2DTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
+{
+	computeReference(m_textures[textureNdx]->getRefTexture(), lod, glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter), grid, dst, dstRegion);
+}
+
+class VertexCubeTextureCase : public TestCase
+{
+public:
+								VertexCubeTextureCase	(Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT);
+								~VertexCubeTextureCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+	typedef PosTexCoordQuadGrid<TEXTURETYPE_CUBE> Grid;
+
+								VertexCubeTextureCase	(const VertexCubeTextureCase& other);
+	VertexCubeTextureCase&		operator=				(const VertexCubeTextureCase& other);
+
+	float						calculateLod			(const Vec2& texScale, const Vec2& dstSize, int textureNdx) const;
+	void						setupShaderInputs		(int textureNdx, float lod, const Grid& grid) const;
+	void						renderCell				(int textureNdx, float lod, const Grid& grid) const;
+	void						computeReferenceCell	(int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;
+
+	const deUint32				m_minFilter;
+	const deUint32				m_magFilter;
+	const deUint32				m_wrapS;
+	const deUint32				m_wrapT;
+
+	const glu::ShaderProgram*	m_program;
+	glu::TextureCube*			m_textures[2];	// 2 textures, a gradient texture and a grid texture.
+};
+
+VertexCubeTextureCase::VertexCubeTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT)
+	: TestCase				(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_program				(DE_NULL)
+{
+	m_textures[0] = DE_NULL;
+	m_textures[1] = DE_NULL;
+}
+
+VertexCubeTextureCase::~VertexCubeTextureCase(void)
+{
+	VertexCubeTextureCase::deinit();
+}
+
+void VertexCubeTextureCase::init (void)
+{
+	const char* const vertexShader =
+		"attribute highp vec2 a_position;\n"
+		"attribute highp vec3 a_texCoord;\n"
+		"uniform highp samplerCube u_texture;\n"
+		"uniform highp float u_lod;\n"
+		"varying mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		"	v_color = textureCubeLod(u_texture, a_texCoord, u_lod);\n"
+		"}\n";
+
+	const char* const fragmentShader =
+		"varying mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_FragColor = v_color;\n"
+		"}\n";
+
+	if (m_context.getRenderTarget().getNumSamples() != 0)
+		throw tcu::NotSupportedError("MSAA config not supported by this test");
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShader, fragmentShader));
+
+	if(!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+
+		GLint maxVertexTextures;
+		glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);
+
+		if (maxVertexTextures < 1)
+			throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
+		else
+			TCU_FAIL("Failed to compile shader");
+	}
+
+	// Make the textures.
+	try
+	{
+		// Compute suitable power-of-two sizes (for mipmaps).
+		const int texWidth		= 1 << deLog2Ceil32(MAX_CUBE_RENDER_WIDTH / 3 / 2);
+		const int texHeight		= 1 << deLog2Ceil32(MAX_CUBE_RENDER_HEIGHT / 2 / 2);
+
+		DE_ASSERT(texWidth == texHeight);
+		DE_UNREF(texHeight);
+
+		for (int i = 0; i < 2; i++)
+		{
+			DE_ASSERT(!m_textures[i]);
+			m_textures[i] = new glu::TextureCube(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth);
+		}
+
+		const bool						mipmaps		= deIsPowerOfTwo32(texWidth) != DE_FALSE;
+		const int						numLevels	= mipmaps ? deLog2Floor32(texWidth)+1 : 1;
+		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+		const Vec4						cBias		= fmtInfo.valueMin;
+		const Vec4						cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+		// Fill first with gradient texture.
+		static const Vec4 gradients[tcu::CUBEFACE_LAST][2] =
+		{
+			{ Vec4(-1.0f, -1.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
+			{ Vec4( 0.0f, -1.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
+			{ Vec4(-1.0f,  0.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
+			{ Vec4(-1.0f, -1.0f,  0.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
+			{ Vec4(-1.0f, -1.0f, -1.0f, 0.0f), Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
+			{ Vec4( 0.0f,  0.0f,  0.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
+		};
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				m_textures[0]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+				tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0]*cScale + cBias, gradients[face][1]*cScale + cBias);
+			}
+		}
+
+		// Fill second with grid texture.
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				const deUint32 step		= 0x00ffffff / (numLevels*tcu::CUBEFACE_LAST);
+				const deUint32 rgb		= step*levelNdx*face;
+				const deUint32 colorA	= 0xff000000 | rgb;
+				const deUint32 colorB	= 0xff000000 | ~rgb;
+
+				m_textures[1]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+				tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+			}
+		}
+
+		// Upload.
+		for (int i = 0; i < 2; i++)
+			m_textures[i]->upload();
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		VertexCubeTextureCase::deinit();
+		throw;
+	}
+}
+
+void VertexCubeTextureCase::deinit (void)
+{
+	for (int i = 0; i < 2; i++)
+	{
+		delete m_textures[i];
+		m_textures[i] = DE_NULL;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+float VertexCubeTextureCase::calculateLod (const Vec2& texScale, const Vec2& dstSize, int textureNdx) const
+{
+	const tcu::TextureCube&		refTexture	= m_textures[textureNdx]->getRefTexture();
+	const Vec2					srcSize		= Vec2((float)refTexture.getSize(), (float)refTexture.getSize());
+	const Vec2					sizeRatio	= texScale*srcSize / dstSize;
+
+	// \note In this particular case, dv/dx and du/dy are zero, simplifying the expression.
+	return deFloatLog2(de::max(sizeRatio.x(), sizeRatio.y()));
+}
+
+VertexCubeTextureCase::IterateResult VertexCubeTextureCase::iterate (void)
+{
+	const int	viewportWidth		= deMin32(m_context.getRenderTarget().getWidth(), MAX_CUBE_RENDER_WIDTH);
+	const int	viewportHeight		= deMin32(m_context.getRenderTarget().getHeight(), MAX_CUBE_RENDER_HEIGHT);
+
+	const int	viewportXOffsetMax	= m_context.getRenderTarget().getWidth() - viewportWidth;
+	const int	viewportYOffsetMax	= m_context.getRenderTarget().getHeight() - viewportHeight;
+
+	de::Random	rnd					(deStringHash(getName()));
+
+	const int	viewportXOffset		= rnd.getInt(0, viewportXOffsetMax);
+	const int	viewportYOffset		= rnd.getInt(0, viewportYOffsetMax);
+
+	glUseProgram(m_program->getProgram());
+
+	// Divide viewport into 4 areas.
+	const int leftWidth		= viewportWidth / 2;
+	const int rightWidth	= viewportWidth - leftWidth;
+	const int bottomHeight	= viewportHeight / 2;
+	const int topHeight		= viewportHeight - bottomHeight;
+
+	// Clear.
+	glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	// Texture scaling and offsetting vectors.
+	const Vec2 texMinScale		(1.0f, 1.0f);
+	const Vec2 texMinOffset		(0.0f, 0.0f);
+	const Vec2 texMagScale		(0.3f, 0.3f);
+	const Vec2 texMagOffset		(0.5f, 0.3f);
+
+	// Surface for the reference image.
+	tcu::Surface refImage(viewportWidth, viewportHeight);
+
+	// Each of the four areas is divided into 6 cells.
+	const int defCellWidth	= viewportWidth / 2 / 3;
+	const int defCellHeight	= viewportHeight / 2 / 2;
+
+	for (int i = 0; i < tcu::CUBEFACE_LAST; i++)
+	{
+		const int	cellOffsetX			= defCellWidth * (i % 3);
+		const int	cellOffsetY			= defCellHeight * (i / 3);
+		const bool	isRightmostCell		= i == 2 || i == 5;
+		const bool	isTopCell			= i >= 3;
+		const int	leftCellWidth		= isRightmostCell	? leftWidth		- cellOffsetX : defCellWidth;
+		const int	rightCellWidth		= isRightmostCell	? rightWidth	- cellOffsetX : defCellWidth;
+		const int	bottomCellHeight	= isTopCell			? bottomHeight	- cellOffsetY : defCellHeight;
+		const int	topCellHeight		= isTopCell			? topHeight		- cellOffsetY : defCellHeight;
+
+		const struct Render
+		{
+			const Rect	region;
+			int			textureNdx;
+			const Vec2	texCoordScale;
+			const Vec2	texCoordOffset;
+			Render (const Rect& r, int tN, const Vec2& tS, const Vec2& tO) : region(r), textureNdx(tN), texCoordScale(tS), texCoordOffset(tO) {}
+		} renders[] =
+		{
+			Render(Rect(cellOffsetX + 0,			cellOffsetY + 0,				leftCellWidth,	bottomCellHeight),	0, texMinScale, texMinOffset),
+			Render(Rect(cellOffsetX + leftWidth,	cellOffsetY + 0,				rightCellWidth,	bottomCellHeight),	0, texMagScale, texMagOffset),
+			Render(Rect(cellOffsetX + 0,			cellOffsetY + bottomHeight,		leftCellWidth,	topCellHeight),		1, texMinScale, texMinOffset),
+			Render(Rect(cellOffsetX + leftWidth,	cellOffsetY + bottomHeight,		rightCellWidth,	topCellHeight),		1, texMagScale, texMagOffset)
+		};
+
+		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
+		{
+			const Render&	rend				= renders[renderNdx];
+			const float		lod					= calculateLod(rend.texCoordScale, rend.region.size().asFloat(), rend.textureNdx);
+			const bool		useSafeTexCoords	= isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
+			const Grid		grid				(GRID_SIZE_CUBE, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
+												 TexTypeCoordParams<TEXTURETYPE_CUBE>(rend.texCoordScale, rend.texCoordOffset, (tcu::CubeFace)i), useSafeTexCoords);
+
+			glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
+			renderCell				(rend.textureNdx, lod, grid);
+			computeReferenceCell	(rend.textureNdx, lod, grid, refImage, rend.region);
+		}
+	}
+
+	// Read back rendered results.
+	tcu::Surface resImage(viewportWidth, viewportHeight);
+	glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());
+
+	glUseProgram(0);
+
+	// Compare and log.
+	{
+		const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Image comparison failed");
+	}
+
+	return STOP;
+}
+
+void VertexCubeTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
+{
+	const deUint32 programID = m_program->getProgram();
+
+	// SETUP ATTRIBUTES.
+
+	{
+		const int positionLoc = glGetAttribLocation(programID, "a_position");
+		if (positionLoc != -1)
+		{
+			glEnableVertexAttribArray(positionLoc);
+			glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
+		}
+	}
+
+	{
+		const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
+		if (texCoordLoc != -1)
+		{
+			glEnableVertexAttribArray(texCoordLoc);
+			glVertexAttribPointer(texCoordLoc, 3, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
+		}
+	}
+
+	// SETUP UNIFORMS.
+
+	{
+		const int lodLoc = glGetUniformLocation(programID, "u_lod");
+		if (lodLoc != -1)
+			glUniform1f(lodLoc, lod);
+	}
+
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_CUBE_MAP, m_textures[textureNdx]->getGLTexture());
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	{
+		const int texLoc = glGetUniformLocation(programID, "u_texture");
+		if (texLoc != -1)
+			glUniform1i(texLoc, 0);
+	}
+}
+
+// Renders one cube face with given parameters.
+void VertexCubeTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
+{
+	setupShaderInputs(textureNdx, lod, grid);
+	glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
+}
+
+// Computes reference for one cube face with given parameters.
+void VertexCubeTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
+{
+	tcu::Sampler sampler = glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	computeReference(m_textures[textureNdx]->getRefTexture(), lod, sampler, grid, dst, dstRegion);
+}
+
+VertexTextureTests::VertexTextureTests (Context& context)
+	: TestCaseGroup(context, "vertex", "Vertex Texture Tests")
+{
+}
+
+VertexTextureTests::~VertexTextureTests(void)
+{
+}
+
+void VertexTextureTests::init (void)
+{
+	// 2D and cube map groups, and their filtering and wrap sub-groups.
+	TestCaseGroup* const group2D				= new TestCaseGroup(m_context, "2d",			"2D Vertex Texture Tests");
+	TestCaseGroup* const groupCube				= new TestCaseGroup(m_context, "cube",			"Cube Map Vertex Texture Tests");
+	TestCaseGroup* const filteringGroup2D		= new TestCaseGroup(m_context, "filtering",		"2D Vertex Texture Filtering Tests");
+	TestCaseGroup* const wrapGroup2D			= new TestCaseGroup(m_context, "wrap",			"2D Vertex Texture Wrap Tests");
+	TestCaseGroup* const filteringGroupCube		= new TestCaseGroup(m_context, "filtering",		"Cube Map Vertex Texture Filtering Tests");
+	TestCaseGroup* const wrapGroupCube			= new TestCaseGroup(m_context, "wrap",			"Cube Map Vertex Texture Wrap Tests");
+
+	group2D->addChild(filteringGroup2D);
+	group2D->addChild(wrapGroup2D);
+	groupCube->addChild(filteringGroupCube);
+	groupCube->addChild(wrapGroupCube);
+
+	addChild(group2D);
+	addChild(groupCube);
+
+	static const struct
+	{
+		const char*		name;
+		GLenum			mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE	},
+		{ "repeat",		GL_REPEAT			},
+		{ "mirror",		GL_MIRRORED_REPEAT	}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		GLenum			mode;
+	} minFilterModes[] =
+	{
+		{ "nearest",				GL_NEAREST					},
+		{ "linear",					GL_LINEAR					},
+		{ "nearest_mipmap_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
+		{ "linear_mipmap_nearest",	GL_LINEAR_MIPMAP_NEAREST	},
+		{ "nearest_mipmap_linear",	GL_NEAREST_MIPMAP_LINEAR	},
+		{ "linear_mipmap_linear",	GL_LINEAR_MIPMAP_LINEAR		}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		GLenum			mode;
+	} magFilterModes[] =
+	{
+		{ "nearest",	GL_NEAREST	},
+		{ "linear",		GL_LINEAR	}
+	};
+
+#define FOR_EACH(ITERATOR, ARRAY, BODY)	\
+	for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++)	\
+		BODY
+
+	// 2D cases.
+
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+		{
+			const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;
+
+			filteringGroup2D->addChild(new Vertex2DTextureCase(m_context,
+															   name.c_str(), "",
+															   minFilterModes[minFilter].mode,
+															   magFilterModes[magFilter].mode,
+															   wrapModes[wrapMode].mode,
+															   wrapModes[wrapMode].mode));
+		})));
+
+	FOR_EACH(wrapSMode,		wrapModes,
+	FOR_EACH(wrapTMode,		wrapModes,
+		{
+			const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name;
+
+			wrapGroup2D->addChild(new Vertex2DTextureCase(m_context,
+														  name.c_str(), "",
+														  GL_LINEAR_MIPMAP_LINEAR,
+														  GL_LINEAR,
+														  wrapModes[wrapSMode].mode,
+														  wrapModes[wrapTMode].mode));
+		}));
+
+	// Cube map cases.
+
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+		{
+			const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;
+
+			filteringGroupCube->addChild(new VertexCubeTextureCase(m_context,
+																   name.c_str(), "",
+																   minFilterModes[minFilter].mode,
+																   magFilterModes[magFilter].mode,
+																   wrapModes[wrapMode].mode,
+																   wrapModes[wrapMode].mode));
+		})));
+
+	FOR_EACH(wrapSMode,		wrapModes,
+	FOR_EACH(wrapTMode,		wrapModes,
+		{
+			const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name;
+
+			wrapGroupCube->addChild(new VertexCubeTextureCase(m_context,
+															  name.c_str(), "",
+															  GL_LINEAR_MIPMAP_LINEAR,
+															  GL_LINEAR,
+															  wrapModes[wrapSMode].mode,
+															  wrapModes[wrapTMode].mode));
+		}));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles2/functional/es2fVertexTextureTests.hpp b/modules/gles2/functional/es2fVertexTextureTests.hpp
new file mode 100644
index 0000000..9d1ffc2
--- /dev/null
+++ b/modules/gles2/functional/es2fVertexTextureTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2FVERTEXTEXTURETESTS_HPP
+#define _ES2FVERTEXTEXTURETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex-side texture tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Functional
+{
+
+class VertexTextureTests : public TestCaseGroup
+{
+public:
+								VertexTextureTests			(Context& context);
+								~VertexTextureTests			(void);
+
+	void						init						(void);
+
+private:
+								VertexTextureTests			(const VertexTextureTests& other);
+	VertexTextureTests&			operator=					(const VertexTextureTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES2FVERTEXTEXTURETESTS_HPP
diff --git a/modules/gles2/gles2.cmake b/modules/gles2/gles2.cmake
new file mode 100644
index 0000000..99c9ac4
--- /dev/null
+++ b/modules/gles2/gles2.cmake
@@ -0,0 +1,3 @@
+if (DEQP_SUPPORT_GLES2)
+	add_subdirectory(gles2)
+endif ()
diff --git a/modules/gles2/performance/CMakeLists.txt b/modules/gles2/performance/CMakeLists.txt
new file mode 100644
index 0000000..80a56df
--- /dev/null
+++ b/modules/gles2/performance/CMakeLists.txt
@@ -0,0 +1,39 @@
+# dEQP-GLES2.performance
+
+set(DEQP_GLES2_PERFORMANCE_SRCS
+	es2pBlendTests.cpp
+	es2pBlendTests.hpp
+	es2pPerformanceTests.cpp
+	es2pPerformanceTests.hpp
+	es2pShaderControlStatementTests.cpp
+	es2pShaderControlStatementTests.hpp
+	es2pShaderOperatorTests.cpp
+	es2pShaderOperatorTests.hpp
+	es2pTextureCases.cpp
+	es2pTextureCases.hpp
+	es2pTextureCountTests.cpp
+	es2pTextureCountTests.hpp
+	es2pTextureFilteringTests.cpp
+	es2pTextureFilteringTests.hpp
+	es2pTextureFormatTests.cpp
+	es2pTextureFormatTests.hpp
+	es2pShaderCompilerTests.cpp
+	es2pShaderCompilerTests.hpp
+	es2pTextureUploadTests.cpp
+	es2pTextureUploadTests.hpp
+	es2pStateChangeCallTests.hpp
+	es2pStateChangeCallTests.cpp
+	es2pStateChangeTests.hpp
+	es2pStateChangeTests.cpp
+	es2pRedundantStateChangeTests.hpp
+	es2pRedundantStateChangeTests.cpp
+	es2pDrawCallBatchingTests.hpp
+	es2pDrawCallBatchingTests.cpp
+	es2pShaderOptimizationTests.hpp
+	es2pShaderOptimizationTests.cpp
+	es2pShaderCompilationCases.hpp
+	es2pShaderCompilationCases.cpp
+	)
+
+add_library(deqp-gles2-performance STATIC ${DEQP_GLES2_PERFORMANCE_SRCS})
+target_link_libraries(deqp-gles2-performance deqp-gl-shared glutil tcutil ${DEQP_GLES2_LIBRARIES})
diff --git a/modules/gles2/performance/es2pBlendTests.cpp b/modules/gles2/performance/es2pBlendTests.cpp
new file mode 100644
index 0000000..f06a4c1
--- /dev/null
+++ b/modules/gles2/performance/es2pBlendTests.cpp
@@ -0,0 +1,169 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Blend performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pBlendTests.hpp"
+#include "glsShaderPerformanceCase.hpp"
+#include "tcuTestLog.hpp"
+#include "gluStrUtil.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+using namespace gls;
+using namespace glw; // GL types
+using tcu::Vec4;
+using tcu::TestLog;
+
+class BlendCase : public ShaderPerformanceCase
+{
+public:
+						BlendCase			(Context& context, const char* name, const char* description, GLenum modeRGB, GLenum modeAlpha, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
+						~BlendCase			(void);
+
+	void				init				(void);
+
+private:
+	void				setupRenderState	(void);
+
+	GLenum				m_modeRGB;
+	GLenum				m_modeAlpha;
+	GLenum				m_srcRGB;
+	GLenum				m_dstRGB;
+	GLenum				m_srcAlpha;
+	GLenum				m_dstAlpha;
+};
+
+BlendCase::BlendCase (Context& context, const char* name, const char* description, GLenum modeRGB, GLenum modeAlpha, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha)
+	: ShaderPerformanceCase	(context.getTestContext(), context.getRenderContext(), name, description, CASETYPE_FRAGMENT)
+	, m_modeRGB				(modeRGB)
+	, m_modeAlpha			(modeAlpha)
+	, m_srcRGB				(srcRGB)
+	, m_dstRGB				(dstRGB)
+	, m_srcAlpha			(srcAlpha)
+	, m_dstAlpha			(dstAlpha)
+{
+}
+
+BlendCase::~BlendCase (void)
+{
+}
+
+void BlendCase::init (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	log << TestLog::Message << "modeRGB: " << glu::getBlendEquationStr(m_modeRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "modeAlpha: " << glu::getBlendEquationStr(m_modeAlpha) << TestLog::EndMessage;
+	log << TestLog::Message << "srcRGB: " << glu::getBlendFactorStr(m_srcRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "dstRGB: " << glu::getBlendFactorStr(m_dstRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "srcAlpha: " << glu::getBlendFactorStr(m_srcAlpha) << TestLog::EndMessage;
+	log << TestLog::Message << "dstAlpha: " << glu::getBlendFactorStr(m_dstAlpha) << TestLog::EndMessage;
+
+	m_vertShaderSource =
+		"attribute highp vec4 a_position;\n"
+		"attribute mediump vec4 a_color;\n"
+		"varying mediump vec4 v_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_color = a_color;\n"
+		"}\n";
+	m_fragShaderSource =
+		"varying mediump vec4 v_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_FragColor = v_color;\n"
+		"}\n";
+
+	m_attributes.push_back(AttribSpec("a_color", Vec4(0.0f, 0.5f, 0.5f, 1.0f),
+												 Vec4(0.5f, 1.0f, 0.0f, 0.5f),
+												 Vec4(0.5f, 0.0f, 1.0f, 0.5f),
+												 Vec4(1.0f, 0.5f, 0.5f, 0.0f)));
+
+	ShaderPerformanceCase::init();
+}
+
+void BlendCase::setupRenderState (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	gl.enable(GL_BLEND);
+	gl.blendEquationSeparate(m_modeRGB, m_modeAlpha);
+	gl.blendFuncSeparate(m_srcRGB, m_dstRGB, m_srcAlpha, m_dstAlpha);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After render state setup");
+}
+
+BlendTests::BlendTests (Context& context)
+	: TestCaseGroup(context, "blend", "Blend Performance Tests")
+{
+}
+
+BlendTests::~BlendTests (void)
+{
+}
+
+void BlendTests::init (void)
+{
+	static const struct
+	{
+		const char*	name;
+		GLenum		modeRGB;
+		GLenum		modeAlpha;
+		GLenum		srcRGB;
+		GLenum		dstRGB;
+		GLenum		srcAlpha;
+		GLenum		dstAlpha;
+	} cases[] =
+	{
+		// Single blend func, factor one.
+		{ "add",						GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE,		GL_ONE,		GL_ONE,		GL_ONE		},
+		{ "subtract",					GL_FUNC_SUBTRACT,			GL_FUNC_SUBTRACT,			GL_ONE,		GL_ONE,		GL_ONE,		GL_ONE		},
+		{ "reverse_subtract",			GL_FUNC_REVERSE_SUBTRACT,	GL_FUNC_REVERSE_SUBTRACT,	GL_ONE,		GL_ONE,		GL_ONE,		GL_ONE		},
+
+		// Porter-duff modes that can be implemented.
+		{ "dst_atop",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE_MINUS_DST_ALPHA,		GL_SRC_ALPHA,				GL_ONE,					GL_ZERO					},
+		{ "dst_in",						GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ZERO,					GL_SRC_ALPHA,				GL_ZERO,				GL_SRC_ALPHA			},
+		{ "dst_out",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ZERO,					GL_ONE_MINUS_SRC_ALPHA,		GL_ZERO,				GL_ONE_MINUS_SRC_ALPHA	},
+		{ "dst_over",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE_MINUS_DST_ALPHA,		GL_ONE,						GL_ONE,					GL_ONE_MINUS_SRC_ALPHA	},
+		{ "src_atop",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_DST_ALPHA,				GL_ONE_MINUS_SRC_ALPHA,		GL_ZERO,				GL_ONE					},
+		{ "src_in",						GL_FUNC_ADD,				GL_FUNC_ADD,				GL_DST_ALPHA,				GL_ZERO,					GL_DST_ALPHA,			GL_ZERO					},
+		{ "src_out",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE_MINUS_DST_ALPHA,		GL_ZERO,					GL_ONE_MINUS_DST_ALPHA,	GL_ZERO					},
+		{ "src_over",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE,						GL_ONE_MINUS_SRC_ALPHA,		GL_ONE,					GL_ONE_MINUS_SRC_ALPHA	},
+		{ "multiply",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_DST_COLOR,				GL_ZERO,					GL_DST_ALPHA,			GL_ZERO					},
+		{ "screen",						GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE,						GL_ONE_MINUS_SRC_COLOR,		GL_ONE,					GL_ONE_MINUS_SRC_ALPHA	}
+	};
+
+	for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+		addChild(new BlendCase(m_context, cases[caseNdx].name, "", cases[caseNdx].modeRGB, cases[caseNdx].modeAlpha, cases[caseNdx].srcRGB, cases[caseNdx].dstRGB, cases[caseNdx].srcAlpha, cases[caseNdx].dstAlpha));
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pBlendTests.hpp b/modules/gles2/performance/es2pBlendTests.hpp
new file mode 100644
index 0000000..4999304
--- /dev/null
+++ b/modules/gles2/performance/es2pBlendTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PBLENDTESTS_HPP
+#define _ES2PBLENDTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Blend performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class BlendTests : public TestCaseGroup
+{
+public:
+					BlendTests		(Context& context);
+					~BlendTests		(void);
+
+	void			init			(void);
+
+private:
+					BlendTests		(const BlendTests& other);
+	BlendTests&		operator=		(const BlendTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PBLENDTESTS_HPP
diff --git a/modules/gles2/performance/es2pDrawCallBatchingTests.cpp b/modules/gles2/performance/es2pDrawCallBatchingTests.cpp
new file mode 100644
index 0000000..b2426aa
--- /dev/null
+++ b/modules/gles2/performance/es2pDrawCallBatchingTests.cpp
@@ -0,0 +1,1090 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Draw call batching performance tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pDrawCallBatchingTests.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+
+#include "glwDefs.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "tcuTestLog.hpp"
+
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+#include "deFile.h"
+#include "deString.h"
+#include "deClock.h"
+#include "deThread.h"
+
+#include <cmath>
+#include <vector>
+#include <string>
+#include <sstream>
+
+using tcu::TestLog;
+
+using namespace glw;
+
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+namespace
+{
+const int CALIBRATION_SAMPLE_COUNT = 34;
+
+class DrawCallBatchingTest : public tcu::TestCase
+{
+public:
+	struct TestSpec
+	{
+		bool	useStaticBuffer;
+		int		staticAttributeCount;
+
+		bool	useDynamicBuffer;
+		int		dynamicAttributeCount;
+
+		int 	triangleCount;
+		int		drawCallCount;
+
+		bool	useDrawElements;
+		bool	useIndexBuffer;
+		bool	dynamicIndices;
+	};
+
+					DrawCallBatchingTest	(Context& context, const char* name, const char* description, const TestSpec& spec);
+					~DrawCallBatchingTest	(void);
+
+	void			init					(void);
+	void			deinit					(void);
+	IterateResult	iterate					(void);
+
+private:
+	enum State
+	{
+		STATE_LOG_INFO = 0,
+		STATE_WARMUP_BATCHED,
+		STATE_WARMUP_UNBATCHED,
+		STATE_CALC_CALIBRATION,
+		STATE_SAMPLE
+	};
+
+	State						m_state;
+
+	glu::RenderContext&			m_renderCtx;
+	de::Random					m_rnd;
+	int							m_sampleIteration;
+
+	int							m_unbatchedSampleCount;
+	int							m_batchedSampleCount;
+
+	TestSpec					m_spec;
+
+	glu::ShaderProgram*			m_program;
+
+	vector<deUint8>				m_dynamicIndexData;
+	vector<deUint8>				m_staticIndexData;
+
+	vector<GLuint>				m_unbatchedDynamicIndexBuffers;
+	GLuint						m_batchedDynamicIndexBuffer;
+
+	GLuint						m_unbatchedStaticIndexBuffer;
+	GLuint						m_batchedStaticIndexBuffer;
+
+	vector<vector<deInt8> >		m_staticAttributeDatas;
+	vector<vector<deInt8> >		m_dynamicAttributeDatas;
+
+	vector<GLuint>				m_batchedStaticBuffers;
+	vector<GLuint>				m_unbatchedStaticBuffers;
+
+	vector<GLuint>				m_batchedDynamicBuffers;
+	vector<vector<GLuint> >		m_unbatchedDynamicBuffers;
+
+	vector<deUint64>			m_unbatchedSamplesUs;
+	vector<deUint64>			m_batchedSamplesUs;
+
+	void						logTestInfo				(void);
+
+	deUint64					renderUnbatched			(void);
+	deUint64					renderBatched			(void);
+
+	void						createIndexData			(void);
+	void						createIndexBuffer		(void);
+
+	void						createShader			(void);
+	void						createAttributeDatas	(void);
+	void						createArrayBuffers		(void);
+};
+
+DrawCallBatchingTest::DrawCallBatchingTest (Context& context, const char* name, const char* description, const TestSpec& spec)
+	: tcu::TestCase					(context.getTestContext(), tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_state						(STATE_LOG_INFO)
+	, m_renderCtx					(context.getRenderContext())
+	, m_rnd							(deStringHash(name))
+	, m_sampleIteration				(0)
+	, m_unbatchedSampleCount		(CALIBRATION_SAMPLE_COUNT)
+	, m_batchedSampleCount			(CALIBRATION_SAMPLE_COUNT)
+	, m_spec						(spec)
+	, m_program						(NULL)
+	, m_batchedDynamicIndexBuffer	(0)
+	, m_unbatchedStaticIndexBuffer	(0)
+	, m_batchedStaticIndexBuffer	(0)
+{
+}
+
+DrawCallBatchingTest::~DrawCallBatchingTest (void)
+{
+	deinit();
+}
+
+void DrawCallBatchingTest::createIndexData (void)
+{
+	if (m_spec.dynamicIndices)
+	{
+		for (int drawNdx = 0; drawNdx < m_spec.drawCallCount; drawNdx++)
+		{
+			for (int triangleNdx = 0; triangleNdx < m_spec.triangleCount; triangleNdx++)
+			{
+				m_dynamicIndexData.push_back(triangleNdx * 3);
+				m_dynamicIndexData.push_back(triangleNdx * 3 + 1);
+				m_dynamicIndexData.push_back(triangleNdx * 3 + 2);
+			}
+		}
+	}
+	else
+	{
+		for (int drawNdx = 0; drawNdx < m_spec.drawCallCount; drawNdx++)
+		{
+			for (int triangleNdx = 0; triangleNdx < m_spec.triangleCount; triangleNdx++)
+			{
+				m_staticIndexData.push_back(triangleNdx * 3);
+				m_staticIndexData.push_back(triangleNdx * 3 + 1);
+				m_staticIndexData.push_back(triangleNdx * 3 + 2);
+			}
+		}
+	}
+}
+
+void DrawCallBatchingTest::createShader (void)
+{
+	std::ostringstream		vertexShader;
+	std::ostringstream		fragmentShader;
+
+	for (int attributeNdx = 0; attributeNdx < m_spec.staticAttributeCount; attributeNdx++)
+		vertexShader << "attribute mediump vec4 a_static" << attributeNdx << ";\n";
+
+	if (m_spec.staticAttributeCount > 0 && m_spec.dynamicAttributeCount > 0)
+		vertexShader << "\n";
+
+	for (int attributeNdx = 0; attributeNdx < m_spec.dynamicAttributeCount; attributeNdx++)
+		vertexShader << "attribute mediump vec4 a_dyn" << attributeNdx << ";\n";
+
+	vertexShader
+	<< "\n"
+	<< "varying mediump vec4 v_color;\n"
+	<< "\n"
+	<< "void main (void)\n"
+	<< "{\n";
+
+	vertexShader << "\tv_color = ";
+
+	bool first = true;
+
+	for (int attributeNdx = 0; attributeNdx < m_spec.staticAttributeCount; attributeNdx++)
+	{
+		if (!first)
+			vertexShader << " + ";
+		first = false;
+
+		vertexShader << "a_static" << attributeNdx;
+	}
+
+	for (int attributeNdx = 0; attributeNdx < m_spec.dynamicAttributeCount; attributeNdx++)
+	{
+		if (!first)
+			vertexShader << " + ";
+		first = false;
+
+		vertexShader << "a_dyn" << attributeNdx;
+	}
+
+	vertexShader << ";\n";
+
+	if (m_spec.dynamicAttributeCount > 0)
+		vertexShader << "\tgl_Position = a_dyn0;\n";
+	else
+		vertexShader << "\tgl_Position = a_static0;\n";
+
+	vertexShader
+	<< "}";
+
+	fragmentShader
+	<< "varying mediump vec4 v_color;\n"
+	<< "\n"
+	<< "void main(void)\n"
+	<< "{\n"
+	<< "\tgl_FragColor = v_color;\n"
+	<< "}\n";
+
+	m_program = new glu::ShaderProgram(m_renderCtx, glu::ProgramSources() << glu::VertexSource(vertexShader.str()) << glu::FragmentSource(fragmentShader.str()));
+
+	m_testCtx.getLog() << (*m_program);
+	TCU_CHECK(m_program->isOk());
+}
+
+void DrawCallBatchingTest::createAttributeDatas (void)
+{
+	// Generate data for static attributes
+	for (int attribute = 0; attribute < m_spec.staticAttributeCount; attribute++)
+	{
+		vector<deInt8> data;
+
+		if (m_spec.dynamicAttributeCount == 0 && attribute == 0)
+		{
+			data.reserve(4 * 3 * m_spec.triangleCount * m_spec.drawCallCount);
+
+			for (int i = 0; i < m_spec.triangleCount * m_spec.drawCallCount; i++)
+			{
+				int sign = (m_spec.triangleCount % 2 == 1 || i % 2 == 0 ? 1 : -1);
+
+				data.push_back(-127 * sign);
+				data.push_back(-127 * sign);
+				data.push_back(0);
+				data.push_back(127);
+
+				data.push_back(127 * sign);
+				data.push_back(-127 * sign);
+				data.push_back(0);
+				data.push_back(127);
+
+				data.push_back(127 * sign);
+				data.push_back(127 * sign);
+				data.push_back(0);
+				data.push_back(127);
+			}
+		}
+		else
+		{
+			data.reserve(4 * 3 * m_spec.triangleCount * m_spec.drawCallCount);
+
+			for (int i = 0; i < 4 * 3 * m_spec.triangleCount * m_spec.drawCallCount; i++)
+				data.push_back(m_rnd.getUint32());
+		}
+
+		m_staticAttributeDatas.push_back(data);
+	}
+
+	// Generate data for dynamic attributes
+	for (int attribute = 0; attribute < m_spec.dynamicAttributeCount; attribute++)
+	{
+		vector<deInt8> data;
+
+		if (attribute == 0)
+		{
+			data.reserve(4 * 3 * m_spec.triangleCount * m_spec.drawCallCount);
+
+			for (int i = 0; i < m_spec.triangleCount * m_spec.drawCallCount; i++)
+			{
+				int sign = (i % 2 == 0 ? 1 : -1);
+
+				data.push_back(-127 * sign);
+				data.push_back(-127 * sign);
+				data.push_back(0);
+				data.push_back(127);
+
+				data.push_back(127 * sign);
+				data.push_back(-127 * sign);
+				data.push_back(0);
+				data.push_back(127);
+
+				data.push_back(127 * sign);
+				data.push_back(127 * sign);
+				data.push_back(0);
+				data.push_back(127);
+			}
+		}
+		else
+		{
+			data.reserve(4 * 3 * m_spec.triangleCount * m_spec.drawCallCount);
+
+			for (int i = 0; i < 4 * 3 * m_spec.triangleCount * m_spec.drawCallCount; i++)
+				data.push_back(m_rnd.getUint32());
+		}
+
+		m_dynamicAttributeDatas.push_back(data);
+	}
+}
+
+void DrawCallBatchingTest::createArrayBuffers (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_spec.useStaticBuffer)
+	{
+		// Upload static attributes for batched
+		for (int attribute = 0; attribute < m_spec.staticAttributeCount; attribute++)
+		{
+			GLuint buffer;
+
+			gl.genBuffers(1, &buffer);
+			gl.bindBuffer(GL_ARRAY_BUFFER, buffer);
+			gl.bufferData(GL_ARRAY_BUFFER, 4 * 3 * m_spec.triangleCount * m_spec.drawCallCount, &(m_staticAttributeDatas[attribute][0]), GL_STATIC_DRAW);
+			gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Creating static buffer failed");
+
+			m_batchedStaticBuffers.push_back(buffer);
+		}
+
+		// Upload static attributes for unbatched
+		for (int attribute = 0; attribute < m_spec.staticAttributeCount; attribute++)
+		{
+			GLuint buffer;
+
+			gl.genBuffers(1, &buffer);
+			gl.bindBuffer(GL_ARRAY_BUFFER, buffer);
+			gl.bufferData(GL_ARRAY_BUFFER, 4 * 3 * m_spec.triangleCount, &(m_staticAttributeDatas[attribute][0]), GL_STATIC_DRAW);
+			gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Creating static buffer failed");
+
+			m_unbatchedStaticBuffers.push_back(buffer);
+		}
+	}
+
+	if (m_spec.useDynamicBuffer)
+	{
+		// Upload dynamic attributes for batched
+		for (int attribute = 0; attribute < m_spec.dynamicAttributeCount; attribute++)
+		{
+			GLuint buffer;
+
+			gl.genBuffers(1, &buffer);
+			gl.bindBuffer(GL_ARRAY_BUFFER, buffer);
+			gl.bufferData(GL_ARRAY_BUFFER, 4 * 3 * m_spec.triangleCount * m_spec.drawCallCount, &(m_dynamicAttributeDatas[attribute][0]), GL_STATIC_DRAW);
+			gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic buffer failed");
+
+			m_batchedDynamicBuffers.push_back(buffer);
+		}
+
+		// Upload dynamic attributes for unbatched
+		for (int attribute = 0; attribute < m_spec.dynamicAttributeCount; attribute++)
+		{
+			vector<GLuint> buffers;
+
+			for (int drawNdx = 0; drawNdx < m_spec.drawCallCount; drawNdx++)
+			{
+				GLuint buffer;
+
+				gl.genBuffers(1, &buffer);
+				gl.bindBuffer(GL_ARRAY_BUFFER, buffer);
+				gl.bufferData(GL_ARRAY_BUFFER, 4 * 3 * m_spec.triangleCount * m_spec.drawCallCount, &(m_dynamicAttributeDatas[attribute][0]), GL_STATIC_DRAW);
+				gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic buffer failed");
+
+				buffers.push_back(buffer);
+			}
+
+			m_unbatchedDynamicBuffers.push_back(buffers);
+		}
+	}
+}
+
+void DrawCallBatchingTest::createIndexBuffer (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_spec.dynamicIndices)
+	{
+		for (int drawNdx = 0; drawNdx < m_spec.drawCallCount; drawNdx++)
+		{
+			GLuint buffer;
+
+			gl.genBuffers(1, &buffer);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
+			gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * m_spec.triangleCount, &(m_dynamicIndexData[drawNdx * m_spec.triangleCount * 3]), GL_STATIC_DRAW);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic index buffer failed");
+
+			m_unbatchedDynamicIndexBuffers.push_back(buffer);
+		}
+
+		{
+			GLuint buffer;
+
+			gl.genBuffers(1, &buffer);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
+			gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * m_spec.triangleCount * m_spec.drawCallCount, &(m_dynamicIndexData[0]), GL_STATIC_DRAW);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic index buffer failed");
+
+			m_batchedDynamicIndexBuffer = buffer;
+		}
+	}
+	else
+	{
+		{
+			GLuint buffer;
+
+			gl.genBuffers(1, &buffer);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
+			gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * m_spec.triangleCount * m_spec.drawCallCount, &(m_staticIndexData[0]), GL_STATIC_DRAW);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic index buffer failed");
+
+			m_batchedStaticIndexBuffer = buffer;
+		}
+
+		{
+			GLuint buffer;
+
+			gl.genBuffers(1, &buffer);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
+			gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * m_spec.triangleCount, &(m_staticIndexData[0]), GL_STATIC_DRAW);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic index buffer failed");
+
+			m_unbatchedStaticIndexBuffer = buffer;
+		}
+	}
+}
+
+void DrawCallBatchingTest::init (void)
+{
+	createShader();
+	createAttributeDatas();
+	createArrayBuffers();
+
+	if (m_spec.useDrawElements)
+	{
+		createIndexData();
+
+		if (m_spec.useIndexBuffer)
+			createIndexBuffer();
+	}
+}
+
+void DrawCallBatchingTest::deinit (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	delete m_program;
+	m_program = NULL;
+
+	m_dynamicIndexData	= vector<deUint8>();
+	m_staticIndexData	= vector<deUint8>();
+
+	if (!m_unbatchedDynamicIndexBuffers.empty())
+	{
+		gl.deleteBuffers((GLsizei)m_unbatchedDynamicIndexBuffers.size(), &(m_unbatchedDynamicIndexBuffers[0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()");
+
+		m_unbatchedDynamicIndexBuffers = vector<GLuint>();
+	}
+
+	if (m_batchedDynamicIndexBuffer)
+	{
+		gl.deleteBuffers((GLsizei)1, &m_batchedDynamicIndexBuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()");
+
+		m_batchedDynamicIndexBuffer = 0;
+	}
+
+	if (m_unbatchedStaticIndexBuffer)
+	{
+		gl.deleteBuffers((GLsizei)1, &m_unbatchedStaticIndexBuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()");
+
+		m_unbatchedStaticIndexBuffer = 0;
+	}
+
+	if (m_batchedStaticIndexBuffer)
+	{
+		gl.deleteBuffers((GLsizei)1, &m_batchedStaticIndexBuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()");
+
+		m_batchedStaticIndexBuffer = 0;
+	}
+
+	m_staticAttributeDatas	= vector<vector<deInt8> >();
+	m_dynamicAttributeDatas	= vector<vector<deInt8> >();
+
+	if (!m_batchedStaticBuffers.empty())
+	{
+		gl.deleteBuffers((GLsizei)m_batchedStaticBuffers.size(), &(m_batchedStaticBuffers[0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()");
+
+		m_batchedStaticBuffers = vector<GLuint>();
+	}
+
+	if (!m_unbatchedStaticBuffers.empty())
+	{
+		gl.deleteBuffers((GLsizei)m_unbatchedStaticBuffers.size(), &(m_unbatchedStaticBuffers[0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()");
+
+		m_unbatchedStaticBuffers = vector<GLuint>();
+	}
+
+	if (!m_batchedDynamicBuffers.empty())
+	{
+		gl.deleteBuffers((GLsizei)m_batchedDynamicBuffers.size(), &(m_batchedDynamicBuffers[0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()");
+
+		m_batchedDynamicBuffers = vector<GLuint>();
+	}
+
+	for (int i = 0; i < (int)m_unbatchedDynamicBuffers.size(); i++)
+	{
+		gl.deleteBuffers((GLsizei)m_unbatchedDynamicBuffers[i].size(), &(m_unbatchedDynamicBuffers[i][0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()");
+	}
+
+	m_unbatchedDynamicBuffers = vector<vector<GLuint> >();
+
+	m_unbatchedSamplesUs	= vector<deUint64>();
+	m_batchedSamplesUs		= vector<deUint64>();
+}
+
+deUint64 DrawCallBatchingTest::renderUnbatched (void)
+{
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+	deUint64				beginUs	= 0;
+	deUint64				endUs	= 0;
+	vector<GLint>			dynamicAttributeLocations;
+
+	gl.viewport(0, 0, 32, 32);
+	gl.useProgram(m_program->getProgram());
+
+	// Setup static buffers
+	for (int attribNdx = 0; attribNdx < m_spec.staticAttributeCount; attribNdx++)
+	{
+		GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_static" + de::toString(attribNdx)).c_str());
+
+		gl.enableVertexAttribArray(location);
+
+		if (m_spec.useStaticBuffer)
+		{
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_unbatchedStaticBuffers[attribNdx]);
+			gl.vertexAttribPointer(location, 4, GL_BYTE, GL_TRUE, 0, NULL);
+			gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+		}
+		else
+			gl.vertexAttribPointer(location, 4, GL_BYTE, GL_TRUE, 0, &(m_staticAttributeDatas[attribNdx][0]));
+	}
+
+	// Get locations of dynamic attributes
+	for (int attribNdx = 0; attribNdx < m_spec.dynamicAttributeCount; attribNdx++)
+	{
+		GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_dyn" + de::toString(attribNdx)).c_str());
+
+		gl.enableVertexAttribArray(location);
+		dynamicAttributeLocations.push_back(location);
+	}
+
+	if (m_spec.useDrawElements && m_spec.useIndexBuffer && !m_spec.dynamicIndices)
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_unbatchedStaticIndexBuffer);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup initial state for rendering.");
+
+	gl.finish();
+
+	beginUs = deGetMicroseconds();
+
+	for (int drawNdx = 0; drawNdx < m_spec.drawCallCount; drawNdx++)
+	{
+		for (int attribNdx = 0; attribNdx < m_spec.dynamicAttributeCount; attribNdx++)
+		{
+			if (m_spec.useDynamicBuffer)
+			{
+				gl.bindBuffer(GL_ARRAY_BUFFER, m_unbatchedDynamicBuffers[attribNdx][drawNdx]);
+				gl.vertexAttribPointer(dynamicAttributeLocations[attribNdx], 4, GL_BYTE, GL_TRUE, 0, NULL);
+				gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+			}
+			else
+				gl.vertexAttribPointer(dynamicAttributeLocations[attribNdx], 4, GL_BYTE, GL_TRUE, 0, &(m_dynamicAttributeDatas[attribNdx][m_spec.triangleCount * 3 * drawNdx * 4]));
+		}
+
+		if (m_spec.useDrawElements)
+		{
+			if (m_spec.useIndexBuffer)
+			{
+				if (m_spec.dynamicIndices)
+				{
+					gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_unbatchedDynamicIndexBuffers[drawNdx]);
+					gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3, GL_UNSIGNED_BYTE, NULL);
+					gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+				}
+				else
+					gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3, GL_UNSIGNED_BYTE, NULL);
+			}
+			else
+			{
+				if (m_spec.dynamicIndices)
+					gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3, GL_UNSIGNED_BYTE, &(m_dynamicIndexData[drawNdx * m_spec.triangleCount * 3]));
+				else
+					gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3, GL_UNSIGNED_BYTE, &(m_staticIndexData[0]));
+			}
+		}
+		else
+			gl.drawArrays(GL_TRIANGLES, 0, 3 * m_spec.triangleCount);
+	}
+
+	gl.finish();
+
+	endUs = deGetMicroseconds();
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Unbatched rendering failed");
+
+	gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+	for (int attribNdx = 0; attribNdx < m_spec.staticAttributeCount; attribNdx++)
+	{
+		GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_static" + de::toString(attribNdx)).c_str());
+		gl.disableVertexAttribArray(location);
+	}
+
+	for (int attribNdx = 0; attribNdx < m_spec.dynamicAttributeCount; attribNdx++)
+		gl.disableVertexAttribArray(dynamicAttributeLocations[attribNdx]);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to reset state after unbatched rendering");
+
+	return endUs - beginUs;
+}
+
+deUint64 DrawCallBatchingTest::renderBatched (void)
+{
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+	deUint64				beginUs	= 0;
+	deUint64				endUs	= 0;
+	vector<GLint>			dynamicAttributeLocations;
+
+	gl.viewport(0, 0, 32, 32);
+	gl.useProgram(m_program->getProgram());
+
+	// Setup static buffers
+	for (int attribNdx = 0; attribNdx < m_spec.staticAttributeCount; attribNdx++)
+	{
+		GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_static" + de::toString(attribNdx)).c_str());
+
+		gl.enableVertexAttribArray(location);
+
+		if (m_spec.useStaticBuffer)
+		{
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_batchedStaticBuffers[attribNdx]);
+			gl.vertexAttribPointer(location, 4, GL_BYTE, GL_TRUE, 0, NULL);
+			gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+		}
+		else
+			gl.vertexAttribPointer(location, 4, GL_BYTE, GL_TRUE, 0, &(m_staticAttributeDatas[attribNdx][0]));
+	}
+
+	// Get locations of dynamic attributes
+	for (int attribNdx = 0; attribNdx < m_spec.dynamicAttributeCount; attribNdx++)
+	{
+		GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_dyn" + de::toString(attribNdx)).c_str());
+
+		gl.enableVertexAttribArray(location);
+		dynamicAttributeLocations.push_back(location);
+	}
+
+	if (m_spec.useDrawElements && m_spec.useIndexBuffer && !m_spec.dynamicIndices)
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_batchedStaticIndexBuffer);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup initial state for rendering.");
+
+	gl.finish();
+
+	beginUs = deGetMicroseconds();
+
+	for (int attribute = 0; attribute < m_spec.dynamicAttributeCount; attribute++)
+	{
+		if (m_spec.useDynamicBuffer)
+		{
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_batchedDynamicBuffers[attribute]);
+			gl.vertexAttribPointer(dynamicAttributeLocations[attribute], 4, GL_BYTE, GL_TRUE, 0, NULL);
+			gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+		}
+		else
+			gl.vertexAttribPointer(dynamicAttributeLocations[attribute], 4, GL_BYTE, GL_TRUE, 0, &(m_dynamicAttributeDatas[attribute][0]));
+	}
+
+	if (m_spec.useDrawElements)
+	{
+		if (m_spec.useIndexBuffer)
+		{
+			if (m_spec.dynamicIndices)
+			{
+				gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_batchedDynamicIndexBuffer);
+				gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3 * m_spec.drawCallCount, GL_UNSIGNED_BYTE, NULL);
+				gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+			}
+			else
+				gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3 * m_spec.drawCallCount, GL_UNSIGNED_BYTE, NULL);
+		}
+		else
+		{
+			if (m_spec.dynamicIndices)
+				gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3 * m_spec.drawCallCount, GL_UNSIGNED_BYTE, &(m_dynamicIndexData[0]));
+			else
+				gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3 * m_spec.drawCallCount, GL_UNSIGNED_BYTE, &(m_staticIndexData[0]));
+		}
+	}
+	else
+		gl.drawArrays(GL_TRIANGLES, 0, 3 * m_spec.triangleCount * m_spec.drawCallCount);
+
+	gl.finish();
+
+	endUs = deGetMicroseconds();
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Batched rendering failed");
+
+	gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+	for (int attribNdx = 0; attribNdx < m_spec.staticAttributeCount; attribNdx++)
+	{
+		GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_static" + de::toString(attribNdx)).c_str());
+		gl.disableVertexAttribArray(location);
+	}
+
+	for (int attribNdx = 0; attribNdx < m_spec.dynamicAttributeCount; attribNdx++)
+		gl.disableVertexAttribArray(dynamicAttributeLocations[attribNdx]);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to reset state after batched rendering");
+
+	return endUs - beginUs;
+}
+
+struct Statistics
+{
+	double mean;
+	double standardDeviation;
+	double standardErrorOfMean;
+};
+
+Statistics calculateStats (const vector<deUint64>& samples)
+{
+	double mean = 0.0;
+
+	for (int i = 0; i < (int)samples.size(); i++)
+		mean += (double)samples[i];
+
+	mean /= (double)samples.size();
+
+	double standardDeviation = 0.0;
+
+	for (int i = 0; i < (int)samples.size(); i++)
+	{
+		double x = (double)samples[i];
+		standardDeviation += (x - mean) * (x - mean);
+	}
+
+	standardDeviation /= (double)samples.size();
+	standardDeviation = std::sqrt(standardDeviation);
+
+	double standardErrorOfMean = standardDeviation / std::sqrt((double)samples.size());
+
+	Statistics stats;
+
+	stats.mean					= mean;
+	stats.standardDeviation		= standardDeviation;
+	stats.standardErrorOfMean	= standardErrorOfMean;
+
+	return stats;
+}
+
+void DrawCallBatchingTest::logTestInfo (void)
+{
+	TestLog&				log		= m_testCtx.getLog();
+	tcu::ScopedLogSection	section	(log, "Test info", "Test info");
+
+	log << TestLog::Message << "Rendering using " << (m_spec.useDrawElements ? "glDrawElements()" : "glDrawArrays()") << "." << TestLog::EndMessage;
+
+	if (m_spec.useDrawElements)
+		log << TestLog::Message << "Using " << (m_spec.dynamicIndices ? "dynamic " : "") << "indices from " << (m_spec.useIndexBuffer ? "buffer" : "pointer") << "." << TestLog::EndMessage;
+
+	if (m_spec.staticAttributeCount > 0)
+		log << TestLog::Message << "Using " << m_spec.staticAttributeCount << " static attribute" << (m_spec.staticAttributeCount > 1 ? "s" : "") << " from " << (m_spec.useStaticBuffer ? "buffer" : "pointer") << "." << TestLog::EndMessage;
+
+	if (m_spec.dynamicAttributeCount > 0)
+		log << TestLog::Message << "Using " << m_spec.dynamicAttributeCount << " dynamic attribute" << (m_spec.dynamicAttributeCount > 1 ? "s" : "") << " from " << (m_spec.useDynamicBuffer ? "buffer" : "pointer") << "." << TestLog::EndMessage;
+
+	log << TestLog::Message << "Rendering " << m_spec.drawCallCount << " draw calls with " << m_spec.triangleCount << " triangles per call." << TestLog::EndMessage;
+}
+
+tcu::TestCase::IterateResult DrawCallBatchingTest::iterate (void)
+{
+	if (m_state == STATE_LOG_INFO)
+	{
+		logTestInfo();
+		m_state = STATE_WARMUP_BATCHED;
+	}
+	else if (m_state == STATE_WARMUP_BATCHED)
+	{
+		renderBatched();
+		m_state = STATE_WARMUP_UNBATCHED;
+	}
+	else if (m_state == STATE_WARMUP_UNBATCHED)
+	{
+		renderUnbatched();
+		m_state = STATE_SAMPLE;
+	}
+	else if (m_state == STATE_SAMPLE)
+	{
+		if ((int)m_unbatchedSamplesUs.size() < m_unbatchedSampleCount && (m_unbatchedSamplesUs.size() / ((double)m_unbatchedSampleCount) < m_batchedSamplesUs.size() / ((double)m_batchedSampleCount) || (int)m_batchedSamplesUs.size() >= m_batchedSampleCount))
+			m_unbatchedSamplesUs.push_back(renderUnbatched());
+		else if ((int)m_batchedSamplesUs.size() < m_batchedSampleCount)
+			m_batchedSamplesUs.push_back(renderBatched());
+		else
+			m_state = STATE_CALC_CALIBRATION;
+	}
+	else if (m_state == STATE_CALC_CALIBRATION)
+	{
+		TestLog& log = m_testCtx.getLog();
+
+		tcu::ScopedLogSection	section(log, ("Sampling iteration " + de::toString(m_sampleIteration)).c_str(), ("Sampling iteration " + de::toString(m_sampleIteration)).c_str());
+		const double targetSEM	= 0.02;
+		const double limitSEM	= 0.025;
+
+		Statistics unbatchedStats	= calculateStats(m_unbatchedSamplesUs);
+		Statistics batchedStats		= calculateStats(m_batchedSamplesUs);
+
+		log << TestLog::Message << "Batched samples; Count: " << m_batchedSamplesUs.size() << ", Mean: " << batchedStats.mean << "us, Standard deviation: " << batchedStats.standardDeviation << "us, Standard error of mean: " << batchedStats.standardErrorOfMean << "us(" << (batchedStats.standardErrorOfMean/batchedStats.mean) << ")" << TestLog::EndMessage;
+		log << TestLog::Message << "Unbatched samples; Count: " << m_unbatchedSamplesUs.size() << ", Mean: " << unbatchedStats.mean << "us, Standard deviation: " << unbatchedStats.standardDeviation << "us, Standard error of mean: " << unbatchedStats.standardErrorOfMean << "us(" << (unbatchedStats.standardErrorOfMean/unbatchedStats.mean) << ")" << TestLog::EndMessage;
+
+		if (m_sampleIteration > 2 || (m_sampleIteration > 0 && (unbatchedStats.standardErrorOfMean/unbatchedStats.mean) + (batchedStats.standardErrorOfMean/batchedStats.mean) <= 2.0 * limitSEM))
+		{
+			if (m_sampleIteration > 2)
+				log << TestLog::Message << "Maximum iteration count reached." << TestLog::EndMessage;
+
+			log << TestLog::Message << "Standard errors in target range." << TestLog::EndMessage;
+			log << TestLog::Message << "Batched/Unbatched ratio: " << (batchedStats.mean / unbatchedStats.mean) << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)(batchedStats.mean/unbatchedStats.mean), 1).c_str());
+			return STOP;
+		}
+		else
+		{
+			if ((unbatchedStats.standardErrorOfMean/unbatchedStats.mean) > targetSEM)
+				log << TestLog::Message << "Unbatched standard error of mean outside of range." << TestLog::EndMessage;
+
+			if ((batchedStats.standardErrorOfMean/batchedStats.mean) > targetSEM)
+				log << TestLog::Message << "Batched standard error of mean outside of range." << TestLog::EndMessage;
+
+			if (unbatchedStats.standardDeviation > 0.0)
+			{
+				double x = (unbatchedStats.standardDeviation / unbatchedStats.mean) / targetSEM;
+				m_unbatchedSampleCount = std::max((int)m_unbatchedSamplesUs.size(), (int)(x * x));
+			}
+			else
+				m_unbatchedSampleCount = (int)m_unbatchedSamplesUs.size();
+
+			if (batchedStats.standardDeviation > 0.0)
+			{
+				double x = (batchedStats.standardDeviation / batchedStats.mean) / targetSEM;
+				m_batchedSampleCount = std::max((int)m_batchedSamplesUs.size(), (int)(x * x));
+			}
+			else
+				m_batchedSampleCount = (int)m_batchedSamplesUs.size();
+
+			m_batchedSamplesUs.clear();
+			m_unbatchedSamplesUs.clear();
+
+			m_sampleIteration++;
+			m_state = STATE_SAMPLE;
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+	return CONTINUE;
+}
+
+string specToName (const DrawCallBatchingTest::TestSpec& spec)
+{
+	std::ostringstream stream;
+
+	DE_ASSERT(!spec.useStaticBuffer || spec.staticAttributeCount > 0);
+	DE_ASSERT(!spec.useDynamicBuffer|| spec.dynamicAttributeCount > 0);
+
+	if (spec.staticAttributeCount > 0)
+		stream << spec.staticAttributeCount << "_static_";
+
+	if (spec.useStaticBuffer)
+		stream << (spec.staticAttributeCount == 1 ? "buffer_" : "buffers_");
+
+	if (spec.dynamicAttributeCount > 0)
+		stream << spec.dynamicAttributeCount << "_dynamic_";
+
+	if (spec.useDynamicBuffer)
+		stream << (spec.dynamicAttributeCount == 1 ? "buffer_" : "buffers_");
+
+	stream << spec.triangleCount << "_triangles";
+
+	return stream.str();
+}
+
+string specToDescrpition (const DrawCallBatchingTest::TestSpec& spec)
+{
+	DE_UNREF(spec);
+	return "Test performance of batched rendering against non-batched rendering.";
+}
+
+} // anonymous
+
+DrawCallBatchingTests::DrawCallBatchingTests (Context& context)
+	: TestCaseGroup(context, "draw_call_batching", "Draw call batching performance tests.")
+{
+}
+
+DrawCallBatchingTests::~DrawCallBatchingTests (void)
+{
+}
+
+void DrawCallBatchingTests::init (void)
+{
+	int drawCallCounts[] = {
+		10, 100
+	};
+
+	int triangleCounts[] = {
+		2, 10
+	};
+
+	int staticAttributeCounts[] = {
+		1, 0, 4, 8, 0
+	};
+
+	int dynamicAttributeCounts[] = {
+		0, 1, 4, 0, 8
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(staticAttributeCounts) == DE_LENGTH_OF_ARRAY(dynamicAttributeCounts));
+
+	for (int drawType = 0; drawType < 2; drawType++)
+	{
+		bool drawElements = (drawType == 1);
+
+		for (int indexBufferNdx = 0; indexBufferNdx < 2; indexBufferNdx++)
+		{
+			bool useIndexBuffer = (indexBufferNdx == 1);
+
+			if (useIndexBuffer && !drawElements)
+				continue;
+
+			for (int dynamicIndexNdx = 0; dynamicIndexNdx < 2; dynamicIndexNdx++)
+			{
+				bool dynamicIndices = (dynamicIndexNdx == 1);
+
+				if (dynamicIndices && !drawElements)
+					continue;
+
+				if (dynamicIndices && !useIndexBuffer)
+					continue;
+
+				TestCaseGroup* drawTypeGroup = new TestCaseGroup(m_context, (string(dynamicIndices ? "dynamic_" : "") + (useIndexBuffer ? "buffer_" : "" ) + (drawElements ? "draw_elements" : "draw_arrays")).c_str(), (string("Test batched rendering with ") + (drawElements ? "draw_elements" : "draw_arrays")).c_str());
+
+				addChild(drawTypeGroup);
+
+				for (int drawCallCountNdx = 0; drawCallCountNdx < DE_LENGTH_OF_ARRAY(drawCallCounts); drawCallCountNdx++)
+				{
+					int drawCallCount = drawCallCounts[drawCallCountNdx];
+
+					TestCaseGroup*	callCountGroup			= new TestCaseGroup(m_context, (de::toString(drawCallCount) + (drawCallCount == 1 ? "_draw" : "_draws")).c_str(), ("Test batched rendering performance with " + de::toString(drawCallCount) + " draw calls.").c_str());
+					TestCaseGroup*	attributeCount1Group	= new TestCaseGroup(m_context, "1_attribute", "Test draw call batching with 1 attribute.");
+					TestCaseGroup*	attributeCount8Group	= new TestCaseGroup(m_context, "8_attributes", "Test draw call batching with 8 attributes.");
+
+					callCountGroup->addChild(attributeCount1Group);
+					callCountGroup->addChild(attributeCount8Group);
+
+					drawTypeGroup->addChild(callCountGroup);
+
+					for (int attributeCountNdx = 0; attributeCountNdx < DE_LENGTH_OF_ARRAY(dynamicAttributeCounts); attributeCountNdx++)
+					{
+						TestCaseGroup*	attributeCountGroup		= NULL;
+
+						int				staticAttributeCount	= staticAttributeCounts[attributeCountNdx];
+						int				dynamicAttributeCount	= dynamicAttributeCounts[attributeCountNdx];
+
+						if (staticAttributeCount + dynamicAttributeCount == 1)
+							attributeCountGroup = attributeCount1Group;
+						else if (staticAttributeCount + dynamicAttributeCount == 8)
+							attributeCountGroup = attributeCount8Group;
+						else
+							DE_ASSERT(false);
+
+						for (int triangleCountNdx = 0; triangleCountNdx < DE_LENGTH_OF_ARRAY(triangleCounts); triangleCountNdx++)
+						{
+							int triangleCount = triangleCounts[triangleCountNdx];
+
+							for (int dynamicBufferNdx = 0; dynamicBufferNdx < 2; dynamicBufferNdx++)
+							{
+								bool useDynamicBuffer = (dynamicBufferNdx != 0);
+
+								for (int staticBufferNdx = 0; staticBufferNdx < 2; staticBufferNdx++)
+								{
+									bool useStaticBuffer = (staticBufferNdx != 0);
+
+									DrawCallBatchingTest::TestSpec spec;
+
+									spec.useStaticBuffer		= useStaticBuffer;
+									spec.staticAttributeCount	= staticAttributeCount;
+
+									spec.useDynamicBuffer		= useDynamicBuffer;
+									spec.dynamicAttributeCount	= dynamicAttributeCount;
+
+									spec.drawCallCount			= drawCallCount;
+									spec.triangleCount			= triangleCount;
+
+									spec.useDrawElements		= drawElements;
+									spec.useIndexBuffer			= useIndexBuffer;
+									spec.dynamicIndices			= dynamicIndices;
+
+									if (spec.useStaticBuffer && spec.staticAttributeCount == 0)
+										continue;
+
+									if (spec.useDynamicBuffer && spec.dynamicAttributeCount == 0)
+										continue;
+
+									attributeCountGroup->addChild(new DrawCallBatchingTest(m_context, specToName(spec).c_str(), specToDescrpition(spec).c_str(), spec));
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pDrawCallBatchingTests.hpp b/modules/gles2/performance/es2pDrawCallBatchingTests.hpp
new file mode 100644
index 0000000..9b20bf4
--- /dev/null
+++ b/modules/gles2/performance/es2pDrawCallBatchingTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PDRAWCALLBATCHINGTESTS_HPP
+#define _ES2PDRAWCALLBATCHINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Draw call batching performance tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class DrawCallBatchingTests : public TestCaseGroup
+{
+public:
+							DrawCallBatchingTests	(Context& context);
+							~DrawCallBatchingTests	(void);
+
+	void					init					(void);
+
+private:
+							DrawCallBatchingTests	(const DrawCallBatchingTests& other);
+	DrawCallBatchingTests&	operator=				(const DrawCallBatchingTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PDRAWCALLBATCHINGTESTS_HPP
diff --git a/modules/gles2/performance/es2pPerformanceTests.cpp b/modules/gles2/performance/es2pPerformanceTests.cpp
new file mode 100644
index 0000000..3752853
--- /dev/null
+++ b/modules/gles2/performance/es2pPerformanceTests.cpp
@@ -0,0 +1,124 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pPerformanceTests.hpp"
+
+#include "es2pBlendTests.hpp"
+#include "es2pTextureFormatTests.hpp"
+#include "es2pTextureFilteringTests.hpp"
+#include "es2pTextureCountTests.hpp"
+#include "es2pShaderOperatorTests.hpp"
+#include "es2pShaderControlStatementTests.hpp"
+#include "es2pShaderCompilerTests.hpp"
+#include "es2pTextureUploadTests.hpp"
+#include "es2pStateChangeCallTests.hpp"
+#include "es2pStateChangeTests.hpp"
+#include "es2pRedundantStateChangeTests.hpp"
+#include "es2pDrawCallBatchingTests.hpp"
+#include "es2pShaderOptimizationTests.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+// TextureTestGroup
+
+class TextureTestGroup : public TestCaseGroup
+{
+public:
+	TextureTestGroup (Context& context)
+		: TestCaseGroup(context, "texture", "Texture Performance Tests")
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new TextureFormatTests		(m_context));
+		addChild(new TextureFilteringTests	(m_context));
+		addChild(new TextureCountTests		(m_context));
+		addChild(new TextureUploadTests		(m_context));
+	}
+};
+
+// ShadersTestGroup
+
+class ShadersTestGroup : public TestCaseGroup
+{
+public:
+	ShadersTestGroup (Context& context)
+		: TestCaseGroup(context, "shader", "Shader Performance Tests")
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new ShaderOperatorTests			(m_context));
+		addChild(new ShaderControlStatementTests	(m_context));
+	}
+};
+
+// APITestGroup
+
+class APITestGroup : public TestCaseGroup
+{
+public:
+	APITestGroup (Context& context)
+		: TestCaseGroup(context, "api", "API Performance Tests")
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new StateChangeCallTests		(m_context));
+		addChild(new StateChangeTests			(m_context));
+		addChild(new RedundantStateChangeTests	(m_context));
+		addChild(new DrawCallBatchingTests		(m_context));
+	}
+};
+
+// PerformanceTests
+
+PerformanceTests::PerformanceTests (Context& context)
+	: TestCaseGroup(context, "performance", "Performance Tests")
+{
+}
+
+PerformanceTests::~PerformanceTests (void)
+{
+}
+
+void PerformanceTests::init (void)
+{
+	addChild(new BlendTests					(m_context));
+	addChild(new TextureTestGroup			(m_context));
+	addChild(new ShadersTestGroup			(m_context));
+	addChild(new ShaderCompilerTests		(m_context));
+	addChild(new APITestGroup				(m_context));
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pPerformanceTests.hpp b/modules/gles2/performance/es2pPerformanceTests.hpp
new file mode 100644
index 0000000..b66869d
--- /dev/null
+++ b/modules/gles2/performance/es2pPerformanceTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PPERFORMANCETESTS_HPP
+#define _ES2PPERFORMANCETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class PerformanceTests : public TestCaseGroup
+{
+public:
+						PerformanceTests	(Context& context);
+						~PerformanceTests	(void);
+
+	void				init				(void);
+
+private:
+						PerformanceTests	(const PerformanceTests& other);
+	PerformanceTests&	operator=			(const PerformanceTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PPERFORMANCETESTS_HPP
diff --git a/modules/gles2/performance/es2pRedundantStateChangeTests.cpp b/modules/gles2/performance/es2pRedundantStateChangeTests.cpp
new file mode 100644
index 0000000..9add7dc
--- /dev/null
+++ b/modules/gles2/performance/es2pRedundantStateChangeTests.cpp
@@ -0,0 +1,1352 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Redundant state change performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pRedundantStateChangeTests.hpp"
+#include "glsStateChangePerfTestCases.hpp"
+#include "gluShaderProgram.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+using namespace glw; // GL types
+
+namespace
+{
+
+enum
+{
+	VIEWPORT_WIDTH	= 24,
+	VIEWPORT_HEIGHT	= 24
+};
+
+class RedundantStateChangeCase : public gls::StateChangePerformanceCase
+{
+public:
+					RedundantStateChangeCase	(Context& context, int drawCallCount, int triangleCount, bool drawArrays, bool useIndexBuffer, const char* name, const char* description);
+					~RedundantStateChangeCase	(void);
+
+protected:
+	virtual void	renderTest					(const glw::Functions& gl);
+	virtual void	renderReference				(const glw::Functions& gl);
+	virtual void	changeState					(const glw::Functions& gl) = 0;
+};
+
+RedundantStateChangeCase::RedundantStateChangeCase (Context& context, int drawCallCount, int triangleCount, bool drawArrays, bool useIndexBuffer, const char* name, const char* description)
+	: gls::StateChangePerformanceCase(context.getTestContext(), context.getRenderContext(), name, description,
+									  (useIndexBuffer	? DRAWTYPE_INDEXED_BUFFER	:
+									   drawArrays		? DRAWTYPE_NOT_INDEXED		:
+														  DRAWTYPE_INDEXED_USER_PTR), drawCallCount, triangleCount)
+{
+	DE_ASSERT(!useIndexBuffer || !drawArrays);
+}
+
+RedundantStateChangeCase::~RedundantStateChangeCase (void)
+{
+}
+
+void RedundantStateChangeCase::renderTest (const glw::Functions& gl)
+{
+	for (int callNdx = 0; callNdx < m_callCount; callNdx++)
+	{
+		changeState(gl);
+		callDraw(gl);
+	}
+}
+
+void RedundantStateChangeCase::renderReference (const glw::Functions& gl)
+{
+	changeState(gl);
+
+	for (int callNdx = 0; callNdx < m_callCount; callNdx++)
+		callDraw(gl);
+}
+
+} // anonymous
+
+RedundantStateChangeTests::RedundantStateChangeTests (Context& context)
+	: TestCaseGroup(context, "redundant_state_change_draw", "Test performance with redundant sate changes between rendering.")
+{
+}
+
+RedundantStateChangeTests::~RedundantStateChangeTests (void)
+{
+}
+
+#define MACRO_BLOCK(...) __VA_ARGS__
+
+#define ADD_TESTCASE(NAME, DESC, DRAWARRAYS, INDEXBUFFER, INIT_FUNC, CHANGE_FUNC)\
+do {\
+	class RedundantStateChangeCase_ ## NAME : public RedundantStateChangeCase\
+	{\
+	public:\
+			RedundantStateChangeCase_ ## NAME (Context& context, int drawCallCount, int triangleCount, const char* name, const char* description)\
+				: RedundantStateChangeCase(context, drawCallCount, triangleCount, (DRAWARRAYS), (INDEXBUFFER), name, description)\
+			{}\
+		virtual void setupInitialState (const glw::Functions& gl)\
+		{\
+			INIT_FUNC\
+		}\
+		virtual void changeState (const glw::Functions& gl)\
+		{\
+			CHANGE_FUNC\
+		}\
+	};\
+	manySmallCallsGroup->addChild	(new RedundantStateChangeCase_ ## NAME (m_context,1000,2,#NAME,(DESC)));\
+	fewBigCallsGroup->addChild		(new RedundantStateChangeCase_ ## NAME (m_context,10,200,#NAME,(DESC)));\
+} while (0);
+
+void RedundantStateChangeTests::init (void)
+{
+	tcu::TestCaseGroup* const	manySmallCallsGroup	= new tcu::TestCaseGroup(m_testCtx, "many_small_calls",	"1000 calls, 2 triangles in each");
+	tcu::TestCaseGroup* const	fewBigCallsGroup	= new tcu::TestCaseGroup(m_testCtx, "few_big_calls",	"10 calls, 200 triangles in each");
+
+	addChild(manySmallCallsGroup);
+	addChild(fewBigCallsGroup);
+
+	ADD_TESTCASE(blend, "Enable/Disable blending.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_BLEND);
+		})
+	)
+
+	ADD_TESTCASE(depth_test, "Enable/Disable depth test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.depthFunc(GL_LEQUAL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthFunc()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_DEPTH_TEST);
+		})
+	)
+
+	ADD_TESTCASE(stencil_test, "Enable/Disable stencil test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_STENCIL_TEST);
+		})
+	)
+
+	ADD_TESTCASE(scissor_test, "Enable/Disable scissor test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.scissor(2, 3, 12, 13);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glScissor()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_SCISSOR_TEST);
+		})
+	)
+
+	ADD_TESTCASE(dither, "Enable/Disable dithering.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_DITHER);
+		})
+	)
+
+	ADD_TESTCASE(culling, "Enable/Disable culling.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.frontFace(GL_CW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glFrontFace()");
+
+			gl.cullFace(GL_FRONT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glCullFace()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_CULL_FACE);
+		})
+	)
+
+	ADD_TESTCASE(depth_func, "Change depth func.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_DEPTH_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.depthFunc(GL_GEQUAL);
+		})
+	)
+
+
+	ADD_TESTCASE(depth_mask, "Toggle depth mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_DEPTH_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.depthFunc(GL_LEQUAL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthFunc()");
+		}),
+		MACRO_BLOCK({
+			gl.depthMask(GL_FALSE);
+		})
+	)
+
+	ADD_TESTCASE(depth_rangef, "Change depth range.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.depthRangef(0.0f, 1.0f);
+		})
+	)
+
+	ADD_TESTCASE(blend_equation, "Change blend equation.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.blendEquation(GL_FUNC_SUBTRACT);
+		})
+	)
+
+	ADD_TESTCASE(blend_func, "Change blend function.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+		})
+	)
+
+	ADD_TESTCASE(polygon_offset, "Change polygon offset.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_POLYGON_OFFSET_FILL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.polygonOffset(0.0f, 0.0f);
+		})
+	)
+
+	ADD_TESTCASE(sample_coverage, "Sample coverage.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.sampleCoverage(0.25f, GL_TRUE);
+		})
+	)
+
+	ADD_TESTCASE(viewport, "Change viewport.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.viewport(10, 11, 5, 6);
+		})
+	)
+
+	ADD_TESTCASE(scissor, "Change scissor box.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_SCISSOR_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.scissor(17, 13, 5, 8);
+		})
+	)
+
+	ADD_TESTCASE(color_mask, "Change color mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.colorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
+		})
+	)
+
+	ADD_TESTCASE(cull_face, "Change culling mode.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_CULL_FACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.cullFace(GL_FRONT);
+		})
+	)
+
+	ADD_TESTCASE(front_face, "Change front face.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_CULL_FACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.frontFace(GL_CCW);
+		})
+	)
+
+	ADD_TESTCASE(stencil_mask, "Change stencil mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			gl.stencilMask(0xDD);
+		})
+	)
+
+	ADD_TESTCASE(stencil_func, "Change stencil func.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			gl.stencilFunc(GL_LEQUAL, 0, 0xFF);
+		})
+	)
+
+	ADD_TESTCASE(stencil_op, "Change stencil op.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+		})
+	)
+
+	ADD_TESTCASE(bind_array_buffer, "Change array buffer and refresh vertex attrib pointer.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.bindAttribLocation(m_programs[0]->getProgram(), 0, "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindAttribLocation()"); 
+			gl.linkProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glLinkProgram()");
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+			gl.enableVertexAttribArray(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+		})
+	)
+
+	ADD_TESTCASE(element_array_buffer, "Change element array buffer.",
+		false,
+		true,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireIndexBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+		}),
+		MACRO_BLOCK({
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffers[0]);
+		})
+	)
+
+	ADD_TESTCASE(bind_texture, "Change texture binding.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+		})
+	)
+
+	ADD_TESTCASE(use_program, "Change used program.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.useProgram(m_programs[0]->getProgram());
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_min_filter, "Change texture parameter min filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_mag_filter, "Change texture parameter mag filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_wrap, "Change texture parameter wrap filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+		})
+	)
+
+	ADD_TESTCASE(bind_framebuffer, "Change framebuffer.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requireFramebuffers(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()");
+		}),
+		MACRO_BLOCK({
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffers[0]);
+		})
+	)
+
+	ADD_TESTCASE(blend_color, "Change blend color.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.blendFunc(GL_CONSTANT_COLOR, GL_CONSTANT_COLOR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBlendFunc()");
+		}),
+		MACRO_BLOCK({
+			gl.blendColor(0.75f, 0.75f, 0.75f, 0.75f);
+		})
+	)
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pRedundantStateChangeTests.hpp b/modules/gles2/performance/es2pRedundantStateChangeTests.hpp
new file mode 100644
index 0000000..bf14989
--- /dev/null
+++ b/modules/gles2/performance/es2pRedundantStateChangeTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PREDUNDANTSTATECHANGETESTS_HPP
+#define _ES2PREDUNDANTSTATECHANGETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Redundant state change performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class RedundantStateChangeTests : public TestCaseGroup
+{
+public:
+								RedundantStateChangeTests	(Context& context);
+								~RedundantStateChangeTests	(void);
+
+	void						init						(void);
+
+private:
+								RedundantStateChangeTests	(const RedundantStateChangeTests& other);
+	RedundantStateChangeTests&	operator=					(const RedundantStateChangeTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PREDUNDANTSTATECHANGETESTS_HPP
diff --git a/modules/gles2/performance/es2pShaderCompilationCases.cpp b/modules/gles2/performance/es2pShaderCompilationCases.cpp
new file mode 100644
index 0000000..7b3d436
--- /dev/null
+++ b/modules/gles2/performance/es2pShaderCompilationCases.cpp
@@ -0,0 +1,3132 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader compilation performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pShaderCompilationCases.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuPlatform.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuCPUWarmup.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluTexture.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deClock.h"
+#include "deMath.h"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <map>
+#include <algorithm>
+#include <limits>
+#include <iomanip>
+
+using tcu::TestLog;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::Mat3;
+using tcu::Mat4;
+using std::string;
+using std::vector;
+using namespace glw; // GL types
+
+namespace deqp
+{
+
+namespace gles2
+{
+
+namespace Performance
+{
+
+static const bool	WARMUP_CPU_AT_BEGINNING_OF_CASE					= false;
+static const bool	WARMUP_CPU_BEFORE_EACH_MEASUREMENT				= true;
+
+static const int	MAX_VIEWPORT_WIDTH								= 64;
+static const int	MAX_VIEWPORT_HEIGHT								= 64;
+
+static const int	DEFAULT_MINIMUM_MEASUREMENT_COUNT				= 15;
+static const float	RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD	= 0.05f;
+
+// Texture size for the light shader and texture lookup shader cases.
+static const int	TEXTURE_WIDTH									= 64;
+static const int	TEXTURE_HEIGHT									= 64;
+
+template <typename T>
+inline string toStringWithPadding (T value, int minLength)
+{
+	std::ostringstream s;
+	s << std::setfill('0') << std::setw(minLength) << value;
+	return s.str();
+}
+
+// Add some whitespace and comments to str. They should depend on uniqueNumber.
+static string strWithWhiteSpaceAndComments (const string& str, deUint32 uniqueNumber)
+{
+	string res("");
+
+	// Find the first newline.
+	int firstLineEndNdx = 0;
+	while (firstLineEndNdx < (int)str.size() && str[firstLineEndNdx] != '\n')
+	{
+		res += str[firstLineEndNdx];
+		firstLineEndNdx++;
+	}
+	res += '\n';
+	DE_ASSERT(firstLineEndNdx < (int)str.size());
+
+	// Add the whitespaces and comments just after the first line.
+
+	de::Random		rnd		(uniqueNumber);
+	int				numWS	= rnd.getInt(10, 20);
+
+	for (int i = 0; i < numWS; i++)
+		res += " \t\n"[rnd.getInt(0, 2)];
+
+	res += "/* unique comment " + de::toString(uniqueNumber) + " */\n";
+	res += "// unique comment " + de::toString(uniqueNumber) + "\n";
+
+	for (int i = 0; i < numWS; i++)
+		res += " \t\n"[rnd.getInt(0, 2)];
+
+	// Add the rest of the string.
+	res.append(&str.c_str()[firstLineEndNdx + 1]);
+
+	return res;
+}
+
+//! Helper for computing relative magnitudes while avoiding division by zero.
+static float hackySafeRelativeResult (float x, float y)
+{
+	// \note A possible case is that x is standard deviation, and y is average
+	//		 (or similarly for median or some such). So, if y is 0, that
+	//		 probably means that x is also 0(ish) (because in practice we're
+	//		 dealing with non-negative values, in which case an average of 0
+	//		 implies that the samples are all 0 - note that the same isn't
+	//		 strictly true for things like median) so a relative result of 0
+	//		 wouldn't be that far from the truth.
+	return y == 0.0f ? 0.0f : x/y;
+}
+
+template <typename T>
+static float vectorFloatAverage (const vector<T>& v)
+{
+	DE_ASSERT(!v.empty());
+	float result = 0.0f;
+	for (int i = 0; i < (int)v.size(); i++)
+		result += (float)v[i];
+	return result / (float)v.size();
+}
+
+template <typename T>
+static float vectorFloatMedian (const vector<T>& v)
+{
+	DE_ASSERT(!v.empty());
+	vector<T> temp = v;
+	std::sort(temp.begin(), temp.end());
+	return temp.size() % 2 == 0
+		   ? 0.5f * ((float)temp[temp.size()/2-1] + (float)temp[temp.size()/2])
+		   : (float)temp[temp.size()/2];
+}
+
+template <typename T>
+static float vectorFloatMinimum (const vector<T>& v)
+{
+	DE_ASSERT(!v.empty());
+	return (float)*std::min_element(v.begin(), v.end());
+}
+
+template <typename T>
+static float vectorFloatMaximum (const vector<T>& v)
+{
+	DE_ASSERT(!v.empty());
+	return (float)*std::max_element(v.begin(), v.end());
+}
+
+template <typename T>
+static float vectorFloatStandardDeviation (const vector<T>& v)
+{
+	float average	= vectorFloatAverage(v);
+	float result	= 0.0f;
+	for (int i = 0; i < (int)v.size(); i++)
+	{
+		float d = (float)v[i] - average;
+		result += d*d;
+	}
+	return deFloatSqrt(result/(float)v.size());
+}
+
+template <typename T>
+static float vectorFloatRelativeStandardDeviation (const vector<T>& v)
+{
+	return hackySafeRelativeResult(vectorFloatStandardDeviation(v), vectorFloatAverage(v));
+}
+
+template <typename T>
+static float vectorFloatMedianAbsoluteDeviation (const vector<T>& v)
+{
+	float			median				= vectorFloatMedian(v);
+	vector<float>	absoluteDeviations	(v.size());
+
+	for (int i = 0; i < (int)v.size(); i++)
+		absoluteDeviations[i] = deFloatAbs((float)v[i] - median);
+
+	return vectorFloatMedian(absoluteDeviations);
+}
+
+template <typename T>
+static float vectorFloatRelativeMedianAbsoluteDeviation (const vector<T>& v)
+{
+	return hackySafeRelativeResult(vectorFloatMedianAbsoluteDeviation(v), vectorFloatMedian(v));
+}
+
+template <typename T>
+static float vectorFloatMaximumMinusMinimum (const vector<T>& v)
+{
+	return vectorFloatMaximum(v) - vectorFloatMinimum(v);
+}
+
+template <typename T>
+static float vectorFloatRelativeMaximumMinusMinimum (const vector<T>& v)
+{
+	return hackySafeRelativeResult(vectorFloatMaximumMinusMinimum(v), vectorFloatMaximum(v));
+}
+
+template <typename T>
+static vector<T> vectorLowestPercentage (const vector<T>& v, float factor)
+{
+	DE_ASSERT(0.0f < factor && factor <= 1.0f);
+
+	int			targetSize	= (int)(deFloatCeil(factor*(float)v.size()));
+	vector<T>	temp		= v;
+	std::sort(temp.begin(), temp.end());
+
+	while ((int)temp.size() > targetSize)
+		temp.pop_back();
+
+	return temp;
+}
+
+template <typename T>
+static float vectorFloatFirstQuartile (const vector<T>& v)
+{
+	return vectorFloatMedian(vectorLowestPercentage(v, 0.5f));
+}
+
+// Helper function for combining 4 tcu::Vec4's into one tcu::Vector<float, 16>.
+static tcu::Vector<float, 16> combineVec4ToVec16 (const Vec4& a0, const Vec4& a1, const Vec4& a2, const Vec4& a3)
+{
+	tcu::Vector<float, 16> result;
+
+	for (int vecNdx = 0; vecNdx < 4; vecNdx++)
+	{
+		const Vec4& srcVec = vecNdx == 0 ? a0 : vecNdx == 1 ? a1 : vecNdx == 2 ? a2 : a3;
+		for (int i = 0; i < 4; i++)
+			result[vecNdx*4 + i] = srcVec[i];
+	}
+
+	return result;
+}
+
+// Helper function for extending an n-sized (n <= 16) vector to a 16-sized vector (padded with zeros).
+template <int Size>
+static tcu::Vector<float, 16> vecTo16 (const tcu::Vector<float, Size>& vec)
+{
+	DE_STATIC_ASSERT(Size <= 16);
+
+	tcu::Vector<float, 16> res(0.0f);
+
+	for (int i = 0; i < Size; i++)
+		res[i] = vec[i];
+
+	return res;
+}
+
+// Helper function for extending an n-sized (n <= 16) array to a 16-sized vector (padded with zeros).
+template <int Size>
+static tcu::Vector<float, 16> arrTo16 (const tcu::Array<float, Size>& arr)
+{
+	DE_STATIC_ASSERT(Size <= 16);
+
+	tcu::Vector<float, 16> res(0.0f);
+
+	for(int i = 0; i < Size; i++)
+		res[i] = arr[i];
+
+	return res;
+}
+
+static string getShaderInfoLog (const glw::Functions& gl, deUint32 shader)
+{
+	string			result;
+	int				infoLogLen;
+	vector<char>	infoLogBuf;
+
+	gl.getShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen);
+	infoLogBuf.resize(infoLogLen + 1);
+	gl.getShaderInfoLog(shader, infoLogLen + 1, DE_NULL, &infoLogBuf[0]);
+	result = &infoLogBuf[0];
+
+	return result;
+}
+
+static string getProgramInfoLog (const glw::Functions& gl, deUint32 program)
+{
+	string			result;
+	int				infoLogLen;
+	vector<char>	infoLogBuf;
+
+	gl.getProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
+	infoLogBuf.resize(infoLogLen + 1);
+	gl.getProgramInfoLog(program, infoLogLen + 1, DE_NULL, &infoLogBuf[0]);
+	result = &infoLogBuf[0];
+
+	return result;
+}
+
+enum LightType
+{
+	LIGHT_DIRECTIONAL = 0,
+	LIGHT_POINT,
+
+	LIGHT_LAST,
+};
+
+enum LoopType
+{
+	LOOP_TYPE_STATIC = 0,
+	LOOP_TYPE_UNIFORM,
+	LOOP_TYPE_DYNAMIC,
+
+	LOOP_LAST
+};
+
+// For texture lookup cases: which texture lookups are inside a conditional statement.
+enum ConditionalUsage
+{
+	CONDITIONAL_USAGE_NONE = 0,		// No conditional statements.
+	CONDITIONAL_USAGE_FIRST_HALF,	// First numLookUps/2 lookups are inside a conditional statement.
+	CONDITIONAL_USAGE_EVERY_OTHER,	// First, third etc. lookups are inside conditional statements.
+
+	CONDITIONAL_USAGE_LAST
+};
+
+enum ConditionalType
+{
+	CONDITIONAL_TYPE_STATIC = 0,
+	CONDITIONAL_TYPE_UNIFORM,
+	CONDITIONAL_TYPE_DYNAMIC,
+
+	CONDITIONAL_TYPE_LAST
+};
+
+// For the invalid shader compilation tests; what kind of invalidity a shader shall contain.
+enum ShaderValidity
+{
+	SHADER_VALIDITY_VALID = 0,
+	SHADER_VALIDITY_INVALID_CHAR,
+	SHADER_VALIDITY_SEMANTIC_ERROR,
+
+	SHADER_VALIDITY_LAST
+};
+
+class ShaderCompilerCase : public TestCase
+{
+public:
+	struct AttribSpec
+	{
+		string					name;
+		tcu::Vector<float, 16>	value;
+
+		AttribSpec (const string& n, const tcu::Vector<float, 16>& v) : name(n), value(v) {}
+	};
+
+	struct UniformSpec
+	{
+		enum Type
+		{
+			TYPE_FLOAT = 0,
+			TYPE_VEC2,
+			TYPE_VEC3,
+			TYPE_VEC4,
+
+			TYPE_MAT3,
+			TYPE_MAT4,
+
+			TYPE_TEXTURE_UNIT,
+
+			TYPE_LAST
+		};
+
+		string					name;
+		Type					type;
+		tcu::Vector<float, 16>	value;
+
+		UniformSpec (const string& n, Type t, float v)							: name(n), type(t), value(v) {}
+		UniformSpec (const string& n, Type t, const tcu::Vector<float, 16>& v)	: name(n), type(t), value(v) {}
+	};
+
+								ShaderCompilerCase		(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments);
+								~ShaderCompilerCase		(void);
+
+	void						init					(void);
+
+	IterateResult				iterate					(void);
+
+protected:
+	struct ProgramContext
+	{
+		string					vertShaderSource;
+		string					fragShaderSource;
+		vector<AttribSpec>		vertexAttributes;
+		vector<UniformSpec>		uniforms;
+	};
+
+	deUint32					getSpecializationID		(int measurementNdx) const;		// Return an ID that depends on the case ID, current measurement index and time; used to specialize attribute names etc. (avoid shader caching).
+	virtual ProgramContext		generateShaderData		(int measurementNdx) const = 0;	// Generate shader sources and inputs. Attribute etc. names depend on above name specialization.
+
+private:
+	struct Measurement
+	{
+		// \note All times in microseconds. 32-bit integers would probably suffice (would need over an hour of test case runtime to overflow), but better safe than sorry.
+		deInt64 sourceSetTime;
+		deInt64 vertexCompileTime;
+		deInt64 fragmentCompileTime;
+		deInt64 programLinkTime;
+		deInt64 firstInputSetTime;
+		deInt64 firstDrawTime;
+
+		deInt64 secondInputSetTime;
+		deInt64 secondDrawTime;
+
+		deInt64 firstPhase				(void) const { return sourceSetTime + vertexCompileTime + fragmentCompileTime + programLinkTime + firstInputSetTime + firstDrawTime; }
+		deInt64 secondPhase				(void) const { return secondInputSetTime + secondDrawTime; }
+
+		deInt64 totalTimeWithoutDraw	(void) const { return firstPhase() - de::min(secondPhase(), firstInputSetTime + firstDrawTime); }
+
+		Measurement (deInt64 sourceSetTime_,
+					 deInt64 vertexCompileTime_,
+					 deInt64 fragmentCompileTime_,
+					 deInt64 programLinkTime_,
+					 deInt64 firstInputSetTime_,
+					 deInt64 firstDrawTime_,
+					 deInt64 secondInputSetTime_,
+					 deInt64 secondDrawTime_)
+			: sourceSetTime			(sourceSetTime_)
+			, vertexCompileTime		(vertexCompileTime_)
+			, fragmentCompileTime	(fragmentCompileTime_)
+			, programLinkTime		(programLinkTime_)
+			, firstInputSetTime		(firstInputSetTime_)
+			, firstDrawTime			(firstDrawTime_)
+			, secondInputSetTime	(secondInputSetTime_)
+			, secondDrawTime		(secondDrawTime_)
+		{
+		}
+	};
+
+	struct ShadersAndProgram
+	{
+		deUint32 vertShader;
+		deUint32 fragShader;
+		deUint32 program;
+	};
+
+	struct Logs
+	{
+		string vert;
+		string frag;
+		string link;
+	};
+
+	struct BuildInfo
+	{
+		bool vertCompileSuccess;
+		bool fragCompileSuccess;
+		bool linkSuccess;
+
+		Logs logs;
+	};
+
+	ShadersAndProgram			createShadersAndProgram		(void) const;
+	void						setShaderSources			(deUint32 vertShader, deUint32 fragShader, const ProgramContext&) const;
+	bool						compileShader				(deUint32 shader) const;
+	bool						linkAndUseProgram			(deUint32 program) const;
+	void						setShaderInputs				(deUint32 program, const ProgramContext&) const;							// Set attribute pointers and uniforms.
+	void						draw						(void) const;																// Clear, draw and finish.
+	void						cleanup						(const ShadersAndProgram&, const ProgramContext&, bool linkSuccess) const;	// Do GL deinitializations.
+
+	Logs						getLogs						(const ShadersAndProgram&) const;
+	void						logProgramData				(const BuildInfo&, const ProgramContext&) const;
+	bool						goodEnoughMeasurements		(const vector<Measurement>& measurements) const;
+
+	int							m_viewportWidth;
+	int							m_viewportHeight;
+
+	bool						m_avoidCache;				// If true, avoid caching between measurements as well (and not only between test cases).
+	bool						m_addWhitespaceAndComments;	// If true, add random whitespace and comments to the source (good caching should ignore those).
+	deUint32					m_startHash;				// A hash from case id and time, at the time of construction.
+
+	int							m_minimumMeasurementCount;
+	int							m_maximumMeasurementCount;
+};
+
+class ShaderCompilerLightCase : public ShaderCompilerCase
+{
+public:
+							ShaderCompilerLightCase		(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, int numLights, LightType lightType);
+							~ShaderCompilerLightCase	(void);
+
+	void					init						(void);
+	void					deinit						(void);
+
+protected:
+	ProgramContext			generateShaderData			(int measurementNdx) const;
+
+private:
+	int						m_numLights;
+	bool					m_isVertexCase;
+	LightType				m_lightType;
+	glu::Texture2D*			m_texture;
+};
+
+class ShaderCompilerTextureCase : public ShaderCompilerCase
+{
+public:
+									ShaderCompilerTextureCase	(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType);
+									~ShaderCompilerTextureCase	(void);
+
+	void							init						(void);
+	void							deinit						(void);
+
+protected:
+	ProgramContext					generateShaderData			(int measurementNdx) const;
+
+private:
+	int								m_numLookups;
+	vector<glu::Texture2D*>			m_textures;
+	ConditionalUsage				m_conditionalUsage;
+	ConditionalType					m_conditionalType;
+};
+
+class ShaderCompilerLoopCase : public ShaderCompilerCase
+{
+public:
+						ShaderCompilerLoopCase	(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, LoopType type, int numLoopIterations, int nestingDepth);
+						~ShaderCompilerLoopCase	(void);
+
+protected:
+	ProgramContext		generateShaderData		(int measurementNdx) const;
+
+private:
+	int					m_numLoopIterations;
+	int					m_nestingDepth;
+	bool				m_isVertexCase;
+	LoopType			m_type;
+};
+
+class ShaderCompilerOperCase : public ShaderCompilerCase
+{
+public:
+						ShaderCompilerOperCase	(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, const char* oper, int numOperations);
+						~ShaderCompilerOperCase	(void);
+
+protected:
+	ProgramContext		generateShaderData		(int measurementNdx) const;
+
+private:
+	string				m_oper;
+	int					m_numOperations;
+	bool				m_isVertexCase;
+};
+
+class ShaderCompilerMandelbrotCase : public ShaderCompilerCase
+{
+public:
+						ShaderCompilerMandelbrotCase	(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, int numFractalIterations);
+						~ShaderCompilerMandelbrotCase	(void);
+
+protected:
+	ProgramContext		generateShaderData				(int measurementNdx) const;
+
+private:
+	int					m_numFractalIterations;
+};
+
+class InvalidShaderCompilerCase : public TestCase
+{
+public:
+	// \note Similar to the ShaderValidity enum, but doesn't have a VALID type.
+	enum InvalidityType
+	{
+		INVALIDITY_INVALID_CHAR = 0,
+		INVALIDITY_SEMANTIC_ERROR,
+
+		INVALIDITY_LAST
+	};
+
+						InvalidShaderCompilerCase	(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType);
+						~InvalidShaderCompilerCase	(void);
+
+	IterateResult		iterate						(void);
+
+protected:
+	struct ProgramContext
+	{
+		string vertShaderSource;
+		string fragShaderSource;
+	};
+
+	deUint32				getSpecializationID		(int measurementNdx) const;			// Return an ID that depends on the case ID, current measurement index and time; used to specialize attribute names etc. (avoid shader caching).
+	virtual ProgramContext	generateShaderSources	(int measurementNdx) const = 0;		// Generate shader sources. Attribute etc. names depend on above name specialization.
+
+	InvalidityType			m_invalidityType;
+
+private:
+	struct Measurement
+	{
+		// \note All times in microseconds. 32-bit integers would probably suffice (would need over an hour of test case runtime to overflow), but better safe than sorry.
+		deInt64 sourceSetTime;
+		deInt64 vertexCompileTime;
+		deInt64 fragmentCompileTime;
+
+		deInt64 totalTime (void) const { return sourceSetTime + vertexCompileTime + fragmentCompileTime; }
+
+		Measurement (deInt64 sourceSetTime_,
+					 deInt64 vertexCompileTime_,
+					 deInt64 fragmentCompileTime_)
+			: sourceSetTime			(sourceSetTime_)
+			, vertexCompileTime		(vertexCompileTime_)
+			, fragmentCompileTime	(fragmentCompileTime_)
+		{
+		}
+	};
+
+	struct Shaders
+	{
+		deUint32 vertShader;
+		deUint32 fragShader;
+	};
+
+	struct Logs
+	{
+		string vert;
+		string frag;
+	};
+
+	struct BuildInfo
+	{
+		bool vertCompileSuccess;
+		bool fragCompileSuccess;
+
+		Logs logs;
+	};
+
+	Shaders						createShaders			(void) const;
+	void						setShaderSources		(const Shaders&, const ProgramContext&) const;
+	bool						compileShader			(deUint32 shader) const;
+	void						cleanup					(const Shaders&) const;
+
+	Logs						getLogs					(const Shaders&) const;
+	void						logProgramData			(const BuildInfo&, const ProgramContext&) const;
+	bool						goodEnoughMeasurements	(const vector<Measurement>& measurements) const;
+
+	deUint32					m_startHash; // A hash from case id and time, at the time of construction.
+
+	int							m_minimumMeasurementCount;
+	int							m_maximumMeasurementCount;
+};
+
+class InvalidShaderCompilerLightCase : public InvalidShaderCompilerCase
+{
+public:
+							InvalidShaderCompilerLightCase	(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool isVertexCase, int numLights, LightType lightType);
+							~InvalidShaderCompilerLightCase	(void);
+
+protected:
+	ProgramContext			generateShaderSources			(int measurementNdx) const;
+
+private:
+	bool					m_isVertexCase;
+	int						m_numLights;
+	LightType				m_lightType;
+};
+
+class InvalidShaderCompilerTextureCase : public InvalidShaderCompilerCase
+{
+public:
+							InvalidShaderCompilerTextureCase	(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType);
+							~InvalidShaderCompilerTextureCase	(void);
+
+protected:
+	ProgramContext			generateShaderSources				(int measurementNdx) const;
+
+private:
+	int						m_numLookups;
+	ConditionalUsage		m_conditionalUsage;
+	ConditionalType			m_conditionalType;
+};
+
+class InvalidShaderCompilerLoopCase : public InvalidShaderCompilerCase
+{
+public:
+						InvalidShaderCompilerLoopCase	(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool , LoopType type, int numLoopIterations, int nestingDepth);
+						~InvalidShaderCompilerLoopCase	(void);
+
+protected:
+	ProgramContext		generateShaderSources			(int measurementNdx) const;
+
+private:
+	bool				m_isVertexCase;
+	int					m_numLoopIterations;
+	int					m_nestingDepth;
+	LoopType			m_type;
+};
+
+class InvalidShaderCompilerOperCase : public InvalidShaderCompilerCase
+{
+public:
+						InvalidShaderCompilerOperCase	(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool isVertexCase, const char* oper, int numOperations);
+						~InvalidShaderCompilerOperCase	(void);
+
+protected:
+	ProgramContext		generateShaderSources			(int measurementNdx) const;
+
+private:
+	bool				m_isVertexCase;
+	string				m_oper;
+	int					m_numOperations;
+};
+
+class InvalidShaderCompilerMandelbrotCase : public InvalidShaderCompilerCase
+{
+public:
+						InvalidShaderCompilerMandelbrotCase		(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, int numFractalIterations);
+						~InvalidShaderCompilerMandelbrotCase	(void);
+
+protected:
+	ProgramContext		generateShaderSources					(int measurementNdx) const;
+
+private:
+	int					m_numFractalIterations;
+};
+
+static string getNameSpecialization (deUint32 id)
+{
+	return "_" + toStringWithPadding(id, 10);
+}
+
+// Substitute StringTemplate parameters for attribute/uniform/varying name and constant expression specialization as well as possible shader compilation error causes.
+static string specializeShaderSource (const string& shaderSourceTemplate, deUint32 cacheAvoidanceID, ShaderValidity validity)
+{
+	std::map<string, string> params;
+	params["NAME_SPEC"]			= getNameSpecialization(cacheAvoidanceID);
+	params["FLOAT01"]			= de::floatToString((float)cacheAvoidanceID / (float)(std::numeric_limits<deUint32>::max()), 6);
+	params["SEMANTIC_ERROR"]	= validity != SHADER_VALIDITY_SEMANTIC_ERROR	? "" : "\tmediump float invalid = sin(1.0, 2.0);\n";
+	params["INVALID_CHAR"]		= validity != SHADER_VALIDITY_INVALID_CHAR		? "" : "@\n"; // \note Some implementations crash when the invalid character is the last character in the source, so use newline.
+
+	return tcu::StringTemplate(shaderSourceTemplate).specialize(params);
+}
+
+// Function for generating the vertex shader of a (directional or point) light case.
+static string lightVertexTemplate (int numLights, bool isVertexCase, LightType lightType)
+{
+	string resultTemplate;
+
+	resultTemplate +=
+		"attribute highp vec4 a_position${NAME_SPEC};\n"
+		"attribute mediump vec3 a_normal${NAME_SPEC};\n"
+		"attribute mediump vec4 a_texCoord0${NAME_SPEC};\n"
+		"uniform mediump vec3 u_material_ambientColor${NAME_SPEC};\n"
+		"uniform mediump vec4 u_material_diffuseColor${NAME_SPEC};\n"
+		"uniform mediump vec3 u_material_emissiveColor${NAME_SPEC};\n"
+		"uniform mediump vec3 u_material_specularColor${NAME_SPEC};\n"
+		"uniform mediump float u_material_shininess${NAME_SPEC};\n";
+
+	for (int lightNdx = 0; lightNdx < numLights; lightNdx++)
+	{
+		string ndxStr = de::toString(lightNdx);
+
+		resultTemplate +=
+			"uniform mediump vec3 u_light" + ndxStr + "_color${NAME_SPEC};\n"
+			"uniform mediump vec3 u_light" + ndxStr + "_direction${NAME_SPEC};\n";
+
+		if (lightType == LIGHT_POINT)
+			resultTemplate +=
+				"uniform mediump vec4 u_light" + ndxStr + "_position${NAME_SPEC};\n"
+				"uniform mediump float u_light" + ndxStr + "_constantAttenuation${NAME_SPEC};\n"
+				"uniform mediump float u_light" + ndxStr + "_linearAttenuation${NAME_SPEC};\n"
+				"uniform mediump float u_light" + ndxStr + "_quadraticAttenuation${NAME_SPEC};\n";
+	}
+
+	resultTemplate +=
+		"uniform highp mat4 u_mvpMatrix${NAME_SPEC};\n"
+		"uniform highp mat4 u_modelViewMatrix${NAME_SPEC};\n"
+		"uniform mediump mat3 u_normalMatrix${NAME_SPEC};\n"
+		"uniform mediump mat4 u_texCoordMatrix0${NAME_SPEC};\n"
+		"varying mediump vec4 v_color${NAME_SPEC};\n"
+		"varying mediump vec2 v_texCoord0${NAME_SPEC};\n";
+
+	if (!isVertexCase)
+	{
+		resultTemplate += "varying mediump vec3 v_eyeNormal${NAME_SPEC};\n";
+
+		if (lightType == LIGHT_POINT)
+			resultTemplate +=
+				"varying mediump vec3 v_directionToLight${NAME_SPEC}[" + de::toString(numLights) + "];\n"
+				"varying mediump float v_distanceToLight${NAME_SPEC}[" + de::toString(numLights) + "];\n";
+	}
+
+	resultTemplate +=
+		"mediump vec3 direction (mediump vec4 from, mediump vec4 to)\n"
+		"{\n"
+		"	return vec3(to.xyz * from.w - from.xyz * to.w);\n"
+		"}\n"
+		"\n"
+		"mediump vec3 computeLighting (\n"
+		"	mediump vec3 directionToLight,\n"
+		"	mediump vec3 halfVector,\n"
+		"	mediump vec3 normal,\n"
+		"	mediump vec3 lightColor,\n"
+		"	mediump vec3 diffuseColor,\n"
+		"	mediump vec3 specularColor,\n"
+		"	mediump float shininess)\n"
+		"{\n"
+		"	mediump float normalDotDirection  = max(dot(normal, directionToLight), 0.0);\n"
+		"	mediump vec3  color               = normalDotDirection * diffuseColor * lightColor;\n"
+		"\n"
+		"	if (normalDotDirection != 0.0)\n"
+		"		color += pow(max(dot(normal, halfVector), 0.0), shininess) * specularColor * lightColor;\n"
+		"\n"
+		"	return color;\n"
+		"}\n"
+		"\n";
+
+	if (lightType == LIGHT_POINT)
+		resultTemplate +=
+			"mediump float computeDistanceAttenuation (mediump float distToLight, mediump float constAtt, mediump float linearAtt, mediump float quadraticAtt)\n"
+			"{\n"
+			"	return 1.0 / (constAtt + linearAtt * distToLight + quadraticAtt * distToLight * distToLight);\n"
+			"}\n"
+			"\n";
+
+	resultTemplate +=
+		"void main (void)\n"
+		"{\n"
+		"	highp vec4 position = a_position${NAME_SPEC};\n"
+		"	highp vec3 normal = a_normal${NAME_SPEC};\n"
+		"	gl_Position = u_mvpMatrix${NAME_SPEC} * position * (0.95 + 0.05*${FLOAT01});\n"
+		"	v_texCoord0${NAME_SPEC} = (u_texCoordMatrix0${NAME_SPEC} * a_texCoord0${NAME_SPEC}).xy;\n"
+		"	mediump vec4 color = vec4(u_material_emissiveColor${NAME_SPEC}, u_material_diffuseColor${NAME_SPEC}.a);\n"
+		"\n"
+		"	highp vec4 eyePosition = u_modelViewMatrix${NAME_SPEC} * position;\n"
+		"	mediump vec3 eyeNormal = normalize(u_normalMatrix${NAME_SPEC} * normal);\n";
+
+	if (!isVertexCase)
+		resultTemplate += "\tv_eyeNormal${NAME_SPEC} = eyeNormal;\n";
+
+	resultTemplate += "\n";
+
+	for (int lightNdx = 0; lightNdx < numLights; lightNdx++)
+	{
+		string ndxStr = de::toString(lightNdx);
+
+		resultTemplate +=
+			"	/* Light " + ndxStr + " */\n";
+
+		if (lightType == LIGHT_POINT)
+		{
+			resultTemplate +=
+				"	mediump float distanceToLight" + ndxStr + " = distance(eyePosition, u_light" + ndxStr + "_position${NAME_SPEC});\n"
+				"	mediump vec3 directionToLight" + ndxStr + " = normalize(direction(eyePosition, u_light" + ndxStr + "_position${NAME_SPEC}));\n";
+
+			if (isVertexCase)
+				resultTemplate +=
+					"	mediump vec3 halfVector" + ndxStr + " = normalize(directionToLight" + ndxStr + " + vec3(0.0, 0.0, 1.0));\n"
+					"	color.rgb += computeLighting(directionToLight" + ndxStr + ", halfVector" + ndxStr + ", eyeNormal, u_light" + ndxStr + "_color${NAME_SPEC}, u_material_diffuseColor${NAME_SPEC}.rgb, "
+					"u_material_specularColor${NAME_SPEC}, u_material_shininess${NAME_SPEC}) * computeDistanceAttenuation(distanceToLight" + ndxStr + ", u_light" + ndxStr + "_constantAttenuation${NAME_SPEC}, "
+					"u_light" + ndxStr + "_linearAttenuation${NAME_SPEC}, u_light" + ndxStr + "_quadraticAttenuation${NAME_SPEC});\n";
+			else
+				resultTemplate +=
+					"	v_directionToLight${NAME_SPEC}[" + ndxStr + "] = directionToLight" + ndxStr + ";\n"
+					"	v_distanceToLight${NAME_SPEC}[" + ndxStr + "]  = distanceToLight" + ndxStr + ";\n";
+		}
+		else if (lightType == LIGHT_DIRECTIONAL)
+		{
+			if (isVertexCase)
+				resultTemplate +=
+					"	mediump vec3 directionToLight" + ndxStr + " = -u_light" + ndxStr + "_direction${NAME_SPEC};\n"
+					"	mediump vec3 halfVector" + ndxStr + " = normalize(directionToLight" + ndxStr + " + vec3(0.0, 0.0, 1.0));\n"
+					"	color.rgb += computeLighting(directionToLight" + ndxStr + ", halfVector" + ndxStr + ", eyeNormal, u_light" + ndxStr + "_color${NAME_SPEC}, u_material_diffuseColor${NAME_SPEC}.rgb, u_material_specularColor${NAME_SPEC}, u_material_shininess${NAME_SPEC});\n";
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		resultTemplate += "\n";
+	}
+
+	resultTemplate +=
+		"	v_color${NAME_SPEC} = color;\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the fragment shader of a (directional or point) light case.
+static string lightFragmentTemplate (int numLights, bool isVertexCase, LightType lightType)
+{
+	string resultTemplate;
+
+	if (!isVertexCase)
+	{
+		resultTemplate +=
+			"uniform mediump vec3 u_material_ambientColor${NAME_SPEC};\n"
+			"uniform mediump vec4 u_material_diffuseColor${NAME_SPEC};\n"
+			"uniform mediump vec3 u_material_emissiveColor${NAME_SPEC};\n"
+			"uniform mediump vec3 u_material_specularColor${NAME_SPEC};\n"
+			"uniform mediump float u_material_shininess${NAME_SPEC};\n";
+
+		for (int lightNdx = 0; lightNdx < numLights; lightNdx++)
+		{
+			string ndxStr = de::toString(lightNdx);
+
+			resultTemplate +=
+				"uniform mediump vec3 u_light" + ndxStr + "_color${NAME_SPEC};\n"
+				"uniform mediump vec3 u_light" + ndxStr + "_direction${NAME_SPEC};\n";
+
+			if (lightType == LIGHT_POINT)
+				resultTemplate +=
+					"uniform mediump vec4 u_light" + ndxStr + "_position${NAME_SPEC};\n"
+					"uniform mediump float u_light" + ndxStr + "_constantAttenuation${NAME_SPEC};\n"
+					"uniform mediump float u_light" + ndxStr + "_linearAttenuation${NAME_SPEC};\n"
+					"uniform mediump float u_light" + ndxStr + "_quadraticAttenuation${NAME_SPEC};\n";
+		}
+	}
+
+	resultTemplate +=
+		"uniform sampler2D u_sampler0${NAME_SPEC};\n"
+		"varying mediump vec4 v_color${NAME_SPEC};\n"
+		"varying mediump vec2 v_texCoord0${NAME_SPEC};\n";
+
+	if (!isVertexCase)
+	{
+		resultTemplate +=
+			"varying mediump vec3 v_eyeNormal${NAME_SPEC};\n";
+
+		if (lightType == LIGHT_POINT)
+			resultTemplate +=
+				"varying mediump vec3 v_directionToLight${NAME_SPEC}[" + de::toString(numLights) + "];\n"
+				"varying mediump float v_distanceToLight${NAME_SPEC}[" + de::toString(numLights) + "];\n";
+
+		resultTemplate +=
+			"mediump vec3 direction (mediump vec4 from, mediump vec4 to)\n"
+			"{\n"
+			"	return vec3(to.xyz * from.w - from.xyz * to.w);\n"
+			"}\n"
+			"\n";
+
+		resultTemplate +=
+			"mediump vec3 computeLighting (\n"
+			"	mediump vec3 directionToLight,\n"
+			"	mediump vec3 halfVector,\n"
+			"	mediump vec3 normal,\n"
+			"	mediump vec3 lightColor,\n"
+			"	mediump vec3 diffuseColor,\n"
+			"	mediump vec3 specularColor,\n"
+			"	mediump float shininess)\n"
+			"{\n"
+			"	mediump float normalDotDirection  = max(dot(normal, directionToLight), 0.0);\n"
+			"	mediump vec3  color               = normalDotDirection * diffuseColor * lightColor;\n"
+			"\n"
+			"	if (normalDotDirection != 0.0)\n"
+			"		color += pow(max(dot(normal, halfVector), 0.0), shininess) * specularColor * lightColor;\n"
+			"\n"
+			"	return color;\n"
+			"}\n"
+			"\n";
+
+		if (lightType == LIGHT_POINT)
+			resultTemplate +=
+				"mediump float computeDistanceAttenuation (mediump float distToLight, mediump float constAtt, mediump float linearAtt, mediump float quadraticAtt)\n"
+				"{\n"
+				"	return 1.0 / (constAtt + linearAtt * distToLight + quadraticAtt * distToLight * distToLight);\n"
+				"}\n"
+				"\n";
+	}
+
+	resultTemplate +=
+		"void main (void)\n"
+		"{\n"
+		"	mediump vec2 texCoord0 = v_texCoord0${NAME_SPEC}.xy;\n"
+		"	mediump vec4 color = v_color${NAME_SPEC};\n";
+
+	if (!isVertexCase)
+	{
+		resultTemplate +=
+			"	mediump vec3 eyeNormal = normalize(v_eyeNormal${NAME_SPEC});\n"
+			"\n";
+
+		for (int lightNdx = 0; lightNdx < numLights; lightNdx++)
+		{
+			string ndxStr = de::toString(lightNdx);
+
+			resultTemplate +=
+				"	/* Light " + ndxStr + " */\n";
+
+			if (lightType == LIGHT_POINT)
+				resultTemplate +=
+					"	mediump vec3 directionToLight" + ndxStr + " = normalize(v_directionToLight${NAME_SPEC}[" + ndxStr + "]);\n"
+					"	mediump float distanceToLight" + ndxStr + " = v_distanceToLight${NAME_SPEC}[" + ndxStr + "];\n"
+					"	mediump vec3 halfVector" + ndxStr + " = normalize(directionToLight" + ndxStr + " + vec3(0.0, 0.0, 1.0));\n"
+					"	color.rgb += computeLighting(directionToLight" + ndxStr + ", halfVector" + ndxStr + ", eyeNormal, u_light" + ndxStr + "_color${NAME_SPEC}, u_material_diffuseColor${NAME_SPEC}.rgb, "
+					"u_material_specularColor${NAME_SPEC}, u_material_shininess${NAME_SPEC}) * computeDistanceAttenuation(distanceToLight" + ndxStr + ", u_light" + ndxStr + "_constantAttenuation${NAME_SPEC}, "
+					"u_light" + ndxStr + "_linearAttenuation${NAME_SPEC}, u_light" + ndxStr + "_quadraticAttenuation${NAME_SPEC});\n"
+					"\n";
+			else if (lightType == LIGHT_DIRECTIONAL)
+				resultTemplate +=
+					"	mediump vec3 directionToLight" + ndxStr + " = -u_light" + ndxStr + "_direction${NAME_SPEC};\n"
+					"	mediump vec3 halfVector" + ndxStr + " = normalize(directionToLight" + ndxStr + " + vec3(0.0, 0.0, 1.0));\n"
+					"	color.rgb += computeLighting(directionToLight" + ndxStr + ", halfVector" + ndxStr + ", eyeNormal, u_light" + ndxStr + "_color${NAME_SPEC}, u_material_diffuseColor${NAME_SPEC}.rgb, u_material_specularColor${NAME_SPEC}, u_material_shininess${NAME_SPEC});\n"
+					"\n";
+			else
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+
+	resultTemplate +=
+		"	color *= texture2D(u_sampler0${NAME_SPEC}, texCoord0);\n"
+		"	gl_FragColor = color + ${FLOAT01};\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the shader attributes of a (directional or point) light case.
+static vector<ShaderCompilerCase::AttribSpec> lightShaderAttributes (const string& nameSpecialization)
+{
+	vector<ShaderCompilerCase::AttribSpec> result;
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_position" + nameSpecialization,
+													combineVec4ToVec16(Vec4(-1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4(-1.0f,  1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  0.0f,  1.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_normal" + nameSpecialization,
+													combineVec4ToVec16(Vec4(0.0f, 0.0f, -1.0f, 0.0f),
+																	   Vec4(0.0f, 0.0f, -1.0f, 0.0f),
+																	   Vec4(0.0f, 0.0f, -1.0f, 0.0f),
+																	   Vec4(0.0f, 0.0f, -1.0f, 0.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_texCoord0" + nameSpecialization,
+													combineVec4ToVec16(Vec4(0.0f, 0.0f, 0.0f, 0.0f),
+																	   Vec4(1.0f, 0.0f, 0.0f, 0.0f),
+																	   Vec4(0.0f, 1.0f, 0.0f, 0.0f),
+																	   Vec4(1.0f, 1.0f, 0.0f, 0.0f))));
+
+	return result;
+}
+
+// Function for generating the shader uniforms of a (directional or point) light case.
+static vector<ShaderCompilerCase::UniformSpec> lightShaderUniforms (const string& nameSpecialization, int numLights, LightType lightType)
+{
+	vector<ShaderCompilerCase::UniformSpec> result;
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_material_ambientColor" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_VEC3,
+													 vecTo16(Vec3(0.5f, 0.7f, 0.9f))));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_material_diffuseColor" + nameSpecialization,
+													 ShaderCompilerCase:: UniformSpec::TYPE_VEC4,
+													 vecTo16(Vec4(0.3f, 0.4f, 0.5f, 1.0f))));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_material_emissiveColor" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_VEC3,
+													 vecTo16(Vec3(0.7f, 0.2f, 0.2f))));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_material_specularColor" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_VEC3,
+													 vecTo16(Vec3(0.2f, 0.6f, 1.0f))));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_material_shininess" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+													 0.8f));
+
+	for (int lightNdx = 0; lightNdx < numLights; lightNdx++)
+	{
+		string ndxStr = de::toString(lightNdx);
+
+		result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_color" + nameSpecialization,
+														 ShaderCompilerCase::UniformSpec::TYPE_VEC3,
+														 vecTo16(Vec3(0.8f, 0.6f, 0.3f))));
+
+		result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_direction" + nameSpecialization,
+														 ShaderCompilerCase::UniformSpec::TYPE_VEC3,
+														 vecTo16(Vec3(0.2f, 0.3f, 0.4f))));
+
+		if (lightType == LIGHT_POINT)
+		{
+			result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_position" + nameSpecialization,
+															 ShaderCompilerCase::UniformSpec::TYPE_VEC4,
+															 vecTo16(Vec4(1.0f, 0.6f, 0.3f, 0.2f))));
+
+			result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_constantAttenuation" + nameSpecialization,
+															 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+															 0.6f));
+
+			result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_linearAttenuation" + nameSpecialization,
+															 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+															 0.5f));
+
+			result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_quadraticAttenuation" + nameSpecialization,
+															 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+															 0.4f));
+		}
+	}
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_mvpMatrix" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_MAT4,
+													 arrTo16(Mat4(1.0f).getColumnMajorData())));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_modelViewMatrix" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_MAT4,
+													 arrTo16(Mat4(1.0f).getColumnMajorData())));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_normalMatrix" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_MAT3,
+													 arrTo16(Mat3(1.0f).getColumnMajorData())));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_texCoordMatrix0" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_MAT4,
+													 arrTo16(Mat4(1.0f).getColumnMajorData())));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_sampler0" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_TEXTURE_UNIT,
+													 0.0f));
+
+	return result;
+}
+
+// Function for generating a vertex shader with a for loop.
+static string loopVertexTemplate (LoopType type, bool isVertexCase, int numLoopIterations, int nestingDepth)
+{
+	string resultTemplate;
+	string loopBound		= type == LOOP_TYPE_STATIC	? de::toString(numLoopIterations)
+							: type == LOOP_TYPE_UNIFORM	? "int(u_loopBound${NAME_SPEC})"
+							: type == LOOP_TYPE_DYNAMIC	? "int(a_loopBound${NAME_SPEC})"
+							: "";
+
+	DE_ASSERT(!loopBound.empty());
+
+	resultTemplate +=
+		"attribute highp vec4 a_position${NAME_SPEC};\n";
+
+	if (type == LOOP_TYPE_DYNAMIC)
+		resultTemplate +=
+			"attribute mediump float a_loopBound${NAME_SPEC};\n";
+
+	resultTemplate +=
+		"attribute mediump vec4 a_value${NAME_SPEC};\n"
+		"varying mediump vec4 v_value${NAME_SPEC};\n";
+
+	if (isVertexCase)
+	{
+		if (type == LOOP_TYPE_UNIFORM)
+			resultTemplate += "uniform mediump float u_loopBound${NAME_SPEC};\n";
+
+		resultTemplate +=
+			"\n"
+			"void main()\n"
+			"{\n"
+			"	gl_Position = a_position${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+			"	mediump vec4 value = a_value${NAME_SPEC};\n";
+
+		for (int i = 0; i < nestingDepth; i++)
+		{
+			string iterName = "i" + de::toString(i);
+			resultTemplate += string(i + 1, '\t') + "for (int " + iterName + " = 0; " + iterName + " < " + loopBound + "; " + iterName + "++)\n";
+		}
+
+		resultTemplate += string(nestingDepth + 1, '\t') + "value *= a_value${NAME_SPEC};\n";
+
+		resultTemplate +=
+			"	v_value${NAME_SPEC} = value;\n";
+	}
+	else
+	{
+		if (type == LOOP_TYPE_DYNAMIC)
+			resultTemplate +=
+				"varying mediump float v_loopBound${NAME_SPEC};\n";
+
+		resultTemplate +=
+			"\n"
+			"void main()\n"
+			"{\n"
+			"	gl_Position = a_position${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+			"	v_value${NAME_SPEC} = a_value${NAME_SPEC};\n";
+
+		if (type == LOOP_TYPE_DYNAMIC)
+			resultTemplate +=
+				"	v_loopBound${NAME_SPEC} = a_loopBound${NAME_SPEC};\n";
+	}
+
+	resultTemplate +=
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating a fragment shader with a for loop.
+static string loopFragmentTemplate (LoopType type, bool isVertexCase, int numLoopIterations, int nestingDepth)
+{
+	string resultTemplate;
+	string loopBound		= type == LOOP_TYPE_STATIC	? de::toString(numLoopIterations)
+							: type == LOOP_TYPE_UNIFORM	? "int(u_loopBound${NAME_SPEC})"
+							: type == LOOP_TYPE_DYNAMIC	? "int(v_loopBound${NAME_SPEC})"
+							: "";
+
+	DE_ASSERT(!loopBound.empty());
+
+	resultTemplate +=
+		"varying mediump vec4 v_value${NAME_SPEC};\n";
+
+	if (!isVertexCase)
+	{
+		if (type == LOOP_TYPE_DYNAMIC)
+			resultTemplate +=
+				"varying mediump float v_loopBound${NAME_SPEC};\n";
+		else if (type == LOOP_TYPE_UNIFORM)
+			resultTemplate +=
+				"uniform mediump float u_loopBound${NAME_SPEC};\n";
+
+		resultTemplate +=
+			"\n"
+			"void main()\n"
+			"{\n"
+			"	mediump vec4 value = v_value${NAME_SPEC};\n";
+
+		for (int i = 0; i < nestingDepth; i++)
+		{
+			string iterName = "i" + de::toString(i);
+			resultTemplate += string(i + 1, '\t') + "for (int " + iterName + " = 0; " + iterName + " < " + loopBound + "; " + iterName + "++)\n";
+		}
+
+		resultTemplate += string(nestingDepth + 1, '\t') + "value *= v_value${NAME_SPEC};\n";
+
+		resultTemplate +=
+			"	gl_FragColor = value + ${FLOAT01};\n";
+	}
+	else
+		resultTemplate +=
+			"\n"
+			"void main()\n"
+			"{\n"
+			"	gl_FragColor = v_value${NAME_SPEC} + ${FLOAT01};\n";
+
+	resultTemplate +=
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the shader attributes for a loop case.
+static vector<ShaderCompilerCase::AttribSpec> loopShaderAttributes (const string& nameSpecialization, LoopType type, int numLoopIterations)
+{
+	vector<ShaderCompilerCase::AttribSpec> result;
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_position" + nameSpecialization,
+													combineVec4ToVec16(Vec4(-1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4(-1.0f,  1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  0.0f,  1.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_value" + nameSpecialization,
+													combineVec4ToVec16(Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f))));
+
+	if (type == LOOP_TYPE_DYNAMIC)
+		result.push_back(ShaderCompilerCase::AttribSpec("a_loopBound" + nameSpecialization,
+														combineVec4ToVec16(Vec4((float)numLoopIterations, 0.0f, 0.0f, 0.0f),
+																		   Vec4((float)numLoopIterations, 0.0f, 0.0f, 0.0f),
+																		   Vec4((float)numLoopIterations, 0.0f, 0.0f, 0.0f),
+																		   Vec4((float)numLoopIterations, 0.0f, 0.0f, 0.0f))));
+
+	return result;
+}
+
+static vector<ShaderCompilerCase::UniformSpec> loopShaderUniforms (const string& nameSpecialization, LoopType type, int numLoopIterations)
+{
+	vector<ShaderCompilerCase::UniformSpec> result;
+
+	if (type == LOOP_TYPE_UNIFORM)
+		result.push_back(ShaderCompilerCase::UniformSpec("u_loopBound" + nameSpecialization,
+														 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+														 (float)numLoopIterations));
+
+	return result;
+}
+
+// Function for generating the shader attributes for a case with only one attribute value in addition to the position attribute.
+static vector<ShaderCompilerCase::AttribSpec> singleValueShaderAttributes (const string& nameSpecialization)
+{
+	vector<ShaderCompilerCase::AttribSpec> result;
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_position" + nameSpecialization,
+													combineVec4ToVec16(Vec4(-1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4(-1.0f,  1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  0.0f,  1.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_value" + nameSpecialization,
+													combineVec4ToVec16(Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f))));
+
+	return result;
+}
+
+// Function for generating a vertex shader with a binary operation chain.
+static string binaryOpVertexTemplate (int numOperations, const char* op)
+{
+	string resultTemplate;
+
+	resultTemplate +=
+		"attribute highp vec4 a_position${NAME_SPEC};\n"
+		"attribute mediump vec4 a_value${NAME_SPEC};\n"
+		"varying mediump vec4 v_value${NAME_SPEC};\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = a_position${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+		"	mediump vec4 value = ";
+
+	for (int i = 0; i < numOperations; i++)
+		resultTemplate += string(i > 0 ? op : "") + "a_value${NAME_SPEC}";
+
+	resultTemplate +=
+		";\n"
+		"	v_value${NAME_SPEC} = value;\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating a fragment shader with a binary operation chain.
+static string binaryOpFragmentTemplate (int numOperations, const char* op)
+{
+	string resultTemplate;
+
+	resultTemplate +=
+		"varying mediump vec4 v_value${NAME_SPEC};\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	mediump vec4 value = ";
+
+	for (int i = 0; i < numOperations; i++)
+		resultTemplate += string(i > 0 ? op : "") + "v_value${NAME_SPEC}";
+
+	resultTemplate +=
+		";\n"
+		"	gl_FragColor = value + ${FLOAT01};\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating a vertex that takes one attribute in addition to position and just passes it to the fragment shader as a varying.
+static string singleVaryingVertexTemplate (void)
+{
+	const char* resultTemplate =
+		"attribute highp vec4 a_position${NAME_SPEC};\n"
+		"attribute mediump vec4 a_value${NAME_SPEC};\n"
+		"varying mediump vec4 v_value${NAME_SPEC};\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = a_position${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+		"	v_value${NAME_SPEC} = a_value${NAME_SPEC};\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating a fragment shader that takes a single varying and uses it as the color.
+static string singleVaryingFragmentTemplate (void)
+{
+	const char* resultTemplate =
+		"varying mediump vec4 v_value${NAME_SPEC};\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_FragColor = v_value${NAME_SPEC} + ${FLOAT01};\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the vertex shader of a texture lookup case.
+static string textureLookupVertexTemplate (ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+{
+	string	resultTemplate;
+	bool	conditionVaryingNeeded = conditionalUsage != CONDITIONAL_USAGE_NONE && conditionalType == CONDITIONAL_TYPE_DYNAMIC;
+
+	resultTemplate +=
+		"attribute highp vec4 a_position${NAME_SPEC};\n"
+		"attribute mediump vec2 a_coords${NAME_SPEC};\n"
+		"varying mediump vec2 v_coords${NAME_SPEC};\n";
+
+	if (conditionVaryingNeeded)
+		resultTemplate +=
+			"attribute mediump float a_condition${NAME_SPEC};\n"
+			"varying mediump float v_condition${NAME_SPEC};\n";
+
+	resultTemplate +=
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = a_position${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+		"	v_coords${NAME_SPEC} = a_coords${NAME_SPEC};\n";
+
+	if (conditionVaryingNeeded)
+		resultTemplate +=
+			"	v_condition${NAME_SPEC} = a_condition${NAME_SPEC};\n";
+
+	resultTemplate +=
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the fragment shader of a texture lookup case.
+static string textureLookupFragmentTemplate (int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+{
+	string resultTemplate;
+
+	resultTemplate +=
+		"varying mediump vec2 v_coords${NAME_SPEC};\n";
+
+	if (conditionalUsage != CONDITIONAL_USAGE_NONE && conditionalType == CONDITIONAL_TYPE_DYNAMIC)
+		resultTemplate +=
+			"varying mediump float v_condition${NAME_SPEC};\n";
+
+	for (int i = 0; i < numLookups; i++)
+		resultTemplate +=
+			"uniform sampler2D u_sampler" + de::toString(i) + "${NAME_SPEC};\n";
+
+	if (conditionalUsage != CONDITIONAL_USAGE_NONE && conditionalType == CONDITIONAL_TYPE_UNIFORM)
+		resultTemplate +=
+			"uniform mediump float u_condition${NAME_SPEC};\n";
+
+	resultTemplate +=
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	mediump vec4 color = vec4(0.0);\n";
+
+	const char* conditionalTerm = conditionalType == CONDITIONAL_TYPE_STATIC	? "1.0 > 0.0"
+								: conditionalType == CONDITIONAL_TYPE_UNIFORM	? "u_condition${NAME_SPEC} > 0.0"
+								: conditionalType == CONDITIONAL_TYPE_DYNAMIC	? "v_condition${NAME_SPEC} > 0.0"
+								: DE_NULL;
+
+	DE_ASSERT(conditionalTerm != DE_NULL);
+
+	if (conditionalUsage == CONDITIONAL_USAGE_FIRST_HALF)
+		resultTemplate += string("") +
+			"	if (" + conditionalTerm + ")\n"
+			"	{\n";
+
+	for (int i = 0; i < numLookups; i++)
+	{
+		if (conditionalUsage == CONDITIONAL_USAGE_FIRST_HALF)
+		{
+			if (i < (numLookups + 1) / 2)
+				resultTemplate += "\t";
+		}
+		else if (conditionalUsage == CONDITIONAL_USAGE_EVERY_OTHER)
+		{
+			if (i % 2 == 0)
+				resultTemplate += string("") +
+					"	if (" + conditionalTerm + ")\n"
+					"\t";
+		}
+
+		resultTemplate +=
+			"	color += texture2D(u_sampler" + de::toString(i) + "${NAME_SPEC}, v_coords${NAME_SPEC});\n";
+
+		if (conditionalUsage == CONDITIONAL_USAGE_FIRST_HALF && i == (numLookups - 1) / 2)
+			resultTemplate += "\t}\n";
+	}
+
+	resultTemplate +=
+		"	gl_FragColor = color/" + de::toString(numLookups) + ".0 + ${FLOAT01};\n" +
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the shader attributes of a texture lookup case.
+static vector<ShaderCompilerCase::AttribSpec> textureLookupShaderAttributes (const string& nameSpecialization, ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+{
+	vector<ShaderCompilerCase::AttribSpec> result;
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_position" + nameSpecialization,
+													combineVec4ToVec16(Vec4(-1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4(-1.0f,  1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  0.0f,  1.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_coords" + nameSpecialization,
+													combineVec4ToVec16(Vec4(0.0f, 0.0f, 0.0f, 0.0f),
+																	   Vec4(0.0f, 1.0f, 0.0f, 0.0f),
+																	   Vec4(1.0f, 0.0f, 0.0f, 0.0f),
+																	   Vec4(1.0f, 1.0f, 0.0f, 0.0f))));
+
+	if (conditionalUsage != CONDITIONAL_USAGE_NONE && conditionalType == CONDITIONAL_TYPE_DYNAMIC)
+		result.push_back(ShaderCompilerCase::AttribSpec("a_condition" + nameSpecialization,
+														combineVec4ToVec16(Vec4(1.0f), Vec4(1.0f), Vec4(1.0f), Vec4(1.0f))));
+
+	return result;
+}
+
+// Function for generating the shader uniforms of a texture lookup case.
+static vector<ShaderCompilerCase::UniformSpec> textureLookupShaderUniforms (const string& nameSpecialization, int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+{
+	vector<ShaderCompilerCase::UniformSpec> result;
+
+	for (int i = 0; i < numLookups; i++)
+		result.push_back(ShaderCompilerCase::UniformSpec("u_sampler" + de::toString(i) + nameSpecialization,
+														 ShaderCompilerCase::UniformSpec::TYPE_TEXTURE_UNIT,
+														 (float)i));
+
+	if (conditionalUsage != CONDITIONAL_USAGE_NONE && conditionalType == CONDITIONAL_TYPE_UNIFORM)
+		result.push_back(ShaderCompilerCase::UniformSpec("u_condition" + nameSpecialization,
+														 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+														 1.0f));
+
+	return result;
+}
+
+static string mandelbrotVertexTemplate (void)
+{
+	const char* resultTemplate =
+		"uniform highp mat4 u_mvp${NAME_SPEC};\n"
+		"\n"
+		"attribute highp vec4 a_vertex${NAME_SPEC};\n"
+		"attribute highp vec4 a_coord${NAME_SPEC};\n"
+		"\n"
+		"varying mediump vec2 v_coord${NAME_SPEC};\n"
+		"\n"
+		"void main(void)\n"
+		"{\n"
+		"	gl_Position = u_mvp${NAME_SPEC} * a_vertex${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+		"\n"
+		"	float xMin = -2.0;\n"
+		"	float xMax = +0.5;\n"
+		"	float yMin = -1.5;\n"
+		"	float yMax = +1.5;\n"
+		"\n"
+		"	v_coord${NAME_SPEC}.x = a_coord${NAME_SPEC}.x * (xMax - xMin) + xMin;\n"
+		"	v_coord${NAME_SPEC}.y = a_coord${NAME_SPEC}.y * (yMax - yMin) + yMin;\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+static string mandelbrotFragmentTemplate (int numFractalIterations)
+{
+	string resultTemplate =
+		"varying mediump vec2 v_coord${NAME_SPEC};\n"
+		"\n"
+		"precision mediump float;\n"
+		"\n"
+		"#define NUM_ITERS " + de::toString(numFractalIterations) + "\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	vec2 coords = v_coord${NAME_SPEC};\n"
+		"	float u_limit = 2.0 * 2.0;\n"
+		"	vec2 tmp = vec2(0, 0);\n"
+		"	int iter;\n"
+		"\n"
+		"	for (iter = 0; iter < NUM_ITERS; iter++)\n"
+		"	{\n"
+		"		tmp = vec2((tmp.x + tmp.y) * (tmp.x - tmp.y), 2.0 * (tmp.x * tmp.y)) + coords;\n"
+		"\n"
+		"		if (dot(tmp, tmp) > u_limit)\n"
+		"			break;\n"
+		"	}\n"
+		"\n"
+		"	vec3 color = vec3(float(iter) * (1.0 / float(NUM_ITERS)));\n"
+		"\n"
+		"	gl_FragColor = vec4(color, 1.0) + ${FLOAT01};\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+static vector<ShaderCompilerCase::AttribSpec> mandelbrotShaderAttributes (const string& nameSpecialization)
+{
+	vector<ShaderCompilerCase::AttribSpec> result;
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_vertex" + nameSpecialization,
+													combineVec4ToVec16(Vec4(-1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4(-1.0f,  1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  0.0f,  1.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_coord" + nameSpecialization,
+													combineVec4ToVec16(Vec4(0.0f, 0.0f, 0.0f, 1.0f),
+																	   Vec4(0.0f, 1.0f, 0.0f, 1.0f),
+																	   Vec4(1.0f, 0.0f, 0.0f, 1.0f),
+																	   Vec4(1.0f, 1.0f, 0.0f, 1.0f))));
+
+	return result;
+}
+
+static vector<ShaderCompilerCase::UniformSpec> mandelbrotShaderUniforms (const string& nameSpecialization)
+{
+	vector<ShaderCompilerCase::UniformSpec> result;
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_mvp" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_MAT4,
+													 arrTo16(Mat4(1.0f).getColumnMajorData())));
+
+	return result;
+}
+
+ShaderCompilerCase::ShaderCompilerCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments)
+	: TestCase								(context, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_viewportWidth						(0)
+	, m_viewportHeight						(0)
+	, m_avoidCache							(avoidCache)
+	, m_addWhitespaceAndComments			(addWhitespaceAndComments)
+	, m_startHash							((deUint32)(deUint64Hash(deGetTime()) ^ deUint64Hash(deGetMicroseconds()) ^ deInt32Hash(caseID)))
+{
+	int cmdLineIterCount = context.getTestContext().getCommandLine().getTestIterationCount();
+	m_minimumMeasurementCount = cmdLineIterCount > 0 ? cmdLineIterCount : DEFAULT_MINIMUM_MEASUREMENT_COUNT;
+	m_maximumMeasurementCount = m_minimumMeasurementCount*3;
+}
+
+ShaderCompilerCase::~ShaderCompilerCase (void)
+{
+}
+
+deUint32 ShaderCompilerCase::getSpecializationID (int measurementNdx) const
+{
+	if (m_avoidCache)
+		return m_startHash ^ (deUint32)deInt32Hash((deInt32)measurementNdx);
+	else
+		return m_startHash;
+}
+
+void ShaderCompilerCase::init (void)
+{
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderContext().getRenderTarget();
+
+	m_viewportWidth		= deMin32(MAX_VIEWPORT_WIDTH, renderTarget.getWidth());
+	m_viewportHeight	= deMin32(MAX_VIEWPORT_HEIGHT, renderTarget.getHeight());
+
+	gl.viewport(0, 0, m_viewportWidth, m_viewportHeight);
+}
+
+ShaderCompilerCase::ShadersAndProgram ShaderCompilerCase::createShadersAndProgram (void) const
+{
+	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();
+	ShadersAndProgram		result;
+
+	result.vertShader	= gl.createShader(GL_VERTEX_SHADER);
+	result.fragShader	= gl.createShader(GL_FRAGMENT_SHADER);
+	result.program		= gl.createProgram();
+
+	gl.attachShader(result.program, result.vertShader);
+	gl.attachShader(result.program, result.fragShader);
+
+	return result;
+}
+
+void ShaderCompilerCase::setShaderSources (deUint32 vertShader, deUint32 fragShader, const ProgramContext& progCtx) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	const char* vertShaderSourceCStr = progCtx.vertShaderSource.c_str();
+	const char* fragShaderSourceCStr = progCtx.fragShaderSource.c_str();
+	gl.shaderSource(vertShader, 1, &vertShaderSourceCStr, DE_NULL);
+	gl.shaderSource(fragShader, 1, &fragShaderSourceCStr, DE_NULL);
+}
+
+bool ShaderCompilerCase::compileShader (deUint32 shader) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	GLint status;
+	gl.compileShader(shader);
+	gl.getShaderiv(shader, GL_COMPILE_STATUS, &status);
+	return status != 0;
+}
+
+bool ShaderCompilerCase::linkAndUseProgram (deUint32 program) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	GLint linkStatus;
+
+	gl.linkProgram(program);
+	gl.getProgramiv(program, GL_LINK_STATUS, &linkStatus);
+
+	if (linkStatus != 0)
+		gl.useProgram(program);
+
+	return linkStatus != 0;
+}
+
+void ShaderCompilerCase::setShaderInputs (deUint32 program, const ProgramContext& progCtx) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Setup attributes.
+
+	for (int attribNdx = 0; attribNdx < (int)progCtx.vertexAttributes.size(); attribNdx++)
+	{
+		int location = gl.getAttribLocation(program, progCtx.vertexAttributes[attribNdx].name.c_str());
+		if (location >= 0)
+		{
+			gl.enableVertexAttribArray(location);
+			gl.vertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, 0, progCtx.vertexAttributes[attribNdx].value.getPtr());
+		}
+	}
+
+	// Setup uniforms.
+
+	for (int uniformNdx = 0; uniformNdx < (int)progCtx.uniforms.size(); uniformNdx++)
+	{
+		int location = gl.getUniformLocation(program, progCtx.uniforms[uniformNdx].name.c_str());
+		if (location >= 0)
+		{
+			const float* floatPtr = progCtx.uniforms[uniformNdx].value.getPtr();
+
+			switch (progCtx.uniforms[uniformNdx].type)
+			{
+				case UniformSpec::TYPE_FLOAT:			gl.uniform1fv(location, 1, floatPtr);								break;
+				case UniformSpec::TYPE_VEC2:			gl.uniform2fv(location, 1, floatPtr);								break;
+				case UniformSpec::TYPE_VEC3:			gl.uniform3fv(location, 1, floatPtr);								break;
+				case UniformSpec::TYPE_VEC4:			gl.uniform4fv(location, 1, floatPtr);								break;
+				case UniformSpec::TYPE_MAT3:			gl.uniformMatrix3fv(location, 1, GL_FALSE, floatPtr);				break;
+				case UniformSpec::TYPE_MAT4:			gl.uniformMatrix4fv(location, 1, GL_FALSE, floatPtr);				break;
+				case UniformSpec::TYPE_TEXTURE_UNIT:	gl.uniform1i(location, (GLint)deRoundFloatToInt32(*floatPtr));		break;
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+		}
+	}
+}
+
+void ShaderCompilerCase::draw (void) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	static const deUint8 indices[] =
+	{
+		0, 1, 2,
+		2, 1, 3
+	};
+
+	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	gl.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, indices);
+
+	// \note Read one pixel to force compilation.
+	deUint32 pixel;
+	gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
+}
+
+void ShaderCompilerCase::cleanup (const ShadersAndProgram& shadersAndProgram, const ProgramContext& progCtx, bool linkSuccess) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (linkSuccess)
+	{
+		for (int attribNdx = 0; attribNdx < (int)progCtx.vertexAttributes.size(); attribNdx++)
+		{
+			int location = gl.getAttribLocation(shadersAndProgram.program, progCtx.vertexAttributes[attribNdx].name.c_str());
+			if (location >= 0)
+				gl.disableVertexAttribArray(location);
+		}
+	}
+
+	gl.useProgram(0);
+	gl.detachShader(shadersAndProgram.program, shadersAndProgram.vertShader);
+	gl.detachShader(shadersAndProgram.program, shadersAndProgram.fragShader);
+	gl.deleteShader(shadersAndProgram.vertShader);
+	gl.deleteShader(shadersAndProgram.fragShader);
+	gl.deleteProgram(shadersAndProgram.program);
+}
+
+void ShaderCompilerCase::logProgramData (const BuildInfo& buildInfo, const ProgramContext& progCtx) const
+{
+	m_testCtx.getLog() << TestLog::ShaderProgram(buildInfo.linkSuccess, buildInfo.logs.link)
+					   << TestLog::Shader(QP_SHADER_TYPE_VERTEX,	progCtx.vertShaderSource, buildInfo.vertCompileSuccess, buildInfo.logs.vert)
+					   << TestLog::Shader(QP_SHADER_TYPE_FRAGMENT,	progCtx.fragShaderSource, buildInfo.fragCompileSuccess, buildInfo.logs.frag)
+					   << TestLog::EndShaderProgram;
+}
+
+ShaderCompilerCase::Logs ShaderCompilerCase::getLogs (const ShadersAndProgram& shadersAndProgram) const
+{
+	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();
+	Logs					result;
+
+	result.vert = getShaderInfoLog(gl, shadersAndProgram.vertShader);
+	result.frag = getShaderInfoLog(gl, shadersAndProgram.fragShader);
+	result.link = getProgramInfoLog(gl, shadersAndProgram.program);
+
+	return result;
+}
+
+bool ShaderCompilerCase::goodEnoughMeasurements (const vector<Measurement>& measurements) const
+{
+	if ((int)measurements.size() < m_minimumMeasurementCount)
+		return false;
+	else
+	{
+		if ((int)measurements.size() >= m_maximumMeasurementCount)
+			return true;
+		else
+		{
+			vector<deInt64> totalTimesWithoutDraw;
+			for (int i = 0; i < (int)measurements.size(); i++)
+				totalTimesWithoutDraw.push_back(measurements[i].totalTimeWithoutDraw());
+			return vectorFloatRelativeMedianAbsoluteDeviation(vectorLowestPercentage(totalTimesWithoutDraw, 0.5f)) < RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD;
+		}
+	}
+}
+
+ShaderCompilerCase::IterateResult ShaderCompilerCase::iterate (void)
+{
+	// Before actual measurements, compile and draw with a dummy shader to avoid possible initial slowdowns in the actual test.
+	{
+		deUint32		specID = getSpecializationID(0);
+		ProgramContext	progCtx;
+		progCtx.vertShaderSource = specializeShaderSource(singleVaryingVertexTemplate(), specID, SHADER_VALIDITY_VALID);
+		progCtx.fragShaderSource = specializeShaderSource(singleVaryingFragmentTemplate(), specID, SHADER_VALIDITY_VALID);
+		progCtx.vertexAttributes = singleValueShaderAttributes(getNameSpecialization(specID));
+
+		ShadersAndProgram shadersAndProgram = createShadersAndProgram();
+		setShaderSources(shadersAndProgram.vertShader, shadersAndProgram.fragShader, progCtx);
+
+		BuildInfo buildInfo;
+		buildInfo.vertCompileSuccess	= compileShader(shadersAndProgram.vertShader);
+		buildInfo.fragCompileSuccess	= compileShader(shadersAndProgram.fragShader);
+		buildInfo.linkSuccess			= linkAndUseProgram(shadersAndProgram.program);
+		if (!(buildInfo.vertCompileSuccess && buildInfo.fragCompileSuccess && buildInfo.linkSuccess))
+		{
+			buildInfo.logs = getLogs(shadersAndProgram);
+			logProgramData(buildInfo, progCtx);
+			cleanup(shadersAndProgram, progCtx, buildInfo.linkSuccess);
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compilation failed");
+			return STOP;
+		}
+		setShaderInputs(shadersAndProgram.program, progCtx);
+		draw();
+		cleanup(shadersAndProgram, progCtx, buildInfo.linkSuccess);
+	}
+
+	vector<Measurement>		measurements;
+	// \note These are logged after measurements are done.
+	ProgramContext			latestProgramContext;
+	BuildInfo				latestBuildInfo;
+
+	if (WARMUP_CPU_AT_BEGINNING_OF_CASE)
+		tcu::warmupCPU();
+
+	// Actual test measurements.
+	while (!goodEnoughMeasurements(measurements))
+	{
+		// Create shaders, compile & link, set shader inputs and draw. Time measurement is done at relevant points.
+		// \note Setting inputs and drawing are done twice in order to find out the time for actual compiling.
+
+		// \note Shader data (sources and inputs) are generated and GL shader and program objects are created before any time measurements.
+		ProgramContext		progCtx				= generateShaderData((int)measurements.size());
+		ShadersAndProgram	shadersAndProgram	= createShadersAndProgram();
+		BuildInfo			buildInfo;
+
+		if (m_addWhitespaceAndComments)
+		{
+			const deUint32 hash = m_startHash ^ (deUint32)deInt32Hash((deInt32)measurements.size());
+			progCtx.vertShaderSource = strWithWhiteSpaceAndComments(progCtx.vertShaderSource, hash);
+			progCtx.fragShaderSource = strWithWhiteSpaceAndComments(progCtx.fragShaderSource, hash);
+		}
+
+		if (WARMUP_CPU_BEFORE_EACH_MEASUREMENT)
+			tcu::warmupCPU();
+
+		// \note Do NOT do anything too hefty between the first and last deGetMicroseconds() here (other than the gl calls); it would disturb the measurement.
+
+		deUint64 startTime = deGetMicroseconds();
+
+		setShaderSources(shadersAndProgram.vertShader, shadersAndProgram.fragShader, progCtx);
+		deUint64 shaderSourceSetEndTime = deGetMicroseconds();
+
+		buildInfo.vertCompileSuccess = compileShader(shadersAndProgram.vertShader);
+		deUint64 vertexShaderCompileEndTime = deGetMicroseconds();
+
+		buildInfo.fragCompileSuccess = compileShader(shadersAndProgram.fragShader);
+		deUint64 fragmentShaderCompileEndTime = deGetMicroseconds();
+
+		buildInfo.linkSuccess = linkAndUseProgram(shadersAndProgram.program);
+		deUint64 programLinkEndTime = deGetMicroseconds();
+
+		// Check compilation and linking status here, after all compilation and linking gl calls are made.
+		if (!(buildInfo.vertCompileSuccess && buildInfo.fragCompileSuccess && buildInfo.linkSuccess))
+		{
+			buildInfo.logs = getLogs(shadersAndProgram);
+			logProgramData(buildInfo, progCtx);
+			cleanup(shadersAndProgram, progCtx, buildInfo.linkSuccess);
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compilation failed");
+			return STOP;
+		}
+
+		setShaderInputs(shadersAndProgram.program, progCtx);
+		deUint64 firstShaderInputSetEndTime = deGetMicroseconds();
+
+		// Draw for the first time.
+		draw();
+		deUint64 firstDrawEndTime = deGetMicroseconds();
+
+		// Set inputs and draw again.
+
+		setShaderInputs(shadersAndProgram.program, progCtx);
+		deUint64 secondShaderInputSetEndTime = deGetMicroseconds();
+
+		draw();
+		deUint64 secondDrawEndTime = deGetMicroseconds();
+
+		// De-initializations (detach shaders etc.).
+
+		buildInfo.logs = getLogs(shadersAndProgram);
+		cleanup(shadersAndProgram, progCtx, buildInfo.linkSuccess);
+
+		// Output measurement log later (after last measurement).
+
+		measurements.push_back(Measurement((deInt64)(shaderSourceSetEndTime			- startTime),
+										   (deInt64)(vertexShaderCompileEndTime		- shaderSourceSetEndTime),
+										   (deInt64)(fragmentShaderCompileEndTime	- vertexShaderCompileEndTime),
+										   (deInt64)(programLinkEndTime				- fragmentShaderCompileEndTime),
+										   (deInt64)(firstShaderInputSetEndTime		- programLinkEndTime),
+										   (deInt64)(firstDrawEndTime				- firstShaderInputSetEndTime),
+										   (deInt64)(secondShaderInputSetEndTime	- firstDrawEndTime),
+										   (deInt64)(secondDrawEndTime				- secondShaderInputSetEndTime)));
+
+		latestBuildInfo			= buildInfo;
+		latestProgramContext	= progCtx;
+
+		m_testCtx.touchWatchdog(); // \note Measurements may take a while in a bad case.
+	}
+
+	// End of test case, log information about measurements.
+	{
+		TestLog& log = m_testCtx.getLog();
+
+		vector<deInt64> sourceSetTimes;
+		vector<deInt64> vertexCompileTimes;
+		vector<deInt64> fragmentCompileTimes;
+		vector<deInt64> programLinkTimes;
+		vector<deInt64> firstInputSetTimes;
+		vector<deInt64> firstDrawTimes;
+		vector<deInt64> secondInputTimes;
+		vector<deInt64> secondDrawTimes;
+		vector<deInt64> firstPhaseTimes;
+		vector<deInt64> secondPhaseTimes;
+		vector<deInt64> totalTimesWithoutDraw;
+		vector<deInt64> specializationTimes;
+
+		if (!m_avoidCache)
+			log << TestLog::Message << "Note: Testing cache hits, so the medians and averages exclude the first iteration." << TestLog::EndMessage;
+
+		log << TestLog::Message << "Note: \"Specialization time\" means first draw time minus second draw time." << TestLog::EndMessage
+			<< TestLog::Message << "Note: \"Compilation time\" means the time up to (and including) linking, plus specialization time." << TestLog::EndMessage;
+
+		log << TestLog::Section("IterationMeasurements", "Iteration measurements of compilation and linking times");
+
+		DE_ASSERT((int)measurements.size() > (m_avoidCache ? 0 : 1));
+
+		for (int ndx = 0; ndx < (int)measurements.size(); ndx++)
+		{
+			const Measurement& curMeas = measurements[ndx];
+
+			// Subtract time of second phase (second input setup and draw) from first (from start to end of first draw).
+			// \note Cap if second phase seems unreasonably high (higher than first input set and draw).
+			deInt64 timeWithoutDraw		= curMeas.totalTimeWithoutDraw();
+
+			// Specialization time = first draw - second draw time. Again, cap at 0 if second draw was longer than first draw.
+			deInt64 specializationTime	= de::max<deInt64>(0, curMeas.firstDrawTime - curMeas.secondDrawTime);
+
+			if (ndx > 0 || m_avoidCache) // \note When allowing cache hits, don't account for the first measurement when calculating median or average.
+			{
+				sourceSetTimes.push_back		(curMeas.sourceSetTime);
+				vertexCompileTimes.push_back	(curMeas.vertexCompileTime);
+				fragmentCompileTimes.push_back	(curMeas.fragmentCompileTime);
+				programLinkTimes.push_back		(curMeas.programLinkTime);
+				firstInputSetTimes.push_back	(curMeas.firstInputSetTime);
+				firstDrawTimes.push_back		(curMeas.firstDrawTime);
+				firstPhaseTimes.push_back		(curMeas.firstPhase());
+				secondDrawTimes.push_back		(curMeas.secondDrawTime);
+				secondInputTimes.push_back		(curMeas.secondInputSetTime);
+				secondPhaseTimes.push_back		(curMeas.secondPhase());
+				totalTimesWithoutDraw.push_back	(timeWithoutDraw);
+				specializationTimes.push_back	(specializationTime);
+			}
+
+			// Log this measurement.
+			log << TestLog::Float("Measurement" + de::toString(ndx) + "CompilationTime",
+								  "Measurement " + de::toString(ndx) + " compilation time",
+								  "ms", QP_KEY_TAG_TIME, timeWithoutDraw / 1000.0f)
+				<< TestLog::Float("Measurement" + de::toString(ndx) + "SpecializationTime",
+								  "Measurement " + de::toString(ndx) + " specialization time",
+								  "ms", QP_KEY_TAG_TIME, specializationTime / 1000.0f);
+		}
+
+		// Log some statistics.
+
+		for (int entireRangeOrLowestHalf = 0; entireRangeOrLowestHalf < 2; entireRangeOrLowestHalf++)
+		{
+			bool				isEntireRange				= entireRangeOrLowestHalf == 0;
+			string				statNamePrefix				= isEntireRange ? "" : "LowestHalf";
+			vector<deInt64>		rangeTotalTimes				= isEntireRange ? totalTimesWithoutDraw	: vectorLowestPercentage(totalTimesWithoutDraw,	0.5f);
+			vector<deInt64>		rangeSpecializationTimes	= isEntireRange ? specializationTimes	: vectorLowestPercentage(specializationTimes,	0.5f);
+
+#define LOG_COMPILE_SPECIALIZE_TIME_STAT(NAME, DESC, FUNC)																													\
+	log << TestLog::Float(statNamePrefix + "CompilationTime" + (NAME), (DESC) + string(" of compilation time"), "ms", QP_KEY_TAG_TIME, (FUNC)(rangeTotalTimes)/1000.0f)		\
+		<< TestLog::Float(statNamePrefix + "SpecializationTime" + (NAME), (DESC) + string(" of specialization time"), "ms", QP_KEY_TAG_TIME, (FUNC)(rangeSpecializationTimes)/1000.0f)
+
+#define LOG_COMPILE_SPECIALIZE_RELATIVE_STAT(NAME, DESC, FUNC)																										\
+	log << TestLog::Float(statNamePrefix + "CompilationTime" + (NAME), (DESC) + string(" of compilation time"), "", QP_KEY_TAG_NONE, (FUNC)(rangeTotalTimes))		\
+		<< TestLog::Float(statNamePrefix + "SpecializationTime" + (NAME), (DESC) + string(" of specialization time"), "", QP_KEY_TAG_NONE, (FUNC)(rangeSpecializationTimes))
+
+			log << TestLog::Message << "\nStatistics computed from "
+									<< (isEntireRange ? "all" : "only the lowest 50%")
+									<< " of the above measurements:"
+									<< TestLog::EndMessage;
+
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("Median",							"Median",								vectorFloatMedian);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("Average",							"Average",								vectorFloatAverage);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("Minimum",							"Minimum",								vectorFloatMinimum);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("Maximum",							"Maximum",								vectorFloatMaximum);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("MedianAbsoluteDeviation",			"Median absolute deviation",			vectorFloatMedianAbsoluteDeviation);
+			LOG_COMPILE_SPECIALIZE_RELATIVE_STAT	("RelativeMedianAbsoluteDeviation",	"Relative median absolute deviation",	vectorFloatRelativeMedianAbsoluteDeviation);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("StandardDeviation",				"Standard deviation",					vectorFloatStandardDeviation);
+			LOG_COMPILE_SPECIALIZE_RELATIVE_STAT	("RelativeStandardDeviation",		"Relative standard deviation",			vectorFloatRelativeStandardDeviation);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("MaxMinusMin",						"Max-min",								vectorFloatMaximumMinusMinimum);
+			LOG_COMPILE_SPECIALIZE_RELATIVE_STAT	("RelativeMaxMinusMin",				"Relative max-min",						vectorFloatRelativeMaximumMinusMinimum);
+
+#undef LOG_COMPILE_SPECIALIZE_RELATIVE_STAT
+#undef LOG_COMPILE_SPECIALIZE_TIME_STAT
+
+			if (!isEntireRange && vectorFloatRelativeMedianAbsoluteDeviation(rangeTotalTimes) > RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD)
+				log << TestLog::Message << "\nWARNING: couldn't achieve relative median absolute deviation under threshold value "
+										<< RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD
+										<< " for compilation time of the lowest 50% of measurements" << TestLog::EndMessage;
+		}
+
+		log << TestLog::EndSection; // End section IterationMeasurements
+
+		for (int medianOrAverage = 0; medianOrAverage < 2; medianOrAverage++)
+		{
+			typedef float (*VecFunc)(const vector<deInt64>&);
+
+			bool	isMedian						= medianOrAverage == 0;
+			string	singular						= isMedian ? "Median" : "Average";
+			string	plural							= singular + "s";
+			VecFunc	func							= isMedian ? (VecFunc) vectorFloatMedian<deInt64> : (VecFunc) vectorFloatAverage<deInt64>;
+
+			log << TestLog::Section(plural + "PerPhase", plural + " per phase");
+
+			for (int entireRangeOrLowestHalf = 0; entireRangeOrLowestHalf < 2; entireRangeOrLowestHalf++)
+			{
+				bool	isEntireRange	= entireRangeOrLowestHalf == 0;
+				string	statNamePrefix	= isEntireRange ? "" : "LowestHalf";
+				float	rangeSizeRatio	= isEntireRange ? 1.0f : 0.5f;
+
+#define LOG_TIME(NAME, DESC, DATA) log << TestLog::Float(statNamePrefix + (NAME) + singular, singular + " of " + (DESC), "ms", QP_KEY_TAG_TIME, func(vectorLowestPercentage((DATA), rangeSizeRatio))/1000.0f);
+
+				log << TestLog::Message << (isEntireRange ? "For all measurements:" : "\nFor only the lowest 50% of the measurements:") << TestLog::EndMessage;
+				LOG_TIME("ShaderSourceSetTime",			"shader source set time",			sourceSetTimes);
+				LOG_TIME("VertexShaderCompileTime",		"vertex shader compile time",		vertexCompileTimes);
+				LOG_TIME("FragmentShaderCompileTime",	"fragment shader compile time",		fragmentCompileTimes);
+				LOG_TIME("ProgramLinkTime",				"program link time",				programLinkTimes);
+				LOG_TIME("FirstShaderInputSetTime",		"first shader input set time",		firstInputSetTimes);
+				LOG_TIME("FirstDrawTime",				"first draw time",					firstDrawTimes);
+				LOG_TIME("SecondShaderInputSetTime",	"second shader input set time",		secondInputTimes);
+				LOG_TIME("SecondDrawTime",				"second draw time",					secondDrawTimes);
+
+#undef LOG_TIME
+			}
+
+			log << TestLog::EndSection;
+		}
+
+		// Set result.
+
+		{
+			log << TestLog::Message << "Note: test result is the first quartile (i.e. median of the lowest half of measurements) of compilation times" << TestLog::EndMessage;
+			float result = vectorFloatFirstQuartile(totalTimesWithoutDraw) / 1000.0f;
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(result, 2).c_str());
+		}
+
+		// Log shaders.
+
+		if (m_avoidCache || m_addWhitespaceAndComments)
+		{
+			string msg = "Note: the following shaders are the ones from the last iteration; ";
+
+			if (m_avoidCache)
+				msg += "variables' names and some constant expressions";
+			if (m_addWhitespaceAndComments)
+				msg += string(m_avoidCache ? " as well as " : "") + "whitespace and comments";
+
+			msg += " differ between iterations.";
+
+			log << TestLog::Message << msg.c_str() << TestLog::EndMessage;
+		}
+
+		logProgramData(latestBuildInfo, latestProgramContext);
+
+		return STOP;
+	}
+}
+
+ShaderCompilerLightCase::ShaderCompilerLightCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, int numLights, LightType lightType)
+	: ShaderCompilerCase	(context, name, description, caseID, avoidCache, addWhitespaceAndComments)
+	, m_numLights			(numLights)
+	, m_isVertexCase		(isVertexCase)
+	, m_lightType			(lightType)
+	, m_texture				(DE_NULL)
+{
+}
+
+ShaderCompilerLightCase::~ShaderCompilerLightCase (void)
+{
+	ShaderCompilerLightCase::deinit();
+}
+
+void ShaderCompilerLightCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+}
+
+void ShaderCompilerLightCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Setup texture.
+
+	DE_ASSERT(m_texture == DE_NULL);
+
+	m_texture = new glu::Texture2D(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, TEXTURE_WIDTH, TEXTURE_HEIGHT);
+
+	tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	m_texture->getRefTexture().allocLevel(0);
+	tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), fmtInfo.valueMin, fmtInfo.valueMax);
+
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	m_texture->upload();
+
+	ShaderCompilerCase::init();
+}
+
+ShaderCompilerCase::ProgramContext ShaderCompilerLightCase::generateShaderData (int measurementNdx) const
+{
+	deUint32		specID		= getSpecializationID(measurementNdx);
+	string			nameSpec	= getNameSpecialization(specID);
+	ProgramContext	result;
+
+	result.vertShaderSource		= specializeShaderSource(lightVertexTemplate(m_numLights, m_isVertexCase, m_lightType), specID, SHADER_VALIDITY_VALID);
+	result.fragShaderSource		= specializeShaderSource(lightFragmentTemplate(m_numLights, m_isVertexCase, m_lightType), specID, SHADER_VALIDITY_VALID);
+	result.vertexAttributes		= lightShaderAttributes(nameSpec);
+	result.uniforms				= lightShaderUniforms(nameSpec, m_numLights, m_lightType);
+
+	return result;
+}
+
+ShaderCompilerTextureCase::ShaderCompilerTextureCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+	: ShaderCompilerCase	(context, name, description, caseID, avoidCache, addWhitespaceAndComments)
+	, m_numLookups			(numLookups)
+	, m_conditionalUsage	(conditionalUsage)
+	, m_conditionalType		(conditionalType)
+{
+}
+
+ShaderCompilerTextureCase::~ShaderCompilerTextureCase (void)
+{
+	ShaderCompilerTextureCase::deinit();
+}
+
+void ShaderCompilerTextureCase::deinit (void)
+{
+	for (vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+}
+
+void ShaderCompilerTextureCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Setup texture.
+
+	DE_ASSERT(m_textures.empty());
+
+	m_textures.reserve(m_numLookups);
+
+	for (int i = 0; i < m_numLookups; i++)
+	{
+		glu::Texture2D*			tex		= new glu::Texture2D(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, TEXTURE_WIDTH, TEXTURE_HEIGHT);
+		tcu::TextureFormatInfo	fmtInfo	= tcu::getTextureFormatInfo(tex->getRefTexture().getFormat());
+
+		tex->getRefTexture().allocLevel(0);
+		tcu::fillWithComponentGradients(tex->getRefTexture().getLevel(0), fmtInfo.valueMin, fmtInfo.valueMax);
+
+		gl.activeTexture(GL_TEXTURE0 + i);
+		gl.bindTexture(GL_TEXTURE_2D, tex->getGLTexture());
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+		tex->upload();
+
+		m_textures.push_back(tex);
+	}
+
+	ShaderCompilerCase::init();
+}
+
+ShaderCompilerCase::ProgramContext ShaderCompilerTextureCase::generateShaderData (int measurementNdx) const
+{
+	deUint32		specID		= getSpecializationID(measurementNdx);
+	string			nameSpec	= getNameSpecialization(specID);
+	ProgramContext	result;
+
+	result.vertShaderSource		= specializeShaderSource(textureLookupVertexTemplate(m_conditionalUsage, m_conditionalType), specID, SHADER_VALIDITY_VALID);
+	result.fragShaderSource		= specializeShaderSource(textureLookupFragmentTemplate(m_numLookups, m_conditionalUsage, m_conditionalType), specID, SHADER_VALIDITY_VALID);
+	result.vertexAttributes		= textureLookupShaderAttributes(nameSpec, m_conditionalUsage, m_conditionalType);
+	result.uniforms				= textureLookupShaderUniforms(nameSpec, m_numLookups, m_conditionalUsage, m_conditionalType);
+
+	return result;
+}
+
+ShaderCompilerLoopCase::ShaderCompilerLoopCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, LoopType type, int numLoopIterations, int nestingDepth)
+	: ShaderCompilerCase	(context, name, description, caseID, avoidCache, addWhitespaceAndComments)
+	, m_numLoopIterations	(numLoopIterations)
+	, m_nestingDepth		(nestingDepth)
+	, m_isVertexCase		(isVertexCase)
+	, m_type				(type)
+{
+}
+
+ShaderCompilerLoopCase::~ShaderCompilerLoopCase (void)
+{
+}
+
+ShaderCompilerCase::ProgramContext ShaderCompilerLoopCase::generateShaderData (int measurementNdx) const
+{
+	deUint32		specID		= getSpecializationID(measurementNdx);
+	string			nameSpec	= getNameSpecialization(specID);
+	ProgramContext	result;
+
+	result.vertShaderSource		= specializeShaderSource(loopVertexTemplate(m_type, m_isVertexCase, m_numLoopIterations, m_nestingDepth), specID, SHADER_VALIDITY_VALID);
+	result.fragShaderSource		= specializeShaderSource(loopFragmentTemplate(m_type, m_isVertexCase, m_numLoopIterations, m_nestingDepth), specID, SHADER_VALIDITY_VALID);
+
+	result.vertexAttributes		= loopShaderAttributes(nameSpec, m_type, m_numLoopIterations);
+	result.uniforms				= loopShaderUniforms(nameSpec, m_type, m_numLoopIterations);
+
+	return result;
+}
+
+ShaderCompilerOperCase::ShaderCompilerOperCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, const char* oper, int numOperations)
+	: ShaderCompilerCase	(context, name, description, caseID, avoidCache, addWhitespaceAndComments)
+	, m_oper				(oper)
+	, m_numOperations		(numOperations)
+	, m_isVertexCase		(isVertexCase)
+{
+}
+
+ShaderCompilerOperCase::~ShaderCompilerOperCase (void)
+{
+}
+
+ShaderCompilerCase::ProgramContext ShaderCompilerOperCase::generateShaderData (int measurementNdx) const
+{
+	deUint32		specID		= getSpecializationID(measurementNdx);
+	string			nameSpec	= getNameSpecialization(specID);
+	ProgramContext	result;
+
+	if (m_isVertexCase)
+	{
+		result.vertShaderSource = specializeShaderSource(binaryOpVertexTemplate(m_numOperations, m_oper.c_str()), specID, SHADER_VALIDITY_VALID);
+		result.fragShaderSource = specializeShaderSource(singleVaryingFragmentTemplate(), specID, SHADER_VALIDITY_VALID);
+	}
+	else
+	{
+		result.vertShaderSource = specializeShaderSource(singleVaryingVertexTemplate(), specID, SHADER_VALIDITY_VALID);
+		result.fragShaderSource = specializeShaderSource(binaryOpFragmentTemplate(m_numOperations, m_oper.c_str()), specID, SHADER_VALIDITY_VALID);
+	}
+
+	result.vertexAttributes = singleValueShaderAttributes(nameSpec);
+
+	result.uniforms.clear(); // No uniforms used.
+
+	return result;
+}
+
+ShaderCompilerMandelbrotCase::ShaderCompilerMandelbrotCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, int numFractalIterations)
+	: ShaderCompilerCase		(context, name, description, caseID, avoidCache, addWhitespaceAndComments)
+	, m_numFractalIterations	(numFractalIterations)
+{
+}
+
+ShaderCompilerMandelbrotCase::~ShaderCompilerMandelbrotCase (void)
+{
+}
+
+ShaderCompilerCase::ProgramContext ShaderCompilerMandelbrotCase::generateShaderData (int measurementNdx) const
+{
+	deUint32		specID		= getSpecializationID(measurementNdx);
+	string			nameSpec	= getNameSpecialization(specID);
+	ProgramContext	result;
+
+	result.vertShaderSource = specializeShaderSource(mandelbrotVertexTemplate(), specID, SHADER_VALIDITY_VALID);
+	result.fragShaderSource = specializeShaderSource(mandelbrotFragmentTemplate(m_numFractalIterations), specID, SHADER_VALIDITY_VALID);
+
+	result.vertexAttributes = mandelbrotShaderAttributes(nameSpec);
+	result.uniforms = mandelbrotShaderUniforms(nameSpec);
+
+	return result;
+}
+
+InvalidShaderCompilerCase::InvalidShaderCompilerCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType)
+	: TestCase						(context, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_invalidityType				(invalidityType)
+	, m_startHash					((deUint32)(deUint64Hash(deGetTime()) ^ deUint64Hash(deGetMicroseconds()) ^ deInt32Hash(caseID)))
+{
+	int cmdLineIterCount = context.getTestContext().getCommandLine().getTestIterationCount();
+	m_minimumMeasurementCount = cmdLineIterCount > 0 ? cmdLineIterCount : DEFAULT_MINIMUM_MEASUREMENT_COUNT;
+	m_maximumMeasurementCount = 3*m_minimumMeasurementCount;
+}
+
+InvalidShaderCompilerCase::~InvalidShaderCompilerCase (void)
+{
+}
+
+deUint32 InvalidShaderCompilerCase::getSpecializationID (int measurementNdx) const
+{
+	return m_startHash ^ (deUint32)deInt32Hash((deInt32)measurementNdx);
+}
+
+InvalidShaderCompilerCase::Shaders InvalidShaderCompilerCase::createShaders (void) const
+{
+	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();
+	Shaders					result;
+
+	result.vertShader = gl.createShader(GL_VERTEX_SHADER);
+	result.fragShader = gl.createShader(GL_FRAGMENT_SHADER);
+
+	return result;
+}
+
+void InvalidShaderCompilerCase::setShaderSources (const Shaders& shaders, const ProgramContext& progCtx) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	const char* vertShaderSourceCStr = progCtx.vertShaderSource.c_str();
+	const char* fragShaderSourceCStr = progCtx.fragShaderSource.c_str();
+	gl.shaderSource(shaders.vertShader, 1, &vertShaderSourceCStr, DE_NULL);
+	gl.shaderSource(shaders.fragShader, 1, &fragShaderSourceCStr, DE_NULL);
+}
+
+bool InvalidShaderCompilerCase::compileShader (deUint32 shader) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	GLint status;
+	gl.compileShader(shader);
+	gl.getShaderiv(shader, GL_COMPILE_STATUS, &status);
+	return status != 0;
+}
+
+void InvalidShaderCompilerCase::logProgramData (const BuildInfo& buildInfo, const ProgramContext& progCtx) const
+{
+	m_testCtx.getLog() << TestLog::ShaderProgram(false, "(No linking done)")
+					   << TestLog::Shader(QP_SHADER_TYPE_VERTEX,	progCtx.vertShaderSource, buildInfo.vertCompileSuccess, buildInfo.logs.vert)
+					   << TestLog::Shader(QP_SHADER_TYPE_FRAGMENT,	progCtx.fragShaderSource, buildInfo.fragCompileSuccess, buildInfo.logs.frag)
+					   << TestLog::EndShaderProgram;
+}
+
+InvalidShaderCompilerCase::Logs InvalidShaderCompilerCase::getLogs (const Shaders& shaders) const
+{
+	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();
+	Logs					result;
+
+	result.vert = getShaderInfoLog(gl, shaders.vertShader);
+	result.frag = getShaderInfoLog(gl, shaders.fragShader);
+
+	return result;
+}
+
+void InvalidShaderCompilerCase::cleanup (const Shaders& shaders) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.deleteShader(shaders.vertShader);
+	gl.deleteShader(shaders.fragShader);
+}
+
+bool InvalidShaderCompilerCase::goodEnoughMeasurements (const vector<Measurement>& measurements) const
+{
+	if ((int)measurements.size() < m_minimumMeasurementCount)
+		return false;
+	else
+	{
+		if ((int)measurements.size() >= m_maximumMeasurementCount)
+			return true;
+		else
+		{
+			vector<deInt64> totalTimes;
+			for (int i = 0; i < (int)measurements.size(); i++)
+				totalTimes.push_back(measurements[i].totalTime());
+			return vectorFloatRelativeMedianAbsoluteDeviation(vectorLowestPercentage(totalTimes, 0.5f)) < RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD;
+		}
+	}
+}
+
+InvalidShaderCompilerCase::IterateResult InvalidShaderCompilerCase::iterate (void)
+{
+	ShaderValidity shaderValidity = m_invalidityType == INVALIDITY_INVALID_CHAR		? SHADER_VALIDITY_INVALID_CHAR
+								  : m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+								  : SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	// Before actual measurements, compile a dummy shader to avoid possible initial slowdowns in the actual test.
+	{
+		deUint32		specID = getSpecializationID(0);
+		ProgramContext	progCtx;
+		progCtx.vertShaderSource = specializeShaderSource(singleVaryingVertexTemplate(), specID, shaderValidity);
+		progCtx.fragShaderSource = specializeShaderSource(singleVaryingFragmentTemplate(), specID, shaderValidity);
+
+		Shaders shaders = createShaders();
+		setShaderSources(shaders, progCtx);
+
+		BuildInfo buildInfo;
+		buildInfo.vertCompileSuccess = compileShader(shaders.vertShader);
+		buildInfo.fragCompileSuccess = compileShader(shaders.fragShader);
+		if (buildInfo.vertCompileSuccess || buildInfo.fragCompileSuccess)
+		{
+			buildInfo.logs = getLogs(shaders);
+			logProgramData(buildInfo, progCtx);
+			cleanup(shaders);
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compilation of a shader erroneously succeeded");
+			return STOP;
+		}
+		cleanup(shaders);
+	}
+
+	vector<Measurement>		measurements;
+	// \note These are logged after measurements are done.
+	ProgramContext			latestProgramContext;
+	BuildInfo				latestBuildInfo;
+
+	if (WARMUP_CPU_AT_BEGINNING_OF_CASE)
+		tcu::warmupCPU();
+
+	// Actual test measurements.
+	while (!goodEnoughMeasurements(measurements))
+	{
+		// Create shader and compile. Measure time.
+
+		// \note Shader sources are generated and GL shader objects are created before any time measurements.
+		ProgramContext	progCtx		= generateShaderSources((int)measurements.size());
+		Shaders			shaders		= createShaders();
+		BuildInfo		buildInfo;
+
+		if (WARMUP_CPU_BEFORE_EACH_MEASUREMENT)
+			tcu::warmupCPU();
+
+		// \note Do NOT do anything too hefty between the first and last deGetMicroseconds() here (other than the gl calls); it would disturb the measurement.
+
+		deUint64 startTime = deGetMicroseconds();
+
+		setShaderSources(shaders, progCtx);
+		deUint64 shaderSourceSetEndTime = deGetMicroseconds();
+
+		buildInfo.vertCompileSuccess = compileShader(shaders.vertShader);
+		deUint64 vertexShaderCompileEndTime = deGetMicroseconds();
+
+		buildInfo.fragCompileSuccess = compileShader(shaders.fragShader);
+		deUint64 fragmentShaderCompileEndTime = deGetMicroseconds();
+
+		buildInfo.logs = getLogs(shaders);
+
+		// Both shader compilations should have failed.
+		if (buildInfo.vertCompileSuccess || buildInfo.fragCompileSuccess)
+		{
+			logProgramData(buildInfo, progCtx);
+			cleanup(shaders);
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compilation of a shader erroneously succeeded");
+			return STOP;
+		}
+
+		// De-initializations (delete shaders).
+
+		cleanup(shaders);
+
+		// Output measurement log later (after last measurement).
+
+		measurements.push_back(Measurement((deInt64)(shaderSourceSetEndTime			- startTime),
+										   (deInt64)(vertexShaderCompileEndTime		- shaderSourceSetEndTime),
+										   (deInt64)(fragmentShaderCompileEndTime	- vertexShaderCompileEndTime)));
+
+		latestBuildInfo			= buildInfo;
+		latestProgramContext	= progCtx;
+
+		m_testCtx.touchWatchdog(); // \note Measurements may take a while in a bad case.
+	}
+
+	// End of test case, log information about measurements.
+	{
+		TestLog& log = m_testCtx.getLog();
+
+		vector<deInt64> sourceSetTimes;
+		vector<deInt64> vertexCompileTimes;
+		vector<deInt64> fragmentCompileTimes;
+		vector<deInt64> totalTimes;
+
+		log << TestLog::Section("IterationMeasurements", "Iteration measurements of compilation times");
+
+		for (int ndx = 0; ndx < (int)measurements.size(); ndx++)
+		{
+			sourceSetTimes.push_back		(measurements[ndx].sourceSetTime);
+			vertexCompileTimes.push_back	(measurements[ndx].vertexCompileTime);
+			fragmentCompileTimes.push_back	(measurements[ndx].fragmentCompileTime);
+			totalTimes.push_back			(measurements[ndx].totalTime());
+
+			// Log this measurement.
+			log << TestLog::Float("Measurement" + de::toString(ndx) + "Time",
+								  "Measurement " + de::toString(ndx) + " time",
+								  "ms", QP_KEY_TAG_TIME, measurements[ndx].totalTime()/1000.0f);
+		}
+
+		// Log some statistics.
+
+		for (int entireRangeOrLowestHalf = 0; entireRangeOrLowestHalf < 2; entireRangeOrLowestHalf++)
+		{
+			bool				isEntireRange	= entireRangeOrLowestHalf == 0;
+			string				statNamePrefix	= isEntireRange ? "" : "LowestHalf";
+			vector<deInt64>		rangeTimes		= isEntireRange ? totalTimes : vectorLowestPercentage(totalTimes, 0.5f);
+
+			log << TestLog::Message << "\nStatistics computed from "
+									<< (isEntireRange ? "all" : "only the lowest 50%")
+									<< " of the above measurements:"
+									<< TestLog::EndMessage;
+
+#define LOG_TIME_STAT(NAME, DESC, FUNC)			log << TestLog::Float(statNamePrefix + "TotalTime" + (NAME), (DESC) + string(" of total time"), "ms",	QP_KEY_TAG_TIME, (FUNC)(rangeTimes)/1000.0f)
+#define LOG_RELATIVE_STAT(NAME, DESC, FUNC)		log << TestLog::Float(statNamePrefix + "TotalTime" + (NAME), (DESC) + string(" of total time"), "",		QP_KEY_TAG_NONE, (FUNC)(rangeTimes))
+
+			LOG_TIME_STAT		("Median",							"Median",								vectorFloatMedian);
+			LOG_TIME_STAT		("Average",							"Average",								vectorFloatAverage);
+			LOG_TIME_STAT		("Minimum",							"Minimum",								vectorFloatMinimum);
+			LOG_TIME_STAT		("Maximum",							"Maximum",								vectorFloatMaximum);
+			LOG_TIME_STAT		("MedianAbsoluteDeviation",			"Median absolute deviation",			vectorFloatMedianAbsoluteDeviation);
+			LOG_RELATIVE_STAT	("RelativeMedianAbsoluteDeviation",	"Relative median absolute deviation",	vectorFloatRelativeMedianAbsoluteDeviation);
+			LOG_TIME_STAT		("StandardDeviation",				"Standard deviation",					vectorFloatStandardDeviation);
+			LOG_RELATIVE_STAT	("RelativeStandardDeviation",		"Relative standard deviation",			vectorFloatRelativeStandardDeviation);
+			LOG_TIME_STAT		("MaxMinusMin",						"Max-min",								vectorFloatMaximumMinusMinimum);
+			LOG_RELATIVE_STAT	("RelativeMaxMinusMin",				"Relative max-min",						vectorFloatRelativeMaximumMinusMinimum);
+
+#undef LOG_TIME_STAT
+#undef LOG_RELATIVE_STAT
+
+			if (!isEntireRange && vectorFloatRelativeMedianAbsoluteDeviation(rangeTimes) > RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD)
+				log << TestLog::Message << "\nWARNING: couldn't achieve relative median absolute deviation under threshold value " << RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD << TestLog::EndMessage;
+		}
+
+		log << TestLog::EndSection; // End section IterationMeasurements
+
+		for (int medianOrAverage = 0; medianOrAverage < 2; medianOrAverage++)
+		{
+			typedef float (*VecFunc)(const vector<deInt64>&);
+
+			bool	isMedian						= medianOrAverage == 0;
+			string	singular						= isMedian ? "Median" : "Average";
+			string	plural							= singular + "s";
+			VecFunc func							= isMedian ? (VecFunc) vectorFloatMedian<deInt64> : (VecFunc) vectorFloatAverage<deInt64>;
+
+			log << TestLog::Section(plural + "PerPhase", plural + " per phase");
+
+			for (int entireRangeOrLowestHalf = 0; entireRangeOrLowestHalf < 2; entireRangeOrLowestHalf++)
+			{
+				bool	isEntireRange	= entireRangeOrLowestHalf == 0;
+				string	statNamePrefix	= isEntireRange ? "" : "LowestHalf";
+				float	rangeSizeRatio	= isEntireRange ? 1.0f : 0.5f;
+
+#define LOG_TIME(NAME, DESC, DATA) log << TestLog::Float(statNamePrefix + (NAME) + singular, singular + " of " + (DESC), "ms", QP_KEY_TAG_TIME, func(vectorLowestPercentage((DATA), rangeSizeRatio))/1000.0f);
+
+				log << TestLog::Message << (isEntireRange ? "For all measurements:" : "\nFor only the lowest 50% of the measurements:") << TestLog::EndMessage;
+				LOG_TIME("ShaderSourceSetTime",			"shader source set time",			sourceSetTimes);
+				LOG_TIME("VertexShaderCompileTime",		"vertex shader compile time",		vertexCompileTimes);
+				LOG_TIME("FragmentShaderCompileTime",	"fragment shader compile time",		fragmentCompileTimes);
+
+#undef LOG_TIME
+			}
+
+			log << TestLog::EndSection;
+		}
+
+		// Set result.
+
+		{
+			log << TestLog::Message << "Note: test result is the first quartile (i.e. median of the lowest half of measurements) of total times" << TestLog::EndMessage;
+			float result = vectorFloatFirstQuartile(totalTimes) / 1000.0f;
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(result, 2).c_str());
+		}
+
+		// Log shaders.
+
+		log << TestLog::Message << "Note: the following shaders are the ones from the last iteration; variables' names and some constant expressions differ between iterations." << TestLog::EndMessage;
+
+		logProgramData(latestBuildInfo, latestProgramContext);
+
+		return STOP;
+	}
+}
+
+InvalidShaderCompilerLightCase::InvalidShaderCompilerLightCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool isVertexCase, int numLights, LightType lightType)
+	: InvalidShaderCompilerCase	(context, name, description, caseID, invalidityType)
+	, m_isVertexCase			(isVertexCase)
+	, m_numLights				(numLights)
+	, m_lightType				(lightType)
+{
+}
+
+InvalidShaderCompilerLightCase::~InvalidShaderCompilerLightCase (void)
+{
+}
+
+InvalidShaderCompilerCase::ProgramContext InvalidShaderCompilerLightCase::generateShaderSources (int measurementNdx) const
+{
+	deUint32		specID			= getSpecializationID(measurementNdx);
+	ProgramContext	result;
+	ShaderValidity	shaderValidity	= m_invalidityType == INVALIDITY_INVALID_CHAR	? SHADER_VALIDITY_INVALID_CHAR
+									: m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+									: SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	result.vertShaderSource = specializeShaderSource(lightVertexTemplate(m_numLights, m_isVertexCase, m_lightType), specID, shaderValidity);
+	result.fragShaderSource = specializeShaderSource(lightFragmentTemplate(m_numLights, m_isVertexCase, m_lightType), specID, shaderValidity);
+
+	return result;
+}
+
+InvalidShaderCompilerTextureCase::InvalidShaderCompilerTextureCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+	: InvalidShaderCompilerCase	(context, name, description, caseID, invalidityType)
+	, m_numLookups				(numLookups)
+	, m_conditionalUsage		(conditionalUsage)
+	, m_conditionalType			(conditionalType)
+{
+}
+
+InvalidShaderCompilerTextureCase::~InvalidShaderCompilerTextureCase (void)
+{
+}
+
+InvalidShaderCompilerCase::ProgramContext InvalidShaderCompilerTextureCase::generateShaderSources (int measurementNdx) const
+{
+	deUint32		specID			= getSpecializationID(measurementNdx);
+	ProgramContext	result;
+	ShaderValidity	shaderValidity	= m_invalidityType == INVALIDITY_INVALID_CHAR	? SHADER_VALIDITY_INVALID_CHAR
+									: m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+									: SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	result.vertShaderSource = specializeShaderSource(textureLookupVertexTemplate(m_conditionalUsage, m_conditionalType), specID, shaderValidity);
+	result.fragShaderSource = specializeShaderSource(textureLookupFragmentTemplate(m_numLookups, m_conditionalUsage, m_conditionalType), specID, shaderValidity);
+
+	return result;
+}
+
+InvalidShaderCompilerLoopCase::InvalidShaderCompilerLoopCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool isVertexCase, LoopType type, int numLoopIterations, int nestingDepth)
+	: InvalidShaderCompilerCase	(context, name, description, caseID, invalidityType)
+	, m_isVertexCase			(isVertexCase)
+	, m_numLoopIterations		(numLoopIterations)
+	, m_nestingDepth			(nestingDepth)
+	, m_type					(type)
+{
+}
+
+InvalidShaderCompilerLoopCase::~InvalidShaderCompilerLoopCase (void)
+{
+}
+
+InvalidShaderCompilerCase::ProgramContext InvalidShaderCompilerLoopCase::generateShaderSources (int measurementNdx) const
+{
+	deUint32		specID			= getSpecializationID(measurementNdx);
+	ProgramContext	result;
+	ShaderValidity	shaderValidity	= m_invalidityType == INVALIDITY_INVALID_CHAR	? SHADER_VALIDITY_INVALID_CHAR
+									: m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+									: SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	result.vertShaderSource = specializeShaderSource(loopVertexTemplate(m_type, m_isVertexCase, m_numLoopIterations, m_nestingDepth), specID, shaderValidity);
+	result.fragShaderSource = specializeShaderSource(loopFragmentTemplate(m_type, m_isVertexCase, m_numLoopIterations, m_nestingDepth), specID, shaderValidity);
+
+	return result;
+}
+
+InvalidShaderCompilerOperCase::InvalidShaderCompilerOperCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool isVertexCase, const char* oper, int numOperations)
+	: InvalidShaderCompilerCase	(context, name, description, caseID, invalidityType)
+	, m_isVertexCase			(isVertexCase)
+	, m_oper					(oper)
+	, m_numOperations			(numOperations)
+{
+}
+
+InvalidShaderCompilerOperCase::~InvalidShaderCompilerOperCase (void)
+{
+}
+
+InvalidShaderCompilerCase::ProgramContext InvalidShaderCompilerOperCase::generateShaderSources (int measurementNdx) const
+{
+	deUint32		specID			= getSpecializationID(measurementNdx);
+	ProgramContext	result;
+	ShaderValidity	shaderValidity	= m_invalidityType == INVALIDITY_INVALID_CHAR	? SHADER_VALIDITY_INVALID_CHAR
+									: m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+									: SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	if (m_isVertexCase)
+	{
+		result.vertShaderSource = specializeShaderSource(binaryOpVertexTemplate(m_numOperations, m_oper.c_str()), specID, shaderValidity);
+		result.fragShaderSource = specializeShaderSource(singleVaryingFragmentTemplate(), specID, shaderValidity);
+	}
+	else
+	{
+		result.vertShaderSource = specializeShaderSource(singleVaryingVertexTemplate(), specID, shaderValidity);
+		result.fragShaderSource = specializeShaderSource(binaryOpFragmentTemplate(m_numOperations, m_oper.c_str()), specID, shaderValidity);
+	}
+
+	return result;
+}
+
+InvalidShaderCompilerMandelbrotCase::InvalidShaderCompilerMandelbrotCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, int numFractalIterations)
+	: InvalidShaderCompilerCase	(context, name, description, caseID, invalidityType)
+	, m_numFractalIterations	(numFractalIterations)
+{
+}
+
+InvalidShaderCompilerMandelbrotCase::~InvalidShaderCompilerMandelbrotCase (void)
+{
+}
+
+InvalidShaderCompilerCase::ProgramContext InvalidShaderCompilerMandelbrotCase::generateShaderSources (int measurementNdx) const
+{
+	deUint32		specID			= getSpecializationID(measurementNdx);
+	ProgramContext	result;
+	ShaderValidity	shaderValidity	= m_invalidityType == INVALIDITY_INVALID_CHAR	? SHADER_VALIDITY_INVALID_CHAR
+									: m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+									: SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	result.vertShaderSource = specializeShaderSource(mandelbrotVertexTemplate(), specID, shaderValidity);
+	result.fragShaderSource = specializeShaderSource(mandelbrotFragmentTemplate(m_numFractalIterations), specID, shaderValidity);
+
+	return result;
+}
+
+void addShaderCompilationPerformanceCases (TestCaseGroup& parentGroup)
+{
+	Context&	context		= parentGroup.getContext();
+	int			caseID		= 0; // Increment this after adding each case. Used for avoiding cache hits between cases.
+
+	TestCaseGroup* validGroup			= new TestCaseGroup(context, "valid_shader",	"Valid Shader Compiler Cases");
+	TestCaseGroup* invalidGroup			= new TestCaseGroup(context, "invalid_shader",	"Invalid Shader Compiler Cases");
+	TestCaseGroup* cacheGroup			= new TestCaseGroup(context, "cache",			"Allow shader caching");
+	parentGroup.addChild(validGroup);
+	parentGroup.addChild(invalidGroup);
+	parentGroup.addChild(cacheGroup);
+
+	TestCaseGroup* invalidCharGroup		= new TestCaseGroup(context, "invalid_char",	"Invalid Character Shader Compiler Cases");
+	TestCaseGroup* semanticErrorGroup	= new TestCaseGroup(context, "semantic_error",	"Semantic Error Shader Compiler Cases");
+	invalidGroup->addChild(invalidCharGroup);
+	invalidGroup->addChild(semanticErrorGroup);
+
+	// Lighting shader compilation cases.
+
+	{
+		static const int lightCounts[] = { 1, 2, 4, 8 };
+
+		TestCaseGroup* validLightingGroup			= new TestCaseGroup(context, "lighting", "Shader Compiler Lighting Cases");
+		TestCaseGroup* invalidCharLightingGroup		= new TestCaseGroup(context, "lighting", "Invalid Character Shader Compiler Lighting Cases");
+		TestCaseGroup* semanticErrorLightingGroup	= new TestCaseGroup(context, "lighting", "Semantic Error Shader Compiler Lighting Cases");
+		TestCaseGroup* cacheLightingGroup			= new TestCaseGroup(context, "lighting", "Shader Compiler Lighting Cache Cases");
+		validGroup->addChild(validLightingGroup);
+		invalidCharGroup->addChild(invalidCharLightingGroup);
+		semanticErrorGroup->addChild(semanticErrorLightingGroup);
+		cacheGroup->addChild(cacheLightingGroup);
+
+		for (int lightType = 0; lightType < (int)LIGHT_LAST; lightType++)
+		{
+			const char* lightTypeName = lightType == (int)LIGHT_DIRECTIONAL	? "directional"
+									  : lightType == (int)LIGHT_POINT		? "point"
+									  : DE_NULL;
+
+			DE_ASSERT(lightTypeName != DE_NULL);
+
+			for (int isFrag = 0; isFrag <= 1; isFrag++)
+			{
+				bool		isVertex	= isFrag == 0;
+				const char*	vertFragStr	= isVertex ? "vertex" : "fragment";
+
+				for (int lightCountNdx = 0; lightCountNdx < DE_LENGTH_OF_ARRAY(lightCounts); lightCountNdx++)
+				{
+					int numLights = lightCounts[lightCountNdx];
+
+					string caseName = string("") + lightTypeName + "_" + de::toString(numLights) + "_lights_" + vertFragStr;
+
+					// Valid shader case, no-cache and cache versions.
+
+					validLightingGroup->addChild(new ShaderCompilerLightCase(context, caseName.c_str(), "", caseID++, true  /* avoid cache */, false, isVertex, numLights, (LightType)lightType));
+					cacheLightingGroup->addChild(new ShaderCompilerLightCase(context, caseName.c_str(), "", caseID++, false /* allow cache */, false, isVertex, numLights, (LightType)lightType));
+
+					// Invalid shader cases.
+
+					for (int invalidityType = 0; invalidityType < (int)InvalidShaderCompilerCase::INVALIDITY_LAST; invalidityType++)
+					{
+						TestCaseGroup* curInvalidGroup	= invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_INVALID_CHAR		? invalidCharLightingGroup
+														: invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_SEMANTIC_ERROR	? semanticErrorLightingGroup
+														: DE_NULL;
+
+						DE_ASSERT(curInvalidGroup != DE_NULL);
+
+						curInvalidGroup->addChild(new InvalidShaderCompilerLightCase(context, caseName.c_str(), "", caseID++, (InvalidShaderCompilerCase::InvalidityType)invalidityType, isVertex, numLights, (LightType)lightType));
+					}
+				}
+			}
+		}
+	}
+
+	// Texture lookup shader compilation cases.
+
+	{
+		static const int texLookupCounts[] = { 1, 2, 4, 8 };
+
+		TestCaseGroup* validTexGroup			= new TestCaseGroup(context, "texture", "Shader Compiler Texture Lookup Cases");
+		TestCaseGroup* invalidCharTexGroup		= new TestCaseGroup(context, "texture", "Invalid Character Shader Compiler Texture Lookup Cases");
+		TestCaseGroup* semanticErrorTexGroup	= new TestCaseGroup(context, "texture", "Semantic Error Shader Compiler Texture Lookup Cases");
+		TestCaseGroup* cacheTexGroup			= new TestCaseGroup(context, "texture", "Shader Compiler Texture Lookup Cache Cases");
+		validGroup->addChild(validTexGroup);
+		invalidCharGroup->addChild(invalidCharTexGroup);
+		semanticErrorGroup->addChild(semanticErrorTexGroup);
+		cacheGroup->addChild(cacheTexGroup);
+
+		for (int conditionalUsage = 0; conditionalUsage < (int)CONDITIONAL_USAGE_LAST; conditionalUsage++)
+		{
+			const char* conditionalUsageName = conditionalUsage == (int)CONDITIONAL_USAGE_NONE			? "no_conditionals"
+											 : conditionalUsage == (int)CONDITIONAL_USAGE_FIRST_HALF	? "first_half"
+											 : conditionalUsage == (int)CONDITIONAL_USAGE_EVERY_OTHER	? "every_other"
+											 : DE_NULL;
+
+			DE_ASSERT(conditionalUsageName != DE_NULL);
+
+			int lastConditionalType = conditionalUsage == (int)CONDITIONAL_USAGE_NONE ? 1 : (int)CONDITIONAL_TYPE_LAST;
+
+			for (int conditionalType = 0; conditionalType < lastConditionalType; conditionalType++)
+			{
+				const char* conditionalTypeName = conditionalType == (int)CONDITIONAL_TYPE_STATIC	? "static_conditionals"
+												: conditionalType == (int)CONDITIONAL_TYPE_UNIFORM	? "uniform_conditionals"
+												: conditionalType == (int)CONDITIONAL_TYPE_DYNAMIC	? "dynamic_conditionals"
+												: DE_NULL;
+
+				DE_ASSERT(conditionalTypeName != DE_NULL);
+
+				for (int lookupCountNdx = 0; lookupCountNdx < DE_LENGTH_OF_ARRAY(texLookupCounts); lookupCountNdx++)
+				{
+					int numLookups = texLookupCounts[lookupCountNdx];
+
+					string caseName = de::toString(numLookups) + "_lookups_" + conditionalUsageName + (conditionalUsage == (int)CONDITIONAL_USAGE_NONE ? "" : string("_") + conditionalTypeName);
+
+					// Valid shader case, no-cache and cache versions.
+
+					validTexGroup->addChild(new ShaderCompilerTextureCase(context, caseName.c_str(), "", caseID++, true  /* avoid cache */, false, numLookups, (ConditionalUsage)conditionalUsage, (ConditionalType)conditionalType));
+					cacheTexGroup->addChild(new ShaderCompilerTextureCase(context, caseName.c_str(), "", caseID++, false /* allow cache */, false, numLookups, (ConditionalUsage)conditionalUsage, (ConditionalType)conditionalType));
+
+					// Invalid shader cases.
+
+					for (int invalidityType = 0; invalidityType < (int)InvalidShaderCompilerCase::INVALIDITY_LAST; invalidityType++)
+					{
+						TestCaseGroup* curInvalidGroup	= invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_INVALID_CHAR		? invalidCharTexGroup
+														: invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_SEMANTIC_ERROR	? semanticErrorTexGroup
+														: DE_NULL;
+
+						DE_ASSERT(curInvalidGroup != DE_NULL);
+
+						curInvalidGroup->addChild(new InvalidShaderCompilerTextureCase(context, caseName.c_str(), "", caseID++, (InvalidShaderCompilerCase::InvalidityType)invalidityType, numLookups, (ConditionalUsage)conditionalUsage, (ConditionalType)conditionalType));
+					}
+				}
+			}
+		}
+	}
+
+	// Loop shader compilation cases.
+
+	{
+		static const int loopIterCounts[]		= { 10, 100, 1000 };
+		static const int maxLoopNestingDepth	= 3;
+		static const int maxTotalLoopIterations	= 2000; // If <loop iteration count> ** <loop nesting depth> (where ** is exponentiation) exceeds this, don't generate the case.
+
+		TestCaseGroup* validLoopGroup			= new TestCaseGroup(context, "loop", "Shader Compiler Loop Cases");
+		TestCaseGroup* invalidCharLoopGroup		= new TestCaseGroup(context, "loop", "Invalid Character Shader Compiler Loop Cases");
+		TestCaseGroup* semanticErrorLoopGroup	= new TestCaseGroup(context, "loop", "Semantic Error Shader Compiler Loop Cases");
+		TestCaseGroup* cacheLoopGroup			= new TestCaseGroup(context, "loop", "Shader Compiler Loop Cache Cases");
+		validGroup->addChild(validLoopGroup);
+		invalidCharGroup->addChild(invalidCharLoopGroup);
+		semanticErrorGroup->addChild(semanticErrorLoopGroup);
+		cacheGroup->addChild(cacheLoopGroup);
+
+		for (int loopType = 0; loopType < (int)LOOP_LAST; loopType++)
+		{
+			const char* loopTypeName = loopType == (int)LOOP_TYPE_STATIC	? "static"
+									 : loopType == (int)LOOP_TYPE_UNIFORM	? "uniform"
+									 : loopType == (int)LOOP_TYPE_DYNAMIC	? "dynamic"
+									 : DE_NULL;
+
+			DE_ASSERT(loopTypeName != DE_NULL);
+
+			TestCaseGroup* validLoopTypeGroup			= new TestCaseGroup(context, loopTypeName, "");
+			TestCaseGroup* invalidCharLoopTypeGroup		= new TestCaseGroup(context, loopTypeName, "");
+			TestCaseGroup* semanticErrorLoopTypeGroup	= new TestCaseGroup(context, loopTypeName, "");
+			TestCaseGroup* cacheLoopTypeGroup			= new TestCaseGroup(context, loopTypeName, "");
+			validLoopGroup->addChild(validLoopTypeGroup);
+			invalidCharLoopGroup->addChild(invalidCharLoopTypeGroup);
+			semanticErrorLoopGroup->addChild(semanticErrorLoopTypeGroup);
+			cacheLoopGroup->addChild(cacheLoopTypeGroup);
+
+			for (int isFrag = 0; isFrag <= 1; isFrag++)
+			{
+				bool		isVertex	= isFrag == 0;
+				const char*	vertFragStr	= isVertex ? "vertex" : "fragment";
+
+				// \note Non-static loop cases with different iteration counts have identical shaders, so only make one of each.
+				int loopIterCountMaxNdx = loopType != (int)LOOP_TYPE_STATIC ? 1 : DE_LENGTH_OF_ARRAY(loopIterCounts);
+
+				for (int nestingDepth = 1; nestingDepth <= maxLoopNestingDepth; nestingDepth++)
+				{
+					for (int loopIterCountNdx = 0; loopIterCountNdx < loopIterCountMaxNdx; loopIterCountNdx++)
+					{
+						int numIterations = loopIterCounts[loopIterCountNdx];
+
+						if (deFloatPow((float)numIterations, (float)nestingDepth) > (float)maxTotalLoopIterations)
+							continue; // Don't generate too heavy tasks.
+
+						string validCaseName = de::toString(numIterations) + "_iterations_" + de::toString(nestingDepth) + "_levels_" + vertFragStr;
+
+						// Valid shader case, no-cache and cache versions.
+
+						validLoopTypeGroup->addChild(new ShaderCompilerLoopCase(context, validCaseName.c_str(), "", caseID++, true  /* avoid cache */, false, isVertex, (LoopType)loopType, numIterations, nestingDepth));
+						cacheLoopTypeGroup->addChild(new ShaderCompilerLoopCase(context, validCaseName.c_str(), "", caseID++, false /* allow cache */, false, isVertex, (LoopType)loopType, numIterations, nestingDepth));
+
+						// Invalid shader cases.
+
+						for (int invalidityType = 0; invalidityType < (int)InvalidShaderCompilerCase::INVALIDITY_LAST; invalidityType++)
+						{
+							TestCaseGroup* curInvalidGroup	= invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_INVALID_CHAR		? invalidCharLoopTypeGroup
+															: invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_SEMANTIC_ERROR	? semanticErrorLoopTypeGroup
+															: DE_NULL;
+
+							DE_ASSERT(curInvalidGroup != DE_NULL);
+
+							string invalidCaseName = de::toString(nestingDepth) + "_levels_" + vertFragStr;
+
+							if (loopType == (int)LOOP_TYPE_STATIC)
+								invalidCaseName = de::toString(numIterations) + "_iterations_" + invalidCaseName; // \note For invalid, non-static loop cases the iteration count means nothing (since no uniforms or attributes are set).
+
+							curInvalidGroup->addChild(new InvalidShaderCompilerLoopCase(context, invalidCaseName.c_str(), "", caseID++, (InvalidShaderCompilerCase::InvalidityType)invalidityType, isVertex, (LoopType)loopType, numIterations, nestingDepth));
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// Multiplication shader compilation cases.
+
+	{
+		static const int multiplicationCounts[] = { 10, 100, 1000 };
+
+		TestCaseGroup* validMulGroup			= new TestCaseGroup(context, "multiplication", "Shader Compiler Multiplication Cases");
+		TestCaseGroup* invalidCharMulGroup		= new TestCaseGroup(context, "multiplication", "Invalid Character Shader Compiler Multiplication Cases");
+		TestCaseGroup* semanticErrorMulGroup	= new TestCaseGroup(context, "multiplication", "Semantic Error Shader Compiler Multiplication Cases");
+		TestCaseGroup* cacheMulGroup			= new TestCaseGroup(context, "multiplication", "Shader Compiler Multiplication Cache Cases");
+		validGroup->addChild(validMulGroup);
+		invalidCharGroup->addChild(invalidCharMulGroup);
+		semanticErrorGroup->addChild(semanticErrorMulGroup);
+		cacheGroup->addChild(cacheMulGroup);
+
+		for (int isFrag = 0; isFrag <= 1; isFrag++)
+		{
+			bool		isVertex	= isFrag == 0;
+			const char*	vertFragStr	= isVertex ? "vertex" : "fragment";
+
+			for (int operCountNdx = 0; operCountNdx < DE_LENGTH_OF_ARRAY(multiplicationCounts); operCountNdx++)
+			{
+				int numOpers = multiplicationCounts[operCountNdx];
+
+				string caseName = de::toString(numOpers) + "_operations_" + vertFragStr;
+
+				// Valid shader case, no-cache and cache versions.
+
+				validMulGroup->addChild(new ShaderCompilerOperCase(context, caseName.c_str(), "", caseID++, true  /* avoid cache */, false, isVertex, "*", numOpers));
+				cacheMulGroup->addChild(new ShaderCompilerOperCase(context, caseName.c_str(), "", caseID++, false /* allow cache */, false, isVertex, "*", numOpers));
+
+				// Invalid shader cases.
+
+				for (int invalidityType = 0; invalidityType < (int)InvalidShaderCompilerCase::INVALIDITY_LAST; invalidityType++)
+				{
+					TestCaseGroup* curInvalidGroup	= invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_INVALID_CHAR		? invalidCharMulGroup
+													: invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_SEMANTIC_ERROR	? semanticErrorMulGroup
+													: DE_NULL;
+
+					DE_ASSERT(curInvalidGroup != DE_NULL);
+
+					curInvalidGroup->addChild(new InvalidShaderCompilerOperCase(context, caseName.c_str(), "", caseID++, (InvalidShaderCompilerCase::InvalidityType)invalidityType, isVertex, "*", numOpers));
+				}
+			}
+		}
+	}
+
+	// Mandelbrot shader compilation cases.
+
+	{
+		static const int mandelbrotIterationCounts[] = { 32, 64, 128 };
+
+		TestCaseGroup* validMandelbrotGroup			= new TestCaseGroup(context, "mandelbrot", "Shader Compiler Mandelbrot Fractal Cases");
+		TestCaseGroup* invalidCharMandelbrotGroup	= new TestCaseGroup(context, "mandelbrot", "Invalid Character Shader Compiler Mandelbrot Fractal Cases");
+		TestCaseGroup* semanticErrorMandelbrotGroup	= new TestCaseGroup(context, "mandelbrot", "Semantic Error Shader Compiler Mandelbrot Fractal Cases");
+		TestCaseGroup* cacheMandelbrotGroup			= new TestCaseGroup(context, "mandelbrot", "Shader Compiler Mandelbrot Fractal Cache Cases");
+		validGroup->addChild(validMandelbrotGroup);
+		invalidCharGroup->addChild(invalidCharMandelbrotGroup);
+		semanticErrorGroup->addChild(semanticErrorMandelbrotGroup);
+		cacheGroup->addChild(cacheMandelbrotGroup);
+
+		for (int iterCountNdx = 0; iterCountNdx < DE_LENGTH_OF_ARRAY(mandelbrotIterationCounts); iterCountNdx++)
+		{
+			int		numFractalIterations	= mandelbrotIterationCounts[iterCountNdx];
+			string	caseName				= de::toString(numFractalIterations) + "_iterations";
+
+			// Valid shader case, no-cache and cache versions.
+
+			validMandelbrotGroup->addChild(new ShaderCompilerMandelbrotCase(context, caseName.c_str(), "", caseID++, true  /* avoid cache */, false, numFractalIterations));
+			cacheMandelbrotGroup->addChild(new ShaderCompilerMandelbrotCase(context, caseName.c_str(), "", caseID++, false /* allow cache */, false, numFractalIterations));
+
+			// Invalid shader cases.
+
+			for (int invalidityType = 0; invalidityType < (int)InvalidShaderCompilerCase::INVALIDITY_LAST; invalidityType++)
+			{
+				TestCaseGroup* curInvalidGroup	= invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_INVALID_CHAR		? invalidCharMandelbrotGroup
+												: invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_SEMANTIC_ERROR	? semanticErrorMandelbrotGroup
+												: DE_NULL;
+
+				DE_ASSERT(curInvalidGroup != DE_NULL);
+
+				curInvalidGroup->addChild(new InvalidShaderCompilerMandelbrotCase(context, caseName.c_str(), "", caseID++, (InvalidShaderCompilerCase::InvalidityType)invalidityType, numFractalIterations));
+			}
+		}
+	}
+
+	// Cases testing cache behaviour when whitespace and comments are added.
+
+	{
+		TestCaseGroup* whitespaceCommentCacheGroup = new TestCaseGroup(context, "cache_whitespace_comment", "Cases testing the effect of whitespace and comments on caching");
+		parentGroup.addChild(whitespaceCommentCacheGroup);
+
+		// \note Add just a small subset of the cases that were added above for the main performance tests.
+
+		// Cases with both vertex and fragment variants.
+		for (int isFrag = 0; isFrag <= 1; isFrag++)
+		{
+			bool	isVertex		= isFrag == 0;
+			string	vtxFragSuffix	= isVertex ? "_vertex" : "_fragment";
+			string	dirLightName	= "directional_2_lights" + vtxFragSuffix;
+			string	loopName		= "static_loop_100_iterations" + vtxFragSuffix;
+			string	multCase		= "multiplication_100_operations" + vtxFragSuffix;
+
+			whitespaceCommentCacheGroup->addChild(new ShaderCompilerLightCase(context, dirLightName.c_str(), "", caseID++, false, true, isVertex, 2, LIGHT_DIRECTIONAL));
+			whitespaceCommentCacheGroup->addChild(new ShaderCompilerLoopCase(context, loopName.c_str(), "", caseID++, false, true, isVertex, LOOP_TYPE_STATIC, 100, 1));
+			whitespaceCommentCacheGroup->addChild(new ShaderCompilerOperCase(context, multCase.c_str(), "", caseID++, false, true, isVertex, "*", 100));
+		}
+
+		// Cases that don't have vertex and fragment variants.
+		whitespaceCommentCacheGroup->addChild(new ShaderCompilerTextureCase(context, "texture_4_lookups", "", caseID++, false, true, 4, CONDITIONAL_USAGE_NONE, CONDITIONAL_TYPE_STATIC));
+		whitespaceCommentCacheGroup->addChild(new ShaderCompilerMandelbrotCase(context, "mandelbrot_32_operations", "", caseID++, false, true, 32));
+	}
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pShaderCompilationCases.hpp b/modules/gles2/performance/es2pShaderCompilationCases.hpp
new file mode 100644
index 0000000..5bc4fa0
--- /dev/null
+++ b/modules/gles2/performance/es2pShaderCompilationCases.hpp
@@ -0,0 +1,42 @@
+#ifndef _ES2PSHADERCOMPILATIONCASES_HPP
+#define _ES2PSHADERCOMPILATIONCASES_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader compilation performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+void addShaderCompilationPerformanceCases (TestCaseGroup& parentGroup);
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PSHADERCOMPILATIONCASES_HPP
diff --git a/modules/gles2/performance/es2pShaderCompilerTests.cpp b/modules/gles2/performance/es2pShaderCompilerTests.cpp
new file mode 100644
index 0000000..c892498
--- /dev/null
+++ b/modules/gles2/performance/es2pShaderCompilerTests.cpp
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader compiler-related performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pShaderCompilerTests.hpp"
+#include "es2pShaderCompilationCases.hpp"
+#include "es2pShaderOptimizationTests.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+ShaderCompilerTests::ShaderCompilerTests (Context& context)
+	: TestCaseGroup(context, "compiler", "Shader Compiler Performance Tests")
+{
+}
+
+ShaderCompilerTests::~ShaderCompilerTests (void)
+{
+}
+
+void ShaderCompilerTests::init (void)
+{
+	addShaderCompilationPerformanceCases(*this);
+
+	addChild(new ShaderOptimizationTests(m_context));
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pShaderCompilerTests.hpp b/modules/gles2/performance/es2pShaderCompilerTests.hpp
new file mode 100644
index 0000000..e2f3b4c
--- /dev/null
+++ b/modules/gles2/performance/es2pShaderCompilerTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PSHADERCOMPILERTESTS_HPP
+#define _ES2PSHADERCOMPILERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader compiler-related performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class ShaderCompilerTests : public TestCaseGroup
+{
+public:
+							ShaderCompilerTests		(Context& context);
+							~ShaderCompilerTests	(void);
+
+	void					init					(void);
+
+private:
+							ShaderCompilerTests		(const ShaderCompilerTests& other);
+	ShaderCompilerTests&	operator=				(const ShaderCompilerTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PSHADERCOMPILERTESTS_HPP
diff --git a/modules/gles2/performance/es2pShaderControlStatementTests.cpp b/modules/gles2/performance/es2pShaderControlStatementTests.cpp
new file mode 100644
index 0000000..1326711
--- /dev/null
+++ b/modules/gles2/performance/es2pShaderControlStatementTests.cpp
@@ -0,0 +1,965 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader control statement performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pShaderControlStatementTests.hpp"
+#include "glsShaderPerformanceCase.hpp"
+#include "tcuTestLog.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <string>
+#include <vector>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+using namespace gls;
+using namespace glw; // GL types
+using tcu::Vec4;
+using tcu::TestLog;
+using std::string;
+using std::vector;
+
+// Writes the workload expression used in conditional tests.
+static void writeConditionalWorkload (std::ostringstream& stream, const char* resultName, const char* operandName)
+{
+	const int numMultiplications = 64;
+
+	stream << resultName << " = ";
+
+	for (int i = 0; i < numMultiplications; i++)
+	{
+		if (i > 0)
+			stream << "*";
+
+		stream << operandName;
+	}
+
+	stream << ";";
+}
+
+// Writes the workload expression used in loop tests (one iteration).
+static void writeLoopWorkload (std::ostringstream& stream, const char* resultName, const char* operandName)
+{
+	const int numMultiplications = 8;
+
+	stream << resultName << " = ";
+
+	for (int i = 0; i < numMultiplications; i++)
+	{
+		if (i > 0)
+			stream << " * ";
+
+		stream << "(" << resultName << " + " << operandName << ")";
+	}
+
+	stream << ";";
+}
+
+// The type of decision to be made in a conditional expression.
+// \note In fragment cases with DECISION_ATTRIBUTE, the value in the expression will actually be a varying.
+enum DecisionType
+{
+	DECISION_STATIC = 0,
+	DECISION_UNIFORM,
+	DECISION_ATTRIBUTE,
+
+	DECISION_LAST
+};
+
+class ControlStatementCase :  public ShaderPerformanceCase
+{
+public:
+	ControlStatementCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, gls::PerfCaseType caseType)
+		: ShaderPerformanceCase(testCtx, renderCtx, name, description, caseType)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Using additive blending." << TestLog::EndMessage;
+		ShaderPerformanceCase::init();
+	}
+
+	void setupRenderState (void)
+	{
+		const glw::Functions& gl = m_renderCtx.getFunctions();
+
+		gl.enable(GL_BLEND);
+		gl.blendEquation(GL_FUNC_ADD);
+		gl.blendFunc(GL_ONE, GL_ONE);
+	}
+};
+
+class ConditionalCase : public ControlStatementCase
+{
+public:
+	enum BranchResult
+	{
+		BRANCH_TRUE = 0,
+		BRANCH_FALSE,
+		BRANCH_MIXED,
+
+		BRANCH_LAST
+	};
+
+	enum WorkloadDivision
+	{
+		WORKLOAD_DIVISION_EVEN = 0,		//! Both true and false branches contain same amount of computation.
+		WORKLOAD_DIVISION_TRUE_HEAVY,	//! True branch contains more computation.
+		WORKLOAD_DIVISION_FALSE_HEAVY,	//! False branch contains more computation.
+
+		WORKLOAD_DIVISION_LAST
+	};
+
+						ConditionalCase		(Context& context, const char* name, const char* description, DecisionType decisionType, BranchResult branchType, WorkloadDivision workloadDivision, bool isVertex);
+						~ConditionalCase	(void);
+
+	void				init				(void);
+	void				deinit				(void);
+	void				setupProgram		(deUint32 program);
+
+private:
+	DecisionType		m_decisionType;
+	BranchResult		m_branchType;
+	WorkloadDivision	m_workloadDivision;
+
+	vector<float>		m_comparisonValueArray; // Will contain per-vertex comparison values if using mixed branch type in vertex case.
+	deUint32			m_arrayBuffer;
+};
+
+ConditionalCase::ConditionalCase (Context& context, const char* name, const char* description, DecisionType decisionType, BranchResult branchType, WorkloadDivision workloadDivision, bool isVertex)
+	: ControlStatementCase			(context.getTestContext(), context.getRenderContext(), name, description, isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT)
+	, m_decisionType				(decisionType)
+	, m_branchType					(branchType)
+	, m_workloadDivision			(workloadDivision)
+	, m_arrayBuffer					(0)
+{
+}
+
+void ConditionalCase::init (void)
+{
+	bool			isVertexCase		= m_caseType == CASETYPE_VERTEX;
+
+	bool			isStaticCase		= m_decisionType == DECISION_STATIC;
+	bool			isUniformCase		= m_decisionType == DECISION_UNIFORM;
+	bool			isAttributeCase		= m_decisionType == DECISION_ATTRIBUTE;
+
+	DE_ASSERT(isStaticCase || isUniformCase || isAttributeCase);
+
+	bool			isConditionTrue		= m_branchType == BRANCH_TRUE;
+	bool			isConditionFalse	= m_branchType == BRANCH_FALSE;
+	bool			isConditionMixed	= m_branchType == BRANCH_MIXED;
+
+	DE_ASSERT(isConditionTrue || isConditionFalse || isConditionMixed);
+	DE_UNREF(isConditionFalse);
+
+	DE_ASSERT(isAttributeCase || !isConditionMixed); // The branch taken can vary between executions only if using attribute input.
+
+	const char*		staticCompareValueStr	= isConditionTrue	? "1.0" : "-1.0";
+	const char*		compareValueStr			= isStaticCase		? staticCompareValueStr :
+											  isUniformCase		? "u_compareValue" :
+											  isVertexCase		? "a_compareValue" :
+																  "v_compareValue";
+
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op		= isVertexCase ? vtx : frag;
+
+	vtx << "attribute highp vec4 a_position;\n";	// Position attribute.
+	vtx << "attribute mediump vec4 a_value0;\n";	// Input for workload calculations of "true" branch.
+	vtx << "attribute mediump vec4 a_value1;\n";	// Input for workload calculations of "false" branch.
+
+	// Value to be used in the conditional expression.
+	if (isAttributeCase)
+		vtx << "attribute mediump float a_compareValue;\n";
+	else if (isUniformCase)
+		op << "uniform mediump float u_compareValue;\n";
+
+	// Varyings.
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "varying mediump vec4 v_value0;\n";
+		vtx << "varying mediump vec4 v_value1;\n";
+		frag << "varying mediump vec4 v_value0;\n";
+		frag << "varying mediump vec4 v_value1;\n";
+
+		if (isAttributeCase)
+		{
+			vtx << "varying mediump float v_compareValue;\n";
+			frag << "varying mediump float v_compareValue;\n";
+		}
+	}
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	op << "	mediump vec4 res;\n";
+
+	string condition;
+
+	if (isConditionMixed && !isVertexCase)
+		condition = string("") + "fract(" + compareValueStr + ") < 0.5"; // Comparison result varies with high frequency.
+	else
+		condition = string("") + compareValueStr + " > 0.0";
+
+	op << "	if (" << condition << ")\n";
+	op << "	{\n";
+
+	op << "\t\t";
+	if (m_workloadDivision == WORKLOAD_DIVISION_EVEN || m_workloadDivision == WORKLOAD_DIVISION_TRUE_HEAVY)
+		writeConditionalWorkload(op, "res", isVertexCase ? "a_value0" : "v_value0"); // Workload calculation for the "true" branch.
+	else
+		op << "res = " << (isVertexCase ? "a_value0" : "v_value0") << ";";
+	op << "\n";
+
+	op << "	}\n";
+	op << "	else\n";
+	op << "	{\n";
+
+	op << "\t\t";
+	if (m_workloadDivision == WORKLOAD_DIVISION_EVEN || m_workloadDivision == WORKLOAD_DIVISION_FALSE_HEAVY)
+		writeConditionalWorkload(op, "res", isVertexCase ? "a_value1" : "v_value1"); // Workload calculations for the "false" branch.
+	else
+		op << "res = " << (isVertexCase ? "a_value1" : "v_value1") << ";";
+	op << "\n";
+
+	op << "	}\n";
+
+	if (isVertexCase)
+	{
+		// Put result to color variable.
+		vtx << "	v_color = res;\n";
+		frag << "	gl_FragColor = v_color;\n";
+	}
+	else
+	{
+		// Transfer inputs to fragment shader through varyings.
+		if (isAttributeCase)
+			vtx << "	v_compareValue = a_compareValue;\n";
+		vtx << "	v_value0 = a_value0;\n";
+		vtx << "	v_value1 = a_value1;\n";
+
+		frag << "	gl_FragColor = res;\n"; // Put result to color variable.
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource = vtx.str();
+	m_fragShaderSource = frag.str();
+
+	if (isAttributeCase)
+	{
+		if (!isConditionMixed)
+		{
+			// Every execution takes the same branch.
+
+			float value = isConditionTrue ? +1.0f : -1.0f;
+			m_attributes.push_back(AttribSpec("a_compareValue",	Vec4(value, 0.0f, 0.0f, 0.0f),
+																Vec4(value, 0.0f, 0.0f, 0.0f),
+																Vec4(value, 0.0f, 0.0f, 0.0f),
+																Vec4(value, 0.0f, 0.0f, 0.0f)));
+		}
+		else if (isVertexCase)
+		{
+			// Vertex case, not every execution takes the same branch.
+
+			const int	numComponents	= 4;
+			int			numVertices		= (getGridWidth() + 1) * (getGridHeight() + 1);
+
+			// setupProgram() will later bind this array as an attribute.
+			m_comparisonValueArray.resize(numVertices * numComponents);
+
+			// Make every second vertex take the true branch, and every second the false branch.
+			for (int i = 0; i < (int)m_comparisonValueArray.size(); i++)
+			{
+				if (i % numComponents == 0)
+					m_comparisonValueArray[i] = (i / numComponents) % 2 == 0 ? +1.0f : -1.0f;
+				else
+					m_comparisonValueArray[i] = 0.0f;
+			}
+		}
+		else // isConditionMixed && !isVertexCase
+		{
+			// Fragment case, not every execution takes the same branch.
+			// \note fract(a_compareValue) < 0.5 will be true for every second column of fragments.
+
+			float minValue = 0.0f;
+			float maxValue = (float)getViewportWidth()*0.5f;
+			m_attributes.push_back(AttribSpec("a_compareValue",	Vec4(minValue, 0.0f, 0.0f, 0.0f),
+																Vec4(maxValue, 0.0f, 0.0f, 0.0f),
+																Vec4(minValue, 0.0f, 0.0f, 0.0f),
+																Vec4(maxValue, 0.0f, 0.0f, 0.0f)));
+		}
+	}
+
+	m_attributes.push_back(AttribSpec("a_value0",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
+													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
+													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
+													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));
+
+	m_attributes.push_back(AttribSpec("a_value1",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
+													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
+													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
+													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));
+
+	ControlStatementCase::init();
+}
+
+void ConditionalCase::setupProgram (deUint32 program)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_decisionType == DECISION_UNIFORM)
+	{
+		int location = gl.getUniformLocation(program, "u_compareValue");
+		gl.uniform1f(location, m_branchType == BRANCH_TRUE ? +1.0f : -1.0f);
+	}
+	else if (m_decisionType == DECISION_ATTRIBUTE && m_branchType == BRANCH_MIXED && m_caseType == CASETYPE_VERTEX)
+	{
+		// Setup per-vertex comparison values calculated in init().
+
+		const int	numComponents			= 4;
+		int			compareAttribLocation	= gl.getAttribLocation(program, "a_compareValue");
+
+		DE_ASSERT((int)m_comparisonValueArray.size() == numComponents * (getGridWidth() + 1) * (getGridHeight() + 1));
+
+		gl.genBuffers(1, &m_arrayBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(m_comparisonValueArray.size()*sizeof(float)), &m_comparisonValueArray[0], GL_STATIC_DRAW);
+		gl.enableVertexAttribArray(compareAttribLocation);
+		gl.vertexAttribPointer(compareAttribLocation, (GLint)numComponents, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Setup program state");
+}
+
+ConditionalCase::~ConditionalCase (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_arrayBuffer != 0)
+	{
+		gl.deleteBuffers(1, &m_arrayBuffer);
+		m_arrayBuffer = 0;
+	}
+}
+
+void ConditionalCase::deinit (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	m_comparisonValueArray.clear();
+
+	if (m_arrayBuffer != 0)
+	{
+		gl.deleteBuffers(1, &m_arrayBuffer);
+		m_arrayBuffer = 0;
+	}
+
+	ShaderPerformanceCase::deinit();
+}
+
+class LoopCase : public ControlStatementCase
+{
+public:
+	enum LoopType
+	{
+		LOOP_FOR = 0,
+		LOOP_WHILE,
+		LOOP_DO_WHILE,
+
+		LOOP_LAST
+	};
+					LoopCase			(Context& context, const char* name, const char* description, LoopType type, DecisionType decisionType, bool isLoopBoundStable, bool isVertex);
+					~LoopCase			(void);
+
+	void			init				(void);
+	void			deinit				(void);
+	void			setupProgram		(deUint32 program);
+
+private:
+	DecisionType	m_decisionType;
+	LoopType		m_type;
+
+	bool			m_isLoopBoundStable;	// Whether loop bound is same in all executions.
+	vector<float>	m_boundArray;			// Will contain per-vertex loop bounds if using non-stable attribute in vertex case.
+	deUint32		m_arrayBuffer;
+};
+
+LoopCase::LoopCase (Context& context, const char* name, const char* description, LoopType type, DecisionType decisionType, bool isLoopBoundStable, bool isVertex)
+	: ControlStatementCase	(context.getTestContext(), context.getRenderContext(), name, description, isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT)
+	, m_decisionType		(decisionType)
+	, m_type				(type)
+	, m_isLoopBoundStable	(isLoopBoundStable)
+	, m_arrayBuffer			(0)
+{
+}
+
+void LoopCase::init (void)
+{
+	bool				isVertexCase	= m_caseType == CASETYPE_VERTEX;
+
+	bool				isStaticCase	= m_decisionType == DECISION_STATIC;
+	bool				isUniformCase	= m_decisionType == DECISION_UNIFORM;
+	bool				isAttributeCase	= m_decisionType == DECISION_ATTRIBUTE;
+
+	DE_ASSERT(isStaticCase || isUniformCase || isAttributeCase);
+
+	DE_ASSERT(m_type == LOOP_FOR		||
+			  m_type == LOOP_WHILE		||
+			  m_type == LOOP_DO_WHILE);
+
+	DE_ASSERT(isAttributeCase || m_isLoopBoundStable); // The loop bound count can vary between executions only if using attribute input.
+
+	// \note The fractional part is .5 (instead of .0) so that these can be safely used as loop bounds.
+	const float			loopBound				= 10.5f;
+	const float			unstableBoundLow		= 5.5f;
+	const float			unstableBoundHigh		= 15.5f;
+	static const char*	loopBoundStr			= "10.5";
+	static const char*	unstableBoundLowStr		= "5.5";
+	static const char*	unstableBoundHighStr	= "15.5";
+
+	const char*			boundValueStr		= isStaticCase			? loopBoundStr :
+											  isUniformCase			? "u_bound" :
+											  isVertexCase			? "a_bound" :
+											  m_isLoopBoundStable	? "v_bound" :
+																	  "loopBound";
+
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op		= isVertexCase ? vtx : frag;
+
+	vtx << "attribute highp vec4 a_position;\n";	// Position attribute.
+	vtx << "attribute mediump vec4 a_value;\n";		// Input for workload calculations.
+
+	// Value to be used as the loop iteration count.
+	if (isAttributeCase)
+		vtx << "attribute mediump float a_bound;\n";
+	else if (isUniformCase)
+		op << "uniform mediump float u_bound;\n";
+
+	// Varyings.
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "varying mediump vec4 v_value;\n";
+		frag << "varying mediump vec4 v_value;\n";
+
+		if (isAttributeCase)
+		{
+			vtx << "varying mediump float v_bound;\n";
+			frag << "varying mediump float v_bound;\n";
+		}
+	}
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	op << "	mediump vec4 res = vec4(0.0);\n";
+
+	if (!m_isLoopBoundStable && !isVertexCase)
+	{
+		// Choose the actual loop bound based on v_bound.
+		// \note Loop bound will vary with high frequency between fragment columns, given appropriate range for v_bound.
+		op << "	mediump float loopBound = fract(v_bound) < 0.5 ? " << unstableBoundLowStr << " : " << unstableBoundHighStr << ";\n";
+	}
+
+	// Start a for, while or do-while loop.
+	if (m_type == LOOP_FOR)
+		op << "	for (mediump float i = 0.0; i < " << boundValueStr << "; i++)\n";
+	else
+	{
+		op << "	mediump float i = 0.0;\n";
+		if (m_type == LOOP_WHILE)
+			op << "	while (i < " << boundValueStr << ")\n";
+		else // LOOP_DO_WHILE
+			op << "	do\n";
+	}
+	op << "	{\n";
+
+	// Workload calculations inside the loop.
+	op << "\t\t";
+	writeLoopWorkload(op, "res", isVertexCase ? "a_value" : "v_value");
+	op << "\n";
+
+	// Only "for" has counter increment in the loop head.
+	if (m_type != LOOP_FOR)
+		op << "		i++;\n";
+
+	// End the loop.
+	if (m_type == LOOP_DO_WHILE)
+		op << "	} while (i < " << boundValueStr << ");\n";
+	else
+		op << "	}\n";
+
+	if (isVertexCase)
+	{
+		// Put result to color variable.
+		vtx << "	v_color = res;\n";
+		frag << "	gl_FragColor = v_color;\n";
+	}
+	else
+	{
+		// Transfer inputs to fragment shader through varyings.
+		if (isAttributeCase)
+			vtx << "	v_bound = a_bound;\n";
+		vtx << "	v_value = a_value;\n";
+
+		frag << "	gl_FragColor = res;\n"; // Put result to color variable.
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource = vtx.str();
+	m_fragShaderSource = frag.str();
+
+	if (isAttributeCase)
+	{
+		if (m_isLoopBoundStable)
+		{
+			// Every execution has same number of iterations.
+
+			m_attributes.push_back(AttribSpec("a_bound",	Vec4(loopBound, 0.0f, 0.0f, 0.0f),
+															Vec4(loopBound, 0.0f, 0.0f, 0.0f),
+															Vec4(loopBound, 0.0f, 0.0f, 0.0f),
+															Vec4(loopBound, 0.0f, 0.0f, 0.0f)));
+		}
+		else if (isVertexCase)
+		{
+			// Vertex case, with non-constant number of iterations.
+
+			const int	numComponents	= 4;
+			int			numVertices		= (getGridWidth() + 1) * (getGridHeight() + 1);
+
+			// setupProgram() will later bind this array as an attribute.
+			m_boundArray.resize(numVertices * numComponents);
+
+			// Vary between low and high loop bounds; they should average to loopBound however.
+			for (int i = 0; i < (int)m_boundArray.size(); i++)
+			{
+				if (i % numComponents == 0)
+					m_boundArray[i] = (i / numComponents) % 2 == 0 ? unstableBoundLow : unstableBoundHigh;
+				else
+					m_boundArray[i] = 0.0f;
+			}
+		}
+		else // !m_isLoopBoundStable && !isVertexCase
+		{
+			// Fragment case, with non-constant number of iterations.
+			// \note fract(a_bound) < 0.5 will be true for every second fragment.
+
+			float minValue = 0.0f;
+			float maxValue = (float)getViewportWidth()*0.5f;
+			m_attributes.push_back(AttribSpec("a_bound",	Vec4(minValue, 0.0f, 0.0f, 0.0f),
+															Vec4(maxValue, 0.0f, 0.0f, 0.0f),
+															Vec4(minValue, 0.0f, 0.0f, 0.0f),
+															Vec4(maxValue, 0.0f, 0.0f, 0.0f)));
+		}
+	}
+
+	m_attributes.push_back(AttribSpec("a_value",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
+													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
+													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
+													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));
+
+	ControlStatementCase::init();
+}
+
+void LoopCase::setupProgram (deUint32 program)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_decisionType == DECISION_UNIFORM)
+	{
+		const float loopBound = 10.5f;
+
+		int location = gl.getUniformLocation(program, "u_bound");
+		gl.uniform1f(location, loopBound);
+	}
+	else if (m_decisionType == DECISION_ATTRIBUTE && !m_isLoopBoundStable && m_caseType == CASETYPE_VERTEX)
+	{
+		// Setup per-vertex loop bounds calculated in init().
+
+		const int	numComponents		= 4;
+		int			boundAttribLocation	= gl.getAttribLocation(program, "a_bound");
+
+		DE_ASSERT((int)m_boundArray.size() == numComponents * (getGridWidth() + 1) * (getGridHeight() + 1));
+
+		gl.genBuffers(1, &m_arrayBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(m_boundArray.size()*sizeof(float)), &m_boundArray[0], GL_STATIC_DRAW);
+		gl.enableVertexAttribArray(boundAttribLocation);
+		gl.vertexAttribPointer(boundAttribLocation, (GLint)numComponents, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Setup program state");
+}
+
+LoopCase::~LoopCase (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_arrayBuffer)
+	{
+		gl.deleteBuffers(1, &m_arrayBuffer);
+		m_arrayBuffer = 0;
+	}
+}
+
+void LoopCase::deinit (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	m_boundArray.clear();
+
+	if (m_arrayBuffer)
+	{
+		gl.deleteBuffers(1, &m_arrayBuffer);
+		m_arrayBuffer = 0;
+	}
+
+	ShaderPerformanceCase::deinit();
+}
+
+// A reference case, same calculations as the actual tests but without control statements.
+class WorkloadReferenceCase : public ControlStatementCase
+{
+public:
+							WorkloadReferenceCase		(Context& context, const char* name, const char* description, bool isVertex);
+
+	void					init						(void);
+
+protected:
+	virtual void			writeWorkload				(std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const = 0;
+};
+
+WorkloadReferenceCase::WorkloadReferenceCase (Context& context, const char* name, const char* description, bool isVertex)
+	: ControlStatementCase(context.getTestContext(), context.getRenderContext(), name, description, isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT)
+{
+}
+
+void WorkloadReferenceCase::init (void)
+{
+	bool isVertexCase = m_caseType == CASETYPE_VERTEX;
+
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op			= isVertexCase ? vtx : frag;
+
+	vtx << "attribute highp vec4 a_position;\n";	// Position attribute.
+	vtx << "attribute mediump vec4 a_value;\n";		// Value for workload calculations.
+
+	// Varyings.
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "varying mediump vec4 v_value;\n";
+		frag << "varying mediump vec4 v_value;\n";
+	}
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	op << "\tmediump vec4 res;\n";
+	writeWorkload(op, "res", isVertexCase ? "a_value" : "v_value");
+
+	if (isVertexCase)
+	{
+		// Put result to color variable.
+		vtx << "	v_color = res;\n";
+		frag << "	gl_FragColor = v_color;\n";
+	}
+	else
+	{
+		vtx << "	v_value = a_value;\n";	// Transfer input to fragment shader through varying.
+		frag << "	gl_FragColor = res;\n";	// Put result to color variable.
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource = vtx.str();
+	m_fragShaderSource = frag.str();
+
+	m_attributes.push_back(AttribSpec("a_value",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
+													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
+													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
+													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));
+
+	ControlStatementCase::init();
+}
+
+class LoopWorkloadReferenceCase : public WorkloadReferenceCase
+{
+public:
+	LoopWorkloadReferenceCase (Context& context, const char* name, const char* description, bool isAttributeStable, bool isVertex)
+		: WorkloadReferenceCase		(context, name, description, isVertex)
+		, m_isAttributeStable		(isAttributeStable)
+	{
+	}
+
+protected:
+	void writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const;
+
+private:
+	bool m_isAttributeStable;
+};
+
+void LoopWorkloadReferenceCase::writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const
+{
+	const int	loopIterations	= 11;
+	bool		isVertexCase	= m_caseType == CASETYPE_VERTEX;
+
+	dst << "\t" << resultVariableName << " = vec4(0.0);\n";
+
+	for (int i = 0; i < loopIterations; i++)
+	{
+		dst << "\t";
+		writeLoopWorkload(dst, resultVariableName, inputVariableName);
+		dst << "\n";
+	}
+
+	if (!isVertexCase && !m_isAttributeStable)
+	{
+		// Corresponds to the fract() done in a real test's fragment case with non-stable attribute.
+		dst << "	res.x = fract(res.x);\n";
+	}
+}
+
+class ConditionalWorkloadReferenceCase : public WorkloadReferenceCase
+{
+public:
+	ConditionalWorkloadReferenceCase (Context& context, const char* name, const char* description, bool isAttributeStable, bool isVertex)
+		: WorkloadReferenceCase		(context, name, description, isVertex)
+		, m_isAttributeStable		(isAttributeStable)
+	{
+	}
+
+protected:
+	void writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const;
+
+private:
+	bool m_isAttributeStable;
+};
+
+void ConditionalWorkloadReferenceCase::writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const
+{
+	bool isVertexCase = m_caseType == CASETYPE_VERTEX;
+
+	dst << "\t";
+	writeConditionalWorkload(dst, resultVariableName, inputVariableName);
+	dst << "\n";
+
+	if (!isVertexCase && !m_isAttributeStable)
+	{
+		// Corresponds to the fract() done in a real test's fragment case with non-stable attribute.
+		dst << "	res.x = fract(res.x);\n";
+	}
+}
+
+// A workload reference case for e.g. a conditional case with a branch with no computation.
+class EmptyWorkloadReferenceCase : public WorkloadReferenceCase
+{
+public:
+	EmptyWorkloadReferenceCase	(Context& context, const char* name, const char* description, bool isVertex)
+		: WorkloadReferenceCase (context, name, description, isVertex)
+	{
+	}
+
+protected:
+	void writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const
+	{
+		dst << "\t" << resultVariableName << " = " << inputVariableName << ";\n";
+	}
+};
+
+ShaderControlStatementTests::ShaderControlStatementTests (Context& context)
+	: TestCaseGroup(context, "control_statement", "Control Statement Performance Tests")
+{
+}
+
+ShaderControlStatementTests::~ShaderControlStatementTests (void)
+{
+}
+
+void ShaderControlStatementTests::init (void)
+{
+	// Conditional cases (if-else).
+
+	tcu::TestCaseGroup* ifElseGroup = new tcu::TestCaseGroup(m_testCtx, "if_else", "if-else Conditional Performance Tests");
+	addChild(ifElseGroup);
+
+	for (int isFrag = 0; isFrag <= 1; isFrag++)
+	{
+		bool isVertex = isFrag == 0;
+		ShaderPerformanceCaseGroup* vertexOrFragmentGroup = new ShaderPerformanceCaseGroup(m_testCtx, isVertex ? "vertex" : "fragment", "");
+		ifElseGroup->addChild(vertexOrFragmentGroup);
+
+		DE_STATIC_ASSERT(DECISION_STATIC == 0);
+		for (int decisionType = (int)DECISION_STATIC; decisionType < (int)DECISION_LAST; decisionType++)
+		{
+			const char* decisionName = decisionType == (int)DECISION_STATIC		? "static" :
+										decisionType == (int)DECISION_UNIFORM	? "uniform" :
+										decisionType == (int)DECISION_ATTRIBUTE	? (isVertex ? "attribute" : "varying") :
+																					DE_NULL;
+			DE_ASSERT(decisionName != DE_NULL);
+
+			for (int workloadDivision = 0; workloadDivision < ConditionalCase::WORKLOAD_DIVISION_LAST; workloadDivision++)
+			{
+				const char* workloadDivisionSuffix = workloadDivision == (int)ConditionalCase::WORKLOAD_DIVISION_EVEN			? "" :
+													 workloadDivision == (int)ConditionalCase::WORKLOAD_DIVISION_TRUE_HEAVY		? "_with_heavier_true" :
+													 workloadDivision == (int)ConditionalCase::WORKLOAD_DIVISION_FALSE_HEAVY	? "_with_heavier_false" :
+																																  DE_NULL;
+				DE_ASSERT(workloadDivisionSuffix != DE_NULL);
+
+				DE_STATIC_ASSERT(ConditionalCase::BRANCH_TRUE == 0);
+				for (int branchResult = (int)ConditionalCase::BRANCH_TRUE; branchResult < (int)ConditionalCase::BRANCH_LAST; branchResult++)
+				{
+					if (decisionType != (int)DECISION_ATTRIBUTE && branchResult == (int)ConditionalCase::BRANCH_MIXED)
+						continue;
+
+					const char* branchResultName = branchResult == (int)ConditionalCase::BRANCH_TRUE	? "true" :
+												   branchResult == (int)ConditionalCase::BRANCH_FALSE	? "false" :
+												   branchResult == (int)ConditionalCase::BRANCH_MIXED	? "mixed" :
+																										  DE_NULL;
+					DE_ASSERT(branchResultName != DE_NULL);
+
+					string caseName = string("") + decisionName + "_" + branchResultName + workloadDivisionSuffix;
+
+					vertexOrFragmentGroup->addChild(new ConditionalCase(m_context, caseName.c_str(), "",
+																		(DecisionType)decisionType, (ConditionalCase::BranchResult)branchResult,
+																		(ConditionalCase::WorkloadDivision)workloadDivision, isVertex));
+				}
+			}
+		}
+
+		if (isVertex)
+			vertexOrFragmentGroup->addChild(new ConditionalWorkloadReferenceCase(m_context, "reference", "", true, isVertex));
+		else
+		{
+			// Only fragment case with BRANCH_MIXED has an additional fract() call.
+			vertexOrFragmentGroup->addChild(new ConditionalWorkloadReferenceCase(m_context, "reference_unmixed", "", true, isVertex));
+			vertexOrFragmentGroup->addChild(new ConditionalWorkloadReferenceCase(m_context, "reference_mixed", "", false, isVertex));
+		}
+
+		vertexOrFragmentGroup->addChild(new EmptyWorkloadReferenceCase(m_context, "reference_empty", "", isVertex));
+	}
+
+	// Loop cases.
+
+	static const struct
+	{
+		LoopCase::LoopType	type;
+		const char*			name;
+		const char*			description;
+	} loopGroups[] =
+	{
+		{LoopCase::LOOP_FOR,		"for",		"for Loop Performance Tests"},
+		{LoopCase::LOOP_WHILE,		"while",	"while Loop Performance Tests"},
+		{LoopCase::LOOP_DO_WHILE,	"do_while",	"do-while Loop Performance Tests"}
+	};
+
+	for (int groupNdx = 0; groupNdx < DE_LENGTH_OF_ARRAY(loopGroups); groupNdx++)
+	{
+		tcu::TestCaseGroup* currentLoopGroup = new tcu::TestCaseGroup(m_testCtx, loopGroups[groupNdx].name, loopGroups[groupNdx].description);
+		addChild(currentLoopGroup);
+
+		for (int isFrag = 0; isFrag <= 1; isFrag++)
+		{
+			bool isVertex = isFrag == 0;
+			ShaderPerformanceCaseGroup* vertexOrFragmentGroup = new ShaderPerformanceCaseGroup(m_testCtx, isVertex ? "vertex" : "fragment", "");
+			currentLoopGroup->addChild(vertexOrFragmentGroup);
+
+			DE_STATIC_ASSERT(DECISION_STATIC == 0);
+			for (int decisionType = (int)DECISION_STATIC; decisionType < (int)DECISION_LAST; decisionType++)
+			{
+				const char* decisionName = decisionType == (int)DECISION_STATIC		? "static" :
+										   decisionType == (int)DECISION_UNIFORM	? "uniform" :
+										   decisionType == (int)DECISION_ATTRIBUTE	? (isVertex ? "attribute" : "varying") :
+																					  DE_NULL;
+				DE_ASSERT(decisionName != DE_NULL);
+
+				if (decisionType == (int)DECISION_ATTRIBUTE)
+				{
+					vertexOrFragmentGroup->addChild(new LoopCase(m_context, (string(decisionName) + "_stable").c_str(), "", loopGroups[groupNdx].type, (DecisionType)decisionType, true, isVertex));
+					vertexOrFragmentGroup->addChild(new LoopCase(m_context, (string(decisionName) + "_unstable").c_str(), "", loopGroups[groupNdx].type, (DecisionType)decisionType, false, isVertex));
+				}
+				else
+					vertexOrFragmentGroup->addChild(new LoopCase(m_context, decisionName, "", loopGroups[groupNdx].type, (DecisionType)decisionType, true, isVertex));
+
+			}
+
+			if (isVertex)
+				vertexOrFragmentGroup->addChild(new LoopWorkloadReferenceCase(m_context, "reference", "", true, isVertex));
+			else
+			{
+				// Only fragment case with unstable attribute has an additional fract() call.
+				vertexOrFragmentGroup->addChild(new LoopWorkloadReferenceCase(m_context, "reference_stable", "", true, isVertex));
+				vertexOrFragmentGroup->addChild(new LoopWorkloadReferenceCase(m_context, "reference_unstable", "", false, isVertex));
+			}
+		}
+	}
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pShaderControlStatementTests.hpp b/modules/gles2/performance/es2pShaderControlStatementTests.hpp
new file mode 100644
index 0000000..0120e54
--- /dev/null
+++ b/modules/gles2/performance/es2pShaderControlStatementTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PSHADERCONTROLSTATEMENTTESTS_HPP
+#define _ES2PSHADERCONTROLSTATEMENTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader control statement performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class ShaderControlStatementTests : public TestCaseGroup
+{
+public:
+									ShaderControlStatementTests		(Context& context);
+									~ShaderControlStatementTests	(void);
+
+	void							init							(void);
+
+private:
+									ShaderControlStatementTests		(const ShaderControlStatementTests& other);
+	ShaderControlStatementTests&	operator=						(const ShaderControlStatementTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PSHADERCONTROLSTATEMENTTESTS_HPP
diff --git a/modules/gles2/performance/es2pShaderOperatorTests.cpp b/modules/gles2/performance/es2pShaderOperatorTests.cpp
new file mode 100644
index 0000000..9dd2f61
--- /dev/null
+++ b/modules/gles2/performance/es2pShaderOperatorTests.cpp
@@ -0,0 +1,2274 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader operator performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pShaderOperatorTests.hpp"
+#include "glsCalibration.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuSurface.hpp"
+#include "deStringUtil.hpp"
+#include "deSharedPtr.hpp"
+#include "deClock.h"
+#include "deMath.h"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <map>
+#include <algorithm>
+#include <limits>
+#include <set>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+using namespace gls;
+using namespace glu;
+using tcu::Vec2;
+using tcu::Vec4;
+using tcu::TestLog;
+using de::SharedPtr;
+
+using std::string;
+using std::vector;
+
+#define MEASUREMENT_FAIL() throw tcu::InternalError("Unable to get sensible measurements for estimation", DE_NULL, __FILE__, __LINE__)
+
+// Number of measurements in OperatorPerformanceCase for each workload size, unless specified otherwise by a command line argument.
+static const int	DEFAULT_NUM_MEASUREMENTS_PER_WORKLOAD	= 3;
+// How many different workload sizes are used by OperatorPerformanceCase.
+static const int	NUM_WORKLOADS							= 8;
+// Maximum workload size that can be attempted. In a sensible case, this most likely won't be reached.
+static const int	MAX_WORKLOAD_SIZE						= 1<<29;
+
+// BinaryOpCase-specific constants for shader generation.
+static const int	BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS	= 4;
+static const int	BINARY_OPERATOR_CASE_SMALL_PROGRAM_UNROLL_AMOUNT	= 2;
+static const int	BINARY_OPERATOR_CASE_BIG_PROGRAM_UNROLL_AMOUNT		= 4;
+
+// FunctionCase-specific constants for shader generation.
+static const int	FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS			= 4;
+
+static const char* const s_swizzles[][4] =
+{
+	{ "x", "yx", "yzx", "wzyx" },
+	{ "y", "zy", "wyz", "xwzy" },
+	{ "z", "wy", "zxy", "yzwx" },
+	{ "w", "xw", "yxw", "zyxw" }
+};
+
+template <int N>
+static tcu::Vector<float, N> mean (const vector<tcu::Vector<float, N> >& data)
+{
+	tcu::Vector<float, N> sum(0.0f);
+	for (int i = 0; i < (int)data.size(); i++)
+		sum += data[i];
+	return sum / tcu::Vector<float, N>((float)data.size());
+}
+
+static void uniformNfv (const glw::Functions& gl, int n, int location, int count, const float* data)
+{
+	switch (n)
+	{
+		case 1: gl.uniform1fv(location, count, data); break;
+		case 2: gl.uniform2fv(location, count, data); break;
+		case 3: gl.uniform3fv(location, count, data); break;
+		case 4: gl.uniform4fv(location, count, data); break;
+		default: DE_ASSERT(false);
+	}
+}
+
+static void uniformNiv (const glw::Functions& gl, int n, int location, int count, const int* data)
+{
+	switch (n)
+	{
+		case 1: gl.uniform1iv(location, count, data); break;
+		case 2: gl.uniform2iv(location, count, data); break;
+		case 3: gl.uniform3iv(location, count, data); break;
+		case 4: gl.uniform4iv(location, count, data); break;
+		default: DE_ASSERT(false);
+	}
+}
+
+static void uniformMatrixNfv (const glw::Functions& gl, int n, int location, int count, const float* data)
+{
+	switch (n)
+	{
+		case 2: gl.uniformMatrix2fv(location, count, GL_FALSE, &data[0]); break;
+		case 3: gl.uniformMatrix3fv(location, count, GL_FALSE, &data[0]); break;
+		case 4: gl.uniformMatrix4fv(location, count, GL_FALSE, &data[0]); break;
+		default: DE_ASSERT(false);
+	}
+}
+
+static glu::DataType getDataTypeFloatOrVec (int size)
+{
+	return size == 1 ? glu::TYPE_FLOAT : glu::getDataTypeFloatVec(size);
+}
+
+static int getIterationCountOrDefault (const tcu::CommandLine& cmdLine, int def)
+{
+	const int cmdLineVal = cmdLine.getTestIterationCount();
+	return cmdLineVal > 0 ? cmdLineVal : def;
+}
+
+static string lineParamsString (const LineParameters& params)
+{
+	return "y = " + de::toString(params.offset) + " + " + de::toString(params.coefficient) + "*x";
+}
+
+namespace
+{
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Abstract class for measuring shader operator performance.
+ *
+ * This class draws multiple times with different workload sizes (set
+ * via a uniform, by subclass). Time for each frame is measured, and the
+ * slope of the workload size vs frame time data is estimated. This slope
+ * tells us the estimated increase in frame time caused by a workload
+ * increase of 1 unit (what 1 workload unit means is up to subclass).
+ *
+ * Generally, the shaders contain not just the operation we're interested
+ * in (e.g. addition) but also some other stuff (e.g. loop overhead). To
+ * eliminate this cost, we actually do the stuff described in the above
+ * paragraph with multiple programs (usually two), which contain different
+ * kinds of workload (e.g. different loop contents). Then we can (in
+ * theory) compute the cost of just one operation in a subclass-dependent
+ * manner.
+ *
+ * At this point, the result tells us the increase in frame time caused
+ * by the addition of one operation. Dividing this by the amount of
+ * draw calls in a frame, and further by the amount of vertices or
+ * fragments in a draw call, we get the time cost of one operation.
+ *
+ * In reality, there sometimes isn't just a trivial linear dependence
+ * between workload size and frame time. Instead, there tends to be some
+ * amount of initial "free" operations. That is, it may be that all
+ * workload sizes below some positive integer C yield the same frame time,
+ * and only workload sizes beyond C increase the frame time in a supposedly
+ * linear manner. Graphically, this means that there graph consists of two
+ * parts: a horizontal left part, and a linearly increasing right part; the
+ * right part starts where the left parts ends. The principal task of these
+ * tests is to look at the slope of the increasing right part. Additionally
+ * an estimate for the amount of initial free operations is calculated.
+ * Note that it is also normal to get graphs where the horizontal left part
+ * is of zero width, i.e. there are no free operations.
+ *//*--------------------------------------------------------------------*/
+class OperatorPerformanceCase : public tcu::TestCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_VERTEX = 0,
+		CASETYPE_FRAGMENT,
+
+		CASETYPE_LAST
+	};
+
+	struct InitialCalibration
+	{
+		int initialNumCalls;
+		InitialCalibration (void) : initialNumCalls(1) {}
+	};
+
+	typedef SharedPtr<InitialCalibration> InitialCalibrationStorage;
+
+								OperatorPerformanceCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description,
+															 CaseType caseType, int numWorkloads, const InitialCalibrationStorage& initialCalibrationStorage);
+								~OperatorPerformanceCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+
+	IterateResult				iterate						(void);
+
+	struct AttribSpec
+	{
+		AttribSpec (const char* name_, const tcu::Vec4& p00_, const tcu::Vec4& p01_, const tcu::Vec4& p10_, const tcu::Vec4& p11_)
+			: name		(name_)
+			, p00		(p00_)
+			, p01		(p01_)
+			, p10		(p10_)
+			, p11		(p11_)
+		{
+		}
+
+		AttribSpec (void) {}
+
+		std::string		name;
+		tcu::Vec4		p00;	//!< Bottom left.
+		tcu::Vec4		p01;	//!< Bottom right.
+		tcu::Vec4		p10;	//!< Top left.
+		tcu::Vec4		p11;	//!< Top right.
+	};
+
+protected:
+	struct ProgramContext
+	{
+		string				vertShaderSource;
+		string				fragShaderSource;
+		vector<AttribSpec>	attributes;
+
+		string				description;
+
+		ProgramContext (void) {}
+		ProgramContext (const string& vs, const string& fs, const vector<AttribSpec>& attrs, const string& desc)
+			: vertShaderSource(vs), fragShaderSource(fs), attributes(attrs), description(desc) {}
+	};
+
+	virtual vector<ProgramContext>	generateProgramData					(void) const = 0;
+	//! Sets program-specific uniforms that don't depend on the workload size.
+	virtual void					setGeneralUniforms					(deUint32 program) const = 0;
+	//! Sets the uniform(s) that specifies the workload size in the shader.
+	virtual void					setWorkloadSizeUniform				(deUint32 program, int workload) const = 0;
+	//! Computes the cost of a single operation, given the workload costs per program.
+	virtual float					computeSingleOperationTime			(const vector<float>& perProgramWorkloadCosts) const = 0;
+	//! Logs a human-readable description of what computeSingleOperationTime does.
+	virtual void					logSingleOperationCalculationInfo	(void) const = 0;
+
+	glu::RenderContext&				m_renderCtx;
+
+	CaseType						m_caseType;
+
+private:
+	enum State
+	{
+		STATE_CALIBRATING = 0,		//!< Calibrate draw call count, using first program in m_programs, with workload size 1.
+		STATE_FIND_HIGH_WORKLOAD,	//!< Find an appropriate lower bound for the highest workload size we intend to use (one with high-enough frame time compared to workload size 1) for each program.
+		STATE_MEASURING,			//!< Do actual measurements, for each program in m_programs.
+		STATE_REPORTING,			//!< Measurements are done; calculate results and log.
+		STATE_FINISHED,				//!< All done.
+
+		STATE_LAST
+	};
+
+	struct WorkloadRecord
+	{
+		int				workloadSize;
+		vector<float>	frameTimes; //!< In microseconds.
+
+				WorkloadRecord	(int workloadSize_)						: workloadSize(workloadSize_) {}
+		bool	operator<		(const WorkloadRecord& other) const		{ return this->workloadSize < other.workloadSize; }
+		void	addFrameTime	(float time)							{ frameTimes.push_back(time); }
+		float	getMedianTime	(void) const
+		{
+			vector<float> times = frameTimes;
+			std::sort(times.begin(), times.end());
+			return times.size() % 2 == 0 ?
+					(times[times.size()/2-1] + times[times.size()/2])*0.5f :
+					times[times.size()/2];
+		}
+	};
+
+	void								prepareProgram				(int progNdx);					//!< Sets attributes and uniforms for m_programs[progNdx].
+	void								prepareWorkload				(int progNdx, int workload);	//!< Calls setWorkloadSizeUniform and draws, in case the implementation does some draw-time compilation.
+	void								prepareNextRound			(void);							//!< Increases workload and/or updates m_state.
+	void								render						(int numDrawCalls);
+	deUint64							renderAndMeasure			(int numDrawCalls);
+	void								adjustAndLogGridAndViewport	(void);							//!< Log grid and viewport sizes, after possibly reducing them to reduce draw time.
+
+	vector<Vec2>						getWorkloadMedianDataPoints	(int progNdx) const; //!< [ Vec2(r.workloadSize, r.getMedianTime()) for r in m_workloadRecords[progNdx] ]
+
+	const int							m_numMeasurementsPerWorkload;
+	const int							m_numWorkloads;				//!< How many different workload sizes are used for measurement for each program.
+
+	int									m_workloadNdx;				//!< Runs from 0 to m_numWorkloads-1.
+
+	int									m_workloadMeasurementNdx;
+	vector<vector<WorkloadRecord> >		m_workloadRecordsFindHigh;	//!< The measurements done during STATE_FIND_HIGH_WORKLOAD.
+	vector<vector<WorkloadRecord> >		m_workloadRecords;			//!< The measurements of each program in m_programs. Generated during STATE_MEASURING, into index specified by m_measureProgramNdx.
+
+	State								m_state;
+	int									m_measureProgramNdx;		//!< When m_state is STATE_FIND_HIGH_WORKLOAD or STATE_MEASURING, this tells which program in m_programs is being measured.
+
+	vector<int>							m_highWorkloadSizes;		//!< The first workload size encountered during STATE_FIND_HIGH_WORKLOAD that was determined suitable, for each program.
+
+	TheilSenCalibrator					m_calibrator;
+	InitialCalibrationStorage			m_initialCalibrationStorage;
+
+	int									m_viewportWidth;
+	int									m_viewportHeight;
+	int									m_gridSizeX;
+	int									m_gridSizeY;
+
+	vector<ProgramContext>				m_programData;
+	vector<SharedPtr<ShaderProgram> >	m_programs;
+
+	std::vector<deUint32>				m_attribBuffers;
+};
+
+static inline float triangleInterpolate (float v0, float v1, float v2, float x, float y)
+{
+	return v0 + (v2-v0)*x + (v1-v0)*y;
+}
+
+static inline float triQuadInterpolate (float x, float y, const tcu::Vec4& quad)
+{
+	// \note Top left fill rule.
+	if (x + y < 1.0f)
+		return triangleInterpolate(quad.x(), quad.y(), quad.z(), x, y);
+	else
+		return triangleInterpolate(quad.w(), quad.z(), quad.y(), 1.0f-x, 1.0f-y);
+}
+
+static inline int getNumVertices (int gridSizeX, int gridSizeY)
+{
+	return gridSizeX * gridSizeY * 2 * 3;
+}
+
+static void generateVertices (std::vector<float>& dst, int gridSizeX, int gridSizeY, const OperatorPerformanceCase::AttribSpec& spec)
+{
+	const int numComponents = 4;
+
+	DE_ASSERT(gridSizeX >= 1 && gridSizeY >= 1);
+	dst.resize(getNumVertices(gridSizeX, gridSizeY) * numComponents);
+
+	{
+		int dstNdx = 0;
+
+		for (int baseY = 0; baseY < gridSizeY; baseY++)
+		for (int baseX = 0; baseX < gridSizeX; baseX++)
+		{
+			const float xf0 = (float)(baseX + 0) / (float)gridSizeX;
+			const float yf0 = (float)(baseY + 0) / (float)gridSizeY;
+			const float xf1 = (float)(baseX + 1) / (float)gridSizeX;
+			const float yf1 = (float)(baseY + 1) / (float)gridSizeY;
+
+#define ADD_VERTEX(XF, YF)										\
+	for (int compNdx = 0; compNdx < numComponents; compNdx++)	\
+		dst[dstNdx++] = triQuadInterpolate((XF), (YF), tcu::Vec4(spec.p00[compNdx], spec.p01[compNdx], spec.p10[compNdx], spec.p11[compNdx]))
+
+			ADD_VERTEX(xf0, yf0);
+			ADD_VERTEX(xf1, yf0);
+			ADD_VERTEX(xf0, yf1);
+
+			ADD_VERTEX(xf1, yf0);
+			ADD_VERTEX(xf1, yf1);
+			ADD_VERTEX(xf0, yf1);
+
+#undef ADD_VERTEX
+		}
+	}
+}
+
+static float intersectionX (const gls::LineParameters& a, const gls::LineParameters& b)
+{
+	return (a.offset - b.offset) / (b.coefficient - a.coefficient);
+}
+
+static int numDistinctX (const vector<Vec2>& data)
+{
+	std::set<float> xs;
+	for (int i = 0; i < (int)data.size(); i++)
+		xs.insert(data[i].x());
+	return (int)xs.size();
+}
+
+static gls::LineParameters simpleLinearRegression (const vector<Vec2>& data)
+{
+	const Vec2	mid					= mean(data);
+
+	float		slopeNumerator		= 0.0f;
+	float		slopeDenominator	= 0.0f;
+
+	for (int i = 0; i < (int)data.size(); i++)
+	{
+		const Vec2 diff = data[i] - mid;
+
+		slopeNumerator		+= diff.x()*diff.y();
+		slopeDenominator	+= diff.x()*diff.x();
+	}
+
+	const float slope	= slopeNumerator / slopeDenominator;
+	const float offset	= mid.y() - slope*mid.x();
+
+	return gls::LineParameters(offset, slope);
+}
+
+static float simpleLinearRegressionError (const vector<Vec2>& data)
+{
+	if (numDistinctX(data) <= 2)
+		return 0.0f;
+	else
+	{
+		const gls::LineParameters	estimator	= simpleLinearRegression(data);
+		float						error		= 0.0f;
+
+		for (int i = 0; i < (int)data.size(); i++)
+		{
+			const float estY = estimator.offset + estimator.coefficient*data[i].x();
+			const float diff = estY - data[i].y();
+			error += diff*diff;
+		}
+
+		return error / (float)data.size();
+	}
+}
+
+static float verticalVariance (const vector<Vec2>& data)
+{
+	if (numDistinctX(data) <= 2)
+		return 0.0f;
+	else
+	{
+		const float		meanY = mean(data).y();
+		float			error = 0.0f;
+
+		for (int i = 0; i < (int)data.size(); i++)
+		{
+			const float diff = meanY - data[i].y();
+			error += diff*diff;
+		}
+
+		return error / (float)data.size();
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Find the x coord that divides the input data into two slopes.
+ *
+ * The operator performance measurements tend to produce results where
+ * we get small operation counts "for free" (e.g. because the operations
+ * are performed during some memory transfer overhead or something),
+ * resulting in a curve with two parts: an initial horizontal line segment,
+ * and a rising line.
+ *
+ * This function finds the x coordinate that divides the input data into
+ * two parts such that the sum of the mean square errors for the
+ * least-squares estimated lines for the two parts is minimized, under the
+ * additional condition that the left line is horizontal.
+ *
+ * This function returns a number X s.t. { pt | pt is in data, pt.x >= X }
+ * is the right line, and the rest of data is the left line.
+ *//*--------------------------------------------------------------------*/
+static float findSlopePivotX (const vector<Vec2>& data)
+{
+	std::set<float> xCoords;
+	for (int i = 0; i < (int)data.size(); i++)
+		xCoords.insert(data[i].x());
+
+	float			lowestError		= std::numeric_limits<float>::infinity();
+	float			bestPivotX		= -std::numeric_limits<float>::infinity();
+
+	for (std::set<float>::const_iterator pivotX = xCoords.begin(); pivotX != xCoords.end(); ++pivotX)
+	{
+		vector<Vec2> leftData;
+		vector<Vec2> rightData;
+		for (int i = 0; i < (int)data.size(); i++)
+		{
+			if (data[i].x() < *pivotX)
+				leftData.push_back(data[i]);
+			else
+				rightData.push_back(data[i]);
+		}
+
+		if (numDistinctX(rightData) < 3) // We don't trust the right data if there's too little of it.
+			break;
+
+		{
+			const float totalError = verticalVariance(leftData) + simpleLinearRegressionError(rightData);
+
+			if (totalError < lowestError)
+			{
+				lowestError = totalError;
+				bestPivotX = *pivotX;
+			}
+		}
+	}
+
+	DE_ASSERT(lowestError < std::numeric_limits<float>::infinity());
+
+	return bestPivotX;
+}
+
+struct SegmentedEstimator
+{
+	float					pivotX; //!< Value returned by findSlopePivotX, or -infinity if only single line.
+	gls::LineParameters		left;
+	gls::LineParameters		right;
+	SegmentedEstimator (const gls::LineParameters& l, const gls::LineParameters& r, float pivotX_) : pivotX(pivotX_), left(l), right(r) {}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Compute line estimators for (potentially) two-segment data.
+ *
+ * Splits the given data into left and right parts (using findSlopePivotX)
+ * and returns the line estimates for them.
+ *
+ * Sometimes, however (especially in fragment shader cases) the data is
+ * in fact not segmented, but a straight line. This function attempts to
+ * detect if this the case, and if so, sets left.offset = right.offset and
+ * left.slope = 0, meaning essentially that the initial "flat" part of the
+ * data has zero width.
+ *//*--------------------------------------------------------------------*/
+static SegmentedEstimator computeSegmentedEstimator (const vector<Vec2>& data)
+{
+	const float		pivotX = findSlopePivotX(data);
+	vector<Vec2>	leftData;
+	vector<Vec2>	rightData;
+
+	for (int i = 0; i < (int)data.size(); i++)
+	{
+		if (data[i].x() < pivotX)
+			leftData.push_back(data[i]);
+		else
+			rightData.push_back(data[i]);
+	}
+
+	{
+		const gls::LineParameters leftLine		= gls::theilSenEstimator(leftData);
+		const gls::LineParameters rightLine		= gls::theilSenEstimator(rightData);
+
+		if (numDistinctX(leftData) < 2 || leftLine.coefficient > rightLine.coefficient*0.5f)
+		{
+			// Left data doesn't seem credible; assume the data is just a single line.
+			const gls::LineParameters entireLine = gls::theilSenEstimator(data);
+			return SegmentedEstimator(gls::LineParameters(entireLine.offset, 0.0f), entireLine, -std::numeric_limits<float>::infinity());
+		}
+		else
+			return SegmentedEstimator(leftLine, rightLine, pivotX);
+	}
+}
+
+OperatorPerformanceCase::OperatorPerformanceCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description,
+												  CaseType caseType, int numWorkloads, const InitialCalibrationStorage& initialCalibrationStorage)
+	: tcu::TestCase					(testCtx, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_renderCtx					(renderCtx)
+	, m_caseType					(caseType)
+	, m_numMeasurementsPerWorkload	(getIterationCountOrDefault(m_testCtx.getCommandLine(), DEFAULT_NUM_MEASUREMENTS_PER_WORKLOAD))
+	, m_numWorkloads				(numWorkloads)
+	, m_workloadNdx					(-1)
+	, m_workloadMeasurementNdx		(-1)
+	, m_state						(STATE_LAST)
+	, m_measureProgramNdx			(-1)
+	, m_initialCalibrationStorage	(initialCalibrationStorage)
+	, m_viewportWidth				(caseType == CASETYPE_VERTEX	? 32	: renderCtx.getRenderTarget().getWidth())
+	, m_viewportHeight				(caseType == CASETYPE_VERTEX	? 32	: renderCtx.getRenderTarget().getHeight())
+	, m_gridSizeX					(caseType == CASETYPE_FRAGMENT	? 1		: 100)
+	, m_gridSizeY					(caseType == CASETYPE_FRAGMENT	? 1		: 100)
+{
+	DE_ASSERT(m_numWorkloads > 0);
+}
+
+OperatorPerformanceCase::~OperatorPerformanceCase (void)
+{
+	if (!m_attribBuffers.empty())
+	{
+		m_renderCtx.getFunctions().deleteBuffers((glw::GLsizei)m_attribBuffers.size(), &m_attribBuffers[0]);
+		m_attribBuffers.clear();
+	}
+}
+
+static void logRenderTargetInfo (TestLog& log, const tcu::RenderTarget& renderTarget)
+{
+	log << TestLog::Section("RenderTarget", "Render target")
+		<< TestLog::Message << "size: " << renderTarget.getWidth() << "x" << renderTarget.getHeight() << TestLog::EndMessage
+		<< TestLog::Message << "bits:"
+							<< " R" << renderTarget.getPixelFormat().redBits
+							<< " G" << renderTarget.getPixelFormat().greenBits
+							<< " B" << renderTarget.getPixelFormat().blueBits
+							<< " A" << renderTarget.getPixelFormat().alphaBits
+							<< " D" << renderTarget.getDepthBits()
+							<< " S" << renderTarget.getStencilBits()
+							<< TestLog::EndMessage;
+
+	if (renderTarget.getNumSamples() != 0)
+		log << TestLog::Message << renderTarget.getNumSamples() << "x MSAA" << TestLog::EndMessage;
+	else
+		log << TestLog::Message << "No MSAA" << TestLog::EndMessage;
+
+	log << TestLog::EndSection;
+}
+
+vector<Vec2> OperatorPerformanceCase::getWorkloadMedianDataPoints (int progNdx) const
+{
+	const vector<WorkloadRecord>&	records = m_workloadRecords[progNdx];
+	vector<Vec2>					result;
+
+	for (int i = 0; i < (int)records.size(); i++)
+		result.push_back(Vec2((float)records[i].workloadSize, records[i].getMedianTime()));
+
+	return result;
+}
+
+void OperatorPerformanceCase::prepareProgram (int progNdx)
+{
+	DE_ASSERT(progNdx < (int)m_programs.size());
+	DE_ASSERT(m_programData.size() == m_programs.size());
+
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	const ShaderProgram&	program		= *m_programs[progNdx];
+
+	vector<AttribSpec>		attributes	= m_programData[progNdx].attributes;
+
+	attributes.push_back(AttribSpec("a_position",
+									Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
+									Vec4( 1.0f, -1.0f, 0.0f, 1.0f),
+									Vec4(-1.0f,  1.0f, 0.0f, 1.0f),
+									Vec4( 1.0f,  1.0f, 0.0f, 1.0f)));
+
+	DE_ASSERT(program.isOk());
+
+	// Generate vertices.
+	if (!m_attribBuffers.empty())
+		gl.deleteBuffers((glw::GLsizei)m_attribBuffers.size(), &m_attribBuffers[0]);
+	m_attribBuffers.resize(attributes.size(), 0);
+	gl.genBuffers((glw::GLsizei)m_attribBuffers.size(), &m_attribBuffers[0]);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenBuffers()");
+
+	for (int attribNdx = 0; attribNdx < (int)attributes.size(); attribNdx++)
+	{
+		std::vector<float> vertices;
+		generateVertices(vertices, m_gridSizeX, m_gridSizeY, attributes[attribNdx]);
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_attribBuffers[attribNdx]);
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(vertices.size()*sizeof(float)), &vertices[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Upload buffer data");
+	}
+
+	// Setup attribute bindings.
+	for (int attribNdx = 0; attribNdx < (int)attributes.size(); attribNdx++)
+	{
+		int location = gl.getAttribLocation(program.getProgram(), attributes[attribNdx].name.c_str());
+
+		if (location >= 0)
+		{
+			gl.enableVertexAttribArray(location);
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_attribBuffers[attribNdx]);
+			gl.vertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		}
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Setup vertex input state");
+
+	gl.useProgram(program.getProgram());
+	setGeneralUniforms(program.getProgram());
+	gl.viewport(0, 0, m_viewportWidth, m_viewportHeight);
+}
+
+void OperatorPerformanceCase::prepareWorkload (int progNdx, int workload)
+{
+	setWorkloadSizeUniform(m_programs[progNdx]->getProgram(), workload);
+	render(m_calibrator.getCallCount());
+}
+
+void OperatorPerformanceCase::prepareNextRound (void)
+{
+	DE_ASSERT(m_state == STATE_CALIBRATING			||
+			  m_state == STATE_FIND_HIGH_WORKLOAD	||
+			  m_state == STATE_MEASURING);
+
+	TestLog& log = m_testCtx.getLog();
+
+	if (m_state == STATE_CALIBRATING && m_calibrator.getState() == TheilSenCalibrator::STATE_FINISHED)
+	{
+		m_measureProgramNdx = 0;
+		m_state = STATE_FIND_HIGH_WORKLOAD;
+	}
+
+	if (m_state == STATE_CALIBRATING)
+		prepareWorkload(0, 1);
+	else if (m_state == STATE_FIND_HIGH_WORKLOAD)
+	{
+		vector<WorkloadRecord>& records = m_workloadRecordsFindHigh[m_measureProgramNdx];
+
+		if (records.empty() || records.back().getMedianTime() < 2.0f*records[0].getMedianTime())
+		{
+			int workloadSize;
+
+			if (records.empty())
+				workloadSize = 1;
+			else
+			{
+				workloadSize = records.back().workloadSize*2;
+
+				if (workloadSize > MAX_WORKLOAD_SIZE)
+				{
+					log << TestLog::Message << "Even workload size " << records.back().workloadSize
+											<< " doesn't give high enough frame time for program " << m_measureProgramNdx
+											<< ". Can't get sensible result." << TestLog::EndMessage;
+					MEASUREMENT_FAIL();
+				}
+			}
+
+			records.push_back(WorkloadRecord(workloadSize));
+			prepareWorkload(0, workloadSize);
+			m_workloadMeasurementNdx = 0;
+		}
+		else
+		{
+			m_highWorkloadSizes[m_measureProgramNdx] = records.back().workloadSize;
+			m_measureProgramNdx++;
+
+			if (m_measureProgramNdx >= (int)m_programs.size())
+			{
+				m_state = STATE_MEASURING;
+				m_workloadNdx = -1;
+				m_measureProgramNdx = 0;
+			}
+
+			prepareProgram(m_measureProgramNdx);
+			prepareNextRound();
+		}
+	}
+	else
+	{
+		m_workloadNdx++;
+
+		if (m_workloadNdx < m_numWorkloads)
+		{
+			DE_ASSERT(m_numWorkloads > 1);
+			const int highWorkload	= m_highWorkloadSizes[m_measureProgramNdx];
+			const int workload		= highWorkload > m_numWorkloads ?
+										1 + m_workloadNdx*(highWorkload-1)/(m_numWorkloads-1) :
+										1 + m_workloadNdx;
+
+			prepareWorkload(m_measureProgramNdx, workload);
+
+			m_workloadMeasurementNdx = 0;
+
+			m_workloadRecords[m_measureProgramNdx].push_back(WorkloadRecord(workload));
+		}
+		else
+		{
+			m_measureProgramNdx++;
+
+			if (m_measureProgramNdx < (int)m_programs.size())
+			{
+				m_workloadNdx = -1;
+				m_workloadMeasurementNdx = 0;
+				prepareProgram(m_measureProgramNdx);
+				prepareNextRound();
+			}
+			else
+				m_state = STATE_REPORTING;
+		}
+	}
+}
+
+void OperatorPerformanceCase::init (void)
+{
+	TestLog&				log		= m_testCtx.getLog();
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+
+	// Validate that we have sane grid and viewport setup.
+	DE_ASSERT(de::inBounds(m_gridSizeX, 1, 256) && de::inBounds(m_gridSizeY, 1, 256));
+	TCU_CHECK(de::inRange(m_viewportWidth,	1, m_renderCtx.getRenderTarget().getWidth()) &&
+			  de::inRange(m_viewportHeight,	1, m_renderCtx.getRenderTarget().getHeight()));
+
+	logRenderTargetInfo(log, m_renderCtx.getRenderTarget());
+
+	log << TestLog::Message << "Using additive blending." << TestLog::EndMessage;
+	gl.enable(GL_BLEND);
+	gl.blendEquation(GL_FUNC_ADD);
+	gl.blendFunc(GL_ONE, GL_ONE);
+
+	// Generate programs.
+	DE_ASSERT(m_programs.empty());
+	m_programData = generateProgramData();
+	DE_ASSERT(!m_programData.empty());
+
+	for (int progNdx = 0; progNdx < (int)m_programData.size(); progNdx++)
+	{
+		const string& vert = m_programData[progNdx].vertShaderSource;
+		const string& frag = m_programData[progNdx].fragShaderSource;
+
+		m_programs.push_back(SharedPtr<ShaderProgram>(new ShaderProgram(m_renderCtx, glu::makeVtxFragSources(vert, frag))));
+
+		if (!m_programs.back()->isOk())
+		{
+			log << *m_programs.back();
+			TCU_FAIL("Compile failed");
+		}
+	}
+
+	// Log all programs.
+	for (int progNdx = 0; progNdx < (int)m_programs.size(); progNdx++)
+		log << TestLog::Section("Program" + de::toString(progNdx), "Program " + de::toString(progNdx))
+				<< TestLog::Message << m_programData[progNdx].description << TestLog::EndMessage
+				<< *m_programs[progNdx]
+			<< TestLog::EndSection;
+
+	m_highWorkloadSizes.resize(m_programData.size());
+	m_workloadRecordsFindHigh.resize(m_programData.size());
+	m_workloadRecords.resize(m_programData.size());
+
+	m_calibrator.clear(CalibratorParameters(m_initialCalibrationStorage->initialNumCalls, 10 /* calibrate iteration frames */, 2000.0f /* calibrate iteration shortcut threshold (ms) */, 16 /* max calibrate iterations */,
+											1000.0f/30.0f /* frame time (ms) */, 1000.0f/60.0f /* frame time cap (ms) */, 1000.0f /* target measure duration (ms) */));
+	m_state = STATE_CALIBRATING;
+
+	prepareProgram(0);
+	prepareNextRound();
+}
+
+void OperatorPerformanceCase::deinit (void)
+{
+	if (!m_attribBuffers.empty())
+	{
+		m_renderCtx.getFunctions().deleteBuffers((glw::GLsizei)m_attribBuffers.size(), &m_attribBuffers[0]);
+		m_attribBuffers.clear();
+	}
+
+	m_programs.clear();
+}
+
+void OperatorPerformanceCase::render (int numDrawCalls)
+{
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	const int				numVertices		= getNumVertices(m_gridSizeX, m_gridSizeY);
+
+	for (int callNdx = 0; callNdx < numDrawCalls; callNdx++)
+		gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+
+	glu::readPixels(m_renderCtx, 0, 0, tcu::Surface(1, 1).getAccess()); // \note Serves as a more reliable replacement for glFinish().
+}
+
+deUint64 OperatorPerformanceCase::renderAndMeasure (int numDrawCalls)
+{
+	const deUint64 startTime = deGetMicroseconds();
+	render(numDrawCalls);
+	return deGetMicroseconds() - startTime;
+}
+
+void OperatorPerformanceCase::adjustAndLogGridAndViewport (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	// If call count is just 1, and the target frame time still wasn't reached, reduce grid or viewport size.
+	if (m_calibrator.getCallCount() == 1)
+	{
+		const gls::MeasureState&	calibratorMeasure	= m_calibrator.getMeasureState();
+		const float					drawCallTime		= (float)calibratorMeasure.getTotalTime() / (float)calibratorMeasure.frameTimes.size();
+		const float					targetDrawCallTime	= m_calibrator.getParameters().targetFrameTimeUs;
+		const float					targetRatio			= targetDrawCallTime / drawCallTime;
+
+		if (targetRatio < 0.95f)
+		{
+			// Reduce grid or viewport size assuming draw call time scales proportionally.
+			if (m_caseType == CASETYPE_VERTEX)
+			{
+				const float targetRatioSqrt = deFloatSqrt(targetRatio);
+				m_gridSizeX = (int)(targetRatioSqrt * (float)m_gridSizeX);
+				m_gridSizeY = (int)(targetRatioSqrt * (float)m_gridSizeY);
+				TCU_CHECK_MSG(m_gridSizeX >= 1 && m_gridSizeY >= 1, "Can't decrease grid size enough to achieve low-enough draw times");
+				log << TestLog::Message << "Note: triangle grid size reduced from original; it's now smaller than during calibration." << TestLog::EndMessage;
+			}
+			else
+			{
+				const float targetRatioSqrt = deFloatSqrt(targetRatio);
+				m_viewportWidth  = (int)(targetRatioSqrt * (float)m_viewportWidth);
+				m_viewportHeight = (int)(targetRatioSqrt * (float)m_viewportHeight);
+				TCU_CHECK_MSG(m_viewportWidth >= 1 && m_viewportHeight >= 1, "Can't decrease viewport size enough to achieve low-enough draw times");
+				log << TestLog::Message << "Note: viewport size reduced from original; it's now smaller than during calibration." << TestLog::EndMessage;
+			}
+		}
+	}
+
+	prepareProgram(0);
+
+	// Log grid and viewport sizes.
+	log << TestLog::Message << "Grid size: " << m_gridSizeX << "x" << m_gridSizeY << TestLog::EndMessage;
+	log << TestLog::Message << "Viewport: " << m_viewportWidth << "x" << m_viewportHeight << TestLog::EndMessage;
+}
+
+OperatorPerformanceCase::IterateResult OperatorPerformanceCase::iterate (void)
+{
+	const TheilSenCalibrator::State calibratorState = m_calibrator.getState();
+
+	if (calibratorState != TheilSenCalibrator::STATE_FINISHED)
+	{
+		if (calibratorState == TheilSenCalibrator::STATE_RECOMPUTE_PARAMS)
+			m_calibrator.recomputeParameters();
+		else if (calibratorState == TheilSenCalibrator::STATE_MEASURE)
+			m_calibrator.recordIteration(renderAndMeasure(m_calibrator.getCallCount()));
+		else
+			DE_ASSERT(false);
+
+		if (m_calibrator.getState() == TheilSenCalibrator::STATE_FINISHED)
+		{
+			logCalibrationInfo(m_testCtx.getLog(), m_calibrator);
+			adjustAndLogGridAndViewport();
+			prepareNextRound();
+			m_initialCalibrationStorage->initialNumCalls = m_calibrator.getCallCount();
+		}
+	}
+	else if (m_state == STATE_FIND_HIGH_WORKLOAD || m_state == STATE_MEASURING)
+	{
+		if (m_workloadMeasurementNdx < m_numMeasurementsPerWorkload)
+		{
+			vector<WorkloadRecord>& records = m_state == STATE_FIND_HIGH_WORKLOAD ? m_workloadRecordsFindHigh[m_measureProgramNdx] : m_workloadRecords[m_measureProgramNdx];
+			records.back().addFrameTime((float)renderAndMeasure(m_calibrator.getCallCount()));
+			m_workloadMeasurementNdx++;
+		}
+		else
+			prepareNextRound();
+	}
+	else
+	{
+		DE_ASSERT(m_state == STATE_REPORTING);
+
+		TestLog&	log				= m_testCtx.getLog();
+		const int	drawCallCount	= m_calibrator.getCallCount();
+
+		{
+			// Compute per-program estimators for measurements.
+			vector<SegmentedEstimator> estimators;
+			for (int progNdx = 0; progNdx < (int)m_programs.size(); progNdx++)
+				estimators.push_back(computeSegmentedEstimator(getWorkloadMedianDataPoints(progNdx)));
+
+			// Log measurements and their estimators for all programs.
+			for (int progNdx = 0; progNdx < (int)m_programs.size(); progNdx++)
+			{
+				const SegmentedEstimator&	estimator	= estimators[progNdx];
+				const string				progNdxStr	= de::toString(progNdx);
+				vector<WorkloadRecord>		records		= m_workloadRecords[progNdx];
+				std::sort(records.begin(), records.end());
+
+				{
+					const tcu::ScopedLogSection section(log,
+														"Program" + progNdxStr + "Measurements",
+														"Measurements for program " + progNdxStr);
+
+					// Sample list of individual frame times.
+
+					log << TestLog::SampleList("Program" + progNdxStr + "IndividualFrameTimes", "Individual frame times")
+						<< TestLog::SampleInfo << TestLog::ValueInfo("Workload",	"Workload",		"",		QP_SAMPLE_VALUE_TAG_PREDICTOR)
+											   << TestLog::ValueInfo("FrameTime",	"Frame time",	"us",	QP_SAMPLE_VALUE_TAG_RESPONSE)
+						<< TestLog::EndSampleInfo;
+
+					for (int i = 0; i < (int)records.size(); i++)
+						for (int j = 0; j < (int)records[i].frameTimes.size(); j++)
+							log << TestLog::Sample << records[i].workloadSize << records[i].frameTimes[j] << TestLog::EndSample;
+
+					log << TestLog::EndSampleList;
+
+					// Sample list of median frame times.
+
+					log << TestLog::SampleList("Program" + progNdxStr + "MedianFrameTimes", "Median frame times")
+						<< TestLog::SampleInfo << TestLog::ValueInfo("Workload",		"Workload",				"",		QP_SAMPLE_VALUE_TAG_PREDICTOR)
+											   << TestLog::ValueInfo("MedianFrameTime",	"Median frame time",	"us",	QP_SAMPLE_VALUE_TAG_RESPONSE)
+						<< TestLog::EndSampleInfo;
+
+					for (int i = 0; i < (int)records.size(); i++)
+						log << TestLog::Sample << records[i].workloadSize << records[i].getMedianTime() << TestLog::EndSample;
+
+					log << TestLog::EndSampleList;
+
+					log << TestLog::Float("Program" + progNdxStr + "WorkloadCostEstimate", "Workload cost estimate", "us / workload", QP_KEY_TAG_TIME, estimator.right.coefficient);
+
+					if (estimator.pivotX > -std::numeric_limits<float>::infinity())
+						log << TestLog::Message << "Note: the data points with x coordinate greater than or equal to " << estimator.pivotX
+												<< " seem to form a rising line, and the rest of data points seem to form a near-horizontal line" << TestLog::EndMessage
+							<< TestLog::Message << "Note: the left line is estimated to be " << lineParamsString(estimator.left)
+												<< " and the right line " << lineParamsString(estimator.right) << TestLog::EndMessage;
+					else
+						log << TestLog::Message << "Note: the data seem to form a single line: " << lineParamsString(estimator.right) << TestLog::EndMessage;
+				}
+			}
+
+			for (int progNdx = 0; progNdx < (int)m_programs.size(); progNdx++)
+			{
+				if (estimators[progNdx].right.coefficient <= 0.0f)
+				{
+					log << TestLog::Message << "Slope of measurements for program " << progNdx << " isn't positive. Can't get sensible result." << TestLog::EndMessage;
+					MEASUREMENT_FAIL();
+				}
+			}
+
+			// \note For each estimator, .right.coefficient is the increase in draw time (in microseconds) when
+			// incrementing shader workload size by 1, when D draw calls are done, with a vertex/fragment count
+			// of R.
+			//
+			// The measurements of any single program can't tell us the final result (time of single operation),
+			// so we use computeSingleOperationTime to compute it from multiple programs' measurements in a
+			// subclass-defined manner.
+			//
+			// After that, microseconds per operation can be calculated as singleOperationTime / (D * R).
+
+			{
+				vector<float>	perProgramSlopes;
+				for (int i = 0; i < (int)m_programs.size(); i++)
+					perProgramSlopes.push_back(estimators[i].right.coefficient);
+
+				logSingleOperationCalculationInfo();
+
+				const float		maxSlope				= *std::max_element(perProgramSlopes.begin(), perProgramSlopes.end());
+				const float		usecsPerFramePerOp		= computeSingleOperationTime(perProgramSlopes);
+				const int		vertexOrFragmentCount	= m_caseType == CASETYPE_VERTEX ?
+															getNumVertices(m_gridSizeX, m_gridSizeY) :
+															m_viewportWidth*m_viewportHeight;
+				const double	usecsPerDrawCallPerOp	= usecsPerFramePerOp / (double)drawCallCount;
+				const double	usecsPerSingleOp		= usecsPerDrawCallPerOp / (double)vertexOrFragmentCount;
+				const double	megaOpsPerSecond		= (double)(drawCallCount*vertexOrFragmentCount) / usecsPerFramePerOp;
+				const int		numFreeOps				= de::max(0, (int)deFloatFloor(intersectionX(estimators[0].left,
+																									 LineParameters(estimators[0].right.offset,
+																													usecsPerFramePerOp))));
+
+				log << TestLog::Integer("VertexOrFragmentCount",
+										"R = " + string(m_caseType == CASETYPE_VERTEX ? "Vertex" : "Fragment") + " count",
+										"", QP_KEY_TAG_NONE, vertexOrFragmentCount)
+
+					<< TestLog::Integer("DrawCallsPerFrame", "D = Draw calls per frame", "", QP_KEY_TAG_NONE, drawCallCount)
+
+					<< TestLog::Integer("VerticesOrFragmentsPerFrame",
+										"R*D = " + string(m_caseType == CASETYPE_VERTEX ? "Vertices" : "Fragments") + " per frame",
+										"", QP_KEY_TAG_NONE, vertexOrFragmentCount*drawCallCount)
+
+					<< TestLog::Float("TimePerFramePerOp",
+									  "Estimated cost of R*D " + string(m_caseType == CASETYPE_VERTEX ? "vertices" : "fragments")
+									  + " (i.e. one frame) with one shader operation",
+									  "us", QP_KEY_TAG_TIME, (float)usecsPerFramePerOp)
+
+					<< TestLog::Float("TimePerDrawcallPerOp",
+									  "Estimated cost of one draw call with one shader operation",
+									  "us", QP_KEY_TAG_TIME, (float)usecsPerDrawCallPerOp)
+
+					<< TestLog::Float("TimePerSingleOp",
+									  "Estimated cost of a single shader operation",
+									  "us", QP_KEY_TAG_TIME, (float)usecsPerSingleOp);
+
+				// \note Sometimes, when the operation is free or very cheap, it can happen that the shader with the operation runs,
+				//		 for some reason, a bit faster than the shader without the operation, and thus we get a negative result. The
+				//		 following threshold values for accepting a negative or almost-zero result are rather quick and dirty.
+				if (usecsPerFramePerOp <= -0.1f*maxSlope)
+				{
+					log << TestLog::Message << "Got strongly negative result." << TestLog::EndMessage;
+					MEASUREMENT_FAIL();
+				}
+				else if (usecsPerFramePerOp <= 0.001*maxSlope)
+				{
+					log << TestLog::Message << "Cost of operation seems to be approximately zero." << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+				}
+				else
+				{
+					log << TestLog::Float("OpsPerSecond",
+										  "Operations per second",
+										  "Million/s", QP_KEY_TAG_PERFORMANCE, (float)megaOpsPerSecond)
+
+						<< TestLog::Integer("NumFreeOps",
+											"Estimated number of \"free\" operations",
+											"", QP_KEY_TAG_PERFORMANCE, numFreeOps);
+
+					m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)megaOpsPerSecond, 2).c_str());
+				}
+
+				m_state = STATE_FINISHED;
+			}
+		}
+
+		return STOP;
+	}
+
+	return CONTINUE;
+}
+
+// Binary operator case.
+class BinaryOpCase : public OperatorPerformanceCase
+{
+public:
+						BinaryOpCase				(Context& context, const char* name, const char* description, const char* op,
+													 glu::DataType type, glu::Precision precision, bool useSwizzle, bool isVertex, const InitialCalibrationStorage& initialCalibration);
+
+protected:
+	vector<ProgramContext>	generateProgramData					(void) const;
+	void					setGeneralUniforms					(deUint32 program) const;
+	void					setWorkloadSizeUniform				(deUint32 program, int numOperations) const;
+	float					computeSingleOperationTime			(const vector<float>& perProgramOperationCosts) const;
+	void					logSingleOperationCalculationInfo	(void) const;
+
+private:
+	enum ProgramID
+	{
+		// \note 0-based sequential numbering is relevant, because these are also used as vector indices.
+		// \note The first program should be the heaviest, because OperatorPerformanceCase uses it to reduce grid/viewport size when going too slow.
+		PROGRAM_WITH_BIGGER_LOOP = 0,
+		PROGRAM_WITH_SMALLER_LOOP,
+
+		PROGRAM_LAST
+	};
+
+	ProgramContext			generateSingleProgramData		(ProgramID) const;
+
+	const string			m_op;
+	const glu::DataType		m_type;
+	const glu::Precision	m_precision;
+	const bool				m_useSwizzle;
+};
+
+BinaryOpCase::BinaryOpCase (Context& context, const char* name, const char* description, const char* op,
+							glu::DataType type, glu::Precision precision, bool useSwizzle, bool isVertex, const InitialCalibrationStorage& initialCalibration)
+	: OperatorPerformanceCase	(context.getTestContext(), context.getRenderContext(), name, description,
+								 isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT, NUM_WORKLOADS, initialCalibration)
+	, m_op						(op)
+	, m_type					(type)
+	, m_precision				(precision)
+	, m_useSwizzle				(useSwizzle)
+{
+}
+
+BinaryOpCase::ProgramContext BinaryOpCase::generateSingleProgramData (ProgramID programID) const
+{
+	DE_ASSERT(glu::isDataTypeFloatOrVec(m_type) || glu::isDataTypeIntOrIVec(m_type));
+
+	const bool			isVertexCase	= m_caseType == CASETYPE_VERTEX;
+	const char* const	precision		= glu::getPrecisionName(m_precision);
+	const char* const	inputPrecision	= glu::isDataTypeIntOrIVec(m_type) && m_precision == glu::PRECISION_LOWP ? "mediump" : precision;
+	const char* const	typeName		= getDataTypeName(m_type);
+
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op				= isVertexCase ? vtx : frag;
+
+	// Attributes.
+	vtx << "attribute highp vec4 a_position;\n";
+	for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS+1; i++)
+		vtx << "attribute " << inputPrecision << " vec4 a_in" << i << ";\n";
+
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+	else
+	{
+		for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS+1; i++)
+		{
+			vtx << "varying " << inputPrecision << " vec4 v_in" << i << ";\n";
+			frag << "varying " << inputPrecision << " vec4 v_in" << i << ";\n";
+		}
+	}
+
+	op << "uniform mediump int u_numLoopIterations;\n";
+	if (isVertexCase)
+		op << "uniform mediump float u_zero;\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+
+	if (!isVertexCase)
+		vtx << "\tgl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Expression inputs.
+	const char* const prefix = isVertexCase ? "a_" : "v_";
+	for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS+1; i++)
+	{
+		const int	inSize		= getDataTypeScalarSize(m_type);
+		const bool	isInt		= de::inRange<int>(m_type, TYPE_INT, TYPE_INT_VEC4);
+		const bool	cast		= isInt || (!m_useSwizzle && m_type != TYPE_FLOAT_VEC4);
+
+		op << "\t" << precision << " " << typeName << " in" << i << " = ";
+
+		if (cast)
+			op << typeName << "(";
+
+		op << prefix << "in" << i;
+
+		if (m_useSwizzle)
+			op << "." << s_swizzles[i % DE_LENGTH_OF_ARRAY(s_swizzles)][inSize-1];
+
+		if (cast)
+			op << ")";
+
+		op << ";\n";
+	}
+
+	// Operation accumulation variables.
+	for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS; i++)
+	{
+		op << "\t" << precision << " " << typeName << " acc" << i << "a" << " = in" << i+0 << ";\n";
+		op << "\t" << precision << " " << typeName << " acc" << i << "b" << " = in" << i+1 << ";\n";
+	}
+
+	// Loop, with expressions in it.
+	op << "\tfor (int i = 0; i < u_numLoopIterations; i++)\n";
+	op << "\t{\n";
+	{
+		const int unrollAmount = programID == PROGRAM_WITH_SMALLER_LOOP ? BINARY_OPERATOR_CASE_SMALL_PROGRAM_UNROLL_AMOUNT : BINARY_OPERATOR_CASE_BIG_PROGRAM_UNROLL_AMOUNT;
+		for (int unrollNdx = 0; unrollNdx < unrollAmount; unrollNdx++)
+		{
+			for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS; i++)
+			{
+				if (i > 0 || unrollNdx > 0)
+					op << "\n";
+				op << "\t\tacc" << i << "a = acc" << i << "b " << m_op << " acc" << i << "a" << ";\n";
+				op << "\t\tacc" << i << "b = acc" << i << "a " << m_op << " acc" << i << "b" << ";\n";
+			}
+		}
+	}
+	op << "\t}\n";
+	op << "\n";
+
+	// Result variable (sum of accumulation variables).
+	op << "\t" << precision << " " << typeName << " res =";
+	for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS; i++)
+		op << (i > 0 ? " "+m_op : "") << " acc" << i << "b";
+	op << ";\n";
+
+	// Convert to color.
+	op << "\tmediump vec4 color = ";
+	if (m_type == TYPE_FLOAT_VEC4)
+		op << "res";
+	else
+	{
+		int size = getDataTypeScalarSize(m_type);
+		op << "vec4(res";
+
+		for (int i = size; i < 4; i++)
+			op << ", " << (i == 3 ? "1.0" : "0.0");
+
+		op << ")";
+	}
+	op << ";\n";
+	op << "\t" << (isVertexCase ? "v_color" : "gl_FragColor") << " = color;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "	gl_Position = a_position + u_zero*color;\n";
+		frag << "	gl_FragColor = v_color;\n";
+	}
+	else
+	{
+		for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS+1; i++)
+			vtx << "	v_in" << i << " = a_in" << i << ";\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	{
+		vector<AttribSpec> attributes;
+		for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS+1; i++)
+			attributes.push_back(AttribSpec(("a_in" + de::toString(i)).c_str(),
+											Vec4(2.0f, 2.0f, 2.0f, 1.0f).swizzle((i+0)%4, (i+1)%4, (i+2)%4, (i+3)%4),
+											Vec4(1.0f, 2.0f, 1.0f, 2.0f).swizzle((i+0)%4, (i+1)%4, (i+2)%4, (i+3)%4),
+											Vec4(2.0f, 1.0f, 2.0f, 2.0f).swizzle((i+0)%4, (i+1)%4, (i+2)%4, (i+3)%4),
+											Vec4(1.0f, 1.0f, 2.0f, 1.0f).swizzle((i+0)%4, (i+1)%4, (i+2)%4, (i+3)%4)));
+
+		{
+			string description = "This is the program with the ";
+
+			description += programID == PROGRAM_WITH_SMALLER_LOOP	? "smaller"
+						 : programID == PROGRAM_WITH_BIGGER_LOOP	? "bigger"
+						 : DE_NULL;
+
+			description += " loop.\n"
+						   "Note: workload size for this program means the number of loop iterations.";
+
+			return ProgramContext(vtx.str(), frag.str(), attributes, description);
+		}
+	}
+}
+
+vector<BinaryOpCase::ProgramContext> BinaryOpCase::generateProgramData (void) const
+{
+	vector<ProgramContext> progData;
+	for (int i = 0; i < PROGRAM_LAST; i++)
+		progData.push_back(generateSingleProgramData((ProgramID)i));
+	return progData;
+}
+
+void BinaryOpCase::setGeneralUniforms (deUint32 program) const
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.uniform1f(gl.getUniformLocation(program, "u_zero"), 0.0f);
+}
+
+void BinaryOpCase::setWorkloadSizeUniform (deUint32 program, int numLoopIterations) const
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.uniform1i(gl.getUniformLocation(program, "u_numLoopIterations"), numLoopIterations);
+}
+
+float BinaryOpCase::computeSingleOperationTime (const vector<float>& perProgramOperationCosts) const
+{
+	DE_ASSERT(perProgramOperationCosts.size() == PROGRAM_LAST);
+
+	const int		baseNumOpsInsideLoop				= 2 * BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS;
+	const int		numOpsInsideLoopInSmallProgram		= baseNumOpsInsideLoop * BINARY_OPERATOR_CASE_SMALL_PROGRAM_UNROLL_AMOUNT;
+	const int		numOpsInsideLoopInBigProgram		= baseNumOpsInsideLoop * BINARY_OPERATOR_CASE_BIG_PROGRAM_UNROLL_AMOUNT;
+	DE_STATIC_ASSERT(numOpsInsideLoopInBigProgram > numOpsInsideLoopInSmallProgram);
+	const int		opDiff								= numOpsInsideLoopInBigProgram - numOpsInsideLoopInSmallProgram;
+	const float		programOperationCostDiff			= perProgramOperationCosts[PROGRAM_WITH_BIGGER_LOOP] - perProgramOperationCosts[PROGRAM_WITH_SMALLER_LOOP];
+
+	return programOperationCostDiff / (float)opDiff;
+}
+
+void BinaryOpCase::logSingleOperationCalculationInfo (void) const
+{
+	const int			baseNumOpsInsideLoop			= 2 * BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS;
+	const int			numOpsInsideLoopInSmallProgram	= baseNumOpsInsideLoop * BINARY_OPERATOR_CASE_SMALL_PROGRAM_UNROLL_AMOUNT;
+	const int			numOpsInsideLoopInBigProgram	= baseNumOpsInsideLoop * BINARY_OPERATOR_CASE_BIG_PROGRAM_UNROLL_AMOUNT;
+	const int			opDiff							= numOpsInsideLoopInBigProgram - numOpsInsideLoopInSmallProgram;
+	const char* const	opName							= m_op == "+" ? "addition"
+														: m_op == "-" ? "subtraction"
+														: m_op == "*" ? "multiplication"
+														: m_op == "/" ? "division"
+														: DE_NULL;
+	DE_ASSERT(opName != DE_NULL);
+
+	m_testCtx.getLog() << TestLog::Message << "Note: the bigger program contains " << opDiff << " more "
+										   << opName << " operations in one loop iteration than the small program; "
+										   << "cost of one operation is calculated as (cost_of_bigger_workload - cost_of_smaller_workload) / " << opDiff
+										   << TestLog::EndMessage;
+}
+
+// Built-in function case.
+class FunctionCase : public OperatorPerformanceCase
+{
+public:
+	enum
+	{
+		MAX_PARAMS = 3
+	};
+
+						FunctionCase			(Context&							context,
+												 const char*						name,
+												 const char*						description,
+												 const char*						func,
+												 glu::DataType						returnType,
+												 const glu::DataType				paramTypes[MAX_PARAMS],
+												 const Vec4&						attribute,
+												 int								modifyParamNdx, //!< Add a compile-time constant (2.0) to the parameter at this index. This is ignored if negative.
+												 bool								useNearlyConstantINputs, //!< Function inputs shouldn't be much bigger than 'attribute'.
+												 glu::Precision						precision,
+												 bool								isVertex,
+												 const InitialCalibrationStorage&	initialCalibration);
+
+protected:
+	vector<ProgramContext>	generateProgramData					(void) const;
+	void					setGeneralUniforms					(deUint32 program) const;
+	void					setWorkloadSizeUniform				(deUint32 program, int numOperations) const;
+	float					computeSingleOperationTime			(const vector<float>& perProgramOperationCosts) const;
+	void					logSingleOperationCalculationInfo	(void) const;
+
+private:
+	enum ProgramID
+	{
+		// \note 0-based sequential numbering is relevant, because these are also used as vector indices.
+		// \note The first program should be the heaviest, because OperatorPerformanceCase uses it to reduce grid/viewport size when going too slow.
+		PROGRAM_WITH_FUNCTION_CALLS = 0,
+		PROGRAM_WITHOUT_FUNCTION_CALLS,
+
+		PROGRAM_LAST
+	};
+
+	//! Forms a "sum" expression from aExpr and bExpr; for booleans, this is "equal(a,b)", otherwise actual sum.
+	static string		sumExpr						(const string& aExpr, const string& bExpr, glu::DataType type);
+	//! Forms an expression used to increment an input value in the shader. If type is boolean, this is just
+	//! baseExpr; otherwise, baseExpr is modified by multiplication or division by a loop index,
+	//! to prevent simple compiler optimizations. See m_useNearlyConstantInputs for more explanation.
+	static string		incrementExpr				(const string& baseExpr, glu::DataType type, bool divide);
+
+	ProgramContext		generateSingleProgramData	(ProgramID) const;
+
+	const string			m_func;
+	const glu::DataType		m_returnType;
+	glu::DataType			m_paramTypes[MAX_PARAMS];
+	// \note m_modifyParamNdx, if not negative, specifies the index of the parameter to which a
+	//		 compile-time constant (2.0) is added. This is a quick and dirty way to deal with
+	//		 functions like clamp or smoothstep that require that a certain parameter is
+	//		 greater than a certain other parameter.
+	const int				m_modifyParamNdx;
+	// \note m_useNearlyConstantInputs determines whether the inputs given to the function
+	//		 should increase (w.r.t m_attribute) only by very small amounts. This is relevant
+	//		 for functions like asin, which requires its inputs to be in a specific range.
+	//		 In practice, this affects whether expressions used to increment the input
+	//		 variables use division instead of multiplication; normally, multiplication is used,
+	//		 but it's hard to keep the increments very small that way, and division shouldn't
+	//		 be the default, since for many functions (probably not asin, luckily), division
+	//		 is too heavy and dominates time-wise.
+	const bool				m_useNearlyConstantInputs;
+	const Vec4				m_attribute;
+	const glu::Precision	m_precision;
+};
+
+FunctionCase::FunctionCase (Context&							context,
+							const char*							name,
+							const char*							description,
+							const char*							func,
+							glu::DataType						returnType,
+							const glu::DataType					paramTypes[MAX_PARAMS],
+							const Vec4&							attribute,
+							int									modifyParamNdx,
+							bool								useNearlyConstantInputs,
+							glu::Precision						precision,
+							bool								isVertex,
+							const InitialCalibrationStorage&	initialCalibration)
+	: OperatorPerformanceCase	(context.getTestContext(), context.getRenderContext(), name, description,
+								 isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT, NUM_WORKLOADS, initialCalibration)
+	, m_func					(func)
+	, m_returnType				(returnType)
+	, m_modifyParamNdx			(modifyParamNdx)
+	, m_useNearlyConstantInputs	(useNearlyConstantInputs)
+	, m_attribute				(attribute)
+	, m_precision				(precision)
+{
+	for (int i = 0; i < MAX_PARAMS; i++)
+		m_paramTypes[i] = paramTypes[i];
+}
+
+string FunctionCase::sumExpr (const string& aExpr, const string& bExpr, glu::DataType type)
+{
+	if (glu::isDataTypeBoolOrBVec(type))
+	{
+		if (type == glu::TYPE_BOOL)
+			return "(" + aExpr + " == " + bExpr + ")";
+		else
+			return "equal(" + aExpr + ", " + bExpr + ")";
+	}
+	else
+		return "(" + aExpr + " + " + bExpr + ")";
+}
+
+string FunctionCase::incrementExpr (const string& baseExpr, glu::DataType type, bool divide)
+{
+	const string mulOrDiv = divide ? "/" : "*";
+
+	return glu::isDataTypeBoolOrBVec(type)	? baseExpr
+		 : glu::isDataTypeIntOrIVec(type)	? "(" + baseExpr + mulOrDiv + "(i+1))"
+		 :									  "(" + baseExpr + mulOrDiv + "float(i+1))";
+}
+
+FunctionCase::ProgramContext FunctionCase::generateSingleProgramData (ProgramID programID) const
+{
+	const bool			isVertexCase			= m_caseType == CASETYPE_VERTEX;
+	const char* const	precision				= glu::getPrecisionName(m_precision);
+	const char* const	returnTypeName			= getDataTypeName(m_returnType);
+	const string		returnPrecisionMaybe	= glu::isDataTypeBoolOrBVec(m_returnType) ? "" : string() + precision + " ";
+	const char*			inputPrecision			= DE_NULL;
+	const bool			isMatrixReturn			= isDataTypeMatrix(m_returnType);
+	int					numParams				= 0;
+	const char*			paramTypeNames[MAX_PARAMS];
+	string				paramPrecisionsMaybe[MAX_PARAMS];
+
+	for (int i = 0; i < MAX_PARAMS; i++)
+	{
+		paramTypeNames[i]			= getDataTypeName(m_paramTypes[i]);
+		paramPrecisionsMaybe[i]		= glu::isDataTypeBoolOrBVec(m_paramTypes[i]) ? "" : string() + precision + " ";
+
+		if (inputPrecision == DE_NULL && isDataTypeIntOrIVec(m_paramTypes[i]) && m_precision == glu::PRECISION_LOWP)
+			inputPrecision = "mediump";
+
+		if (m_paramTypes[i] != TYPE_INVALID)
+			numParams = i+1;
+	}
+
+	DE_ASSERT(numParams > 0);
+
+	if (inputPrecision == DE_NULL)
+		inputPrecision = precision;
+
+	int						numAttributes	= FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS + numParams - 1;
+	std::ostringstream		vtx;
+	std::ostringstream		frag;
+	std::ostringstream&		op				= isVertexCase ? vtx : frag;
+
+	// Attributes.
+	vtx << "attribute highp vec4 a_position;\n";
+	for (int i = 0; i < numAttributes; i++)
+		vtx << "attribute " << inputPrecision << " vec4 a_in" << i << ";\n";
+
+	if (isVertexCase)
+	{
+		vtx << "varying mediump vec4 v_color;\n";
+		frag << "varying mediump vec4 v_color;\n";
+	}
+	else
+	{
+		for (int i = 0; i < numAttributes; i++)
+		{
+			vtx << "varying " << inputPrecision << " vec4 v_in" << i << ";\n";
+			frag << "varying " << inputPrecision << " vec4 v_in" << i << ";\n";
+		}
+	}
+
+	op << "uniform mediump int u_numLoopIterations;\n";
+	if (isVertexCase)
+		op << "uniform mediump float u_zero;\n";
+
+	for (int paramNdx = 0; paramNdx < numParams; paramNdx++)
+		op << "uniform " << paramPrecisionsMaybe[paramNdx] << paramTypeNames[paramNdx] << " u_inc" << (char)('A'+paramNdx) << ";\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+
+	if (!isVertexCase)
+		vtx << "\tgl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Function call input and return value accumulation variables.
+	{
+		const char* const inPrefix = isVertexCase ? "a_" : "v_";
+
+		for (int calcNdx = 0; calcNdx < FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS; calcNdx++)
+		{
+			for (int paramNdx = 0; paramNdx < numParams; paramNdx++)
+			{
+				const glu::DataType		paramType	= m_paramTypes[paramNdx];
+				const bool				mustCast	= paramType != glu::TYPE_FLOAT_VEC4;
+
+				op << "\t" << paramPrecisionsMaybe[paramNdx] << paramTypeNames[paramNdx] << " in" << calcNdx << (char)('a'+paramNdx) << " = ";
+
+				if (mustCast)
+					op << paramTypeNames[paramNdx] << "(";
+
+				if (glu::isDataTypeMatrix(paramType))
+				{
+					static const char* const	swizzles[3]		= { "x", "xy", "xyz" };
+					const int					numRows			= glu::getDataTypeMatrixNumRows(paramType);
+					const int					numCols			= glu::getDataTypeMatrixNumColumns(paramType);
+					const string				swizzle			= numRows < 4 ? string() + "." + swizzles[numRows-1] : "";
+
+					for (int i = 0; i < numCols; i++)
+						op << (i > 0 ? ", " : "") << inPrefix << "in" << calcNdx+paramNdx << swizzle;
+				}
+				else
+				{
+					op << inPrefix << "in" << calcNdx+paramNdx;
+
+					if (paramNdx == m_modifyParamNdx)
+					{
+						DE_ASSERT(glu::isDataTypeFloatOrVec(paramType));
+						op << " + 2.0";
+					}
+				}
+
+				if (mustCast)
+					op << ")";
+
+				op << ";\n";
+			}
+
+			op << "\t" << returnPrecisionMaybe << returnTypeName << " res" << calcNdx << " = " << returnTypeName << "(0);\n";
+		}
+	}
+
+	// Loop with expressions in it.
+	op << "\tfor (int i = 0; i < u_numLoopIterations; i++)\n";
+	op << "\t{\n";
+	for (int calcNdx = 0; calcNdx < FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS; calcNdx++)
+	{
+		if (calcNdx > 0)
+			op << "\n";
+
+		op << "\t\t{\n";
+
+		for (int inputNdx = 0; inputNdx < numParams; inputNdx++)
+		{
+			const string inputName	= "in" + de::toString(calcNdx) + (char)('a'+inputNdx);
+			const string incName	= string() + "u_inc" + (char)('A'+inputNdx);
+			const string incExpr	= incrementExpr(incName, m_paramTypes[inputNdx], m_useNearlyConstantInputs);
+
+			op << "\t\t\t" << inputName << " = " << sumExpr(inputName, incExpr, m_paramTypes[inputNdx]) << ";\n";
+		}
+
+		op << "\t\t\t" << returnPrecisionMaybe << returnTypeName << " eval" << calcNdx << " = ";
+
+		if (programID == PROGRAM_WITH_FUNCTION_CALLS)
+		{
+			op << m_func << "(";
+
+			for (int paramNdx = 0; paramNdx < numParams; paramNdx++)
+			{
+				if (paramNdx > 0)
+					op << ", ";
+
+				op << "in" << calcNdx << (char)('a'+paramNdx);
+			}
+
+			op << ")";
+		}
+		else
+		{
+			DE_ASSERT(programID == PROGRAM_WITHOUT_FUNCTION_CALLS);
+			op << returnTypeName << "(1)";
+		}
+
+		op << ";\n";
+
+		{
+			const string resName	= "res" + de::toString(calcNdx);
+			const string evalName	= "eval" + de::toString(calcNdx);
+			const string incExpr	= incrementExpr(evalName, m_returnType, m_useNearlyConstantInputs);
+
+			op << "\t\t\tres" << calcNdx << " = " << sumExpr(resName, incExpr, m_returnType) << ";\n";
+		}
+
+		op << "\t\t}\n";
+	}
+	op << "\t}\n";
+	op << "\n";
+
+	// Result variables.
+	for (int inputNdx = 0; inputNdx < numParams; inputNdx++)
+	{
+		op << "\t" << paramPrecisionsMaybe[inputNdx] << paramTypeNames[inputNdx] << " sumIn" << (char)('A'+inputNdx) << " = ";
+		{
+			string expr = string() + "in0" + (char)('a'+inputNdx);
+			for (int i = 1; i < FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS; i++)
+				expr = sumExpr(expr, string() + "in" + de::toString(i) + (char)('a'+inputNdx), m_paramTypes[inputNdx]);
+			op << expr;
+		}
+		op << ";\n";
+	}
+
+	op << "\t" << returnPrecisionMaybe << returnTypeName << " sumRes = ";
+	{
+		string expr = "res0";
+		for (int i = 1; i < FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS; i++)
+			expr = sumExpr(expr, "res" + de::toString(i), m_returnType);
+		op << expr;
+	}
+	op << ";\n";
+
+	{
+		glu::DataType finalResultDataType = glu::TYPE_LAST;
+
+		if (glu::isDataTypeMatrix(m_returnType))
+		{
+			finalResultDataType = m_returnType;
+
+			op << "\t" << precision << " " << returnTypeName << " finalRes = ";
+
+			for (int inputNdx = 0; inputNdx < numParams; inputNdx++)
+			{
+				DE_ASSERT(m_paramTypes[inputNdx] == m_returnType);
+				op << "sumIn" << (char)('A'+inputNdx) << " + ";
+			}
+			op << "sumRes;\n";
+		}
+		else
+		{
+			int numFinalResComponents = glu::getDataTypeScalarSize(m_returnType);
+			for (int inputNdx = 0; inputNdx < numParams; inputNdx++)
+				numFinalResComponents = de::max(numFinalResComponents, glu::getDataTypeScalarSize(m_paramTypes[inputNdx]));
+
+			finalResultDataType = getDataTypeFloatOrVec(numFinalResComponents);
+
+			{
+				const string finalResType = glu::getDataTypeName(finalResultDataType);
+				op << "\t" << precision << " " << finalResType << " finalRes = ";
+				for (int inputNdx = 0; inputNdx < numParams; inputNdx++)
+					op << finalResType << "(sumIn" << (char)('A'+inputNdx) << ") + ";
+				op << finalResType << "(sumRes);\n";
+			}
+		}
+
+		// Convert to color.
+		op << "\tmediump vec4 color = ";
+		if (finalResultDataType == TYPE_FLOAT_VEC4)
+			op << "finalRes";
+		else
+		{
+			int size = isMatrixReturn ? getDataTypeMatrixNumRows(finalResultDataType) : getDataTypeScalarSize(finalResultDataType);
+
+			op << "vec4(";
+
+			if (isMatrixReturn)
+			{
+				for (int i = 0; i < getDataTypeMatrixNumColumns(finalResultDataType); i++)
+				{
+					if (i > 0)
+						op << " + ";
+					op << "finalRes[" << i << "]";
+				}
+			}
+			else
+				op << "finalRes";
+
+			for (int i = size; i < 4; i++)
+				op << ", " << (i == 3 ? "1.0" : "0.0");
+
+			op << ")";
+		}
+		op << ";\n";
+		op << "\t" << (isVertexCase ? "v_color" : "gl_FragColor") << " = color;\n";
+
+		if (isVertexCase)
+		{
+			vtx << "	gl_Position = a_position + u_zero*color;\n";
+			frag << "	gl_FragColor = v_color;\n";
+		}
+		else
+		{
+			for (int i = 0; i < numAttributes; i++)
+				vtx << "	v_in" << i << " = a_in" << i << ";\n";
+		}
+
+		vtx << "}\n";
+		frag << "}\n";
+	}
+
+	{
+		vector<AttribSpec> attributes;
+		for (int i = 0; i < numAttributes; i++)
+			attributes.push_back(AttribSpec(("a_in" + de::toString(i)).c_str(),
+											m_attribute.swizzle((i+0)%4, (i+1)%4, (i+2)%4, (i+3)%4),
+											m_attribute.swizzle((i+1)%4, (i+2)%4, (i+3)%4, (i+0)%4),
+											m_attribute.swizzle((i+2)%4, (i+3)%4, (i+0)%4, (i+1)%4),
+											m_attribute.swizzle((i+3)%4, (i+0)%4, (i+1)%4, (i+2)%4)));
+
+		{
+			string description = "This is the program ";
+
+			description += programID == PROGRAM_WITHOUT_FUNCTION_CALLS	? "without"
+						 : programID == PROGRAM_WITH_FUNCTION_CALLS		? "with"
+						 : DE_NULL;
+
+			description += " '" + m_func + "' function calls.\n"
+						   "Note: workload size for this program means the number of loop iterations.";
+
+			return ProgramContext(vtx.str(), frag.str(), attributes, description);
+		}
+	}
+}
+
+vector<FunctionCase::ProgramContext> FunctionCase::generateProgramData (void) const
+{
+	vector<ProgramContext> progData;
+	for (int i = 0; i < PROGRAM_LAST; i++)
+		progData.push_back(generateSingleProgramData((ProgramID)i));
+	return progData;
+}
+
+void FunctionCase::setGeneralUniforms (deUint32 program) const
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	gl.uniform1f(gl.getUniformLocation(program, "u_zero"), 0.0f);
+
+	for (int paramNdx = 0; paramNdx < MAX_PARAMS; paramNdx++)
+	{
+		if (m_paramTypes[paramNdx] != glu::TYPE_INVALID)
+		{
+			const glu::DataType		paramType	= m_paramTypes[paramNdx];
+			const int				scalarSize	= glu::getDataTypeScalarSize(paramType);
+			const int				location	= gl.getUniformLocation(program, (string() + "u_inc" + (char)('A'+paramNdx)).c_str());
+
+			if (glu::isDataTypeFloatOrVec(paramType))
+			{
+				float values[4];
+				for (int i = 0; i < DE_LENGTH_OF_ARRAY(values); i++)
+					values[i] = (float)paramNdx*0.01f + (float)i*0.001f; // Arbitrary small values.
+				uniformNfv(gl, scalarSize, location, 1, &values[0]);
+			}
+			else if (glu::isDataTypeIntOrIVec(paramType))
+			{
+				int values[4];
+				for (int i = 0; i < DE_LENGTH_OF_ARRAY(values); i++)
+					values[i] = paramNdx*100 + i; // Arbitrary values.
+				uniformNiv(gl, scalarSize, location, 1, &values[0]);
+			}
+			else if (glu::isDataTypeBoolOrBVec(paramType))
+			{
+				int values[4];
+				for (int i = 0; i < DE_LENGTH_OF_ARRAY(values); i++)
+					values[i] = (paramNdx >> i) & 1; // Arbitrary values.
+				uniformNiv(gl, scalarSize, location, 1, &values[0]);
+			}
+			else if (glu::isDataTypeMatrix(paramType))
+			{
+				const int size = glu::getDataTypeMatrixNumRows(paramType);
+				DE_ASSERT(size == glu::getDataTypeMatrixNumColumns(paramType));
+				float values[4*4];
+				for (int i = 0; i < DE_LENGTH_OF_ARRAY(values); i++)
+					values[i] = (float)paramNdx*0.01f + (float)i*0.001f; // Arbitrary values.
+				uniformMatrixNfv(gl, size, location, 1, &values[0]);
+			}
+			else
+				DE_ASSERT(false);
+		}
+	}
+}
+
+void FunctionCase::setWorkloadSizeUniform (deUint32 program, int numLoopIterations) const
+{
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+	const int				loc		= gl.getUniformLocation(program, "u_numLoopIterations");
+
+	gl.uniform1i(loc, numLoopIterations);
+}
+
+float FunctionCase::computeSingleOperationTime (const vector<float>& perProgramOperationCosts) const
+{
+	DE_ASSERT(perProgramOperationCosts.size() == PROGRAM_LAST);
+	const int		numFunctionCalls			= FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS;
+	const float		programOperationCostDiff	= perProgramOperationCosts[PROGRAM_WITH_FUNCTION_CALLS] - perProgramOperationCosts[PROGRAM_WITHOUT_FUNCTION_CALLS];
+
+	return programOperationCostDiff / (float)numFunctionCalls;
+}
+
+void FunctionCase::logSingleOperationCalculationInfo (void) const
+{
+	const int numFunctionCalls = FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS;
+
+	m_testCtx.getLog() << TestLog::Message << "Note: program " << (int)PROGRAM_WITH_FUNCTION_CALLS << " contains "
+										   << numFunctionCalls << " calls to '" << m_func << "' in one loop iteration; "
+										   << "cost of one operation is calculated as "
+										   << "(cost_of_workload_with_calls - cost_of_workload_without_calls) / " << numFunctionCalls << TestLog::EndMessage;
+}
+
+} // anonymous
+
+ShaderOperatorTests::ShaderOperatorTests (Context& context)
+	: TestCaseGroup(context, "operator", "Operator Performance Tests")
+{
+}
+
+ShaderOperatorTests::~ShaderOperatorTests (void)
+{
+}
+
+void ShaderOperatorTests::init (void)
+{
+	// Binary operator cases
+
+	static const DataType binaryOpTypes[] =
+	{
+		TYPE_FLOAT,
+		TYPE_FLOAT_VEC2,
+		TYPE_FLOAT_VEC3,
+		TYPE_FLOAT_VEC4,
+		TYPE_INT,
+		TYPE_INT_VEC2,
+		TYPE_INT_VEC3,
+		TYPE_INT_VEC4,
+	};
+	static const Precision precisions[] =
+	{
+		PRECISION_LOWP,
+		PRECISION_MEDIUMP,
+		PRECISION_HIGHP
+	};
+	static const struct
+	{
+		const char*		name;
+		const char*		op;
+		bool			swizzle;
+	} binaryOps[] =
+	{
+		{ "add",		"+",		false	},
+		{ "sub",		"-",		true	},
+		{ "mul",		"*",		false	},
+		{ "div",		"/",		true	}
+	};
+
+	tcu::TestCaseGroup* const binaryOpsGroup = new tcu::TestCaseGroup(m_testCtx, "binary_operator", "Binary Operator Performance Tests");
+	addChild(binaryOpsGroup);
+
+	for (int opNdx = 0; opNdx < DE_LENGTH_OF_ARRAY(binaryOps); opNdx++)
+	{
+		tcu::TestCaseGroup* const opGroup = new tcu::TestCaseGroup(m_testCtx, binaryOps[opNdx].name, "");
+		binaryOpsGroup->addChild(opGroup);
+
+		for (int isFrag = 0; isFrag <= 1; isFrag++)
+		{
+			const BinaryOpCase::InitialCalibrationStorage	shaderGroupCalibrationStorage	(new BinaryOpCase::InitialCalibration);
+			const bool										isVertex						= isFrag == 0;
+			tcu::TestCaseGroup* const						shaderGroup						= new tcu::TestCaseGroup(m_testCtx, isVertex ? "vertex" : "fragment", "");
+			opGroup->addChild(shaderGroup);
+
+			for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(binaryOpTypes); typeNdx++)
+			{
+				for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+				{
+					const DataType		type			= binaryOpTypes[typeNdx];
+					const Precision		precision		= precisions[precNdx];
+					const char* const	op				= binaryOps[opNdx].op;
+					const bool			useSwizzle		= binaryOps[opNdx].swizzle;
+					std::ostringstream	name;
+
+					name << getPrecisionName(precision) << "_" << getDataTypeName(type);
+
+					shaderGroup->addChild(new BinaryOpCase(m_context, name.str().c_str(), "", op, type, precision, useSwizzle, isVertex, shaderGroupCalibrationStorage));
+				}
+			}
+		}
+	}
+
+	// Built-in function cases.
+
+	// Non-specific (i.e. includes gentypes) parameter types for the functions.
+	enum ValueType
+	{
+		VALUE_NONE			= 0,
+		VALUE_FLOAT			= (1<<0),	// float scalar
+		VALUE_FLOAT_VEC		= (1<<1),	// float vector
+		VALUE_FLOAT_VEC34	= (1<<2),	// float vector of size 3 or 4
+		VALUE_FLOAT_GENTYPE	= (1<<3),	// float scalar/vector
+		VALUE_VEC3			= (1<<4),	// vec3 only
+		VALUE_VEC4			= (1<<5),	// vec4 only
+		VALUE_MATRIX		= (1<<6),	// matrix
+		VALUE_BOOL			= (1<<7),	// boolean scalar
+		VALUE_BOOL_VEC		= (1<<8),	// boolean vector
+		VALUE_BOOL_GENTYPE	= (1<<9),	// boolean scalar/vector
+		VALUE_INT			= (1<<10),	// int scalar
+		VALUE_INT_VEC		= (1<<11),	// int vector
+		VALUE_INT_GENTYPE	= (1<<12),	// int scalar/vector
+
+		// Shorthands.
+		N				= VALUE_NONE,
+		F				= VALUE_FLOAT,
+		FV				= VALUE_FLOAT_VEC,
+		VL				= VALUE_FLOAT_VEC34, // L for "large"
+		GT				= VALUE_FLOAT_GENTYPE,
+		V3				= VALUE_VEC3,
+		V4				= VALUE_VEC4,
+		M				= VALUE_MATRIX,
+		B				= VALUE_BOOL,
+		BV				= VALUE_BOOL_VEC,
+		BGT				= VALUE_BOOL_GENTYPE,
+		I				= VALUE_INT,
+		IV				= VALUE_INT_VEC,
+		IGT				= VALUE_INT_GENTYPE,
+
+		VALUE_ANY_FLOAT			= VALUE_FLOAT		|	VALUE_FLOAT_VEC		|	VALUE_FLOAT_GENTYPE	| VALUE_VEC3 | VALUE_VEC4 | VALUE_FLOAT_VEC34,
+		VALUE_ANY_INT			= VALUE_INT			|	VALUE_INT_VEC		|	VALUE_INT_GENTYPE,
+		VALUE_ANY_BOOL			= VALUE_BOOL		|	VALUE_BOOL_VEC		|	VALUE_BOOL_GENTYPE,
+
+		VALUE_ANY_GENTYPE		= VALUE_FLOAT_VEC	|	VALUE_FLOAT_GENTYPE	|	VALUE_FLOAT_VEC34	|
+								  VALUE_BOOL_VEC	|	VALUE_BOOL_GENTYPE	|
+								  VALUE_INT_VEC		|	VALUE_INT_GENTYPE	|
+								  VALUE_MATRIX
+	};
+	enum PrecisionMask
+	{
+		PRECMASK_NA				= 0,						//!< Precision not applicable (booleans)
+		PRECMASK_LOWP			= (1<<PRECISION_LOWP),
+		PRECMASK_MEDIUMP		= (1<<PRECISION_MEDIUMP),
+		PRECMASK_HIGHP			= (1<<PRECISION_HIGHP),
+
+		PRECMASK_MEDIUMP_HIGHP	= (1<<PRECISION_MEDIUMP) | (1<<PRECISION_HIGHP),
+		PRECMASK_ALL			= (1<<PRECISION_LOWP) | (1<<PRECISION_MEDIUMP) | (1<<PRECISION_HIGHP)
+	};
+
+	static const DataType floatTypes[] =
+	{
+		TYPE_FLOAT,
+		TYPE_FLOAT_VEC2,
+		TYPE_FLOAT_VEC3,
+		TYPE_FLOAT_VEC4
+	};
+	static const DataType intTypes[] =
+	{
+		TYPE_INT,
+		TYPE_INT_VEC2,
+		TYPE_INT_VEC3,
+		TYPE_INT_VEC4
+	};
+	static const DataType boolTypes[] =
+	{
+		TYPE_BOOL,
+		TYPE_BOOL_VEC2,
+		TYPE_BOOL_VEC3,
+		TYPE_BOOL_VEC4
+	};
+	static const DataType matrixTypes[] =
+	{
+		TYPE_FLOAT_MAT2,
+		TYPE_FLOAT_MAT3,
+		TYPE_FLOAT_MAT4
+	};
+
+	tcu::TestCaseGroup* const angleAndTrigonometryGroup		= new tcu::TestCaseGroup(m_testCtx, "angle_and_trigonometry",	"Built-In Angle and Trigonometry Function Performance Tests");
+	tcu::TestCaseGroup* const exponentialGroup				= new tcu::TestCaseGroup(m_testCtx, "exponential",				"Built-In Exponential Function Performance Tests");
+	tcu::TestCaseGroup* const commonFunctionsGroup			= new tcu::TestCaseGroup(m_testCtx, "common_functions",			"Built-In Common Function Performance Tests");
+	tcu::TestCaseGroup* const geometricFunctionsGroup		= new tcu::TestCaseGroup(m_testCtx, "geometric",				"Built-In Geometric Function Performance Tests");
+	tcu::TestCaseGroup* const matrixFunctionsGroup			= new tcu::TestCaseGroup(m_testCtx, "matrix",					"Built-In Matrix Function Performance Tests");
+	tcu::TestCaseGroup* const floatCompareGroup				= new tcu::TestCaseGroup(m_testCtx, "float_compare",			"Built-In Floating Point Comparison Function Performance Tests");
+	tcu::TestCaseGroup* const intCompareGroup				= new tcu::TestCaseGroup(m_testCtx, "int_compare",				"Built-In Integer Comparison Function Performance Tests");
+	tcu::TestCaseGroup* const boolCompareGroup				= new tcu::TestCaseGroup(m_testCtx, "bool_compare",				"Built-In Boolean Comparison Function Performance Tests");
+
+	addChild(angleAndTrigonometryGroup);
+	addChild(exponentialGroup);
+	addChild(commonFunctionsGroup);
+	addChild(geometricFunctionsGroup);
+	addChild(matrixFunctionsGroup);
+	addChild(floatCompareGroup);
+	addChild(intCompareGroup);
+	addChild(boolCompareGroup);
+
+	// Some attributes to be used as parameters for the functions.
+	const Vec4 attrPos		= Vec4( 2.3f,  1.9f,  0.8f,  0.7f);
+	const Vec4 attrNegPos	= Vec4(-1.3f,  2.5f, -3.5f,	 4.3f);
+	const Vec4 attrSmall	= Vec4(-0.9f,  0.8f, -0.4f,	 0.2f);
+
+	// Function name, return type and parameter type information; also, what attribute should be used in the test.
+	// \note Different versions of the same function (i.e. with the same group name) can be defined by putting them successively in this array.
+	// \note In order to reduce case count and thus total execution time, we don't test all input type combinations for every function.
+	static const struct
+	{
+		tcu::TestCaseGroup*					parentGroup;
+		const char*							groupName;
+		const char*							func;
+		const ValueType						types[FunctionCase::MAX_PARAMS + 1]; // Return type and parameter types, in that order.
+		const Vec4&							attribute;
+		int									modifyParamNdx;
+		bool								useNearlyConstantInputs;
+		bool								booleanCase;
+		PrecisionMask						precMask;
+	} functionCaseGroups[] =
+	{
+		{ angleAndTrigonometryGroup,	"radians",			"radians",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"degrees",			"degrees",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"sin",				"sin",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"cos",				"cos",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"tan",				"tan",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"asin",				"asin",				{ F,  F,  N,  N  }, attrSmall,		-1, true,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"acos",				"acos",				{ F,  F,  N,  N  }, attrSmall,		-1, true,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"atan2",			"atan",				{ F,  F,  F,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"atan",				"atan",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ exponentialGroup,				"pow",				"pow",				{ F,  F,  F,  N  }, attrPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"exp",				"exp",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"log",				"log",				{ F,  F,  N,  N  }, attrPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"exp2",				"exp2",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"log2",				"log2",				{ F,  F,  N,  N  }, attrPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"sqrt",				"sqrt",				{ F,  F,  N,  N  }, attrPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"inversesqrt",		"inversesqrt",		{ F,  F,  N,  N  }, attrPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ commonFunctionsGroup,			"abs",				"abs",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"abs",				"abs",				{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"sign",				"sign",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"sign",				"sign",				{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"floor",			"floor",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"floor",			"floor",			{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"ceil",				"ceil",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"ceil",				"ceil",				{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"fract",			"fract",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"fract",			"fract",			{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"mod",				"mod",				{ GT, GT, GT, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"min",				"min",				{ F,  F,  F,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"min",				"min",				{ V4, V4, V4, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"max",				"max",				{ F,  F,  F,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"max",				"max",				{ V4, V4, V4, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"clamp",			"clamp",			{ F,  F,  F,  F  }, attrSmall,		 2, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"clamp",			"clamp",			{ V4, V4, V4, V4 }, attrSmall,		 2, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"mix",				"mix",				{ F,  F,  F,  F  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"mix",				"mix",				{ V4, V4, V4, V4 }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"step",				"step",				{ F,  F,  F,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"step",				"step",				{ V4, V4, V4, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"smoothstep",		"smoothstep",		{ F,  F,  F,  F  }, attrSmall,		 1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"smoothstep",		"smoothstep",		{ V4, V4, V4, V4 }, attrSmall,		 1, false,	false,	PRECMASK_ALL			},
+
+		{ geometricFunctionsGroup,		"length",			"length",			{ F,  VL, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"distance",			"distance",			{ F,  VL, VL, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"dot",				"dot",				{ F,  VL, VL, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"cross",			"cross",			{ V3, V3, V3, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"normalize",		"normalize",		{ VL, VL, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"faceforward",		"faceforward",		{ VL, VL, VL, VL }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"reflect",			"reflect",			{ VL, VL, VL, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"refract",			"refract",			{ VL, VL, VL, F  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ matrixFunctionsGroup,			"matrixCompMult",	"matrixCompMult",	{ M,  M,  M,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ floatCompareGroup,			"lessThan",			"lessThan",			{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ floatCompareGroup,			"lessThanEqual",	"lessThanEqual",	{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ floatCompareGroup,			"greaterThan",		"greaterThan",		{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ floatCompareGroup,			"greaterThanEqual",	"greaterThanEqual",	{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ floatCompareGroup,			"equal",			"equal",			{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ floatCompareGroup,			"notEqual",			"notEqual",			{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ intCompareGroup,				"lessThan",			"lessThan",			{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ intCompareGroup,				"lessThanEqual",	"lessThanEqual",	{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ intCompareGroup,				"greaterThan",		"greaterThan",		{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ intCompareGroup,				"greaterThanEqual",	"greaterThanEqual",	{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ intCompareGroup,				"equal",			"equal",			{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ intCompareGroup,				"notEqual",			"notEqual",			{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ boolCompareGroup,				"equal",			"equal",			{ BV, BV, BV, N  }, attrNegPos,		-1, false,	true,	PRECMASK_MEDIUMP		},
+		{ boolCompareGroup,				"notEqual",			"notEqual",			{ BV, BV, BV, N  }, attrNegPos,		-1, false,	true,	PRECMASK_MEDIUMP		},
+		{ boolCompareGroup,				"any",				"any",				{ B,  BV, N,  N  }, attrNegPos,		-1, false,	true,	PRECMASK_MEDIUMP		},
+		{ boolCompareGroup,				"all",				"all",				{ B,  BV, N,  N  }, attrNegPos,		-1, false,	true,	PRECMASK_MEDIUMP		},
+		{ boolCompareGroup,				"not",				"not",				{ BV, BV, N,  N  }, attrNegPos,		-1, false,	true,	PRECMASK_MEDIUMP		}
+	};
+
+	// vertexSubGroup and fragmentSubGroup are the groups where the various vertex/fragment cases of a single function are added.
+	// \note These are defined here so that different versions (different entries in the functionCaseGroups array) of the same function can be put in the same group.
+	tcu::TestCaseGroup*							vertexSubGroup		= DE_NULL;
+	tcu::TestCaseGroup*							fragmentSubGroup	= DE_NULL;
+	FunctionCase::InitialCalibrationStorage		vertexSubGroupCalibrationStorage;
+	FunctionCase::InitialCalibrationStorage		fragmentSubGroupCalibrationStorage;
+	for (int funcNdx = 0; funcNdx < DE_LENGTH_OF_ARRAY(functionCaseGroups); funcNdx++)
+	{
+		tcu::TestCaseGroup* const	parentGroup					= functionCaseGroups[funcNdx].parentGroup;
+		const char* const			groupName					= functionCaseGroups[funcNdx].groupName;
+		const char* const			groupFunc					= functionCaseGroups[funcNdx].func;
+		const ValueType* const		funcTypes					= functionCaseGroups[funcNdx].types;
+		const Vec4&					groupAttribute				= functionCaseGroups[funcNdx].attribute;
+		const int					modifyParamNdx				= functionCaseGroups[funcNdx].modifyParamNdx;
+		const bool					useNearlyConstantInputs		= functionCaseGroups[funcNdx].useNearlyConstantInputs;
+		const bool					booleanCase					= functionCaseGroups[funcNdx].booleanCase;
+		const PrecisionMask			precMask					= functionCaseGroups[funcNdx].precMask;
+
+		// If this is a new function and not just a different version of the previously defined function, create a new group.
+		if (funcNdx == 0 || parentGroup != functionCaseGroups[funcNdx-1].parentGroup || string(groupName) != functionCaseGroups[funcNdx-1].groupName)
+		{
+			tcu::TestCaseGroup* const funcGroup = new tcu::TestCaseGroup(m_testCtx, groupName, "");
+			functionCaseGroups[funcNdx].parentGroup->addChild(funcGroup);
+
+			vertexSubGroup		= new tcu::TestCaseGroup(m_testCtx, "vertex", "");
+			fragmentSubGroup	= new tcu::TestCaseGroup(m_testCtx, "fragment", "");
+
+			funcGroup->addChild(vertexSubGroup);
+			funcGroup->addChild(fragmentSubGroup);
+
+			vertexSubGroupCalibrationStorage	= FunctionCase::InitialCalibrationStorage(new FunctionCase::InitialCalibration);
+			fragmentSubGroupCalibrationStorage	= FunctionCase::InitialCalibrationStorage(new FunctionCase::InitialCalibration);
+		}
+
+		DE_ASSERT(vertexSubGroup != DE_NULL);
+		DE_ASSERT(fragmentSubGroup != DE_NULL);
+
+		// Find the type size range of parameters (e.g. from 2 to 4 in case of vectors).
+		int genTypeFirstSize	= 1;
+		int genTypeLastSize		= 1;
+
+		// Find the first return value or parameter with a gentype (if any) and set sizes accordingly.
+		// \note Assumes only matching sizes gentypes are to be found, e.g. no "genType func (vec param)"
+		for (int i = 0; i < FunctionCase::MAX_PARAMS + 1 && genTypeLastSize == 1; i++)
+		{
+			switch (funcTypes[i])
+			{
+				case VALUE_FLOAT_VEC:
+				case VALUE_BOOL_VEC:
+				case VALUE_INT_VEC:			// \note Fall-through.
+					genTypeFirstSize = 2;
+					genTypeLastSize = 4;
+					break;
+				case VALUE_FLOAT_VEC34:
+					genTypeFirstSize = 3;
+					genTypeLastSize = 4;
+					break;
+				case VALUE_FLOAT_GENTYPE:
+				case VALUE_BOOL_GENTYPE:
+				case VALUE_INT_GENTYPE:		// \note Fall-through.
+					genTypeFirstSize = 1;
+					genTypeLastSize = 4;
+					break;
+				case VALUE_MATRIX:
+					genTypeFirstSize = 2;
+					genTypeLastSize = 4;
+					break;
+				// If none of the above, keep looping.
+				default:
+					break;
+			}
+		}
+
+		// Create a case for each possible size of the gentype.
+		for (int curSize = genTypeFirstSize; curSize <= genTypeLastSize; curSize++)
+		{
+			// Determine specific types for return value and the parameters, according to curSize. Non-gentypes not affected by curSize.
+			DataType types[FunctionCase::MAX_PARAMS + 1];
+			for (int i = 0; i < FunctionCase::MAX_PARAMS + 1; i++)
+			{
+				if (funcTypes[i] == VALUE_NONE)
+					types[i] = TYPE_INVALID;
+				else
+				{
+					int isFloat	= funcTypes[i] & VALUE_ANY_FLOAT;
+					int isBool	= funcTypes[i] & VALUE_ANY_BOOL;
+					int isInt	= funcTypes[i] & VALUE_ANY_INT;
+					int isMat	= funcTypes[i] == VALUE_MATRIX;
+					int inSize	= (funcTypes[i] & VALUE_ANY_GENTYPE)	? curSize
+								: funcTypes[i] == VALUE_VEC3			? 3
+								: funcTypes[i] == VALUE_VEC4			? 4
+								: 1;
+					int			typeArrayNdx = isMat ? inSize - 2 : inSize - 1; // \note No matrices of size 1.
+
+					types[i]	= isFloat	? floatTypes[typeArrayNdx]
+								: isBool	? boolTypes[typeArrayNdx]
+								: isInt		? intTypes[typeArrayNdx]
+								: isMat		? matrixTypes[typeArrayNdx]
+								: TYPE_LAST;
+				}
+
+				DE_ASSERT(types[i] != TYPE_LAST);
+			}
+
+			// Array for just the parameter types.
+			DataType paramTypes[FunctionCase::MAX_PARAMS];
+			for (int i = 0; i < FunctionCase::MAX_PARAMS; i++)
+				paramTypes[i] = types[i+1];
+
+			for (int prec = (int)PRECISION_LOWP; prec < (int)PRECISION_LAST; prec++)
+			{
+				if ((precMask & (1 << prec)) == 0)
+					continue;
+
+				const string		precisionPrefix = booleanCase ? "" : (string(getPrecisionName((Precision)prec)) + "_");
+				std::ostringstream	caseName;
+
+				caseName << precisionPrefix;
+
+				// Write the name of each distinct parameter data type into the test case name.
+				for (int i = 1; i < FunctionCase::MAX_PARAMS + 1 && types[i] != TYPE_INVALID; i++)
+				{
+					if (i == 1 || types[i] != types[i-1])
+					{
+						if (i > 1)
+							caseName << "_";
+
+						caseName << getDataTypeName(types[i]);
+					}
+				}
+
+				for (int fragI = 0; fragI <= 1; fragI++)
+				{
+					const bool					vert	= fragI == 0;
+					tcu::TestCaseGroup* const	group	= vert ? vertexSubGroup : fragmentSubGroup;
+					group->addChild	(new FunctionCase(m_context,
+													  caseName.str().c_str(), "",
+													  groupFunc,
+													  types[0], paramTypes,
+													  groupAttribute, modifyParamNdx, useNearlyConstantInputs,
+													  (Precision)prec, vert,
+													  vert ? vertexSubGroupCalibrationStorage : fragmentSubGroupCalibrationStorage));
+				}
+			}
+		}
+	}
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pShaderOperatorTests.hpp b/modules/gles2/performance/es2pShaderOperatorTests.hpp
new file mode 100644
index 0000000..0fb397e
--- /dev/null
+++ b/modules/gles2/performance/es2pShaderOperatorTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PSHADEROPERATORTESTS_HPP
+#define _ES2PSHADEROPERATORTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader operator performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class ShaderOperatorTests : public TestCaseGroup
+{
+public:
+							ShaderOperatorTests		(Context& context);
+							~ShaderOperatorTests	(void);
+
+	void					init					(void);
+
+private:
+							ShaderOperatorTests		(const ShaderOperatorTests& other);
+	ShaderOperatorTests&	operator=				(const ShaderOperatorTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PSHADEROPERATORTESTS_HPP
diff --git a/modules/gles2/performance/es2pShaderOptimizationTests.cpp b/modules/gles2/performance/es2pShaderOptimizationTests.cpp
new file mode 100644
index 0000000..1bef454
--- /dev/null
+++ b/modules/gles2/performance/es2pShaderOptimizationTests.cpp
@@ -0,0 +1,1015 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Optimized vs unoptimized shader performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pShaderOptimizationTests.hpp"
+#include "glsShaderPerformanceMeasurer.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "tcuStringTemplate.hpp"
+#include "deSharedPtr.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+
+#include "glwFunctions.hpp"
+
+#include <vector>
+#include <string>
+#include <map>
+
+using glu::ShaderProgram;
+using tcu::TestLog;
+using tcu::Vec4;
+using de::SharedPtr;
+using de::toString;
+
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+
+using gls::ShaderPerformanceMeasurer;
+
+namespace gles2
+{
+namespace Performance
+{
+
+static inline std::map<string, string> singleMap (const string& key, const string& value)
+{
+	std::map<string, string> res;
+	res[key] = value;
+	return res;
+}
+
+static inline string repeat (const string& str, int numRepeats, const string& delim = "")
+{
+	string result = str;
+	for (int i = 1; i < numRepeats; i++)
+		result += delim + str;
+	return result;
+}
+
+static inline string repeatIndexedTemplate (const string& strTempl, int numRepeats, const string& delim = "", int ndxStart = 0)
+{
+	const tcu::StringTemplate	templ(strTempl);
+	string						result;
+	std::map<string, string>	params;
+
+	for (int i = 0; i < numRepeats; i++)
+	{
+		params["PREV_NDX"]	= toString(i + ndxStart - 1);
+		params["NDX"]		= toString(i + ndxStart);
+
+		result += (i > 0 ? delim : "") + templ.specialize(params);
+	}
+
+	return result;
+}
+
+namespace
+{
+
+enum CaseShaderType
+{
+	CASESHADERTYPE_VERTEX = 0,
+	CASESHADERTYPE_FRAGMENT,
+
+	CASESHADERTYPE_LAST
+};
+
+static inline string getShaderPrecision (CaseShaderType shaderType)
+{
+	switch (shaderType)
+	{
+		case CASESHADERTYPE_VERTEX:		return "highp";
+		case CASESHADERTYPE_FRAGMENT:	return "mediump";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+struct ProgramData
+{
+	glu::ProgramSources			sources;
+	vector<gls::AttribSpec>		attributes; //!< \note Shouldn't contain a_position; that one is set by gls::ShaderPerformanceMeasurer.
+
+	ProgramData (void) {}
+	ProgramData (const glu::ProgramSources& sources_, const vector<gls::AttribSpec>& attributes_ = vector<gls::AttribSpec>())	: sources(sources_), attributes(attributes_)	{}
+	ProgramData (const glu::ProgramSources& sources_, const gls::AttribSpec& attribute)											: sources(sources_), attributes(1, attribute)	{}
+};
+
+//! Shader boilerplate helper; most cases have similar basic shader structure.
+static inline ProgramData defaultProgramData (CaseShaderType shaderType, const string& funcDefs, const string& mainStatements)
+{
+	const bool		isVertexCase	= shaderType == CASESHADERTYPE_VERTEX;
+	const bool		isFragmentCase	= shaderType == CASESHADERTYPE_FRAGMENT;
+	const string	vtxPrec			= getShaderPrecision(CASESHADERTYPE_VERTEX);
+	const string	fragPrec		= getShaderPrecision(CASESHADERTYPE_FRAGMENT);
+
+	return ProgramData(glu::ProgramSources() << glu::VertexSource(		"attribute " + vtxPrec + " vec4 a_position;\n"
+																		"attribute " + vtxPrec + " vec4 a_value;\n"
+																		"varying " + fragPrec + " vec4 v_value;\n"
+																		+ (isVertexCase ? funcDefs : "") +
+																		"void main (void)\n"
+																		"{\n"
+																		"	gl_Position = a_position;\n"
+																		"	" + vtxPrec + " vec4 value = a_value;\n"
+																		+ (isVertexCase ? mainStatements : "") +
+																		"	v_value = value;\n"
+																		"}\n")
+
+											 << glu::FragmentSource(	"varying " + fragPrec + " vec4 v_value;\n"
+																		+ (isFragmentCase ? funcDefs : "") +
+																		"void main (void)\n"
+																		"{\n"
+																		"	" + fragPrec + " vec4 value = v_value;\n"
+																		+ (isFragmentCase ? mainStatements : "") +
+																		"	gl_FragColor = value;\n"
+																		"}\n"),
+					  gls::AttribSpec("a_value",
+									  Vec4(1.0f, 0.0f, 0.0f, 0.0f),
+									  Vec4(0.0f, 1.0f, 0.0f, 0.0f),
+									  Vec4(0.0f, 0.0f, 1.0f, 0.0f),
+									  Vec4(0.0f, 0.0f, 0.0f, 1.0f)));
+}
+
+static inline ProgramData defaultProgramData (CaseShaderType shaderType, const string& mainStatements)
+{
+	return defaultProgramData(shaderType, "", mainStatements);
+}
+
+class ShaderOptimizationCase : public TestCase
+{
+public:
+	ShaderOptimizationCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType)
+		: TestCase				(context, tcu::NODETYPE_PERFORMANCE, name, description)
+		, m_caseShaderType		(caseShaderType)
+		, m_state				(STATE_LAST)
+		, m_measurer			(context.getRenderContext(), caseShaderType == CASESHADERTYPE_VERTEX	? gls::CASETYPE_VERTEX
+														   : caseShaderType == CASESHADERTYPE_FRAGMENT	? gls::CASETYPE_FRAGMENT
+														   : gls::CASETYPE_LAST)
+		, m_unoptimizedResult	(-1.0f, -1.0f)
+		, m_optimizedResult		(-1.0f, -1.0f)
+	{
+	}
+
+	virtual ~ShaderOptimizationCase (void) {}
+
+	void			init		(void);
+	IterateResult	iterate		(void);
+
+protected:
+	virtual ProgramData		generateProgramData (bool optimized) const = 0;
+
+	const CaseShaderType	m_caseShaderType;
+
+private:
+	enum State
+	{
+		STATE_INIT_UNOPTIMIZED = 0,
+		STATE_MEASURE_UNOPTIMIZED,
+		STATE_INIT_OPTIMIZED,
+		STATE_MEASURE_OPTIMIZED,
+		STATE_FINISHED,
+
+		STATE_LAST
+	};
+
+	ProgramData&						programData		(bool optimized) { return optimized ? m_optimizedData		: m_unoptimizedData;		}
+	SharedPtr<const ShaderProgram>&		program			(bool optimized) { return optimized ? m_optimizedProgram	: m_unoptimizedProgram;		}
+	ShaderPerformanceMeasurer::Result&	result			(bool optimized) { return optimized ? m_optimizedResult		: m_unoptimizedResult;		}
+
+	State								m_state;
+	ShaderPerformanceMeasurer			m_measurer;
+
+	ProgramData							m_unoptimizedData;
+	ProgramData							m_optimizedData;
+	SharedPtr<const ShaderProgram>		m_unoptimizedProgram;
+	SharedPtr<const ShaderProgram>		m_optimizedProgram;
+	ShaderPerformanceMeasurer::Result	m_unoptimizedResult;
+	ShaderPerformanceMeasurer::Result	m_optimizedResult;
+};
+
+void ShaderOptimizationCase::init (void)
+{
+	const glu::RenderContext&	renderCtx	= m_context.getRenderContext();
+	TestLog&					log			= m_testCtx.getLog();
+
+	m_measurer.logParameters(log);
+
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		const bool optimized = ndx == 1;
+
+		programData(optimized) = generateProgramData(optimized);
+
+		for (int i = 0; i < (int)programData(optimized).attributes.size(); i++)
+			DE_ASSERT(programData(optimized).attributes[i].name != "a_position"); // \note Position attribute is set by m_measurer.
+
+		program(optimized) = SharedPtr<const ShaderProgram>(new ShaderProgram(renderCtx, programData(optimized).sources));
+
+		{
+			const tcu::ScopedLogSection section(log, optimized ? "OptimizedProgram"			: "UnoptimizedProgram",
+													 optimized ? "Hand-optimized program"	: "Unoptimized program");
+			log << *program(optimized);
+		}
+
+		if (!program(optimized)->isOk())
+			TCU_FAIL("Shader compilation failed");
+	}
+
+	m_state = STATE_INIT_UNOPTIMIZED;
+}
+
+ShaderOptimizationCase::IterateResult ShaderOptimizationCase::iterate (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (m_state == STATE_INIT_UNOPTIMIZED || m_state == STATE_INIT_OPTIMIZED)
+	{
+		const bool optimized = m_state == STATE_INIT_OPTIMIZED;
+		m_measurer.init(program(optimized)->getProgram(), programData(optimized).attributes, 1);
+		m_state = optimized ? STATE_MEASURE_OPTIMIZED : STATE_MEASURE_UNOPTIMIZED;
+
+		return CONTINUE;
+	}
+	else if (m_state == STATE_MEASURE_UNOPTIMIZED || m_state == STATE_MEASURE_OPTIMIZED)
+	{
+		m_measurer.iterate();
+
+		if (m_measurer.isFinished())
+		{
+			const bool						optimized	= m_state == STATE_MEASURE_OPTIMIZED;
+			const tcu::ScopedLogSection		section		(log, optimized ? "OptimizedResult"									: "UnoptimizedResult",
+															  optimized ? "Measurement results for hand-optimized program"	: "Measurement result for unoptimized program");
+			m_measurer.logMeasurementInfo(log);
+			result(optimized) = m_measurer.getResult();
+			m_measurer.deinit();
+			m_state = optimized ? STATE_FINISHED : STATE_INIT_OPTIMIZED;
+		}
+
+		return CONTINUE;
+	}
+	else
+	{
+		DE_ASSERT(m_state == STATE_FINISHED);
+
+		const float			unoptimizedRelevantResult	= m_caseShaderType == CASESHADERTYPE_VERTEX ? m_unoptimizedResult.megaVertPerSec	: m_unoptimizedResult.megaFragPerSec;
+		const float			optimizedRelevantResult		= m_caseShaderType == CASESHADERTYPE_VERTEX ? m_optimizedResult.megaVertPerSec		: m_optimizedResult.megaFragPerSec;
+		const char* const	relevantResultName			= m_caseShaderType == CASESHADERTYPE_VERTEX ? "vertex"								: "fragment";
+		const float			ratio						= unoptimizedRelevantResult / optimizedRelevantResult;
+		const int			handOptimizationGain		= (int)deFloatRound(100.0f/ratio) - 100;
+
+		log << TestLog::Message << "Unoptimized / optimized " << relevantResultName << " performance ratio: " << ratio << TestLog::EndMessage;
+
+		if (handOptimizationGain >= 0)
+			log << TestLog::Message << "Note: " << handOptimizationGain << "% performance gain was achieved with hand-optimized version" << TestLog::EndMessage;
+		else
+			log << TestLog::Message << "Note: hand-optimization degraded performance by " << -handOptimizationGain << "%" << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(ratio, 2).c_str());
+
+		return STOP;
+	}
+}
+
+class LoopUnrollCase : public ShaderOptimizationCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_INDEPENDENT = 0,
+		CASETYPE_DEPENDENT,
+
+		CASETYPE_LAST
+	};
+
+	LoopUnrollCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, CaseType caseType, int numRepetitions)
+		: ShaderOptimizationCase	(context, name, description, caseShaderType)
+		, m_numRepetitions			(numRepetitions)
+		, m_caseType				(caseType)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		const string repetition = optimized ? repeatIndexedTemplate("\t" + expressionTemplate(m_caseType) + ";\n", m_numRepetitions)
+											: loop(m_numRepetitions, expressionTemplate(m_caseType));
+
+		return defaultProgramData(m_caseShaderType, "\t" + getShaderPrecision(m_caseShaderType) + " vec4 valueOrig = value;\n" + repetition);
+	}
+
+private:
+	const int		m_numRepetitions;
+	const CaseType	m_caseType;
+
+	static inline string expressionTemplate (CaseType caseType)
+	{
+		switch (caseType)
+		{
+			case CASETYPE_INDEPENDENT:	return "value += sin(float(${NDX}+1)*valueOrig)";
+			case CASETYPE_DEPENDENT:	return "value = sin(value)";
+			default:
+				DE_ASSERT(false);
+				return DE_NULL;
+		}
+	}
+
+	static inline string loop (int iterations, const string& innerExpr)
+	{
+		return "\tfor (int i = 0; i < " + toString(iterations) + "; i++)\n\t\t" + tcu::StringTemplate(innerExpr).specialize(singleMap("NDX", "i")) + ";\n";
+	}
+};
+
+class LoopInvariantCodeMotionCase : public ShaderOptimizationCase
+{
+public:
+	LoopInvariantCodeMotionCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, int numLoopIterations)
+		: ShaderOptimizationCase	(context, name, description, caseShaderType)
+		, m_numLoopIterations		(numLoopIterations)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		float scale = 0.0f;
+		for (int i = 0; i < m_numLoopIterations; i++)
+			scale += 3.2f*(float)i + 4.6f;
+		scale = 1.0f / scale;
+
+		const string precision		= getShaderPrecision(m_caseShaderType);
+		const string statements		= optimized ?	"	" + precision + " vec4 valueOrig = value;\n"
+													"	" + precision + " vec4 y = sin(cos(sin(valueOrig)));\n"
+													"	for (int i = 0; i < " + toString(m_numLoopIterations) + "; i++)\n"
+													"	{\n"
+													"		" + precision + " float x = 3.2*float(i) + 4.6;\n"
+													"		value += x*y;\n"
+													"	}\n"
+													"	value *= " + toString(scale) + ";\n"
+
+												:	"	" + precision + " vec4 valueOrig = value;\n"
+													"	for (int i = 0; i < " + toString(m_numLoopIterations) + "; i++)\n"
+													"	{\n"
+													"		" + precision + " float x = 3.2*float(i) + 4.6;\n"
+													"		" + precision + " vec4 y = sin(cos(sin(valueOrig)));\n"
+													"		value += x*y;\n"
+													"	}\n"
+													"	value *= " + toString(scale) + ";\n";
+
+		return defaultProgramData(m_caseShaderType, statements);
+	}
+
+private:
+	const int m_numLoopIterations;
+};
+
+class FunctionInliningCase : public ShaderOptimizationCase
+{
+public:
+	FunctionInliningCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, int callNestingDepth)
+		: ShaderOptimizationCase	(context, name, description, caseShaderType)
+		, m_callNestingDepth		(callNestingDepth)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		const string precision				= getShaderPrecision(m_caseShaderType);
+		const string expression				= "value*vec4(0.8, 0.7, 0.6, 0.9)";
+		const string maybeFuncDefs			= optimized ? "" : funcDefinitions(m_callNestingDepth, precision, expression);
+		const string mainValueStatement		= (optimized ? "\tvalue = " + expression : "\tvalue = func" + toString(m_callNestingDepth-1) + "(value)") + ";\n";
+
+		return defaultProgramData(m_caseShaderType, maybeFuncDefs, mainValueStatement);
+	}
+
+private:
+	const int m_callNestingDepth;
+
+	static inline string funcDefinitions (int callNestingDepth, const string& precision, const string& expression)
+	{
+		string result = precision + " vec4 func0 (" + precision + " vec4 value) { return " + expression + "; }\n";
+
+		for (int i = 1; i < callNestingDepth; i++)
+			result += precision + " vec4 func" + toString(i) + " (" + precision + " vec4 v) { return func" + toString(i-1) + "(v); }\n";
+
+		return result;
+	}
+};
+
+class ConstantPropagationCase : public ShaderOptimizationCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_BUILT_IN_FUNCTIONS = 0,
+		CASETYPE_ARRAY,
+		CASETYPE_STRUCT,
+
+		CASETYPE_LAST
+	};
+
+	ConstantPropagationCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, CaseType caseType, bool useConstantExpressionsOnly)
+		: ShaderOptimizationCase			(context, name, description, caseShaderType)
+		, m_caseType						(caseType)
+		, m_useConstantExpressionsOnly		(useConstantExpressionsOnly)
+	{
+		DE_ASSERT(!(m_caseType == CASETYPE_ARRAY && m_useConstantExpressionsOnly)); // \note Would need array constructors, which GLSL ES 1 doesn't have.
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		const bool		isVertexCase	= m_caseShaderType == CASESHADERTYPE_VERTEX;
+		const string	precision		= getShaderPrecision(m_caseShaderType);
+		const string	statements		= m_caseType == CASETYPE_BUILT_IN_FUNCTIONS		? builtinFunctionsCaseStatements	(optimized, m_useConstantExpressionsOnly, precision, isVertexCase)
+										: m_caseType == CASETYPE_ARRAY					? arrayCaseStatements				(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_STRUCT					? structCaseStatements				(optimized, m_useConstantExpressionsOnly, precision, isVertexCase)
+										: DE_NULL;
+
+		return defaultProgramData(m_caseShaderType, statements);
+	}
+
+private:
+	const CaseType	m_caseType;
+	const bool		m_useConstantExpressionsOnly;
+
+	static inline string builtinFunctionsCaseStatements (bool optimized, bool constantExpressionsOnly, const string& precision, bool useHeavierWorkload)
+	{
+		const string	constMaybe = constantExpressionsOnly ? "const " : "";
+		const int		numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	value = vec4(0.4, 0.5, 0.6, 0.7) * value; // NOTE: factor doesn't necessarily match the one in unoptimized shader, but shouldn't make a difference performance-wise\n"
+
+						 :	"	" + constMaybe + precision + " vec4 a = vec4(sin(0.7), cos(0.2), sin(0.9), abs(-0.5));\n"
+							"	" + constMaybe + precision + " vec4 b = cos(a) + fract(3.0*a.xzzw);\n"
+							"	" + constMaybe + "bvec4 c = bvec4(true, false, true, true);\n"
+							"	" + constMaybe + precision + " vec4 d = exp(b + vec4(c));\n"
+							"	" + constMaybe + precision + " vec4 e0 = inversesqrt(mix(d+a, d+b, a));\n"
+							+ repeatIndexedTemplate("	" + constMaybe + precision + " vec4 e${NDX} = sin(sin(sin(sin(e${PREV_NDX}))));\n", numSinRows, "", 1) +
+							"	" + constMaybe + precision + " vec4 f = abs(e" + toString(numSinRows) + ");\n" +
+							"	value = f*value;\n";
+	}
+
+	static inline string arrayCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	value = vec4(0.4, 0.5, 0.6, 0.7) * value; // NOTE: factor doesn't necessarily match the one in unoptimized shader, but shouldn't make a difference performance-wise\n"
+
+						 :	"	const int arrLen = 4;\n"
+							"	" + precision + " vec4 arr[arrLen];\n"
+							"	arr[0] = vec4(0.1, 0.5, 0.9, 1.3);\n"
+							"	arr[1] = vec4(0.2, 0.6, 1.0, 1.4);\n"
+							"	arr[2] = vec4(0.3, 0.7, 1.1, 1.5);\n"
+							"	arr[3] = vec4(0.4, 0.8, 1.2, 1.6);\n"
+							"	" + precision + " vec4 a = (arr[0] + arr[1] + arr[2] + arr[3]) * 0.25;\n"
+							"	" + precision + " vec4 b0 = cos(sin(a));\n"
+							+ repeatIndexedTemplate("	" + precision + " vec4 b${NDX} = sin(sin(sin(sin(b${PREV_NDX}))));\n", numSinRows, "", 1) +
+							"	" + precision + " vec4 c = abs(b" + toString(numSinRows) + ");\n" +
+							"	value = c*value;\n";
+	}
+
+	static inline string structCaseStatements (bool optimized, bool constantExpressionsOnly, const string& precision, bool useHeavierWorkload)
+	{
+		const string	constMaybe = constantExpressionsOnly ? "const " : "";
+		const int		numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	value = vec4(0.4, 0.5, 0.6, 0.7) * value; // NOTE: factor doesn't necessarily match the one in unoptimized shader, but shouldn't make a difference performance-wise\n"
+
+						 :	"	struct S\n"
+							"	{\n"
+							"		" + precision + " vec4 a;\n"
+							"		" + precision + " vec4 b;\n"
+							"		" + precision + " vec4 c;\n"
+							"		" + precision + " vec4 d;\n"
+							"	};\n"
+							"\n"
+							"	" + constMaybe + "S s =\n"
+							"		S(vec4(0.1, 0.5, 0.9, 1.3),\n"
+							"		  vec4(0.2, 0.6, 1.0, 1.4),\n"
+							"		  vec4(0.3, 0.7, 1.1, 1.5),\n"
+							"		  vec4(0.4, 0.8, 1.2, 1.6));\n"
+							"	" + constMaybe + precision + " vec4 a = (s.a + s.b + s.c + s.d) * 0.25;\n"
+							"	" + constMaybe + precision + " vec4 b0 = cos(sin(a));\n"
+							+ repeatIndexedTemplate("	" + constMaybe + precision + " vec4 b${NDX} = sin(sin(sin(sin(b${PREV_NDX}))));\n", numSinRows, "", 1) +
+							"	" + constMaybe + precision + " vec4 c = abs(b" + toString(numSinRows) + ");\n" +
+							"	value = c*value;\n";
+	}
+};
+
+class CommonSubexpressionCase : public ShaderOptimizationCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_SINGLE_STATEMENT = 0,
+		CASETYPE_MULTIPLE_STATEMENTS,
+		CASETYPE_STATIC_BRANCH,
+		CASETYPE_LOOP,
+
+		CASETYPE_LAST
+	};
+
+	CommonSubexpressionCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, CaseType caseType)
+		: ShaderOptimizationCase	(context, name, description, caseShaderType)
+		, m_caseType				(caseType)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		const bool		isVertexCase	= m_caseShaderType == CASESHADERTYPE_VERTEX;
+		const string	precision		= getShaderPrecision(m_caseShaderType);
+		const string	statements		= m_caseType == CASETYPE_SINGLE_STATEMENT		? singleStatementCaseStatements		(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_MULTIPLE_STATEMENTS	? multipleStatementsCaseStatements	(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_STATIC_BRANCH			? staticBranchCaseStatements		(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_LOOP					? loopCaseStatements				(optimized, precision, isVertexCase)
+										: DE_NULL;
+
+		return defaultProgramData(m_caseShaderType, statements);
+	}
+
+private:
+	const CaseType m_caseType;
+
+	static inline string singleStatementCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numTopLevelRepeats = useHeavierWorkload ? 4 : 1;
+
+		return optimized ?	"	" + precision + " vec4 s = sin(value);\n"
+							"	" + precision + " vec4 cs = cos(s);\n"
+							"	" + precision + " vec4 d = fract(s + cs) + sqrt(s + exp(cs));\n"
+							"	value = " + repeat("d", numTopLevelRepeats, "+") + ";\n"
+
+						 :	"	value = " + repeat("fract(sin(value) + cos(sin(value))) + sqrt(sin(value) + exp(cos(sin(value))))", numTopLevelRepeats, "\n\t      + ") + ";\n";
+	}
+
+	static inline string multipleStatementsCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numTopLevelRepeats = useHeavierWorkload ? 4 : 2;
+		DE_ASSERT(numTopLevelRepeats >= 2);
+
+		return optimized ?	"	" + precision + " vec4 a = sin(value) + cos(exp(value));\n"
+							"	" + precision + " vec4 b = cos(cos(a));\n"
+							"	a = fract(exp(sqrt(b)));\n"
+							"\n"
+							+ repeat("\tvalue += a*b;\n", numTopLevelRepeats)
+
+						 :	repeatIndexedTemplate(	"	" + precision + " vec4 a${NDX} = sin(value) + cos(exp(value));\n"
+													"	" + precision + " vec4 b${NDX} = cos(cos(a${NDX}));\n"
+													"	a${NDX} = fract(exp(sqrt(b${NDX})));\n"
+													"\n",
+													numTopLevelRepeats) +
+
+							repeatIndexedTemplate(	"	value += a${NDX}*b${NDX};\n", numTopLevelRepeats);
+	}
+
+	static inline string staticBranchCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numTopLevelRepeats = useHeavierWorkload ? 4 : 2;
+		DE_ASSERT(numTopLevelRepeats >= 2);
+
+		if (optimized)
+		{
+			return "	" + precision + " vec4 a = sin(value) + cos(exp(value));\n"
+				   "	" + precision + " vec4 b = cos(a);\n"
+				   "	b = cos(b);\n"
+				   "	a = fract(exp(sqrt(b)));\n"
+				   "\n"
+				   + repeat("	value += a*b;\n", numTopLevelRepeats);
+		}
+		else
+		{
+			string result;
+
+			for (int i = 0; i < numTopLevelRepeats; i++)
+			{
+				result +=	"	" + precision + " vec4 a" + toString(i) + " = sin(value) + cos(exp(value));\n"
+							"	" + precision + " vec4 b" + toString(i) + " = cos(a" + toString(i) + ");\n";
+
+				if (i % 3 == 0)
+					result +=	"	if (1 < 2)\n"
+								"		b" + toString(i) + " = cos(b" + toString(i) + ");\n";
+				else if (i % 3 == 1)
+					result +=	"	b" + toString(i) + " = cos(b" + toString(i) + ");\n";
+				else if (i % 3 == 2)
+					result +=	"	if (2 < 1);\n"
+								"	else\n"
+								"		b" + toString(i) + " = cos(b" + toString(i) + ");\n";
+				else
+					DE_ASSERT(false);
+
+				result +=	"	a" + toString(i) + " = fract(exp(sqrt(b" + toString(i) + ")));\n\n";
+			}
+
+			result += repeatIndexedTemplate("	value += a${NDX}*b${NDX};\n", numTopLevelRepeats);
+
+			return result;
+		}
+	}
+
+	static inline string loopCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numLoopIterations = useHeavierWorkload ? 32 : 4;
+
+		return optimized ?	"	" + precision + " vec4 acc = value;\n"
+							"	for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"		acc = sin(acc);\n"
+							"\n"
+							"	value += acc;\n"
+							"	value += acc;\n"
+
+						 :	"	" + precision + " vec4 acc0 = value;\n"
+							"	for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"		acc0 = sin(acc0);\n"
+							"\n"
+							"	" + precision + " vec4 acc1 = value;\n"
+							"	for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"		acc1 = sin(acc1);\n"
+							"\n"
+							"	value += acc0;\n"
+							"	value += acc1;\n";
+	}
+};
+
+class DeadCodeEliminationCase : public ShaderOptimizationCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_DEAD_BRANCH_SIMPLE = 0,
+		CASETYPE_DEAD_BRANCH_COMPLEX,
+		CASETYPE_DEAD_BRANCH_COMPLEX_NO_CONST,
+		CASETYPE_DEAD_BRANCH_FUNC_CALL,
+		CASETYPE_UNUSED_VALUE_BASIC,
+		CASETYPE_UNUSED_VALUE_LOOP,
+		CASETYPE_UNUSED_VALUE_DEAD_BRANCH,
+		CASETYPE_UNUSED_VALUE_AFTER_RETURN,
+		CASETYPE_UNUSED_VALUE_MUL_ZERO,
+
+		CASETYPE_LAST
+	};
+
+	DeadCodeEliminationCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, CaseType caseType)
+		: ShaderOptimizationCase	(context, name, description, caseShaderType)
+		, m_caseType				(caseType)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		const bool		isVertexCase	= m_caseShaderType == CASESHADERTYPE_VERTEX;
+		const string	precision		= getShaderPrecision(m_caseShaderType);
+		const string	funcDefs		= m_caseType == CASETYPE_DEAD_BRANCH_FUNC_CALL		? deadBranchFuncCallCaseFuncDefs		(optimized, precision)
+										: m_caseType == CASETYPE_UNUSED_VALUE_AFTER_RETURN	? unusedValueAfterReturnCaseFuncDefs	(optimized, precision, isVertexCase)
+										: "";
+
+		const string	statements		= m_caseType == CASETYPE_DEAD_BRANCH_SIMPLE				? deadBranchSimpleCaseStatements			(optimized, isVertexCase)
+										: m_caseType == CASETYPE_DEAD_BRANCH_COMPLEX			? deadBranchComplexCaseStatements			(optimized, precision, true,	isVertexCase)
+										: m_caseType == CASETYPE_DEAD_BRANCH_COMPLEX_NO_CONST	? deadBranchComplexCaseStatements			(optimized, precision, false,	isVertexCase)
+										: m_caseType == CASETYPE_DEAD_BRANCH_FUNC_CALL			? deadBranchFuncCallCaseStatements			(optimized, isVertexCase)
+										: m_caseType == CASETYPE_UNUSED_VALUE_BASIC				? unusedValueBasicCaseStatements			(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_UNUSED_VALUE_LOOP				? unusedValueLoopCaseStatements				(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_UNUSED_VALUE_DEAD_BRANCH		? unusedValueDeadBranchCaseStatements		(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_UNUSED_VALUE_AFTER_RETURN		? unusedValueAfterReturnCaseStatements		()
+										: m_caseType == CASETYPE_UNUSED_VALUE_MUL_ZERO			? unusedValueMulZeroCaseStatements			(optimized, precision, isVertexCase)
+										: DE_NULL;
+
+		return defaultProgramData(m_caseShaderType, funcDefs, statements);
+	}
+
+private:
+	const CaseType m_caseType;
+
+	static inline string deadBranchSimpleCaseStatements (bool optimized, bool useHeavierWorkload)
+	{
+		const int numLoopIterations = useHeavierWorkload ? 16 : 4;
+
+		return optimized ?	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+
+						 :	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	if (2 < 1)\n"
+							"	{\n"
+							"		value = cos(exp(sin(value))*log(sqrt(value)));\n"
+							"		for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"			value = sin(value);\n"
+							"	}\n";
+	}
+
+	static inline string deadBranchComplexCaseStatements (bool optimized, const string& precision, bool useConst, bool useHeavierWorkload)
+	{
+		const string	constMaybe			= useConst ? "const " : "";
+		const int		numLoopIterations	= useHeavierWorkload ? 16 : 4;
+
+		return optimized ?	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+
+						 :	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	" + constMaybe + precision + " vec4 a = vec4(sin(0.7), cos(0.2), sin(0.9), abs(-0.5));\n"
+							"	" + constMaybe + precision + " vec4 b = cos(a) + fract(3.0*a.xzzw);\n"
+							"	" + constMaybe + "bvec4 c = bvec4(true, false, true, true);\n"
+							"	" + constMaybe + precision + " vec4 d = exp(b + vec4(c));\n"
+							"	" + constMaybe + precision + " vec4 e = 1.8*abs(sin(sin(inversesqrt(mix(d+a, d+b, a)))));\n"
+							"	if (e.x > 1.0)\n"
+							"	{\n"
+							"		value = cos(exp(sin(value))*log(sqrt(value)));\n"
+							"		for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"			value = sin(value);\n"
+							"	}\n";
+	}
+
+	static inline string deadBranchFuncCallCaseFuncDefs (bool optimized, const string& precision)
+	{
+		return optimized ? "" : precision + " float func (" + precision + " float x) { return 2.0*x; }\n";
+	}
+
+	static inline string deadBranchFuncCallCaseStatements (bool optimized, bool useHeavierWorkload)
+	{
+		const int numLoopIterations = useHeavierWorkload ? 16 : 4;
+
+		return optimized ?	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+
+						 :	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	if (func(0.3) > 1.0)\n"
+							"	{\n"
+							"		value = cos(exp(sin(value))*log(sqrt(value)));\n"
+							"		for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"			value = sin(value);\n"
+							"	}\n";
+	}
+
+	static inline string unusedValueBasicCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	value = used;\n"
+
+						 :	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	" + precision + " vec4 unused = cos(exp(sin(value))*log(sqrt(value))) + used;\n"
+							+ repeat("	unused = sin(sin(sin(sin(unused))));\n", numSinRows) +
+							"	value = used;\n";
+	}
+
+	static inline string unusedValueLoopCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numLoopIterations = useHeavierWorkload ? 16 : 4;
+
+		return optimized ?	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	value = used;\n"
+
+						 :	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	" + precision + " vec4 unused = cos(exp(sin(value))*log(sqrt(value)));\n"
+							"	for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"		unused = sin(unused + used);\n"
+							"	value = used;\n";
+	}
+
+	static inline string unusedValueAfterReturnCaseFuncDefs (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	precision + " vec4 func (" + precision + " vec4 v)\n"
+							"{\n"
+							"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * v;\n"
+							"	return used;\n"
+							"}\n"
+
+						 :	precision + " vec4 func (" + precision + " vec4 v)\n"
+							"{\n"
+							"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * v;\n"
+							"	" + precision + " vec4 unused = cos(exp(sin(v))*log(sqrt(v)));\n"
+							+ repeat("	unused = sin(sin(sin(sin(unused))));\n", numSinRows) +
+							"	return used;\n"
+							"	used = used*unused;"
+							"	return used;\n"
+							"}\n";
+	}
+
+	static inline string unusedValueAfterReturnCaseStatements (void)
+	{
+		return "	value = func(value);\n";
+	}
+
+	static inline string unusedValueDeadBranchCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	value = used;\n"
+
+						 :	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	" + precision + " vec4 unused = cos(exp(sin(value))*log(sqrt(value)));\n"
+							+ repeat("	unused = sin(sin(sin(sin(unused))));\n", numSinRows) +
+							"	if (2 < 1)\n"
+							"		used = used*unused;\n"
+							"	value = used;\n";
+	}
+
+	static inline string unusedValueMulZeroCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	value = used;\n"
+
+						 :	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	" + precision + " vec4 unused = cos(exp(sin(value))*log(sqrt(value)));\n"
+							+ repeat("	unused = sin(sin(sin(sin(unused))));\n", numSinRows) +
+							"	value = used + unused*float(1-1);\n";
+	}
+};
+
+} // anonymous
+
+ShaderOptimizationTests::ShaderOptimizationTests (Context& context)
+	: TestCaseGroup(context, "optimization", "Shader Optimization Performance Tests")
+{
+}
+
+ShaderOptimizationTests::~ShaderOptimizationTests (void)
+{
+}
+
+void ShaderOptimizationTests::init (void)
+{
+	TestCaseGroup* const unrollGroup					= new TestCaseGroup(m_context, "loop_unrolling",					"Loop Unrolling Cases");
+	TestCaseGroup* const loopInvariantCodeMotionGroup	= new TestCaseGroup(m_context, "loop_invariant_code_motion",		"Loop-Invariant Code Motion Cases");
+	TestCaseGroup* const inlineGroup					= new TestCaseGroup(m_context, "function_inlining",					"Function Inlining Cases");
+	TestCaseGroup* const constantPropagationGroup		= new TestCaseGroup(m_context, "constant_propagation",				"Constant Propagation Cases");
+	TestCaseGroup* const commonSubexpressionGroup		= new TestCaseGroup(m_context, "common_subexpression_elimination",	"Common Subexpression Elimination Cases");
+	TestCaseGroup* const deadCodeEliminationGroup		= new TestCaseGroup(m_context, "dead_code_elimination",				"Dead Code Elimination Cases");
+	addChild(unrollGroup);
+	addChild(loopInvariantCodeMotionGroup);
+	addChild(inlineGroup);
+	addChild(constantPropagationGroup);
+	addChild(commonSubexpressionGroup);
+	addChild(deadCodeEliminationGroup);
+
+	for (int caseShaderTypeI = 0; caseShaderTypeI < CASESHADERTYPE_LAST; caseShaderTypeI++)
+	{
+		const CaseShaderType	caseShaderType			= (CaseShaderType)caseShaderTypeI;
+		const char* const		caseShaderTypeSuffix	= caseShaderType == CASESHADERTYPE_VERTEX		? "_vertex"
+														: caseShaderType == CASESHADERTYPE_FRAGMENT		? "_fragment"
+														: DE_NULL;
+
+		// Loop unrolling cases.
+
+		{
+			static const int loopIterationCounts[] = { 4, 8, 32 };
+
+			for (int caseTypeI = 0; caseTypeI < LoopUnrollCase::CASETYPE_LAST; caseTypeI++)
+			{
+				const LoopUnrollCase::CaseType	caseType		= (LoopUnrollCase::CaseType)caseTypeI;
+				const string					caseTypeName	= caseType == LoopUnrollCase::CASETYPE_INDEPENDENT	? "independent_iterations"
+																: caseType == LoopUnrollCase::CASETYPE_DEPENDENT	? "dependent_iterations"
+																: DE_NULL;
+				const string					caseTypeDesc	= caseType == LoopUnrollCase::CASETYPE_INDEPENDENT	? "loop iterations don't depend on each other"
+																: caseType == LoopUnrollCase::CASETYPE_DEPENDENT	? "loop iterations depend on each other"
+																: DE_NULL;
+
+				for (int loopIterNdx = 0; loopIterNdx < DE_LENGTH_OF_ARRAY(loopIterationCounts); loopIterNdx++)
+				{
+					const int			loopIterations	= loopIterationCounts[loopIterNdx];
+					const string		name			= caseTypeName + "_" + toString(loopIterations) + caseShaderTypeSuffix;
+					const string		description		= toString(loopIterations) + " iterations; " + caseTypeDesc;
+
+					unrollGroup->addChild(new LoopUnrollCase(m_context, name.c_str(), description.c_str(), caseShaderType, caseType, loopIterations));
+				}
+			}
+		}
+
+		// Loop-invariant code motion cases.
+
+		{
+			static const int loopIterationCounts[] = { 4, 8, 32 };
+
+			for (int loopIterNdx = 0; loopIterNdx < DE_LENGTH_OF_ARRAY(loopIterationCounts); loopIterNdx++)
+			{
+				const int		loopIterations	= loopIterationCounts[loopIterNdx];
+				const string	name			= toString(loopIterations) + "_iterations" + caseShaderTypeSuffix;
+
+				loopInvariantCodeMotionGroup->addChild(new LoopInvariantCodeMotionCase(m_context, name.c_str(), "", caseShaderType, loopIterations));
+			}
+		}
+
+		// Function inlining cases.
+
+		{
+			static const int callNestingDepths[] = { 4, 8, 32 };
+
+			for (int nestDepthNdx = 0; nestDepthNdx < DE_LENGTH_OF_ARRAY(callNestingDepths); nestDepthNdx++)
+			{
+				const int		nestingDepth	= callNestingDepths[nestDepthNdx];
+				const string	name			= toString(nestingDepth) + "_nested" + caseShaderTypeSuffix;
+
+				inlineGroup->addChild(new FunctionInliningCase(m_context, name.c_str(), "", caseShaderType, nestingDepth));
+			}
+		}
+
+		// Constant propagation cases.
+
+		for (int caseTypeI = 0; caseTypeI < ConstantPropagationCase::CASETYPE_LAST; caseTypeI++)
+		{
+			const ConstantPropagationCase::CaseType		caseType		= (ConstantPropagationCase::CaseType)caseTypeI;
+			const string								caseTypeName	= caseType == ConstantPropagationCase::CASETYPE_BUILT_IN_FUNCTIONS		? "built_in_functions"
+																		: caseType == ConstantPropagationCase::CASETYPE_ARRAY					? "array"
+																		: caseType == ConstantPropagationCase::CASETYPE_STRUCT					? "struct"
+																		: DE_NULL;
+
+			for (int constantExpressionsOnlyI = 0; constantExpressionsOnlyI <= 1; constantExpressionsOnlyI++)
+			{
+				const bool		constantExpressionsOnly		= constantExpressionsOnlyI != 0;
+				const string	name						= caseTypeName + (constantExpressionsOnly ? "" : "_no_const") + caseShaderTypeSuffix;
+
+				if (caseType == ConstantPropagationCase::CASETYPE_ARRAY && constantExpressionsOnly) // \note See ConstantPropagationCase's constructor for explanation.
+					continue;
+
+				constantPropagationGroup->addChild(new ConstantPropagationCase(m_context, name.c_str(), "", caseShaderType, caseType, constantExpressionsOnly));
+			}
+		}
+
+		// Common subexpression cases.
+
+		for (int caseTypeI = 0; caseTypeI < CommonSubexpressionCase::CASETYPE_LAST; caseTypeI++)
+		{
+			const CommonSubexpressionCase::CaseType		caseType		= (CommonSubexpressionCase::CaseType)caseTypeI;
+
+			const string								caseTypeName	= caseType == CommonSubexpressionCase::CASETYPE_SINGLE_STATEMENT		? "single_statement"
+																		: caseType == CommonSubexpressionCase::CASETYPE_MULTIPLE_STATEMENTS		? "multiple_statements"
+																		: caseType == CommonSubexpressionCase::CASETYPE_STATIC_BRANCH			? "static_branch"
+																		: caseType == CommonSubexpressionCase::CASETYPE_LOOP					? "loop"
+																		: DE_NULL;
+
+			const string								description		= caseType == CommonSubexpressionCase::CASETYPE_SINGLE_STATEMENT		? "A single statement containing multiple uses of same subexpression"
+																		: caseType == CommonSubexpressionCase::CASETYPE_MULTIPLE_STATEMENTS		? "Multiple statements performing same computations"
+																		: caseType == CommonSubexpressionCase::CASETYPE_STATIC_BRANCH			? "Multiple statements including a static conditional"
+																		: caseType == CommonSubexpressionCase::CASETYPE_LOOP					? "Multiple loops performing the same computations"
+																		: DE_NULL;
+
+			commonSubexpressionGroup->addChild(new CommonSubexpressionCase(m_context, (caseTypeName + caseShaderTypeSuffix).c_str(), description.c_str(), caseShaderType, caseType));
+		}
+
+		// Dead code elimination cases.
+
+		for (int caseTypeI = 0; caseTypeI < DeadCodeEliminationCase::CASETYPE_LAST; caseTypeI++)
+		{
+			const DeadCodeEliminationCase::CaseType		caseType				= (DeadCodeEliminationCase::CaseType)caseTypeI;
+			const char* const							caseTypeName			= caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_SIMPLE				? "dead_branch_simple"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_COMPLEX				? "dead_branch_complex"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_COMPLEX_NO_CONST	? "dead_branch_complex_no_const"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_FUNC_CALL			? "dead_branch_func_call"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_BASIC				? "unused_value_basic"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_LOOP				? "unused_value_loop"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_DEAD_BRANCH		? "unused_value_dead_branch"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_AFTER_RETURN		? "unused_value_after_return"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_MUL_ZERO			? "unused_value_mul_zero"
+																				: DE_NULL;
+
+			const char* const							caseTypeDescription		= caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_SIMPLE				? "Do computation inside a branch that is never taken (condition is simple false constant expression)"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_COMPLEX				? "Do computation inside a branch that is never taken (condition is complex false constant expression)"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_COMPLEX_NO_CONST	? "Do computation inside a branch that is never taken (condition is complex false expression, not constant expression but still compile-time computable)"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_FUNC_CALL			? "Do computation inside a branch that is never taken (condition is compile-time computable false expression containing function call to a simple inlineable function)"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_BASIC				? "Compute a value that is never used even statically"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_LOOP				? "Compute a value, using a loop, that is never used even statically"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_DEAD_BRANCH		? "Compute a value that is used only inside a statically dead branch"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_AFTER_RETURN		? "Compute a value that is used only after a return statement"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_MUL_ZERO			? "Compute a value that is used but multiplied by a zero constant expression"
+																				: DE_NULL;
+
+			deadCodeEliminationGroup->addChild(new DeadCodeEliminationCase(m_context, (string() + caseTypeName + caseShaderTypeSuffix).c_str(), caseTypeDescription, caseShaderType, caseType));
+		}
+	}
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pShaderOptimizationTests.hpp b/modules/gles2/performance/es2pShaderOptimizationTests.hpp
new file mode 100644
index 0000000..f530485
--- /dev/null
+++ b/modules/gles2/performance/es2pShaderOptimizationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PSHADEROPTIMIZATIONTESTS_HPP
+#define _ES2PSHADEROPTIMIZATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Optimized vs unoptimized shader performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class ShaderOptimizationTests : public TestCaseGroup
+{
+public:
+								ShaderOptimizationTests		(Context& context);
+								~ShaderOptimizationTests	(void);
+
+	void						init						(void);
+
+private:
+								ShaderOptimizationTests		(const ShaderOptimizationTests& other);
+	ShaderOptimizationTests&	operator=					(const ShaderOptimizationTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PSHADEROPTIMIZATIONTESTS_HPP
diff --git a/modules/gles2/performance/es2pStateChangeCallTests.cpp b/modules/gles2/performance/es2pStateChangeCallTests.cpp
new file mode 100644
index 0000000..b164bbd
--- /dev/null
+++ b/modules/gles2/performance/es2pStateChangeCallTests.cpp
@@ -0,0 +1,915 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State change call performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pStateChangeCallTests.hpp"
+#include "glsStateChangePerfTestCases.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+using namespace glw;
+
+StateChangeCallTests::StateChangeCallTests (Context& context)
+	: TestCaseGroup(context, "state_change_only", "Test cost of state change calls without rendering anything")
+{
+}
+
+StateChangeCallTests::~StateChangeCallTests (void)
+{
+}
+
+#define ARG_LIST(...) __VA_ARGS__
+
+#define ADD_ARG_CASE1(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				gl.FUNCNAME(arg0);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+#define ADD_ARG_CASE2(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0, TYPE1, ARGS1)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			const TYPE1 args1[] = ARGS1;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				const TYPE1		arg1		= args1[baseNdx%DE_LENGTH_OF_ARRAY(args1)];\
+				gl.FUNCNAME(arg0, arg1);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+#define ADD_ARG_CASE3(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0, TYPE1, ARGS1, TYPE2, ARGS2)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			const TYPE1 args1[] = ARGS1;\
+			const TYPE2 args2[] = ARGS2;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				const TYPE1		arg1		= args1[baseNdx%DE_LENGTH_OF_ARRAY(args1)];\
+				const TYPE2		arg2		= args2[baseNdx%DE_LENGTH_OF_ARRAY(args2)];\
+				gl.FUNCNAME(arg0, arg1, arg2);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+#define ADD_ARG_CASE4(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0, TYPE1, ARGS1, TYPE2, ARGS2, TYPE3, ARGS3)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			const TYPE1 args1[] = ARGS1;\
+			const TYPE2 args2[] = ARGS2;\
+			const TYPE3 args3[] = ARGS3;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				const TYPE1		arg1		= args1[baseNdx%DE_LENGTH_OF_ARRAY(args1)];\
+				const TYPE2		arg2		= args2[baseNdx%DE_LENGTH_OF_ARRAY(args2)];\
+				const TYPE3		arg3		= args3[baseNdx%DE_LENGTH_OF_ARRAY(args3)];\
+				gl.FUNCNAME(arg0, arg1, arg2, arg3);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+#define ADD_ARG_CASE6(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0, TYPE1, ARGS1, TYPE2, ARGS2, TYPE3, ARGS3, TYPE4, ARGS4, TYPE5, ARGS5)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			const TYPE1 args1[] = ARGS1;\
+			const TYPE2 args2[] = ARGS2;\
+			const TYPE3 args3[] = ARGS3;\
+			const TYPE4 args4[] = ARGS4;\
+			const TYPE5 args5[] = ARGS5;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				const TYPE1		arg1		= args1[baseNdx%DE_LENGTH_OF_ARRAY(args1)];\
+				const TYPE2		arg2		= args2[baseNdx%DE_LENGTH_OF_ARRAY(args2)];\
+				const TYPE3		arg3		= args3[baseNdx%DE_LENGTH_OF_ARRAY(args3)];\
+				const TYPE4		arg4		= args4[baseNdx%DE_LENGTH_OF_ARRAY(args4)];\
+				const TYPE5		arg5		= args5[baseNdx%DE_LENGTH_OF_ARRAY(args5)];\
+				gl.FUNCNAME(arg0, arg1, arg2, arg3, arg4, arg5);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+void StateChangeCallTests::init (void)
+{
+	ADD_ARG_CASE1(enable, "Test cost of glEnable() calls",
+		enable,
+		GLenum,
+		ARG_LIST({
+			GL_CULL_FACE,
+			GL_POLYGON_OFFSET_FILL,
+			GL_SAMPLE_ALPHA_TO_COVERAGE,
+			GL_SAMPLE_COVERAGE,
+			GL_SCISSOR_TEST,
+			GL_STENCIL_TEST,
+			GL_DEPTH_TEST,
+			GL_BLEND,
+			GL_DITHER
+		})
+	);
+
+	ADD_ARG_CASE1(disable, "Test cost of glDisable() calls",
+		disable,
+		GLenum,
+		ARG_LIST({
+			GL_CULL_FACE,
+			GL_POLYGON_OFFSET_FILL,
+			GL_SAMPLE_ALPHA_TO_COVERAGE,
+			GL_SAMPLE_COVERAGE,
+			GL_SCISSOR_TEST,
+			GL_STENCIL_TEST,
+			GL_DEPTH_TEST,
+			GL_BLEND,
+			GL_DITHER
+		})
+	);
+
+	ADD_ARG_CASE1(depth_func, "Test cost of glDepthFunc() calls",
+		depthFunc,
+		GLenum,
+		ARG_LIST({
+			GL_NEVER,
+			GL_ALWAYS,
+			GL_LESS,
+			GL_LEQUAL,
+			GL_EQUAL,
+			GL_GREATER,
+			GL_GEQUAL,
+			GL_NOTEQUAL
+		})
+	);
+
+	ADD_ARG_CASE1(depth_mask, "Test cost of glDepthMask() calls",
+		depthMask,
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		})
+	);
+
+	ADD_ARG_CASE1(stencil_mask, "Test cost of glStencilMask() calls",
+		stencilMask,
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		})
+	);
+
+	ADD_ARG_CASE1(clear_depth, "Test cost of glClearDepth() calls",
+		clearDepthf,
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			0.5f,
+			1.0f
+		})
+	);
+
+	ADD_ARG_CASE1(clear_stencil, "Test cost of glClearStencil() calls",
+		clearStencil,
+		GLint,
+		ARG_LIST({
+			0,
+			128,
+			28
+		})
+	);
+
+	ADD_ARG_CASE1(line_width, "Test cost of glLineWidth() calls",
+		lineWidth,
+		GLfloat,
+		ARG_LIST({
+			1.0f,
+			0.5f,
+			10.0f
+		})
+	);
+
+	ADD_ARG_CASE1(cull_face, "Test cost of glCullFace() calls",
+		cullFace,
+		GLenum,
+		ARG_LIST({
+			GL_FRONT,
+			GL_BACK,
+			GL_FRONT_AND_BACK
+		})
+	);
+
+	ADD_ARG_CASE1(front_face, "Test cost of glFrontFace() calls",
+		frontFace,
+		GLenum,
+		ARG_LIST({
+			GL_CCW,
+			GL_CW
+		})
+	);
+
+	ADD_ARG_CASE1(blend_equation, "Test cost of glBlendEquation() calls",
+		blendEquation,
+		GLenum,
+		ARG_LIST({
+			GL_FUNC_ADD,
+			GL_FUNC_SUBTRACT,
+			GL_FUNC_REVERSE_SUBTRACT
+		})
+	);
+
+	ADD_ARG_CASE1(enable_vertex_attrib_array, "Test cost of glEnableVertexAttribArray() calls",
+		enableVertexAttribArray,
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			2,
+			3,
+			4,
+			5,
+			6,
+			7,
+		})
+	);
+
+	ADD_ARG_CASE1(disable_vertex_attrib_array, "Test cost of glDisableVertexAttribArray() calls",
+		disableVertexAttribArray,
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			2,
+			3,
+			4,
+			5,
+			6,
+			7,
+		})
+	);
+
+	ADD_ARG_CASE1(use_program, "Test cost of glUseProgram() calls. Note: Uses only program 0.",
+		useProgram,
+		GLuint,
+		ARG_LIST({
+			0,
+		})
+	);
+
+	ADD_ARG_CASE1(active_texture, "Test cost of glActiveTexture() calls",
+		activeTexture,
+		GLenum,
+		ARG_LIST({
+			GL_TEXTURE0,
+			GL_TEXTURE1,
+			GL_TEXTURE2,
+			GL_TEXTURE3,
+			GL_TEXTURE4,
+			GL_TEXTURE5,
+			GL_TEXTURE6,
+			GL_TEXTURE7
+		})
+	);
+
+	ADD_ARG_CASE2(depth_range, "Test cost of glDepthRangef() calls",
+		depthRangef,
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f
+		}),
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f
+		})
+	);
+
+	ADD_ARG_CASE2(polygon_offset, "Test cost of glPolygonOffset() calls",
+		polygonOffset,
+		GLfloat,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			10.0f
+		}),
+		GLfloat,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			1000.0f
+		})
+	);
+
+	ADD_ARG_CASE2(sample_coverage, "Test cost of glSampleCoverage() calls",
+		sampleCoverage,
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			0.67f
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		})
+	);
+
+	ADD_ARG_CASE2(blend_func, "Test cost of glBlendFunc() calls",
+		blendFunc,
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		})
+	);
+
+	ADD_ARG_CASE2(blend_equation_separate, "Test cost of glBlendEquationSeparate() calls",
+		blendEquationSeparate,
+		GLenum,
+		ARG_LIST({
+			GL_FUNC_ADD,
+			GL_FUNC_SUBTRACT,
+			GL_FUNC_REVERSE_SUBTRACT
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_FUNC_ADD,
+			GL_FUNC_SUBTRACT,
+			GL_FUNC_REVERSE_SUBTRACT
+		})
+	);
+
+	ADD_ARG_CASE2(stencil_mask_separate, "Test cost of glStencilMaskSeparate() calls",
+		stencilMaskSeparate,
+		GLenum,
+		ARG_LIST({
+			GL_FRONT,
+			GL_BACK,
+			GL_FRONT_AND_BACK
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		})
+	);
+
+	ADD_ARG_CASE2(bind_buffer, "Test cost of glBindBuffer() calls. Note: Uses only buffer 0",
+		bindBuffer,
+		GLenum,
+		ARG_LIST({
+			GL_ELEMENT_ARRAY_BUFFER,
+			GL_ARRAY_BUFFER
+		}),
+		GLuint,
+		ARG_LIST({
+			0
+		})
+	);
+
+	ADD_ARG_CASE2(bind_texture, "Test cost of glBindTexture() calls. Note: Uses only texture 0",
+		bindTexture,
+		GLenum,
+		ARG_LIST({
+			GL_TEXTURE_2D,
+			GL_TEXTURE_CUBE_MAP
+		}),
+		GLuint,
+		ARG_LIST({
+			0
+		})
+	);
+
+	ADD_ARG_CASE2(hint, "Test cost of glHint() calls",
+		hint,
+		GLenum,
+		ARG_LIST({
+			GL_GENERATE_MIPMAP_HINT
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_FASTEST,
+			GL_NICEST,
+			GL_DONT_CARE
+		})
+	);
+
+	ADD_ARG_CASE3(stencil_func, "Test cost of glStencilFunc() calls",
+		stencilFunc,
+		GLenum,
+		ARG_LIST({
+			GL_NEVER,
+			GL_ALWAYS,
+			GL_LESS,
+			GL_LEQUAL,
+			GL_EQUAL,
+			GL_GEQUAL,
+			GL_GREATER,
+			GL_NOTEQUAL
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			255,
+			128,
+			7
+		}),
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			255,
+			128,
+			7,
+			0xFFFFFFFF
+		})
+	);
+
+	ADD_ARG_CASE3(stencil_op, "Test cost of glStencilOp() calls",
+		stencilOp,
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		})
+	);
+
+	ADD_ARG_CASE4(viewport, "Test cost of glViewport() calls",
+		viewport,
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		})
+	);
+
+	ADD_ARG_CASE4(scissor, "Test cost of glScissor() calls",
+		scissor,
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		})
+	);
+
+	ADD_ARG_CASE4(stencil_func_separate, "Test cost of glStencilFuncSeparate() calls",
+		stencilFuncSeparate,
+		GLenum,
+		ARG_LIST({
+			GL_FRONT,
+			GL_BACK,
+			GL_FRONT_AND_BACK
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_NEVER,
+			GL_ALWAYS,
+			GL_LESS,
+			GL_LEQUAL,
+			GL_EQUAL,
+			GL_GEQUAL,
+			GL_GREATER,
+			GL_NOTEQUAL
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			255,
+			128,
+			7
+		}),
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			255,
+			128,
+			7,
+			0xFFFFFFFF
+		})
+	);
+
+	ADD_ARG_CASE4(stencil_op_separatae, "Test cost of glStencilOpSeparate() calls",
+		stencilOpSeparate,
+		GLenum,
+		ARG_LIST({
+			GL_FRONT,
+			GL_BACK,
+			GL_FRONT_AND_BACK
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		})
+	);
+
+	ADD_ARG_CASE4(blend_func_separate, "Test cost of glBlendFuncSeparate() calls",
+		blendFuncSeparate,
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		})
+	);
+
+	ADD_ARG_CASE4(color_mask, "Test cost of glColorMask() calls",
+		colorMask,
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		})
+	);
+
+	ADD_ARG_CASE4(clear_color, "Test cost of glClearColor() calls",
+		clearColor,
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			0.33f
+		}),
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			0.33f
+		}),
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			0.33f
+		}),
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			0.33f
+		})
+	);
+
+	ADD_ARG_CASE6(vertex_attrib_pointer, "Test cost of glVertexAttribPointer() calls",
+		vertexAttribPointer,
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			2,
+			3,
+			4,
+			5,
+			6,
+			7
+		}),
+		GLint,
+		ARG_LIST({
+			1,
+			2,
+			3,
+			4
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_UNSIGNED_BYTE,
+			GL_BYTE,
+			GL_UNSIGNED_SHORT,
+			GL_SHORT,
+			GL_FLOAT
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_FALSE,
+			GL_TRUE
+		}),
+		GLsizei,
+		ARG_LIST({
+			0,
+			2,
+			4
+		}),
+		void*,
+		ARG_LIST({
+			(void*)(deUintptr)(0x0FF),
+			(void*)(deUintptr)(0x0EF)
+		})
+	);
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pStateChangeCallTests.hpp b/modules/gles2/performance/es2pStateChangeCallTests.hpp
new file mode 100644
index 0000000..f24cd5b
--- /dev/null
+++ b/modules/gles2/performance/es2pStateChangeCallTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PSTATECHANGECALLTESTS_HPP
+#define _ES2PSTATECHANGECALLTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State change call performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class StateChangeCallTests : public TestCaseGroup
+{
+public:
+							StateChangeCallTests	(Context& context);
+							~StateChangeCallTests	(void);
+
+	void					init						(void);
+
+private:
+							StateChangeCallTests	(const StateChangeCallTests& other);
+	StateChangeCallTests&	operator=				(const StateChangeCallTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PSTATECHANGECALLTESTS_HPP
diff --git a/modules/gles2/performance/es2pStateChangeTests.cpp b/modules/gles2/performance/es2pStateChangeTests.cpp
new file mode 100644
index 0000000..b315908
--- /dev/null
+++ b/modules/gles2/performance/es2pStateChangeTests.cpp
@@ -0,0 +1,1527 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State change performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pStateChangeTests.hpp"
+#include "glsStateChangePerfTestCases.hpp"
+#include "gluShaderProgram.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+using namespace glw; // GL types
+
+namespace
+{
+
+enum
+{
+	VIEWPORT_WIDTH	= 24,
+	VIEWPORT_HEIGHT	= 24
+};
+
+class StateChangeCase : public gls::StateChangePerformanceCase
+{
+public:
+					StateChangeCase			(Context& context, int drawCallCount, int triangleCount, bool drawArrays, bool useIndexBuffer, const char* name, const char* description);
+					~StateChangeCase		(void);
+
+protected:
+	virtual void	renderTest				(const glw::Functions& gl);
+	virtual void	renderReference			(const glw::Functions& gl);
+
+	virtual void	changeState				(const glw::Functions& gl, int stateId) = 0;
+};
+
+StateChangeCase::StateChangeCase (Context& context, int drawCallCount, int triangleCount, bool drawArrays, bool useIndexBuffer, const char* name, const char* description)
+	: gls::StateChangePerformanceCase(context.getTestContext(), context.getRenderContext(), name, description,
+									  (useIndexBuffer	? DRAWTYPE_INDEXED_BUFFER	:
+									   drawArrays		? DRAWTYPE_NOT_INDEXED		:
+														  DRAWTYPE_INDEXED_USER_PTR), drawCallCount, triangleCount)
+{
+	DE_ASSERT(!useIndexBuffer || !drawArrays);
+}
+
+StateChangeCase::~StateChangeCase (void)
+{
+}
+
+void StateChangeCase::renderTest (const glw::Functions& gl)
+{
+	for (int callNdx = 0; callNdx < m_callCount; callNdx++)
+	{
+		changeState(gl, 0);
+		callDraw(gl);
+
+		changeState(gl, 1);
+		callDraw(gl);
+	}
+}
+
+void StateChangeCase::renderReference (const glw::Functions& gl)
+{
+	changeState(gl, 0);
+
+	for (int callNdx = 0; callNdx < m_callCount; callNdx++)
+		callDraw(gl);
+
+	changeState(gl, 1);
+
+	for (int callNdx = 0; callNdx < m_callCount; callNdx++)
+		callDraw(gl);
+}
+
+} // anonymous
+
+StateChangeTests::StateChangeTests (Context& context)
+	: TestCaseGroup(context, "state_change_draw", "Test state change perfomance with draw calls.")
+{
+}
+
+StateChangeTests::~StateChangeTests (void)
+{
+}
+
+#define MACRO_BLOCK(...) __VA_ARGS__
+
+#define ADD_TESTCASE(NAME, DESC, DRAWARRAYS, INDEXBUFFER, INIT_FUNC, CHANGE_FUNC)\
+do {\
+	class StateChangeCase_ ## NAME : public StateChangeCase\
+	{\
+	public:\
+			StateChangeCase_ ## NAME (Context& context, int drawCallCount, int triangleCount, const char* name, const char* description)\
+				: StateChangeCase(context, drawCallCount, triangleCount, (DRAWARRAYS), (INDEXBUFFER), name, description)\
+			{}\
+		virtual void setupInitialState (const glw::Functions& gl)\
+		{\
+			INIT_FUNC\
+		}\
+		virtual void changeState (const glw::Functions& gl, int stateId)\
+		{\
+			CHANGE_FUNC\
+		}\
+	};\
+	manySmallCallsGroup->addChild	(new StateChangeCase_ ## NAME (m_context,1000,2,#NAME,(DESC)));\
+	fewBigCallsGroup->addChild		(new StateChangeCase_ ## NAME (m_context,10,200,#NAME,(DESC)));\
+} while (0);
+
+void StateChangeTests::init (void)
+{
+	tcu::TestCaseGroup* const	manySmallCallsGroup	= new tcu::TestCaseGroup(m_testCtx, "many_small_calls",	"1000 calls, 2 triangles in each");
+	tcu::TestCaseGroup* const	fewBigCallsGroup	= new tcu::TestCaseGroup(m_testCtx, "few_big_calls",	"10 calls, 200 triangles in each");
+
+	addChild(manySmallCallsGroup);
+	addChild(fewBigCallsGroup);
+
+	ADD_TESTCASE(blend, "Enable/Disable blending.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_BLEND);
+			else if (stateId == 1)
+				gl.disable(GL_BLEND);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(depth_test, "Enable/Disable depth test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.depthFunc(GL_LEQUAL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthFunc()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_DEPTH_TEST);
+			else if (stateId == 1)
+				gl.disable(GL_DEPTH_TEST);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(stencil_test, "Enable/Disable stencil test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_STENCIL_TEST);
+			else if (stateId == 1)
+				gl.disable(GL_STENCIL_TEST);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(scissor_test, "Enable/Disable scissor test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.scissor(2, 3, 12, 13);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glScissor()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_SCISSOR_TEST);
+			else if (stateId == 1)
+				gl.disable(GL_SCISSOR_TEST);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(dither, "Enable/Disable dithering.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_DITHER);
+			else if (stateId == 1)
+				gl.disable(GL_DITHER);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(culling, "Enable/Disable culling.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.frontFace(GL_CW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glFrontFace()");
+
+			gl.cullFace(GL_FRONT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glCullFace()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_CULL_FACE);
+			else if (stateId == 1)
+				gl.disable(GL_CULL_FACE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(depth_func, "Change depth func.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_DEPTH_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.depthFunc(GL_GEQUAL);
+			else if (stateId == 1)
+				gl.depthFunc(GL_LEQUAL);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+
+	ADD_TESTCASE(depth_mask, "Toggle depth mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_DEPTH_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.depthFunc(GL_LEQUAL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthFunc()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.depthMask(GL_FALSE);
+			else if (stateId == 1)
+				gl.depthMask(GL_TRUE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(depth_rangef, "Change depth range.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.depthRangef(0.0f, 1.0f);
+			else if (stateId == 1)
+				gl.depthRangef(0.25f, 0.75f);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(blend_equation, "Change blend equation.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.blendEquation(GL_FUNC_SUBTRACT);
+			else if (stateId == 1)
+				gl.blendEquation(GL_FUNC_ADD);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(blend_func, "Change blend function.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+			else if (stateId == 1)
+				gl.blendFunc(GL_ONE, GL_ONE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(polygon_offset, "Change polygon offset.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_POLYGON_OFFSET_FILL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.polygonOffset(0.0f, 0.0f);
+			else if (stateId == 1)
+				gl.polygonOffset(0.1f, 0.1f);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(sample_coverage, "Sample coverage.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.sampleCoverage(0.25f, GL_TRUE);
+			else if (stateId == 1)
+				gl.sampleCoverage(0.75f, GL_FALSE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(viewport, "Change viewport.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.viewport(10, 11, 5, 6);
+			else if (stateId == 1)
+				gl.viewport(2, 3, 17, 14);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(scissor, "Change scissor box.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_SCISSOR_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.scissor(17, 13, 5, 8);
+			else if (stateId == 1)
+				gl.scissor(7, 3, 13, 13);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(color_mask, "Change color mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.colorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
+			else if (stateId == 1)
+				gl.colorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(cull_face, "Change culling mode.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_CULL_FACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.cullFace(GL_FRONT);
+			else if (stateId == 1)
+				gl.cullFace(GL_BACK);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(front_face, "Change front face.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_CULL_FACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.frontFace(GL_CCW);
+			else if (stateId == 1)
+				gl.frontFace(GL_CW);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(stencil_mask, "Change stencil mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.stencilMask(0xDD);
+			else if (stateId == 1)
+				gl.stencilMask(~0xDD);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(stencil_func, "Change stencil func.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.stencilFunc(GL_LEQUAL, 0, 0xFF);
+			else if (stateId == 1)
+				gl.stencilFunc(GL_GEQUAL, 0, 0x00);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(stencil_op, "Change stencil op.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			else if (stateId == 1)
+				gl.stencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(bind_array_buffer, "Change array buffer and refresh vertex attrib pointer.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(2);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.bindAttribLocation(m_programs[0]->getProgram(), 0, "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindAttribLocation()"); 
+			gl.linkProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glLinkProgram()");
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+			gl.enableVertexAttribArray(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+			{
+				gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+				gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			}
+			else if (stateId == 1)
+			{
+				gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[1]);
+				gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			}
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(element_array_buffer, "Change element array buffer.",
+		false,
+		true,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireIndexBuffers(2);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffers[0]);
+			else if (stateId == 1)
+				gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffers[1]);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(bind_texture, "Change texture binding.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(2);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			else if (stateId == 1)
+				gl.bindTexture(GL_TEXTURE_2D, m_textures[1]);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(use_program, "Change used program.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(2);
+
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			{
+				GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+				gl.useProgram(m_programs[0]->getProgram());
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+				gl.uniform1i(samplerLoc, 0);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+			}
+
+			{
+				GLint samplerLoc = gl.getUniformLocation(m_programs[1]->getProgram(), "u_sampler");
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+				gl.useProgram(m_programs[1]->getProgram());
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+				gl.uniform1i(samplerLoc, 0);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+			}
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.useProgram(m_programs[0]->getProgram());
+			else if (stateId == 1)
+				gl.useProgram(m_programs[1]->getProgram());
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_min_filter, "Change texture parameter min filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+			else if (stateId == 1)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_mag_filter, "Change texture parameter mag filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+			else if (stateId == 1)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_wrap, "Change texture parameter wrap filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+			else if (stateId == 1)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(bind_framebuffer, "Change framebuffer.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requireFramebuffers(2);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffers[0]);
+			else if (stateId == 1)
+				gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffers[1]);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(blend_color, "Change blend color.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.blendFunc(GL_CONSTANT_COLOR, GL_CONSTANT_COLOR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBlendFunc()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.blendColor(0.25f, 0.25f, 0.25f, 0.25f);
+			else if (stateId == 1)
+				gl.blendColor(0.75f, 0.75f, 0.75f, 0.75f);
+			else
+				DE_ASSERT(false);
+		})
+	)
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pStateChangeTests.hpp b/modules/gles2/performance/es2pStateChangeTests.hpp
new file mode 100644
index 0000000..959a06e
--- /dev/null
+++ b/modules/gles2/performance/es2pStateChangeTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PSTATECHANGETESTS_HPP
+#define _ES2PSTATECHANGETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State change performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class StateChangeTests : public TestCaseGroup
+{
+public:
+						StateChangeTests	(Context& context);
+						~StateChangeTests	(void);
+
+	void				init				(void);
+
+private:
+						StateChangeTests	(const StateChangeTests& other);
+	StateChangeTests&	operator=			(const StateChangeTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PSTATECHANGETESTS_HPP
diff --git a/modules/gles2/performance/es2pTextureCases.cpp b/modules/gles2/performance/es2pTextureCases.cpp
new file mode 100644
index 0000000..8a79ecc
--- /dev/null
+++ b/modules/gles2/performance/es2pTextureCases.cpp
@@ -0,0 +1,229 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pTextureCases.hpp"
+#include "glsShaderPerformanceCase.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluTexture.hpp"
+#include "gluStrUtil.hpp"
+
+#include "deStringUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+using namespace gls;
+using namespace glw; // GL types
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec4;
+using std::string;
+using std::vector;
+using tcu::TestLog;
+
+Texture2DRenderCase::Texture2DRenderCase (Context&			context,
+										  const char*		name,
+										  const char*		description,
+										  deUint32			format,
+										  deUint32			dataType,
+										  deUint32			wrapS,
+										  deUint32			wrapT,
+										  deUint32			minFilter,
+										  deUint32			magFilter,
+										  const tcu::Mat3&	coordTransform,
+										  int				numTextures,
+										  bool				powerOfTwo)
+	: ShaderPerformanceCase	(context.getTestContext(), context.getRenderContext(), name, description, CASETYPE_FRAGMENT)
+	, m_format				(format)
+	, m_dataType			(dataType)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_coordTransform		(coordTransform)
+	, m_numTextures			(numTextures)
+	, m_powerOfTwo			(powerOfTwo)
+{
+}
+
+Texture2DRenderCase::~Texture2DRenderCase (void)
+{
+	for (vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+}
+
+static inline int roundDownToPowerOfTwo (int val)
+{
+	DE_ASSERT(val >= 0);
+	int l0 = deClz32(val);
+	return val & ~((1<<(31-l0))-1);
+}
+
+void Texture2DRenderCase::init (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	int width	= m_renderCtx.getRenderTarget().getWidth();
+	int height	= m_renderCtx.getRenderTarget().getHeight();
+
+	if (m_powerOfTwo)
+	{
+		width	= roundDownToPowerOfTwo(width);
+		height	= roundDownToPowerOfTwo(height);
+	}
+
+	bool mipmaps = m_minFilter == GL_NEAREST_MIPMAP_NEAREST ||
+				   m_minFilter == GL_NEAREST_MIPMAP_LINEAR	||
+				   m_minFilter == GL_LINEAR_MIPMAP_NEAREST	||
+				   m_minFilter == GL_LINEAR_MIPMAP_LINEAR;
+
+	DE_ASSERT(m_powerOfTwo || (!mipmaps && m_wrapS == GL_CLAMP_TO_EDGE && m_wrapT == GL_CLAMP_TO_EDGE));
+
+	Vec2 p00 = (m_coordTransform * Vec3(0.0f, 0.0f, 1.0f)).swizzle(0,1);
+	Vec2 p10 = (m_coordTransform * Vec3(1.0f, 0.0f, 1.0f)).swizzle(0,1);
+	Vec2 p01 = (m_coordTransform * Vec3(0.0f, 1.0f, 1.0f)).swizzle(0,1);
+	Vec2 p11 = (m_coordTransform * Vec3(1.0f, 1.0f, 1.0f)).swizzle(0,1);
+
+	m_attributes.push_back(AttribSpec("a_coords", Vec4(p00.x(), p00.y(), 0.0f, 0.0f),
+												  Vec4(p10.x(), p10.y(), 0.0f, 0.0f),
+												  Vec4(p01.x(), p01.y(), 0.0f, 0.0f),
+												  Vec4(p11.x(), p11.y(), 0.0f, 0.0f)));
+
+	log << TestLog::Message << "Size: " << width << "x" << height << TestLog::EndMessage;
+	log << TestLog::Message << "Format: " <<glu::getPixelFormatName(m_format) << " " << glu::getTypeName(m_dataType) << TestLog::EndMessage;
+	log << TestLog::Message << "Coords: " << p00 << ", " << p10 << ", " << p01 << ", " << p11 << TestLog::EndMessage;
+	log << TestLog::Message << "Wrap: " << glu::getTextureWrapModeStr(m_wrapS) << " / " << glu::getTextureWrapModeStr(m_wrapT) << TestLog::EndMessage;
+	log << TestLog::Message << "Filter: " << glu::getTextureFilterStr(m_minFilter) << " / " << glu::getTextureFilterStr(m_magFilter) << TestLog::EndMessage;
+	log << TestLog::Message << "Mipmaps: " << (mipmaps ? "true" : "false") << TestLog::EndMessage;
+	log << TestLog::Message << "Using additive blending." << TestLog::EndMessage;
+
+	// Use same viewport size as texture size.
+	setViewportSize(width, height);
+
+	m_vertShaderSource =
+		"attribute highp vec4 a_position;\n"
+		"attribute mediump vec2 a_coords;\n"
+		"varying mediump vec2 v_coords;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_coords = a_coords;\n"
+		"}\n";
+
+	std::ostringstream fragSrc;
+	fragSrc << "varying mediump vec2 v_coords;\n";
+
+	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+		fragSrc << "uniform sampler2D u_sampler" << texNdx << ";\n";
+
+	fragSrc << "void main (void)\n"
+			<< "{\n";
+
+	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+		fragSrc << "\t" << (texNdx == 0 ? "lowp vec4 r = " : "r += ") << "texture2D(u_sampler" << texNdx << ", v_coords);\n";
+
+	fragSrc << "	gl_FragColor = r;\n"
+			<< "}\n";
+
+	m_fragShaderSource = fragSrc.str();
+
+	m_textures.reserve(m_numTextures);
+	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+	{
+		static const IVec4 swizzles[] = { IVec4(0,1,2,3), IVec4(1,2,3,0), IVec4(2,3,0,1), IVec4(3,0,1,2),
+										  IVec4(3,2,1,0), IVec4(2,1,0,3), IVec4(1,0,3,2), IVec4(0,3,2,1) };
+		const IVec4& sw = swizzles[texNdx % DE_LENGTH_OF_ARRAY(swizzles)];
+
+		glu::Texture2D* texture = new glu::Texture2D(m_renderCtx, m_format, m_dataType, width, height);
+		m_textures.push_back(texture);
+
+		// Fill levels.
+		int numLevels = mipmaps ? texture->getRefTexture().getNumLevels() : 1;
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			texture->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithComponentGradients(texture->getRefTexture().getLevel(levelNdx),
+											Vec4(0.0f, 0.0f, 0.0f, 0.0f).swizzle(sw[0], sw[1], sw[2], sw[3]),
+											Vec4(1.0f, 1.0f, 1.0f, 1.0f).swizzle(sw[0], sw[1], sw[2], sw[3]));
+		}
+
+		texture->upload();
+	}
+
+	ShaderPerformanceCase::init();
+}
+
+void Texture2DRenderCase::deinit (void)
+{
+	for (vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	ShaderPerformanceCase::deinit();
+}
+
+void Texture2DRenderCase::setupProgram (deUint32 program)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+	{
+		int samplerLoc = gl.getUniformLocation(program, (string("u_sampler") + de::toString(texNdx)).c_str());
+		gl.uniform1i(samplerLoc, texNdx);
+	}
+}
+
+void Texture2DRenderCase::setupRenderState (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	// Setup additive blending.
+	gl.enable(GL_BLEND);
+	gl.blendFunc(GL_ONE, GL_ONE);
+	gl.blendEquation(GL_FUNC_ADD);
+
+	// Setup textures.
+	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+	{
+		gl.activeTexture(GL_TEXTURE0 + texNdx);
+		gl.bindTexture(GL_TEXTURE_2D, m_textures[texNdx]->getGLTexture());
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	}
+}
+
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pTextureCases.hpp b/modules/gles2/performance/es2pTextureCases.hpp
new file mode 100644
index 0000000..1e793a4
--- /dev/null
+++ b/modules/gles2/performance/es2pTextureCases.hpp
@@ -0,0 +1,73 @@
+#ifndef _ES2PTEXTURECASES_HPP
+#define _ES2PTEXTURECASES_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+#include "glsShaderPerformanceCase.hpp"
+#include "tcuMatrix.hpp"
+#include "gluTexture.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class Texture2DRenderCase : public gls::ShaderPerformanceCase
+{
+public:
+									Texture2DRenderCase			(Context& context, const char* name, const char* description,
+																 deUint32 format, deUint32 dataType,
+																 deUint32 wrapS, deUint32 wrapT,
+																 deUint32 minFilter, deUint32 magFilter,
+																 const tcu::Mat3& coordTransform,
+																 int numTextures, bool powerOfTwo);
+									~Texture2DRenderCase		(void);
+
+	void							init						(void);
+	void							deinit						(void);
+
+private:
+	void							setupProgram				(deUint32 program);
+	void							setupRenderState			(void);
+
+	deUint32						m_format;
+	deUint32						m_dataType;
+	deUint32						m_wrapS;
+	deUint32						m_wrapT;
+	deUint32						m_minFilter;
+	deUint32						m_magFilter;
+	tcu::Mat3						m_coordTransform;
+	int								m_numTextures;
+	bool							m_powerOfTwo;
+	std::vector<glu::Texture2D*>	m_textures;
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PTEXTURECASES_HPP
diff --git a/modules/gles2/performance/es2pTextureCountTests.cpp b/modules/gles2/performance/es2pTextureCountTests.cpp
new file mode 100644
index 0000000..65584e9
--- /dev/null
+++ b/modules/gles2/performance/es2pTextureCountTests.cpp
@@ -0,0 +1,87 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture count performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pTextureCountTests.hpp"
+#include "es2pTextureCases.hpp"
+#include "gluStrUtil.hpp"
+
+#include "deStringUtil.hpp"
+
+#include "glwEnums.hpp"
+
+using std::string;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+TextureCountTests::TextureCountTests (Context& context)
+	: TestCaseGroup(context, "count", "Texture Count Performance Tests")
+{
+}
+
+TextureCountTests::~TextureCountTests (void)
+{
+}
+
+void TextureCountTests::init (void)
+{
+	static const struct
+	{
+		const char*	name;
+		deUint32	format;
+		deUint32	dataType;
+	} texFormats[] =
+	{
+		{ "a8",			GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "rgb565",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+	static const int texCounts[] = { 1, 2, 4, 8 };
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+	{
+		for (int cntNdx = 0; cntNdx < DE_LENGTH_OF_ARRAY(texCounts); cntNdx++)
+		{
+			deUint32	format			= texFormats[formatNdx].format;
+			deUint32	dataType		= texFormats[formatNdx].dataType;
+			deUint32	wrapS			= GL_CLAMP_TO_EDGE;
+			deUint32	wrapT			= GL_CLAMP_TO_EDGE;
+			deUint32	minFilter		= GL_NEAREST;
+			deUint32	magFilter		= GL_NEAREST;
+			int			numTextures		= texCounts[cntNdx];
+			string		name			= string(texFormats[formatNdx].name) + "_" + de::toString(numTextures);
+			string		description 	= string(glu::getPixelFormatName(format)) + ", " + glu::getTypeName(dataType);
+
+			addChild(new Texture2DRenderCase(m_context, name.c_str(), description.c_str(), format, dataType, wrapS, wrapT, minFilter, magFilter, tcu::Mat3(), numTextures, false /* npot */));
+		}
+	}
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pTextureCountTests.hpp b/modules/gles2/performance/es2pTextureCountTests.hpp
new file mode 100644
index 0000000..0d1bcbe
--- /dev/null
+++ b/modules/gles2/performance/es2pTextureCountTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PTEXTURECOUNTTESTS_HPP
+#define _ES2PTEXTURECOUNTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture count performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class TextureCountTests : public TestCaseGroup
+{
+public:
+							TextureCountTests		(Context& context);
+							~TextureCountTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureCountTests		(const TextureCountTests& other);
+	TextureCountTests&		operator=				(const TextureCountTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PTEXTURECOUNTTESTS_HPP
diff --git a/modules/gles2/performance/es2pTextureFilteringTests.cpp b/modules/gles2/performance/es2pTextureFilteringTests.cpp
new file mode 100644
index 0000000..fa4b74d
--- /dev/null
+++ b/modules/gles2/performance/es2pTextureFilteringTests.cpp
@@ -0,0 +1,101 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pTextureFilteringTests.hpp"
+#include "es2pTextureCases.hpp"
+#include "tcuMatrixUtil.hpp"
+
+#include "glwEnums.hpp"
+
+using std::string;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+TextureFilteringTests::TextureFilteringTests (Context& context)
+	: TestCaseGroup(context, "filter", "Texture Filtering Performance Tests")
+{
+}
+
+TextureFilteringTests::~TextureFilteringTests (void)
+{
+}
+
+void TextureFilteringTests::init (void)
+{
+	static const struct
+	{
+		const char*	name;
+		deUint32	format;
+		deUint32	dataType;
+	} texFormats[] =
+	{
+		{ "rgb565",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+	static const struct
+	{
+		const char*	name;
+		deUint32	filter;
+		bool		minify;
+	} cases[] =
+	{
+		{ "nearest",				GL_NEAREST,					true	},
+		{ "nearest",				GL_NEAREST,					false	},
+		{ "linear",					GL_LINEAR,					true	},
+		{ "linear",					GL_LINEAR,					false	},
+		{ "nearest_mipmap_nearest",	GL_NEAREST_MIPMAP_NEAREST,	true	},
+		{ "nearest_mipmap_linear",	GL_NEAREST_MIPMAP_LINEAR,	true	},
+		{ "linear_mipmap_nearest",	GL_LINEAR_MIPMAP_NEAREST,	true	},
+		{ "linear_mipmap_linear",	GL_LINEAR_MIPMAP_LINEAR,	true	}
+	};
+
+	tcu::Mat3 minTransform	= tcu::translationMatrix(tcu::Vec2(-0.3f, -0.6f)) * tcu::Mat3(tcu::Vec3(1.7f, 2.3f, 1.0f));
+	tcu::Mat3 magTransform	= tcu::translationMatrix(tcu::Vec2( 0.3f,  0.4f)) * tcu::Mat3(tcu::Vec3(0.3f, 0.2f, 1.0f));
+
+	for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+	{
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+		{
+			deUint32	format		= texFormats[formatNdx].format;
+			deUint32	dataType	= texFormats[formatNdx].dataType;
+			deUint32	minFilter	= cases[caseNdx].filter;
+			deUint32	magFilter	= (minFilter == GL_NEAREST || minFilter == GL_LINEAR) ? minFilter : GL_LINEAR;
+			deUint32	wrapS		= GL_REPEAT;
+			deUint32	wrapT		= GL_REPEAT;
+			int			numTextures	= 1;
+			bool		minify		= cases[caseNdx].minify;
+			string		name		= string(cases[caseNdx].name) + (minify ? "_minify_" : "_magnify_") + texFormats[formatNdx].name;
+
+			addChild(new Texture2DRenderCase(m_context, name.c_str(), "", format, dataType, wrapS, wrapT, minFilter, magFilter, minify ? minTransform : magTransform, numTextures, true /* pot */));
+		}
+	}
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pTextureFilteringTests.hpp b/modules/gles2/performance/es2pTextureFilteringTests.hpp
new file mode 100644
index 0000000..a1db708
--- /dev/null
+++ b/modules/gles2/performance/es2pTextureFilteringTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PTEXTUREFILTERINGTESTS_HPP
+#define _ES2PTEXTUREFILTERINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class TextureFilteringTests : public TestCaseGroup
+{
+public:
+								TextureFilteringTests		(Context& context);
+								~TextureFilteringTests		(void);
+
+	void						init						(void);
+
+private:
+								TextureFilteringTests		(const TextureFilteringTests& other);
+	TextureFilteringTests&		operator=					(const TextureFilteringTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PTEXTUREFILTERINGTESTS_HPP
diff --git a/modules/gles2/performance/es2pTextureFormatTests.cpp b/modules/gles2/performance/es2pTextureFormatTests.cpp
new file mode 100644
index 0000000..0bd3dfe
--- /dev/null
+++ b/modules/gles2/performance/es2pTextureFormatTests.cpp
@@ -0,0 +1,85 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pTextureFormatTests.hpp"
+#include "es2pTextureCases.hpp"
+#include "gluStrUtil.hpp"
+
+#include "glwEnums.hpp"
+
+using std::string;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+TextureFormatTests::TextureFormatTests (Context& context)
+	: TestCaseGroup(context, "format", "Texture Format Performance Tests")
+{
+}
+
+TextureFormatTests::~TextureFormatTests (void)
+{
+}
+
+void TextureFormatTests::init (void)
+{
+	static const struct
+	{
+		const char*	name;
+		deUint32	format;
+		deUint32	dataType;
+	} texFormats[] =
+	{
+		{ "a8",			GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "la88",		GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
+		{ "rgb565",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgba5551",	GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
+		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+	{
+		deUint32	format			= texFormats[formatNdx].format;
+		deUint32	dataType		= texFormats[formatNdx].dataType;
+		string		nameBase		= texFormats[formatNdx].name;
+		deUint32	wrapS			= GL_CLAMP_TO_EDGE;
+		deUint32	wrapT			= GL_CLAMP_TO_EDGE;
+		deUint32	minFilter		= GL_NEAREST;
+		deUint32	magFilter		= GL_NEAREST;
+		int			numTextures		= 1;
+		string		descriptionBase	= string(glu::getPixelFormatName(format)) + ", " + glu::getTypeName(dataType);
+
+		addChild(new Texture2DRenderCase(m_context, nameBase.c_str(), descriptionBase.c_str(), format, dataType, wrapS, wrapT, minFilter, magFilter, tcu::Mat3(), numTextures, false /* npot */));
+	}
+}
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pTextureFormatTests.hpp b/modules/gles2/performance/es2pTextureFormatTests.hpp
new file mode 100644
index 0000000..580e43d
--- /dev/null
+++ b/modules/gles2/performance/es2pTextureFormatTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2PTEXTUREFORMATTESTS_HPP
+#define _ES2PTEXTUREFORMATTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+class TextureFormatTests : public TestCaseGroup
+{
+public:
+							TextureFormatTests		(Context& context);
+							~TextureFormatTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureFormatTests		(const TextureFormatTests& other);
+	TextureFormatTests&		operator=				(const TextureFormatTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PTEXTUREFORMATTESTS_HPP
diff --git a/modules/gles2/performance/es2pTextureUploadTests.cpp b/modules/gles2/performance/es2pTextureUploadTests.cpp
new file mode 100644
index 0000000..a2600f1
--- /dev/null
+++ b/modules/gles2/performance/es2pTextureUploadTests.cpp
@@ -0,0 +1,564 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture upload performance tests.
+ *
+ * \todo [2012-10-01 pyry]
+ *  - Test different pixel unpack alignments
+ *  - Use multiple textures
+ *  - Trash cache prior to uploading from data ptr
+ *//*--------------------------------------------------------------------*/
+
+#include "es2pTextureUploadTests.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deClock.h"
+#include "deString.h"
+
+#include "glsCalibration.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <algorithm>
+#include <vector>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec4;
+using std::string;
+using std::vector;
+using tcu::TestLog;
+using tcu::TextureFormat;
+using namespace glw; // GL types
+
+static const int	VIEWPORT_SIZE	= 64;
+static const float	quadCoords[] =
+{
+	-1.0f, -1.0f,
+	 1.0f, -1.0f,
+	-1.0f,  1.0f,
+	 1.0f,  1.0f
+};
+
+class TextureUploadCase : public TestCase
+{
+public:
+								TextureUploadCase	(Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize);
+								~TextureUploadCase	(void);
+
+	virtual void				init				(void);
+	void						deinit				(void);
+
+	virtual IterateResult		iterate				(void) = 0;
+	void						logResults			(void);
+
+protected:
+	UploadFunction				m_uploadFunction;
+	deUint32					m_format;
+	deUint32					m_type;
+	int							m_texSize;
+	int							m_alignment;
+
+	gls::TheilSenCalibrator		m_calibrator;
+	glu::ShaderProgram*			m_program;
+	deUint32					m_texture;
+	de::Random					m_rnd;
+	TestLog&					m_log;
+
+	vector<deUint8>				m_texData;
+};
+
+TextureUploadCase::TextureUploadCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize)
+	: TestCase			(context, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_uploadFunction	(uploadFunction)
+	, m_format			(format)
+	, m_type			(type)
+	, m_texSize			(texSize)
+	, m_alignment		(4)
+	, m_calibrator		()
+	, m_program			(DE_NULL)
+	, m_texture			(0)
+	, m_rnd				(deStringHash(name))
+	, m_log				(context.getTestContext().getLog())
+{
+}
+
+TextureUploadCase::~TextureUploadCase (void)
+{
+	TextureUploadCase::deinit();
+}
+
+void TextureUploadCase::deinit (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_program)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+	}
+
+	gl.deleteTextures(1, &m_texture);
+	m_texture = 0;
+
+	m_texData.clear();
+}
+
+void TextureUploadCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	int maxTextureSize;
+	gl.getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+
+	if (m_texSize > maxTextureSize)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Unsupported texture size");
+		return;
+	}
+
+	// Create program
+
+	string vertexShaderSource = "";
+	string fragmentShaderSource = "";
+
+	vertexShaderSource.append(	"precision mediump	float;\n"
+								"attribute vec2		a_pos;\n"
+								"varying   vec2		v_texCoord;\n"
+								"\n"
+								"void main (void)\n"
+								"{\n"
+								"	v_texCoord	= a_pos;\n"
+								"	gl_Position = vec4(a_pos, 0.5, 1.0);\n"
+								"}\n");
+
+	fragmentShaderSource.append("precision	mediump	float;\n"
+								"uniform	lowp sampler2D	u_sampler;\n"
+								"varying	vec2			v_texCoord;\n"
+								"\n"
+								"void main (void)\n"
+								"{\n"
+								"	gl_FragColor = texture2D(u_sampler, v_texCoord.xy);\n"
+								"}\n");
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+	if (!m_program->isOk())
+	{
+		m_log << *m_program;
+		TCU_FAIL("Failed to create shader program (m_programRender)");
+	}
+
+	gl.useProgram (m_program->getProgram());
+
+	// Init GL state
+
+	gl.viewport		(0, 0, VIEWPORT_SIZE, VIEWPORT_SIZE);
+	gl.disable		(GL_DEPTH_TEST);
+	gl.disable		(GL_CULL_FACE);
+	gl.enable		(GL_BLEND);
+	gl.blendFunc	(GL_ONE, GL_ONE);
+	gl.clearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear		(GL_COLOR_BUFFER_BIT);
+
+	deUint32 uSampler	= gl.getUniformLocation(m_program->getProgram(), "u_sampler");
+	deUint32 aPos		= gl.getAttribLocation (m_program->getProgram(), "a_pos");
+	gl.enableVertexAttribArray	(aPos);
+	gl.vertexAttribPointer		(aPos,	2, GL_FLOAT, GL_FALSE, 0, &quadCoords[0]);
+	gl.uniform1i				(uSampler, 0);
+
+	// Create texture
+
+	gl.activeTexture	(GL_TEXTURE0);
+	gl.genTextures		(1, &m_texture);
+	gl.bindTexture		(GL_TEXTURE_2D, m_texture);
+	gl.pixelStorei		(GL_UNPACK_ALIGNMENT, m_alignment);
+	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+	// Prepare texture data
+
+	{
+		const tcu::TextureFormat&	texFmt		= glu::mapGLTransferFormat(m_format, m_type);
+		int							pixelSize	= texFmt.getPixelSize();
+		int							stride		= deAlign32(pixelSize*m_texSize, m_alignment);
+
+		m_texData.resize(stride*m_texSize);
+
+		tcu::PixelBufferAccess		access		(texFmt, m_texSize, m_texSize, 1, stride, 0, &m_texData[0]);
+
+		tcu::fillWithComponentGradients(access, tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+	}
+
+	// Do a dry-run to ensure the pipes are hot
+
+	gl.texImage2D	(GL_TEXTURE_2D, 0, m_format, m_texSize, m_texSize, 0, m_format, m_type, &m_texData[0]);
+	gl.drawArrays	(GL_TRIANGLE_STRIP, 0, 4);
+	gl.finish		();
+}
+
+void TextureUploadCase::logResults (void)
+{
+	const gls::MeasureState& measureState = m_calibrator.getMeasureState();
+
+	// Log measurement details
+
+	m_log << TestLog::Section("Measurement details", "Measurement details");
+	m_log << TestLog::Message << "Uploading texture with " << (m_uploadFunction == UPLOAD_TEXIMAGE2D ? "glTexImage2D" : "glTexSubImage2D") << "." << TestLog::EndMessage; // \todo [arttu] Change enum to struct with name included
+	m_log << TestLog::Message << "Texture size = "	<< m_texSize	 << "x" << m_texSize	 << "." << TestLog::EndMessage;
+	m_log << TestLog::Message << "Viewport size = " << VIEWPORT_SIZE << "x" << VIEWPORT_SIZE << "." << TestLog::EndMessage;
+	m_log << TestLog::Message << measureState.numDrawCalls << " upload calls / iteration" << TestLog::EndMessage;
+	m_log << TestLog::EndSection;
+
+	// Log results
+
+	TestLog& log = m_testCtx.getLog();
+	log << TestLog::Section("Results", "Results");
+
+	// Log individual frame durations
+	//for (int i = 0; i < m_calibrator.measureState.numFrames; i++)
+	//	m_log << TestLog::Message	<< "Frame "	<< i+1 << " duration: \t" << m_calibrator.measureState.frameTimes[i] << " us."<< TestLog::EndMessage;
+
+	std::vector<deUint64> sortedFrameTimes(measureState.frameTimes.begin(), measureState.frameTimes.end());
+	std::sort(sortedFrameTimes.begin(), sortedFrameTimes.end());
+	vector<deUint64>::const_iterator first	= sortedFrameTimes.begin();
+	vector<deUint64>::const_iterator last	= sortedFrameTimes.end();
+	vector<deUint64>::const_iterator middle	= first + (last - first) / 2;
+
+	deUint64 medianFrameTime			=  *middle;
+	double medianMTexelsPerSeconds		= (double)(m_texSize*m_texSize*measureState.numDrawCalls) / medianFrameTime;
+	double medianTexelDrawDurationNs	= (double)medianFrameTime * 1000.0 / (double)(m_texSize*m_texSize*measureState.numDrawCalls);
+
+	deUint64	totalTime			= measureState.getTotalTime();
+	int			numFrames			= (int)measureState.frameTimes.size();
+	deInt64		numTexturesDrawn	= measureState.numDrawCalls * numFrames;
+	deInt64		numPixels			= (deInt64)m_texSize * (deInt64)m_texSize * numTexturesDrawn;
+
+	double		framesPerSecond			= (double)numFrames / ((double)totalTime / 1000000.0);
+	double		avgFrameTime			= (double)totalTime / (double)numFrames;
+	double		avgMTexelsPerSeconds	= (double)numPixels / (double)totalTime;
+	double		avgTexelDrawDurationNs	= (double)totalTime * 1000.0 / (double)numPixels;
+
+	log << TestLog::Float("FramesPerSecond",	"Frames per second in measurement\t\t",		"Frames/s",		QP_KEY_TAG_PERFORMANCE,	(float)framesPerSecond);
+	log << TestLog::Float("AverageFrameTime",	"Average frame duration in measurement\t",	"us",			QP_KEY_TAG_PERFORMANCE,	(float)avgFrameTime);
+	log << TestLog::Float("AverageTexelPerf",	"Average texel upload performance\t\t",		"MTex/s",		QP_KEY_TAG_PERFORMANCE,	(float)avgMTexelsPerSeconds);
+	log << TestLog::Float("AverageTexelTime",	"Average texel upload duration\t\t",		"ns",			QP_KEY_TAG_PERFORMANCE,	(float)avgTexelDrawDurationNs);
+	log << TestLog::Float("MedianTexelPerf",	"Median texel upload performance\t\t",		"MTex/s",		QP_KEY_TAG_PERFORMANCE,	(float)medianMTexelsPerSeconds);
+	log << TestLog::Float("MedianTexelTime",	"Median texel upload duration\t\t",			"ns",			QP_KEY_TAG_PERFORMANCE,	(float)medianTexelDrawDurationNs);
+
+	log << TestLog::EndSection;
+
+	gls::logCalibrationInfo(log, m_calibrator);	// Log calibration details
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)avgMTexelsPerSeconds, 2).c_str());
+}
+
+// Texture upload call case
+
+class TextureUploadCallCase : public TextureUploadCase
+{
+public:
+							TextureUploadCallCase	(Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize);
+							~TextureUploadCallCase	(void);
+
+	IterateResult			iterate					(void);
+	void					render					(void);
+};
+
+TextureUploadCallCase::TextureUploadCallCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize)
+	: TextureUploadCase (context, name, description, uploadFunction, format, type, texSize)
+{
+}
+
+TextureUploadCallCase::~TextureUploadCallCase (void)
+{
+	TextureUploadCase::deinit();
+}
+
+void TextureUploadCallCase::render (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Draw multiple quads to ensure enough workload
+
+	switch (m_uploadFunction)
+	{
+		case UPLOAD_TEXIMAGE2D:
+			gl.texImage2D(GL_TEXTURE_2D, 0, m_format, m_texSize, m_texSize, 0, m_format, m_type, &m_texData[0]);
+			break;
+		case UPLOAD_TEXSUBIMAGE2D:
+			gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_texSize, m_texSize, m_format, m_type, &m_texData[0]);
+			break;
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+tcu::TestNode::IterateResult TextureUploadCallCase::iterate (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_testCtx.getTestResult() == QP_TEST_RESULT_NOT_SUPPORTED)
+		return STOP;
+
+	for (;;)
+	{
+		gls::TheilSenCalibrator::State state = m_calibrator.getState();
+
+		if (state == gls::TheilSenCalibrator::STATE_MEASURE)
+		{
+			int			numCalls	= m_calibrator.getCallCount();
+			deUint64	startTime	= deGetMicroseconds();
+
+			for (int i = 0; i < numCalls; i++)
+				render();
+
+			gl.finish();
+
+			deUint64	endTime		= deGetMicroseconds();
+			deUint64	duration	= endTime-startTime;
+
+			m_calibrator.recordIteration(duration);
+		}
+		else if (state == gls::TheilSenCalibrator::STATE_RECOMPUTE_PARAMS)
+		{
+			m_calibrator.recomputeParameters();
+		}
+		else
+		{
+			DE_ASSERT(state == gls::TheilSenCalibrator::STATE_FINISHED);
+			break;
+		}
+
+		// Touch watchdog between iterations to avoid timeout.
+		{
+			qpWatchDog* dog = m_testCtx.getWatchDog();
+			if (dog)
+				qpWatchDog_touch(dog);
+		}
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "iterate");
+	logResults();
+	return STOP;
+}
+
+// Texture upload and draw case
+
+class TextureUploadAndDrawCase : public TextureUploadCase
+{
+public:
+								TextureUploadAndDrawCase	(Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize);
+								~TextureUploadAndDrawCase	(void);
+
+	IterateResult				iterate						(void);
+	void						render						(void);
+
+private:
+	bool						m_lastIterationRender;
+	deUint64					m_renderStart;
+};
+
+TextureUploadAndDrawCase::TextureUploadAndDrawCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize)
+	: TextureUploadCase		(context, name, description, uploadFunction, format, type, texSize)
+	, m_lastIterationRender	(false)
+	, m_renderStart			(0)
+{
+}
+
+TextureUploadAndDrawCase::~TextureUploadAndDrawCase (void)
+{
+	TextureUploadCase::deinit();
+}
+
+void TextureUploadAndDrawCase::render (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Draw multiple quads to ensure enough workload
+
+	switch (m_uploadFunction)
+	{
+		case UPLOAD_TEXIMAGE2D:
+			gl.texImage2D(GL_TEXTURE_2D, 0, m_format, m_texSize, m_texSize, 0, m_format, m_type, &m_texData[0]);
+			break;
+		case UPLOAD_TEXSUBIMAGE2D:
+			gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_texSize, m_texSize, m_format, m_type, &m_texData[0]);
+			break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+}
+
+tcu::TestNode::IterateResult TextureUploadAndDrawCase::iterate (void)
+{
+	if (m_testCtx.getTestResult() == QP_TEST_RESULT_NOT_SUPPORTED)
+		return STOP;
+
+	if (m_lastIterationRender && (m_calibrator.getState() == gls::TheilSenCalibrator::STATE_MEASURE))
+	{
+		deUint64 curTime = deGetMicroseconds();
+		m_calibrator.recordIteration(curTime - m_renderStart);
+	}
+
+	gls::TheilSenCalibrator::State state = m_calibrator.getState();
+
+	if (state == gls::TheilSenCalibrator::STATE_MEASURE)
+	{
+		// Render
+		int	numCalls = m_calibrator.getCallCount();
+
+		m_renderStart			= deGetMicroseconds();
+		m_lastIterationRender	= true;
+
+		for (int i = 0; i < numCalls; i++)
+			render();
+
+		return CONTINUE;
+	}
+	else if (state == gls::TheilSenCalibrator::STATE_RECOMPUTE_PARAMS)
+	{
+		m_calibrator.recomputeParameters();
+		m_lastIterationRender = false;
+		return CONTINUE;
+	}
+	else
+	{
+		DE_ASSERT(state == gls::TheilSenCalibrator::STATE_FINISHED);
+		GLU_EXPECT_NO_ERROR(m_context.getRenderContext().getFunctions().getError(), "finish");
+		logResults();
+		return STOP;
+	}
+}
+
+// Texture upload tests
+
+TextureUploadTests::TextureUploadTests (Context& context)
+	: TestCaseGroup(context, "upload", "Texture upload tests")
+{
+}
+
+TextureUploadTests::~TextureUploadTests (void)
+{
+	TextureUploadTests::deinit();
+}
+
+void TextureUploadTests::deinit (void)
+{
+}
+
+void TextureUploadTests::init (void)
+{
+	TestCaseGroup* uploadCall		= new TestCaseGroup(m_context, "upload",			"Texture upload");
+	TestCaseGroup* uploadAndDraw	= new TestCaseGroup(m_context, "upload_draw_swap",	"Texture upload, draw & buffer swap");
+
+	addChild(uploadCall);
+	addChild(uploadAndDraw);
+
+	static const struct
+	{
+		const char*		name;
+		const char*		nameLower;
+		UploadFunction	func;
+	} uploadFunctions[] =
+	{
+		{ "texImage2D",		"teximage2d",		UPLOAD_TEXIMAGE2D },
+		{ "texSubImage2D",	"texsubimage2d",	UPLOAD_TEXSUBIMAGE2D }
+	};
+
+	static const struct
+	{
+		const char*	name;
+		deUint32	format;
+		deUint32	type;
+	} textureCombinations[] =
+	{
+		{ "rgb_ubyte",				GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba_ubyte",				GL_RGBA,			GL_UNSIGNED_BYTE },
+		{ "alpha_ubyte",			GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "luminance_ubyte",		GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "luminance-alpha_ubyte",	GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
+		{ "rgb_ushort565",			GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgba_ushort4444",		GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgba_ushort5551",		GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
+	};
+
+	static const struct
+	{
+		int				size;
+		TestCaseGroup*	uploadCallGroup;
+		TestCaseGroup*	uploadAndDrawGroup;
+	} textureSizes[] =
+	{
+		{ 16,	new TestCaseGroup(m_context, "16x16",		"Texture size 16x16"),		new TestCaseGroup(m_context, "16x16",		"Texture size 16x16") },
+		{ 256,	new TestCaseGroup(m_context, "256x256",		"Texture size 256x256"),	new TestCaseGroup(m_context, "256x256",		"Texture size 256x256") },
+		{ 257,	new TestCaseGroup(m_context, "257x257",		"Texture size 257x257"),	new TestCaseGroup(m_context, "257x257",		"Texture size 257x257") },
+		{ 1024,	new TestCaseGroup(m_context, "1024x1024",	"Texture size 1024x1024"),	new TestCaseGroup(m_context, "1024x1024",	"Texture size 1024x1024") },
+		{ 2048,	new TestCaseGroup(m_context, "2048x2048",	"Texture size 2048x2048"),	new TestCaseGroup(m_context, "2048x2048",	"Texture size 2048x2048") },
+	};
+
+#define FOR_EACH(ITERATOR, ARRAY, BODY)	\
+	for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++)	\
+		BODY
+
+	FOR_EACH(uploadFunc,	 uploadFunctions,
+	FOR_EACH(texSize,		 textureSizes,
+	FOR_EACH(texCombination, textureCombinations,
+	{
+		string			caseName	= string("") + uploadFunctions[uploadFunc].nameLower + "_" + textureCombinations[texCombination].name;
+		UploadFunction	function	= uploadFunctions[uploadFunc].func;
+		deUint32		format		= textureCombinations[texCombination].format;
+		deUint32		type		= textureCombinations[texCombination].type;
+		int				size		= textureSizes[texSize].size;
+
+		textureSizes[texSize].uploadCallGroup->addChild		(new TextureUploadCallCase		(m_context, caseName.c_str(), "", function, format, type, size));
+		textureSizes[texSize].uploadAndDrawGroup->addChild	(new TextureUploadAndDrawCase	(m_context, caseName.c_str(), "", function, format, type, size));
+	})));
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(textureSizes); i++)
+	{
+		uploadCall->addChild	(textureSizes[i].uploadCallGroup);
+		uploadAndDraw->addChild	(textureSizes[i].uploadAndDrawGroup);
+	}
+}
+
+
+} // Performance
+} // gles2
+} // deqp
diff --git a/modules/gles2/performance/es2pTextureUploadTests.hpp b/modules/gles2/performance/es2pTextureUploadTests.hpp
new file mode 100644
index 0000000..32aa429
--- /dev/null
+++ b/modules/gles2/performance/es2pTextureUploadTests.hpp
@@ -0,0 +1,59 @@
+#ifndef _ES2PTEXTUREUPLOADTESTS_HPP
+#define _ES2PTEXTUREUPLOADTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture upload performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+#include "gluTexture.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Performance
+{
+
+enum UploadFunction
+{
+	UPLOAD_TEXIMAGE2D,
+	UPLOAD_TEXSUBIMAGE2D,
+
+	UPLOAD_LAST
+};
+
+class TextureUploadTests : public TestCaseGroup
+{
+public:
+					TextureUploadTests			(Context& context);
+					~TextureUploadTests			(void);
+
+	void			init						(void);
+	void			deinit						(void);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES2PTEXTUREUPLOADTESTS_HPP
diff --git a/modules/gles2/scripts/gen-conversions.py b/modules/gles2/scripts/gen-conversions.py
new file mode 100644
index 0000000..c664209
--- /dev/null
+++ b/modules/gles2/scripts/gen-conversions.py
@@ -0,0 +1,330 @@
+import sys
+import random
+import operator
+import itertools
+
+from genutil import *
+
+random.seed(1234567)
+indices = xrange(sys.maxint)
+
+# Constructors:
+#
+# - scalars types
+#   * int <-> float <-> bool (also float(float) etc.)
+#   * to bool: zero means false, others true
+#   * from bool: false==0, true==1
+#   * \todo [petri] float<->int rounding rules?
+# - scalar type from vector
+#   * choose the first component
+# - vectors & matrices
+#   * vector from scalar: broadcast to all components
+#   * matrix from scalar: broadcast scalar to diagonal, other components zero
+#   * vector from vector: copy existing components
+#     + illegal: vector from smaller vector
+#   * mat from mat: copy existing components, other components from identity matrix
+#   * from components: consumed by-component in column-major order, must have same
+#     number of components,
+#     + note: vec4(mat2) valid
+#     \todo [petri] Implement!
+# - notes:
+#   * type conversions are always allowed: mat3(ivec3, bvec3, bool, int, float) is valid!
+#
+# Accessors:
+#
+# - vector components
+#   * .xyzw, .rgba, .stpq
+#   * illegal to mix
+#   * now allowed for scalar types
+#   * legal to chain: vec4.rgba.xyzw.stpq
+#   * illegal to select more than 4 components
+#   * array indexing with [] operator
+#   * can also write!
+# - matrix columns
+#   * [] accessor
+#   * note: mat4[0].x = 1.0; vs mat4[0][0] = 1.0; ??
+#   * out-of-bounds accesses
+#
+# \todo [petri] Accessors!
+#
+# Spec issues:
+#
+# - constructing larger vector from smaller: vec3(vec2) ?
+# - base type and size conversion at same time: vec4(bool), int(vec3) allowed?
+
+def combineVec(comps):
+	res = []
+	for ndx in range(len(comps[0])):
+#		for x in comps:
+#			print x[ndx].toFloat().getScalars() ,
+		scalars = reduce(operator.add, [x[ndx].toFloat().getScalars() for x in comps])
+#		print "->", scalars
+		res.append(Vec.fromScalarList(scalars))
+	return res
+
+def combineIVec(comps):
+	res = []
+	for ndx in range(len(comps[0])):
+		res.append(Vec.fromScalarList(reduce(operator.add, [x[ndx].toInt().getScalars() for x in comps])))
+	return res
+
+def combineBVec(comps):
+	res = []
+	for ndx in range(len(comps[0])):
+		res.append(Vec.fromScalarList(reduce(operator.add, [x[ndx].toBool().getScalars() for x in comps])))
+	return res
+
+def combineMat(numCols, numRows, comps):
+	res = []
+	for ndx in range(len(comps[0])):
+		scalars = reduce(operator.add, [x[ndx].toFloat().getScalars() for x in comps])
+		res.append(Mat(numCols, numRows, scalars))
+	return res
+
+def combineMat2(comps):		return combineMat(2, 2, comps)
+def combineMat3(comps):		return combineMat(3, 3, comps)
+def combineMat4(comps):		return combineMat(4, 4, comps)
+
+# 0 \+ [f*f for f in lst]
+# r = 0 \+ [f in lst -> f*f]
+# r = 0 \+ lst
+
+# Templates.
+
+s_simpleCaseTemplate = """
+case ${{NAME}}
+	values
+	{
+		${{VALUES}}
+	}
+
+	both ""
+		precision mediump float;
+		precision mediump int;
+
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			${{OP}}
+			${OUTPUT}
+		}
+	""
+end
+"""[1:]
+
+s_simpleIllegalCaseTemplate = """
+case ${{NAME}}
+	expect compile_fail
+	values {}
+
+	both ""
+		precision mediump float;
+		precision mediump int;
+
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			${{OP}}
+			${OUTPUT}
+		}
+	""
+end
+"""[1:]
+
+class SimpleCase(ShaderCase):
+	def __init__(self, name, inputs, outputs, op):
+		self.name		= name
+		self.inputs		= inputs
+		self.outputs	= outputs
+		self.op			= op
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"VALUES":	genValues(self.inputs, self.outputs),
+			"OP":		self.op
+		}
+		return fillTemplate(s_simpleCaseTemplate, params)
+
+class ConversionCase(ShaderCase):
+	def __init__(self, inValues, convFunc):
+		outValues = convFunc(inValues)
+		inType	= inValues[0].typeString()
+		outType	= outValues[0].typeString()
+		self.name		= "%s_to_%s" % (inType, outType)
+		self.op			= "out0 = %s(in0);" % outType
+		self.inputs		= [("%s in0" % inType, inValues)]
+		self.outputs	= [("%s out0" % outType, outValues)]
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"VALUES":	genValues(self.inputs, self.outputs),
+			"OP":		self.op
+		}
+		return fillTemplate(s_simpleCaseTemplate, params)
+
+class IllegalConversionCase(ShaderCase):
+	def __init__(self, inValue, outValue):
+		inType	= inValue.typeString()
+		outType	= outValue.typeString()
+		self.name		= "%s_to_%s" % (inType, outType)
+		self.op			= "%s in0 = %s;\n%s out0 = %s(in0);" % (inType, str(inValue), outType, outType)
+		self.inType		= inType
+		self.outType	= outType
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"OP":		self.op
+		}
+		return fillTemplate(s_simpleIllegalCaseTemplate, params)
+
+class CombineCase(ShaderCase):
+	def __init__(self, inComps, combFunc):
+		self.inComps	= inComps
+		self.outValues	= combFunc(inComps)
+		self.outType	= self.outValues[0].typeString()
+		inTypes = [values[0].typeString() for values in inComps]
+		self.name		= "%s_to_%s" % ("_".join(inTypes), self.outType)
+		self.inputs		= [("%s in%s" % (comp[0].typeString(), ndx), comp) for (comp, ndx) in zip(inComps, indices)]
+		self.outputs	= [("%s out0" % self.outType, self.outValues)]
+		self.op			= "out0 = %s(%s);" % (self.outType, ", ".join(["in%d" % x for x in range(len(inComps))]))
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"VALUES":	genValues(self.inputs, self.outputs),
+			"OP":		self.op
+		}
+		return fillTemplate(s_simpleCaseTemplate, params)
+
+# CASE DECLARATIONS
+
+inFloat	= [Scalar(x) for x in [0.0, 1.0, 2.0, 3.5, -0.5, -8.25, -20.125, 36.8125]]
+inInt	= [Scalar(x) for x in [0, 1, 2, 5, 8, 11, -12, -66, -192, 255]]
+inBool	= [Scalar(x) for x in [True, False]]
+
+inVec4	= [Vec4(0.0, 0.5, 0.75, 0.825), Vec4(1.0, 1.25, 1.125, 1.75),
+		   Vec4(-0.5, -2.25, -4.875, 9.0), Vec4(-32.0, 64.0, -51.0, 24.0),
+		   Vec4(-0.75, -1.0/31.0, 1.0/19.0, 1.0/4.0)]
+inVec3	= toVec3(inVec4)
+inVec2	= toVec2(inVec4)
+inIVec4	= toIVec4(inVec4)
+inIVec3	= toIVec3(inVec4)
+inIVec2	= toIVec2(inVec4)
+inBVec4	= [Vec4(True, False, False, True), Vec4(False, False, False, True), Vec4(False, True, False, False), Vec4(True, True, True, True), Vec4(False, False, False, False)]
+inBVec3	= toBVec3(inBVec4)
+inBVec2	= toBVec2(inBVec4)
+
+# \todo [petri] Enable large values when epsilon adapts to the values.
+inMat4	= [Mat4(1.0, 0.0, 0.0, 0.0,  0.0, 1.0, 0.0, 0.0,  0.0, 0.0, 1.0, 0.0,  0.0, 0.0, 0.0, 1.0),
+		   Mat4(6.5, 12.5, -0.75, 9.975,  32.0, 1.0/48.0, -8.425, -6.542,  1.0/8.0, 1.0/16.0, 1.0/32.0, 1.0/64.0,  -6.725, -0.5, -0.0125, 9.975),
+		   #Mat4(128.0, 256.0, -512.0, -1024.0,  2048.0, -4096.0, 8192.0, -8192.0,  192.0, -384.0, 768.0, -1536.0,  8192.0, -8192.0, 6144.0, -6144.0)
+		   ]
+inMat3	= [Mat3(1.0, 0.0, 0.0,  0.0, 1.0, 0.0,  0.0, 0.0, 1.0),
+		   Mat3(6.5, 12.5, -0.75,  32.0, 1.0/32.0, 1.0/64.0,  1.0/8.0, 1.0/16.0, 1.0/32.0),
+		   #Mat3(-18.725, -0.5, -0.0125,  19.975, -0.25, -17.75,  9.25, 65.125, -21.425),
+		   #Mat3(128.0, -4096.0, -8192.0,  192.0, 768.0, -1536.0,  8192.0, 6144.0, -6144.0)
+		   ]
+inMat2	= [Mat2(1.0, 0.0,  0.0, 1.0),
+		   Mat2(6.5, 12.5,  -0.75, 9.975),
+		   Mat2(6.5, 12.5,  -0.75, 9.975),
+		   Mat2(8.0, 16.0,  -24.0, -16.0),
+		   Mat2(1.0/8.0, 1.0/16.0,  1.0/32.0, 1.0/64.0),
+		   Mat2(-18.725, -0.5,  -0.0125, 19.975),
+		   #Mat2(128.0, -4096.0,  192.0, -1536.0),
+		   #Mat2(-1536.0, 8192.0,  6144.0, -6144.0)
+		   ]
+
+def genConversionCases(inValueList, convFuncList):
+	combinations = list(itertools.product(inValueList, convFuncList))
+	return [ConversionCase(inValues, convFunc) for (inValues, convFunc) in combinations]
+
+def genIllegalConversionCases(inValueList, outValueList):
+	inValues	= [x[0] for x in inValueList]
+	outValues	= [x[0] for x in outValueList]
+	combinations = list(itertools.product(inValues, outValues))
+	return [IllegalConversionCase(inVal, outVal) for (inVal, outVal) in combinations]
+
+def shuffleSubLists(outer):
+	return [shuffled(inner) for inner in outer]
+
+# Generate all combinations of CombineCases.
+# inTupleList	a list of tuples of value-lists
+# combFuncList	a list of comb* functions to combine
+def genComponentCases(inCompLists, combFuncList):
+	res = []
+	for comps in inCompLists:
+		maxLen = reduce(max, [len(values) for values in comps])
+		comps = [repeatToLength(values, maxLen) for values in comps]
+		comps = [shuffled(values) for values in comps]
+		for combFunc in combFuncList:
+			res += [CombineCase(comps, combFunc)]
+	return res
+
+allConversionCases = []
+
+# Scalar-to-scalar conversions.
+allConversionCases.append(CaseGroup("scalar_to_scalar", "Scalar to Scalar Conversions",
+	genConversionCases([inFloat, inInt, inBool], [toFloat, toInt, toBool])))
+
+# Scalar-to-vector conversions.
+allConversionCases.append(CaseGroup("scalar_to_vector", "Scalar to Vector Conversions",
+	genConversionCases([inFloat, inInt, inBool], [toVec2, toVec3, toVec4, toIVec2, toIVec3, toIVec4, toBVec2, toBVec3, toBVec4])))
+
+# Vector-to-scalar conversions.
+allConversionCases.append(CaseGroup("vector_to_scalar", "Vector to Scalar Conversions",
+	genConversionCases([inVec2, inVec3, inVec4, inIVec2, inIVec3, inIVec4, inBVec2, inBVec3, inBVec4], [toFloat, toInt, toBool])))
+
+# Illegal vector-to-vector conversions (to longer vec).
+allConversionCases.append(CaseGroup("vector_illegal", "Illegal Vector Conversions",
+	genIllegalConversionCases([inVec2, inIVec2, inBVec2], [inVec3, inIVec3, inBVec3, inVec4, inIVec4, inBVec4]) +\
+	genIllegalConversionCases([inVec3, inIVec3, inBVec3], [inVec4, inIVec4, inBVec4])))
+
+# Vector-to-vector conversions (type conversions, downcasts).
+allConversionCases.append(CaseGroup("vector_to_vector", "Vector to Vector Conversions",
+	genConversionCases([inVec4, inIVec4, inBVec4], [toVec4, toVec3, toVec2, toIVec4, toIVec3, toIVec2, toBVec4, toBVec3, toBVec2]) +\
+	genConversionCases([inVec3, inIVec3, inBVec3], [toVec3, toVec2, toIVec3, toIVec2, toBVec3, toBVec2]) +\
+	genConversionCases([inVec2, inIVec2, inBVec2], [toVec2, toIVec2, toBVec2])))
+
+# Scalar-to-matrix.
+allConversionCases.append(CaseGroup("scalar_to_matrix", "Scalar to Matrix Conversions",
+	genConversionCases([inFloat, inInt, inBool], [toMat4, toMat3, toMat2])))
+
+# Vector-to-matrix.
+#allConversionCases += genConversionCases([inVec4, inIVec4, inBVec4], [toMat4])
+#allConversionCases += genConversionCases([inVec3, inIVec3, inBVec3], [toMat3])
+#allConversionCases += genConversionCases([inVec2, inIVec2, inBVec2], [toMat2])
+
+# Matrix-to-matrix.
+allConversionCases.append(CaseGroup("matrix_to_matrix", "Matrix to Matrix Conversions",
+	genConversionCases([inMat4, inMat3, inMat2], [toMat4, toMat3, toMat2])))
+
+# Vector-from-components, matrix-from-components.
+in2Comp 	= [[inFloat, inFloat], [inInt, inInt], [inBool, inBool], [inFloat, inInt], [inFloat, inBool], [inInt, inBool]]
+in3Comp 	= [[inFloat, inFloat, inFloat], [inInt, inInt, inInt], [inBool, inBool, inBool], [inBool, inFloat, inInt], [inVec2, inBool], [inBVec2, inFloat], [inBVec2, inInt], [inBool, inIVec2]]
+in4Comp 	= [[inVec2, inVec2], [inBVec2, inBVec2], [inFloat, inFloat, inFloat, inFloat], [inInt, inInt, inInt, inInt], [inBool, inBool, inBool, inBool], [inBool, inFloat, inInt, inBool], [inVec2, inIVec2], [inVec2, inBVec2], [inBVec3, inFloat], [inVec3, inFloat], [inInt, inIVec2, inInt], [inBool, inFloat, inIVec2]]
+in9Comp 	= [[inVec3, inVec3, inVec3], [inIVec3, inIVec3, inIVec3], [inVec2, inIVec2, inFloat, inFloat, inInt, inBool, inBool], [inBool, inFloat, inInt, inVec2, inBool, inBVec2, inFloat], [inBool, inBVec2, inInt, inVec4, inBool], [inFloat, inBVec4, inIVec2, inBool, inBool]]
+in16Comp	= [[inVec4, inVec4, inVec4, inVec4], [inIVec4, inIVec4, inIVec4, inIVec4], [inBVec4, inBVec4, inBVec4, inBVec4], [inFloat, inIVec3, inBVec3, inVec4, inIVec2, inFloat, inVec2]]
+
+allConversionCases.append(CaseGroup("vector_combine", "Vector Combine Constructors",
+	genComponentCases(in4Comp, [combineVec, combineIVec, combineBVec]) +\
+	genComponentCases(in3Comp, [combineVec, combineIVec, combineBVec]) +\
+	genComponentCases(in2Comp, [combineVec, combineIVec, combineBVec])))
+
+allConversionCases.append(CaseGroup("matrix_combine", "Matrix Combine Constructors",
+	genComponentCases(in4Comp, [combineMat2]) +\
+	genComponentCases(in9Comp, [combineMat3]) +\
+	genComponentCases(in16Comp, [combineMat4])
+	))
+
+# Main program.
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	writeAllCases("conversions.test", allConversionCases)
diff --git a/modules/gles2/scripts/gen-keywords.py b/modules/gles2/scripts/gen-keywords.py
new file mode 100644
index 0000000..c036011
--- /dev/null
+++ b/modules/gles2/scripts/gen-keywords.py
@@ -0,0 +1,94 @@
+import sys
+from genutil import *
+
+# \todo [arttu 2012-12-20] Current set tests variable names only, add function names, structure names, and field selectors.
+
+# Templates
+
+identifierCaseTemplate = """
+case ${{NAME}}
+	expect compile_fail
+	values {}
+
+	both ""
+		precision mediump float;
+
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			float ${{IDENTIFIER}} = 1.0;
+			${OUTPUT}
+		}
+	""
+end
+"""[1:-1]
+
+# Classes
+
+class IdentifierCase(ShaderCase):
+	def __init__(self, name, identifier):
+		self.name 		= name
+		self.identifier = identifier
+
+	def __str__(self):
+		params = { 	"NAME"			: self.name,
+					"IDENTIFIER"	: self.identifier }
+		return fillTemplate(identifierCaseTemplate, params)
+
+
+# Declarations
+
+KEYWORDS = [
+	"attribute", "const", "uniform", "varying",	"break", "continue", "do", "for", "while",
+	"if", "else", "in", "out", "inout", "float", "int", "void", "bool", "true", "false",
+	"lowp", "mediump", "highp", "precision", "invariant", "discard", "return", "mat2", "mat3",
+	"mat4",	"vec2", "vec3", "vec4", "ivec2", "ivec3", "ivec4", "bvec2", "bvec3", "bvec4",
+	"sampler2D", "samplerCube",	"struct"
+]
+
+RESERVED_KEYWORDS = [
+	"asm", "class", "union", "enum", "typedef", "template", "this", "packed", "goto", "switch",
+	"default", "inline", "noinline", "volatile", "public", "static", "extern", "external",
+	"interface", "flat", "long", "short", "double", "half", "fixed", "unsigned", "superp",
+	"input", "output", "hvec2", "hvec3", "hvec4", "dvec2", "dvec3", "dvec4", "fvec2", "fvec3",
+	"fvec4", "sampler1D", "sampler3D", "sampler1DShadow", "sampler2DShadow", "sampler2DRect",
+	"sampler3DRect", "sampler2DRectShadow", "sizeof", "cast", "namespace", "using"
+]
+
+INVALID_IDENTIFIERS = [
+	("two_underscores_begin", 	"__invalid"),
+	("two_underscores_middle", 	"in__valid"),
+	("two_underscores_end",		"invalid__"),
+	("gl_begin", 				"gl_Invalid"),
+	("digit", 					"0123"),
+	("digit_begin",				"0invalid")
+]
+
+# Keyword usage
+
+keywords 			= []
+reservedKeywords 	= []
+invalidIdentifiers 	= []
+
+for keyword in KEYWORDS:
+	keywords.append(IdentifierCase(keyword, keyword)) 			# Keywords
+
+for keyword in RESERVED_KEYWORDS:
+	reservedKeywords.append(IdentifierCase(keyword, keyword)) 	# Reserved keywords
+
+for (name, identifier) in INVALID_IDENTIFIERS:
+	invalidIdentifiers.append(IdentifierCase(name, identifier)) # Invalid identifiers
+
+keywordCases = [
+	CaseGroup("keywords", 				"Usage of keywords as identifiers.", 			keywords),
+	CaseGroup("reserved_keywords",		"Usage of reserved keywords as identifiers.", 	reservedKeywords),
+	CaseGroup("invalid_identifiers",	"Usage of invalid identifiers.", 				invalidIdentifiers)
+]
+
+# Main program
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	writeAllCases("keywords.test", keywordCases)
diff --git a/modules/gles2/scripts/gen-qualification_order.py b/modules/gles2/scripts/gen-qualification_order.py
new file mode 100644
index 0000000..327eda7
--- /dev/null
+++ b/modules/gles2/scripts/gen-qualification_order.py
@@ -0,0 +1,209 @@
+import sys
+import itertools
+from collections import namedtuple
+from genutil import *
+
+# Templates
+
+declarationTemplate = """
+case ${{NAME}}
+	${{COMPILE_FAIL}}
+	values {}
+
+	vertex ""
+		precision mediump float;
+		attribute highp vec4 dEQP_Position;
+
+		${{VARIABLE_VTX}}
+
+		void main()
+		{
+			x0 = 1.0;
+			gl_Position = dEQP_Position;
+		}
+	""
+
+	fragment ""
+		precision mediump float;
+
+		${{VARIABLE_FRG}}
+
+		void main()
+		{
+			float result = x0 + x1;
+			gl_FragColor = vec4(result, result, result, 1.0);
+		}
+	""
+end
+"""[1:-1]
+
+parameterTemplate = """
+case ${{NAME}}
+	${{COMPILE_FAIL}}
+	values {}
+
+	both ""
+		precision mediump float;
+		${DECLARATIONS}
+
+		float foo0 (${{PARAMETER0}})
+		{
+			return x + 1.0;
+		}
+
+		void foo1 (${{PARAMETER1}})
+		{
+			x = 1.0;
+		}
+
+		float foo2 (${{PARAMETER2}})
+		{
+			return x + 1.0;
+		}
+
+		void main()
+		{
+			${SETUP}
+			float result;
+			foo1(result);
+			float x0 = foo0(1.0);
+			foo2(result);
+			${OUTPUT}
+		}
+	""
+end
+"""[1:-1]
+
+# Classes
+
+class DeclarationCase(ShaderCase):
+	def __init__(self, compileFail, paramList):
+		self.compileFail	= "expect compile_fail" if compileFail else "expect pass"
+		self.name			= ''
+		var0				= ''
+		var1				= ''
+		var2				= ''
+
+		for p in paramList:
+			self.name += p.name
+			if paramList.index(p) != len(paramList)-1:
+				self.name += '_'
+
+			var0 += p.vars[0] + ' '
+			var1 += p.vars[1] + ' '
+			var2 += p.vars[2] + ' '
+
+		var0 += 'float x0;\n'
+		var1 += 'float x1;\n'
+		var2 += 'float x2;'
+
+		self.variableVtx 	= (var0 + var1 + var2).strip()
+		self.variableFrg 	= (var0 + var1).strip()			# Omit 'attribute' in frag shader
+		self.variableVtx 	= self.variableVtx.replace("  ", " ")
+		self.variableFrg 	= self.variableFrg.replace("  ", " ")
+
+	def __str__(self):
+		params = {
+			"NAME"			: self.name,
+			"COMPILE_FAIL"	: self.compileFail,
+			"VARIABLE_VTX"	: self.variableVtx,
+			"VARIABLE_FRG"	: self.variableFrg
+		}
+		return fillTemplate(declarationTemplate, params)
+
+class ParameterCase(ShaderCase):
+	def __init__(self, compileFail, paramList):
+		self.compileFail	= "expect compile_fail" if compileFail else "expect pass"
+		self.name			= ''
+		self.param0			= ''
+		self.param1			= ''
+		self.param2			= ''
+
+		for p in paramList:
+			self.name += p.name
+			if paramList.index(p) != len(paramList)-1:
+				self.name += '_'
+
+			self.param0 += p.vars[0] + ' '
+			self.param1 += p.vars[1] + ' '
+			self.param2 += p.vars[2] + ' '
+
+		self.param0 += 'float x'
+		self.param1 += 'float x'
+		self.param2 += 'float x'
+		self.param0	= self.param0.replace("  ", " ")
+		self.param1	= self.param1.replace("  ", " ")
+		self.param2	= self.param2.replace("  ", " ")
+
+	def __str__(self):
+		params = {
+			"NAME"			: self.name,
+			"COMPILE_FAIL"	: self.compileFail,
+			"PARAMETER0"	: self.param0,
+			"PARAMETER1"	: self.param1,
+			"PARAMETER2"	: self.param2,
+		}
+		return fillTemplate(parameterTemplate, params)
+
+# Declarations
+
+CaseFormat			= namedtuple('CaseFormat', 'name vars')
+
+DECL_INVARIANT		= CaseFormat("invariant",	["invariant", 	"",			""])
+DECL_STORAGE		= CaseFormat("storage", 	["varying", 	"uniform",	"attribute"])
+DECL_PRECISION		= CaseFormat("precision", 	["lowp", 		"mediump",	"mediump"])
+
+PARAM_STORAGE		= CaseFormat("storage",		[ "const", 		"", 				""])
+PARAM_PARAMETER 	= CaseFormat("parameter",	[ "in", 		"out", 				"inout" ])
+PARAM_PRECISION		= CaseFormat("precision",	[ "lowp", 		"mediump",			"mediump" ])
+
+# Order of qualification tests
+
+validDeclarationCases	= []
+invalidDeclarationCases = []
+validParameterCases		= []
+invalidParameterCases	= []
+
+declFormats = [
+	[DECL_INVARIANT, DECL_STORAGE, DECL_PRECISION],
+	[DECL_STORAGE, DECL_PRECISION],
+	[DECL_INVARIANT, DECL_STORAGE]
+]
+
+paramFormats = [
+	[PARAM_STORAGE, PARAM_PARAMETER, PARAM_PRECISION],
+	[PARAM_STORAGE, PARAM_PARAMETER],
+	[PARAM_STORAGE, PARAM_PRECISION],
+	[PARAM_PARAMETER, PARAM_PRECISION]
+]
+
+for f in declFormats:
+	for p in itertools.permutations(f):
+		if list(p) == f:
+			validDeclarationCases.append(DeclarationCase(False, p))		# Correct order
+		else:
+			invalidDeclarationCases.append(DeclarationCase(True, p))	# Incorrect order
+
+for f in paramFormats:
+	for p in itertools.permutations(f):
+		if list(p) == f:
+			validParameterCases.append(ParameterCase(False, p))			# Correct order
+		else:
+			invalidParameterCases.append(ParameterCase(True, p))		# Incorrect order
+
+qualificationOrderCases = [
+	CaseGroup("variables",	"Order of qualification in variable declarations.", children = [
+		CaseGroup("valid", 		"Valid orderings.", 	validDeclarationCases),
+		CaseGroup("invalid",	"Invalid orderings.", 	invalidDeclarationCases)
+	]),
+	CaseGroup("parameters", "Order of qualification in function parameters.", children = [
+		CaseGroup("valid", 		"Valid orderings.", 	validParameterCases),
+		CaseGroup("invalid",	"Invalid orderings.", 	invalidParameterCases)
+	])
+]
+
+# Main program
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	writeAllCases("qualification_order.test", qualificationOrderCases)
diff --git a/modules/gles2/scripts/gen-reserved_operators.py b/modules/gles2/scripts/gen-reserved_operators.py
new file mode 100644
index 0000000..11f33f7
--- /dev/null
+++ b/modules/gles2/scripts/gen-reserved_operators.py
@@ -0,0 +1,80 @@
+import sys
+from genutil import *
+
+# Templates
+
+reservedOperatorCaseTemplate = """
+case operator_${{NAME}}
+	expect compile_fail
+	values {}
+
+	both ""
+		precision mediump float;
+		precision mediump int;
+
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			int value = 100;
+			${{OP}}
+			${OUTPUT}
+		}
+	""
+end
+"""[1:-1]
+
+# Classes
+
+class ReservedOperatorCase(ShaderCase):
+	def __init__(self, op):
+		self.name 		= op.name
+		if op.operator == "~":
+			self.operation = 'value = ~value;'
+		else:
+			self.operation 	= 'value ' + op.operator + ' 1;'
+
+	def __str__(self):
+		params = {
+			"NAME": self.name,
+			"OP"  : self.operation
+		}
+		return fillTemplate(reservedOperatorCaseTemplate, params)
+
+
+class Operator():
+	def __init__(self, operator, name):
+		self.operator 	= operator
+		self.name 		= name
+
+# Declarations
+
+RESERVED_OPERATORS = [
+	Operator("%",	"modulo"),
+	Operator("~",	"bitwise_not"),
+	Operator("<<",	"bitwise_shift_left"),
+	Operator(">>",	"bitwise_shift_right"),
+	Operator("&",	"bitwise_and"),
+	Operator("^",	"bitwise_xor"),
+	Operator("|",	"bitwise_or"),
+	Operator("%=",	"assign_modulo"),
+	Operator("<<=",	"assign_shift_left"),
+	Operator(">>=", "assign_shift_right"),
+	Operator("&=",	"assign_and"),
+	Operator("^=",	"assign_xor"),
+	Operator("|=",	"assign_or")
+]
+
+# Reserved operator usage cases
+
+reservedOperatorCases = []
+
+for operator in RESERVED_OPERATORS:
+	reservedOperatorCases.append(ReservedOperatorCase(operator)) 		# Reserved operators
+
+# Main program
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	writeAllCases("reserved_operators.test", 	reservedOperatorCases)
\ No newline at end of file
diff --git a/modules/gles2/scripts/gen-swizzles.py b/modules/gles2/scripts/gen-swizzles.py
new file mode 100644
index 0000000..e0b0a5d
--- /dev/null
+++ b/modules/gles2/scripts/gen-swizzles.py
@@ -0,0 +1,274 @@
+import sys
+import random
+import operator
+import itertools
+
+from genutil import *
+
+random.seed(1234567)
+indices = xrange(sys.maxint)
+
+# Swizzles:
+# - vector components
+#   * int, float, bool vectors
+#   * .xyzw, .rgba, .stpq
+#   * illegal to mix
+#   * not allowed for scalar types
+#   * legal to chain: vec4.rgba.xyzw.stpq
+#   * illegal to select more than 4 components
+#
+# Subscripts:
+# - array-like indexing with [] operator
+#   * vectors, matrices
+# - read & write
+# - vectors components
+#   * [] accessor
+# - matrix columns
+#   * [] accessor
+#   * note: mat4[0].x = 1.0; vs mat4[0][0] = 1.0; ??
+#   * out-of-bounds accesses
+
+#
+# - vector swizzles
+#   * all vector types (bvec2..4, ivec2..4, vec2..4)
+#   * all precisions (lowp, mediump, highp)
+#   * all component names (xyzw, rgba, stpq)
+#   * broadcast each, reverse, N random
+# - component-masked writes
+#   * all vector types (bvec2..4, ivec2..4, vec2..4)
+#   * all precisions (lowp, mediump, highp)
+#   * all component names (xyzw, rgba, stpq)
+#   * all possible subsets
+#   * all input types (attribute, varying, uniform, tmp)
+#   -> a few hundred cases
+# - concatenated swizzles
+
+#
+VECTOR_TYPES	= [ "vec2", "vec3", "vec4", "ivec2", "ivec3", "ivec4", "bvec2", "bvec3", "bvec4" ]
+PRECISION_TYPES	= [ "lowp", "mediump", "highp" ]
+INPUT_TYPES		= [ "uniform", "varying", "attribute", "tmp" ]
+SWIZZLE_NAMES	= [ "xyzw", "stpq", "rgba" ]
+
+def getDataTypeScalarSize (dt):
+	return {
+		"float":	1,
+		"vec2":		2,
+		"vec3":		3,
+		"vec4":		4,
+		"int":		1,
+		"ivec2":	2,
+		"ivec3":	3,
+		"ivec4":	4,
+		"bool":		1,
+		"bvec2":	2,
+		"bvec3":	3,
+		"bvec4":	4,
+		"mat2":		4,
+		"mat3":		9,
+		"mat4":		16
+	}[dt]
+
+if False:
+	class Combinations:
+		def __init__(self, *args):
+			self.lists				= list(args)
+			self.numLists			= len(args)
+			self.numCombinations	= reduce(operator.mul, map(len, self.lists), 1)
+			print self.lists
+			print self.numCombinations
+
+		def iterate(self):
+			return [tuple(map(lambda x: x[0], self.lists))]
+
+	combinations = Combinations(INPUT_TYPES, VECTOR_TYPES, PRECISION_TYPES)
+	print combinations.iterate()
+	for (inputType, dataType, precision) in combinations.iterate():
+		scalarSize = getDataTypeScalarSize(dataType)
+		print inputType, precision, dataType
+
+def getSwizzlesForWidth(width):
+	if (width == 2):
+		return [(0,), (0,0), (0,1), (1,0), (1,0,1), (0,1,0,0), (1,1,1,1)]
+	elif (width == 3):
+		return [(0,), (2,), (0,2), (2,2), (0,1,2), (2,1,0), (0,0,0), (2,2,2), (2,2,1), (1,0,1), (0,2,0), (0,1,1,0), (2,2,2,2)]
+	elif (width == 4):
+		return [(0,), (3,), (3,0), (3,2), (3,3,3), (1,1,3), (3,2,1), (0,1,2,3), (3,2,1,0), (0,0,0,0), (1,1,1,1), (3,3,3,3), (3,2,2,3), (3,3,3,1), (0,1,0,0), (2,2,3,2)]
+	else:
+		assert False
+
+# Templates.
+
+s_swizzleCaseTemplate = """
+case ${{NAME}}
+	values
+	{
+		${{VALUES}}
+	}
+
+	both ""
+		precision mediump float;
+
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			${{OP}}
+			${OUTPUT}
+		}
+	""
+end
+"""[1:]
+
+s_simpleIllegalCaseTemplate = """
+case ${{NAME}}
+	expect compile_fail
+	values {}
+
+	both ""
+		precision mediump float;
+		precision mediump int;
+
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			${{OP}}
+			${OUTPUT}
+		}
+	""
+end
+"""[1:]
+
+class SwizzleCase(ShaderCase):
+	def __init__(self, name, precision, dataType, swizzle, inputs, outputs):
+		self.name		= name
+		self.precision	= precision
+		self.dataType	= dataType
+		self.swizzle	= swizzle
+		self.inputs		= inputs
+		self.outputs	= outputs
+		self.op			= "out0 = in0.%s;" % swizzle
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"VALUES":	genValues(self.inputs, self.outputs),
+			"OP":		self.op
+		}
+		return fillTemplate(s_swizzleCaseTemplate, params)
+
+# CASE DECLARATIONS
+
+inFloat	= [Scalar(x) for x in [0.0, 1.0, 2.0, 3.5, -0.5, -20.125, 36.8125]]
+inInt	= [Scalar(x) for x in [0, 1, 2, 5, 8, 11, -12, -66, -192, 255]]
+inBool	= [Scalar(x) for x in [True, False]]
+
+inVec4	= [Vec4(0.0, 0.5, 0.75, 0.825), Vec4(1.0, 1.25, 1.125, 1.75),
+		   Vec4(-0.5, -2.25, -4.875, 9.0), Vec4(-32.0, 64.0, -51.0, 24.0),
+		   Vec4(-0.75, -1.0/31.0, 1.0/19.0, 1.0/4.0)]
+inVec3	= toVec3(inVec4)
+inVec2	= toVec2(inVec4)
+inIVec4	= toIVec4(inVec4)
+inIVec3	= toIVec3(inVec4)
+inIVec2	= toIVec2(inVec4)
+inBVec4	= [Vec4(True, False, False, True), Vec4(False, False, False, True), Vec4(False, True, False, False), Vec4(True, True, True, True), Vec4(False, False, False, False)]
+inBVec3	= toBVec3(inBVec4)
+inBVec2	= toBVec2(inBVec4)
+
+# \todo [petri] Enable large values when epsilon adapts to the values.
+inMat4	= [Mat4(1.0, 0.0, 0.0, 0.0,  0.0, 1.0, 0.0, 0.0,  0.0, 0.0, 1.0, 0.0,  0.0, 0.0, 0.0, 1.0),
+		   Mat4(6.5, 12.5, -0.75, 9.975,  32.0, 1.0/48.0, -8.425, -6.542,  1.0/8.0, 1.0/16.0, 1.0/32.0, 1.0/64.0,  -6.725, -0.5, -0.0125, 9.975),
+		   #Mat4(128.0, 256.0, -512.0, -1024.0,  2048.0, -4096.0, 8192.0, -8192.0,  192.0, -384.0, 768.0, -1536.0,  8192.0, -8192.0, 6144.0, -6144.0)
+		   ]
+inMat3	= [Mat3(1.0, 0.0, 0.0,  0.0, 1.0, 0.0,  0.0, 0.0, 1.0),
+		   Mat3(6.5, 12.5, -0.75,  32.0, 1.0/32.0, 1.0/64.0,  1.0/8.0, 1.0/16.0, 1.0/32.0),
+		   #Mat3(-18.725, -0.5, -0.0125,  19.975, -0.25, -17.75,  9.25, 65.125, -21.425),
+		   #Mat3(128.0, -4096.0, -8192.0,  192.0, 768.0, -1536.0,  8192.0, 6144.0, -6144.0)
+		   ]
+inMat2	= [Mat2(1.0, 0.0,  0.0, 1.0),
+		   Mat2(6.5, 12.5,  -0.75, 9.975),
+		   Mat2(6.5, 12.5,  -0.75, 9.975),
+		   Mat2(8.0, 16.0,  -24.0, -16.0),
+		   Mat2(1.0/8.0, 1.0/16.0,  1.0/32.0, 1.0/64.0),
+		   Mat2(-18.725, -0.5,  -0.0125, 19.975),
+		   #Mat2(128.0, -4096.0,  192.0, -1536.0),
+		   #Mat2(-1536.0, 8192.0,  6144.0, -6144.0)
+		   ]
+
+INPUTS = {
+	"float":	inFloat,
+	"vec2":		inVec2,
+	"vec3":		inVec3,
+	"vec4":		inVec4,
+	"int":		inInt,
+	"ivec2":	inIVec2,
+	"ivec3":	inIVec3,
+	"ivec4":	inIVec4,
+	"bool":		inBool,
+	"bvec2":	inBVec2,
+	"bvec3":	inBVec3,
+	"bvec4":	inBVec4,
+	"mat2":		inMat2,
+	"mat3":		inMat3,
+	"mat4":		inMat4
+}
+
+def genConversionCases(inValueList, convFuncList):
+	combinations = list(itertools.product(inValueList, convFuncList))
+	return [ConversionCase(inValues, convFunc) for (inValues, convFunc) in combinations]
+
+allCases = []
+
+# Vector swizzles.
+
+vectorSwizzleCases = []
+
+# \todo [petri] Uses fixed precision.
+for dataType in VECTOR_TYPES:
+	scalarSize = getDataTypeScalarSize(dataType)
+	precision = "mediump"
+	for swizzleComponents in SWIZZLE_NAMES:
+		for swizzleIndices in getSwizzlesForWidth(scalarSize):
+			swizzle = "".join(map(lambda x: swizzleComponents[x], swizzleIndices))
+			#print "%s %s .%s" % (precision, dataType, swizzle)
+			caseName = "%s_%s_%s" % (precision, dataType, swizzle)
+			inputs = INPUTS[dataType]
+			outputs = map(lambda x: x.swizzle(swizzleIndices), inputs)
+			outType = outputs[0].typeString()
+			vectorSwizzleCases.append(SwizzleCase(caseName, precision, dataType, swizzle, [("%s in0" % dataType, inputs)], [("%s out0" % outType, outputs)]))
+
+# ??
+#for dataType in VECTOR_TYPES:
+#	scalarSize = getDataTypeScalarSize(dataType)
+#	for precision in PRECISION_TYPES:
+#		for swizzleIndices in getSwizzlesForWidth(scalarSize):
+#			swizzle = "".join(map(lambda x: "xyzw"[x], swizzleIndices))
+#			#print "%s %s .%s" % (precision, dataType, swizzle)
+#			caseName = "%s_%s_%s" % (precision, dataType, swizzle)
+#			inputs = INPUTS[dataType]
+#			outputs = map(lambda x: x.swizzle(swizzleIndices), inputs)
+#			vectorSwizzleCases.append(SwizzleCase(caseName, precision, dataType, swizzle, [("in0", inputs)], [("out0", outputs)]))
+
+allCases.append(CaseGroup("vector_swizzles", "Vector Swizzles", vectorSwizzleCases))
+
+# Swizzles:
+# - vector components
+#   * int, float, bool vectors
+#   * .xyzw, .rgba, .stpq
+#   * illegal to mix
+#   * not allowed for scalar types
+#   * legal to chain: vec4.rgba.xyzw.stpq
+#   * illegal to select more than 4 components
+
+# TODO: precisions!!
+
+#allCases.append(CaseGroup("vector_swizzles", "Vector Swizzles",
+#	genSwizzleCase([inVec2, inVec3, inVec4],
+
+# Main program.
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	writeAllCases("swizzles.test", allCases)
diff --git a/modules/gles2/scripts/genutil.py b/modules/gles2/scripts/genutil.py
new file mode 100644
index 0000000..9fb97fb
--- /dev/null
+++ b/modules/gles2/scripts/genutil.py
@@ -0,0 +1,650 @@
+import re
+import math
+import random
+
+PREAMBLE = """
+# WARNING: This file is auto-generated. Do NOT modify it manually, but rather
+# modify the generating script file. Otherwise changes will be lost!
+"""[1:]
+
+class CaseGroup:
+	def __init__(self, name, description, children):
+		self.name			= name
+		self.description	= description
+		self.children		= children
+
+class ShaderCase:
+	def __init__(self):
+		pass
+
+g_processedCases = {}
+
+def indentTextBlock(text, indent):
+	indentStr = indent * "\t"
+	lines = text.split("\n")
+	lines = [indentStr + line for line in lines]
+	lines = [ ["", line][line.strip() != ""] for line in lines]
+	return "\n".join(lines)
+
+def writeCase(f, case, indent, prefix):
+	print "\t%s" % (prefix + case.name)
+	if isinstance(case, CaseGroup):
+		f.write(indentTextBlock('group %s "%s"\n\n' % (case.name, case.description), indent))
+		for child in case.children:
+			writeCase(f, child, indent + 1, prefix + case.name + ".")
+		f.write(indentTextBlock("\nend # %s\n" % case.name, indent))
+	else:
+		# \todo [petri] Fix hack.
+		fullPath = prefix + case.name
+		assert (fullPath not in g_processedCases)
+		g_processedCases[fullPath] = None
+		f.write(indentTextBlock(str(case) + "\n", indent))
+
+def writeAllCases(fileName, caseList):
+	# Write all cases to file.
+	print "  %s.." % fileName
+	f = file(fileName, "wb")
+	f.write(PREAMBLE + "\n")
+	for case in caseList:
+		writeCase(f, case, 0, "")
+	f.close()
+
+	print "done! (%d cases written)" % len(g_processedCases)
+
+# Template operations.
+
+def genValues(inputs, outputs):
+	res = []
+	for (name, values) in inputs:
+		res.append("input %s = [ %s ];" % (name, " | ".join([str(v) for v in values]).lower()))
+	for (name, values) in outputs:
+		res.append("output %s = [ %s ];" % (name, " | ".join([str(v) for v in values]).lower()))
+	return ("\n".join(res))
+
+def fillTemplate(template, params):
+	s = template
+
+	for (key, value) in params.items():
+		m = re.search(r"^(\s*)\$\{\{%s\}\}$" % key, s, re.M)
+		if m is not None:
+			start = m.start(0)
+			end = m.end(0)
+			ws = m.group(1)
+			if value is not None:
+				repl = "\n".join(["%s%s" % (ws, line) for line in value.split("\n")])
+				s = s[:start] + repl + s[end:]
+			else:
+				s = s[:start] + s[end+1:] # drop the whole line
+		else:
+			s = s.replace("${{%s}}" % key, value)
+	return s
+
+# Return shuffled version of list
+def shuffled(lst):
+	tmp = lst[:]
+	random.shuffle(tmp)
+	return tmp
+
+def repeatToLength(lst, toLength):
+	return (toLength / len(lst)) * lst + lst[: toLength % len(lst)]
+
+# Helpers to convert a list of Scalar/Vec values into another type.
+
+def toFloat(lst):	return [Scalar(float(v.x)) for v in lst]
+def toInt(lst):		return [Scalar(int(v.x)) for v in lst]
+def toBool(lst):	return [Scalar(bool(v.x)) for v in lst]
+def toVec4(lst):	return [v.toFloat().toVec4() for v in lst]
+def toVec3(lst):	return [v.toFloat().toVec3() for v in lst]
+def toVec2(lst):	return [v.toFloat().toVec2() for v in lst]
+def toIVec4(lst):	return [v.toInt().toVec4() for v in lst]
+def toIVec3(lst):	return [v.toInt().toVec3() for v in lst]
+def toIVec2(lst):	return [v.toInt().toVec2() for v in lst]
+def toBVec4(lst):	return [v.toBool().toVec4() for v in lst]
+def toBVec3(lst):	return [v.toBool().toVec3() for v in lst]
+def toBVec2(lst):	return [v.toBool().toVec2() for v in lst]
+def toMat2(lst):	return [v.toMat2() for v in lst]
+def toMat3(lst):	return [v.toMat3() for v in lst]
+def toMat4(lst):	return [v.toMat4() for v in lst]
+
+# Random value generation.
+
+class GenRandom:
+	def __init__(self):
+		pass
+
+	def uniformVec4(self, count, mn, mx):
+		ret = [Vec4(random.uniform(mn, mx), random.uniform(mn, mx), random.uniform(mn, mx), random.uniform(mn, mx)) for x in xrange(count)]
+		ret[0].x = mn
+		ret[1].x = mx
+		ret[2].x = (mn + mx) * 0.5
+		return ret
+
+	def uniformBVec4(self, count):
+		ret = [Vec4(random.random() >= 0.5, random.random() >= 0.5, random.random() >= 0.5, random.random() >= 0.5) for x in xrange(count)]
+		ret[0].x = True
+		ret[1].x = False
+		return ret
+
+#	def uniform(self,
+
+# Math operating on Scalar/Vector types.
+
+def glslSign(a):			return 0.0 if (a == 0) else +1.0 if (a > 0.0) else -1.0
+def glslMod(x, y):			return x - y*math.floor(x/y)
+def glslClamp(x, mn, mx):	return mn if (x < mn) else mx if (x > mx) else x
+
+class GenMath:
+	@staticmethod
+	def unary(func):	return lambda val: val.applyUnary(func)
+
+	@staticmethod
+	def binary(func):	return lambda a, b: (b.expandVec(a)).applyBinary(func, a.expandVec(b))
+
+	@staticmethod
+	def frac(val):		return val.applyUnary(lambda x: x - math.floor(x))
+
+	@staticmethod
+	def exp2(val):		return val.applyUnary(lambda x: math.pow(2.0, x))
+
+	@staticmethod
+	def log2(val):		return val.applyUnary(lambda x: math.log(x, 2.0))
+
+	@staticmethod
+	def rsq(val):		return val.applyUnary(lambda x: 1.0 / math.sqrt(x))
+
+	@staticmethod
+	def sign(val):		return val.applyUnary(glslSign)
+
+	@staticmethod
+	def isEqual(a, b):	return Scalar(a.isEqual(b))
+
+	@staticmethod
+	def isNotEqual(a, b):	return Scalar(not a.isEqual(b))
+
+	@staticmethod
+	def step(a, b):		return (b.expandVec(a)).applyBinary(lambda edge, x: [1.0, 0.0][x < edge], a.expandVec(b))
+
+	@staticmethod
+	def length(a):		return a.length()
+
+	@staticmethod
+	def distance(a, b):	return a.distance(b)
+
+	@staticmethod
+	def dot(a, b):		return a.dot(b)
+
+	@staticmethod
+	def cross(a, b):	return a.cross(b)
+
+	@staticmethod
+	def normalize(a):	return a.normalize()
+
+	@staticmethod
+	def boolAny(a):		return a.boolAny()
+
+	@staticmethod
+	def boolAll(a):		return a.boolAll()
+
+	@staticmethod
+	def boolNot(a):		return a.boolNot()
+
+# ..
+
+class Scalar:
+	def __init__(self, x):
+		self.x = x
+
+	def applyUnary(self, func):			return Scalar(func(self.x))
+	def applyBinary(self, func, other):	return Scalar(func(self.x, other.x))
+
+	def isEqual(self, other):	assert isinstance(other, Scalar); return (self.x == other.x)
+
+	def expandVec(self, val):	return val
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.x)
+	def toVec3(self):			return Vec3(self.x, self.x, self.x)
+	def toVec4(self):			return Vec4(self.x, self.x, self.x, self.x)
+	def toMat2(self):			return self.toVec2().toMat2()
+	def toMat3(self):			return self.toVec3().toMat3()
+	def toMat4(self):			return self.toVec4().toMat4()
+
+	def toFloat(self):			return Scalar(float(self.x))
+	def toInt(self):			return Scalar(int(self.x))
+	def toBool(self):			return Scalar(bool(self.x))
+
+	def getNumScalars(self):	return 1
+	def getScalars(self):		return [self.x]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bool"
+		elif isinstance(self.x, int):
+			return "int"
+		elif isinstance(self.x, float):
+			return "float"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ""
+
+	def __str__(self):
+		return "%s" % self.x
+
+	def length(self):
+		return Scalar(abs(self.x))
+
+	def distance(self, v):
+		assert isinstance(v, Scalar)
+		return Scalar(abs(self.x - v.x))
+
+	def dot(self, v):
+		assert isinstance(v, Scalar)
+		return Scalar(self.x * v.x)
+
+	def normalize(self):
+		return Scalar(glslSign(self.x))
+
+	def __neg__(self):
+		return Scalar(-self.x)
+
+	def __add__(self, val):
+		assert isinstance(val, Scalar)
+		return Scalar(self.x + val.x)
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			return Scalar(self.x * val.x)
+		elif isinstance(val, Vec2):
+			return Vec2(self.x * val.x, self.x * val.y)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x * val.x, self.x * val.y, self.x * val.z)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x * val.x, self.x * val.y, self.x * val.z, self.x * val.w)
+		else:
+			assert False
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Scalar(self.x / val.x)
+		elif isinstance(val, Vec2):
+			return Vec2(self.x / val.x, self.x / val.y)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x / val.x, self.x / val.y, self.x / val.z)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x / val.x, self.x / val.y, self.x / val.z, self.x / val.w)
+		else:
+			assert False
+
+class Vec:
+	@staticmethod
+	def fromScalarList(lst):
+		assert (len(lst) >= 1 and len(lst) <= 4)
+		if (len(lst) == 1):		return Scalar(lst[0])
+		elif (len(lst) == 2):	return Vec2(lst[0], lst[1])
+		elif (len(lst) == 3):	return Vec3(lst[0], lst[1], lst[2])
+		else:					return Vec4(lst[0], lst[1], lst[2], lst[3])
+
+	def isEqual(self, other):
+		assert isinstance(other, Vec);
+		return (self.getScalars() == other.getScalars())
+
+	def length(self):
+		return Scalar(math.sqrt(self.dot(self).x))
+
+	def normalize(self):
+		return self * Scalar(1.0 / self.length().x)
+
+	def swizzle(self, indexList):
+		inScalars = self.getScalars()
+		outScalars = map(lambda ndx: inScalars[ndx], indexList)
+		return Vec.fromScalarList(outScalars)
+
+	def __init__(self):
+		pass
+
+class Vec2(Vec):
+	def __init__(self, x, y):
+		assert(x.__class__ == y.__class__)
+		self.x = x
+		self.y = y
+
+	def applyUnary(self, func):			return Vec2(func(self.x), func(self.y))
+	def applyBinary(self, func, other):	return Vec2(func(self.x, other.x), func(self.y, other.y))
+
+	def expandVec(self, val):	return val.toVec2()
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.y)
+	def toVec3(self):			return Vec3(self.x, self.y, 0.0)
+	def toVec4(self):			return Vec4(self.x, self.y, 0.0, 0.0)
+	def toMat2(self):			return Mat2(float(self.x), 0.0, 0.0, float(self.y));
+
+	def toFloat(self):			return Vec2(float(self.x), float(self.y))
+	def toInt(self):			return Vec2(int(self.x), int(self.y))
+	def toBool(self):			return Vec2(bool(self.x), bool(self.y))
+
+	def getNumScalars(self):	return 2
+	def getScalars(self):		return [self.x, self.y]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bvec2"
+		elif isinstance(self.x, int):
+			return "ivec2"
+		elif isinstance(self.x, float):
+			return "vec2"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ".xyxy"
+
+	def __str__(self):
+		if isinstance(self.x, bool):
+			return "bvec2(%s, %s)" % (str(self.x).lower(), str(self.y).lower())
+		elif isinstance(self.x, int):
+			return "ivec2(%i, %i)" % (self.x, self.y)
+		elif isinstance(self.x, float):
+			return "vec2(%s, %s)" % (self.x, self.y)
+		else:
+			assert False
+
+	def distance(self, v):
+		assert isinstance(v, Vec2)
+		return (self - v).length()
+
+	def dot(self, v):
+		assert isinstance(v, Vec2)
+		return Scalar(self.x*v.x + self.y*v.y)
+
+	def __neg__(self):
+		return Vec2(-self.x, -self.y)
+
+	def __add__(self, val):
+		if isinstance(val, Scalar):
+			return Vec2(self.x + val, self.y + val)
+		elif isinstance(val, Vec2):
+			return Vec2(self.x + val.x, self.y + val.y)
+		else:
+			assert False
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			val = val.toVec2()
+		assert isinstance(val, Vec2)
+		return Vec2(self.x * val.x, self.y * val.y)
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Vec2(self.x / val.x, self.y / val.x)
+		else:
+			assert isinstance(val, Vec2)
+			return Vec2(self.x / val.x, self.y / val.y)
+
+	def boolAny(self):	return Scalar(self.x or self.y)
+	def boolAll(self):	return Scalar(self.x and self.y)
+	def boolNot(self):	return Vec2(not self.x, not self.y)
+
+class Vec3(Vec):
+	def __init__(self, x, y, z):
+		assert((x.__class__ == y.__class__) and (x.__class__ == z.__class__))
+		self.x = x
+		self.y = y
+		self.z = z
+
+	def applyUnary(self, func):			return Vec3(func(self.x), func(self.y), func(self.z))
+	def applyBinary(self, func, other):	return Vec3(func(self.x, other.x), func(self.y, other.y), func(self.z, other.z))
+
+	def expandVec(self, val):	return val.toVec3()
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.y)
+	def toVec3(self):			return Vec3(self.x, self.y, self.z)
+	def toVec4(self):			return Vec4(self.x, self.y, self.z, 0.0)
+	def toMat3(self):			return Mat3(float(self.x), 0.0, 0.0,  0.0, float(self.y), 0.0,  0.0, 0.0, float(self.z));
+
+	def toFloat(self):			return Vec3(float(self.x), float(self.y), float(self.z))
+	def toInt(self):			return Vec3(int(self.x), int(self.y), int(self.z))
+	def toBool(self):			return Vec3(bool(self.x), bool(self.y), bool(self.z))
+
+	def getNumScalars(self):	return 3
+	def getScalars(self):		return [self.x, self.y, self.z]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bvec3"
+		elif isinstance(self.x, int):
+			return "ivec3"
+		elif isinstance(self.x, float):
+			return "vec3"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ".xyzx"
+
+	def __str__(self):
+		if isinstance(self.x, bool):
+			return "bvec3(%s, %s, %s)" % (str(self.x).lower(), str(self.y).lower(), str(self.z).lower())
+		elif isinstance(self.x, int):
+			return "ivec3(%i, %i, %i)" % (self.x, self.y, self.z)
+		elif isinstance(self.x, float):
+			return "vec3(%s, %s, %s)" % (self.x, self.y, self.z)
+		else:
+			assert False
+
+	def distance(self, v):
+		assert isinstance(v, Vec3)
+		return (self - v).length()
+
+	def dot(self, v):
+		assert isinstance(v, Vec3)
+		return Scalar(self.x*v.x + self.y*v.y + self.z*v.z)
+
+	def cross(self, v):
+		assert isinstance(v, Vec3)
+		return Vec3(self.y*v.z - v.y*self.z,
+					self.z*v.x - v.z*self.x,
+					self.x*v.y - v.x*self.y)
+
+	def __neg__(self):
+		return Vec3(-self.x, -self.y, -self.z)
+
+	def __add__(self, val):
+		if isinstance(val, Scalar):
+			return Vec3(self.x + val, self.y + val)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x + val.x, self.y + val.y, self.z + val.z)
+		else:
+			assert False
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			val = val.toVec3()
+		assert isinstance(val, Vec3)
+		return Vec3(self.x * val.x, self.y * val.y, self.z * val.z)
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Vec3(self.x / val.x, self.y / val.x, self.z / val.x)
+		else:
+			assert False
+
+	def boolAny(self):	return Scalar(self.x or self.y or self.z)
+	def boolAll(self):	return Scalar(self.x and self.y and self.z)
+	def boolNot(self):	return Vec3(not self.x, not self.y, not self.z)
+
+class Vec4(Vec):
+	def __init__(self, x, y, z, w):
+		assert((x.__class__ == y.__class__) and (x.__class__ == z.__class__) and (x.__class__ == w.__class__))
+		self.x = x
+		self.y = y
+		self.z = z
+		self.w = w
+
+	def applyUnary(self, func):			return Vec4(func(self.x), func(self.y), func(self.z), func(self.w))
+	def applyBinary(self, func, other):	return Vec4(func(self.x, other.x), func(self.y, other.y), func(self.z, other.z), func(self.w, other.w))
+
+	def expandVec(self, val):	return val.toVec4()
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.y)
+	def toVec3(self):			return Vec3(self.x, self.y, self.z)
+	def toVec4(self):			return Vec4(self.x, self.y, self.z, self.w)
+	def toMat2(self):			return Mat2(float(self.x), float(self.y), float(self.z), float(self.w))
+	def toMat4(self):			return Mat4(float(self.x), 0.0, 0.0, 0.0,  0.0, float(self.y), 0.0, 0.0,  0.0, 0.0, float(self.z), 0.0,  0.0, 0.0, 0.0, float(self.w));
+
+	def toFloat(self):			return Vec4(float(self.x), float(self.y), float(self.z), float(self.w))
+	def toInt(self):			return Vec4(int(self.x), int(self.y), int(self.z), int(self.w))
+	def toBool(self):			return Vec4(bool(self.x), bool(self.y), bool(self.z), bool(self.w))
+
+	def getNumScalars(self):	return 4
+	def getScalars(self):		return [self.x, self.y, self.z, self.w]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bvec4"
+		elif isinstance(self.x, int):
+			return "ivec4"
+		elif isinstance(self.x, float):
+			return "vec4"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ""
+
+	def __str__(self):
+		if isinstance(self.x, bool):
+			return "bvec4(%s, %s, %s, %s)" % (str(self.x).lower(), str(self.y).lower(), str(self.z).lower(), str(self.w).lower())
+		elif isinstance(self.x, int):
+			return "ivec4(%i, %i, %i, %i)" % (self.x, self.y, self.z, self.w)
+		elif isinstance(self.x, float):
+			return "vec4(%s, %s, %s, %s)" % (self.x, self.y, self.z, self.w)
+		else:
+			assert False
+
+	def distance(self, v):
+		assert isinstance(v, Vec4)
+		return (self - v).length()
+
+	def dot(self, v):
+		assert isinstance(v, Vec4)
+		return Scalar(self.x*v.x + self.y*v.y + self.z*v.z + self.w*v.w)
+
+	def __neg__(self):
+		return Vec4(-self.x, -self.y, -self.z, -self.w)
+
+	def __add__(self, val):
+		if isinstance(val, Scalar):
+			return Vec3(self.x + val, self.y + val)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x + val.x, self.y + val.y, self.z + val.z, self.w + val.w)
+		else:
+			assert False
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			val = val.toVec4()
+		assert isinstance(val, Vec4)
+		return Vec4(self.x * val.x, self.y * val.y, self.z * val.z, self.w * val.w)
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Vec4(self.x / val.x, self.y / val.x, self.z / val.x, self.w / val.x)
+		else:
+			assert False
+
+	def boolAny(self):	return Scalar(self.x or self.y or self.z or self.w)
+	def boolAll(self):	return Scalar(self.x and self.y and self.z and self.w)
+	def boolNot(self):	return Vec4(not self.x, not self.y, not self.z, not self.w)
+
+# \note Column-major storage.
+class Mat:
+	def __init__ (self, numCols, numRows, scalars):
+		assert len(scalars) == numRows*numCols
+		self.numCols	= numCols
+		self.numRows	= numRows
+		self.scalars	= scalars
+
+	@staticmethod
+	def identity (numCols, numRows):
+		scalars = []
+		for col in range(0, numCols):
+			for row in range(0, numRows):
+				scalars.append(1.0 if col == row else 0.0)
+		return Mat(numCols, numRows, scalars)
+
+	def get (self, colNdx, rowNdx):
+		assert 0 <= colNdx and colNdx < self.numCols
+		assert 0 <= rowNdx and rowNdx < self.numRows
+		return self.scalars[colNdx*self.numRows + rowNdx]
+
+	def set (self, colNdx, rowNdx, scalar):
+		assert 0 <= colNdx and colNdx < self.numCols
+		assert 0 <= rowNdx and rowNdx < self.numRows
+		self.scalars[colNdx*self.numRows + rowNdx] = scalar
+
+	def toMatrix (self, numCols, numRows):
+		res = Mat.identity(numCols, numRows)
+		for col in range(0, min(self.numCols, numCols)):
+			for row in range(0, min(self.numRows, numRows)):
+				res.set(col, row, self.get(col, row))
+		return res
+
+	def toMat2 (self):		return self.toMatrix(2, 2)
+	def toMat2x3 (self):	return self.toMatrix(2, 3)
+	def toMat2x4 (self):	return self.toMatrix(2, 4)
+	def toMat3x2 (self):	return self.toMatrix(3, 2)
+	def toMat3 (self):		return self.toMatrix(3, 3)
+	def toMat3x4 (self):	return self.toMatrix(3, 4)
+	def toMat4x2 (self):	return self.toMatrix(4, 2)
+	def toMat4x3 (self):	return self.toMatrix(4, 3)
+	def toMat4 (self):		return self.toMatrix(4, 4)
+
+	def typeString(self):
+		if self.numRows == self.numCols:
+			return "mat%d" % self.numRows
+		else:
+			return "mat%dx%d" % (self.numCols, self.numRows)
+
+	def __str__(self):
+		return "%s(%s)" % (self.typeString(), ", ".join([str(s) for s in self.scalars]))
+
+	def isTypeEqual (self, other):
+		return isinstance(other, Mat) and self.numRows == other.numRows and self.numCols == other.numCols
+
+	def isEqual(self, other):
+		assert self.isTypeEqual(other)
+		return (self.scalars == other.scalars)
+
+	def compMul(self, val):
+		assert self.isTypeEqual(val)
+		return Mat(self.numRows, self.numCols, [self.scalars(i) * val.scalars(i) for i in range(self.numRows*self.numCols)])
+
+class Mat2(Mat):
+	def __init__(self, m00, m01, m10, m11):
+		Mat.__init__(self, 2, 2, [m00, m10, m01, m11])
+
+class Mat3(Mat):
+	def __init__(self, m00, m01, m02, m10, m11, m12, m20, m21, m22):
+		Mat.__init__(self, 3, 3, [m00, m10, m20,
+								  m01, m11, m21,
+								  m02, m12, m22])
+
+class Mat4(Mat):
+	def __init__(self, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33):
+		Mat.__init__(self, 4, 4, [m00, m10, m20, m30,
+								  m01, m11, m21, m31,
+								  m02, m12, m22, m32,
+								  m03, m13, m23, m33])
diff --git a/modules/gles2/stress/CMakeLists.txt b/modules/gles2/stress/CMakeLists.txt
new file mode 100644
index 0000000..371c6a6
--- /dev/null
+++ b/modules/gles2/stress/CMakeLists.txt
@@ -0,0 +1,19 @@
+# dEQP-GLES2.stress
+
+set(DEQP_GLES2_STRESS_SRCS
+	es2sStressTests.hpp
+	es2sStressTests.cpp
+	es2sMemoryTests.hpp
+	es2sMemoryTests.cpp
+	es2sLongRunningTests.hpp
+	es2sLongRunningTests.cpp
+	es2sSpecialFloatTests.hpp
+	es2sSpecialFloatTests.cpp
+	es2sVertexArrayTests.hpp
+	es2sVertexArrayTests.cpp
+	es2sDrawTests.hpp
+	es2sDrawTests.cpp
+	)
+
+add_library(deqp-gles2-stress STATIC ${DEQP_GLES2_STRESS_SRCS})
+target_link_libraries(deqp-gles2-stress deqp-gl-shared glutil tcutil ${DEQP_GLES2_LIBRARIES})
diff --git a/modules/gles2/stress/es2sDrawTests.cpp b/modules/gles2/stress/es2sDrawTests.cpp
new file mode 100644
index 0000000..b6d8a66
--- /dev/null
+++ b/modules/gles2/stress/es2sDrawTests.cpp
@@ -0,0 +1,456 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Drawing tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2sDrawTests.hpp"
+#include "glsDrawTest.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+
+#include "glwEnums.hpp"
+
+#include <set>
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+namespace
+{
+
+static void genBasicSpec (gls::DrawTestSpec& spec, gls::DrawTestSpec::DrawMethod method)
+{
+	spec.apiType							= glu::ApiType::es(2,0);
+	spec.primitive							= gls::DrawTestSpec::PRIMITIVE_TRIANGLES;
+	spec.primitiveCount						= 5;
+	spec.drawMethod							= method;
+	spec.indexType							= gls::DrawTestSpec::INDEXTYPE_LAST;
+	spec.indexPointerOffset					= 0;
+	spec.indexStorage						= gls::DrawTestSpec::STORAGE_LAST;
+	spec.first								= 0;
+	spec.indexMin							= 0;
+	spec.indexMax							= 0;
+	spec.instanceCount						= 1;
+
+	spec.attribs.resize(2);
+
+	spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+	spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+	spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+	spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+	spec.attribs[0].componentCount			= 4;
+	spec.attribs[0].offset					= 0;
+	spec.attribs[0].stride					= 0;
+	spec.attribs[0].normalize				= false;
+	spec.attribs[0].instanceDivisor			= 0;
+	spec.attribs[0].useDefaultAttribute		= false;
+
+	spec.attribs[1].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+	spec.attribs[1].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+	spec.attribs[1].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+	spec.attribs[1].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+	spec.attribs[1].componentCount			= 2;
+	spec.attribs[1].offset					= 0;
+	spec.attribs[1].stride					= 0;
+	spec.attribs[1].normalize				= false;
+	spec.attribs[1].instanceDivisor			= 0;
+	spec.attribs[1].useDefaultAttribute		= false;
+}
+
+class IndexGroup : public TestCaseGroup
+{
+public:
+									IndexGroup		(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~IndexGroup		(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+IndexGroup::IndexGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+IndexGroup::~IndexGroup (void)
+{
+}
+
+void IndexGroup::init (void)
+{
+	struct IndexTest
+	{
+		gls::DrawTestSpec::Storage		storage;
+		gls::DrawTestSpec::IndexType	type;
+		bool							aligned;
+		int								offsets[3];
+	};
+
+	const IndexTest tests[] =
+	{
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_BYTE,	true,	{ 0, 1, -1 } },
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_SHORT,	true,	{ 0, 2, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_SHORT,	false,	{ 1, 3, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_BYTE,	true,	{ 0, 1, -1 } },
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_SHORT,	true,	{ 0, 2, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_SHORT,	false,	{ 1, 3, -1 } },
+	};
+
+	gls::DrawTestSpec spec;
+
+	tcu::TestCaseGroup* userPtrGroup			= new tcu::TestCaseGroup(m_testCtx, "user_ptr", "user pointer");
+	tcu::TestCaseGroup* unalignedUserPtrGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_user_ptr", "unaligned user pointer");
+	tcu::TestCaseGroup* bufferGroup				= new tcu::TestCaseGroup(m_testCtx, "buffer", "buffer");
+	tcu::TestCaseGroup* unalignedBufferGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_buffer", "unaligned buffer");
+
+	genBasicSpec(spec, m_method);
+
+	this->addChild(userPtrGroup);
+	this->addChild(unalignedUserPtrGroup);
+	this->addChild(bufferGroup);
+	this->addChild(unalignedBufferGroup);
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(tests); ++testNdx)
+	{
+		const IndexTest&				indexTest	= tests[testNdx];
+		tcu::TestCaseGroup*				group		= (indexTest.storage == gls::DrawTestSpec::STORAGE_USER) ? ((indexTest.aligned) ? (userPtrGroup) : (unalignedUserPtrGroup)) : ((indexTest.aligned) ? (bufferGroup) : (unalignedBufferGroup));
+
+		const std::string				name		= std::string("index_") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
+		const std::string				desc		= std::string("index ") + gls::DrawTestSpec::indexTypeToString(indexTest.type) + " in " + gls::DrawTestSpec::storageToString(indexTest.storage);
+		de::MovePtr<gls::DrawTest>		test		(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str()));
+
+		spec.indexType			= indexTest.type;
+		spec.indexStorage		= indexTest.storage;
+
+		for (int iterationNdx = 0; iterationNdx < DE_LENGTH_OF_ARRAY(indexTest.offsets) && indexTest.offsets[iterationNdx] != -1; ++iterationNdx)
+		{
+			const std::string iterationDesc = std::string("offset ") + de::toString(indexTest.offsets[iterationNdx]);
+			spec.indexPointerOffset	= indexTest.offsets[iterationNdx];
+			test->addIteration(spec, iterationDesc.c_str());
+		}
+
+		if (spec.isCompatibilityTest() == gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET ||
+			spec.isCompatibilityTest() == gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+			group->addChild(test.release());
+	}
+}
+
+class MethodGroup : public TestCaseGroup
+{
+public:
+									MethodGroup			(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~MethodGroup		(void);
+
+	void							init				(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+MethodGroup::MethodGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+MethodGroup::~MethodGroup (void)
+{
+}
+
+void MethodGroup::init (void)
+{
+	const bool indexed = (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS) || (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED) || (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED);
+
+	DE_ASSERT(indexed);
+	DE_UNREF(indexed);
+
+	this->addChild(new IndexGroup(m_context, "indices", "Index tests", m_method));
+}
+
+class RandomGroup : public TestCaseGroup
+{
+public:
+									RandomGroup		(Context& context, const char* name, const char* descr);
+									~RandomGroup	(void);
+
+	void							init			(void);
+};
+
+template <int SIZE>
+struct UniformWeightArray
+{
+	float weights[SIZE];
+
+	UniformWeightArray (void)
+	{
+		for (int i=0; i<SIZE; ++i)
+			weights[i] = 1.0f;
+	}
+};
+
+RandomGroup::RandomGroup (Context& context, const char* name, const char* descr)
+	: TestCaseGroup	(context, name, descr)
+{
+}
+
+RandomGroup::~RandomGroup (void)
+{
+}
+
+void RandomGroup::init (void)
+{
+	const int	numAttempts				= 100;
+
+	const int	attribCounts[]			= { 1, 2, 5 };
+	const float	attribWeights[]			= { 30, 10, 1 };
+	const int	primitiveCounts[]		= { 1, 5, 64 };
+	const float	primitiveCountWeights[]	= { 20, 10, 1 };
+	const int	indexOffsets[]			= { 0, 7, 13 };
+	const float	indexOffsetWeights[]	= { 20, 20, 1 };
+	const int	firsts[]				= { 0, 7, 13 };
+	const float	firstWeights[]			= { 20, 20, 1 };
+	const int	offsets[]				= { 0, 1, 5, 12 };
+	const float	offsetWeights[]			= { 50, 10, 10, 10 };
+	const int	strides[]				= { 0, 7, 16, 17 };
+	const float	strideWeights[]			= { 50, 10, 10, 10 };
+
+	gls::DrawTestSpec::Primitive primitives[] =
+	{
+		gls::DrawTestSpec::PRIMITIVE_POINTS,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINES,
+		gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINE_LOOP
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(primitives)> primitiveWeights;
+
+	gls::DrawTestSpec::DrawMethod drawMethods[] =
+	{
+		gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(drawMethods)> drawMethodWeights;
+
+	gls::DrawTestSpec::IndexType indexTypes[] =
+	{
+		gls::DrawTestSpec::INDEXTYPE_BYTE,
+		gls::DrawTestSpec::INDEXTYPE_SHORT,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(indexTypes)> indexTypeWeights;
+
+	gls::DrawTestSpec::Storage storages[] =
+	{
+		gls::DrawTestSpec::STORAGE_USER,
+		gls::DrawTestSpec::STORAGE_BUFFER,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(storages)> storageWeights;
+
+	gls::DrawTestSpec::InputType inputTypes[] =
+	{
+		gls::DrawTestSpec::INPUTTYPE_FLOAT,
+		gls::DrawTestSpec::INPUTTYPE_FIXED,
+		gls::DrawTestSpec::INPUTTYPE_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_SHORT,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(inputTypes)> inputTypeWeights;
+
+	gls::DrawTestSpec::OutputType outputTypes[] =
+	{
+		gls::DrawTestSpec::OUTPUTTYPE_FLOAT,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC4,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(outputTypes)> outputTypeWeights;
+
+	gls::DrawTestSpec::Usage usages[] =
+	{
+		gls::DrawTestSpec::USAGE_STATIC_DRAW,
+		gls::DrawTestSpec::USAGE_DYNAMIC_DRAW,
+		gls::DrawTestSpec::USAGE_STREAM_DRAW,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(usages)> usageWeights;
+
+	const deUint32 blacklistedCases[]=
+	{
+		3153,	//!< extremely narrow triangle, results depend on sample positions
+	};
+
+	std::set<deUint32>	insertedHashes;
+	size_t				insertedCount = 0;
+
+	for (int ndx = 0; ndx < numAttempts; ++ndx)
+	{
+		de::Random random(0xc551393 + ndx); // random does not depend on previous cases
+
+		int					attributeCount = random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(attribCounts), DE_ARRAY_END(attribCounts), attribWeights);
+		gls::DrawTestSpec	spec;
+
+		spec.apiType				= glu::ApiType::es(2,0);
+		spec.primitive				= random.chooseWeighted<gls::DrawTestSpec::Primitive>	(DE_ARRAY_BEGIN(primitives),		DE_ARRAY_END(primitives),		primitiveWeights.weights);
+		spec.primitiveCount			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(primitiveCounts),	DE_ARRAY_END(primitiveCounts),	primitiveCountWeights);
+		spec.drawMethod				= random.chooseWeighted<gls::DrawTestSpec::DrawMethod>	(DE_ARRAY_BEGIN(drawMethods),		DE_ARRAY_END(drawMethods),		drawMethodWeights.weights);
+		spec.indexType				= random.chooseWeighted<gls::DrawTestSpec::IndexType>	(DE_ARRAY_BEGIN(indexTypes),		DE_ARRAY_END(indexTypes),		indexTypeWeights.weights);
+		spec.indexPointerOffset		= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexOffsets),		DE_ARRAY_END(indexOffsets),		indexOffsetWeights);
+		spec.indexStorage			= random.chooseWeighted<gls::DrawTestSpec::Storage>		(DE_ARRAY_BEGIN(storages),			DE_ARRAY_END(storages),			storageWeights.weights);
+		spec.first					= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(firsts),			DE_ARRAY_END(firsts),			firstWeights);
+		spec.indexMin				= 0;
+		spec.indexMax				= 0;
+		spec.instanceCount			= 0;
+
+		// check spec is legal
+		if (!spec.valid())
+			continue;
+
+		for (int attrNdx = 0; attrNdx < attributeCount;)
+		{
+			bool valid;
+			gls::DrawTestSpec::AttributeSpec attribSpec;
+
+			attribSpec.inputType			= random.chooseWeighted<gls::DrawTestSpec::InputType>	(DE_ARRAY_BEGIN(inputTypes),		DE_ARRAY_END(inputTypes),		inputTypeWeights.weights);
+			attribSpec.outputType			= random.chooseWeighted<gls::DrawTestSpec::OutputType>	(DE_ARRAY_BEGIN(outputTypes),		DE_ARRAY_END(outputTypes),		outputTypeWeights.weights);
+			attribSpec.storage				= random.chooseWeighted<gls::DrawTestSpec::Storage>		(DE_ARRAY_BEGIN(storages),			DE_ARRAY_END(storages),			storageWeights.weights);
+			attribSpec.usage				= random.chooseWeighted<gls::DrawTestSpec::Usage>		(DE_ARRAY_BEGIN(usages),			DE_ARRAY_END(usages),			usageWeights.weights);
+			attribSpec.componentCount		= random.getInt(1, 4);
+			attribSpec.offset				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), offsetWeights);
+			attribSpec.stride				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(strides), DE_ARRAY_END(strides), strideWeights);
+			attribSpec.normalize			= random.getBool();
+			attribSpec.instanceDivisor		= 0;
+			attribSpec.useDefaultAttribute	= random.getBool();
+
+			// check spec is legal
+			valid = attribSpec.valid(spec.apiType);
+
+			// we do not want interleaved elements. (Might result in some weird floating point values)
+			if (attribSpec.stride && attribSpec.componentCount * gls::DrawTestSpec::inputTypeSize(attribSpec.inputType) > attribSpec.stride)
+				valid = false;
+
+			// try again if not valid
+			if (valid)
+			{
+				spec.attribs.push_back(attribSpec);
+				++attrNdx;
+			}
+		}
+
+		// Do not collapse all vertex positions to a single positions
+		if (spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+			spec.attribs[0].instanceDivisor = 0;
+
+		// Is render result meaningful?
+		{
+			// Only one vertex
+			if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && spec.indexMin == spec.indexMax && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+			if (spec.attribs[0].useDefaultAttribute && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+
+			// Triangle only on one axis
+			if (spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLES || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP)
+			{
+				if (spec.attribs[0].componentCount == 1)
+					continue;
+				if (spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_FLOAT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_INT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_UINT)
+					continue;
+				if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && (spec.indexMax - spec.indexMin) < 2)
+					continue;
+			}
+		}
+
+		// Add case
+		{
+			deUint32 hash = spec.hash();
+			for (int attrNdx = 0; attrNdx < attributeCount; ++attrNdx)
+				hash = (hash << 2) ^ (deUint32)spec.attribs[attrNdx].hash();
+
+			if (insertedHashes.find(hash) == insertedHashes.end() &&
+				std::find(DE_ARRAY_BEGIN(blacklistedCases), DE_ARRAY_END(blacklistedCases), hash) == DE_ARRAY_END(blacklistedCases))
+			{
+				// Only unaligned cases
+				if (spec.isCompatibilityTest() == gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET ||
+					spec.isCompatibilityTest() == gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+					this->addChild(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), spec, de::toString(insertedCount).c_str(), spec.getDesc().c_str()));
+				insertedHashes.insert(hash);
+
+				++insertedCount;
+			}
+		}
+	}
+}
+
+} // anonymous
+
+DrawTests::DrawTests (Context& context)
+	: TestCaseGroup(context, "draw", "Drawing tests")
+{
+}
+
+DrawTests::~DrawTests (void)
+{
+}
+
+void DrawTests::init (void)
+{
+	tcu::TestCaseGroup* const unalignedGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_data", "Test with unaligned data");
+
+	addChild(unalignedGroup);
+
+	// .unaligned_data
+	{
+		const gls::DrawTestSpec::DrawMethod basicMethods[] =
+		{
+			// gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS,
+			gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS,
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(basicMethods); ++ndx)
+		{
+			std::string name = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
+			std::string desc = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
+
+			unalignedGroup->addChild(new MethodGroup(m_context, name.c_str(), desc.c_str(), basicMethods[ndx]));
+		}
+
+		// Random
+
+		unalignedGroup->addChild(new RandomGroup(m_context, "random", "random draw commands."));
+	}
+
+}
+
+} // Stress
+} // gles2
+} // deqp
diff --git a/modules/gles2/stress/es2sDrawTests.hpp b/modules/gles2/stress/es2sDrawTests.hpp
new file mode 100644
index 0000000..e97b128
--- /dev/null
+++ b/modules/gles2/stress/es2sDrawTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2SDRAWTESTS_HPP
+#define _ES2SDRAWTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Drawing stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+
+class DrawTests : public TestCaseGroup
+{
+public:
+						DrawTests		(Context& context);
+						~DrawTests		(void);
+
+	void				init			(void);
+
+private:
+						DrawTests		(const DrawTests& other);
+	DrawTests&			operator=		(const DrawTests& other);
+};
+
+} // Stress
+} // gles2
+} // deqp
+
+#endif // _ES2SDRAWTESTS_HPP
diff --git a/modules/gles2/stress/es2sLongRunningTests.cpp b/modules/gles2/stress/es2sLongRunningTests.cpp
new file mode 100644
index 0000000..0022dce
--- /dev/null
+++ b/modules/gles2/stress/es2sLongRunningTests.cpp
@@ -0,0 +1,357 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Long-running stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2sLongRunningTests.hpp"
+#include "glsLongStressCase.hpp"
+#include "glsLongStressTestUtil.hpp"
+#include "glwEnums.hpp"
+
+#include <string>
+
+using std::string;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+
+LongRunningTests::LongRunningTests (Context& context)
+	: TestCaseGroup(context, "long", "Long-running stress tests")
+{
+}
+
+LongRunningTests::~LongRunningTests (void)
+{
+}
+
+void LongRunningTests::init (void)
+{
+	static const int								Mi			= 1<<20;
+	const gls::LongStressTestUtil::ProgramLibrary	progLib		(glu::GLSL_VERSION_100_ES);
+
+	typedef gls::LongStressCase::FeatureProbabilities Probs;
+
+	// Buffer cases.
+
+	{
+		static const struct MemCase
+		{
+			const char* const	nameSuffix;
+			const char* const	descSuffix;
+			const int			limit;
+			const int			redundantBufferFactor;
+			MemCase (const char* n, const char* d, int l, int r) : nameSuffix(n), descSuffix(d), limit(l), redundantBufferFactor(r) {}
+		} memoryLimitCases[] =
+		{
+			MemCase("_low_memory",	"; use a low buffer memory usage limit",	8*Mi,		2),
+			MemCase("_high_memory",	"; use a high buffer memory usage limit",	256*Mi,		64)
+		};
+
+		const std::vector<gls::ProgramContext> contexts(1, progLib.generateBufferContext(4));
+
+		static const struct Case
+		{
+			const char* const	name;
+			const char*	const	desc;
+			const int			redundantBufferFactor; //!< If non-positive, taken from memoryLimitCases.
+			const Probs			probs;
+			Case (const char* const name_, const char* const desc_, int bufFact, const Probs& probs_ = Probs()) : name(name_), desc(desc_), redundantBufferFactor(bufFact), probs(probs_) {}
+		} cases[] =
+		{
+			Case("always_reupload",
+				 "Re-upload buffer data at the beginning of each iteration",
+				 -1,
+				 Probs().pReuploadBuffer(1.0f)),
+
+			Case("always_reupload_bufferdata",
+				 "Re-upload buffer data at the beginning of each iteration, using glBufferData",
+				 -1,
+				 Probs().pReuploadBuffer(1.0f).pReuploadWithBufferData(1.0f)),
+
+			Case("always_delete",
+				 "Delete buffers at the end of each iteration, and re-create at the beginning of the next",
+				 -1,
+				 Probs().pDeleteBuffer(1.0f)),
+
+			Case("wasteful",
+				 "Don't reuse buffers, and only delete them when given memory limit is reached",
+				 2,
+				 Probs().pWastefulBufferMemoryUsage(1.0f)),
+
+			Case("separate_attribute_buffers_wasteful",
+				 "Give each vertex attribute its own buffer",
+				 2,
+				 Probs().pSeparateAttribBuffers(1.0f).pWastefulBufferMemoryUsage(1.0f))
+		};
+
+		TestCaseGroup* const bufferGroup = new TestCaseGroup(m_context, "buffer", "Buffer stress tests");
+		addChild(bufferGroup);
+
+		for (int memoryLimitNdx = 0; memoryLimitNdx < DE_LENGTH_OF_ARRAY(memoryLimitCases); memoryLimitNdx++)
+		{
+			for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+			{
+				const int redundantBufferFactor = cases[caseNdx].redundantBufferFactor > 0 ? cases[caseNdx].redundantBufferFactor : memoryLimitCases[memoryLimitNdx].redundantBufferFactor;
+
+				bufferGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+															  (string() + cases[caseNdx].name + memoryLimitCases[memoryLimitNdx].nameSuffix).c_str(),
+															  (string() + cases[caseNdx].desc + memoryLimitCases[memoryLimitNdx].descSuffix).c_str(),
+															  0 /* tex memory */, memoryLimitCases[memoryLimitNdx].limit,
+															  1 /* draw calls per iteration */, 50000 /* tris per call */,
+															  contexts, cases[caseNdx].probs,
+															  GL_DYNAMIC_DRAW, GL_DYNAMIC_DRAW,
+															  redundantBufferFactor));
+			}
+		}
+	}
+
+	// Texture cases.
+
+	{
+		static const struct MemCase
+		{
+			const char* const	nameSuffix;
+			const char* const	descSuffix;
+			const int			limit;
+			const int			numTextures;
+			MemCase (const char* n, const char* d, int l, int t) : nameSuffix(n), descSuffix(d), limit(l), numTextures(t) {}
+		} memoryLimitCases[] =
+		{
+			MemCase("_low_memory",	"; use a low texture memory usage limit",	8*Mi,		6),
+			MemCase("_high_memory",	"; use a high texture memory usage limit",	256*Mi,		192)
+		};
+
+		static const struct Case
+		{
+			const char* const	name;
+			const char* const	desc;
+			const int			numTextures; //!< If non-positive, taken from memoryLimitCases.
+			const Probs			probs;
+			Case (const char* const name_, const char* const desc_, int numTextures_, const Probs& probs_ = Probs()) : name(name_), desc(desc_), numTextures(numTextures_), probs(probs_) {}
+		} cases[] =
+		{
+			Case("always_reupload",
+				 "Re-upload texture data at the beginning of each iteration",
+				 -1,
+				 Probs().pReuploadTexture(1.0f)),
+
+			Case("always_reupload_teximage",
+				 "Re-upload texture data at the beginning of each iteration, using glTexImage*",
+				 -1,
+				 Probs().pReuploadTexture(1.0f).pReuploadWithTexImage(1.0f)),
+
+			Case("always_delete",
+				 "Delete textures at the end of each iteration, and re-create at the beginning of the next",
+				 -1,
+				 Probs().pDeleteTexture(1.0f)),
+
+			Case("wasteful",
+				 "Don't reuse textures, and only delete them when given memory limit is reached",
+				 6,
+				 Probs().pWastefulTextureMemoryUsage(1.0f))
+		};
+
+		TestCaseGroup* const textureGroup = new TestCaseGroup(m_context, "texture", "Texture stress tests");
+		addChild(textureGroup);
+
+		for (int memoryLimitNdx = 0; memoryLimitNdx < DE_LENGTH_OF_ARRAY(memoryLimitCases); memoryLimitNdx++)
+		{
+			for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+			{
+				const int								numTextures		= cases[caseNdx].numTextures > 0 ? cases[caseNdx].numTextures : memoryLimitCases[memoryLimitNdx].numTextures;
+				const std::vector<gls::ProgramContext>	contexts		(1, progLib.generateTextureContext(numTextures, 512, 512, 0.1f));
+
+				textureGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+																(string() + cases[caseNdx].name + memoryLimitCases[memoryLimitNdx].nameSuffix).c_str(),
+																(string() + cases[caseNdx].desc + memoryLimitCases[memoryLimitNdx].descSuffix).c_str(),
+																memoryLimitCases[memoryLimitNdx].limit, 1*Mi /* buf memory */,
+																1 /* draw calls per iteration */, 10000 /* tris per call */,
+																contexts, cases[caseNdx].probs,
+																GL_STATIC_DRAW, GL_STATIC_DRAW));
+			}
+		}
+	}
+
+	// Draw call cases.
+
+	{
+		const std::vector<gls::ProgramContext> contexts(1, progLib.generateTextureContext(1, 128, 128, 0.5f));
+
+		static const struct Case
+		{
+			const char* const	name;
+			const char* const	desc;
+			const int			drawCallsPerIteration;
+			const int			numTrisPerDrawCall;
+			const Probs			probs;
+			Case (const char* const name_, const char* const desc_, const int calls, const int tris, const Probs& probs_ = Probs())
+				: name(name_), desc(desc_), drawCallsPerIteration(calls), numTrisPerDrawCall(tris), probs(probs_) {}
+		} cases[] =
+		{
+			Case("client_memory_data",
+				 "Use client-memory for index and attribute data, instead of GL buffers",
+				 200, 500,
+				 Probs().pClientMemoryAttributeData(1.0f).pClientMemoryIndexData(1.0f)),
+
+			Case("vary_draw_function",
+				 "Choose between glDrawElements and glDrawArrays each iteration, with uniform probability",
+				 200, 500,
+				 Probs().pUseDrawArrays(0.5f)),
+
+			Case("few_big_calls",
+				 "Per iteration, do a few draw calls with a big number of triangles per call",
+				 2, 50000),
+
+			Case("many_small_calls",
+				 "Per iteration, do many draw calls with a small number of triangles per call",
+				 2000, 50)
+		};
+
+		TestCaseGroup* const drawCallGroup = new TestCaseGroup(m_context, "draw_call", "Draw call stress tests");
+		addChild(drawCallGroup);
+
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+		{
+			drawCallGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+															cases[caseNdx].name, cases[caseNdx].desc,
+															1*Mi /* tex memory */, 2*Mi /* buf memory */,
+															cases[caseNdx].drawCallsPerIteration, cases[caseNdx].numTrisPerDrawCall,
+															contexts, cases[caseNdx].probs,
+															GL_STATIC_DRAW, GL_STATIC_DRAW));
+		}
+	}
+
+	// Shader cases.
+
+	{
+		std::vector<gls::ProgramContext> contexts;
+		contexts.push_back(progLib.generateFragmentPointLightContext(512, 512));
+		contexts.push_back(progLib.generateVertexUniformLoopLightContext(512, 512));
+
+		static const struct Case
+		{
+			const char* const	name;
+			const char* const	desc;
+			const Probs			probs;
+			Case (const char* const name_, const char* const desc_, const Probs& probs_ = Probs()) : name(name_), desc(desc_), probs(probs_) {}
+		} cases[] =
+		{
+			Case("several_programs",
+				 "Use several different programs, choosing between them uniformly on each iteration"),
+
+			Case("several_programs_always_rebuild",
+				 "Use several different programs, choosing between them uniformly on each iteration, and always rebuild the program",
+				 Probs().pRebuildProgram(1.0f))
+		};
+
+		TestCaseGroup* const shaderGroup = new TestCaseGroup(m_context, "program", "Shader program stress tests");
+		addChild(shaderGroup);
+
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+		{
+			shaderGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+														  cases[caseNdx].name, cases[caseNdx].desc,
+														  3*Mi /* tex memory */, 1*Mi /* buf memory */,
+														  1 /* draw calls per iteration */, 10000 /* tris per call */,
+														  contexts, cases[caseNdx].probs,
+														  GL_STATIC_DRAW, GL_STATIC_DRAW));
+		}
+	}
+
+	// Mixed cases.
+
+	{
+		static const struct MemCase
+		{
+			const char* const	nameSuffix;
+			const char* const	descSuffix;
+			const int			texLimit;
+			const int			bufLimit;
+			MemCase (const char* n, const char* d, int t, int b) : nameSuffix(n), descSuffix(d), texLimit(t), bufLimit(b) {}
+		} memoryLimitCases[] =
+		{
+			MemCase("_low_memory",	"; use a low memory usage limit",	8*Mi,	8*Mi),
+			MemCase("_high_memory",	"; use a high memory usage limit",	128*Mi,	128*Mi)
+		};
+
+		TestCaseGroup* const mixedGroup = new TestCaseGroup(m_context, "mixed", "Mixed stress tests");
+		addChild(mixedGroup);
+
+		for (int memoryLimitNdx = 0; memoryLimitNdx < DE_LENGTH_OF_ARRAY(memoryLimitCases); memoryLimitNdx++)
+		{
+			mixedGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+														 (string() + "buffer_texture_wasteful"					+ memoryLimitCases[memoryLimitNdx].nameSuffix).c_str(),
+														 (string() + "Use both buffers and textures wastefully"	+ memoryLimitCases[memoryLimitNdx].descSuffix).c_str(),
+														 memoryLimitCases[memoryLimitNdx].texLimit, memoryLimitCases[memoryLimitNdx].bufLimit,
+														 1 /* draw calls per iteration */, 10000 /* tris per call */,
+														 std::vector<gls::ProgramContext>(1, progLib.generateBufferAndTextureContext(4, 512, 512)),
+														 Probs()
+														 .pReuploadTexture				(0.3f)
+														 .pReuploadWithTexImage			(0.5f)
+														 .pReuploadBuffer				(0.3f)
+														 .pReuploadWithBufferData		(0.5f)
+														 .pDeleteTexture				(0.2f)
+														 .pDeleteBuffer					(0.2f)
+														 .pWastefulTextureMemoryUsage	(0.5f)
+														 .pWastefulBufferMemoryUsage	(0.5f)
+														 .pRandomBufferUploadTarget		(1.0f)
+														 .pRandomBufferUsage			(1.0f),
+														 GL_STATIC_DRAW, GL_STATIC_DRAW));
+
+			{
+				std::vector<gls::ProgramContext> contexts;
+				contexts.push_back(progLib.generateFragmentPointLightContext(512, 512));
+				contexts.push_back(progLib.generateVertexUniformLoopLightContext(512, 512));
+				mixedGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+															 (string() + "random"					+ memoryLimitCases[memoryLimitNdx].nameSuffix).c_str(),
+															 (string() + "Highly random behavior"	+ memoryLimitCases[memoryLimitNdx].descSuffix).c_str(),
+															  memoryLimitCases[memoryLimitNdx].texLimit, memoryLimitCases[memoryLimitNdx].bufLimit,
+															 1 /* draw calls per iteration */, 10000 /* tris per call */,
+															 contexts,
+															 Probs()
+															 .pRebuildProgram				(0.3f)
+															 .pReuploadTexture				(0.3f)
+															 .pReuploadWithTexImage			(0.3f)
+															 .pReuploadBuffer				(0.3f)
+															 .pReuploadWithBufferData		(0.3f)
+															 .pDeleteTexture				(0.2f)
+															 .pDeleteBuffer					(0.2f)
+															 .pWastefulTextureMemoryUsage	(0.3f)
+															 .pWastefulBufferMemoryUsage	(0.3f)
+															 .pClientMemoryAttributeData	(0.2f)
+															 .pClientMemoryIndexData		(0.2f)
+															 .pSeparateAttribBuffers		(0.4f)
+															 .pUseDrawArrays				(0.4f)
+															 .pRandomBufferUploadTarget		(1.0f)
+															 .pRandomBufferUsage			(1.0f),
+															 GL_STATIC_DRAW, GL_STATIC_DRAW));
+			}
+		}
+	}
+}
+
+} // Stress
+} // gles2
+} // deqp
diff --git a/modules/gles2/stress/es2sLongRunningTests.hpp b/modules/gles2/stress/es2sLongRunningTests.hpp
new file mode 100644
index 0000000..5ff9d17
--- /dev/null
+++ b/modules/gles2/stress/es2sLongRunningTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2SLONGRUNNINGTESTS_HPP
+#define _ES2SLONGRUNNINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Long-running stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+
+class LongRunningTests : public TestCaseGroup
+{
+public:
+						LongRunningTests	(Context& context);
+						~LongRunningTests	(void);
+
+	void				init				(void);
+
+private:
+						LongRunningTests	(const LongRunningTests& other);
+	LongRunningTests&	operator=			(const LongRunningTests& other);
+};
+
+} // Stress
+} // gles2
+} // deqp
+
+#endif // _ES2SLONGRUNNINGTESTS_HPP
diff --git a/modules/gles2/stress/es2sMemoryTests.cpp b/modules/gles2/stress/es2sMemoryTests.cpp
new file mode 100644
index 0000000..1ee186c
--- /dev/null
+++ b/modules/gles2/stress/es2sMemoryTests.cpp
@@ -0,0 +1,187 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Memory object stress test
+ *//*--------------------------------------------------------------------*/
+
+#include "es2sMemoryTests.hpp"
+
+#include "glsMemoryStressCase.hpp"
+#include "deRandom.hpp"
+#include "tcuTestLog.hpp"
+
+#include <vector>
+#include <iostream>
+
+using std::vector;
+using tcu::TestLog;
+
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+
+MemoryTests::MemoryTests (Context& testCtx)
+	: TestCaseGroup(testCtx, "memory", "Memory stress tests")
+{
+}
+
+MemoryTests::~MemoryTests(void)
+{
+}
+
+void MemoryTests::init (void)
+{
+	const int MiB = 1024*1024;
+
+	// Basic tests
+	tcu::TestCaseGroup* basicGroup = new TestCaseGroup(m_context, "basic", "Basic allocation stress tests.");
+
+	// Buffers
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, false, false, false, false,	"buffer_1mb_no_write_no_use",	"1MiB buffer allocations, no data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, true,  false, false, false,	"buffer_1mb_write_no_use",		"1MiB buffer allocations, data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, false, true,  false, false,	"buffer_1mb_no_write_use",		"1MiB buffer allocations, no data writes, data used"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, true,  true,  false, false,	"buffer_1mb_write_use",			"1MiB buffer allocations, data writes, data used"));
+
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, false, false, false, false,	"buffer_8mb_no_write_no_use",	"8MiB buffer allocations, no data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, true,  false, false, false,	"buffer_8mb_write_no_use",		"8MiB buffer allocations, data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, false, true,  false, false,	"buffer_8mb_no_write_use",		"8MiB buffer allocations, no data writes, data used"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, true,  true,  false, false,	"buffer_8mb_write_use",			"8MiB buffer allocations, data writes, data used"));
+
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, false, false, false, false,	"buffer_32mb_no_write_no_use",	"32MiB buffer allocations, no data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, true,  false, false, false,	"buffer_32mb_write_no_use",		"32MiB buffer allocations, data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, false, true,  false, false,	"buffer_32mb_no_write_use",		"32MiB buffer allocations, no data writes, data used"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, true, true,  false, false,	"buffer_32mb_write_use",		"32MiB buffer allocations, data writes, data used"));
+
+	// Textures
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, false, false, false, false,	"texture_512x512_rgba_no_write_no_use",	"512x512 RGBA texture allocations, no data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, true,  false, false, false,	"texture_512x512_rgba_write_no_use",	"512x512 RGBA texture allocations, data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, false, true,  false, false,	"texture_512x512_rgba_no_write_use",	"512x512 RGBA texture allocations, no data writes, data used"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, true, true,  false, false,	"texture_512x512_rgba_write_use",		"512x512 RGBA texture allocations, data writes, data used"));
+
+	// Random tests
+	tcu::TestCaseGroup*	randomGroup = new TestCaseGroup(m_context, "random", "Random allocation stress tests.");
+
+	// Buffers
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, false, false, false, false,	"buffer_small_no_write_no_use",		"Random small buffer allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, true,  false, false, false,	"buffer_small_write_no_use",		"Random small allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, false, true,  false, false,	"buffer_small_no_write_use",		"Random small allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, true,  true,  false, false,	"buffer_small_write_use",			"Random small allocations, data writes, data used"));
+
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, false, false, false, false,	"buffer_large_no_write_no_use",		"Random large buffer allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, true,  false, false, false,	"buffer_large_write_no_use",		"Random large buffer allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, false, true,  false, false,	"buffer_large_no_write_use",		"Random large buffer allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, true,  true,  false, false,	"buffer_large_write_use",			"Random large buffer allocations, data writes, data used"));
+
+	// Textures
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, false, false, false, false,	"texture_small_rgba_no_write_no_use",	"Small RGBA texture allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, true,  false, false, false,	"texture_small_rgba_write_no_use",		"Small RGBA texture allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, false, true,  false, false,	"texture_small_rgba_no_write_use",		"Small RGBA texture allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, true,  true,  false, false,	"texture_small_rgba_write_use",			"Small RGBA texture allocations, data writes, data used"));
+
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, false, false, false, false,	"texture_large_rgba_no_write_no_use",	"Large RGBA texture allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, true,  false, false, false,	"texture_large_rgba_write_no_use",		"Large RGBA texture allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, false, true,  false, false,	"texture_large_rgba_no_write_use",		"Large RGBA texture allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, true,  true,  false, false,	"texture_large_rgba_write_use",			"Large RGBA texture allocations, data writes, data used"));
+
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, false, false, false, false,	"mixed_small_rgba_no_write_no_use",		"Small RGBA mixed allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, true,  false, false, false,	"mixed_small_rgba_write_no_use",		"Small RGBA mixed allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, false, true,  false, false,	"mixed_small_rgba_no_write_use",		"Small RGBA mixed allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, true,  true,  false, false,	"mixed_small_rgba_write_use",			"Small RGBA mixed allocations, data writes, data used"));
+
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, false, false, false, false,	"mixed_large_rgba_no_write_no_use",		"Large RGBA mixed allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, true,  false, false, false,	"mixed_large_rgba_write_no_use",		"Large RGBA mixed allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, false, true,  false, false,	"mixed_large_rgba_no_write_use",		"Large RGBA mixed allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, true,  true,  false, false,	"mixed_large_rgba_write_use",			"Large RGBA mixed allocations, data writes, data used"));
+
+	addChild(basicGroup);
+	addChild(randomGroup);
+
+	// Basic tests with clear
+	tcu::TestCaseGroup* basicClearGroup	= new TestCaseGroup(m_context, "basic_clear", "Basic allocation stress tests with glClear after OOM.");
+
+	// Buffers
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, false, false, false, true,	"buffer_1mb_no_write_no_use",	"1MiB buffer allocations, no data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, true,  false, false, true,	"buffer_1mb_write_no_use",		"1MiB buffer allocations, data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, false, true,  false, true,	"buffer_1mb_no_write_use",		"1MiB buffer allocations, no data writes, data used"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, true,  true,  false, true,	"buffer_1mb_write_use",			"1MiB buffer allocations, data writes, data used"));
+
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, false, false, false, true,	"buffer_8mb_no_write_no_use",	"8MiB buffer allocations, no data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, true,  false, false, true,	"buffer_8mb_write_no_use",		"8MiB buffer allocations, data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, false, true,  false, true,	"buffer_8mb_no_write_use",		"8MiB buffer allocations, no data writes, data used"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, true,  true,  false, true,	"buffer_8mb_write_use",			"8MiB buffer allocations, data writes, data used"));
+
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, false, false, false, true,	"buffer_32mb_no_write_no_use",	"32MiB buffer allocations, no data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, true,  false, false, true,	"buffer_32mb_write_no_use",		"32MiB buffer allocations, data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, false, true,  false, true,	"buffer_32mb_no_write_use",		"32MiB buffer allocations, no data writes, data used"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, true, true,  false, true,		"buffer_32mb_write_use",		"32MiB buffer allocations, data writes, data used"));
+
+	// Textures
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, false, false, false, true,	"texture_512x512_rgba_no_write_no_use",	"512x512 RGBA texture allocations, no data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, true,  false, false, true,	"texture_512x512_rgba_write_no_use",	"512x512 RGBA texture allocations, data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, false, true,  false, true,	"texture_512x512_rgba_no_write_use",	"512x512 RGBA texture allocations, no data writes, data used"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, true, true,  false, true,	"texture_512x512_rgba_write_use",		"512x512 RGBA texture allocations, data writes, data used"));
+
+	// Random tests
+	tcu::TestCaseGroup*	randomClearGroup = new TestCaseGroup(m_context, "random_clear", "Random allocation stress tests with glClear after OOM.");
+
+	// Buffers
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, false, false, false, true,	"buffer_small_no_write_no_use",		"Random small buffer allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, true,  false, false, true,	"buffer_small_write_no_use",		"Random small allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, false, true,  false, true,	"buffer_small_no_write_use",		"Random small allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, true,  true,  false, true,	"buffer_small_write_use",			"Random small allocations, data writes, data used"));
+
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, false, false, false, true,	"buffer_large_no_write_no_use",		"Random large buffer allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, true,  false, false, true,	"buffer_large_write_no_use",		"Random large buffer allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, false, true,  false, true,	"buffer_large_no_write_use",		"Random large buffer allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, true,  true,  false, true,	"buffer_large_write_use",			"Random large buffer allocations, data writes, data used"));
+
+	// Textures
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, false, false, false, true,	"texture_small_rgba_no_write_no_use",	"Small RGBA texture allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, true,  false, false, true,	"texture_small_rgba_write_no_use",		"Small RGBA texture allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, false, true,  false, true,	"texture_small_rgba_no_write_use",		"Small RGBA texture allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, true,  true,  false, true,	"texture_small_rgba_write_use",			"Small RGBA texture allocations, data writes, data used"));
+
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, false, false, false, true,	"texture_large_rgba_no_write_no_use",	"Large RGBA texture allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, true,  false, false, true,	"texture_large_rgba_write_no_use",		"Large RGBA texture allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, false, true,  false, true,	"texture_large_rgba_no_write_use",		"Large RGBA texture allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, true,  true,  false, true,	"texture_large_rgba_write_use",			"Large RGBA texture allocations, data writes, data used"));
+
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, false, false, false, true,	"mixed_small_rgba_no_write_no_use",		"Small RGBA mixed allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, true,  false, false, true,	"mixed_small_rgba_write_no_use",		"Small RGBA mixed allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, false, true,  false, true,	"mixed_small_rgba_no_write_use",		"Small RGBA mixed allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, true,  true,  false, true,	"mixed_small_rgba_write_use",			"Small RGBA mixed allocations, data writes, data used"));
+
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, false, false, false, true,	"mixed_large_rgba_no_write_no_use",		"Large RGBA mixed allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, true,  false, false, true,	"mixed_large_rgba_write_no_use",		"Large RGBA mixed allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, false, true,  false, true,	"mixed_large_rgba_no_write_use",		"Large RGBA mixed allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, true,  true,  false, true,	"mixed_large_rgba_write_use",			"Large RGBA mixed allocations, data writes, data used"));
+
+	addChild(basicClearGroup);
+	addChild(randomClearGroup);
+}
+
+} // Stress
+} // gles2
+} // deqp
diff --git a/modules/gles2/stress/es2sMemoryTests.hpp b/modules/gles2/stress/es2sMemoryTests.hpp
new file mode 100644
index 0000000..3d0ae14
--- /dev/null
+++ b/modules/gles2/stress/es2sMemoryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2SMEMORYTESTS_HPP
+#define _ES2SMEMORYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Memory object stress test
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+
+class MemoryTests : public TestCaseGroup
+{
+public:
+					MemoryTests		(Context& testCtx);
+					~MemoryTests	(void);
+
+	void			init			(void);
+
+private:
+					MemoryTests		(const MemoryTests& other);
+	MemoryTests&	operator=		(const MemoryTests& other);
+};
+
+} // Stress
+} // gles2
+} // deqp
+
+#endif // _ES2SMEMORYTESTS_HPP
diff --git a/modules/gles2/stress/es2sSpecialFloatTests.cpp b/modules/gles2/stress/es2sSpecialFloatTests.cpp
new file mode 100644
index 0000000..9396c40
--- /dev/null
+++ b/modules/gles2/stress/es2sSpecialFloatTests.cpp
@@ -0,0 +1,1764 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Special float stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2sSpecialFloatTests.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+#include "deRandom.hpp"
+
+#include <limits>
+#include <sstream>
+
+using namespace glw;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+namespace
+{
+
+static const int		TEST_CANVAS_SIZE		= 256;
+static const int		TEST_TEXTURE_SIZE		= 128;
+static const int		TEST_TEXTURE_CUBE_SIZE	= 32;
+static const deUint32	s_specialFloats[]		=
+{
+	0x00000000,	//          zero
+	0x80000000,	// negative zero
+	0x3F800000,	//          one
+	0xBF800000,	// negative one
+	0x00800000,	// minimum positive normalized value
+	0x80800000,	// maximum negative normalized value
+	0x00000001,	// minimum positive denorm value
+	0x80000001,	// maximum negative denorm value
+	0x7F7FFFFF,	// maximum finite value.
+	0xFF7FFFFF,	// minimum finite value.
+	0x7F800000,	//  inf
+	0xFF800000,	// -inf
+	0x34000000,	//          epsilon
+	0xB4000000,	// negative epsilon
+	0x7FC00000,	//          quiet_NaN
+	0xFFC00000,	// negative quiet_NaN
+	0x7FC00001,	//          signaling_NaN
+	0xFFC00001,	// negative signaling_NaN
+	0x7FEAAAAA,	//          quiet payloaded NaN		(payload of repeated pattern of 101010...)
+	0xFFEAAAAA,	// negative quiet payloaded NaN		( .. )
+	0x7FAAAAAA,	//          signaling payloaded NaN	( .. )
+	0xFFAAAAAA,	// negative signaling payloaded NaN	( .. )
+};
+
+static const char* const s_colorPassthroughFragmentShaderSource =	"varying mediump vec4 v_out;\n"
+																	"void main ()\n"
+																	"{\n"
+																	"	gl_FragColor = v_out;\n"
+																	"}\n";
+static const char* const s_attrPassthroughVertexShaderSource	=	"attribute highp vec4 a_pos;\n"
+																	"attribute highp vec4 a_attr;\n"
+																	"varying mediump vec4 v_attr;\n"
+																	"void main ()\n"
+																	"{\n"
+																	"	v_attr = a_attr;\n"
+																	"	gl_Position = a_pos;\n"
+																	"}\n";
+
+class RenderCase : public TestCase
+{
+public:
+	enum RenderTargetType
+	{
+		RENDERTARGETTYPE_SCREEN,
+		RENDERTARGETTYPE_FBO
+	};
+
+								RenderCase			(Context& context, const char* name, const char* desc, RenderTargetType renderTargetType = RENDERTARGETTYPE_SCREEN);
+	virtual						~RenderCase			(void);
+
+	virtual void				init				(void);
+	virtual void				deinit				(void);
+
+protected:
+	bool						checkResultImage	(const tcu::Surface& result);
+	bool						drawTestPattern		(bool useTexture);
+
+	virtual std::string			genVertexSource		(void) const = 0;
+	virtual std::string			genFragmentSource	(void) const = 0;
+
+	const glu::ShaderProgram*	m_program;
+	const RenderTargetType		m_renderTargetType;
+};
+
+RenderCase::RenderCase (Context& context, const char* name, const char* desc, RenderTargetType renderTargetType)
+	: TestCase				(context, name, desc)
+	, m_program				(DE_NULL)
+	, m_renderTargetType	(renderTargetType)
+{
+}
+
+RenderCase::~RenderCase (void)
+{
+	deinit();
+}
+
+void RenderCase::init (void)
+{
+	const int width	 = m_context.getRenderTarget().getWidth();
+	const int height = m_context.getRenderTarget().getHeight();
+
+	// check target size
+	if (m_renderTargetType == RENDERTARGETTYPE_SCREEN)
+	{
+		if (width < TEST_CANVAS_SIZE || height < TEST_CANVAS_SIZE)
+			throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(TEST_CANVAS_SIZE) + "x" + de::toString(TEST_CANVAS_SIZE));
+	}
+	else if (m_renderTargetType == RENDERTARGETTYPE_FBO)
+	{
+		GLint maxTexSize = 0;
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
+
+		if (maxTexSize < TEST_CANVAS_SIZE)
+			throw tcu::NotSupportedError(std::string("GL_MAX_TEXTURE_SIZE must be at least ") + de::toString(TEST_CANVAS_SIZE));
+	}
+	else
+		DE_ASSERT(false);
+
+	// gen shader
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Creating test shader." << tcu::TestLog::EndMessage;
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource()) << glu::FragmentSource(genFragmentSource()));
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+		throw tcu::TestError("shader compile failed");
+}
+
+void RenderCase::deinit (void)
+{
+	if (m_program)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+	}
+}
+
+bool RenderCase::checkResultImage (const tcu::Surface& result)
+{
+	tcu::Surface	errorMask	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	bool			error		= false;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output image." << tcu::TestLog::EndMessage;
+
+	for (int y = 0; y < TEST_CANVAS_SIZE; ++y)
+	for (int x = 0; x < TEST_CANVAS_SIZE; ++x)
+	{
+		const tcu::RGBA col = result.getPixel(x, y);
+
+		if (col.getGreen() == 255)
+			errorMask.setPixel(x, y, tcu::RGBA::green);
+		else
+		{
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+			error = true;
+		}
+	}
+
+	if (error)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Result image has missing or invalid pixels" << tcu::TestLog::EndMessage;
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Results", "Result verification")
+			<< tcu::TestLog::Image("Result",		"Result",		result)
+			<< tcu::TestLog::Image("Error mask",	"Error mask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+	}
+	else
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Results", "Result verification")
+			<< tcu::TestLog::Image("Result", "Result", result)
+			<< tcu::TestLog::EndImageSet;
+	}
+
+	return !error;
+}
+
+bool RenderCase::drawTestPattern (bool useTexture)
+{
+	static const tcu::Vec4 fullscreenQuad[4] =
+	{
+		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),
+	};
+	const char* const	vertexSource		=	"attribute highp vec4 a_pos;\n"
+												"varying mediump vec4 v_position;\n"
+												"void main ()\n"
+												"{\n"
+												"	v_position = a_pos;\n"
+												"	gl_Position = a_pos;\n"
+												"}\n";
+	const char* const	fragmentSourceNoTex	=	"varying mediump vec4 v_position;\n"
+												"void main ()\n"
+												"{\n"
+												"	gl_FragColor = vec4((v_position.x + 1.0) * 0.5, 1.0, 1.0, 1.0);\n"
+												"}\n";
+	const char* const	fragmentSourceTex	=	"uniform mediump sampler2D u_sampler;\n"
+												"varying mediump vec4 v_position;\n"
+												"void main ()\n"
+												"{\n"
+												"	gl_FragColor = texture2D(u_sampler, v_position.xy);\n"
+												"}\n";
+	const char* const	fragmentSource		=	(useTexture) ? (fragmentSourceTex) : (fragmentSourceNoTex);
+	const tcu::RGBA		formatThreshold		= m_context.getRenderTarget().getPixelFormat().getColorThreshold();
+
+	tcu::Surface		resultImage			(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::Surface		errorMask			(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	bool				error				=	false;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a test pattern to detect " << ((useTexture) ? ("texture sampling") : ("")) << " side-effects." << tcu::TestLog::EndMessage;
+
+	// draw pattern
+	{
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	patternProgram	(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexSource) << glu::FragmentSource(fragmentSource));
+		const GLint					positionLoc		= gl.getAttribLocation(patternProgram.getProgram(), "a_pos");
+		GLuint						textureID		= 0;
+
+		if (useTexture)
+		{
+			const int textureSize = 32;
+			std::vector<tcu::Vector<deUint8, 4> > buffer(textureSize*textureSize);
+
+			for (int x = 0; x < textureSize; ++x)
+			for (int y = 0; y < textureSize; ++y)
+			{
+				// sum of two axis aligned gradients. Each gradient is 127 at the edges and 0 at the center.
+				// pattern is symmetric (x and y) => no discontinuity near boundary => no need to worry of results with LINEAR filtering near boundaries
+				const deUint8 redComponent = (deUint8)de::clamp(de::abs((float)x / (float)textureSize - 0.5f) * 255.0f + de::abs((float)y / (float)textureSize - 0.5f) * 255.0f, 0.0f, 255.0f);
+
+				buffer[x * textureSize + y] = tcu::Vector<deUint8, 4>(redComponent, 255, 255, 255);
+			}
+
+			gl.genTextures(1, &textureID);
+			gl.bindTexture(GL_TEXTURE_2D, textureID);
+			gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureSize, textureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer[0].getPtr());
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		}
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(patternProgram.getProgram());
+
+		if (useTexture)
+			gl.uniform1i(gl.getUniformLocation(patternProgram.getProgram(), "u_sampler"), 0);
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &fullscreenQuad[0]);
+
+		gl.enableVertexAttribArray(positionLoc);
+		gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+		gl.disableVertexAttribArray(positionLoc);
+
+		gl.useProgram(0);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "RenderCase::drawTestPattern");
+
+		if (textureID)
+			gl.deleteTextures(1, &textureID);
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+	}
+
+	// verify pattern
+	for (int y = 0; y < TEST_CANVAS_SIZE; ++y)
+	for (int x = 0; x < TEST_CANVAS_SIZE; ++x)
+	{
+		const float			texGradientPosX = deFloatFrac((float)x * 2.0f / (float)TEST_CANVAS_SIZE);
+		const float			texGradientPosY = deFloatFrac((float)y * 2.0f / (float)TEST_CANVAS_SIZE);
+		const deUint8		texRedComponent = (deUint8)de::clamp(de::abs(texGradientPosX - 0.5f) * 255.0f + de::abs(texGradientPosY - 0.5f) * 255.0f, 0.0f, 255.0f);
+
+		const tcu::RGBA		refColTexture	= tcu::RGBA(texRedComponent, 255, 255, 255);
+		const tcu::RGBA		refColGradient	= tcu::RGBA((int)((float)x / (float)TEST_CANVAS_SIZE * 255.0f), 255, 255, 255);
+		const tcu::RGBA&	refCol			= (useTexture) ? (refColTexture) : (refColGradient);
+
+		const int			colorThreshold	= 10;
+		const tcu::RGBA		col				= resultImage.getPixel(x, y);
+		const tcu::IVec4	colorDiff		= tcu::abs(col.toIVec() - refCol.toIVec());
+
+		if (colorDiff.x() > formatThreshold.getRed()   + colorThreshold ||
+			colorDiff.y() > formatThreshold.getGreen() + colorThreshold ||
+			colorDiff.z() > formatThreshold.getBlue()  + colorThreshold)
+		{
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+			error = true;
+		}
+		else
+			errorMask.setPixel(x, y, tcu::RGBA::green);
+	}
+
+	// report error
+	if (error)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Test pattern has missing/invalid pixels" << tcu::TestLog::EndMessage;
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Results", "Result verification")
+			<< tcu::TestLog::Image("Result",		"Result",		resultImage)
+			<< tcu::TestLog::Image("Error mask",	"Error mask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+	}
+	else
+		m_testCtx.getLog() << tcu::TestLog::Message << "No side-effects found." << tcu::TestLog::EndMessage;
+
+	return !error;
+}
+
+class FramebufferRenderCase : public RenderCase
+{
+public:
+	enum FrameBufferType
+	{
+		FBO_DEFAULT = 0,
+		FBO_RGBA,
+		FBO_RGBA4,
+		FBO_RGB5_A1,
+		FBO_RGB565,
+		FBO_RGBA_FLOAT16,
+
+		FBO_LAST
+	};
+
+							FramebufferRenderCase	(Context& context, const char* name, const char* desc, FrameBufferType fboType);
+	virtual					~FramebufferRenderCase	(void);
+
+	virtual void			init					(void);
+	virtual void			deinit					(void);
+	IterateResult			iterate					(void);
+
+	virtual void			testFBO					(void) = DE_NULL;
+
+protected:
+	const FrameBufferType	m_fboType;
+
+private:
+	GLuint					m_texID;
+	GLuint					m_fboID;
+};
+
+FramebufferRenderCase::FramebufferRenderCase (Context& context, const char* name, const char* desc, FrameBufferType fboType)
+	: RenderCase	(context, name, desc, (fboType == FBO_DEFAULT) ? (RENDERTARGETTYPE_SCREEN) : (RENDERTARGETTYPE_FBO))
+	, m_fboType		(fboType)
+	, m_texID		(0)
+	, m_fboID		(0)
+{
+	DE_ASSERT(m_fboType < FBO_LAST);
+}
+
+FramebufferRenderCase::~FramebufferRenderCase (void)
+{
+	deinit();
+}
+
+void FramebufferRenderCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// check requirements
+	if (m_fboType == FBO_RGBA_FLOAT16)
+	{
+		// half float texture is allowed (OES_texture_half_float) and it is color renderable (EXT_color_buffer_half_float)
+		if (!m_context.getContextInfo().isExtensionSupported("GL_OES_texture_half_float") ||
+			!m_context.getContextInfo().isExtensionSupported("GL_EXT_color_buffer_half_float"))
+			throw tcu::NotSupportedError("Color renderable half float texture required.");
+	}
+
+	// gen shader
+	RenderCase::init();
+
+	// create render target
+	if (m_fboType == FBO_DEFAULT)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Using default framebuffer." << tcu::TestLog::EndMessage;
+	}
+	else
+	{
+		GLuint internalFormat	= 0;
+		GLuint format			= 0;
+		GLuint type				= 0;
+
+#if !defined(GL_HALF_FLOAT_OES)
+#	define	GL_HALF_FLOAT_OES 0x8D61
+#endif
+
+		switch (m_fboType)
+		{
+			case FBO_RGBA:			internalFormat = GL_RGBA;	format = GL_RGBA;	type = GL_UNSIGNED_BYTE;				break;
+			case FBO_RGBA4:			internalFormat = GL_RGBA;	format = GL_RGBA;	type = GL_UNSIGNED_SHORT_4_4_4_4;		break;
+			case FBO_RGB5_A1:		internalFormat = GL_RGBA;	format = GL_RGBA;	type = GL_UNSIGNED_SHORT_5_5_5_1;		break;
+			case FBO_RGB565:		internalFormat = GL_RGB;	format = GL_RGB;	type = GL_UNSIGNED_SHORT_5_6_5;			break;
+			case FBO_RGBA_FLOAT16:	internalFormat = GL_RGBA;	format = GL_RGBA;	type = GL_HALF_FLOAT_OES;				break;
+
+			default:
+				DE_ASSERT(false);
+				break;
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message
+			<< "Creating fbo. Texture internalFormat = " << glu::getPixelFormatStr(internalFormat)
+			<< ", format = " << glu::getPixelFormatStr(format)
+			<< ", type = " << glu::getTypeStr(type)
+			<< tcu::TestLog::EndMessage;
+
+		// gen texture
+		gl.genTextures(1, &m_texID);
+		gl.bindTexture(GL_TEXTURE_2D, m_texID);
+		gl.texImage2D(GL_TEXTURE_2D, 0, internalFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, 0, format, type, DE_NULL);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "texture init");
+
+		// gen fbo
+		gl.genFramebuffers(1, &m_fboID);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
+		gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texID, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "fbo init");
+
+		if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+			throw tcu::NotSupportedError("could not create fbo for testing.");
+	}
+}
+
+void FramebufferRenderCase::deinit (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_texID)
+	{
+		gl.deleteTextures(1, &m_texID);
+		m_texID = 0;
+	}
+
+	if (m_fboID)
+	{
+		gl.deleteFramebuffers(1, &m_fboID);
+		m_fboID = 0;
+	}
+}
+
+FramebufferRenderCase::IterateResult FramebufferRenderCase::iterate (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// bind fbo (or don't if we are using default)
+	if (m_fboID)
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
+
+	// do something with special floats
+	testFBO();
+
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats as vertex attributes
+ *
+ * Tests that special floats transferred to the shader using vertex
+ * attributes do not change the results of normal floating point
+ * calculations. Special floats are put to 4-vector's x and y components and
+ * value 1.0 is put to z and w. The resulting fragment's green channel
+ * should be 1.0 everywhere.
+ *
+ * After the calculation test a test pattern is drawn to detect possible
+ * floating point operation anomalies.
+ *//*--------------------------------------------------------------------*/
+class VertexAttributeCase : public RenderCase
+{
+public:
+	enum Storage
+	{
+		STORAGE_BUFFER = 0,
+		STORAGE_CLIENT,
+
+		STORAGE_LAST
+	};
+	enum ShaderType
+	{
+		TYPE_VERTEX = 0,
+		TYPE_FRAGMENT,
+
+		TYPE_LAST
+	};
+
+						VertexAttributeCase			(Context& context, const char* name, const char* desc, Storage storage, ShaderType type);
+						~VertexAttributeCase		(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+private:
+	std::string			genVertexSource				(void) const;
+	std::string			genFragmentSource			(void) const;
+
+	const Storage		m_storage;
+	const ShaderType	m_type;
+	GLuint				m_positionVboID;
+	GLuint				m_attribVboID;
+	GLuint				m_elementVboID;
+};
+
+VertexAttributeCase::VertexAttributeCase (Context& context, const char* name, const char* desc, Storage storage, ShaderType type)
+	: RenderCase			(context, name, desc)
+	, m_storage				(storage)
+	, m_type				(type)
+	, m_positionVboID		(0)
+	, m_attribVboID			(0)
+	, m_elementVboID		(0)
+{
+	DE_ASSERT(storage < STORAGE_LAST);
+	DE_ASSERT(type < TYPE_LAST);
+}
+
+VertexAttributeCase::~VertexAttributeCase (void)
+{
+	deinit();
+}
+
+void VertexAttributeCase::init (void)
+{
+	RenderCase::init();
+
+	// init gl resources
+	if (m_storage == STORAGE_BUFFER)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.genBuffers(1, &m_positionVboID);
+		gl.genBuffers(1, &m_attribVboID);
+		gl.genBuffers(1, &m_elementVboID);
+	}
+}
+
+void VertexAttributeCase::deinit (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	RenderCase::deinit();
+
+	if (m_attribVboID)
+	{
+		gl.deleteBuffers(1, &m_attribVboID);
+		m_attribVboID = 0;
+	}
+
+	if (m_positionVboID)
+	{
+		gl.deleteBuffers(1, &m_positionVboID);
+		m_positionVboID = 0;
+	}
+
+	if (m_elementVboID)
+	{
+		gl.deleteBuffers(1, &m_elementVboID);
+		m_elementVboID = 0;
+	}
+}
+
+VertexAttributeCase::IterateResult VertexAttributeCase::iterate (void)
+{
+	// Create a [s_specialFloats] X [s_specialFloats] grid of vertices with each vertex having 2 [s_specialFloats] values
+	// and calculate some basic operations with the floating point values. If all goes well, nothing special should happen
+
+	std::vector<tcu::Vec4>	gridVertices	(DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats));
+	std::vector<tcu::UVec4>	gridAttributes	(DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats));
+	std::vector<deUint16>	indices			((DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * 6);
+	tcu::Surface			resultImage		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+	{
+		const deUint32	one		= 0x3F800000;
+		const float		posX	= (float)x / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) - 1] to [-1, 1]
+		const float		posY	= (float)y / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f;
+
+		gridVertices[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y]	= tcu::Vec4(posX, posY, 0.0f, 1.0f);
+		gridAttributes[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y]	= tcu::UVec4(s_specialFloats[x], s_specialFloats[y], one, one);
+	}
+
+	// tiles
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++y)
+	{
+		const int baseNdx = (x * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) + y) * 6;
+
+		indices[baseNdx + 0] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+		indices[baseNdx + 1] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+		indices[baseNdx + 2] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+
+		indices[baseNdx + 3] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+		indices[baseNdx + 4] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+		indices[baseNdx + 5] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a grid with the shader. Setting a_attr for each vertex to (special, special, 1, 1)." << tcu::TestLog::EndMessage;
+
+	// Draw grid
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				attribLoc	= gl.getAttribLocation(m_program->getProgram(), "a_attr");
+
+		if (m_storage == STORAGE_BUFFER)
+		{
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_positionVboID);
+			gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(gridVertices.size() * sizeof(tcu::Vec4)), &gridVertices[0], GL_STATIC_DRAW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate");
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_attribVboID);
+			gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(gridAttributes.size() * sizeof(tcu::UVec4)), &gridAttributes[0], GL_STATIC_DRAW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate");
+
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementVboID);
+			gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indices.size() * sizeof(deUint16)), &indices[0], GL_STATIC_DRAW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate");
+		}
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+
+		if (m_storage == STORAGE_BUFFER)
+		{
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_positionVboID);
+			gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_attribVboID);
+			gl.vertexAttribPointer(attribLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+
+			gl.enableVertexAttribArray(positionLoc);
+			gl.enableVertexAttribArray(attribLoc);
+			gl.drawElements(GL_TRIANGLES, (glw::GLsizei)(indices.size()), GL_UNSIGNED_SHORT, DE_NULL);
+			gl.disableVertexAttribArray(positionLoc);
+			gl.disableVertexAttribArray(attribLoc);
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+		}
+		else if (m_storage == STORAGE_CLIENT)
+		{
+			gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+			gl.vertexAttribPointer(attribLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridAttributes[0]);
+
+			gl.enableVertexAttribArray(positionLoc);
+			gl.enableVertexAttribArray(attribLoc);
+			gl.drawElements(GL_TRIANGLES, (glw::GLsizei)(indices.size()), GL_UNSIGNED_SHORT, &indices[0]);
+			gl.disableVertexAttribArray(positionLoc);
+			gl.disableVertexAttribArray(attribLoc);
+		}
+		else
+			DE_ASSERT(false);
+
+		gl.useProgram(0);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate");
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+	}
+
+	// verify everywhere was drawn (all pixels have Green = 255)
+	if (!checkResultImage(resultImage))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing or invalid fragments");
+		return STOP;
+	}
+
+	// test drawing still works
+	if (!drawTestPattern(false))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "test pattern failed");
+		return STOP;
+	}
+
+	// all ok
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+std::string VertexAttributeCase::genVertexSource (void) const
+{
+	if (m_type == TYPE_VERTEX)
+		return
+			"attribute highp vec4 a_pos;\n"
+			"attribute highp vec4 a_attr;\n"
+			"varying mediump vec4 v_out;\n"
+			"void main ()\n"
+			"{\n"
+			"	highp vec2 a1 = a_attr.xz + a_attr.yw; // add\n"
+			"	highp vec2 a2 = a_attr.xz - a_attr.yw; // sub\n"
+			"	highp vec2 a3 = a_attr.xz * a_attr.yw; // mul\n"
+			"	highp vec2 a4 = a_attr.xz / a_attr.yw; // div\n"
+			"	highp vec2 a5 = a_attr.xz + a_attr.yw * a_attr.xz; // fma\n"
+			"\n"
+			"	highp float green = 1.0 - abs((a1.y + a2.y + a3.y + a4.y + a5.y) - 6.0);\n"
+			"	v_out = vec4(a1.x*a3.x + a2.x*a4.x, green, a5.x, 1.0);\n"
+			"	gl_Position = a_pos;\n"
+			"}\n";
+	else
+		return s_attrPassthroughVertexShaderSource;
+}
+
+std::string VertexAttributeCase::genFragmentSource (void) const
+{
+	if (m_type == TYPE_VERTEX)
+		return s_colorPassthroughFragmentShaderSource;
+	else
+		return
+			"varying mediump vec4 v_attr;\n"
+			"void main ()\n"
+			"{\n"
+			"	mediump vec2 a1 = v_attr.xz + v_attr.yw; // add\n"
+			"	mediump vec2 a2 = v_attr.xz - v_attr.yw; // sub\n"
+			"	mediump vec2 a3 = v_attr.xz * v_attr.yw; // mul\n"
+			"	mediump vec2 a4 = v_attr.xz / v_attr.yw; // div\n"
+			"	mediump vec2 a5 = v_attr.xz + v_attr.yw * v_attr.xz; // fma\n"
+			"\n"
+			"	const mediump float epsilon = 0.1; // allow small differences. To results to be wrong they must be more wrong than that.\n"
+			"	mediump float green = 1.0 + epsilon - abs((a1.y + a2.y + a3.y + a4.y + a5.y) - 6.0);\n"
+			"	gl_FragColor = vec4(a1.x*a3.x + a2.x*a4.x, green, a5.x, 1.0);\n"
+			"}\n";
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats as uniforms
+ *
+ * Tests that special floats transferred to the shader as uniforms do
+ * not change the results of normal floating point calculations. Special
+ * floats are put to 4-vector's x and y components and value 1.0 is put to
+ * z and w. The resulting fragment's green channel should be 1.0
+ * everywhere.
+ *
+ * After the calculation test a test pattern is drawn to detect possible
+ * floating point operation anomalies.
+ *//*--------------------------------------------------------------------*/
+class UniformCase : public RenderCase
+{
+public:
+	enum ShaderType
+	{
+		TYPE_VERTEX = 0,
+		TYPE_FRAGMENT,
+	};
+
+						UniformCase					(Context& context, const char* name, const char* desc, ShaderType type);
+						~UniformCase				(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+private:
+	std::string			genVertexSource				(void) const;
+	std::string			genFragmentSource			(void) const;
+
+	const ShaderType	m_type;
+};
+
+UniformCase::UniformCase (Context& context, const char* name, const char* desc, ShaderType type)
+	: RenderCase	(context, name, desc)
+	, m_type		(type)
+{
+}
+
+UniformCase::~UniformCase (void)
+{
+	deinit();
+}
+
+void UniformCase::init (void)
+{
+	RenderCase::init();
+}
+
+void UniformCase::deinit (void)
+{
+	RenderCase::deinit();
+}
+
+UniformCase::IterateResult UniformCase::iterate (void)
+{
+	// Create a [s_specialFloats] X [s_specialFloats] grid of tile with each tile having 2 [s_specialFloats] values
+	// and calculate some basic operations with the floating point values. If all goes well, nothing special should happen
+
+	std::vector<tcu::Vec4>	gridVertices	((DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1));
+	std::vector<deUint16>	indices			(DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats) * 6);
+	tcu::Surface			resultImage		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++y)
+	{
+		const float		posX	= (float)x / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) ] to [-1, 1]
+		const float		posY	= (float)y / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f;
+
+		gridVertices[x * (DE_LENGTH_OF_ARRAY(s_specialFloats)+1) + y] = tcu::Vec4(posX, posY, 0.0f, 1.0f);
+	}
+
+	// tiles
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+	{
+		const int baseNdx = (x * (DE_LENGTH_OF_ARRAY(s_specialFloats)) + y) * 6;
+
+		indices[baseNdx + 0] = (x+0) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+0);
+		indices[baseNdx + 1] = (x+1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+1);
+		indices[baseNdx + 2] = (x+1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+0);
+
+		indices[baseNdx + 3] = (x+0) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+0);
+		indices[baseNdx + 4] = (x+1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+1);
+		indices[baseNdx + 5] = (x+0) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+1);
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a grid with the shader. Setting u_special for vertex each tile to (special, special, 1, 1)." << tcu::TestLog::EndMessage;
+
+	// Draw grid
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				specialLoc	= gl.getUniformLocation(m_program->getProgram(), "u_special");
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+		gl.enableVertexAttribArray(positionLoc);
+
+		for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+		for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+		{
+			const deUint32		one				= 0x3F800000;
+			const tcu::UVec4	uniformValue	= tcu::UVec4(s_specialFloats[x], s_specialFloats[y], one, one);
+			const int			indexIndex		= (x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y) * 6;
+
+			gl.uniform4fv(specialLoc, 1, (const float*)uniformValue.getPtr());
+			gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, &indices[indexIndex]);
+		}
+
+		gl.disableVertexAttribArray(positionLoc);
+
+		gl.useProgram(0);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "UniformCase::iterate");
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+	}
+
+	// verify everywhere was drawn (all pixels have Green = 255)
+	if (!checkResultImage(resultImage))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing or invalid fragments");
+		return STOP;
+	}
+
+	// test drawing still works
+	if (!drawTestPattern(false))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "test pattern failed");
+		return STOP;
+	}
+
+	// all ok
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+std::string UniformCase::genVertexSource (void) const
+{
+	if (m_type == TYPE_VERTEX)
+		return
+			"attribute highp vec4 a_pos;\n"
+			"uniform highp vec4 u_special;\n"
+			"varying mediump vec4 v_out;\n"
+			"void main ()\n"
+			"{\n"
+			"	highp vec2 a1 = u_special.xz + u_special.yw; // add\n"
+			"	highp vec2 a2 = u_special.xz - u_special.yw; // sub\n"
+			"	highp vec2 a3 = u_special.xz * u_special.yw; // mul\n"
+			"	highp vec2 a4 = u_special.xz / u_special.yw; // div\n"
+			"	highp vec2 a5 = u_special.xz + u_special.yw * u_special.xz; // fma\n"
+			"\n"
+			"	highp float green = 1.0 - abs((a1.y + a2.y + a3.y + a4.y + a5.y) - 6.0);\n"
+			"	v_out = vec4(a1.x*a3.x + a2.x*a4.x, green, a5.x, 1.0);\n"
+			"	gl_Position = a_pos;\n"
+			"}\n";
+	else
+		return
+			"attribute highp vec4 a_pos;\n"
+			"void main ()\n"
+			"{\n"
+			"	gl_Position = a_pos;\n"
+			"}\n";
+}
+
+std::string UniformCase::genFragmentSource (void) const
+{
+	if (m_type == TYPE_VERTEX)
+		return s_colorPassthroughFragmentShaderSource;
+	else
+		return
+			"uniform mediump vec4 u_special;\n"
+			"void main ()\n"
+			"{\n"
+			"	mediump vec2 a1 = u_special.xz + u_special.yw; // add\n"
+			"	mediump vec2 a2 = u_special.xz - u_special.yw; // sub\n"
+			"	mediump vec2 a3 = u_special.xz * u_special.yw; // mul\n"
+			"	mediump vec2 a4 = u_special.xz / u_special.yw; // div\n"
+			"	mediump vec2 a5 = u_special.xz + u_special.yw * u_special.xz; // fma\n"
+			"	mediump vec2 a6 = mod(u_special.xz, u_special.yw);\n"
+			"	mediump vec2 a7 = mix(u_special.xz, u_special.yw, a6);\n"
+			"\n"
+			"	mediump float green = 1.0 - abs((a1.y + a2.y + a3.y + a4.y + a5.y + a6.y + a7.y) - 7.0);\n"
+			"	gl_FragColor = vec4(a1.x*a3.x, green, a5.x*a4.x + a2.x*a7.x, 1.0);\n"
+			"}\n";
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats as texture samping arguments
+ *
+ * Tests that special floats given as texture coordinates or LOD levels
+ * to sampling functions do not return invalid values (values not in the
+ * texture). Every texel's green component is 1.0.
+ *
+ * After the calculation test a test pattern is drawn to detect possible
+ * texture sampling anomalies.
+ *//*--------------------------------------------------------------------*/
+class TextureSamplerCase : public RenderCase
+{
+public:
+	enum ShaderType
+	{
+		TYPE_VERTEX = 0,
+		TYPE_FRAGMENT,
+
+		TYPE_LAST
+	};
+	enum TestType
+	{
+		TEST_TEX_COORD = 0,
+		TEST_LOD,
+		TEST_TEX_COORD_CUBE,
+
+		TEST_LAST
+	};
+
+						TextureSamplerCase			(Context& context, const char* name, const char* desc, ShaderType type, TestType testType);
+						~TextureSamplerCase			(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+private:
+	std::string			genVertexSource				(void) const;
+	std::string			genFragmentSource			(void) const;
+
+	const ShaderType	m_type;
+	const TestType		m_testType;
+	GLuint				m_textureID;
+};
+
+TextureSamplerCase::TextureSamplerCase (Context& context, const char* name, const char* desc, ShaderType type, TestType testType)
+	: RenderCase	(context, name, desc)
+	, m_type		(type)
+	, m_testType	(testType)
+	, m_textureID	(0)
+{
+	DE_ASSERT(type < TYPE_LAST);
+	DE_ASSERT(testType < TEST_LAST);
+}
+
+TextureSamplerCase::~TextureSamplerCase (void)
+{
+	deinit();
+}
+
+void TextureSamplerCase::init (void)
+{
+	// requirements
+	{
+		GLint maxTextureSize = 0;
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+		if (maxTextureSize < TEST_TEXTURE_SIZE)
+			throw tcu::NotSupportedError(std::string("GL_MAX_TEXTURE_SIZE must be at least ") + de::toString(TEST_TEXTURE_SIZE));
+	}
+
+	// vertex shader supports textures?
+	if (m_type == TYPE_VERTEX)
+	{
+		GLint maxVertexTexUnits = 0;
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTexUnits);
+		if (maxVertexTexUnits < 1)
+			throw tcu::NotSupportedError("GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS must be at least 1");
+	}
+
+	RenderCase::init();
+
+	// gen texture
+	{
+		const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+		std::vector<deUint8>	texData	(TEST_TEXTURE_SIZE*TEST_TEXTURE_SIZE*4);
+		de::Random				rnd		(12345);
+
+		gl.genTextures(1, &m_textureID);
+
+		for (int x = 0; x < TEST_TEXTURE_SIZE; ++x)
+		for (int y = 0; y < TEST_TEXTURE_SIZE; ++y)
+		{
+			// RGBA8, green and alpha channel are always 255 for verification
+			texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 0] = rnd.getUint32() & 0xFF;
+			texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 1] = 0xFF;
+			texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 2] = rnd.getUint32() & 0xFF;
+			texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 3] = 0xFF;
+		}
+
+		if (m_testType == TEST_TEX_COORD)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating a 2D texture with a test pattern." << tcu::TestLog::EndMessage;
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textureID);
+			gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData[0]);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::init");
+		}
+		else if (m_testType == TEST_LOD)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating a mipmapped 2D texture with a test pattern." << tcu::TestLog::EndMessage;
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textureID);
+
+			for (int level = 0; (TEST_TEXTURE_SIZE >> level); ++level)
+				gl.texImage2D(GL_TEXTURE_2D, level, GL_RGBA, TEST_TEXTURE_SIZE >> level, TEST_TEXTURE_SIZE >> level, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData[0]);
+
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::init");
+		}
+		else if (m_testType == TEST_TEX_COORD_CUBE)
+		{
+			DE_STATIC_ASSERT(TEST_TEXTURE_CUBE_SIZE <= TEST_TEXTURE_SIZE);
+
+			static const GLenum faces[] =
+			{
+				GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+				GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+				GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+				GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+			};
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating a cube map with a test pattern." << tcu::TestLog::EndMessage;
+
+			gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_textureID);
+
+			for (int faceNdx = 0; faceNdx < DE_LENGTH_OF_ARRAY(faces); ++faceNdx)
+				gl.texImage2D(faces[faceNdx], 0, GL_RGBA, TEST_TEXTURE_CUBE_SIZE, TEST_TEXTURE_CUBE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData[0]);
+
+			gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::init");
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+void TextureSamplerCase::deinit (void)
+{
+	RenderCase::deinit();
+
+	if (m_textureID)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.deleteTextures(1, &m_textureID);
+		m_textureID = 0;
+	}
+}
+
+TextureSamplerCase::IterateResult TextureSamplerCase::iterate (void)
+{
+	// Draw a grid and texture it with a texture and sample it using special special values. The result samples should all have the green channel at 255 as per the test image.
+
+	std::vector<tcu::Vec4>	gridVertices	(DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats));
+	std::vector<tcu::UVec2>	gridTexCoords	(DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats));
+	std::vector<deUint16>	indices			((DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * 6);
+	tcu::Surface			resultImage		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+	{
+		const float posX = (float)x / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) - 1] to [-1, 1]
+		const float posY = (float)y / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f;
+
+		gridVertices[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y]	= tcu::Vec4(posX, posY, 0.0f, 1.0f);
+		gridTexCoords[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y]	= tcu::UVec2(s_specialFloats[x], s_specialFloats[y]);
+	}
+
+	// tiles
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++y)
+	{
+		const int baseNdx = (x * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) + y) * 6;
+
+		indices[baseNdx + 0] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+		indices[baseNdx + 1] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+		indices[baseNdx + 2] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+
+		indices[baseNdx + 3] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+		indices[baseNdx + 4] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+		indices[baseNdx + 5] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a textured grid with the shader. Sampling from the texture using special floating point values." << tcu::TestLog::EndMessage;
+
+	// Draw grid
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				texCoordLoc	= gl.getAttribLocation(m_program->getProgram(), "a_attr");
+		const GLint				samplerLoc	= gl.getUniformLocation(m_program->getProgram(), "u_sampler");
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+
+		gl.uniform1i(samplerLoc, 0);
+		if (m_testType != TEST_TEX_COORD_CUBE)
+			gl.bindTexture(GL_TEXTURE_2D, m_textureID);
+		else
+			gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_textureID);
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+		gl.vertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, &gridTexCoords[0]);
+
+		gl.enableVertexAttribArray(positionLoc);
+		gl.enableVertexAttribArray(texCoordLoc);
+		gl.drawElements(GL_TRIANGLES, (glw::GLsizei)(indices.size()), GL_UNSIGNED_SHORT, &indices[0]);
+		gl.disableVertexAttribArray(positionLoc);
+		gl.disableVertexAttribArray(texCoordLoc);
+
+		gl.useProgram(0);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::iterate");
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+	}
+
+	// verify everywhere was drawn and samples were from the texture (all pixels have Green = 255)
+	if (!checkResultImage(resultImage))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing or invalid fragments");
+		return STOP;
+	}
+
+	// test drawing and textures still works
+	if (!drawTestPattern(true))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "test pattern failed");
+		return STOP;
+	}
+
+	// all ok
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+std::string TextureSamplerCase::genVertexSource (void) const
+{
+	// vertex shader is passthrough, fragment does the calculations
+	if (m_type == TYPE_FRAGMENT)
+		return s_attrPassthroughVertexShaderSource;
+
+	// vertex shader does the calculations
+	std::ostringstream buf;
+	buf <<	"attribute highp vec4 a_pos;\n"
+			"attribute highp vec2 a_attr;\n";
+
+	if (m_testType != TEST_TEX_COORD_CUBE)
+		buf <<	"uniform highp sampler2D u_sampler;\n";
+	else
+		buf <<	"uniform highp samplerCube u_sampler;\n";
+
+	buf <<	"varying mediump vec4 v_out;\n"
+			"void main ()\n"
+			"{\n";
+
+	if (m_testType == TEST_TEX_COORD)
+		buf <<	"	v_out = texture2DLod(u_sampler, a_attr, 0.0);\n";
+	else if (m_testType == TEST_LOD)
+		buf <<	"	v_out = texture2DLod(u_sampler, a_attr, a_attr.x);\n";
+	else if (m_testType == TEST_TEX_COORD_CUBE)
+		buf <<	"	v_out = textureCubeLod(u_sampler, vec3(a_attr, a_attr.x+a_attr.y), 0.0);\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf <<	"\n"
+			"	gl_Position = a_pos;\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string TextureSamplerCase::genFragmentSource (void) const
+{
+	// fragment shader is passthrough
+	if (m_type == TYPE_VERTEX)
+		return s_colorPassthroughFragmentShaderSource;
+
+	// fragment shader does the calculations
+	std::ostringstream buf;
+	if (m_testType != TEST_TEX_COORD_CUBE)
+		buf <<	"uniform mediump sampler2D u_sampler;\n";
+	else
+		buf <<	"uniform mediump samplerCube u_sampler;\n";
+
+	buf <<	"varying mediump vec4 v_attr;\n"
+			"void main ()\n"
+			"{\n";
+
+	if (m_testType == TEST_TEX_COORD)
+		buf << "	gl_FragColor = texture2D(u_sampler, v_attr.xy);\n";
+	else if (m_testType == TEST_LOD)
+		buf << "	gl_FragColor = texture2D(u_sampler, v_attr.xy, v_attr.x);\n";
+	else if (m_testType == TEST_TEX_COORD_CUBE)
+		buf <<	"	gl_FragColor = textureCube(u_sampler, vec3(v_attr.xy, v_attr.x + v_attr.y));\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats as fragment shader outputs
+ *
+ * Tests that outputting special floats from a fragment shader does not change
+ * the normal floating point values of outputted from a fragment shader. Special
+ * floats are outputted in the green component, normal floating point values
+ * in the red and blue component. Potential changes are tested by rendering
+ * test pattern two times with different floating point values. The resulting
+ * images' red and blue channels should be equal.
+ *//*--------------------------------------------------------------------*/
+class OutputCase : public FramebufferRenderCase
+{
+public:
+						OutputCase				(Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type);
+						~OutputCase				(void);
+
+	void				testFBO					(void);
+
+private:
+	std::string			genVertexSource			(void) const;
+	std::string			genFragmentSource		(void) const;
+};
+
+OutputCase::OutputCase (Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type)
+	: FramebufferRenderCase	(context, name, desc, type)
+{
+}
+
+OutputCase::~OutputCase (void)
+{
+	deinit();
+}
+
+void OutputCase::testFBO (void)
+{
+	// Create a 1 X [s_specialFloats] grid of tiles (stripes).
+	std::vector<tcu::Vec4>	gridVertices	((DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) * 2);
+	std::vector<deUint16>	indices			(DE_LENGTH_OF_ARRAY(s_specialFloats) * 6);
+	tcu::TextureFormat		textureFormat	(tcu::TextureFormat::RGBA, (m_fboType == FBO_RGBA_FLOAT16) ? (tcu::TextureFormat::FLOAT) : (tcu::TextureFormat::UNORM_INT8));
+	tcu::TextureLevel		specialImage	(textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::TextureLevel		normalImage		(textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++y)
+	{
+		const float posY = (float)y / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) ] to [-1, 1]
+
+		gridVertices[y * 2 + 0] = tcu::Vec4(-1.0, posY, 0.0f, 1.0f);
+		gridVertices[y * 2 + 1] = tcu::Vec4( 1.0, posY, 0.0f, 1.0f);
+	}
+
+	// tiles
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+	{
+		const int baseNdx = y * 6;
+
+		indices[baseNdx + 0] = (y + 0) * 2;
+		indices[baseNdx + 1] = (y + 1) * 2;
+		indices[baseNdx + 2] = (y + 1) * 2 + 1;
+
+		indices[baseNdx + 3] = (y + 0) * 2;
+		indices[baseNdx + 4] = (y + 1) * 2 + 1;
+		indices[baseNdx + 5] = (y + 0) * 2 + 1;
+	}
+
+	// Draw grids
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				specialLoc	= gl.getUniformLocation(m_program->getProgram(), "u_special");
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "pre-draw");
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+		gl.enableVertexAttribArray(positionLoc);
+
+		// draw 2 passes. Special and normal.
+		for (int passNdx = 0; passNdx < 2; ++passNdx)
+		{
+			const bool specialPass	= (passNdx == 0);
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Pass " << passNdx << ": Drawing stripes with the shader. Setting u_special for each stripe to (" << ((specialPass) ? ("special") : ("1.0")) << ")." << tcu::TestLog::EndMessage;
+
+			// draw stripes
+			gl.clear(GL_COLOR_BUFFER_BIT);
+			for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+			{
+				const deUint32	one				= 0x3F800000;
+				const deUint32	special			= s_specialFloats[y];
+				const deUint32	uniformValue	= (specialPass) ? (special) : (one);
+				const int		indexIndex		= y * 6;
+
+				gl.uniform1fv(specialLoc, 1, (const float*)&uniformValue);
+				gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, &indices[indexIndex]);
+			}
+
+			gl.finish();
+			glu::readPixels(m_context.getRenderContext(), 0, 0, ((specialPass) ? (specialImage) : (normalImage)).getAccess());
+		}
+
+		gl.disableVertexAttribArray(positionLoc);
+		gl.useProgram(0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "OutputCase::iterate");
+	}
+
+	// Check results
+	{
+		tcu::Surface		errorMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		const tcu::RGBA		badPixelColor	= tcu::RGBA::red;
+		const tcu::RGBA		okPixelColor	= tcu::RGBA::green;
+		int					badPixels		= 0;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Checking passes have identical red and blue channels and the green channel is correct in the constant pass." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < specialImage.getHeight(); ++y)
+		for (int x = 0; x < specialImage.getWidth(); ++x)
+		{
+			const float		greenThreshold	= 0.1f;
+			const tcu::Vec4	cNormal			= normalImage.getAccess().getPixel(x, y);
+			const tcu::Vec4	cSpecial		= specialImage.getAccess().getPixel(x, y);
+
+			if (cNormal.x() != cSpecial.x() || cNormal.z() != cSpecial.z() || cNormal.y() < 1.0f - greenThreshold)
+			{
+				++badPixels;
+				errorMask.setPixel(x, y, badPixelColor);
+			}
+			else
+				errorMask.setPixel(x, y, okPixelColor);
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Found " << badPixels << " invalid pixel(s)." << tcu::TestLog::EndMessage;
+
+		if (badPixels)
+		{
+			m_testCtx.getLog()
+					<< tcu::TestLog::ImageSet("Results", "Result verification")
+					<< tcu::TestLog::Image("Image with special green channel",	"Image with special green channel",		specialImage)
+					<< tcu::TestLog::Image("Image with constant green channel",	"Image with constant green channel",	normalImage)
+					<< tcu::TestLog::Image("Error Mask",						"Error Mask",							errorMask)
+					<< tcu::TestLog::EndImageSet;
+
+			// all ok?
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+		}
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+}
+
+std::string OutputCase::genVertexSource (void) const
+{
+	return
+		"attribute highp vec4 a_pos;\n"
+		"varying mediump vec2 v_pos;\n"
+		"void main ()\n"
+		"{\n"
+		"	gl_Position = a_pos;\n"
+		"	v_pos = a_pos.xy;\n"
+		"}\n";
+}
+
+std::string OutputCase::genFragmentSource (void) const
+{
+	return
+		"uniform mediump float u_special;\n"
+		"varying mediump vec2 v_pos;\n"
+		"void main ()\n"
+		"{\n"
+		"	gl_FragColor = vec4((v_pos.x + 1.0) * 0.5, u_special, (v_pos.y + 1.0) * 0.5, 1.0);\n"
+		"}\n";
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats in blending
+ *
+ * Tests special floats as alpha and color components with various blending
+ * modes. Test draws a test pattern and then does various blend operations
+ * with special float values. After the blending test another test pattern
+ * is drawn to detect possible blending anomalies. Test patterns should be
+ * identical.
+ *//*--------------------------------------------------------------------*/
+class BlendingCase : public FramebufferRenderCase
+{
+public:
+						BlendingCase			(Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type);
+						~BlendingCase			(void);
+
+	void				testFBO					(void);
+
+private:
+	void				drawTestImage			(tcu::PixelBufferAccess dst, GLuint uColorLoc, int maxVertexIndex);
+
+	std::string			genVertexSource			(void) const;
+	std::string			genFragmentSource		(void) const;
+};
+
+BlendingCase::BlendingCase (Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type)
+	: FramebufferRenderCase	(context, name, desc, type)
+{
+}
+
+BlendingCase::~BlendingCase (void)
+{
+	deinit();
+}
+
+void BlendingCase::testFBO (void)
+{
+	static const GLenum equations[] =
+	{
+		GL_FUNC_ADD,
+		GL_FUNC_SUBTRACT,
+		GL_FUNC_REVERSE_SUBTRACT,
+	};
+	static const GLenum functions[] =
+	{
+		GL_ZERO,
+		GL_ONE,
+		GL_SRC_COLOR,
+		GL_ONE_MINUS_SRC_COLOR,
+		GL_SRC_ALPHA,
+		GL_ONE_MINUS_SRC_ALPHA,
+	};
+
+	// Create a [BlendFuncs] X [s_specialFloats] grid of tiles. ( BlendFuncs = equations x functions )
+
+	const int						numBlendFuncs	= DE_LENGTH_OF_ARRAY(equations) * DE_LENGTH_OF_ARRAY(functions);
+	std::vector<tcu::Vec4>			gridVertices	((numBlendFuncs + 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1));
+	std::vector<deUint16>			indices			(numBlendFuncs * DE_LENGTH_OF_ARRAY(s_specialFloats) * 6);
+	tcu::TextureFormat				textureFormat	(tcu::TextureFormat::RGBA, (m_fboType == FBO_RGBA_FLOAT16) ? (tcu::TextureFormat::FLOAT) : (tcu::TextureFormat::UNORM_INT8));
+	tcu::TextureLevel				beforeImage		(textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::TextureLevel				afterImage		(textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++x)
+	for (int y = 0; y < numBlendFuncs + 1; ++y)
+	{
+		const float		posX	= (float)x / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f; // map from [0, len(s_specialFloats)] to [-1, 1]
+		const float		posY	= (float)y / (float)numBlendFuncs * 2.0f - 1.0f;
+
+		gridVertices[x * (numBlendFuncs + 1) + y]	= tcu::Vec4(posX, posY, 0.0f, 1.0f);
+	}
+
+	// tiles
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+	for (int y = 0; y < numBlendFuncs; ++y)
+	{
+		const int baseNdx = (x * numBlendFuncs + y) * 6;
+
+		indices[baseNdx + 0] = (x+0) * (numBlendFuncs + 1) + (y+0);
+		indices[baseNdx + 1] = (x+1) * (numBlendFuncs + 1) + (y+1);
+		indices[baseNdx + 2] = (x+1) * (numBlendFuncs + 1) + (y+0);
+
+		indices[baseNdx + 3] = (x+0) * (numBlendFuncs + 1) + (y+0);
+		indices[baseNdx + 4] = (x+1) * (numBlendFuncs + 1) + (y+1);
+		indices[baseNdx + 5] = (x+0) * (numBlendFuncs + 1) + (y+1);
+	}
+
+	// Draw tiles
+	{
+		const int				numPasses	= 5;
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				specialLoc	= gl.getUniformLocation(m_program->getProgram(), "u_special");
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+		gl.enable(GL_BLEND);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "pre-draw");
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+		gl.enableVertexAttribArray(positionLoc);
+
+		// draw "before" image
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing pre-draw pattern." << tcu::TestLog::EndMessage;
+		drawTestImage(beforeImage.getAccess(), specialLoc, (int)gridVertices.size() - 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "pre-draw pattern");
+
+		// draw multiple passes with special floats
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		for (int passNdx = 0; passNdx < numPasses; ++passNdx)
+		{
+			de::Random rnd(123 + 567 * passNdx);
+
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "Pass " << passNdx << ": Drawing tiles with the shader.\n"
+				<< "\tVarying u_special for each tile.\n"
+				<< "\tVarying blend function and blend equation for each tile.\n"
+				<< tcu::TestLog::EndMessage;
+
+			// draw tiles
+			for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+			for (int y = 0; y < numBlendFuncs; ++y)
+			{
+				const GLenum		blendEquation		= equations[y % DE_LENGTH_OF_ARRAY(equations)];
+				const GLenum		blendFunction		= functions[y / DE_LENGTH_OF_ARRAY(equations)];
+				const GLenum		blendFunctionDst	= rnd.choose<GLenum>(DE_ARRAY_BEGIN(functions), DE_ARRAY_END(functions));
+				const int			indexIndex			= (x * numBlendFuncs + y) * 6;
+
+				// "rnd.get"s are run in a deterministic order
+				const deUint32		componentR			= rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats));
+				const deUint32		componentG			= rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats));
+				const deUint32		componentB			= rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats));
+				const deUint32		componentA			= rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats));
+				const tcu::UVec4	uniformValue		= tcu::UVec4(componentR, componentG, componentB, componentA);
+
+				gl.uniform4fv(specialLoc, 1, (const float*)uniformValue.getPtr());
+				gl.blendEquation(blendEquation);
+				gl.blendFunc(blendFunction, blendFunctionDst);
+				gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, &indices[indexIndex]);
+			}
+		}
+		GLU_EXPECT_NO_ERROR(gl.getError(), "special passes");
+
+		// draw "after" image
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing post-draw pattern." << tcu::TestLog::EndMessage;
+		drawTestImage(afterImage.getAccess(), specialLoc, (int)gridVertices.size() - 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "post-draw pattern");
+
+		gl.disableVertexAttribArray(positionLoc);
+		gl.useProgram(0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "OutputCase::iterate");
+	}
+
+	// Check results
+	{
+		tcu::Surface		errorMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		const tcu::RGBA		badPixelColor	= tcu::RGBA::red;
+		const tcu::RGBA		okPixelColor	= tcu::RGBA::green;
+		int					badPixels		= 0;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Checking patterns are identical." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < beforeImage.getHeight(); ++y)
+		for (int x = 0; x < beforeImage.getWidth(); ++x)
+		{
+			const tcu::Vec4	cBefore	= beforeImage.getAccess().getPixel(x, y);
+			const tcu::Vec4	cAfter	= afterImage.getAccess().getPixel(x, y);
+
+			if (cBefore != cAfter)
+			{
+				++badPixels;
+				errorMask.setPixel(x, y, badPixelColor);
+			}
+			else
+				errorMask.setPixel(x, y, okPixelColor);
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Found " << badPixels << " invalid pixel(s)." << tcu::TestLog::EndMessage;
+
+		if (badPixels)
+		{
+			m_testCtx.getLog()
+					<< tcu::TestLog::ImageSet("Results", "Result verification")
+					<< tcu::TestLog::Image("Pattern drawn before special float blending",	"Pattern drawn before special float blending",		beforeImage)
+					<< tcu::TestLog::Image("Pattern drawn after special float blending",	"Pattern drawn after special float blending",		afterImage)
+					<< tcu::TestLog::EndImageSet;
+
+			// all ok?
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+		}
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+}
+
+void BlendingCase::drawTestImage (tcu::PixelBufferAccess dst, GLuint uColorLoc, int maxVertexIndex)
+{
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+	de::Random				rnd	(123);
+
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	gl.blendEquation(GL_FUNC_ADD);
+	gl.blendFunc(GL_ONE, GL_ONE);
+
+	for (int tri = 0; tri < 20; ++tri)
+	{
+		tcu::Vec4 color;
+		color.x() = rnd.getFloat();
+		color.y() = rnd.getFloat();
+		color.z() = rnd.getFloat();
+		color.w() = rnd.getFloat();
+		gl.uniform4fv(uColorLoc, 1, color.getPtr());
+
+		deUint16 indices[3];
+		indices[0] = rnd.getInt(0, maxVertexIndex);
+		indices[1] = rnd.getInt(0, maxVertexIndex);
+		indices[2] = rnd.getInt(0, maxVertexIndex);
+
+		gl.drawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, indices);
+	}
+
+	gl.finish();
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst);
+}
+
+std::string BlendingCase::genVertexSource (void) const
+{
+	return
+		"attribute highp vec4 a_pos;\n"
+		"void main ()\n"
+		"{\n"
+		"	gl_Position = a_pos;\n"
+		"}\n";
+}
+
+std::string BlendingCase::genFragmentSource (void) const
+{
+	return
+		"uniform mediump vec4 u_special;\n"
+		"void main ()\n"
+		"{\n"
+		"	gl_FragColor = u_special;\n"
+		"}\n";
+}
+
+} //anonymous
+
+SpecialFloatTests::SpecialFloatTests (Context& context)
+	: TestCaseGroup(context, "special_float", "Special float tests")
+{
+}
+
+SpecialFloatTests::~SpecialFloatTests (void)
+{
+}
+
+void SpecialFloatTests::init (void)
+{
+	tcu::TestCaseGroup* const vertexGroup	= new tcu::TestCaseGroup(m_testCtx, "vertex",	"Run vertex shader with special float values");
+	tcu::TestCaseGroup* const fragmentGroup	= new tcu::TestCaseGroup(m_testCtx, "fragment",	"Run fragment shader with special float values");
+	tcu::TestCaseGroup* const framebufferGroup	= new tcu::TestCaseGroup(m_testCtx, "framebuffer",	"Test framebuffers containing special float values");
+
+	// .vertex
+	{
+		vertexGroup->addChild(new VertexAttributeCase	(m_context, "attribute_buffer",			"special attribute values in a buffer",					VertexAttributeCase::STORAGE_BUFFER, VertexAttributeCase::TYPE_VERTEX));
+		vertexGroup->addChild(new VertexAttributeCase	(m_context, "attribute_client",			"special attribute values in a client storage",			VertexAttributeCase::STORAGE_CLIENT, VertexAttributeCase::TYPE_VERTEX));
+		vertexGroup->addChild(new UniformCase			(m_context, "uniform",					"special uniform values",								UniformCase::TYPE_VERTEX));
+		vertexGroup->addChild(new TextureSamplerCase	(m_context, "sampler_tex_coord",		"special texture coords",								TextureSamplerCase::TYPE_VERTEX, TextureSamplerCase::TEST_TEX_COORD));
+		vertexGroup->addChild(new TextureSamplerCase	(m_context, "sampler_tex_coord_cube",	"special texture coords to cubemap",					TextureSamplerCase::TYPE_VERTEX, TextureSamplerCase::TEST_TEX_COORD_CUBE));
+		vertexGroup->addChild(new TextureSamplerCase	(m_context, "sampler_lod",				"special texture lod",									TextureSamplerCase::TYPE_VERTEX, TextureSamplerCase::TEST_LOD));
+
+		addChild(vertexGroup);
+	}
+
+	// .fragment
+	{
+		fragmentGroup->addChild(new VertexAttributeCase	(m_context, "attribute_buffer",			"special attribute values in a buffer",					VertexAttributeCase::STORAGE_BUFFER, VertexAttributeCase::TYPE_FRAGMENT));
+		fragmentGroup->addChild(new VertexAttributeCase	(m_context, "attribute_client",			"special attribute values in a client storage",			VertexAttributeCase::STORAGE_CLIENT, VertexAttributeCase::TYPE_FRAGMENT));
+		fragmentGroup->addChild(new UniformCase			(m_context, "uniform",					"special uniform values",								UniformCase::TYPE_FRAGMENT));
+		fragmentGroup->addChild(new TextureSamplerCase	(m_context, "sampler_tex_coord",		"special texture coords",								TextureSamplerCase::TYPE_FRAGMENT, TextureSamplerCase::TEST_TEX_COORD));
+		fragmentGroup->addChild(new TextureSamplerCase	(m_context, "sampler_tex_coord_cube",	"special texture coords to cubemap",					TextureSamplerCase::TYPE_FRAGMENT, TextureSamplerCase::TEST_TEX_COORD_CUBE));
+		fragmentGroup->addChild(new TextureSamplerCase	(m_context, "sampler_lod",				"special texture lod",									TextureSamplerCase::TYPE_FRAGMENT, TextureSamplerCase::TEST_LOD));
+
+		addChild(fragmentGroup);
+	}
+
+	// .framebuffer
+	{
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_default",			"write special floating point values to default framebuffer",	FramebufferRenderCase::FBO_DEFAULT));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_rgba",				"write special floating point values to RGBA framebuffer",		FramebufferRenderCase::FBO_RGBA));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_rgba4",				"write special floating point values to RGBA4 framebuffer",		FramebufferRenderCase::FBO_RGBA4));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_rgb5_a1",			"write special floating point values to RGB5_A1 framebuffer",	FramebufferRenderCase::FBO_RGB5_A1));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_rgb565",				"write special floating point values to RGB565 framebuffer",	FramebufferRenderCase::FBO_RGB565));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_float16",			"write special floating point values to float16 framebuffer",	FramebufferRenderCase::FBO_RGBA_FLOAT16));
+
+		framebufferGroup->addChild(new BlendingCase		(m_context, "blend_default",			"blend special floating point values in a default framebuffer",	FramebufferRenderCase::FBO_DEFAULT));
+		framebufferGroup->addChild(new BlendingCase		(m_context, "blend_rgba",				"blend special floating point values in a RGBA framebuffer",	FramebufferRenderCase::FBO_RGBA));
+		framebufferGroup->addChild(new BlendingCase		(m_context, "blend_float16",			"blend special floating point values in a float16 framebuffer",	FramebufferRenderCase::FBO_RGBA_FLOAT16));
+
+		addChild(framebufferGroup);
+	}
+}
+
+} // Stress
+} // gles2
+} // deqp
diff --git a/modules/gles2/stress/es2sSpecialFloatTests.hpp b/modules/gles2/stress/es2sSpecialFloatTests.hpp
new file mode 100644
index 0000000..ec010db
--- /dev/null
+++ b/modules/gles2/stress/es2sSpecialFloatTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2SSPECIALFLOATTESTS_HPP
+#define _ES2SSPECIALFLOATTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Special float stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+
+class SpecialFloatTests : public TestCaseGroup
+{
+public:
+						SpecialFloatTests	(Context& context);
+						~SpecialFloatTests	(void);
+
+	void				init				(void);
+
+private:
+						SpecialFloatTests	(const SpecialFloatTests& other);
+	SpecialFloatTests&	operator=			(const SpecialFloatTests& other);
+};
+
+} // Stress
+} // gles2
+} // deqp
+
+#endif // _ES2SSPECIALFLOATTESTS_HPP
diff --git a/modules/gles2/stress/es2sStressTests.cpp b/modules/gles2/stress/es2sStressTests.cpp
new file mode 100644
index 0000000..a582649
--- /dev/null
+++ b/modules/gles2/stress/es2sStressTests.cpp
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es2sStressTests.hpp"
+#include "es2sMemoryTests.hpp"
+#include "es2sLongRunningTests.hpp"
+#include "es2sSpecialFloatTests.hpp"
+#include "es2sVertexArrayTests.hpp"
+#include "es2sDrawTests.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+
+StressTests::StressTests (Context& context)
+	: TestCaseGroup(context, "stress", "Stress tests")
+{
+}
+
+StressTests::~StressTests (void)
+{
+}
+
+void StressTests::init (void)
+{
+	addChild(new MemoryTests		(m_context));
+	addChild(new LongRunningTests	(m_context));
+	addChild(new SpecialFloatTests	(m_context));
+	addChild(new VertexArrayTests	(m_context));
+	addChild(new DrawTests			(m_context));
+}
+
+} // Stress
+} // gles2
+} // deqp
diff --git a/modules/gles2/stress/es2sStressTests.hpp b/modules/gles2/stress/es2sStressTests.hpp
new file mode 100644
index 0000000..8588b2e
--- /dev/null
+++ b/modules/gles2/stress/es2sStressTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2SSTRESSTESTS_HPP
+#define _ES2SSTRESSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+
+class StressTests : public TestCaseGroup
+{
+public:
+					StressTests		(Context& context);
+					~StressTests	(void);
+
+	void			init			(void);
+
+private:
+					StressTests		(const StressTests& other);
+	StressTests&	operator=		(const StressTests& other);
+};
+
+} // Stress
+} // gles2
+} // deqp
+
+#endif // _ES2SSTRESSTESTS_HPP
diff --git a/modules/gles2/stress/es2sVertexArrayTests.cpp b/modules/gles2/stress/es2sVertexArrayTests.cpp
new file mode 100644
index 0000000..b2e3e77
--- /dev/null
+++ b/modules/gles2/stress/es2sVertexArrayTests.cpp
@@ -0,0 +1,351 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array and buffer unaligned access stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es2sVertexArrayTests.hpp"
+#include "glsVertexArrayTests.hpp"
+
+#include "glwEnums.hpp"
+
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+namespace
+{
+
+template<class T>
+static std::string typeToString (T t)
+{
+	std::stringstream strm;
+	strm << t;
+	return strm.str();
+}
+
+class SingleVertexArrayUsageTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayUsageTests		(Context& context);
+	virtual							~SingleVertexArrayUsageTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayUsageTests		(const SingleVertexArrayUsageTests& other);
+	SingleVertexArrayUsageTests&	operator=						(const SingleVertexArrayUsageTests& other);
+};
+
+SingleVertexArrayUsageTests::SingleVertexArrayUsageTests (Context& context)
+	: TestCaseGroup(context, "usages", "Single vertex atribute, usage")
+{
+}
+
+SingleVertexArrayUsageTests::~SingleVertexArrayUsageTests (void)
+{
+}
+
+void SingleVertexArrayUsageTests::init (void)
+{
+	// Test usage
+	Array::Usage		usages[]		= {Array::USAGE_STATIC_DRAW, Array::USAGE_STREAM_DRAW, Array::USAGE_DYNAMIC_DRAW};
+	int					counts[]		= {1, 256};
+	int					strides[]		= {17};
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_FIXED, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE};
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+		{
+			for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+			{
+				for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usages); usageNdx++)
+				{
+					const int	componentCount	= 2;
+					const int	stride			= (strides[strideNdx] < 0 ? Array::inputTypeSize(inputTypes[inputTypeNdx]) * componentCount : strides[strideNdx]);
+					const bool	aligned			= (stride % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0;
+					MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																	Array::OUTPUTTYPE_VEC2,
+																	Array::STORAGE_BUFFER,
+																	usages[usageNdx],
+																	componentCount,
+																	0,
+																	stride,
+																	false,
+																	GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																	GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+					MultiVertexArrayTest::Spec spec;
+					spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+					spec.drawCount	= counts[countNdx];
+					spec.first		= 0;
+					spec.arrays.push_back(arraySpec);
+
+					std::string name = spec.getName();
+
+					if (!aligned)
+						addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayStrideTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayStrideTests	(Context& context);
+	virtual							~SingleVertexArrayStrideTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayStrideTests	(const SingleVertexArrayStrideTests& other);
+	SingleVertexArrayStrideTests&	operator=						(const SingleVertexArrayStrideTests& other);
+};
+
+SingleVertexArrayStrideTests::SingleVertexArrayStrideTests (Context& context)
+	: TestCaseGroup(context, "strides", "Single stride vertex atribute")
+{
+}
+
+SingleVertexArrayStrideTests::~SingleVertexArrayStrideTests (void)
+{
+}
+
+void SingleVertexArrayStrideTests::init (void)
+{
+	// Test strides with different input types, component counts and storage, Usage(?)
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE, /*Array::INPUTTYPE_UNSIGNED_SHORT, Array::INPUTTYPE_UNSIGNED_BYTE,*/ Array::INPUTTYPE_FIXED};
+	Array::Storage		storages[]		= {Array::STORAGE_BUFFER};
+	int					counts[]		= {1, 256};
+	int					strides[]		= {17};
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int storageNdx = 0; storageNdx < DE_LENGTH_OF_ARRAY(storages); storageNdx++)
+		{
+			for (int componentCount = 2; componentCount < 5; componentCount++)
+			{
+				for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+				{
+					for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+					{
+						const int	stride			= (strides[strideNdx] < 0 ? Array::inputTypeSize(inputTypes[inputTypeNdx]) * componentCount : strides[strideNdx]);
+						const bool	bufferUnaligned	= (storages[storageNdx] == Array::STORAGE_BUFFER) && (stride % Array::inputTypeSize(inputTypes[inputTypeNdx])) != 0;
+
+						MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																		Array::OUTPUTTYPE_VEC4,
+																		storages[storageNdx],
+																		Array::USAGE_DYNAMIC_DRAW,
+																		componentCount,
+																		0,
+																		stride,
+																		false,
+																		GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																		GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+						MultiVertexArrayTest::Spec spec;
+						spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+						spec.drawCount	= counts[countNdx];
+						spec.first		= 0;
+						spec.arrays.push_back(arraySpec);
+
+						std::string name = spec.getName();
+						if (bufferUnaligned)
+							addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+					}
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayFirstTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayFirstTests	(Context& context);
+	virtual							~SingleVertexArrayFirstTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayFirstTests	(const SingleVertexArrayFirstTests& other);
+	SingleVertexArrayFirstTests&	operator=						(const SingleVertexArrayFirstTests& other);
+};
+
+SingleVertexArrayFirstTests::SingleVertexArrayFirstTests (Context& context)
+	: TestCaseGroup(context, "first", "Single vertex atribute different first values")
+{
+}
+
+SingleVertexArrayFirstTests::~SingleVertexArrayFirstTests (void)
+{
+}
+
+void SingleVertexArrayFirstTests::init (void)
+{
+	// Test strides with different input types, component counts and storage, Usage(?)
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_FIXED};
+	int					counts[]		= {5, 256};
+	int					firsts[]		= {6, 24};
+	int					offsets[]		= {1, 17};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(offsets); offsetNdx++)
+		{
+			for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+			{
+				for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+				{
+					for (int firstNdx = 0; firstNdx < DE_LENGTH_OF_ARRAY(firsts); firstNdx++)
+					{
+						const int	stride	= (strides[strideNdx] < 0 ? Array::inputTypeSize(inputTypes[inputTypeNdx]) * 2 : strides[strideNdx]);
+						const bool	aligned	= ((stride % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0) && (offsets[offsetNdx] % Array::inputTypeSize(inputTypes[inputTypeNdx]) == 0);
+
+						MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																		Array::OUTPUTTYPE_VEC2,
+																		Array::STORAGE_BUFFER,
+																		Array::USAGE_DYNAMIC_DRAW,
+																		2,
+																		offsets[offsetNdx],
+																		stride,
+																		false,
+																		GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																		GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+						MultiVertexArrayTest::Spec spec;
+						spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+						spec.drawCount	= counts[countNdx];
+						spec.first		= firsts[firstNdx];
+						spec.arrays.push_back(arraySpec);
+
+						std::string name = Array::inputTypeToString(inputTypes[inputTypeNdx]) + "_first" + typeToString(firsts[firstNdx]) + "_offset" + typeToString(offsets[offsetNdx]) + "_stride" + typeToString(stride) + "_quads" + typeToString(counts[countNdx]);
+						if (!aligned)
+							addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+					}
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayOffsetTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayOffsetTests	(Context& context);
+	virtual							~SingleVertexArrayOffsetTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayOffsetTests	(const SingleVertexArrayOffsetTests& other);
+	SingleVertexArrayOffsetTests&	operator=						(const SingleVertexArrayOffsetTests& other);
+};
+
+SingleVertexArrayOffsetTests::SingleVertexArrayOffsetTests (Context& context)
+	: TestCaseGroup(context, "offset", "Single vertex atribute offset element")
+{
+}
+
+SingleVertexArrayOffsetTests::~SingleVertexArrayOffsetTests (void)
+{
+}
+
+void SingleVertexArrayOffsetTests::init (void)
+{
+	// Test strides with different input types, component counts and storage, Usage(?)
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_FIXED};
+	int					counts[]		= {1, 256};
+	int					offsets[]		= {1, 4, 17, 32};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(offsets); offsetNdx++)
+		{
+			for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+			{
+				for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+				{
+					const int	stride	= (strides[strideNdx] < 0 ? Array::inputTypeSize(inputTypes[inputTypeNdx]) * 2 : strides[strideNdx]);
+					const bool	aligned	= ((stride % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0) && ((offsets[offsetNdx] % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0);
+
+					MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																	Array::OUTPUTTYPE_VEC2,
+																	Array::STORAGE_BUFFER,
+																	Array::USAGE_DYNAMIC_DRAW,
+																	2,
+																	offsets[offsetNdx],
+																	stride,
+																	false,
+																	GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																	GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+					MultiVertexArrayTest::Spec spec;
+					spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+					spec.drawCount	= counts[countNdx];
+					spec.first		= 0;
+					spec.arrays.push_back(arraySpec);
+
+					std::string name = spec.getName();
+					if (!aligned)
+						addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+				}
+			}
+		}
+	}
+}
+
+} // anonymous
+
+VertexArrayTests::VertexArrayTests (Context& context)
+	: TestCaseGroup(context, "vertex_arrays", "Vertex array and array tests")
+{
+}
+
+VertexArrayTests::~VertexArrayTests (void)
+{
+}
+
+void VertexArrayTests::init (void)
+{
+	tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "single_attribute", "Single attribute");
+	addChild(group);
+
+	// .single_attribute
+	{
+		group->addChild(new SingleVertexArrayStrideTests(m_context));
+		group->addChild(new SingleVertexArrayUsageTests(m_context));
+		group->addChild(new SingleVertexArrayOffsetTests(m_context));
+		group->addChild(new SingleVertexArrayFirstTests(m_context));
+	}
+}
+
+} // Stress
+} // gles2
+} // deqp
diff --git a/modules/gles2/stress/es2sVertexArrayTests.hpp b/modules/gles2/stress/es2sVertexArrayTests.hpp
new file mode 100644
index 0000000..fa8e58d
--- /dev/null
+++ b/modules/gles2/stress/es2sVertexArrayTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES2SVERTEXARRAYTESTS_HPP
+#define _ES2SVERTEXARRAYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array and buffer unaligned access stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+namespace Stress
+{
+
+class VertexArrayTests : public TestCaseGroup
+{
+public:
+						VertexArrayTests	(Context& testCtx);
+						~VertexArrayTests	(void);
+
+	void				init				(void);
+
+private:
+						VertexArrayTests	(const VertexArrayTests& other);
+	VertexArrayTests&	operator=			(const VertexArrayTests& other);
+};
+
+} // Stress
+} // gles2
+} // deqp
+
+#endif // _ES2SVERTEXARRAYTESTS_HPP
diff --git a/modules/gles2/tes2CapabilityTests.cpp b/modules/gles2/tes2CapabilityTests.cpp
new file mode 100644
index 0000000..b40a763
--- /dev/null
+++ b/modules/gles2/tes2CapabilityTests.cpp
@@ -0,0 +1,282 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Capability Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2CapabilityTests.hpp"
+#include "gluStrUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "deStringUtil.hpp"
+#include "gluContextInfo.hpp"
+
+#include <algorithm>
+#include <iterator>
+
+#include "glw.h"
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+namespace gles2
+{
+
+class GetIntCase : public tcu::TestCase
+{
+public:
+	GetIntCase (Context& context, const char* name, const char* description, GLenum param)
+		: tcu::TestCase	(context.getTestContext(), tcu::NODETYPE_CAPABILITY, name, description)
+		, m_param		(param)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		GLint value;
+		GLU_CHECK_CALL(glGetIntegerv(m_param, &value));
+
+		m_testCtx.getLog() << TestLog::Message << glu::getParamQueryStr(m_param) << " = " << value << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(value).c_str());
+		return STOP;
+	}
+
+private:
+	GLenum	m_param;
+};
+
+class LimitTests : public TestCaseGroup
+{
+public:
+	LimitTests (Context& context)
+		: TestCaseGroup(context, "limits", "Implementation-defined limits")
+	{
+	}
+
+	void init (void)
+	{
+		static const struct
+		{
+			const char*		name;
+			const char*		description;
+			GLenum			param;
+		} getIntCases[] =
+		{
+			{ "vertex_attribs",					"Number of vertex attributes supported",							GL_MAX_VERTEX_ATTRIBS					},
+			{ "varying_vectors",				"Number of varying vectors supported",								GL_MAX_VARYING_VECTORS					},
+			{ "vertex_uniform_vectors",			"Number of vertex uniform vectors supported",						GL_MAX_VERTEX_UNIFORM_VECTORS			},
+			{ "fragment_uniform_vectors",		"Number of fragment uniform vectors supported",						GL_MAX_FRAGMENT_UNIFORM_VECTORS			},
+			{ "texture_image_units",			"Number of fragment texture units supported",						GL_MAX_TEXTURE_IMAGE_UNITS				},
+			{ "vertex_texture_image_units",		"Number of vertex texture units supported",							GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS		},
+			{ "combined_texture_image_units",	"Number of vertex and fragment combined texture units supported",	GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS		},
+			{ "texture_2d_size",				"Maximum 2D texture size",											GL_MAX_TEXTURE_SIZE						},
+			{ "texture_cube_size",				"Maximum cubemap texture size",										GL_MAX_CUBE_MAP_TEXTURE_SIZE			},
+			{ "renderbuffer_size",				"Maximum renderbuffer size",										GL_MAX_RENDERBUFFER_SIZE				},
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(getIntCases); ndx++)
+			addChild(new GetIntCase(m_context, getIntCases[ndx].name, getIntCases[ndx].description, getIntCases[ndx].param));
+	}
+};
+
+class ExtensionCase : public tcu::TestCase
+{
+public:
+								ExtensionCase				(tcu::TestContext& testCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, const char* extName);
+
+	IterateResult				iterate						(void);
+
+private:
+	const glu::ContextInfo&		m_ctxInfo;
+	std::string					m_extName;
+};
+
+ExtensionCase::ExtensionCase (tcu::TestContext& testCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, const char* extName)
+	: tcu::TestCase		(testCtx, tcu::NODETYPE_CAPABILITY, name, desc)
+	, m_ctxInfo			(ctxInfo)
+	, m_extName			(extName)
+{
+}
+
+ExtensionCase::IterateResult ExtensionCase::iterate (void)
+{
+	bool isSupported = std::find(m_ctxInfo.getExtensions().begin(), m_ctxInfo.getExtensions().end(), m_extName) != m_ctxInfo.getExtensions().end();
+	m_testCtx.setTestResult(isSupported ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_NOT_SUPPORTED,
+							isSupported ? "Supported"			: "Not supported");
+	return STOP;
+}
+
+class ExtensionTests : public TestCaseGroup
+{
+public:
+	ExtensionTests (Context& context)
+		: TestCaseGroup(context, "extensions", "Supported extensions")
+	{
+	}
+
+	void init (void)
+	{
+		struct ExtGroup
+		{
+			tcu::TestCaseGroup*		group;
+			const glu::ContextInfo&	ctxInfo;
+
+			ExtGroup (TestCaseGroup* parent, const char* name, const char* desc)
+				: group		(DE_NULL)
+				, ctxInfo	(parent->getContext().getContextInfo())
+			{
+				group = new tcu::TestCaseGroup(parent->getTestContext(), name, desc);
+				parent->addChild(group);
+			}
+
+			ExtGroup& operator<< (const char* extName)
+			{
+				group->addChild(new ExtensionCase(group->getTestContext(), ctxInfo, extName, "", extName));
+				return *this;
+			}
+		};
+
+		// Uncompressed formats.
+		ExtGroup(this, "uncompressed_texture_formats", "Uncompressed texture formats")
+			<< "GL_OES_texture_float_linear"
+			<< "GL_OES_texture_half_float_linear"
+			<< "GL_OES_texture_float"
+			<< "GL_OES_texture_half_float"
+			<< "GL_OES_texture_npot"
+			<< "GL_EXT_texture_format_BGRA8888"
+			<< "GL_EXT_texture_rg"
+			<< "GL_EXT_texture_type_2_10_10_10_REV"
+			<< "GL_EXT_sRGB"
+			<< "GL_APPLE_rgb_422"
+			<< "GL_APPLE_texture_format_BGRA8888";
+
+		// Compressed formats.
+		ExtGroup(this, "compressed_texture_formats", "Compressed texture formats")
+			<< "GL_OES_compressed_ETC1_RGB8_texture"
+			<< "GL_OES_compressed_paletted_texture"
+			<< "GL_EXT_texture_compression_dxt1"
+			<< "GL_AMD_compressed_3DC_texture"
+			<< "GL_AMD_compressed_ATC_texture"
+			<< "GL_IMG_texture_compression_pvrtc"
+			<< "GL_NV_texture_compression_s3tc_update";
+
+		// Texture features.
+		ExtGroup(this, "texture", "Texturing features")
+			<< "GL_OES_texture_3D"
+			<< "GL_OES_depth_texture"
+			<< "GL_EXT_texture_filter_anisotropic"
+			<< "GL_EXT_texture_lod_bias"
+			<< "GL_EXT_shadow_samplers"
+			<< "GL_EXT_texture_storage"
+			<< "GL_NV_texture_npot_2D_mipmap"
+			<< "GL_APPLE_texture_max_level";
+
+		// FBO features
+		ExtGroup(this, "fbo", "FBO features")
+			<< "GL_OES_depth24"
+			<< "GL_OES_depth32"
+			<< "GL_OES_packed_depth_stencil"
+			<< "GL_OES_fbo_render_mipmap"
+			<< "GL_OES_rgb8_rgba8"
+			<< "GL_OES_stencil1"
+			<< "GL_OES_stencil4"
+			<< "GL_OES_stencil8"
+			<< "GL_EXT_color_buffer_half_float"
+			<< "GL_EXT_multisampled_render_to_texture"
+			<< "GL_IMG_multisampled_render_to_texture"
+			<< "GL_ARM_rgba8"
+			<< "GL_NV_depth_nonlinear"
+			<< "GL_NV_draw_buffers"
+			<< "GL_NV_fbo_color_attachments"
+			<< "GL_NV_read_buffer"
+			<< "GL_APPLE_framebuffer_multisample";
+
+		// Vertex data formats.
+		ExtGroup(this, "vertex_data_formats", "Vertex data formats")
+			<< "GL_OES_element_index_uint"
+			<< "GL_OES_vertex_half_float"
+			<< "GL_OES_vertex_type_10_10_10_2";
+
+		// Shader functionality.
+		ExtGroup(this, "shaders", "Shader features")
+			<< "GL_OES_fragment_precision_high"
+			<< "GL_OES_standard_derivatives"
+			<< "GL_EXT_shader_texture_lod"
+			<< "GL_EXT_frag_depth"
+			<< "GL_EXT_separate_shader_objects";
+
+		// Shader binary formats.
+		ExtGroup(this, "shader_binary_formats", "Shader binary formats")
+			<< "GL_OES_get_program_binary"
+			<< "GL_AMD_program_binary_Z400"
+			<< "GL_IMG_shader_binary"
+			<< "GL_IMG_program_binary"
+			<< "GL_ARM_mali_shader_binary"
+			<< "GL_VIV_shader_binary"
+			<< "GL_DMP_shader_binary";
+
+		// Development features.
+		ExtGroup(this, "development", "Development aids")
+			<< "GL_EXT_debug_label"
+			<< "GL_EXT_debug_marker"
+			<< "GL_AMD_performance_monitor"
+			<< "GL_QCOM_performance_monitor_global_mode"
+			<< "GL_QCOM_extended_get"
+			<< "GL_QCOM_extended_get2";
+
+		// Other extensions.
+		ExtGroup(this, "other", "Other extensions")
+			<< "GL_OES_draw_texture"
+			<< "GL_OES_mapbuffer"
+			<< "GL_OES_vertex_array_object"
+			<< "GL_EXT_occlusion_query_boolean"
+			<< "GL_EXT_robustness"
+			<< "GL_EXT_discard_framebuffer"
+			<< "GL_EXT_read_format_bgra"
+			<< "GL_EXT_multi_draw_arrays"
+			<< "GL_EXT_unpack_subimage"
+			<< "GL_EXT_blend_minmax"
+			<< "GL_IMG_read_format"
+			<< "GL_NV_coverage_sample"
+			<< "GL_NV_read_depth_stencil"
+			<< "GL_SUN_multi_draw_arrays";
+	}
+};
+
+CapabilityTests::CapabilityTests (Context& context)
+	: TestCaseGroup(context, "capability", "Capability Tests")
+{
+}
+
+CapabilityTests::~CapabilityTests (void)
+{
+}
+
+void CapabilityTests::init (void)
+{
+	addChild(new LimitTests		(m_context));
+	addChild(new ExtensionTests	(m_context));
+}
+
+} // gles2
+} // deqp
diff --git a/modules/gles2/tes2CapabilityTests.hpp b/modules/gles2/tes2CapabilityTests.hpp
new file mode 100644
index 0000000..fcea4f1
--- /dev/null
+++ b/modules/gles2/tes2CapabilityTests.hpp
@@ -0,0 +1,50 @@
+#ifndef _TES2CAPABILITYTESTS_HPP
+#define _TES2CAPABILITYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Capability Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+
+class CapabilityTests : public TestCaseGroup
+{
+public:
+						CapabilityTests		(Context& context);
+						~CapabilityTests	(void);
+
+	void				init				(void);
+
+private:
+						CapabilityTests		(const CapabilityTests& other);
+	CapabilityTests&	operator=			(const CapabilityTests& other);
+};
+
+} // gles2
+} // deqp
+
+#endif // _TES2CAPABILITYTESTS_HPP
diff --git a/modules/gles2/tes2Context.cpp b/modules/gles2/tes2Context.cpp
new file mode 100644
index 0000000..74b6667
--- /dev/null
+++ b/modules/gles2/tes2Context.cpp
@@ -0,0 +1,79 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 2.0 test context.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2Context.hpp"
+#include "gluContextFactory.hpp"
+#include "gluRenderContext.hpp"
+#include "gluRenderConfig.hpp"
+#include "gluFboRenderContext.hpp"
+#include "gluContextInfo.hpp"
+#include "tcuCommandLine.hpp"
+#include "glwWrapper.hpp"
+
+#include "gluDefs.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+
+Context::Context (tcu::TestContext& testCtx)
+	: m_testCtx		(testCtx)
+	, m_renderCtx	(DE_NULL)
+	, m_contextInfo	(DE_NULL)
+{
+	try
+	{
+		m_renderCtx		= glu::createDefaultRenderContext(m_testCtx.getPlatform(), m_testCtx.getCommandLine(), glu::ApiType::es(2,0));
+		m_contextInfo	= glu::ContextInfo::create(*m_renderCtx);
+
+		// Set up function table for transparent wrapper.
+		glw::setCurrentThreadFunctions(&m_renderCtx->getFunctions());
+	}
+	catch (...)
+	{
+		glw::setCurrentThreadFunctions(DE_NULL);
+
+		delete m_contextInfo;
+		delete m_renderCtx;
+
+		throw;
+	}
+}
+
+Context::~Context (void)
+{
+	// Remove functions from wrapper.
+	glw::setCurrentThreadFunctions(DE_NULL);
+
+	delete m_contextInfo;
+	delete m_renderCtx;
+}
+
+const tcu::RenderTarget& Context::getRenderTarget (void) const
+{
+	return m_renderCtx->getRenderTarget();
+}
+
+} // gles2
+} // deqp
diff --git a/modules/gles2/tes2Context.hpp b/modules/gles2/tes2Context.hpp
new file mode 100644
index 0000000..8449e9f
--- /dev/null
+++ b/modules/gles2/tes2Context.hpp
@@ -0,0 +1,65 @@
+#ifndef _TES2CONTEXT_HPP
+#define _TES2CONTEXT_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 2.0 test context.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestContext.hpp"
+
+namespace glu
+{
+class RenderContext;
+class ContextInfo;
+}
+
+namespace tcu
+{
+class RenderTarget;
+}
+
+namespace deqp
+{
+namespace gles2
+{
+
+class Context
+{
+public:
+									Context					(tcu::TestContext& testCtx);
+									~Context				(void);
+
+	tcu::TestContext&				getTestContext			(void)			{ return m_testCtx;			}
+	glu::RenderContext&				getRenderContext		(void)			{ return *m_renderCtx;		}
+	const glu::ContextInfo&			getContextInfo			(void)			{ return *m_contextInfo;	}
+	const tcu::RenderTarget&		getRenderTarget			(void) const;
+
+private:
+	tcu::TestContext&				m_testCtx;
+	glu::RenderContext*				m_renderCtx;
+	glu::ContextInfo*				m_contextInfo;
+};
+
+} // gles2
+} // deqp
+
+#endif // _TES2CONTEXT_HPP
diff --git a/modules/gles2/tes2InfoTests.cpp b/modules/gles2/tes2InfoTests.cpp
new file mode 100644
index 0000000..5616aea
--- /dev/null
+++ b/modules/gles2/tes2InfoTests.cpp
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Platform Information and Capability Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2InfoTests.hpp"
+#include "gluDefs.hpp"
+#include "tcuTestLog.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <string>
+#include <vector>
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace gles2
+{
+
+class QueryStringCase : public TestCase
+{
+public:
+	QueryStringCase (Context& context, const char* name, const char* description, deUint32 query)
+		: TestCase	(context, name, description)
+		, m_query	(query)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+		const char*				result	= (const char*)gl.getString(m_query);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetString() failed");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << result << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		return STOP;
+	}
+
+private:
+	deUint32 m_query;
+};
+
+class QueryExtensionsCase : public TestCase
+{
+public:
+	QueryExtensionsCase (Context& context)
+		: TestCase	(context, "extensions", "Supported Extensions")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const vector<string> extensions = m_context.getContextInfo().getExtensions();
+
+		for (vector<string>::const_iterator i = extensions.begin(); i != extensions.end(); i++)
+			m_testCtx.getLog() << tcu::TestLog::Message << *i << tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		return STOP;
+	}
+};
+
+class RenderTargetInfoCase : public TestCase
+{
+public:
+	RenderTargetInfoCase (Context& context)
+		: TestCase	(context, "render_target", "Render Target Info")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const tcu::RenderTarget&	rt		= m_context.getRenderTarget();
+		const tcu::PixelFormat&		pf		= rt.getPixelFormat();
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Integer("RedBits",		"Red channel bits",		"", QP_KEY_TAG_NONE,	pf.redBits)
+			<< tcu::TestLog::Integer("GreenBits",	"Green channel bits",	"",	QP_KEY_TAG_NONE,	pf.greenBits)
+			<< tcu::TestLog::Integer("BlueBits",	"Blue channel bits",	"",	QP_KEY_TAG_NONE,	pf.blueBits)
+			<< tcu::TestLog::Integer("AlphaBits",	"Alpha channel bits",	"",	QP_KEY_TAG_NONE,	pf.alphaBits)
+			<< tcu::TestLog::Integer("DepthBits",	"Depth bits",			"",	QP_KEY_TAG_NONE,	rt.getDepthBits())
+			<< tcu::TestLog::Integer("StencilBits",	"Stencil bits",			"",	QP_KEY_TAG_NONE,	rt.getStencilBits())
+			<< tcu::TestLog::Integer("NumSamples",	"Number of samples",	"",	QP_KEY_TAG_NONE,	rt.getNumSamples())
+			<< tcu::TestLog::Integer("Width",		"Width",				"",	QP_KEY_TAG_NONE,	rt.getWidth())
+			<< tcu::TestLog::Integer("Height",		"Height",				"",	QP_KEY_TAG_NONE,	rt.getHeight());
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+InfoTests::InfoTests (Context& context)
+	: TestCaseGroup(context, "info", "Platform information queries")
+{
+}
+
+InfoTests::~InfoTests (void)
+{
+}
+
+void InfoTests::init (void)
+{
+	addChild(new QueryStringCase(m_context, "vendor",					"Vendor String",					GL_VENDOR));
+	addChild(new QueryStringCase(m_context, "renderer",					"Renderer String",					GL_RENDERER));
+	addChild(new QueryStringCase(m_context, "version",					"Version String",					GL_VERSION));
+	addChild(new QueryStringCase(m_context, "shading_language_version",	"Shading Language Version String",	GL_SHADING_LANGUAGE_VERSION));
+	addChild(new QueryExtensionsCase(m_context));
+	addChild(new RenderTargetInfoCase(m_context));
+}
+
+} // gles2
+} // deqp
diff --git a/modules/gles2/tes2InfoTests.hpp b/modules/gles2/tes2InfoTests.hpp
new file mode 100644
index 0000000..482baa3
--- /dev/null
+++ b/modules/gles2/tes2InfoTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TES2INFOTESTS_HPP
+#define _TES2INFOTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Platform Information and Capability Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes2TestCase.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+
+class InfoTests : public TestCaseGroup
+{
+public:
+				InfoTests		(Context& context);
+				~InfoTests		(void);
+
+	void		init			(void);
+};
+
+} // gles2
+} // deqp
+
+#endif // _TES2INFOTESTS_HPP
diff --git a/modules/gles2/tes2TestCase.cpp b/modules/gles2/tes2TestCase.cpp
new file mode 100644
index 0000000..c0aec63
--- /dev/null
+++ b/modules/gles2/tes2TestCase.cpp
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 2.0 test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCase.hpp"
diff --git a/modules/gles2/tes2TestCase.hpp b/modules/gles2/tes2TestCase.hpp
new file mode 100644
index 0000000..1f4ae00
--- /dev/null
+++ b/modules/gles2/tes2TestCase.hpp
@@ -0,0 +1,86 @@
+#ifndef _TES2TESTCASE_HPP
+#define _TES2TESTCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 2.0 test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "tes2Context.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+
+class TestCaseGroup : public tcu::TestCaseGroup
+{
+public:
+						TestCaseGroup		(Context& context, const char* name, const char* description);
+						TestCaseGroup		(Context& context, const char* name, const char* description, const std::vector<TestNode*>& children);
+	virtual				~TestCaseGroup		(void) {}
+
+	Context&			getContext			(void) { return m_context; }
+
+protected:
+	Context&			m_context;
+};
+
+class TestCase : public tcu::TestCase
+{
+public:
+						TestCase			(Context& context, const char* name, const char* description);
+						TestCase			(Context& context, tcu::TestNodeType type, const char* name, const char* description);
+	virtual				~TestCase			(void) {}
+
+protected:
+	Context&			m_context;
+};
+
+inline TestCaseGroup::TestCaseGroup (Context& context, const char* name, const char* description)
+	: tcu::TestCaseGroup	(context.getTestContext(), name, description)
+	, m_context				(context)
+{
+}
+
+inline TestCaseGroup::TestCaseGroup (Context& context, const char* name, const char* description, const std::vector<TestNode*>& children)
+	: tcu::TestCaseGroup	(context.getTestContext(), name, description, children)
+	, m_context				(context)
+{
+}
+
+inline TestCase::TestCase (Context& context, const char* name, const char* description)
+	: tcu::TestCase			(context.getTestContext(), name, description)
+	, m_context				(context)
+{
+}
+
+inline TestCase::TestCase (Context& context, tcu::TestNodeType type, const char* name, const char* description)
+	: tcu::TestCase			(context.getTestContext(), type, name, description)
+	, m_context				(context)
+{
+}
+
+} // gles2
+} // deqp
+
+#endif // _TES2TESTCASE_HPP
diff --git a/modules/gles2/tes2TestCaseWrapper.cpp b/modules/gles2/tes2TestCaseWrapper.cpp
new file mode 100644
index 0000000..c7c2eb9
--- /dev/null
+++ b/modules/gles2/tes2TestCaseWrapper.cpp
@@ -0,0 +1,109 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 2.0 Test Case Wrapper.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestCaseWrapper.hpp"
+#include "tcuTestLog.hpp"
+#include "gluDefs.hpp"
+#include "gluStateReset.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+
+using tcu::TestLog;
+
+TestCaseWrapper::TestCaseWrapper (tcu::TestContext& testCtx, glu::RenderContext& renderCtx)
+	: tcu::TestCaseWrapper	(testCtx)
+	, m_renderCtx			(renderCtx)
+{
+	TCU_CHECK(contextSupports(renderCtx.getType(), glu::ApiType::es(2,0)));
+}
+
+TestCaseWrapper::~TestCaseWrapper (void)
+{
+}
+
+bool TestCaseWrapper::initTestCase (tcu::TestCase* testCase)
+{
+	return tcu::TestCaseWrapper::initTestCase(testCase);
+}
+
+bool TestCaseWrapper::deinitTestCase (tcu::TestCase* testCase)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (!tcu::TestCaseWrapper::deinitTestCase(testCase))
+		return false;
+
+	try
+	{
+		// Reset state
+		glu::resetState(m_renderCtx);
+	}
+	catch (const std::exception& e)
+	{
+		log << e;
+		log << TestLog::Message << "Error in state reset, test program will terminate." << TestLog::EndMessage;
+		return false;
+	}
+
+	return true;
+}
+
+tcu::TestNode::IterateResult TestCaseWrapper::iterateTestCase (tcu::TestCase* testCase)
+{
+	TestLog&						log		= m_testCtx.getLog();
+	const glw::Functions&			gl		= m_renderCtx.getFunctions();
+	tcu::TestCase::IterateResult	result	= tcu::TestNode::STOP;
+
+	// Clear to surrender-blue
+	gl.clearColor(0.125f, 0.25f, 0.5f, 1.f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	result = tcu::TestCaseWrapper::iterateTestCase(testCase);
+
+	// Call implementation specific post-iterate routine (usually handles native events and swaps buffers)
+	try
+	{
+		m_renderCtx.postIterate();
+		return result;
+	}
+	catch (const tcu::ResourceError& e)
+	{
+		m_testCtx.getLog() << e;
+		m_testCtx.setTestResult(QP_TEST_RESULT_RESOURCE_ERROR, "Resource error in context post-iteration routine");
+		return tcu::TestNode::STOP;
+	}
+	catch (const std::exception& e)
+	{
+		log << e;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Error in context post-iteration routine");
+		return tcu::TestNode::STOP;
+	}
+}
+
+} // gles2
+} // deqp
diff --git a/modules/gles2/tes2TestCaseWrapper.hpp b/modules/gles2/tes2TestCaseWrapper.hpp
new file mode 100644
index 0000000..3f96070
--- /dev/null
+++ b/modules/gles2/tes2TestCaseWrapper.hpp
@@ -0,0 +1,54 @@
+#ifndef _TES2TESTCASEWRAPPER_HPP
+#define _TES2TESTCASEWRAPPER_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 2.0 Test Case Wrapper.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCaseWrapper.hpp"
+#include "gluRenderContext.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+
+class TestCaseWrapper : public tcu::TestCaseWrapper
+{
+public:
+											TestCaseWrapper			(tcu::TestContext& testCtx, glu::RenderContext& renderCtx);
+	virtual									~TestCaseWrapper		(void);
+
+	virtual bool							initTestCase			(tcu::TestCase* testCase);
+
+	// If deinit returns false, test execution will be aborted.
+	virtual bool							deinitTestCase			(tcu::TestCase* testCase);
+	virtual tcu::TestNode::IterateResult	iterateTestCase			(tcu::TestCase* testCase);
+
+private:
+	glu::RenderContext&						m_renderCtx;
+};
+
+} // gles2
+} // deqp
+
+#endif // _TES2TESTCASEWRAPPER_HPP
diff --git a/modules/gles2/tes2TestPackage.cpp b/modules/gles2/tes2TestPackage.cpp
new file mode 100644
index 0000000..7eeb087
--- /dev/null
+++ b/modules/gles2/tes2TestPackage.cpp
@@ -0,0 +1,107 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 2.0 Test Package
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestPackage.hpp"
+#include "es2fFunctionalTests.hpp"
+#include "es2pPerformanceTests.hpp"
+#include "tes2InfoTests.hpp"
+#include "tes2CapabilityTests.hpp"
+#include "es2aAccuracyTests.hpp"
+#include "es2sStressTests.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+
+PackageContext::PackageContext (tcu::TestContext& testCtx)
+	: m_context		(DE_NULL)
+	, m_caseWrapper	(DE_NULL)
+{
+	try
+	{
+		m_context		= new Context(testCtx);
+		m_caseWrapper	= new TestCaseWrapper(testCtx, m_context->getRenderContext());
+	}
+	catch (...)
+	{
+		delete m_caseWrapper;
+		delete m_context;
+
+		throw;
+	}
+}
+
+PackageContext::~PackageContext (void)
+{
+	delete m_caseWrapper;
+	delete m_context;
+}
+
+TestPackage::TestPackage (tcu::TestContext& testCtx)
+	: tcu::TestPackage	(testCtx, "dEQP-GLES2", "dEQP OpenGL ES 2.0 Tests")
+	, m_packageCtx		(DE_NULL)
+	, m_archive			(testCtx.getRootArchive(), "gles2/")
+{
+}
+
+TestPackage::~TestPackage (void)
+{
+	// Destroy children first since destructors may access context.
+	TestNode::deinit();
+	delete m_packageCtx;
+}
+
+void TestPackage::init (void)
+{
+	try
+	{
+		// Create context
+		m_packageCtx = new PackageContext(m_testCtx);
+
+		// Add main test groups
+		addChild(new InfoTests						(m_packageCtx->getContext()));
+		addChild(new CapabilityTests				(m_packageCtx->getContext()));
+		addChild(new Functional::FunctionalTests	(m_packageCtx->getContext()));
+		addChild(new Accuracy::AccuracyTests		(m_packageCtx->getContext()));
+		addChild(new Performance::PerformanceTests	(m_packageCtx->getContext()));
+		addChild(new Stress::StressTests			(m_packageCtx->getContext()));
+	}
+	catch (...)
+	{
+		delete m_packageCtx;
+		m_packageCtx = DE_NULL;
+
+		throw;
+	}
+}
+
+void TestPackage::deinit (void)
+{
+	TestNode::deinit();
+	delete m_packageCtx;
+	m_packageCtx = DE_NULL;
+}
+
+} // gles2
+} // deqp
diff --git a/modules/gles2/tes2TestPackage.hpp b/modules/gles2/tes2TestPackage.hpp
new file mode 100644
index 0000000..3584ba0
--- /dev/null
+++ b/modules/gles2/tes2TestPackage.hpp
@@ -0,0 +1,71 @@
+#ifndef _TES2TESTPACKAGE_HPP
+#define _TES2TESTPACKAGE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 2.0 Test Package
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestPackage.hpp"
+#include "tes2TestCaseWrapper.hpp"
+#include "tes2Context.hpp"
+#include "tcuResource.hpp"
+
+namespace deqp
+{
+namespace gles2
+{
+
+class PackageContext
+{
+public:
+									PackageContext			(tcu::TestContext& testCtx);
+									~PackageContext			(void);
+
+	Context&						getContext				(void) { return *m_context;		}
+	tcu::TestCaseWrapper&			getTestCaseWrapper		(void) { return *m_caseWrapper; }
+
+private:
+	Context*						m_context;
+	TestCaseWrapper*				m_caseWrapper;
+};
+
+class TestPackage : public tcu::TestPackage
+{
+public:
+									TestPackage				(tcu::TestContext& testCtx);
+	virtual							~TestPackage			(void);
+
+	virtual void					init					(void);
+	virtual void					deinit					(void);
+
+	tcu::TestCaseWrapper&			getTestCaseWrapper		(void) { return m_packageCtx->getTestCaseWrapper(); }
+	tcu::Archive&					getArchive				(void) { return m_archive; }
+
+private:
+	PackageContext*					m_packageCtx;
+	tcu::ResourcePrefix				m_archive;
+};
+
+} // gles2
+} // deqp
+
+#endif // _TES2TESTPACKAGE_HPP
diff --git a/modules/gles2/tes2TestPackageEntry.cpp b/modules/gles2/tes2TestPackageEntry.cpp
new file mode 100644
index 0000000..d532a5b
--- /dev/null
+++ b/modules/gles2/tes2TestPackageEntry.cpp
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 2.0 Test Package Entry Point.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes2TestPackage.hpp"
+
+// Register package to test executor.
+
+static tcu::TestPackage* createTestPackage (tcu::TestContext& testCtx)
+{
+	return new deqp::gles2::TestPackage(testCtx);
+}
+
+tcu::TestPackageDescriptor g_gles2PackageDescriptor("dEQP-GLES2", createTestPackage);
diff --git a/modules/gles3/CMakeLists.txt b/modules/gles3/CMakeLists.txt
new file mode 100644
index 0000000..e709dff
--- /dev/null
+++ b/modules/gles3/CMakeLists.txt
@@ -0,0 +1,47 @@
+# dEQP-GLES3
+
+include_directories(
+	../glshared
+	.				# For child modules
+	)
+
+add_subdirectory(accuracy)
+add_subdirectory(functional)
+add_subdirectory(performance)
+add_subdirectory(stress)
+
+include_directories(
+	accuracy
+	functional
+	performance
+	stress
+	)
+
+set(DEQP_GLES3_SRCS
+	tes3Context.cpp
+	tes3Context.hpp
+	tes3InfoTests.cpp
+	tes3InfoTests.hpp
+	tes3TestCase.cpp
+	tes3TestCase.hpp
+	tes3TestCaseWrapper.cpp
+	tes3TestCaseWrapper.hpp
+	tes3TestPackage.cpp
+	tes3TestPackage.hpp
+	)
+
+set(DEQP_GLES3_LIBS
+	deqp-gles3-accuracy
+	deqp-gles3-functional
+	deqp-gles3-performance
+	deqp-gles3-stress
+	tcutil
+	glutil
+	${DEQP_GLES3_LIBRARIES}
+	)
+
+add_deqp_module(deqp-gles3 "${DEQP_GLES3_SRCS}" "${DEQP_GLES3_LIBS}" tes3TestPackageEntry.cpp)
+
+# Data directories
+add_data_dir(deqp-gles3 ../../data/gles3/data		gles3/data)
+add_data_dir(deqp-gles3 ../../data/gles3/shaders	gles3/shaders)
diff --git a/modules/gles3/accuracy/CMakeLists.txt b/modules/gles3/accuracy/CMakeLists.txt
new file mode 100644
index 0000000..4bec278
--- /dev/null
+++ b/modules/gles3/accuracy/CMakeLists.txt
@@ -0,0 +1,15 @@
+# dEQP-GLES3.accuracy
+
+set(DEQP_GLES3_ACCURACY_SRCS
+	es3aAccuracyTests.cpp
+	es3aAccuracyTests.hpp
+	es3aTextureFilteringTests.cpp
+	es3aTextureFilteringTests.hpp
+	es3aTextureMipmapTests.cpp
+	es3aTextureMipmapTests.hpp
+	es3aVaryingInterpolationTests.cpp
+	es3aVaryingInterpolationTests.hpp
+	)
+
+add_library(deqp-gles3-accuracy STATIC ${DEQP_GLES3_ACCURACY_SRCS})
+target_link_libraries(deqp-gles3-accuracy deqp-gl-shared glutil tcutil ${DEQP_GLES3_LIBRARIES})
diff --git a/modules/gles3/accuracy/es3aAccuracyTests.cpp b/modules/gles3/accuracy/es3aAccuracyTests.cpp
new file mode 100644
index 0000000..bb0853a
--- /dev/null
+++ b/modules/gles3/accuracy/es3aAccuracyTests.cpp
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3aAccuracyTests.hpp"
+#include "es3aTextureFilteringTests.hpp"
+#include "es3aTextureMipmapTests.hpp"
+#include "es3aVaryingInterpolationTests.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Accuracy
+{
+
+class TextureTests : public TestCaseGroup
+{
+public:
+	TextureTests (Context& context)
+		: TestCaseGroup(context, "texture", "Texturing Accuracy Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new TextureFilteringTests	(m_context));
+		addChild(new TextureMipmapTests		(m_context));
+	}
+};
+
+AccuracyTests::AccuracyTests (Context& context)
+	: TestCaseGroup(context, "accuracy", "Accuracy Tests")
+{
+}
+
+AccuracyTests::~AccuracyTests (void)
+{
+}
+
+void AccuracyTests::init (void)
+{
+	addChild(new VaryingInterpolationTests	(m_context));
+	addChild(new TextureTests				(m_context));
+}
+
+} // Accuracy
+} // gles3
+} // deqp
diff --git a/modules/gles3/accuracy/es3aAccuracyTests.hpp b/modules/gles3/accuracy/es3aAccuracyTests.hpp
new file mode 100644
index 0000000..ed9e826
--- /dev/null
+++ b/modules/gles3/accuracy/es3aAccuracyTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3AACCURACYTESTS_HPP
+#define _ES3AACCURACYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Accuracy
+{
+
+class AccuracyTests : public TestCaseGroup
+{
+public:
+						AccuracyTests		(Context& context);
+						~AccuracyTests		(void);
+
+	void				init				(void);
+
+private:
+						AccuracyTests		(const AccuracyTests& other);
+	AccuracyTests&		operator=			(const AccuracyTests& other);
+};
+
+} // Accuracy
+} // gles3
+} // deqp
+
+#endif // _ES3AACCURACYTESTS_HPP
diff --git a/modules/gles3/accuracy/es3aTextureFilteringTests.cpp b/modules/gles3/accuracy/es3aTextureFilteringTests.cpp
new file mode 100644
index 0000000..8fca97f
--- /dev/null
+++ b/modules/gles3/accuracy/es3aTextureFilteringTests.cpp
@@ -0,0 +1,770 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3aTextureFilteringTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Accuracy
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using namespace gls::TextureTestUtil;
+
+class Texture2DFilteringCase : public tcu::TestCase
+{
+public:
+								Texture2DFilteringCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 internalFormat, int width, int height);
+								Texture2DFilteringCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames);
+								~Texture2DFilteringCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Texture2DFilteringCase		(const Texture2DFilteringCase& other);
+	Texture2DFilteringCase&		operator=					(const Texture2DFilteringCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	deUint32					m_minFilter;
+	deUint32					m_magFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+
+	deUint32					m_internalFormat;
+	int							m_width;
+	int							m_height;
+
+	std::vector<std::string>	m_filenames;
+
+	std::vector<glu::Texture2D*>	m_textures;
+	TextureRenderer					m_renderer;
+};
+
+Texture2DFilteringCase::Texture2DFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 internalFormat, int width, int height)
+	: TestCase			(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(ctxInfo)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_internalFormat	(internalFormat)
+	, m_width			(width)
+	, m_height			(height)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture2DFilteringCase::Texture2DFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames)
+	: TestCase			(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(ctxInfo)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_internalFormat	(GL_NONE)
+	, m_width			(0)
+	, m_height			(0)
+	, m_filenames		(filenames)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_100_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture2DFilteringCase::~Texture2DFilteringCase (void)
+{
+	deinit();
+}
+
+void Texture2DFilteringCase::init (void)
+{
+	try
+	{
+		if (!m_filenames.empty())
+		{
+			m_textures.reserve(1);
+			m_textures.push_back(glu::Texture2D::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size(), m_filenames));
+		}
+		else
+		{
+			// Create 2 textures.
+			m_textures.reserve(2);
+			for (int ndx = 0; ndx < 2; ndx++)
+				m_textures.push_back(new glu::Texture2D(m_renderCtx, m_internalFormat, m_width, m_height));
+
+			const int				numLevels	= deLog2Floor32(de::max(m_width, m_height))+1;
+			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+			tcu::Vec4				cBias		= fmtInfo.valueMin;
+			tcu::Vec4				cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+			// Fill first gradient texture.
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				tcu::Vec4 gMin = tcu::Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
+				tcu::Vec4 gMax = tcu::Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;
+
+				m_textures[0]->getRefTexture().allocLevel(levelNdx);
+				tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
+			}
+
+			// Fill second with grid texture.
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				deUint32	step	= 0x00ffffff / numLevels;
+				deUint32	rgb		= step*levelNdx;
+				deUint32	colorA	= 0xff000000 | rgb;
+				deUint32	colorB	= 0xff000000 | ~rgb;
+
+				m_textures[1]->getRefTexture().allocLevel(levelNdx);
+				tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+			}
+
+			// Upload.
+			for (std::vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+				(*i)->upload();
+		}
+	}
+	catch (...)
+	{
+		// Clean up to save memory.
+		Texture2DFilteringCase::deinit();
+		throw;
+	}
+}
+
+void Texture2DFilteringCase::deinit (void)
+{
+	for (std::vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	m_renderer.clear();
+}
+
+Texture2DFilteringCase::IterateResult Texture2DFilteringCase::iterate (void)
+{
+	const glw::Functions&		gl					= m_renderCtx.getFunctions();
+	TestLog&					log					= m_testCtx.getLog();
+	const int					defViewportWidth	= 256;
+	const int					defViewportHeight	= 256;
+	RandomViewport				viewport			(m_renderCtx.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+	tcu::Surface				renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface				referenceFrame		(viewport.width, viewport.height);
+	const tcu::TextureFormat&	texFmt				= m_textures[0]->getRefTexture().getFormat();
+	tcu::TextureFormatInfo		fmtInfo				= tcu::getTextureFormatInfo(texFmt);
+	ReferenceParams				refParams			(TEXTURETYPE_2D);
+	vector<float>				texCoord;
+
+	// Accuracy measurements are off unless viewport size is 256x256
+	if (viewport.width < defViewportWidth || viewport.height < defViewportHeight)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Viewport is divided into 4 sections.
+	int				leftWidth			= viewport.width / 2;
+	int				rightWidth			= viewport.width - leftWidth;
+	int				bottomHeight		= viewport.height / 2;
+	int				topHeight			= viewport.height - bottomHeight;
+
+	int				curTexNdx			= 0;
+
+	// Use unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture(GL_TEXTURE_2D, m_textures[curTexNdx]->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	// Setup params for reference.
+	refParams.sampler		= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	refParams.samplerType	= getSamplerType(texFmt);
+	refParams.lodMode		= LODMODE_EXACT;
+	refParams.colorBias		= fmtInfo.lookupBias;
+	refParams.colorScale	= fmtInfo.lookupScale;
+
+	// Bottom left: Minification
+	{
+		gl.viewport(viewport.x, viewport.y, leftWidth, bottomHeight);
+
+		computeQuadTexCoord2D(texCoord, tcu::Vec2(-4.0f, -4.5f), tcu::Vec2(4.0f, 2.5f));
+
+		m_renderer.renderQuad(0, &texCoord[0], refParams);
+		sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), 0, 0, leftWidth, bottomHeight),
+					  m_textures[curTexNdx]->getRefTexture(), &texCoord[0], refParams);
+	}
+
+	// Bottom right: Magnification
+	{
+		gl.viewport(viewport.x+leftWidth, viewport.y, rightWidth, bottomHeight);
+
+		computeQuadTexCoord2D(texCoord, tcu::Vec2(-0.5f, 0.75f), tcu::Vec2(0.25f, 1.25f));
+
+		m_renderer.renderQuad(0, &texCoord[0], refParams);
+		sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), leftWidth, 0, rightWidth, bottomHeight),
+					  m_textures[curTexNdx]->getRefTexture(), &texCoord[0], refParams);
+	}
+
+	if (m_textures.size() >= 2)
+	{
+		curTexNdx += 1;
+
+		// Setup second texture.
+		gl.bindTexture(GL_TEXTURE_2D, m_textures[curTexNdx]->getGLTexture());
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	}
+
+	// Top left: Minification
+	// \note Minification is chosen so that 0.0 < lod <= 0.5. This way special minification threshold rule will be triggered.
+	{
+		gl.viewport(viewport.x, viewport.y+bottomHeight, leftWidth, topHeight);
+
+		float	sMin		= -0.5f;
+		float	tMin		= -0.2f;
+		float	sRange		= ((float)leftWidth * 1.2f) / (float)m_textures[curTexNdx]->getRefTexture().getWidth();
+		float	tRange		= ((float)topHeight * 1.1f) / (float)m_textures[curTexNdx]->getRefTexture().getHeight();
+
+		computeQuadTexCoord2D(texCoord, tcu::Vec2(sMin, tMin), tcu::Vec2(sMin+sRange, tMin+tRange));
+
+		m_renderer.renderQuad(0, &texCoord[0], refParams);
+		sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), 0, bottomHeight, leftWidth, topHeight),
+					  m_textures[curTexNdx]->getRefTexture(), &texCoord[0], refParams);
+	}
+
+	// Top right: Magnification
+	{
+		gl.viewport(viewport.x+leftWidth, viewport.y+bottomHeight, rightWidth, topHeight);
+
+		computeQuadTexCoord2D(texCoord, tcu::Vec2(-0.5f, 0.75f), tcu::Vec2(0.25f, 1.25f));
+
+		m_renderer.renderQuad(0, &texCoord[0], refParams);
+		sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), leftWidth, bottomHeight, rightWidth, topHeight),
+					  m_textures[curTexNdx]->getRefTexture(), &texCoord[0], refParams);
+	}
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log.
+	{
+		const int	bestScoreDiff	= 16;
+		const int	worstScoreDiff	= 3200;
+
+		int score = measureAccuracy(log, referenceFrame, renderedFrame, bestScoreDiff, worstScoreDiff);
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(score).c_str());
+	}
+
+	return STOP;
+}
+
+class TextureCubeFilteringCase : public tcu::TestCase
+{
+public:
+								TextureCubeFilteringCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, bool onlySampleFaceInterior, deUint32 internalFormat, int width, int height);
+								TextureCubeFilteringCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, bool onlySampleFaceInterior, const std::vector<std::string>& filenames);
+								~TextureCubeFilteringCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								TextureCubeFilteringCase	(const TextureCubeFilteringCase& other);
+	TextureCubeFilteringCase&	operator=					(const TextureCubeFilteringCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	deUint32					m_minFilter;
+	deUint32					m_magFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	bool						m_onlySampleFaceInterior; //!< If true, we avoid sampling anywhere near a face's edges.
+
+	deUint32					m_internalFormat;
+	int							m_width;
+	int							m_height;
+
+	std::vector<std::string>	m_filenames;
+
+	std::vector<glu::TextureCube*>	m_textures;
+	TextureRenderer					m_renderer;
+};
+
+TextureCubeFilteringCase::TextureCubeFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, bool onlySampleFaceInterior, deUint32 internalFormat, int width, int height)
+	: TestCase					(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx				(renderCtx)
+	, m_renderCtxInfo			(ctxInfo)
+	, m_minFilter				(minFilter)
+	, m_magFilter				(magFilter)
+	, m_wrapS					(wrapS)
+	, m_wrapT					(wrapT)
+	, m_onlySampleFaceInterior	(onlySampleFaceInterior)
+	, m_internalFormat			(internalFormat)
+	, m_width					(width)
+	, m_height					(height)
+	, m_renderer				(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+TextureCubeFilteringCase::TextureCubeFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, bool onlySampleFaceInterior, const std::vector<std::string>& filenames)
+	: TestCase					(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx				(renderCtx)
+	, m_renderCtxInfo			(ctxInfo)
+	, m_minFilter				(minFilter)
+	, m_magFilter				(magFilter)
+	, m_wrapS					(wrapS)
+	, m_wrapT					(wrapT)
+	, m_onlySampleFaceInterior	(onlySampleFaceInterior)
+	, m_internalFormat			(GL_NONE)
+	, m_width					(0)
+	, m_height					(0)
+	, m_filenames				(filenames)
+	, m_renderer				(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+TextureCubeFilteringCase::~TextureCubeFilteringCase (void)
+{
+	deinit();
+}
+
+void TextureCubeFilteringCase::init (void)
+{
+	try
+	{
+		if (!m_filenames.empty())
+		{
+			m_textures.reserve(1);
+			m_textures.push_back(glu::TextureCube::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size() / 6, m_filenames));
+		}
+		else
+		{
+			m_textures.reserve(2);
+			DE_ASSERT(m_width == m_height);
+			for (int ndx = 0; ndx < 2; ndx++)
+				m_textures.push_back(new glu::TextureCube(m_renderCtx, m_internalFormat, m_width));
+
+			const int				numLevels	= deLog2Floor32(de::max(m_width, m_height))+1;
+			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+			tcu::Vec4				cBias		= fmtInfo.valueMin;
+			tcu::Vec4				cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+			// Fill first with gradient texture.
+			static const tcu::Vec4 gradients[tcu::CUBEFACE_LAST][2] =
+			{
+				{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
+				{ tcu::Vec4( 0.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
+				{ tcu::Vec4(-1.0f,  0.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
+				{ tcu::Vec4(-1.0f, -1.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
+				{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
+				{ tcu::Vec4( 0.0f,  0.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
+			};
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			{
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					m_textures[0]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+					tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0]*cScale + cBias, gradients[face][1]*cScale + cBias);
+				}
+			}
+
+			// Fill second with grid texture.
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			{
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					deUint32	step	= 0x00ffffff / (numLevels*tcu::CUBEFACE_LAST);
+					deUint32	rgb		= step*levelNdx*face;
+					deUint32	colorA	= 0xff000000 | rgb;
+					deUint32	colorB	= 0xff000000 | ~rgb;
+
+					m_textures[1]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+					tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+				}
+			}
+
+			if (m_magFilter == GL_LINEAR || m_minFilter == GL_LINEAR || m_minFilter == GL_LINEAR_MIPMAP_NEAREST || m_minFilter == GL_LINEAR_MIPMAP_LINEAR)
+			{
+				// Using seamless linear cube map filtering - set all corner texels to the same color, because cube corner sampling in this case is not very well defined by the spec.
+				// \todo Probably should also do this for cases where textures are loaded from files.
+
+				for (int texNdx = 0; texNdx < (int)m_textures.size(); texNdx++)
+				{
+					for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+					{
+						for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+						{
+							static const tcu::Vec4 color(0.0f, 0.0f, 0.0f, 1.0f);
+							tcu::PixelBufferAccess access = m_textures[texNdx]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face);
+
+							access.setPixel(color, 0,					0);
+							access.setPixel(color, access.getWidth()-1,	0);
+							access.setPixel(color, 0,					access.getHeight()-1);
+							access.setPixel(color, access.getWidth()-1,	access.getHeight()-1);
+						}
+					}
+				}
+			}
+
+			// Upload.
+			for (std::vector<glu::TextureCube*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+				(*i)->upload();
+		}
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		TextureCubeFilteringCase::deinit();
+		throw;
+	}
+}
+
+void TextureCubeFilteringCase::deinit (void)
+{
+	for (std::vector<glu::TextureCube*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	m_renderer.clear();
+}
+
+static void renderFaces (
+	const glw::Functions&		gl,
+	const SurfaceAccess&		dstRef,
+	const tcu::TextureCube&		refTexture,
+	const ReferenceParams&			params,
+	TextureRenderer&			renderer,
+	int							x,
+	int							y,
+	int							width,
+	int							height,
+	const tcu::Vec2&			bottomLeft,
+	const tcu::Vec2&			topRight,
+	const tcu::Vec2&			texCoordTopRightFactor,
+	bool						multiFace)
+{
+	DE_ASSERT(width == dstRef.getWidth() && height == dstRef.getHeight());
+
+	vector<float> texCoord;
+
+	DE_STATIC_ASSERT(tcu::CUBEFACE_LAST == 6);
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+	{
+		bool	isRightmost		= (face == 2) || (face == 5);
+		bool	isTop			= face >= 3;
+		int		curX			= (face % 3) * (width  / 3);
+		int		curY			= (face / 3) * (height / 2);
+		int		curW			= isRightmost	? (width-curX)	: (width	/ 3);
+		int		curH			= isTop			? (height-curY)	: (height	/ 2);
+
+		computeQuadTexCoordCube(texCoord, (tcu::CubeFace)face, bottomLeft, topRight);
+
+		{
+			// Move the top and right edges of the texture coord quad. This is useful when we want a cube edge visible.
+			int texCoordSRow = face == tcu::CUBEFACE_NEGATIVE_X || face == tcu::CUBEFACE_POSITIVE_X ? 2 : 0;
+			int texCoordTRow = face == tcu::CUBEFACE_NEGATIVE_Y || face == tcu::CUBEFACE_POSITIVE_Y ? 2 : 1;
+			texCoord[6 + texCoordSRow] *= texCoordTopRightFactor.x();
+			texCoord[9 + texCoordSRow] *= texCoordTopRightFactor.x();
+			texCoord[3 + texCoordTRow] *= texCoordTopRightFactor.y();
+			texCoord[9 + texCoordTRow] *= texCoordTopRightFactor.y();
+		}
+
+		gl.viewport(x+curX, y+curY, curW, curH);
+
+		renderer.renderQuad(0, &texCoord[0], params);
+
+		if (multiFace)
+			sampleTextureMultiFace(SurfaceAccess(dstRef, curX, curY, curW, curH), refTexture, &texCoord[0], params);
+		else
+			sampleTexture(SurfaceAccess(dstRef, curX, curY, curW, curH), refTexture, &texCoord[0], params);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Post render");
+}
+
+TextureCubeFilteringCase::IterateResult TextureCubeFilteringCase::iterate (void)
+{
+	const glw::Functions&		gl					= m_renderCtx.getFunctions();
+	TestLog&					log					= m_testCtx.getLog();
+	const int					cellSize			= 28;
+	const int					defViewportWidth	= cellSize*6;
+	const int					defViewportHeight	= cellSize*4;
+	RandomViewport				viewport			(m_renderCtx.getRenderTarget(), cellSize*6, cellSize*4, deStringHash(getName()));
+	tcu::Surface				renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface				referenceFrame		(viewport.width, viewport.height);
+	ReferenceParams				sampleParams		(TEXTURETYPE_CUBE);
+	const tcu::TextureFormat&	texFmt				= m_textures[0]->getRefTexture().getFormat();
+	tcu::TextureFormatInfo		fmtInfo				= tcu::getTextureFormatInfo(texFmt);
+
+	// Accuracy measurements are off unless viewport size is exactly as expected.
+	if (m_nodeType == tcu::NODETYPE_ACCURACY && (viewport.width < defViewportWidth || viewport.height < defViewportHeight))
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Viewport is divided into 4 sections.
+	int				leftWidth			= viewport.width / 2;
+	int				rightWidth			= viewport.width - leftWidth;
+	int				bottomHeight		= viewport.height / 2;
+	int				topHeight			= viewport.height - bottomHeight;
+
+	int				curTexNdx			= 0;
+
+	// Sampling parameters.
+	sampleParams.sampler					= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	sampleParams.sampler.seamlessCubeMap	= true;
+	sampleParams.samplerType				= getSamplerType(texFmt);
+	sampleParams.colorBias					= fmtInfo.lookupBias;
+	sampleParams.colorScale					= fmtInfo.lookupScale;
+	sampleParams.lodMode					= LODMODE_EXACT;
+
+	// Use unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+
+	// Setup gradient texture.
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_textures[curTexNdx]->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	// Bottom left: Minification
+	renderFaces(gl,
+				SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), 0, 0, leftWidth, bottomHeight),
+				m_textures[curTexNdx]->getRefTexture(), sampleParams,
+				m_renderer,
+				viewport.x, viewport.y, leftWidth, bottomHeight,
+				m_onlySampleFaceInterior ? tcu::Vec2(-0.81f, -0.81f) : tcu::Vec2(-0.975f, -0.975f),
+				m_onlySampleFaceInterior ? tcu::Vec2( 0.8f,  0.8f) : tcu::Vec2( 0.975f,  0.975f),
+				!m_onlySampleFaceInterior ? tcu::Vec2(1.3f, 1.25f) : tcu::Vec2(1.0f, 1.0f),
+				!m_onlySampleFaceInterior);
+
+	// Bottom right: Magnification
+	renderFaces(gl,
+				SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), leftWidth, 0, rightWidth, bottomHeight),
+				m_textures[curTexNdx]->getRefTexture(), sampleParams,
+				m_renderer,
+				viewport.x+leftWidth, viewport.y, rightWidth, bottomHeight,
+				tcu::Vec2(0.5f, 0.65f), m_onlySampleFaceInterior ? tcu::Vec2(0.8f, 0.8f) : tcu::Vec2(0.975f, 0.975f),
+				!m_onlySampleFaceInterior ? tcu::Vec2(1.1f, 1.06f) : tcu::Vec2(1.0f, 1.0f),
+				!m_onlySampleFaceInterior);
+
+	if (m_textures.size() >= 2)
+	{
+		curTexNdx += 1;
+
+		// Setup second texture.
+		gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_textures[curTexNdx]->getGLTexture());
+		gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+		gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+		gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+		gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	}
+
+	// Top left: Minification
+	renderFaces(gl,
+				SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), 0, bottomHeight, leftWidth, topHeight),
+				m_textures[curTexNdx]->getRefTexture(), sampleParams,
+				m_renderer,
+				viewport.x, viewport.y+bottomHeight, leftWidth, topHeight,
+				m_onlySampleFaceInterior ? tcu::Vec2(-0.81f, -0.81f) : tcu::Vec2(-0.975f, -0.975f),
+				m_onlySampleFaceInterior ? tcu::Vec2( 0.8f,  0.8f) : tcu::Vec2( 0.975f,  0.975f),
+				!m_onlySampleFaceInterior ? tcu::Vec2(1.3f, 1.25f) : tcu::Vec2(1.0f, 1.0f),
+				!m_onlySampleFaceInterior);
+
+	// Top right: Magnification
+	renderFaces(gl,
+				SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), leftWidth, bottomHeight, rightWidth, topHeight),
+				m_textures[curTexNdx]->getRefTexture(), sampleParams,
+				m_renderer,
+				viewport.x+leftWidth, viewport.y+bottomHeight, rightWidth, topHeight,
+				tcu::Vec2(0.5f, -0.65f), m_onlySampleFaceInterior ? tcu::Vec2(0.8f, -0.8f) : tcu::Vec2(0.975f, -0.975f),
+				!m_onlySampleFaceInterior ? tcu::Vec2(1.1f, 1.06f) : tcu::Vec2(1.0f, 1.0f),
+				!m_onlySampleFaceInterior);
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log.
+	{
+		const int	bestScoreDiff	= 16;
+		const int	worstScoreDiff	= 10000;
+
+		int score = measureAccuracy(log, referenceFrame, renderedFrame, bestScoreDiff, worstScoreDiff);
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(score).c_str());
+	}
+
+	return STOP;
+}
+
+TextureFilteringTests::TextureFilteringTests (Context& context)
+	: TestCaseGroup(context, "filter", "Texture Filtering Accuracy Tests")
+{
+}
+
+TextureFilteringTests::~TextureFilteringTests (void)
+{
+}
+
+void TextureFilteringTests::init (void)
+{
+	tcu::TestCaseGroup* group2D		= new tcu::TestCaseGroup(m_testCtx, "2d",	"2D Texture Filtering");
+	tcu::TestCaseGroup*	groupCube	= new tcu::TestCaseGroup(m_testCtx, "cube",	"Cube Map Filtering");
+	addChild(group2D);
+	addChild(groupCube);
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE },
+		{ "repeat",		GL_REPEAT },
+		{ "mirror",		GL_MIRRORED_REPEAT }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} minFilterModes[] =
+	{
+		{ "nearest",				GL_NEAREST					},
+		{ "linear",					GL_LINEAR					},
+		{ "nearest_mipmap_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
+		{ "linear_mipmap_nearest",	GL_LINEAR_MIPMAP_NEAREST	},
+		{ "nearest_mipmap_linear",	GL_NEAREST_MIPMAP_LINEAR	},
+		{ "linear_mipmap_linear",	GL_LINEAR_MIPMAP_LINEAR		}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} magFilterModes[] =
+	{
+		{ "nearest",	GL_NEAREST },
+		{ "linear",		GL_LINEAR }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		int				width;
+		int				height;
+	} sizes2D[] =
+	{
+		{ "pot",		32, 64 },
+		{ "npot",		31, 55 }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		int				width;
+		int				height;
+	} sizesCube[] =
+	{
+		{ "pot",		64, 64 },
+		{ "npot",		63, 63 }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		format;
+	} formats[] =
+	{
+		{ "rgba8",		GL_RGBA8 }
+	};
+
+#define FOR_EACH(ITERATOR, ARRAY, BODY)	\
+	for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++)	\
+		BODY
+
+	// 2D cases.
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+	FOR_EACH(format,		formats,
+	FOR_EACH(size,			sizes2D,
+		{
+			string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name + "_" + formats[format].name + string("_") + sizes2D[size].name;
+
+			group2D->addChild(new Texture2DFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+														 name.c_str(), "",
+														 minFilterModes[minFilter].mode,
+														 magFilterModes[magFilter].mode,
+														 wrapModes[wrapMode].mode,
+														 wrapModes[wrapMode].mode,
+														 formats[format].format,
+														 sizes2D[size].width, sizes2D[size].height));
+		})))));
+
+	// Cubemap cases.
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+	FOR_EACH(format,		formats,
+	FOR_EACH(size,			sizesCube,
+		{
+			string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name + "_" + formats[format].name + string("_") + sizesCube[size].name;
+
+			groupCube->addChild(new TextureCubeFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+															 name.c_str(), "",
+															 minFilterModes[minFilter].mode,
+															 magFilterModes[magFilter].mode,
+															 wrapModes[wrapMode].mode,
+															 wrapModes[wrapMode].mode,
+															 false,
+															 formats[format].format,
+															 sizesCube[size].width, sizesCube[size].height));
+		})))));
+}
+
+} // Accuracy
+} // gles3
+} // deqp
diff --git a/modules/gles3/accuracy/es3aTextureFilteringTests.hpp b/modules/gles3/accuracy/es3aTextureFilteringTests.hpp
new file mode 100644
index 0000000..4ce22fb
--- /dev/null
+++ b/modules/gles3/accuracy/es3aTextureFilteringTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3ATEXTUREFILTERINGTESTS_HPP
+#define _ES3ATEXTUREFILTERINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Accuracy
+{
+
+class TextureFilteringTests : public TestCaseGroup
+{
+public:
+								TextureFilteringTests		(Context& context);
+								~TextureFilteringTests		(void);
+
+	void						init						(void);
+
+private:
+								TextureFilteringTests		(const TextureFilteringTests& other);
+	TextureFilteringTests&		operator=					(const TextureFilteringTests& other);
+};
+
+} // Accuracy
+} // gles3
+} // deqp
+
+#endif // _ES3ATEXTUREFILTERINGTESTS_HPP
diff --git a/modules/gles3/accuracy/es3aTextureMipmapTests.cpp b/modules/gles3/accuracy/es3aTextureMipmapTests.cpp
new file mode 100644
index 0000000..67a700c
--- /dev/null
+++ b/modules/gles3/accuracy/es3aTextureMipmapTests.cpp
@@ -0,0 +1,741 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Mipmapping accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3aTextureMipmapTests.hpp"
+
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuMatrixUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Accuracy
+{
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec4;
+using namespace gls::TextureTestUtil;
+
+
+enum CoordType
+{
+	COORDTYPE_BASIC,		//!< texCoord = translateScale(position).
+	COORDTYPE_BASIC_BIAS,	//!< Like basic, but with bias values.
+	COORDTYPE_AFFINE,		//!< texCoord = translateScaleRotateShear(position).
+	COORDTYPE_PROJECTED,	//!< Projected coordinates, w != 1
+
+	COORDTYPE_LAST
+};
+
+// Texture2DMipmapCase
+
+class Texture2DMipmapCase : public tcu::TestCase
+{
+public:
+
+								Texture2DMipmapCase			(tcu::TestContext&			testCtx,
+															 glu::RenderContext&		renderCtx,
+															 const glu::ContextInfo&	renderCtxInfo,
+															 const char*				name,
+															 const char*				desc,
+															 CoordType					coordType,
+															 deUint32					minFilter,
+															 deUint32					wrapS,
+															 deUint32					wrapT,
+															 deUint32					format,
+															 deUint32					dataType,
+															 int						width,
+															 int						height);
+								~Texture2DMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Texture2DMipmapCase			(const Texture2DMipmapCase& other);
+	Texture2DMipmapCase&		operator=					(const Texture2DMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	CoordType					m_coordType;
+	deUint32					m_minFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	deUint32					m_format;
+	deUint32					m_dataType;
+	int							m_width;
+	int							m_height;
+
+	glu::Texture2D*				m_texture;
+	TextureRenderer				m_renderer;
+};
+
+Texture2DMipmapCase::Texture2DMipmapCase (tcu::TestContext&			testCtx,
+										  glu::RenderContext&		renderCtx,
+										  const glu::ContextInfo&	renderCtxInfo,
+										  const char*				name,
+										  const char*				desc,
+										  CoordType					coordType,
+										  deUint32					minFilter,
+										  deUint32					wrapS,
+										  deUint32					wrapT,
+										  deUint32					format,
+										  deUint32					dataType,
+										  int						width,
+										  int						height)
+	: TestCase			(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_coordType		(coordType)
+	, m_minFilter		(minFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_width			(width)
+	, m_height			(height)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture2DMipmapCase::~Texture2DMipmapCase (void)
+{
+	deinit();
+}
+
+void Texture2DMipmapCase::init (void)
+{
+	m_texture = new glu::Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+
+	int numLevels = deLog2Floor32(de::max(m_width, m_height))+1;
+
+	// Fill texture with colored grid.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		deUint32	step		= 0xff / (numLevels-1);
+		deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+		deUint32	dec			= 0xff - inc;
+		deUint32	rgb			= (inc << 16) | (dec << 8) | 0xff;
+		deUint32	color		= 0xff000000 | rgb;
+
+		m_texture->getRefTexture().allocLevel(levelNdx);
+		tcu::clear(m_texture->getRefTexture().getLevel(levelNdx), toVec4(tcu::RGBA(color)));
+	}
+}
+
+void Texture2DMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+static void getBasicTexCoord2D (std::vector<float>& dst, int cellNdx)
+{
+	static const struct
+	{
+		Vec2 bottomLeft;
+		Vec2 topRight;
+	} s_basicCoords[] =
+	{
+		{ Vec2(-0.1f,  0.1f), Vec2( 0.8f,  1.0f) },
+		{ Vec2(-0.3f, -0.6f), Vec2( 0.7f,  0.4f) },
+		{ Vec2(-0.3f,  0.6f), Vec2( 0.7f, -0.9f) },
+		{ Vec2(-0.8f,  0.6f), Vec2( 0.7f, -0.9f) },
+
+		{ Vec2(-0.5f, -0.5f), Vec2( 1.5f,  1.5f) },
+		{ Vec2( 1.0f, -1.0f), Vec2(-1.3f,  1.0f) },
+		{ Vec2( 1.2f, -1.0f), Vec2(-1.3f,  1.6f) },
+		{ Vec2( 2.2f, -1.1f), Vec2(-1.3f,  0.8f) },
+
+		{ Vec2(-1.5f,  1.6f), Vec2( 1.7f, -1.4f) },
+		{ Vec2( 2.0f,  1.6f), Vec2( 2.3f, -1.4f) },
+		{ Vec2( 1.3f, -2.6f), Vec2(-2.7f,  2.9f) },
+		{ Vec2(-0.8f, -6.6f), Vec2( 6.0f, -0.9f) },
+
+		{ Vec2( -8.0f,   9.0f), Vec2(  8.3f,  -7.0f) },
+		{ Vec2(-16.0f,  10.0f), Vec2( 18.3f,  24.0f) },
+		{ Vec2( 30.2f,  55.0f), Vec2(-24.3f,  -1.6f) },
+		{ Vec2(-33.2f,  64.1f), Vec2( 32.1f, -64.1f) },
+	};
+
+	DE_ASSERT(de::inBounds(cellNdx, 0, DE_LENGTH_OF_ARRAY(s_basicCoords)));
+
+	const Vec2& bottomLeft	= s_basicCoords[cellNdx].bottomLeft;
+	const Vec2& topRight	= s_basicCoords[cellNdx].topRight;
+
+	computeQuadTexCoord2D(dst, bottomLeft, topRight);
+}
+
+static void getAffineTexCoord2D (std::vector<float>& dst, int cellNdx)
+{
+	// Use basic coords as base.
+	getBasicTexCoord2D(dst, cellNdx);
+
+	// Rotate based on cell index.
+	float		angle		= 2.0f*DE_PI * ((float)cellNdx / 16.0f);
+	tcu::Mat2	rotMatrix	= tcu::rotationMatrix(angle);
+
+	// Second and third row are sheared.
+	float		shearX		= de::inRange(cellNdx, 4, 11) ? (float)(15-cellNdx) / 16.0f : 0.0f;
+	tcu::Mat2	shearMatrix	= tcu::shearMatrix(tcu::Vec2(shearX, 0.0f));
+
+	tcu::Mat2	transform	= rotMatrix * shearMatrix;
+	Vec2		p0			= transform * Vec2(dst[0], dst[1]);
+	Vec2		p1			= transform * Vec2(dst[2], dst[3]);
+	Vec2		p2			= transform * Vec2(dst[4], dst[5]);
+	Vec2		p3			= transform * Vec2(dst[6], dst[7]);
+
+	dst[0] = p0.x();	dst[1] = p0.y();
+	dst[2] = p1.x();	dst[3] = p1.y();
+	dst[4] = p2.x();	dst[5] = p2.y();
+	dst[6] = p3.x();	dst[7] = p3.y();
+}
+
+Texture2DMipmapCase::IterateResult Texture2DMipmapCase::iterate (void)
+{
+	// Constants.
+	const deUint32				magFilter			= GL_NEAREST;
+
+	const glw::Functions&		gl					= m_renderCtx.getFunctions();
+	TestLog&					log					= m_testCtx.getLog();
+
+	const tcu::Texture2D&		refTexture			= m_texture->getRefTexture();
+	const tcu::TextureFormat&	texFmt				= refTexture.getFormat();
+	tcu::TextureFormatInfo		fmtInfo				= tcu::getTextureFormatInfo(texFmt);
+
+	int							texWidth			= refTexture.getWidth();
+	int							texHeight			= refTexture.getHeight();
+	int							defViewportWidth	= texWidth*4;
+	int							defViewportHeight	= texHeight*4;
+
+	RandomViewport				viewport			(m_renderCtx.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+	ReferenceParams				sampleParams		(TEXTURETYPE_2D);
+	vector<float>				texCoord;
+
+	bool						isProjected			= m_coordType == COORDTYPE_PROJECTED;
+	bool						useLodBias			= m_coordType == COORDTYPE_BASIC_BIAS;
+
+	tcu::Surface				renderedFrame		(viewport.width, viewport.height);
+
+	// Accuracy cases test against ideal lod computation.
+	tcu::Surface				idealFrame			(viewport.width, viewport.height);
+
+	// Viewport is divided into 4x4 grid.
+	int							gridWidth			= 4;
+	int							gridHeight			= 4;
+	int							cellWidth			= viewport.width / gridWidth;
+	int							cellHeight			= viewport.height / gridHeight;
+
+	// Accuracy measurements are off unless we get the expected viewport size.
+	if (viewport.width < defViewportWidth || viewport.height < defViewportHeight)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Sampling parameters.
+	sampleParams.sampler		= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, magFilter);
+	sampleParams.samplerType	= gls::TextureTestUtil::getSamplerType(m_texture->getRefTexture().getFormat());
+	sampleParams.colorBias		= fmtInfo.lookupBias;
+	sampleParams.colorScale		= fmtInfo.lookupScale;
+	sampleParams.flags			= (isProjected ? ReferenceParams::PROJECTED : 0) | (useLodBias ? ReferenceParams::USE_BIAS : 0);
+
+	// Upload texture data.
+	m_texture->upload();
+
+	// Use unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Bias values.
+	static const float s_bias[] = { 1.0f, -2.0f, 0.8f, -0.5f, 1.5f, 0.9f, 2.0f, 4.0f };
+
+	// Projection values.
+	static const Vec4 s_projections[] =
+	{
+		Vec4(1.2f, 1.0f, 0.7f, 1.0f),
+		Vec4(1.3f, 0.8f, 0.6f, 2.0f),
+		Vec4(0.8f, 1.0f, 1.7f, 0.6f),
+		Vec4(1.2f, 1.0f, 1.7f, 1.5f)
+	};
+
+	// Render cells.
+	for (int gridY = 0; gridY < gridHeight; gridY++)
+	{
+		for (int gridX = 0; gridX < gridWidth; gridX++)
+		{
+			int				curX		= cellWidth*gridX;
+			int				curY		= cellHeight*gridY;
+			int				curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+			int				curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+			int				cellNdx		= gridY*gridWidth + gridX;
+
+			// Compute texcoord.
+			switch (m_coordType)
+			{
+				case COORDTYPE_BASIC_BIAS:	// Fall-through.
+				case COORDTYPE_PROJECTED:
+				case COORDTYPE_BASIC:		getBasicTexCoord2D	(texCoord, cellNdx);	break;
+				case COORDTYPE_AFFINE:		getAffineTexCoord2D	(texCoord, cellNdx);	break;
+				default:					DE_ASSERT(DE_FALSE);
+			}
+
+			if (isProjected)
+				sampleParams.w = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+
+			if (useLodBias)
+				sampleParams.bias = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+
+			// Render with GL.
+			gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+			m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+
+			// Render reference(s).
+			{
+				SurfaceAccess idealDst(idealFrame, m_renderCtx.getRenderTarget().getPixelFormat(), curX, curY, curW, curH);
+				sampleParams.lodMode = LODMODE_EXACT;
+				sampleTexture(idealDst, m_texture->getRefTexture(), &texCoord[0], sampleParams);
+			}
+		}
+	}
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log.
+	{
+		const int	bestScoreDiff	= (texWidth/16)*(texHeight/16);
+		const int	worstScoreDiff	= texWidth*texHeight;
+
+		int score = measureAccuracy(log, idealFrame, renderedFrame, bestScoreDiff, worstScoreDiff);
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(score).c_str());
+	}
+
+	return STOP;
+}
+
+// TextureCubeMipmapCase
+
+class TextureCubeMipmapCase : public tcu::TestCase
+{
+public:
+
+								TextureCubeMipmapCase		(tcu::TestContext&			testCtx,
+															 glu::RenderContext&		renderCtx,
+															 const glu::ContextInfo&	renderCtxInfo,
+															 const char*				name,
+															 const char*				desc,
+															 CoordType					coordType,
+															 deUint32					minFilter,
+															 deUint32					wrapS,
+															 deUint32					wrapT,
+															 deUint32					format,
+															 deUint32					dataType,
+															 int						size);
+								~TextureCubeMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								TextureCubeMipmapCase		(const TextureCubeMipmapCase& other);
+	TextureCubeMipmapCase&		operator=					(const TextureCubeMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	CoordType					m_coordType;
+	deUint32					m_minFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	deUint32					m_format;
+	deUint32					m_dataType;
+	int							m_size;
+
+	glu::TextureCube*			m_texture;
+	TextureRenderer				m_renderer;
+};
+
+TextureCubeMipmapCase::TextureCubeMipmapCase (tcu::TestContext&			testCtx,
+											  glu::RenderContext&		renderCtx,
+											  const glu::ContextInfo&	renderCtxInfo,
+											  const char*				name,
+											  const char*				desc,
+											  CoordType					coordType,
+											  deUint32					minFilter,
+											  deUint32					wrapS,
+											  deUint32					wrapT,
+											  deUint32					format,
+											  deUint32					dataType,
+											  int						size)
+	: TestCase			(testCtx, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_coordType		(coordType)
+	, m_minFilter		(minFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_size			(size)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+TextureCubeMipmapCase::~TextureCubeMipmapCase (void)
+{
+	deinit();
+}
+
+void TextureCubeMipmapCase::init (void)
+{
+	m_texture = new glu::TextureCube(m_renderCtx, m_format, m_dataType, m_size);
+
+	int numLevels = deLog2Floor32(m_size)+1;
+
+	// Fill texture with colored grid.
+	for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+	{
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			deUint32	step		= 0xff / (numLevels-1);
+			deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+			deUint32	dec			= 0xff - inc;
+			deUint32	rgb			= 0;
+
+			switch (faceNdx)
+			{
+				case 0: rgb = (inc << 16) | (dec << 8) | 255; break;
+				case 1: rgb = (255 << 16) | (inc << 8) | dec; break;
+				case 2: rgb = (dec << 16) | (255 << 8) | inc; break;
+				case 3: rgb = (dec << 16) | (inc << 8) | 255; break;
+				case 4: rgb = (255 << 16) | (dec << 8) | inc; break;
+				case 5: rgb = (inc << 16) | (255 << 8) | dec; break;
+			}
+
+			deUint32	color		= 0xff000000 | rgb;
+
+			m_texture->getRefTexture().allocLevel((tcu::CubeFace)faceNdx, levelNdx);
+			tcu::clear(m_texture->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)faceNdx), toVec4(tcu::RGBA(color)));
+		}
+	}
+}
+
+void TextureCubeMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+static void randomPartition (vector<IVec4>& dst, de::Random& rnd, int x, int y, int width, int height)
+{
+	const int minWidth	= 8;
+	const int minHeight	= 8;
+
+	bool	partition		= rnd.getFloat() > 0.4f;
+	bool	partitionX		= partition && width > minWidth && rnd.getBool();
+	bool	partitionY		= partition && height > minHeight && !partitionX;
+
+	if (partitionX)
+	{
+		int split = width/2 + rnd.getInt(-width/4, +width/4);
+		randomPartition(dst, rnd, x, y, split, height);
+		randomPartition(dst, rnd, x+split, y, width-split, height);
+	}
+	else if (partitionY)
+	{
+		int split = height/2 + rnd.getInt(-height/4, +height/4);
+		randomPartition(dst, rnd, x, y, width, split);
+		randomPartition(dst, rnd, x, y+split, width, height-split);
+	}
+	else
+		dst.push_back(IVec4(x, y, width, height));
+}
+
+static void computeGridLayout (vector<IVec4>& dst, int width, int height)
+{
+	de::Random rnd(7);
+	randomPartition(dst, rnd, 0, 0, width, height);
+}
+
+TextureCubeMipmapCase::IterateResult TextureCubeMipmapCase::iterate (void)
+{
+	// Constants.
+	const deUint32			magFilter			= GL_NEAREST;
+
+	int						texWidth			= m_texture->getRefTexture().getSize();
+	int						texHeight			= m_texture->getRefTexture().getSize();
+
+	int						defViewportWidth	= texWidth*2;
+	int						defViewportHeight	= texHeight*2;
+
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+	tcu::Sampler			sampler				= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, magFilter);
+	sampler.seamlessCubeMap = true;
+
+	vector<float>			texCoord;
+
+	bool					isProjected			= m_coordType == COORDTYPE_PROJECTED;
+	bool					useLodBias			= m_coordType == COORDTYPE_BASIC_BIAS;
+
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+
+	// Accuracy cases test against ideal lod computation.
+	tcu::Surface			idealFrame			(viewport.width, viewport.height);
+
+	// Accuracy measurements are off unless we get the expected viewport size.
+	if (viewport.width < defViewportWidth || viewport.height < defViewportHeight)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Upload texture data.
+	m_texture->upload();
+
+	// Use unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Compute grid.
+	vector<IVec4> gridLayout;
+	computeGridLayout(gridLayout, viewport.width, viewport.height);
+
+	// Bias values.
+	static const float s_bias[] = { 1.0f, -2.0f, 0.8f, -0.5f, 1.5f, 0.9f, 2.0f, 4.0f };
+
+	// Projection values \note Less agressive than in 2D case due to smaller quads.
+	static const Vec4 s_projections[] =
+	{
+		Vec4(1.2f, 1.0f, 0.7f, 1.0f),
+		Vec4(1.3f, 0.8f, 0.6f, 1.1f),
+		Vec4(0.8f, 1.0f, 1.2f, 0.8f),
+		Vec4(1.2f, 1.0f, 1.3f, 0.9f)
+	};
+
+	for (int cellNdx = 0; cellNdx < (int)gridLayout.size(); cellNdx++)
+	{
+		int				curX		= gridLayout[cellNdx].x();
+		int				curY		= gridLayout[cellNdx].y();
+		int				curW		= gridLayout[cellNdx].z();
+		int				curH		= gridLayout[cellNdx].w();
+		tcu::CubeFace	cubeFace	= (tcu::CubeFace)(cellNdx % tcu::CUBEFACE_LAST);
+		ReferenceParams	params		(TEXTURETYPE_CUBE);
+
+		params.sampler = sampler;
+
+		DE_ASSERT(m_coordType != COORDTYPE_AFFINE); // Not supported.
+		computeQuadTexCoordCube(texCoord, cubeFace);
+
+		if (isProjected)
+		{
+			params.flags	|= ReferenceParams::PROJECTED;
+			params.w		 = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+		}
+
+		if (useLodBias)
+		{
+			params.flags	|= ReferenceParams::USE_BIAS;
+			params.bias		 = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+		}
+
+		// Render with GL.
+		gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+		m_renderer.renderQuad(0, &texCoord[0], params);
+
+		// Render reference(s).
+		{
+			SurfaceAccess idealDst(idealFrame, m_renderCtx.getRenderTarget().getPixelFormat(), curX, curY, curW, curH);
+			params.lodMode = LODMODE_EXACT;
+			sampleTexture(idealDst, m_texture->getRefTexture(), &texCoord[0], params);
+		}
+	}
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log.
+	{
+		const int	bestScoreDiff	= (texWidth/16)*(texHeight/16);
+		const int	worstScoreDiff	= texWidth*texHeight;
+
+		int score = measureAccuracy(log, idealFrame, renderedFrame, bestScoreDiff, worstScoreDiff);
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(score).c_str());
+	}
+
+	return STOP;
+}
+
+TextureMipmapTests::TextureMipmapTests (Context& context)
+	: TestCaseGroup(context, "mipmap", "Mipmapping accuracy tests")
+{
+}
+
+TextureMipmapTests::~TextureMipmapTests (void)
+{
+}
+
+void TextureMipmapTests::init (void)
+{
+	tcu::TestCaseGroup* group2D		= new tcu::TestCaseGroup(m_testCtx, "2d",	"2D Texture Mipmapping");
+	tcu::TestCaseGroup*	groupCube	= new tcu::TestCaseGroup(m_testCtx, "cube",	"Cube Map Filtering");
+	addChild(group2D);
+	addChild(groupCube);
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE },
+		{ "repeat",		GL_REPEAT },
+		{ "mirror",		GL_MIRRORED_REPEAT }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} minFilterModes[] =
+	{
+		{ "nearest_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
+		{ "linear_nearest",		GL_LINEAR_MIPMAP_NEAREST	},
+		{ "nearest_linear",		GL_NEAREST_MIPMAP_LINEAR	},
+		{ "linear_linear",		GL_LINEAR_MIPMAP_LINEAR		}
+	};
+
+	static const struct
+	{
+		CoordType		type;
+		const char*		name;
+		const char*		desc;
+	} coordTypes[] =
+	{
+		{ COORDTYPE_BASIC,		"basic",		"Mipmapping with translated and scaled coordinates" },
+		{ COORDTYPE_AFFINE,		"affine",		"Mipmapping with affine coordinate transform"		},
+		{ COORDTYPE_PROJECTED,	"projected",	"Mipmapping with perspective projection"			}
+	};
+
+	const int tex2DWidth	= 64;
+	const int tex2DHeight	= 64;
+
+	// 2D cases.
+	for (int coordType = 0; coordType < DE_LENGTH_OF_ARRAY(coordTypes); coordType++)
+	{
+		tcu::TestCaseGroup* coordTypeGroup = new tcu::TestCaseGroup(m_testCtx, coordTypes[coordType].name, coordTypes[coordType].desc);
+		group2D->addChild(coordTypeGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+		{
+			for (int wrapMode = 0; wrapMode < DE_LENGTH_OF_ARRAY(wrapModes); wrapMode++)
+			{
+				std::ostringstream name;
+				name << minFilterModes[minFilter].name
+						<< "_" << wrapModes[wrapMode].name;
+
+				coordTypeGroup->addChild(new Texture2DMipmapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																	name.str().c_str(), "",
+																	coordTypes[coordType].type,
+																	minFilterModes[minFilter].mode,
+																	wrapModes[wrapMode].mode,
+																	wrapModes[wrapMode].mode,
+																	GL_RGBA, GL_UNSIGNED_BYTE,
+																	tex2DWidth, tex2DHeight));
+			}
+		}
+	}
+
+	const int cubeMapSize = 64;
+
+	static const struct
+	{
+		CoordType		type;
+		const char*		name;
+		const char*		desc;
+	} cubeCoordTypes[] =
+	{
+		{ COORDTYPE_BASIC,		"basic",		"Mipmapping with translated and scaled coordinates" },
+		{ COORDTYPE_PROJECTED,	"projected",	"Mipmapping with perspective projection"			}
+	};
+
+	// Cubemap cases.
+	for (int coordType = 0; coordType < DE_LENGTH_OF_ARRAY(cubeCoordTypes); coordType++)
+	{
+		tcu::TestCaseGroup* coordTypeGroup = new tcu::TestCaseGroup(m_testCtx, cubeCoordTypes[coordType].name, cubeCoordTypes[coordType].desc);
+		groupCube->addChild(coordTypeGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+		{
+			coordTypeGroup->addChild(new TextureCubeMipmapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+															   minFilterModes[minFilter].name, "",
+															   cubeCoordTypes[coordType].type,
+															   minFilterModes[minFilter].mode,
+															   GL_CLAMP_TO_EDGE,
+															   GL_CLAMP_TO_EDGE,
+															   GL_RGBA, GL_UNSIGNED_BYTE, cubeMapSize));
+		}
+	}
+}
+
+} // Accuracy
+} // gles3
+} // deqp
diff --git a/modules/gles3/accuracy/es3aTextureMipmapTests.hpp b/modules/gles3/accuracy/es3aTextureMipmapTests.hpp
new file mode 100644
index 0000000..6363b31
--- /dev/null
+++ b/modules/gles3/accuracy/es3aTextureMipmapTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3ATEXTUREMIPMAPTESTS_HPP
+#define _ES3ATEXTUREMIPMAPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Mipmapping accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Accuracy
+{
+
+class TextureMipmapTests : public TestCaseGroup
+{
+public:
+							TextureMipmapTests		(Context& context);
+							~TextureMipmapTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureMipmapTests		(const TextureMipmapTests& other);
+	TextureMipmapTests&		operator=				(const TextureMipmapTests& other);
+};
+
+} // Accuracy
+} // gles3
+} // deqp
+
+#endif // _ES3ATEXTUREMIPMAPTESTS_HPP
diff --git a/modules/gles3/accuracy/es3aVaryingInterpolationTests.cpp b/modules/gles3/accuracy/es3aVaryingInterpolationTests.cpp
new file mode 100644
index 0000000..1c7da06
--- /dev/null
+++ b/modules/gles3/accuracy/es3aVaryingInterpolationTests.cpp
@@ -0,0 +1,338 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Varying interpolation accuracy tests.
+ *
+ * \todo [2012-07-03 pyry] On GLES3 we could use floating-point render target
+ *						   for better accuracy evaluation.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3aVaryingInterpolationTests.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluContextInfo.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuFloat.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+#include "glw.h"
+
+using tcu::TestLog;
+using tcu::Vec3;
+using tcu::Vec4;
+using std::string;
+using std::vector;
+using std::map;
+using deqp::gls::TextureTestUtil::SurfaceAccess;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Accuracy
+{
+
+static inline float projectedTriInterpolate (const tcu::Vec3& s, const tcu::Vec3& w, float nx, float ny)
+{
+	return (s[0]*(1.0f-nx-ny)/w[0] + s[1]*ny/w[1] + s[2]*nx/w[2]) / ((1.0f-nx-ny)/w[0] + ny/w[1] + nx/w[2]);
+}
+
+static void renderReference (const SurfaceAccess& dst, const float coords[4*3], const Vec4& wCoord, const Vec3& scale, const Vec3& bias)
+{
+	float		dstW		= (float)dst.getWidth();
+	float		dstH		= (float)dst.getHeight();
+
+	Vec3		triR[2]		= { Vec3(coords[0*3+0], coords[1*3+0], coords[2*3+0]), Vec3(coords[3*3+0], coords[2*3+0], coords[1*3+0]) };
+	Vec3		triG[2]		= { Vec3(coords[0*3+1], coords[1*3+1], coords[2*3+1]), Vec3(coords[3*3+1], coords[2*3+1], coords[1*3+1]) };
+	Vec3		triB[2]		= { Vec3(coords[0*3+2], coords[1*3+2], coords[2*3+2]), Vec3(coords[3*3+2], coords[2*3+2], coords[1*3+2]) };
+	tcu::Vec3	triW[2]		= { wCoord.swizzle(0, 1, 2), wCoord.swizzle(3, 2, 1) };
+
+	for (int py = 0; py < dst.getHeight(); py++)
+	{
+		for (int px = 0; px < dst.getWidth(); px++)
+		{
+			float	wx		= (float)px + 0.5f;
+			float	wy		= (float)py + 0.5f;
+			float	nx		= wx / dstW;
+			float	ny		= wy / dstH;
+
+			int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+			float	triNx	= triNdx ? 1.0f - nx : nx;
+			float	triNy	= triNdx ? 1.0f - ny : ny;
+
+			float	r		= projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy) * scale[0] + bias[0];
+			float	g		= projectedTriInterpolate(triG[triNdx], triW[triNdx], triNx, triNy) * scale[1] + bias[1];
+			float	b		= projectedTriInterpolate(triB[triNdx], triW[triNdx], triNx, triNy) * scale[2] + bias[2];
+
+			Vec4	color	= Vec4(r, g, b, 1.0f);
+
+			dst.setPixel(color, px, py);
+		}
+	}
+}
+
+class InterpolationCase : public TestCase
+{
+public:
+					InterpolationCase			(Context& context, const char* name, const char* desc, glu::Precision precision, const tcu::Vec3& minVal, const tcu::Vec3& maxVal, bool projective);
+					~InterpolationCase			(void);
+
+	IterateResult	iterate						(void);
+
+private:
+	glu::Precision	m_precision;
+	tcu::Vec3		m_min;
+	tcu::Vec3		m_max;
+	bool			m_projective;
+};
+
+InterpolationCase::InterpolationCase (Context& context, const char* name, const char* desc, glu::Precision precision, const tcu::Vec3& minVal, const tcu::Vec3& maxVal, bool projective)
+	: TestCase		(context, tcu::NODETYPE_ACCURACY, name, desc)
+	, m_precision	(precision)
+	, m_min			(minVal)
+	, m_max			(maxVal)
+	, m_projective	(projective)
+{
+}
+
+InterpolationCase::~InterpolationCase (void)
+{
+}
+
+static bool isValidFloat (glu::Precision precision, float val)
+{
+	if (precision == glu::PRECISION_MEDIUMP)
+	{
+		tcu::Float16 fp16(val);
+		return !fp16.isDenorm() && !fp16.isInf() && !fp16.isNaN();
+	}
+	else
+	{
+		tcu::Float32 fp32(val);
+		return !fp32.isDenorm() && !fp32.isInf() && !fp32.isNaN();
+	}
+}
+
+template <int Size>
+static bool isValidFloatVec (glu::Precision precision, const tcu::Vector<float, Size>& vec)
+{
+	for (int ndx = 0; ndx < Size; ndx++)
+	{
+		if (!isValidFloat(precision, vec[ndx]))
+			return false;
+	}
+	return true;
+}
+
+InterpolationCase::IterateResult InterpolationCase::iterate (void)
+{
+	TestLog&					log				= m_testCtx.getLog();
+	de::Random					rnd				(deStringHash(getName()));
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderTarget();
+	int							viewportWidth	= 128;
+	int							viewportHeight	= 128;
+
+	if (renderTarget.getWidth() < viewportWidth ||
+		renderTarget.getHeight() < viewportHeight)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	int							viewportX		= rnd.getInt(0, renderTarget.getWidth()		- viewportWidth);
+	int							viewportY		= rnd.getInt(0, renderTarget.getHeight()	- viewportHeight);
+
+	static const char* s_vertShaderTemplate =
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in ${PRECISION} vec3 a_coords;\n"
+		"out ${PRECISION} vec3 v_coords;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_coords = a_coords;\n"
+		"}\n";
+	static const char* s_fragShaderTemplate =
+		"#version 300 es\n"
+		"in ${PRECISION} vec3 v_coords;\n"
+		"uniform ${PRECISION} vec3 u_scale;\n"
+		"uniform ${PRECISION} vec3 u_bias;\n"
+		"layout(location = 0) out ${PRECISION} vec4 o_color;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	o_color = vec4(v_coords * u_scale + u_bias, 1.0);\n"
+		"}\n";
+
+	map<string, string> templateParams;
+	templateParams["PRECISION"] = glu::getPrecisionName(m_precision);
+
+	glu::ShaderProgram program(m_context.getRenderContext(),
+							   glu::makeVtxFragSources(tcu::StringTemplate(s_vertShaderTemplate).specialize(templateParams),
+													   tcu::StringTemplate(s_fragShaderTemplate).specialize(templateParams)));
+	log << program;
+	if (!program.isOk())
+	{
+		if (m_precision == glu::PRECISION_HIGHP && !m_context.getContextInfo().isFragmentHighPrecisionSupported())
+			m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Fragment highp not supported");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		return STOP;
+	}
+
+	// Position coordinates.
+	Vec4 wCoord = m_projective ? Vec4(1.3f, 0.8f, 0.6f, 2.0f) : Vec4(1.0f, 1.0f, 1.0f, 1.0f);
+	float positions[] =
+	{
+		-1.0f*wCoord.x(), -1.0f*wCoord.x(), 0.0f, wCoord.x(),
+		-1.0f*wCoord.y(), +1.0f*wCoord.y(), 0.0f, wCoord.y(),
+		+1.0f*wCoord.z(), -1.0f*wCoord.z(), 0.0f, wCoord.z(),
+		+1.0f*wCoord.w(), +1.0f*wCoord.w(), 0.0f, wCoord.w()
+	};
+
+	// Coordinates for interpolation.
+	tcu::Vec3 scale	= 1.0f / (m_max - m_min);
+	tcu::Vec3 bias	= -1.0f*m_min*scale;
+	float coords[] =
+	{
+		(0.0f - bias[0])/scale[0], (0.5f - bias[1])/scale[1], (1.0f - bias[2])/scale[2],
+		(0.5f - bias[0])/scale[0], (1.0f - bias[1])/scale[1], (0.5f - bias[2])/scale[2],
+		(0.5f - bias[0])/scale[0], (0.0f - bias[1])/scale[1], (0.5f - bias[2])/scale[2],
+		(1.0f - bias[0])/scale[0], (0.5f - bias[1])/scale[1], (0.0f - bias[2])/scale[2]
+	};
+
+	log << TestLog::Message << "a_coords = " << ((tcu::Vec3(0.0f) - bias)/scale) << " -> " << ((tcu::Vec3(1.0f) - bias)/scale) << TestLog::EndMessage;
+	log << TestLog::Message << "u_scale = " << scale << TestLog::EndMessage;
+	log << TestLog::Message << "u_bias = " << bias << TestLog::EndMessage;
+
+	// Verify that none of the inputs are denormalized / inf / nan.
+	TCU_CHECK(isValidFloatVec(m_precision, scale));
+	TCU_CHECK(isValidFloatVec(m_precision, bias));
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(coords); ndx++)
+	{
+		TCU_CHECK(isValidFloat(m_precision, coords[ndx]));
+		TCU_CHECK(isValidFloat(m_precision, coords[ndx] * scale[ndx % 3] + bias[ndx % 3]));
+	}
+
+	// Indices.
+	static const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+	{
+		const int	posLoc		= glGetAttribLocation(program.getProgram(), "a_position");
+		const int	coordLoc	= glGetAttribLocation(program.getProgram(), "a_coords");
+
+		glEnableVertexAttribArray(posLoc);
+		glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &positions[0]);
+
+		glEnableVertexAttribArray(coordLoc);
+		glVertexAttribPointer(coordLoc, 3, GL_FLOAT, GL_FALSE, 0, &coords[0]);
+	}
+
+	glUseProgram(program.getProgram());
+	glUniform3f(glGetUniformLocation(program.getProgram(), "u_scale"), scale.x(), scale.y(), scale.z());
+	glUniform3f(glGetUniformLocation(program.getProgram(), "u_bias"), bias.x(), bias.y(), bias.z());
+
+	GLU_CHECK_MSG("After program setup");
+
+	// Frames.
+	tcu::Surface	rendered		(viewportWidth, viewportHeight);
+	tcu::Surface	reference		(viewportWidth, viewportHeight);
+	tcu::Surface	diffMask		(viewportWidth, viewportHeight);
+
+	// Render with GL.
+	glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
+	glDrawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_SHORT, &indices[0]);
+
+	// Render reference \note While GPU is hopefully doing our draw call.
+	renderReference(SurfaceAccess(reference, m_context.getRenderTarget().getPixelFormat()), coords, wCoord, scale, bias);
+
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, rendered.getAccess());
+
+	// Compute difference.
+	const int		bestScoreDiff	= 16;
+	const int		worstScoreDiff	= 300;
+	int				score			= tcu::measurePixelDiffAccuracy(log, "Result", "Image comparison result", reference, rendered, bestScoreDiff, worstScoreDiff, tcu::COMPARE_LOG_EVERYTHING);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(score).c_str());
+	return STOP;
+}
+
+VaryingInterpolationTests::VaryingInterpolationTests (Context& context)
+	: TestCaseGroup(context, "interpolation", "Varying Interpolation Accuracy Tests")
+{
+}
+
+VaryingInterpolationTests::~VaryingInterpolationTests (void)
+{
+}
+
+void VaryingInterpolationTests::init (void)
+{
+	DE_STATIC_ASSERT(glu::PRECISION_LOWP+1		== glu::PRECISION_MEDIUMP);
+	DE_STATIC_ASSERT(glu::PRECISION_MEDIUMP+1	== glu::PRECISION_HIGHP);
+
+	// Exp = Emax-3, Mantissa = 0
+	float minF32 = tcu::Float32((0u<<31) | (0xfcu<<23) | 0x0u).asFloat();
+	float maxF32 = tcu::Float32((1u<<31) | (0xfcu<<23) | 0x0u).asFloat();
+	float minF16 = tcu::Float16((deUint16)((0u<<15) | (0x1cu<<10) | 0x0u)).asFloat();
+	float maxF16 = tcu::Float16((deUint16)((1u<<15) | (0x1cu<<10) | 0x0u)).asFloat();
+
+	static const struct
+	{
+		const char*		name;
+		Vec3			minVal;
+		Vec3			maxVal;
+		glu::Precision	minPrecision;
+	} coordRanges[] =
+	{
+		{ "zero_to_one",		Vec3(  0.0f,   0.0f,   0.0f), Vec3(  1.0f,   1.0f,   1.0f), glu::PRECISION_LOWP		},
+		{ "zero_to_minus_one",	Vec3(  0.0f,   0.0f,   0.0f), Vec3( -1.0f,  -1.0f,  -1.0f), glu::PRECISION_LOWP		},
+		{ "minus_one_to_one",	Vec3( -1.0f,  -1.0f,  -1.0f), Vec3(  1.0f,   1.0f,   1.0f), glu::PRECISION_LOWP		},
+		{ "minus_ten_to_ten",	Vec3(-10.0f, -10.0f, -10.0f), Vec3( 10.0f,  10.0f,  10.0f), glu::PRECISION_MEDIUMP	},
+		{ "thousands",			Vec3( -5e3f,   1e3f,   1e3f), Vec3(  3e3f,  -1e3f,   7e3f), glu::PRECISION_MEDIUMP	},
+		{ "full_mediump",		Vec3(minF16, minF16, minF16), Vec3(maxF16, maxF16, maxF16), glu::PRECISION_MEDIUMP	},
+		{ "full_highp",			Vec3(minF32, minF32, minF32), Vec3(maxF32, maxF32, maxF32), glu::PRECISION_HIGHP	},
+	};
+
+	for (int precision = glu::PRECISION_LOWP; precision <= glu::PRECISION_HIGHP; precision++)
+	{
+		for (int coordNdx = 0; coordNdx < DE_LENGTH_OF_ARRAY(coordRanges); coordNdx++)
+		{
+			if (precision < (int)coordRanges[coordNdx].minPrecision)
+				continue;
+
+			string baseName = string(glu::getPrecisionName((glu::Precision)precision)) + "_" + coordRanges[coordNdx].name;
+
+			addChild(new InterpolationCase(m_context, baseName.c_str(),				"",	(glu::Precision)precision, coordRanges[coordNdx].minVal, coordRanges[coordNdx].maxVal, false));
+			addChild(new InterpolationCase(m_context, (baseName + "_proj").c_str(),	"",	(glu::Precision)precision, coordRanges[coordNdx].minVal, coordRanges[coordNdx].maxVal, true));
+		}
+	}
+}
+
+} // Accuracy
+} // gles3
+} // deqp
diff --git a/modules/gles3/accuracy/es3aVaryingInterpolationTests.hpp b/modules/gles3/accuracy/es3aVaryingInterpolationTests.hpp
new file mode 100644
index 0000000..89df6e6
--- /dev/null
+++ b/modules/gles3/accuracy/es3aVaryingInterpolationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3AVARYINGINTERPOLATIONTESTS_HPP
+#define _ES3AVARYINGINTERPOLATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Varying interpolation accuracy tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Accuracy
+{
+
+class VaryingInterpolationTests : public TestCaseGroup
+{
+public:
+									VaryingInterpolationTests		(Context& context);
+									~VaryingInterpolationTests		(void);
+
+	void							init							(void);
+
+private:
+									VaryingInterpolationTests		(const VaryingInterpolationTests& other);
+	VaryingInterpolationTests&		operator=						(const VaryingInterpolationTests& other);
+};
+
+} // Accuracy
+} // gles3
+} // deqp
+
+#endif // _ES3AVARYINGINTERPOLATIONTESTS_HPP
diff --git a/modules/gles3/functional/CMakeLists.txt b/modules/gles3/functional/CMakeLists.txt
new file mode 100644
index 0000000..7705dfb
--- /dev/null
+++ b/modules/gles3/functional/CMakeLists.txt
@@ -0,0 +1,213 @@
+# dEQP-GLES3.functional
+
+set(DEQP_GLES3_FUNCTIONAL_SRCS
+	es3fAttribLocationTests.cpp
+	es3fAttribLocationTests.hpp
+	es3fBuiltinPrecisionTests.cpp
+	es3fBuiltinPrecisionTests.hpp
+	es3fBufferCopyTests.cpp
+	es3fBufferCopyTests.hpp
+	es3fBufferMapTests.cpp
+	es3fBufferMapTests.hpp
+	es3fBufferWriteTests.cpp
+	es3fBufferWriteTests.hpp
+	es3fColorClearTest.cpp
+	es3fColorClearTest.hpp
+	es3fDefaultVertexAttributeTests.cpp
+	es3fDefaultVertexAttributeTests.hpp
+	es3fDepthStencilClearTests.cpp
+	es3fDepthStencilClearTests.hpp
+	es3fDepthStencilTests.cpp
+	es3fDepthStencilTests.hpp
+	es3fDepthTests.cpp
+	es3fDepthTests.hpp
+	es3fFboApiTests.cpp
+	es3fFboApiTests.hpp
+	es3fFboColorbufferTests.cpp
+	es3fFboColorbufferTests.hpp
+	es3fFboCompletenessTests.cpp
+	es3fFboCompletenessTests.hpp
+	es3fFboDepthbufferTests.cpp
+	es3fFboDepthbufferTests.hpp
+	es3fFboInvalidateTests.cpp
+	es3fFboInvalidateTests.hpp
+	es3fFboMultisampleTests.cpp
+	es3fFboMultisampleTests.hpp
+	es3fFboRenderTest.cpp
+	es3fFboRenderTest.hpp
+	es3fFboStencilbufferTests.cpp
+	es3fFboStencilbufferTests.hpp
+	es3fFboTestCase.cpp
+	es3fFboTestCase.hpp
+	es3fFboTestUtil.cpp
+	es3fFboTestUtil.hpp
+	es3fFragDepthTests.cpp
+	es3fFragDepthTests.hpp
+	es3fFragmentOutputTests.cpp
+	es3fFragmentOutputTests.hpp
+	es3fFramebufferBlitTests.cpp
+	es3fFramebufferBlitTests.hpp
+	es3fFunctionalTests.cpp
+	es3fFunctionalTests.hpp
+	es3fLifetimeTests.cpp
+	es3fLifetimeTests.hpp
+	es3fPrerequisiteTests.cpp
+	es3fPrerequisiteTests.hpp
+	es3fRasterizationTests.cpp
+	es3fRasterizationTests.hpp
+	es3fRandomShaderTests.cpp
+	es3fRandomShaderTests.hpp
+	es3fScissorTests.cpp
+	es3fScissorTests.hpp
+	es3fShaderApiTests.cpp
+	es3fShaderApiTests.hpp
+	es3fShaderBuiltinVarTests.cpp
+	es3fShaderBuiltinVarTests.hpp
+	es3fShaderConstExprTests.cpp
+	es3fShaderConstExprTests.hpp
+	es3fShaderDiscardTests.cpp
+	es3fShaderDiscardTests.hpp
+	es3fShaderIndexingTests.cpp
+	es3fShaderIndexingTests.hpp
+	es3fShaderLoopTests.cpp
+	es3fShaderLoopTests.hpp
+	es3fShaderMatrixTests.cpp
+	es3fShaderMatrixTests.hpp
+	es3fShaderOperatorTests.cpp
+	es3fShaderOperatorTests.hpp
+	es3fShaderPrecisionTests.cpp
+	es3fShaderPrecisionTests.hpp
+	es3fShaderReturnTests.cpp
+	es3fShaderReturnTests.hpp
+	es3fShaderStructTests.cpp
+	es3fShaderStructTests.hpp
+	es3fShaderSwitchTests.cpp
+	es3fShaderSwitchTests.hpp
+	es3fShaderTextureFunctionTests.cpp
+	es3fShaderTextureFunctionTests.hpp
+	es3fStencilTests.cpp
+	es3fStencilTests.hpp
+	es3fSyncTests.cpp
+	es3fSyncTests.hpp
+	es3fTextureFilteringTests.cpp
+	es3fTextureFilteringTests.hpp
+	es3fTextureFormatTests.cpp
+	es3fTextureFormatTests.hpp
+	es3fTextureMipmapTests.cpp
+	es3fTextureMipmapTests.hpp
+	es3fTextureShadowTests.cpp
+	es3fTextureShadowTests.hpp
+	es3fTextureSizeTests.cpp
+	es3fTextureSizeTests.hpp
+	es3fTextureSpecificationTests.cpp
+	es3fTextureSpecificationTests.hpp
+	es3fTextureSwizzleTests.cpp
+	es3fTextureSwizzleTests.hpp
+	es3fTextureWrapTests.cpp
+	es3fTextureWrapTests.hpp
+	es3fCompressedTextureTests.cpp
+	es3fCompressedTextureTests.hpp
+	es3fASTCDecompressionCases.cpp
+	es3fASTCDecompressionCases.hpp
+	es3fTransformFeedbackTests.cpp
+	es3fTransformFeedbackTests.hpp
+	es3fUniformBlockTests.cpp
+	es3fUniformBlockTests.hpp
+	es3fVertexArrayTest.cpp
+	es3fVertexArrayTest.hpp
+	es3fSamplerObjectTests.hpp
+	es3fSamplerObjectTests.cpp
+	es3fPixelBufferObjectTests.hpp
+	es3fPixelBufferObjectTests.cpp
+	es3fOcclusionQueryTests.cpp
+	es3fOcclusionQueryTests.hpp
+	es3fVertexTextureTests.cpp
+	es3fVertexTextureTests.hpp
+	es3fTextureUnitTests.cpp
+	es3fTextureUnitTests.hpp
+	es3fVertexArrayObjectTests.cpp
+	es3fVertexArrayObjectTests.hpp
+	es3fPrimitiveRestartTests.cpp
+	es3fPrimitiveRestartTests.hpp
+	es3fRandomFragmentOpTests.cpp
+	es3fRandomFragmentOpTests.hpp
+	es3fInstancedRenderingTests.cpp
+	es3fInstancedRenderingTests.hpp
+	es3fRasterizerDiscardTests.cpp
+	es3fRasterizerDiscardTests.hpp
+	es3fApiCase.cpp
+	es3fApiCase.hpp
+	es3fNegativeBufferApiTests.cpp
+	es3fNegativeBufferApiTests.hpp
+	es3fNegativeTextureApiTests.cpp
+	es3fNegativeTextureApiTests.hpp
+	es3fNegativeShaderApiTests.cpp
+	es3fNegativeShaderApiTests.hpp
+	es3fNegativeFragmentApiTests.cpp
+	es3fNegativeFragmentApiTests.hpp
+	es3fNegativeVertexArrayApiTests.cpp
+	es3fNegativeVertexArrayApiTests.hpp
+	es3fNegativeStateApiTests.cpp
+	es3fNegativeStateApiTests.hpp
+	es3fBlendTests.cpp
+	es3fBlendTests.hpp
+	es3fMultisampleTests.cpp
+	es3fMultisampleTests.hpp
+	es3fUniformApiTests.cpp
+	es3fUniformApiTests.hpp
+	es3fImplementationLimitTests.cpp
+	es3fImplementationLimitTests.hpp
+	es3fReadPixelsTests.hpp
+	es3fReadPixelsTests.cpp
+	es3fDitheringTests.hpp
+	es3fDitheringTests.cpp
+	es3fBooleanStateQueryTests.hpp
+	es3fBooleanStateQueryTests.cpp
+	es3fIntegerStateQueryTests.hpp
+	es3fIntegerStateQueryTests.cpp
+	es3fInteger64StateQueryTests.hpp
+	es3fInteger64StateQueryTests.cpp
+	es3fFloatStateQueryTests.hpp
+	es3fFloatStateQueryTests.cpp
+	es3fTextureStateQueryTests.hpp
+	es3fTextureStateQueryTests.cpp
+	es3fStringQueryTests.hpp
+	es3fStringQueryTests.cpp
+	es3fSamplerStateQueryTests.hpp
+	es3fSamplerStateQueryTests.cpp
+	es3fBufferObjectQueryTests.hpp
+	es3fBufferObjectQueryTests.cpp
+	es3fFboStateQueryTests.hpp
+	es3fFboStateQueryTests.cpp
+	es3fRboStateQueryTests.hpp
+	es3fRboStateQueryTests.cpp
+	es3fShaderStateQueryTests.hpp
+	es3fShaderStateQueryTests.cpp
+	es3fInternalFormatQueryTests.hpp
+	es3fInternalFormatQueryTests.cpp
+	es3fIndexedStateQueryTests.hpp
+	es3fIndexedStateQueryTests.cpp
+	es3fClippingTests.hpp
+	es3fClippingTests.cpp
+	es3fPolygonOffsetTests.hpp
+	es3fPolygonOffsetTests.cpp
+	es3fShaderDerivateTests.cpp
+	es3fShaderDerivateTests.hpp
+	es3fDrawTests.cpp
+	es3fDrawTests.hpp
+	es3fShaderPackingFunctionTests.cpp
+	es3fShaderPackingFunctionTests.hpp
+	es3fShaderCommonFunctionTests.cpp
+	es3fShaderCommonFunctionTests.hpp
+	es3fShaderInvarianceTests.cpp
+	es3fShaderInvarianceTests.hpp
+	es3fFragOpInteractionTests.cpp
+	es3fFragOpInteractionTests.hpp
+	es3fFlushFinishTests.cpp
+	es3fFlushFinishTests.hpp
+	es3fShaderFragDataTests.cpp
+	es3fShaderFragDataTests.hpp
+	)
+
+add_library(deqp-gles3-functional STATIC ${DEQP_GLES3_FUNCTIONAL_SRCS})
+target_link_libraries(deqp-gles3-functional deqp-gl-shared glutil glutil-sglr tcutil referencerenderer ${DEQP_GLES3_LIBRARIES})
diff --git a/modules/gles3/functional/es3fASTCDecompressionCases.cpp b/modules/gles3/functional/es3fASTCDecompressionCases.cpp
new file mode 100644
index 0000000..299dcbd
--- /dev/null
+++ b/modules/gles3/functional/es3fASTCDecompressionCases.cpp
@@ -0,0 +1,2078 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief ASTC decompression tests
+ *
+ * \todo Parts of the block-generation code are same as in decompression
+ *		 code in tcuCompressedTexture.cpp ; could put them to some shared
+ *		 ASTC utility file.
+ *
+ * \todo Tests for void extents with nontrivial extent coordinates.
+ *
+ * \todo Better checking of the error color. Currently legitimate error
+ *		 pixels are just ignored in image comparison; however, spec says
+ *		 that error color is either magenta or all-NaNs. Can NaNs cause
+ *		 troubles, or can we assume that NaNs are well-supported in shader
+ *		 if the implementation chooses NaNs as error color?
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fASTCDecompressionCases.hpp"
+#include "gluTexture.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "tcuCompressedTexture.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuSurface.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deFloat16.h"
+#include "deString.h"
+#include "deMemory.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <vector>
+#include <string>
+#include <algorithm>
+
+using tcu::TestLog;
+using tcu::CompressedTexture;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::Vec2;
+using tcu::Vec4;
+using tcu::Sampler;
+using tcu::Surface;
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+
+using gls::TextureTestUtil::TextureRenderer;
+using gls::TextureTestUtil::RandomViewport;
+using gls::TextureTestUtil::ReferenceParams;
+
+namespace gles3
+{
+namespace Functional
+{
+
+namespace ASTCDecompressionCaseInternal
+{
+
+static const int ASTC_BLOCK_SIZE_BYTES = 128/8;
+
+static inline int divRoundUp (int a, int b)
+{
+	return a/b + ((a%b) ? 1 : 0);
+}
+
+namespace ASTCBlockGeneratorInternal
+{
+
+static inline deUint32 reverseBits (deUint32 src, int numBits)
+{
+	DE_ASSERT(de::inRange(numBits, 0, 32));
+	deUint32 result = 0;
+	for (int i = 0; i < numBits; i++)
+		result |= ((src >> i) & 1) << (numBits-1-i);
+	return result;
+}
+
+static inline deUint32 getBit (deUint32 src, int ndx)
+{
+	DE_ASSERT(de::inBounds(ndx, 0, 32));
+	return (src >> ndx) & 1;
+}
+
+static inline deUint32 getBits (deUint32 src, int low, int high)
+{
+	const int numBits = (high-low) + 1;
+	if (numBits == 0)
+		return 0;
+	DE_ASSERT(de::inRange(numBits, 1, 32));
+	return (src >> low) & ((1u<<numBits)-1);
+}
+
+#if defined(DE_DEBUG)
+static inline bool isFloat16InfOrNan (deFloat16 v)
+{
+	return getBits(v, 10, 14) == 31;
+}
+#endif
+
+template <typename T, typename Y>
+struct isSameType			{ enum { V = 0 }; };
+template <typename T>
+struct isSameType<T, T>		{ enum { V = 1 }; };
+
+// Helper class for setting bits in a 128-bit block.
+class AssignBlock128
+{
+private:
+	typedef deUint64 Word;
+
+	enum
+	{
+		WORD_BYTES	= sizeof(Word),
+		WORD_BITS	= 8*WORD_BYTES,
+		NUM_WORDS	= 128 / WORD_BITS
+	};
+
+	DE_STATIC_ASSERT(128 % WORD_BITS == 0);
+
+public:
+	AssignBlock128 (void)
+	{
+		for (int wordNdx = 0; wordNdx < NUM_WORDS; wordNdx++)
+			m_words[wordNdx] = 0;
+	}
+
+	void setBit (int ndx, deUint32 val)
+	{
+		DE_ASSERT(de::inBounds(ndx, 0, 128));
+		DE_ASSERT((val & 1) == val);
+		const int wordNdx	= ndx / WORD_BITS;
+		const int bitNdx	= ndx % WORD_BITS;
+		m_words[wordNdx] = (m_words[wordNdx] & ~((Word)1 << bitNdx)) | ((Word)val << bitNdx);
+	}
+
+	void setBits (int low, int high, deUint32 bits)
+	{
+		DE_ASSERT(de::inBounds(low, 0, 128));
+		DE_ASSERT(de::inBounds(high, 0, 128));
+		DE_ASSERT(de::inRange(high-low+1, 0, 32));
+		DE_ASSERT((bits & (((Word)1 << (high-low+1)) - 1)) == bits);
+
+		if (high-low+1 == 0)
+			return;
+
+		const int word0Ndx		= low / WORD_BITS;
+		const int word1Ndx		= high / WORD_BITS;
+		const int lowNdxInW0	= low % WORD_BITS;
+
+		if (word0Ndx == word1Ndx)
+			m_words[word0Ndx] = (m_words[word0Ndx] & ~((((Word)1 << (high-low+1)) - 1) << lowNdxInW0)) | ((Word)bits << lowNdxInW0);
+		else
+		{
+			DE_ASSERT(word1Ndx == word0Ndx + 1);
+
+			const int	highNdxInW1			= high % WORD_BITS;
+			const int	numBitsToSetInW0	= WORD_BITS - lowNdxInW0;
+			const Word	bitsLowMask			= ((Word)1 << numBitsToSetInW0) - 1;
+
+			m_words[word0Ndx] = (m_words[word0Ndx] & (((Word)1 << lowNdxInW0) - 1))			| (((Word)bits & bitsLowMask) << lowNdxInW0);
+			m_words[word1Ndx] = (m_words[word1Ndx] & ~(((Word)1 << (highNdxInW1+1)) - 1))	| (((Word)bits & ~bitsLowMask) >> numBitsToSetInW0);
+		}
+	}
+
+	void assignToMemory (deUint8* dst) const
+	{
+		for (int wordNdx = 0; wordNdx < NUM_WORDS; wordNdx++)
+		{
+			for (int byteNdx = 0; byteNdx < WORD_BYTES; byteNdx++)
+				dst[wordNdx*WORD_BYTES + byteNdx] = (deUint8)((m_words[wordNdx] >> (8*byteNdx)) & 0xff);
+		}
+	}
+
+	void pushBytesToVector (vector<deUint8>& dst) const
+	{
+		const int assignStartIndex = (int)dst.size();
+		dst.resize(dst.size() + ASTC_BLOCK_SIZE_BYTES);
+		assignToMemory(&dst[assignStartIndex]);
+	}
+
+private:
+	Word m_words[NUM_WORDS];
+};
+
+// A helper for sequential access into a AssignBlock128.
+class BitAssignAccessStream
+{
+public:
+	BitAssignAccessStream (AssignBlock128& dst, int startNdxInSrc, int length, bool forward)
+		: m_dst				(dst)
+		, m_startNdxInSrc	(startNdxInSrc)
+		, m_length			(length)
+		, m_forward			(forward)
+		, m_ndx				(0)
+	{
+	}
+
+	// Set the next num bits. Bits at positions greater than or equal to m_length are not touched.
+	void setNext (int num, deUint32 bits)
+	{
+		DE_ASSERT((bits & (((deUint64)1 << num) - 1)) == bits);
+
+		if (num == 0 || m_ndx >= m_length)
+			return;
+
+		const int		end				= m_ndx + num;
+		const int		numBitsToDst	= de::max(0, de::min(m_length, end) - m_ndx);
+		const int		low				= m_ndx;
+		const int		high			= m_ndx + numBitsToDst - 1;
+		const deUint32	actualBits		= getBits(bits, 0, numBitsToDst-1);
+
+		m_ndx += num;
+
+		return m_forward ? m_dst.setBits(m_startNdxInSrc + low,  m_startNdxInSrc + high, actualBits)
+						 : m_dst.setBits(m_startNdxInSrc - high, m_startNdxInSrc - low, reverseBits(actualBits, numBitsToDst));
+	}
+
+private:
+	AssignBlock128&		m_dst;
+	const int			m_startNdxInSrc;
+	const int			m_length;
+	const bool			m_forward;
+
+	int					m_ndx;
+};
+
+struct VoidExtentParams
+{
+	DE_STATIC_ASSERT((isSameType<deFloat16, deUint16>::V));
+	bool		isHDR;
+	deUint16	r;
+	deUint16	g;
+	deUint16	b;
+	deUint16	a;
+	// \note Currently extent coordinates are all set to all-ones.
+
+	VoidExtentParams (bool isHDR_, deUint16 r_, deUint16 g_, deUint16 b_, deUint16 a_) : isHDR(isHDR_), r(r_), g(g_), b(b_), a(a_) {}
+};
+
+static AssignBlock128 generateVoidExtentBlock (const VoidExtentParams& params)
+{
+	AssignBlock128 block;
+
+	block.setBits(0, 8, 0x1fc); // \note Marks void-extent block.
+	block.setBit(9, params.isHDR);
+	block.setBits(10, 11, 3); // \note Spec shows that these bits are both set, although they serve no purpose.
+
+	// Extent coordinates - currently all-ones.
+	block.setBits(12, 24, 0x1fff);
+	block.setBits(25, 37, 0x1fff);
+	block.setBits(38, 50, 0x1fff);
+	block.setBits(51, 63, 0x1fff);
+
+	DE_ASSERT(!params.isHDR || (!isFloat16InfOrNan(params.r) &&
+								!isFloat16InfOrNan(params.g) &&
+								!isFloat16InfOrNan(params.b) &&
+								!isFloat16InfOrNan(params.a)));
+
+	block.setBits(64,  79,  params.r);
+	block.setBits(80,  95,  params.g);
+	block.setBits(96,  111, params.b);
+	block.setBits(112, 127, params.a);
+
+	return block;
+}
+
+enum ISEMode
+{
+	ISEMODE_TRIT = 0,
+	ISEMODE_QUINT,
+	ISEMODE_PLAIN_BIT,
+
+	ISEMODE_LAST
+};
+
+struct ISEParams
+{
+	ISEMode		mode;
+	int			numBits;
+
+	ISEParams (ISEMode mode_, int numBits_) : mode(mode_), numBits(numBits_) {}
+};
+
+// An input array of ISE inputs for an entire ASTC block. Can be given as either single values in the
+// range [0, maximumValueOfISERange] or as explicit block value specifications. The latter is needed
+// so we can test all possible values of T and Q in a block, since multiple T or Q values may map
+// to the same set of decoded values.
+struct ISEInput
+{
+	struct Block
+	{
+		deUint32 tOrQValue; //!< The 8-bit T or 7-bit Q in a trit or quint ISE block.
+		deUint32 bitValues[5];
+	};
+
+	bool isGivenInBlockForm;
+	union
+	{
+		//!< \note 64 comes from the maximum number of weight values in an ASTC block.
+		deUint32	plain[64];
+		Block		block[64];
+	} value;
+
+	ISEInput (void)
+		: isGivenInBlockForm (false)
+	{
+	}
+};
+
+static inline int computeNumRequiredBits (const ISEParams& iseParams, int numValues)
+{
+	switch (iseParams.mode)
+	{
+		case ISEMODE_TRIT:			return divRoundUp(numValues*8, 5) + numValues*iseParams.numBits;
+		case ISEMODE_QUINT:			return divRoundUp(numValues*7, 3) + numValues*iseParams.numBits;
+		case ISEMODE_PLAIN_BIT:		return numValues*iseParams.numBits;
+		default:
+			DE_ASSERT(false);
+			return -1;
+	}
+}
+
+static inline deUint32 computeISERangeMax (const ISEParams& iseParams)
+{
+	switch (iseParams.mode)
+	{
+		case ISEMODE_TRIT:			return (1u << iseParams.numBits) * 3 - 1;
+		case ISEMODE_QUINT:			return (1u << iseParams.numBits) * 5 - 1;
+		case ISEMODE_PLAIN_BIT:		return (1u << iseParams.numBits)     - 1;
+		default:
+			DE_ASSERT(false);
+			return -1;
+	}
+}
+
+struct NormalBlockParams
+{
+	int					weightGridWidth;
+	int					weightGridHeight;
+	ISEParams			weightISEParams;
+	bool				isDualPlane;
+	deUint32			ccs; //! \note Irrelevant if !isDualPlane.
+	int					numPartitions;
+	deUint32			colorEndpointModes[4];
+	// \note Below members are irrelevant if numPartitions == 1.
+	bool				isMultiPartSingleCemMode; //! \note If true, the single CEM is at colorEndpointModes[0].
+	deUint32			partitionSeed;
+
+	NormalBlockParams (void)
+		: weightGridWidth			(-1)
+		, weightGridHeight			(-1)
+		, weightISEParams			(ISEMODE_LAST, -1)
+		, isDualPlane				(true)
+		, ccs						((deUint32)-1)
+		, numPartitions				(-1)
+		, isMultiPartSingleCemMode	(false)
+		, partitionSeed				((deUint32)-1)
+	{
+		colorEndpointModes[0] = 0;
+		colorEndpointModes[1] = 0;
+		colorEndpointModes[2] = 0;
+		colorEndpointModes[3] = 0;
+	}
+};
+
+struct NormalBlockISEInputs
+{
+	ISEInput weight;
+	ISEInput endpoint;
+
+	NormalBlockISEInputs (void)
+		: weight	()
+		, endpoint	()
+	{
+	}
+};
+
+static inline int computeNumWeights (const NormalBlockParams& params)
+{
+	return params.weightGridWidth * params.weightGridHeight * (params.isDualPlane ? 2 : 1);
+}
+
+static inline int computeNumBitsForColorEndpoints (const NormalBlockParams& params)
+{
+	const int numWeightBits			= computeNumRequiredBits(params.weightISEParams, computeNumWeights(params));
+	const int numConfigDataBits		= (params.numPartitions == 1 ? 17 : params.isMultiPartSingleCemMode ? 29 : 25 + 3*params.numPartitions) +
+									  (params.isDualPlane ? 2 : 0);
+
+	return 128 - numWeightBits - numConfigDataBits;
+}
+
+static inline int computeNumColorEndpointValues (deUint32 endpointMode)
+{
+	DE_ASSERT(endpointMode < 16);
+	return (endpointMode/4 + 1) * 2;
+}
+
+static inline int computeNumColorEndpointValues (const deUint32* endpointModes, int numPartitions, bool isMultiPartSingleCemMode)
+{
+	if (isMultiPartSingleCemMode)
+		return numPartitions * computeNumColorEndpointValues(endpointModes[0]);
+	else
+	{
+		int result = 0;
+		for (int i = 0; i < numPartitions; i++)
+			result += computeNumColorEndpointValues(endpointModes[i]);
+		return result;
+	}
+}
+
+static inline bool isValidBlockParams (const NormalBlockParams& params, int blockWidth, int blockHeight)
+{
+	const int numWeights				= computeNumWeights(params);
+	const int numWeightBits				= computeNumRequiredBits(params.weightISEParams, numWeights);
+	const int numColorEndpointValues	= computeNumColorEndpointValues(&params.colorEndpointModes[0], params.numPartitions, params.isMultiPartSingleCemMode);
+	const int numBitsForColorEndpoints	= computeNumBitsForColorEndpoints(params);
+
+	return numWeights <= 64										&&
+		   de::inRange(numWeightBits, 24, 96)					&&
+		   params.weightGridWidth <= blockWidth					&&
+		   params.weightGridHeight <= blockHeight				&&
+		   !(params.numPartitions == 4 && params.isDualPlane)	&&
+		   numColorEndpointValues <= 18							&&
+		   numBitsForColorEndpoints >= divRoundUp(13*numColorEndpointValues, 5);
+}
+
+// Write bits 0 to 10 of an ASTC block.
+static void writeBlockMode (AssignBlock128& dst, const NormalBlockParams& blockParams)
+{
+	const deUint32	d = blockParams.isDualPlane != 0;
+	// r and h initialized in switch below.
+	deUint32		r;
+	deUint32		h;
+	// a, b and blockModeLayoutNdx initialized in block mode layout index detecting loop below.
+	deUint32		a = (deUint32)-1;
+	deUint32		b = (deUint32)-1;
+	int				blockModeLayoutNdx;
+
+	// Find the values of r and h (ISE range).
+	switch (computeISERangeMax(blockParams.weightISEParams))
+	{
+		case 1:		r = 2; h = 0;	break;
+		case 2:		r = 3; h = 0;	break;
+		case 3:		r = 4; h = 0;	break;
+		case 4:		r = 5; h = 0;	break;
+		case 5:		r = 6; h = 0;	break;
+		case 7:		r = 7; h = 0;	break;
+
+		case 9:		r = 2; h = 1;	break;
+		case 11:	r = 3; h = 1;	break;
+		case 15:	r = 4; h = 1;	break;
+		case 19:	r = 5; h = 1;	break;
+		case 23:	r = 6; h = 1;	break;
+		case 31:	r = 7; h = 1;	break;
+
+		default:
+			DE_ASSERT(false);
+			r = (deUint32)-1;
+			h = (deUint32)-1;
+	}
+
+	// Find block mode layout index, i.e. appropriate row in the "2d block mode layout" table in ASTC spec.
+
+	{
+		enum BlockModeLayoutABVariable { Z=0, A=1, B=2 };
+
+		static const struct BlockModeLayout
+		{
+			int							aNumBits;
+			int							bNumBits;
+			BlockModeLayoutABVariable	gridWidthVariableTerm;
+			int							gridWidthConstantTerm;
+			BlockModeLayoutABVariable	gridHeightVariableTerm;
+			int							gridHeightConstantTerm;
+		} blockModeLayouts[] =
+		{
+			{ 2, 2,   B,  4,   A,  2},
+			{ 2, 2,   B,  8,   A,  2},
+			{ 2, 2,   A,  2,   B,  8},
+			{ 2, 1,   A,  2,   B,  6},
+			{ 2, 1,   B,  2,   A,  2},
+			{ 2, 0,   Z, 12,   A,  2},
+			{ 2, 0,   A,  2,   Z, 12},
+			{ 0, 0,   Z,  6,   Z, 10},
+			{ 0, 0,   Z, 10,   Z,  6},
+			{ 2, 2,   A,  6,   B,  6}
+		};
+
+		for (blockModeLayoutNdx = 0; blockModeLayoutNdx < DE_LENGTH_OF_ARRAY(blockModeLayouts); blockModeLayoutNdx++)
+		{
+			const BlockModeLayout&	layout					= blockModeLayouts[blockModeLayoutNdx];
+			const int				aMax					= (1 << layout.aNumBits) - 1;
+			const int				bMax					= (1 << layout.bNumBits) - 1;
+			const int				variableOffsetsMax[3]	= { 0, aMax, bMax };
+			const int				widthMin				= layout.gridWidthConstantTerm;
+			const int				heightMin				= layout.gridHeightConstantTerm;
+			const int				widthMax				= widthMin  + variableOffsetsMax[layout.gridWidthVariableTerm];
+			const int				heightMax				= heightMin + variableOffsetsMax[layout.gridHeightVariableTerm];
+
+			DE_ASSERT(layout.gridWidthVariableTerm != layout.gridHeightVariableTerm || layout.gridWidthVariableTerm == Z);
+
+			if (de::inRange(blockParams.weightGridWidth, widthMin, widthMax) &&
+				de::inRange(blockParams.weightGridHeight, heightMin, heightMax))
+			{
+				deUint32	dummy			= 0;
+				deUint32&	widthVariable	= layout.gridWidthVariableTerm == A  ? a : layout.gridWidthVariableTerm == B  ? b : dummy;
+				deUint32&	heightVariable	= layout.gridHeightVariableTerm == A ? a : layout.gridHeightVariableTerm == B ? b : dummy;
+
+				widthVariable	= blockParams.weightGridWidth  - layout.gridWidthConstantTerm;
+				heightVariable	= blockParams.weightGridHeight - layout.gridHeightConstantTerm;
+
+				break;
+			}
+		}
+	}
+
+	// Set block mode bits.
+
+	const deUint32 a0 = getBit(a, 0);
+	const deUint32 a1 = getBit(a, 1);
+	const deUint32 b0 = getBit(b, 0);
+	const deUint32 b1 = getBit(b, 1);
+	const deUint32 r0 = getBit(r, 0);
+	const deUint32 r1 = getBit(r, 1);
+	const deUint32 r2 = getBit(r, 2);
+
+#define SB(NDX, VAL) dst.setBit((NDX), (VAL))
+#define ASSIGN_BITS(B10, B9, B8, B7, B6, B5, B4, B3, B2, B1, B0) do { SB(10,(B10)); SB(9,(B9)); SB(8,(B8)); SB(7,(B7)); SB(6,(B6)); SB(5,(B5)); SB(4,(B4)); SB(3,(B3)); SB(2,(B2)); SB(1,(B1)); SB(0,(B0)); } while (false)
+
+	switch (blockModeLayoutNdx)
+	{
+		case 0: ASSIGN_BITS(d,  h,  b1, b0, a1, a0, r0, 0,  0,  r2, r1);									break;
+		case 1: ASSIGN_BITS(d,  h,  b1, b0, a1, a0, r0, 0,  1,  r2, r1);									break;
+		case 2: ASSIGN_BITS(d,  h,  b1, b0, a1, a0, r0, 1,  0,  r2, r1);									break;
+		case 3: ASSIGN_BITS(d,  h,   0,  b, a1, a0, r0, 1,  1,  r2, r1);									break;
+		case 4: ASSIGN_BITS(d,  h,   1,  b, a1, a0, r0, 1,  1,  r2, r1);									break;
+		case 5: ASSIGN_BITS(d,  h,   0,  0, a1, a0, r0, r2, r1,  0,  0);									break;
+		case 6: ASSIGN_BITS(d,  h,   0,  1, a1, a0, r0, r2, r1,  0,  0);									break;
+		case 7: ASSIGN_BITS(d,  h,   1,  1,  0,  0, r0, r2, r1,  0,  0);									break;
+		case 8: ASSIGN_BITS(d,  h,   1,  1,  0,  1, r0, r2, r1,  0,  0);									break;
+		case 9: ASSIGN_BITS(b1, b0,  1,  0, a1, a0, r0, r2, r1,  0,  0); DE_ASSERT(d == 0 && h == 0);		break;
+		default:
+			DE_ASSERT(false);
+	}
+
+#undef ASSIGN_BITS
+#undef SB
+}
+
+// Write color endpoint mode data of an ASTC block.
+static void writeColorEndpointModes (AssignBlock128& dst, const deUint32* colorEndpointModes, bool isMultiPartSingleCemMode, int numPartitions, int extraCemBitsStart)
+{
+	if (numPartitions == 1)
+		dst.setBits(13, 16, colorEndpointModes[0]);
+	else
+	{
+		if (isMultiPartSingleCemMode)
+		{
+			dst.setBits(23, 24, 0);
+			dst.setBits(25, 28, colorEndpointModes[0]);
+		}
+		else
+		{
+			DE_ASSERT(numPartitions > 0);
+			const deUint32 minCem				= *std::min_element(&colorEndpointModes[0], &colorEndpointModes[numPartitions]);
+			const deUint32 maxCem				= *std::max_element(&colorEndpointModes[0], &colorEndpointModes[numPartitions]);
+			const deUint32 minCemClass			= minCem/4;
+			const deUint32 maxCemClass			= maxCem/4;
+			DE_ASSERT(maxCemClass - minCemClass <= 1);
+			DE_UNREF(minCemClass); // \note For non-debug builds.
+			const deUint32 highLevelSelector	= de::max(1u, maxCemClass);
+
+			dst.setBits(23, 24, highLevelSelector);
+
+			for (int partNdx = 0; partNdx < numPartitions; partNdx++)
+			{
+				const deUint32 c			= colorEndpointModes[partNdx] / 4 == highLevelSelector ? 1 : 0;
+				const deUint32 m			= colorEndpointModes[partNdx] % 4;
+				const deUint32 lowMBit0Ndx	= numPartitions + 2*partNdx;
+				const deUint32 lowMBit1Ndx	= numPartitions + 2*partNdx + 1;
+				dst.setBit(25 + partNdx, c);
+				dst.setBit(lowMBit0Ndx < 4 ? 25+lowMBit0Ndx : extraCemBitsStart+lowMBit0Ndx-4, getBit(m, 0));
+				dst.setBit(lowMBit1Ndx < 4 ? 25+lowMBit1Ndx : extraCemBitsStart+lowMBit1Ndx-4, getBit(m, 1));
+			}
+		}
+	}
+}
+
+static ISEParams computeMaximumRangeISEParams (int numAvailableBits, int numValuesInSequence)
+{
+	int curBitsForTritMode		= 6;
+	int curBitsForQuintMode		= 5;
+	int curBitsForPlainBitMode	= 8;
+
+	while (true)
+	{
+		DE_ASSERT(curBitsForTritMode > 0 || curBitsForQuintMode > 0 || curBitsForPlainBitMode > 0);
+
+		const int tritRange			= curBitsForTritMode > 0		? (3 << curBitsForTritMode) - 1			: -1;
+		const int quintRange		= curBitsForQuintMode > 0		? (5 << curBitsForQuintMode) - 1		: -1;
+		const int plainBitRange		= curBitsForPlainBitMode > 0	? (1 << curBitsForPlainBitMode) - 1		: -1;
+		const int maxRange			= de::max(de::max(tritRange, quintRange), plainBitRange);
+
+		if (maxRange == tritRange)
+		{
+			const ISEParams params(ISEMODE_TRIT, curBitsForTritMode);
+			if (computeNumRequiredBits(params, numValuesInSequence) <= numAvailableBits)
+				return ISEParams(ISEMODE_TRIT, curBitsForTritMode);
+			curBitsForTritMode--;
+		}
+		else if (maxRange == quintRange)
+		{
+			const ISEParams params(ISEMODE_QUINT, curBitsForQuintMode);
+			if (computeNumRequiredBits(params, numValuesInSequence) <= numAvailableBits)
+				return ISEParams(ISEMODE_QUINT, curBitsForQuintMode);
+			curBitsForQuintMode--;
+		}
+		else
+		{
+			const ISEParams params(ISEMODE_PLAIN_BIT, curBitsForPlainBitMode);
+			DE_ASSERT(maxRange == plainBitRange);
+			if (computeNumRequiredBits(params, numValuesInSequence) <= numAvailableBits)
+				return ISEParams(ISEMODE_PLAIN_BIT, curBitsForPlainBitMode);
+			curBitsForPlainBitMode--;
+		}
+	}
+}
+
+static void encodeISETritBlock (BitAssignAccessStream& dst, int numBits, bool fromExplicitInputBlock, const ISEInput::Block& blockInput, const deUint32* nonBlockInput, int numValues)
+{
+	// tritBlockTValue[t0][t1][t2][t3][t4] is a value of T (not necessarily the only one) that will yield the given trits when decoded.
+	static const deUint32 tritBlockTValue[3][3][3][3][3] =
+	{
+		{
+			{{{0, 128, 96}, {32, 160, 224}, {64, 192, 28}}, {{16, 144, 112}, {48, 176, 240}, {80, 208, 156}}, {{3, 131, 99}, {35, 163, 227}, {67, 195, 31}}},
+			{{{4, 132, 100}, {36, 164, 228}, {68, 196, 60}}, {{20, 148, 116}, {52, 180, 244}, {84, 212, 188}}, {{19, 147, 115}, {51, 179, 243}, {83, 211, 159}}},
+			{{{8, 136, 104}, {40, 168, 232}, {72, 200, 92}}, {{24, 152, 120}, {56, 184, 248}, {88, 216, 220}}, {{12, 140, 108}, {44, 172, 236}, {76, 204, 124}}}
+		},
+		{
+			{{{1, 129, 97}, {33, 161, 225}, {65, 193, 29}}, {{17, 145, 113}, {49, 177, 241}, {81, 209, 157}}, {{7, 135, 103}, {39, 167, 231}, {71, 199, 63}}},
+			{{{5, 133, 101}, {37, 165, 229}, {69, 197, 61}}, {{21, 149, 117}, {53, 181, 245}, {85, 213, 189}}, {{23, 151, 119}, {55, 183, 247}, {87, 215, 191}}},
+			{{{9, 137, 105}, {41, 169, 233}, {73, 201, 93}}, {{25, 153, 121}, {57, 185, 249}, {89, 217, 221}}, {{13, 141, 109}, {45, 173, 237}, {77, 205, 125}}}
+		},
+		{
+			{{{2, 130, 98}, {34, 162, 226}, {66, 194, 30}}, {{18, 146, 114}, {50, 178, 242}, {82, 210, 158}}, {{11, 139, 107}, {43, 171, 235}, {75, 203, 95}}},
+			{{{6, 134, 102}, {38, 166, 230}, {70, 198, 62}}, {{22, 150, 118}, {54, 182, 246}, {86, 214, 190}}, {{27, 155, 123}, {59, 187, 251}, {91, 219, 223}}},
+			{{{10, 138, 106}, {42, 170, 234}, {74, 202, 94}}, {{26, 154, 122}, {58, 186, 250}, {90, 218, 222}}, {{14, 142, 110}, {46, 174, 238}, {78, 206, 126}}}
+		}
+	};
+
+	DE_ASSERT(de::inRange(numValues, 1, 5));
+
+	deUint32 tritParts[5];
+	deUint32 bitParts[5];
+
+	for (int i = 0; i < 5; i++)
+	{
+		if (i < numValues)
+		{
+			if (fromExplicitInputBlock)
+			{
+				bitParts[i]		= blockInput.bitValues[i];
+				tritParts[i]	= -1; // \note Won't be used, but silences warning.
+			}
+			else
+			{
+				bitParts[i]		= getBits(nonBlockInput[i], 0, numBits-1);
+				tritParts[i]	= nonBlockInput[i] >> numBits;
+			}
+		}
+		else
+		{
+			bitParts[i]		= 0;
+			tritParts[i]	= 0;
+		}
+	}
+
+	const deUint32 T = fromExplicitInputBlock ? blockInput.tOrQValue : tritBlockTValue[tritParts[0]]
+																					  [tritParts[1]]
+																					  [tritParts[2]]
+																					  [tritParts[3]]
+																					  [tritParts[4]];
+
+	dst.setNext(numBits,	bitParts[0]);
+	dst.setNext(2,			getBits(T, 0, 1));
+	dst.setNext(numBits,	bitParts[1]);
+	dst.setNext(2,			getBits(T, 2, 3));
+	dst.setNext(numBits,	bitParts[2]);
+	dst.setNext(1,			getBit(T, 4));
+	dst.setNext(numBits,	bitParts[3]);
+	dst.setNext(2,			getBits(T, 5, 6));
+	dst.setNext(numBits,	bitParts[4]);
+	dst.setNext(1,			getBit(T, 7));
+}
+
+static void encodeISEQuintBlock (BitAssignAccessStream& dst, int numBits, bool fromExplicitInputBlock, const ISEInput::Block& blockInput, const deUint32* nonBlockInput, int numValues)
+{
+	// quintBlockQValue[q0][q1][q2] is a value of Q (not necessarily the only one) that will yield the given quints when decoded.
+	static const deUint32 quintBlockQValue[5][5][5] =
+	{
+		{{0, 32, 64, 96, 102}, {8, 40, 72, 104, 110}, {16, 48, 80, 112, 118}, {24, 56, 88, 120, 126}, {5, 37, 69, 101, 39}},
+		{{1, 33, 65, 97, 103}, {9, 41, 73, 105, 111}, {17, 49, 81, 113, 119}, {25, 57, 89, 121, 127}, {13, 45, 77, 109, 47}},
+		{{2, 34, 66, 98, 70}, {10, 42, 74, 106, 78}, {18, 50, 82, 114, 86}, {26, 58, 90, 122, 94}, {21, 53, 85, 117, 55}},
+		{{3, 35, 67, 99, 71}, {11, 43, 75, 107, 79}, {19, 51, 83, 115, 87}, {27, 59, 91, 123, 95}, {29, 61, 93, 125, 63}},
+		{{4, 36, 68, 100, 38}, {12, 44, 76, 108, 46}, {20, 52, 84, 116, 54}, {28, 60, 92, 124, 62}, {6, 14, 22, 30, 7}}
+	};
+
+	DE_ASSERT(de::inRange(numValues, 1, 3));
+
+	deUint32 quintParts[3];
+	deUint32 bitParts[3];
+
+	for (int i = 0; i < 3; i++)
+	{
+		if (i < numValues)
+		{
+			if (fromExplicitInputBlock)
+			{
+				bitParts[i]		= blockInput.bitValues[i];
+				quintParts[i]	= -1; // \note Won't be used, but silences warning.
+			}
+			else
+			{
+				bitParts[i]		= getBits(nonBlockInput[i], 0, numBits-1);
+				quintParts[i]	= nonBlockInput[i] >> numBits;
+			}
+		}
+		else
+		{
+			bitParts[i]		= 0;
+			quintParts[i]	= 0;
+		}
+	}
+
+	const deUint32 Q = fromExplicitInputBlock ? blockInput.tOrQValue : quintBlockQValue[quintParts[0]]
+																					   [quintParts[1]]
+																					   [quintParts[2]];
+
+	dst.setNext(numBits,	bitParts[0]);
+	dst.setNext(3,			getBits(Q, 0, 2));
+	dst.setNext(numBits,	bitParts[1]);
+	dst.setNext(2,			getBits(Q, 3, 4));
+	dst.setNext(numBits,	bitParts[2]);
+	dst.setNext(2,			getBits(Q, 5, 6));
+}
+
+static void encodeISEBitBlock (BitAssignAccessStream& dst, int numBits, deUint32 value)
+{
+	DE_ASSERT(de::inRange(value, 0u, (1u<<numBits)-1));
+	dst.setNext(numBits, value);
+}
+
+static void encodeISE (BitAssignAccessStream& dst, const ISEParams& params, const ISEInput& input, int numValues)
+{
+	if (params.mode == ISEMODE_TRIT)
+	{
+		const int numBlocks = divRoundUp(numValues, 5);
+		for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+		{
+			const int numValuesInBlock = blockNdx == numBlocks-1 ? numValues - 5*(numBlocks-1) : 5;
+			encodeISETritBlock(dst, params.numBits, input.isGivenInBlockForm,
+							   input.isGivenInBlockForm ? input.value.block[blockNdx]	: ISEInput::Block(),
+							   input.isGivenInBlockForm ? DE_NULL						: &input.value.plain[5*blockNdx],
+							   numValuesInBlock);
+		}
+	}
+	else if (params.mode == ISEMODE_QUINT)
+	{
+		const int numBlocks = divRoundUp(numValues, 3);
+		for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+		{
+			const int numValuesInBlock = blockNdx == numBlocks-1 ? numValues - 3*(numBlocks-1) : 3;
+			encodeISEQuintBlock(dst, params.numBits, input.isGivenInBlockForm,
+								input.isGivenInBlockForm ? input.value.block[blockNdx]	: ISEInput::Block(),
+								input.isGivenInBlockForm ? DE_NULL						: &input.value.plain[3*blockNdx],
+								numValuesInBlock);
+		}
+	}
+	else
+	{
+		DE_ASSERT(params.mode == ISEMODE_PLAIN_BIT);
+		for (int i = 0; i < numValues; i++)
+			encodeISEBitBlock(dst, params.numBits, input.isGivenInBlockForm ? input.value.block[i].bitValues[0] : input.value.plain[i]);
+	}
+}
+
+static void writeWeightData (AssignBlock128& dst, const ISEParams& iseParams, const ISEInput& input, int numWeights)
+{
+	const int				numWeightBits	= computeNumRequiredBits(iseParams, numWeights);
+	BitAssignAccessStream	access			(dst, 127, numWeightBits, false);
+	encodeISE(access, iseParams, input, numWeights);
+}
+
+static void writeColorEndpointData (AssignBlock128& dst, const ISEParams& iseParams, const ISEInput& input, int numEndpoints, int numBitsForColorEndpoints, int colorEndpointDataStartNdx)
+{
+	BitAssignAccessStream access(dst, colorEndpointDataStartNdx, numBitsForColorEndpoints, true);
+	encodeISE(access, iseParams, input, numEndpoints);
+}
+
+static AssignBlock128 generateNormalBlock (const NormalBlockParams& blockParams, int blockWidth, int blockHeight, const NormalBlockISEInputs& iseInputs)
+{
+	DE_ASSERT(isValidBlockParams(blockParams, blockWidth, blockHeight));
+	DE_UNREF(blockWidth);	// \note For non-debug builds.
+	DE_UNREF(blockHeight);	// \note For non-debug builds.
+
+	AssignBlock128	block;
+	const int		numWeights		= computeNumWeights(blockParams);
+	const int		numWeightBits	= computeNumRequiredBits(blockParams.weightISEParams, numWeights);
+
+	writeBlockMode(block, blockParams);
+
+	block.setBits(11, 12, blockParams.numPartitions - 1);
+	if (blockParams.numPartitions > 1)
+		block.setBits(13, 22, blockParams.partitionSeed);
+
+	{
+		const int extraCemBitsStart = 127 - numWeightBits - (blockParams.numPartitions == 1 || blockParams.isMultiPartSingleCemMode		? -1
+															: blockParams.numPartitions == 4											? 7
+															: blockParams.numPartitions == 3											? 4
+															: blockParams.numPartitions == 2											? 1
+															: 0);
+
+		writeColorEndpointModes(block, &blockParams.colorEndpointModes[0], blockParams.isMultiPartSingleCemMode, blockParams.numPartitions, extraCemBitsStart);
+
+		if (blockParams.isDualPlane)
+			block.setBits(extraCemBitsStart-2, extraCemBitsStart-1, blockParams.ccs);
+	}
+
+	writeWeightData(block, blockParams.weightISEParams, iseInputs.weight, numWeights);
+
+	{
+		const int			numColorEndpointValues		= computeNumColorEndpointValues(&blockParams.colorEndpointModes[0], blockParams.numPartitions, blockParams.isMultiPartSingleCemMode);
+		const int			numBitsForColorEndpoints	= computeNumBitsForColorEndpoints(blockParams);
+		const int			colorEndpointDataStartNdx	= blockParams.numPartitions == 1 ? 17 : 29;
+		const ISEParams&	colorEndpointISEParams		= computeMaximumRangeISEParams(numBitsForColorEndpoints, numColorEndpointValues);
+
+		writeColorEndpointData(block, colorEndpointISEParams, iseInputs.endpoint, numColorEndpointValues, numBitsForColorEndpoints, colorEndpointDataStartNdx);
+	}
+
+	return block;
+}
+
+// Generate default ISE inputs for weight and endpoint data - gradient-ish values.
+static NormalBlockISEInputs generateDefaultISEInputs (const NormalBlockParams& blockParams)
+{
+	NormalBlockISEInputs result;
+
+	{
+		result.weight.isGivenInBlockForm = false;
+
+		const int numWeights		= computeNumWeights(blockParams);
+		const int weightRangeMax	= computeISERangeMax(blockParams.weightISEParams);
+
+		if (blockParams.isDualPlane)
+		{
+			for (int i = 0; i < numWeights; i += 2)
+				result.weight.value.plain[i] = (i*weightRangeMax + (numWeights-1)/2) / (numWeights-1);
+
+			for (int i = 1; i < numWeights; i += 2)
+				result.weight.value.plain[i] = weightRangeMax - (i*weightRangeMax + (numWeights-1)/2) / (numWeights-1);
+		}
+		else
+		{
+			for (int i = 0; i < numWeights; i++)
+				result.weight.value.plain[i] = (i*weightRangeMax + (numWeights-1)/2) / (numWeights-1);
+		}
+	}
+
+	{
+		result.endpoint.isGivenInBlockForm = false;
+
+		const int			numColorEndpointValues		= computeNumColorEndpointValues(&blockParams.colorEndpointModes[0], blockParams.numPartitions, blockParams.isMultiPartSingleCemMode);
+		const int			numBitsForColorEndpoints	= computeNumBitsForColorEndpoints(blockParams);
+		const ISEParams&	colorEndpointISEParams		= computeMaximumRangeISEParams(numBitsForColorEndpoints, numColorEndpointValues);
+		const int			colorEndpointRangeMax		= computeISERangeMax(colorEndpointISEParams);
+
+		for (int i = 0; i < numColorEndpointValues; i++)
+			result.endpoint.value.plain[i] = (i*colorEndpointRangeMax + (numColorEndpointValues-1)/2) / (numColorEndpointValues-1);
+	}
+
+	return result;
+}
+
+} // ASTCBlockGeneratorInternal
+
+static Vec4 getBlockTestTypeColorScale (ASTCBlockTestType testType)
+{
+	switch (testType)
+	{
+		case ASTCBLOCKTESTTYPE_VOID_EXTENT_HDR:				return Vec4(0.5f/65504.0f);
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_NO_15:	return Vec4(1.0f/65504.0f, 1.0f/65504.0f, 1.0f/65504.0f, 1.0f);
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_15:		return Vec4(1.0f/65504.0f);
+		default:											return Vec4(1.0f);
+	}
+}
+
+static Vec4 getBlockTestTypeColorBias (ASTCBlockTestType testType)
+{
+	switch (testType)
+	{
+		case ASTCBLOCKTESTTYPE_VOID_EXTENT_HDR:		return Vec4(0.5f);
+		default:									return Vec4(0.0f);
+	}
+}
+
+// Generate block data for a given ASTCBlockTestType and format.
+static void generateBlockCaseTestData (vector<deUint8>& dst, CompressedTexture::Format format, ASTCBlockTestType testType)
+{
+	using namespace ASTCBlockGeneratorInternal;
+
+	static const ISEParams weightISEParamsCandidates[] =
+	{
+		ISEParams(ISEMODE_PLAIN_BIT,	1),
+		ISEParams(ISEMODE_TRIT,			0),
+		ISEParams(ISEMODE_PLAIN_BIT,	2),
+		ISEParams(ISEMODE_QUINT,		0),
+		ISEParams(ISEMODE_TRIT,			1),
+		ISEParams(ISEMODE_PLAIN_BIT,	3),
+		ISEParams(ISEMODE_QUINT,		1),
+		ISEParams(ISEMODE_TRIT,			2),
+		ISEParams(ISEMODE_PLAIN_BIT,	4),
+		ISEParams(ISEMODE_QUINT,		2),
+		ISEParams(ISEMODE_TRIT,			3),
+		ISEParams(ISEMODE_PLAIN_BIT,	5)
+	};
+
+	DE_ASSERT(tcu::isASTCFormat(format));
+	DE_ASSERT(!(tcu::isASTCSRGBFormat(format) && isBlockTestTypeHDROnly(testType)));
+
+	const IVec3 blockSize = getASTCBlockSize(format);
+	DE_ASSERT(blockSize.z() == 1);
+
+	switch (testType)
+	{
+		case ASTCBLOCKTESTTYPE_VOID_EXTENT_LDR:
+		// Generate a gradient-like set of LDR void-extent blocks.
+		{
+			const int			numBlocks	= 1<<13;
+			const deUint32		numValues	= 1<<16;
+			dst.reserve(numBlocks*ASTC_BLOCK_SIZE_BYTES);
+
+			for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+			{
+				const deUint32 baseValue	= blockNdx*(numValues-1) / (numBlocks-1);
+				const deUint16 r			= (deUint16)((baseValue + numValues*0/4) % numValues);
+				const deUint16 g			= (deUint16)((baseValue + numValues*1/4) % numValues);
+				const deUint16 b			= (deUint16)((baseValue + numValues*2/4) % numValues);
+				const deUint16 a			= (deUint16)((baseValue + numValues*3/4) % numValues);
+				AssignBlock128 block;
+
+				generateVoidExtentBlock(VoidExtentParams(false, r, g, b, a)).pushBytesToVector(dst);
+			}
+
+			break;
+		}
+
+		case ASTCBLOCKTESTTYPE_VOID_EXTENT_HDR:
+		// Generate a gradient-like set of HDR void-extent blocks, with values ranging from the largest finite negative to largest finite positive of fp16.
+		{
+			const float		minValue	= -65504.0f;
+			const float		maxValue	= +65504.0f;
+			const int		numBlocks	= 1<<13;
+			dst.reserve(numBlocks*ASTC_BLOCK_SIZE_BYTES);
+
+			for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+			{
+				const int			rNdx	= (blockNdx + numBlocks*0/4) % numBlocks;
+				const int			gNdx	= (blockNdx + numBlocks*1/4) % numBlocks;
+				const int			bNdx	= (blockNdx + numBlocks*2/4) % numBlocks;
+				const int			aNdx	= (blockNdx + numBlocks*3/4) % numBlocks;
+				const deFloat16		r		= deFloat32To16(minValue + (float)rNdx * (maxValue - minValue) / (float)(numBlocks-1));
+				const deFloat16		g		= deFloat32To16(minValue + (float)gNdx * (maxValue - minValue) / (float)(numBlocks-1));
+				const deFloat16		b		= deFloat32To16(minValue + (float)bNdx * (maxValue - minValue) / (float)(numBlocks-1));
+				const deFloat16		a		= deFloat32To16(minValue + (float)aNdx * (maxValue - minValue) / (float)(numBlocks-1));
+
+				generateVoidExtentBlock(VoidExtentParams(true, r, g, b, a)).pushBytesToVector(dst);
+			}
+
+			break;
+		}
+
+		case ASTCBLOCKTESTTYPE_WEIGHT_GRID:
+		// Generate different combinations of plane count, weight ISE params, and grid size.
+		{
+			for (int isDualPlane = 0;		isDualPlane <= 1;												isDualPlane++)
+			for (int iseParamsNdx = 0;		iseParamsNdx < DE_LENGTH_OF_ARRAY(weightISEParamsCandidates);	iseParamsNdx++)
+			for (int weightGridWidth = 2;	weightGridWidth <= 12;											weightGridWidth++)
+			for (int weightGridHeight = 2;	weightGridHeight <= 12;											weightGridHeight++)
+			{
+				NormalBlockParams		blockParams;
+				NormalBlockISEInputs	iseInputs;
+
+				blockParams.weightGridWidth			= weightGridWidth;
+				blockParams.weightGridHeight		= weightGridHeight;
+				blockParams.isDualPlane				= isDualPlane != 0;
+				blockParams.weightISEParams			= weightISEParamsCandidates[iseParamsNdx];
+				blockParams.ccs						= 0;
+				blockParams.numPartitions			= 1;
+				blockParams.colorEndpointModes[0]	= 0;
+
+				if (isValidBlockParams(blockParams, blockSize.x(), blockSize.y()))
+					generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), generateDefaultISEInputs(blockParams)).pushBytesToVector(dst);
+			}
+
+			break;
+		}
+
+		case ASTCBLOCKTESTTYPE_WEIGHT_ISE:
+		// For each weight ISE param set, generate blocks that cover:
+		// - each single value of the ISE's range, at each position inside an ISE block
+		// - for trit and quint ISEs, each single T or Q value of an ISE block
+		{
+			for (int iseParamsNdx = 0;	iseParamsNdx < DE_LENGTH_OF_ARRAY(weightISEParamsCandidates);	iseParamsNdx++)
+			{
+				const ISEParams&	iseParams = weightISEParamsCandidates[iseParamsNdx];
+				NormalBlockParams	blockParams;
+
+				blockParams.weightGridWidth			= 4;
+				blockParams.weightGridHeight		= 4;
+				blockParams.weightISEParams			= iseParams;
+				blockParams.numPartitions			= 1;
+				blockParams.isDualPlane				= blockParams.weightGridWidth * blockParams.weightGridHeight < 24 ? true : false;
+				blockParams.ccs						= 0;
+				blockParams.colorEndpointModes[0]	= 0;
+
+				while (!isValidBlockParams(blockParams, blockSize.x(), blockSize.y()))
+				{
+					blockParams.weightGridWidth--;
+					blockParams.weightGridHeight--;
+				}
+
+				const int numValuesInISEBlock	= iseParams.mode == ISEMODE_TRIT ? 5 : iseParams.mode == ISEMODE_QUINT ? 3 : 1;
+				const int numWeights			= computeNumWeights(blockParams);
+
+				{
+					const int				numWeightValues		= (int)computeISERangeMax(iseParams) + 1;
+					const int				numBlocks			= divRoundUp(numWeightValues, numWeights);
+					NormalBlockISEInputs	iseInputs			= generateDefaultISEInputs(blockParams);
+					iseInputs.weight.isGivenInBlockForm = false;
+
+					for (int offset = 0;	offset < numValuesInISEBlock;	offset++)
+					for (int blockNdx = 0;	blockNdx < numBlocks;			blockNdx++)
+					{
+						for (int weightNdx = 0; weightNdx < numWeights; weightNdx++)
+							iseInputs.weight.value.plain[weightNdx] = (blockNdx*numWeights + weightNdx + offset) % numWeightValues;
+
+						generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), iseInputs).pushBytesToVector(dst);
+					}
+				}
+
+				if (iseParams.mode == ISEMODE_TRIT || iseParams.mode == ISEMODE_QUINT)
+				{
+					NormalBlockISEInputs iseInputs = generateDefaultISEInputs(blockParams);
+					iseInputs.weight.isGivenInBlockForm = true;
+
+					const int numTQValues			= 1 << (iseParams.mode == ISEMODE_TRIT ? 8 : 7);
+					const int numISEBlocksPerBlock	= divRoundUp(numWeights, numValuesInISEBlock);
+					const int numBlocks				= divRoundUp(numTQValues, numISEBlocksPerBlock);
+
+					for (int offset = 0;	offset < numValuesInISEBlock;	offset++)
+					for (int blockNdx = 0;	blockNdx < numBlocks;			blockNdx++)
+					{
+						for (int iseBlockNdx = 0; iseBlockNdx < numISEBlocksPerBlock; iseBlockNdx++)
+						{
+							for (int i = 0; i < numValuesInISEBlock; i++)
+								iseInputs.weight.value.block[iseBlockNdx].bitValues[i] = 0;
+							iseInputs.weight.value.block[iseBlockNdx].tOrQValue = (blockNdx*numISEBlocksPerBlock + iseBlockNdx + offset) % numTQValues;
+						}
+
+						generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), iseInputs).pushBytesToVector(dst);
+					}
+				}
+			}
+
+			break;
+		}
+
+		case ASTCBLOCKTESTTYPE_CEMS:
+		// For each plane count & partition count combination, generate all color endpoint mode combinations.
+		{
+			for (int isDualPlane = 0;		isDualPlane <= 1;								isDualPlane++)
+			for (int numPartitions = 1;		numPartitions <= (isDualPlane != 0 ? 3 : 4);	numPartitions++)
+			{
+				// Multi-partition, single-CEM mode.
+				if (numPartitions > 1)
+				{
+					for (deUint32 singleCem = 0; singleCem < 16; singleCem++)
+					{
+						NormalBlockParams blockParams;
+						blockParams.weightGridWidth				= 4;
+						blockParams.weightGridHeight			= 4;
+						blockParams.isDualPlane					= isDualPlane != 0;
+						blockParams.ccs							= 0;
+						blockParams.numPartitions				= numPartitions;
+						blockParams.isMultiPartSingleCemMode	= true;
+						blockParams.colorEndpointModes[0]		= singleCem;
+						blockParams.partitionSeed				= 634;
+
+						for (int iseParamsNdx = 0; iseParamsNdx < DE_LENGTH_OF_ARRAY(weightISEParamsCandidates); iseParamsNdx++)
+						{
+							blockParams.weightISEParams = weightISEParamsCandidates[iseParamsNdx];
+							if (isValidBlockParams(blockParams, blockSize.x(), blockSize.y()))
+							{
+								generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), generateDefaultISEInputs(blockParams)).pushBytesToVector(dst);
+								break;
+							}
+						}
+					}
+				}
+
+				// Separate-CEM mode.
+				for (deUint32 cem0 = 0; cem0 < 16; cem0++)
+				for (deUint32 cem1 = 0; cem1 < (numPartitions >= 2 ? 16u : 1u); cem1++)
+				for (deUint32 cem2 = 0; cem2 < (numPartitions >= 3 ? 16u : 1u); cem2++)
+				for (deUint32 cem3 = 0; cem3 < (numPartitions >= 4 ? 16u : 1u); cem3++)
+				{
+					NormalBlockParams blockParams;
+					blockParams.weightGridWidth				= 4;
+					blockParams.weightGridHeight			= 4;
+					blockParams.isDualPlane					= isDualPlane != 0;
+					blockParams.ccs							= 0;
+					blockParams.numPartitions				= numPartitions;
+					blockParams.isMultiPartSingleCemMode	= false;
+					blockParams.colorEndpointModes[0]		= cem0;
+					blockParams.colorEndpointModes[1]		= cem1;
+					blockParams.colorEndpointModes[2]		= cem2;
+					blockParams.colorEndpointModes[3]		= cem3;
+					blockParams.partitionSeed				= 634;
+
+					{
+						const deUint32 minCem		= *std::min_element(&blockParams.colorEndpointModes[0], &blockParams.colorEndpointModes[numPartitions]);
+						const deUint32 maxCem		= *std::max_element(&blockParams.colorEndpointModes[0], &blockParams.colorEndpointModes[numPartitions]);
+						const deUint32 minCemClass	= minCem/4;
+						const deUint32 maxCemClass	= maxCem/4;
+
+						if (maxCemClass - minCemClass > 1)
+							continue;
+					}
+
+					for (int iseParamsNdx = 0; iseParamsNdx < DE_LENGTH_OF_ARRAY(weightISEParamsCandidates); iseParamsNdx++)
+					{
+						blockParams.weightISEParams = weightISEParamsCandidates[iseParamsNdx];
+						if (isValidBlockParams(blockParams, blockSize.x(), blockSize.y()))
+						{
+							generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), generateDefaultISEInputs(blockParams)).pushBytesToVector(dst);
+							break;
+						}
+					}
+				}
+			}
+
+			break;
+		}
+
+		case ASTCBLOCKTESTTYPE_PARTITION_SEED:
+		// Test all partition seeds ("partition pattern indices").
+		{
+			for (int		numPartitions = 2;	numPartitions <= 4;		numPartitions++)
+			for (deUint32	partitionSeed = 0;	partitionSeed < 1<<10;	partitionSeed++)
+			{
+				NormalBlockParams blockParams;
+				blockParams.weightGridWidth				= 4;
+				blockParams.weightGridHeight			= 4;
+				blockParams.weightISEParams				= ISEParams(ISEMODE_PLAIN_BIT, 2);
+				blockParams.isDualPlane					= false;
+				blockParams.numPartitions				= numPartitions;
+				blockParams.isMultiPartSingleCemMode	= true;
+				blockParams.colorEndpointModes[0]		= 0;
+				blockParams.partitionSeed				= partitionSeed;
+
+				generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), generateDefaultISEInputs(blockParams)).pushBytesToVector(dst);
+			}
+
+			break;
+		}
+
+		// \note Fall-through.
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_LDR:
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_NO_15:
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_15:
+		// For each endpoint mode, for each pair of components in the endpoint value, test 10x10 combinations of values for that pair.
+		// \note Separate modes for HDR and mode 15 due to different color scales and biases.
+		{
+			for (deUint32 cem = 0; cem < 16; cem++)
+			{
+				const bool isHDRCem = cem == 2		||
+									  cem == 3		||
+									  cem == 7		||
+									  cem == 11		||
+									  cem == 14		||
+									  cem == 15;
+
+				if ((testType == ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_LDR			&& isHDRCem)					||
+					(testType == ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_NO_15		&& (!isHDRCem || cem == 15))	||
+					(testType == ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_15		&& cem != 15))
+					continue;
+
+				NormalBlockParams blockParams;
+				blockParams.weightGridWidth			= 3;
+				blockParams.weightGridHeight		= 4;
+				blockParams.weightISEParams			= ISEParams(ISEMODE_PLAIN_BIT, 2);
+				blockParams.isDualPlane				= false;
+				blockParams.numPartitions			= 1;
+				blockParams.colorEndpointModes[0]	= cem;
+
+				{
+					const int			numBitsForEndpoints		= computeNumBitsForColorEndpoints(blockParams);
+					const int			numEndpointParts		= computeNumColorEndpointValues(cem);
+					const ISEParams		endpointISE				= computeMaximumRangeISEParams(numBitsForEndpoints, numEndpointParts);
+					const int			endpointISERangeMax		= computeISERangeMax(endpointISE);
+
+					for (int endpointPartNdx0 = 0;						endpointPartNdx0 < numEndpointParts; endpointPartNdx0++)
+					for (int endpointPartNdx1 = endpointPartNdx0+1;		endpointPartNdx1 < numEndpointParts; endpointPartNdx1++)
+					{
+						NormalBlockISEInputs	iseInputs			= generateDefaultISEInputs(blockParams);
+						const int				numEndpointValues	= de::min(10, endpointISERangeMax+1);
+
+						for (int endpointValueNdx0 = 0; endpointValueNdx0 < numEndpointValues; endpointValueNdx0++)
+						for (int endpointValueNdx1 = 0; endpointValueNdx1 < numEndpointValues; endpointValueNdx1++)
+						{
+							const int endpointValue0 = endpointValueNdx0 * endpointISERangeMax / (numEndpointValues-1);
+							const int endpointValue1 = endpointValueNdx1 * endpointISERangeMax / (numEndpointValues-1);
+
+							iseInputs.endpoint.value.plain[endpointPartNdx0] = endpointValue0;
+							iseInputs.endpoint.value.plain[endpointPartNdx1] = endpointValue1;
+
+							generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), iseInputs).pushBytesToVector(dst);
+						}
+					}
+				}
+			}
+
+			break;
+		}
+
+		case ASTCBLOCKTESTTYPE_ENDPOINT_ISE:
+		// Similar to ASTCBLOCKTESTTYPE_WEIGHT_ISE, see above.
+		{
+			static const deUint32 endpointRangeMaximums[] = { 5, 9, 11, 19, 23, 39, 47, 79, 95, 159, 191 };
+
+			for (int endpointRangeNdx = 0; endpointRangeNdx < DE_LENGTH_OF_ARRAY(endpointRangeMaximums); endpointRangeNdx++)
+			{
+				bool validCaseGenerated = false;
+
+				for (int numPartitions = 1;			!validCaseGenerated && numPartitions <= 4;													numPartitions++)
+				for (int isDual = 0;				!validCaseGenerated && isDual <= 1;															isDual++)
+				for (int weightISEParamsNdx = 0;	!validCaseGenerated && weightISEParamsNdx < DE_LENGTH_OF_ARRAY(weightISEParamsCandidates);	weightISEParamsNdx++)
+				for (int weightGridWidth = 2;		!validCaseGenerated && weightGridWidth <= 12;												weightGridWidth++)
+				for (int weightGridHeight = 2;		!validCaseGenerated && weightGridHeight <= 12;												weightGridHeight++)
+				{
+					NormalBlockParams blockParams;
+					blockParams.weightGridWidth				= weightGridWidth;
+					blockParams.weightGridHeight			= weightGridHeight;
+					blockParams.weightISEParams				= weightISEParamsCandidates[weightISEParamsNdx];
+					blockParams.isDualPlane					= isDual != 0;
+					blockParams.ccs							= 0;
+					blockParams.numPartitions				= numPartitions;
+					blockParams.isMultiPartSingleCemMode	= true;
+					blockParams.colorEndpointModes[0]		= 12;
+					blockParams.partitionSeed				= 634;
+
+					if (isValidBlockParams(blockParams, blockSize.x(), blockSize.y()))
+					{
+						const ISEParams endpointISEParams = computeMaximumRangeISEParams(computeNumBitsForColorEndpoints(blockParams),
+																						 computeNumColorEndpointValues(&blockParams.colorEndpointModes[0], numPartitions, true));
+
+						if (computeISERangeMax(endpointISEParams) == endpointRangeMaximums[endpointRangeNdx])
+						{
+							validCaseGenerated = true;
+
+							const int numColorEndpoints		= computeNumColorEndpointValues(&blockParams.colorEndpointModes[0], numPartitions, blockParams.isMultiPartSingleCemMode);
+							const int numValuesInISEBlock	= endpointISEParams.mode == ISEMODE_TRIT ? 5 : endpointISEParams.mode == ISEMODE_QUINT ? 3 : 1;
+
+							{
+								const int				numColorEndpointValues	= (int)computeISERangeMax(endpointISEParams) + 1;
+								const int				numBlocks				= divRoundUp(numColorEndpointValues, numColorEndpoints);
+								NormalBlockISEInputs	iseInputs				= generateDefaultISEInputs(blockParams);
+								iseInputs.endpoint.isGivenInBlockForm = false;
+
+								for (int offset = 0;	offset < numValuesInISEBlock;	offset++)
+								for (int blockNdx = 0;	blockNdx < numBlocks;			blockNdx++)
+								{
+									for (int endpointNdx = 0; endpointNdx < numColorEndpoints; endpointNdx++)
+										iseInputs.endpoint.value.plain[endpointNdx] = (blockNdx*numColorEndpoints + endpointNdx + offset) % numColorEndpointValues;
+
+									generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), iseInputs).pushBytesToVector(dst);
+								}
+							}
+
+							if (endpointISEParams.mode == ISEMODE_TRIT || endpointISEParams.mode == ISEMODE_QUINT)
+							{
+								NormalBlockISEInputs iseInputs = generateDefaultISEInputs(blockParams);
+								iseInputs.endpoint.isGivenInBlockForm = true;
+
+								const int numTQValues			= 1 << (endpointISEParams.mode == ISEMODE_TRIT ? 8 : 7);
+								const int numISEBlocksPerBlock	= divRoundUp(numColorEndpoints, numValuesInISEBlock);
+								const int numBlocks				= divRoundUp(numTQValues, numISEBlocksPerBlock);
+
+								for (int offset = 0;	offset < numValuesInISEBlock;	offset++)
+								for (int blockNdx = 0;	blockNdx < numBlocks;			blockNdx++)
+								{
+									for (int iseBlockNdx = 0; iseBlockNdx < numISEBlocksPerBlock; iseBlockNdx++)
+									{
+										for (int i = 0; i < numValuesInISEBlock; i++)
+											iseInputs.endpoint.value.block[iseBlockNdx].bitValues[i] = 0;
+										iseInputs.endpoint.value.block[iseBlockNdx].tOrQValue = (blockNdx*numISEBlocksPerBlock + iseBlockNdx + offset) % numTQValues;
+									}
+
+									generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), iseInputs).pushBytesToVector(dst);
+								}
+							}
+						}
+					}
+				}
+
+				DE_ASSERT(validCaseGenerated);
+			}
+
+			break;
+		}
+
+		case ASTCBLOCKTESTTYPE_CCS:
+		// For all partition counts, test all values of the CCS (color component selector).
+		{
+			for (int		numPartitions = 1;		numPartitions <= 3;		numPartitions++)
+			for (deUint32	ccs = 0;				ccs < 4;				ccs++)
+			{
+				NormalBlockParams blockParams;
+				blockParams.weightGridWidth				= 3;
+				blockParams.weightGridHeight			= 3;
+				blockParams.weightISEParams				= ISEParams(ISEMODE_PLAIN_BIT, 2);
+				blockParams.isDualPlane					= true;
+				blockParams.ccs							= ccs;
+				blockParams.numPartitions				= numPartitions;
+				blockParams.isMultiPartSingleCemMode	= true;
+				blockParams.colorEndpointModes[0]		= 8;
+				blockParams.partitionSeed				= 634;
+
+				generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), generateDefaultISEInputs(blockParams)).pushBytesToVector(dst);
+			}
+
+			break;
+		}
+
+		case ASTCBLOCKTESTTYPE_RANDOM:
+		// Generate a number of random (but valid) blocks.
+		{
+			const int		numBlocks			= 16384;
+			de::Random		rnd					(1);
+			int				numBlocksGenerated	= 0;
+
+			dst.reserve(numBlocks*ASTC_BLOCK_SIZE_BYTES);
+
+			for (numBlocksGenerated = 0; numBlocksGenerated < numBlocks; numBlocksGenerated++)
+			{
+				if (rnd.getFloat() < 0.1f)
+				{
+					// Void extent block.
+					const bool		isVoidExtentHDR		= rnd.getBool();
+					const deUint16	r					= isVoidExtentHDR ? deFloat32To16(rnd.getFloat(0.0f, 1.0f)) : rnd.getInt(0, 0xffff);
+					const deUint16	g					= isVoidExtentHDR ? deFloat32To16(rnd.getFloat(0.0f, 1.0f)) : rnd.getInt(0, 0xffff);
+					const deUint16	b					= isVoidExtentHDR ? deFloat32To16(rnd.getFloat(0.0f, 1.0f)) : rnd.getInt(0, 0xffff);
+					const deUint16	a					= isVoidExtentHDR ? deFloat32To16(rnd.getFloat(0.0f, 1.0f)) : rnd.getInt(0, 0xffff);
+					generateVoidExtentBlock(VoidExtentParams(isVoidExtentHDR, r, g, b, a)).pushBytesToVector(dst);
+				}
+				else
+				{
+					// Not void extent block.
+
+					// Generate block params.
+
+					NormalBlockParams blockParams;
+
+					do
+					{
+						blockParams.weightGridWidth				= rnd.getInt(2, blockSize.x());
+						blockParams.weightGridHeight			= rnd.getInt(2, blockSize.y());
+						blockParams.weightISEParams				= weightISEParamsCandidates[rnd.getInt(0, DE_LENGTH_OF_ARRAY(weightISEParamsCandidates)-1)];
+						blockParams.numPartitions				= rnd.getInt(1, 4);
+						blockParams.isMultiPartSingleCemMode	= rnd.getFloat() < 0.25f;
+						blockParams.isDualPlane					= blockParams.numPartitions != 4 && rnd.getBool();
+						blockParams.ccs							= rnd.getInt(0, 3);
+						blockParams.partitionSeed				= rnd.getInt(0, 1023);
+
+						blockParams.colorEndpointModes[0] = rnd.getInt(0, 15);
+
+						{
+							const int cemDiff = blockParams.isMultiPartSingleCemMode		? 0
+												: blockParams.colorEndpointModes[0] == 0	? 1
+												: blockParams.colorEndpointModes[0] == 15	? -1
+												: rnd.getBool()								? 1 : -1;
+
+							for (int i = 1; i < blockParams.numPartitions; i++)
+								blockParams.colorEndpointModes[i] = blockParams.colorEndpointModes[0] + (cemDiff == -1 ? rnd.getInt(-1, 0) : cemDiff == 1 ? rnd.getInt(0, 1) : 0);
+						}
+					} while (!isValidBlockParams(blockParams, blockSize.x(), blockSize.y()));
+
+					// Generate ISE inputs for both weight and endpoint data.
+
+					NormalBlockISEInputs iseInputs;
+
+					for (int weightOrEndpoints = 0; weightOrEndpoints <= 1; weightOrEndpoints++)
+					{
+						const bool			setWeights	= weightOrEndpoints == 0;
+						const int			numValues	= setWeights ? computeNumWeights(blockParams) :
+														  computeNumColorEndpointValues(&blockParams.colorEndpointModes[0], blockParams.numPartitions, blockParams.isMultiPartSingleCemMode);
+						const ISEParams		iseParams	= setWeights ? blockParams.weightISEParams : computeMaximumRangeISEParams(computeNumBitsForColorEndpoints(blockParams), numValues);
+						ISEInput&			iseInput	= setWeights ? iseInputs.weight : iseInputs.endpoint;
+
+						iseInput.isGivenInBlockForm = rnd.getBool();
+
+						if (iseInput.isGivenInBlockForm)
+						{
+							const int numValuesPerISEBlock	= iseParams.mode == ISEMODE_TRIT	? 5
+															: iseParams.mode == ISEMODE_QUINT	? 3
+															:									  1;
+							const int iseBitMax				= (1 << iseParams.numBits) - 1;
+							const int numISEBlocks			= divRoundUp(numValues, numValuesPerISEBlock);
+
+							for (int iseBlockNdx = 0; iseBlockNdx < numISEBlocks; iseBlockNdx++)
+							{
+								iseInput.value.block[iseBlockNdx].tOrQValue = rnd.getInt(0, 255);
+								for (int i = 0; i < numValuesPerISEBlock; i++)
+									iseInput.value.block[iseBlockNdx].bitValues[i] = rnd.getInt(0, iseBitMax);
+							}
+						}
+						else
+						{
+							const int rangeMax = computeISERangeMax(iseParams);
+
+							for (int valueNdx = 0; valueNdx < numValues; valueNdx++)
+								iseInput.value.plain[valueNdx] = rnd.getInt(0, rangeMax);
+						}
+					}
+
+					generateNormalBlock(blockParams, blockSize.x(), blockSize.y(), iseInputs).pushBytesToVector(dst);
+				}
+			}
+
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+// Get a string describing the data of an ASTC block. Currently contains just hex and bin dumps of the block.
+static string astcBlockDataStr (const deUint8* data)
+{
+	string result;
+	result += "  Hexadecimal (big endian: upper left hex digit is block bits 127 to 124):";
+
+	{
+		static const char* const hexDigits = "0123456789ABCDEF";
+
+		for (int i = ASTC_BLOCK_SIZE_BYTES-1; i >= 0; i--)
+		{
+			if ((i+1) % 2 == 0)
+				result += "\n    ";
+			else
+				result += "  ";
+
+			result += hexDigits[(data[i] & 0xf0) >> 4];
+			result += " ";
+			result += hexDigits[(data[i] & 0x0f) >> 0];
+		}
+	}
+
+	result += "\n\n  Binary (big endian: upper left bit is block bit 127):";
+
+	for (int i = ASTC_BLOCK_SIZE_BYTES-1; i >= 0; i--)
+	{
+		if ((i+1) % 2 == 0)
+			result += "\n    ";
+		else
+			result += "  ";
+
+		for (int j = 8-1; j >= 0; j--)
+		{
+			if (j == 3)
+				result += " ";
+
+			result += (data[i] >> j) & 1 ? "1" : "0";
+		}
+	}
+
+	result += "\n";
+
+	return result;
+}
+
+// Compare reference and result block images, reporting also the position of the first non-matching block.
+static bool compareBlockImages (const Surface&		reference,
+								const Surface&		result,
+								const tcu::RGBA&	thresholdRGBA,
+								const IVec2&		blockSize,
+								int					numNonDummyBlocks,
+								IVec2&				firstFailedBlockCoordDst,
+								Surface&			errorMaskDst,
+								IVec4&				maxDiffDst)
+{
+	TCU_CHECK_INTERNAL(reference.getWidth() == result.getWidth() && reference.getHeight() == result.getHeight());
+
+	const int		width		= result.getWidth();
+	const int		height		= result.getHeight();
+	const IVec4		threshold	= thresholdRGBA.toIVec();
+	const int		numXBlocks	= width / blockSize.x();
+
+	DE_ASSERT(width % blockSize.x() == 0 && height % blockSize.y() == 0);
+
+	errorMaskDst.setSize(width, height);
+
+	firstFailedBlockCoordDst	= IVec2(-1, -1);
+	maxDiffDst					= IVec4(0);
+
+	for (int y = 0; y < height; y++)
+	for (int x = 0; x < width; x++)
+	{
+		const IVec2 blockCoord = IVec2(x, y) / blockSize;
+
+		if (blockCoord.y()*numXBlocks + blockCoord.x() < numNonDummyBlocks)
+		{
+			const IVec4 refPix = reference.getPixel(x, y).toIVec();
+
+			if (refPix == IVec4(255, 0, 255, 255))
+			{
+				// ASTC error color - allow anything in result.
+				errorMaskDst.setPixel(x, y, tcu::RGBA(255, 0, 255, 255));
+				continue;
+			}
+
+			const IVec4		resPix		= result.getPixel(x, y).toIVec();
+			const IVec4		diff		= tcu::abs(refPix - resPix);
+			const bool		isOk		= tcu::boolAll(tcu::lessThanEqual(diff, threshold));
+
+			maxDiffDst = tcu::max(maxDiffDst, diff);
+
+			errorMaskDst.setPixel(x, y, isOk ? tcu::RGBA::green : tcu::RGBA::red);
+
+			if (!isOk && firstFailedBlockCoordDst.x() == -1)
+				firstFailedBlockCoordDst = blockCoord;
+		}
+	}
+
+	return boolAll(lessThanEqual(maxDiffDst, threshold));
+}
+
+enum ASTCSupportLevel
+{
+	// \note Ordered from smallest subset to full, for convenient comparison.
+	ASTCSUPPORTLEVEL_NONE = 0,
+	ASTCSUPPORTLEVEL_LDR,
+	ASTCSUPPORTLEVEL_HDR,
+	ASTCSUPPORTLEVEL_FULL
+};
+
+static inline ASTCSupportLevel getASTCSupportLevel (const glu::ContextInfo& contextInfo)
+{
+	const vector<string>& extensions = contextInfo.getExtensions();
+
+	ASTCSupportLevel maxLevel = ASTCSUPPORTLEVEL_NONE;
+
+	for (int extNdx = 0; extNdx < (int)extensions.size(); extNdx++)
+	{
+		const string& ext = extensions[extNdx];
+
+		maxLevel = de::max(maxLevel, ext == "GL_KHR_texture_compression_astc_ldr"	? ASTCSUPPORTLEVEL_LDR
+								   : ext == "GL_KHR_texture_compression_astc_hdr"	? ASTCSUPPORTLEVEL_HDR
+								   : ext == "GL_OES_texture_compression_astc"		? ASTCSUPPORTLEVEL_FULL
+								   : ASTCSUPPORTLEVEL_NONE);
+	}
+
+	return maxLevel;
+}
+
+// Class handling the common rendering stuff of ASTC cases.
+class ASTCRenderer2D
+{
+public:
+										ASTCRenderer2D		(Context&					context,
+															 CompressedTexture::Format	format,
+															 deUint32					randomSeed);
+
+										~ASTCRenderer2D		(void);
+
+	void								initialize			(int minRenderWidth, int minRenderHeight, const Vec4& colorScale, const Vec4& colorBias);
+	void								clear				(void);
+
+	void								render				(Surface&					referenceDst,
+															 Surface&					resultDst,
+															 const glu::Texture2D&		texture,
+															 const tcu::TextureFormat&	uncompressedFormat);
+
+	CompressedTexture::Format			getFormat			(void) const { return m_format; }
+	IVec2								getBlockSize		(void) const { return m_blockSize; }
+	ASTCSupportLevel					getASTCSupport		(void) const { DE_ASSERT(m_initialized); return m_astcSupport;	}
+
+private:
+	Context&							m_context;
+	TextureRenderer						m_renderer;
+
+	const CompressedTexture::Format		m_format;
+	const IVec2							m_blockSize;
+	ASTCSupportLevel					m_astcSupport;
+	Vec4								m_colorScale;
+	Vec4								m_colorBias;
+
+	de::Random							m_rnd;
+
+	bool								m_initialized;
+};
+
+} // ASTCDecompressionCaseInternal
+
+using namespace ASTCDecompressionCaseInternal;
+
+ASTCRenderer2D::ASTCRenderer2D (Context&					context,
+								CompressedTexture::Format	format,
+								deUint32					randomSeed)
+	: m_context			(context)
+	, m_renderer		(context.getRenderContext(), context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_format			(format)
+	, m_blockSize		(tcu::getASTCBlockSize(format).xy())
+	, m_astcSupport		(ASTCSUPPORTLEVEL_NONE)
+	, m_colorScale		(-1.0f)
+	, m_colorBias		(-1.0f)
+	, m_rnd				(randomSeed)
+	, m_initialized		(false)
+{
+	DE_ASSERT(tcu::getASTCBlockSize(format).z() == 1);
+}
+
+ASTCRenderer2D::~ASTCRenderer2D (void)
+{
+	clear();
+}
+
+void ASTCRenderer2D::initialize (int minRenderWidth, int minRenderHeight, const Vec4& colorScale, const Vec4& colorBias)
+{
+	DE_ASSERT(!m_initialized);
+
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderTarget();
+	TestLog&					log				= m_context.getTestContext().getLog();
+
+	m_astcSupport	= getASTCSupportLevel(m_context.getContextInfo());
+	m_colorScale	= colorScale;
+	m_colorBias		= colorBias;
+
+	switch (m_astcSupport)
+	{
+		case ASTCSUPPORTLEVEL_NONE:		log << TestLog::Message << "No ASTC support detected" << TestLog::EndMessage;		throw tcu::NotSupportedError("ASTC not supported");
+		case ASTCSUPPORTLEVEL_LDR:		log << TestLog::Message << "LDR ASTC support detected" << TestLog::EndMessage;		break;
+		case ASTCSUPPORTLEVEL_HDR:		log << TestLog::Message << "HDR ASTC support detected" << TestLog::EndMessage;		break;
+		case ASTCSUPPORTLEVEL_FULL:		log << TestLog::Message << "Full ASTC support detected" << TestLog::EndMessage;		break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	if (renderTarget.getWidth() < minRenderWidth || renderTarget.getHeight() < minRenderHeight)
+		throw tcu::NotSupportedError("Render target must be at least " + de::toString(minRenderWidth) + "x" + de::toString(minRenderHeight));
+
+	log << TestLog::Message << "Using color scale and bias: result = raw * " << colorScale << " + " << colorBias << TestLog::EndMessage;
+
+	m_initialized = true;
+}
+
+void ASTCRenderer2D::clear (void)
+{
+	m_renderer.clear();
+}
+
+void ASTCRenderer2D::render (Surface& referenceDst, Surface& resultDst, const glu::Texture2D& texture, const tcu::TextureFormat& uncompressedFormat)
+{
+	DE_ASSERT(m_initialized);
+
+	const glw::Functions&			gl						= m_context.getRenderContext().getFunctions();
+	const glu::RenderContext&		renderCtx				= m_context.getRenderContext();
+	const int						textureWidth			= texture.getRefTexture().getWidth();
+	const int						textureHeight			= texture.getRefTexture().getHeight();
+	const RandomViewport			viewport				(renderCtx.getRenderTarget(), textureWidth, textureHeight, m_rnd.getUint32());
+	ReferenceParams					renderParams			(gls::TextureTestUtil::TEXTURETYPE_2D);
+	vector<float>					texCoord;
+	gls::TextureTestUtil::computeQuadTexCoord2D(texCoord, Vec2(0.0f, 0.0f), Vec2(1.0f, 1.0f));
+
+	renderParams.samplerType	= gls::TextureTestUtil::getSamplerType(uncompressedFormat);
+	renderParams.sampler		= Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	renderParams.colorScale		= m_colorScale;
+	renderParams.colorBias		= m_colorBias;
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, texture.getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Issue GL draws.
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	gl.flush();
+
+	// Compute reference.
+	sampleTexture(gls::TextureTestUtil::SurfaceAccess(referenceDst, renderCtx.getRenderTarget().getPixelFormat()), texture.getRefTexture(), &texCoord[0], renderParams);
+
+	// Read GL-rendered image.
+	glu::readPixels(renderCtx, viewport.x, viewport.y, resultDst.getAccess());
+}
+
+ASTCBlockCase2D::ASTCBlockCase2D (Context&						context,
+								  const char*					name,
+								  const char*					description,
+								  ASTCBlockTestType				testType,
+								  CompressedTexture::Format		format)
+	: TestCase				(context, name, description)
+	, m_testType			(testType)
+	, m_format				(format)
+	, m_numBlocksTested		(0)
+	, m_currentIteration	(0)
+	, m_renderer			(new ASTCRenderer2D(context, format, deStringHash(getName())))
+{
+	DE_ASSERT(!(tcu::isASTCSRGBFormat(m_format) && isBlockTestTypeHDROnly(m_testType))); // \note There is no HDR sRGB mode, so these would be redundant.
+}
+
+ASTCBlockCase2D::~ASTCBlockCase2D (void)
+{
+	ASTCBlockCase2D::deinit();
+}
+
+void ASTCBlockCase2D::init (void)
+{
+	m_renderer->initialize(64, 64, getBlockTestTypeColorScale(m_testType), getBlockTestTypeColorBias(m_testType));
+
+	generateBlockCaseTestData(m_blockData, m_format, m_testType);
+	DE_ASSERT(!m_blockData.empty());
+	DE_ASSERT(m_blockData.size() % ASTC_BLOCK_SIZE_BYTES == 0);
+
+	m_testCtx.getLog() << TestLog::Message << "Total " << m_blockData.size() / ASTC_BLOCK_SIZE_BYTES << " blocks to test" << TestLog::EndMessage
+					   << TestLog::Message << "Note: Legitimate ASTC error pixels will be ignored when comparing to reference" << TestLog::EndMessage;
+}
+
+void ASTCBlockCase2D::deinit (void)
+{
+	m_renderer->clear();
+	m_blockData.clear();
+}
+
+ASTCBlockCase2D::IterateResult ASTCBlockCase2D::iterate (void)
+{
+	TestLog&						log						= m_testCtx.getLog();
+
+	if (m_renderer->getASTCSupport() == ASTCSUPPORTLEVEL_LDR && isBlockTestTypeHDROnly(m_testType))
+	{
+		log << TestLog::Message << "Passing the case immediately, since only LDR support was detected and test only contains HDR blocks" << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+	const IVec2						blockSize				= m_renderer->getBlockSize();
+	const int						totalNumBlocks			= (int)m_blockData.size() / ASTC_BLOCK_SIZE_BYTES;
+	const int						numXBlocksPerImage		= de::min(m_context.getRenderTarget().getWidth(),  512) / blockSize.x();
+	const int						numYBlocksPerImage		= de::min(m_context.getRenderTarget().getHeight(), 512) / blockSize.y();
+	const int						numBlocksPerImage		= numXBlocksPerImage * numYBlocksPerImage;
+	const int						imageWidth				= numXBlocksPerImage * blockSize.x();
+	const int						imageHeight				= numYBlocksPerImage * blockSize.y();
+	const int						numBlocksRemaining		= totalNumBlocks - m_numBlocksTested;
+	const int						curNumNonDummyBlocks	= de::min(numBlocksPerImage, numBlocksRemaining);
+	const int						curNumDummyBlocks		= numBlocksPerImage - curNumNonDummyBlocks;
+	const glu::RenderContext&		renderCtx				= m_context.getRenderContext();
+	const tcu::RGBA					threshold				= renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + (tcu::isASTCSRGBFormat(m_format) ? tcu::RGBA(2,2,2,2) : tcu::RGBA(1,1,1,1));
+	tcu::CompressedTexture			compressed				(m_format, imageWidth, imageHeight);
+
+	if (m_currentIteration == 0)
+	{
+		log << TestLog::Message << "Using texture of size "
+								<< imageWidth << "x" << imageHeight
+								<< ", with " << numXBlocksPerImage << " block columns and " << numYBlocksPerImage << " block rows "
+								<< ", with block size " << blockSize.x() << "x" << blockSize.y()
+			<< TestLog::EndMessage;
+	}
+
+	DE_ASSERT(compressed.getDataSize() == numBlocksPerImage*ASTC_BLOCK_SIZE_BYTES);
+	deMemcpy(compressed.getData(), &m_blockData[m_numBlocksTested*ASTC_BLOCK_SIZE_BYTES], curNumNonDummyBlocks*ASTC_BLOCK_SIZE_BYTES);
+	if (curNumDummyBlocks > 1)
+		generateDummyBlocks((deUint8*)compressed.getData() + curNumNonDummyBlocks*ASTC_BLOCK_SIZE_BYTES, curNumDummyBlocks);
+
+	// Create texture and render.
+
+	glu::Texture2D	texture			(renderCtx, m_context.getContextInfo(), 1, &compressed, tcu::CompressedTexture::DecompressionParams(m_renderer->getASTCSupport() == ASTCSUPPORTLEVEL_LDR));
+	Surface			renderedFrame	(imageWidth, imageHeight);
+	Surface			referenceFrame	(imageWidth, imageHeight);
+
+	m_renderer->render(referenceFrame, renderedFrame, texture, compressed.getUncompressedFormat());
+
+	// Compare and log.
+	// \note Since a case can draw quite many images, only log the first iteration and failures.
+
+	{
+		Surface		errorMask;
+		IVec2		firstFailedBlockCoord;
+		IVec4		maxDiff;
+		const bool	compareOk = compareBlockImages(referenceFrame, renderedFrame, threshold, blockSize, curNumNonDummyBlocks, firstFailedBlockCoord, errorMask, maxDiff);
+
+		if (m_currentIteration == 0 || !compareOk)
+		{
+			const char* const		imageSetName	= "ComparisonResult";
+			const char* const		imageSetDesc	= "Comparison Result";
+
+			{
+				tcu::ScopedLogSection section(log, "Iteration " + de::toString(m_currentIteration),
+													"Blocks " + de::toString(m_numBlocksTested) + " to " + de::toString(m_numBlocksTested + curNumNonDummyBlocks - 1));
+
+				if (curNumDummyBlocks > 0)
+					log << TestLog::Message << "Note: Only the first " << curNumNonDummyBlocks << " blocks in the image are relevant; rest " << curNumDummyBlocks << " are dummies and not checked" << TestLog::EndMessage;
+
+				if (!compareOk)
+				{
+					log << TestLog::Message << "Image comparison failed: max difference = " << maxDiff << ", threshold = " << threshold << TestLog::EndMessage
+						<< TestLog::ImageSet(imageSetName, imageSetDesc)
+						<< TestLog::Image("Result",		"Result",		renderedFrame)
+						<< TestLog::Image("Reference",	"Reference",	referenceFrame)
+						<< TestLog::Image("ErrorMask",	"Error mask",	errorMask)
+						<< TestLog::EndImageSet;
+
+					const int blockNdx = m_numBlocksTested + firstFailedBlockCoord.y()*numXBlocksPerImage + firstFailedBlockCoord.x();
+					DE_ASSERT(blockNdx < totalNumBlocks);
+
+					log << TestLog::Message << "First failed block at column " << firstFailedBlockCoord.x() << " and row " << firstFailedBlockCoord.y() << TestLog::EndMessage
+						<< TestLog::Message << "Data of first failed block:\n" << astcBlockDataStr(&m_blockData[blockNdx*ASTC_BLOCK_SIZE_BYTES]) << TestLog::EndMessage;
+
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+					return STOP;
+				}
+				else
+				{
+					log << TestLog::ImageSet(imageSetName, imageSetDesc)
+						<< TestLog::Image("Result", "Result", renderedFrame)
+						<< TestLog::EndImageSet;
+				}
+			}
+
+			if (m_numBlocksTested + curNumNonDummyBlocks < totalNumBlocks)
+				log << TestLog::Message << "Note: not logging further images unless reference comparison fails" << TestLog::EndMessage;
+		}
+	}
+
+	m_currentIteration++;
+	m_numBlocksTested += curNumNonDummyBlocks;
+
+	if (m_numBlocksTested >= totalNumBlocks)
+	{
+		DE_ASSERT(m_numBlocksTested == totalNumBlocks);
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+	return CONTINUE;
+}
+
+// Generate a number of trivial dummy blocks to fill unneeded space in a texture.
+void ASTCBlockCase2D::generateDummyBlocks (deUint8* dst, int num)
+{
+	using namespace ASTCBlockGeneratorInternal;
+
+	AssignBlock128 block = generateVoidExtentBlock(VoidExtentParams(false, 0, 0, 0, 0));
+	for (int i = 0; i < num; i++)
+		block.assignToMemory(&dst[i * ASTC_BLOCK_SIZE_BYTES]);
+}
+
+ASTCBlockSizeRemainderCase2D::ASTCBlockSizeRemainderCase2D (Context&					context,
+															const char*					name,
+															const char*					description,
+															CompressedTexture::Format	format)
+	: TestCase				(context, name, description)
+	, m_format				(format)
+	, m_currentIteration	(0)
+	, m_renderer			(new ASTCRenderer2D(context, format, deStringHash(getName())))
+{
+}
+
+ASTCBlockSizeRemainderCase2D::~ASTCBlockSizeRemainderCase2D (void)
+{
+	ASTCBlockSizeRemainderCase2D::deinit();
+}
+
+void ASTCBlockSizeRemainderCase2D::init (void)
+{
+	const IVec2 blockSize = m_renderer->getBlockSize();
+	m_renderer->initialize(MAX_NUM_BLOCKS_X*blockSize.x(), MAX_NUM_BLOCKS_Y*blockSize.y(), Vec4(1.0f), Vec4(0.0f));
+}
+
+void ASTCBlockSizeRemainderCase2D::deinit (void)
+{
+	m_renderer->clear();
+}
+
+ASTCBlockSizeRemainderCase2D::IterateResult ASTCBlockSizeRemainderCase2D::iterate (void)
+{
+	TestLog&						log						= m_testCtx.getLog();
+	const IVec2						blockSize				= m_renderer->getBlockSize();
+	const int						curRemainderX			= m_currentIteration % blockSize.x();
+	const int						curRemainderY			= m_currentIteration / blockSize.x();
+	const int						imageWidth				= (MAX_NUM_BLOCKS_X-1)*blockSize.x() + curRemainderX;
+	const int						imageHeight				= (MAX_NUM_BLOCKS_Y-1)*blockSize.y() + curRemainderY;
+	const int						numBlocksX				= divRoundUp(imageWidth, blockSize.x());
+	const int						numBlocksY				= divRoundUp(imageHeight, blockSize.y());
+	const int						totalNumBlocks			= numBlocksX * numBlocksY;
+	const glu::RenderContext&		renderCtx				= m_context.getRenderContext();
+	const tcu::RGBA					threshold				= renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + (tcu::isASTCSRGBFormat(m_format) ? tcu::RGBA(2,2,2,2) : tcu::RGBA(1,1,1,1));
+	tcu::CompressedTexture			compressed				(m_format, imageWidth, imageHeight);
+
+	DE_ASSERT(compressed.getDataSize() == totalNumBlocks*ASTC_BLOCK_SIZE_BYTES);
+	generateDefaultBlockData((deUint8*)compressed.getData(), totalNumBlocks, blockSize.x(), blockSize.y());
+
+	// Create texture and render.
+
+	Surface			renderedFrame	(imageWidth, imageHeight);
+	Surface			referenceFrame	(imageWidth, imageHeight);
+	glu::Texture2D	texture			(renderCtx, m_context.getContextInfo(), 1, &compressed, tcu::CompressedTexture::DecompressionParams(m_renderer->getASTCSupport() == ASTCSUPPORTLEVEL_LDR));
+
+	m_renderer->render(referenceFrame, renderedFrame, texture, compressed.getUncompressedFormat());
+
+	{
+		// Compare and log.
+
+		tcu::ScopedLogSection section(log, "Iteration " + de::toString(m_currentIteration),
+										   "Remainder " + de::toString(curRemainderX) + "x" + de::toString(curRemainderY));
+
+		log << TestLog::Message << "Using texture of size "
+								<< imageWidth << "x" << imageHeight
+								<< " and block size "
+								<< blockSize.x() << "x" << blockSize.y()
+								<< "; the x and y remainders are "
+								<< curRemainderX << " and " << curRemainderY << " respectively"
+			<< TestLog::EndMessage;
+
+		const bool compareOk = tcu::pixelThresholdCompare(m_testCtx.getLog(), "ComparisonResult", "Comparison Result", referenceFrame, renderedFrame, threshold,
+														  m_currentIteration == 0 ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);
+
+		if (!compareOk)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+			return STOP;
+		}
+	}
+
+	if (m_currentIteration == 0 && m_currentIteration+1 < blockSize.x()*blockSize.y())
+		log << TestLog::Message << "Note: not logging further images unless reference comparison fails" << TestLog::EndMessage;
+
+	m_currentIteration++;
+
+	if (m_currentIteration >= blockSize.x()*blockSize.y())
+	{
+		DE_ASSERT(m_currentIteration == blockSize.x()*blockSize.y());
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+	return CONTINUE;
+}
+
+void ASTCBlockSizeRemainderCase2D::generateDefaultBlockData (deUint8* dst, int numBlocks, int blockWidth, int blockHeight)
+{
+	using namespace ASTCBlockGeneratorInternal;
+
+	NormalBlockParams blockParams;
+
+	blockParams.weightGridWidth			= 3;
+	blockParams.weightGridHeight		= 3;
+	blockParams.weightISEParams			= ISEParams(ISEMODE_PLAIN_BIT, 5);
+	blockParams.isDualPlane				= false;
+	blockParams.numPartitions			= 1;
+	blockParams.colorEndpointModes[0]	= 8;
+
+	NormalBlockISEInputs iseInputs = generateDefaultISEInputs(blockParams);
+	iseInputs.weight.isGivenInBlockForm = false;
+
+	const int numWeights		= computeNumWeights(blockParams);
+	const int weightRangeMax	= computeISERangeMax(blockParams.weightISEParams);
+
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		for (int weightNdx = 0; weightNdx < numWeights; weightNdx++)
+			iseInputs.weight.value.plain[weightNdx] = (blockNdx*numWeights + weightNdx) * weightRangeMax / (numBlocks*numWeights-1);
+
+		generateNormalBlock(blockParams, blockWidth, blockHeight, iseInputs).assignToMemory(dst + blockNdx*ASTC_BLOCK_SIZE_BYTES);
+	}
+}
+
+const char* getBlockTestTypeName (ASTCBlockTestType testType)
+{
+	switch (testType)
+	{
+		case ASTCBLOCKTESTTYPE_VOID_EXTENT_LDR:				return "void_extent_ldr";
+		case ASTCBLOCKTESTTYPE_VOID_EXTENT_HDR:				return "void_extent_hdr";
+		case ASTCBLOCKTESTTYPE_WEIGHT_GRID:					return "weight_grid";
+		case ASTCBLOCKTESTTYPE_WEIGHT_ISE:					return "weight_ise";
+		case ASTCBLOCKTESTTYPE_CEMS:						return "color_endpoint_modes";
+		case ASTCBLOCKTESTTYPE_PARTITION_SEED:				return "partition_pattern_index";
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_LDR:			return "endpoint_value_ldr";
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_NO_15:	return "endpoint_value_hdr_cem_not_15";
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_15:		return "endpoint_value_hdr_cem_15";
+		case ASTCBLOCKTESTTYPE_ENDPOINT_ISE:				return "endpoint_ise";
+		case ASTCBLOCKTESTTYPE_CCS:							return "color_component_selector";
+		case ASTCBLOCKTESTTYPE_RANDOM:						return "random";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+const char* getBlockTestTypeDescription (ASTCBlockTestType testType)
+{
+	switch (testType)
+	{
+		case ASTCBLOCKTESTTYPE_VOID_EXTENT_LDR:				return "Test void extent block, LDR mode";
+		case ASTCBLOCKTESTTYPE_VOID_EXTENT_HDR:				return "Test void extent block, HDR mode";
+		case ASTCBLOCKTESTTYPE_WEIGHT_GRID:					return "Test combinations of plane count, weight integer sequence encoding parameters, and weight grid size";
+		case ASTCBLOCKTESTTYPE_WEIGHT_ISE:					return "Test different integer sequence encoding block values for weight grid";
+		case ASTCBLOCKTESTTYPE_CEMS:						return "Test different color endpoint mode combinations, combined with different plane and partition counts";
+		case ASTCBLOCKTESTTYPE_PARTITION_SEED:				return "Test different partition pattern indices";
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_LDR:			return "Test various combinations of each pair of color endpoint values, for each LDR color endpoint mode";
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_NO_15:	return "Test various combinations of each pair of color endpoint values, for each HDR color endpoint mode other than mode 15";
+		case ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_15:		return "Test various combinations of each pair of color endpoint values, HDR color endpoint mode 15";
+		case ASTCBLOCKTESTTYPE_ENDPOINT_ISE:				return "Test different integer sequence encoding block values for color endpoints";
+		case ASTCBLOCKTESTTYPE_CCS:							return "Test color component selector, for different partition counts";
+		case ASTCBLOCKTESTTYPE_RANDOM:						return "Random block test";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+bool isBlockTestTypeHDROnly (ASTCBlockTestType testType)
+{
+	return testType == ASTCBLOCKTESTTYPE_VOID_EXTENT_HDR			||
+		   testType == ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_NO_15	||
+		   testType == ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_15;
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fASTCDecompressionCases.hpp b/modules/gles3/functional/es3fASTCDecompressionCases.hpp
new file mode 100644
index 0000000..115caff
--- /dev/null
+++ b/modules/gles3/functional/es3fASTCDecompressionCases.hpp
@@ -0,0 +1,137 @@
+#ifndef _ES3FASTCDECOMPRESSIONCASES_HPP
+#define _ES3FASTCDECOMPRESSIONCASES_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief ASTC decompression tests
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes3TestCase.hpp"
+#include "tcuCompressedTexture.hpp"
+#include "deUniquePtr.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+namespace ASTCDecompressionCaseInternal
+{
+
+class ASTCRenderer2D;
+
+}
+
+enum ASTCBlockTestType
+{
+	ASTCBLOCKTESTTYPE_VOID_EXTENT_LDR = 0,
+	ASTCBLOCKTESTTYPE_VOID_EXTENT_HDR,
+	ASTCBLOCKTESTTYPE_WEIGHT_GRID,
+	ASTCBLOCKTESTTYPE_WEIGHT_ISE,
+	ASTCBLOCKTESTTYPE_CEMS,
+	ASTCBLOCKTESTTYPE_PARTITION_SEED,
+	ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_LDR,
+	ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_NO_15,
+	ASTCBLOCKTESTTYPE_ENDPOINT_VALUE_HDR_15,
+	ASTCBLOCKTESTTYPE_ENDPOINT_ISE,
+	ASTCBLOCKTESTTYPE_CCS,
+	ASTCBLOCKTESTTYPE_RANDOM,
+
+	ASTCBLOCKTESTTYPE_LAST
+};
+
+// General ASTC block test class.
+class ASTCBlockCase2D : public TestCase
+{
+public:
+																	ASTCBlockCase2D			(Context&							context,
+																							 const char*						name,
+																							 const char*						description,
+																							 ASTCBlockTestType					testType,
+																							 tcu::CompressedTexture::Format		format);
+																	~ASTCBlockCase2D		(void);
+
+	void															init					(void);
+	void															deinit					(void);
+	IterateResult													iterate					(void);
+
+private:
+	static void														generateDummyBlocks		(deUint8* dst, int num);
+
+																	ASTCBlockCase2D			(const ASTCBlockCase2D& other);
+	ASTCBlockCase2D&												operator=				(const ASTCBlockCase2D& other);
+
+	const ASTCBlockTestType											m_testType;
+	const tcu::CompressedTexture::Format							m_format;
+	std::vector<deUint8>											m_blockData;
+
+	int																m_numBlocksTested;
+	int																m_currentIteration;
+
+	de::UniquePtr<ASTCDecompressionCaseInternal::ASTCRenderer2D>	m_renderer;
+};
+
+// For a format with block size (W, H), test with texture sizes {(k*W + a, k*H + b) | 0 <= a < W, 0 <= b < H } .
+class ASTCBlockSizeRemainderCase2D : public TestCase
+{
+public:
+																	ASTCBlockSizeRemainderCase2D	(Context&							context,
+																									 const char*						name,
+																									 const char*						description,
+																									 tcu::CompressedTexture::Format		format);
+																	~ASTCBlockSizeRemainderCase2D	(void);
+
+	void															init							(void);
+	void															deinit							(void);
+	IterateResult													iterate							(void);
+
+private:
+	enum
+	{
+		MAX_NUM_BLOCKS_X = 5,
+		MAX_NUM_BLOCKS_Y = 5
+	};
+
+	static void														generateDefaultBlockData		(deUint8* dst, int numBlocks, int blockWidth, int blockHeight);
+
+																	ASTCBlockSizeRemainderCase2D	(const ASTCBlockSizeRemainderCase2D& other);
+	ASTCBlockSizeRemainderCase2D&									operator=						(const ASTCBlockSizeRemainderCase2D& other);
+
+	const tcu::CompressedTexture::Format							m_format;
+
+	int																m_currentIteration;
+
+	de::UniquePtr<ASTCDecompressionCaseInternal::ASTCRenderer2D>	m_renderer;
+};
+
+const char*		getBlockTestTypeName			(ASTCBlockTestType testType);
+const char*		getBlockTestTypeDescription		(ASTCBlockTestType testType);
+bool			isBlockTestTypeHDROnly			(ASTCBlockTestType testType);
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FASTCDECOMPRESSIONCASES_HPP
diff --git a/modules/gles3/functional/es3fApiCase.cpp b/modules/gles3/functional/es3fApiCase.cpp
new file mode 100644
index 0000000..892ad35
--- /dev/null
+++ b/modules/gles3/functional/es3fApiCase.cpp
@@ -0,0 +1,114 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief API test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fApiCase.hpp"
+#include "gluStrUtil.hpp"
+#include "gluRenderContext.hpp"
+
+#include <algorithm>
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+
+ApiCase::ApiCase (Context& context, const char* name, const char* description)
+	: TestCase		(context, name, description)
+	, CallLogWrapper(context.getRenderContext().getFunctions(), context.getTestContext().getLog())
+	, m_log			(getLog())
+{
+}
+
+ApiCase::~ApiCase (void)
+{
+}
+
+ApiCase::IterateResult ApiCase::iterate (void)
+{
+	// Initialize result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Enable call logging.
+	enableLogging(true);
+
+	// Run test.
+	test();
+
+	return STOP;
+}
+
+void ApiCase::expectError (deUint32 expected)
+{
+	deUint32 err = glGetError();
+	if (err != expected)
+	{
+		m_log << TestLog::Message << "// ERROR: expected " << glu::getErrorStr(expected) << TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid error");
+	}
+}
+
+void ApiCase::expectError (deUint32 expected0, deUint32 expected1)
+{
+	deUint32 err = glGetError();
+	if (err != expected0 && err != expected1)
+	{
+		m_log << TestLog::Message << "// ERROR: expected " << glu::getErrorStr(expected0) << " or " << glu::getErrorStr(expected1) << TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid error");
+	}
+}
+
+void ApiCase::checkBooleans (deUint8 value, deUint8 expected)
+{
+	if (value != expected)
+	{
+		m_log << TestLog::Message << "// ERROR: expected " << (expected	? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void ApiCase::getSupportedExtensions (const deUint32 numSupportedValues, const deUint32 extension, std::vector<int>& values)
+{
+	deInt32 numFormats;
+	GLU_CHECK_CALL(glGetIntegerv(numSupportedValues, &numFormats));
+	if (numFormats == 0)
+	{
+		m_log << TestLog::Message << "// No supported extensions available." << TestLog::EndMessage;
+		return;
+	}
+	values.resize(numFormats);
+	GLU_CHECK_CALL(glGetIntegerv(extension, &values[0]));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fApiCase.hpp b/modules/gles3/functional/es3fApiCase.hpp
new file mode 100644
index 0000000..b644282
--- /dev/null
+++ b/modules/gles3/functional/es3fApiCase.hpp
@@ -0,0 +1,73 @@
+#ifndef _ES3FAPICASE_HPP
+#define _ES3FAPICASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief API test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "tcuTestLog.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ApiCase : public TestCase, protected glu::CallLogWrapper
+{
+public:
+						ApiCase					(Context& context, const char* name, const char* description);
+	virtual				~ApiCase				(void);
+
+	IterateResult		iterate					(void);
+
+protected:
+	virtual void		test					(void) = DE_NULL;
+
+	void				expectError				(deUint32 error);
+	void				expectError				(deUint32 error0, deUint32 error1);
+	void				getSupportedExtensions	(const deUint32 numSupportedValues, const deUint32 extension, std::vector<int>& values);
+	void				checkBooleans			(deUint8 value, deUint8 expected);
+
+	tcu::TestLog&		m_log;
+};
+
+// Helper macro for declaring ApiCases.
+#define ES3F_ADD_API_CASE(NAME, DESCRIPTION, TEST_FUNC_BODY)							\
+	do {																				\
+		class ApiCase_##NAME : public ApiCase {											\
+		public:																			\
+			ApiCase_##NAME (Context& context) : ApiCase(context, #NAME, DESCRIPTION) {}	\
+		protected:																		\
+			void test (void) TEST_FUNC_BODY												\
+		};																				\
+		addChild(new ApiCase_##NAME(m_context));										\
+	} while (deGetFalse())
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FAPICASE_HPP
diff --git a/modules/gles3/functional/es3fAttribLocationTests.cpp b/modules/gles3/functional/es3fAttribLocationTests.cpp
new file mode 100644
index 0000000..4a074f7
--- /dev/null
+++ b/modules/gles3/functional/es3fAttribLocationTests.cpp
@@ -0,0 +1,294 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Attribute location test
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fAttribLocationTests.hpp"
+
+#include "glsAttributeLocationTests.hpp"
+
+#include "glw.h"
+
+using namespace deqp::gls::AttributeLocationTestUtil;
+using std::vector;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+TestCaseGroup* createAttributeLocationTests	(Context& context)
+{
+	const AttribType	types[] =
+	{
+		AttribType("float",		1,  GL_FLOAT),
+		AttribType("vec2",		1,  GL_FLOAT_VEC2),
+		AttribType("vec3",		1,  GL_FLOAT_VEC3),
+		AttribType("vec4",		1,  GL_FLOAT_VEC4),
+
+		AttribType("mat2",		2,  GL_FLOAT_MAT2),
+		AttribType("mat3",		3,  GL_FLOAT_MAT3),
+		AttribType("mat4",		4,  GL_FLOAT_MAT4),
+
+		AttribType("int",		1,	GL_INT),
+		AttribType("ivec2",		1,	GL_INT_VEC2),
+		AttribType("ivec3",		1,	GL_INT_VEC3),
+		AttribType("ivec4",		1,	GL_INT_VEC4),
+
+		AttribType("uint",		1,	GL_UNSIGNED_INT),
+		AttribType("uvec2",		1,	GL_UNSIGNED_INT_VEC2),
+		AttribType("uvec3",		1,	GL_UNSIGNED_INT_VEC3),
+		AttribType("uvec4",		1,	GL_UNSIGNED_INT_VEC4),
+
+		AttribType("mat2x2",	2,	GL_FLOAT_MAT2),
+		AttribType("mat2x3",	2,	GL_FLOAT_MAT2x3),
+		AttribType("mat2x4",	2,	GL_FLOAT_MAT2x4),
+
+		AttribType("mat3x2",	3,	GL_FLOAT_MAT3x2),
+		AttribType("mat3x3",	3,	GL_FLOAT_MAT3),
+		AttribType("mat3x4",	3,	GL_FLOAT_MAT3x4),
+
+		AttribType("mat4x2",	4,	GL_FLOAT_MAT4x2),
+		AttribType("mat4x3",	4,	GL_FLOAT_MAT4x3),
+		AttribType("mat4x4",	4,	GL_FLOAT_MAT4)
+	};
+
+	const AttribType	es2Types[] =
+	{
+		AttribType("float",	1,  GL_FLOAT),
+		AttribType("vec2",	1,  GL_FLOAT_VEC2),
+		AttribType("vec3",	1,  GL_FLOAT_VEC3),
+		AttribType("vec4",	1,  GL_FLOAT_VEC4),
+
+		AttribType("mat2",	2,  GL_FLOAT_MAT2),
+		AttribType("mat3",	3,  GL_FLOAT_MAT3),
+		AttribType("mat4",	4,  GL_FLOAT_MAT4)
+	};
+
+	TestCaseGroup* const root = new TestCaseGroup (context, "attribute_location", "Attribute location tests");
+
+	// Basic bind attribute tests
+	{
+		TestCaseGroup* const bindAttributeGroup = new TestCaseGroup(context, "bind", "Basic bind attribute location tests.");
+
+		root->addChild(bindAttributeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+			bindAttributeGroup->addChild(new gls::BindAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Bind max number of attributes
+	{
+		TestCaseGroup* const bindMaxAttributeGroup = new TestCaseGroup(context, "bind_max_attributes", "Use bind with maximum number of attributes.");
+
+		root->addChild(bindMaxAttributeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+			bindMaxAttributeGroup->addChild(new gls::BindMaxAttributesTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test aliasing
+	{
+		TestCaseGroup* const aliasingGroup = new TestCaseGroup(context, "bind_aliasing", "Test binding aliasing locations.");
+
+		root->addChild(aliasingGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(es2Types); typeNdx++)
+		{
+			const AttribType& type = es2Types[typeNdx];
+
+			// Simple aliasing cases
+			aliasingGroup->addChild(new gls::BindAliasingAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+
+			// For types which occupy more than one location. Alias second location.
+			if (type.getLocationSize() > 1)
+				aliasingGroup->addChild(new gls::BindAliasingAttributeTest(context.getTestContext(), context.getRenderContext(), type, 1));
+
+			// Use more than maximum attributes with aliasing
+			aliasingGroup->addChild(new gls::BindMaxAliasingAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+
+			// Use more than maximum attributes but inactive
+			aliasingGroup->addChild(new gls::BindInactiveAliasingAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test filling holes in attribute location
+	{
+		TestCaseGroup* const holeGroup = new TestCaseGroup(context, "bind_hole", "Bind all, but one attribute and leave hole in location space for it.");
+
+		root->addChild(holeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+
+			// Bind first location, leave hole size of type and fill rest of locations
+			holeGroup->addChild(new gls::BindHoleAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test binding at different times
+	{
+		TestCaseGroup* const bindTimeGroup = new TestCaseGroup(context, "bind_time", "Bind time tests. Test binding at different stages.");
+
+		root->addChild(bindTimeGroup);
+
+		bindTimeGroup->addChild(new gls::PreAttachBindAttributeTest(context.getTestContext(), context.getRenderContext()));
+		bindTimeGroup->addChild(new gls::PreLinkBindAttributeTest(context.getTestContext(), context.getRenderContext()));
+		bindTimeGroup->addChild(new gls::PostLinkBindAttributeTest(context.getTestContext(), context.getRenderContext()));
+		bindTimeGroup->addChild(new gls::BindRelinkAttributeTest(context.getTestContext(), context.getRenderContext()));
+		bindTimeGroup->addChild(new gls::BindReattachAttributeTest(context.getTestContext(), context.getRenderContext()));
+	}
+
+	// Basic layout location attribute tests
+	{
+		TestCaseGroup* const layoutAttributeGroup = new TestCaseGroup(context, "layout", "Basic layout location tests.");
+
+		root->addChild(layoutAttributeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+			layoutAttributeGroup->addChild(new gls::LocationAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test max attributes with layout locations
+	{
+		TestCaseGroup* const layoutMaxAttributeGroup = new TestCaseGroup(context, "layout_max_attributes", "Maximum attributes used with layout location qualifiers.");
+
+		root->addChild(layoutMaxAttributeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+			layoutMaxAttributeGroup->addChild(new gls::LocationMaxAttributesTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test filling holes in attribute location
+	{
+		TestCaseGroup* const holeGroup = new TestCaseGroup(context, "layout_hole", "Define layout location for all, but one attribute consuming max attribute locations.");
+
+		root->addChild(holeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+
+			// Location first location, leave hole size of type and fill rest of locations
+			holeGroup->addChild(new gls::LocationHoleAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Basic mixed mixed attribute tests
+	{
+		TestCaseGroup* const mixedAttributeGroup = new TestCaseGroup(context, "mixed", "Basic mixed location tests.");
+
+		root->addChild(mixedAttributeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+			mixedAttributeGroup->addChild(new gls::MixedAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	{
+		TestCaseGroup* const mixedMaxAttributeGroup = new TestCaseGroup(context, "mixed_max_attributes", "Maximum attributes used with mixed binding and layout qualifiers.");
+
+		root->addChild(mixedMaxAttributeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+			mixedMaxAttributeGroup->addChild(new gls::MixedMaxAttributesTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test mixed binding at different times
+	{
+		TestCaseGroup* const mixedTimeGroup = new TestCaseGroup(context, "mixed_time", "Bind time tests. Test binding at different stages.");
+
+		root->addChild(mixedTimeGroup);
+
+		mixedTimeGroup->addChild(new gls::PreAttachMixedAttributeTest(context.getTestContext(), context.getRenderContext()));
+		mixedTimeGroup->addChild(new gls::PreLinkMixedAttributeTest(context.getTestContext(), context.getRenderContext()));
+		mixedTimeGroup->addChild(new gls::PostLinkMixedAttributeTest(context.getTestContext(), context.getRenderContext()));
+		mixedTimeGroup->addChild(new gls::MixedRelinkAttributeTest(context.getTestContext(), context.getRenderContext()));
+		mixedTimeGroup->addChild(new gls::MixedReattachAttributeTest(context.getTestContext(), context.getRenderContext()));
+	}
+
+	{
+		TestCaseGroup* const holeGroup = new TestCaseGroup(context, "mixed_hole", "Use layout location qualifiers and binding. Leave hole in location space for only free attribute.");
+
+		root->addChild(holeGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+
+			holeGroup->addChild(new gls::MixedHoleAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test hole in location space that moves when relinking
+	{
+		TestCaseGroup* const relinkBindHoleGroup = new TestCaseGroup(context, "bind_relink_hole", "Test relinking with moving hole in attribute location space.");
+
+		root->addChild(relinkBindHoleGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+
+			relinkBindHoleGroup->addChild(new gls::BindRelinkHoleAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	// Test hole in location space that moves when relinking
+	{
+		TestCaseGroup* const relinkMixedHoleGroup = new TestCaseGroup(context, "mixed_relink_hole", "Test relinking with moving hole in attribute location space.");
+
+		root->addChild(relinkMixedHoleGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(types); typeNdx++)
+		{
+			const AttribType& type = types[typeNdx];
+
+			relinkMixedHoleGroup->addChild(new gls::MixedRelinkHoleAttributeTest(context.getTestContext(), context.getRenderContext(), type));
+		}
+	}
+
+	return root;
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fAttribLocationTests.hpp b/modules/gles3/functional/es3fAttribLocationTests.hpp
new file mode 100644
index 0000000..b470057
--- /dev/null
+++ b/modules/gles3/functional/es3fAttribLocationTests.hpp
@@ -0,0 +1,42 @@
+#ifndef _ES3FATTRIBLOCATIONTESTS_HPP
+#define _ES3FATTRIBLOCATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Attribute location tests
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+TestCaseGroup* createAttributeLocationTests	(Context& context);
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FATTRIBLOCATIONTESTS_HPP
diff --git a/modules/gles3/functional/es3fBlendTests.cpp b/modules/gles3/functional/es3fBlendTests.cpp
new file mode 100644
index 0000000..f91573a
--- /dev/null
+++ b/modules/gles3/functional/es3fBlendTests.cpp
@@ -0,0 +1,513 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Blend tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fBlendTests.hpp"
+#include "gluStrUtil.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuPixelFormat.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "deRandom.hpp"
+#include "rrFragmentOperations.hpp"
+#include "sglrReferenceUtils.hpp"
+
+#include <string>
+#include <vector>
+
+#include "glw.h"
+
+namespace deqp
+{
+
+using gls::FragmentOpUtil::Quad;
+using gls::FragmentOpUtil::IntegerQuad;
+using gls::FragmentOpUtil::QuadRenderer;
+using gls::FragmentOpUtil::ReferenceQuadRenderer;
+using glu::getBlendEquationName;
+using glu::getBlendFactorName;
+using tcu::Vec4;
+using tcu::UVec4;
+using tcu::TestLog;
+using tcu::TextureLevel;
+using tcu::TextureFormat;
+using std::string;
+using std::vector;
+
+namespace gles3
+{
+namespace Functional
+{
+
+static const int MAX_VIEWPORT_WIDTH		= 64;
+static const int MAX_VIEWPORT_HEIGHT	= 64;
+
+static TextureLevel sRGBATextureLevelToLinear (const tcu::ConstPixelBufferAccess& sRGBAAccess)
+{
+	DE_ASSERT(sRGBAAccess.getFormat().order == TextureFormat::sRGBA);
+
+	int						width			= sRGBAAccess.getWidth();
+	int						height			= sRGBAAccess.getHeight();
+	TextureLevel			linear			(TextureFormat(TextureFormat::RGBA, sRGBAAccess.getFormat().type), width, height);
+	tcu::PixelBufferAccess	linearAccess	= linear.getAccess();
+
+	for (int y = 0; y < height; y++)
+	for (int x = 0; x < width; x++)
+		linearAccess.setPixel(tcu::sRGBToLinear(sRGBAAccess.getPixel(x, y)), x, y);
+
+	return linear;
+}
+
+struct BlendParams
+{
+	GLenum	equationRGB;
+	GLenum	srcFuncRGB;
+	GLenum	dstFuncRGB;
+	GLenum	equationAlpha;
+	GLenum	srcFuncAlpha;
+	GLenum	dstFuncAlpha;
+	Vec4	blendColor;
+
+	BlendParams (GLenum		equationRGB_,
+				 GLenum		srcFuncRGB_,
+				 GLenum		dstFuncRGB_,
+				 GLenum		equationAlpha_,
+				 GLenum		srcFuncAlpha_,
+				 GLenum		dstFuncAlpha_,
+				 Vec4		blendColor_)
+	: equationRGB	(equationRGB_)
+	, srcFuncRGB	(srcFuncRGB_)
+	, dstFuncRGB	(dstFuncRGB_)
+	, equationAlpha	(equationAlpha_)
+	, srcFuncAlpha	(srcFuncAlpha_)
+	, dstFuncAlpha	(dstFuncAlpha_)
+	, blendColor	(blendColor_)
+	{
+	}
+};
+
+class BlendCase : public TestCase
+{
+public:
+							BlendCase	(Context&						context,
+										 const char*					name,
+										 const char*					desc,
+										 const vector<BlendParams>&		paramSets,
+										 bool							useSrgbFbo);
+
+							~BlendCase	(void);
+
+	void					init		(void);
+	void					deinit		(void);
+
+	IterateResult			iterate		(void);
+
+private:
+							BlendCase	(const BlendCase& other);
+	BlendCase&				operator=	(const BlendCase& other);
+
+	vector<BlendParams>		m_paramSets;
+	int						m_curParamSetNdx;
+
+	bool					m_useSrgbFbo;
+	deUint32				m_colorRbo;
+	deUint32				m_fbo;
+
+	QuadRenderer*			m_renderer;
+	ReferenceQuadRenderer*	m_referenceRenderer;
+	TextureLevel*			m_refColorBuffer;
+	Quad					m_firstQuad;
+	Quad					m_secondQuad;
+	IntegerQuad				m_firstQuadInt;
+	IntegerQuad				m_secondQuadInt;
+
+	int						m_renderWidth;
+	int						m_renderHeight;
+	int						m_viewportWidth;
+	int						m_viewportHeight;
+};
+
+BlendCase::BlendCase (Context&						context,
+					  const char*					name,
+					  const char*					desc,
+					  const vector<BlendParams>&	paramSets,
+					  bool							useSrgbFbo)
+	: TestCase				(context, name, desc)
+	, m_paramSets			(paramSets)
+	, m_curParamSetNdx		(0)
+	, m_useSrgbFbo			(useSrgbFbo)
+	, m_colorRbo			(0)
+	, m_fbo					(0)
+	, m_renderer			(DE_NULL)
+	, m_referenceRenderer	(DE_NULL)
+	, m_refColorBuffer		(DE_NULL)
+	, m_renderWidth			(m_useSrgbFbo ? 2*MAX_VIEWPORT_WIDTH	: m_context.getRenderTarget().getWidth())
+	, m_renderHeight		(m_useSrgbFbo ? 2*MAX_VIEWPORT_HEIGHT	: m_context.getRenderTarget().getHeight())
+	, m_viewportWidth		(0)
+	, m_viewportHeight		(0)
+{
+	DE_ASSERT(!m_paramSets.empty());
+}
+
+void BlendCase::init (void)
+{
+	bool useRGB = !m_useSrgbFbo && m_context.getRenderTarget().getPixelFormat().alphaBits == 0;
+
+	static const Vec4 baseGradientColors[4] =
+	{
+		Vec4(0.0f, 0.5f, 1.0f, 0.5f),
+		Vec4(0.5f, 0.0f, 0.5f, 1.0f),
+		Vec4(0.5f, 1.0f, 0.5f, 0.0f),
+		Vec4(1.0f, 0.5f, 0.0f, 0.5f)
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(m_firstQuad.color) == DE_LENGTH_OF_ARRAY(m_firstQuadInt.color));
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(m_firstQuad.color); i++)
+	{
+		m_firstQuad.color[i]		= (baseGradientColors[i] - 0.5f) * 0.2f + 0.5f;
+		m_firstQuadInt.color[i]		= m_firstQuad.color[i];
+
+		m_secondQuad.color[i]		= (Vec4(1.0f) - baseGradientColors[i] - 0.5f) * 1.0f + 0.5f;
+		m_secondQuadInt.color[i]	= m_secondQuad.color[i];
+	}
+
+	m_viewportWidth		= de::min<int>(m_renderWidth,	MAX_VIEWPORT_WIDTH);
+	m_viewportHeight	= de::min<int>(m_renderHeight,	MAX_VIEWPORT_HEIGHT);
+
+	m_firstQuadInt.posA		= tcu::IVec2(0,						0);
+	m_secondQuadInt.posA	= tcu::IVec2(0,						0);
+	m_firstQuadInt.posB		= tcu::IVec2(m_viewportWidth-1,		m_viewportHeight-1);
+	m_secondQuadInt.posB	= tcu::IVec2(m_viewportWidth-1,		m_viewportHeight-1);
+
+	DE_ASSERT(!m_renderer);
+	DE_ASSERT(!m_referenceRenderer);
+	DE_ASSERT(!m_refColorBuffer);
+
+	m_renderer				= new QuadRenderer(m_context.getRenderContext(), glu::GLSL_VERSION_300_ES);
+	m_referenceRenderer		= new ReferenceQuadRenderer;
+	m_refColorBuffer		= new TextureLevel(TextureFormat(m_useSrgbFbo ? TextureFormat::sRGBA : useRGB ? TextureFormat::RGB : TextureFormat::RGBA, TextureFormat::UNORM_INT8),
+											   m_viewportWidth, m_viewportHeight);
+
+	m_curParamSetNdx = 0;
+
+	if (m_useSrgbFbo)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Using FBO of size (" << m_renderWidth << ", " << m_renderHeight << ") with format GL_SRGB8_ALPHA8" << TestLog::EndMessage;
+
+		GLU_CHECK_CALL(glGenRenderbuffers(1, &m_colorRbo));
+		GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_colorRbo));
+		GLU_CHECK_CALL(glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8_ALPHA8, m_renderWidth, m_renderHeight));
+
+		GLU_CHECK_CALL(glGenFramebuffers(1, &m_fbo));
+		GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_fbo));
+		GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorRbo));
+	}
+}
+
+BlendCase::~BlendCase (void)
+{
+	BlendCase::deinit();
+}
+
+void BlendCase::deinit (void)
+{
+	delete m_renderer;
+	delete m_referenceRenderer;
+	delete m_refColorBuffer;
+
+	m_renderer			= DE_NULL;
+	m_referenceRenderer	= DE_NULL;
+	m_refColorBuffer	= DE_NULL;
+
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
+
+	if (m_colorRbo != 0)
+	{
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &m_colorRbo));
+		m_colorRbo = 0;
+	}
+	if (m_fbo != 0)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &m_fbo));
+		m_fbo = 0;
+	}
+}
+
+BlendCase::IterateResult BlendCase::iterate (void)
+{
+	de::Random						rnd				(deStringHash(getName()) ^ deInt32Hash(m_curParamSetNdx));
+	int								viewportX		= rnd.getInt(0, m_renderWidth - m_viewportWidth);
+	int								viewportY		= rnd.getInt(0, m_renderHeight - m_viewportHeight);
+	TextureLevel					renderedImg		(TextureFormat(m_useSrgbFbo ? TextureFormat::sRGBA : TextureFormat::RGBA, TextureFormat::UNORM_INT8), m_viewportWidth, m_viewportHeight);
+	TestLog&						log				(m_testCtx.getLog());
+	const BlendParams&				paramSet		= m_paramSets[m_curParamSetNdx];
+	rr::FragmentOperationState		referenceState;
+
+	// Log the blend parameters.
+
+	log << TestLog::Message << "RGB equation = " << getBlendEquationName(paramSet.equationRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "RGB src func = " << getBlendFactorName(paramSet.srcFuncRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "RGB dst func = " << getBlendFactorName(paramSet.dstFuncRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "Alpha equation = " << getBlendEquationName(paramSet.equationAlpha) << TestLog::EndMessage;
+	log << TestLog::Message << "Alpha src func = " << getBlendFactorName(paramSet.srcFuncAlpha) << TestLog::EndMessage;
+	log << TestLog::Message << "Alpha dst func = " << getBlendFactorName(paramSet.dstFuncAlpha) << TestLog::EndMessage;
+	log << TestLog::Message << "Blend color = (" << paramSet.blendColor.x() << ", " << paramSet.blendColor.y() << ", " << paramSet.blendColor.z() << ", " << paramSet.blendColor.w() << ")" << TestLog::EndMessage;
+
+	// Set GL state.
+
+	GLU_CHECK_CALL(glBlendEquationSeparate(paramSet.equationRGB, paramSet.equationAlpha));
+	GLU_CHECK_CALL(glBlendFuncSeparate(paramSet.srcFuncRGB, paramSet.dstFuncRGB, paramSet.srcFuncAlpha, paramSet.dstFuncAlpha));
+	GLU_CHECK_CALL(glBlendColor(paramSet.blendColor.x(), paramSet.blendColor.y(), paramSet.blendColor.z(), paramSet.blendColor.w()));
+
+	// Set reference state.
+
+	referenceState.blendRGBState.equation	= sglr::rr_util::mapGLBlendEquation(paramSet.equationRGB);
+	referenceState.blendRGBState.srcFunc	= sglr::rr_util::mapGLBlendFunc(paramSet.srcFuncRGB);
+	referenceState.blendRGBState.dstFunc	= sglr::rr_util::mapGLBlendFunc(paramSet.dstFuncRGB);
+	referenceState.blendAState.equation		= sglr::rr_util::mapGLBlendEquation(paramSet.equationAlpha);
+	referenceState.blendAState.srcFunc		= sglr::rr_util::mapGLBlendFunc(paramSet.srcFuncAlpha);
+	referenceState.blendAState.dstFunc		= sglr::rr_util::mapGLBlendFunc(paramSet.dstFuncAlpha);
+	referenceState.blendColor				= paramSet.blendColor;
+
+	// Render with GL.
+
+	glDisable(GL_BLEND);
+	glViewport(viewportX, viewportY, m_viewportWidth, m_viewportHeight);
+	m_renderer->render(m_firstQuad);
+	glEnable(GL_BLEND);
+	m_renderer->render(m_secondQuad);
+	glFlush();
+
+	// Render reference.
+
+	const tcu::PixelBufferAccess nullAccess(TextureFormat(), 0, 0, 0, DE_NULL);
+
+	referenceState.blendMode = rr::BLENDMODE_NONE;
+	m_referenceRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()), nullAccess /* no depth */, nullAccess /* no stencil */, m_firstQuadInt, referenceState);
+	referenceState.blendMode = rr::BLENDMODE_STANDARD;
+	m_referenceRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()), nullAccess /* no depth */, nullAccess /* no stencil */, m_secondQuadInt, referenceState);
+
+	// Read GL image.
+
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedImg.getAccess());
+
+	// Compare images.
+	// \note In sRGB cases, convert to linear space for comparison.
+
+	UVec4 compareThreshold = (m_useSrgbFbo ? tcu::PixelFormat(8, 8, 8, 8) : m_context.getRenderTarget().getPixelFormat()).getColorThreshold().toIVec().asUint()
+							 * UVec4(5) / UVec4(2) + UVec4(m_useSrgbFbo ? 4 : 2); // \note Non-scientific ad hoc formula. Need big threshold when few color bits; blending brings extra inaccuracy.
+
+	bool comparePass = tcu::intThresholdCompare(m_testCtx.getLog(), "CompareResult", "Image Comparison Result",
+												(m_useSrgbFbo ? sRGBATextureLevelToLinear(*m_refColorBuffer) : *m_refColorBuffer).getAccess(),
+												(m_useSrgbFbo ? sRGBATextureLevelToLinear(renderedImg) : renderedImg).getAccess(),
+												compareThreshold, tcu::COMPARE_LOG_RESULT);
+
+	// Fail now if images don't match.
+
+	if (!comparePass)
+	{
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Image compare failed");
+		return STOP;
+	}
+
+	// Continue if param sets still remain in m_paramSets; otherwise stop.
+
+	m_curParamSetNdx++;
+
+	if (m_curParamSetNdx < (int)m_paramSets.size())
+		return CONTINUE;
+	else
+	{
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+		return STOP;
+	}
+}
+
+BlendTests::BlendTests (Context& context)
+	: TestCaseGroup(context, "blend", "Blend tests")
+{
+}
+
+BlendTests::~BlendTests (void)
+{
+}
+
+void BlendTests::init (void)
+{
+	struct EnumGL
+	{
+		GLenum			glValue;
+		const char*		nameStr;
+	};
+
+	static const EnumGL blendEquations[] =
+	{
+		{ GL_FUNC_ADD,					"add"					},
+		{ GL_FUNC_SUBTRACT,				"subtract"				},
+		{ GL_FUNC_REVERSE_SUBTRACT,		"reverse_subtract"		},
+		{ GL_MIN,						"min"					},
+		{ GL_MAX,						"max"					}
+	};
+
+	static const EnumGL blendFunctions[] =
+	{
+		{ GL_ZERO,							"zero"						},
+		{ GL_ONE,							"one"						},
+		{ GL_SRC_COLOR,						"src_color"					},
+		{ GL_ONE_MINUS_SRC_COLOR,			"one_minus_src_color"		},
+		{ GL_DST_COLOR,						"dst_color"					},
+		{ GL_ONE_MINUS_DST_COLOR,			"one_minus_dst_color"		},
+		{ GL_SRC_ALPHA,						"src_alpha"					},
+		{ GL_ONE_MINUS_SRC_ALPHA,			"one_minus_src_alpha"		},
+		{ GL_DST_ALPHA,						"dst_alpha"					},
+		{ GL_ONE_MINUS_DST_ALPHA,			"one_minus_dst_alpha"		},
+		{ GL_CONSTANT_COLOR,				"constant_color"			},
+		{ GL_ONE_MINUS_CONSTANT_COLOR,		"one_minus_constant_color"	},
+		{ GL_CONSTANT_ALPHA,				"constant_alpha"			},
+		{ GL_ONE_MINUS_CONSTANT_ALPHA,		"one_minus_constant_alpha"	},
+		{ GL_SRC_ALPHA_SATURATE,			"src_alpha_saturate"		}
+	};
+
+	const Vec4 defaultBlendColor(0.2f, 0.4f, 0.6f, 0.8f);
+
+	for (int useSrgbFboI = 0; useSrgbFboI <= 1; useSrgbFboI++)
+	{
+		bool			useSrgbFbo	= useSrgbFboI != 0;
+		TestCaseGroup*	fbGroup		= new TestCaseGroup(m_context, useSrgbFbo ? "fbo_srgb" : "default_framebuffer", useSrgbFbo ? "Use a FBO with GL_SRGB8_ALPHA8" : "Use the default framebuffer");
+		addChild(fbGroup);
+
+		// Test all blend equation, src blend function, dst blend function combinations. RGB and alpha modes are the same.
+
+		{
+			TestCaseGroup* group = new TestCaseGroup(m_context, "equation_src_func_dst_func", "Combinations of Blend Equations and Functions");
+			fbGroup->addChild(group);
+
+			for (int equationNdx = 0;	equationNdx < DE_LENGTH_OF_ARRAY(blendEquations);	equationNdx++)
+			for (int srcFuncNdx = 0;	srcFuncNdx < DE_LENGTH_OF_ARRAY(blendFunctions);	srcFuncNdx++)
+			for (int dstFuncNdx = 0;	dstFuncNdx < DE_LENGTH_OF_ARRAY(blendFunctions);	dstFuncNdx++)
+			{
+				const EnumGL& eq	= blendEquations[equationNdx];
+				const EnumGL& src	= blendFunctions[srcFuncNdx];
+				const EnumGL& dst	= blendFunctions[dstFuncNdx];
+
+				if ((eq.glValue == GL_MIN || eq.glValue == GL_MAX) && (srcFuncNdx > 0 || dstFuncNdx > 0)) // MIN and MAX don't depend on factors.
+					continue;
+
+				string name			= eq.nameStr;
+				string description	= string("") +
+									  "Equations "		+ getBlendEquationName(eq.glValue) +
+									  ", src funcs "	+ getBlendFactorName(src.glValue) +
+									  ", dst funcs "	+ getBlendFactorName(dst.glValue);
+
+				if (eq.glValue != GL_MIN && eq.glValue != GL_MAX)
+					name += string("") + "_" + src.nameStr + "_" + dst.nameStr;
+
+				vector<BlendParams> paramSets;
+				paramSets.push_back(BlendParams(eq.glValue, src.glValue, dst.glValue, eq.glValue, src.glValue, dst.glValue, defaultBlendColor));
+
+				group->addChild(new BlendCase(m_context, name.c_str(), description.c_str(), paramSets, useSrgbFbo));
+			}
+		}
+
+		// Test all RGB src, alpha src and RGB dst, alpha dst combinations. Equations are ADD.
+		// \note For all RGB src, alpha src combinations, also test a couple of different RGBA dst functions, and vice versa.
+
+		{
+			TestCaseGroup* mainGroup = new TestCaseGroup(m_context, "rgb_func_alpha_func", "Combinations of RGB and Alpha Functions");
+			fbGroup->addChild(mainGroup);
+			TestCaseGroup* srcGroup = new TestCaseGroup(m_context, "src", "Source functions");
+			TestCaseGroup* dstGroup = new TestCaseGroup(m_context, "dst", "Destination functions");
+			mainGroup->addChild(srcGroup);
+			mainGroup->addChild(dstGroup);
+
+			for (int isDstI = 0;		isDstI <= 1;										isDstI++)
+			for (int rgbFuncNdx = 0;	rgbFuncNdx < DE_LENGTH_OF_ARRAY(blendFunctions);	rgbFuncNdx++)
+			for (int alphaFuncNdx = 0;	alphaFuncNdx < DE_LENGTH_OF_ARRAY(blendFunctions);	alphaFuncNdx++)
+			{
+				bool			isSrc			= isDstI == 0;
+				TestCaseGroup*	curGroup		= isSrc ? srcGroup : dstGroup;
+				const EnumGL&	funcRGB			= blendFunctions[rgbFuncNdx];
+				const EnumGL&	funcAlpha		= blendFunctions[alphaFuncNdx];
+				const char*		dstOrSrcStr		= isSrc ? "src" : "dst";
+
+				string name			= string("") + funcRGB.nameStr + "_" + funcAlpha.nameStr;
+				string description	= string("") +
+									  "RGB "		+ dstOrSrcStr + " func " + getBlendFactorName(funcRGB.glValue) +
+									  ", alpha "	+ dstOrSrcStr + " func " + getBlendFactorName(funcAlpha.glValue);
+
+				// First, make param sets as if this was a src case.
+
+				vector<BlendParams> paramSets;
+				paramSets.push_back(BlendParams(GL_FUNC_ADD, funcRGB.glValue, GL_ONE,			GL_FUNC_ADD, funcAlpha.glValue, GL_ONE,			defaultBlendColor));
+				paramSets.push_back(BlendParams(GL_FUNC_ADD, funcRGB.glValue, GL_ZERO,			GL_FUNC_ADD, funcAlpha.glValue, GL_ZERO,		defaultBlendColor));
+				paramSets.push_back(BlendParams(GL_FUNC_ADD, funcRGB.glValue, GL_SRC_COLOR,		GL_FUNC_ADD, funcAlpha.glValue, GL_SRC_COLOR,	defaultBlendColor));
+				paramSets.push_back(BlendParams(GL_FUNC_ADD, funcRGB.glValue, GL_DST_COLOR,		GL_FUNC_ADD, funcAlpha.glValue, GL_DST_COLOR,	defaultBlendColor));
+
+				// Swap src and dst params if this is a dst case.
+
+				if (!isSrc)
+				{
+					for (int i = 0; i < (int)paramSets.size(); i++)
+					{
+						std::swap(paramSets[i].srcFuncRGB,		paramSets[i].dstFuncRGB);
+						std::swap(paramSets[i].srcFuncAlpha,	paramSets[i].dstFuncAlpha);
+					}
+				}
+
+				curGroup->addChild(new BlendCase(m_context, name.c_str(), description.c_str(), paramSets, useSrgbFbo));
+			}
+		}
+
+		// Test all RGB and alpha equation combinations. Src and dst funcs are ONE for both.
+
+		{
+			TestCaseGroup* group = new TestCaseGroup(m_context, "rgb_equation_alpha_equation", "Combinations of RGB and Alpha Equation Combinations");
+			fbGroup->addChild(group);
+
+			for (int equationRGBNdx = 0;	equationRGBNdx < DE_LENGTH_OF_ARRAY(blendEquations);	equationRGBNdx++)
+			for (int equationAlphaNdx = 0;	equationAlphaNdx < DE_LENGTH_OF_ARRAY(blendEquations);	equationAlphaNdx++)
+			{
+				const EnumGL& eqRGB			= blendEquations[equationRGBNdx];
+				const EnumGL& eqAlpha		= blendEquations[equationAlphaNdx];
+
+				string name			= string("") + eqRGB.nameStr + "_" + eqAlpha.nameStr;
+				string description	= string("") +
+									  "RGB equation "		+ getBlendEquationName(eqRGB.glValue) +
+									  ", alpha equation "	+ getBlendEquationName(eqAlpha.glValue);
+
+				vector<BlendParams> paramSets;
+				paramSets.push_back(BlendParams(eqRGB.glValue, GL_ONE, GL_ONE, eqAlpha.glValue, GL_ONE, GL_ONE, defaultBlendColor));
+
+				group->addChild(new BlendCase(m_context, name.c_str(), description.c_str(), paramSets, useSrgbFbo));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fBlendTests.hpp b/modules/gles3/functional/es3fBlendTests.hpp
new file mode 100644
index 0000000..8831459
--- /dev/null
+++ b/modules/gles3/functional/es3fBlendTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FBLENDTESTS_HPP
+#define _ES3FBLENDTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Blend tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class BlendTests : public TestCaseGroup
+{
+public:
+					BlendTests			(Context& context);
+					~BlendTests			(void);
+
+	void			init				(void);
+
+private:
+					BlendTests			(const BlendTests& other);
+	BlendTests&		operator=			(const BlendTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FBLENDTESTS_HPP
diff --git a/modules/gles3/functional/es3fBooleanStateQueryTests.cpp b/modules/gles3/functional/es3fBooleanStateQueryTests.cpp
new file mode 100644
index 0000000..308bb49
--- /dev/null
+++ b/modules/gles3/functional/es3fBooleanStateQueryTests.cpp
@@ -0,0 +1,828 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Boolean State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fBooleanStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "tcuRenderTarget.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace BooleanStateQueryVerifiers
+{
+
+// StateVerifier
+
+class StateVerifier : protected glu::CallLogWrapper
+{
+public:
+						StateVerifier					(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~StateVerifier					(); // make GCC happy
+
+	const char*			getTestNamePostfix				(void)																																													const;
+
+	virtual void		verifyBoolean					(tcu::TestContext& testCtx, GLenum name, bool reference)																																		= DE_NULL;
+	virtual void		verifyBoolean4					(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)																					= DE_NULL;
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+StateVerifier::StateVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+
+StateVerifier::~StateVerifier ()
+{
+}
+
+const char* StateVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+// IsEnabledVerifier
+
+class IsEnabledVerifier : public StateVerifier
+{
+public:
+			IsEnabledVerifier			(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyBoolean					(tcu::TestContext& testCtx, GLenum name, bool reference);
+	void	verifyBoolean4					(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3);
+};
+
+IsEnabledVerifier::IsEnabledVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_isenabled")
+{
+}
+
+void IsEnabledVerifier::verifyBoolean (tcu::TestContext& testCtx, GLenum name, bool reference)
+{
+	using tcu::TestLog;
+
+	const GLboolean state = glIsEnabled(name);
+	const GLboolean expectedGLState = reference ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << (reference ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void IsEnabledVerifier::verifyBoolean4 (tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)
+{
+	DE_UNREF(testCtx);
+	DE_UNREF(name);
+	DE_UNREF(reference0);
+	DE_UNREF(reference1);
+	DE_UNREF(reference2);
+	DE_UNREF(reference3);
+	DE_ASSERT(false && "not supported");
+}
+
+// GetBooleanVerifier
+
+class GetBooleanVerifier : public StateVerifier
+{
+public:
+			GetBooleanVerifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyBoolean					(tcu::TestContext& testCtx, GLenum name, bool reference);
+	void	verifyBoolean4					(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3);
+};
+
+GetBooleanVerifier::GetBooleanVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getboolean")
+{
+}
+
+void GetBooleanVerifier::verifyBoolean (tcu::TestContext& testCtx, GLenum name, bool reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLboolean expectedGLState = reference ? GL_TRUE : GL_FALSE;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << (reference ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyBoolean4 (tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean[4]> boolVector4;
+	glGetBooleanv(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	const GLboolean referenceAsGLBoolean[] =
+	{
+		reference0 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference1 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference2 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference3 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+	};
+
+	if (boolVector4[0] != referenceAsGLBoolean[0] ||
+		boolVector4[1] != referenceAsGLBoolean[1] ||
+		boolVector4[2] != referenceAsGLBoolean[2] ||
+		boolVector4[3] != referenceAsGLBoolean[3])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (referenceAsGLBoolean[0] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[1] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[2] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[3] ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+//GetIntegerVerifier
+
+class GetIntegerVerifier : public StateVerifier
+{
+public:
+			GetIntegerVerifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyBoolean					(tcu::TestContext& testCtx, GLenum name, bool reference);
+	void	verifyBoolean4					(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3);
+
+};
+
+GetIntegerVerifier::GetIntegerVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger")
+{
+}
+
+void GetIntegerVerifier::verifyBoolean (tcu::TestContext& testCtx, GLenum name, bool reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint expectedGLState = reference ? 1 : 0;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << expectedGLState << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyBoolean4 (tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[4]> boolVector4;
+	glGetIntegerv(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	const GLint referenceAsGLint[] =
+	{
+		reference0 ? 1 : 0,
+		reference1 ? 1 : 0,
+		reference2 ? 1 : 0,
+		reference3 ? 1 : 0,
+	};
+
+	if (boolVector4[0] != referenceAsGLint[0] ||
+		boolVector4[1] != referenceAsGLint[1] ||
+		boolVector4[2] != referenceAsGLint[2] ||
+		boolVector4[3] != referenceAsGLint[3])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< referenceAsGLint[0] << " "
+			<< referenceAsGLint[1] << " "
+			<< referenceAsGLint[2] << " "
+			<< referenceAsGLint[3] << " " << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+//GetInteger64Verifier
+
+class GetInteger64Verifier : public StateVerifier
+{
+public:
+			GetInteger64Verifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyBoolean					(tcu::TestContext& testCtx, GLenum name, bool reference);
+	void	verifyBoolean4					(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3);
+};
+
+GetInteger64Verifier::GetInteger64Verifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger64")
+{
+}
+
+void GetInteger64Verifier::verifyBoolean (tcu::TestContext& testCtx, GLenum name, bool reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint64 expectedGLState = reference ? 1 : 0;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << expectedGLState << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyBoolean4 (tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64[4]> boolVector4;
+	glGetInteger64v(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	const GLint64 referenceAsGLint64[] =
+	{
+		reference0 ? 1 : 0,
+		reference1 ? 1 : 0,
+		reference2 ? 1 : 0,
+		reference3 ? 1 : 0,
+	};
+
+	if (boolVector4[0] != referenceAsGLint64[0] ||
+		boolVector4[1] != referenceAsGLint64[1] ||
+		boolVector4[2] != referenceAsGLint64[2] ||
+		boolVector4[3] != referenceAsGLint64[3])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< referenceAsGLint64[0] << " "
+			<< referenceAsGLint64[1] << " "
+			<< referenceAsGLint64[2] << " "
+			<< referenceAsGLint64[3] << " " << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+//GetFloatVerifier
+
+class GetFloatVerifier : public StateVerifier
+{
+public:
+			GetFloatVerifier			(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyBoolean					(tcu::TestContext& testCtx, GLenum name, bool reference);
+	void	verifyBoolean4					(tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3);
+};
+
+GetFloatVerifier::GetFloatVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getfloat")
+{
+}
+
+void GetFloatVerifier::verifyBoolean (tcu::TestContext& testCtx, GLenum name, bool reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLfloat expectedGLState = reference ? 1.0f : 0.0f;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << expectedGLState << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyBoolean4 (tcu::TestContext& testCtx, GLenum name, bool reference0, bool reference1, bool reference2, bool reference3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[4]> boolVector4;
+	glGetFloatv(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	const GLfloat referenceAsGLfloat[] =
+	{
+		reference0 ? 1.0f : 0.0f,
+		reference1 ? 1.0f : 0.0f,
+		reference2 ? 1.0f : 0.0f,
+		reference3 ? 1.0f : 0.0f,
+	};
+
+	if (boolVector4[0] != referenceAsGLfloat[0] ||
+		boolVector4[1] != referenceAsGLfloat[1] ||
+		boolVector4[2] != referenceAsGLfloat[2] ||
+		boolVector4[3] != referenceAsGLfloat[3])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< referenceAsGLfloat[0] << " "
+			<< referenceAsGLfloat[1] << " "
+			<< referenceAsGLfloat[2] << " "
+			<< referenceAsGLfloat[3] << " " << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+} // BooleanStateQueryVerifiers
+
+namespace
+{
+
+using namespace BooleanStateQueryVerifiers;
+
+static const char* transformFeedbackTestVertSource	=	"#version 300 es\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = vec4(0.0);\n"
+														"}\n\0";
+static const char* transformFeedbackTestFragSource	=	"#version 300 es\n"
+														"layout(location = 0) out mediump vec4 fragColor;"
+														"void main (void)\n"
+														"{\n"
+														"	fragColor = vec4(0.0);\n"
+														"}\n\0";
+
+class IsEnabledStateTestCase : public ApiCase
+{
+public:
+	IsEnabledStateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName, bool initial)
+		: ApiCase		(context, name, description)
+		, m_targetName	(targetName)
+		, m_initial		(initial)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		// check inital value
+		m_verifier->verifyBoolean(m_testCtx, m_targetName, m_initial);
+		expectError(GL_NO_ERROR);
+
+		// check toggle
+
+		glEnable(m_targetName);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, m_targetName, true);
+		expectError(GL_NO_ERROR);
+
+		glDisable(m_targetName);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, m_targetName, false);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	GLenum			m_targetName;
+	bool			m_initial;
+	StateVerifier*	m_verifier;
+};
+
+class DepthWriteMaskTestCase : public ApiCase
+{
+public:
+	DepthWriteMaskTestCase	(Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyBoolean(m_testCtx, GL_DEPTH_WRITEMASK, true);
+		expectError(GL_NO_ERROR);
+
+		glDepthMask(GL_FALSE);
+		m_verifier->verifyBoolean(m_testCtx, GL_DEPTH_WRITEMASK, false);
+		expectError(GL_NO_ERROR);
+
+		glDepthMask(GL_TRUE);
+		m_verifier->verifyBoolean(m_testCtx, GL_DEPTH_WRITEMASK, true);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class SampleCoverageInvertTestCase : public ApiCase
+{
+public:
+	SampleCoverageInvertTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyBoolean(m_testCtx, GL_SAMPLE_COVERAGE_INVERT, false);
+		expectError(GL_NO_ERROR);
+
+		glSampleCoverage(1.0f, GL_TRUE);
+		m_verifier->verifyBoolean(m_testCtx, GL_SAMPLE_COVERAGE_INVERT, true);
+		expectError(GL_NO_ERROR);
+
+		glSampleCoverage(1.0f, GL_FALSE);
+		m_verifier->verifyBoolean(m_testCtx, GL_SAMPLE_COVERAGE_INVERT, false);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class InitialBooleanTestCase : public ApiCase
+{
+public:
+	InitialBooleanTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum target, bool reference)
+		: ApiCase		(context, name, description)
+		, m_target		(target)
+		, m_reference	(reference)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyBoolean(m_testCtx, m_target, m_reference);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	GLenum			m_target;
+	bool			m_reference;
+	StateVerifier*	m_verifier;
+};
+
+class ColorMaskTestCase : public ApiCase
+{
+public:
+	ColorMaskTestCase	(Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+	void test (void)
+	{
+		m_verifier->verifyBoolean4(m_testCtx, GL_COLOR_WRITEMASK, true, true, true, true);
+		expectError(GL_NO_ERROR);
+
+		const struct ColorMask
+		{
+			GLboolean r, g, b, a;
+		} testMasks[] =
+		{
+			{ GL_TRUE,	GL_TRUE,	GL_TRUE,	GL_TRUE  },
+			{ GL_TRUE,	GL_TRUE,	GL_TRUE,	GL_FALSE },
+			{ GL_TRUE,	GL_TRUE,	GL_FALSE,	GL_TRUE  },
+			{ GL_TRUE,	GL_TRUE,	GL_FALSE,	GL_FALSE },
+			{ GL_TRUE,	GL_FALSE,	GL_TRUE,	GL_TRUE  },
+			{ GL_TRUE,	GL_FALSE,	GL_TRUE,	GL_FALSE },
+			{ GL_TRUE,	GL_FALSE,	GL_FALSE,	GL_TRUE  },
+			{ GL_TRUE,	GL_FALSE,	GL_FALSE,	GL_FALSE },
+			{ GL_FALSE,	GL_TRUE,	GL_TRUE,	GL_TRUE  },
+			{ GL_FALSE,	GL_TRUE,	GL_TRUE,	GL_FALSE },
+			{ GL_FALSE,	GL_TRUE,	GL_FALSE,	GL_TRUE  },
+			{ GL_FALSE,	GL_TRUE,	GL_FALSE,	GL_FALSE },
+			{ GL_FALSE,	GL_FALSE,	GL_TRUE,	GL_TRUE  },
+			{ GL_FALSE,	GL_FALSE,	GL_TRUE,	GL_FALSE },
+			{ GL_FALSE,	GL_FALSE,	GL_FALSE,	GL_TRUE  },
+			{ GL_FALSE,	GL_FALSE,	GL_FALSE,	GL_FALSE },
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(testMasks); ndx++)
+		{
+			glColorMask(testMasks[ndx].r, testMasks[ndx].g, testMasks[ndx].b, testMasks[ndx].a);
+			m_verifier->verifyBoolean4(m_testCtx, GL_COLOR_WRITEMASK, testMasks[ndx].r==GL_TRUE, testMasks[ndx].g==GL_TRUE, testMasks[ndx].b==GL_TRUE, testMasks[ndx].a==GL_TRUE);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+
+class TransformFeedbackTestCase : public ApiCase
+{
+public:
+	TransformFeedbackTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase				(context, name, description)
+		, m_verifier			(verifier)
+		, m_transformfeedback	(0)
+	{
+	}
+
+	void test (void)
+	{
+		glGenTransformFeedbacks(1, &m_transformfeedback);
+		expectError(GL_NO_ERROR);
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		glShaderSource(shaderVert, 1, &transformFeedbackTestVertSource, DE_NULL);
+		glCompileShader(shaderVert);
+		expectError(GL_NO_ERROR);
+		GLint compileStatus;
+		glGetShaderiv(shaderVert, GL_COMPILE_STATUS, &compileStatus);
+		checkBooleans(compileStatus, GL_TRUE);
+
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+		glShaderSource(shaderFrag, 1, &transformFeedbackTestFragSource, DE_NULL);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+		glGetShaderiv(shaderFrag, GL_COMPILE_STATUS, &compileStatus);
+		checkBooleans(compileStatus, GL_TRUE);
+
+		GLuint shaderProg = glCreateProgram();
+		glAttachShader(shaderProg, shaderVert);
+		glAttachShader(shaderProg, shaderFrag);
+		const char* transform_feedback_outputs = "gl_Position";
+		glTransformFeedbackVaryings(shaderProg, 1, &transform_feedback_outputs, GL_INTERLEAVED_ATTRIBS);
+		glLinkProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+		GLint linkStatus;
+		glGetProgramiv(shaderProg, GL_LINK_STATUS, &linkStatus);
+		checkBooleans(linkStatus, GL_TRUE);
+
+		glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_transformfeedback);
+		expectError(GL_NO_ERROR);
+
+		GLuint feedbackBufferId;
+		glGenBuffers(1, &feedbackBufferId);
+		glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackBufferId);
+		glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_DYNAMIC_READ);
+		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, feedbackBufferId);
+		expectError(GL_NO_ERROR);
+
+		glUseProgram(shaderProg);
+
+		testTransformFeedback();
+
+		glUseProgram(0);
+		glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+		glDeleteTransformFeedbacks(1, &m_transformfeedback);
+		glDeleteBuffers(1, &feedbackBufferId);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+	}
+
+	virtual void testTransformFeedback (void) = DE_NULL;
+
+protected:
+	StateVerifier*	m_verifier;
+	GLuint			m_transformfeedback;
+};
+
+class TransformFeedbackBasicTestCase : public TransformFeedbackTestCase
+{
+public:
+	TransformFeedbackBasicTestCase (Context& context, StateVerifier* verifier, const char* name)
+		: TransformFeedbackTestCase	(context, verifier, name, "Test TRANSFORM_FEEDBACK_ACTIVE and TRANSFORM_FEEDBACK_PAUSED")
+	{
+	}
+
+	void testTransformFeedback (void)
+	{
+		glBeginTransformFeedback(GL_POINTS);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_ACTIVE, true);
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_PAUSED, false);
+		expectError(GL_NO_ERROR);
+
+		glPauseTransformFeedback();
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_ACTIVE, true);
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_PAUSED, true);
+		expectError(GL_NO_ERROR);
+
+		glResumeTransformFeedback();
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_ACTIVE, true);
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_PAUSED, false);
+		expectError(GL_NO_ERROR);
+
+		glEndTransformFeedback();
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_ACTIVE, false);
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_PAUSED, false);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class TransformFeedbackImplicitResumeTestCase : public TransformFeedbackTestCase
+{
+public:
+	TransformFeedbackImplicitResumeTestCase (Context& context, StateVerifier* verifier, const char* name)
+		: TransformFeedbackTestCase(context, verifier, name, "EndTransformFeedback performs an implicit ResumeTransformFeedback.")
+	{
+	}
+
+	void testTransformFeedback (void)
+	{
+		glBeginTransformFeedback(GL_POINTS);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_ACTIVE, true);
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_PAUSED, false);
+		expectError(GL_NO_ERROR);
+
+		glPauseTransformFeedback();
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_ACTIVE, true);
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_PAUSED, true);
+		expectError(GL_NO_ERROR);
+
+		glEndTransformFeedback();
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_ACTIVE, false);
+		m_verifier->verifyBoolean(m_testCtx, GL_TRANSFORM_FEEDBACK_PAUSED, false);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		StateVerifier* verifier = VERIFIERS[_verifierNdx];										\
+		CODE_BLOCK;																				\
+	}
+
+} // anonymous
+
+BooleanStateQueryTests::BooleanStateQueryTests (Context& context)
+	: TestCaseGroup				(context, "boolean", "Boolean State Query tests")
+	, m_verifierIsEnabled		(DE_NULL)
+	, m_verifierBoolean			(DE_NULL)
+	, m_verifierInteger			(DE_NULL)
+	, m_verifierInteger64		(DE_NULL)
+	, m_verifierFloat			(DE_NULL)
+{
+}
+
+BooleanStateQueryTests::~BooleanStateQueryTests (void)
+{
+	deinit();
+}
+
+void BooleanStateQueryTests::init (void)
+{
+	DE_ASSERT(m_verifierIsEnabled == DE_NULL);
+	DE_ASSERT(m_verifierBoolean == DE_NULL);
+	DE_ASSERT(m_verifierInteger == DE_NULL);
+	DE_ASSERT(m_verifierInteger64 == DE_NULL);
+	DE_ASSERT(m_verifierFloat == DE_NULL);
+
+	m_verifierIsEnabled		= new IsEnabledVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierBoolean		= new GetBooleanVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInteger		= new GetIntegerVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInteger64		= new GetInteger64Verifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierFloat			= new GetFloatVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+
+	StateVerifier* isEnabledVerifiers[] = {m_verifierIsEnabled, m_verifierBoolean, m_verifierInteger, m_verifierInteger64, m_verifierFloat};
+	StateVerifier* normalVerifiers[] = {m_verifierBoolean, m_verifierInteger, m_verifierInteger64, m_verifierFloat};
+
+	struct StateBoolean
+	{
+		const char*		name;
+		const char*		description;
+		GLenum			targetName;
+		bool			value;
+	};
+	const StateBoolean isEnableds[] =
+	{
+		{ "primitive_restart_fixed_index",	"PRIMITIVE_RESTART_FIXED_INDEX",	GL_PRIMITIVE_RESTART_FIXED_INDEX,	false},
+		{ "rasterizer_discard",				"RASTERIZER_DISCARD",				GL_RASTERIZER_DISCARD,				false},
+		{ "cull_face",						"CULL_FACE",						GL_CULL_FACE,						false},
+		{ "polygon_offset_fill",			"POLYGON_OFFSET_FILL",				GL_POLYGON_OFFSET_FILL,				false},
+		{ "sample_alpha_to_coverage",		"SAMPLE_ALPHA_TO_COVERAGE",			GL_SAMPLE_ALPHA_TO_COVERAGE,		false},
+		{ "sample_coverage",				"SAMPLE_COVERAGE",					GL_SAMPLE_COVERAGE,					false},
+		{ "scissor_test",					"SCISSOR_TEST",						GL_SCISSOR_TEST,					false},
+		{ "stencil_test",					"STENCIL_TEST",						GL_STENCIL_TEST,					false},
+		{ "depth_test",						"DEPTH_TEST",						GL_DEPTH_TEST,						false},
+		{ "blend",							"BLEND",							GL_BLEND,							false},
+		{ "dither",							"DITHER",							GL_DITHER,							true },
+	};
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(isEnableds); testNdx++)
+	{
+		FOR_EACH_VERIFIER(isEnabledVerifiers, addChild(new IsEnabledStateTestCase(m_context, verifier, (std::string(isEnableds[testNdx].name) + verifier->getTestNamePostfix()).c_str(), isEnableds[testNdx].description, isEnableds[testNdx].targetName, isEnableds[testNdx].value)));
+	}
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ColorMaskTestCase							(m_context, verifier, (std::string("color_writemask")						+ verifier->getTestNamePostfix()).c_str(), "COLOR_WRITEMASK")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new DepthWriteMaskTestCase						(m_context, verifier, (std::string("depth_writemask")						+ verifier->getTestNamePostfix()).c_str(), "DEPTH_WRITEMASK")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new SampleCoverageInvertTestCase				(m_context, verifier, (std::string("sample_coverage_invert")				+ verifier->getTestNamePostfix()).c_str(), "SAMPLE_COVERAGE_INVERT")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new InitialBooleanTestCase						(m_context, verifier, (std::string("shader_compiler")						+ verifier->getTestNamePostfix()).c_str(), "SHADER_COMPILER",						GL_SHADER_COMPILER, true)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new InitialBooleanTestCase						(m_context, verifier, (std::string("transform_feedback_active_initial")		+ verifier->getTestNamePostfix()).c_str(), "initial TRANSFORM_FEEDBACK_ACTIVE",		GL_TRANSFORM_FEEDBACK_ACTIVE, false)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new InitialBooleanTestCase						(m_context, verifier, (std::string("transform_feedback_paused_initial")		+ verifier->getTestNamePostfix()).c_str(), "initial TRANSFORM_FEEDBACK_PAUSED",		GL_TRANSFORM_FEEDBACK_PAUSED, false)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new TransformFeedbackBasicTestCase				(m_context, verifier, (std::string("transform_feedback")					+ verifier->getTestNamePostfix()).c_str())));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new TransformFeedbackImplicitResumeTestCase		(m_context, verifier, (std::string("transform_feedback_implicit_resume")	+ verifier->getTestNamePostfix()).c_str())));
+}
+
+void BooleanStateQueryTests::deinit (void)
+{
+	if (m_verifierIsEnabled)
+	{
+		delete m_verifierIsEnabled;
+		m_verifierIsEnabled = DE_NULL;
+	}
+	if (m_verifierBoolean)
+	{
+		delete m_verifierBoolean;
+		m_verifierBoolean = DE_NULL;
+	}
+	if (m_verifierInteger)
+	{
+		delete m_verifierInteger;
+		m_verifierInteger = DE_NULL;
+	}
+	if (m_verifierInteger64)
+	{
+		delete m_verifierInteger64;
+		m_verifierInteger64 = DE_NULL;
+	}
+	if (m_verifierFloat)
+	{
+		delete m_verifierFloat;
+		m_verifierFloat = DE_NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fBooleanStateQueryTests.hpp b/modules/gles3/functional/es3fBooleanStateQueryTests.hpp
new file mode 100644
index 0000000..8f69c8d
--- /dev/null
+++ b/modules/gles3/functional/es3fBooleanStateQueryTests.hpp
@@ -0,0 +1,70 @@
+#ifndef _ES3FBOOLEANSTATEQUERYTESTS_HPP
+#define _ES3FBOOLEANSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Boolean State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace BooleanStateQueryVerifiers
+{
+
+class IsEnabledVerifier;
+class GetBooleanVerifier;
+class GetIntegerVerifier;
+class GetInteger64Verifier;
+class GetFloatVerifier;
+
+} // BooleanStateQueryVerifiers
+
+class BooleanStateQueryTests : public TestCaseGroup
+{
+public:
+																BooleanStateQueryTests	(Context& context);
+																~BooleanStateQueryTests	(void);
+
+	void														init					(void);
+	void														deinit					(void);
+
+private:
+																BooleanStateQueryTests	(const BooleanStateQueryTests& other);
+	BooleanStateQueryTests&										operator=				(const BooleanStateQueryTests& other);
+
+	BooleanStateQueryVerifiers::IsEnabledVerifier*				m_verifierIsEnabled;
+	BooleanStateQueryVerifiers::GetBooleanVerifier*				m_verifierBoolean;
+	BooleanStateQueryVerifiers::GetIntegerVerifier*				m_verifierInteger;
+	BooleanStateQueryVerifiers::GetInteger64Verifier*			m_verifierInteger64;
+	BooleanStateQueryVerifiers::GetFloatVerifier*				m_verifierFloat;
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FBOOLEANSTATEQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fBufferCopyTests.cpp b/modules/gles3/functional/es3fBufferCopyTests.cpp
new file mode 100644
index 0000000..7532894
--- /dev/null
+++ b/modules/gles3/functional/es3fBufferCopyTests.cpp
@@ -0,0 +1,343 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer copying tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fBufferCopyTests.hpp"
+#include "glsBufferTestUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "deMemory.h"
+#include "deString.h"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <algorithm>
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using namespace gls::BufferTestUtil;
+
+class BasicBufferCopyCase : public BufferCase
+{
+public:
+	BasicBufferCopyCase (Context&		context,
+						 const char*	name,
+						 const char*	desc,
+						 deUint32		srcTarget,
+						 int			srcSize,
+						 deUint32		srcHint,
+						 deUint32		dstTarget,
+						 int			dstSize,
+						 deUint32		dstHint,
+						 int			copySrcOffset,
+						 int			copyDstOffset,
+						 int			copySize,
+						 VerifyType		verifyType)
+		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_srcTarget		(srcTarget)
+		, m_srcSize			(srcSize)
+		, m_srcHint			(srcHint)
+		, m_dstTarget		(dstTarget)
+		, m_dstSize			(dstSize)
+		, m_dstHint			(dstHint)
+		, m_copySrcOffset	(copySrcOffset)
+		, m_copyDstOffset	(copyDstOffset)
+		, m_copySize		(copySize)
+		, m_verifyType		(verifyType)
+	{
+		DE_ASSERT(de::inBounds(m_copySrcOffset, 0, m_srcSize) && de::inRange(m_copySrcOffset+m_copySize, m_copySrcOffset, m_srcSize));
+		DE_ASSERT(de::inBounds(m_copyDstOffset, 0, m_dstSize) && de::inRange(m_copyDstOffset+m_copySize, m_copyDstOffset, m_dstSize));
+	}
+
+	IterateResult iterate (void)
+	{
+		BufferVerifier	verifier	(m_renderCtx, m_testCtx.getLog(), m_verifyType);
+		ReferenceBuffer	srcRef;
+		ReferenceBuffer	dstRef;
+		deUint32		srcBuf		= 0;
+		deUint32		dstBuf		= 0;
+		deUint32		srcSeed		= deStringHash(getName()) ^ 0xabcd;
+		deUint32		dstSeed		= deStringHash(getName()) ^ 0xef01;
+		bool			isOk		= true;
+
+		srcRef.setSize(m_srcSize);
+		fillWithRandomBytes(srcRef.getPtr(), m_srcSize, srcSeed);
+
+		dstRef.setSize(m_dstSize);
+		fillWithRandomBytes(dstRef.getPtr(), m_dstSize, dstSeed);
+
+		// Create source buffer and fill with data.
+		srcBuf = genBuffer();
+		glBindBuffer(m_srcTarget, srcBuf);
+		glBufferData(m_srcTarget, m_srcSize, srcRef.getPtr(), m_srcHint);
+		GLU_CHECK_MSG("glBufferData");
+
+		// Create destination buffer and fill with data.
+		dstBuf = genBuffer();
+		glBindBuffer(m_dstTarget, dstBuf);
+		glBufferData(m_dstTarget, m_dstSize, dstRef.getPtr(), m_dstHint);
+		GLU_CHECK_MSG("glBufferData");
+
+		// Verify both buffers before executing copy.
+		isOk = verifier.verify(srcBuf, srcRef.getPtr(), 0, m_srcSize, m_srcTarget) && isOk;
+		isOk = verifier.verify(dstBuf, dstRef.getPtr(), 0, m_dstSize, m_dstTarget) && isOk;
+
+		// Execute copy.
+		deMemcpy(dstRef.getPtr()+m_copyDstOffset, srcRef.getPtr()+m_copySrcOffset, m_copySize);
+
+		glBindBuffer(m_srcTarget, srcBuf);
+		glBindBuffer(m_dstTarget, dstBuf);
+		glCopyBufferSubData(m_srcTarget, m_dstTarget, m_copySrcOffset, m_copyDstOffset, m_copySize);
+		GLU_CHECK_MSG("glCopyBufferSubData");
+
+		// Verify both buffers after copy.
+		isOk = verifier.verify(srcBuf, srcRef.getPtr(), 0, m_srcSize, m_srcTarget) && isOk;
+		isOk = verifier.verify(dstBuf, dstRef.getPtr(), 0, m_dstSize, m_dstTarget) && isOk;
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32	m_srcTarget;
+	int			m_srcSize;
+	deUint32	m_srcHint;
+
+	deUint32	m_dstTarget;
+	int			m_dstSize;
+	deUint32	m_dstHint;
+
+	int			m_copySrcOffset;
+	int			m_copyDstOffset;
+	int			m_copySize;
+
+	VerifyType	m_verifyType;
+};
+
+// Case B: same buffer, take range as parameter
+
+class SingleBufferCopyCase : public BufferCase
+{
+public:
+	SingleBufferCopyCase (Context&		context,
+						  const char*	name,
+						  const char*	desc,
+						  deUint32		srcTarget,
+						  deUint32		dstTarget,
+						  deUint32		hint,
+						  VerifyType	verifyType)
+		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_srcTarget		(srcTarget)
+		, m_dstTarget		(dstTarget)
+		, m_hint			(hint)
+		, m_verifyType		(verifyType)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const int		size		= 1000;
+		BufferVerifier	verifier	(m_renderCtx, m_testCtx.getLog(), m_verifyType);
+		ReferenceBuffer	ref;
+		deUint32		buf			= 0;
+		deUint32		baseSeed	= deStringHash(getName());
+		bool			isOk		= true;
+
+		ref.setSize(size);
+
+		// Create buffer.
+		buf = genBuffer();
+		glBindBuffer(m_srcTarget, buf);
+
+		static const struct
+		{
+			int				srcOffset;
+			int				dstOffset;
+			int				copySize;
+		} copyRanges[] =
+		{
+			{ 57,		701,	101 },	// Non-adjecent, from low to high.
+			{ 640,		101,	101 },	// Non-adjecent, from high to low.
+			{ 0,		500,	500 },	// Lower half to upper half.
+			{ 500,		0,		500 }	// Upper half to lower half.
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(copyRanges) && isOk; ndx++)
+		{
+			int	srcOffset	= copyRanges[ndx].srcOffset;
+			int	dstOffset	= copyRanges[ndx].dstOffset;
+			int	copySize	= copyRanges[ndx].copySize;
+
+			fillWithRandomBytes(ref.getPtr(), size, baseSeed ^ deInt32Hash(ndx));
+
+			// Fill with data.
+			glBindBuffer(m_srcTarget, buf);
+			glBufferData(m_srcTarget, size, ref.getPtr(), m_hint);
+			GLU_CHECK_MSG("glBufferData");
+
+			// Execute copy.
+			deMemcpy(ref.getPtr()+dstOffset, ref.getPtr()+srcOffset, copySize);
+
+			glBindBuffer(m_dstTarget, buf);
+			glCopyBufferSubData(m_srcTarget, m_dstTarget, srcOffset, dstOffset, copySize);
+			GLU_CHECK_MSG("glCopyBufferSubData");
+
+			// Verify buffer after copy.
+			isOk = verifier.verify(buf, ref.getPtr(), 0, size, m_dstTarget) && isOk;
+		}
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32	m_srcTarget;
+	deUint32	m_dstTarget;
+	deUint32	m_hint;
+
+	VerifyType	m_verifyType;
+};
+
+BufferCopyTests::BufferCopyTests (Context& context)
+	: TestCaseGroup(context, "copy", "Buffer copy tests")
+{
+}
+
+BufferCopyTests::~BufferCopyTests (void)
+{
+}
+
+void BufferCopyTests::init (void)
+{
+	static const deUint32 bufferTargets[] =
+	{
+		GL_ARRAY_BUFFER,
+		GL_COPY_READ_BUFFER,
+		GL_COPY_WRITE_BUFFER,
+		GL_ELEMENT_ARRAY_BUFFER,
+		GL_PIXEL_PACK_BUFFER,
+		GL_PIXEL_UNPACK_BUFFER,
+		GL_TRANSFORM_FEEDBACK_BUFFER,
+		GL_UNIFORM_BUFFER
+	};
+
+	// .basic
+	{
+		tcu::TestCaseGroup* basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic buffer copy cases");
+		addChild(basicGroup);
+
+		for (int srcTargetNdx = 0; srcTargetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); srcTargetNdx++)
+		{
+			for (int dstTargetNdx = 0; dstTargetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); dstTargetNdx++)
+			{
+				if (srcTargetNdx == dstTargetNdx)
+					continue;
+
+				deUint32		srcTarget		= bufferTargets[srcTargetNdx];
+				deUint32		dstTarget		= bufferTargets[dstTargetNdx];
+				const int		size			= 1017;
+				const deUint32	hint			= GL_STATIC_DRAW;
+				VerifyType		verify			= VERIFY_AS_VERTEX_ARRAY;
+				string			name			= string(getBufferTargetName(srcTarget)) + "_" + getBufferTargetName(dstTarget);
+
+				basicGroup->addChild(new BasicBufferCopyCase(m_context, name.c_str(), "", srcTarget, size, hint, dstTarget, size, hint, 0, 0, size, verify));
+			}
+		}
+	}
+
+	// .subrange
+	{
+		tcu::TestCaseGroup* subrangeGroup = new tcu::TestCaseGroup(m_testCtx, "subrange", "Buffer subrange copy tests");
+		addChild(subrangeGroup);
+
+		static const struct
+		{
+			const char*		name;
+			int				srcSize;
+			int				dstSize;
+			int				srcOffset;
+			int				dstOffset;
+			int				copySize;
+		} cases[] =
+		{
+			//						srcSize		dstSize		srcOffs		dstOffs		copySize
+			{ "middle",				1000,		1000,		250,		250,		500 },
+			{ "small_to_large",		100,		1000,		0,			409,		100 },
+			{ "large_to_small",		1000,		100,		409,		0,			100 },
+			{ "low_to_high_1",		1000,		1000,		0,			500,		500 },
+			{ "low_to_high_2",		997,		1027,		0,			701,		111 },
+			{ "high_to_low_1",		1000,		1000,		500,		0,			500 },
+			{ "high_to_low_2",		1027,		997,		701,		17,			111 }
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
+		{
+			deUint32		srcTarget		= GL_COPY_READ_BUFFER;
+			deUint32		dstTarget		= GL_COPY_WRITE_BUFFER;
+			deUint32		hint			= GL_STATIC_DRAW;
+			VerifyType		verify			= VERIFY_AS_VERTEX_ARRAY;
+
+			subrangeGroup->addChild(new BasicBufferCopyCase(m_context, cases[ndx].name, "",
+															srcTarget, cases[ndx].srcSize, hint,
+															dstTarget, cases[ndx].dstSize, hint,
+															cases[ndx].srcOffset, cases[ndx].dstOffset, cases[ndx].copySize,
+															verify));
+		}
+	}
+
+	// .single_buffer
+	{
+		tcu::TestCaseGroup* singleBufGroup = new tcu::TestCaseGroup(m_testCtx, "single_buffer", "Copies within single buffer");
+		addChild(singleBufGroup);
+
+		for (int srcTargetNdx = 0; srcTargetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); srcTargetNdx++)
+		{
+			for (int dstTargetNdx = 0; dstTargetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); dstTargetNdx++)
+			{
+				if (srcTargetNdx == dstTargetNdx)
+					continue;
+
+				deUint32		srcTarget		= bufferTargets[srcTargetNdx];
+				deUint32		dstTarget		= bufferTargets[dstTargetNdx];
+				const deUint32	hint			= GL_STATIC_DRAW;
+				VerifyType		verify			= VERIFY_AS_VERTEX_ARRAY;
+				string			name			= string(getBufferTargetName(srcTarget)) + "_" + getBufferTargetName(dstTarget);
+
+				singleBufGroup->addChild(new SingleBufferCopyCase(m_context, name.c_str(), "", srcTarget, dstTarget, hint, verify));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fBufferCopyTests.hpp b/modules/gles3/functional/es3fBufferCopyTests.hpp
new file mode 100644
index 0000000..93c0247
--- /dev/null
+++ b/modules/gles3/functional/es3fBufferCopyTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FBUFFERCOPYTESTS_HPP
+#define _ES3FBUFFERCOPYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer copying tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class BufferCopyTests : public TestCaseGroup
+{
+public:
+						BufferCopyTests		(Context& context);
+						~BufferCopyTests	(void);
+
+	void				init				(void);
+
+private:
+						BufferCopyTests		(const BufferCopyTests& other);
+	BufferCopyTests&	operator=			(const BufferCopyTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FBUFFERCOPYTESTS_HPP
diff --git a/modules/gles3/functional/es3fBufferMapTests.cpp b/modules/gles3/functional/es3fBufferMapTests.cpp
new file mode 100644
index 0000000..70b2006
--- /dev/null
+++ b/modules/gles3/functional/es3fBufferMapTests.cpp
@@ -0,0 +1,797 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer map tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fBufferMapTests.hpp"
+#include "glsBufferTestUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "deMemory.h"
+#include "deString.h"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <algorithm>
+
+using std::set;
+using std::vector;
+using std::string;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using namespace gls::BufferTestUtil;
+
+// Test cases.
+
+class BufferMapReadCase : public BufferCase
+{
+public:
+	BufferMapReadCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, int bufferSize, int mapOffset, int mapSize, WriteType write)
+		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_bufferTarget	(bufferTarget)
+		, m_usage			(usage)
+		, m_bufferSize		(bufferSize)
+		, m_mapOffset		(mapOffset)
+		, m_mapSize			(mapSize)
+		, m_write			(write)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		deUint32		dataSeed	= deStringHash(getName());
+		ReferenceBuffer	refBuf;
+		BufferWriter	writer		(m_renderCtx, m_testCtx.getLog(), m_write);
+		bool			isOk		= false;
+
+		// Setup reference data.
+		refBuf.setSize(m_bufferSize);
+		fillWithRandomBytes(refBuf.getPtr(), m_bufferSize, dataSeed);
+
+		deUint32 buf = genBuffer();
+		glBindBuffer(m_bufferTarget, buf);
+		glBufferData(m_bufferTarget, m_bufferSize, DE_NULL, m_usage);
+		writer.write(buf, 0, m_bufferSize, refBuf.getPtr(), m_bufferTarget);
+
+		glBindBuffer(m_bufferTarget, buf);
+		void* ptr = glMapBufferRange(m_bufferTarget, m_mapOffset, m_mapSize, GL_MAP_READ_BIT);
+		GLU_CHECK_MSG("glMapBufferRange");
+		TCU_CHECK(ptr);
+
+		isOk = compareByteArrays(log, (const deUint8*)ptr, refBuf.getPtr(m_mapOffset), m_mapSize);
+
+		glUnmapBuffer(m_bufferTarget);
+		GLU_CHECK_MSG("glUnmapBuffer");
+
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_bufferTarget;
+	deUint32		m_usage;
+	int				m_bufferSize;
+	int				m_mapOffset;
+	int				m_mapSize;
+	WriteType		m_write;
+};
+
+class BufferMapWriteCase : public BufferCase
+{
+public:
+	BufferMapWriteCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, int size, VerifyType verify)
+		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_bufferTarget	(bufferTarget)
+		, m_usage			(usage)
+		, m_size			(size)
+		, m_verify			(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		deUint32		dataSeed	= deStringHash(getName());
+		ReferenceBuffer	refBuf;
+		BufferVerifier	verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
+
+		// Setup reference data.
+		refBuf.setSize(m_size);
+		fillWithRandomBytes(refBuf.getPtr(), m_size, dataSeed);
+
+		deUint32 buf = genBuffer();
+		glBindBuffer(m_bufferTarget, buf);
+		glBufferData(m_bufferTarget, m_size, DE_NULL, m_usage);
+
+		void* ptr = glMapBufferRange(m_bufferTarget, 0, m_size, GL_MAP_WRITE_BIT);
+		GLU_CHECK_MSG("glMapBufferRange");
+		TCU_CHECK(ptr);
+
+		fillWithRandomBytes((deUint8*)ptr, m_size, dataSeed);
+		glUnmapBuffer(m_bufferTarget);
+		GLU_CHECK_MSG("glUnmapBuffer");
+
+		bool isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_size, m_bufferTarget);
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_bufferTarget;
+	deUint32		m_usage;
+	int				m_size;
+	VerifyType		m_verify;
+};
+
+class BufferPartialMapWriteCase : public BufferCase
+{
+public:
+	BufferPartialMapWriteCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, int bufferSize, int mapOffset, int mapSize, VerifyType verify)
+		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_bufferTarget	(bufferTarget)
+		, m_usage			(usage)
+		, m_bufferSize		(bufferSize)
+		, m_mapOffset		(mapOffset)
+		, m_mapSize			(mapSize)
+		, m_verify			(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		deUint32		dataSeed	= deStringHash(getName());
+		ReferenceBuffer	refBuf;
+		BufferVerifier	verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
+
+		// Setup reference data.
+		refBuf.setSize(m_bufferSize);
+		fillWithRandomBytes(refBuf.getPtr(), m_bufferSize, dataSeed);
+
+		deUint32 buf = genBuffer();
+		glBindBuffer(m_bufferTarget, buf);
+		glBufferData(m_bufferTarget, m_bufferSize, refBuf.getPtr(), m_usage);
+
+		// Do reference map.
+		fillWithRandomBytes(refBuf.getPtr(m_mapOffset), m_mapSize, dataSeed&0xabcdef);
+
+		void* ptr = glMapBufferRange(m_bufferTarget, m_mapOffset, m_mapSize, GL_MAP_WRITE_BIT);
+		GLU_CHECK_MSG("glMapBufferRange");
+		TCU_CHECK(ptr);
+
+		deMemcpy(ptr, refBuf.getPtr(m_mapOffset), m_mapSize);
+		glUnmapBuffer(m_bufferTarget);
+		GLU_CHECK_MSG("glUnmapBuffer");
+
+		bool isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_bufferSize, m_bufferTarget);
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_bufferTarget;
+	deUint32		m_usage;
+	int				m_bufferSize;
+	int				m_mapOffset;
+	int				m_mapSize;
+	VerifyType		m_verify;
+};
+
+class BufferMapInvalidateCase : public BufferCase
+{
+public:
+	BufferMapInvalidateCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, bool partialWrite, VerifyType verify)
+		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_bufferTarget	(bufferTarget)
+		, m_usage			(usage)
+		, m_partialWrite	(partialWrite)
+		, m_verify			(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		deUint32		dataSeed		= deStringHash(getName());
+		deUint32		buf				= 0;
+		ReferenceBuffer	refBuf;
+		BufferVerifier	verifier		(m_renderCtx, m_testCtx.getLog(), m_verify);
+		const int		bufferSize		= 1300;
+		const int		mapOffset		= 200;
+		const int		mapSize			= 1011;
+		const int		mapWriteOffs	= m_partialWrite ? 91 : 0;
+		const int		verifyOffset	= mapOffset+mapWriteOffs;
+		const int		verifySize		= mapSize-mapWriteOffs;
+
+		// Setup reference data.
+		refBuf.setSize(bufferSize);
+		fillWithRandomBytes(refBuf.getPtr(), bufferSize, dataSeed);
+
+		buf = genBuffer();
+		glBindBuffer(m_bufferTarget, buf);
+		glBufferData(m_bufferTarget, bufferSize, refBuf.getPtr(), m_usage);
+
+		// Do reference map.
+		fillWithRandomBytes(refBuf.getPtr(mapOffset+mapWriteOffs), mapSize-mapWriteOffs, dataSeed&0xabcdef);
+
+		void* ptr = glMapBufferRange(m_bufferTarget, mapOffset, mapSize, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_BUFFER_BIT);
+		GLU_CHECK_MSG("glMapBufferRange");
+		TCU_CHECK(ptr);
+
+		deMemcpy((deUint8*)ptr+mapWriteOffs, refBuf.getPtr(mapOffset+mapWriteOffs), mapSize-mapWriteOffs);
+		glUnmapBuffer(m_bufferTarget);
+		GLU_CHECK_MSG("glUnmapBuffer");
+
+		bool isOk = verifier.verify(buf, refBuf.getPtr(), verifyOffset, verifySize, m_bufferTarget);
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_bufferTarget;
+	deUint32		m_usage;
+	bool			m_partialWrite;
+	VerifyType		m_verify;
+};
+
+class BufferMapPartialInvalidateCase : public BufferCase
+{
+public:
+	BufferMapPartialInvalidateCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, bool partialWrite, VerifyType verify)
+		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_bufferTarget	(bufferTarget)
+		, m_usage			(usage)
+		, m_partialWrite	(partialWrite)
+		, m_verify			(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		deUint32		dataSeed		= deStringHash(getName());
+		deUint32		buf				= 0;
+		ReferenceBuffer	refBuf;
+		BufferVerifier	verifier		(m_renderCtx, m_testCtx.getLog(), m_verify);
+		const int		bufferSize		= 1300;
+		const int		mapOffset		= 200;
+		const int		mapSize			= 1011;
+		const int		mapWriteOffs	= m_partialWrite ? 91						: 0;
+		const int		verifyOffset	= m_partialWrite ? mapOffset+mapWriteOffs	: 0;
+		const int		verifySize		= bufferSize-verifyOffset;
+
+		// Setup reference data.
+		refBuf.setSize(bufferSize);
+		fillWithRandomBytes(refBuf.getPtr(), bufferSize, dataSeed);
+
+		buf = genBuffer();
+		glBindBuffer(m_bufferTarget, buf);
+		glBufferData(m_bufferTarget, bufferSize, refBuf.getPtr(), m_usage);
+
+		// Do reference map.
+		fillWithRandomBytes(refBuf.getPtr(mapOffset+mapWriteOffs), mapSize-mapWriteOffs, dataSeed&0xabcdef);
+
+		void* ptr = glMapBufferRange(m_bufferTarget, mapOffset, mapSize, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_RANGE_BIT);
+		GLU_CHECK_MSG("glMapBufferRange");
+		TCU_CHECK(ptr);
+
+		deMemcpy((deUint8*)ptr+mapWriteOffs, refBuf.getPtr(mapOffset+mapWriteOffs), mapSize-mapWriteOffs);
+		glUnmapBuffer(m_bufferTarget);
+		GLU_CHECK_MSG("glUnmapBuffer");
+
+		bool isOk = verifier.verify(buf, refBuf.getPtr(), verifyOffset, verifySize, m_bufferTarget);
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_bufferTarget;
+	deUint32		m_usage;
+	bool			m_partialWrite;
+	VerifyType		m_verify;
+};
+
+class BufferMapExplicitFlushCase : public BufferCase
+{
+public:
+	BufferMapExplicitFlushCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, bool partialWrite, VerifyType verify)
+		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_bufferTarget	(bufferTarget)
+		, m_usage			(usage)
+		, m_partialWrite	(partialWrite)
+		, m_verify			(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		deUint32		dataSeed		= deStringHash(getName());
+		deUint32		buf				= 0;
+		ReferenceBuffer	refBuf;
+		BufferVerifier	verifier		(m_renderCtx, m_testCtx.getLog(), m_verify);
+		const int		bufferSize		= 1300;
+		const int		mapOffset		= 200;
+		const int		mapSize			= 1011;
+		const int		sliceAOffs		= m_partialWrite ? 1		: 0;
+		const int		sliceASize		= m_partialWrite ? 96		: 473;
+		const int		sliceBOffs		= m_partialWrite ? 503		: sliceAOffs+sliceASize;
+		const int		sliceBSize		= mapSize-sliceBOffs;
+		bool			isOk			= true;
+
+		// Setup reference data.
+		refBuf.setSize(bufferSize);
+		fillWithRandomBytes(refBuf.getPtr(), bufferSize, dataSeed);
+
+		buf = genBuffer();
+		glBindBuffer(m_bufferTarget, buf);
+		glBufferData(m_bufferTarget, bufferSize, refBuf.getPtr(), m_usage);
+
+		// Do reference map.
+		fillWithRandomBytes(refBuf.getPtr(mapOffset), mapSize, dataSeed&0xabcdef);
+
+		void* ptr = glMapBufferRange(m_bufferTarget, mapOffset, mapSize, GL_MAP_WRITE_BIT|GL_MAP_FLUSH_EXPLICIT_BIT);
+		GLU_CHECK_MSG("glMapBufferRange");
+		TCU_CHECK(ptr);
+
+		deMemcpy(ptr, refBuf.getPtr(mapOffset), mapSize);
+
+		glFlushMappedBufferRange(m_bufferTarget, sliceAOffs, sliceASize);
+		GLU_CHECK_MSG("glFlushMappedBufferRange");
+		glFlushMappedBufferRange(m_bufferTarget, sliceBOffs, sliceBSize);
+		GLU_CHECK_MSG("glFlushMappedBufferRange");
+
+		glUnmapBuffer(m_bufferTarget);
+		GLU_CHECK_MSG("glUnmapBuffer");
+
+		if (m_partialWrite)
+		{
+			if (!verifier.verify(buf, refBuf.getPtr(), mapOffset+sliceAOffs, sliceASize, m_bufferTarget))
+				isOk = false;
+
+			if (!verifier.verify(buf, refBuf.getPtr(), mapOffset+sliceBOffs, sliceBSize, m_bufferTarget))
+				isOk = false;
+		}
+		else
+		{
+			if (!verifier.verify(buf, refBuf.getPtr(), mapOffset, mapSize, m_bufferTarget))
+				isOk = false;
+		}
+
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_bufferTarget;
+	deUint32		m_usage;
+	bool			m_partialWrite;
+	VerifyType		m_verify;
+};
+
+class BufferMapUnsyncWriteCase : public BufferCase
+{
+public:
+	BufferMapUnsyncWriteCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage)
+		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_bufferTarget	(bufferTarget)
+		, m_usage			(usage)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		VertexArrayVerifier	verifier	(m_renderCtx, m_testCtx.getLog());
+		deUint32			dataSeed	= deStringHash(getName());
+		ReferenceBuffer		refBuf;
+		deUint32			buf			= 0;
+		bool				isOk		= true;
+		const int			size		= 1200;
+
+		// Setup reference data.
+		refBuf.setSize(size);
+		fillWithRandomBytes(refBuf.getPtr(), size, dataSeed);
+
+		buf = genBuffer();
+		glBindBuffer(m_bufferTarget, buf);
+		glBufferData(m_bufferTarget, size, refBuf.getPtr(), m_usage);
+
+		// Use for rendering.
+		if (!verifier.verify(buf, refBuf.getPtr(), 0, size))
+			isOk = false;
+		// \note ReadPixels() implies Finish
+
+		glBindBuffer(m_bufferTarget, buf);
+		void* ptr = glMapBufferRange(m_bufferTarget, 0, size, GL_MAP_WRITE_BIT|GL_MAP_UNSYNCHRONIZED_BIT);
+		GLU_CHECK_MSG("glMapBufferRange");
+		TCU_CHECK(ptr);
+
+		fillWithRandomBytes(refBuf.getPtr(), size, dataSeed&0xabcdef);
+		deMemcpy(ptr, refBuf.getPtr(), size);
+
+		glUnmapBuffer(m_bufferTarget);
+		GLU_CHECK_MSG("glUnmapBuffer");
+
+		// Synchronize.
+		glFinish();
+
+		if (!verifier.verify(buf, refBuf.getPtr(), 0, size))
+			isOk = false;
+
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_bufferTarget;
+	deUint32		m_usage;
+};
+
+class BufferMapReadWriteCase : public BufferCase
+{
+public:
+	BufferMapReadWriteCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, int bufferSize, int mapOffset, int mapSize, VerifyType verify)
+		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_bufferTarget	(bufferTarget)
+		, m_usage			(usage)
+		, m_bufferSize		(bufferSize)
+		, m_mapOffset		(mapOffset)
+		, m_mapSize			(mapSize)
+		, m_verify			(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		deUint32		dataSeed	= deStringHash(getName());
+		deUint32		buf			= 0;
+		ReferenceBuffer	refBuf;
+		BufferVerifier	verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
+		bool			isOk		= true;
+
+		// Setup reference data.
+		refBuf.setSize(m_bufferSize);
+		fillWithRandomBytes(refBuf.getPtr(), m_bufferSize, dataSeed);
+
+		buf = genBuffer();
+		glBindBuffer(m_bufferTarget, buf);
+		glBufferData(m_bufferTarget, m_bufferSize, refBuf.getPtr(), m_usage);
+
+		// Verify before mapping.
+		if (!verifier.verify(buf, refBuf.getPtr(), 0, m_bufferSize, m_bufferTarget))
+			isOk = false;
+
+		glBindBuffer(m_bufferTarget, buf);
+		void* ptr = glMapBufferRange(m_bufferTarget, m_mapOffset, m_mapSize, GL_MAP_READ_BIT|GL_MAP_WRITE_BIT);
+		GLU_CHECK_MSG("glMapBufferRange");
+		TCU_CHECK(ptr);
+
+		// Compare mapped ptr.
+		if (!compareByteArrays(log, (const deUint8*)ptr, refBuf.getPtr(m_mapOffset), m_mapSize))
+			isOk = false;
+
+		fillWithRandomBytes(refBuf.getPtr(m_mapOffset), m_mapSize, dataSeed&0xabcdef);
+		deMemcpy(ptr, refBuf.getPtr(m_mapOffset), m_mapSize);
+
+		glUnmapBuffer(m_bufferTarget);
+		GLU_CHECK_MSG("glUnmapBuffer");
+
+		// Compare final buffer.
+		if (!verifier.verify(buf, refBuf.getPtr(), 0, m_bufferSize, m_bufferTarget))
+			isOk = false;
+
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_bufferTarget;
+	deUint32		m_usage;
+	int				m_bufferSize;
+	int				m_mapOffset;
+	int				m_mapSize;
+	VerifyType		m_verify;
+};
+
+BufferMapTests::BufferMapTests (Context& context)
+	: TestCaseGroup(context, "map", "Buffer map tests")
+{
+}
+
+BufferMapTests::~BufferMapTests (void)
+{
+}
+
+void BufferMapTests::init (void)
+{
+	static const deUint32 bufferTargets[] =
+	{
+		GL_ARRAY_BUFFER,
+		GL_COPY_READ_BUFFER,
+		GL_COPY_WRITE_BUFFER,
+		GL_ELEMENT_ARRAY_BUFFER,
+		GL_PIXEL_PACK_BUFFER,
+		GL_PIXEL_UNPACK_BUFFER,
+		GL_TRANSFORM_FEEDBACK_BUFFER,
+		GL_UNIFORM_BUFFER
+	};
+
+	static const deUint32 usageHints[] =
+	{
+		GL_STREAM_DRAW,
+		GL_STREAM_READ,
+		GL_STREAM_COPY,
+		GL_STATIC_DRAW,
+		GL_STATIC_READ,
+		GL_STATIC_COPY,
+		GL_DYNAMIC_DRAW,
+		GL_DYNAMIC_READ,
+		GL_DYNAMIC_COPY
+	};
+
+	static const struct
+	{
+		const char*		name;
+		WriteType		write;
+	} bufferDataSources[] =
+	{
+		{ "sub_data",		WRITE_BUFFER_SUB_DATA	},
+		{ "map_write",		WRITE_BUFFER_WRITE_MAP	}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		VerifyType		verify;
+	} bufferUses[] =
+	{
+		{ "map_read",				VERIFY_BUFFER_READ_MAP	},
+		{ "render_as_vertex_array",	VERIFY_AS_VERTEX_ARRAY	},
+		{ "render_as_index_array",	VERIFY_AS_INDEX_ARRAY	}
+	};
+
+	// .read
+	{
+		tcu::TestCaseGroup* mapReadGroup = new tcu::TestCaseGroup(m_testCtx, "read", "Buffer read using glMapBufferRange()");
+		addChild(mapReadGroup);
+
+		// .[data src]
+		for (int srcNdx = 0; srcNdx < DE_LENGTH_OF_ARRAY(bufferDataSources); srcNdx++)
+		{
+			WriteType			write		= bufferDataSources[srcNdx].write;
+			tcu::TestCaseGroup* writeGroup	= new tcu::TestCaseGroup(m_testCtx, bufferDataSources[srcNdx].name, "");
+			mapReadGroup->addChild(writeGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				deUint32		target		= bufferTargets[targetNdx];
+				const deUint32	hint		= GL_STATIC_READ;
+				const int		size		= 1019;
+				const int		partialOffs	= 17;
+				const int		partialSize	= 501;
+
+				writeGroup->addChild(new BufferMapReadCase(m_context, (string(getBufferTargetName(target)) + "_full").c_str(),		"", target, hint, size, 0, size, write));
+				writeGroup->addChild(new BufferMapReadCase(m_context, (string(getBufferTargetName(target)) + "_partial").c_str(),	"", target, hint, size, partialOffs, partialSize, write));
+			}
+		}
+
+		// .usage_hints
+		{
+			tcu::TestCaseGroup* hintsGroup = new tcu::TestCaseGroup(m_testCtx, "usage_hints", "Different usage hints with glMapBufferRange()");
+			mapReadGroup->addChild(hintsGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				for (int hintNdx = 0; hintNdx < DE_LENGTH_OF_ARRAY(usageHints); hintNdx++)
+				{
+					deUint32		target		= bufferTargets[targetNdx];
+					deUint32		hint		= usageHints[hintNdx];
+					const int		size		= 1019;
+					string			name		= string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);
+
+					hintsGroup->addChild(new BufferMapReadCase(m_context, name.c_str(), "", target, hint, size, 0, size, WRITE_BUFFER_SUB_DATA));
+				}
+			}
+		}
+	}
+
+	// .write
+	{
+		tcu::TestCaseGroup* mapWriteGroup = new tcu::TestCaseGroup(m_testCtx, "write", "Buffer write using glMapBufferRange()");
+		addChild(mapWriteGroup);
+
+		// .[verify type]
+		for (int useNdx = 0; useNdx < DE_LENGTH_OF_ARRAY(bufferUses); useNdx++)
+		{
+			VerifyType			verify		= bufferUses[useNdx].verify;
+			tcu::TestCaseGroup* useGroup	= new tcu::TestCaseGroup(m_testCtx, bufferUses[useNdx].name, "");
+			mapWriteGroup->addChild(useGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				deUint32		target		= bufferTargets[targetNdx];
+				deUint32		hint		= GL_STATIC_DRAW;
+				const int		size		= 1019;
+				const int		partialOffs	= 17;
+				const int		partialSize	= 501;
+				string			name		= string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);
+
+				useGroup->addChild(new BufferMapWriteCase			(m_context, (string(getBufferTargetName(target)) + "_full").c_str(),	"", target, hint, size, verify));
+				useGroup->addChild(new BufferPartialMapWriteCase	(m_context, (string(getBufferTargetName(target)) + "_partial").c_str(),	"", target, hint, size, partialOffs, partialSize, verify));
+			}
+		}
+
+		// .usage_hints
+		{
+			tcu::TestCaseGroup* hintsGroup = new tcu::TestCaseGroup(m_testCtx, "usage_hints", "Usage hints");
+			mapWriteGroup->addChild(hintsGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				for (int hintNdx = 0; hintNdx < DE_LENGTH_OF_ARRAY(usageHints); hintNdx++)
+				{
+					deUint32		target		= bufferTargets[targetNdx];
+					deUint32		hint		= usageHints[hintNdx];
+					const int		size		= 1019;
+					string			name		= string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);
+
+					hintsGroup->addChild(new BufferMapWriteCase(m_context, name.c_str(), "", target, hint, size, VERIFY_AS_VERTEX_ARRAY));
+				}
+			}
+		}
+
+		// .invalidate
+		{
+			tcu::TestCaseGroup* invalidateGroup = new tcu::TestCaseGroup(m_testCtx, "invalidate", "Buffer invalidate");
+			mapWriteGroup->addChild(invalidateGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				deUint32		target		= bufferTargets[targetNdx];
+				deUint32		hint		= GL_STATIC_DRAW;
+
+				invalidateGroup->addChild(new BufferMapInvalidateCase(m_context, (string(getBufferTargetName(target)) + "_write_all").c_str(),		"", target, hint, false,	VERIFY_AS_VERTEX_ARRAY));
+				invalidateGroup->addChild(new BufferMapInvalidateCase(m_context, (string(getBufferTargetName(target)) + "_write_partial").c_str(),	"", target, hint, true,		VERIFY_AS_VERTEX_ARRAY));
+			}
+		}
+
+		// .partial_invalidate
+		{
+			tcu::TestCaseGroup* invalidateGroup = new tcu::TestCaseGroup(m_testCtx, "partial_invalidate", "Partial invalidate");
+			mapWriteGroup->addChild(invalidateGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				deUint32		target		= bufferTargets[targetNdx];
+				deUint32		hint		= GL_STATIC_DRAW;
+
+				invalidateGroup->addChild(new BufferMapPartialInvalidateCase(m_context, (string(getBufferTargetName(target)) + "_write_all").c_str(),		"", target, hint, false,	VERIFY_AS_VERTEX_ARRAY));
+				invalidateGroup->addChild(new BufferMapPartialInvalidateCase(m_context, (string(getBufferTargetName(target)) + "_write_partial").c_str(),	"", target, hint, true,		VERIFY_AS_VERTEX_ARRAY));
+			}
+		}
+
+		// .explicit_flush
+		{
+			tcu::TestCaseGroup* flushGroup = new tcu::TestCaseGroup(m_testCtx, "explicit_flush", "Explicit flush");
+			mapWriteGroup->addChild(flushGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				deUint32		target		= bufferTargets[targetNdx];
+				deUint32		hint		= GL_STATIC_DRAW;
+
+				flushGroup->addChild(new BufferMapExplicitFlushCase(m_context, (string(getBufferTargetName(target)) + "_all").c_str(),		"", target, hint, false,	VERIFY_AS_VERTEX_ARRAY));
+				flushGroup->addChild(new BufferMapExplicitFlushCase(m_context, (string(getBufferTargetName(target)) + "_partial").c_str(),	"", target, hint, true,		VERIFY_AS_VERTEX_ARRAY));
+			}
+		}
+
+		// .unsynchronized
+		{
+			tcu::TestCaseGroup* unsyncGroup = new tcu::TestCaseGroup(m_testCtx, "unsynchronized", "Unsynchronized map");
+			mapWriteGroup->addChild(unsyncGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				deUint32		target		= bufferTargets[targetNdx];
+				deUint32		hint		= GL_STATIC_DRAW;
+
+				unsyncGroup->addChild(new BufferMapUnsyncWriteCase(m_context, getBufferTargetName(target),	"", target, hint));
+			}
+		}
+	}
+
+	// .read_write
+	{
+		tcu::TestCaseGroup* mapReadWriteGroup = new tcu::TestCaseGroup(m_testCtx, "read_write", "Buffer read and write using glMapBufferRange()");
+		addChild(mapReadWriteGroup);
+
+		// .[verify type]
+		for (int useNdx = 0; useNdx < DE_LENGTH_OF_ARRAY(bufferUses); useNdx++)
+		{
+			VerifyType			verify		= bufferUses[useNdx].verify;
+			tcu::TestCaseGroup* useGroup	= new tcu::TestCaseGroup(m_testCtx, bufferUses[useNdx].name, "");
+			mapReadWriteGroup->addChild(useGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				deUint32		target		= bufferTargets[targetNdx];
+				deUint32		hint		= GL_STATIC_DRAW;
+				const int		size		= 1019;
+				const int		partialOffs	= 17;
+				const int		partialSize	= 501;
+				string			name		= string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);
+
+				useGroup->addChild(new BufferMapReadWriteCase(m_context, (string(getBufferTargetName(target)) + "_full").c_str(),		"", target, hint, size, 0, size, verify));
+				useGroup->addChild(new BufferMapReadWriteCase(m_context, (string(getBufferTargetName(target)) + "_partial").c_str(),	"", target, hint, size, partialOffs, partialSize, verify));
+			}
+		}
+
+		// .usage_hints
+		{
+			tcu::TestCaseGroup* hintsGroup = new tcu::TestCaseGroup(m_testCtx, "usage_hints", "Usage hints");
+			mapReadWriteGroup->addChild(hintsGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+			{
+				for (int hintNdx = 0; hintNdx < DE_LENGTH_OF_ARRAY(usageHints); hintNdx++)
+				{
+					deUint32		target		= bufferTargets[targetNdx];
+					deUint32		hint		= usageHints[hintNdx];
+					const int		size		= 1019;
+					string			name		= string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);
+
+					hintsGroup->addChild(new BufferMapReadWriteCase(m_context, name.c_str(), "", target, hint, size, 0, size, VERIFY_AS_VERTEX_ARRAY));
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fBufferMapTests.hpp b/modules/gles3/functional/es3fBufferMapTests.hpp
new file mode 100644
index 0000000..9e63e1f
--- /dev/null
+++ b/modules/gles3/functional/es3fBufferMapTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FBUFFERMAPTESTS_HPP
+#define _ES3FBUFFERMAPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer mapping tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class BufferMapTests : public TestCaseGroup
+{
+public:
+						BufferMapTests		(Context& context);
+						~BufferMapTests		(void);
+
+	void				init				(void);
+
+private:
+						BufferMapTests		(const BufferMapTests& other);
+	BufferMapTests&		operator=			(const BufferMapTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FBUFFERMAPTESTS_HPP
diff --git a/modules/gles3/functional/es3fBufferObjectQueryTests.cpp b/modules/gles3/functional/es3fBufferObjectQueryTests.cpp
new file mode 100644
index 0000000..34b238e
--- /dev/null
+++ b/modules/gles3/functional/es3fBufferObjectQueryTests.cpp
@@ -0,0 +1,539 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer Object Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fBufferObjectQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+
+#include <limits>
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace BufferParamVerifiers
+{
+
+void checkIntEquals (tcu::TestContext& testCtx, GLint got, GLint expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void checkPointerEquals (tcu::TestContext& testCtx, const void* got, const void* expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+class BufferParamVerifier : protected glu::CallLogWrapper
+{
+public:
+						BufferParamVerifier		(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~BufferParamVerifier	(); // make GCC happy
+
+	const char*			getTestNamePostfix		(void) const;
+
+	virtual void		verifyInteger			(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)	= DE_NULL;
+	virtual void		verifyInteger64			(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint64 reference)	= DE_NULL;
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+BufferParamVerifier::BufferParamVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+
+BufferParamVerifier::~BufferParamVerifier ()
+{
+}
+
+const char* BufferParamVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+class GetBufferParameterIVerifier : public BufferParamVerifier
+{
+public:
+			GetBufferParameterIVerifier	(const glw::Functions& gl, tcu::TestLog& log);
+
+	void	verifyInteger								(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference);
+	void	verifyInteger64								(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint64 reference);
+};
+
+GetBufferParameterIVerifier::GetBufferParameterIVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: BufferParamVerifier(gl, log, "_getbufferparameteri")
+{
+}
+
+void GetBufferParameterIVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetBufferParameteriv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	}
+}
+
+void GetBufferParameterIVerifier::verifyInteger64 (tcu::TestContext& testCtx, GLenum target, GLenum name, GLint64 reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetBufferParameteriv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	// check that the converted value would be in the correct range, otherwise checking wont tell us anything
+	if (!de::inRange(reference, (GLint64)std::numeric_limits<GLint>::min(), (GLint64)std::numeric_limits<GLint>::max()))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	}
+}
+
+class GetBufferParameterI64Verifier : public BufferParamVerifier
+{
+public:
+			GetBufferParameterI64Verifier	(const glw::Functions& gl, tcu::TestLog& log);
+
+	void	verifyInteger					(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference);
+	void	verifyInteger64					(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint64 reference);
+};
+
+GetBufferParameterI64Verifier::GetBufferParameterI64Verifier (const glw::Functions& gl, tcu::TestLog& log)
+	: BufferParamVerifier(gl, log, "_getbufferparameteri64")
+{
+}
+
+void GetBufferParameterI64Verifier::verifyInteger (tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetBufferParameteri64v(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	}
+}
+
+void GetBufferParameterI64Verifier::verifyInteger64 (tcu::TestContext& testCtx, GLenum target, GLenum name, GLint64 reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetBufferParameteri64v(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	}
+}
+
+
+} // BufferParamVerifiers
+
+namespace
+{
+
+using namespace BufferParamVerifiers;
+
+// Tests
+
+class BufferCase : public ApiCase
+{
+public:
+	BufferCase (Context& context, BufferParamVerifier* verifier, const char* name, const char* description)
+		: ApiCase			(context, name, description)
+		, m_bufferTarget	(0)
+		, m_verifier		(verifier)
+		, m_testAllTargets	(false)
+	{
+	}
+
+	virtual void testBuffer (void) = DE_NULL;
+
+	void test (void)
+	{
+		const GLenum bufferTargets[] =
+		{
+			GL_ARRAY_BUFFER, GL_COPY_READ_BUFFER,
+			GL_TRANSFORM_FEEDBACK_BUFFER, GL_UNIFORM_BUFFER,
+
+			GL_COPY_WRITE_BUFFER, GL_ELEMENT_ARRAY_BUFFER,
+			GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER
+		};
+
+		// most test need only to be run with a subset of targets
+		const int targets = m_testAllTargets ? DE_LENGTH_OF_ARRAY(bufferTargets) : 4;
+
+		for (int ndx = 0; ndx < targets; ++ndx)
+		{
+			m_bufferTarget = bufferTargets[ndx];
+
+			GLuint bufferId = 0;
+			glGenBuffers(1, &bufferId);
+			glBindBuffer(m_bufferTarget, bufferId);
+			expectError(GL_NO_ERROR);
+
+			testBuffer();
+
+			glDeleteBuffers(1, &bufferId);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+protected:
+	GLenum					m_bufferTarget;
+	BufferParamVerifier*	m_verifier;
+	bool					m_testAllTargets;
+};
+
+class BufferSizeCase : public BufferCase
+{
+public:
+	BufferSizeCase (Context& context, BufferParamVerifier* verifier, const char* name, const char* description)
+		: BufferCase(context, verifier, name, description)
+	{
+		m_testAllTargets = true;
+	}
+
+	void testBuffer (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyInteger64(m_testCtx, m_bufferTarget, GL_BUFFER_SIZE, 0);
+
+		const int numIterations = 16;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLint len = rnd.getInt(0, 1024);
+			glBufferData(m_bufferTarget, len, DE_NULL, GL_STREAM_DRAW);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger64(m_testCtx, m_bufferTarget, GL_BUFFER_SIZE, len);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class BufferUsageCase : public BufferCase
+{
+public:
+	BufferUsageCase (Context& context, BufferParamVerifier* verifier, const char* name, const char* description)
+		: BufferCase(context, verifier, name, description)
+	{
+	}
+
+	void testBuffer (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_USAGE, GL_STATIC_DRAW);
+
+		const GLenum usages[] =
+		{
+			GL_STREAM_DRAW, GL_STREAM_READ,
+			GL_STREAM_COPY, GL_STATIC_DRAW,
+			GL_STATIC_READ, GL_STATIC_COPY,
+			GL_DYNAMIC_DRAW, GL_DYNAMIC_READ,
+			GL_DYNAMIC_COPY
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(usages); ++ndx)
+		{
+			glBufferData(m_bufferTarget, 16, DE_NULL, usages[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_USAGE, usages[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class BufferAccessFlagsCase : public BufferCase
+{
+public:
+	BufferAccessFlagsCase (Context& context, BufferParamVerifier* verifier, const char* name, const char* description)
+		: BufferCase(context, verifier, name, description)
+	{
+	}
+
+	void testBuffer (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_ACCESS_FLAGS, 0);
+
+		const GLenum accessFlags[] =
+		{
+			GL_MAP_READ_BIT,
+
+			GL_MAP_WRITE_BIT,
+			GL_MAP_WRITE_BIT																	| GL_MAP_INVALIDATE_RANGE_BIT,
+			GL_MAP_WRITE_BIT																									| GL_MAP_INVALIDATE_BUFFER_BIT,
+			GL_MAP_WRITE_BIT																	| GL_MAP_INVALIDATE_RANGE_BIT	| GL_MAP_INVALIDATE_BUFFER_BIT,
+
+			GL_MAP_WRITE_BIT									| GL_MAP_FLUSH_EXPLICIT_BIT,
+			GL_MAP_WRITE_BIT									| GL_MAP_FLUSH_EXPLICIT_BIT		| GL_MAP_INVALIDATE_RANGE_BIT,
+			GL_MAP_WRITE_BIT									| GL_MAP_FLUSH_EXPLICIT_BIT										| GL_MAP_INVALIDATE_BUFFER_BIT,
+			GL_MAP_WRITE_BIT									| GL_MAP_FLUSH_EXPLICIT_BIT		| GL_MAP_INVALIDATE_RANGE_BIT	| GL_MAP_INVALIDATE_BUFFER_BIT,
+
+			GL_MAP_WRITE_BIT	| GL_MAP_UNSYNCHRONIZED_BIT,
+			GL_MAP_WRITE_BIT	| GL_MAP_UNSYNCHRONIZED_BIT										| GL_MAP_INVALIDATE_RANGE_BIT,
+			GL_MAP_WRITE_BIT	| GL_MAP_UNSYNCHRONIZED_BIT																		| GL_MAP_INVALIDATE_BUFFER_BIT,
+			GL_MAP_WRITE_BIT	| GL_MAP_UNSYNCHRONIZED_BIT										| GL_MAP_INVALIDATE_RANGE_BIT	| GL_MAP_INVALIDATE_BUFFER_BIT,
+
+			GL_MAP_WRITE_BIT	| GL_MAP_UNSYNCHRONIZED_BIT		| GL_MAP_FLUSH_EXPLICIT_BIT,
+			GL_MAP_WRITE_BIT	| GL_MAP_UNSYNCHRONIZED_BIT		| GL_MAP_FLUSH_EXPLICIT_BIT		| GL_MAP_INVALIDATE_RANGE_BIT,
+			GL_MAP_WRITE_BIT	| GL_MAP_UNSYNCHRONIZED_BIT		| GL_MAP_FLUSH_EXPLICIT_BIT										| GL_MAP_INVALIDATE_BUFFER_BIT,
+			GL_MAP_WRITE_BIT	| GL_MAP_UNSYNCHRONIZED_BIT		| GL_MAP_FLUSH_EXPLICIT_BIT		| GL_MAP_INVALIDATE_RANGE_BIT	| GL_MAP_INVALIDATE_BUFFER_BIT,
+
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(accessFlags); ++ndx)
+		{
+			glBufferData(m_bufferTarget, 16, DE_NULL, GL_DYNAMIC_COPY);
+			glMapBufferRange(m_bufferTarget, 0, 16, accessFlags[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_ACCESS_FLAGS, accessFlags[ndx]);
+			expectError(GL_NO_ERROR);
+
+			glUnmapBuffer(m_bufferTarget);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class BufferMappedCase : public BufferCase
+{
+public:
+	BufferMappedCase (Context& context, BufferParamVerifier* verifier, const char* name, const char* description)
+		: BufferCase(context, verifier, name, description)
+	{
+	}
+
+	void testBuffer (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_MAPPED, GL_FALSE);
+
+		glBufferData(m_bufferTarget, 16, DE_NULL, GL_DYNAMIC_COPY);
+		glMapBufferRange(m_bufferTarget, 0, 16, GL_MAP_WRITE_BIT);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_MAPPED, GL_TRUE);
+		expectError(GL_NO_ERROR);
+
+		glUnmapBuffer(m_bufferTarget);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class BufferOffsetLengthCase : public BufferCase
+{
+public:
+	BufferOffsetLengthCase (Context& context, BufferParamVerifier* verifier, const char* name, const char* description)
+		: BufferCase(context, verifier, name, description)
+	{
+	}
+
+	void testBuffer (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_MAP_OFFSET, 0);
+		m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_MAP_LENGTH, 0);
+
+		glBufferData(m_bufferTarget, 16, DE_NULL, GL_DYNAMIC_COPY);
+
+		const struct BufferRange
+		{
+			int offset;
+			int length;
+		} ranges[] =
+		{
+			{ 0, 16 },
+			{ 4, 12 },
+			{ 0, 12 },
+			{ 8,  8 },
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(ranges); ++ndx)
+		{
+			glMapBufferRange(m_bufferTarget, ranges[ndx].offset, ranges[ndx].length, GL_MAP_WRITE_BIT);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_MAP_OFFSET, ranges[ndx].offset);
+			m_verifier->verifyInteger(m_testCtx, m_bufferTarget, GL_BUFFER_MAP_LENGTH, ranges[ndx].length);
+			expectError(GL_NO_ERROR);
+
+			glUnmapBuffer(m_bufferTarget);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class BufferPointerCase : public ApiCase
+{
+public:
+	BufferPointerCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint bufferId = 0;
+		glGenBuffers(1, &bufferId);
+		glBindBuffer(GL_ARRAY_BUFFER, bufferId);
+		expectError(GL_NO_ERROR);
+
+		StateQueryMemoryWriteGuard<GLvoid*> initialState;
+		glGetBufferPointerv(GL_ARRAY_BUFFER, GL_BUFFER_MAP_POINTER, &initialState);
+		initialState.verifyValidity(m_testCtx);
+		checkPointerEquals(m_testCtx, initialState, 0);
+
+		glBufferData(GL_ARRAY_BUFFER, 8, DE_NULL, GL_DYNAMIC_COPY);
+		GLvoid* mapPointer = glMapBufferRange(GL_ARRAY_BUFFER, 0, 8, GL_MAP_READ_BIT);
+		expectError(GL_NO_ERROR);
+
+		StateQueryMemoryWriteGuard<GLvoid*> mapPointerState;
+		glGetBufferPointerv(GL_ARRAY_BUFFER, GL_BUFFER_MAP_POINTER, &mapPointerState);
+		mapPointerState.verifyValidity(m_testCtx);
+		checkPointerEquals(m_testCtx, mapPointerState, mapPointer);
+
+		glDeleteBuffers(1, &bufferId);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+} // anonymous
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		BufferParamVerifier* verifier = VERIFIERS[_verifierNdx];								\
+		CODE_BLOCK;																				\
+	}
+
+BufferObjectQueryTests::BufferObjectQueryTests (Context& context)
+	: TestCaseGroup		(context, "buffer_object", "Buffer Object Query tests")
+	, m_verifierInt		(DE_NULL)
+	, m_verifierInt64	(DE_NULL)
+{
+}
+
+BufferObjectQueryTests::~BufferObjectQueryTests (void)
+{
+	deinit();
+}
+
+void BufferObjectQueryTests::init (void)
+{
+	using namespace BufferParamVerifiers;
+
+	DE_ASSERT(m_verifierInt == DE_NULL);
+	DE_ASSERT(m_verifierInt64 == DE_NULL);
+
+	m_verifierInt		= new GetBufferParameterIVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInt64		= new GetBufferParameterI64Verifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	BufferParamVerifier* verifiers[] = {m_verifierInt, m_verifierInt64};
+
+	FOR_EACH_VERIFIER(verifiers, addChild(new BufferSizeCase		(m_context, verifier,	(std::string("buffer_size")				+ verifier->getTestNamePostfix()).c_str(), "BUFFER_SIZE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new BufferUsageCase		(m_context, verifier,	(std::string("buffer_usage")			+ verifier->getTestNamePostfix()).c_str(), "BUFFER_USAGE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new BufferAccessFlagsCase	(m_context, verifier,	(std::string("buffer_access_flags")		+ verifier->getTestNamePostfix()).c_str(), "BUFFER_ACCESS_FLAGS")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new BufferMappedCase		(m_context, verifier,	(std::string("buffer_mapped")			+ verifier->getTestNamePostfix()).c_str(), "BUFFER_MAPPED")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new BufferOffsetLengthCase(m_context, verifier,	(std::string("buffer_map_offset_length")+ verifier->getTestNamePostfix()).c_str(), "BUFFER_MAP_OFFSET and BUFFER_MAP_LENGTH")));
+
+	addChild(new BufferPointerCase(m_context, "buffer_pointer", "GetBufferPointerv"));
+}
+
+void BufferObjectQueryTests::deinit (void)
+{
+	if (m_verifierInt)
+	{
+		delete m_verifierInt;
+		m_verifierInt = NULL;
+	}
+	if (m_verifierInt64)
+	{
+		delete m_verifierInt64;
+		m_verifierInt64 = NULL;
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fBufferObjectQueryTests.hpp b/modules/gles3/functional/es3fBufferObjectQueryTests.hpp
new file mode 100644
index 0000000..ac3dab3
--- /dev/null
+++ b/modules/gles3/functional/es3fBufferObjectQueryTests.hpp
@@ -0,0 +1,64 @@
+#ifndef _ES3FBUFFEROBJECTQUERYTESTS_HPP
+#define _ES3FBUFFEROBJECTQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer Object Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace BufferParamVerifiers
+{
+
+class GetBufferParameterIVerifier;
+class GetBufferParameterI64Verifier;
+
+} // BufferParamVerifiers
+
+class BufferObjectQueryTests : public TestCaseGroup
+{
+public:
+																		BufferObjectQueryTests	(Context& context);
+																		~BufferObjectQueryTests	(void);
+
+	void																init					(void);
+	void																deinit					(void);
+
+private:
+																		BufferObjectQueryTests	(const BufferObjectQueryTests& other);
+	BufferObjectQueryTests&												operator=				(const BufferObjectQueryTests& other);
+
+	BufferParamVerifiers::GetBufferParameterIVerifier*					m_verifierInt;
+	BufferParamVerifiers::GetBufferParameterI64Verifier*				m_verifierInt64;
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FBUFFEROBJECTQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fBufferWriteTests.cpp b/modules/gles3/functional/es3fBufferWriteTests.cpp
new file mode 100644
index 0000000..66db338
--- /dev/null
+++ b/modules/gles3/functional/es3fBufferWriteTests.cpp
@@ -0,0 +1,774 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer data upload tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fBufferWriteTests.hpp"
+#include "glsBufferTestUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "gluStrUtil.hpp"
+#include "deMemory.h"
+#include "deString.h"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <algorithm>
+#include <list>
+
+using std::set;
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using tcu::IVec2;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using namespace gls::BufferTestUtil;
+
+struct DataStoreSpec
+{
+	DataStoreSpec (void)
+		: target	(0)
+		, usage		(0)
+		, size		(0)
+	{
+	}
+
+	DataStoreSpec (deUint32 target_, deUint32 usage_, int size_)
+		: target	(target_)
+		, usage		(usage_)
+		, size		(size_)
+	{
+	}
+
+	deUint32	target;
+	deUint32	usage;
+	int			size;
+};
+
+struct DataStoreSpecVecBuilder
+{
+	std::vector<DataStoreSpec>& list;
+
+	DataStoreSpecVecBuilder (std::vector<DataStoreSpec>& list_)
+		: list(list_)
+	{
+	}
+
+	DataStoreSpecVecBuilder& operator<< (const DataStoreSpec& spec)
+	{
+		list.push_back(spec);
+		return *this;
+	}
+};
+
+struct RangeVecBuilder
+{
+	std::vector<tcu::IVec2>& list;
+
+	RangeVecBuilder (std::vector<tcu::IVec2>& list_)
+		: list(list_)
+	{
+	}
+
+	RangeVecBuilder& operator<< (const tcu::IVec2& vec)
+	{
+		list.push_back(vec);
+		return *this;
+	}
+};
+
+template<typename Iterator>
+static bool isRangeListValid (Iterator begin, Iterator end)
+{
+	if (begin != end)
+	{
+		// Fetch first.
+		tcu::IVec2 prev = *begin;
+		++begin;
+
+		for (; begin != end; ++begin)
+		{
+			tcu::IVec2 cur = *begin;
+			if (cur.x() <= prev.x() || cur.x() <= prev.x()+prev.y())
+				return false;
+			prev = cur;
+		}
+	}
+
+	return true;
+}
+
+inline bool rangesIntersect (const tcu::IVec2& a, const tcu::IVec2& b)
+{
+	return de::inRange(a.x(), b.x(), b.x()+b.y()) || de::inRange(a.x()+a.y(), b.x(), b.x()+b.y()) ||
+		   de::inRange(b.x(), a.x(), a.x()+a.y()) || de::inRange(b.x()+b.y(), a.x(), a.x()+a.y());
+}
+
+inline tcu::IVec2 unionRanges (const tcu::IVec2& a, const tcu::IVec2& b)
+{
+	DE_ASSERT(rangesIntersect(a, b));
+
+	int start	= de::min(a.x(), b.x());
+	int end		= de::max(a.x()+a.y(), b.x()+b.y());
+
+	return tcu::IVec2(start, end-start);
+}
+
+//! Updates range list (start, len) with a new range.
+std::vector<tcu::IVec2> addRangeToList (const std::vector<tcu::IVec2>& oldList, const tcu::IVec2& newRange)
+{
+	DE_ASSERT(newRange.y() > 0);
+
+	std::vector<tcu::IVec2>					newList;
+	std::vector<tcu::IVec2>::const_iterator	oldListIter	= oldList.begin();
+
+	// Append ranges that end before the new range.
+	for (; oldListIter != oldList.end() && oldListIter->x()+oldListIter->y() < newRange.x(); ++oldListIter)
+		newList.push_back(*oldListIter);
+
+	// Join any ranges that intersect new range
+	{
+		tcu::IVec2 curRange = newRange;
+		while (oldListIter != oldList.end() && rangesIntersect(curRange, *oldListIter))
+		{
+			curRange = unionRanges(curRange, *oldListIter);
+			++oldListIter;
+		}
+
+		newList.push_back(curRange);
+	}
+
+	// Append remaining ranges.
+	for (; oldListIter != oldList.end(); oldListIter++)
+		newList.push_back(*oldListIter);
+
+	DE_ASSERT(isRangeListValid(newList.begin(), newList.end()));
+
+	return newList;
+}
+
+class BasicBufferDataCase : public BufferCase
+{
+public:
+	BasicBufferDataCase (Context& context, const char* name, const char* desc, deUint32 target, deUint32 usage, int size, VerifyType verify)
+		: BufferCase	(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_target		(target)
+		, m_usage		(usage)
+		, m_size		(size)
+		, m_verify		(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32			dataSeed	= deStringHash(getName()) ^ 0x125;
+		BufferVerifier			verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
+		ReferenceBuffer			refBuf;
+		bool					isOk		= false;
+
+		refBuf.setSize(m_size);
+		fillWithRandomBytes(refBuf.getPtr(), m_size, dataSeed);
+
+		deUint32 buf = genBuffer();
+		glBindBuffer(m_target, buf);
+		glBufferData(m_target, m_size, refBuf.getPtr(), m_usage);
+
+		checkError();
+
+		isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_size, m_target);
+
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_target;
+	deUint32		m_usage;
+	int				m_size;
+	VerifyType		m_verify;
+};
+
+class RecreateBufferDataStoreCase : public BufferCase
+{
+public:
+	RecreateBufferDataStoreCase (Context& context, const char* name, const char* desc, const DataStoreSpec* specs, int numSpecs, VerifyType verify)
+		: BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_specs	(specs, specs+numSpecs)
+		, m_verify	(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32			baseSeed	= deStringHash(getName()) ^ 0xbeef;
+		BufferVerifier			verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
+		ReferenceBuffer			refBuf;
+		const deUint32			buf			= genBuffer();
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		for (vector<DataStoreSpec>::const_iterator spec = m_specs.begin(); spec != m_specs.end(); spec++)
+		{
+			bool iterOk = false;
+
+			refBuf.setSize(spec->size);
+			fillWithRandomBytes(refBuf.getPtr(), spec->size, baseSeed ^ deInt32Hash(spec->size+spec->target+spec->usage));
+
+			glBindBuffer(spec->target, buf);
+			glBufferData(spec->target, spec->size, refBuf.getPtr(), spec->usage);
+
+			checkError();
+
+			iterOk = verifier.verify(buf, refBuf.getPtr(), 0, spec->size, spec->target);
+
+			if (!iterOk)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+				break;
+			}
+		}
+
+		deleteBuffer(buf);
+		return STOP;
+	}
+
+private:
+	std::vector<DataStoreSpec>	m_specs;
+	VerifyType					m_verify;
+};
+
+class BasicBufferSubDataCase : public BufferCase
+{
+public:
+	BasicBufferSubDataCase (Context& context, const char* name, const char* desc, deUint32 target, deUint32 usage, int size, int subDataOffs, int subDataSize, VerifyType verify)
+		: BufferCase	(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_target		(target)
+		, m_usage		(usage)
+		, m_size		(size)
+		, m_subDataOffs	(subDataOffs)
+		, m_subDataSize	(subDataSize)
+		, m_verify		(verify)
+	{
+		DE_ASSERT(de::inBounds(subDataOffs, 0, size) && de::inRange(subDataOffs+subDataSize, 0, size));
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32			dataSeed	= deStringHash(getName());
+		BufferVerifier			verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
+		ReferenceBuffer			refBuf;
+		bool					isOk		= false;
+
+		refBuf.setSize(m_size);
+
+		deUint32 buf = genBuffer();
+		glBindBuffer(m_target, buf);
+
+		// Initialize with glBufferData()
+		fillWithRandomBytes(refBuf.getPtr(), m_size, dataSeed ^ 0x80354f);
+		glBufferData(m_target, m_size, refBuf.getPtr(), m_usage);
+		checkError();
+
+		// Re-specify part of buffer
+		fillWithRandomBytes(refBuf.getPtr()+m_subDataOffs, m_subDataSize, dataSeed ^ 0xfac425c);
+		glBufferSubData(m_target, m_subDataOffs, m_subDataSize, refBuf.getPtr()+m_subDataOffs);
+
+		isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_size, m_target);
+
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32		m_target;
+	deUint32		m_usage;
+	int				m_size;
+	int				m_subDataOffs;
+	int				m_subDataSize;
+	VerifyType		m_verify;
+};
+
+class SubDataToUndefinedCase : public BufferCase
+{
+public:
+	SubDataToUndefinedCase (Context& context, const char* name, const char* desc, deUint32 target, deUint32 usage, int size, const tcu::IVec2* ranges, int numRanges, VerifyType verify)
+		: BufferCase	(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_target		(target)
+		, m_usage		(usage)
+		, m_size		(size)
+		, m_ranges		(ranges, ranges+numRanges)
+		, m_verify		(verify)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32			dataSeed	= deStringHash(getName());
+		BufferVerifier			verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
+		ReferenceBuffer			refBuf;
+		bool					isOk		= true;
+		std::vector<tcu::IVec2>	definedRanges;
+
+		refBuf.setSize(m_size);
+
+		deUint32 buf = genBuffer();
+		glBindBuffer(m_target, buf);
+
+		// Initialize storage with glBufferData()
+		glBufferData(m_target, m_size, DE_NULL, m_usage);
+		checkError();
+
+		// Fill specified ranges with glBufferSubData()
+		for (vector<tcu::IVec2>::const_iterator range = m_ranges.begin(); range != m_ranges.end(); range++)
+		{
+			fillWithRandomBytes(refBuf.getPtr()+range->x(), range->y(), dataSeed ^ deInt32Hash(range->x()+range->y()));
+			glBufferSubData(m_target, range->x(), range->y(), refBuf.getPtr()+range->x());
+
+			// Mark range as defined
+			definedRanges = addRangeToList(definedRanges, *range);
+		}
+
+		// Verify defined parts
+		for (vector<tcu::IVec2>::const_iterator range = definedRanges.begin(); range != definedRanges.end(); range++)
+		{
+			if (!verifier.verify(buf, refBuf.getPtr(), range->x(), range->y(), m_target))
+				isOk = false;
+		}
+
+		deleteBuffer(buf);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Buffer verification failed");
+		return STOP;
+	}
+
+private:
+	deUint32				m_target;
+	deUint32				m_usage;
+	int						m_size;
+	std::vector<tcu::IVec2>	m_ranges;
+	VerifyType				m_verify;
+};
+
+class RandomBufferWriteCase : public BufferCase
+{
+public:
+	RandomBufferWriteCase (Context& context, const char* name, const char* desc, deUint32 seed)
+		: BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
+		, m_seed		(seed)
+		, m_verifier	(DE_NULL)
+		, m_buffer		(0)
+		, m_curSize		(0)
+		, m_iterNdx		(0)
+	{
+	}
+
+	~RandomBufferWriteCase (void)
+	{
+		delete m_verifier;
+	}
+
+	void init (void)
+	{
+		BufferCase::init();
+
+		m_iterNdx	= 0;
+		m_buffer	= genBuffer();
+		m_curSize	= 0;
+		m_verifier	= new BufferVerifier(m_renderCtx, m_testCtx.getLog(), VERIFY_AS_VERTEX_ARRAY);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+
+	void deinit (void)
+	{
+		deleteBuffer(m_buffer);
+		m_refBuffer.setSize(0);
+
+		delete m_verifier;
+		m_verifier = DE_NULL;
+
+		BufferCase::deinit();
+	}
+
+	IterateResult iterate (void)
+	{
+		// Parameters.
+		const int	numIterations				= 5;
+		const int	uploadsPerIteration			= 7;
+		const int	minSize						= 12;
+		const int	maxSize						= 32*1024;
+		const float	respecifyProbability		= 0.07f;
+		const float	respecifyDataProbability	= 0.2f;
+
+		static const deUint32 bufferTargets[] =
+		{
+			GL_ARRAY_BUFFER,
+			GL_COPY_READ_BUFFER,
+			GL_COPY_WRITE_BUFFER,
+			GL_ELEMENT_ARRAY_BUFFER,
+			GL_PIXEL_PACK_BUFFER,
+			GL_PIXEL_UNPACK_BUFFER,
+			GL_TRANSFORM_FEEDBACK_BUFFER,
+			GL_UNIFORM_BUFFER
+		};
+
+		static const deUint32 usageHints[] =
+		{
+			GL_STREAM_DRAW,
+			GL_STREAM_READ,
+			GL_STREAM_COPY,
+			GL_STATIC_DRAW,
+			GL_STATIC_READ,
+			GL_STATIC_COPY,
+			GL_DYNAMIC_DRAW,
+			GL_DYNAMIC_READ,
+			GL_DYNAMIC_COPY
+		};
+
+		bool		iterOk					= true;
+		deUint32	curBoundTarget			= GL_NONE;
+		de::Random	rnd						(m_seed ^ deInt32Hash(m_iterNdx) ^ 0xacf92e);
+
+		m_testCtx.getLog() << TestLog::Section(string("Iteration") + de::toString(m_iterNdx+1), string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(numIterations));
+
+		for (int uploadNdx = 0; uploadNdx < uploadsPerIteration; uploadNdx++)
+		{
+			const deUint32	target		= bufferTargets[rnd.getInt(0, DE_LENGTH_OF_ARRAY(bufferTargets)-1)];
+			const bool		respecify	= m_curSize == 0 || rnd.getFloat() < respecifyProbability;
+
+			if (target != curBoundTarget)
+			{
+				glBindBuffer(target, m_buffer);
+				curBoundTarget = target;
+			}
+
+			if (respecify)
+			{
+				const int		size			= rnd.getInt(minSize, maxSize);
+				const deUint32	hint			= usageHints[rnd.getInt(0, DE_LENGTH_OF_ARRAY(usageHints)-1)];
+				const bool		fillWithData	= rnd.getFloat() < respecifyDataProbability;
+
+				m_refBuffer.setSize(size);
+				if (fillWithData)
+					fillWithRandomBytes(m_refBuffer.getPtr(), size, rnd.getUint32());
+
+				glBufferData(target, size, fillWithData ? m_refBuffer.getPtr() : DE_NULL, hint);
+
+				m_validRanges.clear();
+				if (fillWithData)
+					m_validRanges.push_back(tcu::IVec2(0, size));
+
+				m_curSize = size;
+			}
+			else
+			{
+				// \note Non-uniform size distribution.
+				const int	size	= de::clamp(deRoundFloatToInt32((float)m_curSize * deFloatPow(rnd.getFloat(0.0f, 0.7f), 3.0f)), minSize, m_curSize);
+				const int	offset	= rnd.getInt(0, m_curSize-size);
+
+				fillWithRandomBytes(m_refBuffer.getPtr()+offset, size, rnd.getUint32());
+				glBufferSubData(target, offset, size, m_refBuffer.getPtr()+offset);
+
+				m_validRanges = addRangeToList(m_validRanges, tcu::IVec2(offset, size));
+			}
+		}
+
+		// Check error.
+		{
+			deUint32 err = glGetError();
+			if (err != GL_NO_ERROR)
+				throw tcu::TestError(string("Got ") + glu::getErrorStr(err).toString());
+		}
+
+		// Verify valid ranges.
+		for (vector<IVec2>::const_iterator range = m_validRanges.begin(); range != m_validRanges.end(); range++)
+		{
+			const deUint32 targetHint = GL_ARRAY_BUFFER;
+			if (!m_verifier->verify(m_buffer, m_refBuffer.getPtr(), range->x(), range->y(), targetHint))
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer verification failed");
+				iterOk = false;
+				break;
+			}
+		}
+
+		m_testCtx.getLog() << TestLog::EndSection;
+
+		DE_ASSERT(iterOk || m_testCtx.getTestResult() != QP_TEST_RESULT_PASS);
+
+		m_iterNdx += 1;
+		return (iterOk && m_iterNdx < numIterations) ? CONTINUE : STOP;
+	}
+
+private:
+	deUint32				m_seed;
+
+	BufferVerifier*			m_verifier;
+	deUint32				m_buffer;
+	ReferenceBuffer			m_refBuffer;
+	std::vector<tcu::IVec2>	m_validRanges;
+	int						m_curSize;
+	int						m_iterNdx;
+};
+
+BufferWriteTests::BufferWriteTests (Context& context)
+	: TestCaseGroup(context, "write", "Buffer data upload tests")
+{
+}
+
+BufferWriteTests::~BufferWriteTests (void)
+{
+}
+
+void BufferWriteTests::init (void)
+{
+	static const deUint32 bufferTargets[] =
+	{
+		GL_ARRAY_BUFFER,
+		GL_COPY_READ_BUFFER,
+		GL_COPY_WRITE_BUFFER,
+		GL_ELEMENT_ARRAY_BUFFER,
+		GL_PIXEL_PACK_BUFFER,
+		GL_PIXEL_UNPACK_BUFFER,
+		GL_TRANSFORM_FEEDBACK_BUFFER,
+		GL_UNIFORM_BUFFER
+	};
+
+	static const deUint32 usageHints[] =
+	{
+		GL_STREAM_DRAW,
+		GL_STREAM_READ,
+		GL_STREAM_COPY,
+		GL_STATIC_DRAW,
+		GL_STATIC_READ,
+		GL_STATIC_COPY,
+		GL_DYNAMIC_DRAW,
+		GL_DYNAMIC_READ,
+		GL_DYNAMIC_COPY
+	};
+
+	// .basic
+	{
+		tcu::TestCaseGroup* const basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic upload with glBufferData()");
+		addChild(basicGroup);
+
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+		{
+			for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usageHints); usageNdx++)
+			{
+				const deUint32		target	= bufferTargets[targetNdx];
+				const deUint32		usage	= usageHints[usageNdx];
+				const int			size	= 1020;
+				const VerifyType	verify	= VERIFY_AS_VERTEX_ARRAY;
+				const string		name	= string(getBufferTargetName(target)) + "_" + getUsageHintName(usage);
+
+				basicGroup->addChild(new BasicBufferDataCase(m_context, name.c_str(), "", target, usage, size, verify));
+			}
+		}
+	}
+
+	// .recreate_store
+	{
+		tcu::TestCaseGroup* const recreateStoreGroup = new tcu::TestCaseGroup(m_testCtx, "recreate_store", "Data store recreate using glBufferData()");
+		addChild(recreateStoreGroup);
+
+#define RECREATE_STORE_CASE(NAME, DESC, SPECLIST)																												\
+		do {																																				\
+			std::vector<DataStoreSpec> specs;																												\
+			DataStoreSpecVecBuilder builder(specs);																											\
+			builder SPECLIST;																																\
+			recreateStoreGroup->addChild(new RecreateBufferDataStoreCase(m_context, #NAME, DESC, &specs[0], (int)specs.size(), VERIFY_AS_VERTEX_ARRAY));	\
+		} while (deGetFalse())
+
+		RECREATE_STORE_CASE(identical_1, "Recreate with identical parameters",
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996)
+			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996));
+
+		RECREATE_STORE_CASE(identical_2, "Recreate with identical parameters",
+			<< DataStoreSpec(GL_COPY_WRITE_BUFFER, GL_STATIC_DRAW, 72)
+			<< DataStoreSpec(GL_COPY_WRITE_BUFFER, GL_STATIC_DRAW, 72)
+			<< DataStoreSpec(GL_COPY_WRITE_BUFFER, GL_STATIC_DRAW, 72));
+
+		RECREATE_STORE_CASE(different_target, "Recreate with different target",
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_DRAW, 504)
+			<< DataStoreSpec(GL_COPY_READ_BUFFER,			GL_STATIC_DRAW, 504)
+			<< DataStoreSpec(GL_COPY_WRITE_BUFFER,			GL_STATIC_DRAW, 504)
+			<< DataStoreSpec(GL_ELEMENT_ARRAY_BUFFER,		GL_STATIC_DRAW, 504)
+			<< DataStoreSpec(GL_PIXEL_PACK_BUFFER,			GL_STATIC_DRAW, 504)
+			<< DataStoreSpec(GL_PIXEL_UNPACK_BUFFER,		GL_STATIC_DRAW, 504)
+			<< DataStoreSpec(GL_TRANSFORM_FEEDBACK_BUFFER,	GL_STATIC_DRAW, 504)
+			<< DataStoreSpec(GL_UNIFORM_BUFFER,				GL_STATIC_DRAW, 504)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_DRAW, 504));
+
+		RECREATE_STORE_CASE(different_usage, "Recreate with different usage",
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_COPY,		1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_READ,		1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_READ,		1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_DYNAMIC_COPY,	1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_COPY,		1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_DYNAMIC_READ,	1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_DYNAMIC_DRAW,	1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_DRAW,		1644)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		1644));
+
+		RECREATE_STORE_CASE(different_size, "Recreate with different size",
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		1024)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		12)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		3327)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		92)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		123795)
+			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		571));
+
+#undef RECREATE_STORE_CASE
+
+		// Random cases.
+		{
+			const int			numRandomCases		= 4;
+			const int			numUploadsPerCase	= 10;
+			const int			minSize				= 12;
+			const int			maxSize				= 65536;
+			const VerifyType	verify				= VERIFY_AS_VERTEX_ARRAY;
+			de::Random			rnd					(23921);
+
+			for (int caseNdx = 0; caseNdx < numRandomCases; caseNdx++)
+			{
+				vector<DataStoreSpec> specs(numUploadsPerCase);
+
+				for (vector<DataStoreSpec>::iterator spec = specs.begin(); spec != specs.end(); spec++)
+				{
+					spec->target	= bufferTargets[rnd.getInt(0, DE_LENGTH_OF_ARRAY(bufferTargets)-1)];
+					spec->usage		= usageHints[rnd.getInt(0, DE_LENGTH_OF_ARRAY(usageHints)-1)];
+					spec->size		= rnd.getInt(minSize, maxSize);
+				}
+
+				recreateStoreGroup->addChild(new RecreateBufferDataStoreCase(m_context, (string("random_") + de::toString(caseNdx+1)).c_str(), "", &specs[0], (int)specs.size(), verify));
+			}
+		}
+	}
+
+	// .basic_subdata
+	{
+		tcu::TestCaseGroup* const basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic_subdata", "Basic glBufferSubData() usage");
+		addChild(basicGroup);
+
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
+		{
+			for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usageHints); usageNdx++)
+			{
+				const deUint32		target	= bufferTargets[targetNdx];
+				const deUint32		usage	= usageHints[usageNdx];
+				const int			size	= 1020;
+				const VerifyType	verify	= VERIFY_AS_VERTEX_ARRAY;
+				const string		name	= string(getBufferTargetName(target)) + "_" + getUsageHintName(usage);
+
+				basicGroup->addChild(new BasicBufferDataCase(m_context, name.c_str(), "", target, usage, size, verify));
+			}
+		}
+	}
+
+	// .partial_specify
+	{
+		tcu::TestCaseGroup* const partialSpecifyGroup = new tcu::TestCaseGroup(m_testCtx, "partial_specify", "Partial buffer data specification with glBufferSubData()");
+		addChild(partialSpecifyGroup);
+
+#define PARTIAL_SPECIFY_CASE(NAME, DESC, TARGET, USAGE, SIZE, RANGELIST)																									\
+		do {																																								\
+			std::vector<tcu::IVec2> ranges;																																	\
+			RangeVecBuilder builder(ranges);																																\
+			builder RANGELIST;																																				\
+			partialSpecifyGroup->addChild(new SubDataToUndefinedCase(m_context, #NAME, DESC, TARGET, USAGE, SIZE, &ranges[0], (int)ranges.size(), VERIFY_AS_VERTEX_ARRAY));	\
+		} while (deGetFalse())
+
+		PARTIAL_SPECIFY_CASE(whole_1, "Whole buffer specification with single glBufferSubData()", GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996,
+			<< IVec2(0, 996));
+		PARTIAL_SPECIFY_CASE(whole_2, "Whole buffer specification with two calls", GL_UNIFORM_BUFFER, GL_DYNAMIC_READ, 1728,
+			<< IVec2(729, 999)
+			<< IVec2(0, 729));
+		PARTIAL_SPECIFY_CASE(whole_3, "Whole buffer specification with three calls", GL_TRANSFORM_FEEDBACK_BUFFER, GL_STREAM_COPY, 1944,
+			<< IVec2(0, 421)
+			<< IVec2(1421, 523)
+			<< IVec2(421, 1000));
+		PARTIAL_SPECIFY_CASE(whole_4, "Whole buffer specification with three calls", GL_TRANSFORM_FEEDBACK_BUFFER, GL_STREAM_COPY, 1200,
+			<< IVec2(0, 500)
+			<< IVec2(429, 200)
+			<< IVec2(513, 687));
+
+		PARTIAL_SPECIFY_CASE(low_1, "Low part of buffer specified with single call", GL_ELEMENT_ARRAY_BUFFER, GL_DYNAMIC_DRAW, 1000,
+			<< IVec2(0, 513));
+		PARTIAL_SPECIFY_CASE(low_2, "Low part of buffer specified with two calls", GL_COPY_READ_BUFFER, GL_DYNAMIC_COPY, 996,
+			<< IVec2(0, 98)
+			<< IVec2(98, 511));
+		PARTIAL_SPECIFY_CASE(low_3, "Low part of buffer specified with two calls", GL_COPY_READ_BUFFER, GL_DYNAMIC_COPY, 1200,
+			<< IVec2(0, 591)
+			<< IVec2(371, 400));
+
+		PARTIAL_SPECIFY_CASE(high_1, "High part of buffer specified with single call", GL_COPY_WRITE_BUFFER, GL_STATIC_COPY, 1000,
+			<< IVec2(500, 500));
+		PARTIAL_SPECIFY_CASE(high_2, "High part of buffer specified with two calls", GL_TRANSFORM_FEEDBACK_BUFFER, GL_STREAM_DRAW, 1200,
+			<< IVec2(600, 123)
+			<< IVec2(723, 477));
+		PARTIAL_SPECIFY_CASE(high_3, "High part of buffer specified with two calls", GL_PIXEL_PACK_BUFFER, GL_STREAM_READ, 1200,
+			<< IVec2(600, 200)
+			<< IVec2(601, 599));
+
+		PARTIAL_SPECIFY_CASE(middle_1, "Middle part of buffer specified with single call", GL_PIXEL_UNPACK_BUFFER, GL_STREAM_READ, 2500,
+			<< IVec2(1000, 799));
+		PARTIAL_SPECIFY_CASE(middle_2, "Middle part of buffer specified with two calls", GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, 2500,
+			<< IVec2(780, 220)
+			<< IVec2(1000, 500));
+		PARTIAL_SPECIFY_CASE(middle_3, "Middle part of buffer specified with two calls", GL_ARRAY_BUFFER, GL_STREAM_READ, 2500,
+			<< IVec2(780, 321)
+			<< IVec2(1000, 501));
+
+#undef PARTIAL_SPECIFY_CASE
+	}
+
+	// .random
+	{
+		tcu::TestCaseGroup* const randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Randomized buffer data cases");
+		addChild(randomGroup);
+
+		for (int i = 0; i < 10; i++)
+			randomGroup->addChild(new RandomBufferWriteCase(m_context, de::toString(i).c_str(), "", deInt32Hash(i)));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fBufferWriteTests.hpp b/modules/gles3/functional/es3fBufferWriteTests.hpp
new file mode 100644
index 0000000..7b1738e
--- /dev/null
+++ b/modules/gles3/functional/es3fBufferWriteTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FBUFFERWRITETESTS_HPP
+#define _ES3FBUFFERWRITETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer data upload tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class BufferWriteTests : public TestCaseGroup
+{
+public:
+						BufferWriteTests	(Context& context);
+						~BufferWriteTests	(void);
+
+	void				init				(void);
+
+private:
+						BufferWriteTests	(const BufferWriteTests& other);
+	BufferWriteTests&	operator=			(const BufferWriteTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FBUFFERWRITETESTS_HPP
diff --git a/modules/gles3/functional/es3fBuiltinPrecisionTests.cpp b/modules/gles3/functional/es3fBuiltinPrecisionTests.cpp
new file mode 100644
index 0000000..365ba6a
--- /dev/null
+++ b/modules/gles3/functional/es3fBuiltinPrecisionTests.cpp
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tests for precision and range of GLSL builtins and types.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fBuiltinPrecisionTests.hpp"
+
+#include "deUniquePtr.hpp"
+#include "glsBuiltinPrecisionTests.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace bpt = gls::BuiltinPrecisionTests;
+
+TestCaseGroup* createBuiltinPrecisionTests (Context& context)
+{
+	TestCaseGroup*							group		= new TestCaseGroup(
+		context, "precision", "Builtin precision tests");
+	std::vector<glu::ShaderType>			shaderTypes;
+	de::MovePtr<const bpt::CaseFactories>	es3Cases	= bpt::createES3BuiltinCases();
+
+	shaderTypes.push_back(glu::SHADERTYPE_VERTEX);
+	shaderTypes.push_back(glu::SHADERTYPE_FRAGMENT);
+
+	bpt::addBuiltinPrecisionTests(context.getTestContext(),
+								  context.getRenderContext(),
+								  *es3Cases,
+								  shaderTypes,
+								  *group);
+	return group;
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fBuiltinPrecisionTests.hpp b/modules/gles3/functional/es3fBuiltinPrecisionTests.hpp
new file mode 100644
index 0000000..1ea1a14
--- /dev/null
+++ b/modules/gles3/functional/es3fBuiltinPrecisionTests.hpp
@@ -0,0 +1,42 @@
+#ifndef _ES3FBUILTINPRECISIONTESTS_HPP
+#define _ES3FBUILTINPRECISIONTESTS_HPP
+
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tests for precision and range of GLSL builtins and types.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+TestCaseGroup* createBuiltinPrecisionTests (Context& context);
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FBUILTINPRECISIONTESTS_HPP
diff --git a/modules/gles3/functional/es3fClippingTests.cpp b/modules/gles3/functional/es3fClippingTests.cpp
new file mode 100644
index 0000000..bc73012
--- /dev/null
+++ b/modules/gles3/functional/es3fClippingTests.cpp
@@ -0,0 +1,2066 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Clipping tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fClippingTests.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+
+#include "sglrReferenceContext.hpp"
+#include "sglrGLContext.hpp"
+
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+#include "glwFunctions.hpp"
+
+using namespace glw; // GLint and other GL types
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+using tcu::PixelBufferAccess;
+using tcu::ConstPixelBufferAccess;
+using tcu::TestLog;
+
+static const tcu::Vec4	MASK_COLOR_OK			 = tcu::Vec4(0.0f, 0.1f, 0.0f, 1.0f);
+static const tcu::Vec4	MASK_COLOR_DEV			 = tcu::Vec4(0.8f, 0.5f, 0.0f, 1.0f);
+static const tcu::Vec4	MASK_COLOR_FAIL			 = tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f);
+
+const int					TEST_CANVAS_SIZE  = 200;
+const rr::WindowRectangle	VIEWPORT_WHOLE		(0,						0,					TEST_CANVAS_SIZE,		TEST_CANVAS_SIZE);
+const rr::WindowRectangle	VIEWPORT_CENTER		(TEST_CANVAS_SIZE/4,	TEST_CANVAS_SIZE/4,	TEST_CANVAS_SIZE/2,		TEST_CANVAS_SIZE/2);
+const rr::WindowRectangle	VIEWPORT_CORNER		(TEST_CANVAS_SIZE/2,	TEST_CANVAS_SIZE/2,	TEST_CANVAS_SIZE/2,		TEST_CANVAS_SIZE/2);
+
+
+const char* shaderSourceVertex =	"#version 300 es\n"
+									"in highp vec4 a_position;\n"
+									"in highp vec4 a_color;\n"
+									"in highp float a_pointSize;\n"
+									"out highp vec4 varFragColor;\n"
+									"void main (void)\n"
+									"{\n"
+									"	gl_Position = a_position;\n"
+									"	gl_PointSize = a_pointSize;\n"
+									"	varFragColor = a_color;\n"
+									"}\n";
+const char* shaderSourceFragment =	"#version 300 es\n"
+									"layout(location = 0) out mediump vec4 fragColor;"
+									"in highp vec4 varFragColor;\n"
+									"void main (void)\n"
+									"{\n"
+									"	fragColor = varFragColor;\n"
+									"}\n";
+
+inline bool isBlack (const tcu::IVec4& a)
+{
+	return a.x() == 0 && a.y() == 0 && a.z() == 0;
+}
+
+inline bool isHalfFilled (const tcu::IVec4& a)
+{
+	const tcu::IVec4 halfFilled	(127, 0, 0, 0);
+	const tcu::IVec4 threshold	(20, 256, 256, 256);
+
+	return tcu::boolAll(tcu::lessThanEqual(tcu::abs(a - halfFilled), threshold));
+}
+
+inline bool isLessThanHalfFilled (const tcu::IVec4& a)
+{
+	const int halfFilled = 127;
+	const int threshold	 = 20;
+
+	return a.x() + threshold < halfFilled;
+}
+
+inline bool compareBlackNonBlackPixels (const tcu::IVec4& a, const tcu::IVec4& b)
+{
+	return isBlack(a) == isBlack(b);
+}
+
+inline bool compareColoredPixels (const tcu::IVec4& a, const tcu::IVec4& b)
+{
+	const bool aIsBlack = isBlack(a);
+	const bool bIsBlack = isBlack(b);
+	const tcu::IVec4 threshold(20, 20, 20, 0);
+
+	if (aIsBlack && bIsBlack)
+		return true;
+	if (aIsBlack != bIsBlack)
+		return false;
+
+	return tcu::boolAll(tcu::lessThanEqual(tcu::abs(a - b), threshold));
+}
+
+void blitImageOnBlackSurface(const ConstPixelBufferAccess& src, const PixelBufferAccess& dst)
+{
+	const int			height	= src.getHeight();
+	const int			width	= src.getWidth();
+	const tcu::IVec4	black	= tcu::IVec4(0, 0, 0, 255);
+
+	for (int y = 0; y < height; y++)
+	for (int x = 0; x < width; x++)
+	{
+		const tcu::IVec4 cSrc = src.getPixelInt(x, y);
+		const tcu::IVec4 cDst = tcu::IVec4(cSrc.x(), cSrc.y(), cSrc.z(), 255);
+
+		dst.setPixel(cDst, x, y);
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Pixelwise comparison of two images.
+ * \note copied & modified from glsRasterizationTests
+ *
+ * Kernel radius defines maximum allowed distance. If radius is 0, only
+ * perfect match is allowed. Radius of 1 gives a 3x3 kernel. Pixels are
+ * equal if pixelCmp returns true..
+ *
+ * Return values:  -1 = Perfect match
+ *					0 = Deviation within kernel
+ *				   >0 = Number of faulty pixels
+ *//*--------------------------------------------------------------------*/
+inline int compareImages (tcu::TestLog& log, const ConstPixelBufferAccess& test, const ConstPixelBufferAccess& ref, const PixelBufferAccess& diffMask, int kernelRadius, bool (*pixelCmp)(const tcu::IVec4& a, const tcu::IVec4& b))
+{
+	const int			height				= test.getHeight();
+	const int			width				= test.getWidth();
+	int					deviatingPixels		= 0;
+	int					faultyPixels		= 0;
+	int					compareFailed		= -1;
+
+	tcu::clear(diffMask, MASK_COLOR_OK);
+
+	for (int y = 0; y < height; y++)
+	{
+		for (int x = 0; x < width; x++)
+		{
+			const tcu::IVec4 cRef	= ref.getPixelInt(x, y);
+			const tcu::IVec4 cTest	= test.getPixelInt(x, y);
+
+			// Pixelwise match, no deviation or fault
+			if ((*pixelCmp)(cRef, cTest))
+				continue;
+
+			// Deviation
+			{
+				const int radius	= kernelRadius;
+				bool foundRef		= false;
+				bool foundTest		= false;
+
+				// edges are considered a "deviation" too. The suitable pixel could be "behind" the edge
+				if (y < radius || x < radius || y + radius >= height || x + radius >= width)
+				{
+					foundRef	= true;
+					foundTest	= true;
+				}
+				else
+				{
+					// find ref
+					for (int kY = y - radius; kY <= y + radius; kY++)
+					for (int kX = x - radius; kX <= x + radius; kX++)
+					{
+						if ((*pixelCmp)(cRef, test.getPixelInt(kX, kY)))
+						{
+							foundRef = true;
+							break;
+						}
+					}
+
+					// find result
+					for (int kY = y - radius; kY <= y + radius; kY++)
+					for (int kX = x - radius; kX <= x + radius; kX++)
+					{
+						if ((*pixelCmp)(cTest, ref.getPixelInt(kX, kY)))
+						{
+							foundTest = true;
+							break;
+						}
+					}
+				}
+
+				// A pixel is deviating if the reference color is found inside the kernel and (~= every pixel reference draws must be drawn by the gl too)
+				// the result color is found in the reference image inside the kernel         (~= every pixel gl draws must be drawn by the reference too)
+				if (foundRef && foundTest)
+				{
+					diffMask.setPixel(MASK_COLOR_DEV, x, y);
+					if (compareFailed == -1)
+						compareFailed = 0;
+					deviatingPixels++;
+					continue;
+				}
+			}
+
+			diffMask.setPixel(MASK_COLOR_FAIL, x, y);
+			faultyPixels++;									// The pixel is faulty if the color is not found
+			compareFailed = 1;
+		}
+	}
+
+	log << TestLog::Message << deviatingPixels	<< " deviating pixel(s) found." << TestLog::EndMessage;
+	log << TestLog::Message << faultyPixels		<< " faulty pixel(s) found." << TestLog::EndMessage;
+
+	return (compareFailed == 1 ? faultyPixels : compareFailed);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Pixelwise comparison of two images.
+ *
+ * Kernel radius defines maximum allowed distance. If radius is 0, only
+ * perfect match is allowed. Radius of 1 gives a 3x3 kernel. Pixels are
+ * equal if they both are black, or both are non-black.
+ *
+ * Return values:  -1 = Perfect match
+ *					0 = Deviation within kernel
+ *				   >0 = Number of faulty pixels
+ *//*--------------------------------------------------------------------*/
+int compareBlackNonBlackImages (tcu::TestLog& log, const ConstPixelBufferAccess& test, const ConstPixelBufferAccess& ref, const PixelBufferAccess& diffMask, int kernelRadius)
+{
+	return compareImages(log, test, ref, diffMask, kernelRadius, compareBlackNonBlackPixels);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Pixelwise comparison of two images.
+ *
+ * Kernel radius defines maximum allowed distance. If radius is 0, only
+ * perfect match is allowed. Radius of 1 gives a 3x3 kernel. Pixels are
+ * equal if they both are black, or both are non-black with color values
+ * close to each other.
+ *
+ * Return values:  -1 = Perfect match
+ *					0 = Deviation within kernel
+ *				   >0 = Number of faulty pixels
+ *//*--------------------------------------------------------------------*/
+int compareColoredImages (tcu::TestLog& log, const ConstPixelBufferAccess& test, const ConstPixelBufferAccess& ref, const PixelBufferAccess& diffMask, int kernelRadius)
+{
+	return compareImages(log, test, ref, diffMask, kernelRadius, compareColoredPixels);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Overdraw check verification
+ *
+ * Check that image does not have at any point a
+ * pixel with red component value > 0.5
+ *
+ * Return values:  false = area not filled, or leaking
+ *//*--------------------------------------------------------------------*/
+bool checkHalfFilledImageOverdraw (tcu::TestLog& log, const tcu::RenderTarget& m_renderTarget, const ConstPixelBufferAccess& image, const PixelBufferAccess& output)
+{
+	const int			height				= image.getHeight();
+	const int			width				= image.getWidth();
+
+	bool				faulty				= false;
+
+	tcu::clear(output, MASK_COLOR_OK);
+
+	for (int y = 0; y < height; y++)
+	{
+		for (int x = 0; x < width; x++)
+		{
+			const tcu::IVec4	cTest	= image.getPixelInt(x, y);
+
+			const bool pixelValid = isBlack(cTest) || isHalfFilled(cTest) || (m_renderTarget.getNumSamples() > 1 && isLessThanHalfFilled(cTest));
+
+			if (!pixelValid)
+			{
+				output.setPixel(MASK_COLOR_FAIL, x, y);
+				faulty = true;
+			}
+		}
+	}
+
+	if (faulty)
+		log << TestLog::Message << "Faulty pixel(s) found." << TestLog::EndMessage;
+
+	return !faulty;
+}
+
+void checkPointSize (const glw::Functions& gl, float pointSize)
+{
+	GLfloat pointSizeRange[2] = {0,0};
+	gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
+	if (pointSizeRange[1] < pointSize)
+		throw tcu::NotSupportedError("Maximum point size is too low for this test");
+}
+
+void checkLineWidth (const glw::Functions& gl, float lineWidth)
+{
+	GLfloat lineWidthRange[2] = {0,0};
+	gl.getFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);
+	if (lineWidthRange[1] < lineWidth)
+		throw tcu::NotSupportedError("Maximum line width is too low for this test");
+}
+
+tcu::Vec3 IVec3ToVec3 (const tcu::IVec3& v)
+{
+	return tcu::Vec3((float)v.x(), (float)v.y(), (float)v.z());
+}
+
+bool pointOnTriangle (const tcu::IVec3& p, const tcu::IVec3& t0, const tcu::IVec3& t1, const tcu::IVec3& t2)
+{
+	// Must be on the plane
+	const tcu::IVec3 n = tcu::cross(t1 - t0, t2 - t0);
+	const tcu::IVec3 d = (p - t0);
+
+	if (tcu::dot(n, d))
+		return false;
+
+	// Must be within the triangle area
+	if (deSign32(tcu::dot(n, tcu::cross(t1 - t0, p - t0))) == deSign32(tcu::dot(n, tcu::cross(t2 - t0, p - t0))))
+		return false;
+	if (deSign32(tcu::dot(n, tcu::cross(t2 - t1, p - t1))) == deSign32(tcu::dot(n, tcu::cross(t0 - t1, p - t1))))
+		return false;
+	if (deSign32(tcu::dot(n, tcu::cross(t0 - t2, p - t2))) == deSign32(tcu::dot(n, tcu::cross(t1 - t2, p - t2))))
+		return false;
+
+	return true;
+}
+
+bool pointsOnLine (const tcu::IVec2& t0, const tcu::IVec2& t1, const tcu::IVec2& t2)
+{
+	return (t1 - t0).x() * (t2 - t0).y() - (t2 - t0).x() * (t1 - t0).y() == 0;
+}
+
+// returns true for cases where polygon is (almost) along xz or yz planes (normal.z < 0.1)
+// \note[jarkko] Doesn't have to be accurate, just to detect some obviously bad cases
+bool twoPointClippedTriangleInvisible(const tcu::Vec3& p, const tcu::IVec3& dir1, const tcu::IVec3& dir2)
+{
+	// fixed-point-like coords
+	const deInt64					fixedScale	= 64;
+	const deInt64					farValue	= 1024;
+	const tcu::Vector<deInt64, 3>	d1			= tcu::Vector<deInt64, 3>(dir1.x(), dir1.y(), dir1.z());
+	const tcu::Vector<deInt64, 3>	d2			= tcu::Vector<deInt64, 3>(dir2.x(), dir2.y(), dir2.z());
+	const tcu::Vector<deInt64, 3>	pfixed		= tcu::Vector<deInt64, 3>(deFloorFloatToInt32(p.x() * fixedScale), deFloorFloatToInt32(p.y() * fixedScale), deFloorFloatToInt32(p.z() * fixedScale));
+	const tcu::Vector<deInt64, 3>	normalDir	= tcu::cross(d1*farValue - pfixed, d2*farValue - pfixed);
+	const deInt64					normalLen2	= tcu::lengthSquared(normalDir);
+
+	return (normalDir.z() * normalDir.z() - normalLen2/100) < 0;
+}
+
+std::string genClippingPointInfoString(const tcu::Vec4& p)
+{
+	std::ostringstream msg;
+
+	if (p.x() < -p.w())		msg << "\t(-X clip)";
+	if (p.x() >  p.w())		msg << "\t(+X clip)";
+	if (p.y() < -p.w())		msg << "\t(-Y clip)";
+	if (p.y() >  p.w())		msg << "\t(+Y clip)";
+	if (p.z() < -p.w())		msg << "\t(-Z clip)";
+	if (p.z() >  p.w())		msg << "\t(+Z clip)";
+
+	return msg.str();
+}
+
+std::string genColorString(const tcu::Vec4& p)
+{
+	const tcu::Vec4 white	(1.0f, 1.0f, 1.0f, 1.0f);
+	const tcu::Vec4 red		(1.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4 yellow	(1.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 blue	(0.0f, 0.0f, 1.0f, 1.0f);
+
+	if (p == white)		return "(white)";
+	if (p == red)		return "(red)";
+	if (p == yellow)	return "(yellow)";
+	if (p == blue)		return "(blue)";
+	return "";
+}
+
+class PositionColorShader : public sglr::ShaderProgram
+{
+public:
+	enum
+	{
+		VARYINGLOC_COLOR = 0
+	};
+
+			PositionColorShader (void);
+
+	void	shadeVertices		(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void	shadeFragments		(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+};
+
+PositionColorShader::PositionColorShader (void)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_pointSize", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(shaderSourceVertex)
+							<< sglr::pdec::FragmentSource(shaderSourceFragment))
+{
+}
+
+void PositionColorShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const int positionAttrLoc = 0;
+		const int colorAttrLoc = 1;
+		const int pointSizeAttrLoc = 2;
+
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		// Transform to position
+		packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx);
+
+		// output point size
+		packet.pointSize = rr::readVertexAttribFloat(inputs[pointSizeAttrLoc], packet.instanceNdx, packet.vertexNdx).x();
+
+		// Pass color to FS
+		packet.outputs[VARYINGLOC_COLOR] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void PositionColorShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::FragmentPacket& packet = packets[packetNdx];
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, VARYINGLOC_COLOR, fragNdx));
+	}
+}
+
+class RenderTestCase : public TestCase
+{
+public:
+					RenderTestCase	(Context& context, const char* name, const char* description);
+
+	virtual void	testRender		(void) = DE_NULL;
+	virtual void	init			(void) { }
+
+	IterateResult	iterate			(void);
+};
+
+RenderTestCase::RenderTestCase (Context& context, const char* name, const char* description)
+	: TestCase	(context, name, description)
+{
+}
+
+RenderTestCase::IterateResult RenderTestCase::iterate (void)
+{
+	const int width	 = m_context.getRenderTarget().getWidth();
+	const int height = m_context.getRenderTarget().getHeight();
+
+	m_testCtx.getLog() << TestLog::Message << "Render target size: " << width << "x" << height << TestLog::EndMessage;
+	if (width < TEST_CANVAS_SIZE || height < TEST_CANVAS_SIZE)
+		throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(TEST_CANVAS_SIZE) + "x" + de::toString(TEST_CANVAS_SIZE));
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); // success by default
+	testRender();
+
+	return STOP;
+}
+
+class PointCase : public RenderTestCase
+{
+public:
+									PointCase	(Context& context, const char* name, const char* description, const tcu::Vec4* pointsBegin, const tcu::Vec4* pointsEnd, float pointSize, const rr::WindowRectangle& viewport);
+
+	void							init		(void);
+	void							testRender	(void);
+
+private:
+	const std::vector<tcu::Vec4>	m_points;
+	const float						m_pointSize;
+	const rr::WindowRectangle		m_viewport;
+};
+
+PointCase::PointCase (Context& context, const char* name, const char* description, const tcu::Vec4* pointsBegin, const tcu::Vec4* pointsEnd, float pointSize, const rr::WindowRectangle& viewport)
+	: RenderTestCase(context, name, description)
+	, m_points		(pointsBegin, pointsEnd)
+	, m_pointSize	(pointSize)
+	, m_viewport	(viewport)
+{
+}
+
+void PointCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	checkPointSize (gl, m_pointSize);
+}
+
+void PointCase::testRender (void)
+{
+	using tcu::TestLog;
+
+	const int numSamples			= de::max(m_context.getRenderTarget().getNumSamples(), 1);
+
+	tcu::TestLog&					log			= m_testCtx.getLog();
+	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
+	sglr::ReferenceContextLimits	limits;
+	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
+	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+	PositionColorShader				program;
+	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	sglr::Context*					contexts[2] = {&glesContext, &refContext};
+	tcu::Surface*					surfaces[2] = {&testSurface, &refSurface};
+
+	// log the purpose of the test
+	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
+	log << TestLog::Message << "Rendering points with point size " << m_pointSize << ". Coordinates:" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < m_points.size(); ++ndx)
+		log << TestLog::Message
+				<< "\tx=" << m_points[ndx].x()
+				<< "\ty=" << m_points[ndx].y()
+				<< "\tz=" << m_points[ndx].z()
+				<< "\tw=" << m_points[ndx].w()
+				<< "\t" << genClippingPointInfoString(m_points[ndx])
+				<< TestLog::EndMessage;
+
+	for (int contextNdx = 0; contextNdx < 2; ++contextNdx)
+	{
+		sglr::Context&	ctx				= *contexts[contextNdx];
+		tcu::Surface&	dstSurface		= *surfaces[contextNdx];
+		const deUint32	programId		= ctx.createProgram(&program);
+		const GLint		positionLoc		= ctx.getAttribLocation(programId, "a_position");
+		const GLint		pointSizeLoc	= ctx.getAttribLocation(programId, "a_pointSize");
+		const GLint		colorLoc		= ctx.getAttribLocation(programId, "a_color");
+
+		ctx.clearColor					(0, 0, 0, 1);
+		ctx.clearDepthf					(1.0f);
+		ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
+		ctx.useProgram					(programId);
+		ctx.enableVertexAttribArray		(positionLoc);
+		ctx.vertexAttribPointer			(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &m_points[0]);
+		ctx.vertexAttrib1f				(pointSizeLoc, m_pointSize);
+		ctx.vertexAttrib4f				(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+		ctx.drawArrays					(GL_POINTS, 0, (glw::GLsizei)m_points.size());
+		ctx.disableVertexAttribArray	(positionLoc);
+		ctx.useProgram					(0);
+		ctx.deleteProgram				(programId);
+		ctx.finish						();
+
+		ctx.readPixels(dstSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	}
+
+	// do the comparison
+	{
+		tcu::Surface		diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		const int			kernelRadius	= 1;
+		int					faultyPixels;
+
+		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
+
+		faultyPixels = compareBlackNonBlackImages(log, testSurface.getAccess(), refSurface.getAccess(), diffMask.getAccess(), kernelRadius);
+
+		if (faultyPixels > 0)
+		{
+			log << TestLog::ImageSet("Images", "Image comparison")
+				<< TestLog::Image("TestImage", "Test image", testSurface.getAccess())
+				<< TestLog::Image("ReferenceImage", "Reference image", refSurface.getAccess())
+				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+				<< TestLog::EndImageSet
+				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+		}
+	}
+}
+
+class LineRenderTestCase : public RenderTestCase
+{
+public:
+	struct ColoredLineData
+	{
+		tcu::Vec4 p0;
+		tcu::Vec4 c0;
+		tcu::Vec4 p1;
+		tcu::Vec4 c1;
+	};
+
+	struct ColorlessLineData
+	{
+		tcu::Vec4 p0;
+		tcu::Vec4 p1;
+	};
+										LineRenderTestCase		(Context& context, const char* name, const char* description, const ColoredLineData*   linesBegin, const ColoredLineData*   linesEnd, float lineWidth, const rr::WindowRectangle& viewport);
+										LineRenderTestCase		(Context& context, const char* name, const char* description, const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport);
+
+	virtual void						verifyImage				(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess) = DE_NULL;
+	void								init					(void);
+	void								testRender				(void);
+
+private:
+	std::vector<ColoredLineData>		convertToColoredLines	(const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd);
+
+	const std::vector<ColoredLineData>	m_lines;
+	const float							m_lineWidth;
+	const rr::WindowRectangle			m_viewport;
+};
+
+LineRenderTestCase::LineRenderTestCase (Context& context, const char* name, const char* description, const ColoredLineData* linesBegin, const ColoredLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport)
+	: RenderTestCase	(context, name, description)
+	, m_lines			(linesBegin, linesEnd)
+	, m_lineWidth		(lineWidth)
+	, m_viewport		(viewport)
+{
+}
+
+LineRenderTestCase::LineRenderTestCase (Context& context, const char* name, const char* description, const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport)
+	: RenderTestCase	(context, name, description)
+	, m_lines			(convertToColoredLines(linesBegin, linesEnd))
+	, m_lineWidth		(lineWidth)
+	, m_viewport		(viewport)
+{
+}
+
+void LineRenderTestCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	checkLineWidth (gl, m_lineWidth);
+}
+
+void LineRenderTestCase::testRender (void)
+{
+	using tcu::TestLog;
+
+	const int numSamples			= de::max(m_context.getRenderTarget().getNumSamples(), 1);
+	const int verticesPerLine		= 2;
+
+	tcu::TestLog&					log			= m_testCtx.getLog();
+	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
+	sglr::ReferenceContextLimits	limits;
+	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
+	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+	PositionColorShader				program;
+	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	sglr::Context*					contexts[2] = {&glesContext, &refContext};
+	tcu::Surface*					surfaces[2] = {&testSurface, &refSurface};
+
+	// log the purpose of the test
+	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
+	log << TestLog::Message << "Rendering lines with line width " << m_lineWidth << ". Coordinates:" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < m_lines.size(); ++ndx)
+	{
+		const std::string fromProperties = genClippingPointInfoString(m_lines[ndx].p0);
+		const std::string toProperties   = genClippingPointInfoString(m_lines[ndx].p1);
+
+		log << TestLog::Message << "\tfrom (x=" << m_lines[ndx].p0.x() << "\ty=" << m_lines[ndx].p0.y() << "\tz=" << m_lines[ndx].p0.z() << "\tw=" << m_lines[ndx].p0.w() << ")\t" << fromProperties << TestLog::EndMessage;
+		log << TestLog::Message << "\tto   (x=" << m_lines[ndx].p1.x() << "\ty=" << m_lines[ndx].p1.y() << "\tz=" << m_lines[ndx].p1.z() << "\tw=" << m_lines[ndx].p1.w() << ")\t" << toProperties   << TestLog::EndMessage;
+		log << TestLog::Message << TestLog::EndMessage;
+	}
+
+	// render test image
+	for (int contextNdx = 0; contextNdx < 2; ++contextNdx)
+	{
+		sglr::Context&	ctx				= *contexts[contextNdx];
+		tcu::Surface&	dstSurface		= *surfaces[contextNdx];
+		const deUint32	programId		= ctx.createProgram(&program);
+		const GLint		positionLoc		= ctx.getAttribLocation(programId, "a_position");
+		const GLint		colorLoc		= ctx.getAttribLocation(programId, "a_color");
+
+		ctx.clearColor					(0, 0, 0, 1);
+		ctx.clearDepthf					(1.0f);
+		ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
+		ctx.useProgram					(programId);
+		ctx.enableVertexAttribArray		(positionLoc);
+		ctx.enableVertexAttribArray		(colorLoc);
+		ctx.vertexAttribPointer			(positionLoc,	4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_lines[0].p0);
+		ctx.vertexAttribPointer			(colorLoc,		4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_lines[0].c0);
+		ctx.lineWidth					(m_lineWidth);
+		ctx.drawArrays					(GL_LINES, 0, verticesPerLine * (glw::GLsizei)m_lines.size());
+		ctx.disableVertexAttribArray	(positionLoc);
+		ctx.disableVertexAttribArray	(colorLoc);
+		ctx.useProgram					(0);
+		ctx.deleteProgram				(programId);
+		ctx.finish						();
+
+		ctx.readPixels(dstSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	}
+
+	// compare
+	verifyImage(testSurface.getAccess(), refSurface.getAccess());
+}
+
+std::vector<LineRenderTestCase::ColoredLineData> LineRenderTestCase::convertToColoredLines(const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd)
+{
+	std::vector<ColoredLineData> ret;
+
+	for (const ColorlessLineData* it = linesBegin; it != linesEnd; ++it)
+	{
+		ColoredLineData r;
+
+		r.p0 = (*it).p0;
+		r.c0 = tcu::Vec4(1, 1, 1, 1);
+		r.p1 = (*it).p1;
+		r.c1 = tcu::Vec4(1, 1, 1, 1);
+
+		ret.push_back(r);
+	}
+
+	return ret;
+}
+
+class LineCase : public LineRenderTestCase
+{
+public:
+				LineCase			(Context& context, const char* name, const char* description, const LineRenderTestCase::ColorlessLineData* linesBegin, const LineRenderTestCase::ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport, int searchKernelSize = 1);
+
+	void		verifyImage			(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
+
+private:
+	const int	m_searchKernelSize;
+};
+
+LineCase::LineCase (Context& context, const char* name, const char* description, const LineRenderTestCase::ColorlessLineData* linesBegin, const LineRenderTestCase::ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport, int searchKernelSize)
+	: LineRenderTestCase	(context, name, description, linesBegin, linesEnd, lineWidth, viewport)
+	, m_searchKernelSize	(searchKernelSize)
+{
+}
+
+void LineCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
+{
+	const int	faultyLimit = 6;
+	int			faultyPixels;
+
+	tcu::TestLog&		log			= m_testCtx.getLog();
+	tcu::Surface		diffMask	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+	log << TestLog::Message << "Deviation within radius of " << m_searchKernelSize << " is allowed." << TestLog::EndMessage;
+	log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;
+
+	faultyPixels = compareBlackNonBlackImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), m_searchKernelSize);
+
+	if (faultyPixels > faultyLimit)
+	{
+		log << TestLog::ImageSet("Images", "Image comparison")
+			<< TestLog::Image("TestImage", "Test image", testImageAccess)
+			<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
+			<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+			<< TestLog::EndImageSet
+			<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+	}
+}
+
+class ColoredLineCase : public LineRenderTestCase
+{
+public:
+	ColoredLineCase (Context& context, const char* name, const char* description, const LineRenderTestCase::ColoredLineData* linesBegin, const LineRenderTestCase::ColoredLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport);
+
+	void verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
+};
+
+ColoredLineCase::ColoredLineCase (Context& context, const char* name, const char* description, const LineRenderTestCase::ColoredLineData* linesBegin, const LineRenderTestCase::ColoredLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport)
+	: LineRenderTestCase (context, name, description, linesBegin, linesEnd, lineWidth, viewport)
+{
+}
+
+void ColoredLineCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
+{
+	const bool		msaa	= m_context.getRenderTarget().getNumSamples() > 1;
+	tcu::TestLog&	log		= m_testCtx.getLog();
+
+	if (!msaa)
+	{
+		const int	kernelRadius	= 1;
+		const int	faultyLimit		= 6;
+		int			faultyPixels;
+
+		tcu::Surface diffMask(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
+		log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;
+
+		faultyPixels = compareColoredImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), kernelRadius);
+
+		if (faultyPixels > faultyLimit)
+		{
+			log << TestLog::ImageSet("Images", "Image comparison")
+				<< TestLog::Image("TestImage", "Test image", testImageAccess)
+				<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
+				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+				<< TestLog::EndImageSet
+				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+		}
+	}
+	else
+	{
+		const float threshold = 0.3f;
+		if (!tcu::fuzzyCompare(log, "Images", "", referenceImageAccess, testImageAccess, threshold, tcu::COMPARE_LOG_ON_ERROR))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+	}
+}
+
+class TriangleCaseBase : public RenderTestCase
+{
+public:
+	struct TriangleData
+	{
+		tcu::Vec4 p0;
+		tcu::Vec4 c0;
+		tcu::Vec4 p1;
+		tcu::Vec4 c1;
+		tcu::Vec4 p2;
+		tcu::Vec4 c2;
+	};
+
+										TriangleCaseBase	(Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport);
+
+	virtual void						verifyImage			(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess) = DE_NULL;
+	void								testRender			(void);
+
+private:
+	const std::vector<TriangleData>		m_polys;
+	const rr::WindowRectangle			m_viewport;
+};
+
+TriangleCaseBase::TriangleCaseBase (Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport)
+	: RenderTestCase(context, name, description)
+	, m_polys		(polysBegin, polysEnd)
+	, m_viewport	(viewport)
+{
+}
+
+void TriangleCaseBase::testRender (void)
+{
+	using tcu::TestLog;
+
+	const int numSamples			= de::max(m_context.getRenderTarget().getNumSamples(), 1);
+	const int verticesPerTriangle	= 3;
+
+	tcu::TestLog&					log			= m_testCtx.getLog();
+	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
+	sglr::ReferenceContextLimits	limits;
+	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
+	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+	PositionColorShader				program;
+	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	sglr::Context*					contexts[2] = {&glesContext, &refContext};
+	tcu::Surface*					surfaces[2] = {&testSurface, &refSurface};
+
+	// log the purpose of the test
+	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
+	log << TestLog::Message << "Rendering triangles. Coordinates:" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < m_polys.size(); ++ndx)
+	{
+		const std::string v0Properties = genClippingPointInfoString(m_polys[ndx].p0);
+		const std::string v1Properties = genClippingPointInfoString(m_polys[ndx].p1);
+		const std::string v2Properties = genClippingPointInfoString(m_polys[ndx].p2);
+		const std::string c0Properties = genColorString(m_polys[ndx].c0);
+		const std::string c1Properties = genColorString(m_polys[ndx].c1);
+		const std::string c2Properties = genColorString(m_polys[ndx].c2);
+
+		log << TestLog::Message << "\tv0 (x=" << m_polys[ndx].p0.x() << "\ty=" << m_polys[ndx].p0.y() << "\tz=" << m_polys[ndx].p0.z() << "\tw=" << m_polys[ndx].p0.w() << ")\t" << v0Properties << "\t" << c0Properties << TestLog::EndMessage;
+		log << TestLog::Message << "\tv1 (x=" << m_polys[ndx].p1.x() << "\ty=" << m_polys[ndx].p1.y() << "\tz=" << m_polys[ndx].p1.z() << "\tw=" << m_polys[ndx].p1.w() << ")\t" << v1Properties << "\t" << c1Properties << TestLog::EndMessage;
+		log << TestLog::Message << "\tv2 (x=" << m_polys[ndx].p2.x() << "\ty=" << m_polys[ndx].p2.y() << "\tz=" << m_polys[ndx].p2.z() << "\tw=" << m_polys[ndx].p2.w() << ")\t" << v2Properties << "\t" << c2Properties << TestLog::EndMessage;
+		log << TestLog::Message << TestLog::EndMessage;
+	}
+
+	// render test image
+	for (int contextNdx = 0; contextNdx < 2; ++contextNdx)
+	{
+		sglr::Context&	ctx				= *contexts[contextNdx];
+		tcu::Surface&	dstSurface		= *surfaces[contextNdx];
+		const deUint32	programId		= ctx.createProgram(&program);
+		const GLint		positionLoc		= ctx.getAttribLocation(programId, "a_position");
+		const GLint		colorLoc		= ctx.getAttribLocation(programId, "a_color");
+
+		ctx.clearColor					(0, 0, 0, 1);
+		ctx.clearDepthf					(1.0f);
+		ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
+		ctx.useProgram					(programId);
+		ctx.enableVertexAttribArray		(positionLoc);
+		ctx.enableVertexAttribArray		(colorLoc);
+		ctx.vertexAttribPointer			(positionLoc,	4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_polys[0].p0);
+		ctx.vertexAttribPointer			(colorLoc,		4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_polys[0].c0);
+		ctx.drawArrays					(GL_TRIANGLES, 0, verticesPerTriangle * (glw::GLsizei)m_polys.size());
+		ctx.disableVertexAttribArray	(positionLoc);
+		ctx.disableVertexAttribArray	(colorLoc);
+		ctx.useProgram					(0);
+		ctx.deleteProgram				(programId);
+		ctx.finish						();
+
+		ctx.readPixels(dstSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	}
+
+	verifyImage(testSurface.getAccess(), refSurface.getAccess());
+}
+
+class TriangleCase : public TriangleCaseBase
+{
+public:
+			TriangleCase	(Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport);
+
+	void	verifyImage		(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
+};
+
+TriangleCase::TriangleCase (Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport)
+	: TriangleCaseBase(context, name, description, polysBegin, polysEnd, viewport)
+{
+}
+
+void TriangleCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
+{
+	const int			kernelRadius	= 1;
+	const int			faultyLimit		= 6;
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	int					faultyPixels;
+
+	log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+	log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
+	log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;
+
+	faultyPixels = compareBlackNonBlackImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), kernelRadius);
+
+	if (faultyPixels > faultyLimit)
+	{
+		log << TestLog::ImageSet("Images", "Image comparison")
+			<< TestLog::Image("TestImage", "Test image", testImageAccess)
+			<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
+			<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+			<< TestLog::EndImageSet
+			<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+	}
+}
+
+class TriangleAttributeCase : public TriangleCaseBase
+{
+public:
+			TriangleAttributeCase	(Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport);
+
+	void	verifyImage				(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
+};
+
+TriangleAttributeCase::TriangleAttributeCase (Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport)
+	: TriangleCaseBase(context, name, description, polysBegin, polysEnd, viewport)
+{
+}
+
+void TriangleAttributeCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
+{
+	const bool		msaa	= m_context.getRenderTarget().getNumSamples() > 1;
+	tcu::TestLog&	log		= m_testCtx.getLog();
+
+	if (!msaa)
+	{
+		const int		kernelRadius	= 1;
+		const int		faultyLimit		= 6;
+		int				faultyPixels;
+		tcu::Surface	diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
+		log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;
+		faultyPixels = compareColoredImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), kernelRadius);
+
+		if (faultyPixels > faultyLimit)
+		{
+			log << TestLog::ImageSet("Images", "Image comparison")
+				<< TestLog::Image("TestImage", "Test image", testImageAccess)
+				<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
+				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+				<< TestLog::EndImageSet
+				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+		}
+	}
+	else
+	{
+		const float threshold = 0.3f;
+		if (!tcu::fuzzyCompare(log, "Images", "", referenceImageAccess, testImageAccess, threshold, tcu::COMPARE_LOG_ON_ERROR))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+	}
+}
+
+class FillTest : public RenderTestCase
+{
+public:
+								FillTest	(Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport);
+
+	virtual void				render		(sglr::Context& ctx) = DE_NULL;
+	void						testRender	(void);
+
+protected:
+	const rr::WindowRectangle	m_viewport;
+};
+
+FillTest::FillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport)
+	: RenderTestCase(context, name, description)
+	, m_viewport	(viewport)
+{
+}
+
+void FillTest::testRender (void)
+{
+	using tcu::TestLog;
+
+	const int						numSamples	= 1;
+
+	tcu::TestLog&					log			= m_testCtx.getLog();
+	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
+	sglr::ReferenceContextLimits	limits;
+	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
+	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	render(glesContext);
+	glesContext.readPixels(testSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	render(refContext);
+	refContext.readPixels(refSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// check overdraw
+	{
+		bool				overdrawOk;
+		tcu::Surface		outputImage(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+		log << TestLog::Message << "Checking for overdraw " << TestLog::EndMessage;
+		overdrawOk = checkHalfFilledImageOverdraw(log, m_context.getRenderTarget(), testSurface.getAccess(), outputImage.getAccess());
+
+		if (!overdrawOk)
+		{
+			log << TestLog::ImageSet("Images", "Image comparison")
+				<< TestLog::Image("TestImage", "Test image", testSurface.getAccess())
+				<< TestLog::Image("InvalidPixels", "Invalid pixels", outputImage.getAccess())
+				<< TestLog::EndImageSet
+				<< tcu::TestLog::Message << "Got overdraw." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got overdraw");
+		}
+	}
+
+	// compare & check missing pixels
+	{
+		const int			kernelRadius	= 1;
+		tcu::Surface		diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		int					faultyPixels;
+
+		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
+		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
+
+		blitImageOnBlackSurface(refSurface.getAccess(), refSurface.getAccess()); // makes images look right in Candy
+
+		faultyPixels = compareBlackNonBlackImages(log, testSurface.getAccess(), refSurface.getAccess(), diffMask.getAccess(), kernelRadius);
+
+		if (faultyPixels > 0)
+		{
+			log << TestLog::ImageSet("Images", "Image comparison")
+				<< TestLog::Image("TestImage", "Test image", testSurface.getAccess())
+				<< TestLog::Image("ReferenceImage", "Reference image", refSurface.getAccess())
+				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
+				<< TestLog::EndImageSet
+				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+		}
+	}
+}
+
+class TriangleFillTest : public FillTest
+{
+public:
+	struct FillTriangle
+	{
+		tcu::Vec4 v0;
+		tcu::Vec4 c0;
+		tcu::Vec4 v1;
+		tcu::Vec4 c1;
+		tcu::Vec4 v2;
+		tcu::Vec4 c2;
+	};
+
+								TriangleFillTest	(Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport);
+
+	void						render				(sglr::Context& ctx);
+
+protected:
+	std::vector<FillTriangle>	m_triangles;
+};
+
+TriangleFillTest::TriangleFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport)
+	: FillTest(context, name, description, viewport)
+{
+}
+
+void TriangleFillTest::render (sglr::Context& ctx)
+{
+	const int			verticesPerTriangle		= 3;
+	PositionColorShader program;
+	const deUint32		programId				= ctx.createProgram(&program);
+	const GLint			positionLoc				= ctx.getAttribLocation(programId, "a_position");
+	const GLint			colorLoc				= ctx.getAttribLocation(programId, "a_color");
+	tcu::TestLog&		log						= m_testCtx.getLog();
+
+	// log the purpose of the test
+	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
+	log << TestLog::Message << "Rendering triangles. Coordinates:" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < m_triangles.size(); ++ndx)
+	{
+		const std::string v0Properties = genClippingPointInfoString(m_triangles[ndx].v0);
+		const std::string v1Properties = genClippingPointInfoString(m_triangles[ndx].v1);
+		const std::string v2Properties = genClippingPointInfoString(m_triangles[ndx].v2);
+
+		log << TestLog::Message << "\tv0 (x=" << m_triangles[ndx].v0.x() << "\ty=" << m_triangles[ndx].v0.y() << "\tz=" << m_triangles[ndx].v0.z() << "\tw=" << m_triangles[ndx].v0.w() << ")\t" << v0Properties << TestLog::EndMessage;
+		log << TestLog::Message << "\tv1 (x=" << m_triangles[ndx].v1.x() << "\ty=" << m_triangles[ndx].v1.y() << "\tz=" << m_triangles[ndx].v1.z() << "\tw=" << m_triangles[ndx].v1.w() << ")\t" << v1Properties << TestLog::EndMessage;
+		log << TestLog::Message << "\tv2 (x=" << m_triangles[ndx].v2.x() << "\ty=" << m_triangles[ndx].v2.y() << "\tz=" << m_triangles[ndx].v2.z() << "\tw=" << m_triangles[ndx].v2.w() << ")\t" << v2Properties << TestLog::EndMessage;
+		log << TestLog::Message << TestLog::EndMessage;
+	}
+
+	ctx.clearColor					(0, 0, 0, 1);
+	ctx.clearDepthf					(1.0f);
+	ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
+	ctx.useProgram					(programId);
+	ctx.blendFunc					(GL_ONE, GL_ONE);
+	ctx.enable						(GL_BLEND);
+	ctx.enableVertexAttribArray		(positionLoc);
+	ctx.enableVertexAttribArray		(colorLoc);
+	ctx.vertexAttribPointer			(positionLoc,	4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_triangles[0].v0);
+	ctx.vertexAttribPointer			(colorLoc,		4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_triangles[0].c0);
+	ctx.drawArrays					(GL_TRIANGLES, 0, verticesPerTriangle * (glw::GLsizei)m_triangles.size());
+	ctx.disableVertexAttribArray	(positionLoc);
+	ctx.disableVertexAttribArray	(colorLoc);
+	ctx.useProgram					(0);
+	ctx.deleteProgram				(programId);
+	ctx.finish						();
+}
+
+class QuadFillTest : public TriangleFillTest
+{
+public:
+	QuadFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport, const tcu::Vec3& d1, const tcu::Vec3& d2, const tcu::Vec3& center_ = tcu::Vec3(0, 0, 0));
+};
+
+QuadFillTest::QuadFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport, const tcu::Vec3& d1, const tcu::Vec3& d2, const tcu::Vec3& center_)
+	: TriangleFillTest(context, name, description, viewport)
+{
+	const float		radius		= 40000.0f;
+	const tcu::Vec4 center		= tcu::Vec4(center_.x(), center_.y(), center_.z(), 1.0f);
+	const tcu::Vec4 halfWhite	= tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f);
+	const tcu::Vec4 halfRed		= tcu::Vec4(0.5f, 0.0f, 0.0f, 0.5f);
+	const tcu::Vec4	e1			= radius * tcu::Vec4(d1.x(), d1.y(), d1.z(), 0.0f);
+	const tcu::Vec4	e2			= radius * tcu::Vec4(d2.x(), d2.y(), d2.z(), 0.0f);
+
+	FillTriangle triangle1;
+	FillTriangle triangle2;
+
+	triangle1.c0 = halfWhite;
+	triangle1.c1 = halfWhite;
+	triangle1.c2 = halfWhite;
+	triangle1.v0 = center + e1 + e2;
+	triangle1.v1 = center + e1 - e2;
+	triangle1.v2 = center - e1 - e2;
+	m_triangles.push_back(triangle1);
+
+	triangle2.c0 = halfRed;
+	triangle2.c1 = halfRed;
+	triangle2.c2 = halfRed;
+	triangle2.v0 = center + e1 + e2;
+	triangle2.v1 = center - e1 - e2;
+	triangle2.v2 = center - e1 + e2;
+	m_triangles.push_back(triangle2);
+}
+
+class TriangleFanFillTest : public TriangleFillTest
+{
+public:
+	TriangleFanFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport);
+};
+
+TriangleFanFillTest::TriangleFanFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport)
+	: TriangleFillTest(context, name, description, viewport)
+{
+	const float		radius				= 70000.0f;
+	const int		trianglesPerVisit	= 40;
+	const tcu::Vec4 center				= tcu::Vec4(0, 0, 0, 1.0f);
+	const tcu::Vec4 halfWhite			= tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f);
+	const tcu::Vec4 oddSliceColor		= tcu::Vec4(0.0f, 0.0f, 0.5f, 0.0f);
+
+	// create a continuous surface that goes through all 6 clip planes
+
+	/*
+		*   /           /
+		*  /_ _ _ _ _  /x
+		* |           |  |
+		* |           | /
+		* |       / --xe /
+		* |      |    | /
+		* |_ _ _ e _ _|/
+		*
+		* e = enter
+		* x = exit
+		*/
+	const struct ClipPlaneVisit
+	{
+		const tcu::Vec3 corner;
+		const tcu::Vec3 entryPoint;
+		const tcu::Vec3 exitPoint;
+	} visits[] =
+	{
+		{ tcu::Vec3( 1, 1, 1),	tcu::Vec3( 0, 1, 1),	tcu::Vec3( 1, 0, 1) },
+		{ tcu::Vec3( 1,-1, 1),	tcu::Vec3( 1, 0, 1),	tcu::Vec3( 1,-1, 0) },
+		{ tcu::Vec3( 1,-1,-1),	tcu::Vec3( 1,-1, 0),	tcu::Vec3( 0,-1,-1) },
+		{ tcu::Vec3(-1,-1,-1),	tcu::Vec3( 0,-1,-1),	tcu::Vec3(-1, 0,-1) },
+		{ tcu::Vec3(-1, 1,-1),	tcu::Vec3(-1, 0,-1),	tcu::Vec3(-1, 1, 0) },
+		{ tcu::Vec3(-1, 1, 1),	tcu::Vec3(-1, 1, 0),	tcu::Vec3( 0, 1, 1) },
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(visits); ++ndx)
+	{
+		const ClipPlaneVisit& visit = visits[ndx];
+
+		for (int tri = 0; tri < trianglesPerVisit; ++tri)
+		{
+			tcu::Vec3 vertex0;
+			tcu::Vec3 vertex1;
+
+			if (tri == 0) // first vertex is magic
+			{
+				vertex0 = visit.entryPoint;
+			}
+			else
+			{
+				const tcu::Vec3 v1 = visit.entryPoint - visit.corner;
+				const tcu::Vec3 v2 = visit.exitPoint  - visit.corner;
+
+				vertex0 = visit.corner + tcu::normalize(tcu::mix(v1, v2, tcu::Vec3(float(tri)/trianglesPerVisit)));
+			}
+
+			if (tri == trianglesPerVisit-1) // last vertex is magic
+			{
+				vertex1 = visit.exitPoint;
+			}
+			else
+			{
+				const tcu::Vec3 v1 = visit.entryPoint - visit.corner;
+				const tcu::Vec3 v2 = visit.exitPoint  - visit.corner;
+
+				vertex1 = visit.corner + tcu::normalize(tcu::mix(v1, v2, tcu::Vec3(float(tri+1)/trianglesPerVisit)));
+			}
+
+			// write vec out
+			{
+				FillTriangle triangle;
+
+				triangle.c0 = (tri % 2) ? halfWhite : halfWhite + oddSliceColor;
+				triangle.c1 = (tri % 2) ? halfWhite : halfWhite + oddSliceColor;
+				triangle.c2 = (tri % 2) ? halfWhite : halfWhite + oddSliceColor;
+				triangle.v0 = center;
+				triangle.v1 = tcu::Vec4(vertex0.x() * radius, vertex0.y() * radius, vertex0.z() * radius, 1.0f);
+				triangle.v2 = tcu::Vec4(vertex1.x() * radius, vertex1.y() * radius, vertex1.z() * radius, 1.0f);
+
+				m_triangles.push_back(triangle);
+			}
+
+		}
+	}
+}
+
+class PointsTestGroup : public TestCaseGroup
+{
+public:
+			PointsTestGroup	(Context& context);
+
+	void	init			(void);
+};
+
+PointsTestGroup::PointsTestGroup (Context& context)
+	: TestCaseGroup(context, "point", "Point clipping tests")
+{
+}
+
+void PointsTestGroup::init (void)
+{
+	const float littleOverViewport = 1.0f + (2.0f / (TEST_CANVAS_SIZE)); // one pixel over the viewport edge in VIEWPORT_WHOLE, half pixels over in the reduced viewport.
+
+	const tcu::Vec4 viewportTestPoints[] =
+	{
+		// in clip volume
+		tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),
+		tcu::Vec4( 0.1f,  0.1f,  0.1f,  1.0f),
+		tcu::Vec4(-0.1f,  0.1f, -0.1f,  1.0f),
+		tcu::Vec4(-0.1f, -0.1f,  0.1f,  1.0f),
+		tcu::Vec4( 0.1f, -0.1f, -0.1f,  1.0f),
+
+		// in clip volume with w != 1
+		tcu::Vec4( 2.0f,  2.0f,  2.0f,  3.0f),
+		tcu::Vec4(-2.0f, -2.0f,  2.0f,  3.0f),
+		tcu::Vec4( 0.5f, -0.5f,  0.5f,  0.7f),
+		tcu::Vec4(-0.5f,  0.5f, -0.5f,  0.7f),
+
+		// near the edge
+		tcu::Vec4(-2.0f, -2.0f,  0.0f,  2.2f),
+		tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.1f),
+		tcu::Vec4(-1.0f,  1.0f,  0.0f,  1.1f),
+
+		// not in the volume but still between near and far planes
+		tcu::Vec4( 1.3f,  0.0f,  0.0f,  1.0f),
+		tcu::Vec4(-1.3f,  0.0f,  0.0f,  1.0f),
+		tcu::Vec4( 0.0f,  1.3f,  0.0f,  1.0f),
+		tcu::Vec4( 0.0f, -1.3f,  0.0f,  1.0f),
+
+		tcu::Vec4(-1.3f, -1.3f,  0.0f,  1.0f),
+		tcu::Vec4(-1.3f,  1.3f,  0.0f,  1.0f),
+		tcu::Vec4( 1.3f,  1.3f,  0.0f,  1.0f),
+		tcu::Vec4( 1.3f, -1.3f,  0.0f,  1.0f),
+
+		// outside the viewport, wide points have fragments in the viewport
+		tcu::Vec4( littleOverViewport,  littleOverViewport,  0.0f,  1.0f),
+		tcu::Vec4(               0.0f,  littleOverViewport,  0.0f,  1.0f),
+		tcu::Vec4( littleOverViewport,                0.0f,  0.0f,  1.0f),
+	};
+	const tcu::Vec4 depthTestPoints[] =
+	{
+		// in clip volume
+		tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),
+		tcu::Vec4( 0.1f,  0.1f,  0.1f,  1.0f),
+		tcu::Vec4(-0.1f,  0.1f, -0.1f,  1.0f),
+		tcu::Vec4(-0.1f, -0.1f,  0.1f,  1.0f),
+		tcu::Vec4( 0.1f, -0.1f, -0.1f,  1.0f),
+
+		// not between the near and the far planes. These should be clipped
+		tcu::Vec4( 0.1f,  0.0f,  1.1f,  1.0f),
+		tcu::Vec4(-0.1f,  0.0f, -1.1f,  1.0f),
+		tcu::Vec4(-0.0f, -0.1f,  1.1f,  1.0f),
+		tcu::Vec4( 0.0f,  0.1f, -1.1f,  1.0f)
+	};
+
+	addChild(new PointCase(m_context, "point_z_clip",						"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		1.0f,	VIEWPORT_WHOLE));
+	addChild(new PointCase(m_context, "point_z_clip_viewport_center",		"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		1.0f,	VIEWPORT_CENTER));
+	addChild(new PointCase(m_context, "point_z_clip_viewport_corner",		"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		1.0f,	VIEWPORT_CORNER));
+
+	addChild(new PointCase(m_context, "point_clip_viewport_center",			"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	1.0f,	VIEWPORT_CENTER));
+	addChild(new PointCase(m_context, "point_clip_viewport_corner",			"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	1.0f,	VIEWPORT_CORNER));
+
+	addChild(new PointCase(m_context, "wide_point_z_clip",					"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		5.0f,	VIEWPORT_WHOLE));
+	addChild(new PointCase(m_context, "wide_point_z_clip_viewport_center",	"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		5.0f,	VIEWPORT_CENTER));
+	addChild(new PointCase(m_context, "wide_point_z_clip_viewport_corner",	"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		5.0f,	VIEWPORT_CORNER));
+
+	addChild(new PointCase(m_context, "wide_point_clip",					"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	5.0f,	VIEWPORT_WHOLE));
+	addChild(new PointCase(m_context, "wide_point_clip_viewport_center",	"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	5.0f,	VIEWPORT_CENTER));
+	addChild(new PointCase(m_context, "wide_point_clip_viewport_corner",	"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	5.0f,	VIEWPORT_CORNER));
+}
+
+class LinesTestGroup : public TestCaseGroup
+{
+public:
+			LinesTestGroup	(Context& context);
+
+	void	init			(void);
+};
+
+LinesTestGroup::LinesTestGroup (Context& context)
+	: TestCaseGroup(context, "line", "Line clipping tests")
+{
+}
+
+void LinesTestGroup::init (void)
+{
+	const float littleOverViewport = 1.0f + (2.0f / (TEST_CANVAS_SIZE)); // one pixel over the viewport edge in VIEWPORT_WHOLE, half pixels over in the reduced viewport.
+
+	// lines
+	const LineRenderTestCase::ColorlessLineData viewportTestLines[] =
+	{
+		// from center to outside of viewport
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 0.0f,  1.5f,  0.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.5f,  1.0f,  0.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.5f,  0.0f,  0.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 0.2f,  0.4f,  1.5f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-2.0f, -1.0f,  0.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.0f,  0.1f,  0.0f,  0.6f)},
+
+		// from outside to inside of viewport
+		{tcu::Vec4( 1.5f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 0.8f, -0.2f,  0.0f,  1.0f)},
+		{tcu::Vec4( 0.0f, -1.5f,  0.0f,  1.0f),		tcu::Vec4( 0.9f, -0.7f,  0.0f,  1.0f)},
+
+		// from outside to outside
+		{tcu::Vec4( 0.0f, -1.3f,  0.0f,  1.0f),		tcu::Vec4( 1.3f,  0.0f,  0.0f,  1.0f)},
+
+		// outside the viewport, wide lines have fragments in the viewport
+		{tcu::Vec4(-0.8f,                      -littleOverViewport,  0.0f,  1.0f),	tcu::Vec4( 0.0f, -littleOverViewport,         0.0f,  1.0f)},
+		{tcu::Vec4(-littleOverViewport - 1.0f,  0.0f,                0.0f,  1.0f),	tcu::Vec4( 0.0f, -littleOverViewport - 1.0f,  0.0f,  1.0f)},
+	};
+	const LineRenderTestCase::ColorlessLineData depthTestLines[] =
+	{
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.3f,  1.0f,  2.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.3f, -1.0f,  2.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.0f, -1.1f, -2.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.0f,  1.1f, -2.0f,  1.0f)},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.0f,  0.1f,  2.0f,  0.6f)},
+	};
+	const LineRenderTestCase::ColorlessLineData longTestLines[] =
+	{
+		{tcu::Vec4( -41000.0f,		-40000.0f,		-1000000.0f,	1.0f),	tcu::Vec4( 41000.0f,		40000.0f,		1000000.0f,	1.0f)},
+		{tcu::Vec4(  41000.0f,		-40000.0f,		 1000000.0f,	1.0f),	tcu::Vec4(-41000.0f,		40000.0f,	   -1000000.0f,	1.0f)},
+		{tcu::Vec4(  0.5f,			-40000.0f,		 100000.0f,		1.0f),	tcu::Vec4( 0.5f,			40000.0f,	   -100000.0f,	1.0f)},
+		{tcu::Vec4( -0.5f,			 40000.0f,		 100000.0f,		1.0f),	tcu::Vec4(-0.5f,		   -40000.0f,	   -100000.0f,	1.0f)},
+	};
+
+	// line attribute clipping
+	const tcu::Vec4 red			(1.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4 yellow		(1.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 lightBlue	(0.3f, 0.3f, 1.0f, 1.0f);
+	const LineRenderTestCase::ColoredLineData colorTestLines[] =
+	{
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4( 1.3f,  1.0f,  2.0f,  1.0f),	yellow		},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4( 1.3f, -1.0f,  2.0f,  1.0f),	lightBlue	},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4(-1.0f, -1.0f, -2.0f,  1.0f),	yellow		},
+		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4(-1.0f,  1.0f, -2.0f,  1.0f),	lightBlue	},
+	};
+
+	// line clipping
+	addChild(new LineCase(m_context, "line_z_clip",							"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		1.0f,	VIEWPORT_WHOLE));
+	addChild(new LineCase(m_context, "line_z_clip_viewport_center",			"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		1.0f,	VIEWPORT_CENTER));
+	addChild(new LineCase(m_context, "line_z_clip_viewport_corner",			"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		1.0f,	VIEWPORT_CORNER));
+
+	addChild(new LineCase(m_context, "line_clip_viewport_center",			"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	1.0f,	VIEWPORT_CENTER));
+	addChild(new LineCase(m_context, "line_clip_viewport_corner",			"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	1.0f,	VIEWPORT_CORNER));
+
+	addChild(new LineCase(m_context, "wide_line_z_clip",					"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		5.0f,	VIEWPORT_WHOLE));
+	addChild(new LineCase(m_context, "wide_line_z_clip_viewport_center",	"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		5.0f,	VIEWPORT_CENTER));
+	addChild(new LineCase(m_context, "wide_line_z_clip_viewport_corner",	"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		5.0f,	VIEWPORT_CORNER));
+
+	addChild(new LineCase(m_context, "wide_line_clip",						"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	5.0f,	VIEWPORT_WHOLE));
+	addChild(new LineCase(m_context, "wide_line_clip_viewport_center",		"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	5.0f,	VIEWPORT_CENTER));
+	addChild(new LineCase(m_context, "wide_line_clip_viewport_corner",		"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	5.0f,	VIEWPORT_CORNER));
+
+	addChild(new LineCase(m_context, "long_line_clip",						"line viewport clipping",		DE_ARRAY_BEGIN(longTestLines),		DE_ARRAY_END(longTestLines),		1.0f,	VIEWPORT_WHOLE, 2));
+	addChild(new LineCase(m_context, "long_wide_line_clip",					"line viewport clipping",		DE_ARRAY_BEGIN(longTestLines),		DE_ARRAY_END(longTestLines),		5.0f,	VIEWPORT_WHOLE, 2));
+
+	// line attribute clipping
+	addChild(new ColoredLineCase(m_context, "line_attrib_clip",				"line attribute clipping",		DE_ARRAY_BEGIN(colorTestLines),		DE_ARRAY_END(colorTestLines),		1.0f,	VIEWPORT_WHOLE));
+	addChild(new ColoredLineCase(m_context, "wide_line_attrib_clip",		"line attribute clipping",		DE_ARRAY_BEGIN(colorTestLines),		DE_ARRAY_END(colorTestLines),		5.0f,	VIEWPORT_WHOLE));
+}
+
+class PolysTestGroup : public TestCaseGroup
+{
+public:
+			PolysTestGroup	(Context& context);
+
+	void	init			(void);
+};
+
+PolysTestGroup::PolysTestGroup (Context& context)
+	: TestCaseGroup(context, "polygon", "Polygon clipping tests")
+{
+}
+
+void PolysTestGroup::init (void)
+{
+	const float		large = 100000.0f;
+	const float		offset = 0.9f;
+	const tcu::Vec4 white	(1.0f, 1.0f, 1.0f, 1.0f);
+	const tcu::Vec4 red		(1.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4 yellow	(1.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 blue	(0.0f, 0.0f, 1.0f, 1.0f);
+
+	// basic cases
+	{
+		const TriangleCase::TriangleData viewportPolys[] =
+		{
+			// one vertex clipped
+			{tcu::Vec4(-0.8f, -0.2f,  0.0f,  1.0f), white, tcu::Vec4(-0.8f,  0.2f,  0.0f,  1.0f), white, tcu::Vec4(-1.3f,  0.05f,  0.0f,  1.0f), white},
+
+			// two vertices clipped
+			{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), white, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), white, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), white},
+
+			// three vertices clipped
+			{tcu::Vec4(-1.1f,  0.6f,  0.0f,  1.0f), white, tcu::Vec4(-1.1f,  1.1f,  0.0f,  1.0f), white, tcu::Vec4(-0.6f,  1.1f,  0.0f,  1.0f), white},
+			{tcu::Vec4( 0.8f,  1.1f,  0.0f,  1.0f), white, tcu::Vec4( 0.95f,-1.1f,  0.0f,  1.0f), white, tcu::Vec4( 3.0f,  0.0f,  0.0f,  1.0f), white},
+		};
+		const TriangleCase::TriangleData depthPolys[] =
+		{
+			// one vertex clipped to Z+
+			{tcu::Vec4(-0.2f,  0.7f,  0.0f,  1.0f), white, tcu::Vec4( 0.2f,  0.7f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f,  0.9f,  2.0f,  1.0f), white},
+
+			// two vertices clipped to Z-
+			{tcu::Vec4( 0.9f, 0.4f,  -1.5f,  1.0f), white, tcu::Vec4( 0.9f, -0.4f, -1.5f,  1.0f), white, tcu::Vec4( 0.6f,  0.0f,  0.0f,  1.0f), white},
+
+			// three vertices clipped
+			{tcu::Vec4(-0.9f, 0.6f,  -2.0f,  1.0f), white, tcu::Vec4(-0.9f, -0.6f, -2.0f,  1.0f), white, tcu::Vec4(-0.4f,  0.0f,  2.0f,  1.0f), white},
+
+			// three vertices clipped by X, Y and Z
+			{tcu::Vec4( 0.0f, -1.2f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f,  0.5f,  -1.5f, 1.0f), white, tcu::Vec4( 1.2f, -0.9f,  0.0f,  1.0f), white},
+		};
+		const TriangleCase::TriangleData largePolys[] =
+		{
+			// one vertex clipped
+			{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f, -large,  2.0f,  1.0f), white},
+
+			// two vertices clipped
+			{tcu::Vec4( 0.5f, 0.5f,  0.0f,  1.0f), white, tcu::Vec4( large, 0.5f, 0.0f,  1.0f), white, tcu::Vec4( 0.5f,  large,  0.0f,  1.0f), white},
+
+			// three vertices clipped
+			{tcu::Vec4(-0.9f, -large, 0.0f,  1.0f), white, tcu::Vec4(-1.1f, -large, 0.0f,  1.0f), white, tcu::Vec4(-0.9f,  large,  0.0f,  1.0f), white},
+		};
+		const TriangleCase::TriangleData largeDepthPolys[] =
+		{
+			// one vertex clipped
+			{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f, -large, large,  1.0f), white},
+
+			// two vertices clipped
+			{tcu::Vec4( 0.5f, 0.5f,  0.0f,  1.0f), white, tcu::Vec4( 0.9f, large/2, -large,  1.0f), white, tcu::Vec4( large/4, 0.0f, -large,  1.0f), white},
+
+			// three vertices clipped
+			{tcu::Vec4(-0.9f, large/4, large,  1.0f), white, tcu::Vec4(-0.5f, -large/4, -large,  1.0f), white, tcu::Vec4(-0.2f, large/4, large,  1.0f), white},
+		};
+		const TriangleCase::TriangleData attribPolys[] =
+		{
+			// one vertex clipped to edge, large
+			{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -large,  2.0f,  1.0f), blue},
+
+			// two vertices clipped to edges
+			{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+
+			// two vertices clipped to edges, with non-uniform w
+			{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+
+			// three vertices clipped, large, Z
+			{tcu::Vec4(-0.9f, large/4, large,  1.0f), red, tcu::Vec4(-0.5f, -large/4, -large,  1.0f), yellow, tcu::Vec4(-0.2f, large/4, large,  1.0f), blue},
+		};
+
+		addChild(new TriangleCase(m_context, "poly_clip_viewport_center",			"polygon viewport clipping",	DE_ARRAY_BEGIN(viewportPolys),		DE_ARRAY_END(viewportPolys),	VIEWPORT_CENTER));
+		addChild(new TriangleCase(m_context, "poly_clip_viewport_corner",			"polygon viewport clipping",	DE_ARRAY_BEGIN(viewportPolys),		DE_ARRAY_END(viewportPolys),	VIEWPORT_CORNER));
+
+		addChild(new TriangleCase(m_context, "poly_z_clip",							"polygon z clipping",			DE_ARRAY_BEGIN(depthPolys),			DE_ARRAY_END(depthPolys),		VIEWPORT_WHOLE));
+		addChild(new TriangleCase(m_context, "poly_z_clip_viewport_center",			"polygon z clipping",			DE_ARRAY_BEGIN(depthPolys),			DE_ARRAY_END(depthPolys),		VIEWPORT_CENTER));
+		addChild(new TriangleCase(m_context, "poly_z_clip_viewport_corner",			"polygon z clipping",			DE_ARRAY_BEGIN(depthPolys),			DE_ARRAY_END(depthPolys),		VIEWPORT_CORNER));
+
+		addChild(new TriangleCase(m_context, "large_poly_clip_viewport_center",		"polygon viewport clipping",	DE_ARRAY_BEGIN(largePolys),			DE_ARRAY_END(largePolys),		VIEWPORT_CENTER));
+		addChild(new TriangleCase(m_context, "large_poly_clip_viewport_corner",		"polygon viewport clipping",	DE_ARRAY_BEGIN(largePolys),			DE_ARRAY_END(largePolys),		VIEWPORT_CORNER));
+
+		addChild(new TriangleCase(m_context, "large_poly_z_clip",					"polygon z clipping",			DE_ARRAY_BEGIN(largeDepthPolys),	DE_ARRAY_END(largeDepthPolys),	VIEWPORT_WHOLE));
+		addChild(new TriangleCase(m_context, "large_poly_z_clip_viewport_center",	"polygon z clipping",			DE_ARRAY_BEGIN(largeDepthPolys),	DE_ARRAY_END(largeDepthPolys),	VIEWPORT_CENTER));
+		addChild(new TriangleCase(m_context, "large_poly_z_clip_viewport_corner",	"polygon z clipping",			DE_ARRAY_BEGIN(largeDepthPolys),	DE_ARRAY_END(largeDepthPolys),	VIEWPORT_CORNER));
+
+		addChild(new TriangleAttributeCase(m_context, "poly_attrib_clip",					"polygon clipping",		DE_ARRAY_BEGIN(attribPolys),		DE_ARRAY_END(attribPolys),		VIEWPORT_WHOLE));
+		addChild(new TriangleAttributeCase(m_context, "poly_attrib_clip_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(attribPolys),		DE_ARRAY_END(attribPolys),		VIEWPORT_CENTER));
+		addChild(new TriangleAttributeCase(m_context, "poly_attrib_clip_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(attribPolys),		DE_ARRAY_END(attribPolys),		VIEWPORT_CORNER));
+	}
+
+	// multiple polygons
+	{
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to edge
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+
+				// two vertices clipped to edges, with non-uniform w
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+
+				// three vertices clipped, Z
+				{tcu::Vec4(-0.9f, offset/4, offset,  1.0f), red, tcu::Vec4(-0.5f, -offset/4, -offset,  1.0f), yellow, tcu::Vec4(-0.2f, offset/4, offset,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_0",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_0_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_0_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+
+				// two vertices clipped to edges, with non-uniform w
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_1",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_1_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_1_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_2",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_2_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_2_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset, -2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_3",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_3_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_3_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(0.3f,  0.2f,  0.0f,  1.0f), red, tcu::Vec4( 0.3f, -0.2f,  0.0f,  1.0f), yellow, tcu::Vec4( offset, 0.0f,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_4",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_4_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_4_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.3f,  0.2f,  0.0f,  1.0f), red, tcu::Vec4(-0.3f, -0.2f,  0.0f,  1.0f), yellow, tcu::Vec4(-offset, 0.0f,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_5",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_5_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_5_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, 0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, offset,  2.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_6",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_6_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_6_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+
+				// two vertices clipped to edges
+				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
+				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_7",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_7_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_7_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// fill
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), white},
+				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), blue,	tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue, tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_8",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_8_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_8_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// fill
+				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), red,  tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), red,  tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), red},
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), blue, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue, tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_9",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_9_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_9_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// fill
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), white},
+				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), red,   tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), red,   tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), red},
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), blue,  tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue,  tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), blue},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_10",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_10_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_10_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+
+		{
+			const TriangleAttributeCase::TriangleData polys[] =
+			{
+				// one vertex clipped to z
+				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},
+
+				// fill
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), white,  tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), white,  tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), white},
+				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), red,    tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), red,    tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), red},
+				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), blue,   tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue,   tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), blue},
+				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), yellow, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), yellow, tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), yellow},
+			};
+
+			addChild(new TriangleAttributeCase(m_context, "multiple_11",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
+			addChild(new TriangleAttributeCase(m_context, "multiple_11_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
+			addChild(new TriangleAttributeCase(m_context, "multiple_11_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
+		}
+	}
+}
+
+class PolyEdgesTestGroup : public TestCaseGroup
+{
+public:
+			PolyEdgesTestGroup	(Context& context);
+
+	void	init				(void);
+};
+
+PolyEdgesTestGroup::PolyEdgesTestGroup (Context& context)
+	: TestCaseGroup(context, "polygon_edge", "Polygon clipping edge tests")
+{
+}
+
+void PolyEdgesTestGroup::init (void)
+{
+	// Quads via origin
+	const struct Quad
+	{
+		tcu::Vec3 d1; // tangent
+		tcu::Vec3 d2; // bi-tangent
+	} quads[] =
+	{
+		{ tcu::Vec3( 1, 1, 1),	tcu::Vec3( 1,   -1, 1) },
+		{ tcu::Vec3( 1, 1, 1),	tcu::Vec3(-1, 1.1f, 1) },
+		{ tcu::Vec3( 1, 1, 0),	tcu::Vec3(-1,    1, 0) },
+		{ tcu::Vec3( 0, 1, 0),	tcu::Vec3( 1,    0, 0) },
+		{ tcu::Vec3( 0, 1, 0),	tcu::Vec3( 1, 0.1f, 0) },
+	};
+
+	// Quad near edge
+	const struct EdgeQuad
+	{
+		tcu::Vec3 d1;		// tangent
+		tcu::Vec3 d2;		// bi-tangent
+		tcu::Vec3 center;	// center
+	} edgeQuads[] =
+	{
+		{ tcu::Vec3( 1,     0.01f, 0    ),	tcu::Vec3( 0,      0.01f,  0),  tcu::Vec3( 0,     0.99f, 0    ) }, // edge near x-plane
+		{ tcu::Vec3( 0.01f, 1,     0    ),	tcu::Vec3( 0.01f,  0,      0),  tcu::Vec3( 0.99f, 0,     0    ) }, // edge near y-plane
+		{ tcu::Vec3( 1,     1,     0.01f),	tcu::Vec3( 0.01f,  -0.01f, 0),  tcu::Vec3( 0,     0,     0.99f) }, // edge near z-plane
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(quads); ++ndx)
+		addChild(new QuadFillTest(m_context, (std::string("quad_at_origin_") + de::toString(ndx)).c_str(), "polygon edge clipping", VIEWPORT_CENTER, quads[ndx].d1, quads[ndx].d2));
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(edgeQuads); ++ndx)
+		addChild(new QuadFillTest(m_context, (std::string("quad_near_edge_") + de::toString(ndx)).c_str(), "polygon edge clipping", VIEWPORT_CENTER, edgeQuads[ndx].d1, edgeQuads[ndx].d2, edgeQuads[ndx].center));
+
+	// Polyfan
+	addChild(new TriangleFanFillTest(m_context, "poly_fan", "polygon edge clipping", VIEWPORT_CENTER));
+}
+
+class PolyVertexClipTestGroup : public TestCaseGroup
+{
+public:
+			PolyVertexClipTestGroup	(Context& context);
+
+	void	init					(void);
+};
+
+PolyVertexClipTestGroup::PolyVertexClipTestGroup (Context& context)
+	: TestCaseGroup(context, "triangle_vertex", "Clip n vertices")
+{
+}
+
+void PolyVertexClipTestGroup::init (void)
+{
+	const float far = 30000.0f;
+	const tcu::IVec3 outside[] =
+	{
+		// outside one clipping plane
+		tcu::IVec3(-1,  0,  0),
+		tcu::IVec3( 1,  0,  0),
+		tcu::IVec3( 0,  1,  0),
+		tcu::IVec3( 0, -1,  0),
+		tcu::IVec3( 0,  0,  1),
+		tcu::IVec3( 0,  0, -1),
+
+		// outside two clipping planes
+		tcu::IVec3(-1, -1,  0),
+		tcu::IVec3( 1, -1,  0),
+		tcu::IVec3( 1,  1,  0),
+		tcu::IVec3(-1,  1,  0),
+
+		tcu::IVec3(-1,  0, -1),
+		tcu::IVec3( 1,  0, -1),
+		tcu::IVec3( 1,  0,  1),
+		tcu::IVec3(-1,  0,  1),
+
+		tcu::IVec3( 0, -1, -1),
+		tcu::IVec3( 0,  1, -1),
+		tcu::IVec3( 0,  1,  1),
+		tcu::IVec3( 0, -1,  1),
+
+		// outside three clipping planes
+		tcu::IVec3(-1, -1,  1),
+		tcu::IVec3( 1, -1,  1),
+		tcu::IVec3( 1,  1,  1),
+		tcu::IVec3(-1,  1,  1),
+
+		tcu::IVec3(-1, -1, -1),
+		tcu::IVec3( 1, -1, -1),
+		tcu::IVec3( 1,  1, -1),
+		tcu::IVec3(-1,  1, -1),
+	};
+
+	de::Random rnd(0xabcdef);
+
+	TestCaseGroup* clipOne		= new TestCaseGroup(m_context, "clip_one",		"Clip one vertex");
+	TestCaseGroup* clipTwo		= new TestCaseGroup(m_context, "clip_two",		"Clip two vertices");
+	TestCaseGroup* clipThree	= new TestCaseGroup(m_context, "clip_three",	"Clip three vertices");
+
+	addChild(clipOne);
+	addChild(clipTwo);
+	addChild(clipThree);
+
+	// Test 1 point clipped
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(outside); ++ndx)
+	{
+		const float		w0		= rnd.getFloat(0.2f, 16.0f);
+		const float		w1		= rnd.getFloat(0.2f, 16.0f);
+		const float		w2		= rnd.getFloat(0.2f, 16.0f);
+		const tcu::Vec4 white	= tcu::Vec4(    1,	    1,	1,	1);
+		const tcu::Vec3 r0		= tcu::Vec3( 0.2f,	 0.3f,	0);
+		const tcu::Vec3 r1		= tcu::Vec3(-0.3f,	-0.4f,	0);
+		const tcu::Vec3 r2		= IVec3ToVec3(outside[ndx]) * far;
+		const tcu::Vec4 p0		= tcu::Vec4(r0.x() * w0, r0.y() * w0, r0.z() * w0, w0);
+		const tcu::Vec4 p1		= tcu::Vec4(r1.x() * w1, r1.y() * w1, r1.z() * w1, w1);
+		const tcu::Vec4 p2		= tcu::Vec4(r2.x() * w2, r2.y() * w2, r2.z() * w2, w2);
+
+		const std::string name	= std::string("clip") +
+			(outside[ndx].x() > 0 ? "_pos_x" : (outside[ndx].x() < 0 ? "_neg_x" : "")) +
+			(outside[ndx].y() > 0 ? "_pos_y" : (outside[ndx].y() < 0 ? "_neg_y" : "")) +
+			(outside[ndx].z() > 0 ? "_pos_z" : (outside[ndx].z() < 0 ? "_neg_z" : ""));
+
+		const TriangleCase::TriangleData triangle =	{p0, white, p1, white, p2, white};
+
+		// don't try to test with degenerate (or almost degenerate) triangles
+		if (outside[ndx].x() == 0 && outside[ndx].y() == 0)
+			continue;
+
+		clipOne->addChild(new TriangleCase(m_context, name.c_str(), "clip one vertex", &triangle, &triangle + 1, VIEWPORT_CENTER));
+	}
+
+	// Special triangles for "clip_z" cases, default triangles is not good, since it has very small visible area => problems with MSAA
+	{
+		const tcu::Vec4 white = tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);
+
+		const TriangleCase::TriangleData posZTriangle =
+		{
+			tcu::Vec4( 0.0f, -0.7f, -0.9f, 1.0f), white,
+			tcu::Vec4( 0.8f,  0.0f, -0.7f, 1.0f), white,
+			tcu::Vec4(-0.9f,  0.9f,  3.0f, 1.0f), white
+		};
+		const TriangleCase::TriangleData negZTriangle =
+		{
+			tcu::Vec4( 0.0f, -0.7f,  0.9f, 1.0f), white,
+			tcu::Vec4( 0.4f,  0.0f,  0.7f, 1.0f), white,
+			tcu::Vec4(-0.9f,  0.9f, -3.0f, 1.0f), white
+		};
+
+		clipOne->addChild(new TriangleCase(m_context, "clip_pos_z", "clip one vertex", &posZTriangle, &posZTriangle + 1, VIEWPORT_CENTER));
+		clipOne->addChild(new TriangleCase(m_context, "clip_neg_z", "clip one vertex", &negZTriangle, &negZTriangle + 1, VIEWPORT_CENTER));
+	}
+
+	// Test 2 points clipped
+	for (int ndx1 = 0; ndx1 < DE_LENGTH_OF_ARRAY(outside); ++ndx1)
+	for (int ndx2 = ndx1 + 1; ndx2 < DE_LENGTH_OF_ARRAY(outside); ++ndx2)
+	{
+		const float		w0		= rnd.getFloat(0.2f, 16.0f);
+		const float		w1		= rnd.getFloat(0.2f, 16.0f);
+		const float		w2		= rnd.getFloat(0.2f, 16.0f);
+		const tcu::Vec4 white	= tcu::Vec4(    1,	    1,	1,	1);
+		const tcu::Vec3 r0		= tcu::Vec3( 0.2f,	 0.3f,	0);
+		const tcu::IVec3 r1		= outside[ndx1];
+		const tcu::IVec3 r2		= outside[ndx2];
+		const tcu::Vec4 p0		= tcu::Vec4(r0.x() * w0, r0.y() * w0, r0.z() * w0, w0);
+		const tcu::Vec4 p1		= tcu::Vec4(r1.x() * far * w1, r1.y() * far * w1, r1.z() * far * w1, w1);
+		const tcu::Vec4 p2		= tcu::Vec4(r2.x() * far * w2, r2.y() * far * w2, r2.z() * far * w2, w2);
+
+		const std::string name	= std::string("clip") +
+			(outside[ndx1].x() > 0 ? "_pos_x" : (outside[ndx1].x() < 0 ? "_neg_x" : "")) +
+			(outside[ndx1].y() > 0 ? "_pos_y" : (outside[ndx1].y() < 0 ? "_neg_y" : "")) +
+			(outside[ndx1].z() > 0 ? "_pos_z" : (outside[ndx1].z() < 0 ? "_neg_z" : "")) +
+			"_and" +
+			(outside[ndx2].x() > 0 ? "_pos_x" : (outside[ndx2].x() < 0 ? "_neg_x" : "")) +
+			(outside[ndx2].y() > 0 ? "_pos_y" : (outside[ndx2].y() < 0 ? "_neg_y" : "")) +
+			(outside[ndx2].z() > 0 ? "_pos_z" : (outside[ndx2].z() < 0 ? "_neg_z" : ""));
+
+		const TriangleCase::TriangleData triangle =	{p0, white, p1, white, p2, white};
+
+		if (twoPointClippedTriangleInvisible(r0, r1, r2))
+			continue;
+
+		clipTwo->addChild(new TriangleCase(m_context, name.c_str(), "clip two vertices", &triangle, &triangle + 1, VIEWPORT_CENTER));
+	}
+
+	// Test 3 points clipped
+	for (int ndx1 = 0; ndx1 < DE_LENGTH_OF_ARRAY(outside); ++ndx1)
+	for (int ndx2 = ndx1 + 1; ndx2 < DE_LENGTH_OF_ARRAY(outside); ++ndx2)
+	for (int ndx3 = ndx2 + 1; ndx3 < DE_LENGTH_OF_ARRAY(outside); ++ndx3)
+	{
+		const float		w0		= rnd.getFloat(0.2f, 16.0f);
+		const float		w1		= rnd.getFloat(0.2f, 16.0f);
+		const float		w2		= rnd.getFloat(0.2f, 16.0f);
+		const tcu::Vec4 white	= tcu::Vec4(1, 1, 1, 1);
+		const tcu::IVec3 r0		= outside[ndx1];
+		const tcu::IVec3 r1		= outside[ndx2];
+		const tcu::IVec3 r2		= outside[ndx3];
+		const tcu::Vec4 p0		= tcu::Vec4(r0.x() * far * w0, r0.y() * far * w0, r0.z() * far * w0, w0);
+		const tcu::Vec4 p1		= tcu::Vec4(r1.x() * far * w1, r1.y() * far * w1, r1.z() * far * w1, w1);
+		const tcu::Vec4 p2		= tcu::Vec4(r2.x() * far * w2, r2.y() * far * w2, r2.z() * far * w2, w2);
+
+		// ignore cases where polygon is along xz or yz planes
+		if (pointsOnLine(r0.swizzle(0, 1), r1.swizzle(0, 1), r2.swizzle(0, 1)))
+			continue;
+
+		// triangle is visible only if it intersects the origin
+		if (pointOnTriangle(tcu::IVec3(0, 0, 0), r0, r1, r2))
+		{
+			const TriangleCase::TriangleData triangle =	{p0, white, p1, white, p2, white};
+			const std::string name	= std::string("clip") +
+				(outside[ndx1].x() > 0 ? "_pos_x" : (outside[ndx1].x() < 0 ? "_neg_x" : "")) +
+				(outside[ndx1].y() > 0 ? "_pos_y" : (outside[ndx1].y() < 0 ? "_neg_y" : "")) +
+				(outside[ndx1].z() > 0 ? "_pos_z" : (outside[ndx1].z() < 0 ? "_neg_z" : "")) +
+				"_and" +
+				(outside[ndx2].x() > 0 ? "_pos_x" : (outside[ndx2].x() < 0 ? "_neg_x" : "")) +
+				(outside[ndx2].y() > 0 ? "_pos_y" : (outside[ndx2].y() < 0 ? "_neg_y" : "")) +
+				(outside[ndx2].z() > 0 ? "_pos_z" : (outside[ndx2].z() < 0 ? "_neg_z" : "")) +
+				"_and" +
+				(outside[ndx3].x() > 0 ? "_pos_x" : (outside[ndx3].x() < 0 ? "_neg_x" : "")) +
+				(outside[ndx3].y() > 0 ? "_pos_y" : (outside[ndx3].y() < 0 ? "_neg_y" : "")) +
+				(outside[ndx3].z() > 0 ? "_pos_z" : (outside[ndx3].z() < 0 ? "_neg_z" : ""));
+
+			clipThree->addChild(new TriangleCase(m_context, name.c_str(), "clip three vertices", &triangle, &triangle + 1, VIEWPORT_CENTER));
+		}
+	}
+}
+
+} // anonymous
+
+ClippingTests::ClippingTests (Context& context)
+	: TestCaseGroup(context, "clipping", "Clipping tests")
+{
+}
+
+ClippingTests::~ClippingTests (void)
+{
+}
+
+void ClippingTests::init (void)
+{
+	addChild(new PointsTestGroup		(m_context));
+	addChild(new LinesTestGroup			(m_context));
+	addChild(new PolysTestGroup			(m_context));
+	addChild(new PolyEdgesTestGroup		(m_context));
+	addChild(new PolyVertexClipTestGroup(m_context));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fClippingTests.hpp b/modules/gles3/functional/es3fClippingTests.hpp
new file mode 100644
index 0000000..52a4639
--- /dev/null
+++ b/modules/gles3/functional/es3fClippingTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FCLIPPINGTESTS_HPP
+#define _ES3FCLIPPINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Clipping tests
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ClippingTests : public TestCaseGroup
+{
+public:
+						ClippingTests	(Context& context);
+						~ClippingTests	(void);
+	void				init			(void);
+
+private:
+	ClippingTests&		operator=		(const ClippingTests&);
+						ClippingTests	(const ClippingTests&);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FCLIPPINGTESTS_HPP
diff --git a/modules/gles3/functional/es3fColorClearTest.cpp b/modules/gles3/functional/es3fColorClearTest.cpp
new file mode 100644
index 0000000..9a75ecd
--- /dev/null
+++ b/modules/gles3/functional/es3fColorClearTest.cpp
@@ -0,0 +1,331 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Screen clearing test.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fColorClearTest.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "gluRenderContext.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuVector.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "deInt32.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <vector>
+
+using tcu::RGBA;
+using tcu::Surface;
+using tcu::TestLog;
+
+using namespace std;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ColorClearCase : public TestCase
+{
+public:
+	ColorClearCase(Context& context, const char* name, int numIters, int numClearsMin, int numClearsMax, bool testAlpha, bool testScissoring, bool testColorMasks, bool firstClearFull)
+		: TestCase			(context, name, name)
+		, m_numIters		(numIters)
+		, m_numClearsMin	(numClearsMin)
+		, m_numClearsMax	(numClearsMax)
+		, m_testAlpha		(testAlpha)
+		, m_testScissoring	(testScissoring)
+		, m_testColorMasks	(testColorMasks)
+		, m_firstClearFull	(firstClearFull)
+	{
+		m_curIter = 0;
+	}
+
+	virtual ~ColorClearCase (void)
+	{
+	}
+
+	virtual IterateResult iterate (void);
+
+private:
+	const int		m_numIters;
+	const int		m_numClearsMin;
+	const int		m_numClearsMax;
+	const bool		m_testAlpha;
+	const bool		m_testScissoring;
+	const bool		m_testColorMasks;
+	const bool		m_firstClearFull;
+
+	int				m_curIter;
+};
+
+class ClearInfo
+{
+public:
+	ClearInfo (const tcu::IVec4& rect, deUint8 colorMask, tcu::RGBA color)
+		: m_rect(rect), m_colorMask(colorMask), m_color(color)
+	{
+	}
+
+	tcu::IVec4		m_rect;
+	deUint8			m_colorMask;
+	tcu::RGBA		m_color;
+};
+
+TestCase::IterateResult ColorClearCase::iterate (void)
+{
+	TestLog&					log						= m_testCtx.getLog();
+	const glw::Functions&		gl						= m_context.getRenderContext().getFunctions();
+	const tcu::RenderTarget&	renderTarget			= m_context.getRenderTarget();
+	const tcu::PixelFormat&		pixelFormat				= renderTarget.getPixelFormat();
+	const int					targetWidth				= renderTarget.getWidth();
+	const int					targetHeight			= renderTarget.getHeight();
+	const int					numPixels				= targetWidth * targetHeight;
+
+	de::Random					rnd						(deInt32Hash(m_curIter));
+	vector<deUint8>				pixelKnownChannelMask	(numPixels, 0);
+	Surface						refImage				(targetWidth, targetHeight);
+	Surface						resImage				(targetWidth, targetHeight);
+	Surface						diffImage				(targetWidth, targetHeight);
+	int							numClears				= rnd.getUint32() % (m_numClearsMax + 1 - m_numClearsMin) + m_numClearsMin;
+	std::vector<ClearInfo>		clearOps;
+
+	if (m_testScissoring)
+		gl.enable(GL_SCISSOR_TEST);
+
+	for (int clearNdx = 0; clearNdx < numClears; clearNdx++)
+	{
+		// Rectangle.
+		int clearX;
+		int clearY;
+		int clearWidth;
+		int clearHeight;
+		if (!m_testScissoring || (clearNdx == 0 && m_firstClearFull))
+		{
+			clearX		= 0;
+			clearY		= 0;
+			clearWidth	= targetWidth;
+			clearHeight	= targetHeight;
+		}
+		else
+		{
+			clearX		= (rnd.getUint32() % (2*targetWidth)) - targetWidth;
+			clearY		= (rnd.getUint32() % (2*targetHeight)) - targetHeight;
+			clearWidth	= (rnd.getUint32() % targetWidth);
+			clearHeight	= (rnd.getUint32() % targetHeight);
+		}
+		gl.scissor(clearX, clearY, clearWidth, clearHeight);
+
+		// Color.
+		int		r = (int)(rnd.getUint32() & 0xFF);
+		int		g = (int)(rnd.getUint32() & 0xFF);
+		int		b = (int)(rnd.getUint32() & 0xFF);
+		int		a = m_testAlpha ? (int)(rnd.getUint32() & 0xFF) : 0xFF;
+		RGBA	clearCol(r, g, b, a);
+		gl.clearColor(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
+
+		// Mask.
+		deUint8	clearMask;
+		if (!m_testColorMasks || (clearNdx == 0 && m_firstClearFull))
+			clearMask = 0xF;
+		else
+			clearMask = (rnd.getUint32() & 0xF);
+		gl.colorMask((clearMask&0x1) != 0, (clearMask&0x2) != 0, (clearMask&0x4) != 0, (clearMask&0x8) != 0);
+
+		// Clear & store op.
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		clearOps.push_back(ClearInfo(tcu::IVec4(clearX, clearY, clearWidth, clearHeight), clearMask, clearCol));
+
+		// Let watchdog know we're alive.
+		m_testCtx.touchWatchdog();
+	}
+
+	// Compute reference image.
+	{
+		for (int y = 0; y < targetHeight; y++)
+		{
+			std::vector<ClearInfo>	scanlineClearOps;
+
+			// Find all rectangles affecting this scanline.
+			for (int opNdx = 0; opNdx < (int)clearOps.size(); opNdx++)
+			{
+				ClearInfo& op = clearOps[opNdx];
+				if (de::inBounds(y, op.m_rect.y(), op.m_rect.y()+op.m_rect.w()))
+					scanlineClearOps.push_back(op);
+			}
+
+			// Compute reference for scanline.
+			int x = 0;
+			while (x < targetWidth)
+			{
+				tcu::RGBA	spanColor;
+				deUint8		spanKnownMask	= 0;
+				int			spanLength		= (targetWidth - x);
+
+				for (int opNdx = (int)scanlineClearOps.size() - 1; opNdx >= 0; opNdx--)
+				{
+					const ClearInfo& op = scanlineClearOps[opNdx];
+
+					if (de::inBounds(x, op.m_rect.x(), op.m_rect.x()+op.m_rect.z()) &&
+						de::inBounds(y, op.m_rect.y(), op.m_rect.y()+op.m_rect.w()))
+					{
+						// Compute span length until end of given rectangle.
+						spanLength = deMin32(spanLength, op.m_rect.x() + op.m_rect.z() - x);
+
+						tcu::RGBA	clearCol	= op.m_color;
+						deUint8		clearMask	= op.m_colorMask;
+						if ((clearMask & 0x1) && !(spanKnownMask & 0x1)) spanColor.setRed(clearCol.getRed());
+						if ((clearMask & 0x2) && !(spanKnownMask & 0x2)) spanColor.setGreen(clearCol.getGreen());
+						if ((clearMask & 0x4) && !(spanKnownMask & 0x4)) spanColor.setBlue(clearCol.getBlue());
+						if ((clearMask & 0x8) && !(spanKnownMask & 0x8)) spanColor.setAlpha(clearCol.getAlpha());
+						spanKnownMask |= clearMask;
+
+						// Break if have all colors.
+						if (spanKnownMask == 0xF)
+							break;
+					}
+					else if (op.m_rect.x() > x)
+						spanLength = deMin32(spanLength, op.m_rect.x() - x);
+				}
+
+				// Set reference alpha channel to 0xFF, if no alpha in target.
+				if (pixelFormat.alphaBits == 0)
+					spanColor.setAlpha(0xFF);
+
+				// Fill the span.
+				for (int ndx = 0; ndx < spanLength; ndx++)
+				{
+					refImage.setPixel(x + ndx, y, spanColor);
+					pixelKnownChannelMask[y*targetWidth + x + ndx] |= spanKnownMask;
+				}
+
+				x += spanLength;
+			}
+		}
+	}
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, resImage.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels");
+
+	// Compute difference image.
+	RGBA colorThreshold = pixelFormat.getColorThreshold();
+	RGBA matchColor(0, 255, 0, 255);
+	RGBA diffColor(255, 0, 0, 255);
+	RGBA maxDiff(0, 0, 0, 0);
+	bool isImageOk = true;
+
+	if (gl.isEnabled(GL_DITHER))
+	{
+		colorThreshold.setRed(colorThreshold.getRed() + 1);
+		colorThreshold.setGreen(colorThreshold.getGreen() + 1);
+		colorThreshold.setBlue(colorThreshold.getBlue() + 1);
+		colorThreshold.setAlpha(colorThreshold.getAlpha() + 1);
+	}
+
+	for (int y = 0; y < targetHeight; y++)
+	for (int x = 0; x < targetWidth; x++)
+	{
+		int			offset		= (y*targetWidth + x);
+		RGBA		refRGBA		= refImage.getPixel(x, y);
+		RGBA		resRGBA		= resImage.getPixel(x, y);
+		deUint8		colMask		= pixelKnownChannelMask[offset];
+		RGBA		diff		= computeAbsDiffMasked(refRGBA, resRGBA, colMask);
+		bool		isPixelOk	= diff.isBelowThreshold(colorThreshold);
+
+		diffImage.setPixel(x, y, isPixelOk ? matchColor : diffColor);
+
+		isImageOk	= isImageOk && isPixelOk;
+		maxDiff		= max(maxDiff, diff);
+	}
+
+	if (isImageOk)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Image comparison failed, max diff = " << maxDiff << ", threshold = " << colorThreshold << tcu::TestLog::EndMessage;
+
+		log << TestLog::ImageSet("Result", "Resulting framebuffer")
+			<< TestLog::Image("Result",		"Resulting framebuffer",	resImage)
+			<< TestLog::Image("Reference",	"Reference image",			refImage)
+			<< TestLog::Image("DiffMask",	"Failing pixels",			diffImage)
+			<< TestLog::EndImageSet;
+		return TestCase::STOP;
+	}
+
+	bool isFinal = (++m_curIter == m_numIters);
+
+	// On final frame, dump images.
+	if (isFinal)
+	{
+		log << TestLog::ImageSet("Result", "Resulting framebuffer")
+			<< TestLog::Image("Result",		"Resulting framebuffer",	resImage)
+			<< TestLog::EndImageSet;
+	}
+
+	return isFinal ? TestCase::STOP : TestCase::CONTINUE;
+}
+
+ColorClearTest::ColorClearTest (Context& context) : TestCaseGroup(context, "color_clear", "Color Clear Tests")
+{
+}
+
+ColorClearTest::~ColorClearTest (void)
+{
+}
+
+void ColorClearTest::init (void)
+{
+	//										name					iters,	#..#,		alpha?,	scissor,masks,	1stfull?
+	addChild(new ColorClearCase(m_context, "single_rgb",			30,		1,3,		false,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "single_rgba",			30,		1,3,		true,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "multiple_rgb",			15,		4,20,		false,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "multiple_rgba",			15,		4,20,		true,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "long_rgb",				2,		100,500,	false,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "long_rgba",				2,		100,500,	true,	false,	false,	true	));
+	addChild(new ColorClearCase(m_context, "subclears_rgb",			15,		4,30,		false,	false,	false,	false	));
+	addChild(new ColorClearCase(m_context, "subclears_rgba",		15,		4,30,		true,	false,	false,	false	));
+	addChild(new ColorClearCase(m_context, "short_scissored_rgb",	30,		2,4,		false,	true,	false,	true	));
+	addChild(new ColorClearCase(m_context, "scissored_rgb",			15,		4,30,		false,	true,	false,	true	));
+	addChild(new ColorClearCase(m_context, "scissored_rgba",		15,		4,30,		true,	true,	false,	true	));
+	addChild(new ColorClearCase(m_context, "masked_rgb",			15,		4,30,		false,	true,	false,	true	));
+	addChild(new ColorClearCase(m_context, "masked_rgba",			15,		4,30,		true,	true,	false,	true	));
+	addChild(new ColorClearCase(m_context, "masked_scissored_rgb",	15,		4,30,		false,	true,	true,	true	));
+	addChild(new ColorClearCase(m_context, "masked_scissored_rgba",	15,		4,30,		true,	true,	true,	true	));
+	addChild(new ColorClearCase(m_context, "complex_rgb",			15,		5,50,		false,	true,	true,	false	));
+	addChild(new ColorClearCase(m_context, "complex_rgba",			15,		5,50,		true,	true,	true,	false	));
+	addChild(new ColorClearCase(m_context, "long_masked_rgb",		2,		100,500,	false,	true,	true,	true	));
+	addChild(new ColorClearCase(m_context, "long_masked_rgba",		2,		100,500,	true,	true,	true,	true	));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fColorClearTest.hpp b/modules/gles3/functional/es3fColorClearTest.hpp
new file mode 100644
index 0000000..b5ba038
--- /dev/null
+++ b/modules/gles3/functional/es3fColorClearTest.hpp
@@ -0,0 +1,55 @@
+#ifndef _ES3FCOLORCLEARTEST_HPP
+#define _ES3FCOLORCLEARTEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Color buffer clear test.
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+struct ClearTestCaseInfo;
+
+class ColorClearTest : public TestCaseGroup
+{
+public:
+							ColorClearTest			(Context& context);
+	virtual					~ColorClearTest			(void);
+
+	virtual void			init					(void);
+
+private:
+							ColorClearTest			(const ColorClearTest&);		// not allowed!
+	ColorClearTest&			operator=				(const ColorClearTest&);		// not allowed!
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES3FCOLORCLEARTEST_HPP
diff --git a/modules/gles3/functional/es3fCompressedTextureTests.cpp b/modules/gles3/functional/es3fCompressedTextureTests.cpp
new file mode 100644
index 0000000..3dbdff7
--- /dev/null
+++ b/modules/gles3/functional/es3fCompressedTextureTests.cpp
@@ -0,0 +1,112 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Compressed texture tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fCompressedTextureTests.hpp"
+#include "es3fASTCDecompressionCases.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuCompressedTexture.hpp"
+#include "tcuVector.hpp"
+#include "deStringUtil.hpp"
+
+#include <string>
+
+using std::string;
+using tcu::IVec3;
+using tcu::CompressedTexture;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+static const string getASTCFormatShortName (CompressedTexture::Format format)
+{
+	DE_ASSERT(tcu::isASTCFormat(format));
+	const IVec3 blockSize = tcu::getASTCBlockSize(format);
+	DE_ASSERT(blockSize.z() == 1);
+
+	return de::toString(blockSize.x()) + "x" + de::toString(blockSize.y()) + (tcu::isASTCSRGBFormat(format) ? "_srgb" : "");
+}
+
+CompressedTextureTests::CompressedTextureTests (Context& context)
+	: TestCaseGroup (context, "compressed", "Compressed Texture Tests")
+{
+}
+
+CompressedTextureTests::~CompressedTextureTests (void)
+{
+}
+
+void CompressedTextureTests::init (void)
+{
+	// ASTC cases.
+	{
+		TestCaseGroup* const astcGroup = new TestCaseGroup(m_context, "astc", "ASTC Tests");
+		addChild(astcGroup);
+
+		// Block test cases.
+
+		for (int astcTestTypeI = 0; astcTestTypeI < ASTCBLOCKTESTTYPE_LAST; astcTestTypeI++)
+		{
+			const ASTCBlockTestType		astcTestType	= (ASTCBlockTestType)astcTestTypeI;
+			TestCaseGroup* const		testTypeGroup	= new TestCaseGroup(m_context, getBlockTestTypeName(astcTestType), getBlockTestTypeDescription(astcTestType));
+			astcGroup->addChild(testTypeGroup);
+
+			for (int formatI = 0; formatI < CompressedTexture::FORMAT_LAST; formatI++)
+			{
+				const CompressedTexture::Format format = (CompressedTexture::Format)formatI;
+
+				if (!tcu::isASTCFormat(format))
+					continue;
+				if (tcu::isASTCSRGBFormat(format) && isBlockTestTypeHDROnly(astcTestType))
+					continue;
+
+				testTypeGroup->addChild(new ASTCBlockCase2D(m_context, getASTCFormatShortName(format).c_str(), glu::getCompressedTexFormatName(glu::getGLFormat(format)), astcTestType, format));
+			}
+		}
+
+		// Image size/block size remainder cases.
+
+		{
+			TestCaseGroup* const blockSizeRemainderGroup = new TestCaseGroup(m_context, "block_size_remainder", "Test image size/block size remainders");
+			astcGroup->addChild(blockSizeRemainderGroup);
+
+			for (int formatI = 0; formatI < CompressedTexture::FORMAT_LAST; formatI++)
+			{
+				const CompressedTexture::Format format = (CompressedTexture::Format)formatI;
+
+				if (!tcu::isASTCFormat(format))
+					continue;
+
+				blockSizeRemainderGroup->addChild(new ASTCBlockSizeRemainderCase2D(m_context, getASTCFormatShortName(format).c_str(), glu::getCompressedTexFormatName(glu::getGLFormat(format)), format));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fCompressedTextureTests.hpp b/modules/gles3/functional/es3fCompressedTextureTests.hpp
new file mode 100644
index 0000000..2686a57
--- /dev/null
+++ b/modules/gles3/functional/es3fCompressedTextureTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FCOMPRESSEDTEXTURETESTS_HPP
+#define _ES3FCOMPRESSEDTEXTURETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Compressed texture tests
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class CompressedTextureTests : public TestCaseGroup
+{
+public:
+								CompressedTextureTests		(Context& context);
+								~CompressedTextureTests		(void);
+	void						init						(void);
+
+private:
+	CompressedTextureTests&		operator=					(const CompressedTextureTests&);
+								CompressedTextureTests		(const CompressedTextureTests&);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FCOMPRESSEDTEXTURETESTS_HPP
diff --git a/modules/gles3/functional/es3fDefaultVertexAttributeTests.cpp b/modules/gles3/functional/es3fDefaultVertexAttributeTests.cpp
new file mode 100644
index 0000000..7c769a8
--- /dev/null
+++ b/modules/gles3/functional/es3fDefaultVertexAttributeTests.cpp
@@ -0,0 +1,665 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Default vertex attribute test
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fDefaultVertexAttributeTests.hpp"
+#include "tcuVector.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deMath.h"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+#include <limits>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+static const int s_valueRange = 10;
+
+static const char* const s_passThroughFragmentShaderSource =	"#version 300 es\n"
+																"layout(location = 0) out mediump vec4 fragColor;\n"
+																"in mediump vec4 v_color;\n"
+																"void main (void)\n"
+																"{\n"
+																"	fragColor = v_color;\n"
+																"}\n";
+
+template <typename T1, int S1, typename T2, int S2>
+tcu::Vector<T1, S1> convertToTypeVec (const tcu::Vector<T2, S2>& v)
+{
+	tcu::Vector<T1, S1> retVal;
+
+	for (int ndx = 0; ndx < S1; ++ndx)
+		retVal[ndx] = T1(0);
+
+	if (S1 == 4)
+		retVal[3] = T1(1);
+
+	for (int ndx = 0; ndx < de::min(S1, S2); ++ndx)
+		retVal[ndx] = T1(v[ndx]);
+
+	return retVal;
+}
+
+class FloatLoader
+{
+public:
+	virtual				~FloatLoader	(void) {};
+
+	// returns the value loaded
+	virtual tcu::Vec4	load			(glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const = 0;
+};
+
+#define GEN_DIRECT_FLOAT_LOADER(TYPE, COMPS, TYPECODE, CASENAME, VALUES)				\
+	class LoaderVertexAttrib##COMPS##TYPECODE : public FloatLoader						\
+	{																					\
+	public:																				\
+		enum																			\
+		{																				\
+			NORMALIZING = 0,															\
+		};																				\
+		enum																			\
+		{																				\
+			COMPONENTS = COMPS															\
+		};																				\
+		typedef TYPE Type;																\
+																						\
+		tcu::Vec4 load (glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const	\
+		{																				\
+			tcu::Vector<TYPE, COMPONENTS> value;										\
+			value = convertToTypeVec<Type, COMPONENTS>(v);								\
+																						\
+			gl.glVertexAttrib ##COMPS ##TYPECODE VALUES;								\
+			return convertToTypeVec<float, 4>(value);									\
+		}																				\
+																						\
+		static const char* getCaseName (void)											\
+		{																				\
+			return CASENAME;															\
+		}																				\
+																						\
+		static const char* getName (void)												\
+		{																				\
+			return "VertexAttrib" #COMPS #TYPECODE;										\
+		}																				\
+	}
+
+#define GEN_INDIRECT_FLOAT_LOADER(TYPE, COMPS, TYPECODE, CASENAME)						\
+	class LoaderVertexAttrib##COMPS##TYPECODE : public FloatLoader						\
+	{																					\
+	public:																				\
+		enum																			\
+		{																				\
+			NORMALIZING = 0,															\
+		};																				\
+		enum																			\
+		{																				\
+			COMPONENTS = COMPS															\
+		};																				\
+		typedef TYPE Type;																\
+																						\
+		tcu::Vec4 load (glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const	\
+		{																				\
+			tcu::Vector<TYPE, COMPONENTS> value;										\
+			value = convertToTypeVec<Type, COMPONENTS>(v);								\
+																						\
+			gl.glVertexAttrib ##COMPS ##TYPECODE (index, value.getPtr());				\
+			return convertToTypeVec<float, 4>(value);									\
+		}																				\
+																						\
+		static const char* getCaseName (void)											\
+		{																				\
+			return CASENAME;															\
+		}																				\
+																						\
+		static const char* getName (void)												\
+		{																				\
+			return "VertexAttrib" #COMPS #TYPECODE;										\
+		}																				\
+	}
+
+#define GEN_DIRECT_INTEGER_LOADER(TYPE, COMPS, TYPECODE, CASENAME, VALUES)				\
+	class LoaderVertexAttribI##COMPS##TYPECODE : public FloatLoader						\
+	{																					\
+	public:																				\
+		enum																			\
+		{																				\
+			NORMALIZING = 0,															\
+		};																				\
+		enum																			\
+		{																				\
+			COMPONENTS = COMPS															\
+		};																				\
+		typedef TYPE Type;																\
+																						\
+		tcu::Vec4 load (glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const	\
+		{																				\
+			tcu::Vector<TYPE, COMPONENTS> value;										\
+			value = convertToTypeVec<Type, COMPONENTS>(v);								\
+																						\
+			gl.glVertexAttribI ##COMPS ##TYPECODE VALUES;								\
+			return convertToTypeVec<float, 4>(value);									\
+		}																				\
+																						\
+		static const char* getCaseName (void)											\
+		{																				\
+			return CASENAME;															\
+		}																				\
+																						\
+		static const char* getName (void)												\
+		{																				\
+			return "VertexAttrib" #COMPS #TYPECODE;										\
+		}																				\
+	}
+
+#define GEN_INDIRECT_INTEGER_LOADER(TYPE, COMPS, TYPECODE, CASENAME)					\
+	class LoaderVertexAttribI##COMPS##TYPECODE : public FloatLoader						\
+	{																					\
+	public:																				\
+		enum																			\
+		{																				\
+			NORMALIZING = 0,															\
+		};																				\
+		enum																			\
+		{																				\
+			COMPONENTS = COMPS															\
+		};																				\
+		typedef TYPE Type;																\
+																						\
+		tcu::Vec4 load (glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const	\
+		{																				\
+			tcu::Vector<TYPE, COMPONENTS> value;										\
+			value = convertToTypeVec<Type, COMPONENTS>(v);								\
+																						\
+			gl.glVertexAttribI ##COMPS ##TYPECODE (index, value.getPtr());				\
+			return convertToTypeVec<float, 4>(value);									\
+		}																				\
+																						\
+		static const char* getCaseName (void)											\
+		{																				\
+			return CASENAME;															\
+		}																				\
+																						\
+		static const char* getName (void)												\
+		{																				\
+			return "VertexAttrib" #COMPS #TYPECODE;										\
+		}																				\
+	}
+
+GEN_DIRECT_FLOAT_LOADER(float, 1, f, "vertex_attrib_1f", (index, value.x()));
+GEN_DIRECT_FLOAT_LOADER(float, 2, f, "vertex_attrib_2f", (index, value.x(), value.y()));
+GEN_DIRECT_FLOAT_LOADER(float, 3, f, "vertex_attrib_3f", (index, value.x(), value.y(), value.z()));
+GEN_DIRECT_FLOAT_LOADER(float, 4, f, "vertex_attrib_4f", (index, value.x(), value.y(), value.z(), value.w()));
+
+GEN_INDIRECT_FLOAT_LOADER(float, 1, fv, "vertex_attrib_1fv");
+GEN_INDIRECT_FLOAT_LOADER(float, 2, fv, "vertex_attrib_2fv");
+GEN_INDIRECT_FLOAT_LOADER(float, 3, fv, "vertex_attrib_3fv");
+GEN_INDIRECT_FLOAT_LOADER(float, 4, fv, "vertex_attrib_4fv");
+
+GEN_DIRECT_INTEGER_LOADER(deInt32, 4, i, "vertex_attribi_4i", (index, value.x(), value.y(), value.z(), value.w()));
+GEN_INDIRECT_INTEGER_LOADER(deInt32, 4, iv, "vertex_attribi_4iv");
+
+GEN_DIRECT_INTEGER_LOADER(deUint32, 4, ui, "vertex_attribi_4ui", (index, value.x(), value.y(), value.z(), value.w()));
+GEN_INDIRECT_INTEGER_LOADER(deUint32, 4, uiv, "vertex_attribi_4uiv");
+
+class AttributeCase : public TestCase
+{
+									AttributeCase			(Context& ctx, const char* name, const char* desc, const char* funcName, bool normalizing, bool useNegative, glu::DataType dataType);
+public:
+	template<typename LoaderType>
+	static AttributeCase*			create					(Context& ctx, glu::DataType dataType);
+									~AttributeCase			(void);
+
+private:
+	void							init					(void);
+	void							deinit					(void);
+	IterateResult					iterate					(void);
+
+	glu::DataType					getTargetType			(void) const;
+	std::string						genVertexSource			(void) const;
+	bool							renderWithValue			(const tcu::Vec4& v);
+	tcu::Vec4						computeColor			(const tcu::Vec4& value);
+	bool							verifyUnicoloredBuffer	(const tcu::Surface& scene, const tcu::Vec4& refValue);
+
+	const bool						m_normalizing;
+	const bool						m_useNegativeValues;
+	const char* const				m_funcName;
+	const glu::DataType				m_dataType;
+	const FloatLoader*				m_loader;
+	glu::ShaderProgram*				m_program;
+	deUint32						m_bufID;
+	bool							m_allIterationsPassed;
+	int								m_iteration;
+
+	enum
+	{
+		RENDER_SIZE = 32
+	};
+};
+
+AttributeCase::AttributeCase (Context& ctx, const char* name, const char* desc, const char* funcName, bool normalizing, bool useNegative, glu::DataType dataType)
+	: TestCase				(ctx, name, desc)
+	, m_normalizing			(normalizing)
+	, m_useNegativeValues	(useNegative)
+	, m_funcName			(funcName)
+	, m_dataType			(dataType)
+	, m_loader				(DE_NULL)
+	, m_program				(DE_NULL)
+	, m_bufID				(0)
+	, m_allIterationsPassed	(true)
+	, m_iteration			(0)
+{
+}
+
+template<typename LoaderType>
+AttributeCase* AttributeCase::create (Context& ctx, glu::DataType dataType)
+{
+	AttributeCase* retVal = new AttributeCase(ctx,
+											  LoaderType::getCaseName(),
+											  (std::string("Test ") + LoaderType::getName()).c_str(),
+											  LoaderType::getName(),
+											  LoaderType::NORMALIZING != 0,
+											  std::numeric_limits<typename LoaderType::Type>::is_signed,
+											  dataType);
+	retVal->m_loader = new LoaderType();
+	return retVal;
+}
+
+AttributeCase::~AttributeCase (void)
+{
+	deinit();
+}
+
+void AttributeCase::init (void)
+{
+	if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE)
+		throw tcu::NotSupportedError("Render target must be at least " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE));
+
+	// log test info
+
+	{
+		const float			maxRange		= (m_normalizing) ? (1.0f) : (s_valueRange);
+		const float			minRange		= (m_useNegativeValues) ? (-maxRange) : (0.0f);
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Loading attribute values using " << m_funcName << "\n"
+			<< "Attribute type: " << glu::getDataTypeName(m_dataType) << "\n"
+			<< "Attribute value range: [" << minRange << ", " << maxRange << "]"
+			<< tcu::TestLog::EndMessage;
+	}
+
+	// gen shader and base quad
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource()) << glu::FragmentSource(s_passThroughFragmentShaderSource));
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build program");
+
+	{
+		const tcu::Vec4 fullscreenQuad[] =
+		{
+			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),
+		};
+
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.genBuffers(1, &m_bufID);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_bufID);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "fill buffer");
+	}
+}
+
+void AttributeCase::deinit (void)
+{
+	delete m_loader;
+	m_loader = DE_NULL;
+
+	delete m_program;
+	m_program = DE_NULL;
+
+	if (m_bufID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_bufID);
+		m_bufID = 0;
+	}
+}
+
+AttributeCase::IterateResult AttributeCase::iterate (void)
+{
+	static const tcu::Vec4 testValues[] =
+	{
+		tcu::Vec4(0.0f, 0.5f, 0.2f, 1.0f),
+		tcu::Vec4(0.1f, 0.7f, 1.0f, 0.6f),
+		tcu::Vec4(0.4f, 0.2f, 0.0f, 0.5f),
+		tcu::Vec4(0.5f, 0.0f, 0.9f, 0.1f),
+		tcu::Vec4(0.6f, 0.2f, 0.2f, 0.9f),
+		tcu::Vec4(0.9f, 1.0f, 0.0f, 0.0f),
+		tcu::Vec4(1.0f, 0.5f, 0.3f, 0.8f),
+	};
+
+	const tcu::ScopedLogSection section(m_testCtx.getLog(), "Iteration", "Iteration " + de::toString(m_iteration+1) + "/" + de::toString(DE_LENGTH_OF_ARRAY(testValues)));
+
+	// Test normalizing transfers with whole range, non-normalizing with up to s_valueRange
+	const tcu::Vec4 testValue = ((m_useNegativeValues) ? (testValues[m_iteration] * 2.0f - tcu::Vec4(1.0f)) : (testValues[m_iteration])) * ((m_normalizing) ? (1.0f) : ((float)s_valueRange));
+
+	if (!renderWithValue(testValue))
+		m_allIterationsPassed = false;
+
+	// continue
+
+	if (++m_iteration < DE_LENGTH_OF_ARRAY(testValues))
+		return CONTINUE;
+
+	if (m_allIterationsPassed)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected values");
+
+	return STOP;
+}
+
+std::string AttributeCase::genVertexSource (void) const
+{
+	const int			vectorSize	= (glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeMatrixNumRows(m_dataType)) : (glu::isDataTypeVector(m_dataType)) ? (glu::getDataTypeScalarSize(m_dataType)) : (-1);
+	const char* const	vectorType	= glu::getDataTypeName((glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeVector(glu::TYPE_FLOAT, vectorSize)) : (glu::isDataTypeVector(m_dataType)) ? (glu::getDataTypeVector(glu::TYPE_FLOAT, vectorSize)) : (glu::TYPE_FLOAT));
+	const int			components	= (glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeMatrixNumRows(m_dataType)) : (glu::getDataTypeScalarSize(m_dataType));
+	std::ostringstream	buf;
+
+	buf <<	"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"in highp " << glu::getDataTypeName(m_dataType) << " a_value;\n"
+			"out highp vec4 v_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"\n";
+
+	if (m_normalizing)
+		buf << "	highp " << vectorType << " normalizedValue = " << ((glu::getDataTypeScalarType(m_dataType) == glu::TYPE_FLOAT) ? ("") : (vectorType)) << "(a_value" << ((glu::isDataTypeMatrix(m_dataType)) ? ("[1]") : ("")) << ");\n";
+	else
+		buf << "	highp " << vectorType << " normalizedValue = " << ((glu::getDataTypeScalarType(m_dataType) == glu::TYPE_FLOAT) ? ("") : (vectorType)) << "(a_value" << ((glu::isDataTypeMatrix(m_dataType)) ? ("[1]") : ("")) << ") / float(" << s_valueRange << ");\n";
+
+	if (m_useNegativeValues)
+		buf << "	highp " << vectorType << " positiveNormalizedValue = (normalizedValue + " << vectorType << "(1.0)) / 2.0;\n";
+	else
+		buf << "	highp " << vectorType << " positiveNormalizedValue = normalizedValue;\n";
+
+	if (components == 1)
+		buf << "	v_color = vec4(positiveNormalizedValue, 0.0, 0.0, 1.0);\n";
+	else if (components == 2)
+		buf << "	v_color = vec4(positiveNormalizedValue.xy, 0.0, 1.0);\n";
+	else if (components == 3)
+		buf << "	v_color = vec4(positiveNormalizedValue.xyz, 1.0);\n";
+	else if (components == 4)
+		buf << "	v_color = vec4((positiveNormalizedValue.xy + positiveNormalizedValue.zz) / 2.0, positiveNormalizedValue.w, 1.0);\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "}\n";
+
+	return buf.str();
+}
+
+bool AttributeCase::renderWithValue (const tcu::Vec4& v)
+{
+	glu::CallLogWrapper	gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	gl.enableLogging(true);
+
+	const int			positionIndex	= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
+	const int			valueIndex		= gl.glGetAttribLocation(m_program->getProgram(), "a_value");
+	tcu::Surface		dest			(RENDER_SIZE, RENDER_SIZE);
+	tcu::Vec4			loadedValue;
+
+	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	gl.glClear(GL_COLOR_BUFFER_BIT);
+	gl.glViewport(0, 0, RENDER_SIZE, RENDER_SIZE);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup");
+
+	gl.glBindBuffer(GL_ARRAY_BUFFER, m_bufID);
+	gl.glVertexAttribPointer(positionIndex, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.glEnableVertexAttribArray(positionIndex);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "position va");
+
+	// transfer test value. Load to the second column in the matrix case
+	loadedValue = m_loader->load(gl, (glu::isDataTypeMatrix(m_dataType)) ? (valueIndex + 1) : (valueIndex), v);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "default va");
+
+	gl.glUseProgram(m_program->getProgram());
+	gl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+	gl.glUseProgram(0);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dest.getAccess());
+
+	// check whole result is colored correctly
+	return verifyUnicoloredBuffer(dest, computeColor(loadedValue));
+}
+
+tcu::Vec4 AttributeCase::computeColor (const tcu::Vec4& value)
+{
+	const tcu::Vec4 normalizedValue			= value / ((m_normalizing) ? (1.0f) : ((float)s_valueRange));
+	const tcu::Vec4 positiveNormalizedValue = ((m_useNegativeValues) ? ((normalizedValue + tcu::Vec4(1.0f)) / 2.0f) : (normalizedValue));
+	const int		components				= (glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeMatrixNumRows(m_dataType)) : (glu::getDataTypeScalarSize(m_dataType));
+
+	if (components == 1)
+		return tcu::Vec4(positiveNormalizedValue.x(), 0.0f, 0.0f, 1.0f);
+	else if (components == 2)
+		return tcu::Vec4(positiveNormalizedValue.x(), positiveNormalizedValue.y(), 0.0f, 1.0f);
+	else if (components == 3)
+		return tcu::Vec4(positiveNormalizedValue.x(), positiveNormalizedValue.y(), positiveNormalizedValue.z(), 1.0f);
+	else if (components == 4)
+		return tcu::Vec4((positiveNormalizedValue.x() + positiveNormalizedValue.z()) / 2.0f, (positiveNormalizedValue.y() + positiveNormalizedValue.z()) / 2.0f, positiveNormalizedValue.w(), 1.0f);
+	else
+		DE_ASSERT(DE_FALSE);
+
+	return tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+bool AttributeCase::verifyUnicoloredBuffer (const tcu::Surface& scene, const tcu::Vec4& refValue)
+{
+	tcu::Surface	errorMask		(RENDER_SIZE, RENDER_SIZE);
+	const tcu::RGBA	refColor		(refValue);
+	const int		resultThreshold	= 2;
+	const tcu::RGBA	colorThreshold	= m_context.getRenderTarget().getPixelFormat().getColorThreshold() * resultThreshold;
+	bool			error			= false;
+
+	tcu::RGBA		exampleColor;
+	tcu::IVec2		examplePos;
+
+	tcu::clear(errorMask.getAccess(), tcu::RGBA::green.toIVec());
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying rendered image. Expecting color " << refColor << ", threshold " << colorThreshold << tcu::TestLog::EndMessage;
+
+	for (int y = 0; y < RENDER_SIZE; ++y)
+	for (int x = 0; x < RENDER_SIZE; ++x)
+	{
+		const tcu::RGBA color = scene.getPixel(x, y);
+
+		if (de::abs(color.getRed()   - refColor.getRed())   > colorThreshold.getRed()   ||
+			de::abs(color.getGreen() - refColor.getGreen()) > colorThreshold.getGreen() ||
+			de::abs(color.getBlue()  - refColor.getBlue())  > colorThreshold.getBlue())
+		{
+			// first error
+			if (!error)
+			{
+				exampleColor = color;
+				examplePos = tcu::IVec2(x, y);
+			}
+
+			error = true;
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+		}
+	}
+
+	if (!error)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Rendered image is valid." << tcu::TestLog::EndMessage;
+	else
+	{
+		m_testCtx.getLog()	<< tcu::TestLog::Message
+							<< "Found invalid pixel(s).\n"
+							<< "Pixel at (" << examplePos.x() << ", " << examplePos.y() << ") color: " << exampleColor
+							<< tcu::TestLog::EndMessage
+							<< tcu::TestLog::ImageSet("Result", "Render result")
+							<< tcu::TestLog::Image("Result", "Result", scene)
+							<< tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask)
+							<< tcu::TestLog::EndImageSet;
+	}
+
+	return !error;
+}
+
+} // anonymous
+
+DefaultVertexAttributeTests::DefaultVertexAttributeTests (Context& context)
+	: TestCaseGroup(context, "default_vertex_attrib", "Test default vertex attributes")
+{
+}
+
+DefaultVertexAttributeTests::~DefaultVertexAttributeTests (void)
+{
+}
+
+void DefaultVertexAttributeTests::init (void)
+{
+	struct Target
+	{
+		const char*		name;
+		glu::DataType	dataType;
+		bool			reducedTestSets;	// !< use reduced coverage
+	};
+
+	static const Target floatTargets[] =
+	{
+		{ "float",	glu::TYPE_FLOAT,		false	},
+		{ "vec2",	glu::TYPE_FLOAT_VEC2,	true	},
+		{ "vec3",	glu::TYPE_FLOAT_VEC3,	true	},
+		{ "vec4",	glu::TYPE_FLOAT_VEC4,	false	},
+		{ "mat2",	glu::TYPE_FLOAT_MAT2,	true	},
+		{ "mat2x3",	glu::TYPE_FLOAT_MAT2X3,	true	},
+		{ "mat2x4",	glu::TYPE_FLOAT_MAT2X4,	true	},
+		{ "mat3",	glu::TYPE_FLOAT_MAT3,	true	},
+		{ "mat3x2",	glu::TYPE_FLOAT_MAT3X2,	true	},
+		{ "mat3x4",	glu::TYPE_FLOAT_MAT3X4,	true	},
+		{ "mat4",	glu::TYPE_FLOAT_MAT4,	false	},
+		{ "mat4x2",	glu::TYPE_FLOAT_MAT4X2,	true	},
+		{ "mat4x3",	glu::TYPE_FLOAT_MAT4X3,	true	},
+	};
+
+	static const Target intTargets[] =
+	{
+		{ "int",	glu::TYPE_INT,			false	},
+		{ "ivec2",	glu::TYPE_INT_VEC2,		true	},
+		{ "ivec3",	glu::TYPE_INT_VEC3,		true	},
+		{ "ivec4",	glu::TYPE_INT_VEC4,		false	},
+	};
+
+	static const Target uintTargets[] =
+	{
+		{ "uint",	glu::TYPE_UINT,			false	},
+		{ "uvec2",	glu::TYPE_UINT_VEC2,	true	},
+		{ "uvec3",	glu::TYPE_UINT_VEC3,	true	},
+		{ "uvec4",	glu::TYPE_UINT_VEC4,	false	},
+	};
+
+	// float targets
+
+	for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(floatTargets); ++targetNdx)
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(m_testCtx, floatTargets[targetNdx].name, (std::string("test with ") + floatTargets[targetNdx].name).c_str());
+		const bool					fullSet	= !floatTargets[targetNdx].reducedTestSets;
+
+#define ADD_CASE(X) group->addChild(AttributeCase::create<X>(m_context, floatTargets[targetNdx].dataType))
+#define ADD_REDUCED_CASE(X)	if (fullSet) ADD_CASE(X)
+
+		ADD_CASE		(LoaderVertexAttrib1f);
+		ADD_REDUCED_CASE(LoaderVertexAttrib2f);
+		ADD_REDUCED_CASE(LoaderVertexAttrib3f);
+		ADD_CASE		(LoaderVertexAttrib4f);
+
+		ADD_CASE		(LoaderVertexAttrib1fv);
+		ADD_REDUCED_CASE(LoaderVertexAttrib2fv);
+		ADD_REDUCED_CASE(LoaderVertexAttrib3fv);
+		ADD_CASE		(LoaderVertexAttrib4fv);
+
+#undef ADD_CASE
+#undef ADD_REDUCED_CASE
+
+		addChild(group);
+	}
+
+	// int targets
+
+	for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(intTargets); ++targetNdx)
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(m_testCtx, intTargets[targetNdx].name, (std::string("test with ") + intTargets[targetNdx].name).c_str());
+
+#define ADD_CASE(X) group->addChild(AttributeCase::create<X>(m_context, intTargets[targetNdx].dataType))
+
+		ADD_CASE		(LoaderVertexAttribI4i);
+		ADD_CASE		(LoaderVertexAttribI4iv);
+
+#undef ADD_CASE
+
+		addChild(group);
+	}
+
+	// uint targets
+
+	for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(uintTargets); ++targetNdx)
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(m_testCtx, uintTargets[targetNdx].name, (std::string("test with ") + uintTargets[targetNdx].name).c_str());
+
+#define ADD_CASE(X) group->addChild(AttributeCase::create<X>(m_context, uintTargets[targetNdx].dataType))
+
+		ADD_CASE		(LoaderVertexAttribI4ui);
+		ADD_CASE		(LoaderVertexAttribI4uiv);
+
+#undef ADD_CASE
+
+		addChild(group);
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fDefaultVertexAttributeTests.hpp b/modules/gles3/functional/es3fDefaultVertexAttributeTests.hpp
new file mode 100644
index 0000000..772997a
--- /dev/null
+++ b/modules/gles3/functional/es3fDefaultVertexAttributeTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FDEFAULTVERTEXATTRIBUTETESTS_HPP
+#define _ES3FDEFAULTVERTEXATTRIBUTETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Default vertex attribute test
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class DefaultVertexAttributeTests : public TestCaseGroup
+{
+public:
+									DefaultVertexAttributeTests		(Context& context);
+									~DefaultVertexAttributeTests	(void);
+
+	void							init							(void);
+
+private:
+									DefaultVertexAttributeTests		(const DefaultVertexAttributeTests& other);
+	DefaultVertexAttributeTests&	operator=						(const DefaultVertexAttributeTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FDEFAULTVERTEXATTRIBUTETESTS_HPP
diff --git a/modules/gles3/functional/es3fDepthStencilClearTests.cpp b/modules/gles3/functional/es3fDepthStencilClearTests.cpp
new file mode 100644
index 0000000..d58d48f
--- /dev/null
+++ b/modules/gles3/functional/es3fDepthStencilClearTests.cpp
@@ -0,0 +1,524 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth and stencil clear tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fDepthStencilClearTests.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::TestLog;
+using std::string;
+using std::vector;
+
+namespace
+{
+
+enum
+{
+	STENCIL_STEPS	= 32,
+	DEPTH_STEPS		= 32
+};
+
+struct Clear
+{
+	Clear (void)
+		: clearMask			(0)
+		, clearDepth		(0.0f)
+		, clearStencil		(0)
+		, useScissor		(false)
+		, scissor			(0, 0, 0, 0)
+		, depthMask			(false)
+		, stencilMask		(0)
+	{
+	}
+
+	deUint32	clearMask;
+	float		clearDepth;
+	int			clearStencil;
+
+	bool		useScissor;
+	tcu::IVec4	scissor;
+
+	bool		depthMask;
+	deUint32	stencilMask;
+};
+
+tcu::TextureFormat getDepthFormat (int depthBits)
+{
+	switch (depthBits)
+	{
+		case 8:		return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT8);
+		case 16:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16);
+		case 24:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNSIGNED_INT_24_8);
+		case 32:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT);
+		default:
+			TCU_FAIL("Can't map depth buffer format");
+	}
+}
+
+tcu::TextureFormat getStencilFormat (int stencilBits)
+{
+	switch (stencilBits)
+	{
+		case 8:		return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT8);
+		case 16:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT16);
+		case 24:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT_24_8);
+		case 32:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT32);
+		default:
+			TCU_FAIL("Can't map depth buffer format");
+	}
+}
+
+} // anonymous.
+
+class DepthStencilClearCase : public TestCase
+{
+public:
+								DepthStencilClearCase	(Context& context, const char* name, const char* description, int numIters, int numClears, bool depth, bool stencil, bool scissor, bool masked);
+								~DepthStencilClearCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+
+	IterateResult				iterate					(void);
+
+private:
+	void						generateClears			(vector<Clear>& dst, deUint32 seed);
+	void						renderGL				(tcu::Surface& dst, const vector<Clear>& clears);
+	void						renderReference			(tcu::Surface& dst, const vector<Clear>& clears);
+
+	bool						m_testDepth;
+	bool						m_testStencil;
+	bool						m_testScissor;
+	bool						m_masked;
+	int							m_numIters;
+	int							m_numClears;
+	int							m_curIter;
+
+	glu::ShaderProgram*			m_visProgram;
+};
+
+DepthStencilClearCase::DepthStencilClearCase (Context& context, const char* name, const char* description, int numIters, int numClears, bool depth, bool stencil, bool scissor, bool masked)
+	: TestCase			(context, name, description)
+	, m_testDepth		(depth)
+	, m_testStencil		(stencil)
+	, m_testScissor		(scissor)
+	, m_masked			(masked)
+	, m_numIters		(numIters)
+	, m_numClears		(numClears)
+	, m_curIter			(0)
+	, m_visProgram		(DE_NULL)
+{
+}
+
+DepthStencilClearCase::~DepthStencilClearCase (void)
+{
+	DepthStencilClearCase::deinit();
+}
+
+void DepthStencilClearCase::init (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	m_visProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(
+			// Vertex shader.
+			"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n",
+
+			// Fragment shader.
+			"#version 300 es\n"
+			"uniform mediump vec4 u_color;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = u_color;\n"
+			"}\n"));
+
+	if (!m_visProgram->isOk())
+	{
+		log << *m_visProgram;
+		delete m_visProgram;
+		m_visProgram = DE_NULL;
+		TCU_FAIL("Compile failed");
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void DepthStencilClearCase::deinit (void)
+{
+	delete m_visProgram;
+	m_visProgram = DE_NULL;
+}
+
+DepthStencilClearCase::IterateResult DepthStencilClearCase::iterate (void)
+{
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderTarget();
+	int							width			= renderTarget.getWidth();
+	int							height			= renderTarget.getHeight();
+	tcu::Surface				result			(width, height);
+	tcu::Surface				reference		(width, height);
+	tcu::RGBA					threshold		= renderTarget.getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<Clear>				clears;
+
+	if ((m_testDepth && renderTarget.getDepthBits() == 0) ||
+		(m_testStencil && renderTarget.getStencilBits() == 0))
+		throw tcu::NotSupportedError("No depth/stencil buffers", "", __FILE__, __LINE__);
+
+	generateClears(clears, deStringHash(getName())^deInt32Hash(m_curIter));
+	renderGL(result, clears);
+	renderReference(reference, clears);
+
+	bool	isLastIter		= m_curIter+1 == m_numIters;
+	bool	isOk			= tcu::pixelThresholdCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference, result, threshold, isLastIter ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);
+
+	if (!isOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+	m_curIter += 1;
+	return isLastIter || !isOk ? STOP : CONTINUE;
+}
+
+void DepthStencilClearCase::generateClears (vector<Clear>& clears, deUint32 seed)
+{
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderContext().getRenderTarget();
+	int							width			= renderTarget.getWidth();
+	int							height			= renderTarget.getHeight();
+	de::Random					rnd				(seed);
+
+	clears.resize(m_numClears);
+
+	for (vector<Clear>::iterator clear = clears.begin(); clear != clears.end(); clear++)
+	{
+		if (m_testScissor)
+		{
+			int w = rnd.getInt(1, width);
+			int h = rnd.getInt(1, height);
+			int x = rnd.getInt(0, width-w);
+			int y = rnd.getInt(0, height-h);
+
+			clear->useScissor	= true; // \todo [pyry] Should we randomize?
+			clear->scissor		= tcu::IVec4(x, y, w, h);
+		}
+		else
+			clear->useScissor = false;
+
+		clear->clearDepth	= rnd.getFloat(-0.2f, 1.2f);
+		clear->clearStencil	= rnd.getUint32();
+
+		clear->depthMask	= m_masked ? rnd.getBool()		: true;
+		clear->stencilMask	= m_masked ? rnd.getUint32()	: 0xffffffffu;
+
+		if (m_testDepth && m_testStencil)
+		{
+			switch (rnd.getInt(0, 2))
+			{
+				case 0: clear->clearMask = GL_DEPTH_BUFFER_BIT;							break;
+				case 1: clear->clearMask = GL_STENCIL_BUFFER_BIT;						break;
+				case 2: clear->clearMask = GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT;	break;
+			}
+		}
+		else if (m_testDepth)
+			clear->clearMask = GL_DEPTH_BUFFER_BIT;
+		else
+		{
+			DE_ASSERT(m_testStencil);
+			clear->clearMask = GL_STENCIL_BUFFER_BIT;
+		}
+	}
+}
+
+void DepthStencilClearCase::renderGL (tcu::Surface& dst, const vector<Clear>& clears)
+{
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	int							colorLoc		= gl.getUniformLocation(m_visProgram->getProgram(), "u_color");
+	int							positionLoc		= gl.getAttribLocation(m_visProgram->getProgram(), "a_position");
+	static const deUint8		indices[]		= { 0, 1, 2, 2, 1, 3 };
+
+	// Clear with default values.
+	gl.clearDepthf	(1.0f);
+	gl.clearStencil	(0);
+	gl.clearColor	(1.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear		(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Before clears");
+
+	for (vector<Clear>::const_iterator clear = clears.begin(); clear != clears.end(); clear++)
+	{
+		if (clear->useScissor)
+		{
+			gl.enable(GL_SCISSOR_TEST);
+			gl.scissor(clear->scissor.x(), clear->scissor.y(), clear->scissor.z(), clear->scissor.w());
+		}
+
+		// Clear values.
+		gl.clearDepthf	(clear->clearDepth);
+		gl.clearStencil	(clear->clearStencil);
+
+		// Masks.
+		gl.depthMask	(clear->depthMask ? GL_TRUE : GL_FALSE);
+		gl.stencilMask	(clear->stencilMask);
+
+		// Execute clear.
+		gl.clear		(clear->clearMask);
+
+		if (clear->useScissor)
+			gl.disable(GL_SCISSOR_TEST);
+	}
+
+	// Restore default masks.
+	gl.depthMask	(GL_TRUE);
+	gl.stencilMask	(0xffffffffu);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After clears");
+
+	gl.useProgram				(m_visProgram->getProgram());
+	gl.enableVertexAttribArray	(positionLoc);
+
+	// Visualize depth / stencil buffers.
+	if (m_testDepth)
+	{
+		int		numSteps	= DEPTH_STEPS;
+		float	step		= 2.0f / numSteps;
+
+		gl.enable	(GL_DEPTH_TEST);
+		gl.depthFunc(GL_LESS);
+		gl.depthMask(GL_FALSE);
+		gl.colorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
+
+		for (int ndx = 0; ndx < numSteps; ndx++)
+		{
+			float	d		= -1.0f + step*ndx;
+			float	c		= (float)ndx / (float)(numSteps-1);
+			float	pos[]	=
+			{
+				-1.0f, -1.0f, d,
+				-1.0f,  1.0f, d,
+				 1.0f, -1.0f, d,
+				 1.0f,  1.0f, d
+			};
+
+			gl.uniform4f			(colorLoc, 0.0f, 0.0f, c, 1.0f);
+			gl.vertexAttribPointer	(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, &pos[0]);
+			gl.drawElements			(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, &indices[0]);
+		}
+
+		gl.disable	(GL_DEPTH_TEST);
+		gl.depthMask(GL_TRUE);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "After depth visualization");
+	}
+
+	if (m_testStencil)
+	{
+		int		numSteps	= STENCIL_STEPS;
+		int		numValues	= (1 << TestCase::m_context.getRenderContext().getRenderTarget().getStencilBits()); // 2^bits
+		int		step		= numValues / numSteps;
+
+		gl.enable		(GL_STENCIL_TEST);
+		gl.stencilOp	(GL_KEEP, GL_KEEP, GL_KEEP);
+		gl.colorMask	(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
+
+		static const float pos[] =
+		{
+			-1.0f, -1.0f,
+			-1.0f,  1.0f,
+			 1.0f, -1.0f,
+			 1.0f,  1.0f
+		};
+		gl.vertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, &pos[0]);
+
+		for (int ndx = 0; ndx < numSteps; ndx++)
+		{
+			int		s	= step*ndx;
+			float	c	= (float)ndx / (float)(numSteps-1);
+
+			gl.stencilFunc	(GL_LEQUAL, s, 0xffu);
+			gl.uniform4f	(colorLoc, 0.0f, c, 0.0f, 1.0f);
+			gl.drawElements	(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, &indices[0]);
+		}
+
+		gl.disable(GL_STENCIL_TEST);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "After stencil visualization");
+	}
+
+	// Restore color mask (changed by visualization).
+	gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+}
+
+void DepthStencilClearCase::renderReference (tcu::Surface& dst, const vector<Clear>& clears)
+{
+	glu::RenderContext&			renderCtx		= TestCase::m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget	= renderCtx.getRenderTarget();
+
+	// Clear surface to red.
+	tcu::clear(dst.getAccess(), tcu::RGBA::red.toVec());
+
+	if (m_testDepth)
+	{
+		// Simulated depth buffer span.
+		tcu::TextureLevel		depthBufRow		(getDepthFormat(renderTarget.getDepthBits()), dst.getWidth(), 1, 1);
+		tcu::PixelBufferAccess	rowAccess		= depthBufRow.getAccess();
+
+		for (int y = 0; y < dst.getHeight(); y++)
+		{
+			// Clear to default value.
+			for (int x = 0; x < rowAccess.getWidth(); x++)
+				rowAccess.setPixel(Vec4(1.0f), x, 0);
+
+			// Execute clears.
+			for (vector<Clear>::const_iterator clear = clears.begin(); clear != clears.end(); clear++)
+			{
+				// Clear / mask test.
+				if ((clear->clearMask & GL_DEPTH_BUFFER_BIT) == 0 || !clear->depthMask)
+					continue;
+
+				tcu::IVec4 clearRect = clear->useScissor ? clear->scissor : tcu::IVec4(0, 0, dst.getWidth(), dst.getHeight());
+
+				// Intersection test.
+				if (!de::inBounds(y, clearRect.y(), clearRect.y()+clearRect.w()))
+					continue;
+
+				for (int x = clearRect.x(); x < clearRect.x()+clearRect.z(); x++)
+					rowAccess.setPixel(Vec4(de::clamp(clear->clearDepth, 0.0f, 1.0f)), x, 0);
+			}
+
+			// Map to colors.
+			for (int x = 0; x < dst.getWidth(); x++)
+			{
+				float		depth		= rowAccess.getPixel(x, 0).x();
+				float		step		= deFloatFloor(depth * (float)DEPTH_STEPS) / (float)(DEPTH_STEPS-1);
+				tcu::RGBA	oldColor	= dst.getPixel(x, y);
+				tcu::RGBA	newColor	= tcu::RGBA(oldColor.getRed(), oldColor.getGreen(), deClamp32(deRoundFloatToInt32(step * 255.0f), 0, 255), oldColor.getAlpha());
+
+				dst.setPixel(x, y, newColor);
+			}
+		}
+	}
+
+	if (m_testStencil)
+	{
+		// Simulated stencil buffer span.
+		int						stencilBits		= renderTarget.getStencilBits();
+		tcu::TextureLevel		depthBufRow		(getStencilFormat(stencilBits), dst.getWidth(), 1, 1);
+		tcu::PixelBufferAccess	rowAccess		= depthBufRow.getAccess();
+		deUint32				bufMask			= (1u<<stencilBits)-1;
+
+		for (int y = 0; y < dst.getHeight(); y++)
+		{
+			// Clear to default value.
+			for (int x = 0; x < rowAccess.getWidth(); x++)
+				rowAccess.setPixel(tcu::UVec4(0), x, 0);
+
+			// Execute clears.
+			for (vector<Clear>::const_iterator clear = clears.begin(); clear != clears.end(); clear++)
+			{
+				// Clear / mask test.
+				if ((clear->clearMask & GL_STENCIL_BUFFER_BIT) == 0 || clear->stencilMask == 0)
+					continue;
+
+				tcu::IVec4 clearRect = clear->useScissor ? clear->scissor : tcu::IVec4(0, 0, dst.getWidth(), dst.getHeight());
+
+				// Intersection test.
+				if (!de::inBounds(y, clearRect.y(), clearRect.y()+clearRect.w()))
+					continue;
+
+				for (int x = clearRect.x(); x < clearRect.x()+clearRect.z(); x++)
+				{
+					deUint32	oldVal	= rowAccess.getPixelUint(x, 0).w();
+					deUint32	newVal	= ((oldVal & ~clear->stencilMask) | (clear->clearStencil & clear->stencilMask)) & bufMask;
+					rowAccess.setPixel(tcu::UVec4(newVal), x, 0);
+				}
+			}
+
+			// Map to colors.
+			for (int x = 0; x < dst.getWidth(); x++)
+			{
+				deUint32	stencil		= rowAccess.getPixelUint(x, 0).w();
+				float		step		= (float)(stencil / ((1u<<stencilBits) / (deUint32)STENCIL_STEPS)) / (float)(STENCIL_STEPS-1);
+				tcu::RGBA	oldColor	= dst.getPixel(x, y);
+				tcu::RGBA	newColor	= tcu::RGBA(oldColor.getRed(), deClamp32(deRoundFloatToInt32(step * 255.0f), 0, 255), oldColor.getBlue(), oldColor.getAlpha());
+
+				dst.setPixel(x, y, newColor);
+			}
+		}
+	}
+}
+
+DepthStencilClearTests::DepthStencilClearTests (Context& context)
+	: TestCaseGroup(context, "depth_stencil_clear", "Depth and stencil clear tests")
+{
+}
+
+void DepthStencilClearTests::init (void)
+{
+	//																					iters	clears	depth	stencil	scissor	masked
+	addChild(new DepthStencilClearCase(m_context, "depth",							"",	4,		2,		true,	false,	false,	false));
+	addChild(new DepthStencilClearCase(m_context, "depth_scissored",				"",	4,		16,		true,	false,	true,	false));
+	addChild(new DepthStencilClearCase(m_context, "depth_scissored_masked",			"",	4,		16,		true,	false,	true,	true));
+
+	addChild(new DepthStencilClearCase(m_context, "stencil",						"",	4,		2,		false,	true,	false,	false));
+	addChild(new DepthStencilClearCase(m_context, "stencil_masked",					"",	4,		8,		false,	true,	false,	true));
+	addChild(new DepthStencilClearCase(m_context, "stencil_scissored",				"",	4,		16,		false,	true,	true,	false));
+	addChild(new DepthStencilClearCase(m_context, "stencil_scissored_masked",		"",	4,		16,		false,	true,	true,	true));
+
+	addChild(new DepthStencilClearCase(m_context, "depth_stencil",					"",	4,		2,		true,	true,	false,	false));
+	addChild(new DepthStencilClearCase(m_context, "depth_stencil_masked",			"",	4,		8,		true,	true,	false,	true));
+	addChild(new DepthStencilClearCase(m_context, "depth_stencil_scissored",		"",	4,		16,		true,	true,	true,	false));
+	addChild(new DepthStencilClearCase(m_context, "depth_stencil_scissored_masked",	"",	4,		16,		true,	true,	true,	true));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fDepthStencilClearTests.hpp b/modules/gles3/functional/es3fDepthStencilClearTests.hpp
new file mode 100644
index 0000000..8c3caf7
--- /dev/null
+++ b/modules/gles3/functional/es3fDepthStencilClearTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FDEPTHSTENCILCLEARTESTS_HPP
+#define _ES3FDEPTHSTENCILCLEARTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth and stencil buffer clear tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class DepthStencilClearTests : public TestCaseGroup
+{
+public:
+								DepthStencilClearTests		(Context& context);
+	virtual						~DepthStencilClearTests		(void) {}
+
+	virtual void				init						(void);
+
+private:
+								DepthStencilClearTests		(const DepthStencilClearTests&);
+	DepthStencilClearTests&		operator=					(const DepthStencilClearTests&);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FDEPTHSTENCILCLEARTESTS_HPP
diff --git a/modules/gles3/functional/es3fDepthStencilTests.cpp b/modules/gles3/functional/es3fDepthStencilTests.cpp
new file mode 100644
index 0000000..861d2a0
--- /dev/null
+++ b/modules/gles3/functional/es3fDepthStencilTests.cpp
@@ -0,0 +1,1158 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth & stencil tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fDepthStencilTests.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuTextureUtil.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMemory.h"
+#include "deString.h"
+#include "rrFragmentOperations.hpp"
+#include "sglrReferenceUtils.hpp"
+
+#include <algorithm>
+#include <sstream>
+
+#include "glw.h"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::vector;
+using tcu::IVec2;
+using tcu::Vec2;
+using tcu::Vec4;
+using tcu::TestLog;
+using std::ostringstream;
+
+enum
+{
+	VIEWPORT_WIDTH			= 4*3*4,
+	VIEWPORT_HEIGHT			= 4*12,
+
+	NUM_RANDOM_CASES		= 25,
+	NUM_RANDOM_SUB_CASES	= 10
+};
+
+namespace DepthStencilCaseUtil
+{
+
+struct StencilParams
+{
+	deUint32	function;
+	int			reference;
+	deUint32	compareMask;
+
+	deUint32	stencilFailOp;
+	deUint32	depthFailOp;
+	deUint32	depthPassOp;
+
+	deUint32	writeMask;
+
+	StencilParams (void)
+		: function		(0)
+		, reference		(0)
+		, compareMask	(0)
+		, stencilFailOp	(0)
+		, depthFailOp	(0)
+		, depthPassOp	(0)
+		, writeMask		(0)
+	{
+	}
+};
+
+struct DepthStencilParams
+{
+	rr::FaceType	visibleFace;			//!< Quad visible face.
+
+	bool			stencilTestEnabled;
+	StencilParams	stencil[rr::FACETYPE_LAST];
+
+	bool			depthTestEnabled;
+	deUint32		depthFunc;
+	float			depth;
+	bool			depthWriteMask;
+
+	DepthStencilParams (void)
+		: visibleFace			(rr::FACETYPE_LAST)
+		, stencilTestEnabled	(false)
+		, depthTestEnabled		(false)
+		, depthFunc				(0)
+		, depth					(0.0f)
+		, depthWriteMask		(false)
+	{
+	}
+};
+
+tcu::TestLog& operator<< (tcu::TestLog& log, const StencilParams& params)
+{
+	log << TestLog::Message << "  func = " << glu::getCompareFuncStr(params.function) << "\n"
+							<< "  ref = " << params.reference << "\n"
+							<< "  compare mask = " << tcu::toHex(params.compareMask) << "\n"
+							<< "  stencil fail = " << glu::getStencilOpStr(params.stencilFailOp) << "\n"
+							<< "  depth fail = " << glu::getStencilOpStr(params.depthFailOp) << "\n"
+							<< "  depth pass = " << glu::getStencilOpStr(params.depthPassOp) << "\n"
+							<< "  write mask = " << tcu::toHex(params.writeMask) << "\n"
+		<< TestLog::EndMessage;
+	return log;
+}
+
+tcu::TestLog& operator<< (tcu::TestLog& log, const DepthStencilParams& params)
+{
+	log << TestLog::Message << "Stencil test: " << (params.stencilTestEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
+	if (params.stencilTestEnabled)
+	{
+		log << TestLog::Message << "Front-face stencil state: " << TestLog::EndMessage;
+		log << params.stencil[rr::FACETYPE_FRONT];
+
+		log << TestLog::Message << "Back-face stencil state: " << TestLog::EndMessage;
+		log << params.stencil[rr::FACETYPE_BACK];
+	}
+
+	log << TestLog::Message << "Depth test: " << (params.depthTestEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
+	if (params.depthTestEnabled)
+	{
+		log << TestLog::Message << "  func = " << glu::getCompareFuncStr(params.depthFunc) << "\n"
+								   "  depth value = " << params.depth << "\n"
+								   "  write mask = " << (params.depthWriteMask ? "true" : "false") << "\n"
+			<< TestLog::EndMessage;
+	}
+
+	log << TestLog::Message << "Triangles are " << (params.visibleFace == rr::FACETYPE_FRONT ? "front" : "back") << "-facing" << TestLog::EndMessage;
+
+	return log;
+}
+
+struct ClearCommand
+{
+	rr::WindowRectangle	rect;
+	deUint32			buffers;
+	tcu::Vec4			color;
+	int					stencil;
+	// \note No depth here - don't use clears for setting depth values; use quad rendering instead. Cleared depths are in [0, 1] to begin with,
+	//		 whereas rendered depths are given in [-1, 1] and then mapped to [0, 1]; this discrepancy could cause precision issues in depth tests.
+
+	ClearCommand (void)
+		: rect		(0, 0, 0, 0)
+		, buffers	(0)
+		, stencil	(0)
+	{
+	}
+
+	ClearCommand (const rr::WindowRectangle&	rect_,
+				  deUint32						buffers_,
+				  const tcu::Vec4&				color_,
+				  int							stencil_)
+		: rect		(rect_)
+		, buffers	(buffers_)
+		, color		(color_)
+		, stencil	(stencil_)
+	{
+	}
+};
+
+struct RenderCommand
+{
+	DepthStencilParams		params;
+	rr::WindowRectangle		rect;
+	tcu::Vec4				color;
+	tcu::BVec4				colorMask;
+
+	RenderCommand (void)
+		: rect(0, 0, 0, 0)
+	{
+	}
+};
+
+struct RefRenderCommand
+{
+	gls::FragmentOpUtil::IntegerQuad	quad;
+	rr::FragmentOperationState			state;
+};
+
+struct TestRenderTarget
+{
+	int		width;
+	int		height;
+	int		depthBits;
+	int		stencilBits;
+
+	TestRenderTarget (int width_,
+					  int height_,
+					  int depthBits_,
+					  int stencilBits_)
+		: width			(width_)
+		, height		(height_)
+		, depthBits		(depthBits_)
+		, stencilBits	(stencilBits_)
+	{
+	}
+
+	TestRenderTarget (void)
+		: width			(0)
+		, height		(0)
+		, depthBits		(0)
+		, stencilBits	(0)
+	{
+	}
+};
+
+void getStencilTestValues (int stencilBits, int numValues, int* values)
+{
+	int numLowest		= numValues/2;
+	int	numHighest		= numValues-numLowest;
+	int	maxVal			= (1<<stencilBits)-1;
+
+	for (int ndx = 0; ndx < numLowest; ndx++)
+		values[ndx] = ndx;
+
+	for (int ndx = 0; ndx < numHighest; ndx++)
+		values[numValues-ndx-1] = maxVal-ndx;
+}
+
+void generateBaseClearAndDepthCommands (const TestRenderTarget& target, vector<ClearCommand>& clearCommands, vector<RenderCommand>& renderCommands)
+{
+	DE_ASSERT(clearCommands.empty());
+	DE_ASSERT(renderCommands.empty());
+
+	const int		numL0CellsX		= 4;
+	const int		numL0CellsY		= 4;
+	const int		numL1CellsX		= 3;
+	const int		numL1CellsY		= 1;
+	int				cellL0Width		= target.width/numL0CellsX;
+	int				cellL0Height	= target.height/numL0CellsY;
+	int				cellL1Width		= cellL0Width/numL1CellsX;
+	int				cellL1Height	= cellL0Height/numL1CellsY;
+
+	int				stencilValues[numL0CellsX*numL0CellsY];
+	float			depthValues[numL1CellsX*numL1CellsY];
+
+	if (cellL0Width <= 0 || cellL1Width <= 0 || cellL0Height <= 0 || cellL1Height <= 0)
+		throw tcu::NotSupportedError("Too small render target");
+
+	// Fullscreen clear to black.
+	clearCommands.push_back(ClearCommand(rr::WindowRectangle(0, 0, target.width, target.height), GL_COLOR_BUFFER_BIT, Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0));
+
+	// Compute stencil values: numL0CellsX*numL0CellsY combinations of lowest and highest bits.
+	getStencilTestValues(target.stencilBits, numL0CellsX*numL0CellsY, &stencilValues[0]);
+
+	// Compute depth values
+	{
+		int		numValues		= DE_LENGTH_OF_ARRAY(depthValues);
+		float	depthStep		= 2.0f/(numValues-1);
+
+		for (int ndx = 0; ndx < numValues; ndx++)
+			depthValues[ndx] = -1.0f + depthStep*ndx;
+	}
+
+	for (int y0 = 0; y0 < numL0CellsY; y0++)
+	{
+		for (int x0 = 0; x0 < numL0CellsX; x0++)
+		{
+			int stencilValue = stencilValues[y0*numL0CellsX + x0];
+
+			for (int y1 = 0; y1 < numL1CellsY; y1++)
+			{
+				for (int x1 = 0; x1 < numL1CellsX; x1++)
+				{
+					int					x			= x0*cellL0Width + x1*cellL1Width;
+					int					y			= y0*cellL0Height + y1*cellL1Height;
+					rr::WindowRectangle	cellL1Rect	(x, y, cellL1Width, cellL1Height);
+
+					clearCommands.push_back(ClearCommand(cellL1Rect, GL_STENCIL_BUFFER_BIT, Vec4(0), stencilValue));
+
+					RenderCommand renderCmd;
+					renderCmd.params.visibleFace		= rr::FACETYPE_FRONT;
+					renderCmd.params.depth				= depthValues[y1*numL1CellsX + x1];;
+					renderCmd.params.depthTestEnabled	= true;
+					renderCmd.params.depthFunc			= GL_ALWAYS;
+					renderCmd.params.depthWriteMask		= true;
+					renderCmd.colorMask					= tcu::BVec4(false);
+					renderCmd.rect						= cellL1Rect;
+
+					renderCommands.push_back(renderCmd);
+				}
+			}
+		}
+	}
+}
+
+void generateDepthVisualizeCommands (const TestRenderTarget& target, vector<RenderCommand>& commands)
+{
+	const float			epsilon			= -0.05f;
+	static const float	depthSteps[]	= {-1.0f, -0.5f, 0.0f, 0.5f, 1.0f};
+	int					numSteps		= DE_LENGTH_OF_ARRAY(depthSteps);
+	const float			colorStep		= 1.0f / (float)(numSteps-1);
+
+	for (int ndx = 0; ndx < numSteps; ndx++)
+	{
+		RenderCommand cmd;
+
+		cmd.params.visibleFace		= rr::FACETYPE_FRONT;
+		cmd.rect					= rr::WindowRectangle(0, 0, target.width, target.height);
+		cmd.color					= Vec4(0.0f, 0.0f, colorStep*ndx, 0.0f);
+		cmd.colorMask				= tcu::BVec4(false, false, true, false);
+		cmd.params.depth			= depthSteps[ndx]+epsilon;
+		cmd.params.depthTestEnabled	= true;
+		cmd.params.depthFunc		= GL_LESS;
+		cmd.params.depthWriteMask	= false;
+
+		commands.push_back(cmd);
+	}
+}
+
+void generateStencilVisualizeCommands (const TestRenderTarget& target, vector<RenderCommand>& commands)
+{
+	const int	numValues		= 4*4;
+	float		colorStep		= 1.0f / numValues; // 0 is reserved for non-matching.
+	int			stencilValues[numValues];
+
+	getStencilTestValues(target.stencilBits, numValues, &stencilValues[0]);
+
+	for (int ndx = 0; ndx < numValues; ndx++)
+	{
+		RenderCommand cmd;
+
+		cmd.params.visibleFace							= rr::FACETYPE_FRONT;
+		cmd.rect										= rr::WindowRectangle(0, 0, target.width, target.height);
+		cmd.color										= Vec4(0.0f, colorStep*(ndx+1), 0.0f, 0.0f);
+		cmd.colorMask									= tcu::BVec4(false, true, false, false);
+		cmd.params.stencilTestEnabled					= true;
+
+		cmd.params.stencil[rr::FACETYPE_FRONT].function			= GL_EQUAL;
+		cmd.params.stencil[rr::FACETYPE_FRONT].reference		= stencilValues[ndx];
+		cmd.params.stencil[rr::FACETYPE_FRONT].compareMask		= ~0u;
+		cmd.params.stencil[rr::FACETYPE_FRONT].stencilFailOp	= GL_KEEP;
+		cmd.params.stencil[rr::FACETYPE_FRONT].depthFailOp		= GL_KEEP;
+		cmd.params.stencil[rr::FACETYPE_FRONT].depthPassOp		= GL_KEEP;
+		cmd.params.stencil[rr::FACETYPE_FRONT].writeMask		= 0u;
+
+		cmd.params.stencil[rr::FACETYPE_BACK] = cmd.params.stencil[rr::FACETYPE_FRONT];
+
+		commands.push_back(cmd);
+	}
+}
+
+void translateStencilState (const StencilParams& src, rr::StencilState& dst)
+{
+	dst.func		= sglr::rr_util::mapGLTestFunc(src.function);
+	dst.ref			= src.reference;
+	dst.compMask	= src.compareMask;
+	dst.sFail		= sglr::rr_util::mapGLStencilOp(src.stencilFailOp);
+	dst.dpFail		= sglr::rr_util::mapGLStencilOp(src.depthFailOp);
+	dst.dpPass		= sglr::rr_util::mapGLStencilOp(src.depthPassOp);
+	dst.writeMask	= src.writeMask;
+}
+
+void translateCommand (const RenderCommand& src, RefRenderCommand& dst, const TestRenderTarget& renderTarget)
+{
+	const float		far				= 1.0f;
+	const float		near			= 0.0f;
+	bool			hasDepth		= renderTarget.depthBits > 0;
+	bool			hasStencil		= renderTarget.stencilBits > 0;
+	bool			isFrontFacing	= src.params.visibleFace == rr::FACETYPE_FRONT;
+
+	dst.quad.posA = IVec2(isFrontFacing ? src.rect.left : (src.rect.left+src.rect.width-1), src.rect.bottom);
+	dst.quad.posB = IVec2(isFrontFacing ? (src.rect.left+src.rect.width-1) : src.rect.left, src.rect.bottom+src.rect.height-1);
+
+	std::fill(DE_ARRAY_BEGIN(dst.quad.color), DE_ARRAY_END(dst.quad.color), src.color);
+	std::fill(DE_ARRAY_BEGIN(dst.quad.depth), DE_ARRAY_END(dst.quad.depth), ((far-near)/2.0f) * src.params.depth + (near+far)/2.0f);
+
+	dst.state.colorMask = src.colorMask;
+
+	dst.state.scissorTestEnabled		= false;
+	dst.state.stencilTestEnabled		= hasStencil && src.params.stencilTestEnabled;
+	dst.state.depthTestEnabled			= hasDepth && src.params.depthTestEnabled;
+	dst.state.blendMode					= rr::BLENDMODE_NONE;
+	dst.state.numStencilBits			= renderTarget.stencilBits;
+
+	if (dst.state.depthTestEnabled)
+	{
+		dst.state.depthFunc					= sglr::rr_util::mapGLTestFunc(src.params.depthFunc);
+		dst.state.depthMask					= src.params.depthWriteMask;
+	}
+
+	if (dst.state.stencilTestEnabled)
+	{
+		translateStencilState(src.params.stencil[rr::FACETYPE_BACK],	dst.state.stencilStates[rr::FACETYPE_BACK]);
+		translateStencilState(src.params.stencil[rr::FACETYPE_FRONT],	dst.state.stencilStates[rr::FACETYPE_FRONT]);
+	}
+}
+
+void render (const vector<ClearCommand>& clears, int viewportX, int viewportY)
+{
+	glEnable(GL_SCISSOR_TEST);
+
+	for (int ndx = 0; ndx < (int)clears.size(); ndx++)
+	{
+		const ClearCommand& clear = clears[ndx];
+
+		if (clear.buffers & GL_COLOR_BUFFER_BIT)	glClearColor(clear.color.x(), clear.color.y(), clear.color.z(), clear.color.w());
+		if (clear.buffers & GL_STENCIL_BUFFER_BIT)	glClearStencil(clear.stencil);
+
+		DE_ASSERT(clear.buffers == (clear.buffers & (GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT))); // \note Don't use clear for depths.
+
+		glScissor(clear.rect.left+viewportX, clear.rect.bottom+viewportY, clear.rect.width, clear.rect.height);
+		glClear(clear.buffers);
+	}
+
+	glDisable(GL_SCISSOR_TEST);
+}
+
+void render (gls::FragmentOpUtil::QuadRenderer& renderer, const RenderCommand& command, int viewportX, int viewportY)
+{
+	if (command.params.stencilTestEnabled)
+	{
+		glEnable(GL_STENCIL_TEST);
+
+		for (int face = 0; face < rr::FACETYPE_LAST; face++)
+		{
+			deUint32				glFace	= face == rr::FACETYPE_BACK ? GL_BACK : GL_FRONT;
+			const StencilParams&	sParams	= command.params.stencil[face];
+
+			glStencilFuncSeparate(glFace, sParams.function, sParams.reference, sParams.compareMask);
+			glStencilOpSeparate(glFace, sParams.stencilFailOp, sParams.depthFailOp, sParams.depthPassOp);
+			glStencilMaskSeparate(glFace, sParams.writeMask);
+		}
+	}
+	else
+		glDisable(GL_STENCIL_TEST);
+
+	if (command.params.depthTestEnabled)
+	{
+		glEnable(GL_DEPTH_TEST);
+		glDepthFunc(command.params.depthFunc);
+		glDepthMask(command.params.depthWriteMask ? GL_TRUE : GL_FALSE);
+	}
+	else
+		glDisable(GL_DEPTH_TEST);
+
+	glColorMask(command.colorMask[0] ? GL_TRUE : GL_FALSE,
+				command.colorMask[1] ? GL_TRUE : GL_FALSE,
+				command.colorMask[2] ? GL_TRUE : GL_FALSE,
+				command.colorMask[3] ? GL_TRUE : GL_FALSE);
+	glViewport(command.rect.left+viewportX, command.rect.bottom+viewportY, command.rect.width, command.rect.height);
+
+	gls::FragmentOpUtil::Quad quad;
+
+	bool isFrontFacing = command.params.visibleFace == rr::FACETYPE_FRONT;
+	quad.posA = Vec2(isFrontFacing ? -1.0f :  1.0f, -1.0f);
+	quad.posB = Vec2(isFrontFacing ?  1.0f : -1.0f,  1.0f);
+
+	std::fill(DE_ARRAY_BEGIN(quad.color), DE_ARRAY_END(quad.color), command.color);
+	std::fill(DE_ARRAY_BEGIN(quad.depth), DE_ARRAY_END(quad.depth), command.params.depth);
+
+	renderer.render(quad);
+	GLU_CHECK();
+}
+
+void renderReference (const vector<ClearCommand>& clears, const tcu::PixelBufferAccess& dstColor, const tcu::PixelBufferAccess& dstStencil, int stencilBits)
+{
+	for (int ndx = 0; ndx < (int)clears.size(); ndx++)
+	{
+		const ClearCommand& clear = clears[ndx];
+
+		if (clear.buffers & GL_COLOR_BUFFER_BIT)
+			tcu::clear(tcu::getSubregion(dstColor, clear.rect.left, clear.rect.bottom, clear.rect.width, clear.rect.height), clear.color);
+
+		if (clear.buffers & GL_STENCIL_BUFFER_BIT && stencilBits > 0)
+		{
+			int maskedVal = clear.stencil & ((1<<stencilBits)-1);
+			tcu::clearStencil(tcu::getSubregion(dstStencil, clear.rect.left, clear.rect.bottom, clear.rect.width, clear.rect.height), maskedVal);
+		}
+
+		DE_ASSERT(clear.buffers == (clear.buffers & (GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT))); // \note Don't use clear for depths.
+	}
+}
+
+} // DepthStencilCaseUtil
+
+using namespace DepthStencilCaseUtil;
+
+class DepthStencilCase : public TestCase
+{
+public:
+							DepthStencilCase		(Context& context, const char* name, const char* desc, const std::vector<DepthStencilParams>& cases);
+							~DepthStencilCase		(void);
+
+	void					init					(void);
+	void					deinit					(void);
+
+	IterateResult			iterate					(void);
+
+private:
+							DepthStencilCase		(const DepthStencilCase& other);
+	DepthStencilCase&		operator=				(const DepthStencilCase& other);
+
+	std::vector<DepthStencilParams>					m_cases;
+
+	TestRenderTarget								m_renderTarget;
+	std::vector<ClearCommand>						m_baseClears;
+	std::vector<RenderCommand>						m_baseDepthRenders;
+	std::vector<RenderCommand>						m_visualizeCommands;
+	std::vector<RefRenderCommand>					m_refBaseDepthRenders;
+	std::vector<RefRenderCommand>					m_refVisualizeCommands;
+
+	gls::FragmentOpUtil::QuadRenderer*				m_renderer;
+	tcu::Surface*									m_refColorBuffer;
+	tcu::TextureLevel*								m_refDepthBuffer;
+	tcu::TextureLevel*								m_refStencilBuffer;
+	gls::FragmentOpUtil::ReferenceQuadRenderer*		m_refRenderer;
+
+	int												m_iterNdx;
+};
+
+DepthStencilCase::DepthStencilCase (Context& context, const char* name, const char* desc, const std::vector<DepthStencilParams>& cases)
+	: TestCase				(context, name, desc)
+	, m_cases				(cases)
+	, m_renderer			(DE_NULL)
+	, m_refColorBuffer		(DE_NULL)
+	, m_refDepthBuffer		(DE_NULL)
+	, m_refStencilBuffer	(DE_NULL)
+	, m_refRenderer			(DE_NULL)
+	, m_iterNdx				(0)
+{
+}
+
+DepthStencilCase::~DepthStencilCase (void)
+{
+	delete m_renderer;
+	delete m_refColorBuffer;
+	delete m_refDepthBuffer;
+	delete m_refStencilBuffer;
+	delete m_refRenderer;
+}
+
+void DepthStencilCase::init (void)
+{
+	DE_ASSERT(!m_renderer && !m_refColorBuffer && !m_refDepthBuffer && !m_refStencilBuffer && !m_refRenderer);
+
+	// Compute render target.
+	int viewportW	= de::min<int>(m_context.getRenderTarget().getWidth(), VIEWPORT_WIDTH);
+	int viewportH	= de::min<int>(m_context.getRenderTarget().getHeight(), VIEWPORT_HEIGHT);
+	m_renderTarget	= TestRenderTarget(viewportW, viewportH, m_context.getRenderTarget().getDepthBits(), m_context.getRenderTarget().getStencilBits());
+
+	// Compute base clears & visualization commands.
+	generateBaseClearAndDepthCommands(m_renderTarget, m_baseClears, m_baseDepthRenders);
+	generateDepthVisualizeCommands(m_renderTarget, m_visualizeCommands);
+	generateStencilVisualizeCommands(m_renderTarget, m_visualizeCommands);
+
+	// Translate to ref commands.
+	m_refBaseDepthRenders.resize(m_baseDepthRenders.size());
+	for (int ndx = 0; ndx < (int)m_baseDepthRenders.size(); ndx++)
+		translateCommand(m_baseDepthRenders[ndx], m_refBaseDepthRenders[ndx], m_renderTarget);
+
+	m_refVisualizeCommands.resize(m_visualizeCommands.size());
+	for (int ndx = 0; ndx < (int)m_visualizeCommands.size(); ndx++)
+		translateCommand(m_visualizeCommands[ndx], m_refVisualizeCommands[ndx], m_renderTarget);
+
+	m_renderer			= new gls::FragmentOpUtil::QuadRenderer(m_context.getRenderContext(), glu::GLSL_VERSION_300_ES);
+	m_refColorBuffer	= new tcu::Surface(viewportW, viewportH);
+	m_refDepthBuffer	= new tcu::TextureLevel(tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT),			viewportW, viewportH);
+	m_refStencilBuffer	= new tcu::TextureLevel(tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT32),	viewportW, viewportH);
+	m_refRenderer		= new gls::FragmentOpUtil::ReferenceQuadRenderer();
+
+	m_iterNdx			= 0;
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void DepthStencilCase::deinit (void)
+{
+	delete m_renderer;
+	delete m_refColorBuffer;
+	delete m_refDepthBuffer;
+	delete m_refStencilBuffer;
+	delete m_refRenderer;
+
+	m_renderer			= DE_NULL;
+	m_refColorBuffer	= DE_NULL;
+	m_refDepthBuffer	= DE_NULL;
+	m_refStencilBuffer	= DE_NULL;
+	m_refRenderer		= DE_NULL;
+
+	m_baseClears.clear();
+	m_baseDepthRenders.clear();
+	m_visualizeCommands.clear();
+	m_refBaseDepthRenders.clear();
+	m_refVisualizeCommands.clear();
+}
+
+DepthStencilCase::IterateResult DepthStencilCase::iterate (void)
+{
+	de::Random				rnd				(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
+	int						viewportX		= rnd.getInt(0, m_context.getRenderTarget().getWidth()-m_renderTarget.width);
+	int						viewportY		= rnd.getInt(0, m_context.getRenderTarget().getHeight()-m_renderTarget.height);
+	RenderCommand			testCmd;
+
+	tcu::Surface			renderedImg		(m_renderTarget.width, m_renderTarget.height);
+	tcu::RGBA				threshold		= m_context.getRenderTarget().getPixelFormat().getColorThreshold();
+
+	// Fill in test command for this iteration.
+	testCmd.color		= Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+	testCmd.colorMask	= tcu::BVec4(true);
+	testCmd.rect		= rr::WindowRectangle(0, 0, m_renderTarget.width, m_renderTarget.height);
+	testCmd.params		= m_cases[m_iterNdx];
+
+	if (m_iterNdx == 0)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Channels:\n"
+													"  RED: passing pixels\n"
+													"  GREEN: stencil values\n"
+													"  BLUE: depth values"
+							<< TestLog::EndMessage;
+	}
+
+	if (m_cases.size() > 1)
+		m_testCtx.getLog() << TestLog::Message << "Iteration " << m_iterNdx << "..." << TestLog::EndMessage;
+
+	m_testCtx.getLog() << m_cases[m_iterNdx];
+
+	// Submit render commands to gl GL.
+
+	// Base clears.
+	render(m_baseClears, viewportX, viewportY);
+
+	// Base depths.
+	for (vector<RenderCommand>::const_iterator cmd = m_baseDepthRenders.begin(); cmd != m_baseDepthRenders.end(); ++cmd)
+		render(*m_renderer, *cmd, viewportX, viewportY);
+
+	// Test command.
+	render(*m_renderer, testCmd, viewportX, viewportY);
+
+	// Visualization commands.
+	for (vector<RenderCommand>::const_iterator cmd = m_visualizeCommands.begin(); cmd != m_visualizeCommands.end(); ++cmd)
+		render(*m_renderer, *cmd, viewportX, viewportY);
+
+	// Re-enable all write masks.
+	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+	glDepthMask(GL_TRUE);
+	glStencilMask(~0u);
+
+	// Ask GPU to start rendering.
+	glFlush();
+
+	// Render reference while GPU is doing work.
+	{
+		RefRenderCommand refTestCmd;
+		translateCommand(testCmd, refTestCmd, m_renderTarget);
+
+		// Base clears.
+		renderReference(m_baseClears, m_refColorBuffer->getAccess(), m_refStencilBuffer->getAccess(), m_renderTarget.depthBits);
+
+		// Base depths.
+		for (vector<RefRenderCommand>::const_iterator cmd = m_refBaseDepthRenders.begin(); cmd != m_refBaseDepthRenders.end(); ++cmd)
+			m_refRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()),
+								  gls::FragmentOpUtil::getMultisampleAccess(m_refDepthBuffer->getAccess()),
+								  gls::FragmentOpUtil::getMultisampleAccess(m_refStencilBuffer->getAccess()),
+								  cmd->quad, cmd->state);
+
+		// Test command.
+		m_refRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()),
+							  gls::FragmentOpUtil::getMultisampleAccess(m_refDepthBuffer->getAccess()),
+							  gls::FragmentOpUtil::getMultisampleAccess(m_refStencilBuffer->getAccess()),
+							  refTestCmd.quad, refTestCmd.state);
+
+		// Visualization commands.
+		for (vector<RefRenderCommand>::const_iterator cmd = m_refVisualizeCommands.begin(); cmd != m_refVisualizeCommands.end(); ++cmd)
+			m_refRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()),
+								  gls::FragmentOpUtil::getMultisampleAccess(m_refDepthBuffer->getAccess()),
+								  gls::FragmentOpUtil::getMultisampleAccess(m_refStencilBuffer->getAccess()),
+								  cmd->quad, cmd->state);
+	}
+
+	// Read rendered image.
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedImg.getAccess());
+
+	m_iterNdx += 1;
+
+	// Compare to reference.
+	bool	isLastIter	= m_iterNdx >= (int)m_cases.size();
+	bool	compareOk	= tcu::pixelThresholdCompare(m_testCtx.getLog(), "CompareResult", "Image Comparison Result", *m_refColorBuffer, renderedImg, threshold,
+													 tcu::COMPARE_LOG_RESULT);
+
+	m_testCtx.getLog() << TestLog::Message << (compareOk ? "  Passed." : "  FAILED!") << TestLog::EndMessage;
+	if (!compareOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+	if (compareOk && !isLastIter)
+		return CONTINUE;
+	else
+		return STOP;
+}
+
+DepthStencilTests::DepthStencilTests (Context& context)
+	: TestCaseGroup(context, "depth_stencil", "Depth and Stencil Op Tests")
+{
+}
+
+DepthStencilTests::~DepthStencilTests (void)
+{
+}
+
+static void randomDepthStencilState (de::Random& rnd, DepthStencilParams& params)
+{
+	const float stencilTestProbability	= 0.8f;
+	const float depthTestProbability	= 0.7f;
+
+	static const deUint32 compareFuncs[] =
+	{
+		GL_NEVER,
+		GL_ALWAYS,
+		GL_LESS,
+		GL_LEQUAL,
+		GL_EQUAL,
+		GL_GEQUAL,
+		GL_GREATER,
+		GL_NOTEQUAL
+	};
+
+	static const deUint32 stencilOps[] =
+	{
+		GL_KEEP,
+		GL_ZERO,
+		GL_REPLACE,
+		GL_INCR,
+		GL_DECR,
+		GL_INVERT,
+		GL_INCR_WRAP,
+		GL_DECR_WRAP
+	};
+
+	static const float depthValues[] = { -1.0f, -0.8f, -0.6f, -0.4f, -0.2f, 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f };
+
+	params.visibleFace			= rnd.getBool() ? rr::FACETYPE_FRONT : rr::FACETYPE_BACK;
+	params.stencilTestEnabled	= rnd.getFloat() < stencilTestProbability;
+	params.depthTestEnabled		= !params.stencilTestEnabled || (rnd.getFloat() < depthTestProbability);
+
+	if (params.stencilTestEnabled)
+	{
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			params.stencil[ndx].function		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(compareFuncs), DE_ARRAY_END(compareFuncs));
+			params.stencil[ndx].reference		= rnd.getInt(-2, 260);
+			params.stencil[ndx].compareMask		= rnd.getUint32();
+			params.stencil[ndx].stencilFailOp	= rnd.choose<deUint32>(DE_ARRAY_BEGIN(stencilOps), DE_ARRAY_END(stencilOps));
+			params.stencil[ndx].depthFailOp		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(stencilOps), DE_ARRAY_END(stencilOps));
+			params.stencil[ndx].depthPassOp		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(stencilOps), DE_ARRAY_END(stencilOps));
+			params.stencil[ndx].writeMask		= rnd.getUint32();
+		}
+	}
+
+	if (params.depthTestEnabled)
+	{
+		params.depthFunc		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(compareFuncs), DE_ARRAY_END(compareFuncs));
+		params.depth			= rnd.choose<float>(DE_ARRAY_BEGIN(depthValues), DE_ARRAY_END(depthValues));
+		params.depthWriteMask	= rnd.getBool();
+	}
+}
+
+void DepthStencilTests::init (void)
+{
+	static const struct
+	{
+		const char*	name;
+		deUint32	func;
+	} compareFuncs[] =
+	{
+		{ "never",		GL_NEVER	},
+		{ "always",		GL_ALWAYS	},
+		{ "less",		GL_LESS		},
+		{ "lequal",		GL_LEQUAL	},
+		{ "equal",		GL_EQUAL	},
+		{ "gequal",		GL_GEQUAL	},
+		{ "greater",	GL_GREATER	},
+		{ "notequal",	GL_NOTEQUAL	}
+	};
+
+	static const struct
+	{
+		const char*	name;
+		deUint32	op;
+	} stencilOps[] =
+	{
+		{ "keep",		GL_KEEP			},
+		{ "zero",		GL_ZERO			},
+		{ "replace",	GL_REPLACE		},
+		{ "incr",		GL_INCR			},
+		{ "decr",		GL_DECR			},
+		{ "invert",		GL_INVERT		},
+		{ "incr_wrap",	GL_INCR_WRAP	},
+		{ "decr_wrap",	GL_DECR_WRAP	}
+	};
+
+	static const struct
+	{
+		rr::FaceType	visibleFace;
+		deUint32		sFail;
+		deUint32		dFail;
+		deUint32		dPass;
+		int				stencilRef;
+		deUint32		compareMask;
+		deUint32		writeMask;
+		float			depth;
+	} functionCases[] =
+	{
+		{ rr::FACETYPE_BACK,	GL_DECR,		GL_INCR,	GL_INVERT,		4,	~0u, ~0u,	-0.7f },
+		{ rr::FACETYPE_FRONT,	GL_DECR,		GL_INCR,	GL_INVERT,		2,	~0u, ~0u,	0.0f },
+		{ rr::FACETYPE_BACK,	GL_DECR,		GL_INCR,	GL_INVERT,		1,	~0u, ~0u,	0.2f },
+		{ rr::FACETYPE_FRONT,	GL_DECR_WRAP,	GL_INVERT,	GL_REPLACE,		4,	~0u, ~0u,	1.0f }
+	};
+
+	// All combinations of depth stencil functions.
+	{
+		tcu::TestCaseGroup* functionsGroup = new tcu::TestCaseGroup(m_testCtx, "stencil_depth_funcs", "Combinations of Depth and Stencil Functions");
+		addChild(functionsGroup);
+
+		for (int stencilFunc = 0; stencilFunc < DE_LENGTH_OF_ARRAY(compareFuncs)+1; stencilFunc++)
+		{
+			// One extra: depth test disabled.
+			for (int depthFunc = 0; depthFunc < DE_LENGTH_OF_ARRAY(compareFuncs)+1; depthFunc++)
+			{
+				DepthStencilParams	params;
+				ostringstream		name;
+				bool				hasStencilFunc	= de::inBounds(stencilFunc, 0, DE_LENGTH_OF_ARRAY(compareFuncs));
+				bool				hasDepthFunc	= de::inBounds(depthFunc, 0, DE_LENGTH_OF_ARRAY(compareFuncs));
+
+				if (hasStencilFunc)
+					name << "stencil_" << compareFuncs[stencilFunc].name << "_";
+				else
+					name << "no_stencil_";
+
+				if (hasDepthFunc)
+					name << "depth_" << compareFuncs[depthFunc].name;
+				else
+					name << "no_depth";
+
+				params.depthFunc			= hasDepthFunc ? compareFuncs[depthFunc].func : 0;
+				params.depthTestEnabled		= hasDepthFunc;
+				params.depthWriteMask		= true;
+
+				params.stencilTestEnabled	= hasStencilFunc;
+
+				vector<DepthStencilParams> cases;
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(functionCases); ndx++)
+				{
+					rr::FaceType	visible		= functionCases[ndx].visibleFace;
+					rr::FaceType	notVisible	= visible == rr::FACETYPE_FRONT ? rr::FACETYPE_BACK : rr::FACETYPE_FRONT;
+
+					params.depth								= functionCases[ndx].depth;
+					params.visibleFace							= visible;
+
+					params.stencil[visible].function			= hasStencilFunc ? compareFuncs[stencilFunc].func : 0;
+					params.stencil[visible].reference			= functionCases[ndx].stencilRef;
+					params.stencil[visible].stencilFailOp		= functionCases[ndx].sFail;
+					params.stencil[visible].depthFailOp			= functionCases[ndx].dFail;
+					params.stencil[visible].depthPassOp			= functionCases[ndx].dPass;
+					params.stencil[visible].compareMask			= functionCases[ndx].compareMask;
+					params.stencil[visible].writeMask			= functionCases[ndx].writeMask;
+
+					params.stencil[notVisible].function			= GL_ALWAYS;
+					params.stencil[notVisible].reference		= 0;
+					params.stencil[notVisible].stencilFailOp	= GL_REPLACE;
+					params.stencil[notVisible].depthFailOp		= GL_REPLACE;
+					params.stencil[notVisible].depthPassOp		= GL_REPLACE;
+					params.stencil[notVisible].compareMask		= 0u;
+					params.stencil[notVisible].writeMask		= ~0u;
+
+
+					cases.push_back(params);
+				}
+
+				functionsGroup->addChild(new DepthStencilCase(m_context, name.str().c_str(), "", cases));
+			}
+		}
+	}
+
+	static const struct
+	{
+		rr::FaceType	visibleFace;
+		deUint32		func;
+		int				ref;
+		deUint32		compareMask;
+		deUint32		writeMask;
+	} opCombinationCases[] =
+	{
+		{ rr::FACETYPE_BACK,	GL_LESS,		4,		~0u,	~0u	},
+		{ rr::FACETYPE_FRONT,	GL_GREATER,		2,		~0u,	~0u	},
+		{ rr::FACETYPE_BACK,	GL_EQUAL,		3,		~2u,	~0u	},
+		{ rr::FACETYPE_FRONT,	GL_NOTEQUAL,	1,		~0u,	~1u	}
+	};
+
+	// All combinations of stencil ops.
+	{
+		tcu::TestCaseGroup* opCombinationGroup = new tcu::TestCaseGroup(m_testCtx, "stencil_ops", "Stencil Op Combinations");
+		addChild(opCombinationGroup);
+
+		for (int sFail = 0; sFail < DE_LENGTH_OF_ARRAY(stencilOps); sFail++)
+		{
+			for (int dFail = 0; dFail < DE_LENGTH_OF_ARRAY(stencilOps); dFail++)
+			{
+				for (int dPass = 0; dPass < DE_LENGTH_OF_ARRAY(stencilOps); dPass++)
+				{
+					DepthStencilParams	params;
+					ostringstream		name;
+
+					name << stencilOps[sFail].name << "_" << stencilOps[dFail].name << "_" << stencilOps[dPass].name;
+
+					params.depthFunc			= GL_LEQUAL;
+					params.depth				= 0.0f;
+					params.depthTestEnabled		= true;
+					params.depthWriteMask		= true;
+
+					params.stencilTestEnabled	= true;
+
+					vector<DepthStencilParams> cases;
+					for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(opCombinationCases); ndx++)
+					{
+						rr::FaceType	visible		= opCombinationCases[ndx].visibleFace;
+						rr::FaceType	notVisible	= visible == rr::FACETYPE_FRONT ? rr::FACETYPE_BACK : rr::FACETYPE_FRONT;
+
+						params.visibleFace							= visible;
+
+						params.stencil[visible].function			= opCombinationCases[ndx].func;
+						params.stencil[visible].reference			= opCombinationCases[ndx].ref;
+						params.stencil[visible].stencilFailOp		= stencilOps[sFail].op;
+						params.stencil[visible].depthFailOp			= stencilOps[dFail].op;
+						params.stencil[visible].depthPassOp			= stencilOps[dPass].op;
+						params.stencil[visible].compareMask			= opCombinationCases[ndx].compareMask;
+						params.stencil[visible].writeMask			= opCombinationCases[ndx].writeMask;
+
+						params.stencil[notVisible].function			= GL_ALWAYS;
+						params.stencil[notVisible].reference		= 0;
+						params.stencil[notVisible].stencilFailOp	= GL_REPLACE;
+						params.stencil[notVisible].depthFailOp		= GL_REPLACE;
+						params.stencil[notVisible].depthPassOp		= GL_REPLACE;
+						params.stencil[notVisible].compareMask		= 0u;
+						params.stencil[notVisible].writeMask		= ~0u;
+
+
+						cases.push_back(params);
+					}
+
+					opCombinationGroup->addChild(new DepthStencilCase(m_context, name.str().c_str(), "", cases));
+				}
+			}
+		}
+	}
+
+	// Write masks
+	{
+		tcu::TestCaseGroup* writeMaskGroup = new tcu::TestCaseGroup(m_testCtx, "write_mask", "Depth and Stencil Write Masks");
+		addChild(writeMaskGroup);
+
+		// Depth mask
+		{
+			DepthStencilParams params;
+
+			params.depthFunc			= GL_LEQUAL;
+			params.depth				= 0.0f;
+			params.depthTestEnabled		= true;
+			params.stencilTestEnabled	= true;
+
+			params.stencil[rr::FACETYPE_FRONT].function			= GL_NOTEQUAL;
+			params.stencil[rr::FACETYPE_FRONT].reference		= 1;
+			params.stencil[rr::FACETYPE_FRONT].stencilFailOp	= GL_INVERT;
+			params.stencil[rr::FACETYPE_FRONT].depthFailOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_FRONT].depthPassOp		= GL_DECR;
+			params.stencil[rr::FACETYPE_FRONT].compareMask		= ~0u;
+			params.stencil[rr::FACETYPE_FRONT].writeMask		= ~0u;
+
+			params.stencil[rr::FACETYPE_BACK].function			= GL_ALWAYS;
+			params.stencil[rr::FACETYPE_BACK].reference			= 0;
+			params.stencil[rr::FACETYPE_BACK].stencilFailOp		= GL_REPLACE;
+			params.stencil[rr::FACETYPE_BACK].depthFailOp		= GL_INVERT;
+			params.stencil[rr::FACETYPE_BACK].depthPassOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_BACK].compareMask		= ~0u;
+			params.stencil[rr::FACETYPE_BACK].writeMask		= ~0u;
+
+			vector<DepthStencilParams> cases;
+
+			// Case 1: front, depth write enabled
+			params.visibleFace		= rr::FACETYPE_FRONT;
+			params.depthWriteMask	= true;
+			cases.push_back(params);
+
+			// Case 2: front, depth write disabled
+			params.visibleFace		= rr::FACETYPE_FRONT;
+			params.depthWriteMask	= false;
+			cases.push_back(params);
+
+			// Case 3: back, depth write enabled
+			params.visibleFace		= rr::FACETYPE_BACK;
+			params.depthWriteMask	= true;
+			cases.push_back(params);
+
+			// Case 4: back, depth write disabled
+			params.visibleFace		= rr::FACETYPE_BACK;
+			params.depthWriteMask	= false;
+			cases.push_back(params);
+
+			writeMaskGroup->addChild(new DepthStencilCase(m_context, "depth", "Depth Write Mask", cases));
+		}
+
+		// Stencil write masks.
+		{
+			static const struct
+			{
+				rr::FaceType	visibleFace;
+				deUint32		frontWriteMask;
+				deUint32		backWriteMask;
+			} stencilWmaskCases[] =
+			{
+				{ rr::FACETYPE_FRONT,	~0u,	0u		},
+				{ rr::FACETYPE_FRONT,	0u,		~0u		},
+				{ rr::FACETYPE_FRONT,	0xfu,	0xf0u	},
+				{ rr::FACETYPE_FRONT,	0x2u,	0x4u	},
+				{ rr::FACETYPE_BACK,	0u,		~0u		},
+				{ rr::FACETYPE_BACK,	~0u,	0u		},
+				{ rr::FACETYPE_BACK,	0xf0u,	0xfu	},
+				{ rr::FACETYPE_BACK,	0x4u,	0x2u	}
+			};
+
+			DepthStencilParams params;
+
+			params.depthFunc			= GL_LEQUAL;
+			params.depth				= 0.0f;
+			params.depthTestEnabled		= true;
+			params.depthWriteMask		= true;
+			params.stencilTestEnabled	= true;
+
+			params.stencil[rr::FACETYPE_FRONT].function			= GL_NOTEQUAL;
+			params.stencil[rr::FACETYPE_FRONT].reference		= 1;
+			params.stencil[rr::FACETYPE_FRONT].stencilFailOp	= GL_INVERT;
+			params.stencil[rr::FACETYPE_FRONT].depthFailOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_FRONT].depthPassOp		= GL_DECR;
+			params.stencil[rr::FACETYPE_FRONT].compareMask		= ~0u;
+
+			params.stencil[rr::FACETYPE_BACK].function			= GL_ALWAYS;
+			params.stencil[rr::FACETYPE_BACK].reference			= 0;
+			params.stencil[rr::FACETYPE_BACK].stencilFailOp		= GL_REPLACE;
+			params.stencil[rr::FACETYPE_BACK].depthFailOp		= GL_INVERT;
+			params.stencil[rr::FACETYPE_BACK].depthPassOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_BACK].compareMask		= ~0u;
+
+			vector<DepthStencilParams> cases;
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(stencilWmaskCases); ndx++)
+			{
+				params.visibleFace								= stencilWmaskCases[ndx].visibleFace;
+				params.stencil[rr::FACETYPE_FRONT].writeMask	= stencilWmaskCases[ndx].frontWriteMask;
+				params.stencil[rr::FACETYPE_BACK].writeMask	= stencilWmaskCases[ndx].backWriteMask;
+				cases.push_back(params);
+			}
+
+			writeMaskGroup->addChild(new DepthStencilCase(m_context, "stencil", "Stencil Write Mask", cases));
+		}
+
+		// Depth & stencil write masks.
+		{
+			static const struct
+			{
+				bool			depthWriteMask;
+				rr::FaceType	visibleFace;
+				deUint32		frontWriteMask;
+				deUint32		backWriteMask;
+			} depthStencilWmaskCases[] =
+			{
+				{ false,	rr::FACETYPE_FRONT,		~0u,	0u		},
+				{ false,	rr::FACETYPE_FRONT,		0u,		~0u		},
+				{ false,	rr::FACETYPE_FRONT,		0xfu,	0xf0u	},
+				{ true,		rr::FACETYPE_FRONT,		~0u,	0u		},
+				{ true,		rr::FACETYPE_FRONT,		0u,		~0u		},
+				{ true,		rr::FACETYPE_FRONT,		0xfu,	0xf0u	},
+				{ false,	rr::FACETYPE_BACK,		0u,		~0u		},
+				{ false,	rr::FACETYPE_BACK,		~0u,	0u		},
+				{ false,	rr::FACETYPE_BACK,		0xf0u,	0xfu	},
+				{ true,		rr::FACETYPE_BACK,		0u,		~0u		},
+				{ true,		rr::FACETYPE_BACK,		~0u,	0u		},
+				{ true,		rr::FACETYPE_BACK,		0xf0u,	0xfu	}
+			};
+
+			DepthStencilParams params;
+
+			params.depthFunc			= GL_LEQUAL;
+			params.depth				= 0.0f;
+			params.depthTestEnabled		= true;
+			params.depthWriteMask		= true;
+			params.stencilTestEnabled	= true;
+
+			params.stencil[rr::FACETYPE_FRONT].function			= GL_NOTEQUAL;
+			params.stencil[rr::FACETYPE_FRONT].reference		= 1;
+			params.stencil[rr::FACETYPE_FRONT].stencilFailOp	= GL_INVERT;
+			params.stencil[rr::FACETYPE_FRONT].depthFailOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_FRONT].depthPassOp		= GL_DECR;
+			params.stencil[rr::FACETYPE_FRONT].compareMask		= ~0u;
+
+			params.stencil[rr::FACETYPE_BACK].function			= GL_ALWAYS;
+			params.stencil[rr::FACETYPE_BACK].reference			= 0;
+			params.stencil[rr::FACETYPE_BACK].stencilFailOp		= GL_REPLACE;
+			params.stencil[rr::FACETYPE_BACK].depthFailOp		= GL_INVERT;
+			params.stencil[rr::FACETYPE_BACK].depthPassOp		= GL_INCR;
+			params.stencil[rr::FACETYPE_BACK].compareMask		= ~0u;
+
+			vector<DepthStencilParams> cases;
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilWmaskCases); ndx++)
+			{
+				params.depthWriteMask							= depthStencilWmaskCases[ndx].depthWriteMask;
+				params.visibleFace								= depthStencilWmaskCases[ndx].visibleFace;
+				params.stencil[rr::FACETYPE_FRONT].writeMask	= depthStencilWmaskCases[ndx].frontWriteMask;
+				params.stencil[rr::FACETYPE_BACK].writeMask		= depthStencilWmaskCases[ndx].backWriteMask;
+				cases.push_back(params);
+			}
+
+			writeMaskGroup->addChild(new DepthStencilCase(m_context, "both", "Depth and Stencil Write Masks", cases));
+		}
+	}
+
+	// Randomized cases
+	{
+		tcu::TestCaseGroup* randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Randomized Depth and Stencil Test Cases");
+		addChild(randomGroup);
+
+		for (int caseNdx = 0; caseNdx < NUM_RANDOM_CASES; caseNdx++)
+		{
+			vector<DepthStencilParams>	subCases	(NUM_RANDOM_SUB_CASES);
+			de::Random					rnd			(deInt32Hash(caseNdx) ^ deInt32Hash(m_testCtx.getCommandLine().getBaseSeed()));
+
+			for (vector<DepthStencilParams>::iterator iter = subCases.begin(); iter != subCases.end(); ++iter)
+				randomDepthStencilState(rnd, *iter);
+
+			randomGroup->addChild(new DepthStencilCase(m_context, de::toString(caseNdx).c_str(), "", subCases));
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fDepthStencilTests.hpp b/modules/gles3/functional/es3fDepthStencilTests.hpp
new file mode 100644
index 0000000..9be9752
--- /dev/null
+++ b/modules/gles3/functional/es3fDepthStencilTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FDEPTHSTENCILTESTS_HPP
+#define _ES3FDEPTHSTENCILTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth & stencil tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class DepthStencilTests : public TestCaseGroup
+{
+public:
+						DepthStencilTests	(Context& context);
+						~DepthStencilTests	(void);
+
+	void				init				(void);
+
+private:
+						DepthStencilTests	(const DepthStencilTests& other);
+	DepthStencilTests&	operator=			(const DepthStencilTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FDEPTHSTENCILTESTS_HPP
diff --git a/modules/gles3/functional/es3fDepthTests.cpp b/modules/gles3/functional/es3fDepthTests.cpp
new file mode 100644
index 0000000..4d704cd
--- /dev/null
+++ b/modules/gles3/functional/es3fDepthTests.cpp
@@ -0,0 +1,280 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fDepthTests.hpp"
+
+#include "tcuTestLog.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluStrUtil.hpp"
+
+#include "sglrContextUtil.hpp"
+#include "sglrReferenceContext.hpp"
+#include "sglrGLContext.hpp"
+
+#include "deRandom.hpp"
+
+#include "glwEnums.hpp"
+
+using tcu::RGBA;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class DepthShader : public sglr::ShaderProgram
+{
+public:
+								DepthShader		(void);
+
+	void						setColor		(sglr::Context& ctx, deUint32 programID, const tcu::Vec4& color);
+
+private:
+	void						shadeVertices	(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void						shadeFragments	(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+	const sglr::UniformSlot&	u_color;
+};
+
+DepthShader::DepthShader (void)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::VertexSource("#version 300 es\n"
+														"in highp vec4 a_position;\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = a_position;\n"
+														"}\n")
+							<< sglr::pdec::FragmentSource("#version 300 es\n"
+														  "uniform highp vec4 u_color;\n"
+														  "layout(location = 0) out mediump vec4 o_color;\n"
+														  "void main (void)\n"
+														  "{\n"
+														  "	o_color = u_color;\n"
+														  "}\n"))
+	, u_color(getUniformByName("u_color"))
+{
+}
+
+void DepthShader::setColor (sglr::Context& ctx, deUint32 programID, const tcu::Vec4& color)
+{
+	ctx.useProgram(programID);
+	ctx.uniform4fv(ctx.getUniformLocation(programID, "u_color"), 1, color.getPtr());
+}
+
+void DepthShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		packets[packetNdx]->position = rr::readVertexAttribFloat(inputs[0], packets[packetNdx]->instanceNdx, packets[packetNdx]->vertexNdx);
+}
+
+void DepthShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4 color(u_color.value.f4);
+
+	DE_UNREF(packets);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+}
+
+// \todo [2011-07-11 pyry] This code is duplicated in a quite many places. Move it to some utility?
+class DepthCase : public TestCase
+{
+public:
+								DepthCase				(Context& context, const char* name, const char* description);
+	virtual						~DepthCase				(void) {}
+
+	virtual IterateResult		iterate					(void);
+	virtual void				render					(sglr::Context& context) = DE_NULL;
+};
+
+DepthCase::DepthCase (Context& context, const char* name, const char* description)
+	: TestCase(context, name, description)
+{
+}
+
+TestCase::IterateResult DepthCase::iterate (void)
+{
+	tcu::Vec4					clearColor				= tcu::Vec4(0.125f, 0.25f, 0.5f, 1.0f);
+	glu::RenderContext&			renderCtx				= m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget			= renderCtx.getRenderTarget();
+	tcu::TestLog&				log						= m_testCtx.getLog();
+	const char*					failReason				= DE_NULL;
+
+	// Position & size for context
+	de::Random rnd(deStringHash(getName()));
+
+	int		width	= deMin32(renderTarget.getWidth(),	128);
+	int		height	= deMin32(renderTarget.getHeight(),	128);
+	int		x		= rnd.getInt(0, renderTarget.getWidth()		- width);
+	int		y		= rnd.getInt(0, renderTarget.getHeight()	- height);
+
+	tcu::Surface	gles2Frame	(width, height);
+	tcu::Surface	refFrame	(width, height);
+	deUint32		gles2Error;
+	deUint32		refError;
+
+	// Render using GLES2
+	{
+		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(x, y, width, height));
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		render(context); // Call actual render func
+		context.readPixels(gles2Frame, 0, 0, width, height);
+		gles2Error = context.getError();
+	}
+
+	// Render reference image
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), renderTarget.getDepthBits(), renderTarget.getStencilBits(), width, height);
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		render(context);
+		context.readPixels(refFrame, 0, 0, width, height);
+		refError = context.getError();
+	}
+
+	// Compare error codes
+	bool errorCodesOk = (gles2Error == refError);
+
+	if (!errorCodesOk)
+	{
+		log << tcu::TestLog::Message << "Error code mismatch: got " << glu::getErrorStr(gles2Error) << ", expected " << glu::getErrorStr(refError) << tcu::TestLog::EndMessage;
+		failReason = "Got unexpected error";
+	}
+
+	// Compare images
+	const float		threshold	= 0.02f;
+	bool			imagesOk	= tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, gles2Frame, threshold, tcu::COMPARE_LOG_RESULT);
+
+	if (!imagesOk && !failReason)
+		failReason = "Image comparison failed";
+
+	// Store test result
+	bool isOk = errorCodesOk && imagesOk;
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: failReason);
+
+	return STOP;
+}
+
+class DepthCompareCase : public DepthCase
+{
+public:
+	DepthCompareCase (Context& context, const char* name, const char* description, deUint32 compareOp)
+		: DepthCase		(context, name, description)
+		, m_compareOp	(compareOp)
+	{
+	}
+
+	void render (sglr::Context& context)
+	{
+		using tcu::Vec3;
+
+		DepthShader	shader;
+		deUint32	shaderID = context.createProgram(&shader);
+
+		tcu::Vec4	red		(1.0f, 0.0f, 0.0f, 1.0);
+		tcu::Vec4	green	(0.0f, 1.0f, 0.0f, 1.0f);
+
+		// Clear depth to 1
+		context.clearDepthf(1.0f);
+		context.clear(GL_DEPTH_BUFFER_BIT);
+
+		// Enable depth test.
+		context.enable(GL_DEPTH_TEST);
+
+		// Upper left: two quads with same depth
+		context.depthFunc(GL_ALWAYS);
+		shader.setColor(context, shaderID, red);
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.2f),	Vec3(0.0f, 0.0f, 0.2f));
+		context.depthFunc(m_compareOp);
+		shader.setColor(context, shaderID, green);
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.2f),	Vec3(0.0f, 0.0f, 0.2f));
+
+		// Lower left: two quads, d1 < d2
+		context.depthFunc(GL_ALWAYS);
+		shader.setColor(context, shaderID, red);
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, 0.0f, -0.4f),	Vec3(0.0f, 1.0f, -0.4f));
+		context.depthFunc(m_compareOp);
+		shader.setColor(context, shaderID, green);
+		sglr::drawQuad(context, shaderID, Vec3(-1.0f, 0.0f, -0.1f),	Vec3(0.0f, 1.0f, -0.1f));
+
+		// Upper right: two quads, d1 > d2
+		context.depthFunc(GL_ALWAYS);
+		shader.setColor(context, shaderID, red);
+		sglr::drawQuad(context, shaderID, Vec3(0.0f, -1.0f, 0.5f),	Vec3(1.0f, 0.0f, 0.5f));
+		context.depthFunc(m_compareOp);
+		shader.setColor(context, shaderID, green);
+		sglr::drawQuad(context, shaderID, Vec3(0.0f, -1.0f, 0.3f),	Vec3(1.0f, 0.0f, 0.3f));
+
+		// Lower right: two quads, d1 = 0, d2 = [-1..1]
+		context.depthFunc(GL_ALWAYS);
+		shader.setColor(context, shaderID, red);
+		sglr::drawQuad(context, shaderID, Vec3(0.0f, 0.0f, 0.0f),	Vec3(1.0f, 1.0f, 0.0f));
+		context.depthFunc(m_compareOp);
+		shader.setColor(context, shaderID, green);
+		sglr::drawQuad(context, shaderID, Vec3(0.0f, 0.0f, -1.0f),	Vec3(1.0f, 1.0f, 1.0f));
+	}
+
+private:
+	deUint32	m_compareOp;
+};
+
+DepthTests::DepthTests (Context& context)
+	: TestCaseGroup(context, "depth", "Depth Tests")
+{
+}
+
+DepthTests::~DepthTests (void)
+{
+}
+
+void DepthTests::init (void)
+{
+	addChild(new DepthCompareCase(m_context, "cmp_always",				"Always pass depth test",				GL_ALWAYS));
+	addChild(new DepthCompareCase(m_context, "cmp_never",				"Never pass depth test",				GL_NEVER));
+	addChild(new DepthCompareCase(m_context, "cmp_equal",				"Depth compare: equal",					GL_EQUAL));
+	addChild(new DepthCompareCase(m_context, "cmp_not_equal",			"Depth compare: not equal",				GL_NOTEQUAL));
+	addChild(new DepthCompareCase(m_context, "cmp_less_than",			"Depth compare: less than",				GL_LESS));
+	addChild(new DepthCompareCase(m_context, "cmp_less_or_equal",		"Depth compare: less than or equal",	GL_LEQUAL));
+	addChild(new DepthCompareCase(m_context, "cmp_greater_than",		"Depth compare: greater than",			GL_GREATER));
+	addChild(new DepthCompareCase(m_context, "cmp_greater_or_equal",	"Depth compare: greater than or equal",	GL_GEQUAL));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fDepthTests.hpp b/modules/gles3/functional/es3fDepthTests.hpp
new file mode 100644
index 0000000..da4a6d8
--- /dev/null
+++ b/modules/gles3/functional/es3fDepthTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FDEPTHTESTS_HPP
+#define _ES3FDEPTHTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Depth tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class DepthTests : public TestCaseGroup
+{
+public:
+						DepthTests			(Context& context);
+						~DepthTests			(void);
+
+	void				init				(void);
+
+private:
+						DepthTests			(const DepthTests& other);
+	DepthTests&			operator=			(const DepthTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FDEPTHTESTS_HPP
diff --git a/modules/gles3/functional/es3fDitheringTests.cpp b/modules/gles3/functional/es3fDitheringTests.cpp
new file mode 100644
index 0000000..8fe1bc8
--- /dev/null
+++ b/modules/gles3/functional/es3fDitheringTests.cpp
@@ -0,0 +1,541 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Dithering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fDitheringTests.hpp"
+#include "gluRenderContext.hpp"
+#include "gluDefs.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuVector.hpp"
+#include "tcuPixelFormat.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuCommandLine.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+#include "deMath.h"
+
+#include <string>
+#include <algorithm>
+
+#include "glw.h"
+
+namespace deqp
+{
+
+using tcu::Vec4;
+using tcu::IVec4;
+using tcu::TestLog;
+using gls::FragmentOpUtil::QuadRenderer;
+using gls::FragmentOpUtil::Quad;
+using tcu::PixelFormat;
+using tcu::Surface;
+using de::Random;
+using std::vector;
+using std::string;
+
+namespace gles3
+{
+namespace Functional
+{
+
+static const char* const s_channelNames[4] = { "red", "green", "blue", "alpha" };
+
+static inline IVec4 pixelFormatToIVec4 (const PixelFormat& format)
+{
+	return IVec4(format.redBits, format.greenBits, format.blueBits, format.alphaBits);
+}
+
+template<typename T>
+static inline string choiceListStr (const vector<T>& choices)
+{
+	string result;
+	for (int i = 0; i < (int)choices.size(); i++)
+	{
+		if (i == (int)choices.size()-1)
+			result += " or ";
+		else if (i > 0)
+			result += ", ";
+		result += de::toString(choices[i]);
+	}
+	return result;
+}
+
+class DitheringCase : public tcu::TestCase
+{
+public:
+	enum PatternType
+	{
+		PATTERNTYPE_GRADIENT = 0,
+		PATTERNTYPE_UNICOLORED_QUAD,
+
+		PATTERNTYPE_LAST
+	};
+
+											DitheringCase				(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, bool isEnabled, PatternType patternType, const tcu::Vec4& color);
+											~DitheringCase				(void);
+
+	IterateResult							iterate						(void);
+	void									init						(void);
+	void									deinit						(void);
+
+	static const char*						getPatternTypeName			(PatternType type);
+
+private:
+	bool									checkColor					(const tcu::Vec4& inputClr, const tcu::RGBA& renderedClr, bool logErrors) const;
+
+	bool									drawAndCheckGradient		(bool isVerticallyIncreasing, const tcu::Vec4& highColor) const;
+	bool									drawAndCheckUnicoloredQuad	(const tcu::Vec4& color) const;
+
+	const glu::RenderContext&				m_renderCtx;
+
+	const bool								m_ditheringEnabled;
+	const PatternType						m_patternType;
+	const tcu::Vec4							m_color;
+
+	const tcu::PixelFormat					m_renderFormat;
+
+	const QuadRenderer*						m_renderer;
+	int										m_iteration;
+};
+
+const char* DitheringCase::getPatternTypeName (const PatternType type)
+{
+	switch (type)
+	{
+		case PATTERNTYPE_GRADIENT:			return "gradient";
+		case PATTERNTYPE_UNICOLORED_QUAD:	return "unicolored_quad";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+
+DitheringCase::DitheringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* const name, const char* const description, const bool ditheringEnabled, const PatternType patternType, const Vec4& color)
+	: TestCase				(testCtx, name, description)
+	, m_renderCtx			(renderCtx)
+	, m_ditheringEnabled	(ditheringEnabled)
+	, m_patternType			(patternType)
+	, m_color				(color)
+	, m_renderFormat		(renderCtx.getRenderTarget().getPixelFormat())
+	, m_renderer			(DE_NULL)
+	, m_iteration			(0)
+{
+}
+
+DitheringCase::~DitheringCase (void)
+{
+	DitheringCase::deinit();
+}
+
+void DitheringCase::init (void)
+{
+	DE_ASSERT(!m_renderer);
+	m_renderer = new QuadRenderer(m_renderCtx, glu::GLSL_VERSION_300_ES);
+	m_iteration = 0;
+}
+
+void DitheringCase::deinit (void)
+{
+	delete m_renderer;
+	m_renderer = DE_NULL;
+}
+
+bool DitheringCase::checkColor (const Vec4& inputClr, const tcu::RGBA& renderedClr, const bool logErrors) const
+{
+	const IVec4		channelBits		= pixelFormatToIVec4(m_renderFormat);
+	bool			allChannelsOk	= true;
+
+	for (int chanNdx = 0; chanNdx < 4; chanNdx++)
+	{
+		if (channelBits[chanNdx] == 0)
+			continue;
+
+		const int		channelMax			= (1 << channelBits[chanNdx]) - 1;
+		const float		scaledInput			= inputClr[chanNdx] * (float)channelMax;
+		const bool		useRoundingMargin	= deFloatAbs(scaledInput - deFloatRound(scaledInput)) < 0.0001f;
+		vector<int>		channelChoices;
+
+		channelChoices.push_back(de::min(channelMax,	(int)deFloatCeil(scaledInput)));
+		channelChoices.push_back(de::max(0,				(int)deFloatCeil(scaledInput) - 1));
+
+		// If the input color results in a scaled value that is very close to an integer, account for a little bit of possible inaccuracy.
+		if (useRoundingMargin)
+		{
+			if (scaledInput > deFloatRound(scaledInput))
+				channelChoices.push_back((int)deFloatCeil(scaledInput) - 2);
+			else
+				channelChoices.push_back((int)deFloatCeil(scaledInput) + 1);
+		}
+
+		std::sort(channelChoices.begin(), channelChoices.end());
+
+		{
+			const int		renderedClrInFormat	= (int)deFloatRound((float)(renderedClr.toIVec()[chanNdx] * channelMax) / 255.0f);
+			bool			goodChannel			= false;
+
+			for (int i = 0; i < (int)channelChoices.size(); i++)
+			{
+				if (renderedClrInFormat == channelChoices[i])
+				{
+					goodChannel = true;
+					break;
+				}
+			}
+
+			if (!goodChannel)
+			{
+				if (logErrors)
+				{
+					m_testCtx.getLog() << TestLog::Message
+									   << "Failure: " << channelBits[chanNdx] << "-bit " << s_channelNames[chanNdx] << " channel is " << renderedClrInFormat
+									   << ", should be " << choiceListStr(channelChoices)
+									   << " (corresponding fragment color channel is " << inputClr[chanNdx] << ")"
+									   << TestLog::EndMessage
+									   << TestLog::Message
+									   << "Note: " << inputClr[chanNdx] << " * (" << channelMax + 1 << "-1) = " << scaledInput
+									   << TestLog::EndMessage;
+
+					if (useRoundingMargin)
+					{
+						m_testCtx.getLog() << TestLog::Message
+										   << "Note: one extra color candidate was allowed because fragmentColorChannel * (2^bits-1) is close to an integer"
+										   << TestLog::EndMessage;
+					}
+				}
+
+				allChannelsOk = false;
+			}
+		}
+	}
+
+	return allChannelsOk;
+}
+
+bool DitheringCase::drawAndCheckGradient (const bool isVerticallyIncreasing, const Vec4& highColor) const
+{
+	TestLog&					log					= m_testCtx.getLog();
+	Random						rnd					(deStringHash(getName()));
+	const int					maxViewportWid		= 256;
+	const int					maxViewportHei		= 256;
+	const int					viewportWid			= de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid);
+	const int					viewportHei			= de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei);
+	const int					viewportX			= rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid);
+	const int					viewportY			= rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei);
+	const Vec4					quadClr0			(0.0f, 0.0f, 0.0f, 0.0f);
+	const Vec4&					quadClr1			= highColor;
+	Quad						quad;
+	Surface						renderedImg			(viewportWid, viewportHei);
+
+	GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei));
+
+	log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
+
+	if (m_ditheringEnabled)
+		GLU_CHECK_CALL(glEnable(GL_DITHER));
+	else
+		GLU_CHECK_CALL(glDisable(GL_DITHER));
+
+	log << TestLog::Message << "Drawing a " << (isVerticallyIncreasing ? "vertically" : "horizontally") << " increasing gradient" << TestLog::EndMessage;
+
+	quad.color[0] = quadClr0;
+	quad.color[1] = isVerticallyIncreasing ? quadClr1 : quadClr0;
+	quad.color[2] = isVerticallyIncreasing ? quadClr0 : quadClr1;
+	quad.color[3] = quadClr1;
+
+	m_renderer->render(quad);
+
+	glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess());
+	GLU_CHECK_MSG("glReadPixels()");
+
+	log << TestLog::Image(isVerticallyIncreasing ? "VerGradient"		: "HorGradient",
+						  isVerticallyIncreasing ? "Vertical gradient"	: "Horizontal gradient",
+						  renderedImg);
+
+	// Validate, at each pixel, that each color channel is one of its two allowed values.
+
+	{
+		Surface		errorMask		(viewportWid, viewportHei);
+		bool		colorChoicesOk	= true;
+
+		for (int y = 0; y < renderedImg.getHeight(); y++)
+		{
+			for (int x = 0; x < renderedImg.getWidth(); x++)
+			{
+				const float		inputF		= ((float)(isVerticallyIncreasing ? y : x) + 0.5f) / (float)(isVerticallyIncreasing ? renderedImg.getHeight() : renderedImg.getWidth());
+				const Vec4		inputClr	= (1.0f-inputF)*quadClr0 + inputF*quadClr1;
+
+				if (!checkColor(inputClr, renderedImg.getPixel(x, y), colorChoicesOk))
+				{
+					errorMask.setPixel(x, y, tcu::RGBA::red);
+
+					if (colorChoicesOk)
+					{
+						log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage;
+						colorChoicesOk = false;
+					}
+				}
+				else
+					errorMask.setPixel(x, y, tcu::RGBA::green);
+			}
+		}
+
+		if (!colorChoicesOk)
+		{
+			log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask);
+			return false;
+		}
+	}
+
+	// When dithering is disabled, the color selection must be coordinate-independent - i.e. the colors must be constant in the gradient's constant direction.
+
+	if (!m_ditheringEnabled)
+	{
+		const int increasingDirectionSize	= isVerticallyIncreasing ? renderedImg.getHeight() : renderedImg.getWidth();
+		const int constantDirectionSize		= isVerticallyIncreasing ? renderedImg.getWidth() : renderedImg.getHeight();
+
+		for (int incrPos = 0; incrPos < increasingDirectionSize; incrPos++)
+		{
+			tcu::RGBA prevConstantDirectionPix;
+			for (int constPos = 0; constPos < constantDirectionSize; constPos++)
+			{
+				const int			x		= isVerticallyIncreasing ? constPos : incrPos;
+				const int			y		= isVerticallyIncreasing ? incrPos : constPos;
+				const tcu::RGBA		clr		= renderedImg.getPixel(x, y);
+
+				if (constPos > 0 && clr != prevConstantDirectionPix)
+				{
+					log << TestLog::Message
+						<< "Failure: colors should be constant per " << (isVerticallyIncreasing ? "row" : "column")
+						<< " (since dithering is disabled), but the color at position (" << x << ", " << y << ") is " << clr
+						<< " and does not equal the color at (" << (isVerticallyIncreasing ? x-1 : x) << ", " << (isVerticallyIncreasing ? y : y-1) << "), which is " << prevConstantDirectionPix
+						<< TestLog::EndMessage;
+
+					return false;
+				}
+
+				prevConstantDirectionPix = clr;
+			}
+		}
+	}
+
+	return true;
+}
+
+bool DitheringCase::drawAndCheckUnicoloredQuad (const Vec4& quadColor) const
+{
+	TestLog&					log					= m_testCtx.getLog();
+	Random						rnd					(deStringHash(getName()));
+	const int					maxViewportWid		= 32;
+	const int					maxViewportHei		= 32;
+	const int					viewportWid			= de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid);
+	const int					viewportHei			= de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei);
+	const int					viewportX			= rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid);
+	const int					viewportY			= rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei);
+	Quad						quad;
+	Surface						renderedImg			(viewportWid, viewportHei);
+
+	GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei));
+
+	log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
+
+	if (m_ditheringEnabled)
+		GLU_CHECK_CALL(glEnable(GL_DITHER));
+	else
+		GLU_CHECK_CALL(glDisable(GL_DITHER));
+
+	log << TestLog::Message << "Drawing an unicolored quad with color " << quadColor << TestLog::EndMessage;
+
+	quad.color[0] = quadColor;
+	quad.color[1] = quadColor;
+	quad.color[2] = quadColor;
+	quad.color[3] = quadColor;
+
+	m_renderer->render(quad);
+
+	glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess());
+	GLU_CHECK_MSG("glReadPixels()");
+
+	log << TestLog::Image(("Quad" + de::toString(m_iteration)).c_str(), ("Quad " + de::toString(m_iteration)).c_str(), renderedImg);
+
+	// Validate, at each pixel, that each color channel is one of its two allowed values.
+
+	{
+		Surface		errorMask		(viewportWid, viewportHei);
+		bool		colorChoicesOk	= true;
+
+		for (int y = 0; y < renderedImg.getHeight(); y++)
+		{
+			for (int x = 0; x < renderedImg.getWidth(); x++)
+			{
+				if (!checkColor(quadColor, renderedImg.getPixel(x, y), colorChoicesOk))
+				{
+					errorMask.setPixel(x, y, tcu::RGBA::red);
+
+					if (colorChoicesOk)
+					{
+						log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage;
+						colorChoicesOk = false;
+					}
+				}
+				else
+					errorMask.setPixel(x, y, tcu::RGBA::green);
+			}
+		}
+
+		if (!colorChoicesOk)
+		{
+			log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask);
+			return false;
+		}
+	}
+
+	// When dithering is disabled, the color selection must be coordinate-independent - i.e. the entire rendered image must be unicolored.
+
+	if (!m_ditheringEnabled)
+	{
+		const tcu::RGBA renderedClr00 = renderedImg.getPixel(0, 0);
+
+		for (int y = 0; y < renderedImg.getHeight(); y++)
+		{
+			for (int x = 0; x < renderedImg.getWidth(); x++)
+			{
+				const tcu::RGBA curClr = renderedImg.getPixel(x, y);
+
+				if (curClr != renderedClr00)
+				{
+					log << TestLog::Message
+						<< "Failure: color at (" << x << ", " << y << ") is " << curClr
+						<< " and does not equal the color at (0, 0), which is " << renderedClr00
+						<< TestLog::EndMessage;
+
+					return false;
+				}
+			}
+		}
+	}
+
+	return true;
+}
+
+DitheringCase::IterateResult DitheringCase::iterate (void)
+{
+	if (m_patternType == PATTERNTYPE_GRADIENT)
+	{
+		// Draw horizontal and vertical gradients.
+
+		DE_ASSERT(m_iteration < 2);
+
+		const bool success = drawAndCheckGradient(m_iteration == 1, m_color);
+
+		if (!success)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		if (m_iteration == 1)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+			return STOP;
+		}
+	}
+	else if (m_patternType == PATTERNTYPE_UNICOLORED_QUAD)
+	{
+		const int numQuads = m_testCtx.getCommandLine().getTestIterationCount() > 0 ? m_testCtx.getCommandLine().getTestIterationCount() : 30;
+
+		DE_ASSERT(m_iteration < numQuads);
+
+		const Vec4 quadColor	= (float)m_iteration / (float)(numQuads-1) * m_color;
+		const bool success		=  drawAndCheckUnicoloredQuad(quadColor);
+
+		if (!success)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+			return STOP;
+		}
+
+		if (m_iteration == numQuads - 1)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+			return STOP;
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+	m_iteration++;
+
+	return CONTINUE;
+}
+
+DitheringTests::DitheringTests (Context& context)
+	: TestCaseGroup(context, "dither", "Dithering tests")
+{
+}
+
+DitheringTests::~DitheringTests (void)
+{
+}
+
+void DitheringTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		Vec4			color;
+	} caseColors[] =
+	{
+		{ "white",		Vec4(1.0f, 1.0f, 1.0f, 1.0f) },
+		{ "red",		Vec4(1.0f, 0.0f, 0.0f, 1.0f) },
+		{ "green",		Vec4(0.0f, 1.0f, 0.0f, 1.0f) },
+		{ "blue",		Vec4(0.0f, 0.0f, 1.0f, 1.0f) },
+		{ "alpha",		Vec4(0.0f, 0.0f, 0.0f, 1.0f) }
+	};
+
+	for (int ditheringEnabledI = 0; ditheringEnabledI <= 1; ditheringEnabledI++)
+	{
+		const bool				ditheringEnabled	= ditheringEnabledI != 0;
+		TestCaseGroup* const	group				= new TestCaseGroup(m_context, ditheringEnabled ? "enabled" : "disabled", "");
+		addChild(group);
+
+		for (int patternTypeI = 0; patternTypeI < DitheringCase::PATTERNTYPE_LAST; patternTypeI++)
+		{
+			for (int caseColorNdx = 0; caseColorNdx < DE_LENGTH_OF_ARRAY(caseColors); caseColorNdx++)
+			{
+				const DitheringCase::PatternType	patternType		= (DitheringCase::PatternType)patternTypeI;
+				const string						caseName		= string("") + DitheringCase::getPatternTypeName(patternType) + "_" + caseColors[caseColorNdx].name;
+
+				group->addChild(new DitheringCase(m_context.getTestContext(), m_context.getRenderContext(), caseName.c_str(), "", ditheringEnabled, patternType, caseColors[caseColorNdx].color));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fDitheringTests.hpp b/modules/gles3/functional/es3fDitheringTests.hpp
new file mode 100644
index 0000000..5f1ce2d
--- /dev/null
+++ b/modules/gles3/functional/es3fDitheringTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FDITHERINGTESTS_HPP
+#define _ES3FDITHERINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Dithering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class DitheringTests : public TestCaseGroup
+{
+public:
+						DitheringTests		(Context& context);
+						~DitheringTests		(void);
+
+	void				init				(void);
+
+private:
+						DitheringTests		(const DitheringTests& other);
+	DitheringTests&		operator=			(const DitheringTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FDITHERINGTESTS_HPP
diff --git a/modules/gles3/functional/es3fDrawTests.cpp b/modules/gles3/functional/es3fDrawTests.cpp
new file mode 100644
index 0000000..ff9fbfc
--- /dev/null
+++ b/modules/gles3/functional/es3fDrawTests.cpp
@@ -0,0 +1,1167 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Drawing tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fDrawTests.hpp"
+#include "glsDrawTest.hpp"
+#include "tcuRenderTarget.hpp"
+#include "sglrGLContext.hpp"
+#include "glwEnums.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+#include "deSTLUtil.hpp"
+
+#include <set>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+enum TestIterationType
+{
+	TYPE_DRAW_COUNT,		// !< test with 1, 5, and 25 primitives
+	TYPE_INSTANCE_COUNT,	// !< test with 1, 4, and 11 instances
+	TYPE_INDEX_RANGE,		// !< test with index range of [0, 23], [23, 40], and [5, 5]
+
+	TYPE_LAST
+};
+
+static void addTestIterations (gls::DrawTest* test, const gls::DrawTestSpec& baseSpec, TestIterationType type)
+{
+	gls::DrawTestSpec spec(baseSpec);
+
+	if (type == TYPE_DRAW_COUNT)
+	{
+		spec.primitiveCount = 1;
+		test->addIteration(spec, "draw count = 1");
+
+		spec.primitiveCount = 5;
+		test->addIteration(spec, "draw count = 5");
+
+		spec.primitiveCount = 25;
+		test->addIteration(spec, "draw count = 25");
+	}
+	else if (type == TYPE_INSTANCE_COUNT)
+	{
+		spec.instanceCount = 1;
+		test->addIteration(spec, "instance count = 1");
+
+		spec.instanceCount = 4;
+		test->addIteration(spec, "instance count = 4");
+
+		spec.instanceCount = 11;
+		test->addIteration(spec, "instance count = 11");
+	}
+	else if (type == TYPE_INDEX_RANGE)
+	{
+		spec.indexMin = 0;
+		spec.indexMax = 23;
+		test->addIteration(spec, "index range = [0, 23]");
+
+		spec.indexMin = 23;
+		spec.indexMax = 40;
+		test->addIteration(spec, "index range = [23, 40]");
+
+		// Only makes sense with points
+		if (spec.primitive == gls::DrawTestSpec::PRIMITIVE_POINTS)
+		{
+			spec.indexMin = 5;
+			spec.indexMax = 5;
+			test->addIteration(spec, "index range = [5, 5]");
+		}
+	}
+	else
+		DE_ASSERT(false);
+}
+
+static void genBasicSpec (gls::DrawTestSpec& spec, gls::DrawTestSpec::DrawMethod method)
+{
+	spec.apiType				= glu::ApiType::es(3,0);
+	spec.primitive				= gls::DrawTestSpec::PRIMITIVE_TRIANGLES;
+	spec.primitiveCount			= 5;
+	spec.drawMethod				= method;
+	spec.indexType				= gls::DrawTestSpec::INDEXTYPE_LAST;
+	spec.indexPointerOffset		= 0;
+	spec.indexStorage			= gls::DrawTestSpec::STORAGE_LAST;
+	spec.first					= 0;
+	spec.indexMin				= 0;
+	spec.indexMax				= 0;
+	spec.instanceCount			= 1;
+
+	spec.attribs.resize(2);
+
+	spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+	spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+	spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+	spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+	spec.attribs[0].componentCount			= 4;
+	spec.attribs[0].offset					= 0;
+	spec.attribs[0].stride					= 0;
+	spec.attribs[0].normalize				= false;
+	spec.attribs[0].instanceDivisor			= 0;
+	spec.attribs[0].useDefaultAttribute		= false;
+
+	spec.attribs[1].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+	spec.attribs[1].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+	spec.attribs[1].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+	spec.attribs[1].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+	spec.attribs[1].componentCount			= 2;
+	spec.attribs[1].offset					= 0;
+	spec.attribs[1].stride					= 0;
+	spec.attribs[1].normalize				= false;
+	spec.attribs[1].instanceDivisor			= 0;
+	spec.attribs[1].useDefaultAttribute		= false;
+}
+
+class AttributeGroup : public TestCaseGroup
+{
+public:
+									AttributeGroup	(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod, gls::DrawTestSpec::Primitive primitive, gls::DrawTestSpec::IndexType indexType, gls::DrawTestSpec::Storage indexStorage);
+									~AttributeGroup	(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+	gls::DrawTestSpec::Primitive	m_primitive;
+	gls::DrawTestSpec::IndexType	m_indexType;
+	gls::DrawTestSpec::Storage		m_indexStorage;
+};
+
+AttributeGroup::AttributeGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod, gls::DrawTestSpec::Primitive primitive, gls::DrawTestSpec::IndexType indexType, gls::DrawTestSpec::Storage indexStorage)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+	, m_primitive		(primitive)
+	, m_indexType		(indexType)
+	, m_indexStorage	(indexStorage)
+{
+}
+
+AttributeGroup::~AttributeGroup (void)
+{
+}
+
+void AttributeGroup::init (void)
+{
+	// select test type
+	const bool				instanced			= (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INSTANCED) ||
+												  (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED);
+	const bool				ranged				= (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED);
+	const TestIterationType	testType			= (instanced) ? (TYPE_INSTANCE_COUNT) : ((ranged) ? (TYPE_INDEX_RANGE) : (TYPE_DRAW_COUNT));
+
+	// Single attribute
+	{
+		gls::DrawTest*		test				= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "single_attribute", "Single attribute array.");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType							= glu::ApiType::es(3,0);
+		spec.primitive							= m_primitive;
+		spec.primitiveCount						= 5;
+		spec.drawMethod							= m_method;
+		spec.indexType							= m_indexType;
+		spec.indexPointerOffset					= 0;
+		spec.indexStorage						= m_indexStorage;
+		spec.first								= 0;
+		spec.indexMin							= 0;
+		spec.indexMax							= 0;
+		spec.instanceCount						= 1;
+
+		spec.attribs.resize(1);
+
+		spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount			= 2;
+		spec.attribs[0].offset					= 0;
+		spec.attribs[0].stride					= 0;
+		spec.attribs[0].normalize				= false;
+		spec.attribs[0].instanceDivisor			= 0;
+		spec.attribs[0].useDefaultAttribute		= false;
+
+		addTestIterations(test, spec, testType);
+
+		this->addChild(test);
+	}
+
+	// Multiple attribute
+	{
+		gls::DrawTest*		test				= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "multiple_attributes", "Multiple attribute arrays.");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType							= glu::ApiType::es(3,0);
+		spec.primitive							= m_primitive;
+		spec.primitiveCount						= 5;
+		spec.drawMethod							= m_method;
+		spec.indexType							= m_indexType;
+		spec.indexPointerOffset					= 0;
+		spec.indexStorage						= m_indexStorage;
+		spec.first								= 0;
+		spec.indexMin							= 0;
+		spec.indexMax							= 0;
+		spec.instanceCount						= 1;
+
+		spec.attribs.resize(2);
+
+		spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount			= 4;
+		spec.attribs[0].offset					= 0;
+		spec.attribs[0].stride					= 0;
+		spec.attribs[0].normalize				= false;
+		spec.attribs[0].instanceDivisor			= 0;
+		spec.attribs[0].useDefaultAttribute		= false;
+
+		spec.attribs[1].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[1].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[1].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[1].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[1].componentCount			= 2;
+		spec.attribs[1].offset					= 0;
+		spec.attribs[1].stride					= 0;
+		spec.attribs[1].normalize				= false;
+		spec.attribs[1].instanceDivisor			= 0;
+		spec.attribs[1].useDefaultAttribute		= false;
+
+		addTestIterations(test, spec, testType);
+
+		this->addChild(test);
+	}
+
+	// Multiple attribute, second one divided
+	{
+		gls::DrawTest*		test					= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "instanced_attributes", "Instanced attribute array.");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType								= glu::ApiType::es(3,0);
+		spec.primitive								= m_primitive;
+		spec.primitiveCount							= 5;
+		spec.drawMethod								= m_method;
+		spec.indexType								= m_indexType;
+		spec.indexPointerOffset						= 0;
+		spec.indexStorage							= m_indexStorage;
+		spec.first									= 0;
+		spec.indexMin								= 0;
+		spec.indexMax								= 0;
+		spec.instanceCount							= 1;
+
+		spec.attribs.resize(3);
+
+		spec.attribs[0].inputType					= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType					= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage						= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage						= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount				= 4;
+		spec.attribs[0].offset						= 0;
+		spec.attribs[0].stride						= 0;
+		spec.attribs[0].normalize					= false;
+		spec.attribs[0].instanceDivisor				= 0;
+		spec.attribs[0].useDefaultAttribute			= false;
+
+		// Add another position component so the instances wont be drawn on each other
+		spec.attribs[1].inputType					= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[1].outputType					= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[1].storage						= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[1].usage						= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[1].componentCount				= 2;
+		spec.attribs[1].offset						= 0;
+		spec.attribs[1].stride						= 0;
+		spec.attribs[1].normalize					= false;
+		spec.attribs[1].instanceDivisor				= 1;
+		spec.attribs[1].useDefaultAttribute			= false;
+		spec.attribs[1].additionalPositionAttribute	= true;
+
+		// Instanced color
+		spec.attribs[2].inputType					= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[2].outputType					= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[2].storage						= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[2].usage						= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[2].componentCount				= 3;
+		spec.attribs[2].offset						= 0;
+		spec.attribs[2].stride						= 0;
+		spec.attribs[2].normalize					= false;
+		spec.attribs[2].instanceDivisor				= 1;
+		spec.attribs[2].useDefaultAttribute			= false;
+
+		addTestIterations(test, spec, testType);
+
+		this->addChild(test);
+	}
+
+	// Multiple attribute, second one default
+	{
+		gls::DrawTest*		test				= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "default_attribute", "Attribute specified with glVertexAttrib*.");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType							= glu::ApiType::es(3,0);
+		spec.primitive							= m_primitive;
+		spec.primitiveCount						= 5;
+		spec.drawMethod							= m_method;
+		spec.indexType							= m_indexType;
+		spec.indexPointerOffset					= 0;
+		spec.indexStorage						= m_indexStorage;
+		spec.first								= 0;
+		spec.indexMin							= 0;
+		spec.indexMax							= 20; // \note addTestIterations is not called for the spec, so we must ensure [indexMin, indexMax] is a good range
+		spec.instanceCount						= 1;
+
+		spec.attribs.resize(2);
+
+		spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount			= 2;
+		spec.attribs[0].offset					= 0;
+		spec.attribs[0].stride					= 0;
+		spec.attribs[0].normalize				= false;
+		spec.attribs[0].instanceDivisor			= 0;
+		spec.attribs[0].useDefaultAttribute		= false;
+
+		struct IOPair
+		{
+			gls::DrawTestSpec::InputType  input;
+			gls::DrawTestSpec::OutputType output;
+			int							  componentCount;
+		} iopairs[] =
+		{
+			{ gls::DrawTestSpec::INPUTTYPE_FLOAT,        gls::DrawTestSpec::OUTPUTTYPE_VEC2,  4 },
+			{ gls::DrawTestSpec::INPUTTYPE_FLOAT,        gls::DrawTestSpec::OUTPUTTYPE_VEC4,  2 },
+			{ gls::DrawTestSpec::INPUTTYPE_INT,          gls::DrawTestSpec::OUTPUTTYPE_IVEC3, 4 },
+			{ gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT, gls::DrawTestSpec::OUTPUTTYPE_UVEC2, 4 },
+		};
+
+		for (int ioNdx = 0; ioNdx < DE_LENGTH_OF_ARRAY(iopairs); ++ioNdx)
+		{
+			const std::string desc = gls::DrawTestSpec::inputTypeToString(iopairs[ioNdx].input) + de::toString(iopairs[ioNdx].componentCount) + " to " + gls::DrawTestSpec::outputTypeToString(iopairs[ioNdx].output);
+
+			spec.attribs[1].inputType			= iopairs[ioNdx].input;
+			spec.attribs[1].outputType			= iopairs[ioNdx].output;
+			spec.attribs[1].storage				= gls::DrawTestSpec::STORAGE_BUFFER;
+			spec.attribs[1].usage				= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+			spec.attribs[1].componentCount		= iopairs[ioNdx].componentCount;
+			spec.attribs[1].offset				= 0;
+			spec.attribs[1].stride				= 0;
+			spec.attribs[1].normalize			= false;
+			spec.attribs[1].instanceDivisor		= 0;
+			spec.attribs[1].useDefaultAttribute	= true;
+
+			test->addIteration(spec, desc.c_str());
+		}
+
+		this->addChild(test);
+	}
+}
+
+class IndexGroup : public TestCaseGroup
+{
+public:
+									IndexGroup		(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~IndexGroup		(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+IndexGroup::IndexGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+IndexGroup::~IndexGroup (void)
+{
+}
+
+void IndexGroup::init (void)
+{
+	struct IndexTest
+	{
+		gls::DrawTestSpec::Storage		storage;
+		gls::DrawTestSpec::IndexType	type;
+		bool							aligned;
+		int								offsets[3];
+	};
+
+	const IndexTest tests[] =
+	{
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_BYTE,	true,	{ 0, 1, -1 } },
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_SHORT,	true,	{ 0, 2, -1 } },
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_INT,	true,	{ 0, 4, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_SHORT,	false,	{ 1, 3, -1 } },
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_INT,	false,	{ 2, 3, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_BYTE,	true,	{ 0, 1, -1 } },
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_SHORT,	true,	{ 0, 2, -1 } },
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_INT,	true,	{ 0, 4, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_SHORT,	false,	{ 1, 3, -1 } },
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_INT,	false,	{ 2, 3, -1 } },
+	};
+
+	gls::DrawTestSpec spec;
+
+	tcu::TestCaseGroup* userPtrGroup			= new tcu::TestCaseGroup(m_testCtx, "user_ptr", "user pointer");
+	tcu::TestCaseGroup* unalignedUserPtrGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_user_ptr", "unaligned user pointer");
+	tcu::TestCaseGroup* bufferGroup				= new tcu::TestCaseGroup(m_testCtx, "buffer", "buffer");
+	tcu::TestCaseGroup* unalignedBufferGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_buffer", "unaligned buffer");
+
+	genBasicSpec(spec, m_method);
+
+	this->addChild(userPtrGroup);
+	this->addChild(unalignedUserPtrGroup);
+	this->addChild(bufferGroup);
+	this->addChild(unalignedBufferGroup);
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(tests); ++testNdx)
+	{
+		const IndexTest&				indexTest	= tests[testNdx];
+		tcu::TestCaseGroup*				group		= (indexTest.storage == gls::DrawTestSpec::STORAGE_USER) ? ((indexTest.aligned) ? (userPtrGroup) : (unalignedUserPtrGroup)) : ((indexTest.aligned) ? (bufferGroup) : (unalignedBufferGroup));
+
+		const std::string				name		= std::string("index_") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
+		const std::string				desc		= std::string("index ") + gls::DrawTestSpec::indexTypeToString(indexTest.type) + " in " + gls::DrawTestSpec::storageToString(indexTest.storage);
+		de::MovePtr<gls::DrawTest>		test		(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str()));
+
+		spec.indexType			= indexTest.type;
+		spec.indexStorage		= indexTest.storage;
+
+		for (int iterationNdx = 0; iterationNdx < DE_LENGTH_OF_ARRAY(indexTest.offsets) && indexTest.offsets[iterationNdx] != -1; ++iterationNdx)
+		{
+			const std::string iterationDesc = std::string("offset ") + de::toString(indexTest.offsets[iterationNdx]);
+			spec.indexPointerOffset	= indexTest.offsets[iterationNdx];
+			test->addIteration(spec, iterationDesc.c_str());
+		}
+
+		if (spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET &&
+			spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+			group->addChild(test.release());
+	}
+}
+
+
+class FirstGroup : public TestCaseGroup
+{
+public:
+									FirstGroup		(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~FirstGroup		(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+FirstGroup::FirstGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+FirstGroup::~FirstGroup (void)
+{
+}
+
+void FirstGroup::init (void)
+{
+	const int firsts[] =
+	{
+		1, 3, 17
+	};
+
+	gls::DrawTestSpec spec;
+	genBasicSpec(spec, m_method);
+
+	for (int firstNdx = 0; firstNdx < DE_LENGTH_OF_ARRAY(firsts); ++firstNdx)
+	{
+		const std::string	name = std::string("first_") + de::toString(firsts[firstNdx]);
+		const std::string	desc = std::string("first ") + de::toString(firsts[firstNdx]);
+		gls::DrawTest*		test = new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str());
+
+		spec.first = firsts[firstNdx];
+
+		addTestIterations(test, spec, TYPE_DRAW_COUNT);
+
+		this->addChild(test);
+	}
+}
+
+class MethodGroup : public TestCaseGroup
+{
+public:
+									MethodGroup			(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~MethodGroup		(void);
+
+	void							init				(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+MethodGroup::MethodGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+MethodGroup::~MethodGroup (void)
+{
+}
+
+void MethodGroup::init (void)
+{
+	const bool indexed		= (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS) || (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED) || (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED);
+	const bool hasFirst		= (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS) || (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INSTANCED);
+
+	const gls::DrawTestSpec::Primitive primitive[] =
+	{
+		gls::DrawTestSpec::PRIMITIVE_POINTS,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINES,
+		gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINE_LOOP
+	};
+
+	if (hasFirst)
+	{
+		// First-tests
+		this->addChild(new FirstGroup(m_context, "first", "First tests", m_method));
+	}
+
+	if (indexed)
+	{
+		// Index-tests
+		if (m_method != gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED)
+			this->addChild(new IndexGroup(m_context, "indices", "Index tests", m_method));
+	}
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(primitive); ++ndx)
+	{
+		const std::string name = gls::DrawTestSpec::primitiveToString(primitive[ndx]);
+		const std::string desc = gls::DrawTestSpec::primitiveToString(primitive[ndx]);
+
+		this->addChild(new AttributeGroup(m_context, name.c_str(), desc.c_str(), m_method, primitive[ndx], gls::DrawTestSpec::INDEXTYPE_SHORT, gls::DrawTestSpec::STORAGE_BUFFER));
+	}
+}
+
+class GridProgram : public sglr::ShaderProgram
+{
+public:
+			GridProgram		(void);
+
+	void	shadeVertices	(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void	shadeFragments	(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+};
+
+GridProgram::GridProgram (void)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_offset", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource("#version 300 es\n"
+														"in highp vec4 a_position;\n"
+														"in highp vec4 a_offset;\n"
+														"in highp vec4 a_color;\n"
+														"out mediump vec4 v_color;\n"
+														"void main(void)\n"
+														"{\n"
+														"	gl_Position = a_position + a_offset;\n"
+														"	v_color = a_color;\n"
+														"}\n")
+							<< sglr::pdec::FragmentSource(
+														"#version 300 es\n"
+														"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+														"in mediump vec4 v_color;\n"
+														"void main(void)\n"
+														"{\n"
+														"	dEQP_FragColor = v_color;\n"
+														"}\n"))
+{
+}
+
+void GridProgram::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		packets[ndx]->position = rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx) + rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+		packets[ndx]->outputs[0] = rr::readVertexAttribFloat(inputs[2], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+	}
+}
+
+void GridProgram::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx));
+}
+
+class InstancedGridRenderTest : public TestCase
+{
+public:
+					InstancedGridRenderTest		(Context& context, const char* name, const char* desc, int gridSide, bool useIndices);
+					~InstancedGridRenderTest	(void);
+
+	IterateResult	iterate						(void);
+
+private:
+	void			renderTo					(sglr::Context& ctx, sglr::ShaderProgram& program, tcu::Surface& dst);
+	bool			verifyImage					(const tcu::Surface& image);
+
+	const int		m_gridSide;
+	const bool		m_useIndices;
+};
+
+InstancedGridRenderTest::InstancedGridRenderTest (Context& context, const char* name, const char* desc, int gridSide, bool useIndices)
+	: TestCase		(context, name, desc)
+	, m_gridSide	(gridSide)
+	, m_useIndices	(useIndices)
+{
+}
+
+InstancedGridRenderTest::~InstancedGridRenderTest (void)
+{
+}
+
+InstancedGridRenderTest::IterateResult InstancedGridRenderTest::iterate (void)
+{
+	const int renderTargetWidth  = de::min(1024, m_context.getRenderTarget().getWidth());
+	const int renderTargetHeight = de::min(1024, m_context.getRenderTarget().getHeight());
+
+	sglr::GLContext ctx		(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS | sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(0, 0, renderTargetWidth, renderTargetHeight));
+	tcu::Surface	surface	(renderTargetWidth, renderTargetHeight);
+	GridProgram		program;
+
+	// render
+
+	renderTo(ctx, program, surface);
+
+	// verify image
+
+	if (verifyImage(surface))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect rendering result");
+	return STOP;
+}
+
+void InstancedGridRenderTest::renderTo (sglr::Context& ctx, sglr::ShaderProgram& program, tcu::Surface& dst)
+{
+	const tcu::Vec4 green	(0, 1, 0, 1);
+	const tcu::Vec4 yellow	(1, 1, 0, 1);
+
+	deUint32 positionBuf	= 0;
+	deUint32 offsetBuf		= 0;
+	deUint32 colorBuf		= 0;
+	deUint32 indexBuf		= 0;
+	deUint32 programID		= ctx.createProgram(&program);
+	deInt32 posLocation		= ctx.getAttribLocation(programID, "a_position");
+	deInt32 offsetLocation	= ctx.getAttribLocation(programID, "a_offset");
+	deInt32 colorLocation	= ctx.getAttribLocation(programID, "a_color");
+
+	float cellW	= 2.0f / m_gridSide;
+	float cellH	= 2.0f / m_gridSide;
+	const tcu::Vec4 vertexPositions[] =
+	{
+		tcu::Vec4(0,		0,		0, 1),
+		tcu::Vec4(cellW,	0,		0, 1),
+		tcu::Vec4(0,		cellH,	0, 1),
+
+		tcu::Vec4(0,		cellH,	0, 1),
+		tcu::Vec4(cellW,	0,		0, 1),
+		tcu::Vec4(cellW,	cellH,	0, 1),
+	};
+
+	const deUint16 indices[] =
+	{
+		0, 4, 3,
+		2, 1, 5
+	};
+
+	std::vector<tcu::Vec4> offsets;
+	for (int x = 0; x < m_gridSide; ++x)
+	for (int y = 0; y < m_gridSide; ++y)
+		offsets.push_back(tcu::Vec4(x * cellW - 1.0f, y * cellW - 1.0f, 0, 0));
+
+	std::vector<tcu::Vec4> colors;
+	for (int x = 0; x < m_gridSide; ++x)
+	for (int y = 0; y < m_gridSide; ++y)
+		colors.push_back(((x + y) % 2 == 0) ? (green) : (yellow));
+
+	ctx.genBuffers(1, &positionBuf);
+	ctx.bindBuffer(GL_ARRAY_BUFFER, positionBuf);
+	ctx.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
+	ctx.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	ctx.vertexAttribDivisor(posLocation, 0);
+	ctx.enableVertexAttribArray(posLocation);
+
+	ctx.genBuffers(1, &offsetBuf);
+	ctx.bindBuffer(GL_ARRAY_BUFFER, offsetBuf);
+	ctx.bufferData(GL_ARRAY_BUFFER, offsets.size() * sizeof(tcu::Vec4), &offsets[0], GL_STATIC_DRAW);
+	ctx.vertexAttribPointer(offsetLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	ctx.vertexAttribDivisor(offsetLocation, 1);
+	ctx.enableVertexAttribArray(offsetLocation);
+
+	ctx.genBuffers(1, &colorBuf);
+	ctx.bindBuffer(GL_ARRAY_BUFFER, colorBuf);
+	ctx.bufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(tcu::Vec4), &colors[0], GL_STATIC_DRAW);
+	ctx.vertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	ctx.vertexAttribDivisor(colorLocation, 1);
+	ctx.enableVertexAttribArray(colorLocation);
+
+	if (m_useIndices)
+	{
+		ctx.genBuffers(1, &indexBuf);
+		ctx.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuf);
+		ctx.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+	}
+
+	ctx.clearColor(0, 0, 0, 1);
+	ctx.clear(GL_COLOR_BUFFER_BIT);
+
+	ctx.viewport(0, 0, dst.getWidth(), dst.getHeight());
+
+	ctx.useProgram(programID);
+	if (m_useIndices)
+		ctx.drawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, DE_NULL, m_gridSide * m_gridSide);
+	else
+		ctx.drawArraysInstanced(GL_TRIANGLES, 0, 6, m_gridSide * m_gridSide);
+	ctx.useProgram(0);
+
+	if (m_useIndices)
+		ctx.deleteBuffers(1, &indexBuf);
+	ctx.deleteBuffers(1, &colorBuf);
+	ctx.deleteBuffers(1, &offsetBuf);
+	ctx.deleteBuffers(1, &positionBuf);
+	ctx.deleteProgram(programID);
+
+	ctx.finish();
+	ctx.readPixels(dst, 0, 0, dst.getWidth(), dst.getHeight());
+
+	glu::checkError(ctx.getError(), "", __FILE__, __LINE__);
+}
+
+bool InstancedGridRenderTest::verifyImage (const tcu::Surface& image)
+{
+	// \note the green/yellow pattern is only for clarity. The test will only verify that all instances were drawn by looking for anything non-green/yellow.
+	using tcu::TestLog;
+
+	const tcu::RGBA green		(0, 255, 0, 255);
+	const tcu::RGBA yellow		(255, 255, 0, 255);
+	const int colorThreshold	= 20;
+
+	tcu::Surface error			(image.getWidth(), image.getHeight());
+	bool isOk					= true;
+
+	for (int y = 1; y < image.getHeight()-1; y++)
+	for (int x = 1; x < image.getWidth()-1; x++)
+	{
+		const tcu::RGBA pixel = image.getPixel(x, y);
+		bool pixelOk = true;
+
+		// Any pixel with !(G ~= 255) is faulty (not a linear combinations of green and yellow)
+		if (de::abs(pixel.getGreen() - 255) > colorThreshold)
+			pixelOk = false;
+
+		// Any pixel with !(B ~= 0) is faulty (not a linear combinations of green and yellow)
+		if (de::abs(pixel.getBlue() - 0) > colorThreshold)
+			pixelOk = false;
+
+		error.setPixel(x, y, (pixelOk) ? (tcu::RGBA(0, 255, 0, 255)) : (tcu::RGBA(255, 0, 0, 255)));
+		isOk = isOk && pixelOk;
+	}
+
+	if (!isOk)
+	{
+		tcu::TestLog& log = m_testCtx.getLog();
+
+		log << TestLog::Message << "Image verification failed." << TestLog::EndMessage;
+		log << TestLog::ImageSet("Verfication result", "Result of rendering")
+			<< TestLog::Image("Result",		"Result",		image)
+			<< TestLog::Image("ErrorMask",	"Error mask",	error)
+			<< TestLog::EndImageSet;
+	}
+	else
+	{
+		tcu::TestLog& log = m_testCtx.getLog();
+
+		log << TestLog::ImageSet("Verfication result", "Result of rendering")
+			<< TestLog::Image("Result", "Result", image)
+			<< TestLog::EndImageSet;
+	}
+
+	return isOk;
+}
+
+class InstancingGroup : public TestCaseGroup
+{
+public:
+									InstancingGroup		(Context& context, const char* name, const char* descr);
+									~InstancingGroup	(void);
+
+	void							init				(void);
+};
+
+InstancingGroup::InstancingGroup (Context& context, const char* name, const char* descr)
+	: TestCaseGroup	(context, name, descr)
+{
+}
+
+InstancingGroup::~InstancingGroup (void)
+{
+}
+
+void InstancingGroup::init (void)
+{
+	const int gridWidths[] =
+	{
+		2,
+		5,
+		10,
+		32,
+		100,
+	};
+
+	// drawArrays
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(gridWidths); ++ndx)
+	{
+		const std::string name = std::string("draw_arrays_instanced_grid_") + de::toString(gridWidths[ndx]) + "x" + de::toString(gridWidths[ndx]);
+		const std::string desc = std::string("DrawArraysInstanced, Grid size ") + de::toString(gridWidths[ndx]) + "x" + de::toString(gridWidths[ndx]);
+
+		this->addChild(new InstancedGridRenderTest(m_context, name.c_str(), desc.c_str(), gridWidths[ndx], false));
+	}
+
+	// drawElements
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(gridWidths); ++ndx)
+	{
+		const std::string name = std::string("draw_elements_instanced_grid_") + de::toString(gridWidths[ndx]) + "x" + de::toString(gridWidths[ndx]);
+		const std::string desc = std::string("DrawElementsInstanced, Grid size ") + de::toString(gridWidths[ndx]) + "x" + de::toString(gridWidths[ndx]);
+
+		this->addChild(new InstancedGridRenderTest(m_context, name.c_str(), desc.c_str(), gridWidths[ndx], true));
+	}
+}
+
+class RandomGroup : public TestCaseGroup
+{
+public:
+									RandomGroup		(Context& context, const char* name, const char* descr);
+									~RandomGroup	(void);
+
+	void							init			(void);
+};
+
+template <int SIZE>
+struct UniformWeightArray
+{
+	float weights[SIZE];
+
+	UniformWeightArray (void)
+	{
+		for (int i=0; i<SIZE; ++i)
+			weights[i] = 1.0f;
+	}
+};
+
+RandomGroup::RandomGroup (Context& context, const char* name, const char* descr)
+	: TestCaseGroup	(context, name, descr)
+{
+}
+
+RandomGroup::~RandomGroup (void)
+{
+}
+
+void RandomGroup::init (void)
+{
+	const int			numAttempts				= 300;
+
+	static const int	attribCounts[]			= { 1, 2, 5 };
+	static const float	attribWeights[]			= { 30, 10, 1 };
+	static const int	primitiveCounts[]		= { 1, 5, 64 };
+	static const float	primitiveCountWeights[]	= { 20, 10, 1 };
+	static const int	indexOffsets[]			= { 0, 7, 13 };
+	static const float	indexOffsetWeights[]	= { 20, 20, 1 };
+	static const int	firsts[]				= { 0, 7, 13 };
+	static const float	firstWeights[]			= { 20, 20, 1 };
+	static const int	instanceCounts[]		= { 1, 2, 16, 17 };
+	static const float	instanceWeights[]		= { 20, 10, 5, 1 };
+	static const int	indexMins[]				= { 0, 1, 3, 8 };
+	static const int	indexMaxs[]				= { 4, 8, 128, 257 };
+	static const float	indexWeights[]			= { 50, 50, 50, 50 };
+	static const int	offsets[]				= { 0, 1, 5, 12 };
+	static const float	offsetWeights[]			= { 50, 10, 10, 10 };
+	static const int	strides[]				= { 0, 7, 16, 17 };
+	static const float	strideWeights[]			= { 50, 10, 10, 10 };
+	static const int	instanceDivisors[]		= { 0, 1, 3, 129 };
+	static const float	instanceDivisorWeights[]= { 70, 30, 10, 10 };
+
+	static const gls::DrawTestSpec::Primitive primitives[] =
+	{
+		gls::DrawTestSpec::PRIMITIVE_POINTS,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINES,
+		gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINE_LOOP
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(primitives)> primitiveWeights;
+
+	static const gls::DrawTestSpec::DrawMethod drawMethods[] =
+	{
+		gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INSTANCED,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(drawMethods)> drawMethodWeights;
+
+	static const gls::DrawTestSpec::IndexType indexTypes[] =
+	{
+		gls::DrawTestSpec::INDEXTYPE_BYTE,
+		gls::DrawTestSpec::INDEXTYPE_SHORT,
+		gls::DrawTestSpec::INDEXTYPE_INT,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(indexTypes)> indexTypeWeights;
+
+	static const gls::DrawTestSpec::Storage storages[] =
+	{
+		gls::DrawTestSpec::STORAGE_USER,
+		gls::DrawTestSpec::STORAGE_BUFFER,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(storages)> storageWeights;
+
+	static const gls::DrawTestSpec::InputType inputTypes[] =
+	{
+		gls::DrawTestSpec::INPUTTYPE_FLOAT,
+		gls::DrawTestSpec::INPUTTYPE_FIXED,
+		gls::DrawTestSpec::INPUTTYPE_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_SHORT,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT,
+		gls::DrawTestSpec::INPUTTYPE_INT,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT,
+		gls::DrawTestSpec::INPUTTYPE_HALF,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		gls::DrawTestSpec::INPUTTYPE_INT_2_10_10_10,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(inputTypes)> inputTypeWeights;
+
+	static const gls::DrawTestSpec::OutputType outputTypes[] =
+	{
+		gls::DrawTestSpec::OUTPUTTYPE_FLOAT,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC4,
+		gls::DrawTestSpec::OUTPUTTYPE_INT,
+		gls::DrawTestSpec::OUTPUTTYPE_UINT,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC4,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC4,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(outputTypes)> outputTypeWeights;
+
+	static const gls::DrawTestSpec::Usage usages[] =
+	{
+		gls::DrawTestSpec::USAGE_DYNAMIC_DRAW,
+		gls::DrawTestSpec::USAGE_STATIC_DRAW,
+		gls::DrawTestSpec::USAGE_STREAM_DRAW,
+		gls::DrawTestSpec::USAGE_STREAM_READ,
+		gls::DrawTestSpec::USAGE_STREAM_COPY,
+		gls::DrawTestSpec::USAGE_STATIC_READ,
+		gls::DrawTestSpec::USAGE_STATIC_COPY,
+		gls::DrawTestSpec::USAGE_DYNAMIC_READ,
+		gls::DrawTestSpec::USAGE_DYNAMIC_COPY,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(usages)> usageWeights;
+
+	static const deUint32 blacklistedCases[]=
+	{
+		544,	//!< extremely narrow triangle
+	};
+
+	std::set<deUint32>	insertedHashes;
+	size_t				insertedCount = 0;
+
+	for (int ndx = 0; ndx < numAttempts; ++ndx)
+	{
+		de::Random random(0xc551393 + ndx); // random does not depend on previous cases
+
+		int					attributeCount = random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(attribCounts), DE_ARRAY_END(attribCounts), attribWeights);
+		gls::DrawTestSpec	spec;
+
+		spec.apiType				= glu::ApiType::es(3,0);
+		spec.primitive				= random.chooseWeighted<gls::DrawTestSpec::Primitive>	(DE_ARRAY_BEGIN(primitives),		DE_ARRAY_END(primitives),		primitiveWeights.weights);
+		spec.primitiveCount			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(primitiveCounts),	DE_ARRAY_END(primitiveCounts),	primitiveCountWeights);
+		spec.drawMethod				= random.chooseWeighted<gls::DrawTestSpec::DrawMethod>	(DE_ARRAY_BEGIN(drawMethods),		DE_ARRAY_END(drawMethods),		drawMethodWeights.weights);
+		spec.indexType				= random.chooseWeighted<gls::DrawTestSpec::IndexType>	(DE_ARRAY_BEGIN(indexTypes),		DE_ARRAY_END(indexTypes),		indexTypeWeights.weights);
+		spec.indexPointerOffset		= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexOffsets),		DE_ARRAY_END(indexOffsets),		indexOffsetWeights);
+		spec.indexStorage			= random.chooseWeighted<gls::DrawTestSpec::Storage>		(DE_ARRAY_BEGIN(storages),			DE_ARRAY_END(storages),			storageWeights.weights);
+		spec.first					= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(firsts),			DE_ARRAY_END(firsts),			firstWeights);
+		spec.indexMin				= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexMins),			DE_ARRAY_END(indexMins),		indexWeights);
+		spec.indexMax				= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexMaxs),			DE_ARRAY_END(indexMaxs),		indexWeights);
+		spec.instanceCount			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(instanceCounts),	DE_ARRAY_END(instanceCounts),	instanceWeights);
+
+		// check spec is legal
+		if (!spec.valid())
+			continue;
+
+		for (int attrNdx = 0; attrNdx < attributeCount;)
+		{
+			bool valid;
+			gls::DrawTestSpec::AttributeSpec attribSpec;
+
+			attribSpec.inputType			= random.chooseWeighted<gls::DrawTestSpec::InputType>	(DE_ARRAY_BEGIN(inputTypes),		DE_ARRAY_END(inputTypes),		inputTypeWeights.weights);
+			attribSpec.outputType			= random.chooseWeighted<gls::DrawTestSpec::OutputType>	(DE_ARRAY_BEGIN(outputTypes),		DE_ARRAY_END(outputTypes),		outputTypeWeights.weights);
+			attribSpec.storage				= random.chooseWeighted<gls::DrawTestSpec::Storage>		(DE_ARRAY_BEGIN(storages),			DE_ARRAY_END(storages),			storageWeights.weights);
+			attribSpec.usage				= random.chooseWeighted<gls::DrawTestSpec::Usage>		(DE_ARRAY_BEGIN(usages),			DE_ARRAY_END(usages),			usageWeights.weights);
+			attribSpec.componentCount		= random.getInt(1, 4);
+			attribSpec.offset				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), offsetWeights);
+			attribSpec.stride				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(strides), DE_ARRAY_END(strides), strideWeights);
+			attribSpec.normalize			= random.getBool();
+			attribSpec.instanceDivisor		= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(instanceDivisors), DE_ARRAY_END(instanceDivisors), instanceDivisorWeights);
+			attribSpec.useDefaultAttribute	= random.getBool();
+
+			// check spec is legal
+			valid = attribSpec.valid(spec.apiType);
+
+			// we do not want interleaved elements. (Might result in some weird floating point values)
+			if (attribSpec.stride && attribSpec.componentCount * gls::DrawTestSpec::inputTypeSize(attribSpec.inputType) > attribSpec.stride)
+				valid = false;
+
+			// try again if not valid
+			if (valid)
+			{
+				spec.attribs.push_back(attribSpec);
+				++attrNdx;
+			}
+		}
+
+		// Do not collapse all vertex positions to a single positions
+		if (spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+			spec.attribs[0].instanceDivisor = 0;
+
+		// Is render result meaningful?
+		{
+			// Only one vertex
+			if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && spec.indexMin == spec.indexMax && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+			if (spec.attribs[0].useDefaultAttribute && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+
+			// Triangle only on one axis
+			if (spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLES || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP)
+			{
+				if (spec.attribs[0].componentCount == 1)
+					continue;
+				if (spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_FLOAT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_INT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_UINT)
+					continue;
+				if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && (spec.indexMax - spec.indexMin) < 2)
+					continue;
+			}
+		}
+
+		// Add case
+		{
+			deUint32 hash = spec.hash();
+			for (int attrNdx = 0; attrNdx < attributeCount; ++attrNdx)
+				hash = (hash << 2) ^ (deUint32)spec.attribs[attrNdx].hash();
+
+			if (insertedHashes.find(hash) == insertedHashes.end())
+			{
+				// Only properly aligned and not blacklisted cases
+				if (spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET			&&
+					spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE			&&
+					!de::contains(DE_ARRAY_BEGIN(blacklistedCases), DE_ARRAY_END(blacklistedCases), hash))
+				{
+					this->addChild(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), spec, de::toString(insertedCount).c_str(), spec.getDesc().c_str()));
+				}
+				insertedHashes.insert(hash);
+
+				++insertedCount;
+			}
+		}
+	}
+}
+
+} // anonymous
+
+DrawTests::DrawTests (Context& context)
+	: TestCaseGroup(context, "draw", "Drawing tests")
+{
+}
+
+DrawTests::~DrawTests (void)
+{
+}
+
+void DrawTests::init (void)
+{
+	// Basic
+	{
+		const gls::DrawTestSpec::DrawMethod basicMethods[] =
+		{
+			gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS,
+			gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS,
+			gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INSTANCED,
+			gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED,
+			gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED,
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(basicMethods); ++ndx)
+		{
+			const std::string name = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
+			const std::string desc = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
+
+			this->addChild(new MethodGroup(m_context, name.c_str(), desc.c_str(), basicMethods[ndx]));
+		}
+	}
+
+	// extreme instancing
+
+	this->addChild(new InstancingGroup(m_context, "instancing", "draw tests with a large instance count."));
+
+	// Random
+
+	this->addChild(new RandomGroup(m_context, "random", "random draw commands."));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fDrawTests.hpp b/modules/gles3/functional/es3fDrawTests.hpp
new file mode 100644
index 0000000..fb41dc8
--- /dev/null
+++ b/modules/gles3/functional/es3fDrawTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FDRAWTESTS_HPP
+#define _ES3FDRAWTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Drawing tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class DrawTests : public TestCaseGroup
+{
+public:
+						DrawTests		(Context& context);
+						~DrawTests		(void);
+
+	void				init			(void);
+
+private:
+						DrawTests		(const DrawTests& other);
+	DrawTests&			operator=		(const DrawTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FDRAWTESTS_HPP
diff --git a/modules/gles3/functional/es3fFboApiTests.cpp b/modules/gles3/functional/es3fFboApiTests.cpp
new file mode 100644
index 0000000..3f57a8d
--- /dev/null
+++ b/modules/gles3/functional/es3fFboApiTests.cpp
@@ -0,0 +1,607 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer Object API Tests.
+ *
+ * Notes:
+ *   All gl calls are passed thru sglr::Context class. Reasons:
+ *    + Name, object allocation is tracked and live resources are freed
+ *      when Context is destroyed.
+ *    + Makes it possible to easily log all relevant calls into test log.
+ *      \todo [pyry] This is not implemented yet
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboApiTests.hpp"
+#include "sglrGLContext.hpp"
+#include "gluDefs.hpp"
+#include "gluStrUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deString.h"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+
+using glw::GLenum;
+using glw::GLint;
+
+static void logComment (tcu::TestContext& testCtx, const char* comment)
+{
+	testCtx.getLog() << TestLog::Message << "// " << comment << TestLog::EndMessage;
+}
+
+static void checkError (tcu::TestContext& testCtx, sglr::Context& ctx, GLenum expect)
+{
+	GLenum result = ctx.getError();
+	testCtx.getLog() << TestLog::Message << "// " << (result == expect ? "Pass" : "Fail") << ", expected " << glu::getErrorStr(expect) << TestLog::EndMessage;
+
+	if (result != expect)
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Error code mismatch");
+}
+
+static const char* getAttachmentName (GLenum attachment)
+{
+	switch (attachment)
+	{
+		case GL_COLOR_ATTACHMENT0:	return "GL_COLOR_ATTACHMENT0";
+		case GL_DEPTH_ATTACHMENT:	return "GL_DEPTH_ATTACHMENT";
+		case GL_STENCIL_ATTACHMENT:	return "GL_STENCIL_ATTACHMENT";
+		default: throw tcu::InternalError("Unknown attachment", "", __FILE__, __LINE__);
+	}
+}
+
+static const char* getAttachmentParameterName (GLenum pname)
+{
+	switch (pname)
+	{
+		case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:				return "GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE";
+		case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:				return "GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME";
+		case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL:			return "GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL";
+		case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE:	return "GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE";
+		default: throw tcu::InternalError("Unknown parameter", "", __FILE__, __LINE__);
+	}
+}
+
+static string getAttachmentParameterValueName (GLint value)
+{
+	switch (value)
+	{
+		case 0:									return "GL_NONE(0)";
+		case GL_TEXTURE:						return "GL_TEXTURE";
+		case GL_RENDERBUFFER:					return "GL_RENDERBUFFER";
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_X:	return "GL_TEXTURE_CUBE_MAP_POSITIVE_X";
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:	return "GL_TEXTURE_CUBE_MAP_NEGATIVE_X";
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:	return "GL_TEXTURE_CUBE_MAP_POSITIVE_Y";
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:	return "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y";
+		case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:	return "GL_TEXTURE_CUBE_MAP_POSITIVE_Z";
+		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:	return "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z";
+		default:
+		{
+			char tmp[64];
+			deSprintf(tmp, sizeof(tmp), "0x%x", value);
+			return string(tmp);
+		}
+	}
+}
+
+static void checkFboAttachmentParam (tcu::TestContext& testCtx, sglr::Context& ctx, GLenum attachment, GLenum pname, GLint expectedValue)
+{
+	TestLog& log = testCtx.getLog();
+	log << TestLog::Message << "// Querying " << getAttachmentName(attachment) << " " << getAttachmentParameterName(pname) << TestLog::EndMessage;
+
+	GLint value = 0xcdcdcdcd;
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, attachment, pname, &value);
+
+	GLenum err = ctx.getError();
+
+	if (value == expectedValue && err == GL_NO_ERROR)
+		log << TestLog::Message << "// Pass" << TestLog::EndMessage;
+	else
+	{
+		log << TestLog::Message << "// Fail, expected " << getAttachmentParameterValueName(expectedValue) << " without error" << TestLog::EndMessage;
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid result for attachment param query");
+	}
+}
+
+static void textureLevelsTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	deUint32	tex		= 1;
+	deUint32	fbo		= 1;
+	GLint		maxTexSize;
+	int			log2MaxTexSize;
+
+	context.getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
+	log2MaxTexSize = deLog2Floor32(maxTexSize);
+
+	testCtx.getLog() << TestLog::Message << "// GL_MAX_TEXTURE_SIZE is " << maxTexSize << ", floor(log2(" << maxTexSize << ")) = " << log2MaxTexSize << TestLog::EndMessage;
+
+	context.bindTexture(GL_TEXTURE_2D, tex);
+	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256);
+	context.texImage2D(GL_TEXTURE_2D, 1, GL_RGB, 128, 128);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+	const int levels[] = { 2, 1, 0, -1, 0x7fffffff, 0, log2MaxTexSize-2, log2MaxTexSize-1, log2MaxTexSize, log2MaxTexSize+1, log2MaxTexSize+2, 1 };
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(levels); ndx++)
+	{
+		context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, levels[ndx]);
+		checkError(testCtx, context, levels[ndx] >= 0 && levels[ndx] <= log2MaxTexSize ? GL_NO_ERROR : GL_INVALID_VALUE);
+	}
+}
+
+static void validTex2DAttachmentsTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+	static const GLenum attachmentPoints[] =
+	{
+		GL_COLOR_ATTACHMENT0,
+		GL_DEPTH_ATTACHMENT,
+		GL_STENCIL_ATTACHMENT
+	};
+
+	// Texture2D
+	deUint32 tex2D = 1;
+	context.bindTexture(GL_TEXTURE_2D, tex2D);
+	for (int pointNdx = 0; pointNdx < DE_LENGTH_OF_ARRAY(attachmentPoints); pointNdx++)
+	{
+		context.framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoints[pointNdx], GL_TEXTURE_2D, tex2D, 0);
+		checkError(testCtx, context, GL_NO_ERROR);
+	}
+}
+
+static void validTexCubeAttachmentsTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	static const GLenum attachmentPoints[] =
+	{
+		GL_COLOR_ATTACHMENT0,
+		GL_DEPTH_ATTACHMENT,
+		GL_STENCIL_ATTACHMENT
+	};
+	static const GLenum cubeTargets[] =
+	{
+		GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+		GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+		GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+		GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+		GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+		GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
+	};
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	// TextureCube
+	deUint32 texCube = 2;
+	context.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+	for (int pointNdx = 0; pointNdx < DE_LENGTH_OF_ARRAY(attachmentPoints); pointNdx++)
+	{
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(cubeTargets); targetNdx++)
+		{
+			context.framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoints[pointNdx], cubeTargets[targetNdx], texCube, 0);
+			checkError(testCtx, context, GL_NO_ERROR);
+		}
+	}
+}
+
+static void validRboAttachmentsTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	static const GLenum attachmentPoints[] =
+	{
+		GL_COLOR_ATTACHMENT0,
+		GL_DEPTH_ATTACHMENT,
+		GL_STENCIL_ATTACHMENT
+	};
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	// Renderbuffer
+	deUint32 rbo = 3;
+	context.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+	for (int pointNdx = 0; pointNdx < DE_LENGTH_OF_ARRAY(attachmentPoints); pointNdx++)
+	{
+		context.framebufferRenderbuffer(GL_FRAMEBUFFER, attachmentPoints[pointNdx], GL_RENDERBUFFER, rbo);
+		checkError(testCtx, context, GL_NO_ERROR);
+	}
+}
+
+static void attachToDefaultFramebufferTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	logComment(testCtx, "Attaching 2D texture to default framebuffer");
+
+	deUint32 tex2D = 1;
+	context.bindTexture(GL_TEXTURE_2D, tex2D);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+
+	logComment(testCtx, "Attaching renderbuffer to default framebuffer");
+
+	deUint32 rbo = 1;
+	context.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+}
+
+static void invalidTex2DAttachmentTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	logComment(testCtx, "Attaching 2D texture using GL_TEXTURE_CUBE_MAP_NEGATIVE_X texture target");
+
+	deUint32 tex2D = 1;
+	context.bindTexture(GL_TEXTURE_2D, tex2D);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, tex2D, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+
+	logComment(testCtx, "Attaching deleted 2D texture object");
+	context.deleteTextures(1, &tex2D);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+}
+
+static void invalidTexCubeAttachmentTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	logComment(testCtx, "Attaching cube texture using GL_TEXTURE_2D texture target");
+	deUint32 texCube = 2;
+	context.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texCube, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+
+	logComment(testCtx, "Attaching deleted cube texture object");
+	context.deleteTextures(1, &texCube);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+}
+
+static void invalidRboAttachmentTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	logComment(testCtx, "Attaching renderbuffer using GL_FRAMEBUFFER renderbuffer target");
+	deUint32 rbo = 3;
+	context.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER, rbo);
+	checkError(testCtx, context, GL_INVALID_ENUM);
+
+	logComment(testCtx, "Attaching deleted renderbuffer object");
+	context.deleteRenderbuffers(1, &rbo);
+	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+}
+
+static void attachNamesTest (tcu::TestContext& testCtx, sglr::Context& context)
+{
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	// Just allocate some names, don't bind for storage
+	deUint32 reservedTexName;
+	context.genTextures(1, &reservedTexName);
+
+	logComment(testCtx, "Attaching allocated texture name to 2D target");
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, reservedTexName, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+
+	logComment(testCtx, "Attaching allocated texture name to cube target");
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, reservedTexName, 0);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+
+	deUint32 reservedRboName;
+	context.genRenderbuffers(1, &reservedRboName);
+
+	logComment(testCtx, "Attaching allocated renderbuffer name");
+	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, reservedRboName);
+	checkError(testCtx, context, GL_INVALID_OPERATION);
+}
+
+static void attachmentQueryDefaultFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	// Check that proper error codes are returned
+	GLint unused = -1;
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+}
+
+static void attachmentQueryEmptyFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	static const GLenum attachmentPoints[] =
+	{
+		GL_COLOR_ATTACHMENT0,
+		GL_DEPTH_ATTACHMENT,
+		GL_STENCIL_ATTACHMENT
+	};
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(attachmentPoints); ndx++)
+		checkFboAttachmentParam(testCtx, ctx, attachmentPoints[ndx], GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 0);
+
+	// Check that proper error codes are returned
+	GLint unused = -1;
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &unused);
+	checkError(testCtx, ctx, GL_INVALID_OPERATION);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &unused);
+	checkError(testCtx, ctx, GL_INVALID_OPERATION);
+}
+
+static void attachmentQueryTex2DTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	ctx.bindTexture(GL_TEXTURE_2D, 1);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 1, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 1);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, 0);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, 0);
+}
+
+static void attachmentQueryTexCubeTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	ctx.bindTexture(GL_TEXTURE_CUBE_MAP, 2);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 2, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 2);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, 0);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y);
+}
+
+static void attachmentQueryRboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	ctx.bindRenderbuffer(GL_RENDERBUFFER, 3);
+	ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 3);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 3);
+
+	GLint unused = 0;
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &unused);
+	checkError(testCtx, ctx, GL_INVALID_ENUM);
+}
+
+static void deleteTex2DAttachedToBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 tex2D = 1;
+	ctx.bindTexture(GL_TEXTURE_2D, tex2D);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, tex2D);
+
+	ctx.deleteTextures(1, &tex2D);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
+}
+
+static void deleteTexCubeAttachedToBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 texCube = 1;
+	ctx.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, texCube);
+
+	ctx.deleteTextures(1, &texCube);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
+}
+
+static void deleteRboAttachedToBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 rbo = 1;
+	ctx.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+	ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, rbo);
+
+	ctx.deleteRenderbuffers(1, &rbo);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
+}
+
+static void deleteTex2DAttachedToNotBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 tex2D = 1;
+	ctx.bindTexture(GL_TEXTURE_2D, tex2D);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, tex2D);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	ctx.deleteTextures(1, &tex2D);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, tex2D);
+}
+
+static void deleteTexCubeAttachedToNotBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 texCube = 1;
+	ctx.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, texCube);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	ctx.deleteTextures(1, &texCube);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, texCube);
+}
+
+static void deleteRboAttachedToNotBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
+{
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 rbo = 1;
+	ctx.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+	ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, rbo);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	ctx.deleteRenderbuffers(1, &rbo);
+
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
+	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, rbo);
+}
+
+class FboApiCase : public TestCase
+{
+public:
+	typedef void (*TestFunc) (tcu::TestContext& testCtx, sglr::Context& context);
+
+								FboApiCase				(Context& context, const char* name, const char* description, TestFunc test);
+	virtual						~FboApiCase				(void);
+
+	virtual IterateResult		iterate					(void);
+
+private:
+								FboApiCase				(const FboApiCase& other);
+	FboApiCase&					operator=				(const FboApiCase& other);
+
+	TestFunc					m_testFunc;
+};
+
+FboApiCase::FboApiCase (Context& context, const char* name, const char* description, TestFunc test)
+	: TestCase		(context, name, description)
+	, m_testFunc	(test)
+{
+}
+
+FboApiCase::~FboApiCase (void)
+{
+}
+
+TestCase::IterateResult FboApiCase::iterate (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Before test case");
+
+	// Initialize result to PASS
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Execute test case
+	{
+		tcu::TestLog& log = m_context.getTestContext().getLog();
+		sglr::GLContext context(m_context.getRenderContext(), log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(0, 0, m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight()));
+		m_testFunc(m_testCtx, context);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After test case");
+
+	return STOP;
+}
+
+FboApiTests::FboApiTests (Context& context)
+	: TestCaseGroup(context, "api", "API Tests")
+{
+}
+
+FboApiTests::~FboApiTests (void)
+{
+}
+
+void FboApiTests::init (void)
+{
+	// Valid attachments
+	addChild(new FboApiCase(m_context, "valid_tex2d_attachments",					"Valid 2D texture attachments",							validTex2DAttachmentsTest));
+	addChild(new FboApiCase(m_context, "valid_texcube_attachments",					"Valid cubemap attachments",							validTexCubeAttachmentsTest));
+	addChild(new FboApiCase(m_context, "valid_rbo_attachments",						"Valid renderbuffer attachments",						validRboAttachmentsTest));
+
+	// Invalid attachments
+	addChild(new FboApiCase(m_context, "attach_to_default_fbo",						"Invalid usage: attaching to default FBO",				attachToDefaultFramebufferTest));
+	addChild(new FboApiCase(m_context, "invalid_tex2d_attachments",					"Invalid 2D texture attachments",						invalidTex2DAttachmentTest));
+	addChild(new FboApiCase(m_context, "invalid_texcube_attachments",				"Invalid cubemap attachments",							invalidTexCubeAttachmentTest));
+	addChild(new FboApiCase(m_context, "invalid_rbo_attachments",					"Invalid renderbuffer attachments",						invalidRboAttachmentTest));
+	addChild(new FboApiCase(m_context, "attach_names",								"Attach allocated names without objects",				attachNamesTest));
+
+	addChild(new FboApiCase(m_context, "texture_levels",							"Valid and invalid texturel levels",					textureLevelsTest));
+
+	// Attachment queries
+	addChild(new FboApiCase(m_context, "attachment_query_default_fbo",				"Query attachments from default FBO",					attachmentQueryDefaultFboTest));
+	addChild(new FboApiCase(m_context, "attachment_query_empty_fbo",				"Query attachments from empty FBO",						attachmentQueryEmptyFboTest));
+	addChild(new FboApiCase(m_context, "attachment_query_tex2d",					"Query 2d texture attachment properties",				attachmentQueryTex2DTest));
+	addChild(new FboApiCase(m_context, "attachment_query_texcube",					"Query cubemap attachment properties",					attachmentQueryTexCubeTest));
+	addChild(new FboApiCase(m_context, "attachment_query_rbo",						"Query renderbuffer attachment properties",				attachmentQueryRboTest));
+
+	// Delete attachments
+	addChild(new FboApiCase(m_context, "delete_tex_2d_attached_to_bound_fbo",		"Delete 2d texture attached to currently bound FBO",	deleteTex2DAttachedToBoundFboTest));
+	addChild(new FboApiCase(m_context, "delete_tex_cube_attached_to_bound_fbo",		"Delete cubemap attached to currently bound FBO",		deleteTexCubeAttachedToBoundFboTest));
+	addChild(new FboApiCase(m_context, "delete_rbo_attached_to_bound_fbo",			"Delete renderbuffer attached to currently bound FBO",	deleteRboAttachedToBoundFboTest));
+
+	addChild(new FboApiCase(m_context, "delete_tex_2d_attached_to_not_bound_fbo",	"Delete 2d texture attached to FBO",					deleteTex2DAttachedToNotBoundFboTest));
+	addChild(new FboApiCase(m_context, "delete_tex_cube_attached_to_not_bound_fbo",	"Delete cubemap attached to FBO",						deleteTexCubeAttachedToNotBoundFboTest));
+	addChild(new FboApiCase(m_context, "delete_rbo_attached_to_not_bound_fbo",		"Delete renderbuffer attached to FBO",					deleteRboAttachedToNotBoundFboTest));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboApiTests.hpp b/modules/gles3/functional/es3fFboApiTests.hpp
new file mode 100644
index 0000000..b0185f6
--- /dev/null
+++ b/modules/gles3/functional/es3fFboApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFBOAPITESTS_HPP
+#define _ES3FFBOAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer Object API Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FboApiTests : public TestCaseGroup
+{
+public:
+					FboApiTests			(Context& context);
+	virtual			~FboApiTests		(void);
+
+	virtual void	init				(void);
+
+private:
+					FboApiTests			(const FboApiTests& other);
+	FboApiTests&	operator=			(const FboApiTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFBOAPITESTS_HPP
diff --git a/modules/gles3/functional/es3fFboColorbufferTests.cpp b/modules/gles3/functional/es3fFboColorbufferTests.cpp
new file mode 100644
index 0000000..64eb2a5
--- /dev/null
+++ b/modules/gles3/functional/es3fFboColorbufferTests.cpp
@@ -0,0 +1,953 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO colorbuffer tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboColorbufferTests.hpp"
+#include "es3fFboTestCase.hpp"
+#include "es3fFboTestUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "sglrContextUtil.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::UVec4;
+using tcu::TestLog;
+using namespace FboTestUtil;
+
+const tcu::RGBA MIN_THRESHOLD(12, 12, 12, 12);
+
+template <int Size>
+static tcu::Vector<float, Size> randomVector (de::Random& rnd, const tcu::Vector<float, Size>& minVal = tcu::Vector<float, Size>(0.0f), const tcu::Vector<float, Size>& maxVal = tcu::Vector<float, Size>(1.0f))
+{
+	tcu::Vector<float, Size> res;
+	for (int ndx = 0; ndx < Size; ndx++)
+		res[ndx] = rnd.getFloat(minVal[ndx], maxVal[ndx]);
+	return res;
+}
+
+static tcu::Vec4 generateRandomColor (de::Random& random)
+{
+	tcu::Vec4 retVal;
+
+	for (int i = 0; i < 3; ++i)
+		retVal[i] = random.getFloat();
+	retVal[3] = 1.0f;
+
+	return retVal;
+}
+
+class FboColorbufferCase : public FboTestCase
+{
+public:
+	FboColorbufferCase (Context& context, const char* name, const char* desc, const deUint32 format)
+		: FboTestCase			(context, name, desc)
+		, m_format				(format)
+	{
+	}
+
+	bool compare (const tcu::Surface& reference, const tcu::Surface& result)
+	{
+		const tcu::RGBA threshold (tcu::max(getFormatThreshold(m_format), MIN_THRESHOLD));
+
+		m_testCtx.getLog() << TestLog::Message << "Comparing images, threshold: " << threshold << TestLog::EndMessage;
+
+		return tcu::bilinearCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference.getAccess(), result.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
+	}
+
+protected:
+	const deUint32	m_format;
+};
+
+class FboColorClearCase : public FboColorbufferCase
+{
+public:
+	FboColorClearCase (Context& context, const char* name, const char* desc, deUint32 format, int width, int height)
+		: FboColorbufferCase	(context, name, desc, format)
+		, m_width				(width)
+		, m_height				(height)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_format);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		tcu::TextureFormat			fboFormat	= glu::mapGLInternalFormat(m_format);
+		tcu::TextureChannelClass	fmtClass	= tcu::getTextureChannelClass(fboFormat.type);
+		tcu::TextureFormatInfo		fmtInfo		= tcu::getTextureFormatInfo(fboFormat);
+		de::Random					rnd			(17);
+		const int					numClears	= 16;
+		deUint32					fbo			= 0;
+		deUint32					rbo			= 0;
+
+		glGenFramebuffers(1, &fbo);
+		glGenRenderbuffers(1, &rbo);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+		glRenderbufferStorage(GL_RENDERBUFFER, m_format, m_width, m_height);
+		checkError();
+
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+		checkError();
+		checkFramebufferStatus(GL_FRAMEBUFFER);
+
+		glViewport(0, 0, m_width, m_height);
+
+		// Initialize to transparent black.
+		switch (fmtClass)
+		{
+			case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+			case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+				glClearBufferfv(GL_COLOR, 0, Vec4(0.0f).getPtr());
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+				glClearBufferuiv(GL_COLOR, 0, UVec4(0).getPtr());
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+				glClearBufferiv(GL_COLOR, 0, IVec4(0).getPtr());
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+
+		// Do random scissored clears.
+		glEnable(GL_SCISSOR_TEST);
+		for (int ndx = 0; ndx < numClears; ndx++)
+		{
+			int		x		= rnd.getInt(0, m_width		- 1);
+			int		y		= rnd.getInt(0, m_height	- 1);
+			int		w		= rnd.getInt(1, m_width		- x);
+			int		h		= rnd.getInt(1, m_height	- y);
+			Vec4	color	= randomVector<4>(rnd, fmtInfo.valueMin, fmtInfo.valueMax);
+
+			glScissor(x, y, w, h);
+
+			switch (fmtClass)
+			{
+				case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+				case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
+				case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+					glClearBufferfv(GL_COLOR, 0, color.getPtr());
+					break;
+
+				case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+					glClearBufferuiv(GL_COLOR, 0, color.cast<deUint32>().getPtr());
+					break;
+
+				case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+					glClearBufferiv(GL_COLOR, 0, color.cast<int>().getPtr());
+					break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+		}
+
+		// Read results from renderbuffer.
+		readPixels(dst, 0, 0, m_width, m_height, fboFormat, fmtInfo.lookupScale, fmtInfo.lookupBias);
+		checkError();
+	}
+
+private:
+	const int			m_width;
+	const int			m_height;
+};
+
+class FboColorMultiTex2DCase : public FboColorbufferCase
+{
+public:
+	FboColorMultiTex2DCase (Context& context, const char* name, const char* description, deUint32 tex0Fmt, const IVec2& tex0Size, deUint32 tex1Fmt, const IVec2& tex1Size)
+		: FboColorbufferCase	(context, name, description, tex0Fmt)
+		, m_tex0Fmt				(tex0Fmt)
+		, m_tex1Fmt				(tex1Fmt)
+		, m_tex0Size			(tex0Size)
+		, m_tex1Size			(tex1Size)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_tex0Fmt);
+		checkFormatSupport(m_tex1Fmt);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		tcu::TextureFormat		texFmt0			= glu::mapGLInternalFormat(m_tex0Fmt);
+		tcu::TextureFormat		texFmt1			= glu::mapGLInternalFormat(m_tex1Fmt);
+		tcu::TextureFormatInfo	fmtInfo0		= tcu::getTextureFormatInfo(texFmt0);
+		tcu::TextureFormatInfo	fmtInfo1		= tcu::getTextureFormatInfo(texFmt1);
+
+		Texture2DShader			texToFbo0Shader	(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt0), fmtInfo0.valueMax-fmtInfo0.valueMin, fmtInfo0.valueMin);
+		Texture2DShader			texToFbo1Shader	(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt1), fmtInfo1.valueMax-fmtInfo1.valueMin, fmtInfo1.valueMin);
+		Texture2DShader			multiTexShader	(DataTypes() << glu::getSampler2DType(texFmt0) << glu::getSampler2DType(texFmt1), glu::TYPE_FLOAT_VEC4);
+
+		deUint32				texToFbo0ShaderID = getCurrentContext()->createProgram(&texToFbo0Shader);
+		deUint32				texToFbo1ShaderID = getCurrentContext()->createProgram(&texToFbo1Shader);
+		deUint32				multiTexShaderID  = getCurrentContext()->createProgram(&multiTexShader);
+
+		// Setup shaders
+		multiTexShader.setTexScaleBias(0, fmtInfo0.lookupScale * 0.5f, fmtInfo0.lookupBias * 0.5f);
+		multiTexShader.setTexScaleBias(1, fmtInfo1.lookupScale * 0.5f, fmtInfo1.lookupBias * 0.5f);
+		texToFbo0Shader.setUniforms(*getCurrentContext(), texToFbo0ShaderID);
+		texToFbo1Shader.setUniforms(*getCurrentContext(), texToFbo1ShaderID);
+		multiTexShader.setUniforms (*getCurrentContext(), multiTexShaderID);
+
+		// Framebuffers.
+		deUint32				fbo0, fbo1;
+		deUint32				tex0, tex1;
+
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			glu::TransferFormat		transferFmt		= glu::getTransferFormat(ndx ? texFmt1 : texFmt0);
+			deUint32				format			= ndx ? m_tex1Fmt : m_tex0Fmt;
+			bool					isFilterable	= glu::isGLInternalColorFormatFilterable(format);
+			const IVec2&			size			= ndx ? m_tex1Size : m_tex0Size;
+			deUint32&				fbo				= ndx ? fbo1 : fbo0;
+			deUint32&				tex				= ndx ? tex1 : tex0;
+
+			glGenFramebuffers(1, &fbo);
+			glGenTextures(1, &tex);
+
+			glBindTexture(GL_TEXTURE_2D, tex);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
+			glTexImage2D(GL_TEXTURE_2D, 0, format, size.x(), size.y(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+			checkError();
+			checkFramebufferStatus(GL_FRAMEBUFFER);
+		}
+
+		// Render textures to both framebuffers.
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			const deUint32		format		= GL_RGBA;
+			const deUint32		dataType	= GL_UNSIGNED_BYTE;
+			const int			texW		= 128;
+			const int			texH		= 128;
+			deUint32			tmpTex		= 0;
+			deUint32			fbo			= ndx ? fbo1 : fbo0;
+			const IVec2&		viewport	= ndx ? m_tex1Size : m_tex0Size;
+			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);
+
+			if (ndx == 0)
+				tcu::fillWithComponentGradients(data.getAccess(), Vec4(0.0f), Vec4(1.0f));
+			else
+				tcu::fillWithGrid(data.getAccess(), 8, Vec4(0.2f, 0.7f, 0.1f, 1.0f), Vec4(0.7f, 0.1f, 0.5f, 0.8f));
+
+			glGenTextures(1, &tmpTex);
+			glBindTexture(GL_TEXTURE_2D, tmpTex);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
+			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());
+
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glViewport(0, 0, viewport.x(), viewport.y());
+			sglr::drawQuad(*getCurrentContext(), ndx ? texToFbo1ShaderID : texToFbo0ShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+
+		// Render to framebuffer.
+		glBindFramebuffer(GL_FRAMEBUFFER, 0);
+		glViewport(0, 0, getWidth(), getHeight());
+		glActiveTexture(GL_TEXTURE0);
+		glBindTexture(GL_TEXTURE_2D, tex0);
+		glActiveTexture(GL_TEXTURE1);
+		glBindTexture(GL_TEXTURE_2D, tex1);
+		sglr::drawQuad(*getCurrentContext(), multiTexShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+private:
+	deUint32		m_tex0Fmt;
+	deUint32		m_tex1Fmt;
+	IVec2			m_tex0Size;
+	IVec2			m_tex1Size;
+};
+
+class FboColorTexCubeCase : public FboColorbufferCase
+{
+public:
+	FboColorTexCubeCase			(Context& context, const char* name, const char* description, deUint32 texFmt, const IVec2& texSize)
+		: FboColorbufferCase	(context, name, description, texFmt)
+		, m_texSize				(texSize)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_format);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		static const deUint32 cubeGLFaces[] =
+		{
+			GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+			GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+			GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+			GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+			GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+			GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
+		};
+
+		static const tcu::CubeFace cubeTexFaces[] =
+		{
+			tcu::CUBEFACE_POSITIVE_X,
+			tcu::CUBEFACE_POSITIVE_Y,
+			tcu::CUBEFACE_POSITIVE_Z,
+			tcu::CUBEFACE_NEGATIVE_X,
+			tcu::CUBEFACE_NEGATIVE_Y,
+			tcu::CUBEFACE_NEGATIVE_Z
+		};
+
+		de::Random				rnd					(deStringHash(getName()) ^ 0x9eef603d);
+		tcu::TextureFormat		texFmt				= glu::mapGLInternalFormat(m_format);
+		tcu::TextureFormatInfo	fmtInfo				= tcu::getTextureFormatInfo(texFmt);
+
+		Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt), fmtInfo.valueMax-fmtInfo.valueMin, fmtInfo.valueMin);
+		TextureCubeShader		cubeTexShader		(glu::getSamplerCubeType(texFmt), glu::TYPE_FLOAT_VEC4);
+
+		deUint32				texToFboShaderID	= getCurrentContext()->createProgram(&texToFboShader);
+		deUint32				cubeTexShaderID		= getCurrentContext()->createProgram(&cubeTexShader);
+
+		// Setup shaders
+		texToFboShader.setUniforms(*getCurrentContext(), texToFboShaderID);
+		cubeTexShader.setTexScaleBias(fmtInfo.lookupScale, fmtInfo.lookupBias);
+
+		// Framebuffers.
+		std::vector<deUint32>	fbos;
+		deUint32				tex;
+
+		{
+			glu::TransferFormat		transferFmt		= glu::getTransferFormat(texFmt);
+			bool					isFilterable	= glu::isGLInternalColorFormatFilterable(m_format);
+			const IVec2&			size			= m_texSize;
+
+
+			glGenTextures(1, &tex);
+
+			glBindTexture(GL_TEXTURE_CUBE_MAP,		tex);
+			glTexParameteri(GL_TEXTURE_CUBE_MAP,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_CUBE_MAP,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_CUBE_MAP,	GL_TEXTURE_MIN_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
+			glTexParameteri(GL_TEXTURE_CUBE_MAP,	GL_TEXTURE_MAG_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
+
+			// Generate an image and FBO for each cube face
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cubeGLFaces); ndx++)
+				glTexImage2D(cubeGLFaces[ndx], 0, m_format, size.x(), size.y(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+			checkError();
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cubeGLFaces); ndx++)
+			{
+				deUint32			layerFbo;
+
+				glGenFramebuffers(1, &layerFbo);
+				glBindFramebuffer(GL_FRAMEBUFFER, layerFbo);
+				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cubeGLFaces[ndx], tex, 0);
+				checkError();
+				checkFramebufferStatus(GL_FRAMEBUFFER);
+
+				fbos.push_back(layerFbo);
+			}
+		}
+
+		// Render test images to random cube faces
+		std::vector<int> order;
+
+		for (size_t n = 0; n < fbos.size(); n++)
+			order.push_back((int)n);
+		rnd.shuffle(order.begin(), order.end());
+
+		DE_ASSERT(order.size() >= 4);
+		for (int ndx = 0; ndx < 4; ndx++)
+		{
+			const int			face		= order[ndx];
+			const deUint32		format		= GL_RGBA;
+			const deUint32		dataType	= GL_UNSIGNED_BYTE;
+			const int			texW		= 128;
+			const int			texH		= 128;
+			deUint32			tmpTex		= 0;
+			const deUint32		fbo			= fbos[face];
+			const IVec2&		viewport	= m_texSize;
+			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);
+
+			tcu::fillWithGrid(data.getAccess(), 8, generateRandomColor(rnd), Vec4(0.0f));
+
+			glGenTextures(1, &tmpTex);
+			glBindTexture(GL_TEXTURE_2D, tmpTex);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
+			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());
+
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glViewport(0, 0, viewport.x(), viewport.y());
+			sglr::drawQuad(*getCurrentContext(), texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+			checkError();
+
+			// Render to framebuffer
+			{
+				const Vec3		p0	= Vec3(float(ndx % 2) - 1.0f, float(ndx / 2) - 1.0f, 0.0f);
+				const Vec3		p1	= p0 + Vec3(1.0f, 1.0f, 0.0f);
+
+				glBindFramebuffer(GL_FRAMEBUFFER, 0);
+				glViewport(0, 0, getWidth(), getHeight());
+
+				glActiveTexture(GL_TEXTURE0);
+				glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+
+				cubeTexShader.setFace(cubeTexFaces[face]);
+				cubeTexShader.setUniforms(*getCurrentContext(), cubeTexShaderID);
+
+				sglr::drawQuad(*getCurrentContext(), cubeTexShaderID, p0, p1);
+				checkError();
+			}
+		}
+
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+private:
+	IVec2			m_texSize;
+};
+
+class FboColorTex2DArrayCase : public FboColorbufferCase
+{
+public:
+	FboColorTex2DArrayCase (Context& context, const char* name, const char* description, deUint32 texFmt, const IVec3& texSize)
+		: FboColorbufferCase	(context, name, description, texFmt)
+		, m_texSize				(texSize)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_format);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		de::Random				rnd					(deStringHash(getName()) ^ 0xed607a89);
+		tcu::TextureFormat		texFmt				= glu::mapGLInternalFormat(m_format);
+		tcu::TextureFormatInfo	fmtInfo				= tcu::getTextureFormatInfo(texFmt);
+
+		Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt), fmtInfo.valueMax-fmtInfo.valueMin, fmtInfo.valueMin);
+		Texture2DArrayShader	arrayTexShader		(glu::getSampler2DArrayType(texFmt), glu::TYPE_FLOAT_VEC4);
+
+		deUint32				texToFboShaderID	= getCurrentContext()->createProgram(&texToFboShader);
+		deUint32				arrayTexShaderID	= getCurrentContext()->createProgram(&arrayTexShader);
+
+		// Setup textures
+		texToFboShader.setUniforms(*getCurrentContext(), texToFboShaderID);
+		arrayTexShader.setTexScaleBias(fmtInfo.lookupScale, fmtInfo.lookupBias);
+
+		// Framebuffers.
+		std::vector<deUint32>	fbos;
+		deUint32				tex;
+
+		{
+			glu::TransferFormat		transferFmt		= glu::getTransferFormat(texFmt);
+			bool					isFilterable	= glu::isGLInternalColorFormatFilterable(m_format);
+			const IVec3&			size			= m_texSize;
+
+
+			glGenTextures(1, &tex);
+
+			glBindTexture(GL_TEXTURE_2D_ARRAY,		tex);
+			glTexParameteri(GL_TEXTURE_2D_ARRAY,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D_ARRAY,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D_ARRAY,	GL_TEXTURE_WRAP_R,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D_ARRAY,	GL_TEXTURE_MIN_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
+			glTexParameteri(GL_TEXTURE_2D_ARRAY,	GL_TEXTURE_MAG_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, m_format, size.x(), size.y(), size.z(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+
+			// Generate an FBO for each layer
+			for (int ndx = 0; ndx < m_texSize.z(); ndx++)
+			{
+				deUint32			layerFbo;
+
+				glGenFramebuffers(1, &layerFbo);
+				glBindFramebuffer(GL_FRAMEBUFFER, layerFbo);
+				glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, ndx);
+				checkError();
+				checkFramebufferStatus(GL_FRAMEBUFFER);
+
+				fbos.push_back(layerFbo);
+			}
+		}
+
+		// Render test images to random texture layers
+		std::vector<int>		order;
+
+		for (size_t n = 0; n < fbos.size(); n++)
+			order.push_back((int)n);
+		rnd.shuffle(order.begin(), order.end());
+
+		for (size_t ndx = 0; ndx < order.size(); ndx++)
+		{
+			const int			layer		= order[ndx];
+			const deUint32		format		= GL_RGBA;
+			const deUint32		dataType	= GL_UNSIGNED_BYTE;
+			const int			texW		= 128;
+			const int			texH		= 128;
+			deUint32			tmpTex		= 0;
+			const deUint32		fbo			= fbos[layer];
+			const IVec3&		viewport	= m_texSize;
+			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);
+
+			tcu::fillWithGrid(data.getAccess(), 8, generateRandomColor(rnd), Vec4(0.0f));
+
+			glGenTextures(1, &tmpTex);
+			glBindTexture(GL_TEXTURE_2D, tmpTex);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
+			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());
+
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glViewport(0, 0, viewport.x(), viewport.y());
+			sglr::drawQuad(*getCurrentContext(), texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+			checkError();
+
+			// Render to framebuffer
+			{
+				const Vec3		p0	= Vec3(float(ndx % 2) - 1.0f, float(ndx / 2) - 1.0f, 0.0f);
+				const Vec3		p1	= p0 + Vec3(1.0f, 1.0f, 0.0f);
+
+				glBindFramebuffer(GL_FRAMEBUFFER, 0);
+				glViewport(0, 0, getWidth(), getHeight());
+
+				glActiveTexture(GL_TEXTURE0);
+				glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+
+				arrayTexShader.setLayer(layer);
+				arrayTexShader.setUniforms(*getCurrentContext(), arrayTexShaderID);
+
+				sglr::drawQuad(*getCurrentContext(), arrayTexShaderID, p0, p1);
+				checkError();
+			}
+		}
+
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+private:
+	IVec3			m_texSize;
+};
+
+class FboColorTex3DCase : public FboColorbufferCase
+{
+public:
+	FboColorTex3DCase (Context& context, const char* name, const char* description, deUint32 texFmt, const IVec3& texSize)
+		: FboColorbufferCase	(context, name, description, texFmt)
+		, m_texSize				(texSize)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_format);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		de::Random				rnd				(deStringHash(getName()) ^ 0x74d947b2);
+		tcu::TextureFormat		texFmt			= glu::mapGLInternalFormat(m_format);
+		tcu::TextureFormatInfo	fmtInfo			= tcu::getTextureFormatInfo(texFmt);
+
+		Texture2DShader			texToFboShader	(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt), fmtInfo.valueMax-fmtInfo.valueMin, fmtInfo.valueMin);
+		Texture3DShader			tdTexShader		(glu::getSampler3DType(texFmt), glu::TYPE_FLOAT_VEC4);
+
+		deUint32				texToFboShaderID= getCurrentContext()->createProgram(&texToFboShader);
+		deUint32				tdTexShaderID	= getCurrentContext()->createProgram(&tdTexShader);
+
+		// Setup shaders
+		texToFboShader.setUniforms(*getCurrentContext(), texToFboShaderID);
+		tdTexShader.setTexScaleBias(fmtInfo.lookupScale, fmtInfo.lookupBias);
+
+		// Framebuffers.
+		std::vector<deUint32>	fbos;
+		deUint32				tex;
+
+		{
+			glu::TransferFormat		transferFmt		= glu::getTransferFormat(texFmt);
+			bool					isFilterable	= glu::isGLInternalColorFormatFilterable(m_format);
+			const IVec3&			size			= m_texSize;
+
+
+			glGenTextures(1, &tex);
+
+			glBindTexture(GL_TEXTURE_3D,		tex);
+			glTexParameteri(GL_TEXTURE_3D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_3D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_3D,	GL_TEXTURE_WRAP_R,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_3D,	GL_TEXTURE_MIN_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
+			glTexParameteri(GL_TEXTURE_3D,	GL_TEXTURE_MAG_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
+			glTexImage3D(GL_TEXTURE_3D, 0, m_format, size.x(), size.y(), size.z(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+
+			// Generate an FBO for each layer
+			for (int ndx = 0; ndx < m_texSize.z(); ndx++)
+			{
+				deUint32			layerFbo;
+
+				glGenFramebuffers(1, &layerFbo);
+				glBindFramebuffer(GL_FRAMEBUFFER, layerFbo);
+				glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, ndx);
+				checkError();
+				checkFramebufferStatus(GL_FRAMEBUFFER);
+
+				fbos.push_back(layerFbo);
+			}
+		}
+
+		// Render test images to random texture layers
+		std::vector<int> order;
+
+		for (size_t n = 0; n < fbos.size(); n++)
+			order.push_back((int)n);
+		rnd.shuffle(order.begin(), order.end());
+
+		for (size_t ndx = 0; ndx < order.size(); ndx++)
+		{
+			const int			layer		= order[ndx];
+			const deUint32		format		= GL_RGBA;
+			const deUint32		dataType	= GL_UNSIGNED_BYTE;
+			const int			texW		= 128;
+			const int			texH		= 128;
+			deUint32			tmpTex		= 0;
+			const deUint32		fbo			= fbos[layer];
+			const IVec3&		viewport	= m_texSize;
+			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);
+
+			tcu::fillWithGrid(data.getAccess(), 8, generateRandomColor(rnd), Vec4(0.0f));
+
+			glGenTextures(1, &tmpTex);
+			glBindTexture(GL_TEXTURE_2D, tmpTex);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
+			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());
+
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glViewport(0, 0, viewport.x(), viewport.y());
+			sglr::drawQuad(*getCurrentContext() , texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+			checkError();
+
+			// Render to framebuffer
+			{
+				const Vec3		p0	= Vec3(float(ndx % 2) - 1.0f, float(ndx / 2) - 1.0f, 0.0f);
+				const Vec3		p1	= p0 + Vec3(1.0f, 1.0f, 0.0f);
+
+				glBindFramebuffer(GL_FRAMEBUFFER, 0);
+				glViewport(0, 0, getWidth(), getHeight());
+
+				glActiveTexture(GL_TEXTURE0);
+				glBindTexture(GL_TEXTURE_3D, tex);
+
+				tdTexShader.setDepth((float(layer) + 0.5f) / float(m_texSize.z()));
+				tdTexShader.setUniforms(*getCurrentContext(), tdTexShaderID);
+
+				sglr::drawQuad(*getCurrentContext(), tdTexShaderID, p0, p1);
+				checkError();
+			}
+		}
+
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+private:
+	IVec3			m_texSize;
+};
+
+class FboBlendCase : public FboColorbufferCase
+{
+public:
+	FboBlendCase (Context& context, const char* name, const char* desc, deUint32 format, IVec2 size, deUint32 funcRGB, deUint32 funcAlpha, deUint32 srcRGB, deUint32 dstRGB, deUint32 srcAlpha, deUint32 dstAlpha)
+		: FboColorbufferCase	(context, name, desc, format)
+		, m_size				(size)
+		, m_funcRGB				(funcRGB)
+		, m_funcAlpha			(funcAlpha)
+		, m_srcRGB				(srcRGB)
+		, m_dstRGB				(dstRGB)
+		, m_srcAlpha			(srcAlpha)
+		, m_dstAlpha			(dstAlpha)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_format);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		// \note Assumes floating-point or fixed-point format.
+		tcu::TextureFormat			fboFmt			= glu::mapGLInternalFormat(m_format);
+		Texture2DShader				texShader		(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
+		GradientShader				gradShader		(glu::TYPE_FLOAT_VEC4);
+		deUint32					texShaderID		= getCurrentContext()->createProgram(&texShader);
+		deUint32					gradShaderID	= getCurrentContext()->createProgram(&gradShader);
+		deUint32					fbo				= 0;
+		deUint32					rbo				= 0;
+
+		// Setup shaders
+		texShader.setUniforms (*getCurrentContext(), texShaderID);
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, tcu::Vec4(0.0f), tcu::Vec4(1.0f));
+
+		glGenFramebuffers(1, &fbo);
+		glGenRenderbuffers(1, &rbo);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+		glRenderbufferStorage(GL_RENDERBUFFER, m_format, m_size.x(), m_size.y());
+		checkError();
+
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+		checkError();
+		checkFramebufferStatus(GL_FRAMEBUFFER);
+
+		glViewport(0, 0, m_size.x(), m_size.y());
+
+		// Fill framebuffer with grid pattern.
+		{
+			const deUint32		format		= GL_RGBA;
+			const deUint32		dataType	= GL_UNSIGNED_BYTE;
+			const int			texW		= 128;
+			const int			texH		= 128;
+			deUint32			gridTex		= 0;
+			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);
+
+			tcu::fillWithGrid(data.getAccess(), 8, Vec4(0.2f, 0.7f, 0.1f, 1.0f), Vec4(0.7f, 0.1f, 0.5f, 0.8f));
+
+			glGenTextures(1, &gridTex);
+			glBindTexture(GL_TEXTURE_2D, gridTex);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
+			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());
+
+			sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+
+		// Setup blend.
+		glEnable(GL_BLEND);
+		glBlendEquationSeparate(m_funcRGB, m_funcAlpha);
+		glBlendFuncSeparate(m_srcRGB, m_dstRGB, m_srcAlpha, m_dstAlpha);
+
+		// Render gradient with blend.
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, m_size.x(), m_size.y(), fboFmt, Vec4(1.0f), Vec4(0.0f));
+	}
+
+private:
+	IVec2		m_size;
+	deUint32	m_funcRGB;
+	deUint32	m_funcAlpha;
+	deUint32	m_srcRGB;
+	deUint32	m_dstRGB;
+	deUint32	m_srcAlpha;
+	deUint32	m_dstAlpha;
+};
+
+FboColorTests::FboColorTests (Context& context)
+	: TestCaseGroup(context, "color", "Colorbuffer tests")
+{
+}
+
+FboColorTests::~FboColorTests (void)
+{
+}
+
+void FboColorTests::init (void)
+{
+	static const deUint32 colorFormats[] =
+	{
+		// RGBA formats
+		GL_RGBA32I,
+		GL_RGBA32UI,
+		GL_RGBA16I,
+		GL_RGBA16UI,
+		GL_RGBA8,
+		GL_RGBA8I,
+		GL_RGBA8UI,
+		GL_SRGB8_ALPHA8,
+		GL_RGB10_A2,
+		GL_RGB10_A2UI,
+		GL_RGBA4,
+		GL_RGB5_A1,
+
+		// RGB formats
+		GL_RGB8,
+		GL_RGB565,
+
+		// RG formats
+		GL_RG32I,
+		GL_RG32UI,
+		GL_RG16I,
+		GL_RG16UI,
+		GL_RG8,
+		GL_RG8I,
+		GL_RG8UI,
+
+		// R formats
+		GL_R32I,
+		GL_R32UI,
+		GL_R16I,
+		GL_R16UI,
+		GL_R8,
+		GL_R8I,
+		GL_R8UI,
+
+		// GL_EXT_color_buffer_float
+		GL_RGBA32F,
+		GL_RGBA16F,
+		GL_R11F_G11F_B10F,
+		GL_RG32F,
+		GL_RG16F,
+		GL_R32F,
+		GL_R16F,
+
+		// GL_EXT_color_buffer_half_float
+		GL_RGB16F
+	};
+
+	// .clear
+	{
+		tcu::TestCaseGroup* clearGroup = new tcu::TestCaseGroup(m_testCtx, "clear", "Color clears");
+		addChild(clearGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorFormats); ndx++)
+			clearGroup->addChild(new FboColorClearCase(m_context, getFormatName(colorFormats[ndx]), "", colorFormats[ndx], 129, 117));
+	}
+
+	// .tex2d
+	{
+		tcu::TestCaseGroup* tex2DGroup = new tcu::TestCaseGroup(m_testCtx, "tex2d", "Texture 2D tests");
+		addChild(tex2DGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
+			tex2DGroup->addChild(new FboColorMultiTex2DCase(m_context, getFormatName(colorFormats[fmtNdx]), "",
+															colorFormats[fmtNdx], IVec2(129, 117),
+															colorFormats[fmtNdx], IVec2(99, 128)));
+	}
+
+	// .texcube
+	{
+		tcu::TestCaseGroup* texCubeGroup = new tcu::TestCaseGroup(m_testCtx, "texcube", "Texture cube map tests");
+		addChild(texCubeGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
+			texCubeGroup->addChild(new FboColorTexCubeCase(m_context, getFormatName(colorFormats[fmtNdx]), "",
+														   colorFormats[fmtNdx], IVec2(128, 128)));
+	}
+
+	// .tex2darray
+	{
+		tcu::TestCaseGroup* tex2DArrayGroup = new tcu::TestCaseGroup(m_testCtx, "tex2darray", "Texture 2D array tests");
+		addChild(tex2DArrayGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
+			tex2DArrayGroup->addChild(new FboColorTex2DArrayCase(m_context, getFormatName(colorFormats[fmtNdx]), "",
+																 colorFormats[fmtNdx], IVec3(128, 128, 5)));
+	}
+
+	// .tex3d
+	{
+		tcu::TestCaseGroup* tex3DGroup = new tcu::TestCaseGroup(m_testCtx, "tex3d", "Texture 3D tests");
+		addChild(tex3DGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
+			tex3DGroup->addChild(new FboColorTex3DCase(m_context, getFormatName(colorFormats[fmtNdx]), "",
+													   colorFormats[fmtNdx], IVec3(128, 128, 5)));
+	}
+
+	// .blend
+	{
+		tcu::TestCaseGroup* blendGroup = new tcu::TestCaseGroup(m_testCtx, "blend", "Blending tests");
+		addChild(blendGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
+		{
+			deUint32					format		= colorFormats[fmtNdx];
+			tcu::TextureFormat			texFmt		= glu::mapGLInternalFormat(format);
+			tcu::TextureChannelClass	fmtClass	= tcu::getTextureChannelClass(texFmt.type);
+			string						fmtName		= getFormatName(format);
+
+			if (texFmt.type	== tcu::TextureFormat::FLOAT				||
+				fmtClass	== tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER	||
+				fmtClass	== tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER)
+				continue; // Blending is not supported.
+
+			blendGroup->addChild(new FboBlendCase(m_context, (fmtName + "_src_over").c_str(), "", format, IVec2(127, 111), GL_FUNC_ADD, GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE));
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboColorbufferTests.hpp b/modules/gles3/functional/es3fFboColorbufferTests.hpp
new file mode 100644
index 0000000..42f9abe
--- /dev/null
+++ b/modules/gles3/functional/es3fFboColorbufferTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFBOCOLORBUFFERTESTS_HPP
+#define _ES3FFBOCOLORBUFFERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO colorbuffer tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FboColorTests : public TestCaseGroup
+{
+public:
+						FboColorTests			(Context& context);
+						~FboColorTests			(void);
+
+	void				init					(void);
+
+private:
+						FboColorTests			(const FboColorTests& other);
+	FboColorTests&		operator=				(const FboColorTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFBOCOLORBUFFERTESTS_HPP
diff --git a/modules/gles3/functional/es3fFboCompletenessTests.cpp b/modules/gles3/functional/es3fFboCompletenessTests.cpp
new file mode 100644
index 0000000..e3239d2
--- /dev/null
+++ b/modules/gles3/functional/es3fFboCompletenessTests.cpp
@@ -0,0 +1,483 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer completeness tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboCompletenessTests.hpp"
+
+#include "glsFboCompletenessTests.hpp"
+#include <sstream>
+
+using namespace glw;
+using deqp::gls::Range;
+using namespace deqp::gls::FboUtil;
+using namespace deqp::gls::FboUtil::config;
+namespace fboc = deqp::gls::fboc;
+typedef tcu::TestCase::IterateResult IterateResult;
+using std::string;
+using std::ostringstream;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+static const FormatKey s_es3ColorRenderables[] =
+{
+	// GLES3, 4.4.4: "An internal format is color-renderable if it is one of
+	// the formats from table 3.12 noted as color-renderable..."
+	GL_R8, GL_RG8, GL_RGB8, GL_RGB565, GL_RGBA4, GL_RGB5_A1, GL_RGBA8,
+	GL_RGB10_A2, GL_RGB10_A2UI, GL_SRGB8_ALPHA8,
+	GL_R8I, GL_R8UI, GL_R16I, GL_R16UI, GL_R32I, GL_R32UI,
+	GL_RG8I, GL_RG8UI, GL_RG16I, GL_RG16UI, GL_RG32I, GL_RG32UI,
+	GL_RGBA8I, GL_RGBA8UI, GL_RGBA16I, GL_RGBA16UI, GL_RGBA32I, GL_RGBA32UI,
+};
+
+static const FormatKey s_es3UnsizedColorRenderables[] =
+{
+	// "...or if it is unsized format RGBA or RGB."
+	// See Table 3.3 in GLES3.
+	GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_UNSIGNED_BYTE),
+	GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_UNSIGNED_SHORT_4_4_4_4),
+	GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_UNSIGNED_SHORT_5_5_5_1),
+	GLS_UNSIZED_FORMATKEY(GL_RGB,	GL_UNSIGNED_BYTE),
+	GLS_UNSIZED_FORMATKEY(GL_RGB,	GL_UNSIGNED_SHORT_5_6_5),
+};
+
+static const FormatKey s_es3DepthRenderables[] =
+{
+	// GLES3, 4.4.4: "An internal format is depth-renderable if it is one of
+	// the formats from table 3.13."
+	GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32F,
+	GL_DEPTH24_STENCIL8, GL_DEPTH32F_STENCIL8,
+};
+
+static const FormatKey s_es3StencilRboRenderables[] =
+{
+	// GLES3, 4.4.4: "An internal format is stencil-renderable if it is
+	// STENCIL_INDEX8..."
+	GL_STENCIL_INDEX8,
+};
+
+static const FormatKey s_es3StencilRenderables[] =
+{
+	// "...or one of the formats from table 3.13 whose base internal format is
+	// DEPTH_STENCIL."
+	GL_DEPTH24_STENCIL8, GL_DEPTH32F_STENCIL8,
+};
+
+static const FormatKey s_es3TextureFloatFormats[] =
+{
+	GL_RGBA32F, GL_RGBA16F, GL_R11F_G11F_B10F,
+	GL_RG32F, GL_RG16F, GL_R32F, GL_R16F,
+	GL_RGBA16F, GL_RGB16F, GL_RG16F, GL_R16F,
+};
+
+static const FormatEntry s_es3Formats[] =
+{
+	// Renderbuffers don't support unsized formats
+	{ REQUIRED_RENDERABLE | COLOR_RENDERABLE | TEXTURE_VALID,
+	  GLS_ARRAY_RANGE(s_es3UnsizedColorRenderables) },
+	{ REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID | TEXTURE_VALID,
+	  GLS_ARRAY_RANGE(s_es3ColorRenderables) },
+	{ REQUIRED_RENDERABLE | DEPTH_RENDERABLE | RENDERBUFFER_VALID | TEXTURE_VALID,
+	  GLS_ARRAY_RANGE(s_es3DepthRenderables) },
+	{ REQUIRED_RENDERABLE | STENCIL_RENDERABLE | RENDERBUFFER_VALID,
+	  GLS_ARRAY_RANGE(s_es3StencilRboRenderables) },
+	{ REQUIRED_RENDERABLE | STENCIL_RENDERABLE | RENDERBUFFER_VALID | TEXTURE_VALID,
+	  GLS_ARRAY_RANGE(s_es3StencilRenderables) },
+
+	// These are not color-renderable in vanilla ES3, but we need to mark them
+	// as valid for textures, since EXT_color_buffer_(half_)float brings in
+	// color-renderability and only renderbuffer-validity.
+	{ TEXTURE_VALID,
+	  GLS_ARRAY_RANGE(s_es3TextureFloatFormats) },
+};
+
+// GL_EXT_color_buffer_float
+static const FormatKey s_extColorBufferFloatFormats[] =
+{
+	GL_RGBA32F, GL_RGBA16F, GL_R11F_G11F_B10F, GL_RG32F, GL_RG16F, GL_R32F, GL_R16F,
+};
+
+static const FormatExtEntry s_es3ExtFormats[] =
+{
+	{ "GL_EXT_color_buffer_float",
+	  // These are already texture-valid in ES3, the extension just adds RBO
+	  // support and makes them color-renderable.
+	  REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
+	  GLS_ARRAY_RANGE(s_extColorBufferFloatFormats) },
+};
+
+class ES3Checker : public Checker
+{
+public:
+				ES3Checker	(void)
+					: m_numSamples			(-1)
+					, m_depthStencilImage	(0)
+					, m_depthStencilType	(GL_NONE) {}
+	void		check 		(GLenum attPoint, const Attachment& att, const Image* image);
+
+private:
+	//! The common number of samples of images.
+	GLsizei		m_numSamples;
+
+	//! The common image for depth and stencil attachments.
+	GLuint		m_depthStencilImage;
+	GLenum		m_depthStencilType;
+};
+
+void ES3Checker::check (GLenum attPoint, const Attachment& att, const Image* image)
+{
+	GLsizei imgSamples = imageNumSamples(*image);
+
+	if (m_numSamples == -1)
+	{
+		m_numSamples = imgSamples;
+	}
+	else
+	{
+		// GLES3: "The value of RENDERBUFFER_SAMPLES is the same for all attached
+		// renderbuffers and, if the attached images are a mix of renderbuffers
+		// and textures, the value of RENDERBUFFER_SAMPLES is zero."
+		//
+		// On creating a renderbuffer: "If _samples_ is zero, then
+		// RENDERBUFFER_SAMPLES is set to zero. Otherwise [...] the resulting
+		// value for RENDERBUFFER_SAMPLES is guaranteed to be greater than or
+		// equal to _samples_ and no more than the next larger sample count
+		// supported by the implementation."
+
+		// Either all attachments are zero-sample renderbuffers and/or
+		// textures, or none of them are.
+		require((m_numSamples == 0) == (imgSamples == 0),
+				GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE);
+
+		// If the attachments requested a different number of samples, the
+		// implementation is allowed to report this as incomplete. However, it
+		// is also possible that despite the different requests, the
+		// implementation allocated the same number of samples to both. Hence
+		// reporting the framebuffer as complete is also legal.
+		canRequire(m_numSamples == imgSamples,
+				   GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE);
+	}
+
+	// "Depth and stencil attachments, if present, are the same image."
+	if (attPoint == GL_DEPTH_ATTACHMENT || attPoint == GL_STENCIL_ATTACHMENT)
+	{
+		if (m_depthStencilImage == 0)
+		{
+			m_depthStencilImage = att.imageName;
+			m_depthStencilType = attachmentType(att);
+		}
+		else
+			require(m_depthStencilImage == att.imageName &&
+					m_depthStencilType == attachmentType(att),
+					GL_FRAMEBUFFER_UNSUPPORTED);
+	}
+}
+
+struct NumLayersParams
+{
+	GLenum		textureKind;		//< GL_TEXTURE_3D or GL_TEXTURE_2D_ARRAY
+	GLsizei		numLayers;			//< Number of layers in texture
+	GLsizei		attachmentLayer;	//< Layer referenced by attachment
+
+	static string	getName			(const NumLayersParams& params);
+	static string	getDescription	(const NumLayersParams& params);
+};
+
+string NumLayersParams::getName (const NumLayersParams& params)
+{
+	ostringstream os;
+	const string kindStr = params.textureKind == GL_TEXTURE_3D ? "3d" : "2darr";
+	os << kindStr << "_" << params.numLayers << "_" << params.attachmentLayer;
+	return os.str();
+}
+
+string NumLayersParams::getDescription (const NumLayersParams& params)
+{
+	ostringstream os;
+	const string kindStr = (params.textureKind == GL_TEXTURE_3D
+							? "3D Texture"
+							: "2D Array Texture");
+	os << kindStr + ", "
+	   << params.numLayers << " layers, "
+	   << "attached layer " << params.attachmentLayer << ".";
+	return os.str();
+}
+
+class NumLayersTest : public fboc::ParamTest<NumLayersParams>
+{
+public:
+					NumLayersTest		(fboc::Context& ctx, NumLayersParams param)
+						: fboc::ParamTest<NumLayersParams> (ctx, param) {}
+
+	IterateResult	build				(FboBuilder& builder);
+};
+
+IterateResult NumLayersTest::build (FboBuilder& builder)
+{
+	TextureLayered* texCfg = DE_NULL;
+	const GLenum target = GL_COLOR_ATTACHMENT0;
+
+	switch (m_params.textureKind)
+	{
+		case GL_TEXTURE_3D:
+			texCfg = &builder.makeConfig<Texture3D>();
+			break;
+		case GL_TEXTURE_2D_ARRAY:
+			texCfg = &builder.makeConfig<Texture2DArray>();
+			break;
+		default:
+			DE_ASSERT(!"Impossible case");
+	}
+	texCfg->internalFormat = getDefaultFormat(target, GL_TEXTURE);
+	texCfg->width = 64;
+	texCfg->height = 64;
+	texCfg->numLayers = m_params.numLayers;
+	const GLuint tex = builder.glCreateTexture(*texCfg);
+
+	TextureLayerAttachment* att = &builder.makeConfig<TextureLayerAttachment>();
+	att->layer = m_params.attachmentLayer;
+	att->imageName = tex;
+
+	builder.glAttach(target, att);
+
+	return STOP;
+}
+
+enum
+{
+	SAMPLES_NONE = -2,
+	SAMPLES_TEXTURE = -1
+};
+struct NumSamplesParams
+{
+	// >= 0: renderbuffer with N samples, -1: texture, -2: no attachment
+	GLsizei		numSamples[3];
+
+	static string	getName			(const NumSamplesParams& params);
+	static string	getDescription	(const NumSamplesParams& params);
+};
+
+string NumSamplesParams::getName (const NumSamplesParams& params)
+{
+	ostringstream os;
+	bool first = true;
+	for (const GLsizei* ns	=	DE_ARRAY_BEGIN(params.numSamples);
+		 ns 				!=	DE_ARRAY_END(params.numSamples);
+		 ns++)
+	{
+		if (first)
+			first = false;
+		else
+			os << "_";
+
+		if (*ns == SAMPLES_NONE)
+			os << "none";
+		else if (*ns == SAMPLES_TEXTURE)
+			os << "tex";
+		else
+			os << "rbo" << *ns;
+	}
+	return os.str();
+}
+
+string NumSamplesParams::getDescription (const NumSamplesParams& params)
+{
+	ostringstream os;
+	bool first = true;
+	static const char* const s_names[] = { "color", "depth", "stencil" };
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == DE_LENGTH_OF_ARRAY(params.numSamples));
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_names); i++)
+	{
+		GLsizei ns = params.numSamples[i];
+
+		if (ns == SAMPLES_NONE)
+			continue;
+
+		if (first)
+			first = false;
+		else
+			os << ", ";
+
+		if (ns == SAMPLES_TEXTURE)
+			os << "texture " << s_names[i] << " attachment";
+		else
+			os << ns << "-sample renderbuffer " << s_names[i] << " attachment";
+	}
+	return os.str();
+}
+
+class NumSamplesTest : public fboc::ParamTest<NumSamplesParams>
+{
+public:
+					NumSamplesTest		(fboc::Context& ctx, NumSamplesParams param)
+						: fboc::ParamTest<NumSamplesParams> (ctx, param) {}
+
+	IterateResult	build				(FboBuilder& builder);
+};
+
+IterateResult NumSamplesTest::build (FboBuilder& builder)
+{
+	static const GLenum s_targets[] =
+		{
+			GL_COLOR_ATTACHMENT0,	GL_COLOR_ATTACHMENT1,	GL_DEPTH_ATTACHMENT,
+		};
+	// Non-integer formats for each attachment type.
+	// \todo [2013-12-17 lauri] Add fixed/floating/integer metadata for formats so
+	// we can pick one smartly or maybe try several.
+	static const GLenum s_formats[] =
+		{
+			GL_RGBA8,				GL_RGB565,				GL_DEPTH_COMPONENT24,
+		};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_targets) == DE_LENGTH_OF_ARRAY(m_params.numSamples));
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_targets); i++)
+	{
+		const GLenum target = s_targets[i];
+		const ImageFormat fmt = { s_formats[i], GL_NONE };
+
+		const GLsizei ns = m_params.numSamples[i];
+		if (ns == -2)
+			continue;
+
+		if (ns == -1)
+		{
+			attachTargetToNew(target, GL_TEXTURE, fmt, 64, 64, builder);
+		}
+		else
+		{
+			Renderbuffer& rboCfg = builder.makeConfig<Renderbuffer>();
+			rboCfg.internalFormat = fmt;
+			rboCfg.width = rboCfg.height = 64;
+			rboCfg.numSamples = ns;
+
+			const GLuint rbo = builder.glCreateRbo(rboCfg);
+			// Implementations do not necessarily support sample sizes greater than 1.
+			TCU_CHECK_AND_THROW(NotSupportedError,
+								builder.getError() != GL_INVALID_OPERATION,
+								"Unsupported number of samples");
+			RenderbufferAttachment& att = builder.makeConfig<RenderbufferAttachment>();
+			att.imageName = rbo;
+			builder.glAttach(target, &att);
+		}
+	}
+
+	return STOP;
+}
+
+class ES3CheckerFactory : public CheckerFactory
+{
+public:
+	Checker*			createChecker	(void) { return new ES3Checker(); }
+};
+
+class TestGroup : public TestCaseGroup
+{
+public:
+						TestGroup		(Context& context);
+	void				init			(void);
+private:
+	ES3CheckerFactory	m_checkerFactory;
+	fboc::Context		m_fboc;
+};
+
+void TestGroup::init (void)
+{
+	addChild(m_fboc.createRenderableTests());
+	addChild(m_fboc.createAttachmentTests());
+	addChild(m_fboc.createSizeTests());
+
+	TestCaseGroup* layerTests = new TestCaseGroup(
+		getContext(), "layer", "Tests for layer attachments");
+
+	static const NumLayersParams s_layersParams[] =
+		{ //  textureKind			numLayers	attachmentKind
+			{ GL_TEXTURE_2D_ARRAY,	1,			0 },
+			{ GL_TEXTURE_2D_ARRAY,	1,			3 },
+			{ GL_TEXTURE_2D_ARRAY,	4,			3 },
+			{ GL_TEXTURE_2D_ARRAY,	4,			15 },
+			{ GL_TEXTURE_3D,		1,			0 },
+			{ GL_TEXTURE_3D,		1,			15 },
+			{ GL_TEXTURE_3D,		4,			15 },
+			{ GL_TEXTURE_3D,		64,			15 },
+		};
+
+	for (const NumLayersParams* lp	=	DE_ARRAY_BEGIN(s_layersParams);
+		 lp							!=  DE_ARRAY_END(s_layersParams);
+		 ++lp)
+		layerTests->addChild(new NumLayersTest(m_fboc, *lp));
+
+	addChild(layerTests);
+
+	TestCaseGroup* sampleTests = new TestCaseGroup(
+		getContext(), "samples", "Tests for multisample attachments");
+
+	static const NumSamplesParams s_samplesParams[] =
+	{
+		{ { 0,					SAMPLES_NONE,		SAMPLES_NONE } },
+		{ { 1,					SAMPLES_NONE,		SAMPLES_NONE } },
+		{ { 2,					SAMPLES_NONE,		SAMPLES_NONE } },
+		{ { 0,					SAMPLES_TEXTURE,	SAMPLES_NONE } },
+		{ { 1,					SAMPLES_TEXTURE,	SAMPLES_NONE } },
+		{ { 2,					SAMPLES_TEXTURE,	SAMPLES_NONE } },
+		{ { 2,					1,					SAMPLES_NONE } },
+		{ { 2,					2,					SAMPLES_NONE } },
+		{ { 0,					0,					SAMPLES_TEXTURE } },
+		{ { 1,					2,					0 } },
+		{ { 2,					2,					0 } },
+		{ { 1,					1,					1 } },
+		{ { 1,					2,					4 } },
+	};
+
+	for (const NumSamplesParams* lp	=	DE_ARRAY_BEGIN(s_samplesParams);
+		 lp							!=  DE_ARRAY_END(s_samplesParams);
+		 ++lp)
+		sampleTests->addChild(new NumSamplesTest(m_fboc, *lp));
+
+	addChild(sampleTests);
+}
+
+TestGroup::TestGroup (Context& ctx)
+	: TestCaseGroup		(ctx, "completeness", "Completeness tests")
+	, m_checkerFactory	()
+	, m_fboc			(ctx.getTestContext(), ctx.getRenderContext(), m_checkerFactory)
+{
+	const FormatEntries stdRange = GLS_ARRAY_RANGE(s_es3Formats);
+	const FormatExtEntries extRange = GLS_ARRAY_RANGE(s_es3ExtFormats);
+
+	m_fboc.addFormats(stdRange);
+	m_fboc.addExtFormats(extRange);
+	m_fboc.setHaveMulticolorAtts(true); // Vanilla ES3 has multiple color attachments
+}
+
+tcu::TestCaseGroup* createFboCompletenessTests (Context& context)
+{
+	return new TestGroup(context);
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboCompletenessTests.hpp b/modules/gles3/functional/es3fFboCompletenessTests.hpp
new file mode 100644
index 0000000..dab7fbc
--- /dev/null
+++ b/modules/gles3/functional/es3fFboCompletenessTests.hpp
@@ -0,0 +1,41 @@
+#ifndef _ES3FFBOCOMPLETENESSTESTS_HPP
+#define _ES3FFBOCOMPLETENESSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer completeness tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+tcu::TestCaseGroup* createFboCompletenessTests (Context& context);
+
+} // Functioal
+} // gles3
+} // deqp
+
+#endif // _ES3FFBOCOMPLETENESSTESTS_HPP
diff --git a/modules/gles3/functional/es3fFboDepthbufferTests.cpp b/modules/gles3/functional/es3fFboDepthbufferTests.cpp
new file mode 100644
index 0000000..1239f22
--- /dev/null
+++ b/modules/gles3/functional/es3fFboDepthbufferTests.cpp
@@ -0,0 +1,357 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO depthbuffer tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboDepthbufferTests.hpp"
+#include "es3fFboTestCase.hpp"
+#include "es3fFboTestUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "sglrContextUtil.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::UVec4;
+using namespace FboTestUtil;
+
+class BasicFboDepthCase : public FboTestCase
+{
+public:
+	BasicFboDepthCase (Context& context, const char* name, const char* desc, deUint32 format, int width, int height)
+		: FboTestCase	(context, name, desc)
+		, m_format		(format)
+		, m_width		(width)
+		, m_height		(height)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_format);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		const deUint32	colorFormat		= GL_RGBA8;
+		deUint32		fbo				= 0;
+		deUint32		colorRbo		= 0;
+		deUint32		depthRbo		= 0;
+		GradientShader	gradShader		(glu::TYPE_FLOAT_VEC4);
+		Texture2DShader	texShader		(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
+		deUint32		gradShaderID	= getCurrentContext()->createProgram(&gradShader);
+		deUint32		texShaderID		= getCurrentContext()->createProgram(&texShader);
+		float			clearDepth		= 1.0f;
+
+		// Setup shaders
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, tcu::Vec4(0.0f), tcu::Vec4(1.0f));
+		texShader.setUniforms (*getCurrentContext(), texShaderID);
+
+		// Setup FBO
+
+		glGenFramebuffers(1, &fbo);
+		glGenRenderbuffers(1, &colorRbo);
+		glGenRenderbuffers(1, &depthRbo);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, colorRbo);
+		glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, m_width, m_height);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, depthRbo);
+		glRenderbufferStorage(GL_RENDERBUFFER, m_format, m_width, m_height);
+
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRbo);
+		checkError();
+		checkFramebufferStatus(GL_FRAMEBUFFER);
+
+		glViewport(0, 0, m_width, m_height);
+
+		// Clear depth to 1
+		glClearBufferfv(GL_DEPTH, 0, &clearDepth);
+
+		// Render gradient with depth = [-1..1]
+		glEnable(GL_DEPTH_TEST);
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		// Render grid pattern with depth = 0
+		{
+			const deUint32		format		= GL_RGBA;
+			const deUint32		dataType	= GL_UNSIGNED_BYTE;
+			const int			texW		= 128;
+			const int			texH		= 128;
+			deUint32			gridTex		= 0;
+			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);
+
+			tcu::fillWithGrid(data.getAccess(), 8, Vec4(0.2f, 0.7f, 0.1f, 1.0f), Vec4(0.7f, 0.1f, 0.5f, 0.8f));
+
+			glGenTextures(1, &gridTex);
+			glBindTexture(GL_TEXTURE_2D, gridTex);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
+			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());
+
+			sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+
+		// Read results.
+		readPixels(dst, 0, 0, m_width, m_height, glu::mapGLInternalFormat(colorFormat), Vec4(1.0f), Vec4(0.0f));
+	}
+
+private:
+	deUint32		m_format;
+	int				m_width;
+	int				m_height;
+};
+
+class DepthWriteClampCase : public FboTestCase
+{
+public:
+	DepthWriteClampCase (Context& context, const char* name, const char* desc, deUint32 format, int width, int height)
+		: FboTestCase	(context, name, desc)
+		, m_format		(format)
+		, m_width		(width)
+		, m_height		(height)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_format);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		const deUint32			colorFormat			= GL_RGBA8;
+		deUint32				fbo					= 0;
+		deUint32				colorRbo			= 0;
+		deUint32				depthTexture		= 0;
+		glu::TransferFormat		transferFmt			= glu::getTransferFormat(glu::mapGLInternalFormat(m_format));
+
+		DepthGradientShader		depthGradShader		(glu::TYPE_FLOAT_VEC4);
+		const deUint32			depthGradShaderID	= getCurrentContext()->createProgram(&depthGradShader);
+		const float				clearDepth			= 1.0f;
+		const tcu::Vec4			red					(1.0, 0.0, 0.0, 1.0);
+		const tcu::Vec4			green				(0.0, 1.0, 0.0, 1.0);
+
+		// Setup FBO
+
+		glGenFramebuffers(1, &fbo);
+		glGenRenderbuffers(1, &colorRbo);
+		glGenTextures(1, &depthTexture);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, colorRbo);
+		glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, m_width, m_height);
+
+		glBindTexture(GL_TEXTURE_2D, depthTexture);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_format, m_width, m_height, 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
+		checkError();
+		checkFramebufferStatus(GL_FRAMEBUFFER);
+
+		glViewport(0, 0, m_width, m_height);
+
+		// Clear depth to 1
+		glClearBufferfv(GL_DEPTH, 0, &clearDepth);
+
+		// Test that invalid values are not written to the depth buffer
+
+		// Render green quad, depth gradient = [-1..2]
+		glEnable(GL_DEPTH_TEST);
+		glDepthFunc(GL_ALWAYS);
+
+		depthGradShader.setUniforms(*getCurrentContext(), depthGradShaderID, -1.0f, 2.0f, green);
+		sglr::drawQuad(*getCurrentContext(), depthGradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+		glDepthMask(GL_FALSE);
+
+		// Test if any fragment has greater depth than 1; there should be none
+		glDepthFunc(GL_LESS); // (1 < depth) ?
+		depthGradShader.setUniforms(*getCurrentContext(), depthGradShaderID, 1.0f, 1.0f, red);
+		sglr::drawQuad(*getCurrentContext(), depthGradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		// Test if any fragment has smaller depth than 0; there should be none
+		glDepthFunc(GL_GREATER); // (0 > depth) ?
+		depthGradShader.setUniforms(*getCurrentContext(), depthGradShaderID, 0.0f, 0.0f, red);
+		sglr::drawQuad(*getCurrentContext(), depthGradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		// Read results.
+		readPixels(dst, 0, 0, m_width, m_height, glu::mapGLInternalFormat(colorFormat), Vec4(1.0f), Vec4(0.0f));
+	}
+
+private:
+	const deUint32		m_format;
+	const int			m_width;
+	const int			m_height;
+};
+
+class DepthTestClampCase : public FboTestCase
+{
+public:
+	DepthTestClampCase (Context& context, const char* name, const char* desc, deUint32 format, int width, int height)
+		: FboTestCase	(context, name, desc)
+		, m_format		(format)
+		, m_width		(width)
+		, m_height		(height)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_format);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		const deUint32			colorFormat			= GL_RGBA8;
+		deUint32				fbo					= 0;
+		deUint32				colorRbo			= 0;
+		deUint32				depthTexture		= 0;
+		glu::TransferFormat		transferFmt			= glu::getTransferFormat(glu::mapGLInternalFormat(m_format));
+
+		DepthGradientShader		depthGradShader		(glu::TYPE_FLOAT_VEC4);
+		const deUint32			depthGradShaderID	= getCurrentContext()->createProgram(&depthGradShader);
+		const float				clearDepth			= 1.0f;
+		const tcu::Vec4			yellow				(1.0, 1.0, 0.0, 1.0);
+		const tcu::Vec4			green				(0.0, 1.0, 0.0, 1.0);
+
+		// Setup FBO
+
+		glGenFramebuffers(1, &fbo);
+		glGenRenderbuffers(1, &colorRbo);
+		glGenTextures(1, &depthTexture);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, colorRbo);
+		glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, m_width, m_height);
+
+		glBindTexture(GL_TEXTURE_2D, depthTexture);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_format, m_width, m_height, 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
+		checkError();
+		checkFramebufferStatus(GL_FRAMEBUFFER);
+
+		glViewport(0, 0, m_width, m_height);
+
+		// Clear depth to 1
+		glClearBufferfv(GL_DEPTH, 0, &clearDepth);
+
+		// Test values used in depth test are clamped
+
+		// Render green quad, depth gradient = [-1..2]
+		glEnable(GL_DEPTH_TEST);
+		glDepthFunc(GL_ALWAYS);
+
+		depthGradShader.setUniforms(*getCurrentContext(), depthGradShaderID, -1.0f, 2.0f, green);
+		sglr::drawQuad(*getCurrentContext(), depthGradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		// Render yellow quad, depth gradient = [-0.5..3]. Gradients have equal values only outside [0, 1] range due to clamping
+		glDepthFunc(GL_EQUAL);
+		depthGradShader.setUniforms(*getCurrentContext(), depthGradShaderID, -0.5f, 3.0f, yellow);
+		sglr::drawQuad(*getCurrentContext(), depthGradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		// Read results.
+		readPixels(dst, 0, 0, m_width, m_height, glu::mapGLInternalFormat(colorFormat), Vec4(1.0f), Vec4(0.0f));
+	}
+
+private:
+	const deUint32		m_format;
+	const int			m_width;
+	const int			m_height;
+};
+
+FboDepthTests::FboDepthTests (Context& context)
+	: TestCaseGroup(context, "depth", "Depth tests")
+{
+}
+
+FboDepthTests::~FboDepthTests (void)
+{
+}
+
+void FboDepthTests::init (void)
+{
+	static const deUint32 depthFormats[] =
+	{
+		GL_DEPTH_COMPONENT32F,
+		GL_DEPTH_COMPONENT24,
+		GL_DEPTH_COMPONENT16,
+		GL_DEPTH32F_STENCIL8,
+		GL_DEPTH24_STENCIL8
+	};
+
+	// .basic
+	{
+		tcu::TestCaseGroup* basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic depth tests");
+		addChild(basicGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthFormats); fmtNdx++)
+			basicGroup->addChild(new BasicFboDepthCase(m_context, getFormatName(depthFormats[fmtNdx]), "", depthFormats[fmtNdx], 119, 127));
+	}
+
+	// .depth_write_clamp
+	{
+		tcu::TestCaseGroup* depthClampGroup = new tcu::TestCaseGroup(m_testCtx, "depth_write_clamp", "Depth write clamping tests");
+		addChild(depthClampGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthFormats); fmtNdx++)
+			depthClampGroup->addChild(new DepthWriteClampCase(m_context, getFormatName(depthFormats[fmtNdx]), "", depthFormats[fmtNdx], 119, 127));
+	}
+
+	// .depth_test_clamp
+	{
+		tcu::TestCaseGroup* depthClampGroup = new tcu::TestCaseGroup(m_testCtx, "depth_test_clamp", "Depth test value clamping tests");
+		addChild(depthClampGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthFormats); fmtNdx++)
+			depthClampGroup->addChild(new DepthTestClampCase(m_context, getFormatName(depthFormats[fmtNdx]), "", depthFormats[fmtNdx], 119, 127));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboDepthbufferTests.hpp b/modules/gles3/functional/es3fFboDepthbufferTests.hpp
new file mode 100644
index 0000000..7b2713d
--- /dev/null
+++ b/modules/gles3/functional/es3fFboDepthbufferTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFBODEPTHBUFFERTESTS_HPP
+#define _ES3FFBODEPTHBUFFERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO depthbuffer tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FboDepthTests : public TestCaseGroup
+{
+public:
+						FboDepthTests		(Context& context);
+						~FboDepthTests		(void);
+
+	void				init				(void);
+
+private:
+						FboDepthTests		(const FboDepthTests& other);
+	FboDepthTests&		operator=			(const FboDepthTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFBODEPTHBUFFERTESTS_HPP
diff --git a/modules/gles3/functional/es3fFboInvalidateTests.cpp b/modules/gles3/functional/es3fFboInvalidateTests.cpp
new file mode 100644
index 0000000..4a02c29
--- /dev/null
+++ b/modules/gles3/functional/es3fFboInvalidateTests.cpp
@@ -0,0 +1,1539 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO invalidate tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboInvalidateTests.hpp"
+#include "es3fFboTestCase.hpp"
+#include "es3fFboTestUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTextureUtil.hpp"
+#include "sglrContextUtil.hpp"
+
+#include "glwEnums.hpp"
+
+#include <algorithm>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::UVec4;
+using namespace FboTestUtil;
+
+static std::vector<deUint32> getDefaultFBDiscardAttachments (deUint32 discardBufferBits)
+{
+	vector<deUint32> attachments;
+
+	if (discardBufferBits & GL_COLOR_BUFFER_BIT)
+		attachments.push_back(GL_COLOR);
+
+	if (discardBufferBits & GL_DEPTH_BUFFER_BIT)
+		attachments.push_back(GL_DEPTH);
+
+	if (discardBufferBits & GL_STENCIL_BUFFER_BIT)
+		attachments.push_back(GL_STENCIL);
+
+	return attachments;
+}
+
+static std::vector<deUint32> getFBODiscardAttachments (deUint32 discardBufferBits)
+{
+	vector<deUint32> attachments;
+
+	if (discardBufferBits & GL_COLOR_BUFFER_BIT)
+		attachments.push_back(GL_COLOR_ATTACHMENT0);
+
+	// \note DEPTH_STENCIL_ATTACHMENT is allowed when discarding FBO, but not with default FB
+	if ((discardBufferBits & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)) == (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT))
+		attachments.push_back(GL_DEPTH_STENCIL_ATTACHMENT);
+	else if (discardBufferBits & GL_DEPTH_BUFFER_BIT)
+		attachments.push_back(GL_DEPTH_ATTACHMENT);
+	else if (discardBufferBits & GL_STENCIL_BUFFER_BIT)
+		attachments.push_back(GL_STENCIL_ATTACHMENT);
+
+	return attachments;
+}
+
+static inline bool hasAttachment (const std::vector<deUint32>& attachmentList, deUint32 attachment)
+{
+	return std::find(attachmentList.begin(), attachmentList.end(), attachment) != attachmentList.end();
+}
+
+static deUint32 getCompatibleColorFormat (const tcu::RenderTarget& renderTargetInfo)
+{
+	const tcu::PixelFormat& pxFmt = renderTargetInfo.getPixelFormat();
+	DE_ASSERT(de::inBounds(pxFmt.redBits,	0, 0xff) &&
+			  de::inBounds(pxFmt.greenBits,	0, 0xff) &&
+			  de::inBounds(pxFmt.blueBits,	0, 0xff) &&
+			  de::inBounds(pxFmt.alphaBits,	0, 0xff));
+
+#define PACK_FMT(R, G, B, A) (((R) << 24) | ((G) << 16) | ((B) << 8) | (A))
+
+	// \note [pyry] This may not hold true on some implementations - best effort guess only.
+	switch (PACK_FMT(pxFmt.redBits, pxFmt.greenBits, pxFmt.blueBits, pxFmt.alphaBits))
+	{
+		case PACK_FMT(8,8,8,8):		return GL_RGBA8;
+		case PACK_FMT(8,8,8,0):		return GL_RGB8;
+		case PACK_FMT(4,4,4,4):		return GL_RGBA4;
+		case PACK_FMT(5,5,5,1):		return GL_RGB5_A1;
+		case PACK_FMT(5,6,5,0):		return GL_RGB565;
+		default:					return GL_NONE;
+	}
+
+#undef PACK_FMT
+}
+
+static deUint32 getCompatibleDepthStencilFormat (const tcu::RenderTarget& renderTargetInfo)
+{
+	const int	depthBits		= renderTargetInfo.getDepthBits();
+	const int	stencilBits		= renderTargetInfo.getStencilBits();
+	const bool	hasDepth		= depthBits > 0;
+	const bool	hasStencil		= stencilBits > 0;
+
+	if (!hasDepth || !hasStencil || (stencilBits != 8))
+		return GL_NONE;
+
+	if (depthBits == 32)
+		return GL_DEPTH32F_STENCIL8;
+	else if (depthBits == 24)
+		return GL_DEPTH24_STENCIL8;
+	else
+		return GL_NONE;
+}
+
+class InvalidateDefaultFramebufferRenderCase : public FboTestCase
+{
+public:
+	InvalidateDefaultFramebufferRenderCase (Context& context, const char* name, const char* description, deUint32 buffers, deUint32 fboTarget = GL_FRAMEBUFFER)
+		: FboTestCase	(context, name, description)
+		, m_buffers		(buffers)
+		, m_fboTarget	(fboTarget)
+	{
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		FlatColorShader		flatShader	(glu::TYPE_FLOAT_VEC4);
+		vector<deUint32>	attachments	= getDefaultFBDiscardAttachments(m_buffers);
+		deUint32			flatShaderID= getCurrentContext()->createProgram(&flatShader);
+
+		glClearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		glInvalidateFramebuffer(m_fboTarget, (int)attachments.size(), attachments.empty() ? DE_NULL : &attachments[0]);
+
+		if ((m_buffers & GL_COLOR_BUFFER_BIT) != 0)
+		{
+			// Color was not preserved - fill with green.
+			glDisable(GL_DEPTH_TEST);
+			glDisable(GL_STENCIL_TEST);
+
+			flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+			sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+			glEnable(GL_DEPTH_TEST);
+			glEnable(GL_STENCIL_TEST);
+		}
+
+		if ((m_buffers & GL_DEPTH_BUFFER_BIT) != 0)
+		{
+			// Depth was not preserved.
+			glDepthFunc(GL_ALWAYS);
+		}
+
+		if ((m_buffers & GL_STENCIL_BUFFER_BIT) == 0)
+		{
+			// Stencil was preserved.
+			glStencilFunc(GL_EQUAL, 1, 0xff);
+		}
+
+		glEnable		(GL_BLEND);
+		glBlendFunc		(GL_ONE, GL_ONE);
+		glBlendEquation	(GL_FUNC_ADD);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+private:
+	deUint32 m_buffers;
+	deUint32 m_fboTarget;
+};
+
+class InvalidateDefaultFramebufferBindCase : public FboTestCase
+{
+public:
+	InvalidateDefaultFramebufferBindCase (Context& context, const char* name, const char* description, deUint32 buffers)
+		: FboTestCase	(context, name, description)
+		, m_buffers		(buffers)
+	{
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		deUint32			fbo			= 0;
+		deUint32			tex			= 0;
+		FlatColorShader		flatShader	(glu::TYPE_FLOAT_VEC4);
+		Texture2DShader		texShader	(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
+		GradientShader		gradShader	(glu::TYPE_FLOAT_VEC4);
+		vector<deUint32>	attachments	= getDefaultFBDiscardAttachments(m_buffers);
+		deUint32			flatShaderID= getCurrentContext()->createProgram(&flatShader);
+		deUint32			texShaderID = getCurrentContext()->createProgram(&texShader);
+		deUint32			gradShaderID= getCurrentContext()->createProgram(&gradShader);
+
+		glClearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		// Create fbo.
+		glGenFramebuffers		(1, &fbo);
+		glGenTextures			(1, &tex);
+		glBindTexture			(GL_TEXTURE_2D, tex);
+		glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, getWidth(), getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+		glTexParameteri			(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+		glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+		glBindTexture			(GL_TEXTURE_2D, 0);
+		checkFramebufferStatus	(GL_FRAMEBUFFER);
+
+		glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		glInvalidateFramebuffer(GL_FRAMEBUFFER, (int)attachments.size(), attachments.empty() ? DE_NULL : &attachments[0]);
+
+		// Switch to fbo and render gradient into it.
+		glDisable			(GL_DEPTH_TEST);
+		glDisable			(GL_STENCIL_TEST);
+		glBindFramebuffer	(GL_FRAMEBUFFER, fbo);
+
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, Vec4(0.0f), Vec4(1.0f));
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		// Restore default fbo.
+		glBindFramebuffer	(GL_FRAMEBUFFER, 0);
+
+		if ((m_buffers & GL_COLOR_BUFFER_BIT) != 0)
+		{
+			// Color was not preserved - fill with green.
+			flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+			sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+
+		if ((m_buffers & GL_DEPTH_BUFFER_BIT) != 0)
+		{
+			// Depth was not preserved.
+			glDepthFunc(GL_ALWAYS);
+		}
+
+		if ((m_buffers & GL_STENCIL_BUFFER_BIT) == 0)
+		{
+			// Stencil was preserved.
+			glStencilFunc(GL_EQUAL, 1, 0xff);
+		}
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glEnable		(GL_BLEND);
+		glBlendFunc		(GL_ONE, GL_ONE);
+		glBlendEquation	(GL_FUNC_ADD);
+		glBindTexture	(GL_TEXTURE_2D, tex);
+
+		texShader.setUniforms(*getCurrentContext(), texShaderID);
+		sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+private:
+	deUint32 m_buffers;
+};
+
+class InvalidateDefaultSubFramebufferRenderCase : public FboTestCase
+{
+public:
+	InvalidateDefaultSubFramebufferRenderCase (Context& context, const char* name, const char* description, deUint32 buffers)
+		: FboTestCase	(context, name, description)
+		, m_buffers		(buffers)
+	{
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		int					invalidateX		= getWidth()	/ 4;
+		int					invalidateY		= getHeight()	/ 4;
+		int					invalidateW		= getWidth()	/ 2;
+		int					invalidateH		= getHeight()	/ 2;
+		FlatColorShader		flatShader		(glu::TYPE_FLOAT_VEC4);
+		vector<deUint32>	attachments		= getDefaultFBDiscardAttachments(m_buffers);
+		deUint32			flatShaderID	= getCurrentContext()->createProgram(&flatShader);
+
+		glClearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		glInvalidateSubFramebuffer(GL_FRAMEBUFFER, (int)attachments.size(), &attachments[0], invalidateX, invalidateY, invalidateW, invalidateH);
+
+		// Clear invalidated buffers.
+		glClearColor	(0.0f, 1.0f, 0.0f, 1.0f);
+		glClearStencil	(1);
+		glScissor		(invalidateX, invalidateY, invalidateW, invalidateH);
+		glEnable		(GL_SCISSOR_TEST);
+		glClear			(m_buffers);
+		glDisable		(GL_SCISSOR_TEST);
+
+		glEnable		(GL_BLEND);
+		glBlendFunc		(GL_ONE, GL_ONE);
+		glBlendEquation	(GL_FUNC_ADD);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+private:
+	deUint32 m_buffers;
+};
+
+class InvalidateDefaultSubFramebufferBindCase : public FboTestCase
+{
+public:
+	InvalidateDefaultSubFramebufferBindCase (Context& context, const char* name, const char* description, deUint32 buffers)
+		: FboTestCase	(context, name, description)
+		, m_buffers		(buffers)
+	{
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		deUint32			fbo				= 0;
+		deUint32			tex				= 0;
+		FlatColorShader		flatShader		(glu::TYPE_FLOAT_VEC4);
+		Texture2DShader		texShader		(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
+		GradientShader		gradShader		(glu::TYPE_FLOAT_VEC4);
+		vector<deUint32>	attachments		= getDefaultFBDiscardAttachments(m_buffers);
+		deUint32			flatShaderID	= getCurrentContext()->createProgram(&flatShader);
+		deUint32			texShaderID		= getCurrentContext()->createProgram(&texShader);
+		deUint32			gradShaderID	= getCurrentContext()->createProgram(&gradShader);
+
+		int				invalidateX		= getWidth()	/ 4;
+		int				invalidateY		= getHeight()	/ 4;
+		int				invalidateW		= getWidth()	/ 2;
+		int				invalidateH		= getHeight()	/ 2;
+
+
+		flatShader.setColor   (*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+		texShader.setUniforms (*getCurrentContext(), texShaderID);
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, Vec4(0.0f), Vec4(1.0f));
+
+		glClearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		// Create fbo.
+		glGenFramebuffers		(1, &fbo);
+		glGenTextures			(1, &tex);
+		glBindTexture			(GL_TEXTURE_2D, tex);
+		glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, getWidth(), getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+		glTexParameteri			(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+		glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+		glBindTexture			(GL_TEXTURE_2D, 0);
+		checkFramebufferStatus	(GL_FRAMEBUFFER);
+
+		glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		glInvalidateSubFramebuffer(GL_FRAMEBUFFER, (int)attachments.size(), &attachments[0], invalidateX, invalidateY, invalidateW, invalidateH);
+
+		// Switch to fbo and render gradient into it.
+		glDisable			(GL_DEPTH_TEST);
+		glDisable			(GL_STENCIL_TEST);
+		glBindFramebuffer	(GL_FRAMEBUFFER, fbo);
+
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		// Restore default fbo.
+		glBindFramebuffer	(GL_FRAMEBUFFER, 0);
+
+		// Clear invalidated buffers.
+		glClearColor	(0.0f, 1.0f, 0.0f, 1.0f);
+		glClearStencil	(1);
+		glScissor		(invalidateX, invalidateY, invalidateW, invalidateH);
+		glEnable		(GL_SCISSOR_TEST);
+		glClear			(m_buffers);
+		glDisable		(GL_SCISSOR_TEST);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glEnable		(GL_BLEND);
+		glBlendFunc		(GL_ONE, GL_ONE);
+		glBlendEquation	(GL_FUNC_ADD);
+		glBindTexture	(GL_TEXTURE_2D, tex);
+
+		sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+private:
+	deUint32 m_buffers;
+};
+
+class InvalidateFboRenderCase : public FboTestCase
+{
+public:
+	InvalidateFboRenderCase (Context& context, const char* name, const char* description, deUint32 colorFmt, deUint32 depthStencilFmt, deUint32 invalidateBuffers)
+		: FboTestCase			(context, name, description)
+		, m_colorFmt			(colorFmt)
+		, m_depthStencilFmt		(depthStencilFmt)
+		, m_invalidateBuffers	(invalidateBuffers)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		if (m_colorFmt != GL_NONE)			checkFormatSupport(m_colorFmt);
+		if (m_depthStencilFmt != GL_NONE)	checkFormatSupport(m_depthStencilFmt);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		tcu::TextureFormat		colorFmt				= glu::mapGLInternalFormat(m_colorFmt);
+		tcu::TextureFormat		depthStencilFmt			= m_depthStencilFmt != GL_NONE ? glu::mapGLInternalFormat(m_depthStencilFmt) : tcu::TextureFormat();
+		tcu::TextureFormatInfo	colorFmtInfo			= tcu::getTextureFormatInfo(colorFmt);
+		bool					depth					= depthStencilFmt.order == tcu::TextureFormat::D || depthStencilFmt.order == tcu::TextureFormat::DS;
+		bool					stencil					= depthStencilFmt.order == tcu::TextureFormat::S || depthStencilFmt.order == tcu::TextureFormat::DS;
+		const tcu::Vec4&		cBias					= colorFmtInfo.valueMin;
+		tcu::Vec4				cScale					= colorFmtInfo.valueMax-colorFmtInfo.valueMin;
+		deUint32				fbo						= 0;
+		deUint32				colorRbo				= 0;
+		deUint32				depthStencilRbo			= 0;
+		FlatColorShader			flatShader				(glu::TYPE_FLOAT_VEC4);
+		vector<deUint32>		attachments				= getFBODiscardAttachments(m_invalidateBuffers);
+		deUint32				flatShaderID			= getCurrentContext()->createProgram(&flatShader);
+
+		// Create fbo.
+		glGenRenderbuffers		(1, &colorRbo);
+		glBindRenderbuffer		(GL_RENDERBUFFER, colorRbo);
+		glRenderbufferStorage	(GL_RENDERBUFFER, m_colorFmt, getWidth(), getHeight());
+
+		if (m_depthStencilFmt != GL_NONE)
+		{
+			glGenRenderbuffers		(1, &depthStencilRbo);
+			glBindRenderbuffer		(GL_RENDERBUFFER, depthStencilRbo);
+			glRenderbufferStorage	(GL_RENDERBUFFER, m_depthStencilFmt, getWidth(), getHeight());
+		}
+
+		glGenFramebuffers			(1, &fbo);
+		glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+
+		if (depth)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+		if (stencil)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+		checkFramebufferStatus		(GL_FRAMEBUFFER);
+
+		glClearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias);
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		glInvalidateFramebuffer(GL_FRAMEBUFFER, (int)attachments.size(), attachments.empty() ? DE_NULL : &attachments[0]);
+
+		if ((m_invalidateBuffers & GL_COLOR_BUFFER_BIT) != 0)
+		{
+			// Color was not preserved - fill with green.
+			glDisable(GL_DEPTH_TEST);
+			glDisable(GL_STENCIL_TEST);
+
+			flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias);
+			sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+			glEnable(GL_DEPTH_TEST);
+			glEnable(GL_STENCIL_TEST);
+		}
+
+		if ((m_invalidateBuffers & GL_DEPTH_BUFFER_BIT) != 0)
+		{
+			// Depth was not preserved.
+			glDepthFunc(GL_ALWAYS);
+		}
+
+		if ((m_invalidateBuffers & GL_STENCIL_BUFFER_BIT) == 0)
+		{
+			// Stencil was preserved.
+			glStencilFunc(GL_EQUAL, 1, 0xff);
+		}
+
+		glEnable		(GL_BLEND);
+		glBlendFunc		(GL_ONE, GL_ONE);
+		glBlendEquation	(GL_FUNC_ADD);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f)*cScale + cBias);
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, getWidth(), getHeight(), colorFmt, colorFmtInfo.lookupScale, colorFmtInfo.lookupBias);
+	}
+
+private:
+	deUint32	m_colorFmt;
+	deUint32	m_depthStencilFmt;
+	deUint32	m_invalidateBuffers;
+};
+
+class InvalidateFboUnbindReadCase : public FboTestCase
+{
+public:
+	InvalidateFboUnbindReadCase (Context& context, const char* name, const char* description, deUint32 colorFmt, deUint32 depthStencilFmt, deUint32 invalidateBuffers)
+		: FboTestCase			(context, name, description)
+		, m_colorFmt			(colorFmt)
+		, m_depthStencilFmt		(depthStencilFmt)
+		, m_invalidateBuffers	(invalidateBuffers)
+	{
+		DE_ASSERT((m_invalidateBuffers & (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)) != (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT));
+	}
+
+protected:
+	void preCheck (void)
+	{
+		if (m_colorFmt != GL_NONE)			checkFormatSupport(m_colorFmt);
+		if (m_depthStencilFmt != GL_NONE)	checkFormatSupport(m_depthStencilFmt);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		tcu::TextureFormat		colorFmt				= glu::mapGLInternalFormat(m_colorFmt);
+		tcu::TextureFormat		depthStencilFmt			= m_depthStencilFmt != GL_NONE ? glu::mapGLInternalFormat(m_depthStencilFmt) : tcu::TextureFormat();
+		tcu::TextureFormatInfo	colorFmtInfo			= tcu::getTextureFormatInfo(colorFmt);
+		bool					depth					= depthStencilFmt.order == tcu::TextureFormat::D || depthStencilFmt.order == tcu::TextureFormat::DS;
+		bool					stencil					= depthStencilFmt.order == tcu::TextureFormat::S || depthStencilFmt.order == tcu::TextureFormat::DS;
+		deUint32				fbo						= 0;
+		deUint32				colorTex				= 0;
+		deUint32				depthStencilTex			= 0;
+		GradientShader			gradShader				(getFragmentOutputType(colorFmt));
+		vector<deUint32>		attachments				= getFBODiscardAttachments(m_invalidateBuffers);
+		deUint32				gradShaderID			= getCurrentContext()->createProgram(&gradShader);
+
+		// Create fbo.
+		{
+			glu::TransferFormat transferFmt = glu::getTransferFormat(colorFmt);
+
+			glGenTextures	(1, &colorTex);
+			glBindTexture	(GL_TEXTURE_2D, colorTex);
+			glTexImage2D	(GL_TEXTURE_2D, 0, m_colorFmt, getWidth(), getHeight(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+			glTexParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		}
+
+		if (m_depthStencilFmt != GL_NONE)
+		{
+			glu::TransferFormat transferFmt = glu::getTransferFormat(depthStencilFmt);
+
+			glGenTextures	(1, &depthStencilTex);
+			glBindTexture	(GL_TEXTURE_2D, depthStencilTex);
+			glTexImage2D	(GL_TEXTURE_2D, 0, m_depthStencilFmt, getWidth(), getHeight(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+			glTexParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+			glTexParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		}
+
+		glGenFramebuffers		(1, &fbo);
+		glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+		glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
+
+		if (depth)
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthStencilTex, 0);
+
+		if (stencil)
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthStencilTex, 0);
+
+		checkFramebufferStatus		(GL_FRAMEBUFFER);
+
+		glClearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, colorFmtInfo.valueMin, colorFmtInfo.valueMax);
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		glInvalidateFramebuffer(GL_FRAMEBUFFER, (int)attachments.size(), &attachments[0]);
+
+		glBindFramebuffer	(GL_FRAMEBUFFER, 0);
+		glDisable			(GL_DEPTH_TEST);
+		glDisable			(GL_STENCIL_TEST);
+
+		if ((m_invalidateBuffers & GL_DEPTH_BUFFER_BIT) != 0)
+		{
+			// Render color.
+			Texture2DShader texShader(DataTypes() << glu::getSampler2DType(colorFmt), glu::TYPE_FLOAT_VEC4);
+			deUint32		texShaderID = getCurrentContext()->createProgram(&texShader);
+
+			texShader.setTexScaleBias(0, colorFmtInfo.lookupScale, colorFmtInfo.lookupBias);
+			texShader.setUniforms(*getCurrentContext(), texShaderID);
+
+			glBindTexture(GL_TEXTURE_2D, colorTex);
+			sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+		else
+		{
+			// Render depth.
+			Texture2DShader texShader(DataTypes() << glu::getSampler2DType(depthStencilFmt), glu::TYPE_FLOAT_VEC4);
+			deUint32		texShaderID = getCurrentContext()->createProgram(&texShader);
+
+			texShader.setUniforms(*getCurrentContext(), texShaderID);
+
+			glBindTexture(GL_TEXTURE_2D, depthStencilTex);
+			sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+private:
+	deUint32	m_colorFmt;
+	deUint32	m_depthStencilFmt;
+	deUint32	m_invalidateBuffers;
+};
+
+class InvalidateFboUnbindBlitCase : public FboTestCase
+{
+public:
+	InvalidateFboUnbindBlitCase (Context& context, const char* name, const char* description, int numSamples, deUint32 invalidateBuffers)
+		: FboTestCase			(context, name, description, numSamples > 0)	// \note Use fullscreen viewport when multisampling - we can't allow GLES3Context do its
+																				//		 behing-the-scenes viewport position randomization, because with glBlitFramebuffer,
+																				//		 source and destination rectangles must match when multisampling.
+		, m_colorFmt			(0)
+		, m_depthStencilFmt		(0)
+		, m_numSamples			(numSamples)
+		, m_invalidateBuffers	(invalidateBuffers)
+	{
+		// Figure out formats that are compatible with default framebuffer.
+		m_colorFmt			= getCompatibleColorFormat(m_context.getRenderTarget());
+		m_depthStencilFmt	= getCompatibleDepthStencilFormat(m_context.getRenderTarget());
+	}
+
+protected:
+	void preCheck (void)
+	{
+		if (m_context.getRenderTarget().getNumSamples() > 0)
+			throw tcu::NotSupportedError("Not supported in MSAA config");
+
+		if (m_colorFmt == GL_NONE)
+			throw tcu::NotSupportedError("Unsupported color format");
+
+		if (m_depthStencilFmt == GL_NONE)
+			throw tcu::NotSupportedError("Unsupported depth/stencil format");
+
+		checkFormatSupport(m_colorFmt);
+		checkFormatSupport(m_depthStencilFmt);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		// \note When using fullscreen viewport (when m_numSamples > 0), still only use a 128x128 pixel quad at most.
+		IVec2					quadSizePixels			(m_numSamples == 0 ? getWidth() : de::min(128, getWidth()),
+														 m_numSamples == 0 ? getHeight() : de::min(128, getHeight()));
+		Vec2					quadNDCLeftBottomXY		(-1.0f, -1.0f);
+		Vec2					quadNDCSize				(2.0f*quadSizePixels.x()/getWidth(), 2.0f*quadSizePixels.y()/getHeight());
+		Vec2					quadNDCRightTopXY		= quadNDCLeftBottomXY + quadNDCSize;
+		tcu::TextureFormat		depthStencilFmt			= m_depthStencilFmt != GL_NONE ? glu::mapGLInternalFormat(m_depthStencilFmt) : tcu::TextureFormat();
+		bool					depth					= depthStencilFmt.order == tcu::TextureFormat::D || depthStencilFmt.order == tcu::TextureFormat::DS;
+		bool					stencil					= depthStencilFmt.order == tcu::TextureFormat::S || depthStencilFmt.order == tcu::TextureFormat::DS;
+		deUint32				fbo						= 0;
+		deUint32				colorRbo				= 0;
+		deUint32				depthStencilRbo			= 0;
+		FlatColorShader			flatShader				(glu::TYPE_FLOAT_VEC4);
+		vector<deUint32>		attachments				= getFBODiscardAttachments(m_invalidateBuffers);
+		deUint32				flatShaderID			= getCurrentContext()->createProgram(&flatShader);
+
+		glClearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		// Create fbo.
+		glGenRenderbuffers					(1, &colorRbo);
+		glBindRenderbuffer					(GL_RENDERBUFFER, colorRbo);
+		glRenderbufferStorageMultisample	(GL_RENDERBUFFER, m_numSamples, m_colorFmt, quadSizePixels.x(), quadSizePixels.y());
+
+		if (m_depthStencilFmt != GL_NONE)
+		{
+			glGenRenderbuffers					(1, &depthStencilRbo);
+			glBindRenderbuffer					(GL_RENDERBUFFER, depthStencilRbo);
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, m_numSamples, m_depthStencilFmt, quadSizePixels.x(), quadSizePixels.y());
+		}
+
+		glGenFramebuffers			(1, &fbo);
+		glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+
+		if (depth)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+		if (stencil)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+		checkFramebufferStatus		(GL_FRAMEBUFFER);
+
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(quadNDCLeftBottomXY.x(), quadNDCLeftBottomXY.y(), -1.0f), Vec3(quadNDCRightTopXY.x(), quadNDCRightTopXY.y(), 1.0f));
+
+		glInvalidateFramebuffer(GL_FRAMEBUFFER, (int)attachments.size(), &attachments[0]);
+
+		// Set default framebuffer as draw framebuffer and blit preserved buffers.
+		glBindFramebuffer	(GL_DRAW_FRAMEBUFFER, 0);
+		glBlitFramebuffer	(0, 0, quadSizePixels.x(), quadSizePixels.y(),
+							 0, 0, quadSizePixels.x(), quadSizePixels.y(),
+							 (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT) & ~m_invalidateBuffers, GL_NEAREST);
+		glBindFramebuffer	(GL_READ_FRAMEBUFFER, 0);
+
+		if ((m_invalidateBuffers & GL_COLOR_BUFFER_BIT) != 0)
+		{
+			// Color was not preserved - fill with green.
+			glDisable(GL_DEPTH_TEST);
+			glDisable(GL_STENCIL_TEST);
+
+			flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+			sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(quadNDCLeftBottomXY.x(), quadNDCLeftBottomXY.y(), 0.0f), Vec3(quadNDCRightTopXY.x(), quadNDCRightTopXY.y(), 0.0f));
+
+			glEnable(GL_DEPTH_TEST);
+			glEnable(GL_STENCIL_TEST);
+		}
+
+		if ((m_invalidateBuffers & GL_DEPTH_BUFFER_BIT) != 0)
+		{
+			// Depth was not preserved.
+			glDepthFunc(GL_ALWAYS);
+		}
+
+		if ((m_invalidateBuffers & GL_STENCIL_BUFFER_BIT) == 0)
+		{
+			// Stencil was preserved.
+			glStencilFunc(GL_EQUAL, 1, 0xff);
+		}
+
+		glEnable		(GL_BLEND);
+		glBlendFunc		(GL_ONE, GL_ONE);
+		glBlendEquation	(GL_FUNC_ADD);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(quadNDCLeftBottomXY.x(), quadNDCLeftBottomXY.y(), 0.0f), Vec3(quadNDCRightTopXY.x(), quadNDCRightTopXY.y(), 0.0f));
+
+		readPixels(dst, 0, 0, quadSizePixels.x(), quadSizePixels.y());
+	}
+
+private:
+	deUint32	m_colorFmt;
+	deUint32	m_depthStencilFmt;
+	int			m_numSamples;
+	deUint32	m_invalidateBuffers;
+};
+
+class InvalidateSubFboRenderCase : public FboTestCase
+{
+public:
+	InvalidateSubFboRenderCase (Context& context, const char* name, const char* description, deUint32 colorFmt, deUint32 depthStencilFmt, deUint32 invalidateBuffers)
+		: FboTestCase			(context, name, description)
+		, m_colorFmt			(colorFmt)
+		, m_depthStencilFmt		(depthStencilFmt)
+		, m_invalidateBuffers	(invalidateBuffers)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		if (m_colorFmt != GL_NONE)			checkFormatSupport(m_colorFmt);
+		if (m_depthStencilFmt != GL_NONE)	checkFormatSupport(m_depthStencilFmt);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		tcu::TextureFormat		colorFmt				= glu::mapGLInternalFormat(m_colorFmt);
+		tcu::TextureFormat		depthStencilFmt			= m_depthStencilFmt != GL_NONE ? glu::mapGLInternalFormat(m_depthStencilFmt) : tcu::TextureFormat();
+		tcu::TextureFormatInfo	colorFmtInfo			= tcu::getTextureFormatInfo(colorFmt);
+		bool					depth					= depthStencilFmt.order == tcu::TextureFormat::D || depthStencilFmt.order == tcu::TextureFormat::DS;
+		bool					stencil					= depthStencilFmt.order == tcu::TextureFormat::S || depthStencilFmt.order == tcu::TextureFormat::DS;
+		const tcu::Vec4&		cBias					= colorFmtInfo.valueMin;
+		tcu::Vec4				cScale					= colorFmtInfo.valueMax-colorFmtInfo.valueMin;
+		deUint32				fbo						= 0;
+		deUint32				colorRbo				= 0;
+		deUint32				depthStencilRbo			= 0;
+		int						invalidateX				= getWidth()	/ 4;
+		int						invalidateY				= getHeight()	/ 4;
+		int						invalidateW				= getWidth()	/ 2;
+		int						invalidateH				= getHeight()	/ 2;
+		FlatColorShader			flatShader				(glu::TYPE_FLOAT_VEC4);
+		vector<deUint32>		attachments				= getFBODiscardAttachments(m_invalidateBuffers);
+		deUint32				flatShaderID			= getCurrentContext()->createProgram(&flatShader);
+
+		// Create fbo.
+		glGenRenderbuffers		(1, &colorRbo);
+		glBindRenderbuffer		(GL_RENDERBUFFER, colorRbo);
+		glRenderbufferStorage	(GL_RENDERBUFFER, m_colorFmt, getWidth(), getHeight());
+
+		if (m_depthStencilFmt != GL_NONE)
+		{
+			glGenRenderbuffers		(1, &depthStencilRbo);
+			glBindRenderbuffer		(GL_RENDERBUFFER, depthStencilRbo);
+			glRenderbufferStorage	(GL_RENDERBUFFER, m_depthStencilFmt, getWidth(), getHeight());
+		}
+
+		glGenFramebuffers			(1, &fbo);
+		glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+
+		if (depth)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+		if (stencil)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+		checkFramebufferStatus		(GL_FRAMEBUFFER);
+
+		glClearBufferfv	(GL_COLOR, 0, (Vec4(0.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias).getPtr());
+		glClearBufferfi	(GL_DEPTH_STENCIL, 0, 1.0f, 0);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias);
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		glInvalidateSubFramebuffer(GL_FRAMEBUFFER, (int)attachments.size(), attachments.empty() ? DE_NULL : &attachments[0], invalidateX, invalidateY, invalidateW, invalidateH);
+
+		// Clear invalidated buffers.
+		glScissor	(invalidateX, invalidateY, invalidateW, invalidateH);
+		glEnable	(GL_SCISSOR_TEST);
+
+		if (m_invalidateBuffers & GL_COLOR_BUFFER_BIT)
+			glClearBufferfv(GL_COLOR, 0, (Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias).getPtr());
+
+		glClear		(m_invalidateBuffers & ~GL_COLOR_BUFFER_BIT);
+		glDisable	(GL_SCISSOR_TEST);
+
+		glEnable		(GL_BLEND);
+		glBlendFunc		(GL_ONE, GL_ONE);
+		glBlendEquation	(GL_FUNC_ADD);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f)*cScale + cBias);
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, getWidth(), getHeight(), colorFmt, colorFmtInfo.lookupScale, colorFmtInfo.lookupBias);
+	}
+
+private:
+	deUint32	m_colorFmt;
+	deUint32	m_depthStencilFmt;
+	deUint32	m_invalidateBuffers;
+};
+
+class InvalidateSubFboUnbindReadCase : public FboTestCase
+{
+public:
+	InvalidateSubFboUnbindReadCase (Context& context, const char* name, const char* description, deUint32 colorFmt, deUint32 depthStencilFmt, deUint32 invalidateBuffers)
+		: FboTestCase			(context, name, description)
+		, m_colorFmt			(colorFmt)
+		, m_depthStencilFmt		(depthStencilFmt)
+		, m_invalidateBuffers	(invalidateBuffers)
+	{
+		DE_ASSERT((m_invalidateBuffers & (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)) != (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT));
+	}
+
+protected:
+	void preCheck (void)
+	{
+		if (m_colorFmt != GL_NONE)			checkFormatSupport(m_colorFmt);
+		if (m_depthStencilFmt != GL_NONE)	checkFormatSupport(m_depthStencilFmt);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		tcu::TextureFormat		colorFmt				= glu::mapGLInternalFormat(m_colorFmt);
+		tcu::TextureFormat		depthStencilFmt			= m_depthStencilFmt != GL_NONE ? glu::mapGLInternalFormat(m_depthStencilFmt) : tcu::TextureFormat();
+		tcu::TextureFormatInfo	colorFmtInfo			= tcu::getTextureFormatInfo(colorFmt);
+		bool					depth					= depthStencilFmt.order == tcu::TextureFormat::D || depthStencilFmt.order == tcu::TextureFormat::DS;
+		bool					stencil					= depthStencilFmt.order == tcu::TextureFormat::S || depthStencilFmt.order == tcu::TextureFormat::DS;
+		deUint32				fbo						= 0;
+		deUint32				colorTex				= 0;
+		deUint32				depthStencilTex			= 0;
+		int						invalidateX				= 0;
+		int						invalidateY				= 0;
+		int						invalidateW				= getWidth()/2;
+		int						invalidateH				= getHeight();
+		int						readX					= invalidateW;
+		int						readY					= 0;
+		int						readW					= getWidth()/2;
+		int						readH					= getHeight();
+		GradientShader			gradShader				(getFragmentOutputType(colorFmt));
+		vector<deUint32>		attachments				= getFBODiscardAttachments(m_invalidateBuffers);
+		deUint32				gradShaderID			= getCurrentContext()->createProgram(&gradShader);
+
+		// Create fbo.
+		{
+			glu::TransferFormat transferFmt = glu::getTransferFormat(colorFmt);
+
+			glGenTextures	(1, &colorTex);
+			glBindTexture	(GL_TEXTURE_2D, colorTex);
+			glTexImage2D	(GL_TEXTURE_2D, 0, m_colorFmt, getWidth(), getHeight(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+			glTexParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+			glTexParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		}
+
+		if (m_depthStencilFmt != GL_NONE)
+		{
+			glu::TransferFormat transferFmt = glu::getTransferFormat(depthStencilFmt);
+
+			glGenTextures	(1, &depthStencilTex);
+			glBindTexture	(GL_TEXTURE_2D, depthStencilTex);
+			glTexImage2D	(GL_TEXTURE_2D, 0, m_depthStencilFmt, getWidth(), getHeight(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+			glTexParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+			glTexParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		}
+
+		glGenFramebuffers		(1, &fbo);
+		glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+		glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
+
+		if (depth)
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthStencilTex, 0);
+
+		if (stencil)
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthStencilTex, 0);
+
+		checkFramebufferStatus		(GL_FRAMEBUFFER);
+
+		clearColorBuffer(colorFmt, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+		glClear			(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, colorFmtInfo.valueMin, colorFmtInfo.valueMax);
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		glInvalidateSubFramebuffer(GL_FRAMEBUFFER, (int)attachments.size(), &attachments[0], invalidateX, invalidateY, invalidateW, invalidateH);
+
+		glBindFramebuffer	(GL_FRAMEBUFFER, 0);
+		glDisable			(GL_DEPTH_TEST);
+		glDisable			(GL_STENCIL_TEST);
+
+		glClearColor		(0.25f, 0.5f, 0.75f, 1.0f);
+		glClear				(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		// Limit read area using scissor.
+		glScissor			(readX, readY, readW, readH);
+		glEnable			(GL_SCISSOR_TEST);
+
+		if ((m_invalidateBuffers & GL_COLOR_BUFFER_BIT) != 0)
+		{
+			// Render color.
+			Texture2DShader texShader(DataTypes() << glu::getSampler2DType(colorFmt), glu::TYPE_FLOAT_VEC4);
+			deUint32		texShaderID = getCurrentContext()->createProgram(&texShader);
+
+			texShader.setTexScaleBias(0, colorFmtInfo.lookupScale, colorFmtInfo.lookupBias);
+			texShader.setUniforms(*getCurrentContext(), texShaderID);
+
+			glBindTexture(GL_TEXTURE_2D, colorTex);
+			sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+		else
+		{
+			// Render depth.
+			Texture2DShader texShader(DataTypes() << glu::getSampler2DType(depthStencilFmt), glu::TYPE_FLOAT_VEC4);
+			deUint32		texShaderID = getCurrentContext()->createProgram(&texShader);
+
+			texShader.setUniforms(*getCurrentContext(), texShaderID);
+
+			glBindTexture(GL_TEXTURE_2D, depthStencilTex);
+			sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+	bool compare (const tcu::Surface& reference, const tcu::Surface& result)
+	{
+		const tcu::RGBA threshold (tcu::max(getFormatThreshold(m_colorFmt), tcu::RGBA(12, 12, 12, 12)));
+
+		return tcu::bilinearCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference.getAccess(), result.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
+	}
+
+private:
+	deUint32	m_colorFmt;
+	deUint32	m_depthStencilFmt;
+	deUint32	m_invalidateBuffers;
+};
+
+class InvalidateSubFboUnbindBlitCase : public FboTestCase
+{
+public:
+	InvalidateSubFboUnbindBlitCase (Context& context, const char* name, const char* description, int numSamples, deUint32 invalidateBuffers)
+		: FboTestCase			(context, name, description, numSamples > 0)	// \note Use fullscreen viewport when multisampling - we can't allow GLES3Context do its
+																				//		 behing-the-scenes viewport position randomization, because with glBlitFramebuffer,
+																				//		 source and destination rectangles must match when multisampling.
+		, m_colorFmt			(0)
+		, m_depthStencilFmt		(0)
+		, m_numSamples			(numSamples)
+		, m_invalidateBuffers	(invalidateBuffers)
+	{
+		// Figure out formats that are compatible with default framebuffer.
+		m_colorFmt			= getCompatibleColorFormat(m_context.getRenderTarget());
+		m_depthStencilFmt	= getCompatibleDepthStencilFormat(m_context.getRenderTarget());
+	}
+
+protected:
+	void preCheck (void)
+	{
+		if (m_context.getRenderTarget().getNumSamples() > 0)
+			throw tcu::NotSupportedError("Not supported in MSAA config");
+
+		if (m_colorFmt == GL_NONE)
+			throw tcu::NotSupportedError("Unsupported color format");
+
+		if (m_depthStencilFmt == GL_NONE)
+			throw tcu::NotSupportedError("Unsupported depth/stencil format");
+
+		checkFormatSupport(m_colorFmt);
+		checkFormatSupport(m_depthStencilFmt);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		// \note When using fullscreen viewport (when m_numSamples > 0), still only use a 128x128 pixel quad at most.
+		IVec2					quadSizePixels			(m_numSamples == 0 ? getWidth() : de::min(128, getWidth()),
+														 m_numSamples == 0 ? getHeight() : de::min(128, getHeight()));
+		Vec2					quadNDCLeftBottomXY		(-1.0f, -1.0f);
+		Vec2					quadNDCSize				(2.0f*quadSizePixels.x()/getWidth(), 2.0f*quadSizePixels.y()/getHeight());
+		Vec2					quadNDCRightTopXY		= quadNDCLeftBottomXY + quadNDCSize;
+		tcu::TextureFormat		depthStencilFmt			= m_depthStencilFmt != GL_NONE ? glu::mapGLInternalFormat(m_depthStencilFmt) : tcu::TextureFormat();
+		bool					depth					= depthStencilFmt.order == tcu::TextureFormat::D || depthStencilFmt.order == tcu::TextureFormat::DS;
+		bool					stencil					= depthStencilFmt.order == tcu::TextureFormat::S || depthStencilFmt.order == tcu::TextureFormat::DS;
+		deUint32				fbo						= 0;
+		deUint32				colorRbo				= 0;
+		deUint32				depthStencilRbo			= 0;
+		int						invalidateX				= 0;
+		int						invalidateY				= 0;
+		int						invalidateW				= quadSizePixels.x()/2;
+		int						invalidateH				= quadSizePixels.y();
+		int						blitX0					= invalidateW;
+		int						blitY0					= 0;
+		int						blitX1					= blitX0 + quadSizePixels.x()/2;
+		int						blitY1					= blitY0 + quadSizePixels.y();
+		FlatColorShader			flatShader				(glu::TYPE_FLOAT_VEC4);
+		vector<deUint32>		attachments				= getFBODiscardAttachments(m_invalidateBuffers);
+		deUint32				flatShaderID			= getCurrentContext()->createProgram(&flatShader);
+
+		glClearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		// Create fbo.
+		glGenRenderbuffers					(1, &colorRbo);
+		glBindRenderbuffer					(GL_RENDERBUFFER, colorRbo);
+		glRenderbufferStorageMultisample	(GL_RENDERBUFFER, m_numSamples, m_colorFmt, quadSizePixels.x(), quadSizePixels.y());
+
+		if (m_depthStencilFmt != GL_NONE)
+		{
+			glGenRenderbuffers					(1, &depthStencilRbo);
+			glBindRenderbuffer					(GL_RENDERBUFFER, depthStencilRbo);
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, m_numSamples, m_depthStencilFmt, quadSizePixels.x(), quadSizePixels.y());
+		}
+
+		glGenFramebuffers			(1, &fbo);
+		glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+
+		if (depth)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+		if (stencil)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+		checkFramebufferStatus		(GL_FRAMEBUFFER);
+
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(quadNDCLeftBottomXY.x(), quadNDCLeftBottomXY.y(), -1.0f), Vec3(quadNDCRightTopXY.x(), quadNDCRightTopXY.y(), 1.0f));
+
+		glInvalidateSubFramebuffer(GL_FRAMEBUFFER, (int)attachments.size(), &attachments[0], invalidateX, invalidateY, invalidateW, invalidateH);
+
+		// Set default framebuffer as draw framebuffer and blit preserved buffers.
+		glBindFramebuffer	(GL_DRAW_FRAMEBUFFER, 0);
+		glBlitFramebuffer	(blitX0, blitY0, blitX1, blitY1, blitX0, blitY0, blitX1, blitY1, (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT) & ~m_invalidateBuffers, GL_NEAREST);
+		glBindFramebuffer	(GL_READ_FRAMEBUFFER, 0);
+
+		if ((m_invalidateBuffers & GL_COLOR_BUFFER_BIT) != 0)
+		{
+			// Color was not preserved - fill with green.
+			glDisable(GL_DEPTH_TEST);
+			glDisable(GL_STENCIL_TEST);
+
+			flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+			sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(quadNDCLeftBottomXY.x(), quadNDCLeftBottomXY.y(), 0.0f), Vec3(quadNDCRightTopXY.x(), quadNDCRightTopXY.y(), 0.0f));
+
+			glEnable(GL_DEPTH_TEST);
+			glEnable(GL_STENCIL_TEST);
+		}
+
+		if ((m_invalidateBuffers & GL_DEPTH_BUFFER_BIT) != 0)
+		{
+			// Depth was not preserved.
+			glDepthFunc(GL_ALWAYS);
+		}
+
+		if ((m_invalidateBuffers & GL_STENCIL_BUFFER_BIT) == 0)
+		{
+			// Stencil was preserved.
+			glStencilFunc(GL_EQUAL, 1, 0xff);
+		}
+
+		glEnable		(GL_BLEND);
+		glBlendFunc		(GL_ONE, GL_ONE);
+		glBlendEquation	(GL_FUNC_ADD);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(quadNDCLeftBottomXY.x(), quadNDCLeftBottomXY.y(), 0.0f), Vec3(quadNDCRightTopXY.x(), quadNDCRightTopXY.y(), 0.0f));
+
+		readPixels(dst, 0, 0, quadSizePixels.x(), quadSizePixels.y());
+	}
+
+private:
+	deUint32	m_colorFmt;
+	deUint32	m_depthStencilFmt;
+	int			m_numSamples;
+	deUint32	m_invalidateBuffers;
+};
+
+class InvalidateFboTargetCase : public FboTestCase
+{
+public:
+	InvalidateFboTargetCase (Context& context, const char* name, const char* description, deUint32 boundTarget, deUint32 invalidateTarget, const deUint32* invalidateAttachments, int numAttachments)
+		: FboTestCase				(context, name, description)
+		, m_boundTarget				(boundTarget)
+		, m_invalidateTarget		(invalidateTarget)
+		, m_invalidateAttachments	(invalidateAttachments, invalidateAttachments+numAttachments)
+	{
+	}
+
+protected:
+	void render (tcu::Surface& dst)
+	{
+		const deUint32					colorFormat				= GL_RGBA8;
+		const deUint32					depthStencilFormat		= GL_DEPTH24_STENCIL8;
+		const tcu::TextureFormat		colorFmt				= glu::mapGLInternalFormat(colorFormat);
+		const tcu::TextureFormatInfo	colorFmtInfo			= tcu::getTextureFormatInfo(colorFmt);
+		const tcu::Vec4&				cBias					= colorFmtInfo.valueMin;
+		const tcu::Vec4					cScale					= colorFmtInfo.valueMax-colorFmtInfo.valueMin;
+		const bool						isDiscarded				= (m_invalidateTarget == GL_FRAMEBUFFER && m_boundTarget == GL_DRAW_FRAMEBUFFER) || (m_invalidateTarget == m_boundTarget);
+		const bool						isColorDiscarded		= isDiscarded && hasAttachment(m_invalidateAttachments, GL_COLOR_ATTACHMENT0);
+		const bool						isDepthDiscarded		= isDiscarded && (hasAttachment(m_invalidateAttachments, GL_DEPTH_ATTACHMENT) || hasAttachment(m_invalidateAttachments, GL_DEPTH_STENCIL_ATTACHMENT));
+		const bool						isStencilDiscarded		= isDiscarded && (hasAttachment(m_invalidateAttachments, GL_STENCIL_ATTACHMENT) || hasAttachment(m_invalidateAttachments, GL_DEPTH_STENCIL_ATTACHMENT));
+
+		deUint32						fbo						= 0;
+		deUint32						colorRbo				= 0;
+		deUint32						depthStencilRbo			= 0;
+		FlatColorShader					flatShader				(glu::TYPE_FLOAT_VEC4);
+		deUint32						flatShaderID			= getCurrentContext()->createProgram(&flatShader);
+
+		// Create fbo.
+		glGenRenderbuffers		(1, &colorRbo);
+		glBindRenderbuffer		(GL_RENDERBUFFER, colorRbo);
+		glRenderbufferStorage	(GL_RENDERBUFFER, colorFormat, getWidth(), getHeight());
+
+		glGenRenderbuffers		(1, &depthStencilRbo);
+		glBindRenderbuffer		(GL_RENDERBUFFER, depthStencilRbo);
+		glRenderbufferStorage	(GL_RENDERBUFFER, depthStencilFormat, getWidth(), getHeight());
+
+		glGenFramebuffers			(1, &fbo);
+		glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+		glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+		glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+		checkFramebufferStatus		(GL_FRAMEBUFFER);
+
+		glClearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+		glClear			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		glEnable		(GL_DEPTH_TEST);
+		glEnable		(GL_STENCIL_TEST);
+		glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc	(GL_ALWAYS, 1, 0xff);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias);
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		// Bound FBO to test target and default to other
+		if (m_boundTarget != GL_FRAMEBUFFER)
+		{
+			// Dummy fbo is used as complemeting target (read when discarding draw for example).
+			// \note Framework takes care of deleting objects at the end of test case.
+			const deUint32	dummyTarget		= m_boundTarget == GL_DRAW_FRAMEBUFFER ?  GL_READ_FRAMEBUFFER : GL_DRAW_FRAMEBUFFER;
+			deUint32		dummyFbo		= 0;
+			deUint32		dummyColorRbo	= 0;
+
+			glGenRenderbuffers			(1, &dummyColorRbo);
+			glBindRenderbuffer			(GL_RENDERBUFFER, dummyColorRbo);
+			glRenderbufferStorage		(GL_RENDERBUFFER, GL_RGBA8, 64, 64);
+			glGenFramebuffers			(1, &dummyFbo);
+			glBindFramebuffer			(dummyTarget, dummyFbo);
+			glFramebufferRenderbuffer	(dummyTarget, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, dummyColorRbo);
+
+			glBindFramebuffer			(m_boundTarget, fbo);
+		}
+
+		glInvalidateFramebuffer(m_invalidateTarget, (int)m_invalidateAttachments.size(), m_invalidateAttachments.empty() ? DE_NULL : &m_invalidateAttachments[0]);
+
+		if (m_boundTarget != GL_FRAMEBUFFER)
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+		if (isColorDiscarded)
+		{
+			// Color was not preserved - fill with green.
+			glDisable(GL_DEPTH_TEST);
+			glDisable(GL_STENCIL_TEST);
+
+			flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias);
+			sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+			glEnable(GL_DEPTH_TEST);
+			glEnable(GL_STENCIL_TEST);
+		}
+
+		if (isDepthDiscarded)
+		{
+			// Depth was not preserved.
+			glDepthFunc(GL_ALWAYS);
+		}
+
+		if (!isStencilDiscarded)
+		{
+			// Stencil was preserved.
+			glStencilFunc(GL_EQUAL, 1, 0xff);
+		}
+
+		glEnable		(GL_BLEND);
+		glBlendFunc		(GL_ONE, GL_ONE);
+		glBlendEquation	(GL_FUNC_ADD);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f)*cScale + cBias);
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, getWidth(), getHeight(), colorFmt, colorFmtInfo.lookupScale, colorFmtInfo.lookupBias);
+	}
+
+private:
+	deUint32				m_boundTarget;
+	deUint32				m_invalidateTarget;
+	std::vector<deUint32>	m_invalidateAttachments;
+};
+
+FboInvalidateTests::FboInvalidateTests (Context& context)
+	: TestCaseGroup(context, "invalidate", "Framebuffer invalidate tests")
+{
+}
+
+FboInvalidateTests::~FboInvalidateTests (void)
+{
+}
+
+void FboInvalidateTests::init (void)
+{
+	// invalidate.default.
+	{
+		tcu::TestCaseGroup* defaultFbGroup = new tcu::TestCaseGroup(m_testCtx, "default", "Default framebuffer invalidate tests");
+		addChild(defaultFbGroup);
+
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferRenderCase		(m_context,	"render_none",				"Invalidating no framebuffers (ref)",						0));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferRenderCase		(m_context,	"render_color",				"Rendering after invalidating colorbuffer",					GL_COLOR_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferRenderCase		(m_context,	"render_depth",				"Rendering after invalidating depthbuffer",					GL_DEPTH_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferRenderCase		(m_context,	"render_stencil",			"Rendering after invalidating stencilbuffer",				GL_STENCIL_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferRenderCase		(m_context,	"render_depth_stencil",		"Rendering after invalidating depth- and stencilbuffers",	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferRenderCase		(m_context,	"render_all",				"Rendering after invalidating all buffers",					GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferBindCase		(m_context,	"bind_color",				"Binding fbo after invalidating colorbuffer",				GL_COLOR_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferBindCase		(m_context,	"bind_depth",				"Binding fbo after invalidating depthbuffer",				GL_DEPTH_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferBindCase		(m_context,	"bind_stencil",				"Binding fbo after invalidating stencilbuffer",				GL_STENCIL_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferBindCase		(m_context,	"bind_depth_stencil",		"Binding fbo after invalidating depth- and stencilbuffers",	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferBindCase		(m_context,	"bind_all",					"Binding fbo after invalidating all buffers",				GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+		defaultFbGroup->addChild(new InvalidateDefaultSubFramebufferRenderCase	(m_context,	"sub_render_color",			"Rendering after invalidating colorbuffer",					GL_COLOR_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultSubFramebufferRenderCase	(m_context,	"sub_render_depth",			"Rendering after invalidating depthbuffer",					GL_DEPTH_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultSubFramebufferRenderCase	(m_context,	"sub_render_stencil",		"Rendering after invalidating stencilbuffer",				GL_STENCIL_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultSubFramebufferRenderCase	(m_context,	"sub_render_depth_stencil",	"Rendering after invalidating depth- and stencilbuffers",	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultSubFramebufferRenderCase	(m_context,	"sub_render_all",			"Rendering after invalidating all buffers",					GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+		defaultFbGroup->addChild(new InvalidateDefaultSubFramebufferBindCase	(m_context,	"sub_bind_color",			"Binding fbo after invalidating colorbuffer",				GL_COLOR_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultSubFramebufferBindCase	(m_context,	"sub_bind_depth",			"Binding fbo after invalidating depthbuffer",				GL_DEPTH_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultSubFramebufferBindCase	(m_context,	"sub_bind_stencil",			"Binding fbo after invalidating stencilbuffer",				GL_STENCIL_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultSubFramebufferBindCase	(m_context,	"sub_bind_depth_stencil",	"Binding fbo after invalidating depth- and stencilbuffers",	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+		defaultFbGroup->addChild(new InvalidateDefaultSubFramebufferBindCase	(m_context,	"sub_bind_all",				"Binding fbo after invalidating all buffers",				GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferRenderCase		(m_context,	"draw_framebuffer_color",	"Invalidating GL_COLOR in GL_DRAW_FRAMEBUFFER",				GL_COLOR_BUFFER_BIT,											GL_DRAW_FRAMEBUFFER));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferRenderCase		(m_context,	"draw_framebuffer_all",		"Invalidating all in GL_DRAW_FRAMEBUFFER",					GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT,	GL_DRAW_FRAMEBUFFER));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferRenderCase		(m_context,	"read_framebuffer_color",	"Invalidating GL_COLOR in GL_READ_FRAMEBUFFER",				GL_COLOR_BUFFER_BIT,											GL_READ_FRAMEBUFFER));
+		defaultFbGroup->addChild(new InvalidateDefaultFramebufferRenderCase		(m_context,	"read_framebuffer_all",		"Invalidating all in GL_READ_FRAMEBUFFER",					GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT,	GL_READ_FRAMEBUFFER));
+	}
+
+	// invalidate.whole.
+	{
+		tcu::TestCaseGroup* wholeFboGroup = new tcu::TestCaseGroup(m_testCtx, "whole", "Invalidating whole framebuffer object");
+		addChild(wholeFboGroup);
+
+		wholeFboGroup->addChild(new InvalidateFboRenderCase		(m_context, "render_none",						"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	0));
+		wholeFboGroup->addChild(new InvalidateFboRenderCase		(m_context, "render_color",						"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_COLOR_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboRenderCase		(m_context, "render_depth",						"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_DEPTH_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboRenderCase		(m_context, "render_stencil",					"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_STENCIL_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboRenderCase		(m_context, "render_depth_stencil",				"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboRenderCase		(m_context, "render_all",						"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+		wholeFboGroup->addChild(new InvalidateFboUnbindReadCase	(m_context, "unbind_read_color",				"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_COLOR_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboUnbindReadCase	(m_context, "unbind_read_depth",				"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_DEPTH_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboUnbindReadCase	(m_context, "unbind_read_stencil",				"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_STENCIL_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboUnbindReadCase	(m_context, "unbind_read_depth_stencil",		"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboUnbindReadCase	(m_context, "unbind_read_color_stencil",		"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+		wholeFboGroup->addChild(new InvalidateFboUnbindBlitCase	(m_context, "unbind_blit_color",				"",		0,	GL_COLOR_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboUnbindBlitCase	(m_context, "unbind_blit_depth",				"",		0,	GL_DEPTH_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboUnbindBlitCase	(m_context, "unbind_blit_stencil",				"",		0,	GL_STENCIL_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboUnbindBlitCase	(m_context, "unbind_blit_depth_stencil",		"",		0,	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+		wholeFboGroup->addChild(new InvalidateFboUnbindBlitCase	(m_context, "unbind_blit_msaa_color",			"",		4,	GL_COLOR_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboUnbindBlitCase	(m_context, "unbind_blit_msaa_depth",			"",		4,	GL_DEPTH_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboUnbindBlitCase	(m_context, "unbind_blit_msaa_stencil",			"",		4,	GL_STENCIL_BUFFER_BIT));
+		wholeFboGroup->addChild(new InvalidateFboUnbindBlitCase	(m_context, "unbind_blit_msaa_depth_stencil",	"",		4,	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+	}
+
+	// invalidate.sub.
+	{
+		tcu::TestCaseGroup* subFboGroup = new tcu::TestCaseGroup(m_testCtx, "sub", "Invalidating subsection of framebuffer object");
+		addChild(subFboGroup);
+
+		subFboGroup->addChild(new InvalidateSubFboRenderCase	(m_context, "render_none",						"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	0));
+		subFboGroup->addChild(new InvalidateSubFboRenderCase	(m_context, "render_color",						"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_COLOR_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboRenderCase	(m_context, "render_depth",						"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_DEPTH_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboRenderCase	(m_context, "render_stencil",					"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_STENCIL_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboRenderCase	(m_context, "render_depth_stencil",				"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboRenderCase	(m_context, "render_all",						"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+		subFboGroup->addChild(new InvalidateSubFboUnbindReadCase(m_context, "unbind_read_color",				"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_COLOR_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboUnbindReadCase(m_context, "unbind_read_depth",				"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_DEPTH_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboUnbindReadCase(m_context, "unbind_read_stencil",				"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_STENCIL_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboUnbindReadCase(m_context, "unbind_read_depth_stencil",		"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboUnbindReadCase(m_context, "unbind_read_color_stencil",		"",		GL_RGBA8,		GL_DEPTH24_STENCIL8,	GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+		subFboGroup->addChild(new InvalidateSubFboUnbindBlitCase(m_context, "unbind_blit_color",				"",		0,	GL_COLOR_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboUnbindBlitCase(m_context, "unbind_blit_depth",				"",		0,	GL_DEPTH_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboUnbindBlitCase(m_context, "unbind_blit_stencil",				"",		0,	GL_STENCIL_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboUnbindBlitCase(m_context, "unbind_blit_depth_stencil",		"",		0,	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+		subFboGroup->addChild(new InvalidateSubFboUnbindBlitCase(m_context, "unbind_blit_msaa_color",			"",		4,	GL_COLOR_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboUnbindBlitCase(m_context, "unbind_blit_msaa_depth",			"",		4,	GL_DEPTH_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboUnbindBlitCase(m_context, "unbind_blit_msaa_stencil",			"",		4,	GL_STENCIL_BUFFER_BIT));
+		subFboGroup->addChild(new InvalidateSubFboUnbindBlitCase(m_context, "unbind_blit_msaa_depth_stencil",	"",		4,	GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+	}
+
+	// invalidate.format.
+	{
+		tcu::TestCaseGroup* formatGroup = new tcu::TestCaseGroup(m_testCtx, "format", "Invalidating framebuffers with selected formats");
+		addChild(formatGroup);
+
+		// Color buffer formats.
+		static const deUint32 colorFormats[] =
+		{
+			// RGBA formats
+			GL_RGBA32I,
+			GL_RGBA32UI,
+			GL_RGBA16I,
+			GL_RGBA16UI,
+			GL_RGBA8,
+			GL_RGBA8I,
+			GL_RGBA8UI,
+			GL_SRGB8_ALPHA8,
+			GL_RGB10_A2,
+			GL_RGB10_A2UI,
+			GL_RGBA4,
+			GL_RGB5_A1,
+
+			// RGB formats
+			GL_RGB8,
+			GL_RGB565,
+
+			// RG formats
+			GL_RG32I,
+			GL_RG32UI,
+			GL_RG16I,
+			GL_RG16UI,
+			GL_RG8,
+			GL_RG8I,
+			GL_RG8UI,
+
+			// R formats
+			GL_R32I,
+			GL_R32UI,
+			GL_R16I,
+			GL_R16UI,
+			GL_R8,
+			GL_R8I,
+			GL_R8UI,
+
+			// GL_EXT_color_buffer_float
+			GL_RGBA32F,
+			GL_RGBA16F,
+			GL_R11F_G11F_B10F,
+			GL_RG32F,
+			GL_RG16F,
+			GL_R32F,
+			GL_R16F
+		};
+
+		// Depth/stencilbuffer formats.
+		static const deUint32 depthStencilFormats[] =
+		{
+			GL_DEPTH_COMPONENT32F,
+			GL_DEPTH_COMPONENT24,
+			GL_DEPTH_COMPONENT16,
+			GL_DEPTH32F_STENCIL8,
+			GL_DEPTH24_STENCIL8,
+			GL_STENCIL_INDEX8
+		};
+
+		// Colorbuffer tests use invalidate, unbind, read test.
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorFormats); ndx++)
+			formatGroup->addChild(new InvalidateSubFboUnbindReadCase(m_context, getFormatName(colorFormats[ndx]), "", colorFormats[ndx], GL_NONE, GL_COLOR_BUFFER_BIT));
+
+		// Depth/stencilbuffer tests use invalidate, render test.
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
+			formatGroup->addChild(new InvalidateSubFboRenderCase(m_context, getFormatName(depthStencilFormats[ndx]), "", GL_RGBA8, depthStencilFormats[ndx], GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+	}
+
+	// invalidate.target
+	{
+		tcu::TestCaseGroup* targetGroup = new tcu::TestCaseGroup(m_testCtx, "target", "Invalidate target");
+		addChild(targetGroup);
+
+		static const struct
+		{
+			const char*		name;
+			deUint32		invalidateTarget;
+			deUint32		boundTarget;
+		} s_targetCases[] =
+		{
+			{ "framebuffer_framebuffer",			GL_FRAMEBUFFER,			GL_FRAMEBUFFER		},
+			{ "framebuffer_read_framebuffer",		GL_FRAMEBUFFER,			GL_READ_FRAMEBUFFER },
+			{ "framebuffer_draw_framebuffer",		GL_FRAMEBUFFER,			GL_DRAW_FRAMEBUFFER },
+			{ "read_framebuffer_framebuffer",		GL_READ_FRAMEBUFFER,	GL_FRAMEBUFFER		},
+			{ "read_framebuffer_read_framebuffer",	GL_READ_FRAMEBUFFER,	GL_READ_FRAMEBUFFER },
+			{ "read_framebuffer_draw_framebuffer",	GL_READ_FRAMEBUFFER,	GL_DRAW_FRAMEBUFFER },
+			{ "draw_framebuffer_framebuffer",		GL_DRAW_FRAMEBUFFER,	GL_FRAMEBUFFER		},
+			{ "draw_framebuffer_read_framebuffer",	GL_DRAW_FRAMEBUFFER,	GL_READ_FRAMEBUFFER },
+			{ "draw_framebuffer_draw_framebuffer",	GL_DRAW_FRAMEBUFFER,	GL_DRAW_FRAMEBUFFER },
+		};
+
+		static const deUint32 colorAttachment[]			= { GL_COLOR_ATTACHMENT0 };
+		static const deUint32 depthStencilAttachment[]	= { GL_DEPTH_STENCIL_ATTACHMENT };
+		static const deUint32 allAttachments[]			= { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT };
+
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(s_targetCases); caseNdx++)
+		{
+			const std::string	baseName		= s_targetCases[caseNdx].name;
+			const deUint32		invalidateT		= s_targetCases[caseNdx].invalidateTarget;
+			const deUint32		boundT			= s_targetCases[caseNdx].boundTarget;
+
+			targetGroup->addChild(new InvalidateFboTargetCase(m_context, (baseName + "_color").c_str(),			"",	boundT, invalidateT, &colorAttachment[0],			DE_LENGTH_OF_ARRAY(colorAttachment)));
+			targetGroup->addChild(new InvalidateFboTargetCase(m_context, (baseName + "_depth_stencil").c_str(),	"",	boundT, invalidateT, &depthStencilAttachment[0],	DE_LENGTH_OF_ARRAY(depthStencilAttachment)));
+			targetGroup->addChild(new InvalidateFboTargetCase(m_context, (baseName + "_all").c_str(),			"",	boundT, invalidateT, &allAttachments[0],			DE_LENGTH_OF_ARRAY(allAttachments)));
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboInvalidateTests.hpp b/modules/gles3/functional/es3fFboInvalidateTests.hpp
new file mode 100644
index 0000000..c7d8e4e
--- /dev/null
+++ b/modules/gles3/functional/es3fFboInvalidateTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFBOINVALIDATETESTS_HPP
+#define _ES3FFBOINVALIDATETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO invalidate tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FboInvalidateTests : public TestCaseGroup
+{
+public:
+							FboInvalidateTests		(Context& context);
+							~FboInvalidateTests		(void);
+
+	void					init					(void);
+
+private:
+							FboInvalidateTests		(const FboInvalidateTests& other);
+	FboInvalidateTests&		operator=				(const FboInvalidateTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFBOINVALIDATETESTS_HPP
diff --git a/modules/gles3/functional/es3fFboMultisampleTests.cpp b/modules/gles3/functional/es3fFboMultisampleTests.cpp
new file mode 100644
index 0000000..0072c2e
--- /dev/null
+++ b/modules/gles3/functional/es3fFboMultisampleTests.cpp
@@ -0,0 +1,319 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO multisample tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboMultisampleTests.hpp"
+#include "es3fFboTestCase.hpp"
+#include "es3fFboTestUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "sglrContextUtil.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::UVec4;
+using namespace FboTestUtil;
+
+class BasicFboMultisampleCase : public FboTestCase
+{
+public:
+	BasicFboMultisampleCase (Context& context, const char* name, const char* desc, deUint32 colorFormat, deUint32 depthStencilFormat, const IVec2& size, int numSamples)
+		: FboTestCase			(context, name, desc)
+		, m_colorFormat			(colorFormat)
+		, m_depthStencilFormat	(depthStencilFormat)
+		, m_size				(size)
+		, m_numSamples			(numSamples)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport	(m_colorFormat);
+		checkSampleCount	(m_colorFormat, m_numSamples);
+
+		if (m_depthStencilFormat != GL_NONE)
+		{
+			checkFormatSupport	(m_depthStencilFormat);
+			checkSampleCount	(m_depthStencilFormat, m_numSamples);
+		}
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		tcu::TextureFormat		colorFmt				= glu::mapGLInternalFormat(m_colorFormat);
+		tcu::TextureFormat		depthStencilFmt			= m_depthStencilFormat != GL_NONE ? glu::mapGLInternalFormat(m_depthStencilFormat) : tcu::TextureFormat();
+		tcu::TextureFormatInfo	colorFmtInfo			= tcu::getTextureFormatInfo(colorFmt);
+		bool					depth					= depthStencilFmt.order == tcu::TextureFormat::D || depthStencilFmt.order == tcu::TextureFormat::DS;
+		bool					stencil					= depthStencilFmt.order == tcu::TextureFormat::S || depthStencilFmt.order == tcu::TextureFormat::DS;
+		GradientShader			gradShader				(getFragmentOutputType(colorFmt));
+		FlatColorShader			flatShader				(getFragmentOutputType(colorFmt));
+		deUint32				gradShaderID			= getCurrentContext()->createProgram(&gradShader);
+		deUint32				flatShaderID			= getCurrentContext()->createProgram(&flatShader);
+		deUint32				msaaFbo					= 0;
+		deUint32				resolveFbo				= 0;
+		deUint32				msaaColorRbo			= 0;
+		deUint32				resolveColorRbo			= 0;
+		deUint32				msaaDepthStencilRbo		= 0;
+		deUint32				resolveDepthStencilRbo	= 0;
+
+		// Create framebuffers.
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			deUint32&	fbo				= ndx ? resolveFbo				: msaaFbo;
+			deUint32&	colorRbo		= ndx ? resolveColorRbo			: msaaColorRbo;
+			deUint32&	depthStencilRbo	= ndx ? resolveDepthStencilRbo	: msaaDepthStencilRbo;
+			int			samples			= ndx ? 0						: m_numSamples;
+
+			glGenRenderbuffers(1, &colorRbo);
+			glBindRenderbuffer(GL_RENDERBUFFER, colorRbo);
+			glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, m_colorFormat, m_size.x(), m_size.y());
+
+			if (depth || stencil)
+			{
+				glGenRenderbuffers(1, &depthStencilRbo);
+				glBindRenderbuffer(GL_RENDERBUFFER, depthStencilRbo);
+				glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, m_depthStencilFormat, m_size.x(), m_size.y());
+			}
+
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+			if (depth)
+				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+			if (stencil)
+				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+			checkError();
+			checkFramebufferStatus(GL_FRAMEBUFFER);
+		}
+
+		glBindFramebuffer(GL_FRAMEBUFFER, msaaFbo);
+		glViewport(0, 0, m_size.x(), m_size.y());
+
+		// Clear depth and stencil buffers.
+		glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0);
+
+		// Fill MSAA fbo with gradient, depth = [-1..1]
+		glEnable(GL_DEPTH_TEST);
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, colorFmtInfo.valueMin, colorFmtInfo.valueMax);
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		// Render random-colored quads.
+		{
+			const int		numQuads	= 8;
+			de::Random		rnd			(9);
+
+			glDepthFunc(GL_ALWAYS);
+			glEnable(GL_STENCIL_TEST);
+			glStencilFunc(GL_ALWAYS, 0, 0xffu);
+			glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+
+			for (int ndx = 0; ndx < numQuads; ndx++)
+			{
+				float	r		= rnd.getFloat();
+				float	g		= rnd.getFloat();
+				float	b		= rnd.getFloat();
+				float	a		= rnd.getFloat();
+				float	x0		= rnd.getFloat(-1.0f, 1.0f);
+				float	y0		= rnd.getFloat(-1.0f, 1.0f);
+				float	z0		= rnd.getFloat(-1.0f, 1.0f);
+				float	x1		= rnd.getFloat(-1.0f, 1.0f);
+				float	y1		= rnd.getFloat(-1.0f, 1.0f);
+				float	z1		= rnd.getFloat(-1.0f, 1.0f);
+
+				flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(r,g,b,a) * (colorFmtInfo.valueMax-colorFmtInfo.valueMin) + colorFmtInfo.valueMin);
+				sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(x0, y0, z0), Vec3(x1, y1, z1));
+			}
+		}
+
+		glDisable(GL_DEPTH_TEST);
+		glDisable(GL_STENCIL_TEST);
+		checkError();
+
+		// Resolve using glBlitFramebuffer().
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFbo);
+		glBlitFramebuffer(0, 0, m_size.x(), m_size.y(), 0, 0, m_size.x(), m_size.y(), GL_COLOR_BUFFER_BIT | (depth ? GL_DEPTH_BUFFER_BIT : 0) | (stencil ? GL_STENCIL_BUFFER_BIT : 0), GL_NEAREST);
+
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFbo);
+
+		if (depth)
+		{
+			// Visualize depth.
+			const int	numSteps	= 8;
+			const float	step		= 2.0f / (float)numSteps;
+
+			glEnable(GL_DEPTH_TEST);
+			glDepthFunc(GL_LESS);
+			glDepthMask(GL_FALSE);
+			glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
+
+			for (int ndx = 0; ndx < numSteps; ndx++)
+			{
+				float d = -1.0f + step*ndx;
+				float c = (float)ndx / (float)(numSteps-1);
+
+				flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, c, 1.0f) * (colorFmtInfo.valueMax-colorFmtInfo.valueMin) + colorFmtInfo.valueMin);
+				sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, d), Vec3(1.0f, 1.0f, d));
+			}
+
+			glDisable(GL_DEPTH_TEST);
+		}
+
+		if (stencil)
+		{
+			// Visualize stencil.
+			const int	numSteps	= 4;
+			const int	step		= 1;
+
+			glEnable(GL_STENCIL_TEST);
+			glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+			glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
+
+			for (int ndx = 0; ndx < numSteps; ndx++)
+			{
+				int		s	= step*ndx;
+				float	c	= (float)ndx / (float)(numSteps-1);
+
+				glStencilFunc(GL_EQUAL, s, 0xffu);
+
+				flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, c, 0.0f, 1.0f) * (colorFmtInfo.valueMax-colorFmtInfo.valueMin) + colorFmtInfo.valueMin);
+				sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+			}
+
+			glDisable(GL_STENCIL_TEST);
+		}
+
+		readPixels(dst, 0, 0, m_size.x(), m_size.y(), colorFmt, colorFmtInfo.lookupScale, colorFmtInfo.lookupBias);
+	}
+
+	bool colorCompare (const tcu::Surface& reference, const tcu::Surface& result)
+	{
+		const tcu::RGBA threshold (tcu::max(getFormatThreshold(m_colorFormat), tcu::RGBA(12, 12, 12, 12)));
+
+		return tcu::bilinearCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference.getAccess(), result.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
+	}
+
+	bool compare (const tcu::Surface& reference, const tcu::Surface& result)
+	{
+		if (m_depthStencilFormat != GL_NONE)
+			return FboTestCase::compare(reference, result);
+		else
+			return colorCompare(reference, result);
+	}
+
+private:
+	deUint32	m_colorFormat;
+	deUint32	m_depthStencilFormat;
+	IVec2		m_size;
+	int			m_numSamples;
+};
+
+FboMultisampleTests::FboMultisampleTests (Context& context)
+	: TestCaseGroup(context, "msaa", "Multisample FBO tests")
+{
+}
+
+FboMultisampleTests::~FboMultisampleTests (void)
+{
+}
+
+void FboMultisampleTests::init (void)
+{
+	static const deUint32 colorFormats[] =
+	{
+		// RGBA formats
+		GL_RGBA8,
+		GL_SRGB8_ALPHA8,
+		GL_RGB10_A2,
+		GL_RGBA4,
+		GL_RGB5_A1,
+
+		// RGB formats
+		GL_RGB8,
+		GL_RGB565,
+
+		// RG formats
+		GL_RG8,
+
+		// R formats
+		GL_R8,
+
+		// GL_EXT_color_buffer_float
+		GL_RGBA32F,
+		GL_RGBA16F,
+		GL_R11F_G11F_B10F,
+		GL_RG32F,
+		GL_RG16F,
+		GL_R32F,
+		GL_R16F
+	};
+
+	static const deUint32 depthStencilFormats[] =
+	{
+		GL_DEPTH_COMPONENT32F,
+		GL_DEPTH_COMPONENT24,
+		GL_DEPTH_COMPONENT16,
+		GL_DEPTH32F_STENCIL8,
+		GL_DEPTH24_STENCIL8,
+		GL_STENCIL_INDEX8
+	};
+
+	static const int sampleCounts[] = { 2, 4, 8 };
+
+	for (int sampleCntNdx = 0; sampleCntNdx < DE_LENGTH_OF_ARRAY(sampleCounts); sampleCntNdx++)
+	{
+		int					samples				= sampleCounts[sampleCntNdx];
+		tcu::TestCaseGroup*	sampleCountGroup	= new tcu::TestCaseGroup(m_testCtx, (de::toString(samples) + "_samples").c_str(), "");
+		addChild(sampleCountGroup);
+
+		// Color formats.
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
+			sampleCountGroup->addChild(new BasicFboMultisampleCase(m_context, getFormatName(colorFormats[fmtNdx]), "", colorFormats[fmtNdx], GL_NONE, IVec2(119, 131), samples));
+
+		// Depth/stencil formats.
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); fmtNdx++)
+			sampleCountGroup->addChild(new BasicFboMultisampleCase(m_context, getFormatName(depthStencilFormats[fmtNdx]), "", GL_RGBA8, depthStencilFormats[fmtNdx], IVec2(119, 131), samples));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboMultisampleTests.hpp b/modules/gles3/functional/es3fFboMultisampleTests.hpp
new file mode 100644
index 0000000..d9a79e6
--- /dev/null
+++ b/modules/gles3/functional/es3fFboMultisampleTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFBOMULTISAMPLETESTS_HPP
+#define _ES3FFBOMULTISAMPLETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO multisample tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FboMultisampleTests : public TestCaseGroup
+{
+public:
+							FboMultisampleTests		(Context& context);
+							~FboMultisampleTests	(void);
+
+	void					init					(void);
+
+private:
+							FboMultisampleTests		(const FboMultisampleTests& other);
+	FboMultisampleTests&	operator=				(const FboMultisampleTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFBOMULTISAMPLETESTS_HPP
diff --git a/modules/gles3/functional/es3fFboRenderTest.cpp b/modules/gles3/functional/es3fFboRenderTest.cpp
new file mode 100644
index 0000000..bf83b39
--- /dev/null
+++ b/modules/gles3/functional/es3fFboRenderTest.cpp
@@ -0,0 +1,1698 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer Object Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboRenderTest.hpp"
+#include "sglrContextUtil.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "es3fFboTestUtil.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "deRandom.h"
+#include "deString.h"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+#include <sstream>
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::RGBA;
+using tcu::Surface;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using glw::GLenum;
+using namespace FboTestUtil;
+
+class FboConfig
+{
+public:
+	FboConfig (deUint32 buffers_, deUint32 colorType_, deUint32 colorFormat_, deUint32 depthStencilType_, deUint32 depthStencilFormat_, int width_ = 0, int height_ = 0, int samples_ = 0)
+		: buffers				(buffers_)
+		, colorType				(colorType_)
+		, colorFormat			(colorFormat_)
+		, depthStencilType		(depthStencilType_)
+		, depthStencilFormat	(depthStencilFormat_)
+		, width					(width_)
+		, height				(height_)
+		, samples				(samples_)
+	{
+	}
+
+	FboConfig (void)
+		: buffers				(0)
+		, colorType				(GL_NONE)
+		, colorFormat			(GL_NONE)
+		, depthStencilType		(GL_NONE)
+		, depthStencilFormat	(GL_NONE)
+		, width					(0)
+		, height				(0)
+		, samples				(0)
+	{
+	}
+
+	std::string				getName			(void) const;
+
+	deUint32				buffers;		//!< Buffer bit mask (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|...)
+
+	GLenum					colorType;		//!< GL_TEXTURE_2D, GL_TEXTURE_CUBE_MAP, GL_RENDERBUFFER
+	GLenum					colorFormat;	//!< Internal format for color buffer texture or renderbuffer
+
+	GLenum					depthStencilType;
+	GLenum					depthStencilFormat;
+
+	int						width;
+	int						height;
+	int						samples;
+};
+
+static const char* getTypeName (GLenum type)
+{
+	switch (type)
+	{
+		case GL_TEXTURE_2D:		return "tex2d";
+		case GL_RENDERBUFFER:	return "rbo";
+		default:
+			TCU_FAIL("Unknown type");
+	}
+}
+
+std::string FboConfig::getName (void) const
+{
+	std::ostringstream name;
+
+	DE_ASSERT(buffers & GL_COLOR_BUFFER_BIT);
+	name << getTypeName(colorType) << "_" << getFormatName(colorFormat);
+
+	if (buffers & GL_DEPTH_BUFFER_BIT)
+		name << "_depth";
+	if (buffers & GL_STENCIL_BUFFER_BIT)
+		name << "_stencil";
+
+	if (buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT))
+		name << "_" << getTypeName(depthStencilType) << "_" << getFormatName(depthStencilFormat);
+
+	return name.str();
+}
+
+class Framebuffer
+{
+public:
+						Framebuffer				(sglr::Context& context, const FboConfig& config, int width, int height, deUint32 fbo = 0, deUint32 colorBuffer = 0, deUint32 depthStencilBuffer = 0);
+						~Framebuffer			(void);
+
+	const FboConfig&	getConfig				(void) const { return m_config;				}
+	deUint32			getFramebuffer			(void) const { return m_framebuffer;		}
+	deUint32			getColorBuffer			(void) const { return m_colorBuffer;		}
+	deUint32			getDepthStencilBuffer	(void) const { return m_depthStencilBuffer;	}
+
+	void				checkCompleteness		(void);
+
+private:
+	deUint32			createTex2D				(deUint32 name, GLenum format, int width, int height);
+	deUint32			createRbo				(deUint32 name, GLenum format, int width, int height);
+	void				destroyBuffer			(deUint32 name, GLenum type);
+
+	FboConfig			m_config;
+	sglr::Context&		m_context;
+	deUint32			m_framebuffer;
+	deUint32			m_colorBuffer;
+	deUint32			m_depthStencilBuffer;
+};
+
+static std::vector<std::string> getEnablingExtensions (deUint32 format)
+{
+	std::vector<std::string> out;
+
+	switch (format)
+	{
+		case GL_RGB16F:
+			out.push_back("GL_EXT_color_buffer_half_float");
+			break;
+
+		case GL_RGBA16F:
+		case GL_RG16F:
+		case GL_R16F:
+			out.push_back("GL_EXT_color_buffer_half_float");
+
+		case GL_RGBA32F:
+		case GL_RGB32F:
+		case GL_R11F_G11F_B10F:
+		case GL_RG32F:
+		case GL_R32F:
+			out.push_back("GL_EXT_color_buffer_float");
+
+		default:
+			break;
+	}
+
+	return out;
+}
+
+static bool isExtensionSupported (sglr::Context& context, const char* name)
+{
+	std::istringstream extensions(context.getString(GL_EXTENSIONS));
+	std::string extension;
+
+	while (std::getline(extensions, extension, ' '))
+	{
+		if (extension == name)
+			return true;
+	}
+
+	return false;
+}
+
+static bool isAnyExtensionSupported (sglr::Context& context, const std::vector<std::string>& requiredExts)
+{
+	if (requiredExts.empty())
+		return true;
+
+	for (std::vector<std::string>::const_iterator iter = requiredExts.begin(); iter != requiredExts.end(); iter++)
+	{
+		const std::string& extension = *iter;
+
+		if (isExtensionSupported(context, extension.c_str()))
+			return true;
+	}
+
+	return false;
+}
+
+template<typename T>
+static std::string join (const std::vector<T>& list, const std::string& sep)
+{
+	std::ostringstream	out;
+
+	for (typename std::vector<T>::const_iterator iter = list.begin(); iter != list.end(); iter++)
+	{
+		if (iter != list.begin())
+			out << sep;
+		out << *iter;
+	}
+
+	return out.str();
+}
+
+static void checkColorFormatSupport (sglr::Context& context, deUint32 sizedFormat)
+{
+	const std::vector<std::string> requiredExts = getEnablingExtensions(sizedFormat);
+
+	if (!isAnyExtensionSupported(context, requiredExts))
+	{
+		std::string	errMsg	= "Format not supported, requires "
+							+ ((requiredExts.size() == 1) ? requiredExts[0] : " one of the following: " + join(requiredExts, ", "));
+
+		throw tcu::NotSupportedError(errMsg);
+	}
+}
+
+Framebuffer::Framebuffer (sglr::Context& context, const FboConfig& config, int width, int height, deUint32 fbo, deUint32 colorBufferName, deUint32 depthStencilBufferName)
+	: m_config				(config)
+	, m_context				(context)
+	, m_framebuffer			(fbo)
+	, m_colorBuffer			(0)
+	, m_depthStencilBuffer	(0)
+{
+	// Verify that color format is supported
+	checkColorFormatSupport(context, config.colorFormat);
+
+	if (m_framebuffer == 0)
+		context.genFramebuffers(1, &m_framebuffer);
+	context.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+
+	if (m_config.buffers & (GL_COLOR_BUFFER_BIT))
+	{
+		switch (m_config.colorType)
+		{
+			case GL_TEXTURE_2D:
+				m_colorBuffer = createTex2D(colorBufferName, m_config.colorFormat, width, height);
+				context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer, 0);
+				break;
+
+			case GL_RENDERBUFFER:
+				m_colorBuffer = createRbo(colorBufferName, m_config.colorFormat, width, height);
+				context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorBuffer);
+				break;
+
+			default:
+				TCU_FAIL("Unsupported type");
+		}
+	}
+
+	if (m_config.buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT))
+	{
+		switch (m_config.depthStencilType)
+		{
+			case GL_TEXTURE_2D:		m_depthStencilBuffer = createTex2D(depthStencilBufferName, m_config.depthStencilFormat, width, height);		break;
+			case GL_RENDERBUFFER:	m_depthStencilBuffer = createRbo(depthStencilBufferName, m_config.depthStencilFormat, width, height);		break;
+			default:
+				TCU_FAIL("Unsupported type");
+		}
+	}
+
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		deUint32	bit		= ndx ? GL_STENCIL_BUFFER_BIT : GL_DEPTH_BUFFER_BIT;
+		deUint32	point	= ndx ? GL_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
+
+		if ((m_config.buffers & bit) == 0)
+			continue; /* Not used. */
+
+		switch (m_config.depthStencilType)
+		{
+			case GL_TEXTURE_2D:		context.framebufferTexture2D(GL_FRAMEBUFFER, point, GL_TEXTURE_2D, m_depthStencilBuffer, 0);	break;
+			case GL_RENDERBUFFER:	context.framebufferRenderbuffer(GL_FRAMEBUFFER, point, GL_RENDERBUFFER, m_depthStencilBuffer);	break;
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	GLenum err = m_context.getError();
+	if (err != GL_NO_ERROR)
+		throw glu::Error(err, glu::getErrorStr(err).toString().c_str(), "", __FILE__, __LINE__);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+Framebuffer::~Framebuffer (void)
+{
+	m_context.deleteFramebuffers(1, &m_framebuffer);
+	destroyBuffer(m_colorBuffer, m_config.colorType);
+	destroyBuffer(m_depthStencilBuffer, m_config.depthStencilType);
+}
+
+void Framebuffer::checkCompleteness (void)
+{
+	m_context.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+	GLenum status = m_context.checkFramebufferStatus(GL_FRAMEBUFFER);
+	m_context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	if (status != GL_FRAMEBUFFER_COMPLETE)
+		throw FboIncompleteException(status, __FILE__, __LINE__);
+}
+
+deUint32 Framebuffer::createTex2D (deUint32 name, GLenum format, int width, int height)
+{
+	if (name == 0)
+		m_context.genTextures(1, &name);
+
+	m_context.bindTexture(GL_TEXTURE_2D, name);
+	m_context.texImage2D(GL_TEXTURE_2D, 0, format, width, height);
+
+	if (!deIsPowerOfTwo32(width) || !deIsPowerOfTwo32(height))
+	{
+		// Set wrap mode to clamp for NPOT FBOs
+		m_context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		m_context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	}
+
+	m_context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	m_context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	return name;
+}
+
+deUint32 Framebuffer::createRbo (deUint32 name, GLenum format, int width, int height)
+{
+	if (name == 0)
+		m_context.genRenderbuffers(1, &name);
+
+	m_context.bindRenderbuffer(GL_RENDERBUFFER, name);
+	m_context.renderbufferStorage(GL_RENDERBUFFER, format, width, height);
+
+	return name;
+}
+
+void Framebuffer::destroyBuffer (deUint32 name, GLenum type)
+{
+	if (type == GL_TEXTURE_2D || type == GL_TEXTURE_CUBE_MAP)
+		m_context.deleteTextures(1, &name);
+	else if (type == GL_RENDERBUFFER)
+		m_context.deleteRenderbuffers(1, &name);
+	else
+		DE_ASSERT(type == GL_NONE);
+}
+
+static void createMetaballsTex2D (sglr::Context& context, deUint32 name, GLenum format, GLenum dataType, int width, int height)
+{
+	tcu::TextureFormat	texFormat	= glu::mapGLTransferFormat(format, dataType);
+	tcu::TextureLevel	level		(texFormat, width, height);
+
+	tcu::fillWithMetaballs(level.getAccess(), 5, name ^ width ^ height);
+
+	context.bindTexture(GL_TEXTURE_2D, name);
+	context.texImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, level.getAccess().getDataPtr());
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+}
+
+static void createQuadsTex2D (sglr::Context& context, deUint32 name, GLenum format, GLenum dataType, int width, int height)
+{
+	tcu::TextureFormat	texFormat	= glu::mapGLTransferFormat(format, dataType);
+	tcu::TextureLevel	level		(texFormat, width, height);
+
+	tcu::fillWithRGBAQuads(level.getAccess());
+
+	context.bindTexture(GL_TEXTURE_2D, name);
+	context.texImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, level.getAccess().getDataPtr());
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+}
+
+class FboRenderCase : public TestCase
+{
+public:
+								FboRenderCase			(Context& context, const char* name, const char* description, const FboConfig& config);
+	virtual						~FboRenderCase			(void) {}
+
+	virtual IterateResult		iterate					(void);
+	virtual void				render					(sglr::Context& fboContext, Surface& dst) = DE_NULL;
+
+	bool						compare					(const tcu::Surface& reference, const tcu::Surface& result);
+
+protected:
+	const FboConfig				m_config;
+};
+
+FboRenderCase::FboRenderCase (Context& context, const char* name, const char* description, const FboConfig& config)
+	: TestCase	(context, name, description)
+	, m_config	(config)
+{
+}
+
+TestCase::IterateResult FboRenderCase::iterate (void)
+{
+	tcu::Vec4					clearColor				= tcu::Vec4(0.125f, 0.25f, 0.5f, 1.0f);
+	glu::RenderContext&			renderCtx				= m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget			= renderCtx.getRenderTarget();
+	tcu::TestLog&				log						= m_testCtx.getLog();
+	const char*					failReason				= DE_NULL;
+
+	// Position & size for context
+	deRandom rnd;
+	deRandom_init(&rnd, deStringHash(getName()));
+
+	int		width	= deMin32(renderTarget.getWidth(), 128);
+	int		height	= deMin32(renderTarget.getHeight(), 128);
+	int		xMax	= renderTarget.getWidth()-width+1;
+	int		yMax	= renderTarget.getHeight()-height+1;
+	int		x		= deRandom_getUint32(&rnd) % xMax;
+	int		y		= deRandom_getUint32(&rnd) % yMax;
+
+	tcu::Surface	gles3Frame	(width, height);
+	tcu::Surface	refFrame	(width, height);
+	GLenum			gles3Error;
+	GLenum			refError;
+
+	// Render using GLES3
+	try
+	{
+		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(x, y, width, height));
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		render(context, gles3Frame); // Call actual render func
+		gles3Error = context.getError();
+	}
+	catch (const FboIncompleteException& e)
+	{
+		if (e.getReason() == GL_FRAMEBUFFER_UNSUPPORTED)
+		{
+			// Mark test case as unsupported
+			log << e;
+			m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not supported");
+			return STOP;
+		}
+		else
+			throw; // Propagate error
+	}
+
+	// Render reference image
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), renderTarget.getDepthBits(), renderTarget.getStencilBits(), width, height);
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		render(context, refFrame);
+		refError = context.getError();
+	}
+
+	// Compare error codes
+	bool errorCodesOk = (gles3Error == refError);
+
+	if (!errorCodesOk)
+	{
+		log << tcu::TestLog::Message << "Error code mismatch: got " << glu::getErrorStr(gles3Error) << ", expected " << glu::getErrorStr(refError) << tcu::TestLog::EndMessage;
+		failReason = "Got unexpected error";
+	}
+
+	// Compare images
+	bool imagesOk = compare(refFrame, gles3Frame);
+
+	if (!imagesOk && !failReason)
+		failReason = "Image comparison failed";
+
+	// Store test result
+	bool isOk = errorCodesOk && imagesOk;
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: failReason);
+
+	return STOP;
+}
+
+bool FboRenderCase::compare (const tcu::Surface& reference, const tcu::Surface& result)
+{
+	const tcu::RGBA threshold (tcu::max(getFormatThreshold(m_config.colorFormat), tcu::RGBA(12, 12, 12, 12)));
+
+	return tcu::bilinearCompare(m_testCtx.getLog(), "ComparisonResult", "Image comparison result", reference.getAccess(), result.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
+}
+
+namespace FboCases
+{
+
+class StencilClearsTest : public FboRenderCase
+{
+public:
+						StencilClearsTest		(Context& context, const FboConfig& config);
+	virtual				~StencilClearsTest		(void) {};
+
+	void				render					(sglr::Context& context, Surface& dst);
+};
+
+StencilClearsTest::StencilClearsTest (Context& context, const FboConfig& config)
+	: FboRenderCase	(context, config.getName().c_str(), "Stencil clears", config)
+{
+}
+
+void StencilClearsTest::render (sglr::Context& context, Surface& dst)
+{
+	tcu::TextureFormat		colorFormat			= glu::mapGLInternalFormat(m_config.colorFormat);
+	glu::DataType			fboSamplerType		= glu::getSampler2DType(colorFormat);
+	glu::DataType			fboOutputType		= getFragmentOutputType(colorFormat);
+	tcu::TextureFormatInfo	fboRangeInfo		= tcu::getTextureFormatInfo(colorFormat);
+	Vec4					fboOutScale			= fboRangeInfo.valueMax - fboRangeInfo.valueMin;
+	Vec4					fboOutBias			= fboRangeInfo.valueMin;
+
+	Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, fboOutputType);
+	Texture2DShader			texFromFboShader	(DataTypes() << fboSamplerType, glu::TYPE_FLOAT_VEC4);
+
+	deUint32				texToFboShaderID	= context.createProgram(&texToFboShader);
+	deUint32				texFromFboShaderID	= context.createProgram(&texFromFboShader);
+
+	deUint32				metaballsTex		= 1;
+	deUint32				quadsTex			= 2;
+	int						width				= 128;
+	int						height				= 128;
+
+	texToFboShader.setOutScaleBias(fboOutScale, fboOutBias);
+	texFromFboShader.setTexScaleBias(0, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);
+
+	createQuadsTex2D(context, quadsTex, GL_RGBA, GL_UNSIGNED_BYTE, width, height);
+	createMetaballsTex2D(context, metaballsTex, GL_RGBA, GL_UNSIGNED_BYTE, width, height);
+
+	Framebuffer fbo(context, m_config, width, height);
+	fbo.checkCompleteness();
+
+	// Bind framebuffer and clear
+	context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	context.viewport(0, 0, width, height);
+	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	// Do stencil clears
+	context.enable(GL_SCISSOR_TEST);
+	context.scissor(10, 16, 32, 120);
+	context.clearStencil(1);
+	context.clear(GL_STENCIL_BUFFER_BIT);
+	context.scissor(16, 32, 100, 64);
+	context.clearStencil(2);
+	context.clear(GL_STENCIL_BUFFER_BIT);
+	context.disable(GL_SCISSOR_TEST);
+
+	// Draw 2 textures with stecil tests
+	context.enable(GL_STENCIL_TEST);
+
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	context.stencilFunc(GL_EQUAL, 1, 0xffu);
+
+	texToFboShader.setUniforms(context, texToFboShaderID);
+	sglr::drawQuad(context, texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	context.stencilFunc(GL_EQUAL, 2, 0xffu);
+
+	texToFboShader.setUniforms(context, texToFboShaderID);
+	sglr::drawQuad(context, texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+
+	context.disable(GL_STENCIL_TEST);
+
+	if (fbo.getConfig().colorType == GL_TEXTURE_2D)
+	{
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.bindTexture(GL_TEXTURE_2D, fbo.getColorBuffer());
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+
+		texFromFboShader.setUniforms(context, texFromFboShaderID);
+		sglr::drawQuad(context, texFromFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+		readPixels(context, dst, 0, 0, width, height, colorFormat, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);
+}
+
+class SharedColorbufferTest : public FboRenderCase
+{
+public:
+						SharedColorbufferTest			(Context& context, const FboConfig& config);
+	virtual				~SharedColorbufferTest			(void) {};
+
+	void				render							(sglr::Context& context, Surface& dst);
+};
+
+SharedColorbufferTest::SharedColorbufferTest (Context& context, const FboConfig& config)
+	: FboRenderCase	(context, config.getName().c_str(), "Shared colorbuffer", config)
+{
+}
+
+void SharedColorbufferTest::render (sglr::Context& context, Surface& dst)
+{
+	Texture2DShader			texShader		(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
+	FlatColorShader			flatShader		(glu::TYPE_FLOAT_VEC4);
+	deUint32				texShaderID		= context.createProgram(&texShader);
+	deUint32				flatShaderID	= context.createProgram(&flatShader);
+
+	int						width			= 128;
+	int						height			= 128;
+	deUint32				quadsTex		= 1;
+	deUint32				metaballsTex	= 2;
+	bool					stencil			= (m_config.buffers & GL_STENCIL_BUFFER_BIT) != 0;
+
+	context.disable(GL_DITHER);
+
+	// Textures
+	createQuadsTex2D(context, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+	createMetaballsTex2D(context, metaballsTex, GL_RGBA, GL_UNSIGNED_BYTE, 64, 64);
+
+	context.viewport(0, 0, width, height);
+
+	// Fbo A
+	Framebuffer fboA(context, m_config, width, height);
+	fboA.checkCompleteness();
+
+	// Fbo B - don't create colorbuffer
+	FboConfig cfg = m_config;
+	cfg.buffers		&= ~GL_COLOR_BUFFER_BIT;
+	cfg.colorType	 = GL_NONE;
+	cfg.colorFormat	 = GL_NONE;
+	Framebuffer fboB(context, cfg, width, height);
+
+	// Attach color buffer from fbo A
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
+	switch (m_config.colorType)
+	{
+		case GL_TEXTURE_2D:
+			context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboA.getColorBuffer(), 0);
+			break;
+
+		case GL_RENDERBUFFER:
+			context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fboA.getColorBuffer());
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// Clear depth and stencil in fbo B
+	context.clear(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	// Render quads to fbo 1, with depth 0.0
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboA.getFramebuffer());
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	if (stencil)
+	{
+		// Stencil to 1 in fbo A
+		context.clearStencil(1);
+		context.clear(GL_STENCIL_BUFFER_BIT);
+	}
+
+	texShader.setUniforms(context, texShaderID);
+
+	context.enable(GL_DEPTH_TEST);
+	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+	context.disable(GL_DEPTH_TEST);
+
+	// Blend metaballs to fbo 2
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	context.enable(GL_BLEND);
+	context.blendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
+	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	// Render small quad that is only visible if depth buffer is not shared with fbo A - or there is no depth bits
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	context.enable(GL_DEPTH_TEST);
+	sglr::drawQuad(context, texShaderID, Vec3(0.5f, 0.5f, 0.5f), Vec3(1.0f, 1.0f, 0.5f));
+	context.disable(GL_DEPTH_TEST);
+
+	if (stencil)
+	{
+		flatShader.setColor(context, flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+		// Clear subset of stencil buffer to 1
+		context.enable(GL_SCISSOR_TEST);
+		context.scissor(10, 10, 12, 25);
+		context.clearStencil(1);
+		context.clear(GL_STENCIL_BUFFER_BIT);
+		context.disable(GL_SCISSOR_TEST);
+
+		// Render quad with stencil mask == 1
+		context.enable(GL_STENCIL_TEST);
+		context.stencilFunc(GL_EQUAL, 1, 0xffu);
+		sglr::drawQuad(context, flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		context.disable(GL_STENCIL_TEST);
+	}
+
+	// Get results
+	if (fboA.getConfig().colorType == GL_TEXTURE_2D)
+	{
+		texShader.setUniforms(context, texShaderID);
+
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.bindTexture(GL_TEXTURE_2D, fboA.getColorBuffer());
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+		readPixels(context, dst, 0, 0, width, height, glu::mapGLInternalFormat(fboA.getConfig().colorFormat), Vec4(1.0f), Vec4(0.0f));
+}
+
+class SharedColorbufferClearsTest : public FboRenderCase
+{
+public:
+					SharedColorbufferClearsTest		(Context& context, const FboConfig& config);
+	virtual			~SharedColorbufferClearsTest	(void) {}
+
+	void			render							(sglr::Context& context, Surface& dst);
+};
+
+SharedColorbufferClearsTest::SharedColorbufferClearsTest (Context& context, const FboConfig& config)
+	: FboRenderCase	(context, config.getName().c_str(), "Shared colorbuffer clears", config)
+{
+}
+
+void SharedColorbufferClearsTest::render (sglr::Context& context, Surface& dst)
+{
+	tcu::TextureFormat		colorFormat		= glu::mapGLInternalFormat(m_config.colorFormat);
+	glu::DataType			fboSamplerType	= glu::getSampler2DType(colorFormat);
+	int						width			= 128;
+	int						height			= 128;
+	deUint32				colorbuffer		= 1;
+
+	// Check for format support.
+	checkColorFormatSupport(context, m_config.colorFormat);
+
+	// Single colorbuffer
+	if (m_config.colorType == GL_TEXTURE_2D)
+	{
+		context.bindTexture(GL_TEXTURE_2D, colorbuffer);
+		context.texImage2D(GL_TEXTURE_2D, 0, m_config.colorFormat, width, height);
+		context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	}
+	else
+	{
+		DE_ASSERT(m_config.colorType == GL_RENDERBUFFER);
+		context.bindRenderbuffer(GL_RENDERBUFFER, colorbuffer);
+		context.renderbufferStorage(GL_RENDERBUFFER, m_config.colorFormat, width, height);
+	}
+
+	// Multiple framebuffers sharing the colorbuffer
+	for (int fbo = 1; fbo <= 3; fbo++)
+	{
+		context.bindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+		if (m_config.colorType == GL_TEXTURE_2D)
+			context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0);
+		else
+			context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuffer);
+	}
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	// Check completeness
+	{
+		GLenum status = context.checkFramebufferStatus(GL_FRAMEBUFFER);
+		if (status != GL_FRAMEBUFFER_COMPLETE)
+			throw FboIncompleteException(status, __FILE__, __LINE__);
+	}
+
+	// Render to them
+	context.viewport(0, 0, width, height);
+	context.clearColor(0.0f, 0.0f, 1.0f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT);
+
+	context.enable(GL_SCISSOR_TEST);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 2);
+	context.clearColor(0.6f, 0.0f, 0.0f, 1.0f);
+	context.scissor(10, 10, 64, 64);
+	context.clear(GL_COLOR_BUFFER_BIT);
+	context.clearColor(0.0f, 0.6f, 0.0f, 1.0f);
+	context.scissor(60, 60, 40, 20);
+	context.clear(GL_COLOR_BUFFER_BIT);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 3);
+	context.clearColor(0.0f, 0.0f, 0.6f, 1.0f);
+	context.scissor(20, 20, 100, 10);
+	context.clear(GL_COLOR_BUFFER_BIT);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+	context.clearColor(0.6f, 0.0f, 0.6f, 1.0f);
+	context.scissor(20, 20, 5, 100);
+	context.clear(GL_COLOR_BUFFER_BIT);
+
+	context.disable(GL_SCISSOR_TEST);
+
+	if (m_config.colorType == GL_TEXTURE_2D)
+	{
+		Texture2DShader shader(DataTypes() << fboSamplerType, glu::TYPE_FLOAT_VEC4);
+		deUint32 shaderID = context.createProgram(&shader);
+
+		shader.setUniforms(context, shaderID);
+
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		sglr::drawQuad(context, shaderID, Vec3(-0.9f, -0.9f, 0.0f), Vec3(0.9f, 0.9f, 0.0f));
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+		readPixels(context, dst, 0, 0, width, height, colorFormat, Vec4(1.0f), Vec4(0.0f));
+}
+
+class SharedDepthStencilTest : public FboRenderCase
+{
+public:
+					SharedDepthStencilTest		(Context& context, const FboConfig& config);
+	virtual			~SharedDepthStencilTest		(void) {};
+
+	static bool		isConfigSupported			(const FboConfig& config);
+	void			render						(sglr::Context& context, Surface& dst);
+};
+
+SharedDepthStencilTest::SharedDepthStencilTest (Context& context, const FboConfig& config)
+	: FboRenderCase	(context, config.getName().c_str(), "Shared depth/stencilbuffer", config)
+{
+}
+
+bool SharedDepthStencilTest::isConfigSupported (const FboConfig& config)
+{
+	return (config.buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)) != 0;
+}
+
+void SharedDepthStencilTest::render (sglr::Context& context, Surface& dst)
+{
+	Texture2DShader	texShader		(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
+	FlatColorShader	flatShader		(glu::TYPE_FLOAT_VEC4);
+	deUint32		texShaderID		= context.createProgram(&texShader);
+	deUint32		flatShaderID	= context.createProgram(&flatShader);
+	int				width			= 128;
+	int				height			= 128;
+//	bool			depth			= (m_config.buffers & GL_DEPTH_BUFFER_BIT) != 0;
+	bool			stencil			= (m_config.buffers & GL_STENCIL_BUFFER_BIT) != 0;
+
+	// Textures
+	deUint32 metaballsTex	= 5;
+	deUint32 quadsTex		= 6;
+	createMetaballsTex2D(context, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+	createQuadsTex2D(context, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+
+	context.viewport(0, 0, width, height);
+
+	// Fbo A
+	Framebuffer fboA(context, m_config, width, height);
+	fboA.checkCompleteness();
+
+	// Fbo B
+	FboConfig cfg = m_config;
+	cfg.buffers				&= ~(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	cfg.depthStencilType	 = GL_NONE;
+	cfg.depthStencilFormat	 = GL_NONE;
+	Framebuffer fboB(context, cfg, width, height);
+
+	// Bind depth/stencil buffers from fbo A to fbo B
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		deUint32	bit		= ndx ? GL_STENCIL_BUFFER_BIT : GL_DEPTH_BUFFER_BIT;
+		deUint32	point	= ndx ? GL_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
+
+		if ((m_config.buffers & bit) == 0)
+			continue;
+
+		switch (m_config.depthStencilType)
+		{
+			case GL_TEXTURE_2D:		context.framebufferTexture2D(GL_FRAMEBUFFER, point, GL_TEXTURE_2D, fboA.getDepthStencilBuffer(), 0);	break;
+			case GL_RENDERBUFFER:	context.framebufferRenderbuffer(GL_FRAMEBUFFER, point, GL_RENDERBUFFER, fboA.getDepthStencilBuffer());	break;
+			default:
+				TCU_FAIL("Not implemented");
+		}
+	}
+
+	// Setup uniforms
+	texShader.setUniforms(context, texShaderID);
+
+	// Clear color to red and stencil to 1 in fbo B.
+	context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+	context.clearStencil(1);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	context.enable(GL_DEPTH_TEST);
+
+	// Render quad to fbo A
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboA.getFramebuffer());
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	if (stencil)
+	{
+		// Clear subset of stencil buffer to 0 in fbo A
+		context.enable(GL_SCISSOR_TEST);
+		context.scissor(10, 10, 12, 25);
+		context.clearStencil(0);
+		context.clear(GL_STENCIL_BUFFER_BIT);
+		context.disable(GL_SCISSOR_TEST);
+	}
+
+	// Render metaballs to fbo B
+	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+	context.disable(GL_DEPTH_TEST);
+
+	if (stencil)
+	{
+		// Render quad with stencil mask == 0
+		context.enable(GL_STENCIL_TEST);
+		context.stencilFunc(GL_EQUAL, 0, 0xffu);
+		context.useProgram(flatShaderID);
+		flatShader.setColor(context, flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+		sglr::drawQuad(context, flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+		context.disable(GL_STENCIL_TEST);
+	}
+
+	if (m_config.colorType == GL_TEXTURE_2D)
+	{
+		// Render both to screen
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		context.bindTexture(GL_TEXTURE_2D, fboA.getColorBuffer());
+		sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f));
+		context.bindTexture(GL_TEXTURE_2D, fboB.getColorBuffer());
+		sglr::drawQuad(context, texShaderID, Vec3(0.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+	{
+		// Read results from fbo B
+		readPixels(context, dst, 0, 0, width, height, glu::mapGLInternalFormat(m_config.colorFormat), Vec4(1.0f), Vec4(0.0f));
+	}
+}
+
+#if 0
+class TexSubImageAfterRenderTest : public FboRenderCase
+{
+public:
+					TexSubImageAfterRenderTest		(Context& context, const FboConfig& config);
+	virtual			~TexSubImageAfterRenderTest		(void) {}
+
+	void			render							(sglr::Context& context, Surface& dst);
+};
+
+TexSubImageAfterRenderTest::TexSubImageAfterRenderTest (Context& context, const FboConfig& config)
+	: FboRenderCase(context, (string("after_render_") + config.getName()).c_str(), "TexSubImage after rendering to texture", config)
+{
+}
+
+void TexSubImageAfterRenderTest::render (sglr::Context& context, Surface& dst)
+{
+	using sglr::TexturedQuadOp;
+
+	bool isRGBA = true;
+
+	Surface fourQuads(Surface::PIXELFORMAT_RGB, 64, 64);
+	tcu::SurfaceUtil::fillWithFourQuads(fourQuads);
+
+	Surface metaballs(isRGBA ? Surface::PIXELFORMAT_RGBA : Surface::PIXELFORMAT_RGB, 64, 64);
+	tcu::SurfaceUtil::fillWithMetaballs(metaballs, 5, 3);
+
+	deUint32 fourQuadsTex = 1;
+	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, fourQuads);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 fboTex = 2;
+	context.bindTexture(GL_TEXTURE_2D, fboTex);
+	context.texImage2D(GL_TEXTURE_2D, 0, isRGBA ? GL_RGBA : GL_RGB, 128, 128);
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTex, 0);
+
+	// Render to fbo
+	context.viewport(0, 0, 128, 128);
+	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
+	context.draw(TexturedQuadOp(-1.0f, -1.0f, 1.0f, 1.0f, 0));
+
+	// Update texture using TexSubImage2D
+	context.bindTexture(GL_TEXTURE_2D, fboTex);
+	context.texSubImage2D(GL_TEXTURE_2D, 0, 32, 32, metaballs);
+
+	// Draw to screen
+	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	context.viewport(0, 0, context.getWidth(), context.getHeight());
+	context.draw(TexturedQuadOp(-1.0f, -1.0f, 1.0f, 1.0f, 0));
+	context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+}
+
+class TexSubImageBetweenRenderTest : public FboRenderCase
+{
+public:
+					TexSubImageBetweenRenderTest		(Context& context, const FboConfig& config);
+	virtual			~TexSubImageBetweenRenderTest		(void) {}
+
+	void			render								(sglr::Context& context, Surface& dst);
+};
+
+TexSubImageBetweenRenderTest::TexSubImageBetweenRenderTest (Context& context, const FboConfig& config)
+	: FboRenderCase(context, (string("between_render_") + config.getName()).c_str(), "TexSubImage between rendering calls", config)
+{
+}
+
+void TexSubImageBetweenRenderTest::render (sglr::Context& context, Surface& dst)
+{
+	using sglr::TexturedQuadOp;
+	using sglr::BlendTextureOp;
+
+	bool isRGBA = true;
+
+	Surface fourQuads(Surface::PIXELFORMAT_RGB, 64, 64);
+	tcu::SurfaceUtil::fillWithFourQuads(fourQuads);
+
+	Surface metaballs(isRGBA ? Surface::PIXELFORMAT_RGBA : Surface::PIXELFORMAT_RGB, 64, 64);
+	tcu::SurfaceUtil::fillWithMetaballs(metaballs, 5, 3);
+
+	Surface metaballs2(Surface::PIXELFORMAT_RGBA, 64, 64);
+	tcu::SurfaceUtil::fillWithMetaballs(metaballs2, 5, 4);
+
+	deUint32 metaballsTex = 3;
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, metaballs2);
+
+	deUint32 fourQuadsTex = 1;
+	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, fourQuads);
+
+	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
+
+	deUint32 fboTex = 2;
+	context.bindTexture(GL_TEXTURE_2D, fboTex);
+	context.texImage2D(GL_TEXTURE_2D, 0, isRGBA ? GL_RGBA : GL_RGB, 128, 128);
+	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTex, 0);
+
+	// Render to fbo
+	context.viewport(0, 0, 128, 128);
+	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
+	context.draw(TexturedQuadOp(-1.0f, -1.0f, 1.0f, 1.0f, 0));
+
+	// Update texture using TexSubImage2D
+	context.bindTexture(GL_TEXTURE_2D, fboTex);
+	context.texSubImage2D(GL_TEXTURE_2D, 0, 32, 32, metaballs);
+
+	// Render again to fbo
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	context.draw(BlendTextureOp(0));
+
+	// Draw to screen
+	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	context.viewport(0, 0, context.getWidth(), context.getHeight());
+	context.bindTexture(GL_TEXTURE_2D, fboTex);
+	context.draw(TexturedQuadOp(-1.0f, -1.0f, 1.0f, 1.0f, 0));
+
+	context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+}
+#endif
+
+class ResizeTest : public FboRenderCase
+{
+public:
+					ResizeTest				(Context& context, const FboConfig& config);
+	virtual			~ResizeTest				(void) {}
+
+	void			render					(sglr::Context& context, Surface& dst);
+};
+
+ResizeTest::ResizeTest (Context& context, const FboConfig& config)
+	: FboRenderCase	(context, config.getName().c_str(), "Resize framebuffer", config)
+{
+}
+
+void ResizeTest::render (sglr::Context& context, Surface& dst)
+{
+	tcu::TextureFormat		colorFormat			= glu::mapGLInternalFormat(m_config.colorFormat);
+	glu::DataType			fboSamplerType		= glu::getSampler2DType(colorFormat);
+	glu::DataType			fboOutputType		= getFragmentOutputType(colorFormat);
+	tcu::TextureFormatInfo	fboRangeInfo		= tcu::getTextureFormatInfo(colorFormat);
+	Vec4					fboOutScale			= fboRangeInfo.valueMax - fboRangeInfo.valueMin;
+	Vec4					fboOutBias			= fboRangeInfo.valueMin;
+
+	Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, fboOutputType);
+	Texture2DShader			texFromFboShader	(DataTypes() << fboSamplerType, glu::TYPE_FLOAT_VEC4);
+	FlatColorShader			flatShader			(fboOutputType);
+	deUint32				texToFboShaderID	= context.createProgram(&texToFboShader);
+	deUint32				texFromFboShaderID	= context.createProgram(&texFromFboShader);
+	deUint32				flatShaderID		= context.createProgram(&flatShader);
+
+	deUint32				quadsTex			= 1;
+	deUint32				metaballsTex		= 2;
+	bool					depth				= (m_config.buffers & GL_DEPTH_BUFFER_BIT)		!= 0;
+	bool					stencil				= (m_config.buffers & GL_STENCIL_BUFFER_BIT)	!= 0;
+	int						initialWidth		= 128;
+	int						initialHeight		= 128;
+	int						newWidth			= 64;
+	int						newHeight			= 32;
+
+	texToFboShader.setOutScaleBias(fboOutScale, fboOutBias);
+	texFromFboShader.setTexScaleBias(0, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);
+
+	createQuadsTex2D(context, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+	createMetaballsTex2D(context, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 32, 32);
+
+	Framebuffer fbo(context, m_config, initialWidth, initialHeight);
+	fbo.checkCompleteness();
+
+	// Setup shaders
+	texToFboShader.setUniforms	(context, texToFboShaderID);
+	texFromFboShader.setUniforms(context, texFromFboShaderID);
+	flatShader.setColor			(context, flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f) * fboOutScale + fboOutBias);
+
+	// Render quads
+	context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	context.viewport(0, 0, initialWidth, initialHeight);
+	clearColorBuffer(context, colorFormat, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+	context.clear(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	sglr::drawQuad(context, texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	if (fbo.getConfig().colorType == GL_TEXTURE_2D)
+	{
+		// Render fbo to screen
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		context.bindTexture(GL_TEXTURE_2D, fbo.getColorBuffer());
+		sglr::drawQuad(context, texFromFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		// Restore binding
+		context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	}
+
+	// Resize buffers
+	switch (fbo.getConfig().colorType)
+	{
+		case GL_TEXTURE_2D:
+			context.bindTexture(GL_TEXTURE_2D, fbo.getColorBuffer());
+			context.texImage2D(GL_TEXTURE_2D, 0, fbo.getConfig().colorFormat, newWidth, newHeight);
+			break;
+
+		case GL_RENDERBUFFER:
+			context.bindRenderbuffer(GL_RENDERBUFFER, fbo.getColorBuffer());
+			context.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().colorFormat, newWidth, newHeight);
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	if (depth || stencil)
+	{
+		switch (fbo.getConfig().depthStencilType)
+		{
+			case GL_TEXTURE_2D:
+				context.bindTexture(GL_TEXTURE_2D, fbo.getDepthStencilBuffer());
+				context.texImage2D(GL_TEXTURE_2D, 0, fbo.getConfig().depthStencilFormat, newWidth, newHeight);
+				break;
+
+			case GL_RENDERBUFFER:
+				context.bindRenderbuffer(GL_RENDERBUFFER, fbo.getDepthStencilBuffer());
+				context.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().depthStencilFormat, newWidth, newHeight);
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	// Render to resized fbo
+	context.viewport(0, 0, newWidth, newHeight);
+	clearColorBuffer(context, colorFormat, tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+	context.clear(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	context.enable(GL_DEPTH_TEST);
+
+	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	sglr::drawQuad(context, texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+
+	context.bindTexture(GL_TEXTURE_2D, quadsTex);
+	sglr::drawQuad(context, texToFboShaderID, Vec3(0.0f, 0.0f, -1.0f), Vec3(+1.0f, +1.0f, 1.0f));
+
+	context.disable(GL_DEPTH_TEST);
+
+	if (stencil)
+	{
+		context.enable(GL_SCISSOR_TEST);
+		context.clearStencil(1);
+		context.scissor(10, 10, 5, 15);
+		context.clear(GL_STENCIL_BUFFER_BIT);
+		context.disable(GL_SCISSOR_TEST);
+
+		context.enable(GL_STENCIL_TEST);
+		context.stencilFunc(GL_EQUAL, 1, 0xffu);
+		sglr::drawQuad(context, flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+		context.disable(GL_STENCIL_TEST);
+	}
+
+	if (m_config.colorType == GL_TEXTURE_2D)
+	{
+		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		context.viewport(0, 0, context.getWidth(), context.getHeight());
+		context.bindTexture(GL_TEXTURE_2D, fbo.getColorBuffer());
+		sglr::drawQuad(context, texFromFboShaderID, Vec3(-0.5f, -0.5f, 0.0f), Vec3(0.5f, 0.5f, 0.0f));
+		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
+	}
+	else
+		readPixels(context, dst, 0, 0, newWidth, newHeight, colorFormat, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);
+}
+
+class RecreateBuffersTest : public FboRenderCase
+{
+public:
+					RecreateBuffersTest			(Context& context, const FboConfig& config, deUint32 buffers, bool rebind);
+	virtual			~RecreateBuffersTest		(void) {}
+
+	void			render						(sglr::Context& context, Surface& dst);
+
+private:
+	deUint32		m_buffers;
+	bool			m_rebind;
+};
+
+RecreateBuffersTest::RecreateBuffersTest (Context& context, const FboConfig& config, deUint32 buffers, bool rebind)
+	: FboRenderCase		(context, (string(config.getName()) + (rebind ? "" : "_no_rebind")).c_str(), "Recreate buffers", config)
+	, m_buffers			(buffers)
+	, m_rebind			(rebind)
+{
+}
+
+void RecreateBuffersTest::render (sglr::Context& ctx, Surface& dst)
+{
+	tcu::TextureFormat		colorFormat			= glu::mapGLInternalFormat(m_config.colorFormat);
+	glu::DataType			fboSamplerType		= glu::getSampler2DType(colorFormat);
+	glu::DataType			fboOutputType		= getFragmentOutputType(colorFormat);
+	tcu::TextureFormatInfo	fboRangeInfo		= tcu::getTextureFormatInfo(colorFormat);
+	Vec4					fboOutScale			= fboRangeInfo.valueMax - fboRangeInfo.valueMin;
+	Vec4					fboOutBias			= fboRangeInfo.valueMin;
+
+	Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, fboOutputType);
+	Texture2DShader			texFromFboShader	(DataTypes() << fboSamplerType, glu::TYPE_FLOAT_VEC4);
+	FlatColorShader			flatShader			(fboOutputType);
+	deUint32				texToFboShaderID	= ctx.createProgram(&texToFboShader);
+	deUint32				texFromFboShaderID	= ctx.createProgram(&texFromFboShader);
+	deUint32				flatShaderID		= ctx.createProgram(&flatShader);
+
+	int						width				= 128;
+	int						height				= 128;
+	deUint32				metaballsTex		= 1;
+	deUint32				quadsTex			= 2;
+	bool					stencil				= (m_config.buffers & GL_STENCIL_BUFFER_BIT) != 0;
+
+	createQuadsTex2D(ctx, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+	createMetaballsTex2D(ctx, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
+
+	Framebuffer fbo(ctx, m_config, width, height);
+	fbo.checkCompleteness();
+
+	// Setup shaders
+	texToFboShader.setOutScaleBias(fboOutScale, fboOutBias);
+	texFromFboShader.setTexScaleBias(0, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);
+	texToFboShader.setUniforms	(ctx, texToFboShaderID);
+	texFromFboShader.setUniforms(ctx, texFromFboShaderID);
+	flatShader.setColor			(ctx, flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f) * fboOutScale + fboOutBias);
+
+	// Draw scene
+	ctx.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+	ctx.viewport(0, 0, width, height);
+	clearColorBuffer(ctx, colorFormat, tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+	ctx.clear(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	ctx.enable(GL_DEPTH_TEST);
+
+	ctx.bindTexture(GL_TEXTURE_2D, quadsTex);
+	sglr::drawQuad(ctx, texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+	ctx.disable(GL_DEPTH_TEST);
+
+	if (stencil)
+	{
+		ctx.enable(GL_SCISSOR_TEST);
+		ctx.scissor(width/4, height/4, width/2, height/2);
+		ctx.clearStencil(1);
+		ctx.clear(GL_STENCIL_BUFFER_BIT);
+		ctx.disable(GL_SCISSOR_TEST);
+	}
+
+	// Recreate buffers
+	if (!m_rebind)
+		ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	DE_ASSERT((m_buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)) == 0 ||
+			  (m_buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)) == (m_config.buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)));
+
+	// Recreate.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		deUint32	bit		= ndx == 0 ? GL_COLOR_BUFFER_BIT
+									   : (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+		deUint32	type	= ndx == 0 ? fbo.getConfig().colorType
+									   : fbo.getConfig().depthStencilType;
+		deUint32	format	= ndx == 0 ? fbo.getConfig().colorFormat
+									   : fbo.getConfig().depthStencilFormat;
+		deUint32	buf		= ndx == 0 ? fbo.getColorBuffer()
+									   : fbo.getDepthStencilBuffer();
+
+		if ((m_buffers & bit) == 0)
+			continue;
+
+		switch (type)
+		{
+			case GL_TEXTURE_2D:
+				ctx.deleteTextures(1, &buf);
+				ctx.bindTexture(GL_TEXTURE_2D, buf);
+				ctx.texImage2D(GL_TEXTURE_2D, 0, format, width, height);
+				ctx.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+				ctx.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+				break;
+
+			case GL_RENDERBUFFER:
+				ctx.deleteRenderbuffers(1, &buf);
+				ctx.bindRenderbuffer(GL_RENDERBUFFER, buf);
+				ctx.renderbufferStorage(GL_RENDERBUFFER, format, width, height);
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	// Rebind.
+	if (m_rebind)
+	{
+		for (int ndx = 0; ndx < 3; ndx++)
+		{
+			deUint32	bit		= ndx == 0 ? GL_COLOR_BUFFER_BIT	:
+								  ndx == 1 ? GL_DEPTH_BUFFER_BIT	:
+								  ndx == 2 ? GL_STENCIL_BUFFER_BIT	: 0;
+			deUint32	point	= ndx == 0 ? GL_COLOR_ATTACHMENT0	:
+								  ndx == 1 ? GL_DEPTH_ATTACHMENT	:
+								  ndx == 2 ? GL_STENCIL_ATTACHMENT	: 0;
+			deUint32	type	= ndx == 0 ? fbo.getConfig().colorType
+										   : fbo.getConfig().depthStencilType;
+			deUint32	buf		= ndx == 0 ? fbo.getColorBuffer()
+										   : fbo.getDepthStencilBuffer();
+
+			if ((m_buffers & bit) == 0)
+				continue;
+
+			switch (type)
+			{
+				case GL_TEXTURE_2D:
+					ctx.framebufferTexture2D(GL_FRAMEBUFFER, point, GL_TEXTURE_2D, buf, 0);
+					break;
+
+				case GL_RENDERBUFFER:
+					ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, point, GL_RENDERBUFFER, buf);
+					break;
+
+				default:
+					DE_ASSERT(false);
+			}
+		}
+	}
+
+	if (!m_rebind)
+		ctx.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
+
+	ctx.clearStencil(0);
+	ctx.clear(m_buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)); // \note Clear only buffers that were re-created
+	if (m_buffers & GL_COLOR_BUFFER_BIT)
+	{
+		// Clearing of integer buffers is undefined so do clearing by rendering flat color.
+		sglr::drawQuad(ctx, flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+	}
+
+	ctx.enable(GL_DEPTH_TEST);
+
+	if (stencil)
+	{
+		// \note Stencil test enabled only if we have stencil buffer
+		ctx.enable(GL_STENCIL_TEST);
+		ctx.stencilFunc(GL_EQUAL, 0, 0xffu);
+	}
+	ctx.bindTexture(GL_TEXTURE_2D, metaballsTex);
+	sglr::drawQuad(ctx, texToFboShaderID, Vec3(-1.0f, -1.0f, 1.0f), Vec3(1.0f, 1.0f, -1.0f));
+	if (stencil)
+		ctx.disable(GL_STENCIL_TEST);
+
+	ctx.disable(GL_DEPTH_TEST);
+
+	if (fbo.getConfig().colorType == GL_TEXTURE_2D)
+	{
+		// Unbind fbo
+		ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+		// Draw to screen
+		ctx.bindTexture(GL_TEXTURE_2D, fbo.getColorBuffer());
+		ctx.viewport(0, 0, ctx.getWidth(), ctx.getHeight());
+		sglr::drawQuad(ctx, texFromFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		// Read from screen
+		ctx.readPixels(dst, 0, 0, ctx.getWidth(), ctx.getHeight());
+	}
+	else
+	{
+		// Read from fbo
+		readPixels(ctx, dst, 0, 0, width, height, colorFormat, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);
+	}
+}
+
+} // FboCases
+
+FboRenderTestGroup::FboRenderTestGroup (Context& context)
+	: TestCaseGroup(context, "render", "Rendering Tests")
+{
+}
+
+FboRenderTestGroup::~FboRenderTestGroup (void)
+{
+}
+
+void FboRenderTestGroup::init (void)
+{
+	static const deUint32 objectTypes[] =
+	{
+		GL_TEXTURE_2D,
+		GL_RENDERBUFFER
+	};
+
+	enum FormatType
+	{
+		FORMATTYPE_FLOAT = 0,
+		FORMATTYPE_FIXED,
+		FORMATTYPE_INT,
+		FORMATTYPE_UINT,
+
+		FORMATTYPE_LAST
+	};
+
+	// Required by specification.
+	static const struct
+	{
+		deUint32	format;
+		FormatType	type;
+	} colorFormats[] =
+	{
+		{ GL_RGBA32F,			FORMATTYPE_FLOAT	},
+		{ GL_RGBA32I,			FORMATTYPE_INT		},
+		{ GL_RGBA32UI,			FORMATTYPE_UINT		},
+		{ GL_RGBA16F,			FORMATTYPE_FLOAT	},
+		{ GL_RGBA16I,			FORMATTYPE_INT		},
+		{ GL_RGBA16UI,			FORMATTYPE_UINT		},
+		{ GL_RGB16F,			FORMATTYPE_FLOAT	},
+		{ GL_RGBA8,				FORMATTYPE_FIXED	},
+		{ GL_RGBA8I,			FORMATTYPE_INT		},
+		{ GL_RGBA8UI,			FORMATTYPE_UINT		},
+		{ GL_SRGB8_ALPHA8,		FORMATTYPE_FIXED	},
+		{ GL_RGB10_A2,			FORMATTYPE_FIXED	},
+		{ GL_RGB10_A2UI,		FORMATTYPE_UINT		},
+		{ GL_RGBA4,				FORMATTYPE_FIXED	},
+		{ GL_RGB5_A1,			FORMATTYPE_FIXED	},
+		{ GL_RGB8,				FORMATTYPE_FIXED	},
+		{ GL_RGB565,			FORMATTYPE_FIXED	},
+		{ GL_R11F_G11F_B10F,	FORMATTYPE_FLOAT	},
+		{ GL_RG32F,				FORMATTYPE_FLOAT	},
+		{ GL_RG32I,				FORMATTYPE_INT		},
+		{ GL_RG32UI,			FORMATTYPE_UINT		},
+		{ GL_RG16F,				FORMATTYPE_FLOAT	},
+		{ GL_RG16I,				FORMATTYPE_INT		},
+		{ GL_RG16UI,			FORMATTYPE_UINT		},
+		{ GL_RG8,				FORMATTYPE_FLOAT	},
+		{ GL_RG8I,				FORMATTYPE_INT		},
+		{ GL_RG8UI,				FORMATTYPE_UINT		},
+		{ GL_R32F,				FORMATTYPE_FLOAT	},
+		{ GL_R32I,				FORMATTYPE_INT		},
+		{ GL_R32UI,				FORMATTYPE_UINT		},
+		{ GL_R16F,				FORMATTYPE_FLOAT	},
+		{ GL_R16I,				FORMATTYPE_INT		},
+		{ GL_R16UI,				FORMATTYPE_UINT		},
+		{ GL_R8,				FORMATTYPE_FLOAT	},
+		{ GL_R8I,				FORMATTYPE_INT		},
+		{ GL_R8UI,				FORMATTYPE_UINT		}
+	};
+
+	static const struct
+	{
+		deUint32	format;
+		bool		depth;
+		bool		stencil;
+	} depthStencilFormats[] =
+	{
+		{ GL_DEPTH_COMPONENT32F,	true,	false	},
+		{ GL_DEPTH_COMPONENT24,		true,	false	},
+		{ GL_DEPTH_COMPONENT16,		true,	false	},
+		{ GL_DEPTH32F_STENCIL8,		true,	true	},
+		{ GL_DEPTH24_STENCIL8,		true,	true	},
+		{ GL_STENCIL_INDEX8,		false,	true	}
+	};
+
+	using namespace FboCases;
+
+	// .stencil_clear
+	tcu::TestCaseGroup* stencilClearGroup = new tcu::TestCaseGroup(m_testCtx, "stencil_clear", "Stencil buffer clears");
+	addChild(stencilClearGroup);
+	for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); fmtNdx++)
+	{
+		deUint32	colorType	= GL_TEXTURE_2D;
+		deUint32	stencilType	= GL_RENDERBUFFER;
+		deUint32	colorFmt	= GL_RGBA8;
+
+		if (!depthStencilFormats[fmtNdx].stencil)
+			continue;
+
+		FboConfig config(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT, colorType, colorFmt, stencilType, depthStencilFormats[fmtNdx].format);
+		stencilClearGroup->addChild(new StencilClearsTest(m_context, config));
+	}
+
+	// .shared_colorbuffer_clear
+	tcu::TestCaseGroup* sharedColorbufferClearGroup = new tcu::TestCaseGroup(m_testCtx, "shared_colorbuffer_clear", "Shader colorbuffer clears");
+	addChild(sharedColorbufferClearGroup);
+	for (int colorFmtNdx = 0; colorFmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); colorFmtNdx++)
+	{
+		// Clearing of integer buffers is undefined.
+		if (colorFormats[colorFmtNdx].type == FORMATTYPE_INT || colorFormats[colorFmtNdx].type == FORMATTYPE_UINT)
+			continue;
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
+		{
+			FboConfig config(GL_COLOR_BUFFER_BIT, objectTypes[typeNdx], colorFormats[colorFmtNdx].format, GL_NONE, GL_NONE);
+			sharedColorbufferClearGroup->addChild(new SharedColorbufferClearsTest(m_context, config));
+		}
+	}
+
+	// .shared_colorbuffer
+	tcu::TestCaseGroup* sharedColorbufferGroup = new tcu::TestCaseGroup(m_testCtx, "shared_colorbuffer", "Shared colorbuffer tests");
+	addChild(sharedColorbufferGroup);
+	for (int colorFmtNdx = 0; colorFmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); colorFmtNdx++)
+	{
+		deUint32	depthStencilType	= GL_RENDERBUFFER;
+		deUint32	depthStencilFormat	= GL_DEPTH24_STENCIL8;
+
+		// Blending with integer buffers and fp32 targets is not supported.
+		if (colorFormats[colorFmtNdx].type == FORMATTYPE_INT	||
+			colorFormats[colorFmtNdx].type == FORMATTYPE_UINT	||
+			colorFormats[colorFmtNdx].format == GL_RGBA32F		||
+			colorFormats[colorFmtNdx].format == GL_RGB32F		||
+			colorFormats[colorFmtNdx].format == GL_RG32F		||
+			colorFormats[colorFmtNdx].format == GL_R32F)
+			continue;
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
+		{
+			FboConfig colorOnlyConfig			(GL_COLOR_BUFFER_BIT,											objectTypes[typeNdx], colorFormats[colorFmtNdx].format, GL_NONE, GL_NONE);
+			FboConfig colorDepthConfig			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT,						objectTypes[typeNdx], colorFormats[colorFmtNdx].format, depthStencilType, depthStencilFormat);
+			FboConfig colorDepthStencilConfig	(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT,	objectTypes[typeNdx], colorFormats[colorFmtNdx].format, depthStencilType, depthStencilFormat);
+
+			sharedColorbufferGroup->addChild(new SharedColorbufferTest(m_context, colorOnlyConfig));
+			sharedColorbufferGroup->addChild(new SharedColorbufferTest(m_context, colorDepthConfig));
+			sharedColorbufferGroup->addChild(new SharedColorbufferTest(m_context, colorDepthStencilConfig));
+		}
+	}
+
+	// .shared_depth_stencil
+	tcu::TestCaseGroup* sharedDepthStencilGroup = new tcu::TestCaseGroup(m_testCtx, "shared_depth_stencil", "Shared depth and stencil buffers");
+	addChild(sharedDepthStencilGroup);
+	for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); fmtNdx++)
+	{
+		deUint32	colorType		= GL_TEXTURE_2D;
+		deUint32	colorFmt		= GL_RGBA8;
+		bool		depth			= depthStencilFormats[fmtNdx].depth;
+		bool		stencil			= depthStencilFormats[fmtNdx].stencil;
+
+		if (!depth)
+			continue; // Not verified.
+
+		// Depth and stencil: both rbo and textures
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
+		{
+			FboConfig config(GL_COLOR_BUFFER_BIT|(depth ? GL_DEPTH_BUFFER_BIT : 0)|(stencil ? GL_STENCIL_BUFFER_BIT : 0), colorType, colorFmt, objectTypes[typeNdx], depthStencilFormats[fmtNdx].format);
+			sharedDepthStencilGroup->addChild(new SharedDepthStencilTest(m_context, config));
+		}
+	}
+
+	// .resize
+	tcu::TestCaseGroup* resizeGroup = new tcu::TestCaseGroup(m_testCtx, "resize", "FBO resize tests");
+	addChild(resizeGroup);
+	for (int colorFmtNdx = 0; colorFmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); colorFmtNdx++)
+	{
+		deUint32 colorFormat = colorFormats[colorFmtNdx].format;
+
+		// Color-only.
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
+		{
+			FboConfig config(GL_COLOR_BUFFER_BIT, objectTypes[typeNdx], colorFormat, GL_NONE, GL_NONE);
+			resizeGroup->addChild(new ResizeTest(m_context, config));
+		}
+
+		// For selected color formats tests depth & stencil variants.
+		if (colorFormat == GL_RGBA8 || colorFormat == GL_RGBA16F)
+		{
+			for (int depthStencilFmtNdx = 0; depthStencilFmtNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); depthStencilFmtNdx++)
+			{
+				deUint32	colorType		= GL_TEXTURE_2D;
+				bool		depth			= depthStencilFormats[depthStencilFmtNdx].depth;
+				bool		stencil			= depthStencilFormats[depthStencilFmtNdx].stencil;
+
+				// Depth and stencil: both rbo and textures
+				for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
+				{
+					if (!depth && objectTypes[typeNdx] != GL_RENDERBUFFER)
+						continue; // Not supported.
+
+					FboConfig config(GL_COLOR_BUFFER_BIT|(depth ? GL_DEPTH_BUFFER_BIT : 0)|(stencil ? GL_STENCIL_BUFFER_BIT : 0),
+									 colorType, colorFormat,
+									 objectTypes[typeNdx], depthStencilFormats[depthStencilFmtNdx].format);
+					resizeGroup->addChild(new ResizeTest(m_context, config));
+				}
+			}
+		}
+	}
+
+	// .recreate_color
+	tcu::TestCaseGroup* recreateColorGroup = new tcu::TestCaseGroup(m_testCtx, "recreate_color", "Recreate colorbuffer tests");
+	addChild(recreateColorGroup);
+	for (int colorFmtNdx = 0; colorFmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); colorFmtNdx++)
+	{
+		deUint32	colorFormat			= colorFormats[colorFmtNdx].format;
+		deUint32	depthStencilFormat	= GL_DEPTH24_STENCIL8;
+		deUint32	depthStencilType	= GL_RENDERBUFFER;
+
+		// Color-only.
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
+		{
+			FboConfig config(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT, objectTypes[typeNdx], colorFormat, depthStencilType, depthStencilFormat);
+			recreateColorGroup->addChild(new RecreateBuffersTest(m_context, config, GL_COLOR_BUFFER_BIT, true /* rebind */));
+		}
+	}
+
+	// .recreate_depth_stencil
+	tcu::TestCaseGroup* recreateDepthStencilGroup = new tcu::TestCaseGroup(m_testCtx, "recreate_depth_stencil", "Recreate depth and stencil buffers");
+	addChild(recreateDepthStencilGroup);
+	for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); fmtNdx++)
+	{
+		deUint32	colorType		= GL_TEXTURE_2D;
+		deUint32	colorFmt		= GL_RGBA8;
+		bool		depth			= depthStencilFormats[fmtNdx].depth;
+		bool		stencil			= depthStencilFormats[fmtNdx].stencil;
+
+		// Depth and stencil: both rbo and textures
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
+		{
+			if (!depth && objectTypes[typeNdx] != GL_RENDERBUFFER)
+				continue;
+
+			FboConfig config(GL_COLOR_BUFFER_BIT|(depth ? GL_DEPTH_BUFFER_BIT : 0)|(stencil ? GL_STENCIL_BUFFER_BIT : 0), colorType, colorFmt, objectTypes[typeNdx], depthStencilFormats[fmtNdx].format);
+			recreateDepthStencilGroup->addChild(new RecreateBuffersTest(m_context, config, (depth ? GL_DEPTH_BUFFER_BIT : 0)|(stencil ? GL_STENCIL_BUFFER_BIT : 0), true /* rebind */));
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboRenderTest.hpp b/modules/gles3/functional/es3fFboRenderTest.hpp
new file mode 100644
index 0000000..b389037
--- /dev/null
+++ b/modules/gles3/functional/es3fFboRenderTest.hpp
@@ -0,0 +1,49 @@
+#ifndef _ES3FFBORENDERTEST_HPP
+#define _ES3FFBORENDERTEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer Object Rendering Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FboRenderTestGroup : public TestCaseGroup
+{
+public:
+					FboRenderTestGroup			(Context& context);
+	virtual			~FboRenderTestGroup			(void);
+
+	virtual void	init						(void);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFBORENDERTEST_HPP
diff --git a/modules/gles3/functional/es3fFboStateQueryTests.cpp b/modules/gles3/functional/es3fFboStateQueryTests.cpp
new file mode 100644
index 0000000..2ff8064
--- /dev/null
+++ b/modules/gles3/functional/es3fFboStateQueryTests.cpp
@@ -0,0 +1,1030 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Fbo state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deMath.h"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+void checkAttachmentComponentSizeAtLeast (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLenum target, GLenum attachment, int r, int g, int b, int a, int d, int s)
+{
+	using tcu::TestLog;
+
+	const int referenceSizes[] = {r, g, b, a, d, s};
+	const GLenum paramNames[] =
+	{
+		GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE,	GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE,
+		GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE,
+		GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(referenceSizes) == DE_LENGTH_OF_ARRAY(paramNames));
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(referenceSizes); ++ndx)
+	{
+		if (referenceSizes[ndx] == -1)
+			continue;
+
+		StateQueryMemoryWriteGuard<GLint> state;
+		gl.glGetFramebufferAttachmentParameteriv(target, attachment, paramNames[ndx], &state);
+
+		if (!state.verifyValidity(testCtx))
+		{
+			gl.glGetError(); // just log the error
+			continue;
+		}
+
+		if (state < referenceSizes[ndx])
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: Expected greater or equal to " << referenceSizes[ndx] << "; got " << state << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+		}
+	}
+}
+
+void checkAttachmentComponentSizeExactly (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLenum target, GLenum attachment, int r, int g, int b, int a, int d, int s)
+{
+	using tcu::TestLog;
+
+	const int referenceSizes[] = {r, g, b, a, d, s};
+	const GLenum paramNames[] =
+	{
+		GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE,	GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE,
+		GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE,
+		GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(referenceSizes) == DE_LENGTH_OF_ARRAY(paramNames));
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(referenceSizes); ++ndx)
+	{
+		if (referenceSizes[ndx] == -1)
+			continue;
+
+		StateQueryMemoryWriteGuard<GLint> state;
+		gl.glGetFramebufferAttachmentParameteriv(target, attachment, paramNames[ndx], &state);
+
+		if (!state.verifyValidity(testCtx))
+		{
+			gl.glGetError(); // just log the error
+			continue;
+		}
+
+		if (state != referenceSizes[ndx])
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << referenceSizes[ndx] << "; got " << state << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+		}
+	}
+}
+
+void checkIntEquals (tcu::TestContext& testCtx, GLint got, GLint expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void checkIntEqualsAny (tcu::TestContext& testCtx, GLint got, GLint expected0, GLint expected1)
+{
+	using tcu::TestLog;
+
+	if (got != expected0 && got != expected1)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected0 << " or " << expected1 << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void checkAttachmentParam(tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLenum target, GLenum attachment, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetFramebufferAttachmentParameteriv(target, attachment, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntEquals(testCtx, state, reference);
+}
+
+void checkColorAttachmentParam(tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLenum target, GLenum pname, GLenum reference)
+{
+	checkAttachmentParam(testCtx, gl, target, GL_COLOR_ATTACHMENT0, pname, reference);
+}
+
+class DefaultFramebufferCase : public ApiCase
+{
+public:
+	DefaultFramebufferCase (Context& context, const char* name, const char* description, GLenum framebufferTarget)
+		: ApiCase				(context, name, description)
+		, m_framebufferTarget	(framebufferTarget)
+	{
+	}
+
+	void test (void)
+	{
+		const bool		hasColorBuffer =	m_context.getRenderTarget().getPixelFormat().redBits   > 0 ||
+											m_context.getRenderTarget().getPixelFormat().greenBits > 0 ||
+											m_context.getRenderTarget().getPixelFormat().blueBits  > 0 ||
+											m_context.getRenderTarget().getPixelFormat().alphaBits > 0;
+		const GLenum	attachments[] =
+		{
+			GL_BACK,
+			GL_DEPTH,
+			GL_STENCIL
+		};
+		const bool		attachmentExists[] =
+		{
+			hasColorBuffer,
+			m_context.getRenderTarget().getDepthBits() > 0,
+			m_context.getRenderTarget().getStencilBits() > 0
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(attachments); ++ndx)
+		{
+			StateQueryMemoryWriteGuard<GLint> state;
+			glGetFramebufferAttachmentParameteriv(m_framebufferTarget, attachments[ndx], GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &state);
+			expectError(GL_NO_ERROR);
+
+			if (state.verifyValidity(m_testCtx))
+			{
+				if (attachmentExists[ndx])
+				{
+					checkIntEquals(m_testCtx, state, GL_FRAMEBUFFER_DEFAULT);
+				}
+				else
+				{
+					// \note [jarkko] FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE "identifes the type of object which contains the attached image". However, it
+					// is unclear if an object of type FRAMEBUFFER_DEFAULT can contain a null image (or a 0-bits-per-pixel image). Accept both
+					// FRAMEBUFFER_DEFAULT and NONE as valid results in these cases.
+					checkIntEqualsAny(m_testCtx, state, GL_FRAMEBUFFER_DEFAULT, GL_NONE);
+				}
+			}
+		}
+	}
+
+private:
+	GLenum m_framebufferTarget;
+};
+
+class AttachmentObjectCase : public ApiCase
+{
+public:
+	AttachmentObjectCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		// initial
+		{
+			checkAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
+			checkAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 0);
+			expectError(GL_NO_ERROR);
+		}
+
+		// texture
+		{
+			GLuint textureID = 0;
+			glGenTextures(1, &textureID);
+			glBindTexture(GL_TEXTURE_2D, textureID);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, textureID);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			glDeleteTextures(1, &textureID);
+		}
+
+		// rb
+		{
+			GLuint renderbufferID = 0;
+			glGenRenderbuffers(1, &renderbufferID);
+			glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbufferID);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, renderbufferID);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			glDeleteRenderbuffers(1, &renderbufferID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class AttachmentTextureLevelCase : public ApiCase
+{
+public:
+	AttachmentTextureLevelCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		for (int mipmapLevel = 0; mipmapLevel < 7; ++mipmapLevel)
+		{
+			GLuint textureID = 0;
+			glGenTextures(1, &textureID);
+			glBindTexture(GL_TEXTURE_2D, textureID);
+			glTexStorage2D(GL_TEXTURE_2D, 7, GL_RGB8, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, mipmapLevel);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, mipmapLevel);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			glDeleteTextures(1, &textureID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class AttachmentTextureCubeMapFaceCase : public ApiCase
+{
+public:
+	AttachmentTextureCubeMapFaceCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		{
+			GLuint textureID = 0;
+			glGenTextures(1, &textureID);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
+			expectError(GL_NO_ERROR);
+
+			glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_RGB8, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			const GLenum faces[] =
+			{
+				GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
+			};
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(faces); ++ndx)
+			{
+				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, faces[ndx], textureID, 0);
+				checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, faces[ndx]);
+			}
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			glDeleteTextures(1, &textureID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class AttachmentTextureLayerCase : public ApiCase
+{
+public:
+	AttachmentTextureLayerCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		// tex3d
+		{
+			GLuint textureID = 0;
+			glGenTextures(1, &textureID);
+			glBindTexture(GL_TEXTURE_3D, textureID);
+			glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, 16, 16, 16);
+
+			for (int layer = 0; layer < 16; ++layer)
+			{
+				glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureID, 0, layer);
+				checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER, layer);
+			}
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			glDeleteTextures(1, &textureID);
+		}
+		// tex2d array
+		{
+			GLuint textureID = 0;
+			glGenTextures(1, &textureID);
+			glBindTexture(GL_TEXTURE_2D_ARRAY, textureID);
+			glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 16, 16, 16);
+
+			for (int layer = 0; layer < 16; ++layer)
+			{
+				glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureID, 0, layer);
+				checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER, layer);
+			}
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			glDeleteTextures(1, &textureID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class AttachmentTextureColorCodingCase : public ApiCase
+{
+public:
+	AttachmentTextureColorCodingCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		// rgb8 color
+		{
+			GLuint renderbufferID = 0;
+			glGenRenderbuffers(1, &renderbufferID);
+			glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbufferID);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, GL_LINEAR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			glDeleteRenderbuffers(1, &renderbufferID);
+		}
+
+		// srgb8_alpha8 color
+		{
+			GLuint renderbufferID = 0;
+			glGenRenderbuffers(1, &renderbufferID);
+			glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8_ALPHA8, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbufferID);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, GL_SRGB);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			glDeleteRenderbuffers(1, &renderbufferID);
+		}
+
+		// depth
+		{
+			GLuint renderbufferID = 0;
+			glGenRenderbuffers(1, &renderbufferID);
+			glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbufferID);
+			expectError(GL_NO_ERROR);
+
+			checkAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, GL_LINEAR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			glDeleteRenderbuffers(1, &renderbufferID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class AttachmentTextureComponentTypeCase : public ApiCase
+{
+public:
+	AttachmentTextureComponentTypeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		// color-renderable required texture formats
+		const struct RequiredColorFormat
+		{
+			GLenum internalFormat;
+			GLenum componentType;
+		} requiredColorformats[] =
+		{
+			{ GL_R8,			GL_UNSIGNED_NORMALIZED	},
+			{ GL_RG8,			GL_UNSIGNED_NORMALIZED	},
+			{ GL_RGB8,			GL_UNSIGNED_NORMALIZED	},
+			{ GL_RGB565,		GL_UNSIGNED_NORMALIZED	},
+			{ GL_RGBA4,			GL_UNSIGNED_NORMALIZED	},
+			{ GL_RGB5_A1,		GL_UNSIGNED_NORMALIZED	},
+			{ GL_RGBA8,			GL_UNSIGNED_NORMALIZED	},
+			{ GL_RGB10_A2,		GL_UNSIGNED_NORMALIZED	},
+			{ GL_RGB10_A2UI,	GL_UNSIGNED_INT			},
+			{ GL_SRGB8_ALPHA8,	GL_UNSIGNED_NORMALIZED	},
+			{ GL_R8I,			GL_INT					},
+			{ GL_R8UI,			GL_UNSIGNED_INT			},
+			{ GL_R16I,			GL_INT					},
+			{ GL_R16UI,			GL_UNSIGNED_INT			},
+			{ GL_R32I,			GL_INT					},
+			{ GL_R32UI,			GL_UNSIGNED_INT			},
+			{ GL_RG8I,			GL_INT					},
+			{ GL_RG8UI,			GL_UNSIGNED_INT			},
+			{ GL_RG16I,			GL_INT					},
+			{ GL_RG16UI,		GL_UNSIGNED_INT			},
+			{ GL_RG32I,			GL_INT					},
+			{ GL_RG32UI,		GL_UNSIGNED_INT			},
+			{ GL_RGBA8I,		GL_INT					},
+			{ GL_RGBA8UI,		GL_UNSIGNED_INT			},
+			{ GL_RGBA16I,		GL_INT					},
+			{ GL_RGBA16UI,		GL_UNSIGNED_INT			},
+			{ GL_RGBA32I,		GL_INT					},
+			{ GL_RGBA32UI,		GL_UNSIGNED_INT			}
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requiredColorformats); ++ndx)
+		{
+			const GLenum colorFormat = requiredColorformats[ndx].internalFormat;
+
+			GLuint textureID = 0;
+			glGenTextures(1, &textureID);
+			glBindTexture(GL_TEXTURE_2D, textureID);
+			glTexStorage2D(GL_TEXTURE_2D, 1, colorFormat, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, requiredColorformats[ndx].componentType);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			glDeleteTextures(1, &textureID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class AttachmentSizeInitialCase : public ApiCase
+{
+public:
+	AttachmentSizeInitialCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// check default
+		if (attachmentExists(GL_BACK))
+		{
+			checkAttachmentComponentSizeAtLeast(
+				m_testCtx,
+				*this,
+				GL_FRAMEBUFFER,
+				GL_BACK,
+				m_context.getRenderTarget().getPixelFormat().redBits,
+				m_context.getRenderTarget().getPixelFormat().greenBits,
+				m_context.getRenderTarget().getPixelFormat().blueBits,
+				m_context.getRenderTarget().getPixelFormat().alphaBits,
+				-1,
+				-1);
+			expectError(GL_NO_ERROR);
+		}
+
+		if (attachmentExists(GL_DEPTH))
+		{
+			checkAttachmentComponentSizeAtLeast(
+				m_testCtx,
+				*this,
+				GL_FRAMEBUFFER,
+				GL_DEPTH,
+				-1,
+				-1,
+				-1,
+				-1,
+				m_context.getRenderTarget().getDepthBits(),
+				-1);
+			expectError(GL_NO_ERROR);
+		}
+
+		if (attachmentExists(GL_STENCIL))
+		{
+			checkAttachmentComponentSizeAtLeast(
+				m_testCtx,
+				*this,
+				GL_FRAMEBUFFER,
+				GL_STENCIL,
+				-1,
+				-1,
+				-1,
+				-1,
+				-1,
+				m_context.getRenderTarget().getStencilBits());
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+	bool attachmentExists (GLenum attachment)
+	{
+		StateQueryMemoryWriteGuard<GLint> state;
+		glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &state);
+		expectError(GL_NO_ERROR);
+
+		return !state.isUndefined() && state != GL_NONE;
+	}
+};
+
+class AttachmentSizeCase : public ApiCase
+{
+public:
+	AttachmentSizeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	virtual void testColorAttachment (GLenum internalFormat, GLenum attachment, GLint bitsR, GLint bitsG, GLint bitsB, GLint bitsA) = DE_NULL;
+
+	virtual void testDepthAttachment (GLenum internalFormat, GLenum attachment, GLint bitsD, GLint bitsS) = DE_NULL;
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		// check some color targets
+
+		const struct ColorAttachment
+		{
+			GLenum	internalFormat;
+			int		bitsR, bitsG, bitsB, bitsA;
+		} colorAttachments[] =
+		{
+			{ GL_RGBA8,		8,	8,	8,	8 },
+			{ GL_RGB565,	5,	6,	5,	0 },
+			{ GL_RGBA4,		4,	4,	4,	4 },
+			{ GL_RGB5_A1,	5,	5,	5,	1 },
+			{ GL_RGBA8I,	8,	8,	8,	8 },
+			{ GL_RG32UI,	32,	32,	0,	0 }
+		};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorAttachments); ++ndx)
+			testColorAttachment(colorAttachments[ndx].internalFormat, GL_COLOR_ATTACHMENT0, colorAttachments[ndx].bitsR, colorAttachments[ndx].bitsG, colorAttachments[ndx].bitsB, colorAttachments[ndx].bitsA);
+
+		// check some depth targets
+
+		const struct DepthAttachment
+		{
+			GLenum	internalFormat;
+			GLenum	attachment;
+			int		dbits;
+			int		sbits;
+		} depthAttachments[] =
+		{
+			{ GL_DEPTH_COMPONENT16,		GL_DEPTH_ATTACHMENT,			16, 0 },
+			{ GL_DEPTH_COMPONENT24,		GL_DEPTH_ATTACHMENT,			24, 0 },
+			{ GL_DEPTH_COMPONENT32F,	GL_DEPTH_ATTACHMENT,			32, 0 },
+			{ GL_DEPTH24_STENCIL8,		GL_DEPTH_STENCIL_ATTACHMENT,	24, 8 },
+			{ GL_DEPTH32F_STENCIL8,		GL_DEPTH_STENCIL_ATTACHMENT,	32, 8 },
+		};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthAttachments); ++ndx)
+			testDepthAttachment(depthAttachments[ndx].internalFormat, depthAttachments[ndx].attachment, depthAttachments[ndx].dbits, depthAttachments[ndx].sbits);
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class AttachmentSizeRboCase : public AttachmentSizeCase
+{
+public:
+	AttachmentSizeRboCase (Context& context, const char* name, const char* description)
+		: AttachmentSizeCase(context, name, description)
+	{
+	}
+
+	void testColorAttachment (GLenum internalFormat, GLenum attachment, GLint bitsR, GLint bitsG, GLint bitsB, GLint bitsA)
+	{
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, 128, 128);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkAttachmentComponentSizeAtLeast	(m_testCtx, *this, GL_FRAMEBUFFER, attachment, bitsR, bitsG, bitsB, bitsA, -1, -1);
+		checkAttachmentComponentSizeExactly	(m_testCtx, *this, GL_FRAMEBUFFER, attachment, -1, -1, -1, -1, 0, 0);
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, 0);
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+
+	void testDepthAttachment (GLenum internalFormat, GLenum attachment, GLint bitsD, GLint bitsS)
+	{
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, 128, 128);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkAttachmentComponentSizeAtLeast	(m_testCtx, *this, GL_FRAMEBUFFER, attachment, -1, -1, -1, -1, bitsD, bitsS);
+		checkAttachmentComponentSizeExactly	(m_testCtx, *this, GL_FRAMEBUFFER, attachment, 0, 0, 0, 0, -1, -1);
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, 0);
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+class AttachmentSizeTextureCase : public AttachmentSizeCase
+{
+public:
+	AttachmentSizeTextureCase (Context& context, const char* name, const char* description)
+		: AttachmentSizeCase(context, name, description)
+	{
+	}
+
+	void testColorAttachment (GLenum internalFormat, GLenum attachment, GLint bitsR, GLint bitsG, GLint bitsB, GLint bitsA)
+	{
+		GLuint textureID = 0;
+		glGenTextures(1, &textureID);
+		glBindTexture(GL_TEXTURE_2D, textureID);
+		glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, 128, 128);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, textureID, 0);
+		expectError(GL_NO_ERROR);
+
+		checkAttachmentComponentSizeAtLeast	(m_testCtx, *this, GL_FRAMEBUFFER, attachment, bitsR, bitsG, bitsB, bitsA, -1, -1);
+		checkAttachmentComponentSizeExactly	(m_testCtx, *this, GL_FRAMEBUFFER, attachment, -1, -1, -1, -1, 0, 0);
+
+		glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, 0, 0);
+		glDeleteTextures(1, &textureID);
+	}
+
+	void testDepthAttachment (GLenum internalFormat, GLenum attachment, GLint bitsD, GLint bitsS)
+	{
+		// don't test stencil formats with textures
+		if (attachment == GL_DEPTH_STENCIL_ATTACHMENT)
+			return;
+
+		GLuint textureID = 0;
+		glGenTextures(1, &textureID);
+		glBindTexture(GL_TEXTURE_2D, textureID);
+		glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, 128, 128);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, textureID, 0);
+		expectError(GL_NO_ERROR);
+
+		checkAttachmentComponentSizeAtLeast	(m_testCtx, *this, GL_FRAMEBUFFER, attachment, -1, -1, -1, -1, bitsD, bitsS);
+		checkAttachmentComponentSizeExactly	(m_testCtx, *this, GL_FRAMEBUFFER, attachment, 0, 0, 0, 0, -1, -1);
+
+		glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, 0, 0);
+		glDeleteTextures(1, &textureID);
+	}
+};
+
+class UnspecifiedAttachmentTextureColorCodingCase : public ApiCase
+{
+public:
+	UnspecifiedAttachmentTextureColorCodingCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		// color
+		{
+			GLuint renderbufferID = 0;
+			glGenRenderbuffers(1, &renderbufferID);
+			glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbufferID);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, GL_LINEAR);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			glDeleteRenderbuffers(1, &renderbufferID);
+		}
+
+		// depth
+		{
+			GLuint renderbufferID = 0;
+			glGenRenderbuffers(1, &renderbufferID);
+			glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbufferID);
+			expectError(GL_NO_ERROR);
+
+			checkAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, GL_LINEAR);
+
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			glDeleteRenderbuffers(1, &renderbufferID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UnspecifiedAttachmentTextureComponentTypeCase : public ApiCase
+{
+public:
+	UnspecifiedAttachmentTextureComponentTypeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		{
+			GLuint textureID = 0;
+			glGenTextures(1, &textureID);
+			glBindTexture(GL_TEXTURE_2D, textureID);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
+			expectError(GL_NO_ERROR);
+
+			checkColorAttachmentParam(m_testCtx, *this, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, GL_NONE);
+			expectError(GL_NO_ERROR);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			glDeleteTextures(1, &textureID);
+		}
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UnspecifiedAttachmentSizeCase : public ApiCase
+{
+public:
+	UnspecifiedAttachmentSizeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	virtual void testColorAttachment (void) = DE_NULL;
+
+	virtual void testDepthAttachment (void) = DE_NULL;
+
+	void test (void)
+	{
+		GLuint framebufferID = 0;
+		glGenFramebuffers(1, &framebufferID);
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
+		expectError(GL_NO_ERROR);
+
+		// check color target
+		testColorAttachment();
+
+		// check depth target
+		testDepthAttachment();
+
+		glDeleteFramebuffers(1, &framebufferID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UnspecifiedAttachmentSizeRboCase : public UnspecifiedAttachmentSizeCase
+{
+public:
+	UnspecifiedAttachmentSizeRboCase (Context& context, const char* name, const char* description)
+		: UnspecifiedAttachmentSizeCase(context, name, description)
+	{
+	}
+
+	void testColorAttachment (void)
+	{
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkAttachmentComponentSizeExactly	(m_testCtx, *this, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0, 0, 0, 0, 0);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+
+	void testDepthAttachment (void)
+	{
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkAttachmentComponentSizeExactly	(m_testCtx, *this, GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 0, 0, 0, 0, 0, 0);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+class UnspecifiedAttachmentSizeTextureCase : public UnspecifiedAttachmentSizeCase
+{
+public:
+	UnspecifiedAttachmentSizeTextureCase (Context& context, const char* name, const char* description)
+		: UnspecifiedAttachmentSizeCase(context, name, description)
+	{
+	}
+
+	void testColorAttachment (void)
+	{
+		GLuint textureID = 0;
+		glGenTextures(1, &textureID);
+		glBindTexture(GL_TEXTURE_2D, textureID);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
+		expectError(GL_NO_ERROR);
+
+		checkAttachmentComponentSizeExactly	(m_testCtx, *this, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0, 0, 0, 0, 0);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+		glDeleteTextures(1, &textureID);
+	}
+
+	void testDepthAttachment (void)
+	{
+		GLuint textureID = 0;
+		glGenTextures(1, &textureID);
+		glBindTexture(GL_TEXTURE_2D, textureID);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textureID, 0);
+		expectError(GL_NO_ERROR);
+
+		checkAttachmentComponentSizeExactly	(m_testCtx, *this, GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 0, 0, 0, 0, 0, 0);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+		glDeleteTextures(1, &textureID);
+	}
+};
+
+} // anonymous
+
+
+FboStateQueryTests::FboStateQueryTests (Context& context)
+	: TestCaseGroup(context, "fbo", "Fbo State Query tests")
+{
+}
+
+void FboStateQueryTests::init (void)
+{
+	const struct FramebufferTarget
+	{
+		const char*	name;
+		GLenum		target;
+	} fboTargets[] =
+	{
+		{ "draw_framebuffer_", GL_DRAW_FRAMEBUFFER },
+		{ "read_framebuffer_", GL_READ_FRAMEBUFFER }
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fboTargets); ++ndx)
+		addChild(new DefaultFramebufferCase(m_context, (std::string(fboTargets[ndx].name) + "default_framebuffer").c_str(), "default framebuffer", fboTargets[ndx].target));
+
+	addChild(new AttachmentObjectCase							(m_context, "framebuffer_attachment_object",							"FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE and FRAMEBUFFER_ATTACHMENT_OBJECT_NAME"));
+	addChild(new AttachmentTextureLevelCase						(m_context, "framebuffer_attachment_texture_level",						"FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL"));
+	addChild(new AttachmentTextureCubeMapFaceCase				(m_context, "framebuffer_attachment_texture_cube_map_face",				"FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE"));
+	addChild(new AttachmentTextureLayerCase						(m_context, "framebuffer_attachment_texture_layer",						"FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER"));
+	addChild(new AttachmentTextureColorCodingCase				(m_context, "framebuffer_attachment_color_encoding",					"FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING"));
+	addChild(new AttachmentTextureComponentTypeCase				(m_context, "framebuffer_attachment_component_type",					"FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE"));
+	addChild(new AttachmentSizeInitialCase						(m_context, "framebuffer_attachment_x_size_initial",					"FRAMEBUFFER_ATTACHMENT_x_SIZE"));
+	addChild(new AttachmentSizeRboCase							(m_context, "framebuffer_attachment_x_size_rbo",						"FRAMEBUFFER_ATTACHMENT_x_SIZE"));
+	addChild(new AttachmentSizeTextureCase						(m_context, "framebuffer_attachment_x_size_texture",					"FRAMEBUFFER_ATTACHMENT_x_SIZE"));
+
+	addChild(new UnspecifiedAttachmentTextureColorCodingCase	(m_context, "framebuffer_unspecified_attachment_color_encoding",		"FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING"));
+	addChild(new UnspecifiedAttachmentTextureComponentTypeCase	(m_context, "framebuffer_unspecified_attachment_component_type",		"FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE"));
+	addChild(new UnspecifiedAttachmentSizeRboCase				(m_context, "framebuffer_unspecified_attachment_x_size_rbo",			"FRAMEBUFFER_ATTACHMENT_x_SIZE"));
+	addChild(new UnspecifiedAttachmentSizeTextureCase			(m_context, "framebuffer_unspecified_attachment_x_size_texture",		"FRAMEBUFFER_ATTACHMENT_x_SIZE"));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboStateQueryTests.hpp b/modules/gles3/functional/es3fFboStateQueryTests.hpp
new file mode 100644
index 0000000..f633d41
--- /dev/null
+++ b/modules/gles3/functional/es3fFboStateQueryTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FFBOSTATEQUERYTESTS_HPP
+#define _ES3FFBOSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Fbo state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FboStateQueryTests : public TestCaseGroup
+{
+public:
+																		FboStateQueryTests	(Context& context);
+
+	void																init				(void);
+
+private:
+																		FboStateQueryTests	(const FboStateQueryTests& other);
+	FboStateQueryTests&													operator=			(const FboStateQueryTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFBOSTATEQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fFboStencilbufferTests.cpp b/modules/gles3/functional/es3fFboStencilbufferTests.cpp
new file mode 100644
index 0000000..252086f
--- /dev/null
+++ b/modules/gles3/functional/es3fFboStencilbufferTests.cpp
@@ -0,0 +1,286 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO stencilbuffer tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboStencilbufferTests.hpp"
+#include "es3fFboTestCase.hpp"
+#include "es3fFboTestUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "sglrContextUtil.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::UVec4;
+using namespace FboTestUtil;
+
+class BasicFboStencilCase : public FboTestCase
+{
+public:
+	BasicFboStencilCase (Context& context, const char* name, const char* desc, deUint32 format, IVec2 size, bool useDepth)
+		: FboTestCase	(context, name, desc)
+		, m_format		(format)
+		, m_size		(size)
+		, m_useDepth	(useDepth)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_format);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		const deUint32			colorFormat		= GL_RGBA8;
+
+		GradientShader			gradShader		(glu::TYPE_FLOAT_VEC4);
+		FlatColorShader			flatShader		(glu::TYPE_FLOAT_VEC4);
+		deUint32				flatShaderID	= getCurrentContext()->createProgram(&flatShader);
+		deUint32				gradShaderID	= getCurrentContext()->createProgram(&gradShader);
+
+		deUint32				fbo				= 0;
+		deUint32				colorRbo		= 0;
+		deUint32				depthStencilRbo	= 0;
+
+		// Colorbuffer.
+		glGenRenderbuffers(1, &colorRbo);
+		glBindRenderbuffer(GL_RENDERBUFFER, colorRbo);
+		glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, m_size.x(), m_size.y());
+
+		// Stencil (and depth) buffer.
+		glGenRenderbuffers(1, &depthStencilRbo);
+		glBindRenderbuffer(GL_RENDERBUFFER, depthStencilRbo);
+		glRenderbufferStorage(GL_RENDERBUFFER, m_format, m_size.x(), m_size.y());
+
+		// Framebuffer.
+		glGenFramebuffers(1, &fbo);
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+		if (m_useDepth)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+		checkError();
+		checkFramebufferStatus(GL_FRAMEBUFFER);
+
+		glViewport(0, 0, m_size.x(), m_size.y());
+
+		// Clear framebuffer.
+		glClearBufferfv(GL_COLOR, 0, Vec4(0.0f).getPtr());
+		glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0);
+
+		// Render intersecting quads - increment stencil on depth pass
+		glEnable(GL_DEPTH_TEST);
+		glEnable(GL_STENCIL_TEST);
+		glStencilFunc(GL_ALWAYS, 0, 0xffu);
+		glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f,  0.0f), Vec3(+1.0f, +1.0f,  0.0f));
+
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, Vec4(0.0f), Vec4(1.0f));
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(+1.0f, +1.0f, +1.0f));
+
+		glDisable(GL_DEPTH_TEST);
+
+		// Draw quad with stencil test (stencil == 1 or 2 depending on depth) - decrement on stencil failure
+		glStencilFunc(GL_EQUAL, m_useDepth ? 2 : 1, 0xffu);
+		glStencilOp(GL_DECR, GL_KEEP, GL_KEEP);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-0.5f, -0.5f, 0.0f), Vec3(+0.5f, +0.5f, 0.0f));
+
+		// Draw quad with stencil test where stencil > 1 or 2 depending on depth buffer
+		glStencilFunc(GL_GREATER, m_useDepth ? 1 : 2, 0xffu);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, m_size.x(), m_size.y(), glu::mapGLInternalFormat(colorFormat), Vec4(1.0f), Vec4(0.0f));
+	}
+
+private:
+	deUint32	m_format;
+	IVec2		m_size;
+	bool		m_useDepth;
+};
+
+class DepthStencilAttachCase : public FboTestCase
+{
+public:
+	DepthStencilAttachCase (Context& context, const char* name, const char* desc, deUint32 attachDepth, deUint32 attachStencil)
+		: FboTestCase		(context, name, desc)
+		, m_attachDepth		(attachDepth)
+		, m_attachStencil	(attachStencil)
+	{
+		DE_ASSERT(m_attachDepth == GL_DEPTH_ATTACHMENT || m_attachDepth == GL_DEPTH_STENCIL_ATTACHMENT || m_attachDepth == GL_NONE);
+		DE_ASSERT(m_attachStencil == GL_STENCIL_ATTACHMENT || m_attachStencil == GL_NONE);
+		DE_ASSERT(m_attachDepth != GL_DEPTH_STENCIL || m_attachStencil == GL_NONE);
+	}
+
+protected:
+	void render (tcu::Surface& dst)
+	{
+		const deUint32			colorFormat			= GL_RGBA8;
+		const deUint32			depthStencilFormat	= GL_DEPTH24_STENCIL8;
+		const int				width				= 128;
+		const int				height				= 128;
+		const bool				hasDepth			= (m_attachDepth == GL_DEPTH_STENCIL || m_attachDepth == GL_DEPTH_ATTACHMENT);
+//		const bool				hasStencil			= (m_attachDepth == GL_DEPTH_STENCIL || m_attachStencil == GL_DEPTH_STENCIL_ATTACHMENT);
+
+		GradientShader			gradShader			(glu::TYPE_FLOAT_VEC4);
+		FlatColorShader			flatShader			(glu::TYPE_FLOAT_VEC4);
+		deUint32				flatShaderID		= getCurrentContext()->createProgram(&flatShader);
+		deUint32				gradShaderID		= getCurrentContext()->createProgram(&gradShader);
+
+		deUint32				fbo					= 0;
+		deUint32				colorRbo			= 0;
+		deUint32				depthStencilRbo		= 0;
+
+		// Colorbuffer.
+		glGenRenderbuffers(1, &colorRbo);
+		glBindRenderbuffer(GL_RENDERBUFFER, colorRbo);
+		glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, width, height);
+
+		// Depth-stencil buffer.
+		glGenRenderbuffers(1, &depthStencilRbo);
+		glBindRenderbuffer(GL_RENDERBUFFER, depthStencilRbo);
+		glRenderbufferStorage(GL_RENDERBUFFER, depthStencilFormat, width, height);
+
+		// Framebuffer.
+		glGenFramebuffers(1, &fbo);
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+
+		if (m_attachDepth != GL_NONE)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, m_attachDepth, GL_RENDERBUFFER, depthStencilRbo);
+		if (m_attachStencil != GL_NONE)
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, m_attachStencil, GL_RENDERBUFFER, depthStencilRbo);
+
+		checkError();
+		checkFramebufferStatus(GL_FRAMEBUFFER);
+
+		glViewport(0, 0, width, height);
+
+		// Clear framebuffer.
+		glClearBufferfv(GL_COLOR, 0, Vec4(0.0f).getPtr());
+		glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0);
+
+		// Render intersecting quads - increment stencil on depth pass
+		glEnable(GL_DEPTH_TEST);
+		glEnable(GL_STENCIL_TEST);
+		glStencilFunc(GL_ALWAYS, 0, 0xffu);
+		glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f,  0.0f), Vec3(+1.0f, +1.0f,  0.0f));
+
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, Vec4(0.0f), Vec4(1.0f));
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(+1.0f, +1.0f, +1.0f));
+
+		glDisable(GL_DEPTH_TEST);
+
+		// Draw quad with stencil test (stencil == 1 or 2 depending on depth) - decrement on stencil failure
+		glStencilFunc(GL_EQUAL, hasDepth ? 2 : 1, 0xffu);
+		glStencilOp(GL_DECR, GL_KEEP, GL_KEEP);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-0.5f, -0.5f, 0.0f), Vec3(+0.5f, +0.5f, 0.0f));
+
+		// Draw quad with stencil test where stencil > 1 or 2 depending on depth buffer
+		glStencilFunc(GL_GREATER, hasDepth ? 1 : 2, 0xffu);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+
+		readPixels(dst, 0, 0, width, height, glu::mapGLInternalFormat(colorFormat), Vec4(1.0f), Vec4(0.0f));
+	}
+
+private:
+	deUint32		m_attachDepth;
+	deUint32		m_attachStencil;
+};
+
+FboStencilTests::FboStencilTests (Context& context)
+	: TestCaseGroup(context, "stencil", "FBO Stencilbuffer tests")
+{
+}
+
+FboStencilTests::~FboStencilTests (void)
+{
+}
+
+void FboStencilTests::init (void)
+{
+	static const deUint32 stencilFormats[] =
+	{
+		GL_DEPTH32F_STENCIL8,
+		GL_DEPTH24_STENCIL8,
+		GL_STENCIL_INDEX8
+	};
+
+	// .basic
+	{
+		tcu::TestCaseGroup* basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic stencil tests");
+		addChild(basicGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(stencilFormats); fmtNdx++)
+		{
+			deUint32			format		= stencilFormats[fmtNdx];
+			tcu::TextureFormat	texFmt		= glu::mapGLInternalFormat(format);
+
+			basicGroup->addChild(new BasicFboStencilCase(m_context, getFormatName(format), "", format, IVec2(111, 132), false));
+
+			if (texFmt.order == tcu::TextureFormat::DS)
+				basicGroup->addChild(new BasicFboStencilCase(m_context, (string(getFormatName(format)) + "_depth").c_str(), "", format, IVec2(111, 132), true));
+		}
+	}
+
+	// .attach
+	{
+		tcu::TestCaseGroup* attachGroup = new tcu::TestCaseGroup(m_testCtx, "attach", "Attaching depth stencil");
+		addChild(attachGroup);
+
+		attachGroup->addChild(new DepthStencilAttachCase(m_context, "depth_only",				"Only depth part of depth-stencil RBO attached",			GL_DEPTH_ATTACHMENT,			GL_NONE));
+		attachGroup->addChild(new DepthStencilAttachCase(m_context, "stencil_only",				"Only stencil part of depth-stencil RBO attached",			GL_NONE,						GL_STENCIL_ATTACHMENT));
+		attachGroup->addChild(new DepthStencilAttachCase(m_context, "depth_stencil_separate",	"Depth and stencil attached separately",					GL_DEPTH_ATTACHMENT,			GL_STENCIL_ATTACHMENT));
+		attachGroup->addChild(new DepthStencilAttachCase(m_context, "depth_stencil_attachment",	"Depth and stencil attached with DEPTH_STENCIL_ATTACHMENT",	GL_DEPTH_STENCIL_ATTACHMENT,	GL_NONE));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboStencilbufferTests.hpp b/modules/gles3/functional/es3fFboStencilbufferTests.hpp
new file mode 100644
index 0000000..bafee4c
--- /dev/null
+++ b/modules/gles3/functional/es3fFboStencilbufferTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFBOSTENCILBUFFERTESTS_HPP
+#define _ES3FFBOSTENCILBUFFERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO stencilbuffer tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FboStencilTests : public TestCaseGroup
+{
+public:
+						FboStencilTests		(Context& context);
+						~FboStencilTests	(void);
+
+	void				init				(void);
+
+private:
+						FboStencilTests		(const FboStencilTests& other);
+	FboStencilTests&	operator=			(const FboStencilTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFBOSTENCILBUFFERTESTS_HPP
diff --git a/modules/gles3/functional/es3fFboTestCase.cpp b/modules/gles3/functional/es3fFboTestCase.cpp
new file mode 100644
index 0000000..443b7dd
--- /dev/null
+++ b/modules/gles3/functional/es3fFboTestCase.cpp
@@ -0,0 +1,364 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Base class for FBO tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboTestCase.hpp"
+#include "es3fFboTestUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "gluStrUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "deRandom.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <algorithm>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+using std::string;
+
+FboTestCase::FboTestCase (Context& context, const char* name, const char* description, bool useScreenSizedViewport)
+	: TestCase			(context, name, description)
+	, m_viewportWidth	(useScreenSizedViewport ? context.getRenderTarget().getWidth() : 128)
+	, m_viewportHeight	(useScreenSizedViewport ? context.getRenderTarget().getHeight() : 128)
+{
+}
+
+FboTestCase::~FboTestCase (void)
+{
+}
+
+FboTestCase::IterateResult FboTestCase::iterate (void)
+{
+	glu::RenderContext&			renderCtx		= TestCase::m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget	= renderCtx.getRenderTarget();
+	TestLog&					log				= m_testCtx.getLog();
+
+	// Viewport.
+	de::Random					rnd				(deStringHash(getName()));
+	int							width			= deMin32(renderTarget.getWidth(),	m_viewportWidth);
+	int							height			= deMin32(renderTarget.getHeight(),	m_viewportHeight);
+	int							x				= rnd.getInt(0, renderTarget.getWidth()		- width);
+	int							y				= rnd.getInt(0, renderTarget.getHeight()	- height);
+
+	// Surface format and storage is choosen by render().
+	tcu::Surface				reference;
+	tcu::Surface				result;
+
+	// Call preCheck() that can throw exception if some requirement is not met.
+	preCheck();
+
+	// Render using GLES3.
+	try
+	{
+		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(x, y, width, height));
+		setContext(&context);
+		render(result);
+
+		// Check error.
+		deUint32 err = glGetError();
+		if (err != GL_NO_ERROR)
+			throw glu::Error(err, glu::getErrorStr(err).toString().c_str(), DE_NULL, __FILE__, __LINE__);
+
+		setContext(DE_NULL);
+	}
+	catch (const FboTestUtil::FboIncompleteException& e)
+	{
+		if (e.getReason() == GL_FRAMEBUFFER_UNSUPPORTED)
+		{
+			log << e;
+			m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not supported");
+			return STOP;
+		}
+		else
+			throw;
+	}
+
+	// Render reference.
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), renderTarget.getDepthBits(), renderTarget.getStencilBits(), width, height);
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		setContext(&context);
+		render(reference);
+		setContext(DE_NULL);
+	}
+
+	bool isOk = compare(reference, result);
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+	return STOP;
+}
+
+bool FboTestCase::compare (const tcu::Surface& reference, const tcu::Surface& result)
+{
+	return tcu::fuzzyCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference, result, 0.05f, tcu::COMPARE_LOG_RESULT);
+}
+
+void FboTestCase::readPixels (tcu::Surface& dst, int x, int y, int width, int height, const tcu::TextureFormat& format, const tcu::Vec4& scale, const tcu::Vec4& bias)
+{
+	FboTestUtil::readPixels(*getCurrentContext(), dst, x, y, width, height, format, scale, bias);
+}
+
+void FboTestCase::readPixels (tcu::Surface& dst, int x, int y, int width, int height)
+{
+	getCurrentContext()->readPixels(dst, x, y, width, height);
+}
+
+void FboTestCase::checkFramebufferStatus (deUint32 target)
+{
+	deUint32 status = glCheckFramebufferStatus(target);
+	if (status != GL_FRAMEBUFFER_COMPLETE)
+		throw FboTestUtil::FboIncompleteException(status, __FILE__, __LINE__);
+}
+
+void FboTestCase::checkError (void)
+{
+	deUint32 err = glGetError();
+	if (err != GL_NO_ERROR)
+		throw glu::Error((int)err, (string("Got ") + glu::getErrorStr(err).toString()).c_str(), DE_NULL, __FILE__, __LINE__);
+}
+
+static bool isRequiredFormat (deUint32 format)
+{
+	switch (format)
+	{
+		// Color-renderable formats
+		case GL_RGBA32I:
+		case GL_RGBA32UI:
+		case GL_RGBA16I:
+		case GL_RGBA16UI:
+		case GL_RGBA8:
+		case GL_RGBA8I:
+		case GL_RGBA8UI:
+		case GL_SRGB8_ALPHA8:
+		case GL_RGB10_A2:
+		case GL_RGB10_A2UI:
+		case GL_RGBA4:
+		case GL_RGB5_A1:
+		case GL_RGB8:
+		case GL_RGB565:
+		case GL_RG32I:
+		case GL_RG32UI:
+		case GL_RG16I:
+		case GL_RG16UI:
+		case GL_RG8:
+		case GL_RG8I:
+		case GL_RG8UI:
+		case GL_R32I:
+		case GL_R32UI:
+		case GL_R16I:
+		case GL_R16UI:
+		case GL_R8:
+		case GL_R8I:
+		case GL_R8UI:
+			return true;
+
+		// Depth formats
+		case GL_DEPTH_COMPONENT32F:
+		case GL_DEPTH_COMPONENT24:
+		case GL_DEPTH_COMPONENT16:
+			return true;
+
+		// Depth+stencil formats
+		case GL_DEPTH32F_STENCIL8:
+		case GL_DEPTH24_STENCIL8:
+			return true;
+
+		// Stencil formats
+		case GL_STENCIL_INDEX8:
+			return true;
+
+		default:
+			return false;
+	}
+}
+
+static std::vector<std::string> getEnablingExtensions (deUint32 format)
+{
+	std::vector<std::string> out;
+
+	DE_ASSERT(!isRequiredFormat(format));
+
+	switch (format)
+	{
+		case GL_RGB16F:
+			out.push_back("GL_EXT_color_buffer_half_float");
+			break;
+
+		case GL_RGBA16F:
+		case GL_RG16F:
+		case GL_R16F:
+			out.push_back("GL_EXT_color_buffer_half_float");
+
+		case GL_RGBA32F:
+		case GL_RGB32F:
+		case GL_R11F_G11F_B10F:
+		case GL_RG32F:
+		case GL_R32F:
+			out.push_back("GL_EXT_color_buffer_float");
+
+		default:
+			break;
+	}
+
+	return out;
+}
+
+static bool isAnyExtensionSupported (Context& context, const std::vector<std::string>& requiredExts)
+{
+	for (std::vector<std::string>::const_iterator iter = requiredExts.begin(); iter != requiredExts.end(); iter++)
+	{
+		const std::string& extension = *iter;
+
+		if (context.getContextInfo().isExtensionSupported(extension.c_str()))
+			return true;
+	}
+
+	return false;
+}
+
+void FboTestCase::checkFormatSupport (deUint32 sizedFormat)
+{
+	const bool						isCoreFormat	= isRequiredFormat(sizedFormat);
+	const std::vector<std::string>	requiredExts	= (!isCoreFormat) ? getEnablingExtensions(sizedFormat) : std::vector<std::string>();
+
+	// Check that we don't try to use invalid formats.
+	DE_ASSERT(isCoreFormat || !requiredExts.empty());
+
+	if (!requiredExts.empty() && !isAnyExtensionSupported(m_context, requiredExts))
+		throw tcu::NotSupportedError("Format not supported");
+}
+
+static int getMinimumSampleCount (deUint32 format)
+{
+	switch (format)
+	{
+		// Core formats
+		case GL_RGBA32I:
+		case GL_RGBA32UI:
+		case GL_RGBA16I:
+		case GL_RGBA16UI:
+		case GL_RGBA8:
+		case GL_RGBA8I:
+		case GL_RGBA8UI:
+		case GL_SRGB8_ALPHA8:
+		case GL_RGB10_A2:
+		case GL_RGB10_A2UI:
+		case GL_RGBA4:
+		case GL_RGB5_A1:
+		case GL_RGB8:
+		case GL_RGB565:
+		case GL_RG32I:
+		case GL_RG32UI:
+		case GL_RG16I:
+		case GL_RG16UI:
+		case GL_RG8:
+		case GL_RG8I:
+		case GL_RG8UI:
+		case GL_R32I:
+		case GL_R32UI:
+		case GL_R16I:
+		case GL_R16UI:
+		case GL_R8:
+		case GL_R8I:
+		case GL_R8UI:
+		case GL_DEPTH_COMPONENT32F:
+		case GL_DEPTH_COMPONENT24:
+		case GL_DEPTH_COMPONENT16:
+		case GL_DEPTH32F_STENCIL8:
+		case GL_DEPTH24_STENCIL8:
+		case GL_STENCIL_INDEX8:
+			return 4;
+
+		// GL_EXT_color_buffer_float
+		case GL_R11F_G11F_B10F:
+		case GL_RG16F:
+		case GL_R16F:
+			return 4;
+
+		case GL_RGBA32F:
+		case GL_RGBA16F:
+		case GL_RG32F:
+		case GL_R32F:
+			return 0;
+
+		// GL_EXT_color_buffer_half_float
+		case GL_RGB16F:
+			return 0;
+
+		default:
+			DE_ASSERT(!"Unknown format");
+			return 0;
+	}
+}
+
+static std::vector<int> querySampleCounts (const glw::Functions& gl, deUint32 format)
+{
+	int					numSampleCounts		= 0;
+	std::vector<int>	sampleCounts;
+
+	gl.getInternalformativ(GL_RENDERBUFFER, format, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts);
+
+	if (numSampleCounts > 0)
+	{
+		sampleCounts.resize(numSampleCounts);
+		gl.getInternalformativ(GL_RENDERBUFFER, format, GL_SAMPLES, (glw::GLsizei)sampleCounts.size(), &sampleCounts[0]);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to query sample counts for format");
+
+	return sampleCounts;
+}
+
+void FboTestCase::checkSampleCount (deUint32 sizedFormat, int numSamples)
+{
+	const int minSampleCount = getMinimumSampleCount(sizedFormat);
+
+	if (numSamples > minSampleCount)
+	{
+		// Exceeds spec-mandated minimum - need to check.
+		const std::vector<int> supportedSampleCounts = querySampleCounts(m_context.getRenderContext().getFunctions(), sizedFormat);
+
+		if (std::find(supportedSampleCounts.begin(), supportedSampleCounts.end(), numSamples) == supportedSampleCounts.end())
+			throw tcu::NotSupportedError("Sample count not supported");
+	}
+}
+
+void FboTestCase::clearColorBuffer (const tcu::TextureFormat& format, const tcu::Vec4& value)
+{
+	FboTestUtil::clearColorBuffer(*getCurrentContext(), format, value);
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboTestCase.hpp b/modules/gles3/functional/es3fFboTestCase.hpp
new file mode 100644
index 0000000..204955c
--- /dev/null
+++ b/modules/gles3/functional/es3fFboTestCase.hpp
@@ -0,0 +1,77 @@
+#ifndef _ES3FFBOTESTCASE_HPP
+#define _ES3FFBOTESTCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Base class for FBO tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+#include "sglrContextWrapper.hpp"
+
+namespace tcu
+{
+class Surface;
+class TextureFormat;
+}
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FboTestCase : public TestCase, public sglr::ContextWrapper
+{
+public:
+						FboTestCase				(Context& context, const char* name, const char* description, bool useScreenSizedViewport = false);
+						~FboTestCase			(void);
+
+	IterateResult		iterate					(void);
+
+protected:
+	virtual void		preCheck				(void) {}
+	virtual void		render					(tcu::Surface& dst) = DE_NULL;
+	virtual bool		compare					(const tcu::Surface& reference, const tcu::Surface& result);
+
+	// Utilities.
+	void				checkFormatSupport		(deUint32 sizedFormat);
+	void				checkSampleCount		(deUint32 sizedFormat, int numSamples);
+	void				readPixels				(tcu::Surface& dst, int x, int y, int width, int height, const tcu::TextureFormat& format, const tcu::Vec4& scale, const tcu::Vec4& bias);
+	void				readPixels				(tcu::Surface& dst, int x, int y, int width, int height);
+	void				checkFramebufferStatus	(deUint32 target);
+	void				checkError				(void);
+	void				clearColorBuffer		(const tcu::TextureFormat& format, const tcu::Vec4& value = tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f));
+
+	int					m_viewportWidth;
+	int					m_viewportHeight;
+
+private:
+						FboTestCase				(const FboTestCase& other);
+	FboTestCase&		operator=				(const FboTestCase& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFBOTESTCASE_HPP
diff --git a/modules/gles3/functional/es3fFboTestUtil.cpp b/modules/gles3/functional/es3fFboTestUtil.cpp
new file mode 100644
index 0000000..67ed74f
--- /dev/null
+++ b/modules/gles3/functional/es3fFboTestUtil.cpp
@@ -0,0 +1,1116 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFboTestUtil.hpp"
+#include "sglrContextUtil.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <limits>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace FboTestUtil
+{
+
+using std::string;
+using std::vector;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+
+static rr::GenericVecType mapDataTypeToGenericVecType(glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_FLOAT_VEC4:	return rr::GENERICVECTYPE_FLOAT;
+		case glu::TYPE_INT_VEC4:	return rr::GENERICVECTYPE_INT32;
+		case glu::TYPE_UINT_VEC4:	return rr::GENERICVECTYPE_UINT32;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return rr::GENERICVECTYPE_LAST;
+	}
+}
+
+template <typename T>
+static tcu::Vector<T, 4> castVectorSaturate (const tcu::Vec4& in)
+{
+	return tcu::Vector<T, 4>((in.x() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.x() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.x()))),
+	                         (in.y() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.y() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.y()))),
+							 (in.z() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.z() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.z()))),
+							 (in.w() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.w() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.w()))));
+}
+
+FlatColorShader::FlatColorShader (glu::DataType outputType)
+	: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
+					<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
+					<< sglr::pdec::VertexSource(
+							"#version 300 es\n"
+							"in highp vec4 a_position;\n"
+							"void main (void)\n"
+							"{\n"
+							"	gl_Position = a_position;\n"
+							"}\n")
+					<< sglr::pdec::FragmentSource(
+							string(
+								"#version 300 es\n"
+								"uniform highp vec4 u_color;\n"
+								"layout(location = 0) out highp ") + glu::getDataTypeName(outputType) + " o_color;\n"
+								"void main (void)\n"
+								"{\n"
+								"	o_color = " + glu::getDataTypeName(outputType) + "(u_color);\n"
+								"}\n"))
+	, m_outputType(outputType)
+{
+}
+
+void FlatColorShader::setColor (sglr::Context& context, deUint32 program, const tcu::Vec4& color)
+{
+	deInt32 location = context.getUniformLocation(program, "u_color");
+
+	context.useProgram(program);
+	context.uniform4fv(location, 1, color.getPtr());
+}
+
+void FlatColorShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		packet.position = rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void FlatColorShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4		color	(m_uniforms[0].value.f4);
+	const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
+	const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);
+
+	DE_UNREF(packets);
+
+	if (m_outputType == glu::TYPE_FLOAT_VEC4)
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+	}
+	else if (m_outputType == glu::TYPE_INT_VEC4)
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
+	}
+	else if (m_outputType == glu::TYPE_UINT_VEC4)
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+}
+
+GradientShader::GradientShader (glu::DataType outputType)
+	: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
+					<< sglr::pdec::Uniform("u_gradientMin", glu::TYPE_FLOAT_VEC4)
+					<< sglr::pdec::Uniform("u_gradientMax", glu::TYPE_FLOAT_VEC4)
+					<< sglr::pdec::VertexSource(
+							"#version 300 es\n"
+							"in highp vec4 a_position;\n"
+							"in highp vec4 a_coord;\n"
+							"out highp vec4 v_coord;\n"
+							"void main (void)\n"
+							"{\n"
+							"	gl_Position = a_position;\n"
+							"	v_coord = a_coord;\n"
+							"}\n")
+					<< sglr::pdec::FragmentSource(
+							string(
+								"#version 300 es\n"
+								"in highp vec4 v_coord;\n"
+								"uniform highp vec4 u_gradientMin;\n"
+								"uniform highp vec4 u_gradientMax;\n"
+								"layout(location = 0) out highp ") + glu::getDataTypeName(outputType) + " o_color;\n"
+								"void main (void)\n"
+								"{\n"
+								"	highp float x = v_coord.x;\n"
+								"	highp float y = v_coord.y;\n"
+								"	highp float f0 = (x + y) * 0.5;\n"
+								"	highp float f1 = 0.5 + (x - y) * 0.5;\n"
+								"	highp vec4 fv = vec4(f0, f1, 1.0f-f0, 1.0f-f1);\n"
+								"	o_color = " + glu::getDataTypeName(outputType) + "(u_gradientMin + (u_gradientMax-u_gradientMin)*fv);\n"
+								"}\n"))
+	, m_outputType(outputType)
+{
+}
+
+void GradientShader::setGradient (sglr::Context& ctx, deUint32 program, const tcu::Vec4& gradientMin, const tcu::Vec4& gradientMax)
+{
+	ctx.useProgram(program);
+	ctx.uniform4fv(ctx.getUniformLocation(program, "u_gradientMin"), 1, gradientMin.getPtr());
+	ctx.uniform4fv(ctx.getUniformLocation(program, "u_gradientMax"), 1, gradientMax.getPtr());
+}
+
+void GradientShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void GradientShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4	gradientMin(m_uniforms[0].value.f4);
+	const tcu::Vec4	gradientMax(m_uniforms[1].value.f4);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+	{
+		const tcu::Vec4		coord	= rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
+		const float			x		= coord.x();
+		const float			y		= coord.y();
+		const float			f0		= (x + y) * 0.5f;
+		const float			f1		= 0.5f + (x - y) * 0.5f;
+		const tcu::Vec4		fv		= Vec4(f0, f1, 1.0f-f0, 1.0f-f1);
+
+		const tcu::Vec4		color	= gradientMin + (gradientMax-gradientMin) * fv;
+		const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
+		const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);
+
+		if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+		else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
+		else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+static string genTexFragmentShader (const vector<glu::DataType>& samplerTypes, glu::DataType outputType)
+{
+	const char*			precision	= "highp";
+	std::ostringstream	src;
+
+	src << "#version 300 es\n"
+		<< "layout(location = 0) out highp " << glu::getDataTypeName(outputType) << " o_color0;\n";
+
+	src << "in highp vec2 v_coord;\n";
+
+	for (int samplerNdx = 0; samplerNdx < (int)samplerTypes.size(); samplerNdx++)
+	{
+		src << "uniform " << precision << " " << glu::getDataTypeName(samplerTypes[samplerNdx]) << " u_sampler" << samplerNdx << ";\n";
+		src << "uniform " << precision << " vec4 u_texScale" << samplerNdx << ";\n";
+		src << "uniform " << precision << " vec4 u_texBias" << samplerNdx << ";\n";
+	}
+
+	// Output scale & bias
+	src << "uniform " << precision << " vec4 u_outScale0;\n"
+		<< "uniform " << precision << " vec4 u_outBias0;\n";
+
+	src << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	" << precision << " vec4 out0 = vec4(0.0);\n";
+
+	// Texture input fetch and combine.
+	for (int inNdx = 0; inNdx < (int)samplerTypes.size(); inNdx++)
+		src << "\tout0 += vec4("
+			<< "texture(u_sampler" << inNdx << ", v_coord)) * u_texScale" << inNdx << " + u_texBias" << inNdx << ";\n";
+
+	// Write output.
+	src << "	o_color0 = " << glu::getDataTypeName(outputType) << "(out0 * u_outScale0 + u_outBias0);\n";
+
+	src << "}\n";
+
+	return src.str();
+}
+
+static sglr::pdec::ShaderProgramDeclaration genTexture2DShaderDecl (const DataTypes& samplerTypes, glu::DataType outputType)
+{
+	sglr::pdec::ShaderProgramDeclaration decl;
+
+	decl << sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType));
+
+	decl << sglr::pdec::VertexSource(
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in highp vec2 a_coord;\n"
+		"out highp vec2 v_coord;\n"
+		"void main(void)\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_coord = a_coord;\n"
+		"}\n");
+	decl << sglr::pdec::FragmentSource(genTexFragmentShader(samplerTypes.vec, outputType));
+
+	decl << sglr::pdec::Uniform("u_outScale0", glu::TYPE_FLOAT_VEC4);
+	decl << sglr::pdec::Uniform("u_outBias0", glu::TYPE_FLOAT_VEC4);
+
+	for (size_t ndx = 0; ndx < samplerTypes.vec.size(); ++ndx)
+	{
+		decl << sglr::pdec::Uniform(std::string("u_sampler")  + de::toString(ndx), samplerTypes.vec[ndx]);
+		decl << sglr::pdec::Uniform(std::string("u_texScale") + de::toString(ndx), glu::TYPE_FLOAT_VEC4);
+		decl << sglr::pdec::Uniform(std::string("u_texBias")  + de::toString(ndx), glu::TYPE_FLOAT_VEC4);
+	}
+
+	return decl;
+}
+
+Texture2DShader::Texture2DShader (const DataTypes& samplerTypes, glu::DataType outputType, const Vec4& outScale, const Vec4& outBias)
+	: sglr::ShaderProgram	(genTexture2DShaderDecl(samplerTypes, outputType))
+	, m_outScale			(outScale)
+	, m_outBias				(outBias)
+	, m_outputType			(outputType)
+{
+	m_inputs.resize(samplerTypes.vec.size());
+
+	// Initialize units.
+	for (int ndx = 0; ndx < (int)m_inputs.size(); ndx++)
+	{
+		m_inputs[ndx].unitNdx	= ndx;
+		m_inputs[ndx].scale		= Vec4(1.0f);
+		m_inputs[ndx].bias		= Vec4(0.0f);
+	}
+}
+
+void Texture2DShader::setUnit (int inputNdx, int unitNdx)
+{
+	m_inputs[inputNdx].unitNdx = unitNdx;
+}
+
+void Texture2DShader::setTexScaleBias (int inputNdx, const Vec4& scale, const Vec4& bias)
+{
+	m_inputs[inputNdx].scale	= scale;
+	m_inputs[inputNdx].bias		= bias;
+}
+
+void Texture2DShader::setOutScaleBias (const Vec4& scale, const Vec4& bias)
+{
+	m_outScale	= scale;
+	m_outBias	= bias;
+}
+
+void Texture2DShader::setUniforms (sglr::Context& gl, deUint32 program) const
+{
+	gl.useProgram(program);
+
+	for (int texNdx = 0; texNdx < (int)m_inputs.size(); texNdx++)
+	{
+		string	samplerName	= string("u_sampler") + de::toString(texNdx);
+		string	scaleName	= string("u_texScale") + de::toString(texNdx);
+		string	biasName	= string("u_texBias") + de::toString(texNdx);
+
+		gl.uniform1i(gl.getUniformLocation(program, samplerName.c_str()), m_inputs[texNdx].unitNdx);
+		gl.uniform4fv(gl.getUniformLocation(program, scaleName.c_str()), 1, m_inputs[texNdx].scale.getPtr());
+		gl.uniform4fv(gl.getUniformLocation(program, biasName.c_str()), 1, m_inputs[texNdx].bias.getPtr());
+	}
+
+	gl.uniform4fv(gl.getUniformLocation(program, "u_outScale0"), 1, m_outScale.getPtr());
+	gl.uniform4fv(gl.getUniformLocation(program, "u_outBias0"), 1, m_outBias.getPtr());
+}
+
+void Texture2DShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void Texture2DShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4 outScale (m_uniforms[0].value.f4);
+	const tcu::Vec4 outBias	 (m_uniforms[1].value.f4);
+
+	tcu::Vec2 texCoords[4];
+	tcu::Vec4 colors[4];
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		// setup tex coords
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4	coord = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
+			texCoords[fragNdx] = tcu::Vec2(coord.x(), coord.y());
+		}
+
+		// clear result
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			colors[fragNdx] = tcu::Vec4(0.0f);
+
+		// sample each texture
+		for (int ndx = 0; ndx < (int)m_inputs.size(); ndx++)
+		{
+			const sglr::rc::Texture2D*	tex		= m_uniforms[2 + ndx*3].sampler.tex2D;
+			const tcu::Vec4				scale	(m_uniforms[2 + ndx*3 + 1].value.f4);
+			const tcu::Vec4				bias	(m_uniforms[2 + ndx*3 + 2].value.f4);
+			tcu::Vec4 tmpColors[4];
+
+			tex->sample4(tmpColors, texCoords);
+
+			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+				colors[fragNdx] += tmpColors[fragNdx] * scale + bias;
+		}
+
+		// write out
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4		color	= colors[fragNdx] * outScale + outBias;
+			const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
+			const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);
+
+			if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+			else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
+			else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
+			else
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+TextureCubeShader::TextureCubeShader (glu::DataType samplerType, glu::DataType outputType)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
+							<< sglr::pdec::Uniform("u_coordMat", glu::TYPE_FLOAT_MAT3)
+							<< sglr::pdec::Uniform("u_sampler0", samplerType)
+							<< sglr::pdec::Uniform("u_scale", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::Uniform("u_bias", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::VertexSource(
+									"#version 300 es\n"
+									"in highp vec4 a_position;\n"
+									"in mediump vec2 a_coord;\n"
+									"uniform mat3 u_coordMat;\n"
+									"out mediump vec3 v_coord;\n"
+									"void main (void)\n"
+									"{\n"
+									"	gl_Position = a_position;\n"
+									"	v_coord = u_coordMat * vec3(a_coord, 1.0);\n"
+									"}\n")
+							<< sglr::pdec::FragmentSource(
+									string("") +
+									"#version 300 es\n"
+									"uniform highp " + glu::getDataTypeName(samplerType) + " u_sampler0;\n"
+									"uniform highp vec4 u_scale;\n"
+									"uniform highp vec4 u_bias;\n"
+									"in mediump vec3 v_coord;\n"
+									"layout(location = 0) out highp " + glu::getDataTypeName(outputType) + " o_color;\n"
+									"void main (void)\n"
+									"{\n"
+									"	o_color = " + glu::getDataTypeName(outputType) + "(vec4(texture(u_sampler0, v_coord)) * u_scale + u_bias);\n"
+									"}\n"))
+	, m_texScale	(1.0f)
+	, m_texBias		(0.0f)
+	, m_outputType	(outputType)
+{
+}
+
+void TextureCubeShader::setFace (tcu::CubeFace face)
+{
+	static const float s_cubeTransforms[][3*3] =
+	{
+		// Face -X: (x, y, 1) -> (-1, -(2*y-1), +(2*x-1))
+		{  0.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   2.0f,  0.0f, -1.0f },
+		// Face +X: (x, y, 1) -> (+1, -(2*y-1), -(2*x-1))
+		{  0.0f,  0.0f,  1.0f,
+		   0.0f, -2.0f,  1.0f,
+		  -2.0f,  0.0f,  1.0f },
+		// Face -Y: (x, y, 1) -> (+(2*x-1), -1, -(2*y-1))
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f },
+		// Face +Y: (x, y, 1) -> (+(2*x-1), +1, +(2*y-1))
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f,  0.0f,  1.0f,
+		   0.0f,  2.0f, -1.0f },
+		// Face -Z: (x, y, 1) -> (-(2*x-1), -(2*y-1), -1)
+		{ -2.0f,  0.0f,  1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   0.0f,  0.0f, -1.0f },
+		// Face +Z: (x, y, 1) -> (+(2*x-1), -(2*y-1), +1)
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   0.0f,  0.0f,  1.0f }
+	};
+	DE_ASSERT(de::inBounds<int>(face, 0, tcu::CUBEFACE_LAST));
+	m_coordMat = tcu::Mat3(s_cubeTransforms[face]);
+}
+
+void TextureCubeShader::setTexScaleBias (const Vec4& scale, const Vec4& bias)
+{
+	m_texScale	= scale;
+	m_texBias	= bias;
+}
+
+void TextureCubeShader::setUniforms (sglr::Context& gl, deUint32 program) const
+{
+	gl.useProgram(program);
+
+	gl.uniform1i(gl.getUniformLocation(program, "u_sampler0"), 0);
+	gl.uniformMatrix3fv(gl.getUniformLocation(program, "u_coordMat"), 1, GL_FALSE, m_coordMat.getColumnMajorData().getPtr());
+	gl.uniform4fv(gl.getUniformLocation(program, "u_scale"), 1, m_texScale.getPtr());
+	gl.uniform4fv(gl.getUniformLocation(program, "u_bias"), 1, m_texBias.getPtr());
+}
+
+void TextureCubeShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	tcu::Mat3 texCoordMat = tcu::Mat3(m_uniforms[0].value.m3);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::VertexPacket&	packet	= *packets[packetNdx];
+		tcu::Vec2			a_coord = rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx).xy();
+		tcu::Vec3			v_coord = texCoordMat * tcu::Vec3(a_coord.x(), a_coord.y(), 1.0f);
+
+		packet.position				= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		packet.outputs[0]			= tcu::Vec4(v_coord.x(), v_coord.y(), v_coord.z(), 0.0f);
+	}
+}
+
+void TextureCubeShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4 texScale (m_uniforms[2].value.f4);
+	const tcu::Vec4 texBias	 (m_uniforms[3].value.f4);
+
+	tcu::Vec3 texCoords[4];
+	tcu::Vec4 colors[4];
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const sglr::rc::TextureCube* tex = m_uniforms[1].sampler.texCube;
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4	coord = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
+			texCoords[fragNdx] = tcu::Vec3(coord.x(), coord.y(), coord.z());
+		}
+
+		tex->sample4(colors, texCoords);
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4		color	= colors[fragNdx] * texScale + texBias;
+			const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
+			const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);
+
+			if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+			else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
+			else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
+			else
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+Texture2DArrayShader::Texture2DArrayShader (glu::DataType samplerType, glu::DataType outputType)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
+							<< sglr::pdec::Uniform("u_sampler0", samplerType)
+							<< sglr::pdec::Uniform("u_scale", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::Uniform("u_bias", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::Uniform("u_layer", glu::TYPE_INT)
+							<< sglr::pdec::VertexSource(
+									"#version 300 es\n"
+									"in highp vec4 a_position;\n"
+									"in highp vec2 a_coord;\n"
+									"out highp vec2 v_coord;\n"
+									"void main (void)\n"
+									"{\n"
+									"	gl_Position = a_position;\n"
+									"	v_coord = a_coord;\n"
+									"}\n")
+							<< sglr::pdec::FragmentSource(
+									string("") +
+									"#version 300 es\n"
+									"uniform highp " + glu::getDataTypeName(samplerType) + " u_sampler0;\n"
+									"uniform highp vec4 u_scale;\n"
+									"uniform highp vec4 u_bias;\n"
+									"uniform highp int u_layer;\n"
+									"in highp vec2 v_coord;\n"
+									"layout(location = 0) out highp " + glu::getDataTypeName(outputType) + " o_color;\n"
+									"void main (void)\n"
+									"{\n"
+									"	o_color = " + glu::getDataTypeName(outputType) + "(vec4(texture(u_sampler0, vec3(v_coord, u_layer))) * u_scale + u_bias);\n"
+									"}\n"))
+	, m_texScale	(1.0f)
+	, m_texBias		(0.0f)
+	, m_layer		(0)
+	, m_outputType	(outputType)
+{
+}
+
+void Texture2DArrayShader::setLayer (int layer)
+{
+	m_layer = layer;
+}
+
+void Texture2DArrayShader::setTexScaleBias (const Vec4& scale, const Vec4& bias)
+{
+	m_texScale	= scale;
+	m_texBias	= bias;
+}
+
+void Texture2DArrayShader::setUniforms (sglr::Context& gl, deUint32 program) const
+{
+	gl.useProgram(program);
+
+	gl.uniform1i	(gl.getUniformLocation(program, "u_sampler0"),	0);
+	gl.uniform1i	(gl.getUniformLocation(program, "u_layer"),		m_layer);
+	gl.uniform4fv	(gl.getUniformLocation(program, "u_scale"),		1, m_texScale.getPtr());
+	gl.uniform4fv	(gl.getUniformLocation(program, "u_bias"),		1, m_texBias.getPtr());
+}
+
+void Texture2DArrayShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void Texture2DArrayShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4 texScale	(m_uniforms[1].value.f4);
+	const tcu::Vec4 texBias		(m_uniforms[2].value.f4);
+	const int		layer		= m_uniforms[3].value.i;
+
+	tcu::Vec3 texCoords[4];
+	tcu::Vec4 colors[4];
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const sglr::rc::Texture2DArray* tex = m_uniforms[0].sampler.tex2DArray;
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4	coord = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
+			texCoords[fragNdx] = tcu::Vec3(coord.x(), coord.y(), float(layer));
+		}
+
+		tex->sample4(colors, texCoords);
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4		color	= colors[fragNdx] * texScale + texBias;
+			const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
+			const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);
+
+			if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+			else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
+			else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
+			else
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+Texture3DShader::Texture3DShader (glu::DataType samplerType, glu::DataType outputType)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
+							<< sglr::pdec::Uniform("u_sampler0", samplerType)
+							<< sglr::pdec::Uniform("u_scale", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::Uniform("u_bias", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::Uniform("u_depth", glu::TYPE_FLOAT)
+							<< sglr::pdec::VertexSource(
+									"#version 300 es\n"
+									"in highp vec4 a_position;\n"
+									"in highp vec2 a_coord;\n"
+									"out highp vec2 v_coord;\n"
+									"void main (void)\n"
+									"{\n"
+									"	gl_Position = a_position;\n"
+									"	v_coord = a_coord;\n"
+									"}\n")
+							<< sglr::pdec::FragmentSource(
+									string("") +
+									"#version 300 es\n"
+									"uniform highp " + glu::getDataTypeName(samplerType) + " u_sampler0;\n"
+									"uniform highp vec4 u_scale;\n"
+									"uniform highp vec4 u_bias;\n"
+									"uniform highp float u_depth;\n"
+									"in highp vec2 v_coord;\n"
+									"layout(location = 0) out highp " + glu::getDataTypeName(outputType) + " o_color;\n"
+									"void main (void)\n"
+									"{\n"
+									"	o_color = " + glu::getDataTypeName(outputType) + "(vec4(texture(u_sampler0, vec3(v_coord, u_depth))) * u_scale + u_bias);\n"
+									"}\n"))
+	, m_texScale	(1.0f)
+	, m_texBias		(0.0f)
+	, m_depth		(0.0f)
+	, m_outputType	(outputType)
+{
+}
+
+void Texture3DShader::setDepth (float depth)
+{
+	m_depth = depth;
+}
+
+void Texture3DShader::setTexScaleBias (const Vec4& scale, const Vec4& bias)
+{
+	m_texScale	= scale;
+	m_texBias	= bias;
+}
+
+void Texture3DShader::setUniforms (sglr::Context& gl, deUint32 program) const
+{
+	gl.useProgram(program);
+
+	gl.uniform1i	(gl.getUniformLocation(program, "u_sampler0"),	0);
+	gl.uniform1f	(gl.getUniformLocation(program, "u_depth"),		m_depth);
+	gl.uniform4fv	(gl.getUniformLocation(program, "u_scale"),		1, m_texScale.getPtr());
+	gl.uniform4fv	(gl.getUniformLocation(program, "u_bias"),		1, m_texBias.getPtr());
+}
+
+void Texture3DShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void Texture3DShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4 texScale	(m_uniforms[1].value.f4);
+	const tcu::Vec4 texBias		(m_uniforms[2].value.f4);
+	const float		depth		= m_uniforms[3].value.f;
+
+	tcu::Vec3 texCoords[4];
+	tcu::Vec4 colors[4];
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const sglr::rc::Texture3D* tex = m_uniforms[0].sampler.tex3D;
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4	coord = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
+			texCoords[fragNdx] = tcu::Vec3(coord.x(), coord.y(), depth);
+		}
+
+		tex->sample4(colors, texCoords);
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4		color	= colors[fragNdx] * texScale + texBias;
+			const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
+			const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);
+
+			if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+			else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
+			else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
+			else
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+DepthGradientShader::DepthGradientShader (glu::DataType outputType)
+	: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+					<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
+					<< sglr::pdec::Uniform("u_maxGradient", glu::TYPE_FLOAT)
+					<< sglr::pdec::Uniform("u_minGradient", glu::TYPE_FLOAT)
+					<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
+					<< sglr::pdec::VertexSource(
+							"#version 300 es\n"
+							"in highp vec4 a_position;\n"
+							"in highp vec4 a_coord;\n"
+							"out highp vec4 v_coord;\n"
+							"void main (void)\n"
+							"{\n"
+							"	gl_Position = a_position;\n"
+							"	v_coord = a_coord;\n"
+							"}\n")
+					<< sglr::pdec::FragmentSource(
+							string(
+								"#version 300 es\n"
+								"in highp vec4 v_coord;\n"
+								"uniform highp float u_minGradient;\n"
+								"uniform highp float u_maxGradient;\n"
+								"uniform highp vec4 u_color;\n"
+								"layout(location = 0) out highp ") + glu::getDataTypeName(outputType) + " o_color;\n"
+								"void main (void)\n"
+								"{\n"
+								"	highp float x = v_coord.x;\n"
+								"	highp float y = v_coord.y;\n"
+								"	highp float f0 = (x + y) * 0.5;\n"
+								"	gl_FragDepth = u_minGradient + (u_maxGradient-u_minGradient)*f0;\n"
+								"	o_color = " + glu::getDataTypeName(outputType) + "(u_color);\n"
+								"}\n"))
+	, m_outputType	(outputType)
+	, u_minGradient	(getUniformByName("u_minGradient"))
+	, u_maxGradient	(getUniformByName("u_maxGradient"))
+	, u_color		(getUniformByName("u_color"))
+{
+}
+
+void DepthGradientShader::setUniforms (sglr::Context& ctx, deUint32 program, const float gradientMin, const float gradientMax, const tcu::Vec4& color)
+{
+	ctx.useProgram(program);
+	ctx.uniform1fv(ctx.getUniformLocation(program, "u_minGradient"), 1, &gradientMin);
+	ctx.uniform1fv(ctx.getUniformLocation(program, "u_maxGradient"), 1, &gradientMax);
+	ctx.uniform4fv(ctx.getUniformLocation(program, "u_color"), 1, color.getPtr());
+}
+
+void DepthGradientShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void DepthGradientShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const float			gradientMin	(u_minGradient.value.f);
+	const float			gradientMax	(u_maxGradient.value.f);
+	const tcu::Vec4		color		(u_color.value.f4);
+	const tcu::IVec4	icolor		(castVectorSaturate<deInt32>(color));
+	const tcu::UVec4	uicolor		(castVectorSaturate<deUint32>(color));
+
+	// running this shader without a depth buffer does not make any sense
+	DE_ASSERT(context.fragmentDepths);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+	{
+		const tcu::Vec4		coord	= rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
+		const float			x		= coord.x();
+		const float			y		= coord.y();
+		const float			f0		= (x + y) * 0.5f;
+
+		rr::writeFragmentDepth(context, packetNdx, fragNdx, 0, gradientMin + (gradientMax-gradientMin) * f0);
+
+		if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+		else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
+		else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+void clearColorBuffer (sglr::Context& ctx, const tcu::TextureFormat& format, const tcu::Vec4& value)
+{
+	const tcu::TextureChannelClass fmtClass = tcu::getTextureChannelClass(format.type);
+
+	switch (fmtClass)
+	{
+		case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+		case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
+		case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+			ctx.clearBufferfv(GL_COLOR, 0, value.getPtr());
+			break;
+
+		case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+			ctx.clearBufferuiv(GL_COLOR, 0, value.asUint().getPtr());
+			break;
+
+		case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+			ctx.clearBufferiv(GL_COLOR, 0, value.asInt().getPtr());
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+void readPixels (sglr::Context& ctx, tcu::Surface& dst, int x, int y, int width, int height, const tcu::TextureFormat& format, const tcu::Vec4& scale, const tcu::Vec4& bias)
+{
+	tcu::TextureFormat		readFormat		= getFramebufferReadFormat(format);
+	glu::TransferFormat		transferFmt		= glu::getTransferFormat(readFormat);
+	int						alignment		= 4; // \note GL_PACK_ALIGNMENT = 4 is assumed.
+	int						rowSize			= deAlign32(readFormat.getPixelSize()*width, alignment);
+	vector<deUint8>			data			(rowSize*height);
+
+	ctx.readPixels(x, y, width, height, transferFmt.format, transferFmt.dataType, &data[0]);
+
+	// Convert to surface.
+	tcu::ConstPixelBufferAccess src(readFormat, width, height, 1, rowSize, 0, &data[0]);
+
+	dst.setSize(width, height);
+	tcu::PixelBufferAccess dstAccess = dst.getAccess();
+
+	for (int yo = 0; yo < height; yo++)
+	for (int xo = 0; xo < width; xo++)
+		dstAccess.setPixel(src.getPixel(xo, yo) * scale + bias, xo, yo);
+}
+
+static const char* getFboIncompleteReasonName (deUint32 reason)
+{
+	switch (reason)
+	{
+		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:			return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
+		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:	return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
+		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:			return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+		case GL_FRAMEBUFFER_UNSUPPORTED:					return "GL_FRAMEBUFFER_UNSUPPORTED";
+		case GL_FRAMEBUFFER_COMPLETE:						return "GL_FRAMEBUFFER_COMPLETE";
+		default:											return "UNKNOWN";
+	}
+}
+
+FboIncompleteException::FboIncompleteException (deUint32 reason, const char* file, int line)
+	: TestError		("Framebuffer is not complete", getFboIncompleteReasonName(reason), file, line)
+	, m_reason		(reason)
+{
+}
+
+const char* getFormatName (deUint32 format)
+{
+	switch (format)
+	{
+		case GL_RGB565:				return "rgb565";
+		case GL_RGB5_A1:			return "rgb5_a1";
+		case GL_RGBA4:				return "rgba4";
+		case GL_DEPTH_COMPONENT16:	return "depth_component16";
+		case GL_STENCIL_INDEX8:		return "stencil_index8";
+		case GL_RGBA32F:			return "rgba32f";
+		case GL_RGBA32I:			return "rgba32i";
+		case GL_RGBA32UI:			return "rgba32ui";
+		case GL_RGBA16F:			return "rgba16f";
+		case GL_RGBA16I:			return "rgba16i";
+		case GL_RGBA16UI:			return "rgba16ui";
+		case GL_RGBA8:				return "rgba8";
+		case GL_RGBA8I:				return "rgba8i";
+		case GL_RGBA8UI:			return "rgba8ui";
+		case GL_SRGB8_ALPHA8:		return "srgb8_alpha8";
+		case GL_RGB10_A2:			return "rgb10_a2";
+		case GL_RGB10_A2UI:			return "rgb10_a2ui";
+		case GL_RGBA8_SNORM:		return "rgba8_snorm";
+		case GL_RGB8:				return "rgb8";
+		case GL_R11F_G11F_B10F:		return "r11f_g11f_b10f";
+		case GL_RGB32F:				return "rgb32f";
+		case GL_RGB32I:				return "rgb32i";
+		case GL_RGB32UI:			return "rgb32ui";
+		case GL_RGB16F:				return "rgb16f";
+		case GL_RGB16I:				return "rgb16i";
+		case GL_RGB16UI:			return "rgb16ui";
+		case GL_RGB8_SNORM:			return "rgb8_snorm";
+		case GL_RGB8I:				return "rgb8i";
+		case GL_RGB8UI:				return "rgb8ui";
+		case GL_SRGB8:				return "srgb8";
+		case GL_RGB9_E5:			return "rgb9_e5";
+		case GL_RG32F:				return "rg32f";
+		case GL_RG32I:				return "rg32i";
+		case GL_RG32UI:				return "rg32ui";
+		case GL_RG16F:				return "rg16f";
+		case GL_RG16I:				return "rg16i";
+		case GL_RG16UI:				return "rg16ui";
+		case GL_RG8:				return "rg8";
+		case GL_RG8I:				return "rg8i";
+		case GL_RG8UI:				return "rg8ui";
+		case GL_RG8_SNORM:			return "rg8_snorm";
+		case GL_R32F:				return "r32f";
+		case GL_R32I:				return "r32i";
+		case GL_R32UI:				return "r32ui";
+		case GL_R16F:				return "r16f";
+		case GL_R16I:				return "r16i";
+		case GL_R16UI:				return "r16ui";
+		case GL_R8:					return "r8";
+		case GL_R8I:				return "r8i";
+		case GL_R8UI:				return "r8ui";
+		case GL_R8_SNORM:			return "r8_snorm";
+		case GL_DEPTH_COMPONENT32F:	return "depth_component32f";
+		case GL_DEPTH_COMPONENT24:	return "depth_component24";
+		case GL_DEPTH32F_STENCIL8:	return "depth32f_stencil8";
+		case GL_DEPTH24_STENCIL8:	return "depth24_stencil8";
+
+		default:
+			TCU_FAIL("Unknown format");
+	}
+}
+
+glu::DataType getFragmentOutputType (const tcu::TextureFormat& format)
+{
+	switch (tcu::getTextureChannelClass(format.type))
+	{
+		case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+		case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
+		case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+			return glu::TYPE_FLOAT_VEC4;
+
+		case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+			return glu::TYPE_UINT_VEC4;
+
+		case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+			return glu::TYPE_INT_VEC4;
+
+		default:
+			DE_ASSERT(!"Unknown format");
+			return glu::TYPE_LAST;
+	}
+}
+
+tcu::TextureFormat getFramebufferReadFormat (const tcu::TextureFormat& format)
+{
+	switch (tcu::getTextureChannelClass(format.type))
+	{
+		case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+			return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::FLOAT);
+
+		case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
+		case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+			return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);
+
+		case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+			return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNSIGNED_INT32);
+
+		case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+			return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::SIGNED_INT32);
+
+		default:
+			DE_ASSERT(!"Unknown format");
+			return tcu::TextureFormat();
+	}
+}
+
+static int calculateU8ConversionError (int srcBits)
+{
+	if (srcBits > 0)
+	{
+		const int clampedBits	= de::clamp<int>(srcBits, 0, 8);
+		const int srcMaxValue	= de::max((1<<clampedBits) - 1, 1);
+		const int error			= int(deFloatCeil(255.0f * 2.0f / float(srcMaxValue)));
+
+		return de::clamp<int>(error, 0, 255);
+	}
+	else
+		return 1;
+}
+
+tcu::RGBA getFormatThreshold (const tcu::TextureFormat& format)
+{
+	const tcu::IVec4 bits = tcu::getTextureFormatBitDepth(format);
+
+	return tcu::RGBA(calculateU8ConversionError(bits.x()),
+					 calculateU8ConversionError(bits.y()),
+					 calculateU8ConversionError(bits.z()),
+					 calculateU8ConversionError(bits.w()));
+}
+
+tcu::RGBA getFormatThreshold (deUint32 glFormat)
+{
+	const tcu::TextureFormat format = glu::mapGLInternalFormat(glFormat);
+
+	return getFormatThreshold(format);
+}
+
+static int getToSRGB8ConversionError (int srcBits)
+{
+	// \note These are pre-computed based on simulation results.
+	static const int errors[] =
+	{
+		1,		// 0 bits - rounding
+		255,	// 1 bits
+		157,	// 2 bits
+		106,	// 3 bits
+		74,		// 4 bits
+		51,		// 5 bits
+		34,		// 6 bits
+		22,		// 7 bits
+		13,		// 8 bits
+		7,		// 9 bits
+		4,		// 10 bits
+		3,		// 11 bits
+		2,		// 12 bits
+		// 1 from this on
+	};
+
+	DE_ASSERT(srcBits >= 0);
+	if (srcBits < DE_LENGTH_OF_ARRAY(errors))
+		return errors[srcBits];
+	else
+		return 1;
+}
+
+tcu::RGBA getToSRGBConversionThreshold (const tcu::TextureFormat& src, const tcu::TextureFormat& dst)
+{
+	// Only SRGB8 and SRGB8_ALPHA8 formats are supported.
+	DE_ASSERT(dst.type == tcu::TextureFormat::UNORM_INT8 && (dst.order == tcu::TextureFormat::sRGB || dst.order == tcu::TextureFormat::sRGBA));
+
+	const tcu::IVec4	bits		= tcu::getTextureFormatBitDepth(src);
+	const bool			dstHasAlpha	= dst.order == tcu::TextureFormat::sRGBA;
+
+	return tcu::RGBA(getToSRGB8ConversionError(bits.x()),
+					 getToSRGB8ConversionError(bits.y()),
+					 getToSRGB8ConversionError(bits.z()),
+					 dstHasAlpha ? calculateU8ConversionError(bits.w()) : 0);
+}
+
+} // FboTestUtil
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFboTestUtil.hpp b/modules/gles3/functional/es3fFboTestUtil.hpp
new file mode 100644
index 0000000..84c1b2f
--- /dev/null
+++ b/modules/gles3/functional/es3fFboTestUtil.hpp
@@ -0,0 +1,232 @@
+#ifndef _ES3FFBOTESTUTIL_HPP
+#define _ES3FFBOTESTUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "sglrContext.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuTexture.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace FboTestUtil
+{
+
+// \todo [2012-04-29 pyry] Clean up and name as SglrUtil
+
+// Helper class for constructing DataType vectors.
+struct DataTypes
+{
+	std::vector<glu::DataType> vec;
+	DataTypes& operator<< (glu::DataType type) { vec.push_back(type); return *this; }
+};
+
+// Shaders.
+
+class FlatColorShader : public sglr::ShaderProgram
+{
+public:
+						FlatColorShader		(glu::DataType outputType);
+						~FlatColorShader	(void) {}
+
+	void				setColor			(sglr::Context& context, deUint32 program, const tcu::Vec4& color);
+
+	void				shadeVertices		(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void				shadeFragments		(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+private:
+	const glu::DataType	m_outputType;
+};
+
+class GradientShader : public sglr::ShaderProgram
+{
+public:
+						GradientShader		(glu::DataType outputType);
+						~GradientShader		(void) {}
+
+	void				setGradient			(sglr::Context& context, deUint32 program, const tcu::Vec4& gradientMin, const tcu::Vec4& gradientMax);
+
+	void				shadeVertices		(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void				shadeFragments		(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+private:
+	const glu::DataType	m_outputType;
+};
+
+class Texture2DShader : public sglr::ShaderProgram
+{
+public:
+					Texture2DShader			(const DataTypes& samplerTypes, glu::DataType outputType, const tcu::Vec4& outScale = tcu::Vec4(1.0f), const tcu::Vec4& outBias = tcu::Vec4(0.0f));
+					~Texture2DShader		(void) {}
+
+	void			setUnit					(int samplerNdx, int unitNdx);
+	void			setTexScaleBias			(int samplerNdx, const tcu::Vec4& scale, const tcu::Vec4& bias);
+	void			setOutScaleBias			(const tcu::Vec4& scale, const tcu::Vec4& bias);
+
+	void			setUniforms				(sglr::Context& context, deUint32 program) const;
+
+	void			shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void			shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+private:
+	struct Input
+	{
+		int			unitNdx;
+		tcu::Vec4	scale;
+		tcu::Vec4	bias;
+	};
+
+	std::vector<Input>	m_inputs;
+	tcu::Vec4			m_outScale;
+	tcu::Vec4			m_outBias;
+
+	const glu::DataType	m_outputType;
+};
+
+class TextureCubeShader : public sglr::ShaderProgram
+{
+public:
+						TextureCubeShader		(glu::DataType samplerType, glu::DataType outputType);
+						~TextureCubeShader		(void) {}
+
+	void				setFace					(tcu::CubeFace face);
+	void				setTexScaleBias			(const tcu::Vec4& scale, const tcu::Vec4& bias);
+
+	void				setUniforms				(sglr::Context& context, deUint32 program) const;
+
+	void				shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void				shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+private:
+	tcu::Mat3			m_coordMat;
+	tcu::Vec4			m_texScale;
+	tcu::Vec4			m_texBias;
+
+	const glu::DataType	m_outputType;
+};
+
+class Texture2DArrayShader : public sglr::ShaderProgram
+{
+public:
+						Texture2DArrayShader	(glu::DataType samplerType, glu::DataType outputType);
+						~Texture2DArrayShader	(void) {}
+
+	void				setLayer				(int layer);
+	void				setTexScaleBias			(const tcu::Vec4& scale, const tcu::Vec4& bias);
+
+	void				setUniforms				(sglr::Context& context, deUint32 program) const;
+
+	void				shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void				shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+private:
+	tcu::Vec4			m_texScale;
+	tcu::Vec4			m_texBias;
+	int					m_layer;
+
+	const glu::DataType	m_outputType;
+};
+
+class Texture3DShader : public sglr::ShaderProgram
+{
+public:
+						Texture3DShader			(glu::DataType samplerType, glu::DataType outputType);
+						~Texture3DShader		(void) {}
+
+	void				setDepth				(float r);
+	void				setTexScaleBias			(const tcu::Vec4& scale, const tcu::Vec4& bias);
+
+	void				setUniforms				(sglr::Context& context, deUint32 program) const;
+
+	void				shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void				shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+private:
+	tcu::Vec4			m_texScale;
+	tcu::Vec4			m_texBias;
+	float				m_depth;
+
+	const glu::DataType	m_outputType;
+};
+
+class DepthGradientShader : public sglr::ShaderProgram
+{
+public:
+								DepthGradientShader		(glu::DataType outputType);
+								~DepthGradientShader	(void) {}
+
+	void						setUniforms				(sglr::Context& context, deUint32 program, const float gradientMin, const float gradientMax, const tcu::Vec4& color);
+
+	void						shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void						shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+private:
+	const glu::DataType			m_outputType;
+	const sglr::UniformSlot&	u_minGradient;
+	const sglr::UniformSlot&	u_maxGradient;
+	const sglr::UniformSlot&	u_color;
+};
+
+// Framebuffer incomplete exception.
+class FboIncompleteException : public tcu::TestError
+{
+public:
+						FboIncompleteException		(deUint32 reason, const char* file, int line);
+	virtual				~FboIncompleteException		(void) throw() {}
+
+	deUint32			getReason					(void) const { return m_reason; }
+
+private:
+	deUint32			m_reason;
+};
+
+// Utility functions.
+
+glu::DataType			getFragmentOutputType				(const tcu::TextureFormat& format);
+tcu::TextureFormat		getFramebufferReadFormat			(const tcu::TextureFormat& format);
+
+const char*				getFormatName						(deUint32 format);
+
+void					clearColorBuffer					(sglr::Context& ctx, const tcu::TextureFormat& format, const tcu::Vec4& value);
+void					readPixels							(sglr::Context& ctx, tcu::Surface& dst, int x, int y, int width, int height, const tcu::TextureFormat& format, const tcu::Vec4& scale, const tcu::Vec4& bias);
+
+tcu::RGBA				getFormatThreshold					(const tcu::TextureFormat& format);
+tcu::RGBA				getFormatThreshold					(const deUint32 glFormat);
+
+tcu::RGBA				getToSRGBConversionThreshold		(const tcu::TextureFormat& src, const tcu::TextureFormat& dst);
+
+} // FboTestUtil
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFBOTESTUTIL_HPP
diff --git a/modules/gles3/functional/es3fFloatStateQueryTests.cpp b/modules/gles3/functional/es3fFloatStateQueryTests.cpp
new file mode 100644
index 0000000..1c00af4
--- /dev/null
+++ b/modules/gles3/functional/es3fFloatStateQueryTests.cpp
@@ -0,0 +1,1316 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Float State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFloatStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuFormatUtil.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "glwEnums.hpp"
+
+#include <limits>
+
+using namespace glw; // GLint and other GL types
+using namespace deqp::gls;
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace FloatStateQueryVerifiers
+{
+namespace
+{
+
+const int FLOAT_EXPANSION_E = 0x03FF;
+
+GLint64 expandGLFloatToInteger (GLfloat f)
+{
+	const GLuint64 referenceValue = (GLint64)(f * 2147483647.0);
+	return referenceValue;
+}
+
+GLint clampToGLint (GLint64 val)
+{
+	return (GLint)de::clamp<GLint64>(val, std::numeric_limits<GLint>::min(), std::numeric_limits<GLint>::max());
+}
+
+} // anonymous
+
+// StateVerifier
+
+class StateVerifier : protected glu::CallLogWrapper
+{
+public:
+						StateVerifier					(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~StateVerifier					(); // make GCC happy
+
+	const char*			getTestNamePostfix				(void)																																													const;
+
+	virtual void		verifyFloat						(tcu::TestContext& testCtx, GLenum name, GLfloat reference)																																		= DE_NULL;
+
+	// "Expanded" == Float to int conversion converts from [-1.0 to 1.0] -> [MIN_INT MAX_INT]
+	virtual void		verifyFloatExpanded				(tcu::TestContext& testCtx, GLenum name, GLfloat reference)																																		= DE_NULL;
+	virtual void		verifyFloat2Expanded			(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1)																												= DE_NULL;
+	virtual void		verifyFloat4Color				(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3)																		= DE_NULL;
+
+	// verify that the given range is completely whitin the GL state range
+	virtual void		verifyFloatRange				(tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max)																																= DE_NULL;
+	virtual void		verifyFloatGreaterOrEqual		(tcu::TestContext& testCtx, GLenum name, GLfloat reference)																																		= DE_NULL;
+
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+StateVerifier::StateVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+
+StateVerifier::~StateVerifier ()
+{
+}
+
+const char* StateVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+// GetBooleanVerifier
+
+class GetBooleanVerifier : public StateVerifier
+{
+public:
+			GetBooleanVerifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyFloat						(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloatExpanded				(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloat2Expanded			(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1);
+	void	verifyFloat4Color				(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3);
+	void	verifyFloatRange				(tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max);
+	void	verifyFloatGreaterOrEqual		(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+};
+
+GetBooleanVerifier::GetBooleanVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getboolean")
+{
+}
+
+void GetBooleanVerifier::verifyFloat (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLboolean expectedGLState = reference ? GL_TRUE : GL_FALSE;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << (expectedGLState==GL_TRUE ? "GL_TRUE" : "GL_FALSE") << "; got " << (state == GL_TRUE ? "GL_TRUE" : (state == GL_FALSE ? "GL_FALSE" : "non-boolean")) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyFloatExpanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	DE_ASSERT(de::inRange(reference, -1.0f, 1.0f));
+	verifyFloat(testCtx, name, reference);
+}
+
+void GetBooleanVerifier::verifyFloat2Expanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1)
+{
+	DE_ASSERT(de::inRange(reference0, -1.0f, 1.0f));
+	DE_ASSERT(de::inRange(reference1, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean[2]> boolVector2;
+	glGetBooleanv(name, boolVector2);
+
+	if (!boolVector2.verifyValidity(testCtx))
+		return;
+
+	const GLboolean referenceAsGLBoolean[] =
+	{
+		reference0 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference1 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+	};
+
+	if (boolVector2[0] != referenceAsGLBoolean[0] ||
+		boolVector2[1] != referenceAsGLBoolean[1])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (boolVector2[0] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (boolVector2[1] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyFloat4Color (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean[4]> boolVector4;
+	glGetBooleanv(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	const GLboolean referenceAsGLBoolean[] =
+	{
+		reference0 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference1 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference2 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+		reference3 ? GLboolean(GL_TRUE) : GLboolean(GL_FALSE),
+	};
+
+	if (boolVector4[0] != referenceAsGLBoolean[0] ||
+		boolVector4[1] != referenceAsGLBoolean[1] ||
+		boolVector4[2] != referenceAsGLBoolean[2] ||
+		boolVector4[3] != referenceAsGLBoolean[3])
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (referenceAsGLBoolean[0] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[1] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[2] ? "GL_TRUE" : "GL_FALSE") << " "
+			<< (referenceAsGLBoolean[3] ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyFloatRange (tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean[2]> range;
+	glGetBooleanv(name, range);
+
+	if (!range.verifyValidity(testCtx))
+		return;
+
+	if (range[0] == GL_FALSE)
+	{
+		if (max < 0 || min < 0)
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: range [" << min << ", " << max << "] is not in range [" << (range[0] == GL_TRUE ? "GL_TRUE" : (range[0] == GL_FALSE ? "GL_FALSE" : "non-boolean")) << ", " << (range[1] == GL_TRUE ? "GL_TRUE" : (range[1] == GL_FALSE ? "GL_FALSE" : "non-boolean")) << "]"  << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean range");
+			return;
+		}
+	}
+	if (range[1] == GL_FALSE)
+	{
+		if (max > 0 || min > 0)
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: range [" << min << ", " << max << "] is not in range [" << (range[0] == GL_TRUE ? "GL_TRUE" : (range[0] == GL_FALSE ? "GL_FALSE" : "non-boolean")) << ", " << (range[1] == GL_TRUE ? "GL_TRUE" : (range[1] == GL_FALSE ? "GL_FALSE" : "non-boolean")) << "]"  << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean range");
+			return;
+		}
+	}
+}
+
+void GetBooleanVerifier::verifyFloatGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state == GL_TRUE) // state is non-zero, could be greater than reference (correct)
+		return;
+
+	if (state == GL_FALSE) // state is zero
+	{
+		if (reference > 0) // and reference is greater than zero?
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+		}
+	}
+	else
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE or GL_FALSE" << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+//GetIntegerVerifier
+
+class GetIntegerVerifier : public StateVerifier
+{
+public:
+			GetIntegerVerifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyFloat						(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloatExpanded				(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloat2Expanded			(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1);
+	void	verifyFloat4Color				(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3);
+	void	verifyFloatRange				(tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max);
+	void	verifyFloatGreaterOrEqual		(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+};
+
+GetIntegerVerifier::GetIntegerVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger")
+{
+}
+
+void GetIntegerVerifier::verifyFloat (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	const GLint expectedGLStateMax = StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint>(reference);
+	const GLint expectedGLStateMin = StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint>(reference);
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected rounding to the nearest integer, valid range [" << expectedGLStateMin << "," << expectedGLStateMax << "]; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyFloatExpanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	DE_ASSERT(de::inRange(reference, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint expectedGLStateMax = clampToGLint(expandGLFloatToInteger(reference) + FLOAT_EXPANSION_E);
+	const GLint expectedGLStateMin = clampToGLint(expandGLFloatToInteger(reference) - FLOAT_EXPANSION_E);
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected in range [" << toHex(expectedGLStateMin) << "," << toHex(expectedGLStateMax) << "]; got " << toHex((GLint)state) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyFloat2Expanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1)
+{
+	DE_ASSERT(de::inRange(reference0, -1.0f, 1.0f));
+	DE_ASSERT(de::inRange(reference1, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	StateQueryMemoryWriteGuard<GLint[2]> floatVector2;
+	glGetIntegerv(name, floatVector2);
+
+	if (!floatVector2.verifyValidity(testCtx))
+		return;
+
+	const GLint referenceAsGLintMin[] =
+	{
+		clampToGLint(expandGLFloatToInteger(reference0) - FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference1) - FLOAT_EXPANSION_E)
+	};
+	const GLint referenceAsGLintMax[] =
+	{
+		clampToGLint(expandGLFloatToInteger(reference0) + FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference1) + FLOAT_EXPANSION_E)
+	};
+
+	if (floatVector2[0] < referenceAsGLintMin[0] || floatVector2[0] > referenceAsGLintMax[0] ||
+		floatVector2[1] < referenceAsGLintMin[1] || floatVector2[1] > referenceAsGLintMax[1])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected in ranges "
+			<< "[" << toHex(referenceAsGLintMin[0]) << " " << toHex(referenceAsGLintMax[0]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[1]) << " " << toHex(referenceAsGLintMax[1]) << "]"
+			<< "; got "
+			<< toHex(floatVector2[0]) << ", "
+			<< toHex(floatVector2[1]) << " "<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyFloat4Color (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3)
+{
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	StateQueryMemoryWriteGuard<GLint[4]> floatVector4;
+	glGetIntegerv(name, floatVector4);
+
+	if (!floatVector4.verifyValidity(testCtx))
+		return;
+
+	const GLint referenceAsGLintMin[] =
+	{
+		clampToGLint(expandGLFloatToInteger(reference0) - FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference1) - FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference2) - FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference3) - FLOAT_EXPANSION_E)
+	};
+	const GLint referenceAsGLintMax[] =
+	{
+		clampToGLint(expandGLFloatToInteger(reference0) + FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference1) + FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference2) + FLOAT_EXPANSION_E),
+		clampToGLint(expandGLFloatToInteger(reference3) + FLOAT_EXPANSION_E)
+	};
+
+	if (floatVector4[0] < referenceAsGLintMin[0] || floatVector4[0] > referenceAsGLintMax[0] ||
+		floatVector4[1] < referenceAsGLintMin[1] || floatVector4[1] > referenceAsGLintMax[1] ||
+		floatVector4[2] < referenceAsGLintMin[2] || floatVector4[2] > referenceAsGLintMax[2] ||
+		floatVector4[3] < referenceAsGLintMin[3] || floatVector4[3] > referenceAsGLintMax[3])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected in ranges "
+			<< "[" << toHex(referenceAsGLintMin[0]) << " " << toHex(referenceAsGLintMax[0]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[1]) << " " << toHex(referenceAsGLintMax[1]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[2]) << " " << toHex(referenceAsGLintMax[2]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[3]) << " " << toHex(referenceAsGLintMax[3]) << "]"
+			<< "; got "
+			<< toHex(floatVector4[0]) << ", "
+			<< toHex(floatVector4[1]) << ", "
+			<< toHex(floatVector4[2]) << ", "
+			<< toHex(floatVector4[3]) << " "<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyFloatRange (tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[2]> range;
+	glGetIntegerv(name, range);
+
+	if (!range.verifyValidity(testCtx))
+		return;
+
+	const GLint testRangeAsGLint[] =
+	{
+		StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint>(min),
+		StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint>(max)
+	};
+
+	// check if test range outside of gl state range
+	if (testRangeAsGLint[0] < range[0] ||
+		testRangeAsGLint[1] > range[1])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: range ["
+			<< testRangeAsGLint[0] << ", "
+			<< testRangeAsGLint[1] << "]"
+			<< " is not in range ["
+			<< range[0] << ", "
+			<< range[1] << "]" << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer range");
+	}
+}
+
+void GetIntegerVerifier::verifyFloatGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint referenceAsInt = StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint>(reference);
+
+	if (state < referenceAsInt)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected expected greater or equal to " << referenceAsInt << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+//GetInteger64Verifier
+
+class GetInteger64Verifier : public StateVerifier
+{
+public:
+			GetInteger64Verifier		(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyFloat						(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloatExpanded				(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloat2Expanded			(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1);
+	void	verifyFloat4Color				(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3);
+	void	verifyFloatRange				(tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max);
+	void	verifyFloatGreaterOrEqual		(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+};
+
+GetInteger64Verifier::GetInteger64Verifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger64")
+{
+}
+
+void GetInteger64Verifier::verifyFloat (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	const GLint64 expectedGLStateMax = StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint64>(reference);
+	const GLint64 expectedGLStateMin = StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint64>(reference);
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected rounding to the nearest integer, valid range [" << expectedGLStateMin << "," << expectedGLStateMax << "]; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyFloatExpanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	DE_ASSERT(de::inRange(reference, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint64 expectedGLStateMax = expandGLFloatToInteger(reference) + FLOAT_EXPANSION_E;
+	const GLint64 expectedGLStateMin = expandGLFloatToInteger(reference) - FLOAT_EXPANSION_E;
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected in range [" << toHex(expectedGLStateMin) << "," << toHex(expectedGLStateMax) << "]; got " << toHex((GLint64)state) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyFloat2Expanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1)
+{
+	DE_ASSERT(de::inRange(reference0, -1.0f, 1.0f));
+	DE_ASSERT(de::inRange(reference1, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	StateQueryMemoryWriteGuard<GLint64[2]> floatVector2;
+	glGetInteger64v(name, floatVector2);
+
+	if (!floatVector2.verifyValidity(testCtx))
+		return;
+
+	const GLint64 referenceAsGLintMin[] =
+	{
+		expandGLFloatToInteger(reference0) - FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference1) - FLOAT_EXPANSION_E
+	};
+	const GLint64 referenceAsGLintMax[] =
+	{
+		expandGLFloatToInteger(reference0) + FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference1) + FLOAT_EXPANSION_E
+	};
+
+	if (floatVector2[0] < referenceAsGLintMin[0] || floatVector2[0] > referenceAsGLintMax[0] ||
+		floatVector2[1] < referenceAsGLintMin[1] || floatVector2[1] > referenceAsGLintMax[1])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected in ranges "
+			<< "[" << toHex(referenceAsGLintMin[0]) << " " << toHex(referenceAsGLintMax[0]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[1]) << " " << toHex(referenceAsGLintMax[1]) << "]"
+			<< "; got "
+			<< toHex(floatVector2[0]) << ", "
+			<< toHex(floatVector2[1]) << " "<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyFloat4Color (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3)
+{
+	using tcu::TestLog;
+	using tcu::toHex;
+
+	StateQueryMemoryWriteGuard<GLint64[4]> floatVector4;
+	glGetInteger64v(name, floatVector4);
+
+	if (!floatVector4.verifyValidity(testCtx))
+		return;
+
+	const GLint64 referenceAsGLintMin[] =
+	{
+		expandGLFloatToInteger(reference0) - FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference1) - FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference2) - FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference3) - FLOAT_EXPANSION_E
+	};
+	const GLint64 referenceAsGLintMax[] =
+	{
+		expandGLFloatToInteger(reference0) + FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference1) + FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference2) + FLOAT_EXPANSION_E,
+		expandGLFloatToInteger(reference3) + FLOAT_EXPANSION_E
+	};
+
+	if (floatVector4[0] < referenceAsGLintMin[0] || floatVector4[0] > referenceAsGLintMax[0] ||
+		floatVector4[1] < referenceAsGLintMin[1] || floatVector4[1] > referenceAsGLintMax[1] ||
+		floatVector4[2] < referenceAsGLintMin[2] || floatVector4[2] > referenceAsGLintMax[2] ||
+		floatVector4[3] < referenceAsGLintMin[3] || floatVector4[3] > referenceAsGLintMax[3])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected in ranges "
+			<< "[" << toHex(referenceAsGLintMin[0]) << " " << toHex(referenceAsGLintMax[0]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[1]) << " " << toHex(referenceAsGLintMax[1]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[2]) << " " << toHex(referenceAsGLintMax[2]) << "], "
+			<< "[" << toHex(referenceAsGLintMin[3]) << " " << toHex(referenceAsGLintMax[3]) << "]"
+			<< "; got "
+			<< toHex(floatVector4[0]) << ", "
+			<< toHex(floatVector4[1]) << ", "
+			<< toHex(floatVector4[2]) << ", "
+			<< toHex(floatVector4[3]) << " "<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyFloatRange (tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64[2]> range;
+	glGetInteger64v(name, range);
+
+	if (!range.verifyValidity(testCtx))
+		return;
+
+	const GLint64 testRangeAsGLint[] =
+	{
+		StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint64>(min),
+		StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint64>(max)
+	};
+
+	// check if test range outside of gl state range
+	if (testRangeAsGLint[0] < range[0] ||
+		testRangeAsGLint[1] > range[1])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: range ["
+			<< testRangeAsGLint[0] << ", "
+			<< testRangeAsGLint[1] << "]"
+			<< " is not in range ["
+			<< range[0] << ", "
+			<< range[1] << "]" << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer range");
+	}
+}
+
+void GetInteger64Verifier::verifyFloatGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint64 referenceAsInt = StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint64>(reference);
+
+	if (state < referenceAsInt)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected expected greater or equal to " << referenceAsInt << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+//GetFloatVerifier
+
+class GetFloatVerifier : public StateVerifier
+{
+public:
+			GetFloatVerifier			(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyFloat						(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloatExpanded				(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+	void	verifyFloat2Expanded			(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1);
+	void	verifyFloat4Color				(tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3);
+	void	verifyFloatRange				(tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max);
+	void	verifyFloatGreaterOrEqual		(tcu::TestContext& testCtx, GLenum name, GLfloat reference);
+};
+
+GetFloatVerifier::GetFloatVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getfloat")
+{
+}
+
+void GetFloatVerifier::verifyFloat (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyFloatExpanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	DE_ASSERT(de::inRange(reference, -1.0f, 1.0f));
+	verifyFloat(testCtx, name, reference);
+}
+
+void GetFloatVerifier::verifyFloat2Expanded (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1)
+{
+	DE_ASSERT(de::inRange(reference0, -1.0f, 1.0f));
+	DE_ASSERT(de::inRange(reference1, -1.0f, 1.0f));
+
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[2]> floatVector2;
+	glGetFloatv(name, floatVector2);
+
+	if (!floatVector2.verifyValidity(testCtx))
+		return;
+
+	if (floatVector2[0] != reference0 ||
+		floatVector2[1] != reference1)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference0 << ", " << reference1 << "; got " << floatVector2[0] << " " << floatVector2[1] << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyFloat4Color (tcu::TestContext& testCtx, GLenum name, GLfloat reference0, GLfloat reference1, GLfloat reference2, GLfloat reference3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[4]> floatVector4;
+	glGetFloatv(name, floatVector4);
+
+	if (!floatVector4.verifyValidity(testCtx))
+		return;
+
+	if (floatVector4[0] != reference0 ||
+		floatVector4[1] != reference1 ||
+		floatVector4[2] != reference2 ||
+		floatVector4[3] != reference3)
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected "<< reference0 << ", " << reference1 << ", " << reference2 << ", " << reference3
+			<< "; got " << floatVector4[0] << ", " << floatVector4[1] << ", " << floatVector4[2] << ", " << floatVector4[3]
+			<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyFloatRange (tcu::TestContext& testCtx, GLenum name, GLfloat min, GLfloat max)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[2]> floatVector2;
+	glGetFloatv(name, floatVector2);
+
+	if (!floatVector2.verifyValidity(testCtx))
+		return;
+
+	if (floatVector2[0] > min ||
+		floatVector2[1] < max)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected in range [" << min << ", " << max << "]; got [" << floatVector2[0] << " " << floatVector2[1]  << "]" << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float range");
+	}
+}
+
+void GetFloatVerifier::verifyFloatGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+
+} // FloatStateQueryVerifiers
+
+namespace
+{
+
+using namespace FloatStateQueryVerifiers;
+
+class DepthRangeCase : public ApiCase
+{
+public:
+	DepthRangeCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloat2Expanded(m_testCtx, GL_DEPTH_RANGE, 0.0f, 1.0f);
+		expectError(GL_NO_ERROR);
+
+		const struct FixedTest
+		{
+			float n, f;
+		} fixedTests[] =
+		{
+			{ 0.5f, 1.0f },
+			{ 0.0f, 0.5f },
+			{ 0.0f, 0.0f },
+			{ 1.0f, 1.0f }
+		};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+		{
+			glDepthRangef(fixedTests[ndx].n, fixedTests[ndx].f);
+
+			m_verifier->verifyFloat2Expanded(m_testCtx, GL_DEPTH_RANGE, fixedTests[ndx].n, fixedTests[ndx].f);
+			expectError(GL_NO_ERROR);
+		}
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			GLfloat n	= rnd.getFloat(0, 1);
+			GLfloat f	= rnd.getFloat(0, 1);
+
+			glDepthRangef(n, f);
+			m_verifier->verifyFloat2Expanded(m_testCtx, GL_DEPTH_RANGE, n, f);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class LineWidthCase : public ApiCase
+{
+public:
+	LineWidthCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloat(m_testCtx, GL_LINE_WIDTH, 1.0f);
+		expectError(GL_NO_ERROR);
+
+		GLfloat range[2] = {1};
+		glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, range);
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLfloat reference = rnd.getFloat(range[0], range[1]);
+
+			glLineWidth(reference);
+			m_verifier->verifyFloat(m_testCtx, GL_LINE_WIDTH, reference);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class PolygonOffsetFactorCase : public ApiCase
+{
+public:
+	PolygonOffsetFactorCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_FACTOR, 0.0f);
+		expectError(GL_NO_ERROR);
+
+		const float fixedTests[] =
+		{
+			0.0f, 0.5f, -0.5f, 1.5f
+		};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+		{
+			glPolygonOffset(fixedTests[ndx], 0);
+			m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_FACTOR, fixedTests[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLfloat reference = rnd.getFloat(-64000, 64000);
+
+			glPolygonOffset(reference, 0);
+			m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_FACTOR, reference);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class PolygonOffsetUnitsCase : public ApiCase
+{
+public:
+	PolygonOffsetUnitsCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_UNITS, 0.0f);
+		expectError(GL_NO_ERROR);
+
+		const float fixedTests[] =
+		{
+			0.0f, 0.5f, -0.5f, 1.5f
+		};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+		{
+			glPolygonOffset(0, fixedTests[ndx]);
+			m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_UNITS, fixedTests[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLfloat reference = rnd.getFloat(-64000, 64000);
+
+			glPolygonOffset(0, reference);
+			m_verifier->verifyFloat(m_testCtx, GL_POLYGON_OFFSET_UNITS, reference);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class SampleCoverageCase : public ApiCase
+{
+public:
+	SampleCoverageCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloat(m_testCtx, GL_SAMPLE_COVERAGE_VALUE, 1.0f);
+		expectError(GL_NO_ERROR);
+
+		{
+			const float fixedTests[] =
+			{
+				0.0f, 0.5f, 0.45f, 0.55f
+			};
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+			{
+				glSampleCoverage(fixedTests[ndx], GL_FALSE);
+				m_verifier->verifyFloat(m_testCtx, GL_SAMPLE_COVERAGE_VALUE, fixedTests[ndx]);
+				expectError(GL_NO_ERROR);
+			}
+		}
+
+		{
+			const float clampTests[] =
+			{
+				-1.0f, -1.5f, 1.45f, 3.55f
+			};
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(clampTests); ++ndx)
+			{
+				glSampleCoverage(clampTests[ndx], GL_FALSE);
+				m_verifier->verifyFloat(m_testCtx, GL_SAMPLE_COVERAGE_VALUE, de::clamp(clampTests[ndx], 0.0f, 1.0f));
+				expectError(GL_NO_ERROR);
+			}
+		}
+
+		{
+			const int numIterations = 120;
+			for (int i = 0; i < numIterations; ++i)
+			{
+				GLfloat		reference	= rnd.getFloat(0, 1);
+				GLboolean	invert		= rnd.getBool() ? GL_TRUE : GL_FALSE;
+
+				glSampleCoverage(reference, invert);
+				m_verifier->verifyFloat(m_testCtx, GL_SAMPLE_COVERAGE_VALUE, reference);
+				expectError(GL_NO_ERROR);
+			}
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class BlendColorCase : public ApiCase
+{
+public:
+	BlendColorCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloat4Color(m_testCtx, GL_BLEND_COLOR, 0, 0, 0, 0);
+		expectError(GL_NO_ERROR);
+
+		const struct FixedTest
+		{
+			float r, g, b, a;
+		} fixedTests[] =
+		{
+			{ 0.5f, 1.0f, 0.5f, 1.0f },
+			{ 0.0f, 0.5f, 0.0f, 0.5f },
+			{ 0.0f, 0.0f, 0.0f, 0.0f },
+			{ 1.0f, 1.0f, 1.0f, 1.0f },
+		};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+		{
+			glBlendColor(fixedTests[ndx].r, fixedTests[ndx].g, fixedTests[ndx].b, fixedTests[ndx].a);
+			m_verifier->verifyFloat4Color(m_testCtx, GL_BLEND_COLOR, fixedTests[ndx].r, fixedTests[ndx].g, fixedTests[ndx].b, fixedTests[ndx].a);
+			expectError(GL_NO_ERROR);
+		}
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLfloat r = rnd.getFloat(0, 1);
+			const GLfloat g = rnd.getFloat(0, 1);
+			const GLfloat b = rnd.getFloat(0, 1);
+			const GLfloat a = rnd.getFloat(0, 1);
+
+			glBlendColor(r, g, b, a);
+			m_verifier->verifyFloat4Color(m_testCtx, GL_BLEND_COLOR, r, g, b, a);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ColorClearCase : public ApiCase
+{
+public:
+	ColorClearCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		// \note Initial color clear value check is temorarily removed. (until the framework does not alter it)
+		//m_verifier->verifyFloat4Color(m_testCtx, GL_COLOR_CLEAR_VALUE, 0, 0, 0, 0);
+		//expectError(GL_NO_ERROR);
+
+		const struct FixedTest
+		{
+			float r, g, b, a;
+		} fixedTests[] =
+		{
+			{ 0.5f, 1.0f, 0.5f, 1.0f },
+			{ 0.0f, 0.5f, 0.0f, 0.5f },
+			{ 0.0f, 0.0f, 0.0f, 0.0f },
+			{ 1.0f, 1.0f, 1.0f, 1.0f },
+		};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(fixedTests); ++ndx)
+		{
+			glClearColor(fixedTests[ndx].r, fixedTests[ndx].g, fixedTests[ndx].b, fixedTests[ndx].a);
+			m_verifier->verifyFloat4Color(m_testCtx, GL_COLOR_CLEAR_VALUE, fixedTests[ndx].r, fixedTests[ndx].g, fixedTests[ndx].b, fixedTests[ndx].a);
+			expectError(GL_NO_ERROR);
+		}
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLfloat r = rnd.getFloat(0, 1);
+			const GLfloat g = rnd.getFloat(0, 1);
+			const GLfloat b = rnd.getFloat(0, 1);
+			const GLfloat a = rnd.getFloat(0, 1);
+
+			glClearColor(r, g, b, a);
+			m_verifier->verifyFloat4Color(m_testCtx, GL_COLOR_CLEAR_VALUE, r, g, b, a);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class DepthClearCase : public ApiCase
+{
+public:
+	DepthClearCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyFloatExpanded(m_testCtx, GL_DEPTH_CLEAR_VALUE, 1);
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLfloat ref = rnd.getFloat(0, 1);
+
+			glClearDepthf(ref);
+			m_verifier->verifyFloatExpanded(m_testCtx, GL_DEPTH_CLEAR_VALUE, ref);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class MaxTextureLODBiasCase : public ApiCase
+{
+public:
+	MaxTextureLODBiasCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyFloatGreaterOrEqual(m_testCtx, GL_MAX_TEXTURE_LOD_BIAS, 2.0f);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class AliasedPointSizeRangeCase : public ApiCase
+{
+public:
+	AliasedPointSizeRangeCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyFloatRange(m_testCtx, GL_ALIASED_POINT_SIZE_RANGE, 1, 1);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class AliasedLineWidthRangeCase : public ApiCase
+{
+public:
+	AliasedLineWidthRangeCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyFloatRange(m_testCtx, GL_ALIASED_LINE_WIDTH_RANGE, 1, 1);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		StateVerifier* verifier = VERIFIERS[_verifierNdx];										\
+		CODE_BLOCK;																				\
+	}
+
+} // anonymous
+
+FloatStateQueryTests::FloatStateQueryTests (Context& context)
+	: TestCaseGroup			(context, "floats", "Float Values")
+	, m_verifierBoolean		(DE_NULL)
+	, m_verifierInteger		(DE_NULL)
+	, m_verifierInteger64	(DE_NULL)
+	, m_verifierFloat		(DE_NULL)
+{
+}
+
+FloatStateQueryTests::~FloatStateQueryTests (void)
+{
+	deinit();
+}
+
+void FloatStateQueryTests::init (void)
+{
+	DE_ASSERT(m_verifierBoolean == DE_NULL);
+	DE_ASSERT(m_verifierInteger == DE_NULL);
+	DE_ASSERT(m_verifierInteger64 == DE_NULL);
+	DE_ASSERT(m_verifierFloat == DE_NULL);
+
+	m_verifierBoolean		= new GetBooleanVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInteger		= new GetIntegerVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInteger64		= new GetInteger64Verifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierFloat			= new GetFloatVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+
+	StateVerifier* verifiers[] = {m_verifierBoolean, m_verifierInteger, m_verifierInteger64, m_verifierFloat};
+
+	FOR_EACH_VERIFIER(verifiers, addChild(new DepthRangeCase				(m_context, verifier,	(std::string("depth_range")					+ verifier->getTestNamePostfix()).c_str(),	"DEPTH_RANGE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new LineWidthCase					(m_context, verifier,	(std::string("line_width")					+ verifier->getTestNamePostfix()).c_str(),	"LINE_WIDTH")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new PolygonOffsetFactorCase		(m_context, verifier,	(std::string("polygon_offset_factor")		+ verifier->getTestNamePostfix()).c_str(),	"POLYGON_OFFSET_FACTOR")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new PolygonOffsetUnitsCase		(m_context, verifier,	(std::string("polygon_offset_units")		+ verifier->getTestNamePostfix()).c_str(),	"POLYGON_OFFSET_UNITS")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new SampleCoverageCase			(m_context, verifier,	(std::string("sample_coverage_value")		+ verifier->getTestNamePostfix()).c_str(),	"SAMPLE_COVERAGE_VALUE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new BlendColorCase				(m_context, verifier,	(std::string("blend_color")					+ verifier->getTestNamePostfix()).c_str(),	"BLEND_COLOR")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new ColorClearCase				(m_context, verifier,	(std::string("color_clear_value")			+ verifier->getTestNamePostfix()).c_str(),	"COLOR_CLEAR_VALUE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new DepthClearCase				(m_context, verifier,	(std::string("depth_clear_value")			+ verifier->getTestNamePostfix()).c_str(),	"DEPTH_CLEAR_VALUE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new MaxTextureLODBiasCase			(m_context, verifier,	(std::string("max_texture_lod_bias")		+ verifier->getTestNamePostfix()).c_str(),	"MAX_TEXTURE_LOD_BIAS")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new AliasedPointSizeRangeCase		(m_context, verifier,	(std::string("aliased_point_size_range")	+ verifier->getTestNamePostfix()).c_str(),	"ALIASED_POINT_SIZE_RANGE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new AliasedLineWidthRangeCase		(m_context, verifier,	(std::string("aliased_line_width_range")	+ verifier->getTestNamePostfix()).c_str(),	"ALIASED_LINE_WIDTH_RANGE")));
+}
+
+void FloatStateQueryTests::deinit (void)
+{
+	if (m_verifierBoolean)
+	{
+		delete m_verifierBoolean;
+		m_verifierBoolean = DE_NULL;
+	}
+	if (m_verifierInteger)
+	{
+		delete m_verifierInteger;
+		m_verifierInteger = DE_NULL;
+	}
+	if (m_verifierInteger64)
+	{
+		delete m_verifierInteger64;
+		m_verifierInteger64 = DE_NULL;
+	}
+	if (m_verifierFloat)
+	{
+		delete m_verifierFloat;
+		m_verifierFloat = DE_NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFloatStateQueryTests.hpp b/modules/gles3/functional/es3fFloatStateQueryTests.hpp
new file mode 100644
index 0000000..7a143a5
--- /dev/null
+++ b/modules/gles3/functional/es3fFloatStateQueryTests.hpp
@@ -0,0 +1,68 @@
+#ifndef _ES3FFLOATSTATEQUERYTESTS_HPP
+#define _ES3FFLOATSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Float State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace FloatStateQueryVerifiers
+{
+
+class GetBooleanVerifier;
+class GetIntegerVerifier;
+class GetInteger64Verifier;
+class GetFloatVerifier;
+
+} // FloatStateQueryVerifiers
+
+class FloatStateQueryTests : public TestCaseGroup
+{
+public:
+															FloatStateQueryTests				(Context& context);
+															~FloatStateQueryTests				(void);
+
+	void													init								(void);
+	void													deinit								(void);
+
+private:
+															FloatStateQueryTests				(const FloatStateQueryTests& other);
+	FloatStateQueryTests&									operator=							(const FloatStateQueryTests& other);
+
+	FloatStateQueryVerifiers::GetBooleanVerifier*			m_verifierBoolean;
+	FloatStateQueryVerifiers::GetIntegerVerifier*			m_verifierInteger;
+	FloatStateQueryVerifiers::GetInteger64Verifier*			m_verifierInteger64;
+	FloatStateQueryVerifiers::GetFloatVerifier*				m_verifierFloat;
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFLOATSTATEQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fFlushFinishTests.cpp b/modules/gles3/functional/es3fFlushFinishTests.cpp
new file mode 100644
index 0000000..16ea854
--- /dev/null
+++ b/modules/gles3/functional/es3fFlushFinishTests.cpp
@@ -0,0 +1,684 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Flush and finish tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFlushFinishTests.hpp"
+
+#include "gluRenderContext.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluDrawUtil.hpp"
+
+#include "glsCalibration.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include "deRandom.hpp"
+#include "deClock.h"
+#include "deThread.h"
+#include "deMath.h"
+
+#include <algorithm>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using tcu::Vec2;
+using deqp::gls::theilSenEstimator;
+using deqp::gls::LineParameters;
+
+namespace
+{
+
+enum
+{
+	MAX_VIEWPORT_SIZE		= 256,
+	MAX_SAMPLE_DURATION_US	= 200*1000,
+	WAIT_TIME_MS			= 150,
+	MIN_DRAW_CALL_COUNT		= 10,
+	MAX_DRAW_CALL_COUNT		= 1<<20,
+	MAX_SHADER_ITER_COUNT	= 1<<10,
+	NUM_SAMPLES				= 50
+};
+
+const float		NO_CORR_COEF_THRESHOLD		= 0.1f;
+const float		FLUSH_COEF_THRESHOLD		= 0.2f;
+const float		CORRELATED_COEF_THRESHOLD	= 0.5f;
+
+static void busyWait (int milliseconds)
+{
+	const deUint64	startTime	= deGetMicroseconds();
+	float			v			= 2.0f;
+
+	for (;;)
+	{
+		for (int i = 0; i < 10; i++)
+			v = deFloatSin(v);
+
+		if (deGetMicroseconds()-startTime >= deUint64(1000*milliseconds))
+			break;
+	}
+}
+
+class CalibrationFailedException : public std::runtime_error
+{
+public:
+	CalibrationFailedException (const std::string& reason) : std::runtime_error(reason) {}
+};
+
+class FlushFinishCase : public TestCase
+{
+public:
+	enum ExpectedBehavior
+	{
+		EXPECT_COEF_LESS_THAN = 0,
+		EXPECT_COEF_GREATER_THAN,
+	};
+
+							FlushFinishCase		(Context&			context,
+												 const char*		name,
+												 const char*		description,
+												 ExpectedBehavior	waitBehavior,
+												 float				waitThreshold,
+												 ExpectedBehavior	readBehavior,
+												 float				readThreshold);
+							~FlushFinishCase	(void);
+
+	void					init				(void);
+	void					deinit				(void);
+	IterateResult			iterate				(void);
+
+	struct Sample
+	{
+		int			numDrawCalls;
+		deUint64	waitTime;
+		deUint64	readPixelsTime;
+	};
+
+	struct CalibrationParams
+	{
+		int			numItersInShader;
+		int			maxDrawCalls;
+	};
+
+protected:
+	virtual void			waitForGL			(void) = 0;
+
+private:
+							FlushFinishCase		(const FlushFinishCase&);
+	FlushFinishCase&		operator=			(const FlushFinishCase&);
+
+	CalibrationParams		calibrate			(void);
+	void					analyzeResults		(const std::vector<Sample>& samples, const CalibrationParams& calibrationParams);
+
+	void					setupRenderState	(void);
+	void					setShaderIterCount	(int numIters);
+	void					render				(int numDrawCalls);
+	void					readPixels			(void);
+
+	const ExpectedBehavior	m_waitBehavior;
+	const float				m_waitThreshold;
+	const ExpectedBehavior	m_readBehavior;
+	const float				m_readThreshold;
+
+	glu::ShaderProgram*		m_program;
+	int						m_iterCountLoc;
+};
+
+FlushFinishCase::FlushFinishCase (Context& context, const char* name, const char* description, ExpectedBehavior waitBehavior, float waitThreshold, ExpectedBehavior readBehavior, float readThreshold)
+	: TestCase			(context, name, description)
+	, m_waitBehavior	(waitBehavior)
+	, m_waitThreshold	(waitThreshold)
+	, m_readBehavior	(readBehavior)
+	, m_readThreshold	(readThreshold)
+	, m_program			(DE_NULL)
+	, m_iterCountLoc	(0)
+{
+}
+
+FlushFinishCase::~FlushFinishCase (void)
+{
+	FlushFinishCase::deinit();
+}
+
+void FlushFinishCase::init (void)
+{
+	DE_ASSERT(!m_program);
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(),
+		glu::ProgramSources()
+			<< glu::VertexSource(
+				"#version 300 es\n"
+				"in highp vec4 a_position;\n"
+				"out highp vec4 v_coord;\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = a_position;\n"
+				"	v_coord = a_position;\n"
+				"}\n")
+			<< glu::FragmentSource(
+				"#version 300 es\n"
+				"uniform highp int u_numIters;\n"
+				"in highp vec4 v_coord;\n"
+				"out mediump vec4 o_color;\n"
+				"void main (void)\n"
+				"{\n"
+				"	highp vec4 color = v_coord;\n"
+				"	for (int i = 0; i < u_numIters; i++)\n"
+				"		color = sin(color);\n"
+				"	o_color = color;\n"
+				"}\n"));
+
+	if (!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+		delete m_program;
+		m_program = DE_NULL;
+		TCU_FAIL("Compile failed");
+	}
+
+	m_iterCountLoc = m_context.getRenderContext().getFunctions().getUniformLocation(m_program->getProgram(), "u_numIters");
+	TCU_CHECK(m_iterCountLoc >= 0);
+}
+
+void FlushFinishCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+tcu::TestLog& operator<< (tcu::TestLog& log, const FlushFinishCase::Sample& sample)
+{
+	log << TestLog::Message << sample.numDrawCalls << " calls:\t" << sample.waitTime << " us wait,\t" << sample.readPixelsTime << " us read" << TestLog::EndMessage;
+	return log;
+}
+
+void FlushFinishCase::setupRenderState (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const int				posLoc			= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	const int				viewportW		= de::min<int>(m_context.getRenderTarget().getWidth(), MAX_VIEWPORT_SIZE);
+	const int				viewportH		= de::min<int>(m_context.getRenderTarget().getHeight(), MAX_VIEWPORT_SIZE);
+
+	static const float s_positions[] =
+	{
+		-1.0f, -1.0f,
+		+1.0f, -1.0f,
+		-1.0f, +1.0f,
+		+1.0f, +1.0f
+	};
+
+	TCU_CHECK(posLoc >= 0);
+
+	gl.viewport(0, 0, viewportW, viewportH);
+	gl.useProgram(m_program->getProgram());
+	gl.enableVertexAttribArray(posLoc);
+	gl.vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, &s_positions[0]);
+	gl.enable(GL_BLEND);
+	gl.blendFunc(GL_ONE, GL_ONE);
+	gl.blendEquation(GL_FUNC_ADD);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set up render state");
+}
+
+void FlushFinishCase::setShaderIterCount (int numIters)
+{
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+	gl.uniform1i(m_iterCountLoc, numIters);
+}
+
+void FlushFinishCase::render (int numDrawCalls)
+{
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+
+	const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	for (int ndx = 0; ndx < numDrawCalls; ndx++)
+		gl.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, &indices[0]);
+}
+
+void FlushFinishCase::readPixels (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	deUint8					tmp[4];
+
+	gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &tmp);
+}
+
+FlushFinishCase::CalibrationParams FlushFinishCase::calibrate (void)
+{
+	tcu::ScopedLogSection		section				(m_testCtx.getLog(), "CalibrationInfo", "Calibration info");
+	CalibrationParams			params;
+
+	// Step 1: find iteration count that results in rougly 1/10th of target maximum sample duration.
+	{
+		const deUint64		targetDurationUs		= MAX_SAMPLE_DURATION_US/100;
+		deUint64			prevDuration			= 0;
+		int					prevIterCount			= 1;
+		int					curIterCount			= 1;
+
+		m_testCtx.getLog() << TestLog::Message << "Calibrating shader iteration count, target duration = " << targetDurationUs << " us" << TestLog::EndMessage;
+
+		for (;;)
+		{
+			deUint64 curDuration;
+
+			setShaderIterCount(curIterCount);
+
+			{
+				const deUint64	startTime	= deGetMicroseconds();
+				render(1);
+				readPixels();
+				curDuration = deGetMicroseconds()-startTime;
+			}
+
+			m_testCtx.getLog() << TestLog::Message << "Duration with " << curIterCount << " iterations = " << curDuration << " us" << TestLog::EndMessage;
+
+			if (curDuration > targetDurationUs)
+			{
+				if (curIterCount > 1)
+				{
+					// Compute final count by using linear estimation.
+					const float		a		= float(curDuration - prevDuration) / float(curIterCount - prevIterCount);
+					const float		b		= float(prevDuration) - a*float(prevIterCount);
+					const float		est		= (float(targetDurationUs) - b) / a;
+
+					curIterCount = de::clamp(deFloorFloatToInt32(est), 1, int(MAX_SHADER_ITER_COUNT));
+				}
+				// else: Settle on 1.
+
+				break;
+			}
+			else if (curIterCount >= MAX_SHADER_ITER_COUNT)
+				break; // Settle on maximum.
+			else
+			{
+				prevIterCount	= curIterCount;
+				prevDuration	= curDuration;
+				curIterCount	= curIterCount*2;
+			}
+		}
+
+		params.numItersInShader = curIterCount;
+
+		m_testCtx.getLog() << TestLog::Integer("ShaderIterCount", "Shader iteration count", "", QP_KEY_TAG_NONE, params.numItersInShader);
+	}
+
+	// Step 2: Find draw call count that results in desired maximum time.
+	{
+		deUint64			prevDuration			= 0;
+		int					prevDrawCount			= 1;
+		int					curDrawCount			= 1;
+
+		m_testCtx.getLog() << TestLog::Message << "Calibrating maximum draw call count, target duration = " << int(MAX_SAMPLE_DURATION_US) << " us" << TestLog::EndMessage;
+
+		setShaderIterCount(params.numItersInShader);
+
+		for (;;)
+		{
+			deUint64 curDuration;
+
+			{
+				const deUint64	startTime	= deGetMicroseconds();
+				render(curDrawCount);
+				readPixels();
+				curDuration = deGetMicroseconds()-startTime;
+			}
+
+			m_testCtx.getLog() << TestLog::Message << "Duration with " << curDrawCount << " draw calls = " << curDuration << " us" << TestLog::EndMessage;
+
+			if (curDuration > MAX_SAMPLE_DURATION_US)
+			{
+				if (curDrawCount > 1)
+				{
+					// Compute final count by using linear estimation.
+					const float		a		= float(curDuration - prevDuration) / float(curDrawCount - prevDrawCount);
+					const float		b		= float(prevDuration) - a*float(prevDrawCount);
+					const float		est		= (float(MAX_SAMPLE_DURATION_US) - b) / a;
+
+					curDrawCount = de::clamp(deFloorFloatToInt32(est), 1, int(MAX_DRAW_CALL_COUNT));
+				}
+				// else: Settle on 1.
+
+				break;
+			}
+			else if (curDrawCount >= MAX_DRAW_CALL_COUNT)
+				break; // Settle on maximum.
+			else
+			{
+				prevDrawCount	= curDrawCount;
+				prevDuration	= curDuration;
+				curDrawCount	= curDrawCount*2;
+			}
+		}
+
+		params.maxDrawCalls = curDrawCount;
+
+		m_testCtx.getLog() << TestLog::Integer("MaxDrawCalls", "Maximum number of draw calls", "", QP_KEY_TAG_NONE, params.maxDrawCalls);
+	}
+
+	// Sanity check.
+	if (params.maxDrawCalls < MIN_DRAW_CALL_COUNT)
+		throw CalibrationFailedException("Calibration failed, maximum draw call count is too low");
+
+	return params;
+}
+
+struct CompareSampleDrawCount
+{
+	bool operator() (const FlushFinishCase::Sample& a, const FlushFinishCase::Sample& b) const { return a.numDrawCalls < b.numDrawCalls; }
+};
+
+std::vector<Vec2> getPointsFromSamples (const std::vector<FlushFinishCase::Sample>& samples, const deUint64 FlushFinishCase::Sample::*field)
+{
+	vector<Vec2> points(samples.size());
+
+	for (size_t ndx = 0; ndx < samples.size(); ndx++)
+		points[ndx] = Vec2(float(samples[ndx].numDrawCalls), float(samples[ndx].*field));
+
+	return points;
+}
+
+template<typename T>
+T getMaximumValue (const std::vector<FlushFinishCase::Sample>& samples, const T FlushFinishCase::Sample::*field)
+{
+	DE_ASSERT(!samples.empty());
+
+	T maxVal = samples[0].*field;
+
+	for (size_t ndx = 1; ndx < samples.size(); ndx++)
+		maxVal = de::max(maxVal, samples[ndx].*field);
+
+	return maxVal;
+}
+
+void FlushFinishCase::analyzeResults (const std::vector<Sample>& samples, const CalibrationParams& calibrationParams)
+{
+	const vector<Vec2>		waitTimes		= getPointsFromSamples(samples, &Sample::waitTime);
+	const vector<Vec2>		readTimes		= getPointsFromSamples(samples, &Sample::readPixelsTime);
+	const LineParameters	waitLine		= theilSenEstimator(waitTimes);
+	const LineParameters	readLine		= theilSenEstimator(readTimes);
+	const float				normWaitCoef	= waitLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US);
+	const float				normReadCoef	= readLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US);
+	bool					allOk			= true;
+
+	{
+		tcu::ScopedLogSection	section			(m_testCtx.getLog(), "Samples", "Samples");
+		vector<Sample>			sortedSamples	(samples.begin(), samples.end());
+
+		std::sort(sortedSamples.begin(), sortedSamples.end(), CompareSampleDrawCount());
+
+		for (vector<Sample>::const_iterator iter = sortedSamples.begin(); iter != sortedSamples.end(); ++iter)
+			m_testCtx.getLog() << *iter;
+	}
+
+	m_testCtx.getLog() << TestLog::Float("WaitCoefficient",				"Wait coefficient", "", QP_KEY_TAG_NONE, waitLine.coefficient)
+					   << TestLog::Float("ReadCoefficient",				"Read coefficient", "", QP_KEY_TAG_NONE, readLine.coefficient)
+					   << TestLog::Float("NormalizedWaitCoefficient",	"Normalized wait coefficient", "", QP_KEY_TAG_NONE, normWaitCoef)
+					   << TestLog::Float("NormalizedReadCoefficient",	"Normalized read coefficient", "", QP_KEY_TAG_NONE, normReadCoef);
+
+	{
+		const bool		waitCorrelated		= normWaitCoef > CORRELATED_COEF_THRESHOLD;
+		const bool		readCorrelated		= normReadCoef > CORRELATED_COEF_THRESHOLD;
+		const bool		waitNotCorr			= normWaitCoef < NO_CORR_COEF_THRESHOLD;
+		const bool		readNotCorr			= normReadCoef < NO_CORR_COEF_THRESHOLD;
+
+		if (waitCorrelated || waitNotCorr)
+			m_testCtx.getLog() << TestLog::Message << "Wait time is" << (waitCorrelated ? "" : " NOT") << " correlated to rendering workload size." << TestLog::EndMessage;
+		else
+			m_testCtx.getLog() << TestLog::Message << "Warning: Wait time correlation to rendering workload size is unclear." << TestLog::EndMessage;
+
+		if (readCorrelated || readNotCorr)
+			m_testCtx.getLog() << TestLog::Message << "Read time is" << (readCorrelated ? "" : " NOT") << " correlated to rendering workload size." << TestLog::EndMessage;
+		else
+			m_testCtx.getLog() << TestLog::Message << "Warning: Read time correlation to rendering workload size is unclear." << TestLog::EndMessage;
+	}
+
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		const float				coef		= ndx == 0 ? normWaitCoef : normReadCoef;
+		const char*				name		= ndx == 0 ? "wait" : "read";
+		const ExpectedBehavior	behavior	= ndx == 0 ? m_waitBehavior : m_readBehavior;
+		const float				threshold	= ndx == 0 ? m_waitThreshold : m_readThreshold;
+		const bool				isOk		= behavior == EXPECT_COEF_GREATER_THAN	? coef > threshold :
+											  behavior == EXPECT_COEF_LESS_THAN		? coef < threshold : false;
+		const char*				cmpName		= behavior == EXPECT_COEF_GREATER_THAN	? "greater than" :
+											  behavior == EXPECT_COEF_LESS_THAN		? "less than" : DE_NULL;
+
+		if (!isOk)
+		{
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << name << " coefficient to be " << cmpName << " " << threshold << TestLog::EndMessage;
+			allOk = false;
+		}
+	}
+
+	m_testCtx.setTestResult(allOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							allOk ? "Pass"				: "Suspicious performance behavior");
+}
+
+FlushFinishCase::IterateResult FlushFinishCase::iterate (void)
+{
+	vector<Sample>		samples		(NUM_SAMPLES);
+	CalibrationParams	params;
+
+	// Try to poke CPU into full speed. \todo [2013-12-26 pyry] Use more robust method.
+	busyWait(10);
+
+	setupRenderState();
+
+	// Do one full render cycle.
+	{
+		setShaderIterCount(1);
+		render(1);
+		readPixels();
+	}
+
+	// Calibrate.
+	try
+	{
+		params = calibrate();
+	}
+	catch (const CalibrationFailedException& e)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, e.what());
+		return STOP;
+	}
+
+	// Do measurement.
+	{
+		de::Random	rnd		(123);
+
+		setShaderIterCount(params.numItersInShader);
+
+		for (size_t ndx = 0; ndx < samples.size(); ndx++)
+		{
+			const int	drawCallCount	= rnd.getInt(1, params.maxDrawCalls);
+			deUint64	waitStartTime;
+			deUint64	readStartTime;
+			deUint64	readFinishTime;
+
+			render(drawCallCount);
+
+			waitStartTime = deGetMicroseconds();
+			waitForGL();
+
+			readStartTime = deGetMicroseconds();
+			readPixels();
+			readFinishTime = deGetMicroseconds();
+
+			samples[ndx].numDrawCalls	= drawCallCount;
+			samples[ndx].waitTime		= readStartTime-waitStartTime;
+			samples[ndx].readPixelsTime	= readFinishTime-readStartTime;
+
+			if (m_testCtx.getWatchDog())
+				qpWatchDog_touch(m_testCtx.getWatchDog());
+		}
+	}
+
+	// Analyze - sets test case result.
+	analyzeResults(samples, params);
+
+	return STOP;
+}
+
+class WaitOnlyCase : public FlushFinishCase
+{
+public:
+	WaitOnlyCase (Context& context)
+		: FlushFinishCase(context, "wait", "Wait only", EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD, EXPECT_COEF_GREATER_THAN, -1000.0f /* practically nothing is expected */)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
+		FlushFinishCase::init();
+	}
+
+protected:
+	void waitForGL (void)
+	{
+		busyWait(WAIT_TIME_MS);
+	}
+};
+
+class FlushOnlyCase : public FlushFinishCase
+{
+public:
+	FlushOnlyCase (Context& context)
+		: FlushFinishCase(context, "flush", "Flush only", EXPECT_COEF_LESS_THAN, FLUSH_COEF_THRESHOLD, EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Single call to glFlush()" << TestLog::EndMessage;
+		FlushFinishCase::init();
+	}
+
+protected:
+	void waitForGL (void)
+	{
+		m_context.getRenderContext().getFunctions().flush();
+	}
+};
+
+class FlushWaitCase : public FlushFinishCase
+{
+public:
+	FlushWaitCase (Context& context)
+		: FlushFinishCase(context, "flush_wait", "Wait after flushing", EXPECT_COEF_LESS_THAN, FLUSH_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "glFlush() followed by " << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
+		FlushFinishCase::init();
+	}
+
+protected:
+	void waitForGL (void)
+	{
+		m_context.getRenderContext().getFunctions().flush();
+		busyWait(WAIT_TIME_MS);
+	}
+};
+
+class FinishOnlyCase : public FlushFinishCase
+{
+public:
+	FinishOnlyCase (Context& context)
+		: FlushFinishCase(context, "finish", "Finish only", EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Single call to glFinish()" << TestLog::EndMessage;
+		FlushFinishCase::init();
+	}
+
+protected:
+	void waitForGL (void)
+	{
+		m_context.getRenderContext().getFunctions().finish();
+	}
+};
+
+class FinishWaitCase : public FlushFinishCase
+{
+public:
+	FinishWaitCase (Context& context)
+		: FlushFinishCase(context, "finish_wait", "Finish and wait", EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "glFinish() followed by " << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
+		FlushFinishCase::init();
+	}
+
+protected:
+	void waitForGL (void)
+	{
+		m_context.getRenderContext().getFunctions().finish();
+		busyWait(WAIT_TIME_MS);
+	}
+};
+
+} // anonymous
+
+FlushFinishTests::FlushFinishTests (Context& context)
+	: TestCaseGroup(context, "flush_finish", "Flush and Finish tests")
+{
+}
+
+FlushFinishTests::~FlushFinishTests (void)
+{
+}
+
+void FlushFinishTests::init (void)
+{
+	addChild(new WaitOnlyCase	(m_context));
+	addChild(new FlushOnlyCase	(m_context));
+	addChild(new FlushWaitCase	(m_context));
+	addChild(new FinishOnlyCase	(m_context));
+	addChild(new FinishWaitCase	(m_context));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFlushFinishTests.hpp b/modules/gles3/functional/es3fFlushFinishTests.hpp
new file mode 100644
index 0000000..b5cc670
--- /dev/null
+++ b/modules/gles3/functional/es3fFlushFinishTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFLUSHFINISHTESTS_HPP
+#define _ES3FFLUSHFINISHTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Flush and finish tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FlushFinishTests : public TestCaseGroup
+{
+public:
+						FlushFinishTests	(Context& context);
+						~FlushFinishTests	(void);
+
+	void				init				(void);
+
+private:
+						FlushFinishTests	(const FlushFinishTests&);
+	FlushFinishTests&	operator=			(const FlushFinishTests&);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFLUSHFINISHTESTS_HPP
diff --git a/modules/gles3/functional/es3fFragDepthTests.cpp b/modules/gles3/functional/es3fFragDepthTests.cpp
new file mode 100644
index 0000000..5647765
--- /dev/null
+++ b/modules/gles3/functional/es3fFragDepthTests.cpp
@@ -0,0 +1,591 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief gl_FragDepth tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFragDepthTests.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluDrawUtil.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+// For setupDefaultUniforms()
+#include "glsShaderRenderCase.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::TestLog;
+using std::string;
+using std::vector;
+
+typedef float (*EvalFragDepthFunc) (const Vec2& coord);
+
+static const char* s_vertexShaderSrc =
+	"#version 300 es\n"
+	"in highp vec4 a_position;\n"
+	"in highp vec2 a_coord;\n"
+	"out highp vec2 v_coord;\n"
+	"void main (void)\n"
+	"{\n"
+	"	gl_Position = a_position;\n"
+	"	v_coord = a_coord;\n"
+	"}\n";
+static const char* s_defaultFragmentShaderSrc =
+	"#version 300 es\n"
+	"uniform highp vec4 u_color;\n"
+	"layout(location = 0) out mediump vec4 o_color;\n"
+	"void main (void)\n"
+	"{\n"
+	"	o_color = u_color;\n"
+	"}\n";
+
+template <typename T>
+static inline bool compare (deUint32 func, T a, T b)
+{
+	switch (func)
+	{
+		case GL_NEVER:		return false;
+		case GL_ALWAYS:		return true;
+		case GL_LESS:		return a < b;
+		case GL_LEQUAL:		return a <= b;
+		case GL_EQUAL:		return a == b;
+		case GL_NOTEQUAL:	return a != b;
+		case GL_GEQUAL:		return a >= b;
+		case GL_GREATER:	return a > b;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return false;
+	}
+}
+
+class FragDepthCompareCase : public TestCase
+{
+public:
+							FragDepthCompareCase	(Context& context, const char* name, const char* desc, const char* fragSrc, EvalFragDepthFunc evalFunc, deUint32 compareFunc);
+							~FragDepthCompareCase	(void);
+
+	IterateResult			iterate					(void);
+
+private:
+	string					m_fragSrc;
+	EvalFragDepthFunc		m_evalFunc;
+	deUint32				m_compareFunc;
+};
+
+FragDepthCompareCase::FragDepthCompareCase (Context& context, const char* name, const char* desc, const char* fragSrc, EvalFragDepthFunc evalFunc, deUint32 compareFunc)
+	: TestCase			(context, name, desc)
+	, m_fragSrc			(fragSrc)
+	, m_evalFunc		(evalFunc)
+	, m_compareFunc		(compareFunc)
+{
+}
+
+FragDepthCompareCase::~FragDepthCompareCase (void)
+{
+}
+
+FragDepthCompareCase::IterateResult FragDepthCompareCase::iterate (void)
+{
+	TestLog&					log				= m_testCtx.getLog();
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	de::Random					rnd				(deStringHash(getName()));
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderContext().getRenderTarget();
+	int							viewportW		= de::min(128, renderTarget.getWidth());
+	int							viewportH		= de::min(128, renderTarget.getHeight());
+	int							viewportX		= rnd.getInt(0, renderTarget.getWidth()-viewportW);
+	int							viewportY		= rnd.getInt(0, renderTarget.getHeight()-viewportH);
+	tcu::Surface				renderedFrame	(viewportW, viewportH);
+	tcu::Surface				referenceFrame	(viewportW, viewportH);
+	const float					constDepth		= 0.1f;
+
+	if (renderTarget.getDepthBits() == 0)
+		throw tcu::NotSupportedError("Depth buffer is required", "", __FILE__, __LINE__);
+
+	gl.viewport(viewportX, viewportY, viewportW, viewportH);
+	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	gl.enable(GL_DEPTH_TEST);
+
+	static const deUint16 quadIndices[] = { 0, 1, 2, 2, 1, 3 };
+
+	// Fill viewport with 2 quads - one with constant depth and another with d = [-1..1]
+	{
+		glu::ShaderProgram basicQuadProgram(m_context.getRenderContext(), glu::makeVtxFragSources(s_vertexShaderSrc, s_defaultFragmentShaderSrc));
+
+		if (!basicQuadProgram.isOk())
+		{
+			log << basicQuadProgram;
+			TCU_FAIL("Compile failed");
+		}
+
+		const float constDepthCoord[] =
+		{
+			-1.0f, -1.0f, constDepth, 1.0f,
+			-1.0f, +1.0f, constDepth, 1.0f,
+			 0.0f, -1.0f, constDepth, 1.0f,
+			 0.0f, +1.0f, constDepth, 1.0f
+		};
+		const float varyingDepthCoord[] =
+		{
+			 0.0f, -1.0f, +1.0f, 1.0f,
+			 0.0f, +1.0f,  0.0f, 1.0f,
+			+1.0f, -1.0f,  0.0f, 1.0f,
+			+1.0f, +1.0f, -1.0f, 1.0f
+		};
+
+		gl.useProgram(basicQuadProgram.getProgram());
+		gl.uniform4f(gl.getUniformLocation(basicQuadProgram.getProgram(), "u_color"), 0.0f, 0.0f, 1.0f, 1.0f);
+		gl.depthFunc(GL_ALWAYS);
+
+		{
+			glu::VertexArrayBinding posBinding = glu::va::Float("a_position", 4, 4, 0, &constDepthCoord[0]);
+			glu::draw(m_context.getRenderContext(), basicQuadProgram.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(quadIndices), &quadIndices[0]));
+		}
+
+		{
+			glu::VertexArrayBinding posBinding = glu::va::Float("a_position", 4, 4, 0, &varyingDepthCoord[0]);
+			glu::draw(m_context.getRenderContext(), basicQuadProgram.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(quadIndices), &quadIndices[0]));
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw base quads");
+	}
+
+	// Render with depth test.
+	{
+		glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(s_vertexShaderSrc, m_fragSrc.c_str()));
+		log << program;
+
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		const float coord[] =
+		{
+			0.0f, 0.0f,
+			0.0f, 1.0f,
+			1.0f, 0.0f,
+			1.0f, 1.0f
+		};
+		const float position[] =
+		{
+			-1.0f, -1.0f, +1.0f, 1.0f,
+			-1.0f, +1.0f,  0.0f, 1.0f,
+			+1.0f, -1.0f,  0.0f, 1.0f,
+			+1.0f, +1.0f, -1.0f, 1.0f
+		};
+
+		gl.useProgram(program.getProgram());
+		gl.depthFunc(m_compareFunc);
+		gl.uniform4f(gl.getUniformLocation(program.getProgram(), "u_color"), 0.0f, 1.0f, 0.0f, 1.0f);
+
+		// Setup default helper uniforms.
+		gls::setupDefaultUniforms(m_context.getRenderContext(), program.getProgram());
+
+		{
+			glu::VertexArrayBinding vertexArrays[] =
+			{
+				glu::va::Float("a_position",	4, 4, 0, &position[0]),
+				glu::va::Float("a_coord",		2, 4, 0, &coord[0])
+			};
+			glu::draw(m_context.getRenderContext(), program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(quadIndices), &quadIndices[0]));
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw test quad");
+	}
+
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedFrame.getAccess());
+
+	// Render reference.
+	for (int y = 0; y < referenceFrame.getHeight(); y++)
+	{
+		float	yf		= ((float)y + 0.5f) / referenceFrame.getHeight();
+		int		half	= de::clamp((int)((float)referenceFrame.getWidth()*0.5f + 0.5f), 0, referenceFrame.getWidth());
+
+		// Fill left half - comparison to constant 0.5
+		for (int x = 0; x < half; x++)
+		{
+			float	xf		= ((float)x + 0.5f) / (float)referenceFrame.getWidth();
+			float	d		= m_evalFunc(Vec2(xf, yf));
+			bool	dpass	= compare(m_compareFunc, d, constDepth*0.5f + 0.5f);
+
+			referenceFrame.setPixel(x, y, dpass ? tcu::RGBA::green : tcu::RGBA::blue);
+		}
+
+		// Fill right half - comparison to interpolated depth
+		for (int x = half; x < referenceFrame.getWidth(); x++)
+		{
+			float	xf		= ((float)x + 0.5f) / (float)referenceFrame.getWidth();
+			float	xh		= ((float)x - half + 0.5f) / (float)(referenceFrame.getWidth()-half);
+			float	rd		= 1.0f - (xh + yf) * 0.5f;
+			float	d		= m_evalFunc(Vec2(xf, yf));
+			bool	dpass	= compare(m_compareFunc, d, rd);
+
+			referenceFrame.setPixel(x, y, dpass ? tcu::RGBA::green : tcu::RGBA::blue);
+		}
+	}
+
+	bool isOk = tcu::fuzzyCompare(log, "Result", "Image comparison result", referenceFrame, renderedFrame, 0.05f, tcu::COMPARE_LOG_RESULT);
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Fail");
+	return STOP;
+}
+
+class FragDepthWriteCase : public TestCase
+{
+public:
+							FragDepthWriteCase		(Context& context, const char* name, const char* desc, const char* fragSrc, EvalFragDepthFunc evalFunc);
+							~FragDepthWriteCase		(void);
+
+	IterateResult			iterate					(void);
+
+private:
+	string					m_fragSrc;
+	EvalFragDepthFunc		m_evalFunc;
+};
+
+FragDepthWriteCase::FragDepthWriteCase (Context& context, const char* name, const char* desc, const char* fragSrc, EvalFragDepthFunc evalFunc)
+	: TestCase			(context, name, desc)
+	, m_fragSrc			(fragSrc)
+	, m_evalFunc		(evalFunc)
+{
+}
+
+FragDepthWriteCase::~FragDepthWriteCase (void)
+{
+}
+
+FragDepthWriteCase::IterateResult FragDepthWriteCase::iterate (void)
+{
+	TestLog&					log				= m_testCtx.getLog();
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	de::Random					rnd				(deStringHash(getName()));
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderContext().getRenderTarget();
+	int							viewportW		= de::min(128, renderTarget.getWidth());
+	int							viewportH		= de::min(128, renderTarget.getHeight());
+	int							viewportX		= rnd.getInt(0, renderTarget.getWidth()-viewportW);
+	int							viewportY		= rnd.getInt(0, renderTarget.getHeight()-viewportH);
+	tcu::Surface				renderedFrame	(viewportW, viewportH);
+	tcu::Surface				referenceFrame	(viewportW, viewportH);
+	const int					numDepthSteps	= 16;
+	const float					depthStep		= 1.0f/(float)(numDepthSteps-1);
+
+	if (renderTarget.getDepthBits() == 0)
+		throw tcu::NotSupportedError("Depth buffer is required", "", __FILE__, __LINE__);
+
+	gl.viewport(viewportX, viewportY, viewportW, viewportH);
+	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	gl.enable(GL_DEPTH_TEST);
+	gl.depthFunc(GL_LESS);
+
+	static const deUint16 quadIndices[] = { 0, 1, 2, 2, 1, 3 };
+
+	// Render with given shader.
+	{
+		glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(s_vertexShaderSrc, m_fragSrc.c_str()));
+		log << program;
+
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		const float coord[] =
+		{
+			0.0f, 0.0f,
+			0.0f, 1.0f,
+			1.0f, 0.0f,
+			1.0f, 1.0f
+		};
+		const float position[] =
+		{
+			-1.0f, -1.0f, +1.0f, 1.0f,
+			-1.0f, +1.0f,  0.0f, 1.0f,
+			+1.0f, -1.0f,  0.0f, 1.0f,
+			+1.0f, +1.0f, -1.0f, 1.0f
+		};
+
+		gl.useProgram(program.getProgram());
+		gl.uniform4f(gl.getUniformLocation(program.getProgram(), "u_color"), 0.0f, 1.0f, 0.0f, 1.0f);
+
+		// Setup default helper uniforms.
+		gls::setupDefaultUniforms(m_context.getRenderContext(), program.getProgram());
+
+		{
+			glu::VertexArrayBinding vertexArrays[] =
+			{
+				glu::va::Float("a_position",	4, 4, 0, &position[0]),
+				glu::va::Float("a_coord",		2, 4, 0, &coord[0])
+			};
+			glu::draw(m_context.getRenderContext(), program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(quadIndices), &quadIndices[0]));
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw test quad");
+	}
+
+	// Visualize by rendering full-screen quads with increasing depth and color.
+	{
+		glu::ShaderProgram program (m_context.getRenderContext(), glu::makeVtxFragSources(s_vertexShaderSrc, s_defaultFragmentShaderSrc));
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Compile failed");
+		}
+
+		int posLoc		= gl.getAttribLocation(program.getProgram(), "a_position");
+		int colorLoc	= gl.getUniformLocation(program.getProgram(), "u_color");
+
+		gl.useProgram(program.getProgram());
+		gl.depthMask(GL_FALSE);
+
+		for (int stepNdx = 0; stepNdx < numDepthSteps; stepNdx++)
+		{
+			float	f		= (float)stepNdx*depthStep;
+			float	depth	= f*2.0f - 1.0f;
+			Vec4	color	= Vec4(f, f, f, 1.0f);
+
+			const float position[] =
+			{
+				-1.0f, -1.0f, depth, 1.0f,
+				-1.0f, +1.0f, depth, 1.0f,
+				+1.0f, -1.0f, depth, 1.0f,
+				+1.0f, +1.0f, depth, 1.0f
+			};
+			glu::VertexArrayBinding	posBinding = glu::va::Float(posLoc, 4, 4, 0, &position[0]);
+
+			gl.uniform4fv(colorLoc, 1, color.getPtr());
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(quadIndices), &quadIndices[0]));
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Visualization draw");
+	}
+
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedFrame.getAccess());
+
+	// Render reference.
+	for (int y = 0; y < referenceFrame.getHeight(); y++)
+	{
+		for (int x = 0; x < referenceFrame.getWidth(); x++)
+		{
+			float	xf		= ((float)x + 0.5f) / (float)referenceFrame.getWidth();
+			float	yf		= ((float)y + 0.5f) / (float)referenceFrame.getHeight();
+			float	d		= m_evalFunc(Vec2(xf, yf));
+			int		step	= (int)deFloatFloor(d / depthStep);
+			int		col		= de::clamp(deRoundFloatToInt32((float)step*depthStep*255.0f), 0, 255);
+
+			referenceFrame.setPixel(x, y, tcu::RGBA(col, col, col, 0xff));
+		}
+	}
+
+	bool isOk = tcu::fuzzyCompare(log, "Result", "Image comparison result", referenceFrame, renderedFrame, 0.05f, tcu::COMPARE_LOG_RESULT);
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Fail");
+	return STOP;
+}
+
+FragDepthTests::FragDepthTests (Context& context)
+	: TestCaseGroup(context, "fragdepth", "gl_FragDepth tests")
+{
+}
+
+FragDepthTests::~FragDepthTests (void)
+{
+}
+
+static float evalConstDepth		(const Vec2& coord)	{ DE_UNREF(coord); return 0.5f; }
+static float evalDynamicDepth	(const Vec2& coord)	{ return (coord.x()+coord.y())*0.5f; }
+static float evalNoWrite		(const Vec2& coord)	{ return 1.0f - (coord.x()+coord.y())*0.5f; }
+
+static float evalDynamicConditionalDepth (const Vec2& coord)
+{
+	float d = (coord.x()+coord.y())*0.5f;
+	if (coord.y() < 0.5f)
+		return d;
+	else
+		return 1.0f - d;
+}
+
+void FragDepthTests::init (void)
+{
+	static const struct
+	{
+		const char*			name;
+		const char*			desc;
+		EvalFragDepthFunc	evalFunc;
+		const char*			fragSrc;
+	} cases[] =
+	{
+		{
+			"no_write", "No gl_FragDepth write", evalNoWrite,
+			"#version 300 es\n"
+			"uniform highp vec4 u_color;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = u_color;\n"
+			"}\n"
+		},
+		{
+			"const", "Const depth write", evalConstDepth,
+			"#version 300 es\n"
+			"uniform highp vec4 u_color;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = u_color;\n"
+			"	gl_FragDepth = 0.5;\n"
+			"}\n"
+		},
+		{
+			"uniform", "Uniform depth write", evalConstDepth,
+			"#version 300 es\n"
+			"uniform highp vec4 u_color;\n"
+			"uniform highp float uf_half;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = u_color;\n"
+			"	gl_FragDepth = uf_half;\n"
+			"}\n"
+		},
+		{
+			"dynamic", "Dynamic depth write", evalDynamicDepth,
+			"#version 300 es\n"
+			"uniform highp vec4 u_color;\n"
+			"in highp vec2 v_coord;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = u_color;\n"
+			"	gl_FragDepth = (v_coord.x+v_coord.y)*0.5;\n"
+			"}\n"
+		},
+		{
+			"fragcoord_z", "gl_FragDepth write from gl_FragCoord.z", evalNoWrite,
+			"#version 300 es\n"
+			"uniform highp vec4 u_color;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = u_color;\n"
+			"	gl_FragDepth = gl_FragCoord.z;\n"
+			"}\n"
+		},
+		{
+			"uniform_conditional_write", "Uniform conditional write", evalDynamicDepth,
+			"#version 300 es\n"
+			"uniform highp vec4 u_color;\n"
+			"uniform bool ub_true;\n"
+			"in highp vec2 v_coord;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = u_color;\n"
+			"	if (ub_true)\n"
+			"		gl_FragDepth = (v_coord.x+v_coord.y)*0.5;\n"
+			"}\n"
+		},
+		{
+			"dynamic_conditional_write", "Uniform conditional write", evalDynamicConditionalDepth,
+			"#version 300 es\n"
+			"uniform highp vec4 u_color;\n"
+			"uniform bool ub_true;\n"
+			"in highp vec2 v_coord;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = u_color;\n"
+			"	mediump float d = (v_coord.x+v_coord.y)*0.5f;\n"
+			"	if (v_coord.y < 0.5)\n"
+			"		gl_FragDepth = d;\n"
+			"	else\n"
+			"		gl_FragDepth = 1.0 - d;\n"
+			"}\n"
+		},
+		{
+			"uniform_loop_write", "Uniform loop write", evalConstDepth,
+			"#version 300 es\n"
+			"uniform highp vec4 u_color;\n"
+			"uniform int ui_two;\n"
+			"uniform highp float uf_fourth;\n"
+			"in highp vec2 v_coord;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = u_color;\n"
+			"	gl_FragDepth = 0.0;\n"
+			"	for (int i = 0; i < ui_two; i++)\n"
+			"		gl_FragDepth += uf_fourth;\n"
+			"}\n"
+		},
+		{
+			"write_in_function", "Uniform loop write", evalDynamicDepth,
+			"#version 300 es\n"
+			"uniform highp vec4 u_color;\n"
+			"uniform highp float uf_half;\n"
+			"in highp vec2 v_coord;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void myfunc (highp vec2 coord)\n"
+			"{\n"
+			"	gl_FragDepth = (coord.x+coord.y)*0.5;\n"
+			"}\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = u_color;\n"
+			"	myfunc(v_coord);\n"
+			"}\n"
+		}
+	};
+
+	// .write
+	tcu::TestCaseGroup* writeGroup = new tcu::TestCaseGroup(m_testCtx, "write", "gl_FragDepth write tests");
+	addChild(writeGroup);
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
+		writeGroup->addChild(new FragDepthWriteCase(m_context, cases[ndx].name, cases[ndx].desc, cases[ndx].fragSrc, cases[ndx].evalFunc));
+
+	// .compare
+	tcu::TestCaseGroup* compareGroup = new tcu::TestCaseGroup(m_testCtx, "compare", "gl_FragDepth used with depth comparison");
+	addChild(compareGroup);
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
+		compareGroup->addChild(new FragDepthCompareCase(m_context, cases[ndx].name, cases[ndx].desc, cases[ndx].fragSrc, cases[ndx].evalFunc, GL_LESS));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFragDepthTests.hpp b/modules/gles3/functional/es3fFragDepthTests.hpp
new file mode 100644
index 0000000..c56ab56
--- /dev/null
+++ b/modules/gles3/functional/es3fFragDepthTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFRAGDEPTHTESTS_HPP
+#define _ES3FFRAGDEPTHTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief gl_FragDepth tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FragDepthTests : public TestCaseGroup
+{
+public:
+						FragDepthTests		(Context& context);
+						~FragDepthTests		(void);
+
+	void				init				(void);
+
+private:
+						FragDepthTests		(const FragDepthTests& other);
+	FragDepthTests&		operator=			(const FragDepthTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFRAGDEPTHTESTS_HPP
diff --git a/modules/gles3/functional/es3fFragOpInteractionTests.cpp b/modules/gles3/functional/es3fFragOpInteractionTests.cpp
new file mode 100644
index 0000000..0844742
--- /dev/null
+++ b/modules/gles3/functional/es3fFragOpInteractionTests.cpp
@@ -0,0 +1,86 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader - render state interaction tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFragOpInteractionTests.hpp"
+#include "glsFragOpInteractionCase.hpp"
+#include "deStringUtil.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using gls::FragOpInteractionCase;
+
+FragOpInteractionTests::FragOpInteractionTests (Context& context)
+	: TestCaseGroup(context, "interaction", "Shader - Render State Interaction Tests")
+{
+}
+
+FragOpInteractionTests::~FragOpInteractionTests (void)
+{
+}
+
+void FragOpInteractionTests::init (void)
+{
+	// .basic
+	{
+		tcu::TestCaseGroup* const	basicGroup	= new tcu::TestCaseGroup(m_testCtx, "basic_shader", "Basic shaders");
+		const deUint32				baseSeed	= 0xac2301cf;
+		const int					numCases	= 100;
+		rsg::ProgramParameters		params;
+
+		addChild(basicGroup);
+
+		params.version					= rsg::VERSION_300;
+
+		params.useScalarConversions		= true;
+		params.useSwizzle				= true;
+		params.useComparisonOps			= true;
+		params.useConditionals			= true;
+
+		params.vertexParameters.randomize						= true;
+		params.vertexParameters.maxStatementDepth				= 3;
+		params.vertexParameters.maxStatementsPerBlock			= 4;
+		params.vertexParameters.maxExpressionDepth				= 4;
+		params.vertexParameters.maxCombinedVariableScalars		= 64;
+
+		params.fragmentParameters.randomize						= true;
+		params.fragmentParameters.maxStatementDepth				= 3;
+		params.fragmentParameters.maxStatementsPerBlock			= 4;
+		params.fragmentParameters.maxExpressionDepth			= 4;
+		params.fragmentParameters.maxCombinedVariableScalars	= 64;
+
+		for (int ndx = 0; ndx < numCases; ndx++)
+		{
+			params.seed = baseSeed ^ deInt32Hash(ndx);
+			basicGroup->addChild(new FragOpInteractionCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), de::toString(ndx).c_str(), params));
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFragOpInteractionTests.hpp b/modules/gles3/functional/es3fFragOpInteractionTests.hpp
new file mode 100644
index 0000000..c8a0197
--- /dev/null
+++ b/modules/gles3/functional/es3fFragOpInteractionTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFRAGOPINTERACTIONTESTS_HPP
+#define _ES3FFRAGOPINTERACTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader - render state interaction tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FragOpInteractionTests : public TestCaseGroup
+{
+public:
+								FragOpInteractionTests		(Context& context);
+								~FragOpInteractionTests		(void);
+
+	void						init						(void);
+
+private:
+								FragOpInteractionTests		(const FragOpInteractionTests&);
+	FragOpInteractionTests&		operator=					(const FragOpInteractionTests&);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFRAGOPINTERACTIONTESTS_HPP
diff --git a/modules/gles3/functional/es3fFragmentOutputTests.cpp b/modules/gles3/functional/es3fFragmentOutputTests.cpp
new file mode 100644
index 0000000..319dcc9
--- /dev/null
+++ b/modules/gles3/functional/es3fFragmentOutputTests.cpp
@@ -0,0 +1,1297 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Fragment shader output tests.
+ *
+ * \todo [2012-04-10 pyry] Missing:
+ *  + non-contiguous attachments in framebuffer
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFragmentOutputTests.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+
+// For getFormatName() \todo [pyry] Move to glu?
+#include "es3fFboTestUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::IVec2;
+using tcu::IVec4;
+using tcu::UVec2;
+using tcu::UVec4;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::BVec4;
+using tcu::TestLog;
+using FboTestUtil::getFormatName;
+using FboTestUtil::getFramebufferReadFormat;
+
+struct BufferSpec
+{
+	BufferSpec (void)
+		: format	(GL_NONE)
+		, width		(0)
+		, height	(0)
+		, samples	(0)
+	{
+	}
+
+	BufferSpec (deUint32 format_, int width_, int height_, int samples_)
+		: format	(format_)
+		, width		(width_)
+		, height	(height_)
+		, samples	(samples_)
+	{
+	}
+
+	deUint32	format;
+	int			width;
+	int			height;
+	int			samples;
+};
+
+struct FragmentOutput
+{
+	FragmentOutput (void)
+		: type			(glu::TYPE_LAST)
+		, precision		(glu::PRECISION_LAST)
+		, location		(0)
+		, arrayLength	(0)
+	{
+	}
+
+	FragmentOutput (glu::DataType type_, glu::Precision precision_, int location_, int arrayLength_ = 0)
+		: type			(type_)
+		, precision		(precision_)
+		, location		(location_)
+		, arrayLength	(arrayLength_)
+	{
+	}
+
+	glu::DataType	type;
+	glu::Precision	precision;
+	int				location;
+	int				arrayLength;	//!< 0 if not an array.
+};
+
+struct OutputVec
+{
+	vector<FragmentOutput> outputs;
+
+	OutputVec& operator<< (const FragmentOutput& output)
+	{
+		outputs.push_back(output);
+		return *this;
+	}
+
+	vector<FragmentOutput> toVec (void) const
+	{
+		return outputs;
+	}
+};
+
+class FragmentOutputCase : public TestCase
+{
+public:
+								FragmentOutputCase			(Context& context, const char* name, const char* desc, const vector<BufferSpec>& fboSpec, const vector<FragmentOutput>& outputs);
+								~FragmentOutputCase			(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								FragmentOutputCase			(const FragmentOutputCase& other);
+	FragmentOutputCase&			operator=					(const FragmentOutputCase& other);
+
+	vector<BufferSpec>			m_fboSpec;
+	vector<FragmentOutput>		m_outputs;
+
+	glu::ShaderProgram*			m_program;
+	deUint32					m_framebuffer;
+	vector<deUint32>			m_renderbuffers;
+};
+
+FragmentOutputCase::FragmentOutputCase (Context& context, const char* name, const char* desc, const vector<BufferSpec>& fboSpec, const vector<FragmentOutput>& outputs)
+	: TestCase		(context, name, desc)
+	, m_fboSpec		(fboSpec)
+	, m_outputs		(outputs)
+	, m_program		(DE_NULL)
+	, m_framebuffer	(0)
+{
+}
+
+FragmentOutputCase::~FragmentOutputCase (void)
+{
+	deinit();
+}
+
+static glu::ShaderProgram* createProgram (const glu::RenderContext& context, const vector<FragmentOutput>& outputs)
+{
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+
+	vtx << "#version 300 es\n"
+		<< "in highp vec4 a_position;\n";
+	frag << "#version 300 es\n";
+
+	// Input-output declarations.
+	for (int outNdx = 0; outNdx < (int)outputs.size(); outNdx++)
+	{
+		const FragmentOutput&	output		= outputs[outNdx];
+		bool					isArray		= output.arrayLength > 0;
+		const char*				typeName	= glu::getDataTypeName(output.type);
+		const char*				precName	= glu::getPrecisionName(output.precision);
+		bool					isFloat		= glu::isDataTypeFloatOrVec(output.type);
+		const char*				interp		= isFloat ? "smooth" : "flat";
+
+		if (isArray)
+		{
+			for (int elemNdx = 0; elemNdx < output.arrayLength; elemNdx++)
+			{
+				vtx << "in " << precName << " " << typeName << " in" << outNdx << "_" << elemNdx << ";\n"
+					<< interp << " out " << precName << " " << typeName << " var" << outNdx << "_" << elemNdx << ";\n";
+				frag << interp << " in " << precName << " " << typeName << " var" << outNdx << "_" << elemNdx << ";\n";
+			}
+			frag << "layout(location = " << output.location << ") out " << precName << " " << typeName << " out" << outNdx << "[" << output.arrayLength << "];\n";
+		}
+		else
+		{
+			vtx << "in " << precName << " " << typeName << " in" << outNdx << ";\n"
+				<< interp << " out " << precName << " " << typeName << " var" << outNdx << ";\n";
+			frag << interp << " in " << precName << " " << typeName << " var" << outNdx << ";\n"
+				 << "layout(location = " << output.location << ") out " << precName << " " << typeName << " out" << outNdx << ";\n";
+		}
+	}
+
+	vtx << "\nvoid main()\n{\n";
+	frag << "\nvoid main()\n{\n";
+
+	vtx << "	gl_Position = a_position;\n";
+
+	// Copy body
+	for (int outNdx = 0; outNdx < (int)outputs.size(); outNdx++)
+	{
+		const FragmentOutput&	output		= outputs[outNdx];
+		bool					isArray		= output.arrayLength > 0;
+
+		if (isArray)
+		{
+			for (int elemNdx = 0; elemNdx < output.arrayLength; elemNdx++)
+			{
+				vtx << "\tvar" << outNdx << "_" << elemNdx << " = in" << outNdx << "_" << elemNdx << ";\n";
+				frag << "\tout" << outNdx << "[" << elemNdx << "] = var" << outNdx << "_" << elemNdx << ";\n";
+			}
+		}
+		else
+		{
+			vtx << "\tvar" << outNdx << " = in" << outNdx << ";\n";
+			frag << "\tout" << outNdx << " = var" << outNdx << ";\n";
+		}
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	return new glu::ShaderProgram(context, glu::makeVtxFragSources(vtx.str(), frag.str()));
+}
+
+void FragmentOutputCase::init (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	TestLog&				log		= m_testCtx.getLog();
+
+	// Check that all attachments are supported
+	for (std::vector<BufferSpec>::const_iterator bufIter = m_fboSpec.begin(); bufIter != m_fboSpec.end(); ++bufIter)
+	{
+		if (!glu::isSizedFormatColorRenderable(m_context.getRenderContext(), m_context.getContextInfo(), bufIter->format))
+			throw tcu::NotSupportedError("Unsupported attachment format");
+	}
+
+	DE_ASSERT(!m_program);
+	m_program = createProgram(m_context.getRenderContext(), m_outputs);
+
+	log << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Compile failed");
+
+	// Print render target info to log.
+	log << TestLog::Section("Framebuffer", "Framebuffer configuration");
+
+	for (int ndx = 0; ndx < (int)m_fboSpec.size(); ndx++)
+		log << TestLog::Message << "COLOR_ATTACHMENT" << ndx << ": "
+								<< glu::getPixelFormatStr(m_fboSpec[ndx].format) << ", "
+								<< m_fboSpec[ndx].width << "x" << m_fboSpec[ndx].height << ", "
+								<< m_fboSpec[ndx].samples << " samples"
+			<< TestLog::EndMessage;
+
+	log << TestLog::EndSection;
+
+	// Create framebuffer.
+	m_renderbuffers.resize(m_fboSpec.size(), 0);
+	gl.genFramebuffers(1, &m_framebuffer);
+	gl.genRenderbuffers((int)m_renderbuffers.size(), &m_renderbuffers[0]);
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+
+	for (int bufNdx = 0; bufNdx < (int)m_renderbuffers.size(); bufNdx++)
+	{
+		deUint32			rbo			= m_renderbuffers[bufNdx];
+		const BufferSpec&	bufSpec		= m_fboSpec[bufNdx];
+		deUint32			attachment	= GL_COLOR_ATTACHMENT0+bufNdx;
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+		gl.renderbufferStorageMultisample(GL_RENDERBUFFER, bufSpec.samples, bufSpec.format, bufSpec.width, bufSpec.height);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rbo);
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After framebuffer setup");
+
+	deUint32 fboStatus = gl.checkFramebufferStatus(GL_FRAMEBUFFER);
+	if (fboStatus == GL_FRAMEBUFFER_UNSUPPORTED)
+		throw tcu::NotSupportedError("Framebuffer not supported", "", __FILE__, __LINE__);
+	else if (fboStatus != GL_FRAMEBUFFER_COMPLETE)
+		throw tcu::TestError((string("Incomplete framebuffer: ") + glu::getFramebufferStatusStr(fboStatus).toString()).c_str(), "", __FILE__, __LINE__);
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After init");
+}
+
+void FragmentOutputCase::deinit (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_framebuffer)
+	{
+		gl.deleteFramebuffers(1, &m_framebuffer);
+		m_framebuffer = 0;
+	}
+
+	if (!m_renderbuffers.empty())
+	{
+		gl.deleteRenderbuffers((int)m_renderbuffers.size(), &m_renderbuffers[0]);
+		m_renderbuffers.clear();
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+static IVec2 getMinSize (const vector<BufferSpec>& fboSpec)
+{
+	IVec2 minSize(0x7fffffff, 0x7fffffff);
+	for (vector<BufferSpec>::const_iterator i = fboSpec.begin(); i != fboSpec.end(); i++)
+	{
+		minSize.x() = de::min(minSize.x(), i->width);
+		minSize.y() = de::min(minSize.y(), i->height);
+	}
+	return minSize;
+}
+
+static int getNumInputVectors (const vector<FragmentOutput>& outputs)
+{
+	int numVecs = 0;
+	for (vector<FragmentOutput>::const_iterator i = outputs.begin(); i != outputs.end(); i++)
+		numVecs += (i->arrayLength > 0 ? i->arrayLength : 1);
+	return numVecs;
+}
+
+static Vec2 getFloatRange (glu::Precision precision)
+{
+	// \todo [2012-04-09 pyry] Not quite the full ranges.
+	static const Vec2 ranges[] =
+	{
+		Vec2(-2.0f, 2.0f),
+		Vec2(-16000.0f, 16000.0f),
+		Vec2(-1e35f, 1e35f)
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(ranges) == glu::PRECISION_LAST);
+	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(ranges)));
+	return ranges[precision];
+}
+
+static IVec2 getIntRange (glu::Precision precision)
+{
+	static const IVec2 ranges[] =
+	{
+		IVec2(-(1<< 7), (1<< 7)-1),
+		IVec2(-(1<<15), (1<<15)-1),
+		IVec2(0x80000000, 0x7fffffff)
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(ranges) == glu::PRECISION_LAST);
+	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(ranges)));
+	return ranges[precision];
+}
+
+static UVec2 getUintRange (glu::Precision precision)
+{
+	static const UVec2 ranges[] =
+	{
+		UVec2(0, (1<< 8)-1),
+		UVec2(0, (1<<16)-1),
+		UVec2(0, 0xffffffffu)
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(ranges) == glu::PRECISION_LAST);
+	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(ranges)));
+	return ranges[precision];
+}
+
+static inline Vec4 readVec4 (const float* ptr, int numComponents)
+{
+	DE_ASSERT(numComponents >= 1);
+	return Vec4(ptr[0],
+				numComponents >= 2 ? ptr[1] : 0.0f,
+				numComponents >= 3 ? ptr[2] : 0.0f,
+				numComponents >= 4 ? ptr[3] : 0.0f);
+}
+
+static inline IVec4 readIVec4 (const int* ptr, int numComponents)
+{
+	DE_ASSERT(numComponents >= 1);
+	return IVec4(ptr[0],
+				 numComponents >= 2 ? ptr[1] : 0,
+				 numComponents >= 3 ? ptr[2] : 0,
+				 numComponents >= 4 ? ptr[3] : 0);
+}
+
+static void renderFloatReference (const tcu::PixelBufferAccess& dst, int gridWidth, int gridHeight, int numComponents, const float* vertices)
+{
+	const bool	isSRGB		= dst.getFormat().order == tcu::TextureFormat::sRGB ||dst.getFormat().order == tcu::TextureFormat::sRGBA;
+	const float	cellW		= (float)dst.getWidth() / (float)(gridWidth-1);
+	const float	cellH		= (float)dst.getHeight() / (float)(gridHeight-1);
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	{
+		for (int x = 0; x < dst.getWidth(); x++)
+		{
+			const int		cellX	= de::clamp(deFloorFloatToInt32((float)x / cellW), 0, gridWidth-2);
+			const int		cellY	= de::clamp(deFloorFloatToInt32((float)y / cellH), 0, gridHeight-2);
+			const float		xf		= ((float)x - (float)cellX*cellW + 0.5f) / cellW;
+			const float		yf		= ((float)y - (float)cellY*cellH + 0.5f) / cellH;
+			const Vec4		v00		= readVec4(vertices + ((cellY+0)*gridWidth + cellX+0)*numComponents, numComponents);
+			const Vec4		v01		= readVec4(vertices + ((cellY+1)*gridWidth + cellX+0)*numComponents, numComponents);
+			const Vec4		v10		= readVec4(vertices + ((cellY+0)*gridWidth + cellX+1)*numComponents, numComponents);
+			const Vec4		v11		= readVec4(vertices + ((cellY+1)*gridWidth + cellX+1)*numComponents, numComponents);
+			const bool		tri		= xf + yf >= 1.0f;
+			const Vec4&		v0		= tri ? v11 : v00;
+			const Vec4&		v1		= tri ? v01 : v10;
+			const Vec4&		v2		= tri ? v10 : v01;
+			const float		s		= tri ? 1.0f-xf : xf;
+			const float		t		= tri ? 1.0f-yf : yf;
+			const Vec4		color	= v0 + (v1-v0)*s + (v2-v0)*t;
+
+			dst.setPixel(isSRGB ? tcu::linearToSRGB(color) : color, x, y);
+		}
+	}
+}
+
+static void renderIntReference (const tcu::PixelBufferAccess& dst, int gridWidth, int gridHeight, int numComponents, const int* vertices)
+{
+	float	cellW		= (float)dst.getWidth() / (float)(gridWidth-1);
+	float	cellH		= (float)dst.getHeight() / (float)(gridHeight-1);
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	{
+		for (int x = 0; x < dst.getWidth(); x++)
+		{
+			int			cellX	= de::clamp(deFloorFloatToInt32((float)x / cellW), 0, gridWidth-2);
+			int			cellY	= de::clamp(deFloorFloatToInt32((float)y / cellH), 0, gridHeight-2);
+			IVec4		c		= readIVec4(vertices + (cellY*gridWidth + cellX+1)*numComponents, numComponents);
+
+			dst.setPixel(c, x, y);
+		}
+	}
+}
+
+static const IVec4 s_swizzles[] =
+{
+	IVec4(0,1,2,3),
+	IVec4(1,2,3,0),
+	IVec4(2,3,0,1),
+	IVec4(3,0,1,2),
+	IVec4(3,2,1,0),
+	IVec4(2,1,0,3),
+	IVec4(1,0,3,2),
+	IVec4(0,3,2,1)
+};
+
+template <typename T>
+inline tcu::Vector<T, 4> swizzleVec (const tcu::Vector<T, 4>& vec, int swzNdx)
+{
+	const IVec4& swz = s_swizzles[swzNdx % DE_LENGTH_OF_ARRAY(s_swizzles)];
+	return vec.swizzle(swz[0], swz[1], swz[2], swz[3]);
+}
+
+namespace
+{
+
+struct AttachmentData
+{
+	tcu::TextureFormat		format;					//!< Actual format of attachment.
+	tcu::TextureFormat		referenceFormat;		//!< Used for reference rendering.
+	tcu::TextureFormat		readFormat;
+	int						numWrittenChannels;
+	glu::Precision			outPrecision;
+	vector<deUint8>			renderedData;
+	vector<deUint8>			referenceData;
+};
+
+} // anonymous
+
+FragmentOutputCase::IterateResult FragmentOutputCase::iterate (void)
+{
+	TestLog&					log					= m_testCtx.getLog();
+	const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
+
+	// Compute grid size & index list.
+	const int					minCellSize			= 8;
+	const IVec2					minBufSize			= getMinSize(m_fboSpec);
+	const int					gridWidth			= de::clamp(minBufSize.x()/minCellSize, 1, 255)+1;
+	const int					gridHeight			= de::clamp(minBufSize.y()/minCellSize, 1, 255)+1;
+	const int					numVertices			= gridWidth*gridHeight;
+	const int					numQuads			= (gridWidth-1)*(gridHeight-1);
+	const int					numIndices			= numQuads*6;
+
+	const int					numInputVecs		= getNumInputVectors(m_outputs);
+	vector<vector<deUint32> >	inputs				(numInputVecs);
+	vector<float>				positions			(numVertices*4);
+	vector<deUint16>			indices				(numIndices);
+
+	const int					readAlignment		= 4;
+	const int					viewportW			= minBufSize.x();
+	const int					viewportH			= minBufSize.y();
+	const int					numAttachments		= (int)m_fboSpec.size();
+
+	vector<deUint32>			drawBuffers			(numAttachments);
+	vector<AttachmentData>		attachments			(numAttachments);
+
+	// Initialize attachment data.
+	for (int ndx = 0; ndx < numAttachments; ndx++)
+	{
+		const tcu::TextureFormat		texFmt			= glu::mapGLInternalFormat(m_fboSpec[ndx].format);
+		const tcu::TextureChannelClass	chnClass		= tcu::getTextureChannelClass(texFmt.type);
+		const bool						isFixedPoint	= chnClass == tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT ||
+														  chnClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
+
+		// \note Fixed-point formats use float reference to enable more accurate result verification.
+		const tcu::TextureFormat		refFmt			= isFixedPoint ? tcu::TextureFormat(texFmt.order, tcu::TextureFormat::FLOAT) : texFmt;
+		const tcu::TextureFormat		readFmt			= getFramebufferReadFormat(texFmt);
+		const int						attachmentW		= m_fboSpec[ndx].width;
+		const int						attachmentH		= m_fboSpec[ndx].height;
+
+		drawBuffers[ndx]					= GL_COLOR_ATTACHMENT0+ndx;
+		attachments[ndx].format				= texFmt;
+		attachments[ndx].readFormat			= readFmt;
+		attachments[ndx].referenceFormat	= refFmt;
+		attachments[ndx].renderedData.resize(readFmt.getPixelSize()*attachmentW*attachmentH);
+		attachments[ndx].referenceData.resize(refFmt.getPixelSize()*attachmentW*attachmentH);
+	}
+
+	// Initialize indices.
+	for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
+	{
+		int	quadY	= quadNdx / (gridWidth-1);
+		int quadX	= quadNdx - quadY*(gridWidth-1);
+
+		indices[quadNdx*6+0] = quadX + quadY*gridWidth;
+		indices[quadNdx*6+1] = quadX + (quadY+1)*gridWidth;
+		indices[quadNdx*6+2] = quadX + quadY*gridWidth + 1;
+		indices[quadNdx*6+3] = indices[quadNdx*6+1];
+		indices[quadNdx*6+4] = quadX + (quadY+1)*gridWidth + 1;
+		indices[quadNdx*6+5] = indices[quadNdx*6+2];
+	}
+
+	for (int y = 0; y < gridHeight; y++)
+	{
+		for (int x = 0; x < gridWidth; x++)
+		{
+			float	xf	= (float)x / (float)(gridWidth-1);
+			float	yf	= (float)y / (float)(gridHeight-1);
+
+			positions[(y*gridWidth + x)*4 + 0] = 2.0f*xf - 1.0f;
+			positions[(y*gridWidth + x)*4 + 1] = 2.0f*yf - 1.0f;
+			positions[(y*gridWidth + x)*4 + 2] = 0.0f;
+			positions[(y*gridWidth + x)*4 + 3] = 1.0f;
+		}
+	}
+
+	// Initialize input vectors.
+	{
+		int curInVec = 0;
+		for (int outputNdx = 0; outputNdx < (int)m_outputs.size(); outputNdx++)
+		{
+			const FragmentOutput&	output		= m_outputs[outputNdx];
+			bool					isFloat		= glu::isDataTypeFloatOrVec(output.type);
+			bool					isInt		= glu::isDataTypeIntOrIVec(output.type);
+			bool					isUint		= glu::isDataTypeUintOrUVec(output.type);
+			int						numVecs		= output.arrayLength > 0 ? output.arrayLength : 1;
+			int						numScalars	= glu::getDataTypeScalarSize(output.type);
+
+			for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
+			{
+				inputs[curInVec].resize(numVertices*numScalars);
+
+				// Record how many outputs are written in attachment.
+				DE_ASSERT(output.location+vecNdx < (int)attachments.size());
+				attachments[output.location+vecNdx].numWrittenChannels	= numScalars;
+				attachments[output.location+vecNdx].outPrecision		= output.precision;
+
+				if (isFloat)
+				{
+					Vec2		range	= getFloatRange(output.precision);
+					Vec4		minVal	(range.x());
+					Vec4		maxVal	(range.y());
+					float*		dst		= (float*)&inputs[curInVec][0];
+
+					if (de::inBounds(output.location+vecNdx, 0, (int)attachments.size()))
+					{
+						// \note Floating-point precision conversion is not well-defined. For that reason we must
+						//       limit value range to intersection of both data type and render target value ranges.
+						const tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(attachments[output.location+vecNdx].format);
+						minVal = tcu::max(minVal, fmtInfo.valueMin);
+						maxVal = tcu::min(maxVal, fmtInfo.valueMax);
+					}
+
+					m_testCtx.getLog() << TestLog::Message << "out" << curInVec << " value range: " << minVal << " -> " << maxVal << TestLog::EndMessage;
+
+					for (int y = 0; y < gridHeight; y++)
+					{
+						for (int x = 0; x < gridWidth; x++)
+						{
+							float	xf	= (float)x / (float)(gridWidth-1);
+							float	yf	= (float)y / (float)(gridHeight-1);
+
+							float	f0	= (xf + yf) * 0.5f;
+							float	f1	= 0.5f + (xf - yf) * 0.5f;
+							Vec4	f	= swizzleVec(Vec4(f0, f1, 1.0f-f0, 1.0f-f1), curInVec);
+							Vec4	c	= minVal + (maxVal-minVal)*f;
+							float*	v	= dst + (y*gridWidth + x)*numScalars;
+
+							for (int ndx = 0; ndx < numScalars; ndx++)
+								v[ndx] = c[ndx];
+						}
+					}
+				}
+				else if (isInt)
+				{
+					const IVec2	range	= getIntRange(output.precision);
+					IVec4		minVal	(range.x());
+					IVec4		maxVal	(range.y());
+
+					if (de::inBounds(output.location+vecNdx, 0, (int)attachments.size()))
+					{
+						// Limit to range of output format as conversion mode is not specified.
+						const IVec4 fmtBits		= tcu::getTextureFormatBitDepth(attachments[output.location+vecNdx].format);
+						const BVec4	isZero		= lessThanEqual(fmtBits, IVec4(0));
+						const IVec4	fmtMinVal	= (-(tcu::Vector<deInt64, 4>(1) << (fmtBits-1).cast<deInt64>())).asInt();
+						const IVec4	fmtMaxVal	= ((tcu::Vector<deInt64, 4>(1) << (fmtBits-1).cast<deInt64>())-deInt64(1)).asInt();
+
+						minVal = select(minVal, tcu::max(minVal, fmtMinVal), isZero);
+						maxVal = select(maxVal, tcu::min(maxVal, fmtMaxVal), isZero);
+					}
+
+					m_testCtx.getLog() << TestLog::Message << "out" << curInVec << " value range: " << minVal << " -> " << maxVal << TestLog::EndMessage;
+
+					const IVec4	rangeDiv	= swizzleVec((IVec4(gridWidth, gridHeight, gridWidth, gridHeight)-1), curInVec);
+					const IVec4	step		= ((maxVal.cast<deInt64>() - minVal.cast<deInt64>()) / (rangeDiv.cast<deInt64>())).asInt();
+					deInt32*	dst			= (deInt32*)&inputs[curInVec][0];
+
+					for (int y = 0; y < gridHeight; y++)
+					{
+						for (int x = 0; x < gridWidth; x++)
+						{
+							int			ix	= gridWidth - x - 1;
+							int			iy	= gridHeight - y - 1;
+							IVec4		c	= minVal + step*swizzleVec(IVec4(x, y, ix, iy), curInVec);
+							deInt32*	v	= dst + (y*gridWidth + x)*numScalars;
+
+							DE_ASSERT(boolAll(logicalAnd(greaterThanEqual(c, minVal), lessThanEqual(c, maxVal))));
+
+							for (int ndx = 0; ndx < numScalars; ndx++)
+								v[ndx] = c[ndx];
+						}
+					}
+				}
+				else if (isUint)
+				{
+					const UVec2	range	= getUintRange(output.precision);
+					UVec4		maxVal	(range.y());
+
+					if (de::inBounds(output.location+vecNdx, 0, (int)attachments.size()))
+					{
+						// Limit to range of output format as conversion mode is not specified.
+						const IVec4	fmtBits		= tcu::getTextureFormatBitDepth(attachments[output.location+vecNdx].format);
+						const UVec4	fmtMaxVal	= ((tcu::Vector<deUint64, 4>(1) << fmtBits.cast<deUint64>())-deUint64(1)).asUint();
+
+						maxVal = tcu::min(maxVal, fmtMaxVal);
+					}
+
+					m_testCtx.getLog() << TestLog::Message << "out" << curInVec << " value range: " << UVec4(0) << " -> " << maxVal << TestLog::EndMessage;
+
+					const IVec4	rangeDiv	= swizzleVec((IVec4(gridWidth, gridHeight, gridWidth, gridHeight)-1), curInVec);
+					const UVec4	step		= maxVal / rangeDiv.asUint();
+					deUint32*	dst			= &inputs[curInVec][0];
+
+					DE_ASSERT(range.x() == 0);
+
+					for (int y = 0; y < gridHeight; y++)
+					{
+						for (int x = 0; x < gridWidth; x++)
+						{
+							int			ix	= gridWidth - x - 1;
+							int			iy	= gridHeight - y - 1;
+							UVec4		c	= step*swizzleVec(IVec4(x, y, ix, iy).asUint(), curInVec);
+							deUint32*	v	= dst + (y*gridWidth + x)*numScalars;
+
+							DE_ASSERT(boolAll(lessThanEqual(c, maxVal)));
+
+							for (int ndx = 0; ndx < numScalars; ndx++)
+								v[ndx] = c[ndx];
+						}
+					}
+				}
+				else
+					DE_ASSERT(false);
+
+				curInVec += 1;
+			}
+		}
+	}
+
+	// Render using gl.
+	gl.useProgram(m_program->getProgram());
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+	gl.viewport(0, 0, viewportW, viewportH);
+	gl.drawBuffers((int)drawBuffers.size(), &drawBuffers[0]);
+	gl.disable(GL_DITHER); // Dithering causes issues with unorm formats. Those issues could be worked around in threshold, but it makes validation less accurate.
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After program setup");
+
+	{
+		int curInVec = 0;
+		for (int outputNdx = 0; outputNdx < (int)m_outputs.size(); outputNdx++)
+		{
+			const FragmentOutput&	output			= m_outputs[outputNdx];
+			bool					isArray			= output.arrayLength > 0;
+			bool					isFloat			= glu::isDataTypeFloatOrVec(output.type);
+			bool					isInt			= glu::isDataTypeIntOrIVec(output.type);
+			bool					isUint			= glu::isDataTypeUintOrUVec(output.type);
+			int						scalarSize		= glu::getDataTypeScalarSize(output.type);
+			deUint32				glScalarType	= isFloat	? GL_FLOAT			:
+													  isInt		? GL_INT			:
+													  isUint	? GL_UNSIGNED_INT	: GL_NONE;
+			int						numVecs			= isArray ? output.arrayLength : 1;
+
+			for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
+			{
+				string	name	= string("in") + de::toString(outputNdx) + (isArray ? string("_") + de::toString(vecNdx) : string());
+				int		loc		= gl.getAttribLocation(m_program->getProgram(), name.c_str());
+
+				if (loc >= 0)
+				{
+					gl.enableVertexAttribArray(loc);
+					if (isFloat)
+						gl.vertexAttribPointer(loc, scalarSize, glScalarType, GL_FALSE, 0, &inputs[curInVec][0]);
+					else
+						gl.vertexAttribIPointer(loc, scalarSize, glScalarType, 0, &inputs[curInVec][0]);
+				}
+				else
+					log << TestLog::Message << "Warning: No location for attribute '" << name << "' found." << TestLog::EndMessage;
+
+				curInVec += 1;
+			}
+		}
+	}
+	{
+		int posLoc = gl.getAttribLocation(m_program->getProgram(), "a_position");
+		TCU_CHECK(posLoc >= 0);
+		gl.enableVertexAttribArray(posLoc);
+		gl.vertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &positions[0]);
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After attribute setup");
+
+	gl.drawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, &indices[0]);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDrawElements");
+
+	// Read all attachment points.
+	for (int ndx = 0; ndx < numAttachments; ndx++)
+	{
+		const glu::TransferFormat		transferFmt		= glu::getTransferFormat(attachments[ndx].readFormat);
+		void*							dst				= &attachments[ndx].renderedData[0];
+
+		gl.readBuffer(GL_COLOR_ATTACHMENT0+ndx);
+		gl.readPixels(0, 0, minBufSize.x(), minBufSize.y(), transferFmt.format, transferFmt.dataType, dst);
+	}
+
+	// Render reference images.
+	{
+		int curInNdx = 0;
+		for (int outputNdx = 0; outputNdx < (int)m_outputs.size(); outputNdx++)
+		{
+			const FragmentOutput&	output			= m_outputs[outputNdx];
+			const bool				isArray			= output.arrayLength > 0;
+			const bool				isFloat			= glu::isDataTypeFloatOrVec(output.type);
+			const bool				isInt			= glu::isDataTypeIntOrIVec(output.type);
+			const bool				isUint			= glu::isDataTypeUintOrUVec(output.type);
+			const int				scalarSize		= glu::getDataTypeScalarSize(output.type);
+			const int				numVecs			= isArray ? output.arrayLength : 1;
+
+			for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
+			{
+				const int		location	= output.location+vecNdx;
+				const void*		inputData	= &inputs[curInNdx][0];
+
+				DE_ASSERT(de::inBounds(location, 0, (int)m_fboSpec.size()));
+
+				const int						bufW			= m_fboSpec[location].width;
+				const int						bufH			= m_fboSpec[location].height;
+				const tcu::PixelBufferAccess	buf				(attachments[location].referenceFormat, bufW, bufH, 1, &attachments[location].referenceData[0]);
+				const tcu::PixelBufferAccess	viewportBuf		= getSubregion(buf, 0, 0, 0, viewportW, viewportH, 1);
+
+				if (isInt || isUint)
+					renderIntReference(viewportBuf, gridWidth, gridHeight, scalarSize, (const int*)inputData);
+				else if (isFloat)
+					renderFloatReference(viewportBuf, gridWidth, gridHeight, scalarSize, (const float*)inputData);
+				else
+					DE_ASSERT(false);
+
+				curInNdx += 1;
+			}
+		}
+	}
+
+	// Compare all images.
+	bool allLevelsOk = true;
+	for (int attachNdx = 0; attachNdx < numAttachments; attachNdx++)
+	{
+		const int						attachmentW			= m_fboSpec[attachNdx].width;
+		const int						attachmentH			= m_fboSpec[attachNdx].height;
+		const int						numValidChannels	= attachments[attachNdx].numWrittenChannels;
+		const tcu::BVec4				cmpMask				(numValidChannels >= 1, numValidChannels >= 2, numValidChannels >= 3, numValidChannels >= 4);
+		const glu::Precision			outPrecision		= attachments[attachNdx].outPrecision;
+		const tcu::TextureFormat&		format				= attachments[attachNdx].format;
+		tcu::ConstPixelBufferAccess		rendered			(attachments[attachNdx].readFormat, attachmentW, attachmentH, 1, deAlign32(attachments[attachNdx].readFormat.getPixelSize()*attachmentW, readAlignment), 0, &attachments[attachNdx].renderedData[0]);
+		tcu::ConstPixelBufferAccess		reference			(attachments[attachNdx].referenceFormat, attachmentW, attachmentH, 1, &attachments[attachNdx].referenceData[0]);
+		tcu::TextureChannelClass		texClass			= tcu::getTextureChannelClass(format.type);
+		bool							isOk				= true;
+		const string					name				= string("Attachment") + de::toString(attachNdx);
+		const string					desc				= string("Color attachment ") + de::toString(attachNdx);
+
+		log << TestLog::Message << "Attachment " << attachNdx << ": " << numValidChannels << " channels have defined values and used for comparison" << TestLog::EndMessage;
+
+		switch (texClass)
+		{
+			case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+			{
+				UVec4		formatThreshold;		//!< Threshold computed based on format.
+				deUint32	precThreshold	= 0;	//!< Threshold computed based on output type precision
+				UVec4		finalThreshold;
+
+				switch (format.type)
+				{
+					case tcu::TextureFormat::FLOAT:							formatThreshold = UVec4(4);										break;
+					case tcu::TextureFormat::HALF_FLOAT:					formatThreshold = UVec4((1<<13) + 4);							break;
+					case tcu::TextureFormat::UNSIGNED_INT_11F_11F_10F_REV:	formatThreshold = UVec4((1<<17) + 4, (1<<17)+4, (1<<18)+4, 4);	break;
+					default:
+						DE_ASSERT(false);
+						break;
+				}
+
+				switch (outPrecision)
+				{
+					case glu::PRECISION_LOWP:		precThreshold	= (1<<21);	break;
+					case glu::PRECISION_MEDIUMP:	precThreshold	= (1<<13);	break;
+					case glu::PRECISION_HIGHP:		precThreshold	= 0;		break;
+					default:
+						DE_ASSERT(false);
+				}
+
+				finalThreshold = select(max(formatThreshold, UVec4(precThreshold)), UVec4(~0u), cmpMask);
+
+				isOk = tcu::floatUlpThresholdCompare(log, name.c_str(), desc.c_str(), reference, rendered, finalThreshold, tcu::COMPARE_LOG_RESULT);
+				break;
+			}
+
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+			{
+				// \note glReadPixels() allows only 8 bits to be read. This means that RGB10_A2 will loose some
+				// bits in the process and it must be taken into account when computing threshold.
+				const IVec4		bits			= min(IVec4(8), tcu::getTextureFormatBitDepth(format));
+				const Vec4		baseThreshold	= 1.0f / ((IVec4(1) << bits)-1).asFloat();
+				const Vec4		threshold		= select(baseThreshold, Vec4(2.0f), cmpMask);
+
+				isOk = tcu::floatThresholdCompare(log, name.c_str(), desc.c_str(), reference, rendered, threshold, tcu::COMPARE_LOG_RESULT);
+				break;
+			}
+
+			case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+			{
+				const tcu::UVec4 threshold = select(UVec4(0u), UVec4(~0u), cmpMask);
+				isOk = tcu::intThresholdCompare(log, name.c_str(), desc.c_str(), reference, rendered, threshold, tcu::COMPARE_LOG_RESULT);
+				break;
+			}
+
+			default:
+				TCU_FAIL("Unsupported comparison");
+				break;
+		}
+
+		if (!isOk)
+			allLevelsOk = false;
+	}
+
+	m_testCtx.setTestResult(allLevelsOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							allLevelsOk ? "Pass"				: "Image comparison failed");
+	return STOP;
+}
+
+FragmentOutputTests::FragmentOutputTests (Context& context)
+	: TestCaseGroup(context, "fragment_out", "Fragment output tests")
+{
+}
+
+FragmentOutputTests::~FragmentOutputTests (void)
+{
+}
+
+static FragmentOutputCase* createRandomCase (Context& context, int minRenderTargets, int maxRenderTargets, deUint32 seed)
+{
+	static const glu::DataType outputTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_FLOAT_VEC2,
+		glu::TYPE_FLOAT_VEC3,
+		glu::TYPE_FLOAT_VEC4,
+		glu::TYPE_INT,
+		glu::TYPE_INT_VEC2,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_INT_VEC4,
+		glu::TYPE_UINT,
+		glu::TYPE_UINT_VEC2,
+		glu::TYPE_UINT_VEC3,
+		glu::TYPE_UINT_VEC4
+	};
+	static const glu::Precision precisions[] =
+	{
+		glu::PRECISION_LOWP,
+		glu::PRECISION_MEDIUMP,
+		glu::PRECISION_HIGHP
+	};
+	static const deUint32 floatFormats[] =
+	{
+		GL_RGBA32F,
+		GL_RGBA16F,
+		GL_R11F_G11F_B10F,
+		GL_RG32F,
+		GL_RG16F,
+		GL_R32F,
+		GL_R16F,
+		GL_RGBA8,
+		GL_SRGB8_ALPHA8,
+		GL_RGB10_A2,
+		GL_RGBA4,
+		GL_RGB5_A1,
+		GL_RGB8,
+		GL_RGB565,
+		GL_RG8,
+		GL_R8
+	};
+	static const deUint32 intFormats[] =
+	{
+		GL_RGBA32I,
+		GL_RGBA16I,
+		GL_RGBA8I,
+		GL_RG32I,
+		GL_RG16I,
+		GL_RG8I,
+		GL_R32I,
+		GL_R16I,
+		GL_R8I
+	};
+	static const deUint32 uintFormats[] =
+	{
+		GL_RGBA32UI,
+		GL_RGBA16UI,
+		GL_RGBA8UI,
+		GL_RGB10_A2UI,
+		GL_RG32UI,
+		GL_RG16UI,
+		GL_RG8UI,
+		GL_R32UI,
+		GL_R16UI,
+		GL_R8UI
+	};
+
+	de::Random					rnd			(seed);
+	vector<FragmentOutput>		outputs;
+	vector<BufferSpec>			targets;
+	vector<glu::DataType>		outTypes;
+
+	int							numTargets	= rnd.getInt(minRenderTargets, maxRenderTargets);
+	const int					width		= 128; // \todo [2012-04-10 pyry] Separate randomized sizes per target?
+	const int					height		= 64;
+	const int					samples		= 0;
+
+	// Compute outputs.
+	int curLoc = 0;
+	while (curLoc < numTargets)
+	{
+		bool			useArray		= rnd.getFloat() < 0.3f;
+		int				maxArrayLen		= numTargets-curLoc;
+		int				arrayLen		= useArray ? rnd.getInt(1, maxArrayLen) : 0;
+		glu::DataType	basicType		= rnd.choose<glu::DataType>(&outputTypes[0], &outputTypes[0] + DE_LENGTH_OF_ARRAY(outputTypes));
+		glu::Precision	precision		= rnd.choose<glu::Precision>(&precisions[0], &precisions[0] + DE_LENGTH_OF_ARRAY(precisions));
+		int				numLocations	= useArray ? arrayLen : 1;
+
+		outputs.push_back(FragmentOutput(basicType, precision, curLoc, arrayLen));
+
+		for (int ndx = 0; ndx < numLocations; ndx++)
+			outTypes.push_back(basicType);
+
+		curLoc += numLocations;
+	}
+	DE_ASSERT(curLoc == numTargets);
+	DE_ASSERT((int)outTypes.size() == numTargets);
+
+	// Compute buffers.
+	while ((int)targets.size() < numTargets)
+	{
+		glu::DataType	outType		= outTypes[targets.size()];
+		bool			isFloat		= glu::isDataTypeFloatOrVec(outType);
+		bool			isInt		= glu::isDataTypeIntOrIVec(outType);
+		bool			isUint		= glu::isDataTypeUintOrUVec(outType);
+		deUint32		format		= 0;
+
+		if (isFloat)
+			format = rnd.choose<deUint32>(&floatFormats[0], &floatFormats[0] + DE_LENGTH_OF_ARRAY(floatFormats));
+		else if (isInt)
+			format = rnd.choose<deUint32>(&intFormats[0], &intFormats[0] + DE_LENGTH_OF_ARRAY(intFormats));
+		else if (isUint)
+			format = rnd.choose<deUint32>(&uintFormats[0], &uintFormats[0] + DE_LENGTH_OF_ARRAY(uintFormats));
+		else
+			DE_ASSERT(false);
+
+		targets.push_back(BufferSpec(format, width, height, samples));
+	}
+
+	return new FragmentOutputCase(context, de::toString(seed).c_str(), "", targets, outputs);
+}
+
+void FragmentOutputTests::init (void)
+{
+	static const deUint32 requiredFloatFormats[] =
+	{
+		GL_RGBA32F,
+		GL_RGBA16F,
+		GL_R11F_G11F_B10F,
+		GL_RG32F,
+		GL_RG16F,
+		GL_R32F,
+		GL_R16F
+	};
+	static const deUint32 requiredFixedFormats[] =
+	{
+		GL_RGBA8,
+		GL_SRGB8_ALPHA8,
+		GL_RGB10_A2,
+		GL_RGBA4,
+		GL_RGB5_A1,
+		GL_RGB8,
+		GL_RGB565,
+		GL_RG8,
+		GL_R8
+	};
+	static const deUint32 requiredIntFormats[] =
+	{
+		GL_RGBA32I,
+		GL_RGBA16I,
+		GL_RGBA8I,
+		GL_RG32I,
+		GL_RG16I,
+		GL_RG8I,
+		GL_R32I,
+		GL_R16I,
+		GL_R8I
+	};
+	static const deUint32 requiredUintFormats[] =
+	{
+		GL_RGBA32UI,
+		GL_RGBA16UI,
+		GL_RGBA8UI,
+		GL_RGB10_A2UI,
+		GL_RG32UI,
+		GL_RG16UI,
+		GL_RG8UI,
+		GL_R32UI,
+		GL_R16UI,
+		GL_R8UI
+	};
+
+	static const glu::Precision precisions[] =
+	{
+		glu::PRECISION_LOWP,
+		glu::PRECISION_MEDIUMP,
+		glu::PRECISION_HIGHP
+	};
+
+	// .basic.
+	{
+		tcu::TestCaseGroup* basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic fragment output tests");
+		addChild(basicGroup);
+
+		const int	width	= 64;
+		const int	height	= 64;
+		const int	samples	= 0;
+
+		// .float
+		tcu::TestCaseGroup* floatGroup = new tcu::TestCaseGroup(m_testCtx, "float", "Floating-point output tests");
+		basicGroup->addChild(floatGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFloatFormats); fmtNdx++)
+		{
+			deUint32			format		= requiredFloatFormats[fmtNdx];
+			string				fmtName		= getFormatName(format);
+			vector<BufferSpec>	fboSpec;
+
+			fboSpec.push_back(BufferSpec(format, width, height, samples));
+
+			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+			{
+				glu::Precision	prec		= precisions[precNdx];
+				string			precName	= glu::getPrecisionName(prec);
+
+				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_float").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT,		prec, 0)).toVec()));
+				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2,	prec, 0)).toVec()));
+				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3,	prec, 0)).toVec()));
+				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4,	prec, 0)).toVec()));
+			}
+		}
+
+		// .fixed
+		tcu::TestCaseGroup* fixedGroup = new tcu::TestCaseGroup(m_testCtx, "fixed", "Fixed-point output tests");
+		basicGroup->addChild(fixedGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFixedFormats); fmtNdx++)
+		{
+			deUint32			format		= requiredFixedFormats[fmtNdx];
+			string				fmtName		= getFormatName(format);
+			vector<BufferSpec>	fboSpec;
+
+			fboSpec.push_back(BufferSpec(format, width, height, samples));
+
+			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+			{
+				glu::Precision	prec		= precisions[precNdx];
+				string			precName	= glu::getPrecisionName(prec);
+
+				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_float").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT,		prec, 0)).toVec()));
+				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2,	prec, 0)).toVec()));
+				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3,	prec, 0)).toVec()));
+				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4,	prec, 0)).toVec()));
+			}
+		}
+
+		// .int
+		tcu::TestCaseGroup* intGroup = new tcu::TestCaseGroup(m_testCtx, "int", "Integer output tests");
+		basicGroup->addChild(intGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredIntFormats); fmtNdx++)
+		{
+			deUint32			format		= requiredIntFormats[fmtNdx];
+			string				fmtName		= getFormatName(format);
+			vector<BufferSpec>	fboSpec;
+
+			fboSpec.push_back(BufferSpec(format, width, height, samples));
+
+			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+			{
+				glu::Precision	prec		= precisions[precNdx];
+				string			precName	= glu::getPrecisionName(prec);
+
+				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_int").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT,		prec, 0)).toVec()));
+				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC2,	prec, 0)).toVec()));
+				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC3,	prec, 0)).toVec()));
+				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC4,	prec, 0)).toVec()));
+			}
+		}
+
+		// .uint
+		tcu::TestCaseGroup* uintGroup = new tcu::TestCaseGroup(m_testCtx, "uint", "Usigned integer output tests");
+		basicGroup->addChild(uintGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredUintFormats); fmtNdx++)
+		{
+			deUint32			format		= requiredUintFormats[fmtNdx];
+			string				fmtName		= getFormatName(format);
+			vector<BufferSpec>	fboSpec;
+
+			fboSpec.push_back(BufferSpec(format, width, height, samples));
+
+			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+			{
+				glu::Precision	prec		= precisions[precNdx];
+				string			precName	= glu::getPrecisionName(prec);
+
+				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uint").c_str(),		"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT,			prec, 0)).toVec()));
+				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC2,	prec, 0)).toVec()));
+				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC3,	prec, 0)).toVec()));
+				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC4,	prec, 0)).toVec()));
+			}
+		}
+	}
+
+	// .array
+	{
+		tcu::TestCaseGroup* arrayGroup = new tcu::TestCaseGroup(m_testCtx, "array", "Array outputs");
+		addChild(arrayGroup);
+
+		const int	width		= 64;
+		const int	height		= 64;
+		const int	samples		= 0;
+		const int	numTargets	= 3;
+
+		// .float
+		tcu::TestCaseGroup* floatGroup = new tcu::TestCaseGroup(m_testCtx, "float", "Floating-point output tests");
+		arrayGroup->addChild(floatGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFloatFormats); fmtNdx++)
+		{
+			deUint32			format		= requiredFloatFormats[fmtNdx];
+			string				fmtName		= getFormatName(format);
+			vector<BufferSpec>	fboSpec;
+
+			for (int ndx = 0; ndx < numTargets; ndx++)
+				fboSpec.push_back(BufferSpec(format, width, height, samples));
+
+			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+			{
+				glu::Precision	prec		= precisions[precNdx];
+				string			precName	= glu::getPrecisionName(prec);
+
+				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_float").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT,		prec, 0, numTargets)).toVec()));
+				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2,	prec, 0, numTargets)).toVec()));
+				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3,	prec, 0, numTargets)).toVec()));
+				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4,	prec, 0, numTargets)).toVec()));
+			}
+		}
+
+		// .fixed
+		tcu::TestCaseGroup* fixedGroup = new tcu::TestCaseGroup(m_testCtx, "fixed", "Fixed-point output tests");
+		arrayGroup->addChild(fixedGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFixedFormats); fmtNdx++)
+		{
+			deUint32			format		= requiredFixedFormats[fmtNdx];
+			string				fmtName		= getFormatName(format);
+			vector<BufferSpec>	fboSpec;
+
+			for (int ndx = 0; ndx < numTargets; ndx++)
+				fboSpec.push_back(BufferSpec(format, width, height, samples));
+
+			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+			{
+				glu::Precision	prec		= precisions[precNdx];
+				string			precName	= glu::getPrecisionName(prec);
+
+				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_float").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT,		prec, 0, numTargets)).toVec()));
+				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2,	prec, 0, numTargets)).toVec()));
+				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3,	prec, 0, numTargets)).toVec()));
+				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4,	prec, 0, numTargets)).toVec()));
+			}
+		}
+
+		// .int
+		tcu::TestCaseGroup* intGroup = new tcu::TestCaseGroup(m_testCtx, "int", "Integer output tests");
+		arrayGroup->addChild(intGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredIntFormats); fmtNdx++)
+		{
+			deUint32			format		= requiredIntFormats[fmtNdx];
+			string				fmtName		= getFormatName(format);
+			vector<BufferSpec>	fboSpec;
+
+			for (int ndx = 0; ndx < numTargets; ndx++)
+				fboSpec.push_back(BufferSpec(format, width, height, samples));
+
+			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+			{
+				glu::Precision	prec		= precisions[precNdx];
+				string			precName	= glu::getPrecisionName(prec);
+
+				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_int").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT,		prec, 0, numTargets)).toVec()));
+				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC2,	prec, 0, numTargets)).toVec()));
+				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC3,	prec, 0, numTargets)).toVec()));
+				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC4,	prec, 0, numTargets)).toVec()));
+			}
+		}
+
+		// .uint
+		tcu::TestCaseGroup* uintGroup = new tcu::TestCaseGroup(m_testCtx, "uint", "Usigned integer output tests");
+		arrayGroup->addChild(uintGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredUintFormats); fmtNdx++)
+		{
+			deUint32			format		= requiredUintFormats[fmtNdx];
+			string				fmtName		= getFormatName(format);
+			vector<BufferSpec>	fboSpec;
+
+			for (int ndx = 0; ndx < numTargets; ndx++)
+				fboSpec.push_back(BufferSpec(format, width, height, samples));
+
+			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+			{
+				glu::Precision	prec		= precisions[precNdx];
+				string			precName	= glu::getPrecisionName(prec);
+
+				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uint").c_str(),		"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT,			prec, 0, numTargets)).toVec()));
+				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC2,	prec, 0, numTargets)).toVec()));
+				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC3,	prec, 0, numTargets)).toVec()));
+				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC4,	prec, 0, numTargets)).toVec()));
+			}
+		}
+	}
+
+	// .random
+	{
+		tcu::TestCaseGroup* randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Random fragment output cases");
+		addChild(randomGroup);
+
+		for (deUint32 seed = 0; seed < 100; seed++)
+			randomGroup->addChild(createRandomCase(m_context, 2, 4, seed));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFragmentOutputTests.hpp b/modules/gles3/functional/es3fFragmentOutputTests.hpp
new file mode 100644
index 0000000..79284c7
--- /dev/null
+++ b/modules/gles3/functional/es3fFragmentOutputTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFRAGMENTOUTPUTTESTS_HPP
+#define _ES3FFRAGMENTOUTPUTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Fragment shader output tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FragmentOutputTests : public TestCaseGroup
+{
+public:
+								FragmentOutputTests			(Context& context);
+								~FragmentOutputTests		(void);
+
+	void						init						(void);
+
+private:
+								FragmentOutputTests			(const FragmentOutputTests& other);
+	FragmentOutputTests&		operator=					(const FragmentOutputTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFRAGMENTOUTPUTTESTS_HPP
diff --git a/modules/gles3/functional/es3fFramebufferBlitTests.cpp b/modules/gles3/functional/es3fFramebufferBlitTests.cpp
new file mode 100644
index 0000000..efcdef3
--- /dev/null
+++ b/modules/gles3/functional/es3fFramebufferBlitTests.cpp
@@ -0,0 +1,1119 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO stencilbuffer tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFramebufferBlitTests.hpp"
+#include "es3fFboTestCase.hpp"
+#include "es3fFboTestUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "sglrContextUtil.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::UVec4;
+using namespace FboTestUtil;
+
+class BlitRectCase : public FboTestCase
+{
+public:
+	BlitRectCase (Context& context, const char* name, const char* desc, deUint32 filter, const IVec2& srcSize, const IVec4& srcRect, const IVec2& dstSize, const IVec4& dstRect, int cellSize = 8)
+		: FboTestCase		(context, name, desc)
+		, m_filter			(filter)
+		, m_srcSize			(srcSize)
+		, m_srcRect			(srcRect)
+		, m_dstSize			(dstSize)
+		, m_dstRect			(dstRect)
+		, m_cellSize		(cellSize)
+		, m_gridCellColorA	(0.2f, 0.7f, 0.1f, 1.0f)
+		, m_gridCellColorB	(0.7f, 0.1f, 0.5f, 0.8f)
+	{
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		const deUint32			colorFormat		= GL_RGBA8;
+
+		GradientShader			gradShader		(glu::TYPE_FLOAT_VEC4);
+		Texture2DShader			texShader		(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
+		deUint32				gradShaderID	= getCurrentContext()->createProgram(&gradShader);
+		deUint32				texShaderID		= getCurrentContext()->createProgram(&texShader);
+
+		deUint32				srcFbo, dstFbo;
+		deUint32				srcRbo, dstRbo;
+
+		// Setup shaders
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, Vec4(0.0f), Vec4(1.0f));
+		texShader.setUniforms(*getCurrentContext(), texShaderID);
+
+		// Create framebuffers.
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			deUint32&		fbo		= ndx ? dstFbo : srcFbo;
+			deUint32&		rbo		= ndx ? dstRbo : srcRbo;
+			const IVec2&	size	= ndx ? m_dstSize : m_srcSize;
+
+			glGenFramebuffers(1, &fbo);
+			glGenRenderbuffers(1, &rbo);
+
+			glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+			glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, size.x(), size.y());
+
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+			checkError();
+			checkFramebufferStatus(GL_FRAMEBUFFER);
+		}
+
+		// Fill destination with gradient.
+		glBindFramebuffer(GL_FRAMEBUFFER, dstFbo);
+		glViewport(0, 0, m_dstSize.x(), m_dstSize.y());
+
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		// Fill source with grid pattern.
+		{
+			const deUint32		format		= GL_RGBA;
+			const deUint32		dataType	= GL_UNSIGNED_BYTE;
+			const int			texW		= m_srcSize.x();
+			const int			texH		= m_srcSize.y();
+			deUint32			gridTex		= 0;
+			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);
+
+			tcu::fillWithGrid(data.getAccess(), m_cellSize, m_gridCellColorA, m_gridCellColorB);
+
+			glGenTextures(1, &gridTex);
+			glBindTexture(GL_TEXTURE_2D, gridTex);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());
+
+			glBindFramebuffer(GL_FRAMEBUFFER, srcFbo);
+			glViewport(0, 0, m_srcSize.x(), m_srcSize.y());
+			sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+
+		// Perform copy.
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo);
+		glBlitFramebuffer(m_srcRect.x(), m_srcRect.y(), m_srcRect.z(), m_srcRect.w(), m_dstRect.x(), m_dstRect.y(), m_dstRect.z(), m_dstRect.w(), GL_COLOR_BUFFER_BIT, m_filter);
+
+		// Read back results.
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo);
+		readPixels(dst, 0, 0, m_dstSize.x(), m_dstSize.y(), glu::mapGLInternalFormat(colorFormat), Vec4(1.0f), Vec4(0.0f));
+	}
+
+	virtual bool compare (const tcu::Surface& reference, const tcu::Surface& result)
+	{
+		// Use pixel-threshold compare for rect cases since 1px off will mean failure.
+		tcu::RGBA threshold = TestCase::m_context.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(7,7,7,7);
+		return tcu::pixelThresholdCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference, result, threshold, tcu::COMPARE_LOG_RESULT);
+	}
+
+protected:
+	const deUint32	m_filter;
+	const IVec2		m_srcSize;
+	const IVec4		m_srcRect;
+	const IVec2		m_dstSize;
+	const IVec4		m_dstRect;
+	const int		m_cellSize;
+	const Vec4		m_gridCellColorA;
+	const Vec4		m_gridCellColorB;
+};
+
+class BlitNearestFilterConsistencyCase : public BlitRectCase
+{
+public:
+			BlitNearestFilterConsistencyCase	(Context& context, const char* name, const char* desc, const IVec2& srcSize, const IVec4& srcRect, const IVec2& dstSize, const IVec4& dstRect);
+
+	bool	compare								(const tcu::Surface& reference, const tcu::Surface& result);
+};
+
+BlitNearestFilterConsistencyCase::BlitNearestFilterConsistencyCase (Context& context, const char* name, const char* desc, const IVec2& srcSize, const IVec4& srcRect, const IVec2& dstSize, const IVec4& dstRect)
+	: BlitRectCase(context, name, desc, GL_NEAREST, srcSize, srcRect, dstSize, dstRect, 1)
+{
+}
+
+bool BlitNearestFilterConsistencyCase::compare (const tcu::Surface& reference, const tcu::Surface& result)
+{
+	DE_ASSERT(reference.getWidth() == result.getWidth());
+	DE_ASSERT(reference.getHeight() == result.getHeight());
+	DE_UNREF(reference);
+
+	// Image origin must be visible (for baseColor)
+	DE_ASSERT(de::min(m_dstRect.x(), m_dstRect.z()) >= 0);
+	DE_ASSERT(de::min(m_dstRect.y(), m_dstRect.w()) >= 0);
+
+	const tcu::RGBA		cellColorA		(m_gridCellColorA);
+	const tcu::RGBA		cellColorB		(m_gridCellColorB);
+	const tcu::RGBA		threshold		= TestCase::m_context.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(7,7,7,7);
+	const tcu::IVec4	destinationArea	= tcu::IVec4(de::clamp(de::min(m_dstRect.x(), m_dstRect.z()), 0, result.getWidth()),
+													 de::clamp(de::min(m_dstRect.y(), m_dstRect.w()), 0, result.getHeight()),
+													 de::clamp(de::max(m_dstRect.x(), m_dstRect.z()), 0, result.getWidth()),
+													 de::clamp(de::max(m_dstRect.y(), m_dstRect.w()), 0, result.getHeight()));
+	const tcu::RGBA		baseColor		= result.getPixel(destinationArea.x(), destinationArea.y());
+	const bool			signConfig		= tcu::compareThreshold(baseColor, cellColorA, threshold);
+
+	bool				error			= false;
+	tcu::Surface		errorMask		(result.getWidth(), result.getHeight());
+	std::vector<bool>	horisontalSign	(destinationArea.z() - destinationArea.x());
+	std::vector<bool>	verticalSign	(destinationArea.w() - destinationArea.y());
+
+	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+	// Checking only area in our destination rect
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying consistency of NEAREST filtering. Verifying rect " << m_dstRect << ".\n"
+		<< "Rounding direction of the NEAREST filter at the horisontal texel edge (x = n + 0.5) should not depend on the y-coordinate.\n"
+		<< "Rounding direction of the NEAREST filter at the vertical texel edge (y = n + 0.5) should not depend on the x-coordinate.\n"
+		<< "Blitting a grid (with uniform sized cells) should result in a grid (with non-uniform sized cells)."
+		<< tcu::TestLog::EndMessage;
+
+	// Verify that destination only contains valid colors
+
+	for (int dy = 0; dy < destinationArea.w() - destinationArea.y(); ++dy)
+	for (int dx = 0; dx < destinationArea.z() - destinationArea.x(); ++dx)
+	{
+		const tcu::RGBA	color	= result.getPixel(destinationArea.x() + dx, destinationArea.y() + dy);
+		const bool isValidColor = tcu::compareThreshold(color, cellColorA, threshold) || tcu::compareThreshold(color, cellColorB, threshold);
+
+		if (!isValidColor)
+		{
+			errorMask.setPixel(destinationArea.x() + dx, destinationArea.y() + dy, tcu::RGBA::red);
+			error = true;
+		}
+	}
+	if (error)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Image verification failed, destination rect contains unexpected values. "
+			<< "Expected either " << cellColorA << " or " << cellColorB << "."
+			<< tcu::TestLog::EndMessage
+			<< tcu::TestLog::ImageSet("Result", "Image verification result")
+			<< tcu::TestLog::Image("Result",	"Result",		result)
+			<< tcu::TestLog::Image("ErrorMask",	"Error mask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+		return false;
+	}
+
+	// Detect result edges by reading the first row and first column of the blitted area.
+	// Blitting a grid should result in a grid-like image. ("sign changes" should be consistent)
+
+	for (int dx = 0; dx < destinationArea.z() - destinationArea.x(); ++dx)
+	{
+		const tcu::RGBA color = result.getPixel(destinationArea.x() + dx, destinationArea.y());
+
+		if (tcu::compareThreshold(color, cellColorA, threshold))
+			horisontalSign[dx] = true;
+		else if (tcu::compareThreshold(color, cellColorB, threshold))
+			horisontalSign[dx] = false;
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+	for (int dy = 0; dy < destinationArea.w() - destinationArea.y(); ++dy)
+	{
+		const tcu::RGBA color = result.getPixel(destinationArea.x(), destinationArea.y() + dy);
+
+		if (tcu::compareThreshold(color, cellColorA, threshold))
+			verticalSign[dy] = true;
+		else if (tcu::compareThreshold(color, cellColorB, threshold))
+			verticalSign[dy] = false;
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// Verify grid-like image
+
+	for (int dy = 0; dy < destinationArea.w() - destinationArea.y(); ++dy)
+	for (int dx = 0; dx < destinationArea.z() - destinationArea.x(); ++dx)
+	{
+		const tcu::RGBA	color		= result.getPixel(destinationArea.x() + dx, destinationArea.y() + dy);
+		const bool		resultSign	= tcu::compareThreshold(cellColorA, color, threshold);
+		const bool		correctSign	= (horisontalSign[dx] == verticalSign[dy]) == signConfig;
+
+		if (resultSign != correctSign)
+		{
+			errorMask.setPixel(destinationArea.x() + dx, destinationArea.y() + dy, tcu::RGBA::red);
+			error = true;
+		}
+	}
+
+	// Report result
+
+	if (error)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Image verification failed, nearest filter is not consistent."
+			<< tcu::TestLog::EndMessage
+			<< tcu::TestLog::ImageSet("Result", "Image verification result")
+			<< tcu::TestLog::Image("Result",	"Result",		result)
+			<< tcu::TestLog::Image("ErrorMask",	"Error mask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+	}
+	else
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Image verification passed."
+			<< tcu::TestLog::EndMessage
+			<< tcu::TestLog::ImageSet("Result", "Image verification result")
+			<< tcu::TestLog::Image("Result", "Result", result)
+			<< tcu::TestLog::EndImageSet;
+	}
+
+	return !error;
+}
+
+static tcu::BVec4 getChannelMask (tcu::TextureFormat::ChannelOrder order)
+{
+	switch (order)
+	{
+		case tcu::TextureFormat::R:		return tcu::BVec4(true,	false,	false,	false);
+		case tcu::TextureFormat::RG:	return tcu::BVec4(true,	true,	false,	false);
+		case tcu::TextureFormat::RGB:	return tcu::BVec4(true,	true,	true,	false);
+		case tcu::TextureFormat::RGBA:	return tcu::BVec4(true,	true,	true,	true);
+		case tcu::TextureFormat::sRGB:	return tcu::BVec4(true,	true,	true,	false);
+		case tcu::TextureFormat::sRGBA:	return tcu::BVec4(true,	true,	true,	true);
+		default:
+			DE_ASSERT(false);
+			return tcu::BVec4(false);
+	}
+}
+
+class BlitColorConversionCase : public FboTestCase
+{
+public:
+	BlitColorConversionCase (Context& context, const char* name, const char* desc, deUint32 srcFormat, deUint32 dstFormat, const IVec2& size)
+		: FboTestCase	(context, name, desc)
+		, m_srcFormat	(srcFormat)
+		, m_dstFormat	(dstFormat)
+		, m_size		(size)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_srcFormat);
+		checkFormatSupport(m_dstFormat);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		tcu::TextureFormat		srcFormat			= glu::mapGLInternalFormat(m_srcFormat);
+		tcu::TextureFormat		dstFormat			= glu::mapGLInternalFormat(m_dstFormat);
+		glu::DataType			srcOutputType		= getFragmentOutputType(srcFormat);
+		glu::DataType			dstOutputType		= getFragmentOutputType(dstFormat);
+
+		// Compute ranges \note Doesn't handle case where src or dest is not subset of the another!
+		tcu::TextureFormatInfo	srcFmtRangeInfo		= tcu::getTextureFormatInfo(srcFormat);
+		tcu::TextureFormatInfo	dstFmtRangeInfo		= tcu::getTextureFormatInfo(dstFormat);
+		tcu::BVec4				copyMask			= tcu::logicalAnd(getChannelMask(srcFormat.order), getChannelMask(dstFormat.order));
+		tcu::BVec4				srcIsGreater		= tcu::greaterThan(srcFmtRangeInfo.valueMax-srcFmtRangeInfo.valueMin, dstFmtRangeInfo.valueMax-dstFmtRangeInfo.valueMin);
+		tcu::TextureFormatInfo	srcRangeInfo		(tcu::select(dstFmtRangeInfo.valueMin,		srcFmtRangeInfo.valueMin,		tcu::logicalAnd(copyMask, srcIsGreater)),
+													 tcu::select(dstFmtRangeInfo.valueMax,		srcFmtRangeInfo.valueMax,		tcu::logicalAnd(copyMask, srcIsGreater)),
+													 tcu::select(dstFmtRangeInfo.lookupScale,	srcFmtRangeInfo.lookupScale,	tcu::logicalAnd(copyMask, srcIsGreater)),
+													 tcu::select(dstFmtRangeInfo.lookupBias,	srcFmtRangeInfo.lookupBias,		tcu::logicalAnd(copyMask, srcIsGreater)));
+		tcu::TextureFormatInfo	dstRangeInfo		(tcu::select(dstFmtRangeInfo.valueMin,		srcFmtRangeInfo.valueMin,		tcu::logicalOr(tcu::logicalNot(copyMask), srcIsGreater)),
+													 tcu::select(dstFmtRangeInfo.valueMax,		srcFmtRangeInfo.valueMax,		tcu::logicalOr(tcu::logicalNot(copyMask), srcIsGreater)),
+													 tcu::select(dstFmtRangeInfo.lookupScale,	srcFmtRangeInfo.lookupScale,	tcu::logicalOr(tcu::logicalNot(copyMask), srcIsGreater)),
+													 tcu::select(dstFmtRangeInfo.lookupBias,	srcFmtRangeInfo.lookupBias,		tcu::logicalOr(tcu::logicalNot(copyMask), srcIsGreater)));
+
+		// Shaders.
+		GradientShader			gradientToSrcShader	(srcOutputType);
+		GradientShader			gradientToDstShader	(dstOutputType);
+
+		deUint32				gradShaderSrcID		= getCurrentContext()->createProgram(&gradientToSrcShader);
+		deUint32				gradShaderDstID		= getCurrentContext()->createProgram(&gradientToDstShader);
+
+		deUint32				srcFbo, dstFbo;
+		deUint32				srcRbo, dstRbo;
+
+		// Create framebuffers.
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			deUint32&	fbo		= ndx ? dstFbo : srcFbo;
+			deUint32&	rbo		= ndx ? dstRbo : srcRbo;
+			deUint32	format	= ndx ? m_dstFormat : m_srcFormat;
+
+			glGenFramebuffers(1, &fbo);
+			glGenRenderbuffers(1, &rbo);
+
+			glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+			glRenderbufferStorage(GL_RENDERBUFFER, format, m_size.x(), m_size.y());
+
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+			checkError();
+			checkFramebufferStatus(GL_FRAMEBUFFER);
+		}
+
+		glViewport(0, 0, m_size.x(), m_size.y());
+
+		// Render gradients.
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			glBindFramebuffer(GL_FRAMEBUFFER, ndx ? dstFbo : srcFbo);
+
+			if (ndx)
+			{
+				gradientToDstShader.setGradient(*getCurrentContext(), gradShaderDstID, dstRangeInfo.valueMax, dstRangeInfo.valueMin);
+				sglr::drawQuad(*getCurrentContext(), gradShaderDstID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+			}
+			else
+			{
+				gradientToSrcShader.setGradient(*getCurrentContext(), gradShaderSrcID, srcRangeInfo.valueMin, dstRangeInfo.valueMax);
+				sglr::drawQuad(*getCurrentContext(), gradShaderSrcID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+			}
+		}
+
+		// Execute copy.
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo);
+		glBlitFramebuffer(0, 0, m_size.x(), m_size.y(), 0, 0, m_size.x(), m_size.y(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
+		checkError();
+
+		// Read results.
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo);
+		readPixels(dst, 0, 0, m_size.x(), m_size.y(), dstFormat, dstRangeInfo.lookupScale, dstRangeInfo.lookupBias);
+	}
+
+	bool compare (const tcu::Surface& reference, const tcu::Surface& result)
+	{
+		const tcu::TextureFormat	srcFormat	= glu::mapGLInternalFormat(m_srcFormat);
+		const tcu::TextureFormat	dstFormat	= glu::mapGLInternalFormat(m_dstFormat);
+		const bool					srcIsSRGB	= (srcFormat.order == tcu::TextureFormat::sRGBA);
+		const bool					dstIsSRGB	= (dstFormat.order == tcu::TextureFormat::sRGBA);
+
+		tcu::RGBA					threshold;
+
+		if (dstIsSRGB)
+		{
+			threshold = getToSRGBConversionThreshold(srcFormat, dstFormat);
+		}
+		else
+		{
+			const tcu::RGBA	srcMaxDiff	= getFormatThreshold(srcFormat) * (srcIsSRGB ? 2 : 1);
+			const tcu::RGBA	dstMaxDiff	= getFormatThreshold(dstFormat);
+
+			threshold = tcu::max(srcMaxDiff, dstMaxDiff);
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "threshold = " << threshold << tcu::TestLog::EndMessage;
+		return tcu::pixelThresholdCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference, result, threshold, tcu::COMPARE_LOG_RESULT);
+	}
+
+private:
+	deUint32		m_srcFormat;
+	deUint32		m_dstFormat;
+	IVec2			m_size;
+};
+
+class BlitDepthStencilCase : public FboTestCase
+{
+public:
+	BlitDepthStencilCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 srcBuffers, const IVec2& srcSize, const IVec4& srcRect, deUint32 dstBuffers, const IVec2& dstSize, const IVec4& dstRect, deUint32 copyBuffers)
+		: FboTestCase	(context, name, desc)
+		, m_format		(format)
+		, m_srcBuffers	(srcBuffers)
+		, m_srcSize		(srcSize)
+		, m_srcRect		(srcRect)
+		, m_dstBuffers	(dstBuffers)
+		, m_dstSize		(dstSize)
+		, m_dstRect		(dstRect)
+		, m_copyBuffers	(copyBuffers)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		checkFormatSupport(m_format);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		const deUint32			colorFormat			= GL_RGBA8;
+
+		GradientShader			gradShader			(glu::TYPE_FLOAT_VEC4);
+		Texture2DShader			texShader			(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
+		FlatColorShader			flatShader			(glu::TYPE_FLOAT_VEC4);
+
+		deUint32				flatShaderID		= getCurrentContext()->createProgram(&flatShader);
+		deUint32				texShaderID			= getCurrentContext()->createProgram(&texShader);
+		deUint32				gradShaderID		= getCurrentContext()->createProgram(&gradShader);
+
+		deUint32				srcFbo				= 0;
+		deUint32				dstFbo				= 0;
+		deUint32				srcColorRbo			= 0;
+		deUint32				dstColorRbo			= 0;
+		deUint32				srcDepthStencilRbo	= 0;
+		deUint32				dstDepthStencilRbo	= 0;
+
+		// setup shaders
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, Vec4(0.0f), Vec4(1.0f));
+		texShader.setUniforms(*getCurrentContext(), texShaderID);
+
+		// Create framebuffers.
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			deUint32&		fbo				= ndx ? dstFbo : srcFbo;
+			deUint32&		colorRbo		= ndx ? dstColorRbo : srcColorRbo;
+			deUint32&		depthStencilRbo	= ndx ? dstDepthStencilRbo : srcDepthStencilRbo;
+			deUint32		bufs			= ndx ? m_dstBuffers : m_srcBuffers;
+			const IVec2&	size			= ndx ? m_dstSize : m_srcSize;
+
+			glGenFramebuffers(1, &fbo);
+			glGenRenderbuffers(1, &colorRbo);
+			glGenRenderbuffers(1, &depthStencilRbo);
+
+			glBindRenderbuffer(GL_RENDERBUFFER, colorRbo);
+			glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, size.x(), size.y());
+
+			glBindRenderbuffer(GL_RENDERBUFFER, depthStencilRbo);
+			glRenderbufferStorage(GL_RENDERBUFFER, m_format, size.x(), size.y());
+
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
+
+			if (bufs & GL_DEPTH_BUFFER_BIT)
+				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+			if (bufs & GL_STENCIL_BUFFER_BIT)
+				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
+
+			checkError();
+			checkFramebufferStatus(GL_FRAMEBUFFER);
+
+			// Clear depth to 1 and stencil to 0.
+			glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0);
+		}
+
+		// Fill source with gradient, depth = [-1..1], stencil = 7
+		glBindFramebuffer(GL_FRAMEBUFFER, srcFbo);
+		glViewport(0, 0, m_srcSize.x(), m_srcSize.y());
+		glEnable(GL_DEPTH_TEST);
+		glEnable(GL_STENCIL_TEST);
+		glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+		glStencilFunc(GL_ALWAYS, 7, 0xffu);
+
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));
+
+		// Fill destination with grid pattern, depth = 0 and stencil = 1
+		{
+			const deUint32		format		= GL_RGBA;
+			const deUint32		dataType	= GL_UNSIGNED_BYTE;
+			const int			texW		= m_srcSize.x();
+			const int			texH		= m_srcSize.y();
+			deUint32			gridTex		= 0;
+			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);
+
+			tcu::fillWithGrid(data.getAccess(), 8, Vec4(0.2f, 0.7f, 0.1f, 1.0f), Vec4(0.7f, 0.1f, 0.5f, 0.8f));
+
+			glGenTextures(1, &gridTex);
+			glBindTexture(GL_TEXTURE_2D, gridTex);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());
+
+			glBindFramebuffer(GL_FRAMEBUFFER, dstFbo);
+			glViewport(0, 0, m_dstSize.x(), m_dstSize.y());
+			glStencilFunc(GL_ALWAYS, 1, 0xffu);
+			sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+
+		// Perform copy.
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo);
+		glBlitFramebuffer(m_srcRect.x(), m_srcRect.y(), m_srcRect.z(), m_srcRect.w(), m_dstRect.x(), m_dstRect.y(), m_dstRect.z(), m_dstRect.w(), m_copyBuffers, GL_NEAREST);
+
+		// Render blue color where depth < 0, decrement on depth failure.
+		glBindFramebuffer(GL_FRAMEBUFFER, dstFbo);
+		glViewport(0, 0, m_dstSize.x(), m_dstSize.y());
+		glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
+		glStencilFunc(GL_ALWAYS, 0, 0xffu);
+
+		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		if (m_dstBuffers & GL_STENCIL_BUFFER_BIT)
+		{
+			// Render green color where stencil == 6.
+			glDisable(GL_DEPTH_TEST);
+			glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+			glStencilFunc(GL_EQUAL, 6, 0xffu);
+
+			flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+			sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+		}
+
+		readPixels(dst, 0, 0, m_dstSize.x(), m_dstSize.y(), glu::mapGLInternalFormat(colorFormat), Vec4(1.0f), Vec4(0.0f));
+	}
+
+private:
+	deUint32	m_format;
+	deUint32	m_srcBuffers;
+	IVec2		m_srcSize;
+	IVec4		m_srcRect;
+	deUint32	m_dstBuffers;
+	IVec2		m_dstSize;
+	IVec4		m_dstRect;
+	deUint32	m_copyBuffers;
+};
+
+class BlitDefaultFramebufferCase : public FboTestCase
+{
+public:
+	BlitDefaultFramebufferCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 filter)
+		: FboTestCase	(context, name, desc)
+		, m_format		(format)
+		, m_filter		(filter)
+	{
+	}
+
+protected:
+	void preCheck (void)
+	{
+		if (m_context.getRenderTarget().getNumSamples() > 0)
+			throw tcu::NotSupportedError("Not supported in MSAA config");
+
+		checkFormatSupport(m_format);
+	}
+
+	virtual void render (tcu::Surface& dst)
+	{
+		tcu::TextureFormat		colorFormat		= glu::mapGLInternalFormat(m_format);
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(colorFormat);
+		GradientShader			gradShader		(glu::TYPE_FLOAT_VEC4);
+		Texture2DShader			texShader		(DataTypes() << glu::getSampler2DType(colorFormat), glu::TYPE_FLOAT_VEC4);
+		deUint32				gradShaderID	= getCurrentContext()->createProgram(&gradShader);
+		deUint32				texShaderID		= getCurrentContext()->createProgram(&texShader);
+		deUint32				fbo				= 0;
+		deUint32				tex				= 0;
+		const int				texW			= 128;
+		const int				texH			= 128;
+
+		// Setup shaders
+		gradShader.setGradient(*getCurrentContext(), gradShaderID, Vec4(0.0f), Vec4(1.0f));
+		texShader.setUniforms(*getCurrentContext(), texShaderID);
+
+		// FBO
+		glGenFramebuffers(1, &fbo);
+		glGenTextures(1, &tex);
+
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	m_filter);
+		glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	m_filter);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_format, texW, texH, 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+		checkError();
+		checkFramebufferStatus(GL_FRAMEBUFFER);
+
+		// Render gradient to screen.
+		glBindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer());
+
+		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+
+		// Blit gradient from screen to fbo.
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+		glBlitFramebuffer(0, 0, getWidth(), getHeight(), 0, 0, texW, texH, GL_COLOR_BUFFER_BIT, m_filter);
+
+		// Fill left half of viewport with quad that uses texture.
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer());
+		glClearBufferfv(GL_COLOR, 0, Vec4(1.0f, 0.0f, 0.0f, 1.0f).getPtr());
+		sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f));
+
+		// Blit fbo to right half.
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
+		glBlitFramebuffer(0, 0, texW, texH, getWidth()/2, 0, getWidth(), getHeight(), GL_COLOR_BUFFER_BIT, m_filter);
+
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer());
+		readPixels(dst, 0, 0, getWidth(), getHeight());
+	}
+
+	bool compare (const tcu::Surface& reference, const tcu::Surface& result)
+	{
+		const tcu::RGBA threshold (tcu::max(getFormatThreshold(m_format), tcu::RGBA(12, 12, 12, 12)));
+
+		m_testCtx.getLog() << TestLog::Message << "Comparing images, threshold: " << threshold << TestLog::EndMessage;
+
+		return tcu::bilinearCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference.getAccess(), result.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
+	}
+
+protected:
+	const deUint32	m_format;
+	const deUint32	m_filter;
+};
+
+class DefaultFramebufferBlitCase : public BlitDefaultFramebufferCase
+{
+public:
+	enum BlitDirection
+	{
+		BLIT_DEFAULT_TO_TARGET,
+		BLIT_TO_DEFAULT_FROM_TARGET,
+
+		BLIT_LAST
+	};
+	enum BlitArea
+	{
+		AREA_SCALE,
+		AREA_OUT_OF_BOUNDS,
+
+		AREA_LAST
+	};
+
+	DefaultFramebufferBlitCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 filter, BlitDirection dir, BlitArea area)
+		: BlitDefaultFramebufferCase	(context, name, desc, format, filter)
+		, m_blitDir						(dir)
+		, m_blitArea					(area)
+		, m_srcRect						(-1, -1, -1, -1)
+		, m_dstRect						(-1, -1, -1, -1)
+		, m_interestingArea				(-1, -1, -1, -1)
+	{
+		DE_ASSERT(dir < BLIT_LAST);
+		DE_ASSERT(area < AREA_LAST);
+	}
+
+	void init (void)
+	{
+		// requirements
+		const int minViewportSize = 128;
+		if (m_context.getRenderTarget().getWidth() < minViewportSize || m_context.getRenderTarget().getHeight() < minViewportSize)
+			throw tcu::NotSupportedError("Viewport size " + de::toString(minViewportSize) + "x" + de::toString(minViewportSize) + " required");
+
+		// prevent viewport randoming
+		m_viewportWidth = m_context.getRenderTarget().getWidth();
+		m_viewportHeight = m_context.getRenderTarget().getHeight();
+
+		// set proper areas
+		if (m_blitArea == AREA_SCALE)
+		{
+			m_srcRect = IVec4( 10,  20,  65, 100);
+			m_dstRect = IVec4( 25,  30, 125,  94);
+			m_interestingArea = IVec4(0, 0, 128, 128);
+		}
+		else if (m_blitArea == AREA_OUT_OF_BOUNDS)
+		{
+			const tcu::IVec2 ubound = (m_blitDir == BLIT_DEFAULT_TO_TARGET) ? (tcu::IVec2(128, 128)) : (tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight()));
+
+			m_srcRect = IVec4(-10, -15, 100,  63);
+			m_dstRect = ubound.swizzle(0, 1, 0, 1) + IVec4(-75, -99, 8, 16);
+			m_interestingArea = IVec4(ubound.x() - 128, ubound.y() - 128, ubound.x(), ubound.y());
+		}
+		else
+			DE_ASSERT(false);
+	}
+
+	void render (tcu::Surface& dst)
+	{
+		const tcu::TextureFormat		colorFormat		= glu::mapGLInternalFormat(m_format);
+		const glu::TransferFormat		transferFmt		= glu::getTransferFormat(colorFormat);
+		const tcu::TextureChannelClass	targetClass		= (m_blitDir == BLIT_DEFAULT_TO_TARGET) ? (tcu::getTextureChannelClass(colorFormat.type)) : (tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT);
+		deUint32						fbo				= 0;
+		deUint32						fboTex			= 0;
+		const int						fboTexW			= 128;
+		const int						fboTexH			= 128;
+		const int						sourceWidth		= (m_blitDir == BLIT_DEFAULT_TO_TARGET) ? (getWidth()) : (fboTexW);
+		const int						sourceHeight	= (m_blitDir == BLIT_DEFAULT_TO_TARGET) ? (getHeight()) : (fboTexH);
+		const int						gridRenderWidth	= de::min(256, sourceWidth);
+		const int						gridRenderHeight= de::min(256, sourceHeight);
+
+		int								targetFbo		= -1;
+		int								sourceFbo		= -1;
+
+		// FBO
+		glGenFramebuffers(1, &fbo);
+		glGenTextures(1, &fboTex);
+
+		glBindTexture(GL_TEXTURE_2D, fboTex);
+		glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	m_filter);
+		glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	m_filter);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_format, fboTexW, fboTexH, 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTex, 0);
+		checkError();
+		checkFramebufferStatus(GL_FRAMEBUFFER);
+
+		targetFbo = (m_blitDir == BLIT_DEFAULT_TO_TARGET) ? (fbo) : (m_context.getRenderContext().getDefaultFramebuffer());
+		sourceFbo = (m_blitDir == BLIT_DEFAULT_TO_TARGET) ? (m_context.getRenderContext().getDefaultFramebuffer()) : (fbo);
+
+		// Render grid to source framebuffer
+		{
+			Texture2DShader		texShader		(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
+			const deUint32		texShaderID		= getCurrentContext()->createProgram(&texShader);
+			const deUint32		internalFormat	= GL_RGBA8;
+			const deUint32		format			= GL_RGBA;
+			const deUint32		dataType		= GL_UNSIGNED_BYTE;
+			const int			gridTexW		= 128;
+			const int			gridTexH		= 128;
+			deUint32			gridTex			= 0;
+			tcu::TextureLevel	data			(glu::mapGLTransferFormat(format, dataType), gridTexW, gridTexH, 1);
+
+			tcu::fillWithGrid(data.getAccess(), 9, tcu::Vec4(0.9f, 0.5f, 0.1f, 0.9f), tcu::Vec4(0.2f, 0.8f, 0.2f, 0.7f));
+
+			glGenTextures(1, &gridTex);
+			glBindTexture(GL_TEXTURE_2D, gridTex);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+			glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, gridTexW, gridTexH, 0, format, dataType, data.getAccess().getDataPtr());
+
+			glBindFramebuffer(GL_FRAMEBUFFER, sourceFbo);
+			glViewport(0, 0, gridRenderWidth, gridRenderHeight);
+			glClearBufferfv(GL_COLOR, 0, Vec4(1.0f, 0.0f, 0.0f, 1.0f).getPtr());
+
+			texShader.setUniforms(*getCurrentContext(), texShaderID);
+			sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+			glUseProgram(0);
+		}
+
+		// Blit source framebuffer to destination
+
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFbo);
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetFbo);
+		checkError();
+
+		if (targetClass == tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT || targetClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT || targetClass == tcu::TEXTURECHANNELCLASS_FLOATING_POINT)
+			glClearBufferfv(GL_COLOR, 0, Vec4(1.0f, 1.0f, 0.0f, 1.0f).getPtr());
+		else if (targetClass == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER)
+			glClearBufferiv(GL_COLOR, 0, IVec4(0, 0, 0, 0).getPtr());
+		else if (targetClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER)
+			glClearBufferuiv(GL_COLOR, 0, UVec4(0, 0, 0, 0).getPtr());
+		else
+			DE_ASSERT(false);
+
+		glBlitFramebuffer(m_srcRect.x(), m_srcRect.y(), m_srcRect.z(), m_srcRect.w(), m_dstRect.x(), m_dstRect.y(), m_dstRect.z(), m_dstRect.w(), GL_COLOR_BUFFER_BIT, m_filter);
+		checkError();
+
+		// Read target
+
+		glBindFramebuffer(GL_FRAMEBUFFER, targetFbo);
+
+		if (m_blitDir == BLIT_TO_DEFAULT_FROM_TARGET)
+			readPixels(dst, m_interestingArea.x(), m_interestingArea.y(), m_interestingArea.z() - m_interestingArea.x(), m_interestingArea.w() - m_interestingArea.y());
+		else
+			readPixels(dst, m_interestingArea.x(), m_interestingArea.y(), m_interestingArea.z() - m_interestingArea.x(), m_interestingArea.w() - m_interestingArea.y(), colorFormat, tcu::Vec4(1.0f), tcu::Vec4(0.0f));
+
+		checkError();
+	}
+
+private:
+	const BlitDirection	m_blitDir;
+	const BlitArea		m_blitArea;
+	tcu::IVec4			m_srcRect;
+	tcu::IVec4			m_dstRect;
+	tcu::IVec4			m_interestingArea;
+};
+
+FramebufferBlitTests::FramebufferBlitTests (Context& context)
+	: TestCaseGroup(context, "blit", "Framebuffer blit tests")
+{
+}
+
+FramebufferBlitTests::~FramebufferBlitTests (void)
+{
+}
+
+void FramebufferBlitTests::init (void)
+{
+	static const deUint32 colorFormats[] =
+	{
+		// RGBA formats
+		GL_RGBA32I,
+		GL_RGBA32UI,
+		GL_RGBA16I,
+		GL_RGBA16UI,
+		GL_RGBA8,
+		GL_RGBA8I,
+		GL_RGBA8UI,
+		GL_SRGB8_ALPHA8,
+		GL_RGB10_A2,
+		GL_RGB10_A2UI,
+		GL_RGBA4,
+		GL_RGB5_A1,
+
+		// RGB formats
+		GL_RGB8,
+		GL_RGB565,
+
+		// RG formats
+		GL_RG32I,
+		GL_RG32UI,
+		GL_RG16I,
+		GL_RG16UI,
+		GL_RG8,
+		GL_RG8I,
+		GL_RG8UI,
+
+		// R formats
+		GL_R32I,
+		GL_R32UI,
+		GL_R16I,
+		GL_R16UI,
+		GL_R8,
+		GL_R8I,
+		GL_R8UI,
+
+		// GL_EXT_color_buffer_float
+		GL_RGBA32F,
+		GL_RGBA16F,
+		GL_R11F_G11F_B10F,
+		GL_RG32F,
+		GL_RG16F,
+		GL_R32F,
+		GL_R16F
+	};
+
+	static const deUint32 depthStencilFormats[] =
+	{
+		GL_DEPTH_COMPONENT32F,
+		GL_DEPTH_COMPONENT24,
+		GL_DEPTH_COMPONENT16,
+		GL_DEPTH32F_STENCIL8,
+		GL_DEPTH24_STENCIL8,
+		GL_STENCIL_INDEX8
+	};
+
+	// .rect
+	{
+		static const struct
+		{
+			const char*	name;
+			IVec4		srcRect;
+			IVec4		dstRect;
+		} copyRects[] =
+		{
+			{ "basic",						IVec4( 10,  20,  65, 100),		IVec4( 45,   5, 100,  85) },
+			{ "scale",						IVec4( 10,  20,  65, 100),		IVec4( 25,  30, 125,  94) },
+			{ "out_of_bounds",				IVec4(-10, -15, 100,  63),		IVec4( 50,  30, 136, 144) },
+		};
+
+		static const struct
+		{
+			const char*	name;
+			IVec4		srcRect;
+			IVec4		dstRect;
+		} filterConsistencyRects[] =
+		{
+			{ "mag",						IVec4( 20,  10,  74, 88),		IVec4( 10,  10,  91, 101) },
+			{ "min",						IVec4( 10,  20,  78, 100),		IVec4( 20,  20,  71,  80) },
+			{ "out_of_bounds_mag",			IVec4( 21,  10,  73, 82),		IVec4( 11,  43, 141, 151) },
+			{ "out_of_bounds_min",			IVec4( 11,  21,  77, 97),		IVec4( 80,  82, 135, 139) },
+		};
+
+		static const struct
+		{
+			const char* name;
+			IVec4		srcSwizzle;
+			IVec4		dstSwizzle;
+		} swizzles[] =
+		{
+			{ DE_NULL,				IVec4(0,1,2,3),	IVec4(0,1,2,3) },
+			{ "reverse_src_x",		IVec4(2,1,0,3), IVec4(0,1,2,3) },
+			{ "reverse_src_y",		IVec4(0,3,2,1), IVec4(0,1,2,3) },
+			{ "reverse_dst_x",		IVec4(0,1,2,3), IVec4(2,1,0,3) },
+			{ "reverse_dst_y",		IVec4(0,1,2,3), IVec4(0,3,2,1) },
+			{ "reverse_src_dst_x",	IVec4(2,1,0,3), IVec4(2,1,0,3) },
+			{ "reverse_src_dst_y",	IVec4(0,3,2,1), IVec4(0,3,2,1) }
+		};
+
+		const IVec2 srcSize(127, 119);
+		const IVec2 dstSize(132, 128);
+
+		// Blit rectangle tests.
+		tcu::TestCaseGroup* rectGroup = new tcu::TestCaseGroup(m_testCtx, "rect", "Blit rectangle tests");
+		addChild(rectGroup);
+		for (int rectNdx = 0; rectNdx < DE_LENGTH_OF_ARRAY(copyRects); rectNdx++)
+		{
+			for (int swzNdx = 0; swzNdx < DE_LENGTH_OF_ARRAY(swizzles); swzNdx++)
+			{
+				string		name	= string(copyRects[rectNdx].name) + (swizzles[swzNdx].name ? (string("_") + swizzles[swzNdx].name) : string());
+				IVec4		srcSwz	= swizzles[swzNdx].srcSwizzle;
+				IVec4		dstSwz	= swizzles[swzNdx].dstSwizzle;
+				IVec4		srcRect	= copyRects[rectNdx].srcRect.swizzle(srcSwz[0], srcSwz[1], srcSwz[2], srcSwz[3]);
+				IVec4		dstRect	= copyRects[rectNdx].dstRect.swizzle(dstSwz[0], dstSwz[1], dstSwz[2], dstSwz[3]);
+
+				rectGroup->addChild(new BlitRectCase(m_context, (name + "_nearest").c_str(),	"", GL_NEAREST,	srcSize, srcRect, dstSize, dstRect));
+				rectGroup->addChild(new BlitRectCase(m_context, (name + "_linear").c_str(),		"", GL_LINEAR,	srcSize, srcRect, dstSize, dstRect));
+			}
+		}
+
+		// Nearest filter tests
+		for (int rectNdx = 0; rectNdx < DE_LENGTH_OF_ARRAY(filterConsistencyRects); rectNdx++)
+		{
+			for (int swzNdx = 0; swzNdx < DE_LENGTH_OF_ARRAY(swizzles); swzNdx++)
+			{
+				string		name	= string("nearest_consistency_") + filterConsistencyRects[rectNdx].name + (swizzles[swzNdx].name ? (string("_") + swizzles[swzNdx].name) : string());
+				IVec4		srcSwz	= swizzles[swzNdx].srcSwizzle;
+				IVec4		dstSwz	= swizzles[swzNdx].dstSwizzle;
+				IVec4		srcRect	= filterConsistencyRects[rectNdx].srcRect.swizzle(srcSwz[0], srcSwz[1], srcSwz[2], srcSwz[3]);
+				IVec4		dstRect	= filterConsistencyRects[rectNdx].dstRect.swizzle(dstSwz[0], dstSwz[1], dstSwz[2], dstSwz[3]);
+
+				rectGroup->addChild(new BlitNearestFilterConsistencyCase(m_context, name.c_str(), "Test consistency of the nearest filter", srcSize, srcRect, dstSize, dstRect));
+			}
+		}
+	}
+
+	// .conversion
+	{
+		tcu::TestCaseGroup* conversionGroup = new tcu::TestCaseGroup(m_testCtx, "conversion", "Color conversion tests");
+		addChild(conversionGroup);
+
+		for (int srcFmtNdx = 0; srcFmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); srcFmtNdx++)
+		{
+			for (int dstFmtNdx = 0; dstFmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); dstFmtNdx++)
+			{
+				deUint32					srcFormat	= colorFormats[srcFmtNdx];
+				tcu::TextureFormat			srcTexFmt	= glu::mapGLInternalFormat(srcFormat);
+				tcu::TextureChannelClass	srcType		= tcu::getTextureChannelClass(srcTexFmt.type);
+				deUint32					dstFormat	= colorFormats[dstFmtNdx];
+				tcu::TextureFormat			dstTexFmt	= glu::mapGLInternalFormat(dstFormat);
+				tcu::TextureChannelClass	dstType		= tcu::getTextureChannelClass(dstTexFmt.type);
+
+				if (((srcType == tcu::TEXTURECHANNELCLASS_FLOATING_POINT || srcType == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT) !=
+					 (dstType == tcu::TEXTURECHANNELCLASS_FLOATING_POINT || dstType == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT)) ||
+					((srcType == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER) != (dstType == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER)) ||
+					((srcType == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER) != (dstType == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER)))
+					continue; // Conversion not supported.
+
+				string						name		= string(getFormatName(srcFormat)) + "_to_" + getFormatName(dstFormat);
+
+				conversionGroup->addChild(new BlitColorConversionCase(m_context, name.c_str(), "", srcFormat, dstFormat, IVec2(127, 113)));
+			}
+		}
+	}
+
+	// .depth_stencil
+	{
+		tcu::TestCaseGroup* depthStencilGroup = new tcu::TestCaseGroup(m_testCtx, "depth_stencil", "Depth and stencil blits");
+		addChild(depthStencilGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); fmtNdx++)
+		{
+			deUint32			format		= depthStencilFormats[fmtNdx];
+			tcu::TextureFormat	texFmt		= glu::mapGLInternalFormat(format);
+			string				fmtName		= getFormatName(format);
+			bool				depth		= texFmt.order == tcu::TextureFormat::D || texFmt.order == tcu::TextureFormat::DS;
+			bool				stencil		= texFmt.order == tcu::TextureFormat::S || texFmt.order == tcu::TextureFormat::DS;
+			deUint32			buffers		= (depth ? GL_DEPTH_BUFFER_BIT : 0) | (stencil ? GL_STENCIL_BUFFER_BIT : 0);
+
+			depthStencilGroup->addChild(new BlitDepthStencilCase(m_context, (fmtName + "_basic").c_str(), "", format, buffers, IVec2(128, 128), IVec4(0, 0, 128, 128), buffers, IVec2(128, 128), IVec4(0, 0, 128, 128), buffers));
+			depthStencilGroup->addChild(new BlitDepthStencilCase(m_context, (fmtName + "_scale").c_str(), "", format, buffers, IVec2(127, 119), IVec4(10, 30, 100, 70), buffers, IVec2(111, 130), IVec4(20, 5, 80, 130), buffers));
+
+			if (depth && stencil)
+			{
+				depthStencilGroup->addChild(new BlitDepthStencilCase(m_context, (fmtName + "_depth_only").c_str(),		"", format, buffers, IVec2(128, 128), IVec4(0, 0, 128, 128), buffers, IVec2(128, 128), IVec4(0, 0, 128, 128), GL_DEPTH_BUFFER_BIT));
+				depthStencilGroup->addChild(new BlitDepthStencilCase(m_context, (fmtName + "_stencil_only").c_str(),	"", format, buffers, IVec2(128, 128), IVec4(0, 0, 128, 128), buffers, IVec2(128, 128), IVec4(0, 0, 128, 128), GL_STENCIL_BUFFER_BIT));
+			}
+		}
+	}
+
+	// .default_framebuffer
+	{
+		static const struct
+		{
+			const char*								name;
+			DefaultFramebufferBlitCase::BlitArea	area;
+		} areas[] =
+		{
+			{ "scale",						DefaultFramebufferBlitCase::AREA_SCALE			},
+			{ "out_of_bounds",				DefaultFramebufferBlitCase::AREA_OUT_OF_BOUNDS	},
+		};
+
+		tcu::TestCaseGroup* defaultFbGroup = new tcu::TestCaseGroup(m_testCtx, "default_framebuffer", "Blits with default framebuffer");
+		addChild(defaultFbGroup);
+
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
+		{
+			const deUint32					format		= colorFormats[fmtNdx];
+			const tcu::TextureFormat		texFmt		= glu::mapGLInternalFormat(format);
+			const tcu::TextureChannelClass	fmtClass	= tcu::getTextureChannelClass(texFmt.type);
+			const deUint32					filter		= glu::isGLInternalColorFormatFilterable(format) ? GL_LINEAR : GL_NEAREST;
+			const bool						filterable	= glu::isGLInternalColorFormatFilterable(format);
+
+			if (fmtClass != tcu::TEXTURECHANNELCLASS_FLOATING_POINT &&
+				fmtClass != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT &&
+				fmtClass != tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT)
+				continue; // Conversion not supported.
+
+			defaultFbGroup->addChild(new BlitDefaultFramebufferCase(m_context, getFormatName(format), "", format, filter));
+
+			for (int areaNdx = 0; areaNdx < DE_LENGTH_OF_ARRAY(areas); areaNdx++)
+			{
+				const string	name				= string(areas[areaNdx].name);
+				const bool		addLinear			= filterable;
+				const bool		addNearest			= !addLinear || (areas[areaNdx].area != DefaultFramebufferBlitCase::AREA_OUT_OF_BOUNDS); // No need to check out-of-bounds with different filtering
+
+				if (addNearest)
+				{
+					defaultFbGroup->addChild(new DefaultFramebufferBlitCase(m_context, (std::string(getFormatName(format)) + "_nearest_" + name + "_blit_from_default").c_str(), "", format, GL_NEAREST, DefaultFramebufferBlitCase::BLIT_DEFAULT_TO_TARGET, areas[areaNdx].area));
+					defaultFbGroup->addChild(new DefaultFramebufferBlitCase(m_context, (std::string(getFormatName(format)) + "_nearest_" + name + "_blit_to_default").c_str(), "", format, GL_NEAREST, DefaultFramebufferBlitCase::BLIT_TO_DEFAULT_FROM_TARGET, areas[areaNdx].area));
+				}
+
+				if (addLinear)
+				{
+					defaultFbGroup->addChild(new DefaultFramebufferBlitCase(m_context, (std::string(getFormatName(format)) + "_linear_" + name + "_blit_from_default").c_str(), "", format, GL_LINEAR, DefaultFramebufferBlitCase::BLIT_DEFAULT_TO_TARGET, areas[areaNdx].area));
+					defaultFbGroup->addChild(new DefaultFramebufferBlitCase(m_context, (std::string(getFormatName(format)) + "_linear_" + name + "_blit_to_default").c_str(), "", format, GL_LINEAR, DefaultFramebufferBlitCase::BLIT_TO_DEFAULT_FROM_TARGET, areas[areaNdx].area));
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFramebufferBlitTests.hpp b/modules/gles3/functional/es3fFramebufferBlitTests.hpp
new file mode 100644
index 0000000..b4122f5
--- /dev/null
+++ b/modules/gles3/functional/es3fFramebufferBlitTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFRAMEBUFFERBLITTESTS_HPP
+#define _ES3FFRAMEBUFFERBLITTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer blit tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FramebufferBlitTests : public TestCaseGroup
+{
+public:
+								FramebufferBlitTests	(Context& context);
+								~FramebufferBlitTests	(void);
+
+	void						init					(void);
+
+private:
+								FramebufferBlitTests	(const FramebufferBlitTests& other);
+	FramebufferBlitTests&		operator=				(const FramebufferBlitTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFRAMEBUFFERBLITTESTS_HPP
diff --git a/modules/gles3/functional/es3fFunctionalTests.cpp b/modules/gles3/functional/es3fFunctionalTests.cpp
new file mode 100644
index 0000000..c7ed4d6
--- /dev/null
+++ b/modules/gles3/functional/es3fFunctionalTests.cpp
@@ -0,0 +1,411 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Functional Test Group.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fFunctionalTests.hpp"
+
+#include "es3fColorClearTest.hpp"
+#include "es3fDepthTests.hpp"
+#include "es3fPrerequisiteTests.hpp"
+#include "es3fStencilTests.hpp"
+#include "es3fDepthStencilTests.hpp"
+#include "es3fVertexArrayTest.hpp"
+#include "es3fUniformBlockTests.hpp"
+#include "es3fUniformApiTests.hpp"
+#include "es3fFragmentOutputTests.hpp"
+#include "es3fOcclusionQueryTests.hpp"
+#include "es3fDepthStencilClearTests.hpp"
+#include "es3fSamplerObjectTests.hpp"
+#include "es3fAttribLocationTests.hpp"
+#include "es3fPixelBufferObjectTests.hpp"
+#include "es3fRasterizationTests.hpp"
+#include "es3fRasterizerDiscardTests.hpp"
+#include "es3fTransformFeedbackTests.hpp"
+#include "es3fVertexArrayObjectTests.hpp"
+#include "es3fPrimitiveRestartTests.hpp"
+#include "es3fInstancedRenderingTests.hpp"
+#include "es3fSyncTests.hpp"
+#include "es3fBlendTests.hpp"
+#include "es3fRandomFragmentOpTests.hpp"
+#include "es3fMultisampleTests.hpp"
+#include "es3fImplementationLimitTests.hpp"
+#include "es3fDitheringTests.hpp"
+#include "es3fClippingTests.hpp"
+#include "es3fPolygonOffsetTests.hpp"
+#include "es3fDrawTests.hpp"
+#include "es3fFragOpInteractionTests.hpp"
+#include "es3fFlushFinishTests.hpp"
+#include "es3fFlushFinishTests.hpp"
+#include "es3fDefaultVertexAttributeTests.hpp"
+#include "es3fScissorTests.hpp"
+#include "es3fLifetimeTests.hpp"
+
+// Shader tests
+#include "es3fShaderApiTests.hpp"
+#include "es3fShaderConstExprTests.hpp"
+#include "es3fShaderDiscardTests.hpp"
+#include "es3fShaderIndexingTests.hpp"
+#include "es3fShaderLoopTests.hpp"
+#include "es3fShaderMatrixTests.hpp"
+#include "es3fShaderOperatorTests.hpp"
+#include "es3fShaderReturnTests.hpp"
+#include "es3fShaderStructTests.hpp"
+#include "es3fShaderSwitchTests.hpp"
+#include "es3fRandomShaderTests.hpp"
+#include "es3fFragDepthTests.hpp"
+#include "es3fShaderPrecisionTests.hpp"
+#include "es3fShaderBuiltinVarTests.hpp"
+#include "es3fShaderTextureFunctionTests.hpp"
+#include "es3fShaderDerivateTests.hpp"
+#include "es3fShaderPackingFunctionTests.hpp"
+#include "es3fShaderCommonFunctionTests.hpp"
+#include "es3fShaderInvarianceTests.hpp"
+#include "es3fShaderFragDataTests.hpp"
+#include "es3fBuiltinPrecisionTests.hpp"
+
+// Texture tests
+#include "es3fTextureFormatTests.hpp"
+#include "es3fTextureWrapTests.hpp"
+#include "es3fTextureFilteringTests.hpp"
+#include "es3fTextureMipmapTests.hpp"
+#include "es3fTextureSizeTests.hpp"
+#include "es3fTextureSwizzleTests.hpp"
+#include "es3fTextureShadowTests.hpp"
+#include "es3fTextureSpecificationTests.hpp"
+#include "es3fVertexTextureTests.hpp"
+#include "es3fTextureUnitTests.hpp"
+#include "es3fCompressedTextureTests.hpp"
+
+// Fbo tests
+#include "es3fFboApiTests.hpp"
+#include "es3fFboCompletenessTests.hpp"
+#include "es3fFboColorbufferTests.hpp"
+#include "es3fFboDepthbufferTests.hpp"
+#include "es3fFboStencilbufferTests.hpp"
+#include "es3fFramebufferBlitTests.hpp"
+#include "es3fFboMultisampleTests.hpp"
+#include "es3fFboRenderTest.hpp"
+#include "es3fFboInvalidateTests.hpp"
+
+// Buffer tests
+#include "es3fBufferWriteTests.hpp"
+#include "es3fBufferMapTests.hpp"
+#include "es3fBufferCopyTests.hpp"
+
+// Negative API tests
+#include "es3fNegativeBufferApiTests.hpp"
+#include "es3fNegativeTextureApiTests.hpp"
+#include "es3fNegativeShaderApiTests.hpp"
+#include "es3fNegativeFragmentApiTests.hpp"
+#include "es3fNegativeVertexArrayApiTests.hpp"
+#include "es3fNegativeStateApiTests.hpp"
+
+// State query tests
+#include "es3fBooleanStateQueryTests.hpp"
+#include "es3fIntegerStateQueryTests.hpp"
+#include "es3fInteger64StateQueryTests.hpp"
+#include "es3fFloatStateQueryTests.hpp"
+#include "es3fTextureStateQueryTests.hpp"
+#include "es3fStringQueryTests.hpp"
+#include "es3fSamplerStateQueryTests.hpp"
+#include "es3fBufferObjectQueryTests.hpp"
+#include "es3fFboStateQueryTests.hpp"
+#include "es3fRboStateQueryTests.hpp"
+#include "es3fShaderStateQueryTests.hpp"
+#include "es3fInternalFormatQueryTests.hpp"
+#include "es3fIndexedStateQueryTests.hpp"
+
+#include "es3fReadPixelsTests.hpp"
+
+#include "glsShaderLibrary.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderLibraryTest : public TestCaseGroup
+{
+public:
+	ShaderLibraryTest (Context& context, const char* name, const char* description)
+		: TestCaseGroup(context, name, description)
+	{
+	}
+
+	void init (void)
+	{
+		gls::ShaderLibrary			shaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
+		std::string					fileName	= std::string("shaders/") + getName() + ".test";
+		std::vector<tcu::TestNode*>	children	= shaderLibrary.loadShaderFile(fileName.c_str());
+
+		for (int i = 0; i < (int)children.size(); i++)
+			addChild(children[i]);
+	}
+};
+
+class ShaderBuiltinFunctionTests : public TestCaseGroup
+{
+public:
+	ShaderBuiltinFunctionTests (Context& context)
+		: TestCaseGroup(context, "builtin_functions", "Built-in Function Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new ShaderCommonFunctionTests	(m_context));
+		addChild(new ShaderPackingFunctionTests	(m_context));
+		addChild(createBuiltinPrecisionTests	(m_context));
+	}
+};
+
+class ShaderTests : public TestCaseGroup
+{
+public:
+	ShaderTests (Context& context)
+		: TestCaseGroup(context, "shaders", "Shading Language Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new ShaderLibraryTest			(m_context, "preprocessor",			"Preprocessor Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "constants",			"Constant Literal Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "linkage",				"Linkage Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "conversions",			"Type Conversion Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "conditionals",			"Conditionals Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "declarations",			"Declarations Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "swizzles",				"Swizzle Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "functions",			"Function Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "arrays",				"Array Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "keywords",				"Keyword Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "qualification_order",	"Order Of Qualification Tests"));
+		addChild(new ShaderLibraryTest			(m_context, "scoping",				"Scoping of Declarations"));
+		addChild(new ShaderLibraryTest			(m_context, "negative",				"Miscellaneous Negative Shader Compilation Tests"));
+
+		addChild(new ShaderDiscardTests			(m_context));
+		addChild(new ShaderIndexingTests		(m_context));
+		addChild(new ShaderLoopTests			(m_context));
+		addChild(new ShaderOperatorTests		(m_context));
+		addChild(new ShaderMatrixTests			(m_context));
+		addChild(new ShaderReturnTests			(m_context));
+		addChild(new ShaderStructTests			(m_context));
+		addChild(new ShaderSwitchTests			(m_context));
+		addChild(new FragDepthTests				(m_context));
+		addChild(new ShaderPrecisionTests		(m_context));
+		addChild(new ShaderBuiltinVarTests		(m_context));
+		addChild(new ShaderTextureFunctionTests	(m_context)); // \todo [pyry] Move to builtin?
+		addChild(new ShaderDerivateTests		(m_context)); // \todo [pyry] Move to builtin?
+		addChild(new ShaderBuiltinFunctionTests	(m_context));
+		addChild(new ShaderInvarianceTests		(m_context));
+		addChild(new ShaderFragDataTests		(m_context));
+		addChild(new ShaderConstExprTests		(m_context));
+
+		addChild(new RandomShaderTests			(m_context));
+	}
+};
+
+class TextureTests : public TestCaseGroup
+{
+public:
+	TextureTests (Context& context)
+		: TestCaseGroup(context, "texture", "Texture Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new TextureFormatTests			(m_context));
+		addChild(new TextureSizeTests			(m_context));
+		addChild(new TextureWrapTests			(m_context));
+		addChild(new TextureFilteringTests		(m_context));
+		addChild(new TextureMipmapTests			(m_context));
+		addChild(new TextureSwizzleTests		(m_context));
+		addChild(new TextureShadowTests			(m_context));
+		addChild(new TextureSpecificationTests	(m_context));
+		addChild(new VertexTextureTests			(m_context));
+		addChild(new TextureUnitTests			(m_context));
+		addChild(new CompressedTextureTests		(m_context));
+	}
+};
+
+class FboTests : public TestCaseGroup
+{
+public:
+	FboTests (Context& context)
+		: TestCaseGroup(context, "fbo", "Framebuffer Object Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new FboApiTests			(m_context));
+		addChild(createFboCompletenessTests	(m_context));
+		addChild(new FboRenderTestGroup		(m_context));
+		addChild(new FboColorTests			(m_context));
+		addChild(new FboDepthTests			(m_context));
+		addChild(new FboStencilTests		(m_context));
+		addChild(new FramebufferBlitTests	(m_context));
+		addChild(new FboMultisampleTests	(m_context));
+		addChild(new FboInvalidateTests		(m_context));
+	}
+};
+
+class BufferTests : public TestCaseGroup
+{
+public:
+	BufferTests (Context& context)
+		: TestCaseGroup(context, "buffer", "Buffer object tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new BufferWriteTests	(m_context));
+		addChild(new BufferMapTests		(m_context));
+		addChild(new BufferCopyTests	(m_context));
+	}
+};
+
+class NegativeApiTestGroup : public TestCaseGroup
+{
+public:
+	NegativeApiTestGroup (Context& context)
+		: TestCaseGroup(context, "negative_api", "Negative API Tests")
+	{
+	}
+
+	virtual ~NegativeApiTestGroup (void)
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new NegativeBufferApiTests			(m_context));
+		addChild(new NegativeTextureApiTests		(m_context));
+		addChild(new NegativeShaderApiTests			(m_context));
+		addChild(new NegativeFragmentApiTests		(m_context));
+		addChild(new NegativeVertexArrayApiTests	(m_context));
+		addChild(new NegativeStateApiTests			(m_context));
+	}
+};
+
+class FragmentOpTests : public TestCaseGroup
+{
+public:
+	FragmentOpTests (Context& context)
+		: TestCaseGroup(context, "fragment_ops", "Per-Fragment Operation Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new DepthTests				(m_context));
+		addChild(new StencilTests			(m_context));
+		addChild(new DepthStencilTests		(m_context));
+		addChild(new BlendTests				(m_context));
+		addChild(new RandomFragmentOpTests	(m_context));
+		addChild(new FragOpInteractionTests	(m_context));
+		addChild(new ScissorTests			(m_context));
+	}
+};
+
+class StateQueryTests : public TestCaseGroup
+{
+public:
+	StateQueryTests (Context& context)
+		: TestCaseGroup(context, "state_query", "State Query Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new BooleanStateQueryTests		(m_context));
+		addChild(new IntegerStateQueryTests		(m_context));
+		addChild(new Integer64StateQueryTests	(m_context));
+		addChild(new FloatStateQueryTests		(m_context));
+		addChild(new IndexedStateQueryTests		(m_context));
+		addChild(new TextureStateQueryTests		(m_context));
+		addChild(new StringQueryTests			(m_context));
+		addChild(new SamplerStateQueryTests		(m_context));
+		addChild(new BufferObjectQueryTests		(m_context));
+		addChild(new FboStateQueryTests			(m_context));
+		addChild(new RboStateQueryTests			(m_context));
+		addChild(new ShaderStateQueryTests		(m_context));
+		addChild(new InternalFormatQueryTests	(m_context));
+	}
+};
+
+FunctionalTests::FunctionalTests (Context& context)
+	: TestCaseGroup(context, "functional", "Functionality Tests")
+{
+}
+
+FunctionalTests::~FunctionalTests (void)
+{
+}
+
+void FunctionalTests::init (void)
+{
+	addChild(new PrerequisiteTests			(m_context));
+	addChild(new ImplementationLimitTests	(m_context));
+	addChild(new ColorClearTest				(m_context));
+	addChild(new DepthStencilClearTests		(m_context));
+	addChild(new BufferTests				(m_context));
+	addChild(new ShaderTests				(m_context));
+	addChild(new TextureTests				(m_context));
+	addChild(new FragmentOpTests			(m_context));
+	addChild(new FboTests					(m_context));
+	addChild(new VertexArrayTestGroup		(m_context));
+	addChild(new UniformBlockTests			(m_context));
+	addChild(new UniformApiTests			(m_context));
+	addChild(createAttributeLocationTests	(m_context));
+	addChild(new FragmentOutputTests		(m_context));
+	addChild(new SamplerObjectTests			(m_context));
+	addChild(new PixelBufferObjectTests		(m_context));
+	addChild(new RasterizationTests			(m_context));
+	addChild(new OcclusionQueryTests		(m_context));
+	addChild(new VertexArrayObjectTestGroup	(m_context));
+	addChild(new PrimitiveRestartTests		(m_context));
+	addChild(new InstancedRenderingTests	(m_context));
+	addChild(new RasterizerDiscardTests		(m_context));
+	addChild(new TransformFeedbackTests		(m_context));
+	addChild(new SyncTests					(m_context));
+	addChild(new ShaderApiTests				(m_context));
+	addChild(new NegativeApiTestGroup		(m_context));
+	addChild(new MultisampleTests			(m_context));
+	addChild(new ReadPixelsTests			(m_context));
+	addChild(new DitheringTests				(m_context));
+	addChild(new StateQueryTests			(m_context));
+	addChild(new ClippingTests				(m_context));
+	addChild(new PolygonOffsetTests			(m_context));
+	addChild(new DrawTests					(m_context));
+	addChild(new FlushFinishTests			(m_context));
+	addChild(new DefaultVertexAttributeTests(m_context));
+	addChild(createLifetimeTests			(m_context));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fFunctionalTests.hpp b/modules/gles3/functional/es3fFunctionalTests.hpp
new file mode 100644
index 0000000..b7a089f
--- /dev/null
+++ b/modules/gles3/functional/es3fFunctionalTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FFUNCTIONALTESTS_HPP
+#define _ES3FFUNCTIONALTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Functional Test Group.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class FunctionalTests : public TestCaseGroup
+{
+public:
+						FunctionalTests		(Context& context);
+						~FunctionalTests	(void);
+
+	void				init				(void);
+
+private:
+						FunctionalTests		(const FunctionalTests& other);
+	FunctionalTests&	operator=			(const FunctionalTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FFUNCTIONALTESTS_HPP
diff --git a/modules/gles3/functional/es3fImplementationLimitTests.cpp b/modules/gles3/functional/es3fImplementationLimitTests.cpp
new file mode 100644
index 0000000..1e1e3d3
--- /dev/null
+++ b/modules/gles3/functional/es3fImplementationLimitTests.cpp
@@ -0,0 +1,509 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Implementation-defined limit tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fImplementationLimitTests.hpp"
+#include "tcuTestLog.hpp"
+#include "gluDefs.hpp"
+#include "gluStrUtil.hpp"
+#include "gluRenderContext.hpp"
+
+#include <vector>
+#include <set>
+#include <algorithm>
+#include <iterator>
+#include <limits>
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using std::set;
+using namespace glw; // GL types
+
+namespace LimitQuery
+{
+
+// Query function template.
+template<typename T>
+T query (const glw::Functions& gl, deUint32 param);
+
+// Compare template.
+template<typename T>
+inline bool compare (const T& min, const T& reported) { return min <= reported; }
+
+// Types for queries
+
+struct NegInt
+{
+	GLint value;
+	NegInt (GLint value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const NegInt& v) { return str << v.value; }
+
+struct FloatRange
+{
+	float min;
+	float max;
+	FloatRange (float min_, float max_) : min(min_), max(max_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const FloatRange& range) { return str << range.min << ", " << range.max; }
+
+struct AlignmentInt
+{
+	GLint value;
+	AlignmentInt (GLint value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const AlignmentInt& v) { return str << v.value; }
+
+// For custom formatting
+struct Boolean
+{
+	GLboolean value;
+	Boolean (GLboolean value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const Boolean& boolean) { return str << (boolean.value ? "GL_TRUE" : "GL_FALSE"); }
+
+// Query function implementations.
+template<>
+GLint query<GLint> (const glw::Functions& gl, deUint32 param)
+{
+	GLint val = -1;
+	gl.getIntegerv(param, &val);
+	return val;
+}
+
+template<>
+GLint64 query<GLint64> (const glw::Functions& gl, deUint32 param)
+{
+	GLint64 val = -1;
+	gl.getInteger64v(param, &val);
+	return val;
+}
+
+template<>
+GLuint64 query<GLuint64> (const glw::Functions& gl, deUint32 param)
+{
+	GLint64 val = 0;
+	gl.getInteger64v(param, &val);
+	return (GLuint64)val;
+}
+
+template<>
+GLfloat query<GLfloat> (const glw::Functions& gl,deUint32 param)
+{
+	GLfloat val = -1000.f;
+	gl.getFloatv(param, &val);
+	return val;
+}
+
+template<>
+NegInt query<NegInt> (const glw::Functions& gl, deUint32 param)
+{
+	return NegInt(query<GLint>(gl, param));
+}
+
+template<>
+Boolean query<Boolean> (const glw::Functions& gl, deUint32 param)
+{
+	GLboolean val = GL_FALSE;
+	gl.getBooleanv(param, &val);
+	return Boolean(val);
+}
+
+template<>
+FloatRange query<FloatRange> (const glw::Functions& gl, deUint32 param)
+{
+	float v[2] = { -1.0f, -1.0f };
+	gl.getFloatv(param, &v[0]);
+	return FloatRange(v[0], v[1]);
+}
+
+template<>
+AlignmentInt query<AlignmentInt> (const glw::Functions& gl, deUint32 param)
+{
+	return AlignmentInt(query<GLint>(gl, param));
+}
+
+// Special comparison operators
+template<>
+bool compare<Boolean> (const Boolean& min, const Boolean& reported)
+{
+	return !min.value || (min.value && reported.value);
+}
+
+template<>
+bool compare<NegInt> (const NegInt& min, const NegInt& reported)
+{
+	// Reverse comparison.
+	return reported.value <= min.value;
+}
+
+template<>
+bool compare<FloatRange> (const FloatRange& min, const FloatRange& reported)
+{
+	return reported.min <= min.min && min.max <= reported.max;
+}
+
+template<>
+bool compare<AlignmentInt> (const AlignmentInt& min, const AlignmentInt& reported)
+{
+	// Reverse comparison.
+	return reported.value <= min.value;
+}
+
+// Special error descriptions
+
+enum QueryClass
+{
+	CLASS_VALUE = 0,
+	CLASS_RANGE,
+	CLASS_ALIGNMENT,
+};
+
+template <QueryClass Class>
+struct QueryClassTraits
+{
+	static const char* const s_errorDescription;
+};
+
+template <>
+const char* const QueryClassTraits<CLASS_VALUE>::s_errorDescription = "reported value is less than minimum required value!";
+
+template <>
+const char* const QueryClassTraits<CLASS_RANGE>::s_errorDescription = "reported range does not contain the minimum required range!";
+
+template <>
+const char* const QueryClassTraits<CLASS_ALIGNMENT>::s_errorDescription = "reported alignment is larger than minimum required aligmnent!";
+
+template <typename T>
+struct QueryTypeTraits
+{
+};
+
+template <> struct QueryTypeTraits<GLint>			{	enum { CLASS = CLASS_VALUE };		};
+template <> struct QueryTypeTraits<GLint64>			{	enum { CLASS = CLASS_VALUE };		};
+template <> struct QueryTypeTraits<GLuint64>		{	enum { CLASS = CLASS_VALUE };		};
+template <> struct QueryTypeTraits<GLfloat>			{	enum { CLASS = CLASS_VALUE };		};
+template <> struct QueryTypeTraits<Boolean>			{	enum { CLASS = CLASS_VALUE };		};
+template <> struct QueryTypeTraits<NegInt>			{	enum { CLASS = CLASS_VALUE };		};
+template <> struct QueryTypeTraits<FloatRange>		{	enum { CLASS = CLASS_RANGE };		};
+template <> struct QueryTypeTraits<AlignmentInt>	{	enum { CLASS = CLASS_ALIGNMENT };	};
+
+} // LimitQuery
+
+using namespace LimitQuery;
+using tcu::TestLog;
+
+template<typename T>
+class LimitQueryCase : public TestCase
+{
+public:
+	LimitQueryCase (Context& context, const char* name, const char* description, deUint32 limit, const T& minRequiredValue)
+		: TestCase				(context, name, description)
+		, m_limit				(limit)
+		, m_minRequiredValue	(minRequiredValue)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+		const T					value	= query<T>(m_context.getRenderContext().getFunctions(), m_limit);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Query failed");
+
+		const bool isOk = compare<T>(m_minRequiredValue, value);
+
+		m_testCtx.getLog() << TestLog::Message << "Reported: " << value << TestLog::EndMessage;
+		m_testCtx.getLog() << TestLog::Message << "Minimum required: " << m_minRequiredValue << TestLog::EndMessage;
+
+		if (!isOk)
+			m_testCtx.getLog() << TestLog::Message << "FAIL: " << QueryClassTraits<(QueryClass)QueryTypeTraits<T>::CLASS>::s_errorDescription << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Requirement not satisfied");
+		return STOP;
+	}
+
+private:
+	deUint32	m_limit;
+	T			m_minRequiredValue;
+};
+
+static const deUint32 s_requiredCompressedTexFormats[] =
+{
+	GL_COMPRESSED_R11_EAC,
+	GL_COMPRESSED_SIGNED_R11_EAC,
+	GL_COMPRESSED_RG11_EAC,
+	GL_COMPRESSED_SIGNED_RG11_EAC,
+	GL_COMPRESSED_RGB8_ETC2,
+	GL_COMPRESSED_SRGB8_ETC2,
+	GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
+	GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
+	GL_COMPRESSED_RGBA8_ETC2_EAC,
+	GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC
+};
+
+class CompressedTextureFormatsQueryCase : public TestCase
+{
+public:
+	CompressedTextureFormatsQueryCase (Context& context)
+		: TestCase(context, "compressed_texture_formats", "GL_COMPRESSED_TEXTURE_FORMATS")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+		const GLint				numFormats		= query<GLint>(gl, GL_NUM_COMPRESSED_TEXTURE_FORMATS);
+		vector<GLint>			formats			(numFormats);
+		bool					allFormatsOk	= true;
+
+		if (numFormats > 0)
+			gl.getIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, &formats[0]);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Query failed");
+
+		// Log formats.
+		m_testCtx.getLog() << TestLog::Message << "Reported:" << TestLog::EndMessage;
+		for (vector<GLint>::const_iterator fmt = formats.begin(); fmt != formats.end(); fmt++)
+			m_testCtx.getLog() << TestLog::Message << glu::getCompressedTexFormatStr(*fmt) << TestLog::EndMessage;
+
+		// Check that all required formats are in list.
+		{
+			set<GLint> formatSet(formats.begin(), formats.end());
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_requiredCompressedTexFormats); ndx++)
+			{
+				const deUint32	fmt		= s_requiredCompressedTexFormats[ndx];
+				const bool		found	= formatSet.find(fmt) != formatSet.end();
+
+				if (!found)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: " << glu::getCompressedTexFormatStr(fmt) << " is missing!" << TestLog::EndMessage;
+					allFormatsOk = false;
+				}
+			}
+		}
+
+		m_testCtx.setTestResult(allFormatsOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								allFormatsOk ? "Pass"				: "Requirement not satisfied");
+		return STOP;
+	}
+};
+
+static vector<string> queryExtensionsNonIndexed (const glw::Functions& gl)
+{
+	const string	extensionStr	= (const char*)gl.getString(GL_EXTENSIONS);
+	vector<string>	extensionList;
+	size_t			pos				= 0;
+
+	for (;;)
+	{
+		const size_t	nextPos	= extensionStr.find(' ', pos);
+		const size_t	len		= nextPos == string::npos ? extensionStr.length()-pos : nextPos-pos;
+
+		if (len > 0)
+			extensionList.push_back(extensionStr.substr(pos, len));
+
+		if (nextPos == string::npos)
+			break;
+		else
+			pos = nextPos+1;
+	}
+
+	return extensionList;
+}
+
+static vector<string> queryExtensionsIndexed (const glw::Functions& gl)
+{
+	const int		numExtensions		= query<GLint>(gl, GL_NUM_EXTENSIONS);
+	vector<string>	extensions			(numExtensions);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "GL_NUM_EXTENSIONS query failed");
+
+	for (int ndx = 0; ndx < numExtensions; ndx++)
+		extensions[ndx] = (const char*)gl.getStringi(GL_EXTENSIONS, (GLuint)ndx);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetStringi(GL_EXTENSIONS) failed");
+
+	return extensions;
+}
+
+static bool compareExtensionLists (const vector<string>& a, const vector<string>& b)
+{
+	if (a.size() != b.size())
+		return false;
+
+	set<string> extsInB(b.begin(), b.end());
+
+	for (vector<string>::const_iterator i = a.begin(); i != a.end(); ++i)
+	{
+		if (extsInB.find(*i) == extsInB.end())
+			return false;
+	}
+
+	return true;
+}
+
+class ExtensionQueryCase : public TestCase
+{
+public:
+	ExtensionQueryCase (Context& context)
+		: TestCase(context, "extensions", "GL_EXTENSIONS")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+		const vector<string>	nonIndexedExts		= queryExtensionsNonIndexed(gl);
+		const vector<string>	indexedExts			= queryExtensionsIndexed(gl);
+		const bool				isOk				= compareExtensionLists(nonIndexedExts, indexedExts);
+
+		m_testCtx.getLog() << TestLog::Message << "Extensions as reported by glGetStringi(GL_EXTENSIONS):" << TestLog::EndMessage;
+		for (vector<string>::const_iterator ext = indexedExts.begin(); ext != indexedExts.end(); ++ext)
+			m_testCtx.getLog() << TestLog::Message << *ext << TestLog::EndMessage;
+
+		if (!isOk)
+		{
+			m_testCtx.getLog() << TestLog::Message << "Extensions as reported by glGetString(GL_EXTENSIONS):" << TestLog::EndMessage;
+			for (vector<string>::const_iterator ext = nonIndexedExts.begin(); ext != nonIndexedExts.end(); ++ext)
+				m_testCtx.getLog() << TestLog::Message << *ext << TestLog::EndMessage;
+
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Extension lists do not match!" << TestLog::EndMessage;
+		}
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Invalid extension list");
+		return STOP;
+	}
+};
+
+ImplementationLimitTests::ImplementationLimitTests (Context& context)
+	: TestCaseGroup(context, "implementation_limits", "Implementation-defined limits")
+{
+}
+
+ImplementationLimitTests::~ImplementationLimitTests (void)
+{
+}
+
+void ImplementationLimitTests::init (void)
+{
+	const int	minVertexUniformBlocks					= 12;
+	const int	minVertexUniformComponents				= 1024;
+
+	const int	minFragmentUniformBlocks				= 12;
+	const int	minFragmentUniformComponents			= 896;
+
+	const int	minUniformBlockSize						= 16384;
+	const int	minCombinedVertexUniformComponents		= (minVertexUniformBlocks*minUniformBlockSize)/4 + minVertexUniformComponents;
+	const int	minCombinedFragmentUniformComponents	= (minFragmentUniformBlocks*minUniformBlockSize)/4 + minFragmentUniformComponents;
+
+#define LIMIT_CASE(NAME, PARAM, TYPE, MIN_VAL)	\
+	addChild(new LimitQueryCase<TYPE>(m_context, #NAME, #PARAM, PARAM, MIN_VAL))
+
+	LIMIT_CASE(max_element_index,				GL_MAX_ELEMENT_INDEX,					GLint64,	(1<<24)-1);
+	LIMIT_CASE(subpixel_bits,					GL_SUBPIXEL_BITS,						GLint,		4);
+	LIMIT_CASE(max_3d_texture_size,				GL_MAX_3D_TEXTURE_SIZE,					GLint,		256);
+	LIMIT_CASE(max_texture_size,				GL_MAX_TEXTURE_SIZE,					GLint,		2048);
+	LIMIT_CASE(max_array_texture_layers,		GL_MAX_ARRAY_TEXTURE_LAYERS,			GLint,		256);
+	LIMIT_CASE(max_texture_lod_bias,			GL_MAX_TEXTURE_LOD_BIAS,				GLfloat,	2.0f);
+	LIMIT_CASE(max_cube_map_texture_size,		GL_MAX_CUBE_MAP_TEXTURE_SIZE,			GLint,		2048);
+	LIMIT_CASE(max_renderbuffer_size,			GL_MAX_RENDERBUFFER_SIZE,				GLint,		2048);
+	LIMIT_CASE(max_draw_buffers,				GL_MAX_DRAW_BUFFERS,					GLint,		4);
+	LIMIT_CASE(max_color_attachments,			GL_MAX_COLOR_ATTACHMENTS,				GLint,		4);
+	// GL_MAX_VIEWPORT_DIMS
+	LIMIT_CASE(aliased_point_size_range,		GL_ALIASED_POINT_SIZE_RANGE,			FloatRange,	FloatRange(1,1));
+	LIMIT_CASE(aliased_line_width_range,		GL_ALIASED_LINE_WIDTH_RANGE,			FloatRange,	FloatRange(1,1));
+	LIMIT_CASE(max_elements_indices,			GL_MAX_ELEMENTS_INDICES,				GLint,		0);
+	LIMIT_CASE(max_elements_vertices,			GL_MAX_ELEMENTS_VERTICES,				GLint,		0);
+	LIMIT_CASE(num_compressed_texture_formats,	GL_NUM_COMPRESSED_TEXTURE_FORMATS,		GLint,		DE_LENGTH_OF_ARRAY(s_requiredCompressedTexFormats));
+	addChild(new CompressedTextureFormatsQueryCase(m_context)); // GL_COMPRESSED_TEXTURE_FORMATS
+	// GL_PROGRAM_BINARY_FORMATS
+	LIMIT_CASE(num_program_binary_formats,		GL_NUM_PROGRAM_BINARY_FORMATS,			GLint,		0);
+	// GL_SHADER_BINARY_FORMATS
+	LIMIT_CASE(num_shader_binary_formats,		GL_NUM_SHADER_BINARY_FORMATS,			GLint,		0);
+	LIMIT_CASE(shader_compiler,					GL_SHADER_COMPILER,						Boolean,	GL_TRUE);
+	// Shader data type ranges & precisions
+	LIMIT_CASE(max_server_wait_timeout,			GL_MAX_SERVER_WAIT_TIMEOUT,				GLuint64,	0);
+
+	// Version and extension support
+	addChild(new ExtensionQueryCase(m_context)); // GL_EXTENSIONS + consistency validation
+	LIMIT_CASE(num_extensions,					GL_NUM_EXTENSIONS,						GLint,		0);
+	LIMIT_CASE(major_version,					GL_MAJOR_VERSION,						GLint,		3);
+	LIMIT_CASE(minor_version,					GL_MINOR_VERSION,						GLint,		0);
+	// GL_RENDERER
+	// GL_SHADING_LANGUAGE_VERSION
+	// GL_VENDOR
+	// GL_VERSION
+
+	// Vertex shader limits
+	LIMIT_CASE(max_vertex_attribs,				GL_MAX_VERTEX_ATTRIBS,					GLint,		16);
+	LIMIT_CASE(max_vertex_uniform_components,	GL_MAX_VERTEX_UNIFORM_COMPONENTS,		GLint,		minVertexUniformComponents);
+	LIMIT_CASE(max_vertex_uniform_vectors,		GL_MAX_VERTEX_UNIFORM_VECTORS,			GLint,		minVertexUniformComponents/4);
+	LIMIT_CASE(max_vertex_uniform_blocks,		GL_MAX_VERTEX_UNIFORM_BLOCKS,			GLint,		minVertexUniformBlocks);
+	LIMIT_CASE(max_vertex_output_components,	GL_MAX_VERTEX_OUTPUT_COMPONENTS,		GLint,		64);
+	LIMIT_CASE(max_vertex_texture_image_units,	GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,		GLint,		16);
+
+	// Fragment shader limits
+	LIMIT_CASE(max_fragment_uniform_components,	GL_MAX_FRAGMENT_UNIFORM_COMPONENTS,		GLint,		minFragmentUniformComponents);
+	LIMIT_CASE(max_fragment_uniform_vectors,	GL_MAX_FRAGMENT_UNIFORM_VECTORS,		GLint,		minFragmentUniformComponents/4);
+	LIMIT_CASE(max_fragment_uniform_blocks,		GL_MAX_FRAGMENT_UNIFORM_BLOCKS,			GLint,		minFragmentUniformBlocks);
+	LIMIT_CASE(max_fragment_input_components,	GL_MAX_FRAGMENT_INPUT_COMPONENTS,		GLint,		60);
+	LIMIT_CASE(max_texture_image_units,			GL_MAX_TEXTURE_IMAGE_UNITS,				GLint,		16);
+	LIMIT_CASE(min_program_texel_offset,		GL_MIN_PROGRAM_TEXEL_OFFSET,			NegInt,		-8);
+	LIMIT_CASE(max_program_texel_offset,		GL_MAX_PROGRAM_TEXEL_OFFSET,			GLint,		7);
+
+	// Aggregate shader limits
+	LIMIT_CASE(max_uniform_buffer_bindings,						GL_MAX_UNIFORM_BUFFER_BINDINGS,						GLint,				24);
+	LIMIT_CASE(max_uniform_block_size,							GL_MAX_UNIFORM_BLOCK_SIZE,							GLint64,			minUniformBlockSize);
+	LIMIT_CASE(uniform_buffer_offset_alignment,					GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT,					AlignmentInt,		256);
+	LIMIT_CASE(max_combined_uniform_blocks,						GL_MAX_COMBINED_UNIFORM_BLOCKS,						GLint,				24);
+	LIMIT_CASE(max_combined_vertex_uniform_components,			GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS,			GLint64,			minCombinedVertexUniformComponents);
+	LIMIT_CASE(max_combined_fragment_uniform_components,		GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS,		GLint64,			minCombinedFragmentUniformComponents);
+	LIMIT_CASE(max_varying_components,							GL_MAX_VARYING_COMPONENTS,							GLint,				60);
+	LIMIT_CASE(max_varying_vectors,								GL_MAX_VARYING_VECTORS,								GLint,				15);
+	LIMIT_CASE(max_combined_texture_image_units,				GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,				GLint,				32);
+
+	// Transform feedback limits
+	LIMIT_CASE(max_transform_feedback_interleaved_components,	GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,	GLint,				64);
+	LIMIT_CASE(max_transform_feedback_separate_attribs,			GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,			GLint,				4);
+	LIMIT_CASE(max_transform_feedback_separate_components,		GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS,		GLint,				4);
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fImplementationLimitTests.hpp b/modules/gles3/functional/es3fImplementationLimitTests.hpp
new file mode 100644
index 0000000..8d691a9
--- /dev/null
+++ b/modules/gles3/functional/es3fImplementationLimitTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FIMPLEMENTATIONLIMITTESTS_HPP
+#define _ES3FIMPLEMENTATIONLIMITTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Implementation-defined limit tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ImplementationLimitTests : public TestCaseGroup
+{
+public:
+									ImplementationLimitTests			(Context& context);
+									~ImplementationLimitTests			(void);
+
+	void							init								(void);
+
+private:
+									ImplementationLimitTests			(const ImplementationLimitTests& other);
+	ImplementationLimitTests&		operator=							(const ImplementationLimitTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FIMPLEMENTATIONLIMITTESTS_HPP
diff --git a/modules/gles3/functional/es3fIndexedStateQueryTests.cpp b/modules/gles3/functional/es3fIndexedStateQueryTests.cpp
new file mode 100644
index 0000000..d5a4c44
--- /dev/null
+++ b/modules/gles3/functional/es3fIndexedStateQueryTests.cpp
@@ -0,0 +1,449 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Indexed State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fIndexedStateQueryTests.hpp"
+#include "es3fApiCase.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+void checkIntEquals (tcu::TestContext& testCtx, GLint got, GLint expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void checkIntEquals (tcu::TestContext& testCtx, GLint64 got, GLint64 expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+class TransformFeedbackCase : public ApiCase
+{
+public:
+	TransformFeedbackCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	virtual void testTransformFeedback (void) = DE_NULL;
+
+	void test (void)
+	{
+		static const char* transformFeedbackTestVertSource	=	"#version 300 es\n"
+																"out highp vec4 anotherOutput;\n"
+																"void main (void)\n"
+																"{\n"
+																"	gl_Position = vec4(0.0);\n"
+																"	anotherOutput = vec4(0.0);\n"
+																"}\n\0";
+		static const char* transformFeedbackTestFragSource	=	"#version 300 es\n"
+																"layout(location = 0) out mediump vec4 fragColor;"
+																"void main (void)\n"
+																"{\n"
+																"	fragColor = vec4(0.0);\n"
+																"}\n\0";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &transformFeedbackTestVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &transformFeedbackTestFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint shaderProg = glCreateProgram();
+		glAttachShader(shaderProg, shaderVert);
+		glAttachShader(shaderProg, shaderFrag);
+
+		const char* transformFeedbackOutputs[] =
+		{
+			"gl_Position",
+			"anotherOutput"
+		};
+
+		glTransformFeedbackVaryings(shaderProg, 2, transformFeedbackOutputs, GL_INTERLEAVED_ATTRIBS);
+		glLinkProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+
+		GLuint transformFeedbackId = 0;
+		glGenTransformFeedbacks(1, &transformFeedbackId);
+		glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedbackId);
+		expectError(GL_NO_ERROR);
+
+		testTransformFeedback();
+
+		// cleanup
+
+		glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+
+		glDeleteTransformFeedbacks(1, &transformFeedbackId);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class TransformFeedbackBufferBindingCase : public TransformFeedbackCase
+{
+public:
+	TransformFeedbackBufferBindingCase (Context& context, const char* name, const char* description)
+		: TransformFeedbackCase(context, name, description)
+	{
+	}
+
+	void testTransformFeedback (void)
+	{
+		const int feedbackPositionIndex = 0;
+		const int feedbackOutputIndex = 1;
+		const int feedbackIndex[2] = {feedbackPositionIndex, feedbackOutputIndex};
+
+		// bind bffers
+
+		GLuint feedbackBuffers[2];
+		glGenBuffers(2, feedbackBuffers);
+		expectError(GL_NO_ERROR);
+
+		for (int ndx = 0; ndx < 2; ++ndx)
+		{
+			glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackBuffers[ndx]);
+			glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_DYNAMIC_READ);
+			glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackIndex[ndx], feedbackBuffers[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		// test TRANSFORM_FEEDBACK_BUFFER_BINDING
+
+		for (int ndx = 0; ndx < 2; ++ndx)
+		{
+			StateQueryMemoryWriteGuard<GLint> boundBuffer;
+			glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, feedbackIndex[ndx], &boundBuffer);
+			boundBuffer.verifyValidity(m_testCtx);
+			checkIntEquals(m_testCtx, boundBuffer, feedbackBuffers[ndx]);
+		}
+
+
+		// cleanup
+
+		glDeleteBuffers(2, feedbackBuffers);
+	}
+};
+
+class TransformFeedbackBufferBufferCase : public TransformFeedbackCase
+{
+public:
+	TransformFeedbackBufferBufferCase (Context& context, const char* name, const char* description)
+		: TransformFeedbackCase(context, name, description)
+	{
+	}
+
+	void testTransformFeedback (void)
+	{
+		const int feedbackPositionIndex = 0;
+		const int feedbackOutputIndex = 1;
+
+		const int rangeBufferOffset = 4;
+		const int rangeBufferSize = 8;
+
+		// bind buffers
+
+		GLuint feedbackBuffers[2];
+		glGenBuffers(2, feedbackBuffers);
+		expectError(GL_NO_ERROR);
+
+		glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackBuffers[0]);
+		glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_DYNAMIC_READ);
+		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackPositionIndex, feedbackBuffers[0]);
+		expectError(GL_NO_ERROR);
+
+		glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackBuffers[1]);
+		glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_DYNAMIC_READ);
+		glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackOutputIndex, feedbackBuffers[1], rangeBufferOffset, rangeBufferSize);
+		expectError(GL_NO_ERROR);
+
+		// test TRANSFORM_FEEDBACK_BUFFER_START and TRANSFORM_FEEDBACK_BUFFER_SIZE
+
+		const struct BufferRequirements
+		{
+			GLint	index;
+			GLenum	pname;
+			GLint64 value;
+		} requirements[] =
+		{
+			{ feedbackPositionIndex,	GL_TRANSFORM_FEEDBACK_BUFFER_START, 0					},
+			{ feedbackPositionIndex,	GL_TRANSFORM_FEEDBACK_BUFFER_SIZE,	0					},
+			{ feedbackOutputIndex,		GL_TRANSFORM_FEEDBACK_BUFFER_START, rangeBufferOffset	},
+			{ feedbackOutputIndex,		GL_TRANSFORM_FEEDBACK_BUFFER_SIZE,	rangeBufferSize		}
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requirements); ++ndx)
+		{
+			StateQueryMemoryWriteGuard<GLint64> state;
+			glGetInteger64i_v(requirements[ndx].pname, requirements[ndx].index, &state);
+
+			if (state.verifyValidity(m_testCtx))
+				checkIntEquals(m_testCtx, state, requirements[ndx].value);
+		}
+
+		// cleanup
+
+		glDeleteBuffers(2, feedbackBuffers);
+	}
+};
+
+class UniformBufferCase : public ApiCase
+{
+public:
+	UniformBufferCase (Context& context, const char* name, const char* description)
+		: ApiCase	(context, name, description)
+		, m_program	(0)
+	{
+	}
+
+	virtual void testUniformBuffers (void) = DE_NULL;
+
+	void test (void)
+	{
+		static const char* testVertSource	=	"#version 300 es\n"
+												"uniform highp vec4 input1;\n"
+												"uniform highp vec4 input2;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = input1 + input2;\n"
+												"}\n\0";
+		static const char* testFragSource	=	"#version 300 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(0.0);\n"
+												"}\n\0";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		m_program = glCreateProgram();
+		glAttachShader(m_program, shaderVert);
+		glAttachShader(m_program, shaderFrag);
+		glLinkProgram(m_program);
+		glUseProgram(m_program);
+		expectError(GL_NO_ERROR);
+
+		testUniformBuffers();
+
+		glUseProgram(0);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(m_program);
+		expectError(GL_NO_ERROR);
+	}
+
+protected:
+	GLuint	m_program;
+};
+
+class UniformBufferBindingCase : public UniformBufferCase
+{
+public:
+	UniformBufferBindingCase (Context& context, const char* name, const char* description)
+		: UniformBufferCase(context, name, description)
+	{
+	}
+
+	void testUniformBuffers (void)
+	{
+		const char* uniformNames[] =
+		{
+			"input1",
+			"input2"
+		};
+		GLuint uniformIndices[2] = {0};
+		glGetUniformIndices(m_program, 2, uniformNames, uniformIndices);
+
+		GLuint buffers[2];
+		glGenBuffers(2, buffers);
+
+		for (int ndx = 0; ndx < 2; ++ndx)
+		{
+			glBindBuffer(GL_UNIFORM_BUFFER, buffers[ndx]);
+			glBufferData(GL_UNIFORM_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase(GL_UNIFORM_BUFFER, uniformIndices[ndx], buffers[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		for (int ndx = 0; ndx < 2; ++ndx)
+		{
+			StateQueryMemoryWriteGuard<GLint> boundBuffer;
+			glGetIntegeri_v(GL_UNIFORM_BUFFER_BINDING, uniformIndices[ndx], &boundBuffer);
+
+			if (boundBuffer.verifyValidity(m_testCtx))
+				checkIntEquals(m_testCtx, boundBuffer, buffers[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		glDeleteBuffers(2, buffers);
+	}
+};
+
+class UniformBufferBufferCase : public UniformBufferCase
+{
+public:
+	UniformBufferBufferCase (Context& context, const char* name, const char* description)
+		: UniformBufferCase(context, name, description)
+	{
+	}
+
+	void testUniformBuffers (void)
+	{
+		const char* uniformNames[] =
+		{
+			"input1",
+			"input2"
+		};
+		GLuint uniformIndices[2] = {0};
+		glGetUniformIndices(m_program, 2, uniformNames, uniformIndices);
+
+		const GLint alignment = GetAlignment();
+		if (alignment == -1) // cannot continue without this
+			return;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Alignment is " << alignment << tcu::TestLog::EndMessage;
+
+		int rangeBufferOffset		= alignment;
+		int rangeBufferSize			= alignment * 2;
+		int rangeBufferTotalSize	= rangeBufferOffset + rangeBufferSize + 8; // + 8 has no special meaning, just to make it != with the size of the range
+
+		GLuint buffers[2];
+		glGenBuffers(2, buffers);
+
+		glBindBuffer(GL_UNIFORM_BUFFER, buffers[0]);
+		glBufferData(GL_UNIFORM_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+		glBindBufferBase(GL_UNIFORM_BUFFER, uniformIndices[0], buffers[0]);
+		expectError(GL_NO_ERROR);
+
+		glBindBuffer(GL_UNIFORM_BUFFER, buffers[1]);
+		glBufferData(GL_UNIFORM_BUFFER, rangeBufferTotalSize, DE_NULL, GL_DYNAMIC_DRAW);
+		glBindBufferRange(GL_UNIFORM_BUFFER, uniformIndices[1], buffers[1], rangeBufferOffset, rangeBufferSize);
+		expectError(GL_NO_ERROR);
+
+		// test UNIFORM_BUFFER_START and UNIFORM_BUFFER_SIZE
+
+		const struct BufferRequirements
+		{
+			GLuint	index;
+			GLenum	pname;
+			GLint64 value;
+		} requirements[] =
+		{
+			{ uniformIndices[0], GL_UNIFORM_BUFFER_START,	0					},
+			{ uniformIndices[0], GL_UNIFORM_BUFFER_SIZE,	0					},
+			{ uniformIndices[1], GL_UNIFORM_BUFFER_START,	rangeBufferOffset	},
+			{ uniformIndices[1], GL_UNIFORM_BUFFER_SIZE,	rangeBufferSize		}
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requirements); ++ndx)
+		{
+			StateQueryMemoryWriteGuard<GLint64> state;
+			glGetInteger64i_v(requirements[ndx].pname, requirements[ndx].index, &state);
+
+			if (state.verifyValidity(m_testCtx))
+				checkIntEquals(m_testCtx, state, requirements[ndx].value);
+			expectError(GL_NO_ERROR);
+		}
+
+		glDeleteBuffers(2, buffers);
+	}
+
+	int GetAlignment()
+	{
+		StateQueryMemoryWriteGuard<GLint> state;
+		glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &state);
+
+		if (!state.verifyValidity(m_testCtx))
+			return -1;
+
+		if (state <= 256)
+			return state;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: UNIFORM_BUFFER_OFFSET_ALIGNMENT has a maximum value of 256." << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid UNIFORM_BUFFER_OFFSET_ALIGNMENT value");
+
+		return -1;
+	}
+};
+
+} // anonymous
+
+IndexedStateQueryTests::IndexedStateQueryTests (Context& context)
+	: TestCaseGroup(context, "indexed", "Indexed Integer Values")
+{
+}
+
+void IndexedStateQueryTests::init (void)
+{
+	// transform feedback
+	addChild(new TransformFeedbackBufferBindingCase(m_context, "transform_feedback_buffer_binding", "TRANSFORM_FEEDBACK_BUFFER_BINDING"));
+	addChild(new TransformFeedbackBufferBufferCase(m_context, "transform_feedback_buffer_start_size", "TRANSFORM_FEEDBACK_BUFFER_START and TRANSFORM_FEEDBACK_BUFFER_SIZE"));
+
+	// uniform buffers
+	addChild(new UniformBufferBindingCase(m_context, "uniform_buffer_binding", "UNIFORM_BUFFER_BINDING"));
+	addChild(new UniformBufferBufferCase(m_context, "uniform_buffer_start_size", "UNIFORM_BUFFER_START and UNIFORM_BUFFER_SIZE"));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fIndexedStateQueryTests.hpp b/modules/gles3/functional/es3fIndexedStateQueryTests.hpp
new file mode 100644
index 0000000..f8c658d
--- /dev/null
+++ b/modules/gles3/functional/es3fIndexedStateQueryTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FINDEXEDSTATEQUERYTESTS_HPP
+#define _ES3FINDEXEDSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Indexed State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class IndexedStateQueryTests : public TestCaseGroup
+{
+public:
+									IndexedStateQueryTests		(Context& context);
+
+	void							init						(void);
+
+private:
+									IndexedStateQueryTests		(const IndexedStateQueryTests& other);
+	IndexedStateQueryTests&			operator=					(const IndexedStateQueryTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FINDEXEDSTATEQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fInstancedRenderingTests.cpp b/modules/gles3/functional/es3fInstancedRenderingTests.cpp
new file mode 100644
index 0000000..aec9c25
--- /dev/null
+++ b/modules/gles3/functional/es3fInstancedRenderingTests.cpp
@@ -0,0 +1,721 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Instanced rendering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fInstancedRenderingTests.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVector.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+#include "glw.h"
+
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+static const int	MAX_RENDER_WIDTH		= 128;
+static const int	MAX_RENDER_HEIGHT		= 128;
+
+static const int	QUAD_GRID_SIZE			= 127;
+
+// Attribute divisors for the attributes defining the color's RGB components.
+static const int	ATTRIB_DIVISOR_R		= 3;
+static const int	ATTRIB_DIVISOR_G		= 2;
+static const int	ATTRIB_DIVISOR_B		= 1;
+
+static const int	OFFSET_COMPONENTS		= 3; // \note Affects whether a float or a vecN is used in shader, but only first component is non-zero.
+
+// Scale and bias values when converting float to integer, when attribute is of integer type.
+static const float	FLOAT_INT_SCALE			= 100.0f;
+static const float	FLOAT_INT_BIAS			= -50.0f;
+static const float	FLOAT_UINT_SCALE		= 100.0f;
+static const float	FLOAT_UINT_BIAS			= 0.0f;
+
+// \note Non-anonymous namespace needed; VarComp is used as a template parameter.
+namespace vcns
+{
+
+union VarComp
+{
+	float		f32;
+	deUint32	u32;
+	deInt32		i32;
+
+	VarComp(float v)	: f32(v) {}
+	VarComp(deUint32 v)	: u32(v) {}
+	VarComp(deInt32 v)	: i32(v) {}
+};
+DE_STATIC_ASSERT(sizeof(VarComp) == sizeof(deUint32));
+
+} // vcns
+
+using namespace vcns;
+
+class InstancedRenderingCase : public TestCase
+{
+public:
+	enum DrawFunction
+	{
+		FUNCTION_DRAW_ARRAYS_INSTANCED = 0,
+		FUNCTION_DRAW_ELEMENTS_INSTANCED,
+
+		FUNCTION_LAST
+	};
+
+	enum InstancingType
+	{
+		TYPE_INSTANCE_ID = 0,
+		TYPE_ATTRIB_DIVISOR,
+		TYPE_MIXED,
+
+		TYPE_LAST
+	};
+
+								InstancedRenderingCase	(Context& context, const char* name, const char* description, DrawFunction function, InstancingType instancingType, glu::DataType rgbAttrType, int numInstances);
+								~InstancedRenderingCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+								InstancedRenderingCase	(const InstancedRenderingCase& other);
+	InstancedRenderingCase&		operator=				(const InstancedRenderingCase& other);
+
+	void						pushVarCompAttrib		(vector<VarComp>& vec, float val);
+
+	void						setupVarAttribPointer	(const void* attrPtr, int startLocation, int divisor);
+	void						setupAndRender			(void);
+	void						computeReference		(tcu::Surface& dst);
+
+	DrawFunction				m_function;
+	InstancingType				m_instancingType;
+	glu::DataType				m_rgbAttrType;			// \note Instance attribute types, color components only. Position offset attribute is always float/vecN.
+	int							m_numInstances;
+
+	vector<float>				m_gridVertexPositions;	// X and Y components per vertex.
+	vector<deUint16>			m_gridIndices;			// \note Only used if m_function is FUNCTION_DRAW_ELEMENTS_INSTANCED.
+
+	// \note Some or all of the following instance attribute parameters may be unused with TYPE_INSTANCE_ID or TYPE_MIXED.
+	vector<float>				m_instanceOffsets;		// Position offsets. OFFSET_COMPONENTS components per offset.
+	// Attribute data for float, int or uint (or respective vector types) color components.
+	vector<VarComp>				m_instanceColorR;
+	vector<VarComp>				m_instanceColorG;
+	vector<VarComp>				m_instanceColorB;
+
+	glu::ShaderProgram*			m_program;
+};
+
+InstancedRenderingCase::InstancedRenderingCase (Context& context, const char* name, const char* description, DrawFunction function, InstancingType instancingType, glu::DataType rgbAttrType, int numInstances)
+	: TestCase			(context, name, description)
+	, m_function		(function)
+	, m_instancingType	(instancingType)
+	, m_rgbAttrType		(rgbAttrType)
+	, m_numInstances	(numInstances)
+	, m_program			(DE_NULL)
+{
+}
+
+InstancedRenderingCase::~InstancedRenderingCase (void)
+{
+	InstancedRenderingCase::deinit();
+}
+
+// Helper function that does biasing and scaling when converting float to integer.
+void InstancedRenderingCase::pushVarCompAttrib (vector<VarComp>& vec, float val)
+{
+	bool	isFloatCase	= glu::isDataTypeFloatOrVec(m_rgbAttrType);
+	bool	isIntCase	= glu::isDataTypeIntOrIVec(m_rgbAttrType);
+	bool	isUintCase	= glu::isDataTypeUintOrUVec(m_rgbAttrType);
+	bool	isMatCase	= glu::isDataTypeMatrix(m_rgbAttrType);
+
+	if (isFloatCase || isMatCase)
+		vec.push_back(VarComp(val));
+	else if (isIntCase)
+		vec.push_back(VarComp((deInt32)(val*FLOAT_INT_SCALE + FLOAT_INT_BIAS)));
+	else if (isUintCase)
+		vec.push_back(VarComp((deUint32)(val*FLOAT_UINT_SCALE + FLOAT_UINT_BIAS)));
+	else
+		DE_ASSERT(DE_FALSE);
+}
+
+void InstancedRenderingCase::init (void)
+{
+	bool	isFloatCase			= glu::isDataTypeFloatOrVec(m_rgbAttrType);
+	bool	isIntCase			= glu::isDataTypeIntOrIVec(m_rgbAttrType);
+	bool	isUintCase			= glu::isDataTypeUintOrUVec(m_rgbAttrType);
+	bool	isMatCase			= glu::isDataTypeMatrix(m_rgbAttrType);
+	int		typeSize			= glu::getDataTypeScalarSize(m_rgbAttrType);
+	bool	isScalarCase		= typeSize == 1;
+	string	swizzleFirst		= isScalarCase ? "" : ".x";
+	string	typeName			= glu::getDataTypeName(m_rgbAttrType);
+
+	string	floatIntScaleStr	= "(" + de::floatToString(FLOAT_INT_SCALE, 3) + ")";
+	string	floatIntBiasStr		= "(" + de::floatToString(FLOAT_INT_BIAS, 3) + ")";
+	string	floatUintScaleStr	= "(" + de::floatToString(FLOAT_UINT_SCALE, 3) + ")";
+	string	floatUintBiasStr	= "(" + de::floatToString(FLOAT_UINT_BIAS, 3) + ")";
+
+	DE_ASSERT(isFloatCase || isIntCase || isUintCase || isMatCase);
+
+	// Generate shader.
+	// \note For case TYPE_MIXED, vertex position offset and color red component get their values from instance id, while green and blue get their values from instanced attributes.
+
+	string numInstancesStr = de::toString(m_numInstances) + ".0";
+
+	string instanceAttribs;
+	string posExpression;
+	string colorRExpression;
+	string colorGExpression;
+	string colorBExpression;
+
+	if (m_instancingType == TYPE_INSTANCE_ID || m_instancingType == TYPE_MIXED)
+	{
+		posExpression = "a_position + vec4(float(gl_InstanceID) * 2.0 / " + numInstancesStr + ", 0.0, 0.0, 0.0)";
+		colorRExpression = "float(gl_InstanceID)/" + numInstancesStr;
+
+		if (m_instancingType == TYPE_INSTANCE_ID)
+		{
+			colorGExpression = "float(gl_InstanceID)*2.0/" + numInstancesStr;
+			colorBExpression = "1.0 - float(gl_InstanceID)/" + numInstancesStr;
+		}
+	}
+
+	if (m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED)
+	{
+		if (m_instancingType == TYPE_ATTRIB_DIVISOR)
+		{
+			posExpression = "a_position + vec4(a_instanceOffset";
+
+			DE_STATIC_ASSERT(OFFSET_COMPONENTS >= 1 && OFFSET_COMPONENTS <= 4);
+
+			for (int i = 0; i < 4-OFFSET_COMPONENTS; i++)
+				posExpression += ", 0.0";
+			posExpression += ")";
+
+			if (isFloatCase)
+				colorRExpression = "a_instanceR" + swizzleFirst;
+			else if (isIntCase)
+				colorRExpression = "(float(a_instanceR" + swizzleFirst + ") - " + floatIntBiasStr + ") / " + floatIntScaleStr;
+			else if (isUintCase)
+				colorRExpression = "(float(a_instanceR" + swizzleFirst + ") - " + floatUintBiasStr + ") / " + floatUintScaleStr;
+			else if (isMatCase)
+				colorRExpression = "a_instanceR[0][0]";
+			else
+				DE_ASSERT(DE_FALSE);
+
+			instanceAttribs += "in highp " + (OFFSET_COMPONENTS == 1 ? string("float") : "vec" + de::toString(OFFSET_COMPONENTS)) + " a_instanceOffset;\n";
+			instanceAttribs += "in mediump " + typeName + " a_instanceR;\n";
+		}
+
+		if (isFloatCase)
+		{
+			colorGExpression = "a_instanceG" + swizzleFirst;
+			colorBExpression = "a_instanceB" + swizzleFirst;
+		}
+		else if (isIntCase)
+		{
+			colorGExpression = "(float(a_instanceG" + swizzleFirst + ") - " + floatIntBiasStr + ") / " + floatIntScaleStr;
+			colorBExpression = "(float(a_instanceB" + swizzleFirst + ") - " + floatIntBiasStr + ") / " + floatIntScaleStr;
+		}
+		else if (isUintCase)
+		{
+			colorGExpression = "(float(a_instanceG" + swizzleFirst + ") - " + floatUintBiasStr + ") / " + floatUintScaleStr;
+			colorBExpression = "(float(a_instanceB" + swizzleFirst + ") - " + floatUintBiasStr + ") / " + floatUintScaleStr;
+		}
+		else if (isMatCase)
+		{
+			colorGExpression = "a_instanceG[0][0]";
+			colorBExpression = "a_instanceB[0][0]";
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		instanceAttribs += "in mediump " + typeName + " a_instanceG;\n";
+		instanceAttribs += "in mediump " + typeName + " a_instanceB;\n";
+	}
+
+	DE_ASSERT(!posExpression.empty());
+	DE_ASSERT(!colorRExpression.empty());
+	DE_ASSERT(!colorGExpression.empty());
+	DE_ASSERT(!colorBExpression.empty());
+
+	std::string vertShaderSourceStr =
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n" +
+		instanceAttribs +
+		"out mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = " + posExpression + ";\n"
+		"	v_color.r = " + colorRExpression + ";\n"
+		"	v_color.g = " + colorGExpression + ";\n"
+		"	v_color.b = " + colorBExpression + ";\n"
+		"	v_color.a = 1.0;\n"
+		"}\n";
+
+	static const char* fragShaderSource =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"in mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	o_color = v_color;\n"
+		"}\n";
+
+	// Create shader program and log it.
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSourceStr, fragShaderSource));
+
+	tcu::TestLog& log = m_testCtx.getLog();
+
+	log << *m_program;
+
+	if(!m_program->isOk())
+		TCU_FAIL("Failed to compile shader");
+
+	// Vertex shader attributes.
+
+	if (m_function == FUNCTION_DRAW_ELEMENTS_INSTANCED)
+	{
+		// Vertex positions. Positions form a vertical bar of width <screen width>/<number of instances>.
+
+		for (int y = 0; y < QUAD_GRID_SIZE + 1; y++)
+			for (int x = 0; x < QUAD_GRID_SIZE + 1; x++)
+			{
+				float fx = -1.0f + (float)x / (float)QUAD_GRID_SIZE * 2.0f / (float)m_numInstances;
+				float fy = -1.0f + (float)y / (float)QUAD_GRID_SIZE * 2.0f;
+
+				m_gridVertexPositions.push_back(fx);
+				m_gridVertexPositions.push_back(fy);
+			}
+
+		// Indices.
+
+		for (int y = 0; y < QUAD_GRID_SIZE; y++)
+			for (int x = 0; x < QUAD_GRID_SIZE; x++)
+			{
+				int ndx00 = y*(QUAD_GRID_SIZE + 1) + x;
+				int ndx10 = y*(QUAD_GRID_SIZE + 1) + x + 1;
+				int ndx01 = (y + 1)*(QUAD_GRID_SIZE + 1) + x;
+				int ndx11 = (y + 1)*(QUAD_GRID_SIZE + 1) + x + 1;
+
+				// Lower-left triangle of a quad.
+				m_gridIndices.push_back(ndx00);
+				m_gridIndices.push_back(ndx10);
+				m_gridIndices.push_back(ndx01);
+
+				// Upper-right triangle of a quad.
+				m_gridIndices.push_back(ndx11);
+				m_gridIndices.push_back(ndx01);
+				m_gridIndices.push_back(ndx10);
+			}
+	}
+	else
+	{
+		DE_ASSERT(m_function == FUNCTION_DRAW_ARRAYS_INSTANCED);
+
+		// Vertex positions. Positions form a vertical bar of width <screen width>/<number of instances>.
+
+		for (int y = 0; y < QUAD_GRID_SIZE; y++)
+			for (int x = 0; x < QUAD_GRID_SIZE; x++)
+			{
+				float fx0 = -1.0f + (float)(x+0) / (float)QUAD_GRID_SIZE * 2.0f / (float)m_numInstances;
+				float fx1 = -1.0f + (float)(x+1) / (float)QUAD_GRID_SIZE * 2.0f / (float)m_numInstances;
+				float fy0 = -1.0f + (float)(y+0) / (float)QUAD_GRID_SIZE * 2.0f;
+				float fy1 = -1.0f + (float)(y+1) / (float)QUAD_GRID_SIZE * 2.0f;
+
+				// Vertices of a quad's lower-left triangle: (fx0, fy0), (fx1, fy0) and (fx0, fy1)
+				m_gridVertexPositions.push_back(fx0);
+				m_gridVertexPositions.push_back(fy0);
+				m_gridVertexPositions.push_back(fx1);
+				m_gridVertexPositions.push_back(fy0);
+				m_gridVertexPositions.push_back(fx0);
+				m_gridVertexPositions.push_back(fy1);
+
+				// Vertices of a quad's upper-right triangle: (fx1, fy1), (fx0, fy1) and (fx1, fy0)
+				m_gridVertexPositions.push_back(fx1);
+				m_gridVertexPositions.push_back(fy1);
+				m_gridVertexPositions.push_back(fx0);
+				m_gridVertexPositions.push_back(fy1);
+				m_gridVertexPositions.push_back(fx1);
+				m_gridVertexPositions.push_back(fy0);
+			}
+	}
+
+	// Instanced attributes: position offset and color RGB components.
+
+	if (m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED)
+	{
+		if (m_instancingType == TYPE_ATTRIB_DIVISOR)
+		{
+			// Offsets are such that the vertical bars are drawn next to each other.
+			for (int i = 0; i < m_numInstances; i++)
+			{
+				m_instanceOffsets.push_back((float)i * 2.0f / (float)m_numInstances);
+
+				DE_STATIC_ASSERT(OFFSET_COMPONENTS >= 1 && OFFSET_COMPONENTS <= 4);
+
+				for (int j = 0; j < OFFSET_COMPONENTS-1; j++)
+					m_instanceOffsets.push_back(0.0f);
+			}
+
+			int rInstances = m_numInstances / ATTRIB_DIVISOR_R + (m_numInstances % ATTRIB_DIVISOR_R == 0 ? 0 : 1);
+			for (int i = 0; i < rInstances; i++)
+			{
+				pushVarCompAttrib(m_instanceColorR, (float)i / (float)rInstances);
+
+				for (int j = 0; j < typeSize - 1; j++)
+					pushVarCompAttrib(m_instanceColorR, 0.0f);
+			}
+		}
+
+		int gInstances = m_numInstances / ATTRIB_DIVISOR_G + (m_numInstances % ATTRIB_DIVISOR_G == 0 ? 0 : 1);
+		for (int i = 0; i < gInstances; i++)
+		{
+			pushVarCompAttrib(m_instanceColorG, (float)i*2.0f / (float)gInstances);
+
+			for (int j = 0; j < typeSize - 1; j++)
+				pushVarCompAttrib(m_instanceColorG, 0.0f);
+		}
+
+		int bInstances = m_numInstances / ATTRIB_DIVISOR_B + (m_numInstances % ATTRIB_DIVISOR_B == 0 ? 0 : 1);
+		for (int i = 0; i < bInstances; i++)
+		{
+			pushVarCompAttrib(m_instanceColorB, 1.0f - (float)i / (float)bInstances);
+
+			for (int j = 0; j < typeSize - 1; j++)
+				pushVarCompAttrib(m_instanceColorB, 0.0f);
+		}
+	}
+}
+
+void InstancedRenderingCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+InstancedRenderingCase::IterateResult InstancedRenderingCase::iterate (void)
+{
+	int							width			= deMin32(m_context.getRenderTarget().getWidth(), MAX_RENDER_WIDTH);
+	int							height			= deMin32(m_context.getRenderTarget().getHeight(), MAX_RENDER_HEIGHT);
+
+	int							xOffsetMax		= m_context.getRenderTarget().getWidth() - width;
+	int							yOffsetMax		= m_context.getRenderTarget().getHeight() - height;
+
+	de::Random					rnd				(deStringHash(getName()));
+
+	int							xOffset			= rnd.getInt(0, xOffsetMax);
+	int							yOffset			= rnd.getInt(0, yOffsetMax);
+	tcu::Surface				referenceImg	(width, height);
+	tcu::Surface				resultImg		(width, height);
+
+	// Draw result.
+
+	glViewport(xOffset, yOffset, width, height);
+
+	setupAndRender();
+
+	glu::readPixels(m_context.getRenderContext(), xOffset, yOffset, resultImg.getAccess());
+
+	// Compute reference.
+
+	computeReference(referenceImg);
+
+	// Compare.
+
+	bool testOk = tcu::fuzzyCompare(m_testCtx.getLog(), "ComparisonResult", "Image comparison result", referenceImg, resultImg, 0.05f, tcu::COMPARE_LOG_RESULT);
+
+	m_testCtx.setTestResult(testOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							testOk ? "Pass"					: "Fail");
+
+	return STOP;
+}
+
+void InstancedRenderingCase::setupVarAttribPointer (const void* attrPtr, int location, int divisor)
+{
+	bool	isFloatCase		= glu::isDataTypeFloatOrVec(m_rgbAttrType);
+	bool	isIntCase		= glu::isDataTypeIntOrIVec(m_rgbAttrType);
+	bool	isUintCase		= glu::isDataTypeUintOrUVec(m_rgbAttrType);
+	bool	isMatCase		= glu::isDataTypeMatrix(m_rgbAttrType);
+	int		typeSize		= glu::getDataTypeScalarSize(m_rgbAttrType);
+	int		numSlots		= isMatCase ? glu::getDataTypeMatrixNumColumns(m_rgbAttrType) : 1; // Matrix uses as many attribute slots as it has columns.
+
+	for (int slotNdx = 0; slotNdx < numSlots; slotNdx++)
+	{
+		int curLoc = location + slotNdx;
+
+		glEnableVertexAttribArray(curLoc);
+		glVertexAttribDivisor(curLoc, divisor);
+
+		if (isFloatCase)
+			glVertexAttribPointer(curLoc, typeSize, GL_FLOAT, GL_FALSE, 0, attrPtr);
+		else if (isIntCase)
+			glVertexAttribIPointer(curLoc, typeSize, GL_INT, 0, attrPtr);
+		else if (isUintCase)
+			glVertexAttribIPointer(curLoc, typeSize, GL_UNSIGNED_INT, 0, attrPtr);
+		else if (isMatCase)
+		{
+			int numRows = glu::getDataTypeMatrixNumRows(m_rgbAttrType);
+			int numCols = glu::getDataTypeMatrixNumColumns(m_rgbAttrType);
+
+			glVertexAttribPointer(curLoc, numRows, GL_FLOAT, GL_FALSE, numCols*numRows*sizeof(float), attrPtr);
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+void InstancedRenderingCase::setupAndRender (void)
+{
+	deUint32 program = m_program->getProgram();
+
+	glUseProgram(program);
+
+	{
+		// Setup attributes.
+
+		// Position attribute is non-instanced.
+		int positionLoc = glGetAttribLocation(program, "a_position");
+		glEnableVertexAttribArray(positionLoc);
+		glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, &m_gridVertexPositions[0]);
+
+		if (m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED)
+		{
+			if (m_instancingType == TYPE_ATTRIB_DIVISOR)
+			{
+				// Position offset attribute is instanced with separate offset for every instance.
+				int offsetLoc = glGetAttribLocation(program, "a_instanceOffset");
+				glEnableVertexAttribArray(offsetLoc);
+				glVertexAttribDivisor(offsetLoc, 1);
+				glVertexAttribPointer(offsetLoc, OFFSET_COMPONENTS, GL_FLOAT, GL_FALSE, 0, &m_instanceOffsets[0]);
+
+				int rLoc = glGetAttribLocation(program, "a_instanceR");
+				setupVarAttribPointer((void*)&m_instanceColorR[0].u32, rLoc, ATTRIB_DIVISOR_R);
+			}
+
+			int gLoc = glGetAttribLocation(program, "a_instanceG");
+			setupVarAttribPointer((void*)&m_instanceColorG[0].u32, gLoc, ATTRIB_DIVISOR_G);
+
+			int bLoc = glGetAttribLocation(program, "a_instanceB");
+			setupVarAttribPointer((void*)&m_instanceColorB[0].u32, bLoc, ATTRIB_DIVISOR_B);
+		}
+	}
+
+	// Draw using appropriate function.
+
+	if (m_function == FUNCTION_DRAW_ARRAYS_INSTANCED)
+	{
+		const int numPositionComponents = 2;
+		glDrawArraysInstanced(GL_TRIANGLES, 0, ((int)m_gridVertexPositions.size() / numPositionComponents), m_numInstances);
+	}
+	else
+		glDrawElementsInstanced(GL_TRIANGLES, (int)m_gridIndices.size(), GL_UNSIGNED_SHORT, &m_gridIndices[0], m_numInstances);
+
+	glUseProgram(0);
+}
+
+void InstancedRenderingCase::computeReference (tcu::Surface& dst)
+{
+	int wid = dst.getWidth();
+	int hei = dst.getHeight();
+
+	// Draw a rectangle (vertical bar) for each instance.
+
+	for (int instanceNdx = 0; instanceNdx < m_numInstances; instanceNdx++)
+	{
+		int xStart		= instanceNdx * wid / m_numInstances;
+		int xEnd		= (instanceNdx + 1) * wid / m_numInstances;
+
+		// Emulate attribute divisors if that is the case.
+
+		int clrNdxR		= m_instancingType == TYPE_ATTRIB_DIVISOR									? instanceNdx / ATTRIB_DIVISOR_R : instanceNdx;
+		int clrNdxG		= m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED	? instanceNdx / ATTRIB_DIVISOR_G : instanceNdx;
+		int clrNdxB		= m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED	? instanceNdx / ATTRIB_DIVISOR_B : instanceNdx;
+
+		int rInstances	= m_instancingType == TYPE_ATTRIB_DIVISOR									? m_numInstances / ATTRIB_DIVISOR_R + (m_numInstances % ATTRIB_DIVISOR_R == 0 ? 0 : 1) : m_numInstances;
+		int gInstances	= m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED	? m_numInstances / ATTRIB_DIVISOR_G + (m_numInstances % ATTRIB_DIVISOR_G == 0 ? 0 : 1) : m_numInstances;
+		int bInstances	= m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED	? m_numInstances / ATTRIB_DIVISOR_B + (m_numInstances % ATTRIB_DIVISOR_B == 0 ? 0 : 1) : m_numInstances;
+
+		// Calculate colors.
+
+		float r = (float)clrNdxR / (float)rInstances;
+		float g = (float)clrNdxG * 2.0f / (float)gInstances;
+		float b = 1.0f - (float)clrNdxB / (float)bInstances;
+
+		// Convert to integer and back if shader inputs are integers.
+
+		if (glu::isDataTypeIntOrIVec(m_rgbAttrType))
+		{
+			deInt32 intR = (deInt32)(r*FLOAT_INT_SCALE + FLOAT_INT_BIAS);
+			deInt32 intG = (deInt32)(g*FLOAT_INT_SCALE + FLOAT_INT_BIAS);
+			deInt32 intB = (deInt32)(b*FLOAT_INT_SCALE + FLOAT_INT_BIAS);
+			r = (float)(intR - FLOAT_INT_BIAS) / FLOAT_INT_SCALE;
+			g = (float)(intG - FLOAT_INT_BIAS) / FLOAT_INT_SCALE;
+			b = (float)(intB - FLOAT_INT_BIAS) / FLOAT_INT_SCALE;
+		}
+		else if(glu::isDataTypeUintOrUVec(m_rgbAttrType))
+		{
+			deUint32 uintR = (deInt32)(r*FLOAT_UINT_SCALE + FLOAT_UINT_BIAS);
+			deUint32 uintG = (deInt32)(g*FLOAT_UINT_SCALE + FLOAT_UINT_BIAS);
+			deUint32 uintB = (deInt32)(b*FLOAT_UINT_SCALE + FLOAT_UINT_BIAS);
+			r = (float)(uintR - FLOAT_UINT_BIAS) / FLOAT_UINT_SCALE;
+			g = (float)(uintG - FLOAT_UINT_BIAS) / FLOAT_UINT_SCALE;
+			b = (float)(uintB - FLOAT_UINT_BIAS) / FLOAT_UINT_SCALE;
+		}
+
+		// Draw rectangle.
+
+		for (int y = 0; y < hei; y++)
+			for (int x = xStart; x < xEnd; x++)
+				dst.setPixel(x, y, tcu::RGBA(tcu::Vec4(r, g, b, 1.0f)));
+	}
+}
+
+InstancedRenderingTests::InstancedRenderingTests (Context& context)
+	: TestCaseGroup(context, "instanced", "Instanced rendering tests")
+{
+}
+
+InstancedRenderingTests::~InstancedRenderingTests (void)
+{
+}
+
+void InstancedRenderingTests::init (void)
+{
+	// Cases testing function, instancing method and instance count.
+
+	static const int instanceCounts[] = { 1, 2, 4, 20 };
+
+	for (int function = 0; function < (int)InstancedRenderingCase::FUNCTION_LAST; function++)
+	{
+		const char* functionName = function == (int)InstancedRenderingCase::FUNCTION_DRAW_ARRAYS_INSTANCED		? "draw_arrays_instanced"
+								 : function == (int)InstancedRenderingCase::FUNCTION_DRAW_ELEMENTS_INSTANCED	? "draw_elements_instanced"
+								 : DE_NULL;
+
+		const char* functionDesc = function == (int)InstancedRenderingCase::FUNCTION_DRAW_ARRAYS_INSTANCED		? "Use glDrawArraysInstanced()"
+								 : function == (int)InstancedRenderingCase::FUNCTION_DRAW_ELEMENTS_INSTANCED	? "Use glDrawElementsInstanced()"
+								 : DE_NULL;
+
+		DE_ASSERT(functionName != DE_NULL);
+		DE_ASSERT(functionDesc != DE_NULL);
+
+		TestCaseGroup* functionGroup = new TestCaseGroup(m_context, functionName, functionDesc);
+		addChild(functionGroup);
+
+		for (int instancingType = 0; instancingType < (int)InstancedRenderingCase::TYPE_LAST; instancingType++)
+		{
+			const char* instancingTypeName = instancingType == (int)InstancedRenderingCase::TYPE_INSTANCE_ID	? "instance_id"
+										   : instancingType == (int)InstancedRenderingCase::TYPE_ATTRIB_DIVISOR	? "attribute_divisor"
+										   : instancingType == (int)InstancedRenderingCase::TYPE_MIXED			? "mixed"
+										   : DE_NULL;
+
+			const char* instancingTypeDesc = instancingType == (int)InstancedRenderingCase::TYPE_INSTANCE_ID	? "Use gl_InstanceID for instancing"
+										   : instancingType == (int)InstancedRenderingCase::TYPE_ATTRIB_DIVISOR	? "Use vertex attribute divisors for instancing"
+										   : instancingType == (int)InstancedRenderingCase::TYPE_MIXED			? "Use both gl_InstanceID and vertex attribute divisors for instancing"
+										   : DE_NULL;
+
+			DE_ASSERT(instancingTypeName != DE_NULL);
+			DE_ASSERT(instancingTypeDesc != DE_NULL);
+
+			TestCaseGroup* instancingTypeGroup = new TestCaseGroup(m_context, instancingTypeName, instancingTypeDesc);
+			functionGroup->addChild(instancingTypeGroup);
+
+			for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(instanceCounts); countNdx++)
+			{
+				std::string countName = de::toString(instanceCounts[countNdx]) + "_instances";
+
+				instancingTypeGroup->addChild(new InstancedRenderingCase(m_context, countName.c_str(), "",
+																		 (InstancedRenderingCase::DrawFunction)function,
+																		 (InstancedRenderingCase::InstancingType)instancingType,
+																		 glu::TYPE_FLOAT,
+																		 instanceCounts[countNdx]));
+			}
+		}
+	}
+
+	// Data type specific cases.
+
+	static const glu::DataType s_testTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_FLOAT_VEC2,
+		glu::TYPE_FLOAT_VEC3,
+		glu::TYPE_FLOAT_VEC4,
+		glu::TYPE_FLOAT_MAT2,
+		glu::TYPE_FLOAT_MAT2X3,
+		glu::TYPE_FLOAT_MAT2X4,
+		glu::TYPE_FLOAT_MAT3X2,
+		glu::TYPE_FLOAT_MAT3,
+		glu::TYPE_FLOAT_MAT3X4,
+		glu::TYPE_FLOAT_MAT4X2,
+		glu::TYPE_FLOAT_MAT4X3,
+		glu::TYPE_FLOAT_MAT4,
+
+		glu::TYPE_INT,
+		glu::TYPE_INT_VEC2,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_INT_VEC4,
+
+		glu::TYPE_UINT,
+		glu::TYPE_UINT_VEC2,
+		glu::TYPE_UINT_VEC3,
+		glu::TYPE_UINT_VEC4
+	};
+
+	const int typeTestNumInstances = 4;
+
+	TestCaseGroup* typesGroup = new TestCaseGroup(m_context, "types", "Tests for instanced attributes of particular data types");
+	addChild(typesGroup);
+
+	for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_testTypes); typeNdx++)
+	{
+		glu::DataType type = s_testTypes[typeNdx];
+
+		typesGroup->addChild(new InstancedRenderingCase(m_context, glu::getDataTypeName(type), "",
+														InstancedRenderingCase::FUNCTION_DRAW_ARRAYS_INSTANCED,
+														InstancedRenderingCase::TYPE_ATTRIB_DIVISOR,
+														type,
+														typeTestNumInstances));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fInstancedRenderingTests.hpp b/modules/gles3/functional/es3fInstancedRenderingTests.hpp
new file mode 100644
index 0000000..307ca8c
--- /dev/null
+++ b/modules/gles3/functional/es3fInstancedRenderingTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FINSTANCEDRENDERINGTESTS_HPP
+#define _ES3FINSTANCEDRENDERINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Instanced rendering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class InstancedRenderingTests : public TestCaseGroup
+{
+public:
+								InstancedRenderingTests		(Context& context);
+								~InstancedRenderingTests	(void);
+
+	void						init						(void);
+
+private:
+								InstancedRenderingTests		(const InstancedRenderingTests& other);
+	InstancedRenderingTests&	operator=					(const InstancedRenderingTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FINSTANCEDRENDERINGTESTS_HPP
diff --git a/modules/gles3/functional/es3fInteger64StateQueryTests.cpp b/modules/gles3/functional/es3fInteger64StateQueryTests.cpp
new file mode 100644
index 0000000..1ad90d9
--- /dev/null
+++ b/modules/gles3/functional/es3fInteger64StateQueryTests.cpp
@@ -0,0 +1,335 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Integer64 State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fInteger64StateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "tcuRenderTarget.hpp"
+#include "glwEnums.hpp"
+
+#include <limits>
+
+using namespace glw; // GLint and other
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace Integer64StateQueryVerifiers
+{
+
+// StateVerifier
+
+class StateVerifier : protected glu::CallLogWrapper
+{
+public:
+						StateVerifier							(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~StateVerifier							(); // make GCC happy
+
+	const char*			getTestNamePostfix						(void) const;
+
+	virtual void		verifyUnsignedInteger64GreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint64 reference) = DE_NULL;
+
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+StateVerifier::StateVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+
+StateVerifier::~StateVerifier ()
+{
+}
+
+const char* StateVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+// GetBooleanVerifier
+
+class GetBooleanVerifier : public StateVerifier
+{
+public:
+			GetBooleanVerifier				(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyUnsignedInteger64GreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint64 reference);
+};
+
+GetBooleanVerifier::GetBooleanVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getboolean")
+{
+}
+
+void GetBooleanVerifier::verifyUnsignedInteger64GreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint64 reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state == GL_TRUE) // state is non-zero, could be greater than reference (correct)
+		return;
+
+	if (state == GL_FALSE) // state is zero
+	{
+		if (reference > 0) // and reference is greater than zero?
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+		}
+	}
+	else
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE or GL_FALSE" << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+//GetIntegerVerifier
+
+class GetIntegerVerifier : public StateVerifier
+{
+public:
+			GetIntegerVerifier				(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyUnsignedInteger64GreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint64 reference);
+};
+
+GetIntegerVerifier::GetIntegerVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger")
+{
+}
+
+void GetIntegerVerifier::verifyUnsignedInteger64GreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint64 reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	// check that the converted value would be in the correct range, otherwise checking wont tell us anything
+	if (reference > (GLuint64)std::numeric_limits<GLint>::max())
+		return;
+
+	if (GLuint(state) < reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+//GetFloatVerifier
+
+class GetFloatVerifier : public StateVerifier
+{
+public:
+			GetFloatVerifier					(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyUnsignedInteger64GreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint64 reference);
+};
+
+GetFloatVerifier::GetFloatVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getfloat")
+{
+}
+
+void GetFloatVerifier::verifyUnsignedInteger64GreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint64 reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < GLfloat(reference))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLfloat(reference) << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+} // Integer64StateQueryVerifiers
+
+namespace
+{
+
+using namespace Integer64StateQueryVerifiers;
+
+class ConstantMinimumValue64TestCase : public ApiCase
+{
+public:
+	ConstantMinimumValue64TestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName, GLuint64 minValue)
+		: ApiCase		(context, name, description)
+		, m_targetName	(targetName)
+		, m_minValue	(minValue)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyUnsignedInteger64GreaterOrEqual(m_testCtx, m_targetName, m_minValue);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	GLenum			m_targetName;
+	GLuint64		m_minValue;
+	StateVerifier*	m_verifier;
+};
+
+class MaxCombinedStageUniformComponentsCase : public ApiCase
+{
+public:
+	MaxCombinedStageUniformComponentsCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName, GLenum targetMaxUniformBlocksName, GLenum targetMaxUniformComponentsName)
+		: ApiCase							(context, name, description)
+		, m_targetName						(targetName)
+		, m_targetMaxUniformBlocksName		(targetMaxUniformBlocksName)
+		, m_targetMaxUniformComponentsName	(targetMaxUniformComponentsName)
+		, m_verifier						(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		GLint uniformBlockSize = 0;
+		glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &uniformBlockSize);
+		expectError(GL_NO_ERROR);
+
+		GLint maxUniformBlocks = 0;
+		GLint maxUniformComponents = 0;
+		glGetIntegerv(m_targetMaxUniformBlocksName, &maxUniformBlocks);
+		glGetIntegerv(m_targetMaxUniformComponentsName, &maxUniformComponents);
+		expectError(GL_NO_ERROR);
+
+		// MAX_stage_UNIFORM_BLOCKS * MAX_UNIFORM_BLOCK_SIZE / 4 + MAX_stage_UNIFORM_COMPONENTS
+		const GLuint64 minCombinedUniformComponents = GLuint64(maxUniformBlocks) * uniformBlockSize / 4 + maxUniformComponents;
+
+		m_verifier->verifyUnsignedInteger64GreaterOrEqual(m_testCtx, m_targetName, minCombinedUniformComponents);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	GLenum			m_targetName;
+	GLenum			m_targetMaxUniformBlocksName;
+	GLenum			m_targetMaxUniformComponentsName;
+	StateVerifier*	m_verifier;
+};
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		StateVerifier* verifier = VERIFIERS[_verifierNdx];										\
+		CODE_BLOCK;																				\
+	}
+
+} // anonymous
+
+Integer64StateQueryTests::Integer64StateQueryTests (Context& context)
+	: TestCaseGroup			(context, "integers64", "Integer (64) Values")
+	, m_verifierBoolean		(DE_NULL)
+	, m_verifierInteger		(DE_NULL)
+	, m_verifierFloat		(DE_NULL)
+{
+}
+
+Integer64StateQueryTests::~Integer64StateQueryTests (void)
+{
+	deinit();
+}
+
+void Integer64StateQueryTests::init (void)
+{
+	DE_ASSERT(m_verifierBoolean == DE_NULL);
+	DE_ASSERT(m_verifierInteger == DE_NULL);
+	DE_ASSERT(m_verifierFloat == DE_NULL);
+
+	m_verifierBoolean		= new GetBooleanVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInteger		= new GetIntegerVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierFloat			= new GetFloatVerifier			(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+
+	const struct LimitedStateInteger64
+	{
+		const char*		name;
+		const char*		description;
+		GLenum			targetName;
+		GLuint64		minValue;
+	} implementationLimits[] =
+	{
+		{ "max_element_index",			"MAX_ELEMENT_INDEX",		GL_MAX_ELEMENT_INDEX,			0x00FFFFFF /*2^24-1*/	},
+		{ "max_server_wait_timeout",	"MAX_SERVER_WAIT_TIMEOUT",	GL_MAX_SERVER_WAIT_TIMEOUT,		0						},
+		{ "max_uniform_block_size",		"MAX_UNIFORM_BLOCK_SIZE",	GL_MAX_UNIFORM_BLOCK_SIZE,		16384					}
+	};
+
+	// \note do not check the values with integer64 verifier as that has already been checked in implementation_limits
+	StateVerifier* verifiers[] = {m_verifierBoolean, m_verifierInteger, m_verifierFloat};
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(implementationLimits); testNdx++)
+		FOR_EACH_VERIFIER(verifiers, addChild(new ConstantMinimumValue64TestCase(m_context, verifier, (std::string(implementationLimits[testNdx].name) + verifier->getTestNamePostfix()).c_str(), implementationLimits[testNdx].description, implementationLimits[testNdx].targetName, implementationLimits[testNdx].minValue)));
+
+	FOR_EACH_VERIFIER(verifiers, addChild(new MaxCombinedStageUniformComponentsCase (m_context, verifier,	(std::string("max_combined_vertex_uniform_components")		+ verifier->getTestNamePostfix()).c_str(),	"MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS",	GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS,		GL_MAX_VERTEX_UNIFORM_BLOCKS,		GL_MAX_VERTEX_UNIFORM_COMPONENTS)));
+	FOR_EACH_VERIFIER(verifiers, addChild(new MaxCombinedStageUniformComponentsCase (m_context, verifier,	(std::string("max_combined_fragment_uniform_components")	+ verifier->getTestNamePostfix()).c_str(),	"MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS",	GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS,	GL_MAX_FRAGMENT_UNIFORM_BLOCKS,		GL_MAX_FRAGMENT_UNIFORM_COMPONENTS)));
+}
+
+void Integer64StateQueryTests::deinit (void)
+{
+	if (m_verifierBoolean)
+	{
+		delete m_verifierBoolean;
+		m_verifierBoolean = DE_NULL;
+	}
+	if (m_verifierInteger)
+	{
+		delete m_verifierInteger;
+		m_verifierInteger = DE_NULL;
+	}
+	if (m_verifierFloat)
+	{
+		delete m_verifierFloat;
+		m_verifierFloat = DE_NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fInteger64StateQueryTests.hpp b/modules/gles3/functional/es3fInteger64StateQueryTests.hpp
new file mode 100644
index 0000000..cfe7ea8
--- /dev/null
+++ b/modules/gles3/functional/es3fInteger64StateQueryTests.hpp
@@ -0,0 +1,66 @@
+#ifndef _ES3FINTEGER64STATEQUERYTESTS_HPP
+#define _ES3FINTEGER64STATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Integer64 State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace Integer64StateQueryVerifiers
+{
+
+class GetBooleanVerifier;
+class GetIntegerVerifier;
+class GetFloatVerifier;
+
+} // Integer64StateQueryVerifiers
+
+class Integer64StateQueryTests : public TestCaseGroup
+{
+public:
+																Integer64StateQueryTests			(Context& context);
+																~Integer64StateQueryTests			(void);
+
+	void														init								(void);
+	void														deinit								(void);
+
+private:
+																Integer64StateQueryTests			(const Integer64StateQueryTests& other);
+	Integer64StateQueryTests&									operator=							(const Integer64StateQueryTests& other);
+
+	Integer64StateQueryVerifiers::GetBooleanVerifier*			m_verifierBoolean;
+	Integer64StateQueryVerifiers::GetIntegerVerifier*			m_verifierInteger;
+	Integer64StateQueryVerifiers::GetFloatVerifier*				m_verifierFloat;
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FINTEGER64STATEQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fIntegerStateQueryTests.cpp b/modules/gles3/functional/es3fIntegerStateQueryTests.cpp
new file mode 100644
index 0000000..93bb961
--- /dev/null
+++ b/modules/gles3/functional/es3fIntegerStateQueryTests.cpp
@@ -0,0 +1,2997 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fIntegerStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "gluContextInfo.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+#ifndef GL_SLUMINANCE_NV
+#define	GL_SLUMINANCE_NV 0x8C46
+#endif
+#ifndef GL_SLUMINANCE_ALPHA_NV
+#define	GL_SLUMINANCE_ALPHA_NV 0x8C44
+#endif
+#ifndef GL_BGR_NV
+#define GL_BGR_NV 0x80E0
+#endif
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace IntegerStateQueryVerifiers
+{
+
+// StateVerifier
+
+class StateVerifier : protected glu::CallLogWrapper
+{
+public:
+						StateVerifier						(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~StateVerifier						(); // make GCC happy
+
+	const char*			getTestNamePostfix					(void) const;
+
+	virtual void		verifyInteger						(tcu::TestContext& testCtx, GLenum name, GLint reference)																																		= DE_NULL;
+	virtual void		verifyInteger4						(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3)																				= DE_NULL;
+	virtual void		verifyInteger4Mask					(tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3)			= DE_NULL;
+	virtual void		verifyIntegerGreaterOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference)																																		= DE_NULL;
+	virtual void		verifyUnsignedIntegerGreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint reference)																																		= DE_NULL;
+	virtual void		verifyIntegerLessOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference)																																		= DE_NULL;
+	virtual void		verifyIntegerGreaterOrEqual2		(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1)																													= DE_NULL;
+	virtual void		verifyIntegerAnyOf					(tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength)																											= DE_NULL;
+	virtual void		verifyStencilMaskInitial			(tcu::TestContext& testCtx, GLenum name, int stencilBits)																																		= DE_NULL;
+
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+StateVerifier::StateVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+
+StateVerifier::~StateVerifier ()
+{
+}
+
+const char* StateVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+// GetBooleanVerifier
+
+class GetBooleanVerifier : public StateVerifier
+{
+public:
+			GetBooleanVerifier			(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyInteger						(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyInteger4						(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3);
+	void	verifyInteger4Mask					(tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3);
+	void	verifyIntegerGreaterOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyUnsignedIntegerGreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint reference);
+	void	verifyIntegerLessOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyIntegerGreaterOrEqual2		(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1);
+	void	verifyIntegerAnyOf					(tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength);
+	void	verifyStencilMaskInitial			(tcu::TestContext& testCtx, GLenum name, int stencilBits);
+};
+
+GetBooleanVerifier::GetBooleanVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getboolean")
+{
+}
+
+void GetBooleanVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLboolean expectedGLState = reference ? GL_TRUE : GL_FALSE;
+
+	if (state != expectedGLState)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << (expectedGLState==GL_TRUE ? "GL_TRUE" : "GL_FALSE") << "; got " << (state == GL_TRUE ? "GL_TRUE" : (state == GL_FALSE ? "GL_FALSE" : "non-boolean")) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyInteger4 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3)
+{
+	verifyInteger4Mask(testCtx, name, reference0, true, reference1, true, reference2, true, reference3, true);
+}
+
+void GetBooleanVerifier::verifyInteger4Mask (tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean[4]> boolVector4;
+	glGetBooleanv(name, boolVector4);
+
+	if (!boolVector4.verifyValidity(testCtx))
+		return;
+
+	const GLboolean referenceAsGLBoolean[] =
+	{
+		reference0 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE,
+		reference1 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE,
+		reference2 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE,
+		reference3 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE,
+	};
+
+	if ((enableRef0 && (boolVector4[0] != referenceAsGLBoolean[0])) ||
+		(enableRef1 && (boolVector4[1] != referenceAsGLBoolean[1])) ||
+		(enableRef2 && (boolVector4[2] != referenceAsGLBoolean[2])) ||
+		(enableRef3 && (boolVector4[3] != referenceAsGLBoolean[3])))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (enableRef0 ? (referenceAsGLBoolean[0] ? "GL_TRUE" : "GL_FALSE") : " - ") << ", "
+			<< (enableRef1 ? (referenceAsGLBoolean[1] ? "GL_TRUE" : "GL_FALSE") : " - ") << ", "
+			<< (enableRef2 ? (referenceAsGLBoolean[2] ? "GL_TRUE" : "GL_FALSE") : " - ") << ", "
+			<< (enableRef3 ? (referenceAsGLBoolean[3] ? "GL_TRUE" : "GL_FALSE") : " - ") << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state == GL_TRUE) // state is non-zero, could be greater than reference (correct)
+		return;
+
+	if (state == GL_FALSE) // state is zero
+	{
+		if (reference > 0) // and reference is greater than zero?
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+		}
+	}
+	else
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE or GL_FALSE" << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyUnsignedIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state == GL_TRUE) // state is non-zero, could be greater than reference (correct)
+		return;
+
+	if (state == GL_FALSE) // state is zero
+	{
+		if (reference > 0) // and reference is greater than zero?
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+		}
+	}
+	else
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE or GL_FALSE" << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyIntegerLessOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state == GL_TRUE) // state is non-zero, could be less than reference (correct)
+		return;
+
+	if (state == GL_FALSE) // state is zero
+	{
+		if (reference < 0) // and reference is less than zero?
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+		}
+	}
+	else
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE or GL_FALSE" << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+void GetBooleanVerifier::verifyIntegerGreaterOrEqual2 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean[2]> boolVector;
+	glGetBooleanv(name, boolVector);
+
+	if (!boolVector.verifyValidity(testCtx))
+		return;
+
+	const GLboolean referenceAsGLBoolean[2] =
+	{
+		reference0 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE,
+		reference1 ? (GLboolean)GL_TRUE : (GLboolean)GL_FALSE
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(referenceAsGLBoolean); ++ndx)
+	{
+		if (boolVector[ndx] == GL_TRUE) // state is non-zero, could be greater than any integer
+		{
+			continue;
+		}
+		else if (boolVector[ndx] == GL_FALSE) // state is zero
+		{
+			if (referenceAsGLBoolean[ndx] > 0) // and reference is greater than zero?
+			{
+				testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+				if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+			}
+		}
+		else
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE or GL_FALSE" << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+		}
+	}
+}
+
+void GetBooleanVerifier::verifyIntegerAnyOf (tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (size_t ndx = 0; ndx < referencesLength; ++ndx)
+	{
+		const GLboolean expectedGLState = references[ndx] ? GL_TRUE : GL_FALSE;
+
+		if (state == expectedGLState)
+			return;
+	}
+
+	testCtx.getLog() << TestLog::Message << "// ERROR: got " << (state==GL_TRUE ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+	if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+}
+
+void GetBooleanVerifier::verifyStencilMaskInitial (tcu::TestContext& testCtx, GLenum name, int stencilBits)
+{
+	// if stencilBits == 0, the mask is allowed to be either GL_TRUE or GL_FALSE
+	// otherwise it must be GL_TRUE
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLboolean> state;
+	glGetBooleanv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (stencilBits > 0 && state != GL_TRUE)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
+	}
+}
+
+//GetIntegerVerifier
+
+class GetIntegerVerifier : public StateVerifier
+{
+public:
+			GetIntegerVerifier			(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyInteger						(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyInteger4						(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3);
+	void	verifyInteger4Mask					(tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3);
+	void	verifyIntegerGreaterOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyUnsignedIntegerGreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint reference);
+	void	verifyIntegerLessOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyIntegerGreaterOrEqual2		(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1);
+	void	verifyIntegerAnyOf					(tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength);
+	void	verifyStencilMaskInitial			(tcu::TestContext& testCtx, GLenum name, int stencilBits);
+};
+
+GetIntegerVerifier::GetIntegerVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger")
+{
+}
+
+void GetIntegerVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyInteger4 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3)
+{
+	verifyInteger4Mask(testCtx, name, reference0, true, reference1, true, reference2, true, reference3, true);
+}
+
+void GetIntegerVerifier::verifyInteger4Mask (tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[4]> intVector4;
+	glGetIntegerv(name, intVector4);
+
+	if (!intVector4.verifyValidity(testCtx))
+		return;
+
+	if ((enableRef0 && (intVector4[0] != reference0)) ||
+		(enableRef1 && (intVector4[1] != reference1)) ||
+		(enableRef2 && (intVector4[2] != reference2)) ||
+		(enableRef3 && (intVector4[3] != reference3)))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (enableRef0?"":"(") << reference0 << (enableRef0?"":")") << ", "
+			<< (enableRef1?"":"(") << reference1 << (enableRef1?"":")") << ", "
+			<< (enableRef2?"":"(") << reference2 << (enableRef2?"":")") << ", "
+			<< (enableRef3?"":"(") << reference3 << (enableRef3?"":")")	<< TestLog::EndMessage;
+
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyUnsignedIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (GLuint(state) < reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << reference << "; got " << GLuint(state) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyIntegerLessOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state > reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected less or equal to " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyIntegerGreaterOrEqual2 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[2]> intVector2;
+	glGetIntegerv(name, intVector2);
+
+	if (!intVector2.verifyValidity(testCtx))
+		return;
+
+	if (intVector2[0] < reference0 || intVector2[1] < reference1)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << reference0 << ", " << reference1 << "; got " << intVector2[0] << ", " << intVector2[0] << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetIntegerVerifier::verifyIntegerAnyOf (tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (size_t ndx = 0; ndx < referencesLength; ++ndx)
+	{
+		const GLint expectedGLState = references[ndx];
+
+		if (state == expectedGLState)
+			return;
+	}
+
+	testCtx.getLog() << TestLog::Message << "// ERROR: got " << state << TestLog::EndMessage;
+	if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+}
+
+void GetIntegerVerifier::verifyStencilMaskInitial (tcu::TestContext& testCtx, GLenum name, int stencilBits)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetIntegerv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint reference = (1 << stencilBits) - 1;
+
+	if ((state & reference) != reference) // the least significant stencilBits bits should be on
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected minimum mask of " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid mask value");
+	}
+}
+
+//GetInteger64Verifier
+
+class GetInteger64Verifier : public StateVerifier
+{
+public:
+			GetInteger64Verifier			(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyInteger						(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyInteger4						(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3);
+	void	verifyInteger4Mask					(tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3);
+	void	verifyIntegerGreaterOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyUnsignedIntegerGreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint reference);
+	void	verifyIntegerLessOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyIntegerGreaterOrEqual2		(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1);
+	void	verifyIntegerAnyOf					(tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength);
+	void	verifyStencilMaskInitial			(tcu::TestContext& testCtx, GLenum name, int stencilBits);
+};
+
+GetInteger64Verifier::GetInteger64Verifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getinteger64")
+{
+}
+
+void GetInteger64Verifier::verifyInteger (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != GLint64(reference))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyInteger4 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3)
+{
+	verifyInteger4Mask(testCtx, name, reference0, true, reference1, true, reference2, true, reference3, true);
+}
+
+void GetInteger64Verifier::verifyInteger4Mask (tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64[4]> intVector4;
+	glGetInteger64v(name, intVector4);
+
+	if (!intVector4.verifyValidity(testCtx))
+		return;
+
+	if ((enableRef0 && (intVector4[0] != GLint64(reference0))) ||
+		(enableRef1 && (intVector4[1] != GLint64(reference1))) ||
+		(enableRef2 && (intVector4[2] != GLint64(reference2))) ||
+		(enableRef3 && (intVector4[3] != GLint64(reference3))))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (enableRef0?"":"(") << reference0 << (enableRef0?"":")") << ", "
+			<< (enableRef1?"":"(") << reference1 << (enableRef1?"":")") << ", "
+			<< (enableRef2?"":"(") << reference2 << (enableRef2?"":")") << ", "
+			<< (enableRef3?"":"(") << reference3 << (enableRef3?"":")")	<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < GLint64(reference))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLint64(reference) << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyUnsignedIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (GLuint(state) < GLint64(reference))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLint64(reference) << "; got " << GLuint(state) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyIntegerLessOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state > GLint64(reference))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected less or equal to " << GLint64(reference) << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyIntegerGreaterOrEqual2 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64[2]> intVector2;
+	glGetInteger64v(name, intVector2);
+
+	if (!intVector2.verifyValidity(testCtx))
+		return;
+
+	if (intVector2[0] < GLint64(reference0) || intVector2[1] < GLint64(reference1))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLint64(reference0) << ", " << GLint64(reference1) << "; got " << intVector2[0] << ", " << intVector2[1] << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+	}
+}
+
+void GetInteger64Verifier::verifyIntegerAnyOf (tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (size_t ndx = 0; ndx < referencesLength; ++ndx)
+	{
+		const GLint64 expectedGLState = GLint64(references[ndx]);
+
+		if (state == expectedGLState)
+			return;
+	}
+
+	testCtx.getLog() << TestLog::Message << "// ERROR: got " << state << TestLog::EndMessage;
+	if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
+}
+
+void GetInteger64Verifier::verifyStencilMaskInitial (tcu::TestContext& testCtx, GLenum name, int stencilBits)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint64> state;
+	glGetInteger64v(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint64 reference = (1ULL << stencilBits) - 1;
+
+	if ((state & reference) != reference) // the least significant stencilBits bits should be on
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected mimimum mask of " << reference << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid mask value");
+	}
+}
+
+//GetFloatVerifier
+
+class GetFloatVerifier : public StateVerifier
+{
+public:
+			GetFloatVerifier				(const glw::Functions& gl, tcu::TestLog& log);
+	void	verifyInteger						(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyInteger4						(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3);
+	void	verifyInteger4Mask					(tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3);
+	void	verifyIntegerGreaterOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyUnsignedIntegerGreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint reference);
+	void	verifyIntegerLessOrEqual			(tcu::TestContext& testCtx, GLenum name, GLint reference);
+	void	verifyIntegerGreaterOrEqual2		(tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1);
+	void	verifyIntegerAnyOf					(tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength);
+	void	verifyStencilMaskInitial			(tcu::TestContext& testCtx, GLenum name, int stencilBits);
+};
+
+GetFloatVerifier::GetFloatVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: StateVerifier(gl, log, "_getfloat")
+{
+}
+
+void GetFloatVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	const GLfloat referenceAsFloat = GLfloat(reference);
+	DE_ASSERT(reference == GLint(referenceAsFloat)); // reference integer must have 1:1 mapping to float for this to work. Reference value is always such value in these tests
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != referenceAsFloat)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << referenceAsFloat << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyInteger4 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1, GLint reference2, GLint reference3)
+{
+	verifyInteger4Mask(testCtx, name, reference0, true, reference1, true, reference2, true, reference3, true);
+}
+
+void GetFloatVerifier::verifyInteger4Mask (tcu::TestContext& testCtx, GLenum name, GLint reference0, bool enableRef0, GLint reference1, bool enableRef1, GLint reference2, bool enableRef2, GLint reference3, bool enableRef3)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[4]> floatVector4;
+	glGetFloatv(name, floatVector4);
+
+	if (!floatVector4.verifyValidity(testCtx))
+		return;
+
+	if ((enableRef0 && (floatVector4[0] != GLfloat(reference0))) ||
+		(enableRef1 && (floatVector4[1] != GLfloat(reference1))) ||
+		(enableRef2 && (floatVector4[2] != GLfloat(reference2))) ||
+		(enableRef3 && (floatVector4[3] != GLfloat(reference3))))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected "
+			<< (enableRef0?"":"(") << GLfloat(reference0) << (enableRef0?"":")") << ", "
+			<< (enableRef1?"":"(") << GLfloat(reference1) << (enableRef1?"":")") << ", "
+			<< (enableRef2?"":"(") << GLfloat(reference2) << (enableRef2?"":")") << ", "
+			<< (enableRef3?"":"(") << GLfloat(reference3) << (enableRef3?"":")") << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state < GLfloat(reference))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLfloat(reference) << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyUnsignedIntegerGreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (GLuint(state) < GLfloat(reference))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLfloat(reference) << "; got " << GLuint(state) << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyIntegerLessOrEqual (tcu::TestContext& testCtx, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state > GLfloat(reference))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected less or equal to " << GLfloat(reference) << "; got " << state << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyIntegerGreaterOrEqual2 (tcu::TestContext& testCtx, GLenum name, GLint reference0, GLint reference1)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[2]> floatVector2;
+	glGetFloatv(name, floatVector2);
+
+	if (!floatVector2.verifyValidity(testCtx))
+		return;
+
+	if (floatVector2[0] < GLfloat(reference0) || floatVector2[1] < GLfloat(reference1))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLfloat(reference0) << ", " << GLfloat(reference1) << "; got " << floatVector2[0] << ", " << floatVector2[1] << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetFloatVerifier::verifyIntegerAnyOf (tcu::TestContext& testCtx, GLenum name, const GLint references[], size_t referencesLength)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetFloatv(name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (size_t ndx = 0; ndx < referencesLength; ++ndx)
+	{
+		const GLfloat expectedGLState = GLfloat(references[ndx]);
+		DE_ASSERT(references[ndx] == GLint(expectedGLState)); // reference integer must have 1:1 mapping to float for this to work. Reference value is always such value in these tests
+
+		if (state == expectedGLState)
+			return;
+	}
+
+	testCtx.getLog() << TestLog::Message << "// ERROR: got " << state << TestLog::EndMessage;
+	if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+}
+
+void GetFloatVerifier::verifyStencilMaskInitial (tcu::TestContext& testCtx, GLenum name, int stencilBits)
+{
+	// checking the mask bits with float doesn't make much sense because of conversion errors
+	// just verify that the value is greater or equal to the minimum value
+	const GLint reference = (1 << stencilBits) - 1;
+	verifyIntegerGreaterOrEqual(testCtx, name, reference);
+}
+
+} // IntegerStateQueryVerifiers
+
+namespace
+{
+
+using namespace IntegerStateQueryVerifiers;
+
+static const char* transformFeedbackTestVertSource	=	"#version 300 es\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = vec4(0.0);\n"
+														"}\n\0";
+static const char* transformFeedbackTestFragSource	=	"#version 300 es\n"
+														"layout(location = 0) out mediump vec4 fragColor;"
+														"void main (void)\n"
+														"{\n"
+														"	fragColor = vec4(0.0);\n"
+														"}\n\0";
+
+static const char* testVertSource					=	"#version 300 es\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = vec4(0.0);\n"
+														"}\n\0";
+static const char* testFragSource					=	"#version 300 es\n"
+														"layout(location = 0) out mediump vec4 fragColor;"
+														"void main (void)\n"
+														"{\n"
+														"	fragColor = vec4(0.0);\n"
+														"}\n\0";
+
+class TransformFeedbackTestCase : public ApiCase
+{
+public:
+	TransformFeedbackTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase				(context, name, description)
+		, m_verifier			(verifier)
+		, m_transformfeedback	(0)
+	{
+	}
+
+	void test (void)
+	{
+		glGenTransformFeedbacks(1, &m_transformfeedback);
+		expectError(GL_NO_ERROR);
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		glShaderSource(shaderVert, 1, &transformFeedbackTestVertSource, DE_NULL);
+		glCompileShader(shaderVert);
+		expectError(GL_NO_ERROR);
+		GLint compileStatus;
+		glGetShaderiv(shaderVert, GL_COMPILE_STATUS, &compileStatus);
+		checkBooleans(compileStatus, GL_TRUE);
+
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+		glShaderSource(shaderFrag, 1, &transformFeedbackTestFragSource, DE_NULL);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+		glGetShaderiv(shaderFrag, GL_COMPILE_STATUS, &compileStatus);
+		checkBooleans(compileStatus, GL_TRUE);
+
+		GLuint shaderProg = glCreateProgram();
+		glAttachShader(shaderProg, shaderVert);
+		glAttachShader(shaderProg, shaderFrag);
+		const char* transform_feedback_outputs = "gl_Position";
+		glTransformFeedbackVaryings(shaderProg, 1, &transform_feedback_outputs, GL_INTERLEAVED_ATTRIBS);
+		glLinkProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+		GLint linkStatus;
+		glGetProgramiv(shaderProg, GL_LINK_STATUS, &linkStatus);
+		checkBooleans(linkStatus, GL_TRUE);
+
+		glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_transformfeedback);
+		expectError(GL_NO_ERROR);
+
+		GLuint feedbackBufferId;
+		glGenBuffers(1, &feedbackBufferId);
+		glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackBufferId);
+		glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_DYNAMIC_READ);
+		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, feedbackBufferId);
+		expectError(GL_NO_ERROR);
+
+		glUseProgram(shaderProg);
+
+		testTransformFeedback();
+
+		glUseProgram(0);
+		glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+		glDeleteTransformFeedbacks(1, &m_transformfeedback);
+		glDeleteBuffers(1, &feedbackBufferId);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+	}
+
+	virtual void testTransformFeedback (void) = DE_NULL;
+
+protected:
+	StateVerifier*	m_verifier;
+	GLuint			m_transformfeedback;
+};
+
+class TransformFeedbackBindingTestCase : public TransformFeedbackTestCase
+{
+public:
+	TransformFeedbackBindingTestCase (Context& context, StateVerifier* verifier, const char* name)
+		: TransformFeedbackTestCase	(context, verifier, name, "GL_TRANSFORM_FEEDBACK_BINDING")
+	{
+	}
+
+protected:
+	void beforeTransformFeedbackTest (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_TRANSFORM_FEEDBACK_BINDING, 0);
+		expectError(GL_NO_ERROR);
+	}
+	void testTransformFeedback (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_TRANSFORM_FEEDBACK_BINDING, m_transformfeedback);
+		expectError(GL_NO_ERROR);
+	}
+	void afterTransformFeedbackTest (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_TRANSFORM_FEEDBACK_BINDING, 0);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ConstantMinimumValueTestCase : public ApiCase
+{
+public:
+	ConstantMinimumValueTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName, GLint minValue)
+		: ApiCase		(context, name, description)
+		, m_targetName	(targetName)
+		, m_minValue	(minValue)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyUnsignedIntegerGreaterOrEqual(m_testCtx, m_targetName, m_minValue);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	GLenum			m_targetName;
+	GLint			m_minValue;
+	StateVerifier*	m_verifier;
+};
+
+class ConstantMaximumValueTestCase : public ApiCase
+{
+public:
+	ConstantMaximumValueTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName, GLint minValue)
+		: ApiCase		(context, name, description)
+		, m_targetName	(targetName)
+		, m_minValue	(minValue)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyIntegerLessOrEqual(m_testCtx, m_targetName, m_minValue);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	GLenum			m_targetName;
+	GLint			m_minValue;
+	StateVerifier*	m_verifier;
+};
+
+class SampleBuffersTestCase : public ApiCase
+{
+public:
+	SampleBuffersTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		const int expectedSampleBuffers = (m_context.getRenderTarget().getNumSamples() > 1) ? 1 : 0;
+
+		m_log << tcu::TestLog::Message << "Sample count is " << (m_context.getRenderTarget().getNumSamples()) << ", expecting GL_SAMPLE_BUFFERS to be " << expectedSampleBuffers << tcu::TestLog::EndMessage;
+
+		m_verifier->verifyInteger(m_testCtx, GL_SAMPLE_BUFFERS, expectedSampleBuffers);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class SamplesTestCase : public ApiCase
+{
+public:
+	SamplesTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		// MSAA?
+		if (m_context.getRenderTarget().getNumSamples() > 1)
+		{
+			m_log << tcu::TestLog::Message << "Sample count is " << (m_context.getRenderTarget().getNumSamples()) << tcu::TestLog::EndMessage;
+
+			m_verifier->verifyInteger(m_testCtx, GL_SAMPLES, m_context.getRenderTarget().getNumSamples());
+			expectError(GL_NO_ERROR);
+		}
+		else
+		{
+			const glw::GLint validSamples[] = {0, 1};
+
+			m_log << tcu::TestLog::Message << "Expecting GL_SAMPLES to be 0 or 1" << tcu::TestLog::EndMessage;
+
+			m_verifier->verifyIntegerAnyOf(m_testCtx, GL_SAMPLES, validSamples, DE_LENGTH_OF_ARRAY(validSamples));
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class HintTestCase : public ApiCase
+{
+public:
+	HintTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName)
+		: ApiCase		(context, name, description)
+		, m_targetName	(targetName)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_targetName, GL_DONT_CARE);
+		expectError(GL_NO_ERROR);
+
+		glHint(m_targetName, GL_NICEST);
+		m_verifier->verifyInteger(m_testCtx, m_targetName, GL_NICEST);
+		expectError(GL_NO_ERROR);
+
+		glHint(m_targetName, GL_FASTEST);
+		m_verifier->verifyInteger(m_testCtx, m_targetName, GL_FASTEST);
+		expectError(GL_NO_ERROR);
+
+		glHint(m_targetName, GL_DONT_CARE);
+		m_verifier->verifyInteger(m_testCtx, m_targetName, GL_DONT_CARE);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	GLenum		m_targetName;
+	StateVerifier*	m_verifier;
+};
+
+class DepthFuncTestCase : public ApiCase
+{
+public:
+	DepthFuncTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_DEPTH_FUNC, GL_LESS);
+		expectError(GL_NO_ERROR);
+
+		const GLenum depthFunctions[] = {GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GREATER, GL_GEQUAL, GL_NOTEQUAL};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthFunctions); ndx++)
+		{
+			glDepthFunc(depthFunctions[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_DEPTH_FUNC, depthFunctions[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class CullFaceTestCase : public ApiCase
+{
+public:
+	CullFaceTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_CULL_FACE_MODE, GL_BACK);
+		expectError(GL_NO_ERROR);
+
+		const GLenum cullFaces[] = {GL_FRONT, GL_BACK, GL_FRONT_AND_BACK};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cullFaces); ndx++)
+		{
+			glCullFace(cullFaces[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_CULL_FACE_MODE, cullFaces[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class FrontFaceTestCase : public ApiCase
+{
+public:
+	FrontFaceTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_FRONT_FACE, GL_CCW);
+		expectError(GL_NO_ERROR);
+
+		const GLenum frontFaces[] = {GL_CW, GL_CCW};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(frontFaces); ndx++)
+		{
+			glFrontFace(frontFaces[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_FRONT_FACE, frontFaces[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ViewPortTestCase : public ApiCase
+{
+public:
+	ViewPortTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		GLint maxViewportDimensions[2] = {0};
+		glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDimensions);
+
+		// verify initial value of first two values
+		m_verifier->verifyInteger4(m_testCtx, GL_VIEWPORT, 0, 0, m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			GLint x			= rnd.getInt(-64000, 64000);
+			GLint y			= rnd.getInt(-64000, 64000);
+			GLsizei width	= rnd.getInt(0, maxViewportDimensions[0]);
+			GLsizei height	= rnd.getInt(0, maxViewportDimensions[1]);
+
+			glViewport(x, y, width, height);
+			m_verifier->verifyInteger4(m_testCtx, GL_VIEWPORT, x, y, width, height);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ScissorBoxTestCase : public ApiCase
+{
+public:
+	ScissorBoxTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		// verify initial value of first two values
+		m_verifier->verifyInteger4Mask(m_testCtx, GL_SCISSOR_BOX, 0, true, 0, true, 0, false, 0, false);
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			GLint left		= rnd.getInt(-64000, 64000);
+			GLint bottom	= rnd.getInt(-64000, 64000);
+			GLsizei width	= rnd.getInt(0, 64000);
+			GLsizei height	= rnd.getInt(0, 64000);
+
+			glScissor(left, bottom, width, height);
+			m_verifier->verifyInteger4(m_testCtx, GL_SCISSOR_BOX, left, bottom, width, height);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class MaxViewportDimsTestCase : public ApiCase
+{
+public:
+	MaxViewportDimsTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyIntegerGreaterOrEqual2(m_testCtx, GL_MAX_VIEWPORT_DIMS, m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class StencilRefTestCase : public ApiCase
+{
+public:
+	StencilRefTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, 0);
+		expectError(GL_NO_ERROR);
+
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int ref = 1 << stencilBit;
+
+			glStencilFunc(GL_ALWAYS, ref, 0); // mask should not affect the REF
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, ref);
+			expectError(GL_NO_ERROR);
+
+			glStencilFunc(GL_ALWAYS, ref, ref);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, ref);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+};
+
+class StencilRefSeparateTestCase : public ApiCase
+{
+public:
+	StencilRefSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, GLenum stencilFuncTargetFace)
+		: ApiCase					(context, name, description)
+		, m_verifier				(verifier)
+		, m_testTargetName			(testTargetName)
+		, m_stencilFuncTargetFace	(stencilFuncTargetFace)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, 0);
+		expectError(GL_NO_ERROR);
+
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int ref = 1 << stencilBit;
+
+			glStencilFuncSeparate(m_stencilFuncTargetFace, GL_ALWAYS, ref, 0);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, ref);
+			expectError(GL_NO_ERROR);
+
+			glStencilFuncSeparate(m_stencilFuncTargetFace, GL_ALWAYS, ref, ref);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, ref);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+	GLenum		m_stencilFuncTargetFace;
+};
+
+class StencilOpTestCase : public ApiCase
+{
+public:
+	StencilOpTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum stencilOpName)
+		: ApiCase					(context, name, description)
+		, m_verifier				(verifier)
+		, m_stencilOpName			(stencilOpName)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_stencilOpName, GL_KEEP);
+		expectError(GL_NO_ERROR);
+
+		const GLenum stencilOpValues[] = {GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(stencilOpValues); ++ndx)
+		{
+			SetStencilOp(stencilOpValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_stencilOpName, stencilOpValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+protected:
+	virtual void SetStencilOp (GLenum stencilOpValue)
+	{
+		switch (m_stencilOpName)
+		{
+		case GL_STENCIL_FAIL:
+		case GL_STENCIL_BACK_FAIL:
+			glStencilOp(stencilOpValue, GL_KEEP, GL_KEEP);
+			break;
+
+		case GL_STENCIL_PASS_DEPTH_FAIL:
+		case GL_STENCIL_BACK_PASS_DEPTH_FAIL:
+			glStencilOp(GL_KEEP, stencilOpValue, GL_KEEP);
+			break;
+
+		case GL_STENCIL_PASS_DEPTH_PASS:
+		case GL_STENCIL_BACK_PASS_DEPTH_PASS:
+			glStencilOp(GL_KEEP, GL_KEEP, stencilOpValue);
+			break;
+
+		default:
+			DE_ASSERT(false && "should not happen");
+			break;
+		}
+	}
+
+	StateVerifier*				m_verifier;
+	GLenum					m_stencilOpName;
+};
+
+
+class StencilOpSeparateTestCase : public StencilOpTestCase
+{
+public:
+	StencilOpSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum stencilOpName, GLenum stencilOpFace)
+		: StencilOpTestCase		(context, verifier, name, description, stencilOpName)
+		, m_stencilOpFace		(stencilOpFace)
+	{
+	}
+
+private:
+	void SetStencilOp (GLenum stencilOpValue)
+	{
+		switch (m_stencilOpName)
+		{
+		case GL_STENCIL_FAIL:
+		case GL_STENCIL_BACK_FAIL:
+			glStencilOpSeparate(m_stencilOpFace, stencilOpValue, GL_KEEP, GL_KEEP);
+			break;
+
+		case GL_STENCIL_PASS_DEPTH_FAIL:
+		case GL_STENCIL_BACK_PASS_DEPTH_FAIL:
+			glStencilOpSeparate(m_stencilOpFace, GL_KEEP, stencilOpValue, GL_KEEP);
+			break;
+
+		case GL_STENCIL_PASS_DEPTH_PASS:
+		case GL_STENCIL_BACK_PASS_DEPTH_PASS:
+			glStencilOpSeparate(m_stencilOpFace, GL_KEEP, GL_KEEP, stencilOpValue);
+			break;
+
+		default:
+			DE_ASSERT(false && "should not happen");
+			break;
+		}
+	}
+
+	GLenum		m_stencilOpFace;
+};
+
+class StencilFuncTestCase : public ApiCase
+{
+public:
+	StencilFuncTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_STENCIL_FUNC, GL_ALWAYS);
+		expectError(GL_NO_ERROR);
+
+		const GLenum stencilfuncValues[] = {GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(stencilfuncValues); ++ndx)
+		{
+			glStencilFunc(stencilfuncValues[ndx], 0, 0);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_STENCIL_FUNC, stencilfuncValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_STENCIL_BACK_FUNC, stencilfuncValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class StencilFuncSeparateTestCase : public ApiCase
+{
+public:
+	StencilFuncSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum stencilFuncName, GLenum stencilFuncFace)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_stencilFuncName	(stencilFuncName)
+		, m_stencilFuncFace	(stencilFuncFace)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_stencilFuncName, GL_ALWAYS);
+		expectError(GL_NO_ERROR);
+
+		const GLenum stencilfuncValues[] = {GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(stencilfuncValues); ++ndx)
+		{
+			glStencilFuncSeparate(m_stencilFuncFace, stencilfuncValues[ndx], 0, 0);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_stencilFuncName, stencilfuncValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_stencilFuncName;
+	GLenum		m_stencilFuncFace;
+};
+
+class StencilMaskTestCase : public ApiCase
+{
+public:
+	StencilMaskTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+	{
+	}
+
+	void test (void)
+	{
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		m_verifier->verifyStencilMaskInitial(m_testCtx, m_testTargetName, stencilBits);
+		expectError(GL_NO_ERROR);
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int mask = 1 << stencilBit;
+
+			glStencilFunc(GL_ALWAYS, 0, mask);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, mask);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+};
+
+class StencilMaskSeparateTestCase : public ApiCase
+{
+public:
+	StencilMaskSeparateTestCase	(Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, GLenum stencilFuncTargetFace)
+		: ApiCase					(context, name, description)
+		, m_verifier				(verifier)
+		, m_testTargetName			(testTargetName)
+		, m_stencilFuncTargetFace	(stencilFuncTargetFace)
+	{
+	}
+
+	void test (void)
+	{
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		m_verifier->verifyStencilMaskInitial(m_testCtx, m_testTargetName, stencilBits);
+		expectError(GL_NO_ERROR);
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int mask = 1 << stencilBit;
+
+			glStencilFuncSeparate(m_stencilFuncTargetFace, GL_ALWAYS, 0, mask);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, mask);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+	GLenum		m_stencilFuncTargetFace;
+};
+
+class StencilWriteMaskTestCase : public ApiCase
+{
+public:
+	StencilWriteMaskTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+	{
+	}
+
+	void test (void)
+	{
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int mask = 1 << stencilBit;
+
+			glStencilMask(mask);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, mask);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+};
+
+class StencilWriteMaskSeparateTestCase : public ApiCase
+{
+public:
+	StencilWriteMaskSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, GLenum stencilTargetFace)
+		: ApiCase				(context, name, description)
+		, m_verifier			(verifier)
+		, m_testTargetName		(testTargetName)
+		, m_stencilTargetFace	(stencilTargetFace)
+	{
+	}
+
+	void test (void)
+	{
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int mask = 1 << stencilBit;
+
+			glStencilMaskSeparate(m_stencilTargetFace, mask);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, mask);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+	GLenum		m_stencilTargetFace;
+};
+
+class PixelStoreTestCase : public ApiCase
+{
+public:
+	PixelStoreTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, int initialValue)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+		, m_initialValue	(initialValue)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, m_initialValue);
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 120;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const int referenceValue = rnd.getInt(0, 64000);
+
+			glPixelStorei(m_testTargetName, referenceValue);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, referenceValue);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+	int			m_initialValue;
+};
+
+class PixelStoreAlignTestCase : public ApiCase
+{
+public:
+	PixelStoreAlignTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, 4);
+		expectError(GL_NO_ERROR);
+
+		const int alignments[] = {1, 2, 4, 8};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(alignments); ++ndx)
+		{
+			const int referenceValue = alignments[ndx];
+
+			glPixelStorei(m_testTargetName, referenceValue);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, referenceValue);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testTargetName;
+};
+
+class BlendFuncTestCase : public ApiCase
+{
+public:
+	BlendFuncTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, int initialValue)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+		, m_initialValue	(initialValue)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, m_initialValue);
+		expectError(GL_NO_ERROR);
+
+		const GLenum blendFuncValues[] =
+		{
+			GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA,
+			GL_SRC_ALPHA_SATURATE
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(blendFuncValues); ++ndx)
+		{
+			const GLenum referenceValue = blendFuncValues[ndx];
+
+			SetBlendFunc(referenceValue);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, referenceValue);
+			expectError(GL_NO_ERROR);
+		}
+	}
+protected:
+	virtual void SetBlendFunc (GLenum func)
+	{
+		switch (m_testTargetName)
+		{
+		case GL_BLEND_SRC_RGB:
+		case GL_BLEND_SRC_ALPHA:
+			glBlendFunc(func, GL_ZERO);
+			break;
+
+		case GL_BLEND_DST_RGB:
+		case GL_BLEND_DST_ALPHA:
+			glBlendFunc(GL_ZERO, func);
+			break;
+
+		default:
+			DE_ASSERT(false && "should not happen");
+			break;
+		}
+	}
+
+	StateVerifier*		m_verifier;
+	GLenum			m_testTargetName;
+	int				m_initialValue;
+};
+
+class BlendFuncSeparateTestCase : public BlendFuncTestCase
+{
+public:
+	BlendFuncSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, int initialValue)
+		: BlendFuncTestCase	(context, verifier, name, description, testTargetName, initialValue)
+	{
+	}
+
+	void SetBlendFunc (GLenum func)
+	{
+		switch (m_testTargetName)
+		{
+		case GL_BLEND_SRC_RGB:
+			glBlendFuncSeparate(func, GL_ZERO, GL_ZERO, GL_ZERO);
+			break;
+
+		case GL_BLEND_DST_RGB:
+			glBlendFuncSeparate(GL_ZERO, func, GL_ZERO, GL_ZERO);
+			break;
+
+		case GL_BLEND_SRC_ALPHA:
+			glBlendFuncSeparate(GL_ZERO, GL_ZERO, func, GL_ZERO);
+			break;
+
+		case GL_BLEND_DST_ALPHA:
+			glBlendFuncSeparate(GL_ZERO, GL_ZERO, GL_ZERO, func);
+			break;
+
+		default:
+			DE_ASSERT(false && "should not happen");
+			break;
+		}
+	}
+};
+
+class BlendEquationTestCase : public ApiCase
+{
+public:
+	BlendEquationTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, int initialValue)
+		: ApiCase			(context, name, description)
+		, m_verifier		(verifier)
+		, m_testTargetName	(testTargetName)
+		, m_initialValue	(initialValue)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testTargetName, m_initialValue);
+		expectError(GL_NO_ERROR);
+
+		const GLenum blendFuncValues[] =
+		{
+			GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MIN, GL_MAX
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(blendFuncValues); ++ndx)
+		{
+			const GLenum referenceValue = blendFuncValues[ndx];
+
+			SetBlendEquation(referenceValue);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_testTargetName, referenceValue);
+			expectError(GL_NO_ERROR);
+		}
+	}
+protected:
+	virtual void SetBlendEquation (GLenum equation)
+	{
+		glBlendEquation(equation);
+	}
+
+	StateVerifier*		m_verifier;
+	GLenum			m_testTargetName;
+	int				m_initialValue;
+};
+class BlendEquationSeparateTestCase : public BlendEquationTestCase
+{
+public:
+	BlendEquationSeparateTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, int initialValue)
+		: BlendEquationTestCase	(context, verifier, name, description, testTargetName, initialValue)
+	{
+	}
+
+protected:
+	void SetBlendEquation (GLenum equation)
+	{
+		switch (m_testTargetName)
+		{
+		case GL_BLEND_EQUATION_RGB:
+			glBlendEquationSeparate(equation, GL_FUNC_ADD);
+			break;
+
+		case GL_BLEND_EQUATION_ALPHA:
+			glBlendEquationSeparate(GL_FUNC_ADD, equation);
+			break;
+
+		default:
+			DE_ASSERT(false && "should not happen");
+			break;
+		}
+	}
+};
+
+class ImplementationArrayTestCase : public ApiCase
+{
+public:
+	ImplementationArrayTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testTargetName, GLenum testTargetLengthTargetName, int minValue)
+		: ApiCase						(context, name, description)
+		, m_verifier					(verifier)
+		, m_testTargetName				(testTargetName)
+		, m_testTargetLengthTargetName	(testTargetLengthTargetName)
+		, m_minValue					(minValue)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyIntegerGreaterOrEqual(m_testCtx, m_testTargetLengthTargetName, m_minValue);
+		expectError(GL_NO_ERROR);
+
+		GLint targetArrayLength = 0;
+		glGetIntegerv(m_testTargetLengthTargetName, &targetArrayLength);
+		expectError(GL_NO_ERROR);
+
+		if (targetArrayLength)
+		{
+			std::vector<GLint> queryResult;
+			queryResult.resize(targetArrayLength, 0);
+
+			glGetIntegerv(m_testTargetName, &queryResult[0]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*		m_verifier;
+	GLenum			m_testTargetName;
+	GLenum			m_testTargetLengthTargetName;
+	int				m_minValue;
+};
+
+class CurrentProgramBindingTestCase : public ApiCase
+{
+public:
+	CurrentProgramBindingTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_CURRENT_PROGRAM, 0);
+		expectError(GL_NO_ERROR);
+
+		{
+			GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+			glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+			glCompileShader(shaderVert);
+			expectError(GL_NO_ERROR);
+			GLint compileStatus;
+			glGetShaderiv(shaderVert, GL_COMPILE_STATUS, &compileStatus);
+			checkBooleans(compileStatus, GL_TRUE);
+
+			GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+			glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+			glCompileShader(shaderFrag);
+			expectError(GL_NO_ERROR);
+			glGetShaderiv(shaderFrag, GL_COMPILE_STATUS, &compileStatus);
+			checkBooleans(compileStatus, GL_TRUE);
+
+			GLuint shaderProg = glCreateProgram();
+			glAttachShader(shaderProg, shaderVert);
+			glAttachShader(shaderProg, shaderFrag);
+			glLinkProgram(shaderProg);
+			expectError(GL_NO_ERROR);
+			GLint linkStatus;
+			glGetProgramiv(shaderProg, GL_LINK_STATUS, &linkStatus);
+			checkBooleans(linkStatus, GL_TRUE);
+
+			glUseProgram(shaderProg);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_CURRENT_PROGRAM, shaderProg);
+			expectError(GL_NO_ERROR);
+
+			glDeleteShader(shaderVert);
+			glDeleteShader(shaderFrag);
+			glDeleteProgram(shaderProg);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_CURRENT_PROGRAM, shaderProg);
+			expectError(GL_NO_ERROR);
+		}
+
+		glUseProgram(0);
+		expectError(GL_NO_ERROR);
+		m_verifier->verifyInteger(m_testCtx, GL_CURRENT_PROGRAM, 0);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*		m_verifier;
+};
+
+class VertexArrayBindingTestCase : public ApiCase
+{
+public:
+	VertexArrayBindingTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test(void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_VERTEX_ARRAY_BINDING, 0);
+		expectError(GL_NO_ERROR);
+
+		GLuint vertexArrayObject = 0;
+		glGenVertexArrays(1, &vertexArrayObject);
+		expectError(GL_NO_ERROR);
+
+		glBindVertexArray(vertexArrayObject);
+		m_verifier->verifyInteger(m_testCtx, GL_VERTEX_ARRAY_BINDING, vertexArrayObject);
+		expectError(GL_NO_ERROR);
+
+		glDeleteVertexArrays(1, &vertexArrayObject);
+		m_verifier->verifyInteger(m_testCtx, GL_VERTEX_ARRAY_BINDING, 0);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*		m_verifier;
+};
+
+class BufferBindingTestCase : public ApiCase
+{
+public:
+	BufferBindingTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum bufferBindingName, GLenum bufferType)
+		: ApiCase				(context, name, description)
+		, m_verifier			(verifier)
+		, m_bufferBindingName	(bufferBindingName)
+		, m_bufferType			(bufferType)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_bufferBindingName, 0);
+		expectError(GL_NO_ERROR);
+
+		GLuint bufferObject = 0;
+		glGenBuffers(1, &bufferObject);
+		expectError(GL_NO_ERROR);
+
+		glBindBuffer(m_bufferType, bufferObject);
+		m_verifier->verifyInteger(m_testCtx, m_bufferBindingName, bufferObject);
+		expectError(GL_NO_ERROR);
+
+		glDeleteBuffers(1, &bufferObject);
+		m_verifier->verifyInteger(m_testCtx, m_bufferBindingName, 0);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*	m_verifier;
+	GLenum			m_bufferBindingName;
+	GLenum			m_bufferType;
+};
+
+class ElementArrayBufferBindingTestCase : public ApiCase
+{
+public:
+	ElementArrayBufferBindingTestCase (Context& context, StateVerifier* verifier, const char* name)
+		: ApiCase		(context, name, "GL_ELEMENT_ARRAY_BUFFER_BINDING")
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		// Test with default VAO
+		{
+			const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");
+
+			m_verifier->verifyInteger(m_testCtx, GL_ELEMENT_ARRAY_BUFFER_BINDING, 0);
+			expectError(GL_NO_ERROR);
+
+			GLuint bufferObject = 0;
+			glGenBuffers(1, &bufferObject);
+			expectError(GL_NO_ERROR);
+
+			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferObject);
+			m_verifier->verifyInteger(m_testCtx, GL_ELEMENT_ARRAY_BUFFER_BINDING, bufferObject);
+			expectError(GL_NO_ERROR);
+
+			glDeleteBuffers(1, &bufferObject);
+			m_verifier->verifyInteger(m_testCtx, GL_ELEMENT_ARRAY_BUFFER_BINDING, 0);
+			expectError(GL_NO_ERROR);
+		}
+
+		// Test with multiple VAOs
+		{
+			const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");
+
+			GLuint vaos[2]		= {0};
+			GLuint buffers[2]	= {0};
+
+			glGenVertexArrays(2, vaos);
+			expectError(GL_NO_ERROR);
+
+			glGenBuffers(2, buffers);
+			expectError(GL_NO_ERROR);
+
+			// initial
+			glBindVertexArray(vaos[0]);
+			m_verifier->verifyInteger(m_testCtx, GL_ELEMENT_ARRAY_BUFFER_BINDING, 0);
+			expectError(GL_NO_ERROR);
+
+			// after setting
+			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[0]);
+			m_verifier->verifyInteger(m_testCtx, GL_ELEMENT_ARRAY_BUFFER_BINDING, buffers[0]);
+			expectError(GL_NO_ERROR);
+
+			// initial of vao 2
+			glBindVertexArray(vaos[1]);
+			m_verifier->verifyInteger(m_testCtx, GL_ELEMENT_ARRAY_BUFFER_BINDING, 0);
+			expectError(GL_NO_ERROR);
+
+			// after setting to 2
+			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
+			m_verifier->verifyInteger(m_testCtx, GL_ELEMENT_ARRAY_BUFFER_BINDING, buffers[1]);
+			expectError(GL_NO_ERROR);
+
+			// vao 1 still has buffer 1 bound?
+			glBindVertexArray(vaos[0]);
+			m_verifier->verifyInteger(m_testCtx, GL_ELEMENT_ARRAY_BUFFER_BINDING, buffers[0]);
+			expectError(GL_NO_ERROR);
+
+			// deleting clears from bound vaos ...
+			glDeleteBuffers(2, buffers);
+			m_verifier->verifyInteger(m_testCtx, GL_ELEMENT_ARRAY_BUFFER_BINDING, 0);
+			expectError(GL_NO_ERROR);
+
+			// ... but does not from non-bound vaos?
+			glBindVertexArray(vaos[1]);
+			m_verifier->verifyInteger(m_testCtx, GL_ELEMENT_ARRAY_BUFFER_BINDING, buffers[1]);
+			expectError(GL_NO_ERROR);
+
+			glDeleteVertexArrays(2, vaos);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class StencilClearValueTestCase : public ApiCase
+{
+public:
+	StencilClearValueTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_STENCIL_CLEAR_VALUE, 0);
+		expectError(GL_NO_ERROR);
+
+		const int stencilBits = m_context.getRenderTarget().getStencilBits();
+
+		for (int stencilBit = 0; stencilBit < stencilBits; ++stencilBit)
+		{
+			const int ref = 1 << stencilBit;
+
+			glClearStencil(ref); // mask should not affect the REF
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_STENCIL_CLEAR_VALUE, ref);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ActiveTextureTestCase : public ApiCase
+{
+public:
+	ActiveTextureTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_ACTIVE_TEXTURE, GL_TEXTURE0);
+		expectError(GL_NO_ERROR);
+
+		GLint textureUnits = 0;
+		glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &textureUnits);
+		expectError(GL_NO_ERROR);
+
+		for (int ndx = 0; ndx < textureUnits; ++ndx)
+		{
+			glActiveTexture(GL_TEXTURE0 + ndx);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_ACTIVE_TEXTURE, GL_TEXTURE0 + ndx);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*		m_verifier;
+};
+
+class RenderbufferBindingTestCase : public ApiCase
+{
+public:
+	RenderbufferBindingTestCase	(Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_RENDERBUFFER_BINDING, 0);
+		expectError(GL_NO_ERROR);
+
+		GLuint renderBuffer = 0;
+		glGenRenderbuffers(1, &renderBuffer);
+		expectError(GL_NO_ERROR);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_RENDERBUFFER_BINDING, renderBuffer);
+		expectError(GL_NO_ERROR);
+
+		glDeleteRenderbuffers(1, &renderBuffer);
+		m_verifier->verifyInteger(m_testCtx, GL_RENDERBUFFER_BINDING, 0);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*		m_verifier;
+};
+
+class SamplerObjectBindingTestCase : public ApiCase
+{
+public:
+	SamplerObjectBindingTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_SAMPLER_BINDING, 0);
+		expectError(GL_NO_ERROR);
+
+		{
+			const tcu::ScopedLogSection section(m_log, "SingleUnit", "Single unit");
+			GLuint sampler = 0;
+			glGenSamplers(1, &sampler);
+			expectError(GL_NO_ERROR);
+
+			glBindSampler(0, sampler);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_SAMPLER_BINDING, sampler);
+			expectError(GL_NO_ERROR);
+
+			glDeleteSamplers(1, &sampler);
+			m_verifier->verifyInteger(m_testCtx, GL_SAMPLER_BINDING, 0);
+			expectError(GL_NO_ERROR);
+		}
+
+		{
+			const tcu::ScopedLogSection section(m_log, "MultipleUnits", "Multiple units");
+
+			GLuint samplerA = 0;
+			GLuint samplerB = 0;
+			glGenSamplers(1, &samplerA);
+			glGenSamplers(1, &samplerB);
+			expectError(GL_NO_ERROR);
+
+			glBindSampler(1, samplerA);
+			glBindSampler(2, samplerB);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, GL_SAMPLER_BINDING, 0);
+			expectError(GL_NO_ERROR);
+
+			glActiveTexture(GL_TEXTURE1);
+			m_verifier->verifyInteger(m_testCtx, GL_SAMPLER_BINDING, samplerA);
+			expectError(GL_NO_ERROR);
+
+			glActiveTexture(GL_TEXTURE2);
+			m_verifier->verifyInteger(m_testCtx, GL_SAMPLER_BINDING, samplerB);
+			expectError(GL_NO_ERROR);
+
+			glDeleteSamplers(1, &samplerB);
+			glDeleteSamplers(1, &samplerA);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	StateVerifier*		m_verifier;
+};
+
+class TextureBindingTestCase : public ApiCase
+{
+public:
+	TextureBindingTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum testBindingName, GLenum textureType)
+		: ApiCase				(context, name, description)
+		, m_verifier			(verifier)
+		, m_testBindingName		(testBindingName)
+		, m_textureType			(textureType)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_testBindingName, 0);
+		expectError(GL_NO_ERROR);
+
+		GLuint texture = 0;
+		glGenTextures(1, &texture);
+		expectError(GL_NO_ERROR);
+
+		glBindTexture(m_textureType, texture);
+		m_verifier->verifyInteger(m_testCtx, m_testBindingName, texture);
+
+		glDeleteTextures(1, &texture);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, m_testBindingName, 0);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+	GLenum		m_testBindingName;
+	GLenum		m_textureType;
+};
+
+class FrameBufferBindingTestCase : public ApiCase
+{
+public:
+	FrameBufferBindingTestCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_FRAMEBUFFER_BINDING,	0);
+		m_verifier->verifyInteger(m_testCtx, GL_FRAMEBUFFER_BINDING,			0);
+		m_verifier->verifyInteger(m_testCtx, GL_READ_FRAMEBUFFER_BINDING,	0);
+		expectError(GL_NO_ERROR);
+
+		GLuint framebufferId = 0;
+		glGenFramebuffers(1, &framebufferId);
+		expectError(GL_NO_ERROR);
+
+		glBindFramebuffer(GL_FRAMEBUFFER, framebufferId);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_FRAMEBUFFER_BINDING,	framebufferId);
+		m_verifier->verifyInteger(m_testCtx, GL_FRAMEBUFFER_BINDING,		framebufferId);
+		m_verifier->verifyInteger(m_testCtx, GL_READ_FRAMEBUFFER_BINDING,	framebufferId);
+		expectError(GL_NO_ERROR);
+
+		glBindFramebuffer(GL_FRAMEBUFFER, 0);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_FRAMEBUFFER_BINDING,	0);
+		m_verifier->verifyInteger(m_testCtx, GL_FRAMEBUFFER_BINDING,			0);
+		m_verifier->verifyInteger(m_testCtx, GL_READ_FRAMEBUFFER_BINDING,	0);
+		expectError(GL_NO_ERROR);
+
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferId);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_FRAMEBUFFER_BINDING,	0);
+		m_verifier->verifyInteger(m_testCtx, GL_FRAMEBUFFER_BINDING,		0);
+		m_verifier->verifyInteger(m_testCtx, GL_READ_FRAMEBUFFER_BINDING,	framebufferId);
+		expectError(GL_NO_ERROR);
+
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebufferId);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_FRAMEBUFFER_BINDING,	framebufferId);
+		m_verifier->verifyInteger(m_testCtx, GL_FRAMEBUFFER_BINDING,		framebufferId);
+		m_verifier->verifyInteger(m_testCtx, GL_READ_FRAMEBUFFER_BINDING,	framebufferId);
+		expectError(GL_NO_ERROR);
+
+		glDeleteFramebuffers(1, &framebufferId);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_FRAMEBUFFER_BINDING,	0);
+		m_verifier->verifyInteger(m_testCtx, GL_FRAMEBUFFER_BINDING,		0);
+		m_verifier->verifyInteger(m_testCtx, GL_READ_FRAMEBUFFER_BINDING,	0);
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ImplementationColorReadTestCase : public ApiCase
+{
+public:
+	ImplementationColorReadTestCase	(Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		const GLint defaultColorTypes[] =
+		{
+			GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT,
+			GL_UNSIGNED_INT, GL_INT, GL_HALF_FLOAT, GL_FLOAT, GL_UNSIGNED_SHORT_5_6_5,
+			GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1,
+			GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV
+		};
+		const GLint defaultColorFormats[] =
+		{
+			GL_RGBA, GL_RGBA_INTEGER, GL_RGB, GL_RGB_INTEGER,
+			GL_RG, GL_RG_INTEGER, GL_RED, GL_RED_INTEGER
+		};
+
+		std::vector<GLint> validColorTypes;
+		std::vector<GLint> validColorFormats;
+
+		// Defined by the spec
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(defaultColorTypes); ++ndx)
+			validColorTypes.push_back(defaultColorTypes[ndx]);
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(defaultColorFormats); ++ndx)
+			validColorFormats.push_back(defaultColorFormats[ndx]);
+
+		// Extensions
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_format_BGRA8888") ||
+			m_context.getContextInfo().isExtensionSupported("GL_APPLE_texture_format_BGRA8888"))
+			validColorFormats.push_back(GL_BGRA);
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_EXT_read_format_bgra"))
+		{
+			validColorFormats.push_back(GL_BGRA);
+			validColorTypes.push_back(GL_UNSIGNED_SHORT_4_4_4_4_REV);
+			validColorTypes.push_back(GL_UNSIGNED_SHORT_1_5_5_5_REV);
+		}
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_IMG_read_format"))
+		{
+			validColorFormats.push_back(GL_BGRA);
+			validColorTypes.push_back(GL_UNSIGNED_SHORT_4_4_4_4_REV);
+		}
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_NV_sRGB_formats"))
+		{
+			validColorFormats.push_back(GL_SLUMINANCE_NV);
+			validColorFormats.push_back(GL_SLUMINANCE_ALPHA_NV);
+		}
+
+		if (m_context.getContextInfo().isExtensionSupported("GL_NV_bgr"))
+		{
+			validColorFormats.push_back(GL_BGR_NV);
+		}
+
+		m_verifier->verifyIntegerAnyOf(m_testCtx, GL_IMPLEMENTATION_COLOR_READ_TYPE,	&validColorTypes[0],	validColorTypes.size());
+		m_verifier->verifyIntegerAnyOf(m_testCtx, GL_IMPLEMENTATION_COLOR_READ_FORMAT,	&validColorFormats[0],	validColorFormats.size());
+		expectError(GL_NO_ERROR);
+	}
+
+private:
+	StateVerifier*	m_verifier;
+};
+
+class ReadBufferCase : public ApiCase
+{
+public:
+	ReadBufferCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		const GLint validInitialValues[] = {GL_BACK, GL_NONE};
+		m_verifier->verifyIntegerAnyOf(m_testCtx, GL_READ_BUFFER, validInitialValues, DE_LENGTH_OF_ARRAY(validInitialValues));
+		expectError(GL_NO_ERROR);
+
+		glReadBuffer(GL_NONE);
+		m_verifier->verifyInteger(m_testCtx, GL_READ_BUFFER, GL_NONE);
+		expectError(GL_NO_ERROR);
+
+		glReadBuffer(GL_BACK);
+		m_verifier->verifyInteger(m_testCtx, GL_READ_BUFFER, GL_BACK);
+		expectError(GL_NO_ERROR);
+
+		// test GL_READ_BUFFER with framebuffers
+
+		GLuint framebufferId = 0;
+		glGenFramebuffers(1, &framebufferId);
+		expectError(GL_NO_ERROR);
+
+		GLuint renderbuffer_id = 0;
+		glGenRenderbuffers(1, &renderbuffer_id);
+		expectError(GL_NO_ERROR);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer_id);
+		expectError(GL_NO_ERROR);
+
+		glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 128, 128);
+		expectError(GL_NO_ERROR);
+
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferId);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer_id);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_READ_BUFFER, GL_COLOR_ATTACHMENT0);
+
+		glDeleteFramebuffers(1, &framebufferId);
+		glDeleteRenderbuffers(1, &renderbuffer_id);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_READ_BUFFER, GL_BACK);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+class DrawBufferCase : public ApiCase
+{
+public:
+	DrawBufferCase (Context& context, StateVerifier* verifier, const char* name, const char* description)
+		: ApiCase		(context, name, description)
+		, m_verifier	(verifier)
+	{
+	}
+
+	void test (void)
+	{
+		const GLint validInitialValues[] = {GL_BACK, GL_NONE};
+		m_verifier->verifyIntegerAnyOf(m_testCtx, GL_DRAW_BUFFER0, validInitialValues, DE_LENGTH_OF_ARRAY(validInitialValues));
+		expectError(GL_NO_ERROR);
+
+		GLenum bufs = GL_NONE;
+		glDrawBuffers(1, &bufs);
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_BUFFER0, GL_NONE);
+		expectError(GL_NO_ERROR);
+
+		bufs = GL_BACK;
+		glDrawBuffers(1, &bufs);
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_BUFFER0, GL_BACK);
+		expectError(GL_NO_ERROR);
+
+		// test GL_DRAW_BUFFER with framebuffers
+
+		GLuint framebufferId = 0;
+		glGenFramebuffers(1, &framebufferId);
+		expectError(GL_NO_ERROR);
+
+		GLuint renderbuffer_ids[2] = {0};
+		glGenRenderbuffers(2, renderbuffer_ids);
+		expectError(GL_NO_ERROR);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer_ids[0]);
+		expectError(GL_NO_ERROR);
+		glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 128, 128);
+		expectError(GL_NO_ERROR);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer_ids[1]);
+		expectError(GL_NO_ERROR);
+		glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 128, 128);
+		expectError(GL_NO_ERROR);
+
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebufferId);
+		expectError(GL_NO_ERROR);
+
+		glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer_ids[0]);
+		expectError(GL_NO_ERROR);
+		glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, renderbuffer_ids[1]);
+		expectError(GL_NO_ERROR);
+
+		// only the initial state the draw buffer for fragment color zero is defined
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_BUFFER0, GL_COLOR_ATTACHMENT0);
+
+		GLenum bufTargets[2] = {GL_NONE, GL_COLOR_ATTACHMENT1};
+		glDrawBuffers(2, bufTargets);
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_BUFFER0, GL_NONE);
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_BUFFER1, GL_COLOR_ATTACHMENT1);
+
+		glDeleteFramebuffers(1, &framebufferId);
+		glDeleteRenderbuffers(2, renderbuffer_ids);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, GL_DRAW_BUFFER0, GL_BACK);
+		expectError(GL_NO_ERROR);
+	}
+private:
+	StateVerifier*	m_verifier;
+};
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		StateVerifier* verifier = VERIFIERS[_verifierNdx];										\
+		CODE_BLOCK;																				\
+	}
+
+} // anonymous
+
+IntegerStateQueryTests::IntegerStateQueryTests (Context& context)
+	: TestCaseGroup			(context, "integers", "Integer Values")
+	, m_verifierBoolean		(DE_NULL)
+	, m_verifierInteger		(DE_NULL)
+	, m_verifierInteger64	(DE_NULL)
+	, m_verifierFloat		(DE_NULL)
+{
+}
+
+IntegerStateQueryTests::~IntegerStateQueryTests (void)
+{
+	deinit();
+}
+
+void IntegerStateQueryTests::init (void)
+{
+	DE_ASSERT(m_verifierBoolean == DE_NULL);
+	DE_ASSERT(m_verifierInteger == DE_NULL);
+	DE_ASSERT(m_verifierInteger64 == DE_NULL);
+	DE_ASSERT(m_verifierFloat == DE_NULL);
+
+	m_verifierBoolean		= new GetBooleanVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInteger		= new GetIntegerVerifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierInteger64		= new GetInteger64Verifier	(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierFloat			= new GetFloatVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+
+	const struct LimitedStateInteger
+	{
+		const char*		name;
+		const char*		description;
+		GLenum			targetName;
+		GLint			value;
+	} implementationMinLimits[] =
+	{
+		{ "subpixel_bits",									"SUBPIXEL_BITS has minimum value of 4",										GL_SUBPIXEL_BITS,									4	},
+		{ "max_3d_texture_size",							"MAX_3D_TEXTURE_SIZE has minimum value of 256",								GL_MAX_3D_TEXTURE_SIZE,								256	},
+		{ "max_texture_size",								"MAX_TEXTURE_SIZE has minimum value of 2048",								GL_MAX_TEXTURE_SIZE,								2048},
+		{ "max_array_texture_layers",						"MAX_ARRAY_TEXTURE_LAYERS has minimum value of 256",						GL_MAX_ARRAY_TEXTURE_LAYERS,						256	},
+		{ "max_cube_map_texture_size",						"MAX_CUBE_MAP_TEXTURE_SIZE has minimum value of 2048",						GL_MAX_CUBE_MAP_TEXTURE_SIZE,						2048},
+		{ "max_renderbuffer_size",							"MAX_RENDERBUFFER_SIZE has minimum value of 2048",							GL_MAX_RENDERBUFFER_SIZE,							2048},
+		{ "max_draw_buffers",								"MAX_DRAW_BUFFERS has minimum value of 4",									GL_MAX_DRAW_BUFFERS,								4	},
+		{ "max_color_attachments",							"MAX_COLOR_ATTACHMENTS has minimum value of 4",								GL_MAX_COLOR_ATTACHMENTS,							4	},
+		{ "max_elements_indices",							"MAX_ELEMENTS_INDICES has minimum value of 0",								GL_MAX_ELEMENTS_INDICES,							0	},
+		{ "max_elements_vertices",							"MAX_ELEMENTS_VERTICES has minimum value of 0",								GL_MAX_ELEMENTS_VERTICES,							0	},
+		{ "num_extensions",									"NUM_EXTENSIONS has minimum value of 0",									GL_NUM_EXTENSIONS,									0	},
+		{ "major_version",									"MAJOR_VERSION has minimum value of 3",										GL_MAJOR_VERSION,									3	},
+		{ "minor_version",									"MINOR_VERSION has minimum value of 0",										GL_MINOR_VERSION,									0	},
+		{ "max_vertex_attribs",								"MAX_VERTEX_ATTRIBS has minimum value of 16",								GL_MAX_VERTEX_ATTRIBS,								16	},
+		{ "max_vertex_uniform_components",					"MAX_VERTEX_UNIFORM_COMPONENTS has minimum value of 1024",					GL_MAX_VERTEX_UNIFORM_COMPONENTS,					1024},
+		{ "max_vertex_uniform_vectors",						"MAX_VERTEX_UNIFORM_VECTORS has minimum value of 256",						GL_MAX_VERTEX_UNIFORM_VECTORS,						256	},
+		{ "max_vertex_uniform_blocks",						"MAX_VERTEX_UNIFORM_BLOCKS has minimum value of 12",						GL_MAX_VERTEX_UNIFORM_BLOCKS,						12	},
+		{ "max_vertex_output_components",					"MAX_VERTEX_OUTPUT_COMPONENTS has minimum value of 64",						GL_MAX_VERTEX_OUTPUT_COMPONENTS,					64	},
+		{ "max_vertex_texture_image_units",					"MAX_VERTEX_TEXTURE_IMAGE_UNITS has minimum value of 16",					GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,					16	},
+		{ "max_fragment_uniform_components",				"MAX_FRAGMENT_UNIFORM_COMPONENTS has minimum value of 896",					GL_MAX_FRAGMENT_UNIFORM_COMPONENTS,					896	},
+		{ "max_fragment_uniform_vectors",					"MAX_FRAGMENT_UNIFORM_VECTORS has minimum value of 224",					GL_MAX_FRAGMENT_UNIFORM_VECTORS,					224	},
+		{ "max_fragment_uniform_blocks",					"MAX_FRAGMENT_UNIFORM_BLOCKS has minimum value of 12",						GL_MAX_FRAGMENT_UNIFORM_BLOCKS,						12	},
+		{ "max_fragment_input_components",					"MAX_FRAGMENT_INPUT_COMPONENTS has minimum value of 60",					GL_MAX_FRAGMENT_INPUT_COMPONENTS,					60	},
+		{ "max_texture_image_units",						"MAX_TEXTURE_IMAGE_UNITS has minimum value of 16",							GL_MAX_TEXTURE_IMAGE_UNITS,							16	},
+		{ "max_program_texel_offset",						"MAX_PROGRAM_TEXEL_OFFSET has minimum value of 7",							GL_MAX_PROGRAM_TEXEL_OFFSET,						7	},
+		{ "max_uniform_buffer_bindings",					"MAX_UNIFORM_BUFFER_BINDINGS has minimum value of 24",						GL_MAX_UNIFORM_BUFFER_BINDINGS,						24	},
+		{ "max_combined_uniform_blocks",					"MAX_COMBINED_UNIFORM_BLOCKS has minimum value of 24",						GL_MAX_COMBINED_UNIFORM_BLOCKS,						24	},
+		{ "max_varying_components",							"MAX_VARYING_COMPONENTS has minimum value of 60",							GL_MAX_VARYING_COMPONENTS,							60	},
+		{ "max_varying_vectors",							"MAX_VARYING_VECTORS has minimum value of 15",								GL_MAX_VARYING_VECTORS,								15	},
+		{ "max_combined_texture_image_units",				"MAX_COMBINED_TEXTURE_IMAGE_UNITS has minimum value of 32",					GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,				32	},
+		{ "max_transform_feedback_interleaved_components",	"MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS has minimum value of 64",	GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,	64	},
+		{ "max_transform_feedback_separate_attribs",		"MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS has minimum value of 4",			GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,			4	},
+		{ "max_transform_feedback_separate_components",		"MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS has minimum value of 4",		GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS,		4	},
+		{ "max_samples",									"MAX_SAMPLES has minimum value of 4",										GL_MAX_SAMPLES,										4	},
+		{ "red_bits",										"RED_BITS has minimum value of 0",											GL_RED_BITS,										0	},
+		{ "green_bits",										"GREEN_BITS has minimum value of 0",										GL_GREEN_BITS,										0	},
+		{ "blue_bits",										"BLUE_BITS has minimum value of 0",											GL_BLUE_BITS,										0	},
+		{ "alpha_bits",										"ALPHA_BITS has minimum value of 0",										GL_ALPHA_BITS,										0	},
+		{ "depth_bits",										"DEPTH_BITS has minimum value of 0",										GL_DEPTH_BITS,										0	},
+		{ "stencil_bits",									"STENCIL_BITS has minimum value of 0",										GL_STENCIL_BITS,									0	},
+	};
+	const LimitedStateInteger implementationMaxLimits[] =
+	{
+		{ "min_program_texel_offset",						"MIN_PROGRAM_TEXEL_OFFSET has maximum value of -8",							GL_MIN_PROGRAM_TEXEL_OFFSET,						-8	},
+		{ "uniform_buffer_offset_alignment",				"UNIFORM_BUFFER_OFFSET_ALIGNMENT has minimum value of 1",					GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT,					256	},
+	};
+
+	// \note implementation defined limits have their own tests so just check the conversions to boolean, int64 and float
+	StateVerifier* implementationLimitVerifiers[] = {m_verifierBoolean, m_verifierInteger64, m_verifierFloat};
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(implementationMinLimits); testNdx++)
+		FOR_EACH_VERIFIER(implementationLimitVerifiers, addChild(new ConstantMinimumValueTestCase(m_context, verifier, (std::string(implementationMinLimits[testNdx].name) + verifier->getTestNamePostfix()).c_str(), implementationMinLimits[testNdx].description, implementationMinLimits[testNdx].targetName, implementationMinLimits[testNdx].value)));
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(implementationMaxLimits); testNdx++)
+		FOR_EACH_VERIFIER(implementationLimitVerifiers, addChild(new ConstantMaximumValueTestCase(m_context, verifier, (std::string(implementationMaxLimits[testNdx].name) + verifier->getTestNamePostfix()).c_str(), implementationMaxLimits[testNdx].description, implementationMaxLimits[testNdx].targetName, implementationMaxLimits[testNdx].value)));
+
+	StateVerifier* normalVerifiers[] = {m_verifierBoolean, m_verifierInteger, m_verifierInteger64, m_verifierFloat};
+
+	FOR_EACH_VERIFIER(implementationLimitVerifiers, addChild(new SampleBuffersTestCase		(m_context,	 verifier, (std::string("sample_buffers")						+ verifier->getTestNamePostfix()).c_str(),		"SAMPLE_BUFFERS")));
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new SamplesTestCase				(m_context,	 verifier, (std::string("samples")								+ verifier->getTestNamePostfix()).c_str(),		"SAMPLES")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new HintTestCase				(m_context,	 verifier, (std::string("generate_mipmap_hint")					+ verifier->getTestNamePostfix()).c_str(),		"GENERATE_MIPMAP_HINT",				GL_GENERATE_MIPMAP_HINT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new HintTestCase				(m_context,	 verifier, (std::string("fragment_shader_derivative_hint")		+ verifier->getTestNamePostfix()).c_str(),		"FRAGMENT_SHADER_DERIVATIVE_HINT",	GL_FRAGMENT_SHADER_DERIVATIVE_HINT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new DepthFuncTestCase			(m_context,	 verifier, (std::string("depth_func")							+ verifier->getTestNamePostfix()).c_str(),		"DEPTH_FUNC")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new CullFaceTestCase			(m_context,	 verifier, (std::string("cull_face_mode")						+ verifier->getTestNamePostfix()).c_str(),		"CULL_FACE_MODE")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new FrontFaceTestCase			(m_context,	 verifier, (std::string("front_face_mode")						+ verifier->getTestNamePostfix()).c_str(),		"FRONT_FACE")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ViewPortTestCase			(m_context,	 verifier, (std::string("viewport")								+ verifier->getTestNamePostfix()).c_str(),		"VIEWPORT")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ScissorBoxTestCase			(m_context,	 verifier, (std::string("scissor_box")							+ verifier->getTestNamePostfix()).c_str(),		"SCISSOR_BOX")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new MaxViewportDimsTestCase		(m_context,	 verifier, (std::string("max_viewport_dims")					+ verifier->getTestNamePostfix()).c_str(),		"MAX_VIEWPORT_DIMS")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefTestCase			(m_context,	 verifier, (std::string("stencil_ref")							+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_REF",						GL_STENCIL_REF)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefTestCase			(m_context,	 verifier, (std::string("stencil_back_ref")						+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_BACK_REF",					GL_STENCIL_BACK_REF)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefSeparateTestCase	(m_context,	 verifier, (std::string("stencil_ref_separate")					+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_REF (separate)",			GL_STENCIL_REF,			GL_FRONT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefSeparateTestCase	(m_context,	 verifier, (std::string("stencil_ref_separate_both")			+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_REF (separate)",			GL_STENCIL_REF,			GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefSeparateTestCase	(m_context,	 verifier, (std::string("stencil_back_ref_separate")			+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_BACK_REF (separate)",		GL_STENCIL_BACK_REF,	GL_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilRefSeparateTestCase	(m_context,	 verifier, (std::string("stencil_back_ref_separate_both")		+ verifier->getTestNamePostfix()).c_str(),		"STENCIL_BACK_REF (separate)",		GL_STENCIL_BACK_REF,	GL_FRONT_AND_BACK)));
+
+	const struct NamedStencilOp
+	{
+		const char*		name;
+
+		const char*		frontDescription;
+		GLenum			frontTarget;
+		const char*		backDescription;
+		GLenum			backTarget;
+	} stencilOps[] =
+	{
+		{ "fail",		"STENCIL_FAIL",				GL_STENCIL_FAIL,			"STENCIL_BACK_FAIL",			GL_STENCIL_BACK_FAIL			},
+		{ "depth_fail",	"STENCIL_PASS_DEPTH_FAIL",	GL_STENCIL_PASS_DEPTH_FAIL,	"STENCIL_BACK_PASS_DEPTH_FAIL",	GL_STENCIL_BACK_PASS_DEPTH_FAIL	},
+		{ "depth_pass",	"STENCIL_PASS_DEPTH_PASS",	GL_STENCIL_PASS_DEPTH_PASS,	"STENCIL_BACK_PASS_DEPTH_PASS",	GL_STENCIL_BACK_PASS_DEPTH_PASS	}
+	};
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(stencilOps); testNdx++)
+	{
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpTestCase			(m_context, verifier, (std::string("stencil_")		+ stencilOps[testNdx].name + verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].frontDescription,	stencilOps[testNdx].frontTarget)));
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpTestCase			(m_context, verifier, (std::string("stencil_back_")	+ stencilOps[testNdx].name + verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].backDescription,	stencilOps[testNdx].backTarget)));
+
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpSeparateTestCase	(m_context, verifier, (std::string("stencil_")		+ stencilOps[testNdx].name + "_separate_both"	+ verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].frontDescription,	stencilOps[testNdx].frontTarget,	GL_FRONT_AND_BACK)));
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpSeparateTestCase	(m_context, verifier, (std::string("stencil_back_")	+ stencilOps[testNdx].name + "_separate_both"	+ verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].backDescription,		stencilOps[testNdx].backTarget,		GL_FRONT_AND_BACK)));
+
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpSeparateTestCase	(m_context, verifier, (std::string("stencil_")		+ stencilOps[testNdx].name + "_separate"		+ verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].frontDescription,	stencilOps[testNdx].frontTarget,	GL_FRONT)));
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilOpSeparateTestCase	(m_context, verifier, (std::string("stencil_back_")	+ stencilOps[testNdx].name + "_separate"		+ verifier->getTestNamePostfix()).c_str(), stencilOps[testNdx].backDescription,		stencilOps[testNdx].backTarget,		GL_BACK)));
+	}
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilFuncTestCase					(m_context, verifier,	(std::string("stencil_func")								+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_FUNC")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilFuncSeparateTestCase			(m_context, verifier,	(std::string("stencil_func_separate")						+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_FUNC (separate)",				GL_STENCIL_FUNC,				GL_FRONT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilFuncSeparateTestCase			(m_context, verifier,	(std::string("stencil_func_separate_both")					+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_FUNC (separate)",				GL_STENCIL_FUNC,				GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilFuncSeparateTestCase			(m_context, verifier,	(std::string("stencil_back_func_separate")					+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_FUNC (separate)",				GL_STENCIL_BACK_FUNC,			GL_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilFuncSeparateTestCase			(m_context, verifier,	(std::string("stencil_back_func_separate_both")				+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_FUNC (separate)",				GL_STENCIL_BACK_FUNC,			GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskTestCase					(m_context, verifier,	(std::string("stencil_value_mask")							+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_VALUE_MASK",					GL_STENCIL_VALUE_MASK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskTestCase					(m_context, verifier,	(std::string("stencil_back_value_mask")						+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_VALUE_MASK",				GL_STENCIL_BACK_VALUE_MASK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskSeparateTestCase			(m_context, verifier,	(std::string("stencil_value_mask_separate")					+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_VALUE_MASK (separate)",		GL_STENCIL_VALUE_MASK,			GL_FRONT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskSeparateTestCase			(m_context, verifier,	(std::string("stencil_value_mask_separate_both")			+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_VALUE_MASK (separate)",		GL_STENCIL_VALUE_MASK,			GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskSeparateTestCase			(m_context, verifier,	(std::string("stencil_back_value_mask_separate")			+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_VALUE_MASK (separate)",	GL_STENCIL_BACK_VALUE_MASK,		GL_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilMaskSeparateTestCase			(m_context, verifier,	(std::string("stencil_back_value_mask_separate_both")		+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_VALUE_MASK (separate)",	GL_STENCIL_BACK_VALUE_MASK,		GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskTestCase			(m_context, verifier,	(std::string("stencil_writemask")							+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_WRITEMASK",					GL_STENCIL_WRITEMASK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskTestCase			(m_context, verifier,	(std::string("stencil_back_writemask")						+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_WRITEMASK",				GL_STENCIL_BACK_WRITEMASK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskSeparateTestCase	(m_context, verifier,	(std::string("stencil_writemask_separate")					+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_WRITEMASK (separate)",			GL_STENCIL_WRITEMASK,			GL_FRONT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskSeparateTestCase	(m_context, verifier,	(std::string("stencil_writemask_separate_both")				+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_WRITEMASK (separate)",			GL_STENCIL_WRITEMASK,			GL_FRONT_AND_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskSeparateTestCase	(m_context, verifier,	(std::string("stencil_back_writemask_separate")				+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_WRITEMASK (separate)",	GL_STENCIL_BACK_WRITEMASK,		GL_BACK)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilWriteMaskSeparateTestCase	(m_context, verifier,	(std::string("stencil_back_writemask_separate_both")		+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_BACK_WRITEMASK (separate)",	GL_STENCIL_BACK_WRITEMASK,		GL_FRONT_AND_BACK)));
+
+	const struct PixelStoreState
+	{
+		const char*	name;
+		const char*	description;
+		GLenum		target;
+		int			initialValue;
+	} pixelStoreStates[] =
+	{
+		{ "unpack_image_height","UNPACK_IMAGE_HEIGHT",	GL_UNPACK_IMAGE_HEIGHT,	0 },
+		{ "unpack_skip_images",	"UNPACK_SKIP_IMAGES",	GL_UNPACK_SKIP_IMAGES,	0 },
+		{ "unpack_row_length",	"UNPACK_ROW_LENGTH",	GL_UNPACK_ROW_LENGTH,	0 },
+		{ "unpack_skip_rows",	"UNPACK_SKIP_ROWS",		GL_UNPACK_SKIP_ROWS,	0 },
+		{ "unpack_skip_pixels",	"UNPACK_SKIP_PIXELS",	GL_UNPACK_SKIP_PIXELS,	0 },
+		{ "pack_row_length",	"PACK_ROW_LENGTH",		GL_PACK_ROW_LENGTH,		0 },
+		{ "pack_skip_rows",		"PACK_SKIP_ROWS",		GL_PACK_SKIP_ROWS,		0 },
+		{ "pack_skip_pixels",	"PACK_SKIP_PIXELS",		GL_PACK_SKIP_PIXELS,	0 }
+	};
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(pixelStoreStates); testNdx++)
+	{
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new PixelStoreTestCase(m_context, verifier, (std::string(pixelStoreStates[testNdx].name) + verifier->getTestNamePostfix()).c_str(), pixelStoreStates[testNdx].description, pixelStoreStates[testNdx].target, pixelStoreStates[testNdx].initialValue)));
+	}
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new PixelStoreAlignTestCase(m_context, verifier, (std::string("unpack_alignment")	+ verifier->getTestNamePostfix()).c_str(),	"UNPACK_ALIGNMENT",	GL_UNPACK_ALIGNMENT)));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new PixelStoreAlignTestCase(m_context, verifier, (std::string("pack_alignment")		+ verifier->getTestNamePostfix()).c_str(),	"PACK_ALIGNMENT",	GL_PACK_ALIGNMENT)));
+
+	const struct BlendColorState
+	{
+		const char*	name;
+		const char*	description;
+		GLenum		target;
+		int			initialValue;
+	} blendColorStates[] =
+	{
+		{ "blend_src_rgb",		"BLEND_SRC_RGB",	GL_BLEND_SRC_RGB,		GL_ONE	},
+		{ "blend_src_alpha",	"BLEND_SRC_ALPHA",	GL_BLEND_SRC_ALPHA,		GL_ONE	},
+		{ "blend_dst_rgb",		"BLEND_DST_RGB",	GL_BLEND_DST_RGB,		GL_ZERO	},
+		{ "blend_dst_alpha",	"BLEND_DST_ALPHA",	GL_BLEND_DST_ALPHA,		GL_ZERO	}
+	};
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(blendColorStates); testNdx++)
+	{
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new BlendFuncTestCase			(m_context, verifier, (std::string(blendColorStates[testNdx].name)					+ verifier->getTestNamePostfix()).c_str(),	blendColorStates[testNdx].description,	blendColorStates[testNdx].target,	blendColorStates[testNdx].initialValue)));
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new BlendFuncSeparateTestCase	(m_context, verifier, (std::string(blendColorStates[testNdx].name) + "_separate"	+ verifier->getTestNamePostfix()).c_str(),	blendColorStates[testNdx].description,	blendColorStates[testNdx].target,	blendColorStates[testNdx].initialValue)));
+	}
+
+	const struct BlendEquationState
+	{
+		const char*	name;
+		const char*	description;
+		GLenum		target;
+		int			initialValue;
+	} blendEquationStates[] =
+	{
+		{ "blend_equation_rgb",		"BLEND_EQUATION_RGB",	GL_BLEND_EQUATION_RGB,		GL_FUNC_ADD	},
+		{ "blend_equation_alpha",	"BLEND_EQUATION_ALPHA",	GL_BLEND_EQUATION_ALPHA,	GL_FUNC_ADD	}
+	};
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(blendEquationStates); testNdx++)
+	{
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new BlendEquationTestCase			(m_context, verifier, (std::string(blendEquationStates[testNdx].name) +				+ verifier->getTestNamePostfix()).c_str(),		blendEquationStates[testNdx].description,	blendEquationStates[testNdx].target,	blendEquationStates[testNdx].initialValue)));
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new BlendEquationSeparateTestCase	(m_context, verifier, (std::string(blendEquationStates[testNdx].name) + "_separate"	+ verifier->getTestNamePostfix()).c_str(),		blendEquationStates[testNdx].description,	blendEquationStates[testNdx].target,	blendEquationStates[testNdx].initialValue)));
+	}
+
+	const struct ImplementationArrayReturningState
+	{
+		const char*	name;
+		const char*	description;
+		GLenum		target;
+		GLenum		targetLengthTarget;
+		int			minLength;
+	} implementationArrayReturningStates[] =
+	{
+		{ "compressed_texture_formats",		"COMPRESSED_TEXTURE_FORMATS",	GL_COMPRESSED_TEXTURE_FORMATS,	GL_NUM_COMPRESSED_TEXTURE_FORMATS,	10	},
+		{ "program_binary_formats",			"PROGRAM_BINARY_FORMATS",		GL_PROGRAM_BINARY_FORMATS,		GL_NUM_PROGRAM_BINARY_FORMATS,		0	},
+		{ "shader_binary_formats",			"SHADER_BINARY_FORMATS",		GL_SHADER_BINARY_FORMATS,		GL_NUM_SHADER_BINARY_FORMATS,		0	}
+	};
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(implementationArrayReturningStates); testNdx++)
+	{
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new ImplementationArrayTestCase(m_context, verifier, (std::string(implementationArrayReturningStates[testNdx].name) + verifier->getTestNamePostfix()).c_str(), implementationArrayReturningStates[testNdx].description,	implementationArrayReturningStates[testNdx].target,	implementationArrayReturningStates[testNdx].targetLengthTarget,	implementationArrayReturningStates[testNdx].minLength)));
+	}
+
+	const struct BufferBindingState
+	{
+		const char*	name;
+		const char*	description;
+		GLenum		target;
+		GLenum		type;
+	} bufferBindingStates[] =
+	{
+		{ "array_buffer_binding",				"ARRAY_BUFFER_BINDING",					GL_ARRAY_BUFFER_BINDING,				GL_ARRAY_BUFFER				},
+		{ "uniform_buffer_binding",				"UNIFORM_BUFFER_BINDING",				GL_UNIFORM_BUFFER_BINDING,				GL_UNIFORM_BUFFER			},
+		{ "pixel_pack_buffer_binding",			"PIXEL_PACK_BUFFER_BINDING",			GL_PIXEL_PACK_BUFFER_BINDING,			GL_PIXEL_PACK_BUFFER		},
+		{ "pixel_unpack_buffer_binding",		"PIXEL_UNPACK_BUFFER_BINDING",			GL_PIXEL_UNPACK_BUFFER_BINDING,			GL_PIXEL_UNPACK_BUFFER		},
+		{ "transform_feedback_buffer_binding",	"TRANSFORM_FEEDBACK_BUFFER_BINDING",	GL_TRANSFORM_FEEDBACK_BUFFER_BINDING,	GL_TRANSFORM_FEEDBACK_BUFFER},
+		{ "copy_read_buffer_binding",			"COPY_READ_BUFFER_BINDING",				GL_COPY_READ_BUFFER_BINDING,			GL_COPY_READ_BUFFER			},
+		{ "copy_write_buffer_binding",			"COPY_WRITE_BUFFER_BINDING",			GL_COPY_WRITE_BUFFER_BINDING,			GL_COPY_WRITE_BUFFER		}
+	};
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(bufferBindingStates); testNdx++)
+	{
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new BufferBindingTestCase(m_context, verifier, (std::string(bufferBindingStates[testNdx].name) + verifier->getTestNamePostfix()).c_str(), bufferBindingStates[testNdx].description, bufferBindingStates[testNdx].target, bufferBindingStates[testNdx].type)));
+	}
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ElementArrayBufferBindingTestCase	(m_context, verifier, (std::string("element_array_buffer_binding")	+ verifier->getTestNamePostfix()).c_str())));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new TransformFeedbackBindingTestCase	(m_context, verifier, (std::string("transform_feedback_binding")	+ verifier->getTestNamePostfix()).c_str())));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new CurrentProgramBindingTestCase		(m_context, verifier, (std::string("current_program_binding")		+ verifier->getTestNamePostfix()).c_str(),	"CURRENT_PROGRAM")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new VertexArrayBindingTestCase			(m_context, verifier, (std::string("vertex_array_binding")			+ verifier->getTestNamePostfix()).c_str(),	"VERTEX_ARRAY_BINDING")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new StencilClearValueTestCase			(m_context, verifier, (std::string("stencil_clear_value")			+ verifier->getTestNamePostfix()).c_str(),	"STENCIL_CLEAR_VALUE")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ActiveTextureTestCase				(m_context, verifier, (std::string("active_texture")				+ verifier->getTestNamePostfix()).c_str(),	"ACTIVE_TEXTURE")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new RenderbufferBindingTestCase			(m_context, verifier, (std::string("renderbuffer_binding")			+ verifier->getTestNamePostfix()).c_str(),	"RENDERBUFFER_BINDING")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new SamplerObjectBindingTestCase		(m_context, verifier, (std::string("sampler_binding")				+ verifier->getTestNamePostfix()).c_str(),	"SAMPLER_BINDING")));
+
+	const struct TextureBinding
+	{
+		const char*	name;
+		const char*	description;
+		GLenum		target;
+		GLenum		type;
+	} textureBindings[] =
+	{
+		{ "texture_binding_2d",			"TEXTURE_BINDING_2D",		GL_TEXTURE_BINDING_2D,			GL_TEXTURE_2D		},
+		{ "texture_binding_3d",			"TEXTURE_BINDING_3D",		GL_TEXTURE_BINDING_3D,			GL_TEXTURE_3D		},
+		{ "texture_binding_2d_array",	"TEXTURE_BINDING_2D_ARRAY",	GL_TEXTURE_BINDING_2D_ARRAY,	GL_TEXTURE_2D_ARRAY	},
+		{ "texture_binding_cube_map",	"TEXTURE_BINDING_CUBE_MAP",	GL_TEXTURE_BINDING_CUBE_MAP,	GL_TEXTURE_CUBE_MAP	}
+	};
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(textureBindings); testNdx++)
+	{
+		FOR_EACH_VERIFIER(normalVerifiers, addChild(new TextureBindingTestCase(m_context, verifier, (std::string(textureBindings[testNdx].name) + verifier->getTestNamePostfix()).c_str(), textureBindings[testNdx].description, textureBindings[testNdx].target, textureBindings[testNdx].type)));
+	}
+
+
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new FrameBufferBindingTestCase		(m_context, verifier, (std::string("framebuffer_binding")			+ verifier->getTestNamePostfix()).c_str(),	"DRAW_FRAMEBUFFER_BINDING and READ_FRAMEBUFFER_BINDING")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ImplementationColorReadTestCase	(m_context, verifier, (std::string("implementation_color_read")		+ verifier->getTestNamePostfix()).c_str(),	"IMPLEMENTATION_COLOR_READ_TYPE and IMPLEMENTATION_COLOR_READ_FORMAT")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new ReadBufferCase					(m_context, verifier, (std::string("read_buffer")					+ verifier->getTestNamePostfix()).c_str(),	"READ_BUFFER")));
+	FOR_EACH_VERIFIER(normalVerifiers, addChild(new DrawBufferCase					(m_context, verifier, (std::string("draw_buffer")					+ verifier->getTestNamePostfix()).c_str(),	"DRAW_BUFFER")));
+}
+
+void IntegerStateQueryTests::deinit (void)
+{
+	if (m_verifierBoolean)
+	{
+		delete m_verifierBoolean;
+		m_verifierBoolean = DE_NULL;
+	}
+	if (m_verifierInteger)
+	{
+		delete m_verifierInteger;
+		m_verifierInteger = DE_NULL;
+	}
+	if (m_verifierInteger64)
+	{
+		delete m_verifierInteger64;
+		m_verifierInteger64 = DE_NULL;
+	}
+	if (m_verifierFloat)
+	{
+		delete m_verifierFloat;
+		m_verifierFloat = DE_NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fIntegerStateQueryTests.hpp b/modules/gles3/functional/es3fIntegerStateQueryTests.hpp
new file mode 100644
index 0000000..27af331
--- /dev/null
+++ b/modules/gles3/functional/es3fIntegerStateQueryTests.hpp
@@ -0,0 +1,68 @@
+#ifndef _ES3FINTEGERSTATEQUERYTESTS_HPP
+#define _ES3FINTEGERSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Integer State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace IntegerStateQueryVerifiers
+{
+
+class GetBooleanVerifier;
+class GetIntegerVerifier;
+class GetInteger64Verifier;
+class GetFloatVerifier;
+
+} // IntegerStateQueryVerifiers
+
+class IntegerStateQueryTests : public TestCaseGroup
+{
+public:
+																IntegerStateQueryTests	(Context& context);
+																~IntegerStateQueryTests	(void);
+
+	void														init					(void);
+	void														deinit					(void);
+
+private:
+																IntegerStateQueryTests	(const IntegerStateQueryTests& other);
+	IntegerStateQueryTests&										operator=				(const IntegerStateQueryTests& other);
+
+	IntegerStateQueryVerifiers::GetBooleanVerifier*				m_verifierBoolean;
+	IntegerStateQueryVerifiers::GetIntegerVerifier*				m_verifierInteger;
+	IntegerStateQueryVerifiers::GetInteger64Verifier*			m_verifierInteger64;
+	IntegerStateQueryVerifiers::GetFloatVerifier*				m_verifierFloat;
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FINTEGERSTATEQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fInternalFormatQueryTests.cpp b/modules/gles3/functional/es3fInternalFormatQueryTests.cpp
new file mode 100644
index 0000000..5955250
--- /dev/null
+++ b/modules/gles3/functional/es3fInternalFormatQueryTests.cpp
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Internal Format Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fInternalFormatQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deMath.h"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+class SamplesCase : public ApiCase
+{
+public:
+	SamplesCase(Context& context, const char* name, const char* description, GLenum internalFormat, bool isIntegerInternalFormat)
+		: ApiCase					(context, name, description)
+		, m_internalFormat			(internalFormat)
+		, m_isIntegerInternalFormat	(isIntegerInternalFormat)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		StateQueryMemoryWriteGuard<GLint> sampleCounts;
+		glGetInternalformativ(GL_RENDERBUFFER, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &sampleCounts);
+		expectError(GL_NO_ERROR);
+
+		if (!sampleCounts.verifyValidity(m_testCtx))
+			return;
+
+		m_testCtx.getLog() << TestLog::Message << "// sample counts is " << sampleCounts << TestLog::EndMessage;
+
+		if (m_isIntegerInternalFormat && sampleCounts != 0)
+		{
+			// Since multisampling is not supported for signed and unsigned integer internal
+			// formats, the value of NUM_SAMPLE_COUNTS will be zero for such formats.
+			m_testCtx.getLog() << TestLog::Message << "// ERROR: integer internal formats should have NUM_SAMPLE_COUNTS = 0; got " << sampleCounts << TestLog::EndMessage;
+			if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+		}
+
+		if (sampleCounts == 0)
+			return;
+
+		std::vector<GLint> samples;
+		samples.resize(sampleCounts, -1);
+		glGetInternalformativ(GL_RENDERBUFFER, m_internalFormat, GL_SAMPLES, sampleCounts, &samples[0]);
+		expectError(GL_NO_ERROR);
+
+		GLint prevSampleCount = 0;
+		GLint sampleCount = 0;
+		for (size_t ndx = 0; ndx < samples.size(); ++ndx, prevSampleCount = sampleCount)
+		{
+			sampleCount = samples[ndx];
+
+			// sample count must be > 0
+			if (sampleCount <= 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected sample count to be at least one; got " << sampleCount << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+			}
+
+			// samples must be ordered descending
+			if (ndx != 0 && !(sampleCount < prevSampleCount))
+			{
+				m_testCtx.getLog() << TestLog::Message
+					<< "// ERROR: Expected sample count to be ordered in descending order;"
+					<< "got " << prevSampleCount << " at index " << (ndx - 1) << ", and " << sampleCount << " at index " << ndx << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid order");
+			}
+		}
+
+		// the maximum value in SAMPLES is guaranteed to be at least the value of MAX_SAMPLES
+		StateQueryMemoryWriteGuard<GLint> maxSamples;
+		glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
+		expectError(GL_NO_ERROR);
+
+		if (maxSamples.verifyValidity(m_testCtx))
+		{
+			const GLint maximumFormatSampleCount = samples[0];
+			if (!(maximumFormatSampleCount >= maxSamples))
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected maximum value in SAMPLES (" << maximumFormatSampleCount << ") to be at least the value of MAX_SAMPLES (" << maxSamples << ")" << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid maximum sample count");
+			}
+		}
+	}
+
+private:
+	GLenum	m_internalFormat;
+	bool	m_isIntegerInternalFormat;
+};
+
+class SamplesBufferSizeCase : public ApiCase
+{
+public:
+	SamplesBufferSizeCase(Context& context, const char* name, const char* description, GLenum internalFormat)
+		: ApiCase			(context, name, description)
+		, m_internalFormat	(internalFormat)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		StateQueryMemoryWriteGuard<GLint> sampleCounts;
+		glGetInternalformativ(GL_RENDERBUFFER, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &sampleCounts);
+		expectError(GL_NO_ERROR);
+
+		if (!sampleCounts.verifyValidity(m_testCtx))
+			return;
+
+		// test with bufSize = 0
+		GLint queryTargetValue = -1;
+		glGetInternalformativ(GL_RENDERBUFFER, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 0, &queryTargetValue);
+		expectError(GL_NO_ERROR);
+
+		if (queryTargetValue != -1)
+		{
+			m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected output variable not to be written to." << TestLog::EndMessage;
+			if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid write");
+		}
+	}
+
+private:
+	GLenum m_internalFormat;
+};
+
+} // anonymous
+
+
+InternalFormatQueryTests::InternalFormatQueryTests (Context& context)
+	: TestCaseGroup(context, "internal_format", "Internal Format Query tests.")
+{
+}
+
+void InternalFormatQueryTests::init (void)
+{
+	const struct InternalFormat
+	{
+		const char*	name;
+		GLenum		format;
+		bool		isIntegerFormat;
+	} internalFormats[] =
+	{
+		// color renderable and unsized
+		// \note These unsized formats seem to allowed by the spec, but they are not useful in any way. (You can't create a renderbuffer with such internalFormat)
+		{ "rgba",					GL_RGBA,				false	},
+		{ "rgb",					GL_RGB,					false	},
+
+		// color renderable
+		{ "r8",						GL_R8,					false	},
+		{ "rg8",					GL_RG8,					false	},
+		{ "rgb8",					GL_RGB8,				false	},
+		{ "rgb565",					GL_RGB565,				false	},
+		{ "rgba4",					GL_RGBA4,				false	},
+		{ "rgb5_a1",				GL_RGB5_A1,				false	},
+		{ "rgba8",					GL_RGBA8,				false	},
+		{ "rgb10_a2",				GL_RGB10_A2,			false	},
+		{ "rgb10_a2ui",				GL_RGB10_A2UI,			true	},
+		{ "srgb8_alpha8",			GL_SRGB8_ALPHA8,		false	},
+		{ "r8i",					GL_R8I,					true	},
+		{ "r8ui",					GL_R8UI,				true	},
+		{ "r16i",					GL_R16I,				true	},
+		{ "r16ui",					GL_R16UI,				true	},
+		{ "r32i",					GL_R32I,				true	},
+		{ "r32ui",					GL_R32UI,				true	},
+		{ "rg8i",					GL_RG8I,				true	},
+		{ "rg8ui",					GL_RG8UI,				true	},
+		{ "rg16i",					GL_RG16I,				true	},
+		{ "rg16ui",					GL_RG16UI,				true	},
+		{ "rg32i",					GL_RG32I,				true	},
+		{ "rg32ui",					GL_RG32UI,				true	},
+		{ "rgba8i",					GL_RGBA8I,				true	},
+		{ "rgba8ui",				GL_RGBA8UI,				true	},
+		{ "rgba16i",				GL_RGBA16I,				true	},
+		{ "rgba16ui",				GL_RGBA16UI,			true	},
+		{ "rgba32i",				GL_RGBA32I,				true	},
+		{ "rgba32ui",				GL_RGBA32UI,			true	},
+
+		// depth renderable
+		{ "depth_component16",		GL_DEPTH_COMPONENT16,	false	},
+		{ "depth_component24",		GL_DEPTH_COMPONENT24,	false	},
+		{ "depth_component32f",		GL_DEPTH_COMPONENT32F,	false	},
+		{ "depth24_stencil8",		GL_DEPTH24_STENCIL8,	false	},
+		{ "depth32f_stencil8",		GL_DEPTH32F_STENCIL8,	false	},
+
+		// stencil renderable
+		{ "stencil_index8",			GL_STENCIL_INDEX8,		false	}
+		// DEPTH24_STENCIL8,  duplicate
+		// DEPTH32F_STENCIL8  duplicate
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(internalFormats); ++ndx)
+	{
+		const InternalFormat internalFormat = internalFormats[ndx];
+
+		addChild(new SamplesCase(m_context, (std::string(internalFormat.name) + "_samples").c_str(), "SAMPLES and NUM_SAMPLE_COUNTS", internalFormat.format, internalFormat.isIntegerFormat));
+	}
+
+	addChild(new SamplesBufferSizeCase(m_context, "rgba8_samples_buffer", "SAMPLES bufSize parameter", GL_RGBA8));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fInternalFormatQueryTests.hpp b/modules/gles3/functional/es3fInternalFormatQueryTests.hpp
new file mode 100644
index 0000000..7d91d73
--- /dev/null
+++ b/modules/gles3/functional/es3fInternalFormatQueryTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FINTERNALFORMATQUERYTESTS_HPP
+#define _ES3FINTERNALFORMATQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Internal Format Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class InternalFormatQueryTests : public TestCaseGroup
+{
+public:
+									InternalFormatQueryTests	(Context& context);
+
+	void							init						(void);
+
+private:
+									InternalFormatQueryTests	(const InternalFormatQueryTests& other);
+	InternalFormatQueryTests&		operator=					(const InternalFormatQueryTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FINTERNALFORMATQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fLifetimeTests.cpp b/modules/gles3/functional/es3fLifetimeTests.cpp
new file mode 100644
index 0000000..15ddd94
--- /dev/null
+++ b/modules/gles3/functional/es3fLifetimeTests.cpp
@@ -0,0 +1,524 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Object lifetime tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fLifetimeTests.hpp"
+
+#include "deRandom.hpp"
+#include "deUniquePtr.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+#include "glsLifetimeTests.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+using std::vector;
+using de::MovePtr;
+using de::Random;
+using tcu::RenderTarget;
+using tcu::Surface;
+using tcu::TestContext;
+using tcu::TestLog;
+using glu::CallLogWrapper;
+using glu::RenderContext;
+using glu::ProgramSources;
+using glu::VertexArray;
+using glu::Buffer;
+namespace lt = gls::LifetimeTests;
+using namespace lt;
+using namespace glw;
+typedef TestCase::IterateResult IterateResult;
+
+enum { VIEWPORT_SIZE = 128 };
+
+class ScaleProgram : public glu::ShaderProgram
+{
+public:
+							ScaleProgram	(lt::Context& ctx);
+	void 					draw			(GLuint vao, GLfloat scale, bool tf, Surface* dst);
+	void					setPos			(GLuint buffer, GLuint vao);
+
+private:
+	ProgramSources			getSources 		(void);
+
+	const RenderContext&	m_renderCtx;
+	GLint					m_scaleLoc;
+	GLint					m_posLoc;
+};
+
+enum { NUM_COMPONENTS = 4, NUM_VERTICES = 3 };
+
+ScaleProgram::ScaleProgram (lt::Context& ctx)
+	: glu::ShaderProgram	(ctx.getRenderContext(), getSources())
+	, m_renderCtx			(ctx.getRenderContext())
+{
+	const Functions& gl = m_renderCtx.getFunctions();
+	TCU_CHECK(isOk());
+	m_scaleLoc = gl.getUniformLocation(getProgram(), "scale");
+	m_posLoc = gl.getAttribLocation(getProgram(), "pos");
+}
+
+#define GLSL(VERSION, BODY) ("#version " #VERSION "\n" #BODY "\n")
+
+static const char* const s_vertexShaderSrc = GLSL(
+	100,
+	attribute vec4 pos;
+	uniform float scale;
+	void main ()
+	{
+		gl_Position = vec4(scale * pos.xy, pos.zw);
+	}
+	);
+
+static const char* const s_fragmentShaderSrc = GLSL(
+	100,
+	void main ()
+	{
+		gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
+	}
+	);
+
+ProgramSources ScaleProgram::getSources (void)
+{
+	using namespace glu;
+	ProgramSources sources;
+	sources << VertexSource(s_vertexShaderSrc)
+			<< FragmentSource(s_fragmentShaderSrc)
+			<< TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)
+			<< TransformFeedbackVarying("gl_Position");
+	return sources;
+}
+
+void ScaleProgram::draw (GLuint vao, GLfloat scale, bool tf, Surface* dst)
+{
+	const Functions&	gl			= m_renderCtx.getFunctions();
+	de::Random			rnd			(vao);
+	Rectangle			viewport	= randomViewport(m_renderCtx,
+													 VIEWPORT_SIZE, VIEWPORT_SIZE, rnd);
+	setViewport(m_renderCtx, viewport);
+	gl.clearColor(0, 0, 0, 1);
+	gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+	gl.bindVertexArray(vao);
+	gl.enableVertexAttribArray(m_posLoc);
+	GLU_CHECK_CALL_ERROR(gl.useProgram(getProgram()),
+						 gl.getError());
+
+	gl.uniform1f(m_scaleLoc, scale);
+
+	if (tf)
+		gl.beginTransformFeedback(GL_TRIANGLES);
+	GLU_CHECK_CALL_ERROR(gl.drawArrays(GL_TRIANGLES, 0, 3), gl.getError());
+	if (tf)
+		gl.endTransformFeedback();
+
+	if (dst != DE_NULL)
+		readRectangle(m_renderCtx, viewport, *dst);
+
+	gl.bindVertexArray(0);
+}
+
+void ScaleProgram::setPos (GLuint buffer, GLuint vao)
+{
+	const Functions& gl = m_renderCtx.getFunctions();
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, buffer);
+	gl.bindVertexArray(vao);
+	GLU_CHECK_CALL_ERROR(
+		gl.vertexAttribPointer(m_posLoc, NUM_COMPONENTS, GL_FLOAT, false, 0, DE_NULL),
+		gl.getError());
+	gl.bindVertexArray(0);
+	gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+	GLU_CHECK_ERROR(gl.getError());
+}
+
+class VertexArrayBinder : public SimpleBinder
+{
+public:
+						VertexArrayBinder	(lt::Context& ctx)
+							: SimpleBinder	(ctx, 0, GL_NONE, GL_VERTEX_ARRAY_BINDING, true) {}
+	void				bind			(GLuint name) { glBindVertexArray(name); }
+};
+
+class SamplerBinder : public Binder
+{
+public:
+						SamplerBinder	(lt::Context& ctx) : Binder(ctx) {}
+	void				bind			(GLuint name) { glBindSampler(0, name); }
+	GLuint				getBinding		(void)
+	{
+		GLint arr[32] = {};
+		glGetIntegerv(GL_SAMPLER_BINDING, arr);
+		log() << TestLog::Message << "// First output integer: " << arr[0]
+			  << TestLog::EndMessage;
+		return arr[0];
+	}
+	bool				genRequired		(void) const { return true; }
+};
+
+class QueryBinder : public Binder
+{
+public:
+						QueryBinder		(lt::Context& ctx) : Binder(ctx) {}
+	void				bind			(GLuint name)
+	{
+		if (name != 0)
+			glBeginQuery(GL_ANY_SAMPLES_PASSED, name);
+		else
+			glEndQuery(GL_ANY_SAMPLES_PASSED);
+	}
+	GLuint				getBinding		(void) { return 0; }
+};
+
+class BufferVAOAttacher : public Attacher
+{
+public:
+						BufferVAOAttacher	(lt::Context& ctx, Type& elementType,
+											 Type& varrType, ScaleProgram& program)
+							: Attacher		(ctx, elementType, varrType)
+							, m_program		(program) {}
+	void				initAttachment		(GLuint seed, GLuint element);
+	void				attach				(GLuint element, GLuint container);
+	void				detach				(GLuint element, GLuint container);
+	bool				canAttachDeleted	(void) const { return false; }
+	ScaleProgram&		getProgram			(void) { return m_program; }
+	GLuint				getAttachment		(GLuint container);
+
+private:
+	ScaleProgram&		m_program;
+};
+
+static const GLfloat s_varrData[NUM_VERTICES * NUM_COMPONENTS] =
+{
+	-1.0,  0.0, 0.0, 1.0,
+	 1.0,  1.0, 0.0, 1.0,
+	 0.0, -1.0, 0.0, 1.0
+};
+
+void initBuffer (const Functions& gl, GLuint seed, GLenum usage, GLuint buffer)
+{
+	gl.bindBuffer(GL_ARRAY_BUFFER, buffer);
+	if (seed == 0)
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(s_varrData), s_varrData, usage);
+	else
+	{
+		Random	rnd	(seed);
+		GLfloat data[DE_LENGTH_OF_ARRAY(s_varrData)];
+
+		for (int ndx = 0; ndx < NUM_VERTICES; ndx++)
+		{
+			GLfloat* vertex = &data[ndx * NUM_COMPONENTS];
+			vertex[0] = 2.0f * (rnd.getFloat() - 0.5f);
+			vertex[1] = 2.0f * (rnd.getFloat() - 0.5f);
+			DE_STATIC_ASSERT(NUM_COMPONENTS == 4);
+			vertex[2] = 0.0f;
+			vertex[3] = 1.0f;
+		}
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(data), data, usage);
+	}
+	gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+	GLU_CHECK_ERROR(gl.getError());
+}
+
+void BufferVAOAttacher::initAttachment (GLuint seed, GLuint buffer)
+{
+	initBuffer(gl(), seed, GL_STATIC_DRAW, buffer);
+	log() << TestLog::Message << "// Initialized buffer " << buffer << " from seed " << seed
+		  << TestLog::EndMessage;
+}
+
+void BufferVAOAttacher::attach (GLuint buffer, GLuint vao)
+{
+	m_program.setPos(buffer, vao);
+	log() << TestLog::Message
+		  << "// Set the `pos` attribute in VAO " << vao << " to buffer " << buffer
+		  << TestLog::EndMessage;
+}
+
+void BufferVAOAttacher::detach (GLuint buffer, GLuint varr)
+{
+	DE_UNREF(buffer);
+	attach(0, varr);
+}
+
+GLuint BufferVAOAttacher::getAttachment (GLuint varr)
+{
+	GLint name = 0;
+	gl().bindVertexArray(varr);
+	gl().getVertexAttribiv(0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &name);
+	gl().bindVertexArray(0);
+	GLU_CHECK_ERROR(gl().getError());
+	return GLuint(name);
+}
+
+class BufferVAOInputAttacher : public InputAttacher
+{
+public:
+						BufferVAOInputAttacher	(BufferVAOAttacher& attacher)
+							: InputAttacher		(attacher)
+							, m_program			(attacher.getProgram()) {}
+	void				drawContainer			(GLuint container, Surface& dst);
+
+private:
+	ScaleProgram&		m_program;
+};
+
+void BufferVAOInputAttacher::drawContainer (GLuint vao, Surface& dst)
+{
+	m_program.draw(vao, 1.0, false, &dst);
+	log() << TestLog::Message << "// Drew an output image with VAO " << vao
+		  << TestLog::EndMessage;
+};
+
+class BufferTfAttacher : public Attacher
+{
+public:
+				BufferTfAttacher	(lt::Context& ctx, Type& bufferType, Type& tfType)
+					: Attacher		(ctx, bufferType, tfType) {}
+	void		initAttachment		(GLuint seed, GLuint element);
+	void		attach				(GLuint buffer, GLuint tf);
+	void		detach				(GLuint buffer, GLuint tf);
+	bool		canAttachDeleted	(void) const { return false; }
+	GLuint		getAttachment		(GLuint tf);
+};
+
+void BufferTfAttacher::initAttachment (GLuint seed, GLuint buffer)
+{
+	initBuffer(gl(), seed, GL_DYNAMIC_READ, buffer);
+	log() << TestLog::Message << "// Initialized buffer " << buffer << " from seed " << seed
+		  << TestLog::EndMessage;
+}
+
+void BufferTfAttacher::attach (GLuint buffer, GLuint tf)
+{
+	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf);
+	glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buffer);
+	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+	GLU_CHECK_ERROR(gl().getError());
+}
+
+void BufferTfAttacher::detach (GLuint buffer, GLuint tf)
+{
+	DE_UNREF(buffer);
+	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf);
+	glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
+	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+	GLU_CHECK_ERROR(gl().getError());
+}
+
+GLuint BufferTfAttacher::getAttachment (GLuint tf)
+{
+	GLint ret = 0;
+	gl().bindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf);
+	gl().getIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, &ret);
+	gl().bindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+	GLU_CHECK_ERROR(gl().getError());
+	return GLuint(ret);
+}
+
+class BufferTfOutputAttacher : public OutputAttacher
+{
+public:
+				BufferTfOutputAttacher	(BufferTfAttacher&	attacher, ScaleProgram& program)
+					: OutputAttacher	(attacher)
+					, m_program			(program) {}
+	void		setupContainer		(GLuint seed, GLuint container);
+	void		drawAttachment		(GLuint attachment, Surface& dst);
+
+private:
+	ScaleProgram&	m_program;
+};
+
+void BufferTfOutputAttacher::drawAttachment (GLuint buffer, Surface& dst)
+{
+	VertexArray vao(getRenderContext());
+
+	m_program.setPos(buffer, *vao);
+	m_program.draw(*vao, 1.0, false, &dst);
+	log() << TestLog::Message
+		  << "// Drew output image with vertices from buffer " << buffer
+		  << TestLog::EndMessage;
+	GLU_CHECK_ERROR(gl().getError());
+}
+
+void BufferTfOutputAttacher::setupContainer (GLuint seed, GLuint tf)
+{
+	Buffer		posBuf	(getRenderContext());
+	VertexArray	vao		(getRenderContext());
+
+	initBuffer(gl(), seed, GL_STATIC_DRAW, *posBuf);
+	m_program.setPos(*posBuf, *vao);
+
+	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf);
+	m_program.draw(*vao, -1.0, true, DE_NULL);
+	log() << TestLog::Message
+		  << "// Drew an image with seed " << seed << " with transform feedback to " << tf
+		  << TestLog::EndMessage;
+	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+	GLU_CHECK_ERROR(gl().getError());
+}
+
+class ES3Types : public ES2Types
+{
+public:
+							ES3Types		(lt::Context& ctx);
+private:
+	ScaleProgram			m_program;
+	QueryBinder				m_queryBind;
+	SimpleType				m_queryType;
+	SimpleBinder			m_tfBind;
+	SimpleType				m_tfType;
+	VertexArrayBinder		m_varrBind;
+	SimpleType				m_varrType;
+	SamplerBinder			m_samplerBind;
+	SimpleType				m_samplerType;
+	BufferVAOAttacher		m_bufVarrAtt;
+	BufferVAOInputAttacher	m_bufVarrInAtt;
+	BufferTfAttacher		m_bufTfAtt;
+	BufferTfOutputAttacher	m_bufTfOutAtt;
+};
+
+ES3Types::ES3Types (lt::Context& ctx)
+	: ES2Types		(ctx)
+	, m_program		(ctx)
+	, m_queryBind	(ctx)
+	, m_queryType	(ctx, "query", &CallLogWrapper::glGenQueries,
+					 &CallLogWrapper::glDeleteQueries,
+					 &CallLogWrapper::glIsQuery, &m_queryBind)
+	, m_tfBind		(ctx, &CallLogWrapper::glBindTransformFeedback, GL_TRANSFORM_FEEDBACK,
+					 GL_TRANSFORM_FEEDBACK_BINDING, true)
+	, m_tfType		(ctx, "transform_feedback", &CallLogWrapper::glGenTransformFeedbacks,
+					 &CallLogWrapper::glDeleteTransformFeedbacks,
+					 &CallLogWrapper::glIsTransformFeedback, &m_tfBind)
+	, m_varrBind	(ctx)
+	, m_varrType	(ctx, "vertex_array", &CallLogWrapper::glGenVertexArrays,
+					 &CallLogWrapper::glDeleteVertexArrays,
+					 &CallLogWrapper::glIsVertexArray, &m_varrBind)
+	, m_samplerBind	(ctx)
+	, m_samplerType	(ctx, "sampler", &CallLogWrapper::glGenSamplers,
+					 &CallLogWrapper::glDeleteSamplers,
+					 &CallLogWrapper::glIsSampler, &m_samplerBind, true)
+	, m_bufVarrAtt	(ctx, m_bufferType, m_varrType, m_program)
+	, m_bufVarrInAtt(m_bufVarrAtt)
+	, m_bufTfAtt	(ctx, m_bufferType, m_tfType)
+	, m_bufTfOutAtt	(m_bufTfAtt, m_program)
+{
+	Type* types[] = { &m_queryType, &m_tfType, &m_varrType, &m_samplerType };
+	m_types.insert(m_types.end(), DE_ARRAY_BEGIN(types), DE_ARRAY_END(types));
+
+	m_attachers.push_back(&m_bufVarrAtt);
+	m_attachers.push_back(&m_bufTfAtt);
+
+	m_inAttachers.push_back(&m_bufVarrInAtt);
+	m_outAttachers.push_back(&m_bufTfOutAtt);
+}
+
+class TfDeleteActiveTest : public TestCase, private CallLogWrapper
+{
+	public:
+						TfDeleteActiveTest	(gles3::Context& context,
+											 const char* name, const char* description);
+	IterateResult		iterate				(void);
+};
+
+TfDeleteActiveTest::TfDeleteActiveTest (gles3::Context& context,
+										const char* name, const char* description)
+	: TestCase			(context, name, description)
+	, CallLogWrapper	(context.getRenderContext().getFunctions(),
+						 context.getTestContext().getLog())
+{
+	enableLogging(true);
+}
+
+IterateResult TfDeleteActiveTest::iterate (void)
+{
+	GLuint tf = 0;
+	glGenTransformFeedbacks(1, &tf);
+	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf);
+	glBeginTransformFeedback(GL_TRIANGLES);
+	glDeleteTransformFeedbacks(1, &tf);
+	{
+		GLenum err = glGetError();
+		if (err != GL_INVALID_OPERATION)
+			getTestContext().setTestResult(
+				QP_TEST_RESULT_FAIL,
+				"Deleting active transform feedback did not produce GL_INVALID_OPERATION");
+		else
+			getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	glEndTransformFeedback();
+	glDeleteTransformFeedbacks(1, &tf);
+	return STOP;
+}
+
+class TestGroup : public TestCaseGroup
+{
+public:
+							TestGroup		(gles3::Context& context)
+								: TestCaseGroup	(context, "lifetime", "Object lifetime tests")
+							{}
+	void					init			(void);
+private:
+	MovePtr<Types>			m_types;
+};
+
+void TestGroup::init (void)
+{
+	gles3::Context&	ctx		= getContext();
+	lt::Context		ltCtx	(ctx.getRenderContext(), ctx.getTestContext());
+
+	m_types	= MovePtr<Types>(new ES3Types(ltCtx));
+
+	addTestCases(*this, *m_types);
+
+	TestCaseGroup* deleteActiveGroup =
+		new TestCaseGroup(ctx, "delete_active", "Delete active object");
+	addChild(deleteActiveGroup);
+	deleteActiveGroup->addChild(
+		new TfDeleteActiveTest(ctx, "transform_feedback", "Transform Feedback"));
+}
+
+} // anonymous
+
+TestCaseGroup* createLifetimeTests (Context& context)
+{
+	return new TestGroup(context);
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fLifetimeTests.hpp b/modules/gles3/functional/es3fLifetimeTests.hpp
new file mode 100644
index 0000000..4f0da7a
--- /dev/null
+++ b/modules/gles3/functional/es3fLifetimeTests.hpp
@@ -0,0 +1,41 @@
+#ifndef _ES3FLIFETIMETESTS_HPP
+#define _ES3FLIFETIMETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Object lifetime tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+TestCaseGroup* createLifetimeTests (Context& context);
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FLIFETIMETESTS_HPP
diff --git a/modules/gles3/functional/es3fMultisampleTests.cpp b/modules/gles3/functional/es3fMultisampleTests.cpp
new file mode 100644
index 0000000..b060ed8
--- /dev/null
+++ b/modules/gles3/functional/es3fMultisampleTests.cpp
@@ -0,0 +1,1682 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisampling tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fMultisampleTests.hpp"
+#include "gluStrUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuCommandLine.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+#include <string>
+#include <vector>
+
+#include "glw.h"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec4;
+using tcu::TestLog;
+using std::vector;
+
+static const GLenum		FBO_COLOR_FORMAT	= GL_RGBA8;
+static const float		SQRT_HALF			= 0.707107f;
+
+namespace
+{
+
+struct QuadCorners
+{
+	Vec2 p0;
+	Vec2 p1;
+	Vec2 p2;
+	Vec2 p3;
+
+	QuadCorners(const Vec2& p0_, const Vec2& p1_, const Vec2& p2_, const Vec2& p3_) : p0(p0_), p1(p1_), p2(p2_), p3(p3_) {}
+};
+
+} // anonymous
+
+static inline int getIterationCount (const tcu::TestContext& ctx, int defaultCount)
+{
+	int cmdLineValue = ctx.getCommandLine().getTestIterationCount();
+	return cmdLineValue > 0 ? cmdLineValue : defaultCount;
+}
+
+static inline int getGLInteger (GLenum name)
+{
+	int result;
+	GLU_CHECK_CALL(glGetIntegerv(name, &result));
+	return result;
+}
+
+template<typename T>
+static inline T min4 (T a, T b, T c, T d)
+{
+	return de::min(de::min(de::min(a, b), c), d);
+}
+
+template<typename T>
+static inline T max4 (T a, T b, T c, T d)
+{
+	return de::max(de::max(de::max(a, b), c), d);
+}
+
+static inline bool isInsideQuad (const IVec2& point, const IVec2& p0, const IVec2& p1, const IVec2& p2, const IVec2& p3)
+{
+	int dot0 = (point.x()-p0.x()) * (p1.y()-p0.y()) + (point.y()-p0.y()) * (p0.x()-p1.x());
+	int dot1 = (point.x()-p1.x()) * (p2.y()-p1.y()) + (point.y()-p1.y()) * (p1.x()-p2.x());
+	int dot2 = (point.x()-p2.x()) * (p3.y()-p2.y()) + (point.y()-p2.y()) * (p2.x()-p3.x());
+	int dot3 = (point.x()-p3.x()) * (p0.y()-p3.y()) + (point.y()-p3.y()) * (p3.x()-p0.x());
+
+	return (dot0 > 0) == (dot1 > 0) && (dot1 > 0) == (dot2 > 0) && (dot2 > 0) == (dot3 > 0);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Check if a region in an image is unicolored.
+ *
+ * Checks if the pixels in img inside the convex quadilateral defined by
+ * p0, p1, p2 and p3 are all (approximately) of the same color.
+ *//*--------------------------------------------------------------------*/
+static bool isPixelRegionUnicolored (const tcu::Surface& img, const IVec2& p0, const IVec2& p1, const IVec2& p2, const IVec2& p3)
+{
+	int			xMin				= de::clamp(min4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1);
+	int			yMin				= de::clamp(min4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1);
+	int			xMax				= de::clamp(max4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1);
+	int			yMax				= de::clamp(max4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1);
+	bool		insideEncountered	= false;	//!< Whether we have already seen at least one pixel inside the region.
+	tcu::RGBA	insideColor;					//!< Color of the first pixel inside the region.
+
+	for (int y = yMin; y <= yMax; y++)
+	for (int x = xMin; x <= xMax; x++)
+	{
+		if (isInsideQuad(IVec2(x, y), p0, p1, p2, p3))
+		{
+			tcu::RGBA pixColor = img.getPixel(x, y);
+
+			if (insideEncountered)
+			{
+				if (!tcu::compareThreshold(pixColor, insideColor, tcu::RGBA(3, 3, 3, 3))) // Pixel color differs from already-detected color inside same region - region not unicolored.
+					return false;
+			}
+			else
+			{
+				insideEncountered = true;
+				insideColor = pixColor;
+			}
+		}
+	}
+
+	return true;
+}
+
+static bool drawUnicolorTestErrors (tcu::Surface& img, const tcu::PixelBufferAccess& errorImg, const IVec2& p0, const IVec2& p1, const IVec2& p2, const IVec2& p3)
+{
+	int			xMin		= de::clamp(min4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1);
+	int			yMin		= de::clamp(min4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1);
+	int			xMax		= de::clamp(max4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1);
+	int			yMax		= de::clamp(max4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1);
+	tcu::RGBA	refColor	= img.getPixel((xMin + xMax) / 2, (yMin + yMax) / 2);
+
+	for (int y = yMin; y <= yMax; y++)
+	for (int x = xMin; x <= xMax; x++)
+	{
+		if (isInsideQuad(IVec2(x, y), p0, p1, p2, p3))
+		{
+			if (!tcu::compareThreshold(img.getPixel(x, y), refColor, tcu::RGBA(3, 3, 3, 3)))
+			{
+				img.setPixel(x, y, tcu::RGBA::red);
+				errorImg.setPixel(Vec4(1.0f, 0.0f, 0.0f, 1.0f), x, y);
+			}
+		}
+	}
+
+	return true;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Abstract base class handling common stuff for multisample cases.
+ *//*--------------------------------------------------------------------*/
+class MultisampleCase : public TestCase
+{
+public:
+	struct FboParams
+	{
+		bool		useFbo;
+		int			numSamples; //!< If 0, use implementation-defined maximum.
+		bool		useDepth;
+		bool		useStencil;
+
+		FboParams (int numSamples_, bool useDepth_, bool useStencil_)
+			: useFbo			(true)
+			, numSamples		(numSamples_)
+			, useDepth			(useDepth_)
+			, useStencil		(useStencil_)
+		{
+		}
+
+		FboParams (void)
+			: useFbo			(false)
+			, numSamples		(-1)
+			, useDepth			(false)
+			, useStencil		(false)
+		{
+		}
+	};
+
+						MultisampleCase			(Context& context, const char* name, const char* desc, int desiredViewportSize, const FboParams& fboParams = FboParams());
+	virtual				~MultisampleCase		(void);
+
+	virtual void		init					(void);
+	virtual void		deinit					(void);
+
+protected:
+	void				renderTriangle			(const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const;
+	void				renderTriangle			(const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& color) const;
+	void				renderTriangle			(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const;
+	void				renderTriangle			(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const;
+	void				renderQuad				(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& c0, const Vec4& c1, const Vec4& c2, const Vec4& c3) const;
+	void				renderQuad				(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& color) const;
+	void				renderLine				(const Vec2& p0, const Vec2& p1, const Vec4& color) const;
+
+	void				randomizeViewport		(void);
+	void				readImage				(tcu::Surface& dst) const;
+
+	int					m_numSamples;
+
+	int					m_viewportSize;
+
+private:
+						MultisampleCase			(const MultisampleCase& other);
+	MultisampleCase&	operator=				(const MultisampleCase& other);
+
+	const int			m_desiredViewportSize;
+
+	const FboParams		m_fboParams;
+	deUint32			m_msColorRbo;
+	deUint32			m_msDepthStencilRbo;
+	deUint32			m_resolveColorRbo;
+	deUint32			m_msFbo;
+	deUint32			m_resolveFbo;
+
+	glu::ShaderProgram*	m_program;
+	int					m_attrPositionLoc;
+	int					m_attrColorLoc;
+
+	int					m_renderWidth;
+	int					m_renderHeight;
+	int					m_viewportX;
+	int					m_viewportY;
+	de::Random			m_rnd;
+};
+
+MultisampleCase::MultisampleCase (Context& context, const char* name, const char* desc, int desiredViewportSize, const FboParams& fboParams)
+	: TestCase				(context, name, desc)
+	, m_numSamples			(0)
+	, m_viewportSize		(0)
+	, m_desiredViewportSize	(desiredViewportSize)
+	, m_fboParams			(fboParams)
+	, m_msColorRbo			(0)
+	, m_msDepthStencilRbo	(0)
+	, m_resolveColorRbo		(0)
+	, m_msFbo				(0)
+	, m_resolveFbo			(0)
+	, m_program				(DE_NULL)
+	, m_attrPositionLoc		(-1)
+	, m_attrColorLoc		(-1)
+	, m_renderWidth			(fboParams.useFbo ? 2*desiredViewportSize : context.getRenderTarget().getWidth())
+	, m_renderHeight		(fboParams.useFbo ? 2*desiredViewportSize : context.getRenderTarget().getHeight())
+	, m_viewportX			(0)
+	, m_viewportY			(0)
+	, m_rnd					(deStringHash(name))
+{
+	if (m_fboParams.useFbo)
+		DE_ASSERT(m_fboParams.numSamples >= 0);
+}
+
+MultisampleCase::~MultisampleCase (void)
+{
+	MultisampleCase::deinit();
+}
+
+void MultisampleCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+
+	GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+	GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
+
+	if (m_msColorRbo != 0)
+	{
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &m_msColorRbo));
+		m_msColorRbo = 0;
+	}
+	if (m_msDepthStencilRbo != 0)
+	{
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &m_msDepthStencilRbo));
+		m_msDepthStencilRbo = 0;
+	}
+	if (m_resolveColorRbo != 0)
+	{
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &m_resolveColorRbo));
+		m_resolveColorRbo = 0;
+	}
+
+	if (m_msFbo != 0)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &m_msFbo));
+		m_msFbo = 0;
+	}
+	if (m_resolveFbo != 0)
+	{
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &m_resolveFbo));
+		m_resolveFbo = 0;
+	}
+}
+
+void MultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const
+{
+	float vertexPositions[] =
+	{
+		p0.x(), p0.y(), p0.z(), 1.0f,
+		p1.x(), p1.y(), p1.z(), 1.0f,
+		p2.x(), p2.y(), p2.z(), 1.0f
+	};
+	float vertexColors[] =
+	{
+		c0.x(), c0.y(), c0.z(), c0.w(),
+		c1.x(), c1.y(), c1.z(), c1.w(),
+		c2.x(), c2.y(), c2.z(), c2.w(),
+	};
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrPositionLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(m_attrPositionLoc, 4, GL_FLOAT, false, 0, &vertexPositions[0]));
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrColorLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(m_attrColorLoc, 4, GL_FLOAT, false, 0, &vertexColors[0]));
+
+	GLU_CHECK_CALL(glUseProgram(m_program->getProgram()));
+	GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 3));
+}
+
+void MultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& color) const
+{
+	renderTriangle(p0, p1, p2, color, color, color);
+}
+
+void MultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const
+{
+	renderTriangle(Vec3(p0.x(), p0.y(), 0.0f),
+				   Vec3(p1.x(), p1.y(), 0.0f),
+				   Vec3(p2.x(), p2.y(), 0.0f),
+				   c0, c1, c2);
+}
+
+void MultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const
+{
+	renderTriangle(p0, p1, p2, color, color, color);
+}
+
+void MultisampleCase::renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& c0, const Vec4& c1, const Vec4& c2, const Vec4& c3) const
+{
+	renderTriangle(p0, p1, p2, c0, c1, c2);
+	renderTriangle(p2, p1, p3, c2, c1, c3);
+}
+
+void MultisampleCase::renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& color) const
+{
+	renderQuad(p0, p1, p2, p3, color, color, color, color);
+}
+
+void MultisampleCase::renderLine (const Vec2& p0, const Vec2& p1, const Vec4& color) const
+{
+	float vertexPositions[] =
+	{
+		p0.x(), p0.y(), 0.0f, 1.0f,
+		p1.x(), p1.y(), 0.0f, 1.0f
+	};
+	float vertexColors[] =
+	{
+		color.x(), color.y(), color.z(), color.w(),
+		color.x(), color.y(), color.z(), color.w()
+	};
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrPositionLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(m_attrPositionLoc, 4, GL_FLOAT, false, 0, &vertexPositions[0]));
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrColorLoc));
+	GLU_CHECK_CALL(glVertexAttribPointer(m_attrColorLoc, 4, GL_FLOAT, false, 0, &vertexColors[0]));
+
+	GLU_CHECK_CALL(glUseProgram(m_program->getProgram()));
+	GLU_CHECK_CALL(glDrawArrays(GL_LINES, 0, 2));
+}
+
+void MultisampleCase::randomizeViewport (void)
+{
+	m_viewportX = m_rnd.getInt(0, m_renderWidth - m_viewportSize);
+	m_viewportY = m_rnd.getInt(0, m_renderHeight - m_viewportSize);
+
+	GLU_CHECK_CALL(glViewport(m_viewportX, m_viewportY, m_viewportSize, m_viewportSize));
+}
+
+void MultisampleCase::readImage (tcu::Surface& dst) const
+{
+	if (m_fboParams.useFbo)
+	{
+		GLU_CHECK_CALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolveFbo));
+		GLU_CHECK_CALL(glBlitFramebuffer(0, 0, m_renderWidth, m_renderHeight, 0, 0, m_renderWidth, m_renderHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST));
+		GLU_CHECK_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, m_resolveFbo));
+
+		glu::readPixels(m_context.getRenderContext(), m_viewportX, m_viewportY, dst.getAccess());
+
+		GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_msFbo));
+	}
+	else
+		glu::readPixels(m_context.getRenderContext(), m_viewportX, m_viewportY, dst.getAccess());
+}
+
+void MultisampleCase::init (void)
+{
+	static const char* vertShaderSource =
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in mediump vec4 a_color;\n"
+		"out mediump vec4 v_color;\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_color = a_color;\n"
+		"}\n";
+
+	static const char* fragShaderSource =
+		"#version 300 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";
+
+	TestLog& log = m_testCtx.getLog();
+
+	if (!m_fboParams.useFbo && m_context.getRenderTarget().getNumSamples() <= 1)
+		throw tcu::NotSupportedError("No multisample buffers");
+
+	if (m_fboParams.useFbo)
+	{
+		if (m_fboParams.numSamples > 0)
+			m_numSamples = m_fboParams.numSamples;
+		else
+		{
+			log << TestLog::Message << "Querying maximum number of samples for " << glu::getPixelFormatName(FBO_COLOR_FORMAT) << " with glGetInternalformativ()" << TestLog::EndMessage;
+			GLU_CHECK_CALL(glGetInternalformativ(GL_RENDERBUFFER, FBO_COLOR_FORMAT, GL_SAMPLES, 1, &m_numSamples));
+		}
+
+		log << TestLog::Message << "Using FBO of size (" << m_renderWidth << ", " << m_renderHeight << ") with " << m_numSamples << " samples" << TestLog::EndMessage;
+	}
+	else
+	{
+		// Query and log number of samples per pixel.
+
+		m_numSamples = getGLInteger(GL_SAMPLES);
+		log << TestLog::Message << "GL_SAMPLES = " << m_numSamples << TestLog::EndMessage;
+	}
+
+	// Prepare program.
+
+	DE_ASSERT(!m_program);
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource));
+	if (!m_program->isOk())
+		throw tcu::TestError("Failed to compile program", DE_NULL, __FILE__, __LINE__);
+
+	GLU_CHECK_CALL(m_attrPositionLoc	= glGetAttribLocation(m_program->getProgram(), "a_position"));
+	GLU_CHECK_CALL(m_attrColorLoc		= glGetAttribLocation(m_program->getProgram(), "a_color"));
+
+	if (m_attrPositionLoc < 0 || m_attrColorLoc < 0)
+	{
+		delete m_program;
+		throw tcu::TestError("Invalid attribute locations", DE_NULL, __FILE__, __LINE__);
+	}
+
+	if (m_fboParams.useFbo)
+	{
+		DE_STATIC_ASSERT(sizeof(deUint32) == sizeof(GLuint));
+
+		// Setup ms color RBO.
+		GLU_CHECK_CALL(glGenRenderbuffers(1, &m_msColorRbo));
+		GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_msColorRbo));
+
+		// If glRenderbufferStorageMultisample() fails, check if it's because of a too high sample count.
+		// \note We don't do the check until now because some implementations can't handle the GL_SAMPLES query with glGetInternalformativ(),
+		//		 and we don't want that to be the cause of test case failure.
+		try
+		{
+			GLU_CHECK_CALL(glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples, FBO_COLOR_FORMAT, m_renderWidth, m_renderHeight));
+		}
+		catch (const glu::Error&)
+		{
+			GLint maxSampleCount = -1;
+			GLU_CHECK_CALL(glGetInternalformativ(GL_RENDERBUFFER, FBO_COLOR_FORMAT, GL_SAMPLES, 1, &maxSampleCount));
+			if (maxSampleCount < m_numSamples)
+				throw tcu::NotSupportedError(std::string("") + "Maximum sample count returned by glGetInternalformativ() for " + glu::getPixelFormatName(FBO_COLOR_FORMAT) + " is only " + de::toString(maxSampleCount));
+			else
+				throw;
+		}
+
+		if (m_fboParams.useDepth || m_fboParams.useStencil)
+		{
+			// Setup ms depth & stencil RBO.
+			GLU_CHECK_CALL(glGenRenderbuffers(1, &m_msDepthStencilRbo));
+			GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_msDepthStencilRbo));
+			GLU_CHECK_CALL(glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples, GL_DEPTH24_STENCIL8, m_renderWidth, m_renderHeight));
+		}
+
+		// Setup ms FBO.
+		GLU_CHECK_CALL(glGenFramebuffers(1, &m_msFbo));
+		GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_msFbo));
+		GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_msColorRbo));
+		GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_msDepthStencilRbo));
+
+		// Setup resolve color RBO.
+		GLU_CHECK_CALL(glGenRenderbuffers(1, &m_resolveColorRbo));
+		GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_resolveColorRbo));
+		GLU_CHECK_CALL(glRenderbufferStorage(GL_RENDERBUFFER, FBO_COLOR_FORMAT, m_renderWidth, m_renderHeight));
+
+		// Setup resolve FBO.
+		GLU_CHECK_CALL(glGenFramebuffers(1, &m_resolveFbo));
+		GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo));
+		GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_resolveColorRbo));
+
+		// Use ms FBO.
+		GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_msFbo));
+	}
+
+	// Get suitable viewport size.
+
+	m_viewportSize = de::min<int>(m_desiredViewportSize, de::min(m_renderWidth, m_renderHeight));
+	randomizeViewport();
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Base class for cases testing the value of sample count.
+ *
+ * Draws a test pattern (defined by renderPattern() of an inheriting class)
+ * and counts the number of distinct colors in the resulting image. That
+ * number should be at least the value of sample count plus one. This is
+ * repeated with increased values of m_currentIteration until this correct
+ * number of colors is detected or m_currentIteration reaches
+ * m_maxNumIterations.
+ *//*--------------------------------------------------------------------*/
+class NumSamplesCase : public MultisampleCase
+{
+public:
+						NumSamplesCase			(Context& context, const char* name, const char* description, const FboParams& fboParams = FboParams());
+						~NumSamplesCase			(void) {}
+
+	IterateResult		iterate					(void);
+
+protected:
+	virtual void		renderPattern			(void) const = 0;
+
+	int					m_currentIteration;
+
+private:
+	enum { DEFAULT_MAX_NUM_ITERATIONS = 16 };
+
+	const int			m_maxNumIterations;
+	vector<tcu::RGBA>	m_detectedColors;
+};
+
+NumSamplesCase::NumSamplesCase (Context& context, const char* name, const char* description, const FboParams& fboParams)
+	: MultisampleCase		(context, name, description, 256, fboParams)
+	, m_currentIteration	(0)
+	, m_maxNumIterations	(getIterationCount(m_testCtx, DEFAULT_MAX_NUM_ITERATIONS))
+{
+}
+
+NumSamplesCase::IterateResult NumSamplesCase::iterate (void)
+{
+	TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface	renderedImg		(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	renderPattern();
+
+	// Read and log rendered image.
+
+	readImage(renderedImg);
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	// Detect new, previously unseen colors from image.
+
+	int requiredNumDistinctColors = m_numSamples + 1;
+
+	for (int y = 0; y < renderedImg.getHeight() && (int)m_detectedColors.size() < requiredNumDistinctColors; y++)
+	for (int x = 0; x < renderedImg.getWidth() && (int)m_detectedColors.size() < requiredNumDistinctColors; x++)
+	{
+		tcu::RGBA color = renderedImg.getPixel(x, y);
+
+		int i;
+		for (i = 0; i < (int)m_detectedColors.size(); i++)
+		{
+			if (tcu::compareThreshold(color, m_detectedColors[i], tcu::RGBA(3, 3, 3, 3)))
+				break;
+		}
+
+		if (i == (int)m_detectedColors.size())
+			m_detectedColors.push_back(color); // Color not previously detected.
+	}
+
+	// Log results.
+
+	log << TestLog::Message
+		<< "Number of distinct colors detected so far: "
+		<< ((int)m_detectedColors.size() >= requiredNumDistinctColors ? "at least " : "")
+		<< de::toString(m_detectedColors.size())
+		<< TestLog::EndMessage;
+
+	if ((int)m_detectedColors.size() < requiredNumDistinctColors)
+	{
+		// Haven't detected enough different colors yet.
+
+		m_currentIteration++;
+
+		if (m_currentIteration >= m_maxNumIterations)
+		{
+			log << TestLog::Message << "Failure: Number of distinct colors detected is lower than sample count+1" << TestLog::EndMessage;
+			m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+			return STOP;
+		}
+		else
+		{
+			log << TestLog::Message << "The number of distinct colors detected is lower than sample count+1 - trying again with a slightly altered pattern" << TestLog::EndMessage;
+			return CONTINUE;
+		}
+	}
+	else
+	{
+		log << TestLog::Message << "Success: The number of distinct colors detected is at least sample count+1" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+		return STOP;
+	}
+}
+
+class PolygonNumSamplesCase : public NumSamplesCase
+{
+public:
+			PolygonNumSamplesCase	(Context& context, const char* name, const char* description, int numFboSamples = 0);
+			~PolygonNumSamplesCase	(void) {}
+
+protected:
+	void	renderPattern			(void) const;
+};
+
+PolygonNumSamplesCase::PolygonNumSamplesCase (Context& context, const char* name, const char* description, int numFboSamples)
+	: NumSamplesCase(context, name, description, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams())
+{
+}
+
+void PolygonNumSamplesCase::renderPattern (void) const
+{
+	// The test pattern consists of several triangles with edges at different angles.
+
+	const int numTriangles = 25;
+	for (int i = 0; i < numTriangles; i++)
+	{
+		float angle0 = 2.0f*DE_PI * (float)i			/ (float)numTriangles + 0.001f*(float)m_currentIteration;
+		float angle1 = 2.0f*DE_PI * (float)(i + 0.5f)	/ (float)numTriangles + 0.001f*(float)m_currentIteration;
+
+		renderTriangle(Vec2(0.0f, 0.0f),
+					   Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f),
+					   Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f),
+					   Vec4(1.0f));
+	}
+}
+
+class LineNumSamplesCase : public NumSamplesCase
+{
+public:
+			LineNumSamplesCase		(Context& context, const char* name, const char* description, int numFboSamples = 0);
+			~LineNumSamplesCase		(void) {}
+
+protected:
+	void	renderPattern			(void) const;
+};
+
+LineNumSamplesCase::LineNumSamplesCase (Context& context, const char* name, const char* description, int numFboSamples)
+	: NumSamplesCase (context, name, description, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams())
+{
+}
+
+void LineNumSamplesCase::renderPattern (void) const
+{
+	// The test pattern consists of several lines at different angles.
+
+	// We scale the number of lines based on the viewport size. This is because a gl line's thickness is
+	// constant in pixel units, i.e. they get relatively thicker as viewport size decreases. Thus we must
+	// decrease the number of lines in order to decrease the extent of overlap among the lines in the
+	// center of the pattern.
+	const int numLines = (int)(100.0f * deFloatSqrt((float)m_viewportSize / 256.0f));
+
+	for (int i = 0; i < numLines; i++)
+	{
+		float angle = 2.0f*DE_PI * (float)i / (float)numLines + 0.001f*(float)m_currentIteration;
+		renderLine(Vec2(0.0f, 0.0f), Vec2(deFloatCos(angle)*0.95f, deFloatSin(angle)*0.95f), Vec4(1.0f));
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Case testing behaviour of common edges when multisampling.
+ *
+ * Draws a number of test patterns, each with a number of quads, each made
+ * of two triangles, rotated at different angles. The inner edge inside the
+ * quad (i.e. the common edge of the two triangles) still should not be
+ * visible, despite multisampling - i.e. the two triangles forming the quad
+ * should never get any common coverage bits in any pixel.
+ *//*--------------------------------------------------------------------*/
+class CommonEdgeCase : public MultisampleCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_SMALL_QUADS = 0,				//!< Draw several small quads per iteration.
+		CASETYPE_BIGGER_THAN_VIEWPORT_QUAD,		//!< Draw one bigger-than-viewport quad per iteration.
+		CASETYPE_FIT_VIEWPORT_QUAD,				//!< Draw one exactly viewport-sized, axis aligned quad per iteration.
+
+		CASETYPE_LAST
+	};
+
+					CommonEdgeCase			(Context& context, const char* name, const char* description, CaseType caseType, int numFboSamples = 0);
+					~CommonEdgeCase			(void) {}
+
+	void			init					(void);
+
+	IterateResult	iterate					(void);
+
+private:
+	enum
+	{
+		DEFAULT_SMALL_QUADS_ITERATIONS					= 16,
+		DEFAULT_BIGGER_THAN_VIEWPORT_QUAD_ITERATIONS	= 8*8
+		// \note With CASETYPE_FIT_VIEWPORT_QUAD, we don't do rotations other than multiples of 90 deg -> constant number of iterations.
+	};
+
+	const CaseType	m_caseType;
+
+	const int		m_numIterations;
+	int				m_currentIteration;
+};
+
+CommonEdgeCase::CommonEdgeCase (Context& context, const char* name, const char* description, CaseType caseType, int numFboSamples)
+	: MultisampleCase		(context, name, description, caseType == CASETYPE_SMALL_QUADS ? 128 : 32, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams())
+	, m_caseType			(caseType)
+	, m_numIterations		(caseType == CASETYPE_SMALL_QUADS					? getIterationCount(m_testCtx, DEFAULT_SMALL_QUADS_ITERATIONS)
+							: caseType == CASETYPE_BIGGER_THAN_VIEWPORT_QUAD	? getIterationCount(m_testCtx, DEFAULT_BIGGER_THAN_VIEWPORT_QUAD_ITERATIONS)
+							: 8)
+	, m_currentIteration	(0)
+{
+}
+
+void CommonEdgeCase::init (void)
+{
+	MultisampleCase::init();
+
+	if (m_caseType == CASETYPE_SMALL_QUADS)
+	{
+		// Check for a big enough viewport. With too small viewports the test case can't analyze the resulting image well enough.
+
+		const int minViewportSize = 32;
+
+		if (m_viewportSize < minViewportSize)
+			throw tcu::InternalError("Render target width or height too low (is " + de::toString(m_viewportSize) + ", should be at least " + de::toString(minViewportSize) + ")");
+	}
+
+	GLU_CHECK_CALL(glEnable(GL_BLEND));
+	GLU_CHECK_CALL(glBlendEquation(GL_FUNC_ADD));
+	GLU_CHECK_CALL(glBlendFunc(GL_ONE, GL_ONE));
+	m_testCtx.getLog() << TestLog::Message << "Additive blending enabled in order to detect (erroneously) overlapping samples" << TestLog::EndMessage;
+}
+
+CommonEdgeCase::IterateResult CommonEdgeCase::iterate (void)
+{
+	TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface	renderedImg		(m_viewportSize, m_viewportSize);
+	tcu::Surface	errorImg		(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	// Draw test pattern. Test patterns consist of quads formed with two triangles.
+	// After drawing the pattern, we check that the interior pixels of each quad are
+	// all the same color - this is meant to verify that there are no artifacts on the inner edge.
+
+	vector<QuadCorners> unicoloredRegions;
+
+	if (m_caseType == CASETYPE_SMALL_QUADS)
+	{
+		// Draw several quads, rotated at different angles.
+
+		const float		quadDiagLen = 2.0f / 3.0f * 0.9f; // \note Fit 3 quads in both x and y directions.
+		float			angleCos;
+		float			angleSin;
+
+		// \note First and second iteration get exact 0 (and 90, 180, 270) and 45 (and 135, 225, 315) angle quads, as they are kind of a special case.
+
+		if (m_currentIteration == 0)
+		{
+			angleCos = 1.0f;
+			angleSin = 0.0f;
+		}
+		else if (m_currentIteration == 1)
+		{
+			angleCos = SQRT_HALF;
+			angleSin = SQRT_HALF;
+		}
+		else
+		{
+			float angle = 0.5f * DE_PI * (float)(m_currentIteration-1) / (float)(m_numIterations-1);
+			angleCos = deFloatCos(angle);
+			angleSin = deFloatSin(angle);
+		}
+
+		Vec2 corners[4] =
+		{
+			0.5f * quadDiagLen * Vec2( angleCos,  angleSin),
+			0.5f * quadDiagLen * Vec2(-angleSin,  angleCos),
+			0.5f * quadDiagLen * Vec2(-angleCos, -angleSin),
+			0.5f * quadDiagLen * Vec2( angleSin, -angleCos)
+		};
+
+		unicoloredRegions.reserve(8);
+
+		// Draw 8 quads.
+		// First four are rotated at angles angle+0, angle+90, angle+180 and angle+270.
+		// Last four are rotated the same angles as the first four, but the ordering of the last triangle's vertices is reversed.
+
+		for (int quadNdx = 0; quadNdx < 8; quadNdx++)
+		{
+			Vec2 center = (2.0f-quadDiagLen) * Vec2((float)(quadNdx%3), (float)(quadNdx/3)) / 2.0f - 0.5f*(2.0f-quadDiagLen);
+
+			renderTriangle(corners[(0+quadNdx) % 4] + center,
+						   corners[(1+quadNdx) % 4] + center,
+						   corners[(2+quadNdx) % 4] + center,
+						   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+
+			if (quadNdx >= 4)
+			{
+				renderTriangle(corners[(3+quadNdx) % 4] + center,
+							   corners[(2+quadNdx) % 4] + center,
+							   corners[(0+quadNdx) % 4] + center,
+							   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+			}
+			else
+			{
+				renderTriangle(corners[(0+quadNdx) % 4] + center,
+							   corners[(2+quadNdx) % 4] + center,
+							   corners[(3+quadNdx) % 4] + center,
+							   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+			}
+
+			// The size of the "interior" of a quad is assumed to be approximately unicolorRegionScale*<actual size of quad>.
+			// By "interior" we here mean the region of non-boundary pixels of the rendered quad for which we can safely assume
+			// that it has all coverage bits set to 1, for every pixel.
+			float unicolorRegionScale = 1.0f - 6.0f*2.0f / (float)m_viewportSize / quadDiagLen;
+			unicoloredRegions.push_back(QuadCorners((center + corners[0]*unicolorRegionScale),
+													(center + corners[1]*unicolorRegionScale),
+													(center + corners[2]*unicolorRegionScale),
+													(center + corners[3]*unicolorRegionScale)));
+		}
+	}
+	else if (m_caseType == CASETYPE_BIGGER_THAN_VIEWPORT_QUAD)
+	{
+		// Draw a bigger-than-viewport quad, rotated at an angle depending on m_currentIteration.
+
+		int				quadBaseAngleNdx		= m_currentIteration / 8;
+		int				quadSubAngleNdx			= m_currentIteration % 8;
+		float			angleCos;
+		float			angleSin;
+
+		if (quadBaseAngleNdx == 0)
+		{
+			angleCos = 1.0f;
+			angleSin = 0.0f;
+		}
+		else if (quadBaseAngleNdx == 1)
+		{
+			angleCos = SQRT_HALF;
+			angleSin = SQRT_HALF;
+		}
+		else
+		{
+			float angle = 0.5f * DE_PI * (float)(m_currentIteration-1) / (float)(m_numIterations-1);
+			angleCos = deFloatCos(angle);
+			angleSin = deFloatSin(angle);
+		}
+
+		float quadDiagLen = 2.5f / de::max(angleCos, angleSin);
+
+		Vec2 corners[4] =
+		{
+			0.5f * quadDiagLen * Vec2( angleCos,  angleSin),
+			0.5f * quadDiagLen * Vec2(-angleSin,  angleCos),
+			0.5f * quadDiagLen * Vec2(-angleCos, -angleSin),
+			0.5f * quadDiagLen * Vec2( angleSin, -angleCos)
+		};
+
+		renderTriangle(corners[(0+quadSubAngleNdx) % 4],
+					   corners[(1+quadSubAngleNdx) % 4],
+					   corners[(2+quadSubAngleNdx) % 4],
+					   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+
+		if (quadSubAngleNdx >= 4)
+		{
+			renderTriangle(corners[(3+quadSubAngleNdx) % 4],
+						   corners[(2+quadSubAngleNdx) % 4],
+						   corners[(0+quadSubAngleNdx) % 4],
+						   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+		}
+		else
+		{
+			renderTriangle(corners[(0+quadSubAngleNdx) % 4],
+						   corners[(2+quadSubAngleNdx) % 4],
+						   corners[(3+quadSubAngleNdx) % 4],
+						   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+		}
+
+		float unicolorRegionScale = 1.0f - 6.0f*2.0f / (float)m_viewportSize / quadDiagLen;
+		unicoloredRegions.push_back(QuadCorners((corners[0]*unicolorRegionScale),
+												(corners[1]*unicolorRegionScale),
+												(corners[2]*unicolorRegionScale),
+												(corners[3]*unicolorRegionScale)));
+	}
+	else if (m_caseType == CASETYPE_FIT_VIEWPORT_QUAD)
+	{
+		// Draw an exactly viewport-sized quad, rotated by multiples of 90 degrees angle depending on m_currentIteration.
+
+		int quadSubAngleNdx = m_currentIteration % 8;
+
+		Vec2 corners[4] =
+		{
+			Vec2( 1.0f,  1.0f),
+			Vec2(-1.0f,  1.0f),
+			Vec2(-1.0f, -1.0f),
+			Vec2( 1.0f, -1.0f)
+		};
+
+		renderTriangle(corners[(0+quadSubAngleNdx) % 4],
+					   corners[(1+quadSubAngleNdx) % 4],
+					   corners[(2+quadSubAngleNdx) % 4],
+					   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+
+		if (quadSubAngleNdx >= 4)
+		{
+			renderTriangle(corners[(3+quadSubAngleNdx) % 4],
+						   corners[(2+quadSubAngleNdx) % 4],
+						   corners[(0+quadSubAngleNdx) % 4],
+						   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+		}
+		else
+		{
+			renderTriangle(corners[(0+quadSubAngleNdx) % 4],
+						   corners[(2+quadSubAngleNdx) % 4],
+						   corners[(3+quadSubAngleNdx) % 4],
+						   Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+		}
+
+		unicoloredRegions.push_back(QuadCorners(corners[0], corners[1], corners[2], corners[3]));
+	}
+	else
+		DE_ASSERT(false);
+
+	// Read pixels and check unicolored regions.
+
+	readImage(renderedImg);
+
+	tcu::clear(errorImg.getAccess(), Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	bool errorsDetected = false;
+	for (int i = 0; i < (int)unicoloredRegions.size(); i++)
+	{
+		const QuadCorners&	region					= unicoloredRegions[i];
+		IVec2				p0Win					= ((region.p0+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt();
+		IVec2				p1Win					= ((region.p1+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt();
+		IVec2				p2Win					= ((region.p2+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt();
+		IVec2				p3Win					= ((region.p3+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt();
+		bool				errorsInCurrentRegion	= !isPixelRegionUnicolored(renderedImg, p0Win, p1Win, p2Win, p3Win);
+
+		if (errorsInCurrentRegion)
+			drawUnicolorTestErrors(renderedImg, errorImg.getAccess(), p0Win, p1Win, p2Win, p3Win);
+
+		errorsDetected = errorsDetected || errorsInCurrentRegion;
+	}
+
+	m_currentIteration++;
+
+	if (errorsDetected)
+	{
+		log << TestLog::Message << "Failure: Not all quad interiors seem unicolored - common-edge artifacts?" << TestLog::EndMessage;
+		log << TestLog::Message << "Erroneous pixels are drawn red in the following image" << TestLog::EndMessage;
+		log << TestLog::Image("RenderedImageWithErrors",	"Rendered image with errors marked",	renderedImg,	QP_IMAGE_COMPRESSION_MODE_PNG);
+		log << TestLog::Image("ErrorsOnly",					"Image with error pixels only",			errorImg,		QP_IMAGE_COMPRESSION_MODE_PNG);
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+	else if (m_currentIteration < m_numIterations)
+	{
+		log << TestLog::Message << "Quads seem OK - moving on to next pattern" << TestLog::EndMessage;
+		return CONTINUE;
+	}
+	else
+	{
+		log << TestLog::Message << "Success: All quad interiors seem unicolored (no common-edge artifacts)" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+		return STOP;
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test that depth values are per-sample.
+ *
+ * Draws intersecting, differently-colored polygons and checks that there
+ * are at least sample count+1 distinct colors present, due to some of the
+ * samples at the intersection line belonging to one and some to another
+ * polygon.
+ *//*--------------------------------------------------------------------*/
+class SampleDepthCase : public NumSamplesCase
+{
+public:
+						SampleDepthCase			(Context& context, const char* name, const char* description, int numFboSamples = 0);
+						~SampleDepthCase		(void) {}
+
+	void				init					(void);
+
+protected:
+	void				renderPattern			(void) const;
+};
+
+SampleDepthCase::SampleDepthCase (Context& context, const char* name, const char* description, int numFboSamples)
+	: NumSamplesCase (context, name, description, numFboSamples >= 0 ? FboParams(numFboSamples, true, false) : FboParams())
+{
+}
+
+void SampleDepthCase::init (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	MultisampleCase::init();
+
+	GLU_CHECK_CALL(glEnable(GL_DEPTH_TEST));
+	GLU_CHECK_CALL(glDepthFunc(GL_LESS));
+
+	log << TestLog::Message << "Depth test enabled, depth func is GL_LESS" << TestLog::EndMessage;
+	log << TestLog::Message << "Drawing several bigger-than-viewport black or white polygons intersecting each other" << TestLog::EndMessage;
+}
+
+void SampleDepthCase::renderPattern (void) const
+{
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
+	GLU_CHECK_CALL(glClearDepthf(1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
+
+	{
+		const int numPolygons = 50;
+
+		for (int i = 0; i < numPolygons; i++)
+		{
+			Vec4	color	= i % 2 == 0 ? Vec4(1.0f, 1.0f, 1.0f, 1.0f) : Vec4(0.0f, 0.0f, 0.0f, 1.0f);
+			float	angle	= 2.0f * DE_PI * (float)i / (float)numPolygons + 0.001f*(float)m_currentIteration;
+			Vec3	pt0		(3.0f*deFloatCos(angle + 2.0f*DE_PI*0.0f/3.0f), 3.0f*deFloatSin(angle + 2.0f*DE_PI*0.0f/3.0f), 1.0f);
+			Vec3	pt1		(3.0f*deFloatCos(angle + 2.0f*DE_PI*1.0f/3.0f), 3.0f*deFloatSin(angle + 2.0f*DE_PI*1.0f/3.0f), 0.0f);
+			Vec3	pt2		(3.0f*deFloatCos(angle + 2.0f*DE_PI*2.0f/3.0f), 3.0f*deFloatSin(angle + 2.0f*DE_PI*2.0f/3.0f), 0.0f);
+
+			renderTriangle(pt0, pt1, pt2, color);
+		}
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test that stencil buffer values are per-sample.
+ *
+ * Draws a unicolored pattern and marks drawn samples in stencil buffer;
+ * then clears and draws a viewport-size quad with that color and with
+ * proper stencil test such that the resulting image should be exactly the
+ * same as after the pattern was first drawn.
+ *//*--------------------------------------------------------------------*/
+class SampleStencilCase : public MultisampleCase
+{
+public:
+						SampleStencilCase		(Context& context, const char* name, const char* description, int numFboSamples = 0);
+						~SampleStencilCase		(void) {}
+
+	IterateResult		iterate					(void);
+};
+
+SampleStencilCase::SampleStencilCase (Context& context, const char* name, const char* description, int numFboSamples)
+	: MultisampleCase (context, name, description, 256, numFboSamples >= 0 ? FboParams(numFboSamples, false, true) : FboParams())
+{
+}
+
+SampleStencilCase::IterateResult SampleStencilCase::iterate (void)
+{
+	TestLog&		log					= m_testCtx.getLog();
+	tcu::Surface	renderedImgFirst	(m_viewportSize, m_viewportSize);
+	tcu::Surface	renderedImgSecond	(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClearStencil(0));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
+	GLU_CHECK_CALL(glEnable(GL_STENCIL_TEST));
+	GLU_CHECK_CALL(glStencilFunc(GL_ALWAYS, 1, 1));
+	GLU_CHECK_CALL(glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE));
+
+	log << TestLog::Message << "Drawing a pattern with glStencilFunc(GL_ALWAYS, 1, 1) and glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)" << TestLog::EndMessage;
+
+	{
+		const int numTriangles = 25;
+		for (int i = 0; i < numTriangles; i++)
+		{
+			float angle0 = 2.0f*DE_PI * (float)i			/ (float)numTriangles;
+			float angle1 = 2.0f*DE_PI * (float)(i + 0.5f)	/ (float)numTriangles;
+
+			renderTriangle(Vec2(0.0f, 0.0f),
+						   Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f),
+						   Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f),
+						   Vec4(1.0f));
+		}
+	}
+
+	readImage(renderedImgFirst);
+	log << TestLog::Image("RenderedImgFirst", "First image rendered", renderedImgFirst, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	log << TestLog::Message << "Clearing color buffer to black" << TestLog::EndMessage;
+
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+	GLU_CHECK_CALL(glStencilFunc(GL_EQUAL, 1, 1));
+	GLU_CHECK_CALL(glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP));
+
+	{
+		log << TestLog::Message << "Checking that color buffer was actually cleared to black" << TestLog::EndMessage;
+
+		tcu::Surface clearedImg(m_viewportSize, m_viewportSize);
+		readImage(clearedImg);
+
+		for (int y = 0; y < clearedImg.getHeight(); y++)
+		for (int x = 0; x < clearedImg.getWidth(); x++)
+		{
+			const tcu::RGBA& clr = clearedImg.getPixel(x, y);
+			if (clr != tcu::RGBA::black)
+			{
+				log << TestLog::Message << "Failure: first non-black pixel, color " << clr << ", detected at coordinates (" << x << ", " << y << ")" << TestLog::EndMessage;
+				log << TestLog::Image("ClearedImg", "Image after clearing, erroneously non-black", clearedImg);
+				m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+				return STOP;
+			}
+		}
+	}
+
+	log << TestLog::Message << "Drawing a viewport-sized quad with glStencilFunc(GL_EQUAL, 1, 1) and glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) - should result in same image as the first" << TestLog::EndMessage;
+
+	renderQuad(Vec2(-1.0f, -1.0f),
+			   Vec2( 1.0f, -1.0f),
+			   Vec2(-1.0f,  1.0f),
+			   Vec2( 1.0f,  1.0f),
+			   Vec4(1.0f));
+
+	readImage(renderedImgSecond);
+	log << TestLog::Image("RenderedImgSecond", "Second image rendered", renderedImgSecond, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	bool passed = tcu::pixelThresholdCompare(log,
+											 "ImageCompare",
+											 "Image comparison",
+											 renderedImgFirst,
+											 renderedImgSecond,
+											 tcu::RGBA(0),
+											 tcu::COMPARE_LOG_ON_ERROR);
+
+	if (passed)
+		log << TestLog::Message << "Success: The two images rendered are identical" << TestLog::EndMessage;
+
+	m_context.getTestContext().setTestResult(passed ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+											 passed ? "Passed"				: "Failed");
+
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests coverage mask generation proportionality property.
+ *
+ * Tests that the number of coverage bits in a coverage mask created by
+ * GL_SAMPLE_ALPHA_TO_COVERAGE or GL_SAMPLE_COVERAGE is, on average,
+ * proportional to the alpha or coverage value, respectively. Draws
+ * multiple frames, each time increasing the alpha or coverage value used,
+ * and checks that the average color is changing appropriately.
+ *//*--------------------------------------------------------------------*/
+class MaskProportionalityCase : public MultisampleCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_ALPHA_TO_COVERAGE = 0,
+		CASETYPE_SAMPLE_COVERAGE,
+		CASETYPE_SAMPLE_COVERAGE_INVERTED,
+
+		CASETYPE_LAST
+	};
+
+					MaskProportionalityCase				(Context& context, const char* name, const char* description, CaseType type, int numFboSamples = 0);
+					~MaskProportionalityCase			(void) {}
+
+	void			init								(void);
+
+	IterateResult	iterate								(void);
+
+private:
+	const CaseType	m_type;
+
+	int				m_numIterations;
+	int				m_currentIteration;
+
+	deInt32			m_previousIterationColorSum;
+};
+
+MaskProportionalityCase::MaskProportionalityCase (Context& context, const char* name, const char* description, CaseType type, int numFboSamples)
+	: MultisampleCase				(context, name, description, 32, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams())
+	, m_type						(type)
+	, m_currentIteration			(0)
+	, m_previousIterationColorSum	(-1)
+{
+}
+
+void MaskProportionalityCase::init (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	MultisampleCase::init();
+
+	if (m_type == CASETYPE_ALPHA_TO_COVERAGE)
+	{
+		GLU_CHECK_CALL(glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE));
+		log << TestLog::Message << "GL_SAMPLE_ALPHA_TO_COVERAGE is enabled" << TestLog::EndMessage;
+	}
+	else
+	{
+		DE_ASSERT(m_type == CASETYPE_SAMPLE_COVERAGE || m_type == CASETYPE_SAMPLE_COVERAGE_INVERTED);
+
+		GLU_CHECK_CALL(glEnable(GL_SAMPLE_COVERAGE));
+		log << TestLog::Message << "GL_SAMPLE_COVERAGE is enabled" << TestLog::EndMessage;
+	}
+
+	m_numIterations = de::max(2, getIterationCount(m_testCtx, m_numSamples * 5));
+
+	randomizeViewport(); // \note Using the same viewport for every iteration since coverage mask may depend on window-relative pixel coordinate.
+}
+
+MaskProportionalityCase::IterateResult MaskProportionalityCase::iterate (void)
+{
+	TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface	renderedImg		(m_viewportSize, m_viewportSize);
+	deInt32			numPixels		= (deInt32)renderedImg.getWidth()*(deInt32)renderedImg.getHeight();
+
+	log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
+	GLU_CHECK_CALL(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE));
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	if (m_type == CASETYPE_ALPHA_TO_COVERAGE)
+	{
+		GLU_CHECK_CALL(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE));
+		log << TestLog::Message << "Using color mask TRUE, TRUE, TRUE, FALSE" << TestLog::EndMessage;
+	}
+
+	// Draw quad.
+
+	{
+		const Vec2		pt0						(-1.0f, -1.0f);
+		const Vec2		pt1						( 1.0f, -1.0f);
+		const Vec2		pt2						(-1.0f,  1.0f);
+		const Vec2		pt3						( 1.0f,  1.0f);
+		Vec4			quadColor				(1.0f, 0.0f, 0.0f, 1.0f);
+		float			alphaOrCoverageValue	= (float)m_currentIteration / (float)(m_numIterations-1);
+
+		if (m_type == CASETYPE_ALPHA_TO_COVERAGE)
+		{
+			log << TestLog::Message << "Drawing a red quad using alpha value " + de::floatToString(alphaOrCoverageValue, 2) << TestLog::EndMessage;
+			quadColor.w() = alphaOrCoverageValue;
+		}
+		else
+		{
+			DE_ASSERT(m_type == CASETYPE_SAMPLE_COVERAGE || m_type == CASETYPE_SAMPLE_COVERAGE_INVERTED);
+
+			bool	isInverted		= m_type == CASETYPE_SAMPLE_COVERAGE_INVERTED;
+			float	coverageValue	= isInverted ? 1.0f - alphaOrCoverageValue : alphaOrCoverageValue;
+			log << TestLog::Message << "Drawing a red quad using sample coverage value " + de::floatToString(coverageValue, 2) << (isInverted ? " (inverted)" : "") << TestLog::EndMessage;
+			GLU_CHECK_CALL(glSampleCoverage(coverageValue, isInverted ? GL_TRUE : GL_FALSE));
+		}
+
+		renderQuad(pt0, pt1, pt2, pt3, quadColor);
+	}
+
+	// Read ang log image.
+
+	readImage(renderedImg);
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	// Compute average red component in rendered image.
+
+	deInt32 sumRed = 0;
+
+	for (int y = 0; y < renderedImg.getHeight(); y++)
+	for (int x = 0; x < renderedImg.getWidth(); x++)
+		sumRed += renderedImg.getPixel(x, y).getRed();
+
+	log << TestLog::Message << "Average red color component: " << de::floatToString((float)sumRed / 255.0f / (float)numPixels, 2) << TestLog::EndMessage;
+
+	// Check if average color has decreased from previous frame's color.
+
+	if (sumRed < m_previousIterationColorSum)
+	{
+		log << TestLog::Message << "Failure: Current average red color component is lower than previous" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+
+	// Check if coverage mask is not all-zeros if alpha or coverage value is 0 (or 1, if inverted).
+
+	if (m_currentIteration == 0 && sumRed != 0)
+	{
+		log << TestLog::Message << "Failure: Image should be completely black" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+
+	if (m_currentIteration == m_numIterations-1 && sumRed != 0xff*numPixels)
+	{
+		log << TestLog::Message << "Failure: Image should be completely red" << TestLog::EndMessage;
+
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+
+	m_previousIterationColorSum = sumRed;
+
+	m_currentIteration++;
+
+	if (m_currentIteration >= m_numIterations)
+	{
+		log << TestLog::Message
+			<< "Success: Number of coverage mask bits set appears to be, on average, proportional to "
+			<< (m_type == CASETYPE_ALPHA_TO_COVERAGE ? "alpha" : m_type == CASETYPE_SAMPLE_COVERAGE ? "sample coverage value" : "inverted sample coverage value")
+			<< TestLog::EndMessage;
+
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests coverage mask generation constancy property.
+ *
+ * Tests that the coverage mask created by GL_SAMPLE_ALPHA_TO_COVERAGE or
+ * GL_SAMPLE_COVERAGE is constant at given pixel coordinates, with a given
+ * alpha component or coverage value, respectively. Draws two quads, with
+ * the second one fully overlapping the first one such that at any given
+ * pixel, both quads have the same alpha or coverage value. This way, if
+ * the constancy property is fulfilled, only the second quad should be
+ * visible.
+ *//*--------------------------------------------------------------------*/
+class MaskConstancyCase : public MultisampleCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_ALPHA_TO_COVERAGE = 0,		//!< Use only alpha-to-coverage.
+		CASETYPE_SAMPLE_COVERAGE,			//!< Use only sample coverage.
+		CASETYPE_SAMPLE_COVERAGE_INVERTED,	//!< Use only inverted sample coverage.
+		CASETYPE_BOTH,						//!< Use both alpha-to-coverage and sample coverage.
+		CASETYPE_BOTH_INVERTED,				//!< Use both alpha-to-coverage and inverted sample coverage.
+
+		CASETYPE_LAST
+	};
+
+					MaskConstancyCase			(Context& context, const char* name, const char* description, CaseType type, int numFboSamples = 0);
+					~MaskConstancyCase			(void) {}
+
+	IterateResult	iterate						(void);
+
+private:
+	const bool		m_isAlphaToCoverageCase;
+	const bool		m_isSampleCoverageCase;
+	const bool		m_isInvertedSampleCoverageCase;
+};
+
+MaskConstancyCase::MaskConstancyCase (Context& context, const char* name, const char* description, CaseType type, int numFboSamples)
+	: MultisampleCase					(context, name, description, 256, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams())
+	, m_isAlphaToCoverageCase			(type == CASETYPE_ALPHA_TO_COVERAGE			|| type == CASETYPE_BOTH						|| type == CASETYPE_BOTH_INVERTED)
+	, m_isSampleCoverageCase			(type == CASETYPE_SAMPLE_COVERAGE			|| type == CASETYPE_SAMPLE_COVERAGE_INVERTED	|| type == CASETYPE_BOTH			|| type == CASETYPE_BOTH_INVERTED)
+	, m_isInvertedSampleCoverageCase	(type == CASETYPE_SAMPLE_COVERAGE_INVERTED	|| type == CASETYPE_BOTH_INVERTED)
+{
+}
+
+MaskConstancyCase::IterateResult MaskConstancyCase::iterate (void)
+{
+	TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface	renderedImg		(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	if (m_isAlphaToCoverageCase)
+	{
+		GLU_CHECK_CALL(glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE));
+		GLU_CHECK_CALL(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE));
+		log << TestLog::Message << "GL_SAMPLE_ALPHA_TO_COVERAGE is enabled" << TestLog::EndMessage;
+		log << TestLog::Message << "Color mask is TRUE, TRUE, TRUE, FALSE" << TestLog::EndMessage;
+	}
+
+	if (m_isSampleCoverageCase)
+	{
+		GLU_CHECK_CALL(glEnable(GL_SAMPLE_COVERAGE));
+		log << TestLog::Message << "GL_SAMPLE_COVERAGE is enabled" << TestLog::EndMessage;
+	}
+
+	log << TestLog::Message
+		<< "Drawing several green quads, each fully overlapped by a red quad with the same "
+		<< (m_isAlphaToCoverageCase ? "alpha" : "")
+		<< (m_isAlphaToCoverageCase && m_isSampleCoverageCase ? " and " : "")
+		<< (m_isInvertedSampleCoverageCase ? "inverted " : "")
+		<< (m_isSampleCoverageCase ? "sample coverage" : "")
+		<< " values"
+		<< TestLog::EndMessage;
+
+	const int numQuadRowsCols = m_numSamples*4;
+
+	for (int row = 0; row < numQuadRowsCols; row++)
+	{
+		for (int col = 0; col < numQuadRowsCols; col++)
+		{
+			float		x0			= (float)(col+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float		x1			= (float)(col+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float		y0			= (float)(row+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float		y1			= (float)(row+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			const Vec4	baseGreen	(0.0f, 1.0f, 0.0f, 0.0f);
+			const Vec4	baseRed		(1.0f, 0.0f, 0.0f, 0.0f);
+			Vec4		alpha0		(0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)col / (float)(numQuadRowsCols-1) : 1.0f);
+			Vec4		alpha1		(0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)row / (float)(numQuadRowsCols-1) : 1.0f);
+
+			if (m_isSampleCoverageCase)
+			{
+				float value = (float)(row*numQuadRowsCols + col) / (float)(numQuadRowsCols*numQuadRowsCols-1);
+				GLU_CHECK_CALL(glSampleCoverage(m_isInvertedSampleCoverageCase ? 1.0f - value : value, m_isInvertedSampleCoverageCase ? GL_TRUE : GL_FALSE));
+			}
+
+			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseGreen + alpha0,	baseGreen + alpha1,	baseGreen + alpha0,	baseGreen + alpha1);
+			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseRed + alpha0,	baseRed + alpha1,	baseRed + alpha0,	baseRed + alpha1);
+		}
+	}
+
+	readImage(renderedImg);
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	for (int y = 0; y < renderedImg.getHeight(); y++)
+	for (int x = 0; x < renderedImg.getWidth(); x++)
+	{
+		if (renderedImg.getPixel(x, y).getGreen() > 0)
+		{
+			log << TestLog::Message << "Failure: Non-zero green color component detected - should have been completely overwritten by red quad" << TestLog::EndMessage;
+			m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+			return STOP;
+		}
+	}
+
+	log << TestLog::Message
+		<< "Success: Coverage mask appears to be constant at a given pixel coordinate with a given "
+		<< (m_isAlphaToCoverageCase ? "alpha" : "")
+		<< (m_isAlphaToCoverageCase && m_isSampleCoverageCase ? " and " : "")
+		<< (m_isSampleCoverageCase ? "coverage value" : "")
+		<< TestLog::EndMessage;
+
+	m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests coverage mask inversion validity.
+ *
+ * Tests that the coverage masks obtained by glSampleCoverage(..., GL_TRUE)
+ * and glSampleCoverage(..., GL_FALSE) are indeed each others' inverses.
+ * This is done by drawing a pattern, with varying coverage values,
+ * overlapped by a pattern that has inverted masks and is otherwise
+ * identical. The resulting image is compared to one obtained by drawing
+ * the same pattern but with all-ones coverage masks.
+ *//*--------------------------------------------------------------------*/
+class CoverageMaskInvertCase : public MultisampleCase
+{
+public:
+					CoverageMaskInvertCase		(Context& context, const char* name, const char* description, int numFboSamples = 0);
+					~CoverageMaskInvertCase		(void) {}
+
+	IterateResult	iterate						(void);
+
+private:
+	void			drawPattern					(bool invertSampleCoverage) const;
+};
+
+CoverageMaskInvertCase::CoverageMaskInvertCase (Context& context, const char* name, const char* description, int numFboSamples)
+	: MultisampleCase (context, name, description, 256, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams())
+{
+}
+
+void CoverageMaskInvertCase::drawPattern (bool invertSampleCoverage) const
+{
+	const int numTriangles = 25;
+	for (int i = 0; i < numTriangles; i++)
+	{
+		GLU_CHECK_CALL(glSampleCoverage((float)i / (float)(numTriangles-1), invertSampleCoverage ? GL_TRUE : GL_FALSE));
+
+		float angle0 = 2.0f*DE_PI * (float)i			/ (float)numTriangles;
+		float angle1 = 2.0f*DE_PI * (float)(i + 0.5f)	/ (float)numTriangles;
+
+		renderTriangle(Vec2(0.0f, 0.0f),
+					   Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f),
+					   Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f),
+					   Vec4(0.4f + (float)i/(float)numTriangles*0.6f,
+							0.5f + (float)i/(float)numTriangles*0.3f,
+							0.6f - (float)i/(float)numTriangles*0.5f,
+							0.7f - (float)i/(float)numTriangles*0.7f));
+	}
+}
+
+CoverageMaskInvertCase::IterateResult CoverageMaskInvertCase::iterate (void)
+{
+	TestLog&		log								= m_testCtx.getLog();
+	tcu::Surface	renderedImgNoSampleCoverage		(m_viewportSize, m_viewportSize);
+	tcu::Surface	renderedImgSampleCoverage		(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	GLU_CHECK_CALL(glEnable(GL_BLEND));
+	GLU_CHECK_CALL(glBlendEquation(GL_FUNC_ADD));
+	GLU_CHECK_CALL(glBlendFunc(GL_ONE, GL_ONE));
+	log << TestLog::Message << "Additive blending enabled in order to detect (erroneously) overlapping samples" << TestLog::EndMessage;
+
+	log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage;
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_COVERAGE disabled" << TestLog::EndMessage;
+	drawPattern(false);
+	readImage(renderedImgNoSampleCoverage);
+
+	log << TestLog::Image("RenderedImageNoSampleCoverage", "Rendered image with GL_SAMPLE_COVERAGE disabled", renderedImgNoSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage;
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+	GLU_CHECK_CALL(glEnable(GL_SAMPLE_COVERAGE));
+	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_COVERAGE enabled, using non-inverted masks" << TestLog::EndMessage;
+	drawPattern(false);
+	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_COVERAGE enabled, using same sample coverage values but inverted masks" << TestLog::EndMessage;
+	drawPattern(true);
+	readImage(renderedImgSampleCoverage);
+
+	log << TestLog::Image("RenderedImageSampleCoverage", "Rendered image with GL_SAMPLE_COVERAGE enabled", renderedImgSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	bool passed = tcu::pixelThresholdCompare(log,
+											 "CoverageVsNoCoverage",
+											 "Comparison of same pattern with GL_SAMPLE_COVERAGE disabled and enabled",
+											 renderedImgNoSampleCoverage,
+											 renderedImgSampleCoverage,
+											 tcu::RGBA(0),
+											 tcu::COMPARE_LOG_ON_ERROR);
+
+	if (passed)
+		log << TestLog::Message << "Success: The two images rendered are identical" << TestLog::EndMessage;
+
+	m_context.getTestContext().setTestResult(passed ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+											 passed ? "Passed"				: "Failed");
+
+	return STOP;
+}
+
+MultisampleTests::MultisampleTests (Context& context)
+	: TestCaseGroup(context, "multisample", "Multisampling tests")
+{
+}
+
+MultisampleTests::~MultisampleTests (void)
+{
+}
+
+void MultisampleTests::init (void)
+{
+	enum CaseType
+	{
+		CASETYPE_DEFAULT_FRAMEBUFFER = 0,
+		CASETYPE_FBO_4_SAMPLES,
+		CASETYPE_FBO_8_SAMPLES,
+		CASETYPE_FBO_MAX_SAMPLES,
+
+		CASETYPE_LAST
+	};
+
+	for (int caseTypeI = 0; caseTypeI < (int)CASETYPE_LAST; caseTypeI++)
+	{
+		CaseType		caseType		= (CaseType)caseTypeI;
+		int				numFboSamples	= caseType == CASETYPE_DEFAULT_FRAMEBUFFER	? -1
+										: caseType == CASETYPE_FBO_4_SAMPLES		? 4
+										: caseType == CASETYPE_FBO_8_SAMPLES		? 8
+										: caseType == CASETYPE_FBO_MAX_SAMPLES		? 0
+										: -2;
+
+		TestCaseGroup*	group			= new TestCaseGroup(m_context,
+															caseType == CASETYPE_DEFAULT_FRAMEBUFFER	? "default_framebuffer" :
+															caseType == CASETYPE_FBO_4_SAMPLES			? "fbo_4_samples" :
+															caseType == CASETYPE_FBO_8_SAMPLES			? "fbo_8_samples" :
+															caseType == CASETYPE_FBO_MAX_SAMPLES		? "fbo_max_samples" :
+															DE_NULL,
+															caseType == CASETYPE_DEFAULT_FRAMEBUFFER	? "Render into default framebuffer" :
+															caseType == CASETYPE_FBO_4_SAMPLES			? "Render into a framebuffer object with 4 samples" :
+															caseType == CASETYPE_FBO_8_SAMPLES			? "Render into a framebuffer object with 8 samples" :
+															caseType == CASETYPE_FBO_MAX_SAMPLES		? "Render into a framebuffer object with the maximum number of samples" :
+															DE_NULL);
+		DE_ASSERT(group->getName() != DE_NULL);
+		DE_ASSERT(group->getDescription() != DE_NULL);
+		DE_ASSERT(numFboSamples >= -1);
+		addChild(group);
+
+		group->addChild(new PolygonNumSamplesCase		(m_context, "num_samples_polygon",			"Test sanity of the sample count, with polygons",										numFboSamples));
+		group->addChild(new LineNumSamplesCase			(m_context, "num_samples_line",				"Test sanity of the sample count, with lines",											numFboSamples));
+		group->addChild(new CommonEdgeCase				(m_context, "common_edge_small_quads",		"Test polygons' common edges with small quads",											CommonEdgeCase::CASETYPE_SMALL_QUADS,				numFboSamples));
+		group->addChild(new CommonEdgeCase				(m_context, "common_edge_big_quad",			"Test polygons' common edges with bigger-than-viewport quads",							CommonEdgeCase::CASETYPE_BIGGER_THAN_VIEWPORT_QUAD,	numFboSamples));
+		group->addChild(new CommonEdgeCase				(m_context, "common_edge_viewport_quad",	"Test polygons' common edges with exactly viewport-sized quads",						CommonEdgeCase::CASETYPE_FIT_VIEWPORT_QUAD,			numFboSamples));
+		group->addChild(new SampleDepthCase				(m_context, "depth",						"Test that depth values are per-sample",												numFboSamples));
+		group->addChild(new SampleStencilCase			(m_context, "stencil",						"Test that stencil values are per-sample",												numFboSamples));
+		group->addChild(new CoverageMaskInvertCase		(m_context, "sample_coverage_invert",		"Test that non-inverted and inverted sample coverage masks are each other's negations",	numFboSamples));
+
+		group->addChild(new MaskProportionalityCase		(m_context, "proportionality_alpha_to_coverage",
+																	"Test the proportionality property of GL_SAMPLE_ALPHA_TO_COVERAGE",
+																	MaskProportionalityCase::CASETYPE_ALPHA_TO_COVERAGE, numFboSamples));
+		group->addChild(new MaskProportionalityCase		(m_context, "proportionality_sample_coverage",
+																	"Test the proportionality property of GL_SAMPLE_COVERAGE",
+																	MaskProportionalityCase::CASETYPE_SAMPLE_COVERAGE, numFboSamples));
+		group->addChild(new MaskProportionalityCase		(m_context, "proportionality_sample_coverage_inverted",
+																	"Test the proportionality property of inverted-mask GL_SAMPLE_COVERAGE",
+																	MaskProportionalityCase::CASETYPE_SAMPLE_COVERAGE_INVERTED, numFboSamples));
+
+		group->addChild(new MaskConstancyCase			(m_context, "constancy_alpha_to_coverage",
+																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE",
+																	MaskConstancyCase::CASETYPE_ALPHA_TO_COVERAGE, numFboSamples));
+		group->addChild(new MaskConstancyCase			(m_context, "constancy_sample_coverage",
+																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_COVERAGE",
+																	MaskConstancyCase::CASETYPE_SAMPLE_COVERAGE, numFboSamples));
+		group->addChild(new MaskConstancyCase			(m_context, "constancy_sample_coverage_inverted",
+																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using inverted-mask GL_SAMPLE_COVERAGE",
+																	MaskConstancyCase::CASETYPE_SAMPLE_COVERAGE_INVERTED, numFboSamples));
+		group->addChild(new MaskConstancyCase			(m_context, "constancy_both",
+																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE and GL_SAMPLE_COVERAGE",
+																	MaskConstancyCase::CASETYPE_BOTH, numFboSamples));
+		group->addChild(new MaskConstancyCase			(m_context, "constancy_both_inverted",
+																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE and inverted-mask GL_SAMPLE_COVERAGE",
+																	MaskConstancyCase::CASETYPE_BOTH_INVERTED, numFboSamples));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fMultisampleTests.hpp b/modules/gles3/functional/es3fMultisampleTests.hpp
new file mode 100644
index 0000000..0b4ad12
--- /dev/null
+++ b/modules/gles3/functional/es3fMultisampleTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FMULTISAMPLETESTS_HPP
+#define _ES3FMULTISAMPLETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisampling tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class MultisampleTests : public TestCaseGroup
+{
+public:
+						MultisampleTests	(Context& context);
+						~MultisampleTests	(void);
+
+	void				init				(void);
+
+private:
+						MultisampleTests	(const MultisampleTests& other);
+	MultisampleTests&	operator=			(const MultisampleTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FMULTISAMPLETESTS_HPP
diff --git a/modules/gles3/functional/es3fNegativeBufferApiTests.cpp b/modules/gles3/functional/es3fNegativeBufferApiTests.cpp
new file mode 100644
index 0000000..827d872
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeBufferApiTests.cpp
@@ -0,0 +1,1390 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Buffer API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fNegativeBufferApiTests.hpp"
+#include "es3fApiCase.hpp"
+#include "gluContextInfo.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+
+NegativeBufferApiTests::NegativeBufferApiTests (Context& context)
+	: TestCaseGroup(context, "buffer", "Negative Buffer API Cases")
+{
+}
+
+NegativeBufferApiTests::~NegativeBufferApiTests (void)
+{
+}
+
+void NegativeBufferApiTests::init (void)
+{
+	// Buffers
+
+	ES3F_ADD_API_CASE(bind_buffer, "Invalid glBindBuffer() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the allowable values.");
+			glBindBuffer(-1, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(delete_buffers, "Invalid glDeleteBuffers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteBuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(gen_buffers, "Invalid glGenBuffers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glGenBuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(buffer_data, "Invalid glBufferData() usage",
+		{
+			GLuint buffer;
+			glGenBuffers(1, &buffer);
+			glBindBuffer(GL_ARRAY_BUFFER, buffer);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER.");
+			glBufferData(-1, 0, NULL, GL_STREAM_DRAW);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if usage is not GL_STREAM_DRAW, GL_STATIC_DRAW, or GL_DYNAMIC_DRAW.");
+			glBufferData(GL_ARRAY_BUFFER, 0, NULL, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if size is negative.");
+			glBufferData(GL_ARRAY_BUFFER, -1, NULL, GL_STREAM_DRAW);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &buffer);
+		});
+	ES3F_ADD_API_CASE(buffer_sub_data, "Invalid glBufferSubData() usage",
+		{
+			GLuint buffer;
+			glGenBuffers(1, &buffer);
+			glBindBuffer(GL_ARRAY_BUFFER, buffer);
+			glBufferData(GL_ARRAY_BUFFER, 10, 0, GL_STREAM_DRAW);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER.");
+			glBufferSubData(-1, 1, 1, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			glBufferSubData(GL_ARRAY_BUFFER, 1, 1, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the buffer object being updated is mapped.");
+			glBindBuffer(GL_ARRAY_BUFFER, buffer);
+			glMapBufferRange(GL_ARRAY_BUFFER, 0, 5, GL_MAP_READ_BIT);
+			expectError(GL_NO_ERROR);
+			glBufferSubData(GL_ARRAY_BUFFER, 1, 1, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &buffer);
+		});
+	ES3F_ADD_API_CASE(buffer_sub_data_size_offset, "Invalid glBufferSubData() usage",
+		{
+			GLuint buffer;
+			glGenBuffers(1, &buffer);
+			glBindBuffer(GL_ARRAY_BUFFER, buffer);
+			glBufferData(GL_ARRAY_BUFFER, 10, 0, GL_STREAM_DRAW);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if offset or size is negative, or if together they define a region of memory that extends beyond the buffer object's allocated data store.");
+			glBufferSubData(GL_ARRAY_BUFFER, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glBufferSubData(GL_ARRAY_BUFFER, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glBufferSubData(GL_ARRAY_BUFFER, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glBufferSubData(GL_ARRAY_BUFFER, 15, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glBufferSubData(GL_ARRAY_BUFFER, 1, 15, 0);
+			expectError(GL_INVALID_VALUE);
+			glBufferSubData(GL_ARRAY_BUFFER, 8, 8, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &buffer);
+		});
+	ES3F_ADD_API_CASE(clear, "Invalid glClear() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if any bit other than the three defined bits is set in mask.");
+			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+			expectError(GL_NO_ERROR);
+			glClear(0x00000200);
+			expectError(GL_INVALID_VALUE);
+			glClear(0x00001000);
+			expectError(GL_INVALID_VALUE);
+			glClear(0x00000010);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(read_pixels, "Invalid glReadPixels() usage",
+		{
+			std::vector<GLubyte> ubyteData(4);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the combination of format and type is unsupported.");
+			glReadPixels(0, 0, 1, 1, GL_LUMINANCE_ALPHA, GL_UNSIGNED_SHORT_4_4_4_4, &ubyteData[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either width or height is negative.");
+			glReadPixels(0, 0, -1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+			expectError(GL_INVALID_VALUE);
+			glReadPixels(0, 0, 1, -1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+			expectError(GL_INVALID_VALUE);
+			glReadPixels(0, 0, -1, -1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			GLuint fbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteFramebuffers(1, &fbo);
+		});
+	ES3F_ADD_API_CASE(read_pixels_format_mismatch, "Invalid glReadPixels() usage",
+		{
+			std::vector<GLubyte> ubyteData(4);
+			std::vector<GLushort> ushortData(4);
+
+			m_log << TestLog::Section("", "Unsupported combinations of format and type will generate an INVALID_OPERATION error.");
+			glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_SHORT_5_6_5, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			glReadPixels(0, 0, 1, 1, GL_ALPHA, GL_UNSIGNED_SHORT_5_6_5, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			glReadPixels(0, 0, 1, 1, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			glReadPixels(0, 0, 1, 1, GL_ALPHA, GL_UNSIGNED_SHORT_4_4_4_4, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			glReadPixels(0, 0, 1, 1, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			glReadPixels(0, 0, 1, 1, GL_ALPHA, GL_UNSIGNED_SHORT_5_5_5_1, &ushortData[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_RGBA/GL_UNSIGNED_BYTE is always accepted and the other acceptable pair can be discovered by querying GL_IMPLEMENTATION_COLOR_READ_FORMAT and GL_IMPLEMENTATION_COLOR_READ_TYPE.");
+			glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+			expectError(GL_NO_ERROR);
+			GLint readFormat;
+			GLint readType;
+			glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
+			glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
+			glReadPixels(0, 0, 1, 1, readFormat, readType, &ubyteData[0]);
+			expectError(GL_NO_ERROR);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(read_pixels_fbo_format_mismatch, "Invalid glReadPixels() usage",
+		{
+			std::vector<GLubyte>	ubyteData(4);
+			std::vector<float>		floatData(4);
+			deUint32				fbo;
+			deUint32				texture;
+
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glGenFramebuffers		(1, &fbo);
+			glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+			glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if currently bound framebuffer format is incompatible with format and type.");
+
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			expectError				(GL_NO_ERROR);
+			glReadPixels			(0, 0, 1, 1, GL_RGBA, GL_FLOAT, &floatData[0]);
+			expectError				(GL_INVALID_OPERATION);
+
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32I, 32, 32, 0, GL_RGBA_INTEGER, GL_INT, NULL);
+			glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			expectError				(GL_NO_ERROR);
+			glReadPixels			(0, 0, 1, 1, GL_RGBA, GL_FLOAT, &floatData[0]);
+			expectError				(GL_INVALID_OPERATION);
+
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32UI, 32, 32, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT, NULL);
+			glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			expectError				(GL_NO_ERROR);
+			glReadPixels			(0, 0, 1, 1, GL_RGBA, GL_FLOAT, &floatData[0]);
+			expectError				(GL_INVALID_OPERATION);
+
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if GL_READ_FRAMEBUFFER_BINDING is non-zero, the read framebuffer is complete, and the value of GL_SAMPLE_BUFFERS for the read framebuffer is greater than zero.");
+
+			int			binding			= -1;
+			int			sampleBuffers;
+			deUint32	rbo;
+
+			glGenRenderbuffers(1, &rbo);
+			glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+			glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 32, 32);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+
+			glGetIntegerv			(GL_READ_FRAMEBUFFER_BINDING, &binding);
+			m_log << TestLog::Message << "// GL_READ_FRAMEBUFFER_BINDING: " << binding << TestLog::EndMessage;
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glGetIntegerv			(GL_SAMPLE_BUFFERS, &sampleBuffers);
+			m_log << TestLog::Message << "// GL_SAMPLE_BUFFERS: " << sampleBuffers << TestLog::EndMessage;
+			expectError				(GL_NO_ERROR);
+
+			if (binding == 0 || sampleBuffers <= 0)
+			{
+				m_log << TestLog::Message << "// ERROR: expected GL_READ_FRAMEBUFFER_BINDING to be non-zero and GL_SAMPLE_BUFFERS to be greater than zero" << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+			}
+			else
+			{
+				glReadPixels	(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+				expectError		(GL_INVALID_OPERATION);
+			}
+
+			m_log << TestLog::EndSection;
+
+			glBindRenderbuffer		(GL_RENDERBUFFER, 0);
+			glBindTexture			(GL_TEXTURE_2D, 0);
+			glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers	(1, &fbo);
+			glDeleteTextures		(1, &texture);
+			glDeleteRenderbuffers	(1, &rbo);
+		});
+	ES3F_ADD_API_CASE(bind_buffer_range, "Invalid glBindBufferRange() usage",
+		{
+			deUint32 bufU;
+			glGenBuffers(1, &bufU);
+			glBindBuffer(GL_UNIFORM_BUFFER, bufU);
+			glBufferData(GL_UNIFORM_BUFFER, 16, NULL, GL_STREAM_DRAW);
+
+			deUint32 bufTF;
+			glGenBuffers(1, &bufTF);
+			glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, bufTF);
+			glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_STREAM_DRAW);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_TRANSFORM_FEEDBACK_BUFFER or GL_UNIFORM_BUFFER.");
+			glBindBufferRange(GL_ARRAY_BUFFER, 0, bufU, 0, 4);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if target is GL_TRANSFORM_FEEDBACK_BUFFER and index is greater than or equal to GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.");
+			int maxTFSize;
+			glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxTFSize);
+			glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, maxTFSize, bufTF, 0, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if target is GL_UNIFORM_BUFFER and index is greater than or equal to GL_MAX_UNIFORM_BUFFER_BINDINGS.");
+			int maxUSize;
+			glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUSize);
+			glBindBufferRange(GL_UNIFORM_BUFFER, maxUSize, bufU, 0, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if size is less than or equal to zero.");
+			glBindBufferRange(GL_UNIFORM_BUFFER, 0, bufU, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glBindBufferRange(GL_UNIFORM_BUFFER, 0, bufU, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if target is GL_TRANSFORM_FEEDBACK_BUFFER and size or offset are not multiples of 4.");
+			glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufTF, 4, 5);
+			expectError(GL_INVALID_VALUE);
+			glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufTF, 5, 4);
+			expectError(GL_INVALID_VALUE);
+			glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufTF, 5, 7);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if target is GL_UNIFORM_BUFFER and offset is not a multiple of GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT.");
+			int alignment;
+			glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment);
+			glBindBufferRange(GL_UNIFORM_BUFFER, 0, bufU, alignment+1, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &bufU);
+			glDeleteBuffers(1, &bufTF);
+		});
+	ES3F_ADD_API_CASE(bind_buffer_base, "Invalid glBindBufferBase() usage",
+		{
+			deUint32 bufU;
+			glGenBuffers(1, &bufU);
+			glBindBuffer(GL_UNIFORM_BUFFER, bufU);
+			glBufferData(GL_UNIFORM_BUFFER, 16, NULL, GL_STREAM_DRAW);
+
+			deUint32 bufTF;
+			glGenBuffers(1, &bufTF);
+			glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, bufTF);
+			glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_STREAM_DRAW);
+			expectError(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_TRANSFORM_FEEDBACK_BUFFER or GL_UNIFORM_BUFFER.");
+			glBindBufferBase(-1, 0, bufU);
+			expectError(GL_INVALID_ENUM);
+			glBindBufferBase(GL_ARRAY_BUFFER, 0, bufU);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if target is GL_UNIFORM_BUFFER and index is greater than or equal to GL_MAX_UNIFORM_BUFFER_BINDINGS.");
+			int maxUSize;
+			glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUSize);
+			glBindBufferBase(GL_UNIFORM_BUFFER, maxUSize, bufU);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if target is GL_TRANSFORM_FEEDBACK_BUFFER andindex is greater than or equal to GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.");
+			int maxTFSize;
+			glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxTFSize);
+			glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, maxTFSize, bufTF);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &bufU);
+			glDeleteBuffers(1, &bufTF);
+		});
+	ES3F_ADD_API_CASE(clear_bufferiv, "Invalid glClearBufferiv() usage",
+		{
+			std::vector<int>		data(32*32);
+			deUint32				fbo;
+			deUint32				texture;
+
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32I, 32, 32, 0, GL_RGBA_INTEGER, GL_INT, NULL);
+			glGenFramebuffers		(1, &fbo);
+			glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+			glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if buffer is not an accepted value.");
+			glClearBufferiv(-1, 0, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			glClearBufferiv(GL_FRAMEBUFFER, 0, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if buffer is GL_COLOR, GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT, or GL_FRONT_AND_BACK and drawBuffer is greater than or equal to GL_MAX_DRAW_BUFFERS.");
+			int maxDrawBuffers;
+			glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
+			glClearBufferiv(GL_COLOR, maxDrawBuffers, &data[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if buffer is GL_DEPTH or GL_DEPTH_STENCIL.");
+			glClearBufferiv(GL_DEPTH, 1, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			glClearBufferiv(GL_DEPTH_STENCIL, 1, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if buffer is GL_STENCIL and drawBuffer is not zero.");
+			glClearBufferiv(GL_STENCIL, 1, &data[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteFramebuffers(1, &fbo);
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(clear_bufferuiv, "Invalid glClearBufferuiv() usage",
+		{
+			std::vector<deUint32>	data(32*32);
+			deUint32				fbo;
+			deUint32				texture;
+
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32UI, 32, 32, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT, NULL);
+			glGenFramebuffers		(1, &fbo);
+			glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+			glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if buffer is not an accepted value.");
+			glClearBufferuiv(-1, 0, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			glClearBufferuiv(GL_FRAMEBUFFER, 0, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if buffer is GL_COLOR, GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT, or GL_FRONT_AND_BACK and drawBuffer is greater than or equal to GL_MAX_DRAW_BUFFERS.");
+			int maxDrawBuffers;
+			glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
+			glClearBufferuiv(GL_COLOR, maxDrawBuffers, &data[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if buffer is GL_DEPTH, GL_STENCIL or GL_DEPTH_STENCIL.");
+			glClearBufferuiv(GL_DEPTH, 1, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			glClearBufferuiv(GL_STENCIL, 1, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			glClearBufferuiv(GL_DEPTH_STENCIL, 1, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteFramebuffers(1, &fbo);
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(clear_bufferfv, "Invalid glClearBufferfv() usage",
+		{
+			std::vector<float>		data(32*32);
+			deUint32				fbo;
+			deUint32				texture;
+
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32F, 32, 32, 0, GL_RGBA, GL_FLOAT, NULL);
+			glGenFramebuffers		(1, &fbo);
+			glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+			glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if buffer is not an accepted value.");
+			glClearBufferfv(-1, 0, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			glClearBufferfv(GL_FRAMEBUFFER, 0, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if buffer is GL_COLOR, GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT, or GL_FRONT_AND_BACK and drawBuffer is greater than or equal to GL_MAX_DRAW_BUFFERS.");
+			int maxDrawBuffers;
+			glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
+			glClearBufferfv(GL_COLOR, maxDrawBuffers, &data[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if buffer is GL_STENCIL or GL_DEPTH_STENCIL.");
+			glClearBufferfv(GL_STENCIL, 1, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			glClearBufferfv(GL_DEPTH_STENCIL, 1, &data[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if buffer is GL_DEPTH and drawBuffer is not zero.");
+			glClearBufferfv(GL_DEPTH, 1, &data[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteFramebuffers(1, &fbo);
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(clear_bufferfi, "Invalid glClearBufferfi() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if buffer is not an accepted value.");
+			glClearBufferfi(-1, 0, 1.0f, 1);
+			expectError(GL_INVALID_ENUM);
+			glClearBufferfi(GL_FRAMEBUFFER, 0, 1.0f, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if buffer is not GL_DEPTH_STENCIL.");
+			glClearBufferfi(GL_DEPTH, 0, 1.0f, 1);
+			expectError(GL_INVALID_ENUM);
+			glClearBufferfi(GL_STENCIL, 0, 1.0f, 1);
+			expectError(GL_INVALID_ENUM);
+			glClearBufferfi(GL_COLOR, 0, 1.0f, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if buffer is GL_DEPTH_STENCIL and drawBuffer is not zero.");
+			glClearBufferfi(GL_DEPTH_STENCIL, 1, 1.0f, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(copy_buffer_sub_data, "Invalid glCopyBufferSubData() usage",
+		{
+			deUint32				buf[2];
+			std::vector<float>		data(32*32);
+
+			glGenBuffers			(2, buf);
+			glBindBuffer			(GL_COPY_READ_BUFFER, buf[0]);
+			glBufferData			(GL_COPY_READ_BUFFER, 32, &data[0], GL_DYNAMIC_COPY);
+			glBindBuffer			(GL_COPY_WRITE_BUFFER, buf[1]);
+			glBufferData			(GL_COPY_WRITE_BUFFER, 32, &data[0], GL_DYNAMIC_COPY);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if any of readoffset, writeoffset or size is negative.");
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, -4);
+			expectError				(GL_INVALID_VALUE);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, -1, 0, 4);
+			expectError				(GL_INVALID_VALUE);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, -1, 4);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if readoffset + size exceeds the size of the buffer object bound to readtarget.");
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 36);
+			expectError				(GL_INVALID_VALUE);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 24, 0, 16);
+			expectError				(GL_INVALID_VALUE);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 36, 0, 4);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if writeoffset + size exceeds the size of the buffer object bound to writetarget.");
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 36);
+			expectError				(GL_INVALID_VALUE);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 24, 16);
+			expectError				(GL_INVALID_VALUE);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 36, 4);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if the same buffer object is bound to both readtarget and writetarget and the ranges [readoffset, readoffset + size) and [writeoffset, writeoffset + size) overlap.");
+			glBindBuffer			(GL_COPY_WRITE_BUFFER, buf[0]);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 16, 4);
+			expectError				(GL_NO_ERROR);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 4);
+			expectError				(GL_INVALID_VALUE);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 16, 18);
+			expectError				(GL_INVALID_VALUE);
+			glBindBuffer			(GL_COPY_WRITE_BUFFER, buf[1]);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if zero is bound to readtarget or writetarget.");
+			glBindBuffer			(GL_COPY_READ_BUFFER, 0);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 16);
+			expectError				(GL_INVALID_OPERATION);
+
+			glBindBuffer			(GL_COPY_READ_BUFFER, buf[0]);
+			glBindBuffer			(GL_COPY_WRITE_BUFFER, 0);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 16);
+			expectError				(GL_INVALID_OPERATION);
+
+			glBindBuffer			(GL_COPY_WRITE_BUFFER, buf[1]);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the buffer object bound to either readtarget or writetarget is mapped.");
+			glMapBufferRange		(GL_COPY_READ_BUFFER, 0, 4, GL_MAP_READ_BIT);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 16);
+			expectError				(GL_INVALID_OPERATION);
+			glUnmapBuffer			(GL_COPY_READ_BUFFER);
+
+			glMapBufferRange		(GL_COPY_WRITE_BUFFER, 0, 4, GL_MAP_READ_BIT);
+			glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 16);
+			expectError				(GL_INVALID_OPERATION);
+			glUnmapBuffer			(GL_COPY_WRITE_BUFFER);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(2, buf);
+		});
+	ES3F_ADD_API_CASE(draw_buffers, "Invalid glDrawBuffers() usage",
+		{
+			deUint32				fbo;
+			deUint32				texture;
+			int						maxDrawBuffers;
+			glGetIntegerv			(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
+			std::vector<deUint32>	values(maxDrawBuffers+1);
+			values[0]				= GL_NONE;
+			values[1]				= GL_BACK;
+			values[2]				= GL_COLOR_ATTACHMENT0;
+			values[3]				= GL_DEPTH_ATTACHMENT;
+			std::vector<GLfloat>	data(32*32);
+
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glGenFramebuffers		(1, &fbo);
+			glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+			glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if one of the values in bufs is not an accepted value.");
+			glDrawBuffers			(2, &values[2]);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the GL is bound to the default framebuffer and n is not 1.");
+			glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+			glDrawBuffers			(2, &values[0]);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the GL is bound to the default framebuffer and the value in bufs is one of the GL_COLOR_ATTACHMENTn tokens.");
+			glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+			glDrawBuffers			(1, &values[2]);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the GL is bound to a framebuffer object and the ith buffer listed in bufs is anything other than GL_NONE or GL_COLOR_ATTACHMENTSi.");
+			glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+			glDrawBuffers			(1, &values[1]);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is less than 0 or greater than GL_MAX_DRAW_BUFFERS.");
+			glDrawBuffers			(-1, &values[1]);
+			expectError				(GL_INVALID_VALUE);
+			glDrawBuffers			(maxDrawBuffers+1, &values[0]);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+			glDeleteFramebuffers(1, &fbo);
+		});
+	ES3F_ADD_API_CASE(flush_mapped_buffer_range, "Invalid glFlushMappedBufferRange() usage",
+		{
+			deUint32				buf;
+			std::vector<GLfloat>	data(32);
+
+			glGenBuffers			(1, &buf);
+			glBindBuffer			(GL_ARRAY_BUFFER, buf);
+			glBufferData			(GL_ARRAY_BUFFER, 32, &data[0], GL_STATIC_READ);
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if offset or length is negative, or if offset + length exceeds the size of the mapping.");
+			glFlushMappedBufferRange(GL_ARRAY_BUFFER, -1, 1);
+			expectError				(GL_INVALID_VALUE);
+			glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, -1);
+			expectError				(GL_INVALID_VALUE);
+			glFlushMappedBufferRange(GL_ARRAY_BUFFER, 12, 8);
+			expectError				(GL_INVALID_VALUE);
+			glFlushMappedBufferRange(GL_ARRAY_BUFFER, 24, 4);
+			expectError				(GL_INVALID_VALUE);
+			glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, 24);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if zero is bound to target.");
+			glBindBuffer			(GL_ARRAY_BUFFER, 0);
+			glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, 8);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the buffer bound to target is not mapped, or is mapped without the GL_MAP_FLUSH_EXPLICIT flag.");
+			glBindBuffer			(GL_ARRAY_BUFFER, buf);
+			glUnmapBuffer			(GL_ARRAY_BUFFER);
+			glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, 8);
+			expectError				(GL_INVALID_OPERATION);
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_WRITE_BIT);
+			glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, 8);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glUnmapBuffer			(GL_ARRAY_BUFFER);
+			glDeleteBuffers			(1, &buf);
+		});
+	ES3F_ADD_API_CASE(map_buffer_range, "Invalid glMapBufferRange() usage",
+		{
+			deUint32				buf;
+			std::vector<GLfloat>	data(32);
+
+			glGenBuffers			(1, &buf);
+			glBindBuffer			(GL_ARRAY_BUFFER, buf);
+			glBufferData			(GL_ARRAY_BUFFER, 32, &data[0], GL_DYNAMIC_COPY);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either of offset or length is negative.");
+			glMapBufferRange		(GL_ARRAY_BUFFER, -1, 1, GL_MAP_READ_BIT);
+			expectError				(GL_INVALID_VALUE);
+
+			glMapBufferRange		(GL_ARRAY_BUFFER, 1, -1, GL_MAP_READ_BIT);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if offset + length is greater than the value of GL_BUFFER_SIZE.");
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 33, GL_MAP_READ_BIT);
+			expectError				(GL_INVALID_VALUE);
+
+			glMapBufferRange		(GL_ARRAY_BUFFER, 32, 1, GL_MAP_READ_BIT);
+			expectError				(GL_INVALID_VALUE);
+
+			glMapBufferRange		(GL_ARRAY_BUFFER, 16, 17, GL_MAP_READ_BIT);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if access has any bits set other than those accepted.");
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_READ_BIT | 0x1000);
+			expectError				(GL_INVALID_VALUE);
+
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_WRITE_BIT | 0x1000);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the buffer is already in a mapped state.");
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_WRITE_BIT);
+			expectError				(GL_NO_ERROR);
+			glMapBufferRange		(GL_ARRAY_BUFFER, 16, 8, GL_MAP_READ_BIT);
+			expectError				(GL_INVALID_OPERATION);
+			glUnmapBuffer			(GL_ARRAY_BUFFER);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if neither GL_MAP_READ_BIT or GL_MAP_WRITE_BIT is set.");
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_INVALIDATE_RANGE_BIT);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if ");
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_INVALIDATE_RANGE_BIT);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if GL_MAP_READ_BIT is set and any of GL_MAP_INVALIDATE_RANGE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT, or GL_MAP_UNSYNCHRONIZED_BIT is set.");
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_READ_BIT | GL_MAP_INVALIDATE_RANGE_BIT);
+			expectError				(GL_INVALID_OPERATION);
+
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_READ_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
+			expectError				(GL_INVALID_OPERATION);
+
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_READ_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if GL_MAP_FLUSH_EXPLICIT_BIT is set and GL_MAP_WRITE_BIT is not set.");
+			glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_READ_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers			(1, &buf);
+		});
+	ES3F_ADD_API_CASE(read_buffer, "Invalid glReadBuffer() usage",
+		{
+			deUint32				fbo;
+			deUint32				texture;
+			int						maxColorAttachments;
+
+			glGetIntegerv			(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glGenFramebuffers		(1, &fbo);
+			glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+			glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not GL_BACK, GL_NONE, or GL_COLOR_ATTACHMENTi, where i is less than GL_MAX_COLOR_ATTACHMENTS.");
+			glReadBuffer			(-1);
+			expectError				(GL_INVALID_ENUM);
+			glReadBuffer			(GL_FRAMEBUFFER);
+			expectError				(GL_INVALID_ENUM);
+			glReadBuffer			(GL_COLOR_ATTACHMENT0 + maxColorAttachments);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the current framebuffer is the default framebuffer and mode is not GL_NONE or GL_BACK.");
+			glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+			glReadBuffer			(GL_COLOR_ATTACHMENT0);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the current framebuffer is a named framebuffer and mode is not GL_NONE or GL_COLOR_ATTACHMENTi.");
+			glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+			glReadBuffer			(GL_BACK);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+			glDeleteFramebuffers(1, &fbo);
+		});
+	ES3F_ADD_API_CASE(unmap_buffer, "Invalid glUnmapBuffer() usage",
+		{
+			deUint32			buf;
+			std::vector<GLfloat>	data(32);
+
+			glGenBuffers			(1, &buf);
+			glBindBuffer			(GL_ARRAY_BUFFER, buf);
+			glBufferData			(GL_ARRAY_BUFFER, 32, &data[0], GL_DYNAMIC_COPY);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the buffer data store is already in an unmapped state.");
+			glUnmapBuffer			(GL_ARRAY_BUFFER);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers			(1, &buf);
+		});
+	// Framebuffer Objects
+
+	ES3F_ADD_API_CASE(bind_framebuffer, "Invalid glBindFramebuffer() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER.");
+			glBindFramebuffer(-1, 0);
+			expectError(GL_INVALID_ENUM);
+			glBindFramebuffer(GL_RENDERBUFFER, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(bind_renderbuffer, "Invalid glBindRenderbuffer() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+			glBindRenderbuffer(-1, 0);
+			expectError(GL_INVALID_ENUM);
+			glBindRenderbuffer(GL_FRAMEBUFFER, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(check_framebuffer_status, "Invalid glCheckFramebufferStatus() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER.");
+			glCheckFramebufferStatus(-1);
+			expectError(GL_INVALID_ENUM);
+			glCheckFramebufferStatus(GL_RENDERBUFFER);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(gen_framebuffers, "Invalid glGenFramebuffers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glGenFramebuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(gen_renderbuffers, "Invalid glGenRenderbuffers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glGenRenderbuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(delete_framebuffers, "Invalid glDeleteFramebuffers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteFramebuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(delete_renderbuffers, "Invalid glDeleteRenderbuffers() usage",
+		{;
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteRenderbuffers(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(framebuffer_renderbuffer, "Invalid glFramebufferRenderbuffer() usage",
+		{
+			GLuint fbo;
+			GLuint rbo;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glGenRenderbuffers(1, &rbo);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+			glFramebufferRenderbuffer(-1, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if renderbuffertarget is not GL_RENDERBUFFER.");
+			glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, -1, rbo);
+			expectError(GL_INVALID_ENUM);
+			glBindRenderbuffer(GL_RENDERBUFFER, 0);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if renderbuffer is neither 0 nor the name of an existing renderbuffer object.");
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, -1);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if zero is bound to target.");
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteRenderbuffers(1, &rbo);
+			glDeleteFramebuffers(1, &fbo);
+		});
+	ES3F_ADD_API_CASE(framebuffer_texture2d, "Invalid glFramebufferTexture2D() usage",
+		{
+			GLuint fbo;
+			GLuint tex2D;
+			GLuint texCube;
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glGenTextures(1, &tex2D);
+			glBindTexture(GL_TEXTURE_2D, tex2D);
+			glGenTextures(1, &texCube);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+			expectError(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+			glFramebufferTexture2D(-1, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if textarget is not an accepted texture target.");
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, -1, tex2D, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0 or larger than log_2 of maximum texture size.");
+			int maxSize;
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, -1);
+			expectError(GL_INVALID_VALUE);
+			maxSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, maxSize);
+			expectError(GL_INVALID_VALUE);
+			maxSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, maxSize);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if texture is neither 0 nor the name of an existing texture object.");
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, -1, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if textarget and texture are not compatible.");
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, tex2D, 0);
+			expectError(GL_INVALID_OPERATION);
+			glDeleteTextures(1, &tex2D);
+
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texCube, 0);
+			expectError(GL_INVALID_OPERATION);
+			glDeleteTextures(1, &texCube);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if zero is bound to target.");
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteFramebuffers(1, &fbo);
+		});
+	ES3F_ADD_API_CASE(renderbuffer_storage, "Invalid glRenderbufferStorage() usage",
+		{
+			deUint32				rbo;
+			glGenRenderbuffers		(1, &rbo);
+			glBindRenderbuffer		(GL_RENDERBUFFER, rbo);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+			glRenderbufferStorage	(-1, GL_RGBA4, 1, 1);
+			expectError				(GL_INVALID_ENUM);
+			glRenderbufferStorage	(GL_FRAMEBUFFER, GL_RGBA4, 1, 1);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if internalformat is not a color-renderable, depth-renderable, or stencil-renderable format.");
+			glRenderbufferStorage	(GL_RENDERBUFFER, -1, 1, 1);
+			expectError				(GL_INVALID_ENUM);
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGB16F, 1, 1);
+			expectError				(GL_INVALID_ENUM);
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA8_SNORM, 1, 1);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than zero.");
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, -1, 1);
+			expectError				(GL_INVALID_VALUE);
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, 1, -1);
+			expectError				(GL_INVALID_VALUE);
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, -1, -1);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_RENDERBUFFER_SIZE.");
+			GLint maxSize;
+			glGetIntegerv			(GL_MAX_RENDERBUFFER_SIZE, &maxSize);
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, 1, maxSize+1);
+			expectError				(GL_INVALID_VALUE);
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, maxSize+1, 1);
+			expectError				(GL_INVALID_VALUE);
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, maxSize+1, maxSize+1);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteRenderbuffers(1, &rbo);
+		});
+	ES3F_ADD_API_CASE(blit_framebuffer, "Invalid glBlitFramebuffer() usage",
+		{
+			deUint32				fbo[2];
+			deUint32				rbo[2];
+			deUint32				texture[2];
+
+			glGenFramebuffers		(2, fbo);
+			glGenTextures			(2, texture);
+			glGenRenderbuffers		(2, rbo);
+
+			glBindTexture			(GL_TEXTURE_2D, texture[0]);
+			glBindRenderbuffer		(GL_RENDERBUFFER, rbo[0]);
+			glBindFramebuffer		(GL_READ_FRAMEBUFFER, fbo[0]);
+
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 32, 32);
+			glFramebufferTexture2D	(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
+			glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo[0]);
+			glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
+
+			glBindTexture			(GL_TEXTURE_2D, texture[1]);
+			glBindRenderbuffer		(GL_RENDERBUFFER, rbo[1]);
+			glBindFramebuffer		(GL_DRAW_FRAMEBUFFER, fbo[1]);
+
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 32, 32);
+			glFramebufferTexture2D	(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
+			glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo[1]);
+			glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST.");
+			glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_LINEAR);
+			expectError				(GL_INVALID_OPERATION);
+			glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_LINEAR);
+			expectError				(GL_INVALID_OPERATION);
+			glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_LINEAR);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if mask contains GL_COLOR_BUFFER_BIT and read buffer format is incompatible with draw buffer format.");
+			glBindTexture			(GL_TEXTURE_2D, texture[0]);
+
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32UI, 32, 32, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT, NULL);
+			glFramebufferTexture2D	(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
+			m_log << TestLog::Message << "// Read buffer: GL_RGBA32UI, draw buffer: GL_RGBA" << TestLog::EndMessage;
+			glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+			expectError				(GL_INVALID_OPERATION);
+
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32I, 32, 32, 0, GL_RGBA_INTEGER, GL_INT, NULL);
+			glFramebufferTexture2D	(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
+			m_log << TestLog::Message << "// Read buffer: GL_RGBA32I, draw buffer: GL_RGBA" << TestLog::EndMessage;
+			glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+			expectError				(GL_INVALID_OPERATION);
+
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glFramebufferTexture2D	(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
+			glBindTexture			(GL_TEXTURE_2D, texture[1]);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32I, 32, 32, 0, GL_RGBA_INTEGER, GL_INT, NULL);
+			glFramebufferTexture2D	(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
+			m_log << TestLog::Message << "// Read buffer: GL_RGBA8, draw buffer: GL_RGBA32I" << TestLog::EndMessage;
+			glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if filter is GL_LINEAR and the read buffer contains integer data.");
+			glBindTexture			(GL_TEXTURE_2D, texture[0]);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32UI, 32, 32, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT, NULL);
+			glFramebufferTexture2D	(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glFramebufferTexture2D	(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
+			m_log << TestLog::Message << "// Read buffer: GL_RGBA32I, draw buffer: GL_RGBA8" << TestLog::EndMessage;
+			glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if mask contains GL_DEPTH_BUFFER_BIT or GL_STENCIL_BUFFER_BIT and the source and destination depth and stencil formats do not match.");
+			glBindRenderbuffer		(GL_RENDERBUFFER, rbo[0]);
+			glRenderbufferStorage	(GL_RENDERBUFFER, GL_DEPTH32F_STENCIL8, 32, 32);
+			glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo[0]);
+			glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+			expectError				(GL_INVALID_OPERATION);
+			glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_STENCIL_BUFFER_BIT, GL_NEAREST);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers	(2, fbo);
+			glDeleteTextures		(2, texture);
+			glDeleteRenderbuffers	(2, rbo);
+		});
+	ES3F_ADD_API_CASE(blit_framebuffer_multisample, "Invalid glBlitFramebuffer() usage",
+		{
+			deUint32						fbo[2];
+			deUint32						rbo[2];
+
+			glGenFramebuffers				(2, fbo);
+			glGenRenderbuffers				(2, rbo);
+
+			glBindRenderbuffer				(GL_RENDERBUFFER, rbo[0]);
+			glBindFramebuffer				(GL_READ_FRAMEBUFFER, fbo[0]);
+			glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 32, 32);
+			glFramebufferRenderbuffer		(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo[0]);
+			glCheckFramebufferStatus		(GL_READ_FRAMEBUFFER);
+
+			glBindRenderbuffer				(GL_RENDERBUFFER, rbo[1]);
+			glBindFramebuffer				(GL_DRAW_FRAMEBUFFER, fbo[1]);
+
+			expectError						(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS for the draw buffer is greater than zero.");
+			glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 32, 32);
+			glFramebufferRenderbuffer		(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo[1]);
+			glBlitFramebuffer				(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+			expectError						(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater than zero and the formats of draw and read buffers are not identical.");
+			glRenderbufferStorage			(GL_RENDERBUFFER, GL_RGBA4, 32, 32);
+			glFramebufferRenderbuffer		(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo[1]);
+			glBlitFramebuffer				(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+			expectError						(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater than zero and the source and destination rectangles are not defined with the same (X0, Y0) and (X1, Y1) bounds.");
+			glRenderbufferStorage			(GL_RENDERBUFFER, GL_RGBA8, 32, 32);
+			glFramebufferRenderbuffer		(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo[1]);
+			glBlitFramebuffer				(0, 0, 16, 16, 2, 2, 18, 18, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+			expectError						(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+			glDeleteRenderbuffers	(2, rbo);
+			glDeleteFramebuffers	(2, fbo);
+		});
+	ES3F_ADD_API_CASE(framebuffer_texture_layer, "Invalid glFramebufferTextureLayer() usage",
+		{
+			deUint32					fbo;
+			deUint32					tex3D;
+			deUint32					tex2DArray;
+			deUint32					tex2D;
+
+			glGenFramebuffers			(1, &fbo);
+			glGenTextures				(1, &tex3D);
+			glGenTextures				(1, &tex2DArray);
+			glGenTextures				(1, &tex2D);
+			glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+
+			glBindTexture				(GL_TEXTURE_3D, tex3D);
+			glTexImage3D				(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glBindTexture				(GL_TEXTURE_2D_ARRAY, tex2DArray);
+			glTexImage3D				(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glBindTexture				(GL_TEXTURE_2D, tex2D);
+			glTexImage2D				(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+			expectError					(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+			glFramebufferTextureLayer	(-1, GL_COLOR_ATTACHMENT0, tex3D, 0, 1);
+			expectError					(GL_INVALID_ENUM);
+			glFramebufferTextureLayer	(GL_RENDERBUFFER, GL_COLOR_ATTACHMENT0, tex3D, 0, 1);
+			expectError					(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if attachment is not one of the accepted tokens.");
+			glFramebufferTextureLayer	(GL_FRAMEBUFFER, -1, tex3D, 0, 1);
+			expectError					(GL_INVALID_ENUM);
+			glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_BACK, tex3D, 0, 1);
+			expectError					(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if texture is non-zero and not the name of a 3D texture or 2D array texture.");
+			glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, -1, 0, 0);
+			expectError					(GL_INVALID_OPERATION);
+			glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 0);
+			expectError					(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if texture is not zero and layer is negative.");
+			glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3D, 0, -1);
+			expectError					(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if texture is not zero and layer is greater than GL_MAX_3D_TEXTURE_SIZE-1 for a 3D texture.");
+			int							max3DTexSize;
+			glGetIntegerv				(GL_MAX_3D_TEXTURE_SIZE, &max3DTexSize);
+			glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3D, 0, max3DTexSize);
+			expectError					(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if texture is not zero and layer is greater than GL_MAX_ARRAY_TEXTURE_LAYERS-1 for a 2D array texture.");
+			int							maxArrayTexLayers;
+			glGetIntegerv				(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxArrayTexLayers);
+			glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, maxArrayTexLayers);
+			expectError					(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if zero is bound to target.");
+			glBindFramebuffer			(GL_FRAMEBUFFER, 0);
+			glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3D, 0, 1);
+			expectError					(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &tex3D);
+			glDeleteTextures		(1, &tex2DArray);
+			glDeleteTextures		(1, &tex2D);
+			glDeleteFramebuffers	(1, &fbo);
+		});
+	ES3F_ADD_API_CASE(invalidate_framebuffer, "Invalid glInvalidateFramebuffer() usage",
+		{
+			deUint32					fbo;
+			deUint32					texture;
+			deUint32					attachments[2];
+			int							maxColorAttachments;
+			glGetIntegerv				(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
+			attachments[0]				= GL_COLOR_ATTACHMENT0;
+			attachments[1]				= GL_COLOR_ATTACHMENT0 + maxColorAttachments;
+
+			glGenFramebuffers			(1, &fbo);
+			glGenTextures				(1, &texture);
+			glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+			glBindTexture				(GL_TEXTURE_2D, texture);
+			glTexImage2D				(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glFramebufferTexture2D		(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			glCheckFramebufferStatus	(GL_FRAMEBUFFER);
+			expectError					(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER, GL_READ_FRAMEBUFFER or GL_DRAW_FRAMEBUFFER.");
+			glInvalidateFramebuffer		(-1, 1, &attachments[0]);
+			expectError					(GL_INVALID_ENUM);
+			glInvalidateFramebuffer		(GL_BACK, 1, &attachments[0]);
+			expectError					(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if attachments contains GL_COLOR_ATTACHMENTm and m is greater than or equal to the value of GL_MAX_COLOR_ATTACHMENTS.");
+			glInvalidateFramebuffer		(GL_FRAMEBUFFER, 1, &attachments[1]);
+			expectError					(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+			glDeleteFramebuffers	(1, &fbo);
+		});
+	ES3F_ADD_API_CASE(invalidate_sub_framebuffer, "Invalid glInvalidateSubFramebuffer() usage",
+		{
+			deUint32					fbo;
+			deUint32					texture;
+			deUint32					attachments[2];
+			int							maxColorAttachments;
+			glGetIntegerv				(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
+			attachments[0]				= GL_COLOR_ATTACHMENT0;
+			attachments[1]				= GL_COLOR_ATTACHMENT0 + maxColorAttachments;
+
+			glGenFramebuffers			(1, &fbo);
+			glGenTextures				(1, &texture);
+			glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+			glBindTexture				(GL_TEXTURE_2D, texture);
+			glTexImage2D				(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glFramebufferTexture2D		(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+			glCheckFramebufferStatus	(GL_FRAMEBUFFER);
+			expectError					(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER, GL_READ_FRAMEBUFFER or GL_DRAW_FRAMEBUFFER.");
+			glInvalidateSubFramebuffer	(-1, 1, &attachments[0], 0, 0, 16, 16);
+			expectError					(GL_INVALID_ENUM);
+			glInvalidateSubFramebuffer	(GL_BACK, 1, &attachments[0], 0, 0, 16, 16);
+			expectError					(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if attachments contains GL_COLOR_ATTACHMENTm and m is greater than or equal to the value of GL_MAX_COLOR_ATTACHMENTS.");
+			glInvalidateSubFramebuffer	(GL_FRAMEBUFFER, 1, &attachments[1], 0, 0, 16, 16);
+			expectError					(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+			glDeleteFramebuffers	(1, &fbo);
+		});
+	ES3F_ADD_API_CASE(renderbuffer_storage_multisample, "Invalid glRenderbufferStorageMultisample() usage",
+		{
+			deUint32							rbo;
+			int									maxSamplesSupportedRGBA4 = -1;
+
+			glGetInternalformativ				(GL_RENDERBUFFER, GL_RGBA4, GL_SAMPLES, 1, &maxSamplesSupportedRGBA4);
+			glGenRenderbuffers					(1, &rbo);
+			glBindRenderbuffer					(GL_RENDERBUFFER, rbo);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+			glRenderbufferStorageMultisample	(-1, 2, GL_RGBA4, 1, 1);
+			expectError							(GL_INVALID_ENUM);
+			glRenderbufferStorageMultisample	(GL_FRAMEBUFFER, 2, GL_RGBA4, 1, 1);
+			expectError							(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if samples is greater than the maximum number of samples supported for internalformat.");
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, maxSamplesSupportedRGBA4+1, GL_RGBA4, 1, 1);
+			expectError							(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if internalformat is not a color-renderable, depth-renderable, or stencil-renderable format.");
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, -1, 1, 1);
+			expectError							(GL_INVALID_ENUM);
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, GL_RGB16F, 1, 1);
+			expectError							(GL_INVALID_ENUM);
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, GL_RGBA8_SNORM, 1, 1);
+			expectError							(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if internalformat is a signed or unsigned integer format and samples is greater than 0.");
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 1, GL_RGBA8UI, 1, 1);
+			expectError							(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than zero.");
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, GL_RGBA4, -1, 1);
+			expectError							(GL_INVALID_VALUE);
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, GL_RGBA4, 1, -1);
+			expectError							(GL_INVALID_VALUE);
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, GL_RGBA4, -1, -1);
+			expectError							(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_RENDERBUFFER_SIZE.");
+			GLint maxSize;
+			glGetIntegerv						(GL_MAX_RENDERBUFFER_SIZE, &maxSize);
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 4, GL_RGBA4, 1, maxSize+1);
+			expectError							(GL_INVALID_VALUE);
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 4, GL_RGBA4, maxSize+1, 1);
+			expectError							(GL_INVALID_VALUE);
+			glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 4, GL_RGBA4, maxSize+1, maxSize+1);
+			expectError							(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteRenderbuffers(1, &rbo);
+		});
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fNegativeBufferApiTests.hpp b/modules/gles3/functional/es3fNegativeBufferApiTests.hpp
new file mode 100644
index 0000000..2b0bc02
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeBufferApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FNEGATIVEBUFFERAPITESTS_HPP
+#define _ES3FNEGATIVEBUFFERAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Buffer API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class NegativeBufferApiTests : public TestCaseGroup
+{
+public:
+									NegativeBufferApiTests		(Context& context);
+									~NegativeBufferApiTests		(void);
+
+	void							init						(void);
+
+private:
+									NegativeBufferApiTests		(const NegativeBufferApiTests& other);
+	NegativeBufferApiTests&			operator=					(const NegativeBufferApiTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FNEGATIVEBUFFERAPITESTS_HPP
diff --git a/modules/gles3/functional/es3fNegativeFragmentApiTests.cpp b/modules/gles3/functional/es3fNegativeFragmentApiTests.cpp
new file mode 100644
index 0000000..2e1fd6b
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeFragmentApiTests.cpp
@@ -0,0 +1,377 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Fragment Pipe API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fNegativeFragmentApiTests.hpp"
+#include "es3fApiCase.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+
+NegativeFragmentApiTests::NegativeFragmentApiTests (Context& context)
+	: TestCaseGroup(context, "fragment", "Negative Fragment API Cases")
+{
+}
+
+NegativeFragmentApiTests::~NegativeFragmentApiTests (void)
+{
+}
+
+void NegativeFragmentApiTests::init (void)
+{
+	ES3F_ADD_API_CASE(scissor, "Invalid glScissor() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either width or height is negative.");
+			glScissor(0, 0, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glScissor(0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glScissor(0, 0, -1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(depth_func, "Invalid glDepthFunc() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if func is not an accepted value.");
+			glDepthFunc(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(viewport, "Invalid glViewport() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either width or height is negative.");
+			glViewport(0, 0, -1, 1);
+			expectError(GL_INVALID_VALUE);
+			glViewport(0, 0, 1, -1);
+			expectError(GL_INVALID_VALUE);
+			glViewport(0, 0, -1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+
+	// Stencil functions
+
+	ES3F_ADD_API_CASE(stencil_func, "Invalid glStencilFunc() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if func is not one of the eight accepted values.");
+			glStencilFunc(-1, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(stencil_func_separate, "Invalid glStencilFuncSeparate() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if face is not GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.");
+			glStencilFuncSeparate(-1, GL_NEVER, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if func is not one of the eight accepted values.");
+			glStencilFuncSeparate(GL_FRONT, -1, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(stencil_op, "Invalid glStencilOp() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if sfail, dpfail, or dppass is any value other than the defined symbolic constant values.");
+			glStencilOp(-1, GL_ZERO, GL_REPLACE);
+			expectError(GL_INVALID_ENUM);
+			glStencilOp(GL_KEEP, -1, GL_REPLACE);
+			expectError(GL_INVALID_ENUM);
+			glStencilOp(GL_KEEP, GL_ZERO, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(stencil_op_separate, "Invalid glStencilOpSeparate() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if face is any value other than GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.");
+			glStencilOpSeparate(-1, GL_KEEP, GL_ZERO, GL_REPLACE);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if sfail, dpfail, or dppass is any value other than the eight defined symbolic constant values.");
+			glStencilOpSeparate(GL_FRONT, -1, GL_ZERO, GL_REPLACE);
+			expectError(GL_INVALID_ENUM);
+			glStencilOpSeparate(GL_FRONT, GL_KEEP, -1, GL_REPLACE);
+			expectError(GL_INVALID_ENUM);
+			glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_ZERO, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(stencil_mask_separate, "Invalid glStencilMaskSeparate() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if face is not GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.");
+			glStencilMaskSeparate(-1, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Blend functions
+
+	ES3F_ADD_API_CASE(blend_equation, "Invalid glBlendEquation() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MAX or GL_MIN.");
+			glBlendEquation(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(blend_equation_separate, "Invalid glBlendEquationSeparate() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if modeRGB is not GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MAX or GL_MIN.");
+			glBlendEquationSeparate(-1, GL_FUNC_ADD);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if modeAlpha is not GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MAX or GL_MIN.");
+			glBlendEquationSeparate(GL_FUNC_ADD, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(blend_func, "Invalid glBlendFunc() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if either sfactor or dfactor is not an accepted value.");
+			glBlendFunc(-1, GL_ONE);
+			expectError(GL_INVALID_ENUM);
+			glBlendFunc(GL_ONE, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(blend_func_separate, "Invalid glBlendFuncSeparate() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if srcRGB, dstRGB, srcAlpha, or dstAlpha is not an accepted value.");
+			glBlendFuncSeparate(-1, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
+			expectError(GL_INVALID_ENUM);
+			glBlendFuncSeparate(GL_ZERO, -1, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
+			expectError(GL_INVALID_ENUM);
+			glBlendFuncSeparate(GL_ZERO, GL_ONE, -1, GL_ONE_MINUS_SRC_COLOR);
+			expectError(GL_INVALID_ENUM);
+			glBlendFuncSeparate(GL_ZERO, GL_ONE, GL_SRC_COLOR, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Rasterization API functions
+
+	ES3F_ADD_API_CASE(cull_face, "Invalid glCullFace() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glCullFace(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(front_face, "Invalid glFrontFace() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glFrontFace(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(line_width, "Invalid glLineWidth() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width is less than or equal to 0.");
+			glLineWidth(0);
+			expectError(GL_INVALID_VALUE);
+			glLineWidth(-1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+
+	// Asynchronous queries
+
+	ES3F_ADD_API_CASE(gen_queries, "Invalid glGenQueries() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			GLuint ids;
+			glGenQueries	(-1, &ids);
+			expectError		(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(begin_query, "Invalid glBeginQuery() usage",
+		{
+			GLuint ids[3];
+			glGenQueries	(3, ids);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+			glBeginQuery	(-1, ids[0]);
+			expectError		(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if glBeginQuery is executed while a query object of the same target is already active.");
+			glBeginQuery	(GL_ANY_SAMPLES_PASSED, ids[0]);
+			expectError		(GL_NO_ERROR);
+			glBeginQuery	(GL_ANY_SAMPLES_PASSED, ids[1]);
+			expectError		(GL_INVALID_OPERATION);
+			// \note GL_ANY_SAMPLES_PASSED and GL_ANY_SAMPLES_PASSED_CONSERVATIVE alias to the same target for the purposes of this error.
+			glBeginQuery	(GL_ANY_SAMPLES_PASSED_CONSERVATIVE, ids[1]);
+			expectError		(GL_INVALID_OPERATION);
+			glBeginQuery	(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, ids[1]);
+			expectError		(GL_NO_ERROR);
+			glBeginQuery	(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, ids[2]);
+			expectError		(GL_INVALID_OPERATION);
+			glEndQuery		(GL_ANY_SAMPLES_PASSED);
+			glEndQuery		(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
+			expectError		(GL_NO_ERROR);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if id is 0.");
+			glBeginQuery	(GL_ANY_SAMPLES_PASSED, 0);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if id not a name returned from a previous call to glGenQueries, or if such a name has since been deleted with glDeleteQueries.");
+			glBeginQuery	(GL_ANY_SAMPLES_PASSED, -1);
+			expectError		(GL_INVALID_OPERATION);
+			glDeleteQueries	(1, &ids[2]);
+			expectError		(GL_NO_ERROR);
+			glBeginQuery	(GL_ANY_SAMPLES_PASSED, ids[2]);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if id is the name of an already active query object.");
+			glBeginQuery	(GL_ANY_SAMPLES_PASSED, ids[0]);
+			expectError		(GL_NO_ERROR);
+			glBeginQuery	(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, ids[0]);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if id refers to an existing query object whose type does not does not match target.");
+			glEndQuery		(GL_ANY_SAMPLES_PASSED);
+			expectError		(GL_NO_ERROR);
+			glBeginQuery	(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, ids[0]);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteQueries	(2, &ids[0]);
+			expectError		(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(end_query, "Invalid glEndQuery() usage",
+		{
+			GLuint id;
+			glGenQueries	(1, &id);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+			glEndQuery		(-1);
+			expectError		(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if glEndQuery is executed when a query object of the same target is not active.");
+			glEndQuery		(GL_ANY_SAMPLES_PASSED);
+			expectError		(GL_INVALID_OPERATION);
+			glBeginQuery	(GL_ANY_SAMPLES_PASSED, id);
+			expectError		(GL_NO_ERROR);
+			glEndQuery		(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
+			expectError		(GL_INVALID_OPERATION);
+			glEndQuery		(GL_ANY_SAMPLES_PASSED);
+			expectError		(GL_NO_ERROR);
+			m_log << TestLog::EndSection;
+
+			glDeleteQueries	(1, &id);
+			expectError		(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(delete_queries, "Invalid glDeleteQueries() usage",
+		{
+			GLuint id;
+			glGenQueries	(1, &id);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteQueries	(-1, &id);
+			expectError		(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteQueries	(1, &id);
+		});
+
+	// Sync objects
+
+	ES3F_ADD_API_CASE(fence_sync, "Invalid glFenceSync() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if condition is not GL_SYNC_GPU_COMMANDS_COMPLETE.");
+			glFenceSync(-1, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if flags is not zero.");
+			glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0x0010);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(wait_sync, "Invalid glWaitSync() usage",
+		{
+			GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if sync is not the name of a sync object.");
+			glWaitSync(0, 0, GL_TIMEOUT_IGNORED);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if flags is not zero.");
+			glWaitSync(sync, 0x0010, GL_TIMEOUT_IGNORED);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if timeout is not GL_TIMEOUT_IGNORED.");
+			glWaitSync(sync, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteSync(sync);
+		});
+	ES3F_ADD_API_CASE(client_wait_sync, "Invalid glClientWaitSync() usage",
+		{
+			GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if sync is not the name of an existing sync object.");
+			glClientWaitSync (0, 0, 10000);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if flags contains any unsupported flag.");
+			glClientWaitSync(sync, 0x00000004, 10000);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteSync(sync);
+		});
+	ES3F_ADD_API_CASE(delete_sync, "Invalid glDeleteSync() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if sync is neither zero or the name of a sync object.");
+			glDeleteSync((GLsync)1);
+			expectError(GL_INVALID_VALUE);
+			glDeleteSync(0);
+			expectError(GL_NO_ERROR);
+			m_log << TestLog::EndSection;
+		});
+}
+
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fNegativeFragmentApiTests.hpp b/modules/gles3/functional/es3fNegativeFragmentApiTests.hpp
new file mode 100644
index 0000000..89e903f
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeFragmentApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FNEGATIVEFRAGMENTAPITESTS_HPP
+#define _ES3FNEGATIVEFRAGMENTAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Fragment Pipe API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class NegativeFragmentApiTests : public TestCaseGroup
+{
+public:
+								NegativeFragmentApiTests		(Context& context);
+								~NegativeFragmentApiTests		(void);
+
+	void						init							(void);
+
+private:
+								NegativeFragmentApiTests		(const NegativeFragmentApiTests& other);
+	NegativeFragmentApiTests&	operator=						(const NegativeFragmentApiTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FNEGATIVEFRAGMENTAPITESTS_HPP
diff --git a/modules/gles3/functional/es3fNegativeShaderApiTests.cpp b/modules/gles3/functional/es3fNegativeShaderApiTests.cpp
new file mode 100644
index 0000000..6d13d5e
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeShaderApiTests.cpp
@@ -0,0 +1,1944 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Shader API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fNegativeShaderApiTests.hpp"
+#include "es3fApiCase.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluContextInfo.hpp"
+#include "deUniquePtr.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+
+static const char* vertexShaderSource		=	"#version 300 es\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vec4(0.0);\n"
+												"}\n\0";
+
+static const char* fragmentShaderSource		=	"#version 300 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(0.0);\n"
+												"}\n\0";
+
+static const char* uniformTestVertSource	=	"#version 300 es\n"
+												"uniform mediump vec4 vec4_v;\n"
+												"uniform mediump mat4 mat4_v;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = mat4_v * vec4_v;\n"
+												"}\n\0";
+
+static const char* uniformTestFragSource	=	"#version 300 es\n"
+												"uniform mediump ivec4 ivec4_f;\n"
+												"uniform mediump uvec4 uvec4_f;\n"
+												"uniform sampler2D sampler_f;\n"
+												"layout(location = 0) out mediump vec4 fragColor;"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor.xy = (vec4(uvec4_f) + vec4(ivec4_f)).xy;\n"
+												"	fragColor.zw = texture(sampler_f, vec2(0.0, 0.0)).zw;\n"
+												"}\n\0";
+
+static const char* uniformBlockVertSource	=	"#version 300 es\n"
+												"layout(shared) uniform Block { lowp float var; };\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vec4(var);\n"
+												"}\n\0";
+
+NegativeShaderApiTests::NegativeShaderApiTests (Context& context)
+	: TestCaseGroup(context, "shader", "Negative Shader API Cases")
+{
+}
+
+NegativeShaderApiTests::~NegativeShaderApiTests (void)
+{
+}
+
+void NegativeShaderApiTests::init (void)
+{
+	// Shader control commands
+
+	ES3F_ADD_API_CASE(create_shader, "Invalid glCreateShader() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if shaderType is not an accepted value.");
+			glCreateShader(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(shader_source, "Invalid glShaderSource() usage",
+		{
+			// \note Shader compilation must be supported.
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glShaderSource(1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if count is less than 0.");
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+			glShaderSource(shader, -1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			GLuint program = glCreateProgram();
+			glShaderSource(program, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(compile_shader, "Invalid glCompileShader() usage",
+		{
+			// \note Shader compilation must be supported.
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glCompileShader(9);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			GLuint program = glCreateProgram();
+			glCompileShader(program);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+		});
+	ES3F_ADD_API_CASE(delete_shader, "Invalid glDeleteShader() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glDeleteShader(9);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(shader_binary, "Invalid glShaderBinary() usage",
+		{
+			std::vector<deInt32> binaryFormats;
+			getSupportedExtensions(GL_NUM_SHADER_BINARY_FORMATS, GL_SHADER_BINARY_FORMATS, binaryFormats);
+			deBool shaderBinarySupported = !binaryFormats.empty();
+			if (!shaderBinarySupported)
+				m_log << TestLog::Message << "// Shader binaries not supported." << TestLog::EndMessage;
+			else
+				m_log << TestLog::Message << "// Shader binaries supported" << TestLog::EndMessage;
+
+			GLuint shaders[2];
+			shaders[0]		= glCreateShader(GL_VERTEX_SHADER);
+			shaders[1]		= glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if binaryFormat is not an accepted value.");
+			glShaderBinary(1, &shaders[0], -1, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			if (shaderBinarySupported)
+			{
+				m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if the data pointed to by binary does not match the format specified by binaryFormat.");
+				const GLbyte data = 0x005F;
+				glShaderBinary(1, &shaders[0], binaryFormats[0], &data, 1);
+				expectError(GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+
+				m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if more than one of the handles in shaders refers to the same type of shader, or GL_INVALID_VALUE due to invalid data pointer.");
+				glShaderBinary(2, &shaders[0], binaryFormats[0], 0, 0);
+				expectError(GL_INVALID_OPERATION, GL_INVALID_VALUE);
+				m_log << TestLog::EndSection;
+			}
+
+			glDeleteShader(shaders[0]);
+			glDeleteShader(shaders[1]);
+		});
+	ES3F_ADD_API_CASE(attach_shader, "Invalid glAttachShader() usage",
+		{
+			GLuint shader1 = glCreateShader(GL_VERTEX_SHADER);
+			GLuint shader2 = glCreateShader(GL_VERTEX_SHADER);
+			GLuint program = glCreateProgram();
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glAttachShader(shader1, shader1);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			glAttachShader(program, program);
+			expectError(GL_INVALID_OPERATION);
+			glAttachShader(shader1, program);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either program or shader is not a value generated by OpenGL.");
+			glAttachShader(program, -1);
+			expectError(GL_INVALID_VALUE);
+			glAttachShader(-1, shader1);
+			expectError(GL_INVALID_VALUE);
+			glAttachShader(-1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is already attached to program.");
+			glAttachShader(program, shader1);
+			expectError(GL_NO_ERROR);
+			glAttachShader(program, shader1);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a shader of the same type as shader is already attached to program.");
+			glAttachShader(program, shader2);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader1);
+			glDeleteShader(shader2);
+		});
+	ES3F_ADD_API_CASE(detach_shader, "Invalid glDetachShader() usage",
+		{
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+			GLuint program = glCreateProgram();
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if either program or shader is not a value generated by OpenGL.");
+			glDetachShader(-1, shader);
+			expectError(GL_INVALID_VALUE);
+			glDetachShader(program, -1);
+			expectError(GL_INVALID_VALUE);
+			glDetachShader(-1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glDetachShader(shader, shader);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			glDetachShader(program, program);
+			expectError(GL_INVALID_OPERATION);
+			glDetachShader(shader, program);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not attached to program.");
+			glDetachShader(program, shader);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(link_program, "Invalid glLinkProgram() usage",
+		{
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glLinkProgram(-1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glLinkProgram(shader);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is the currently active program object and transform feedback mode is active.");
+			glu::ShaderProgram			program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			deUint32					buf;
+			deUint32					tfID;
+			const char* tfVarying		= "gl_Position";
+
+			glGenTransformFeedbacks		(1, &tfID);
+			glGenBuffers				(1, &buf);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			expectError					(GL_NO_ERROR);
+
+			glLinkProgram				(program.getProgram());
+			expectError					(GL_INVALID_OPERATION);
+
+			glEndTransformFeedback		();
+			glDeleteTransformFeedbacks	(1, &tfID);
+			glDeleteBuffers				(1, &buf);
+			expectError					(GL_NO_ERROR);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(use_program, "Invalid glUseProgram() usage",
+		{
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is neither 0 nor a value generated by OpenGL.");
+			glUseProgram(-1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glUseProgram(shader);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if transform feedback mode is active and not paused.");
+			glu::ShaderProgram			program1(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glu::ShaderProgram			program2(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			deUint32					buf;
+			deUint32					tfID;
+			const char* tfVarying		= "gl_Position";
+
+			glGenTransformFeedbacks		(1, &tfID);
+			glGenBuffers				(1, &buf);
+
+			glUseProgram				(program1.getProgram());
+			glTransformFeedbackVaryings	(program1.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program1.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			expectError					(GL_NO_ERROR);
+
+			glUseProgram				(program2.getProgram());
+			expectError					(GL_INVALID_OPERATION);
+
+			glPauseTransformFeedback	();
+			glUseProgram				(program2.getProgram());
+			expectError					(GL_NO_ERROR);
+
+			glEndTransformFeedback		();
+			glDeleteTransformFeedbacks	(1, &tfID);
+			glDeleteBuffers				(1, &buf);
+			expectError					(GL_NO_ERROR);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(delete_program, "Invalid glDeleteProgram() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glDeleteProgram(-1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(validate_program, "Invalid glValidateProgram() usage",
+		{
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glValidateProgram(-1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glValidateProgram(shader);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(get_program_binary, "Invalid glGetProgramBinary() usage",
+		{
+			glu::ShaderProgram				program			(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glu::ShaderProgram				programInvalid	(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, ""));
+			GLenum							binaryFormat	= -1;
+			GLsizei							binaryLength	= -1;
+			GLint							binaryPtr		= -1;
+			GLint							bufSize			= -1;
+			GLint							linkStatus		= -1;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if bufSize is less than the size of GL_PROGRAM_BINARY_LENGTH for program.");
+			glGetProgramiv		(program.getProgram(), GL_PROGRAM_BINARY_LENGTH,	&bufSize);
+			expectError			(GL_NO_ERROR);
+			glGetProgramiv		(program.getProgram(), GL_LINK_STATUS,				&linkStatus);
+			m_log << TestLog::Message << "// GL_PROGRAM_BINARY_LENGTH = " << bufSize << TestLog::EndMessage;
+			m_log << TestLog::Message << "// GL_LINK_STATUS = " << linkStatus << TestLog::EndMessage;
+			expectError			(GL_NO_ERROR);
+
+			glGetProgramBinary	(program.getProgram(), 0, &binaryLength, &binaryFormat, &binaryPtr);
+			expectError			(GL_INVALID_OPERATION);
+			if (bufSize > 0)
+			{
+				glGetProgramBinary	(program.getProgram(), bufSize-1, &binaryLength, &binaryFormat, &binaryPtr);
+				expectError			(GL_INVALID_OPERATION);
+			}
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if GL_LINK_STATUS for the program object is false.");
+			glGetProgramiv		(programInvalid.getProgram(), GL_PROGRAM_BINARY_LENGTH,	&bufSize);
+			expectError			(GL_NO_ERROR);
+			glGetProgramiv		(programInvalid.getProgram(), GL_LINK_STATUS,			&linkStatus);
+			m_log << TestLog::Message << "// GL_PROGRAM_BINARY_LENGTH = " << bufSize << TestLog::EndMessage;
+			m_log << TestLog::Message << "// GL_LINK_STATUS = " << linkStatus << TestLog::EndMessage;
+			expectError			(GL_NO_ERROR);
+
+			glGetProgramBinary	(programInvalid.getProgram(), bufSize, &binaryLength, &binaryFormat, &binaryPtr);
+			expectError			(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(program_binary, "Invalid glProgramBinary() usage",
+		{
+			glu::ShaderProgram		srcProgram		(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			GLuint					dstProgram		= glCreateProgram();
+			GLuint					dummyShader		= glCreateShader(GL_VERTEX_SHADER);
+			GLenum					binaryFormat	= -1;
+			GLsizei					binaryLength	= -1;
+			std::vector<deUint8>	binaryBuf;
+			GLint					bufSize			= -1;
+			GLint					linkStatus		= -1;
+
+			glGetProgramiv		(srcProgram.getProgram(), GL_PROGRAM_BINARY_LENGTH,	&bufSize);
+			glGetProgramiv		(srcProgram.getProgram(), GL_LINK_STATUS,			&linkStatus);
+			m_log << TestLog::Message << "// GL_PROGRAM_BINARY_LENGTH = " << bufSize << TestLog::EndMessage;
+			m_log << TestLog::Message << "// GL_LINK_STATUS = " << linkStatus << TestLog::EndMessage;
+			TCU_CHECK(bufSize > 0);
+			binaryBuf.resize(bufSize);
+			glGetProgramBinary	(srcProgram.getProgram(), bufSize, &binaryLength, &binaryFormat, &binaryBuf[0]);
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not the name of an existing program object.");
+			glProgramBinary		(dummyShader, binaryFormat, &binaryBuf[0], binaryLength);
+			expectError			(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if binaryFormat is not a value recognized by the implementation.");
+			glProgramBinary		(dstProgram, -1, &binaryBuf[0], binaryLength);
+			expectError			(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(dummyShader);
+			glDeleteProgram(dstProgram);
+		});
+	ES3F_ADD_API_CASE(program_parameteri, "Invalid glProgramParameteri() usage",
+		{
+			GLuint	program	= glCreateProgram();
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not the name of an existing program object.");
+			glProgramParameteri		(0, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not GL_PROGRAM_BINARY_RETRIEVABLE_HINT.");
+			glProgramParameteri		(program, -1, GL_TRUE);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if value is not GL_FALSE or GL_TRUE.");
+			glProgramParameteri		(program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, 2);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+		});
+	ES3F_ADD_API_CASE(gen_samplers, "Invalid glGenSamplers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			GLuint sampler;
+			glGenSamplers	(-1, &sampler);
+			expectError		(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(bind_sampler, "Invalid glBindSampler() usage",
+		{
+			int				maxTexImageUnits;
+			GLuint			sampler;
+			glGetIntegerv	(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTexImageUnits);
+			glGenSamplers	(1, &sampler);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if unit is greater than or equal to the value of GL_MAX_COMBIED_TEXTURE_IMAGE_UNITS.");
+			glBindSampler	(maxTexImageUnits, sampler);
+			expectError		(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if sampler is not zero or a name previously returned from a call to glGenSamplers.");
+			glBindSampler	(1, -1);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if sampler has been deleted by a call to glDeleteSamplers.");
+			glDeleteSamplers(1, &sampler);
+			glBindSampler	(1, sampler);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(delete_samplers, "Invalid glDeleteSamplers() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteSamplers(-1, 0);
+			expectError		(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_sampler_parameteriv, "Invalid glGetSamplerParameteriv() usage",
+		{
+			int				params;
+			GLuint			sampler;
+			glGenSamplers	(1, &sampler);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object returned from a previous call to glGenSamplers.");
+			glGetSamplerParameteriv	(-1, GL_TEXTURE_MAG_FILTER, &params);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetSamplerParameteriv	(sampler, -1, &params);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteSamplers(1, &sampler);
+		});
+	ES3F_ADD_API_CASE(get_sampler_parameterfv, "Invalid glGetSamplerParameterfv() usage",
+		{
+			float			params;
+			GLuint			sampler;
+			glGenSamplers	(1, &sampler);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object returned from a previous call to glGenSamplers.");
+			glGetSamplerParameterfv	(-1, GL_TEXTURE_MAG_FILTER, &params);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetSamplerParameterfv	(sampler, -1, &params);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteSamplers(1, &sampler);
+		});
+	ES3F_ADD_API_CASE(sampler_parameteri, "Invalid glSamplerParameteri() usage",
+		{
+			GLuint			sampler;
+			glGenSamplers	(1, &sampler);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object previously returned from a call to glGenSamplers.");
+			glSamplerParameteri		(-1, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined constant value (based on the value of pname) and does not.");
+			glSamplerParameteri		(sampler, GL_TEXTURE_WRAP_S, -1);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteSamplers(1, &sampler);
+		});
+	ES3F_ADD_API_CASE(sampler_parameteriv, "Invalid glSamplerParameteriv() usage",
+		{
+			int				params;
+			GLuint			sampler;
+			glGenSamplers	(1, &sampler);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object previously returned from a call to glGenSamplers.");
+			params = GL_CLAMP_TO_EDGE;
+			glSamplerParameteriv	(-1, GL_TEXTURE_WRAP_S, &params);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined constant value (based on the value of pname) and does not.");
+			params = -1;
+			glSamplerParameteriv	(sampler, GL_TEXTURE_WRAP_S, &params);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteSamplers(1, &sampler);
+		});
+	ES3F_ADD_API_CASE(sampler_parameterf, "Invalid glSamplerParameterf() usage",
+		{
+			GLuint			sampler;
+			glGenSamplers	(1, &sampler);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object previously returned from a call to glGenSamplers.");
+			glSamplerParameterf		(-1, GL_TEXTURE_MIN_LOD, -1000.0f);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined constant value (based on the value of pname) and does not.");
+			glSamplerParameterf		(sampler, GL_TEXTURE_WRAP_S, -1.0f);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteSamplers(1, &sampler);
+		});
+	ES3F_ADD_API_CASE(sampler_parameterfv, "Invalid glSamplerParameterfv() usage",
+		{
+			float			params;
+			GLuint			sampler;
+			glGenSamplers	(1, &sampler);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object previously returned from a call to glGenSamplers.");
+			params = -1000.0f;
+			glSamplerParameterfv	(-1, GL_TEXTURE_WRAP_S, &params);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined constant value (based on the value of pname) and does not.");
+			params = -1.0f;
+			glSamplerParameterfv	(sampler, GL_TEXTURE_WRAP_S, &params);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteSamplers(1, &sampler);
+		});
+
+	// Shader data commands
+
+	ES3F_ADD_API_CASE(get_attrib_location, "Invalid glGetAttribLocation() usage",
+		{
+			GLuint programEmpty		= glCreateProgram();
+			GLuint shader			= glCreateShader(GL_VERTEX_SHADER);
+
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+			glBindAttribLocation	(programEmpty, 0, "test");
+			glGetAttribLocation		(programEmpty, "test");
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a program or shader object.");
+			glUseProgram			(program.getProgram());
+			glBindAttribLocation	(program.getProgram(), 0, "test");
+			expectError				(GL_NO_ERROR);
+			glGetAttribLocation		(program.getProgram(), "test");
+			expectError				(GL_NO_ERROR);
+			glGetAttribLocation		(-2, "test");
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetAttribLocation		(shader, "test");
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glUseProgram			(0);
+			glDeleteShader			(shader);
+			glDeleteProgram			(programEmpty);
+		});
+	ES3F_ADD_API_CASE(get_uniform_location, "Invalid glGetUniformLocation() usage",
+		{
+			GLuint programEmpty = glCreateProgram();
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+			glGetUniformLocation(programEmpty, "test");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glUseProgram(program.getProgram());
+			glGetUniformLocation(-2, "test");
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetAttribLocation(shader, "test");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteProgram(programEmpty);
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(bind_attrib_location, "Invalid glBindAttribLocation() usage",
+		{
+			GLuint program = glCreateProgram();
+			GLuint maxIndex = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			glBindAttribLocation(program, maxIndex, "test");
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if name starts with the reserved prefix \"gl_\".");
+			glBindAttribLocation(program, maxIndex-1, "gl_test");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glBindAttribLocation(-1, maxIndex-1, "test");
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glBindAttribLocation(shader, maxIndex-1, "test");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(uniform_block_binding, "Invalid glUniformBlockBinding() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformBlockVertSource, uniformTestFragSource));
+
+			glUseProgram	(program.getProgram());
+
+			GLint			maxUniformBufferBindings;
+			GLint			numActiveUniforms			= -1;
+			GLint			numActiveBlocks				= -1;
+			glGetIntegerv	(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings);
+			glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORMS,			&numActiveUniforms);
+			glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORM_BLOCKS,	&numActiveBlocks);
+			m_log << TestLog::Message << "// GL_MAX_UNIFORM_BUFFER_BINDINGS = " << maxUniformBufferBindings << TestLog::EndMessage;
+			m_log << TestLog::Message << "// GL_ACTIVE_UNIFORMS = "				<< numActiveUniforms		<< TestLog::EndMessage;
+			m_log << TestLog::Message << "// GL_ACTIVE_UNIFORM_BLOCKS = "		<< numActiveBlocks			<< TestLog::EndMessage;
+			expectError		(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if uniformBlockIndex is not an active uniform block index of program.");
+			glUniformBlockBinding(program.getProgram(), -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glUniformBlockBinding(program.getProgram(), 5, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if uniformBlockBinding is greater than or equal to the value of GL_MAX_UNIFORM_BUFFER_BINDINGS.");
+			glUniformBlockBinding(program.getProgram(), maxUniformBufferBindings, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if program is not the name of a program object generated by the GL.");
+			glUniformBlockBinding(-1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+
+	// glUniform*f
+
+	ES3F_ADD_API_CASE(uniformf_invalid_program, "Invalid glUniform{1234}f() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniform1f(-1, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2f(-1, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3f(-1, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4f(-1, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(uniformf_incompatible_type, "Invalid glUniform{1234}f() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			GLint vec4_v	= glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+			GLint ivec4_f	= glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+			GLint uvec4_f	= glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+			GLint sampler_f	= glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+			expectError(GL_NO_ERROR);
+
+			if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniform1f(vec4_v, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2f(vec4_v, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3f(vec4_v, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4f(vec4_v, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if glUniform{1234}f is used to load a uniform variable of type int, ivec2, ivec3, ivec4, unsigned int, uvec2, uvec3, uvec4.");
+			glUseProgram(program.getProgram());
+			glUniform4f(ivec4_f, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4f(uvec4_f, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than glUniform1i and glUniform1iv.");
+			glUseProgram(program.getProgram());
+			glUniform1f(sampler_f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniformf_invalid_location, "Invalid glUniform{1234}f() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			expectError(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniform1f(-2, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2f(-2, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3f(-2, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4f(-2, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniform1f(-1, 0.0f);
+			expectError(GL_NO_ERROR);
+			glUniform2f(-1, 0.0f, 0.0f);
+			expectError(GL_NO_ERROR);
+			glUniform3f(-1, 0.0f, 0.0f, 0.0f);
+			expectError(GL_NO_ERROR);
+			glUniform4f(-1, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+	// glUniform*fv
+
+	ES3F_ADD_API_CASE(uniformfv_invalid_program, "Invalid glUniform{1234}fv() usage",
+		{
+			std::vector<GLfloat> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniform1fv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2fv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3fv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4fv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(uniformfv_incompatible_type, "Invalid glUniform{1234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			GLint vec4_v	= glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+			GLint ivec4_f	= glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+			GLint uvec4_f	= glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+			GLint sampler_f	= glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+			expectError(GL_NO_ERROR);
+
+			if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLfloat> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniform1fv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2fv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3fv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4fv(vec4_v, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if glUniform{1234}fv is used to load a uniform variable of type int, ivec2, ivec3, ivec4, unsigned int, uvec2, uvec3, uvec4.");
+			glUseProgram(program.getProgram());
+			glUniform4fv(ivec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4fv(uvec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than glUniform1i and glUniform1iv.");
+			glUseProgram(program.getProgram());
+			glUniform1fv(sampler_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniformfv_invalid_location, "Invalid glUniform{1234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			expectError(GL_NO_ERROR);
+
+			std::vector<GLfloat> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniform1fv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2fv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3fv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4fv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniform1fv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform2fv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform3fv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform4fv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniformfv_invalid_count, "Invalid glUniform{1234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram			(program.getProgram());
+			GLint vec4_v			= glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+			expectError(GL_NO_ERROR);
+
+			if (vec4_v == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLfloat> data(8);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+			glUseProgram(program.getProgram());
+			glUniform1fv(vec4_v, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2fv(vec4_v, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3fv(vec4_v, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4fv(vec4_v, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+	// glUniform*i
+
+	ES3F_ADD_API_CASE(uniformi_invalid_program, "Invalid glUniform{1234}i() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniform1i(-1, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2i(-1, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3i(-1, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4i(-1, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(uniformi_incompatible_type, "Invalid glUniform{1234}i() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			GLint vec4_v	= glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+			GLint ivec4_f	= glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+			GLint uvec4_f	= glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+			GLint sampler_f	= glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+			expectError(GL_NO_ERROR);
+
+			if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniform1i(ivec4_f, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2i(ivec4_f, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3i(ivec4_f, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4i(ivec4_f, 0, 0, 0, 0);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if glUniform{1234}i is used to load a uniform variable of type unsigned int, uvec2, uvec3, uvec4, or an array of these.");
+			glUseProgram(program.getProgram());
+			glUniform1i(uvec4_f, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2i(uvec4_f, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3i(uvec4_f, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4i(uvec4_f, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if glUniform{1234}i is used to load a uniform variable of type float, vec2, vec3, or vec4.");
+			glUseProgram(program.getProgram());
+			glUniform1i(vec4_v, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2i(vec4_v, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3i(vec4_v, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4i(vec4_v, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniformi_invalid_location, "Invalid glUniform{1234}i() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			expectError(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniform1i(-2, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2i(-2, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3i(-2, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4i(-2, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniform1i(-1, 0);
+			expectError(GL_NO_ERROR);
+			glUniform2i(-1, 0, 0);
+			expectError(GL_NO_ERROR);
+			glUniform3i(-1, 0, 0, 0);
+			expectError(GL_NO_ERROR);
+			glUniform4i(-1, 0, 0, 0, 0);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+	// glUniform*iv
+
+	ES3F_ADD_API_CASE(uniformiv_invalid_program, "Invalid glUniform{1234}iv() usage",
+		{
+			std::vector<GLint> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniform1iv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2iv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3iv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4iv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(uniformiv_incompatible_type, "Invalid glUniform{1234}iv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			GLint vec4_v	= glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+			GLint ivec4_f	= glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+			GLint uvec4_f	= glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+			GLint sampler_f	= glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+			expectError(GL_NO_ERROR);
+
+			if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLint> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniform1iv(ivec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2iv(ivec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3iv(ivec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4iv(ivec4_f, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if glUniform{1234}iv is used to load a uniform variable of type float, vec2, vec3, or vec4.");
+			glUseProgram(program.getProgram());
+			glUniform1iv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2iv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3iv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4iv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if glUniform{1234}iv is used to load a uniform variable of type unsigned int, uvec2, uvec3 or uvec4.");
+			glUseProgram(program.getProgram());
+			glUniform1iv(uvec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2iv(uvec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3iv(uvec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4iv(uvec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniformiv_invalid_location, "Invalid glUniform{1234}iv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			expectError(GL_NO_ERROR);
+
+			std::vector<GLint> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniform1iv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2iv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3iv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4iv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniform1iv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform2iv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform3iv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform4iv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniformiv_invalid_count, "Invalid glUniform{1234}iv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram			(program.getProgram());
+			GLint ivec4_f			= glGetUniformLocation(program.getProgram(), "ivec4_f"); // ivec4
+			expectError(GL_NO_ERROR);
+
+			if (ivec4_f == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLint> data(8);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+			glUseProgram(program.getProgram());
+			glUniform1iv(ivec4_f, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2iv(ivec4_f, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3iv(ivec4_f, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4iv(ivec4_f, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+	// glUniform{1234}ui
+
+	ES3F_ADD_API_CASE(uniformui_invalid_program, "Invalid glUniform{234}ui() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniform1ui(-1, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2ui(-1, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3ui(-1, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4ui(-1, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(uniformui_incompatible_type, "Invalid glUniform{1234}ui() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			GLint vec4_v	= glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+			GLint ivec4_f	= glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+			GLint uvec4_f	= glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+			GLint sampler_f	= glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+			expectError(GL_NO_ERROR);
+
+			if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniform1ui(uvec4_f, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2ui(uvec4_f, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3ui(uvec4_f, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4ui(uvec4_f, 0, 0, 0, 0);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if glUniform{1234}i is used to load a uniform variable of type int, ivec2, ivec3, ivec4, or an array of these.");
+			glUseProgram(program.getProgram());
+			glUniform1ui(ivec4_f, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2ui(ivec4_f, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3ui(ivec4_f, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4ui(ivec4_f, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if glUniform{1234}i is used to load a uniform variable of type float, vec2, vec3, or vec4.");
+			glUseProgram(program.getProgram());
+			glUniform1ui(vec4_v, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2ui(vec4_v, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3ui(vec4_v, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4ui(vec4_v, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than glUniform1i and glUniform1iv.");
+			glUseProgram(program.getProgram());
+			glUniform1ui(sampler_f, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniformui_invalid_location, "Invalid glUniform{1234}ui() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			expectError(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniform1i(-2, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2i(-2, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3i(-2, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4i(-2, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniform1i(-1, 0);
+			expectError(GL_NO_ERROR);
+			glUniform2i(-1, 0, 0);
+			expectError(GL_NO_ERROR);
+			glUniform3i(-1, 0, 0, 0);
+			expectError(GL_NO_ERROR);
+			glUniform4i(-1, 0, 0, 0, 0);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+	// glUniform{1234}uiv
+
+	ES3F_ADD_API_CASE(uniformuiv_invalid_program, "Invalid glUniform{234}uiv() usage",
+		{
+			std::vector<GLuint> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniform1uiv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2uiv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3uiv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4uiv(-1, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(uniformuiv_incompatible_type, "Invalid glUniform{1234}uiv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			GLint vec4_v	= glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+			GLint ivec4_f	= glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+			GLint uvec4_f	= glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+			GLint sampler_f	= glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+			expectError(GL_NO_ERROR);
+
+			if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLuint> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniform1uiv(uvec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2uiv(uvec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3uiv(uvec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4uiv(uvec4_f, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if glUniform{1234}uiv is used to load a uniform variable of type float, vec2, vec3, or vec4.");
+			glUseProgram(program.getProgram());
+			glUniform1uiv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2uiv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3uiv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4uiv(vec4_v, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if glUniform{1234}uiv is used to load a uniform variable of type int, ivec2, ivec3 or ivec4.");
+			glUseProgram(program.getProgram());
+			glUniform1uiv(ivec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2uiv(ivec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3uiv(ivec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4uiv(ivec4_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than glUniform1i and glUniform1iv.");
+			glUseProgram(program.getProgram());
+			glUniform1uiv(sampler_f, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniformuiv_invalid_location, "Invalid glUniform{1234}uiv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			expectError(GL_NO_ERROR);
+
+			std::vector<GLuint> data(4);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniform1uiv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2uiv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3uiv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4uiv(-2, 1, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniform1uiv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform2uiv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform3uiv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniform4uiv(-1, 1, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniformuiv_invalid_count, "Invalid glUniform{1234}uiv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram			(program.getProgram());
+			int uvec4_f				= glGetUniformLocation(program.getProgram(), "uvec4_f"); // uvec4
+			expectError(GL_NO_ERROR);
+
+			if (uvec4_f == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLuint> data(8);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+			glUseProgram(program.getProgram());
+			glUniform1uiv(uvec4_f, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform2uiv(uvec4_f, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform3uiv(uvec4_f, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniform4uiv(uvec4_f, 2, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+
+	// glUniformMatrix*fv
+
+	ES3F_ADD_API_CASE(uniform_matrixfv_invalid_program, "Invalid glUniformMatrix{234}fv() usage",
+		{
+			std::vector<GLfloat> data(16);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if there is no current program object.");
+			glUseProgram(0);
+			glUniformMatrix2fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUniformMatrix2x3fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3x2fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix2x4fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4x2fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3x4fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4x3fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(uniform_matrixfv_incompatible_type, "Invalid glUniformMatrix{234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram			(program.getProgram());
+			GLint mat4_v			= glGetUniformLocation(program.getProgram(), "mat4_v");	// mat4
+			GLint sampler_f			= glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+			expectError(GL_NO_ERROR);
+
+			if (mat4_v == -1 || sampler_f == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLfloat> data(16);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the glUniform command.");
+			glUseProgram(program.getProgram());
+			glUniformMatrix2fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+
+			glUniformMatrix2x3fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3x2fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix2x4fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4x2fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3x4fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4x3fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than glUniform1i and glUniform1iv.");
+			glUseProgram(program.getProgram());
+			glUniformMatrix2fv(sampler_f, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3fv(sampler_f, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4fv(sampler_f, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUniformMatrix2x3fv(sampler_f, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3x2fv(sampler_f, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix2x4fv(sampler_f, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4x2fv(sampler_f, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3x4fv(sampler_f, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4x3fv(sampler_f, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniform_matrixfv_invalid_location, "Invalid glUniformMatrix{234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram(program.getProgram());
+			expectError(GL_NO_ERROR);
+
+			std::vector<GLfloat> data(16);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+			glUseProgram(program.getProgram());
+			glUniformMatrix2fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUniformMatrix2x3fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3x2fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix2x4fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4x2fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3x4fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4x3fv(-2, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUseProgram(program.getProgram());
+			glUniformMatrix2fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniformMatrix3fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniformMatrix4fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+
+			glUniformMatrix2x3fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniformMatrix3x2fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniformMatrix2x4fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniformMatrix4x2fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniformMatrix3x4fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			glUniformMatrix4x3fv(-1, 1, GL_FALSE, &data[0]);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(uniform_matrixfv_invalid_count, "Invalid glUniformMatrix{234}fv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+			glUseProgram			(program.getProgram());
+			GLint mat4_v			= glGetUniformLocation(program.getProgram(), "mat4_v"); // mat4
+			expectError(GL_NO_ERROR);
+
+			if (mat4_v == -1)
+			{
+				m_log << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+			}
+
+			std::vector<GLfloat> data(32);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+			glUseProgram(program.getProgram());
+			glUniformMatrix2fv(mat4_v, 2, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3fv(mat4_v, 2, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4fv(mat4_v, 2, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glUniformMatrix2x3fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3x2fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix2x4fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4x2fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix3x4fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			glUniformMatrix4x3fv(mat4_v, 1, GL_FALSE, &data[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+
+	// Transform feedback
+
+	ES3F_ADD_API_CASE(gen_transform_feedbacks, "Invalid glGenTransformFeedbacks() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			GLuint id;
+			glGenTransformFeedbacks(-1, &id);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(bind_transform_feedback, "Invalid glBindTransformFeedback() usage",
+		{
+			GLuint						tfID[2];
+			glu::ShaderProgram			program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			deUint32					buf;
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(2, tfID);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_TRANSFORM_FEEDBACK.");
+			glBindTransformFeedback(-1, tfID[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the transform feedback operation is active on the currently bound transform feedback object, and is not paused.");
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID[0]);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			expectError					(GL_NO_ERROR);
+
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID[1]);
+			expectError					(GL_INVALID_OPERATION);
+
+			glEndTransformFeedback		();
+			expectError					(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram				(0);
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(2, tfID);
+			expectError					(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(delete_transform_feedbacks, "Invalid glDeleteTransformFeedbacks() usage",
+		{
+			GLuint id;
+			glGenTransformFeedbacks(1, &id);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteTransformFeedbacks(-1, &id);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			glDeleteTransformFeedbacks(1, &id);
+		});
+	ES3F_ADD_API_CASE(begin_transform_feedback, "Invalid glBeginTransformFeedback() usage",
+		{
+			GLuint						tfID[2];
+			glu::ShaderProgram			program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			deUint32					buf;
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(2, tfID);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID[0]);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			expectError					(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if primitiveMode is not one of GL_POINTS, GL_LINES, or GL_TRIANGLES.");
+			glBeginTransformFeedback	(-1);
+			expectError					(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if transform feedback is already active.");
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			expectError					(GL_NO_ERROR);
+			glBeginTransformFeedback	(GL_POINTS);
+			expectError					(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if any binding point used in transform feedback mode does not have a buffer object bound.");
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			expectError					(GL_INVALID_OPERATION);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if no binding points would be used because no program object is active.");
+			glUseProgram				(0);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			expectError					(GL_INVALID_OPERATION);
+			glUseProgram				(program.getProgram());
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if no binding points would be used because the active program object has specified no varying variables to record.");
+			glTransformFeedbackVaryings	(program.getProgram(), 0, 0, GL_INTERLEAVED_ATTRIBS);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			expectError					(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glEndTransformFeedback		();
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(2, tfID);
+			expectError					(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(pause_transform_feedback, "Invalid glPauseTransformFeedback() usage",
+		{
+			GLuint						tfID[2];
+			glu::ShaderProgram			program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			deUint32					buf;
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(2, tfID);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID[0]);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			expectError					(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the currently bound transform feedback object is not active or is paused.");
+			glPauseTransformFeedback	();
+			expectError					(GL_INVALID_OPERATION);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			glPauseTransformFeedback	();
+			expectError					(GL_NO_ERROR);
+			glPauseTransformFeedback	();
+			expectError					(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glEndTransformFeedback		();
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(2, tfID);
+			expectError					(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(resume_transform_feedback, "Invalid glResumeTransformFeedback() usage",
+		{
+			GLuint						tfID[2];
+			glu::ShaderProgram			program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			deUint32					buf;
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(2, tfID);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID[0]);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			expectError					(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the currently bound transform feedback object is not active or is not paused.");
+			glResumeTransformFeedback	();
+			expectError					(GL_INVALID_OPERATION);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			glResumeTransformFeedback	();
+			expectError					(GL_INVALID_OPERATION);
+			glPauseTransformFeedback	();
+			glResumeTransformFeedback	();
+			expectError					(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glEndTransformFeedback		();
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(2, tfID);
+			expectError					(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(end_transform_feedback, "Invalid glEndTransformFeedback() usage",
+		{
+			GLuint						tfID;
+			glu::ShaderProgram			program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			deUint32					buf;
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(1, &tfID);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			expectError					(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if transform feedback is not active.");
+			glEndTransformFeedback		();
+			expectError					(GL_INVALID_OPERATION);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			glEndTransformFeedback		();
+			expectError					(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(1, &tfID);
+			expectError					(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(get_transform_feedback_varying, "Invalid glGetTransformFeedbackVarying() usage",
+		{
+			GLuint					tfID;
+			glu::ShaderProgram		program			(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glu::ShaderProgram		programInvalid	(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, ""));
+			const char* tfVarying	= "gl_Position";
+			int						maxTransformFeedbackVaryings = 0;
+
+			GLsizei					length;
+			GLsizei					size;
+			GLenum					type;
+			char					name[32];
+
+			glGenTransformFeedbacks			(1, &tfID);
+
+			glTransformFeedbackVaryings		(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			expectError						(GL_NO_ERROR);
+			glLinkProgram					(program.getProgram());
+			expectError						(GL_NO_ERROR);
+
+			glBindTransformFeedback			(GL_TRANSFORM_FEEDBACK, tfID);
+			expectError						(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if program is not the name of a program object.");
+			glGetTransformFeedbackVarying	(-1, 0, 32, &length, &size, &type, &name[0]);
+			expectError						(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater or equal to the value of GL_TRANSFORM_FEEDBACK_VARYINGS.");
+			glGetProgramiv					(program.getProgram(), GL_TRANSFORM_FEEDBACK_VARYINGS, &maxTransformFeedbackVaryings);
+			glGetTransformFeedbackVarying	(program.getProgram(), maxTransformFeedbackVaryings, 32, &length, &size, &type, &name[0]);
+			expectError						(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION or GL_INVALID_VALUE is generated program has not been linked.");
+			glGetTransformFeedbackVarying	(programInvalid.getProgram(), 0, 32, &length, &size, &type, &name[0]);
+			expectError						(GL_INVALID_OPERATION, GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			glDeleteTransformFeedbacks		(1, &tfID);
+			expectError						(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(transform_feedback_varyings, "Invalid glTransformFeedbackVaryings() usage",
+		{
+			GLuint					tfID;
+			glu::ShaderProgram		program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			const char* tfVarying	= "gl_Position";
+			GLint					maxTransformFeedbackSeparateAttribs = 0;
+
+			glGenTransformFeedbacks			(1, &tfID);
+			expectError						(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if program is not the name of a program object.");
+			glTransformFeedbackVaryings		(0, 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			expectError						(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if bufferMode is GL_SEPARATE_ATTRIBS and count is greater than GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.");
+			glGetIntegerv					(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxTransformFeedbackSeparateAttribs);
+			glTransformFeedbackVaryings		(program.getProgram(), maxTransformFeedbackSeparateAttribs+1, &tfVarying, GL_SEPARATE_ATTRIBS);
+			expectError						(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			glDeleteTransformFeedbacks		(1, &tfID);
+			expectError						(GL_NO_ERROR);
+		});
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fNegativeShaderApiTests.hpp b/modules/gles3/functional/es3fNegativeShaderApiTests.hpp
new file mode 100644
index 0000000..bf15060
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeShaderApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FNEGATIVESHADERAPITESTS_HPP
+#define _ES3FNEGATIVESHADERAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Shader API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class NegativeShaderApiTests : public TestCaseGroup
+{
+public:
+									NegativeShaderApiTests		(Context& context);
+									~NegativeShaderApiTests		(void);
+
+	void							init						(void);
+
+private:
+									NegativeShaderApiTests		(const NegativeShaderApiTests& other);
+	NegativeShaderApiTests&			operator=					(const NegativeShaderApiTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FNEGATIVESHADERAPITESTS_HPP
diff --git a/modules/gles3/functional/es3fNegativeStateApiTests.cpp b/modules/gles3/functional/es3fNegativeStateApiTests.cpp
new file mode 100644
index 0000000..958f523
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeStateApiTests.cpp
@@ -0,0 +1,1327 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative GL State API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fNegativeStateApiTests.hpp"
+#include "es3fApiCase.hpp"
+#include "gluShaderProgram.hpp"
+#include "deMemory.h"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+
+static const char* uniformTestVertSource	=	"#version 300 es\n"
+												"uniform mediump vec4 vUnif_vec4;\n"
+												"in mediump vec4 attr;"
+												"layout(shared) uniform Block { mediump vec4 blockVar; };\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vUnif_vec4 + blockVar + attr;\n"
+												"}\n\0";
+static const char* uniformTestFragSource	=	"#version 300 es\n"
+												"uniform mediump ivec4 fUnif_ivec4;\n"
+												"uniform mediump uvec4 fUnif_uvec4;\n"
+												"layout(location = 0) out mediump vec4 fragColor;"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(vec4(fUnif_ivec4) + vec4(fUnif_uvec4));\n"
+												"}\n\0";
+
+NegativeStateApiTests::NegativeStateApiTests (Context& context)
+	: TestCaseGroup(context, "state", "Negative GL State API Cases")
+{
+}
+
+NegativeStateApiTests::~NegativeStateApiTests (void)
+{
+}
+
+void NegativeStateApiTests::init (void)
+{
+	// Enabling & disabling states
+
+	ES3F_ADD_API_CASE(enable, "Invalid glEnable() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if cap is not one of the allowed values.");
+			glEnable(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(disable, "Invalid glDisable() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if cap is not one of the allowed values.");
+			glDisable(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Simple state queries
+
+	ES3F_ADD_API_CASE(get_booleanv, "Invalid glGetBooleanv() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+			GLboolean params = GL_FALSE;
+			glGetBooleanv(-1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_floatv, "Invalid glGetFloatv() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+			GLfloat params = 0.0f;
+			glGetFloatv(-1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_integerv, "Invalid glGetIntegerv() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+			GLint params = -1;
+			glGetIntegerv(-1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_integer64v, "Invalid glGetInteger64v() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+			GLint64 params = -1;
+			glGetInteger64v(-1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_integeri_v, "Invalid glGetIntegeri_v() usage",
+		{
+			GLint data = -1;
+			GLint maxUniformBufferBindings;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if name is not an accepted value.");
+			glGetIntegeri_v(-1, 0, &data);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is outside of the valid range for the indexed state target.");
+			glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings);
+			expectError(GL_NO_ERROR);
+			glGetIntegeri_v(GL_UNIFORM_BUFFER_BINDING, maxUniformBufferBindings, &data);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_integer64i_v, "Invalid glGetInteger64i_v() usage",
+		{
+			GLint64 data = (GLint64)-1;;
+			GLint maxUniformBufferBindings;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if name is not an accepted value.");
+			glGetInteger64i_v(-1, 0, &data);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is outside of the valid range for the indexed state target.");
+			glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings);
+			expectError(GL_NO_ERROR);
+			glGetInteger64i_v(GL_UNIFORM_BUFFER_START, maxUniformBufferBindings, &data);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_string, "Invalid glGetString() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if name is not an accepted value.");
+			glGetString(-1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_stringi, "Invalid glGetStringi() usage",
+		{
+			GLint numExtensions;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if name is not an accepted value.");
+			glGetStringi(-1, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is outside the valid range for indexed state name.");
+			glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
+			glGetStringi(GL_EXTENSIONS, numExtensions);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+
+	// Enumerated state queries: Shaders
+
+	ES3F_ADD_API_CASE(get_attached_shaders, "Invalid glGetAttachedShaders() usage",
+		{
+			GLuint shaders[1];
+			GLuint shaderObject = glCreateShader(GL_VERTEX_SHADER);
+			GLuint program		= glCreateProgram();
+			GLsizei count[1];
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetAttachedShaders(-1, 1, &count[0], &shaders[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetAttachedShaders(shaderObject, 1, &count[0], &shaders[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if maxCount is less than 0.");
+			glGetAttachedShaders(program, -1, &count[0], &shaders[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shaderObject);
+			glDeleteProgram(program);
+		});
+	ES3F_ADD_API_CASE(get_shaderiv, "Invalid glGetShaderiv() usage",
+		{
+			GLboolean shaderCompilerSupported;
+			glGetBooleanv(GL_SHADER_COMPILER, &shaderCompilerSupported);
+			m_log << TestLog::Message << "// GL_SHADER_COMPILER = " << (shaderCompilerSupported ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+			GLuint program	= glCreateProgram();
+			GLint param[1]	= { -1 };
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetShaderiv(shader, -1, &param[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glGetShaderiv(-1, GL_SHADER_TYPE, &param[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader does not refer to a shader object.");
+			glGetShaderiv(program, GL_SHADER_TYPE, &param[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+			glDeleteProgram(program);
+		});
+	ES3F_ADD_API_CASE(get_shader_info_log, "Invalid glGetShaderInfoLog() usage",
+		{
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+			GLuint program	= glCreateProgram();
+			GLsizei length[1];
+			char infoLog[128];
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glGetShaderInfoLog(-1, 128, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			glGetShaderInfoLog(program, 128, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if maxLength is less than 0.");
+			glGetShaderInfoLog(shader, -1, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+			glDeleteProgram(program);
+		});
+	ES3F_ADD_API_CASE(get_shader_precision_format, "Invalid glGetShaderPrecisionFormat() usage",
+		{
+			GLboolean shaderCompilerSupported;
+			glGetBooleanv(GL_SHADER_COMPILER, &shaderCompilerSupported);
+			m_log << TestLog::Message << "// GL_SHADER_COMPILER = " << (shaderCompilerSupported ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+
+			GLint range[2];
+			GLint precision[1];
+
+			deMemset(&range[0], 0xcd, sizeof(range));
+			deMemset(&precision[0], 0xcd, sizeof(precision));
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if shaderType or precisionType is not an accepted value.");
+			glGetShaderPrecisionFormat (-1, GL_MEDIUM_FLOAT, &range[0], &precision[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetShaderPrecisionFormat (GL_VERTEX_SHADER, -1, &range[0], &precision[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetShaderPrecisionFormat (-1, -1, &range[0], &precision[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_shader_source, "Invalid glGetShaderSource() usage",
+		{
+			GLsizei length[1];
+			char source[1];
+			GLuint program	= glCreateProgram();
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+			glGetShaderSource(-1, 1, &length[0], &source[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if shader is not a shader object.");
+			glGetShaderSource(program, 1, &length[0], &source[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if bufSize is less than 0.");
+			glGetShaderSource(shader, -1, &length[0], &source[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+
+	// Enumerated state queries: Programs
+
+	ES3F_ADD_API_CASE(get_programiv, "Invalid glGetProgramiv() usage",
+		{
+			GLuint program	= glCreateProgram();
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+			GLint params[1]	= { -1 };
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetProgramiv(program, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetProgramiv(-1, GL_LINK_STATUS, &params[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program does not refer to a program object.");
+			glGetProgramiv(shader, GL_LINK_STATUS, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(get_program_info_log, "Invalid glGetProgramInfoLog() usage",
+		{
+			GLuint program	= glCreateProgram();
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+			GLsizei length[1];
+			char infoLog[1];
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetProgramInfoLog (-1, 1, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetProgramInfoLog (shader, 1, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if maxLength is less than 0.");
+			glGetProgramInfoLog (program, -1, &length[0], &infoLog[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+
+	// Enumerated state queries: Shader variables
+
+	ES3F_ADD_API_CASE(get_tex_parameterfv, "Invalid glGetTexParameterfv() usage",
+		{
+			GLfloat params[1];
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not an accepted value.");
+			glGetTexParameterfv (-1, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetTexParameterfv (GL_TEXTURE_2D, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetTexParameterfv (-1, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_tex_parameteriv, "Invalid glGetTexParameteriv() usage",
+		{
+			GLint params[1];
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not an accepted value.");
+			glGetTexParameteriv (-1, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetTexParameteriv (GL_TEXTURE_2D, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glGetTexParameteriv (-1, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_uniformfv, "Invalid glGetUniformfv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			GLint unif = glGetUniformLocation(program.getProgram(), "vUnif_vec4");	// vec4
+			if (unif == -1)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+
+			GLuint shader		= glCreateShader(GL_VERTEX_SHADER);
+			GLuint programEmpty = glCreateProgram();
+			GLfloat params[4];
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetUniformfv (-1, unif, &params[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetUniformfv (shader, unif, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+			glGetUniformfv (programEmpty, unif, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if location does not correspond to a valid uniform variable location for the specified program object.");
+			glGetUniformfv (program.getProgram(), -1, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+			glDeleteProgram(programEmpty);
+		});
+	ES3F_ADD_API_CASE(get_uniformiv, "Invalid glGetUniformiv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			GLint unif = glGetUniformLocation(program.getProgram(), "fUnif_ivec4");	// ivec4
+			if (unif == -1)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+
+			GLuint shader		= glCreateShader(GL_VERTEX_SHADER);
+			GLuint programEmpty = glCreateProgram();
+			GLint params[4];
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetUniformiv (-1, unif, &params[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetUniformiv (shader, unif, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+			glGetUniformiv (programEmpty, unif, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if location does not correspond to a valid uniform variable location for the specified program object.");
+			glGetUniformiv (program.getProgram(), -1, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+			glDeleteProgram(programEmpty);
+		});
+	ES3F_ADD_API_CASE(get_uniformuiv, "Invalid glGetUniformuiv() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			glUseProgram(program.getProgram());
+
+			GLint unif = glGetUniformLocation(program.getProgram(), "fUnif_uvec4");	// uvec4
+			if (unif == -1)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to retrieve uniform location");
+
+			GLuint shader		= glCreateShader(GL_VERTEX_SHADER);
+			GLuint programEmpty = glCreateProgram();
+			GLuint params[4];
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetUniformuiv (-1, unif, &params[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetUniformuiv (shader, unif, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+			glGetUniformuiv (programEmpty, unif, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if location does not correspond to a valid uniform variable location for the specified program object.");
+			glGetUniformuiv (program.getProgram(), -1, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteShader(shader);
+			glDeleteProgram(programEmpty);
+		});
+	ES3F_ADD_API_CASE(get_active_uniform, "Invalid glGetActiveUniform() usage",
+		{
+			GLuint				shader				= glCreateShader(GL_VERTEX_SHADER);
+			glu::ShaderProgram	program				(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			GLint				numActiveUniforms	= -1;
+
+			glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORMS,	&numActiveUniforms);
+			m_log << TestLog::Message << "// GL_ACTIVE_UNIFORMS = " << numActiveUniforms << " (expected 4)." << TestLog::EndMessage;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetActiveUniform(-1, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetActiveUniform(shader, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to the number of active uniform variables in program.");
+			glUseProgram(program.getProgram());
+			glGetActiveUniform(program.getProgram(), numActiveUniforms, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if bufSize is less than 0.");
+			glGetActiveUniform(program.getProgram(), 0, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(get_active_uniformsiv, "Invalid glGetActiveUniformsiv() usage",
+		{
+			GLuint					shader				= glCreateShader(GL_VERTEX_SHADER);
+			glu::ShaderProgram		program				(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			GLuint					dummyUniformIndex	= 1;
+			GLint					dummyParamDst		= -1;
+			GLint					numActiveUniforms	= -1;
+
+			glUseProgram(program.getProgram());
+
+			glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORMS, &numActiveUniforms);
+			m_log << TestLog::Message << "// GL_ACTIVE_UNIFORMS = " << numActiveUniforms << " (expected 4)." << TestLog::EndMessage;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetActiveUniformsiv(-1, 1, &dummyUniformIndex, GL_UNIFORM_TYPE, &dummyParamDst);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetActiveUniformsiv(shader, 1, &dummyUniformIndex, GL_UNIFORM_TYPE, &dummyParamDst);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if any value in uniformIndices is greater than or equal to the value of GL_ACTIVE_UNIFORMS for program.");
+			for (int excess = 0; excess <= 2; excess++)
+			{
+				std::vector<GLuint> invalidUniformIndices;
+				invalidUniformIndices.push_back(1);
+				invalidUniformIndices.push_back(numActiveUniforms-1+excess);
+				invalidUniformIndices.push_back(1);
+
+				std::vector<GLint> dummyParamsDst(invalidUniformIndices.size());
+				glGetActiveUniformsiv(program.getProgram(), (GLsizei)invalidUniformIndices.size(), &invalidUniformIndices[0], GL_UNIFORM_TYPE, &dummyParamsDst[0]);
+				expectError(excess == 0 ? GL_NO_ERROR : GL_INVALID_VALUE);
+			}
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted token.");
+			glGetActiveUniformsiv(program.getProgram(), 1, &dummyUniformIndex, -1, &dummyParamDst);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(get_active_uniform_blockiv, "Invalid glGetActiveUniformBlockiv() usage",
+		{
+			glu::ShaderProgram	program			(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			GLint				params			= -1;
+			GLint				numActiveBlocks	= -1;
+
+			glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORM_BLOCKS,	&numActiveBlocks);
+			m_log << TestLog::Message << "// GL_ACTIVE_UNIFORM_BLOCKS = " << numActiveBlocks << " (expected 1)." << TestLog::EndMessage;
+			expectError		(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if uniformBlockIndex is greater than or equal to the value of GL_ACTIVE_UNIFORM_BLOCKS or is not the index of an active uniform block in program.");
+			glUseProgram(program.getProgram());
+			expectError(GL_NO_ERROR);
+			glGetActiveUniformBlockiv(program.getProgram(), numActiveBlocks, GL_UNIFORM_BLOCK_BINDING, &params);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the accepted tokens.");
+			glGetActiveUniformBlockiv(program.getProgram(), 0, -1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(get_active_uniform_block_name, "Invalid glGetActiveUniformBlockName() usage",
+		{
+			glu::ShaderProgram	program			(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			GLsizei				length			= -1;
+			GLint				numActiveBlocks	= -1;
+			GLchar				uniformBlockName[128];
+
+			deMemset(&uniformBlockName[0], 0, sizeof(uniformBlockName));
+
+			glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORM_BLOCKS,	&numActiveBlocks);
+			m_log << TestLog::Message << "// GL_ACTIVE_UNIFORM_BLOCKS = " << numActiveBlocks << " (expected 1)." << TestLog::EndMessage;
+			expectError		(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if uniformBlockIndex is greater than or equal to the value of GL_ACTIVE_UNIFORM_BLOCKS or is not the index of an active uniform block in program.");
+			glUseProgram(program.getProgram());
+			expectError(GL_NO_ERROR);
+			glGetActiveUniformBlockName(program.getProgram(), numActiveBlocks, (int)sizeof(uniformBlockName), &length, &uniformBlockName[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(get_active_attrib, "Invalid glGetActiveAttrib() usage",
+		{
+			GLuint				shader				= glCreateShader(GL_VERTEX_SHADER);
+			glu::ShaderProgram	program				(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			GLint				numActiveAttributes	= -1;
+
+			GLsizei				length				= -1;
+			GLint				size				= -1;
+			GLenum				type				= -1;
+			GLchar				name[32];
+
+			deMemset(&name[0], 0, sizeof(name));
+
+			glGetProgramiv	(program.getProgram(), GL_ACTIVE_ATTRIBUTES,	&numActiveAttributes);
+			m_log << TestLog::Message << "// GL_ACTIVE_ATTRIBUTES = " << numActiveAttributes << " (expected 1)." << TestLog::EndMessage;
+
+			glUseProgram(program.getProgram());
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+			glGetActiveAttrib(-1, 0, 32, &length, &size, &type, &name[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is not a program object.");
+			glGetActiveAttrib(shader, 0, 32, &length, &size, &type, &name[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_ACTIVE_ATTRIBUTES.");
+			glGetActiveAttrib(program.getProgram(), numActiveAttributes, (int)sizeof(name), &length, &size, &type, &name[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if bufSize is less than 0.");
+			glGetActiveAttrib(program.getProgram(), 0, -1, &length, &size, &type, &name[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(get_uniform_indices, "Invalid glGetUniformIndices() usage",
+		{
+			GLuint shader			= glCreateShader(GL_VERTEX_SHADER);
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+			GLint numActiveBlocks = -1;
+			const GLchar* uniformName =  "Block.blockVar";
+			GLuint uniformIndices = -1;
+
+			glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORM_BLOCKS,	&numActiveBlocks);
+			m_log << TestLog::Message << "// GL_ACTIVE_UNIFORM_BLOCKS = "		<< numActiveBlocks			<< TestLog::EndMessage;
+			expectError		(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is a name of shader object.");
+			glGetUniformIndices(shader, 1, &uniformName, &uniformIndices);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if program is not name of program or shader object.");
+			GLuint invalid = -1;
+			glGetUniformIndices(invalid, 1, &uniformName, &uniformIndices);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glUseProgram(0);
+			glDeleteShader(shader);
+		});
+	ES3F_ADD_API_CASE(get_vertex_attribfv, "Invalid glGetVertexAttribfv() usage",
+		{
+			GLfloat params = 0.0f;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetVertexAttribfv(0, -1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			GLint maxVertexAttribs;
+			glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+			glGetVertexAttribfv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &params);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_vertex_attribiv, "Invalid glGetVertexAttribiv() usage",
+		{
+			GLint params = -1;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetVertexAttribiv(0, -1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			GLint maxVertexAttribs;
+			glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+			glGetVertexAttribiv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &params);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_vertex_attribi_iv, "Invalid glGetVertexAttribIiv() usage",
+		{
+			GLint params = -1;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetVertexAttribIiv(0, -1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			GLint maxVertexAttribs;
+			glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+			glGetVertexAttribIiv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &params);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_vertex_attribi_uiv, "Invalid glGetVertexAttribIuiv() usage",
+		{
+			GLuint params = (GLuint)-1;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetVertexAttribIuiv(0, -1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			GLint maxVertexAttribs;
+			glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+			glGetVertexAttribIuiv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &params);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_vertex_attrib_pointerv, "Invalid glGetVertexAttribPointerv() usage",
+		{
+			GLvoid* ptr[1] = { DE_NULL };
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetVertexAttribPointerv(0, -1, &ptr[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			GLint maxVertexAttribs;
+			glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+			glGetVertexAttribPointerv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_POINTER, &ptr[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_frag_data_location, "Invalid glGetFragDataLocation() usage",
+		{
+			GLuint shader	= glCreateShader(GL_VERTEX_SHADER);
+			GLuint program	= glCreateProgram();
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program is the name of a shader object.");
+			glGetFragDataLocation(shader, "gl_FragColor");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if program has not been linked.");
+			glGetFragDataLocation(program, "gl_FragColor");
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteProgram(program);
+			glDeleteShader(shader);
+		});
+
+	// Enumerated state queries: Buffers
+
+	ES3F_ADD_API_CASE(get_buffer_parameteriv, "Invalid glGetBufferParameteriv() usage",
+		{
+			GLint params = -1;
+			GLuint buf;
+			glGenBuffers(1, &buf);
+			glBindBuffer(GL_ARRAY_BUFFER, buf);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or value is not an accepted value.");
+			glGetBufferParameteriv(-1, GL_BUFFER_SIZE, &params);
+			expectError(GL_INVALID_ENUM);
+			glGetBufferParameteriv(GL_ARRAY_BUFFER, -1, &params);
+			expectError(GL_INVALID_ENUM);
+			glGetBufferParameteriv(-1, -1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &params);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &buf);
+		});
+	ES3F_ADD_API_CASE(get_buffer_parameteri64v, "Invalid glGetBufferParameteri64v() usage",
+		{
+			GLint64 params = -1;
+			GLuint buf;
+			glGenBuffers(1, &buf);
+			glBindBuffer(GL_ARRAY_BUFFER, buf);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or value is not an accepted value.");
+			glGetBufferParameteri64v(-1, GL_BUFFER_SIZE, &params);
+			expectError(GL_INVALID_ENUM);
+			glGetBufferParameteri64v(GL_ARRAY_BUFFER , -1, &params);
+			expectError(GL_INVALID_ENUM);
+			glGetBufferParameteri64v(-1, -1, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			glGetBufferParameteri64v(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &params);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &buf);
+		});
+	ES3F_ADD_API_CASE(get_buffer_pointerv, "Invalid glGetBufferPointerv() usage",
+		{
+			GLvoid* params = DE_NULL;
+			GLuint buf;
+			glGenBuffers(1, &buf);
+			glBindBuffer(GL_ARRAY_BUFFER, buf);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not an accepted value.");
+			glGetBufferPointerv(GL_ARRAY_BUFFER, -1, &params);
+			expectError(GL_INVALID_ENUM);
+			glGetBufferPointerv(-1, GL_BUFFER_MAP_POINTER, &params);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			glGetBufferPointerv(GL_ARRAY_BUFFER, GL_BUFFER_MAP_POINTER, &params);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers(1, &buf);
+		});
+	ES3F_ADD_API_CASE(get_framebuffer_attachment_parameteriv, "Invalid glGetFramebufferAttachmentParameteriv() usage",
+		{
+			GLint params[1] = { -1 };
+			GLuint fbo;
+			GLuint rbo[2];
+
+			glGenFramebuffers			(1, &fbo);
+			glGenRenderbuffers			(2, rbo);
+
+			glBindFramebuffer			(GL_FRAMEBUFFER,	fbo);
+			glBindRenderbuffer			(GL_RENDERBUFFER,	rbo[0]);
+			glRenderbufferStorage		(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 16, 16);
+			glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo[0]);
+			glBindRenderbuffer			(GL_RENDERBUFFER,	rbo[1]);
+			glRenderbufferStorage		(GL_RENDERBUFFER, GL_STENCIL_INDEX8, 16, 16);
+			glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo[1]);
+			glCheckFramebufferStatus	(GL_FRAMEBUFFER);
+			expectError					(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+			glGetFramebufferAttachmentParameteriv(-1, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &params[0]);					// TYPE is GL_RENDERBUFFER
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not valid for the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE.");
+			glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &params[0]);	// TYPE is GL_RENDERBUFFER
+			expectError(GL_INVALID_ENUM);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_BACK, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);					// TYPE is GL_FRAMEBUFFER_DEFAULT
+			expectError(GL_INVALID_ENUM);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if attachment is GL_DEPTH_STENCIL_ATTACHMENT and different objects are bound to the depth and stencil attachment points of target.");
+			glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE and pname is not GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME.");
+			glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);		// TYPE is GL_NONE
+			expectError(GL_NO_ERROR);
+			glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &params[0]);	// TYPE is GL_NONE
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION or GL_INVALID_ENUM is generated if attachment is not one of the accepted values for the current binding of target.");
+			glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_BACK, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);					// A FBO is bound so GL_BACK is invalid
+			expectError(GL_INVALID_OPERATION, GL_INVALID_ENUM);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);		// Default framebuffer is bound so GL_COLOR_ATTACHMENT0 is invalid
+			expectError(GL_INVALID_OPERATION, GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteFramebuffers(1, &fbo);
+		});
+	ES3F_ADD_API_CASE(get_renderbuffer_parameteriv, "Invalid glGetRenderbufferParameteriv() usage",
+		{
+			GLint params[1] = { -1 };
+			GLuint rbo;
+			glGenRenderbuffers(1, &rbo);
+			glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+			glGetRenderbufferParameteriv(-1, GL_RENDERBUFFER_WIDTH, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the accepted tokens.");
+			glGetRenderbufferParameteriv(GL_RENDERBUFFER, -1, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteRenderbuffers(1, &rbo);
+			glBindRenderbuffer(GL_RENDERBUFFER, 0);
+		});
+	ES3F_ADD_API_CASE(get_internalformativ, "Invalid glGetInternalformativ() usage",
+		{
+			GLint params[16];
+
+			deMemset(&params[0], 0xcd, sizeof(params));
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if bufSize is negative.");
+			glGetInternalformativ	(GL_RENDERBUFFER, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, -1, &params[0]);
+			expectError				(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not GL_SAMPLES or GL_NUM_SAMPLE_COUNTS.");
+			glGetInternalformativ	(GL_RENDERBUFFER, GL_RGBA8, -1, 16, &params[0]);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if internalformat is not color-, depth-, or stencil-renderable.");
+			glGetInternalformativ	(GL_RENDERBUFFER, GL_RG8_SNORM, GL_NUM_SAMPLE_COUNTS, 16, &params[0]);
+			expectError				(GL_INVALID_ENUM);
+			glGetInternalformativ	(GL_RENDERBUFFER, GL_COMPRESSED_RGB8_ETC2, GL_NUM_SAMPLE_COUNTS, 16, &params[0]);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+			glGetInternalformativ	(-1, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 16, &params[0]);
+			expectError				(GL_INVALID_ENUM);
+			glGetInternalformativ	(GL_FRAMEBUFFER, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 16, &params[0]);
+			expectError				(GL_INVALID_ENUM);
+			glGetInternalformativ	(GL_TEXTURE_2D, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 16, &params[0]);
+			expectError				(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Query object queries
+
+	ES3F_ADD_API_CASE(get_queryiv, "Invalid glGetQueryiv() usage",
+		{
+			GLint params = -1;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not an accepted value.");
+			glGetQueryiv	(GL_ANY_SAMPLES_PASSED, -1, &params);
+			expectError		(GL_INVALID_ENUM);
+			glGetQueryiv	(-1, GL_CURRENT_QUERY, &params);
+			expectError		(GL_INVALID_ENUM);
+			glGetQueryiv	(-1, -1, &params);
+			expectError		(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(get_query_objectuiv, "Invalid glGetQueryObjectuiv() usage",
+		{
+			GLuint params	= -1;
+			GLuint id;
+			glGenQueries		(1, &id);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if id is not the name of a query object.");
+			glGetQueryObjectuiv	(-1, GL_QUERY_RESULT_AVAILABLE, &params);
+			expectError			(GL_INVALID_OPERATION);
+			m_log << TestLog::Message << "// Note: " << id << " is not a query object yet, since it hasn't been used by glBeginQuery" << TestLog::EndMessage;
+			glGetQueryObjectuiv	(id, GL_QUERY_RESULT_AVAILABLE, &params);
+			expectError			(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glBeginQuery		(GL_ANY_SAMPLES_PASSED, id);
+			glEndQuery			(GL_ANY_SAMPLES_PASSED);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glGetQueryObjectuiv	(id, -1, &params);
+			expectError			(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if id is the name of a currently active query object.");
+			glBeginQuery		(GL_ANY_SAMPLES_PASSED, id);
+			expectError			(GL_NO_ERROR);
+			glGetQueryObjectuiv	(id, GL_QUERY_RESULT_AVAILABLE, &params);
+			expectError			(GL_INVALID_OPERATION);
+			glEndQuery			(GL_ANY_SAMPLES_PASSED);
+			expectError			(GL_NO_ERROR);
+			m_log << TestLog::EndSection;
+
+			glDeleteQueries		(1, &id);
+		});
+
+	// Sync object queries
+
+	ES3F_ADD_API_CASE(get_synciv, "Invalid glGetSynciv() usage",
+		{
+			GLsizei length	= -1;
+			GLint	values[32];
+			GLsync	sync;
+
+			deMemset(&values[0], 0xcd, sizeof(values));
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if sync is not the name of a sync object.");
+			glGetSynciv	(0, GL_OBJECT_TYPE, 32, &length, &values[0]);
+			expectError	(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not one of the accepted tokens.");
+			sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+			expectError	(GL_NO_ERROR);
+			glGetSynciv	(sync, -1, 32, &length, &values[0]);
+			expectError	(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Enumerated boolean state queries
+
+	ES3F_ADD_API_CASE(is_enabled, "Invalid glIsEnabled() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if cap is not an accepted value.");
+			glIsEnabled(-1);
+			expectError(GL_INVALID_ENUM);
+			glIsEnabled(GL_TRIANGLES);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Hints
+
+	ES3F_ADD_API_CASE(hint, "Invalid glHint() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if either target or mode is not an accepted value.");
+			glHint(GL_GENERATE_MIPMAP_HINT, -1);
+			expectError(GL_INVALID_ENUM);
+			glHint(-1, GL_FASTEST);
+			expectError(GL_INVALID_ENUM);
+			glHint(-1, -1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// Named Object Usage
+
+	ES3F_ADD_API_CASE(is_buffer, "Invalid glIsBuffer() usage",
+		{
+			GLuint		buffer = 0;
+			GLboolean	isBuffer;
+
+			m_log << TestLog::Section("", "A name returned by glGenBuffers, but not yet associated with a buffer object by calling glBindBuffer, is not the name of a buffer object.");
+			isBuffer		= glIsBuffer(buffer);
+			checkBooleans	(isBuffer, GL_FALSE);
+
+			glGenBuffers	(1, &buffer);
+			isBuffer		= glIsBuffer(buffer);
+			checkBooleans	(isBuffer, GL_FALSE);
+
+			glBindBuffer	(GL_ARRAY_BUFFER, buffer);
+			isBuffer		= glIsBuffer(buffer);
+			checkBooleans	(isBuffer, GL_TRUE);
+
+			glBindBuffer	(GL_ARRAY_BUFFER, 0);
+			glDeleteBuffers	(1, &buffer);
+			isBuffer		= glIsBuffer(buffer);
+			checkBooleans	(isBuffer, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(is_framebuffer, "Invalid glIsFramebuffer() usage",
+		{
+			GLuint		fbo = 0;
+			GLboolean	isFbo;
+
+			m_log << TestLog::Section("", "A name returned by glGenFramebuffers, but not yet bound through a call to glBindFramebuffer is not the name of a framebuffer object.");
+			isFbo				= glIsFramebuffer(fbo);
+			checkBooleans		(isFbo, GL_FALSE);
+
+			glGenFramebuffers	(1, &fbo);
+			isFbo				= glIsFramebuffer(fbo);
+			checkBooleans		(isFbo, GL_FALSE);
+
+			glBindFramebuffer	(GL_FRAMEBUFFER, fbo);
+			isFbo				= glIsFramebuffer(fbo);
+			checkBooleans		(isFbo, GL_TRUE);
+
+			glBindFramebuffer	(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			isFbo				= glIsFramebuffer(fbo);
+			checkBooleans		(isFbo, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(is_program, "Invalid glIsProgram() usage",
+		{
+			GLuint		program = 0;
+			GLboolean	isProgram;
+
+			m_log << TestLog::Section("", "A name created with glCreateProgram, and not yet deleted with glDeleteProgram is a name of a program object.");
+			isProgram			= glIsProgram(program);
+			checkBooleans		(isProgram, GL_FALSE);
+
+			program				= glCreateProgram();
+			isProgram			= glIsProgram(program);
+			checkBooleans		(isProgram, GL_TRUE);
+
+			glDeleteProgram		(program);
+			isProgram			= glIsProgram(program);
+			checkBooleans		(isProgram, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(is_renderbuffer, "Invalid glIsRenderbuffer() usage",
+		{
+			GLuint		rbo = 0;
+			GLboolean	isRbo;
+
+			m_log << TestLog::Section("", "A name returned by glGenRenderbuffers, but not yet bound through a call to glBindRenderbuffer or glFramebufferRenderbuffer is not the name of a renderbuffer object.");
+			isRbo					= glIsRenderbuffer(rbo);
+			checkBooleans			(isRbo, GL_FALSE);
+
+			glGenRenderbuffers		(1, &rbo);
+			isRbo					= glIsRenderbuffer(rbo);
+			checkBooleans			(isRbo, GL_FALSE);
+
+			glBindRenderbuffer		(GL_RENDERBUFFER, rbo);
+			isRbo					= glIsRenderbuffer(rbo);
+			checkBooleans			(isRbo, GL_TRUE);
+
+			glBindRenderbuffer		(GL_RENDERBUFFER, 0);
+			glDeleteRenderbuffers	(1, &rbo);
+			isRbo					= glIsRenderbuffer(rbo);
+			checkBooleans			(isRbo, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(is_shader, "Invalid glIsShader() usage",
+		{
+			GLuint		shader = 0;
+			GLboolean	isShader;
+
+			m_log << TestLog::Section("", "A name created with glCreateShader, and not yet deleted with glDeleteShader is a name of a shader object.");
+			isShader			= glIsProgram(shader);
+			checkBooleans		(isShader, GL_FALSE);
+
+			shader				= glCreateShader(GL_VERTEX_SHADER);
+			isShader			= glIsShader(shader);
+			checkBooleans		(isShader, GL_TRUE);
+
+			glDeleteShader		(shader);
+			isShader			= glIsShader(shader);
+			checkBooleans		(isShader, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(is_texture, "Invalid glIsTexture() usage",
+		{
+			GLuint		texture = 0;
+			GLboolean	isTexture;
+
+			m_log << TestLog::Section("", "A name returned by glGenTextures, but not yet bound through a call to glBindTexture is not the name of a texture.");
+			isTexture			= glIsTexture(texture);
+			checkBooleans		(isTexture, GL_FALSE);
+
+			glGenTextures		(1, &texture);
+			isTexture			= glIsTexture(texture);
+			checkBooleans		(isTexture, GL_FALSE);
+
+			glBindTexture		(GL_TEXTURE_2D, texture);
+			isTexture			= glIsTexture(texture);
+			checkBooleans		(isTexture, GL_TRUE);
+
+			glBindTexture		(GL_TEXTURE_2D, 0);
+			glDeleteTextures	(1, &texture);
+			isTexture			= glIsTexture(texture);
+			checkBooleans		(isTexture, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(is_query, "Invalid glIsQuery() usage",
+		{
+			GLuint		query = 0;
+			GLboolean	isQuery;
+
+			m_log << TestLog::Section("", "A name returned by glGenQueries, but not yet associated with a query object by calling glBeginQuery, is not the name of a query object.");
+			isQuery				= glIsQuery(query);
+			checkBooleans		(isQuery, GL_FALSE);
+
+			glGenQueries		(1, &query);
+			isQuery				= glIsQuery(query);
+			checkBooleans		(isQuery, GL_FALSE);
+
+			glBeginQuery		(GL_ANY_SAMPLES_PASSED, query);
+			isQuery				= glIsQuery(query);
+			checkBooleans		(isQuery, GL_TRUE);
+
+			glEndQuery			(GL_ANY_SAMPLES_PASSED);
+			glDeleteQueries		(1, &query);
+			isQuery				= glIsQuery(query);
+			checkBooleans		(isQuery, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(is_sampler, "Invalid glIsSampler() usage",
+		{
+			GLuint		sampler = 0;
+			GLboolean	isSampler;
+
+			m_log << TestLog::Section("", "A name returned by glGenSamplers is the name of a sampler object.");
+			isSampler			= glIsSampler(sampler);
+			checkBooleans		(isSampler, GL_FALSE);
+
+			glGenSamplers		(1, &sampler);
+			isSampler			= glIsSampler(sampler);
+			checkBooleans		(isSampler, GL_TRUE);
+
+			glBindSampler		(0, sampler);
+			isSampler			= glIsSampler(sampler);
+			checkBooleans		(isSampler, GL_TRUE);
+
+			glDeleteSamplers	(1, &sampler);
+			isSampler			= glIsSampler(sampler);
+			checkBooleans		(isSampler, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(is_sync, "Invalid glIsSync() usage",
+		{
+			GLsync		sync = 0;
+			GLboolean	isSync;
+
+			m_log << TestLog::Section("", "A name returned by glFenceSync is the name of a sync object.");
+			isSync			= glIsSync(sync);
+			checkBooleans	(isSync, GL_FALSE);
+
+			sync			= glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+			isSync			= glIsSync(sync);
+			checkBooleans	(isSync, GL_TRUE);
+
+			glDeleteSync	(sync);
+			isSync			= glIsSync(sync);
+			checkBooleans	(isSync, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(is_transform_feedback, "Invalid glIsTransformFeedback() usage",
+		{
+			GLuint		tf = 0;
+			GLboolean	isTF;
+
+			m_log << TestLog::Section("", "A name returned by glGenTransformFeedbacks, but not yet bound using glBindTransformFeedback, is not the name of a transform feedback object.");
+			isTF						= glIsTransformFeedback(tf);
+			checkBooleans				(isTF, GL_FALSE);
+
+			glGenTransformFeedbacks		(1, &tf);
+			isTF						= glIsTransformFeedback(tf);
+			checkBooleans				(isTF, GL_FALSE);
+
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tf);
+			isTF						= glIsTransformFeedback(tf);
+			checkBooleans				(isTF, GL_TRUE);
+
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, 0);
+			glDeleteTransformFeedbacks	(1, &tf);
+			isTF						= glIsTransformFeedback(tf);
+			checkBooleans				(isTF, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+	ES3F_ADD_API_CASE(is_vertex_array, "Invalid glIsVertexArray() usage",
+		{
+			GLuint		vao = 0;
+			GLboolean	isVao;
+
+			m_log << TestLog::Section("", "A name returned by glGenVertexArrays, but not yet bound using glBindVertexArray, is not the name of a vertex array object.");
+			isVao					= glIsVertexArray(vao);
+			checkBooleans			(isVao, GL_FALSE);
+
+			glGenVertexArrays			(1, &vao);
+			isVao					= glIsVertexArray(vao);
+			checkBooleans			(isVao, GL_FALSE);
+
+			glBindVertexArray			(vao);
+			isVao					= glIsVertexArray(vao);
+			checkBooleans			(isVao, GL_TRUE);
+
+			glBindVertexArray		(0);
+			glDeleteVertexArrays	(1, &vao);
+			isVao					= glIsVertexArray(vao);
+			checkBooleans			(isVao, GL_FALSE);
+			m_log << TestLog::EndSection;
+
+			expectError			(GL_NO_ERROR);
+		});
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fNegativeStateApiTests.hpp b/modules/gles3/functional/es3fNegativeStateApiTests.hpp
new file mode 100644
index 0000000..ea9d103
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeStateApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FNEGATIVESTATEAPITESTS_HPP
+#define _ES3FNEGATIVESTATEAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative GL State API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class NegativeStateApiTests : public TestCaseGroup
+{
+public:
+								NegativeStateApiTests		(Context& context);
+								~NegativeStateApiTests		(void);
+
+	void						init						(void);
+
+private:
+								NegativeStateApiTests		(const NegativeStateApiTests& other);
+	NegativeStateApiTests&		operator=					(const NegativeStateApiTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FNEGATIVESTATEAPITESTS_HPP
diff --git a/modules/gles3/functional/es3fNegativeTextureApiTests.cpp b/modules/gles3/functional/es3fNegativeTextureApiTests.cpp
new file mode 100644
index 0000000..91fdd71
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeTextureApiTests.cpp
@@ -0,0 +1,2964 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Texture API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fNegativeTextureApiTests.hpp"
+#include "es3fApiCase.hpp"
+#include "gluContextInfo.hpp"
+#include "tcuFormatUtil.hpp"
+
+#include <vector>
+#include <algorithm>
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+using std::vector;
+
+static inline int divRoundUp (int a, int b)
+{
+	return a/b + (a%b != 0 ? 1 : 0);
+}
+
+static inline int etc2DataSize (int width, int height)
+{
+	return (int)(divRoundUp(width, 4) * divRoundUp(height, 4) * sizeof(deUint64));
+}
+
+static inline int etc2EacDataSize (int width, int height)
+{
+	return 2 * etc2DataSize(width, height);
+}
+
+static deUint32 cubeFaceToGLFace (tcu::CubeFace face)
+{
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
+		case tcu::CUBEFACE_POSITIVE_X: return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
+		case tcu::CUBEFACE_NEGATIVE_Y: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
+		case tcu::CUBEFACE_POSITIVE_Y: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
+		case tcu::CUBEFACE_NEGATIVE_Z: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
+		case tcu::CUBEFACE_POSITIVE_Z: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return GL_NONE;
+	}
+}
+
+#define FOR_CUBE_FACES(FACE_GL_VAR, BODY)												\
+	do																					\
+	{																					\
+		for (int faceIterTcu = 0; faceIterTcu < tcu::CUBEFACE_LAST; faceIterTcu++)		\
+		{																				\
+			const GLenum FACE_GL_VAR = cubeFaceToGLFace((tcu::CubeFace)faceIterTcu);	\
+			BODY																		\
+		}																				\
+	} while (false)
+
+NegativeTextureApiTests::NegativeTextureApiTests (Context& context)
+	: TestCaseGroup(context, "texture", "Negative Texture API Cases")
+{
+}
+
+NegativeTextureApiTests::~NegativeTextureApiTests (void)
+{
+}
+
+void NegativeTextureApiTests::init (void)
+{
+	// glActiveTexture
+
+	ES3F_ADD_API_CASE(activetexture, "Invalid glActiveTexture() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if texture is not one of GL_TEXTUREi, where i ranges from 0 to (GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1).");
+			glActiveTexture(-1);
+			expectError(GL_INVALID_ENUM);
+			int numMaxTextureUnits = m_context.getContextInfo().getInt(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+			glActiveTexture(GL_TEXTURE0 + numMaxTextureUnits);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+
+	// glBindTexture
+
+	ES3F_ADD_API_CASE(bindtexture, "Invalid glBindTexture() usage",
+		{
+			GLuint texture[2];
+			glGenTextures(2, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the allowable values.");
+			glBindTexture(0, 1);
+			expectError(GL_INVALID_ENUM);
+			glBindTexture(GL_FRAMEBUFFER, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if texture was previously created with a target that doesn't match that of target.");
+			glBindTexture(GL_TEXTURE_2D, texture[0]);
+			expectError(GL_NO_ERROR);
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texture[0]);
+			expectError(GL_INVALID_OPERATION);
+			glBindTexture(GL_TEXTURE_3D, texture[0]);
+			expectError(GL_INVALID_OPERATION);
+			glBindTexture(GL_TEXTURE_2D_ARRAY, texture[0]);
+			expectError(GL_INVALID_OPERATION);
+
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texture[1]);
+			expectError(GL_NO_ERROR);
+			glBindTexture(GL_TEXTURE_2D, texture[1]);
+			expectError(GL_INVALID_OPERATION);
+			glBindTexture(GL_TEXTURE_3D, texture[1]);
+			expectError(GL_INVALID_OPERATION);
+			glBindTexture(GL_TEXTURE_2D_ARRAY, texture[1]);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(2, texture);
+		});
+
+	// glCompressedTexImage2D
+
+	ES3F_ADD_API_CASE(compressedteximage2d_invalid_target, "Invalid glCompressedTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glCompressedTexImage2D(0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage2d_invalid_format, "Invalid glCompressedTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if internalformat is not a supported format returned in GL_COMPRESSED_TEXTURE_FORMATS.");
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage2d_neg_level, "Invalid glCompressedTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glCompressedTexImage2D(GL_TEXTURE_2D, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage2d_max_level, "Invalid glCompressedTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE) for a 2d texture target.");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glCompressedTexImage2D(GL_TEXTURE_2D, log2MaxTextureSize, GL_COMPRESSED_RGB8_ETC2, 16, 16, 0, etc2DataSize(16, 16), 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE) for a cubemap target.");
+			deUint32 log2MaxCubemapSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage2d_neg_width_height, "Invalid glCompressedTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+
+			m_log << TestLog::Section("", "GL_TEXTURE_2D target");
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage2d_max_width_height, "Invalid glCompressedTexImage2D() usage",
+		{
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE) + 1;
+			int maxCubemapSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_TEXTURE_SIZE.");
+
+			m_log << TestLog::Section("", "GL_TEXTURE_2D target");
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxTextureSize, 1, 0, etc2EacDataSize(maxTextureSize, 1), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxTextureSize, 0, etc2EacDataSize(1, maxTextureSize), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxTextureSize, maxTextureSize, 0, etc2EacDataSize(maxTextureSize, maxTextureSize), 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage2d_invalid_border, "Invalid glCompressedTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+
+			m_log << TestLog::Section("", "GL_TEXTURE_2D target");
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage2d_invalid_size, "Invalid glCompressedTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if imageSize is not consistent with the format, dimensions, and contents of the specified compressed image data.");
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, 4*4*8, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, 16, 16, 0, 4*4*16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_SIGNED_R11_EAC, 16, 16, 0, 4*4*16, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage2d_invalid_buffer_target, "Invalid glCompressedTexImage2D() usage",
+		{
+			deUint32				buf;
+			std::vector<GLubyte>	data(64);
+
+			glGenBuffers			(1, &buf);
+			glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+			glBufferData			(GL_PIXEL_UNPACK_BUFFER, 64, &data[0], GL_DYNAMIC_COPY);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and the buffer object's data store is currently mapped.");
+			glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 32, GL_MAP_WRITE_BIT);
+			glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, 4, 4, 0, etc2DataSize(4, 4), 0);
+			expectError				(GL_INVALID_OPERATION);
+			glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+			glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, 16, 16, 0, etc2DataSize(16, 16), 0);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers			(1, &buf);
+		});
+
+	// glCopyTexImage2D
+
+	ES3F_ADD_API_CASE(copyteximage2d_invalid_target, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glCopyTexImage2D(0, 0, GL_RGB, 0, 0, 64, 64, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(copyteximage2d_invalid_format, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM or GL_INVALID_VALUE is generated if internalformat is not an accepted format.");
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 64, 64, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 0, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(copyteximage2d_inequal_width_height_cube, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if target is one of the six cube map 2D image targets and the width and height parameters are not equal.");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 16, 17, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(copyteximage2d_neg_level, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glCopyTexImage2D(GL_TEXTURE_2D, -1, GL_RGB, 0, 0, 64, 64, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(copyteximage2d_max_level, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glCopyTexImage2D(GL_TEXTURE_2D, log2MaxTextureSize, GL_RGB, 0, 0, 64, 64, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+			deUint32 log2MaxCubemapSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(copyteximage2d_neg_width_height, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+
+			m_log << TestLog::Section("", "GL_TEXTURE_2D target");
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, -1, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, -1, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(copyteximage2d_max_width_height, "Invalid glCopyTexImage2D() usage",
+		{
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE) + 1;
+			int maxCubemapSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_TEXTURE_SIZE.");
+
+			m_log << TestLog::Section("", "GL_TEXTURE_2D target");
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, maxTextureSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 1, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, maxTextureSize, maxTextureSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(copyteximage2d_invalid_border, "Invalid glCopyTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+
+			m_log << TestLog::Section("", "GL_TEXTURE_2D target");
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 0, 0, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 0, 0, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 0, 0, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_2D target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 0, 0, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 0, 0, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 0, 0, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 0, 0, 1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(copyteximage2d_incomplete_framebuffer, "Invalid glCopyTexImage2D() usage",
+		{
+			GLuint fbo;
+			glGenFramebuffers		(1, &fbo);
+			glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glBindFramebuffer	(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+		});
+
+	// glCopyTexSubImage2D
+
+	ES3F_ADD_API_CASE(copytexsubimage2d_invalid_target, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_2D, texture);
+			glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glCopyTexSubImage2D(0, 0, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage2d_neg_level, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint textures[2];
+			glGenTextures	(2, &textures[0]);
+			glBindTexture	(GL_TEXTURE_2D, textures[0]);
+			glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glBindTexture	(GL_TEXTURE_CUBE_MAP, textures[1]);
+			FOR_CUBE_FACES(faceGL, glTexImage2D(faceGL, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0););
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glCopyTexSubImage2D(GL_TEXTURE_2D, -1, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			FOR_CUBE_FACES(faceGL,
+			{
+				glCopyTexSubImage2D(faceGL, -1, 0, 0, 0, 0, 4, 4);
+				expectError(GL_INVALID_VALUE);
+			});
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(2, &textures[0]);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage2d_max_level, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint textures[2];
+			glGenTextures	(2, &textures[0]);
+			glBindTexture	(GL_TEXTURE_2D, textures[0]);
+			glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glBindTexture	(GL_TEXTURE_CUBE_MAP, textures[1]);
+			FOR_CUBE_FACES(faceGL, glTexImage2D(faceGL, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0););
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE) for 2D texture targets.");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glCopyTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_SIZE) for cubemap targets.");
+			deUint32 log2MaxCubemapSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			FOR_CUBE_FACES(faceGL,
+			{
+				glCopyTexSubImage2D(faceGL, log2MaxCubemapSize, 0, 0, 0, 0, 4, 4);
+				expectError(GL_INVALID_VALUE);
+			});
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(2, &textures[0]);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage2d_neg_offset, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_2D, texture);
+			glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset < 0 or yoffset < 0.");
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, -1, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, -1, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, -1, -1, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage2d_invalid_offset, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_2D, texture);
+			glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 14, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 14, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 14, 14, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage2d_neg_width_height, "Invalid glCopyTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_2D, texture);
+			glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, -1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage2d_incomplete_framebuffer, "Invalid glCopyTexSubImage2D() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+
+			GLuint texture[2];
+			GLuint fbo;
+
+			glGenTextures			(2, texture);
+			glBindTexture			(GL_TEXTURE_2D, texture[0]);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glBindTexture			(GL_TEXTURE_CUBE_MAP, texture[1]);
+			glTexImage2D			(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D			(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D			(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D			(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D			(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D			(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_NO_ERROR);
+
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			expectError(GL_NO_ERROR);
+
+			glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			glDeleteTextures(2, texture);
+
+			m_log << tcu::TestLog::EndSection;
+		});
+
+	// glDeleteTextures
+
+	ES3F_ADD_API_CASE(deletetextures, "Invalid glDeleteTextures() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteTextures(-1, 0);
+			expectError(GL_INVALID_VALUE);
+
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glDeleteTextures(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glGenerateMipmap
+
+	ES3F_ADD_API_CASE(generatemipmap, "Invalid glGenerateMipmap() usage",
+		{
+			GLuint texture[2];
+			glGenTextures(2, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP.");
+			glGenerateMipmap(0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "INVALID_OPERATION is generated if the texture bound to target is not cube complete.");
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texture[0]);
+			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_REPEAT);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+			expectError(GL_INVALID_OPERATION);
+
+			glBindTexture(GL_TEXTURE_CUBE_MAP, texture[0]);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the zero level array is stored in a compressed internal format.");
+			glBindTexture(GL_TEXTURE_2D, texture[1]);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+			glGenerateMipmap(GL_TEXTURE_2D);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the level base array was not specified with an unsized internal format or a sized internal format that is both color-renderable and texture-filterable.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8_SNORM, 0, 0, 0, GL_RGB, GL_BYTE, 0);
+			glGenerateMipmap(GL_TEXTURE_2D);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_R8I, 0, 0, 0, GL_RED_INTEGER, GL_BYTE, 0);
+			glGenerateMipmap(GL_TEXTURE_2D);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 0, 0, 0, GL_RGBA, GL_FLOAT, 0);
+			glGenerateMipmap(GL_TEXTURE_2D);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(2, texture);
+		});
+
+	// glGenTextures
+
+	ES3F_ADD_API_CASE(gentextures, "Invalid glGenTextures() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glGenTextures(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+
+	// glPixelStorei
+
+	ES3F_ADD_API_CASE(pixelstorei, "Invalid glPixelStorei() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if pname is not an accepted value.");
+			glPixelStorei(0,1);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if a negative row length, pixel skip, or row skip value is specified, or if alignment is specified as other than 1, 2, 4, or 8.");
+			glPixelStorei(GL_PACK_ROW_LENGTH, -1);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_PACK_SKIP_ROWS, -1);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_PACK_SKIP_PIXELS, -1);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_UNPACK_ROW_LENGTH, -1);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, -1);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_UNPACK_SKIP_ROWS, -1);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_UNPACK_SKIP_PIXELS, -1);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_UNPACK_SKIP_IMAGES, -1);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_PACK_ALIGNMENT, 0);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_UNPACK_ALIGNMENT, 0);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_PACK_ALIGNMENT, 16);
+			expectError(GL_INVALID_VALUE);
+			glPixelStorei(GL_UNPACK_ALIGNMENT, 16);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+
+	// glTexImage2D
+
+	ES3F_ADD_API_CASE(teximage2d, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glTexImage2D(0, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if type is not a type constant.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the combination of internalFormat, format and type is invalid.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, 1, 1, 0, GL_RGB, GL_UNSIGNED_INT_2_10_10_10_REV, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32UI, 1, 1, 0, GL_RGBA_INTEGER, GL_INT, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage2d_inequal_width_height_cube, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if target is one of the six cube map 2D image targets and the width and height parameters are not equal.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage2d_neg_level, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glTexImage2D(GL_TEXTURE_2D, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage2d_max_level, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glTexImage2D(GL_TEXTURE_2D, log2MaxTextureSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+			deUint32 log2MaxCubemapSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage2d_neg_width_height, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+
+			m_log << TestLog::Section("", "GL_TEXTURE_2D target");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage2d_max_width_height, "Invalid glTexImage2D() usage",
+		{
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE) + 1;
+			int maxCubemapSize = m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_TEXTURE_SIZE.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, maxTextureSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, maxTextureSize, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage2d_invalid_border, "Invalid glTexImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, -1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage2d_invalid_buffer_target, "Invalid glTexImage2D() usage",
+		{
+			deUint32				buf;
+			deUint32				texture;
+			std::vector<GLubyte>	data(64);
+
+			glGenBuffers			(1, &buf);
+			glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+			glBufferData			(GL_PIXEL_UNPACK_BUFFER, 64, &data[0], GL_DYNAMIC_COPY);
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+			m_log << TestLog::Section("", "...the buffer object's data store is currently mapped.");
+			glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 32, GL_MAP_WRITE_BIT);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError				(GL_INVALID_OPERATION);
+			glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "...data is not evenly divisible into the number of bytes needed to store in memory a datum indicated by type.");
+			m_log << TestLog::Message << "// Set byte offset = 3" << TestLog::EndMessage;
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGB5_A1, 4, 4, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (const GLvoid*)3);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers			(1, &buf);
+			glDeleteTextures		(1, &texture);
+		});
+
+	// glTexSubImage2D
+
+	ES3F_ADD_API_CASE(texsubimage2d, "Invalid glTexSubImage2D() usage",
+		{
+			deUint32			texture;
+			glGenTextures		(1, &texture);
+			glBindTexture		(GL_TEXTURE_2D, texture);
+			glTexImage2D		(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glTexSubImage2D(0, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if format is not an accepted format constant.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 4, 4, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if type is not a type constant.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the combination of internalFormat of the previously specified texture array, format and type is not valid.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_SHORT_5_6_5, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA_INTEGER, GL_UNSIGNED_INT, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, GL_FLOAT, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glDeleteTextures	(1, &texture);
+		});
+	ES3F_ADD_API_CASE(texsubimage2d_neg_level, "Invalid glTexSubImage2D() usage",
+		{
+			deUint32			textures[2];
+			glGenTextures		(2, &textures[0]);
+			glBindTexture		(GL_TEXTURE_2D, textures[0]);
+			glTexImage2D		(GL_TEXTURE_2D, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glBindTexture		(GL_TEXTURE_2D, textures[1]);
+			FOR_CUBE_FACES(faceGL, glTexImage2D(faceGL, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0););
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glTexSubImage2D(GL_TEXTURE_2D, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			FOR_CUBE_FACES(faceGL,
+			{
+				glTexSubImage2D(faceGL, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+				expectError(GL_INVALID_VALUE);
+			});
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(2, &textures[0]);
+		});
+	ES3F_ADD_API_CASE(texsubimage2d_max_level, "Invalid glTexSubImage2D() usage",
+		{
+			deUint32			textures[2];
+			glGenTextures		(2, &textures[0]);
+			glBindTexture		(GL_TEXTURE_2D, textures[0]);
+			glTexImage2D		(GL_TEXTURE_2D, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			glBindTexture		(GL_TEXTURE_CUBE_MAP, textures[1]);
+			FOR_CUBE_FACES(faceGL, glTexImage2D(faceGL, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0););
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+			deUint32 log2MaxCubemapSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			FOR_CUBE_FACES(faceGL,
+			{
+				glTexSubImage2D(faceGL, log2MaxCubemapSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+				expectError(GL_INVALID_VALUE);
+			});
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(2, &textures[0]);
+		});
+	ES3F_ADD_API_CASE(texsubimage2d_neg_offset, "Invalid glTexSubImage2D() usage",
+		{
+			deUint32 texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset or yoffset are negative.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, -1, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, -1, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, -1, -1, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(texsubimage2d_invalid_offset, "Invalid glTexSubImage2D() usage",
+		{
+			deUint32			texture;
+			glGenTextures		(1, &texture);
+			glBindTexture		(GL_TEXTURE_2D, texture);
+			glTexImage2D		(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 30, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 30, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 30, 30, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures	(1, &texture);
+		});
+	ES3F_ADD_API_CASE(texsubimage2d_neg_width_height, "Invalid glTexSubImage2D() usage",
+		{
+			deUint32			texture;
+			glGenTextures		(1, &texture);
+			glBindTexture		(GL_TEXTURE_2D, texture);
+			glTexImage2D		(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, -1, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -1, -1, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures	(1, &texture);
+		});
+	ES3F_ADD_API_CASE(texsubimage2d_invalid_buffer_target, "Invalid glTexSubImage2D() usage",
+		{
+			deUint32				buf;
+			deUint32				texture;
+			std::vector<GLubyte>	data(64);
+
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glGenBuffers			(1, &buf);
+			glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+			glBufferData			(GL_PIXEL_UNPACK_BUFFER, 64, &data[0], GL_DYNAMIC_COPY);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+			m_log << TestLog::Section("", "...the buffer object's data store is currently mapped.");
+			glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 32, GL_MAP_WRITE_BIT);
+			glTexSubImage2D			(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError				(GL_INVALID_OPERATION);
+			glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+			glTexSubImage2D			(GL_TEXTURE_2D, 0, 0, 0, 32, 32, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "...data is not evenly divisible into the number of bytes needed to store in memory a datum indicated by type.");
+			m_log << TestLog::Message << "// Set byte offset = 3" << TestLog::EndMessage;
+			glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, 0);
+			glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+			glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+			expectError				(GL_NO_ERROR);
+			glTexSubImage2D			(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (const GLvoid*)3);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers			(1, &buf);
+			glDeleteTextures		(1, &texture);
+		});
+
+	// glTexParameteri
+
+	ES3F_ADD_API_CASE(texparameteri, "Invalid glTexParameteri() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			glTexParameteri(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(0, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			glTexParameteri(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(0, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glTexParameterf
+
+	ES3F_ADD_API_CASE(texparameterf, "Invalid glTexParameterf() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			glTexParameterf(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(0, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			glTexParameterf(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(0, 0, GL_LINEAR);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glTexParameteriv
+
+	ES3F_ADD_API_CASE(texparameteriv, "Invalid glTexParameteriv() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			GLint params[1] = {GL_LINEAR};
+			glTexParameteriv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteriv(GL_TEXTURE_2D, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteriv(0, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			params[0] = 0;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_REPEAT;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = 0;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_NEAREST;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			params[0] = GL_LINEAR;
+			glTexParameteriv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteriv(GL_TEXTURE_2D, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameteriv(0, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			params[0] = 0;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_REPEAT;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = 0;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_NEAREST;
+			glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glTexParameterfv
+
+	ES3F_ADD_API_CASE(texparameterfv, "Invalid glTexParameterfv() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			GLfloat params[1] = {GL_LINEAR};
+			glTexParameterfv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterfv(GL_TEXTURE_2D, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterfv(0, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			params[0] = 0.0f;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_REPEAT;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = 0.0f;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_NEAREST;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+			params[0] = GL_LINEAR;
+			glTexParameterfv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterfv(GL_TEXTURE_2D, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			glTexParameterfv(0, 0, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+			params[0] = 0.0f;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_REPEAT;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = 0.0f;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			params[0] = GL_NEAREST;
+			glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glCompressedTexSubImage2D
+
+	ES3F_ADD_API_CASE(compressedtexsubimage2d, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glCompressedTexSubImage2D(0, 0, 0, 0, 0, 0, GL_COMPRESSED_RGB8_ETC2, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 0, etc2EacDataSize(18, 18), 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if format does not match the internal format of the texture image being modified.");
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_COMPRESSED_RGB8_ETC2, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "For ETC2/EAC images GL_INVALID_OPERATION is generated if width is not a multiple of four, and width + xoffset is not equal to the width of the texture level.");
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 4, 0, 10, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(10, 4), 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "For ETC2/EAC images GL_INVALID_OPERATION is generated if height is not a multiple of four, and height + yoffset is not equal to the height of the texture level.");
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 4, 4, 10, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 10), 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "For ETC2/EAC images GL_INVALID_OPERATION is generated if xoffset or yoffset is not a multiple of four.");
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 1, 4, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+			expectError(GL_INVALID_OPERATION);
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 1, 0, 4, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+			expectError(GL_INVALID_OPERATION);
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 4, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage2d_neg_level, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			deUint32				textures[2];
+			glGenTextures			(2, &textures[0]);
+			glBindTexture			(GL_TEXTURE_2D, textures[0]);
+			glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 0, etc2EacDataSize(18, 18), 0);
+			glBindTexture			(GL_TEXTURE_CUBE_MAP, textures[1]);
+			FOR_CUBE_FACES(faceGL, glCompressedTexImage2D(faceGL, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 0, etc2EacDataSize(18, 18), 0););
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, -1, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			FOR_CUBE_FACES(faceGL,
+			{
+				glCompressedTexSubImage2D(faceGL, -1, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+				expectError(GL_INVALID_VALUE);
+			});
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(2, &textures[0]);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage2d_max_level, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			deUint32				textures[2];
+			glGenTextures			(2, &textures[0]);
+			glBindTexture			(GL_TEXTURE_2D, textures[0]);
+			glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 0, etc2EacDataSize(18, 18), 0);
+			glBindTexture			(GL_TEXTURE_CUBE_MAP, textures[1]);
+			FOR_CUBE_FACES(faceGL, glCompressedTexImage2D(faceGL, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 0, etc2EacDataSize(18, 18), 0););
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+			deUint32 log2MaxCubemapSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+			FOR_CUBE_FACES(faceGL,
+			{
+				glCompressedTexSubImage2D(faceGL, log2MaxCubemapSize, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+				expectError(GL_INVALID_VALUE);
+			});
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(2, &textures[0]);
+		});
+		ES3F_ADD_API_CASE(compressedtexsubimage2d_neg_offset, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			GLuint texture;
+			glGenTextures(1, &texture);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 8, 8, 0, etc2EacDataSize(8, 8), 0);
+
+			// \note Both GL_INVALID_VALUE and GL_INVALID_OPERATION are valid here since implementation may
+			//		 first check if offsets are valid for certain format and only after that check that they
+			//		 are not negative.
+			m_log << TestLog::Section("", "GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if xoffset or yoffset are negative.");
+
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, -4, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, -4, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, -4, -4, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage2d_invalid_offset, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 12, 0, 8, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(8, 4), 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 12, 4, 8, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 8), 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 12, 12, 8, 8, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(8, 8), 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage2d_neg_width_height, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if width or height is less than 0.");
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -4, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, -4, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -4, -4, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1,		&texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage2d_invalid_size, "Invalid glCompressedTexImage2D() usage",
+		{
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D, texture);
+			glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if imageSize is not consistent with the format, dimensions, and contents of the specified compressed image data.");
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0);
+			expectError(GL_INVALID_VALUE);
+
+			glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 16, 16, GL_COMPRESSED_RGBA8_ETC2_EAC, 4*4*16-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage2d_invalid_buffer_target, "Invalid glCompressedTexSubImage2D() usage",
+		{
+			deUint32					buf;
+			deUint32					texture;
+			std::vector<GLubyte>		data(128);
+
+			glGenTextures				(1, &texture);
+			glBindTexture				(GL_TEXTURE_2D, texture);
+			glCompressedTexImage2D		(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+			glGenBuffers				(1, &buf);
+			glBindBuffer				(GL_PIXEL_UNPACK_BUFFER, buf);
+			glBufferData				(GL_PIXEL_UNPACK_BUFFER, 128, &data[0], GL_DYNAMIC_COPY);
+			expectError					(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+			m_log << TestLog::Section("", "...the buffer object's data store is currently mapped.");
+			glMapBufferRange			(GL_PIXEL_UNPACK_BUFFER, 0, 128, GL_MAP_WRITE_BIT);
+			glCompressedTexSubImage2D	(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+			expectError					(GL_INVALID_OPERATION);
+			glUnmapBuffer				(GL_PIXEL_UNPACK_BUFFER);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+			glCompressedTexSubImage2D	(GL_TEXTURE_2D, 0, 0, 0, 16, 16, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(16, 16), 0);
+			expectError					(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers			(1, &buf);
+			glDeleteTextures		(1, &texture);
+		});
+
+	// glTexImage3D
+
+	ES3F_ADD_API_CASE(teximage3d, "Invalid glTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glTexImage3D(0, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexImage3D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if type is not a type constant.");
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if format is not an accepted format constant.");
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, 0, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if internalFormat is not one of the accepted resolution and format symbolic constants.");
+			glTexImage3D(GL_TEXTURE_3D, 0, 0, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if target is GL_TEXTURE_3D and format is GL_DEPTH_COMPONENT, or GL_DEPTH_STENCIL.");
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the combination of internalFormat, format and type is invalid.");
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB5_A1, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB10_A2, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_INT_2_10_10_10_REV, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA32UI, 1, 1, 1, 0, GL_RGBA_INTEGER, GL_INT, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage3d_neg_level, "Invalid glTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glTexImage3D(GL_TEXTURE_3D, -1, GL_RGB, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, -1, GL_RGB, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage3d_max_level, "Invalid glTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_3D_TEXTURE_SIZE).");
+			deUint32 log2Max3DTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_3D_TEXTURE_SIZE)) + 1;
+			glTexImage3D(GL_TEXTURE_3D, log2Max3DTextureSize, GL_RGB, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, log2MaxTextureSize, GL_RGB, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage3d_neg_width_height_depth, "Invalid glTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height is less than 0.");
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, -1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, -1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, -1, -1, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, -1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 1, -1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 1, 1, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, -1, -1, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage3d_max_width_height_depth, "Invalid glTexImage3D() usage",
+		{
+			int max3DTextureSize	= m_context.getContextInfo().getInt(GL_MAX_3D_TEXTURE_SIZE) + 1;
+			int maxTextureSize		= m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE) + 1;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width, height or depth is greater than GL_MAX_3D_TEXTURE_SIZE.");
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, max3DTextureSize, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, max3DTextureSize, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, max3DTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, max3DTextureSize, max3DTextureSize, max3DTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width, height or depth is greater than GL_MAX_TEXTURE_SIZE.");
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, maxTextureSize, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 1, maxTextureSize, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 1, 1, maxTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, maxTextureSize, maxTextureSize, maxTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage3d_invalid_border, "Invalid glTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0 or 1.");
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, 1, 1, 1, -1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, 1, 1, 1, 2, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGB, 1, 1, 1, -1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGB, 1, 1, 1, 2, GL_RGB, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(teximage3d_invalid_buffer_target, "Invalid glTexImage3D() usage",
+		{
+			deUint32				buf;
+			deUint32				texture;
+			std::vector<GLubyte>	data(512);
+
+			glGenBuffers			(1, &buf);
+			glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+			glBufferData			(GL_PIXEL_UNPACK_BUFFER, 512, &data[0], GL_DYNAMIC_COPY);
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_3D, texture);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+
+			m_log << TestLog::Section("", "...the buffer object's data store is currently mapped.");
+			glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 128, GL_MAP_WRITE_BIT);
+			glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError				(GL_INVALID_OPERATION);
+			glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+			glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGBA, 64, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "...data is not evenly divisible into the number of bytes needed to store in memory a datum indicated by type.");
+			m_log << TestLog::Message << "// Set byte offset = 3" << TestLog::EndMessage;
+			glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGB5_A1, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (const GLvoid*)3);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers			(1, &buf);
+			glDeleteTextures		(1, &texture);
+		});
+
+	// glTexSubImage3D
+
+	ES3F_ADD_API_CASE(texsubimage3d, "Invalid glTexSubImage3D() usage",
+		{
+			deUint32			texture;
+			glGenTextures		(1, &texture);
+			glBindTexture		(GL_TEXTURE_3D, texture);
+			glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glTexSubImage3D(0, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			glTexSubImage3D(GL_TEXTURE_2D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if format is not an accepted format constant.");
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 4, 4, 4, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if type is not a type constant.");
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGB, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if the combination of internalFormat of the previously specified texture array, format and type is not valid.");
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA_INTEGER, GL_UNSIGNED_INT, 0);
+			expectError(GL_INVALID_OPERATION);
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGB, GL_FLOAT, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glDeleteTextures	(1, &texture);
+		});
+	ES3F_ADD_API_CASE(texsubimage3d_neg_level, "Invalid glTexSubImage3D() usage",
+		{
+			deUint32			textures[2];
+			glGenTextures		(2, &textures[0]);
+			glBindTexture		(GL_TEXTURE_3D, textures[0]);
+			glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glBindTexture		(GL_TEXTURE_2D_ARRAY, textures[1]);
+			glTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glTexSubImage3D(GL_TEXTURE_3D, -1, 0, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_2D_ARRAY, -1, 0, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures	(2, &textures[0]);
+		});
+	ES3F_ADD_API_CASE(texsubimage3d_max_level, "Invalid glTexSubImage3D() usage",
+		{
+			deUint32			textures[2];
+			glGenTextures		(2, &textures[0]);
+			glBindTexture		(GL_TEXTURE_3D, textures[0]);
+			glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glBindTexture		(GL_TEXTURE_2D_ARRAY, textures[1]);
+			glTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError			(GL_NO_ERROR);
+
+			deUint32 log2Max3DTextureSize	= deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_3D_TEXTURE_SIZE)) + 1;
+			deUint32 log2MaxTextureSize		= deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_3D_TEXTURE_SIZE).");
+			glTexSubImage3D(GL_TEXTURE_3D, log2Max3DTextureSize, 0, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			glTexSubImage3D(GL_TEXTURE_2D_ARRAY, log2MaxTextureSize, 0, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures	(2, &textures[0]);
+		});
+	ES3F_ADD_API_CASE(texsubimage3d_neg_offset, "Invalid glTexSubImage3D() usage",
+		{
+			deUint32			textures[2];
+			glGenTextures		(2, &textures[0]);
+			glBindTexture		(GL_TEXTURE_3D, textures[0]);
+			glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glBindTexture		(GL_TEXTURE_2D_ARRAY, textures[1]);
+			glTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset, yoffset or zoffset are negative.");
+			glTexSubImage3D(GL_TEXTURE_3D, 0, -1, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, -1, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, -1, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_3D, 0, -1, -1, -1, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, -1, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, -1, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, -1, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, -1, -1, -1, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures	(2, &textures[0]);
+		});
+	ES3F_ADD_API_CASE(texsubimage3d_invalid_offset, "Invalid glTexSubImage3D() usage",
+		{
+			deUint32			texture;
+			glGenTextures		(1, &texture);
+			glBindTexture		(GL_TEXTURE_3D, texture);
+			glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset + width > texture_width.");
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 2, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if yoffset + height > texture_height.");
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 2, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if zoffset + depth > texture_depth.");
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures	(1, &texture);
+		});
+	ES3F_ADD_API_CASE(texsubimage3d_neg_width_height, "Invalid glTexSubImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width, height or depth is less than 0.");
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, -1, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, -1, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, -1, -1, -1, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(texsubimage3d_invalid_buffer_target, "Invalid glTexSubImage3D() usage",
+		{
+			deUint32				buf;
+			deUint32				texture;
+			std::vector<GLubyte>	data(512);
+
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_3D, texture);
+			glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGBA, 16, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glGenBuffers			(1, &buf);
+			glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+			glBufferData			(GL_PIXEL_UNPACK_BUFFER, 512, &data[0], GL_DYNAMIC_COPY);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+
+			m_log << TestLog::Section("", "...the buffer object's data store is currently mapped.");
+			glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 512, GL_MAP_WRITE_BIT);
+			glTexSubImage3D			(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError				(GL_INVALID_OPERATION);
+			glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+			glTexSubImage3D			(GL_TEXTURE_3D, 0, 0, 0, 0, 16, 16, 16, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "...data is not evenly divisible into the number of bytes needed to store in memory a datum indicated by type.");
+			m_log << TestLog::Message << "// Set byte offset = 3" << TestLog::EndMessage;
+			glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, 0);
+			glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGBA4, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+			glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+			expectError				(GL_NO_ERROR);
+			glTexSubImage3D			(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (const GLvoid*)3);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers			(1, &buf);
+			glDeleteTextures		(1, &texture);
+		});
+
+	// glCopyTexSubImage3D
+
+	ES3F_ADD_API_CASE(copytexsubimage3d, "Invalid glCopyTexSubImage3D() usage",
+		{
+			GLuint texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_3D, texture);
+			glTexImage3D	(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glCopyTexSubImage3D(0, 0, 0, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage3d_neg_level, "Invalid glCopyTexSubImage3D() usage",
+		{
+			deUint32			textures[2];
+			glGenTextures		(2, &textures[0]);
+			glBindTexture		(GL_TEXTURE_3D, textures[0]);
+			glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glBindTexture		(GL_TEXTURE_2D_ARRAY, textures[1]);
+			glTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glCopyTexSubImage3D(GL_TEXTURE_3D, -1, 0, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, -1, 0, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(2, &textures[0]);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage3d_max_level, "Invalid glCopyTexSubImage3D() usage",
+		{
+			deUint32	log2Max3DTextureSize	= deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_3D_TEXTURE_SIZE)) + 1;
+			deUint32	log2MaxTextureSize		= deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+
+			deUint32			textures[2];
+			glGenTextures		(2, &textures[0]);
+			glBindTexture		(GL_TEXTURE_3D, textures[0]);
+			glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glBindTexture		(GL_TEXTURE_2D_ARRAY, textures[1]);
+			glTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			expectError			(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_3D_TEXTURE_SIZE).");
+			glCopyTexSubImage3D(GL_TEXTURE_3D, log2Max3DTextureSize, 0, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, log2MaxTextureSize, 0, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(2, &textures[0]);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage3d_neg_offset, "Invalid glCopyTexSubImage3D() usage",
+		{
+			GLuint texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_3D, texture);
+			glTexImage3D	(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset, yoffset or zoffset is negative.");
+			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, -1, 0,  0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, -1, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, -1, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, -1, -1, -1, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage3d_invalid_offset, "Invalid glCopyTexSubImage3D() usage",
+		{
+			GLuint texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_3D, texture);
+			glTexImage3D	(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if xoffset + width > texture_width.");
+			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 1, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if yoffset + height > texture_height.");
+			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 1, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if zoffset + 1 > texture_depth.");
+			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 4, 0, 0, 4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage3d_neg_width_height, "Invalid glCopyTexSubImage3D() usage",
+		{
+			GLuint texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_3D, texture);
+			glTexImage3D	(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width < 0.");
+			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, -4, 4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if height < 0.");
+			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 4, -4);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(copytexsubimage3d_incomplete_framebuffer, "Invalid glCopyTexSubImage3D() usage",
+		{
+			GLuint fbo;
+			GLuint texture[2];
+
+			glGenTextures			(2, texture);
+			glBindTexture			(GL_TEXTURE_3D, texture[0]);
+			glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glBindTexture			(GL_TEXTURE_2D_ARRAY, texture[1]);
+			glTexImage3D			(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+			glGenFramebuffers		(1, &fbo);
+			glBindFramebuffer		(GL_READ_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, 4, 4);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			glDeleteTextures(2, texture);
+		});
+
+	// glCompressedTexImage3D
+
+	ES3F_ADD_API_CASE(compressedteximage3d, "Invalid glCompressedTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glCompressedTexImage3D(0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage3D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if internalformat is not one of the specific compressed internal formats.");
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage3d_neg_level, "Invalid glCompressedTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage3d_max_level, "Invalid glCompressedTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, log2MaxTextureSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage3d_neg_width_height_depth, "Invalid glCompressedTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width, height or depth is less than 0.");
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, -1, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage3d_max_width_height_depth, "Invalid glCompressedTexImage3D() usage",
+		{
+			int maxTextureSize = m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE) + 1;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width, height or depth is greater than GL_MAX_TEXTURE_SIZE.");
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxTextureSize, 0, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, maxTextureSize, 0, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, maxTextureSize, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxTextureSize, maxTextureSize, maxTextureSize, 0, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage3d_invalid_border, "Invalid glCompressedTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if border is not 0.");
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, -1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 1, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage3d_invalid_size, "Invalid glCompressedTexImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if imageSize is not consistent with the format, dimensions, and contents of the specified compressed image data.");
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, 4*4*8, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGB8_ETC2, 16, 16, 1, 0, 4*4*16, 0);
+			expectError(GL_INVALID_VALUE);
+			glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_SIGNED_R11_EAC, 16, 16, 1, 0, 4*4*16, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(compressedteximage3d_invalid_buffer_target, "Invalid glCompressedTexImage3D() usage",
+		{
+			deUint32				buf;
+			std::vector<GLubyte>	data(512);
+
+			glGenBuffers			(1, &buf);
+			glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+			glBufferData			(GL_PIXEL_UNPACK_BUFFER, 64, &data[0], GL_DYNAMIC_COPY);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and the buffer object's data store is currently mapped.");
+			glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 64, GL_MAP_WRITE_BIT);
+			glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGB8_ETC2, 4, 4, 1, 0, etc2DataSize(4, 4), 0);
+			expectError				(GL_INVALID_OPERATION);
+			glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+			glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGB8_ETC2, 16, 16, 1, 0, etc2DataSize(16, 16), 0);
+			expectError				(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers			(1, &buf);
+		});
+
+	// glCompressedTexSubImage3D
+
+	ES3F_ADD_API_CASE(compressedtexsubimage3d, "Invalid glCompressedTexSubImage3D() usage",
+		{
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is invalid.");
+			glCompressedTexSubImage3D(0, 0, 0, 0, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+			glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 1, 0, etc2EacDataSize(18, 18), 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if format does not match the internal format of the texture image being modified.");
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, 0, GL_COMPRESSED_RGB8_ETC2, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if internalformat is an ETC2/EAC format and target is not GL_TEXTURE_2D_ARRAY.");
+			glCompressedTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 18, 18, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(18, 18), 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "For ETC2/EAC images GL_INVALID_OPERATION is generated if width is not a multiple of four, and width + xoffset is not equal to the width of the texture level.");
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 4, 0, 0, 10, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(10, 4), 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "For ETC2/EAC images GL_INVALID_OPERATION is generated if height is not a multiple of four, and height + yoffset is not equal to the height of the texture level.");
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 4, 0, 4, 10, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 10), 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "For ETC2/EAC images GL_INVALID_OPERATION is generated if xoffset or yoffset is not a multiple of four.");
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 1, 0, 0, 4, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+			expectError(GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 1, 0, 4, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+			expectError(GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 1, 1, 0, 4, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+			expectError(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage3d_neg_level, "Invalid glCompressedTexSubImage3D() usage",
+		{
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+			glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, etc2EacDataSize(16, 16), 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is less than 0.");
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, -1, 0, 0, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage3d_max_level, "Invalid glCompressedTexSubImage3D() usage",
+		{
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+			glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, etc2EacDataSize(16, 16), 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+			deUint32 log2MaxTextureSize = deLog2Floor32(m_context.getContextInfo().getInt(GL_MAX_TEXTURE_SIZE)) + 1;
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, log2MaxTextureSize, 0, 0, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage3d_neg_offset, "Invalid glCompressedTexSubImage3D() usage",
+		{
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+			glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, etc2EacDataSize(16, 16), 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if xoffset, yoffset or zoffset are negative.");
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, -4, 0, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, -4, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, -4, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, -4, -4, -4, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage3d_invalid_offset, "Invalid glCompressedTexSubImage3D() usage",
+		{
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+			glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 4, 4, 1, 0, etc2EacDataSize(4, 4), 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 12, 0, 0, 8, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(8, 4), 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 12, 0, 4, 8, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 8), 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 12, 4, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 12, 12, 12, 8, 8, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(8, 8), 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage3d_neg_width_height_depth, "Invalid glCompressedTexSubImage3D() usage",
+		{
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+			glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, etc2EacDataSize(16, 16), 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if width, height or depth are negative.");
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, -4, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, -4, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, -4, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, -4, -4, -4, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+			expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage3d_invalid_size, "Invalid glCompressedTexSubImage3D() usage",
+		{
+			deUint32				texture;
+			glGenTextures			(1, &texture);
+			glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+			glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, 4*4*16, 0);
+			expectError				(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if imageSize is not consistent with the format, dimensions, and contents of the specified compressed image data.");
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 16, 16, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0);
+			expectError(GL_INVALID_VALUE);
+
+			glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 16, 16, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, 4*4*16-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures		(1, &texture);
+		});
+	ES3F_ADD_API_CASE(compressedtexsubimage3d_invalid_buffer_target, "Invalid glCompressedTexSubImage3D() usage",
+		{
+			deUint32					buf;
+			deUint32					texture;
+			std::vector<GLubyte>		data(512);
+
+			glGenTextures				(1, &texture);
+			glBindTexture				(GL_TEXTURE_2D_ARRAY, texture);
+			glCompressedTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, etc2EacDataSize(16, 16), 0);
+			glGenBuffers				(1, &buf);
+			glBindBuffer				(GL_PIXEL_UNPACK_BUFFER, buf);
+			glBufferData				(GL_PIXEL_UNPACK_BUFFER, 512, &data[0], GL_DYNAMIC_COPY);
+			expectError					(GL_NO_ERROR);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+			m_log << TestLog::Section("", "...the buffer object's data store is currently mapped.");
+			glMapBufferRange			(GL_PIXEL_UNPACK_BUFFER, 0, 512, GL_MAP_WRITE_BIT);
+			glCompressedTexSubImage3D	(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 4, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+			expectError					(GL_INVALID_OPERATION);
+			glUnmapBuffer				(GL_PIXEL_UNPACK_BUFFER);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+			glCompressedTexSubImage3D	(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 32, 32, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(32, 32), 0);
+			expectError					(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+			m_log << TestLog::EndSection;
+
+			glDeleteBuffers			(1, &buf);
+			glDeleteTextures		(1, &texture);
+		});
+
+	// glTexStorage2D
+
+	ES3F_ADD_API_CASE(texstorage2d, "Invalid glTexStorage2D() usage",
+		{
+			deUint32		texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM or GL_INVALID_VALUE is generated if internalformat is not a valid sized internal format.");
+			glTexStorage2D	(GL_TEXTURE_2D, 1, 0, 16, 16);
+			expectError		(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA_INTEGER, 16, 16);
+			expectError		(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.");
+			glTexStorage2D	(0, 1, GL_RGBA8, 16, 16);
+			expectError		(GL_INVALID_ENUM);
+			glTexStorage2D	(GL_TEXTURE_3D, 1, GL_RGBA8, 16, 16);
+			expectError		(GL_INVALID_ENUM);
+			glTexStorage2D	(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 16, 16);
+			expectError		(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width or height are less than 1.");
+			glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 0, 16);
+			expectError		(GL_INVALID_VALUE);
+			glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 16, 0);
+			expectError		(GL_INVALID_VALUE);
+			glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 0, 0);
+			expectError		(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	ES3F_ADD_API_CASE(texstorage2d_invalid_binding, "Invalid glTexStorage2D() usage",
+		{
+			glBindTexture	(GL_TEXTURE_2D, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the default texture object is curently bound to target.");
+			glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 16, 16);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			deUint32		texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the texture object currently bound to target already has GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE.");
+			deInt32			immutable;
+			glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable);
+			m_log << TestLog::Message << "// GL_TEXTURE_IMMUTABLE_FORMAT = " << ((immutable != 0) ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+			glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 16, 16);
+			expectError		(GL_NO_ERROR);
+			glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable);
+			m_log << TestLog::Message << "// GL_TEXTURE_IMMUTABLE_FORMAT = " << ((immutable != 0) ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+			glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 16, 16);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(texstorage2d_invalid_levels, "Invalid glTexStorage2D() usage",
+		{
+			deUint32		texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_2D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if levels is less than 1.");
+			glTexStorage2D	(GL_TEXTURE_2D, 0, GL_RGBA8, 16, 16);
+			expectError		(GL_INVALID_VALUE);
+			glTexStorage2D	(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0);
+			expectError		(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if levels is greater than floor(log_2(max(width, height))) + 1");
+			deUint32 log2MaxSize = deLog2Floor32(deMax32(16, 4)) + 1 + 1;
+			glTexStorage2D	(GL_TEXTURE_2D, log2MaxSize, GL_RGBA8, 16, 4);
+			expectError		(GL_INVALID_OPERATION);
+			glTexStorage2D	(GL_TEXTURE_2D, log2MaxSize, GL_RGBA8, 4, 16);
+			expectError		(GL_INVALID_OPERATION);
+			glTexStorage2D	(GL_TEXTURE_2D, log2MaxSize, GL_RGBA8, 16, 16);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+
+	// glTexStorage3D
+
+	ES3F_ADD_API_CASE(texstorage3d, "Invalid glTexStorage3D() usage",
+		{
+			deUint32		texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_3D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM or GL_INVALID_VALUE is generated if internalformat is not a valid sized internal format.");
+			glTexStorage3D	(GL_TEXTURE_3D, 1, 0, 4, 4, 4);
+			expectError		(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA_INTEGER, 4, 4, 4);
+			expectError		(GL_INVALID_ENUM, GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.");
+			glTexStorage3D	(0, 1, GL_RGBA8, 4, 4, 4);
+			expectError		(GL_INVALID_ENUM);
+			glTexStorage3D	(GL_TEXTURE_CUBE_MAP, 1, GL_RGBA8, 4, 4, 4);
+			expectError		(GL_INVALID_ENUM);
+			glTexStorage3D	(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4, 4);
+			expectError		(GL_INVALID_ENUM);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if width, height or depth are less than 1.");
+			glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 0, 4, 4);
+			expectError		(GL_INVALID_VALUE);
+			glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 4, 0, 4);
+			expectError		(GL_INVALID_VALUE);
+			glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 4, 4, 0);
+			expectError		(GL_INVALID_VALUE);
+			glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 0, 0, 0);
+			expectError		(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(texstorage3d_invalid_binding, "Invalid glTexStorage3D() usage",
+		{
+			glBindTexture	(GL_TEXTURE_3D, 0);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the default texture object is curently bound to target.");
+			glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 4, 4, 4);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			deUint32		texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_3D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if the texture object currently bound to target already has GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE.");
+			deInt32			immutable;
+			glGetTexParameteriv(GL_TEXTURE_3D, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable);
+			m_log << TestLog::Message << "// GL_TEXTURE_IMMUTABLE_FORMAT = " << ((immutable != 0) ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+			glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 4, 4, 4);
+			expectError		(GL_NO_ERROR);
+			glGetTexParameteriv(GL_TEXTURE_3D, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable);
+			m_log << TestLog::Message << "// GL_TEXTURE_IMMUTABLE_FORMAT = " << ((immutable != 0) ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+			glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 4, 4, 4);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+	ES3F_ADD_API_CASE(texstorage3d_invalid_levels, "Invalid glTexStorage3D() usage",
+		{
+			deUint32		texture;
+			glGenTextures	(1, &texture);
+			glBindTexture	(GL_TEXTURE_3D, texture);
+
+			m_log << TestLog::Section("", "GL_INVALID_VALUE is generated if levels is less than 1.");
+			glTexStorage3D	(GL_TEXTURE_3D, 0, GL_RGBA8, 4, 4, 4);
+			expectError		(GL_INVALID_VALUE);
+			glTexStorage3D	(GL_TEXTURE_3D, 0, GL_RGBA8, 0, 0, 0);
+			expectError		(GL_INVALID_VALUE);
+			m_log << TestLog::EndSection;
+
+			m_log << TestLog::Section("", "GL_INVALID_OPERATION is generated if levels is greater than floor(log_2(max(width, height, depth))) + 1");
+			deUint32 log2MaxSize = deLog2Floor32(8) + 1 + 1;
+			glTexStorage3D	(GL_TEXTURE_3D, log2MaxSize, GL_RGBA8, 8, 2, 2);
+			expectError		(GL_INVALID_OPERATION);
+			glTexStorage3D	(GL_TEXTURE_3D, log2MaxSize, GL_RGBA8, 2, 8, 2);
+			expectError		(GL_INVALID_OPERATION);
+			glTexStorage3D	(GL_TEXTURE_3D, log2MaxSize, GL_RGBA8, 2, 2, 8);
+			expectError		(GL_INVALID_OPERATION);
+			glTexStorage3D	(GL_TEXTURE_3D, log2MaxSize, GL_RGBA8, 8, 8, 8);
+			expectError		(GL_INVALID_OPERATION);
+			m_log << TestLog::EndSection;
+
+			glDeleteTextures(1, &texture);
+		});
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fNegativeTextureApiTests.hpp b/modules/gles3/functional/es3fNegativeTextureApiTests.hpp
new file mode 100644
index 0000000..0a4fbe4
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeTextureApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FNEGATIVETEXTUREAPITESTS_HPP
+#define _ES3FNEGATIVETEXTUREAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Texture API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class NegativeTextureApiTests : public TestCaseGroup
+{
+public:
+								NegativeTextureApiTests		(Context& context);
+								~NegativeTextureApiTests	(void);
+
+	void						init						(void);
+
+private:
+								NegativeTextureApiTests		(const NegativeTextureApiTests& other);
+	NegativeTextureApiTests&	operator=					(const NegativeTextureApiTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FNEGATIVETEXTUREAPITESTS_HPP
diff --git a/modules/gles3/functional/es3fNegativeVertexArrayApiTests.cpp b/modules/gles3/functional/es3fNegativeVertexArrayApiTests.cpp
new file mode 100644
index 0000000..f3dbb20
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeVertexArrayApiTests.cpp
@@ -0,0 +1,968 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Vertex Array API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fNegativeVertexArrayApiTests.hpp"
+#include "es3fApiCase.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluContextInfo.hpp"
+#include "deString.h"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+static const char* vertexShaderSource		=	"#version 300 es\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vec4(0.0);\n"
+												"}\n\0";
+
+static const char* fragmentShaderSource		=	"#version 300 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(0.0);\n"
+												"}\n\0";
+
+using tcu::TestLog;
+
+NegativeVertexArrayApiTests::NegativeVertexArrayApiTests (Context& context)
+	: TestCaseGroup(context, "vertex_array", "Negative Vertex Array API Cases")
+{
+}
+
+NegativeVertexArrayApiTests::~NegativeVertexArrayApiTests (void)
+{
+}
+
+void NegativeVertexArrayApiTests::init (void)
+{
+	ES3F_ADD_API_CASE(vertex_attribf, "Invalid glVertexAttrib{1234}f() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			glVertexAttrib1f(maxVertexAttribs, 0.0f);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib2f(maxVertexAttribs, 0.0f, 0.0f);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib3f(maxVertexAttribs, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib4f(maxVertexAttribs, 0.0f, 0.0f, 0.0f, 0.0f);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(vertex_attribfv, "Invalid glVertexAttrib{1234}fv() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			float v[4] = {0.0f};
+			glVertexAttrib1fv(maxVertexAttribs, &v[0]);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib2fv(maxVertexAttribs, &v[0]);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib3fv(maxVertexAttribs, &v[0]);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttrib4fv(maxVertexAttribs, &v[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(vertex_attribi4, "Invalid glVertexAttribI4{i|ui}f() usage",
+		{
+			int maxVertexAttribs	= m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			GLint valInt			= 0;
+			GLuint valUint			= 0;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			glVertexAttribI4i(maxVertexAttribs, valInt, valInt, valInt, valInt);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttribI4ui(maxVertexAttribs, valUint, valUint, valUint, valUint);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(vertex_attribi4v, "Invalid glVertexAttribI4{i|ui}fv() usage",
+		{
+			int maxVertexAttribs	= m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			GLint valInt[4]			= { 0 };
+			GLuint valUint[4]		= { 0 };
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			glVertexAttribI4iv(maxVertexAttribs, &valInt[0]);
+			expectError(GL_INVALID_VALUE);
+			glVertexAttribI4uiv(maxVertexAttribs, &valUint[0]);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(vertex_attrib_pointer, "Invalid glVertexAttribPointer() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not an accepted value.");
+			glVertexAttribPointer(0, 1, 0, GL_TRUE, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			glVertexAttribPointer(maxVertexAttribs, 1, GL_BYTE, GL_TRUE, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if size is not 1, 2, 3, or 4.");
+			glVertexAttribPointer(0, 0, GL_BYTE, GL_TRUE, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if stride is negative.");
+			glVertexAttribPointer(0, 1, GL_BYTE, GL_TRUE, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if type is GL_INT_2_10_10_10_REV or GL_UNSIGNED_INT_2_10_10_10_REV and size is not 4.");
+			glVertexAttribPointer(0, 2, GL_INT_2_10_10_10_REV, GL_TRUE, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glVertexAttribPointer(0, 2, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, 0, 0);
+			expectError(GL_INVALID_OPERATION);
+			glVertexAttribPointer(0, 4, GL_INT_2_10_10_10_REV, GL_TRUE, 0, 0);
+			expectError(GL_NO_ERROR);
+			glVertexAttribPointer(0, 4, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, 0, 0);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated a non-zero vertex array object is bound, zero is bound to the GL_ARRAY_BUFFER buffer object binding point and the pointer argument is not NULL.");
+			GLuint vao;
+			GLbyte offset = 1;
+			glGenVertexArrays(1, &vao);
+			glBindVertexArray(vao);
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			expectError(GL_NO_ERROR);
+
+			glVertexAttribPointer(0, 1, GL_BYTE, GL_TRUE, 0, &offset);
+			expectError(GL_INVALID_OPERATION);
+
+			glBindVertexArray(0);
+			glDeleteVertexArrays(1, &vao);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(vertex_attrib_i_pointer, "Invalid glVertexAttribPointer() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not an accepted value.");
+			glVertexAttribIPointer(0, 1, 0, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glVertexAttribIPointer(0, 4, GL_INT_2_10_10_10_REV, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			glVertexAttribIPointer(0, 4, GL_UNSIGNED_INT_2_10_10_10_REV, 0, 0);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			glVertexAttribIPointer(maxVertexAttribs, 1, GL_BYTE, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if size is not 1, 2, 3, or 4.");
+			glVertexAttribIPointer(0, 0, GL_BYTE, 0, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if stride is negative.");
+			glVertexAttribIPointer(0, 1, GL_BYTE, -1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated a non-zero vertex array object is bound, zero is bound to the GL_ARRAY_BUFFER buffer object binding point and the pointer argument is not NULL.");
+			GLuint vao;
+			GLbyte offset = 1;
+			glGenVertexArrays(1, &vao);
+			glBindVertexArray(vao);
+			glBindBuffer(GL_ARRAY_BUFFER, 0);
+			expectError(GL_NO_ERROR);
+
+			glVertexAttribIPointer(0, 1, GL_BYTE, 0, &offset);
+			expectError(GL_INVALID_OPERATION);
+
+			glBindVertexArray(0);
+			glDeleteVertexArrays(1, &vao);
+			expectError(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(enable_vertex_attrib_array, "Invalid glEnableVertexAttribArray() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			glEnableVertexAttribArray(maxVertexAttribs);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(disable_vertex_attrib_array, "Invalid glDisableVertexAttribArray() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			glDisableVertexAttribArray(maxVertexAttribs);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(gen_vertex_arrays, "Invalid glGenVertexArrays() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			GLuint arrays;
+			glGenVertexArrays(-1, &arrays);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(bind_vertex_array, "Invalid glBindVertexArray() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if array is not zero or the name of an existing vertex array object.");
+			glBindVertexArray(-1);
+			expectError(GL_INVALID_OPERATION);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(delete_vertex_arrays, "Invalid glDeleteVertexArrays() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if n is negative.");
+			glDeleteVertexArrays(-1, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(vertex_attrib_divisor, "Invalid glVertexAttribDivisor() usage",
+		{
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+			int maxVertexAttribs = m_context.getContextInfo().getInt(GL_MAX_VERTEX_ATTRIBS);
+			glVertexAttribDivisor(maxVertexAttribs, 0);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(draw_arrays, "Invalid glDrawArrays() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLuint fbo;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawArrays(-1, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawArrays(GL_POINTS, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawArrays(GL_POINTS, 0, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(draw_arrays_invalid_program, "Invalid glDrawArrays() usage",
+		{
+			glUseProgram(0);
+			GLuint fbo;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawArrays(-1, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawArrays(GL_POINTS, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawArrays(GL_POINTS, 0, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(draw_arrays_incomplete_primitive, "Invalid glDrawArrays() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLuint fbo;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawArrays(-1, 0, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawArrays(GL_TRIANGLES, 0, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawArrays(GL_TRIANGLES, 0, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(draw_elements, "Invalid glDrawElements() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLuint fbo;
+			GLuint buf;
+			GLuint tfID;
+			GLfloat vertices[1];
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawElements(-1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+			glDrawElements(GL_POINTS, 1, -1, vertices);
+			expectError(GL_INVALID_ENUM);
+			glDrawElements(GL_POINTS, 1, GL_FLOAT, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawElements(GL_POINTS, -1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawElements(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(1, &tfID);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			glBeginTransformFeedback	(GL_POINTS);
+			expectError					(GL_NO_ERROR);
+
+			glDrawElements				(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError					(GL_INVALID_OPERATION);
+
+			glPauseTransformFeedback();
+			glDrawElements				(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError					(GL_NO_ERROR);
+
+			glEndTransformFeedback		();
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(1, &tfID);
+			expectError					(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(draw_elements_invalid_program, "Invalid glDrawElements() usage",
+		{
+			glUseProgram(0);
+			GLuint fbo;
+			GLfloat vertices[1];
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawElements(-1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+			glDrawElements(GL_POINTS, 1, -1, vertices);
+			expectError(GL_INVALID_ENUM);
+			glDrawElements(GL_POINTS, 1, GL_FLOAT, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawElements(GL_POINTS, -1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawElements(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(draw_elements_incomplete_primitive, "Invalid glDrawElements() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLuint fbo;
+			GLuint buf;
+			GLuint tfID;
+			GLfloat vertices[1];
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawElements(-1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+			glDrawElements(GL_TRIANGLES, 1, -1, vertices);
+			expectError(GL_INVALID_ENUM);
+			glDrawElements(GL_TRIANGLES, 1, GL_FLOAT, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawElements(GL_TRIANGLES, -1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawElements(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(1, &tfID);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			expectError					(GL_NO_ERROR);
+
+			glDrawElements				(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError					(GL_INVALID_OPERATION);
+
+			glPauseTransformFeedback();
+			glDrawElements				(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError					(GL_NO_ERROR);
+
+			glEndTransformFeedback		();
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(1, &tfID);
+			expectError					(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(draw_arrays_instanced, "Invalid glDrawArraysInstanced() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLuint fbo;
+			glVertexAttribDivisor(0, 1);
+			expectError(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawArraysInstanced(-1, 0, 1, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count or primcount are negative.");
+			glDrawArraysInstanced(GL_POINTS, 0, -1, 1);
+			expectError(GL_INVALID_VALUE);
+			glDrawArraysInstanced(GL_POINTS, 0, 1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawArraysInstanced(GL_POINTS, 0, 1, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(draw_arrays_instanced_invalid_program, "Invalid glDrawArraysInstanced() usage",
+		{
+			glUseProgram(0);
+			GLuint fbo;
+			glVertexAttribDivisor(0, 1);
+			expectError(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawArraysInstanced(-1, 0, 1, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count or primcount are negative.");
+			glDrawArraysInstanced(GL_POINTS, 0, -1, 1);
+			expectError(GL_INVALID_VALUE);
+			glDrawArraysInstanced(GL_POINTS, 0, 1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawArraysInstanced(GL_POINTS, 0, 1, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(draw_arrays_instanced_incomplete_primitive, "Invalid glDrawArraysInstanced() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLuint fbo;
+			glVertexAttribDivisor(0, 1);
+			expectError(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawArraysInstanced(-1, 0, 1, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count or primcount are negative.");
+			glDrawArraysInstanced(GL_TRIANGLES, 0, -1, 1);
+			expectError(GL_INVALID_VALUE);
+			glDrawArraysInstanced(GL_TRIANGLES, 0, 1, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawArraysInstanced(GL_TRIANGLES, 0, 1, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(draw_elements_instanced, "Invalid glDrawElementsInstanced() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLuint fbo;
+			GLuint buf;
+			GLuint tfID;
+			GLfloat vertices[1];
+			glVertexAttribDivisor(0, 1);
+			expectError(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawElementsInstanced(-1, 1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+			glDrawElementsInstanced(GL_POINTS, 1, -1, vertices, 1);
+			expectError(GL_INVALID_ENUM);
+			glDrawElementsInstanced(GL_POINTS, 1, GL_FLOAT, vertices, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count or primcount are negative.");
+			glDrawElementsInstanced(GL_POINTS, -1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError(GL_INVALID_VALUE);
+			glDrawElementsInstanced(GL_POINTS, 11, GL_UNSIGNED_BYTE, vertices, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawElementsInstanced(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(1, &tfID);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			glBeginTransformFeedback	(GL_POINTS);
+			expectError					(GL_NO_ERROR);
+
+			glDrawElementsInstanced		(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError					(GL_INVALID_OPERATION);
+
+			glPauseTransformFeedback();
+			glDrawElementsInstanced		(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError					(GL_NO_ERROR);
+
+			glEndTransformFeedback		();
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(1, &tfID);
+			expectError					(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(draw_elements_instanced_invalid_program, "Invalid glDrawElementsInstanced() usage",
+		{
+			glUseProgram(0);
+			GLuint fbo;
+			GLfloat vertices[1];
+			glVertexAttribDivisor(0, 1);
+			expectError(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawElementsInstanced(-1, 1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+			glDrawElementsInstanced(GL_POINTS, 1, -1, vertices, 1);
+			expectError(GL_INVALID_ENUM);
+			glDrawElementsInstanced(GL_POINTS, 1, GL_FLOAT, vertices, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count or primcount are negative.");
+			glDrawElementsInstanced(GL_POINTS, -1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError(GL_INVALID_VALUE);
+			glDrawElementsInstanced(GL_POINTS, 11, GL_UNSIGNED_BYTE, vertices, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawElementsInstanced(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(draw_elements_instanced_incomplete_primitive, "Invalid glDrawElementsInstanced() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLuint fbo;
+			GLuint buf;
+			GLuint tfID;
+			GLfloat vertices[1];
+			glVertexAttribDivisor(0, 1);
+			expectError(GL_NO_ERROR);
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawElementsInstanced(-1, 1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+			glDrawElementsInstanced(GL_TRIANGLES, 1, -1, vertices, 1);
+			expectError(GL_INVALID_ENUM);
+			glDrawElementsInstanced(GL_TRIANGLES, 1, GL_FLOAT, vertices, 1);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count or primcount are negative.");
+			glDrawElementsInstanced(GL_TRIANGLES, -1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError(GL_INVALID_VALUE);
+			glDrawElementsInstanced(GL_TRIANGLES, 11, GL_UNSIGNED_BYTE, vertices, -1);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawElementsInstanced(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(1, &tfID);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			expectError					(GL_NO_ERROR);
+
+			glDrawElementsInstanced		(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError					(GL_INVALID_OPERATION);
+
+			glPauseTransformFeedback();
+			glDrawElementsInstanced		(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices, 1);
+			expectError					(GL_NO_ERROR);
+
+			glEndTransformFeedback		();
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(1, &tfID);
+			expectError					(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(draw_range_elements, "Invalid glDrawRangeElements() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLuint fbo;
+			GLuint buf;
+			GLuint tfID;
+			GLfloat vertices[1];
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawRangeElements(-1, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+			glDrawRangeElements(GL_POINTS, 0, 1, 1, -1, vertices);
+			expectError(GL_INVALID_ENUM);
+			glDrawRangeElements(GL_POINTS, 0, 1, 1, GL_FLOAT, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawRangeElements(GL_POINTS, 0, 1, -1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if end < start.");
+			glDrawRangeElements(GL_POINTS, 1, 0, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawRangeElements(GL_POINTS, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(1, &tfID);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			glBeginTransformFeedback	(GL_POINTS);
+			expectError					(GL_NO_ERROR);
+
+			glDrawRangeElements			(GL_POINTS, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError					(GL_INVALID_OPERATION);
+
+			glPauseTransformFeedback();
+			glDrawRangeElements			(GL_POINTS, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError					(GL_NO_ERROR);
+
+			glEndTransformFeedback		();
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(1, &tfID);
+			expectError					(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+	ES3F_ADD_API_CASE(draw_range_elements_invalid_program, "Invalid glDrawRangeElements() usage",
+		{
+			glUseProgram(0);
+			GLuint fbo;
+			GLfloat vertices[1];
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawRangeElements(-1, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+			glDrawRangeElements(GL_POINTS, 0, 1, 1, -1, vertices);
+			expectError(GL_INVALID_ENUM);
+			glDrawRangeElements(GL_POINTS, 0, 1, 1, GL_FLOAT, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawRangeElements(GL_POINTS, 0, 1, -1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if end < start.");
+			glDrawRangeElements(GL_POINTS, 1, 0, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawRangeElements(GL_POINTS, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+		});
+	ES3F_ADD_API_CASE(draw_range_elements_incomplete_primitive, "Invalid glDrawRangeElements() usage",
+		{
+			glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+			glUseProgram(program.getProgram());
+			GLuint fbo;
+			GLuint buf;
+			GLuint tfID;
+			GLfloat vertices[1];
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if mode is not an accepted value.");
+			glDrawRangeElements(-1, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+			glDrawRangeElements(GL_TRIANGLES, 0, 1, 1, -1, vertices);
+			expectError(GL_INVALID_ENUM);
+			glDrawRangeElements(GL_TRIANGLES, 0, 1, 1, GL_FLOAT, vertices);
+			expectError(GL_INVALID_ENUM);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if count is negative.");
+			glDrawRangeElements(GL_TRIANGLES, 0, 1, -1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_VALUE is generated if end < start.");
+			glDrawRangeElements(GL_TRIANGLES, 1, 0, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_VALUE);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+			glGenFramebuffers(1, &fbo);
+			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+			glCheckFramebufferStatus(GL_FRAMEBUFFER);
+			glDrawRangeElements(GL_TRIANGLES, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			glDeleteFramebuffers(1, &fbo);
+			m_log << tcu::TestLog::EndSection;
+
+			m_log << tcu::TestLog::Section("", "GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+			const char* tfVarying		= "gl_Position";
+
+			glGenBuffers				(1, &buf);
+			glGenTransformFeedbacks		(1, &tfID);
+
+			glUseProgram				(program.getProgram());
+			glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+			glLinkProgram				(program.getProgram());
+			glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+			glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+			glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+			glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+			glBeginTransformFeedback	(GL_TRIANGLES);
+			expectError					(GL_NO_ERROR);
+
+			glDrawRangeElements			(GL_TRIANGLES, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError					(GL_INVALID_OPERATION);
+
+			glPauseTransformFeedback();
+			glDrawRangeElements			(GL_TRIANGLES, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+			expectError					(GL_NO_ERROR);
+
+			glEndTransformFeedback		();
+			glDeleteBuffers				(1, &buf);
+			glDeleteTransformFeedbacks	(1, &tfID);
+			expectError					(GL_NO_ERROR);
+			m_log << tcu::TestLog::EndSection;
+
+			glUseProgram(0);
+		});
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fNegativeVertexArrayApiTests.hpp b/modules/gles3/functional/es3fNegativeVertexArrayApiTests.hpp
new file mode 100644
index 0000000..4aa6263
--- /dev/null
+++ b/modules/gles3/functional/es3fNegativeVertexArrayApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FNEGATIVEVERTEXARRAYAPITESTS_HPP
+#define _ES3FNEGATIVEVERTEXARRAYAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Vertex Array API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class NegativeVertexArrayApiTests : public TestCaseGroup
+{
+public:
+									NegativeVertexArrayApiTests		(Context& context);
+									~NegativeVertexArrayApiTests	(void);
+
+	void							init							(void);
+
+private:
+									NegativeVertexArrayApiTests		(const NegativeVertexArrayApiTests& other);
+	NegativeVertexArrayApiTests&	operator=						(const NegativeVertexArrayApiTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FNEGATIVEVERTEXARRAYAPITESTS_HPP
diff --git a/modules/gles3/functional/es3fOcclusionQueryTests.cpp b/modules/gles3/functional/es3fOcclusionQueryTests.cpp
new file mode 100644
index 0000000..ca513e5
--- /dev/null
+++ b/modules/gles3/functional/es3fOcclusionQueryTests.cpp
@@ -0,0 +1,526 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Occlusion query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fOcclusionQueryTests.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+
+#include "glw.h"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+static const tcu::Vec4	DEPTH_WRITE_COLOR	= tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f);
+static const tcu::Vec4	DEPTH_CLEAR_COLOR	= tcu::Vec4(0.0f, 0.5f, 0.8f, 1.0f);
+static const tcu::Vec4	STENCIL_WRITE_COLOR	= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+static const tcu::Vec4	STENCIL_CLEAR_COLOR	= tcu::Vec4(0.0f, 0.8f, 0.5f, 1.0f);
+static const tcu::Vec4	TARGET_COLOR		= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+static const int		ELEMENTS_PER_VERTEX = 4;
+static const int		NUM_CASE_ITERATIONS = 10;
+
+// Constants to tweak visible/invisible case probability balance.
+
+static const int		DEPTH_CLEAR_OFFSET		= 100;
+static const int		STENCIL_CLEAR_OFFSET	= 100;
+static const int		SCISSOR_OFFSET			= 100;
+static const int		SCISSOR_MINSIZE			= 250;
+
+enum OccluderType
+{
+	OCCLUDER_SCISSOR		= (1 << 0),
+	OCCLUDER_DEPTH_WRITE	= (1 << 1),
+	OCCLUDER_DEPTH_CLEAR	= (1 << 2),
+	OCCLUDER_STENCIL_WRITE	= (1 << 3),
+	OCCLUDER_STENCIL_CLEAR	= (1 << 4)
+};
+
+class OcclusionQueryCase : public TestCase
+{
+public:
+								OcclusionQueryCase		(Context& context, const char* name, const char* description,int numOccluderDraws, int numOccludersPerDraw, float occluderSize, int numTargetDraws, int numTargetsPerDraw, float targetSize, deUint32 queryMode, deUint32 occluderTypes);
+								~OcclusionQueryCase		(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+								OcclusionQueryCase		(const OcclusionQueryCase& other);
+	OcclusionQueryCase&			operator=				(const OcclusionQueryCase& other);
+
+	int							m_numOccluderDraws;
+	int							m_numOccludersPerDraw;
+	float						m_occluderSize;
+	int							m_numTargetDraws;
+	int							m_numTargetsPerDraw;
+	float						m_targetSize;
+	deUint32					m_queryMode;
+	deUint32					m_occluderTypes;
+
+	glu::RenderContext&			m_renderCtx;
+	glu::ShaderProgram*			m_program;
+	int							m_iterNdx;
+	de::Random					m_rnd;
+};
+
+OcclusionQueryCase::OcclusionQueryCase (Context& context, const char* name, const char* description, int numOccluderDraws, int numOccludersPerDraw, float occluderSize, int numTargetDraws, int numTargetsPerDraw, float targetSize, deUint32 queryMode, deUint32 occluderTypes)
+	: TestCase				(context, name, description)
+	, m_numOccluderDraws	(numOccluderDraws)
+	, m_numOccludersPerDraw	(numOccludersPerDraw)
+	, m_occluderSize		(occluderSize)
+	, m_numTargetDraws		(numTargetDraws)
+	, m_numTargetsPerDraw	(numTargetsPerDraw)
+	, m_targetSize			(targetSize)
+	, m_queryMode			(queryMode)
+	, m_occluderTypes		(occluderTypes)
+	, m_renderCtx			(context.getRenderContext())
+	, m_program				(DE_NULL)
+	, m_iterNdx				(0)
+	, m_rnd					(deStringHash(name))
+{
+}
+
+OcclusionQueryCase::~OcclusionQueryCase (void)
+{
+	OcclusionQueryCase::deinit();
+}
+
+static void generateVertices (std::vector<float>& dst, float width, float height, int primitiveCount, int verticesPerPrimitive, de::Random rnd, float primitiveSize, float minZ, float maxZ)
+{
+	float w = width/2.0f;
+	float h = height/2.0f;
+	float s = primitiveSize/2.0f;
+
+	int vertexCount = verticesPerPrimitive * primitiveCount;
+	dst.resize(vertexCount * ELEMENTS_PER_VERTEX);
+
+	for (int i = 0; i < vertexCount; i += 3)			// First loop gets a random point inside unit square
+	{
+		float rndX = rnd.getFloat(-w, w);
+		float rndY = rnd.getFloat(-h, h);
+
+		for (int j = 0; j < verticesPerPrimitive; j++)	// Second loop gets 3 random points within given distance s from (rndX, rndY)
+		{
+			dst[(i+j)*ELEMENTS_PER_VERTEX    ] = rndX + rnd.getFloat(-s,s);	// x
+			dst[(i+j)*ELEMENTS_PER_VERTEX + 1] = rndY + rnd.getFloat(-s,s);	// y
+			dst[(i+j)*ELEMENTS_PER_VERTEX + 2] = rnd.getFloat(minZ, maxZ);	// z
+			dst[(i+j)*ELEMENTS_PER_VERTEX + 3] = 1.0f;						// w
+		}
+	}
+}
+
+void OcclusionQueryCase::init (void)
+{
+	const char*	vertShaderSource =
+				"#version 300 es\n"
+				"layout(location = 0) in mediump vec4 a_position;\n"
+				"\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = a_position;\n"
+				"}\n";
+
+	const char* fragShaderSource =
+				"#version 300 es\n"
+				"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+				"uniform mediump vec4 u_color;\n"
+				"\n"
+				"void main (void)\n"
+				"{\n"
+				"	mediump float depth_gradient = gl_FragCoord.z;\n"
+				"	mediump float bias = 0.1;\n"
+				"	dEQP_FragColor = vec4(u_color.xyz * (depth_gradient + bias), 1.0);\n"
+				"}\n";
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource));
+
+	if (!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+		delete m_program;
+		m_program = DE_NULL;
+		TCU_FAIL("Failed to compile shader program");
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); // Initialize test result to pass.
+	GLU_CHECK_MSG ("Case initialization finished");
+}
+
+void OcclusionQueryCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+OcclusionQueryCase::IterateResult OcclusionQueryCase::iterate (void)
+{
+	tcu::TestLog&				log					= m_testCtx.getLog();
+	const tcu::RenderTarget&	renderTarget		= m_context.getRenderTarget();
+	deUint32					colorUnif			= glGetUniformLocation(m_program->getProgram(), "u_color");
+
+	std::vector<float>			occluderVertices;
+	std::vector<float>			targetVertices;
+	std::vector<deUint32>		queryIds(1, 0);
+	bool						queryResult			= false;
+	bool						colorReadResult		= false;
+	int							targetW				= renderTarget.getWidth();
+	int							targetH				= renderTarget.getHeight();
+
+	log << tcu::TestLog::Message << "Case iteration " << m_iterNdx+1 << " / " << NUM_CASE_ITERATIONS << tcu::TestLog::EndMessage;
+	log << tcu::TestLog::Message << "Parameters:\n"
+								 << "- " << m_numOccluderDraws	<< " occluder draws, "	<< m_numOccludersPerDraw	<< " primitive writes per draw,\n"
+								 << "- " << m_numTargetDraws	<< " target draws, "	<< m_numTargetsPerDraw		<< " targets per draw\n"
+		<< tcu::TestLog::EndMessage;
+
+	DE_ASSERT(m_program);
+
+	glClearColor				(0.0f, 0.0f, 0.0f, 1.0f);
+	glClearDepthf				(1.0f);
+	glClearStencil				(0);
+	glClear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+	glUseProgram				(m_program->getProgram());
+	glEnableVertexAttribArray	(0);
+
+	// Draw occluders
+
+	std::vector<OccluderType> occOptions(0);
+	if (m_occluderTypes & OCCLUDER_DEPTH_WRITE)		occOptions.push_back(OCCLUDER_DEPTH_WRITE);
+	if (m_occluderTypes & OCCLUDER_DEPTH_CLEAR)		occOptions.push_back(OCCLUDER_DEPTH_CLEAR);
+	if (m_occluderTypes & OCCLUDER_STENCIL_WRITE)	occOptions.push_back(OCCLUDER_STENCIL_WRITE);
+	if (m_occluderTypes & OCCLUDER_STENCIL_CLEAR)	occOptions.push_back(OCCLUDER_STENCIL_CLEAR);
+
+	for (int i = 0; i < m_numOccluderDraws; i++)
+	{
+		if (occOptions.empty())
+			break;
+
+		OccluderType type = occOptions[m_rnd.getInt(0, (int)occOptions.size()-1)];	// Choosing a random occluder type from available options
+
+		switch (type)
+		{
+			case OCCLUDER_DEPTH_WRITE:
+				log << tcu::TestLog::Message	<< "Occluder draw "	<< i+1 << " / " << m_numOccluderDraws << " : "
+												<< "Depth write"	<< tcu::TestLog::EndMessage;
+
+				generateVertices(occluderVertices, 2.0f, 2.0f, m_numOccludersPerDraw, 3, m_rnd, m_occluderSize, 0.0f, 0.6f);	// Generate vertices for occluding primitives
+
+				DE_ASSERT(!occluderVertices.empty());
+
+				glEnable				(GL_DEPTH_TEST);
+				glUniform4f				(colorUnif, DEPTH_WRITE_COLOR.x(), DEPTH_WRITE_COLOR.y(), DEPTH_WRITE_COLOR.z(), DEPTH_WRITE_COLOR.w());
+				glVertexAttribPointer	(0, ELEMENTS_PER_VERTEX, GL_FLOAT, GL_FALSE, 0, &occluderVertices[0]);
+				glDrawArrays			(GL_TRIANGLES, 0, 3*m_numOccludersPerDraw);
+				glDisable				(GL_DEPTH_TEST);
+
+				break;
+
+			case OCCLUDER_DEPTH_CLEAR:
+			{
+				int scissorBoxX = m_rnd.getInt(-DEPTH_CLEAR_OFFSET,	targetW);
+				int scissorBoxY = m_rnd.getInt(-DEPTH_CLEAR_OFFSET,	targetH);
+				int scissorBoxW = m_rnd.getInt( DEPTH_CLEAR_OFFSET,	targetW+DEPTH_CLEAR_OFFSET);
+				int scissorBoxH = m_rnd.getInt( DEPTH_CLEAR_OFFSET,	targetH+DEPTH_CLEAR_OFFSET);
+
+				log << tcu::TestLog::Message	<< "Occluder draw "	<< i+1 << " / " << m_numOccluderDraws << " : "	<< "Depth clear"
+					<< tcu::TestLog::EndMessage;
+				log << tcu::TestLog::Message	<< "Depth-clearing box drawn at "
+												<< "("			<< scissorBoxX << ", "			<< scissorBoxY << ")"
+												<< ", width = "	<< scissorBoxW << ", height = " << scissorBoxH << "."
+					<< tcu::TestLog::EndMessage;
+
+				glEnable		(GL_SCISSOR_TEST);
+				glScissor		(scissorBoxX, scissorBoxY, scissorBoxW, scissorBoxH);
+				glClearDepthf	(0.0f);
+				glClearColor	(DEPTH_CLEAR_COLOR.x(), DEPTH_CLEAR_COLOR.y(), DEPTH_CLEAR_COLOR.z(), DEPTH_CLEAR_COLOR.w());
+				glClear			(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+				glDisable		(GL_SCISSOR_TEST);
+
+				break;
+			}
+
+			case OCCLUDER_STENCIL_WRITE:
+				log << tcu::TestLog::Message	<< "Occluder draw "	<< i+1 << " / " << m_numOccluderDraws << " : "
+												<< "Stencil write"	<< tcu::TestLog::EndMessage;
+
+				generateVertices(occluderVertices, 2.0f, 2.0f, m_numOccludersPerDraw, 3, m_rnd, m_occluderSize, 0.0f, 0.6f);
+
+				glStencilFunc	(GL_ALWAYS, 1, 0xFF);
+				glStencilOp		(GL_KEEP, GL_KEEP, GL_REPLACE);
+
+				DE_ASSERT(!occluderVertices.empty());
+
+				glEnable				(GL_STENCIL_TEST);
+				glUniform4f				(colorUnif, STENCIL_WRITE_COLOR.x(), STENCIL_WRITE_COLOR.y(), STENCIL_WRITE_COLOR.z(), STENCIL_WRITE_COLOR.w());
+				glVertexAttribPointer	(0, ELEMENTS_PER_VERTEX, GL_FLOAT, GL_FALSE, 0, &occluderVertices[0]);
+				glDrawArrays			(GL_TRIANGLES, 0, 3*m_numOccludersPerDraw);
+				glDisable				(GL_STENCIL_TEST);
+
+				break;
+
+			case OCCLUDER_STENCIL_CLEAR:
+			{
+				int scissorBoxX = m_rnd.getInt(-STENCIL_CLEAR_OFFSET,	targetW);
+				int scissorBoxY = m_rnd.getInt(-STENCIL_CLEAR_OFFSET,	targetH);
+				int scissorBoxW = m_rnd.getInt(	STENCIL_CLEAR_OFFSET,	targetW+STENCIL_CLEAR_OFFSET);
+				int scissorBoxH = m_rnd.getInt(	STENCIL_CLEAR_OFFSET,	targetH+STENCIL_CLEAR_OFFSET);
+
+				log << tcu::TestLog::Message	<< "Occluder draw "	<< i+1 << " / " << m_numOccluderDraws << " : "	<< "Stencil clear"
+					<< tcu::TestLog::EndMessage;
+				log << tcu::TestLog::Message	<< "Stencil-clearing box drawn at "
+												<< "("			<< scissorBoxX << ", "			<< scissorBoxY << ")"
+												<< ", width = "	<< scissorBoxW << ", height = " << scissorBoxH << "."
+					<< tcu::TestLog::EndMessage;
+
+				glEnable		(GL_SCISSOR_TEST);
+				glScissor		(scissorBoxX, scissorBoxY, scissorBoxW, scissorBoxH);
+				glClearStencil	(1);
+				glClearColor	(STENCIL_CLEAR_COLOR.x(), STENCIL_CLEAR_COLOR.y(), STENCIL_CLEAR_COLOR.z(), STENCIL_CLEAR_COLOR.w());
+				glClear			(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+				glDisable		(GL_SCISSOR_TEST);
+
+				break;
+			}
+
+			default:
+				DE_ASSERT(false);
+				break;
+		}
+	}
+
+	if (m_occluderTypes & OCCLUDER_SCISSOR)
+	{
+		int scissorBoxX = m_rnd.getInt(-SCISSOR_OFFSET,	targetW-SCISSOR_OFFSET);
+		int scissorBoxY = m_rnd.getInt(-SCISSOR_OFFSET,	targetH-SCISSOR_OFFSET);
+		int scissorBoxW = m_rnd.getInt(SCISSOR_MINSIZE,	targetW+SCISSOR_OFFSET);
+		int scissorBoxH = m_rnd.getInt(SCISSOR_MINSIZE,	targetH+SCISSOR_OFFSET);
+
+		log << tcu::TestLog::Message	<< "Scissor box drawn at "
+										<< "("			<< scissorBoxX << ", "			<< scissorBoxY << ")"
+										<< ", width = "	<< scissorBoxW << ", height = " << scissorBoxH << "."
+			<< tcu::TestLog::EndMessage;
+
+		glEnable	(GL_SCISSOR_TEST);
+		glScissor	(scissorBoxX, scissorBoxY, scissorBoxW, scissorBoxH);
+	}
+
+	glGenQueries	(1, &queryIds[0]);
+	glBeginQuery	(m_queryMode, queryIds[0]);
+	GLU_CHECK_MSG	("Occlusion query started");
+
+	// Draw target primitives
+
+	glEnable		(GL_DEPTH_TEST);
+	glEnable		(GL_STENCIL_TEST);
+	glStencilFunc	(GL_EQUAL, 0, 0xFF);
+
+	for (int i = 0; i < m_numTargetDraws; i++)
+	{
+		generateVertices(targetVertices, 2.0f, 2.0f, m_numTargetsPerDraw, 3,  m_rnd, m_targetSize, 0.4f, 1.0f);		// Generate vertices for target primitives
+
+		if (!targetVertices.empty())
+		{
+			glUniform4f				(colorUnif, TARGET_COLOR.x(), TARGET_COLOR.y(), TARGET_COLOR.z(), TARGET_COLOR.w());
+			glVertexAttribPointer	(0, ELEMENTS_PER_VERTEX, GL_FLOAT, GL_FALSE, 0, &targetVertices[0]);
+			glDrawArrays			(GL_TRIANGLES, 0, 3*m_numTargetsPerDraw);
+		}
+	}
+
+	glEndQuery		(m_queryMode);
+	glFinish		();
+	glDisable		(GL_SCISSOR_TEST);
+	glDisable		(GL_STENCIL_TEST);
+	glDisable		(GL_DEPTH_TEST);
+
+	// Check that query result is available.
+	{
+		deUint32 resultAvailable = GL_FALSE;
+		glGetQueryObjectuiv(queryIds[0], GL_QUERY_RESULT_AVAILABLE, &resultAvailable);
+
+		if (resultAvailable == GL_FALSE)
+			TCU_FAIL("Occlusion query failed to return a result after glFinish()");
+	}
+
+	// Read query result.
+	{
+		deUint32 result = 0;
+		glGetQueryObjectuiv(queryIds[0], GL_QUERY_RESULT, &result);
+		queryResult = (result != GL_FALSE);
+	}
+
+	glDeleteQueries	(1, &queryIds[0]);
+	GLU_CHECK_MSG	("Occlusion query finished");
+
+	// Read pixel data
+
+	tcu::Surface pixels(renderTarget.getWidth(), renderTarget.getHeight());
+	glu::readPixels(m_context.getRenderContext(), 0, 0, pixels.getAccess());
+
+	{
+		int width = pixels.getWidth();
+		int height = pixels.getHeight();
+
+		for (int y = 0; y < height; y++)
+		{
+			for (int x = 0; x < width; x++)
+			{
+				if (pixels.getPixel(x,y).getRed() != 0)
+				{
+					colorReadResult = true;
+					break;
+				}
+			}
+			if (colorReadResult) break;
+		}
+	}
+
+	log << tcu::TestLog::Message << "Occlusion query result:  Target " << (queryResult		? "visible" : "invisible") << "\n"
+								 << "Framebuffer read result: Target " << (colorReadResult	? "visible" : "invisible") << tcu::TestLog::EndMessage;
+
+	bool testOk = false;
+	if (m_queryMode == GL_ANY_SAMPLES_PASSED_CONSERVATIVE)
+	{
+		if (queryResult || colorReadResult)
+			testOk = queryResult;	// Allow conservative occlusion query to return false positives.
+		else
+			testOk = queryResult == colorReadResult;
+	}
+	else
+		testOk = (queryResult == colorReadResult);
+
+	if (!testOk)
+	{
+		log << tcu::TestLog::Image("Result image", "Result image", pixels);
+		log << tcu::TestLog::Message << "Case FAILED!" << tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		return STOP;
+	}
+
+	log << tcu::TestLog::Message << "Case passed!" << tcu::TestLog::EndMessage;
+
+	return (++m_iterNdx < NUM_CASE_ITERATIONS) ? CONTINUE : STOP;
+}
+
+OcclusionQueryTests::OcclusionQueryTests (Context& context)
+	: TestCaseGroup(context, "occlusion_query", "Occlusion Query Tests")
+{
+}
+
+OcclusionQueryTests::~OcclusionQueryTests (void)
+{
+}
+
+void OcclusionQueryTests::init (void)
+{
+	// Strict occlusion query cases
+
+	addChild(new OcclusionQueryCase(m_context, "scissor",												"scissor",												1,	10, 1.6f, 1, 1, 0.3f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR));
+	addChild(new OcclusionQueryCase(m_context, "depth_write",											"depth_write",											8,	10, 1.6f, 1, 7, 0.3f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "depth_clear",											"depth_clear",											5,	10, 1.6f, 1, 5, 0.2f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "stencil_write",											"stencil_write",										8,	10, 2.0f, 1, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "stencil_clear",											"stencil_clear",										5,	10, 2.0f, 1, 3, 0.3f, GL_ANY_SAMPLES_PASSED, OCCLUDER_STENCIL_CLEAR));
+
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_write",									"scissor_depth_write",									5,	10, 1.6f, 2, 5, 0.3f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_clear",									"scissor_depth_clear",									7,	10, 1.6f, 2, 5, 1.0f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "scissor_stencil_write",									"scissor_stencil_write",								4,	10, 1.6f, 2, 5, 0.3f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "scissor_stencil_clear",									"scissor_stencil_clear",								4,	10, 1.6f, 2, 5, 1.0f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "depth_write_depth_clear",								"depth_write_depth_clear",								7,	10, 1.6f, 1, 5, 0.2f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "depth_write_stencil_write",								"depth_write_stencil_write",							8,	10, 1.6f, 1, 5, 0.3f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "depth_write_stencil_clear",								"depth_write_stencil_clear",							8,	10, 1.6f, 1, 5, 0.3f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "depth_clear_stencil_write",								"depth_clear_stencil_write",							8,	10, 1.6f, 1, 5, 0.3f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "depth_clear_stencil_clear",								"depth_clear_stencil_clear",							12,	10, 1.6f, 1, 5, 0.2f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "stencil_write_stencil_clear",							"stencil_write_stencil_clear",							5,	10, 2.0f, 1, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_write_depth_clear",						"scissor_depth_write_depth_clear",						5,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_write_stencil_write",						"scissor_depth_write_stencil_write",					4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_write_stencil_clear",						"scissor_depth_write_stencil_clear",					6,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_clear_stencil_write",						"scissor_depth_clear_stencil_write",					4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_clear_stencil_clear",						"scissor_depth_clear_stencil_clear",					5,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "scissor_stencil_write_stencil_clear",					"scissor_stencil_write_stencil_clear",					4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "depth_write_depth_clear_stencil_write",					"depth_write_depth_clear_stencil_write",				7,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "depth_write_depth_clear_stencil_clear",					"depth_write_depth_clear_stencil_clear",				7,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "depth_write_stencil_write_stencil_clear",				"depth_write_stencil_write_stencil_clear",				7,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "depth_clear_stencil_write_stencil_clear",				"depth_clear_stencil_write_stencil_clear",				7,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_write_depth_clear_stencil_write",			"scissor_depth_write_depth_clear_stencil_write",		4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_write_depth_clear_stencil_clear",			"scissor_depth_write_depth_clear_stencil_clear",		4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_write_stencil_write_stencil_clear",		"scissor_depth_write_stencil_write_stencil_clear",		5,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "scissor_depth_clear_stencil_write_stencil_clear",		"scissor_depth_clear_stencil_write_stencil_clear",		4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "depth_write_depth_clear_stencil_write_stencil_clear",	"depth_write_depth_clear_stencil_write_stencil_clear",	7,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED, OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+
+	addChild(new OcclusionQueryCase(m_context, "all_occluders",											"all_occluders",										7,	10, 1.6f, 3, 5, 0.6f, GL_ANY_SAMPLES_PASSED, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+
+	// Conservative occlusion query cases
+
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor",												"conservative_scissor",												1,	10, 1.6f, 1, 1, 0.3f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_write",											"conservative_depth_write",											8,	10, 1.6f, 1, 7, 0.3f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_clear",											"conservative_depth_clear",											5,	10, 1.6f, 1, 5, 0.2f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_stencil_write",										"conservative_stencil_write",										8,	10, 2.0f, 1, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "conservative_stencil_clear",										"conservative_stencil_clear",										5,	10, 2.0f, 1, 3, 0.3f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_STENCIL_CLEAR));
+
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_write",									"conservative_scissor_depth_write",									5,	10, 1.6f, 2, 5, 0.3f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_clear",									"conservative_scissor_depth_clear",									7,	10, 1.6f, 2, 5, 1.0f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_stencil_write",								"conservative_scissor_stencil_write",								4,	10, 1.6f, 2, 5, 0.3f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_stencil_clear",								"conservative_scissor_stencil_clear",								4,	10, 1.6f, 2, 5, 1.0f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_write_depth_clear",								"conservative_depth_write_depth_clear",								7,	10, 1.6f, 1, 5, 0.2f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_write_stencil_write",							"conservative_depth_write_stencil_write",							8,	10, 1.6f, 1, 5, 0.3f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_write_stencil_clear",							"conservative_depth_write_stencil_clear",							8,	10, 1.6f, 1, 5, 0.3f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_clear_stencil_write",							"conservative_depth_clear_stencil_write",							8,	10, 1.6f, 1, 5, 0.3f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_clear_stencil_clear",							"conservative_depth_clear_stencil_clear",							12,	10, 1.6f, 1, 5, 0.2f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_stencil_write_stencil_clear",							"conservative_stencil_write_stencil_clear",							5,	10, 2.0f, 1, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_write_depth_clear",						"conservative_scissor_depth_write_depth_clear",						5,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_write_stencil_write",					"conservative_scissor_depth_write_stencil_write",					4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_write_stencil_clear",					"conservative_scissor_depth_write_stencil_clear",					6,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_clear_stencil_write",					"conservative_scissor_depth_clear_stencil_write",					4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_clear_stencil_clear",					"conservative_scissor_depth_clear_stencil_clear",					5,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_stencil_write_stencil_clear",					"conservative_scissor_stencil_write_stencil_clear",					4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_write_depth_clear_stencil_write",				"conservative_depth_write_depth_clear_stencil_write",				7,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_write_depth_clear_stencil_clear",				"conservative_depth_write_depth_clear_stencil_clear",				7,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_write_stencil_write_stencil_clear",				"conservative_depth_write_stencil_write_stencil_clear",				7,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_clear_stencil_write_stencil_clear",				"conservative_depth_clear_stencil_write_stencil_clear",				7,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_write_depth_clear_stencil_write",		"conservative_scissor_depth_write_depth_clear_stencil_write",		4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_write_depth_clear_stencil_clear",		"conservative_scissor_depth_write_depth_clear_stencil_clear",		4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_write_stencil_write_stencil_clear",		"conservative_scissor_depth_write_stencil_write_stencil_clear",		5,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_scissor_depth_clear_stencil_write_stencil_clear",		"conservative_scissor_depth_clear_stencil_write_stencil_clear",		4,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+	addChild(new OcclusionQueryCase(m_context, "conservative_depth_write_depth_clear_stencil_write_stencil_clear",	"conservative_depth_write_depth_clear_stencil_write_stencil_clear",	7,	10, 1.6f, 2, 5, 0.4f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+
+	addChild(new OcclusionQueryCase(m_context, "conservative_all_occluders",										"conservative_all_occluders",										7,	10, 1.6f, 3, 5, 0.6f, GL_ANY_SAMPLES_PASSED_CONSERVATIVE, OCCLUDER_SCISSOR | OCCLUDER_DEPTH_WRITE | OCCLUDER_DEPTH_CLEAR | OCCLUDER_STENCIL_WRITE | OCCLUDER_STENCIL_CLEAR));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fOcclusionQueryTests.hpp b/modules/gles3/functional/es3fOcclusionQueryTests.hpp
new file mode 100644
index 0000000..3971350
--- /dev/null
+++ b/modules/gles3/functional/es3fOcclusionQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FOCCLUSIONQUERYTESTS_HPP
+#define _ES3FOCCLUSIONQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Occlusion query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class OcclusionQueryTests : public TestCaseGroup
+{
+public:
+							OcclusionQueryTests		(Context& context);
+							~OcclusionQueryTests	(void);
+
+	void					init					(void);
+
+private:
+							OcclusionQueryTests		(const OcclusionQueryTests& other);
+	OcclusionQueryTests&	operator=				(const OcclusionQueryTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FOCCLUSIONQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fPixelBufferObjectTests.cpp b/modules/gles3/functional/es3fPixelBufferObjectTests.cpp
new file mode 100644
index 0000000..2d92630
--- /dev/null
+++ b/modules/gles3/functional/es3fPixelBufferObjectTests.cpp
@@ -0,0 +1,719 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Pixel Buffer Object tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fPixelBufferObjectTests.hpp"
+
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+
+#include "deRandom.hpp"
+#include "deString.h"
+
+#include <string>
+#include <sstream>
+
+#include "glw.h"
+
+using std::string;
+using std::stringstream;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+namespace
+{
+
+class ReadPixelsTest : public TestCase
+{
+public:
+	struct TestSpec
+	{
+		enum FramebufferType
+		{
+			FRAMEBUFFERTYPE_NATIVE = 0,
+			FRAMEBUFFERTYPE_RENDERBUFFER
+		};
+
+		string			name;
+		string			description;
+
+		bool			useColorClear;
+		bool			renderTriangles;
+
+		FramebufferType	framebufferType;
+		GLenum			renderbufferFormat;
+	};
+
+					ReadPixelsTest				(Context& context, const TestSpec& spec);
+					~ReadPixelsTest				(void);
+
+	void			init						(void);
+	void			deinit						(void);
+
+	IterateResult	iterate						(void);
+
+private:
+	void						renderTriangle	(const tcu::Vec3& a, const tcu::Vec3& b, const tcu::Vec3& c);
+	void						clearColor		(float r, float g, float b, float a);
+
+	de::Random					m_random;
+	tcu::TestLog&				m_log;
+	glu::ShaderProgram*			m_program;
+
+	TestSpec::FramebufferType	m_framebuffeType;
+
+	GLenum						m_renderbufferFormat;
+	tcu::TextureChannelClass	m_texChannelClass;
+
+	bool						m_useColorClears;
+	bool						m_renderTriangles;
+
+	GLfloat						m_colorScale;
+};
+
+
+ReadPixelsTest::ReadPixelsTest (Context& context, const TestSpec& spec)
+	: TestCase				(context, spec.name.c_str(), spec.description.c_str())
+	, m_random				(deStringHash(spec.name.c_str()))
+	, m_log					(m_testCtx.getLog())
+	, m_program				(NULL)
+	, m_framebuffeType		(spec.framebufferType)
+	, m_renderbufferFormat	(spec.renderbufferFormat)
+	, m_texChannelClass		(tcu::TEXTURECHANNELCLASS_LAST)
+	, m_useColorClears		(spec.useColorClear)
+	, m_renderTriangles		(spec.renderTriangles)
+	, m_colorScale			(1.0f)
+{
+
+	if (m_framebuffeType == TestSpec::FRAMEBUFFERTYPE_NATIVE)
+	{
+		m_colorScale = 1.0f;
+	}
+	else if (m_framebuffeType == TestSpec::FRAMEBUFFERTYPE_RENDERBUFFER)
+	{
+		m_texChannelClass = tcu::getTextureChannelClass(glu::mapGLInternalFormat(spec.renderbufferFormat).type);
+		switch (m_texChannelClass)
+		{
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+				m_colorScale = 1.0f;
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+				m_colorScale = 100.0f;
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+				m_colorScale = 100.0f;
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+				m_colorScale = 100.0f;
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+	else
+		DE_ASSERT(false);
+}
+
+ReadPixelsTest::~ReadPixelsTest (void)
+{
+}
+
+void ReadPixelsTest::init (void)
+{
+	// Check extensions
+	if (m_framebuffeType == TestSpec::FRAMEBUFFERTYPE_RENDERBUFFER)
+	{
+		bool supported = false;
+
+		if (m_renderbufferFormat == GL_RGBA16F
+			|| m_renderbufferFormat == GL_RG16F)
+		{
+			std::istringstream extensions(std::string((const char*)glGetString(GL_EXTENSIONS)));
+			std::string extension;
+
+			while (std::getline(extensions, extension, ' '))
+			{
+				if (extension=="GL_EXT_color_buffer_half_float")
+				{
+					supported = true;
+					break;
+				}
+				if (extension=="GL_EXT_color_buffer_float")
+				{
+					supported = true;
+					break;
+				}
+			}
+		}
+		else if (m_renderbufferFormat == GL_RGBA32F
+				|| m_renderbufferFormat == GL_RG32F
+				|| m_renderbufferFormat == GL_R11F_G11F_B10F)
+		{
+			std::istringstream extensions(std::string((const char*)glGetString(GL_EXTENSIONS)));
+			std::string extension;
+
+			while (std::getline(extensions, extension, ' '))
+			{
+				if (extension=="GL_EXT_color_buffer_float")
+				{
+					supported = true;
+					break;
+				}
+			}
+		}
+		else
+			supported = true;
+
+		if (!supported)
+			throw tcu::NotSupportedError("Renderbuffer format not supported", "", __FILE__, __LINE__);
+	}
+
+	std::string outtype = "";
+
+	if (m_framebuffeType == TestSpec::FRAMEBUFFERTYPE_NATIVE)
+		outtype = "vec4";
+	else if (m_framebuffeType == TestSpec::FRAMEBUFFERTYPE_RENDERBUFFER)
+	{
+		switch (m_texChannelClass)
+		{
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+				outtype = "vec4";
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+				outtype = "ivec4";
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+				outtype = "uvec4";
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+				outtype = "vec4";
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+
+	const char* vertexShaderSource =
+	"#version 300 es\n"
+	"in mediump vec3 a_position;\n"
+	"in mediump vec4 a_color;\n"
+	"uniform mediump float u_colorScale;\n"
+	"out mediump vec4 v_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\tgl_Position = vec4(a_position, 1.0);\n"
+	"\tv_color = u_colorScale * a_color;\n"
+	"}";
+
+	stringstream fragmentShaderSource;
+
+	fragmentShaderSource <<
+	"#version 300 es\n"
+	"in mediump vec4 v_color;\n";
+
+
+	fragmentShaderSource << "layout (location = 0) out mediump " << outtype << " o_color;\n"
+	"void main(void)\n"
+	"{\n"
+	"\to_color = " << outtype << "(v_color);\n"
+	"}";
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource.str()));
+
+	if (!m_program->isOk())
+	{
+		m_log << *m_program;
+		TCU_FAIL("Failed to compile shader");
+	}
+}
+
+void ReadPixelsTest::deinit (void)
+{
+	if (m_program)
+		delete m_program;
+	m_program = NULL;
+}
+
+void ReadPixelsTest::renderTriangle (const tcu::Vec3& a, const tcu::Vec3& b, const tcu::Vec3& c)
+{
+	float positions[3*3];
+
+	positions[0] = a.x();
+	positions[1] = a.y();
+	positions[2] = a.z();
+
+	positions[3] = b.x();
+	positions[4] = b.y();
+	positions[5] = b.z();
+
+	positions[6] = c.x();
+	positions[7] = c.y();
+	positions[8] = c.z();
+
+	float colors[] = {
+		1.0f, 0.0f, 0.0f, 1.0f,
+		0.0f, 1.0f, 0.0f, 1.0f,
+		0.0f, 0.0f, 1.0f, 1.0f
+	};
+
+	GLU_CHECK_CALL(glUseProgram(m_program->getProgram()));
+
+	GLuint coordLoc = (GLuint)-1;
+	GLuint colorLoc = (GLuint)-1;
+	GLuint colorScaleLoc = (GLuint)-1;
+
+	colorScaleLoc = glGetUniformLocation(m_program->getProgram(), "u_colorScale");
+	TCU_CHECK(colorScaleLoc != (GLuint)-1);
+
+	GLU_CHECK_CALL(glUniform1f(colorScaleLoc, m_colorScale));
+
+	coordLoc = glGetAttribLocation(m_program->getProgram(), "a_position");
+	TCU_CHECK(coordLoc != (GLuint)-1);
+
+	colorLoc = glGetAttribLocation(m_program->getProgram(), "a_color");
+	TCU_CHECK(colorLoc != (GLuint)-1);
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(colorLoc));
+	GLU_CHECK_CALL(glEnableVertexAttribArray(coordLoc));
+
+	GLU_CHECK_CALL(glVertexAttribPointer(coordLoc, 3, GL_FLOAT, GL_FALSE, 0, positions));
+	GLU_CHECK_CALL(glVertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, 0, colors));
+
+	GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 3));
+
+	GLU_CHECK_CALL(glDisableVertexAttribArray(colorLoc));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(coordLoc));
+
+}
+
+
+void ReadPixelsTest::clearColor (float r, float g, float b, float a)
+{
+	if (m_framebuffeType == TestSpec::FRAMEBUFFERTYPE_NATIVE)
+	{
+		GLU_CHECK_CALL(glClearColor(r, g, b, a));
+		GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+	}
+	else if (m_framebuffeType == TestSpec::FRAMEBUFFERTYPE_RENDERBUFFER)
+	{
+		switch (m_texChannelClass)
+		{
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+			{
+				GLU_CHECK_CALL(glClearColor(r, g, b, a));
+				GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+				break;
+			}
+
+			case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+			{
+				GLint color[4] = { (GLint)r, (GLint)g, (GLint)b, (GLint)a };
+
+				GLU_CHECK_CALL(glClearBufferiv(GL_COLOR, 0, color));
+				break;
+			}
+
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+			{
+				GLuint color[4] = { (GLuint)r, (GLuint)g, (GLuint)b, (GLuint)a };
+
+				GLU_CHECK_CALL(glClearBufferuiv(GL_COLOR, 0, color));
+				break;
+			}
+
+			case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+			{
+				GLfloat color[4] = { (GLfloat)r, (GLfloat)g, (GLfloat)b, (GLfloat)a };
+
+				GLU_CHECK_CALL(glClearBufferfv(GL_COLOR, 0, color));
+				break;
+			}
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+}
+
+TestCase::IterateResult ReadPixelsTest::iterate(void)
+{
+	int width				= m_context.getRenderTarget().getWidth();
+	int height				= m_context.getRenderTarget().getHeight();
+
+	GLuint framebuffer	= 0;
+	GLuint renderbuffer	= 0;
+
+	switch (m_framebuffeType)
+	{
+		case TestSpec::FRAMEBUFFERTYPE_NATIVE:
+			GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
+			break;
+
+		case TestSpec::FRAMEBUFFERTYPE_RENDERBUFFER:
+		{
+			GLU_CHECK_CALL(glGenFramebuffers(1, &framebuffer));
+			GLU_CHECK_CALL(glGenRenderbuffers(1, &renderbuffer));
+
+			GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer));
+			GLU_CHECK_CALL(glRenderbufferStorage(GL_RENDERBUFFER, m_renderbufferFormat, width, height));
+
+			GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
+			GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer));
+
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	clearColor(m_colorScale * 0.4f, m_colorScale * 1.0f, m_colorScale * 0.5f, m_colorScale * 1.0f);
+
+	if (m_useColorClears)
+	{
+		const int maxClearCount	= 10;
+		const int minClearCount	= 6;
+		const int minClearSize	= 15;
+
+		int clearCount = m_random.getInt(minClearCount, maxClearCount);
+
+		for (int clearNdx = 0; clearNdx < clearCount; clearNdx++)
+		{
+			int clearX = m_random.getInt(0, width - minClearSize);
+			int clearY = m_random.getInt(0, height - minClearSize);
+
+			int clearWidth = m_random.getInt(minClearSize, width - clearX);
+			int clearHeight = m_random.getInt(minClearSize, height - clearY);
+
+			float clearRed		= m_colorScale * m_random.getFloat();
+			float clearGreen	= m_colorScale * m_random.getFloat();
+			float clearBlue		= m_colorScale * m_random.getFloat();
+			float clearAlpha	= m_colorScale * (0.5f + 0.5f * m_random.getFloat());
+
+			GLU_CHECK_CALL(glEnable(GL_SCISSOR_TEST));
+			GLU_CHECK_CALL(glScissor(clearX, clearY, clearWidth, clearHeight));
+
+			clearColor(clearRed, clearGreen, clearBlue, clearAlpha);
+		}
+
+		GLU_CHECK_CALL(glDisable(GL_SCISSOR_TEST));
+	}
+
+	if (m_renderTriangles)
+	{
+		const int minTriangleCount = 4;
+		const int maxTriangleCount = 10;
+
+		int triangleCount = m_random.getInt(minTriangleCount, maxTriangleCount);
+
+		for (int triangleNdx = 0; triangleNdx < triangleCount; triangleNdx++)
+		{
+			float x1 = 2.0f * m_random.getFloat() - 1.0f;
+			float y1 = 2.0f * m_random.getFloat() - 1.0f;
+			float z1 = 2.0f * m_random.getFloat() - 1.0f;
+
+			float x2 = 2.0f * m_random.getFloat() - 1.0f;
+			float y2 = 2.0f * m_random.getFloat() - 1.0f;
+			float z2 = 2.0f * m_random.getFloat() - 1.0f;
+
+			float x3 = 2.0f * m_random.getFloat() - 1.0f;
+			float y3 = 2.0f * m_random.getFloat() - 1.0f;
+			float z3 = 2.0f * m_random.getFloat() - 1.0f;
+
+			renderTriangle(tcu::Vec3(x1, y1, z1), tcu::Vec3(x2, y2, z2), tcu::Vec3(x3, y3, z3));
+		}
+	}
+
+	tcu::TextureFormat	readFormat;
+	GLenum				readPixelsFormat;
+	GLenum				readPixelsType;
+	bool				floatCompare;
+
+
+	if (m_framebuffeType == TestSpec::FRAMEBUFFERTYPE_NATIVE)
+	{
+		readFormat			= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+		readPixelsFormat	= GL_RGBA;
+		readPixelsType		= GL_UNSIGNED_BYTE;
+		floatCompare		= false;
+	}
+	else if (m_framebuffeType == TestSpec::FRAMEBUFFERTYPE_RENDERBUFFER)
+	{
+		switch (m_texChannelClass)
+		{
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+				readFormat			= glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE);
+				readPixelsFormat	= GL_RGBA;
+				readPixelsType		= GL_UNSIGNED_BYTE;
+				floatCompare		= true;
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+				readFormat			= glu::mapGLTransferFormat(GL_RGBA_INTEGER, GL_INT);
+				readPixelsFormat	= GL_RGBA_INTEGER;
+				readPixelsType		= GL_INT;
+				floatCompare		= false;
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+				readFormat			= glu::mapGLTransferFormat(GL_RGBA_INTEGER, GL_UNSIGNED_INT);
+				readPixelsFormat	= GL_RGBA_INTEGER;
+				readPixelsType		= GL_UNSIGNED_INT;
+				floatCompare		= false;
+				break;
+
+			case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+				readFormat			= glu::mapGLTransferFormat(GL_RGBA, GL_FLOAT);
+				readPixelsFormat	= GL_RGBA;
+				readPixelsType		= GL_FLOAT;
+				floatCompare		= true;
+				break;
+
+			default:
+				DE_ASSERT(false);
+				// Silence warnings
+				readFormat			= glu::mapGLTransferFormat(GL_RGBA, GL_FLOAT);
+				readPixelsFormat	= GL_RGBA;
+				readPixelsType		= GL_FLOAT;
+				floatCompare		= true;
+		}
+	}
+	else
+	{
+		// Silence warnings
+		readFormat			= glu::mapGLTransferFormat(GL_RGBA, GL_FLOAT);
+		readPixelsFormat	= GL_RGBA;
+		readPixelsType		= GL_FLOAT;
+		floatCompare		= true;
+		DE_ASSERT(false);
+	}
+
+	tcu::Texture2D readRefrence(readFormat, width, height);
+	readRefrence.allocLevel(0);
+
+	GLuint pixelBuffer = (GLuint)-1;
+
+	GLU_CHECK_CALL(glGenBuffers(1, &pixelBuffer));
+	GLU_CHECK_CALL(glBindBuffer(GL_PIXEL_PACK_BUFFER, pixelBuffer));
+	GLU_CHECK_CALL(glBufferData(GL_PIXEL_PACK_BUFFER, readRefrence.getLevel(0).getDataSize(), NULL, GL_STREAM_READ));
+
+	GLU_CHECK_CALL(glReadPixels(0, 0, width, height, readPixelsFormat, readPixelsType, 0));
+
+	const deUint8* bufferData = (const deUint8*)glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, readRefrence.getLevel(0).getDataSize(), GL_MAP_READ_BIT);
+	GLU_CHECK_MSG("glMapBufferRange() failed");
+
+	tcu::ConstPixelBufferAccess readResult(readFormat, width, height, 1, bufferData);
+
+	GLU_CHECK_CALL(glBindBuffer(GL_PIXEL_PACK_BUFFER, 0));
+
+	GLU_CHECK_CALL(glReadPixels(0, 0, width, height, readPixelsFormat, readPixelsType, readRefrence.getLevel(0).getDataPtr()));
+
+	if (framebuffer)
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+
+	if (renderbuffer)
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+
+
+	bool isOk = false;
+
+	if (floatCompare)
+		isOk = tcu::floatThresholdCompare(m_log, "Result comparision", "Result of read pixels to memory compared with result of read pixels to buffer", readRefrence.getLevel(0), readResult, tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::COMPARE_LOG_RESULT);
+	else
+		isOk = tcu::intThresholdCompare(m_log, "Result comparision", "Result of read pixels to memory compared with result of read pixels to buffer", readRefrence.getLevel(0), readResult, tcu::UVec4(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);
+
+	GLU_CHECK_CALL(glBindBuffer(GL_PIXEL_PACK_BUFFER, pixelBuffer));
+	GLU_CHECK_CALL(glUnmapBuffer(GL_PIXEL_PACK_BUFFER));
+	GLU_CHECK_CALL(glDeleteBuffers(1, &pixelBuffer));
+
+	if (isOk)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		return STOP;
+	}
+}
+
+} // anonymous
+
+PixelBufferObjectTests::PixelBufferObjectTests (Context& context)
+	: TestCaseGroup (context, "pbo", "Pixel buffer objects tests")
+{
+}
+
+PixelBufferObjectTests::~PixelBufferObjectTests (void)
+{
+}
+
+void PixelBufferObjectTests::init (void)
+{
+	TestCaseGroup* nativeFramebufferGroup = new TestCaseGroup(m_context, "native", "Tests with reading from native framebuffer");
+
+	ReadPixelsTest::TestSpec nativeFramebufferTests[] = {
+		{
+			"clears",
+			"Simple read pixels test with color clears",
+			true,
+			false,
+			ReadPixelsTest::TestSpec::FRAMEBUFFERTYPE_NATIVE,
+			GL_NONE
+		},
+		{
+			"triangles",
+			"Simple read pixels test rendering triangles",
+			false,
+			true,
+			ReadPixelsTest::TestSpec::FRAMEBUFFERTYPE_NATIVE,
+			GL_NONE
+		}
+	};
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(nativeFramebufferTests); testNdx++)
+	{
+		nativeFramebufferGroup->addChild(new ReadPixelsTest(m_context, nativeFramebufferTests[testNdx]));
+	}
+
+	addChild(nativeFramebufferGroup);
+
+	TestCaseGroup* renderbufferGroup = new TestCaseGroup(m_context, "renderbuffer", "Tests with reading from renderbuffer");
+
+	GLenum renderbufferFormats[] = {
+		GL_RGBA8,
+		GL_RGBA8I,
+		GL_RGBA8UI,
+		GL_RGBA16F,
+		GL_RGBA16I,
+		GL_RGBA16UI,
+		GL_RGBA32F,
+		GL_RGBA32I,
+		GL_RGBA32UI,
+
+		GL_SRGB8_ALPHA8,
+		GL_RGB10_A2,
+		GL_RGB10_A2UI,
+		GL_RGBA4,
+		GL_RGB5_A1,
+
+		GL_RGB8,
+		GL_RGB565,
+
+		GL_R11F_G11F_B10F,
+
+		GL_RG8,
+		GL_RG8I,
+		GL_RG8UI,
+		GL_RG16F,
+		GL_RG16I,
+		GL_RG16UI,
+		GL_RG32F,
+		GL_RG32I,
+		GL_RG32UI
+	};
+
+	const char* renderbufferFormatsStr[] = {
+		"rgba8",
+		"rgba8i",
+		"rgba8ui",
+		"rgba16f",
+		"rgba16i",
+		"rgba16ui",
+		"rgba32f",
+		"rgba32i",
+		"rgba32ui",
+
+		"srgb8_alpha8",
+		"rgb10_a2",
+		"rgb10_a2ui",
+		"rgba4",
+		"rgb5_a1",
+
+		"rgb8",
+		"rgb565",
+
+		"r11f_g11f_b10f",
+
+		"rg8",
+		"rg8i",
+		"rg8ui",
+		"rg16f",
+		"rg16i",
+		"rg16ui",
+		"rg32f",
+		"rg32i",
+		"rg32ui"
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(renderbufferFormatsStr) == DE_LENGTH_OF_ARRAY(renderbufferFormats));
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(renderbufferFormats); formatNdx++)
+	{
+		for (int trianglesClears = 0; trianglesClears < 2; trianglesClears++)
+		{
+			ReadPixelsTest::TestSpec testSpec;
+
+			testSpec.name					= string(renderbufferFormatsStr [formatNdx]) + "_" + (trianglesClears == 0 ? "triangles" : "clears"),
+			testSpec.description			= testSpec.name;
+			testSpec.useColorClear			= trianglesClears == 1,
+			testSpec.renderTriangles		= trianglesClears == 0,
+			testSpec.framebufferType		= ReadPixelsTest::TestSpec::FRAMEBUFFERTYPE_RENDERBUFFER,
+			testSpec.renderbufferFormat		= renderbufferFormats[formatNdx];
+
+			renderbufferGroup->addChild(new ReadPixelsTest(m_context, testSpec));
+		}
+	}
+
+	addChild(renderbufferGroup);
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fPixelBufferObjectTests.hpp b/modules/gles3/functional/es3fPixelBufferObjectTests.hpp
new file mode 100644
index 0000000..de8a95f
--- /dev/null
+++ b/modules/gles3/functional/es3fPixelBufferObjectTests.hpp
@@ -0,0 +1,48 @@
+#ifndef _ES3FPIXELBUFFEROBJECTTESTS_HPP
+#define _ES3FPIXELBUFFEROBJECTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Pixel Buffer Object tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class PixelBufferObjectTests : public TestCaseGroup
+{
+public:
+					PixelBufferObjectTests	(Context& context);
+	virtual			~PixelBufferObjectTests	(void);
+	virtual void	init					(void);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FPIXELBUFFEROBJECTTESTS_HPP
diff --git a/modules/gles3/functional/es3fPolygonOffsetTests.cpp b/modules/gles3/functional/es3fPolygonOffsetTests.cpp
new file mode 100644
index 0000000..554539d
--- /dev/null
+++ b/modules/gles3/functional/es3fPolygonOffsetTests.cpp
@@ -0,0 +1,1252 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Polygon offset tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fPolygonOffsetTests.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+#include "glwFunctions.hpp"
+#include "tcuTestContext.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuVectorUtil.hpp"
+#include "rrRenderer.hpp"
+#include "rrFragmentOperations.hpp"
+
+#include "sglrReferenceContext.hpp"
+
+#include <string>
+#include <limits>
+
+using namespace glw; // GLint and other GL types
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+const char* s_shaderSourceVertex	= "#version 300 es\n"
+									  "in highp vec4 a_position;\n"
+									  "in highp vec4 a_color;\n"
+									  "out highp vec4 v_color;\n"
+									  "void main (void)\n"
+									  "{\n"
+									  "	gl_Position = a_position;\n"
+									  "	v_color = a_color;\n"
+									  "}\n";
+const char* s_shaderSourceFragment	= "#version 300 es\n"
+									  "in highp vec4 v_color;\n"
+									  "layout(location = 0) out mediump vec4 fragColor;"
+									  "void main (void)\n"
+									  "{\n"
+									  "	fragColor = v_color;\n"
+									  "}\n";
+
+static const tcu::Vec4	MASK_COLOR_OK	= tcu::Vec4(0.0f, 0.1f, 0.0f, 1.0f);
+static const tcu::Vec4	MASK_COLOR_DEV	= tcu::Vec4(0.8f, 0.5f, 0.0f, 1.0f);
+static const tcu::Vec4	MASK_COLOR_FAIL	= tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f);
+
+inline bool compareThreshold (const tcu::IVec4& a, const tcu::IVec4& b, const tcu::IVec4& threshold)
+{
+	return tcu::boolAll(tcu::lessThanEqual(tcu::abs(a - b), threshold));
+}
+
+/*--------------------------------------------------------------------*//*!
+* \brief Pixelwise comparison of two images.
+* \note copied & modified from glsRasterizationTests
+*
+* Kernel radius defines maximum allowed distance. If radius is 0, only
+* perfect match is allowed. Radius of 1 gives a 3x3 kernel.
+*
+* Return values: -1 = Perfect match
+* 0 = Deviation within kernel
+* >0 = Number of faulty pixels
+*//*--------------------------------------------------------------------*/
+int compareImages (tcu::TestLog& log, glu::RenderContext& renderCtx, const tcu::ConstPixelBufferAccess& test, const tcu::ConstPixelBufferAccess& ref, const tcu::PixelBufferAccess& diffMask, int radius)
+{
+	const int			height			= test.getHeight();
+	const int			width			= test.getWidth();
+	const int			colorThreshold	= 128;
+	const tcu::RGBA		formatThreshold	= renderCtx.getRenderTarget().getPixelFormat().getColorThreshold();
+	const tcu::IVec4	threshold		= tcu::IVec4(colorThreshold, colorThreshold, colorThreshold, formatThreshold.getAlpha() > 0 ? colorThreshold : 0)
+										+ tcu::IVec4(formatThreshold.getRed(), formatThreshold.getGreen(), formatThreshold.getBlue(), formatThreshold.getAlpha());
+
+	int			deviatingPixels = 0;
+	int			faultyPixels	= 0;
+	int			compareFailed	= -1;
+
+	tcu::clear(diffMask, MASK_COLOR_OK);
+
+	for (int y = 0; y < height; y++)
+	{
+		for (int x = 0; x < width; x++)
+		{
+			const tcu::IVec4 cRef = ref.getPixelInt(x, y);
+
+			// Pixelwise match, no deviation or fault
+			{
+				const tcu::IVec4 cTest = test.getPixelInt(x, y);
+				if (compareThreshold(cRef, cTest, threshold))
+					continue;
+			}
+
+			// If not, search within kernel radius
+			{
+				const int kYmin = deMax32(y - radius, 0);
+				const int kYmax = deMin32(y + radius, height-1);
+				const int kXmin = deMax32(x - radius, 0);
+				const int kXmax = deMin32(x + radius, width-1);
+				bool found = false;
+
+				for (int kY = kYmin; kY <= kYmax; kY++)
+				for (int kX = kXmin; kX <= kXmax; kX++)
+				{
+					const tcu::IVec4 cTest = test.getPixelInt(kX, kY);
+					if (compareThreshold(cRef, cTest, threshold))
+						found = true;
+				}
+
+				if (found)	// The pixel is deviating if the color is found inside the kernel
+				{
+					diffMask.setPixel(MASK_COLOR_DEV, x, y);
+					if (compareFailed == -1)
+						compareFailed = 0;
+					deviatingPixels++;
+					continue;
+				}
+			}
+
+			diffMask.setPixel(MASK_COLOR_FAIL, x, y);
+			faultyPixels++;										// The pixel is faulty if the color is not found
+			compareFailed = 1;
+		}
+	}
+
+	log << tcu::TestLog::Message << faultyPixels << " faulty pixel(s) found." << tcu::TestLog::EndMessage;
+
+	return (compareFailed == 1 ? faultyPixels : compareFailed);
+}
+
+void verifyImages (tcu::TestLog& log, tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const tcu::ConstPixelBufferAccess& testImage, const tcu::ConstPixelBufferAccess& referenceImage)
+{
+	using tcu::TestLog;
+
+	const int			kernelRadius		= 1;
+	const int			faultyPixelLimit	= 20;
+	int					faultyPixels;
+	tcu::Surface		diffMask			(testImage.getWidth(), testImage.getHeight());
+
+	faultyPixels = compareImages(log, renderCtx, referenceImage, testImage, diffMask.getAccess(), kernelRadius);
+
+	if (faultyPixels > faultyPixelLimit)
+	{
+		log << TestLog::ImageSet("Images", "Image comparison");
+		log << TestLog::Image("Test image", "Test image", testImage);
+		log << TestLog::Image("Reference image", "Reference image", referenceImage);
+		log << TestLog::Image("Difference mask", "Difference mask", diffMask.getAccess());
+		log << TestLog::EndImageSet;
+
+		log << tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
+	}
+}
+
+void verifyError (tcu::TestContext& testCtx, const glw::Functions& gl, GLenum expected)
+{
+	deUint32 got = gl.getError();
+	if (got != expected)
+	{
+		testCtx.getLog() << tcu::TestLog::Message << "// ERROR: expected " << glu::getErrorStr(expected) << "; got " << glu::getErrorStr(got) << tcu::TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid error");
+	}
+}
+
+void checkCanvasSize (int width, int height, int minWidth, int minHeight)
+{
+	if (width < minWidth || height < minHeight)
+		throw tcu::NotSupportedError(std::string("Render context size must be at least ") + de::toString(minWidth) + "x" + de::toString(minWidth));
+}
+
+class PositionColorShader : public sglr::ShaderProgram
+{
+public:
+	enum
+	{
+		VARYINGLOC_COLOR = 0
+	};
+
+			PositionColorShader (void);
+	void	shadeVertices		(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void	shadeFragments		(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+};
+
+PositionColorShader::PositionColorShader (void)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(s_shaderSourceVertex)
+							<< sglr::pdec::FragmentSource(s_shaderSourceFragment))
+{
+}
+
+void PositionColorShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const int positionAttrLoc = 0;
+		const int colorAttrLoc = 1;
+
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		// Transform to position
+		packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx);
+
+		// Pass color to FS
+		packet.outputs[VARYINGLOC_COLOR] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void PositionColorShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::FragmentPacket& packet = packets[packetNdx];
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readTriangleVarying<float>(packet, context, VARYINGLOC_COLOR, fragNdx));
+	}
+}
+
+// PolygonOffsetTestCase
+
+class PolygonOffsetTestCase : public TestCase
+{
+public:
+					PolygonOffsetTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName, int canvasSize);
+
+	virtual void	testPolygonOffset		(void) = DE_NULL;
+	IterateResult	iterate					(void);
+
+protected:
+	const GLenum	m_internalFormat;
+	const char*		m_internalFormatName;
+	const int		m_targetSize;
+};
+
+PolygonOffsetTestCase::PolygonOffsetTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName, int canvasSize)
+	: TestCase				(context, name, description)
+	, m_internalFormat		(internalFormat)
+	, m_internalFormatName	(internalFormatName)
+	, m_targetSize			(canvasSize)
+{
+}
+
+PolygonOffsetTestCase::IterateResult PolygonOffsetTestCase::iterate (void)
+{
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	m_testCtx.getLog() << tcu::TestLog::Message << "Testing PolygonOffset with " << m_internalFormatName << " depth buffer." << tcu::TestLog::EndMessage;
+
+	if (m_internalFormat == 0)
+	{
+		// default framebuffer
+		const int width		= m_context.getRenderTarget().getWidth();
+		const int height	= m_context.getRenderTarget().getHeight();
+
+		checkCanvasSize(width, height, m_targetSize, m_targetSize);
+
+		if (m_context.getRenderTarget().getDepthBits() == 0)
+			throw tcu::NotSupportedError("polygon offset tests require depth buffer");
+
+		testPolygonOffset();
+	}
+	else
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		// framebuffer object
+		GLuint	colorRboId	= 0;
+		GLuint	depthRboId	= 0;
+		GLuint	fboId		= 0;
+		bool	fboComplete;
+
+		gl.genRenderbuffers(1, &colorRboId);
+		gl.bindRenderbuffer(GL_RENDERBUFFER, colorRboId);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, m_targetSize, m_targetSize);
+		verifyError(m_testCtx, gl, GL_NO_ERROR);
+
+		gl.genRenderbuffers(1, &depthRboId);
+		gl.bindRenderbuffer(GL_RENDERBUFFER, depthRboId);
+		gl.renderbufferStorage(GL_RENDERBUFFER, m_internalFormat, m_targetSize, m_targetSize);
+		verifyError(m_testCtx, gl, GL_NO_ERROR);
+
+		gl.genFramebuffers(1, &fboId);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, fboId);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRboId);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,	GL_RENDERBUFFER, depthRboId);
+		verifyError(m_testCtx, gl, GL_NO_ERROR);
+
+		fboComplete = gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
+
+		if (fboComplete)
+			testPolygonOffset();
+
+		gl.deleteFramebuffers(1, &fboId);
+		gl.deleteRenderbuffers(1, &depthRboId);
+		gl.deleteRenderbuffers(1, &colorRboId);
+
+		if (!fboComplete)
+			throw tcu::NotSupportedError("could not create fbo for testing.");
+	}
+
+	return STOP;
+}
+
+// UsageTestCase
+
+class UsageTestCase : public PolygonOffsetTestCase
+{
+public:
+			UsageTestCase		(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset	(void);
+};
+
+UsageTestCase::UsageTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void UsageTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangle[] =
+	{
+		tcu::Vec4(-1,  1,  0,  1),
+		tcu::Vec4( 1,  1,  0,  1),
+		tcu::Vec4( 1, -1,  0,  1),
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// render test image
+	{
+		const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program		(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc = gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc	= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.depthFunc				(GL_LEQUAL);	// make test pass if polygon offset doesn't do anything. It has its own test case. This test is only for to detect always-on cases.
+
+		log << TestLog::Message << "DepthFunc = GL_LEQUAL" << TestLog::EndMessage;
+
+		gl.enableVertexAttribArray	(positionLoc);
+		gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangle);
+
+		//draw back (offset disabled)
+
+		log << TestLog::Message << "Draw bottom-right. Color = White.\tState: PolygonOffset(0, -2), POLYGON_OFFSET_FILL disabled." << TestLog::EndMessage;
+
+		gl.polygonOffset			(0, -2);
+		gl.disable					(GL_POLYGON_OFFSET_FILL);
+		gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+		gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+		//draw front
+
+		log << TestLog::Message << "Draw bottom-right. Color = Red.\tState: PolygonOffset(0, -1), POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		gl.polygonOffset			(0, -1);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.vertexAttrib4f			(colorLoc, 1.0f, 0.0f, 0.0f, 1.0f);
+		gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	{
+		rr::Renderer		referenceRenderer;
+		rr::VertexAttrib	attribs[2];
+		rr::RenderState		state((rr::ViewportState)(rr::WindowRectangle(0, 0, m_targetSize, m_targetSize)));
+
+		PositionColorShader program;
+
+		attribs[0].type				= rr::VERTEXATTRIBTYPE_FLOAT;
+		attribs[0].size				= 4;
+		attribs[0].stride			= 0;
+		attribs[0].instanceDivisor	= 0;
+		attribs[0].pointer			= triangle;
+
+		attribs[1].type				= rr::VERTEXATTRIBTYPE_DONT_CARE;
+		attribs[1].generic			= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+
+		tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+		log << TestLog::Message << "Expecting: Bottom-right = Red." << TestLog::EndMessage;
+
+		referenceRenderer.draw(
+			rr::DrawCommand(
+				state,
+				rr::RenderTarget(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(referenceImage.getAccess())),
+				rr::Program(program.getVertexShader(), program.getFragmentShader()),
+				2,
+				attribs,
+				rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, 3, 0)));
+	}
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// UsageDisplacementTestCase
+
+class UsageDisplacementTestCase : public PolygonOffsetTestCase
+{
+public:
+				UsageDisplacementTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+private:
+	tcu::Vec4	genRandomVec4				(de::Random& rnd) const;
+	void		testPolygonOffset			(void);
+};
+
+UsageDisplacementTestCase::UsageDisplacementTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+tcu::Vec4 UsageDisplacementTestCase::genRandomVec4 (de::Random& rnd) const
+{
+	// generater triangle endpoint with following properties
+	//	1) it will not be clipped
+	//	2) it is not near either far or near plane to prevent possible problems related to depth clamping
+	// => w >= 1.0 and z in (-0.9, 0.9) range
+	tcu::Vec4 retVal;
+
+	retVal.x() = rnd.getFloat(-1, 1);
+	retVal.y() = rnd.getFloat(-1, 1);
+	retVal.z() = rnd.getFloat(-0.9f, 0.9f);
+	retVal.w() = 1.0f + rnd.getFloat();
+
+	return retVal;
+}
+
+void UsageDisplacementTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	de::Random			rnd				(0xdec0de);
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// render test image
+	{
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program			(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc		= gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc		= gl.getAttribLocation(program.getProgram(), "a_color");
+		const int					numIterations	= 40;
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+		gl.vertexAttrib4f			(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+
+		log << TestLog::Message << "Framebuffer cleared, clear color = Black." << TestLog::EndMessage;
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		// draw colorless (mask = 0,0,0) triangle at random* location, set offset and render green triangle with depthfunc = equal
+		// *) w >= 1.0 and z in (-1, 1) range
+		for (int iterationNdx = 0; iterationNdx < numIterations; ++iterationNdx)
+		{
+			const bool		offsetDirection = rnd.getBool();
+			const float		offset = offsetDirection ? -1.0f : 1.0f;
+			tcu::Vec4		triangle[3];
+
+			for (int vertexNdx = 0; vertexNdx < DE_LENGTH_OF_ARRAY(triangle); ++vertexNdx)
+				triangle[vertexNdx] = genRandomVec4(rnd);
+
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangle);
+
+			log << TestLog::Message << "Setup triangle with random coordinates:" << TestLog::EndMessage;
+			for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(triangle); ++ndx)
+				log << TestLog::Message
+						<< "\tx=" << triangle[ndx].x()
+						<< "\ty=" << triangle[ndx].y()
+						<< "\tz=" << triangle[ndx].z()
+						<< "\tw=" << triangle[ndx].w()
+						<< TestLog::EndMessage;
+
+			log << TestLog::Message << "Draw colorless triangle.\tState: DepthFunc = GL_ALWAYS, PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, 0);
+			gl.colorMask				(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			// all fragments should have different Z => DepthFunc == GL_EQUAL fails with every fragment
+
+			log << TestLog::Message << "Draw green triangle.\tState: DepthFunc = GL_EQUAL, PolygonOffset(0, " << offset << ")." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_EQUAL);
+			gl.polygonOffset			(0, offset);
+			gl.colorMask				(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << TestLog::EndMessage; // empty line for clarity
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	log << TestLog::Message << "Expecting black framebuffer." << TestLog::EndMessage;
+	tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// UsagePositiveNegativeTestCase
+
+class UsagePositiveNegativeTestCase : public PolygonOffsetTestCase
+{
+public:
+			UsagePositiveNegativeTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset				(void);
+};
+
+UsagePositiveNegativeTestCase::UsagePositiveNegativeTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void UsagePositiveNegativeTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangleBottomRight[] =
+	{
+		tcu::Vec4(-1,  1,  0,  1),
+		tcu::Vec4( 1,  1,  0,  1),
+		tcu::Vec4( 1, -1,  0,  1),
+	};
+	const tcu::Vec4 triangleTopLeft[] =
+	{
+		tcu::Vec4(-1, -1,  0,  1),
+		tcu::Vec4(-1,  1,  0,  1),
+		tcu::Vec4( 1, -1,  0,  1),
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// render test image
+	{
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program			(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc		= gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc		= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.depthFunc				(GL_LESS);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+
+		log << TestLog::Message << "DepthFunc = GL_LESS." << TestLog::EndMessage;
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		//draw top left (negative offset test)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleTopLeft);
+
+			log << TestLog::Message << "Draw top-left. Color = White.\tState: PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.polygonOffset			(0, 0);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw top-left. Color = Green.\tState: PolygonOffset(0, -1)." << TestLog::EndMessage;
+
+			gl.polygonOffset			(0, -1);
+			gl.vertexAttrib4f			(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		//draw bottom right (positive offset test)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleBottomRight);
+
+			log << TestLog::Message << "Draw bottom-right. Color = White.\tState: PolygonOffset(0, 1)." << TestLog::EndMessage;
+
+			gl.polygonOffset			(0, 1);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw bottom-right. Color = Yellow.\tState: PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.polygonOffset			(0, 0);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	{
+		rr::Renderer		referenceRenderer;
+		rr::VertexAttrib	attribs[2];
+		rr::RenderState		state((rr::ViewportState)(rr::WindowRectangle(0, 0, m_targetSize, m_targetSize)));
+
+		PositionColorShader program;
+
+		attribs[0].type				= rr::VERTEXATTRIBTYPE_FLOAT;
+		attribs[0].size				= 4;
+		attribs[0].stride			= 0;
+		attribs[0].instanceDivisor	= 0;
+		attribs[0].pointer			= triangleTopLeft;
+
+		attribs[1].type				= rr::VERTEXATTRIBTYPE_DONT_CARE;
+		attribs[1].generic			= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+
+		tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+		log << TestLog::Message << "Expecting: Top-left = Green, Bottom-right = Yellow." << TestLog::EndMessage;
+
+		referenceRenderer.draw(
+			rr::DrawCommand(
+				state,
+				rr::RenderTarget(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(referenceImage.getAccess())),
+				rr::Program(program.getVertexShader(), program.getFragmentShader()),
+				2,
+				attribs,
+				rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, 3, 0)));
+
+		attribs[0].pointer = triangleBottomRight;
+		attribs[1].generic = tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
+
+		referenceRenderer.draw(
+			rr::DrawCommand(
+				state,
+				rr::RenderTarget(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(referenceImage.getAccess())),
+				rr::Program(program.getVertexShader(), program.getFragmentShader()),
+				2,
+				attribs,
+				rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, 3, 0)));
+	}
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// ResultClampingTestCase
+
+class ResultClampingTestCase : public PolygonOffsetTestCase
+{
+public:
+			ResultClampingTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset		(void);
+};
+
+ResultClampingTestCase::ResultClampingTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void ResultClampingTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangleBottomRight[] =
+	{
+		tcu::Vec4(-1,  1,  1,  1),
+		tcu::Vec4( 1,  1,  1,  1),
+		tcu::Vec4( 1, -1,  1,  1),
+	};
+	const tcu::Vec4 triangleTopLeft[] =
+	{
+		tcu::Vec4(-1, -1, -1,  1),
+		tcu::Vec4(-1,  1, -1,  1),
+		tcu::Vec4( 1, -1, -1,  1),
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// render test image
+	{
+		const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program		(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc = gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc	= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		//draw bottom right (far)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleBottomRight);
+
+			log << TestLog::Message << "Draw bottom-right. Color = White.\tState: DepthFunc = ALWAYS, PolygonOffset(0, 8), Polygon Z = 1.0. (Result depth should clamp to 1.0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, 8);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw bottom-right. Color = Red.\tState: DepthFunc = GREATER, PolygonOffset(0, 9), Polygon Z = 1.0. (Result depth should clamp to 1.0 too)" << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_GREATER);
+			gl.polygonOffset			(0, 9);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 0.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		//draw top left (near)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleTopLeft);
+
+			log << TestLog::Message << "Draw top-left. Color = White.\tState: DepthFunc = ALWAYS, PolygonOffset(0, -8), Polygon Z = -1.0. (Result depth should clamp to -1.0)" << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, -8);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw top-left. Color = Yellow.\tState: DepthFunc = LESS, PolygonOffset(0, -9), Polygon Z = -1.0. (Result depth should clamp to -1.0 too)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_LESS);
+			gl.polygonOffset			(0, -9);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	log << TestLog::Message << "Expecting: Top-left = White, Bottom-right = White." << TestLog::EndMessage;
+	tcu::clear(referenceImage.getAccess(), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// UsageSlopeTestCase
+
+class UsageSlopeTestCase  : public PolygonOffsetTestCase
+{
+public:
+			UsageSlopeTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset	(void);
+};
+
+UsageSlopeTestCase::UsageSlopeTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void UsageSlopeTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangleBottomRight[] =
+	{
+		tcu::Vec4(-1,  1,  0.0f,  1),
+		tcu::Vec4( 1,  1,  0.9f,  1),
+		tcu::Vec4( 1, -1,  0.9f,  1),
+	};
+	const tcu::Vec4 triangleTopLeft[] =
+	{
+		tcu::Vec4(-1, -1,  -0.9f,  1),
+		tcu::Vec4(-1,  1,   0.9f,  1),
+		tcu::Vec4( 1, -1,   0.0f,  1),
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// render test image
+	{
+		const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program		(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc = gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc	= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		//draw top left (negative offset test)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleTopLeft);
+
+			log << TestLog::Message << "Draw top-left. Color = White.\tState: DepthFunc = ALWAYS, PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, 0);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw top-left. Color = Green.\tState: DepthFunc = LESS, PolygonOffset(-1, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_LESS);
+			gl.polygonOffset			(-1, 0);
+			gl.vertexAttrib4f			(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		//draw bottom right (positive offset test)
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangleBottomRight);
+
+			log << TestLog::Message << "Draw bottom-right. Color = White.\tState: DepthFunc = ALWAYS, PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, 0);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw bottom-right. Color = Green.\tState: DepthFunc = GREATER, PolygonOffset(1, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_GREATER);
+			gl.polygonOffset			(1, 0);
+			gl.vertexAttrib4f			(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	log << TestLog::Message << "Expecting: Top-left = Green, Bottom-right = Green." << TestLog::EndMessage;
+	tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// ZeroSlopeTestCase
+
+class ZeroSlopeTestCase : public PolygonOffsetTestCase
+{
+public:
+			ZeroSlopeTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset	(void);
+};
+
+ZeroSlopeTestCase::ZeroSlopeTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void ZeroSlopeTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangle[] =
+	{
+		tcu::Vec4(-0.4f,  0.4f, 0.0f, 1.0f),
+		tcu::Vec4(-0.8f, -0.5f, 0.0f, 1.0f),
+		tcu::Vec4( 0.7f,  0.2f, 0.0f, 1.0f),
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// log the triangle
+	log << TestLog::Message << "Setup triangle with coordinates:" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(triangle); ++ndx)
+		log << TestLog::Message
+				<< "\tx=" << triangle[ndx].x()
+				<< "\ty=" << triangle[ndx].y()
+				<< "\tz=" << triangle[ndx].z()
+				<< "\tw=" << triangle[ndx].w()
+				<< TestLog::EndMessage;
+
+	// render test image
+	{
+		const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program		(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc = gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc	= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clearDepthf				(1.0f);
+		gl.clear					(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		{
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangle);
+
+			log << TestLog::Message << "Draw triangle. Color = Red.\tState: DepthFunc = ALWAYS, PolygonOffset(0, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_ALWAYS);
+			gl.polygonOffset			(0, 0);
+			gl.vertexAttrib4f			(colorLoc, 1.0f, 0.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+
+			log << TestLog::Message << "Draw triangle. Color = Black.\tState: DepthFunc = EQUAL, PolygonOffset(4, 0)." << TestLog::EndMessage;
+
+			gl.depthFunc				(GL_EQUAL);
+			gl.polygonOffset			(4, 0);	// triangle slope == 0
+			gl.vertexAttrib4f			(colorLoc, 0.0f, 0.0f, 0.0f, 1.0f);
+			gl.drawArrays				(GL_TRIANGLES, 0, 3);
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	log << TestLog::Message << "Expecting black triangle." << TestLog::EndMessage;
+	tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+// OneSlopeTestCase
+
+class OneSlopeTestCase : public PolygonOffsetTestCase
+{
+public:
+			OneSlopeTestCase	(Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName);
+
+	void	testPolygonOffset	(void);
+};
+
+OneSlopeTestCase::OneSlopeTestCase (Context& context, const char* name, const char* description, GLenum internalFormat, const char* internalFormatName)
+	: PolygonOffsetTestCase(context, name, description, internalFormat, internalFormatName, 200)
+{
+}
+
+void OneSlopeTestCase::testPolygonOffset (void)
+{
+	using tcu::TestLog;
+
+	/*
+		* setup vertices subject to following properties
+		*   dz_w / dx_w == 1
+		*   dz_w / dy_w == 0
+		* or
+		*   dz_w / dx_w == 0
+		*   dz_w / dy_w == 1
+		* ==> m == 1
+		*/
+	const float cornerDepth = float(m_targetSize);
+	const tcu::Vec4 triangles[2][3] =
+	{
+		{
+			tcu::Vec4(-1, -1, -cornerDepth, 1),
+			tcu::Vec4(-1,  1, -cornerDepth, 1),
+			tcu::Vec4( 1, -1,  cornerDepth, 1),
+		},
+		{
+			tcu::Vec4(-1,  1,  cornerDepth, 1),
+			tcu::Vec4( 1,  1,  cornerDepth, 1),
+			tcu::Vec4( 1, -1, -cornerDepth, 1),
+		},
+	};
+
+	tcu::TestLog&		log				= m_testCtx.getLog();
+	tcu::Surface		testImage		(m_targetSize, m_targetSize);
+	tcu::Surface		referenceImage	(m_targetSize, m_targetSize);
+
+	// log triangle info
+	log << TestLog::Message << "Setup triangle0 coordinates: (slope in window coordinates = 1.0)" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(triangles[0]); ++ndx)
+		log << TestLog::Message
+				<< "\tx=" << triangles[0][ndx].x()
+				<< "\ty=" << triangles[0][ndx].y()
+				<< "\tz=" << triangles[0][ndx].z()
+				<< "\tw=" << triangles[0][ndx].w()
+				<< TestLog::EndMessage;
+	log << TestLog::Message << "Setup triangle1 coordinates: (slope in window coordinates = 1.0)" << TestLog::EndMessage;
+	for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(triangles[1]); ++ndx)
+		log << TestLog::Message
+				<< "\tx=" << triangles[1][ndx].x()
+				<< "\ty=" << triangles[1][ndx].y()
+				<< "\tz=" << triangles[1][ndx].z()
+				<< "\tw=" << triangles[1][ndx].w()
+				<< TestLog::EndMessage;
+
+	// render test image
+	{
+		const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	program		(m_context.getRenderContext(), glu::makeVtxFragSources(s_shaderSourceVertex, s_shaderSourceFragment));
+		const GLint					positionLoc = gl.getAttribLocation(program.getProgram(), "a_position");
+		const GLint					colorLoc	= gl.getAttribLocation(program.getProgram(), "a_color");
+
+		if (!program.isOk())
+		{
+			log << program;
+			TCU_FAIL("Shader compile failed.");
+		}
+
+		gl.clearColor				(0, 0, 0, 1);
+		gl.clear					(GL_COLOR_BUFFER_BIT);
+		gl.viewport					(0, 0, m_targetSize, m_targetSize);
+		gl.useProgram				(program.getProgram());
+		gl.enable					(GL_DEPTH_TEST);
+		gl.enable					(GL_POLYGON_OFFSET_FILL);
+		gl.enableVertexAttribArray	(positionLoc);
+
+		log << TestLog::Message << "Framebuffer cleared, clear color = Black." << TestLog::EndMessage;
+		log << TestLog::Message << "POLYGON_OFFSET_FILL enabled." << TestLog::EndMessage;
+
+		// top left (positive offset)
+		{
+			log << TestLog::Message << "Clear depth to 1.0." << TestLog::EndMessage;
+
+			gl.clearDepthf			(1.0f); // far
+			gl.clear				(GL_DEPTH_BUFFER_BIT);
+
+			gl.vertexAttribPointer	(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangles[0]);
+
+			log << TestLog::Message << "Draw triangle0. Color = Red.\tState: DepthFunc = NOTEQUAL, PolygonOffset(10, 0). (Result depth should clamp to 1.0)." << TestLog::EndMessage;
+
+			gl.polygonOffset		(10, 0);		// clamps any depth on the triangle to 1
+			gl.depthFunc			(GL_NOTEQUAL);
+			gl.vertexAttrib4f		(colorLoc, 1.0f, 0.0f, 0.0f, 1.0f);
+			gl.drawArrays			(GL_TRIANGLES, 0, 3);
+		}
+		// bottom right (negative offset)
+		{
+			log << TestLog::Message << "Clear depth to 0.0." << TestLog::EndMessage;
+
+			gl.clearDepthf			(0.0f); // far
+			gl.clear				(GL_DEPTH_BUFFER_BIT);
+
+			gl.vertexAttribPointer	(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangles[1]);
+
+			log << TestLog::Message << "Draw triangle1. Color = Green.\tState: DepthFunc = NOTEQUAL, PolygonOffset(-10, 0). (Result depth should clamp to 0.0)." << TestLog::EndMessage;
+
+			gl.polygonOffset		(-10, 0); // clamps depth to 0
+			gl.depthFunc			(GL_NOTEQUAL);
+			gl.vertexAttrib4f		(colorLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+			gl.drawArrays			(GL_TRIANGLES, 0, 3);
+		}
+
+		gl.disableVertexAttribArray	(positionLoc);
+		gl.useProgram				(0);
+		gl.finish					();
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, testImage.getAccess());
+	}
+
+	// render reference image
+	log << TestLog::Message << "Expecting black framebuffer." << TestLog::EndMessage;
+	tcu::clear(referenceImage.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	// compare
+	verifyImages(log, m_testCtx, m_context.getRenderContext(), testImage.getAccess(), referenceImage.getAccess());
+}
+
+} // anonymous
+
+PolygonOffsetTests::PolygonOffsetTests (Context& context)
+	: TestCaseGroup(context, "polygon_offset", "Polygon offset tests")
+{
+}
+
+PolygonOffsetTests::~PolygonOffsetTests (void)
+{
+}
+
+void PolygonOffsetTests::init (void)
+{
+	const struct DepthBufferFormat
+	{
+		enum BufferType
+		{
+			TYPE_FIXED_POINT,
+			TYPE_FLOATING_POINT,
+			TYPE_UNKNOWN
+		};
+
+		GLenum		internalFormat;
+		int			bits;
+		BufferType	floatingPoint;
+		const char* name;
+	} depthFormats[]=
+	{
+		{ 0,						0,		DepthBufferFormat::TYPE_UNKNOWN,		"default" },
+		{ GL_DEPTH_COMPONENT16,		16,		DepthBufferFormat::TYPE_FIXED_POINT,	"fixed16" },
+		{ GL_DEPTH_COMPONENT24,		24,		DepthBufferFormat::TYPE_FIXED_POINT,	"fixed24" },
+		{ GL_DEPTH_COMPONENT32F,	32,		DepthBufferFormat::TYPE_FLOATING_POINT,	"float32" },
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthFormats); ++ndx)
+	{
+		const DepthBufferFormat& format = depthFormats[ndx];
+
+		// enable works?
+		addChild(new UsageTestCase(m_context, (std::string(format.name) + "_enable").c_str(), "test enable GL_POLYGON_OFFSET_FILL", format.internalFormat, format.name));
+
+		// Really moves the polygons ?
+		addChild(new UsageDisplacementTestCase(m_context, (std::string(format.name) + "_displacement_with_units").c_str(), "test polygon offset", format.internalFormat, format.name));
+
+		// Really moves the polygons to right direction ?
+		addChild(new UsagePositiveNegativeTestCase(m_context, (std::string(format.name) + "_render_with_units").c_str(), "test polygon offset", format.internalFormat, format.name));
+
+		// Is total result clamped to [0,1] like promised?
+		addChild(new ResultClampingTestCase(m_context, (std::string(format.name) + "_result_depth_clamp").c_str(), "test polygon offset clamping", format.internalFormat, format.name));
+
+		// Slope really moves the polygon?
+		addChild(new UsageSlopeTestCase(m_context, (std::string(format.name) + "_render_with_factor").c_str(), "test polygon offset factor", format.internalFormat, format.name));
+
+		// Factor with zero slope
+		addChild(new ZeroSlopeTestCase(m_context, (std::string(format.name) + "_factor_0_slope").c_str(), "test polygon offset factor", format.internalFormat, format.name));
+
+		// Factor with 1.0 slope
+		addChild(new OneSlopeTestCase(m_context, (std::string(format.name) + "_factor_1_slope").c_str(), "test polygon offset factor", format.internalFormat, format.name));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fPolygonOffsetTests.hpp b/modules/gles3/functional/es3fPolygonOffsetTests.hpp
new file mode 100644
index 0000000..2a1f7ed
--- /dev/null
+++ b/modules/gles3/functional/es3fPolygonOffsetTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FPOLYGONOFFSETTESTS_HPP
+#define _ES3FPOLYGONOFFSETTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Polygon offset tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class PolygonOffsetTests : public TestCaseGroup
+{
+public:
+						PolygonOffsetTests		(Context& context);
+	virtual				~PolygonOffsetTests		(void);
+	virtual void		init					(void);
+
+private:
+						PolygonOffsetTests		(const PolygonOffsetTests&);
+	PolygonOffsetTests&	operator=				(const PolygonOffsetTests&);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FPOLYGONOFFSETTESTS_HPP
diff --git a/modules/gles3/functional/es3fPrerequisiteTests.cpp b/modules/gles3/functional/es3fPrerequisiteTests.cpp
new file mode 100644
index 0000000..32e4dea
--- /dev/null
+++ b/modules/gles3/functional/es3fPrerequisiteTests.cpp
@@ -0,0 +1,286 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Prerequisite tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fPrerequisiteTests.hpp"
+#include "deRandom.h"
+#include "tcuRGBA.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStateReset.hpp"
+
+#include "glw.h"
+
+using tcu::RGBA;
+using tcu::Surface;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class StateResetCase : public TestCase
+{
+public:
+										StateResetCase	(Context& context);
+	virtual								~StateResetCase	(void);
+	virtual TestCase::IterateResult		iterate			(void);
+};
+
+StateResetCase::StateResetCase (Context& context)
+	: TestCase(context, "state_reset", "State Reset Test")
+{
+}
+
+StateResetCase::~StateResetCase (void)
+{
+}
+
+TestCase::IterateResult StateResetCase::iterate (void)
+{
+	try
+	{
+		glu::resetState(m_context.getRenderContext());
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	catch (const tcu::TestError& e)
+	{
+		m_testCtx.getLog() << e;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	}
+
+	return TestCase::STOP;
+}
+
+class ClearColorCase : public TestCase
+{
+public:
+										ClearColorCase		(Context& context);
+	virtual								~ClearColorCase		(void);
+	virtual TestCase::IterateResult		iterate				(void);
+
+private:
+	RGBA		m_clearColor;
+	int			m_numIters;
+	int			m_curIter;
+};
+
+ClearColorCase::ClearColorCase (Context& context)
+	: TestCase		(context, "clear_color", "glClearColor test")
+	, m_numIters	(10)
+	, m_curIter		(0)
+{
+}
+
+ClearColorCase::~ClearColorCase (void)
+{
+}
+
+TestCase::IterateResult ClearColorCase::iterate (void)
+{
+	int r = 0;
+	int g = 0;
+	int b = 0;
+	int a = 255;
+
+	switch (m_curIter)
+	{
+		case 0:
+			// Black, skip
+			break;
+		case 1:
+			r = 255;
+			g = 255;
+			b = 255;
+			break;
+		case 2:
+			r = 255;
+			break;
+		case 3:
+			g = 255;
+			break;
+		case 4:
+			b = 255;
+			break;
+		default:
+			deRandom rnd;
+			deRandom_init(&rnd, deInt32Hash(m_curIter));
+			r = (int)(deRandom_getUint32(&rnd) & 0xFF);
+			g = (int)(deRandom_getUint32(&rnd) & 0xFF);
+			b = (int)(deRandom_getUint32(&rnd) & 0xFF);
+			a = (int)(deRandom_getUint32(&rnd) & 0xFF);
+			break;
+
+	};
+
+	glClearColor(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	GLU_CHECK_MSG("CLES2 ClearColor failed.");
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	return (++m_curIter < m_numIters) ? CONTINUE : STOP;
+}
+
+class ReadPixelsCase : public TestCase
+{
+public:
+										ReadPixelsCase		(Context& context);
+	virtual								~ReadPixelsCase		(void);
+	virtual TestCase::IterateResult		iterate				(void);
+
+private:
+	int m_numIters;
+	int m_curIter;
+};
+
+ReadPixelsCase::ReadPixelsCase (Context& context)
+	: TestCase(context, "read_pixels", "Read pixels test")
+	, m_numIters(20)
+	, m_curIter(0)
+{
+}
+
+ReadPixelsCase::~ReadPixelsCase (void)
+{
+}
+
+TestCase::IterateResult ReadPixelsCase::iterate (void)
+{
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderTarget();
+	tcu::PixelFormat			pixelFormat		= renderTarget.getPixelFormat();
+	int							targetWidth		= renderTarget.getWidth();
+	int							targetHeight	= renderTarget.getHeight();
+	int							x				= 0;
+	int							y				= 0;
+	int							imageWidth		= 0;
+	int							imageHeight		= 0;
+
+	deRandom rnd;
+	deRandom_init(&rnd, deInt32Hash(m_curIter));
+
+	switch (m_curIter)
+	{
+		case 0:
+			// Fullscreen
+			x = 0;
+			y = 0;
+			imageWidth  = targetWidth;
+			imageHeight = targetHeight;
+			break;
+		case 1:
+			// Upper left corner
+			x = 0;
+			y = 0;
+			imageWidth = targetWidth / 2;
+			imageHeight = targetHeight / 2;
+			break;
+		case 2:
+			// Lower right corner
+			x = targetWidth / 2;
+			y = targetHeight / 2;
+			imageWidth = targetWidth - x;
+			imageHeight = targetHeight - y;
+			break;
+		default:
+			x = deRandom_getUint32(&rnd) % (targetWidth - 1);
+			y = deRandom_getUint32(&rnd) % (targetHeight - 1);
+			imageWidth = 1 + (deRandom_getUint32(&rnd) % (targetWidth - x - 1));
+			imageHeight = 1 + (deRandom_getUint32(&rnd) % (targetHeight - y - 1));
+			break;
+	}
+
+	Surface	resImage(imageWidth, imageHeight);
+	Surface	refImage(imageWidth, imageHeight);
+	Surface	diffImage(imageWidth, imageHeight);
+
+	int r = (int)(deRandom_getUint32(&rnd) & 0xFF);
+	int g = (int)(deRandom_getUint32(&rnd) & 0xFF);
+	int b = (int)(deRandom_getUint32(&rnd) & 0xFF);
+
+	tcu::clear(refImage.getAccess(), tcu::IVec4(r, g, b, 255));
+	glClearColor(r/255.0f, g/255.0f, b/255.0f, 1.0f);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	glu::readPixels(m_context.getRenderContext(), x, y, resImage.getAccess());
+	GLU_CHECK_MSG("glReadPixels() failed.");
+
+	RGBA colorThreshold = pixelFormat.getColorThreshold();
+	RGBA matchColor(0, 255, 0, 255);
+	RGBA diffColor(255, 0, 0, 255);
+	bool isImageOk = true;
+
+	for (int j = 0; j < imageHeight; j++)
+	{
+		for (int i = 0; i < imageWidth; i++)
+		{
+			RGBA		resRGBA		= resImage.getPixel(i, j);
+			RGBA		refRGBA		= refImage.getPixel(i, j);
+			bool		isPixelOk	= compareThreshold(refRGBA, resRGBA, colorThreshold);
+			diffImage.setPixel(i, j, isPixelOk ? matchColor : diffColor);
+
+			isImageOk = isImageOk && isPixelOk;
+		}
+	}
+
+	if (isImageOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+		m_testCtx.getLog() << TestLog::ImageSet("Result", "Resulting framebuffer")
+						   << TestLog::Image("Result",		"Resulting framebuffer",	resImage)
+						   << TestLog::Image("Reference",	"Reference image",			refImage)
+						   << TestLog::Image("DiffMask",	"Failing pixels",			diffImage)
+						   << TestLog::EndImageSet;
+	}
+
+	return (++m_curIter < m_numIters) ? CONTINUE : STOP;
+}
+
+PrerequisiteTests::PrerequisiteTests (Context& context)
+	: TestCaseGroup(context, "prerequisite", "Prerequisite Test Cases")
+{
+}
+
+PrerequisiteTests::~PrerequisiteTests (void)
+{
+}
+
+void PrerequisiteTests::init (void)
+{
+	addChild(new StateResetCase(m_context));
+	addChild(new ClearColorCase(m_context));
+	addChild(new ReadPixelsCase(m_context));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fPrerequisiteTests.hpp b/modules/gles3/functional/es3fPrerequisiteTests.hpp
new file mode 100644
index 0000000..1313b0e
--- /dev/null
+++ b/modules/gles3/functional/es3fPrerequisiteTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FPREREQUISITETESTS_HPP
+#define _ES3FPREREQUISITETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Prerequisite tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.h"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class PrerequisiteTests : public TestCaseGroup
+{
+public:
+						PrerequisiteTests		(Context& context);
+	virtual				~PrerequisiteTests		(void);
+	virtual void		init					(void);
+
+private:
+						PrerequisiteTests		(const PrerequisiteTests&);
+	PrerequisiteTests&	operator=				(const PrerequisiteTests&);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FPREREQUISITETESTS_HPP
diff --git a/modules/gles3/functional/es3fPrimitiveRestartTests.cpp b/modules/gles3/functional/es3fPrimitiveRestartTests.cpp
new file mode 100644
index 0000000..70389bd
--- /dev/null
+++ b/modules/gles3/functional/es3fPrimitiveRestartTests.cpp
@@ -0,0 +1,712 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Primitive restart tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fPrimitiveRestartTests.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+#include "glw.h"
+
+using tcu::Vec4;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+static const int		MAX_RENDER_WIDTH				= 256;
+static const int		MAX_RENDER_HEIGHT				= 256;
+
+static const deUint32	MAX_UNSIGNED_BYTE				= (1<<8) - 1;
+static const deUint32	MAX_UNSIGNED_SHORT				= (1<<16) - 1;
+static const deUint32	MAX_UNSIGNED_INT				= (deUint32)((1ULL << 32) - 1);
+
+static const deUint8	RESTART_INDEX_UNSIGNED_BYTE		= (deUint8)MAX_UNSIGNED_BYTE;
+static const deUint16	RESTART_INDEX_UNSIGNED_SHORT	= (deUint16)MAX_UNSIGNED_SHORT;
+static const deUint32	RESTART_INDEX_UNSIGNED_INT		= MAX_UNSIGNED_INT;
+
+class PrimitiveRestartCase : public TestCase
+{
+public:
+	enum PrimitiveType
+	{
+		PRIMITIVE_POINTS = 0,
+		PRIMITIVE_LINE_STRIP,
+		PRIMITIVE_LINE_LOOP,
+		PRIMITIVE_LINES,
+		PRIMITIVE_TRIANGLE_STRIP,
+		PRIMITIVE_TRIANGLE_FAN,
+		PRIMITIVE_TRIANGLES,
+
+		PRIMITIVE_LAST
+	};
+
+	enum IndexType
+	{
+		INDEX_UNSIGNED_BYTE = 0,
+		INDEX_UNSIGNED_SHORT,
+		INDEX_UNSIGNED_INT,
+
+		INDEX_LAST
+	};
+
+	enum Function
+	{
+		FUNCTION_DRAW_ELEMENTS = 0,
+		FUNCTION_DRAW_ELEMENTS_INSTANCED,
+		FUNCTION_DRAW_RANGE_ELEMENTS,
+
+		FUNCTION_LAST
+	};
+
+							PrimitiveRestartCase	(Context& context, const char* name, const char* description, PrimitiveType primType, IndexType indexType, Function function, bool beginWithRestart, bool endWithRestart, bool duplicateRestarts);
+							~PrimitiveRestartCase	(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							PrimitiveRestartCase	(const PrimitiveRestartCase& other);
+	PrimitiveRestartCase&	operator=				(const PrimitiveRestartCase& other);
+
+	void					draw					(int startNdx, int count);
+
+	void					renderWithRestart		(void);
+	void					renderWithoutRestart	(void);
+
+	// Helper functions for handling the appropriate index vector (according to m_indexType).
+	void					addIndex				(deUint32 index);
+	deUint32				getIndex				(int indexNdx);
+	int						getNumIndices			(void);
+	void*					getIndexPtr				(int indexNdx);
+
+	// \note Only one of the following index vectors is used (according to m_indexType).
+	std::vector<deUint8>	m_indicesUB;
+	std::vector<deUint16>	m_indicesUS;
+	std::vector<deUint32>	m_indicesUI;
+
+	std::vector<float>		m_positions;
+
+	PrimitiveType			m_primType;
+	IndexType				m_indexType;
+	Function				m_function;
+
+	bool					m_beginWithRestart;		// Whether there will be restart indices at the beginning of the index array.
+	bool					m_endWithRestart;		// Whether there will be restart indices at the end of the index array.
+	bool					m_duplicateRestarts;	// Whether two consecutive restarts are used instead of one.
+
+	glu::ShaderProgram*		m_program;
+};
+
+PrimitiveRestartCase::PrimitiveRestartCase (Context& context, const char* name, const char* description, PrimitiveType primType, IndexType indexType, Function function, bool beginWithRestart, bool endWithRestart, bool duplicateRestarts)
+	: TestCase				(context, name, description)
+	, m_primType			(primType)
+	, m_indexType			(indexType)
+	, m_function			(function)
+	, m_beginWithRestart	(beginWithRestart)
+	, m_endWithRestart		(endWithRestart)
+	, m_duplicateRestarts	(duplicateRestarts)
+	, m_program				(DE_NULL)
+{
+}
+
+PrimitiveRestartCase::~PrimitiveRestartCase (void)
+{
+	PrimitiveRestartCase::deinit();
+}
+
+void PrimitiveRestartCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+void PrimitiveRestartCase::addIndex (deUint32 index)
+{
+	if (m_indexType == INDEX_UNSIGNED_BYTE)
+	{
+		DE_ASSERT(de::inRange(index, (deUint32)0, MAX_UNSIGNED_BYTE));
+		m_indicesUB.push_back((deUint8)index);
+	}
+	else if (m_indexType == INDEX_UNSIGNED_SHORT)
+	{
+		DE_ASSERT(de::inRange(index, (deUint32)0, MAX_UNSIGNED_SHORT));
+		m_indicesUS.push_back((deUint16)index);
+	}
+	else if (m_indexType == INDEX_UNSIGNED_INT)
+	{
+		DE_ASSERT(de::inRange(index, (deUint32)0, MAX_UNSIGNED_INT));
+		m_indicesUI.push_back((deUint32)index);
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+}
+
+deUint32 PrimitiveRestartCase::getIndex (int indexNdx)
+{
+	switch (m_indexType)
+	{
+		case INDEX_UNSIGNED_BYTE:	return (deUint32)m_indicesUB[indexNdx];
+		case INDEX_UNSIGNED_SHORT:	return (deUint32)m_indicesUS[indexNdx];
+		case INDEX_UNSIGNED_INT:	return m_indicesUI[indexNdx];
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+int PrimitiveRestartCase::getNumIndices (void)
+{
+	switch (m_indexType)
+	{
+		case INDEX_UNSIGNED_BYTE:	return (int)m_indicesUB.size();
+		case INDEX_UNSIGNED_SHORT:	return (int)m_indicesUS.size();
+		case INDEX_UNSIGNED_INT:	return (int)m_indicesUI.size();
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+// Pointer to the index value at index indexNdx.
+void* PrimitiveRestartCase::getIndexPtr (int indexNdx)
+{
+	switch (m_indexType)
+	{
+		case INDEX_UNSIGNED_BYTE:	return (void*)&m_indicesUB[indexNdx];
+		case INDEX_UNSIGNED_SHORT:	return (void*)&m_indicesUS[indexNdx];
+		case INDEX_UNSIGNED_INT:	return (void*)&m_indicesUI[indexNdx];
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+void PrimitiveRestartCase::init (void)
+{
+	// Create shader program.
+
+	static const char* vertShaderSource =
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"}\n";
+
+	static const char* fragShaderSource =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	o_color = vec4(1.0f);\n"
+		"}\n";
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource));
+
+	if(!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+		TCU_FAIL("Failed to compile shader");
+	}
+
+	deUint32 restartIndex = m_indexType == INDEX_UNSIGNED_BYTE	? RESTART_INDEX_UNSIGNED_BYTE
+						  : m_indexType == INDEX_UNSIGNED_SHORT	? RESTART_INDEX_UNSIGNED_SHORT
+						  : m_indexType == INDEX_UNSIGNED_INT	? RESTART_INDEX_UNSIGNED_INT
+						  : 0;
+
+	DE_ASSERT(restartIndex != 0);
+
+	DE_ASSERT(getNumIndices() == 0);
+
+	// If testing a case with restart at beginning, add it there.
+	if (m_beginWithRestart)
+	{
+		addIndex(restartIndex);
+		if (m_duplicateRestarts)
+			addIndex(restartIndex);
+	}
+
+	// Generate vertex positions and indices depending on primitive type.
+	// \note At this point, restarts shall not be added to the start or the end of the index vector. Those are special cases, and are done above and after the following if-else chain, respectively.
+
+	if (m_primType == PRIMITIVE_POINTS)
+	{
+		// Generate rows with different numbers of points.
+
+		deUint32	curIndex			= 0;
+		const int	numRows				= 20;
+
+		for (int row = 0; row < numRows; row++)
+		{
+			for (int col = 0; col < row + 1; col++)
+			{
+				float fx = -1.0f + 2.0f * ((float)col + 0.5f) / (float)numRows;
+				float fy = -1.0f + 2.0f * ((float)row + 0.5f) / (float)numRows;
+
+				m_positions.push_back(fx);
+				m_positions.push_back(fy);
+
+				addIndex(curIndex++);
+			}
+
+			if (row < numRows - 1) // Add a restart after all but last row.
+			{
+				addIndex(restartIndex);
+				if (m_duplicateRestarts)
+					addIndex(restartIndex);
+			}
+		}
+	}
+	else if (m_primType == PRIMITIVE_LINE_STRIP || m_primType == PRIMITIVE_LINE_LOOP || m_primType == PRIMITIVE_LINES)
+	{
+		// Generate a numRows x numCols arrangement of line polygons of different vertex counts.
+
+		deUint32	curIndex	= 0;
+		const int	numRows		= 4;
+		const int	numCols		= 4;
+
+		for (int row = 0; row < numRows; row++)
+		{
+			float centerY = -1.0f + 2.0f * ((float)row + 0.5f) / (float)numRows;
+
+			for (int col = 0; col < numCols; col++)
+			{
+				float	centerX		= -1.0f + 2.0f * ((float)col + 0.5f) / (float)numCols;
+				int		numVertices	= row*numCols + col + 1;
+
+				for (int i = 0; i < numVertices; i++)
+				{
+					float fx = centerX + 0.9f * deFloatCos((float)i*2.0f*DE_PI / (float)numVertices) / (float)numCols;
+					float fy = centerY + 0.9f * deFloatSin((float)i*2.0f*DE_PI / (float)numVertices) / (float)numRows;
+
+					m_positions.push_back(fx);
+					m_positions.push_back(fy);
+
+					addIndex(curIndex++);
+				}
+
+				if (col < numCols - 1 || row < numRows - 1) // Add a restart after all but last polygon.
+				{
+					addIndex(restartIndex);
+					if (m_duplicateRestarts)
+						addIndex(restartIndex);
+				}
+			}
+		}
+	}
+	else if (m_primType == PRIMITIVE_TRIANGLE_STRIP)
+	{
+		// Generate a number of horizontal triangle strips of different lengths.
+
+		deUint32	curIndex	= 0;
+		const int	numStrips	= 20;
+
+		for (int stripNdx = 0; stripNdx < numStrips; stripNdx++)
+		{
+			int numVertices = stripNdx + 1;
+
+			for (int i = 0; i < numVertices; i++)
+			{
+				float fx = -0.9f + 1.8f * (float)(i/2*2) / numStrips;
+				float fy = -0.9f + 1.8f * ((float)stripNdx + (i%2 == 0 ? 0.0f : 0.8f)) / numStrips;
+
+				m_positions.push_back(fx);
+				m_positions.push_back(fy);
+
+				addIndex(curIndex++);
+			}
+
+			if (stripNdx < numStrips - 1) // Add a restart after all but last strip.
+			{
+				addIndex(restartIndex);
+				if (m_duplicateRestarts)
+					addIndex(restartIndex);
+			}
+		}
+	}
+	else if (m_primType == PRIMITIVE_TRIANGLE_FAN)
+	{
+		// Generate a numRows x numCols arrangement of triangle fan polygons of different vertex counts.
+
+		deUint32	curIndex	= 0;
+		const int	numRows		= 4;
+		const int	numCols		= 4;
+
+		for (int row = 0; row < numRows; row++)
+		{
+			float centerY = -1.0f + 2.0f * ((float)row + 0.5f) / (float)numRows;
+
+			for (int col = 0; col < numCols; col++)
+			{
+				float	centerX			= -1.0f + 2.0f * ((float)col + 0.5f) / (float)numCols;
+				int		numArcVertices	= row*numCols + col;
+
+				m_positions.push_back(centerX);
+				m_positions.push_back(centerY);
+
+				addIndex(curIndex++);
+
+				for (int i = 0; i < numArcVertices; i++)
+				{
+					float fx = centerX + 0.9f * deFloatCos((float)i*2.0f*DE_PI / (float)numArcVertices) / (float)numCols;
+					float fy = centerY + 0.9f * deFloatSin((float)i*2.0f*DE_PI / (float)numArcVertices) / (float)numRows;
+
+					m_positions.push_back(fx);
+					m_positions.push_back(fy);
+
+					addIndex(curIndex++);
+				}
+
+				if (col < numCols - 1 || row < numRows - 1) // Add a restart after all but last polygon.
+				{
+					addIndex(restartIndex);
+					if (m_duplicateRestarts)
+						addIndex(restartIndex);
+				}
+			}
+		}
+	}
+	else if (m_primType == PRIMITIVE_TRIANGLES)
+	{
+		// Generate a number of rows with (potentially incomplete) triangles.
+
+		deUint32	curIndex	= 0;
+		const int	numRows		= 3*7;
+
+		for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
+		{
+			int numVertices = rowNdx + 1;
+
+			for (int i = 0; i < numVertices; i++)
+			{
+				float fx = -0.9f + 1.8f * ((i/3) + (i%3 == 2 ? 0.8f : 0.0f)) * 3 / numRows;
+				float fy = -0.9f + 1.8f * ((float)rowNdx + (i%3 == 0 ? 0.0f : 0.8f)) / numRows;
+
+				m_positions.push_back(fx);
+				m_positions.push_back(fy);
+
+				addIndex(curIndex++);
+			}
+
+			if (rowNdx < numRows - 1) // Add a restart after all but last row.
+			{
+				addIndex(restartIndex);
+				if (m_duplicateRestarts)
+					addIndex(restartIndex);
+			}
+		}
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	// If testing a case with restart at end, add it there.
+	if (m_endWithRestart)
+	{
+		addIndex(restartIndex);
+		if (m_duplicateRestarts)
+			addIndex(restartIndex);
+	}
+
+	// Special case assertions.
+
+	int numIndices = getNumIndices();
+
+	DE_ASSERT(numIndices > 0);
+	DE_ASSERT(m_beginWithRestart || getIndex(0) != restartIndex);						// We don't want restarts at beginning unless the case is a special case.
+	DE_ASSERT(m_endWithRestart || getIndex(numIndices-1) != restartIndex);			// We don't want restarts at end unless the case is a special case.
+
+	if (!m_duplicateRestarts)
+		for (int i = 1; i < numIndices; i++)
+			DE_ASSERT(getIndex(i) != restartIndex || getIndex(i-1) != restartIndex);	// We don't want duplicate restarts unless the case is a special case.
+}
+
+PrimitiveRestartCase::IterateResult PrimitiveRestartCase::iterate (void)
+{
+	int							width			= deMin32(m_context.getRenderTarget().getWidth(), MAX_RENDER_WIDTH);
+	int							height			= deMin32(m_context.getRenderTarget().getHeight(), MAX_RENDER_HEIGHT);
+
+	int							xOffsetMax		= m_context.getRenderTarget().getWidth() - width;
+	int							yOffsetMax		= m_context.getRenderTarget().getHeight() - height;
+
+	de::Random					rnd				(deStringHash(getName()));
+
+	int							xOffset			= rnd.getInt(0, xOffsetMax);
+	int							yOffset			= rnd.getInt(0, yOffsetMax);
+	tcu::Surface				referenceImg	(width, height);
+	tcu::Surface				resultImg		(width, height);
+
+	glViewport(xOffset, yOffset, width, height);
+	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+
+	deUint32 program = m_program->getProgram();
+	glUseProgram(program);
+
+	// Setup position attribute.
+
+	int loc = glGetAttribLocation(program, "a_position");
+	glEnableVertexAttribArray(loc);
+	glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 0, &m_positions[0]);
+
+	// Render result.
+
+	renderWithRestart();
+	glu::readPixels(m_context.getRenderContext(), xOffset, yOffset, resultImg.getAccess());
+
+	// Render reference (same scene as the real deal, but emulate primitive restart without actually using it).
+
+	renderWithoutRestart();
+	glu::readPixels(m_context.getRenderContext(), xOffset, yOffset, referenceImg.getAccess());
+
+	// Compare.
+
+	bool testOk = tcu::pixelThresholdCompare(m_testCtx.getLog(), "ComparisonResult", "Image comparison result", referenceImg, resultImg, tcu::RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);
+
+	m_testCtx.setTestResult(testOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							testOk ? "Pass"					: "Fail");
+
+	glUseProgram(0);
+
+	return STOP;
+}
+
+// Draw with the appropriate GLES3 draw function.
+void PrimitiveRestartCase::draw (int startNdx, int count)
+{
+	GLenum primTypeGL;
+
+	switch (m_primType)
+	{
+		case PRIMITIVE_POINTS:			primTypeGL = GL_POINTS;			break;
+		case PRIMITIVE_LINE_STRIP:		primTypeGL = GL_LINE_STRIP;		break;
+		case PRIMITIVE_LINE_LOOP:		primTypeGL = GL_LINE_LOOP;		break;
+		case PRIMITIVE_LINES:			primTypeGL = GL_LINES;			break;
+		case PRIMITIVE_TRIANGLE_STRIP:	primTypeGL = GL_TRIANGLE_STRIP;	break;
+		case PRIMITIVE_TRIANGLE_FAN:	primTypeGL = GL_TRIANGLE_FAN;	break;
+		case PRIMITIVE_TRIANGLES:		primTypeGL = GL_TRIANGLES;		break;
+		default:
+			DE_ASSERT(DE_FALSE);
+			primTypeGL = 0;
+	}
+
+	GLenum indexTypeGL;
+
+	switch (m_indexType)
+	{
+		case INDEX_UNSIGNED_BYTE:	indexTypeGL = GL_UNSIGNED_BYTE;		break;
+		case INDEX_UNSIGNED_SHORT:	indexTypeGL = GL_UNSIGNED_SHORT;	break;
+		case INDEX_UNSIGNED_INT:	indexTypeGL = GL_UNSIGNED_INT;		break;
+		default:
+			DE_ASSERT(DE_FALSE);
+			indexTypeGL = 0;
+	}
+
+	deUint32 restartIndex = m_indexType == INDEX_UNSIGNED_BYTE	? RESTART_INDEX_UNSIGNED_BYTE
+						  : m_indexType == INDEX_UNSIGNED_SHORT	? RESTART_INDEX_UNSIGNED_SHORT
+						  : m_indexType == INDEX_UNSIGNED_INT	? RESTART_INDEX_UNSIGNED_INT
+						  : 0;
+
+	DE_ASSERT(restartIndex != 0);
+
+	if (m_function == FUNCTION_DRAW_ELEMENTS)
+		glDrawElements(primTypeGL, (GLsizei)count, indexTypeGL, (GLvoid*)getIndexPtr(startNdx));
+	else if (m_function == FUNCTION_DRAW_ELEMENTS_INSTANCED)
+		glDrawElementsInstanced(primTypeGL, (GLsizei)count, indexTypeGL, (GLvoid*)getIndexPtr(startNdx), 1);
+	else
+	{
+		DE_ASSERT(m_function == FUNCTION_DRAW_RANGE_ELEMENTS);
+
+		// Find the largest non-restart index in the index array (for glDrawRangeElements() end parameter).
+
+		deUint32 max = 0;
+
+		int numIndices = getNumIndices();
+		for (int i = 0; i < numIndices; i++)
+		{
+			deUint32 index = getIndex(i);
+			if (index != restartIndex && index > max)
+				max = index;
+		}
+
+		glDrawRangeElements(primTypeGL, 0, (GLuint)max, (GLsizei)count, indexTypeGL, (GLvoid*)getIndexPtr(startNdx));
+	}
+}
+
+void PrimitiveRestartCase::renderWithRestart (void)
+{
+	GLU_CHECK_MSG("PrimitiveRestartCase::renderWithRestart() begin");
+
+	glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
+	GLU_CHECK_MSG("Enable primitive restart");
+	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	GLU_CHECK_MSG("Clear in PrimitiveRestartCase::renderWithRestart()");
+
+	draw(0, getNumIndices());
+
+	GLU_CHECK_MSG("Draw in PrimitiveRestartCase::renderWithRestart()");
+
+	GLU_CHECK_MSG("PrimitiveRestartCase::renderWithRestart() end");
+}
+
+void PrimitiveRestartCase::renderWithoutRestart (void)
+{
+	GLU_CHECK_MSG("PrimitiveRestartCase::renderWithoutRestart() begin");
+
+	deUint32 restartIndex = m_indexType == INDEX_UNSIGNED_BYTE	? RESTART_INDEX_UNSIGNED_BYTE
+						  : m_indexType == INDEX_UNSIGNED_SHORT	? RESTART_INDEX_UNSIGNED_SHORT
+						  : m_indexType == INDEX_UNSIGNED_INT	? RESTART_INDEX_UNSIGNED_INT
+						  : 0;
+
+	DE_ASSERT(restartIndex != 0);
+
+	glDisable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
+	GLU_CHECK_MSG("Disable primitive restart");
+	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	GLU_CHECK_MSG("Clear in PrimitiveRestartCase::renderWithoutRestart()");
+
+	// Draw, emulating primitive restart.
+
+	int numIndices = getNumIndices();
+
+	DE_ASSERT(numIndices >= 0);
+
+	int indexArrayStartNdx = 0; // Keep track of the draw start index - first index after a primitive restart, or initially the first index altogether.
+
+	for (int indexArrayNdx = 0; indexArrayNdx <= numIndices; indexArrayNdx++) // \note Goes one "too far" in order to detect end of array as well.
+	{
+		if (indexArrayNdx >= numIndices || getIndex(indexArrayNdx) == restartIndex) // \note Handle end of array the same way as a restart index encounter.
+		{
+			if (indexArrayStartNdx < numIndices)
+			{
+				// Draw from index indexArrayStartNdx to index indexArrayNdx-1 .
+
+				draw(indexArrayStartNdx, indexArrayNdx - indexArrayStartNdx);
+				GLU_CHECK_MSG("Draw in PrimitiveRestartCase::renderWithoutRestart()");
+			}
+
+			indexArrayStartNdx = indexArrayNdx + 1; // Next draw starts just after this restart index.
+		}
+	}
+
+	GLU_CHECK_MSG("PrimitiveRestartCase::renderWithoutRestart() end");
+}
+
+PrimitiveRestartTests::PrimitiveRestartTests (Context& context)
+	: TestCaseGroup(context, "primitive_restart", "Primitive restart tests")
+{
+}
+
+PrimitiveRestartTests::~PrimitiveRestartTests (void)
+{
+}
+
+void PrimitiveRestartTests::init (void)
+{
+	for (int isRestartBeginCaseI = 0; isRestartBeginCaseI <= 1; isRestartBeginCaseI++)
+	for (int isRestartEndCaseI = 0; isRestartEndCaseI <= 1; isRestartEndCaseI++)
+	for (int isDuplicateRestartCaseI = 0; isDuplicateRestartCaseI <= 1; isDuplicateRestartCaseI++)
+	{
+		bool			isRestartBeginCase		= isRestartBeginCaseI != 0;
+		bool			isRestartEndCase		= isRestartEndCaseI != 0;
+		bool			isDuplicateRestartCase	= isDuplicateRestartCaseI != 0;
+
+		std::string		specialCaseGroupName;
+
+		if (isRestartBeginCase)		specialCaseGroupName = "begin_restart";
+		if (isRestartEndCase)		specialCaseGroupName += std::string(specialCaseGroupName.empty() ? "" : "_") + "end_restart";
+		if (isDuplicateRestartCase)	specialCaseGroupName += std::string(specialCaseGroupName.empty() ? "" : "_") + "duplicate_restarts";
+
+		if (specialCaseGroupName.empty())
+			specialCaseGroupName = "basic";
+
+		TestCaseGroup* specialCaseGroup = new TestCaseGroup(m_context, specialCaseGroupName.c_str(), "");
+		addChild(specialCaseGroup);
+
+		for (int primType = 0; primType < (int)PrimitiveRestartCase::PRIMITIVE_LAST; primType++)
+		{
+			const char* primTypeName = primType == (int)PrimitiveRestartCase::PRIMITIVE_POINTS			? "points"
+									 : primType == (int)PrimitiveRestartCase::PRIMITIVE_LINE_STRIP		? "line_strip"
+									 : primType == (int)PrimitiveRestartCase::PRIMITIVE_LINE_LOOP		? "line_loop"
+									 : primType == (int)PrimitiveRestartCase::PRIMITIVE_LINES			? "lines"
+									 : primType == (int)PrimitiveRestartCase::PRIMITIVE_TRIANGLE_STRIP	? "triangle_strip"
+									 : primType == (int)PrimitiveRestartCase::PRIMITIVE_TRIANGLE_FAN	? "triangle_fan"
+									 : primType == (int)PrimitiveRestartCase::PRIMITIVE_TRIANGLES		? "triangles"
+									 : DE_NULL;
+
+			DE_ASSERT(primTypeName != DE_NULL);
+
+			TestCaseGroup* primTypeGroup = new TestCaseGroup(m_context, primTypeName, "");
+			specialCaseGroup->addChild(primTypeGroup);
+
+			for (int indexType = 0; indexType < (int)PrimitiveRestartCase::INDEX_LAST; indexType++)
+			{
+				const char *indexTypeName = indexType == (int)PrimitiveRestartCase::INDEX_UNSIGNED_BYTE		? "unsigned_byte"
+										  : indexType == (int)PrimitiveRestartCase::INDEX_UNSIGNED_SHORT	? "unsigned_short"
+										  : indexType == (int)PrimitiveRestartCase::INDEX_UNSIGNED_INT		? "unsigned_int"
+										  : DE_NULL;
+
+				DE_ASSERT(indexTypeName != DE_NULL);
+
+				TestCaseGroup* indexTypeGroup = new TestCaseGroup(m_context, indexTypeName, "");
+				primTypeGroup->addChild(indexTypeGroup);
+
+				for (int function = 0; function < (int)PrimitiveRestartCase::FUNCTION_LAST; function++)
+				{
+					const char* functionName = function == (int)PrimitiveRestartCase::FUNCTION_DRAW_ELEMENTS			? "draw_elements"
+											 : function == (int)PrimitiveRestartCase::FUNCTION_DRAW_ELEMENTS_INSTANCED	? "draw_elements_instanced"
+											 : function == (int)PrimitiveRestartCase::FUNCTION_DRAW_RANGE_ELEMENTS		? "draw_range_elements"
+											 : DE_NULL;
+
+					DE_ASSERT(functionName != DE_NULL);
+
+					indexTypeGroup->addChild(new PrimitiveRestartCase(m_context,
+																	  functionName,
+																	  "",
+																	  (PrimitiveRestartCase::PrimitiveType)primType,
+																	  (PrimitiveRestartCase::IndexType)indexType,
+																	  (PrimitiveRestartCase::Function)function,
+																	  isRestartBeginCase,
+																	  isRestartEndCase,
+																	  isDuplicateRestartCase));
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fPrimitiveRestartTests.hpp b/modules/gles3/functional/es3fPrimitiveRestartTests.hpp
new file mode 100644
index 0000000..a96b6b1
--- /dev/null
+++ b/modules/gles3/functional/es3fPrimitiveRestartTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FPRIMITIVERESTARTTESTS_HPP
+#define _ES3FPRIMITIVERESTARTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Primitive restart tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class PrimitiveRestartTests : public TestCaseGroup
+{
+public:
+								PrimitiveRestartTests	(Context& context);
+								~PrimitiveRestartTests	(void);
+
+	void						init					(void);
+
+private:
+								PrimitiveRestartTests	(const PrimitiveRestartTests& other);
+	PrimitiveRestartTests&		operator=				(const PrimitiveRestartTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FPRIMITIVERESTARTTESTS_HPP
diff --git a/modules/gles3/functional/es3fRandomFragmentOpTests.cpp b/modules/gles3/functional/es3fRandomFragmentOpTests.cpp
new file mode 100644
index 0000000..fa5b6e2
--- /dev/null
+++ b/modules/gles3/functional/es3fRandomFragmentOpTests.cpp
@@ -0,0 +1,416 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Randomized per-fragment operation tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fRandomFragmentOpTests.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "glsInteractionTestUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluRenderContext.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "rrFragmentOperations.hpp"
+#include "sglrReferenceUtils.hpp"
+
+#include <algorithm>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::vector;
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::BVec4;
+
+enum
+{
+	VIEWPORT_WIDTH				= 64,
+	VIEWPORT_HEIGHT				= 64,
+	NUM_CALLS_PER_ITERATION		= 3,
+	NUM_ITERATIONS_PER_CASE		= 10
+};
+
+static const tcu::Vec4		CLEAR_COLOR			(0.25f, 0.5f, 0.75f, 1.0f);
+static const float			CLEAR_DEPTH			= 1.0f;
+static const int			CLEAR_STENCIL		= 0;
+static const bool			ENABLE_CALL_LOG		= true;
+
+using namespace gls::FragmentOpUtil;
+using namespace gls::InteractionTestUtil;
+
+void translateStencilState (const StencilState& src, rr::StencilState& dst)
+{
+	dst.func		= sglr::rr_util::mapGLTestFunc(src.function);
+	dst.ref			= src.reference;
+	dst.compMask	= src.compareMask;
+	dst.sFail		= sglr::rr_util::mapGLStencilOp(src.stencilFailOp);
+	dst.dpFail		= sglr::rr_util::mapGLStencilOp(src.depthFailOp);
+	dst.dpPass		= sglr::rr_util::mapGLStencilOp(src.depthPassOp);
+	dst.writeMask	= src.writeMask;
+}
+
+void translateBlendState (const BlendState& src, rr::BlendState& dst)
+{
+	dst.equation	= sglr::rr_util::mapGLBlendEquation(src.equation);
+	dst.srcFunc		= sglr::rr_util::mapGLBlendFunc(src.srcFunc);
+	dst.dstFunc		= sglr::rr_util::mapGLBlendFunc(src.dstFunc);
+}
+
+void translateState (const RenderState& src, rr::FragmentOperationState& dst, const tcu::RenderTarget& renderTarget)
+{
+	bool hasDepth		= renderTarget.getDepthBits() > 0;
+	bool hasStencil		= renderTarget.getStencilBits() > 0;
+
+	dst.scissorTestEnabled		= src.scissorTestEnabled;
+	dst.scissorRectangle		= src.scissorRectangle;
+	dst.stencilTestEnabled		= hasStencil && src.stencilTestEnabled;
+	dst.depthTestEnabled		= hasDepth && src.depthTestEnabled;
+	dst.blendMode				= src.blendEnabled ? rr::BLENDMODE_STANDARD : rr::BLENDMODE_NONE;
+	dst.numStencilBits			= renderTarget.getStencilBits();
+
+	dst.colorMask = src.colorMask;
+
+	if (dst.depthTestEnabled)
+	{
+		dst.depthFunc	= sglr::rr_util::mapGLTestFunc(src.depthFunc);
+		dst.depthMask	= src.depthWriteMask;
+	}
+
+	if (dst.stencilTestEnabled)
+	{
+		translateStencilState(src.stencil[rr::FACETYPE_BACK],	dst.stencilStates[rr::FACETYPE_BACK]);
+		translateStencilState(src.stencil[rr::FACETYPE_FRONT],	dst.stencilStates[rr::FACETYPE_FRONT]);
+	}
+
+	if (src.blendEnabled)
+	{
+		translateBlendState(src.blendRGBState, dst.blendRGBState);
+		translateBlendState(src.blendAState, dst.blendAState);
+		dst.blendColor = tcu::clamp(src.blendColor, Vec4(0.0f), Vec4(1.0f));
+	}
+}
+
+static void renderQuad (const glw::Functions& gl, gls::FragmentOpUtil::QuadRenderer& renderer, const gls::FragmentOpUtil::IntegerQuad& quad, int baseX, int baseY)
+{
+	gls::FragmentOpUtil::Quad translated;
+
+	std::copy(DE_ARRAY_BEGIN(quad.color), DE_ARRAY_END(quad.color), DE_ARRAY_BEGIN(translated.color));
+
+	bool	flipX		= quad.posB.x() < quad.posA.x();
+	bool	flipY		= quad.posB.y() < quad.posA.y();
+	int		viewportX	= de::min(quad.posA.x(), quad.posB.x());
+	int		viewportY	= de::min(quad.posA.y(), quad.posB.y());
+	int		viewportW	= de::abs(quad.posA.x()-quad.posB.x())+1;
+	int		viewportH	= de::abs(quad.posA.y()-quad.posB.y())+1;
+
+	translated.posA = Vec2(flipX ? 1.0f : -1.0f, flipY ? 1.0f : -1.0f);
+	translated.posB = Vec2(flipX ? -1.0f : 1.0f, flipY ? -1.0f : 1.0f);
+
+	// \todo [2012-12-18 pyry] Pass in DepthRange parameters.
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(quad.depth); ndx++)
+		translated.depth[ndx] = quad.depth[ndx]*2.0f - 1.0f;
+
+	gl.viewport(baseX+viewportX, baseY+viewportY, viewportW, viewportH);
+	renderer.render(translated);
+}
+
+static void setGLState (glu::CallLogWrapper& wrapper, const RenderState& state, int viewportX, int viewportY)
+{
+	if (state.scissorTestEnabled)
+	{
+		wrapper.glEnable(GL_SCISSOR_TEST);
+		wrapper.glScissor(viewportX+state.scissorRectangle.left, viewportY+state.scissorRectangle.bottom,
+						  state.scissorRectangle.width, state.scissorRectangle.height);
+	}
+	else
+		wrapper.glDisable(GL_SCISSOR_TEST);
+
+	if (state.stencilTestEnabled)
+	{
+		wrapper.glEnable(GL_STENCIL_TEST);
+
+		for (int face = 0; face < rr::FACETYPE_LAST; face++)
+		{
+			deUint32				glFace	= face == rr::FACETYPE_BACK ? GL_BACK : GL_FRONT;
+			const StencilState&		sParams	= state.stencil[face];
+
+			wrapper.glStencilFuncSeparate(glFace, sParams.function, sParams.reference, sParams.compareMask);
+			wrapper.glStencilOpSeparate(glFace, sParams.stencilFailOp, sParams.depthFailOp, sParams.depthPassOp);
+			wrapper.glStencilMaskSeparate(glFace, sParams.writeMask);
+		}
+	}
+	else
+		wrapper.glDisable(GL_STENCIL_TEST);
+
+	if (state.depthTestEnabled)
+	{
+		wrapper.glEnable(GL_DEPTH_TEST);
+		wrapper.glDepthFunc(state.depthFunc);
+		wrapper.glDepthMask(state.depthWriteMask ? GL_TRUE : GL_FALSE);
+	}
+	else
+		wrapper.glDisable(GL_DEPTH_TEST);
+
+	if (state.blendEnabled)
+	{
+		wrapper.glEnable(GL_BLEND);
+		wrapper.glBlendEquationSeparate(state.blendRGBState.equation, state.blendAState.equation);
+		wrapper.glBlendFuncSeparate(state.blendRGBState.srcFunc, state.blendRGBState.dstFunc, state.blendAState.srcFunc, state.blendAState.dstFunc);
+		wrapper.glBlendColor(state.blendColor.x(), state.blendColor.y(), state.blendColor.z(), state.blendColor.w());
+	}
+	else
+		wrapper.glDisable(GL_BLEND);
+
+	if (state.ditherEnabled)
+		wrapper.glEnable(GL_DITHER);
+	else
+		wrapper.glDisable(GL_DITHER);
+
+	wrapper.glColorMask(state.colorMask[0] ? GL_TRUE : GL_FALSE,
+						state.colorMask[1] ? GL_TRUE : GL_FALSE,
+						state.colorMask[2] ? GL_TRUE : GL_FALSE,
+						state.colorMask[3] ? GL_TRUE : GL_FALSE);
+}
+
+class RandomFragmentOpCase : public TestCase
+{
+public:
+						RandomFragmentOpCase		(Context& context, const char* name, const char* desc, deUint32 seed);
+						~RandomFragmentOpCase		(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+private:
+	tcu::UVec4			getCompareThreshold			(void) const;
+
+	deUint32										m_seed;
+
+	glu::CallLogWrapper								m_callLogWrapper;
+
+	gls::FragmentOpUtil::QuadRenderer*				m_renderer;
+	tcu::TextureLevel*								m_refColorBuffer;
+	tcu::TextureLevel*								m_refDepthBuffer;
+	tcu::TextureLevel*								m_refStencilBuffer;
+	gls::FragmentOpUtil::ReferenceQuadRenderer*		m_refRenderer;
+
+	int												m_iterNdx;
+};
+
+RandomFragmentOpCase::RandomFragmentOpCase (Context& context, const char* name, const char* desc, deUint32 seed)
+	: TestCase				(context, name, desc)
+	, m_seed				(seed)
+	, m_callLogWrapper		(context.getRenderContext().getFunctions(), context.getTestContext().getLog())
+	, m_renderer			(DE_NULL)
+	, m_refColorBuffer		(DE_NULL)
+	, m_refDepthBuffer		(DE_NULL)
+	, m_refStencilBuffer	(DE_NULL)
+	, m_refRenderer			(DE_NULL)
+	, m_iterNdx				(0)
+{
+	m_callLogWrapper.enableLogging(ENABLE_CALL_LOG);
+}
+
+RandomFragmentOpCase::~RandomFragmentOpCase (void)
+{
+	delete m_renderer;
+	delete m_refColorBuffer;
+	delete m_refDepthBuffer;
+	delete m_refStencilBuffer;
+	delete m_refRenderer;
+}
+
+void RandomFragmentOpCase::init (void)
+{
+	DE_ASSERT(!m_renderer && !m_refColorBuffer && !m_refDepthBuffer && !m_refStencilBuffer && !m_refRenderer);
+
+	int		width	= de::min<int>(m_context.getRenderTarget().getWidth(), VIEWPORT_WIDTH);
+	int		height	= de::min<int>(m_context.getRenderTarget().getHeight(), VIEWPORT_HEIGHT);
+	bool	useRGB	= m_context.getRenderTarget().getPixelFormat().alphaBits == 0;
+
+	m_renderer			= new gls::FragmentOpUtil::QuadRenderer(m_context.getRenderContext(), glu::GLSL_VERSION_300_ES);
+	m_refColorBuffer	= new tcu::TextureLevel(tcu::TextureFormat(useRGB ? tcu::TextureFormat::RGB : tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), width, height);
+	m_refDepthBuffer	= new tcu::TextureLevel(tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT),			width, height);
+	m_refStencilBuffer	= new tcu::TextureLevel(tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT32),	width, height);
+	m_refRenderer		= new gls::FragmentOpUtil::ReferenceQuadRenderer();
+	m_iterNdx			= 0;
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void RandomFragmentOpCase::deinit (void)
+{
+	delete m_renderer;
+	delete m_refColorBuffer;
+	delete m_refDepthBuffer;
+	delete m_refStencilBuffer;
+	delete m_refRenderer;
+
+	m_renderer			= DE_NULL;
+	m_refColorBuffer	= DE_NULL;
+	m_refDepthBuffer	= DE_NULL;
+	m_refStencilBuffer	= DE_NULL;
+	m_refRenderer		= DE_NULL;
+}
+
+RandomFragmentOpCase::IterateResult RandomFragmentOpCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const deUint32			iterSeed		= deUint32Hash(m_seed) ^ deInt32Hash(m_iterNdx) ^ deInt32Hash(m_testCtx.getCommandLine().getBaseSeed());
+	de::Random				rnd				(iterSeed);
+
+	const int				width			= m_refColorBuffer->getWidth();
+	const int				height			= m_refColorBuffer->getHeight();
+	const int				viewportX		= rnd.getInt(0, m_context.getRenderTarget().getWidth()-width);
+	const int				viewportY		= rnd.getInt(0, m_context.getRenderTarget().getHeight()-height);
+
+	tcu::Surface			renderedImg		(width, height);
+
+	const Vec4				clearColor		= CLEAR_COLOR;
+	const float				clearDepth		= CLEAR_DEPTH;
+	const int				clearStencil	= CLEAR_STENCIL;
+
+	bool					gotError		= false;
+
+	const tcu::ScopedLogSection	iterSection	(m_testCtx.getLog(), std::string("Iteration") + de::toString(m_iterNdx), std::string("Iteration ") + de::toString(m_iterNdx));
+
+	// Compute randomized rendering commands.
+	vector<RenderCommand> commands;
+	computeRandomRenderCommands(rnd, glu::ApiType::es(3,0), NUM_CALLS_PER_ITERATION, width, height, commands);
+
+	// Reset default fragment state.
+	gl.disable(GL_SCISSOR_TEST);
+	gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+	gl.depthMask(GL_TRUE);
+	gl.stencilMask(~0u);
+
+	// Render using GL.
+	m_callLogWrapper.glClearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+	m_callLogWrapper.glClearDepthf(clearDepth);
+	m_callLogWrapper.glClearStencil(clearStencil);
+	m_callLogWrapper.glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	m_callLogWrapper.glViewport(viewportX, viewportY, width, height);
+
+	for (vector<RenderCommand>::const_iterator cmd = commands.begin(); cmd != commands.end(); cmd++)
+	{
+		setGLState(m_callLogWrapper, cmd->state, viewportX, viewportY);
+
+		if (ENABLE_CALL_LOG)
+			m_testCtx.getLog() << TestLog::Message << "// Quad: " << cmd->quad.posA << " -> " << cmd->quad.posB
+												   << ", color: [" << cmd->quad.color[0] << ", " << cmd->quad.color[1] << ", " << cmd->quad.color[2] << ", " << cmd->quad.color[3] << "]"
+												   << ", depth: [" << cmd->quad.depth[0] << ", " << cmd->quad.depth[1] << ", " << cmd->quad.depth[2] << ", " << cmd->quad.depth[3] << "]"
+								<< TestLog::EndMessage;
+
+		renderQuad(gl, *m_renderer, cmd->quad, viewportX, viewportY);
+	}
+
+	// Check error.
+	if (m_callLogWrapper.glGetError() != GL_NO_ERROR)
+		gotError = true;
+
+	gl.flush();
+
+	// Render reference while GPU is doing work.
+	tcu::clear			(m_refColorBuffer->getAccess(),		clearColor);
+	tcu::clearDepth		(m_refDepthBuffer->getAccess(),		clearDepth);
+	tcu::clearStencil	(m_refStencilBuffer->getAccess(),	clearStencil);
+
+	for (vector<RenderCommand>::const_iterator cmd = commands.begin(); cmd != commands.end(); cmd++)
+	{
+		rr::FragmentOperationState refState;
+		translateState(cmd->state, refState, m_context.getRenderTarget());
+		m_refRenderer->render(gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess()),
+							  gls::FragmentOpUtil::getMultisampleAccess(m_refDepthBuffer->getAccess()),
+							  gls::FragmentOpUtil::getMultisampleAccess(m_refStencilBuffer->getAccess()),
+							  cmd->quad, refState);
+	}
+
+	// Read rendered image.
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedImg.getAccess());
+
+	m_iterNdx += 1;
+
+	// Compare to reference.
+	bool	isLastIter	= m_iterNdx >= NUM_ITERATIONS_PER_CASE;
+	bool	compareOk	= tcu::intThresholdCompare(m_testCtx.getLog(), "CompareResult", "Image Comparison Result", m_refColorBuffer->getAccess(), renderedImg.getAccess(), getCompareThreshold(),
+													 tcu::COMPARE_LOG_RESULT);
+
+	m_testCtx.getLog() << TestLog::Message << (compareOk ? "  Passed." : "  FAILED!") << TestLog::EndMessage;
+
+	if (!compareOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+	else if (gotError)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "GL error");
+
+	if (compareOk && !gotError && !isLastIter)
+		return CONTINUE;
+	else
+		return STOP;
+}
+
+tcu::UVec4 RandomFragmentOpCase::getCompareThreshold (void) const
+{
+	tcu::PixelFormat format = m_context.getRenderTarget().getPixelFormat();
+
+	if (format == tcu::PixelFormat(8, 8, 8, 8) || format == tcu::PixelFormat(8, 8, 8, 0))
+		return format.getColorThreshold().toIVec().asUint() + tcu::UVec4(2); // Default threshold.
+	else
+		return format.getColorThreshold().toIVec().asUint()
+			   * tcu::UVec4(5) + tcu::UVec4(2); // \note Non-scientific ad hoc formula. Need big threshold when few color bits; especially multiple blendings bring extra inaccuracy.
+}
+
+RandomFragmentOpTests::RandomFragmentOpTests (Context& context)
+	: TestCaseGroup(context, "random", "Randomized Per-Fragment Operation Tests")
+{
+}
+
+RandomFragmentOpTests::~RandomFragmentOpTests (void)
+{
+}
+
+void RandomFragmentOpTests::init (void)
+{
+	for (int ndx = 0; ndx < 100; ndx++)
+		addChild(new RandomFragmentOpCase(m_context, de::toString(ndx).c_str(), "", (deUint32)(ndx*NUM_ITERATIONS_PER_CASE)));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fRandomFragmentOpTests.hpp b/modules/gles3/functional/es3fRandomFragmentOpTests.hpp
new file mode 100644
index 0000000..f3cc454
--- /dev/null
+++ b/modules/gles3/functional/es3fRandomFragmentOpTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FRANDOMFRAGMENTOPTESTS_HPP
+#define _ES3FRANDOMFRAGMENTOPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Randomized per-fragment operation tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class RandomFragmentOpTests : public TestCaseGroup
+{
+public:
+							RandomFragmentOpTests	(Context& context);
+							~RandomFragmentOpTests	(void);
+
+	void					init					(void);
+
+private:
+							RandomFragmentOpTests	(const RandomFragmentOpTests& other);
+	RandomFragmentOpTests&	operator=				(const RandomFragmentOpTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FRANDOMFRAGMENTOPTESTS_HPP
diff --git a/modules/gles3/functional/es3fRandomShaderTests.cpp b/modules/gles3/functional/es3fRandomShaderTests.cpp
new file mode 100644
index 0000000..dc28b8b
--- /dev/null
+++ b/modules/gles3/functional/es3fRandomShaderTests.cpp
@@ -0,0 +1,375 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Random shader tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fRandomShaderTests.hpp"
+#include "glsRandomShaderCase.hpp"
+#include "deString.h"
+#include "deStringUtil.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+namespace
+{
+
+gls::RandomShaderCase* createRandomShaderCase (Context& context, const char* description, const rsg::ProgramParameters& baseParams, deUint32 seed, bool vertex, bool fragment)
+{
+	rsg::ProgramParameters params = baseParams;
+
+	params.version						= rsg::VERSION_300;
+	params.seed							= seed;
+	params.vertexParameters.randomize	= vertex;
+	params.fragmentParameters.randomize	= fragment;
+
+	return new gls::RandomShaderCase(context.getTestContext(), context.getRenderContext(), de::toString(seed).c_str(), description, params);
+}
+
+class BasicExpressionGroup : public TestCaseGroup
+{
+public:
+	BasicExpressionGroup (Context& context)
+		: TestCaseGroup(context, "basic_expression", "Basic arithmetic expressions")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		tcu::TestCaseGroup* combinedGroup = new tcu::TestCaseGroup(m_testCtx, "combined", "Combined tests");
+		addChild(combinedGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Random expressions in vertex shader",					params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Random expressions in fragment shader",				params, seed, false, true));
+			combinedGroup->addChild(createRandomShaderCase(m_context,	"Random expressions in vertex and fragment shaders",	params, seed, true, true));
+		}
+	}
+};
+
+class ScalarConversionGroup : public TestCaseGroup
+{
+public:
+	ScalarConversionGroup (Context& context)
+		: TestCaseGroup(context, "scalar_conversion", "Scalar conversions")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions = true;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		tcu::TestCaseGroup* combinedGroup = new tcu::TestCaseGroup(m_testCtx, "combined", "Combined tests");
+		addChild(combinedGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Scalar conversions in vertex shader",					params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Scalar conversions in fragment shader",				params, seed, false, true));
+			combinedGroup->addChild(createRandomShaderCase(m_context,	"Scalar conversions in vertex and fragment shaders",	params, seed, true, true));
+		}
+	}
+};
+
+class SwizzleGroup : public TestCaseGroup
+{
+public:
+	SwizzleGroup (Context& context)
+		: TestCaseGroup(context, "swizzle", "Vector swizzles")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions = true;
+		params.useSwizzle			= true;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		for (int seed = 0; seed < 50; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Vector swizzles in vertex shader",		params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Vector swizzles in fragment shader",	params, seed, false, true));
+		}
+	}
+};
+
+class ComparisonOpsGroup : public TestCaseGroup
+{
+public:
+	ComparisonOpsGroup (Context& context)
+		: TestCaseGroup(context, "comparison_ops", "Comparison operators")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions = true;
+		params.useComparisonOps		= true;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		for (int seed = 0; seed < 50; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Comparison operators in vertex shader",		params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Comparison operators in fragment shader",		params, seed, false, true));
+		}
+	}
+};
+
+class ConditionalsGroup : public TestCaseGroup
+{
+public:
+	ConditionalsGroup (Context& context)
+		: TestCaseGroup(context, "conditionals", "Conditional control flow (if-else)")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions = true;
+		params.useSwizzle			= true;
+		params.useComparisonOps		= true;
+		params.useConditionals		= true;
+		params.vertexParameters.maxStatementDepth		= 4;
+		params.vertexParameters.maxStatementsPerBlock	= 5;
+		params.fragmentParameters.maxStatementDepth		= 4;
+		params.fragmentParameters.maxStatementsPerBlock	= 5;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		tcu::TestCaseGroup* combinedGroup = new tcu::TestCaseGroup(m_testCtx, "combined", "Combined tests");
+		addChild(combinedGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Conditional control flow in vertex shader",				params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Conditional control flow in fragment shader",				params, seed, false, true));
+			combinedGroup->addChild(createRandomShaderCase(m_context,	"Conditional control flow in vertex and fragment shaders",	params, seed, true, true));
+		}
+	}
+};
+
+class TrigonometricGroup : public TestCaseGroup
+{
+public:
+	TrigonometricGroup (Context& context)
+		: TestCaseGroup(context, "trigonometric", "Trigonometric built-in functions")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions		= true;
+		params.useSwizzle				= true;
+		params.trigonometricBaseWeight	= 4.0f;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Trigonometric ops in vertex shader",	params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Trigonometric ops in fragment shader",	params, seed, false, true));
+		}
+	}
+};
+
+class ExponentialGroup : public TestCaseGroup
+{
+public:
+	ExponentialGroup (Context& context)
+		: TestCaseGroup(context, "exponential", "Exponential built-in functions")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions		= true;
+		params.useSwizzle				= true;
+		params.exponentialBaseWeight	= 4.0f;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Exponential ops in vertex shader",		params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Exponential ops in fragment shader",	params, seed, false, true));
+		}
+	}
+};
+
+class TextureGroup : public TestCaseGroup
+{
+public:
+	TextureGroup (Context& context)
+		: TestCaseGroup(context, "texture", "Texture lookups")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions						= true;
+		params.useSwizzle								= true;
+		params.vertexParameters.texLookupBaseWeight		= 10.0f;
+		params.vertexParameters.useTexture2D			= true;
+		params.vertexParameters.useTextureCube			= true;
+		params.fragmentParameters.texLookupBaseWeight	= 10.0f;
+		params.fragmentParameters.useTexture2D			= true;
+		params.fragmentParameters.useTextureCube		= true;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		// Do only 50 vertex cases and 150 fragment cases.
+		for (int seed = 0; seed < 50; seed++)
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Texture lookups in vertex shader",		params, seed, true, false));
+
+		for (int seed = 0; seed < 150; seed++)
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Texture lookups in fragment shader",	params, seed, false, true));
+	}
+};
+
+class AllFeaturesGroup : public TestCaseGroup
+{
+public:
+	AllFeaturesGroup (Context& context)
+		: TestCaseGroup(context, "all_features", "All features enabled")
+	{
+	}
+
+	void init (void)
+	{
+		rsg::ProgramParameters params;
+		params.useScalarConversions		= true;
+		params.useSwizzle				= true;
+		params.useComparisonOps			= true;
+		params.useConditionals			= true;
+		params.trigonometricBaseWeight	= 1.0f;
+		params.exponentialBaseWeight	= 1.0f;
+
+		params.vertexParameters.maxStatementDepth				= 4;
+		params.vertexParameters.maxStatementsPerBlock			= 7;
+		params.vertexParameters.maxExpressionDepth				= 7;
+		params.vertexParameters.maxCombinedVariableScalars		= 64;
+		params.fragmentParameters.maxStatementDepth				= 4;
+		params.fragmentParameters.maxStatementsPerBlock			= 7;
+		params.fragmentParameters.maxExpressionDepth			= 7;
+		params.fragmentParameters.maxCombinedVariableScalars	= 64;
+
+		params.fragmentParameters.texLookupBaseWeight		= 4.0f; // \note Texture lookups are enabled for fragment shaders only.
+		params.fragmentParameters.useTexture2D				= true;
+		params.fragmentParameters.useTextureCube			= true;
+
+		tcu::TestCaseGroup* vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Vertex-only tests");
+		addChild(vertexGroup);
+
+		tcu::TestCaseGroup* fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Fragment-only tests");
+		addChild(fragmentGroup);
+
+		for (int seed = 0; seed < 100; seed++)
+		{
+			vertexGroup->addChild(createRandomShaderCase(m_context,		"Texture lookups in vertex shader",		params, seed, true, false));
+			fragmentGroup->addChild(createRandomShaderCase(m_context,	"Texture lookups in fragment shader",	params, seed, false, true));
+		}
+	}
+};
+
+} // anonymous
+
+RandomShaderTests::RandomShaderTests (Context& context)
+	: TestCaseGroup(context, "random", "Random shaders")
+{
+}
+
+RandomShaderTests::~RandomShaderTests (void)
+{
+}
+
+namespace
+{
+
+} // anonymous
+
+void RandomShaderTests::init (void)
+{
+	addChild(new BasicExpressionGroup	(m_context));
+	addChild(new ScalarConversionGroup	(m_context));
+	addChild(new SwizzleGroup			(m_context));
+	addChild(new ComparisonOpsGroup		(m_context));
+	addChild(new ConditionalsGroup		(m_context));
+	addChild(new TrigonometricGroup		(m_context));
+	addChild(new ExponentialGroup		(m_context));
+	addChild(new TextureGroup			(m_context));
+	addChild(new AllFeaturesGroup		(m_context));
+}
+
+} // Functional
+} // gles2
+} // deqp
diff --git a/modules/gles3/functional/es3fRandomShaderTests.hpp b/modules/gles3/functional/es3fRandomShaderTests.hpp
new file mode 100644
index 0000000..581a203
--- /dev/null
+++ b/modules/gles3/functional/es3fRandomShaderTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FRANDOMSHADERTESTS_HPP
+#define _ES3FRANDOMSHADERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Random shader tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class RandomShaderTests : public TestCaseGroup
+{
+public:
+							RandomShaderTests			(Context& context);
+							~RandomShaderTests			(void);
+
+	void					init						(void);
+
+private:
+							RandomShaderTests			(const RandomShaderTests& other);
+	RandomShaderTests&		operator=					(const RandomShaderTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FRANDOMSHADERTESTS_HPP
diff --git a/modules/gles3/functional/es3fRasterizationTests.cpp b/modules/gles3/functional/es3fRasterizationTests.cpp
new file mode 100644
index 0000000..d32f455
--- /dev/null
+++ b/modules/gles3/functional/es3fRasterizationTests.cpp
@@ -0,0 +1,2364 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Functional rasterization tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fRasterizationTests.hpp"
+#include "glsRasterizationTestUtil.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuTextureUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+using namespace gls::RasterizationTestUtil;
+
+static const char* const s_shaderVertexTemplate =	"#version 300 es\n"
+													"in highp vec4 a_position;\n"
+													"in highp vec4 a_color;\n"
+													"${INTERPOLATION}out highp vec4 v_color;\n"
+													"uniform highp float u_pointSize;\n"
+													"void main ()\n"
+													"{\n"
+													"	gl_Position = a_position;\n"
+													"	gl_PointSize = u_pointSize;\n"
+													"	v_color = a_color;\n"
+													"}\n";
+static const char* const s_shaderFragmentTemplate =	"#version 300 es\n"
+													"layout(location = 0) out highp vec4 fragColor;\n"
+													"${INTERPOLATION}in highp vec4 v_color;\n"
+													"void main ()\n"
+													"{\n"
+													"	fragColor = v_color;\n"
+													"}\n";
+enum InterpolationCaseFlags
+{
+	INTERPOLATIONFLAGS_NONE = 0,
+	INTERPOLATIONFLAGS_PROJECTED = (1 << 1),
+	INTERPOLATIONFLAGS_FLATSHADE = (1 << 2),
+};
+
+enum PrimitiveWideness
+{
+	PRIMITIVEWIDENESS_NARROW = 0,
+	PRIMITIVEWIDENESS_WIDE,
+
+	PRIMITIVEWIDENESS_LAST
+};
+
+static tcu::PixelFormat getInternalFormatPixelFormat (glw::GLenum internalFormat)
+{
+	const tcu::IVec4 bitDepth = tcu::getTextureFormatBitDepth(glu::mapGLInternalFormat(internalFormat));
+	return tcu::PixelFormat(bitDepth.x(), bitDepth.y(), bitDepth.z(), bitDepth.w());
+}
+
+class BaseRenderingCase : public TestCase
+{
+public:
+	enum RenderTarget
+	{
+		RENDERTARGET_DEFAULT = 0,
+		RENDERTARGET_TEXTURE_2D,
+		RENDERTARGET_RBO_SINGLESAMPLE,
+		RENDERTARGET_RBO_MULTISAMPLE,
+
+		RENDERTARGET_LAST
+	};
+
+	enum
+	{
+		DEFAULT_RENDER_SIZE = 256,
+		SAMPLE_COUNT_MAX = -2,
+	};
+
+							BaseRenderingCase	(Context& context, const char* name, const char* desc, RenderTarget target, int numSamples, int renderSize);
+							~BaseRenderingCase	(void);
+	virtual void			init				(void);
+	void					deinit				(void);
+
+protected:
+	void					drawPrimitives		(tcu::Surface& result, const std::vector<tcu::Vec4>& vertexData, glw::GLenum primitiveType);
+	void					drawPrimitives		(tcu::Surface& result, const std::vector<tcu::Vec4>& vertexData, const std::vector<tcu::Vec4>& coloDrata, glw::GLenum primitiveType);
+
+	virtual float			getLineWidth		(void) const;
+	virtual float			getPointSize		(void) const;
+	const tcu::PixelFormat&	getPixelFormat		(void) const;
+
+	const int				m_renderSize;
+	int						m_numSamples;
+	int						m_subpixelBits;
+	bool					m_flatshade;
+	const int				m_numRequestedSamples;
+
+private:
+	const RenderTarget		m_renderTarget;
+	const glw::GLenum		m_fboInternalFormat;
+	const tcu::PixelFormat	m_pixelFormat;
+	glu::ShaderProgram*		m_shader;
+	glw::GLuint				m_fbo;
+	glw::GLuint				m_texture;
+	glw::GLuint				m_rbo;
+	glw::GLuint				m_blitDstFbo;
+	glw::GLuint				m_blitDstRbo;
+};
+
+BaseRenderingCase::BaseRenderingCase (Context& context, const char* name, const char* desc, RenderTarget target, int numSamples, int renderSize)
+	: TestCase				(context, name, desc)
+	, m_renderSize			(renderSize)
+	, m_numSamples			(-1)
+	, m_subpixelBits		(-1)
+	, m_flatshade			(false)
+	, m_numRequestedSamples	(numSamples)
+	, m_renderTarget		(target)
+	, m_fboInternalFormat	(GL_RGBA8)
+	, m_pixelFormat			((m_renderTarget == RENDERTARGET_DEFAULT) ? (m_context.getRenderTarget().getPixelFormat()) : (getInternalFormatPixelFormat(m_fboInternalFormat)))
+	, m_shader				(DE_NULL)
+	, m_fbo					(0)
+	, m_texture				(0)
+	, m_rbo					(0)
+	, m_blitDstFbo			(0)
+	, m_blitDstRbo			(0)
+{
+	DE_ASSERT(m_renderTarget < RENDERTARGET_LAST);
+	DE_ASSERT((m_numRequestedSamples == -1) == (m_renderTarget != RENDERTARGET_RBO_MULTISAMPLE));
+}
+
+BaseRenderingCase::~BaseRenderingCase (void)
+{
+	deinit();
+}
+
+void BaseRenderingCase::init (void)
+{
+	const glw::Functions&	gl						= m_context.getRenderContext().getFunctions();
+	const int				width					= m_context.getRenderTarget().getWidth();
+	const int				height					= m_context.getRenderTarget().getHeight();
+	int						msaaTargetSamples		= -1;
+
+	// Requirements
+
+	if (m_renderTarget == RENDERTARGET_DEFAULT && (width < m_renderSize || height < m_renderSize))
+		throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(m_renderSize) + "x" + de::toString(m_renderSize));
+
+	if (m_renderTarget == RENDERTARGET_RBO_MULTISAMPLE)
+	{
+		glw::GLint maxSampleCount = 0;
+		gl.getInternalformativ(GL_RENDERBUFFER, m_fboInternalFormat, GL_SAMPLES, 1, &maxSampleCount);
+
+		if (m_numRequestedSamples == SAMPLE_COUNT_MAX)
+			msaaTargetSamples = maxSampleCount;
+		else if (maxSampleCount >= m_numRequestedSamples)
+			msaaTargetSamples = m_numRequestedSamples;
+		else
+			throw tcu::NotSupportedError("Test requires " + de::toString(m_numRequestedSamples) + "x msaa rbo");
+	}
+
+	// Gen shader
+
+	{
+		tcu::StringTemplate					vertexSource	(s_shaderVertexTemplate);
+		tcu::StringTemplate					fragmentSource	(s_shaderFragmentTemplate);
+		std::map<std::string, std::string>	params;
+
+		params["INTERPOLATION"] = (m_flatshade) ? ("flat ") : ("");
+
+		m_shader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexSource.specialize(params)) << glu::FragmentSource(fragmentSource.specialize(params)));
+		if (!m_shader->isOk())
+			throw tcu::TestError("could not create shader");
+	}
+
+	// Fbo
+	if (m_renderTarget != RENDERTARGET_DEFAULT)
+	{
+		glw::GLenum error;
+
+		gl.genFramebuffers(1, &m_fbo);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+
+		switch (m_renderTarget)
+		{
+			case RENDERTARGET_TEXTURE_2D:
+			{
+				gl.genTextures(1, &m_texture);
+				gl.bindTexture(GL_TEXTURE_2D, m_texture);
+				gl.texStorage2D(GL_TEXTURE_2D, 1, m_fboInternalFormat, m_renderSize, m_renderSize);
+
+				error = gl.getError();
+				if (error == GL_OUT_OF_MEMORY)
+					throw tcu::NotSupportedError("could not create target texture, got out of memory");
+				else if (error != GL_NO_ERROR)
+					throw tcu::TestError("got " + de::toString(glu::getErrorStr(error)));
+
+				gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0);
+				break;
+			}
+
+			case RENDERTARGET_RBO_SINGLESAMPLE:
+			case RENDERTARGET_RBO_MULTISAMPLE:
+			{
+				gl.genRenderbuffers(1, &m_rbo);
+				gl.bindRenderbuffer(GL_RENDERBUFFER, m_rbo);
+
+				if (m_renderTarget == RENDERTARGET_RBO_SINGLESAMPLE)
+					gl.renderbufferStorage(GL_RENDERBUFFER, m_fboInternalFormat, m_renderSize, m_renderSize);
+				else if (m_renderTarget == RENDERTARGET_RBO_MULTISAMPLE)
+					gl.renderbufferStorageMultisample(GL_RENDERBUFFER, msaaTargetSamples, m_fboInternalFormat, m_renderSize, m_renderSize);
+				else
+					DE_ASSERT(false);
+
+				error = gl.getError();
+				if (error == GL_OUT_OF_MEMORY)
+					throw tcu::NotSupportedError("could not create target texture, got out of memory");
+				else if (error != GL_NO_ERROR)
+					throw tcu::TestError("got " + de::toString(glu::getErrorStr(error)));
+
+				gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo);
+				break;
+			}
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	// Resolve (blitFramebuffer) target fbo for MSAA targets
+	if (m_renderTarget == RENDERTARGET_RBO_MULTISAMPLE)
+	{
+		glw::GLenum error;
+
+		gl.genFramebuffers(1, &m_blitDstFbo);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_blitDstFbo);
+
+		gl.genRenderbuffers(1, &m_blitDstRbo);
+		gl.bindRenderbuffer(GL_RENDERBUFFER, m_blitDstRbo);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, m_renderSize, m_renderSize);
+
+		error = gl.getError();
+		if (error == GL_OUT_OF_MEMORY)
+			throw tcu::NotSupportedError("could not create blit target, got out of memory");
+		else if (error != GL_NO_ERROR)
+			throw tcu::TestError("got " + de::toString(glu::getErrorStr(error)));
+
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_blitDstRbo);
+
+		// restore state
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+	}
+
+	// Query info
+
+	if (m_renderTarget == RENDERTARGET_DEFAULT)
+		m_numSamples = m_context.getRenderTarget().getNumSamples();
+	else if (m_renderTarget == RENDERTARGET_RBO_MULTISAMPLE)
+	{
+		m_numSamples = -1;
+		gl.bindRenderbuffer(GL_RENDERBUFFER, m_rbo);
+		gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &m_numSamples);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "get RENDERBUFFER_SAMPLES");
+	}
+	else
+		m_numSamples = 0;
+
+	gl.getIntegerv(GL_SUBPIXEL_BITS, &m_subpixelBits);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Sample count = " << m_numSamples << tcu::TestLog::EndMessage;
+	m_testCtx.getLog() << tcu::TestLog::Message << "SUBPIXEL_BITS = " << m_subpixelBits << tcu::TestLog::EndMessage;
+}
+
+void BaseRenderingCase::deinit (void)
+{
+	if (m_shader)
+	{
+		delete m_shader;
+		m_shader = DE_NULL;
+	}
+
+	if (m_fbo)
+	{
+		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_fbo);
+		m_fbo = 0;
+	}
+
+	if (m_rbo)
+	{
+		m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_rbo);
+		m_rbo = 0;
+	}
+
+	if (m_texture)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texture);
+		m_texture = 0;
+	}
+
+	if (m_blitDstFbo)
+	{
+		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_blitDstFbo);
+		m_blitDstFbo = 0;
+	}
+
+	if (m_blitDstRbo)
+	{
+		m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_blitDstRbo);
+		m_blitDstRbo = 0;
+	}
+}
+
+void BaseRenderingCase::drawPrimitives (tcu::Surface& result, const std::vector<tcu::Vec4>& vertexData, glw::GLenum primitiveType)
+{
+	// default to color white
+	const std::vector<tcu::Vec4> colorData(vertexData.size(), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+	drawPrimitives(result, vertexData, colorData, primitiveType);
+}
+
+void BaseRenderingCase::drawPrimitives (tcu::Surface& result, const std::vector<tcu::Vec4>& vertexData, const std::vector<tcu::Vec4>& colorData, glw::GLenum primitiveType)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const glw::GLint		positionLoc		= gl.getAttribLocation(m_shader->getProgram(), "a_position");
+	const glw::GLint		colorLoc		= gl.getAttribLocation(m_shader->getProgram(), "a_color");
+	const glw::GLint		pointSizeLoc	= gl.getUniformLocation(m_shader->getProgram(), "u_pointSize");
+
+	gl.clearColor					(0, 0, 0, 1);
+	gl.clear						(GL_COLOR_BUFFER_BIT);
+	gl.viewport						(0, 0, m_renderSize, m_renderSize);
+	gl.useProgram					(m_shader->getProgram());
+	gl.enableVertexAttribArray		(positionLoc);
+	gl.vertexAttribPointer			(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &vertexData[0]);
+	gl.enableVertexAttribArray		(colorLoc);
+	gl.vertexAttribPointer			(colorLoc, 4, GL_FLOAT, GL_FALSE, 0, &colorData[0]);
+	gl.uniform1f					(pointSizeLoc, getPointSize());
+	gl.lineWidth					(getLineWidth());
+	gl.drawArrays					(primitiveType, 0, (glw::GLsizei)vertexData.size());
+	gl.disableVertexAttribArray		(colorLoc);
+	gl.disableVertexAttribArray		(positionLoc);
+	gl.useProgram					(0);
+	gl.finish						();
+	GLU_EXPECT_NO_ERROR				(gl.getError(), "draw primitives");
+
+	// read pixels
+	if (m_renderTarget == RENDERTARGET_RBO_MULTISAMPLE)
+	{
+		// resolve msaa
+		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
+		gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_blitDstFbo);
+
+		gl.blitFramebuffer(0, 0, m_renderSize, m_renderSize, 0, 0, m_renderSize, m_renderSize, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "blit");
+
+		// read resolved
+		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_blitDstFbo);
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+	}
+	else
+	{
+		glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
+		GLU_EXPECT_NO_ERROR				(gl.getError(), "read pixels");
+	}
+}
+
+float BaseRenderingCase::getLineWidth (void) const
+{
+	return 1.0f;
+}
+
+float BaseRenderingCase::getPointSize (void) const
+{
+	return 1.0f;
+}
+
+const tcu::PixelFormat& BaseRenderingCase::getPixelFormat (void) const
+{
+	return m_pixelFormat;
+}
+
+class BaseTriangleCase : public BaseRenderingCase
+{
+public:
+							BaseTriangleCase	(Context& context, const char* name, const char* desc, glw::GLenum primitiveDrawType, BaseRenderingCase::RenderTarget renderTarget, int numSamples);
+							~BaseTriangleCase	(void);
+	IterateResult			iterate				(void);
+
+private:
+	virtual void			generateTriangles	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles) = DE_NULL;
+
+	int						m_iteration;
+	const int				m_iterationCount;
+	const glw::GLenum		m_primitiveDrawType;
+	bool					m_allIterationsPassed;
+};
+
+BaseTriangleCase::BaseTriangleCase (Context& context, const char* name, const char* desc, glw::GLenum primitiveDrawType, BaseRenderingCase::RenderTarget renderTarget, int numSamples)
+	: BaseRenderingCase		(context, name, desc, renderTarget, numSamples, DEFAULT_RENDER_SIZE)
+	, m_iteration			(0)
+	, m_iterationCount		(3)
+	, m_primitiveDrawType	(primitiveDrawType)
+	, m_allIterationsPassed	(true)
+{
+}
+
+BaseTriangleCase::~BaseTriangleCase (void)
+{
+}
+
+BaseTriangleCase::IterateResult BaseTriangleCase::iterate (void)
+{
+	const std::string								iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection						section					(m_testCtx.getLog(), iterationDescription, iterationDescription);
+	tcu::Surface									resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>							drawBuffer;
+	std::vector<TriangleSceneSpec::SceneTriangle>	triangles;
+
+	generateTriangles(m_iteration, drawBuffer, triangles);
+
+	// draw image
+	drawPrimitives(resultImage, drawBuffer, m_primitiveDrawType);
+
+	// compare
+	{
+		bool					compareOk;
+		RasterizationArguments	args;
+		TriangleSceneSpec		scene;
+
+		args.numSamples		= m_numSamples;
+		args.subpixelBits	= m_subpixelBits;
+		args.redBits		= getPixelFormat().redBits;
+		args.greenBits		= getPixelFormat().greenBits;
+		args.blueBits		= getPixelFormat().blueBits;
+
+		scene.triangles.swap(triangles);
+
+		compareOk = verifyTriangleGroupRasterization(resultImage, scene, args, m_testCtx.getLog());
+
+		if (!compareOk)
+			m_allIterationsPassed = false;
+	}
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect rasterization");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+class BaseLineCase : public BaseRenderingCase
+{
+public:
+							BaseLineCase		(Context& context, const char* name, const char* desc, glw::GLenum primitiveDrawType, PrimitiveWideness wideness, BaseRenderingCase::RenderTarget renderTarget, int numSamples);
+							~BaseLineCase		(void);
+
+	void					init				(void);
+	IterateResult			iterate				(void);
+	float					getLineWidth		(void) const;
+
+private:
+	virtual void			generateLines		(int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines) = DE_NULL;
+
+	int						m_iteration;
+	const int				m_iterationCount;
+	const glw::GLenum		m_primitiveDrawType;
+	const PrimitiveWideness	m_primitiveWideness;
+	bool					m_allIterationsPassed;
+	float					m_maxLineWidth;
+	std::vector<float>		m_lineWidths;
+};
+
+BaseLineCase::BaseLineCase (Context& context, const char* name, const char* desc, glw::GLenum primitiveDrawType, PrimitiveWideness wideness, BaseRenderingCase::RenderTarget renderTarget, int numSamples)
+	: BaseRenderingCase		(context, name, desc, renderTarget, numSamples, DEFAULT_RENDER_SIZE)
+	, m_iteration			(0)
+	, m_iterationCount		(3)
+	, m_primitiveDrawType	(primitiveDrawType)
+	, m_primitiveWideness	(wideness)
+	, m_allIterationsPassed	(true)
+	, m_maxLineWidth		(1.0f)
+{
+	DE_ASSERT(m_primitiveWideness < PRIMITIVEWIDENESS_LAST);
+}
+
+BaseLineCase::~BaseLineCase (void)
+{
+}
+
+void BaseLineCase::init (void)
+{
+	// create line widths
+	if (m_primitiveWideness == PRIMITIVEWIDENESS_NARROW)
+	{
+		m_lineWidths.resize(m_iterationCount, 1.0f);
+	}
+	else if (m_primitiveWideness == PRIMITIVEWIDENESS_WIDE)
+	{
+		float range[2] = { 0.0f, 0.0f };
+		m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_LINE_WIDTH_RANGE, range);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "ALIASED_LINE_WIDTH_RANGE = [" << range[0] << ", " << range[1] << "]" << tcu::TestLog::EndMessage;
+
+		// no wide line support
+		if (range[1] <= 1.0f)
+			throw tcu::NotSupportedError("wide line support required");
+
+		// set hand picked sizes
+		m_lineWidths.push_back(5.0f);
+		m_lineWidths.push_back(10.0f);
+		m_lineWidths.push_back(range[1]);
+		DE_ASSERT((int)m_lineWidths.size() == m_iterationCount);
+
+		m_maxLineWidth = range[1];
+	}
+	else
+		DE_ASSERT(false);
+
+	// init parent
+	BaseRenderingCase::init();
+}
+
+BaseLineCase::IterateResult BaseLineCase::iterate (void)
+{
+	const std::string						iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection				section					(m_testCtx.getLog(), iterationDescription, iterationDescription);
+	const float								lineWidth				= getLineWidth();
+	tcu::Surface							resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>					drawBuffer;
+	std::vector<LineSceneSpec::SceneLine>	lines;
+
+	// supported?
+	if (lineWidth <= m_maxLineWidth)
+	{
+		// gen data
+		generateLines(m_iteration, drawBuffer, lines);
+
+		// draw image
+		drawPrimitives(resultImage, drawBuffer, m_primitiveDrawType);
+
+		// compare
+		{
+			bool					compareOk;
+			RasterizationArguments	args;
+			LineSceneSpec			scene;
+
+			args.numSamples		= m_numSamples;
+			args.subpixelBits	= m_subpixelBits;
+			args.redBits		= getPixelFormat().redBits;
+			args.greenBits		= getPixelFormat().greenBits;
+			args.blueBits		= getPixelFormat().blueBits;
+
+			scene.lines.swap(lines);
+			scene.lineWidth = lineWidth;
+
+			compareOk = verifyLineGroupRasterization(resultImage, scene, args, m_testCtx.getLog());
+
+			if (!compareOk)
+				m_allIterationsPassed = false;
+		}
+	}
+	else
+		m_testCtx.getLog() << tcu::TestLog::Message << "Line width " << lineWidth << " not supported, skipping iteration." << tcu::TestLog::EndMessage;
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect rasterization");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+float BaseLineCase::getLineWidth (void) const
+{
+	return m_lineWidths[m_iteration];
+}
+
+class PointCase : public BaseRenderingCase
+{
+public:
+							PointCase		(Context& context, const char* name, const char* desc, PrimitiveWideness wideness, BaseRenderingCase::RenderTarget renderTarget = RENDERTARGET_DEFAULT, int numSamples = -1);
+							~PointCase		(void);
+
+	void					init			(void);
+	IterateResult			iterate			(void);
+
+protected:
+	float					getPointSize	(void) const;
+
+private:
+	void					generatePoints	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<PointSceneSpec::ScenePoint>& outPoints);
+
+	int						m_iteration;
+	const int				m_iterationCount;
+	const PrimitiveWideness	m_primitiveWideness;
+	bool					m_allIterationsPassed;
+
+	float					m_maxPointSize;
+	std::vector<float>		m_pointSizes;
+};
+
+PointCase::PointCase (Context& context, const char* name, const char* desc, PrimitiveWideness wideness, BaseRenderingCase::RenderTarget renderTarget, int numSamples)
+	: BaseRenderingCase		(context, name, desc, renderTarget, numSamples, DEFAULT_RENDER_SIZE)
+	, m_iteration			(0)
+	, m_iterationCount		(3)
+	, m_primitiveWideness	(wideness)
+	, m_allIterationsPassed	(true)
+	, m_maxPointSize		(1.0f)
+{
+}
+
+PointCase::~PointCase (void)
+{
+}
+
+void PointCase::init (void)
+{
+	// create point sizes
+	if (m_primitiveWideness == PRIMITIVEWIDENESS_NARROW)
+	{
+		m_pointSizes.resize(m_iterationCount, 1.0f);
+	}
+	else if (m_primitiveWideness == PRIMITIVEWIDENESS_WIDE)
+	{
+		float range[2] = { 0.0f, 0.0f };
+		m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_POINT_SIZE_RANGE, range);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_ALIASED_POINT_SIZE_RANGE = [" << range[0] << ", " << range[1] << "]" << tcu::TestLog::EndMessage;
+
+		// no wide line support
+		if (range[1] <= 1.0f)
+			throw tcu::NotSupportedError("wide point support required");
+
+		// set hand picked sizes
+		m_pointSizes.push_back(10.0f);
+		m_pointSizes.push_back(25.0f);
+		m_pointSizes.push_back(range[1]);
+		DE_ASSERT((int)m_pointSizes.size() == m_iterationCount);
+
+		m_maxPointSize = range[1];
+	}
+	else
+		DE_ASSERT(false);
+
+	// init parent
+	BaseRenderingCase::init();
+}
+
+PointCase::IterateResult PointCase::iterate (void)
+{
+	const std::string						iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection				section					(m_testCtx.getLog(), iterationDescription, iterationDescription);
+	const float								pointSize				= getPointSize();
+	tcu::Surface							resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>					drawBuffer;
+	std::vector<PointSceneSpec::ScenePoint>	points;
+
+	// supported?
+	if (pointSize <= m_maxPointSize)
+	{
+		// gen data
+		generatePoints(m_iteration, drawBuffer, points);
+
+		// draw image
+		drawPrimitives(resultImage, drawBuffer, GL_POINTS);
+
+		// compare
+		{
+			bool					compareOk;
+			RasterizationArguments	args;
+			PointSceneSpec			scene;
+
+			args.numSamples		= m_numSamples;
+			args.subpixelBits	= m_subpixelBits;
+			args.redBits		= getPixelFormat().redBits;
+			args.greenBits		= getPixelFormat().greenBits;
+			args.blueBits		= getPixelFormat().blueBits;
+
+			scene.points.swap(points);
+
+			compareOk = verifyPointGroupRasterization(resultImage, scene, args, m_testCtx.getLog());
+
+			if (!compareOk)
+				m_allIterationsPassed = false;
+		}
+	}
+	else
+		m_testCtx.getLog() << tcu::TestLog::Message << "Point size " << pointSize << " not supported, skipping iteration." << tcu::TestLog::EndMessage;
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect rasterization");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+float PointCase::getPointSize (void) const
+{
+	return m_pointSizes[m_iteration];
+}
+
+void PointCase::generatePoints (int iteration, std::vector<tcu::Vec4>& outData, std::vector<PointSceneSpec::ScenePoint>& outPoints)
+{
+	outData.resize(6);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.2f,  0.8f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,  0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.5f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.5f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(-0.2f, -0.4f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(-0.4f,  0.2f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(  0.88f,   0.9f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(   0.4f,   1.2f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  0.3f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( -0.4f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.11f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 0.88f,  0.7f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4( -0.4f,  0.4f, 0.0f, 1.0f);
+			break;
+	}
+
+	outPoints.resize(outData.size());
+	for (int pointNdx = 0; pointNdx < (int)outPoints.size(); ++pointNdx)
+	{
+		outPoints[pointNdx].position = outData[pointNdx];
+		outPoints[pointNdx].pointSize = getPointSize();
+	}
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << outPoints.size() << " point(s): (point size = " << getPointSize() << ")" << tcu::TestLog::EndMessage;
+	for (int pointNdx = 0; pointNdx < (int)outPoints.size(); ++pointNdx)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Point " << (pointNdx+1) << ":\t" << outPoints[pointNdx].position << tcu::TestLog::EndMessage;
+}
+
+class TrianglesCase : public BaseTriangleCase
+{
+public:
+	TrianglesCase		(Context& context, const char* name, const char* desc, BaseRenderingCase::RenderTarget renderTarget = RENDERTARGET_DEFAULT, int numSamples = -1);
+	~TrianglesCase		(void);
+
+	void	generateTriangles	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles);
+};
+
+TrianglesCase::TrianglesCase (Context& context, const char* name, const char* desc, BaseRenderingCase::RenderTarget renderTarget, int numSamples)
+	: BaseTriangleCase(context, name, desc, GL_TRIANGLES, renderTarget, numSamples)
+{
+}
+
+TrianglesCase::~TrianglesCase (void)
+{
+
+}
+
+void TrianglesCase::generateTriangles (int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles)
+{
+	outData.resize(6);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.2f,  0.8f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,  0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.5f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.5f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(-1.5f, -0.4f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(-0.4f,  0.2f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(  0.88f,   0.9f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(   0.4f,   1.2f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( -1.1f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.11f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 0.88f,  0.7f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4( -0.4f,  0.4f, 0.0f, 1.0f);
+			break;
+	}
+
+	outTriangles.resize(2);
+	outTriangles[0].positions[0] = outData[0];	outTriangles[0].sharedEdge[0] = false;
+	outTriangles[0].positions[1] = outData[1];	outTriangles[0].sharedEdge[1] = false;
+	outTriangles[0].positions[2] = outData[2];	outTriangles[0].sharedEdge[2] = false;
+
+	outTriangles[1].positions[0] = outData[3];	outTriangles[1].sharedEdge[0] = false;
+	outTriangles[1].positions[1] = outData[4];	outTriangles[1].sharedEdge[1] = false;
+	outTriangles[1].positions[2] = outData[5];	outTriangles[1].sharedEdge[2] = false;
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << outTriangles.size() << " triangle(s):" << tcu::TestLog::EndMessage;
+	for (int triangleNdx = 0; triangleNdx < (int)outTriangles.size(); ++triangleNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Triangle " << (triangleNdx+1) << ":"
+			<< "\n\t" << outTriangles[triangleNdx].positions[0]
+			<< "\n\t" << outTriangles[triangleNdx].positions[1]
+			<< "\n\t" << outTriangles[triangleNdx].positions[2]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class TriangleStripCase : public BaseTriangleCase
+{
+public:
+			TriangleStripCase	(Context& context, const char* name, const char* desc, BaseRenderingCase::RenderTarget renderTarget = RENDERTARGET_DEFAULT, int numSamples = -1);
+
+	void	generateTriangles	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles);
+};
+
+TriangleStripCase::TriangleStripCase (Context& context, const char* name, const char* desc, BaseRenderingCase::RenderTarget renderTarget, int numSamples)
+	: BaseTriangleCase(context, name, desc, GL_TRIANGLE_STRIP, renderTarget, numSamples)
+{
+}
+
+void TriangleStripCase::generateTriangles (int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles)
+{
+	outData.resize(5);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4(-0.504f,  0.8f,   0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.2f,   -0.2f,   0.0f, 1.0f);
+			outData[2] = tcu::Vec4(-0.2f,    0.199f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4( 0.5f,    0.201f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 1.5f,    0.4f,   0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.129f,  0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f,  0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f,  0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,  -0.31f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(  0.88f,   0.9f,  0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f,  0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f,  0.0f, 1.0f);
+			outData[2] = tcu::Vec4(-0.87f, -0.1f,  0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.11f,  0.19f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 0.88f,  0.7f,  0.0f, 1.0f);
+			break;
+	}
+
+	outTriangles.resize(3);
+	outTriangles[0].positions[0] = outData[0];	outTriangles[0].sharedEdge[0] = false;
+	outTriangles[0].positions[1] = outData[1];	outTriangles[0].sharedEdge[1] = true;
+	outTriangles[0].positions[2] = outData[2];	outTriangles[0].sharedEdge[2] = false;
+
+	outTriangles[1].positions[0] = outData[2];	outTriangles[1].sharedEdge[0] = true;
+	outTriangles[1].positions[1] = outData[1];	outTriangles[1].sharedEdge[1] = false;
+	outTriangles[1].positions[2] = outData[3];	outTriangles[1].sharedEdge[2] = true;
+
+	outTriangles[2].positions[0] = outData[2];	outTriangles[2].sharedEdge[0] = true;
+	outTriangles[2].positions[1] = outData[3];	outTriangles[2].sharedEdge[1] = false;
+	outTriangles[2].positions[2] = outData[4];	outTriangles[2].sharedEdge[2] = false;
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering triangle strip, " << outData.size() << " vertices." << tcu::TestLog::EndMessage;
+	for (int vtxNdx = 0; vtxNdx < (int)outData.size(); ++vtxNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "\t" << outData[vtxNdx]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class TriangleFanCase : public BaseTriangleCase
+{
+public:
+			TriangleFanCase		(Context& context, const char* name, const char* desc, BaseRenderingCase::RenderTarget renderTarget = RENDERTARGET_DEFAULT, int numSamples = -1);
+
+	void	generateTriangles	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles);
+};
+
+TriangleFanCase::TriangleFanCase (Context& context, const char* name, const char* desc, BaseRenderingCase::RenderTarget renderTarget, int numSamples)
+	: BaseTriangleCase(context, name, desc, GL_TRIANGLE_FAN, renderTarget, numSamples)
+{
+}
+
+void TriangleFanCase::generateTriangles (int iteration, std::vector<tcu::Vec4>& outData, std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles)
+{
+	outData.resize(5);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.01f,  0.0f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,   0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.46f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.5f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(-1.5f,  -0.4f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(  0.88f,   0.9f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.7f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4( 0.11f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 0.88f,  0.7f, 0.0f, 1.0f);
+			break;
+	}
+
+	outTriangles.resize(3);
+	outTriangles[0].positions[0] = outData[0];	outTriangles[0].sharedEdge[0] = false;
+	outTriangles[0].positions[1] = outData[1];	outTriangles[0].sharedEdge[1] = false;
+	outTriangles[0].positions[2] = outData[2];	outTriangles[0].sharedEdge[2] = true;
+
+	outTriangles[1].positions[0] = outData[0];	outTriangles[1].sharedEdge[0] = true;
+	outTriangles[1].positions[1] = outData[2];	outTriangles[1].sharedEdge[1] = false;
+	outTriangles[1].positions[2] = outData[3];	outTriangles[1].sharedEdge[2] = true;
+
+	outTriangles[2].positions[0] = outData[0];	outTriangles[2].sharedEdge[0] = true;
+	outTriangles[2].positions[1] = outData[3];	outTriangles[2].sharedEdge[1] = false;
+	outTriangles[2].positions[2] = outData[4];	outTriangles[2].sharedEdge[2] = false;
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering triangle fan, " << outData.size() << " vertices." << tcu::TestLog::EndMessage;
+	for (int vtxNdx = 0; vtxNdx < (int)outData.size(); ++vtxNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "\t" << outData[vtxNdx]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class LinesCase : public BaseLineCase
+{
+public:
+			LinesCase		(Context& context, const char* name, const char* desc, PrimitiveWideness wideness, BaseRenderingCase::RenderTarget renderTarget = RENDERTARGET_DEFAULT, int numSamples = -1);
+
+	void	generateLines	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines);
+};
+
+LinesCase::LinesCase (Context& context, const char* name, const char* desc, PrimitiveWideness wideness, BaseRenderingCase::RenderTarget renderTarget, int numSamples)
+	: BaseLineCase(context, name, desc, GL_LINES, wideness, renderTarget, numSamples)
+{
+}
+
+void LinesCase::generateLines (int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines)
+{
+	outData.resize(6);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.01f,  0.0f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,   0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.46f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.3f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(-1.5f,  -0.4f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4( 0.1f,   0.5f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(  0.88f,   0.9f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(  0.18f,  -0.2f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.7f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4( 0.11f,  0.2f, 0.0f, 1.0f);
+			outData[4] = tcu::Vec4( 0.88f,  0.7f, 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(  0.8f, -0.7f, 0.0f, 1.0f);
+			break;
+	}
+
+	outLines.resize(3);
+	outLines[0].positions[0] = outData[0];
+	outLines[0].positions[1] = outData[1];
+	outLines[1].positions[0] = outData[2];
+	outLines[1].positions[1] = outData[3];
+	outLines[2].positions[0] = outData[4];
+	outLines[2].positions[1] = outData[5];
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << outLines.size() << " lines(s): (width = " << getLineWidth() << ")" << tcu::TestLog::EndMessage;
+	for (int lineNdx = 0; lineNdx < (int)outLines.size(); ++lineNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Line " << (lineNdx+1) << ":"
+			<< "\n\t" << outLines[lineNdx].positions[0]
+			<< "\n\t" << outLines[lineNdx].positions[1]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class LineStripCase : public BaseLineCase
+{
+public:
+			LineStripCase	(Context& context, const char* name, const char* desc, PrimitiveWideness wideness, BaseRenderingCase::RenderTarget renderTarget = RENDERTARGET_DEFAULT, int numSamples = -1);
+
+	void	generateLines	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines);
+};
+
+LineStripCase::LineStripCase (Context& context, const char* name, const char* desc, PrimitiveWideness wideness, BaseRenderingCase::RenderTarget renderTarget, int numSamples)
+	: BaseLineCase(context, name, desc, GL_LINE_STRIP, wideness, renderTarget, numSamples)
+{
+}
+
+void LineStripCase::generateLines (int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines)
+{
+	outData.resize(4);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.01f,  0.0f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,   0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.46f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.5f,   0.2f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.7f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4( 0.11f,  0.2f, 0.0f, 1.0f);
+			break;
+	}
+
+	outLines.resize(3);
+	outLines[0].positions[0] = outData[0];
+	outLines[0].positions[1] = outData[1];
+	outLines[1].positions[0] = outData[1];
+	outLines[1].positions[1] = outData[2];
+	outLines[2].positions[0] = outData[2];
+	outLines[2].positions[1] = outData[3];
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering line strip, width = " << getLineWidth() << ", " << outData.size() << " vertices." << tcu::TestLog::EndMessage;
+	for (int vtxNdx = 0; vtxNdx < (int)outData.size(); ++vtxNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "\t" << outData[vtxNdx]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class LineLoopCase : public BaseLineCase
+{
+public:
+			LineLoopCase	(Context& context, const char* name, const char* desc, PrimitiveWideness wideness, BaseRenderingCase::RenderTarget renderTarget = RENDERTARGET_DEFAULT, int numSamples = -1);
+
+	void	generateLines	(int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines);
+};
+
+LineLoopCase::LineLoopCase (Context& context, const char* name, const char* desc, PrimitiveWideness wideness, BaseRenderingCase::RenderTarget renderTarget, int numSamples)
+	: BaseLineCase(context, name, desc, GL_LINE_LOOP, wideness, renderTarget, numSamples)
+{
+}
+
+void LineLoopCase::generateLines (int iteration, std::vector<tcu::Vec4>& outData, std::vector<LineSceneSpec::SceneLine>& outLines)
+{
+	outData.resize(4);
+
+	switch (iteration)
+	{
+		case 0:
+			// \note: these values are chosen arbitrarily
+			outData[0] = tcu::Vec4( 0.01f,  0.0f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4( 0.5f,   0.2f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4( 0.46f,  0.3f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(-0.5f,   0.2f, 0.0f, 1.0f);
+			break;
+
+		case 1:
+			outData[0] = tcu::Vec4(-0.499f, 0.128f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(-0.501f,  -0.3f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.11f,  -0.2f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(  0.11f,   0.2f, 0.0f, 1.0f);
+			break;
+
+		case 2:
+			outData[0] = tcu::Vec4( -0.9f, -0.3f, 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(  1.1f, -0.9f, 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(  0.7f, -0.1f, 0.0f, 1.0f);
+			outData[3] = tcu::Vec4( 0.11f,  0.2f, 0.0f, 1.0f);
+			break;
+	}
+
+	outLines.resize(4);
+	outLines[0].positions[0] = outData[0];
+	outLines[0].positions[1] = outData[1];
+	outLines[1].positions[0] = outData[1];
+	outLines[1].positions[1] = outData[2];
+	outLines[2].positions[0] = outData[2];
+	outLines[2].positions[1] = outData[3];
+	outLines[3].positions[0] = outData[3];
+	outLines[3].positions[1] = outData[0];
+
+	// log
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering line loop, width = " << getLineWidth() << ", " << outData.size() << " vertices." << tcu::TestLog::EndMessage;
+	for (int vtxNdx = 0; vtxNdx < (int)outData.size(); ++vtxNdx)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "\t" << outData[vtxNdx]
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+class FillRuleCase : public BaseRenderingCase
+{
+public:
+	enum FillRuleCaseType
+	{
+		FILLRULECASE_BASIC = 0,
+		FILLRULECASE_REVERSED,
+		FILLRULECASE_CLIPPED_FULL,
+		FILLRULECASE_CLIPPED_PARTIAL,
+		FILLRULECASE_PROJECTED,
+
+		FILLRULECASE_LAST
+	};
+
+							FillRuleCase		(Context& ctx, const char* name, const char* desc, FillRuleCaseType type, RenderTarget renderTarget = RENDERTARGET_DEFAULT, int numSamples = -1);
+							~FillRuleCase		(void);
+	IterateResult			iterate				(void);
+
+private:
+	int						getRenderSize		(FillRuleCase::FillRuleCaseType type) const;
+	int						getNumIterations	(FillRuleCase::FillRuleCaseType type) const;
+	void					generateTriangles	(int iteration, std::vector<tcu::Vec4>& outData) const;
+
+	const FillRuleCaseType	m_caseType;
+	int						m_iteration;
+	const int				m_iterationCount;
+	bool					m_allIterationsPassed;
+
+};
+
+FillRuleCase::FillRuleCase (Context& ctx, const char* name, const char* desc, FillRuleCaseType type, RenderTarget renderTarget, int numSamples)
+	: BaseRenderingCase		(ctx, name, desc, renderTarget, numSamples, getRenderSize(type))
+	, m_caseType			(type)
+	, m_iteration			(0)
+	, m_iterationCount		(getNumIterations(type))
+	, m_allIterationsPassed	(true)
+{
+	DE_ASSERT(type < FILLRULECASE_LAST);
+}
+
+FillRuleCase::~FillRuleCase (void)
+{
+	deinit();
+}
+
+FillRuleCase::IterateResult FillRuleCase::iterate (void)
+{
+	const std::string						iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection				section					(m_testCtx.getLog(), iterationDescription, iterationDescription);
+	const int								thresholdRed			= 1 << (8 - getPixelFormat().redBits);
+	const int								thresholdGreen			= 1 << (8 - getPixelFormat().greenBits);
+	const int								thresholdBlue			= 1 << (8 - getPixelFormat().blueBits);
+	tcu::Surface							resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>					drawBuffer;
+	bool									imageShown				= false;
+
+	generateTriangles(m_iteration, drawBuffer);
+
+	// draw image
+	{
+		const glw::Functions&			gl				= m_context.getRenderContext().getFunctions();
+		const std::vector<tcu::Vec4>	colorBuffer		(drawBuffer.size(), tcu::Vec4(0.5f, 0.5f, 0.5f, 1.0f));
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing gray triangles with shared edges.\nEnabling additive blending to detect overlapping fragments." << tcu::TestLog::EndMessage;
+
+		gl.enable(GL_BLEND);
+		gl.blendEquation(GL_FUNC_ADD);
+		gl.blendFunc(GL_ONE, GL_ONE);
+		drawPrimitives(resultImage, drawBuffer, colorBuffer, GL_TRIANGLES);
+	}
+
+	// verify no overdraw
+	{
+		const tcu::RGBA	triangleColor	= tcu::RGBA(127, 127, 127, 255);
+		bool			overdraw		= false;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying result." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < resultImage.getHeight(); ++y)
+		for (int x = 0; x < resultImage.getWidth();  ++x)
+		{
+			const tcu::RGBA color = resultImage.getPixel(x, y);
+
+			// color values are greater than triangle color? Allow lower values for multisampled edges and background.
+			if ((color.getRed()   - triangleColor.getRed())   > thresholdRed   ||
+				(color.getGreen() - triangleColor.getGreen()) > thresholdGreen ||
+				(color.getBlue()  - triangleColor.getBlue())  > thresholdBlue)
+				overdraw = true;
+		}
+
+		// results
+		if (!overdraw)
+			m_testCtx.getLog() << tcu::TestLog::Message << "No overlapping fragments detected." << tcu::TestLog::EndMessage;
+		else
+		{
+			m_testCtx.getLog()	<< tcu::TestLog::Message << "Overlapping fragments detected, image is not valid." << tcu::TestLog::EndMessage;
+			m_testCtx.getLog()	<< tcu::TestLog::ImageSet("Result of rendering", "Result of rendering")
+								<< tcu::TestLog::Image("Result", "Result", resultImage)
+								<< tcu::TestLog::EndImageSet;
+
+			imageShown = true;
+			m_allIterationsPassed = false;
+		}
+	}
+
+	// verify no missing fragments in the full viewport case
+	if (m_caseType == FILLRULECASE_CLIPPED_FULL)
+	{
+		bool missingFragments = false;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Searching missing fragments." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < resultImage.getHeight(); ++y)
+		for (int x = 0; x < resultImage.getWidth();  ++x)
+		{
+			const tcu::RGBA color = resultImage.getPixel(x, y);
+
+			// black? (background)
+			if (color.getRed()   <= thresholdRed   ||
+				color.getGreen() <= thresholdGreen ||
+				color.getBlue()  <= thresholdBlue)
+				missingFragments = true;
+		}
+
+		// results
+		if (!missingFragments)
+			m_testCtx.getLog() << tcu::TestLog::Message << "No missing fragments detected." << tcu::TestLog::EndMessage;
+		else
+		{
+			m_testCtx.getLog()	<< tcu::TestLog::Message << "Missing fragments detected, image is not valid." << tcu::TestLog::EndMessage;
+
+			if (!imageShown)
+			{
+				m_testCtx.getLog()	<< tcu::TestLog::ImageSet("Result of rendering", "Result of rendering")
+									<< tcu::TestLog::Image("Result", "Result", resultImage)
+									<< tcu::TestLog::EndImageSet;
+			}
+
+			m_allIterationsPassed = false;
+		}
+	}
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Found invalid pixels");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+int FillRuleCase::getRenderSize (FillRuleCase::FillRuleCaseType type) const
+{
+	if (type == FILLRULECASE_CLIPPED_FULL || type == FILLRULECASE_CLIPPED_PARTIAL)
+		return DEFAULT_RENDER_SIZE / 4;
+	else
+		return DEFAULT_RENDER_SIZE;
+}
+
+int FillRuleCase::getNumIterations (FillRuleCase::FillRuleCaseType type) const
+{
+	if (type == FILLRULECASE_CLIPPED_FULL || type == FILLRULECASE_CLIPPED_PARTIAL)
+		return 15;
+	else
+		return 2;
+}
+
+void FillRuleCase::generateTriangles (int iteration, std::vector<tcu::Vec4>& outData) const
+{
+	switch (m_caseType)
+	{
+		case FILLRULECASE_BASIC:
+		case FILLRULECASE_REVERSED:
+		case FILLRULECASE_PROJECTED:
+		{
+			const int	numRows		= 4;
+			const int	numColumns	= 4;
+			const float	quadSide	= 0.15f;
+			de::Random	rnd			(0xabcd);
+
+			outData.resize(6 * numRows * numColumns);
+
+			for (int col = 0; col < numColumns; ++col)
+			for (int row = 0; row < numRows;    ++row)
+			{
+				const tcu::Vec2 center		= tcu::Vec2((row + 0.5f) / numRows * 2.0f - 1.0f, (col + 0.5f) / numColumns * 2.0f - 1.0f);
+				const float		rotation	= (iteration * numColumns * numRows + col * numRows + row) / (float)(m_iterationCount * numColumns * numRows) * DE_PI / 2.0f;
+				const tcu::Vec2 sideH		= quadSide * tcu::Vec2(deFloatCos(rotation), deFloatSin(rotation));
+				const tcu::Vec2 sideV		= tcu::Vec2(sideH.y(), -sideH.x());
+				const tcu::Vec2 quad[4]		=
+				{
+					center + sideH + sideV,
+					center + sideH - sideV,
+					center - sideH - sideV,
+					center - sideH + sideV,
+				};
+
+				if (m_caseType == FILLRULECASE_BASIC)
+				{
+					outData[6 * (col * numRows + row) + 0] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 1] = tcu::Vec4(quad[1].x(), quad[1].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 2] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 3] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 4] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 5] = tcu::Vec4(quad[3].x(), quad[3].y(), 0.0f, 1.0f);
+				}
+				else if (m_caseType == FILLRULECASE_REVERSED)
+				{
+					outData[6 * (col * numRows + row) + 0] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 1] = tcu::Vec4(quad[1].x(), quad[1].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 2] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 3] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 4] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+					outData[6 * (col * numRows + row) + 5] = tcu::Vec4(quad[3].x(), quad[3].y(), 0.0f, 1.0f);
+				}
+				else if (m_caseType == FILLRULECASE_PROJECTED)
+				{
+					const float w0 = rnd.getFloat(0.1f, 4.0f);
+					const float w1 = rnd.getFloat(0.1f, 4.0f);
+					const float w2 = rnd.getFloat(0.1f, 4.0f);
+					const float w3 = rnd.getFloat(0.1f, 4.0f);
+
+					outData[6 * (col * numRows + row) + 0] = tcu::Vec4(quad[0].x() * w0, quad[0].y() * w0, 0.0f, w0);
+					outData[6 * (col * numRows + row) + 1] = tcu::Vec4(quad[1].x() * w1, quad[1].y() * w1, 0.0f, w1);
+					outData[6 * (col * numRows + row) + 2] = tcu::Vec4(quad[2].x() * w2, quad[2].y() * w2, 0.0f, w2);
+					outData[6 * (col * numRows + row) + 3] = tcu::Vec4(quad[2].x() * w2, quad[2].y() * w2, 0.0f, w2);
+					outData[6 * (col * numRows + row) + 4] = tcu::Vec4(quad[0].x() * w0, quad[0].y() * w0, 0.0f, w0);
+					outData[6 * (col * numRows + row) + 5] = tcu::Vec4(quad[3].x() * w3, quad[3].y() * w3, 0.0f, w3);
+				}
+				else
+					DE_ASSERT(DE_FALSE);
+			}
+
+			break;
+		}
+
+		case FILLRULECASE_CLIPPED_PARTIAL:
+		case FILLRULECASE_CLIPPED_FULL:
+		{
+			const float		quadSide	= (m_caseType == FILLRULECASE_CLIPPED_PARTIAL) ? (1.0f) : (2.0f);
+			const tcu::Vec2 center		= (m_caseType == FILLRULECASE_CLIPPED_PARTIAL) ? (tcu::Vec2(0.5f, 0.5f)) : (tcu::Vec2(0.0f, 0.0f));
+			const float		rotation	= (iteration) / (float)(m_iterationCount - 1) * DE_PI / 2.0f;
+			const tcu::Vec2 sideH		= quadSide * tcu::Vec2(deFloatCos(rotation), deFloatSin(rotation));
+			const tcu::Vec2 sideV		= tcu::Vec2(sideH.y(), -sideH.x());
+			const tcu::Vec2 quad[4]		=
+			{
+				center + sideH + sideV,
+				center + sideH - sideV,
+				center - sideH - sideV,
+				center - sideH + sideV,
+			};
+
+			outData.resize(6);
+			outData[0] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+			outData[1] = tcu::Vec4(quad[1].x(), quad[1].y(), 0.0f, 1.0f);
+			outData[2] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+			outData[3] = tcu::Vec4(quad[2].x(), quad[2].y(), 0.0f, 1.0f);
+			outData[4] = tcu::Vec4(quad[0].x(), quad[0].y(), 0.0f, 1.0f);
+			outData[5] = tcu::Vec4(quad[3].x(), quad[3].y(), 0.0f, 1.0f);
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+class CullingTest : public BaseRenderingCase
+{
+public:
+						CullingTest			(Context& ctx, const char* name, const char* desc, glw::GLenum cullMode, glw::GLenum primitive, glw::GLenum faceOrder);
+						~CullingTest		(void);
+	IterateResult		iterate				(void);
+
+private:
+	void				generateVertices	(std::vector<tcu::Vec4>& outData) const;
+	void				extractTriangles	(std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles, const std::vector<tcu::Vec4>& vertices) const;
+	bool				triangleOrder		(const tcu::Vec4& v0, const tcu::Vec4& v1, const tcu::Vec4& v2) const;
+
+	const glw::GLenum	m_cullMode;
+	const glw::GLenum	m_primitive;
+	const glw::GLenum	m_faceOrder;
+};
+
+CullingTest::CullingTest (Context& ctx, const char* name, const char* desc, glw::GLenum cullMode, glw::GLenum primitive, glw::GLenum faceOrder)
+	: BaseRenderingCase	(ctx, name, desc, RENDERTARGET_DEFAULT, -1, DEFAULT_RENDER_SIZE)
+	, m_cullMode		(cullMode)
+	, m_primitive		(primitive)
+	, m_faceOrder		(faceOrder)
+{
+}
+
+CullingTest::~CullingTest (void)
+{
+}
+
+CullingTest::IterateResult CullingTest::iterate (void)
+{
+	tcu::Surface									resultImage(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>							drawBuffer;
+	std::vector<TriangleSceneSpec::SceneTriangle>	triangles;
+
+	// generate scene
+	generateVertices(drawBuffer);
+	extractTriangles(triangles, drawBuffer);
+
+	// draw image
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.enable(GL_CULL_FACE);
+		gl.cullFace(m_cullMode);
+		gl.frontFace(m_faceOrder);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting front face to " << glu::getWindingName(m_faceOrder) << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting cull face to " << glu::getFaceName(m_cullMode) << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing test pattern (" << glu::getPrimitiveTypeName(m_primitive) << ")" << tcu::TestLog::EndMessage;
+
+		drawPrimitives(resultImage, drawBuffer, m_primitive);
+	}
+
+	// compare
+	{
+		RasterizationArguments	args;
+		TriangleSceneSpec		scene;
+
+		args.numSamples		= m_numSamples;
+		args.subpixelBits	= m_subpixelBits;
+		args.redBits		= getPixelFormat().redBits;
+		args.greenBits		= getPixelFormat().greenBits;
+		args.blueBits		= getPixelFormat().blueBits;
+
+		scene.triangles.swap(triangles);
+
+		if (verifyTriangleGroupRasterization(resultImage, scene, args, m_testCtx.getLog(), VERIFICATIONMODE_WEAK))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect rendering");
+	}
+
+	return STOP;
+}
+
+void CullingTest::generateVertices (std::vector<tcu::Vec4>& outData) const
+{
+	de::Random rnd(543210);
+
+	outData.resize(6);
+	for (int vtxNdx = 0; vtxNdx < (int)outData.size(); ++vtxNdx)
+	{
+		outData[vtxNdx].x() = rnd.getFloat(-0.9f, 0.9f);
+		outData[vtxNdx].y() = rnd.getFloat(-0.9f, 0.9f);
+		outData[vtxNdx].z() = 0.0f;
+		outData[vtxNdx].w() = 1.0f;
+	}
+}
+
+void CullingTest::extractTriangles (std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles, const std::vector<tcu::Vec4>& vertices) const
+{
+	const bool cullDirection = (m_cullMode == GL_FRONT) ^ (m_faceOrder == GL_CCW);
+
+	// No triangles
+	if (m_cullMode == GL_FRONT_AND_BACK)
+		return;
+
+	switch (m_primitive)
+	{
+		case GL_TRIANGLES:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 2; vtxNdx += 3)
+			{
+				const tcu::Vec4& v0 = vertices[vtxNdx + 0];
+				const tcu::Vec4& v1 = vertices[vtxNdx + 1];
+				const tcu::Vec4& v2 = vertices[vtxNdx + 2];
+
+				if (triangleOrder(v0, v1, v2) != cullDirection)
+				{
+					TriangleSceneSpec::SceneTriangle tri;
+					tri.positions[0] = v0;	tri.sharedEdge[0] = false;
+					tri.positions[1] = v1;	tri.sharedEdge[1] = false;
+					tri.positions[2] = v2;	tri.sharedEdge[2] = false;
+
+					outTriangles.push_back(tri);
+				}
+			}
+			break;
+		}
+
+		case GL_TRIANGLE_STRIP:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 2; ++vtxNdx)
+			{
+				const tcu::Vec4& v0 = vertices[vtxNdx + 0];
+				const tcu::Vec4& v1 = vertices[vtxNdx + 1];
+				const tcu::Vec4& v2 = vertices[vtxNdx + 2];
+
+				if (triangleOrder(v0, v1, v2) != (cullDirection ^ (vtxNdx % 2 != 0)))
+				{
+					TriangleSceneSpec::SceneTriangle tri;
+					tri.positions[0] = v0;	tri.sharedEdge[0] = false;
+					tri.positions[1] = v1;	tri.sharedEdge[1] = false;
+					tri.positions[2] = v2;	tri.sharedEdge[2] = false;
+
+					outTriangles.push_back(tri);
+				}
+			}
+			break;
+		}
+
+		case GL_TRIANGLE_FAN:
+		{
+			for (int vtxNdx = 1; vtxNdx < (int)vertices.size() - 1; ++vtxNdx)
+			{
+				const tcu::Vec4& v0 = vertices[0];
+				const tcu::Vec4& v1 = vertices[vtxNdx + 0];
+				const tcu::Vec4& v2 = vertices[vtxNdx + 1];
+
+				if (triangleOrder(v0, v1, v2) != cullDirection)
+				{
+					TriangleSceneSpec::SceneTriangle tri;
+					tri.positions[0] = v0;	tri.sharedEdge[0] = false;
+					tri.positions[1] = v1;	tri.sharedEdge[1] = false;
+					tri.positions[2] = v2;	tri.sharedEdge[2] = false;
+
+					outTriangles.push_back(tri);
+				}
+			}
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+bool CullingTest::triangleOrder (const tcu::Vec4& v0, const tcu::Vec4& v1, const tcu::Vec4& v2) const
+{
+	const tcu::Vec2 s0 = v0.swizzle(0, 1) / v0.w();
+	const tcu::Vec2 s1 = v1.swizzle(0, 1) / v1.w();
+	const tcu::Vec2 s2 = v2.swizzle(0, 1) / v2.w();
+
+	// cross
+	return ((s1.x() - s0.x()) * (s2.y() - s0.y()) - (s2.x() - s0.x()) * (s1.y() - s0.y())) < 0;
+}
+
+class TriangleInterpolationTest : public BaseRenderingCase
+{
+public:
+						TriangleInterpolationTest	(Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags, RenderTarget renderTarget = RENDERTARGET_DEFAULT, int numSamples = -1);
+						~TriangleInterpolationTest	(void);
+	IterateResult		iterate						(void);
+
+private:
+	void				generateVertices			(int iteration, std::vector<tcu::Vec4>& outVertices, std::vector<tcu::Vec4>& outColors) const;
+	void				extractTriangles			(std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles, const std::vector<tcu::Vec4>& vertices, const std::vector<tcu::Vec4>& colors) const;
+
+	const glw::GLenum	m_primitive;
+	const bool			m_projective;
+	const int			m_iterationCount;
+
+	int					m_iteration;
+	bool				m_allIterationsPassed;
+};
+
+TriangleInterpolationTest::TriangleInterpolationTest (Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags, RenderTarget renderTarget, int numSamples)
+	: BaseRenderingCase		(ctx, name, desc, renderTarget, numSamples, DEFAULT_RENDER_SIZE)
+	, m_primitive			(primitive)
+	, m_projective			((flags & INTERPOLATIONFLAGS_PROJECTED) != 0)
+	, m_iterationCount		(3)
+	, m_iteration			(0)
+	, m_allIterationsPassed	(true)
+{
+	m_flatshade = ((flags & INTERPOLATIONFLAGS_FLATSHADE) != 0);
+}
+
+TriangleInterpolationTest::~TriangleInterpolationTest (void)
+{
+	deinit();
+}
+
+TriangleInterpolationTest::IterateResult TriangleInterpolationTest::iterate (void)
+{
+	const std::string								iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection						section					(m_testCtx.getLog(), "Iteration" + de::toString(m_iteration+1), iterationDescription);
+	tcu::Surface									resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>							drawBuffer;
+	std::vector<tcu::Vec4>							colorBuffer;
+	std::vector<TriangleSceneSpec::SceneTriangle>	triangles;
+
+	// generate scene
+	generateVertices(m_iteration, drawBuffer, colorBuffer);
+	extractTriangles(triangles, drawBuffer, colorBuffer);
+
+	// log
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Generated vertices:" << tcu::TestLog::EndMessage;
+		for (int vtxNdx = 0; vtxNdx < (int)drawBuffer.size(); ++vtxNdx)
+			m_testCtx.getLog() << tcu::TestLog::Message << "\t" << drawBuffer[vtxNdx] << ",\tcolor= " << colorBuffer[vtxNdx] << tcu::TestLog::EndMessage;
+	}
+
+	// draw image
+	drawPrimitives(resultImage, drawBuffer, colorBuffer, m_primitive);
+
+	// compare
+	{
+		RasterizationArguments	args;
+		TriangleSceneSpec		scene;
+
+		args.numSamples		= m_numSamples;
+		args.subpixelBits	= m_subpixelBits;
+		args.redBits		= getPixelFormat().redBits;
+		args.greenBits		= getPixelFormat().greenBits;
+		args.blueBits		= getPixelFormat().blueBits;
+
+		scene.triangles.swap(triangles);
+
+		if (!verifyTriangleGroupInterpolation(resultImage, scene, args, m_testCtx.getLog()))
+			m_allIterationsPassed = false;
+	}
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Found invalid pixel values");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+void TriangleInterpolationTest::generateVertices (int iteration, std::vector<tcu::Vec4>& outVertices, std::vector<tcu::Vec4>& outColors) const
+{
+	// use only red, green and blue
+	const tcu::Vec4 colors[] =
+	{
+		tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f),
+	};
+
+	de::Random rnd(123 + iteration * 1000 + (int)m_primitive);
+
+	outVertices.resize(6);
+	outColors.resize(6);
+
+	for (int vtxNdx = 0; vtxNdx < (int)outVertices.size(); ++vtxNdx)
+	{
+		outVertices[vtxNdx].x() = rnd.getFloat(-0.9f, 0.9f);
+		outVertices[vtxNdx].y() = rnd.getFloat(-0.9f, 0.9f);
+		outVertices[vtxNdx].z() = 0.0f;
+
+		if (!m_projective)
+			outVertices[vtxNdx].w() = 1.0f;
+		else
+		{
+			const float w = rnd.getFloat(0.2f, 4.0f);
+
+			outVertices[vtxNdx].x() *= w;
+			outVertices[vtxNdx].y() *= w;
+			outVertices[vtxNdx].z() *= w;
+			outVertices[vtxNdx].w() = w;
+		}
+
+		outColors[vtxNdx] = colors[vtxNdx % DE_LENGTH_OF_ARRAY(colors)];
+	}
+}
+
+void TriangleInterpolationTest::extractTriangles (std::vector<TriangleSceneSpec::SceneTriangle>& outTriangles, const std::vector<tcu::Vec4>& vertices, const std::vector<tcu::Vec4>& colors) const
+{
+	switch (m_primitive)
+	{
+		case GL_TRIANGLES:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 2; vtxNdx += 3)
+			{
+				TriangleSceneSpec::SceneTriangle tri;
+				tri.positions[0]	= vertices[vtxNdx + 0];
+				tri.positions[1]	= vertices[vtxNdx + 1];
+				tri.positions[2]	= vertices[vtxNdx + 2];
+				tri.sharedEdge[0]	= false;
+				tri.sharedEdge[1]	= false;
+				tri.sharedEdge[2]	= false;
+
+				if (m_flatshade)
+				{
+					tri.colors[0] = colors[vtxNdx + 2];
+					tri.colors[1] = colors[vtxNdx + 2];
+					tri.colors[2] = colors[vtxNdx + 2];
+				}
+				else
+				{
+					tri.colors[0] = colors[vtxNdx + 0];
+					tri.colors[1] = colors[vtxNdx + 1];
+					tri.colors[2] = colors[vtxNdx + 2];
+				}
+
+				outTriangles.push_back(tri);
+			}
+			break;
+		}
+
+		case GL_TRIANGLE_STRIP:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 2; ++vtxNdx)
+			{
+				TriangleSceneSpec::SceneTriangle tri;
+				tri.positions[0]	= vertices[vtxNdx + 0];
+				tri.positions[1]	= vertices[vtxNdx + 1];
+				tri.positions[2]	= vertices[vtxNdx + 2];
+				tri.sharedEdge[0]	= false;
+				tri.sharedEdge[1]	= false;
+				tri.sharedEdge[2]	= false;
+
+				if (m_flatshade)
+				{
+					tri.colors[0] = colors[vtxNdx + 2];
+					tri.colors[1] = colors[vtxNdx + 2];
+					tri.colors[2] = colors[vtxNdx + 2];
+				}
+				else
+				{
+					tri.colors[0] = colors[vtxNdx + 0];
+					tri.colors[1] = colors[vtxNdx + 1];
+					tri.colors[2] = colors[vtxNdx + 2];
+				}
+
+				outTriangles.push_back(tri);
+			}
+			break;
+		}
+
+		case GL_TRIANGLE_FAN:
+		{
+			for (int vtxNdx = 1; vtxNdx < (int)vertices.size() - 1; ++vtxNdx)
+			{
+				TriangleSceneSpec::SceneTriangle tri;
+				tri.positions[0]	= vertices[0];
+				tri.positions[1]	= vertices[vtxNdx + 0];
+				tri.positions[2]	= vertices[vtxNdx + 1];
+				tri.sharedEdge[0]	= false;
+				tri.sharedEdge[1]	= false;
+				tri.sharedEdge[2]	= false;
+
+				if (m_flatshade)
+				{
+					tri.colors[0] = colors[vtxNdx + 1];
+					tri.colors[1] = colors[vtxNdx + 1];
+					tri.colors[2] = colors[vtxNdx + 1];
+				}
+				else
+				{
+					tri.colors[0] = colors[0];
+					tri.colors[1] = colors[vtxNdx + 0];
+					tri.colors[2] = colors[vtxNdx + 1];
+				}
+
+				outTriangles.push_back(tri);
+			}
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+class LineInterpolationTest : public BaseRenderingCase
+{
+public:
+							LineInterpolationTest	(Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags, PrimitiveWideness wideness, RenderTarget renderTarget = RENDERTARGET_DEFAULT, int numSamples = -1);
+							~LineInterpolationTest	(void);
+
+	void					init					(void);
+	IterateResult			iterate					(void);
+
+private:
+	void					generateVertices		(int iteration, std::vector<tcu::Vec4>& outVertices, std::vector<tcu::Vec4>& outColors) const;
+	void					extractLines			(std::vector<LineSceneSpec::SceneLine>& outLines, const std::vector<tcu::Vec4>& vertices, const std::vector<tcu::Vec4>& colors) const;
+	float					getLineWidth			(void) const;
+
+	const glw::GLenum		m_primitive;
+	const bool				m_projective;
+	const int				m_iterationCount;
+	const PrimitiveWideness	m_primitiveWideness;
+
+	int						m_iteration;
+	bool					m_allIterationsPassed;
+	float					m_maxLineWidth;
+	std::vector<float>		m_lineWidths;
+};
+
+LineInterpolationTest::LineInterpolationTest (Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags, PrimitiveWideness wideness, RenderTarget renderTarget, int numSamples)
+	: BaseRenderingCase		(ctx, name, desc, renderTarget, numSamples, DEFAULT_RENDER_SIZE)
+	, m_primitive			(primitive)
+	, m_projective			((flags & INTERPOLATIONFLAGS_PROJECTED) != 0)
+	, m_iterationCount		(3)
+	, m_primitiveWideness	(wideness)
+	, m_iteration			(0)
+	, m_allIterationsPassed	(true)
+	, m_maxLineWidth		(1.0f)
+{
+	m_flatshade = ((flags & INTERPOLATIONFLAGS_FLATSHADE) != 0);
+}
+
+LineInterpolationTest::~LineInterpolationTest (void)
+{
+	deinit();
+}
+
+void LineInterpolationTest::init (void)
+{
+	// create line widths
+	if (m_primitiveWideness == PRIMITIVEWIDENESS_NARROW)
+	{
+		m_lineWidths.resize(m_iterationCount, 1.0f);
+	}
+	else if (m_primitiveWideness == PRIMITIVEWIDENESS_WIDE)
+	{
+		float range[2] = { 0.0f, 0.0f };
+		m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_LINE_WIDTH_RANGE, range);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "ALIASED_LINE_WIDTH_RANGE = [" << range[0] << ", " << range[1] << "]" << tcu::TestLog::EndMessage;
+
+		// no wide line support
+		if (range[1] <= 1.0f)
+			throw tcu::NotSupportedError("wide line support required");
+
+		// set hand picked sizes
+		m_lineWidths.push_back(5.0f);
+		m_lineWidths.push_back(10.0f);
+		m_lineWidths.push_back(range[1]);
+		DE_ASSERT((int)m_lineWidths.size() == m_iterationCount);
+
+		m_maxLineWidth = range[1];
+	}
+	else
+		DE_ASSERT(false);
+
+	// init parent
+	BaseRenderingCase::init();
+}
+
+LineInterpolationTest::IterateResult LineInterpolationTest::iterate (void)
+{
+	const std::string						iterationDescription	= "Test iteration " + de::toString(m_iteration+1) + " / " + de::toString(m_iterationCount);
+	const tcu::ScopedLogSection				section					(m_testCtx.getLog(), "Iteration" + de::toString(m_iteration+1), iterationDescription);
+	const float								lineWidth				= getLineWidth();
+	tcu::Surface							resultImage				(m_renderSize, m_renderSize);
+	std::vector<tcu::Vec4>					drawBuffer;
+	std::vector<tcu::Vec4>					colorBuffer;
+	std::vector<LineSceneSpec::SceneLine>	lines;
+
+	// supported?
+	if (lineWidth <= m_maxLineWidth)
+	{
+		// generate scene
+		generateVertices(m_iteration, drawBuffer, colorBuffer);
+		extractLines(lines, drawBuffer, colorBuffer);
+
+		// log
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Generated vertices:" << tcu::TestLog::EndMessage;
+			for (int vtxNdx = 0; vtxNdx < (int)drawBuffer.size(); ++vtxNdx)
+				m_testCtx.getLog() << tcu::TestLog::Message << "\t" << drawBuffer[vtxNdx] << ",\tcolor= " << colorBuffer[vtxNdx] << tcu::TestLog::EndMessage;
+		}
+
+		// draw image
+		drawPrimitives(resultImage, drawBuffer, colorBuffer, m_primitive);
+
+		// compare
+		{
+			RasterizationArguments	args;
+			LineSceneSpec			scene;
+
+			args.numSamples		= m_numSamples;
+			args.subpixelBits	= m_subpixelBits;
+			args.redBits		= getPixelFormat().redBits;
+			args.greenBits		= getPixelFormat().greenBits;
+			args.blueBits		= getPixelFormat().blueBits;
+
+			scene.lines.swap(lines);
+			scene.lineWidth = getLineWidth();
+
+			if (!verifyLineGroupInterpolation(resultImage, scene, args, m_testCtx.getLog()))
+				m_allIterationsPassed = false;
+		}
+	}
+	else
+		m_testCtx.getLog() << tcu::TestLog::Message << "Line width " << lineWidth << " not supported, skipping iteration." << tcu::TestLog::EndMessage;
+
+	// result
+	if (++m_iteration == m_iterationCount)
+	{
+		if (m_allIterationsPassed)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Found invalid pixel values");
+
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+void LineInterpolationTest::generateVertices (int iteration, std::vector<tcu::Vec4>& outVertices, std::vector<tcu::Vec4>& outColors) const
+{
+	// use only red, green and blue
+	const tcu::Vec4 colors[] =
+	{
+		tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f),
+	};
+
+	de::Random rnd(123 + iteration * 1000 + (int)m_primitive);
+
+	outVertices.resize(6);
+	outColors.resize(6);
+
+	for (int vtxNdx = 0; vtxNdx < (int)outVertices.size(); ++vtxNdx)
+	{
+		outVertices[vtxNdx].x() = rnd.getFloat(-0.9f, 0.9f);
+		outVertices[vtxNdx].y() = rnd.getFloat(-0.9f, 0.9f);
+		outVertices[vtxNdx].z() = 0.0f;
+
+		if (!m_projective)
+			outVertices[vtxNdx].w() = 1.0f;
+		else
+		{
+			const float w = rnd.getFloat(0.2f, 4.0f);
+
+			outVertices[vtxNdx].x() *= w;
+			outVertices[vtxNdx].y() *= w;
+			outVertices[vtxNdx].z() *= w;
+			outVertices[vtxNdx].w() = w;
+		}
+
+		outColors[vtxNdx] = colors[vtxNdx % DE_LENGTH_OF_ARRAY(colors)];
+	}
+}
+
+void LineInterpolationTest::extractLines (std::vector<LineSceneSpec::SceneLine>& outLines, const std::vector<tcu::Vec4>& vertices, const std::vector<tcu::Vec4>& colors) const
+{
+	switch (m_primitive)
+	{
+		case GL_LINES:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 1; vtxNdx += 2)
+			{
+				LineSceneSpec::SceneLine line;
+				line.positions[0] = vertices[vtxNdx + 0];
+				line.positions[1] = vertices[vtxNdx + 1];
+
+				if (m_flatshade)
+				{
+					line.colors[0] = colors[vtxNdx + 1];
+					line.colors[1] = colors[vtxNdx + 1];
+				}
+				else
+				{
+					line.colors[0] = colors[vtxNdx + 0];
+					line.colors[1] = colors[vtxNdx + 1];
+				}
+
+				outLines.push_back(line);
+			}
+			break;
+		}
+
+		case GL_LINE_STRIP:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size() - 1; ++vtxNdx)
+			{
+				LineSceneSpec::SceneLine line;
+				line.positions[0] = vertices[vtxNdx + 0];
+				line.positions[1] = vertices[vtxNdx + 1];
+
+				if (m_flatshade)
+				{
+					line.colors[0] = colors[vtxNdx + 1];
+					line.colors[1] = colors[vtxNdx + 1];
+				}
+				else
+				{
+					line.colors[0] = colors[vtxNdx + 0];
+					line.colors[1] = colors[vtxNdx + 1];
+				}
+
+				outLines.push_back(line);
+			}
+			break;
+		}
+
+		case GL_LINE_LOOP:
+		{
+			for (int vtxNdx = 0; vtxNdx < (int)vertices.size(); ++vtxNdx)
+			{
+				LineSceneSpec::SceneLine line;
+				line.positions[0] = vertices[(vtxNdx + 0) % (int)vertices.size()];
+				line.positions[1] = vertices[(vtxNdx + 1) % (int)vertices.size()];
+
+				if (m_flatshade)
+				{
+					line.colors[0] = colors[(vtxNdx + 1) % (int)vertices.size()];
+					line.colors[1] = colors[(vtxNdx + 1) % (int)vertices.size()];
+				}
+				else
+				{
+					line.colors[0] = colors[(vtxNdx + 0) % (int)vertices.size()];
+					line.colors[1] = colors[(vtxNdx + 1) % (int)vertices.size()];
+				}
+
+				outLines.push_back(line);
+			}
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+float LineInterpolationTest::getLineWidth (void) const
+{
+	return m_lineWidths[m_iteration];
+}
+
+} // anonymous
+
+RasterizationTests::RasterizationTests (Context& context)
+	: TestCaseGroup(context, "rasterization", "Rasterization Tests")
+{
+}
+
+RasterizationTests::~RasterizationTests (void)
+{
+}
+
+void RasterizationTests::init (void)
+{
+	// .primitives
+	{
+		tcu::TestCaseGroup* const primitives = new tcu::TestCaseGroup(m_testCtx, "primitives", "Primitive rasterization");
+
+		addChild(primitives);
+
+		primitives->addChild(new TrianglesCase		(m_context, "triangles",		"Render primitives as GL_TRIANGLES, verify rasterization result"));
+		primitives->addChild(new TriangleStripCase	(m_context, "triangle_strip",	"Render primitives as GL_TRIANGLE_STRIP, verify rasterization result"));
+		primitives->addChild(new TriangleFanCase	(m_context, "triangle_fan",		"Render primitives as GL_TRIANGLE_FAN, verify rasterization result"));
+		primitives->addChild(new LinesCase			(m_context, "lines",			"Render primitives as GL_LINES, verify rasterization result",							PRIMITIVEWIDENESS_NARROW));
+		primitives->addChild(new LineStripCase		(m_context, "line_strip",		"Render primitives as GL_LINE_STRIP, verify rasterization result",						PRIMITIVEWIDENESS_NARROW));
+		primitives->addChild(new LineLoopCase		(m_context, "line_loop",		"Render primitives as GL_LINE_LOOP, verify rasterization result",						PRIMITIVEWIDENESS_NARROW));
+		primitives->addChild(new LinesCase			(m_context, "lines_wide",		"Render primitives as GL_LINES with wide lines, verify rasterization result",			PRIMITIVEWIDENESS_WIDE));
+		primitives->addChild(new LineStripCase		(m_context, "line_strip_wide",	"Render primitives as GL_LINE_STRIP with wide lines, verify rasterization result",		PRIMITIVEWIDENESS_WIDE));
+		primitives->addChild(new LineLoopCase		(m_context, "line_loop_wide",	"Render primitives as GL_LINE_LOOP with wide lines, verify rasterization result",		PRIMITIVEWIDENESS_WIDE));
+		primitives->addChild(new PointCase			(m_context, "points",			"Render primitives as GL_POINTS, verify rasterization result",							PRIMITIVEWIDENESS_WIDE));
+	}
+
+	// .fill_rules
+	{
+		tcu::TestCaseGroup* const fillRules = new tcu::TestCaseGroup(m_testCtx, "fill_rules", "Primitive fill rules");
+
+		addChild(fillRules);
+
+		fillRules->addChild(new FillRuleCase(m_context,	"basic_quad",			"Verify fill rules",	FillRuleCase::FILLRULECASE_BASIC));
+		fillRules->addChild(new FillRuleCase(m_context,	"basic_quad_reverse",	"Verify fill rules",	FillRuleCase::FILLRULECASE_REVERSED));
+		fillRules->addChild(new FillRuleCase(m_context,	"clipped_full",			"Verify fill rules",	FillRuleCase::FILLRULECASE_CLIPPED_FULL));
+		fillRules->addChild(new FillRuleCase(m_context,	"clipped_partly",		"Verify fill rules",	FillRuleCase::FILLRULECASE_CLIPPED_PARTIAL));
+		fillRules->addChild(new FillRuleCase(m_context,	"projected",			"Verify fill rules",	FillRuleCase::FILLRULECASE_PROJECTED));
+	}
+
+	// .culling
+	{
+		static const struct CullMode
+		{
+			glw::GLenum	mode;
+			const char*	prefix;
+		} cullModes[] =
+		{
+			{ GL_FRONT,				"front_"	},
+			{ GL_BACK,				"back_"		},
+			{ GL_FRONT_AND_BACK,	"both_"		},
+		};
+		static const struct PrimitiveType
+		{
+			glw::GLenum	type;
+			const char*	name;
+		} primitiveTypes[] =
+		{
+			{ GL_TRIANGLES,			"triangles"			},
+			{ GL_TRIANGLE_STRIP,	"triangle_strip"	},
+			{ GL_TRIANGLE_FAN,		"triangle_fan"		},
+		};
+		static const struct FrontFaceOrder
+		{
+			glw::GLenum	mode;
+			const char*	postfix;
+		} frontOrders[] =
+		{
+			{ GL_CCW,	""			},
+			{ GL_CW,	"_reverse"	},
+		};
+
+		tcu::TestCaseGroup* const culling = new tcu::TestCaseGroup(m_testCtx, "culling", "Culling");
+
+		addChild(culling);
+
+		for (int cullModeNdx   = 0; cullModeNdx   < DE_LENGTH_OF_ARRAY(cullModes);      ++cullModeNdx)
+		for (int primitiveNdx  = 0; primitiveNdx  < DE_LENGTH_OF_ARRAY(primitiveTypes); ++primitiveNdx)
+		for (int frontOrderNdx = 0; frontOrderNdx < DE_LENGTH_OF_ARRAY(frontOrders);    ++frontOrderNdx)
+		{
+			const std::string name = std::string(cullModes[cullModeNdx].prefix) + primitiveTypes[primitiveNdx].name + frontOrders[frontOrderNdx].postfix;
+
+			culling->addChild(new CullingTest(m_context, name.c_str(), "Test primitive culling.", cullModes[cullModeNdx].mode, primitiveTypes[primitiveNdx].type, frontOrders[frontOrderNdx].mode));
+		}
+	}
+
+	// .interpolation
+	{
+		tcu::TestCaseGroup* const interpolation = new tcu::TestCaseGroup(m_testCtx, "interpolation", "Test interpolation");
+
+		addChild(interpolation);
+
+		// .basic
+		{
+			tcu::TestCaseGroup* const basic = new tcu::TestCaseGroup(m_testCtx, "basic", "Non-projective interpolation");
+
+			interpolation->addChild(basic);
+
+			basic->addChild(new TriangleInterpolationTest		(m_context, "triangles",		"Verify triangle interpolation",		GL_TRIANGLES,		INTERPOLATIONFLAGS_NONE));
+			basic->addChild(new TriangleInterpolationTest		(m_context, "triangle_strip",	"Verify triangle strip interpolation",	GL_TRIANGLE_STRIP,	INTERPOLATIONFLAGS_NONE));
+			basic->addChild(new TriangleInterpolationTest		(m_context, "triangle_fan",		"Verify triangle fan interpolation",	GL_TRIANGLE_FAN,	INTERPOLATIONFLAGS_NONE));
+			basic->addChild(new LineInterpolationTest			(m_context, "lines",			"Verify line interpolation",			GL_LINES,			INTERPOLATIONFLAGS_NONE,	PRIMITIVEWIDENESS_NARROW));
+			basic->addChild(new LineInterpolationTest			(m_context, "line_strip",		"Verify line strip interpolation",		GL_LINE_STRIP,		INTERPOLATIONFLAGS_NONE,	PRIMITIVEWIDENESS_NARROW));
+			basic->addChild(new LineInterpolationTest			(m_context, "line_loop",		"Verify line loop interpolation",		GL_LINE_LOOP,		INTERPOLATIONFLAGS_NONE,	PRIMITIVEWIDENESS_NARROW));
+			basic->addChild(new LineInterpolationTest			(m_context, "lines_wide",		"Verify wide line interpolation",		GL_LINES,			INTERPOLATIONFLAGS_NONE,	PRIMITIVEWIDENESS_WIDE));
+			basic->addChild(new LineInterpolationTest			(m_context, "line_strip_wide",	"Verify wide line strip interpolation",	GL_LINE_STRIP,		INTERPOLATIONFLAGS_NONE,	PRIMITIVEWIDENESS_WIDE));
+			basic->addChild(new LineInterpolationTest			(m_context, "line_loop_wide",	"Verify wide line loop interpolation",	GL_LINE_LOOP,		INTERPOLATIONFLAGS_NONE,	PRIMITIVEWIDENESS_WIDE));
+		}
+
+		// .projected
+		{
+			tcu::TestCaseGroup* const projected = new tcu::TestCaseGroup(m_testCtx, "projected", "Projective interpolation");
+
+			interpolation->addChild(projected);
+
+			projected->addChild(new TriangleInterpolationTest	(m_context, "triangles",		"Verify triangle interpolation",		GL_TRIANGLES,		INTERPOLATIONFLAGS_PROJECTED));
+			projected->addChild(new TriangleInterpolationTest	(m_context, "triangle_strip",	"Verify triangle strip interpolation",	GL_TRIANGLE_STRIP,	INTERPOLATIONFLAGS_PROJECTED));
+			projected->addChild(new TriangleInterpolationTest	(m_context, "triangle_fan",		"Verify triangle fan interpolation",	GL_TRIANGLE_FAN,	INTERPOLATIONFLAGS_PROJECTED));
+			projected->addChild(new LineInterpolationTest		(m_context, "lines",			"Verify line interpolation",			GL_LINES,			INTERPOLATIONFLAGS_PROJECTED,	PRIMITIVEWIDENESS_NARROW));
+			projected->addChild(new LineInterpolationTest		(m_context, "line_strip",		"Verify line strip interpolation",		GL_LINE_STRIP,		INTERPOLATIONFLAGS_PROJECTED,	PRIMITIVEWIDENESS_NARROW));
+			projected->addChild(new LineInterpolationTest		(m_context, "line_loop",		"Verify line loop interpolation",		GL_LINE_LOOP,		INTERPOLATIONFLAGS_PROJECTED,	PRIMITIVEWIDENESS_NARROW));
+			projected->addChild(new LineInterpolationTest		(m_context, "lines_wide",		"Verify wide line interpolation",		GL_LINES,			INTERPOLATIONFLAGS_PROJECTED,	PRIMITIVEWIDENESS_WIDE));
+			projected->addChild(new LineInterpolationTest		(m_context, "line_strip_wide",	"Verify wide line strip interpolation",	GL_LINE_STRIP,		INTERPOLATIONFLAGS_PROJECTED,	PRIMITIVEWIDENESS_WIDE));
+			projected->addChild(new LineInterpolationTest		(m_context, "line_loop_wide",	"Verify wide line loop interpolation",	GL_LINE_LOOP,		INTERPOLATIONFLAGS_PROJECTED,	PRIMITIVEWIDENESS_WIDE));
+		}
+	}
+
+	// .flatshading
+	{
+		tcu::TestCaseGroup* const flatshading = new tcu::TestCaseGroup(m_testCtx, "flatshading", "Test flatshading");
+
+		addChild(flatshading);
+
+		flatshading->addChild(new TriangleInterpolationTest		(m_context, "triangles",		"Verify triangle flatshading",			GL_TRIANGLES,		INTERPOLATIONFLAGS_FLATSHADE));
+		flatshading->addChild(new TriangleInterpolationTest		(m_context, "triangle_strip",	"Verify triangle strip flatshading",	GL_TRIANGLE_STRIP,	INTERPOLATIONFLAGS_FLATSHADE));
+		flatshading->addChild(new TriangleInterpolationTest		(m_context, "triangle_fan",		"Verify triangle fan flatshading",		GL_TRIANGLE_FAN,	INTERPOLATIONFLAGS_FLATSHADE));
+		flatshading->addChild(new LineInterpolationTest			(m_context, "lines",			"Verify line flatshading",				GL_LINES,			INTERPOLATIONFLAGS_FLATSHADE,	PRIMITIVEWIDENESS_NARROW));
+		flatshading->addChild(new LineInterpolationTest			(m_context, "line_strip",		"Verify line strip flatshading",		GL_LINE_STRIP,		INTERPOLATIONFLAGS_FLATSHADE,	PRIMITIVEWIDENESS_NARROW));
+		flatshading->addChild(new LineInterpolationTest			(m_context, "line_loop",		"Verify line loop flatshading",			GL_LINE_LOOP,		INTERPOLATIONFLAGS_FLATSHADE,	PRIMITIVEWIDENESS_NARROW));
+		flatshading->addChild(new LineInterpolationTest			(m_context, "lines_wide",		"Verify wide line flatshading",			GL_LINES,			INTERPOLATIONFLAGS_FLATSHADE,	PRIMITIVEWIDENESS_WIDE));
+		flatshading->addChild(new LineInterpolationTest			(m_context, "line_strip_wide",	"Verify wide line strip flatshading",	GL_LINE_STRIP,		INTERPOLATIONFLAGS_FLATSHADE,	PRIMITIVEWIDENESS_WIDE));
+		flatshading->addChild(new LineInterpolationTest			(m_context, "line_loop_wide",	"Verify wide line loop flatshading",	GL_LINE_LOOP,		INTERPOLATIONFLAGS_FLATSHADE,	PRIMITIVEWIDENESS_WIDE));
+	}
+
+	// .fbo
+	{
+		static const struct
+		{
+			const char*						name;
+			BaseRenderingCase::RenderTarget	target;
+			int								numSamples;
+		} renderTargets[] =
+		{
+			{ "texture_2d",				BaseRenderingCase::RENDERTARGET_TEXTURE_2D,			-1									},
+			{ "rbo_singlesample",		BaseRenderingCase::RENDERTARGET_RBO_SINGLESAMPLE,	-1									},
+			{ "rbo_multisample_4",		BaseRenderingCase::RENDERTARGET_RBO_MULTISAMPLE,	4									},
+			{ "rbo_multisample_max",	BaseRenderingCase::RENDERTARGET_RBO_MULTISAMPLE,	BaseRenderingCase::SAMPLE_COUNT_MAX	},
+		};
+
+		tcu::TestCaseGroup* const fboGroup = new tcu::TestCaseGroup(m_testCtx, "fbo", "Test using framebuffer objects");
+		addChild(fboGroup);
+
+		// .texture_2d
+		// .rbo_singlesample
+		// .rbo_multisample_4
+		// .rbo_multisample_max
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(renderTargets); ++targetNdx)
+		{
+			tcu::TestCaseGroup* const colorAttachmentGroup = new tcu::TestCaseGroup(m_testCtx, renderTargets[targetNdx].name, ("Test using " + std::string(renderTargets[targetNdx].name) + " color attachment").c_str());
+			fboGroup->addChild(colorAttachmentGroup);
+
+			// .primitives
+			{
+				tcu::TestCaseGroup* const primitiveGroup = new tcu::TestCaseGroup(m_testCtx, "primitives", "Primitive rasterization");
+				colorAttachmentGroup->addChild(primitiveGroup);
+
+				primitiveGroup->addChild(new TrianglesCase	(m_context, "triangles",	"Render primitives as GL_TRIANGLES, verify rasterization result",											renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+				primitiveGroup->addChild(new LinesCase		(m_context, "lines",		"Render primitives as GL_LINES, verify rasterization result",					PRIMITIVEWIDENESS_NARROW,	renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+				primitiveGroup->addChild(new LinesCase		(m_context, "lines_wide",	"Render primitives as GL_LINES with wide lines, verify rasterization result",	PRIMITIVEWIDENESS_WIDE,		renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+				primitiveGroup->addChild(new PointCase		(m_context, "points",		"Render primitives as GL_POINTS, verify rasterization result",					PRIMITIVEWIDENESS_WIDE,		renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+			}
+
+			// .fill_rules
+			{
+				tcu::TestCaseGroup* const fillRules = new tcu::TestCaseGroup(m_testCtx, "fill_rules", "Primitive fill rules");
+
+				colorAttachmentGroup->addChild(fillRules);
+
+				fillRules->addChild(new FillRuleCase(m_context,	"basic_quad",			"Verify fill rules",	FillRuleCase::FILLRULECASE_BASIC,			renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+				fillRules->addChild(new FillRuleCase(m_context,	"basic_quad_reverse",	"Verify fill rules",	FillRuleCase::FILLRULECASE_REVERSED,		renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+				fillRules->addChild(new FillRuleCase(m_context,	"clipped_full",			"Verify fill rules",	FillRuleCase::FILLRULECASE_CLIPPED_FULL,	renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+				fillRules->addChild(new FillRuleCase(m_context,	"clipped_partly",		"Verify fill rules",	FillRuleCase::FILLRULECASE_CLIPPED_PARTIAL,	renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+				fillRules->addChild(new FillRuleCase(m_context,	"projected",			"Verify fill rules",	FillRuleCase::FILLRULECASE_PROJECTED,		renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+			}
+
+			// .interpolation
+			{
+				tcu::TestCaseGroup* const interpolation = new tcu::TestCaseGroup(m_testCtx, "interpolation", "Non-projective interpolation");
+
+				colorAttachmentGroup->addChild(interpolation);
+
+				interpolation->addChild(new TriangleInterpolationTest		(m_context, "triangles",		"Verify triangle interpolation",		GL_TRIANGLES,		INTERPOLATIONFLAGS_NONE,								renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+				interpolation->addChild(new LineInterpolationTest			(m_context, "lines",			"Verify line interpolation",			GL_LINES,			INTERPOLATIONFLAGS_NONE,	PRIMITIVEWIDENESS_NARROW,	renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+				interpolation->addChild(new LineInterpolationTest			(m_context, "lines_wide",		"Verify wide line interpolation",		GL_LINES,			INTERPOLATIONFLAGS_NONE,	PRIMITIVEWIDENESS_WIDE,		renderTargets[targetNdx].target, renderTargets[targetNdx].numSamples));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fRasterizationTests.hpp b/modules/gles3/functional/es3fRasterizationTests.hpp
new file mode 100644
index 0000000..2485137
--- /dev/null
+++ b/modules/gles3/functional/es3fRasterizationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FRASTERIZATIONTESTS_HPP
+#define _ES3FRASTERIZATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rasterization tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class RasterizationTests : public TestCaseGroup
+{
+public:
+							RasterizationTests	(Context& context);
+							~RasterizationTests	(void);
+
+	void					init				(void);
+
+private:
+							RasterizationTests	(const RasterizationTests& other);
+	RasterizationTests&		operator=			(const RasterizationTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FRASTERIZATIONTESTS_HPP
diff --git a/modules/gles3/functional/es3fRasterizerDiscardTests.cpp b/modules/gles3/functional/es3fRasterizerDiscardTests.cpp
new file mode 100644
index 0000000..71068e4
--- /dev/null
+++ b/modules/gles3/functional/es3fRasterizerDiscardTests.cpp
@@ -0,0 +1,511 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rasterizer discard tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fRasterizerDiscardTests.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+#include "glw.h"
+
+using tcu::Vec4;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+static const int	NUM_CASE_ITERATIONS = 1;
+static const Vec4	FAIL_COLOR_RED		= Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+static const Vec4	PASS_COLOR_BLUE		= Vec4(0.0f, 0.0f, 0.5f, 1.0f);
+static const Vec4	BLACK_COLOR			= Vec4(0.0f, 0.0f, 0.0f, 1.0f);
+static const float	FAIL_DEPTH			= 0.0f;
+static const int	FAIL_STENCIL		= 1;
+static const float	UNIT_SQUARE[16] =
+{
+	 1.0f,  1.0f, 0.05f, 1.0f,
+	 1.0f, -1.0f, 0.05f, 1.0f,
+	-1.0f,  1.0f, 0.05f, 1.0f,
+	-1.0f, -1.0f, 0.05f, 1.0f
+};
+
+enum CaseType
+{
+	CASE_WRITE_DEPTH,
+	CASE_WRITE_STENCIL,
+	CASE_CLEAR_COLOR,
+	CASE_CLEAR_DEPTH,
+	CASE_CLEAR_STENCIL
+};
+
+enum CaseOptions
+{
+	CASEOPTION_FBO		= (1 << 0),
+	CASEOPTION_SCISSOR	= (1 << 1)
+};
+
+class RasterizerDiscardCase : public TestCase
+{
+public:
+								RasterizerDiscardCase	(Context& context, const char* name, const char* description, int numPrimitives, CaseType caseType, deUint32 caseOptions, deUint32 drawMode = GL_TRIANGLES);
+								~RasterizerDiscardCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+								RasterizerDiscardCase	(const RasterizerDiscardCase& other);
+	RasterizerDiscardCase&		operator=				(const RasterizerDiscardCase& other);
+
+	void						setupFramebufferObject	(void);
+	void						deleteFramebufferObject	(void);
+
+	int							m_numPrimitives;
+	CaseType					m_caseType;
+	deUint32					m_caseOptions;
+	deUint32					m_drawMode;
+
+	glu::ShaderProgram*			m_program;
+	deUint32					m_fbo;
+	deUint32					m_colorBuf;
+	deUint32					m_depthStencilBuf;
+	int							m_iterNdx;
+	de::Random					m_rnd;
+};
+
+RasterizerDiscardCase::RasterizerDiscardCase (Context& context, const char* name, const char* description, int numPrimitives, CaseType caseType, deUint32 caseOptions, deUint32 drawMode)
+	: TestCase				(context, name, description)
+	, m_numPrimitives		(numPrimitives)
+	, m_caseType			(caseType)
+	, m_caseOptions			(caseOptions)
+	, m_drawMode			(drawMode)
+	, m_program				(DE_NULL)
+	, m_fbo					(0)
+	, m_colorBuf			(0)
+	, m_depthStencilBuf		(0)
+	, m_iterNdx				(0)
+	, m_rnd					(deStringHash(name))
+{
+}
+
+RasterizerDiscardCase::~RasterizerDiscardCase (void)
+{
+	RasterizerDiscardCase::deinit();
+}
+
+static void generateVertices (std::vector<float>& dst, int numPrimitives, de::Random& rnd, deUint32 drawMode)
+{
+	int numVertices;
+
+	switch (drawMode)
+	{
+		case GL_POINTS:			numVertices = numPrimitives;	break;
+		case GL_LINES:			numVertices = 2*numPrimitives;	break;
+		case GL_LINE_STRIP:		numVertices = numPrimitives+1;	break;
+		case GL_LINE_LOOP:		numVertices = numPrimitives+2;	break;
+		case GL_TRIANGLES:		numVertices = 3*numPrimitives;	break;
+		case GL_TRIANGLE_STRIP:	numVertices = numPrimitives+2;	break;
+		case GL_TRIANGLE_FAN:	numVertices = numPrimitives+2;	break;
+		default:
+			DE_ASSERT(false);
+			numVertices = 0;
+	}
+
+	dst.resize(numVertices * 4);
+
+	for (int i = 0; i < numVertices; i++)
+	{
+		dst[i*4    ] = rnd.getFloat(-1.0f, 1.0f);	// x
+		dst[i*4 + 1] = rnd.getFloat(-1.0f, 1.0f);	// y
+		dst[i*4 + 2] = rnd.getFloat( 0.1f, 0.9f);	// z
+		dst[i*4 + 3] = 1.0f;						// w
+	}
+}
+
+void RasterizerDiscardCase::setupFramebufferObject (void)
+{
+	int width  = m_context.getRenderTarget().getWidth();
+	int height = m_context.getRenderTarget().getHeight();
+
+	// Create framebuffer object
+
+	glGenFramebuffers	(1, &m_fbo);				// FBO
+	glGenTextures		(1, &m_colorBuf);			// Color attachment
+	glGenRenderbuffers	(1, &m_depthStencilBuf);	// Depth and stencil attachments
+
+	// Create color texture
+
+	glBindTexture	(GL_TEXTURE_2D, m_colorBuf);
+	glTexParameteri	(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+	glTexParameteri	(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+	glTexParameteri	(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
+	glTexParameteri	(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
+	glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+
+	// Create depth and stencil buffers
+
+	glBindRenderbuffer	  (GL_RENDERBUFFER, m_depthStencilBuf);
+	glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
+
+	// Attach texture and buffers to FBO
+
+	glBindFramebuffer		  (GL_FRAMEBUFFER, m_fbo);
+	glFramebufferTexture2D	  (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuf, 0);
+	glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuf);
+	glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuf);
+
+	deUint32 fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+	if (fboStatus == GL_FRAMEBUFFER_UNSUPPORTED)
+		throw tcu::NotSupportedError("Framebuffer unsupported", "", __FILE__, __LINE__);
+	else if (fboStatus != GL_FRAMEBUFFER_COMPLETE)
+		throw tcu::TestError("Failed to create framebuffer object", "", __FILE__, __LINE__);
+}
+
+void RasterizerDiscardCase::deleteFramebufferObject (void)
+{
+	glDeleteTextures		(1, &m_colorBuf);			// Color attachment
+	glDeleteRenderbuffers	(1, &m_depthStencilBuf);	// Depth and stencil attachments
+	glDeleteFramebuffers	(1, &m_fbo);				// FBO
+}
+
+void RasterizerDiscardCase::init (void)
+{
+	const char*	vertShaderSource =
+				"#version 300 es\n"
+				"layout(location = 0) in mediump vec4 a_position;\n"
+				"\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = a_position;\n"
+				"}\n";
+
+	const char* fragShaderSource =
+				"#version 300 es\n"
+				"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+				"uniform mediump vec4 u_color;\n"
+				"\n"
+				"void main (void)\n"
+				"{\n"
+				"	mediump float depth_gradient = gl_FragCoord.z;\n"
+				"	mediump float bias = 0.1;\n"
+				"	dEQP_FragColor = vec4(u_color.xyz * (depth_gradient + bias), 1.0);\n"
+				"}\n";
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource));
+
+	if (!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+		TCU_FAIL("Failed to compile shader program");
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); // Initialize test result to pass.
+	GLU_CHECK_MSG ("Case initialization finished");
+}
+
+void RasterizerDiscardCase::deinit (void)
+{
+	deleteFramebufferObject();
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+RasterizerDiscardCase::IterateResult RasterizerDiscardCase::iterate (void)
+{
+	TestLog&					log				= m_testCtx.getLog();
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderTarget();
+	deUint32					colorUnif		= glGetUniformLocation(m_program->getProgram(), "u_color");
+	bool						failColorFound	= false;
+	bool						passColorFound	= false;
+	std::vector<float>			vertices;
+
+	std::string header = "Case iteration " + de::toString(m_iterNdx+1) + " / " + de::toString(NUM_CASE_ITERATIONS);
+	log << TestLog::Section(header, header);
+
+	DE_ASSERT (m_program);
+
+	// Create and bind FBO if needed
+
+	if (m_caseOptions & CASEOPTION_FBO)
+	{
+		try
+		{
+			setupFramebufferObject();
+		}
+		catch (tcu::NotSupportedError& e)
+		{
+			log << TestLog::Message << "ERROR: " << e.what() << "." << TestLog::EndMessage << TestLog::EndSection;
+			m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not supported");
+			return STOP;
+		}
+		catch (tcu::InternalError& e)
+		{
+			log << TestLog::Message << "ERROR: " << e.what() << "." << TestLog::EndMessage << TestLog::EndSection;
+			m_testCtx.setTestResult(QP_TEST_RESULT_INTERNAL_ERROR, "Error");
+			return STOP;
+		}
+	}
+
+	if (m_caseOptions & CASEOPTION_SCISSOR)
+	{
+		glEnable (GL_SCISSOR_TEST);
+		glScissor(0, 0, renderTarget.getWidth(), renderTarget.getHeight());
+		log << TestLog::Message << "Scissor test enabled: glScissor(0, 0, " << renderTarget.getWidth() << ", " << renderTarget.getHeight() << ")" << TestLog::EndMessage;
+	}
+
+	glUseProgram	(m_program->getProgram());
+
+	glEnable		(GL_DEPTH_TEST);
+	glDepthRangef	(0.0f, 1.0f);
+	glDepthFunc		(GL_LEQUAL);
+
+	glEnable		(GL_STENCIL_TEST);
+	glStencilFunc	(GL_NOTEQUAL, 1, 0xFF);
+	glStencilOp		(GL_REPLACE, GL_KEEP, GL_KEEP);
+
+	glClearColor	(PASS_COLOR_BLUE.x(), PASS_COLOR_BLUE.y(), PASS_COLOR_BLUE.z(), PASS_COLOR_BLUE.w());
+	glClearDepthf	(1.0f);
+	glClearStencil	(0);
+	glClear			(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+	// Generate vertices
+
+	glEnableVertexAttribArray (0);
+	generateVertices		  (vertices, m_numPrimitives, m_rnd, m_drawMode);
+	glVertexAttribPointer	  (0, 4, GL_FLOAT, GL_FALSE, 0, &vertices[0]);
+
+	// Clear color to black for depth and stencil clear cases
+
+	if (m_caseType == CASE_CLEAR_DEPTH || m_caseType == CASE_CLEAR_STENCIL)
+	{
+		glClearColor	(BLACK_COLOR.x(), BLACK_COLOR.y(), BLACK_COLOR.z(), BLACK_COLOR.w());
+		glClear			(GL_COLOR_BUFFER_BIT);
+	}
+
+	// Set fail values for color, depth and stencil
+
+	glUniform4f		(colorUnif, FAIL_COLOR_RED.x(), FAIL_COLOR_RED.y(), FAIL_COLOR_RED.z(), FAIL_COLOR_RED.w());
+	glClearColor	(FAIL_COLOR_RED.x(), FAIL_COLOR_RED.y(), FAIL_COLOR_RED.z(), FAIL_COLOR_RED.w());
+	glClearDepthf	(FAIL_DEPTH);
+	glClearStencil	(FAIL_STENCIL);
+
+	// Enable rasterizer discard
+
+	glEnable		(GL_RASTERIZER_DISCARD);
+	GLU_CHECK_MSG	("Rasterizer discard enabled");
+
+	// Do to-be-discarded primitive draws and buffer clears
+
+	switch (m_caseType)
+	{
+		case CASE_WRITE_DEPTH:			glDrawArrays(m_drawMode, 0, (int)vertices.size() / 4);																	break;
+		case CASE_WRITE_STENCIL:		glDrawArrays(m_drawMode, 0, (int)vertices.size() / 4);																	break;
+		case CASE_CLEAR_COLOR:			(m_caseOptions & CASEOPTION_FBO) ? glClearBufferfv(GL_COLOR, 0, &FAIL_COLOR_RED[0])	: glClear(GL_COLOR_BUFFER_BIT);		break;
+		case CASE_CLEAR_DEPTH:			(m_caseOptions & CASEOPTION_FBO) ? glClearBufferfv(GL_DEPTH, 0, &FAIL_DEPTH)		: glClear(GL_DEPTH_BUFFER_BIT);		break;
+		case CASE_CLEAR_STENCIL:		(m_caseOptions & CASEOPTION_FBO) ? glClearBufferiv(GL_STENCIL, 0, &FAIL_STENCIL)	: glClear(GL_STENCIL_BUFFER_BIT);	break;
+		default:						DE_ASSERT(false);
+	}
+
+	// Disable rasterizer discard
+
+	glDisable		(GL_RASTERIZER_DISCARD);
+	GLU_CHECK_MSG	("Rasterizer discard disabled");
+
+	if (m_caseType == CASE_WRITE_STENCIL)
+	{
+		if ((m_caseOptions & CASEOPTION_FBO) || m_context.getRenderTarget().getStencilBits() > 0)
+		{
+			// Draw a full-screen square that colors all pixels red if they have stencil value 1.
+
+			glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE, 0, &UNIT_SQUARE[0]);
+			glStencilFunc		  (GL_EQUAL, 1, 0xFF);
+			glDrawArrays		  (GL_TRIANGLE_STRIP, 0, 4);
+		}
+		// \note If no stencil buffers are present and test is rendering to default framebuffer, test will always pass.
+	}
+	else if (m_caseType == CASE_CLEAR_DEPTH || m_caseType == CASE_CLEAR_STENCIL)
+	{
+		// Draw pass-indicating primitives for depth and stencil clear cases
+
+		glUniform4f	 (colorUnif, PASS_COLOR_BLUE.x(), PASS_COLOR_BLUE.y(), PASS_COLOR_BLUE.z(), PASS_COLOR_BLUE.w());
+		glDrawArrays (m_drawMode, 0, (int)vertices.size() / 4);
+	}
+
+	glFinish  ();
+	glDisable (GL_STENCIL_TEST);
+	glDisable (GL_DEPTH_TEST);
+	glDisable (GL_SCISSOR_TEST);
+
+	// Read and check pixel data
+
+	tcu::Surface pixels(renderTarget.getWidth(), renderTarget.getHeight());
+	glu::readPixels(m_context.getRenderContext(), 0, 0, pixels.getAccess());
+
+	{
+		int width = pixels.getWidth();
+		int height = pixels.getHeight();
+
+		for (int y = 0; y < height; y++)
+		{
+			for (int x = 0; x < width; x++)
+			{
+				if (pixels.getPixel(x,y).getBlue() != 0)
+					passColorFound = true;
+
+				if (pixels.getPixel(x,y).getRed() != 0)
+				{
+					failColorFound = true;
+					break;
+				}
+			}
+			if (failColorFound) break;
+		}
+	}
+
+	// Delete FBO if created
+
+	if (m_caseOptions & CASEOPTION_FBO)
+		deleteFramebufferObject();
+
+	// Evaluate test result
+
+	bool testOk = passColorFound && !failColorFound;
+
+	if (!testOk)
+		log << TestLog::Image ("Result image", "Result image", pixels);
+	log << TestLog::Message << "Test result: " << (testOk ? "Passed!" : "Failed!") << TestLog::EndMessage;
+
+	if (!testOk)
+	{
+		log << TestLog::Message << "Primitive or buffer clear was not discarded." << TestLog::EndMessage << TestLog::EndSection;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		return STOP;
+	}
+
+	log << TestLog::Message << "Primitive or buffer clear was discarded correctly." << TestLog::EndMessage << TestLog::EndSection;
+
+	return (++m_iterNdx < NUM_CASE_ITERATIONS) ? CONTINUE : STOP;
+}
+
+RasterizerDiscardTests::RasterizerDiscardTests (Context& context)
+	: TestCaseGroup(context, "rasterizer_discard", "Rasterizer Discard Tests")
+{
+}
+
+RasterizerDiscardTests::~RasterizerDiscardTests (void)
+{
+}
+
+void RasterizerDiscardTests::init (void)
+{
+	tcu::TestCaseGroup* basic	= new tcu::TestCaseGroup(m_testCtx, "basic",	"Rasterizer discard test for default framebuffer");
+	tcu::TestCaseGroup*	scissor	= new tcu::TestCaseGroup(m_testCtx, "scissor",	"Rasterizer discard test for default framebuffer with scissor test enabled");
+	tcu::TestCaseGroup*	fbo		= new tcu::TestCaseGroup(m_testCtx, "fbo",		"Rasterizer discard test for framebuffer object");
+
+	addChild(basic);
+	addChild(scissor);
+	addChild(fbo);
+
+	// Default framebuffer cases
+
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_depth_points",				"points",			4, CASE_WRITE_DEPTH,	0, GL_POINTS));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_depth_lines",				"lines",			4, CASE_WRITE_DEPTH,	0, GL_LINES));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_depth_line_strip",			"line_strip",		4, CASE_WRITE_DEPTH,	0, GL_LINE_STRIP));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_depth_line_loop",			"line_loop",		4, CASE_WRITE_DEPTH,	0, GL_LINE_LOOP));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_depth_triangles",			"triangles",		4, CASE_WRITE_DEPTH,	0, GL_TRIANGLES));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_depth_triangle_strip",		"triangle_strip",	4, CASE_WRITE_DEPTH,	0, GL_TRIANGLE_STRIP));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_depth_triangle_fan",		"triangle_fan",		4, CASE_WRITE_DEPTH,	0, GL_TRIANGLE_FAN));
+
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_stencil_points",			"points",			4, CASE_WRITE_STENCIL,	0, GL_POINTS));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_stencil_lines",				"lines",			4, CASE_WRITE_STENCIL,	0, GL_LINES));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_stencil_line_strip",		"line_strip",		4, CASE_WRITE_STENCIL,	0, GL_LINE_STRIP));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_stencil_line_loop",			"line_loop",		4, CASE_WRITE_STENCIL,	0, GL_LINE_LOOP));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_stencil_triangles",			"triangles",		4, CASE_WRITE_STENCIL,	0, GL_TRIANGLES));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_stencil_triangle_strip",	"triangle_strip",	4, CASE_WRITE_STENCIL,	0, GL_TRIANGLE_STRIP));
+	basic->addChild(new RasterizerDiscardCase(m_context, "write_stencil_triangle_fan",		"triangle_fan",		4, CASE_WRITE_STENCIL,	0, GL_TRIANGLE_FAN));
+
+	basic->addChild(new RasterizerDiscardCase(m_context, "clear_color",						"clear_color",		4, CASE_CLEAR_COLOR,	0));
+	basic->addChild(new RasterizerDiscardCase(m_context, "clear_depth",						"clear_depth",		4, CASE_CLEAR_DEPTH,	0));
+	basic->addChild(new RasterizerDiscardCase(m_context, "clear_stencil",					"clear_stencil",	4, CASE_CLEAR_STENCIL,	0));
+
+	// Default framebuffer cases with scissor test enabled
+
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_depth_points",			"points",			4, CASE_WRITE_DEPTH,	CASEOPTION_SCISSOR, GL_POINTS));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_depth_lines",				"lines",			4, CASE_WRITE_DEPTH,	CASEOPTION_SCISSOR, GL_LINES));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_depth_line_strip",		"line_strip",		4, CASE_WRITE_DEPTH,	CASEOPTION_SCISSOR, GL_LINE_STRIP));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_depth_line_loop",			"line_loop",		4, CASE_WRITE_DEPTH,	CASEOPTION_SCISSOR, GL_LINE_LOOP));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_depth_triangles",			"triangles",		4, CASE_WRITE_DEPTH,	CASEOPTION_SCISSOR, GL_TRIANGLES));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_depth_triangle_strip",	"triangle_strip",	4, CASE_WRITE_DEPTH,	CASEOPTION_SCISSOR, GL_TRIANGLE_STRIP));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_depth_triangle_fan",		"triangle_fan",		4, CASE_WRITE_DEPTH,	CASEOPTION_SCISSOR, GL_TRIANGLE_FAN));
+
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_stencil_points",			"points",			4, CASE_WRITE_STENCIL,	CASEOPTION_SCISSOR, GL_POINTS));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_stencil_lines",			"lines",			4, CASE_WRITE_STENCIL,	CASEOPTION_SCISSOR, GL_LINES));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_stencil_line_strip",		"line_strip",		4, CASE_WRITE_STENCIL,	CASEOPTION_SCISSOR, GL_LINE_STRIP));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_stencil_line_loop",		"line_loop",		4, CASE_WRITE_STENCIL,	CASEOPTION_SCISSOR, GL_LINE_LOOP));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_stencil_triangles",		"triangles",		4, CASE_WRITE_STENCIL,	CASEOPTION_SCISSOR, GL_TRIANGLES));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_stencil_triangle_strip",	"triangle_strip",	4, CASE_WRITE_STENCIL,	CASEOPTION_SCISSOR, GL_TRIANGLE_STRIP));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "write_stencil_triangle_fan",	"triangle_fan",		4, CASE_WRITE_STENCIL,	CASEOPTION_SCISSOR, GL_TRIANGLE_FAN));
+
+	scissor->addChild(new RasterizerDiscardCase(m_context, "clear_color",					"clear_color",		4, CASE_CLEAR_COLOR,	CASEOPTION_SCISSOR));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "clear_depth",					"clear_depth",		4, CASE_CLEAR_DEPTH,	CASEOPTION_SCISSOR));
+	scissor->addChild(new RasterizerDiscardCase(m_context, "clear_stencil",					"clear_stencil",	4, CASE_CLEAR_STENCIL,	CASEOPTION_SCISSOR));
+
+	// FBO cases
+
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_depth_points",			"points",			4, CASE_WRITE_DEPTH,	CASEOPTION_FBO, GL_POINTS));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_depth_lines",				"lines",			4, CASE_WRITE_DEPTH,	CASEOPTION_FBO, GL_LINES));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_depth_line_strip",		"line_strip",		4, CASE_WRITE_DEPTH,	CASEOPTION_FBO, GL_LINE_STRIP));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_depth_line_loop",			"line_loop",		4, CASE_WRITE_DEPTH,	CASEOPTION_FBO, GL_LINE_LOOP));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_depth_triangles",			"triangles",		4, CASE_WRITE_DEPTH,	CASEOPTION_FBO, GL_TRIANGLES));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_depth_triangle_strip",	"triangle_strip",	4, CASE_WRITE_DEPTH,	CASEOPTION_FBO, GL_TRIANGLE_STRIP));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_depth_triangle_fan",		"triangle_fan",		4, CASE_WRITE_DEPTH,	CASEOPTION_FBO, GL_TRIANGLE_FAN));
+
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_stencil_points",			"points",			4, CASE_WRITE_STENCIL,	CASEOPTION_FBO, GL_POINTS));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_stencil_lines",			"lines",			4, CASE_WRITE_STENCIL,	CASEOPTION_FBO, GL_LINES));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_stencil_line_strip",		"line_strip",		4, CASE_WRITE_STENCIL,	CASEOPTION_FBO, GL_LINE_STRIP));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_stencil_line_loop",		"line_loop",		4, CASE_WRITE_STENCIL,	CASEOPTION_FBO, GL_LINE_LOOP));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_stencil_triangles",		"triangles",		4, CASE_WRITE_STENCIL,	CASEOPTION_FBO, GL_TRIANGLES));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_stencil_triangle_strip",	"triangle_strip",	4, CASE_WRITE_STENCIL,	CASEOPTION_FBO, GL_TRIANGLE_STRIP));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "write_stencil_triangle_fan",	"triangle_fan",		4, CASE_WRITE_STENCIL,	CASEOPTION_FBO, GL_TRIANGLE_FAN));
+
+	fbo->addChild(new RasterizerDiscardCase(m_context, "clear_color",					"clear_color",		4, CASE_CLEAR_COLOR,	CASEOPTION_FBO));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "clear_depth",					"clear_depth",		4, CASE_CLEAR_DEPTH,	CASEOPTION_FBO));
+	fbo->addChild(new RasterizerDiscardCase(m_context, "clear_stencil",					"clear_stencil",	4, CASE_CLEAR_STENCIL,	CASEOPTION_FBO));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fRasterizerDiscardTests.hpp b/modules/gles3/functional/es3fRasterizerDiscardTests.hpp
new file mode 100644
index 0000000..2a66ddc
--- /dev/null
+++ b/modules/gles3/functional/es3fRasterizerDiscardTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FRASTERIZERDISCARDTESTS_HPP
+#define _ES3FRASTERIZERDISCARDTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rasterizer discard tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class RasterizerDiscardTests : public TestCaseGroup
+{
+public:
+							RasterizerDiscardTests	(Context& context);
+							~RasterizerDiscardTests	(void);
+
+	void					init					(void);
+
+private:
+							RasterizerDiscardTests	(const RasterizerDiscardTests& other);
+	RasterizerDiscardTests&	operator=				(const RasterizerDiscardTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FRASTERIZERDISCARDTESTS_HPP
diff --git a/modules/gles3/functional/es3fRboStateQueryTests.cpp b/modules/gles3/functional/es3fRboStateQueryTests.cpp
new file mode 100644
index 0000000..371c834
--- /dev/null
+++ b/modules/gles3/functional/es3fRboStateQueryTests.cpp
@@ -0,0 +1,391 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rbo state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fRboStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+void checkRenderbufferComponentSize (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, int r, int g, int b, int a, int d, int s)
+{
+	using tcu::TestLog;
+
+	const int referenceSizes[] = {r, g, b, a, d, s};
+	const GLenum paramNames[] =
+	{
+		GL_RENDERBUFFER_RED_SIZE,
+		GL_RENDERBUFFER_GREEN_SIZE,
+		GL_RENDERBUFFER_BLUE_SIZE,
+		GL_RENDERBUFFER_ALPHA_SIZE,
+		GL_RENDERBUFFER_DEPTH_SIZE,
+		GL_RENDERBUFFER_STENCIL_SIZE
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(referenceSizes) == DE_LENGTH_OF_ARRAY(paramNames));
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(referenceSizes); ++ndx)
+	{
+		if (referenceSizes[ndx] == -1)
+			continue;
+
+		StateQueryMemoryWriteGuard<GLint> state;
+		gl.glGetRenderbufferParameteriv(GL_RENDERBUFFER, paramNames[ndx], &state);
+
+		if (!state.verifyValidity(testCtx))
+			return;
+
+		if (state < referenceSizes[ndx])
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: Expected greater or equal to " << referenceSizes[ndx] << "; got " << state << TestLog::EndMessage;
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+		}
+	}
+}
+
+void checkIntEquals (tcu::TestContext& testCtx, GLint got, GLint expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void checkIntGreaterOrEqual (tcu::TestContext& testCtx, GLint got, GLint expected)
+{
+	using tcu::TestLog;
+
+	if (got < expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected greater or equal to " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void checkRenderbufferParam (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetRenderbufferParameteriv(GL_RENDERBUFFER, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntEquals(testCtx, state, reference);
+}
+
+void checkRenderbufferParamGreaterOrEqual (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetRenderbufferParameteriv(GL_RENDERBUFFER, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntGreaterOrEqual(testCtx, state, reference);
+}
+
+class RboSizeCase : public ApiCase
+{
+public:
+	RboSizeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_WIDTH,		0);
+		checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_HEIGHT,	0);
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 60;
+		for (int i = 0; i < numIterations; ++i)
+		{
+			const GLint w = rnd.getInt(0, 128);
+			const GLint h = rnd.getInt(0, 128);
+
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, w, h);
+
+			checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_WIDTH,		w);
+			checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_HEIGHT,	h);
+		}
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+class RboInternalFormatCase : public ApiCase
+{
+public:
+	RboInternalFormatCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_INTERNAL_FORMAT, GL_RGBA4);
+		expectError(GL_NO_ERROR);
+
+		const GLenum requiredColorformats[] =
+		{
+			GL_R8, GL_RG8, GL_RGB8, GL_RGB565, GL_RGBA4, GL_RGB5_A1, GL_RGBA8, GL_RGB10_A2,
+			GL_RGB10_A2UI, GL_SRGB8_ALPHA8, GL_R8I, GL_R8UI, GL_R16I, GL_R16UI, GL_R32I, GL_R32UI,
+			GL_RG8I, GL_RG8UI, GL_RG16I, GL_RG16UI, GL_RG32I, GL_RG32UI, GL_RGBA8I, GL_RGBA8UI,
+			GL_RGBA16I, GL_RGBA16UI, GL_RGBA32I, GL_RGBA32UI
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requiredColorformats); ++ndx)
+		{
+			glRenderbufferStorage(GL_RENDERBUFFER, requiredColorformats[ndx], 128, 128);
+			expectError(GL_NO_ERROR);
+
+			checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_INTERNAL_FORMAT, requiredColorformats[ndx]);
+		}
+
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+class RboComponentSizeColorCase : public ApiCase
+{
+public:
+	RboComponentSizeColorCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkRenderbufferComponentSize(m_testCtx, *this, 0, 0, 0, 0, 0, 0);
+		expectError(GL_NO_ERROR);
+
+		const struct ColorFormat
+		{
+			GLenum	internalFormat;
+			int		bitsR, bitsG, bitsB, bitsA;
+		} requiredColorFormats[] =
+		{
+			{ GL_R8,			8,	0,	0,	0	},
+			{ GL_RG8,			8,	8,	0,	0	},
+			{ GL_RGB8,			8,	8,	8,	0	},
+			{ GL_RGB565,		5,	6,	5,	0	},
+			{ GL_RGBA4,			4,	4,	4,	4	},
+			{ GL_RGB5_A1, 		5,	5,	5,	1	},
+			{ GL_RGBA8,			8,	8,	8,	8	},
+			{ GL_RGB10_A2,		10, 10, 10, 2	},
+			{ GL_RGB10_A2UI,	10, 10, 10, 2	},
+			{ GL_SRGB8_ALPHA8,	8,	8,	8,	8	},
+			{ GL_R8I,			8,	0,	0,	0	},
+			{ GL_R8UI,			8,	0,	0,	0	},
+			{ GL_R16I,			16, 0,	0,	0	},
+			{ GL_R16UI,			16, 0,	0,	0	},
+			{ GL_R32I,			32, 0,	0,	0	},
+			{ GL_R32UI,			32, 0,	0,	0	},
+			{ GL_RG8I,			8,	8,	0,	0	},
+			{ GL_RG8UI,			8,	8,	0,	0	},
+			{ GL_RG16I,			16, 16, 0,	0	},
+			{ GL_RG16UI,		16, 16, 0,	0	},
+			{ GL_RG32I,			32, 32, 0,	0	},
+			{ GL_RG32UI,		32, 32, 0,	0	},
+			{ GL_RGBA8I,		8,	8,	8,	8	},
+			{ GL_RGBA8UI,		8,	8,	8,	8	},
+			{ GL_RGBA16I,		16, 16, 16, 16	},
+			{ GL_RGBA16UI,		16, 16, 16, 16	},
+			{ GL_RGBA32I,		32, 32, 32, 32	},
+			{ GL_RGBA32UI,		32, 32, 32, 32	}
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requiredColorFormats); ++ndx)
+		{
+			glRenderbufferStorage(GL_RENDERBUFFER, requiredColorFormats[ndx].internalFormat, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			checkRenderbufferComponentSize(m_testCtx, *this, requiredColorFormats[ndx].bitsR, requiredColorFormats[ndx].bitsG, requiredColorFormats[ndx].bitsB, requiredColorFormats[ndx].bitsA, -1, -1);
+		}
+
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+class RboComponentSizeDepthCase : public ApiCase
+{
+public:
+	RboComponentSizeDepthCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		const struct DepthFormat
+		{
+			GLenum	internalFormat;
+			int		dbits;
+			int		sbits;
+		} requiredDepthFormats[] =
+		{
+			{ GL_DEPTH_COMPONENT16,		16, 0 },
+			{ GL_DEPTH_COMPONENT24,		24, 0 },
+			{ GL_DEPTH_COMPONENT32F,	32, 0 },
+			{ GL_DEPTH24_STENCIL8,		24, 8 },
+			{ GL_DEPTH32F_STENCIL8,		32, 8 },
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requiredDepthFormats); ++ndx)
+		{
+			glRenderbufferStorage(GL_RENDERBUFFER, requiredDepthFormats[ndx].internalFormat, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			checkRenderbufferComponentSize(m_testCtx, *this, -1, -1, -1, -1, requiredDepthFormats[ndx].dbits, requiredDepthFormats[ndx].sbits);
+		}
+
+		// STENCIL_INDEX8 is required, in that case sBits >= 8
+		{
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			StateQueryMemoryWriteGuard<GLint> state;
+			glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_STENCIL_SIZE, &state);
+
+			if (state.verifyValidity(m_testCtx) && state < 8)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected greater or equal to 8; got " << state << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+			}
+		}
+
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+class RboSamplesCase : public ApiCase
+{
+public:
+	RboSamplesCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		GLuint renderbufferID = 0;
+		glGenRenderbuffers(1, &renderbufferID);
+		glBindRenderbuffer(GL_RENDERBUFFER, renderbufferID);
+		expectError(GL_NO_ERROR);
+
+		checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_SAMPLES, 0);
+		expectError(GL_NO_ERROR);
+
+		StateQueryMemoryWriteGuard<GLint> max_samples;
+		glGetIntegerv(GL_MAX_SAMPLES, &max_samples);
+		if (!max_samples.verifyValidity(m_testCtx))
+			return;
+
+		// 0 samples is a special case
+		{
+			glRenderbufferStorageMultisample(GL_RENDERBUFFER, 0, GL_RGBA8, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			checkRenderbufferParam(m_testCtx, *this, GL_RENDERBUFFER_SAMPLES, 0);
+		}
+
+		// test [1, n] samples
+		for (int samples = 1; samples <= max_samples; ++samples)
+		{
+			glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, 128, 128);
+			expectError(GL_NO_ERROR);
+
+			checkRenderbufferParamGreaterOrEqual(m_testCtx, *this, GL_RENDERBUFFER_SAMPLES, samples);
+		}
+
+		glDeleteRenderbuffers(1, &renderbufferID);
+	}
+};
+
+} // anonymous
+
+
+RboStateQueryTests::RboStateQueryTests (Context& context)
+	: TestCaseGroup(context, "rbo", "Rbo State Query tests")
+{
+}
+
+void RboStateQueryTests::init (void)
+{
+	addChild(new RboSizeCase				(m_context, "renderbuffer_size",					"RENDERBUFFER_WIDTH and RENDERBUFFER_HEIGHT"));
+	addChild(new RboInternalFormatCase		(m_context, "renderbuffer_internal_format",			"RENDERBUFFER_INTERNAL_FORMAT"));
+	addChild(new RboComponentSizeColorCase	(m_context, "renderbuffer_component_size_color",	"RENDERBUFFER_x_SIZE"));
+	addChild(new RboComponentSizeDepthCase	(m_context, "renderbuffer_component_size_depth",	"RENDERBUFFER_x_SIZE"));
+	addChild(new RboSamplesCase				(m_context, "renderbuffer_samples",					"RENDERBUFFER_SAMPLES"));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fRboStateQueryTests.hpp b/modules/gles3/functional/es3fRboStateQueryTests.hpp
new file mode 100644
index 0000000..4f04073
--- /dev/null
+++ b/modules/gles3/functional/es3fRboStateQueryTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FRBOSTATEQUERYTESTS_HPP
+#define _ES3FRBOSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rbo state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class RboStateQueryTests : public TestCaseGroup
+{
+public:
+							RboStateQueryTests	(Context& context);
+
+	void					init				(void);
+
+private:
+							RboStateQueryTests	(const RboStateQueryTests& other);
+	RboStateQueryTests&		operator=			(const RboStateQueryTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FRBOSTATEQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fReadPixelsTests.cpp b/modules/gles3/functional/es3fReadPixelsTests.cpp
new file mode 100644
index 0000000..973bc12
--- /dev/null
+++ b/modules/gles3/functional/es3fReadPixelsTests.cpp
@@ -0,0 +1,502 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Read pixels tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fReadPixelsTests.hpp"
+
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+#include "gluDefs.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+
+#include <cstring>
+#include <sstream>
+
+#include "glw.h"
+
+using std::vector;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+namespace
+{
+
+class ReadPixelsTest : public TestCase
+{
+public:
+					ReadPixelsTest	(Context& context, const char* name, const char* description, bool chooseFormat, int alignment, GLint rowLength, GLint skipRows, GLint skipPixels, GLenum format = GL_RGBA, GLenum type = GL_UNSIGNED_BYTE);
+
+	IterateResult	iterate			(void);
+	void			render			(tcu::Texture2D& reference);
+
+private:
+	int				m_seed;
+	bool			m_chooseFormat;
+	int				m_alignment;
+	GLint			m_rowLength;
+	GLint			m_skipRows;
+	GLint			m_skipPixels;
+	GLint			m_format;
+	GLint			m_type;
+
+	const int		m_width;
+	const int		m_height;
+
+	void			getFormatInfo	(tcu::TextureFormat& format, int& pixelSize, bool& align);
+	void			clearColor		(tcu::Texture2D& reference, vector<deUint8>& pixelData, bool align, int pixelSize);
+};
+
+ReadPixelsTest::ReadPixelsTest	(Context& context, const char* name, const char* description, bool chooseFormat, int alignment, GLint rowLength, GLint skipRows, GLint skipPixels, GLenum format, GLenum type)
+	: TestCase			(context, name, description)
+	, m_seed			(deStringHash(name))
+	, m_chooseFormat	(chooseFormat)
+	, m_alignment		(alignment)
+	, m_rowLength		(rowLength)
+	, m_skipRows		(skipRows)
+	, m_skipPixels		(skipPixels)
+	, m_format			(format)
+	, m_type			(type)
+	, m_width			(13)
+	, m_height			(13)
+{
+}
+
+void ReadPixelsTest::render (tcu::Texture2D& reference)
+{
+	// Create program
+	const char* vertexSource =
+	"#version 300 es\n"
+	"in mediump vec2 i_coord;\n"
+	"void main (void)\n"
+	"{\n"
+	"\tgl_Position = vec4(i_coord, 0.0, 1.0);\n"
+	"}\n";
+
+	std::stringstream fragmentSource;
+
+
+	fragmentSource <<
+	"#version 300 es\n";
+
+	if (reference.getFormat().type == tcu::TextureFormat::SIGNED_INT32)
+		fragmentSource << "layout(location = 0) out mediump ivec4 o_color;\n";
+	else if (reference.getFormat().type == tcu::TextureFormat::UNSIGNED_INT32)
+		fragmentSource << "layout(location = 0) out mediump uvec4 o_color;\n";
+	else
+		fragmentSource << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	fragmentSource <<
+	"void main (void)\n"
+	"{\n";
+
+	if (reference.getFormat().type == tcu::TextureFormat::UNSIGNED_INT32)
+		fragmentSource << "\to_color = uvec4(0, 0, 0, 1000);\n";
+	else if (reference.getFormat().type == tcu::TextureFormat::SIGNED_INT32)
+		fragmentSource << "\to_color = ivec4(0, 0, 0, 1000);\n";
+	else
+		fragmentSource << "\to_color = vec4(0.0, 0.0, 0.0, 1.0);\n";
+
+	fragmentSource <<
+	"}\n";
+
+	glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexSource, fragmentSource.str()));
+
+	m_testCtx.getLog() << program;
+	TCU_CHECK(program.isOk());
+	GLU_CHECK_CALL(glUseProgram(program.getProgram()));
+
+	// Render
+	{
+		const float coords[] =
+		{
+			-0.5f, -0.5f,
+			 0.5f, -0.5f,
+			 0.5f,  0.5f,
+
+			 0.5f,  0.5f,
+			-0.5f,  0.5f,
+			-0.5f, -0.5f
+		};
+		GLuint coordLoc;
+
+		coordLoc = glGetAttribLocation(program.getProgram(), "i_coord");
+		GLU_CHECK_MSG("glGetAttribLocation()");
+
+		GLU_CHECK_CALL(glEnableVertexAttribArray(coordLoc));
+
+		GLU_CHECK_CALL(glVertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, coords));
+
+		GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 6));
+		GLU_CHECK_CALL(glDisableVertexAttribArray(coordLoc));
+	}
+
+	// Render reference
+
+	const int coordX1 = (int)((-0.5f * reference.getWidth()		/ 2.0f) + reference.getWidth() / 2.0f);
+	const int coordY1 = (int)((-0.5f * reference.getHeight()	/ 2.0f) + reference.getHeight() / 2.0f);
+	const int coordX2 = (int)(( 0.5f * reference.getWidth()		/ 2.0f) + reference.getWidth() / 2.0f);
+	const int coordY2 = (int)(( 0.5f * reference.getHeight()	/ 2.0f) + reference.getHeight() / 2.0f);
+
+	for (int x = 0; x < reference.getWidth(); x++)
+	{
+		if (x < coordX1 || x > coordX2)
+			continue;
+
+		for (int y = 0; y < reference.getHeight(); y++)
+		{
+			if (y >= coordY1 && y <= coordY2)
+			{
+				if (reference.getFormat().type == tcu::TextureFormat::SIGNED_INT32)
+					reference.getLevel(0).setPixel(tcu::IVec4(0, 0, 0, 1000), x, y);
+				else if (reference.getFormat().type == tcu::TextureFormat::UNSIGNED_INT32)
+					reference.getLevel(0).setPixel(tcu::UVec4(0, 0, 0, 1000), x, y);
+				else
+					reference.getLevel(0).setPixel(tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f), x, y);
+			}
+		}
+	}
+}
+
+void ReadPixelsTest::getFormatInfo (tcu::TextureFormat& format, int& pixelSize, bool& align)
+{
+	if (m_chooseFormat)
+	{
+		GLU_CHECK_CALL(glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &m_format));
+		GLU_CHECK_CALL(glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &m_type));
+	}
+
+	format = glu::mapGLTransferFormat(m_format, m_type);
+
+	switch (m_type)
+	{
+		case GL_BYTE:
+		case GL_UNSIGNED_BYTE:
+		case GL_SHORT:
+		case GL_UNSIGNED_SHORT:
+		case GL_INT:
+		case GL_UNSIGNED_INT:
+		case GL_FLOAT:
+		case GL_HALF_FLOAT:
+			align = true;
+			break;
+
+		case GL_UNSIGNED_SHORT_5_6_5:
+		case GL_UNSIGNED_SHORT_4_4_4_4:
+		case GL_UNSIGNED_SHORT_5_5_5_1:
+		case GL_UNSIGNED_INT_2_10_10_10_REV:
+		case GL_UNSIGNED_INT_10F_11F_11F_REV:
+		case GL_UNSIGNED_INT_24_8:
+		case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
+		case GL_UNSIGNED_INT_5_9_9_9_REV:
+			align = false;
+			break;
+
+		default:
+			throw tcu::InternalError("Unsupported format", "", __FILE__, __LINE__);
+	}
+
+	pixelSize = format.getPixelSize();
+}
+
+void ReadPixelsTest::clearColor (tcu::Texture2D& reference, vector<deUint8>& pixelData, bool align, int pixelSize)
+{
+	de::Random					rnd(m_seed);
+	GLuint						framebuffer = 0;
+	GLuint						renderbuffer = 0;
+
+	if (m_format == GL_RGBA_INTEGER)
+	{
+		if (m_type == GL_UNSIGNED_INT)
+		{
+			GLU_CHECK_CALL(glGenRenderbuffers(1, &renderbuffer));
+			GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer));
+			GLU_CHECK_CALL(glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA32UI, m_width, m_height));
+		}
+		else if (m_type == GL_INT)
+		{
+			GLU_CHECK_CALL(glGenRenderbuffers(1, &renderbuffer));
+			GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer));
+			GLU_CHECK_CALL(glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA32I, m_width, m_height));
+		}
+		else
+			DE_ASSERT(false);
+
+		GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+		GLU_CHECK_CALL(glGenFramebuffers(1, &framebuffer));
+		GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
+		GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer));
+	}
+	else if (m_format == GL_RGBA || m_format == GL_BGRA || m_format == GL_RGB)
+	{
+		// Empty
+	}
+	else
+		DE_ASSERT(false);
+
+	GLU_CHECK_CALL(glViewport(0, 0, reference.getWidth(), reference.getHeight()));
+
+	// Clear color
+	if (m_format == GL_RGBA || m_format == GL_BGRA || m_format == GL_RGB)
+	{
+		const float red		= rnd.getFloat();
+		const float green	= rnd.getFloat();
+		const float blue	= rnd.getFloat();
+		const float alpha	= rnd.getFloat();
+
+		const GLfloat color[] = { red, green, blue, alpha };
+		// Clear target
+		GLU_CHECK_CALL(glClearColor(red, green, blue, alpha));
+		m_testCtx.getLog() << tcu::TestLog::Message << "ClearColor: (" << red << ", " << green << ", " << blue << ")" << tcu::TestLog::EndMessage;
+
+		GLU_CHECK_CALL(glClearBufferfv(GL_COLOR, 0, color));
+
+		// Clear reference
+		for (int x = 0; x < reference.getWidth(); x++)
+			for (int y = 0; y < reference.getHeight(); y++)
+					reference.getLevel(0).setPixel(tcu::UVec4((deUint32)(255.0f * red), (deUint32)(255.0f * green), (deUint32)(255.0f * blue), (deUint32)(255 * alpha)), x, y);
+	}
+	else if (m_format == GL_RGBA_INTEGER)
+	{
+		if (m_type == GL_INT)
+		{
+			const GLint red		= rnd.getUint32();
+			const GLint green	= rnd.getUint32();
+			const GLint blue	= rnd.getUint32();
+			const GLint alpha	= rnd.getUint32();
+
+			const GLint color[] = { red, green, blue, alpha };
+			m_testCtx.getLog() << tcu::TestLog::Message << "ClearColor: (" << red << ", " << green << ", " << blue << ")" << tcu::TestLog::EndMessage;
+
+			GLU_CHECK_CALL(glClearBufferiv(GL_COLOR, 0, color));
+
+			// Clear reference
+			for (int x = 0; x < reference.getWidth(); x++)
+				for (int y = 0; y < reference.getHeight(); y++)
+						reference.getLevel(0).setPixel(tcu::IVec4(red, green, blue, alpha), x, y);
+		}
+		else if (m_type == GL_UNSIGNED_INT)
+		{
+			const GLuint red	= rnd.getUint32();
+			const GLuint green	= rnd.getUint32();
+			const GLuint blue	= rnd.getUint32();
+			const GLuint alpha	= rnd.getUint32();
+
+			const GLuint color[] = { red, green, blue, alpha };
+			m_testCtx.getLog() << tcu::TestLog::Message << "ClearColor: (" << red << ", " << green << ", " << blue << ")" << tcu::TestLog::EndMessage;
+
+			GLU_CHECK_CALL(glClearBufferuiv(GL_COLOR, 0, color));
+
+			// Clear reference
+			for (int x = 0; x < reference.getWidth(); x++)
+				for (int y = 0; y < reference.getHeight(); y++)
+						reference.getLevel(0).setPixel(tcu::UVec4(red, green, blue, alpha), x, y);
+		}
+		else
+			DE_ASSERT(false);
+	}
+	else
+		DE_ASSERT(false);
+
+	render(reference);
+
+	const int rowWidth	= (m_rowLength == 0 ? m_width : m_rowLength) + m_skipPixels;
+	const int rowPitch	= (align ? m_alignment * deCeilFloatToInt32(pixelSize * rowWidth / (float)m_alignment) : rowWidth * pixelSize);
+
+	pixelData.resize(rowPitch * (m_height + m_skipRows), 0);
+
+	GLU_CHECK_CALL(glReadPixels(0, 0, m_width, m_height, m_format, m_type, &(pixelData[0])));
+
+	if (framebuffer)
+		GLU_CHECK_CALL(glDeleteFramebuffers(1, &framebuffer));
+
+	if (renderbuffer)
+		GLU_CHECK_CALL(glDeleteRenderbuffers(1, &renderbuffer));
+}
+
+TestCase::IterateResult ReadPixelsTest::iterate (void)
+{
+	tcu::TextureFormat			format(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);
+	int							pixelSize;
+	bool						align;
+
+	getFormatInfo(format, pixelSize, align);
+	m_testCtx.getLog() << tcu::TestLog::Message << "Format: " << glu::getPixelFormatStr(m_format) << ", Type: " << glu::getTypeStr(m_type) << tcu::TestLog::EndMessage;
+
+	tcu::Texture2D reference(format, m_width, m_height);
+	reference.allocLevel(0);
+
+	GLU_CHECK_CALL(glPixelStorei(GL_PACK_ALIGNMENT, m_alignment));
+	m_testCtx.getLog() << tcu::TestLog::Message << "GL_PACK_ALIGNMENT: " << m_alignment << tcu::TestLog::EndMessage;
+
+	GLU_CHECK_CALL(glPixelStorei(GL_PACK_ROW_LENGTH, m_rowLength));
+	m_testCtx.getLog() << tcu::TestLog::Message << "GL_PACK_ROW_LENGTH: " << m_rowLength << tcu::TestLog::EndMessage;
+
+	GLU_CHECK_CALL(glPixelStorei(GL_PACK_SKIP_ROWS, m_skipRows));
+	m_testCtx.getLog() << tcu::TestLog::Message << "GL_PACK_SKIP_ROWS: " << m_skipRows << tcu::TestLog::EndMessage;
+
+	GLU_CHECK_CALL(glPixelStorei(GL_PACK_SKIP_PIXELS, m_skipPixels));
+	m_testCtx.getLog() << tcu::TestLog::Message << "GL_PACK_SKIP_PIXELS: " << m_skipPixels << tcu::TestLog::EndMessage;
+
+	GLU_CHECK_CALL(glViewport(0, 0, m_width, m_height));
+
+	vector<deUint8>	pixelData;
+	clearColor(reference, pixelData, align, pixelSize);
+
+	const int rowWidth	= (m_rowLength == 0 ? m_width : m_rowLength);
+	const int rowPitch	= (align ? m_alignment * deCeilFloatToInt32(pixelSize * rowWidth / (float)m_alignment) : rowWidth * pixelSize);
+
+	// \note GL_RGBA_INTEGER uses always renderbuffers that are never multisampled. Otherwise default framebuffer is used.
+	if (m_format != GL_RGBA_INTEGER && m_context.getRenderTarget().getNumSamples() > 1)
+	{
+		const tcu::IVec4	formatBitDepths	= tcu::getTextureFormatBitDepth(format);
+		const deUint8		redThreshold	= (deUint8)deCeilFloatToInt32(256.0f * (2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().redBits,		formatBitDepths.x()))));
+		const deUint8		greenThreshold	= (deUint8)deCeilFloatToInt32(256.0f * (2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().greenBits,	formatBitDepths.y()))));
+		const deUint8		blueThreshold	= (deUint8)deCeilFloatToInt32(256.0f * (2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().blueBits,		formatBitDepths.z()))));
+		const deUint8		alphaThreshold	= (deUint8)deCeilFloatToInt32(256.0f * (2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().alphaBits,	formatBitDepths.w()))));
+
+		if (tcu::bilinearCompare(m_testCtx.getLog(), "Result", "Result", reference.getLevel(0), tcu::PixelBufferAccess(format, m_width, m_height, 1, rowPitch, 0, &(pixelData[0])), tcu::RGBA(redThreshold, greenThreshold, blueThreshold, alphaThreshold), tcu::COMPARE_LOG_RESULT))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	}
+	else
+	{
+		const tcu::IVec4	formatBitDepths	= tcu::getTextureFormatBitDepth(format);
+		const float			redThreshold	= 2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().redBits,	formatBitDepths.x()));
+		const float			greenThreshold	= 2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().greenBits,	formatBitDepths.y()));
+		const float			blueThreshold	= 2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().blueBits,	formatBitDepths.z()));
+		const float			alphaThreshold	= 2.0f / (1 << deMin32(m_context.getRenderTarget().getPixelFormat().alphaBits,	formatBitDepths.w()));
+
+		// Compare
+		if (tcu::floatThresholdCompare(m_testCtx.getLog(), "Result", "Result", reference.getLevel(0), tcu::PixelBufferAccess(format, m_width, m_height, 1, rowPitch, 0, &(pixelData[pixelSize * m_skipPixels + m_skipRows * rowPitch])), tcu::Vec4(redThreshold, greenThreshold, blueThreshold, alphaThreshold), tcu::COMPARE_LOG_RESULT))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	}
+
+	return STOP;
+}
+
+} // anonymous
+
+ReadPixelsTests::ReadPixelsTests (Context& context)
+	: TestCaseGroup(context, "read_pixels", "ReadPixel tests")
+{
+}
+
+void ReadPixelsTests::init (void)
+{
+	{
+		TestCaseGroup* group = new TestCaseGroup(m_context, "alignment", "Read pixels pack alignment parameter tests");
+
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_1", "", false, 1, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_2", "", false, 2, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_4", "", false, 4, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_8", "", false, 8, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE));
+
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_1", "", false, 1, 0, 0, 0, GL_RGBA_INTEGER, GL_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_2", "", false, 2, 0, 0, 0, GL_RGBA_INTEGER, GL_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_4", "", false, 4, 0, 0, 0, GL_RGBA_INTEGER, GL_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_8", "", false, 8, 0, 0, 0, GL_RGBA_INTEGER, GL_INT));
+
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_1", "", false, 1, 0, 0, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_2", "", false, 2, 0, 0, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_4", "", false, 4, 0, 0, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_8", "", false, 8, 0, 0, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+
+		group->addChild(new ReadPixelsTest(m_context, "choose_1", "", true, 1, 0, 0, 0));
+		group->addChild(new ReadPixelsTest(m_context, "choose_2", "", true, 2, 0, 0, 0));
+		group->addChild(new ReadPixelsTest(m_context, "choose_4", "", true, 4, 0, 0, 0));
+		group->addChild(new ReadPixelsTest(m_context, "choose_8", "", true, 8, 0, 0, 0));
+
+		addChild(group);
+	}
+
+	{
+		TestCaseGroup* group = new TestCaseGroup(m_context, "rowlength", "Read pixels rowlength test");
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_17", "", false, 4, 17, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_19", "", false, 4, 19, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_23", "", false, 4, 23, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_29", "", false, 4, 29, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE));
+
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_17", "", false, 4, 17, 0, 0, GL_RGBA_INTEGER, GL_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_19", "", false, 4, 19, 0, 0, GL_RGBA_INTEGER, GL_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_23", "", false, 4, 23, 0, 0, GL_RGBA_INTEGER, GL_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_29", "", false, 4, 29, 0, 0, GL_RGBA_INTEGER, GL_INT));
+
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_17", "", false, 4, 17, 0, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_19", "", false, 4, 19, 0, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_23", "", false, 4, 23, 0, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_29", "", false, 4, 29, 0, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+
+		group->addChild(new ReadPixelsTest(m_context, "choose_17", "", true, 4, 17, 0, 0));
+		group->addChild(new ReadPixelsTest(m_context, "choose_19", "", true, 4, 19, 0, 0));
+		group->addChild(new ReadPixelsTest(m_context, "choose_23", "", true, 4, 23, 0, 0));
+		group->addChild(new ReadPixelsTest(m_context, "choose_29", "", true, 4, 29, 0, 0));
+
+		addChild(group);
+	}
+
+	{
+		TestCaseGroup* group = new TestCaseGroup(m_context, "skip", "Read pixels skip pixels and rows test");
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_0_3", "", false, 4, 17, 0, 3, GL_RGBA, GL_UNSIGNED_BYTE));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_3_0", "", false, 4, 17, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_3_3", "", false, 4, 17, 3, 3, GL_RGBA, GL_UNSIGNED_BYTE));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_ubyte_3_5", "", false, 4, 17, 3, 5, GL_RGBA, GL_UNSIGNED_BYTE));
+
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_0_3", "", false, 4, 17, 0, 3, GL_RGBA_INTEGER, GL_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_3_0", "", false, 4, 17, 3, 0, GL_RGBA_INTEGER, GL_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_3_3", "", false, 4, 17, 3, 3, GL_RGBA_INTEGER, GL_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_int_3_5", "", false, 4, 17, 3, 5, GL_RGBA_INTEGER, GL_INT));
+
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_0_3", "", false, 4, 17, 0, 3, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_3_0", "", false, 4, 17, 3, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_3_3", "", false, 4, 17, 3, 3, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+		group->addChild(new ReadPixelsTest(m_context, "rgba_uint_3_5", "", false, 4, 17, 3, 5, GL_RGBA_INTEGER, GL_UNSIGNED_INT));
+
+		group->addChild(new ReadPixelsTest(m_context, "choose_0_3", "", true, 4, 17, 0, 3));
+		group->addChild(new ReadPixelsTest(m_context, "choose_3_0", "", true, 4, 17, 3, 0));
+		group->addChild(new ReadPixelsTest(m_context, "choose_3_3", "", true, 4, 17, 3, 3));
+		group->addChild(new ReadPixelsTest(m_context, "choose_3_5", "", true, 4, 17, 3, 5));
+
+		addChild(group);
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fReadPixelsTests.hpp b/modules/gles3/functional/es3fReadPixelsTests.hpp
new file mode 100644
index 0000000..bac1e27
--- /dev/null
+++ b/modules/gles3/functional/es3fReadPixelsTests.hpp
@@ -0,0 +1,47 @@
+#ifndef _ES3FREADPIXELSTESTS_HPP
+#define _ES3FREADPIXELSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Read pixels tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ReadPixelsTests : public TestCaseGroup
+{
+public:
+			ReadPixelsTests	(Context& context);
+	void	init			(void);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FREADPIXELSTESTS_HPP
diff --git a/modules/gles3/functional/es3fSamplerObjectTests.cpp b/modules/gles3/functional/es3fSamplerObjectTests.cpp
new file mode 100644
index 0000000..80fb938
--- /dev/null
+++ b/modules/gles3/functional/es3fSamplerObjectTests.cpp
@@ -0,0 +1,314 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sampler object tests.
+ *//*--------------------------------------------------------------------*/
+#include "es3fSamplerObjectTests.hpp"
+
+#include "glsSamplerObjectTest.hpp"
+
+#include "tcuTexture.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+
+#include "deRandom.hpp"
+#include "deString.h"
+
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+SamplerObjectTests::SamplerObjectTests (Context& context)
+	: TestCaseGroup(context, "samplers", "Texture sampler tests")
+{
+}
+
+SamplerObjectTests::~SamplerObjectTests (void)
+{
+}
+
+void SamplerObjectTests::init (void)
+{
+	gls::TextureSamplerTest::TestSpec simpleTestCases[] = {
+		{ "diff_wrap_t", "Different GL_TEXTURE_WRAP_T", GL_TEXTURE_2D,
+				{ GL_NEAREST, GL_NEAREST, GL_MIRRORED_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_s", "Different GL_TEXTURE_WRAP_S", GL_TEXTURE_2D,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_MIRRORED_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_r", "Different GL_TEXTURE_WRAP_R", GL_TEXTURE_2D,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_MIRRORED_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_min_filter", "Different GL_TEXTURE_MIN_FILTER", GL_TEXTURE_2D,
+				{ GL_LINEAR, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_mag_filter", "Different GL_TEXTURE_MAG_FILTER", GL_TEXTURE_2D,
+				{ GL_NEAREST, GL_LINEAR, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_max_lod", "Different GL_TEXTURE_MAX_LOD", GL_TEXTURE_2D,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, -999.0f }
+		},
+		{ "diff_min_lod", "Different GL_TEXTURE_MIN_LOD", GL_TEXTURE_2D,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 0.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 100.0f, 1000.0f }
+		}
+	};
+
+	TestCaseGroup* simpleTexture2D = new TestCaseGroup(m_context, "single_tex_2d", "Simple 2D texture with sampler");
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(simpleTestCases); testNdx++)
+		simpleTexture2D->addChild(new gls::TextureSamplerTest(m_testCtx, m_context.getRenderContext(), simpleTestCases[testNdx]));
+
+	addChild(simpleTexture2D);
+
+	gls::MultiTextureSamplerTest::TestSpec multiTestCases[] = {
+		{ "diff_wrap_t", "Different GL_TEXTURE_WRAP_T", GL_TEXTURE_2D,
+				{ GL_NEAREST, GL_NEAREST, GL_MIRRORED_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_MIRRORED_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_s", "Different GL_TEXTURE_WRAP_S", GL_TEXTURE_2D,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_MIRRORED_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_MIRRORED_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_r", "Different GL_TEXTURE_WRAP_R", GL_TEXTURE_2D,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_MIRRORED_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_MIRRORED_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_min_filter", "Different GL_TEXTURE_MIN_FILTER", GL_TEXTURE_2D,
+				{ GL_LINEAR, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_LINEAR, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_mag_filter", "Different GL_TEXTURE_MAG_FILTER", GL_TEXTURE_2D,
+				{ GL_NEAREST, GL_LINEAR, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_LINEAR, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_max_lod", "Different GL_TEXTURE_MAX_LOD", GL_TEXTURE_2D,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, -999.0f }
+		},
+		{ "diff_min_lod", "Different GL_TEXTURE_MIN_LOD", GL_TEXTURE_2D,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 0.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 0.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 100.0f, 1000.0f }
+		}
+	};
+
+	TestCaseGroup* multiTexture2D = new TestCaseGroup(m_context, "multi_tex_2d", "Multiple texture units 2D texture with sampler");
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(multiTestCases); testNdx++)
+		multiTexture2D->addChild(new gls::MultiTextureSamplerTest(m_testCtx, m_context.getRenderContext(), multiTestCases[testNdx]));
+
+	addChild(multiTexture2D);
+
+	gls::TextureSamplerTest::TestSpec simpleTestCases3D[] = {
+		{ "diff_wrap_t", "Different GL_TEXTURE_WRAP_T", GL_TEXTURE_3D,
+				{ GL_NEAREST, GL_NEAREST, GL_MIRRORED_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_s", "Different GL_TEXTURE_WRAP_S", GL_TEXTURE_3D,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_MIRRORED_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_r", "Different GL_TEXTURE_WRAP_R", GL_TEXTURE_3D,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_MIRRORED_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_min_filter", "Different GL_TEXTURE_MIN_FILTER", GL_TEXTURE_3D,
+				{ GL_LINEAR, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_mag_filter", "Different GL_TEXTURE_MAG_FILTER", GL_TEXTURE_3D,
+				{ GL_NEAREST, GL_LINEAR, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_max_lod", "Different GL_TEXTURE_MAX_LOD", GL_TEXTURE_3D,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, -999.0f }
+		},
+		{ "diff_min_lod", "Different GL_TEXTURE_MIN_LOD", GL_TEXTURE_3D,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 0.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 100.0f, 1000.0f }
+		}
+	};
+
+	TestCaseGroup* simpleTexture3D = new TestCaseGroup(m_context, "single_tex_3d", "Simple 3D texture with sampler");
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(simpleTestCases3D); testNdx++)
+		simpleTexture3D->addChild(new gls::TextureSamplerTest(m_testCtx, m_context.getRenderContext(), simpleTestCases3D[testNdx]));
+
+	addChild(simpleTexture3D);
+
+	gls::MultiTextureSamplerTest::TestSpec multiTestCases3D[] = {
+		{ "diff_wrap_t", "Different GL_TEXTURE_WRAP_T", GL_TEXTURE_3D,
+				{ GL_NEAREST, GL_NEAREST, GL_MIRRORED_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_MIRRORED_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_s", "Different GL_TEXTURE_WRAP_S", GL_TEXTURE_3D,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_MIRRORED_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_MIRRORED_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_r", "Different GL_TEXTURE_WRAP_R", GL_TEXTURE_3D,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_MIRRORED_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_MIRRORED_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_min_filter", "Different GL_TEXTURE_MIN_FILTER", GL_TEXTURE_3D,
+				{ GL_LINEAR, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_LINEAR, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_mag_filter", "Different GL_TEXTURE_MAG_FILTER", GL_TEXTURE_3D,
+				{ GL_NEAREST, GL_LINEAR, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_LINEAR, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_max_lod", "Different GL_TEXTURE_MAX_LOD", GL_TEXTURE_3D,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, -999.0f }
+		},
+		{ "diff_min_lod", "Different GL_TEXTURE_MIN_LOD", GL_TEXTURE_3D,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 0.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 0.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 100.0f, 1000.0f }
+		}
+	};
+
+	TestCaseGroup* multiTexture3D = new TestCaseGroup(m_context, "multi_tex_3d", "Multiple texture units 3D texture with sampler");
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(multiTestCases3D); testNdx++)
+		multiTexture3D->addChild(new gls::MultiTextureSamplerTest(m_testCtx, m_context.getRenderContext(), multiTestCases3D[testNdx]));
+
+	addChild(multiTexture3D);
+
+	gls::TextureSamplerTest::TestSpec simpleTestCasesCube[] = {
+		{ "diff_wrap_t", "Different GL_TEXTURE_WRAP_T", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST, GL_NEAREST, GL_MIRRORED_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_s", "Different GL_TEXTURE_WRAP_S", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_MIRRORED_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_r", "Different GL_TEXTURE_WRAP_R", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_MIRRORED_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_min_filter", "Different GL_TEXTURE_MIN_FILTER", GL_TEXTURE_CUBE_MAP,
+				{ GL_LINEAR, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_mag_filter", "Different GL_TEXTURE_MAG_FILTER", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST, GL_LINEAR, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_max_lod", "Different GL_TEXTURE_MAX_LOD", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, -999.0f }
+		},
+		{ "diff_min_lod", "Different GL_TEXTURE_MIN_LOD", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 0.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 100.0f, 1000.0f }
+		}
+	};
+
+	TestCaseGroup* simpleTextureCube = new TestCaseGroup(m_context, "single_cubemap", "Simple cubemap texture with sampler");
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(simpleTestCasesCube); testNdx++)
+		simpleTextureCube->addChild(new gls::TextureSamplerTest(m_testCtx, m_context.getRenderContext(), simpleTestCasesCube[testNdx]));
+
+	addChild(simpleTextureCube);
+
+	gls::MultiTextureSamplerTest::TestSpec multiTestCasesCube[] = {
+		{ "diff_wrap_t", "Different GL_TEXTURE_WRAP_T", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST, GL_NEAREST, GL_MIRRORED_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_MIRRORED_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_s", "Different GL_TEXTURE_WRAP_S", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_MIRRORED_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_MIRRORED_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_wrap_r", "Different GL_TEXTURE_WRAP_R", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_MIRRORED_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_MIRRORED_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_min_filter", "Different GL_TEXTURE_MIN_FILTER", GL_TEXTURE_CUBE_MAP,
+				{ GL_LINEAR, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_LINEAR, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_mag_filter", "Different GL_TEXTURE_MAG_FILTER", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST, GL_LINEAR, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_LINEAR, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f }
+		},
+		{ "diff_max_lod", "Different GL_TEXTURE_MAX_LOD", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, -1000.0f, -999.0f }
+		},
+		{ "diff_min_lod", "Different GL_TEXTURE_MIN_LOD", GL_TEXTURE_CUBE_MAP,
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 0.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 0.0f, 1000.0f },
+				{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT, GL_REPEAT, 100.0f, 1000.0f }
+		}
+	};
+
+	TestCaseGroup* multiTextureCube = new TestCaseGroup(m_context, "multi_cubemap", "Multiple texture units cubemap textures with sampler");
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(multiTestCasesCube); testNdx++)
+		multiTextureCube->addChild(new gls::MultiTextureSamplerTest(m_testCtx, m_context.getRenderContext(), multiTestCasesCube[testNdx]));
+
+	addChild(multiTextureCube);
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fSamplerObjectTests.hpp b/modules/gles3/functional/es3fSamplerObjectTests.hpp
new file mode 100644
index 0000000..06e83bd
--- /dev/null
+++ b/modules/gles3/functional/es3fSamplerObjectTests.hpp
@@ -0,0 +1,48 @@
+#ifndef _ES3FSAMPLEROBJECTTESTS_HPP
+#define _ES3FSAMPLEROBJECTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sampler object tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class SamplerObjectTests : public TestCaseGroup
+{
+public:
+					SamplerObjectTests	(Context& context);
+	virtual			~SamplerObjectTests	(void);
+	virtual void	init				(void);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSAMPLEROBJECTTESTS_HPP
diff --git a/modules/gles3/functional/es3fSamplerStateQueryTests.cpp b/modules/gles3/functional/es3fSamplerStateQueryTests.cpp
new file mode 100644
index 0000000..1f378c2
--- /dev/null
+++ b/modules/gles3/functional/es3fSamplerStateQueryTests.cpp
@@ -0,0 +1,521 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fSamplerStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+
+using namespace glw; // GLint and other GL types
+using namespace deqp::gls;
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+namespace SamplerParamVerifiers
+{
+
+class SamplerParamVerifier : protected glu::CallLogWrapper
+{
+public:
+						SamplerParamVerifier	(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~SamplerParamVerifier	(); // make GCC happy
+
+	const char*			getTestNamePostfix		(void) const;
+
+	virtual void		verifyInteger			(tcu::TestContext& testCtx, GLuint sampler, GLenum name, GLint reference)	= DE_NULL;
+	virtual void		verifyFloat				(tcu::TestContext& testCtx, GLuint sampler, GLenum name, GLfloat reference)	= DE_NULL;
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+SamplerParamVerifier::SamplerParamVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+SamplerParamVerifier::~SamplerParamVerifier ()
+{
+}
+
+const char* SamplerParamVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+class GetSamplerParameterIVerifier : public SamplerParamVerifier
+{
+public:
+			GetSamplerParameterIVerifier	(const glw::Functions& gl, tcu::TestLog& log);
+
+	void	verifyInteger							(tcu::TestContext& testCtx, GLuint sampler, GLenum name, GLint reference);
+	void	verifyFloat								(tcu::TestContext& testCtx, GLuint sampler, GLenum name, GLfloat reference);
+};
+
+GetSamplerParameterIVerifier::GetSamplerParameterIVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: SamplerParamVerifier(gl, log, "_getsamplerparameteri")
+{
+}
+
+void GetSamplerParameterIVerifier::verifyInteger (tcu::TestContext& testCtx, GLuint sampler, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetSamplerParameteriv(sampler, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid sampler param value");
+	}
+}
+
+void GetSamplerParameterIVerifier::verifyFloat (tcu::TestContext& testCtx, GLuint sampler, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetSamplerParameteriv(sampler, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint expectedGLStateMax = StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint>(reference);
+	const GLint expectedGLStateMin = StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint>(reference);
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected in range [" << expectedGLStateMin << ", " << expectedGLStateMax << "]; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got sampler texture param value");
+	}
+}
+
+class GetSamplerParameterFVerifier : public SamplerParamVerifier
+{
+public:
+			GetSamplerParameterFVerifier	(const glw::Functions& gl, tcu::TestLog& log);
+
+	void	verifyInteger							(tcu::TestContext& testCtx, GLuint sampler, GLenum name, GLint reference);
+	void	verifyFloat								(tcu::TestContext& testCtx, GLuint sampler, GLenum name, GLfloat reference);
+};
+
+GetSamplerParameterFVerifier::GetSamplerParameterFVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: SamplerParamVerifier(gl, log, "_getsamplerparameterf")
+{
+}
+
+void GetSamplerParameterFVerifier::verifyInteger (tcu::TestContext& testCtx, GLuint sampler, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	const GLfloat referenceAsFloat = GLfloat(reference);
+	DE_ASSERT(reference == GLint(referenceAsFloat)); // reference integer must have 1:1 mapping to float for this to work. Reference value is always such value in these tests
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetSamplerParameterfv(sampler, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != referenceAsFloat)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << referenceAsFloat << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetSamplerParameterFVerifier::verifyFloat (tcu::TestContext& testCtx, GLuint sampler, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetSamplerParameterfv(sampler, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+} // SamplerParamVerifiers
+
+namespace
+{
+
+using namespace SamplerParamVerifiers;
+
+// Tests
+
+class SamplerCase : public ApiCase
+{
+public:
+	SamplerCase (Context& context, SamplerParamVerifier* verifier, const char* name, const char* description)
+		: ApiCase			(context, name, description)
+		, m_sampler			(0)
+		, m_verifier		(verifier)
+	{
+	}
+
+	virtual void testSampler (void) = DE_NULL;
+
+	void test (void)
+	{
+		glGenSamplers(1, &m_sampler);
+		expectError(GL_NO_ERROR);
+
+		testSampler();
+
+		glDeleteTextures(1, &m_sampler);
+		expectError(GL_NO_ERROR);
+	}
+
+protected:
+	GLuint					m_sampler;
+	SamplerParamVerifier*	m_verifier;
+};
+
+class SamplerWrapCase : public SamplerCase
+{
+public:
+	SamplerWrapCase (Context& context, SamplerParamVerifier* verifier, const char* name, const char* description, GLenum valueName)
+		: SamplerCase	(context, verifier, name, description)
+		, m_valueName	(valueName)
+	{
+	}
+
+	void testSampler (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_sampler, m_valueName, GL_REPEAT);
+		expectError(GL_NO_ERROR);
+
+		const GLenum wrapValues[] = {GL_CLAMP_TO_EDGE, GL_REPEAT, GL_MIRRORED_REPEAT};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(wrapValues); ++ndx)
+		{
+			glSamplerParameteri(m_sampler, m_valueName, wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_sampler, m_valueName, wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		//check unit conversions with float
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(wrapValues); ++ndx)
+		{
+			glSamplerParameterf(m_sampler, m_valueName, (GLfloat)wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_sampler, m_valueName, wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	GLenum	m_valueName;
+};
+
+class SamplerMagFilterCase : public SamplerCase
+{
+public:
+	SamplerMagFilterCase (Context& context, SamplerParamVerifier* verifier, const char* name, const char* description)
+		: SamplerCase(context, verifier, name, description)
+	{
+	}
+
+	void testSampler (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		expectError(GL_NO_ERROR);
+
+		const GLenum magValues[] = {GL_NEAREST, GL_LINEAR};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(magValues); ++ndx)
+		{
+			glSamplerParameteri(m_sampler, GL_TEXTURE_MAG_FILTER, magValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_MAG_FILTER, magValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		//check unit conversions with float
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(magValues); ++ndx)
+		{
+			glSamplerParameterf(m_sampler, GL_TEXTURE_MAG_FILTER, (GLfloat)magValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_MAG_FILTER, magValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class SamplerMinFilterCase : public SamplerCase
+{
+public:
+	SamplerMinFilterCase (Context& context, SamplerParamVerifier* verifier, const char* name, const char* description)
+		: SamplerCase(context, verifier, name, description)
+	{
+	}
+
+	void testSampler (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
+		expectError(GL_NO_ERROR);
+
+		const GLenum minValues[] = {GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(minValues); ++ndx)
+		{
+			glSamplerParameteri(m_sampler, GL_TEXTURE_MIN_FILTER, minValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_MIN_FILTER, minValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		//check unit conversions with float
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(minValues); ++ndx)
+		{
+			glSamplerParameterf(m_sampler, GL_TEXTURE_MIN_FILTER, (GLfloat)minValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_MIN_FILTER, minValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class SamplerLODCase : public SamplerCase
+{
+public:
+	SamplerLODCase (Context& context, SamplerParamVerifier* verifier, const char* name, const char* description, GLenum lodTarget, int initialValue)
+		: SamplerCase		(context, verifier, name, description)
+		, m_lodTarget		(lodTarget)
+		, m_initialValue	(initialValue)
+	{
+	}
+
+	void testSampler (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyInteger(m_testCtx, m_sampler, m_lodTarget, m_initialValue);
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 60;
+		for (int ndx = 0; ndx < numIterations; ++ndx)
+		{
+			const GLfloat ref = rnd.getFloat(-64000, 64000);
+
+			glSamplerParameterf(m_sampler, m_lodTarget, ref);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyFloat(m_testCtx, m_sampler, m_lodTarget, ref);
+			expectError(GL_NO_ERROR);
+		}
+
+		// check unit conversions with int
+
+		for (int ndx = 0; ndx < numIterations; ++ndx)
+		{
+			const GLint ref = rnd.getInt(-64000, 64000);
+
+			glSamplerParameteri(m_sampler, m_lodTarget, ref);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyFloat(m_testCtx, m_sampler, m_lodTarget, (GLfloat)ref);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	GLenum	m_lodTarget;
+	int		m_initialValue;
+};
+
+class SamplerCompareModeCase : public SamplerCase
+{
+public:
+	SamplerCompareModeCase (Context& context, SamplerParamVerifier* verifier, const char* name, const char* description)
+		: SamplerCase(context, verifier, name, description)
+	{
+	}
+
+	void testSampler (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_COMPARE_MODE, GL_NONE);
+		expectError(GL_NO_ERROR);
+
+		const GLenum modes[] = {GL_COMPARE_REF_TO_TEXTURE, GL_NONE};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(modes); ++ndx)
+		{
+			glSamplerParameteri(m_sampler, GL_TEXTURE_COMPARE_MODE, modes[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_COMPARE_MODE, modes[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		// with float too
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(modes); ++ndx)
+		{
+			glSamplerParameterf(m_sampler, GL_TEXTURE_COMPARE_MODE, (GLfloat)modes[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_COMPARE_MODE, modes[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class SamplerCompareFuncCase : public SamplerCase
+{
+public:
+	SamplerCompareFuncCase (Context& context, SamplerParamVerifier* verifier, const char* name, const char* description)
+		: SamplerCase(context, verifier, name, description)
+	{
+	}
+
+	void testSampler (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
+		expectError(GL_NO_ERROR);
+
+		const GLenum compareFuncs[] = {GL_LEQUAL, GL_GEQUAL, GL_LESS, GL_GREATER, GL_EQUAL, GL_NOTEQUAL, GL_ALWAYS, GL_NEVER};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(compareFuncs); ++ndx)
+		{
+			glSamplerParameteri(m_sampler, GL_TEXTURE_COMPARE_FUNC, compareFuncs[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_COMPARE_FUNC, compareFuncs[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		// with float too
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(compareFuncs); ++ndx)
+		{
+			glSamplerParameterf(m_sampler, GL_TEXTURE_COMPARE_FUNC, (GLfloat)compareFuncs[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_sampler, GL_TEXTURE_COMPARE_FUNC, compareFuncs[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+
+
+} // anonymous
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		SamplerParamVerifier* verifier = VERIFIERS[_verifierNdx];								\
+		CODE_BLOCK;																				\
+	}
+
+SamplerStateQueryTests::SamplerStateQueryTests (Context& context)
+	: TestCaseGroup		(context, "sampler", "Sampler State Query tests")
+	, m_verifierInt		(DE_NULL)
+	, m_verifierFloat	(DE_NULL)
+{
+}
+
+SamplerStateQueryTests::~SamplerStateQueryTests (void)
+{
+	deinit();
+}
+
+void SamplerStateQueryTests::init (void)
+{
+	using namespace SamplerParamVerifiers;
+
+	DE_ASSERT(m_verifierInt == DE_NULL);
+	DE_ASSERT(m_verifierFloat == DE_NULL);
+
+	m_verifierInt		= new GetSamplerParameterIVerifier(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierFloat		= new GetSamplerParameterFVerifier(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	SamplerParamVerifier* verifiers[] = {m_verifierInt, m_verifierFloat};
+
+	FOR_EACH_VERIFIER(verifiers, addChild(new SamplerWrapCase		(m_context, verifier,	(std::string("sampler_texture_wrap_s")			+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_WRAP_S",			GL_TEXTURE_WRAP_S)));
+	FOR_EACH_VERIFIER(verifiers, addChild(new SamplerWrapCase		(m_context, verifier,	(std::string("sampler_texture_wrap_t")			+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_WRAP_T",			GL_TEXTURE_WRAP_T)));
+	FOR_EACH_VERIFIER(verifiers, addChild(new SamplerWrapCase		(m_context, verifier,	(std::string("sampler_texture_wrap_r")			+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_WRAP_R",			GL_TEXTURE_WRAP_R)));
+	FOR_EACH_VERIFIER(verifiers, addChild(new SamplerMagFilterCase	(m_context, verifier,	(std::string("sampler_texture_mag_filter")		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MAG_FILTER")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new SamplerMinFilterCase	(m_context, verifier,	(std::string("sampler_texture_min_filter")		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MIN_FILTER")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new SamplerLODCase		(m_context, verifier,	(std::string("sampler_texture_min_lod")			+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MIN_LOD",			GL_TEXTURE_MIN_LOD, -1000)));
+	FOR_EACH_VERIFIER(verifiers, addChild(new SamplerLODCase		(m_context, verifier,	(std::string("sampler_texture_max_lod")			+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MAX_LOD",			GL_TEXTURE_MAX_LOD,  1000)));
+	FOR_EACH_VERIFIER(verifiers, addChild(new SamplerCompareModeCase(m_context, verifier,	(std::string("sampler_texture_compare_mode")	+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_COMPARE_MODE")));
+	FOR_EACH_VERIFIER(verifiers, addChild(new SamplerCompareFuncCase(m_context, verifier,	(std::string("sampler_texture_compare_func")	+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_COMPARE_FUNC")));
+}
+
+void SamplerStateQueryTests::deinit (void)
+{
+	if (m_verifierInt)
+	{
+		delete m_verifierInt;
+		m_verifierInt = NULL;
+	}
+	if (m_verifierFloat)
+	{
+		delete m_verifierFloat;
+		m_verifierFloat = NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fSamplerStateQueryTests.hpp b/modules/gles3/functional/es3fSamplerStateQueryTests.hpp
new file mode 100644
index 0000000..cc59436
--- /dev/null
+++ b/modules/gles3/functional/es3fSamplerStateQueryTests.hpp
@@ -0,0 +1,64 @@
+#ifndef _ES3FSAMPLERSTATEQUERYTESTS_HPP
+#define _ES3FSAMPLERSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sampler State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace SamplerParamVerifiers
+{
+
+class GetSamplerParameterIVerifier;
+class GetSamplerParameterFVerifier;
+
+} // TextureParamVerifiers
+
+class SamplerStateQueryTests : public TestCaseGroup
+{
+public:
+																		SamplerStateQueryTests	(Context& context);
+																		~SamplerStateQueryTests	(void);
+
+	void																init					(void);
+	void																deinit					(void);
+
+private:
+																		SamplerStateQueryTests	(const SamplerStateQueryTests& other);
+	SamplerStateQueryTests&												operator=				(const SamplerStateQueryTests& other);
+
+	SamplerParamVerifiers::GetSamplerParameterIVerifier*				m_verifierInt;
+	SamplerParamVerifiers::GetSamplerParameterFVerifier*				m_verifierFloat;
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSAMPLERSTATEQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fScissorTests.cpp b/modules/gles3/functional/es3fScissorTests.cpp
new file mode 100644
index 0000000..3ef5bc6
--- /dev/null
+++ b/modules/gles3/functional/es3fScissorTests.cpp
@@ -0,0 +1,436 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES3 Scissor tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fScissorTests.hpp"
+
+#include "glsScissorTests.hpp"
+
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "sglrContextUtil.hpp"
+
+#include "tcuVector.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuImageCompare.hpp"
+
+#include "gluStrUtil.hpp"
+#include "gluDrawUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "deDefs.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+using tcu::ConstPixelBufferAccess;
+
+class FramebufferCase : public tcu::TestCase
+{
+public:
+									FramebufferCase			(glu::RenderContext& context, tcu::TestContext& testContext, const char* name, const char* description);
+	virtual							~FramebufferCase		(void) {}
+
+	virtual IterateResult			iterate					(void);
+
+protected:
+	// Must do its own 'readPixels', wrapper does not need to care about formats this way
+	virtual ConstPixelBufferAccess	render					(sglr::Context& context, std::vector<deUint8>& pixelBuffer) = DE_NULL;
+
+	glu::RenderContext&				m_renderContext;
+};
+
+FramebufferCase::FramebufferCase (glu::RenderContext& context, tcu::TestContext& testContext, const char* name, const char* description)
+	: tcu::TestCase		(testContext, name, description)
+	, m_renderContext	(context)
+{
+}
+
+FramebufferCase::IterateResult FramebufferCase::iterate (void)
+{
+	const tcu::Vec4				clearColor				(0.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::RenderTarget&	renderTarget			= m_renderContext.getRenderTarget();
+	const char*					failReason				= DE_NULL;
+
+	const int					width					= 64;
+	const int					height					= 64;
+
+	tcu::TestLog&				log						= m_testCtx.getLog();
+	glu::RenderContext&			renderCtx				= m_renderContext;
+
+	tcu::ConstPixelBufferAccess glesAccess;
+	tcu::ConstPixelBufferAccess refAccess;
+
+	std::vector<deUint8>		glesFrame;
+	std::vector<deUint8>		refFrame;
+	deUint32					glesError;
+
+	{
+		// Render using GLES
+		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(0, 0, width, height));
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		glesAccess = render(context, glesFrame); // Call actual render func
+		glesError = context.getError();
+	}
+
+	// Render reference image
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), renderTarget.getDepthBits(), renderTarget.getStencilBits(), width, height);
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		refAccess = render(context, refFrame);
+		DE_ASSERT(context.getError() == GL_NO_ERROR);
+	}
+
+	if (glesError != GL_NO_ERROR)
+	{
+		log << tcu::TestLog::Message << "Unexpected error: got " << glu::getErrorStr(glesError) << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected error");
+	}
+	else
+	{
+		// Compare images
+		const tcu::Vec4		threshold	(0.02f, 0.02f, 0.02f, 0.02f);
+		const bool			imagesOk	= tcu::floatThresholdCompare(log, "ComparisonResult", "Image comparison result", refAccess, glesAccess, threshold, tcu::COMPARE_LOG_RESULT);
+
+		if (!imagesOk && !failReason)
+			failReason = "Image comparison failed";
+
+		// Store test result
+		m_testCtx.setTestResult(imagesOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								imagesOk ? "Pass"				: failReason);
+	}
+
+	return STOP;
+}
+
+class FramebufferClearCase : public FramebufferCase
+{
+public:
+	enum ClearType
+	{
+		COLOR_FIXED = 0,
+		COLOR_FLOAT,
+		COLOR_INT,
+		COLOR_UINT,
+		DEPTH,
+		STENCIL,
+		DEPTH_STENCIL,
+
+		CLEAR_TYPE_LAST
+	};
+
+									FramebufferClearCase	(glu::RenderContext& context, tcu::TestContext& testContext, ClearType clearType, const char* name, const char* description);
+	virtual							~FramebufferClearCase	(void) {}
+
+	tcu::ConstPixelBufferAccess		render					(sglr::Context& context, std::vector<deUint8>& pixelBuffer);
+
+private:
+	const ClearType					m_clearType;
+};
+
+FramebufferClearCase::FramebufferClearCase (glu::RenderContext& context, tcu::TestContext& testContext, ClearType clearType, const char* name, const char* description)
+	: FramebufferCase	(context, testContext, name, description)
+	, m_clearType		(clearType)
+{
+}
+
+ConstPixelBufferAccess FramebufferClearCase::render (sglr::Context& context, std::vector<deUint8>& pixelBuffer)
+{
+	using gls::Functional::ScissorTestShader;
+
+	ScissorTestShader	shader;
+	const deUint32		shaderID			= context.createProgram(&shader);
+
+	const int			width				= 64;
+	const int			height				= 64;
+
+	const tcu::Vec4		clearColor			(1.0f, 1.0f, 0.5f, 1.0f);
+	const tcu::IVec4	clearInt			(127, -127, 0, 127);
+	const tcu::UVec4	clearUint			(255, 255, 0, 255);
+
+	const tcu::Vec4		baseColor			(0.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::IVec4	baseIntColor		(0, 0, 0, 0);
+	const tcu::UVec4	baseUintColor		(0, 0, 0, 0);
+
+	const int			clearStencil		= 123;
+	const float			clearDepth			= 1.0f;
+
+	deUint32			framebuf, colorbuf, dsbuf;
+
+	deUint32			colorBufferFormat	= GL_RGBA8;
+	deUint32			readFormat			= GL_RGBA;
+	deUint32			readType			= GL_UNSIGNED_BYTE;
+	tcu::TextureFormat	textureFormat		(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);
+
+	context.genFramebuffers(1, &framebuf);
+	context.bindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuf);
+
+	switch (m_clearType)
+	{
+		case COLOR_FLOAT:
+			colorBufferFormat	= GL_RGBA16F;
+			textureFormat		= tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::HALF_FLOAT);
+			DE_ASSERT(!"Floating point clear not implemented");// \todo [2014-1-23 otto] pixel read format & type, nothing guaranteed, need extension...
+			break;
+
+		case COLOR_INT:
+			colorBufferFormat	= GL_RGBA8I;
+			readFormat			= GL_RGBA_INTEGER;
+			readType			= GL_INT;
+			textureFormat		= tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::SIGNED_INT32);
+			pixelBuffer.resize(width*height*4*4);
+			break;
+
+		case COLOR_UINT:
+			colorBufferFormat	= GL_RGBA8UI;
+			readFormat			= GL_RGBA_INTEGER;
+			readType			= GL_UNSIGNED_INT;
+			textureFormat		= tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNSIGNED_INT32);
+			pixelBuffer.resize(width*height*4*4);
+			break;
+
+		default:
+			pixelBuffer.resize(width*height*1*4);
+			break;
+	}
+
+	// Color
+	context.genRenderbuffers(1, &colorbuf);
+	context.bindRenderbuffer(GL_RENDERBUFFER, colorbuf);
+	context.renderbufferStorage(GL_RENDERBUFFER, colorBufferFormat, width, height);
+	context.framebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuf);
+
+	// Depth/stencil
+	context.genRenderbuffers(1, &dsbuf);
+	context.bindRenderbuffer(GL_RENDERBUFFER, dsbuf);
+	context.renderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
+	context.framebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, dsbuf);
+
+	context.clearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f-clearDepth, ~clearStencil);
+	switch (m_clearType)
+	{
+		case COLOR_INT:		context.clearBufferiv(GL_COLOR, 0, baseIntColor.getPtr());		break;
+		case COLOR_UINT:	context.clearBufferuiv(GL_COLOR, 0, baseUintColor.getPtr());	break;
+		default:			context.clearBufferfv(GL_COLOR, 0, baseColor.getPtr());
+	}
+
+	context.enable(GL_SCISSOR_TEST);
+	context.scissor(8, 8, 48, 48);
+
+	switch (m_clearType)
+	{
+		case COLOR_FIXED:		context.clearBufferfv(GL_COLOR, 0, clearColor.getPtr());				break;
+		case COLOR_FLOAT:		context.clearBufferfv(GL_COLOR, 0, clearColor.getPtr());				break;
+		case COLOR_INT:			context.clearBufferiv(GL_COLOR, 0, clearInt.getPtr());					break;
+		case COLOR_UINT:		context.clearBufferuiv(GL_COLOR, 0, clearUint.getPtr());				break;
+		case DEPTH:				context.clearBufferfv(GL_DEPTH, 0, &clearDepth);						break;
+		case STENCIL:			context.clearBufferiv(GL_STENCIL, 0, &clearStencil);					break;
+		case DEPTH_STENCIL:		context.clearBufferfi(GL_DEPTH_STENCIL, 0, clearDepth, clearStencil);	break;
+
+		default:				DE_ASSERT(false);														break;
+	}
+
+	context.disable(GL_SCISSOR_TEST);
+
+	const bool useDepth		= (m_clearType==DEPTH   || m_clearType==DEPTH_STENCIL);
+	const bool useStencil	= (m_clearType==STENCIL || m_clearType==DEPTH_STENCIL);
+
+	if (useDepth)
+		context.enable(GL_DEPTH_TEST);
+
+	if (useStencil)
+	{
+		context.enable(GL_STENCIL_TEST);
+		context.stencilFunc(GL_EQUAL, clearStencil, ~0u);
+	}
+
+	if (useDepth || useStencil)
+	{
+		shader.setColor(context, shaderID, clearColor);
+		sglr::drawQuad(context, shaderID, tcu::Vec3(-1.0f, -1.0f, 0.2f), tcu::Vec3(1.0f, 1.0f, 0.2f));
+	}
+
+	context.bindFramebuffer(GL_READ_FRAMEBUFFER, framebuf);
+	context.readPixels(0, 0, width, height, readFormat, readType, &pixelBuffer[0]);
+
+	context.deleteFramebuffers(1, &framebuf);
+	context.deleteRenderbuffers(1, &colorbuf);
+	context.deleteRenderbuffers(1, &dsbuf);
+
+	return tcu::PixelBufferAccess(textureFormat, width, height, 1, &pixelBuffer[0]);
+}
+
+class FramebufferBlitCase : public gls::Functional::ScissorCase
+{
+public:
+							FramebufferBlitCase		(glu::RenderContext& context, tcu::TestContext& testContext, const tcu::Vec4& scissorArea, const char* name, const char* description);
+	virtual					~FramebufferBlitCase	(void) {}
+
+	virtual void			init					(void);
+
+protected:
+	virtual void			render					(sglr::Context& context, const tcu::IVec4& viewport);
+};
+
+FramebufferBlitCase::FramebufferBlitCase (glu::RenderContext& context, tcu::TestContext& testContext, const tcu::Vec4& scissorArea, const char* name, const char* description):
+	ScissorCase(context, testContext, scissorArea, name, description)
+{
+}
+
+void FramebufferBlitCase::init (void)
+{
+	if (m_renderContext.getRenderTarget().getNumSamples())
+		throw tcu::NotSupportedError("Cannot blit to multisampled render buffer", "", __FILE__, __LINE__);
+}
+
+void FramebufferBlitCase::render (sglr::Context& context, const tcu::IVec4& viewport)
+{
+	deUint32			framebuf;
+	deUint32			colorbuf;
+
+	const int			fboWidth			= 64;
+	const int			fboHeight			= 64;
+	const tcu::Vec4		clearColor			(1.0f, 1.0f, 0.5f, 1.0f);
+	const int			width				= viewport.z();
+	const int			height				= viewport.w();
+	const tcu::IVec4	scissorArea			(int(m_scissorArea.x()*width) + viewport.x(),
+											 int(m_scissorArea.y()*height) + viewport.y(),
+											 int(m_scissorArea.z()*width),
+											 int(m_scissorArea.w()*height));
+	const deInt32		defaultFramebuffer	= m_renderContext.getDefaultFramebuffer();
+
+	context.genFramebuffers(1, &framebuf);
+	context.bindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuf);
+
+	context.genRenderbuffers(1, &colorbuf);
+	context.bindRenderbuffer(GL_RENDERBUFFER, colorbuf);
+	context.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, fboWidth, fboHeight);
+	context.framebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuf);
+
+	context.clearBufferfv(GL_COLOR, 0, clearColor.getPtr());
+
+	context.enable(GL_SCISSOR_TEST);
+	context.scissor(scissorArea.x(), scissorArea.y(), scissorArea.z(), scissorArea.w());
+
+	// blit to default framebuffer
+	context.bindFramebuffer(GL_READ_FRAMEBUFFER, framebuf);
+	context.bindFramebuffer(GL_DRAW_FRAMEBUFFER, defaultFramebuffer);
+
+	context.blitFramebuffer(0, 0, fboWidth, fboHeight, viewport.x(), viewport.y(), viewport.x() + width, viewport.y() + height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+	context.bindFramebuffer(GL_READ_FRAMEBUFFER, defaultFramebuffer);
+
+	context.disable(GL_SCISSOR_TEST);
+
+	context.deleteFramebuffers(1, &framebuf);
+	context.deleteRenderbuffers(1, &colorbuf);
+}
+
+} // Anonymous
+
+ScissorTests::ScissorTests (Context& context):
+	TestCaseGroup	(context, "scissor", "Scissor Tests")
+{
+}
+
+ScissorTests::~ScissorTests (void)
+{
+}
+
+void ScissorTests::init (void)
+{
+	using tcu::Vec4;
+	typedef gls::Functional::ScissorCase SC;
+
+	glu::RenderContext&		rc = m_context.getRenderContext();
+	tcu::TestContext&		tc = m_context.getTestContext();
+
+	static const struct
+	{
+		const char*				name;
+		const char*				description;
+		const tcu::Vec4			scissor;
+		const tcu::Vec4			render;
+		const SC::PrimitiveType	type;
+		const int				primitives;
+	} cases[] =
+	{
+		{ "contained_quads",		"Triangles fully inside scissor area (single call)",		Vec4(0.1f, 0.1f, 0.8f, 0.8f), Vec4(0.2f, 0.2f, 0.6f, 0.6f), SC::TRIANGLE,	30 },
+		{ "partial_quads",			"Triangles partially inside scissor area (single call)",	Vec4(0.3f, 0.3f, 0.4f, 0.4f), Vec4(0.2f, 0.2f, 0.6f, 0.6f), SC::TRIANGLE,	30 },
+		{ "contained_tri",			"Triangle fully inside scissor area",						Vec4(0.1f, 0.1f, 0.8f, 0.8f), Vec4(0.2f, 0.2f, 0.6f, 0.6f), SC::TRIANGLE,	1  },
+		{ "enclosing_tri",			"Triangle fully covering scissor area",						Vec4(0.4f, 0.4f, 0.2f, 0.2f), Vec4(0.2f, 0.2f, 0.6f, 0.6f), SC::TRIANGLE,	1  },
+		{ "partial_tri",			"Triangle partially inside scissor area",					Vec4(0.4f, 0.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 1.0f, 1.0f), SC::TRIANGLE,	1  },
+		{ "outside_render_tri",		"Triangle with scissor area outside render target",			Vec4(1.4f, 1.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 0.6f, 0.6f), SC::TRIANGLE,	1  },
+		{ "partial_lines",			"Linse partially inside scissor area",						Vec4(0.4f, 0.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 1.0f, 1.0f), SC::LINE,		30 },
+		{ "contained_line",			"Line fully inside scissor area",							Vec4(0.1f, 0.1f, 0.8f, 0.8f), Vec4(0.2f, 0.2f, 0.6f, 0.6f), SC::LINE,		1  },
+		{ "partial_line",			"Line partially inside scissor area",						Vec4(0.4f, 0.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 1.0f, 1.0f), SC::LINE,		1  },
+		{ "outside_render_line",	"Line with scissor area outside render target",				Vec4(1.4f, 1.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 0.6f, 0.6f), SC::LINE,		1  },
+		{ "contained_point",		"Point fully inside scissor area",							Vec4(0.1f, 0.1f, 0.8f, 0.8f), Vec4(0.5f, 0.5f, 0.0f, 0.0f), SC::POINT,		1  },
+		{ "partial_points",			"Points partially inside scissor area",						Vec4(0.4f, 0.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 1.0f, 1.0f), SC::POINT,		30 },
+		{ "outside_point",			"Point fully outside scissor area",							Vec4(0.4f, 0.4f, 0.6f, 0.6f), Vec4(0.0f, 0.0f, 0.0f, 0.0f), SC::POINT,		1  },
+		{ "outside_render_point",	"Point with scissor area outside render target",			Vec4(1.4f, 1.4f, 0.6f, 0.6f), Vec4(0.5f, 0.5f, 0.0f, 0.0f),	SC::POINT,		1  }
+	};
+
+	for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+	{
+		addChild(SC::createPrimitiveTest(rc,
+										 tc,
+										 cases[caseNdx].scissor,
+										 cases[caseNdx].render,
+										 cases[caseNdx].type,
+										 cases[caseNdx].primitives,
+										 cases[caseNdx].name,
+										 cases[caseNdx].description));
+	}
+
+	addChild(SC::createClearTest(rc, tc, Vec4(0.1f, 0.1f, 0.8f, 0.8f), GL_DEPTH_BUFFER_BIT,		"clear_depth",		"Depth buffer clear"));
+	addChild(SC::createClearTest(rc, tc, Vec4(0.1f, 0.1f, 0.8f, 0.8f), GL_STENCIL_BUFFER_BIT,	"clear_stencil",	"Stencil buffer clear"));
+	addChild(SC::createClearTest(rc, tc, Vec4(0.1f, 0.1f, 0.8f, 0.8f), GL_COLOR_BUFFER_BIT,		"clear_color",		"Color buffer clear"));
+
+	addChild(new FramebufferClearCase(rc, tc, FramebufferClearCase::COLOR_FIXED,	"clear_fixed_buffer",			"Fixed point color clear"));
+	addChild(new FramebufferClearCase(rc, tc, FramebufferClearCase::COLOR_INT,		"clear_int_buffer",				"Integer color clear"));
+	addChild(new FramebufferClearCase(rc, tc, FramebufferClearCase::COLOR_UINT,		"clear_uint_buffer",			"Unsigned integer buffer clear"));
+	addChild(new FramebufferClearCase(rc, tc, FramebufferClearCase::DEPTH,			"clear_depth_buffer",			"Depth buffer clear"));
+	addChild(new FramebufferClearCase(rc, tc, FramebufferClearCase::STENCIL,		"clear_stencil_buffer",			"Stencil buffer clear"));
+	addChild(new FramebufferClearCase(rc, tc, FramebufferClearCase::DEPTH_STENCIL,	"clear_depth_stencil_buffer",	"Fixed point color buffer clear"));
+
+	addChild(new FramebufferBlitCase(rc, tc, Vec4(0.1f, 0.1f, 0.8f, 0.8f), "framebuffer_blit_center",	"Blit to default framebuffer, scissor away edges"));
+	addChild(new FramebufferBlitCase(rc, tc, Vec4(0.6f, 0.6f, 0.5f, 0.5f), "framebuffer_blit_corner",	"Blit to default framebuffer, scissor all but a corner"));
+	addChild(new FramebufferBlitCase(rc, tc, Vec4(1.6f, 0.6f, 0.5f, 0.5f), "framebuffer_blit_none",		"Blit to default framebuffer, scissor area outside screen"));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fScissorTests.hpp b/modules/gles3/functional/es3fScissorTests.hpp
new file mode 100644
index 0000000..3678c14
--- /dev/null
+++ b/modules/gles3/functional/es3fScissorTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FSCISSORTESTS_HPP
+#define _ES3FSCISSORTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES3 Scissor tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ScissorTests : public TestCaseGroup
+{
+public:
+						ScissorTests		(Context& context);
+	virtual				~ScissorTests		(void);
+
+	void				init				(void);
+
+private:
+						ScissorTests		(const ScissorTests& other);
+	ScissorTests&		operator=			(const ScissorTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSCISSORTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderApiTests.cpp b/modules/gles3/functional/es3fShaderApiTests.cpp
new file mode 100644
index 0000000..be25964
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderApiTests.cpp
@@ -0,0 +1,1763 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderApiTests.hpp"
+#include "es3fApiCase.hpp"
+#include "tcuTestLog.hpp"
+
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "gluCallLogWrapper.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+#include "deString.h"
+
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+#include <string>
+#include <sstream>
+#include <vector>
+#include <map>
+
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+
+namespace
+{
+
+enum ShaderSourceCaseFlags
+{
+	CASE_EXPLICIT_SOURCE_LENGTHS	= 1,
+	CASE_RANDOM_NULL_TERMINATED		= 2
+};
+
+struct ShaderSources
+{
+	std::vector<std::string>	strings;
+	std::vector<int>			lengths;
+};
+
+// Simple shaders
+
+const char* getSimpleShaderSource (const glu::ShaderType shaderType)
+{
+	const char* simpleVertexShaderSource =
+		"#version 300 es\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = vec4(0.0);\n"
+		"}\n";
+
+	const char* simpleFragmentShaderSource =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_fragColor;\n"
+		"void main (void)\n"
+		"{\n"
+		"	o_fragColor = vec4(0.0);\n"
+		"}\n";
+
+	switch (shaderType)
+	{
+		case glu::SHADERTYPE_VERTEX:
+			return simpleVertexShaderSource;
+		case glu::SHADERTYPE_FRAGMENT:
+			return simpleFragmentShaderSource;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return 0;
+}
+
+void setShaderSources (glu::Shader& shader, const ShaderSources& sources)
+{
+	std::vector<const char*> cStrings (sources.strings.size(), 0);
+
+	for (size_t ndx = 0; ndx < sources.strings.size(); ndx++)
+		cStrings[ndx] = sources.strings[ndx].c_str();
+
+	if (sources.lengths.size() > 0)
+		shader.setSources((int)cStrings.size(), &cStrings[0], &sources.lengths[0]);
+	else
+		shader.setSources((int)cStrings.size(), &cStrings[0], 0);
+}
+
+void sliceSourceString (const std::string& in, ShaderSources& out, const int numSlices, const size_t paddingLength = 0)
+{
+	DE_ASSERT(numSlices > 0);
+
+	const size_t		sliceSize			= in.length() / numSlices;
+	const size_t		sliceSizeRemainder	= in.length() - (sliceSize * numSlices);
+	const std::string	padding				(paddingLength, 'E');
+
+	for (int ndx = 0; ndx < numSlices; ndx++)
+	{
+		out.strings.push_back(in.substr(ndx * sliceSize, sliceSize) + padding);
+
+		if (paddingLength > 0)
+			out.lengths.push_back((int)sliceSize);
+	}
+
+	if (sliceSizeRemainder > 0)
+	{
+		const std::string	lastString			= in.substr(numSlices * sliceSize);
+		const int			lastStringLength	= (int)lastString.length();
+
+		out.strings.push_back(lastString + padding);
+
+		if (paddingLength > 0)
+			out.lengths.push_back(lastStringLength);
+	}
+}
+
+void queryShaderInfo (glu::RenderContext& renderCtx, deUint32 shader, glu::ShaderInfo& info)
+{
+	const glw::Functions& gl = renderCtx.getFunctions();
+
+	info.compileOk		= false;
+	info.compileTimeUs	= 0;
+	info.infoLog.clear();
+
+	// Query source, status & log.
+	{
+		int	compileStatus	= 0;
+		int sourceLen		= 0;
+		int	infoLogLen		= 0;
+		int	unusedLen;
+
+		gl.getShaderiv(shader, GL_COMPILE_STATUS,			&compileStatus);
+		gl.getShaderiv(shader, GL_SHADER_SOURCE_LENGTH,	&sourceLen);
+		gl.getShaderiv(shader, GL_INFO_LOG_LENGTH,		&infoLogLen);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv()");
+
+		info.compileOk = compileStatus != GL_FALSE;
+
+		if (sourceLen > 0)
+		{
+			std::vector<char> source(sourceLen);
+			gl.getShaderSource(shader, (int)source.size(), &unusedLen, &source[0]);
+			info.source = std::string(&source[0], sourceLen);
+		}
+
+		if (infoLogLen > 0)
+		{
+			std::vector<char> infoLog(infoLogLen);
+			gl.getShaderInfoLog(shader, (int)infoLog.size(), &unusedLen, &infoLog[0]);
+			info.infoLog = std::string(&infoLog[0], infoLogLen);
+		}
+	}
+}
+
+// Draw test quad
+
+void drawWithProgram (glu::RenderContext& renderCtx, deUint32 program)
+{
+	const glw::Functions& gl = renderCtx.getFunctions();
+
+	const float position[] =
+	{
+		-1.0f, -1.0f,  0.0f, 1.0f,
+		-1.0f, +1.0f,  0.0f, 1.0f,
+		+1.0f, -1.0f,  0.0f, 1.0f,
+		+1.0f, +1.0f,  0.0f, 1.0f
+	};
+	const deUint16 quadIndices[] = { 0, 1, 2, 2, 1, 3 };
+
+	gl.useProgram(program);
+
+	{
+		glu::VertexArrayBinding vertexArrays[] =
+		{
+			glu::va::Float("a_position",	4, 4, 0, &position[0])
+		};
+		glu::draw(renderCtx, program, DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0], glu::pr::Triangles(DE_LENGTH_OF_ARRAY(quadIndices), &quadIndices[0]));
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Draw test quad");
+}
+
+// Shader source generator
+
+class SourceGenerator
+{
+public:
+	virtual				~SourceGenerator	(void)	{}
+
+	virtual std::string	next				(const glu::ShaderType shaderType)			= 0;
+	virtual bool		finished			(const glu::ShaderType shaderType) const	= 0;
+};
+
+class ConstantShaderGenerator : public SourceGenerator
+{
+public:
+				ConstantShaderGenerator		(de::Random& rnd)	: m_rnd(rnd)	{}
+				~ConstantShaderGenerator	(void)								{}
+
+	bool		finished					(const glu::ShaderType shaderType) const	{ DE_UNREF(shaderType); return false; }
+
+	std::string	next						(const glu::ShaderType shaderType);
+
+private:
+	de::Random	m_rnd;
+};
+
+std::string ConstantShaderGenerator::next (const glu::ShaderType shaderType)
+{
+	DE_ASSERT(shaderType == glu::SHADERTYPE_VERTEX || shaderType == glu::SHADERTYPE_FRAGMENT);
+
+	const float			value		= m_rnd.getFloat(0.0f, 1.0f);
+	const std::string	valueString	= de::toString(value);
+	const std::string	outputName	= (shaderType == glu::SHADERTYPE_VERTEX) ? "gl_Position" : "o_fragColor";
+
+	std::ostringstream	out;
+
+	out << "#version 300 es\n";
+
+	if (shaderType == glu::SHADERTYPE_FRAGMENT)
+		out << "layout(location = 0) out mediump vec4 o_fragColor;\n";
+
+	out << "void main (void)\n";
+	out << "{\n";
+	out << "	" << outputName << " = vec4(" << valueString << ");\n";
+	out << "}\n";
+
+	return out.str();
+}
+
+// Shader allocation utility
+
+class ShaderAllocator
+{
+public:
+					ShaderAllocator		(glu::RenderContext& context, SourceGenerator& generator);
+					~ShaderAllocator	(void);
+
+	bool			hasShader			(const glu::ShaderType shaderType);
+
+	void			setSource			(const glu::ShaderType shaderType);
+
+	glu::Shader&	createShader		(const glu::ShaderType shaderType);
+	void			deleteShader		(const glu::ShaderType shaderType);
+
+	glu::Shader&	get					(const glu::ShaderType shaderType)	{ DE_ASSERT(hasShader(shaderType)); return *m_shaders[shaderType]; }
+
+private:
+	const glu::RenderContext&				m_context;
+	SourceGenerator&						m_srcGen;
+	std::map<glu::ShaderType, glu::Shader*>	m_shaders;
+};
+
+ShaderAllocator::ShaderAllocator (glu::RenderContext& context, SourceGenerator& generator)
+	: m_context	(context)
+	, m_srcGen	(generator)
+{
+}
+
+ShaderAllocator::~ShaderAllocator (void)
+{
+	for (std::map<glu::ShaderType, glu::Shader*>::iterator shaderIter = m_shaders.begin(); shaderIter != m_shaders.end(); shaderIter++)
+		delete shaderIter->second;
+	m_shaders.clear();
+}
+
+bool ShaderAllocator::hasShader (const glu::ShaderType shaderType)
+{
+	if (m_shaders.find(shaderType) != m_shaders.end())
+		return true;
+	else
+		return false;
+}
+
+glu::Shader& ShaderAllocator::createShader (const glu::ShaderType shaderType)
+{
+	DE_ASSERT(!this->hasShader(shaderType));
+
+	glu::Shader* const	shader	= new glu::Shader(m_context, shaderType);
+
+	m_shaders[shaderType] = shader;
+	this->setSource(shaderType);
+
+	return *shader;
+}
+
+void ShaderAllocator::deleteShader (const glu::ShaderType shaderType)
+{
+	DE_ASSERT(this->hasShader(shaderType));
+
+	delete m_shaders[shaderType];
+	m_shaders.erase(shaderType);
+}
+
+void ShaderAllocator::setSource (const glu::ShaderType shaderType)
+{
+	DE_ASSERT(this->hasShader(shaderType));
+	DE_ASSERT(!m_srcGen.finished(shaderType));
+
+	const std::string	source	= m_srcGen.next(shaderType);
+	const char* const	cSource	= source.c_str();
+
+	m_shaders[shaderType]->setSources(1, &cSource, 0);
+}
+
+// Logging utilities
+
+void logShader (TestLog& log, glu::RenderContext& renderCtx, glu::Shader& shader)
+{
+	glu::ShaderInfo info;
+
+	queryShaderInfo(renderCtx, shader.getShader(), info);
+
+	log << TestLog::Shader(getLogShaderType(shader.getType()), info.source, info.compileOk, info.infoLog);
+}
+
+void logProgram (TestLog& log, glu::RenderContext& renderCtx, glu::Program& program, ShaderAllocator& shaders)
+{
+	log << TestLog::ShaderProgram(program.getLinkStatus(), program.getInfoLog());
+
+	for (int shaderTypeInt = 0; shaderTypeInt < glu::SHADERTYPE_LAST; shaderTypeInt++)
+	{
+		const glu::ShaderType shaderType = (glu::ShaderType)shaderTypeInt;
+
+		if (shaders.hasShader(shaderType))
+			logShader(log, renderCtx, shaders.get(shaderType));
+	}
+
+	log << TestLog::EndShaderProgram;
+}
+
+void logVertexFragmentProgram (TestLog& log, glu::RenderContext& renderCtx, glu::Program& program, glu::Shader& vertShader, glu::Shader& fragShader)
+{
+	DE_ASSERT(vertShader.getType() == glu::SHADERTYPE_VERTEX && fragShader.getType() == glu::SHADERTYPE_FRAGMENT);
+
+	log << TestLog::ShaderProgram(program.getLinkStatus(), program.getInfoLog());
+
+	logShader(log, renderCtx, vertShader);
+	logShader(log, renderCtx, fragShader);
+
+	log << TestLog::EndShaderProgram;
+}
+
+} // anonymous
+
+// Simple glCreateShader() case
+
+class CreateShaderCase : public ApiCase
+{
+public:
+	CreateShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ApiCase		(context, name, desc)
+		, m_shaderType	(shaderType)
+	{
+	}
+
+	void test (void)
+	{
+		const GLuint shaderObject = glCreateShader(glu::getGLShaderType(m_shaderType));
+
+		TCU_CHECK(shaderObject != 0);
+
+		glDeleteShader(shaderObject);
+	}
+
+private:
+	const glu::ShaderType m_shaderType;
+};
+
+// Simple glCompileShader() case
+
+class CompileShaderCase : public ApiCase
+{
+public:
+	CompileShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ApiCase		(context, name, desc)
+		, m_shaderType	(shaderType)
+	{
+	}
+
+	bool checkCompileStatus (const GLuint shaderObject)
+	{
+		GLint compileStatus = -1;
+		glGetShaderiv(shaderObject, GL_COMPILE_STATUS, &compileStatus);
+		GLU_CHECK();
+
+		return (compileStatus == GL_TRUE);
+	}
+
+	void test (void)
+	{
+		const char*		shaderSource	= getSimpleShaderSource(m_shaderType);
+		const GLuint	shaderObject	= glCreateShader(glu::getGLShaderType(m_shaderType));
+
+		TCU_CHECK(shaderObject != 0);
+
+		glShaderSource(shaderObject, 1, &shaderSource, 0);
+		glCompileShader(shaderObject);
+
+		TCU_CHECK(checkCompileStatus(shaderObject));
+
+		glDeleteShader(shaderObject);
+	}
+
+private:
+	const glu::ShaderType m_shaderType;
+};
+
+// Base class for simple program API tests
+
+class SimpleProgramCase : public ApiCase
+{
+public:
+	SimpleProgramCase (Context& context, const char* name, const char* desc)
+		: ApiCase 		(context, name, desc)
+		, m_vertShader	(0)
+		, m_fragShader	(0)
+		, m_program		(0)
+	{
+	}
+
+	virtual ~SimpleProgramCase (void)
+	{
+	}
+
+	virtual void compileShaders (void)
+	{
+		const char*		vertSource	= getSimpleShaderSource(glu::SHADERTYPE_VERTEX);
+		const char*		fragSource	= getSimpleShaderSource(glu::SHADERTYPE_FRAGMENT);
+
+		const GLuint	vertShader	= glCreateShader(GL_VERTEX_SHADER);
+		const GLuint	fragShader	= glCreateShader(GL_FRAGMENT_SHADER);
+
+		TCU_CHECK(vertShader != 0);
+		TCU_CHECK(fragShader != 0);
+
+		glShaderSource(vertShader, 1, &vertSource, 0);
+		glCompileShader(vertShader);
+
+		glShaderSource(fragShader, 1, &fragSource, 0);
+		glCompileShader(fragShader);
+
+		GLU_CHECK();
+
+		m_vertShader = vertShader;
+		m_fragShader = fragShader;
+	}
+
+	void linkProgram (void)
+	{
+		const GLuint program = glCreateProgram();
+
+		TCU_CHECK(program != 0);
+
+		glAttachShader(program, m_vertShader);
+		glAttachShader(program, m_fragShader);
+		GLU_CHECK();
+
+		glLinkProgram(program);
+
+		m_program = program;
+	}
+
+	void cleanup (void)
+	{
+		glDeleteShader(m_vertShader);
+		glDeleteShader(m_fragShader);
+		glDeleteProgram(m_program);
+	}
+
+protected:
+	GLuint	m_vertShader;
+	GLuint	m_fragShader;
+	GLuint	m_program;
+};
+
+// glDeleteShader() case
+
+class DeleteShaderCase : public SimpleProgramCase
+{
+public:
+	DeleteShaderCase (Context& context, const char* name, const char* desc)
+		: SimpleProgramCase (context, name, desc)
+	{
+	}
+
+	bool checkDeleteStatus(GLuint shader)
+	{
+		GLint deleteStatus = -1;
+		glGetShaderiv(shader, GL_DELETE_STATUS, &deleteStatus);
+		GLU_CHECK();
+
+		return (deleteStatus == GL_TRUE);
+	}
+
+	void deleteShaders (void)
+	{
+		glDeleteShader(m_vertShader);
+		glDeleteShader(m_fragShader);
+		GLU_CHECK();
+	}
+
+	void test (void)
+	{
+		compileShaders();
+		linkProgram();
+		GLU_CHECK();
+
+		deleteShaders();
+
+		TCU_CHECK(checkDeleteStatus(m_vertShader) && checkDeleteStatus(m_fragShader));
+
+		glDeleteProgram(m_program);
+
+		TCU_CHECK(!(glIsShader(m_vertShader) || glIsShader(m_fragShader)));
+	}
+};
+
+// Simple glLinkProgram() case
+
+class LinkVertexFragmentCase : public SimpleProgramCase
+{
+public:
+	LinkVertexFragmentCase (Context& context, const char* name, const char* desc)
+		: SimpleProgramCase (context, name, desc)
+	{
+	}
+
+	bool checkLinkStatus (const GLuint programObject)
+	{
+		GLint linkStatus = -1;
+		glGetProgramiv(programObject, GL_LINK_STATUS, &linkStatus);
+		GLU_CHECK();
+
+		return (linkStatus == GL_TRUE);
+	}
+
+	void test (void)
+	{
+		compileShaders();
+		linkProgram();
+
+		GLU_CHECK_MSG("Linking failed.");
+		TCU_CHECK_MSG(checkLinkStatus(m_program), "Fail, expected LINK_STATUS to be TRUE.");
+
+		cleanup();
+	}
+};
+
+class ShaderSourceReplaceCase : public ApiCase
+{
+public:
+	ShaderSourceReplaceCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ApiCase		(context, name, desc)
+		, m_shaderType	(shaderType)
+	{
+	}
+
+	std::string generateFirstSource (void)
+	{
+		return getSimpleShaderSource(m_shaderType);
+	}
+
+	std::string generateSecondSource (void)
+	{
+		std::ostringstream out;
+
+		out << "#version 300 es\n";
+		out << "precision mediump float;\n";
+
+		if (m_shaderType == glu::SHADERTYPE_FRAGMENT)
+			out << "layout(location = 0) out mediump vec4 o_fragColor;\n";
+
+		out << "void main()\n";
+		out << "{\n";
+		out << "	float variable = 1.0f;\n";
+
+		if		(m_shaderType == glu::SHADERTYPE_VERTEX)	out << "	gl_Position = vec4(variable);\n";
+		else if	(m_shaderType == glu::SHADERTYPE_FRAGMENT)	out << "	o_fragColor = vec4(variable);\n";
+
+		out << "}\n";
+
+		return out.str();
+	}
+
+	GLint getSourceLength (glu::Shader& shader)
+	{
+		GLint sourceLength = 0;
+		glGetShaderiv(shader.getShader(), GL_SHADER_SOURCE_LENGTH, &sourceLength);
+		GLU_CHECK();
+
+		return sourceLength;
+	}
+
+	std::string readSource (glu::Shader& shader)
+	{
+		const GLint			sourceLength	= getSourceLength(shader);
+		std::vector<char>	sourceBuffer	(sourceLength + 1);
+
+		glGetShaderSource(shader.getShader(), (GLsizei)sourceBuffer.size(), 0, &sourceBuffer[0]);
+
+		return std::string(&sourceBuffer[0]);
+	}
+
+	void verifyShaderSourceReplaced (glu::Shader& shader, const std::string& firstSource, const std::string& secondSource)
+	{
+		TestLog&			log		= m_testCtx.getLog();
+		const std::string	result	= readSource(shader);
+
+		if (result == firstSource)
+		{
+			log << TestLog::Message << "Fail, source was not replaced." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader source nor replaced");
+		}
+		else if (result != secondSource)
+		{
+			log << TestLog::Message << "Fail, invalid shader source." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid source");
+		}
+	}
+
+	void test (void)
+	{
+		TestLog&			log				= m_testCtx.getLog();
+
+		glu::Shader			shader			(m_context.getRenderContext(), m_shaderType);
+
+		const std::string	firstSourceStr	= generateFirstSource();
+		const std::string	secondSourceStr	= generateSecondSource();
+
+		const char*			firstSource		= firstSourceStr.c_str();
+		const char*			secondSource	= secondSourceStr.c_str();
+
+		log << TestLog::Message << "Setting shader source." << TestLog::EndMessage;
+
+		shader.setSources(1, &firstSource, 0);
+		GLU_CHECK();
+
+		log << TestLog::Message << "Replacing shader source." << TestLog::EndMessage;
+
+		shader.setSources(1, &secondSource, 0);
+		GLU_CHECK();
+
+		verifyShaderSourceReplaced(shader, firstSourceStr, secondSourceStr);
+	}
+
+private:
+	glu::ShaderType	m_shaderType;
+};
+
+// glShaderSource() split source case
+
+class ShaderSourceSplitCase : public ApiCase
+{
+public:
+	ShaderSourceSplitCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType, const int numSlices, const deUint32 flags = 0)
+		: ApiCase			(context, name, desc)
+		, m_rnd				(deStringHash(getName()) ^ 0x4fb2337d)
+		, m_shaderType		(shaderType)
+		, m_numSlices		(numSlices)
+		, m_explicitLengths	((flags & CASE_EXPLICIT_SOURCE_LENGTHS)	!= 0)
+		, m_randomNullTerm	((flags & CASE_RANDOM_NULL_TERMINATED)	!= 0)
+	{
+		DE_ASSERT(m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT);
+	}
+
+	virtual ~ShaderSourceSplitCase (void)
+	{
+	}
+
+	std::string generateFullSource (void)
+	{
+		std::ostringstream out;
+
+		out << "#version 300 es\n";
+		out << "precision mediump float;\n";
+
+		if (m_shaderType == glu::SHADERTYPE_FRAGMENT)
+			out << "layout(location = 0) out mediump vec4 o_fragColor;\n";
+
+		out << "void main()\n";
+		out << "{\n";
+		out << "	float variable = 1.0f;\n";
+
+		if		(m_shaderType == glu::SHADERTYPE_VERTEX)	out << "	gl_Position = vec4(variable);\n";
+		else if	(m_shaderType == glu::SHADERTYPE_FRAGMENT)	out << "	o_fragColor = vec4(variable);\n";
+
+		out << "}\n";
+
+		return out.str();
+	}
+
+	void insertRandomNullTermStrings (ShaderSources& sources)
+	{
+		const int			numInserts	= de::max(m_numSlices >> 2, 1);
+		std::vector<int>	indices		(sources.strings.size(), 0);
+
+		DE_ASSERT(sources.lengths.size() > 0);
+		DE_ASSERT(sources.lengths.size() == sources.strings.size());
+
+		for (int i = 0; i < (int)sources.strings.size(); i++)
+			indices[i] = i;
+
+		m_rnd.shuffle(indices.begin(), indices.end());
+
+		for (int i = 0; i < numInserts; i++)
+		{
+			const int			ndx				= indices[i];
+			const int			unpaddedLength	= sources.lengths[ndx];
+			const std::string	unpaddedString	= sources.strings[ndx].substr(0, unpaddedLength);
+
+			sources.strings[ndx] = unpaddedString;
+			sources.lengths[ndx] = m_rnd.getInt(-10, -1);
+		}
+	}
+
+	void generateSources (ShaderSources& sources)
+	{
+		const size_t	paddingLength	= (m_explicitLengths ? 10 : 0);
+		std::string		str				= generateFullSource();
+
+		sliceSourceString(str, sources, m_numSlices, paddingLength);
+
+		if (m_randomNullTerm)
+			insertRandomNullTermStrings(sources);
+	}
+
+	void buildProgram (glu::Shader& shader)
+	{
+		TestLog&				log					= m_testCtx.getLog();
+		glu::RenderContext&		renderCtx			= m_context.getRenderContext();
+
+		const glu::ShaderType	supportShaderType	= (m_shaderType == glu::SHADERTYPE_FRAGMENT ? glu::SHADERTYPE_VERTEX : glu::SHADERTYPE_FRAGMENT);
+		const char*				supportShaderSource	= getSimpleShaderSource(supportShaderType);
+		glu::Shader				supportShader		(renderCtx, supportShaderType);
+
+		glu::Program			program				(renderCtx);
+
+		supportShader.setSources(1, &supportShaderSource, 0);
+		supportShader.compile();
+
+		program.attachShader(shader.getShader());
+		program.attachShader(supportShader.getShader());
+
+		program.link();
+
+		if (m_shaderType == glu::SHADERTYPE_VERTEX)
+			logVertexFragmentProgram(log, renderCtx, program, shader, supportShader);
+		else
+			logVertexFragmentProgram(log, renderCtx, program, supportShader, shader);
+	}
+
+	void test (void)
+	{
+		TestLog&			log			= m_testCtx.getLog();
+		glu::RenderContext&	renderCtx	= m_context.getRenderContext();
+
+		ShaderSources		sources;
+		glu::Shader			shader		(renderCtx, m_shaderType);
+
+		generateSources(sources);
+		setShaderSources(shader, sources);
+		shader.compile();
+
+		buildProgram(shader);
+
+		if (!shader.getCompileStatus())
+		{
+			log << TestLog::Message << "Compilation failed." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		}
+	}
+
+private:
+	de::Random				m_rnd;
+
+	const glu::ShaderType	m_shaderType;
+	const int				m_numSlices;
+
+	const bool				m_explicitLengths;
+	const bool				m_randomNullTerm;
+};
+
+// Base class for program state persistence cases
+
+class ProgramStateCase : public ApiCase
+{
+public:
+					ProgramStateCase	(Context& context, const char* name, const char* desc, glu::ShaderType shaderType);
+	virtual			~ProgramStateCase	(void)	{}
+
+	void			buildProgram		(glu::Program& program, ShaderAllocator& shaders);
+	void			verify				(glu::Program& program, const glu::ProgramInfo& reference);
+
+	void			test				(void);
+
+	virtual void	executeForProgram	(glu::Program& program, ShaderAllocator& shaders)	= 0;
+
+protected:
+	de::Random					m_rnd;
+	const glu::ShaderType		m_shaderType;
+};
+
+ProgramStateCase::ProgramStateCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+	: ApiCase		(context, name, desc)
+	, m_rnd			(deStringHash(name) ^ 0x713de0ca)
+	, m_shaderType	(shaderType)
+{
+	DE_ASSERT(m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT);
+}
+
+void ProgramStateCase::buildProgram (glu::Program& program, ShaderAllocator& shaders)
+{
+	TestLog&		log			= m_testCtx.getLog();
+
+	glu::Shader&	vertShader	= shaders.createShader(glu::SHADERTYPE_VERTEX);
+	glu::Shader&	fragShader	= shaders.createShader(glu::SHADERTYPE_FRAGMENT);
+
+	vertShader.compile();
+	fragShader.compile();
+
+	program.attachShader(vertShader.getShader());
+	program.attachShader(fragShader.getShader());
+	program.link();
+
+	logProgram(log, m_context.getRenderContext(), program, shaders);
+}
+
+void ProgramStateCase::verify (glu::Program& program, const glu::ProgramInfo& reference)
+{
+	TestLog&				log			= m_testCtx.getLog();
+	const glu::ProgramInfo&	programInfo	= program.getInfo();
+
+	if (!programInfo.linkOk)
+	{
+		log << TestLog::Message << "Fail, link status may only change as a result of linking or loading a program binary." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Link status changed");
+	}
+
+	if (programInfo.linkTimeUs != reference.linkTimeUs)
+	{
+		log << TestLog::Message << "Fail, reported link time changed." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Link time changed");
+	}
+
+	if (programInfo.infoLog != reference.infoLog)
+	{
+		log << TestLog::Message << "Fail, program infolog changed." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Infolog changed");
+	}
+}
+
+void ProgramStateCase::test (void)
+{
+	TestLog&				log			= m_testCtx.getLog();
+	glu::RenderContext&		renderCtx	= m_context.getRenderContext();
+
+	ConstantShaderGenerator	sourceGen	(m_rnd);
+
+	ShaderAllocator			shaders		(renderCtx, sourceGen);
+	glu::Program			program		(renderCtx);
+
+	buildProgram(program, shaders);
+
+	if (program.getLinkStatus())
+	{
+		glu::ProgramInfo programInfo = program.getInfo();
+
+		executeForProgram(program, shaders);
+
+		verify(program, programInfo);
+
+		logProgram(log, renderCtx, program, shaders);
+	}
+	else
+	{
+		log << TestLog::Message << "Fail, couldn't link program." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Linking failed");
+	}
+}
+
+// Program state case utilities
+
+namespace
+{
+
+template<class T>
+void addProgramStateCase (TestCaseGroup* group, Context& context, const std::string& name, const std::string& desc)
+{
+	for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
+	{
+		const glu::ShaderType	shaderType		= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
+		const std::string		shaderTypeName	= getShaderTypeName(shaderType);
+
+		const std::string		caseName		= name + "_" + shaderTypeName;
+		const std::string		caseDesc		= "Build program, " + desc + ", for " + shaderTypeName + " shader.";
+
+		group->addChild(new T(context, caseName.c_str(), caseDesc.c_str(), shaderType));
+	}
+}
+
+} // anonymous
+
+// Specialized program state cases
+
+class ProgramStateDetachShaderCase : public ProgramStateCase
+{
+public:
+	ProgramStateDetachShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateDetachShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Detaching " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+	}
+};
+
+class ProgramStateReattachShaderCase : public ProgramStateCase
+{
+public:
+	ProgramStateReattachShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateReattachShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Reattaching " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+		program.attachShader(caseShader.getShader());
+	}
+};
+
+class ProgramStateDeleteShaderCase : public ProgramStateCase
+{
+public:
+	ProgramStateDeleteShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateDeleteShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Deleting " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+		shaders.deleteShader(m_shaderType);
+	}
+};
+
+class ProgramStateReplaceShaderCase : public ProgramStateCase
+{
+public:
+	ProgramStateReplaceShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateReplaceShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Deleting and replacing " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+		shaders.deleteShader(m_shaderType);
+		program.attachShader(shaders.createShader(m_shaderType).getShader());
+	}
+};
+
+class ProgramStateRecompileShaderCase : public ProgramStateCase
+{
+public:
+	ProgramStateRecompileShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateRecompileShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Recompiling " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		caseShader.compile();
+		DE_UNREF(program);
+	}
+};
+
+class ProgramStateReplaceSourceCase : public ProgramStateCase
+{
+public:
+	ProgramStateReplaceSourceCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramStateCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramStateReplaceSourceCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Replacing " + std::string(getShaderTypeName(m_shaderType)) + " shader source and recompiling" << TestLog::EndMessage;
+		shaders.setSource(m_shaderType);
+		caseShader.compile();
+		DE_UNREF(program);
+	}
+};
+
+// Program binary utilities
+
+namespace
+{
+
+struct ProgramBinary
+{
+	GLenum					format;
+	std::vector<deUint8>	data;
+};
+
+bool programBinariesEqual (const ProgramBinary& first, const ProgramBinary& second)
+{
+	if ((first.format != second.format) || (first.data.size() != second.data.size()))
+		return false;
+
+	return std::equal(first.data.begin(), first.data.end(), second.data.begin());
+}
+
+} // anonymous
+
+// Base class for program binary cases
+
+class ProgramBinaryCase : public TestCase, protected glu::CallLogWrapper
+{
+public:
+							ProgramBinaryCase	(Context& context, const char* name, const char* desc);
+	virtual					~ProgramBinaryCase	(void);
+
+	void					getBinaryFormats	(std::vector<GLenum>& out);
+	bool					isFormatSupported	(const glw::GLenum format) const;
+
+	void					getProgramBinary	(ProgramBinary& out, GLuint program);
+	void					loadProgramBinary	(ProgramBinary& binary, GLuint program);
+
+	void					verifyProgramBinary	(ProgramBinary& binary);
+
+	void					init				(void);
+	IterateResult			iterate				(void);
+
+	virtual void			test				(void) = 0;
+
+protected:
+	std::vector<GLenum>		m_formats;
+};
+
+ProgramBinaryCase::ProgramBinaryCase (Context& context, const char* name, const char* desc)
+		: TestCase			(context, name, desc)
+		, CallLogWrapper	(context.getRenderContext().getFunctions(), context.getTestContext().getLog())
+{
+}
+
+ProgramBinaryCase::~ProgramBinaryCase (void)
+{
+}
+
+void ProgramBinaryCase::getBinaryFormats (std::vector<GLenum>& out)
+{
+	GLint numFormats = -1;
+	glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &numFormats);
+
+	out.clear();
+
+	if (numFormats > 0)
+	{
+		out.resize(numFormats, 0);
+
+		glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, (GLint*)&out[0]);
+	}
+}
+
+bool ProgramBinaryCase::isFormatSupported (const glw::GLenum format) const
+{
+	return (std::find(m_formats.begin(), m_formats.end(), format) != m_formats.end());
+}
+
+void ProgramBinaryCase::getProgramBinary (ProgramBinary& out, GLuint program)
+{
+	GLint binaryLength = -1;
+	glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binaryLength);
+
+	if (binaryLength > 0)
+	{
+		GLsizei	actualLength;
+		GLenum	format;
+
+		out.data.clear();
+		out.data.resize(binaryLength, 0);
+
+		GLU_CHECK_CALL(glGetProgramBinary(program, (GLsizei)out.data.size(), &actualLength, &format, &(out.data[0])));
+
+		TCU_CHECK(actualLength == binaryLength);
+
+		out.format = format;
+	}
+}
+
+void ProgramBinaryCase::loadProgramBinary (ProgramBinary& binary, GLuint program)
+{
+	glProgramBinary(program, binary.format, &binary.data[0], (GLsizei)binary.data.size());
+	GLU_CHECK_MSG("Failed to load program binary.");
+}
+
+void ProgramBinaryCase::verifyProgramBinary (ProgramBinary& binary)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (!isFormatSupported(binary.format))
+	{
+		log << TestLog::Message << "Program binary format " << binary.format << " is not among the supported formats reported by the platform." << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid format");
+	}
+}
+
+void ProgramBinaryCase::init (void)
+{
+	getBinaryFormats(m_formats);
+}
+
+tcu::TestNode::IterateResult ProgramBinaryCase::iterate (void)
+{
+	TestLog&	log	= m_testCtx.getLog();
+
+	if (m_formats.empty())
+	{
+		log << TestLog::Message << "No program binary formats are supported." << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not supported");
+	}
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		enableLogging(true);
+		test();
+	}
+
+	return STOP;
+}
+
+// Simple program binary case
+
+class ProgramBinarySimpleCase : public ProgramBinaryCase
+{
+public:
+	ProgramBinarySimpleCase (Context& context, const char* name, const char* desc)
+		: ProgramBinaryCase(context, name, desc)
+	{
+	}
+
+	virtual ~ProgramBinarySimpleCase (void)
+	{
+	}
+
+	void test (void)
+	{
+		const std::string			vertSrc	= getSimpleShaderSource(glu::SHADERTYPE_VERTEX);
+		const std::string			fragSrc	= getSimpleShaderSource(glu::SHADERTYPE_FRAGMENT);
+
+		const glu::ProgramSources	sources	= glu::makeVtxFragSources(vertSrc, fragSrc);
+
+		glu::ShaderProgram			program	(m_context.getRenderContext(), sources);
+
+		if (program.isOk())
+		{
+			ProgramBinary binary;
+
+			getProgramBinary(binary, program.getProgram());
+			verifyProgramBinary(binary);
+		}
+	}
+};
+
+// Program binary uniform reset case
+
+class ProgramBinaryUniformResetCase : public ProgramBinaryCase
+{
+public:
+	ProgramBinaryUniformResetCase (Context& context, const char* name, const char* desc)
+		: ProgramBinaryCase	(context, name, desc)
+		, m_rnd				(deStringHash(name) ^ 0xf2b48c6a)
+	{
+	}
+
+	virtual ~ProgramBinaryUniformResetCase (void)
+	{
+	}
+
+	std::string getShaderSource (const glu::ShaderType shaderType) const
+	{
+		const char* vertSrc =
+			"#version 300 es\n"
+			"uniform bool u_boolVar;\n"
+			"uniform highp int u_intVar;\n"
+			"uniform highp float u_floatVar;\n\n"
+			"in highp vec4 a_position;\n\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n";
+		const char* fragSrc =
+			"#version 300 es\n"
+			"uniform bool u_boolVar;\n"
+			"uniform highp int u_intVar;\n"
+			"uniform highp float u_floatVar;\n\n"
+			"layout(location = 0) out mediump vec4 o_fragColor;\n\n"
+			"void main (void)\n"
+			"{\n"
+			"	mediump float refAll = float(u_boolVar) + float(u_intVar) + u_floatVar;\n"
+			"	o_fragColor = vec4(refAll);\n"
+			"}\n";
+
+		DE_ASSERT(shaderType == glu::SHADERTYPE_VERTEX || shaderType == glu::SHADERTYPE_FRAGMENT);
+
+		return (shaderType == glu::SHADERTYPE_VERTEX) ? vertSrc : fragSrc;
+	}
+
+	void setUniformsRandom (glu::ShaderProgram& program)
+	{
+		TestLog&		log		= m_testCtx.getLog();
+		const deUint32	glProg	= program.getProgram();
+
+		log << TestLog::Message << "Setting uniforms to random non-zero values." << TestLog::EndMessage;
+
+		glUseProgram(glProg);
+
+		{
+			const GLint		boolLoc		= glGetUniformLocation(glProg, "u_boolVar");
+			const GLint		intLoc		= glGetUniformLocation(glProg, "u_intVar");
+			const GLint		floatLoc	= glGetUniformLocation(glProg, "u_floatVar");
+
+			const deInt32	intVal		= m_rnd.getInt(1, 1000);
+			const float		floatVal	= m_rnd.getFloat(1.0, 1000.0);
+
+			glUniform1i(boolLoc,	GL_TRUE);
+			glUniform1f(floatLoc,	floatVal);
+			glUniform1i(intLoc,		intVal);
+		}
+	}
+
+	void verifyUniformInt (glu::ShaderProgram& program, const std::string& name)
+	{
+		const GLint		intLoc	= glGetUniformLocation(program.getProgram(), name.c_str());
+		GLint			intVar	= -1;
+
+		glGetUniformiv(program.getProgram(), intLoc, &intVar);
+
+		if (intVar != 0)
+		{
+			m_testCtx.getLog() << TestLog::Message << "Fail, expected zero value for " << name << ", received: " << intVar << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Uniform value not reset");
+		}
+	}
+
+	void verifyUniformFloat (glu::ShaderProgram& program, const std::string& name)
+	{
+		const GLint	floatLoc	= glGetUniformLocation(program.getProgram(), name.c_str());
+		GLfloat		floatVar	= -1;
+
+		glGetUniformfv(program.getProgram(), floatLoc, &floatVar);
+
+		if (floatVar != 0.0f)
+		{
+			m_testCtx.getLog() << TestLog::Message << "Fail, expected zero value for " << name << ", received: " << de::toString(floatVar) << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Uniform value not reset");
+		}
+	}
+
+	void verifyUniformsReset (glu::ShaderProgram& program)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Verifying uniform reset to 0/false." << TestLog::EndMessage;
+
+		verifyUniformInt	(program,	"u_boolVar");
+		verifyUniformInt	(program,	"u_intVar");
+		verifyUniformFloat	(program,	"u_floatVar");
+	}
+
+	void test (void)
+	{
+		TestLog&					log		= m_testCtx.getLog();
+
+		const std::string			vertSrc	= getShaderSource(glu::SHADERTYPE_VERTEX);
+		const std::string			fragSrc	= getShaderSource(glu::SHADERTYPE_FRAGMENT);
+
+		const glu::ProgramSources	sources	= glu::makeVtxFragSources(vertSrc, fragSrc);
+
+		glu::ShaderProgram			program	(m_context.getRenderContext(), sources);
+
+		log << program;
+
+		TCU_CHECK_MSG(program.isOk(), "Couldn't build program");
+
+		{
+			ProgramBinary binary;
+
+			getProgramBinary(binary, program.getProgram());
+			verifyProgramBinary(binary);
+
+			setUniformsRandom(program);
+
+			log << TestLog::Message << "Rendering test image and reloading binary" << TestLog::EndMessage;
+
+			drawWithProgram(m_context.getRenderContext(), program.getProgram());
+			loadProgramBinary(binary, program.getProgram());
+
+			verifyUniformsReset(program);
+		}
+	}
+private:
+	de::Random	m_rnd;
+};
+
+// Base class for program state persistence cases
+
+class ProgramBinaryPersistenceCase : public ProgramBinaryCase
+{
+public:
+					ProgramBinaryPersistenceCase	(Context& context, const char* name, const char* desc, glu::ShaderType shaderType);
+	virtual			~ProgramBinaryPersistenceCase	(void) {}
+
+	void			buildProgram					(glu::Program& program, ShaderAllocator& shaders);
+
+	void			test							(void);
+
+	virtual void	executeForProgram				(glu::Program& program, ShaderAllocator& shaders)	= 0;
+	virtual void	verify							(glu::Program& program, const ProgramBinary& binary);
+
+protected:
+	de::Random				m_rnd;
+	const glu::ShaderType	m_shaderType;
+};
+
+ProgramBinaryPersistenceCase::ProgramBinaryPersistenceCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+	: ProgramBinaryCase	(context, name, desc)
+	, m_rnd				(deStringHash(name) ^ 0x713de0ca)
+	, m_shaderType		(shaderType)
+{
+	DE_ASSERT(m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT);
+}
+
+void ProgramBinaryPersistenceCase::buildProgram (glu::Program& program, ShaderAllocator& shaders)
+{
+	TestLog&		log			= m_testCtx.getLog();
+
+	glu::Shader&	vertShader	= shaders.createShader(glu::SHADERTYPE_VERTEX);
+	glu::Shader&	fragShader	= shaders.createShader(glu::SHADERTYPE_FRAGMENT);
+
+	vertShader.compile();
+	fragShader.compile();
+
+	program.attachShader(vertShader.getShader());
+	program.attachShader(fragShader.getShader());
+	program.link();
+
+	logProgram(log, m_context.getRenderContext(), program, shaders);
+}
+
+void ProgramBinaryPersistenceCase::verify (glu::Program& program, const ProgramBinary& binary)
+{
+	TestLog&		log				= m_testCtx.getLog();
+	ProgramBinary	currentBinary;
+
+	getProgramBinary(currentBinary, program.getProgram());
+
+	if (!programBinariesEqual(binary, currentBinary))
+	{
+		log << TestLog::Message << "Fail, program binary may only change as a result of linking or loading." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program binary changed");
+	}
+}
+
+void ProgramBinaryPersistenceCase::test (void)
+{
+	TestLog&				log			= m_testCtx.getLog();
+	glu::RenderContext&		renderCtx	= m_context.getRenderContext();
+
+	ConstantShaderGenerator	sourceGen	(m_rnd);
+
+	ShaderAllocator			shaders		(renderCtx, sourceGen);
+	glu::Program			program		(renderCtx);
+
+	buildProgram(program, shaders);
+
+	if (program.getLinkStatus())
+	{
+		ProgramBinary binary;
+		getProgramBinary(binary, program.getProgram());
+
+		executeForProgram(program, shaders);
+
+		verify(program, binary);
+
+		logProgram(log, renderCtx, program, shaders);
+	}
+	else
+	{
+		log << TestLog::Message << "Fail, couldn't link program." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Linking failed");
+	}
+}
+
+// Program state case utilities
+
+namespace
+{
+
+template<class T>
+void addProgramBinaryPersistenceCase (TestCaseGroup* group, Context& context, const std::string& name, const std::string& desc)
+{
+	for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
+	{
+		const glu::ShaderType	shaderType		= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
+		const std::string		shaderTypeName	= getShaderTypeName(shaderType);
+
+		const std::string		caseName		= name + "_" + shaderTypeName;
+		const std::string		caseDesc		= "Build program, " + desc + ", for " + shaderTypeName + " shader.";
+
+		group->addChild(new T(context, caseName.c_str(), caseDesc.c_str(), shaderType));
+	}
+}
+
+} // anonymous
+
+// Specialized program state cases
+
+class ProgramBinaryPersistenceDetachShaderCase : public ProgramBinaryPersistenceCase
+{
+public:
+	ProgramBinaryPersistenceDetachShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramBinaryPersistenceDetachShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Detaching " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+	}
+};
+
+class ProgramBinaryPersistenceReattachShaderCase : public ProgramBinaryPersistenceCase
+{
+public:
+	ProgramBinaryPersistenceReattachShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramBinaryPersistenceReattachShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Reattaching " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+		program.attachShader(caseShader.getShader());
+	}
+};
+
+class ProgramBinaryPersistenceDeleteShaderCase : public ProgramBinaryPersistenceCase
+{
+public:
+	ProgramBinaryPersistenceDeleteShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramBinaryPersistenceDeleteShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Deleting " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+		shaders.deleteShader(m_shaderType);
+	}
+};
+
+class ProgramBinaryPersistenceReplaceShaderCase : public ProgramBinaryPersistenceCase
+{
+public:
+	ProgramBinaryPersistenceReplaceShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramBinaryPersistenceReplaceShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Deleting and replacing " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		program.detachShader(caseShader.getShader());
+		shaders.deleteShader(m_shaderType);
+		program.attachShader(shaders.createShader(m_shaderType).getShader());
+	}
+};
+
+class ProgramBinaryPersistenceRecompileShaderCase : public ProgramBinaryPersistenceCase
+{
+public:
+	ProgramBinaryPersistenceRecompileShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramBinaryPersistenceRecompileShaderCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Recompiling " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
+		caseShader.compile();
+		DE_UNREF(program);
+	}
+};
+
+class ProgramBinaryPersistenceReplaceSourceCase : public ProgramBinaryPersistenceCase
+{
+public:
+	ProgramBinaryPersistenceReplaceSourceCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
+		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
+	{
+	}
+
+	virtual ~ProgramBinaryPersistenceReplaceSourceCase (void)
+	{
+	}
+
+	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
+	{
+		TestLog&		log			= m_testCtx.getLog();
+		glu::Shader&	caseShader	= shaders.get(m_shaderType);
+
+		log << TestLog::Message << "Replacing " + std::string(getShaderTypeName(m_shaderType)) + " shader source and recompiling" << TestLog::EndMessage;
+		shaders.setSource(m_shaderType);
+		caseShader.compile();
+		DE_UNREF(program);
+	}
+};
+
+// Test group
+
+ShaderApiTests::ShaderApiTests (Context& context)
+	: TestCaseGroup(context, "shader_api", "Shader API Cases")
+{
+}
+
+ShaderApiTests::~ShaderApiTests (void)
+{
+}
+
+void ShaderApiTests::init (void)
+{
+	// create and delete shaders
+	{
+		TestCaseGroup* createDeleteGroup = new TestCaseGroup(m_context, "create_delete", "glCreateShader() tests");
+		addChild(createDeleteGroup);
+
+		createDeleteGroup->addChild(new CreateShaderCase(m_context,	"create_vertex_shader",		"Create vertex shader object",		glu::SHADERTYPE_VERTEX));
+		createDeleteGroup->addChild(new CreateShaderCase(m_context,	"create_fragment_shader",	"Create fragment shader object",	glu::SHADERTYPE_FRAGMENT));
+
+		createDeleteGroup->addChild(new DeleteShaderCase(m_context,	"delete_vertex_fragment",	"Delete vertex shader and fragment shader"));
+	}
+
+	// compile and link
+	{
+		TestCaseGroup* compileLinkGroup = new TestCaseGroup(m_context, "compile_link", "Compile and link tests");
+		addChild(compileLinkGroup);
+
+		compileLinkGroup->addChild(new CompileShaderCase(m_context,	"compile_vertex_shader",	"Compile vertex shader",	glu::SHADERTYPE_VERTEX));
+		compileLinkGroup->addChild(new CompileShaderCase(m_context,	"compile_fragment_shader",	"Compile fragment shader",	glu::SHADERTYPE_FRAGMENT));
+
+		compileLinkGroup->addChild(new LinkVertexFragmentCase(m_context,	"link_vertex_fragment",	"Link vertex and fragment shaders"));
+	}
+
+	// shader source
+	{
+		TestCaseGroup* shaderSourceGroup = new TestCaseGroup(m_context, "shader_source", "glShaderSource() tests");
+		addChild(shaderSourceGroup);
+
+		for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
+		{
+			const glu::ShaderType	shaderType		= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
+			const std::string		shaderTypeName	= getShaderTypeName(shaderType);
+
+			const std::string		caseName		= std::string("replace_source_") + shaderTypeName;
+			const std::string		caseDesc		= std::string("Replace source code of ") + shaderTypeName + " shader.";
+
+			shaderSourceGroup->addChild(new ShaderSourceReplaceCase(m_context, caseName.c_str(), caseDesc.c_str(), shaderType));
+		}
+
+		for (int stringLengthsInt	= 0; stringLengthsInt < 3; stringLengthsInt++)
+		for (int caseNdx = 1; caseNdx <= 3; caseNdx++)
+		for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
+		{
+			const int				numSlices		= 1 << caseNdx;
+			const glu::ShaderType	shaderType		= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
+
+			const bool				explicitLengths	= (stringLengthsInt != 0);
+			const bool				randomNullTerm	= (stringLengthsInt == 2);
+
+			const deUint32			flags			= (explicitLengths	? CASE_EXPLICIT_SOURCE_LENGTHS	: 0)
+													| (randomNullTerm	? CASE_RANDOM_NULL_TERMINATED	: 0);
+
+			const std::string		caseName		= "split_source_"
+													+ de::toString(numSlices)
+													+ (randomNullTerm ? "_random_negative_length" : (explicitLengths ? "_specify_lengths" : "_null_terminated"))
+													+ ((shaderType == glu::SHADERTYPE_FRAGMENT) ? "_fragment" : "_vertex");
+
+			const std::string		caseDesc		= std::string((shaderType == glu::SHADERTYPE_FRAGMENT) ? "Fragment" : "Vertex")
+													+ " shader source split into "
+													+ de::toString(numSlices)
+													+ " pieces"
+													+ (explicitLengths ? ", using explicitly specified string lengths" : "")
+													+ (randomNullTerm ? " with random negative length values" : "");
+
+			shaderSourceGroup->addChild(new ShaderSourceSplitCase(m_context, caseName.c_str(), caseDesc.c_str(), shaderType, numSlices, flags));
+		}
+	}
+
+	// link status and infolog
+	{
+		TestCaseGroup* linkStatusGroup = new TestCaseGroup(m_context, "program_state", "Program state persistence tests");
+		addChild(linkStatusGroup);
+
+		addProgramStateCase<ProgramStateDetachShaderCase>		(linkStatusGroup,	m_context,	"detach_shader",	"detach shader");
+		addProgramStateCase<ProgramStateReattachShaderCase>		(linkStatusGroup,	m_context,	"reattach_shader",	"reattach shader");
+		addProgramStateCase<ProgramStateDeleteShaderCase>		(linkStatusGroup,	m_context,	"delete_shader",	"delete shader");
+		addProgramStateCase<ProgramStateReplaceShaderCase>		(linkStatusGroup,	m_context,	"replace_shader",	"replace shader object");
+		addProgramStateCase<ProgramStateRecompileShaderCase>	(linkStatusGroup,	m_context,	"recompile_shader",	"recompile shader");
+		addProgramStateCase<ProgramStateReplaceSourceCase>		(linkStatusGroup,	m_context,	"replace_source",	"replace shader source");
+	}
+
+	// program binary
+	{
+		TestCaseGroup* programBinaryGroup = new TestCaseGroup(m_context, "program_binary", "Program binary API tests");
+		addChild(programBinaryGroup);
+
+		{
+			TestCaseGroup* simpleCaseGroup = new TestCaseGroup(m_context, "simple", "Simple API tests");
+			programBinaryGroup->addChild(simpleCaseGroup);
+
+			simpleCaseGroup->addChild(new ProgramBinarySimpleCase		(m_context,	"get_program_binary_vertex_fragment",	"Get vertex and fragment shader program binary"));
+			simpleCaseGroup->addChild(new ProgramBinaryUniformResetCase	(m_context,	"uniform_reset_on_binary_load",			"Verify uniform reset on successful load of program binary"));
+		}
+
+		{
+			TestCaseGroup* binaryPersistenceGroup = new TestCaseGroup(m_context, "binary_persistence", "Program binary persistence tests");
+			programBinaryGroup->addChild(binaryPersistenceGroup);
+
+			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceDetachShaderCase>		(binaryPersistenceGroup,	m_context,	"detach_shader",	"detach shader");
+			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceReattachShaderCase>		(binaryPersistenceGroup,	m_context,	"reattach_shader",	"reattach shader");
+			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceDeleteShaderCase>		(binaryPersistenceGroup,	m_context,	"delete_shader",	"delete shader");
+			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceReplaceShaderCase>		(binaryPersistenceGroup,	m_context,	"replace_shader",	"replace shader object");
+			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceRecompileShaderCase>	(binaryPersistenceGroup,	m_context,	"recompile_shader",	"recompile shader");
+			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceReplaceSourceCase>		(binaryPersistenceGroup,	m_context,	"replace_source",	"replace shader source");
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderApiTests.hpp b/modules/gles3/functional/es3fShaderApiTests.hpp
new file mode 100644
index 0000000..e2da930
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FSHADERAPITESTS_HPP
+#define _ES3FSHADERAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderApiTests : public TestCaseGroup
+{
+public:
+						ShaderApiTests		(Context& context);
+						~ShaderApiTests		(void);
+
+	void				init				(void);
+
+private:
+						ShaderApiTests		(const ShaderApiTests& other);
+	ShaderApiTests&		operator=			(const ShaderApiTests& other);
+};
+
+} // Functional
+} // gles2
+} // deqp
+
+#endif // _ES3FSHADERAPITESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderBuiltinVarTests.cpp b/modules/gles3/functional/es3fShaderBuiltinVarTests.cpp
new file mode 100644
index 0000000..44127d6
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderBuiltinVarTests.cpp
@@ -0,0 +1,1139 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader built-in variable tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderBuiltinVarTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "glsShaderExecUtil.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+#include "deMath.h"
+#include "deUniquePtr.hpp"
+#include "deStringUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuImageCompare.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "rrRenderer.hpp"
+#include "rrFragmentOperations.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+static int getInteger (const glw::Functions& gl, deUint32 pname)
+{
+	int value = -1;
+	gl.getIntegerv(pname, &value);
+	GLU_EXPECT_NO_ERROR(gl.getError(), ("glGetIntegerv(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
+	return value;
+}
+
+template<deUint32 Pname>
+static int getInteger (const glw::Functions& gl)
+{
+	return getInteger(gl, Pname);
+}
+
+static int getVectorsFromComps (const glw::Functions& gl, deUint32 pname)
+{
+	int value = -1;
+	gl.getIntegerv(pname, &value);
+	GLU_EXPECT_NO_ERROR(gl.getError(), ("glGetIntegerv(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
+	TCU_CHECK_MSG(value%4 == 0, ("Expected " + glu::getGettableStateStr((int)pname).toString() + " to be divisible by 4").c_str());
+	return value/4;
+}
+
+template<deUint32 Pname>
+static int getVectorsFromComps (const glw::Functions& gl)
+{
+	return getVectorsFromComps(gl, Pname);
+}
+
+class ShaderBuiltinConstantCase : public TestCase
+{
+public:
+	typedef int (*GetConstantValueFunc) (const glw::Functions& gl);
+
+								ShaderBuiltinConstantCase	(Context& context, const char* name, const char* desc, const char* varName, GetConstantValueFunc getValue, glu::ShaderType shaderType);
+								~ShaderBuiltinConstantCase	(void);
+
+	IterateResult				iterate						(void);
+
+private:
+	const std::string			m_varName;
+	const GetConstantValueFunc	m_getValue;
+	const glu::ShaderType		m_shaderType;
+};
+
+ShaderBuiltinConstantCase::ShaderBuiltinConstantCase (Context& context, const char* name, const char* desc, const char* varName, GetConstantValueFunc getValue, glu::ShaderType shaderType)
+	: TestCase		(context, name, desc)
+	, m_varName		(varName)
+	, m_getValue	(getValue)
+	, m_shaderType	(shaderType)
+{
+}
+
+ShaderBuiltinConstantCase::~ShaderBuiltinConstantCase (void)
+{
+}
+
+static gls::ShaderExecUtil::ShaderExecutor* createGetConstantExecutor (const glu::RenderContext& renderCtx, glu::ShaderType shaderType, const std::string& varName)
+{
+	using namespace gls::ShaderExecUtil;
+
+	ShaderSpec	shaderSpec;
+
+	shaderSpec.version	= glu::GLSL_VERSION_300_ES;
+	shaderSpec.source	= string("result = ") + varName + ";\n";
+	shaderSpec.outputs.push_back(Symbol("result", glu::VarType(glu::TYPE_INT, glu::PRECISION_HIGHP)));
+
+	return createExecutor(renderCtx, shaderType, shaderSpec);
+}
+
+ShaderBuiltinConstantCase::IterateResult ShaderBuiltinConstantCase::iterate (void)
+{
+	using namespace gls::ShaderExecUtil;
+
+	const de::UniquePtr<ShaderExecutor>	shaderExecutor	(createGetConstantExecutor(m_context.getRenderContext(), m_shaderType, m_varName));
+	const int							reference		= m_getValue(m_context.getRenderContext().getFunctions());
+	int									result			= -1;
+	void* const							outputs			= &result;
+
+	if (!shaderExecutor->isOk())
+	{
+		shaderExecutor->log(m_testCtx.getLog());
+		TCU_FAIL("Compile failed");
+	}
+
+	shaderExecutor->useProgram();
+	shaderExecutor->execute(1, DE_NULL, &outputs);
+
+	m_testCtx.getLog() << TestLog::Integer(m_varName, m_varName, "", QP_KEY_TAG_NONE, result);
+
+	if (result != reference)
+	{
+		m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << m_varName << " = " << reference << TestLog::EndMessage
+						   << TestLog::Message << "Test shader:" << TestLog::EndMessage;
+		shaderExecutor->log(m_testCtx.getLog());
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid builtin constant value");
+	}
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	return STOP;
+}
+
+namespace
+{
+
+struct DepthRangeParams
+{
+	DepthRangeParams (void)
+		: zNear	(0.0f)
+		, zFar	(1.0f)
+	{
+	}
+
+	DepthRangeParams (float zNear_, float zFar_)
+		: zNear	(zNear_)
+		, zFar	(zFar_)
+	{
+	}
+
+	float	zNear;
+	float	zFar;
+};
+
+class DepthRangeEvaluator : public gls::ShaderEvaluator
+{
+public:
+	DepthRangeEvaluator (const DepthRangeParams& params)
+		: m_params(params)
+	{
+	}
+
+	void evaluate (gls::ShaderEvalContext& c)
+	{
+		float zNear	= deFloatClamp(m_params.zNear, 0.0f, 1.0f);
+		float zFar	= deFloatClamp(m_params.zFar, 0.0f, 1.0f);
+		float diff	= zFar - zNear;
+		c.color.xyz() = tcu::Vec3(zNear, zFar, diff*0.5f + 0.5f);
+	}
+
+private:
+	const DepthRangeParams& m_params;
+};
+
+} // anonymous
+
+class ShaderDepthRangeTest : public gls::ShaderRenderCase
+{
+public:
+	ShaderDepthRangeTest (Context& context, const char* name, const char* desc, bool isVertexCase)
+		: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, desc, isVertexCase, m_evaluator)
+		, m_evaluator		(m_depthRange)
+		, m_iterNdx			(0)
+	{
+	}
+
+	void init (void)
+	{
+		static const char* defaultVertSrc =
+			"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n";
+		static const char* defaultFragSrc =
+			"#version 300 es\n"
+			"in mediump vec4 v_color;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = v_color;\n"
+			"}\n";
+
+		// Construct shader.
+		std::ostringstream src;
+		src << "#version 300 es\n";
+		if (m_isVertexCase)
+			src << "in highp vec4 a_position;\n"
+				<< "out mediump vec4 v_color;\n";
+		else
+			src << "layout(location = 0) out mediump vec4 o_color;\n";
+
+		src << "void main (void)\n{\n";
+		src << "\t" << (m_isVertexCase ? "v_color" : "o_color") << " = vec4(gl_DepthRange.near, gl_DepthRange.far, gl_DepthRange.diff*0.5 + 0.5, 1.0);\n";
+
+		if (m_isVertexCase)
+			src << "\tgl_Position = a_position;\n";
+
+		src << "}\n";
+
+		m_vertShaderSource		= m_isVertexCase ? src.str()		: defaultVertSrc;
+		m_fragShaderSource		= m_isVertexCase ? defaultFragSrc	: src.str();
+
+		gls::ShaderRenderCase::init();
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions& gl = m_renderCtx.getFunctions();
+
+		const DepthRangeParams cases[] =
+		{
+			DepthRangeParams(0.0f,  1.0f),
+			DepthRangeParams(1.5f, -1.0f),
+			DepthRangeParams(0.7f,  0.3f)
+		};
+
+		m_depthRange = cases[m_iterNdx];
+		m_testCtx.getLog() << tcu::TestLog::Message << "glDepthRangef(" << m_depthRange.zNear << ", " << m_depthRange.zFar << ")" << tcu::TestLog::EndMessage;
+		gl.depthRangef(m_depthRange.zNear, m_depthRange.zFar);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthRangef()");
+
+		gls::ShaderRenderCase::iterate();
+		m_iterNdx += 1;
+
+		if (m_iterNdx == DE_LENGTH_OF_ARRAY(cases) || m_testCtx.getTestResult() != QP_TEST_RESULT_PASS)
+			return STOP;
+		else
+			return CONTINUE;
+	}
+
+private:
+	DepthRangeParams		m_depthRange;
+	DepthRangeEvaluator		m_evaluator;
+	int						m_iterNdx;
+};
+
+class FragCoordXYZCase : public TestCase
+{
+public:
+	FragCoordXYZCase (Context& context)
+		: TestCase(context, "fragcoord_xyz", "gl_FragCoord.xyz Test")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog&				log			= m_testCtx.getLog();
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const int				width		= m_context.getRenderTarget().getWidth();
+		const int				height		= m_context.getRenderTarget().getHeight();
+		const tcu::RGBA			threshold	= tcu::RGBA(1,1,1,1) + m_context.getRenderTarget().getPixelFormat().getColorThreshold();
+		const tcu::Vec3			scale		(1.f / float(width), 1.f / float(height), 1.0f);
+
+		tcu::Surface			testImg		(width, height);
+		tcu::Surface			refImg		(width, height);
+
+		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
+			"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n",
+
+			"#version 300 es\n"
+			"uniform highp vec3 u_scale;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = vec4(gl_FragCoord.xyz*u_scale, 1.0);\n"
+			"}\n"));
+
+		log << program;
+
+		if (!program.isOk())
+			throw tcu::TestError("Compile failed");
+
+		// Draw with GL.
+		{
+			const float positions[] =
+			{
+				-1.0f,  1.0f, -1.0f, 1.0f,
+				-1.0f, -1.0f,  0.0f, 1.0f,
+				 1.0f,  1.0f,  0.0f, 1.0f,
+				 1.0f, -1.0f,  1.0f, 1.0f
+			};
+			const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+			const int				scaleLoc	= gl.getUniformLocation(program.getProgram(), "u_scale");
+			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_position", 4, 4, 0, &positions[0]);
+
+			gl.useProgram(program.getProgram());
+			gl.uniform3fv(scaleLoc, 1, scale.getPtr());
+
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+
+			glu::readPixels(m_context.getRenderContext(), 0, 0, testImg.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+		}
+
+		// Draw reference
+		for (int y = 0; y < refImg.getHeight(); y++)
+		{
+			for (int x = 0; x < refImg.getWidth(); x++)
+			{
+				const float			xf			= (float(x)+.5f) / float(refImg.getWidth());
+				const float			yf			= (float(refImg.getHeight()-y-1)+.5f) / float(refImg.getHeight());
+				const float			z			= (xf + yf) / 2.0f;
+				const tcu::Vec3		fragCoord	(float(x)+.5f, float(y)+.5f, z);
+				const tcu::Vec3		scaledFC	= fragCoord*scale;
+				const tcu::Vec4		color		(scaledFC.x(), scaledFC.y(), scaledFC.z(), 1.0f);
+
+				refImg.setPixel(x, y, tcu::RGBA(color));
+			}
+		}
+
+		// Compare
+		{
+			bool isOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+static inline float projectedTriInterpolate (const tcu::Vec3& s, const tcu::Vec3& w, float nx, float ny)
+{
+	return (s[0]*(1.0f-nx-ny)/w[0] + s[1]*ny/w[1] + s[2]*nx/w[2]) / ((1.0f-nx-ny)/w[0] + ny/w[1] + nx/w[2]);
+}
+
+class FragCoordWCase : public TestCase
+{
+public:
+	FragCoordWCase (Context& context)
+		: TestCase(context, "fragcoord_w", "gl_FragCoord.w Test")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog&				log			= m_testCtx.getLog();
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const int				width		= m_context.getRenderTarget().getWidth();
+		const int				height		= m_context.getRenderTarget().getHeight();
+		const tcu::RGBA			threshold	= tcu::RGBA(1,1,1,1) + m_context.getRenderTarget().getPixelFormat().getColorThreshold();
+
+		tcu::Surface			testImg		(width, height);
+		tcu::Surface			refImg		(width, height);
+
+		const float				w[4]		= { 1.7f, 2.0f, 1.2f, 1.0f };
+
+		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
+			"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n",
+
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = vec4(0.0, 1.0/gl_FragCoord.w - 1.0, 0.0, 1.0);\n"
+			"}\n"));
+
+		log << program;
+
+		if (!program.isOk())
+			throw tcu::TestError("Compile failed");
+
+		// Draw with GL.
+		{
+			const float positions[] =
+			{
+				-w[0],  w[0], 0.0f, w[0],
+				-w[1], -w[1], 0.0f, w[1],
+				 w[2],  w[2], 0.0f, w[2],
+				 w[3], -w[3], 0.0f, w[3]
+			};
+			const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_position", 4, 4, 0, &positions[0]);
+
+			gl.useProgram(program.getProgram());
+
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+
+			glu::readPixels(m_context.getRenderContext(), 0, 0, testImg.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+		}
+
+		// Draw reference
+		for (int y = 0; y < refImg.getHeight(); y++)
+		{
+			for (int x = 0; x < refImg.getWidth(); x++)
+			{
+				const float			xf			= (float(x)+.5f) / float(refImg.getWidth());
+				const float			yf			= (float(refImg.getHeight()-y-1)+.5f) / float(refImg.getHeight());
+				const float			oow			= ((xf + yf) < 1.0f)
+												? projectedTriInterpolate(tcu::Vec3(w[0], w[1], w[2]), tcu::Vec3(w[0], w[1], w[2]), xf, yf)
+												: projectedTriInterpolate(tcu::Vec3(w[3], w[2], w[1]), tcu::Vec3(w[3], w[2], w[1]), 1.0f-xf, 1.0f-yf);
+				const tcu::Vec4		color		(0.0f, oow - 1.0f, 0.0f, 1.0f);
+
+				refImg.setPixel(x, y, tcu::RGBA(color));
+			}
+		}
+
+		// Compare
+		{
+			bool isOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class PointCoordCase : public TestCase
+{
+public:
+	PointCoordCase (Context& context)
+		: TestCase(context, "pointcoord", "gl_PointCoord Test")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog&				log			= m_testCtx.getLog();
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const int				width		= de::min(256, m_context.getRenderTarget().getWidth());
+		const int				height		= de::min(256, m_context.getRenderTarget().getHeight());
+		const float				threshold	= 0.02f;
+
+		const int				numPoints	= 8;
+
+		vector<tcu::Vec3>		coords		(numPoints);
+		float					pointSizeRange[2]	= { 0.0f, 0.0f };
+
+		de::Random				rnd			(0x145fa);
+		tcu::Surface			testImg		(width, height);
+		tcu::Surface			refImg		(width, height);
+
+		gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, &pointSizeRange[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE)");
+
+		if (pointSizeRange[0] <= 0.0f || pointSizeRange[1] <= 0.0f || pointSizeRange[1] < pointSizeRange[0])
+			throw tcu::TestError("Invalid GL_ALIASED_POINT_SIZE_RANGE");
+
+		// Compute coordinates.
+		{
+
+			for (vector<tcu::Vec3>::iterator coord = coords.begin(); coord != coords.end(); ++coord)
+			{
+				coord->x() = rnd.getFloat(-0.9f, 0.9f);
+				coord->y() = rnd.getFloat(-0.9f, 0.9f);
+				coord->z() = rnd.getFloat(pointSizeRange[0], pointSizeRange[1]);
+			}
+		}
+
+		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
+			"#version 300 es\n"
+			"in highp vec3 a_positionSize;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(a_positionSize.xy, 0.0, 1.0);\n"
+			"	gl_PointSize = a_positionSize.z;\n"
+			"}\n",
+
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color = vec4(gl_PointCoord, 0.0, 1.0);\n"
+			"}\n"));
+
+		log << program;
+
+		if (!program.isOk())
+			throw tcu::TestError("Compile failed");
+
+		// Draw with GL.
+		{
+			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_positionSize", 3, (int)coords.size(), 0, (const float*)&coords[0]);
+			const int				viewportX	= rnd.getInt(0, m_context.getRenderTarget().getWidth()-width);
+			const int				viewportY	= rnd.getInt(0, m_context.getRenderTarget().getHeight()-height);
+
+			gl.viewport(viewportX, viewportY, width, height);
+			gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+			gl.clear(GL_COLOR_BUFFER_BIT);
+
+			gl.useProgram(program.getProgram());
+
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Points((int)coords.size()));
+
+			glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+		}
+
+		// Draw reference
+		tcu::clear(refImg.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+		for (vector<tcu::Vec3>::const_iterator pointIter = coords.begin(); pointIter != coords.end(); ++pointIter)
+		{
+			const int	x0		= deRoundFloatToInt32(float(width) *(pointIter->x()*0.5f + 0.5f) - pointIter->z()*0.5f);
+			const int	y0		= deRoundFloatToInt32(float(height)*(pointIter->y()*0.5f + 0.5f) - pointIter->z()*0.5f);
+			const int	x1		= deRoundFloatToInt32(float(width) *(pointIter->x()*0.5f + 0.5f) + pointIter->z()*0.5f);
+			const int	y1		= deRoundFloatToInt32(float(height)*(pointIter->y()*0.5f + 0.5f) + pointIter->z()*0.5f);
+			const int	w		= x1-x0;
+			const int	h		= y1-y0;
+
+			for (int yo = 0; yo < h; yo++)
+			{
+				for (int xo = 0; xo < w; xo++)
+				{
+					const float			xf		= float(xo+0.5f) / float(w);
+					const float			yf		= float((h-yo-1)+0.5f) / float(h);
+					const tcu::Vec4		color	(xf, yf, 0.0f, 1.0f);
+					const int			dx		= x0+xo;
+					const int			dy		= y0+yo;
+
+					if (de::inBounds(dx, 0, refImg.getWidth()) && de::inBounds(dy, 0, refImg.getHeight()))
+						refImg.setPixel(dx, dy, tcu::RGBA(color));
+				}
+			}
+		}
+
+		// Compare
+		{
+			bool isOk = tcu::fuzzyCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class FrontFacingCase : public TestCase
+{
+public:
+	FrontFacingCase (Context& context)
+		: TestCase(context, "frontfacing", "gl_FrontFacing Test")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		// Test case renders two adjecent quads, where left is has front-facing
+		// triagles and right back-facing. Color is selected based on gl_FrontFacing
+		// value.
+
+		TestLog&				log			= m_testCtx.getLog();
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		de::Random				rnd			(0x89f2c);
+		const int				width		= de::min(64, m_context.getRenderTarget().getWidth());
+		const int				height		= de::min(64, m_context.getRenderTarget().getHeight());
+		const int				viewportX	= rnd.getInt(0, m_context.getRenderTarget().getWidth()-width);
+		const int				viewportY	= rnd.getInt(0, m_context.getRenderTarget().getHeight()-height);
+		const tcu::RGBA			threshold	= tcu::RGBA(1,1,1,1) + m_context.getRenderTarget().getPixelFormat().getColorThreshold();
+
+		tcu::Surface			testImg		(width, height);
+		tcu::Surface			refImg		(width, height);
+
+		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
+			"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n",
+
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	if (gl_FrontFacing)\n"
+			"		o_color = vec4(0.0, 1.0, 0.0, 1.0);\n"
+			"	else\n"
+			"		o_color = vec4(0.0, 0.0, 1.0, 1.0);\n"
+			"}\n"));
+
+		log << program;
+
+		if (!program.isOk())
+			throw tcu::TestError("Compile failed");
+
+		// Draw with GL.
+		{
+			const float positions[] =
+			{
+				-1.0f,  1.0f, 0.0f, 1.0f,
+				-1.0f, -1.0f, 0.0f, 1.0f,
+				 1.0f,  1.0f, 0.0f, 1.0f,
+				 1.0f, -1.0f, 0.0f, 1.0f
+			};
+			const deUint16 indicesCCW[]	= { 0, 1, 2, 2, 1, 3 };
+			const deUint16 indicesCW[]	= { 2, 1, 0, 3, 1, 2 };
+
+			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_position", 4, 4, 0, &positions[0]);
+
+			gl.useProgram(program.getProgram());
+
+			gl.viewport(viewportX, viewportY, width/2, height);
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indicesCCW), &indicesCCW[0]));
+
+			gl.viewport(viewportX + width/2, viewportY, width-width/2, height);
+			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indicesCW), &indicesCW[0]));
+
+			glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+		}
+
+		// Draw reference
+		for (int y = 0; y < refImg.getHeight(); y++)
+		{
+			for (int x = 0; x < refImg.getWidth()/2; x++)
+				refImg.setPixel(x, y, tcu::RGBA::green);
+
+			for (int x = refImg.getWidth()/2; x < refImg.getWidth(); x++)
+				refImg.setPixel(x, y, tcu::RGBA::blue);
+		}
+
+		// Compare
+		{
+			bool isOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+// VertexIDCase
+
+class VertexIDCase : public TestCase
+{
+public:
+						VertexIDCase			(Context& context);
+						~VertexIDCase			(void);
+
+	void				init					(void);
+	void				deinit					(void);
+	IterateResult		iterate					(void);
+
+private:
+	enum
+	{
+		MAX_VERTICES = 8*3	//!< 8 triangles, totals 24 vertices
+	};
+
+	void				renderReference			(const tcu::PixelBufferAccess& dst, const int numVertices, const deUint16* const indices, const tcu::Vec4* const positions, const tcu::Vec4* const colors);
+
+	glu::ShaderProgram*	m_program;
+	deUint32			m_positionBuffer;
+	deUint32			m_elementBuffer;
+
+	vector<tcu::Vec4>	m_positions;
+	vector<tcu::Vec4>	m_colors;
+	int					m_viewportW;
+	int					m_viewportH;
+
+	int					m_iterNdx;
+};
+
+VertexIDCase::VertexIDCase (Context& context)
+	: TestCase			(context, "vertex_id",	"gl_VertexID Test")
+	, m_program			(DE_NULL)
+	, m_positionBuffer	(0)
+	, m_elementBuffer	(0)
+	, m_viewportW		(0)
+	, m_viewportH		(0)
+	, m_iterNdx			(0)
+{
+}
+
+VertexIDCase::~VertexIDCase (void)
+{
+	VertexIDCase::deinit();
+}
+
+void VertexIDCase::init (void)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const int				width		= m_context.getRenderTarget().getWidth();
+	const int				height		= m_context.getRenderTarget().getHeight();
+
+	const int				quadWidth	= 32;
+	const int				quadHeight	= 32;
+
+	if (width < quadWidth)
+		throw tcu::NotSupportedError("Too small render target");
+
+	const int				maxQuadsX	= width/quadWidth;
+	const int				numVertices	= MAX_VERTICES;
+
+	const int				numQuads	= numVertices/6 + (numVertices%6 != 0 ? 1 : 0);
+	const int				viewportW	= de::min(numQuads, maxQuadsX)*quadWidth;
+	const int				viewportH	= (numQuads/maxQuadsX + (numQuads%maxQuadsX != 0 ? 1 : 0))*quadHeight;
+
+	if (viewportH > height)
+		throw tcu::NotSupportedError("Too small render target");
+
+	DE_ASSERT(viewportW <= width && viewportH <= height);
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"out mediump vec4 v_color;\n"
+		"uniform highp vec4 u_colors[24];\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_color = u_colors[gl_VertexID];\n"
+		"}\n",
+
+		"#version 300 es\n"
+		"in mediump vec4 v_color;\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"	o_color = v_color;\n"
+		"}\n"));
+
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+	{
+		delete m_program;
+		m_program = DE_NULL;
+		throw tcu::TestError("Compile failed");
+	}
+
+	gl.genBuffers(1, &m_positionBuffer);
+	gl.genBuffers(1, &m_elementBuffer);
+
+	// Set colors (in dynamic memory to save static data space).
+	m_colors.resize(numVertices);
+	m_colors[ 0] = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
+	m_colors[ 1] = tcu::Vec4(0.5f, 1.0f, 0.5f, 1.0f);
+	m_colors[ 2] = tcu::Vec4(0.0f, 0.5f, 1.0f, 1.0f);
+	m_colors[ 3] = tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+	m_colors[ 4] = tcu::Vec4(0.0f, 1.0f, 1.0f, 1.0f);
+	m_colors[ 5] = tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f);
+	m_colors[ 6] = tcu::Vec4(0.5f, 0.0f, 1.0f, 1.0f);
+	m_colors[ 7] = tcu::Vec4(0.5f, 0.0f, 0.5f, 1.0f);
+	m_colors[ 8] = tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+	m_colors[ 9] = tcu::Vec4(0.5f, 1.0f, 0.0f, 1.0f);
+	m_colors[10] = tcu::Vec4(0.0f, 0.5f, 0.0f, 1.0f);
+	m_colors[11] = tcu::Vec4(0.5f, 1.0f, 1.0f, 1.0f);
+	m_colors[12] = tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f);
+	m_colors[13] = tcu::Vec4(1.0f, 0.0f, 0.5f, 1.0f);
+	m_colors[14] = tcu::Vec4(0.0f, 0.5f, 0.5f, 1.0f);
+	m_colors[15] = tcu::Vec4(1.0f, 1.0f, 0.5f, 1.0f);
+	m_colors[16] = tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f);
+	m_colors[17] = tcu::Vec4(1.0f, 0.5f, 0.0f, 1.0f);
+	m_colors[18] = tcu::Vec4(0.0f, 1.0f, 0.5f, 1.0f);
+	m_colors[19] = tcu::Vec4(1.0f, 0.5f, 1.0f, 1.0f);
+	m_colors[20] = tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
+	m_colors[21] = tcu::Vec4(1.0f, 0.5f, 0.5f, 1.0f);
+	m_colors[22] = tcu::Vec4(0.0f, 0.0f, 0.5f, 1.0f);
+	m_colors[23] = tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);
+
+	// Compute positions.
+	m_positions.resize(numVertices);
+	DE_ASSERT(numVertices%3 == 0);
+	for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx += 3)
+	{
+		const float	h			= 2.0f * float(quadHeight)/float(viewportH);
+		const float	w			= 2.0f * float(quadWidth)/float(viewportW);
+
+		const int	triNdx		= vtxNdx/3;
+		const int	quadNdx		= triNdx/2;
+		const int	quadY		= quadNdx/maxQuadsX;
+		const int	quadX		= quadNdx%maxQuadsX;
+
+		const float	x0			= -1.0f + quadX*w;
+		const float	y0			= -1.0f + quadY*h;
+
+		if (triNdx%2 == 0)
+		{
+			m_positions[vtxNdx+0] = tcu::Vec4(x0,   y0,   0.0f, 1.0f);
+			m_positions[vtxNdx+1] = tcu::Vec4(x0+w, y0+h, 0.0f, 1.0f);
+			m_positions[vtxNdx+2] = tcu::Vec4(x0,   y0+h, 0.0f, 1.0f);
+		}
+		else
+		{
+			m_positions[vtxNdx+0] = tcu::Vec4(x0+w, y0+h, 0.0f, 1.0f);
+			m_positions[vtxNdx+1] = tcu::Vec4(x0,   y0,   0.0f, 1.0f);
+			m_positions[vtxNdx+2] = tcu::Vec4(x0+w, y0,   0.0f, 1.0f);
+		}
+	}
+
+	m_viewportW	= viewportW;
+	m_viewportH	= viewportH;
+	m_iterNdx	= 0;
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void VertexIDCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+
+	if (m_positionBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_positionBuffer);
+		m_positionBuffer = 0;
+	}
+
+	if (m_elementBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_elementBuffer);
+		m_elementBuffer = 0;
+	}
+
+	m_positions.clear();
+	m_colors.clear();
+}
+
+class VertexIDReferenceShader : public rr::VertexShader, public rr::FragmentShader
+{
+public:
+	enum
+	{
+		VARYINGLOC_COLOR = 0
+	};
+
+	VertexIDReferenceShader ()
+		: rr::VertexShader	(2, 1)		// color and pos in => color out
+		, rr::FragmentShader(1, 1)		// color in => color out
+	{
+		this->rr::VertexShader::m_inputs[0].type		= rr::GENERICVECTYPE_FLOAT;
+		this->rr::VertexShader::m_inputs[1].type		= rr::GENERICVECTYPE_FLOAT;
+
+		this->rr::VertexShader::m_outputs[0].type		= rr::GENERICVECTYPE_FLOAT;
+		this->rr::VertexShader::m_outputs[0].flatshade	= false;
+
+		this->rr::FragmentShader::m_inputs[0].type		= rr::GENERICVECTYPE_FLOAT;
+		this->rr::FragmentShader::m_inputs[0].flatshade	= false;
+
+		this->rr::FragmentShader::m_outputs[0].type		= rr::GENERICVECTYPE_FLOAT;
+	}
+
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			const int positionAttrLoc = 0;
+			const int colorAttrLoc = 1;
+
+			rr::VertexPacket& packet = *packets[packetNdx];
+
+			// Transform to position
+			packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx);
+
+			// Pass color to FS
+			packet.outputs[VARYINGLOC_COLOR] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx);
+		}
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			rr::FragmentPacket& packet = packets[packetNdx];
+
+			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+				rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, VARYINGLOC_COLOR, fragNdx));
+		}
+	}
+};
+
+void VertexIDCase::renderReference (const tcu::PixelBufferAccess& dst, const int numVertices, const deUint16* const indices, const tcu::Vec4* const positions, const tcu::Vec4* const colors)
+{
+	const rr::Renderer				referenceRenderer;
+	const rr::RenderState			referenceState		((rr::ViewportState)(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(dst)));
+	const rr::RenderTarget			referenceTarget		(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(dst));
+	const VertexIDReferenceShader	referenceShader;
+	      rr::VertexAttrib			attribs[2];
+
+	attribs[0].type				= rr::VERTEXATTRIBTYPE_FLOAT;
+	attribs[0].size				= 4;
+	attribs[0].stride			= 0;
+	attribs[0].instanceDivisor	= 0;
+	attribs[0].pointer			= positions;
+
+	attribs[1].type				= rr::VERTEXATTRIBTYPE_FLOAT;
+	attribs[1].size				= 4;
+	attribs[1].stride			= 0;
+	attribs[1].instanceDivisor	= 0;
+	attribs[1].pointer			= colors;
+
+	referenceRenderer.draw(
+		rr::DrawCommand(
+			referenceState,
+			referenceTarget,
+			rr::Program(&referenceShader, &referenceShader),
+			2,
+			attribs,
+			rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, numVertices, rr::DrawIndices(indices))));
+}
+
+VertexIDCase::IterateResult VertexIDCase::iterate (void)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const int				width		= m_context.getRenderTarget().getWidth();
+	const int				height		= m_context.getRenderTarget().getHeight();
+	const int				viewportW	= m_viewportW;
+	const int				viewportH	= m_viewportH;
+
+	const float				threshold	= 0.02f;
+
+	de::Random				rnd			(0xcf23ab1 ^ deInt32Hash(m_iterNdx));
+	tcu::Surface			refImg		(viewportW, viewportH);
+	tcu::Surface			testImg		(viewportW, viewportH);
+
+	const int				viewportX	= rnd.getInt(0, width-viewportW);
+	const int				viewportY	= rnd.getInt(0, height-viewportH);
+
+	const int				posLoc		= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	const int				colorsLoc	= gl.getUniformLocation(m_program->getProgram(), "u_colors[0]");
+	const tcu::Vec4			clearColor	(0.0f, 0.0f, 0.0f, 1.0f);
+
+	// Setup common state.
+	gl.viewport					(viewportX, viewportY, viewportW, viewportH);
+	gl.useProgram				(m_program->getProgram());
+	gl.bindBuffer				(GL_ARRAY_BUFFER, m_positionBuffer);
+	gl.enableVertexAttribArray	(posLoc);
+	gl.vertexAttribPointer		(posLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.uniform4fv				(colorsLoc, (int)m_colors.size(), (const float*)&m_colors[0]);
+
+	// Clear render target to black.
+	gl.clearColor	(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+	gl.clear		(GL_COLOR_BUFFER_BIT);
+
+	tcu::clear(refImg.getAccess(), clearColor);
+
+	if (m_iterNdx == 0)
+	{
+		tcu::ScopedLogSection	logSection	(m_testCtx.getLog(), "Iter0", "glDrawArrays()");
+		vector<deUint16>		indices		(m_positions.size());
+
+		gl.bufferData(GL_ARRAY_BUFFER, (int)(m_positions.size()*sizeof(tcu::Vec4)), &m_positions[0], GL_DYNAMIC_DRAW);
+		gl.drawArrays(GL_TRIANGLES, 0, (int)m_positions.size());
+
+		glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+
+		// Reference indices
+		for (int ndx = 0; ndx < (int)indices.size(); ndx++)
+			indices[ndx] = (deUint16)ndx;
+
+		renderReference(refImg.getAccess(), (int)m_positions.size(), &indices[0], &m_positions[0], &m_colors[0]);
+	}
+	else if (m_iterNdx == 1)
+	{
+		tcu::ScopedLogSection	logSection	(m_testCtx.getLog(), "Iter1", "glDrawElements(), indices in client-side array");
+		vector<deUint16>		indices		(m_positions.size());
+		vector<tcu::Vec4>		mappedPos	(m_positions.size());
+
+		// Compute initial indices and suffle
+		for (int ndx = 0; ndx < (int)indices.size(); ndx++)
+			indices[ndx] = (deUint16)ndx;
+		rnd.shuffle(indices.begin(), indices.end());
+
+		// Use indices to re-map positions.
+		for (int ndx = 0; ndx < (int)indices.size(); ndx++)
+			mappedPos[indices[ndx]] = m_positions[ndx];
+
+		gl.bufferData(GL_ARRAY_BUFFER, (int)(m_positions.size()*sizeof(tcu::Vec4)), &mappedPos[0], GL_DYNAMIC_DRAW);
+		gl.drawElements(GL_TRIANGLES, (int)indices.size(), GL_UNSIGNED_SHORT, &indices[0]);
+
+		glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+
+		renderReference(refImg.getAccess(), (int)indices.size(), &indices[0], &mappedPos[0], &m_colors[0]);
+	}
+	else if (m_iterNdx == 2)
+	{
+		tcu::ScopedLogSection	logSection	(m_testCtx.getLog(), "Iter2", "glDrawElements(), indices in buffer");
+		vector<deUint16>		indices		(m_positions.size());
+		vector<tcu::Vec4>		mappedPos	(m_positions.size());
+
+		// Compute initial indices and suffle
+		for (int ndx = 0; ndx < (int)indices.size(); ndx++)
+			indices[ndx] = (deUint16)ndx;
+		rnd.shuffle(indices.begin(), indices.end());
+
+		// Use indices to re-map positions.
+		for (int ndx = 0; ndx < (int)indices.size(); ndx++)
+			mappedPos[indices[ndx]] = m_positions[ndx];
+
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementBuffer);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (int)(indices.size()*sizeof(deUint16)), &indices[0], GL_DYNAMIC_DRAW);
+
+		gl.bufferData(GL_ARRAY_BUFFER, (int)(m_positions.size()*sizeof(tcu::Vec4)), &mappedPos[0], GL_DYNAMIC_DRAW);
+		gl.drawElements(GL_TRIANGLES, (int)indices.size(), GL_UNSIGNED_SHORT, DE_NULL);
+
+		glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+
+		tcu::clear(refImg.getAccess(), clearColor);
+		renderReference(refImg.getAccess(), (int)indices.size(), &indices[0], &mappedPos[0], &m_colors[0]);
+	}
+	else
+		DE_ASSERT(false);
+
+	if (!tcu::fuzzyCompare(m_testCtx.getLog(), "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT))
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+	m_iterNdx += 1;
+	return (m_iterNdx < 3) ? CONTINUE : STOP;
+}
+
+ShaderBuiltinVarTests::ShaderBuiltinVarTests (Context& context)
+	: TestCaseGroup(context, "builtin_variable", "Built-in Variable Tests")
+{
+}
+
+ShaderBuiltinVarTests::~ShaderBuiltinVarTests (void)
+{
+}
+
+void ShaderBuiltinVarTests::init (void)
+{
+	// Builtin constants.
+
+	static const struct
+	{
+		const char*											caseName;
+		const char*											varName;
+		ShaderBuiltinConstantCase::GetConstantValueFunc		getValue;
+	} builtinConstants[] =
+	{
+		// GLES 2.
+
+		{ "max_vertex_attribs",					"gl_MaxVertexAttribs",				getInteger<GL_MAX_VERTEX_ATTRIBS>						},
+		{ "max_vertex_uniform_vectors",			"gl_MaxVertexUniformVectors",		getInteger<GL_MAX_VERTEX_UNIFORM_VECTORS>				},
+		{ "max_fragment_uniform_vectors",		"gl_MaxFragmentUniformVectors",		getInteger<GL_MAX_FRAGMENT_UNIFORM_VECTORS>				},
+		{ "max_texture_image_units",			"gl_MaxTextureImageUnits",			getInteger<GL_MAX_TEXTURE_IMAGE_UNITS>					},
+		{ "max_vertex_texture_image_units",		"gl_MaxVertexTextureImageUnits",	getInteger<GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS>			},
+		{ "max_combined_texture_image_units",	"gl_MaxCombinedTextureImageUnits",	getInteger<GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS>			},
+		{ "max_draw_buffers",					"gl_MaxDrawBuffers",				getInteger<GL_MAX_DRAW_BUFFERS>							},
+
+		// GLES 3.
+
+		{ "max_vertex_output_vectors",			"gl_MaxVertexOutputVectors",		getVectorsFromComps<GL_MAX_VERTEX_OUTPUT_COMPONENTS>	},
+		{ "max_fragment_input_vectors",			"gl_MaxFragmentInputVectors",		getVectorsFromComps<GL_MAX_FRAGMENT_INPUT_COMPONENTS>	},
+		{ "min_program_texel_offset",			"gl_MinProgramTexelOffset",			getInteger<GL_MIN_PROGRAM_TEXEL_OFFSET>					},
+		{ "max_program_texel_offset",			"gl_MaxProgramTexelOffset",			getInteger<GL_MAX_PROGRAM_TEXEL_OFFSET>					}
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(builtinConstants); ndx++)
+	{
+		const char* const										caseName	= builtinConstants[ndx].caseName;
+		const char* const										varName		= builtinConstants[ndx].varName;
+		const ShaderBuiltinConstantCase::GetConstantValueFunc	getValue	= builtinConstants[ndx].getValue;
+
+		addChild(new ShaderBuiltinConstantCase(m_context, (string(caseName) + "_vertex").c_str(),	varName, varName, getValue, glu::SHADERTYPE_VERTEX));
+		addChild(new ShaderBuiltinConstantCase(m_context, (string(caseName) + "_fragment").c_str(),	varName, varName, getValue, glu::SHADERTYPE_FRAGMENT));
+	}
+
+	addChild(new ShaderDepthRangeTest(m_context, "depth_range_vertex",		"gl_DepthRange", true));
+	addChild(new ShaderDepthRangeTest(m_context, "depth_range_fragment",	"gl_DepthRange", false));
+
+	// Vertex shader builtin variables.
+	addChild(new VertexIDCase		(m_context));
+	// \todo [2013-03-20 pyry] gl_InstanceID -- tested in instancing tests quite thoroughly.
+
+	// Fragment shader builtin variables.
+
+	addChild(new FragCoordXYZCase	(m_context));
+	addChild(new FragCoordWCase		(m_context));
+	addChild(new PointCoordCase		(m_context));
+	addChild(new FrontFacingCase	(m_context));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderBuiltinVarTests.hpp b/modules/gles3/functional/es3fShaderBuiltinVarTests.hpp
new file mode 100644
index 0000000..630ee7c
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderBuiltinVarTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERBUILTINVARTESTS_HPP
+#define _ES3FSHADERBUILTINVARTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader built-in variable tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderBuiltinVarTests : public TestCaseGroup
+{
+public:
+								ShaderBuiltinVarTests		(Context& context);
+	virtual						~ShaderBuiltinVarTests		(void);
+
+	virtual void				init						(void);
+
+private:
+								ShaderBuiltinVarTests		(const ShaderBuiltinVarTests&);		// not allowed!
+	ShaderBuiltinVarTests&		operator=					(const ShaderBuiltinVarTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERBUILTINVARTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderCommonFunctionTests.cpp b/modules/gles3/functional/es3fShaderCommonFunctionTests.cpp
new file mode 100644
index 0000000..86b0e40
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderCommonFunctionTests.cpp
@@ -0,0 +1,1713 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Common built-in function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderCommonFunctionTests.hpp"
+#include "glsShaderExecUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuFormatUtil.hpp"
+#include "tcuFloat.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using namespace gls::ShaderExecUtil;
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+
+// Utilities
+
+template<typename T, int Size>
+struct VecArrayAccess
+{
+public:
+									VecArrayAccess	(const void* ptr) : m_array((tcu::Vector<T, Size>*)ptr) {}
+									~VecArrayAccess	(void) {}
+
+	const tcu::Vector<T, Size>&		operator[]		(size_t offset) const	{ return m_array[offset];	}
+	tcu::Vector<T, Size>&			operator[]		(size_t offset)			{ return m_array[offset];	}
+
+private:
+	tcu::Vector<T, Size>*			m_array;
+};
+
+template<typename T>	T			randomScalar	(de::Random& rnd, T minValue, T maxValue);
+template<> inline		float		randomScalar	(de::Random& rnd, float minValue, float maxValue)		{ return rnd.getFloat(minValue, maxValue);	}
+template<> inline		deInt32		randomScalar	(de::Random& rnd, deInt32 minValue, deInt32 maxValue)	{ return rnd.getInt(minValue, maxValue);	}
+template<> inline		deUint32	randomScalar	(de::Random& rnd, deUint32 minValue, deUint32 maxValue)	{ return minValue + rnd.getUint32() % (maxValue - minValue + 1); }
+
+template<typename T, int Size>
+inline tcu::Vector<T, Size> randomVector (de::Random& rnd, const tcu::Vector<T, Size>& minValue, const tcu::Vector<T, Size>& maxValue)
+{
+	tcu::Vector<T, Size> res;
+	for (int ndx = 0; ndx < Size; ndx++)
+		res[ndx] = randomScalar<T>(rnd, minValue[ndx], maxValue[ndx]);
+	return res;
+}
+
+template<typename T, int Size>
+static void fillRandomVectors (de::Random& rnd, const tcu::Vector<T, Size>& minValue, const tcu::Vector<T, Size>& maxValue, void* dst, int numValues, int offset = 0)
+{
+	VecArrayAccess<T, Size> access(dst);
+	for (int ndx = 0; ndx < numValues; ndx++)
+		access[offset + ndx] = randomVector<T, Size>(rnd, minValue, maxValue);
+}
+
+template<typename T>
+static void fillRandomScalars (de::Random& rnd, T minValue, T maxValue, void* dst, int numValues, int offset = 0)
+{
+	T* typedPtr = (T*)dst;
+	for (int ndx = 0; ndx < numValues; ndx++)
+		typedPtr[offset + ndx] = randomScalar<T>(rnd, minValue, maxValue);
+}
+
+inline int numBitsLostInOp (float input, float output)
+{
+	const int	inExp		= tcu::Float32(input).exponent();
+	const int	outExp		= tcu::Float32(output).exponent();
+
+	return de::max(0, inExp-outExp); // Lost due to mantissa shift.
+}
+
+inline deUint32 getUlpDiff (float a, float b)
+{
+	const deUint32	aBits	= tcu::Float32(a).bits();
+	const deUint32	bBits	= tcu::Float32(b).bits();
+	return aBits > bBits ? aBits - bBits : bBits - aBits;
+}
+
+inline deUint32 getUlpDiffIgnoreZeroSign (float a, float b)
+{
+	if (tcu::Float32(a).isZero())
+		return getUlpDiff(tcu::Float32::construct(tcu::Float32(b).sign(), 0, 0).asFloat(), b);
+	else if (tcu::Float32(b).isZero())
+		return getUlpDiff(a, tcu::Float32::construct(tcu::Float32(a).sign(), 0, 0).asFloat());
+	else
+		return getUlpDiff(a, b);
+}
+
+inline bool supportsSignedZero (glu::Precision precision)
+{
+	// \note GLSL ES 3.0 doesn't really require support for -0, but we require it for highp
+	//		 as it is very widely supported.
+	return precision == glu::PRECISION_HIGHP;
+}
+
+inline float getEpsFromMaxUlpDiff (float value, deUint32 ulpDiff)
+{
+	const int exp = tcu::Float32(value).exponent();
+	return tcu::Float32::construct(+1, exp, (1u<<23) | ulpDiff).asFloat() - tcu::Float32::construct(+1, exp, 1u<<23).asFloat();
+}
+
+inline deUint32 getMaxUlpDiffFromBits (int numAccurateBits)
+{
+	const int		numGarbageBits	= 23-numAccurateBits;
+	const deUint32	mask			= (1u<<numGarbageBits)-1u;
+
+	return mask;
+}
+
+inline float getEpsFromBits (float value, int numAccurateBits)
+{
+	return getEpsFromMaxUlpDiff(value, getMaxUlpDiffFromBits(numAccurateBits));
+}
+
+static int getMinMantissaBits (glu::Precision precision)
+{
+	const int bits[] =
+	{
+		7,		// lowp
+		10,		// mediump
+		23		// highp
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(bits) == glu::PRECISION_LAST);
+	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(bits)));
+	return bits[precision];
+}
+
+// CommonFunctionCase
+
+class CommonFunctionCase : public TestCase
+{
+public:
+							CommonFunctionCase		(Context& context, const char* name, const char* description, glu::ShaderType shaderType);
+							~CommonFunctionCase		(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+protected:
+							CommonFunctionCase		(const CommonFunctionCase& other);
+	CommonFunctionCase&		operator=				(const CommonFunctionCase& other);
+
+	virtual void			getInputValues			(int numValues, void* const* values) const = 0;
+	virtual bool			compare					(const void* const* inputs, const void* const* outputs) = 0;
+
+	glu::ShaderType			m_shaderType;
+	ShaderSpec				m_spec;
+	int						m_numValues;
+
+	std::ostringstream		m_failMsg;				//!< Comparison failure help message.
+
+private:
+	ShaderExecutor*			m_executor;
+};
+
+CommonFunctionCase::CommonFunctionCase (Context& context, const char* name, const char* description, glu::ShaderType shaderType)
+	: TestCase		(context, name, description)
+	, m_shaderType	(shaderType)
+	, m_numValues	(100)
+	, m_executor	(DE_NULL)
+{
+	m_spec.version = glu::GLSL_VERSION_300_ES;
+}
+
+CommonFunctionCase::~CommonFunctionCase (void)
+{
+	CommonFunctionCase::deinit();
+}
+
+void CommonFunctionCase::init (void)
+{
+	DE_ASSERT(!m_executor);
+
+	m_executor = createExecutor(m_context.getRenderContext(), m_shaderType, m_spec);
+	m_testCtx.getLog() << m_executor;
+
+	if (!m_executor->isOk())
+		throw tcu::TestError("Compile failed");
+}
+
+void CommonFunctionCase::deinit (void)
+{
+	delete m_executor;
+	m_executor = DE_NULL;
+}
+
+static vector<int> getScalarSizes (const vector<Symbol>& symbols)
+{
+	vector<int> sizes(symbols.size());
+	for (int ndx = 0; ndx < (int)symbols.size(); ++ndx)
+		sizes[ndx] = symbols[ndx].varType.getScalarSize();
+	return sizes;
+}
+
+static int computeTotalScalarSize (const vector<Symbol>& symbols)
+{
+	int totalSize = 0;
+	for (vector<Symbol>::const_iterator sym = symbols.begin(); sym != symbols.end(); ++sym)
+		totalSize += sym->varType.getScalarSize();
+	return totalSize;
+}
+
+static vector<void*> getInputOutputPointers (const vector<Symbol>& symbols, vector<deUint32>& data, const int numValues)
+{
+	vector<void*>	pointers		(symbols.size());
+	int				curScalarOffset	= 0;
+
+	for (int varNdx = 0; varNdx < (int)symbols.size(); ++varNdx)
+	{
+		const Symbol&	var				= symbols[varNdx];
+		const int		scalarSize		= var.varType.getScalarSize();
+
+		// Uses planar layout as input/output specs do not support strides.
+		pointers[varNdx] = &data[curScalarOffset];
+		curScalarOffset += scalarSize*numValues;
+	}
+
+	DE_ASSERT(curScalarOffset == (int)data.size());
+
+	return pointers;
+}
+
+// \todo [2013-08-08 pyry] Make generic utility and move to glu?
+
+struct HexFloat
+{
+	const float value;
+	HexFloat (const float value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const HexFloat& v)
+{
+	return str << v.value << " / " << tcu::toHex(tcu::Float32(v.value).bits());
+}
+
+struct HexBool
+{
+	const deUint32 value;
+	HexBool (const deUint32 value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const HexBool& v)
+{
+	return str << (v.value ? "true" : "false") << " / " << tcu::toHex(v.value);
+}
+
+struct VarValue
+{
+	const glu::VarType&	type;
+	const void*			value;
+
+	VarValue (const glu::VarType& type_, const void* value_) : type(type_), value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const VarValue& varValue)
+{
+	DE_ASSERT(varValue.type.isBasicType());
+
+	const glu::DataType		basicType		= varValue.type.getBasicType();
+	const glu::DataType		scalarType		= glu::getDataTypeScalarType(basicType);
+	const int				numComponents	= glu::getDataTypeScalarSize(basicType);
+
+	if (numComponents > 1)
+		str << glu::getDataTypeName(basicType) << "(";
+
+	for (int compNdx = 0; compNdx < numComponents; compNdx++)
+	{
+		if (compNdx != 0)
+			str << ", ";
+
+		switch (scalarType)
+		{
+			case glu::TYPE_FLOAT:	str << HexFloat(((const float*)varValue.value)[compNdx]);			break;
+			case glu::TYPE_INT:		str << ((const deInt32*)varValue.value)[compNdx];					break;
+			case glu::TYPE_UINT:	str << tcu::toHex(((const deUint32*)varValue.value)[compNdx]);		break;
+			case glu::TYPE_BOOL:	str << HexBool(((const deUint32*)varValue.value)[compNdx]);			break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	if (numComponents > 1)
+		str << ")";
+
+	return str;
+}
+
+CommonFunctionCase::IterateResult CommonFunctionCase::iterate (void)
+{
+	const int				numInputScalars			= computeTotalScalarSize(m_spec.inputs);
+	const int				numOutputScalars		= computeTotalScalarSize(m_spec.outputs);
+	vector<deUint32>		inputData				(numInputScalars * m_numValues);
+	vector<deUint32>		outputData				(numOutputScalars * m_numValues);
+	const vector<void*>		inputPointers			= getInputOutputPointers(m_spec.inputs, inputData, m_numValues);
+	const vector<void*>		outputPointers			= getInputOutputPointers(m_spec.outputs, outputData, m_numValues);
+
+	// Initialize input data.
+	getInputValues(m_numValues, &inputPointers[0]);
+
+	// Execute shader.
+	m_executor->useProgram();
+	m_executor->execute(m_numValues, &inputPointers[0], &outputPointers[0]);
+
+	// Compare results.
+	{
+		const vector<int>		inScalarSizes		= getScalarSizes(m_spec.inputs);
+		const vector<int>		outScalarSizes		= getScalarSizes(m_spec.outputs);
+		vector<void*>			curInputPtr			(inputPointers.size());
+		vector<void*>			curOutputPtr		(outputPointers.size());
+		int						numFailed			= 0;
+
+		for (int valNdx = 0; valNdx < m_numValues; valNdx++)
+		{
+			// Set up pointers for comparison.
+			for (int inNdx = 0; inNdx < (int)curInputPtr.size(); ++inNdx)
+				curInputPtr[inNdx] = (deUint32*)inputPointers[inNdx] + inScalarSizes[inNdx]*valNdx;
+
+			for (int outNdx = 0; outNdx < (int)curOutputPtr.size(); ++outNdx)
+				curOutputPtr[outNdx] = (deUint32*)outputPointers[outNdx] + outScalarSizes[outNdx]*valNdx;
+
+			if (!compare(&curInputPtr[0], &curOutputPtr[0]))
+			{
+				// \todo [2013-08-08 pyry] We probably want to log reference value as well?
+
+				m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed for value " << valNdx << ":\n  " << m_failMsg.str() << TestLog::EndMessage;
+
+				m_testCtx.getLog() << TestLog::Message << "  inputs:" << TestLog::EndMessage;
+				for (int inNdx = 0; inNdx < (int)curInputPtr.size(); inNdx++)
+					m_testCtx.getLog() << TestLog::Message << "    " << m_spec.inputs[inNdx].name << " = "
+														   << VarValue(m_spec.inputs[inNdx].varType, curInputPtr[inNdx])
+									   << TestLog::EndMessage;
+
+				m_testCtx.getLog() << TestLog::Message << "  outputs:" << TestLog::EndMessage;
+				for (int outNdx = 0; outNdx < (int)curOutputPtr.size(); outNdx++)
+					m_testCtx.getLog() << TestLog::Message << "    " << m_spec.outputs[outNdx].name << " = "
+														   << VarValue(m_spec.outputs[outNdx].varType, curOutputPtr[outNdx])
+									   << TestLog::EndMessage;
+
+				m_failMsg.str("");
+				m_failMsg.clear();
+				numFailed += 1;
+			}
+		}
+
+		m_testCtx.getLog() << TestLog::Message << (m_numValues - numFailed) << " / " << m_numValues << " values passed" << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								numFailed == 0 ? "Pass"					: "Result comparison failed");
+	}
+
+	return STOP;
+}
+
+static const char* getPrecisionPostfix (glu::Precision precision)
+{
+	static const char* s_postfix[] =
+	{
+		"_lowp",
+		"_mediump",
+		"_highp"
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_postfix) == glu::PRECISION_LAST);
+	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
+	return s_postfix[precision];
+}
+
+static const char* getShaderTypePostfix (glu::ShaderType shaderType)
+{
+	static const char* s_postfix[] =
+	{
+		"_vertex",
+		"_fragment"
+	};
+	DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
+	return s_postfix[shaderType];
+}
+
+static std::string getCommonFuncCaseName (glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+{
+	return string(glu::getDataTypeName(baseType)) + getPrecisionPostfix(precision) + getShaderTypePostfix(shaderType);
+}
+
+class AbsCase : public CommonFunctionCase
+{
+public:
+	AbsCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "abs", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = abs(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 floatRanges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+		const IVec2 intRanges[] =
+		{
+			IVec2(-(1<<7)+1,	(1<<7)-1),
+			IVec2(-(1<<15)+1,	(1<<15)-1),
+			IVec2(0x80000001,	0x7fffffff)
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0x235facu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		if (glu::isDataTypeFloatOrVec(type))
+			fillRandomScalars(rnd, floatRanges[precision].x(), floatRanges[precision].y(), values[0], numValues*scalarSize);
+		else
+			fillRandomScalars(rnd, intRanges[precision].x(), intRanges[precision].y(), values[0], numValues*scalarSize);
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (glu::isDataTypeFloatOrVec(type))
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= (1u<<(23-mantissaBits))-1u;
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref0		= de::abs(in0);
+				const deUint32	ulpDiff0	= getUlpDiff(out0, ref0);
+
+				if (ulpDiff0 > maxUlpDiff)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref0) << " with ULP threshold " << maxUlpDiff << ", got ULP diff " << ulpDiff0;
+					return false;
+				}
+			}
+		}
+		else
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const int	in0		= ((const int*)inputs[0])[compNdx];
+				const int	out0	= ((const int*)outputs[0])[compNdx];
+				const int	ref0	= de::abs(in0);
+
+				if (out0 != ref0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << ref0;
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class SignCase : public CommonFunctionCase
+{
+public:
+	SignCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "sign", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = sign(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 floatRanges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e4f,		1e4f),	// mediump	- note: may end up as inf
+			Vec2(-1e8f,		1e8f)	// highp	- note: may end up as inf
+		};
+		const IVec2 intRanges[] =
+		{
+			IVec2(-(1<<7),		(1<<7)-1),
+			IVec2(-(1<<15),		(1<<15)-1),
+			IVec2(0x80000000,	0x7fffffff)
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0x324u);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		if (glu::isDataTypeFloatOrVec(type))
+		{
+			// Special cases.
+			std::fill((float*)values[0], (float*)values[0] + scalarSize, +1.0f);
+			std::fill((float*)values[0], (float*)values[0] + scalarSize, -1.0f);
+			std::fill((float*)values[0], (float*)values[0] + scalarSize,  0.0f);
+			fillRandomScalars(rnd, floatRanges[precision].x(), floatRanges[precision].y(), (float*)values[0] + scalarSize*3, (numValues-3)*scalarSize);
+		}
+		else
+		{
+			std::fill((int*)values[0], (int*)values[0] + scalarSize, +1);
+			std::fill((int*)values[0], (int*)values[0] + scalarSize, -1);
+			std::fill((int*)values[0], (int*)values[0] + scalarSize,  0);
+			fillRandomScalars(rnd, intRanges[precision].x(), intRanges[precision].y(), (int*)values[0] + scalarSize*3, (numValues-3)*scalarSize);
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (glu::isDataTypeFloatOrVec(type))
+		{
+			// Both highp and mediump should be able to represent -1, 0, and +1 exactly
+			const deUint32 maxUlpDiff = precision == glu::PRECISION_LOWP ? getMaxUlpDiffFromBits(getMinMantissaBits(precision)) : 0;
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref0		= in0 < 0.0f ? -1.0f :
+											  in0 > 0.0f ? +1.0f : 0.0f;
+				const deUint32	ulpDiff0	= getUlpDiff(out0, ref0);
+
+				if (ulpDiff0 > maxUlpDiff)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref0) << " with ULP threshold " << maxUlpDiff << ", got ULP diff " << ulpDiff0;
+					return false;
+				}
+			}
+		}
+		else
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const int	in0		= ((const int*)inputs[0])[compNdx];
+				const int	out0	= ((const int*)outputs[0])[compNdx];
+				const int	ref0	= in0 < 0 ? -1 :
+									  in0 > 0 ? +1 : 0;
+
+				if (out0 != ref0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << ref0;
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+static float roundEven (float v)
+{
+	const float		q			= deFloatFrac(v);
+	const int		truncated	= int(v-q);
+	const int		rounded		= (q > 0.5f)							? (truncated + 1) :	// Rounded up
+									(q == 0.5f && (truncated % 2 != 0))	? (truncated + 1) :	// Round to nearest even at 0.5
+									truncated;												// Rounded down
+
+	return float(rounded);
+}
+
+class RoundEvenCase : public CommonFunctionCase
+{
+public:
+	RoundEvenCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "roundEven", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = roundEven(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd				(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		int						numSpecialCases	= 0;
+
+		// Special cases.
+		if (precision != glu::PRECISION_LOWP)
+		{
+			DE_ASSERT(numValues >= 20);
+			for (int ndx = 0; ndx < 20; ndx++)
+			{
+				const float v = de::clamp(float(ndx) - 10.5f, ranges[precision].x(), ranges[precision].y());
+				std::fill((float*)values[0], (float*)values[0] + scalarSize, v);
+				numSpecialCases += 1;
+			}
+		}
+
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + numSpecialCases*scalarSize, (numValues-numSpecialCases)*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasSignedZero	= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			// Require exact rounding result.
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref			= roundEven(in0);
+
+				const deUint32	ulpDiff		= hasSignedZero ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref);
+
+				if (ulpDiff > 0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);	// ULP diff for rounded integer value.
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const int		minRes		= int(roundEven(in0-eps));
+				const int		maxRes		= int(roundEven(in0+eps));
+				bool			anyOk		= false;
+
+				for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++)
+				{
+					const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal));
+
+					if (ulpDiff <= maxUlpDiff)
+					{
+						anyOk = true;
+						break;
+					}
+				}
+
+				if (!anyOk)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff);
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class ModfCase : public CommonFunctionCase
+{
+public:
+	ModfCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "modf", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out1", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = modf(in0, out1);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), values[0], numValues*scalarSize);
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasZeroSign		= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		const int				mantissaBits	= getMinMantissaBits(precision);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const float		in0			= ((const float*)inputs[0])[compNdx];
+			const float		out0		= ((const float*)outputs[0])[compNdx];
+			const float		out1		= ((const float*)outputs[1])[compNdx];
+
+			const float		refOut1		= float(int(in0));
+			const float		refOut0		= in0 - refOut1;
+
+			const int		bitsLost	= precision != glu::PRECISION_HIGHP ? numBitsLostInOp(in0, refOut0) : 0;
+			const deUint32	maxUlpDiff	= getMaxUlpDiffFromBits(de::max(mantissaBits - bitsLost, 0));
+
+			const float		resSum		= out0 + out1;
+
+			const deUint32	ulpDiff		= hasZeroSign ? getUlpDiff(resSum, in0) : getUlpDiffIgnoreZeroSign(resSum, in0);
+
+			if (ulpDiff > maxUlpDiff)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = (" << HexFloat(refOut0) << ") + (" << HexFloat(refOut1) << ") = " << HexFloat(in0) << " with ULP threshold "
+							<< tcu::toHex(maxUlpDiff) << ", got ULP diff " << tcu::toHex(ulpDiff);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class IsnanCase : public CommonFunctionCase
+{
+public:
+	IsnanCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "isnan", shaderType)
+	{
+		DE_ASSERT(glu::isDataTypeFloatOrVec(baseType));
+
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	boolType	= vecSize > 1 ? glu::getDataTypeBoolVec(vecSize) : glu::TYPE_BOOL;
+
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(boolType, glu::PRECISION_LAST)));
+		m_spec.source = "out0 = isnan(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd				(deStringHash(getName()) ^ 0xc2a39fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const int				mantissaBits	= getMinMantissaBits(precision);
+		const deUint32			mantissaMask	= ~getMaxUlpDiffFromBits(mantissaBits) & ((1u<<23)-1u);
+
+		for (int valNdx = 0; valNdx < numValues*scalarSize; valNdx++)
+		{
+			const bool		isNan		= rnd.getFloat() > 0.3f;
+			const bool		isInf		= !isNan && rnd.getFloat() > 0.4f;
+			const deUint32	mantissa	= !isInf ? ((1u<<22) | (rnd.getUint32() & mantissaMask)) : 0;
+			const deUint32	exp			= !isNan && !isInf ? (rnd.getUint32() & 0x7fu) : 0xffu;
+			const deUint32	sign		= rnd.getUint32() & 0x1u;
+			const deUint32	value		= (sign << 31) | (exp << 23) | mantissa;
+
+			DE_ASSERT(tcu::Float32(value).isInf() == isInf && tcu::Float32(value).isNaN() == isNan);
+
+			((deUint32*)values[0])[valNdx] = value;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP)
+		{
+			// Only highp is required to support inf/nan
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0		= ((const float*)inputs[0])[compNdx];
+				const deUint32	out0	= ((const deUint32*)outputs[0])[compNdx];
+				const deUint32	ref		= tcu::Float32(in0).isNaN() ? 1u : 0u;
+
+				if (out0 != ref)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexBool(ref);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			// Value can be either 0 or 1
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const int out0 = ((const int*)outputs[0])[compNdx];
+
+				if (out0 != 0 && out0 != 1)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = 0 / 1";
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class IsinfCase : public CommonFunctionCase
+{
+public:
+	IsinfCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "isinf", shaderType)
+	{
+		DE_ASSERT(glu::isDataTypeFloatOrVec(baseType));
+
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	boolType	= vecSize > 1 ? glu::getDataTypeBoolVec(vecSize) : glu::TYPE_BOOL;
+
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(boolType, glu::PRECISION_LAST)));
+		m_spec.source = "out0 = isinf(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd				(deStringHash(getName()) ^ 0xc2a39fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const int				mantissaBits	= getMinMantissaBits(precision);
+		const deUint32			mantissaMask	= ~getMaxUlpDiffFromBits(mantissaBits) & ((1u<<23)-1u);
+
+		for (int valNdx = 0; valNdx < numValues*scalarSize; valNdx++)
+		{
+			const bool		isInf		= rnd.getFloat() > 0.3f;
+			const bool		isNan		= !isInf && rnd.getFloat() > 0.4f;
+			const deUint32	mantissa	= !isInf ? ((1u<<22) | (rnd.getUint32() & mantissaMask)) : 0;
+			const deUint32	exp			= !isNan && !isInf ? (rnd.getUint32() & 0x7fu) : 0xffu;
+			const deUint32	sign		= rnd.getUint32() & 0x1u;
+			const deUint32	value		= (sign << 31) | (exp << 23) | mantissa;
+
+			DE_ASSERT(tcu::Float32(value).isInf() == isInf && tcu::Float32(value).isNaN() == isNan);
+
+			((deUint32*)values[0])[valNdx] = value;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP)
+		{
+			// Only highp is required to support inf/nan
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0		= ((const float*)inputs[0])[compNdx];
+				const deUint32	out0	= ((const deUint32*)outputs[0])[compNdx];
+				const deUint32	ref		= tcu::Float32(in0).isInf() ? 1u : 0u;
+
+				if (out0 != ref)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexBool(ref);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			// Value can be either 0 or 1
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const int out0 = ((const int*)outputs[0])[compNdx];
+
+				if (out0 != 0 && out0 != 1)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = 0 / 1";
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class FloatBitsToUintIntCase : public CommonFunctionCase
+{
+public:
+	FloatBitsToUintIntCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType, bool outIsSigned)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), outIsSigned ? "floatBitsToInt" : "floatBitsToUint", shaderType)
+	{
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	intType		= outIsSigned ? (vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT)
+													  : (vecSize > 1 ? glu::getDataTypeUintVec(vecSize) : glu::TYPE_UINT);
+
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(intType, glu::PRECISION_HIGHP)));
+		m_spec.source = outIsSigned ? "out0 = floatBitsToInt(in0);" : "out0 = floatBitsToUint(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0x2790au);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), values[0], numValues*scalarSize);
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		const int				mantissaBits	= getMinMantissaBits(precision);
+		const int				maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const float		in0			= ((const float*)inputs[0])[compNdx];
+			const deUint32	out0		= ((const deUint32*)outputs[0])[compNdx];
+			const deUint32	refOut0		= tcu::Float32(in0).bits();
+			const int		ulpDiff		= de::abs((int)out0 - (int)refOut0);
+
+			if (ulpDiff > maxUlpDiff)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(refOut0) << " with threshold "
+							<< tcu::toHex(maxUlpDiff) << ", got diff " << tcu::toHex(ulpDiff);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class FloatBitsToIntCase : public FloatBitsToUintIntCase
+{
+public:
+	FloatBitsToIntCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: FloatBitsToUintIntCase(context, baseType, precision, shaderType, true)
+	{
+	}
+};
+
+class FloatBitsToUintCase : public FloatBitsToUintIntCase
+{
+public:
+	FloatBitsToUintCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: FloatBitsToUintIntCase(context, baseType, precision, shaderType, false)
+	{
+	}
+};
+
+class BitsToFloatCase : public CommonFunctionCase
+{
+public:
+	BitsToFloatCase (Context& context, glu::DataType baseType, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, glu::PRECISION_HIGHP, shaderType).c_str(), glu::isDataTypeIntOrIVec(baseType) ? "intBitsToFloat" : "uintBitsToFloat", shaderType)
+	{
+		const bool			inIsSigned	= glu::isDataTypeIntOrIVec(baseType);
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	floatType	= vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT;
+
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(floatType, glu::PRECISION_HIGHP)));
+		m_spec.source = inIsSigned ? "out0 = intBitsToFloat(in0);" : "out0 = uintBitsToFloat(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0xbbb225u);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		const Vec2				range		(-1e8f, +1e8f);
+
+		// \note Filled as floats.
+		fillRandomScalars(rnd, range.x(), range.y(), values[0], numValues*scalarSize);
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const int				maxUlpDiff		= 0;
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const float		in0			= ((const float*)inputs[0])[compNdx];
+			const float		out0		= ((const float*)outputs[0])[compNdx];
+			const int		ulpDiff		= de::abs((int)in0 - (int)out0);
+
+			if (ulpDiff > maxUlpDiff)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(in0) << " with ULP threshold "
+							<< tcu::toHex(maxUlpDiff) << ", got ULP diff " << tcu::toHex(ulpDiff);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class FloorCase : public CommonFunctionCase
+{
+public:
+	FloorCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "floor", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = floor(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0], numValues*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			// Require exact result.
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref			= deFloatFloor(in0);
+
+				const deUint32	ulpDiff		= getUlpDiff(out0, ref);
+
+				if (ulpDiff > 0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);	// ULP diff for rounded integer value.
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const int		minRes		= int(deFloatFloor(in0-eps));
+				const int		maxRes		= int(deFloatFloor(in0+eps));
+				bool			anyOk		= false;
+
+				for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++)
+				{
+					const deUint32 ulpDiff = getUlpDiff(out0, float(roundedVal));
+
+					if (ulpDiff <= maxUlpDiff)
+					{
+						anyOk = true;
+						break;
+					}
+				}
+
+				if (!anyOk)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff);
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class TruncCase : public CommonFunctionCase
+{
+public:
+	TruncCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "trunc", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = trunc(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd				(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const float				specialCases[]	= { 0.0f, -0.0f, -0.9f, 0.9f, 1.0f, -1.0f };
+		const int				numSpecialCases	= DE_LENGTH_OF_ARRAY(specialCases);
+
+		// Special cases
+		for (int caseNdx = 0; caseNdx < numSpecialCases; caseNdx++)
+		{
+			for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
+				((float*)values[0])[caseNdx*scalarSize + scalarNdx] = specialCases[caseNdx];
+		}
+
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + scalarSize*numSpecialCases, (numValues-numSpecialCases)*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasSignedZero	= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			// Require exact result.
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const bool		isNeg		= tcu::Float32(in0).sign() < 0;
+				const float		ref			= isNeg ? (-float(int(-in0))) : float(int(in0));
+
+				const deUint32	ulpDiff		= hasSignedZero ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref);
+
+				if (ulpDiff > 0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);	// ULP diff for rounded integer value.
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const int		minRes		= int(in0-eps);
+				const int		maxRes		= int(in0+eps);
+				bool			anyOk		= false;
+
+				for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++)
+				{
+					const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal));
+
+					if (ulpDiff <= maxUlpDiff)
+					{
+						anyOk = true;
+						break;
+					}
+				}
+
+				if (!anyOk)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff);
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class RoundCase : public CommonFunctionCase
+{
+public:
+	RoundCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "round", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = round(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd				(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		int						numSpecialCases	= 0;
+
+		// Special cases.
+		if (precision != glu::PRECISION_LOWP)
+		{
+			DE_ASSERT(numValues >= 10);
+			for (int ndx = 0; ndx < 10; ndx++)
+			{
+				const float v = de::clamp(float(ndx) - 5.5f, ranges[precision].x(), ranges[precision].y());
+				std::fill((float*)values[0], (float*)values[0] + scalarSize, v);
+				numSpecialCases += 1;
+			}
+		}
+
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + numSpecialCases*scalarSize, (numValues-numSpecialCases)*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasZeroSign		= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+
+				if (deFloatFrac(in0) == 0.5f)
+				{
+					// Allow both ceil(in) and floor(in)
+					const float		ref0		= deFloatFloor(in0);
+					const float		ref1		= deFloatCeil(in0);
+					const deUint32	ulpDiff0	= hasZeroSign ? getUlpDiff(out0, ref0) : getUlpDiffIgnoreZeroSign(out0, ref0);
+					const deUint32	ulpDiff1	= hasZeroSign ? getUlpDiff(out0, ref1) : getUlpDiffIgnoreZeroSign(out0, ref1);
+
+					if (ulpDiff0 > 0 && ulpDiff1 > 0)
+					{
+						m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref0) << " or " << HexFloat(ref1) << ", got ULP diff " << tcu::toHex(de::min(ulpDiff0, ulpDiff1));
+						return false;
+					}
+				}
+				else
+				{
+					// Require exact result
+					const float		ref		= roundEven(in0);
+					const deUint32	ulpDiff	= hasZeroSign ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref);
+
+					if (ulpDiff > 0)
+					{
+						m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+						return false;
+					}
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);	// ULP diff for rounded integer value.
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const int		minRes		= int(roundEven(in0-eps));
+				const int		maxRes		= int(roundEven(in0+eps));
+				bool			anyOk		= false;
+
+				for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++)
+				{
+					const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal));
+
+					if (ulpDiff <= maxUlpDiff)
+					{
+						anyOk = true;
+						break;
+					}
+				}
+
+				if (!anyOk)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff);
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class CeilCase : public CommonFunctionCase
+{
+public:
+	CeilCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "ceil", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = ceil(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0], numValues*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasZeroSign		= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			// Require exact result.
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref			= deFloatCeil(in0);
+
+				const deUint32	ulpDiff		= hasZeroSign ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref);
+
+				if (ulpDiff > 0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);	// ULP diff for rounded integer value.
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const int		minRes		= int(deFloatCeil(in0-eps));
+				const int		maxRes		= int(deFloatCeil(in0+eps));
+				bool			anyOk		= false;
+
+				for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++)
+				{
+					const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal));
+
+					if (ulpDiff <= maxUlpDiff)
+					{
+						anyOk = true;
+						break;
+					}
+				}
+
+				if (!anyOk && de::inRange(0, minRes, maxRes))
+				{
+					// Allow -0 as well.
+					const int ulpDiff = de::abs((int)tcu::Float32(out0).bits() - (int)0x80000000u);
+					anyOk = ((deUint32)ulpDiff <= maxUlpDiff);
+				}
+
+				if (!anyOk)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff);
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class FractCase : public CommonFunctionCase
+{
+public:
+	FractCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "fract", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = fract(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd				(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		int						numSpecialCases	= 0;
+
+		// Special cases.
+		if (precision != glu::PRECISION_LOWP)
+		{
+			DE_ASSERT(numValues >= 10);
+			for (int ndx = 0; ndx < 10; ndx++)
+			{
+				const float v = de::clamp(float(ndx) - 5.5f, ranges[precision].x(), ranges[precision].y());
+				std::fill((float*)values[0], (float*)values[0] + scalarSize, v);
+				numSpecialCases += 1;
+			}
+		}
+
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + numSpecialCases*scalarSize, (numValues-numSpecialCases)*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasZeroSign		= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			// Require exact result.
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref			= deFloatFrac(in0);
+
+				const deUint32	ulpDiff		= hasZeroSign ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref);
+
+				if (ulpDiff > 0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+
+				if (int(deFloatFloor(in0-eps)) == int(deFloatFloor(in0+eps)))
+				{
+					const float		ref			= deFloatFrac(in0);
+					const int		bitsLost	= numBitsLostInOp(in0, ref);
+					const deUint32	maxUlpDiff	= getMaxUlpDiffFromBits(de::max(0, mantissaBits-bitsLost));	// ULP diff for rounded integer value.
+					const deUint32	ulpDiff		= getUlpDiffIgnoreZeroSign(out0, ref);
+
+					if (ulpDiff > maxUlpDiff)
+					{
+						m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << " with ULP threshold " << tcu::toHex(maxUlpDiff) << ", got diff " << tcu::toHex(ulpDiff);
+						return false;
+					}
+				}
+				else
+				{
+					if (out0 >= 1.0f)
+					{
+						m_failMsg << "Expected [" << compNdx << "] < 1.0";
+						return false;
+					}
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+ShaderCommonFunctionTests::ShaderCommonFunctionTests (Context& context)
+	: TestCaseGroup(context, "common", "Common function tests")
+{
+}
+
+ShaderCommonFunctionTests::~ShaderCommonFunctionTests (void)
+{
+}
+
+template<class TestClass>
+static void addFunctionCases (TestCaseGroup* parent, const char* functionName, bool floatTypes, bool intTypes, bool uintTypes)
+{
+	tcu::TestCaseGroup* group = new tcu::TestCaseGroup(parent->getTestContext(), functionName, functionName);
+	parent->addChild(group);
+
+	const glu::DataType scalarTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_INT,
+		glu::TYPE_UINT
+	};
+
+	for (int scalarTypeNdx = 0; scalarTypeNdx < DE_LENGTH_OF_ARRAY(scalarTypes); scalarTypeNdx++)
+	{
+		const glu::DataType scalarType = scalarTypes[scalarTypeNdx];
+
+		if ((!floatTypes && scalarType == glu::TYPE_FLOAT)	||
+			(!intTypes && scalarType == glu::TYPE_INT)		||
+			(!uintTypes && scalarType == glu::TYPE_UINT))
+			continue;
+
+		for (int vecSize = 1; vecSize <= 4; vecSize++)
+		{
+			for (int prec = glu::PRECISION_LOWP; prec <= glu::PRECISION_HIGHP; prec++)
+			{
+				for (int shaderType = glu::SHADERTYPE_VERTEX; shaderType <= glu::SHADERTYPE_FRAGMENT; shaderType++)
+					group->addChild(new TestClass(parent->getContext(), glu::DataType(scalarType + vecSize - 1), glu::Precision(prec), glu::ShaderType(shaderType)));
+			}
+		}
+	}
+}
+
+void ShaderCommonFunctionTests::init (void)
+{
+	//																	Float?	Int?	Uint?
+	addFunctionCases<AbsCase>				(this,	"abs",				true,	true,	false);
+	addFunctionCases<SignCase>				(this,	"sign",				true,	true,	false);
+	addFunctionCases<FloorCase>				(this,	"floor",			true,	false,	false);
+	addFunctionCases<TruncCase>				(this,	"trunc",			true,	false,	false);
+	addFunctionCases<RoundCase>				(this,	"round",			true,	false,	false);
+	addFunctionCases<RoundEvenCase>			(this,	"roundeven",		true,	false,	false);
+	addFunctionCases<CeilCase>				(this,	"ceil",				true,	false,	false);
+	addFunctionCases<FractCase>				(this,	"fract",			true,	false,	false);
+	// mod
+	addFunctionCases<ModfCase>				(this,	"modf",				true,	false,	false);
+	// min
+	// max
+	// clamp
+	// mix
+	// step
+	// smoothstep
+	addFunctionCases<IsnanCase>				(this,	"isnan",			true,	false,	false);
+	addFunctionCases<IsinfCase>				(this,	"isinf",			true,	false,	false);
+	addFunctionCases<FloatBitsToIntCase>	(this,	"floatbitstoint",	true,	false,	false);
+	addFunctionCases<FloatBitsToUintCase>	(this,	"floatbitstouint",	true,	false,	false);
+
+	// (u)intBitsToFloat()
+	{
+		tcu::TestCaseGroup* intGroup	= new tcu::TestCaseGroup(m_testCtx, "intbitstofloat",	"intBitsToFloat() Tests");
+		tcu::TestCaseGroup* uintGroup	= new tcu::TestCaseGroup(m_testCtx, "uintbitstofloat",	"uintBitsToFloat() Tests");
+
+		addChild(intGroup);
+		addChild(uintGroup);
+
+		for (int vecSize = 1; vecSize < 4; vecSize++)
+		{
+			const glu::DataType		intType		= vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
+			const glu::DataType		uintType	= vecSize > 1 ? glu::getDataTypeUintVec(vecSize) : glu::TYPE_UINT;
+
+			for (int shaderType = glu::SHADERTYPE_VERTEX; shaderType <= glu::SHADERTYPE_FRAGMENT; shaderType++)
+			{
+				intGroup->addChild(new BitsToFloatCase(m_context, intType, glu::ShaderType(shaderType)));
+				uintGroup->addChild(new BitsToFloatCase(m_context, uintType, glu::ShaderType(shaderType)));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderCommonFunctionTests.hpp b/modules/gles3/functional/es3fShaderCommonFunctionTests.hpp
new file mode 100644
index 0000000..c00c579
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderCommonFunctionTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERCOMMONFUNCTIONTESTS_HPP
+#define _ES3FSHADERCOMMONFUNCTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Common built-in function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderCommonFunctionTests : public TestCaseGroup
+{
+public:
+									ShaderCommonFunctionTests		(Context& context);
+	virtual							~ShaderCommonFunctionTests		(void);
+
+	virtual void					init							(void);
+
+private:
+									ShaderCommonFunctionTests		(const ShaderCommonFunctionTests&);		// not allowed!
+	ShaderCommonFunctionTests&		operator=						(const ShaderCommonFunctionTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERCOMMONFUNCTIONTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderConstExprTests.cpp b/modules/gles3/functional/es3fShaderConstExprTests.cpp
new file mode 100644
index 0000000..d3bc021
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderConstExprTests.cpp
@@ -0,0 +1,326 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES3 shader constant expression tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderConstExprTests.hpp"
+
+#include "glsShaderLibrary.hpp"
+#include "glsShaderConstExprTests.hpp"
+
+#include "tcuStringTemplate.hpp"
+#include "gluShaderUtil.hpp"
+
+#include "deStringUtil.hpp"
+#include "deMath.h"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+// builtins
+class ShaderConstExprBuiltinTests : public TestCaseGroup
+{
+public:
+				ShaderConstExprBuiltinTests		(Context& context) : TestCaseGroup (context, "builtin_functions", "Builtin functions") {}
+	virtual		~ShaderConstExprBuiltinTests	(void) {}
+
+	void		init							(void);
+
+	void		addChildGroup					(const char* name, const char* desc, const gls::ShaderConstExpr::TestParams* cases, int numCases);
+};
+
+void ShaderConstExprBuiltinTests::addChildGroup (const char* name, const char* desc, const gls::ShaderConstExpr::TestParams* cases, int numCases)
+{
+	const std::vector<tcu::TestNode*>	children = createTests(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), cases, numCases, glu::GLSL_VERSION_300_ES);
+	tcu::TestCaseGroup*					group	 = new tcu::TestCaseGroup(m_testCtx, name, desc);
+
+	addChild(group);
+
+	for (int i = 0; i < (int)children.size(); i++)
+		group->addChild(children[i]);
+}
+
+void ShaderConstExprBuiltinTests::init (void)
+{
+	using namespace gls::ShaderConstExpr;
+
+	// ${T} => final type, ${MT} => final type but with scalar version usable even when T is a vector
+
+	// Trigonometry
+	{
+		const TestParams cases[] =
+		{
+			{"radians",			"radians(${T} (90.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatRadians(90.0f)		},
+			{"degrees",			"degrees(${T} (2.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatDegrees(2.0f)		},
+			{"sin",				"sin(${T} (3.0))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatSin(3.0f)			},
+			{"cos",				"cos(${T} (3.2))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatCos(3.2f)			},
+			{"tan",				"tan(${T} (1.5))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatTan(1.5f)			},
+			{"asin",			"asin(${T} (0.0))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAsin(0.0f)			},
+			{"acos",			"acos(${T} (1.0))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAcos(1.0f)			},
+			{"atan_separate",	"atan(${T} (-1.0), ${T} (-1.0))",						glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAtan2(-1.0f, -1.0f)	},
+			{"atan_combined",	"atan(${T} (2.0))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAtanOver(2.0f)		},
+			{"sinh",			"sinh(${T} (1.5))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatSinh(1.5f)			},
+			{"cosh",			"cosh(${T} (1.5))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatCosh(1.5f)			},
+			{"tanh",			"tanh(${T} (1.5))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatTanh(1.5f)			},
+			{"asinh",			"asinh(${T} (2.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAsinh(2.0f)			},
+			{"acosh",			"acosh(${T} (2.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAcosh(2.0f)			},
+			{"atanh",			"atanh(${T} (0.8))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatAtanh(0.8f)			},
+		};
+
+		addChildGroup("angle_and_trigonometry", "Angles and Trigonometry", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Exponential
+	{
+		const TestParams cases[] =
+		{
+			{"pow",				"pow(${T} (1.7), ${T} (3.5))",							glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatPow(1.7f, 3.5f)		},
+			{"exp",				"exp(${T} (4.2))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatExp(4.2f)			},
+			{"log",				"log(${T} (42.12))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatLog(42.12f)			},
+			{"exp2",			"exp2(${T} (6.7))",										glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatExp2(6.7f)			},
+			{"log2",			"log2(${T} (100.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatLog2(100.0f)			},
+			{"sqrt",			"sqrt(${T} (10.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatSqrt(10.0f)			},
+			{"inversesqrt",		"inversesqrt(${T} (10.0))",								glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatRsq(10.0f)			},
+		};
+
+		addChildGroup("exponential", "Exponential", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Common
+	{
+		const TestParams cases[] =
+		{
+			{"abs",				"abs(${T} (-42.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 42.0f						},
+			{"abs",				"abs(${T} (-42))",										glu::TYPE_INT,   1, 4, glu::TYPE_INT,   42.0f						},
+			{"sign",			"sign(${T} (-18.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, -1.0f						},
+			{"sign",			"sign(${T} (-18))",										glu::TYPE_INT,   1, 4, glu::TYPE_INT,	-1.0f						},
+			{"floor",			"floor(${T} (37.3))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatFloor(37.3f)			},
+			{"trunc",			"trunc(${T} (-1.8))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, -1.0f						},
+			{"round",			"round(${T} (42.7))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 42.0f						},
+			{"roundEven",		"roundEven(${T} (1.5))",								glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT,  2.0f						},
+			{"ceil",			"ceil(${T} (82.2))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatCeil(82.2f)			},
+			{"fract",			"fract(${T} (17.75))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatFrac(17.75f)			},
+			{"mod",				"mod(${T} (87.65), ${MT} (3.7))",						glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, deFloatMod(87.65f, 3.7f)	},
+			// modf cannot be tested due to lacking valid ways of using the 'out' parameter in a constant expression
+			{"min",				"min(${T} (12.3), ${MT} (32.1))",						glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 12.3f						},
+			{"min",				"min(${T} (13), ${MT} (-14))",							glu::TYPE_INT,   1, 4, glu::TYPE_INT,  -14.0f						},
+			{"min",				"min(${T} (13), ${MT} (14))",							glu::TYPE_UINT,  1, 4, glu::TYPE_UINT,	13.0f						},
+			{"max",				"max(${T} (12.3), ${MT} (32.1))",						glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 32.1f						},
+			{"max",				"max(${T} (13), ${MT} (-14))",							glu::TYPE_INT,   1, 4, glu::TYPE_INT,	13.0f						},
+			{"max",				"max(${T} (13), ${MT} (14))",							glu::TYPE_UINT,  1, 4, glu::TYPE_UINT,	14.0f						},
+			{"clamp",			"clamp(${T} (42.1),	${MT} (10.0), ${MT} (15.0))",		glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 15.0f						},
+			{"clamp",			"clamp(${T} (42), ${MT} (-10), ${MT} (15))",			glu::TYPE_INT,   1, 4, glu::TYPE_INT,	15.0f						},
+			{"clamp",			"clamp(${T} (42), ${MT} (10), ${MT} (15))",				glu::TYPE_UINT,  1, 4, glu::TYPE_UINT,	15.0f						},
+
+			{"mix",				"mix(${T} (10.0), ${T} (20.0), ${MT}(0.75))",			glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 17.5f						},
+			{"mix_float_bool",	"mix(float(10.0), float(20.0), bool(1))",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 20.0f						},
+			{"mix_vec2_bvec2",	"mix(vec2(10.0), vec2(20.0), bvec2(1)).x",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 20.0f						},
+			{"mix_vec3_bvec3",	"mix(vec3(10.0), vec3(20.0), bvec3(1)).x",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 20.0f						},
+			{"mix_vec4_bvec4",	"mix(vec4(10.0), vec4(20.0), bvec4(1)).x",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 20.0f						},
+
+			{"step",			"step(${MT} (3.2), ${T} (4.2))",						glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 1.0f						},
+			{"smoothstep",		"smoothstep(${MT} (3.0), ${MT} (5.0), ${T} (4.0))",		glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 0.5f						},
+			{"isnan",			"isnan(${T} (1.3))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_BOOL,  0.0f						},
+			{"isinf",			"isinf(${T} (1.3))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_BOOL,  0.0f						},
+			{"floatbits_int",	"intBitsToFloat(floatBitsToInt(42.12))",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 42.12f						},
+			{"floatbits_uint",	"uintBitsToFloat(floatBitsToUint(-14.2))",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -14.2f						},
+		};
+
+		addChildGroup("common", "Common", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Floating point pack & unpack
+	{
+		const TestParams cases[] =
+		{
+			{"packSnorm2x16",	"packSnorm2x16(vec2(0.7, 0.0))",						glu::TYPE_FLOAT, 1, 1, glu::TYPE_UINT,  22937.0f					},
+			{"unpackSnorm2x16",	"unpackSnorm2x16(22937u).x",							glu::TYPE_UINT,  1, 1, glu::TYPE_FLOAT, 0.7f						},
+			{"packUnorm2x16",	"packUnorm2x16(vec2(0.6, -0.3))",						glu::TYPE_FLOAT, 1, 1, glu::TYPE_UINT,	39321.0f					},
+			{"unpackUnorm2x16",	"unpackUnorm2x16(39321u).x",							glu::TYPE_UINT,  1, 1, glu::TYPE_FLOAT, 0.6f						},
+			{"packHalf2x16",	"unpackHalf2x16(packHalf2x16(vec2(0.3, 0.1))).x",		glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 0.3f						},
+			// \todo [2014-01-29 otto] Separate testing of half-precision pack & unpack
+		};
+
+		addChildGroup("float_pack_unpack", "Floating point pack & unpack", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Geometric
+	{
+		const TestParams cases[] =
+		{
+			{"length_float",	"length(1.0)",											glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"length_vec2",		"length(vec2(1.0))",									glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(2.0f)			},
+			{"length_vec3",		"length(vec3(1.0))",									glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(3.0f)			},
+			{"length_vec4",		"length(vec4(1.0))",									glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(4.0f)			},
+
+			{"distance_float",	"distance(1.0, 2.0)",									glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"distance_vec2",	"distance(vec2(1.0), vec2(2.0))",						glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(2.0f)			},
+			{"distance_vec3",	"distance(vec3(1.0), vec3(2.0))",						glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(3.0f)			},
+			{"distance_vec4",	"distance(vec4(1.0), vec4(2.0))",						glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatSqrt(4.0f)			},
+
+			{"dot_float",		"dot(1.0, 1.0)",										glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"dot_vec2",		"dot(vec2(1.0), vec2(1.0))",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.0f						},
+			{"dot_vec3",		"dot(vec3(1.0), vec3(1.0))",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 3.0f						},
+			{"dot_vec4",		"dot(vec4(1.0), vec4(1.0))",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 4.0f						},
+
+			{"normalize_float",	"normalize(1.0)",										glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"normalize_vec2",	"normalize(vec2(1.0)).x",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatRsq(2.0f)			},
+			{"normalize_vec3",	"normalize(vec3(1.0)).x",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatRsq(3.0f)			},
+			{"normalize_vec4",	"normalize(vec4(1.0)).x",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, deFloatRsq(4.0f)			},
+
+			{"faceforward",		"faceforward(${T} (1.0), ${T} (1.0), ${T} (1.0))",		glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, -1.0f						},
+
+			// reflect(I, N) => I - 2*dot(N, I)*N
+			{"reflect_float",	"reflect(1.0, 1.0)",									glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"reflect_vec2",	"reflect(vec2(1.0), vec2(1.0, 0.0)).x",					glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"reflect_vec3",	"reflect(vec3(1.0), vec3(1.0, 0.0, 0.0)).x",			glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"reflect_vec4",	"reflect(vec4(1.0), vec4(1.0, 0.0, 0.0, 0.0)).x",		glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+
+			/*
+			genType refract(genType I, genType N, float eta) =>
+				k = 1.0 - (eta^2)*(1.0-dot(N,I)^2)
+				if k < 0 return 0.0
+				else return eta*I - (eta*dot(N,I) + sqrt(k))*N
+			*/
+			{"refract_float",	"refract(1.0, 1.0, 0.5)",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"refract_vec2",	"refract(vec2(1.0), vec2(1.0, 0.0), 0.5).x",			glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"refract_vec3",	"refract(vec3(1.0), vec3(1.0, 0.0, 0.0), 0.5).x",		glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+			{"refract_vec4",	"refract(vec4(1.0), vec4(1.0, 0.0, 0.0, 0.0), 0.5).x",	glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, -1.0f						},
+		};
+
+		addChildGroup("geometric", "Geometric", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Matrix
+	{
+		const TestParams cases[] =
+		{
+			{"compMult_mat2",	"matrixCompMult(mat2(1.0), mat2(1.0))[0][0]",			glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"compMult_mat3",	"matrixCompMult(mat3(1.0), mat3(1.0))[0][0]",			glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"compMult_mat4",	"matrixCompMult(mat4(1.0), mat4(1.0))[0][0]",			glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 1.0f						},
+			{"outerProd_mat2",	"outerProduct(vec2(3.0), vec2(3.0))[0][0]",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 9.0f						},
+			{"outerProd_mat3",	"outerProduct(vec3(3.0), vec3(3.0))[0][0]",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 9.0f						},
+			{"outerProd_mat4",	"outerProduct(vec4(3.0), vec4(3.0))[0][0]",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 9.0f						},
+
+			{"outerProd_mat2x3","outerProduct(vec3(3.0), vec2(3.0))[0][0]",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 9.0f						},
+			{"outerProd_mat3x2","outerProduct(vec2(3.0), vec3(3.0))[0][0]",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 9.0f						},
+			{"outerProd_mat2x4","outerProduct(vec4(3.0), vec2(3.0))[0][0]",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 9.0f						},
+			{"outerProd_mat4x2","outerProduct(vec2(3.0), vec4(3.0))[0][0]",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 9.0f						},
+			{"outerProd_mat3x4","outerProduct(vec4(3.0), vec3(3.0))[0][0]",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 9.0f						},
+			{"outerProd_mat4x3","outerProduct(vec3(3.0), vec4(3.0))[0][0]",				glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 9.0f						},
+
+			{"transpose_mat2",	"transpose(mat2(2.0))[0][0]",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.0f						},
+			{"transpose_mat3",	"transpose(mat3(2.0))[0][0]",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.0f						},
+			{"transpose_mat4",	"transpose(mat4(2.0))[0][0]",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.0f						},
+			{"transpose_mat3x2","transpose(mat3x2(2.3))[0][0]",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.3f						},
+			{"transpose_mat2x3","transpose(mat2x3(2.3))[0][0]",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.3f						},
+			{"transpose_mat4x2","transpose(mat4x2(2.3))[0][0]",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.3f						},
+			{"transpose_mat4x3","transpose(mat4x3(2.3))[0][0]",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.3f						},
+			{"transpose_mat2x4","transpose(mat2x4(2.3))[0][0]",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.3f						},
+			{"transpose_mat3x4","transpose(mat3x4(2.3))[0][0]",							glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 2.3f						},
+
+			{"determinant_mat2","determinant(mat2(2.0))",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 4.0f						},
+			{"determinant_mat3","determinant(mat3(2.0))",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 8.0f						},
+			{"determinant_mat4","determinant(mat4(2.0))",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 16.0f						},
+
+			{"inverse_mat2",	"inverse(mat2(2.0))[0][0]",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 0.5f						},
+			{"inverse_mat3",	"inverse(mat3(2.0))[0][0]",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 0.5f						},
+			{"inverse_mat4",	"inverse(mat4(2.0))[0][0]",								glu::TYPE_FLOAT, 1, 1, glu::TYPE_FLOAT, 0.5f						},
+		};
+
+		addChildGroup("matrix", "Matrix", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Vector relational
+	{
+		const TestParams cases[] =
+		{
+			{"lessThan",		"lessThan(${T} (1.0), ${T} (2.0))",						glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"lessThan",		"lessThan(${T} (-1), ${T} (2))",						glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"lessThan",		"lessThan(${T} (1), ${T} (2))",							glu::TYPE_UINT,  2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"lessThanEqual",	"lessThanEqual(${T} (1.0), ${T} (1.0))",				glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"lessThanEqual",	"lessThanEqual(${T} (-1), ${T} (-1))",					glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"lessThanEqual",	"lessThanEqual(${T} (1), ${T} (1))",					glu::TYPE_UINT,  2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"greaterThan",		"greaterThan(${T} (1.0), ${T} (2.0))",					glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"greaterThan",		"greaterThan(${T} (-1), ${T} (2))",						glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"greaterThan",		"greaterThan(${T} (1), ${T} (2))",						glu::TYPE_UINT,  2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"greaterThanEqual","greaterThanEqual(${T} (1.0), ${T} (2.0))",				glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"greaterThanEqual","greaterThanEqual(${T} (-1), ${T} (2))",				glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"greaterThanEqual","greaterThanEqual(${T} (1), ${T} (2))",					glu::TYPE_UINT,  2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"equal",			"equal(${T} (1.0), ${T} (1.2))",						glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"equal",			"equal(${T} (1), ${T} (-2))",							glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"equal",			"equal(${T} (1), ${T} (2))",							glu::TYPE_UINT,  2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"equal",			"equal(${T} (true), ${T} (false))",						glu::TYPE_BOOL,  2, 4, glu::TYPE_BOOL,  0.0f						},
+			{"notEqual",		"notEqual(${T} (1.0), ${T} (1.2))",						glu::TYPE_FLOAT, 2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"notEqual",		"notEqual(${T} (1), ${T} (-2))",						glu::TYPE_INT,   2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"notEqual",		"notEqual(${T} (1), ${T} (2))",							glu::TYPE_UINT,  2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"notEqual",		"notEqual(${T} (true), ${T} (false))",					glu::TYPE_BOOL,  2, 4, glu::TYPE_BOOL,  1.0f						},
+			{"any_bvec2",		"any(bvec2(true, false))",								glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  1.0f						},
+			{"any_bvec3",		"any(bvec3(true, false, false))",						glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  1.0f						},
+			{"any_bvec4",		"any(bvec4(true, false, false, false))",				glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  1.0f						},
+			{"all_bvec2",		"all(bvec2(true, false))",								glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  0.0f						},
+			{"all_bvec3",		"all(bvec3(true, false, false))",						glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  0.0f						},
+			{"all_bvec4",		"all(bvec4(true, false, false, false))",				glu::TYPE_BOOL,  1, 1, glu::TYPE_BOOL,  0.0f						},
+			{"not",				"not(${T} (false))",									glu::TYPE_BOOL,  2, 4, glu::TYPE_BOOL,  1.0f						},
+		};
+
+		addChildGroup("vector_relational", "Vector relational", cases, DE_LENGTH_OF_ARRAY(cases));
+	}
+	// Fragment processing (must return zero when used in initilizer with constexpr arguement)
+	{
+		const TestParams cases[] =
+		{
+			{"dFdx",			"dFdx(${T} (123.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 0.0							},
+			{"dFdy",			"dFdx(${T} (234.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 0.0							},
+			{"fwidth",			"fwidth(${T} (345.0))",									glu::TYPE_FLOAT, 1, 4, glu::TYPE_FLOAT, 0.0							},
+		};
+
+		const std::vector<tcu::TestNode*>	children = createTests(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), cases, DE_LENGTH_OF_ARRAY(cases), glu::GLSL_VERSION_300_ES, SHADER_FRAGMENT);
+		tcu::TestCaseGroup*					group	 = new tcu::TestCaseGroup(m_testCtx, "fragment_processing", "Fragment processing");
+
+		addChild(group);
+
+		for (int i = 0; i < (int)children.size(); i++)
+			group->addChild(children[i]);
+	}
+}
+
+// all
+ShaderConstExprTests::ShaderConstExprTests (Context& context)
+	: TestCaseGroup	(context, "constant_expressions", "Constant expressions")
+{
+}
+
+ShaderConstExprTests::~ShaderConstExprTests (void)
+{
+}
+
+void ShaderConstExprTests::init (void)
+{
+	const std::vector<tcu::TestNode*> children = gls::ShaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo()).loadShaderFile("shaders/constant_expressions.test");
+
+	for (int i = 0; i < (int)children.size(); i++)
+		addChild(children[i]);
+
+	addChild(new ShaderConstExprBuiltinTests(m_context));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderConstExprTests.hpp b/modules/gles3/functional/es3fShaderConstExprTests.hpp
new file mode 100644
index 0000000..63c54e8
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderConstExprTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FSHADERCONSTEXPRTESTS_HPP
+#define _ES3FSHADERCONSTEXPRTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES3 shader constant expression tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderConstExprTests : public TestCaseGroup
+{
+public:
+							ShaderConstExprTests		(Context& context);
+	virtual					~ShaderConstExprTests		(void);
+
+	void					init						(void);
+
+private:
+							ShaderConstExprTests		(const ShaderConstExprTests& other);
+	ShaderConstExprTests&	operator=					(const ShaderConstExprTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERCONSTEXPRTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderDerivateTests.cpp b/modules/gles3/functional/es3fShaderDerivateTests.cpp
new file mode 100644
index 0000000..3291e2d
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderDerivateTests.cpp
@@ -0,0 +1,1459 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader derivate function tests.
+ *
+ * \todo [2013-06-25 pyry] Missing features:
+ *  - lines and points
+ *  - projected coordinates
+ *  - continous non-trivial functions (sin, exp)
+ *  - non-continous functions (step)
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderDerivateTests.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluTexture.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuFloat.hpp"
+#include "deRandom.hpp"
+#include "deUniquePtr.hpp"
+#include "deString.h"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "glsShaderRenderCase.hpp" // gls::setupDefaultUniforms()
+
+#include <sstream>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using std::map;
+using tcu::TestLog;
+using std::ostringstream;
+
+enum
+{
+	VIEWPORT_WIDTH		= 167,
+	VIEWPORT_HEIGHT		= 103,
+	FBO_WIDTH			= 99,
+	FBO_HEIGHT			= 133,
+	MAX_FAILED_MESSAGES	= 10
+};
+
+enum DerivateFunc
+{
+	DERIVATE_DFDX	= 0,
+	DERIVATE_DFDY,
+	DERIVATE_FWIDTH,
+
+	DERIVATE_LAST
+};
+
+enum SurfaceType
+{
+	SURFACETYPE_DEFAULT_FRAMEBUFFER = 0,
+	SURFACETYPE_UNORM_FBO,
+	SURFACETYPE_FLOAT_FBO,	// \note Uses RGBA32UI fbo actually, since FP rendertargets are not in core spec.
+
+	SURFACETYPE_LAST
+};
+
+// Utilities
+
+namespace
+{
+
+class AutoFbo
+{
+public:
+	AutoFbo (const glw::Functions& gl)
+		: m_gl	(gl)
+		, m_fbo	(0)
+	{
+	}
+
+	~AutoFbo (void)
+	{
+		if (m_fbo)
+			m_gl.deleteFramebuffers(1, &m_fbo);
+	}
+
+	void gen (void)
+	{
+		DE_ASSERT(!m_fbo);
+		m_gl.genFramebuffers(1, &m_fbo);
+	}
+
+	deUint32 operator* (void) const { return m_fbo; }
+
+private:
+	const glw::Functions&	m_gl;
+	deUint32				m_fbo;
+};
+
+class AutoRbo
+{
+public:
+	AutoRbo (const glw::Functions& gl)
+		: m_gl	(gl)
+		, m_rbo	(0)
+	{
+	}
+
+	~AutoRbo (void)
+	{
+		if (m_rbo)
+			m_gl.deleteRenderbuffers(1, &m_rbo);
+	}
+
+	void gen (void)
+	{
+		DE_ASSERT(!m_rbo);
+		m_gl.genRenderbuffers(1, &m_rbo);
+	}
+
+	deUint32 operator* (void) const { return m_rbo; }
+
+private:
+	const glw::Functions&	m_gl;
+	deUint32				m_rbo;
+};
+
+} // anonymous
+
+static const char* getDerivateFuncName (DerivateFunc func)
+{
+	switch (func)
+	{
+		case DERIVATE_DFDX:		return "dFdx";
+		case DERIVATE_DFDY:		return "dFdy";
+		case DERIVATE_FWIDTH:	return "fwidth";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+static const char* getDerivateFuncCaseName (DerivateFunc func)
+{
+	switch (func)
+	{
+		case DERIVATE_DFDX:		return "dfdx";
+		case DERIVATE_DFDY:		return "dfdy";
+		case DERIVATE_FWIDTH:	return "fwidth";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+static inline tcu::BVec4 getDerivateMask (glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_FLOAT:		return tcu::BVec4(true, false, false, false);
+		case glu::TYPE_FLOAT_VEC2:	return tcu::BVec4(true, true, false, false);
+		case glu::TYPE_FLOAT_VEC3:	return tcu::BVec4(true, true, true, false);
+		case glu::TYPE_FLOAT_VEC4:	return tcu::BVec4(true, true, true, true);
+		default:
+			DE_ASSERT(false);
+			return tcu::BVec4(true);
+	}
+}
+
+static inline tcu::Vec4 readDerivate (const tcu::ConstPixelBufferAccess& surface, const tcu::Vec4& derivScale, const tcu::Vec4& derivBias, int x, int y)
+{
+	return (surface.getPixel(x, y) - derivBias) / derivScale;
+}
+
+static inline tcu::UVec4 getCompExpBits (const tcu::Vec4& v)
+{
+	return tcu::UVec4(tcu::Float32(v[0]).exponentBits(),
+					  tcu::Float32(v[1]).exponentBits(),
+					  tcu::Float32(v[2]).exponentBits(),
+					  tcu::Float32(v[3]).exponentBits());
+}
+
+float computeFloatingPointError (const float value, const int numAccurateBits)
+{
+	const int		numGarbageBits	= 23-numAccurateBits;
+	const deUint32	mask			= (1u<<numGarbageBits)-1u;
+	const int		exp				= tcu::Float32(value).exponent();
+
+	return tcu::Float32::construct(+1, exp, (1u<<23) | mask).asFloat() - tcu::Float32::construct(+1, exp, 1u<<23).asFloat();
+}
+
+static inline tcu::Vec4 getDerivateThreshold (const glu::Precision precision, const tcu::Vec4& valueMin, const tcu::Vec4& valueMax, const tcu::Vec4& expectedDerivate)
+{
+	const int			baseBits		= precision == glu::PRECISION_HIGHP		? 23	:
+										  precision == glu::PRECISION_MEDIUMP	? 10	:
+										  precision == glu::PRECISION_LOWP		? 6		: 0;
+	const tcu::UVec4	derivExp		= getCompExpBits(expectedDerivate);
+	const tcu::UVec4	maxValueExp		= max(getCompExpBits(valueMin), getCompExpBits(valueMax));
+	const tcu::UVec4	numBitsLost		= maxValueExp - min(maxValueExp, derivExp);
+	const tcu::IVec4	numAccurateBits	= max(baseBits - numBitsLost.asInt() - 3, tcu::IVec4(0));
+
+	return tcu::Vec4(computeFloatingPointError(expectedDerivate[0], numAccurateBits[0]),
+					 computeFloatingPointError(expectedDerivate[1], numAccurateBits[1]),
+					 computeFloatingPointError(expectedDerivate[2], numAccurateBits[2]),
+					 computeFloatingPointError(expectedDerivate[3], numAccurateBits[3]));
+}
+
+namespace
+{
+
+struct LogVecComps
+{
+	const tcu::Vec4&	v;
+	int					numComps;
+
+	LogVecComps (const tcu::Vec4& v_, int numComps_)
+		: v			(v_)
+		, numComps	(numComps_)
+	{
+	}
+};
+
+std::ostream& operator<< (std::ostream& str, const LogVecComps& v)
+{
+	DE_ASSERT(de::inRange(v.numComps, 1, 4));
+	if (v.numComps == 1)		return str << v.v[0];
+	else if (v.numComps == 2)	return str << v.v.toWidth<2>();
+	else if (v.numComps == 3)	return str << v.v.toWidth<3>();
+	else						return str << v.v;
+}
+
+} // anonymous
+
+static bool verifyConstantDerivate (tcu::TestLog&						log,
+									const tcu::ConstPixelBufferAccess&	result,
+									const tcu::PixelBufferAccess&		errorMask,
+									glu::DataType						dataType,
+									const tcu::Vec4&					reference,
+									const tcu::Vec4&					threshold,
+									const tcu::Vec4&					scale,
+									const tcu::Vec4&					bias)
+{
+	const int			numComps		= glu::getDataTypeFloatScalars(dataType);
+	const tcu::BVec4	mask			= tcu::logicalNot(getDerivateMask(dataType));
+	int					numFailedPixels	= 0;
+
+	log << TestLog::Message << "Expecting " << LogVecComps(reference, numComps) << " with threshold " << LogVecComps(threshold, numComps) << TestLog::EndMessage;
+
+	for (int y = 0; y < result.getHeight(); y++)
+	{
+		for (int x = 0; x < result.getWidth(); x++)
+		{
+			const tcu::Vec4		resDerivate		= readDerivate(result, scale, bias, x, y);
+			const bool			isOk			= tcu::allEqual(tcu::logicalOr(tcu::lessThanEqual(tcu::abs(reference - resDerivate), threshold), mask), tcu::BVec4(true));
+
+			if (!isOk)
+			{
+				if (numFailedPixels < MAX_FAILED_MESSAGES)
+					log << TestLog::Message << "FAIL: got " << LogVecComps(resDerivate, numComps)
+											<< ", diff = " << LogVecComps(tcu::abs(reference - resDerivate), numComps)
+											<< ", at x = " << x << ", y = " << y
+						<< TestLog::EndMessage;
+				numFailedPixels += 1;
+				errorMask.setPixel(tcu::RGBA::red.toVec(), x, y);
+			}
+		}
+	}
+
+	if (numFailedPixels >= MAX_FAILED_MESSAGES)
+		log << TestLog::Message << "..." << TestLog::EndMessage;
+
+	if (numFailedPixels > 0)
+		log << TestLog::Message << "FAIL: found " << numFailedPixels << " failed pixels" << TestLog::EndMessage;
+
+	return numFailedPixels == 0;
+}
+
+// TriangleDerivateCase
+
+class TriangleDerivateCase : public TestCase
+{
+public:
+						TriangleDerivateCase	(Context& context, const char* name, const char* description);
+						~TriangleDerivateCase	(void);
+
+	IterateResult		iterate					(void);
+
+protected:
+	virtual void		setupRenderState		(deUint32 program) { DE_UNREF(program); }
+	virtual bool		verify					(const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask) = DE_NULL;
+
+	tcu::IVec2			getViewportSize			(void) const;
+	tcu::Vec4			getSurfaceThreshold		(void) const;
+
+	glu::DataType		m_dataType;
+	glu::Precision		m_precision;
+
+	glu::DataType		m_coordDataType;
+	glu::Precision		m_coordPrecision;
+
+	std::string			m_fragmentSrc;
+
+	tcu::Vec4			m_coordMin;
+	tcu::Vec4			m_coordMax;
+	tcu::Vec4			m_derivScale;
+	tcu::Vec4			m_derivBias;
+
+	SurfaceType			m_surfaceType;
+	int					m_numSamples;
+	deUint32			m_hint;
+};
+
+TriangleDerivateCase::TriangleDerivateCase (Context& context, const char* name, const char* description)
+	: TestCase			(context, name, description)
+	, m_dataType		(glu::TYPE_LAST)
+	, m_precision		(glu::PRECISION_LAST)
+	, m_coordDataType	(glu::TYPE_LAST)
+	, m_coordPrecision	(glu::PRECISION_LAST)
+	, m_surfaceType		(SURFACETYPE_DEFAULT_FRAMEBUFFER)
+	, m_numSamples		(0)
+	, m_hint			(GL_DONT_CARE)
+{
+	DE_ASSERT(m_surfaceType != SURFACETYPE_DEFAULT_FRAMEBUFFER || m_numSamples == 0);
+}
+
+TriangleDerivateCase::~TriangleDerivateCase (void)
+{
+	TriangleDerivateCase::deinit();
+}
+
+static std::string genVertexSource (glu::DataType coordType, glu::Precision precision)
+{
+	DE_ASSERT(glu::isDataTypeFloatOrVec(coordType));
+
+	const char* vertexTmpl =
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in ${PRECISION} ${DATATYPE} a_coord;\n"
+		"out ${PRECISION} ${DATATYPE} v_coord;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_coord = a_coord;\n"
+		"}\n";
+
+	map<string, string> vertexParams;
+
+	vertexParams["PRECISION"]	= glu::getPrecisionName(precision);
+	vertexParams["DATATYPE"]	= glu::getDataTypeName(coordType);
+
+	return tcu::StringTemplate(vertexTmpl).specialize(vertexParams);
+}
+
+inline tcu::IVec2 TriangleDerivateCase::getViewportSize (void) const
+{
+	if (m_surfaceType == SURFACETYPE_DEFAULT_FRAMEBUFFER)
+	{
+		const int	width	= de::min<int>(m_context.getRenderTarget().getWidth(),	VIEWPORT_WIDTH);
+		const int	height	= de::min<int>(m_context.getRenderTarget().getHeight(),	VIEWPORT_HEIGHT);
+		return tcu::IVec2(width, height);
+	}
+	else
+		return tcu::IVec2(FBO_WIDTH, FBO_HEIGHT);
+}
+
+TriangleDerivateCase::IterateResult TriangleDerivateCase::iterate (void)
+{
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	const glu::ShaderProgram	program			(m_context.getRenderContext(), glu::makeVtxFragSources(genVertexSource(m_coordDataType, m_coordPrecision), m_fragmentSrc));
+	de::Random					rnd				(deStringHash(getName()) ^ 0xbbc24);
+	const bool					useFbo			= m_surfaceType != SURFACETYPE_DEFAULT_FRAMEBUFFER;
+	const deUint32				fboFormat		= m_surfaceType == SURFACETYPE_FLOAT_FBO ? GL_RGBA32UI : GL_RGBA8;
+	const tcu::IVec2			viewportSize	= getViewportSize();
+	const int					viewportX		= useFbo ? 0 : rnd.getInt(0, m_context.getRenderTarget().getWidth()		- viewportSize.x());
+	const int					viewportY		= useFbo ? 0 : rnd.getInt(0, m_context.getRenderTarget().getHeight()	- viewportSize.y());
+	AutoFbo						fbo				(gl);
+	AutoRbo						rbo				(gl);
+	tcu::TextureLevel			result;
+
+	m_testCtx.getLog() << program;
+
+	if (!program.isOk())
+		TCU_FAIL("Compile failed");
+
+	if (useFbo)
+	{
+		m_testCtx.getLog() << TestLog::Message
+						   << "Rendering to FBO, format = " << glu::getPixelFormatStr(fboFormat)
+						   << ", samples = " << m_numSamples
+						   << TestLog::EndMessage;
+
+		fbo.gen();
+		rbo.gen();
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, *rbo);
+		gl.renderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples, fboFormat, viewportSize.x(), viewportSize.y());
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *rbo);
+		TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+	}
+	else
+		m_testCtx.getLog() << TestLog::Message << "Rendering to default framebuffer" << TestLog::EndMessage;
+
+	m_testCtx.getLog() << TestLog::Message << "in: " << m_coordMin << " -> " << m_coordMax << "\n"
+										   << "v_coord.x = in.x * x\n"
+										   << "v_coord.y = in.y * y\n"
+										   << "v_coord.z = in.z * (x+y)/2\n"
+										   << "v_coord.w = in.w * (1 - (x+y)/2)\n"
+					   << TestLog::EndMessage
+					   << TestLog::Message << "u_scale: " << m_derivScale << ", u_bias: " << m_derivBias << " (displayed values have scale/bias removed)" << TestLog::EndMessage
+					   << TestLog::Message << "Viewport: " << viewportSize.x() << "x" << viewportSize.y() << TestLog::EndMessage
+					   << TestLog::Message << "GL_FRAGMENT_SHADER_DERIVATE_HINT: " << glu::getHintModeStr(m_hint) << TestLog::EndMessage;
+
+	// Draw
+	{
+		const float positions[] =
+		{
+			-1.0f, -1.0f, 0.0f, 1.0f,
+			-1.0f,  1.0f, 0.0f, 1.0f,
+			 1.0f, -1.0f, 0.0f, 1.0f,
+			 1.0f,  1.0f, 0.0f, 1.0f
+		};
+		const float coords[] =
+		{
+			m_coordMin.x(), m_coordMin.y(), m_coordMin.z(),							m_coordMax.w(),
+			m_coordMin.x(), m_coordMax.y(), (m_coordMin.z()+m_coordMax.z())*0.5f,	(m_coordMin.w()+m_coordMax.w())*0.5f,
+			m_coordMax.x(), m_coordMin.y(), (m_coordMin.z()+m_coordMax.z())*0.5f,	(m_coordMin.w()+m_coordMax.w())*0.5f,
+			m_coordMax.x(), m_coordMax.y(), m_coordMax.z(),							m_coordMin.w()
+		};
+		const glu::VertexArrayBinding vertexArrays[] =
+		{
+			glu::va::Float("a_position",	4, 4, 0, &positions[0]),
+			glu::va::Float("a_coord",		4, 4, 0, &coords[0])
+		};
+		const deUint16 indices[] = { 0, 2, 1, 2, 3, 1 };
+
+		gl.clearColor(0.125f, 0.25f, 0.5f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		gl.useProgram(program.getProgram());
+
+		{
+			const int	scaleLoc	= gl.getUniformLocation(program.getProgram(), "u_scale");
+			const int	biasLoc		= gl.getUniformLocation(program.getProgram(), "u_bias");
+
+			switch (m_dataType)
+			{
+				case glu::TYPE_FLOAT:
+					gl.uniform1f(scaleLoc, m_derivScale.x());
+					gl.uniform1f(biasLoc, m_derivBias.x());
+					break;
+
+				case glu::TYPE_FLOAT_VEC2:
+					gl.uniform2fv(scaleLoc, 1, m_derivScale.getPtr());
+					gl.uniform2fv(biasLoc, 1, m_derivBias.getPtr());
+					break;
+
+				case glu::TYPE_FLOAT_VEC3:
+					gl.uniform3fv(scaleLoc, 1, m_derivScale.getPtr());
+					gl.uniform3fv(biasLoc, 1, m_derivBias.getPtr());
+					break;
+
+				case glu::TYPE_FLOAT_VEC4:
+					gl.uniform4fv(scaleLoc, 1, m_derivScale.getPtr());
+					gl.uniform4fv(biasLoc, 1, m_derivBias.getPtr());
+					break;
+
+				default:
+					DE_ASSERT(false);
+			}
+		}
+
+		gls::setupDefaultUniforms(m_context.getRenderContext(), program.getProgram());
+		setupRenderState(program.getProgram());
+
+		gl.hint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, m_hint);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup program state");
+
+		gl.viewport(viewportX, viewportY, viewportSize.x(), viewportSize.y());
+		glu::draw(m_context.getRenderContext(), program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
+				  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+	}
+
+	// Read back results
+	{
+		const bool		isMSAA		= useFbo && m_numSamples > 0;
+		AutoFbo			resFbo		(gl);
+		AutoRbo			resRbo		(gl);
+
+		// Resolve if necessary
+		if (isMSAA)
+		{
+			resFbo.gen();
+			resRbo.gen();
+
+			gl.bindRenderbuffer(GL_RENDERBUFFER, *resRbo);
+			gl.renderbufferStorageMultisample(GL_RENDERBUFFER, 0, fboFormat, viewportSize.x(), viewportSize.y());
+			gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, *resFbo);
+			gl.framebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *resRbo);
+			TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+			gl.blitFramebuffer(0, 0, viewportSize.x(), viewportSize.y(), 0, 0, viewportSize.x(), viewportSize.y(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Resolve blit");
+
+			gl.bindFramebuffer(GL_READ_FRAMEBUFFER, *resFbo);
+		}
+
+		switch (m_surfaceType)
+		{
+			case SURFACETYPE_DEFAULT_FRAMEBUFFER:
+			case SURFACETYPE_UNORM_FBO:
+				result.setStorage(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), viewportSize.x(), viewportSize.y());
+				glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, result);
+				break;
+
+			case SURFACETYPE_FLOAT_FBO:
+			{
+				const tcu::TextureFormat	dataFormat		(tcu::TextureFormat::RGBA, tcu::TextureFormat::FLOAT);
+				const tcu::TextureFormat	transferFormat	(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNSIGNED_INT32);
+
+				result.setStorage(dataFormat, viewportSize.x(), viewportSize.y());
+				glu::readPixels(m_context.getRenderContext(), viewportX, viewportY,
+								tcu::PixelBufferAccess(transferFormat, result.getWidth(), result.getHeight(), result.getDepth(), result.getAccess().getDataPtr()));
+				break;
+			}
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels");
+	}
+
+	// Verify
+	{
+		tcu::Surface errorMask(result.getWidth(), result.getHeight());
+		tcu::clear(errorMask.getAccess(), tcu::RGBA::green.toVec());
+
+		const bool isOk = verify(result.getAccess(), errorMask.getAccess());
+
+		m_testCtx.getLog() << TestLog::ImageSet("Result", "Result images")
+						   << TestLog::Image("Rendered", "Rendered image", result);
+
+		if (!isOk)
+			m_testCtx.getLog() << TestLog::Image("ErrorMask", "Error mask", errorMask);
+
+		m_testCtx.getLog() << TestLog::EndImageSet;
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Image comparison failed");
+	}
+
+	return STOP;
+}
+
+tcu::Vec4 TriangleDerivateCase::getSurfaceThreshold (void) const
+{
+	switch (m_surfaceType)
+	{
+		case SURFACETYPE_DEFAULT_FRAMEBUFFER:
+		{
+			const tcu::PixelFormat	pixelFormat		= m_context.getRenderTarget().getPixelFormat();
+			const tcu::IVec4		channelBits		(pixelFormat.redBits, pixelFormat.greenBits, pixelFormat.blueBits, pixelFormat.alphaBits);
+			const tcu::IVec4		intThreshold	= tcu::IVec4(1) << (8 - channelBits);
+			const tcu::Vec4			normThreshold	= intThreshold.asFloat() / 255.0f;
+
+			return normThreshold;
+		}
+
+		case SURFACETYPE_UNORM_FBO:				return tcu::IVec4(1).asFloat() / 255.0f;
+		case SURFACETYPE_FLOAT_FBO:				return tcu::Vec4(0.0f);
+		default:
+			DE_ASSERT(false);
+			return tcu::Vec4(0.0f);
+	}
+}
+
+// ConstantDerivateCase
+
+class ConstantDerivateCase : public TriangleDerivateCase
+{
+public:
+						ConstantDerivateCase		(Context& context, const char* name, const char* description, DerivateFunc func, glu::DataType type);
+						~ConstantDerivateCase		(void) {}
+
+	void				init						(void);
+
+protected:
+	bool				verify						(const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask);
+
+private:
+	DerivateFunc		m_func;
+};
+
+ConstantDerivateCase::ConstantDerivateCase (Context& context, const char* name, const char* description, DerivateFunc func, glu::DataType type)
+	: TriangleDerivateCase	(context, name, description)
+	, m_func				(func)
+{
+	m_dataType			= type;
+	m_precision			= glu::PRECISION_HIGHP;
+	m_coordDataType		= m_dataType;
+	m_coordPrecision	= m_precision;
+}
+
+void ConstantDerivateCase::init (void)
+{
+	const char* fragmentTmpl =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"uniform ${PRECISION} ${DATATYPE} u_scale;\n"
+		"uniform ${PRECISION} ${DATATYPE} u_bias;\n"
+		"void main (void)\n"
+		"{\n"
+		"	${PRECISION} ${DATATYPE} res = ${FUNC}(${VALUE}) * u_scale + u_bias;\n"
+		"	o_color = ${CAST_TO_OUTPUT};\n"
+		"}\n";
+	map<string, string> fragmentParams;
+	fragmentParams["PRECISION"]			= glu::getPrecisionName(m_precision);
+	fragmentParams["DATATYPE"]			= glu::getDataTypeName(m_dataType);
+	fragmentParams["FUNC"]				= getDerivateFuncName(m_func);
+	fragmentParams["VALUE"]				= m_dataType == glu::TYPE_FLOAT_VEC4 ? "vec4(1.0, 7.2, -1e5, 0.0)" :
+										  m_dataType == glu::TYPE_FLOAT_VEC3 ? "vec3(1e2, 8.0, 0.01)" :
+										  m_dataType == glu::TYPE_FLOAT_VEC2 ? "vec2(-0.0, 2.7)" :
+										  /* TYPE_FLOAT */					   "7.7";
+	fragmentParams["CAST_TO_OUTPUT"]	= m_dataType == glu::TYPE_FLOAT_VEC4 ? "res" :
+										  m_dataType == glu::TYPE_FLOAT_VEC3 ? "vec4(res, 1.0)" :
+										  m_dataType == glu::TYPE_FLOAT_VEC2 ? "vec4(res, 0.0, 1.0)" :
+										  /* TYPE_FLOAT */					   "vec4(res, 0.0, 0.0, 1.0)";
+
+	m_fragmentSrc = tcu::StringTemplate(fragmentTmpl).specialize(fragmentParams);
+
+	m_derivScale	= tcu::Vec4(1e3f, 1e3f, 1e3f, 1e3f);
+	m_derivBias		= tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f);
+}
+
+bool ConstantDerivateCase::verify (const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask)
+{
+	const tcu::Vec4 reference	(0.0f); // Derivate of constant argument should always be 0
+	const tcu::Vec4	threshold	= getSurfaceThreshold() / abs(m_derivScale);
+
+	return verifyConstantDerivate(m_testCtx.getLog(), result, errorMask, m_dataType,
+								  reference, threshold, m_derivScale, m_derivBias);
+}
+
+// LinearDerivateCase
+
+class LinearDerivateCase : public TriangleDerivateCase
+{
+public:
+						LinearDerivateCase		(Context& context, const char* name, const char* description, DerivateFunc func, glu::DataType type, glu::Precision precision, deUint32 hint, SurfaceType surfaceType, int numSamples, const char* fragmentSrcTmpl);
+						~LinearDerivateCase		(void) {}
+
+	void				init					(void);
+
+protected:
+	bool				verify					(const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask);
+
+private:
+	DerivateFunc		m_func;
+	std::string			m_fragmentTmpl;
+};
+
+LinearDerivateCase::LinearDerivateCase (Context& context, const char* name, const char* description, DerivateFunc func, glu::DataType type, glu::Precision precision, deUint32 hint, SurfaceType surfaceType, int numSamples, const char* fragmentSrcTmpl)
+	: TriangleDerivateCase	(context, name, description)
+	, m_func				(func)
+	, m_fragmentTmpl		(fragmentSrcTmpl)
+{
+	m_dataType			= type;
+	m_precision			= precision;
+	m_coordDataType		= m_dataType;
+	m_coordPrecision	= m_precision;
+	m_hint				= hint;
+	m_surfaceType		= surfaceType;
+	m_numSamples		= numSamples;
+}
+
+void LinearDerivateCase::init (void)
+{
+	const tcu::IVec2	viewportSize	= getViewportSize();
+	const float			w				= float(viewportSize.x());
+	const float			h				= float(viewportSize.y());
+	const bool			packToInt		= m_surfaceType == SURFACETYPE_FLOAT_FBO;
+	map<string, string>	fragmentParams;
+
+	fragmentParams["OUTPUT_TYPE"]		= glu::getDataTypeName(packToInt ? glu::TYPE_UINT_VEC4 : glu::TYPE_FLOAT_VEC4);
+	fragmentParams["OUTPUT_PREC"]		= glu::getPrecisionName(packToInt ? glu::PRECISION_HIGHP : m_precision);
+	fragmentParams["PRECISION"]			= glu::getPrecisionName(m_precision);
+	fragmentParams["DATATYPE"]			= glu::getDataTypeName(m_dataType);
+	fragmentParams["FUNC"]				= getDerivateFuncName(m_func);
+
+	if (packToInt)
+	{
+		fragmentParams["CAST_TO_OUTPUT"]	= m_dataType == glu::TYPE_FLOAT_VEC4 ? "floatBitsToUint(res)" :
+											  m_dataType == glu::TYPE_FLOAT_VEC3 ? "floatBitsToUint(vec4(res, 1.0))" :
+											  m_dataType == glu::TYPE_FLOAT_VEC2 ? "floatBitsToUint(vec4(res, 0.0, 1.0))" :
+											  /* TYPE_FLOAT */					   "floatBitsToUint(vec4(res, 0.0, 0.0, 1.0))";
+	}
+	else
+	{
+		fragmentParams["CAST_TO_OUTPUT"]	= m_dataType == glu::TYPE_FLOAT_VEC4 ? "res" :
+											  m_dataType == glu::TYPE_FLOAT_VEC3 ? "vec4(res, 1.0)" :
+											  m_dataType == glu::TYPE_FLOAT_VEC2 ? "vec4(res, 0.0, 1.0)" :
+											  /* TYPE_FLOAT */					   "vec4(res, 0.0, 0.0, 1.0)";
+	}
+
+	m_fragmentSrc = tcu::StringTemplate(m_fragmentTmpl.c_str()).specialize(fragmentParams);
+
+	switch (m_precision)
+	{
+		case glu::PRECISION_HIGHP:
+			m_coordMin = tcu::Vec4(-97.f, 0.2f, 71.f, 74.f);
+			m_coordMax = tcu::Vec4(-13.2f, -77.f, 44.f, 76.f);
+			break;
+
+		case glu::PRECISION_MEDIUMP:
+			m_coordMin = tcu::Vec4(-37.0f, 47.f, -7.f, 0.0f);
+			m_coordMax = tcu::Vec4(-1.0f, 12.f, 7.f, 19.f);
+			break;
+
+		case glu::PRECISION_LOWP:
+			m_coordMin = tcu::Vec4(0.0f, -1.0f, 0.0f, 1.0f);
+			m_coordMax = tcu::Vec4(1.0f, 1.0f, -1.0f, -1.0f);
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	if (m_surfaceType == SURFACETYPE_FLOAT_FBO)
+	{
+		// No scale or bias used for accuracy.
+		m_derivScale	= tcu::Vec4(1.0f);
+		m_derivBias		= tcu::Vec4(0.0f);
+	}
+	else
+	{
+		// Compute scale - bias that normalizes to 0..1 range.
+		const tcu::Vec4 dx = (m_coordMax - m_coordMin) / tcu::Vec4(w, w, w*0.5f, -w*0.5f);
+		const tcu::Vec4 dy = (m_coordMax - m_coordMin) / tcu::Vec4(h, h, h*0.5f, -h*0.5f);
+
+		switch (m_func)
+		{
+			case DERIVATE_DFDX:
+				m_derivScale = 0.5f / dx;
+				break;
+
+			case DERIVATE_DFDY:
+				m_derivScale = 0.5f / dy;
+				break;
+
+			case DERIVATE_FWIDTH:
+				m_derivScale = 0.5f / (tcu::abs(dx) + tcu::abs(dy));
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		m_derivBias = tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f);
+	}
+}
+
+bool LinearDerivateCase::verify (const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask)
+{
+	const tcu::Vec4		xScale				= tcu::Vec4(1.0f, 0.0f, 0.5f, -0.5f);
+	const tcu::Vec4		yScale				= tcu::Vec4(0.0f, 1.0f, 0.5f, -0.5f);
+	const tcu::Vec4		surfaceThreshold	= getSurfaceThreshold() / abs(m_derivScale);
+
+	if (m_func == DERIVATE_DFDX || m_func == DERIVATE_DFDY)
+	{
+		const bool			isX			= m_func == DERIVATE_DFDX;
+		const float			div			= isX ? float(result.getWidth()) : float(result.getHeight());
+		const tcu::Vec4		scale		= isX ? xScale : yScale;
+		const tcu::Vec4		reference	= ((m_coordMax - m_coordMin) / div) * scale;
+		const tcu::Vec4		opThreshold	= getDerivateThreshold(m_precision, m_coordMin*scale, m_coordMax*scale, reference);
+		const tcu::Vec4		threshold	= max(surfaceThreshold, opThreshold);
+
+		return verifyConstantDerivate(m_testCtx.getLog(), result, errorMask, m_dataType,
+									  reference, threshold, m_derivScale, m_derivBias);
+	}
+	else
+	{
+		DE_ASSERT(m_func == DERIVATE_FWIDTH);
+		const float			w			= float(result.getWidth());
+		const float			h			= float(result.getHeight());
+
+		const tcu::Vec4		dx			= ((m_coordMax - m_coordMin) / w) * xScale;
+		const tcu::Vec4		dy			= ((m_coordMax - m_coordMin) / h) * yScale;
+		const tcu::Vec4		reference	= tcu::abs(dx) + tcu::abs(dy);
+		const tcu::Vec4		dxThreshold	= getDerivateThreshold(m_precision, m_coordMin*xScale, m_coordMax*xScale, dx);
+		const tcu::Vec4		dyThreshold	= getDerivateThreshold(m_precision, m_coordMin*yScale, m_coordMax*yScale, dy);
+		const tcu::Vec4		threshold	= max(surfaceThreshold, max(dxThreshold, dyThreshold));
+
+		return verifyConstantDerivate(m_testCtx.getLog(), result, errorMask, m_dataType,
+									  reference, threshold, m_derivScale, m_derivBias);
+	}
+}
+
+// TextureDerivateCase
+
+class TextureDerivateCase : public TriangleDerivateCase
+{
+public:
+						TextureDerivateCase		(Context& context, const char* name, const char* description, DerivateFunc func, glu::DataType type, glu::Precision precision, deUint32 hint, SurfaceType surfaceType, int numSamples);
+						~TextureDerivateCase	(void);
+
+	void				init					(void);
+	void				deinit					(void);
+
+protected:
+	void				setupRenderState		(deUint32 program);
+	bool				verify					(const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask);
+
+private:
+	DerivateFunc		m_func;
+
+	tcu::Vec4			m_texValueMin;
+	tcu::Vec4			m_texValueMax;
+	glu::Texture2D*		m_texture;
+};
+
+TextureDerivateCase::TextureDerivateCase (Context& context, const char* name, const char* description, DerivateFunc func, glu::DataType type, glu::Precision precision, deUint32 hint, SurfaceType surfaceType, int numSamples)
+	: TriangleDerivateCase	(context, name, description)
+	, m_func				(func)
+	, m_texture				(DE_NULL)
+{
+	m_dataType			= type;
+	m_precision			= precision;
+	m_coordDataType		= glu::TYPE_FLOAT_VEC2;
+	m_coordPrecision	= glu::PRECISION_HIGHP;
+	m_hint				= hint;
+	m_surfaceType		= surfaceType;
+	m_numSamples		= numSamples;
+}
+
+TextureDerivateCase::~TextureDerivateCase (void)
+{
+	delete m_texture;
+}
+
+void TextureDerivateCase::init (void)
+{
+	// Generate shader
+	{
+		const char* fragmentTmpl =
+			"#version 300 es\n"
+			"in highp vec2 v_coord;\n"
+			"layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n"
+			"uniform ${PRECISION} sampler2D u_sampler;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_scale;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_bias;\n"
+			"void main (void)\n"
+			"{\n"
+			"	${PRECISION} vec4 tex = texture(u_sampler, v_coord);\n"
+			"	${PRECISION} ${DATATYPE} res = ${FUNC}(tex${SWIZZLE}) * u_scale + u_bias;\n"
+			"	o_color = ${CAST_TO_OUTPUT};\n"
+			"}\n";
+
+		const bool			packToInt		= m_surfaceType == SURFACETYPE_FLOAT_FBO;
+		map<string, string> fragmentParams;
+
+		fragmentParams["OUTPUT_TYPE"]		= glu::getDataTypeName(packToInt ? glu::TYPE_UINT_VEC4 : glu::TYPE_FLOAT_VEC4);
+		fragmentParams["OUTPUT_PREC"]		= glu::getPrecisionName(packToInt ? glu::PRECISION_HIGHP : m_precision);
+		fragmentParams["PRECISION"]			= glu::getPrecisionName(m_precision);
+		fragmentParams["DATATYPE"]			= glu::getDataTypeName(m_dataType);
+		fragmentParams["FUNC"]				= getDerivateFuncName(m_func);
+		fragmentParams["SWIZZLE"]			= m_dataType == glu::TYPE_FLOAT_VEC4 ? "" :
+											  m_dataType == glu::TYPE_FLOAT_VEC3 ? ".xyz" :
+											  m_dataType == glu::TYPE_FLOAT_VEC2 ? ".xy" :
+											  /* TYPE_FLOAT */					   ".x";
+
+		if (packToInt)
+		{
+			fragmentParams["CAST_TO_OUTPUT"]	= m_dataType == glu::TYPE_FLOAT_VEC4 ? "floatBitsToUint(res)" :
+												  m_dataType == glu::TYPE_FLOAT_VEC3 ? "floatBitsToUint(vec4(res, 1.0))" :
+												  m_dataType == glu::TYPE_FLOAT_VEC2 ? "floatBitsToUint(vec4(res, 0.0, 1.0))" :
+												  /* TYPE_FLOAT */					   "floatBitsToUint(vec4(res, 0.0, 0.0, 1.0))";
+		}
+		else
+		{
+			fragmentParams["CAST_TO_OUTPUT"]	= m_dataType == glu::TYPE_FLOAT_VEC4 ? "res" :
+												  m_dataType == glu::TYPE_FLOAT_VEC3 ? "vec4(res, 1.0)" :
+												  m_dataType == glu::TYPE_FLOAT_VEC2 ? "vec4(res, 0.0, 1.0)" :
+												  /* TYPE_FLOAT */					   "vec4(res, 0.0, 0.0, 1.0)";
+		}
+
+		m_fragmentSrc = tcu::StringTemplate(fragmentTmpl).specialize(fragmentParams);
+	}
+
+	// Texture size matches viewport and nearest sampling is used. Thus texture sampling
+	// is equal to just interpolating the texture value range.
+
+	// Determine value range for texture.
+
+	switch (m_precision)
+	{
+		case glu::PRECISION_HIGHP:
+			m_texValueMin = tcu::Vec4(-97.f, 0.2f, 71.f, 74.f);
+			m_texValueMax = tcu::Vec4(-13.2f, -77.f, 44.f, 76.f);
+			break;
+
+		case glu::PRECISION_MEDIUMP:
+			m_texValueMin = tcu::Vec4(-37.0f, 47.f, -7.f, 0.0f);
+			m_texValueMax = tcu::Vec4(-1.0f, 12.f, 7.f, 19.f);
+			break;
+
+		case glu::PRECISION_LOWP:
+			m_texValueMin = tcu::Vec4(0.0f, -1.0f, 0.0f, 1.0f);
+			m_texValueMax = tcu::Vec4(1.0f, 1.0f, -1.0f, -1.0f);
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	// Lowp and mediump cases use RGBA16F format, while highp uses RGBA32F.
+	{
+		const tcu::IVec2 viewportSize = getViewportSize();
+		DE_ASSERT(!m_texture);
+		m_texture = new glu::Texture2D(m_context.getRenderContext(), m_precision == glu::PRECISION_HIGHP ? GL_RGBA32F : GL_RGBA16F, viewportSize.x(), viewportSize.y());
+		m_texture->getRefTexture().allocLevel(0);
+	}
+
+	// Texture coordinates
+	m_coordMin = tcu::Vec4(0.0f);
+	m_coordMax = tcu::Vec4(1.0f);
+
+	// Fill with gradients.
+	{
+		const tcu::PixelBufferAccess level0 = m_texture->getRefTexture().getLevel(0);
+		for (int y = 0; y < level0.getHeight(); y++)
+		{
+			for (int x = 0; x < level0.getWidth(); x++)
+			{
+				const float		xf		= (float(x)+0.5f) / float(level0.getWidth());
+				const float		yf		= (float(y)+0.5f) / float(level0.getHeight());
+				const tcu::Vec4	s		= tcu::Vec4(xf, yf, (xf+yf)/2.0f, 1.0f - (xf+yf)/2.0f);
+
+				level0.setPixel(m_texValueMin + (m_texValueMax - m_texValueMin)*s, x, y);
+			}
+		}
+	}
+
+	m_texture->upload();
+
+	if (m_surfaceType == SURFACETYPE_FLOAT_FBO)
+	{
+		// No scale or bias used for accuracy.
+		m_derivScale	= tcu::Vec4(1.0f);
+		m_derivBias		= tcu::Vec4(0.0f);
+	}
+	else
+	{
+		// Compute scale - bias that normalizes to 0..1 range.
+		const tcu::IVec2	viewportSize	= getViewportSize();
+		const float			w				= float(viewportSize.x());
+		const float			h				= float(viewportSize.y());
+		const tcu::Vec4		dx				= (m_texValueMax - m_texValueMin) / tcu::Vec4(w, w, w*0.5f, -w*0.5f);
+		const tcu::Vec4		dy				= (m_texValueMax - m_texValueMin) / tcu::Vec4(h, h, h*0.5f, -h*0.5f);
+
+		switch (m_func)
+		{
+			case DERIVATE_DFDX:
+				m_derivScale = 0.5f / dx;
+				break;
+
+			case DERIVATE_DFDY:
+				m_derivScale = 0.5f / dy;
+				break;
+
+			case DERIVATE_FWIDTH:
+				m_derivScale = 0.5f / (tcu::abs(dx) + tcu::abs(dy));
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		m_derivBias = tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f);
+	}
+}
+
+void TextureDerivateCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+}
+
+void TextureDerivateCase::setupRenderState (deUint32 program)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const int				texUnit		= 1;
+
+	gl.activeTexture	(GL_TEXTURE0+texUnit);
+	gl.bindTexture		(GL_TEXTURE_2D, m_texture->getGLTexture());
+	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+
+	gl.uniform1i		(gl.getUniformLocation(program, "u_sampler"), texUnit);
+}
+
+bool TextureDerivateCase::verify (const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask)
+{
+	// \note Edges are ignored in comparison
+	if (result.getWidth() < 2 || result.getHeight() < 2)
+		throw tcu::NotSupportedError("Too small viewport");
+
+	tcu::ConstPixelBufferAccess	compareArea			= tcu::getSubregion(result, 1, 1, result.getWidth()-2, result.getHeight()-2);
+	tcu::PixelBufferAccess		maskArea			= tcu::getSubregion(errorMask, 1, 1, errorMask.getWidth()-2, errorMask.getHeight()-2);
+	const tcu::Vec4				xScale				= tcu::Vec4(1.0f, 0.0f, 0.5f, -0.5f);
+	const tcu::Vec4				yScale				= tcu::Vec4(0.0f, 1.0f, 0.5f, -0.5f);
+	const float					w					= float(result.getWidth());
+	const float					h					= float(result.getHeight());
+
+	const tcu::Vec4				surfaceThreshold	= getSurfaceThreshold() / abs(m_derivScale);
+
+	if (m_func == DERIVATE_DFDX || m_func == DERIVATE_DFDY)
+	{
+		const bool			isX			= m_func == DERIVATE_DFDX;
+		const float			div			= isX ? w : h;
+		const tcu::Vec4		scale		= isX ? xScale : yScale;
+		const tcu::Vec4		reference	= ((m_texValueMax - m_texValueMin) / div) * scale;
+		const tcu::Vec4		opThreshold	= getDerivateThreshold(m_precision, m_texValueMin*scale, m_texValueMax*scale, reference);
+		const tcu::Vec4		threshold	= max(surfaceThreshold, opThreshold);
+
+		return verifyConstantDerivate(m_testCtx.getLog(), compareArea, maskArea, m_dataType,
+									  reference, threshold, m_derivScale, m_derivBias);
+	}
+	else
+	{
+		DE_ASSERT(m_func == DERIVATE_FWIDTH);
+		const tcu::Vec4	dx			= ((m_texValueMax - m_texValueMin) / w) * xScale;
+		const tcu::Vec4	dy			= ((m_texValueMax - m_texValueMin) / h) * yScale;
+		const tcu::Vec4	reference	= tcu::abs(dx) + tcu::abs(dy);
+		const tcu::Vec4	dxThreshold	= getDerivateThreshold(m_precision, m_texValueMin*xScale, m_texValueMax*xScale, dx);
+		const tcu::Vec4	dyThreshold	= getDerivateThreshold(m_precision, m_texValueMin*yScale, m_texValueMax*yScale, dy);
+		const tcu::Vec4	threshold	= max(surfaceThreshold, max(dxThreshold, dyThreshold));
+
+		return verifyConstantDerivate(m_testCtx.getLog(), compareArea, maskArea, m_dataType,
+									  reference, threshold, m_derivScale, m_derivBias);
+	}
+}
+
+ShaderDerivateTests::ShaderDerivateTests (Context& context)
+	: TestCaseGroup(context, "derivate", "Derivate Function Tests")
+{
+}
+
+ShaderDerivateTests::~ShaderDerivateTests (void)
+{
+}
+
+struct FunctionSpec
+{
+	std::string		name;
+	DerivateFunc	function;
+	glu::DataType	dataType;
+	glu::Precision	precision;
+
+	FunctionSpec (const std::string& name_, DerivateFunc function_, glu::DataType dataType_, glu::Precision precision_)
+		: name		(name_)
+		, function	(function_)
+		, dataType	(dataType_)
+		, precision	(precision_)
+	{
+	}
+};
+
+void ShaderDerivateTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		const char*		description;
+		const char*		source;
+	} s_linearDerivateCases[] =
+	{
+		{
+			"linear",
+			"Basic derivate of linearly interpolated argument",
+
+			"#version 300 es\n"
+			"in ${PRECISION} ${DATATYPE} v_coord;\n"
+			"layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_scale;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_bias;\n"
+			"void main (void)\n"
+			"{\n"
+			"	${PRECISION} ${DATATYPE} res = ${FUNC}(v_coord) * u_scale + u_bias;\n"
+			"	o_color = ${CAST_TO_OUTPUT};\n"
+			"}\n"
+		},
+		{
+			"in_function",
+			"Derivate of linear function argument",
+
+			"#version 300 es\n"
+			"in ${PRECISION} ${DATATYPE} v_coord;\n"
+			"layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_scale;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_bias;\n"
+			"\n"
+			"${PRECISION} ${DATATYPE} computeRes (${PRECISION} ${DATATYPE} value)\n"
+			"{\n"
+			"	return ${FUNC}(v_coord) * u_scale + u_bias;\n"
+			"}\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	${PRECISION} ${DATATYPE} res = computeRes(v_coord);\n"
+			"	o_color = ${CAST_TO_OUTPUT};\n"
+			"}\n"
+		},
+		{
+			"static_if",
+			"Derivate of linearly interpolated value in static if",
+
+			"#version 300 es\n"
+			"in ${PRECISION} ${DATATYPE} v_coord;\n"
+			"layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_scale;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_bias;\n"
+			"void main (void)\n"
+			"{\n"
+			"	${PRECISION} ${DATATYPE} res;\n"
+			"	if (false)\n"
+			"		res = ${FUNC}(-v_coord) * u_scale + u_bias;\n"
+			"	else\n"
+			"		res = ${FUNC}(v_coord) * u_scale + u_bias;\n"
+			"	o_color = ${CAST_TO_OUTPUT};\n"
+			"}\n"
+		},
+		{
+			"static_loop",
+			"Derivate of linearly interpolated value in static loop",
+
+			"#version 300 es\n"
+			"in ${PRECISION} ${DATATYPE} v_coord;\n"
+			"layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_scale;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_bias;\n"
+			"void main (void)\n"
+			"{\n"
+			"	${PRECISION} ${DATATYPE} res = ${DATATYPE}(0.0);\n"
+			"	for (int i = 0; i < 2; i++)\n"
+			"		res += ${FUNC}(v_coord * float(i));\n"
+			"	res = res * u_scale + u_bias;\n"
+			"	o_color = ${CAST_TO_OUTPUT};\n"
+			"}\n"
+		},
+		{
+			"static_switch",
+			"Derivate of linearly interpolated value in static switch",
+
+			"#version 300 es\n"
+			"in ${PRECISION} ${DATATYPE} v_coord;\n"
+			"layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_scale;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_bias;\n"
+			"void main (void)\n"
+			"{\n"
+			"	${PRECISION} ${DATATYPE} res;\n"
+			"	switch (1)\n"
+			"	{\n"
+			"		case 0:	res = ${FUNC}(-v_coord) * u_scale + u_bias;	break;\n"
+			"		case 1:	res = ${FUNC}(v_coord) * u_scale + u_bias;	break;\n"
+			"	}\n"
+			"	o_color = ${CAST_TO_OUTPUT};\n"
+			"}\n"
+		},
+		{
+			"uniform_if",
+			"Derivate of linearly interpolated value in uniform if",
+
+			"#version 300 es\n"
+			"in ${PRECISION} ${DATATYPE} v_coord;\n"
+			"layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_scale;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_bias;\n"
+			"uniform bool ub_true;\n"
+			"void main (void)\n"
+			"{\n"
+			"	${PRECISION} ${DATATYPE} res;\n"
+			"	if (ub_true)"
+			"		res = ${FUNC}(v_coord) * u_scale + u_bias;\n"
+			"	else\n"
+			"		res = ${FUNC}(-v_coord) * u_scale + u_bias;\n"
+			"	o_color = ${CAST_TO_OUTPUT};\n"
+			"}\n"
+		},
+		{
+			"uniform_loop",
+			"Derivate of linearly interpolated value in uniform loop",
+
+			"#version 300 es\n"
+			"in ${PRECISION} ${DATATYPE} v_coord;\n"
+			"layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_scale;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_bias;\n"
+			"uniform int ui_two;\n"
+			"void main (void)\n"
+			"{\n"
+			"	${PRECISION} ${DATATYPE} res = ${DATATYPE}(0.0);\n"
+			"	for (int i = 0; i < ui_two; i++)\n"
+			"		res += ${FUNC}(v_coord * float(i));\n"
+			"	res = res * u_scale + u_bias;\n"
+			"	o_color = ${CAST_TO_OUTPUT};\n"
+			"}\n"
+		},
+		{
+			"uniform_switch",
+			"Derivate of linearly interpolated value in uniform switch",
+
+			"#version 300 es\n"
+			"in ${PRECISION} ${DATATYPE} v_coord;\n"
+			"layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_scale;\n"
+			"uniform ${PRECISION} ${DATATYPE} u_bias;\n"
+			"uniform int ui_one;\n"
+			"void main (void)\n"
+			"{\n"
+			"	${PRECISION} ${DATATYPE} res;\n"
+			"	switch (ui_one)\n"
+			"	{\n"
+			"		case 0:	res = ${FUNC}(-v_coord) * u_scale + u_bias;	break;\n"
+			"		case 1:	res = ${FUNC}(v_coord) * u_scale + u_bias;	break;\n"
+			"	}\n"
+			"	o_color = ${CAST_TO_OUTPUT};\n"
+			"}\n"
+		},
+	};
+
+	static const struct
+	{
+		const char*		name;
+		SurfaceType		surfaceType;
+		int				numSamples;
+	} s_fboConfigs[] =
+	{
+		{ "fbo",		SURFACETYPE_DEFAULT_FRAMEBUFFER,	0 },
+		{ "fbo_msaa2",	SURFACETYPE_UNORM_FBO,				2 },
+		{ "fbo_msaa4",	SURFACETYPE_UNORM_FBO,				4 },
+		{ "fbo_float",	SURFACETYPE_FLOAT_FBO,				0 },
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		hint;
+	} s_hints[] =
+	{
+		{ "fastest",	GL_FASTEST	},
+		{ "nicest",		GL_NICEST	},
+	};
+
+	static const struct
+	{
+		const char*		name;
+		SurfaceType		surfaceType;
+		int				numSamples;
+	} s_hintFboConfigs[] =
+	{
+		{ "default",		SURFACETYPE_DEFAULT_FRAMEBUFFER,	0 },
+		{ "fbo_msaa4",		SURFACETYPE_UNORM_FBO,				4 },
+		{ "fbo_float",		SURFACETYPE_FLOAT_FBO,				0 }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		SurfaceType		surfaceType;
+		int				numSamples;
+		deUint32		hint;
+	} s_textureConfigs[] =
+	{
+		{ "basic",			SURFACETYPE_DEFAULT_FRAMEBUFFER,	0,	GL_DONT_CARE	},
+		{ "msaa4",			SURFACETYPE_UNORM_FBO,				4,	GL_DONT_CARE	},
+		{ "float_fastest",	SURFACETYPE_FLOAT_FBO,				0,	GL_FASTEST		},
+		{ "float_nicest",	SURFACETYPE_FLOAT_FBO,				0,	GL_NICEST		},
+	};
+
+	// .dfdx, .dfdy, .fwidth
+	for (int funcNdx = 0; funcNdx < DERIVATE_LAST; funcNdx++)
+	{
+		const DerivateFunc			function		= DerivateFunc(funcNdx);
+		tcu::TestCaseGroup* const	functionGroup	= new tcu::TestCaseGroup(m_testCtx, getDerivateFuncCaseName(function), getDerivateFuncName(function));
+		addChild(functionGroup);
+
+		// .constant - no precision variants, checks that derivate of constant arguments is 0
+		{
+			tcu::TestCaseGroup* const constantGroup = new tcu::TestCaseGroup(m_testCtx, "constant", "Derivate of constant argument");
+			functionGroup->addChild(constantGroup);
+
+			for (int vecSize = 1; vecSize <= 4; vecSize++)
+			{
+				const glu::DataType dataType = vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT;
+				constantGroup->addChild(new ConstantDerivateCase(m_context, glu::getDataTypeName(dataType), "", function, dataType));
+			}
+		}
+
+		// Cases based on LinearDerivateCase
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(s_linearDerivateCases); caseNdx++)
+		{
+			tcu::TestCaseGroup* const linearCaseGroup	= new tcu::TestCaseGroup(m_testCtx, s_linearDerivateCases[caseNdx].name, s_linearDerivateCases[caseNdx].description);
+			const char*			source					= s_linearDerivateCases[caseNdx].source;
+			functionGroup->addChild(linearCaseGroup);
+
+			for (int vecSize = 1; vecSize <= 4; vecSize++)
+			{
+				for (int precNdx = 0; precNdx < glu::PRECISION_LAST; precNdx++)
+				{
+					const glu::DataType		dataType		= vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT;
+					const glu::Precision	precision		= glu::Precision(precNdx);
+					const SurfaceType		surfaceType		= SURFACETYPE_DEFAULT_FRAMEBUFFER;
+					const int				numSamples		= 0;
+					const deUint32			hint			= GL_DONT_CARE;
+					ostringstream			caseName;
+
+					if (caseNdx != 0 && precision == glu::PRECISION_LOWP)
+						continue; // Skip as lowp doesn't actually produce any bits when rendered to default FB.
+
+					caseName << glu::getDataTypeName(dataType) << "_" << glu::getPrecisionName(precision);
+
+					linearCaseGroup->addChild(new LinearDerivateCase(m_context, caseName.str().c_str(), "", function, dataType, precision, hint, surfaceType, numSamples, source));
+				}
+			}
+		}
+
+		// Fbo cases
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(s_fboConfigs); caseNdx++)
+		{
+			tcu::TestCaseGroup*	const	fboGroup		= new tcu::TestCaseGroup(m_testCtx, s_fboConfigs[caseNdx].name, "Derivate usage when rendering into FBO");
+			const char*					source			= s_linearDerivateCases[0].source; // use source from .linear group
+			const SurfaceType			surfaceType		= s_fboConfigs[caseNdx].surfaceType;
+			const int					numSamples		= s_fboConfigs[caseNdx].numSamples;
+			functionGroup->addChild(fboGroup);
+
+			for (int vecSize = 1; vecSize <= 4; vecSize++)
+			{
+				for (int precNdx = 0; precNdx < glu::PRECISION_LAST; precNdx++)
+				{
+					const glu::DataType		dataType		= vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT;
+					const glu::Precision	precision		= glu::Precision(precNdx);
+					const deUint32			hint			= GL_DONT_CARE;
+					ostringstream			caseName;
+
+					if (surfaceType != SURFACETYPE_FLOAT_FBO && precision == glu::PRECISION_LOWP)
+						continue; // Skip as lowp doesn't actually produce any bits when rendered to U8 RT.
+
+					caseName << glu::getDataTypeName(dataType) << "_" << glu::getPrecisionName(precision);
+
+					fboGroup->addChild(new LinearDerivateCase(m_context, caseName.str().c_str(), "", function, dataType, precision, hint, surfaceType, numSamples, source));
+				}
+			}
+		}
+
+		// .fastest, .nicest
+		for (int hintCaseNdx = 0; hintCaseNdx < DE_LENGTH_OF_ARRAY(s_hints); hintCaseNdx++)
+		{
+			tcu::TestCaseGroup* const	hintGroup		= new tcu::TestCaseGroup(m_testCtx, s_hints[hintCaseNdx].name, "Shader derivate hints");
+			const char*					source			= s_linearDerivateCases[0].source; // use source from .linear group
+			const deUint32				hint			= s_hints[hintCaseNdx].hint;
+			functionGroup->addChild(hintGroup);
+
+			for (int fboCaseNdx = 0; fboCaseNdx < DE_LENGTH_OF_ARRAY(s_hintFboConfigs); fboCaseNdx++)
+			{
+				tcu::TestCaseGroup*	const	fboGroup		= new tcu::TestCaseGroup(m_testCtx, s_hintFboConfigs[fboCaseNdx].name, "");
+				const SurfaceType			surfaceType		= s_hintFboConfigs[fboCaseNdx].surfaceType;
+				const int					numSamples		= s_hintFboConfigs[fboCaseNdx].numSamples;
+				hintGroup->addChild(fboGroup);
+
+				for (int vecSize = 1; vecSize <= 4; vecSize++)
+				{
+					for (int precNdx = 0; precNdx < glu::PRECISION_LAST; precNdx++)
+					{
+						const glu::DataType		dataType		= vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT;
+						const glu::Precision	precision		= glu::Precision(precNdx);
+						ostringstream			caseName;
+
+						if (surfaceType != SURFACETYPE_FLOAT_FBO && precision == glu::PRECISION_LOWP)
+							continue; // Skip as lowp doesn't actually produce any bits when rendered to U8 RT.
+
+						caseName << glu::getDataTypeName(dataType) << "_" << glu::getPrecisionName(precision);
+
+						fboGroup->addChild(new LinearDerivateCase(m_context, caseName.str().c_str(), "", function, dataType, precision, hint, surfaceType, numSamples, source));
+					}
+				}
+			}
+		}
+
+		// .texture
+		{
+			tcu::TestCaseGroup* const textureGroup = new tcu::TestCaseGroup(m_testCtx, "texture", "Derivate of texture lookup result");
+			functionGroup->addChild(textureGroup);
+
+			for (int texCaseNdx = 0; texCaseNdx < DE_LENGTH_OF_ARRAY(s_textureConfigs); texCaseNdx++)
+			{
+				tcu::TestCaseGroup*	const	caseGroup		= new tcu::TestCaseGroup(m_testCtx, s_textureConfigs[texCaseNdx].name, "");
+				const SurfaceType			surfaceType		= s_textureConfigs[texCaseNdx].surfaceType;
+				const int					numSamples		= s_textureConfigs[texCaseNdx].numSamples;
+				const deUint32				hint			= s_textureConfigs[texCaseNdx].hint;
+				textureGroup->addChild(caseGroup);
+
+				for (int vecSize = 1; vecSize <= 4; vecSize++)
+				{
+					for (int precNdx = 0; precNdx < glu::PRECISION_LAST; precNdx++)
+					{
+						const glu::DataType		dataType		= vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT;
+						const glu::Precision	precision		= glu::Precision(precNdx);
+						ostringstream			caseName;
+
+						if (surfaceType != SURFACETYPE_FLOAT_FBO && precision == glu::PRECISION_LOWP)
+							continue; // Skip as lowp doesn't actually produce any bits when rendered to U8 RT.
+
+						caseName << glu::getDataTypeName(dataType) << "_" << glu::getPrecisionName(precision);
+
+						caseGroup->addChild(new TextureDerivateCase(m_context, caseName.str().c_str(), "", function, dataType, precision, hint, surfaceType, numSamples));
+					}
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderDerivateTests.hpp b/modules/gles3/functional/es3fShaderDerivateTests.hpp
new file mode 100644
index 0000000..ce14ba4
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderDerivateTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERDERIVATETESTS_HPP
+#define _ES3FSHADERDERIVATETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader derivate function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderDerivateTests : public TestCaseGroup
+{
+public:
+							ShaderDerivateTests		(Context& context);
+	virtual					~ShaderDerivateTests	(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderDerivateTests		(const ShaderDerivateTests&);		// not allowed!
+	ShaderDerivateTests&	operator=				(const ShaderDerivateTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERDERIVATETESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderDiscardTests.cpp b/modules/gles3/functional/es3fShaderDiscardTests.cpp
new file mode 100644
index 0000000..c2db43c
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderDiscardTests.cpp
@@ -0,0 +1,364 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader discard statement tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderDiscardTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluTexture.hpp"
+
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using tcu::StringTemplate;
+
+using std::map;
+using std::string;
+using std::ostringstream;
+
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderDiscardCase : public ShaderRenderCase
+{
+public:
+						ShaderDiscardCase			(Context& context, const char* name, const char* description, const char* shaderSource, ShaderEvalFunc evalFunc, bool usesTexture);
+	virtual				~ShaderDiscardCase			(void);
+
+	void				init						(void);
+	void				deinit						(void);
+
+	void				setupUniforms				(int programID, const tcu::Vec4& constCoords);
+
+private:
+	bool				m_usesTexture;
+	glu::Texture2D*		m_brickTexture;
+};
+
+ShaderDiscardCase::ShaderDiscardCase (Context& context, const char* name, const char* description, const char* shaderSource, ShaderEvalFunc evalFunc, bool usesTexture)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, false, evalFunc)
+	, m_usesTexture		(usesTexture)
+	, m_brickTexture	(DE_NULL)
+{
+	m_fragShaderSource	= shaderSource;
+	m_vertShaderSource	=
+		"#version 300 es\n"
+		"in  highp   vec4 a_position;\n"
+		"in  highp   vec4 a_coords;\n"
+		"out mediump vec4 v_color;\n"
+		"out mediump vec4 v_coords;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_Position = a_position;\n"
+		"    v_color = vec4(a_coords.xyz, 1.0);\n"
+		"    v_coords = a_coords;\n"
+		"}\n";
+}
+
+ShaderDiscardCase::~ShaderDiscardCase (void)
+{
+	delete m_brickTexture;
+}
+
+void ShaderDiscardCase::init (void)
+{
+	if (m_usesTexture)
+	{
+		m_brickTexture = glu::Texture2D::create(m_renderCtx, m_ctxInfo, m_testCtx.getArchive(), "data/brick.png");
+		m_textures.push_back(TextureBinding(m_brickTexture, tcu::Sampler(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
+																		 tcu::Sampler::LINEAR, tcu::Sampler::LINEAR)));
+	}
+	gls::ShaderRenderCase::init();
+}
+
+void ShaderDiscardCase::deinit (void)
+{
+	gls::ShaderRenderCase::deinit();
+	delete m_brickTexture;
+	m_brickTexture = DE_NULL;
+}
+
+void ShaderDiscardCase::setupUniforms (int programID, const tcu::Vec4&)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.uniform1i(gl.getUniformLocation(programID, "ut_brick"), 0);
+}
+
+ShaderDiscardTests::ShaderDiscardTests (Context& context)
+	: TestCaseGroup(context, "discard", "Discard statement tests")
+{
+}
+
+ShaderDiscardTests::~ShaderDiscardTests (void)
+{
+}
+
+enum DiscardMode
+{
+	DISCARDMODE_ALWAYS = 0,
+	DISCARDMODE_NEVER,
+	DISCARDMODE_UNIFORM,
+	DISCARDMODE_DYNAMIC,
+	DISCARDMODE_TEXTURE,
+
+	DISCARDMODE_LAST
+};
+
+enum DiscardTemplate
+{
+	DISCARDTEMPLATE_MAIN_BASIC = 0,
+	DISCARDTEMPLATE_FUNCTION_BASIC,
+	DISCARDTEMPLATE_MAIN_STATIC_LOOP,
+	DISCARDTEMPLATE_MAIN_DYNAMIC_LOOP,
+	DISCARDTEMPLATE_FUNCTION_STATIC_LOOP,
+
+	DISCARDTEMPLATE_LAST
+};
+
+// Evaluation functions
+inline void evalDiscardAlways	(ShaderEvalContext& c) { c.discard(); }
+inline void evalDiscardNever	(ShaderEvalContext& c) { c.color.xyz() = c.coords.swizzle(0,1,2); }
+inline void evalDiscardDynamic	(ShaderEvalContext& c) { c.color.xyz() = c.coords.swizzle(0,1,2); if (c.coords.x()+c.coords.y() > 0.0f) c.discard(); }
+
+inline void evalDiscardTexture (ShaderEvalContext& c)
+{
+	c.color.xyz() = c.coords.swizzle(0,1,2);
+	if (c.texture2D(0, c.coords.swizzle(0,1) * 0.25f + 0.5f).x() < 0.7f)
+		c.discard();
+}
+
+static ShaderEvalFunc getEvalFunc (DiscardMode mode)
+{
+	switch (mode)
+	{
+		case DISCARDMODE_ALWAYS:	return evalDiscardAlways;
+		case DISCARDMODE_NEVER:		return evalDiscardNever;
+		case DISCARDMODE_UNIFORM:	return evalDiscardAlways;
+		case DISCARDMODE_DYNAMIC:	return evalDiscardDynamic;
+		case DISCARDMODE_TEXTURE:	return evalDiscardTexture;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return evalDiscardAlways;
+	}
+}
+
+static const char* getTemplate (DiscardTemplate variant)
+{
+	switch (variant)
+	{
+		case DISCARDTEMPLATE_MAIN_BASIC:
+			return "#version 300 es\n"
+				   "in mediump vec4 v_color;\n"
+				   "in mediump vec4 v_coords;\n"
+				   "layout(location = 0) out mediump vec4 o_color;\n"
+				   "uniform sampler2D    ut_brick;\n"
+				   "uniform mediump int  ui_one;\n\n"
+				   "void main (void)\n"
+				   "{\n"
+				   "    o_color = v_color;\n"
+				   "    ${DISCARD};\n"
+				   "}\n";
+
+		case DISCARDTEMPLATE_FUNCTION_BASIC:
+			return "#version 300 es\n"
+				   "in mediump vec4 v_color;\n"
+				   "in mediump vec4 v_coords;\n"
+				   "layout(location = 0) out mediump vec4 o_color;\n"
+				   "uniform sampler2D    ut_brick;\n"
+				   "uniform mediump int  ui_one;\n\n"
+				   "void myfunc (void)\n"
+				   "{\n"
+				   "    ${DISCARD};\n"
+				   "}\n\n"
+				   "void main (void)\n"
+				   "{\n"
+				   "    o_color = v_color;\n"
+				   "    myfunc();\n"
+				   "}\n";
+
+		case DISCARDTEMPLATE_MAIN_STATIC_LOOP:
+			return "#version 300 es\n"
+				   "in mediump vec4 v_color;\n"
+				   "in mediump vec4 v_coords;\n"
+				   "layout(location = 0) out mediump vec4 o_color;\n"
+				   "uniform sampler2D    ut_brick;\n"
+				   "uniform mediump int  ui_one;\n\n"
+				   "void main (void)\n"
+				   "{\n"
+				   "    o_color = v_color;\n"
+				   "    for (int i = 0; i < 2; i++)\n"
+				   "    {\n"
+				   "        if (i > 0)\n"
+				   "            ${DISCARD};\n"
+				   "    }\n"
+				   "}\n";
+
+		case DISCARDTEMPLATE_MAIN_DYNAMIC_LOOP:
+			return "#version 300 es\n"
+				   "in mediump vec4 v_color;\n"
+				   "in mediump vec4 v_coords;\n"
+				   "layout(location = 0) out mediump vec4 o_color;\n"
+				   "uniform sampler2D    ut_brick;\n"
+				   "uniform mediump int  ui_one;\n"
+				   "uniform mediump int  ui_two;\n\n"
+				   "void main (void)\n"
+				   "{\n"
+				   "    o_color = v_color;\n"
+				   "    for (int i = 0; i < ui_two; i++)\n"
+				   "    {\n"
+				   "        if (i > 0)\n"
+				   "            ${DISCARD};\n"
+				   "    }\n"
+				   "}\n";
+
+		case DISCARDTEMPLATE_FUNCTION_STATIC_LOOP:
+			return "#version 300 es\n"
+				   "in mediump vec4 v_color;\n"
+				   "in mediump vec4 v_coords;\n"
+				   "layout(location = 0) out mediump vec4 o_color;\n"
+				   "uniform sampler2D    ut_brick;\n"
+				   "uniform mediump int  ui_one;\n\n"
+				   "void myfunc (void)\n"
+				   "{\n"
+				   "    for (int i = 0; i < 2; i++)\n"
+				   "    {\n"
+				   "        if (i > 0)\n"
+				   "            ${DISCARD};\n"
+				   "    }\n"
+				   "}\n\n"
+				   "void main (void)\n"
+				   "{\n"
+				   "    o_color = v_color;\n"
+				   "    myfunc();\n"
+				   "}\n";
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static const char* getTemplateName (DiscardTemplate variant)
+{
+	switch (variant)
+	{
+		case DISCARDTEMPLATE_MAIN_BASIC:			return "basic";
+		case DISCARDTEMPLATE_FUNCTION_BASIC:		return "function";
+		case DISCARDTEMPLATE_MAIN_STATIC_LOOP:		return "static_loop";
+		case DISCARDTEMPLATE_MAIN_DYNAMIC_LOOP:		return "dynamic_loop";
+		case DISCARDTEMPLATE_FUNCTION_STATIC_LOOP:	return "function_static_loop";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static const char* getModeName (DiscardMode mode)
+{
+	switch (mode)
+	{
+		case DISCARDMODE_ALWAYS:	return "always";
+		case DISCARDMODE_NEVER:		return "never";
+		case DISCARDMODE_UNIFORM:	return "uniform";
+		case DISCARDMODE_DYNAMIC:	return "dynamic";
+		case DISCARDMODE_TEXTURE:	return "texture";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static const char* getTemplateDesc (DiscardTemplate variant)
+{
+	switch (variant)
+	{
+		case DISCARDTEMPLATE_MAIN_BASIC:			return "main";
+		case DISCARDTEMPLATE_FUNCTION_BASIC:		return "function";
+		case DISCARDTEMPLATE_MAIN_STATIC_LOOP:		return "static loop";
+		case DISCARDTEMPLATE_MAIN_DYNAMIC_LOOP:		return "dynamic loop";
+		case DISCARDTEMPLATE_FUNCTION_STATIC_LOOP:	return "static loop in function";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static const char* getModeDesc (DiscardMode mode)
+{
+	switch (mode)
+	{
+		case DISCARDMODE_ALWAYS:	return "Always discard";
+		case DISCARDMODE_NEVER:		return "Never discard";
+		case DISCARDMODE_UNIFORM:	return "Discard based on uniform value";
+		case DISCARDMODE_DYNAMIC:	return "Discard based on varying values";
+		case DISCARDMODE_TEXTURE:	return "Discard based on texture value";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+ShaderDiscardCase* makeDiscardCase (Context& context, DiscardTemplate tmpl, DiscardMode mode)
+{
+	StringTemplate shaderTemplate(getTemplate(tmpl));
+
+	map<string, string> params;
+
+	switch (mode)
+	{
+		case DISCARDMODE_ALWAYS:	params["DISCARD"] = "discard";										break;
+		case DISCARDMODE_NEVER:		params["DISCARD"] = "if (false) discard";							break;
+		case DISCARDMODE_UNIFORM:	params["DISCARD"] = "if (ui_one > 0) discard";						break;
+		case DISCARDMODE_DYNAMIC:	params["DISCARD"] = "if (v_coords.x+v_coords.y > 0.0) discard";		break;
+		case DISCARDMODE_TEXTURE:	params["DISCARD"] = "if (texture(ut_brick, v_coords.xy*0.25+0.5).x < 0.7) discard";	break;
+		default:
+			DE_ASSERT(DE_FALSE);
+			break;
+	}
+
+	string name			= string(getTemplateName(tmpl)) + "_" + getModeName(mode);
+	string description	= string(getModeDesc(mode)) + " in " + getTemplateDesc(tmpl);
+
+	return new ShaderDiscardCase(context, name.c_str(), description.c_str(), shaderTemplate.specialize(params).c_str(), getEvalFunc(mode), mode == DISCARDMODE_TEXTURE);
+}
+
+void ShaderDiscardTests::init (void)
+{
+	for (int tmpl = 0; tmpl < DISCARDTEMPLATE_LAST; tmpl++)
+		for (int mode = 0; mode < DISCARDMODE_LAST; mode++)
+			addChild(makeDiscardCase(m_context, (DiscardTemplate)tmpl, (DiscardMode)mode));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderDiscardTests.hpp b/modules/gles3/functional/es3fShaderDiscardTests.hpp
new file mode 100644
index 0000000..88ae320
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderDiscardTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERDISCARDTESTS_HPP
+#define _ES3FSHADERDISCARDTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader discard statement tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderDiscardTests : public TestCaseGroup
+{
+public:
+							ShaderDiscardTests		(Context& context);
+	virtual					~ShaderDiscardTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderDiscardTests		(const ShaderDiscardTests&);		// not allowed!
+	ShaderDiscardTests&		operator=				(const ShaderDiscardTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERDISCARDTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderFragDataTests.cpp b/modules/gles3/functional/es3fShaderFragDataTests.cpp
new file mode 100644
index 0000000..e96f631
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderFragDataTests.cpp
@@ -0,0 +1,372 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLSL ES 1.0 gl_FragData[] tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderFragDataTests.hpp"
+
+#include "glsShaderLibrary.hpp"
+
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluObjectWrapper.hpp"
+
+#include "tcuRenderTarget.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using tcu::TestLog;
+
+namespace
+{
+
+enum IndexExprType
+{
+	INDEX_EXPR_STATIC	= 0,
+	INDEX_EXPR_UNIFORM,
+	INDEX_EXPR_DYNAMIC,
+
+	INDEX_EXPR_TYPE_LAST
+};
+
+static bool compareSingleColor (tcu::TestLog& log, const tcu::Surface& surface, tcu::RGBA expectedColor, tcu::RGBA threshold)
+{
+	const int	maxPrints			= 10;
+	int			numFailedPixels		= 0;
+
+	log << TestLog::Message << "Expecting " << expectedColor << " with threshold " << threshold << TestLog::EndMessage;
+
+	for (int y = 0; y < surface.getHeight(); y++)
+	{
+		for (int x = 0; x < surface.getWidth(); x++)
+		{
+			const tcu::RGBA		resultColor		= surface.getPixel(x, y);
+			const bool			isOk			= compareThreshold(resultColor, expectedColor, threshold);
+
+			if (!isOk)
+			{
+				if (numFailedPixels < maxPrints)
+					log << TestLog::Message << "ERROR: Got " << resultColor << " at (" << x << ", " << y << ")!" << TestLog::EndMessage;
+				else if (numFailedPixels == maxPrints)
+					log << TestLog::Message << "..." << TestLog::EndMessage;
+
+				numFailedPixels += 1;
+			}
+		}
+	}
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Message << "Found " << numFailedPixels << " invalid pixels, comparison FAILED!" << TestLog::EndMessage;
+		log << TestLog::Image("ResultImage", "Result Image", surface);
+		return false;
+	}
+	else
+	{
+		log << TestLog::Message << "Image comparison passed." << TestLog::EndMessage;
+		return true;
+	}
+}
+
+class FragDataIndexingCase : public TestCase
+{
+public:
+	FragDataIndexingCase (Context& context, const char* name, const char* description, IndexExprType indexExprType)
+		: TestCase			(context, name, description)
+		, m_indexExprType	(indexExprType)
+	{
+	}
+
+	static glu::ProgramSources genSources (const IndexExprType indexExprType)
+	{
+		const char* const	fragIndexExpr	= indexExprType == INDEX_EXPR_STATIC	? "0"				:
+											  indexExprType == INDEX_EXPR_UNIFORM	? "u_index"			:
+											  indexExprType == INDEX_EXPR_DYNAMIC	? "int(v_index)"	: DE_NULL;
+		glu::ProgramSources	sources;
+
+		DE_ASSERT(fragIndexExpr);
+
+		sources << glu::VertexSource(
+			"attribute highp vec4 a_position;\n"
+			"attribute highp float a_index;\n"
+			"attribute highp vec4 a_color;\n"
+			"varying mediump float v_index;\n"
+			"varying mediump vec4 v_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_color = a_color;\n"
+			"	v_index = a_index;\n"
+			"}\n");
+
+		sources << glu::FragmentSource(string(
+			"varying mediump vec4 v_color;\n"
+			"varying mediump float v_index;\n"
+			"uniform mediump int u_index;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_FragData[") + fragIndexExpr + "] = v_color;\n"
+			"}\n");
+
+		return sources;
+	}
+
+	IterateResult iterate (void)
+	{
+		const glu::RenderContext&		renderCtx		= m_context.getRenderContext();
+		const glw::Functions&			gl				= renderCtx.getFunctions();
+		const glu::ShaderProgram		program			(renderCtx, genSources(m_indexExprType));
+		const int						viewportW		= de::min(renderCtx.getRenderTarget().getWidth(), 128);
+		const int						viewportH		= de::min(renderCtx.getRenderTarget().getHeight(), 128);
+
+		const float positions[] =
+		{
+			-1.0f, -1.0f,
+			+1.0f, -1.0f,
+			-1.0f, +1.0f,
+			+1.0f, +1.0f
+		};
+		const float colors[] =
+		{
+			0.0f, 1.0f, 0.0f, 1.0f,
+			0.0f, 1.0f, 0.0f, 1.0f,
+			0.0f, 1.0f, 0.0f, 1.0f,
+			0.0f, 1.0f, 0.0f, 1.0f
+		};
+		const float		indexValues[]	= { 0.0f, 0.0f, 0.0f, 0.0f };
+		const deUint8	indices[]		= { 0, 1, 2, 2, 1, 3 };
+
+		const glu::VertexArrayBinding vertexArrays[] =
+		{
+			glu::va::Float("a_position",	2, 4, 0, &positions[0]),
+			glu::va::Float("a_color",		4, 4, 0, &colors[0]),
+			glu::va::Float("a_index",		1, 4, 0, &indexValues[0])
+		};
+
+		m_testCtx.getLog() << program;
+
+		if (!program.isOk())
+		{
+			if (m_indexExprType == INDEX_EXPR_STATIC)
+				TCU_FAIL("Compile failed");
+			else
+				throw tcu::NotSupportedError("Dynamic indexing of gl_FragData[] not supported");
+		}
+
+		gl.clearColor	(1.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear		(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		gl.viewport		(0, 0, viewportW, viewportH);
+		gl.useProgram	(program.getProgram());
+		gl.uniform1i	(gl.getUniformLocation(program.getProgram(), "u_index"), 0);
+
+		glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
+				  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Rendering failed");
+
+		{
+			tcu::Surface		result		(viewportW, viewportH);
+			const tcu::RGBA		threshold	= renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+			bool				isOk;
+
+			glu::readPixels(renderCtx, 0, 0, result.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");
+
+			isOk = compareSingleColor(m_testCtx.getLog(), result, tcu::RGBA::green, threshold);
+
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	const IndexExprType m_indexExprType;
+};
+
+class FragDataDrawBuffersCase : public TestCase
+{
+public:
+	FragDataDrawBuffersCase (Context& context)
+		: TestCase(context, "draw_buffers", "gl_FragData[] and glDrawBuffers() interaction")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glu::RenderContext&		renderCtx		= m_context.getRenderContext();
+		const glu::ShaderProgram		program			(renderCtx, glu::ProgramSources()
+															<< glu::VertexSource(
+																"attribute highp vec4 a_position;\n"
+																"attribute highp vec4 a_color;\n"
+																"varying mediump vec4 v_color;\n"
+																"void main (void)\n"
+																"{\n"
+																"	gl_Position = a_position;\n"
+																"	v_color = a_color;\n"
+																"}\n")
+															<< glu::FragmentSource(
+																"varying mediump vec4 v_color;\n"
+																"uniform mediump int u_index;\n"
+																"void main (void)\n"
+																"{\n"
+																"	gl_FragData[u_index] = v_color;\n"
+																"}\n"));
+		const glw::Functions&			gl				= renderCtx.getFunctions();
+		const int						width			= 128;
+		const int						height			= 128;
+		const int						indexLoc		= program.isOk() ? gl.getUniformLocation(program.getProgram(), "u_index") : -1;
+		const glu::Framebuffer			fbo				(renderCtx);
+		const glu::Renderbuffer			colorBuf0		(renderCtx);
+		const glu::Renderbuffer			colorBuf1		(renderCtx);
+
+		const float positions[] =
+		{
+			-1.0f, -1.0f,
+			+1.0f, -1.0f,
+			-1.0f, +1.0f,
+			+1.0f, +1.0f
+		};
+		const float colors[] =
+		{
+			0.0f, 1.0f, 0.0f, 1.0f,
+			0.0f, 1.0f, 0.0f, 1.0f,
+			0.0f, 1.0f, 0.0f, 1.0f,
+			0.0f, 1.0f, 0.0f, 1.0f
+		};
+		const deUint8	indices[]		= { 0, 1, 2, 2, 1, 3 };
+
+		const glu::VertexArrayBinding vertexArrays[] =
+		{
+			glu::va::Float("a_position",	2, 4, 0, &positions[0]),
+			glu::va::Float("a_color",		4, 4, 0, &colors[0])
+		};
+
+		m_testCtx.getLog() << program;
+
+		if (!program.isOk())
+			throw tcu::NotSupportedError("Dynamic indexing of gl_FragData[] not supported");
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			const deUint32	rbo	= ndx == 0 ? *colorBuf0 : *colorBuf1;
+
+			gl.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+			gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
+			gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+ndx, GL_RENDERBUFFER, rbo);
+		}
+		TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+		{
+			const deUint32 drawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
+			gl.drawBuffers(DE_LENGTH_OF_ARRAY(drawBuffers), &drawBuffers[0]);
+		}
+
+		gl.clearBufferfv(GL_COLOR, 0, tcu::RGBA::red.toVec().getPtr());
+		gl.clearBufferfv(GL_COLOR, 1, tcu::RGBA::red.toVec().getPtr());
+
+		gl.viewport		(0, 0, width, height);
+		gl.useProgram	(program.getProgram());
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Drawing to attachments 0 and 1, expecting only attachment 0 to change." << TestLog::EndMessage;
+
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			gl.uniform1i(indexLoc, ndx);
+			glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
+					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+		}
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Rendering failed");
+
+		{
+			tcu::Surface		result		(width, height);
+			const tcu::RGBA		threshold	= renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+			bool				allOk		= true;
+
+			for (int ndx = 0; ndx < 2; ndx++)
+			{
+				m_testCtx.getLog() << TestLog::Message << "Verifying attachment " << ndx << "..." << TestLog::EndMessage;
+
+				gl.readBuffer(GL_COLOR_ATTACHMENT0+ndx);
+				glu::readPixels(renderCtx, 0, 0, result.getAccess());
+				GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");
+
+				if (!compareSingleColor(m_testCtx.getLog(), result, ndx == 0 ? tcu::RGBA::green : tcu::RGBA::red, threshold))
+					allOk = false;
+			}
+
+			m_testCtx.setTestResult(allOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									allOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+} // anonymous
+
+ShaderFragDataTests::ShaderFragDataTests (Context& context)
+	: TestCaseGroup(context, "fragdata", "gl_FragData[] Tests")
+{
+}
+
+ShaderFragDataTests::~ShaderFragDataTests (void)
+{
+}
+
+void ShaderFragDataTests::init (void)
+{
+	addChild(new FragDataIndexingCase		(m_context, "valid_static_index",	"Valid gl_FragData[] assignment using static index",	INDEX_EXPR_STATIC));
+	addChild(new FragDataIndexingCase		(m_context, "valid_uniform_index",	"Valid gl_FragData[] assignment using uniform index",	INDEX_EXPR_UNIFORM));
+	addChild(new FragDataIndexingCase		(m_context, "valid_dynamic_index",	"Valid gl_FragData[] assignment using dynamic index",	INDEX_EXPR_DYNAMIC));
+	addChild(new FragDataDrawBuffersCase	(m_context));
+
+	// Negative cases.
+	{
+		gls::ShaderLibrary library(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
+		std::vector<tcu::TestNode*> negativeCases = library.loadShaderFile("shaders/fragdata.test");
+
+		for (std::vector<tcu::TestNode*>::iterator i = negativeCases.begin(); i != negativeCases.end(); i++)
+			addChild(*i);
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderFragDataTests.hpp b/modules/gles3/functional/es3fShaderFragDataTests.hpp
new file mode 100644
index 0000000..ac218c1
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderFragDataTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERFRAGDATATESTS_HPP
+#define _ES3FSHADERFRAGDATATESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLSL ES 1.0 gl_FragData[] tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderFragDataTests : public TestCaseGroup
+{
+public:
+							ShaderFragDataTests		(Context& context);
+	virtual					~ShaderFragDataTests	(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderFragDataTests		(const ShaderFragDataTests&);		// not allowed!
+	ShaderFragDataTests&	operator=				(const ShaderFragDataTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERFRAGDATATESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderIndexingTests.cpp b/modules/gles3/functional/es3fShaderIndexingTests.cpp
new file mode 100644
index 0000000..b7fe3ce
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderIndexingTests.cpp
@@ -0,0 +1,1138 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader indexing (arrays, vector, matrices) tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderIndexingTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuStringTemplate.hpp"
+
+#include "deInt32.h"
+#include "deMemory.h"
+
+#include <map>
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using namespace std;
+using namespace tcu;
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+enum IndexAccessType
+{
+	INDEXACCESS_STATIC = 0,
+	INDEXACCESS_DYNAMIC,
+	INDEXACCESS_STATIC_LOOP,
+	INDEXACCESS_DYNAMIC_LOOP,
+
+	INDEXACCESS_LAST
+};
+
+static const char* getIndexAccessTypeName (IndexAccessType accessType)
+{
+	static const char* s_names[INDEXACCESS_LAST] =
+	{
+		"static",
+		"dynamic",
+		"static_loop",
+		"dynamic_loop"
+	};
+
+	DE_ASSERT(deInBounds32((int)accessType, 0, INDEXACCESS_LAST));
+	return s_names[(int)accessType];
+}
+
+enum VectorAccessType
+{
+	DIRECT = 0,
+	COMPONENT,
+	SUBSCRIPT_STATIC,
+	SUBSCRIPT_DYNAMIC,
+	SUBSCRIPT_STATIC_LOOP,
+	SUBSCRIPT_DYNAMIC_LOOP,
+
+	VECTORACCESS_LAST
+};
+
+static const char* getVectorAccessTypeName (VectorAccessType accessType)
+{
+	static const char* s_names[VECTORACCESS_LAST] =
+	{
+		"direct",
+		"component",
+		"static_subscript",
+		"dynamic_subscript",
+		"static_loop_subscript",
+		"dynamic_loop_subscript"
+	};
+
+	DE_ASSERT(deInBounds32((int)accessType, 0, VECTORACCESS_LAST));
+	return s_names[(int)accessType];
+}
+
+void evalArrayCoordsFloat		(ShaderEvalContext& c) { c.color.x()	= 1.875f * c.coords.x(); }
+void evalArrayCoordsVec2		(ShaderEvalContext& c) { c.color.xy()	= 1.875f * c.coords.swizzle(0,1); }
+void evalArrayCoordsVec3		(ShaderEvalContext& c) { c.color.xyz()	= 1.875f * c.coords.swizzle(0,1,2); }
+void evalArrayCoordsVec4		(ShaderEvalContext& c) { c.color		= 1.875f * c.coords; }
+
+static ShaderEvalFunc getArrayCoordsEvalFunc (DataType dataType)
+{
+	if (dataType == TYPE_FLOAT)				return evalArrayCoordsFloat;
+	else if (dataType == TYPE_FLOAT_VEC2)	return evalArrayCoordsVec2;
+	else if (dataType == TYPE_FLOAT_VEC3)	return evalArrayCoordsVec3;
+	else if (dataType == TYPE_FLOAT_VEC4)	return evalArrayCoordsVec4;
+
+	DE_ASSERT(!"Invalid data type.");
+	return NULL;
+}
+
+void evalArrayUniformFloat		(ShaderEvalContext& c) { c.color.x()	= 1.875f * c.constCoords.x(); }
+void evalArrayUniformVec2		(ShaderEvalContext& c) { c.color.xy()	= 1.875f * c.constCoords.swizzle(0,1); }
+void evalArrayUniformVec3		(ShaderEvalContext& c) { c.color.xyz()	= 1.875f * c.constCoords.swizzle(0,1,2); }
+void evalArrayUniformVec4		(ShaderEvalContext& c) { c.color		= 1.875f * c.constCoords; }
+
+static ShaderEvalFunc getArrayUniformEvalFunc (DataType dataType)
+{
+	if (dataType == TYPE_FLOAT)				return evalArrayUniformFloat;
+	else if (dataType == TYPE_FLOAT_VEC2)	return evalArrayUniformVec2;
+	else if (dataType == TYPE_FLOAT_VEC3)	return evalArrayUniformVec3;
+	else if (dataType == TYPE_FLOAT_VEC4)	return evalArrayUniformVec4;
+
+	DE_ASSERT(!"Invalid data type.");
+	return NULL;
+}
+
+// ShaderIndexingCase
+
+class ShaderIndexingCase : public ShaderRenderCase
+{
+public:
+								ShaderIndexingCase		(Context& context, const char* name, const char* description, bool isVertexCase, DataType varType, ShaderEvalFunc evalFunc, const char* vertShaderSource, const char* fragShaderSource);
+	virtual						~ShaderIndexingCase		(void);
+
+private:
+								ShaderIndexingCase		(const ShaderIndexingCase&);	// not allowed!
+	ShaderIndexingCase&			operator=				(const ShaderIndexingCase&);	// not allowed!
+
+	virtual void				setup					(int programID);
+	virtual void				setupUniforms			(int programID, const Vec4& constCoords);
+
+	DataType					m_varType;
+};
+
+ShaderIndexingCase::ShaderIndexingCase (Context& context, const char* name, const char* description, bool isVertexCase, DataType varType, ShaderEvalFunc evalFunc, const char* vertShaderSource, const char* fragShaderSource)
+	: ShaderRenderCase(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
+{
+	m_varType			= varType;
+	m_vertShaderSource	= vertShaderSource;
+	m_fragShaderSource	= fragShaderSource;
+}
+
+ShaderIndexingCase::~ShaderIndexingCase (void)
+{
+}
+
+void ShaderIndexingCase::setup (int programID)
+{
+	DE_UNREF(programID);
+}
+
+void ShaderIndexingCase::setupUniforms (int programID, const Vec4& constCoords)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	DE_UNREF(constCoords);
+
+	int arrLoc = gl.getUniformLocation(programID, "u_arr");
+	if (arrLoc != -1)
+	{
+		//int scalarSize = getDataTypeScalarSize(m_varType);
+		if (m_varType == TYPE_FLOAT)
+		{
+			float arr[4];
+			arr[0] = constCoords.x();
+			arr[1] = constCoords.x() * 0.5f;
+			arr[2] = constCoords.x() * 0.25f;
+			arr[3] = constCoords.x() * 0.125f;
+			gl.uniform1fv(arrLoc, 4, &arr[0]);
+		}
+		else if (m_varType == TYPE_FLOAT_VEC2)
+		{
+			Vec2 arr[4];
+			arr[0] = constCoords.swizzle(0,1);
+			arr[1] = constCoords.swizzle(0,1) * 0.5f;
+			arr[2] = constCoords.swizzle(0,1) * 0.25f;
+			arr[3] = constCoords.swizzle(0,1) * 0.125f;
+			gl.uniform2fv(arrLoc, 4, arr[0].getPtr());
+		}
+		else if (m_varType == TYPE_FLOAT_VEC3)
+		{
+			Vec3 arr[4];
+			arr[0] = constCoords.swizzle(0,1,2);
+			arr[1] = constCoords.swizzle(0,1,2) * 0.5f;
+			arr[2] = constCoords.swizzle(0,1,2) * 0.25f;
+			arr[3] = constCoords.swizzle(0,1,2) * 0.125f;
+			gl.uniform3fv(arrLoc, 4, arr[0].getPtr());
+		}
+		else if (m_varType == TYPE_FLOAT_VEC4)
+		{
+			Vec4 arr[4];
+			arr[0] = constCoords.swizzle(0,1,2,3);
+			arr[1] = constCoords.swizzle(0,1,2,3) * 0.5f;
+			arr[2] = constCoords.swizzle(0,1,2,3) * 0.25f;
+			arr[3] = constCoords.swizzle(0,1,2,3) * 0.125f;
+			gl.uniform4fv(arrLoc, 4, arr[0].getPtr());
+		}
+		else
+			throw tcu::TestError("u_arr should not have location assigned in this test case");
+	}
+}
+
+// Helpers.
+
+static ShaderIndexingCase* createVaryingArrayCase (Context& context, const char* caseName, const char* description, DataType varType, IndexAccessType vertAccess, IndexAccessType fragAccess)
+{
+	std::ostringstream vtx;
+	vtx << "#version 300 es\n";
+	vtx << "in highp vec4 a_position;\n";
+	vtx << "in highp vec4 a_coords;\n";
+	if (vertAccess == INDEXACCESS_DYNAMIC)
+		vtx << "uniform mediump int ui_zero, ui_one, ui_two, ui_three;\n";
+	else if (vertAccess == INDEXACCESS_DYNAMIC_LOOP)
+		vtx << "uniform mediump int ui_four;\n";
+	vtx << "out ${PRECISION} ${VAR_TYPE} var[${ARRAY_LEN}];\n";
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+	if (vertAccess == INDEXACCESS_STATIC)
+	{
+		vtx << "	var[0] = ${VAR_TYPE}(a_coords);\n";
+		vtx << "	var[1] = ${VAR_TYPE}(a_coords) * 0.5;\n";
+		vtx << "	var[2] = ${VAR_TYPE}(a_coords) * 0.25;\n";
+		vtx << "	var[3] = ${VAR_TYPE}(a_coords) * 0.125;\n";
+	}
+	else if (vertAccess == INDEXACCESS_DYNAMIC)
+	{
+		vtx << "	var[ui_zero]  = ${VAR_TYPE}(a_coords);\n";
+		vtx << "	var[ui_one]   = ${VAR_TYPE}(a_coords) * 0.5;\n";
+		vtx << "	var[ui_two]   = ${VAR_TYPE}(a_coords) * 0.25;\n";
+		vtx << "	var[ui_three] = ${VAR_TYPE}(a_coords) * 0.125;\n";
+	}
+	else if (vertAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		vtx << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(a_coords);\n";
+		vtx << "	for (int i = 0; i < 4; i++)\n";
+		vtx << "	{\n";
+		vtx << "		var[i] = ${VAR_TYPE}(coords);\n";
+		vtx << "		coords = coords * 0.5;\n";
+		vtx << "	}\n";
+	}
+	else
+	{
+		DE_ASSERT(vertAccess == INDEXACCESS_DYNAMIC_LOOP);
+		vtx << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(a_coords);\n";
+		vtx << "	for (int i = 0; i < ui_four; i++)\n";
+		vtx << "	{\n";
+		vtx << "		var[i] = ${VAR_TYPE}(coords);\n";
+		vtx << "		coords = coords * 0.5;\n";
+		vtx << "	}\n";
+	}
+	vtx << "}\n";
+
+	std::ostringstream frag;
+	frag << "#version 300 es\n";
+	frag << "precision mediump int;\n";
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+	if (fragAccess == INDEXACCESS_DYNAMIC)
+		frag << "uniform mediump int ui_zero, ui_one, ui_two, ui_three;\n";
+	else if (fragAccess == INDEXACCESS_DYNAMIC_LOOP)
+		frag << "uniform int ui_four;\n";
+	frag << "in ${PRECISION} ${VAR_TYPE} var[${ARRAY_LEN}];\n";
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+	frag << "	${PRECISION} ${VAR_TYPE} res = ${VAR_TYPE}(0.0);\n";
+	if (fragAccess == INDEXACCESS_STATIC)
+	{
+		frag << "	res += var[0];\n";
+		frag << "	res += var[1];\n";
+		frag << "	res += var[2];\n";
+		frag << "	res += var[3];\n";
+	}
+	else if (fragAccess == INDEXACCESS_DYNAMIC)
+	{
+		frag << "	res += var[ui_zero];\n";
+		frag << "	res += var[ui_one];\n";
+		frag << "	res += var[ui_two];\n";
+		frag << "	res += var[ui_three];\n";
+	}
+	else if (fragAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		frag << "	for (int i = 0; i < 4; i++)\n";
+		frag << "		res += var[i];\n";
+	}
+	else
+	{
+		DE_ASSERT(fragAccess == INDEXACCESS_DYNAMIC_LOOP);
+		frag << "	for (int i = 0; i < ui_four; i++)\n";
+		frag << "		res += var[i];\n";
+	}
+	frag << "	o_color = vec4(res${PADDING});\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("VAR_TYPE", getDataTypeName(varType)));
+	params.insert(pair<string, string>("ARRAY_LEN", "4"));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+
+	if (varType == TYPE_FLOAT)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC2)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC3)
+		params.insert(pair<string, string>("PADDING", ", 1.0"));
+	else
+		params.insert(pair<string, string>("PADDING", ""));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	ShaderEvalFunc evalFunc = getArrayCoordsEvalFunc(varType);
+	return new ShaderIndexingCase(context, caseName, description, true, varType, evalFunc, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+static ShaderIndexingCase* createUniformArrayCase (Context& context, const char* caseName, const char* description, bool isVertexCase, DataType varType, IndexAccessType readAccess)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n";
+	frag << "#version 300 es\n";
+
+	vtx << "in highp vec4 a_position;\n";
+	vtx << "in highp vec4 a_coords;\n";
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "out mediump vec4 v_coords;\n";
+		frag << "in mediump vec4 v_coords;\n";
+	}
+
+	if (readAccess == INDEXACCESS_DYNAMIC)
+		op << "uniform mediump int ui_zero, ui_one, ui_two, ui_three;\n";
+	else if (readAccess == INDEXACCESS_DYNAMIC_LOOP)
+		op << "uniform mediump int ui_four;\n";
+
+	op << "uniform ${PRECISION} ${VAR_TYPE} u_arr[${ARRAY_LEN}];\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Read array.
+	op << "	${PRECISION} ${VAR_TYPE} res = ${VAR_TYPE}(0.0);\n";
+	if (readAccess == INDEXACCESS_STATIC)
+	{
+		op << "	res += u_arr[0];\n";
+		op << "	res += u_arr[1];\n";
+		op << "	res += u_arr[2];\n";
+		op << "	res += u_arr[3];\n";
+	}
+	else if (readAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "	res += u_arr[ui_zero];\n";
+		op << "	res += u_arr[ui_one];\n";
+		op << "	res += u_arr[ui_two];\n";
+		op << "	res += u_arr[ui_three];\n";
+	}
+	else if (readAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < 4; i++)\n";
+		op << "		res += u_arr[i];\n";
+	}
+	else
+	{
+		DE_ASSERT(readAccess == INDEXACCESS_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < ui_four; i++)\n";
+		op << "		res += u_arr[i];\n";
+	}
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = vec4(res${PADDING});\n";
+		frag << "	o_color = v_color;\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	o_color = vec4(res${PADDING});\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("VAR_TYPE", getDataTypeName(varType)));
+	params.insert(pair<string, string>("ARRAY_LEN", "4"));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+
+	if (varType == TYPE_FLOAT)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC2)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC3)
+		params.insert(pair<string, string>("PADDING", ", 1.0"));
+	else
+		params.insert(pair<string, string>("PADDING", ""));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	ShaderEvalFunc evalFunc = getArrayUniformEvalFunc(varType);
+	return new ShaderIndexingCase(context, caseName, description, isVertexCase, varType, evalFunc, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+static ShaderIndexingCase* createTmpArrayCase (Context& context, const char* caseName, const char* description, bool isVertexCase, DataType varType, IndexAccessType writeAccess, IndexAccessType readAccess)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n";
+	frag << "#version 300 es\n";
+
+	vtx << "in highp vec4 a_position;\n";
+	vtx << "in highp vec4 a_coords;\n";
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "out mediump vec4 v_coords;\n";
+		frag << "in mediump vec4 v_coords;\n";
+	}
+
+	if (writeAccess == INDEXACCESS_DYNAMIC || readAccess == INDEXACCESS_DYNAMIC)
+		op << "uniform mediump int ui_zero, ui_one, ui_two, ui_three;\n";
+
+	if (writeAccess == INDEXACCESS_DYNAMIC_LOOP || readAccess == INDEXACCESS_DYNAMIC_LOOP)
+		op << "uniform mediump int ui_four;\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Write array.
+	if (isVertexCase)
+		op << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(a_coords);\n";
+	else
+		op << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(v_coords);\n";
+
+	op << "	${PRECISION} ${VAR_TYPE} arr[${ARRAY_LEN}];\n";
+	if (writeAccess == INDEXACCESS_STATIC)
+	{
+		op << "	arr[0] = ${VAR_TYPE}(coords);\n";
+		op << "	arr[1] = ${VAR_TYPE}(coords) * 0.5;\n";
+		op << "	arr[2] = ${VAR_TYPE}(coords) * 0.25;\n";
+		op << "	arr[3] = ${VAR_TYPE}(coords) * 0.125;\n";
+	}
+	else if (writeAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "	arr[ui_zero]  = ${VAR_TYPE}(coords);\n";
+		op << "	arr[ui_one]   = ${VAR_TYPE}(coords) * 0.5;\n";
+		op << "	arr[ui_two]   = ${VAR_TYPE}(coords) * 0.25;\n";
+		op << "	arr[ui_three] = ${VAR_TYPE}(coords) * 0.125;\n";
+	}
+	else if (writeAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < 4; i++)\n";
+		op << "	{\n";
+		op << "		arr[i] = ${VAR_TYPE}(coords);\n";
+		op << "		coords = coords * 0.5;\n";
+		op << "	}\n";
+	}
+	else
+	{
+		DE_ASSERT(writeAccess == INDEXACCESS_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < ui_four; i++)\n";
+		op << "	{\n";
+		op << "		arr[i] = ${VAR_TYPE}(coords);\n";
+		op << "		coords = coords * 0.5;\n";
+		op << "	}\n";
+	}
+
+	// Read array.
+	op << "	${PRECISION} ${VAR_TYPE} res = ${VAR_TYPE}(0.0);\n";
+	if (readAccess == INDEXACCESS_STATIC)
+	{
+		op << "	res += arr[0];\n";
+		op << "	res += arr[1];\n";
+		op << "	res += arr[2];\n";
+		op << "	res += arr[3];\n";
+	}
+	else if (readAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "	res += arr[ui_zero];\n";
+		op << "	res += arr[ui_one];\n";
+		op << "	res += arr[ui_two];\n";
+		op << "	res += arr[ui_three];\n";
+	}
+	else if (readAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < 4; i++)\n";
+		op << "		res += arr[i];\n";
+	}
+	else
+	{
+		DE_ASSERT(readAccess == INDEXACCESS_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < ui_four; i++)\n";
+		op << "		res += arr[i];\n";
+	}
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = vec4(res${PADDING});\n";
+		frag << "	o_color = v_color;\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	o_color = vec4(res${PADDING});\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("VAR_TYPE", getDataTypeName(varType)));
+	params.insert(pair<string, string>("ARRAY_LEN", "4"));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+
+	if (varType == TYPE_FLOAT)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC2)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 1.0"));
+	else if (varType == TYPE_FLOAT_VEC3)
+		params.insert(pair<string, string>("PADDING", ", 1.0"));
+	else
+		params.insert(pair<string, string>("PADDING", ""));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	ShaderEvalFunc evalFunc = getArrayCoordsEvalFunc(varType);
+	return new ShaderIndexingCase(context, caseName, description, isVertexCase, varType, evalFunc, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+// VECTOR SUBSCRIPT.
+
+void evalSubscriptVec2 (ShaderEvalContext& c) { c.color.xyz() = Vec3(c.coords.x() + 0.5f*c.coords.y()); }
+void evalSubscriptVec3 (ShaderEvalContext& c) { c.color.xyz() = Vec3(c.coords.x() + 0.5f*c.coords.y() + 0.25f*c.coords.z()); }
+void evalSubscriptVec4 (ShaderEvalContext& c) { c.color.xyz() = Vec3(c.coords.x() + 0.5f*c.coords.y() + 0.25f*c.coords.z() + 0.125f*c.coords.w()); }
+
+static ShaderEvalFunc getVectorSubscriptEvalFunc (DataType dataType)
+{
+	if (dataType == TYPE_FLOAT_VEC2)		return evalSubscriptVec2;
+	else if (dataType == TYPE_FLOAT_VEC3)	return evalSubscriptVec3;
+	else if (dataType == TYPE_FLOAT_VEC4)	return evalSubscriptVec4;
+
+	DE_ASSERT(!"Invalid data type.");
+	return NULL;
+}
+
+static ShaderIndexingCase* createVectorSubscriptCase (Context& context, const char* caseName, const char* description, bool isVertexCase, DataType varType, VectorAccessType writeAccess, VectorAccessType readAccess)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	int			vecLen		= getDataTypeScalarSize(varType);
+	const char*	vecLenName	= getIntUniformName(vecLen);
+
+	vtx << "#version 300 es\n";
+	frag << "#version 300 es\n";
+
+	vtx << "in highp vec4 a_position;\n";
+	vtx << "in highp vec4 a_coords;\n";
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec3 v_color;\n";
+		frag << "in mediump vec3 v_color;\n";
+	}
+	else
+	{
+		vtx << "out mediump vec4 v_coords;\n";
+		frag << "in mediump vec4 v_coords;\n";
+	}
+
+	if (writeAccess == SUBSCRIPT_DYNAMIC || readAccess == SUBSCRIPT_DYNAMIC)
+	{
+		op << "uniform mediump int ui_zero";
+		if (vecLen >= 2) op << ", ui_one";
+		if (vecLen >= 3) op << ", ui_two";
+		if (vecLen >= 4) op << ", ui_three";
+		op << ";\n";
+	}
+
+	if (writeAccess == SUBSCRIPT_DYNAMIC_LOOP || readAccess == SUBSCRIPT_DYNAMIC_LOOP)
+		op << "uniform mediump int " << vecLenName << ";\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Write vector.
+	if (isVertexCase)
+		op << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(a_coords);\n";
+	else
+		op << "	${PRECISION} ${VAR_TYPE} coords = ${VAR_TYPE}(v_coords);\n";
+
+	op << "	${PRECISION} ${VAR_TYPE} tmp;\n";
+	if (writeAccess == DIRECT)
+		op << "	tmp = coords.${SWIZZLE} * vec4(1.0, 0.5, 0.25, 0.125).${SWIZZLE};\n";
+	else if (writeAccess == COMPONENT)
+	{
+		op << "	tmp.x = coords.x;\n";
+		if (vecLen >= 2) op << "	tmp.y = coords.y * 0.5;\n";
+		if (vecLen >= 3) op << "	tmp.z = coords.z * 0.25;\n";
+		if (vecLen >= 4) op << "	tmp.w = coords.w * 0.125;\n";
+	}
+	else if (writeAccess == SUBSCRIPT_STATIC)
+	{
+		op << "	tmp[0] = coords.x;\n";
+		if (vecLen >= 2) op << "	tmp[1] = coords.y * 0.5;\n";
+		if (vecLen >= 3) op << "	tmp[2] = coords.z * 0.25;\n";
+		if (vecLen >= 4) op << "	tmp[3] = coords.w * 0.125;\n";
+	}
+	else if (writeAccess == SUBSCRIPT_DYNAMIC)
+	{
+		op << "	tmp[ui_zero]  = coords.x;\n";
+		if (vecLen >= 2) op << "	tmp[ui_one]   = coords.y * 0.5;\n";
+		if (vecLen >= 3) op << "	tmp[ui_two]   = coords.z * 0.25;\n";
+		if (vecLen >= 4) op << "	tmp[ui_three] = coords.w * 0.125;\n";
+	}
+	else if (writeAccess == SUBSCRIPT_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < " << vecLen << "; i++)\n";
+		op << "	{\n";
+		op << "		tmp[i] = coords.x;\n";
+		op << "		coords = coords.${ROT_SWIZZLE} * 0.5;\n";
+		op << "	}\n";
+	}
+	else
+	{
+		DE_ASSERT(writeAccess == SUBSCRIPT_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < " << vecLenName << "; i++)\n";
+		op << "	{\n";
+		op << "		tmp[i] = coords.x;\n";
+		op << "		coords = coords.${ROT_SWIZZLE} * 0.5;\n";
+		op << "	}\n";
+	}
+
+	// Read vector.
+	op << "	${PRECISION} float res = 0.0;\n";
+	if (readAccess == DIRECT)
+		op << "	res = dot(tmp, ${VAR_TYPE}(1.0));\n";
+	else if (readAccess == COMPONENT)
+	{
+		op << "	res += tmp.x;\n";
+		if (vecLen >= 2) op << "	res += tmp.y;\n";
+		if (vecLen >= 3) op << "	res += tmp.z;\n";
+		if (vecLen >= 4) op << "	res += tmp.w;\n";
+	}
+	else if (readAccess == SUBSCRIPT_STATIC)
+	{
+		op << "	res += tmp[0];\n";
+		if (vecLen >= 2) op << "	res += tmp[1];\n";
+		if (vecLen >= 3) op << "	res += tmp[2];\n";
+		if (vecLen >= 4) op << "	res += tmp[3];\n";
+	}
+	else if (readAccess == SUBSCRIPT_DYNAMIC)
+	{
+		op << "	res += tmp[ui_zero];\n";
+		if (vecLen >= 2) op << "	res += tmp[ui_one];\n";
+		if (vecLen >= 3) op << "	res += tmp[ui_two];\n";
+		if (vecLen >= 4) op << "	res += tmp[ui_three];\n";
+	}
+	else if (readAccess == SUBSCRIPT_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < " << vecLen << "; i++)\n";
+		op << "		res += tmp[i];\n";
+	}
+	else
+	{
+		DE_ASSERT(readAccess == SUBSCRIPT_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < " << vecLenName << "; i++)\n";
+		op << "		res += tmp[i];\n";
+	}
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = vec3(res);\n";
+		frag << "	o_color = vec4(v_color.rgb, 1.0);\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	o_color = vec4(vec3(res), 1.0);\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	static const char* s_swizzles[5]	= { "", "x", "xy", "xyz", "xyzw" };
+	static const char* s_rotSwizzles[5]	= { "", "x", "yx", "yzx", "yzwx" };
+
+	map<string, string> params;
+	params.insert(pair<string, string>("VAR_TYPE", getDataTypeName(varType)));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+	params.insert(pair<string, string>("SWIZZLE", s_swizzles[vecLen]));
+	params.insert(pair<string, string>("ROT_SWIZZLE", s_rotSwizzles[vecLen]));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	ShaderEvalFunc evalFunc = getVectorSubscriptEvalFunc(varType);
+	return new ShaderIndexingCase(context, caseName, description, isVertexCase, varType, evalFunc, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+// MATRIX SUBSCRIPT.
+
+void evalSubscriptMat2		(ShaderEvalContext& c) { c.color.xy()	= c.coords.swizzle(0,1) + 0.5f*c.coords.swizzle(1,2); }
+void evalSubscriptMat2x3	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(0,1,2) + 0.5f*c.coords.swizzle(1,2,3); }
+void evalSubscriptMat2x4	(ShaderEvalContext& c) { c.color		= c.coords.swizzle(0,1,2,3) + 0.5f*c.coords.swizzle(1,2,3,0); }
+
+void evalSubscriptMat3x2	(ShaderEvalContext& c) { c.color.xy()	= c.coords.swizzle(0,1) + 0.5f*c.coords.swizzle(1,2) + 0.25f*c.coords.swizzle(2,3); }
+void evalSubscriptMat3		(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(0,1,2) + 0.5f*c.coords.swizzle(1,2,3) + 0.25f*c.coords.swizzle(2,3,0); }
+void evalSubscriptMat3x4	(ShaderEvalContext& c) { c.color		= c.coords.swizzle(0,1,2,3) + 0.5f*c.coords.swizzle(1,2,3,0) + 0.25f*c.coords.swizzle(2,3,0,1); }
+
+void evalSubscriptMat4x2	(ShaderEvalContext& c) { c.color.xy()	= c.coords.swizzle(0,1) + 0.5f*c.coords.swizzle(1,2) + 0.25f*c.coords.swizzle(2,3) + 0.125f*c.coords.swizzle(3,0); }
+void evalSubscriptMat4x3	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(0,1,2) + 0.5f*c.coords.swizzle(1,2,3) + 0.25f*c.coords.swizzle(2,3,0) + 0.125f*c.coords.swizzle(3,0,1); }
+void evalSubscriptMat4		(ShaderEvalContext& c) { c.color		= c.coords + 0.5f*c.coords.swizzle(1,2,3,0) + 0.25f*c.coords.swizzle(2,3,0,1) + 0.125f*c.coords.swizzle(3,0,1,2); }
+
+static ShaderEvalFunc getMatrixSubscriptEvalFunc (DataType dataType)
+{
+	switch (dataType)
+	{
+		case TYPE_FLOAT_MAT2:		return evalSubscriptMat2;
+		case TYPE_FLOAT_MAT2X3:		return evalSubscriptMat2x3;
+		case TYPE_FLOAT_MAT2X4:		return evalSubscriptMat2x4;
+		case TYPE_FLOAT_MAT3X2:		return evalSubscriptMat3x2;
+		case TYPE_FLOAT_MAT3:		return evalSubscriptMat3;
+		case TYPE_FLOAT_MAT3X4:		return evalSubscriptMat3x4;
+		case TYPE_FLOAT_MAT4X2:		return evalSubscriptMat4x2;
+		case TYPE_FLOAT_MAT4X3:		return evalSubscriptMat4x3;
+		case TYPE_FLOAT_MAT4:		return evalSubscriptMat4;
+
+		default:
+			DE_ASSERT(!"Invalid data type.");
+			return DE_NULL;
+	}
+}
+
+static ShaderIndexingCase* createMatrixSubscriptCase (Context& context, const char* caseName, const char* description, bool isVertexCase, DataType varType, IndexAccessType writeAccess, IndexAccessType readAccess)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	int			numCols		= getDataTypeMatrixNumColumns(varType);
+	int			numRows		= getDataTypeMatrixNumRows(varType);
+	const char*	matSizeName	= getIntUniformName(numCols);
+	DataType	vecType		= getDataTypeFloatVec(numRows);
+
+	vtx << "#version 300 es\n";
+	frag << "#version 300 es\n";
+
+	vtx << "in highp vec4 a_position;\n";
+	vtx << "in highp vec4 a_coords;\n";
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "out mediump vec4 v_coords;\n";
+		frag << "in mediump vec4 v_coords;\n";
+	}
+
+	if (writeAccess == INDEXACCESS_DYNAMIC || readAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "uniform mediump int ui_zero";
+		if (numCols >= 2) op << ", ui_one";
+		if (numCols >= 3) op << ", ui_two";
+		if (numCols >= 4) op << ", ui_three";
+		op << ";\n";
+	}
+
+	if (writeAccess == INDEXACCESS_DYNAMIC_LOOP || readAccess == INDEXACCESS_DYNAMIC_LOOP)
+		op << "uniform mediump int " << matSizeName << ";\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Write matrix.
+	if (isVertexCase)
+		op << "	${PRECISION} vec4 coords = a_coords;\n";
+	else
+		op << "	${PRECISION} vec4 coords = v_coords;\n";
+
+	op << "	${PRECISION} ${MAT_TYPE} tmp;\n";
+	if (writeAccess == INDEXACCESS_STATIC)
+	{
+		op << "	tmp[0] = ${VEC_TYPE}(coords);\n";
+		if (numCols >= 2) op << "	tmp[1] = ${VEC_TYPE}(coords.yzwx) * 0.5;\n";
+		if (numCols >= 3) op << "	tmp[2] = ${VEC_TYPE}(coords.zwxy) * 0.25;\n";
+		if (numCols >= 4) op << "	tmp[3] = ${VEC_TYPE}(coords.wxyz) * 0.125;\n";
+	}
+	else if (writeAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "	tmp[ui_zero]  = ${VEC_TYPE}(coords);\n";
+		if (numCols >= 2) op << "	tmp[ui_one]   = ${VEC_TYPE}(coords.yzwx) * 0.5;\n";
+		if (numCols >= 3) op << "	tmp[ui_two]   = ${VEC_TYPE}(coords.zwxy) * 0.25;\n";
+		if (numCols >= 4) op << "	tmp[ui_three] = ${VEC_TYPE}(coords.wxyz) * 0.125;\n";
+	}
+	else if (writeAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < " << numCols << "; i++)\n";
+		op << "	{\n";
+		op << "		tmp[i] = ${VEC_TYPE}(coords);\n";
+		op << "		coords = coords.yzwx * 0.5;\n";
+		op << "	}\n";
+	}
+	else
+	{
+		DE_ASSERT(writeAccess == INDEXACCESS_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < " << matSizeName << "; i++)\n";
+		op << "	{\n";
+		op << "		tmp[i] = ${VEC_TYPE}(coords);\n";
+		op << "		coords = coords.yzwx * 0.5;\n";
+		op << "	}\n";
+	}
+
+	// Read matrix.
+	op << "	${PRECISION} ${VEC_TYPE} res = ${VEC_TYPE}(0.0);\n";
+	if (readAccess == INDEXACCESS_STATIC)
+	{
+		op << "	res += tmp[0];\n";
+		if (numCols >= 2) op << "	res += tmp[1];\n";
+		if (numCols >= 3) op << "	res += tmp[2];\n";
+		if (numCols >= 4) op << "	res += tmp[3];\n";
+	}
+	else if (readAccess == INDEXACCESS_DYNAMIC)
+	{
+		op << "	res += tmp[ui_zero];\n";
+		if (numCols >= 2) op << "	res += tmp[ui_one];\n";
+		if (numCols >= 3) op << "	res += tmp[ui_two];\n";
+		if (numCols >= 4) op << "	res += tmp[ui_three];\n";
+	}
+	else if (readAccess == INDEXACCESS_STATIC_LOOP)
+	{
+		op << "	for (int i = 0; i < " << numCols << "; i++)\n";
+		op << "		res += tmp[i];\n";
+	}
+	else
+	{
+		DE_ASSERT(readAccess == INDEXACCESS_DYNAMIC_LOOP);
+		op << "	for (int i = 0; i < " << matSizeName << "; i++)\n";
+		op << "		res += tmp[i];\n";
+	}
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = vec4(res${PADDING});\n";
+		frag << "	o_color = v_color;\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	o_color = vec4(res${PADDING});\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("MAT_TYPE", getDataTypeName(varType)));
+	params.insert(pair<string, string>("VEC_TYPE", getDataTypeName(vecType)));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+
+	if (numRows == 2)
+		params.insert(pair<string, string>("PADDING", ", 0.0, 1.0"));
+	else if (numRows == 3)
+		params.insert(pair<string, string>("PADDING", ", 1.0"));
+	else
+		params.insert(pair<string, string>("PADDING", ""));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	ShaderEvalFunc evalFunc = getMatrixSubscriptEvalFunc(varType);
+	return new ShaderIndexingCase(context, caseName, description, isVertexCase, varType, evalFunc, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+// ShaderIndexingTests.
+
+ShaderIndexingTests::ShaderIndexingTests(Context& context)
+	: TestCaseGroup(context, "indexing", "Indexing Tests")
+{
+}
+
+ShaderIndexingTests::~ShaderIndexingTests (void)
+{
+}
+
+void ShaderIndexingTests::init (void)
+{
+	static const ShaderType s_shaderTypes[] =
+	{
+		SHADERTYPE_VERTEX,
+		SHADERTYPE_FRAGMENT
+	};
+
+	static const DataType s_floatAndVecTypes[] =
+	{
+		TYPE_FLOAT,
+		TYPE_FLOAT_VEC2,
+		TYPE_FLOAT_VEC3,
+		TYPE_FLOAT_VEC4
+	};
+
+	// Varying array access cases.
+	{
+		TestCaseGroup* varyingGroup = new TestCaseGroup(m_context, "varying_array", "Varying array access tests.");
+		addChild(varyingGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_floatAndVecTypes); typeNdx++)
+		{
+			DataType varType = s_floatAndVecTypes[typeNdx];
+			for (int vertAccess = 0; vertAccess < INDEXACCESS_LAST; vertAccess++)
+			{
+				for (int fragAccess = 0; fragAccess < INDEXACCESS_LAST; fragAccess++)
+				{
+					const char* vertAccessName = getIndexAccessTypeName((IndexAccessType)vertAccess);
+					const char* fragAccessName = getIndexAccessTypeName((IndexAccessType)fragAccess);
+					string name = string(getDataTypeName(varType)) + "_" + vertAccessName + "_write_" + fragAccessName + "_read";
+					string desc = string("Varying array with ") + vertAccessName + " write in vertex shader and " + fragAccessName + " read in fragment shader.";
+					varyingGroup->addChild(createVaryingArrayCase(m_context, name.c_str(), desc.c_str(), varType, (IndexAccessType)vertAccess, (IndexAccessType)fragAccess));
+				}
+			}
+		}
+	}
+
+	// Uniform array access cases.
+	{
+		TestCaseGroup* uniformGroup = new TestCaseGroup(m_context, "uniform_array", "Uniform array access tests.");
+		addChild(uniformGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_floatAndVecTypes); typeNdx++)
+		{
+			DataType varType = s_floatAndVecTypes[typeNdx];
+			for (int readAccess = 0; readAccess < INDEXACCESS_LAST; readAccess++)
+			{
+				const char* readAccessName = getIndexAccessTypeName((IndexAccessType)readAccess);
+				for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+				{
+					ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+					const char*	shaderTypeName	= getShaderTypeName(shaderType);
+					string		name			= string(getDataTypeName(varType)) + "_" + readAccessName + "_read_" + shaderTypeName;
+					string		desc			= string("Uniform array with ") + readAccessName + " read in " + shaderTypeName + " shader.";
+					bool isVertexCase = ((ShaderType)shaderType == SHADERTYPE_VERTEX);
+					uniformGroup->addChild(createUniformArrayCase(m_context, name.c_str(), desc.c_str(), isVertexCase, varType, (IndexAccessType)readAccess));
+				}
+			}
+		}
+	}
+
+	// Temporary array access cases.
+	{
+		TestCaseGroup* tmpGroup = new TestCaseGroup(m_context, "tmp_array", "Temporary array access tests.");
+		addChild(tmpGroup);
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_floatAndVecTypes); typeNdx++)
+		{
+			DataType varType = s_floatAndVecTypes[typeNdx];
+			for (int writeAccess = 0; writeAccess < INDEXACCESS_LAST; writeAccess++)
+			{
+				for (int readAccess = 0; readAccess < INDEXACCESS_LAST; readAccess++)
+				{
+					const char* writeAccessName = getIndexAccessTypeName((IndexAccessType)writeAccess);
+					const char* readAccessName = getIndexAccessTypeName((IndexAccessType)readAccess);
+
+					for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+					{
+						ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+						const char* shaderTypeName	= getShaderTypeName(shaderType);
+						string		name			= string(getDataTypeName(varType)) + "_" + writeAccessName + "_write_" + readAccessName + "_read_" + shaderTypeName;
+						string		desc			= string("Temporary array with ") + writeAccessName + " write and " + readAccessName + " read in " + shaderTypeName + " shader.";
+						bool		isVertexCase	= ((ShaderType)shaderType == SHADERTYPE_VERTEX);
+						tmpGroup->addChild(createTmpArrayCase(m_context, name.c_str(), desc.c_str(), isVertexCase, varType, (IndexAccessType)writeAccess, (IndexAccessType)readAccess));
+					}
+				}
+			}
+		}
+	}
+
+	// Vector indexing with subscripts.
+	{
+		TestCaseGroup* vecGroup = new TestCaseGroup(m_context, "vector_subscript", "Vector subscript indexing.");
+		addChild(vecGroup);
+
+		static const DataType s_vectorTypes[] =
+		{
+			TYPE_FLOAT_VEC2,
+			TYPE_FLOAT_VEC3,
+			TYPE_FLOAT_VEC4
+		};
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_vectorTypes); typeNdx++)
+		{
+			DataType varType = s_vectorTypes[typeNdx];
+			for (int writeAccess = 0; writeAccess < VECTORACCESS_LAST; writeAccess++)
+			{
+				for (int readAccess = 0; readAccess < VECTORACCESS_LAST; readAccess++)
+				{
+					const char* writeAccessName = getVectorAccessTypeName((VectorAccessType)writeAccess);
+					const char* readAccessName = getVectorAccessTypeName((VectorAccessType)readAccess);
+
+					for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+					{
+						ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+						const char* shaderTypeName	= getShaderTypeName(shaderType);
+						string		name			= string(getDataTypeName(varType)) + "_" + writeAccessName + "_write_" + readAccessName + "_read_" + shaderTypeName;
+						string		desc			= string("Vector subscript access with ") + writeAccessName + " write and " + readAccessName + " read in " + shaderTypeName + " shader.";
+						bool		isVertexCase	= ((ShaderType)shaderType == SHADERTYPE_VERTEX);
+						vecGroup->addChild(createVectorSubscriptCase(m_context, name.c_str(), desc.c_str(), isVertexCase, varType, (VectorAccessType)writeAccess, (VectorAccessType)readAccess));
+					}
+				}
+			}
+		}
+	}
+
+	// Matrix indexing with subscripts.
+	{
+		TestCaseGroup* matGroup = new TestCaseGroup(m_context, "matrix_subscript", "Matrix subscript indexing.");
+		addChild(matGroup);
+
+		static const DataType s_matrixTypes[] =
+		{
+			TYPE_FLOAT_MAT2,
+			TYPE_FLOAT_MAT2X3,
+			TYPE_FLOAT_MAT2X4,
+			TYPE_FLOAT_MAT3X2,
+			TYPE_FLOAT_MAT3,
+			TYPE_FLOAT_MAT3X4,
+			TYPE_FLOAT_MAT4X2,
+			TYPE_FLOAT_MAT4X3,
+			TYPE_FLOAT_MAT4
+		};
+
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_matrixTypes); typeNdx++)
+		{
+			DataType varType = s_matrixTypes[typeNdx];
+			for (int writeAccess = 0; writeAccess < INDEXACCESS_LAST; writeAccess++)
+			{
+				for (int readAccess = 0; readAccess < INDEXACCESS_LAST; readAccess++)
+				{
+					const char* writeAccessName = getIndexAccessTypeName((IndexAccessType)writeAccess);
+					const char* readAccessName = getIndexAccessTypeName((IndexAccessType)readAccess);
+
+					for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+					{
+						ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+						const char* shaderTypeName	= getShaderTypeName(shaderType);
+						string		name			= string(getDataTypeName(varType)) + "_" + writeAccessName + "_write_" + readAccessName + "_read_" + shaderTypeName;
+						string		desc			= string("Vector subscript access with ") + writeAccessName + " write and " + readAccessName + " read in " + shaderTypeName + " shader.";
+						bool		isVertexCase	= ((ShaderType)shaderType == SHADERTYPE_VERTEX);
+						matGroup->addChild(createMatrixSubscriptCase(m_context, name.c_str(), desc.c_str(), isVertexCase, varType, (IndexAccessType)writeAccess, (IndexAccessType)readAccess));
+					}
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderIndexingTests.hpp b/modules/gles3/functional/es3fShaderIndexingTests.hpp
new file mode 100644
index 0000000..c343142
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderIndexingTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERINDEXINGTESTS_HPP
+#define _ES3FSHADERINDEXINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader indexing (arrays, vector, matrices) tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderIndexingTests : public TestCaseGroup
+{
+public:
+							ShaderIndexingTests		(Context& context);
+	virtual					~ShaderIndexingTests	(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderIndexingTests		(const ShaderIndexingTests&);		// not allowed!
+	ShaderIndexingTests&	operator=				(const ShaderIndexingTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERINDEXINGTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderInvarianceTests.cpp b/modules/gles3/functional/es3fShaderInvarianceTests.cpp
new file mode 100644
index 0000000..765598a
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderInvarianceTests.cpp
@@ -0,0 +1,861 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Invariance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderInvarianceTests.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuStringTemplate.hpp"
+
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+class FormatArgumentList;
+
+static tcu::Vec4 genRandomVector (de::Random& rnd)
+{
+	tcu::Vec4 retVal;
+
+	retVal.x() = rnd.getFloat(-1.0f, 1.0f);
+	retVal.y() = rnd.getFloat(-1.0f, 1.0f);
+	retVal.z() = rnd.getFloat(-1.0f, 1.0f);
+	retVal.w() = rnd.getFloat( 0.2f, 1.0f);
+
+	return retVal;
+}
+
+class FormatArgument
+{
+public:
+						FormatArgument (const char* name, const std::string& value);
+
+private:
+	friend class FormatArgumentList;
+
+	const char* const	m_name;
+	const std::string	m_value;
+};
+
+FormatArgument::FormatArgument (const char* name, const std::string& value)
+	: m_name	(name)
+	, m_value	(value)
+{
+}
+
+class FormatArgumentList
+{
+public:
+												FormatArgumentList	(void);
+
+	FormatArgumentList&							operator<<			(const FormatArgument&);
+	const std::map<std::string, std::string>&	getArguments		(void) const;
+
+private:
+	std::map<std::string, std::string>			m_formatArguments;
+};
+
+FormatArgumentList::FormatArgumentList (void)
+{
+}
+
+FormatArgumentList&	FormatArgumentList::operator<< (const FormatArgument& arg)
+{
+	m_formatArguments[arg.m_name] = arg.m_value;
+	return *this;
+}
+
+const std::map<std::string, std::string>& FormatArgumentList::getArguments (void) const
+{
+	return m_formatArguments;
+}
+
+static std::string formatGLSL (const char* templateString, const FormatArgumentList& args)
+{
+	const std::map<std::string, std::string>& params = args.getArguments();
+
+	return tcu::StringTemplate(std::string(templateString)).specialize(params);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Vertex shader invariance test
+ *
+ * Test vertex shader invariance by drawing a test pattern two times, each
+ * time with a different shader. Shaders have set identical values to
+ * invariant gl_Position using identical expressions. No fragments from the
+ * first pass using should remain visible.
+ *//*--------------------------------------------------------------------*/
+class InvarianceTest : public TestCase
+{
+public:
+	struct ShaderPair
+	{
+		std::string vertexShaderSource0;
+		std::string fragmentShaderSource0;
+		std::string vertexShaderSource1;
+		std::string fragmentShaderSource1;
+	};
+
+							InvarianceTest		(Context& ctx, const char* name, const char* desc);
+							~InvarianceTest		(void);
+
+	void					init				(void);
+	void					deinit				(void);
+	IterateResult			iterate				(void);
+
+private:
+	virtual ShaderPair		genShaders			(void) const = DE_NULL;
+	bool					checkImage			(const tcu::Surface&) const;
+
+	glu::ShaderProgram*		m_shader0;
+	glu::ShaderProgram*		m_shader1;
+	glw::GLuint				m_arrayBuf;
+	int						m_verticesInPattern;
+
+	const int				m_renderSize;
+};
+
+InvarianceTest::InvarianceTest (Context& ctx, const char* name, const char* desc)
+	: TestCase				(ctx, name, desc)
+	, m_shader0				(DE_NULL)
+	, m_shader1				(DE_NULL)
+	, m_arrayBuf			(0)
+	, m_verticesInPattern	(0)
+	, m_renderSize			(256)
+{
+}
+
+InvarianceTest::~InvarianceTest (void)
+{
+	deinit();
+}
+
+void InvarianceTest::init (void)
+{
+	// Invariance tests require drawing to the screen and reading back results.
+	// Tests results are not reliable if the resolution is too small
+	{
+		if (m_context.getRenderTarget().getWidth()  < m_renderSize ||
+			m_context.getRenderTarget().getHeight() < m_renderSize)
+			throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(m_renderSize) + "x" + de::toString(m_renderSize));
+	}
+
+	// Gen shaders
+	{
+		ShaderPair vertexShaders = genShaders();
+
+		m_shader0 = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaders.vertexShaderSource0) << glu::FragmentSource(vertexShaders.fragmentShaderSource0));
+		if (!m_shader0->isOk())
+		{
+			m_testCtx.getLog() << *m_shader0;
+			throw tcu::TestError("Test shader compile failed.");
+		}
+
+		m_shader1 = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaders.vertexShaderSource1) << glu::FragmentSource(vertexShaders.fragmentShaderSource1));
+		if (!m_shader1->isOk())
+		{
+			m_testCtx.getLog() << *m_shader1;
+			throw tcu::TestError("Test shader compile failed.");
+		}
+
+		// log
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message << "Shader 1:" << tcu::TestLog::EndMessage
+			<< *m_shader0
+			<< tcu::TestLog::Message << "Shader 2:" << tcu::TestLog::EndMessage
+			<< *m_shader1;
+	}
+
+	// Gen test pattern
+	{
+		const int				numTriangles	= 72;
+		de::Random				rnd				(123);
+		std::vector<tcu::Vec4>	triangles		(numTriangles * 3 * 2);
+		const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+
+		// Narrow triangle pattern
+		for (int triNdx = 0; triNdx < numTriangles; ++triNdx)
+		{
+			const tcu::Vec4 vertex1 = genRandomVector(rnd);
+			const tcu::Vec4 vertex2 = genRandomVector(rnd);
+			const tcu::Vec4 vertex3 = vertex2 + genRandomVector(rnd) * 0.01f; // generate narrow triangles
+
+			triangles[triNdx*3 + 0] = vertex1;
+			triangles[triNdx*3 + 1] = vertex2;
+			triangles[triNdx*3 + 2] = vertex3;
+		}
+
+		// Normal triangle pattern
+		for (int triNdx = 0; triNdx < numTriangles; ++triNdx)
+		{
+			triangles[(numTriangles + triNdx)*3 + 0] = genRandomVector(rnd);
+			triangles[(numTriangles + triNdx)*3 + 1] = genRandomVector(rnd);
+			triangles[(numTriangles + triNdx)*3 + 2] = genRandomVector(rnd);
+		}
+
+		// upload
+		gl.genBuffers(1, &m_arrayBuf);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuf);
+		gl.bufferData(GL_ARRAY_BUFFER, (int)(triangles.size() * sizeof(tcu::Vec4)), &triangles[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "buffer gen");
+
+		m_verticesInPattern = numTriangles * 3;
+	}
+}
+
+void InvarianceTest::deinit (void)
+{
+	delete m_shader0;
+	delete m_shader1;
+
+	m_shader0 = DE_NULL;
+	m_shader1 = DE_NULL;
+
+	if (m_arrayBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_arrayBuf);
+		m_arrayBuf = 0;
+	}
+}
+
+InvarianceTest::IterateResult InvarianceTest::iterate (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const bool				depthBufferExists	= m_context.getRenderTarget().getDepthBits() != 0;
+	tcu::Surface			resultSurface		(m_renderSize, m_renderSize);
+	bool					error				= false;
+
+	// Prepare draw
+	gl.clearColor		(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear			(GL_COLOR_BUFFER_BIT);
+	gl.viewport			(0, 0, m_renderSize, m_renderSize);
+	gl.bindBuffer		(GL_ARRAY_BUFFER, m_arrayBuf);
+	GLU_EXPECT_NO_ERROR	(gl.getError(), "setup draw");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Testing position invariance." << tcu::TestLog::EndMessage;
+
+	// Draw position check passes
+	for (int passNdx = 0; passNdx < 2; ++passNdx)
+	{
+		const glu::ShaderProgram&	shader		= (passNdx == 0) ? (*m_shader0) : (*m_shader1);
+		const glw::GLint			positionLoc = gl.getAttribLocation(shader.getProgram(), "a_input");
+		const glw::GLint			colorLoc	= gl.getUniformLocation(shader.getProgram(), "u_color");
+		const tcu::Vec4				red			= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+		const tcu::Vec4				green		= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+		const tcu::Vec4				color		= (passNdx == 0) ? (red) : (green);
+		const char* const			colorStr	= (passNdx == 0) ? ("red - purple") : ("green");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing position test pattern using shader " << (passNdx+1) << ". Primitive color: " << colorStr << "." << tcu::TestLog::EndMessage;
+
+		gl.useProgram				(shader.getProgram());
+		gl.uniform4fv				(colorLoc, 1, color.getPtr());
+		gl.enableVertexAttribArray	(positionLoc);
+		gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, sizeof(tcu::Vec4), DE_NULL);
+		gl.drawArrays				(GL_TRIANGLES, 0, m_verticesInPattern);
+		gl.disableVertexAttribArray	(positionLoc);
+		GLU_EXPECT_NO_ERROR			(gl.getError(), "draw pass");
+	}
+
+	// Read result
+	glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+
+	// Check there are no red pixels
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output. Expecting only green or background colored pixels." << tcu::TestLog::EndMessage;
+	error |= !checkImage(resultSurface);
+
+	if (!depthBufferExists)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Depth buffer not available, skipping z-test." << tcu::TestLog::EndMessage;
+	}
+	else
+	{
+		// Test with Z-test
+		gl.clearDepthf		(1.0f);
+		gl.clear			(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl.enable			(GL_DEPTH_TEST);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Testing position invariance with z-test. Enabling GL_DEPTH_TEST." << tcu::TestLog::EndMessage;
+
+		// Draw position check passes
+		for (int passNdx = 0; passNdx < 2; ++passNdx)
+		{
+			const glu::ShaderProgram&	shader			= (passNdx == 0) ? (*m_shader0) : (*m_shader1);
+			const glw::GLint			positionLoc		= gl.getAttribLocation(shader.getProgram(), "a_input");
+			const glw::GLint			colorLoc		= gl.getUniformLocation(shader.getProgram(), "u_color");
+			const tcu::Vec4				red				= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+			const tcu::Vec4				green			= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+			const tcu::Vec4				color			= (passNdx == 0) ? (red) : (green);
+			const glw::GLenum			depthFunc		= (passNdx == 0) ? (GL_ALWAYS) : (GL_EQUAL);
+			const char* const			depthFuncStr	= (passNdx == 0) ? ("GL_ALWAYS") : ("GL_EQUAL");
+			const char* const			colorStr		= (passNdx == 0) ? ("red - purple") : ("green");
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Drawing Z-test pattern using shader " << (passNdx+1) << ". Primitive color: " << colorStr << ". DepthFunc: " << depthFuncStr << tcu::TestLog::EndMessage;
+
+			gl.useProgram				(shader.getProgram());
+			gl.uniform4fv				(colorLoc, 1, color.getPtr());
+			gl.depthFunc				(depthFunc);
+			gl.enableVertexAttribArray	(positionLoc);
+			gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, sizeof(tcu::Vec4), DE_NULL);
+			gl.drawArrays				(GL_TRIANGLES, m_verticesInPattern, m_verticesInPattern); // !< buffer contains 2 m_verticesInPattern-sized patterns
+			gl.disableVertexAttribArray	(positionLoc);
+			GLU_EXPECT_NO_ERROR			(gl.getError(), "draw pass");
+		}
+
+		// Read result
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+
+		// Check there are no red pixels
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output. Expecting only green or background colored pixels." << tcu::TestLog::EndMessage;
+		error |= !checkImage(resultSurface);
+	}
+
+	// Report result
+	if (error)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Detected variance between two invariant values");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	return STOP;
+}
+
+bool InvarianceTest::checkImage (const tcu::Surface& surface) const
+{
+	const tcu::IVec4	okColor		= tcu::IVec4(0, 255, 0, 255);
+	const tcu::RGBA		errColor	= tcu::RGBA(255, 0, 0, 255);
+	bool				error		= false;
+	tcu::Surface		errorMask	(m_renderSize, m_renderSize);
+
+	tcu::clear(errorMask.getAccess(), okColor);
+
+	for (int y = 0; y < m_renderSize; ++y)
+	for (int x = 0; x < m_renderSize; ++x)
+	{
+		const tcu::RGBA col = surface.getPixel(x, y);
+
+		if (col.getRed() != 0)
+		{
+			errorMask.setPixel(x, y, errColor);
+			error = true;
+		}
+	}
+
+	// report error
+	if (error)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Invalid pixels found (fragments from first render pass found). Variance detected." << tcu::TestLog::EndMessage;
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Results", "Result verification")
+			<< tcu::TestLog::Image("Result",		"Result",		surface)
+			<< tcu::TestLog::Image("Error mask",	"Error mask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+
+		return false;
+	}
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "No variance found." << tcu::TestLog::EndMessage;
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Results", "Result verification")
+			<< tcu::TestLog::Image("Result", "Result", surface)
+			<< tcu::TestLog::EndImageSet;
+
+		return true;
+	}
+}
+
+class BasicInvarianceTest : public InvarianceTest
+{
+public:
+								BasicInvarianceTest		(Context& ctx, const char* name, const char* desc, const std::string& vertexShader1, const std::string& vertexShader2);
+								BasicInvarianceTest		(Context& ctx, const char* name, const char* desc, const std::string& vertexShader1, const std::string& vertexShader2, const std::string& fragmentShader);
+	ShaderPair					genShaders				(void) const;
+
+private:
+	const std::string			m_vertexShader1;
+	const std::string			m_vertexShader2;
+	const std::string			m_fragmentShader;
+	static const char* const	s_basicFragmentShader;
+};
+
+const char* const BasicInvarianceTest::s_basicFragmentShader =	"#version 300 es\n"
+																"layout(location = 0) out mediump vec4 fragColor;\n"
+																"uniform mediump vec4 u_color;\n"
+																"in mediump vec4 v_unrelated;\n"
+																"void main ()\n"
+																"{\n"
+																"	mediump float blue = dot(v_unrelated, vec4(1.0, 1.0, 1.0, 1.0));\n"
+																"	fragColor = vec4(u_color.r, u_color.g, blue, u_color.a);\n"
+																"}\n";
+
+BasicInvarianceTest::BasicInvarianceTest (Context& ctx, const char* name, const char* desc, const std::string& vertexShader1, const std::string& vertexShader2)
+	: InvarianceTest	(ctx, name, desc)
+	, m_vertexShader1	(vertexShader1)
+	, m_vertexShader2	(vertexShader2)
+	, m_fragmentShader	(s_basicFragmentShader)
+{
+}
+
+BasicInvarianceTest::BasicInvarianceTest (Context& ctx, const char* name, const char* desc, const std::string& vertexShader1, const std::string& vertexShader2, const std::string& fragmentShader)
+	: InvarianceTest	(ctx, name, desc)
+	, m_vertexShader1	(vertexShader1)
+	, m_vertexShader2	(vertexShader2)
+	, m_fragmentShader	(fragmentShader)
+{
+}
+
+BasicInvarianceTest::ShaderPair BasicInvarianceTest::genShaders (void) const
+{
+	ShaderPair retVal;
+
+	retVal.vertexShaderSource0 = m_vertexShader1;
+	retVal.vertexShaderSource1 = m_vertexShader2;
+	retVal.fragmentShaderSource0 = m_fragmentShader;
+	retVal.fragmentShaderSource1 = m_fragmentShader;
+
+	return retVal;
+}
+
+} // anonymous
+
+ShaderInvarianceTests::ShaderInvarianceTests (Context& context)
+	: TestCaseGroup(context, "invariance", "Invariance tests")
+{
+}
+
+ShaderInvarianceTests::~ShaderInvarianceTests (void)
+{
+}
+
+void ShaderInvarianceTests::init (void)
+{
+	static const struct PrecisionCase
+	{
+		glu::Precision	prec;
+		const char*		name;
+
+		// set literals in the glsl to be in the representable range
+		const char*		highValue;		// !< highValue < maxValue
+		const char*		invHighValue;
+		const char*		mediumValue;	// !< mediumValue^2 < maxValue
+		const char*		lowValue;		// !< lowValue^4 < maxValue
+		const char*		invlowValue;
+		int				loopIterations;
+		int				loopPartialIterations;
+		int				loopNormalizationExponent;
+		const char*		loopNormalizationConstantLiteral;
+		const char*		loopMultiplier;
+		const char*		sumLoopNormalizationConstantLiteral;
+	} precisions[] =
+	{
+		{ glu::PRECISION_HIGHP,		"highp",	"1.0e20",	"1.0e-20",	"1.0e14",	"1.0e9",	"1.0e-9",	14,	11,	2,	"1.0e4",	"1.9",	"1.0e3"	},
+		{ glu::PRECISION_MEDIUMP,	"mediump",	"1.0e4",	"1.0e-4",	"1.0e2",	"1.0e1",	"1.0e-1",	13,	11,	2,	"1.0e4",	"1.9",	"1.0e3"	},
+		{ glu::PRECISION_LOWP,		"lowp",		"0.9",		"1.1",		"1.1",		"1.15",		"0.87",		6,	2,	0,	"2.0",		"1.1",	"1.0"	},
+	};
+
+	for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); ++precNdx)
+	{
+		const char* const			precisionName	= precisions[precNdx].name;
+		const glu::Precision		precision		= precisions[precNdx].prec;
+		tcu::TestCaseGroup* const	group			= new tcu::TestCaseGroup(m_testCtx, precisionName, "Invariance tests using the given precision.");
+
+		const FormatArgumentList	args			= FormatArgumentList()
+														<< FormatArgument("VERSION",				"#version 300 es\n")
+														<< FormatArgument("IN",						"in")
+														<< FormatArgument("OUT",					"out")
+														<< FormatArgument("IN_PREC",				precisionName)
+														<< FormatArgument("HIGH_VALUE",				de::toString(precisions[precNdx].highValue))
+														<< FormatArgument("HIGH_VALUE_INV",			de::toString(precisions[precNdx].invHighValue))
+														<< FormatArgument("MEDIUM_VALUE",			de::toString(precisions[precNdx].mediumValue))
+														<< FormatArgument("LOW_VALUE",				de::toString(precisions[precNdx].lowValue))
+														<< FormatArgument("LOW_VALUE_INV",			de::toString(precisions[precNdx].invlowValue))
+														<< FormatArgument("LOOP_ITERS",				de::toString(precisions[precNdx].loopIterations))
+														<< FormatArgument("LOOP_ITERS_PARTIAL",		de::toString(precisions[precNdx].loopPartialIterations))
+														<< FormatArgument("LOOP_NORM_FRACT_EXP",	de::toString(precisions[precNdx].loopNormalizationExponent))
+														<< FormatArgument("LOOP_NORM_LITERAL",		precisions[precNdx].loopNormalizationConstantLiteral)
+														<< FormatArgument("LOOP_MULTIPLIER",		precisions[precNdx].loopMultiplier)
+														<< FormatArgument("SUM_LOOP_NORM_LITERAL",	precisions[precNdx].sumLoopNormalizationConstantLiteral);
+
+		addChild(group);
+
+		// subexpression cases
+		{
+			// First shader shares "${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy" with unrelated output variable. Reordering might result in accuracy loss
+			// due to the high exponent. In the second shader, the high exponent may be removed during compilation.
+
+			group->addChild(new BasicInvarianceTest(m_context, "common_subexpression_0", "Shader shares a subexpression with an unrelated variable.",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	v_unrelated = a_input.xzxz + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * (1.08 * a_input.zyzy * a_input.xzxz) * ${HIGH_VALUE_INV} * (a_input.z * a_input.zzxz - a_input.z * a_input.zzxz) + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) / ${HIGH_VALUE};\n"
+							"	gl_Position = a_input + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * ${HIGH_VALUE_INV};\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	gl_Position = a_input + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * ${HIGH_VALUE_INV};\n"
+							"}\n", args)));
+
+			// In the first shader, the unrelated variable "d" has mathematically the same expression as "e", but the different
+			// order of calculation might cause different results.
+
+			group->addChild(new BasicInvarianceTest(m_context, "common_subexpression_1", "Shader shares a subexpression with an unrelated variable.",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 a = ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy - ${HIGH_VALUE} * a_input.zzxx;\n"
+							"	${IN_PREC} vec4 b = ${HIGH_VALUE} * a_input.zzxx;\n"
+							"	${IN_PREC} vec4 c = b - ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy;\n"
+							"	${IN_PREC} vec4 d = (${LOW_VALUE} * a_input.yzxx) * (${LOW_VALUE} * a_input.yzzw) * (1.1*${LOW_VALUE_INV} * a_input.yzxx) * (${LOW_VALUE_INV} * a_input.xzzy);\n"
+							"	${IN_PREC} vec4 e = ((${LOW_VALUE} * a_input.yzxx) * (1.1*${LOW_VALUE_INV} * a_input.yzxx)) * ((${LOW_VALUE_INV} * a_input.xzzy) * (${LOW_VALUE} * a_input.yzzw));\n"
+							"	v_unrelated = a + b + c + d + e;\n"
+							"	gl_Position = a_input + fract(c) + e;\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 b = ${HIGH_VALUE} * a_input.zzxx;\n"
+							"	${IN_PREC} vec4 c = b - ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy;\n"
+							"	${IN_PREC} vec4 e = ((${LOW_VALUE} * a_input.yzxx) * (1.1*${LOW_VALUE_INV} * a_input.yzxx)) * ((${LOW_VALUE_INV} * a_input.xzzy) * (${LOW_VALUE} * a_input.yzzw));\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	gl_Position = a_input + fract(c) + e;\n"
+							"}\n", args)));
+
+			// Intermediate values used by an unrelated output variable
+
+			group->addChild(new BasicInvarianceTest(m_context, "common_subexpression_2", "Shader shares a subexpression with an unrelated variable.",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 a = ${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy);\n"
+							"	${IN_PREC} vec4 b = (${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy)) * (${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy)) / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n"
+							"	${IN_PREC} vec4 c = a * a;\n"
+							"	${IN_PREC} vec4 d = c / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n"
+							"	v_unrelated = a + b + c + d;\n"
+							"	gl_Position = a_input + d;\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 a = ${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy);\n"
+							"	${IN_PREC} vec4 c = a * a;\n"
+							"	${IN_PREC} vec4 d = c / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	gl_Position = a_input + d;\n"
+							"}\n", args)));
+
+			// Invariant value can be calculated using unrelated value
+
+			group->addChild(new BasicInvarianceTest(m_context, "common_subexpression_3", "Shader shares a subexpression with an unrelated variable.",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} float x = a_input.x * 0.2;\n"
+							"	${IN_PREC} vec4 a = a_input.xxyx * 0.7;\n"
+							"	${IN_PREC} vec4 b = a_input.yxyz * 0.7;\n"
+							"	${IN_PREC} vec4 c = a_input.zxyx * 0.5;\n"
+							"	${IN_PREC} vec4 f = x*a + x*b + x*c;\n"
+							"	v_unrelated = f;\n"
+							"	${IN_PREC} vec4 g = x * (a + b + c);\n"
+							"	gl_Position = a_input + g;\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} float x = a_input.x * 0.2;\n"
+							"	${IN_PREC} vec4 a = a_input.xxyx * 0.7;\n"
+							"	${IN_PREC} vec4 b = a_input.yxyz * 0.7;\n"
+							"	${IN_PREC} vec4 c = a_input.zxyx * 0.5;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	${IN_PREC} vec4 g = x * (a + b + c);\n"
+							"	gl_Position = a_input + g;\n"
+							"}\n", args)));
+		}
+
+		// shared subexpression of different precision
+		{
+			for (int precisionOther = glu::PRECISION_LOWP; precisionOther != glu::PRECISION_LAST; ++precisionOther)
+			{
+				const char* const		unrelatedPrec				= glu::getPrecisionName((glu::Precision)precisionOther);
+				const glu::Precision	minPrecision				= (precisionOther < (int)precision) ? ((glu::Precision)precisionOther) : (precision);
+				const char* const		multiplierStr				= (minPrecision == glu::PRECISION_LOWP) ? ("0.8, 0.4, -0.2, 0.3") : ("1.0e1, 5.0e2, 2.0e2, 1.0");
+				const char* const		normalizationStrUsed		= (minPrecision == glu::PRECISION_LOWP) ? ("vec4(fract(used2).xyz, 0.0)") : ("vec4(fract(used2 / 1.0e2).xyz - fract(used2 / 1.0e3).xyz, 0.0)");
+				const char* const		normalizationStrUnrelated	= (minPrecision == glu::PRECISION_LOWP) ? ("vec4(fract(unrelated2).xyz, 0.0)") : ("vec4(fract(unrelated2 / 1.0e2).xyz - fract(unrelated2 / 1.0e3).xyz, 0.0)");
+
+				group->addChild(new BasicInvarianceTest(m_context, ("subexpression_precision_" + std::string(unrelatedPrec)).c_str(), "Shader shares subexpression of different precision with an unrelated variable.",
+					formatGLSL(	"${VERSION}"
+								"${IN} ${IN_PREC} vec4 a_input;\n"
+								"${OUT} ${UNRELATED_PREC} vec4 v_unrelated;\n"
+								"invariant gl_Position;\n"
+								"void main ()\n"
+								"{\n"
+								"	${UNRELATED_PREC} vec4 unrelated0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n"
+								"	${UNRELATED_PREC} vec4 unrelated1 = vec4(${MULTIPLIER}) * unrelated0.xywz + unrelated0;\n"
+								"	${UNRELATED_PREC} vec4 unrelated2 = refract(unrelated1, unrelated0, distance(unrelated0, unrelated1));\n"
+								"	v_unrelated = a_input + 0.02 * ${NORMALIZE_UNRELATED};\n"
+								"	${IN_PREC} vec4 used0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n"
+								"	${IN_PREC} vec4 used1 = vec4(${MULTIPLIER}) * used0.xywz + used0;\n"
+								"	${IN_PREC} vec4 used2 = refract(used1, used0, distance(used0, used1));\n"
+								"	gl_Position = a_input + 0.02 * ${NORMALIZE_USED};\n"
+								"}\n", FormatArgumentList(args)
+											<< FormatArgument("UNRELATED_PREC",			unrelatedPrec)
+											<< FormatArgument("MULTIPLIER",				multiplierStr)
+											<< FormatArgument("NORMALIZE_USED",			normalizationStrUsed)
+											<< FormatArgument("NORMALIZE_UNRELATED",	normalizationStrUnrelated)),
+					formatGLSL(	"${VERSION}"
+								"${IN} ${IN_PREC} vec4 a_input;\n"
+								"${OUT} ${UNRELATED_PREC} vec4 v_unrelated;\n"
+								"invariant gl_Position;\n"
+								"void main ()\n"
+								"{\n"
+								"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+								"	${IN_PREC} vec4 used0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n"
+								"	${IN_PREC} vec4 used1 = vec4(${MULTIPLIER}) * used0.xywz + used0;\n"
+								"	${IN_PREC} vec4 used2 = refract(used1, used0, distance(used0, used1));\n"
+								"	gl_Position = a_input + 0.02 * ${NORMALIZE_USED};\n"
+								"}\n", FormatArgumentList(args)
+											<< FormatArgument("UNRELATED_PREC",			unrelatedPrec)
+											<< FormatArgument("MULTIPLIER",				multiplierStr)
+											<< FormatArgument("NORMALIZE_USED",			normalizationStrUsed)
+											<< FormatArgument("NORMALIZE_UNRELATED",	normalizationStrUnrelated))));
+			}
+		}
+
+		// loops
+		{
+			group->addChild(new BasicInvarianceTest(m_context, "loop_0", "Invariant value set using a loop",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} highp vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		v_unrelated += value;\n"
+							"	}\n"
+							"	gl_Position = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} highp vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"	}\n"
+							"	gl_Position = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"}\n", args)));
+
+			group->addChild(new BasicInvarianceTest(m_context, "loop_1", "Invariant value set using a loop",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		if (i == ${LOOP_ITERS_PARTIAL})\n"
+							"			v_unrelated = value;\n"
+							"	}\n"
+							"	gl_Position = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"	}\n"
+							"	gl_Position = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"}\n", args)));
+
+			group->addChild(new BasicInvarianceTest(m_context, "loop_2", "Invariant value set using a loop",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, -1.0, 1.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		if (i == ${LOOP_ITERS_PARTIAL})\n"
+							"			gl_Position = a_input + 0.05 * vec4(fract(value.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
+							"		else\n"
+							"			v_unrelated = value + a_input;\n"
+							"	}\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, -1.0, 1.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		if (i == ${LOOP_ITERS_PARTIAL})\n"
+							"			gl_Position = a_input + 0.05 * vec4(fract(value.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
+							"		else\n"
+							"			v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	}\n"
+							"}\n", args)));
+
+			group->addChild(new BasicInvarianceTest(m_context, "loop_3", "Invariant value set using a loop",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		gl_Position += vec4(value.xyz / ${SUM_LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"		v_unrelated = gl_Position.xyzx * a_input;\n"
+							"	}\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 value = a_input;\n"
+							"	gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value *= ${LOOP_MULTIPLIER};\n"
+							"		gl_Position += vec4(value.xyz / ${SUM_LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
+							"	}\n"
+							"}\n", args)));
+
+			group->addChild(new BasicInvarianceTest(m_context, "loop_4", "Invariant value set using a loop",
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	${IN_PREC} vec4 value1 = a_input;\n"
+							"	${IN_PREC} vec4 value2 = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value1 *= ${LOOP_MULTIPLIER};\n"
+							"		v_unrelated = v_unrelated*1.3 + a_input.xyzx * value1.xyxw;\n"
+							"	}\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value2 *= ${LOOP_MULTIPLIER};\n"
+							"		position = position*1.3 + a_input.xyzx * value2.xyxw;\n"
+							"	}\n"
+							"	gl_Position = a_input + 0.05 * vec4(fract(position.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
+							"}\n", args),
+				formatGLSL(	"${VERSION}"
+							"${IN} ${IN_PREC} vec4 a_input;\n"
+							"${OUT} mediump vec4 v_unrelated;\n"
+							"invariant gl_Position;\n"
+							"void main ()\n"
+							"{\n"
+							"	${IN_PREC} vec4 position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	${IN_PREC} vec4 value2 = a_input;\n"
+							"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
+							"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
+							"	{\n"
+							"		value2 *= ${LOOP_MULTIPLIER};\n"
+							"		position = position*1.3 + a_input.xyzx * value2.xyxw;\n"
+							"	}\n"
+							"	gl_Position = a_input + 0.05 * vec4(fract(position.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
+							"}\n", args)));
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderInvarianceTests.hpp b/modules/gles3/functional/es3fShaderInvarianceTests.hpp
new file mode 100644
index 0000000..e0e1bb8
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderInvarianceTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FSHADERINVARIANCETESTS_HPP
+#define _ES3FSHADERINVARIANCETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Invariance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderInvarianceTests : public TestCaseGroup
+{
+public:
+							ShaderInvarianceTests	(Context& context);
+							~ShaderInvarianceTests	(void);
+
+	void					init					(void);
+
+private:
+							ShaderInvarianceTests	(const ShaderInvarianceTests& other);
+	ShaderInvarianceTests&	operator=				(const ShaderInvarianceTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERINVARIANCETESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderLoopTests.cpp b/modules/gles3/functional/es3fShaderLoopTests.cpp
new file mode 100644
index 0000000..dd5b24e
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderLoopTests.cpp
@@ -0,0 +1,1219 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture wrap mode test case
+ *
+ * \todo [petri]
+ * - loop body cases (do different operations inside the loops)
+ * - more complex nested loops
+ *   * random generated?
+ *   * dataflow variations
+ *   * mixed loop types
+ * -
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderLoopTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuStringTemplate.hpp"
+
+#include "deStringUtil.hpp"
+#include "deInt32.h"
+#include "deMemory.h"
+
+#include <map>
+
+using namespace std;
+using namespace tcu;
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+// Repeated with for, while, do-while. Examples given as 'for' loops.
+// Repeated for const, uniform, dynamic loops.
+enum LoopCase
+{
+	LOOPCASE_EMPTY_BODY = 0,								// for (...) { }
+	LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST,		// for (...) { break; <body>; }
+	LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST,		// for (...) { <body>; break; }
+	LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK,				// for (...) { <body>; if (cond) break; }
+	LOOPCASE_SINGLE_STATEMENT,								// for (...) statement;
+	LOOPCASE_COMPOUND_STATEMENT,							// for (...) { statement; statement; }
+	LOOPCASE_SEQUENCE_STATEMENT,							// for (...) statement, statement;
+	LOOPCASE_NO_ITERATIONS,									// for (i=0; i<0; i++) ...
+	LOOPCASE_SINGLE_ITERATION,								// for (i=0; i<1; i++) ...
+	LOOPCASE_SELECT_ITERATION_COUNT,						// for (i=0; i<a?b:c; i++) ...
+	LOOPCASE_CONDITIONAL_CONTINUE,							// for (...) { if (cond) continue; }
+	LOOPCASE_UNCONDITIONAL_CONTINUE,						// for (...) { <body>; continue; }
+	LOOPCASE_ONLY_CONTINUE,									// for (...) { continue; }
+	LOOPCASE_DOUBLE_CONTINUE,								// for (...) { if (cond) continue; <body>; continue; }
+	LOOPCASE_CONDITIONAL_BREAK,								// for (...) { if (cond) break; }
+	LOOPCASE_UNCONDITIONAL_BREAK,							// for (...) { <body>; break; }
+	LOOPCASE_PRE_INCREMENT,									// for (...; ++i) { <body>; }
+	LOOPCASE_POST_INCREMENT,								// for (...; i++) { <body>; }
+	LOOPCASE_MIXED_BREAK_CONTINUE,
+	LOOPCASE_VECTOR_COUNTER,								// for (ivec3 ndx = ...; ndx.x < ndx.y; ndx.x += ndx.z) { ... }
+	LOOPCASE_101_ITERATIONS,								// loop for 101 iterations
+	LOOPCASE_SEQUENCE,										// two loops in sequence
+	LOOPCASE_NESTED,										// two nested loops
+	LOOPCASE_NESTED_SEQUENCE,								// two loops in sequence nested inside a third
+	LOOPCASE_NESTED_TRICKY_DATAFLOW_1,						// nested loops with tricky data flow
+	LOOPCASE_NESTED_TRICKY_DATAFLOW_2,						// nested loops with tricky data flow
+
+	//LOOPCASE_MULTI_DECLARATION,							// for (int i,j,k; ...) ...  -- illegal?
+
+	LOOPCASE_LAST
+};
+
+static const char* getLoopCaseName (LoopCase loopCase)
+{
+	static const char* s_names[] =
+	{
+		"empty_body",
+		"infinite_with_unconditional_break_first",
+		"infinite_with_unconditional_break_last",
+		"infinite_with_conditional_break",
+		"single_statement",
+		"compound_statement",
+		"sequence_statement",
+		"no_iterations",
+		"single_iteration",
+		"select_iteration_count",
+		"conditional_continue",
+		"unconditional_continue",
+		"only_continue",
+		"double_continue",
+		"conditional_break",
+		"unconditional_break",
+		"pre_increment",
+		"post_increment",
+		"mixed_break_continue",
+		"vector_counter",
+		"101_iterations",
+		"sequence",
+		"nested",
+		"nested_sequence",
+		"nested_tricky_dataflow_1",
+		"nested_tricky_dataflow_2"
+		//"multi_declaration",
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == LOOPCASE_LAST);
+	DE_ASSERT(deInBounds32((int)loopCase, 0, LOOPCASE_LAST));
+	return s_names[(int)loopCase];
+}
+
+// Complex loop cases.
+
+/*enum LoopBody
+{
+	LOOPBODY_READ_UNIFORM = 0,
+	LOOPBODY_READ_UNIFORM_ARRAY,
+	LOOPBODY_READ_
+};*/
+
+enum LoopType
+{
+	LOOPTYPE_FOR = 0,
+	LOOPTYPE_WHILE,
+	LOOPTYPE_DO_WHILE,
+
+	LOOPTYPE_LAST
+};
+
+static const char* getLoopTypeName (LoopType loopType)
+{
+	static const char* s_names[] =
+	{
+		"for",
+		"while",
+		"do_while"
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == LOOPTYPE_LAST);
+	DE_ASSERT(deInBounds32((int)loopType, 0, LOOPTYPE_LAST));
+	return s_names[(int)loopType];
+}
+
+enum LoopCountType
+{
+	LOOPCOUNT_CONSTANT = 0,
+	LOOPCOUNT_UNIFORM,
+	LOOPCOUNT_DYNAMIC,
+
+	LOOPCOUNT_LAST
+};
+
+static const char* getLoopCountTypeName (LoopCountType countType)
+{
+	static const char* s_names[] =
+	{
+		"constant",
+		"uniform",
+		"dynamic"
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == LOOPCOUNT_LAST);
+	DE_ASSERT(deInBounds32((int)countType, 0, LOOPCOUNT_LAST));
+	return s_names[(int)countType];
+}
+
+static void evalLoop0Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(0,1,2); }
+static void evalLoop1Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(1,2,3); }
+static void evalLoop2Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(2,3,0); }
+static void evalLoop3Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(3,0,1); }
+
+static ShaderEvalFunc getLoopEvalFunc (int numIters)
+{
+	switch (numIters % 4)
+	{
+		case 0: return evalLoop0Iters;
+		case 1:	return evalLoop1Iters;
+		case 2:	return evalLoop2Iters;
+		case 3:	return evalLoop3Iters;
+	}
+
+	DE_ASSERT(!"Invalid loop iteration count.");
+	return NULL;
+}
+
+// ShaderLoopCase
+
+class ShaderLoopCase : public ShaderRenderCase
+{
+public:
+								ShaderLoopCase			(Context& context, const char* name, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, const char* vertShaderSource, const char* fragShaderSource);
+	virtual						~ShaderLoopCase			(void);
+
+private:
+								ShaderLoopCase			(const ShaderLoopCase&);	// not allowed!
+	ShaderLoopCase&				operator=				(const ShaderLoopCase&);	// not allowed!
+
+	virtual void				setup					(int programID);
+	virtual void				setupUniforms			(int programID, const Vec4& constCoords);
+};
+
+ShaderLoopCase::ShaderLoopCase (Context& context, const char* name, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, const char* vertShaderSource, const char* fragShaderSource)
+	: ShaderRenderCase(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
+{
+	m_vertShaderSource	= vertShaderSource;
+	m_fragShaderSource	= fragShaderSource;
+}
+
+ShaderLoopCase::~ShaderLoopCase (void)
+{
+}
+
+void ShaderLoopCase::setup (int programID)
+{
+	DE_UNREF(programID);
+}
+
+void ShaderLoopCase::setupUniforms (int programID, const Vec4& constCoords)
+{
+	DE_UNREF(programID);
+	DE_UNREF(constCoords);
+}
+
+// Test case creation.
+
+static ShaderLoopCase* createGenericLoopCase (Context& context, const char* caseName, const char* description, bool isVertexCase, LoopType loopType, LoopCountType loopCountType, Precision loopCountPrecision, DataType loopCountDataType)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n";
+	frag << "#version 300 es\n";
+
+	vtx << "in highp vec4 a_position;\n";
+	vtx << "in highp vec4 a_coords;\n";
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (loopCountType == LOOPCOUNT_DYNAMIC)
+		vtx << "in mediump float a_one;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec3 v_color;\n";
+		frag << "in mediump vec3 v_color;\n";
+	}
+	else
+	{
+		vtx << "out mediump vec4 v_coords;\n";
+		frag << "in mediump vec4 v_coords;\n";
+
+		if (loopCountType == LOOPCOUNT_DYNAMIC)
+		{
+			vtx << "out mediump float v_one;\n";
+			frag << "in mediump float v_one;\n";
+		}
+	}
+
+	// \todo [petri] Pass numLoopIters from outside?
+	int		numLoopIters = 3;
+	bool	isIntCounter = isDataTypeIntOrIVec(loopCountDataType);
+
+	if (isIntCounter)
+	{
+		if (loopCountType == LOOPCOUNT_UNIFORM || loopCountType == LOOPCOUNT_DYNAMIC)
+			op << "uniform ${COUNTER_PRECISION} int " << getIntUniformName(numLoopIters) << ";\n";
+	}
+	else
+	{
+		if (loopCountType == LOOPCOUNT_UNIFORM || loopCountType == LOOPCOUNT_DYNAMIC)
+			op << "uniform ${COUNTER_PRECISION} float " << getFloatFractionUniformName(numLoopIters) << ";\n";
+
+		if (numLoopIters != 1)
+			op << "uniform ${COUNTER_PRECISION} float uf_one;\n";
+	}
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	if (isVertexCase)
+		vtx << "	${PRECISION} vec4 coords = a_coords;\n";
+	else
+		frag << "	${PRECISION} vec4 coords = v_coords;\n";
+
+	if (loopCountType == LOOPCOUNT_DYNAMIC)
+	{
+		if (isIntCounter)
+		{
+			if (isVertexCase)
+				vtx << "	${COUNTER_PRECISION} int one = int(a_one + 0.5);\n";
+			else
+				frag << "	${COUNTER_PRECISION} int one = int(v_one + 0.5);\n";
+		}
+		else
+		{
+			if (isVertexCase)
+				vtx << "	${COUNTER_PRECISION} float one = a_one;\n";
+			else
+				frag << "	${COUNTER_PRECISION} float one = v_one;\n";
+		}
+	}
+
+	// Read array.
+	op << "	${PRECISION} vec4 res = coords;\n";
+
+	// Loop iteration count.
+	string	iterMaxStr;
+
+	if (isIntCounter)
+	{
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			iterMaxStr = de::toString(numLoopIters);
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			iterMaxStr = getIntUniformName(numLoopIters);
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			iterMaxStr = string(getIntUniformName(numLoopIters)) + "*one";
+		else
+			DE_ASSERT(false);
+	}
+	else
+	{
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			iterMaxStr = "1.0";
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			iterMaxStr = "uf_one";
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			iterMaxStr = "uf_one*one";
+		else
+			DE_ASSERT(false);
+	}
+
+	// Loop operations.
+	string initValue		= isIntCounter ? "0" : "0.05";
+	string loopCountDeclStr	= "${COUNTER_PRECISION} ${LOOP_VAR_TYPE} ndx = " + initValue;
+	string loopCmpStr		= ("ndx < " + iterMaxStr);
+	string incrementStr;
+	if (isIntCounter)
+		incrementStr = "ndx++";
+	else
+	{
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			incrementStr = string("ndx += ") + de::toString(1.0f / numLoopIters);
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			incrementStr = string("ndx += ") + getFloatFractionUniformName(numLoopIters);
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			incrementStr = string("ndx += ") + getFloatFractionUniformName(numLoopIters) + "*one";
+		else
+			DE_ASSERT(false);
+	}
+
+	// Loop body.
+	string loopBody;
+
+	loopBody = "		res = res.yzwx;\n";
+
+	if (loopType == LOOPTYPE_FOR)
+	{
+		op << "	for (" + loopCountDeclStr + "; " + loopCmpStr + "; " + incrementStr + ")\n";
+		op << "	{\n";
+		op << loopBody;
+		op << "	}\n";
+	}
+	else if (loopType == LOOPTYPE_WHILE)
+	{
+		op << "\t" << loopCountDeclStr + ";\n";
+		op << "	while (" + loopCmpStr + ")\n";
+		op << "	{\n";
+		op << loopBody;
+		op << "\t\t" + incrementStr + ";\n";
+		op << "	}\n";
+	}
+	else if (loopType == LOOPTYPE_DO_WHILE)
+	{
+		op << "\t" << loopCountDeclStr + ";\n";
+		op << "	do\n";
+		op << "	{\n";
+		op << loopBody;
+		op << "\t\t" + incrementStr + ";\n";
+		op << "	} while (" + loopCmpStr + ");\n";
+	}
+	else
+		DE_ASSERT(false);
+
+	if (isVertexCase)
+	{
+		vtx << "	v_color = res.rgb;\n";
+		frag << "	o_color = vec4(v_color.rgb, 1.0);\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	o_color = vec4(res.rgb, 1.0);\n";
+
+		if (loopCountType == LOOPCOUNT_DYNAMIC)
+			vtx << "	v_one = a_one;\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("LOOP_VAR_TYPE", getDataTypeName(loopCountDataType)));
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+	params.insert(pair<string, string>("COUNTER_PRECISION", getPrecisionName(loopCountPrecision)));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	// Create the case.
+	ShaderEvalFunc evalFunc = getLoopEvalFunc(numLoopIters);
+	return new ShaderLoopCase(context, caseName, description, isVertexCase, evalFunc, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+}
+
+// \todo [petri] Generalize to float as well?
+static ShaderLoopCase* createSpecialLoopCase (Context& context, const char* caseName, const char* description, bool isVertexCase, LoopCase loopCase, LoopType loopType, LoopCountType loopCountType)
+{
+	std::ostringstream vtx;
+	std::ostringstream frag;
+	std::ostringstream& op = isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n";
+	frag << "#version 300 es\n";
+
+	vtx << "in highp vec4 a_position;\n";
+	vtx << "in highp vec4 a_coords;\n";
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (loopCountType == LOOPCOUNT_DYNAMIC)
+		vtx << "in mediump float a_one;\n";
+
+	// Attribute and varyings.
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec3 v_color;\n";
+		frag << "in mediump vec3 v_color;\n";
+	}
+	else
+	{
+		vtx << "out mediump vec4 v_coords;\n";
+		frag << "in mediump vec4 v_coords;\n";
+
+		if (loopCountType == LOOPCOUNT_DYNAMIC)
+		{
+			vtx << "out mediump float v_one;\n";
+			frag << "in mediump float v_one;\n";
+		}
+	}
+
+	if (loopCase == LOOPCASE_SELECT_ITERATION_COUNT)
+		op << "uniform bool ub_true;\n";
+
+	op << "uniform ${COUNTER_PRECISION} int ui_zero, ui_one, ui_two, ui_three, ui_four, ui_five, ui_six;\n";
+	if (loopCase == LOOPCASE_101_ITERATIONS)
+		op << "uniform ${COUNTER_PRECISION} int ui_oneHundredOne;\n";
+
+	int iterCount	= 3;	// value to use in loop
+	int numIters	= 3;	// actual number of iterations
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	if (loopCountType == LOOPCOUNT_DYNAMIC)
+	{
+		if (isVertexCase)
+			vtx << "	${COUNTER_PRECISION} int one = int(a_one + 0.5);\n";
+		else
+			frag << "	${COUNTER_PRECISION} int one = int(v_one + 0.5);\n";
+	}
+
+	if (isVertexCase)
+		vtx << "	${PRECISION} vec4 coords = a_coords;\n";
+	else
+		frag << "	${PRECISION} vec4 coords = v_coords;\n";
+
+	// Read array.
+	op << "	${PRECISION} vec4 res = coords;\n";
+
+	// Handle all loop types.
+	string counterPrecisionStr = "mediump";
+	string forLoopStr;
+	string whileLoopStr;
+	string doWhileLoopPreStr;
+	string doWhileLoopPostStr;
+
+	if (loopType == LOOPTYPE_FOR)
+	{
+		switch (loopCase)
+		{
+			case LOOPCASE_EMPTY_BODY:
+				numIters = 0;
+				op << "	${FOR_LOOP} {}\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST:
+				numIters = 0;
+				op << "	for (;;) { break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST:
+				numIters = 1;
+				op << "	for (;;) { res = res.yzwx; break; }\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	for (;;) { res = res.yzwx; if (i == ${ONE}) break; i++; }\n";
+				break;
+
+			case LOOPCASE_SINGLE_STATEMENT:
+				op << "	${FOR_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_COMPOUND_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${FOR_LOOP} { res = res.yzwx; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_SEQUENCE_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${FOR_LOOP} res = res.yzwx, res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_NO_ITERATIONS:
+				iterCount	= 0;
+				numIters	= 0;
+				op << "	${FOR_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SINGLE_ITERATION:
+				iterCount	= 1;
+				numIters	= 1;
+				op << "	${FOR_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SELECT_ITERATION_COUNT:
+				op << "	for (int i = 0; i < (ub_true ? ${ITER_COUNT} : 0); i++) res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${FOR_LOOP} { if (i == ${TWO}) continue; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_CONTINUE:
+				op << "	${FOR_LOOP} { res = res.yzwx; continue; }\n";
+				break;
+
+			case LOOPCASE_ONLY_CONTINUE:
+				numIters = 0;
+				op << "	${FOR_LOOP} { continue; }\n";
+				break;
+
+			case LOOPCASE_DOUBLE_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${FOR_LOOP} { if (i == ${TWO}) continue; res = res.yzwx; continue; }\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${FOR_LOOP} { if (i == ${TWO}) break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_BREAK:
+				numIters = 1;
+				op << "	${FOR_LOOP} { res = res.yzwx; break; }\n";
+				break;
+
+			case LOOPCASE_PRE_INCREMENT:
+				op << "	for (int i = 0; i < ${ITER_COUNT}; ++i) { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_POST_INCREMENT:
+				op << "	${FOR_LOOP} { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_MIXED_BREAK_CONTINUE:
+				numIters	= 2;
+				iterCount	= 5;
+				op << "	${FOR_LOOP} { if (i == 0) continue; else if (i == 3) break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_VECTOR_COUNTER:
+				op << "	for (${COUNTER_PRECISION} ivec4 i = ivec4(0, 1, ${ITER_COUNT}, 0); i.x < i.z; i.x += i.y) { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_101_ITERATIONS:
+				numIters = iterCount = 101;
+				op << "	${FOR_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SEQUENCE:
+				iterCount	= 5;
+				numIters	= 5;
+				op << "	${COUNTER_PRECISION} int i;\n";
+				op << "	for (i = 0; i < ${TWO}; i++) { res = res.yzwx; }\n";
+				op << "	for (; i < ${ITER_COUNT}; i++) { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_NESTED:
+				numIters = 2 * iterCount;
+				op << "	for (${COUNTER_PRECISION} int i = 0; i < ${TWO}; i++)\n";
+				op << "	{\n";
+				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${ITER_COUNT}; j++)\n";
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_SEQUENCE:
+				numIters = 3 * iterCount;
+				op << "	for (${COUNTER_PRECISION} int i = 0; i < ${ITER_COUNT}; i++)\n";
+				op << "	{\n";
+				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${TWO}; j++)\n";
+				op << "			res = res.yzwx;\n";
+				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${ONE}; j++)\n";
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_1:
+				numIters = 2;
+				op << "	${FOR_LOOP}\n";
+				op << "	{\n";
+				op << "		res = coords; // ignore outer loop effect \n";
+				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${TWO}; j++)\n";
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_2:
+				numIters = iterCount;
+				op << "	${FOR_LOOP}\n";
+				op << "	{\n";
+				op << "		res = coords.wxyz;\n";
+				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${TWO}; j++)\n";
+				op << "			res = res.yzwx;\n";
+				op << "		coords = res;\n";
+				op << "	}\n";
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			forLoopStr = string("for (") + counterPrecisionStr + " int i = 0; i < " + de::toString(iterCount) + "; i++)";
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			forLoopStr = string("for (") + counterPrecisionStr + " int i = 0; i < " + getIntUniformName(iterCount) + "; i++)";
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			forLoopStr = string("for (") + counterPrecisionStr + " int i = 0; i < one*" + getIntUniformName(iterCount) + "; i++)";
+		else
+			DE_ASSERT(false);
+	}
+	else if (loopType == LOOPTYPE_WHILE)
+	{
+		switch (loopCase)
+		{
+			case LOOPCASE_EMPTY_BODY:
+				numIters = 0;
+				op << "	${WHILE_LOOP} {}\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST:
+				numIters = 0;
+				op << "	while (true) { break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST:
+				numIters = 1;
+				op << "	while (true) { res = res.yzwx; break; }\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (true) { res = res.yzwx; if (i == ${ONE}) break; i++; }\n";
+				break;
+
+			case LOOPCASE_SINGLE_STATEMENT:
+				op << "	${WHILE_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_COMPOUND_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${WHILE_LOOP} { res = res.yzwx; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_SEQUENCE_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${WHILE_LOOP} res = res.yzwx, res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_NO_ITERATIONS:
+				iterCount	= 0;
+				numIters	= 0;
+				op << "	${WHILE_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SINGLE_ITERATION:
+				iterCount	= 1;
+				numIters	= 1;
+				op << "	${WHILE_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SELECT_ITERATION_COUNT:
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (i < (ub_true ? ${ITER_COUNT} : 0)) { res = res.yzwx; i++; }\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${WHILE_LOOP} { if (i == ${TWO}) continue; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_CONTINUE:
+				op << "	${WHILE_LOOP} { res = res.yzwx; continue; }\n";
+				break;
+
+			case LOOPCASE_ONLY_CONTINUE:
+				numIters = 0;
+				op << "	${WHILE_LOOP} { continue; }\n";
+				break;
+
+			case LOOPCASE_DOUBLE_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${WHILE_LOOP} { if (i == ${ONE}) continue; res = res.yzwx; continue; }\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${WHILE_LOOP} { if (i == ${THREE}) break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_BREAK:
+				numIters = 1;
+				op << "	${WHILE_LOOP} { res = res.yzwx; break; }\n";
+				break;
+
+			case LOOPCASE_PRE_INCREMENT:
+				numIters = iterCount - 1;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (++i < ${ITER_COUNT}) { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_POST_INCREMENT:
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (i++ < ${ITER_COUNT}) { res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_MIXED_BREAK_CONTINUE:
+				numIters	= 2;
+				iterCount	= 5;
+				op << "	${WHILE_LOOP} { if (i == 0) continue; else if (i == 3) break; res = res.yzwx; }\n";
+				break;
+
+			case LOOPCASE_VECTOR_COUNTER:
+				op << "	${COUNTER_PRECISION} ivec4 i = ivec4(0, 1, ${ITER_COUNT}, 0);\n";
+				op << "	while (i.x < i.z) { res = res.yzwx; i.x += i.y; }\n";
+				break;
+
+			case LOOPCASE_101_ITERATIONS:
+				numIters = iterCount = 101;
+				op << "	${WHILE_LOOP} res = res.yzwx;\n";
+				break;
+
+			case LOOPCASE_SEQUENCE:
+				iterCount	= 6;
+				numIters	= iterCount - 1;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (i++ < ${TWO}) { res = res.yzwx; }\n";
+				op << "	while (i++ < ${ITER_COUNT}) { res = res.yzwx; }\n"; // \note skips one iteration
+				break;
+
+			case LOOPCASE_NESTED:
+				numIters = 2 * iterCount;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (i++ < ${TWO})\n";
+				op << "	{\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		while (j++ < ${ITER_COUNT})\n";
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_SEQUENCE:
+				numIters = 2 * iterCount;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	while (i++ < ${ITER_COUNT})\n";
+				op << "	{\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		while (j++ < ${ONE})\n";
+				op << "			res = res.yzwx;\n";
+				op << "		while (j++ < ${THREE})\n"; // \note skips one iteration
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_1:
+				numIters = 2;
+				op << "	${WHILE_LOOP}\n";
+				op << "	{\n";
+				op << "		res = coords; // ignore outer loop effect \n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		while (j++ < ${TWO})\n";
+				op << "			res = res.yzwx;\n";
+				op << "	}\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_2:
+				numIters = iterCount;
+				op << "	${WHILE_LOOP}\n";
+				op << "	{\n";
+				op << "		res = coords.wxyz;\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		while (j++ < ${TWO})\n";
+				op << "			res = res.yzwx;\n";
+				op << "		coords = res;\n";
+				op << "	}\n";
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			whileLoopStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "	while(i++ < " + de::toString(iterCount) + ")";
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			whileLoopStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "	while(i++ < " + getIntUniformName(iterCount) + ")";
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			whileLoopStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "	while(i++ < one*" + getIntUniformName(iterCount) + ")";
+		else
+			DE_ASSERT(false);
+	}
+	else
+	{
+		DE_ASSERT(loopType == LOOPTYPE_DO_WHILE);
+
+		switch (loopCase)
+		{
+			case LOOPCASE_EMPTY_BODY:
+				numIters = 0;
+				op << "	${DO_WHILE_PRE} {} ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST:
+				numIters = 0;
+				op << "	do { break; res = res.yzwx; } while (true);\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST:
+				numIters = 1;
+				op << "	do { res = res.yzwx; break; } while (true);\n";
+				break;
+
+			case LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do { res = res.yzwx; if (i == ${ONE}) break; i++; } while (true);\n";
+				break;
+
+			case LOOPCASE_SINGLE_STATEMENT:
+				op << "	${DO_WHILE_PRE} res = res.yzwx; ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_COMPOUND_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${DO_WHILE_PRE} { res = res.yzwx; res = res.yzwx; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_SEQUENCE_STATEMENT:
+				iterCount	= 2;
+				numIters	= 2 * iterCount;
+				op << "	${DO_WHILE_PRE} res = res.yzwx, res = res.yzwx; ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_NO_ITERATIONS:
+				DE_ASSERT(false);
+				break;
+
+			case LOOPCASE_SINGLE_ITERATION:
+				iterCount	= 1;
+				numIters	= 1;
+				op << "	${DO_WHILE_PRE} res = res.yzwx; ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_SELECT_ITERATION_COUNT:
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do { res = res.yzwx; } while (++i < (ub_true ? ${ITER_COUNT} : 0));\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${DO_WHILE_PRE} { if (i == ${TWO}) continue; res = res.yzwx; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_CONTINUE:
+				op << "	${DO_WHILE_PRE} { res = res.yzwx; continue; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_ONLY_CONTINUE:
+				numIters = 0;
+				op << "	${DO_WHILE_PRE} { continue; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_DOUBLE_CONTINUE:
+				numIters = iterCount - 1;
+				op << "	${DO_WHILE_PRE} { if (i == ${TWO}) continue; res = res.yzwx; continue; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_CONDITIONAL_BREAK:
+				numIters = 2;
+				op << "	${DO_WHILE_PRE} { res = res.yzwx; if (i == ${ONE}) break; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_UNCONDITIONAL_BREAK:
+				numIters = 1;
+				op << "	${DO_WHILE_PRE} { res = res.yzwx; break; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_PRE_INCREMENT:
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do { res = res.yzwx; } while (++i < ${ITER_COUNT});\n";
+				break;
+
+			case LOOPCASE_POST_INCREMENT:
+				numIters = iterCount + 1;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do { res = res.yzwx; } while (i++ < ${ITER_COUNT});\n";
+				break;
+
+			case LOOPCASE_MIXED_BREAK_CONTINUE:
+				numIters	= 2;
+				iterCount	= 5;
+				op << "	${DO_WHILE_PRE} { if (i == 0) continue; else if (i == 3) break; res = res.yzwx; } ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_VECTOR_COUNTER:
+				op << "	${COUNTER_PRECISION} ivec4 i = ivec4(0, 1, ${ITER_COUNT}, 0);\n";
+				op << "	do { res = res.yzwx; } while ((i.x += i.y) < i.z);\n";
+				break;
+
+			case LOOPCASE_101_ITERATIONS:
+				numIters = iterCount = 101;
+				op << "	${DO_WHILE_PRE} res = res.yzwx; ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_SEQUENCE:
+				iterCount	= 5;
+				numIters	= 5;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do { res = res.yzwx; } while (++i < ${TWO});\n";
+				op << "	do { res = res.yzwx; } while (++i < ${ITER_COUNT});\n";
+				break;
+
+			case LOOPCASE_NESTED:
+				numIters = 2 * iterCount;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do\n";
+				op << "	{\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		do\n";
+				op << "			res = res.yzwx;\n";
+				op << "		while (++j < ${ITER_COUNT});\n";
+				op << "	} while (++i < ${TWO});\n";
+				break;
+
+			case LOOPCASE_NESTED_SEQUENCE:
+				numIters = 3 * iterCount;
+				op << "	${COUNTER_PRECISION} int i = 0;\n";
+				op << "	do\n";
+				op << "	{\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		do\n";
+				op << "			res = res.yzwx;\n";
+				op << "		while (++j < ${TWO});\n";
+				op << "		do\n";
+				op << "			res = res.yzwx;\n";
+				op << "		while (++j < ${THREE});\n";
+				op << "	} while (++i < ${ITER_COUNT});\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_1:
+				numIters = 2;
+				op << "	${DO_WHILE_PRE}\n";
+				op << "	{\n";
+				op << "		res = coords; // ignore outer loop effect \n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		do\n";
+				op << "			res = res.yzwx;\n";
+				op << "		while (++j < ${TWO});\n";
+				op << "	} ${DO_WHILE_POST}\n";
+				break;
+
+			case LOOPCASE_NESTED_TRICKY_DATAFLOW_2:
+				numIters = iterCount;
+				op << "	${DO_WHILE_PRE}\n";
+				op << "	{\n";
+				op << "		res = coords.wxyz;\n";
+				op << "		${COUNTER_PRECISION} int j = 0;\n";
+				op << "		while (j++ < ${TWO})\n";
+				op << "			res = res.yzwx;\n";
+				op << "		coords = res;\n";
+				op << "	} ${DO_WHILE_POST}\n";
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		doWhileLoopPreStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "\tdo ";
+		if (loopCountType == LOOPCOUNT_CONSTANT)
+			doWhileLoopPostStr = string(" while (++i < ") + de::toString(iterCount) + ");\n";
+		else if (loopCountType == LOOPCOUNT_UNIFORM)
+			doWhileLoopPostStr = string(" while (++i < ") + getIntUniformName(iterCount) + ");\n";
+		else if (loopCountType == LOOPCOUNT_DYNAMIC)
+			doWhileLoopPostStr = string(" while (++i < one*") + getIntUniformName(iterCount) + ");\n";
+		else
+			DE_ASSERT(false);
+	}
+
+	// Shader footers.
+	if (isVertexCase)
+	{
+		vtx << "	v_color = res.rgb;\n";
+		frag << "	o_color = vec4(v_color.rgb, 1.0);\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	o_color = vec4(res.rgb, 1.0);\n";
+
+		if (loopCountType == LOOPCOUNT_DYNAMIC)
+			vtx << "	v_one = a_one;\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	// Constants.
+	string oneStr;
+	string twoStr;
+	string threeStr;
+	string iterCountStr;
+
+	if (loopCountType == LOOPCOUNT_CONSTANT)
+	{
+		oneStr			= "1";
+		twoStr			= "2";
+		threeStr		= "3";
+		iterCountStr	= de::toString(iterCount);
+	}
+	else if (loopCountType == LOOPCOUNT_UNIFORM)
+	{
+		oneStr			= "ui_one";
+		twoStr			= "ui_two";
+		threeStr		= "ui_three";
+		iterCountStr	= getIntUniformName(iterCount);
+	}
+	else if (loopCountType == LOOPCOUNT_DYNAMIC)
+	{
+		oneStr			= "one*ui_one";
+		twoStr			= "one*ui_two";
+		threeStr		= "one*ui_three";
+		iterCountStr	= string("one*") + getIntUniformName(iterCount);
+	}
+	else DE_ASSERT(false);
+
+	// Fill in shader templates.
+	map<string, string> params;
+	params.insert(pair<string, string>("PRECISION", "mediump"));
+	params.insert(pair<string, string>("ITER_COUNT", iterCountStr));
+	params.insert(pair<string, string>("COUNTER_PRECISION", counterPrecisionStr));
+	params.insert(pair<string, string>("FOR_LOOP", forLoopStr));
+	params.insert(pair<string, string>("WHILE_LOOP", whileLoopStr));
+	params.insert(pair<string, string>("DO_WHILE_PRE", doWhileLoopPreStr));
+	params.insert(pair<string, string>("DO_WHILE_POST", doWhileLoopPostStr));
+	params.insert(pair<string, string>("ONE", oneStr));
+	params.insert(pair<string, string>("TWO", twoStr));
+	params.insert(pair<string, string>("THREE", threeStr));
+
+	StringTemplate vertTemplate(vtx.str().c_str());
+	StringTemplate fragTemplate(frag.str().c_str());
+	string vertexShaderSource = vertTemplate.specialize(params);
+	string fragmentShaderSource = fragTemplate.specialize(params);
+
+	// Create the case.
+	ShaderEvalFunc evalFunc = getLoopEvalFunc(numIters);
+	return new ShaderLoopCase(context, caseName, description, isVertexCase, evalFunc, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
+};
+
+// ShaderLoopTests.
+
+ShaderLoopTests::ShaderLoopTests(Context& context)
+	: TestCaseGroup(context, "loops", "Loop Tests")
+{
+}
+
+ShaderLoopTests::~ShaderLoopTests (void)
+{
+}
+
+void ShaderLoopTests::init (void)
+{
+	// Loop cases.
+
+	static const ShaderType s_shaderTypes[] =
+	{
+		SHADERTYPE_VERTEX,
+		SHADERTYPE_FRAGMENT
+	};
+
+	static const DataType s_countDataType[] =
+	{
+		TYPE_INT,
+		TYPE_FLOAT
+	};
+
+	for (int loopType = 0; loopType < LOOPTYPE_LAST; loopType++)
+	{
+		const char* loopTypeName = getLoopTypeName((LoopType)loopType);
+
+		for (int loopCountType = 0; loopCountType < LOOPCOUNT_LAST; loopCountType++)
+		{
+			const char* loopCountName = getLoopCountTypeName((LoopCountType)loopCountType);
+
+			string groupName = string(loopTypeName) + "_" + string(loopCountName) + "_iterations";
+			string groupDesc = string("Loop tests with ") + loopCountName + " loop counter.";
+			TestCaseGroup* group = new TestCaseGroup(m_context, groupName.c_str(), groupDesc.c_str());
+			addChild(group);
+
+			// Generic cases.
+
+			for (int precision = 0; precision < PRECISION_LAST; precision++)
+			{
+				const char* precisionName = getPrecisionName((Precision)precision);
+
+				for (int dataTypeNdx = 0; dataTypeNdx < DE_LENGTH_OF_ARRAY(s_countDataType); dataTypeNdx++)
+				{
+					DataType loopDataType = s_countDataType[dataTypeNdx];
+					const char* dataTypeName = getDataTypeName(loopDataType);
+
+					for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+					{
+						ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+						const char*	shaderTypeName	= getShaderTypeName(shaderType);
+						bool		isVertexCase	= (shaderType == SHADERTYPE_VERTEX);
+
+						string name = string("basic_") + precisionName + "_" + dataTypeName + "_" + shaderTypeName;
+						string desc = string(loopTypeName) + " loop with " + precisionName + dataTypeName + " " + loopCountName + " iteration count in " + shaderTypeName + " shader.";
+						group->addChild(createGenericLoopCase(m_context, name.c_str(), desc.c_str(), isVertexCase, (LoopType)loopType, (LoopCountType)loopCountType, (Precision)precision, loopDataType));
+					}
+				}
+			}
+
+			// Special cases.
+
+			for (int loopCase = 0; loopCase < LOOPCASE_LAST; loopCase++)
+			{
+				const char* loopCaseName = getLoopCaseName((LoopCase)loopCase);
+
+				// no-iterations not possible with do-while.
+				if ((loopCase == LOOPCASE_NO_ITERATIONS) && (loopType == LOOPTYPE_DO_WHILE))
+					continue;
+
+				for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+				{
+					ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
+					const char*	shaderTypeName	= getShaderTypeName(shaderType);
+					bool		isVertexCase	= (shaderType == SHADERTYPE_VERTEX);
+
+					string name = string(loopCaseName) + "_" + shaderTypeName;
+					string desc = string(loopCaseName) + " loop with " + loopTypeName + " iteration count in " + shaderTypeName + " shader.";
+					group->addChild(createSpecialLoopCase(m_context, name.c_str(), desc.c_str(), isVertexCase, (LoopCase)loopCase, (LoopType)loopType, (LoopCountType)loopCountType));
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderLoopTests.hpp b/modules/gles3/functional/es3fShaderLoopTests.hpp
new file mode 100644
index 0000000..c5407f9
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderLoopTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERLOOPTESTS_HPP
+#define _ES3FSHADERLOOPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader loop tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderLoopTests : public TestCaseGroup
+{
+public:
+							ShaderLoopTests			(Context& context);
+	virtual					~ShaderLoopTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderLoopTests			(const ShaderLoopTests&);		// not allowed!
+	ShaderLoopTests&		operator=				(const ShaderLoopTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERLOOPTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderMatrixTests.cpp b/modules/gles3/functional/es3fShaderMatrixTests.cpp
new file mode 100644
index 0000000..1f0a5ee
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderMatrixTests.cpp
@@ -0,0 +1,1973 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader matrix arithmetic tests.
+ *
+ * Variables:
+ *  + operation
+ *    - mat OP mat
+ *    - mat OP vec
+ *    - vec OP mat
+ *    - mat OP scalar
+ *    - OP ( mat )
+ *    - vec OP vec
+ *    - OP mat
+ *  + matrix source
+ *    - constant (ctor)
+ *    - uniform
+ *    - vertex input
+ *    - fragment input
+ *  + other operand: always dynamic data?
+ *  + how to reduce to vec3?
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderMatrixTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuMatrixUtil.hpp"
+#include "deStringUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using namespace glu;
+using namespace deqp::gls;
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::Mat2;
+using tcu::Mat2x3;
+using tcu::Mat2x4;
+using tcu::Mat3x2;
+using tcu::Mat3;
+using tcu::Mat3x4;
+using tcu::Mat4x2;
+using tcu::Mat4x3;
+using tcu::Mat4;
+
+// Uniform / constant values for tests.
+// \note Input1 should not contain 0 components as it is used as divisor in div cases.
+// \todo [2012-02-14 pyry] Make these dynamic.
+static const float	s_constInFloat[2]	= { 0.5f, -0.2f };
+static const Vec2	s_constInVec2[2]	= { Vec2(1.2f, 0.5f), Vec2(0.5f, 1.0f) };
+static const Vec3	s_constInVec3[2]	= { Vec3(1.1f, 0.1f, 0.5f), Vec3(-0.2f, 0.5f, 0.8f) };
+static const Vec4	s_constInVec4[2]	= { Vec4(1.4f, 0.2f, -0.5f, 0.7f), Vec4(0.2f, -1.0f, 0.5f, 0.8f) };
+
+static const float s_constInMat2x2[2][4] =
+{
+	{
+		-0.1f,  1.0f,
+		-0.2f,  0.0f,
+	},
+	{
+		 0.8f,  0.1f,
+		 0.5f, -0.9f,
+	},
+};
+static const float s_constInMat3x2[2][6] =
+{
+	{
+		 0.8f, -0.3f,  0.3f,
+		 1.0f,  1.2f, -1.2f,
+	},
+	{
+		 1.2f, -1.0f,  0.5f,
+		-0.8f,  1.1f,  0.3f,
+	},
+};
+static const float s_constInMat4x2[2][8] =
+{
+	{
+		-0.2f,  0.5f, 0.0f, -1.0f,
+		 1.2f, -0.5f, 0.3f, -0.9f,
+	},
+	{
+		1.0f,  0.1f, -1.1f,  0.6f,
+		0.8f, -1.2f, -1.1f,  0.7f,
+	},
+};
+static const float s_constInMat2x3[2][6] =
+{
+	{
+		-0.6f, -0.1f,
+		-0.7f, -1.2f,
+		-0.2f,  0.0f,
+	},
+	{
+		 1.1f,  0.6f,
+		 0.8f,  1.0f,
+		 0.7f,  0.1f,
+	},
+};
+static const float s_constInMat3x3[2][9] =
+{
+	{
+		-0.2f,  1.1f, 1.2f,
+		-1.0f,  1.2f, 0.5f,
+		 0.7f, -0.2f, 1.0f,
+	},
+	{
+		-0.1f, -0.1f,  0.1f,
+		-0.1f, -0.2f,  1.0f,
+		-0.5f,  0.1f, -0.4f,
+	},
+};
+static const float s_constInMat4x3[2][12] =
+{
+	{
+		-0.9f,  0.0f,  0.6f,  0.2f,
+		 0.9f, -0.1f, -0.3f, -0.7f,
+		-0.1f,  0.1f,  1.0f,  0.0f,
+	},
+	{
+		 0.5f,  0.7f,  0.7f,  1.2f,
+		 1.1f,  0.1f,  1.0f, -1.0f,
+		-0.2f, -0.2f, -0.3f, -0.5f,
+	},
+};
+static const float s_constInMat2x4[2][8] =
+{
+	{
+		-0.6f, -1.1f,
+		-0.6f, -0.6f,
+		-0.2f, -0.6f,
+		-0.1f, -0.1f,
+	},
+	{
+		-1.2f, -1.0f,
+		 0.7f, -1.0f,
+		 0.7f,  0.7f,
+		-0.4f, -0.3f,
+	},
+};
+static const float s_constInMat3x4[2][12] =
+{
+	{
+		 0.6f, -0.4f,  1.2f,
+		 0.9f,  0.8f,  0.4f,
+		 1.1f,  0.3f,  0.5f,
+		-0.2f,  0.0f,  1.1f,
+	},
+	{
+		-0.8f,  1.2f, -0.2f,
+		-1.1f, -0.9f, -0.5f,
+		-1.2f,  1.0f,  1.2f,
+		 0.1f, -0.7f, -0.5f,
+	},
+};
+static const float s_constInMat4x4[2][16] =
+{
+	{
+		 0.3f,  0.9f, -0.2f,  1.0f,
+		-0.4f, -0.6f,  0.6f, -1.0f,
+		-0.9f, -0.1f,  0.3f, -0.2f,
+		-0.3f, -0.9f,  1.0f,  0.1f,
+	},
+	{
+		 0.4f, -0.7f, -0.8f,  0.7f,
+		-0.4f, -0.8f,  0.6f, -0.3f,
+		 0.7f, -1.0f,  0.1f, -0.3f,
+		 0.2f,  0.6f,  0.4f, -1.0f,
+	},
+};
+
+namespace MatrixCaseUtils
+{
+
+enum InputType
+{
+	INPUTTYPE_CONST = 0,
+	INPUTTYPE_UNIFORM,
+	INPUTTYPE_DYNAMIC,
+
+	INPUTTYPE_LAST
+};
+
+struct ShaderInput
+{
+	ShaderInput (InputType inputType_, DataType dataType_, Precision precision_)
+		: inputType	(inputType_)
+		, dataType	(dataType_)
+		, precision	(precision_)
+	{
+	}
+
+	InputType		inputType;
+	DataType		dataType;
+	Precision		precision;
+};
+
+enum MatrixOp
+{
+	OP_ADD = 0,
+	OP_SUB,
+	OP_MUL,
+	OP_DIV,
+	OP_COMP_MUL,
+	OP_OUTER_PRODUCT,
+	OP_TRANSPOSE,
+	OP_INVERSE,
+	OP_DETERMINANT,
+	OP_UNARY_PLUS,
+	OP_NEGATION,
+	OP_PRE_INCREMENT,
+	OP_PRE_DECREMENT,
+	OP_POST_INCREMENT,
+	OP_POST_DECREMENT,
+	OP_ADD_INTO,
+	OP_SUBTRACT_FROM,
+	OP_MULTIPLY_INTO,
+	OP_DIVIDE_INTO,
+	OP_LAST
+};
+
+// Type traits.
+
+template <int DataT>
+struct TypeTraits;
+
+#define DECLARE_TYPE_TRAIT(DATATYPE, TYPE)	\
+template<>									\
+struct TypeTraits<DATATYPE> {				\
+	typedef TYPE Type;						\
+}
+
+DECLARE_TYPE_TRAIT(TYPE_FLOAT,			float);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_VEC2,		tcu::Vec2);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_VEC3,		tcu::Vec3);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_VEC4,		tcu::Vec4);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT2,		tcu::Mat2);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT2X3,	tcu::Mat2x3);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT2X4,	tcu::Mat2x4);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT3X2,	tcu::Mat3x2);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT3,		tcu::Mat3);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT3X4,	tcu::Mat3x4);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT4X2,	tcu::Mat4x2);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT4X3,	tcu::Mat4x3);
+DECLARE_TYPE_TRAIT(TYPE_FLOAT_MAT4,		tcu::Mat4);
+
+// Operation info
+
+enum OperationType
+{
+	OPERATIONTYPE_BINARY_OPERATOR = 0,
+	OPERATIONTYPE_BINARY_FUNCTION,
+	OPERATIONTYPE_UNARY_PREFIX_OPERATOR,
+	OPERATIONTYPE_UNARY_POSTFIX_OPERATOR,
+	OPERATIONTYPE_UNARY_FUNCTION,
+	OPERATIONTYPE_ASSIGNMENT,
+
+	OPERATIONTYPE_LAST
+};
+
+static const char* getOperationName (MatrixOp op)
+{
+	switch (op)
+	{
+		case OP_ADD:			return "+";
+		case OP_SUB:			return "-";
+		case OP_MUL:			return "*";
+		case OP_DIV:			return "/";
+		case OP_COMP_MUL:		return "matrixCompMult";
+		case OP_OUTER_PRODUCT:	return "outerProduct";
+		case OP_TRANSPOSE:		return "transpose";
+		case OP_INVERSE:		return "inverse";
+		case OP_DETERMINANT:	return "determinant";
+		case OP_UNARY_PLUS:		return "+";
+		case OP_NEGATION:		return "-";
+		case OP_PRE_INCREMENT:	return "++";
+		case OP_PRE_DECREMENT:	return "--";
+		case OP_POST_INCREMENT:	return "++";
+		case OP_POST_DECREMENT:	return "--";
+		case OP_ADD_INTO:		return "+=";
+		case OP_SUBTRACT_FROM:	return "-=";
+		case OP_MULTIPLY_INTO:	return "*=";
+		case OP_DIVIDE_INTO:	return "/=";
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return "";
+	}
+}
+
+static OperationType getOperationType (MatrixOp op)
+{
+	switch (op)
+	{
+		case OP_ADD:			return OPERATIONTYPE_BINARY_OPERATOR;
+		case OP_SUB:			return OPERATIONTYPE_BINARY_OPERATOR;
+		case OP_MUL:			return OPERATIONTYPE_BINARY_OPERATOR;
+		case OP_DIV:			return OPERATIONTYPE_BINARY_OPERATOR;
+		case OP_COMP_MUL:		return OPERATIONTYPE_BINARY_FUNCTION;
+		case OP_OUTER_PRODUCT:	return OPERATIONTYPE_BINARY_FUNCTION;
+		case OP_TRANSPOSE:		return OPERATIONTYPE_UNARY_FUNCTION;
+		case OP_INVERSE:		return OPERATIONTYPE_UNARY_FUNCTION;
+		case OP_DETERMINANT:	return OPERATIONTYPE_UNARY_FUNCTION;
+		case OP_UNARY_PLUS:		return OPERATIONTYPE_UNARY_PREFIX_OPERATOR;
+		case OP_NEGATION:		return OPERATIONTYPE_UNARY_PREFIX_OPERATOR;
+		case OP_PRE_INCREMENT:	return OPERATIONTYPE_UNARY_PREFIX_OPERATOR;
+		case OP_PRE_DECREMENT:	return OPERATIONTYPE_UNARY_PREFIX_OPERATOR;
+		case OP_POST_INCREMENT:	return OPERATIONTYPE_UNARY_POSTFIX_OPERATOR;
+		case OP_POST_DECREMENT:	return OPERATIONTYPE_UNARY_POSTFIX_OPERATOR;
+		case OP_ADD_INTO:		return OPERATIONTYPE_ASSIGNMENT;
+		case OP_SUBTRACT_FROM:	return OPERATIONTYPE_ASSIGNMENT;
+		case OP_MULTIPLY_INTO:	return OPERATIONTYPE_ASSIGNMENT;
+		case OP_DIVIDE_INTO:	return OPERATIONTYPE_ASSIGNMENT;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return OPERATIONTYPE_LAST;
+	}
+}
+
+enum TestMatrixType
+{
+	TESTMATRIXTYPE_DEFAULT = 0,
+	TESTMATRIXTYPE_NEGATED,
+	TESTMATRIXTYPE_INCREMENTED,
+	TESTMATRIXTYPE_DECREMENTED,
+	TESTMATRIXTYPE_NEGATED_INCREMENTED,
+	TESTMATRIXTYPE_INCREMENTED_LESS,
+
+	TESTMATRIXTYPE_LAST
+};
+
+static TestMatrixType getOperationTestMatrixType (MatrixOp op)
+{
+	switch(op)
+	{
+		case OP_ADD:			return TESTMATRIXTYPE_DEFAULT;
+		case OP_SUB:			return TESTMATRIXTYPE_DEFAULT;
+		case OP_MUL:			return TESTMATRIXTYPE_DEFAULT;
+		case OP_DIV:			return TESTMATRIXTYPE_DEFAULT;
+		case OP_COMP_MUL:		return TESTMATRIXTYPE_DEFAULT;
+		case OP_OUTER_PRODUCT:	return TESTMATRIXTYPE_DEFAULT;
+		case OP_TRANSPOSE:		return TESTMATRIXTYPE_DEFAULT;
+		case OP_INVERSE:		return TESTMATRIXTYPE_DEFAULT;
+		case OP_DETERMINANT:	return TESTMATRIXTYPE_DEFAULT;
+		case OP_UNARY_PLUS:		return TESTMATRIXTYPE_DECREMENTED;
+		case OP_NEGATION:		return TESTMATRIXTYPE_NEGATED_INCREMENTED;
+		case OP_PRE_INCREMENT:	return TESTMATRIXTYPE_NEGATED;
+		case OP_PRE_DECREMENT:	return TESTMATRIXTYPE_INCREMENTED;
+		case OP_POST_INCREMENT:	return TESTMATRIXTYPE_NEGATED;
+		case OP_POST_DECREMENT:	return TESTMATRIXTYPE_DEFAULT;
+		case OP_ADD_INTO:		return TESTMATRIXTYPE_DEFAULT;
+		case OP_SUBTRACT_FROM:	return TESTMATRIXTYPE_INCREMENTED_LESS;
+		case OP_MULTIPLY_INTO:	return TESTMATRIXTYPE_NEGATED;
+		case OP_DIVIDE_INTO:	return TESTMATRIXTYPE_DECREMENTED;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return TESTMATRIXTYPE_LAST;
+	}
+}
+
+static bool isOperationBinary (MatrixOp op)
+{
+	return getOperationType(op) == OPERATIONTYPE_BINARY_OPERATOR ||
+	       getOperationType(op) == OPERATIONTYPE_BINARY_FUNCTION ||
+	       getOperationType(op) == OPERATIONTYPE_ASSIGNMENT;
+}
+
+static bool isOperationMatrixScalar (MatrixOp op)
+{
+	return op == OP_ADD || op == OP_SUB || op == OP_MUL || op == OP_DIV;
+}
+
+static bool isOperationMatrixVector (MatrixOp op)
+{
+	return op == OP_MUL;
+}
+
+static bool isOperationArithmeticMatrixMatrix (MatrixOp op)
+{
+	return op == OP_MUL;
+}
+
+static bool isOperationComponentwiseMatrixMatrix (MatrixOp op)
+{
+	return op == OP_ADD || op == OP_SUB || op == OP_MUL || op == OP_DIV || op == OP_COMP_MUL;
+}
+
+static bool isOperationVectorVector (MatrixOp op)
+{
+	return op == OP_OUTER_PRODUCT;
+}
+
+static bool isOperationUnaryAnyMatrix (MatrixOp op)
+{
+	return  op == OP_TRANSPOSE			 ||
+			op == OP_UNARY_PLUS			 ||
+			op == OP_NEGATION			 ||
+			op == OP_PRE_INCREMENT		 ||
+			op == OP_PRE_DECREMENT		 ||
+			op == OP_POST_INCREMENT		 ||
+			op == OP_POST_DECREMENT;
+}
+
+static bool isOperationUnarySymmetricMatrix (MatrixOp op)
+{
+	return op == OP_INVERSE || op == OP_DETERMINANT;
+}
+
+static bool isOperationValueModifying (MatrixOp op)
+{
+	return  op == OP_PRE_INCREMENT		 ||
+			op == OP_PRE_DECREMENT		 ||
+			op == OP_POST_INCREMENT		 ||
+			op == OP_POST_DECREMENT;
+}
+
+static bool isOperationAssignment (MatrixOp op)
+{
+	return  op == OP_ADD_INTO		 ||
+			op == OP_SUBTRACT_FROM	 ||
+			op == OP_MULTIPLY_INTO	 ||
+			op == OP_DIVIDE_INTO;
+}
+
+static bool isOperationAssignmentAnyMatrix (MatrixOp op)
+{
+	return  op == OP_ADD_INTO		 ||
+			op == OP_SUBTRACT_FROM	 ||
+			op == OP_DIVIDE_INTO;
+}
+
+static bool isOperationAssignmentSymmetricMatrix (MatrixOp op)
+{
+	return op == OP_MULTIPLY_INTO;
+}
+
+// Operation nature
+
+enum OperationNature
+{
+	OPERATIONNATURE_PURE = 0,
+	OPERATIONNATURE_MUTATING,
+	OPERATIONNATURE_ASSIGNMENT,
+
+	OPERATIONNATURE_LAST
+};
+
+static OperationNature getOperationNature (MatrixOp op)
+{
+	if (isOperationAssignment(op))
+		return OPERATIONNATURE_ASSIGNMENT;
+
+	if (isOperationValueModifying(op))
+		return OPERATIONNATURE_MUTATING;
+
+	return OPERATIONNATURE_PURE;
+}
+
+// Input value loader.
+
+template <int InputT, int DataT>
+typename TypeTraits<DataT>::Type getInputValue (const ShaderEvalContext& evalCtx, int inputNdx);
+
+template <> inline float		getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT>			(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInFloat[inputNdx];	}
+template <> inline tcu::Vec2	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_VEC2>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInVec2[inputNdx];	}
+template <> inline tcu::Vec3	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_VEC3>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInVec3[inputNdx];	}
+template <> inline tcu::Vec4	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_VEC4>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return s_constInVec4[inputNdx];	}
+
+template <> inline tcu::Mat2	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT2>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return tcu::Mat2(s_constInMat2x2[inputNdx]);		}
+template <> inline tcu::Mat2x3	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT2X3>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return tcu::Mat2x3(s_constInMat2x3[inputNdx]);	}
+template <> inline tcu::Mat2x4	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT2X4>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return tcu::Mat2x4(s_constInMat2x4[inputNdx]);	}
+template <> inline tcu::Mat3x2	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT3X2>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return tcu::Mat3x2(s_constInMat3x2[inputNdx]);	}
+template <> inline tcu::Mat3	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT3>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return tcu::Mat3(s_constInMat3x3[inputNdx]);		}
+template <> inline tcu::Mat3x4	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT3X4>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return tcu::Mat3x4(s_constInMat3x4[inputNdx]);	}
+template <> inline tcu::Mat4x2	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT4X2>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return tcu::Mat4x2(s_constInMat4x2[inputNdx]);	}
+template <> inline tcu::Mat4x3	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT4X3>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return tcu::Mat4x3(s_constInMat4x3[inputNdx]);	}
+template <> inline tcu::Mat4	getInputValue<INPUTTYPE_CONST,		TYPE_FLOAT_MAT4>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(evalCtx); return tcu::Mat4(s_constInMat4x4[inputNdx]);		}
+
+template <> inline float		getInputValue<INPUTTYPE_DYNAMIC,	TYPE_FLOAT>			(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(inputNdx); return evalCtx.coords.x();					}
+template <> inline tcu::Vec2	getInputValue<INPUTTYPE_DYNAMIC,	TYPE_FLOAT_VEC2>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(inputNdx); return evalCtx.coords.swizzle(0, 1);			}
+template <> inline tcu::Vec3	getInputValue<INPUTTYPE_DYNAMIC,	TYPE_FLOAT_VEC3>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(inputNdx); return evalCtx.coords.swizzle(0, 1, 2);		}
+template <> inline tcu::Vec4	getInputValue<INPUTTYPE_DYNAMIC,	TYPE_FLOAT_VEC4>	(const ShaderEvalContext& evalCtx, int inputNdx) { DE_UNREF(inputNdx); return evalCtx.coords.swizzle(0, 1, 2, 3);	}
+
+template <> inline tcu::Mat2 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT2> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat2 m;
+	m.setColumn(0, evalCtx.in[0].swizzle(0,1));
+	m.setColumn(1, evalCtx.in[1].swizzle(0,1));
+	return m;
+}
+
+template <> inline tcu::Mat2x3 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT2X3> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat2x3 m;
+	m.setColumn(0, evalCtx.in[0].swizzle(0,1,2));
+	m.setColumn(1, evalCtx.in[1].swizzle(0,1,2));
+	return m;
+}
+
+template <> inline tcu::Mat2x4 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT2X4> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat2x4 m;
+	m.setColumn(0, evalCtx.in[0]);
+	m.setColumn(1, evalCtx.in[1]);
+	return m;
+}
+
+template <> inline tcu::Mat3x2 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT3X2> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat3x2 m;
+	m.setColumn(0, evalCtx.in[0].swizzle(0,1));
+	m.setColumn(1, evalCtx.in[1].swizzle(0,1));
+	m.setColumn(2, evalCtx.in[2].swizzle(0,1));
+	return m;
+}
+
+template <> inline tcu::Mat3 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT3> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat3 m;
+	m.setColumn(0, evalCtx.in[0].swizzle(0,1,2));
+	m.setColumn(1, evalCtx.in[1].swizzle(0,1,2));
+	m.setColumn(2, evalCtx.in[2].swizzle(0,1,2));
+	return m;
+}
+
+template <> inline tcu::Mat3x4 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT3X4> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat3x4 m;
+	m.setColumn(0, evalCtx.in[0]);
+	m.setColumn(1, evalCtx.in[1]);
+	m.setColumn(2, evalCtx.in[2]);
+	return m;
+}
+
+template <> inline tcu::Mat4x2 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT4X2> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat4x2 m;
+	m.setColumn(0, evalCtx.in[0].swizzle(0,1));
+	m.setColumn(1, evalCtx.in[1].swizzle(0,1));
+	m.setColumn(2, evalCtx.in[2].swizzle(0,1));
+	m.setColumn(3, evalCtx.in[3].swizzle(0,1));
+	return m;
+}
+
+template <> inline tcu::Mat4x3 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT4X3> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat4x3 m;
+	m.setColumn(0, evalCtx.in[0].swizzle(0,1,2));
+	m.setColumn(1, evalCtx.in[1].swizzle(0,1,2));
+	m.setColumn(2, evalCtx.in[2].swizzle(0,1,2));
+	m.setColumn(3, evalCtx.in[3].swizzle(0,1,2));
+	return m;
+}
+
+template <> inline tcu::Mat4 getInputValue<INPUTTYPE_DYNAMIC, TYPE_FLOAT_MAT4> (const ShaderEvalContext& evalCtx, int inputNdx)
+{
+	DE_UNREF(inputNdx); // Not used.
+	tcu::Mat4 m;
+	m.setColumn(0, evalCtx.in[0]);
+	m.setColumn(1, evalCtx.in[1]);
+	m.setColumn(2, evalCtx.in[2]);
+	m.setColumn(3, evalCtx.in[3]);
+	return m;
+}
+
+// Reduction from expression result to vec3.
+
+inline tcu::Vec3 reduceToVec3 (const tcu::Vec2& value)		{ return value.swizzle(0,1,0); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Vec3& value)		{ return value; }
+inline tcu::Vec3 reduceToVec3 (const tcu::Vec4& value)		{ return tcu::Vec3(value.x(), value.y(), value.z()+value.w()); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat2& value)		{ return tcu::Vec3(value(0, 0), value(0, 1), value(1, 0)+value(1, 1)); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat2x3& value)	{ return value.getColumn(0) + value.getColumn(1); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat2x4& value)	{ return value.getColumn(0).swizzle(0,1,2) + value.getColumn(1).swizzle(1,2,3); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat3x2& value)	{ return tcu::Vec3(value(0,0)+value(1,0), value(0,1)+value(1,1), value(0,2)+value(1,2)); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat3& value)		{ return value.getColumn(0) + value.getColumn(1) + value.getColumn(2); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat3x4& value)	{ return value.getColumn(0).swizzle(0,1,2) + value.getColumn(1).swizzle(1,2,3) + value.getColumn(2).swizzle(2,3,0); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat4x2& value)	{ return tcu::Vec3(value(0,0)+value(1,0)+value(0,3), value(0,1)+value(1,1)+value(1,3), value(0,2)+value(1,2)); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat4x3& value)	{ return value.getColumn(0) + value.getColumn(1) + value.getColumn(2) + value.getColumn(3); }
+inline tcu::Vec3 reduceToVec3 (const tcu::Mat4& value)		{ return value.getColumn(0).swizzle(0,1,2) + value.getColumn(1).swizzle(1,2,3) + value.getColumn(2).swizzle(2,3,0) + value.getColumn(3).swizzle(3,0,1); }
+
+// matrixCompMult
+
+template <typename T, int Rows, int Cols>
+tcu::Matrix<T, Rows, Cols> matrixCompMult (const tcu::Matrix<T, Rows, Cols>& a, const tcu::Matrix<T, Rows, Cols>& b)
+{
+	tcu::Matrix<T, Rows, Cols> retVal;
+
+	for (int r = 0; r < Rows; ++r)
+		for (int c = 0; c < Cols; ++c)
+			retVal(r,c) = a(r,c) * b(r, c);
+
+	return retVal;
+}
+
+// transpose
+
+template <typename T, int Rows, int Cols>
+tcu::Matrix<T, Cols, Rows> transpose (const tcu::Matrix<T, Rows, Cols>& mat)
+{
+	tcu::Matrix<T, Cols, Rows> retVal;
+
+	for (int r = 0; r < Rows; ++r)
+		for (int c = 0; c < Cols; ++c)
+			retVal(c, r) = mat(r, c);
+
+	return retVal;
+}
+
+// outerProduct
+
+template <typename T, int Rows, int Cols>
+tcu::Matrix<T, Cols, Rows> outerProduct (const tcu::Vector<T, Cols>& a, const tcu::Vector<T, Rows>& b)
+{
+	tcu::Matrix<T, Rows, Cols> retVal;
+
+	for (int r = 0; r < Rows; ++r)
+		for (int c = 0; c < Cols; ++c)
+			retVal(r,c) = a[c] * b[r];
+
+	return transpose(retVal); // to gl-form (column-major)
+}
+
+// Determinant
+
+template <int Size>
+float determinant (const tcu::Matrix<float, Size, Size>& mat);
+
+template <>
+float determinant<2> (const tcu::Matrix<float, 2, 2>& mat)
+{
+	return mat(0,0) * mat(1,1) - mat(1,0) * mat(0,1);
+}
+
+template <>
+float determinant<3> (const tcu::Matrix<float, 3, 3>& mat)
+{
+	return	+ mat(0,0) * mat(1,1) * mat(2,2)
+			+ mat(0,1) * mat(1,2) * mat(2,0)
+			+ mat(0,2) * mat(1,0) * mat(2,1)
+			- mat(0,0) * mat(1,2) * mat(2,1)
+			- mat(0,1) * mat(1,0) * mat(2,2)
+			- mat(0,2) * mat(1,1) * mat(2,0);
+}
+
+template <>
+float determinant<4> (const tcu::Matrix<float, 4, 4>& mat)
+{
+	const float minorMatrices[4][3*3] =
+	{
+		{
+			mat(1,1),	mat(2,1),	mat(3,1),
+			mat(1,2),	mat(2,2),	mat(3,2),
+			mat(1,3),	mat(2,3),	mat(3,3),
+		},
+		{
+			mat(1,0),	mat(2,0),	mat(3,0),
+			mat(1,2),	mat(2,2),	mat(3,2),
+			mat(1,3),	mat(2,3),	mat(3,3),
+		},
+		{
+			mat(1,0),	mat(2,0),	mat(3,0),
+			mat(1,1),	mat(2,1),	mat(3,1),
+			mat(1,3),	mat(2,3),	mat(3,3),
+		},
+		{
+			mat(1,0),	mat(2,0),	mat(3,0),
+			mat(1,1),	mat(2,1),	mat(3,1),
+			mat(1,2),	mat(2,2),	mat(3,2),
+		}
+	};
+
+	return	+ mat(0,0) * determinant(tcu::Mat3(minorMatrices[0]))
+			- mat(0,1) * determinant(tcu::Mat3(minorMatrices[1]))
+			+ mat(0,2) * determinant(tcu::Mat3(minorMatrices[2]))
+			- mat(0,3) * determinant(tcu::Mat3(minorMatrices[3]));
+}
+
+// Inverse
+
+template <int Size>
+tcu::Matrix<float, Size, Size> inverse (const tcu::Matrix<float, Size, Size>& mat);
+
+template <>
+tcu::Matrix<float, 2, 2> inverse<2> (const tcu::Matrix<float, 2, 2>& mat)
+{
+	const float					det		= determinant(mat);
+	tcu::Matrix<float, 2, 2>	retVal;
+
+	DE_ASSERT(det != 0.0f);
+
+	retVal(0, 0) =  mat(1, 1) / det;
+	retVal(0, 1) = -mat(0, 1) / det;
+	retVal(1, 0) = -mat(1, 0) / det;
+	retVal(1, 1) =  mat(0, 0) / det;
+
+	return retVal;
+}
+
+template <>
+tcu::Matrix<float, 3, 3> inverse<3> (const tcu::Matrix<float, 3, 3>& mat)
+{
+	// Blockwise inversion
+
+	DE_ASSERT(determinant(mat) != 0.0f);
+
+	const float areaA[2*2] =
+	{
+		mat(0,0),	mat(0,1),
+		mat(1,0),	mat(1,1)
+	};
+	const float areaB[2] =
+	{
+		mat(0,2),
+		mat(1,2),
+	};
+	const float areaC[2] =
+	{
+		mat(2,0),	mat(2,1),
+	};
+	const float areaD[1] =
+	{
+		mat(2,2)
+	};
+	const float nullField[4] = { 0.0f };
+
+	const tcu::Matrix<float, 2, 2>	invA = inverse(tcu::Matrix<float, 2, 2>(areaA));
+	const tcu::Matrix<float, 2, 1>	matB =         tcu::Matrix<float, 2, 1>(areaB);
+	const tcu::Matrix<float, 1, 2>	matC =         tcu::Matrix<float, 1, 2>(areaC);
+	const tcu::Matrix<float, 1, 1>	matD =         tcu::Matrix<float, 1, 1>(areaD);
+
+	const float						schurComplement = 1.0f / (matD - matC*invA*matB)(0,0);
+	const tcu::Matrix<float, 2, 2>	zeroMat         = Mat2(nullField);
+
+	const tcu::Matrix<float, 2, 2>	blockA = invA + invA*matB*schurComplement*matC*invA;
+	const tcu::Matrix<float, 2, 1>	blockB = (zeroMat-invA)*matB*schurComplement;
+	const tcu::Matrix<float, 1, 2>	blockC = matC*invA*(-schurComplement);
+	const float						blockD = schurComplement;
+
+	const float result[3*3] =
+	{
+		blockA(0,0),	blockA(0,1),	blockB(0,0),
+		blockA(1,0),	blockA(1,1),	blockB(1,0),
+		blockC(0,0),	blockC(0,1),	blockD,
+	};
+
+	return Mat3(result);
+}
+
+template <>
+tcu::Matrix<float, 4, 4> inverse<4> (const tcu::Matrix<float, 4, 4>& mat)
+{
+	// Blockwise inversion
+
+	DE_ASSERT(determinant(mat) != 0.0f);
+
+	const float areaA[2*2] =
+	{
+		mat(0,0),	mat(0,1),
+		mat(1,0),	mat(1,1)
+	};
+	const float areaB[2*2] =
+	{
+		mat(0,2),	mat(0,3),
+		mat(1,2),	mat(1,3)
+	};
+	const float areaC[2*2] =
+	{
+		mat(2,0),	mat(2,1),
+		mat(3,0),	mat(3,1)
+	};
+	const float areaD[2*2] =
+	{
+		mat(2,2),	mat(2,3),
+		mat(3,2),	mat(3,3)
+	};
+	const float nullField[4] = { 0.0f };
+
+	const tcu::Matrix<float, 2, 2> invA = inverse(Mat2(areaA));
+	const tcu::Matrix<float, 2, 2> matB =         Mat2(areaB);
+	const tcu::Matrix<float, 2, 2> matC =         Mat2(areaC);
+	const tcu::Matrix<float, 2, 2> matD =         Mat2(areaD);
+
+	const tcu::Matrix<float, 2, 2> schurComplement = inverse(matD - matC*invA*matB);
+	const tcu::Matrix<float, 2, 2> zeroMat         = Mat2(nullField);
+
+	const tcu::Matrix<float, 2, 2> blockA = invA + invA*matB*schurComplement*matC*invA;
+	const tcu::Matrix<float, 2, 2> blockB = (zeroMat-invA)*matB*schurComplement;
+	const tcu::Matrix<float, 2, 2> blockC = (zeroMat-schurComplement)*matC*invA;
+	const tcu::Matrix<float, 2, 2> blockD = schurComplement;
+
+	const float result[4*4] =
+	{
+		blockA(0,0),	blockA(0,1),	blockB(0,0),	blockB(0,1),
+		blockA(1,0),	blockA(1,1),	blockB(1,0),	blockB(1,1),
+		blockC(0,0),	blockC(0,1),	blockD(0,0),	blockD(0,1),
+		blockC(1,0),	blockC(1,1),	blockD(1,0),	blockD(1,1),
+	};
+
+	return Mat4(result);
+}
+
+// negate
+
+template <typename T, int Rows, int Cols>
+tcu::Matrix<T, Rows, Cols> negate (const tcu::Matrix<T, Rows, Cols>& mat)
+{
+	tcu::Matrix<T, Rows, Cols> retVal;
+
+	for (int r = 0; r < Rows; ++r)
+		for (int c = 0; c < Cols; ++c)
+			retVal(r,c) = -mat(r, c);
+
+	return retVal;
+}
+
+// increment/decrement
+
+template <typename T, int Rows, int Cols>
+tcu::Matrix<T, Rows, Cols> increment (const tcu::Matrix<T, Rows, Cols>& mat)
+{
+	tcu::Matrix<T, Rows, Cols> retVal;
+
+	for (int r = 0; r < Rows; ++r)
+		for (int c = 0; c < Cols; ++c)
+			retVal(r,c) = mat(r, c) + 1.0f;
+
+	return retVal;
+}
+
+template <typename T, int Rows, int Cols>
+tcu::Matrix<T, Rows, Cols> decrement (const tcu::Matrix<T, Rows, Cols>& mat)
+{
+	tcu::Matrix<T, Rows, Cols> retVal;
+
+	for (int r = 0; r < Rows; ++r)
+		for (int c = 0; c < Cols; ++c)
+			retVal(r,c) = mat(r, c) - 1.0f;
+
+	return retVal;
+}
+
+// Evaluator template.
+
+typedef void (*MatrixShaderEvalFunc) (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type);
+
+template <int Op, int In0DataType, int In1DataType>
+struct Evaluator;
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_ADD, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		typename TypeTraits<In1DataType>::Type	in1	= (in1Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In1DataType>(evalCtx, 1)
+																				     : getInputValue<INPUTTYPE_CONST,	In1DataType>(evalCtx, 1);
+		evalCtx.color.xyz() = reduceToVec3(in0 + in1);
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_SUB, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		typename TypeTraits<In1DataType>::Type	in1	= (in1Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In1DataType>(evalCtx, 1)
+																				     : getInputValue<INPUTTYPE_CONST,	In1DataType>(evalCtx, 1);
+		evalCtx.color.xyz() = reduceToVec3(in0 - in1);
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_MUL, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		typename TypeTraits<In1DataType>::Type	in1	= (in1Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In1DataType>(evalCtx, 1)
+																				     : getInputValue<INPUTTYPE_CONST,	In1DataType>(evalCtx, 1);
+		evalCtx.color.xyz() = reduceToVec3(in0 * in1);
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_DIV, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		typename TypeTraits<In1DataType>::Type	in1	= (in1Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In1DataType>(evalCtx, 1)
+																				     : getInputValue<INPUTTYPE_CONST,	In1DataType>(evalCtx, 1);
+		evalCtx.color.xyz() = reduceToVec3(in0 / in1);
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_COMP_MUL, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		typename TypeTraits<In1DataType>::Type	in1	= (in1Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In1DataType>(evalCtx, 1)
+																				     : getInputValue<INPUTTYPE_CONST,	In1DataType>(evalCtx, 1);
+		evalCtx.color.xyz() = reduceToVec3(matrixCompMult(in0, in1));
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_OUTER_PRODUCT, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		typename TypeTraits<In1DataType>::Type	in1	= (in1Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In1DataType>(evalCtx, 1)
+																				     : getInputValue<INPUTTYPE_CONST,	In1DataType>(evalCtx, 1);
+		evalCtx.color.xyz() = reduceToVec3(outerProduct(in0, in1));
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_TRANSPOSE, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		DE_UNREF(in1Type);
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		evalCtx.color.xyz() = reduceToVec3(transpose(in0));
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_INVERSE, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		DE_UNREF(in1Type);
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		evalCtx.color.xyz() = reduceToVec3(inverse(in0));
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_DETERMINANT, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		DE_UNREF(in1Type);
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		evalCtx.color.xyz() = Vec3(determinant(in0));
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_UNARY_PLUS, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		DE_UNREF(in1Type);
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		evalCtx.color.xyz() = reduceToVec3(in0);
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_NEGATION, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		DE_UNREF(in1Type);
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		evalCtx.color.xyz() = reduceToVec3(negate(in0));
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_PRE_INCREMENT, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		DE_UNREF(in1Type);
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+
+		// modifying reduction: sum modified value too
+		evalCtx.color.xyz() = reduceToVec3(increment(in0)) + reduceToVec3(increment(in0));
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_PRE_DECREMENT, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		DE_UNREF(in1Type);
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+
+		// modifying reduction: sum modified value too
+		evalCtx.color.xyz() = reduceToVec3(decrement(in0)) + reduceToVec3(decrement(in0));
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_POST_INCREMENT, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		DE_UNREF(in1Type);
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+
+		// modifying reduction: sum modified value too
+		evalCtx.color.xyz() = reduceToVec3(in0) + reduceToVec3(increment(in0));
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_POST_DECREMENT, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		DE_UNREF(in1Type);
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+
+		// modifying reduction: sum modified value too
+		evalCtx.color.xyz() = reduceToVec3(in0) + reduceToVec3(decrement(in0));
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_ADD_INTO, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		typename TypeTraits<In1DataType>::Type	in1	= (in1Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In1DataType>(evalCtx, 1)
+																				     : getInputValue<INPUTTYPE_CONST,	In1DataType>(evalCtx, 1);
+		evalCtx.color.xyz() = reduceToVec3(in0 + in1);
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_SUBTRACT_FROM, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		typename TypeTraits<In1DataType>::Type	in1	= (in1Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In1DataType>(evalCtx, 1)
+																				     : getInputValue<INPUTTYPE_CONST,	In1DataType>(evalCtx, 1);
+		evalCtx.color.xyz() = reduceToVec3(in0 - in1);
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_MULTIPLY_INTO, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		typename TypeTraits<In1DataType>::Type	in1	= (in1Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In1DataType>(evalCtx, 1)
+																				     : getInputValue<INPUTTYPE_CONST,	In1DataType>(evalCtx, 1);
+		evalCtx.color.xyz() = reduceToVec3(in0 * in1);
+	}
+};
+
+template <int In0DataType, int In1DataType>
+struct Evaluator<OP_DIVIDE_INTO, In0DataType, In1DataType>
+{
+	static void evaluate (ShaderEvalContext& evalCtx, InputType in0Type, InputType in1Type)
+	{
+		typename TypeTraits<In0DataType>::Type	in0	= (in0Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In0DataType>(evalCtx, 0)
+																				     : getInputValue<INPUTTYPE_CONST,	In0DataType>(evalCtx, 0);
+		typename TypeTraits<In1DataType>::Type	in1	= (in1Type == INPUTTYPE_DYNAMIC) ? getInputValue<INPUTTYPE_DYNAMIC, In1DataType>(evalCtx, 1)
+																				     : getInputValue<INPUTTYPE_CONST,	In1DataType>(evalCtx, 1);
+		evalCtx.color.xyz() = reduceToVec3(in0 / in1);
+	}
+};
+
+MatrixShaderEvalFunc getEvalFunc (const ShaderInput& in0, const ShaderInput& in1, MatrixOp op)
+{
+	// Evaluator is selected based on op and input data types.
+	// For efficient lookup the types and op enums are packed together to form a 19-bit key:
+	// [18..14 OP] [13..7 TYPE0] [6..0 TYPE1]
+
+	DE_STATIC_ASSERT(TYPE_LAST	<= (1<<7));
+	DE_STATIC_ASSERT(OP_LAST	<= (1<<5));
+
+#define PACK_EVAL_CASE(OP, IN0DATATYPE, IN1DATATYPE)	(((OP) << 14) | ((IN0DATATYPE) << 7) | (IN1DATATYPE))
+
+#define MAKE_EVAL_CASE(OP, IN0DATATYPE, IN1DATATYPE)	\
+	case PACK_EVAL_CASE(OP, IN0DATATYPE, IN1DATATYPE):	\
+		return Evaluator<OP, IN0DATATYPE, IN1DATATYPE>::evaluate
+
+#define MAKE_SCALAR_OPS(IN0DATATYPE, IN1DATATYPE)		\
+	MAKE_EVAL_CASE(OP_ADD, IN0DATATYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_SUB, IN0DATATYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_MUL, IN0DATATYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_DIV, IN0DATATYPE, IN1DATATYPE)
+
+#define MAKE_CWISE_OPS(IN0DATATYPE, IN1DATATYPE)			\
+	MAKE_EVAL_CASE(OP_ADD,		IN0DATATYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_SUB,		IN0DATATYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_DIV,		IN0DATATYPE, IN1DATATYPE);	\
+	MAKE_EVAL_CASE(OP_COMP_MUL,	IN0DATATYPE, IN1DATATYPE)
+
+#define MAKE_MUL_OP(IN0DATATYPE, IN1DATATYPE)			\
+	MAKE_EVAL_CASE(OP_MUL, IN0DATATYPE, IN1DATATYPE)
+
+#define MAKE_VECVEC_OP(IN0DATATYPE, IN1DATATYPE)			\
+	MAKE_EVAL_CASE(OP_OUTER_PRODUCT, IN0DATATYPE, IN1DATATYPE)
+
+#define MAKE_UNARY_OP(IN0DATATYPE)								\
+	MAKE_EVAL_CASE(OP_TRANSPOSE,		IN0DATATYPE, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_UNARY_PLUS,		IN0DATATYPE, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_NEGATION,			IN0DATATYPE, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_PRE_INCREMENT,	IN0DATATYPE, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_PRE_DECREMENT,	IN0DATATYPE, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_POST_INCREMENT,	IN0DATATYPE, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_POST_DECREMENT,	IN0DATATYPE, TYPE_LAST)
+
+#define MAKE_UNARY_SYMMETRIC_OP(IN0DATATYPE)					\
+	MAKE_UNARY_OP(IN0DATATYPE);									\
+	MAKE_EVAL_CASE(OP_DETERMINANT,	IN0DATATYPE, TYPE_LAST);	\
+	MAKE_EVAL_CASE(OP_INVERSE,		IN0DATATYPE, TYPE_LAST)
+
+#define MAKE_ASSIGNMENT_OP(IN0DATATYPE)								\
+	MAKE_EVAL_CASE(OP_ADD_INTO,			IN0DATATYPE, IN0DATATYPE);	\
+	MAKE_EVAL_CASE(OP_SUBTRACT_FROM,	IN0DATATYPE, IN0DATATYPE);	\
+	MAKE_EVAL_CASE(OP_DIVIDE_INTO,		IN0DATATYPE, IN0DATATYPE)
+
+#define MAKE_ASSIGNMENT_SYMMETRIC_OP(IN0DATATYPE)					\
+	MAKE_ASSIGNMENT_OP(IN0DATATYPE);								\
+	MAKE_EVAL_CASE(OP_MULTIPLY_INTO,	IN0DATATYPE, IN0DATATYPE)
+
+	switch (PACK_EVAL_CASE(op, in0.dataType, in1.dataType))
+	{
+		// Matrix-scalar.
+		MAKE_SCALAR_OPS(TYPE_FLOAT_MAT2,	TYPE_FLOAT);
+		MAKE_SCALAR_OPS(TYPE_FLOAT_MAT2X3,	TYPE_FLOAT);
+		MAKE_SCALAR_OPS(TYPE_FLOAT_MAT2X4,	TYPE_FLOAT);
+		MAKE_SCALAR_OPS(TYPE_FLOAT_MAT3X2,	TYPE_FLOAT);
+		MAKE_SCALAR_OPS(TYPE_FLOAT_MAT3,	TYPE_FLOAT);
+		MAKE_SCALAR_OPS(TYPE_FLOAT_MAT3X4,	TYPE_FLOAT);
+		MAKE_SCALAR_OPS(TYPE_FLOAT_MAT4X2,	TYPE_FLOAT);
+		MAKE_SCALAR_OPS(TYPE_FLOAT_MAT4X3,	TYPE_FLOAT);
+		MAKE_SCALAR_OPS(TYPE_FLOAT_MAT4,	TYPE_FLOAT);
+
+		// Matrix-vector.
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2,	TYPE_FLOAT_VEC2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2X3,	TYPE_FLOAT_VEC2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2X4,	TYPE_FLOAT_VEC2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3X2,	TYPE_FLOAT_VEC3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3,	TYPE_FLOAT_VEC3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3X4,	TYPE_FLOAT_VEC3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4X2,	TYPE_FLOAT_VEC4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4X3,	TYPE_FLOAT_VEC4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4,	TYPE_FLOAT_VEC4);
+
+		// Vector-matrix.
+		MAKE_MUL_OP(TYPE_FLOAT_VEC2, TYPE_FLOAT_MAT2);
+		MAKE_MUL_OP(TYPE_FLOAT_VEC3, TYPE_FLOAT_MAT2X3);
+		MAKE_MUL_OP(TYPE_FLOAT_VEC4, TYPE_FLOAT_MAT2X4);
+		MAKE_MUL_OP(TYPE_FLOAT_VEC2, TYPE_FLOAT_MAT3X2);
+		MAKE_MUL_OP(TYPE_FLOAT_VEC3, TYPE_FLOAT_MAT3);
+		MAKE_MUL_OP(TYPE_FLOAT_VEC4, TYPE_FLOAT_MAT3X4);
+		MAKE_MUL_OP(TYPE_FLOAT_VEC2, TYPE_FLOAT_MAT4X2);
+		MAKE_MUL_OP(TYPE_FLOAT_VEC3, TYPE_FLOAT_MAT4X3);
+		MAKE_MUL_OP(TYPE_FLOAT_VEC4, TYPE_FLOAT_MAT4);
+
+		// Matrix-matrix.
+		MAKE_CWISE_OPS(TYPE_FLOAT_MAT2,		TYPE_FLOAT_MAT2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2,		TYPE_FLOAT_MAT2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2,		TYPE_FLOAT_MAT3X2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2,		TYPE_FLOAT_MAT4X2);
+
+		MAKE_CWISE_OPS(TYPE_FLOAT_MAT2X3,	TYPE_FLOAT_MAT2X3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2X3,		TYPE_FLOAT_MAT2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2X3,		TYPE_FLOAT_MAT3X2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2X3,		TYPE_FLOAT_MAT4X2);
+
+		MAKE_CWISE_OPS(TYPE_FLOAT_MAT2X4,	TYPE_FLOAT_MAT2X4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2X4,		TYPE_FLOAT_MAT2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2X4,		TYPE_FLOAT_MAT3X2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT2X4,		TYPE_FLOAT_MAT4X2);
+
+		MAKE_CWISE_OPS(TYPE_FLOAT_MAT3X2,	TYPE_FLOAT_MAT3X2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3X2,		TYPE_FLOAT_MAT2X3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3X2,		TYPE_FLOAT_MAT3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3X2,		TYPE_FLOAT_MAT4X3);
+
+		MAKE_CWISE_OPS(TYPE_FLOAT_MAT3,		TYPE_FLOAT_MAT3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3,		TYPE_FLOAT_MAT2X3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3,		TYPE_FLOAT_MAT3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3,		TYPE_FLOAT_MAT4X3);
+
+		MAKE_CWISE_OPS(TYPE_FLOAT_MAT3X4,	TYPE_FLOAT_MAT3X4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3X4,		TYPE_FLOAT_MAT2X3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3X4,		TYPE_FLOAT_MAT3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT3X4,		TYPE_FLOAT_MAT4X3);
+
+		MAKE_CWISE_OPS(TYPE_FLOAT_MAT4X2,	TYPE_FLOAT_MAT4X2);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4X2,		TYPE_FLOAT_MAT2X4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4X2,		TYPE_FLOAT_MAT3X4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4X2,		TYPE_FLOAT_MAT4);
+
+		MAKE_CWISE_OPS(TYPE_FLOAT_MAT4X3,	TYPE_FLOAT_MAT4X3);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4X3,		TYPE_FLOAT_MAT2X4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4X3,		TYPE_FLOAT_MAT3X4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4X3,		TYPE_FLOAT_MAT4);
+
+		MAKE_CWISE_OPS(TYPE_FLOAT_MAT4,		TYPE_FLOAT_MAT4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4,		TYPE_FLOAT_MAT2X4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4,		TYPE_FLOAT_MAT3X4);
+		MAKE_MUL_OP(TYPE_FLOAT_MAT4,		TYPE_FLOAT_MAT4);
+
+		// Vector-vector.
+		MAKE_VECVEC_OP(TYPE_FLOAT_VEC2,		TYPE_FLOAT_VEC2);
+		MAKE_VECVEC_OP(TYPE_FLOAT_VEC2,		TYPE_FLOAT_VEC3);
+		MAKE_VECVEC_OP(TYPE_FLOAT_VEC2,		TYPE_FLOAT_VEC4);
+		MAKE_VECVEC_OP(TYPE_FLOAT_VEC3,		TYPE_FLOAT_VEC2);
+		MAKE_VECVEC_OP(TYPE_FLOAT_VEC3,		TYPE_FLOAT_VEC3);
+		MAKE_VECVEC_OP(TYPE_FLOAT_VEC3,		TYPE_FLOAT_VEC4);
+		MAKE_VECVEC_OP(TYPE_FLOAT_VEC4,		TYPE_FLOAT_VEC2);
+		MAKE_VECVEC_OP(TYPE_FLOAT_VEC4,		TYPE_FLOAT_VEC3);
+		MAKE_VECVEC_OP(TYPE_FLOAT_VEC4,		TYPE_FLOAT_VEC4);
+
+		// Unary Matrix.
+		MAKE_UNARY_SYMMETRIC_OP(TYPE_FLOAT_MAT2);
+		MAKE_UNARY_OP(TYPE_FLOAT_MAT2X3);
+		MAKE_UNARY_OP(TYPE_FLOAT_MAT2X4);
+		MAKE_UNARY_OP(TYPE_FLOAT_MAT3X2);
+		MAKE_UNARY_SYMMETRIC_OP(TYPE_FLOAT_MAT3);
+		MAKE_UNARY_OP(TYPE_FLOAT_MAT3X4);
+		MAKE_UNARY_OP(TYPE_FLOAT_MAT4X2);
+		MAKE_UNARY_OP(TYPE_FLOAT_MAT4X3);
+		MAKE_UNARY_SYMMETRIC_OP(TYPE_FLOAT_MAT4);
+
+		// Assignments
+		MAKE_ASSIGNMENT_SYMMETRIC_OP(TYPE_FLOAT_MAT2);
+		MAKE_ASSIGNMENT_OP(TYPE_FLOAT_MAT2X3);
+		MAKE_ASSIGNMENT_OP(TYPE_FLOAT_MAT2X4);
+		MAKE_ASSIGNMENT_OP(TYPE_FLOAT_MAT3X2);
+		MAKE_ASSIGNMENT_SYMMETRIC_OP(TYPE_FLOAT_MAT3);
+		MAKE_ASSIGNMENT_OP(TYPE_FLOAT_MAT3X4);
+		MAKE_ASSIGNMENT_OP(TYPE_FLOAT_MAT4X2);
+		MAKE_ASSIGNMENT_OP(TYPE_FLOAT_MAT4X3);
+		MAKE_ASSIGNMENT_SYMMETRIC_OP(TYPE_FLOAT_MAT4);
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+
+#undef PACK_EVAL_CASE
+#undef MAKE_EVAL_CASE
+#undef MUL_OP
+#undef ALL_OPS
+#undef MAKE_MAT_SCALAR_VEC_CASES
+#undef MAKE_MAT_MAT_CASES
+}
+
+// Shader source format utilities.
+
+template <int Size>
+void writeVectorConstructor (std::ostream& str, const tcu::Vector<float, Size>& v)
+{
+	str << "vec" << Size << "(";
+	for (int ndx = 0; ndx < Size; ndx++)
+	{
+		if (ndx != 0)
+			str << ", ";
+		str << de::floatToString(v[ndx], 1);
+	}
+	str << ")";
+}
+
+template <int Cols, int Rows>
+void writeMatrixConstructor (std::ostream& str, const tcu::Matrix<float, Rows, Cols>& m)
+{
+	if (Rows == Cols)
+		str << "mat" << Cols;
+	else
+		str << "mat" << Cols << "x" << Rows;
+
+	str << "(";
+	for (int colNdx = 0; colNdx < Cols; colNdx++)
+	{
+		for (int rowNdx = 0; rowNdx < Rows; rowNdx++)
+		{
+			if (rowNdx > 0 || colNdx > 0)
+				str << ", ";
+			str << de::floatToString(m(rowNdx, colNdx), 1);
+		}
+	}
+	str << ")";
+}
+
+} // MatrixCaseUtils
+
+using namespace MatrixCaseUtils;
+
+class MatrixShaderEvaluator : public ShaderEvaluator
+{
+public:
+							MatrixShaderEvaluator	(MatrixShaderEvalFunc evalFunc, InputType inType0, InputType inType1);
+
+	virtual void			evaluate				(ShaderEvalContext& evalCtx);
+
+private:
+	MatrixShaderEvalFunc	m_matEvalFunc;
+	InputType				m_inType0;
+	InputType				m_inType1;
+};
+
+MatrixShaderEvaluator::MatrixShaderEvaluator (MatrixShaderEvalFunc evalFunc, InputType inType0, InputType inType1)
+	: m_matEvalFunc	(evalFunc)
+	, m_inType0		(inType0)
+	, m_inType1		(inType1)
+{
+}
+
+void MatrixShaderEvaluator::evaluate (ShaderEvalContext& evalCtx)
+{
+	m_matEvalFunc(evalCtx, m_inType0, m_inType1);
+}
+
+class ShaderMatrixCase : public ShaderRenderCase
+{
+public:
+							ShaderMatrixCase			(Context& context, const char* name, const char* desc, const ShaderInput& in0, const ShaderInput& in1, MatrixOp op, bool isVertexCase);
+							~ShaderMatrixCase			(void);
+
+	void					init						(void);
+
+protected:
+	std::string				genGLSLMatToVec3Reduction	(const glu::DataType& matType, const char* varName);
+	void					setupUniforms				(int programID, const tcu::Vec4& constCoords);
+
+private:
+	ShaderInput				m_in0;
+	ShaderInput				m_in1;
+	MatrixOp				m_op;
+	MatrixShaderEvaluator	m_matEvaluator;
+};
+
+ShaderMatrixCase::ShaderMatrixCase (Context& context, const char* name, const char* desc, const ShaderInput& in0, const ShaderInput& in1, MatrixOp op, bool isVertexCase)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, desc, isVertexCase, m_matEvaluator)
+	, m_in0				(in0)
+	, m_in1				(in1)
+	, m_op				(op)
+	, m_matEvaluator	(getEvalFunc(in0, in1, op), in0.inputType, in1.inputType)
+{
+}
+
+ShaderMatrixCase::~ShaderMatrixCase (void)
+{
+}
+
+void ShaderMatrixCase::init (void)
+{
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op				= m_isVertexCase ? vtx : frag;
+
+	bool				isInDynMat0		= isDataTypeMatrix(m_in0.dataType) && m_in0.inputType == INPUTTYPE_DYNAMIC;
+	bool				isInDynMat1		= isDataTypeMatrix(m_in1.dataType) && m_in1.inputType == INPUTTYPE_DYNAMIC;
+	string				inValue0;
+	string				inValue1;
+	DataType			resultType		= TYPE_LAST;
+	Precision			resultPrec		= m_in0.precision;
+	vector<string>		passVars;
+	int					numInputs		= (isOperationBinary(m_op)) ? (2) : (1);
+
+	std::string			operationValue0;
+	std::string			operationValue1;
+
+	DE_ASSERT(!isInDynMat0 || !isInDynMat1); // Only single dynamic matrix input is allowed.
+	DE_UNREF(isInDynMat0 && isInDynMat1);
+
+	// Compute result type.
+	if (m_op == OP_MUL && isDataTypeMatrix(m_in0.dataType) && isDataTypeMatrix(m_in1.dataType))
+	{
+		resultType = getDataTypeMatrix(getDataTypeMatrixNumColumns(m_in1.dataType), getDataTypeMatrixNumRows(m_in0.dataType));
+	}
+	else if (m_op == OP_OUTER_PRODUCT)
+	{
+		resultType = getDataTypeMatrix(getDataTypeScalarSize(m_in1.dataType), getDataTypeScalarSize(m_in0.dataType));
+	}
+	else if (m_op == OP_TRANSPOSE)
+	{
+		resultType = getDataTypeMatrix(getDataTypeMatrixNumRows(m_in0.dataType), getDataTypeMatrixNumColumns(m_in0.dataType));
+	}
+	else if (m_op == OP_INVERSE)
+	{
+		resultType = m_in0.dataType;
+	}
+	else if (m_op == OP_DETERMINANT)
+	{
+		resultType = TYPE_FLOAT;
+	}
+	else if (getOperationType(m_op) == OPERATIONTYPE_UNARY_PREFIX_OPERATOR ||
+			 getOperationType(m_op) == OPERATIONTYPE_UNARY_POSTFIX_OPERATOR)
+	{
+		resultType = m_in0.dataType;
+	}
+	else if (isDataTypeMatrix(m_in0.dataType) && isDataTypeMatrix(m_in1.dataType))
+	{
+		DE_ASSERT(m_in0.dataType == m_in1.dataType);
+		resultType = m_in0.dataType;
+	}
+	else if (isDataTypeMatrix(m_in0.dataType) || isDataTypeMatrix(m_in1.dataType))
+	{
+		int			matNdx		= isDataTypeMatrix(m_in0.dataType) ? 0 : 1;
+		DataType	matrixType	= matNdx == 0 ? m_in0.dataType : m_in1.dataType;
+		DataType	otherType	= matNdx == 0 ? m_in1.dataType : m_in0.dataType;
+
+		if (otherType == TYPE_FLOAT)
+			resultType = matrixType;
+		else
+		{
+			DE_ASSERT(isDataTypeVector(otherType));
+			resultType = getDataTypeFloatVec(matNdx == 0 ? getDataTypeMatrixNumRows(matrixType) : getDataTypeMatrixNumColumns(matrixType));
+		}
+	}
+	else
+	{
+		DE_ASSERT(DE_FALSE);
+	}
+
+	vtx << "#version 300 es\n";
+	frag << "#version 300 es\n";
+
+	vtx << "in highp vec4 a_position;\n";
+	frag << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+	if (m_isVertexCase)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+
+	// Input declarations.
+	for (int inNdx = 0; inNdx < numInputs; inNdx++)
+	{
+		const ShaderInput&	in			= inNdx > 0 ? m_in1 : m_in0;
+		const char*			precName	= getPrecisionName(in.precision);
+		const char*			typeName	= getDataTypeName(in.dataType);
+		string&				inValue		= inNdx > 0 ? inValue1 : inValue0;
+
+		if (in.inputType == INPUTTYPE_DYNAMIC)
+		{
+			vtx << "in " << precName << " " << typeName << " a_";
+
+			if (isDataTypeMatrix(in.dataType))
+			{
+				// a_matN, v_matN
+				vtx << typeName << ";\n";
+				if (!m_isVertexCase)
+				{
+					vtx << "out " << precName << " " << typeName << " v_" << typeName << ";\n";
+					frag << "in " << precName << " " << typeName << " v_" << typeName << ";\n";
+					passVars.push_back(typeName);
+				}
+
+				inValue = string(m_isVertexCase ? "a_" : "v_") + getDataTypeName(in.dataType);
+			}
+			else
+			{
+				// a_coords, v_coords
+				vtx << "coords;\n";
+				if (!m_isVertexCase)
+				{
+					vtx << "out " << precName << " " << typeName << " v_coords;\n";
+					frag << "in " << precName << " " << typeName << " v_coords;\n";
+					passVars.push_back("coords");
+				}
+
+				inValue = m_isVertexCase ? "a_coords" : "v_coords";
+			}
+		}
+		else if (in.inputType == INPUTTYPE_UNIFORM)
+		{
+			op << "uniform " << precName << " " << typeName << " u_in" << inNdx << ";\n";
+			inValue = string("u_in") + de::toString(inNdx);
+		}
+		else if (in.inputType == INPUTTYPE_CONST)
+		{
+			op << "const " << precName << " " << typeName << " in" << inNdx << " = ";
+
+			// Generate declaration.
+			switch (in.dataType)
+			{
+				case TYPE_FLOAT:		op << de::floatToString(s_constInFloat[inNdx], 1);					break;
+				case TYPE_FLOAT_VEC2:	writeVectorConstructor<2>(op, s_constInVec2[inNdx]);				break;
+				case TYPE_FLOAT_VEC3:	writeVectorConstructor<3>(op, s_constInVec3[inNdx]);				break;
+				case TYPE_FLOAT_VEC4:	writeVectorConstructor<4>(op, s_constInVec4[inNdx]);				break;
+				case TYPE_FLOAT_MAT2:	writeMatrixConstructor<2, 2>(op, Mat2(s_constInMat2x2[inNdx]));		break;
+				case TYPE_FLOAT_MAT2X3:	writeMatrixConstructor<2, 3>(op, Mat2x3(s_constInMat2x3[inNdx]));	break;
+				case TYPE_FLOAT_MAT2X4:	writeMatrixConstructor<2, 4>(op, Mat2x4(s_constInMat2x4[inNdx]));	break;
+				case TYPE_FLOAT_MAT3X2:	writeMatrixConstructor<3, 2>(op, Mat3x2(s_constInMat3x2[inNdx]));	break;
+				case TYPE_FLOAT_MAT3:	writeMatrixConstructor<3, 3>(op, Mat3(s_constInMat3x3[inNdx]));		break;
+				case TYPE_FLOAT_MAT3X4:	writeMatrixConstructor<3, 4>(op, Mat3x4(s_constInMat3x4[inNdx]));	break;
+				case TYPE_FLOAT_MAT4X2:	writeMatrixConstructor<4, 2>(op, Mat4x2(s_constInMat4x2[inNdx]));	break;
+				case TYPE_FLOAT_MAT4X3:	writeMatrixConstructor<4, 3>(op, Mat4x3(s_constInMat4x3[inNdx]));	break;
+				case TYPE_FLOAT_MAT4:	writeMatrixConstructor<4, 4>(op, Mat4(s_constInMat4x4[inNdx]));		break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+
+			op << ";\n";
+
+			inValue = string("in") + de::toString(inNdx);
+		}
+	}
+
+	vtx << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	gl_Position = a_position;\n";
+	frag << "\n"
+		 << "void main (void)\n"
+		 << "{\n";
+
+	if (m_isVertexCase)
+		frag << "	dEQP_FragColor = v_color;\n";
+	else
+	{
+		for (vector<string>::const_iterator copyIter = passVars.begin(); copyIter != passVars.end(); copyIter++)
+			vtx << "	v_" << *copyIter << " = " << "a_" << *copyIter << ";\n";
+	}
+
+	// Operation.
+
+	switch (getOperationNature(m_op))
+	{
+		case OPERATIONNATURE_PURE:
+			DE_ASSERT(getOperationType(m_op) != OPERATIONTYPE_ASSIGNMENT);
+
+			operationValue0 = inValue0;
+			operationValue1 = inValue1;
+			break;
+
+		case OPERATIONNATURE_MUTATING:
+			DE_ASSERT(getOperationType(m_op) != OPERATIONTYPE_ASSIGNMENT);
+
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " tmpValue = " << inValue0 << ";\n";
+
+			operationValue0 = "tmpValue";
+			operationValue1 = inValue1;
+			break;
+
+		case OPERATIONNATURE_ASSIGNMENT:
+			DE_ASSERT(getOperationType(m_op) == OPERATIONTYPE_ASSIGNMENT);
+
+			operationValue0 = inValue0;
+			operationValue1 = inValue1;
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	switch (getOperationType(m_op))
+	{
+		case OPERATIONTYPE_BINARY_OPERATOR:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << operationValue0 << " " << getOperationName(m_op) << " " << operationValue1 << ";\n";
+			break;
+
+		case OPERATIONTYPE_UNARY_PREFIX_OPERATOR:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << getOperationName(m_op) << operationValue0 << ";\n";
+			break;
+
+		case OPERATIONTYPE_UNARY_POSTFIX_OPERATOR:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << operationValue0 << getOperationName(m_op) << ";\n";
+			break;
+
+		case OPERATIONTYPE_BINARY_FUNCTION:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << getOperationName(m_op) << "(" << operationValue0 << ", " << operationValue1 << ");\n";
+			break;
+
+		case OPERATIONTYPE_UNARY_FUNCTION:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << getOperationName(m_op) << "(" << operationValue0 << ");\n";
+			break;
+
+		case OPERATIONTYPE_ASSIGNMENT:
+			op << "	" << getPrecisionName(resultPrec) << " " << getDataTypeName(resultType) << " res = " << operationValue0 << ";\n";
+			op << "	res " << getOperationName(m_op) << " " << operationValue1 << ";\n";
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// Reduction to vec3 (rgb). Check the used value too if it was modified
+	op << "	" << (m_isVertexCase ? "v_color" : "dEQP_FragColor") << " = ";
+
+	if (isOperationValueModifying(m_op))
+		op << "vec4(" << genGLSLMatToVec3Reduction(resultType, "res") << ", 1.0) + vec4(" << genGLSLMatToVec3Reduction(resultType, "tmpValue") << ", 0.0);\n";
+	else
+		op << "vec4(" << genGLSLMatToVec3Reduction(resultType, "res") << ", 1.0);\n";
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource	= vtx.str();
+	m_fragShaderSource	= frag.str();
+
+	// \todo [2012-02-14 pyry] Compute better values for matrix tests.
+	m_userAttribTransforms.resize(4);
+	for (int attribNdx = 0; attribNdx < 4; attribNdx++)
+	{
+		m_userAttribTransforms[attribNdx] = Mat4(0.0f);
+		m_userAttribTransforms[attribNdx](                  0, 3) = 0.2f;						// !< prevent matrix*vec from going into zero (assuming vec.w != 0)
+		m_userAttribTransforms[attribNdx](                  1, 3) = 0.1f;						// !<
+		m_userAttribTransforms[attribNdx](                  2, 3) = 0.4f + 0.15f * attribNdx;	// !<
+		m_userAttribTransforms[attribNdx](                  3, 3) = 0.7f;						// !<
+		m_userAttribTransforms[attribNdx]((0 + attribNdx) % 4, 0) = 1.0f;
+		m_userAttribTransforms[attribNdx]((1 + attribNdx) % 4, 1) = 1.0f;
+		m_userAttribTransforms[attribNdx]((2 + attribNdx) % 4, 2) = 1.0f;
+		m_userAttribTransforms[attribNdx]((3 + attribNdx) % 4, 3) = 1.0f;
+	}
+
+	// prevent bad reference cases such as black result images by fine-tuning used matrices
+	if (getOperationTestMatrixType(m_op) != TESTMATRIXTYPE_DEFAULT)
+	{
+		for (int attribNdx = 0; attribNdx < 4; attribNdx++)
+		{
+			for (int row = 0; row < 4; row++)
+			for (int col = 0; col < 4; col++)
+			{
+				switch (getOperationTestMatrixType(m_op))
+				{
+					case TESTMATRIXTYPE_NEGATED:
+						m_userAttribTransforms[attribNdx](row, col) = -m_userAttribTransforms[attribNdx](row, col);
+						break;
+					case TESTMATRIXTYPE_INCREMENTED:
+						m_userAttribTransforms[attribNdx](row, col) += 0.3f;
+						break;
+					case TESTMATRIXTYPE_DECREMENTED:
+						m_userAttribTransforms[attribNdx](row, col) -= 0.3f;
+						break;
+					case TESTMATRIXTYPE_NEGATED_INCREMENTED:
+						m_userAttribTransforms[attribNdx](row, col) = -m_userAttribTransforms[attribNdx](row, col) + 0.3f;
+						break;
+					case TESTMATRIXTYPE_INCREMENTED_LESS:
+						m_userAttribTransforms[attribNdx](row, col) -= 0.1f;
+						break;
+
+					default:
+						DE_ASSERT(DE_FALSE);
+						break;
+				}
+			}
+		}
+	}
+
+	ShaderRenderCase::init();
+}
+
+std::string ShaderMatrixCase::genGLSLMatToVec3Reduction (const glu::DataType& matType, const char* varName)
+{
+	std::ostringstream op;
+
+	switch (matType)
+	{
+		case TYPE_FLOAT:		op << varName << ", "			<< varName << ", "			<< varName << "";																																			break;
+		case TYPE_FLOAT_VEC2:	op << varName << ".x, "			<< varName << ".y, "		<< varName << ".x";																																			break;
+		case TYPE_FLOAT_VEC3:	op << varName << "";																																																	break;
+		case TYPE_FLOAT_VEC4:	op << varName << ".x, "			<< varName << ".y, "		<< varName << ".z+"			<< varName << ".w";																												break;
+		case TYPE_FLOAT_MAT2:	op << varName << "[0][0], "		<< varName << "[1][0], "	<< varName << "[0][1]+"		<< varName << "[1][1]";																											break;
+		case TYPE_FLOAT_MAT2X3:	op << varName << "[0] + "		<< varName << "[1]";																																									break;
+		case TYPE_FLOAT_MAT2X4:	op << varName << "[0].xyz + "	<< varName << "[1].yzw";																																								break;
+		case TYPE_FLOAT_MAT3X2:	op << varName << "[0][0]+"		<< varName << "[0][1], "	<< varName << "[1][0]+"		<< varName << "[1][1], "	<< varName << "[2][0]+" << varName << "[2][1]";														break;
+		case TYPE_FLOAT_MAT3:	op << varName << "[0] + "		<< varName << "[1] + "		<< varName << "[2]";																																		break;
+		case TYPE_FLOAT_MAT3X4:	op << varName << "[0].xyz + "	<< varName << "[1].yzw + "	<< varName << "[2].zwx";																																	break;
+		case TYPE_FLOAT_MAT4X2:	op << varName << "[0][0]+"		<< varName << "[0][1]+"		<< varName << "[3][0], "	<< varName << "[1][0]+"		<< varName << "[1][1]+" << varName << "[3][1], " << varName << "[2][0]+" << varName << "[2][1]";	break;
+		case TYPE_FLOAT_MAT4X3:	op << varName << "[0] + "		<< varName << "[1] + "		<< varName << "[2] + "		<< varName << "[3]";																											break;
+		case TYPE_FLOAT_MAT4:	op << varName << "[0].xyz+"		<< varName << "[1].yzw+"	<< varName << "[2].zwx+"	<< varName << "[3].wxy";																										break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return op.str();
+}
+
+void ShaderMatrixCase::setupUniforms (int programID, const tcu::Vec4& constCoords)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	DE_UNREF(constCoords);
+
+	for (int inNdx = 0; inNdx < 2; inNdx++)
+	{
+		const ShaderInput& in = inNdx > 0 ? m_in1 : m_in0;
+
+		if (in.inputType == INPUTTYPE_UNIFORM)
+		{
+			int loc = gl.getUniformLocation(programID, (string("u_in") + de::toString(inNdx)).c_str());
+
+			if (loc < 0)
+				continue;
+
+			switch (in.dataType)
+			{
+				case TYPE_FLOAT:		gl.uniform1f(loc, s_constInFloat[inNdx]);						break;
+				case TYPE_FLOAT_VEC2:	gl.uniform2fv(loc, 1, s_constInVec2[inNdx].getPtr());			break;
+				case TYPE_FLOAT_VEC3:	gl.uniform3fv(loc, 1, s_constInVec3[inNdx].getPtr());			break;
+				case TYPE_FLOAT_VEC4:	gl.uniform4fv(loc, 1, s_constInVec4[inNdx].getPtr());			break;
+				// \note GLES3 supports transpose in matrix upload.
+				case TYPE_FLOAT_MAT2:	gl.uniformMatrix2fv	(loc, 1, GL_TRUE, s_constInMat2x2[inNdx]);	break;
+				case TYPE_FLOAT_MAT2X3:	gl.uniformMatrix2x3fv(loc, 1, GL_TRUE, s_constInMat2x3[inNdx]);	break;
+				case TYPE_FLOAT_MAT2X4:	gl.uniformMatrix2x4fv(loc, 1, GL_TRUE, s_constInMat2x4[inNdx]);	break;
+				case TYPE_FLOAT_MAT3X2:	gl.uniformMatrix3x2fv(loc, 1, GL_TRUE, s_constInMat3x2[inNdx]);	break;
+				case TYPE_FLOAT_MAT3:	gl.uniformMatrix3fv	(loc, 1, GL_TRUE, s_constInMat3x3[inNdx]);	break;
+				case TYPE_FLOAT_MAT3X4:	gl.uniformMatrix3x4fv(loc, 1, GL_TRUE, s_constInMat3x4[inNdx]);	break;
+				case TYPE_FLOAT_MAT4X2:	gl.uniformMatrix4x2fv(loc, 1, GL_TRUE, s_constInMat4x2[inNdx]);	break;
+				case TYPE_FLOAT_MAT4X3:	gl.uniformMatrix4x3fv(loc, 1, GL_TRUE, s_constInMat4x3[inNdx]);	break;
+				case TYPE_FLOAT_MAT4:	gl.uniformMatrix4fv	(loc, 1, GL_TRUE, s_constInMat4x4[inNdx]);	break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+	}
+}
+
+ShaderMatrixTests::ShaderMatrixTests (Context& context)
+	: TestCaseGroup(context, "matrix", "Matrix Tests")
+{
+}
+
+ShaderMatrixTests::~ShaderMatrixTests (void)
+{
+}
+
+void ShaderMatrixTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		const char*		desc;
+		MatrixOp		op;
+		bool			extendedInputTypeCases; // !< test with const and uniform types too
+		bool			createInputTypeGroup;	// !< create group for input types
+	} ops[] =
+	{
+		{ "add",			"Matrix addition tests",						OP_ADD,				true,	true	},
+		{ "sub",			"Matrix subtraction tests",						OP_SUB,				true,	true	},
+		{ "mul",			"Matrix multiplication tests",					OP_MUL,				true,	true	},
+		{ "div",			"Matrix division tests",						OP_DIV,				true,	true	},
+		{ "matrixcompmult",	"Matrix component-wise multiplication tests",	OP_COMP_MUL,		false,	true	},
+		{ "outerproduct",	"Matrix outerProduct() tests",					OP_OUTER_PRODUCT,	false,	true	},
+		{ "transpose",		"Matrix transpose() tests",						OP_TRANSPOSE,		false,	true	},
+		{ "determinant",	"Matrix determinant() tests",					OP_DETERMINANT,		false,	true	},
+		{ "inverse",		"Matrix inverse() tests",						OP_INVERSE,			false,	true	},
+		{ "unary_addition",	"Matrix unary addition tests",					OP_UNARY_PLUS,		false,	false	},
+		{ "negation",		"Matrix negation tests",						OP_NEGATION,		false,	false	},
+		{ "pre_increment",	"Matrix prefix increment tests",				OP_PRE_INCREMENT,	false,	false	},
+		{ "pre_decrement",	"Matrix prefix decrement tests",				OP_PRE_DECREMENT,	false,	false	},
+		{ "post_increment",	"Matrix postfix increment tests",				OP_POST_INCREMENT,	false,	false	},
+		{ "post_decrement",	"Matrix postfix decrement tests",				OP_POST_DECREMENT,	false,	false	},
+		{ "add_assign",		"Matrix add into tests",						OP_ADD_INTO,		false,	false	},
+		{ "sub_assign",		"Matrix subtract from tests",					OP_SUBTRACT_FROM,	false,	false	},
+		{ "mul_assign",		"Matrix multiply into tests",					OP_MULTIPLY_INTO,	false,	false	},
+		{ "div_assign",		"Matrix divide into tests",						OP_DIVIDE_INTO,		false,	false	},
+	};
+
+	struct InputTypeSpec
+	{
+		const char*		name;
+		const char*		desc;
+		InputType		type;
+	};
+	static const InputTypeSpec extendedInputTypes[] =
+	{
+		{ "const",		"Constant matrix input",	INPUTTYPE_CONST		},
+		{ "uniform",	"Uniform matrix input",		INPUTTYPE_UNIFORM	},
+		{ "dynamic",	"Dynamic matrix input",		INPUTTYPE_DYNAMIC	}
+	};
+	static const InputTypeSpec reducedInputTypes[] =
+	{
+		{ "dynamic",	"Dynamic matrix input",		INPUTTYPE_DYNAMIC	}
+	};
+
+	static const DataType matrixTypes[] =
+	{
+		TYPE_FLOAT_MAT2,
+		TYPE_FLOAT_MAT2X3,
+		TYPE_FLOAT_MAT2X4,
+		TYPE_FLOAT_MAT3X2,
+		TYPE_FLOAT_MAT3,
+		TYPE_FLOAT_MAT3X4,
+		TYPE_FLOAT_MAT4X2,
+		TYPE_FLOAT_MAT4X3,
+		TYPE_FLOAT_MAT4
+	};
+
+	static const Precision precisions[] =
+	{
+		PRECISION_LOWP,
+		PRECISION_MEDIUMP,
+		PRECISION_HIGHP
+	};
+
+	for (int opNdx = 0; opNdx < DE_LENGTH_OF_ARRAY(ops); opNdx++)
+	{
+		const InputTypeSpec*	inTypeList		= (ops[opNdx].extendedInputTypeCases) ? (extendedInputTypes) : (reducedInputTypes);
+		const int				inTypeListSize	= (ops[opNdx].extendedInputTypeCases) ? (DE_LENGTH_OF_ARRAY(extendedInputTypes)) : (DE_LENGTH_OF_ARRAY(reducedInputTypes));
+		const MatrixOp			op				= ops[opNdx].op;
+		tcu::TestCaseGroup*		opGroup			= new tcu::TestCaseGroup(m_testCtx, ops[opNdx].name, ops[opNdx].desc);
+
+		addChild(opGroup);
+
+		for (int inTypeNdx = 0; inTypeNdx < inTypeListSize; inTypeNdx++)
+		{
+			const InputType		inputType	= inTypeList[inTypeNdx].type;
+			tcu::TestCaseGroup* inGroup;
+
+			if (ops[opNdx].createInputTypeGroup)
+			{
+				inGroup = new tcu::TestCaseGroup(m_testCtx, inTypeList[inTypeNdx].name, inTypeList[inTypeNdx].desc);
+				opGroup->addChild(inGroup);
+			}
+			else
+				inGroup = opGroup;
+
+			for (int matTypeNdx = 0; matTypeNdx < DE_LENGTH_OF_ARRAY(matrixTypes); matTypeNdx++)
+			{
+				DataType	matType		= matrixTypes[matTypeNdx];
+				int			numCols		= getDataTypeMatrixNumColumns(matType);
+				int			numRows		= getDataTypeMatrixNumRows(matType);
+				const char*	matTypeName	= getDataTypeName(matType);
+
+				for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+				{
+					Precision	precision	= precisions[precNdx];
+					const char*	precName	= getPrecisionName(precision);
+					string		baseName	= string(precName) + "_" + matTypeName + "_";
+					ShaderInput	matIn		(inputType, matType, precision);
+
+					if (isOperationMatrixScalar(op))
+					{
+						// Matrix-scalar \note For div cases we use uniform input.
+						ShaderInput scalarIn(op == OP_DIV ? INPUTTYPE_UNIFORM : INPUTTYPE_DYNAMIC, TYPE_FLOAT, precision);
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "float_vertex").c_str(),		"Matrix-scalar case", matIn, scalarIn, op, true));
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "float_fragment").c_str(),	"Matrix-scalar case", matIn, scalarIn, op, false));
+					}
+
+					if (isOperationMatrixVector(op))
+					{
+						// Matrix-vector.
+						DataType	colVecType	= getDataTypeFloatVec(numCols);
+						ShaderInput colVecIn	(op == OP_DIV ? INPUTTYPE_UNIFORM : INPUTTYPE_DYNAMIC, colVecType, precision);
+
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + getDataTypeName(colVecType) + "_vertex").c_str(),		"Matrix-vector case", matIn, colVecIn, op, true));
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + getDataTypeName(colVecType) + "_fragment").c_str(),	"Matrix-vector case", matIn, colVecIn, op, false));
+
+						// Vector-matrix.
+						DataType	rowVecType	= getDataTypeFloatVec(numRows);
+						ShaderInput	rowVecIn	(op == OP_DIV ? INPUTTYPE_UNIFORM : INPUTTYPE_DYNAMIC, rowVecType, precision);
+						string		vecMatName	= string(precName) + "_" + getDataTypeName(rowVecType) + "_" + matTypeName;
+
+						inGroup->addChild(new ShaderMatrixCase(m_context, (vecMatName + "_vertex").c_str(),		"Vector-matrix case", rowVecIn, matIn, op, true));
+						inGroup->addChild(new ShaderMatrixCase(m_context, (vecMatName + "_fragment").c_str(),	"Vector-matrix case", rowVecIn, matIn, op, false));
+					}
+
+					if (isOperationArithmeticMatrixMatrix(op))
+					{
+						// Arithmetic matrix-matrix multiplication.
+						for (int otherCols = 2; otherCols <= 4; otherCols++)
+						{
+							ShaderInput otherMatIn(inputType == INPUTTYPE_DYNAMIC ? INPUTTYPE_UNIFORM : inputType, getDataTypeMatrix(otherCols, numCols /* rows */), precision);
+							inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + getDataTypeName(otherMatIn.dataType) + "_vertex").c_str(),	"Matrix-matrix case", matIn, otherMatIn, op, true));
+							inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + getDataTypeName(otherMatIn.dataType) + "_fragment").c_str(),	"Matrix-matrix case", matIn, otherMatIn, op, false));
+						}
+					}
+					else if (isOperationComponentwiseMatrixMatrix(op))
+					{
+						// Component-wise.
+						ShaderInput otherMatIn(inputType == INPUTTYPE_DYNAMIC ? INPUTTYPE_UNIFORM : inputType, matType, precision);
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + matTypeName + "_vertex").c_str(),		"Matrix-matrix case", matIn, otherMatIn, op, true));
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + matTypeName + "_fragment").c_str(),	"Matrix-matrix case", matIn, otherMatIn, op, false));
+					}
+
+					if (isOperationVectorVector(op))
+					{
+						ShaderInput vec1In(inputType,																getDataTypeFloatVec(numRows), precision);
+						ShaderInput vec2In((inputType == INPUTTYPE_DYNAMIC) ? (INPUTTYPE_UNIFORM) : (inputType),	getDataTypeFloatVec(numCols), precision);
+
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "float_vertex").c_str(),		"Vector-vector case", vec1In, vec2In, op, true));
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "float_fragment").c_str(),	"Vector-vector case", vec1In, vec2In, op, false));
+					}
+
+					if ((isOperationUnaryAnyMatrix(op)) ||
+						(isOperationUnarySymmetricMatrix(op) && numCols == numRows))
+					{
+						ShaderInput voidInput(INPUTTYPE_LAST, TYPE_LAST, PRECISION_LAST);
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "float_vertex").c_str(),		"Matrix case", matIn, voidInput, op, true));
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "float_fragment").c_str(),	"Matrix case", matIn, voidInput, op, false));
+					}
+
+					if ((isOperationAssignmentAnyMatrix(op)) ||
+						(isOperationAssignmentSymmetricMatrix(op) && numCols == numRows))
+					{
+						ShaderInput otherMatIn(inputType == INPUTTYPE_DYNAMIC ? INPUTTYPE_UNIFORM : inputType, matType, precision);
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "float_vertex").c_str(),		"Matrix assignment case", matIn, otherMatIn, op, true));
+						inGroup->addChild(new ShaderMatrixCase(m_context, (baseName + "float_fragment").c_str(),	"Matrix assignment case", matIn, otherMatIn, op, false));
+					}
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderMatrixTests.hpp b/modules/gles3/functional/es3fShaderMatrixTests.hpp
new file mode 100644
index 0000000..208653c
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderMatrixTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERMATRIXTESTS_HPP
+#define _ES3FSHADERMATRIXTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader matrix arithmetic tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderMatrixTests : public TestCaseGroup
+{
+public:
+							ShaderMatrixTests		(Context& context);
+	virtual					~ShaderMatrixTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderMatrixTests		(const ShaderMatrixTests&);		// not allowed!
+	ShaderMatrixTests&		operator=				(const ShaderMatrixTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERMATRIXTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderOperatorTests.cpp b/modules/gles3/functional/es3fShaderOperatorTests.cpp
new file mode 100644
index 0000000..3f24038
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderOperatorTests.cpp
@@ -0,0 +1,2187 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader operators tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderOperatorTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuVectorUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "deStringUtil.hpp"
+#include "deInt32.h"
+#include "deMemory.h"
+
+#include <map>
+#include <limits>
+
+using namespace tcu;
+using namespace glu;
+using namespace deqp::gls;
+
+using std::map;
+using std::pair;
+using std::vector;
+using std::string;
+using std::ostringstream;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+#if defined(abs)
+#	undef abs
+#endif
+
+using de::min;
+using de::max;
+using de::clamp;
+
+// \note VS2013 gets confused without these
+using tcu::asinh;
+using tcu::acosh;
+using tcu::atanh;
+using tcu::exp2;
+using tcu::log2;
+using tcu::trunc;
+
+inline float abs		(float v)			{ return deFloatAbs(v); }
+
+inline bool logicalAnd	(bool a, bool b)	{ return (a && b); }
+inline bool logicalOr	(bool a, bool b)	{ return (a || b); }
+inline bool logicalXor	(bool a, bool b)	{ return (a != b); }
+
+// \note stdlib.h defines div() that is not compatible with the macros.
+template<typename T> inline T div (T a, T b) { return a / b; }
+
+template<typename T> inline T leftShift (T value, int amount) { return value << amount; }
+
+inline deUint32	rightShift (deUint32 value, int amount)		{ return value >> amount; }
+inline int		rightShift (int value, int amount)			{ return (value >> amount) | (value >= 0 ? 0 : ~(~0U >> amount)); } // \note Arithmetic shift.
+
+template<typename T, int Size> Vector<T, Size> leftShift (const Vector<T, Size>& value, const Vector<int, Size>& amount)
+{
+	Vector<T, Size> result;
+	for (int i = 0; i < Size; i++)
+		result[i] = leftShift(value[i], amount[i]);
+	return result;
+}
+
+template<typename T, int Size> Vector<T, Size> rightShift (const Vector<T, Size>& value, const Vector<int, Size>& amount)
+{
+	Vector<T, Size> result;
+	for (int i = 0; i < Size; i++)
+		result[i] = rightShift(value[i], amount[i]);
+	return result;
+}
+
+template<typename T, int Size> Vector<T, Size> leftShiftVecScalar	(const Vector<T, Size>& value, int amount) { return leftShift(value, Vector<int, Size>(amount)); }
+template<typename T, int Size> Vector<T, Size> rightShiftVecScalar	(const Vector<T, Size>& value, int amount) { return rightShift(value, Vector<int, Size>(amount)); }
+
+template<typename T, int Size>
+inline Vector<T, Size> minVecScalar (const Vector<T, Size>& v, T s)
+{
+	Vector<T, Size> res;
+	for (int i = 0; i < Size; i++)
+		res[i] = min(v[i], s);
+	return res;
+}
+
+template<typename T, int Size>
+inline Vector<T, Size> maxVecScalar (const Vector<T, Size>& v, T s)
+{
+	Vector<T, Size> res;
+	for (int i = 0; i < Size; i++)
+		res[i] = max(v[i], s);
+	return res;
+}
+
+template<typename T, int Size>
+inline Vector<T, Size> clampVecScalarScalar (const Vector<T, Size>& v, T s0, T s1)
+{
+	Vector<T, Size> res;
+	for (int i = 0; i < Size; i++)
+		res[i] = clamp(v[i], s0, s1);
+	return res;
+}
+
+template<typename T, int Size>
+inline Vector<T, Size> mixVecVecScalar (const Vector<T, Size>& v0, const Vector<T, Size>& v1, T s)
+{
+	Vector<T, Size> res;
+	for (int i = 0; i < Size; i++)
+		res[i] = mix(v0[i], v1[i], s);
+	return res;
+}
+
+template<typename T, int Size>
+inline Vector<T, Size> stepScalarVec (T s, const Vector<T, Size>& v)
+{
+	Vector<T, Size> res;
+	for (int i = 0; i < Size; i++)
+		res[i] = step(s, v[i]);
+	return res;
+}
+
+template<typename T, int Size>
+inline Vector<T, Size> smoothStepScalarScalarVec (T s0, T s1, const Vector<T, Size>& v)
+{
+	Vector<T, Size> res;
+	for (int i = 0; i < Size; i++)
+		res[i] = smoothStep(s0, s1, v[i]);
+	return res;
+}
+
+inline float	addOne (float v)	{ return v + 1.0f; };
+inline float	subOne (float v)	{ return v - 1.0f; };
+inline int		addOne (int v)		{ return v + 1; };
+inline int		subOne (int v)		{ return v - 1; };
+inline deUint32	addOne (deUint32 v)	{ return v + 1; };
+inline deUint32	subOne (deUint32 v)	{ return v - 1; };
+
+template<int Size> inline Vector<float, Size>		addOne (const Vector<float, Size>& v)		{ return v + 1.0f; };
+template<int Size> inline Vector<float, Size>		subOne (const Vector<float, Size>& v)		{ return v - 1.0f; };
+template<int Size> inline Vector<int, Size>			addOne (const Vector<int, Size>& v)			{ return v + 1; };
+template<int Size> inline Vector<int, Size>			subOne (const Vector<int, Size>& v)			{ return v - 1; };
+template<int Size> inline Vector<deUint32, Size>	addOne (const Vector<deUint32, Size>& v)	{ return v + 1U; };
+template<int Size> inline Vector<deUint32, Size>	subOne (const Vector<deUint32, Size>& v)	{ return (v.asInt() - 1).asUint(); };
+
+template<typename T> inline T selection	(bool cond, T a, T b)	{ return cond ? a : b; };
+
+// Vec-scalar and scalar-vec binary operators.
+
+// \note This one is done separately due to how the overloaded minus operator is implemented for vector-scalar operands.
+template<int Size>				inline Vector<deUint32, Size>	subVecScalar			(const Vector<deUint32, Size>& v, deUint32 s)	{ return (v.asInt() - (int)s).asUint(); };
+
+template<typename T, int Size>	inline Vector<T, Size>			addVecScalar			(const Vector<T, Size>& v, T s)					{ return v + s; };
+template<typename T, int Size>	inline Vector<T, Size>			subVecScalar			(const Vector<T, Size>& v, T s)					{ return v - s; };
+template<typename T, int Size>	inline Vector<T, Size>			mulVecScalar			(const Vector<T, Size>& v, T s)					{ return v * s; };
+template<typename T, int Size>	inline Vector<T, Size>			divVecScalar			(const Vector<T, Size>& v, T s)					{ return v / s; };
+template<typename T, int Size>	inline Vector<T, Size>			modVecScalar			(const Vector<T, Size>& v, T s)					{ return mod(v, Vector<T, Size>(s)); };
+template<typename T, int Size>	inline Vector<T, Size>			bitwiseAndVecScalar		(const Vector<T, Size>& v, T s)					{ return bitwiseAnd(v, Vector<T, Size>(s)); };
+template<typename T, int Size>	inline Vector<T, Size>			bitwiseOrVecScalar		(const Vector<T, Size>& v, T s)					{ return bitwiseOr(v, Vector<T, Size>(s)); };
+template<typename T, int Size>	inline Vector<T, Size>			bitwiseXorVecScalar		(const Vector<T, Size>& v, T s)					{ return bitwiseXor(v, Vector<T, Size>(s)); };
+
+template<typename T, int Size> inline Vector<T, Size>			addScalarVec			(T s, const Vector<T, Size>& v) 				{ return s + v; };
+template<typename T, int Size> inline Vector<T, Size>			subScalarVec			(T s, const Vector<T, Size>& v) 				{ return s - v; };
+template<typename T, int Size> inline Vector<T, Size>			mulScalarVec			(T s, const Vector<T, Size>& v) 				{ return s * v; };
+template<typename T, int Size> inline Vector<T, Size>			divScalarVec			(T s, const Vector<T, Size>& v) 				{ return s / v; };
+template<typename T, int Size> inline Vector<T, Size>			modScalarVec			(T s, const Vector<T, Size>& v)					{ return mod(Vector<T, Size>(s), v); };
+template<typename T, int Size> inline Vector<T, Size>			bitwiseAndScalarVec		(T s, const Vector<T, Size>& v)					{ return bitwiseAnd(Vector<T, Size>(s), v); };
+template<typename T, int Size> inline Vector<T, Size>			bitwiseOrScalarVec		(T s, const Vector<T, Size>& v)					{ return bitwiseOr(Vector<T, Size>(s), v); };
+template<typename T, int Size> inline Vector<T, Size>			bitwiseXorScalarVec		(T s, const Vector<T, Size>& v)					{ return bitwiseXor(Vector<T, Size>(s), v); };
+
+// Reference functions for specific sequence operations for the sequence operator tests.
+
+// Reference for expression "in0, in2 + in1, in1 + in0"
+inline Vec4		sequenceNoSideEffCase0 (const Vec4& in0, const Vec4& in1, const Vec4& in2)		{ DE_UNREF(in2); return in1 + in0; }
+// Reference for expression "in0, in2 + in1, in1 + in0"
+inline deUint32	sequenceNoSideEffCase1 (float in0, deUint32 in1, float in2)						{ DE_UNREF(in0); DE_UNREF(in2); return in1 + in1; }
+// Reference for expression "in0 && in1, in0, ivec2(vec2(in0) + in2)"
+inline IVec2	sequenceNoSideEffCase2 (bool in0, bool in1, const Vec2& in2)					{ DE_UNREF(in1); return IVec2((int)((float)in0 + in2.x()), (int)((float)in0 + in2.y())); }
+// Reference for expression "in0 + vec4(in1), in2, in1"
+inline IVec4	sequenceNoSideEffCase3 (const Vec4& in0, const IVec4& in1, const BVec4& in2)	{ DE_UNREF(in0); DE_UNREF(in2); return in1; }
+// Reference for expression "in0++, in1 = in0 + in2, in2 = in1"
+inline Vec4		sequenceSideEffCase0 (const Vec4& in0, const Vec4& in1, const Vec4& in2)		{ DE_UNREF(in1); return in0 + 1.0f + in2; }
+// Reference for expression "in1++, in0 = float(in1), in1 = uint(in0 + in2)"
+inline deUint32	sequenceSideEffCase1 (float in0, deUint32 in1, float in2)						{ DE_UNREF(in0); return (deUint32)(in1 + 1.0f + in2); }
+// Reference for expression "in1 = in0, in2++, in2 = in2 + vec2(in1), ivec2(in2)"
+inline IVec2	sequenceSideEffCase2 (bool in0, bool in1, const Vec2& in2)						{ DE_UNREF(in1); return (in2 + Vec2(1.0f) + Vec2((float)in0)).asInt(); }
+// Reference for expression "in0 = in0 + vec4(in2), in1 = in1 + ivec4(in0), in1++"
+inline IVec4	sequenceSideEffCase3 (const Vec4& in0, const IVec4& in1, const BVec4& in2)		{ return in1 + (in0 + Vec4((float)in2.x(), (float)in2.y(), (float)in2.z(), (float)in2.w())).asInt(); }
+
+// ShaderEvalFunc-type wrappers for the above functions.
+void evalSequenceNoSideEffCase0	(ShaderEvalContext& ctx) { ctx.color		= sequenceNoSideEffCase0		(ctx.in[0].swizzle(1, 2, 3, 0),	ctx.in[1].swizzle(3, 2, 1, 0),			ctx.in[2].swizzle(0, 3, 2, 1)); }
+void evalSequenceNoSideEffCase1	(ShaderEvalContext& ctx) { ctx.color.x()	= (float)sequenceNoSideEffCase1	(ctx.in[0].z(),					(deUint32)ctx.in[1].x(),				ctx.in[2].y()); }
+void evalSequenceNoSideEffCase2	(ShaderEvalContext& ctx) { ctx.color.yz()	= sequenceNoSideEffCase2		(ctx.in[0].z() > 0.0f,			ctx.in[1].x() > 0.0f,					ctx.in[2].swizzle(2, 1)).asFloat(); }
+void evalSequenceNoSideEffCase3	(ShaderEvalContext& ctx) { ctx.color		= sequenceNoSideEffCase3		(ctx.in[0].swizzle(1, 2, 3, 0),	ctx.in[1].swizzle(3, 2, 1, 0).asInt(),	greaterThan(ctx.in[2].swizzle(0, 3, 2, 1), Vec4(0.0f, 0.0f, 0.0f, 0.0f))).asFloat(); }
+void evalSequenceSideEffCase0	(ShaderEvalContext& ctx) { ctx.color		= sequenceSideEffCase0			(ctx.in[0].swizzle(1, 2, 3, 0),	ctx.in[1].swizzle(3, 2, 1, 0),			ctx.in[2].swizzle(0, 3, 2, 1)); }
+void evalSequenceSideEffCase1	(ShaderEvalContext& ctx) { ctx.color.x()	= (float)sequenceSideEffCase1	(ctx.in[0].z(),					(deUint32)ctx.in[1].x(),				ctx.in[2].y()); }
+void evalSequenceSideEffCase2	(ShaderEvalContext& ctx) { ctx.color.yz()	= sequenceSideEffCase2			(ctx.in[0].z() > 0.0f,			ctx.in[1].x() > 0.0f,					ctx.in[2].swizzle(2, 1)).asFloat(); }
+void evalSequenceSideEffCase3	(ShaderEvalContext& ctx) { ctx.color		= sequenceSideEffCase3			(ctx.in[0].swizzle(1, 2, 3, 0),	ctx.in[1].swizzle(3, 2, 1, 0).asInt(),	greaterThan(ctx.in[2].swizzle(0, 3, 2, 1), Vec4(0.0f, 0.0f, 0.0f, 0.0f))).asFloat(); }
+
+static string stringJoin (const vector<string>& elems, const string& delim)
+{
+	string result;
+	for (int i = 0; i < (int)elems.size(); i++)
+		result += (i > 0 ? delim : "") + elems[i];
+	return result;
+}
+
+static string twoValuedVec4 (const string& first, const string& second, const BVec4& firstMask)
+{
+	vector<string> elems(4);
+	for (int i = 0; i < 4; i++)
+		elems[i] = firstMask[i] ? first : second;
+
+	return "vec4(" + stringJoin(elems, ", ") + ")";
+}
+
+enum
+{
+	MAX_INPUTS = 3
+};
+
+enum PrecisionMask
+{
+	PRECMASK_NA				= 0,						//!< Precision not applicable (booleans)
+	PRECMASK_LOWP			= (1<<PRECISION_LOWP),
+	PRECMASK_MEDIUMP		= (1<<PRECISION_MEDIUMP),
+	PRECMASK_HIGHP			= (1<<PRECISION_HIGHP),
+
+	PRECMASK_LOWP_MEDIUMP	= PRECMASK_LOWP | PRECMASK_MEDIUMP,
+	PRECMASK_MEDIUMP_HIGHP	= PRECMASK_MEDIUMP | PRECMASK_HIGHP,
+	PRECMASK_ALL			= PRECMASK_LOWP | PRECMASK_MEDIUMP | PRECMASK_HIGHP
+};
+
+enum ValueType
+{
+	VALUE_NONE			= 0,
+	VALUE_FLOAT			= (1<<0),	// float scalar
+	VALUE_FLOAT_VEC		= (1<<1),	// float vector
+	VALUE_FLOAT_GENTYPE	= (1<<2),	// float scalar/vector
+	VALUE_VEC3			= (1<<3),	// vec3 only
+	VALUE_MATRIX		= (1<<4),	// matrix
+	VALUE_BOOL			= (1<<5),	// boolean scalar
+	VALUE_BOOL_VEC		= (1<<6),	// boolean vector
+	VALUE_BOOL_GENTYPE	= (1<<7),	// boolean scalar/vector
+	VALUE_INT			= (1<<8),	// int scalar
+	VALUE_INT_VEC		= (1<<9),	// int vector
+	VALUE_INT_GENTYPE	= (1<<10),	// int scalar/vector
+	VALUE_UINT			= (1<<11),	// uint scalar
+	VALUE_UINT_VEC		= (1<<12),	// uint vector
+	VALUE_UINT_GENTYPE	= (1<<13),	// uint scalar/vector
+
+	// Shorthands.
+	F				= VALUE_FLOAT,
+	FV				= VALUE_FLOAT_VEC,
+	GT				= VALUE_FLOAT_GENTYPE,
+	V3				= VALUE_VEC3,
+	M				= VALUE_MATRIX,
+	B				= VALUE_BOOL,
+	BV				= VALUE_BOOL_VEC,
+	BGT				= VALUE_BOOL_GENTYPE,
+	I				= VALUE_INT,
+	IV				= VALUE_INT_VEC,
+	IGT				= VALUE_INT_GENTYPE,
+	U				= VALUE_UINT,
+	UV				= VALUE_UINT_VEC,
+	UGT				= VALUE_UINT_GENTYPE
+};
+
+static inline bool isScalarType (ValueType type)
+{
+	return type == VALUE_FLOAT || type == VALUE_BOOL || type == VALUE_INT || type == VALUE_UINT;
+}
+
+static inline bool isFloatType (ValueType type)
+{
+	return (type & (VALUE_FLOAT | VALUE_FLOAT_VEC | VALUE_FLOAT_GENTYPE)) != 0;
+}
+
+static inline bool isIntType (ValueType type)
+{
+	return (type & (VALUE_INT | VALUE_INT_VEC | VALUE_INT_GENTYPE)) != 0;
+}
+
+static inline bool isUintType (ValueType type)
+{
+	return (type & (VALUE_UINT | VALUE_UINT_VEC | VALUE_UINT_GENTYPE)) != 0;
+}
+
+static inline bool isBoolType (ValueType type)
+{
+	return (type & (VALUE_BOOL | VALUE_BOOL_VEC | VALUE_BOOL_GENTYPE)) != 0;
+}
+
+static inline float getGLSLUintMaxAsFloat (const glw::Functions& gl, ShaderType shaderType, Precision uintPrecision)
+{
+	deUint32 intPrecisionGL;
+	deUint32 shaderTypeGL;
+
+	switch (uintPrecision)
+	{
+		case PRECISION_LOWP:		intPrecisionGL = GL_LOW_INT;		break;
+		case PRECISION_MEDIUMP:		intPrecisionGL = GL_MEDIUM_INT;		break;
+		case PRECISION_HIGHP:		intPrecisionGL = GL_HIGH_INT;		break;
+		default:
+			DE_ASSERT(false);
+			intPrecisionGL = 0;
+	}
+
+	switch (shaderType)
+	{
+		case SHADERTYPE_VERTEX:		shaderTypeGL = GL_VERTEX_SHADER;	break;
+		case SHADERTYPE_FRAGMENT:	shaderTypeGL = GL_FRAGMENT_SHADER;	break;
+		default:
+			DE_ASSERT(false);
+			shaderTypeGL = 0;
+	}
+
+	glw::GLint range[2]		= { -1, -1 };
+	glw::GLint precision	= -1;
+
+	gl.getShaderPrecisionFormat(shaderTypeGL, intPrecisionGL, &range[0], &precision);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderPrecisionFormat failed");
+
+	TCU_CHECK(de::inBounds(range[0], 8, 32));
+
+	const int numBitsInType = range[0] + 1;
+	return (float)((1ull << numBitsInType) - 1);
+}
+
+// Float scalar that can be either constant or a symbol that can be evaluated later.
+class FloatScalar
+{
+public:
+	enum Symbol
+	{
+		SYMBOL_LOWP_UINT_MAX = 0,
+		SYMBOL_MEDIUMP_UINT_MAX,
+
+		SYMBOL_LOWP_UINT_MAX_RECIPROCAL,
+		SYMBOL_MEDIUMP_UINT_MAX_RECIPROCAL,
+
+		SYMBOL_ONE_MINUS_UINT32MAX_DIV_LOWP_UINT_MAX,
+		SYMBOL_ONE_MINUS_UINT32MAX_DIV_MEDIUMP_UINT_MAX,
+
+		SYMBOL_LAST
+	};
+
+	FloatScalar (float c)	: m_isConstant(true),	m_value(c) {}
+	FloatScalar (Symbol s)	: m_isConstant(false),	m_value(s) {}
+
+	float getValue (const glw::Functions& gl, ShaderType shaderType) const
+	{
+		if (m_isConstant)
+			return m_value.constant;
+		else
+		{
+			switch (m_value.symbol)
+			{
+				case SYMBOL_LOWP_UINT_MAX:								return getGLSLUintMaxAsFloat(gl, shaderType, PRECISION_LOWP);
+				case SYMBOL_MEDIUMP_UINT_MAX:							return getGLSLUintMaxAsFloat(gl, shaderType, PRECISION_MEDIUMP);
+
+				case SYMBOL_LOWP_UINT_MAX_RECIPROCAL:					return 1.0f / getGLSLUintMaxAsFloat(gl, shaderType, PRECISION_LOWP);
+				case SYMBOL_MEDIUMP_UINT_MAX_RECIPROCAL:				return 1.0f / getGLSLUintMaxAsFloat(gl, shaderType, PRECISION_MEDIUMP);
+
+				case SYMBOL_ONE_MINUS_UINT32MAX_DIV_LOWP_UINT_MAX:		return 1.0f - (float)std::numeric_limits<deUint32>::max() / getGLSLUintMaxAsFloat(gl, shaderType, PRECISION_LOWP);
+				case SYMBOL_ONE_MINUS_UINT32MAX_DIV_MEDIUMP_UINT_MAX:	return 1.0f - (float)std::numeric_limits<deUint32>::max() / getGLSLUintMaxAsFloat(gl, shaderType, PRECISION_MEDIUMP);
+
+				default:
+					DE_ASSERT(false);
+					return 0.0f;
+			}
+		}
+	}
+
+private:
+	bool m_isConstant;
+
+	union ConstantOrSymbol
+	{
+		float	constant;
+		Symbol	symbol;
+
+		ConstantOrSymbol (float c)	: constant	(c) {}
+		ConstantOrSymbol (Symbol s)	: symbol	(s) {}
+	} m_value;
+};
+
+struct Value
+{
+	Value (ValueType valueType_, const FloatScalar& rangeMin_, const FloatScalar& rangeMax_)
+		: valueType	(valueType_)
+		, rangeMin	(rangeMin_)
+		, rangeMax	(rangeMax_)
+	{
+	}
+
+	ValueType		valueType;
+	FloatScalar		rangeMin;
+	FloatScalar		rangeMax;
+};
+
+enum OperationType
+{
+	FUNCTION = 0,
+	OPERATOR,
+	SIDE_EFFECT_OPERATOR // Test the side-effect (as opposed to the result) of a side-effect operator.
+};
+
+struct BuiltinFuncInfo
+{
+	BuiltinFuncInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_,
+					 Value input0_, Value input1_, Value input2_,
+					 const FloatScalar& resultScale_, const FloatScalar& resultBias_,
+					 deUint32 precisionMask_,
+					 ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_,
+					 OperationType type_=FUNCTION, bool isUnaryPrefix_=true)
+		: caseName			(caseName_)
+		, shaderFuncName	(shaderFuncName_)
+		, outValue			(outValue_)
+		, input0			(input0_)
+		, input1			(input1_)
+		, input2			(input2_)
+		, resultScale		(resultScale_)
+		, resultBias		(resultBias_)
+		, referenceScale	(resultScale_)
+		, referenceBias		(resultBias_)
+		, precisionMask		(precisionMask_)
+		, evalFuncScalar	(evalFuncScalar_)
+		, evalFuncVec2		(evalFuncVec2_)
+		, evalFuncVec3		(evalFuncVec3_)
+		, evalFuncVec4		(evalFuncVec4_)
+		, type				(type_)
+		, isUnaryPrefix		(isUnaryPrefix_)
+	{
+	}
+
+	BuiltinFuncInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_,
+					 Value input0_, Value input1_, Value input2_,
+					 const FloatScalar& resultScale_, const FloatScalar& resultBias_, const FloatScalar& referenceScale_, const FloatScalar& referenceBias_,
+					 deUint32 precisionMask_,
+					 ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_,
+					 OperationType type_=FUNCTION, bool isUnaryPrefix_=true)
+		: caseName			(caseName_)
+		, shaderFuncName	(shaderFuncName_)
+		, outValue			(outValue_)
+		, input0			(input0_)
+		, input1			(input1_)
+		, input2			(input2_)
+		, resultScale		(resultScale_)
+		, resultBias		(resultBias_)
+		, referenceScale	(referenceScale_)
+		, referenceBias		(referenceBias_)
+		, precisionMask		(precisionMask_)
+		, evalFuncScalar	(evalFuncScalar_)
+		, evalFuncVec2		(evalFuncVec2_)
+		, evalFuncVec3		(evalFuncVec3_)
+		, evalFuncVec4		(evalFuncVec4_)
+		, type				(type_)
+		, isUnaryPrefix		(isUnaryPrefix_)
+	{
+	}
+
+	const char*		caseName;			//!< Name of case.
+	const char*		shaderFuncName;		//!< Name in shading language.
+	ValueType		outValue;
+	Value			input0;
+	Value			input1;
+	Value			input2;
+	FloatScalar		resultScale;
+	FloatScalar		resultBias;
+	FloatScalar		referenceScale;
+	FloatScalar		referenceBias;
+	deUint32		precisionMask;
+	ShaderEvalFunc	evalFuncScalar;
+	ShaderEvalFunc	evalFuncVec2;
+	ShaderEvalFunc	evalFuncVec3;
+	ShaderEvalFunc	evalFuncVec4;
+	OperationType	type;
+	bool			isUnaryPrefix;		//!< Whether a unary operator is a prefix operator; redundant unless unary.
+};
+
+static inline BuiltinFuncInfo BuiltinOperInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_, Value input0_, Value input1_, Value input2_, const FloatScalar& resultScale_, const FloatScalar& resultBias_, deUint32 precisionMask_, ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_)
+{
+	return BuiltinFuncInfo(caseName_, shaderFuncName_, outValue_, input0_, input1_, input2_, resultScale_, resultBias_, resultScale_, resultBias_, precisionMask_, evalFuncScalar_, evalFuncVec2_, evalFuncVec3_, evalFuncVec4_, OPERATOR);
+}
+
+static inline BuiltinFuncInfo BuiltinOperInfoSeparateRefScaleBias (const char* caseName_, const char* shaderFuncName_, ValueType outValue_, Value input0_, Value input1_, Value input2_, const FloatScalar& resultScale_, const FloatScalar& resultBias_, deUint32 precisionMask_, ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_, const FloatScalar& referenceScale_, const FloatScalar& referenceBias_)
+{
+	return BuiltinFuncInfo(caseName_, shaderFuncName_, outValue_, input0_, input1_, input2_, resultScale_, resultBias_, referenceScale_, referenceBias_, precisionMask_, evalFuncScalar_, evalFuncVec2_, evalFuncVec3_, evalFuncVec4_, OPERATOR);
+}
+
+// For postfix (unary) operators.
+static inline BuiltinFuncInfo BuiltinPostOperInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_, Value input0_, Value input1_, Value input2_, const FloatScalar& resultScale_, const FloatScalar& resultBias_, deUint32 precisionMask_, ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_)
+{
+	return BuiltinFuncInfo(caseName_, shaderFuncName_, outValue_, input0_, input1_, input2_, resultScale_, resultBias_, resultScale_, resultBias_, precisionMask_, evalFuncScalar_, evalFuncVec2_, evalFuncVec3_, evalFuncVec4_, OPERATOR, false);
+}
+
+static inline BuiltinFuncInfo BuiltinSideEffOperInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_, Value input0_, Value input1_, Value input2_, const FloatScalar& resultScale_, const FloatScalar& resultBias_, deUint32 precisionMask_, ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_)
+{
+	return BuiltinFuncInfo(caseName_, shaderFuncName_, outValue_, input0_, input1_, input2_, resultScale_, resultBias_, resultScale_, resultBias_, precisionMask_, evalFuncScalar_, evalFuncVec2_, evalFuncVec3_, evalFuncVec4_, SIDE_EFFECT_OPERATOR);
+}
+
+// For postfix (unary) operators, testing side-effect.
+static inline BuiltinFuncInfo BuiltinPostSideEffOperInfo (const char* caseName_, const char* shaderFuncName_, ValueType outValue_, Value input0_, Value input1_, Value input2_, const FloatScalar& resultScale_, const FloatScalar& resultBias_, deUint32 precisionMask_, ShaderEvalFunc evalFuncScalar_, ShaderEvalFunc evalFuncVec2_, ShaderEvalFunc evalFuncVec3_, ShaderEvalFunc evalFuncVec4_)
+{
+	return BuiltinFuncInfo(caseName_, shaderFuncName_, outValue_, input0_, input1_, input2_, resultScale_, resultBias_, resultScale_, resultBias_, precisionMask_, evalFuncScalar_, evalFuncVec2_, evalFuncVec3_, evalFuncVec4_, SIDE_EFFECT_OPERATOR, false);
+}
+
+// BuiltinFuncGroup
+
+struct BuiltinFuncGroup
+{
+						BuiltinFuncGroup	(const char* name_, const char* description_) : name(name_), description(description_) {}
+	BuiltinFuncGroup&	operator<<			(const BuiltinFuncInfo& info) { funcInfos.push_back(info); return *this; }
+
+	const char*						name;
+	const char*						description;
+	std::vector<BuiltinFuncInfo>	funcInfos;
+};
+
+static const char* s_inSwizzles[MAX_INPUTS][4] =
+{
+	{ "z", "wy", "zxy", "yzwx" },
+	{ "x", "yx", "yzx", "wzyx" },
+	{ "y", "zy", "wyz", "xwzy" }
+};
+
+static const char* s_outSwizzles[]	= { "x", "yz", "xyz", "xyzw" };
+
+static const BVec4 s_outSwizzleChannelMasks[] =
+{
+	BVec4(true,  false, false, false),
+	BVec4(false, true,  true,  false),
+	BVec4(true,  true,  true,  false),
+	BVec4(true,  true,  true,  true )
+};
+
+// OperatorShaderEvaluator
+
+class OperatorShaderEvaluator : public ShaderEvaluator
+{
+public:
+	OperatorShaderEvaluator (const glw::Functions& gl, ShaderType shaderType, ShaderEvalFunc evalFunc, const FloatScalar& scale, const FloatScalar& bias, int resultScalarSize)
+		: m_gl							(gl)
+		, m_shaderType					(shaderType)
+		, m_evalFunc					(evalFunc)
+		, m_scale						(scale)
+		, m_bias						(bias)
+		, m_resultScalarSize			(resultScalarSize)
+		, m_areScaleAndBiasEvaluated	(false)
+		, m_evaluatedScale				(-1.0f)
+		, m_evaluatedBias				(-1.0f)
+	{
+		DE_ASSERT(de::inRange(resultScalarSize, 1, 4));
+	}
+
+	virtual ~OperatorShaderEvaluator (void)
+	{
+	}
+
+	virtual void evaluate (ShaderEvalContext& ctx)
+	{
+		m_evalFunc(ctx);
+
+		if (!m_areScaleAndBiasEvaluated)
+		{
+			m_evaluatedScale	= m_scale.getValue(m_gl, m_shaderType);
+			m_evaluatedBias		= m_bias.getValue(m_gl, m_shaderType);
+			m_areScaleAndBiasEvaluated = true;
+		}
+
+		for (int i = 0; i < 4; i++)
+			if (s_outSwizzleChannelMasks[m_resultScalarSize-1][i])
+				ctx.color[i] = ctx.color[i] * m_evaluatedScale + m_evaluatedBias;
+	}
+
+private:
+	const glw::Functions&	m_gl;
+	ShaderType				m_shaderType;
+	ShaderEvalFunc			m_evalFunc;
+	FloatScalar				m_scale;
+	FloatScalar				m_bias;
+	int						m_resultScalarSize;
+
+	bool					m_areScaleAndBiasEvaluated;
+	float					m_evaluatedScale;
+	float					m_evaluatedBias;
+};
+
+// Concrete value.
+
+struct ShaderValue
+{
+	ShaderValue (DataType type_, const FloatScalar& rangeMin_, const FloatScalar& rangeMax_)
+		: type		(type_)
+		, rangeMin	(rangeMin_)
+		, rangeMax	(rangeMax_)
+	{
+	}
+
+	ShaderValue (void)
+		: type		(TYPE_LAST)
+		, rangeMin	(0.0f)
+		, rangeMax	(0.0f)
+	{
+	}
+
+	DataType		type;
+	FloatScalar		rangeMin;
+	FloatScalar		rangeMax;
+};
+
+struct ShaderDataSpec
+{
+	ShaderDataSpec (void)
+		: resultScale		(1.0f)
+		, resultBias		(0.0f)
+		, referenceScale	(1.0f)
+		, referenceBias		(0.0f)
+		, precision			(PRECISION_LAST)
+		, output			(TYPE_LAST)
+		, numInputs			(0)
+	{
+	}
+
+	FloatScalar		resultScale;
+	FloatScalar		resultBias;
+	FloatScalar		referenceScale;
+	FloatScalar		referenceBias;
+	Precision		precision;
+	DataType		output;
+	int				numInputs;
+	ShaderValue		inputs[MAX_INPUTS];
+};
+
+// ShaderOperatorCase
+
+class ShaderOperatorCase : public ShaderRenderCase
+{
+public:
+								ShaderOperatorCase		(Context& context, const char* caseName, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, const string& shaderOp, const ShaderDataSpec& spec);
+	virtual						~ShaderOperatorCase		(void);
+
+protected:
+	void						setupShaderData			(void);
+
+private:
+								ShaderOperatorCase		(const ShaderOperatorCase&);	// not allowed!
+	ShaderOperatorCase&			operator=				(const ShaderOperatorCase&);	// not allowed!
+
+	ShaderDataSpec				m_spec;
+	string						m_shaderOp;
+	OperatorShaderEvaluator		m_evaluator;
+};
+
+ShaderOperatorCase::ShaderOperatorCase (Context& context, const char* caseName, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, const string& shaderOp, const ShaderDataSpec& spec)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), caseName, description, isVertexCase, m_evaluator)
+	, m_spec			(spec)
+	, m_shaderOp		(shaderOp)
+	, m_evaluator		(m_renderCtx.getFunctions(), isVertexCase ? SHADERTYPE_VERTEX : SHADERTYPE_FRAGMENT, evalFunc, spec.referenceScale, spec.referenceBias, getDataTypeScalarSize(spec.output))
+{
+}
+
+void ShaderOperatorCase::setupShaderData (void)
+{
+	ShaderType		shaderType	= m_isVertexCase ? SHADERTYPE_VERTEX : SHADERTYPE_FRAGMENT;
+	const char*		precision	= m_spec.precision != PRECISION_LAST ? getPrecisionName(m_spec.precision) : DE_NULL;
+	const char*		inputPrecision[MAX_INPUTS];
+
+	ostringstream	vtx;
+	ostringstream	frag;
+	ostringstream&	op			= m_isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n";
+	frag << "#version 300 es\n";
+
+	// Compute precision for inputs.
+	for (int i = 0; i < m_spec.numInputs; i++)
+	{
+		bool		isBoolVal	= de::inRange<int>(m_spec.inputs[i].type, TYPE_BOOL, TYPE_BOOL_VEC4);
+		bool		isIntVal	= de::inRange<int>(m_spec.inputs[i].type, TYPE_INT, TYPE_INT_VEC4);
+		bool		isUintVal	= de::inRange<int>(m_spec.inputs[i].type, TYPE_UINT, TYPE_UINT_VEC4);
+		// \note Mediump interpolators are used for booleans, and highp for integers.
+		Precision	prec		= isBoolVal	? PRECISION_MEDIUMP
+								: isIntVal || isUintVal ? PRECISION_HIGHP
+								: m_spec.precision;
+		inputPrecision[i] = getPrecisionName(prec);
+	}
+
+	// Attributes.
+	vtx << "in highp vec4 a_position;\n";
+	for (int i = 0; i < m_spec.numInputs; i++)
+		vtx << "in " << inputPrecision[i] << " vec4 a_in" << i << ";\n";
+
+	// Color output.
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (m_isVertexCase)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		for (int i = 0; i < m_spec.numInputs; i++)
+		{
+			vtx << "out " << inputPrecision[i] << " vec4 v_in" << i << ";\n";
+			frag << "in " << inputPrecision[i] << " vec4 v_in" << i << ";\n";
+		}
+	}
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Expression inputs.
+	string prefix = m_isVertexCase ? "a_" : "v_";
+	for (int i = 0; i < m_spec.numInputs; i++)
+	{
+		DataType		inType		= m_spec.inputs[i].type;
+		int				inSize		= getDataTypeScalarSize(inType);
+		bool			isInt		= de::inRange<int>(inType, TYPE_INT, TYPE_INT_VEC4);
+		bool			isUint		= de::inRange<int>(inType, TYPE_UINT, TYPE_UINT_VEC4);
+		bool			isBool		= de::inRange<int>(inType, TYPE_BOOL, TYPE_BOOL_VEC4);
+		const char*		typeName	= getDataTypeName(inType);
+		const char*		swizzle		= s_inSwizzles[i][inSize-1];
+
+		op << "\t";
+		if (precision && !isBool) op << precision << " ";
+
+		op << typeName << " in" << i << " = ";
+
+		if (isBool)
+		{
+			if (inSize == 1)	op << "(";
+			else				op << "greaterThan(";
+		}
+		else if (isInt || isUint)
+			op << typeName << "(";
+
+		op << prefix << "in" << i << "." << swizzle;
+
+		if (isBool)
+		{
+			if (inSize == 1)	op << " > 0.0)";
+			else				op << ", vec" << inSize << "(0.0))";
+		}
+		else if (isInt || isUint)
+			op << ")";
+
+		op << ";\n";
+	}
+
+	// Result variable.
+	{
+		const char* outTypeName = getDataTypeName(m_spec.output);
+		bool		isBoolOut	= de::inRange<int>(m_spec.output, TYPE_BOOL, TYPE_BOOL_VEC4);
+
+		op << "\t";
+		if (precision && !isBoolOut) op << precision << " ";
+		op << outTypeName << " res = " << outTypeName << "(0.0);\n\n";
+	}
+
+	// Expression.
+	op << "\t" << m_shaderOp << "\n\n";
+
+	// Convert to color.
+	bool	isResFloatVec	= de::inRange<int>(m_spec.output, TYPE_FLOAT, TYPE_FLOAT_VEC4);
+	int		outScalarSize	= getDataTypeScalarSize(m_spec.output);
+
+	op << "\thighp vec4 color = vec4(0.0, 0.0, 0.0, 1.0);\n";
+	op << "\tcolor." << s_outSwizzles[outScalarSize-1] << " = ";
+
+	if (!isResFloatVec && outScalarSize == 1)
+		op << "float(res)";
+	else if (!isResFloatVec)
+		op << "vec" << outScalarSize << "(res)";
+	else
+		op << "res";
+
+	op << ";\n";
+
+	// Scale & bias.
+	float	resultScale		= m_spec.resultScale.getValue(m_renderCtx.getFunctions(), shaderType);
+	float	resultBias		= m_spec.resultBias.getValue(m_renderCtx.getFunctions(), shaderType);
+	if ((resultScale != 1.0f) || (resultBias != 0.0f))
+	{
+		op << "\tcolor = color";
+		if (resultScale != 1.0f) op << " * " << twoValuedVec4(de::toString(resultScale),		"1.0", s_outSwizzleChannelMasks[outScalarSize-1]);
+		if (resultBias != 0.0f)  op << " + " << twoValuedVec4(de::floatToString(resultBias, 2),	"0.0", s_outSwizzleChannelMasks[outScalarSize-1]);
+		op << ";\n";
+	}
+
+	// ..
+	if (m_isVertexCase)
+	{
+		vtx << "	v_color = color;\n";
+		frag << "	o_color = v_color;\n";
+	}
+	else
+	{
+		for (int i = 0; i < m_spec.numInputs; i++)
+		vtx << "	v_in" << i << " = a_in" << i << ";\n";
+		frag << "	o_color = color;\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource = vtx.str();
+	m_fragShaderSource = frag.str();
+
+	// Setup the user attributes.
+	m_userAttribTransforms.resize(m_spec.numInputs);
+	for (int inputNdx = 0; inputNdx < m_spec.numInputs; inputNdx++)
+	{
+		const ShaderValue& v = m_spec.inputs[inputNdx];
+		DE_ASSERT(v.type != TYPE_LAST);
+
+		float rangeMin	= v.rangeMin.getValue(m_renderCtx.getFunctions(), shaderType);
+		float rangeMax	= v.rangeMax.getValue(m_renderCtx.getFunctions(), shaderType);
+		float scale		= rangeMax - rangeMin;
+		float minBias	= rangeMin;
+		float maxBias	= rangeMax;
+		Mat4  attribMatrix;
+
+		for (int rowNdx = 0; rowNdx < 4; rowNdx++)
+		{
+			Vec4 row;
+
+			switch ((rowNdx + inputNdx) % 4)
+			{
+				case 0:	row = Vec4(scale, 0.0f, 0.0f, minBias);		break;
+				case 1:	row = Vec4(0.0f, scale, 0.0f, minBias);		break;
+				case 2:	row = Vec4(-scale, 0.0f, 0.0f, maxBias);	break;
+				case 3:	row = Vec4(0.0f, -scale, 0.0f, maxBias);	break;
+				default: DE_ASSERT(false);
+			}
+
+			attribMatrix.setRow(rowNdx, row);
+		}
+
+		m_userAttribTransforms[inputNdx] = attribMatrix;
+	}
+}
+
+ShaderOperatorCase::~ShaderOperatorCase (void)
+{
+}
+
+// ShaderOperatorTests.
+
+ShaderOperatorTests::ShaderOperatorTests(Context& context)
+	: TestCaseGroup(context, "operator", "Operator tests.")
+{
+}
+
+ShaderOperatorTests::~ShaderOperatorTests (void)
+{
+}
+
+// Vector math functions.
+template<typename T> inline T nop (T f) { return f; }
+
+template <typename T, int Size>
+Vector<T, Size> nop (const Vector<T, Size>& v) { return v; }
+
+#define DECLARE_UNARY_GENTYPE_FUNCS(FUNC_NAME)																			\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].z()); }					\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1)); }		\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1)); }	\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0)); }
+
+#define DECLARE_BINARY_GENTYPE_FUNCS(FUNC_NAME)																											\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].z(),                 c.in[1].x()); }					\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0)); }			\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0)); }		\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0)); }
+
+#define DECLARE_TERNARY_GENTYPE_FUNCS(FUNC_NAME)																																	\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].z(),                 c.in[1].x(),                 c.in[2].y()); }					\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0),       c.in[2].swizzle(2, 1)); }			\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0),    c.in[2].swizzle(3, 1, 2)); }		\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0), c.in[2].swizzle(0, 3, 2, 1)); }
+
+#define DECLARE_UNARY_SCALAR_GENTYPE_FUNCS(FUNC_NAME)																	\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].z()); }					\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(3, 1)); }		\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1)); }	\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0)); }
+
+#define DECLARE_BINARY_SCALAR_GENTYPE_FUNCS(FUNC_NAME)																									\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].z(),                 c.in[1].x()); }					\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0)); }			\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0)); }		\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color.x()	= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0)); }
+
+#define DECLARE_BINARY_BOOL_FUNCS(FUNC_NAME)																		\
+	void eval_##FUNC_NAME##_bool	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(c.in[0].z() > 0.0f, c.in[1].x() > 0.0f); }
+
+#define DECLARE_UNARY_BOOL_GENTYPE_FUNCS(FUNC_NAME)																											\
+	void eval_##FUNC_NAME##_bool	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(c.in[0].z() > 0.0f); }										\
+	void eval_##FUNC_NAME##_bvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f))).asFloat(); }		\
+	void eval_##FUNC_NAME##_bvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f))).asFloat(); }		\
+	void eval_##FUNC_NAME##_bvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f))).asFloat(); }
+
+#define DECLARE_TERNARY_BOOL_GENTYPE_FUNCS(FUNC_NAME)																																																					\
+	void eval_##FUNC_NAME##_bool	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(c.in[0].z() > 0.0f,                            c.in[1].x() > 0.0f,                                   c.in[2].y() > 0.0f); }												\
+	void eval_##FUNC_NAME##_bvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f)),       greaterThan(c.in[1].swizzle(1, 0), Vec2(0.0f)),       greaterThan(c.in[2].swizzle(2, 1), Vec2(0.0f))).asFloat(); }		\
+	void eval_##FUNC_NAME##_bvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f)),    greaterThan(c.in[1].swizzle(1, 2, 0), Vec3(0.0f)),    greaterThan(c.in[2].swizzle(3, 1, 2), Vec3(0.0f))).asFloat(); }		\
+	void eval_##FUNC_NAME##_bvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f)), greaterThan(c.in[1].swizzle(3, 2, 1, 0), Vec4(0.0f)), greaterThan(c.in[2].swizzle(0, 3, 2, 1), Vec4(0.0f))).asFloat(); }
+
+#define DECLARE_UNARY_INT_GENTYPE_FUNCS(FUNC_NAME)																						\
+	void eval_##FUNC_NAME##_int		(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME((int)c.in[0].z()); }						\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asInt()).asFloat(); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asInt()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asInt()).asFloat(); }
+
+#define DECLARE_BINARY_INT_GENTYPE_FUNCS(FUNC_NAME)																																\
+	void eval_##FUNC_NAME##_int		(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME((int)c.in[0].z(),				(int)c.in[1].x()); }							\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asInt(),			c.in[1].swizzle(1, 0).asInt()).asFloat(); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asInt(),		c.in[1].swizzle(1, 2, 0).asInt()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asInt(),	c.in[1].swizzle(3, 2, 1, 0).asInt()).asFloat(); }
+
+#define DECLARE_UNARY_UINT_GENTYPE_FUNCS(FUNC_NAME)																						\
+	void eval_##FUNC_NAME##_uint	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME((deUint32)c.in[0].z()); }				\
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asUint()).asFloat(); }
+
+#define DECLARE_BINARY_UINT_GENTYPE_FUNCS(FUNC_NAME)																															\
+	void eval_##FUNC_NAME##_uint	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME((deUint32)c.in[0].z(),			(deUint32)c.in[1].x()); }						\
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asUint(),			c.in[1].swizzle(1, 0).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asUint(),		c.in[1].swizzle(1, 2, 0).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asUint(),	c.in[1].swizzle(3, 2, 1, 0).asUint()).asFloat(); }
+
+#define DECLARE_TERNARY_INT_GENTYPE_FUNCS(FUNC_NAME)																																								\
+	void eval_##FUNC_NAME##_int		(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME((int)c.in[0].z(),				(int)c.in[1].x(),					(int)c.in[2].y()); }							\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asInt(),			c.in[1].swizzle(1, 0).asInt(),       c.in[2].swizzle(2, 1).asInt()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asInt(),		c.in[1].swizzle(1, 2, 0).asInt(),    c.in[2].swizzle(3, 1, 2).asInt()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asInt(),	c.in[1].swizzle(3, 2, 1, 0).asInt(), c.in[2].swizzle(0, 3, 2, 1).asInt()).asFloat(); }
+
+#define DECLARE_TERNARY_UINT_GENTYPE_FUNCS(FUNC_NAME)																																									\
+	void eval_##FUNC_NAME##_uint	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME((deUint32)c.in[0].z(),			(deUint32)c.in[1].x(),					(deUint32)c.in[2].y()); }						\
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asUint(),			c.in[1].swizzle(1, 0).asUint(),			c.in[2].swizzle(2, 1).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asUint(),		c.in[1].swizzle(1, 2, 0).asUint(),		c.in[2].swizzle(3, 1, 2).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asUint(),	c.in[1].swizzle(3, 2, 1, 0).asUint(),	c.in[2].swizzle(0, 3, 2, 1).asUint()).asFloat(); }
+
+#define DECLARE_VEC_FLOAT_FUNCS(FUNC_NAME)																								\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),			c.in[1].x()); } \
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),		c.in[1].x()); } \
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0),	c.in[1].x()); }
+
+#define DECLARE_VEC_FLOAT_FLOAT_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),			c.in[1].x(), c.in[2].y()); } \
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),		c.in[1].x(), c.in[2].y()); } \
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0),	c.in[1].x(), c.in[2].y()); }
+
+#define DECLARE_VEC_VEC_FLOAT_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),			c.in[1].swizzle(1, 0),			c.in[2].y()); } \
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),		c.in[1].swizzle(1, 2, 0),		c.in[2].y()); } \
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0),	c.in[1].swizzle(3, 2, 1, 0),	c.in[2].y()); }
+
+#define DECLARE_FLOAT_FLOAT_VEC_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].z(), c.in[1].x(), c.in[2].swizzle(2, 1)); }			\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].z(), c.in[1].x(), c.in[2].swizzle(3, 1, 2)); }		\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].z(), c.in[1].x(), c.in[2].swizzle(0, 3, 2, 1)); }
+
+#define DECLARE_FLOAT_VEC_FUNCS(FUNC_NAME)																												\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].z(),					c.in[1].swizzle(1, 0)); }		\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].z(),					c.in[1].swizzle(1, 2, 0)); }	\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].z(),					c.in[1].swizzle(3, 2, 1, 0)); }
+
+#define DECLARE_IVEC_INT_FUNCS(FUNC_NAME)																														\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asInt(),			(int)c.in[1].x()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asInt(),		(int)c.in[1].x()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asInt(),	(int)c.in[1].x()).asFloat(); }
+
+#define DECLARE_IVEC_INT_INT_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asInt(),			(int)c.in[1].x(), (int)c.in[2].y()).asFloat(); } \
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asInt(),		(int)c.in[1].x(), (int)c.in[2].y()).asFloat(); } \
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asInt(),	(int)c.in[1].x(), (int)c.in[2].y()).asFloat(); }
+
+#define DECLARE_INT_IVEC_FUNCS(FUNC_NAME)																																	\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME((int)c.in[0].z(),					c.in[1].swizzle(1, 0).asInt()).asFloat(); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME((int)c.in[0].z(),					c.in[1].swizzle(1, 2, 0).asInt()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME((int)c.in[0].z(),					c.in[1].swizzle(3, 2, 1, 0).asInt()).asFloat(); }
+
+#define DECLARE_UVEC_UINT_FUNCS(FUNC_NAME)																															\
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asUint(),			(deUint32)c.in[1].x()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asUint(),		(deUint32)c.in[1].x()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asUint(),	(deUint32)c.in[1].x()).asFloat(); }
+
+#define DECLARE_UVEC_UINT_UINT_FUNCS(FUNC_NAME) \
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asUint(),			(deUint32)c.in[1].x(), (deUint32)c.in[2].y()).asFloat(); } \
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asUint(),		(deUint32)c.in[1].x(), (deUint32)c.in[2].y()).asFloat(); } \
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asUint(),	(deUint32)c.in[1].x(), (deUint32)c.in[2].y()).asFloat(); }
+
+#define DECLARE_UINT_UVEC_FUNCS(FUNC_NAME)																																		\
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME((deUint32)c.in[0].z(),					c.in[1].swizzle(1, 0).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME((deUint32)c.in[0].z(),					c.in[1].swizzle(1, 2, 0).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME((deUint32)c.in[0].z(),					c.in[1].swizzle(3, 2, 1, 0).asUint()).asFloat(); }
+
+#define DECLARE_BINARY_INT_VEC_FUNCS(FUNC_NAME)																																	\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asInt(),			c.in[1].swizzle(1, 0).asInt()).asFloat(); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asInt(),		c.in[1].swizzle(1, 2, 0).asInt()).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asInt(),	c.in[1].swizzle(3, 2, 1, 0).asInt()).asFloat(); }
+
+#define DECLARE_BINARY_UINT_VEC_FUNCS(FUNC_NAME)																																\
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asUint(),			c.in[1].swizzle(1, 0).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asUint(),		c.in[1].swizzle(1, 2, 0).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asUint(),	c.in[1].swizzle(3, 2, 1, 0).asUint()).asFloat(); }
+
+#define DECLARE_UINT_INT_GENTYPE_FUNCS(FUNC_NAME)																																\
+	void eval_##FUNC_NAME##_uint	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME((deUint32)c.in[0].z(),			(int)c.in[1].x()); }							\
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asUint(),			c.in[1].swizzle(1, 0).asInt()).asFloat(); }		\
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asUint(),		c.in[1].swizzle(1, 2, 0).asInt()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asUint(),	c.in[1].swizzle(3, 2, 1, 0).asInt()).asFloat(); }
+
+#define DECLARE_UVEC_INT_FUNCS(FUNC_NAME)																														\
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asUint(),			(int)c.in[1].x()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asUint(),		(int)c.in[1].x()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asUint(),	(int)c.in[1].x()).asFloat(); }
+
+
+// Operators.
+
+DECLARE_UNARY_GENTYPE_FUNCS(nop)
+DECLARE_UNARY_GENTYPE_FUNCS(negate)
+DECLARE_UNARY_GENTYPE_FUNCS(addOne)
+DECLARE_UNARY_GENTYPE_FUNCS(subOne)
+DECLARE_BINARY_GENTYPE_FUNCS(add)
+DECLARE_BINARY_GENTYPE_FUNCS(sub)
+DECLARE_BINARY_GENTYPE_FUNCS(mul)
+DECLARE_BINARY_GENTYPE_FUNCS(div)
+
+void eval_selection_float	(ShaderEvalContext& c) { c.color.x()	= selection(c.in[0].z() > 0.0f,		c.in[1].x(),					c.in[2].y()); }
+void eval_selection_vec2	(ShaderEvalContext& c) { c.color.yz()	= selection(c.in[0].z() > 0.0f,		c.in[1].swizzle(1, 0),			c.in[2].swizzle(2, 1)); }
+void eval_selection_vec3	(ShaderEvalContext& c) { c.color.xyz()	= selection(c.in[0].z() > 0.0f,		c.in[1].swizzle(1, 2, 0),		c.in[2].swizzle(3, 1, 2)); }
+void eval_selection_vec4	(ShaderEvalContext& c) { c.color		= selection(c.in[0].z() > 0.0f,		c.in[1].swizzle(3, 2, 1, 0),	c.in[2].swizzle(0, 3, 2, 1)); }
+
+DECLARE_UNARY_INT_GENTYPE_FUNCS(nop)
+DECLARE_UNARY_INT_GENTYPE_FUNCS(negate)
+DECLARE_UNARY_INT_GENTYPE_FUNCS(addOne)
+DECLARE_UNARY_INT_GENTYPE_FUNCS(subOne)
+DECLARE_UNARY_INT_GENTYPE_FUNCS(bitwiseNot)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(add)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(sub)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(mul)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(div)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(mod)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(bitwiseAnd)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(bitwiseOr)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(bitwiseXor)
+
+void eval_leftShift_int		(ShaderEvalContext& c) { c.color.x()	= (float)leftShift((int)c.in[0].z(),				(int)c.in[1].x()); }
+DECLARE_BINARY_INT_VEC_FUNCS(leftShift)
+void eval_rightShift_int	(ShaderEvalContext& c) { c.color.x()	= (float)rightShift((int)c.in[0].z(),				(int)c.in[1].x()); }
+DECLARE_BINARY_INT_VEC_FUNCS(rightShift)
+DECLARE_IVEC_INT_FUNCS(leftShiftVecScalar)
+DECLARE_IVEC_INT_FUNCS(rightShiftVecScalar)
+
+void eval_selection_int		(ShaderEvalContext& c) { c.color.x()	= (float)selection(c.in[0].z() > 0.0f,	(int)c.in[1].x(),						(int)c.in[2].y()); }
+void eval_selection_ivec2	(ShaderEvalContext& c) { c.color.yz()	= selection(c.in[0].z() > 0.0f,			c.in[1].swizzle(1, 0).asInt(),			c.in[2].swizzle(2, 1).asInt()).asFloat(); }
+void eval_selection_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= selection(c.in[0].z() > 0.0f,			c.in[1].swizzle(1, 2, 0).asInt(),		c.in[2].swizzle(3, 1, 2).asInt()).asFloat(); }
+void eval_selection_ivec4	(ShaderEvalContext& c) { c.color		= selection(c.in[0].z() > 0.0f,			c.in[1].swizzle(3, 2, 1, 0).asInt(),	c.in[2].swizzle(0, 3, 2, 1).asInt()).asFloat(); }
+
+DECLARE_UNARY_UINT_GENTYPE_FUNCS(nop)
+DECLARE_UNARY_UINT_GENTYPE_FUNCS(negate)
+DECLARE_UNARY_UINT_GENTYPE_FUNCS(bitwiseNot)
+DECLARE_UNARY_UINT_GENTYPE_FUNCS(addOne)
+DECLARE_UNARY_UINT_GENTYPE_FUNCS(subOne)
+DECLARE_BINARY_UINT_GENTYPE_FUNCS(add)
+DECLARE_BINARY_UINT_GENTYPE_FUNCS(sub)
+DECLARE_BINARY_UINT_GENTYPE_FUNCS(mul)
+DECLARE_BINARY_UINT_GENTYPE_FUNCS(div)
+DECLARE_BINARY_UINT_GENTYPE_FUNCS(mod)
+DECLARE_BINARY_UINT_GENTYPE_FUNCS(bitwiseAnd)
+DECLARE_BINARY_UINT_GENTYPE_FUNCS(bitwiseOr)
+DECLARE_BINARY_UINT_GENTYPE_FUNCS(bitwiseXor)
+
+DECLARE_UINT_INT_GENTYPE_FUNCS(leftShift)
+DECLARE_UINT_INT_GENTYPE_FUNCS(rightShift)
+DECLARE_UVEC_INT_FUNCS(leftShiftVecScalar)
+DECLARE_UVEC_INT_FUNCS(rightShiftVecScalar)
+
+void eval_selection_uint	(ShaderEvalContext& c) { c.color.x()	= (float)selection(c.in[0].z() > 0.0f,	(deUint32)c.in[1].x(),					(deUint32)c.in[2].y()); }
+void eval_selection_uvec2	(ShaderEvalContext& c) { c.color.yz()	= selection(c.in[0].z() > 0.0f,			c.in[1].swizzle(1, 0).asUint(),			c.in[2].swizzle(2, 1).asUint()).asFloat(); }
+void eval_selection_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= selection(c.in[0].z() > 0.0f,			c.in[1].swizzle(1, 2, 0).asUint(),		c.in[2].swizzle(3, 1, 2).asUint()).asFloat(); }
+void eval_selection_uvec4	(ShaderEvalContext& c) { c.color		= selection(c.in[0].z() > 0.0f,			c.in[1].swizzle(3, 2, 1, 0).asUint(),	c.in[2].swizzle(0, 3, 2, 1).asUint()).asFloat(); }
+
+DECLARE_UNARY_BOOL_GENTYPE_FUNCS(boolNot)
+DECLARE_BINARY_BOOL_FUNCS(logicalAnd)
+DECLARE_BINARY_BOOL_FUNCS(logicalOr)
+DECLARE_BINARY_BOOL_FUNCS(logicalXor)
+
+void eval_selection_bool	(ShaderEvalContext& c) { c.color.x()	= (float)selection(c.in[0].z() > 0.0f,	c.in[1].x() > 0.0f,														c.in[2].y() > 0.0f); }
+void eval_selection_bvec2	(ShaderEvalContext& c) { c.color.yz()	= selection(c.in[0].z() > 0.0f,			greaterThan(c.in[1].swizzle(1, 0), Vec2(0.0f, 0.0f)),					greaterThan(c.in[2].swizzle(2, 1), Vec2(0.0f, 0.0f))).asFloat(); }
+void eval_selection_bvec3	(ShaderEvalContext& c) { c.color.xyz()	= selection(c.in[0].z() > 0.0f,			greaterThan(c.in[1].swizzle(1, 2, 0), Vec3(0.0f, 0.0f, 0.0f)),			greaterThan(c.in[2].swizzle(3, 1, 2), Vec3(0.0f, 0.0f, 0.0f))).asFloat(); }
+void eval_selection_bvec4	(ShaderEvalContext& c) { c.color		= selection(c.in[0].z() > 0.0f,			greaterThan(c.in[1].swizzle(3, 2, 1, 0), Vec4(0.0f, 0.0f, 0.0f, 0.0f)),	greaterThan(c.in[2].swizzle(0, 3, 2, 1), Vec4(0.0f, 0.0f, 0.0f, 0.0f))).asFloat(); }
+
+DECLARE_VEC_FLOAT_FUNCS(addVecScalar)
+DECLARE_VEC_FLOAT_FUNCS(subVecScalar)
+DECLARE_VEC_FLOAT_FUNCS(mulVecScalar)
+DECLARE_VEC_FLOAT_FUNCS(divVecScalar)
+
+DECLARE_FLOAT_VEC_FUNCS(addScalarVec)
+DECLARE_FLOAT_VEC_FUNCS(subScalarVec)
+DECLARE_FLOAT_VEC_FUNCS(mulScalarVec)
+DECLARE_FLOAT_VEC_FUNCS(divScalarVec)
+
+DECLARE_IVEC_INT_FUNCS(addVecScalar)
+DECLARE_IVEC_INT_FUNCS(subVecScalar)
+DECLARE_IVEC_INT_FUNCS(mulVecScalar)
+DECLARE_IVEC_INT_FUNCS(divVecScalar)
+DECLARE_IVEC_INT_FUNCS(modVecScalar)
+DECLARE_IVEC_INT_FUNCS(bitwiseAndVecScalar)
+DECLARE_IVEC_INT_FUNCS(bitwiseOrVecScalar)
+DECLARE_IVEC_INT_FUNCS(bitwiseXorVecScalar)
+
+DECLARE_INT_IVEC_FUNCS(addScalarVec)
+DECLARE_INT_IVEC_FUNCS(subScalarVec)
+DECLARE_INT_IVEC_FUNCS(mulScalarVec)
+DECLARE_INT_IVEC_FUNCS(divScalarVec)
+DECLARE_INT_IVEC_FUNCS(modScalarVec)
+DECLARE_INT_IVEC_FUNCS(bitwiseAndScalarVec)
+DECLARE_INT_IVEC_FUNCS(bitwiseOrScalarVec)
+DECLARE_INT_IVEC_FUNCS(bitwiseXorScalarVec)
+
+DECLARE_UVEC_UINT_FUNCS(addVecScalar)
+DECLARE_UVEC_UINT_FUNCS(subVecScalar)
+DECLARE_UVEC_UINT_FUNCS(mulVecScalar)
+DECLARE_UVEC_UINT_FUNCS(divVecScalar)
+DECLARE_UVEC_UINT_FUNCS(modVecScalar)
+DECLARE_UVEC_UINT_FUNCS(bitwiseAndVecScalar)
+DECLARE_UVEC_UINT_FUNCS(bitwiseOrVecScalar)
+DECLARE_UVEC_UINT_FUNCS(bitwiseXorVecScalar)
+
+DECLARE_UINT_UVEC_FUNCS(addScalarVec)
+DECLARE_UINT_UVEC_FUNCS(subScalarVec)
+DECLARE_UINT_UVEC_FUNCS(mulScalarVec)
+DECLARE_UINT_UVEC_FUNCS(divScalarVec)
+DECLARE_UINT_UVEC_FUNCS(modScalarVec)
+DECLARE_UINT_UVEC_FUNCS(bitwiseAndScalarVec)
+DECLARE_UINT_UVEC_FUNCS(bitwiseOrScalarVec)
+DECLARE_UINT_UVEC_FUNCS(bitwiseXorScalarVec)
+
+// Built-in functions.
+
+DECLARE_UNARY_GENTYPE_FUNCS(radians)
+DECLARE_UNARY_GENTYPE_FUNCS(degrees)
+DECLARE_UNARY_GENTYPE_FUNCS(sin)
+DECLARE_UNARY_GENTYPE_FUNCS(cos)
+DECLARE_UNARY_GENTYPE_FUNCS(tan)
+DECLARE_UNARY_GENTYPE_FUNCS(asin)
+DECLARE_UNARY_GENTYPE_FUNCS(acos)
+DECLARE_UNARY_GENTYPE_FUNCS(atan)
+DECLARE_BINARY_GENTYPE_FUNCS(atan2)
+DECLARE_UNARY_GENTYPE_FUNCS(sinh)
+DECLARE_UNARY_GENTYPE_FUNCS(cosh)
+DECLARE_UNARY_GENTYPE_FUNCS(tanh)
+DECLARE_UNARY_GENTYPE_FUNCS(asinh)
+DECLARE_UNARY_GENTYPE_FUNCS(acosh)
+DECLARE_UNARY_GENTYPE_FUNCS(atanh)
+
+DECLARE_BINARY_GENTYPE_FUNCS(pow)
+DECLARE_UNARY_GENTYPE_FUNCS(exp)
+DECLARE_UNARY_GENTYPE_FUNCS(log)
+DECLARE_UNARY_GENTYPE_FUNCS(exp2)
+DECLARE_UNARY_GENTYPE_FUNCS(log2)
+DECLARE_UNARY_GENTYPE_FUNCS(sqrt)
+DECLARE_UNARY_GENTYPE_FUNCS(inverseSqrt)
+
+DECLARE_UNARY_GENTYPE_FUNCS(abs)
+DECLARE_UNARY_GENTYPE_FUNCS(sign)
+DECLARE_UNARY_GENTYPE_FUNCS(floor)
+DECLARE_UNARY_GENTYPE_FUNCS(trunc)
+DECLARE_UNARY_GENTYPE_FUNCS(roundToEven)
+DECLARE_UNARY_GENTYPE_FUNCS(ceil)
+DECLARE_UNARY_GENTYPE_FUNCS(fract)
+DECLARE_BINARY_GENTYPE_FUNCS(mod)
+DECLARE_VEC_FLOAT_FUNCS(modVecScalar)
+DECLARE_BINARY_GENTYPE_FUNCS(min)
+DECLARE_VEC_FLOAT_FUNCS(minVecScalar)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(min)
+DECLARE_IVEC_INT_FUNCS(minVecScalar)
+DECLARE_BINARY_UINT_GENTYPE_FUNCS(min)
+DECLARE_UVEC_UINT_FUNCS(minVecScalar)
+DECLARE_BINARY_GENTYPE_FUNCS(max)
+DECLARE_VEC_FLOAT_FUNCS(maxVecScalar)
+DECLARE_BINARY_INT_GENTYPE_FUNCS(max)
+DECLARE_IVEC_INT_FUNCS(maxVecScalar)
+DECLARE_BINARY_UINT_GENTYPE_FUNCS(max)
+DECLARE_UVEC_UINT_FUNCS(maxVecScalar)
+DECLARE_TERNARY_GENTYPE_FUNCS(clamp)
+DECLARE_VEC_FLOAT_FLOAT_FUNCS(clampVecScalarScalar)
+DECLARE_TERNARY_INT_GENTYPE_FUNCS(clamp)
+DECLARE_IVEC_INT_INT_FUNCS(clampVecScalarScalar)
+DECLARE_TERNARY_UINT_GENTYPE_FUNCS(clamp)
+DECLARE_UVEC_UINT_UINT_FUNCS(clampVecScalarScalar)
+DECLARE_TERNARY_GENTYPE_FUNCS(mix)
+DECLARE_VEC_VEC_FLOAT_FUNCS(mixVecVecScalar)
+DECLARE_BINARY_GENTYPE_FUNCS(step)
+DECLARE_FLOAT_VEC_FUNCS(stepScalarVec)
+DECLARE_TERNARY_GENTYPE_FUNCS(smoothStep)
+DECLARE_FLOAT_FLOAT_VEC_FUNCS(smoothStepScalarScalarVec)
+
+DECLARE_UNARY_SCALAR_GENTYPE_FUNCS(length)
+DECLARE_BINARY_SCALAR_GENTYPE_FUNCS(distance)
+DECLARE_BINARY_SCALAR_GENTYPE_FUNCS(dot)
+void eval_cross_vec3 (ShaderEvalContext& c) { c.color.xyz()	= cross(c.in[0].swizzle(2, 0, 1), c.in[1].swizzle(1, 2, 0)); }
+
+DECLARE_UNARY_GENTYPE_FUNCS(normalize)
+DECLARE_TERNARY_GENTYPE_FUNCS(faceForward)
+DECLARE_BINARY_GENTYPE_FUNCS(reflect)
+
+void eval_refract_float	(ShaderEvalContext& c) { c.color.x()	= refract(c.in[0].z(),                 c.in[1].x(),                 c.in[2].y()); }
+void eval_refract_vec2	(ShaderEvalContext& c) { c.color.yz()	= refract(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0),       c.in[2].y()); }
+void eval_refract_vec3	(ShaderEvalContext& c) { c.color.xyz()	= refract(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0),    c.in[2].y()); }
+void eval_refract_vec4	(ShaderEvalContext& c) { c.color		= refract(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0), c.in[2].y()); }
+
+// Compare functions.
+
+#define DECLARE_FLOAT_COMPARE_FUNCS(FUNC_NAME)																											\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].z(),          c.in[1].x()); }						\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0)); }		\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0)); }	\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0)); }
+
+#define DECLARE_FLOAT_CWISE_COMPARE_FUNCS(FUNC_NAME)																											\
+	void eval_##FUNC_NAME##_float	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(c.in[0].z(),          c.in[1].x()); }							\
+	void eval_##FUNC_NAME##_vec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1),       c.in[1].swizzle(1, 0)).asFloat(); }		\
+	void eval_##FUNC_NAME##_vec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1),    c.in[1].swizzle(1, 2, 0)).asFloat(); }		\
+	void eval_##FUNC_NAME##_vec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0), c.in[1].swizzle(3, 2, 1, 0)).asFloat(); }
+
+#define DECLARE_INT_COMPARE_FUNCS(FUNC_NAME)																																	\
+	void eval_##FUNC_NAME##_int		(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(chopToInt(c.in[0].z()), chopToInt(c.in[1].x())); }									\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(chopToInt(c.in[0].swizzle(3, 1)),       chopToInt(c.in[1].swizzle(1, 0))); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(chopToInt(c.in[0].swizzle(2, 0, 1)),    chopToInt(c.in[1].swizzle(1, 2, 0))); }		\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(chopToInt(c.in[0].swizzle(1, 2, 3, 0)), chopToInt(c.in[1].swizzle(3, 2, 1, 0))); }
+
+#define DECLARE_INT_CWISE_COMPARE_FUNCS(FUNC_NAME)																																	\
+	void eval_##FUNC_NAME##_int		(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(chopToInt(c.in[0].z()), chopToInt(c.in[1].x())); }									\
+	void eval_##FUNC_NAME##_ivec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(chopToInt(c.in[0].swizzle(3, 1)),       chopToInt(c.in[1].swizzle(1, 0))).asFloat(); }		\
+	void eval_##FUNC_NAME##_ivec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(chopToInt(c.in[0].swizzle(2, 0, 1)),    chopToInt(c.in[1].swizzle(1, 2, 0))).asFloat(); }	\
+	void eval_##FUNC_NAME##_ivec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(chopToInt(c.in[0].swizzle(1, 2, 3, 0)), chopToInt(c.in[1].swizzle(3, 2, 1, 0))).asFloat(); }
+
+#define DECLARE_UINT_COMPARE_FUNCS(FUNC_NAME)																																\
+	void eval_##FUNC_NAME##_uint	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME((deUint32)c.in[0].z(), (deUint32)c.in[1].x()); }								\
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].swizzle(3, 1).asUint(),       c.in[1].swizzle(1, 0).asUint()); }		\
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].swizzle(2, 0, 1).asUint(),    c.in[1].swizzle(1, 2, 0).asUint()); }		\
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asUint(), c.in[1].swizzle(3, 2, 1, 0).asUint()); }
+
+#define DECLARE_UINT_CWISE_COMPARE_FUNCS(FUNC_NAME)																																\
+	void eval_##FUNC_NAME##_uint	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME((deUint32)c.in[0].z(), (deUint32)c.in[1].x()); }									\
+	void eval_##FUNC_NAME##_uvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(c.in[0].swizzle(3, 1).asUint(),       c.in[1].swizzle(1, 0).asUint()).asFloat(); }		\
+	void eval_##FUNC_NAME##_uvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(c.in[0].swizzle(2, 0, 1).asUint(),    c.in[1].swizzle(1, 2, 0).asUint()).asFloat(); }	\
+	void eval_##FUNC_NAME##_uvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(c.in[0].swizzle(1, 2, 3, 0).asUint(), c.in[1].swizzle(3, 2, 1, 0).asUint()).asFloat(); }
+
+#define DECLARE_BOOL_COMPARE_FUNCS(FUNC_NAME)																																								\
+	void eval_##FUNC_NAME##_bool	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(c.in[0].z() > 0.0f, c.in[1].x() > 0.0f); }																		\
+	void eval_##FUNC_NAME##_bvec2	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f)),       greaterThan(c.in[1].swizzle(1, 0), Vec2(0.0f))); }		\
+	void eval_##FUNC_NAME##_bvec3	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f)),    greaterThan(c.in[1].swizzle(1, 2, 0), Vec3(0.0f))); }		\
+	void eval_##FUNC_NAME##_bvec4	(ShaderEvalContext& c) { c.color.x() = (float)FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f)), greaterThan(c.in[1].swizzle(3, 2, 1, 0), Vec4(0.0f))); }
+
+#define DECLARE_BOOL_CWISE_COMPARE_FUNCS(FUNC_NAME)																																								\
+	void eval_##FUNC_NAME##_bool	(ShaderEvalContext& c) { c.color.x()	= (float)FUNC_NAME(c.in[0].z() > 0.0f, c.in[1].x() > 0.0f); }																		\
+	void eval_##FUNC_NAME##_bvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f)),       greaterThan(c.in[1].swizzle(1, 0), Vec2(0.0f))).asFloat(); }		\
+	void eval_##FUNC_NAME##_bvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f)),    greaterThan(c.in[1].swizzle(1, 2, 0), Vec3(0.0f))).asFloat(); }	\
+	void eval_##FUNC_NAME##_bvec4	(ShaderEvalContext& c) { c.color		= FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f)), greaterThan(c.in[1].swizzle(3, 2, 1, 0), Vec4(0.0f))).asFloat(); }
+
+DECLARE_FLOAT_COMPARE_FUNCS(allEqual)
+DECLARE_FLOAT_COMPARE_FUNCS(anyNotEqual)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(lessThan)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(lessThanEqual)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(greaterThan)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(greaterThanEqual)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(equal)
+DECLARE_FLOAT_CWISE_COMPARE_FUNCS(notEqual)
+
+DECLARE_INT_COMPARE_FUNCS(allEqual)
+DECLARE_INT_COMPARE_FUNCS(anyNotEqual)
+DECLARE_INT_CWISE_COMPARE_FUNCS(lessThan)
+DECLARE_INT_CWISE_COMPARE_FUNCS(lessThanEqual)
+DECLARE_INT_CWISE_COMPARE_FUNCS(greaterThan)
+DECLARE_INT_CWISE_COMPARE_FUNCS(greaterThanEqual)
+DECLARE_INT_CWISE_COMPARE_FUNCS(equal)
+DECLARE_INT_CWISE_COMPARE_FUNCS(notEqual)
+
+DECLARE_UINT_COMPARE_FUNCS(allEqual)
+DECLARE_UINT_COMPARE_FUNCS(anyNotEqual)
+DECLARE_UINT_CWISE_COMPARE_FUNCS(lessThan)
+DECLARE_UINT_CWISE_COMPARE_FUNCS(lessThanEqual)
+DECLARE_UINT_CWISE_COMPARE_FUNCS(greaterThan)
+DECLARE_UINT_CWISE_COMPARE_FUNCS(greaterThanEqual)
+DECLARE_UINT_CWISE_COMPARE_FUNCS(equal)
+DECLARE_UINT_CWISE_COMPARE_FUNCS(notEqual)
+
+DECLARE_BOOL_COMPARE_FUNCS(allEqual)
+DECLARE_BOOL_COMPARE_FUNCS(anyNotEqual)
+DECLARE_BOOL_CWISE_COMPARE_FUNCS(equal)
+DECLARE_BOOL_CWISE_COMPARE_FUNCS(notEqual)
+
+// Boolean functions.
+
+#define DECLARE_UNARY_SCALAR_BVEC_FUNCS(GLSL_NAME, FUNC_NAME)																							\
+	void eval_##GLSL_NAME##_bvec2	(ShaderEvalContext& c) { c.color.x()	= float(FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f)))); }		\
+	void eval_##GLSL_NAME##_bvec3	(ShaderEvalContext& c) { c.color.x()	= float(FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f)))); }	\
+	void eval_##GLSL_NAME##_bvec4	(ShaderEvalContext& c) { c.color.x()	= float(FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f)))); }
+
+#define DECLARE_UNARY_BVEC_BVEC_FUNCS(GLSL_NAME, FUNC_NAME)																								\
+	void eval_##GLSL_NAME##_bvec2	(ShaderEvalContext& c) { c.color.yz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(3, 1), Vec2(0.0f))).asFloat(); }	\
+	void eval_##GLSL_NAME##_bvec3	(ShaderEvalContext& c) { c.color.xyz()	= FUNC_NAME(greaterThan(c.in[0].swizzle(2, 0, 1), Vec3(0.0f))).asFloat(); }	\
+	void eval_##GLSL_NAME##_bvec4	(ShaderEvalContext& c) { c.color.xyzw()	= FUNC_NAME(greaterThan(c.in[0].swizzle(1, 2, 3, 0), Vec4(0.0f))).asFloat(); }
+
+DECLARE_UNARY_SCALAR_BVEC_FUNCS(any, boolAny);
+DECLARE_UNARY_SCALAR_BVEC_FUNCS(all, boolAll);
+
+void ShaderOperatorTests::init (void)
+{
+	#define BOOL_FUNCS(FUNC_NAME)			eval_##FUNC_NAME##_bool, DE_NULL, DE_NULL, DE_NULL
+
+	#define FLOAT_VEC_FUNCS(FUNC_NAME)		DE_NULL, eval_##FUNC_NAME##_vec2, eval_##FUNC_NAME##_vec3, eval_##FUNC_NAME##_vec4
+	#define INT_VEC_FUNCS(FUNC_NAME)		DE_NULL, eval_##FUNC_NAME##_ivec2, eval_##FUNC_NAME##_ivec3, eval_##FUNC_NAME##_ivec4
+	#define UINT_VEC_FUNCS(FUNC_NAME)		DE_NULL, eval_##FUNC_NAME##_uvec2, eval_##FUNC_NAME##_uvec3, eval_##FUNC_NAME##_uvec4
+	#define BOOL_VEC_FUNCS(FUNC_NAME)		DE_NULL, eval_##FUNC_NAME##_bvec2, eval_##FUNC_NAME##_bvec3, eval_##FUNC_NAME##_bvec4
+
+	#define FLOAT_GENTYPE_FUNCS(FUNC_NAME)	eval_##FUNC_NAME##_float, eval_##FUNC_NAME##_vec2, eval_##FUNC_NAME##_vec3, eval_##FUNC_NAME##_vec4
+	#define INT_GENTYPE_FUNCS(FUNC_NAME)	eval_##FUNC_NAME##_int, eval_##FUNC_NAME##_ivec2, eval_##FUNC_NAME##_ivec3, eval_##FUNC_NAME##_ivec4
+	#define UINT_GENTYPE_FUNCS(FUNC_NAME)	eval_##FUNC_NAME##_uint, eval_##FUNC_NAME##_uvec2, eval_##FUNC_NAME##_uvec3, eval_##FUNC_NAME##_uvec4
+	#define BOOL_GENTYPE_FUNCS(FUNC_NAME)	eval_##FUNC_NAME##_bool, eval_##FUNC_NAME##_bvec2, eval_##FUNC_NAME##_bvec3, eval_##FUNC_NAME##_bvec4
+
+	// Shorthands.
+	Value					notUsed		= Value(VALUE_NONE, 0.0f, 0.0f);
+	FloatScalar::Symbol		lUMax		= FloatScalar::SYMBOL_LOWP_UINT_MAX;
+	FloatScalar::Symbol		mUMax		= FloatScalar::SYMBOL_MEDIUMP_UINT_MAX;
+	FloatScalar::Symbol		lUMaxR		= FloatScalar::SYMBOL_LOWP_UINT_MAX_RECIPROCAL;
+	FloatScalar::Symbol		mUMaxR		= FloatScalar::SYMBOL_MEDIUMP_UINT_MAX_RECIPROCAL;
+
+	std::vector<BuiltinFuncGroup> funcInfoGroups;
+
+	// Unary operators.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("unary_operator", "Unary operator tests")
+		<< BuiltinOperInfo						("plus",			"+",	GT,		Value(GT,  -1.0f, 1.0f),	notUsed,	notUsed,	0.5f,	0.5f,	PRECMASK_ALL,		FLOAT_GENTYPE_FUNCS(nop))
+		<< BuiltinOperInfo						("plus",			"+",	IGT,	Value(IGT, -5.0f, 5.0f),	notUsed,	notUsed,	0.1f,	0.5f,	PRECMASK_ALL,		INT_GENTYPE_FUNCS(nop))
+		<< BuiltinOperInfo						("plus",			"+",	UGT,	Value(UGT,  0.0f, 2e2f),	notUsed,	notUsed,	5e-3f,	0.0f,	PRECMASK_ALL,		UINT_GENTYPE_FUNCS(nop))
+		<< BuiltinOperInfo						("minus",			"-",	GT,		Value(GT,  -1.0f, 1.0f),	notUsed,	notUsed,	0.5f,	0.5f,	PRECMASK_ALL,		FLOAT_GENTYPE_FUNCS(negate))
+		<< BuiltinOperInfo						("minus",			"-",	IGT,	Value(IGT, -5.0f, 5.0f),	notUsed,	notUsed,	0.1f,	0.5f,	PRECMASK_ALL,		INT_GENTYPE_FUNCS(negate))
+		<< BuiltinOperInfoSeparateRefScaleBias	("minus",			"-",	UGT,	Value(UGT,  0.0f, lUMax),	notUsed,	notUsed,	lUMaxR,	0.0f,	PRECMASK_LOWP,		UINT_GENTYPE_FUNCS(negate), lUMaxR, FloatScalar::SYMBOL_ONE_MINUS_UINT32MAX_DIV_LOWP_UINT_MAX)
+		<< BuiltinOperInfoSeparateRefScaleBias	("minus",			"-",	UGT,	Value(UGT,  0.0f, mUMax),	notUsed,	notUsed,	mUMaxR,	0.0f,	PRECMASK_MEDIUMP,	UINT_GENTYPE_FUNCS(negate), mUMaxR, FloatScalar::SYMBOL_ONE_MINUS_UINT32MAX_DIV_MEDIUMP_UINT_MAX)
+		<< BuiltinOperInfo						("minus",			"-",	UGT,	Value(UGT,  0.0f, 4e9f),	notUsed,	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,		UINT_GENTYPE_FUNCS(negate))
+		<< BuiltinOperInfo						("not",				"!",	B,		Value(B,   -1.0f, 1.0f),	notUsed,	notUsed,	1.0f,	0.0f,	PRECMASK_NA,		eval_boolNot_bool, DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo						("bitwise_not",		"~",	IGT,	Value(IGT, -1e5f, 1e5f),	notUsed,	notUsed,	5e-5f,	0.5f,	PRECMASK_HIGHP,		INT_GENTYPE_FUNCS(bitwiseNot))
+		<< BuiltinOperInfo						("bitwise_not",		"~",	UGT,	Value(UGT,  0.0f, 2e9f),	notUsed,	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,		UINT_GENTYPE_FUNCS(bitwiseNot))
+
+		// Pre/post incr/decr side effect cases.
+		<< BuiltinSideEffOperInfo		("pre_increment_effect",	"++",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(addOne))
+		<< BuiltinSideEffOperInfo		("pre_increment_effect",	"++",	IGT,	Value(IGT,	-6.0f, 4.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(addOne))
+		<< BuiltinSideEffOperInfo		("pre_increment_effect",	"++",	UGT,	Value(UGT,	 0.0f, 9.0f),	notUsed,	notUsed,	0.1f, 0.0f,		PRECMASK_ALL,	UINT_GENTYPE_FUNCS(addOne))
+		<< BuiltinSideEffOperInfo		("pre_decrement_effect",	"--",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 1.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(subOne))
+		<< BuiltinSideEffOperInfo		("pre_decrement_effect",	"--",	IGT,	Value(IGT,	-4.0f, 6.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(subOne))
+		<< BuiltinSideEffOperInfo		("pre_decrement_effect",	"--",	UGT,	Value(UGT,	 1.0f, 10.0f),	notUsed,	notUsed,	0.1f, 0.0f,		PRECMASK_ALL,	UINT_GENTYPE_FUNCS(subOne))
+		<< BuiltinPostSideEffOperInfo	("post_increment_effect",	"++",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(addOne))
+		<< BuiltinPostSideEffOperInfo	("post_increment_effect",	"++",	IGT,	Value(IGT,	-6.0f, 4.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(addOne))
+		<< BuiltinPostSideEffOperInfo	("post_increment_effect",	"++",	UGT,	Value(UGT,	 0.0f, 9.0f),	notUsed,	notUsed,	0.1f, 0.0f,		PRECMASK_ALL,	UINT_GENTYPE_FUNCS(addOne))
+		<< BuiltinPostSideEffOperInfo	("post_decrement_effect",	"--",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 1.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(subOne))
+		<< BuiltinPostSideEffOperInfo	("post_decrement_effect",	"--",	IGT,	Value(IGT,	-4.0f, 6.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(subOne))
+		<< BuiltinPostSideEffOperInfo	("post_decrement_effect",	"--",	UGT,	Value(UGT,	 1.0f, 10.0f),	notUsed,	notUsed,	0.1f, 0.0f,		PRECMASK_ALL,	UINT_GENTYPE_FUNCS(subOne))
+
+		// Pre/post incr/decr result cases.
+		<< BuiltinOperInfo				("pre_increment_result",	"++",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(addOne))
+		<< BuiltinOperInfo				("pre_increment_result",	"++",	IGT,	Value(IGT,	-6.0f, 4.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(addOne))
+		<< BuiltinOperInfo				("pre_increment_result",	"++",	UGT,	Value(UGT,	 0.0f, 9.0f),	notUsed,	notUsed,	0.1f, 0.0f,		PRECMASK_ALL,	UINT_GENTYPE_FUNCS(addOne))
+		<< BuiltinOperInfo				("pre_decrement_result",	"--",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 1.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(subOne))
+		<< BuiltinOperInfo				("pre_decrement_result",	"--",	IGT,	Value(IGT,	-4.0f, 6.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(subOne))
+		<< BuiltinOperInfo				("pre_decrement_result",	"--",	UGT,	Value(UGT,	 1.0f, 10.0f),	notUsed,	notUsed,	0.1f, 0.0f,		PRECMASK_ALL,	UINT_GENTYPE_FUNCS(subOne))
+		<< BuiltinPostOperInfo			("post_increment_result",	"++",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(nop))
+		<< BuiltinPostOperInfo			("post_increment_result",	"++",	IGT,	Value(IGT,	-5.0f, 5.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(nop))
+		<< BuiltinPostOperInfo			("post_increment_result",	"++",	UGT,	Value(UGT,	 0.0f, 9.0f),	notUsed,	notUsed,	0.1f, 0.0f,		PRECMASK_ALL,	UINT_GENTYPE_FUNCS(nop))
+		<< BuiltinPostOperInfo			("post_decrement_result",	"--",	GT,		Value(GT,	-1.0f, 1.0f),	notUsed,	notUsed,	0.5f, 0.5f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(nop))
+		<< BuiltinPostOperInfo			("post_decrement_result",	"--",	IGT,	Value(IGT,	-5.0f, 5.0f),	notUsed,	notUsed,	0.1f, 0.5f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(nop))
+		<< BuiltinPostOperInfo			("post_decrement_result",	"--",	UGT,	Value(UGT,	 1.0f, 10.0f),	notUsed,	notUsed,	0.1f, 0.0f,		PRECMASK_ALL,	UINT_GENTYPE_FUNCS(nop))
+	);
+
+	BuiltinFuncGroup binaryOpGroup("binary_operator", "Binary operator tests");
+
+	// Normal binary operations and their corresponding assignment operations have lots in common; generate both in the following loop.
+
+	for (int binaryOperatorType = 0; binaryOperatorType <= 2; binaryOperatorType++) // 0: normal op test, 1: assignment op side-effect test, 2: assignment op result test
+	{
+		bool		isNormalOp		= binaryOperatorType == 0;
+		bool		isAssignEff		= binaryOperatorType == 1;
+		bool		isAssignRes		= binaryOperatorType == 2;
+
+		DE_ASSERT(isNormalOp || isAssignEff || isAssignRes);
+		DE_UNREF(isAssignRes);
+
+		const char*	addName			= isNormalOp ? "add"			: isAssignEff ? "add_assign_effect"			: "add_assign_result";
+		const char*	subName			= isNormalOp ? "sub"			: isAssignEff ? "sub_assign_effect"			: "sub_assign_result";
+		const char*	mulName			= isNormalOp ? "mul"			: isAssignEff ? "mul_assign_effect"			: "mul_assign_result";
+		const char*	divName			= isNormalOp ? "div"			: isAssignEff ? "div_assign_effect"			: "div_assign_result";
+		const char* modName			= isNormalOp ? "mod"			: isAssignEff ? "mod_assign_effect"			: "mod_assign_result";
+		const char* andName			= isNormalOp ? "bitwise_and"	: isAssignEff ? "bitwise_and_assign_effect"	: "bitwise_and_assign_result";
+		const char* orName			= isNormalOp ? "bitwise_or"		: isAssignEff ? "bitwise_or_assign_effect"	: "bitwise_or_assign_result";
+		const char* xorName			= isNormalOp ? "bitwise_xor"	: isAssignEff ? "bitwise_xor_assign_effect"	: "bitwise_xor_assign_result";
+		const char* leftShiftName	= isNormalOp ? "left_shift"		: isAssignEff ? "left_shift_assign_effect"	: "left_shift_assign_result";
+		const char* rightShiftName	= isNormalOp ? "right_shift"	: isAssignEff ? "right_shift_assign_effect"	: "right_shift_assign_result";
+		const char*	addOp			= isNormalOp ? "+" : "+=";
+		const char*	subOp			= isNormalOp ? "-" : "-=";
+		const char*	mulOp			= isNormalOp ? "*" : "*=";
+		const char*	divOp			= isNormalOp ? "/" : "/=";
+		const char*	modOp			= isNormalOp ? "%" : "%=";
+		const char*	andOp			= isNormalOp ? "&" : "&=";
+		const char*	orOp			= isNormalOp ? "|" : "|=";
+		const char*	xorOp			= isNormalOp ? "^" : "^=";
+		const char*	leftShiftOp		= isNormalOp ? "<<" : "<<=";
+		const char*	rightShiftOp	= isNormalOp ? ">>" : ">>=";
+
+		// Pointer to appropriate OperInfo function.
+		BuiltinFuncInfo (*operInfoFunc)(const char*, const char*, ValueType, Value, Value, Value, const FloatScalar&, const FloatScalar&, deUint32, ShaderEvalFunc, ShaderEvalFunc, ShaderEvalFunc, ShaderEvalFunc) =
+			isAssignEff ? BuiltinSideEffOperInfo : BuiltinOperInfo;
+
+		DE_ASSERT(operInfoFunc != DE_NULL);
+
+		// The following cases will be added for each operator, precision and fundamental type (float, int, uint) combination, where applicable:
+		// gentype <op> gentype
+		// vector <op> scalar
+		// For normal (non-assigning) operators only:
+		//   scalar <op> vector
+
+		// The add operator.
+
+		binaryOpGroup
+			<< operInfoFunc(addName,	addOp,	GT,		Value(GT,  -1.0f, 1.0f),	Value(GT,  -1.0f, 1.0f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_GENTYPE_FUNCS(add))
+			<< operInfoFunc(addName,	addOp,	IGT,	Value(IGT, -4.0f, 6.0f),	Value(IGT, -6.0f, 5.0f),	notUsed,	0.1f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_GENTYPE_FUNCS(add))
+			<< operInfoFunc(addName,	addOp,	IGT,	Value(IGT, -2e9f, 2e9f),	Value(IGT, -2e9f, 2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_GENTYPE_FUNCS(add))
+			<< operInfoFunc(addName,	addOp,	UGT,	Value(UGT,  0.0f, 1e2f),	Value(UGT,  0.0f, 1e2f),	notUsed,	5e-3f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_GENTYPE_FUNCS(add))
+			<< operInfoFunc(addName,	addOp,	UGT,	Value(UGT,  0.0f, 4e9f),	Value(UGT,  0.0f, 4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_GENTYPE_FUNCS(add))
+			<< operInfoFunc(addName,	addOp,	FV,		Value(FV,  -1.0f, 1.0f),	Value(F,   -1.0f, 1.0f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_VEC_FUNCS(addVecScalar))
+			<< operInfoFunc(addName,	addOp,	IV,		Value(IV,  -4.0f, 6.0f),	Value(I,   -6.0f, 5.0f),	notUsed,	0.1f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(addVecScalar))
+			<< operInfoFunc(addName,	addOp,	IV,		Value(IV,  -2e9f, 2e9f),	Value(I,   -2e9f, 2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(addVecScalar))
+			<< operInfoFunc(addName,	addOp,	UV,		Value(UV,   0.0f, 1e2f),	Value(U,    0.0f, 1e2f),	notUsed,	5e-3f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(addVecScalar))
+			<< operInfoFunc(addName,	addOp,	UV,		Value(UV,   0.0f, 4e9f),	Value(U,    0.0f, 4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(addVecScalar));
+
+		if (isNormalOp)
+			binaryOpGroup
+				<< operInfoFunc(addName,	addOp,	FV,		Value(F,   -1.0f, 1.0f),	Value(FV,  -1.0f, 1.0f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_VEC_FUNCS(addScalarVec))
+				<< operInfoFunc(addName,	addOp,	IV,		Value(I,   -4.0f, 6.0f),	Value(IV,  -6.0f, 5.0f),	notUsed,	0.1f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(addScalarVec))
+				<< operInfoFunc(addName,	addOp,	IV,		Value(I,   -2e9f, 2e9f),	Value(IV,  -2e9f, 2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(addScalarVec))
+				<< operInfoFunc(addName,	addOp,	UV,		Value(U,    0.0f, 1e2f),	Value(UV,   0.0f, 1e2f),	notUsed,	5e-3f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(addScalarVec))
+				<< operInfoFunc(addName,	addOp,	UV,		Value(U,    0.0f, 4e9f),	Value(UV,   0.0f, 4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(addScalarVec));
+
+		// The subtract operator.
+
+		binaryOpGroup
+			<< operInfoFunc(subName,	subOp,	GT,		Value(GT,  -1.0f, 1.0f),	Value(GT,  -1.0f, 1.0f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_GENTYPE_FUNCS(sub))
+			<< operInfoFunc(subName,	subOp,	IGT,	Value(IGT, -4.0f, 6.0f),	Value(IGT, -6.0f, 5.0f),	notUsed,	0.1f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_GENTYPE_FUNCS(sub))
+			<< operInfoFunc(subName,	subOp,	IGT,	Value(IGT, -2e9f, 2e9f),	Value(IGT, -2e9f, 2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_GENTYPE_FUNCS(sub))
+			<< operInfoFunc(subName,	subOp,	UGT,	Value(UGT,  1e2f, 2e2f),	Value(UGT,  0.0f, 1e2f),	notUsed,	5e-3f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_GENTYPE_FUNCS(sub))
+			<< operInfoFunc(subName,	subOp,	UGT,	Value(UGT,  .5e9f, 3.7e9f),	Value(UGT,  0.0f, 3.9e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_GENTYPE_FUNCS(sub))
+			<< operInfoFunc(subName,	subOp,	FV,		Value(FV,  -1.0f, 1.0f),	Value(F,   -1.0f, 1.0f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_VEC_FUNCS(subVecScalar))
+			<< operInfoFunc(subName,	subOp,	IV,		Value(IV,  -4.0f, 6.0f),	Value(I,   -6.0f, 5.0f),	notUsed,	0.1f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(subVecScalar))
+			<< operInfoFunc(subName,	subOp,	IV,		Value(IV,  -2e9f, 2e9f),	Value(I,   -2e9f, 2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(subVecScalar))
+			<< operInfoFunc(subName,	subOp,	UV,		Value(UV,   1e2f, 2e2f),	Value(U,    0.0f, 1e2f),	notUsed,	5e-3f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(subVecScalar))
+			<< operInfoFunc(subName,	subOp,	UV,		Value(UV,   0.0f, 4e9f),	Value(U,    0.0f, 4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(subVecScalar));
+
+		if (isNormalOp)
+			binaryOpGroup
+				<< operInfoFunc(subName,	subOp,	FV,		Value(F,   -1.0f, 1.0f),	Value(FV,  -1.0f, 1.0f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_VEC_FUNCS(subScalarVec))
+				<< operInfoFunc(subName,	subOp,	IV,		Value(I,   -4.0f, 6.0f),	Value(IV,  -6.0f, 5.0f),	notUsed,	0.1f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(subScalarVec))
+				<< operInfoFunc(subName,	subOp,	IV,		Value(I,   -2e9f, 2e9f),	Value(IV,  -2e9f, 2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(subScalarVec))
+				<< operInfoFunc(subName,	subOp,	UV,		Value(U,    1e2f, 2e2f),	Value(UV,    0.0f, 1e2f),	notUsed,	5e-3f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(subScalarVec))
+				<< operInfoFunc(subName,	subOp,	UV,		Value(U,    0.0f, 4e9f),	Value(UV,    0.0f, 4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(subScalarVec));
+
+		// The multiply operator.
+
+		binaryOpGroup
+			<< operInfoFunc(mulName,	mulOp,	GT,		Value(GT,  -1.0f, 1.0f),	Value(GT,  -1.0f, 1.0f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_GENTYPE_FUNCS(mul))
+			<< operInfoFunc(mulName,	mulOp,	IGT,	Value(IGT, -4.0f, 6.0f),	Value(IGT, -6.0f, 5.0f),	notUsed,	0.1f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_GENTYPE_FUNCS(mul))
+			<< operInfoFunc(mulName,	mulOp,	IGT,	Value(IGT, -3e5f, 3e5f),	Value(IGT, -3e4f, 3e4f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_GENTYPE_FUNCS(mul))
+			<< operInfoFunc(mulName,	mulOp,	UGT,	Value(UGT,  0.0f, 16.0f),	Value(UGT,  0.0f, 16.0f),	notUsed,	4e-3f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_GENTYPE_FUNCS(mul))
+			<< operInfoFunc(mulName,	mulOp,	UGT,	Value(UGT,  0.0f, 6e5f),	Value(UGT,  0.0f, 6e4f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_GENTYPE_FUNCS(mul))
+			<< operInfoFunc(mulName,	mulOp,	FV,		Value(FV,  -1.0f, 1.0f),	Value(F,   -1.0f,  1.0f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_VEC_FUNCS(mulVecScalar))
+			<< operInfoFunc(mulName,	mulOp,	IV,		Value(IV,  -4.0f, 6.0f),	Value(I,   -6.0f,  5.0f),	notUsed,	0.1f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(mulVecScalar))
+			<< operInfoFunc(mulName,	mulOp,	IV,		Value(IV,  -3e5f, 3e5f),	Value(I,   -3e4f,  3e4f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(mulVecScalar))
+			<< operInfoFunc(mulName,	mulOp,	UV,		Value(UV,   0.0f, 16.0f),	Value(U,    0.0f, 16.0f),	notUsed,	4e-3f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(mulVecScalar))
+			<< operInfoFunc(mulName,	mulOp,	UV,		Value(UV,   0.0f, 6e5f),	Value(U,    0.0f, 6e4f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(mulVecScalar));
+
+		if (isNormalOp)
+			binaryOpGroup
+				<< operInfoFunc(mulName,	mulOp,	FV,		Value(F,   -1.0f, 1.0f),	Value(FV,  -1.0f,  1.0f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_VEC_FUNCS(mulScalarVec))
+				<< operInfoFunc(mulName,	mulOp,	IV,		Value(I,   -4.0f, 6.0f),	Value(IV,  -6.0f,  5.0f),	notUsed,	0.1f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(mulScalarVec))
+				<< operInfoFunc(mulName,	mulOp,	IV,		Value(I,   -3e5f, 3e5f),	Value(IV,  -3e4f,  3e4f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(mulScalarVec))
+				<< operInfoFunc(mulName,	mulOp,	UV,		Value(U,    0.0f, 16.0f),	Value(UV,   0.0f, 16.0f),	notUsed,	4e-3f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(mulScalarVec))
+				<< operInfoFunc(mulName,	mulOp,	UV,		Value(U,    0.0f, 6e5f),	Value(UV,   0.0f, 6e4f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(mulScalarVec));
+
+		// The divide operator.
+
+		binaryOpGroup
+			<< operInfoFunc(divName,	divOp,	GT,		Value(GT,  -1.0f,    1.0f),		Value(GT,  -2.0f, -0.5f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_GENTYPE_FUNCS(div))
+			<< operInfoFunc(divName,	divOp,	IGT,	Value(IGT, 24.0f,    24.0f),	Value(IGT, -4.0f, -1.0f),	notUsed,	0.04f,	1.0f,	PRECMASK_LOWP_MEDIUMP,	INT_GENTYPE_FUNCS(div))
+			<< operInfoFunc(divName,	divOp,	IGT,	Value(IGT, 40320.0f, 40320.0f),	Value(IGT, -8.0f, -1.0f),	notUsed,	1e-5f,	0.5f,	PRECMASK_HIGHP,			INT_GENTYPE_FUNCS(div))
+			<< operInfoFunc(divName,	divOp,	UGT,	Value(UGT,  0.0f,    24.0f),	Value(UGT,  1.0f,  4.0f),	notUsed,	0.04f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_GENTYPE_FUNCS(div))
+			<< operInfoFunc(divName,	divOp,	UGT,	Value(UGT,  0.0f,    40320.0f),	Value(UGT,  1.0f,  8.0f),	notUsed,	1e-5f,	0.0f,	PRECMASK_HIGHP,			UINT_GENTYPE_FUNCS(div))
+			<< operInfoFunc(divName,	divOp,	FV,		Value(FV,  -1.0f,    1.0f),		Value(F,   -2.0f, -0.5f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_VEC_FUNCS(divVecScalar))
+			<< operInfoFunc(divName,	divOp,	IV,		Value(IV,  24.0f,    24.0f),	Value(I,   -4.0f, -1.0f),	notUsed,	0.04f,	1.0f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(divVecScalar))
+			<< operInfoFunc(divName,	divOp,	IV,		Value(IV,  40320.0f, 40320.0f),	Value(I,   -8.0f, -1.0f),	notUsed,	1e-5f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(divVecScalar))
+			<< operInfoFunc(divName,	divOp,	UV,		Value(UV,   0.0f,    24.0f),	Value(U,    1.0f,  4.0f),	notUsed,	0.04f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(divVecScalar))
+			<< operInfoFunc(divName,	divOp,	UV,		Value(UV,   0.0f,    40320.0f),	Value(U,    1.0f,  8.0f),	notUsed,	1e-5f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(divVecScalar));
+
+		if (isNormalOp)
+			binaryOpGroup
+				<< operInfoFunc(divName,	divOp,	FV,		Value(F,   -1.0f,    1.0f),		Value(FV,  -2.0f, -0.5f),	notUsed,	1.0f,	0.0f,	PRECMASK_ALL,			FLOAT_VEC_FUNCS(divScalarVec))
+				<< operInfoFunc(divName,	divOp,	IV,		Value(I,   24.0f,    24.0f),	Value(IV,  -4.0f, -1.0f),	notUsed,	0.04f,	1.0f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(divScalarVec))
+				<< operInfoFunc(divName,	divOp,	IV,		Value(I,   40320.0f, 40320.0f),	Value(IV,  -8.0f, -1.0f),	notUsed,	1e-5f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(divScalarVec))
+				<< operInfoFunc(divName,	divOp,	UV,		Value(U,    0.0f,    24.0f),	Value(UV,   1.0f,  4.0f),	notUsed,	0.04f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(divScalarVec))
+				<< operInfoFunc(divName,	divOp,	UV,		Value(U,    0.0f,    40320.0f),	Value(UV,   1.0f,  8.0f),	notUsed,	1e-5f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(divScalarVec));
+
+		// The modulus operator.
+
+		binaryOpGroup
+			<< operInfoFunc(modName,	modOp,	IGT,	Value(IGT,  0.0f, 6.0f),	Value(IGT,   1.1f,  6.1f),	notUsed,	0.25f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_GENTYPE_FUNCS(mod))
+			<< operInfoFunc(modName,	modOp,	IGT,	Value(IGT,  0.0f, 14.0f),	Value(IGT,   1.1f, 11.1f),	notUsed,	0.1f,	0.5f,	PRECMASK_HIGHP,			INT_GENTYPE_FUNCS(mod))
+			<< operInfoFunc(modName,	modOp,	UGT,	Value(UGT,  0.0f, 6.0f),	Value(UGT,   1.1f,  6.1f),	notUsed,	0.25f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_GENTYPE_FUNCS(mod))
+			<< operInfoFunc(modName,	modOp,	UGT,	Value(UGT,  0.0f, 24.0f),	Value(UGT,   1.1f, 11.1f),	notUsed,	0.1f,	0.0f,	PRECMASK_HIGHP,			UINT_GENTYPE_FUNCS(mod))
+			<< operInfoFunc(modName,	modOp,	IV,		Value(IV,   0.0f, 6.0f),	Value(I,     1.1f,  6.1f),	notUsed,	0.25f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(modVecScalar))
+			<< operInfoFunc(modName,	modOp,	IV,		Value(IV,   0.0f, 6.0f),	Value(I,     1.1f, 11.1f),	notUsed,	0.1f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(modVecScalar))
+			<< operInfoFunc(modName,	modOp,	UV,		Value(UV,   0.0f, 6.0f),	Value(U,     1.1f,  6.1f),	notUsed,	0.25f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(modVecScalar))
+			<< operInfoFunc(modName,	modOp,	UV,		Value(UV,   0.0f, 24.0f),	Value(U,     1.1f, 11.1f),	notUsed,	0.1f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(modVecScalar));
+
+		if (isNormalOp)
+			binaryOpGroup
+				<< operInfoFunc(modName,	modOp,	IV,		Value(I,   0.0f, 6.0f),		Value(IV,     1.1f,  6.1f),	notUsed,	0.25f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(modScalarVec))
+				<< operInfoFunc(modName,	modOp,	IV,		Value(I,   0.0f, 6.0f),		Value(IV,     1.1f, 11.1f),	notUsed,	0.1f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(modScalarVec))
+				<< operInfoFunc(modName,	modOp,	UV,		Value(U,   0.0f, 6.0f),		Value(UV,     1.1f,  6.1f),	notUsed,	0.25f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(modScalarVec))
+				<< operInfoFunc(modName,	modOp,	UV,		Value(U,   0.0f, 24.0f),	Value(UV,     1.1f, 11.1f),	notUsed,	0.1f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(modScalarVec));
+
+		// The bitwise and operator.
+
+		binaryOpGroup
+			<< operInfoFunc(andName,	andOp,	IGT,	Value(IGT, -16.0f, 16.0f),	Value(IGT, -16.0f, 16.0f),	notUsed,	 0.03f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_GENTYPE_FUNCS(bitwiseAnd))
+			<< operInfoFunc(andName,	andOp,	IGT,	Value(IGT,  -2e9f,  2e9f),	Value(IGT,  -2e9f,  2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_GENTYPE_FUNCS(bitwiseAnd))
+			<< operInfoFunc(andName,	andOp,	UGT,	Value(UGT,   0.0f, 32.0f),	Value(UGT,   0.0f, 32.0f),	notUsed,	 0.03f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_GENTYPE_FUNCS(bitwiseAnd))
+			<< operInfoFunc(andName,	andOp,	UGT,	Value(UGT,   0.0f,  4e9f),	Value(UGT,   0.0f,  4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_GENTYPE_FUNCS(bitwiseAnd))
+			<< operInfoFunc(andName,	andOp,	IV,		Value(IV, -16.0f, 16.0f),	Value(I, -16.0f, 16.0f),	notUsed,	 0.03f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(bitwiseAndVecScalar))
+			<< operInfoFunc(andName,	andOp,	IV,		Value(IV,  -2e9f,  2e9f),	Value(I,  -2e9f,  2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(bitwiseAndVecScalar))
+			<< operInfoFunc(andName,	andOp,	UV,		Value(UV,   0.0f, 32.0f),	Value(U,   0.0f, 32.0f),	notUsed,	 0.03f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(bitwiseAndVecScalar))
+			<< operInfoFunc(andName,	andOp,	UV,		Value(UV,   0.0f,  4e9f),	Value(U,   0.0f,  4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(bitwiseAndVecScalar));
+
+		if (isNormalOp)
+			binaryOpGroup
+				<< operInfoFunc(andName,	andOp,	IV,		Value(I, -16.0f, 16.0f),	Value(IV, -16.0f, 16.0f),	notUsed,	 0.03f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(bitwiseAndScalarVec))
+				<< operInfoFunc(andName,	andOp,	IV,		Value(I,  -2e9f,  2e9f),	Value(IV,  -2e9f,  2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(bitwiseAndScalarVec))
+				<< operInfoFunc(andName,	andOp,	UV,		Value(U,   0.0f, 32.0f),	Value(UV,   0.0f, 32.0f),	notUsed,	 0.03f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(bitwiseAndScalarVec))
+				<< operInfoFunc(andName,	andOp,	UV,		Value(U,   0.0f,  4e9f),	Value(UV,   0.0f,  4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(bitwiseAndScalarVec));
+
+		// The bitwise or operator.
+
+		binaryOpGroup
+			<< operInfoFunc(orName,	orOp,	IGT,	Value(IGT, -16.0f, 16.0f),	Value(IGT, -16.0f, 16.0f),	notUsed,	 0.03f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_GENTYPE_FUNCS(bitwiseOr))
+			<< operInfoFunc(orName,	orOp,	IGT,	Value(IGT,  -2e9f,  2e9f),	Value(IGT,  -2e9f,  2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_GENTYPE_FUNCS(bitwiseOr))
+			<< operInfoFunc(orName,	orOp,	UGT,	Value(UGT,   0.0f, 32.0f),	Value(UGT,   0.0f, 32.0f),	notUsed,	 0.03f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_GENTYPE_FUNCS(bitwiseOr))
+			<< operInfoFunc(orName,	orOp,	UGT,	Value(UGT,   0.0f,  4e9f),	Value(UGT,   0.0f,  4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_GENTYPE_FUNCS(bitwiseOr))
+			<< operInfoFunc(orName,	orOp,	IV,		Value(IV, -16.0f, 16.0f),	Value(I, -16.0f, 16.0f),	notUsed,	 0.03f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(bitwiseOrVecScalar))
+			<< operInfoFunc(orName,	orOp,	IV,		Value(IV,  -2e9f,  2e9f),	Value(I,  -2e9f,  2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(bitwiseOrVecScalar))
+			<< operInfoFunc(orName,	orOp,	UV,		Value(UV,   0.0f, 32.0f),	Value(U,   0.0f, 32.0f),	notUsed,	 0.03f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(bitwiseOrVecScalar))
+			<< operInfoFunc(orName,	orOp,	UV,		Value(UV,   0.0f,  4e9f),	Value(U,   0.0f,  4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(bitwiseOrVecScalar));
+
+		if (isNormalOp)
+			binaryOpGroup
+				<< operInfoFunc(orName,	orOp,	IV,		Value(I, -16.0f, 16.0f),	Value(IV, -16.0f, 16.0f),	notUsed,	 0.03f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(bitwiseOrScalarVec))
+				<< operInfoFunc(orName,	orOp,	IV,		Value(I,  -2e9f,  2e9f),	Value(IV,  -2e9f,  2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(bitwiseOrScalarVec))
+				<< operInfoFunc(orName,	orOp,	UV,		Value(U,   0.0f, 32.0f),	Value(UV,   0.0f, 32.0f),	notUsed,	 0.03f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(bitwiseOrScalarVec))
+				<< operInfoFunc(orName,	orOp,	UV,		Value(U,   0.0f,  4e9f),	Value(UV,   0.0f,  4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(bitwiseOrScalarVec));
+
+		// The bitwise xor operator.
+
+		binaryOpGroup
+			<< operInfoFunc(xorName,	xorOp,	IGT,	Value(IGT, -16.0f, 16.0f),	Value(IGT, -16.0f, 16.0f),	notUsed,	 0.03f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_GENTYPE_FUNCS(bitwiseXor))
+			<< operInfoFunc(xorName,	xorOp,	IGT,	Value(IGT,  -2e9f,  2e9f),	Value(IGT,  -2e9f,  2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_GENTYPE_FUNCS(bitwiseXor))
+			<< operInfoFunc(xorName,	xorOp,	UGT,	Value(UGT,   0.0f, 32.0f),	Value(UGT,   0.0f, 32.0f),	notUsed,	 0.03f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_GENTYPE_FUNCS(bitwiseXor))
+			<< operInfoFunc(xorName,	xorOp,	UGT,	Value(UGT,   0.0f,  4e9f),	Value(UGT,   0.0f,  4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_GENTYPE_FUNCS(bitwiseXor))
+			<< operInfoFunc(xorName,	xorOp,	IV,		Value(IV, -16.0f, 16.0f),	Value(I, -16.0f, 16.0f),	notUsed,	 0.03f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(bitwiseXorVecScalar))
+			<< operInfoFunc(xorName,	xorOp,	IV,		Value(IV,  -2e9f,  2e9f),	Value(I,  -2e9f,  2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(bitwiseXorVecScalar))
+			<< operInfoFunc(xorName,	xorOp,	UV,		Value(UV,   0.0f, 32.0f),	Value(U,   0.0f, 32.0f),	notUsed,	 0.03f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(bitwiseXorVecScalar))
+			<< operInfoFunc(xorName,	xorOp,	UV,		Value(UV,   0.0f,  4e9f),	Value(U,   0.0f,  4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(bitwiseXorVecScalar));
+
+		if (isNormalOp)
+			binaryOpGroup
+				<< operInfoFunc(xorName,	xorOp,	IV,		Value(I, -16.0f, 16.0f),	Value(IV, -16.0f, 16.0f),	notUsed,	 0.03f,	0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(bitwiseXorScalarVec))
+				<< operInfoFunc(xorName,	xorOp,	IV,		Value(I,  -2e9f,  2e9f),	Value(IV,  -2e9f,  2e9f),	notUsed,	4e-10f,	0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(bitwiseXorScalarVec))
+				<< operInfoFunc(xorName,	xorOp,	UV,		Value(U,   0.0f, 32.0f),	Value(UV,   0.0f, 32.0f),	notUsed,	 0.03f,	0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(bitwiseXorScalarVec))
+				<< operInfoFunc(xorName,	xorOp,	UV,		Value(U,   0.0f,  4e9f),	Value(UV,   0.0f,  4e9f),	notUsed,	2e-10f,	0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(bitwiseXorScalarVec));
+
+		// The left shift operator. Second operand (shift amount) can be either int or uint, even for uint and int first operand, respectively.
+
+		for (int isSignedAmount = 0; isSignedAmount <= 1; isSignedAmount++)
+		{
+			ValueType gType = isSignedAmount == 0 ? UGT	: IGT;
+			ValueType sType = isSignedAmount == 0 ? U	: I;
+			binaryOpGroup
+				<< operInfoFunc(leftShiftName,	leftShiftOp,	IGT,	Value(IGT, -7.0f, 7.0f),	Value(gType, 0.0f, 4.0f),	notUsed,	4e-3f,  0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_GENTYPE_FUNCS(leftShift))
+				<< operInfoFunc(leftShiftName,	leftShiftOp,	IGT,	Value(IGT, -7.0f, 7.0f),	Value(gType, 0.0f, 27.0f),	notUsed,	5e-10f, 0.5f,	PRECMASK_HIGHP,			INT_GENTYPE_FUNCS(leftShift))
+				<< operInfoFunc(leftShiftName,	leftShiftOp,	UGT,	Value(UGT,  0.0f, 7.0f),	Value(gType, 0.0f, 5.0f),	notUsed,	4e-3f,  0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_GENTYPE_FUNCS(leftShift))
+				<< operInfoFunc(leftShiftName,	leftShiftOp,	UGT,	Value(UGT,  0.0f, 7.0f),	Value(gType, 0.0f, 28.0f),	notUsed,	5e-10f, 0.0f,	PRECMASK_HIGHP,			UINT_GENTYPE_FUNCS(leftShift))
+				<< operInfoFunc(leftShiftName,	leftShiftOp,	IV,		Value(IV,  -7.0f, 7.0f),	Value(sType, 0.0f, 4.0f),	notUsed,	4e-3f,  0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(leftShiftVecScalar))
+				<< operInfoFunc(leftShiftName,	leftShiftOp,	IV,		Value(IV,  -7.0f, 7.0f),	Value(sType, 0.0f, 27.0f),	notUsed,	5e-10f, 0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(leftShiftVecScalar))
+				<< operInfoFunc(leftShiftName,	leftShiftOp,	UV,		Value(UV,   0.0f, 7.0f),	Value(sType, 0.0f, 5.0f),	notUsed,	4e-3f,  0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(leftShiftVecScalar))
+				<< operInfoFunc(leftShiftName,	leftShiftOp,	UV,		Value(UV,   0.0f, 7.0f),	Value(sType, 0.0f, 28.0f),	notUsed,	5e-10f, 0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(leftShiftVecScalar));
+		}
+
+		// The right shift operator. Second operand (shift amount) can be either int or uint, even for uint and int first operand, respectively.
+
+		for (int isSignedAmount = 0; isSignedAmount <= 1; isSignedAmount++)
+		{
+			ValueType gType = isSignedAmount == 0 ? UGT	: IGT;
+			ValueType sType = isSignedAmount == 0 ? U	: I;
+			binaryOpGroup
+				<< operInfoFunc(rightShiftName,	rightShiftOp,	IGT,	Value(IGT, -127.0f, 127.0f),	Value(gType, 0.0f, 8.0f),	notUsed,	4e-3f,  0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_GENTYPE_FUNCS(rightShift))
+				<< operInfoFunc(rightShiftName,	rightShiftOp,	IGT,	Value(IGT, -2e9f, 2e9f),		Value(gType, 0.0f, 31.0f),	notUsed,	5e-10f, 0.5f,	PRECMASK_HIGHP,			INT_GENTYPE_FUNCS(rightShift))
+				<< operInfoFunc(rightShiftName,	rightShiftOp,	UGT,	Value(UGT,  0.0f, 255.0f),		Value(gType, 0.0f, 8.0f),	notUsed,	4e-3f,  0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_GENTYPE_FUNCS(rightShift))
+				<< operInfoFunc(rightShiftName,	rightShiftOp,	UGT,	Value(UGT,  0.0f, 4e9f),		Value(gType, 0.0f, 31.0f),	notUsed,	5e-10f, 0.0f,	PRECMASK_HIGHP,			UINT_GENTYPE_FUNCS(rightShift))
+				<< operInfoFunc(rightShiftName,	rightShiftOp,	IV,		Value(IV,  -127.0f, 127.0f),	Value(sType, 0.0f, 8.0f),	notUsed,	4e-3f,  0.5f,	PRECMASK_LOWP_MEDIUMP,	INT_VEC_FUNCS(rightShiftVecScalar))
+				<< operInfoFunc(rightShiftName,	rightShiftOp,	IV,		Value(IV,  -2e9f, 2e9f),		Value(sType, 0.0f, 31.0f),	notUsed,	5e-10f, 0.5f,	PRECMASK_HIGHP,			INT_VEC_FUNCS(rightShiftVecScalar))
+				<< operInfoFunc(rightShiftName,	rightShiftOp,	UV,		Value(UV,   0.0f, 255.0f),		Value(sType, 0.0f, 8.0f),	notUsed,	4e-3f,  0.0f,	PRECMASK_LOWP_MEDIUMP,	UINT_VEC_FUNCS(rightShiftVecScalar))
+				<< operInfoFunc(rightShiftName,	rightShiftOp,	UV,		Value(UV,   0.0f, 4e9f),		Value(sType, 0.0f, 31.0f),	notUsed,	5e-10f, 0.0f,	PRECMASK_HIGHP,			UINT_VEC_FUNCS(rightShiftVecScalar));
+		}
+	}
+
+	// Rest of binary operators.
+
+	binaryOpGroup
+		// Scalar relational operators.
+		<< BuiltinOperInfo("less",				"<",	B,		Value(F,   -1.0f, 1.0f),	Value(F,   -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_lessThan_float,			DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("less",				"<",	B,		Value(I,   -5.0f, 5.0f),	Value(I,   -5.0f, 5.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_lessThan_int,				DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("less",				"<",	B,		Value(U,    0.0f, 16.0f),	Value(U,    0.0f, 16.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_lessThan_uint,				DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("less_or_equal",		"<=",	B,		Value(F,   -1.0f, 1.0f),	Value(F,   -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_lessThanEqual_float,		DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("less_or_equal",		"<=",	B,		Value(I,   -5.0f, 5.0f),	Value(I,   -5.0f, 5.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_lessThanEqual_int,			DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("less_or_equal",		"<=",	B,		Value(U,    0.0f, 16.0f),	Value(U,    0.0f, 16.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_lessThanEqual_uint,		DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("greater",			">",	B,		Value(F,   -1.0f, 1.0f),	Value(F,   -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_greaterThan_float,			DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("greater",			">",	B,		Value(I,   -5.0f, 5.0f),	Value(I,   -5.0f, 5.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_greaterThan_int,			DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("greater",			">",	B,		Value(U,    0.0f, 16.0f),	Value(U,    0.0f, 16.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_greaterThan_uint,			DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("greater_or_equal",	">=",	B,		Value(F,   -1.0f, 1.0f),	Value(F,   -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_greaterThanEqual_float,	DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("greater_or_equal",	">=",	B,		Value(I,   -5.0f, 5.0f),	Value(I,   -5.0f, 5.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_greaterThanEqual_int,		DE_NULL, DE_NULL, DE_NULL)
+		<< BuiltinOperInfo("greater_or_equal",	">=",	B,		Value(U,    0.0f, 16.0f),	Value(U,    0.0f, 16.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	eval_greaterThanEqual_uint,		DE_NULL, DE_NULL, DE_NULL)
+
+		// Equality comparison operators.
+		<< BuiltinOperInfo("equal",				"==",	B,		Value(GT,  -1.0f, 1.0f),	Value(GT,  -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(allEqual))
+		<< BuiltinOperInfo("equal",				"==",	B,		Value(IGT, -5.5f, 4.7f),	Value(IGT, -2.1f, 0.1f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(allEqual))
+		<< BuiltinOperInfo("equal",				"==",	B,		Value(UGT,  0.0f, 8.0f),	Value(UGT,  3.5f, 4.5f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	UINT_GENTYPE_FUNCS(allEqual))
+		<< BuiltinOperInfo("equal",				"==",	B,		Value(BGT, -2.1f, 2.1f),	Value(BGT, -1.1f, 3.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_NA,	BOOL_GENTYPE_FUNCS(allEqual))
+		<< BuiltinOperInfo("not_equal",			"!=",	B,		Value(GT,  -1.0f, 1.0f),	Value(GT,  -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	FLOAT_GENTYPE_FUNCS(anyNotEqual))
+		<< BuiltinOperInfo("not_equal",			"!=",	B,		Value(IGT, -5.5f, 4.7f),	Value(IGT, -2.1f, 0.1f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	INT_GENTYPE_FUNCS(anyNotEqual))
+		<< BuiltinOperInfo("not_equal",			"!=",	B,		Value(UGT,  0.0f, 8.0f),	Value(UGT,  3.5f, 4.5f),	notUsed,	1.0f, 0.0f,		PRECMASK_ALL,	UINT_GENTYPE_FUNCS(anyNotEqual))
+		<< BuiltinOperInfo("not_equal",			"!=",	B,		Value(BGT, -2.1f, 2.1f),	Value(BGT, -1.1f, 3.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_NA,	BOOL_GENTYPE_FUNCS(anyNotEqual))
+
+		// Logical operators.
+		<< BuiltinOperInfo("logical_and",	"&&",	B,	Value(B, -1.0f, 1.0f),	Value(B, -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_NA,	BOOL_FUNCS(logicalAnd))
+		<< BuiltinOperInfo("logical_or",	"||",	B,	Value(B, -1.0f, 1.0f),	Value(B, -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_NA,	BOOL_FUNCS(logicalOr))
+		<< BuiltinOperInfo("logical_xor",	"^^",	B,	Value(B, -1.0f, 1.0f),	Value(B, -1.0f, 1.0f),	notUsed,	1.0f, 0.0f,		PRECMASK_NA,	BOOL_FUNCS(logicalXor));
+
+	funcInfoGroups.push_back(binaryOpGroup);
+
+	// 8.1 Angle and Trigonometry Functions.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("angle_and_trigonometry", "Angle and trigonometry function tests.")
+		<< BuiltinFuncInfo("radians",		"radians",		GT,	Value(GT, -1.0f, 1.0f),		notUsed,					notUsed,					25.0f, 0.5f,	PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(radians) )
+		<< BuiltinFuncInfo("degrees",		"degrees",		GT,	Value(GT, -1.0f, 1.0f),		notUsed,					notUsed,					0.04f, 0.5f,	PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(degrees) )
+		<< BuiltinFuncInfo("sin",			"sin",			GT,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(sin) )
+		<< BuiltinFuncInfo("sin",			"sin",			GT,	Value(GT, -1.5f, 1.5f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_LOWP,				FLOAT_GENTYPE_FUNCS(sin) )
+		<< BuiltinFuncInfo("cos",			"cos",			GT,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(cos) )
+		<< BuiltinFuncInfo("cos",			"cos",			GT,	Value(GT, -1.5f, 1.5f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_LOWP,				FLOAT_GENTYPE_FUNCS(cos) )
+		<< BuiltinFuncInfo("tan",			"tan",			GT,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(tan) )
+		<< BuiltinFuncInfo("tan",			"tan",			GT,	Value(GT, -1.5f, 5.5f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_LOWP,				FLOAT_GENTYPE_FUNCS(tan) )
+		<< BuiltinFuncInfo("asin",			"asin",			GT,	Value(GT, -1.0f, 1.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(asin) )
+		<< BuiltinFuncInfo("acos",			"acos",			GT,	Value(GT, -1.0f, 1.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(acos) )
+		<< BuiltinFuncInfo("atan",			"atan",			GT,	Value(GT, -4.0f, 4.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(atan) )
+		<< BuiltinFuncInfo("atan2",			"atan",			GT,	Value(GT, -4.0f, 4.0f),		Value(GT, 0.5f, 2.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(atan2) )
+		<< BuiltinFuncInfo("sinh",			"sinh",			GT,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(sinh) )
+		<< BuiltinFuncInfo("sinh",			"sinh",			GT,	Value(GT, -1.5f, 1.5f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_LOWP,				FLOAT_GENTYPE_FUNCS(sinh) )
+		<< BuiltinFuncInfo("cosh",			"cosh",			GT,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(cosh) )
+		<< BuiltinFuncInfo("cosh",			"cosh",			GT,	Value(GT, -1.5f, 1.5f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_LOWP,				FLOAT_GENTYPE_FUNCS(cosh) )
+		<< BuiltinFuncInfo("tanh",			"tanh",			GT,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(tanh) )
+		<< BuiltinFuncInfo("tanh",			"tanh",			GT,	Value(GT, -1.5f, 5.5f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_LOWP,				FLOAT_GENTYPE_FUNCS(tanh) )
+		<< BuiltinFuncInfo("asinh",			"asinh",		GT,	Value(GT, -1.0f, 1.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(asinh) )
+		<< BuiltinFuncInfo("acosh",			"acosh",		GT,	Value(GT, 1.0f, 2.2f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(acosh) )
+		<< BuiltinFuncInfo("atanh",			"atanh",		GT,	Value(GT, -1.0f, 1.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(atanh) )
+	);
+
+	// 8.2 Exponential Functions.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("exponential", "Exponential function tests")
+		<< BuiltinFuncInfo("pow",			"pow",			GT,	Value(GT, 0.1f, 8.0f),		Value(GT, -4.0f, 2.0f),		notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(pow) )
+		<< BuiltinFuncInfo("exp",			"exp",			GT,	Value(GT, -6.0f, 3.0f),		notUsed,					notUsed,					0.5f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(exp) )
+		<< BuiltinFuncInfo("log",			"log",			GT,	Value(GT, 0.1f, 10.0f),		notUsed,					notUsed,					0.5f, 0.3f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(log) )
+		<< BuiltinFuncInfo("exp2",			"exp2",			GT,	Value(GT, -7.0f, 2.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(exp2) )
+		<< BuiltinFuncInfo("log2",			"log2",			GT,	Value(GT, 0.1f, 10.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(log2) )
+		<< BuiltinFuncInfo("sqrt",			"sqrt",			GT,	Value(GT, 0.0f, 10.0f),		notUsed,					notUsed,					0.3f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(sqrt) )
+		<< BuiltinFuncInfo("inversesqrt",	"inversesqrt",	GT,	Value(GT, 0.5f, 10.0f),		notUsed,					notUsed,					1.0f, 0.0f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(inverseSqrt) )
+	);
+
+	// 8.3 Common Functions.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("common_functions", "Common function tests.")
+		<< BuiltinFuncInfo("abs",			"abs",			GT,	Value(GT, -2.0f, 2.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(abs) )
+		<< BuiltinFuncInfo("sign",			"sign",			GT,	Value(GT, -1.5f, 1.5f),		notUsed,					notUsed,					0.3f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(sign) )
+		<< BuiltinFuncInfo("floor",			"floor",		GT,	Value(GT, -2.5f, 2.5f),		notUsed,					notUsed,					0.2f, 0.7f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(floor) )
+		<< BuiltinFuncInfo("trunc",			"trunc",		GT,	Value(GT, -2.5f, 2.5f),		notUsed,					notUsed,					0.2f, 0.7f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(trunc) )
+		<< BuiltinFuncInfo("round",			"round",		GT,	Value(GT, -2.5f, 2.5f),		notUsed,					notUsed,					0.2f, 0.7f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(roundToEven) )
+		<< BuiltinFuncInfo("roundEven",		"roundEven",	GT,	Value(GT, -2.5f, 2.5f),		notUsed,					notUsed,					0.2f, 0.7f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(roundToEven) )
+		<< BuiltinFuncInfo("ceil",			"ceil",			GT,	Value(GT, -2.5f, 2.5f),		notUsed,					notUsed,					0.2f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(ceil) )
+		<< BuiltinFuncInfo("fract",			"fract",		GT,	Value(GT, -1.5f, 1.5f),		notUsed,					notUsed,					0.8f, 0.1f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(fract) )
+		<< BuiltinFuncInfo("mod",			"mod",			GT,	Value(GT, -2.0f, 2.0f),		Value(GT, 0.9f, 6.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(mod) )
+		<< BuiltinFuncInfo("mod",			"mod",			GT,	Value(FV, -2.0f, 2.0f),		Value(F, 0.9f, 6.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_VEC_FUNCS(modVecScalar) )
+		<< BuiltinFuncInfo("min",			"min",			GT,	Value(GT, -1.0f, 1.0f),		Value(GT, -1.0f, 1.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(min) )
+		<< BuiltinFuncInfo("min",			"min",			GT,	Value(FV, -1.0f, 1.0f),		Value(F, -1.0f, 1.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_VEC_FUNCS(minVecScalar) )
+		<< BuiltinFuncInfo("min",			"min",			IGT,Value(IGT, -4.0f, 4.0f),	Value(IGT, -4.0f, 4.0f),	notUsed,					0.125f, 0.5f,	PRECMASK_ALL,				INT_GENTYPE_FUNCS(min) )
+		<< BuiltinFuncInfo("min",			"min",			IGT,Value(IV,  -4.0f, 4.0f),	Value(I, -4.0f, 4.0f),		notUsed,					0.125f, 0.5f,	PRECMASK_ALL,				INT_VEC_FUNCS(minVecScalar) )
+		<< BuiltinFuncInfo("min",			"min",			UGT,Value(UGT, 0.0f, 8.0f),		Value(UGT, 0.0f, 8.0f),		notUsed,					0.125f, 0.0f,	PRECMASK_ALL,				UINT_GENTYPE_FUNCS(min) )
+		<< BuiltinFuncInfo("min",			"min",			UGT,Value(UV,  0.0f, 8.0f),		Value(U, 0.0f, 8.0f),		notUsed,					0.125f, 0.0f,	PRECMASK_ALL,				UINT_VEC_FUNCS(minVecScalar) )
+		<< BuiltinFuncInfo("max",			"max",			GT,	Value(GT, -1.0f, 1.0f),		Value(GT, -1.0f, 1.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(max) )
+		<< BuiltinFuncInfo("max",			"max",			GT,	Value(FV, -1.0f, 1.0f),		Value(F, -1.0f, 1.0f),		notUsed,					0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_VEC_FUNCS(maxVecScalar) )
+		<< BuiltinFuncInfo("max",			"max",			IGT,Value(IGT, -4.0f, 4.0f),	Value(IGT, -4.0f, 4.0f),	notUsed,					0.125f, 0.5f,	PRECMASK_ALL,				INT_GENTYPE_FUNCS(max) )
+		<< BuiltinFuncInfo("max",			"max",			IGT,Value(IV,  -4.0f, 4.0f),	Value(I, -4.0f, 4.0f),		notUsed,					0.125f, 0.5f,	PRECMASK_ALL,				INT_VEC_FUNCS(maxVecScalar) )
+		<< BuiltinFuncInfo("max",			"max",			UGT,Value(UGT, 0.0f, 8.0f),		Value(UGT, 0.0f, 8.0f),		notUsed,					0.125f, 0.0f,	PRECMASK_ALL,				UINT_GENTYPE_FUNCS(max) )
+		<< BuiltinFuncInfo("max",			"max",			UGT,Value(UV,  0.0f, 8.0f),		Value(U, 0.0f, 8.0f),		notUsed,					0.125f, 0.0f,	PRECMASK_ALL,				UINT_VEC_FUNCS(maxVecScalar) )
+		<< BuiltinFuncInfo("clamp",			"clamp",		GT,	Value(GT, -1.0f, 1.0f),		Value(GT, -0.5f, 0.5f),		Value(GT, 0.5f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(clamp) )
+		<< BuiltinFuncInfo("clamp",			"clamp",		GT,	Value(FV, -1.0f, 1.0f),		Value(F, -0.5f, 0.5f),		Value(F, 0.5f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_VEC_FUNCS(clampVecScalarScalar) )
+		<< BuiltinFuncInfo("clamp",			"clamp",		IGT,Value(IGT, -4.0f, 4.0f),	Value(IGT, -2.0f, 2.0f),	Value(IGT, 2.0f, 4.0f),		0.125f, 0.5f,	PRECMASK_ALL,				INT_GENTYPE_FUNCS(clamp) )
+		<< BuiltinFuncInfo("clamp",			"clamp",		IGT,Value(IV,  -4.0f, 4.0f),	Value(I, -2.0f, 2.0f),		Value(I, 2.0f, 4.0f),		0.125f, 0.5f,	PRECMASK_ALL,				INT_VEC_FUNCS(clampVecScalarScalar) )
+		<< BuiltinFuncInfo("clamp",			"clamp",		UGT,Value(UGT, 0.0f, 8.0f),		Value(UGT, 2.0f, 6.0f),		Value(UGT, 6.0f, 8.0f),		0.125f, 0.0f,	PRECMASK_ALL,				UINT_GENTYPE_FUNCS(clamp) )
+		<< BuiltinFuncInfo("clamp",			"clamp",		UGT,Value(UV,  0.0f, 8.0f),		Value(U,   2.0f, 6.0f),		Value(U, 6.0f, 8.0f),		0.125f, 0.0f,	PRECMASK_ALL,				UINT_VEC_FUNCS(clampVecScalarScalar) )
+		<< BuiltinFuncInfo("mix",			"mix",			GT,	Value(GT, -1.0f, 1.0f),		Value(GT, -1.0f, 1.0f),		Value(GT, 0.0f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(mix) )
+		<< BuiltinFuncInfo("mix",			"mix",			GT,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),		Value(F, 0.0f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_VEC_FUNCS(mixVecVecScalar) )
+		<< BuiltinFuncInfo("step",			"step",			GT,	Value(GT, -1.0f, 1.0f),		Value(GT, -1.0f, 0.0f),		notUsed,					0.5f, 0.25f,	PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(step) )
+		<< BuiltinFuncInfo("step",			"step",			GT,	Value(F, -1.0f, 1.0f),		Value(FV, -1.0f, 0.0f),		notUsed,					0.5f, 0.25f,	PRECMASK_ALL,				FLOAT_VEC_FUNCS(stepScalarVec) )
+		<< BuiltinFuncInfo("smoothstep",	"smoothstep",	GT,	Value(GT, -0.5f, 0.0f),		Value(GT, 0.1f, 1.0f),		Value(GT, -1.0f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_GENTYPE_FUNCS(smoothStep) )
+		<< BuiltinFuncInfo("smoothstep",	"smoothstep",	GT,	Value(F, -0.5f, 0.0f),		Value(F, 0.1f, 1.0f),		Value(FV, -1.0f, 1.0f),		0.5f, 0.5f,		PRECMASK_ALL,				FLOAT_VEC_FUNCS(smoothStepScalarScalarVec) )
+	);
+
+	// 8.4 Geometric Functions.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("geometric", "Geometric function tests.")
+		<< BuiltinFuncInfo("length",		"length",		F,	Value(GT, -5.0f, 5.0f),		notUsed,					notUsed,					0.1f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(length) )
+		<< BuiltinFuncInfo("distance",		"distance",		F,	Value(GT, -5.0f, 5.0f),		Value(GT, -5.0f, 5.0f),		notUsed,					0.1f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(distance) )
+		<< BuiltinFuncInfo("dot",			"dot",			F,	Value(GT, -5.0f, 5.0f),		Value(GT, -5.0f, 5.0f),		notUsed,					0.1f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(dot) )
+		<< BuiltinFuncInfo("cross",			"cross",		V3,	Value(GT, -5.0f, 5.0f),		Value(GT, -5.0f, 5.0f),		notUsed,					0.1f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		DE_NULL, DE_NULL, eval_cross_vec3, DE_NULL )
+		<< BuiltinFuncInfo("normalize",		"normalize",	GT,	Value(GT, 0.1f, 4.0f),		notUsed,					notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(normalize) )
+		<< BuiltinFuncInfo("faceforward",	"faceforward",	GT,	Value(GT, -5.0f, 5.0f),		Value(GT, -5.0f, 5.0f),		Value(GT, -1.0f, 1.0f),		0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(faceForward) )
+		<< BuiltinFuncInfo("reflect",		"reflect",		GT,	Value(GT, -0.8f, -0.5f),	Value(GT, 0.5f, 0.8f),		notUsed,					0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(reflect) )
+		<< BuiltinFuncInfo("refract",		"refract",		GT,	Value(GT, -0.8f, 1.2f),		Value(GT, -1.1f, 0.5f),		Value(F, 0.2f, 1.5f),		0.5f, 0.5f,		PRECMASK_MEDIUMP_HIGHP,		FLOAT_GENTYPE_FUNCS(refract) )
+	);
+
+	// 8.5 Matrix Functions.
+	// separate matrix tests?
+//	funcInfoGroups.push_back(
+//		BuiltinFuncGroup("matrix", "Matrix function tests.")
+//		<< BuiltinFuncInfo("matrixCompMult",	"matrixCompMult",	M, ... )
+//	);
+
+	// 8.6 Vector Relational Functions.
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("float_compare", "Floating point comparison tests.")
+		<< BuiltinFuncInfo("lessThan",			"lessThan",			BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(lessThan) )
+		<< BuiltinFuncInfo("lessThanEqual",		"lessThanEqual",	BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(lessThanEqual) )
+		<< BuiltinFuncInfo("greaterThan",		"greaterThan",		BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(greaterThan) )
+		<< BuiltinFuncInfo("greaterThanEqual",	"greaterThanEqual",	BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(greaterThanEqual) )
+		<< BuiltinFuncInfo("equal",				"equal",			BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(equal) )
+		<< BuiltinFuncInfo("notEqual",			"notEqual",			BV,	Value(FV, -1.0f, 1.0f),		Value(FV, -1.0f, 1.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	FLOAT_VEC_FUNCS(notEqual) )
+	);
+
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("int_compare", "Integer comparison tests.")
+		<< BuiltinFuncInfo("lessThan",			"lessThan",			BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(lessThan) )
+		<< BuiltinFuncInfo("lessThanEqual",		"lessThanEqual",	BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(lessThanEqual) )
+		<< BuiltinFuncInfo("greaterThan",		"greaterThan",		BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(greaterThan) )
+		<< BuiltinFuncInfo("greaterThanEqual",	"greaterThanEqual",	BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(greaterThanEqual) )
+		<< BuiltinFuncInfo("equal",				"equal",			BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(equal) )
+		<< BuiltinFuncInfo("notEqual",			"notEqual",			BV,	Value(IV, -5.2f, 4.9f),		Value(IV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_ALL,	INT_VEC_FUNCS(notEqual) )
+	);
+
+	funcInfoGroups.push_back(
+		BuiltinFuncGroup("bool_compare", "Boolean comparison tests.")
+		<< BuiltinFuncInfo("equal",				"equal",			BV,	Value(BV, -5.2f, 4.9f),		Value(BV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_NA,	BOOL_VEC_FUNCS(equal) )
+		<< BuiltinFuncInfo("notEqual",			"notEqual",			BV,	Value(BV, -5.2f, 4.9f),		Value(BV, -5.0f, 5.0f),	notUsed, 1.0f, 0.0f, PRECMASK_NA,	BOOL_VEC_FUNCS(notEqual) )
+		<< BuiltinFuncInfo("any",				"any",				B,	Value(BV, -1.0f, 0.3f),		notUsed,				notUsed, 1.0f, 0.0f, PRECMASK_NA,	BOOL_VEC_FUNCS(any) )
+		<< BuiltinFuncInfo("all",				"all",				B,	Value(BV, -0.3f, 1.0f),		notUsed,				notUsed, 1.0f, 0.0f, PRECMASK_NA,	BOOL_VEC_FUNCS(all) )
+		<< BuiltinFuncInfo("not",				"not",				BV,	Value(BV, -1.0f, 1.0f),		notUsed,				notUsed, 1.0f, 0.0f, PRECMASK_NA,	BOOL_VEC_FUNCS(boolNot) )
+	);
+
+	static const ShaderType s_shaderTypes[] =
+	{
+		SHADERTYPE_VERTEX,
+		SHADERTYPE_FRAGMENT
+	};
+
+	static const DataType s_floatTypes[] =
+	{
+		TYPE_FLOAT,
+		TYPE_FLOAT_VEC2,
+		TYPE_FLOAT_VEC3,
+		TYPE_FLOAT_VEC4
+	};
+
+	static const DataType s_intTypes[] =
+	{
+		TYPE_INT,
+		TYPE_INT_VEC2,
+		TYPE_INT_VEC3,
+		TYPE_INT_VEC4
+	};
+
+	static const DataType s_uintTypes[] =
+	{
+		TYPE_UINT,
+		TYPE_UINT_VEC2,
+		TYPE_UINT_VEC3,
+		TYPE_UINT_VEC4
+	};
+
+	static const DataType s_boolTypes[] =
+	{
+		TYPE_BOOL,
+		TYPE_BOOL_VEC2,
+		TYPE_BOOL_VEC3,
+		TYPE_BOOL_VEC4
+	};
+
+	for (int outerGroupNdx = 0; outerGroupNdx < (int)funcInfoGroups.size(); outerGroupNdx++)
+	{
+		// Create outer group.
+		const BuiltinFuncGroup& outerGroupInfo = funcInfoGroups[outerGroupNdx];
+		TestCaseGroup* outerGroup = new TestCaseGroup(m_context, outerGroupInfo.name, outerGroupInfo.description);
+		addChild(outerGroup);
+
+		// Only create new group if name differs from previous one.
+		TestCaseGroup* innerGroup = DE_NULL;
+
+		for (int funcInfoNdx = 0; funcInfoNdx < (int)outerGroupInfo.funcInfos.size(); funcInfoNdx++)
+		{
+			const BuiltinFuncInfo&	funcInfo 		= outerGroupInfo.funcInfos[funcInfoNdx];
+			const char*				shaderFuncName	= funcInfo.shaderFuncName;
+			bool					isBoolCase		= (funcInfo.precisionMask == PRECMASK_NA);
+			bool					isBoolOut		= (funcInfo.outValue & (VALUE_BOOL | VALUE_BOOL_VEC | VALUE_BOOL_GENTYPE)) != 0;
+			bool					isIntOut		= (funcInfo.outValue & (VALUE_INT | VALUE_INT_VEC | VALUE_INT_GENTYPE)) != 0;
+			bool					isUintOut		= (funcInfo.outValue & (VALUE_UINT | VALUE_UINT_VEC | VALUE_UINT_GENTYPE)) != 0;
+			bool					isFloatOut		= !isBoolOut && !isIntOut && !isUintOut;
+
+			if (!innerGroup || (string(innerGroup->getName()) != funcInfo.caseName))
+			{
+				string groupDesc = string("Built-in function ") + shaderFuncName + "() tests.";
+				innerGroup = new TestCaseGroup(m_context, funcInfo.caseName, groupDesc.c_str());
+				outerGroup->addChild(innerGroup);
+			}
+
+			for (int inScalarSize = 1; inScalarSize <= 4; inScalarSize++)
+			{
+				int			outScalarSize	= ((funcInfo.outValue == VALUE_FLOAT) || (funcInfo.outValue == VALUE_BOOL)) ? 1 : inScalarSize; // \todo [petri] Int.
+				DataType	outDataType		= isFloatOut	? s_floatTypes[outScalarSize - 1]
+											: isIntOut		? s_intTypes[outScalarSize - 1]
+											: isUintOut		? s_uintTypes[outScalarSize - 1]
+											: isBoolOut		? s_boolTypes[outScalarSize - 1]
+											: TYPE_LAST;
+
+				ShaderEvalFunc evalFunc = DE_NULL;
+				if      (inScalarSize == 1)	evalFunc = funcInfo.evalFuncScalar;
+				else if (inScalarSize == 2)	evalFunc = funcInfo.evalFuncVec2;
+				else if (inScalarSize == 3)	evalFunc = funcInfo.evalFuncVec3;
+				else if (inScalarSize == 4)	evalFunc = funcInfo.evalFuncVec4;
+				else DE_ASSERT(false);
+
+				// Skip if no valid eval func.
+				// \todo [petri] Better check for V3 only etc. cases?
+				if (evalFunc == DE_NULL)
+					continue;
+
+				for (int precision = 0; precision < PRECISION_LAST; precision++)
+				{
+					if ((funcInfo.precisionMask & (1<<precision)) ||
+						(funcInfo.precisionMask == PRECMASK_NA && precision == PRECISION_MEDIUMP)) // use mediump interpolators for booleans
+					{
+						const char*	precisionStr	= getPrecisionName((Precision)precision);
+						string		precisionPrefix	= isBoolCase ? "" : (string(precisionStr) + "_");
+
+						for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+						{
+							ShaderType		shaderType		= s_shaderTypes[shaderTypeNdx];
+							ShaderDataSpec	shaderSpec;
+							const char*		shaderTypeName	= getShaderTypeName(shaderType);
+							bool			isVertexCase	= (ShaderType)shaderType == SHADERTYPE_VERTEX;
+							bool			isUnaryOp		= (funcInfo.input1.valueType == VALUE_NONE);
+
+							// \note Data type names will be added to description and name in a following loop.
+							string desc	= string("Built-in function ") + shaderFuncName + "(";
+							string name = precisionPrefix;
+
+							// Generate shader op.
+							string shaderOp = string("res = ");
+
+							// Setup shader data info.
+							shaderSpec.numInputs		= 0;
+							shaderSpec.precision		= isBoolCase ? PRECISION_LAST : (Precision)precision;
+							shaderSpec.output			= outDataType;
+							shaderSpec.resultScale		= funcInfo.resultScale;
+							shaderSpec.resultBias		= funcInfo.resultBias;
+							shaderSpec.referenceScale	= funcInfo.referenceScale;
+							shaderSpec.referenceBias	= funcInfo.referenceBias;
+
+							if (funcInfo.type == OPERATOR)
+							{
+								if (isUnaryOp && funcInfo.isUnaryPrefix)
+									shaderOp += shaderFuncName;
+							}
+							else if (funcInfo.type == FUNCTION)
+								shaderOp += string(shaderFuncName) + "(";
+							else // SIDE_EFFECT_OPERATOR
+								shaderOp += "in0;\n\t";
+
+							for (int inputNdx = 0; inputNdx < MAX_INPUTS; inputNdx++)
+							{
+								const Value&	prevV			= (inputNdx == 1) ? funcInfo.input0 : (inputNdx == 2) ? funcInfo.input1 : funcInfo.input2;
+								const Value&	v				= (inputNdx == 0) ? funcInfo.input0 : (inputNdx == 1) ? funcInfo.input1 : funcInfo.input2;
+
+								if (v.valueType == VALUE_NONE)
+									continue; // Skip unused input.
+
+								int				prevInScalarSize	= isScalarType(prevV.valueType) ? 1 : inScalarSize;
+								DataType		prevInDataType		= isFloatType(prevV.valueType)	? s_floatTypes[prevInScalarSize - 1]
+																	: isIntType(prevV.valueType)	? s_intTypes[prevInScalarSize - 1]
+																	: isUintType(prevV.valueType)	? s_uintTypes[prevInScalarSize - 1]
+																	: isBoolType(prevV.valueType)	? s_boolTypes[prevInScalarSize - 1]
+																	: TYPE_LAST;
+
+								int				curInScalarSize		= isScalarType(v.valueType) ? 1 : inScalarSize;
+								DataType		curInDataType		= isFloatType(v.valueType)	? s_floatTypes[curInScalarSize - 1]
+																	: isIntType(v.valueType)	? s_intTypes[curInScalarSize - 1]
+																	: isUintType(v.valueType)	? s_uintTypes[curInScalarSize - 1]
+																	: isBoolType(v.valueType)	? s_boolTypes[curInScalarSize - 1]
+																	: TYPE_LAST;
+
+								// Write input type(s) to case description and name.
+
+								if (inputNdx > 0)
+									desc += ", ";
+
+								desc += getDataTypeName(curInDataType);
+
+								if (inputNdx == 0 || prevInDataType != curInDataType) // \note Only write input type to case name if different from previous input type (avoid overly long names).
+									name += string("") + getDataTypeName(curInDataType) + "_";
+
+								// Generate op input source.
+
+								if (funcInfo.type == OPERATOR || funcInfo.type == FUNCTION)
+								{
+									if (inputNdx != 0)
+									{
+										if (funcInfo.type == OPERATOR && !isUnaryOp)
+											shaderOp += " " + string(shaderFuncName) + " ";
+										else
+											shaderOp += ", ";
+									}
+
+									shaderOp += "in" + de::toString(inputNdx);
+
+									if (funcInfo.type == OPERATOR && isUnaryOp && !funcInfo.isUnaryPrefix)
+										shaderOp += string(shaderFuncName);
+								}
+								else
+								{
+									DE_ASSERT(funcInfo.type == SIDE_EFFECT_OPERATOR);
+
+									if (inputNdx != 0 || (isUnaryOp && funcInfo.isUnaryPrefix))
+										shaderOp += string("") + (isUnaryOp ? "" : " ") + shaderFuncName + (isUnaryOp ? "" : " ");
+
+									shaderOp += inputNdx == 0 ? "res" : "in" + de::toString(inputNdx); // \note in0 has already been assigned to res, so start from in1.
+
+									if (isUnaryOp && !funcInfo.isUnaryPrefix)
+										shaderOp += shaderFuncName;
+								}
+
+								// Fill in shader info.
+								shaderSpec.inputs[shaderSpec.numInputs++] = ShaderValue(curInDataType, v.rangeMin, v.rangeMax);
+							}
+
+							if (funcInfo.type == FUNCTION)
+								shaderOp += ")";
+
+							shaderOp += ";";
+
+							desc += ").";
+							name += shaderTypeName;
+
+							// Create the test case.
+							innerGroup->addChild(new ShaderOperatorCase(m_context, name.c_str(), desc.c_str(), isVertexCase, evalFunc, shaderOp, shaderSpec));
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// The ?: selection operator.
+
+	static const struct
+	{
+		DataType		type; // The type of "Y" and "Z" operands in "X ? Y : Z" (X is always bool).
+		ShaderEvalFunc	evalFunc;
+	} s_selectionInfo[] =
+	{
+		{ TYPE_FLOAT,		eval_selection_float	},
+		{ TYPE_FLOAT_VEC2,	eval_selection_vec2		},
+		{ TYPE_FLOAT_VEC3,	eval_selection_vec3		},
+		{ TYPE_FLOAT_VEC4,	eval_selection_vec4		},
+		{ TYPE_INT,			eval_selection_int		},
+		{ TYPE_INT_VEC2,	eval_selection_ivec2	},
+		{ TYPE_INT_VEC3,	eval_selection_ivec3	},
+		{ TYPE_INT_VEC4,	eval_selection_ivec4	},
+		{ TYPE_UINT,		eval_selection_uint		},
+		{ TYPE_UINT_VEC2,	eval_selection_uvec2	},
+		{ TYPE_UINT_VEC3,	eval_selection_uvec3	},
+		{ TYPE_UINT_VEC4,	eval_selection_uvec4	},
+		{ TYPE_BOOL,		eval_selection_bool		},
+		{ TYPE_BOOL_VEC2,	eval_selection_bvec2	},
+		{ TYPE_BOOL_VEC3,	eval_selection_bvec3	},
+		{ TYPE_BOOL_VEC4,	eval_selection_bvec4	}
+	};
+
+	TestCaseGroup* selectionGroup = new TestCaseGroup(m_context, "selection", "Selection operator tests");
+	addChild(selectionGroup);
+
+	for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_selectionInfo); typeNdx++)
+	{
+		DataType		curType			= s_selectionInfo[typeNdx].type;
+		ShaderEvalFunc	evalFunc		= s_selectionInfo[typeNdx].evalFunc;
+		bool			isBoolCase		= isDataTypeBoolOrBVec(curType);
+		bool			isFloatCase		= isDataTypeFloatOrVec(curType);
+		bool			isIntCase		= isDataTypeIntOrIVec(curType);
+		bool			isUintCase		= isDataTypeUintOrUVec(curType);
+		const char*		dataTypeStr		= getDataTypeName(curType);
+
+		DE_ASSERT(isBoolCase || isFloatCase || isIntCase || isUintCase);
+		DE_UNREF(isIntCase);
+
+		for (int precision = 0; precision < (int)PRECISION_LAST; precision++)
+		{
+			if (isBoolCase && precision != PRECISION_MEDIUMP) // Use mediump interpolators for booleans.
+				continue;
+
+			const char*	precisionStr	= getPrecisionName((Precision)precision);
+			string		precisionPrefix	= isBoolCase ? "" : (string(precisionStr) + "_");
+
+			for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+			{
+				ShaderType		shaderType		= s_shaderTypes[shaderTypeNdx];
+				ShaderDataSpec	shaderSpec;
+				const char*		shaderTypeName	= getShaderTypeName(shaderType);
+				bool			isVertexCase	= (ShaderType)shaderType == SHADERTYPE_VERTEX;
+
+				string name	= precisionPrefix + dataTypeStr + "_" + shaderTypeName;
+
+				shaderSpec.numInputs		= 3;
+				shaderSpec.precision		= isBoolCase ? PRECISION_LAST : (Precision)precision;
+				shaderSpec.output			= curType;
+				shaderSpec.resultScale		= isBoolCase ? 1.0f : isFloatCase ? 0.5f : isUintCase ? 0.5f : 0.1f;
+				shaderSpec.resultBias		= isBoolCase ? 0.0f : isFloatCase ? 0.5f : isUintCase ? 0.0f : 0.5f;
+				shaderSpec.referenceScale	= shaderSpec.resultScale;
+				shaderSpec.referenceBias	= shaderSpec.resultBias;
+
+				float rangeMin = isBoolCase ? -1.0f : isFloatCase ? -1.0f : isUintCase ? 0.0f : -5.0f;
+				float rangeMax = isBoolCase ?  1.0f : isFloatCase ?  1.0f : isUintCase ? 2.0f :  5.0f;
+
+				shaderSpec.inputs[0] = ShaderValue(TYPE_BOOL, -1.0f, 1.0f);
+				shaderSpec.inputs[1] = ShaderValue(curType, rangeMin, rangeMax);
+				shaderSpec.inputs[2] = ShaderValue(curType, rangeMin, rangeMax);
+
+				selectionGroup->addChild(new ShaderOperatorCase(m_context, name.c_str(), "", isVertexCase, evalFunc, "res = in0 ? in1 : in2;", shaderSpec));
+			}
+		}
+	}
+
+	// The sequence operator (comma).
+
+	TestCaseGroup* sequenceGroup = new TestCaseGroup(m_context, "sequence", "Sequence operator tests");
+	addChild(sequenceGroup);
+
+	TestCaseGroup* sequenceNoSideEffGroup = new TestCaseGroup(m_context, "no_side_effects", "Sequence tests without side-effects");
+	TestCaseGroup* sequenceSideEffGroup = new TestCaseGroup(m_context, "side_effects", "Sequence tests with side-effects");
+	sequenceGroup->addChild(sequenceNoSideEffGroup);
+	sequenceGroup->addChild(sequenceSideEffGroup);
+
+	static const struct
+	{
+		bool			containsSideEffects;
+		const char*		caseName;
+		const char*		expressionStr;
+		int				numInputs;
+		DataType		inputTypes[MAX_INPUTS];
+		DataType		resultType;
+		ShaderEvalFunc	evalFunc;
+	} s_sequenceCases[] =
+	{
+		{ false,	"vec4",					"in0, in2 + in1, in1 + in0",							3,	{ TYPE_FLOAT_VEC4,	TYPE_FLOAT_VEC4,	TYPE_FLOAT_VEC4	},	TYPE_FLOAT_VEC4,	evalSequenceNoSideEffCase0 },
+		{ false,	"float_uint",			"in0 + in2, in1 + in1",									3,	{ TYPE_FLOAT,		TYPE_UINT,			TYPE_FLOAT		},	TYPE_UINT,			evalSequenceNoSideEffCase1 },
+		{ false,	"bool_vec2",			"in0 && in1, in0, ivec2(vec2(in0) + in2)",				3,	{ TYPE_BOOL,		TYPE_BOOL,			TYPE_FLOAT_VEC2	},	TYPE_INT_VEC2,		evalSequenceNoSideEffCase2 },
+		{ false,	"vec4_ivec4_bvec4",		"in0 + vec4(in1), in2, in1",							3,	{ TYPE_FLOAT_VEC4,	TYPE_INT_VEC4,		TYPE_BOOL_VEC4	},	TYPE_INT_VEC4,		evalSequenceNoSideEffCase3 },
+
+		{ true,		"vec4",					"in0++, in1 = in0 + in2, in2 = in1",					3,	{ TYPE_FLOAT_VEC4,	TYPE_FLOAT_VEC4,	TYPE_FLOAT_VEC4	},	TYPE_FLOAT_VEC4,	evalSequenceSideEffCase0 },
+		{ true,		"float_uint",			"in1++, in0 = float(in1), in1 = uint(in0 + in2)",		3,	{ TYPE_FLOAT,		TYPE_UINT,			TYPE_FLOAT		},	TYPE_UINT,			evalSequenceSideEffCase1 },
+		{ true,		"bool_vec2",			"in1 = in0, in2++, in2 = in2 + vec2(in1), ivec2(in2)",	3,	{ TYPE_BOOL,		TYPE_BOOL,			TYPE_FLOAT_VEC2	},	TYPE_INT_VEC2,		evalSequenceSideEffCase2 },
+		{ true,		"vec4_ivec4_bvec4",		"in0 = in0 + vec4(in2), in1 = in1 + ivec4(in0), in1++",	3,	{ TYPE_FLOAT_VEC4,	TYPE_INT_VEC4,		TYPE_BOOL_VEC4	},	TYPE_INT_VEC4,		evalSequenceSideEffCase3 }
+	};
+
+	for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(s_sequenceCases); caseNdx++)
+	{
+		for (int precision = 0; precision < (int)PRECISION_LAST; precision++)
+		{
+			for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
+			{
+				ShaderType		shaderType		= s_shaderTypes[shaderTypeNdx];
+				ShaderDataSpec	shaderSpec;
+				const char*		shaderTypeName	= getShaderTypeName(shaderType);
+				bool			isVertexCase	= (ShaderType)shaderType == SHADERTYPE_VERTEX;
+
+				string name	= string("") + getPrecisionName((Precision)precision) + "_" + s_sequenceCases[caseNdx].caseName + "_" + shaderTypeName;
+
+				shaderSpec.numInputs		= s_sequenceCases[caseNdx].numInputs;
+				shaderSpec.precision		= (Precision)precision;
+				shaderSpec.output			= s_sequenceCases[caseNdx].resultType;
+				shaderSpec.resultScale		= 0.5f;
+				shaderSpec.resultBias		= 0.0f;
+				shaderSpec.referenceScale	= shaderSpec.resultScale;
+				shaderSpec.referenceBias	= shaderSpec.resultBias;
+
+				for (int inputNdx = 0; inputNdx < s_sequenceCases[caseNdx].numInputs; inputNdx++)
+				{
+					DataType	type		= s_sequenceCases[caseNdx].inputTypes[inputNdx];
+					float		rangeMin	= isDataTypeFloatOrVec(type) ? -0.5f : isDataTypeIntOrIVec(type) ? -2.0f : isDataTypeUintOrUVec(type) ? 0.0f : -1.0f;
+					float		rangeMax	= isDataTypeFloatOrVec(type) ?  0.5f : isDataTypeIntOrIVec(type) ?  2.0f : isDataTypeUintOrUVec(type) ? 2.0f :  1.0f;
+
+					shaderSpec.inputs[inputNdx] = ShaderValue(type, rangeMin, rangeMax);
+				}
+
+				string expression = string("") + "res = (" + s_sequenceCases[caseNdx].expressionStr + ");";
+
+				TestCaseGroup* group = s_sequenceCases[caseNdx].containsSideEffects ? sequenceSideEffGroup : sequenceNoSideEffGroup;
+				group->addChild(new ShaderOperatorCase(m_context, name.c_str(), "", isVertexCase, s_sequenceCases[caseNdx].evalFunc, expression.c_str(), shaderSpec));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderOperatorTests.hpp b/modules/gles3/functional/es3fShaderOperatorTests.hpp
new file mode 100644
index 0000000..589873d
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderOperatorTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADEROPERATORTESTS_HPP
+#define _ES3FSHADEROPERATORTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader operators tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderOperatorTests : public TestCaseGroup
+{
+public:
+							ShaderOperatorTests		(Context& context);
+	virtual					~ShaderOperatorTests	(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderOperatorTests		(const ShaderOperatorTests&);		// not allowed!
+	ShaderOperatorTests&	operator=				(const ShaderOperatorTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADEROPERATORTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderPackingFunctionTests.cpp b/modules/gles3/functional/es3fShaderPackingFunctionTests.cpp
new file mode 100644
index 0000000..2fd78a0
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderPackingFunctionTests.cpp
@@ -0,0 +1,782 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Floating-point packing and unpacking function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderPackingFunctionTests.hpp"
+#include "glsShaderExecUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuFormatUtil.hpp"
+#include "tcuFloat.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using tcu::TestLog;
+using namespace gls::ShaderExecUtil;
+
+namespace
+{
+
+inline deUint32 getUlpDiff (float a, float b)
+{
+	const deUint32	aBits	= tcu::Float32(a).bits();
+	const deUint32	bBits	= tcu::Float32(b).bits();
+	return aBits > bBits ? aBits - bBits : bBits - aBits;
+}
+
+struct HexFloat
+{
+	const float value;
+	HexFloat (const float value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const HexFloat& v)
+{
+	return str << v.value << " / " << tcu::toHex(tcu::Float32(v.value).bits());
+}
+
+} // anonymous
+
+// ShaderPackingFunctionCase
+
+class ShaderPackingFunctionCase : public TestCase
+{
+public:
+								ShaderPackingFunctionCase	(Context& context, const char* name, const char* description, glu::ShaderType shaderType);
+								~ShaderPackingFunctionCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+
+protected:
+	glu::ShaderType				m_shaderType;
+	ShaderSpec					m_spec;
+	ShaderExecutor*				m_executor;
+
+private:
+								ShaderPackingFunctionCase	(const ShaderPackingFunctionCase& other);
+	ShaderPackingFunctionCase&	operator=					(const ShaderPackingFunctionCase& other);
+};
+
+ShaderPackingFunctionCase::ShaderPackingFunctionCase (Context& context, const char* name, const char* description, glu::ShaderType shaderType)
+	: TestCase		(context, name, description)
+	, m_shaderType	(shaderType)
+	, m_executor	(DE_NULL)
+{
+	m_spec.version = glu::GLSL_VERSION_300_ES;
+}
+
+ShaderPackingFunctionCase::~ShaderPackingFunctionCase (void)
+{
+	ShaderPackingFunctionCase::deinit();
+}
+
+void ShaderPackingFunctionCase::init (void)
+{
+	DE_ASSERT(!m_executor);
+
+	m_executor = createExecutor(m_context.getRenderContext(), m_shaderType, m_spec);
+	m_testCtx.getLog() << m_executor;
+
+	if (!m_executor->isOk())
+		throw tcu::TestError("Compile failed");
+}
+
+void ShaderPackingFunctionCase::deinit (void)
+{
+	delete m_executor;
+	m_executor = DE_NULL;
+}
+
+// Test cases
+
+static const char* getPrecisionPostfix (glu::Precision precision)
+{
+	static const char* s_postfix[] =
+	{
+		"_lowp",
+		"_mediump",
+		"_highp"
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_postfix) == glu::PRECISION_LAST);
+	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
+	return s_postfix[precision];
+}
+
+static const char* getShaderTypePostfix (glu::ShaderType shaderType)
+{
+	static const char* s_postfix[] =
+	{
+		"_vertex",
+		"_fragment"
+	};
+	DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
+	return s_postfix[shaderType];
+}
+
+class PackSnorm2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	PackSnorm2x16Case (Context& context, glu::ShaderType shaderType, glu::Precision precision)
+		: ShaderPackingFunctionCase	(context, (string("packsnorm2x16") + getPrecisionPostfix(precision) + getShaderTypePostfix(shaderType)).c_str(), "packSnorm2x16", shaderType)
+		, m_precision				(precision)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_FLOAT_VEC2, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = packSnorm2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<tcu::Vec2>		inputs;
+		std::vector<deUint32>		outputs;
+		const int					maxDiff		= m_precision == glu::PRECISION_HIGHP	? 1		:		// Rounding only.
+												  m_precision == glu::PRECISION_MEDIUMP	? 33	:		// (2^-10) * (2^15) + 1
+												  m_precision == glu::PRECISION_LOWP	? 129	: 0;	// (2^-8) * (2^15) + 1
+
+		// Special values to check.
+		inputs.push_back(tcu::Vec2(0.0f, 0.0f));
+		inputs.push_back(tcu::Vec2(-1.0f, 1.0f));
+		inputs.push_back(tcu::Vec2(0.5f, -0.5f));
+		inputs.push_back(tcu::Vec2(-1.5f, 1.5f));
+		inputs.push_back(tcu::Vec2(0.25f, -0.75f));
+
+		// Random values, mostly in range.
+		for (int ndx = 0; ndx < 15; ndx++)
+		{
+			const float x = rnd.getFloat()*2.5f - 1.25f;
+			const float y = rnd.getFloat()*2.5f - 1.25f;
+			inputs.push_back(tcu::Vec2(x, y));
+		}
+
+		// Large random values.
+		for (int ndx = 0; ndx < 80; ndx++)
+		{
+			const float x = rnd.getFloat()*1e6f - 0.5e6f;
+			const float y = rnd.getFloat()*1e6f - 0.5e6f;
+			inputs.push_back(tcu::Vec2(x, y));
+		}
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < numValues; valNdx++)
+			{
+				const deUint16	ref0	= (deUint16)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].x(), -1.0f, 1.0f) * 32767.0f), -(1<<15), (1<<15)-1);
+				const deUint16	ref1	= (deUint16)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].y(), -1.0f, 1.0f) * 32767.0f), -(1<<15), (1<<15)-1);
+				const deUint32	ref		= (ref1 << 16) | ref0;
+				const deUint32	res		= outputs[valNdx];
+				const deUint16	res0	= (deUint16)(res & 0xffff);
+				const deUint16	res1	= (deUint16)(res >> 16);
+				const int		diff0	= de::abs((int)ref0 - (int)res0);
+				const int		diff1	= de::abs((int)ref1 - (int)res1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx
+															   << ", expected packSnorm2x16(" << inputs[valNdx] << ") = " << tcu::toHex(ref)
+															   << ", got " << tcu::toHex(res)
+															   << "\n  diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	glu::Precision m_precision;
+};
+
+class UnpackSnorm2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	UnpackSnorm2x16Case (Context& context, glu::ShaderType shaderType)
+		: ShaderPackingFunctionCase(context, (string("unpacksnorm2x16") + getShaderTypePostfix(shaderType)).c_str(), "unpackSnorm2x16", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = unpackSnorm2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32				maxDiff		= 1; // Rounding error.
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<deUint32>		inputs;
+		std::vector<tcu::Vec2>		outputs;
+
+		inputs.push_back(0x00000000u);
+		inputs.push_back(0x7fff8000u);
+		inputs.push_back(0x80007fffu);
+		inputs.push_back(0xffffffffu);
+		inputs.push_back(0x0001fffeu);
+
+		// Random values.
+		for (int ndx = 0; ndx < 95; ndx++)
+			inputs.push_back(rnd.getUint32());
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deInt16	in0			= (deInt16)(deUint16)(inputs[valNdx] & 0xffff);
+				const deInt16	in1			= (deInt16)(deUint16)(inputs[valNdx] >> 16);
+				const float		ref0		= de::clamp(float(in0) / 32767.f, -1.0f, 1.0f);
+				const float		ref1		= de::clamp(float(in1) / 32767.f, -1.0f, 1.0f);
+				const float		res0		= outputs[valNdx].x();
+				const float		res1		= outputs[valNdx].y();
+
+				const deUint32	diff0	= getUlpDiff(ref0, res0);
+				const deUint32	diff1	= getUlpDiff(ref1, res1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx << ",\n"
+															   << "  expected unpackSnorm2x16(" << tcu::toHex(inputs[valNdx]) << ") = "
+															   << "vec2(" << HexFloat(ref0) << ", " << HexFloat(ref1) << ")"
+															   << ", got vec2(" << HexFloat(res0) << ", " << HexFloat(res1) << ")"
+															   << "\n  ULP diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class PackUnorm2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	PackUnorm2x16Case (Context& context, glu::ShaderType shaderType, glu::Precision precision)
+		: ShaderPackingFunctionCase	(context, (string("packunorm2x16") + getPrecisionPostfix(precision) + getShaderTypePostfix(shaderType)).c_str(), "packUnorm2x16", shaderType)
+		, m_precision				(precision)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_FLOAT_VEC2, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = packUnorm2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<tcu::Vec2>		inputs;
+		std::vector<deUint32>		outputs;
+		const int					maxDiff		= m_precision == glu::PRECISION_HIGHP	? 1		:		// Rounding only.
+												  m_precision == glu::PRECISION_MEDIUMP	? 65	:		// (2^-10) * (2^16) + 1
+												  m_precision == glu::PRECISION_LOWP	? 257	: 0;	// (2^-8) * (2^16) + 1
+
+		// Special values to check.
+		inputs.push_back(tcu::Vec2(0.0f, 0.0f));
+		inputs.push_back(tcu::Vec2(0.5f, 1.0f));
+		inputs.push_back(tcu::Vec2(1.0f, 0.5f));
+		inputs.push_back(tcu::Vec2(-0.5f, 1.5f));
+		inputs.push_back(tcu::Vec2(0.25f, 0.75f));
+
+		// Random values, mostly in range.
+		for (int ndx = 0; ndx < 15; ndx++)
+		{
+			const float x = rnd.getFloat()*1.25f;
+			const float y = rnd.getFloat()*1.25f;
+			inputs.push_back(tcu::Vec2(x, y));
+		}
+
+		// Large random values.
+		for (int ndx = 0; ndx < 80; ndx++)
+		{
+			const float x = rnd.getFloat()*1e6f - 1e5f;
+			const float y = rnd.getFloat()*1e6f - 1e5f;
+			inputs.push_back(tcu::Vec2(x, y));
+		}
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deUint16	ref0	= (deUint16)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].x(), 0.0f, 1.0f) * 65535.0f), 0, (1<<16)-1);
+				const deUint16	ref1	= (deUint16)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].y(), 0.0f, 1.0f) * 65535.0f), 0, (1<<16)-1);
+				const deUint32	ref		= (ref1 << 16) | ref0;
+				const deUint32	res		= outputs[valNdx];
+				const deUint16	res0	= (deUint16)(res & 0xffff);
+				const deUint16	res1	= (deUint16)(res >> 16);
+				const int		diff0	= de::abs((int)ref0 - (int)res0);
+				const int		diff1	= de::abs((int)ref1 - (int)res1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx
+															   << ", expected packUnorm2x16(" << inputs[valNdx] << ") = " << tcu::toHex(ref)
+															   << ", got " << tcu::toHex(res)
+															   << "\n  diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	glu::Precision m_precision;
+};
+
+class UnpackUnorm2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	UnpackUnorm2x16Case (Context& context, glu::ShaderType shaderType)
+		: ShaderPackingFunctionCase(context, (string("unpackunorm2x16") + getShaderTypePostfix(shaderType)).c_str(), "unpackUnorm2x16", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = unpackUnorm2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32				maxDiff		= 1; // Rounding error.
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<deUint32>		inputs;
+		std::vector<tcu::Vec2>		outputs;
+
+		inputs.push_back(0x00000000u);
+		inputs.push_back(0x7fff8000u);
+		inputs.push_back(0x80007fffu);
+		inputs.push_back(0xffffffffu);
+		inputs.push_back(0x0001fffeu);
+
+		// Random values.
+		for (int ndx = 0; ndx < 95; ndx++)
+			inputs.push_back(rnd.getUint32());
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deUint16	in0			= (deUint16)(inputs[valNdx] & 0xffff);
+				const deUint16	in1			= (deUint16)(inputs[valNdx] >> 16);
+				const float		ref0		= float(in0) / 65535.0f;
+				const float		ref1		= float(in1) / 65535.0f;
+				const float		res0		= outputs[valNdx].x();
+				const float		res1		= outputs[valNdx].y();
+
+				const deUint32	diff0		= getUlpDiff(ref0, res0);
+				const deUint32	diff1		= getUlpDiff(ref1, res1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx << ",\n"
+															   << "  expected unpackUnorm2x16(" << tcu::toHex(inputs[valNdx]) << ") = "
+															   << "vec2(" << HexFloat(ref0) << ", " << HexFloat(ref1) << ")"
+															   << ", got vec2(" << HexFloat(res0) << ", " << HexFloat(res1) << ")"
+															   << "\n  ULP diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class PackHalf2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	PackHalf2x16Case (Context& context, glu::ShaderType shaderType)
+		: ShaderPackingFunctionCase(context, (string("packhalf2x16") + getShaderTypePostfix(shaderType)).c_str(), "packHalf2x16", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = packHalf2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		const int					maxDiff		= 0; // Values can be represented exactly in mediump.
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<tcu::Vec2>		inputs;
+		std::vector<deUint32>		outputs;
+
+		// Special values to check.
+		inputs.push_back(tcu::Vec2(0.0f, 0.0f));
+		inputs.push_back(tcu::Vec2(0.5f, 1.0f));
+		inputs.push_back(tcu::Vec2(1.0f, 0.5f));
+		inputs.push_back(tcu::Vec2(-0.5f, 1.5f));
+		inputs.push_back(tcu::Vec2(0.25f, 0.75f));
+
+		// Random values.
+		{
+			const int	minExp	= -14;
+			const int	maxExp	= 15;
+
+			for (int ndx = 0; ndx < 95; ndx++)
+			{
+				tcu::Vec2 v;
+				for (int c = 0; c < 2; c++)
+				{
+					const int		s			= rnd.getBool() ? 1 : -1;
+					const int		exp			= rnd.getInt(minExp, maxExp);
+					const deUint32	mantissa	= rnd.getUint32() & ((1<<23)-1);
+
+					v[c] = tcu::Float32::construct(s, exp ? exp : 1 /* avoid denormals */, (1u<<23) | mantissa).asFloat();
+				}
+				inputs.push_back(v);
+			}
+		}
+
+		// Convert input values to fp16 and back to make sure they can be represented exactly in mediump.
+		for (std::vector<tcu::Vec2>::iterator inVal = inputs.begin(); inVal != inputs.end(); ++inVal)
+			*inVal = tcu::Vec2(tcu::Float16(inVal->x()).asFloat(), tcu::Float16(inVal->y()).asFloat());
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deUint16	ref0	= (deUint16)tcu::Float16(inputs[valNdx].x()).bits();
+				const deUint16	ref1	= (deUint16)tcu::Float16(inputs[valNdx].y()).bits();
+				const deUint32	ref		= (ref1 << 16) | ref0;
+				const deUint32	res		= outputs[valNdx];
+				const deUint16	res0	= (deUint16)(res & 0xffff);
+				const deUint16	res1	= (deUint16)(res >> 16);
+				const int		diff0	= de::abs((int)ref0 - (int)res0);
+				const int		diff1	= de::abs((int)ref1 - (int)res1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx
+															   << ", expected packHalf2x16(" << inputs[valNdx] << ") = " << tcu::toHex(ref)
+															   << ", got " << tcu::toHex(res)
+															   << "\n  diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class UnpackHalf2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	UnpackHalf2x16Case (Context& context, glu::ShaderType shaderType)
+		: ShaderPackingFunctionCase(context, (string("unpackhalf2x16") + getShaderTypePostfix(shaderType)).c_str(), "unpackHalf2x16", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_MEDIUMP)));
+
+		m_spec.source = "out0 = unpackHalf2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		const int					maxDiff		= 0; // All bits must be accurate.
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<deUint32>		inputs;
+		std::vector<tcu::Vec2>		outputs;
+
+		// Special values.
+		inputs.push_back((tcu::Float16( 0.0f).bits() << 16) | tcu::Float16( 1.0f).bits());
+		inputs.push_back((tcu::Float16( 1.0f).bits() << 16) | tcu::Float16( 0.0f).bits());
+		inputs.push_back((tcu::Float16(-1.0f).bits() << 16) | tcu::Float16( 0.5f).bits());
+		inputs.push_back((tcu::Float16( 0.5f).bits() << 16) | tcu::Float16(-0.5f).bits());
+
+		// Construct random values.
+		{
+			const int	minExp		= -14;
+			const int	maxExp		= 15;
+			const int	mantBits	= 10;
+
+			for (int ndx = 0; ndx < 96; ndx++)
+			{
+				deUint32 inVal = 0;
+				for (int c = 0; c < 2; c++)
+				{
+					const int		s			= rnd.getBool() ? 1 : -1;
+					const int		exp			= rnd.getInt(minExp, maxExp);
+					const deUint32	mantissa	= rnd.getUint32() & ((1<<mantBits)-1);
+					const deUint16	value		= tcu::Float16::construct(s, exp ? exp : 1 /* avoid denorm */, (1u<<10) | mantissa).bits();
+
+					inVal |= value << (16*c);
+				}
+				inputs.push_back(inVal);
+			}
+		}
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deUint16	in0			= (deUint16)(inputs[valNdx] & 0xffff);
+				const deUint16	in1			= (deUint16)(inputs[valNdx] >> 16);
+				const float		ref0		= tcu::Float16(in0).asFloat();
+				const float		ref1		= tcu::Float16(in1).asFloat();
+				const float		res0		= outputs[valNdx].x();
+				const float		res1		= outputs[valNdx].y();
+
+				const deUint32	refBits0	= tcu::Float32(ref0).bits();
+				const deUint32	refBits1	= tcu::Float32(ref1).bits();
+				const deUint32	resBits0	= tcu::Float32(res0).bits();
+				const deUint32	resBits1	= tcu::Float32(res1).bits();
+
+				const int		diff0	= de::abs((int)refBits0 - (int)resBits0);
+				const int		diff1	= de::abs((int)refBits1 - (int)resBits1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx << ",\n"
+															   << "  expected unpackHalf2x16(" << tcu::toHex(inputs[valNdx]) << ") = "
+															   << "vec2(" << ref0 << " / " << tcu::toHex(refBits0) << ", " << ref1 << " / " << tcu::toHex(refBits1) << ")"
+															   << ", got vec2(" << res0 << " / " << tcu::toHex(resBits0) << ", " << res1 << " / " << tcu::toHex(resBits1) << ")"
+															   << "\n  ULP diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+ShaderPackingFunctionTests::ShaderPackingFunctionTests (Context& context)
+	: TestCaseGroup(context, "pack_unpack", "Floating-point pack and unpack function tests")
+{
+}
+
+ShaderPackingFunctionTests::~ShaderPackingFunctionTests (void)
+{
+}
+
+void ShaderPackingFunctionTests::init (void)
+{
+	addChild(new PackSnorm2x16Case	(m_context, glu::SHADERTYPE_VERTEX,		glu::PRECISION_LOWP));
+	addChild(new PackSnorm2x16Case	(m_context, glu::SHADERTYPE_FRAGMENT,	glu::PRECISION_LOWP));
+	addChild(new PackSnorm2x16Case	(m_context, glu::SHADERTYPE_VERTEX,		glu::PRECISION_MEDIUMP));
+	addChild(new PackSnorm2x16Case	(m_context, glu::SHADERTYPE_FRAGMENT,	glu::PRECISION_MEDIUMP));
+	addChild(new PackSnorm2x16Case	(m_context, glu::SHADERTYPE_VERTEX,		glu::PRECISION_HIGHP));
+	addChild(new PackSnorm2x16Case	(m_context, glu::SHADERTYPE_FRAGMENT,	glu::PRECISION_HIGHP));
+
+	addChild(new UnpackSnorm2x16Case(m_context, glu::SHADERTYPE_VERTEX));
+	addChild(new UnpackSnorm2x16Case(m_context, glu::SHADERTYPE_FRAGMENT));
+
+	addChild(new PackUnorm2x16Case	(m_context, glu::SHADERTYPE_VERTEX,		glu::PRECISION_LOWP));
+	addChild(new PackUnorm2x16Case	(m_context, glu::SHADERTYPE_FRAGMENT,	glu::PRECISION_LOWP));
+	addChild(new PackUnorm2x16Case	(m_context, glu::SHADERTYPE_VERTEX,		glu::PRECISION_MEDIUMP));
+	addChild(new PackUnorm2x16Case	(m_context, glu::SHADERTYPE_FRAGMENT,	glu::PRECISION_MEDIUMP));
+	addChild(new PackUnorm2x16Case	(m_context, glu::SHADERTYPE_VERTEX,		glu::PRECISION_HIGHP));
+	addChild(new PackUnorm2x16Case	(m_context, glu::SHADERTYPE_FRAGMENT,	glu::PRECISION_HIGHP));
+
+	addChild(new UnpackUnorm2x16Case(m_context, glu::SHADERTYPE_VERTEX));
+	addChild(new UnpackUnorm2x16Case(m_context, glu::SHADERTYPE_FRAGMENT));
+
+	addChild(new PackHalf2x16Case	(m_context, glu::SHADERTYPE_VERTEX));
+	addChild(new PackHalf2x16Case	(m_context, glu::SHADERTYPE_FRAGMENT));
+
+	addChild(new UnpackHalf2x16Case	(m_context, glu::SHADERTYPE_VERTEX));
+	addChild(new UnpackHalf2x16Case	(m_context, glu::SHADERTYPE_FRAGMENT));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderPackingFunctionTests.hpp b/modules/gles3/functional/es3fShaderPackingFunctionTests.hpp
new file mode 100644
index 0000000..45db0e1
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderPackingFunctionTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERPACKINGFUNCTIONTESTS_HPP
+#define _ES3FSHADERPACKINGFUNCTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Floating-point packing and unpacking function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderPackingFunctionTests : public TestCaseGroup
+{
+public:
+									ShaderPackingFunctionTests		(Context& context);
+	virtual							~ShaderPackingFunctionTests		(void);
+
+	virtual void					init							(void);
+
+private:
+									ShaderPackingFunctionTests		(const ShaderPackingFunctionTests&);		// not allowed!
+	ShaderPackingFunctionTests&		operator=						(const ShaderPackingFunctionTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERPACKINGFUNCTIONTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderPrecisionTests.cpp b/modules/gles3/functional/es3fShaderPrecisionTests.cpp
new file mode 100644
index 0000000..0b405fe
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderPrecisionTests.cpp
@@ -0,0 +1,1005 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader precision tests.
+ *
+ * \note Floating-point case uses R32UI render target and uses
+ *		 floatBitsToUint() in shader to write out floating-point value bits.
+ *		 This is done since ES3 core doesn't support FP render targets.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderPrecisionTests.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuFloat.hpp"
+#include "tcuFormatUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluDrawUtil.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <algorithm>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using std::ostringstream;
+using tcu::TestLog;
+
+enum
+{
+	FRAMEBUFFER_WIDTH	= 32,
+	FRAMEBUFFER_HEIGHT	= 32
+};
+
+static glu::ShaderProgram* createFloatPrecisionEvalProgram (const glu::RenderContext& context, glu::Precision precision, const char* evalOp, bool isVertexCase)
+{
+	glu::DataType	type		= glu::TYPE_FLOAT;
+	glu::DataType	outType		= glu::TYPE_UINT;
+	const char*		typeName	= glu::getDataTypeName(type);
+	const char*		outTypeName	= glu::getDataTypeName(outType);
+	const char*		precName	= glu::getPrecisionName(precision);
+	ostringstream	vtx;
+	ostringstream	frag;
+	ostringstream&	op			= isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n"
+		<< "in highp vec4 a_position;\n"
+		<< "in " << precName << " " << typeName << " a_in0;\n"
+		<< "in " << precName << " " << typeName << " a_in1;\n";
+	frag << "#version 300 es\n"
+		 << "layout(location = 0) out highp " << outTypeName << " o_out;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "flat out " << precName << " " << typeName << " v_out;\n";
+		frag << "flat in " << precName << " " << typeName << " v_out;\n";
+	}
+	else
+	{
+		vtx << "flat out " << precName << " " << typeName << " v_in0;\n"
+			<< "flat out " << precName << " " << typeName << " v_in1;\n";
+		frag << "flat in " << precName << " " << typeName << " v_in0;\n"
+			 << "flat in " << precName << " " << typeName << " v_in1;\n";
+	}
+
+	vtx << "\nvoid main (void)\n{\n"
+		<< "	gl_Position = a_position;\n";
+	frag << "\nvoid main (void)\n{\n";
+
+	op << "\t" << precName << " " << typeName << " in0 = " << (isVertexCase ? "a_" : "v_") << "in0;\n"
+	   << "\t" << precName << " " << typeName << " in1 = " << (isVertexCase ? "a_" : "v_") << "in1;\n";
+
+	if (!isVertexCase)
+		op << "\t" << precName << " " << typeName << " res;\n";
+
+	op << "\t" << (isVertexCase ? "v_out" : "res") << " = " << evalOp << ";\n";
+
+	if (isVertexCase)
+	{
+		frag << "	o_out = floatBitsToUint(v_out);\n";
+	}
+	else
+	{
+		vtx << "	v_in0 = a_in0;\n"
+			<< "	v_in1 = a_in1;\n";
+		frag << "	o_out = floatBitsToUint(res);\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	return new glu::ShaderProgram(context, glu::makeVtxFragSources(vtx.str(), frag.str()));
+}
+
+static glu::ShaderProgram* createIntUintPrecisionEvalProgram (const glu::RenderContext& context, glu::DataType type, glu::Precision precision, const char* evalOp, bool isVertexCase)
+{
+	const char*		typeName	= glu::getDataTypeName(type);
+	const char*		precName	= glu::getPrecisionName(precision);
+	ostringstream	vtx;
+	ostringstream	frag;
+	ostringstream&	op			= isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n"
+		<< "in highp vec4 a_position;\n"
+		<< "in " << precName << " " << typeName << " a_in0;\n"
+		<< "in " << precName << " " << typeName << " a_in1;\n";
+	frag << "#version 300 es\n"
+		 << "layout(location = 0) out " << precName << " " << typeName << " o_out;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "flat out " << precName << " " << typeName << " v_out;\n";
+		frag << "flat in " << precName << " " << typeName << " v_out;\n";
+	}
+	else
+	{
+		vtx << "flat out " << precName << " " << typeName << " v_in0;\n"
+			<< "flat out " << precName << " " << typeName << " v_in1;\n";
+		frag << "flat in " << precName << " " << typeName << " v_in0;\n"
+			 << "flat in " << precName << " " << typeName << " v_in1;\n";
+	}
+
+	vtx << "\nvoid main (void)\n{\n"
+		<< "	gl_Position = a_position;\n";
+	frag << "\nvoid main (void)\n{\n";
+
+	op << "\t" << precName << " " << typeName << " in0 = " << (isVertexCase ? "a_" : "v_") << "in0;\n"
+	   << "\t" << precName << " " << typeName << " in1 = " << (isVertexCase ? "a_" : "v_") << "in1;\n";
+
+	op << "\t" << (isVertexCase ? "v_" : "o_") << "out = " << evalOp << ";\n";
+
+	if (isVertexCase)
+	{
+		frag << "	o_out = v_out;\n";
+	}
+	else
+	{
+		vtx << "	v_in0 = a_in0;\n"
+			<< "	v_in1 = a_in1;\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	return new glu::ShaderProgram(context, glu::makeVtxFragSources(vtx.str(), frag.str()));
+}
+
+class ShaderFloatPrecisionCase : public TestCase
+{
+public:
+	typedef double (*EvalFunc) (double in0, double in1);
+
+								ShaderFloatPrecisionCase	(Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, const tcu::Vec2& rangeA, const tcu::Vec2& rangeB, bool isVertexCase);
+								~ShaderFloatPrecisionCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+protected:
+	bool						compare						(float in0, float in1, double reference, float result);
+
+private:
+								ShaderFloatPrecisionCase	(const ShaderFloatPrecisionCase& other);
+	ShaderFloatPrecisionCase&	operator=					(const ShaderFloatPrecisionCase& other);
+
+	// Case parameters.
+	std::string					m_op;
+	EvalFunc					m_evalFunc;
+	glu::Precision				m_precision;
+	tcu::Vec2					m_rangeA;
+	tcu::Vec2					m_rangeB;
+	bool						m_isVertexCase;
+
+	int							m_numTestsPerIter;
+	int							m_numIters;
+	de::Random					m_rnd;
+
+	// Iteration state.
+	glu::ShaderProgram*			m_program;
+	deUint32					m_framebuffer;
+	deUint32					m_renderbuffer;
+	int							m_iterNdx;
+};
+
+ShaderFloatPrecisionCase::ShaderFloatPrecisionCase (Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, const tcu::Vec2& rangeA, const tcu::Vec2& rangeB, bool isVertexCase)
+	: TestCase			(context, name, desc)
+	, m_op				(op)
+	, m_evalFunc		(evalFunc)
+	, m_precision		(precision)
+	, m_rangeA			(rangeA)
+	, m_rangeB			(rangeB)
+	, m_isVertexCase	(isVertexCase)
+	, m_numTestsPerIter	(32)
+	, m_numIters		(4)
+	, m_rnd				(deStringHash(name))
+	, m_program			(DE_NULL)
+	, m_framebuffer		(0)
+	, m_renderbuffer	(0)
+	, m_iterNdx			(0)
+{
+}
+
+ShaderFloatPrecisionCase::~ShaderFloatPrecisionCase (void)
+{
+	ShaderFloatPrecisionCase::deinit();
+}
+
+void ShaderFloatPrecisionCase::init (void)
+{
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+	TestLog&				log	= m_testCtx.getLog();
+
+	DE_ASSERT(!m_program && !m_framebuffer && !m_renderbuffer);
+
+	// Create program.
+	m_program = createFloatPrecisionEvalProgram(m_context.getRenderContext(), m_precision, m_op.c_str(), m_isVertexCase);
+	log << *m_program;
+
+	TCU_CHECK(m_program->isOk());
+
+	// Create framebuffer.
+	gl.genFramebuffers(1, &m_framebuffer);
+	gl.genRenderbuffers(1, &m_renderbuffer);
+
+	gl.bindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer);
+	gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Post framebuffer setup");
+	TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer());
+
+	// Initialize test result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	m_iterNdx = 0;
+}
+
+void ShaderFloatPrecisionCase::deinit (void)
+{
+	delete m_program;
+
+	if (m_framebuffer)
+		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_framebuffer);
+
+	if (m_renderbuffer)
+		m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_renderbuffer);
+
+	m_program		= DE_NULL;
+	m_framebuffer	= 0;
+	m_renderbuffer	= 0;
+}
+
+bool ShaderFloatPrecisionCase::compare (float in0, float in1, double reference, float result)
+{
+	// Comparison is done using 64-bit reference value to accurately evaluate rounding mode error.
+	// If 32-bit reference value is used, 2 bits of rounding error must be allowed.
+
+	// For mediump and lowp types the comparison currently allows 2 bits of rounding error:
+	// one bit from conversion and another from actual operation.
+
+	// \todo [2013-09-30 pyry] Make this more strict: determine if rounding can actually happen.
+
+	const int		mantissaBits		= m_precision == glu::PRECISION_HIGHP ? 23 : 10;
+	const int		numPrecBits			= 52 - mantissaBits;
+
+	const int		in0Exp				= tcu::Float32(in0).exponent();
+	const int		in1Exp				= tcu::Float32(in1).exponent();
+	const int		resExp				= tcu::Float32(result).exponent();
+	const int		numLostBits			= de::max(de::max(in0Exp-resExp, in1Exp-resExp), 0); // Lost due to mantissa shift.
+
+	const int		roundingUlpError	= m_precision == glu::PRECISION_HIGHP ? 1 : 2;
+	const int		maskBits			= numLostBits + numPrecBits;
+
+	m_testCtx.getLog() << TestLog::Message << "Assuming " << mantissaBits << " mantissa bits, " << numLostBits << " bits lost in operation, and " << roundingUlpError << " ULP rounding error."
+					   << TestLog::EndMessage;
+
+	{
+		const deUint64	refBits				= tcu::Float64(reference).bits();
+		const deUint64	resBits				= tcu::Float64(result).bits();
+		const deUint64	accurateRefBits		= refBits >> maskBits;
+		const deUint64	accurateResBits		= resBits >> maskBits;
+		const deUint64	ulpDiff				= (deUint64)de::abs((deInt64)accurateRefBits - (deInt64)accurateResBits);
+
+		if (ulpDiff > (deUint64)roundingUlpError)
+		{
+			m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed! ULP diff (ignoring lost/undefined bits) = " << ulpDiff << TestLog::EndMessage;
+			return false;
+		}
+		else
+			return true;
+	}
+}
+
+ShaderFloatPrecisionCase::IterateResult ShaderFloatPrecisionCase::iterate (void)
+{
+	// Constant data.
+	const float position[] =
+	{
+		-1.0f, -1.0f, 0.0f, 1.0f,
+		-1.0f,  1.0f, 0.0f, 1.0f,
+		 1.0f, -1.0f, 0.0f, 1.0f,
+		 1.0f,  1.0f, 0.0f, 1.0f
+	};
+	const deUint16					indices[]	= { 0, 1, 2, 2, 1, 3 };
+
+	const int						numVertices	= 4;
+	float							in0Arr[4]	= { 0.0f };
+	float							in1Arr[4]	= { 0.0f };
+
+	TestLog&						log			= m_testCtx.getLog();
+	const glw::Functions&			gl			= m_context.getRenderContext().getFunctions();
+	vector<glu::VertexArrayBinding>	vertexArrays;
+
+	// Image read from GL.
+	std::vector<float>	pixels		(FRAMEBUFFER_WIDTH*FRAMEBUFFER_HEIGHT*4);
+
+	// \todo [2012-05-03 pyry] Could be cached.
+	deUint32			prog		= m_program->getProgram();
+
+	gl.useProgram(prog);
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+
+	vertexArrays.push_back(glu::va::Float("a_position", 4, numVertices, 0, &position[0]));
+	vertexArrays.push_back(glu::va::Float("a_in0", 1, numVertices, 0, &in0Arr[0]));
+	vertexArrays.push_back(glu::va::Float("a_in1", 1, numVertices, 0, &in1Arr[0]));
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After program setup");
+
+	// Compute values and reference.
+	for (int testNdx = 0; testNdx < m_numTestsPerIter; testNdx++)
+	{
+		const float		in0		= m_rnd.getFloat(m_rangeA.x(), m_rangeA.y());
+		const float		in1		= m_rnd.getFloat(m_rangeB.x(), m_rangeB.y());
+		const double	refD	= m_evalFunc((double)in0, (double)in1);
+		const float		refF	= tcu::Float64(refD).asFloat(); // Uses RTE rounding mode.
+
+		log << TestLog::Message << "iter " << m_iterNdx << ", test " << testNdx << ": "
+								<< "in0 = " << in0 << " / " << tcu::toHex(tcu::Float32(in0).bits())
+								<< ", in1 = " << in1 << " / " << tcu::toHex(tcu::Float32(in1).bits())
+			<< TestLog::EndMessage
+			<< TestLog::Message << "  reference = " << refF << " / " << tcu::toHex(tcu::Float32(refF).bits()) << TestLog::EndMessage;
+
+		std::fill(&in0Arr[0], &in0Arr[0] + DE_LENGTH_OF_ARRAY(in0Arr), in0);
+		std::fill(&in1Arr[0], &in1Arr[0] + DE_LENGTH_OF_ARRAY(in1Arr), in1);
+
+		glu::draw(m_context.getRenderContext(), prog, (int)vertexArrays.size(), &vertexArrays[0],
+				  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+		gl.readPixels(0, 0, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "After render");
+
+		log << TestLog::Message << "  result = " << pixels[0] << " / " << tcu::toHex(tcu::Float32(pixels[0]).bits()) << TestLog::EndMessage;
+
+		// Verify results
+		{
+			const bool firstPixelOk = compare(in0, in1, refD, pixels[0]);
+
+			if (firstPixelOk)
+			{
+				// Check that rest of pixels match to first one.
+				const deUint32	firstPixelBits	= tcu::Float32(pixels[0]).bits();
+				bool			allPixelsOk		= true;
+
+				for (int y = 0; y < FRAMEBUFFER_HEIGHT; y++)
+				{
+					for (int x = 0; x < FRAMEBUFFER_WIDTH; x++)
+					{
+						const deUint32 pixelBits = tcu::Float32(pixels[(y*FRAMEBUFFER_WIDTH + x)*4]).bits();
+
+						if (pixelBits != firstPixelBits)
+						{
+							log << TestLog::Message << "ERROR: Inconsistent results, got " << tcu::toHex(pixelBits) << " at (" << x << ", " << y << ")" << TestLog::EndMessage;
+							allPixelsOk = false;
+						}
+					}
+
+					if (!allPixelsOk)
+						break;
+				}
+
+				if (!allPixelsOk)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Inconsistent values in framebuffer");
+			}
+			else
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result comparison failed");
+		}
+
+		if (m_testCtx.getTestResult() != QP_TEST_RESULT_PASS)
+			break;
+	}
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After iteration");
+
+	m_iterNdx += 1;
+	return (m_iterNdx < m_numIters && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS) ? CONTINUE : STOP;
+}
+
+class ShaderIntPrecisionCase : public TestCase
+{
+public:
+	typedef int					(*EvalFunc)					(int a, int b);
+
+								ShaderIntPrecisionCase		(Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, int bits, const tcu::IVec2& rangeA, const tcu::IVec2& rangeB, bool isVertexCase);
+								~ShaderIntPrecisionCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								ShaderIntPrecisionCase		(const ShaderIntPrecisionCase& other);
+	ShaderIntPrecisionCase&		operator=					(const ShaderIntPrecisionCase& other);
+
+	// Case parameters.
+	std::string					m_op;
+	EvalFunc					m_evalFunc;
+	glu::Precision				m_precision;
+	int							m_bits;
+	tcu::IVec2					m_rangeA;
+	tcu::IVec2					m_rangeB;
+	bool						m_isVertexCase;
+
+	int							m_numTestsPerIter;
+	int							m_numIters;
+	de::Random					m_rnd;
+
+	// Iteration state.
+	glu::ShaderProgram*			m_program;
+	deUint32					m_framebuffer;
+	deUint32					m_renderbuffer;
+	int							m_iterNdx;
+};
+
+ShaderIntPrecisionCase::ShaderIntPrecisionCase (Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, int bits, const tcu::IVec2& rangeA, const tcu::IVec2& rangeB, bool isVertexCase)
+	: TestCase			(context, name, desc)
+	, m_op				(op)
+	, m_evalFunc		(evalFunc)
+	, m_precision		(precision)
+	, m_bits			(bits)
+	, m_rangeA			(rangeA)
+	, m_rangeB			(rangeB)
+	, m_isVertexCase	(isVertexCase)
+	, m_numTestsPerIter	(32)
+	, m_numIters		(4)
+	, m_rnd				(deStringHash(name))
+	, m_program			(DE_NULL)
+	, m_framebuffer		(0)
+	, m_renderbuffer	(0)
+	, m_iterNdx			(0)
+{
+}
+
+ShaderIntPrecisionCase::~ShaderIntPrecisionCase (void)
+{
+	ShaderIntPrecisionCase::deinit();
+}
+
+void ShaderIntPrecisionCase::init (void)
+{
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+	TestLog&				log	= m_testCtx.getLog();
+
+	DE_ASSERT(!m_program && !m_framebuffer && !m_renderbuffer);
+
+	// Create program.
+	m_program = createIntUintPrecisionEvalProgram(m_context.getRenderContext(), glu::TYPE_INT, m_precision, m_op.c_str(), m_isVertexCase);
+	log << *m_program;
+
+	TCU_CHECK(m_program->isOk());
+
+	// Create framebuffer.
+	gl.genFramebuffers(1, &m_framebuffer);
+	gl.genRenderbuffers(1, &m_renderbuffer);
+
+	gl.bindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer);
+	gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32I, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Post framebuffer setup");
+	TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer());
+
+	// Initialize test result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	m_iterNdx = 0;
+
+	log << TestLog::Message << "Number of accurate bits assumed = " << m_bits << TestLog::EndMessage;
+}
+
+void ShaderIntPrecisionCase::deinit (void)
+{
+	delete m_program;
+
+	if (m_framebuffer)
+		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_framebuffer);
+
+	if (m_renderbuffer)
+		m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_renderbuffer);
+
+	m_program		= DE_NULL;
+	m_framebuffer	= 0;
+	m_renderbuffer	= 0;
+}
+
+inline int extendTo32Bit (int value, int bits)
+{
+	return (value & ((1<<(bits-1))-1)) | (((value & (1<<(bits-1))) << (32-bits)) >> (32-bits));
+}
+
+ShaderIntPrecisionCase::IterateResult ShaderIntPrecisionCase::iterate (void)
+{
+	// Constant data.
+	const float position[] =
+	{
+		-1.0f, -1.0f, 0.0f, 1.0f,
+		-1.0f,  1.0f, 0.0f, 1.0f,
+		 1.0f, -1.0f, 0.0f, 1.0f,
+		 1.0f,  1.0f, 0.0f, 1.0f
+	};
+	const deUint16					indices[]	= { 0, 1, 2, 2, 1, 3 };
+
+	const int						numVertices	= 4;
+	int								in0Arr[4]	= { 0 };
+	int								in1Arr[4]	= { 0 };
+
+	TestLog&						log			= m_testCtx.getLog();
+	const glw::Functions&			gl			= m_context.getRenderContext().getFunctions();
+	deUint32						mask		= m_bits == 32 ? 0xffffffffu : ((1u<<m_bits)-1);
+	vector<int>						pixels		(FRAMEBUFFER_WIDTH*FRAMEBUFFER_HEIGHT*4);
+	vector<glu::VertexArrayBinding>	vertexArrays;
+
+	deUint32						prog		= m_program->getProgram();
+
+	// \todo [2012-05-03 pyry] A bit hacky. getInt() should work fine with ranges like this.
+	bool							isMaxRangeA	= m_rangeA.x() == (int)0x80000000 && m_rangeA.y() == (int)0x7fffffff;
+	bool							isMaxRangeB	= m_rangeB.x() == (int)0x80000000 && m_rangeB.y() == (int)0x7fffffff;
+
+	gl.useProgram(prog);
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+
+	vertexArrays.push_back(glu::va::Float("a_position", 4, numVertices, 0, &position[0]));
+	vertexArrays.push_back(glu::va::Int32("a_in0", 1, numVertices, 0, &in0Arr[0]));
+	vertexArrays.push_back(glu::va::Int32("a_in1", 1, numVertices, 0, &in1Arr[0]));
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After program setup");
+
+	// Compute values and reference.
+	for (int testNdx = 0; testNdx < m_numTestsPerIter; testNdx++)
+	{
+		int		in0			= extendTo32Bit(((isMaxRangeA ? (int)m_rnd.getUint32() : m_rnd.getInt(m_rangeA.x(), m_rangeA.y())) & mask), m_bits);
+		int		in1			= extendTo32Bit(((isMaxRangeB ? (int)m_rnd.getUint32() : m_rnd.getInt(m_rangeB.x(), m_rangeB.y())) & mask), m_bits);
+		int		refMasked	= m_evalFunc(in0, in1) & mask;
+		int		refOut		= extendTo32Bit(refMasked, m_bits);
+
+		log << TestLog::Message << "iter " << m_iterNdx << ", test " << testNdx << ": "
+								<< "in0 = " << in0 << ", in1 = " << in1 << ", ref out = " << refOut << " / " << tcu::toHex(refMasked)
+			<< TestLog::EndMessage;
+
+		std::fill(&in0Arr[0], &in0Arr[0] + DE_LENGTH_OF_ARRAY(in0Arr), in0);
+		std::fill(&in1Arr[0], &in1Arr[0] + DE_LENGTH_OF_ARRAY(in1Arr), in1);
+
+		glu::draw(m_context.getRenderContext(), prog, (int)vertexArrays.size(), &vertexArrays[0],
+				  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+		gl.readPixels(0, 0, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, GL_RGBA_INTEGER, GL_INT, &pixels[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "After render");
+
+		// Compare pixels.
+		for (int y = 0; y < FRAMEBUFFER_HEIGHT; y++)
+		{
+			for (int x = 0; x < FRAMEBUFFER_WIDTH; x++)
+			{
+				int			cmpOut		= pixels[(y*FRAMEBUFFER_WIDTH + x)*4];
+				int			cmpMasked	= cmpOut & mask;
+
+				if (cmpMasked != refMasked)
+				{
+					log << TestLog::Message << "Comparison failed (at " << x << ", " << y << "): "
+											<< "got " << cmpOut << " / " << tcu::toHex(cmpOut)
+						<< TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+					return STOP;
+				}
+			}
+		}
+	}
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After iteration");
+
+	m_iterNdx += 1;
+	return (m_iterNdx < m_numIters) ? CONTINUE : STOP;
+}
+
+class ShaderUintPrecisionCase : public TestCase
+{
+public:
+	typedef deUint32			(*EvalFunc)					(deUint32 a, deUint32 b);
+
+								ShaderUintPrecisionCase		(Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, int bits, const tcu::UVec2& rangeA, const tcu::UVec2& rangeB, bool isVertexCase);
+								~ShaderUintPrecisionCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								ShaderUintPrecisionCase		(const ShaderUintPrecisionCase& other);
+	ShaderUintPrecisionCase&	operator=					(const ShaderUintPrecisionCase& other);
+
+	// Case parameters.
+	std::string					m_op;
+	EvalFunc					m_evalFunc;
+	glu::Precision				m_precision;
+	int							m_bits;
+	tcu::UVec2					m_rangeA;
+	tcu::UVec2					m_rangeB;
+	bool						m_isVertexCase;
+
+	int							m_numTestsPerIter;
+	int							m_numIters;
+	de::Random					m_rnd;
+
+	// Iteration state.
+	glu::ShaderProgram*			m_program;
+	deUint32					m_framebuffer;
+	deUint32					m_renderbuffer;
+	int							m_iterNdx;
+};
+
+ShaderUintPrecisionCase::ShaderUintPrecisionCase (Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, int bits, const tcu::UVec2& rangeA, const tcu::UVec2& rangeB, bool isVertexCase)
+	: TestCase			(context, name, desc)
+	, m_op				(op)
+	, m_evalFunc		(evalFunc)
+	, m_precision		(precision)
+	, m_bits			(bits)
+	, m_rangeA			(rangeA)
+	, m_rangeB			(rangeB)
+	, m_isVertexCase	(isVertexCase)
+	, m_numTestsPerIter	(32)
+	, m_numIters		(4)
+	, m_rnd				(deStringHash(name))
+	, m_program			(DE_NULL)
+	, m_framebuffer		(0)
+	, m_renderbuffer	(0)
+	, m_iterNdx			(0)
+{
+}
+
+ShaderUintPrecisionCase::~ShaderUintPrecisionCase (void)
+{
+	ShaderUintPrecisionCase::deinit();
+}
+
+void ShaderUintPrecisionCase::init (void)
+{
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+	TestLog&				log	= m_testCtx.getLog();
+
+	DE_ASSERT(!m_program && !m_framebuffer && !m_renderbuffer);
+
+	// Create program.
+	m_program = createIntUintPrecisionEvalProgram(m_context.getRenderContext(), glu::TYPE_UINT, m_precision, m_op.c_str(), m_isVertexCase);
+	log << *m_program;
+
+	TCU_CHECK(m_program->isOk());
+
+	// Create framebuffer.
+	gl.genFramebuffers(1, &m_framebuffer);
+	gl.genRenderbuffers(1, &m_renderbuffer);
+
+	gl.bindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer);
+	gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Post framebuffer setup");
+	TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer());
+
+	// Initialize test result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	m_iterNdx = 0;
+
+	log << TestLog::Message << "Number of accurate bits assumed = " << m_bits << TestLog::EndMessage;
+}
+
+void ShaderUintPrecisionCase::deinit (void)
+{
+	delete m_program;
+
+	if (m_framebuffer)
+		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_framebuffer);
+
+	if (m_renderbuffer)
+		m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_renderbuffer);
+
+	m_program		= DE_NULL;
+	m_framebuffer	= 0;
+	m_renderbuffer	= 0;
+}
+
+ShaderUintPrecisionCase::IterateResult ShaderUintPrecisionCase::iterate (void)
+{
+	// Constant data.
+	const float position[] =
+	{
+		-1.0f, -1.0f, 0.0f, 1.0f,
+		-1.0f,  1.0f, 0.0f, 1.0f,
+		 1.0f, -1.0f, 0.0f, 1.0f,
+		 1.0f,  1.0f, 0.0f, 1.0f
+	};
+	const deUint16					indices[]	= { 0, 1, 2, 2, 1, 3 };
+
+	const int						numVertices	= 4;
+	deUint32						in0Arr[4]	= { 0 };
+	deUint32						in1Arr[4]	= { 0 };
+
+	TestLog&						log			= m_testCtx.getLog();
+	const glw::Functions&			gl			= m_context.getRenderContext().getFunctions();
+	deUint32						mask		= m_bits == 32 ? 0xffffffffu : ((1u<<m_bits)-1);
+	vector<deUint32>				pixels		(FRAMEBUFFER_WIDTH*FRAMEBUFFER_HEIGHT*4);
+	vector<glu::VertexArrayBinding>	vertexArrays;
+
+	deUint32						prog		= m_program->getProgram();
+
+	// \todo [2012-05-03 pyry] A bit hacky.
+	bool							isMaxRangeA	= m_rangeA.x() == 0 && m_rangeA.y() == 0xffffffff;
+	bool							isMaxRangeB	= m_rangeB.x() == 0 && m_rangeB.y() == 0xffffffff;
+
+	gl.useProgram(prog);
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
+
+	vertexArrays.push_back(glu::va::Float("a_position", 4, numVertices, 0, &position[0]));
+	vertexArrays.push_back(glu::va::Uint32("a_in0", 1, numVertices, 0, &in0Arr[0]));
+	vertexArrays.push_back(glu::va::Uint32("a_in1", 1, numVertices, 0, &in1Arr[0]));
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After program setup");
+
+	// Compute values and reference.
+	for (int testNdx = 0; testNdx < m_numTestsPerIter; testNdx++)
+	{
+		deUint32	in0		= (isMaxRangeA ? m_rnd.getUint32() : (m_rangeA.x() + m_rnd.getUint32()%(m_rangeA.y()-m_rangeA.x()+1))) & mask;
+		deUint32	in1		= (isMaxRangeB ? m_rnd.getUint32() : (m_rangeB.x() + m_rnd.getUint32()%(m_rangeB.y()-m_rangeB.x()+1))) & mask;
+		deUint32	refOut	= m_evalFunc(in0, in1) & mask;
+
+		log << TestLog::Message << "iter " << m_iterNdx << ", test " << testNdx << ": "
+								<< "in0 = " << tcu::toHex(in0) << ", in1 = " << tcu::toHex(in1) << ", ref out = " << tcu::toHex(refOut)
+			<< TestLog::EndMessage;
+
+		std::fill(&in0Arr[0], &in0Arr[0] + DE_LENGTH_OF_ARRAY(in0Arr), in0);
+		std::fill(&in1Arr[0], &in1Arr[0] + DE_LENGTH_OF_ARRAY(in1Arr), in1);
+
+		glu::draw(m_context.getRenderContext(), prog, (int)vertexArrays.size(), &vertexArrays[0],
+				  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+		gl.readPixels(0, 0, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "After render");
+
+		// Compare pixels.
+		for (int y = 0; y < FRAMEBUFFER_HEIGHT; y++)
+		{
+			for (int x = 0; x < FRAMEBUFFER_WIDTH; x++)
+			{
+				deUint32	cmpOut		= pixels[(y*FRAMEBUFFER_WIDTH + x)*4];
+				deUint32	cmpMasked	= cmpOut & mask;
+
+				if (cmpMasked != refOut)
+				{
+					log << TestLog::Message << "Comparison failed (at " << x << ", " << y << "): "
+											<< "got " << tcu::toHex(cmpOut)
+						<< TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+					return STOP;
+				}
+			}
+		}
+	}
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After iteration");
+
+	m_iterNdx += 1;
+	return (m_iterNdx < m_numIters) ? CONTINUE : STOP;
+}
+
+ShaderPrecisionTests::ShaderPrecisionTests (Context& context)
+	: TestCaseGroup(context, "precision", "Shader precision requirements validation tests")
+{
+}
+
+ShaderPrecisionTests::~ShaderPrecisionTests (void)
+{
+}
+
+void ShaderPrecisionTests::init (void)
+{
+	using tcu::add;
+	using tcu::sub;
+	using tcu::mul;
+	using tcu::div;
+	using tcu::Vec2;
+	using tcu::IVec2;
+	using tcu::UVec2;
+
+	// Exp = Emax-2, Mantissa = 0
+	float		minF32			= tcu::Float32((1u<<31) | (0xfdu<<23) | 0x0u).asFloat();
+	float		maxF32			= tcu::Float32((0u<<31) | (0xfdu<<23) | 0x0u).asFloat();
+	float		minF16			= tcu::Float16((deUint16)((1u<<15) | (0x1du<<10) | 0x0u)).asFloat();
+	float		maxF16			= tcu::Float16((deUint16)((0u<<15) | (0x1du<<10) | 0x0u)).asFloat();
+	tcu::Vec2	fullRange32F	(minF32, maxF32);
+	tcu::Vec2	fullRange16F	(minF16, maxF16);
+	tcu::IVec2	fullRange32I	(0x80000000, 0x7fffffff);
+	tcu::IVec2	fullRange16I	(-(1<<15), (1<<15)-1);
+	tcu::IVec2	fullRange8I		(-(1<<7), (1<<7)-1);
+	tcu::UVec2	fullRange32U	(0u, 0xffffffffu);
+	tcu::UVec2	fullRange16U	(0u, 0xffffu);
+	tcu::UVec2	fullRange8U		(0u, 0xffu);
+
+	// \note Right now it is not programmatically verified that the results shouldn't end up being inf/nan but
+	//       actual values used are ok.
+
+	static const struct
+	{
+		const char*							name;
+		const char*							op;
+		ShaderFloatPrecisionCase::EvalFunc	evalFunc;
+		glu::Precision						precision;
+		tcu::Vec2							rangeA;
+		tcu::Vec2							rangeB;
+	} floatCases[] =
+	{
+		// Name				Op				Eval			Precision				RangeA				RangeB
+		{ "highp_add",		"in0 + in1",	add<double>,	glu::PRECISION_HIGHP,	fullRange32F,		fullRange32F		},
+		{ "highp_sub",		"in0 - in1",	sub<double>,	glu::PRECISION_HIGHP,	fullRange32F,		fullRange32F		},
+		{ "highp_mul",		"in0 * in1",	mul<double>,	glu::PRECISION_HIGHP,	Vec2(-1e5f, 1e5f),	Vec2(-1e5f, 1e5f)	},
+		{ "highp_div",		"in0 / in1",	div<double>,	glu::PRECISION_HIGHP,	Vec2(-1e5f, 1e5f),	Vec2(-1e5f, 1e5f)	},
+		{ "mediump_add",	"in0 + in1",	add<double>,	glu::PRECISION_MEDIUMP,	fullRange16F,		fullRange16F		},
+		{ "mediump_sub",	"in0 - in1",	sub<double>,	glu::PRECISION_MEDIUMP,	fullRange16F,		fullRange16F		},
+		{ "mediump_mul",	"in0 * in1",	mul<double>,	glu::PRECISION_MEDIUMP,	Vec2(-1e2f, 1e2f),	Vec2(-1e2f, 1e2f)	},
+		{ "mediump_div",	"in0 / in1",	div<double>,	glu::PRECISION_MEDIUMP,	Vec2(-1e2f, 1e2f),	Vec2(-1e2f, 1e2f)	}
+	};
+
+	static const struct
+	{
+		const char*							name;
+		const char*							op;
+		ShaderIntPrecisionCase::EvalFunc	evalFunc;
+		glu::Precision						precision;
+		int									bits;
+		tcu::IVec2							rangeA;
+		tcu::IVec2							rangeB;
+	} intCases[] =
+	{
+		// Name				Op				Eval				Precision				Bits	RangeA			RangeB
+		{ "highp_add",		"in0 + in1",	add<int>,			glu::PRECISION_HIGHP,	32,		fullRange32I,	fullRange32I },
+		{ "highp_sub",		"in0 - in1",	sub<int>,			glu::PRECISION_HIGHP,	32,		fullRange32I,	fullRange32I },
+		{ "highp_mul",		"in0 * in1",	mul<int>,			glu::PRECISION_HIGHP,	32,		fullRange32I,	fullRange32I },
+		{ "highp_div",		"in0 / in1",	div<int>,			glu::PRECISION_HIGHP,	32,		fullRange32I,	IVec2(-10000, -1) },
+		{ "mediump_add",	"in0 + in1",	add<int>,			glu::PRECISION_MEDIUMP,	16,		fullRange16I,	fullRange16I },
+		{ "mediump_sub",	"in0 - in1",	sub<int>,			glu::PRECISION_MEDIUMP,	16,		fullRange16I,	fullRange16I },
+		{ "mediump_mul",	"in0 * in1",	mul<int>,			glu::PRECISION_MEDIUMP,	16,		fullRange16I,	fullRange16I },
+		{ "mediump_div",	"in0 / in1",	div<int>,			glu::PRECISION_MEDIUMP,	16,		fullRange16I,	IVec2(1, 1000) },
+		{ "lowp_add",		"in0 + in1",	add<int>,			glu::PRECISION_LOWP,	8,		fullRange8I,	fullRange8I },
+		{ "lowp_sub",		"in0 - in1",	sub<int>,			glu::PRECISION_LOWP,	8,		fullRange8I,	fullRange8I },
+		{ "lowp_mul",		"in0 * in1",	mul<int>,			glu::PRECISION_LOWP,	8,		fullRange8I,	fullRange8I },
+		{ "lowp_div",		"in0 / in1",	div<int>,			glu::PRECISION_LOWP,	8,		fullRange8I,	IVec2(-50, -1) }
+	};
+
+	static const struct
+	{
+		const char*							name;
+		const char*							op;
+		ShaderUintPrecisionCase::EvalFunc	evalFunc;
+		glu::Precision						precision;
+		int									bits;
+		tcu::UVec2							rangeA;
+		tcu::UVec2							rangeB;
+	} uintCases[] =
+	{
+		// Name				Op				Eval				Precision				Bits	RangeA			RangeB
+		{ "highp_add",		"in0 + in1",	add<deUint32>,		glu::PRECISION_HIGHP,	32,		fullRange32U,	fullRange32U },
+		{ "highp_sub",		"in0 - in1",	sub<deUint32>,		glu::PRECISION_HIGHP,	32,		fullRange32U,	fullRange32U },
+		{ "highp_mul",		"in0 * in1",	mul<deUint32>,		glu::PRECISION_HIGHP,	32,		fullRange32U,	fullRange32U },
+		{ "highp_div",		"in0 / in1",	div<deUint32>,		glu::PRECISION_HIGHP,	32,		fullRange32U,	UVec2(1u, 10000u) },
+		{ "mediump_add",	"in0 + in1",	add<deUint32>,		glu::PRECISION_MEDIUMP,	16,		fullRange16U,	fullRange16U },
+		{ "mediump_sub",	"in0 - in1",	sub<deUint32>,		glu::PRECISION_MEDIUMP,	16,		fullRange16U,	fullRange16U },
+		{ "mediump_mul",	"in0 * in1",	mul<deUint32>,		glu::PRECISION_MEDIUMP,	16,		fullRange16U,	fullRange16U },
+		{ "mediump_div",	"in0 / in1",	div<deUint32>,		glu::PRECISION_MEDIUMP,	16,		fullRange16U,	UVec2(1, 1000u) },
+		{ "lowp_add",		"in0 + in1",	add<deUint32>,		glu::PRECISION_LOWP,	8,		fullRange8U,	fullRange8U },
+		{ "lowp_sub",		"in0 - in1",	sub<deUint32>,		glu::PRECISION_LOWP,	8,		fullRange8U,	fullRange8U },
+		{ "lowp_mul",		"in0 * in1",	mul<deUint32>,		glu::PRECISION_LOWP,	8,		fullRange8U,	fullRange8U },
+		{ "lowp_div",		"in0 / in1",	div<deUint32>,		glu::PRECISION_LOWP,	8,		fullRange8U,	UVec2(1, 50u) }
+	};
+
+	tcu::TestCaseGroup* floatGroup = new tcu::TestCaseGroup(m_testCtx, "float", "Floating-point precision tests");
+	addChild(floatGroup);
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(floatCases); ndx++)
+	{
+		floatGroup->addChild(new ShaderFloatPrecisionCase(m_context,
+														  (string(floatCases[ndx].name) + "_vertex").c_str(), "",
+														  floatCases[ndx].op,
+														  floatCases[ndx].evalFunc,
+														  floatCases[ndx].precision,
+														  floatCases[ndx].rangeA,
+														  floatCases[ndx].rangeB,
+														  true));
+		floatGroup->addChild(new ShaderFloatPrecisionCase(m_context,
+														  (string(floatCases[ndx].name) + "_fragment").c_str(), "",
+														  floatCases[ndx].op,
+														  floatCases[ndx].evalFunc,
+														  floatCases[ndx].precision,
+														  floatCases[ndx].rangeA,
+														  floatCases[ndx].rangeB,
+														  false));
+	}
+
+	tcu::TestCaseGroup* intGroup = new tcu::TestCaseGroup(m_testCtx, "int", "Integer precision tests");
+	addChild(intGroup);
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intCases); ndx++)
+	{
+		intGroup->addChild(new ShaderIntPrecisionCase(m_context,
+													  (string(intCases[ndx].name) + "_vertex").c_str(), "",
+													  intCases[ndx].op,
+													  intCases[ndx].evalFunc,
+													  intCases[ndx].precision,
+													  intCases[ndx].bits,
+													  intCases[ndx].rangeA,
+													  intCases[ndx].rangeB,
+													  true));
+		intGroup->addChild(new ShaderIntPrecisionCase(m_context,
+													  (string(intCases[ndx].name) + "_fragment").c_str(), "",
+													  intCases[ndx].op,
+													  intCases[ndx].evalFunc,
+													  intCases[ndx].precision,
+													  intCases[ndx].bits,
+													  intCases[ndx].rangeA,
+													  intCases[ndx].rangeB,
+													  false));
+	}
+
+	tcu::TestCaseGroup* uintGroup = new tcu::TestCaseGroup(m_testCtx, "uint", "Unsigned integer precision tests");
+	addChild(uintGroup);
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(uintCases); ndx++)
+	{
+		uintGroup->addChild(new ShaderUintPrecisionCase(m_context,
+														(string(uintCases[ndx].name) + "_vertex").c_str(), "",
+														uintCases[ndx].op,
+														uintCases[ndx].evalFunc,
+														uintCases[ndx].precision,
+														uintCases[ndx].bits,
+														uintCases[ndx].rangeA,
+														uintCases[ndx].rangeB,
+														true));
+		uintGroup->addChild(new ShaderUintPrecisionCase(m_context,
+														(string(uintCases[ndx].name) + "_fragment").c_str(), "",
+														uintCases[ndx].op,
+														uintCases[ndx].evalFunc,
+														uintCases[ndx].precision,
+														uintCases[ndx].bits,
+														uintCases[ndx].rangeA,
+														uintCases[ndx].rangeB,
+														false));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderPrecisionTests.hpp b/modules/gles3/functional/es3fShaderPrecisionTests.hpp
new file mode 100644
index 0000000..35b1994
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderPrecisionTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERPRECISIONTESTS_HPP
+#define _ES3FSHADERPRECISIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader precision tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderPrecisionTests : public TestCaseGroup
+{
+public:
+								ShaderPrecisionTests		(Context& context);
+	virtual						~ShaderPrecisionTests		(void);
+
+	virtual void				init						(void);
+
+private:
+								ShaderPrecisionTests		(const ShaderPrecisionTests&);		// not allowed!
+	ShaderPrecisionTests&		operator=					(const ShaderPrecisionTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERPRECISIONTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderReturnTests.cpp b/modules/gles3/functional/es3fShaderReturnTests.cpp
new file mode 100644
index 0000000..c8c1613
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderReturnTests.cpp
@@ -0,0 +1,456 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader return statement tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderReturnTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "tcuStringTemplate.hpp"
+
+#include <map>
+#include <sstream>
+#include <string>
+
+using tcu::StringTemplate;
+
+using std::map;
+using std::string;
+using std::ostringstream;
+
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+enum ReturnMode
+{
+	RETURNMODE_ALWAYS = 0,
+	RETURNMODE_NEVER,
+	RETURNMODE_DYNAMIC,
+
+	RETURNMODE_LAST
+};
+
+// Evaluation functions
+inline void evalReturnAlways	(ShaderEvalContext& c) { c.color.xyz() = c.coords.swizzle(0,1,2); }
+inline void evalReturnNever		(ShaderEvalContext& c) { c.color.xyz() = c.coords.swizzle(3,2,1); }
+inline void evalReturnDynamic	(ShaderEvalContext& c) { c.color.xyz() = (c.coords.x()+c.coords.y() >= 0.0f) ? c.coords.swizzle(0,1,2) : c.coords.swizzle(3,2,1); }
+
+static ShaderEvalFunc getEvalFunc (ReturnMode mode)
+{
+	switch (mode)
+	{
+		case RETURNMODE_ALWAYS:		return evalReturnAlways;
+		case RETURNMODE_NEVER:		return evalReturnNever;
+		case RETURNMODE_DYNAMIC:	return evalReturnDynamic;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return (ShaderEvalFunc)DE_NULL;
+	}
+}
+
+class ShaderReturnCase : public ShaderRenderCase
+{
+public:
+						ShaderReturnCase			(Context& context, const char* name, const char* description, bool isVertexCase, const char* shaderSource, ShaderEvalFunc evalFunc);
+	virtual				~ShaderReturnCase			(void);
+};
+
+ShaderReturnCase::ShaderReturnCase (Context& context, const char* name, const char* description, bool isVertexCase, const char* shaderSource, ShaderEvalFunc evalFunc)
+	: ShaderRenderCase(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
+{
+	if (isVertexCase)
+	{
+		m_vertShaderSource = shaderSource;
+		m_fragShaderSource =
+			"#version 300 es\n"
+			"in mediump vec4 v_color;\n"
+			"layout(location = 0) out mediump vec4 o_color;\n\n"
+			"void main (void)\n"
+			"{\n"
+			"    o_color = v_color;\n"
+			"}\n";
+	}
+	else
+	{
+		m_fragShaderSource = shaderSource;
+		m_vertShaderSource =
+			"#version 300 es\n"
+			"in  highp   vec4 a_position;\n"
+			"in  highp   vec4 a_coords;\n"
+			"out mediump vec4 v_coords;\n\n"
+			"void main (void)\n"
+			"{\n"
+			"    gl_Position = a_position;\n"
+			"    v_coords = a_coords;\n"
+			"}\n";
+	}
+}
+
+ShaderReturnCase::~ShaderReturnCase (void)
+{
+}
+
+ShaderReturnTests::ShaderReturnTests (Context& context)
+	: TestCaseGroup(context, "return", "Return Statement Tests")
+{
+}
+
+ShaderReturnTests::~ShaderReturnTests (void)
+{
+}
+
+ShaderReturnCase* makeConditionalReturnInFuncCase (Context& context, const char* name, const char* description, ReturnMode returnMode, bool isVertex)
+{
+	// Template
+	StringTemplate tmpl(
+		"#version 300 es\n"
+		"in ${COORDPREC} vec4 ${COORDS};\n"
+		"${EXTRADECL}\n"
+		"${COORDPREC} vec4 getColor (void)\n"
+		"{\n"
+		"    if (${RETURNCOND})\n"
+		"        return vec4(${COORDS}.xyz, 1.0);\n"
+		"    return vec4(${COORDS}.wzy, 1.0);\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"${POSITIONWRITE}"
+		"    ${OUTPUT} = getColor();\n"
+		"}\n");
+
+	const char* coords = isVertex ? "a_coords" : "v_coords";
+
+	map<string, string> params;
+
+	params["COORDPREC"]		= isVertex ? "highp"		: "mediump";
+	params["OUTPUT"]		= isVertex ? "v_color"		: "o_color";
+	params["COORDS"]		= coords;
+	params["EXTRADECL"]		= isVertex ? "in highp vec4 a_position;\nout mediump vec4 v_color;\n" : "layout(location = 0) out mediump vec4 o_color;\n";
+	params["POSITIONWRITE"]	= isVertex ? "    gl_Position = a_position;\n" : "";
+
+	switch (returnMode)
+	{
+		case RETURNMODE_ALWAYS:		params["RETURNCOND"] = "true";											break;
+		case RETURNMODE_NEVER:		params["RETURNCOND"] = "false";											break;
+		case RETURNMODE_DYNAMIC:	params["RETURNCOND"] = string(coords) + ".x+" + coords + ".y >= 0.0";	break;
+		default:					DE_ASSERT(DE_FALSE);
+	}
+
+	return new ShaderReturnCase(context, name, description, isVertex, tmpl.specialize(params).c_str(), getEvalFunc(returnMode));
+}
+
+ShaderReturnCase* makeOutputWriteReturnCase (Context& context, const char* name, const char* description, bool inFunction, ReturnMode returnMode, bool isVertex)
+{
+	// Template
+	StringTemplate tmpl(
+		inFunction
+		?
+			"#version 300 es\n"
+			"in ${COORDPREC} vec4 ${COORDS};\n"
+			"${EXTRADECL}\n"
+			"void myfunc (void)\n"
+			"{\n"
+			"    ${OUTPUT} = vec4(${COORDS}.xyz, 1.0);\n"
+			"    if (${RETURNCOND})\n"
+			"        return;\n"
+			"    ${OUTPUT} = vec4(${COORDS}.wzy, 1.0);\n"
+			"}\n\n"
+			"void main (void)\n"
+			"{\n"
+			"${POSITIONWRITE}"
+			"    myfunc();\n"
+			"}\n"
+		:
+			"#version 300 es\n"
+			"in ${COORDPREC} vec4 ${COORDS};\n"
+			"uniform mediump int ui_one;\n"
+			"${EXTRADECL}\n"
+			"void main ()\n"
+			"{\n"
+			"${POSITIONWRITE}"
+			"    ${OUTPUT} = vec4(${COORDS}.xyz, 1.0);\n"
+			"    if (${RETURNCOND})\n"
+			"        return;\n"
+			"    ${OUTPUT} = vec4(${COORDS}.wzy, 1.0);\n"
+			"}\n");
+
+	const char* coords = isVertex ? "a_coords" : "v_coords";
+
+	map<string, string> params;
+
+	params["COORDPREC"]		= isVertex ? "highp"		: "mediump";
+	params["COORDS"]		= coords;
+	params["OUTPUT"]		= isVertex ? "v_color"			: "o_color";
+	params["EXTRADECL"]		= isVertex ? "in highp vec4 a_position;\nout mediump vec4 v_color;\n" : "layout(location = 0) out mediump vec4 o_color;\n";
+	params["POSITIONWRITE"]	= isVertex ? "    gl_Position = a_position;\n" : "";
+
+	switch (returnMode)
+	{
+		case RETURNMODE_ALWAYS:		params["RETURNCOND"] = "true";											break;
+		case RETURNMODE_NEVER:		params["RETURNCOND"] = "false";											break;
+		case RETURNMODE_DYNAMIC:	params["RETURNCOND"] = string(coords) + ".x+" + coords + ".y >= 0.0";	break;
+		default:					DE_ASSERT(DE_FALSE);
+	}
+
+	return new ShaderReturnCase(context, name, description, isVertex, tmpl.specialize(params).c_str(), getEvalFunc(returnMode));
+}
+
+ShaderReturnCase* makeReturnInLoopCase (Context& context, const char* name, const char* description, bool isDynamicLoop, ReturnMode returnMode, bool isVertex)
+{
+	// Template
+	StringTemplate tmpl(
+		"#version 300 es\n"
+		"in ${COORDPREC} vec4 ${COORDS};\n"
+		"uniform mediump int ui_one;\n"
+		"${EXTRADECL}\n"
+		"${COORDPREC} vec4 getCoords (void)\n"
+		"{\n"
+		"    ${COORDPREC} vec4 coords = ${COORDS};\n"
+		"    for (int i = 0; i < ${ITERLIMIT}; i++)\n"
+		"    {\n"
+		"        if (${RETURNCOND})\n"
+		"            return coords;\n"
+		"        coords = coords.wzyx;\n"
+		"    }\n"
+		"    return coords;\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"${POSITIONWRITE}"
+		"    ${OUTPUT} = vec4(getCoords().xyz, 1.0);\n"
+		"}\n");
+
+	const char* coords = isVertex ? "a_coords" : "v_coords";
+
+	map<string, string> params;
+
+	params["COORDPREC"]		= isVertex ? "highp"		: "mediump";
+	params["OUTPUT"]		= isVertex ? "v_color"		: "o_color";
+	params["COORDS"]		= coords;
+	params["EXTRADECL"]		= isVertex ? "in highp vec4 a_position;\nout mediump vec4 v_color;\n" : "layout(location = 0) out mediump vec4 o_color;\n";
+	params["POSITIONWRITE"]	= isVertex ? "    gl_Position = a_position;\n" : "";
+	params["ITERLIMIT"]		= isDynamicLoop ? "ui_one" : "1";
+
+	switch (returnMode)
+	{
+		case RETURNMODE_ALWAYS:		params["RETURNCOND"] = "true";											break;
+		case RETURNMODE_NEVER:		params["RETURNCOND"] = "false";											break;
+		case RETURNMODE_DYNAMIC:	params["RETURNCOND"] = string(coords) + ".x+" + coords + ".y >= 0.0";	break;
+		default:					DE_ASSERT(DE_FALSE);
+	}
+
+	return new ShaderReturnCase(context, name, description, isVertex, tmpl.specialize(params).c_str(), getEvalFunc(returnMode));
+}
+
+static const char* getReturnModeName (ReturnMode mode)
+{
+	switch (mode)
+	{
+		case RETURNMODE_ALWAYS:		return "always";
+		case RETURNMODE_NEVER:		return "never";
+		case RETURNMODE_DYNAMIC:	return "dynamic";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static const char* getReturnModeDesc (ReturnMode mode)
+{
+	switch (mode)
+	{
+		case RETURNMODE_ALWAYS:		return "Always return";
+		case RETURNMODE_NEVER:		return "Never return";
+		case RETURNMODE_DYNAMIC:	return "Return based on coords";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+void ShaderReturnTests::init (void)
+{
+	// Single return statement in function.
+	addChild(new ShaderReturnCase(m_context, "single_return_vertex", "Single return statement in function", true,
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in highp vec4 a_coords;\n"
+		"out highp vec4 v_color;\n\n"
+		"vec4 getColor (void)\n"
+		"{\n"
+		"    return vec4(a_coords.xyz, 1.0);\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_Position = a_position;\n"
+		"    v_color = getColor();\n"
+		"}\n", evalReturnAlways));
+	addChild(new ShaderReturnCase(m_context, "single_return_fragment", "Single return statement in function", false,
+		"#version 300 es\n"
+		"in mediump vec4 v_coords;\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"mediump vec4 getColor (void)\n"
+		"{\n"
+		"    return vec4(v_coords.xyz, 1.0);\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    o_color = getColor();\n"
+		"}\n", evalReturnAlways));
+
+	// Conditional return statement in function.
+	for (int returnMode = 0; returnMode < RETURNMODE_LAST; returnMode++)
+	{
+		for (int isFragment = 0; isFragment < 2; isFragment++)
+		{
+			string name			= string("conditional_return_") + getReturnModeName((ReturnMode)returnMode) + (isFragment ? "_fragment" : "_vertex");
+			string description	= string(getReturnModeDesc((ReturnMode)returnMode)) + " in function";
+			addChild(makeConditionalReturnInFuncCase(m_context, name.c_str(), description.c_str(), (ReturnMode)returnMode, isFragment == 0));
+		}
+	}
+
+	// Unconditional double return in function.
+	addChild(new ShaderReturnCase(m_context, "double_return_vertex", "Unconditional double return in function", true,
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in highp vec4 a_coords;\n"
+		"out highp vec4 v_color;\n\n"
+		"vec4 getColor (void)\n"
+		"{\n"
+		"    return vec4(a_coords.xyz, 1.0);\n"
+		"    return vec4(a_coords.wzy, 1.0);\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_Position = a_position;\n"
+		"    v_color = getColor();\n"
+		"}\n", evalReturnAlways));
+	addChild(new ShaderReturnCase(m_context, "double_return_fragment", "Unconditional double return in function", false,
+		"#version 300 es\n"
+		"in mediump vec4 v_coords;\n"
+		"layout(location = 0) out mediump vec4 o_color;\n\n"
+		"mediump vec4 getColor (void)\n"
+		"{\n"
+		"    return vec4(v_coords.xyz, 1.0);\n"
+		"    return vec4(v_coords.wzy, 1.0);\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    o_color = getColor();\n"
+		"}\n", evalReturnAlways));
+
+	// Last statement in main.
+	addChild(new ShaderReturnCase(m_context, "last_statement_in_main_vertex", "Return as a final statement in main()", true,
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in highp vec4 a_coords;\n"
+		"out highp vec4 v_color;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_Position = a_position;\n"
+		"    v_color = vec4(a_coords.xyz, 1.0);\n"
+		"    return;\n"
+		"}\n", evalReturnAlways));
+	addChild(new ShaderReturnCase(m_context, "last_statement_in_main_fragment", "Return as a final statement in main()", false,
+		"#version 300 es\n"
+		"in mediump vec4 v_coords;\n"
+		"layout(location = 0) out mediump vec4 o_color;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    o_color = vec4(v_coords.xyz, 1.0);\n"
+		"    return;\n"
+		"}\n", evalReturnAlways));
+
+	// Return between output variable writes.
+	for (int inFunc = 0; inFunc < 2; inFunc++)
+	{
+		for (int returnMode = 0; returnMode < RETURNMODE_LAST; returnMode++)
+		{
+			for (int isFragment = 0; isFragment < 2; isFragment++)
+			{
+				string name = string("output_write_") + (inFunc ? "in_func_" : "") + getReturnModeName((ReturnMode)returnMode) + (isFragment ? "_fragment" : "_vertex");
+				string desc = string(getReturnModeDesc((ReturnMode)returnMode)) + (inFunc ? " in user-defined function" : " in main()") + " between output writes";
+
+				addChild(makeOutputWriteReturnCase(m_context, name.c_str(), desc.c_str(), inFunc != 0, (ReturnMode)returnMode, isFragment == 0));
+			}
+		}
+	}
+
+	// Conditional return statement in loop.
+	for (int isDynamicLoop = 0; isDynamicLoop < 2; isDynamicLoop++)
+	{
+		for (int returnMode = 0; returnMode < RETURNMODE_LAST; returnMode++)
+		{
+			for (int isFragment = 0; isFragment < 2; isFragment++)
+			{
+				string name			= string("return_in_") + (isDynamicLoop ? "dynamic" : "static") + "_loop_" + getReturnModeName((ReturnMode)returnMode) + (isFragment ? "_fragment" : "_vertex");
+				string description	= string(getReturnModeDesc((ReturnMode)returnMode)) + " in loop";
+				addChild(makeReturnInLoopCase(m_context, name.c_str(), description.c_str(), isDynamicLoop != 0, (ReturnMode)returnMode, isFragment == 0));
+			}
+		}
+	}
+
+	// Unconditional return in infinite loop.
+	addChild(new ShaderReturnCase(m_context, "return_in_infinite_loop_vertex", "Return in infinite loop", true,
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in highp vec4 a_coords;\n"
+		"out highp vec4 v_color;\n"
+		"uniform int ui_zero;\n\n"
+		"highp vec4 getCoords (void)\n"
+		"{\n"
+		"	for (int i = 1; i < 10; i += ui_zero)\n"
+		"		return a_coords;\n"
+		"	return a_coords.wzyx;\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    gl_Position = a_position;\n"
+		"    v_color = vec4(getCoords().xyz, 1.0);\n"
+		"    return;\n"
+		"}\n", evalReturnAlways));
+	addChild(new ShaderReturnCase(m_context, "return_in_infinite_loop_fragment", "Return in infinite loop", false,
+		"#version 300 es\n"
+		"in mediump vec4 v_coords;\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"uniform int ui_zero;\n\n"
+		"mediump vec4 getCoords (void)\n"
+		"{\n"
+		"	for (int i = 1; i < 10; i += ui_zero)\n"
+		"		return v_coords;\n"
+		"	return v_coords.wzyx;\n"
+		"}\n\n"
+		"void main (void)\n"
+		"{\n"
+		"    o_color = vec4(getCoords().xyz, 1.0);\n"
+		"    return;\n"
+		"}\n", evalReturnAlways));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderReturnTests.hpp b/modules/gles3/functional/es3fShaderReturnTests.hpp
new file mode 100644
index 0000000..b93e249
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderReturnTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERRETURNTESTS_HPP
+#define _ES3FSHADERRETURNTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader return statement tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderReturnTests : public TestCaseGroup
+{
+public:
+							ShaderReturnTests		(Context& context);
+	virtual					~ShaderReturnTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderReturnTests		(const ShaderReturnTests&);		// not allowed!
+	ShaderReturnTests&		operator=				(const ShaderReturnTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERRETURNTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderStateQueryTests.cpp b/modules/gles3/functional/es3fShaderStateQueryTests.cpp
new file mode 100644
index 0000000..4c37bd2
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderStateQueryTests.cpp
@@ -0,0 +1,3645 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Rbo state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+using namespace glw; // GLint and other GL types
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace
+{
+
+static const char* commonTestVertSource		=	"#version 300 es\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vec4(0.0);\n"
+												"}\n\0";
+static const char* commonTestFragSource		=	"#version 300 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;\n"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(0.0);\n"
+												"}\n\0";
+
+static const char* brokenShader				=	"#version 300 es\n"
+												"broken, this should not compile!\n"
+												"\n\0";
+
+// rounds x.1 to x+1
+template <typename T>
+T roundGLfloatToNearestIntegerUp (GLfloat val)
+{
+	return (T)(ceil(val));
+}
+
+// rounds x.9 to x
+template <typename T>
+T roundGLfloatToNearestIntegerDown (GLfloat val)
+{
+	return (T)(floor(val));
+}
+
+bool checkIntEquals (tcu::TestContext& testCtx, GLint got, GLint expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+		return false;
+	}
+	return true;
+}
+
+void checkPointerEquals (tcu::TestContext& testCtx, const void* got, const void* expected)
+{
+	using tcu::TestLog;
+
+	if (got != expected)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void verifyShaderParam (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint shader, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetShaderiv(shader, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntEquals(testCtx, state, reference);
+}
+
+bool verifyProgramParam (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetProgramiv(program, pname, &state);
+
+	return state.verifyValidity(testCtx) && checkIntEquals(testCtx, state, reference);
+}
+
+void verifyActiveUniformParam  (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLuint index, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetActiveUniformsiv(program, 1, &index, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntEquals(testCtx, state, reference);
+}
+
+void verifyActiveUniformBlockParam  (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLuint blockIndex, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetActiveUniformBlockiv(program, blockIndex, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntEquals(testCtx, state, reference);
+}
+
+void verifyCurrentVertexAttribf (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[4]> attribValue;
+	gl.glGetVertexAttribfv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);
+
+	attribValue.verifyValidity(testCtx);
+
+	if (attribValue[0] != x || attribValue[1] != y || attribValue[2] != z || attribValue[3] != w)
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: Expected [" << x << "," << y << "," << z << "," << w << "];"
+			<< "got [" << attribValue[0] << "," << attribValue[1] << "," << attribValue[2] << "," << attribValue[3] << "]"
+			<< TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid attribute value");
+	}
+}
+
+void verifyCurrentVertexAttribIi (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLint index, GLint x, GLint y, GLint z, GLint w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[4]> attribValue;
+	gl.glGetVertexAttribIiv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);
+
+	attribValue.verifyValidity(testCtx);
+
+	if (attribValue[0] != x || attribValue[1] != y || attribValue[2] != z || attribValue[3] != w)
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: Expected [" << x << "," << y << "," << z << "," << w << "];"
+			<< "got [" << attribValue[0] << "," << attribValue[1] << "," << attribValue[2] << "," << attribValue[3] << "]"
+			<< TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid attribute value");
+	}
+}
+
+void verifyCurrentVertexAttribIui (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLint index, GLuint x, GLuint y, GLuint z, GLuint w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLuint[4]> attribValue;
+	gl.glGetVertexAttribIuiv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);
+
+	attribValue.verifyValidity(testCtx);
+
+	if (attribValue[0] != x || attribValue[1] != y || attribValue[2] != z || attribValue[3] != w)
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: Expected [" << x << "," << y << "," << z << "," << w << "];"
+			<< "got [" << attribValue[0] << "," << attribValue[1] << "," << attribValue[2] << "," << attribValue[3] << "]"
+			<< TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid attribute value");
+	}
+}
+
+void verifyCurrentVertexAttribConversion (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[4]> attribValue;
+	gl.glGetVertexAttribiv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);
+
+	attribValue.verifyValidity(testCtx);
+
+	const GLint referenceAsGLintMin[] =
+	{
+		roundGLfloatToNearestIntegerDown<GLint>(x),
+		roundGLfloatToNearestIntegerDown<GLint>(y),
+		roundGLfloatToNearestIntegerDown<GLint>(z),
+		roundGLfloatToNearestIntegerDown<GLint>(w)
+	};
+	const GLint referenceAsGLintMax[] =
+	{
+		roundGLfloatToNearestIntegerUp<GLint>(x),
+		roundGLfloatToNearestIntegerUp<GLint>(y),
+		roundGLfloatToNearestIntegerUp<GLint>(z),
+		roundGLfloatToNearestIntegerUp<GLint>(w)
+	};
+
+	if (attribValue[0] < referenceAsGLintMin[0] || attribValue[0] > referenceAsGLintMax[0] ||
+		attribValue[1] < referenceAsGLintMin[1] || attribValue[1] > referenceAsGLintMax[1] ||
+		attribValue[2] < referenceAsGLintMin[2] || attribValue[2] > referenceAsGLintMax[2] ||
+		attribValue[3] < referenceAsGLintMin[3] || attribValue[3] > referenceAsGLintMax[3])
+	{
+		testCtx.getLog() << TestLog::Message
+			<< "// ERROR: expected in range "
+			<< "[" << referenceAsGLintMin[0] << " " << referenceAsGLintMax[0] << "], "
+			<< "[" << referenceAsGLintMin[1] << " " << referenceAsGLintMax[1] << "], "
+			<< "[" << referenceAsGLintMin[2] << " " << referenceAsGLintMax[2] << "], "
+			<< "[" << referenceAsGLintMin[3] << " " << referenceAsGLintMax[3] << "]"
+			<< "; got "
+			<< attribValue[0] << ", "
+			<< attribValue[1] << ", "
+			<< attribValue[2] << ", "
+			<< attribValue[3] << " "
+			<< "; Input="
+			<< x << "; "
+			<< y << "; "
+			<< z << "; "
+			<< w << " " << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid attribute value");
+	}
+}
+
+void verifyVertexAttrib (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLint index, GLenum pname, GLenum reference)
+{
+	StateQueryMemoryWriteGuard<GLint> state;
+	gl.glGetVertexAttribIiv(index, pname, &state);
+
+	if (state.verifyValidity(testCtx))
+		checkIntEquals(testCtx, state, reference);
+}
+
+void verifyUniformValue1f (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, float x)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[1]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x
+		<< "]; got ["
+		<< state[0]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue2f (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, float x, float y)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[2]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue3f (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, float x, float y, float z)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[3]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y ||
+		state[2] != z)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y << ", "
+		<< z
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1] << ", "
+		<< state[2]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue4f (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, float x, float y, float z, float w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[4]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y ||
+		state[2] != z ||
+		state[3] != w)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y << ", "
+		<< z << ", "
+		<< w
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1] << ", "
+		<< state[2] << ", "
+		<< state[3]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue1i (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLint x)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[1]> state;
+	gl.glGetUniformiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x
+		<< "]; got ["
+		<< state[0]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue2i (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLint x, GLint y)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[2]> state;
+	gl.glGetUniformiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue3i (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLint x, GLint y, GLint z)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[3]> state;
+	gl.glGetUniformiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y ||
+		state[2] != z)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y << ", "
+		<< z
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1] << ", "
+		<< state[2]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue4i (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLint x, GLint y, GLint z, GLint w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint[4]> state;
+	gl.glGetUniformiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y ||
+		state[2] != z ||
+		state[3] != w)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y << ", "
+		<< z << ", "
+		<< w
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1] << ", "
+		<< state[2] << ", "
+		<< state[3]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue1ui (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLuint x)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLuint[1]> state;
+	gl.glGetUniformuiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x
+		<< "]; got ["
+		<< state[0]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue2ui (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLuint x, GLuint y)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLuint[2]> state;
+	gl.glGetUniformuiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue3ui (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLuint x, GLuint y, GLuint z)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLuint[3]> state;
+	gl.glGetUniformuiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y ||
+		state[2] != z)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y << ", "
+		<< z
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1] << ", "
+		<< state[2]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+void verifyUniformValue4ui (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, GLuint x, GLuint y, GLuint z, GLuint w)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLuint[4]> state;
+	gl.glGetUniformuiv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state[0] != x ||
+		state[1] != y ||
+		state[2] != z ||
+		state[3] != w)
+	{
+		testCtx.getLog() << TestLog::Message
+		<< "// ERROR: expected ["
+		<< x << ", "
+		<< y << ", "
+		<< z << ", "
+		<< w
+		<< "]; got ["
+		<< state[0] << ", "
+		<< state[1] << ", "
+		<< state[2] << ", "
+		<< state[3]
+		<< "]"
+		<< TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+	}
+}
+
+template <int Count>
+void verifyUniformValues (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, const GLfloat* values)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[Count]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (int ndx = 0; ndx < Count; ++ndx)
+	{
+		if (values[ndx] != state[ndx])
+		{
+			testCtx.getLog() << TestLog::Message << "// ERROR: at index " << ndx << " expected " << values[ndx] << "; got " << state[ndx] << TestLog::EndMessage;
+
+			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+		}
+	}
+}
+
+template <int N>
+void verifyUniformMatrixValues (tcu::TestContext& testCtx, glu::CallLogWrapper& gl, GLuint program, GLint location, const GLfloat* values, bool transpose)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat[N*N]> state;
+	gl.glGetUniformfv(program, location, state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	for (int y = 0; y < N; ++y)
+		for (int x = 0; x < N; ++x)
+		{
+			const int refIndex = y*N + x;
+			const int stateIndex = transpose ? (x*N + y) : (y*N + x);
+
+			if (values[refIndex] != state[stateIndex])
+			{
+				testCtx.getLog() << TestLog::Message << "// ERROR: at index [" << y << "][" << x << "] expected " << values[refIndex] << "; got " << state[stateIndex] << TestLog::EndMessage;
+
+				if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
+			}
+		}
+}
+
+class ShaderTypeCase : public ApiCase
+{
+public:
+	ShaderTypeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		const GLenum shaderTypes[] = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(shaderTypes); ++ndx)
+		{
+			const GLuint shader = glCreateShader(shaderTypes[ndx]);
+			verifyShaderParam(m_testCtx, *this, shader, GL_SHADER_TYPE, shaderTypes[ndx]);
+			glDeleteShader(shader);
+		}
+	}
+};
+
+class ShaderCompileStatusCase : public ApiCase
+{
+public:
+	ShaderCompileStatusCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_FALSE);
+		verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_FALSE);
+
+		glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
+		verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_TRUE);
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ShaderInfoLogCase : public ApiCase
+{
+public:
+	ShaderInfoLogCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		// INFO_LOG_LENGTH is 0 by default and it includes null-terminator
+		const GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+		verifyShaderParam(m_testCtx, *this, shader, GL_INFO_LOG_LENGTH, 0);
+
+		glShaderSource(shader, 1, &brokenShader, DE_NULL);
+		glCompileShader(shader);
+		expectError(GL_NO_ERROR);
+
+		// check the log length
+		StateQueryMemoryWriteGuard<GLint> logLength;
+		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
+		if (!logLength.verifyValidity(m_testCtx))
+		{
+			glDeleteShader(shader);
+			return;
+		}
+		if (logLength == 0)
+		{
+			glDeleteShader(shader);
+			return;
+		}
+
+		// check normal case
+		{
+			char buffer[2048] = {'x'}; // non-zero initialization
+
+			GLint written = 0; // written does not include null terminator
+			glGetShaderInfoLog(shader, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);
+
+			// check lengths are consistent
+			if (logLength <= DE_LENGTH_OF_ARRAY(buffer))
+			{
+				if (written != logLength-1)
+				{
+					m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length " << logLength-1 << "; got " << written << TestLog::EndMessage;
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
+				}
+			}
+
+			// check null-terminator, either at end of buffer or at buffer[written]
+			const char* terminator = &buffer[DE_LENGTH_OF_ARRAY(buffer) - 1];
+			if (logLength < DE_LENGTH_OF_ARRAY(buffer))
+				terminator = &buffer[written];
+
+			if (*terminator != '\0')
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator, got " << (int)*terminator << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log terminator");
+			}
+		}
+
+		// check with too small buffer
+		{
+			char buffer[2048] = {'x'}; // non-zero initialization
+
+			// check string always ends with \0, even with small buffers
+			GLint written = 0;
+			glGetShaderInfoLog(shader, 1, &written, buffer);
+			if (written != 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length 0; got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
+			}
+			if (buffer[0] != '\0')
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator, got " << (int)buffer[0] << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log terminator");
+			}
+		}
+
+		glDeleteShader(shader);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ShaderSourceCase : public ApiCase
+{
+public:
+	ShaderSourceCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		// SHADER_SOURCE_LENGTH does include 0-terminator
+		const GLuint shader = glCreateShader(GL_VERTEX_SHADER);
+		verifyShaderParam(m_testCtx, *this, shader, GL_SHADER_SOURCE_LENGTH, 0);
+
+		// check the SHADER_SOURCE_LENGTH
+		{
+			glShaderSource(shader, 1, &brokenShader, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			StateQueryMemoryWriteGuard<GLint> sourceLength;
+			glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &sourceLength);
+
+			sourceLength.verifyValidity(m_testCtx);
+
+			const GLint referenceLength = (GLint)std::string(brokenShader).length() + 1; // including the null terminator
+			if (sourceLength != referenceLength)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length " << referenceLength	<< "; got " << sourceLength << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
+			}
+		}
+
+		// check the concat source SHADER_SOURCE_LENGTH
+		{
+			const char* shaders[] = {brokenShader, brokenShader};
+			glShaderSource(shader, 2, shaders, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			StateQueryMemoryWriteGuard<GLint> sourceLength;
+			glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &sourceLength);
+
+			sourceLength.verifyValidity(m_testCtx);
+
+			const GLint referenceLength = 2 * (GLint)std::string(brokenShader).length() + 1; // including the null terminator
+			if (sourceLength != referenceLength)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length " << referenceLength << "; got " << sourceLength << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
+			}
+		}
+
+		// check the string length
+		{
+			char buffer[2048] = {'x'};
+			DE_ASSERT(DE_LENGTH_OF_ARRAY(buffer) > 2 * (int)std::string(brokenShader).length());
+
+			GLint written = 0; // not inluding null-terminator
+			glGetShaderSource(shader, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);
+
+			const GLint referenceLength = 2 * (GLint)std::string(brokenShader).length();
+			if (written != referenceLength)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected write length " << referenceLength << "; got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
+			}
+			// check null pointer at
+			else
+			{
+				if (buffer[referenceLength] != '\0')
+				{
+					m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator at " << referenceLength << TestLog::EndMessage;
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "did not get a null terminator");
+				}
+			}
+		}
+
+		// check with small buffer
+		{
+			char buffer[2048] = {'x'};
+
+			GLint written = 0;
+			glGetShaderSource(shader, 1, &written, buffer);
+
+			if (written != 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected write length 0; got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
+			}
+			if (buffer[0] != '\0')
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator; got=" << int(buffer[0]) << ", char=" << buffer[0] << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid terminator");
+			}
+		}
+
+		glDeleteShader(shader);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class DeleteStatusCase : public ApiCase
+{
+public:
+	DeleteStatusCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
+		verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_TRUE);
+
+		GLuint shaderProg = glCreateProgram();
+		glAttachShader(shaderProg, shaderVert);
+		glAttachShader(shaderProg, shaderFrag);
+		glLinkProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+
+		verifyProgramParam	(m_testCtx, *this, shaderProg, GL_LINK_STATUS, GL_TRUE);
+
+		verifyShaderParam	(m_testCtx, *this, shaderVert, GL_DELETE_STATUS, GL_FALSE);
+		verifyShaderParam	(m_testCtx, *this, shaderFrag, GL_DELETE_STATUS, GL_FALSE);
+		verifyProgramParam	(m_testCtx, *this, shaderProg, GL_DELETE_STATUS, GL_FALSE);
+		expectError(GL_NO_ERROR);
+
+		glUseProgram(shaderProg);
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+
+		verifyShaderParam	(m_testCtx, *this, shaderVert, GL_DELETE_STATUS, GL_TRUE);
+		verifyShaderParam	(m_testCtx, *this, shaderFrag, GL_DELETE_STATUS, GL_TRUE);
+		verifyProgramParam	(m_testCtx, *this, shaderProg, GL_DELETE_STATUS, GL_TRUE);
+		expectError(GL_NO_ERROR);
+
+		glUseProgram(0);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class CurrentVertexAttribInitialCase : public ApiCase
+{
+public:
+	CurrentVertexAttribInitialCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		int attribute_count = 16;
+		glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);
+
+		// initial
+
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			StateQueryMemoryWriteGuard<GLfloat[4]> attribValue;
+			glGetVertexAttribfv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);
+			attribValue.verifyValidity(m_testCtx);
+
+			if (attribValue[0] != 0.0f || attribValue[1] != 0.0f || attribValue[2] != 0.0f || attribValue[3] != 1.0f)
+			{
+				m_testCtx.getLog() << TestLog::Message
+					<< "// ERROR: Expected [0, 0, 0, 1];"
+					<< "got [" << attribValue[0] << "," << attribValue[1] << "," << attribValue[2] << "," << attribValue[3] << "]"
+					<< TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid attribute value");
+			}
+		}
+	}
+};
+
+class CurrentVertexAttribFloatCase : public ApiCase
+{
+public:
+	CurrentVertexAttribFloatCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		de::Random rnd(0xabcdef);
+
+		int attribute_count = 16;
+		glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);
+
+		// test write float/read float
+
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = rnd.getFloat(-64000, 64000);
+			const GLfloat w = rnd.getFloat(-64000, 64000);
+
+			glVertexAttrib4f(index, x, y, z, w);
+			verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = rnd.getFloat(-64000, 64000);
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib3f(index, x, y, z);
+			verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = 0.0f;
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib2f(index, x, y);
+			verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = 0.0f;
+			const GLfloat z = 0.0f;
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib1f(index, x);
+			verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
+		}
+	}
+};
+
+class CurrentVertexAttribIntCase : public ApiCase
+{
+public:
+	CurrentVertexAttribIntCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		de::Random rnd(0xabcdef);
+
+		int attribute_count = 16;
+		glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);
+
+		// test write float/read float
+
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLint x = rnd.getInt(-64000, 64000);
+			const GLint y = rnd.getInt(-64000, 64000);
+			const GLint z = rnd.getInt(-64000, 64000);
+			const GLint w = rnd.getInt(-64000, 64000);
+
+			glVertexAttribI4i(index, x, y, z, w);
+			verifyCurrentVertexAttribIi(m_testCtx, *this, index, x, y, z, w);
+		}
+	}
+};
+
+class CurrentVertexAttribUintCase : public ApiCase
+{
+public:
+	CurrentVertexAttribUintCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		de::Random rnd(0xabcdef);
+
+		int attribute_count = 16;
+		glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);
+
+		// test write float/read float
+
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLuint x = rnd.getInt(0, 64000);
+			const GLuint y = rnd.getInt(0, 64000);
+			const GLuint z = rnd.getInt(0, 64000);
+			const GLuint w = rnd.getInt(0, 64000);
+
+			glVertexAttribI4ui(index, x, y, z, w);
+			verifyCurrentVertexAttribIui(m_testCtx, *this, index, x, y, z, w);
+		}
+	}
+};
+
+class CurrentVertexAttribConversionCase : public ApiCase
+{
+public:
+	CurrentVertexAttribConversionCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		de::Random rnd(0xabcdef);
+
+		int attribute_count = 16;
+		glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);
+
+		// test write float/read float
+
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = rnd.getFloat(-64000, 64000);
+			const GLfloat w = rnd.getFloat(-64000, 64000);
+
+			glVertexAttrib4f(index, x, y, z, w);
+			verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = rnd.getFloat(-64000, 64000);
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib3f(index, x, y, z);
+			verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = rnd.getFloat(-64000, 64000);
+			const GLfloat z = 0.0f;
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib2f(index, x, y);
+			verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
+		}
+		for (int index = 0; index < attribute_count; ++index)
+		{
+			const GLfloat x = rnd.getFloat(-64000, 64000);
+			const GLfloat y = 0.0f;
+			const GLfloat z = 0.0f;
+			const GLfloat w = 1.0f;
+
+			glVertexAttrib1f(index, x);
+			verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
+		}
+	}
+};
+
+class ProgramInfoLogCase : public ApiCase
+{
+public:
+	ProgramInfoLogCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &brokenShader, DE_NULL);
+		glShaderSource(shaderFrag, 1, &brokenShader, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+
+		// check INFO_LOG_LENGTH == GetProgramInfoLog len
+		{
+			char buffer[2048] = {'x'};
+
+			GLint written = 0;
+			glGetProgramInfoLog(program, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);
+
+			StateQueryMemoryWriteGuard<GLint> logLength;
+			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
+			logLength.verifyValidity(m_testCtx);
+
+			if (logLength != 0 && written+1 != logLength) // INFO_LOG_LENGTH contains 0-terminator
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected INFO_LOG_LENGTH " << written+1 << "; got " << logLength << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
+			}
+		}
+
+		// check GetProgramInfoLog works with too small buffer
+		{
+			char buffer[2048] = {'x'};
+
+			GLint written = 0;
+			glGetProgramInfoLog(program, 1, &written, buffer);
+
+			if (written != 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected write length 0; got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
+			}
+		}
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ProgramValidateStatusCase : public ApiCase
+{
+public:
+	ProgramValidateStatusCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// test validate ok
+		{
+			GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+			GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+			glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
+			glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);
+
+			glCompileShader(shaderVert);
+			glCompileShader(shaderFrag);
+			expectError(GL_NO_ERROR);
+
+			GLuint program = glCreateProgram();
+			glAttachShader(program, shaderVert);
+			glAttachShader(program, shaderFrag);
+			glLinkProgram(program);
+			expectError(GL_NO_ERROR);
+
+			verifyShaderParam	(m_testCtx, *this, shaderVert,	GL_COMPILE_STATUS,	GL_TRUE);
+			verifyShaderParam	(m_testCtx, *this, shaderFrag,	GL_COMPILE_STATUS,	GL_TRUE);
+			verifyProgramParam	(m_testCtx, *this, program,		GL_LINK_STATUS,		GL_TRUE);
+
+			glValidateProgram(program);
+			verifyProgramParam(m_testCtx, *this, program, GL_VALIDATE_STATUS, GL_TRUE);
+
+			glDeleteShader(shaderVert);
+			glDeleteShader(shaderFrag);
+			glDeleteProgram(program);
+			expectError(GL_NO_ERROR);
+		}
+
+		// test with broken shader
+		{
+			GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+			GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+			glShaderSource(shaderVert, 1, &commonTestVertSource,	DE_NULL);
+			glShaderSource(shaderFrag, 1, &brokenShader,			DE_NULL);
+
+			glCompileShader(shaderVert);
+			glCompileShader(shaderFrag);
+			expectError(GL_NO_ERROR);
+
+			GLuint program = glCreateProgram();
+			glAttachShader(program, shaderVert);
+			glAttachShader(program, shaderFrag);
+			glLinkProgram(program);
+			expectError(GL_NO_ERROR);
+
+			verifyShaderParam	(m_testCtx, *this, shaderVert,	GL_COMPILE_STATUS,	GL_TRUE);
+			verifyShaderParam	(m_testCtx, *this, shaderFrag,	GL_COMPILE_STATUS,	GL_FALSE);
+			verifyProgramParam	(m_testCtx, *this, program,		GL_LINK_STATUS,		GL_FALSE);
+
+			glValidateProgram(program);
+			verifyProgramParam(m_testCtx, *this, program, GL_VALIDATE_STATUS, GL_FALSE);
+
+			glDeleteShader(shaderVert);
+			glDeleteShader(shaderFrag);
+			glDeleteProgram(program);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class ProgramAttachedShadersCase : public ApiCase
+{
+public:
+	ProgramAttachedShadersCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		// check ATTACHED_SHADERS
+
+		GLuint program = glCreateProgram();
+		verifyProgramParam(m_testCtx, *this, program, GL_ATTACHED_SHADERS, 0);
+		expectError(GL_NO_ERROR);
+
+		glAttachShader(program, shaderVert);
+		verifyProgramParam(m_testCtx, *this, program, GL_ATTACHED_SHADERS, 1);
+		expectError(GL_NO_ERROR);
+
+		glAttachShader(program, shaderFrag);
+		verifyProgramParam(m_testCtx, *this, program, GL_ATTACHED_SHADERS, 2);
+		expectError(GL_NO_ERROR);
+
+		// check GetAttachedShaders
+		{
+			GLuint shaders[2] = {0, 0};
+			GLint count = 0;
+			glGetAttachedShaders(program, DE_LENGTH_OF_ARRAY(shaders), &count, shaders);
+
+			if (count != 2)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 2; got " << count << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
+			}
+			// shaders are the attached shaders?
+			if (!((shaders[0] == shaderVert && shaders[1] == shaderFrag) ||
+				  (shaders[0] == shaderFrag && shaders[1] == shaderVert)))
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected {" << shaderVert << ", " << shaderFrag << "}; got {" << shaders[0] << ", " << shaders[1] << "}" << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
+			}
+		}
+
+		// check GetAttachedShaders with too small buffer
+		{
+			GLuint shaders[2] = {0, 0};
+			GLint count = 0;
+
+			glGetAttachedShaders(program, 0, &count, shaders);
+			if (count != 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 0; got " << count << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
+			}
+
+			count = 0;
+			glGetAttachedShaders(program, 1, &count, shaders);
+			if (count != 1)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 1; got " << count << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
+			}
+		}
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ProgramActiveUniformNameCase : public ApiCase
+{
+public:
+	ProgramActiveUniformNameCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		static const char* testVertSource =
+			"#version 300 es\n"
+			"uniform highp float uniformNameWithLength23;\n"
+			"uniform highp vec2 uniformVec2;\n"
+			"uniform highp mat4 uniformMat4;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(0.0) + vec4(uniformNameWithLength23) + vec4(uniformVec2.x) + vec4(uniformMat4[2][3]);\n"
+			"}\n\0";
+		static const char* testFragSource =
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(0.0);\n"
+			"}\n\0";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		expectError(GL_NO_ERROR);
+
+		verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_UNIFORMS, 3);
+		verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_UNIFORM_MAX_LENGTH, (GLint)std::string("uniformNameWithLength23").length() + 1); // including a null terminator
+		expectError(GL_NO_ERROR);
+
+		const char* uniformNames[] =
+		{
+			"uniformNameWithLength23",
+			"uniformVec2",
+			"uniformMat4"
+		};
+		StateQueryMemoryWriteGuard<GLuint[DE_LENGTH_OF_ARRAY(uniformNames)]> uniformIndices;
+		glGetUniformIndices(program, DE_LENGTH_OF_ARRAY(uniformNames), uniformNames, uniformIndices);
+		uniformIndices.verifyValidity(m_testCtx);
+
+		// check name lengths
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(uniformNames); ++ndx)
+		{
+			const GLuint uniformIndex = uniformIndices[ndx];
+
+			StateQueryMemoryWriteGuard<GLint> uniformNameLen;
+			glGetActiveUniformsiv(program, 1, &uniformIndex, GL_UNIFORM_NAME_LENGTH, &uniformNameLen);
+
+			uniformNameLen.verifyValidity(m_testCtx);
+
+			const GLint referenceLength = (GLint)std::string(uniformNames[ndx]).length() + 1;
+			if (referenceLength != uniformNameLen) // uniformNameLen is with null terminator
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << referenceLength << "got " << uniformNameLen << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform name length");
+			}
+		}
+
+		// check names
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(uniformNames); ++ndx)
+		{
+			char buffer[2048] = {'x'};
+
+			const GLuint uniformIndex = uniformIndices[ndx];
+
+			GLint written = 0; // null terminator not included
+			GLint size = 0;
+			GLenum type = 0;
+			glGetActiveUniform(program, uniformIndex, DE_LENGTH_OF_ARRAY(buffer), &written, &size, &type, buffer);
+
+			const GLint referenceLength = (GLint)std::string(uniformNames[ndx]).length();
+			if (referenceLength != written)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << referenceLength << "got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform name length");
+			}
+
+			// and with too small buffer
+			written = 0;
+			glGetActiveUniform(program, uniformIndex, 1, &written, &size, &type, buffer);
+
+			if (written != 0)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 0 got " << written << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform name length");
+			}
+		}
+
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ProgramUniformCase : public ApiCase
+{
+public:
+	ProgramUniformCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		const struct UniformType
+		{
+			const char* declaration;
+			const char* postDeclaration;
+			const char* precision;
+			const char* layout;
+			const char* getter;
+			GLenum		type;
+			GLint		size;
+			GLint		isRowMajor;
+		} uniformTypes[] =
+		{
+			{ "float",					"",			"highp",	"",						"uniformValue",							GL_FLOAT,							1, GL_FALSE },
+			{ "float[2]",				"",			"highp",	"",						"uniformValue[1]",						GL_FLOAT,							2, GL_FALSE },
+			{ "vec2",					"",			"highp",	"",						"uniformValue.x",						GL_FLOAT_VEC2,						1, GL_FALSE },
+			{ "vec3",					"",			"highp",	"",						"uniformValue.x",						GL_FLOAT_VEC3,						1, GL_FALSE },
+			{ "vec4",					"",			"highp",	"",						"uniformValue.x",						GL_FLOAT_VEC4,						1, GL_FALSE },
+			{ "int",					"",			"highp",	"",						"float(uniformValue)",					GL_INT,								1, GL_FALSE },
+			{ "ivec2",					"",			"highp",	"",						"float(uniformValue.x)",				GL_INT_VEC2,						1, GL_FALSE },
+			{ "ivec3",					"",			"highp",	"",						"float(uniformValue.x)",				GL_INT_VEC3,						1, GL_FALSE },
+			{ "ivec4",					"",			"highp",	"",						"float(uniformValue.x)",				GL_INT_VEC4,						1, GL_FALSE },
+			{ "uint",					"",			"highp",	"",						"float(uniformValue)",					GL_UNSIGNED_INT,					1, GL_FALSE },
+			{ "uvec2",					"",			"highp",	"",						"float(uniformValue.x)",				GL_UNSIGNED_INT_VEC2,				1, GL_FALSE },
+			{ "uvec3",					"",			"highp",	"",						"float(uniformValue.x)",				GL_UNSIGNED_INT_VEC3,				1, GL_FALSE },
+			{ "uvec4",					"",			"highp",	"",						"float(uniformValue.x)",				GL_UNSIGNED_INT_VEC4,				1, GL_FALSE },
+			{ "bool",					"",			"",			"",						"float(uniformValue)",					GL_BOOL,							1, GL_FALSE },
+			{ "bvec2",					"",			"",			"",						"float(uniformValue.x)",				GL_BOOL_VEC2,						1, GL_FALSE },
+			{ "bvec3",					"",			"",			"",						"float(uniformValue.x)",				GL_BOOL_VEC3,						1, GL_FALSE },
+			{ "bvec4",					"",			"",			"",						"float(uniformValue.x)",				GL_BOOL_VEC4,						1, GL_FALSE },
+			{ "mat2",					"",			"highp",	"",						"float(uniformValue[0][0])",			GL_FLOAT_MAT2,						1, GL_FALSE },
+			{ "mat3",					"",			"highp",	"",						"float(uniformValue[0][0])",			GL_FLOAT_MAT3,						1, GL_FALSE },
+			{ "mat4",					"",			"highp",	"",						"float(uniformValue[0][0])",			GL_FLOAT_MAT4,						1, GL_FALSE },
+			{ "mat2x3",					"",			"highp",	"",						"float(uniformValue[0][0])",			GL_FLOAT_MAT2x3,					1, GL_FALSE },
+			{ "mat2x4",					"",			"highp",	"",						"float(uniformValue[0][0])",			GL_FLOAT_MAT2x4,					1, GL_FALSE },
+			{ "mat3x2",					"",			"highp",	"",						"float(uniformValue[0][0])",			GL_FLOAT_MAT3x2,					1, GL_FALSE },
+			{ "mat3x4",					"",			"highp",	"",						"float(uniformValue[0][0])",			GL_FLOAT_MAT3x4,					1, GL_FALSE },
+			{ "mat4x2",					"",			"highp",	"",						"float(uniformValue[0][0])",			GL_FLOAT_MAT4x2,					1, GL_FALSE },
+			{ "mat4x3",					"",			"highp",	"",						"float(uniformValue[0][0])",			GL_FLOAT_MAT4x3,					1, GL_FALSE },
+			{ "sampler2D",				"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_SAMPLER_2D,						1, GL_FALSE },
+			{ "sampler3D",				"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_SAMPLER_3D,						1, GL_FALSE },
+			{ "samplerCube",			"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_SAMPLER_CUBE,					1, GL_FALSE },
+			{ "sampler2DShadow",		"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_SAMPLER_2D_SHADOW,				1, GL_FALSE },
+			{ "sampler2DArray",			"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_SAMPLER_2D_ARRAY,				1, GL_FALSE },
+			{ "sampler2DArrayShadow",	"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_SAMPLER_2D_ARRAY_SHADOW,			1, GL_FALSE },
+			{ "samplerCubeShadow",		"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_SAMPLER_CUBE_SHADOW,				1, GL_FALSE },
+			{ "isampler2D",				"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_INT_SAMPLER_2D,					1, GL_FALSE },
+			{ "isampler3D",				"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_INT_SAMPLER_3D,					1, GL_FALSE },
+			{ "isamplerCube",			"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_INT_SAMPLER_CUBE,				1, GL_FALSE },
+			{ "isampler2DArray",		"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_INT_SAMPLER_2D_ARRAY,			1, GL_FALSE },
+			{ "usampler2D",				"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_UNSIGNED_INT_SAMPLER_2D,			1, GL_FALSE },
+			{ "usampler3D",				"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_UNSIGNED_INT_SAMPLER_3D,			1, GL_FALSE },
+			{ "usamplerCube",			"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_UNSIGNED_INT_SAMPLER_CUBE,		1, GL_FALSE },
+			{ "usampler2DArray",		"",			"highp",	"",						"float(textureSize(uniformValue,0).r)",	GL_UNSIGNED_INT_SAMPLER_2D_ARRAY,	1, GL_FALSE },
+		};
+
+		static const char* vertSource =
+			"#version 300 es\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(0.0);\n"
+			"}\n\0";
+
+		GLuint shaderVert	= glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag	= glCreateShader(GL_FRAGMENT_SHADER);
+		GLuint program		= glCreateProgram();
+
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+
+		glShaderSource(shaderVert, 1, &vertSource, DE_NULL);
+		glCompileShader(shaderVert);
+		expectError(GL_NO_ERROR);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(uniformTypes); ++ndx)
+		{
+			tcu::ScopedLogSection(m_log, uniformTypes[ndx].declaration, std::string("Verify type of ") + uniformTypes[ndx].declaration + " variable" + uniformTypes[ndx].postDeclaration );
+
+			// gen fragment shader
+
+			std::ostringstream frag;
+			frag << "#version 300 es\n";
+			frag << uniformTypes[ndx].layout << "uniform " << uniformTypes[ndx].precision << " " << uniformTypes[ndx].declaration << " uniformValue" << uniformTypes[ndx].postDeclaration << ";\n";
+			frag << "layout(location = 0) out mediump vec4 fragColor;\n";
+			frag << "void main (void)\n";
+			frag << "{\n";
+			frag << "	fragColor = vec4(" << uniformTypes[ndx].getter << ");\n";
+			frag << "}\n";
+
+			{
+				std::string fragmentSource = frag.str();
+				const char* fragmentSourceCStr = fragmentSource.c_str();
+				glShaderSource(shaderFrag, 1, &fragmentSourceCStr, DE_NULL);
+			}
+
+			// compile & link
+
+			glCompileShader(shaderFrag);
+			glLinkProgram(program);
+
+			// test
+			if (verifyProgramParam(m_testCtx, *this, program, GL_LINK_STATUS, GL_TRUE))
+			{
+				const char* uniformNames[] = {"uniformValue"};
+				StateQueryMemoryWriteGuard<GLuint> uniformIndex;
+				glGetUniformIndices(program, 1, uniformNames, &uniformIndex);
+				uniformIndex.verifyValidity(m_testCtx);
+
+				verifyActiveUniformParam(m_testCtx, *this, program, uniformIndex, GL_UNIFORM_TYPE,			uniformTypes[ndx].type);
+				verifyActiveUniformParam(m_testCtx, *this, program, uniformIndex, GL_UNIFORM_SIZE,			uniformTypes[ndx].size);
+				verifyActiveUniformParam(m_testCtx, *this, program, uniformIndex, GL_UNIFORM_IS_ROW_MAJOR,	uniformTypes[ndx].isRowMajor);
+			}
+		}
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ProgramActiveUniformBlocksCase : public ApiCase
+{
+public:
+	ProgramActiveUniformBlocksCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		static const char* testVertSource =
+			"#version 300 es\n"
+			"uniform longlongUniformBlockName {highp vec2 vector2;} longlongUniformInstanceName;\n"
+			"uniform shortUniformBlockName {highp vec2 vector2;highp vec4 vector4;} shortUniformInstanceName;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = shortUniformInstanceName.vector4 + vec4(longlongUniformInstanceName.vector2.x) + vec4(shortUniformInstanceName.vector2.x);\n"
+			"}\n\0";
+		static const char* testFragSource =
+			"#version 300 es\n"
+			"uniform longlongUniformBlockName {highp vec2 vector2;} longlongUniformInstanceName;\n"
+			"layout(location = 0) out mediump vec4 fragColor;"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(longlongUniformInstanceName.vector2.y);\n"
+			"}\n\0";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		expectError(GL_NO_ERROR);
+
+		verifyShaderParam	(m_testCtx, *this, shaderVert,	GL_COMPILE_STATUS,	GL_TRUE);
+		verifyShaderParam	(m_testCtx, *this, shaderFrag,	GL_COMPILE_STATUS,	GL_TRUE);
+		verifyProgramParam	(m_testCtx, *this, program,		GL_LINK_STATUS,		GL_TRUE);
+
+		verifyProgramParam	(m_testCtx, *this, program,		GL_ACTIVE_UNIFORM_BLOCKS, 2);
+		verifyProgramParam	(m_testCtx, *this, program,		GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, (GLint)std::string("longlongUniformBlockName").length() + 1); // including a null terminator
+		expectError(GL_NO_ERROR);
+
+		GLint longlongUniformBlockIndex	= glGetUniformBlockIndex(program, "longlongUniformBlockName");
+		GLint shortUniformBlockIndex	= glGetUniformBlockIndex(program, "shortUniformBlockName");
+
+		const char* uniformNames[] =
+		{
+			"longlongUniformBlockName.vector2",
+			"shortUniformBlockName.vector2",
+			"shortUniformBlockName.vector4"
+		};
+
+		// test UNIFORM_BLOCK_INDEX
+
+		DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(uniformNames) == 3);
+
+		StateQueryMemoryWriteGuard<GLuint[DE_LENGTH_OF_ARRAY(uniformNames)]> uniformIndices;
+		StateQueryMemoryWriteGuard<GLint[DE_LENGTH_OF_ARRAY(uniformNames)]> uniformsBlockIndices;
+
+		glGetUniformIndices(program, DE_LENGTH_OF_ARRAY(uniformNames), uniformNames, uniformIndices);
+		uniformIndices.verifyValidity(m_testCtx);
+		expectError(GL_NO_ERROR);
+
+		glGetActiveUniformsiv(program, DE_LENGTH_OF_ARRAY(uniformNames), uniformIndices, GL_UNIFORM_BLOCK_INDEX, uniformsBlockIndices);
+		uniformsBlockIndices.verifyValidity(m_testCtx);
+		expectError(GL_NO_ERROR);
+
+		if (uniformsBlockIndices[0] != longlongUniformBlockIndex ||
+			uniformsBlockIndices[1] != shortUniformBlockIndex ||
+			uniformsBlockIndices[2] != shortUniformBlockIndex)
+		{
+			m_testCtx.getLog() << TestLog::Message
+				<< "// ERROR: Expected ["	<< longlongUniformBlockIndex	<< ", " << shortUniformBlockIndex	<< ", " << shortUniformBlockIndex	<< "];"
+				<<	"got ["					<< uniformsBlockIndices[0]		<< ", " << uniformsBlockIndices[1]	<< ", " << uniformsBlockIndices[2]	<< "]" << TestLog::EndMessage;
+			if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform block index");
+		}
+
+		// test UNIFORM_BLOCK_NAME_LENGTH
+
+		verifyActiveUniformBlockParam(m_testCtx, *this, program, longlongUniformBlockIndex,	GL_UNIFORM_BLOCK_NAME_LENGTH, (GLint)std::string("longlongUniformBlockName").length() + 1); // including null-terminator
+		verifyActiveUniformBlockParam(m_testCtx, *this, program, shortUniformBlockIndex,	GL_UNIFORM_BLOCK_NAME_LENGTH, (GLint)std::string("shortUniformBlockName").length() + 1); // including null-terminator
+		expectError(GL_NO_ERROR);
+
+		// test UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER & UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER
+
+		verifyActiveUniformBlockParam(m_testCtx, *this, program, longlongUniformBlockIndex,	GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER,	GL_TRUE);
+		verifyActiveUniformBlockParam(m_testCtx, *this, program, longlongUniformBlockIndex,	GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER,	GL_TRUE);
+		verifyActiveUniformBlockParam(m_testCtx, *this, program, shortUniformBlockIndex,	GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER,	GL_TRUE);
+		verifyActiveUniformBlockParam(m_testCtx, *this, program, shortUniformBlockIndex,	GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER,	GL_FALSE);
+		expectError(GL_NO_ERROR);
+
+		// test UNIFORM_BLOCK_ACTIVE_UNIFORMS
+
+		verifyActiveUniformBlockParam(m_testCtx, *this, program, longlongUniformBlockIndex,	GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS,	1);
+		verifyActiveUniformBlockParam(m_testCtx, *this, program, shortUniformBlockIndex,	GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS,	2);
+		expectError(GL_NO_ERROR);
+
+		// test UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES
+
+		{
+			StateQueryMemoryWriteGuard<GLint> longlongUniformBlockUniforms;
+			glGetActiveUniformBlockiv(program, longlongUniformBlockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &longlongUniformBlockUniforms);
+			longlongUniformBlockUniforms.verifyValidity(m_testCtx);
+
+			if (longlongUniformBlockUniforms == 2)
+			{
+				StateQueryMemoryWriteGuard<GLint[2]> longlongUniformBlockUniformIndices;
+				glGetActiveUniformBlockiv(program, longlongUniformBlockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, longlongUniformBlockUniformIndices);
+				longlongUniformBlockUniformIndices.verifyValidity(m_testCtx);
+
+				if ((GLuint(longlongUniformBlockUniformIndices[0]) != uniformIndices[0] || GLuint(longlongUniformBlockUniformIndices[1]) != uniformIndices[1]) &&
+					(GLuint(longlongUniformBlockUniformIndices[1]) != uniformIndices[0] || GLuint(longlongUniformBlockUniformIndices[0]) != uniformIndices[1]))
+				{
+					m_testCtx.getLog() << TestLog::Message
+						<< "// ERROR: Expected {"	<< uniformIndices[0]						<< ", " << uniformIndices[1] << "};"
+						<<	"got {"					<< longlongUniformBlockUniformIndices[0]	<< ", " << longlongUniformBlockUniformIndices[1] << "}" << TestLog::EndMessage;
+
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform indices");
+				}
+
+			}
+		}
+
+		// check block names
+
+		{
+			char buffer[2048] = {'x'};
+			GLint written = 0;
+			glGetActiveUniformBlockName(program, longlongUniformBlockIndex, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);
+			checkIntEquals(m_testCtx, written, (GLint)std::string("longlongUniformBlockName").length());
+
+			written = 0;
+			glGetActiveUniformBlockName(program, shortUniformBlockIndex, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);
+			checkIntEquals(m_testCtx, written, (GLint)std::string("shortUniformBlockName").length());
+
+			// and one with too small buffer
+			written = 0;
+			glGetActiveUniformBlockName(program, longlongUniformBlockIndex, 1, &written, buffer);
+			checkIntEquals(m_testCtx, written, 0);
+		}
+
+		expectError(GL_NO_ERROR);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ProgramBinaryCase : public ApiCase
+{
+public:
+	ProgramBinaryCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		expectError(GL_NO_ERROR);
+
+		// test PROGRAM_BINARY_RETRIEVABLE_HINT
+		verifyProgramParam(m_testCtx, *this, program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_FALSE);
+
+		glProgramParameteri(program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
+		expectError(GL_NO_ERROR);
+
+		glLinkProgram(program);
+		expectError(GL_NO_ERROR);
+
+		verifyProgramParam(m_testCtx, *this, program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
+
+		// test PROGRAM_BINARY_LENGTH does something
+
+		StateQueryMemoryWriteGuard<GLint> programLength;
+		glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &programLength);
+		expectError(GL_NO_ERROR);
+		programLength.verifyValidity(m_testCtx);
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class TransformFeedbackCase : public ApiCase
+{
+public:
+	TransformFeedbackCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		static const char* transformFeedbackTestVertSource =
+			"#version 300 es\n"
+			"out highp vec4 tfOutput2withLongName;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(0.0);\n"
+			"	tfOutput2withLongName = vec4(0.0);\n"
+			"}\n";
+		static const char* transformFeedbackTestFragSource =
+			"#version 300 es\n"
+			"layout(location = 0) out highp vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(0.0);\n"
+			"}\n";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+		GLuint shaderProg = glCreateProgram();
+
+		verifyProgramParam(m_testCtx, *this, shaderProg, GL_TRANSFORM_FEEDBACK_BUFFER_MODE, GL_INTERLEAVED_ATTRIBS);
+
+		glShaderSource(shaderVert, 1, &transformFeedbackTestVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &transformFeedbackTestFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+
+		verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
+		verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_TRUE);
+
+		glAttachShader(shaderProg, shaderVert);
+		glAttachShader(shaderProg, shaderFrag);
+
+		// check TRANSFORM_FEEDBACK_BUFFER_MODE
+
+		const char* transform_feedback_outputs[] = {"gl_Position", "tfOutput2withLongName"};
+		const char* longest_output = transform_feedback_outputs[1];
+		const GLenum bufferModes[] = {GL_SEPARATE_ATTRIBS, GL_INTERLEAVED_ATTRIBS};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(bufferModes); ++ndx)
+		{
+			glTransformFeedbackVaryings(shaderProg, DE_LENGTH_OF_ARRAY(transform_feedback_outputs), transform_feedback_outputs, bufferModes[ndx]);
+			glLinkProgram(shaderProg);
+			expectError(GL_NO_ERROR);
+
+			verifyProgramParam(m_testCtx, *this, shaderProg, GL_LINK_STATUS, GL_TRUE);
+			verifyProgramParam(m_testCtx, *this, shaderProg, GL_TRANSFORM_FEEDBACK_BUFFER_MODE, bufferModes[ndx]);
+		}
+
+		// TRANSFORM_FEEDBACK_VARYINGS
+		verifyProgramParam(m_testCtx, *this, shaderProg, GL_TRANSFORM_FEEDBACK_VARYINGS, 2);
+
+		// TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH
+		{
+			StateQueryMemoryWriteGuard<GLint> maxOutputLen;
+			glGetProgramiv(shaderProg, GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, &maxOutputLen);
+
+			maxOutputLen.verifyValidity(m_testCtx);
+
+			const GLint referenceLength = (GLint)std::string(longest_output).length() + 1;
+			checkIntEquals(m_testCtx, maxOutputLen, referenceLength);
+		}
+
+		// check varyings
+		{
+			StateQueryMemoryWriteGuard<GLint> varyings;
+			glGetProgramiv(shaderProg, GL_TRANSFORM_FEEDBACK_VARYINGS, &varyings);
+
+			if (!varyings.isUndefined())
+				for (int index = 0; index < varyings; ++index)
+				{
+					char buffer[2048] = {'x'};
+
+					GLint written = 0;
+					GLint size = 0;
+					GLenum type = 0;
+					glGetTransformFeedbackVarying(shaderProg, index, DE_LENGTH_OF_ARRAY(buffer), &written, &size, &type, buffer);
+
+					if (written < DE_LENGTH_OF_ARRAY(buffer) && buffer[written] != '\0')
+					{
+						m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator" << TestLog::EndMessage;
+						if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid string terminator");
+					}
+
+					// check with too small buffer
+					written = 0;
+					glGetTransformFeedbackVarying(shaderProg, index, 1, &written, &size, &type, buffer);
+					if (written != 0)
+					{
+						m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 0; got " << written << TestLog::EndMessage;
+						if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid write length");
+					}
+				}
+		}
+
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(shaderProg);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class ActiveAttributesCase : public ApiCase
+{
+public:
+	ActiveAttributesCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		static const char* testVertSource =
+			"#version 300 es\n"
+			"in highp vec2 longInputAttributeName;\n"
+			"in highp vec2 shortName;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = longInputAttributeName.yxxy + shortName.xyxy;\n"
+			"}\n\0";
+		static const char* testFragSource =
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(0.0);\n"
+			"}\n\0";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		expectError(GL_NO_ERROR);
+
+		verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_ATTRIBUTES, 2);
+		verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, (GLint)std::string("longInputAttributeName").length() + 1); // does include null-terminator
+
+		// check names
+		for (int attributeNdx = 0; attributeNdx < 2; ++attributeNdx)
+		{
+			char buffer[2048] = {'x'};
+
+			GLint written = 0;
+			GLint size = 0;
+			GLenum type = 0;
+			glGetActiveAttrib(program, attributeNdx, DE_LENGTH_OF_ARRAY(buffer), &written, &size, &type, buffer);
+			expectError(GL_NO_ERROR);
+
+			if (deStringBeginsWith(buffer, "longInputAttributeName"))
+			{
+				checkIntEquals(m_testCtx, written, (GLint)std::string("longInputAttributeName").length()); // does NOT include null-terminator
+			}
+			else if (deStringBeginsWith(buffer, "shortName"))
+			{
+				checkIntEquals(m_testCtx, written, (GLint)std::string("shortName").length()); // does NOT include null-terminator
+			}
+			else
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: Got unexpected attribute name." << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected name");
+			}
+		}
+
+		// and with too short buffer
+		{
+			char buffer[2048] = {'x'};
+
+			GLint written = 0;
+			GLint size = 0;
+			GLenum type = 0;
+
+			glGetActiveAttrib(program, 0, 1, &written, &size, &type, buffer);
+			expectError(GL_NO_ERROR);
+			checkIntEquals(m_testCtx, written, 0);
+		}
+
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+struct PointerData
+{
+	GLint		size;
+	GLenum		type;
+	GLint		stride;
+	GLboolean	normalized;
+	const void*	pointer;
+};
+
+class VertexAttributeSizeCase : public ApiCase
+{
+public:
+	VertexAttributeSizeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		GLfloat vertexData[4] = {0.0f}; // never accessed
+
+		const PointerData pointers[] =
+		{
+			// size test
+			{ 4, GL_FLOAT,		0,	GL_FALSE, vertexData },
+			{ 3, GL_FLOAT,		0,	GL_FALSE, vertexData },
+			{ 2, GL_FLOAT,		0,	GL_FALSE, vertexData },
+			{ 1, GL_FLOAT,		0,	GL_FALSE, vertexData },
+			{ 4, GL_INT,		0,	GL_FALSE, vertexData },
+			{ 3, GL_INT,		0,	GL_FALSE, vertexData },
+			{ 2, GL_INT,		0,	GL_FALSE, vertexData },
+			{ 1, GL_INT,		0,	GL_FALSE, vertexData },
+		};
+
+		// Test with default VAO
+		{
+			const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+			{
+				glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized, pointers[ndx].stride, pointers[ndx].pointer);
+				expectError(GL_NO_ERROR);
+
+				verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_SIZE, pointers[ndx].size);
+			}
+		}
+
+		// Test with multiple VAOs
+		{
+			const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");
+
+			GLuint buf		= 0;
+			GLuint vaos[2]	= {0};
+
+			glGenVertexArrays(2, vaos);
+			glGenBuffers(1, &buf);
+			glBindBuffer(GL_ARRAY_BUFFER, buf);
+			expectError(GL_NO_ERROR);
+
+			// initial
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_SIZE, 4);
+			expectError(GL_NO_ERROR);
+
+			// set vao 0 to some value
+			glVertexAttribPointer(0, pointers[0].size, pointers[0].type, pointers[0].normalized, pointers[0].stride, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// set vao 1 to some other value
+			glBindVertexArray(vaos[1]);
+			glVertexAttribPointer(0, pointers[1].size, pointers[1].type, pointers[1].normalized, pointers[1].stride, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 1 state
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_SIZE, pointers[1].size);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 0 state
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_SIZE, pointers[0].size);
+			expectError(GL_NO_ERROR);
+
+			glDeleteVertexArrays(2, vaos);
+			glDeleteBuffers(1, &buf);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class VertexAttributeTypeCase : public ApiCase
+{
+public:
+	VertexAttributeTypeCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// Test with default VAO
+		{
+			const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");
+
+			const GLfloat vertexData[4] = {0.0f}; // never accessed
+
+			// test VertexAttribPointer
+			{
+				const PointerData pointers[] =
+				{
+					{ 1, GL_BYTE,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_SHORT,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_INT,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_FIXED,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_FLOAT,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_HALF_FLOAT,							0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_BYTE,						0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_SHORT,						0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_INT,						0,	GL_FALSE, vertexData	},
+					{ 4, GL_INT_2_10_10_10_REV,					0,	GL_FALSE, vertexData	},
+					{ 4, GL_UNSIGNED_INT_2_10_10_10_REV,		0,	GL_FALSE, vertexData	},
+				};
+
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+				{
+					glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized, pointers[ndx].stride, pointers[ndx].pointer);
+					expectError(GL_NO_ERROR);
+
+					verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, pointers[ndx].type);
+				}
+			}
+
+			// test glVertexAttribIPointer
+			{
+				const PointerData pointers[] =
+				{
+					{ 1, GL_BYTE,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_SHORT,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_INT,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_BYTE,						0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_SHORT,						0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_INT,						0,	GL_FALSE, vertexData	},
+				};
+
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+				{
+					glVertexAttribIPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].stride, pointers[ndx].pointer);
+					expectError(GL_NO_ERROR);
+
+					verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, pointers[ndx].type);
+				}
+			}
+		}
+
+		// Test with multiple VAOs
+		{
+			const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");
+
+			GLuint buf		= 0;
+			GLuint vaos[2]	= {0};
+
+			glGenVertexArrays(2, vaos);
+			glGenBuffers(1, &buf);
+			glBindBuffer(GL_ARRAY_BUFFER, buf);
+			expectError(GL_NO_ERROR);
+
+			// initial
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, GL_FLOAT);
+			expectError(GL_NO_ERROR);
+
+			// set vao 0 to some value
+			glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// set vao 1 to some other value
+			glBindVertexArray(vaos[1]);
+			glVertexAttribPointer(0, 1, GL_SHORT, GL_FALSE, 0, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 1 state
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, GL_SHORT);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 0 state
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, GL_FLOAT);
+			expectError(GL_NO_ERROR);
+
+			glDeleteVertexArrays(2, vaos);
+			glDeleteBuffers(1, &buf);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class VertexAttributeStrideCase : public ApiCase
+{
+public:
+	VertexAttributeStrideCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// Test with default VAO
+		{
+			const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");
+
+			const GLfloat vertexData[4] = {0.0f}; // never accessed
+
+			struct StridePointerData
+			{
+				GLint		size;
+				GLenum		type;
+				GLint		stride;
+				const void*	pointer;
+			};
+
+			// test VertexAttribPointer
+			{
+				const StridePointerData pointers[] =
+				{
+					{ 1, GL_FLOAT,			0,	vertexData },
+					{ 1, GL_FLOAT,			1,	vertexData },
+					{ 1, GL_FLOAT,			4,	vertexData },
+					{ 1, GL_HALF_FLOAT,		0,	vertexData },
+					{ 1, GL_HALF_FLOAT,		1,	vertexData },
+					{ 1, GL_HALF_FLOAT,		4,	vertexData },
+					{ 1, GL_FIXED,			0,	vertexData },
+					{ 1, GL_FIXED,			1,	vertexData },
+					{ 1, GL_FIXED,			4,	vertexData },
+				};
+
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+				{
+					glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, GL_FALSE, pointers[ndx].stride, pointers[ndx].pointer);
+					expectError(GL_NO_ERROR);
+
+					verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, pointers[ndx].stride);
+				}
+			}
+
+			// test glVertexAttribIPointer
+			{
+				const StridePointerData pointers[] =
+				{
+					{ 1, GL_INT,				0,	vertexData },
+					{ 1, GL_INT,				1,	vertexData },
+					{ 1, GL_INT,				4,	vertexData },
+					{ 4, GL_UNSIGNED_BYTE,		0,	vertexData },
+					{ 4, GL_UNSIGNED_BYTE,		1,	vertexData },
+					{ 4, GL_UNSIGNED_BYTE,		4,	vertexData },
+					{ 2, GL_SHORT,				0,	vertexData },
+					{ 2, GL_SHORT,				1,	vertexData },
+					{ 2, GL_SHORT,				4,	vertexData },
+				};
+
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+				{
+					glVertexAttribIPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].stride, pointers[ndx].pointer);
+					expectError(GL_NO_ERROR);
+
+					verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, pointers[ndx].stride);
+				}
+			}
+		}
+
+		// Test with multiple VAOs
+		{
+			const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");
+
+			GLuint buf		= 0;
+			GLuint vaos[2]	= {0};
+
+			glGenVertexArrays(2, vaos);
+			glGenBuffers(1, &buf);
+			glBindBuffer(GL_ARRAY_BUFFER, buf);
+			expectError(GL_NO_ERROR);
+
+			// initial
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, 0);
+			expectError(GL_NO_ERROR);
+
+			// set vao 0 to some value
+			glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 4, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// set vao 1 to some other value
+			glBindVertexArray(vaos[1]);
+			glVertexAttribPointer(0, 1, GL_SHORT, GL_FALSE, 8, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 1 state
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, 8);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 0 state
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, 4);
+			expectError(GL_NO_ERROR);
+
+			glDeleteVertexArrays(2, vaos);
+			glDeleteBuffers(1, &buf);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class VertexAttributeNormalizedCase : public ApiCase
+{
+public:
+	VertexAttributeNormalizedCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// Test with default VAO
+		{
+			const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");
+
+			const GLfloat vertexData[4] = {0.0f}; // never accessed
+
+			// test VertexAttribPointer
+			{
+				const PointerData pointers[] =
+				{
+					{ 1, GL_BYTE,								0,	GL_FALSE,	vertexData	},
+					{ 1, GL_SHORT,								0,	GL_FALSE,	vertexData	},
+					{ 1, GL_INT,								0,	GL_FALSE,	vertexData	},
+					{ 1, GL_UNSIGNED_BYTE,						0,	GL_FALSE,	vertexData	},
+					{ 1, GL_UNSIGNED_SHORT,						0,	GL_FALSE,	vertexData	},
+					{ 1, GL_UNSIGNED_INT,						0,	GL_FALSE,	vertexData	},
+					{ 4, GL_INT_2_10_10_10_REV,					0,	GL_FALSE,	vertexData	},
+					{ 4, GL_UNSIGNED_INT_2_10_10_10_REV,		0,	GL_FALSE,	vertexData	},
+					{ 1, GL_BYTE,								0,	GL_TRUE,	vertexData	},
+					{ 1, GL_SHORT,								0,	GL_TRUE,	vertexData	},
+					{ 1, GL_INT,								0,	GL_TRUE,	vertexData	},
+					{ 1, GL_UNSIGNED_BYTE,						0,	GL_TRUE,	vertexData	},
+					{ 1, GL_UNSIGNED_SHORT,						0,	GL_TRUE,	vertexData	},
+					{ 1, GL_UNSIGNED_INT,						0,	GL_TRUE,	vertexData	},
+					{ 4, GL_INT_2_10_10_10_REV,					0,	GL_TRUE,	vertexData	},
+					{ 4, GL_UNSIGNED_INT_2_10_10_10_REV,		0,	GL_TRUE,	vertexData	},
+				};
+
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+				{
+					glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized, pointers[ndx].stride, pointers[ndx].pointer);
+					expectError(GL_NO_ERROR);
+
+					verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, pointers[ndx].normalized);
+				}
+			}
+
+			// test glVertexAttribIPointer
+			{
+				const PointerData pointers[] =
+				{
+					{ 1, GL_BYTE,				0,	GL_FALSE, vertexData	},
+					{ 1, GL_SHORT,				0,	GL_FALSE, vertexData	},
+					{ 1, GL_INT,				0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_BYTE,		0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_SHORT,		0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_INT,		0,	GL_FALSE, vertexData	},
+				};
+
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+				{
+					glVertexAttribIPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].stride, pointers[ndx].pointer);
+					expectError(GL_NO_ERROR);
+
+					verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, GL_FALSE);
+				}
+			}
+		}
+
+		// Test with multiple VAOs
+		{
+			const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");
+
+			GLuint buf		= 0;
+			GLuint vaos[2]	= {0};
+
+			glGenVertexArrays(2, vaos);
+			glGenBuffers(1, &buf);
+			glBindBuffer(GL_ARRAY_BUFFER, buf);
+			expectError(GL_NO_ERROR);
+
+			// initial
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, GL_FALSE);
+			expectError(GL_NO_ERROR);
+
+			// set vao 0 to some value
+			glVertexAttribPointer(0, 1, GL_INT, GL_TRUE, 0, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// set vao 1 to some other value
+			glBindVertexArray(vaos[1]);
+			glVertexAttribPointer(0, 1, GL_INT, GL_FALSE, 0, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 1 state
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, GL_FALSE);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 0 state
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, GL_TRUE);
+			expectError(GL_NO_ERROR);
+
+			glDeleteVertexArrays(2, vaos);
+			glDeleteBuffers(1, &buf);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class VertexAttributeIntegerCase : public ApiCase
+{
+public:
+	VertexAttributeIntegerCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// Test with default VAO
+		{
+			const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");
+
+			const GLfloat vertexData[4] = {0.0f}; // never accessed
+
+			// test VertexAttribPointer
+			{
+				const PointerData pointers[] =
+				{
+					{ 1, GL_BYTE,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_SHORT,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_INT,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_FIXED,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_FLOAT,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_HALF_FLOAT,							0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_BYTE,						0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_SHORT,						0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_INT,						0,	GL_FALSE, vertexData	},
+					{ 4, GL_INT_2_10_10_10_REV,					0,	GL_FALSE, vertexData	},
+					{ 4, GL_UNSIGNED_INT_2_10_10_10_REV,		0,	GL_FALSE, vertexData	},
+				};
+
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+				{
+					glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized, pointers[ndx].stride, pointers[ndx].pointer);
+					expectError(GL_NO_ERROR);
+
+					verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_INTEGER, GL_FALSE);
+				}
+			}
+
+			// test glVertexAttribIPointer
+			{
+				const PointerData pointers[] =
+				{
+					{ 1, GL_BYTE,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_SHORT,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_INT,								0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_BYTE,						0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_SHORT,						0,	GL_FALSE, vertexData	},
+					{ 1, GL_UNSIGNED_INT,						0,	GL_FALSE, vertexData	},
+				};
+
+				for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+				{
+					glVertexAttribIPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].stride, pointers[ndx].pointer);
+					expectError(GL_NO_ERROR);
+
+					verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_INTEGER, GL_TRUE);
+				}
+			}
+		}
+
+		// Test with multiple VAOs
+		{
+			const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");
+
+			GLuint buf		= 0;
+			GLuint vaos[2]	= {0};
+
+			glGenVertexArrays(2, vaos);
+			glGenBuffers(1, &buf);
+			glBindBuffer(GL_ARRAY_BUFFER, buf);
+			expectError(GL_NO_ERROR);
+
+			// initial
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_INTEGER, GL_FALSE);
+			expectError(GL_NO_ERROR);
+
+			// set vao 0 to some value
+			glVertexAttribIPointer(0, 1, GL_INT, 0, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// set vao 1 to some other value
+			glBindVertexArray(vaos[1]);
+			glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 1 state
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_INTEGER, GL_FALSE);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 0 state
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_INTEGER, GL_TRUE);
+			expectError(GL_NO_ERROR);
+
+			glDeleteVertexArrays(2, vaos);
+			glDeleteBuffers(1, &buf);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class VertexAttributeEnabledCase : public ApiCase
+{
+public:
+	VertexAttributeEnabledCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// VERTEX_ATTRIB_ARRAY_ENABLED
+
+		// Test with default VAO
+		{
+			const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");
+
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_FALSE);
+			glEnableVertexAttribArray(0);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_TRUE);
+			glDisableVertexAttribArray(0);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_FALSE);
+		}
+
+		// Test with multiple VAOs
+		{
+			const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");
+
+			GLuint vaos[2] = {0};
+
+			glGenVertexArrays(2, vaos);
+			expectError(GL_NO_ERROR);
+
+			// set vao 0 to some value
+			glBindVertexArray(vaos[0]);
+			glEnableVertexAttribArray(0);
+			expectError(GL_NO_ERROR);
+
+			// set vao 1 to some other value
+			glBindVertexArray(vaos[1]);
+			glDisableVertexAttribArray(0);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 1 state
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_FALSE);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 0 state
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_TRUE);
+			expectError(GL_NO_ERROR);
+
+			glDeleteVertexArrays(2, vaos);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class VertexAttributeDivisorCase : public ApiCase
+{
+public:
+	VertexAttributeDivisorCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// Test with default VAO
+		{
+			const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");
+
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_DIVISOR,	0);
+			glVertexAttribDivisor(0, 1);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_DIVISOR,	1);
+			glVertexAttribDivisor(0, 5);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_DIVISOR,	5);
+		}
+
+		// Test with multiple VAOs
+		{
+			const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");
+
+			GLuint vaos[2] = {0};
+
+			glGenVertexArrays(2, vaos);
+			expectError(GL_NO_ERROR);
+
+			// set vao 0 to some value
+			glBindVertexArray(vaos[0]);
+			glVertexAttribDivisor(0, 1);
+			expectError(GL_NO_ERROR);
+
+			// set vao 1 to some other value
+			glBindVertexArray(vaos[1]);
+			glVertexAttribDivisor(0, 5);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 1 state
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_DIVISOR, 5);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 0 state
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_DIVISOR, 1);
+			expectError(GL_NO_ERROR);
+
+			glDeleteVertexArrays(2, vaos);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class VertexAttributeBufferBindingCase : public ApiCase
+{
+public:
+	VertexAttributeBufferBindingCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// Test with default VAO
+		{
+			const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");
+
+			// initial
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, 0);
+
+			GLuint bufferID;
+			glGenBuffers(1, &bufferID);
+			glBindBuffer(GL_ARRAY_BUFFER, bufferID);
+			expectError(GL_NO_ERROR);
+
+			glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, bufferID);
+
+			glDeleteBuffers(1, &bufferID);
+			expectError(GL_NO_ERROR);
+		}
+
+		// Test with multiple VAOs
+		{
+			const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");
+
+			GLuint vaos[2] = {0};
+			GLuint bufs[2] = {0};
+
+			glGenBuffers(2, bufs);
+			expectError(GL_NO_ERROR);
+
+			glGenVertexArrays(2, vaos);
+			expectError(GL_NO_ERROR);
+
+			// set vao 0 to some value
+			glBindVertexArray(vaos[0]);
+			glBindBuffer(GL_ARRAY_BUFFER, bufs[0]);
+			glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// set vao 1 to some other value
+			glBindVertexArray(vaos[1]);
+			glBindBuffer(GL_ARRAY_BUFFER, bufs[1]);
+			glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 1 state
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, bufs[1]);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 0 state
+			glBindVertexArray(vaos[0]);
+			verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, bufs[0]);
+			expectError(GL_NO_ERROR);
+
+			glDeleteVertexArrays(2, vaos);
+			glDeleteBuffers(2, bufs);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class VertexAttributePointerCase : public ApiCase
+{
+public:
+	VertexAttributePointerCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		// Test with default VAO
+		{
+			const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");
+
+			StateQueryMemoryWriteGuard<GLvoid*> initialState;
+			glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &initialState);
+			initialState.verifyValidity(m_testCtx);
+			checkPointerEquals(m_testCtx, initialState, 0);
+
+			const GLfloat vertexData[4] = {0.0f}; // never accessed
+			const PointerData pointers[] =
+			{
+				{ 1, GL_BYTE,				0,	GL_FALSE, &vertexData[2] },
+				{ 1, GL_SHORT,				0,	GL_FALSE, &vertexData[1] },
+				{ 1, GL_INT,				0,	GL_FALSE, &vertexData[2] },
+				{ 1, GL_FIXED,				0,	GL_FALSE, &vertexData[2] },
+				{ 1, GL_FIXED,				0,	GL_FALSE, &vertexData[1] },
+				{ 1, GL_FLOAT,				0,	GL_FALSE, &vertexData[0] },
+				{ 1, GL_FLOAT,				0,	GL_FALSE, &vertexData[3] },
+				{ 1, GL_FLOAT,				0,	GL_FALSE, &vertexData[2] },
+				{ 1, GL_HALF_FLOAT,			0,	GL_FALSE, &vertexData[0] },
+				{ 4, GL_HALF_FLOAT,			0,	GL_FALSE, &vertexData[1] },
+				{ 4, GL_HALF_FLOAT,			0,	GL_FALSE, &vertexData[2] },
+			};
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
+			{
+				glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized, pointers[ndx].stride, pointers[ndx].pointer);
+				expectError(GL_NO_ERROR);
+
+				StateQueryMemoryWriteGuard<GLvoid*> state;
+				glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &state);
+				state.verifyValidity(m_testCtx);
+				checkPointerEquals(m_testCtx, state, pointers[ndx].pointer);
+			}
+		}
+
+		// Test with multiple VAOs
+		{
+			const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");
+
+			GLuint vaos[2] = {0};
+			GLuint bufs[2] = {0};
+
+			glGenBuffers(2, bufs);
+			expectError(GL_NO_ERROR);
+
+			glGenVertexArrays(2, vaos);
+			expectError(GL_NO_ERROR);
+
+			// set vao 0 to some value
+			glBindVertexArray(vaos[0]);
+			glBindBuffer(GL_ARRAY_BUFFER, bufs[0]);
+			glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, ((deUint8*)DE_NULL) + 8);
+			expectError(GL_NO_ERROR);
+
+			// set vao 1 to some other value
+			glBindVertexArray(vaos[1]);
+			glBindBuffer(GL_ARRAY_BUFFER, bufs[1]);
+			glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, ((deUint8*)DE_NULL) + 4);
+			expectError(GL_NO_ERROR);
+
+			// verify vao 1 state
+			{
+				StateQueryMemoryWriteGuard<GLvoid*> state;
+				glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &state);
+				state.verifyValidity(m_testCtx);
+				checkPointerEquals(m_testCtx, state, ((deUint8*)DE_NULL) + 4);
+			}
+			expectError(GL_NO_ERROR);
+
+			// verify vao 0 state
+			glBindVertexArray(vaos[0]);
+			{
+				StateQueryMemoryWriteGuard<GLvoid*> state;
+				glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &state);
+				state.verifyValidity(m_testCtx);
+				checkPointerEquals(m_testCtx, state, ((deUint8*)DE_NULL) + 8);
+			}
+			expectError(GL_NO_ERROR);
+
+			glDeleteVertexArrays(2, vaos);
+			glDeleteBuffers(2, bufs);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class UniformValueFloatCase : public ApiCase
+{
+public:
+	UniformValueFloatCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"#version 300 es\n"
+			"uniform highp float floatUniform;\n"
+			"uniform highp vec2 float2Uniform;\n"
+			"uniform highp vec3 float3Uniform;\n"
+			"uniform highp vec4 float4Uniform;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(floatUniform + float2Uniform.x + float3Uniform.x + float4Uniform.x);\n"
+			"}\n";
+		static const char* testFragSource =
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(0.0);\n"
+			"}\n";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		glUseProgram(program);
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		location = glGetUniformLocation(program,"floatUniform");
+		glUniform1f(location, 1.0f);
+		verifyUniformValue1f(m_testCtx, *this, program, location, 1.0f);
+
+		location = glGetUniformLocation(program,"float2Uniform");
+		glUniform2f(location, 1.0f, 2.0f);
+		verifyUniformValue2f(m_testCtx, *this, program, location, 1.0f, 2.0f);
+
+		location = glGetUniformLocation(program,"float3Uniform");
+		glUniform3f(location, 1.0f, 2.0f, 3.0f);
+		verifyUniformValue3f(m_testCtx, *this, program, location, 1.0f, 2.0f, 3.0f);
+
+		location = glGetUniformLocation(program,"float4Uniform");
+		glUniform4f(location, 1.0f, 2.0f, 3.0f, 4.0f);
+		verifyUniformValue4f(m_testCtx, *this, program, location, 1.0f, 2.0f, 3.0f, 4.0f);
+
+		glUseProgram(0);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UniformValueIntCase : public ApiCase
+{
+public:
+	UniformValueIntCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"#version 300 es\n"
+			"uniform highp int intUniform;\n"
+			"uniform highp ivec2 int2Uniform;\n"
+			"uniform highp ivec3 int3Uniform;\n"
+			"uniform highp ivec4 int4Uniform;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(float(intUniform + int2Uniform.x + int3Uniform.x + int4Uniform.x));\n"
+			"}\n";
+		static const char* testFragSource =
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(0.0);\n"
+			"}\n";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		glUseProgram(program);
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		location = glGetUniformLocation(program,"intUniform");
+		glUniform1i(location, 1);
+		verifyUniformValue1i(m_testCtx, *this, program, location, 1);
+
+		location = glGetUniformLocation(program,"int2Uniform");
+		glUniform2i(location, 1, 2);
+		verifyUniformValue2i(m_testCtx, *this, program, location, 1, 2);
+
+		location = glGetUniformLocation(program,"int3Uniform");
+		glUniform3i(location, 1, 2, 3);
+		verifyUniformValue3i(m_testCtx, *this, program, location, 1, 2, 3);
+
+		location = glGetUniformLocation(program,"int4Uniform");
+		glUniform4i(location, 1, 2, 3, 4);
+		verifyUniformValue4i(m_testCtx, *this, program, location, 1, 2, 3, 4);
+
+		glUseProgram(0);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UniformValueUintCase : public ApiCase
+{
+public:
+	UniformValueUintCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"#version 300 es\n"
+			"uniform highp uint uintUniform;\n"
+			"uniform highp uvec2 uint2Uniform;\n"
+			"uniform highp uvec3 uint3Uniform;\n"
+			"uniform highp uvec4 uint4Uniform;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(float(uintUniform + uint2Uniform.x + uint3Uniform.x + uint4Uniform.x));\n"
+			"}\n";
+		static const char* testFragSource =
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(0.0);\n"
+			"}\n";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		glUseProgram(program);
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		location = glGetUniformLocation(program,"uintUniform");
+		glUniform1ui(location, 1);
+		verifyUniformValue1ui(m_testCtx, *this, program, location, 1);
+
+		location = glGetUniformLocation(program,"uint2Uniform");
+		glUniform2ui(location, 1, 2);
+		verifyUniformValue2ui(m_testCtx, *this, program, location, 1, 2);
+
+		location = glGetUniformLocation(program,"uint3Uniform");
+		glUniform3ui(location, 1, 2, 3);
+		verifyUniformValue3ui(m_testCtx, *this, program, location, 1, 2, 3);
+
+		location = glGetUniformLocation(program,"uint4Uniform");
+		glUniform4ui(location, 1, 2, 3, 4);
+		verifyUniformValue4ui(m_testCtx, *this, program, location, 1, 2, 3, 4);
+
+		glUseProgram(0);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+
+class UniformValueBooleanCase : public ApiCase
+{
+public:
+	UniformValueBooleanCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"#version 300 es\n"
+			"uniform bool boolUniform;\n"
+			"uniform bvec2 bool2Uniform;\n"
+			"uniform bvec3 bool3Uniform;\n"
+			"uniform bvec4 bool4Uniform;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(float(boolUniform) + float(bool2Uniform.x) + float(bool3Uniform.x) + float(bool4Uniform.x));\n"
+			"}\n";
+		static const char* testFragSource =
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(0.0);\n"
+			"}\n";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		glUseProgram(program);
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		// int conversion
+
+		location = glGetUniformLocation(program,"boolUniform");
+		glUniform1i(location, 1);
+		verifyUniformValue1i(m_testCtx, *this, program, location, 1);
+
+		location = glGetUniformLocation(program,"bool2Uniform");
+		glUniform2i(location, 1, 2);
+		verifyUniformValue2i(m_testCtx, *this, program, location, 1, 1);
+
+		location = glGetUniformLocation(program,"bool3Uniform");
+		glUniform3i(location, 0, 1, 2);
+		verifyUniformValue3i(m_testCtx, *this, program, location, 0, 1, 1);
+
+		location = glGetUniformLocation(program,"bool4Uniform");
+		glUniform4i(location, 1, 0, 1, -1);
+		verifyUniformValue4i(m_testCtx, *this, program, location, 1, 0, 1, 1);
+
+		// float conversion
+
+		location = glGetUniformLocation(program,"boolUniform");
+		glUniform1f(location, 1.0f);
+		verifyUniformValue1i(m_testCtx, *this, program, location, 1);
+
+		location = glGetUniformLocation(program,"bool2Uniform");
+		glUniform2f(location, 1.0f, 0.1f);
+		verifyUniformValue2i(m_testCtx, *this, program, location, 1, 1);
+
+		location = glGetUniformLocation(program,"bool3Uniform");
+		glUniform3f(location, 0.0f, 0.1f, -0.1f);
+		verifyUniformValue3i(m_testCtx, *this, program, location, 0, 1, 1);
+
+		location = glGetUniformLocation(program,"bool4Uniform");
+		glUniform4f(location, 1.0f, 0.0f, 0.1f, -0.9f);
+		verifyUniformValue4i(m_testCtx, *this, program, location, 1, 0, 1, 1);
+
+		glUseProgram(0);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UniformValueSamplerCase : public ApiCase
+{
+public:
+	UniformValueSamplerCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"#version 300 es\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(0.0);\n"
+			"}\n";
+		static const char* testFragSource =
+			"#version 300 es\n"
+			"uniform highp sampler2D uniformSampler;\n"
+			"layout(location = 0) out mediump vec4 fragColor;"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(textureSize(uniformSampler, 0).x);\n"
+			"}\n";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		glUseProgram(program);
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		location = glGetUniformLocation(program,"uniformSampler");
+		glUniform1i(location, 1);
+		verifyUniformValue1i(m_testCtx, *this, program, location, 1);
+
+		glUseProgram(0);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UniformValueArrayCase : public ApiCase
+{
+public:
+	UniformValueArrayCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"#version 300 es\n"
+			"uniform highp float arrayUniform[5];"
+			"uniform highp vec2 array2Uniform[5];"
+			"uniform highp vec3 array3Uniform[5];"
+			"uniform highp vec4 array4Uniform[5];"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = \n"
+			"		+ vec4(arrayUniform[0]		+ arrayUniform[1]		+ arrayUniform[2]		+ arrayUniform[3]		+ arrayUniform[4])\n"
+			"		+ vec4(array2Uniform[0].x	+ array2Uniform[1].x	+ array2Uniform[2].x	+ array2Uniform[3].x	+ array2Uniform[4].x)\n"
+			"		+ vec4(array3Uniform[0].x	+ array3Uniform[1].x	+ array3Uniform[2].x	+ array3Uniform[3].x	+ array3Uniform[4].x)\n"
+			"		+ vec4(array4Uniform[0].x	+ array4Uniform[1].x	+ array4Uniform[2].x	+ array4Uniform[3].x	+ array4Uniform[4].x);\n"
+			"}\n";
+		static const char* testFragSource =
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(0.0);\n"
+			"}\n";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		glUseProgram(program);
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		float uniformValue[5 * 4] =
+		{
+			-1.0f,	0.1f,	4.0f,	800.0f,
+			13.0f,	55.0f,	12.0f,	91.0f,
+			-55.1f,	1.1f,	98.0f,	19.0f,
+			41.0f,	65.0f,	4.0f,	12.2f,
+			95.0f,	77.0f,	32.0f,	48.0f
+		};
+
+		location = glGetUniformLocation(program,"arrayUniform");
+		glUniform1fv(location, 5, uniformValue);
+		expectError(GL_NO_ERROR);
+
+		verifyUniformValue1f(m_testCtx, *this, program, glGetUniformLocation(program,"arrayUniform[0]"), uniformValue[0]);
+		verifyUniformValue1f(m_testCtx, *this, program, glGetUniformLocation(program,"arrayUniform[1]"), uniformValue[1]);
+		verifyUniformValue1f(m_testCtx, *this, program, glGetUniformLocation(program,"arrayUniform[2]"), uniformValue[2]);
+		verifyUniformValue1f(m_testCtx, *this, program, glGetUniformLocation(program,"arrayUniform[3]"), uniformValue[3]);
+		verifyUniformValue1f(m_testCtx, *this, program, glGetUniformLocation(program,"arrayUniform[4]"), uniformValue[4]);
+		expectError(GL_NO_ERROR);
+
+		location = glGetUniformLocation(program,"array2Uniform");
+		glUniform2fv(location, 5, uniformValue);
+		expectError(GL_NO_ERROR);
+
+		verifyUniformValue2f(m_testCtx, *this, program, glGetUniformLocation(program,"array2Uniform[0]"), uniformValue[2 * 0], uniformValue[(2 * 0) + 1]);
+		verifyUniformValue2f(m_testCtx, *this, program, glGetUniformLocation(program,"array2Uniform[1]"), uniformValue[2 * 1], uniformValue[(2 * 1) + 1]);
+		verifyUniformValue2f(m_testCtx, *this, program, glGetUniformLocation(program,"array2Uniform[2]"), uniformValue[2 * 2], uniformValue[(2 * 2) + 1]);
+		verifyUniformValue2f(m_testCtx, *this, program, glGetUniformLocation(program,"array2Uniform[3]"), uniformValue[2 * 3], uniformValue[(2 * 3) + 1]);
+		verifyUniformValue2f(m_testCtx, *this, program, glGetUniformLocation(program,"array2Uniform[4]"), uniformValue[2 * 4], uniformValue[(2 * 4) + 1]);
+		expectError(GL_NO_ERROR);
+
+		location = glGetUniformLocation(program,"array3Uniform");
+		glUniform3fv(location, 5, uniformValue);
+		expectError(GL_NO_ERROR);
+
+		verifyUniformValue3f(m_testCtx, *this, program, glGetUniformLocation(program,"array3Uniform[0]"), uniformValue[3 * 0], uniformValue[(3 * 0) + 1], uniformValue[(3 * 0) + 2]);
+		verifyUniformValue3f(m_testCtx, *this, program, glGetUniformLocation(program,"array3Uniform[1]"), uniformValue[3 * 1], uniformValue[(3 * 1) + 1], uniformValue[(3 * 1) + 2]);
+		verifyUniformValue3f(m_testCtx, *this, program, glGetUniformLocation(program,"array3Uniform[2]"), uniformValue[3 * 2], uniformValue[(3 * 2) + 1], uniformValue[(3 * 2) + 2]);
+		verifyUniformValue3f(m_testCtx, *this, program, glGetUniformLocation(program,"array3Uniform[3]"), uniformValue[3 * 3], uniformValue[(3 * 3) + 1], uniformValue[(3 * 3) + 2]);
+		verifyUniformValue3f(m_testCtx, *this, program, glGetUniformLocation(program,"array3Uniform[4]"), uniformValue[3 * 4], uniformValue[(3 * 4) + 1], uniformValue[(3 * 4) + 2]);
+		expectError(GL_NO_ERROR);
+
+		location = glGetUniformLocation(program,"array4Uniform");
+		glUniform4fv(location, 5, uniformValue);
+		expectError(GL_NO_ERROR);
+
+		verifyUniformValue4f(m_testCtx, *this, program, glGetUniformLocation(program,"array4Uniform[0]"), uniformValue[4 * 0], uniformValue[(4 * 0) + 1], uniformValue[(4 * 0) + 2], uniformValue[(4 * 0) + 3]);
+		verifyUniformValue4f(m_testCtx, *this, program, glGetUniformLocation(program,"array4Uniform[1]"), uniformValue[4 * 1], uniformValue[(4 * 1) + 1], uniformValue[(4 * 1) + 2], uniformValue[(4 * 1) + 3]);
+		verifyUniformValue4f(m_testCtx, *this, program, glGetUniformLocation(program,"array4Uniform[2]"), uniformValue[4 * 2], uniformValue[(4 * 2) + 1], uniformValue[(4 * 2) + 2], uniformValue[(4 * 2) + 3]);
+		verifyUniformValue4f(m_testCtx, *this, program, glGetUniformLocation(program,"array4Uniform[3]"), uniformValue[4 * 3], uniformValue[(4 * 3) + 1], uniformValue[(4 * 3) + 2], uniformValue[(4 * 3) + 3]);
+		verifyUniformValue4f(m_testCtx, *this, program, glGetUniformLocation(program,"array4Uniform[4]"), uniformValue[4 * 4], uniformValue[(4 * 4) + 1], uniformValue[(4 * 4) + 2], uniformValue[(4 * 4) + 3]);
+		expectError(GL_NO_ERROR);
+
+		glUseProgram(0);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class UniformValueMatrixCase : public ApiCase
+{
+public:
+	UniformValueMatrixCase (Context& context, const char* name, const char* description)
+		: ApiCase(context, name, description)
+	{
+	}
+
+	void test (void)
+	{
+		static const char* testVertSource =
+			"#version 300 es\n"
+			"uniform highp mat2 mat2Uniform;"
+			"uniform highp mat3 mat3Uniform;"
+			"uniform highp mat4 mat4Uniform;"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = vec4(mat2Uniform[0][0] + mat3Uniform[0][0] + mat4Uniform[0][0]);\n"
+			"}\n";
+		static const char* testFragSource =
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(0.0);\n"
+			"}\n";
+
+		GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
+		GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
+
+		glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
+		glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);
+
+		glCompileShader(shaderVert);
+		glCompileShader(shaderFrag);
+		expectError(GL_NO_ERROR);
+
+		GLuint program = glCreateProgram();
+		glAttachShader(program, shaderVert);
+		glAttachShader(program, shaderFrag);
+		glLinkProgram(program);
+		glUseProgram(program);
+		expectError(GL_NO_ERROR);
+
+		GLint location;
+
+		float matrixValues[4 * 4] =
+		{
+			-1.0f,	0.1f,	4.0f,	800.0f,
+			13.0f,	55.0f,	12.0f,	91.0f,
+			-55.1f,	1.1f,	98.0f,	19.0f,
+			41.0f,	65.0f,	4.0f,	12.2f,
+		};
+
+		// the values of the matrix are returned in column major order but they can be given in either order
+
+		location = glGetUniformLocation(program,"mat2Uniform");
+		glUniformMatrix2fv(location, 1, GL_FALSE, matrixValues);
+		verifyUniformMatrixValues<2>(m_testCtx, *this, program, location, matrixValues, false);
+		glUniformMatrix2fv(location, 1, GL_TRUE, matrixValues);
+		verifyUniformMatrixValues<2>(m_testCtx, *this, program, location, matrixValues, true);
+
+		location = glGetUniformLocation(program,"mat3Uniform");
+		glUniformMatrix3fv(location, 1, GL_FALSE, matrixValues);
+		verifyUniformMatrixValues<3>(m_testCtx, *this, program, location, matrixValues, false);
+		glUniformMatrix3fv(location, 1, GL_TRUE, matrixValues);
+		verifyUniformMatrixValues<3>(m_testCtx, *this, program, location, matrixValues, true);
+
+		location = glGetUniformLocation(program,"mat4Uniform");
+		glUniformMatrix4fv(location, 1, GL_FALSE, matrixValues);
+		verifyUniformMatrixValues<4>(m_testCtx, *this, program, location, matrixValues, false);
+		glUniformMatrix4fv(location, 1, GL_TRUE, matrixValues);
+		verifyUniformMatrixValues<4>(m_testCtx, *this, program, location, matrixValues, true);
+
+		glUseProgram(0);
+		glDeleteShader(shaderVert);
+		glDeleteShader(shaderFrag);
+		glDeleteProgram(program);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+class PrecisionFormatCase : public ApiCase
+{
+public:
+	struct RequiredFormat
+	{
+		int negativeRange;
+		int positiveRange;
+		int precision;
+	};
+
+	PrecisionFormatCase (Context& context, const char* name, const char* description, glw::GLenum shaderType, glw::GLenum precisionType)
+		: ApiCase			(context, name, description)
+		, m_shaderType		(shaderType)
+		, m_precisionType	(precisionType)
+	{
+	}
+
+private:
+	void test (void)
+	{
+		const RequiredFormat											expected = getRequiredFormat();
+		bool															error = false;
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLboolean>	shaderCompiler;
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint[2]>	range;
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint>		precision;
+
+		// query values
+		glGetShaderPrecisionFormat(m_shaderType, m_precisionType, range, &precision);
+		expectError(GL_NO_ERROR);
+
+		if (!range.verifyValidity(m_testCtx))
+			return;
+		if (!precision.verifyValidity(m_testCtx))
+			return;
+
+		m_log
+			<< tcu::TestLog::Message
+			<< "range[0] = " << range[0] << "\n"
+			<< "range[1] = " << range[1] << "\n"
+			<< "precision = " << precision
+			<< tcu::TestLog::EndMessage;
+
+		// verify values
+
+		if (m_precisionType == GL_HIGH_FLOAT)
+		{
+			// highp float must be IEEE 754 single
+
+			if (range[0] != expected.negativeRange ||
+				range[1] != expected.positiveRange ||
+				precision != expected.precision)
+			{
+				m_log
+					<< tcu::TestLog::Message
+					<< "// ERROR: Invalid precision format, expected:\n"
+					<< "\trange[0] = " << expected.negativeRange << "\n"
+					<< "\trange[1] = " << expected.positiveRange << "\n"
+					<< "\tprecision = " << expected.precision
+					<< tcu::TestLog::EndMessage;
+				error = true;
+			}
+		}
+		else
+		{
+			if (range[0] < expected.negativeRange)
+			{
+				m_log << tcu::TestLog::Message << "// ERROR: Invalid range[0], expected greater or equal to " << expected.negativeRange << tcu::TestLog::EndMessage;
+				error = true;
+			}
+
+			if (range[1] < expected.positiveRange)
+			{
+				m_log << tcu::TestLog::Message << "// ERROR: Invalid range[1], expected greater or equal to " << expected.positiveRange << tcu::TestLog::EndMessage;
+				error = true;
+			}
+
+			if (precision < expected.precision)
+			{
+				m_log << tcu::TestLog::Message << "// ERROR: Invalid precision, expected greater or equal to " << expected.precision << tcu::TestLog::EndMessage;
+				error = true;
+			}
+		}
+
+		if (error)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid precision/range");
+	}
+
+	RequiredFormat getRequiredFormat (void) const
+	{
+		// Precisions for different types.
+		const RequiredFormat requirements[] =
+		{
+			{   0,   0,  8 }, //!< lowp float
+			{  13,  13, 10 }, //!< mediump float
+			{ 127, 127, 23 }, //!< highp float
+			{   8,   7,  0 }, //!< lowp int
+			{  15,  14,  0 }, //!< mediump int
+			{  31,  30,  0 }, //!< highp int
+		};
+		const int ndx = (int)m_precisionType - (int)GL_LOW_FLOAT;
+
+		DE_ASSERT(ndx >= 0);
+		DE_ASSERT(ndx < DE_LENGTH_OF_ARRAY(requirements));
+		return requirements[ndx];
+	}
+
+	const glw::GLenum m_shaderType;
+	const glw::GLenum m_precisionType;
+};
+
+} // anonymous
+
+
+ShaderStateQueryTests::ShaderStateQueryTests (Context& context)
+	: TestCaseGroup(context, "shader", "Shader State Query tests")
+{
+}
+
+void ShaderStateQueryTests::init (void)
+{
+	// shader
+	addChild(new ShaderTypeCase						(m_context, "shader_type",							"SHADER_TYPE"));
+	addChild(new ShaderCompileStatusCase			(m_context, "shader_compile_status",				"COMPILE_STATUS"));
+	addChild(new ShaderInfoLogCase					(m_context, "shader_info_log_length",				"INFO_LOG_LENGTH"));
+	addChild(new ShaderSourceCase					(m_context, "shader_source_length",					"SHADER_SOURCE_LENGTH"));
+
+	// shader and program
+	addChild(new DeleteStatusCase					(m_context, "delete_status",						"DELETE_STATUS"));
+
+	// vertex-attrib
+	addChild(new CurrentVertexAttribInitialCase		(m_context, "current_vertex_attrib_initial",		"CURRENT_VERTEX_ATTRIB"));
+	addChild(new CurrentVertexAttribFloatCase		(m_context, "current_vertex_attrib_float",			"CURRENT_VERTEX_ATTRIB"));
+	addChild(new CurrentVertexAttribIntCase			(m_context, "current_vertex_attrib_int",			"CURRENT_VERTEX_ATTRIB"));
+	addChild(new CurrentVertexAttribUintCase		(m_context, "current_vertex_attrib_uint",			"CURRENT_VERTEX_ATTRIB"));
+	addChild(new CurrentVertexAttribConversionCase	(m_context, "current_vertex_attrib_float_to_int",	"CURRENT_VERTEX_ATTRIB"));
+
+	// program
+	addChild(new ProgramInfoLogCase					(m_context, "program_info_log_length",				"INFO_LOG_LENGTH"));
+	addChild(new ProgramValidateStatusCase			(m_context, "program_validate_status",				"VALIDATE_STATUS"));
+	addChild(new ProgramAttachedShadersCase			(m_context, "program_attached_shaders",				"ATTACHED_SHADERS"));
+
+	addChild(new ProgramActiveUniformNameCase		(m_context, "program_active_uniform_name",			"ACTIVE_UNIFORMS and ACTIVE_UNIFORM_MAX_LENGTH"));
+	addChild(new ProgramUniformCase					(m_context, "program_active_uniform_types",			"UNIFORM_TYPE, UNIFORM_SIZE, and UNIFORM_IS_ROW_MAJOR"));
+	addChild(new ProgramActiveUniformBlocksCase		(m_context, "program_active_uniform_blocks",		"ACTIVE_UNIFORM_BLOCK_x"));
+	addChild(new ProgramBinaryCase					(m_context, "program_binary",						"PROGRAM_BINARY_LENGTH and PROGRAM_BINARY_RETRIEVABLE_HINT"));
+
+	// transform feedback
+	addChild(new TransformFeedbackCase				(m_context, "transform_feedback",					"TRANSFORM_FEEDBACK_BUFFER_MODE, TRANSFORM_FEEDBACK_VARYINGS, TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH"));
+
+	// attribute related
+	addChild(new ActiveAttributesCase				(m_context, "active_attributes",					"ACTIVE_ATTRIBUTES and ACTIVE_ATTRIBUTE_MAX_LENGTH"));
+	addChild(new VertexAttributeSizeCase			(m_context, "vertex_attrib_size",					"VERTEX_ATTRIB_ARRAY_SIZE"));
+	addChild(new VertexAttributeTypeCase			(m_context, "vertex_attrib_type",					"VERTEX_ATTRIB_ARRAY_TYPE"));
+	addChild(new VertexAttributeStrideCase			(m_context, "vertex_attrib_stride",					"VERTEX_ATTRIB_ARRAY_STRIDE"));
+	addChild(new VertexAttributeNormalizedCase		(m_context, "vertex_attrib_normalized",				"VERTEX_ATTRIB_ARRAY_NORMALIZED"));
+	addChild(new VertexAttributeIntegerCase			(m_context, "vertex_attrib_integer",				"VERTEX_ATTRIB_ARRAY_INTEGER"));
+	addChild(new VertexAttributeEnabledCase			(m_context, "vertex_attrib_array_enabled",			"VERTEX_ATTRIB_ARRAY_ENABLED"));
+	addChild(new VertexAttributeDivisorCase			(m_context, "vertex_attrib_array_divisor",			"VERTEX_ATTRIB_ARRAY_DIVISOR"));
+	addChild(new VertexAttributeBufferBindingCase	(m_context, "vertex_attrib_array_buffer_binding",	"VERTEX_ATTRIB_ARRAY_BUFFER_BINDING"));
+	addChild(new VertexAttributePointerCase			(m_context, "vertex_attrib_pointerv",				"GetVertexAttribPointerv"));
+
+	// uniform values
+	addChild(new UniformValueFloatCase				(m_context, "uniform_value_float",					"GetUniform*"));
+	addChild(new UniformValueIntCase				(m_context, "uniform_value_int",					"GetUniform*"));
+	addChild(new UniformValueUintCase				(m_context, "uniform_value_uint",					"GetUniform*"));
+	addChild(new UniformValueBooleanCase			(m_context, "uniform_value_boolean",				"GetUniform*"));
+	addChild(new UniformValueSamplerCase			(m_context, "uniform_value_sampler",				"GetUniform*"));
+	addChild(new UniformValueArrayCase				(m_context, "uniform_value_array",					"GetUniform*"));
+	addChild(new UniformValueMatrixCase				(m_context, "uniform_value_matrix",					"GetUniform*"));
+
+	// precision format query
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_lowp_float",			"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_LOW_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_mediump_float",		"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_MEDIUM_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_highp_float",			"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_HIGH_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_lowp_int",			"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_LOW_INT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_mediump_int",			"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_MEDIUM_INT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_vertex_highp_int",			"GetShaderPrecisionFormat",		GL_VERTEX_SHADER,	GL_HIGH_INT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_lowp_float",		"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_LOW_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_mediump_float",		"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_MEDIUM_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_highp_float",		"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_HIGH_FLOAT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_lowp_int",			"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_LOW_INT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_mediump_int",		"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_MEDIUM_INT));
+	addChild(new PrecisionFormatCase				(m_context, "precision_fragment_highp_int",			"GetShaderPrecisionFormat",		GL_FRAGMENT_SHADER,	GL_HIGH_INT));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderStateQueryTests.hpp b/modules/gles3/functional/es3fShaderStateQueryTests.hpp
new file mode 100644
index 0000000..f8e1f97
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderStateQueryTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERSTATEQUERYTESTS_HPP
+#define _ES3FSHADERSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderStateQueryTests : public TestCaseGroup
+{
+public:
+																		ShaderStateQueryTests	(Context& context);
+
+	void																init					(void);
+
+private:
+																		ShaderStateQueryTests	(const ShaderStateQueryTests& other);
+	ShaderStateQueryTests&												operator=				(const ShaderStateQueryTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERSTATEQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderStructTests.cpp b/modules/gles3/functional/es3fShaderStructTests.cpp
new file mode 100644
index 0000000..57dbe60
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderStructTests.cpp
@@ -0,0 +1,1936 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader struct tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderStructTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deMath.h"
+
+using tcu::StringTemplate;
+
+using std::string;
+using std::vector;
+using std::ostringstream;
+
+using namespace glu;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+enum
+{
+	TEXTURE_BRICK = 0 //!< Unit index for brick texture
+};
+
+typedef void (*SetupUniformsFunc) (const glw::Functions& gl, deUint32 programID, const tcu::Vec4& constCoords);
+
+class ShaderStructCase : public ShaderRenderCase
+{
+public:
+						ShaderStructCase		(Context& context, const char* name, const char* description, bool isVertexCase, bool usesTextures, ShaderEvalFunc evalFunc, SetupUniformsFunc setupUniformsFunc, const char* vertShaderSource, const char* fragShaderSource);
+						~ShaderStructCase		(void);
+
+	void				init					(void);
+	void				deinit					(void);
+
+	virtual void		setupUniforms			(int programID, const tcu::Vec4& constCoords);
+
+private:
+						ShaderStructCase		(const ShaderStructCase&);
+	ShaderStructCase&	operator=				(const ShaderStructCase&);
+
+	SetupUniformsFunc	m_setupUniforms;
+	bool				m_usesTexture;
+
+	glu::Texture2D*		m_brickTexture;
+};
+
+ShaderStructCase::ShaderStructCase (Context& context, const char* name, const char* description, bool isVertexCase, bool usesTextures, ShaderEvalFunc evalFunc, SetupUniformsFunc setupUniformsFunc, const char* vertShaderSource, const char* fragShaderSource)
+	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
+	, m_setupUniforms	(setupUniformsFunc)
+	, m_usesTexture		(usesTextures)
+	, m_brickTexture	(DE_NULL)
+{
+	m_vertShaderSource	= vertShaderSource;
+	m_fragShaderSource	= fragShaderSource;
+}
+
+ShaderStructCase::~ShaderStructCase (void)
+{
+	delete m_brickTexture;
+}
+
+void ShaderStructCase::init (void)
+{
+	if (m_usesTexture)
+	{
+		m_brickTexture = glu::Texture2D::create(m_renderCtx, m_ctxInfo, m_testCtx.getArchive(), "data/brick.png");
+		m_textures.push_back(TextureBinding(m_brickTexture, tcu::Sampler(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
+																		 tcu::Sampler::LINEAR, tcu::Sampler::LINEAR)));
+		DE_ASSERT(m_textures.size() == 1);
+	}
+	gls::ShaderRenderCase::init();
+}
+
+void ShaderStructCase::deinit (void)
+{
+	gls::ShaderRenderCase::deinit();
+	delete m_brickTexture;
+	m_brickTexture = DE_NULL;
+}
+
+void ShaderStructCase::setupUniforms (int programID, const tcu::Vec4& constCoords)
+{
+	ShaderRenderCase::setupUniforms(programID, constCoords);
+	if (m_setupUniforms)
+		m_setupUniforms(m_renderCtx.getFunctions(), programID, constCoords);
+}
+
+static ShaderStructCase* createStructCase (Context& context, const char* name, const char* description, bool isVertexCase, bool usesTextures, ShaderEvalFunc evalFunc, SetupUniformsFunc setupUniforms, const LineStream& shaderSrc)
+{
+	static const char* defaultVertSrc =
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in highp vec4 a_coords;\n"
+		"out mediump vec4 v_coords;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"	v_coords = a_coords;\n"
+		"	gl_Position = a_position;\n"
+		"}\n";
+	static const char* defaultFragSrc =
+		"#version 300 es\n"
+		"in mediump vec4 v_color;\n"
+		"layout(location = 0) out mediump vec4 o_color;\n\n"
+		"void main (void)\n"
+		"{\n"
+		"	o_color = v_color;\n"
+		"}\n";
+
+	// Fill in specialization parameters.
+	std::map<std::string, std::string> spParams;
+	if (isVertexCase)
+	{
+		spParams["HEADER"] =
+			"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"in highp vec4 a_coords;\n"
+			"out mediump vec4 v_color;";
+		spParams["COORDS"]		= "a_coords";
+		spParams["DST"]			= "v_color";
+		spParams["ASSIGN_POS"]	= "gl_Position = a_position;";
+	}
+	else
+	{
+		spParams["HEADER"]	=
+			"#version 300 es\n"
+			"in mediump vec4 v_coords;\n"
+			"layout(location = 0) out mediump vec4 o_color;";
+		spParams["COORDS"]			= "v_coords";
+		spParams["DST"]				= "o_color";
+		spParams["ASSIGN_POS"]		= "";
+	}
+
+	if (isVertexCase)
+		return new ShaderStructCase(context, name, description, isVertexCase, usesTextures, evalFunc, setupUniforms, StringTemplate(shaderSrc.str()).specialize(spParams).c_str(), defaultFragSrc);
+	else
+		return new ShaderStructCase(context, name, description, isVertexCase, usesTextures, evalFunc, setupUniforms, defaultVertSrc, StringTemplate(shaderSrc.str()).specialize(spParams).c_str());
+}
+
+class LocalStructTests : public TestCaseGroup
+{
+public:
+	LocalStructTests (Context& context)
+		: TestCaseGroup(context, "local", "Local structs")
+	{
+	}
+
+	~LocalStructTests (void)
+	{
+	}
+
+	virtual void init (void);
+};
+
+void LocalStructTests::init (void)
+{
+	#define LOCAL_STRUCT_CASE(NAME, DESCRIPTION, SHADER_SRC, EVAL_FUNC_BODY)																	\
+		do {																																	\
+			struct Eval_##NAME { static void eval (ShaderEvalContext& c) EVAL_FUNC_BODY };														\
+			addChild(createStructCase(m_context, #NAME "_vertex", DESCRIPTION, true, false, &Eval_##NAME::eval, DE_NULL, SHADER_SRC));			\
+			addChild(createStructCase(m_context, #NAME "_fragment", DESCRIPTION, false, false,&Eval_##NAME::eval, DE_NULL, SHADER_SRC));		\
+		} while (deGetFalse())
+
+	LOCAL_STRUCT_CASE(basic, "Basic struct usage",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, vec3(0.0), ui_one);"
+		<< "	s.b = ${COORDS}.yzw;"
+		<< "	${DST} = vec4(s.a, s.b.x, s.b.y, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(nested, "Nested struct",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(0, vec2(0.0)), ui_one);"
+		<< "	s.b = T(ui_zero, ${COORDS}.yz);"
+		<< "	${DST} = vec4(s.a, s.b.b, s.b.a + s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(array_member, "Struct with array member",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump float	b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s;"
+		<< "	s.a = ${COORDS}.w;"
+		<< "	s.c = ui_one;"
+		<< "	s.b[0] = ${COORDS}.z;"
+		<< "	s.b[1] = ${COORDS}.y;"
+		<< "	s.b[2] = ${COORDS}.x;"
+		<< "	${DST} = vec4(s.a, s.b[0], s.b[1], s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(3,2,1);
+		});
+
+	LOCAL_STRUCT_CASE(array_member_dynamic_index, "Struct with array member, dynamic indexing",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump float	b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s;"
+		<< "	s.a = ${COORDS}.w;"
+		<< "	s.c = ui_one;"
+		<< "	s.b[0] = ${COORDS}.z;"
+		<< "	s.b[1] = ${COORDS}.y;"
+		<< "	s.b[2] = ${COORDS}.x;"
+		<< "	${DST} = vec4(s.b[ui_one], s.b[ui_zero], s.b[ui_two], s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(1,2,0);
+		});
+
+	LOCAL_STRUCT_CASE(struct_array, "Struct array",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[3];"
+		<< "	s[0] = S(${COORDS}.x, ui_zero);"
+		<< "	s[1].a = ${COORDS}.y;"
+		<< "	s[1].b = ui_one;"
+		<< "	s[2] = S(${COORDS}.z, ui_two);"
+		<< "	${DST} = vec4(s[2].a, s[1].a, s[0].a, s[2].b - s[1].b + s[0].b);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,1,0);
+		});
+
+	LOCAL_STRUCT_CASE(struct_array_dynamic_index, "Struct array with dynamic indexing",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[3];"
+		<< "	s[0] = S(${COORDS}.x, ui_zero);"
+		<< "	s[1].a = ${COORDS}.y;"
+		<< "	s[1].b = ui_one;"
+		<< "	s[2] = S(${COORDS}.z, ui_two);"
+		<< "	${DST} = vec4(s[ui_two].a, s[ui_one].a, s[ui_zero].a, s[ui_two].b - s[ui_one].b + s[ui_zero].b);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,1,0);
+		});
+
+	LOCAL_STRUCT_CASE(nested_struct_array, "Nested struct array",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[2];"
+		<< ""
+		<< "	// S[0]"
+		<< "	s[0].a         = ${COORDS}.x;"
+		<< "	s[0].b[0].a    = uf_half;"
+		<< "	s[0].b[0].b[0] = ${COORDS}.xy;"
+		<< "	s[0].b[0].b[1] = ${COORDS}.zw;"
+		<< "	s[0].b[1].a    = uf_third;"
+		<< "	s[0].b[1].b[0] = ${COORDS}.zw;"
+		<< "	s[0].b[1].b[1] = ${COORDS}.xy;"
+		<< "	s[0].b[2].a    = uf_fourth;"
+		<< "	s[0].b[2].b[0] = ${COORDS}.xz;"
+		<< "	s[0].b[2].b[1] = ${COORDS}.yw;"
+		<< "	s[0].c         = ui_zero;"
+		<< ""
+		<< "	// S[1]"
+		<< "	s[1].a         = ${COORDS}.w;"
+		<< "	s[1].b[0].a    = uf_two;"
+		<< "	s[1].b[0].b[0] = ${COORDS}.xx;"
+		<< "	s[1].b[0].b[1] = ${COORDS}.yy;"
+		<< "	s[1].b[1].a    = uf_three;"
+		<< "	s[1].b[1].b[0] = ${COORDS}.zz;"
+		<< "	s[1].b[1].b[1] = ${COORDS}.ww;"
+		<< "	s[1].b[2].a    = uf_four;"
+		<< "	s[1].b[2].b[0] = ${COORDS}.yx;"
+		<< "	s[1].b[2].b[1] = ${COORDS}.wz;"
+		<< "	s[1].c         = ui_one;"
+		<< ""
+		<< "	mediump float r = (s[0].b[1].b[0].x + s[1].b[2].b[1].y) * s[0].b[0].a; // (z + z) * 0.5"
+		<< "	mediump float g = s[1].b[0].b[0].y * s[0].b[2].a * s[1].b[2].a; // x * 0.25 * 4"
+		<< "	mediump float b = (s[0].b[2].b[1].y + s[0].b[1].b[0].y + s[1].a) * s[0].b[1].a; // (w + w + w) * 0.333"
+		<< "	mediump float a = float(s[0].c) + s[1].b[2].a - s[1].b[1].a; // 0 + 4.0 - 3.0"
+		<< "	${DST} = vec4(r, g, b, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,0,3);
+		});
+
+	LOCAL_STRUCT_CASE(nested_struct_array_dynamic_index, "Nested struct array with dynamic indexing",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[2];"
+		<< ""
+		<< "	// S[0]"
+		<< "	s[0].a         = ${COORDS}.x;"
+		<< "	s[0].b[0].a    = uf_half;"
+		<< "	s[0].b[0].b[0] = ${COORDS}.xy;"
+		<< "	s[0].b[0].b[1] = ${COORDS}.zw;"
+		<< "	s[0].b[1].a    = uf_third;"
+		<< "	s[0].b[1].b[0] = ${COORDS}.zw;"
+		<< "	s[0].b[1].b[1] = ${COORDS}.xy;"
+		<< "	s[0].b[2].a    = uf_fourth;"
+		<< "	s[0].b[2].b[0] = ${COORDS}.xz;"
+		<< "	s[0].b[2].b[1] = ${COORDS}.yw;"
+		<< "	s[0].c         = ui_zero;"
+		<< ""
+		<< "	// S[1]"
+		<< "	s[1].a         = ${COORDS}.w;"
+		<< "	s[1].b[0].a    = uf_two;"
+		<< "	s[1].b[0].b[0] = ${COORDS}.xx;"
+		<< "	s[1].b[0].b[1] = ${COORDS}.yy;"
+		<< "	s[1].b[1].a    = uf_three;"
+		<< "	s[1].b[1].b[0] = ${COORDS}.zz;"
+		<< "	s[1].b[1].b[1] = ${COORDS}.ww;"
+		<< "	s[1].b[2].a    = uf_four;"
+		<< "	s[1].b[2].b[0] = ${COORDS}.yx;"
+		<< "	s[1].b[2].b[1] = ${COORDS}.wz;"
+		<< "	s[1].c         = ui_one;"
+		<< ""
+		<< "	mediump float r = (s[0].b[ui_one].b[ui_one-1].x + s[ui_one].b[ui_two].b[ui_zero+1].y) * s[0].b[0].a; // (z + z) * 0.5"
+		<< "	mediump float g = s[ui_two-1].b[ui_two-2].b[ui_zero].y * s[0].b[ui_two].a * s[ui_one].b[2].a; // x * 0.25 * 4"
+		<< "	mediump float b = (s[ui_zero].b[ui_one+1].b[1].y + s[0].b[ui_one*ui_one].b[0].y + s[ui_one].a) * s[0].b[ui_two-ui_one].a; // (w + w + w) * 0.333"
+		<< "	mediump float a = float(s[ui_zero].c) + s[ui_one-ui_zero].b[ui_two].a - s[ui_zero+ui_one].b[ui_two-ui_one].a; // 0 + 4.0 - 3.0"
+		<< "	${DST} = vec4(r, g, b, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,0,3);
+		});
+
+	LOCAL_STRUCT_CASE(parameter, "Struct as a function parameter",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "mediump vec4 myFunc (S s)"
+		<< "{"
+		<< "	return vec4(s.a, s.b.x, s.b.y, s.c);"
+		<< "}"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, vec3(0.0), ui_one);"
+		<< "	s.b = ${COORDS}.yzw;"
+		<< "	${DST} = myFunc(s);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(parameter_nested, "Nested struct as a function parameter",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "mediump vec4 myFunc (S s)"
+		<< "{"
+		<< "	return vec4(s.a, s.b.b, s.b.a + s.c);"
+		<< "}"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(0, vec2(0.0)), ui_one);"
+		<< "	s.b = T(ui_zero, ${COORDS}.yz);"
+		<< "	${DST} = myFunc(s);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(return, "Struct as a return value",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "S myFunc (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, vec3(0.0), ui_one);"
+		<< "	s.b = ${COORDS}.yzw;"
+		<< "	return s;"
+		<< "}"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = myFunc();"
+		<< "	${DST} = vec4(s.a, s.b.x, s.b.y, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(return_nested, "Nested struct",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "S myFunc (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(0, vec2(0.0)), ui_one);"
+		<< "	s.b = T(ui_zero, ${COORDS}.yz);"
+		<< "	return s;"
+		<< "}"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = myFunc();"
+		<< "	${DST} = vec4(s.a, s.b.b, s.b.a + s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,1,2);
+		});
+
+	LOCAL_STRUCT_CASE(conditional_assignment, "Conditional struct assignment",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform mediump float uf_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, ${COORDS}.yzw, ui_zero);"
+		<< "	if (uf_one > 0.0)"
+		<< "		s = S(${COORDS}.w, ${COORDS}.zyx, ui_one);"
+		<< "	${DST} = vec4(s.a, s.b.xy, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(3,2,1);
+		});
+
+	LOCAL_STRUCT_CASE(loop_assignment, "Struct assignment in loop",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, ${COORDS}.yzw, ui_zero);"
+		<< "	for (int i = 0; i < 3; i++)"
+		<< "	{"
+		<< "		if (i == 1)"
+		<< "			s = S(${COORDS}.w, ${COORDS}.zyx, ui_one);"
+		<< "	}"
+		<< "	${DST} = vec4(s.a, s.b.xy, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(3,2,1);
+		});
+
+	LOCAL_STRUCT_CASE(dynamic_loop_assignment, "Struct assignment in loop",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_three;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, ${COORDS}.yzw, ui_zero);"
+		<< "	for (int i = 0; i < ui_three; i++)"
+		<< "	{"
+		<< "		if (i == ui_one)"
+		<< "			s = S(${COORDS}.w, ${COORDS}.zyx, ui_one);"
+		<< "	}"
+		<< "	${DST} = vec4(s.a, s.b.xy, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(3,2,1);
+		});
+
+	LOCAL_STRUCT_CASE(nested_conditional_assignment, "Conditional assignment of nested struct",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform mediump float uf_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(ui_one, ${COORDS}.yz), ui_one);"
+		<< "	if (uf_one > 0.0)"
+		<< "		s.b = T(ui_zero, ${COORDS}.zw);"
+		<< "	${DST} = vec4(s.a, s.b.b, s.c - s.b.a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,2,3);
+		});
+
+	LOCAL_STRUCT_CASE(nested_loop_assignment, "Nested struct assignment in loop",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform mediump float uf_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(ui_one, ${COORDS}.yz), ui_one);"
+		<< "	for (int i = 0; i < 3; i++)"
+		<< "	{"
+		<< "		if (i == 1)"
+		<< "			s.b = T(ui_zero, ${COORDS}.zw);"
+		<< "	}"
+		<< "	${DST} = vec4(s.a, s.b.b, s.c - s.b.a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,2,3);
+		});
+
+	LOCAL_STRUCT_CASE(nested_dynamic_loop_assignment, "Nested struct assignment in dynamic loop",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_three;"
+		<< "uniform mediump float uf_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s = S(${COORDS}.x, T(ui_one, ${COORDS}.yz), ui_one);"
+		<< "	for (int i = 0; i < ui_three; i++)"
+		<< "	{"
+		<< "		if (i == ui_one)"
+		<< "			s.b = T(ui_zero, ${COORDS}.zw);"
+		<< "	}"
+		<< "	${DST} = vec4(s.a, s.b.b, s.c - s.b.a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(0,2,3);
+		});
+
+	LOCAL_STRUCT_CASE(loop_struct_array, "Struct array usage in loop",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[3];"
+		<< "	s[0] = S(${COORDS}.x, ui_zero);"
+		<< "	s[1].a = ${COORDS}.y;"
+		<< "	s[1].b = -ui_one;"
+		<< "	s[2] = S(${COORDS}.z, ui_two);"
+		<< ""
+		<< "	mediump float rgb[3];"
+		<< "	int alpha = 0;"
+		<< "	for (int i = 0; i < 3; i++)"
+		<< "	{"
+		<< "		rgb[i] = s[2-i].a;"
+		<< "		alpha += s[i].b;"
+		<< "	}"
+		<< "	${DST} = vec4(rgb[0], rgb[1], rgb[2], alpha);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,1,0);
+		});
+
+	LOCAL_STRUCT_CASE(loop_nested_struct_array, "Nested struct array usage in loop",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< "uniform mediump float uf_sixth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[2];"
+		<< ""
+		<< "	// S[0]"
+		<< "	s[0].a         = ${COORDS}.x;"
+		<< "	s[0].b[0].a    = uf_half;"
+		<< "	s[0].b[0].b[0] = ${COORDS}.yx;"
+		<< "	s[0].b[0].b[1] = ${COORDS}.zx;"
+		<< "	s[0].b[1].a    = uf_third;"
+		<< "	s[0].b[1].b[0] = ${COORDS}.yy;"
+		<< "	s[0].b[1].b[1] = ${COORDS}.wy;"
+		<< "	s[0].b[2].a    = uf_fourth;"
+		<< "	s[0].b[2].b[0] = ${COORDS}.zx;"
+		<< "	s[0].b[2].b[1] = ${COORDS}.zy;"
+		<< "	s[0].c         = ui_zero;"
+		<< ""
+		<< "	// S[1]"
+		<< "	s[1].a         = ${COORDS}.w;"
+		<< "	s[1].b[0].a    = uf_two;"
+		<< "	s[1].b[0].b[0] = ${COORDS}.zx;"
+		<< "	s[1].b[0].b[1] = ${COORDS}.zy;"
+		<< "	s[1].b[1].a    = uf_three;"
+		<< "	s[1].b[1].b[0] = ${COORDS}.zz;"
+		<< "	s[1].b[1].b[1] = ${COORDS}.ww;"
+		<< "	s[1].b[2].a    = uf_four;"
+		<< "	s[1].b[2].b[0] = ${COORDS}.yx;"
+		<< "	s[1].b[2].b[1] = ${COORDS}.wz;"
+		<< "	s[1].c         = ui_one;"
+		<< ""
+		<< "	mediump float r = 0.0; // (x*3 + y*3) / 6.0"
+		<< "	mediump float g = 0.0; // (y*3 + z*3) / 6.0"
+		<< "	mediump float b = 0.0; // (z*3 + w*3) / 6.0"
+		<< "	mediump float a = 1.0;"
+		<< "	for (int i = 0; i < 2; i++)"
+		<< "	{"
+		<< "		for (int j = 0; j < 3; j++)"
+		<< "		{"
+		<< "			r += s[0].b[j].b[i].y;"
+		<< "			g += s[i].b[j].b[0].x;"
+		<< "			b += s[i].b[j].b[1].x;"
+		<< "			a *= s[i].b[j].a;"
+		<< "		}"
+		<< "	}"
+		<< "	${DST} = vec4(r*uf_sixth, g*uf_sixth, b*uf_sixth, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = (c.coords.swizzle(0,1,2) + c.coords.swizzle(1,2,3)) * 0.5f;
+		});
+
+	LOCAL_STRUCT_CASE(dynamic_loop_struct_array, "Struct array usage in dynamic loop",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform int ui_three;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[3];"
+		<< "	s[0] = S(${COORDS}.x, ui_zero);"
+		<< "	s[1].a = ${COORDS}.y;"
+		<< "	s[1].b = -ui_one;"
+		<< "	s[2] = S(${COORDS}.z, ui_two);"
+		<< ""
+		<< "	mediump float rgb[3];"
+		<< "	int alpha = 0;"
+		<< "	for (int i = 0; i < ui_three; i++)"
+		<< "	{"
+		<< "		rgb[i] = s[2-i].a;"
+		<< "		alpha += s[i].b;"
+		<< "	}"
+		<< "	${DST} = vec4(rgb[0], rgb[1], rgb[2], alpha);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = c.coords.swizzle(2,1,0);
+		});
+
+	LOCAL_STRUCT_CASE(dynamic_loop_nested_struct_array, "Nested struct array usage in dynamic loop",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform int ui_three;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< "uniform mediump float uf_sixth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S s[2];"
+		<< ""
+		<< "	// S[0]"
+		<< "	s[0].a         = ${COORDS}.x;"
+		<< "	s[0].b[0].a    = uf_half;"
+		<< "	s[0].b[0].b[0] = ${COORDS}.yx;"
+		<< "	s[0].b[0].b[1] = ${COORDS}.zx;"
+		<< "	s[0].b[1].a    = uf_third;"
+		<< "	s[0].b[1].b[0] = ${COORDS}.yy;"
+		<< "	s[0].b[1].b[1] = ${COORDS}.wy;"
+		<< "	s[0].b[2].a    = uf_fourth;"
+		<< "	s[0].b[2].b[0] = ${COORDS}.zx;"
+		<< "	s[0].b[2].b[1] = ${COORDS}.zy;"
+		<< "	s[0].c         = ui_zero;"
+		<< ""
+		<< "	// S[1]"
+		<< "	s[1].a         = ${COORDS}.w;"
+		<< "	s[1].b[0].a    = uf_two;"
+		<< "	s[1].b[0].b[0] = ${COORDS}.zx;"
+		<< "	s[1].b[0].b[1] = ${COORDS}.zy;"
+		<< "	s[1].b[1].a    = uf_three;"
+		<< "	s[1].b[1].b[0] = ${COORDS}.zz;"
+		<< "	s[1].b[1].b[1] = ${COORDS}.ww;"
+		<< "	s[1].b[2].a    = uf_four;"
+		<< "	s[1].b[2].b[0] = ${COORDS}.yx;"
+		<< "	s[1].b[2].b[1] = ${COORDS}.wz;"
+		<< "	s[1].c         = ui_one;"
+		<< ""
+		<< "	mediump float r = 0.0; // (x*3 + y*3) / 6.0"
+		<< "	mediump float g = 0.0; // (y*3 + z*3) / 6.0"
+		<< "	mediump float b = 0.0; // (z*3 + w*3) / 6.0"
+		<< "	mediump float a = 1.0;"
+		<< "	for (int i = 0; i < ui_two; i++)"
+		<< "	{"
+		<< "		for (int j = 0; j < ui_three; j++)"
+		<< "		{"
+		<< "			r += s[0].b[j].b[i].y;"
+		<< "			g += s[i].b[j].b[0].x;"
+		<< "			b += s[i].b[j].b[1].x;"
+		<< "			a *= s[i].b[j].a;"
+		<< "		}"
+		<< "	}"
+		<< "	${DST} = vec4(r*uf_sixth, g*uf_sixth, b*uf_sixth, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			c.color.xyz() = (c.coords.swizzle(0,1,2) + c.coords.swizzle(1,2,3)) * 0.5f;
+		});
+
+	LOCAL_STRUCT_CASE(basic_equal, "Basic struct equality",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S a = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y), 2.3), ui_one);"
+		<< "	S b = S(floor(${COORDS}.x+0.5), vec3(0.0, floor(${COORDS}.y), 2.3), ui_one);"
+		<< "	S c = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y+0.5), 2.3), ui_one);"
+		<< "	S d = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y), 2.3), ui_two);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a == b) ${DST}.x = 1.0;"
+		<< "	if (a == c) ${DST}.y = 1.0;"
+		<< "	if (a == d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			if (deFloatFloor(c.coords[0]) == deFloatFloor(c.coords[0]+0.5f))
+				c.color.x() = 1.0f;
+			if (deFloatFloor(c.coords[1]) == deFloatFloor(c.coords[1]+0.5f))
+				c.color.y() = 1.0f;
+		});
+
+	LOCAL_STRUCT_CASE(basic_not_equal, "Basic struct equality",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S a = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y), 2.3), ui_one);"
+		<< "	S b = S(floor(${COORDS}.x+0.5), vec3(0.0, floor(${COORDS}.y), 2.3), ui_one);"
+		<< "	S c = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y+0.5), 2.3), ui_one);"
+		<< "	S d = S(floor(${COORDS}.x), vec3(0.0, floor(${COORDS}.y), 2.3), ui_two);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a != b) ${DST}.x = 1.0;"
+		<< "	if (a != c) ${DST}.y = 1.0;"
+		<< "	if (a != d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			if (deFloatFloor(c.coords[0]) != deFloatFloor(c.coords[0]+0.5f))
+				c.color.x() = 1.0f;
+			if (deFloatFloor(c.coords[1]) != deFloatFloor(c.coords[1]+0.5f))
+				c.color.y() = 1.0f;
+			c.color.z() = 1.0f;
+		});
+
+	LOCAL_STRUCT_CASE(nested_equal, "Nested struct struct equality",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump vec3	a;"
+		<< "	int				b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S a = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_one), 1);"
+		<< "	S b = S(floor(${COORDS}.x+0.5), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_one), 1);"
+		<< "	S c = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y+0.5), 2.3), ui_one), 1);"
+		<< "	S d = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_two), 1);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a == b) ${DST}.x = 1.0;"
+		<< "	if (a == c) ${DST}.y = 1.0;"
+		<< "	if (a == d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			if (deFloatFloor(c.coords[0]) == deFloatFloor(c.coords[0]+0.5f))
+				c.color.x() = 1.0f;
+			if (deFloatFloor(c.coords[1]) == deFloatFloor(c.coords[1]+0.5f))
+				c.color.y() = 1.0f;
+		});
+
+	LOCAL_STRUCT_CASE(nested_not_equal, "Nested struct struct equality",
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump vec3	a;"
+		<< "	int				b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S a = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_one), 1);"
+		<< "	S b = S(floor(${COORDS}.x+0.5), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_one), 1);"
+		<< "	S c = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y+0.5), 2.3), ui_one), 1);"
+		<< "	S d = S(floor(${COORDS}.x), T(vec3(0.0, floor(${COORDS}.y), 2.3), ui_two), 1);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a != b) ${DST}.x = 1.0;"
+		<< "	if (a != c) ${DST}.y = 1.0;"
+		<< "	if (a != d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			if (deFloatFloor(c.coords[0]) != deFloatFloor(c.coords[0]+0.5f))
+				c.color.x() = 1.0f;
+			if (deFloatFloor(c.coords[1]) != deFloatFloor(c.coords[1]+0.5f))
+				c.color.y() = 1.0f;
+			c.color.z() = 1.0f;
+		});
+}
+
+class UniformStructTests : public TestCaseGroup
+{
+public:
+	UniformStructTests (Context& context)
+		: TestCaseGroup(context, "uniform", "Uniform structs")
+	{
+	}
+
+	~UniformStructTests (void)
+	{
+	}
+
+	virtual void init (void);
+};
+
+namespace
+{
+
+#define CHECK_SET_UNIFORM(NAME) GLU_EXPECT_NO_ERROR(gl.getError(), (string("Failed to set ") + NAME).c_str())
+
+#define MAKE_SET_VEC_UNIFORM(VECTYPE, SETUNIFORM)															\
+void setUniform (const glw::Functions& gl, deUint32 programID, const char* name, const tcu::VECTYPE& vec)	\
+{																											\
+	int loc = gl.getUniformLocation(programID, name);														\
+	SETUNIFORM(loc, 1, vec.getPtr());																		\
+	CHECK_SET_UNIFORM(name);																				\
+}																											\
+struct SetUniform##VECTYPE##Dummy_s { int unused; }
+
+#define MAKE_SET_VEC_UNIFORM_PTR(VECTYPE, SETUNIFORM)																		\
+void setUniform (const glw::Functions& gl, deUint32 programID, const char* name, const tcu::VECTYPE* vec, int arraySize)	\
+{																															\
+	int loc = gl.getUniformLocation(programID, name);																		\
+	SETUNIFORM(loc, arraySize, vec->getPtr());																				\
+	CHECK_SET_UNIFORM(name);																								\
+}																															\
+struct SetUniformPtr##VECTYPE##Dummy_s { int unused; }
+
+MAKE_SET_VEC_UNIFORM	(Vec2,	gl.uniform2fv);
+MAKE_SET_VEC_UNIFORM	(Vec3,	gl.uniform3fv);
+MAKE_SET_VEC_UNIFORM_PTR(Vec2,	gl.uniform2fv);
+
+void setUniform (const glw::Functions& gl, deUint32 programID, const char* name, float value)
+{
+	int loc = gl.getUniformLocation(programID, name);
+	gl.uniform1f(loc, value);
+	CHECK_SET_UNIFORM(name);
+}
+
+void setUniform (const glw::Functions& gl, deUint32 programID, const char* name, int value)
+{
+	int loc = gl.getUniformLocation(programID, name);
+	gl.uniform1i(loc, value);
+	CHECK_SET_UNIFORM(name);
+}
+
+void setUniform (const glw::Functions& gl, deUint32 programID, const char* name, const float* value, int arraySize)
+{
+	int loc = gl.getUniformLocation(programID, name);
+	gl.uniform1fv(loc, arraySize, value);
+	CHECK_SET_UNIFORM(name);
+}
+
+} // anonymous
+
+void UniformStructTests::init (void)
+{
+	#define UNIFORM_STRUCT_CASE(NAME, DESCRIPTION, TEXTURES, SHADER_SRC, SET_UNIFORMS_BODY, EVAL_FUNC_BODY)																\
+		do {																																							\
+			struct SetUniforms_##NAME { static void setUniforms (const glw::Functions& gl, deUint32 programID, const tcu::Vec4& constCoords) SET_UNIFORMS_BODY };		\
+			struct Eval_##NAME { static void eval (ShaderEvalContext& c) EVAL_FUNC_BODY };																				\
+			addChild(createStructCase(m_context, #NAME "_vertex", DESCRIPTION, true, TEXTURES, Eval_##NAME::eval, SetUniforms_##NAME::setUniforms, SHADER_SRC));		\
+			addChild(createStructCase(m_context, #NAME "_fragment", DESCRIPTION, false, TEXTURES, Eval_##NAME::eval, SetUniforms_##NAME::setUniforms, SHADER_SRC));		\
+		} while (deGetFalse())
+
+	UNIFORM_STRUCT_CASE(basic, "Basic struct usage", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s.a, s.b.x, s.b.y, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s.a", constCoords.x());
+			setUniform(gl, programID, "s.b", constCoords.swizzle(1, 2, 3));
+			setUniform(gl, programID, "s.c", 1);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(0,1,2);
+		});
+
+	UNIFORM_STRUCT_CASE(nested, "Nested struct", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct T {"
+		<< "	int				a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s.a, s.b.b, s.b.a + s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s.a",	constCoords.x());
+			setUniform(gl, programID, "s.b.a",	0);
+			setUniform(gl, programID, "s.b.b",	constCoords.swizzle(1,2));
+			setUniform(gl, programID, "s.c",	1);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(0,1,2);
+		});
+
+	UNIFORM_STRUCT_CASE(array_member, "Struct with array member", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump float	b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s.a, s.b[0], s.b[1], s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s.a",	constCoords.w());
+			setUniform(gl, programID, "s.c",	1);
+
+			float b[3];
+			b[0] = constCoords.z();
+			b[1] = constCoords.y();
+			b[2] = constCoords.x();
+			setUniform(gl, programID, "s.b", b, DE_LENGTH_OF_ARRAY(b));
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(3,2,1);
+		});
+
+	UNIFORM_STRUCT_CASE(array_member_dynamic_index, "Struct with array member, dynamic indexing", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump float	b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s.b[ui_one], s.b[ui_zero], s.b[ui_two], s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s.a",	constCoords.w());
+			setUniform(gl, programID, "s.c",	1);
+
+			float b[3];
+			b[0] = constCoords.z();
+			b[1] = constCoords.y();
+			b[2] = constCoords.x();
+			setUniform(gl, programID, "s.b", b, DE_LENGTH_OF_ARRAY(b));
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(1,2,0);
+		});
+
+	UNIFORM_STRUCT_CASE(struct_array, "Struct array", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< "uniform S s[3];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s[2].a, s[1].a, s[0].a, s[2].b - s[1].b + s[0].b);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s[0].a",	constCoords.x());
+			setUniform(gl, programID, "s[0].b",	0);
+			setUniform(gl, programID, "s[1].a",	constCoords.y());
+			setUniform(gl, programID, "s[1].b",	1);
+			setUniform(gl, programID, "s[2].a",	constCoords.z());
+			setUniform(gl, programID, "s[2].b",	2);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,1,0);
+		});
+
+	UNIFORM_STRUCT_CASE(struct_array_dynamic_index, "Struct array with dynamic indexing", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< "uniform S s[3];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(s[ui_two].a, s[ui_one].a, s[ui_zero].a, s[ui_two].b - s[ui_one].b + s[ui_zero].b);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s[0].a",	constCoords.x());
+			setUniform(gl, programID, "s[0].b",	0);
+			setUniform(gl, programID, "s[1].a",	constCoords.y());
+			setUniform(gl, programID, "s[1].b",	1);
+			setUniform(gl, programID, "s[2].a",	constCoords.z());
+			setUniform(gl, programID, "s[2].b",	2);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,1,0);
+		});
+
+	UNIFORM_STRUCT_CASE(nested_struct_array, "Nested struct array", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s[2];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float r = (s[0].b[1].b[0].x + s[1].b[2].b[1].y) * s[0].b[0].a; // (z + z) * 0.5"
+		<< "	mediump float g = s[1].b[0].b[0].y * s[0].b[2].a * s[1].b[2].a; // x * 0.25 * 4"
+		<< "	mediump float b = (s[0].b[2].b[1].y + s[0].b[1].b[0].y + s[1].a) * s[0].b[1].a; // (w + w + w) * 0.333"
+		<< "	mediump float a = float(s[0].c) + s[1].b[2].a - s[1].b[1].a; // 0 + 4.0 - 3.0"
+		<< "	${DST} = vec4(r, g, b, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			tcu::Vec2 arr[2];
+
+			setUniform(gl, programID, "s[0].a",			constCoords.x());
+			arr[0] = constCoords.swizzle(0,1);
+			arr[1] = constCoords.swizzle(2,3);
+			setUniform(gl, programID, "s[0].b[0].a",	0.5f);
+			setUniform(gl, programID, "s[0].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,3);
+			arr[1] = constCoords.swizzle(0,1);
+			setUniform(gl, programID, "s[0].b[1].a",	1.0f/3.0f);
+			setUniform(gl, programID, "s[0].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(0,2);
+			arr[1] = constCoords.swizzle(1,3);
+			setUniform(gl, programID, "s[0].b[2].a",	1.0f/4.0f);
+			setUniform(gl, programID, "s[0].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[0].c",			0);
+
+			setUniform(gl, programID, "s[1].a",			constCoords.w());
+			arr[0] = constCoords.swizzle(0,0);
+			arr[1] = constCoords.swizzle(1,1);
+			setUniform(gl, programID, "s[1].b[0].a",	2.0f);
+			setUniform(gl, programID, "s[1].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,2);
+			arr[1] = constCoords.swizzle(3,3);
+			setUniform(gl, programID, "s[1].b[1].a",	3.0f);
+			setUniform(gl, programID, "s[1].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(3,2);
+			setUniform(gl, programID, "s[1].b[2].a",	4.0f);
+			setUniform(gl, programID, "s[1].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[1].c",			1);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,0,3);
+		});
+
+	UNIFORM_STRUCT_CASE(nested_struct_array_dynamic_index, "Nested struct array with dynamic indexing", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s[2];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float r = (s[0].b[ui_one].b[ui_one-1].x + s[ui_one].b[ui_two].b[ui_zero+1].y) * s[0].b[0].a; // (z + z) * 0.5"
+		<< "	mediump float g = s[ui_two-1].b[ui_two-2].b[ui_zero].y * s[0].b[ui_two].a * s[ui_one].b[2].a; // x * 0.25 * 4"
+		<< "	mediump float b = (s[ui_zero].b[ui_one+1].b[1].y + s[0].b[ui_one*ui_one].b[0].y + s[ui_one].a) * s[0].b[ui_two-ui_one].a; // (w + w + w) * 0.333"
+		<< "	mediump float a = float(s[ui_zero].c) + s[ui_one-ui_zero].b[ui_two].a - s[ui_zero+ui_one].b[ui_two-ui_one].a; // 0 + 4.0 - 3.0"
+		<< "	${DST} = vec4(r, g, b, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			tcu::Vec2 arr[2];
+
+			setUniform(gl, programID, "s[0].a",			constCoords.x());
+			arr[0] = constCoords.swizzle(0,1);
+			arr[1] = constCoords.swizzle(2,3);
+			setUniform(gl, programID, "s[0].b[0].a",	0.5f);
+			setUniform(gl, programID, "s[0].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,3);
+			arr[1] = constCoords.swizzle(0,1);
+			setUniform(gl, programID, "s[0].b[1].a",	1.0f/3.0f);
+			setUniform(gl, programID, "s[0].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(0,2);
+			arr[1] = constCoords.swizzle(1,3);
+			setUniform(gl, programID, "s[0].b[2].a",	1.0f/4.0f);
+			setUniform(gl, programID, "s[0].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[0].c",			0);
+
+			setUniform(gl, programID, "s[1].a",			constCoords.w());
+			arr[0] = constCoords.swizzle(0,0);
+			arr[1] = constCoords.swizzle(1,1);
+			setUniform(gl, programID, "s[1].b[0].a",	2.0f);
+			setUniform(gl, programID, "s[1].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,2);
+			arr[1] = constCoords.swizzle(3,3);
+			setUniform(gl, programID, "s[1].b[1].a",	3.0f);
+			setUniform(gl, programID, "s[1].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(3,2);
+			setUniform(gl, programID, "s[1].b[2].a",	4.0f);
+			setUniform(gl, programID, "s[1].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[1].c",			1);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,0,3);
+		});
+
+	UNIFORM_STRUCT_CASE(loop_struct_array, "Struct array usage in loop", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< "uniform S s[3];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float rgb[3];"
+		<< "	int alpha = 0;"
+		<< "	for (int i = 0; i < 3; i++)"
+		<< "	{"
+		<< "		rgb[i] = s[2-i].a;"
+		<< "		alpha += s[i].b;"
+		<< "	}"
+		<< "	${DST} = vec4(rgb[0], rgb[1], rgb[2], alpha);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s[0].a",	constCoords.x());
+			setUniform(gl, programID, "s[0].b",	0);
+			setUniform(gl, programID, "s[1].a",	constCoords.y());
+			setUniform(gl, programID, "s[1].b",	-1);
+			setUniform(gl, programID, "s[2].a",	constCoords.z());
+			setUniform(gl, programID, "s[2].b",	2);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,1,0);
+		});
+
+	UNIFORM_STRUCT_CASE(loop_nested_struct_array, "Nested struct array usage in loop", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< "uniform mediump float uf_sixth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s[2];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float r = 0.0; // (x*3 + y*3) / 6.0"
+		<< "	mediump float g = 0.0; // (y*3 + z*3) / 6.0"
+		<< "	mediump float b = 0.0; // (z*3 + w*3) / 6.0"
+		<< "	mediump float a = 1.0;"
+		<< "	for (int i = 0; i < 2; i++)"
+		<< "	{"
+		<< "		for (int j = 0; j < 3; j++)"
+		<< "		{"
+		<< "			r += s[0].b[j].b[i].y;"
+		<< "			g += s[i].b[j].b[0].x;"
+		<< "			b += s[i].b[j].b[1].x;"
+		<< "			a *= s[i].b[j].a;"
+		<< "		}"
+		<< "	}"
+		<< "	${DST} = vec4(r*uf_sixth, g*uf_sixth, b*uf_sixth, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			tcu::Vec2 arr[2];
+
+			setUniform(gl, programID, "s[0].a",			constCoords.x());
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(2,0);
+			setUniform(gl, programID, "s[0].b[0].a",	0.5f);
+			setUniform(gl, programID, "s[0].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,1);
+			arr[1] = constCoords.swizzle(3,1);
+			setUniform(gl, programID, "s[0].b[1].a",	1.0f/3.0f);
+			setUniform(gl, programID, "s[0].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,1);
+			arr[1] = constCoords.swizzle(2,1);
+			setUniform(gl, programID, "s[0].b[2].a",	1.0f/4.0f);
+			setUniform(gl, programID, "s[0].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[0].c",			0);
+
+			setUniform(gl, programID, "s[1].a",			constCoords.w());
+			arr[0] = constCoords.swizzle(2,0);
+			arr[1] = constCoords.swizzle(2,1);
+			setUniform(gl, programID, "s[1].b[0].a",	2.0f);
+			setUniform(gl, programID, "s[1].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,2);
+			arr[1] = constCoords.swizzle(3,3);
+			setUniform(gl, programID, "s[1].b[1].a",	3.0f);
+			setUniform(gl, programID, "s[1].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(3,2);
+			setUniform(gl, programID, "s[1].b[2].a",	4.0f);
+			setUniform(gl, programID, "s[1].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[1].c",			1);
+		},
+		{
+			c.color.xyz() = (c.constCoords.swizzle(0,1,2) + c.constCoords.swizzle(1,2,3)) * 0.5f;
+		});
+
+	UNIFORM_STRUCT_CASE(dynamic_loop_struct_array, "Struct array usage in dynamic loop", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform int ui_three;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump int		b;"
+		<< "};"
+		<< "uniform S s[3];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float rgb[3];"
+		<< "	int alpha = 0;"
+		<< "	for (int i = 0; i < ui_three; i++)"
+		<< "	{"
+		<< "		rgb[i] = s[2-i].a;"
+		<< "		alpha += s[i].b;"
+		<< "	}"
+		<< "	${DST} = vec4(rgb[0], rgb[1], rgb[2], alpha);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			setUniform(gl, programID, "s[0].a",	constCoords.x());
+			setUniform(gl, programID, "s[0].b",	0);
+			setUniform(gl, programID, "s[1].a",	constCoords.y());
+			setUniform(gl, programID, "s[1].b",	-1);
+			setUniform(gl, programID, "s[2].a",	constCoords.z());
+			setUniform(gl, programID, "s[2].b",	2);
+		},
+		{
+			c.color.xyz() = c.constCoords.swizzle(2,1,0);
+		});
+
+	UNIFORM_STRUCT_CASE(dynamic_loop_nested_struct_array, "Nested struct array usage in dynamic loop", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< "uniform int ui_two;"
+		<< "uniform int ui_three;"
+		<< "uniform mediump float uf_two;"
+		<< "uniform mediump float uf_three;"
+		<< "uniform mediump float uf_four;"
+		<< "uniform mediump float uf_half;"
+		<< "uniform mediump float uf_third;"
+		<< "uniform mediump float uf_fourth;"
+		<< "uniform mediump float uf_sixth;"
+		<< ""
+		<< "struct T {"
+		<< "	mediump float	a;"
+		<< "	mediump vec2	b[2];"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b[3];"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s[2];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	mediump float r = 0.0; // (x*3 + y*3) / 6.0"
+		<< "	mediump float g = 0.0; // (y*3 + z*3) / 6.0"
+		<< "	mediump float b = 0.0; // (z*3 + w*3) / 6.0"
+		<< "	mediump float a = 1.0;"
+		<< "	for (int i = 0; i < ui_two; i++)"
+		<< "	{"
+		<< "		for (int j = 0; j < ui_three; j++)"
+		<< "		{"
+		<< "			r += s[0].b[j].b[i].y;"
+		<< "			g += s[i].b[j].b[0].x;"
+		<< "			b += s[i].b[j].b[1].x;"
+		<< "			a *= s[i].b[j].a;"
+		<< "		}"
+		<< "	}"
+		<< "	${DST} = vec4(r*uf_sixth, g*uf_sixth, b*uf_sixth, a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			tcu::Vec2 arr[2];
+
+			setUniform(gl, programID, "s[0].a",			constCoords.x());
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(2,0);
+			setUniform(gl, programID, "s[0].b[0].a",	0.5f);
+			setUniform(gl, programID, "s[0].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,1);
+			arr[1] = constCoords.swizzle(3,1);
+			setUniform(gl, programID, "s[0].b[1].a",	1.0f/3.0f);
+			setUniform(gl, programID, "s[0].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,1);
+			arr[1] = constCoords.swizzle(2,1);
+			setUniform(gl, programID, "s[0].b[2].a",	1.0f/4.0f);
+			setUniform(gl, programID, "s[0].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[0].c",			0);
+
+			setUniform(gl, programID, "s[1].a",			constCoords.w());
+			arr[0] = constCoords.swizzle(2,0);
+			arr[1] = constCoords.swizzle(2,1);
+			setUniform(gl, programID, "s[1].b[0].a",	2.0f);
+			setUniform(gl, programID, "s[1].b[0].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(2,2);
+			arr[1] = constCoords.swizzle(3,3);
+			setUniform(gl, programID, "s[1].b[1].a",	3.0f);
+			setUniform(gl, programID, "s[1].b[1].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			arr[0] = constCoords.swizzle(1,0);
+			arr[1] = constCoords.swizzle(3,2);
+			setUniform(gl, programID, "s[1].b[2].a",	4.0f);
+			setUniform(gl, programID, "s[1].b[2].b",	&arr[0], DE_LENGTH_OF_ARRAY(arr));
+			setUniform(gl, programID, "s[1].c",			1);
+		},
+		{
+			c.color.xyz() = (c.constCoords.swizzle(0,1,2) + c.constCoords.swizzle(1,2,3)) * 0.5f;
+		});
+
+	UNIFORM_STRUCT_CASE(sampler, "Sampler in struct", true,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	sampler2D		c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(texture(s.c, ${COORDS}.xy * s.b.xy + s.b.z).rgb, s.a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			DE_UNREF(constCoords);
+			setUniform(gl, programID, "s.a", 1.0f);
+			setUniform(gl, programID, "s.b", tcu::Vec3(0.25f, 0.25f, 0.5f));
+			setUniform(gl, programID, "s.c", 0);
+		},
+		{
+			c.color.xyz() = c.texture2D(TEXTURE_BRICK, c.coords.swizzle(0,1) * 0.25f + 0.5f).swizzle(0,1,2);
+		});
+
+	UNIFORM_STRUCT_CASE(sampler_nested, "Sampler in nested struct", true,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_zero;"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct T {"
+		<< "	sampler2D		a;"
+		<< "	mediump vec2	b;"
+		<< "};"
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	T				b;"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S s;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(texture(s.b.a, ${COORDS}.xy * s.b.b + s.a).rgb, s.c);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			DE_UNREF(constCoords);
+			setUniform(gl, programID, "s.a",	0.5f);
+			setUniform(gl, programID, "s.b.a",	0);
+			setUniform(gl, programID, "s.b.b",	tcu::Vec2(0.25f, 0.25f));
+			setUniform(gl, programID, "s.c",	1);
+		},
+		{
+			c.color.xyz() = c.texture2D(TEXTURE_BRICK, c.coords.swizzle(0,1) * 0.25f + 0.5f).swizzle(0,1,2);
+		});
+
+	UNIFORM_STRUCT_CASE(sampler_array, "Sampler in struct array", true,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform int ui_one;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	sampler2D		c;"
+		<< "};"
+		<< "uniform S s[2];"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	${DST} = vec4(texture(s[1].c, ${COORDS}.xy * s[0].b.xy + s[1].b.z).rgb, s[0].a);"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			DE_UNREF(constCoords);
+			setUniform(gl, programID, "s[0].a", 1.0f);
+			setUniform(gl, programID, "s[0].b", tcu::Vec3(0.25f, 0.25f, 0.25f));
+			setUniform(gl, programID, "s[0].c", 1);
+			setUniform(gl, programID, "s[1].a", 0.0f);
+			setUniform(gl, programID, "s[1].b", tcu::Vec3(0.5f, 0.5f, 0.5f));
+			setUniform(gl, programID, "s[1].c", 0);
+		},
+		{
+			c.color.xyz() = c.texture2D(TEXTURE_BRICK, c.coords.swizzle(0,1) * 0.25f + 0.5f).swizzle(0,1,2);
+		});
+
+	UNIFORM_STRUCT_CASE(equal, "Struct equality", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform mediump float uf_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S a;"
+		<< "uniform S b;"
+		<< "uniform S c;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S d = S(uf_one, vec3(0.0, floor(${COORDS}.y+1.0), 2.0), ui_two);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a == b) ${DST}.x = 1.0;"
+		<< "	if (a == c) ${DST}.y = 1.0;"
+		<< "	if (a == d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			DE_UNREF(constCoords);
+			setUniform(gl, programID, "a.a", 1.0f);
+			setUniform(gl, programID, "a.b", tcu::Vec3(0.0f, 1.0f, 2.0f));
+			setUniform(gl, programID, "a.c", 2);
+			setUniform(gl, programID, "b.a", 1.0f);
+			setUniform(gl, programID, "b.b", tcu::Vec3(0.0f, 1.0f, 2.0f));
+			setUniform(gl, programID, "b.c", 2);
+			setUniform(gl, programID, "c.a", 1.0f);
+			setUniform(gl, programID, "c.b", tcu::Vec3(0.0f, 1.1f, 2.0f));
+			setUniform(gl, programID, "c.c", 2);
+		},
+		{
+			c.color.xy() = tcu::Vec2(1.0f, 0.0f);
+			if (deFloatFloor(c.coords[1]+1.0f) == deFloatFloor(1.1f))
+				c.color.z() = 1.0f;
+		});
+
+	UNIFORM_STRUCT_CASE(not_equal, "Struct equality", false,
+		LineStream()
+		<< "${HEADER}"
+		<< "uniform mediump float uf_one;"
+		<< "uniform int ui_two;"
+		<< ""
+		<< "struct S {"
+		<< "	mediump float	a;"
+		<< "	mediump vec3	b;"
+		<< "	int				c;"
+		<< "};"
+		<< "uniform S a;"
+		<< "uniform S b;"
+		<< "uniform S c;"
+		<< ""
+		<< "void main (void)"
+		<< "{"
+		<< "	S d = S(uf_one, vec3(0.0, floor(${COORDS}.y+1.0), 2.0), ui_two);"
+		<< "	${DST} = vec4(0.0, 0.0, 0.0, 1.0);"
+		<< "	if (a != b) ${DST}.x = 1.0;"
+		<< "	if (a != c) ${DST}.y = 1.0;"
+		<< "	if (a != d) ${DST}.z = 1.0;"
+		<< "	${ASSIGN_POS}"
+		<< "}",
+		{
+			DE_UNREF(constCoords);
+			setUniform(gl, programID, "a.a", 1.0f);
+			setUniform(gl, programID, "a.b", tcu::Vec3(0.0f, 1.0f, 2.0f));
+			setUniform(gl, programID, "a.c", 2);
+			setUniform(gl, programID, "b.a", 1.0f);
+			setUniform(gl, programID, "b.b", tcu::Vec3(0.0f, 1.0f, 2.0f));
+			setUniform(gl, programID, "b.c", 2);
+			setUniform(gl, programID, "c.a", 1.0f);
+			setUniform(gl, programID, "c.b", tcu::Vec3(0.0f, 1.1f, 2.0f));
+			setUniform(gl, programID, "c.c", 2);
+		},
+		{
+			c.color.xy() = tcu::Vec2(0.0f, 1.0f);
+			if (deFloatFloor(c.coords[1]+1.0f) != deFloatFloor(1.1f))
+				c.color.z() = 1.0f;
+		});
+}
+
+ShaderStructTests::ShaderStructTests (Context& context)
+	: TestCaseGroup(context, "struct", "Struct Tests")
+{
+}
+
+ShaderStructTests::~ShaderStructTests (void)
+{
+}
+
+void ShaderStructTests::init (void)
+{
+	addChild(new LocalStructTests(m_context));
+	addChild(new UniformStructTests(m_context));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderStructTests.hpp b/modules/gles3/functional/es3fShaderStructTests.hpp
new file mode 100644
index 0000000..f17036a
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderStructTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERSTRUCTTESTS_HPP
+#define _ES3FSHADERSTRUCTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader struct tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderStructTests : public TestCaseGroup
+{
+public:
+							ShaderStructTests		(Context& context);
+	virtual					~ShaderStructTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderStructTests		(const ShaderStructTests&);		// not allowed!
+	ShaderStructTests&		operator=				(const ShaderStructTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERSTRUCTTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderSwitchTests.cpp b/modules/gles3/functional/es3fShaderSwitchTests.cpp
new file mode 100644
index 0000000..1ee0d46
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderSwitchTests.cpp
@@ -0,0 +1,462 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader switch statement tests.
+ *
+ * Variables:
+ *  + Selection expression type: static, uniform, dynamic
+ *  + Switch layout - fall-through or use of default label
+ *  + Switch nested in loop/conditional statement
+ *  + Loop/conditional statement nested in switch
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderSwitchTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "glsShaderLibrary.hpp"
+#include "tcuStringTemplate.hpp"
+#include "deMath.h"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using namespace deqp::gls;
+using std::string;
+using std::map;
+using std::vector;
+
+class ShaderSwitchCase : public ShaderRenderCase
+{
+public:
+						ShaderSwitchCase			(Context& context, const char* name, const char* description, bool isVertexCase, const char* vtxSource, const char* fragSource, ShaderEvalFunc evalFunc);
+	virtual				~ShaderSwitchCase			(void);
+};
+
+ShaderSwitchCase::ShaderSwitchCase (Context& context, const char* name, const char* description, bool isVertexCase, const char* vtxSource, const char* fragSource, ShaderEvalFunc evalFunc)
+	: ShaderRenderCase(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
+{
+	m_vertShaderSource	= vtxSource;
+	m_fragShaderSource	= fragSource;
+}
+
+ShaderSwitchCase::~ShaderSwitchCase (void)
+{
+}
+
+enum SwitchType
+{
+	SWITCHTYPE_STATIC = 0,
+	SWITCHTYPE_UNIFORM,
+	SWITCHTYPE_DYNAMIC,
+
+	SWITCHTYPE_LAST
+};
+
+static void evalSwitchStatic	(ShaderEvalContext& evalCtx)	{ evalCtx.color.xyz() = evalCtx.coords.swizzle(1,2,3);	}
+static void evalSwitchUniform	(ShaderEvalContext& evalCtx)	{ evalCtx.color.xyz() = evalCtx.coords.swizzle(1,2,3);	}
+static void evalSwitchDynamic	(ShaderEvalContext& evalCtx)
+{
+	switch (int(deFloatFloor(evalCtx.coords.z()*1.5f + 2.0f)))
+	{
+		case 0:		evalCtx.color.xyz() = evalCtx.coords.swizzle(0,1,2);	break;
+		case 1:		evalCtx.color.xyz() = evalCtx.coords.swizzle(3,2,1);	break;
+		case 2:		evalCtx.color.xyz() = evalCtx.coords.swizzle(1,2,3);	break;
+		case 3:		evalCtx.color.xyz() = evalCtx.coords.swizzle(2,1,0);	break;
+		default:	evalCtx.color.xyz() = evalCtx.coords.swizzle(0,0,0);	break;
+	}
+}
+
+static tcu::TestCase* makeSwitchCase (Context& context, const char* name, const char* desc, SwitchType type, bool isVertex, const LineStream& switchBody)
+{
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op		= isVertex ? vtx : frag;
+
+	vtx << "#version 300 es\n"
+		<< "in highp vec4 a_position;\n"
+		<< "in highp vec4 a_coords;\n";
+	frag << "#version 300 es\n"
+		 << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (isVertex)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "out highp vec4 v_coords;\n";
+		frag << "in highp vec4 v_coords;\n";
+	}
+
+	if (type == SWITCHTYPE_UNIFORM)
+		op << "uniform highp int ui_two;\n";
+
+	vtx << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	gl_Position = a_position;\n";
+	frag << "\n"
+		 << "void main (void)\n"
+		 << "{\n";
+
+	// Setup.
+	op << "	highp vec4 coords = " << (isVertex ? "a_coords" : "v_coords") << ";\n";
+	op << "	mediump vec3 res = vec3(0.0);\n\n";
+
+	// Switch body.
+	map<string, string> params;
+	params["CONDITION"] = type == SWITCHTYPE_STATIC		? "2"								:
+						  type == SWITCHTYPE_UNIFORM	? "ui_two"							:
+						  type == SWITCHTYPE_DYNAMIC	? "int(floor(coords.z*1.5 + 2.0))"	: "???";
+
+	op << tcu::StringTemplate(switchBody.str()).specialize(params).c_str();
+	op << "\n";
+
+	if (isVertex)
+	{
+		vtx << "	v_color = vec4(res, 1.0);\n";
+		frag << "	o_color = v_color;\n";
+	}
+	else
+	{
+		vtx << "	v_coords = a_coords;\n";
+		frag << "	o_color = vec4(res, 1.0);\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	return new ShaderSwitchCase(context, name, desc, isVertex, vtx.str().c_str(), frag.str().c_str(),
+								type == SWITCHTYPE_STATIC	? evalSwitchStatic	:
+								type == SWITCHTYPE_UNIFORM	? evalSwitchUniform	:
+								type == SWITCHTYPE_DYNAMIC	? evalSwitchDynamic	: (ShaderEvalFunc)DE_NULL);
+}
+
+static void makeSwitchCases (TestCaseGroup* group, const char* name, const char* desc, const LineStream& switchBody)
+{
+	static const char* switchTypeNames[] = { "static", "uniform", "dynamic" };
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(switchTypeNames) == SWITCHTYPE_LAST);
+
+	for (int type = 0; type < SWITCHTYPE_LAST; type++)
+	{
+		group->addChild(makeSwitchCase(group->getContext(), (string(name) + "_" + switchTypeNames[type] + "_vertex").c_str(),	desc, (SwitchType)type, true,	switchBody));
+		group->addChild(makeSwitchCase(group->getContext(), (string(name) + "_" + switchTypeNames[type] + "_fragment").c_str(),	desc, (SwitchType)type, false,	switchBody));
+	}
+}
+
+ShaderSwitchTests::ShaderSwitchTests (Context& context)
+	: TestCaseGroup(context, "switch", "Switch statement tests")
+{
+}
+
+ShaderSwitchTests::~ShaderSwitchTests (void)
+{
+}
+
+void ShaderSwitchTests::init (void)
+{
+	// Expected swizzles:
+	// 0: xyz
+	// 1: wzy
+	// 2: yzw
+	// 3: zyx
+
+	makeSwitchCases(this, "basic", "Basic switch statement usage",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:		res = coords.wzy;	break;"
+		<< "	case 2:		res = coords.yzw;	break;"
+		<< "	case 3:		res = coords.zyx;	break;"
+		<< "}");
+
+	makeSwitchCases(this, "const_expr_in_label", "Constant expression in label",
+		LineStream(1)
+		<< "const int t = 2;"
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case int(0.0):	res = coords.xyz;	break;"
+		<< "	case 2-1:		res = coords.wzy;	break;"
+		<< "	case 3&(1<<1):	res = coords.yzw;	break;"
+		<< "	case t+1:		res = coords.zyx;	break;"
+		<< "}");
+
+	makeSwitchCases(this, "default_label", "Default label usage",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:		res = coords.wzy;	break;"
+		<< "	case 3:		res = coords.zyx;	break;"
+		<< "	default:	res = coords.yzw;"
+		<< "}");
+
+	makeSwitchCases(this, "default_not_last", "Default label usage",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	default:	res = coords.yzw;	break;"
+		<< "	case 1:		res = coords.wzy;	break;"
+		<< "	case 3:		res = coords.zyx;	break;"
+		<< "}");
+
+	makeSwitchCases(this, "no_default_label", "No match in switch without default label",
+		LineStream(1)
+		<< "res = coords.yzw;\n"
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:		res = coords.wzy;	break;"
+		<< "	case 3:		res = coords.zyx;	break;"
+		<< "}");
+
+	makeSwitchCases(this, "fall_through", "Fall-through",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:		res = coords.wzy;	break;"
+		<< "	case 2:		coords = coords.yzwx;"
+		<< "	case 4:		res = vec3(coords);	break;"
+		<< "	case 3:		res = coords.zyx;	break;"
+		<< "}");
+
+	makeSwitchCases(this, "fall_through_default", "Fall-through",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:		res = coords.wzy;	break;"
+		<< "	case 3:		res = coords.zyx;	break;"
+		<< "	case 2:		coords = coords.yzwx;"
+		<< "	default:	res = vec3(coords);"
+		<< "}");
+
+	makeSwitchCases(this, "conditional_fall_through", "Fall-through",
+		LineStream(1)
+		<< "highp vec4 tmp = coords;"
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:		res = coords.wzy;	break;"
+		<< "	case 2:"
+		<< "		tmp = coords.yzwx;"
+		<< "	case 3:"
+		<< "		res = vec3(tmp);"
+		<< "		if (${CONDITION} != 3)"
+		<< "			break;"
+		<< "	default:	res = tmp.zyx;		break;"
+		<< "}");
+
+	makeSwitchCases(this, "conditional_fall_through_2", "Fall-through",
+		LineStream(1)
+		<< "highp vec4 tmp = coords;"
+		<< "mediump int c = ${CONDITION};"
+		<< "switch (c)"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:		res = coords.wzy;	break;"
+		<< "	case 2:"
+		<< "		c += ${CONDITION};"
+		<< "		tmp = coords.yzwx;"
+		<< "	case 3:"
+		<< "		res = vec3(tmp);"
+		<< "		if (c == 4)"
+		<< "			break;"
+		<< "	default:	res = tmp.zyx;		break;"
+		<< "}");
+
+	makeSwitchCases(this, "scope", "Basic switch statement usage",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:		res = coords.wzy;	break;"
+		<< "	case 2:"
+		<< "	{"
+		<< "		mediump vec3 t = coords.yzw;"
+		<< "		res = t;"
+		<< "		break;"
+		<< "	}"
+		<< "	case 3:		res = coords.zyx;	break;"
+		<< "}");
+
+	makeSwitchCases(this, "switch_in_if", "Switch in for loop",
+		LineStream(1)
+		<< "if (${CONDITION} >= 0)"
+		<< "{"
+		<< "	switch (${CONDITION})"
+		<< "	{"
+		<< "		case 0:		res = coords.xyz;	break;"
+		<< "		case 1:		res = coords.wzy;	break;"
+		<< "		case 2:		res = coords.yzw;	break;"
+		<< "		case 3:		res = coords.zyx;	break;"
+		<< "	}"
+		<< "}");
+
+	makeSwitchCases(this, "switch_in_for_loop", "Switch in for loop",
+		LineStream(1)
+		<< "for (int i = 0; i <= ${CONDITION}; i++)"
+		<< "{"
+		<< "	switch (i)"
+		<< "	{"
+		<< "		case 0:		res = coords.xyz;	break;"
+		<< "		case 1:		res = coords.wzy;	break;"
+		<< "		case 2:		res = coords.yzw;	break;"
+		<< "		case 3:		res = coords.zyx;	break;"
+		<< "	}"
+		<< "}");
+
+	makeSwitchCases(this, "switch_in_while_loop", "Switch in while loop",
+		LineStream(1)
+		<< "int i = 0;"
+		<< "while (i <= ${CONDITION})"
+		<< "{"
+		<< "	switch (i)"
+		<< "	{"
+		<< "		case 0:		res = coords.xyz;	break;"
+		<< "		case 1:		res = coords.wzy;	break;"
+		<< "		case 2:		res = coords.yzw;	break;"
+		<< "		case 3:		res = coords.zyx;	break;"
+		<< "	}"
+		<< "	i += 1;"
+		<< "}");
+
+	makeSwitchCases(this, "switch_in_do_while_loop", "Switch in do-while loop",
+		LineStream(1)
+		<< "int i = 0;"
+		<< "do"
+		<< "{"
+		<< "	switch (i)"
+		<< "	{"
+		<< "		case 0:		res = coords.xyz;	break;"
+		<< "		case 1:		res = coords.wzy;	break;"
+		<< "		case 2:		res = coords.yzw;	break;"
+		<< "		case 3:		res = coords.zyx;	break;"
+		<< "	}"
+		<< "	i += 1;"
+		<< "} while (i <= ${CONDITION});");
+
+	makeSwitchCases(this, "if_in_switch", "Basic switch statement usage",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:		res = coords.wzy;	break;"
+		<< "	default:"
+		<< "		if (${CONDITION} == 2)"
+		<< "			res = coords.yzw;"
+		<< "		else"
+		<< "			res = coords.zyx;"
+		<< "		break;"
+		<< "}");
+
+	makeSwitchCases(this, "for_loop_in_switch", "Basic switch statement usage",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:"
+		<< "	case 2:"
+		<< "	{"
+		<< "		highp vec3 t = coords.yzw;"
+		<< "		for (int i = 0; i < ${CONDITION}; i++)"
+		<< "			t = t.zyx;"
+		<< "		res = t;"
+		<< "		break;"
+		<< "	}"
+		<< "	default:	res = coords.zyx;	break;"
+		<< "}");
+
+	makeSwitchCases(this, "while_loop_in_switch", "Basic switch statement usage",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:"
+		<< "	case 2:"
+		<< "	{"
+		<< "		highp vec3 t = coords.yzw;"
+		<< "		int i = 0;"
+		<< "		while (i < ${CONDITION})"
+		<< "		{"
+		<< "			t = t.zyx;"
+		<< "			i += 1;"
+		<< "		}"
+		<< "		res = t;"
+		<< "		break;"
+		<< "	}"
+		<< "	default:	res = coords.zyx;	break;"
+		<< "}");
+
+	makeSwitchCases(this, "do_while_loop_in_switch", "Basic switch statement usage",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:"
+		<< "	case 2:"
+		<< "	{"
+		<< "		highp vec3 t = coords.yzw;"
+		<< "		int i = 0;"
+		<< "		do"
+		<< "		{"
+		<< "			t = t.zyx;"
+		<< "			i += 1;"
+		<< "		} while (i < ${CONDITION});"
+		<< "		res = t;"
+		<< "		break;"
+		<< "	}"
+		<< "	default:	res = coords.zyx;	break;"
+		<< "}");
+
+	makeSwitchCases(this, "switch_in_switch", "Basic switch statement usage",
+		LineStream(1)
+		<< "switch (${CONDITION})"
+		<< "{"
+		<< "	case 0:		res = coords.xyz;	break;"
+		<< "	case 1:"
+		<< "	case 2:"
+		<< "		switch (${CONDITION} - 1)"
+		<< "		{"
+		<< "			case 0:		res = coords.wzy;	break;"
+		<< "			case 1:		res = coords.yzw;	break;"
+		<< "		}"
+		<< "		break;"
+		<< "	default:	res = coords.zyx;	break;"
+		<< "}");
+
+	// Negative cases.
+	ShaderLibrary library(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
+	vector<tcu::TestNode*> negativeCases = library.loadShaderFile("shaders/switch.test");
+
+	for (vector<tcu::TestNode*>::iterator i = negativeCases.begin(); i != negativeCases.end(); i++)
+		addChild(*i);
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderSwitchTests.hpp b/modules/gles3/functional/es3fShaderSwitchTests.hpp
new file mode 100644
index 0000000..a894e09
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderSwitchTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERSWITCHTESTS_HPP
+#define _ES3FSHADERSWITCHTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader switch statement tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderSwitchTests : public TestCaseGroup
+{
+public:
+							ShaderSwitchTests		(Context& context);
+	virtual					~ShaderSwitchTests		(void);
+
+	virtual void			init					(void);
+
+private:
+							ShaderSwitchTests		(const ShaderSwitchTests&);		// not allowed!
+	ShaderSwitchTests&		operator=				(const ShaderSwitchTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERSWITCHTESTS_HPP
diff --git a/modules/gles3/functional/es3fShaderTextureFunctionTests.cpp b/modules/gles3/functional/es3fShaderTextureFunctionTests.cpp
new file mode 100644
index 0000000..7c9d436
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderTextureFunctionTests.cpp
@@ -0,0 +1,2065 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture access function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fShaderTextureFunctionTests.hpp"
+#include "glsShaderRenderCase.hpp"
+#include "glsShaderLibrary.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuMatrixUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "glwFunctions.hpp"
+#include "deMath.h"
+
+#include <sstream>
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+namespace
+{
+
+enum Function
+{
+	FUNCTION_TEXTURE = 0,		//!< texture(), textureOffset()
+	FUNCTION_TEXTUREPROJ,		//!< textureProj(), textureProjOffset()
+	FUNCTION_TEXTUREPROJ3,		//!< textureProj(sampler2D, vec3)
+	FUNCTION_TEXTURELOD,		// ...
+	FUNCTION_TEXTUREPROJLOD,
+	FUNCTION_TEXTUREPROJLOD3,	//!< textureProjLod(sampler2D, vec3)
+	FUNCTION_TEXTUREGRAD,
+	FUNCTION_TEXTUREPROJGRAD,
+	FUNCTION_TEXTUREPROJGRAD3,	//!< textureProjGrad(sampler2D, vec3)
+	FUNCTION_TEXELFETCH,
+
+	FUNCTION_LAST
+};
+
+inline bool functionHasAutoLod (glu::ShaderType shaderType, Function function)
+{
+	return shaderType == glu::SHADERTYPE_FRAGMENT &&
+		   (function == FUNCTION_TEXTURE		||
+			function == FUNCTION_TEXTUREPROJ	||
+			function == FUNCTION_TEXTUREPROJ3);
+}
+
+inline bool functionHasProj (Function function)
+{
+	return function == FUNCTION_TEXTUREPROJ		||
+		   function == FUNCTION_TEXTUREPROJ3	||
+		   function == FUNCTION_TEXTUREPROJLOD	||
+		   function == FUNCTION_TEXTUREPROJGRAD	||
+		   function == FUNCTION_TEXTUREPROJLOD3	||
+		   function == FUNCTION_TEXTUREPROJGRAD3;
+}
+
+inline bool functionHasGrad (Function function)
+{
+	return function == FUNCTION_TEXTUREGRAD || function == FUNCTION_TEXTUREPROJGRAD || function == FUNCTION_TEXTUREPROJGRAD3;
+}
+
+inline bool functionHasLod (Function function)
+{
+	return function == FUNCTION_TEXTURELOD		||
+		   function == FUNCTION_TEXTUREPROJLOD	||
+		   function == FUNCTION_TEXTUREPROJLOD3	||
+		   function == FUNCTION_TEXELFETCH;
+}
+
+struct TextureLookupSpec
+{
+	Function		function;
+
+	tcu::Vec4		minCoord;
+	tcu::Vec4		maxCoord;
+
+	// Bias
+	bool			useBias;
+
+	// Bias or Lod for *Lod* functions
+	float			minLodBias;
+	float			maxLodBias;
+
+	// For *Grad* functions
+	tcu::Vec3		minDX;
+	tcu::Vec3		maxDX;
+	tcu::Vec3		minDY;
+	tcu::Vec3		maxDY;
+
+	bool			useOffset;
+	tcu::IVec3		offset;
+
+	TextureLookupSpec (void)
+		: function		(FUNCTION_LAST)
+		, minCoord		(0.0f)
+		, maxCoord		(1.0f)
+		, useBias		(false)
+		, minLodBias	(0.0f)
+		, maxLodBias	(0.0f)
+		, minDX			(0.0f)
+		, maxDX			(0.0f)
+		, minDY			(0.0f)
+		, maxDY			(0.0f)
+		, useOffset		(false)
+		, offset		(0)
+	{
+	}
+
+	TextureLookupSpec (Function				function_,
+					   const tcu::Vec4&		minCoord_,
+					   const tcu::Vec4&		maxCoord_,
+					   bool					useBias_,
+					   float				minLodBias_,
+					   float				maxLodBias_,
+					   const tcu::Vec3&		minDX_,
+					   const tcu::Vec3&		maxDX_,
+					   const tcu::Vec3&		minDY_,
+					   const tcu::Vec3&		maxDY_,
+					   bool					useOffset_,
+					   const tcu::IVec3&	offset_)
+		: function		(function_)
+		, minCoord		(minCoord_)
+		, maxCoord		(maxCoord_)
+		, useBias		(useBias_)
+		, minLodBias	(minLodBias_)
+		, maxLodBias	(maxLodBias_)
+		, minDX			(minDX_)
+		, maxDX			(maxDX_)
+		, minDY			(minDY_)
+		, maxDY			(maxDY_)
+		, useOffset		(useOffset_)
+		, offset		(offset_)
+	{
+	}
+};
+
+enum TextureType
+{
+	TEXTURETYPE_2D,
+	TEXTURETYPE_CUBE_MAP,
+	TEXTURETYPE_2D_ARRAY,
+	TEXTURETYPE_3D,
+
+	TEXTURETYPE_LAST
+};
+
+struct TextureSpec
+{
+	TextureType			type;		//!< Texture type (2D, cubemap, ...)
+	deUint32			format;		//!< Internal format.
+	int					width;
+	int					height;
+	int					depth;
+	int					numLevels;
+	tcu::Sampler		sampler;
+
+	TextureSpec (void)
+		: type			(TEXTURETYPE_LAST)
+		, format		(GL_NONE)
+		, width			(0)
+		, height		(0)
+		, depth			(0)
+		, numLevels		(0)
+	{
+	}
+
+	TextureSpec (TextureType			type_,
+				 deUint32				format_,
+				 int					width_,
+				 int					height_,
+				 int					depth_,
+				 int					numLevels_,
+				 const tcu::Sampler&	sampler_)
+		: type			(type_)
+		, format		(format_)
+		, width			(width_)
+		, height		(height_)
+		, depth			(depth_)
+		, numLevels		(numLevels_)
+		, sampler		(sampler_)
+	{
+	}
+};
+
+struct TexLookupParams
+{
+	float				lod;
+	tcu::IVec3			offset;
+	tcu::Vec4			scale;
+	tcu::Vec4			bias;
+
+	TexLookupParams (void)
+		: lod		(0.0f)
+		, offset	(0)
+		, scale		(1.0f)
+		, bias		(0.0f)
+	{
+	}
+};
+
+} // anonymous
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+
+enum LodMode
+{
+	LODMODE_EXACT = 0,
+	LODMODE_MIN_BOUND,
+	LODMODE_MAX_BOUND,
+
+	LODMODE_LAST
+};
+
+static const LodMode DEFAULT_LOD_MODE = LODMODE_EXACT;
+
+inline float computeLodFromDerivates (float dudx, float dvdx, float dudy, float dvdy)
+{
+	const LodMode	mode	= DEFAULT_LOD_MODE;
+	float			p;
+
+	switch (mode)
+	{
+		case LODMODE_EXACT:
+			p = de::max(deFloatSqrt(dudx*dudx + dvdx*dvdx), deFloatSqrt(dudy*dudy + dvdy*dvdy));
+			break;
+
+		case LODMODE_MIN_BOUND:
+		case LODMODE_MAX_BOUND:
+		{
+			float mu = de::max(deFloatAbs(dudx), deFloatAbs(dudy));
+			float mv = de::max(deFloatAbs(dvdx), deFloatAbs(dvdy));
+
+			p = mode == LODMODE_MIN_BOUND ? de::max(mu, mv) : mu + mv;
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return deFloatLog2(p);
+}
+
+inline float computeLodFromDerivates (float dudx, float dvdx, float dwdx, float dudy, float dvdy, float dwdy)
+{
+	const LodMode	mode	= DEFAULT_LOD_MODE;
+	float			p;
+
+	switch (mode)
+	{
+		case LODMODE_EXACT:
+			p = de::max(deFloatSqrt(dudx*dudx + dvdx*dvdx + dwdx*dwdx), deFloatSqrt(dudy*dudy + dvdy*dvdy + dwdy*dwdy));
+			break;
+
+		case LODMODE_MIN_BOUND:
+		case LODMODE_MAX_BOUND:
+		{
+			float mu = de::max(deFloatAbs(dudx), deFloatAbs(dudy));
+			float mv = de::max(deFloatAbs(dvdx), deFloatAbs(dvdy));
+			float mw = de::max(deFloatAbs(dwdx), deFloatAbs(dwdy));
+
+			p = mode == LODMODE_MIN_BOUND ? de::max(de::max(mu, mv), mw) : (mu + mv + mw);
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return deFloatLog2(p);
+}
+
+inline float computeLodFromGrad2D (const gls::ShaderEvalContext& c)
+{
+	float w = (float)c.textures[0].tex2D->getWidth();
+	float h = (float)c.textures[0].tex2D->getHeight();
+	return computeLodFromDerivates(c.in[1].x()*w, c.in[1].y()*h, c.in[2].x()*w, c.in[2].y()*h);
+}
+
+inline float computeLodFromGrad2DArray (const gls::ShaderEvalContext& c)
+{
+	float w = (float)c.textures[0].tex2DArray->getWidth();
+	float h = (float)c.textures[0].tex2DArray->getHeight();
+	return computeLodFromDerivates(c.in[1].x()*w, c.in[1].y()*h, c.in[2].x()*w, c.in[2].y()*h);
+}
+
+inline float computeLodFromGrad3D (const gls::ShaderEvalContext& c)
+{
+	float w = (float)c.textures[0].tex3D->getWidth();
+	float h = (float)c.textures[0].tex3D->getHeight();
+	float d = (float)c.textures[0].tex3D->getDepth();
+	return computeLodFromDerivates(c.in[1].x()*w, c.in[1].y()*h, c.in[1].z()*d, c.in[2].x()*w, c.in[2].y()*h, c.in[2].z()*d);
+}
+
+inline float computeLodFromGradCube (const gls::ShaderEvalContext& c)
+{
+	// \note Major axis is always -Z or +Z
+	float m = de::abs(c.in[0].z());
+	float d = (float)c.textures[0].texCube->getSize();
+	float s = d/(2.0f*m);
+	float t = d/(2.0f*m);
+	return computeLodFromDerivates(c.in[1].x()*s, c.in[1].y()*t, c.in[2].x()*s, c.in[2].y()*t);
+}
+
+typedef void (*TexEvalFunc) (gls::ShaderEvalContext& c, const TexLookupParams& lookupParams);
+
+inline Vec4 texture2D		(const gls::ShaderEvalContext& c, float s, float t, float lod)			{ return c.textures[0].tex2D->sample(c.textures[0].sampler, s, t, lod);			}
+inline Vec4 textureCube		(const gls::ShaderEvalContext& c, float s, float t, float r, float lod)	{ return c.textures[0].texCube->sample(c.textures[0].sampler, s, t, r, lod);	}
+inline Vec4 texture2DArray	(const gls::ShaderEvalContext& c, float s, float t, float r, float lod)	{ return c.textures[0].tex2DArray->sample(c.textures[0].sampler, s, t, r, lod);	}
+inline Vec4 texture3D		(const gls::ShaderEvalContext& c, float s, float t, float r, float lod)	{ return c.textures[0].tex3D->sample(c.textures[0].sampler, s, t, r, lod);		}
+
+inline float texture2DShadow		(const gls::ShaderEvalContext& c, float ref, float s, float t, float lod) { return c.textures[0].tex2D->sampleCompare(c.textures[0].sampler, ref, s, t, lod); }
+inline float textureCubeShadow		(const gls::ShaderEvalContext& c, float ref, float s, float t, float r, float lod) { return c.textures[0].texCube->sampleCompare(c.textures[0].sampler, ref, s, t, r, lod); }
+inline float texture2DArrayShadow	(const gls::ShaderEvalContext& c, float ref, float s, float t, float r, float lod) { return c.textures[0].tex2DArray->sampleCompare(c.textures[0].sampler, ref, s, t, r, lod); }
+
+inline Vec4 texture2DOffset			(const gls::ShaderEvalContext& c, float s, float t, float lod, IVec2 offset)			{ return c.textures[0].tex2D->sampleOffset(c.textures[0].sampler, s, t, lod, offset);			}
+inline Vec4 texture2DArrayOffset	(const gls::ShaderEvalContext& c, float s, float t, float r, float lod, IVec2 offset)	{ return c.textures[0].tex2DArray->sampleOffset(c.textures[0].sampler, s, t, r, lod, offset);	}
+inline Vec4 texture3DOffset			(const gls::ShaderEvalContext& c, float s, float t, float r, float lod, IVec3 offset)	{ return c.textures[0].tex3D->sampleOffset(c.textures[0].sampler, s, t, r, lod, offset);		}
+
+inline float texture2DShadowOffset		(const gls::ShaderEvalContext& c, float ref, float s, float t, float lod, IVec2 offset)	{ return c.textures[0].tex2D->sampleCompareOffset(c.textures[0].sampler, ref, s, t, lod, offset); }
+inline float texture2DArrayShadowOffset	(const gls::ShaderEvalContext& c, float ref, float s, float t, float r, float lod, IVec2 offset) { return c.textures[0].tex2DArray->sampleCompareOffset(c.textures[0].sampler, ref, s, t, r, lod, offset); }
+
+// Eval functions.
+static void		evalTexture2D			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x(), c.in[0].y(), p.lod)*p.scale + p.bias; }
+static void		evalTextureCube			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = textureCube(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod)*p.scale + p.bias; }
+static void		evalTexture2DArray		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DArray(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod)*p.scale + p.bias; }
+static void		evalTexture3D			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3D(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod)*p.scale + p.bias; }
+
+static void		evalTexture2DBias		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x(), c.in[0].y(), p.lod+c.in[1].x())*p.scale + p.bias; }
+static void		evalTextureCubeBias		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = textureCube(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod+c.in[1].x())*p.scale + p.bias; }
+static void		evalTexture2DArrayBias	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DArray(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod+c.in[1].x())*p.scale + p.bias; }
+static void		evalTexture3DBias		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3D(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod+c.in[1].x())*p.scale + p.bias; }
+
+static void		evalTexture2DProj3		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), p.lod)*p.scale + p.bias; }
+static void		evalTexture2DProj3Bias	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), p.lod+c.in[1].x())*p.scale + p.bias; }
+static void		evalTexture2DProj		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), p.lod)*p.scale + p.bias; }
+static void		evalTexture2DProjBias	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), p.lod+c.in[1].x())*p.scale + p.bias; }
+static void		evalTexture3DProj		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[0].z()/c.in[0].w(), p.lod)*p.scale + p.bias; }
+static void		evalTexture3DProjBias	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[0].z()/c.in[0].w(), p.lod+c.in[1].x())*p.scale + p.bias; }
+
+static void		evalTexture2DLod		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x(), c.in[0].y(), c.in[1].x())*p.scale + p.bias; }
+static void		evalTextureCubeLod		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = textureCube(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), c.in[1].x())*p.scale + p.bias; }
+static void		evalTexture2DArrayLod	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DArray(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), c.in[1].x())*p.scale + p.bias; }
+static void		evalTexture3DLod		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3D(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), c.in[1].x())*p.scale + p.bias; }
+
+static void		evalTexture2DProjLod3	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), c.in[1].x())*p.scale + p.bias; }
+static void		evalTexture2DProjLod	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[1].x())*p.scale + p.bias; }
+static void		evalTexture3DProjLod	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[0].z()/c.in[0].w(), c.in[1].x())*p.scale + p.bias; }
+
+// Offset variants
+
+static void		evalTexture2DOffset				(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x(), c.in[0].y(), p.lod, p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture2DArrayOffset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DArrayOffset(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod, p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture3DOffset				(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3DOffset(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod, p.offset)*p.scale + p.bias; }
+
+static void		evalTexture2DOffsetBias			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x(), c.in[0].y(), p.lod+c.in[1].x(), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture2DArrayOffsetBias	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DArrayOffset(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod+c.in[1].x(), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture3DOffsetBias			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3DOffset(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod+c.in[1].x(), p.offset)*p.scale + p.bias; }
+
+static void		evalTexture2DLodOffset			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x(), c.in[0].y(), c.in[1].x(), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture2DArrayLodOffset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DArrayOffset(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), c.in[1].x(), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture3DLodOffset			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3DOffset(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), c.in[1].x(), p.offset)*p.scale + p.bias; }
+
+static void		evalTexture2DProj3Offset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), p.lod, p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture2DProj3OffsetBias	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), p.lod+c.in[1].x(), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture2DProjOffset			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), p.lod, p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture2DProjOffsetBias		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), p.lod+c.in[1].x(), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture3DProjOffset			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3DOffset(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[0].z()/c.in[0].w(), p.lod, p.offset)*p.scale + p.bias; }
+static void		evalTexture3DProjOffsetBias		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3DOffset(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[0].z()/c.in[0].w(), p.lod+c.in[1].x(), p.offset)*p.scale + p.bias; }
+
+static void		evalTexture2DProjLod3Offset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), c.in[1].x(), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture2DProjLodOffset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[1].x(), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture3DProjLodOffset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3DOffset(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[0].z()/c.in[0].w(), c.in[1].x(), p.offset)*p.scale + p.bias; }
+
+// Shadow variants
+
+static void		evalTexture2DShadow				(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadow(c, c.in[0].z(), c.in[0].x(), c.in[0].y(), p.lod); }
+static void		evalTexture2DShadowBias			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadow(c, c.in[0].z(), c.in[0].x(), c.in[0].y(), p.lod+c.in[1].x()); }
+
+static void		evalTextureCubeShadow			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = textureCubeShadow(c, c.in[0].w(), c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod); }
+static void		evalTextureCubeShadowBias		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = textureCubeShadow(c, c.in[0].w(), c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod+c.in[1].x()); }
+
+static void		evalTexture2DArrayShadow		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DArrayShadow(c, c.in[0].w(), c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod); }
+
+static void		evalTexture2DShadowLod			(gls::ShaderEvalContext& c, const TexLookupParams&)		{ c.color.x() = texture2DShadow(c, c.in[0].z(), c.in[0].x(), c.in[0].y(), c.in[1].x()); }
+static void		evalTexture2DShadowLodOffset	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadowOffset(c, c.in[0].z(), c.in[0].x(), c.in[0].y(), c.in[1].x(), p.offset.swizzle(0,1)); }
+
+static void		evalTexture2DShadowProj			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadow(c, c.in[0].z()/c.in[0].w(), c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), p.lod); }
+static void		evalTexture2DShadowProjBias		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadow(c, c.in[0].z()/c.in[0].w(), c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), p.lod+c.in[1].x()); }
+
+static void		evalTexture2DShadowProjLod		(gls::ShaderEvalContext& c, const TexLookupParams&)		{ c.color.x() = texture2DShadow(c, c.in[0].z()/c.in[0].w(), c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[1].x()); }
+static void		evalTexture2DShadowProjLodOffset(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadowOffset(c, c.in[0].z()/c.in[0].w(), c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[1].x(), p.offset.swizzle(0,1)); }
+
+static void		evalTexture2DShadowOffset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadowOffset(c, c.in[0].z(), c.in[0].x(), c.in[0].y(), p.lod, p.offset.swizzle(0,1)); }
+static void		evalTexture2DShadowOffsetBias	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadowOffset(c, c.in[0].z(), c.in[0].x(), c.in[0].y(), p.lod+c.in[1].x(), p.offset.swizzle(0,1)); }
+
+static void		evalTexture2DShadowProjOffset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadowOffset(c, c.in[0].z()/c.in[0].w(), c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), p.lod, p.offset.swizzle(0,1)); }
+static void		evalTexture2DShadowProjOffsetBias	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadowOffset(c, c.in[0].z()/c.in[0].w(), c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), p.lod+c.in[1].x(), p.offset.swizzle(0,1)); }
+
+// Gradient variarts
+
+static void		evalTexture2DGrad		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x(), c.in[0].y(), computeLodFromGrad2D(c))*p.scale + p.bias; }
+static void		evalTextureCubeGrad		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = textureCube(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), computeLodFromGradCube(c))*p.scale + p.bias; }
+static void		evalTexture2DArrayGrad	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DArray(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), computeLodFromGrad2DArray(c))*p.scale + p.bias; }
+static void		evalTexture3DGrad		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3D(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), computeLodFromGrad3D(c))*p.scale + p.bias; }
+
+static void		evalTexture2DShadowGrad			(gls::ShaderEvalContext& c, const TexLookupParams&)		{ c.color.x() = texture2DShadow(c, c.in[0].z(), c.in[0].x(), c.in[0].y(), computeLodFromGrad2D(c)); }
+static void		evalTextureCubeShadowGrad		(gls::ShaderEvalContext& c, const TexLookupParams&)		{ c.color.x() = textureCubeShadow(c, c.in[0].w(), c.in[0].x(), c.in[0].y(), c.in[0].z(), computeLodFromGradCube(c)); }
+static void		evalTexture2DArrayShadowGrad	(gls::ShaderEvalContext& c, const TexLookupParams&)		{ c.color.x() = texture2DArrayShadow(c, c.in[0].w(), c.in[0].x(), c.in[0].y(), c.in[0].z(), computeLodFromGrad2DArray(c)); }
+
+static void		evalTexture2DGradOffset			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x(), c.in[0].y(), computeLodFromGrad2D(c), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture2DArrayGradOffset	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DArrayOffset(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), computeLodFromGrad2DArray(c), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture3DGradOffset			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3DOffset(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), computeLodFromGrad3D(c), p.offset)*p.scale + p.bias; }
+
+static void		evalTexture2DShadowGradOffset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadowOffset(c, c.in[0].z(), c.in[0].x(), c.in[0].y(), computeLodFromGrad2D(c), p.offset.swizzle(0,1)); }
+static void		evalTexture2DArrayShadowGradOffset	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DArrayShadowOffset(c, c.in[0].w(), c.in[0].x(), c.in[0].y(), c.in[0].z(), computeLodFromGrad2DArray(c), p.offset.swizzle(0,1)); }
+
+static void		evalTexture2DShadowProjGrad			(gls::ShaderEvalContext& c, const TexLookupParams&)		{ c.color.x() = texture2DShadow(c, c.in[0].z()/c.in[0].w(), c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), computeLodFromGrad2D(c)); }
+static void		evalTexture2DShadowProjGradOffset	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color.x() = texture2DShadowOffset(c, c.in[0].z()/c.in[0].w(), c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), computeLodFromGrad2D(c), p.offset.swizzle(0,1)); }
+
+static void		evalTexture2DProjGrad3			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), computeLodFromGrad2D(c))*p.scale + p.bias; }
+static void		evalTexture2DProjGrad			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), computeLodFromGrad2D(c))*p.scale + p.bias; }
+static void		evalTexture3DProjGrad			(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3D(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[0].z()/c.in[0].w(), computeLodFromGrad3D(c))*p.scale + p.bias; }
+
+static void		evalTexture2DProjGrad3Offset	(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x()/c.in[0].z(), c.in[0].y()/c.in[0].z(), computeLodFromGrad2D(c), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture2DProjGradOffset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture2DOffset(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), computeLodFromGrad2D(c), p.offset.swizzle(0,1))*p.scale + p.bias; }
+static void		evalTexture3DProjGradOffset		(gls::ShaderEvalContext& c, const TexLookupParams& p)	{ c.color = texture3DOffset(c, c.in[0].x()/c.in[0].w(), c.in[0].y()/c.in[0].w(), c.in[0].z()/c.in[0].w(), computeLodFromGrad3D(c), p.offset)*p.scale + p.bias; }
+
+// Texel fetch variants
+
+static void evalTexelFetch2D (gls::ShaderEvalContext& c, const TexLookupParams& p)
+{
+	int	x	= deChopFloatToInt32(c.in[0].x())+p.offset.x();
+	int	y	= deChopFloatToInt32(c.in[0].y())+p.offset.y();
+	int	lod = deChopFloatToInt32(c.in[1].x());
+	c.color = c.textures[0].tex2D->getLevel(lod).getPixel(x, y)*p.scale + p.bias;
+}
+
+static void evalTexelFetch2DArray (gls::ShaderEvalContext& c, const TexLookupParams& p)
+{
+	int	x	= deChopFloatToInt32(c.in[0].x())+p.offset.x();
+	int	y	= deChopFloatToInt32(c.in[0].y())+p.offset.y();
+	int	l	= deChopFloatToInt32(c.in[0].z());
+	int	lod = deChopFloatToInt32(c.in[1].x());
+	c.color = c.textures[0].tex2DArray->getLevel(lod).getPixel(x, y, l)*p.scale + p.bias;
+}
+
+static void evalTexelFetch3D (gls::ShaderEvalContext& c, const TexLookupParams& p)
+{
+	int	x	= deChopFloatToInt32(c.in[0].x())+p.offset.x();
+	int	y	= deChopFloatToInt32(c.in[0].y())+p.offset.y();
+	int	z	= deChopFloatToInt32(c.in[0].z())+p.offset.z();
+	int	lod = deChopFloatToInt32(c.in[1].x());
+	c.color = c.textures[0].tex3D->getLevel(lod).getPixel(x, y, z)*p.scale + p.bias;
+}
+
+class TexLookupEvaluator : public gls::ShaderEvaluator
+{
+public:
+							TexLookupEvaluator		(TexEvalFunc evalFunc, const TexLookupParams& lookupParams) : m_evalFunc(evalFunc), m_lookupParams(lookupParams) {}
+
+	virtual void			evaluate				(gls::ShaderEvalContext& ctx) { m_evalFunc(ctx, m_lookupParams); }
+
+private:
+	TexEvalFunc				m_evalFunc;
+	const TexLookupParams&	m_lookupParams;
+};
+
+class ShaderTextureFunctionCase : public gls::ShaderRenderCase
+{
+public:
+							ShaderTextureFunctionCase		(Context& context, const char* name, const char* desc, const TextureLookupSpec& lookup, const TextureSpec& texture, TexEvalFunc evalFunc, bool isVertexCase);
+							~ShaderTextureFunctionCase		(void);
+
+	void					init							(void);
+	void					deinit							(void);
+
+protected:
+	void					setupUniforms					(int programID, const tcu::Vec4& constCoords);
+
+private:
+	void					initTexture						(void);
+	void					initShaderSources				(void);
+
+	TextureLookupSpec		m_lookupSpec;
+	TextureSpec				m_textureSpec;
+
+	TexLookupParams			m_lookupParams;
+	TexLookupEvaluator		m_evaluator;
+
+	glu::Texture2D*			m_texture2D;
+	glu::TextureCube*		m_textureCube;
+	glu::Texture2DArray*	m_texture2DArray;
+	glu::Texture3D*			m_texture3D;
+};
+
+ShaderTextureFunctionCase::ShaderTextureFunctionCase (Context& context, const char* name, const char* desc, const TextureLookupSpec& lookup, const TextureSpec& texture, TexEvalFunc evalFunc, bool isVertexCase)
+	: gls::ShaderRenderCase(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, desc, isVertexCase, m_evaluator)
+	, m_lookupSpec			(lookup)
+	, m_textureSpec			(texture)
+	, m_evaluator			(evalFunc, m_lookupParams)
+	, m_texture2D			(DE_NULL)
+	, m_textureCube			(DE_NULL)
+	, m_texture2DArray		(DE_NULL)
+	, m_texture3D			(DE_NULL)
+{
+}
+
+ShaderTextureFunctionCase::~ShaderTextureFunctionCase (void)
+{
+	delete m_texture2D;
+	delete m_textureCube;
+	delete m_texture2DArray;
+	delete m_texture3D;
+}
+
+void ShaderTextureFunctionCase::init (void)
+{
+	{
+		// Base coord scale & bias
+		Vec4 s = m_lookupSpec.maxCoord-m_lookupSpec.minCoord;
+		Vec4 b = m_lookupSpec.minCoord;
+
+		float baseCoordTrans[] =
+		{
+			s.x(),		0.0f,		0.f,	b.x(),
+			0.f,		s.y(),		0.f,	b.y(),
+			s.z()/2.f,	-s.z()/2.f,	0.f,	s.z()/2.f + b.z(),
+			-s.w()/2.f,	s.w()/2.f,	0.f,	s.w()/2.f + b.w()
+		};
+
+		m_userAttribTransforms.push_back(tcu::Mat4(baseCoordTrans));
+	}
+
+	bool hasLodBias	= functionHasLod(m_lookupSpec.function) || m_lookupSpec.useBias;
+	bool isGrad		= functionHasGrad(m_lookupSpec.function);
+	DE_ASSERT(!isGrad || !hasLodBias);
+
+	if (hasLodBias)
+	{
+		float s = m_lookupSpec.maxLodBias-m_lookupSpec.minLodBias;
+		float b = m_lookupSpec.minLodBias;
+		float lodCoordTrans[] =
+		{
+			s/2.0f,		s/2.0f,		0.f,	b,
+			0.0f,		0.0f,		0.0f,	0.0f,
+			0.0f,		0.0f,		0.0f,	0.0f,
+			0.0f,		0.0f,		0.0f,	0.0f
+		};
+
+		m_userAttribTransforms.push_back(tcu::Mat4(lodCoordTrans));
+	}
+	else if (isGrad)
+	{
+		Vec3 sx = m_lookupSpec.maxDX-m_lookupSpec.minDX;
+		Vec3 sy = m_lookupSpec.maxDY-m_lookupSpec.minDY;
+		float gradDxTrans[] =
+		{
+			sx.x()/2.0f,	sx.x()/2.0f,	0.f,	m_lookupSpec.minDX.x(),
+			sx.y()/2.0f,	sx.y()/2.0f,	0.0f,	m_lookupSpec.minDX.y(),
+			sx.z()/2.0f,	sx.z()/2.0f,	0.0f,	m_lookupSpec.minDX.z(),
+			0.0f,			0.0f,			0.0f,	0.0f
+		};
+		float gradDyTrans[] =
+		{
+			-sy.x()/2.0f,	-sy.x()/2.0f,	0.f,	m_lookupSpec.maxDY.x(),
+			-sy.y()/2.0f,	-sy.y()/2.0f,	0.0f,	m_lookupSpec.maxDY.y(),
+			-sy.z()/2.0f,	-sy.z()/2.0f,	0.0f,	m_lookupSpec.maxDY.z(),
+			0.0f,			0.0f,			0.0f,	0.0f
+		};
+
+		m_userAttribTransforms.push_back(tcu::Mat4(gradDxTrans));
+		m_userAttribTransforms.push_back(tcu::Mat4(gradDyTrans));
+	}
+
+	initShaderSources();
+	initTexture();
+
+	gls::ShaderRenderCase::init();
+}
+
+void ShaderTextureFunctionCase::initTexture (void)
+{
+	static const IVec4 texCubeSwz[] =
+	{
+		IVec4(0,0,1,1),
+		IVec4(1,1,0,0),
+		IVec4(0,1,0,1),
+		IVec4(1,0,1,0),
+		IVec4(0,1,1,0),
+		IVec4(1,0,0,1)
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(texCubeSwz) == tcu::CUBEFACE_LAST);
+
+	tcu::TextureFormat		texFmt			= glu::mapGLInternalFormat(m_textureSpec.format);
+	tcu::TextureFormatInfo	fmtInfo			= tcu::getTextureFormatInfo(texFmt);
+	tcu::IVec2				viewportSize	= getViewportSize();
+	bool					isProj			= functionHasProj(m_lookupSpec.function);
+	bool					isAutoLod		= functionHasAutoLod(m_isVertexCase ? glu::SHADERTYPE_VERTEX : glu::SHADERTYPE_FRAGMENT,
+																 m_lookupSpec.function); // LOD can vary significantly
+	float					proj			= isProj ? 1.0f/m_lookupSpec.minCoord[m_lookupSpec.function == FUNCTION_TEXTUREPROJ3 ? 2 : 3] : 1.0f;
+
+	switch (m_textureSpec.type)
+	{
+		case TEXTURETYPE_2D:
+		{
+			float	levelStep		= isAutoLod ? 0.0f : 1.0f / (float)de::max(1, m_textureSpec.numLevels-1);
+			Vec4	cScale			= fmtInfo.valueMax-fmtInfo.valueMin;
+			Vec4	cBias			= fmtInfo.valueMin;
+			int		baseCellSize	= de::min(m_textureSpec.width/4, m_textureSpec.height/4);
+
+			m_texture2D = new glu::Texture2D(m_renderCtx, m_textureSpec.format, m_textureSpec.width, m_textureSpec.height);
+			for (int level = 0; level < m_textureSpec.numLevels; level++)
+			{
+				float	fA		= level*levelStep;
+				float	fB		= 1.0f-fA;
+				Vec4	colorA	= cBias + cScale*Vec4(fA, fB, fA, fB);
+				Vec4	colorB	= cBias + cScale*Vec4(fB, fA, fB, fA);
+
+				m_texture2D->getRefTexture().allocLevel(level);
+				tcu::fillWithGrid(m_texture2D->getRefTexture().getLevel(level), de::max(1, baseCellSize>>level), colorA, colorB);
+			}
+			m_texture2D->upload();
+
+			// Compute LOD.
+			float dudx = (m_lookupSpec.maxCoord[0]-m_lookupSpec.minCoord[0])*proj*m_textureSpec.width	/ (float)viewportSize[0];
+			float dvdy = (m_lookupSpec.maxCoord[1]-m_lookupSpec.minCoord[1])*proj*m_textureSpec.height	/ (float)viewportSize[1];
+			m_lookupParams.lod = computeLodFromDerivates(dudx, 0.0f, 0.0f, dvdy);
+
+			// Append to texture list.
+			m_textures.push_back(gls::TextureBinding(m_texture2D, m_textureSpec.sampler));
+			break;
+		}
+
+		case TEXTURETYPE_CUBE_MAP:
+		{
+			float	levelStep		= isAutoLod ? 0.0f : 1.0f / (float)de::max(1, m_textureSpec.numLevels-1);
+			Vec4	cScale			= fmtInfo.valueMax-fmtInfo.valueMin;
+			Vec4	cBias			= fmtInfo.valueMin;
+			int		baseCellSize	= de::min(m_textureSpec.width/4, m_textureSpec.height/4);
+
+			DE_ASSERT(m_textureSpec.width == m_textureSpec.height);
+			m_textureCube = new glu::TextureCube(m_renderCtx, m_textureSpec.format, m_textureSpec.width);
+			for (int level = 0; level < m_textureSpec.numLevels; level++)
+			{
+				float	fA		= level*levelStep;
+				float	fB		= 1.0f-fA;
+				Vec2	f		(fA, fB);
+
+				for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+				{
+					const IVec4&	swzA	= texCubeSwz[face];
+					IVec4			swzB	= 1-swzA;
+					Vec4			colorA	= cBias + cScale*f.swizzle(swzA[0], swzA[1], swzA[2], swzA[3]);
+					Vec4			colorB	= cBias + cScale*f.swizzle(swzB[0], swzB[1], swzB[2], swzB[3]);
+
+					m_textureCube->getRefTexture().allocLevel((tcu::CubeFace)face, level);
+					tcu::fillWithGrid(m_textureCube->getRefTexture().getLevelFace(level, (tcu::CubeFace)face), de::max(1, baseCellSize>>level), colorA, colorB);
+				}
+			}
+			m_textureCube->upload();
+
+			// Compute LOD \note Assumes that only single side is accessed and R is constant major axis.
+			DE_ASSERT(de::abs(m_lookupSpec.minCoord[2] - m_lookupSpec.maxCoord[2]) < 0.005);
+			DE_ASSERT(de::abs(m_lookupSpec.minCoord[0]) < de::abs(m_lookupSpec.minCoord[2]) && de::abs(m_lookupSpec.maxCoord[0]) < de::abs(m_lookupSpec.minCoord[2]));
+			DE_ASSERT(de::abs(m_lookupSpec.minCoord[1]) < de::abs(m_lookupSpec.minCoord[2]) && de::abs(m_lookupSpec.maxCoord[1]) < de::abs(m_lookupSpec.minCoord[2]));
+
+			tcu::CubeFaceFloatCoords	c00		= tcu::getCubeFaceCoords(Vec3(m_lookupSpec.minCoord[0]*proj, m_lookupSpec.minCoord[1]*proj, m_lookupSpec.minCoord[2]*proj));
+			tcu::CubeFaceFloatCoords	c10		= tcu::getCubeFaceCoords(Vec3(m_lookupSpec.maxCoord[0]*proj, m_lookupSpec.minCoord[1]*proj, m_lookupSpec.minCoord[2]*proj));
+			tcu::CubeFaceFloatCoords	c01		= tcu::getCubeFaceCoords(Vec3(m_lookupSpec.minCoord[0]*proj, m_lookupSpec.maxCoord[1]*proj, m_lookupSpec.minCoord[2]*proj));
+			float						dudx	= (c10.s - c00.s)*m_textureSpec.width	/ (float)viewportSize[0];
+			float						dvdy	= (c01.t - c00.t)*m_textureSpec.height	/ (float)viewportSize[1];
+
+			m_lookupParams.lod = computeLodFromDerivates(dudx, 0.0f, 0.0f, dvdy);
+
+			m_textures.push_back(gls::TextureBinding(m_textureCube, m_textureSpec.sampler));
+			break;
+		}
+
+		case TEXTURETYPE_2D_ARRAY:
+		{
+			float	layerStep		= 1.0f / (float)m_textureSpec.depth;
+			float	levelStep		= isAutoLod ? 0.0f : 1.0f / (float)(de::max(1, m_textureSpec.numLevels-1)*m_textureSpec.depth);
+			Vec4	cScale			= fmtInfo.valueMax-fmtInfo.valueMin;
+			Vec4	cBias			= fmtInfo.valueMin;
+			int		baseCellSize	= de::min(m_textureSpec.width/4, m_textureSpec.height/4);
+
+			m_texture2DArray = new glu::Texture2DArray(m_renderCtx, m_textureSpec.format, m_textureSpec.width, m_textureSpec.height, m_textureSpec.depth);
+			for (int level = 0; level < m_textureSpec.numLevels; level++)
+			{
+				m_texture2DArray->getRefTexture().allocLevel(level);
+				tcu::PixelBufferAccess levelAccess = m_texture2DArray->getRefTexture().getLevel(level);
+
+				for (int layer = 0; layer < levelAccess.getDepth(); layer++)
+				{
+					float	fA		= layer*layerStep + level*levelStep;
+					float	fB		= 1.0f-fA;
+					Vec4	colorA	= cBias + cScale*Vec4(fA, fB, fA, fB);
+					Vec4	colorB	= cBias + cScale*Vec4(fB, fA, fB, fA);
+
+					tcu::fillWithGrid(tcu::getSubregion(levelAccess, 0, 0, layer, levelAccess.getWidth(), levelAccess.getHeight(), 1), de::max(1, baseCellSize>>level), colorA, colorB);
+				}
+			}
+			m_texture2DArray->upload();
+
+			// Compute LOD.
+			float dudx = (m_lookupSpec.maxCoord[0]-m_lookupSpec.minCoord[0])*proj*m_textureSpec.width	/ (float)viewportSize[0];
+			float dvdy = (m_lookupSpec.maxCoord[1]-m_lookupSpec.minCoord[1])*proj*m_textureSpec.height	/ (float)viewportSize[1];
+			m_lookupParams.lod = computeLodFromDerivates(dudx, 0.0f, 0.0f, dvdy);
+
+			// Append to texture list.
+			m_textures.push_back(gls::TextureBinding(m_texture2DArray, m_textureSpec.sampler));
+			break;
+		}
+
+		case TEXTURETYPE_3D:
+		{
+			float	levelStep		= isAutoLod ? 0.0f : 1.0f / (float)de::max(1, m_textureSpec.numLevels-1);
+			Vec4	cScale			= fmtInfo.valueMax-fmtInfo.valueMin;
+			Vec4	cBias			= fmtInfo.valueMin;
+			int		baseCellSize	= de::min(de::min(m_textureSpec.width/2, m_textureSpec.height/2), m_textureSpec.depth/2);
+
+			m_texture3D = new glu::Texture3D(m_renderCtx, m_textureSpec.format, m_textureSpec.width, m_textureSpec.height, m_textureSpec.depth);
+			for (int level = 0; level < m_textureSpec.numLevels; level++)
+			{
+				float	fA		= level*levelStep;
+				float	fB		= 1.0f-fA;
+				Vec4	colorA	= cBias + cScale*Vec4(fA, fB, fA, fB);
+				Vec4	colorB	= cBias + cScale*Vec4(fB, fA, fB, fA);
+
+				m_texture3D->getRefTexture().allocLevel(level);
+				tcu::fillWithGrid(m_texture3D->getRefTexture().getLevel(level), de::max(1, baseCellSize>>level), colorA, colorB);
+			}
+			m_texture3D->upload();
+
+			// Compute LOD.
+			float dudx = (m_lookupSpec.maxCoord[0]-m_lookupSpec.minCoord[0])*proj*m_textureSpec.width		/ (float)viewportSize[0];
+			float dvdy = (m_lookupSpec.maxCoord[1]-m_lookupSpec.minCoord[1])*proj*m_textureSpec.height		/ (float)viewportSize[1];
+			float dwdx = (m_lookupSpec.maxCoord[2]-m_lookupSpec.minCoord[2])*0.5f*proj*m_textureSpec.depth	/ (float)viewportSize[0];
+			float dwdy = (m_lookupSpec.maxCoord[2]-m_lookupSpec.minCoord[2])*0.5f*proj*m_textureSpec.depth	/ (float)viewportSize[1];
+			m_lookupParams.lod = computeLodFromDerivates(dudx, 0.0f, dwdx, 0.0f, dvdy, dwdy);
+
+			// Append to texture list.
+			m_textures.push_back(gls::TextureBinding(m_texture3D, m_textureSpec.sampler));
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// Set lookup scale & bias
+	m_lookupParams.scale	= fmtInfo.lookupScale;
+	m_lookupParams.bias		= fmtInfo.lookupBias;
+	m_lookupParams.offset	= m_lookupSpec.offset;
+}
+
+void ShaderTextureFunctionCase::initShaderSources (void)
+{
+	Function			function			= m_lookupSpec.function;
+	bool				isVtxCase			= m_isVertexCase;
+	bool				isProj				= functionHasProj(function);
+	bool				isGrad				= functionHasGrad(function);
+	bool				isShadow			= m_textureSpec.sampler.compare != tcu::Sampler::COMPAREMODE_NONE;
+	bool				is2DProj4			= !isShadow && m_textureSpec.type == TEXTURETYPE_2D && (function == FUNCTION_TEXTUREPROJ || function == FUNCTION_TEXTUREPROJLOD || function == FUNCTION_TEXTUREPROJGRAD);
+	bool				isIntCoord			= function == FUNCTION_TEXELFETCH;
+	bool				hasLodBias			= functionHasLod(m_lookupSpec.function) || m_lookupSpec.useBias;
+	int					texCoordComps		= m_textureSpec.type == TEXTURETYPE_2D ? 2 : 3;
+	int					extraCoordComps		= (isProj ? (is2DProj4 ? 2 : 1) : 0) + (isShadow ? 1 : 0);
+	glu::DataType		coordType			= glu::getDataTypeFloatVec(texCoordComps+extraCoordComps);
+	glu::Precision		coordPrec			= glu::PRECISION_HIGHP;
+	const char*			coordTypeName		= glu::getDataTypeName(coordType);
+	const char*			coordPrecName		= glu::getPrecisionName(coordPrec);
+	tcu::TextureFormat	texFmt				= glu::mapGLInternalFormat(m_textureSpec.format);
+	glu::DataType		samplerType			= glu::TYPE_LAST;
+	glu::DataType		gradType			= (m_textureSpec.type == TEXTURETYPE_CUBE_MAP || m_textureSpec.type == TEXTURETYPE_3D) ? glu::TYPE_FLOAT_VEC3 : glu::TYPE_FLOAT_VEC2;
+	const char*			gradTypeName		= glu::getDataTypeName(gradType);
+	const char*			baseFuncName		= DE_NULL;
+
+	DE_ASSERT(!isGrad || !hasLodBias);
+
+	switch (m_textureSpec.type)
+	{
+		case TEXTURETYPE_2D:		samplerType = isShadow ? glu::TYPE_SAMPLER_2D_SHADOW		: glu::getSampler2DType(texFmt);		break;
+		case TEXTURETYPE_CUBE_MAP:	samplerType = isShadow ? glu::TYPE_SAMPLER_CUBE_SHADOW		: glu::getSamplerCubeType(texFmt);		break;
+		case TEXTURETYPE_2D_ARRAY:	samplerType = isShadow ? glu::TYPE_SAMPLER_2D_ARRAY_SHADOW	: glu::getSampler2DArrayType(texFmt);	break;
+		case TEXTURETYPE_3D:		DE_ASSERT(!isShadow); samplerType = glu::getSampler3DType(texFmt);									break;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	switch (m_lookupSpec.function)
+	{
+		case FUNCTION_TEXTURE:			baseFuncName = "texture";			break;
+		case FUNCTION_TEXTUREPROJ:		baseFuncName = "textureProj";		break;
+		case FUNCTION_TEXTUREPROJ3:		baseFuncName = "textureProj";		break;
+		case FUNCTION_TEXTURELOD:		baseFuncName = "textureLod";		break;
+		case FUNCTION_TEXTUREPROJLOD:	baseFuncName = "textureProjLod";	break;
+		case FUNCTION_TEXTUREPROJLOD3:	baseFuncName = "textureProjLod";	break;
+		case FUNCTION_TEXTUREGRAD:		baseFuncName = "textureGrad";		break;
+		case FUNCTION_TEXTUREPROJGRAD:	baseFuncName = "textureProjGrad";	break;
+		case FUNCTION_TEXTUREPROJGRAD3:	baseFuncName = "textureProjGrad";	break;
+		case FUNCTION_TEXELFETCH:		baseFuncName = "texelFetch";		break;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	std::ostringstream	vert;
+	std::ostringstream	frag;
+	std::ostringstream&	op		= isVtxCase ? vert : frag;
+
+	vert << "#version 300 es\n"
+		 << "in highp vec4 a_position;\n"
+		 << "in " << coordPrecName << " " << coordTypeName << " a_in0;\n";
+
+	if (isGrad)
+	{
+		vert << "in " << coordPrecName << " " << gradTypeName << " a_in1;\n";
+		vert << "in " << coordPrecName << " " << gradTypeName << " a_in2;\n";
+	}
+	else if (hasLodBias)
+		vert << "in " << coordPrecName << " float a_in1;\n";
+
+	frag << "#version 300 es\n"
+		 << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (isVtxCase)
+	{
+		vert << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vert << "out " << coordPrecName << " " << coordTypeName << " v_texCoord;\n";
+		frag << "in " << coordPrecName << " " << coordTypeName << " v_texCoord;\n";
+
+		if (isGrad)
+		{
+			vert << "out " << coordPrecName << " " << gradTypeName << " v_gradX;\n";
+			vert << "out " << coordPrecName << " " << gradTypeName << " v_gradY;\n";
+			frag << "in " << coordPrecName << " " << gradTypeName << " v_gradX;\n";
+			frag << "in " << coordPrecName << " " << gradTypeName << " v_gradY;\n";
+		}
+
+		if (hasLodBias)
+		{
+			vert << "out " << coordPrecName << " float v_lodBias;\n";
+			frag << "in " << coordPrecName << " float v_lodBias;\n";
+		}
+	}
+
+	// Uniforms
+	op << "uniform highp " << glu::getDataTypeName(samplerType) << " u_sampler;\n"
+	   << "uniform highp vec4 u_scale;\n"
+	   << "uniform highp vec4 u_bias;\n";
+
+	vert << "\nvoid main()\n{\n"
+		 << "\tgl_Position = a_position;\n";
+	frag << "\nvoid main()\n{\n";
+
+	if (isVtxCase)
+		vert << "\tv_color = ";
+	else
+		frag << "\to_color = ";
+
+	// Op.
+	{
+		const char*	texCoord	= isVtxCase ? "a_in0" : "v_texCoord";
+		const char* gradX		= isVtxCase ? "a_in1" : "v_gradX";
+		const char* gradY		= isVtxCase ? "a_in2" : "v_gradY";
+		const char*	lodBias		= isVtxCase ? "a_in1" : "v_lodBias";
+
+		op << "vec4(" << baseFuncName;
+		if (m_lookupSpec.useOffset)
+			op << "Offset";
+		op << "(u_sampler, ";
+
+		if (isIntCoord)
+			op << "ivec" << (texCoordComps+extraCoordComps) << "(";
+
+		op << texCoord;
+
+		if (isIntCoord)
+			op << ")";
+
+		if (isGrad)
+			op << ", " << gradX << ", " << gradY;
+
+		if (functionHasLod(function))
+		{
+			if (isIntCoord)
+				op << ", int(" << lodBias << ")";
+			else
+				op << ", " << lodBias;
+		}
+
+		if (m_lookupSpec.useOffset)
+		{
+			int offsetComps = m_textureSpec.type == TEXTURETYPE_3D ? 3 : 2;
+
+			op << ", ivec" << offsetComps << "(";
+			for (int ndx = 0; ndx < offsetComps; ndx++)
+			{
+				if (ndx != 0)
+					op << ", ";
+				op << m_lookupSpec.offset[ndx];
+			}
+			op << ")";
+		}
+
+		if (m_lookupSpec.useBias)
+			op << ", " << lodBias;
+
+		op << ")";
+
+		if (isShadow)
+			op << ", 0.0, 0.0, 1.0)";
+		else
+			op << ")*u_scale + u_bias";
+
+		op << ";\n";
+	}
+
+	if (isVtxCase)
+		frag << "\to_color = v_color;\n";
+	else
+	{
+		vert << "\tv_texCoord = a_in0;\n";
+
+		if (isGrad)
+		{
+			vert << "\tv_gradX = a_in1;\n";
+			vert << "\tv_gradY = a_in2;\n";
+		}
+		else if (hasLodBias)
+			vert << "\tv_lodBias = a_in1;\n";
+	}
+
+	vert << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource = vert.str();
+	m_fragShaderSource = frag.str();
+}
+
+void ShaderTextureFunctionCase::deinit (void)
+{
+	gls::ShaderRenderCase::deinit();
+
+	delete m_texture2D;
+	delete m_textureCube;
+	delete m_texture2DArray;
+	delete m_texture3D;
+
+	m_texture2D			= DE_NULL;
+	m_textureCube		= DE_NULL;
+	m_texture2DArray	= DE_NULL;
+	m_texture3D			= DE_NULL;
+}
+
+void ShaderTextureFunctionCase::setupUniforms (int programID, const tcu::Vec4&)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.uniform1i(gl.getUniformLocation(programID, "u_sampler"),	0);
+	gl.uniform4fv(gl.getUniformLocation(programID, "u_scale"),	1, m_lookupParams.scale.getPtr());
+	gl.uniform4fv(gl.getUniformLocation(programID, "u_bias"),	1, m_lookupParams.bias.getPtr());
+}
+
+class TextureSizeCase : public TestCase
+{
+public:
+						TextureSizeCase		(Context& context, const char* name, const char* desc, const char* samplerType, const TextureSpec& texture, bool isVertexCase);
+						~TextureSizeCase	(void);
+
+	void				deinit				(void);
+	IterateResult		iterate				(void);
+
+private:
+	struct TestSize
+	{
+		tcu::IVec3	textureSize;
+		int			lod;
+		int			lodBase;
+		tcu::IVec3	expectedSize;
+	};
+
+	bool				initShader			(void);
+	void				freeShader			(void);
+	bool				testTextureSize		(const TestSize&);
+	std::string			genVertexShader		(void) const;
+	std::string			genFragmentShader	(void) const;
+	glw::GLenum			getGLTextureTarget	(void) const;
+
+	const char*			m_samplerTypeStr;
+	const TextureSpec	m_textureSpec;
+	const bool			m_isVertexCase;
+	const bool			m_has3DSize;
+	glu::ShaderProgram*	m_program;
+	int					m_iterationCounter;
+};
+
+TextureSizeCase::TextureSizeCase (Context& context, const char* name, const char* desc, const char* samplerType, const TextureSpec& texture, bool isVertexCase)
+	: TestCase			(context, name, desc)
+	, m_samplerTypeStr	(samplerType)
+	, m_textureSpec		(texture)
+	, m_isVertexCase	(isVertexCase)
+	, m_has3DSize		(texture.type == TEXTURETYPE_3D || texture.type == TEXTURETYPE_2D_ARRAY)
+	, m_program			(DE_NULL)
+	, m_iterationCounter(0)
+{
+}
+
+TextureSizeCase::~TextureSizeCase (void)
+{
+	deinit();
+}
+
+void TextureSizeCase::deinit (void)
+{
+	freeShader();
+}
+
+TestCase::IterateResult TextureSizeCase::iterate (void)
+{
+	const int currentIteration = m_iterationCounter++;
+	const TestSize testSizes[] =
+	{
+		{ tcu::IVec3(1, 2, 1),			1,		0,	tcu::IVec3(1, 1, 1)			},
+		{ tcu::IVec3(1, 2, 1),			0,		0,	tcu::IVec3(1, 2, 1)			},
+
+		{ tcu::IVec3(1, 3, 2),			0,		0,	tcu::IVec3(1, 3, 2)			},
+		{ tcu::IVec3(1, 3, 2),			1,		0,	tcu::IVec3(1, 1, 1)			},
+
+		{ tcu::IVec3(100, 31, 18),		0,		0,	tcu::IVec3(100, 31, 18)		},
+		{ tcu::IVec3(100, 31, 18),		1,		0,	tcu::IVec3(50, 15, 9)		},
+		{ tcu::IVec3(100, 31, 18),		2,		0,	tcu::IVec3(25, 7, 4)		},
+		{ tcu::IVec3(100, 31, 18),		3,		0,	tcu::IVec3(12, 3, 2)		},
+		{ tcu::IVec3(100, 31, 18),		4,		0,	tcu::IVec3(6, 1, 1)			},
+		{ tcu::IVec3(100, 31, 18),		5,		0,	tcu::IVec3(3, 1, 1)			},
+		{ tcu::IVec3(100, 31, 18),		6,		0,	tcu::IVec3(1, 1, 1)			},
+
+		{ tcu::IVec3(100, 128, 32),		0,		0,	tcu::IVec3(100, 128, 32)	},
+		{ tcu::IVec3(100, 128, 32),		1,		0,	tcu::IVec3(50, 64, 16)		},
+		{ tcu::IVec3(100, 128, 32),		2,		0,	tcu::IVec3(25, 32, 8)		},
+		{ tcu::IVec3(100, 128, 32),		3,		0,	tcu::IVec3(12, 16, 4)		},
+		{ tcu::IVec3(100, 128, 32),		4,		0,	tcu::IVec3(6, 8, 2)			},
+		{ tcu::IVec3(100, 128, 32),		5,		0,	tcu::IVec3(3, 4, 1)			},
+		{ tcu::IVec3(100, 128, 32),		6,		0,	tcu::IVec3(1, 2, 1)			},
+		{ tcu::IVec3(100, 128, 32),		7,		0,	tcu::IVec3(1, 1, 1)			},
+
+		// pow 2
+		{ tcu::IVec3(128, 64, 32),		0,		0,	tcu::IVec3(128, 64, 32)		},
+		{ tcu::IVec3(128, 64, 32),		1,		0,	tcu::IVec3(64, 32, 16)		},
+		{ tcu::IVec3(128, 64, 32),		2,		0,	tcu::IVec3(32, 16, 8)		},
+		{ tcu::IVec3(128, 64, 32),		3,		0,	tcu::IVec3(16, 8, 4)		},
+		{ tcu::IVec3(128, 64, 32),		4,		0,	tcu::IVec3(8, 4, 2)			},
+		{ tcu::IVec3(128, 64, 32),		5,		0,	tcu::IVec3(4, 2, 1)			},
+		{ tcu::IVec3(128, 64, 32),		6,		0,	tcu::IVec3(2, 1, 1)			},
+		{ tcu::IVec3(128, 64, 32),		7,		0,	tcu::IVec3(1, 1, 1)			},
+
+		// w == h
+		{ tcu::IVec3(1, 1, 1),			0,		0,	tcu::IVec3(1, 1, 1)			},
+		{ tcu::IVec3(64, 64, 64),		0,		0,	tcu::IVec3(64, 64, 64)		},
+		{ tcu::IVec3(64, 64, 64),		1,		0,	tcu::IVec3(32, 32, 32)		},
+		{ tcu::IVec3(64, 64, 64),		2,		0,	tcu::IVec3(16, 16, 16)		},
+		{ tcu::IVec3(64, 64, 64),		3,		0,	tcu::IVec3(8, 8, 8)			},
+		{ tcu::IVec3(64, 64, 64),		4,		0,	tcu::IVec3(4, 4, 4)			},
+
+		// with lod base
+		{ tcu::IVec3(100, 31, 18),		3,		1,	tcu::IVec3(6, 1, 1)			},
+		{ tcu::IVec3(128, 64, 32),		3,		1,	tcu::IVec3(8, 4, 2)			},
+		{ tcu::IVec3(64, 64, 64),		1,		1,	tcu::IVec3(16, 16, 16)		},
+
+	};
+	const int lastIterationIndex = DE_LENGTH_OF_ARRAY(testSizes) + 1;
+
+	if (currentIteration == 0)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return initShader() ? CONTINUE : STOP;
+	}
+	else if (currentIteration == lastIterationIndex)
+	{
+		freeShader();
+		return STOP;
+	}
+	else
+	{
+		if (!testTextureSize(testSizes[currentIteration - 1]))
+			if (m_testCtx.getTestResult() != QP_TEST_RESULT_FAIL)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected texture size");
+		return CONTINUE;
+	}
+}
+
+bool TextureSizeCase::initShader (void)
+{
+	const std::string		vertSrc = genVertexShader();
+	const std::string		fragSrc = genFragmentShader();
+
+	DE_ASSERT(m_program == DE_NULL);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertSrc, fragSrc));
+	m_context.getTestContext().getLog() << *m_program;
+
+	if (!m_program->isOk())
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader failed");
+		return false;
+	}
+
+	return true;
+}
+
+void TextureSizeCase::freeShader (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+bool TextureSizeCase::testTextureSize (const TestSize& testSize)
+{
+	using tcu::TestLog;
+
+	const tcu::Vec4 triangle[3] = // covers entire viewport
+	{
+		tcu::Vec4(-1, -1, 0, 1),
+		tcu::Vec4( 4, -1, 0, 1),
+		tcu::Vec4(-1,  4, 0, 1),
+	};
+
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+
+	const glw::GLint		positionLoc		= gl.getAttribLocation	(m_program->getProgram(), "a_position");
+	const glw::GLint		samplerLoc		= gl.getUniformLocation	(m_program->getProgram(), "u_sampler");
+	const glw::GLint		sizeLoc			= gl.getUniformLocation	(m_program->getProgram(), "u_texSize");
+	const glw::GLint		lodLoc			= gl.getUniformLocation	(m_program->getProgram(), "u_lod");
+	const glw::GLenum		textureTarget	= getGLTextureTarget	();
+	const bool				isSquare		= testSize.textureSize.x() == testSize.textureSize.y();
+	const bool				is2DLodValid	= (testSize.textureSize.x() >> (testSize.lod + testSize.lodBase)) != 0 || (testSize.textureSize.y() >> (testSize.lod + testSize.lodBase)) != 0;
+	bool					success			= true;
+	glw::GLenum				errorValue;
+
+	// Skip incompatible cases
+	if (m_textureSpec.type == TEXTURETYPE_CUBE_MAP && !isSquare)
+		return true;
+	if (m_textureSpec.type == TEXTURETYPE_2D && !is2DLodValid)
+		return true;
+	if (m_textureSpec.type == TEXTURETYPE_2D_ARRAY && !is2DLodValid)
+		return true;
+
+	// setup rendering
+
+	gl.useProgram				(m_program->getProgram());
+	gl.uniform1i				(samplerLoc, 0);
+	gl.clearColor				(0.5f, 0.5f, 0.5f, 1.0f);
+	gl.viewport					(0, 0, 1, 1);
+	gl.vertexAttribPointer		(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, triangle);
+	gl.enableVertexAttribArray	(positionLoc);
+
+	// setup texture
+	{
+		const int	maxLevel	= testSize.lod + testSize.lodBase;
+		const int	levels		= maxLevel + 1;
+		glw::GLuint texId		= 0;
+
+		// gen texture
+		gl.genTextures(1, &texId);
+		gl.bindTexture(textureTarget, texId);
+		gl.texParameteri(textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		gl.texParameteri(textureTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		gl.texParameteri(textureTarget, GL_TEXTURE_BASE_LEVEL, testSize.lodBase);
+
+		// set up texture
+
+		switch (m_textureSpec.type)
+		{
+			case TEXTURETYPE_3D:
+			{
+				m_context.getTestContext().getLog() << TestLog::Message << "Testing image size " << testSize.textureSize.x() << "x" << testSize.textureSize.y() << "x" << testSize.textureSize.z() << TestLog::EndMessage;
+				m_context.getTestContext().getLog() << TestLog::Message << "Lod: " << testSize.lod << ", base level: " << testSize.lodBase << TestLog::EndMessage;
+				m_context.getTestContext().getLog() << TestLog::Message << "Expecting: " << testSize.expectedSize.x() << "x" << testSize.expectedSize.y() << "x" << testSize.expectedSize.z() << TestLog::EndMessage;
+
+				gl.uniform3iv(sizeLoc, 1, testSize.expectedSize.m_data);
+				gl.uniform1iv(lodLoc,  1, &testSize.lod);
+
+				gl.texStorage3D(textureTarget, levels, m_textureSpec.format, testSize.textureSize.x(), testSize.textureSize.y(), testSize.textureSize.z());
+				break;
+			}
+
+			case TEXTURETYPE_2D:
+			case TEXTURETYPE_CUBE_MAP:
+			{
+				m_context.getTestContext().getLog() << TestLog::Message << "Testing image size " << testSize.textureSize.x() << "x" << testSize.textureSize.y() << TestLog::EndMessage;
+				m_context.getTestContext().getLog() << TestLog::Message << "Lod: " << testSize.lod << ", base level: " << testSize.lodBase << TestLog::EndMessage;
+				m_context.getTestContext().getLog() << TestLog::Message << "Expecting: " << testSize.expectedSize.x() << "x" << testSize.expectedSize.y() << TestLog::EndMessage;
+
+				gl.uniform2iv(sizeLoc, 1, testSize.expectedSize.m_data);
+				gl.uniform1iv(lodLoc,  1, &testSize.lod);
+
+				gl.texStorage2D(textureTarget, levels, m_textureSpec.format, testSize.textureSize.x(), testSize.textureSize.y());
+				break;
+			}
+
+			case TEXTURETYPE_2D_ARRAY:
+			{
+				tcu::IVec3 expectedSize(testSize.expectedSize.x(), testSize.expectedSize.y(), testSize.textureSize.z());
+
+				m_context.getTestContext().getLog() << TestLog::Message << "Testing image size " << testSize.textureSize.x() << "x" << testSize.textureSize.y() << " with " << testSize.textureSize.z() << " layer(s)" << TestLog::EndMessage;
+				m_context.getTestContext().getLog() << TestLog::Message << "Lod: " << testSize.lod << ", base level: " << testSize.lodBase << TestLog::EndMessage;
+				m_context.getTestContext().getLog() << TestLog::Message << "Expecting: " << testSize.expectedSize.x() << "x" << testSize.expectedSize.y() << " and " << testSize.textureSize.z() << " layer(s)" << TestLog::EndMessage;
+
+				gl.uniform3iv(sizeLoc, 1, expectedSize.m_data);
+				gl.uniform1iv(lodLoc,  1, &testSize.lod);
+
+				gl.texStorage3D(textureTarget, levels, m_textureSpec.format, testSize.textureSize.x(), testSize.textureSize.y(), testSize.textureSize.z());
+				break;
+			}
+
+			default:
+			{
+				DE_ASSERT(false);
+				break;
+			}
+		}
+
+		errorValue = gl.getError();
+		if (errorValue == GL_OUT_OF_MEMORY)
+		{
+			throw glu::OutOfMemoryError("Failed to allocate texture, got GL_OUT_OF_MEMORY.", "TexStorageXD", __FILE__, __LINE__);
+		}
+		else if (errorValue != GL_NO_ERROR)
+		{
+			// error is a failure too
+			m_context.getTestContext().getLog() << tcu::TestLog::Message << "Failed, got " << glu::getErrorStr(errorValue) << "." << tcu::TestLog::EndMessage;
+			success = false;
+		}
+		else
+		{
+			// test
+			const float			colorTolerance = 0.1f;
+			tcu::TextureLevel	sample			(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 1, 1);
+			tcu::Vec4			outputColor;
+
+			gl.clear			(GL_COLOR_BUFFER_BIT);
+			gl.drawArrays		(GL_TRIANGLES, 0, 3);
+			gl.finish			();
+
+			glu::readPixels		(m_context.getRenderContext(), 0, 0, sample.getAccess());
+
+			outputColor = sample.getAccess().getPixel(0, 0);
+
+			if (outputColor.x() >= 1.0f - colorTolerance &&
+				outputColor.y() >= 1.0f - colorTolerance &&
+				outputColor.z() >= 1.0f - colorTolerance)
+			{
+				// success
+				m_context.getTestContext().getLog() << TestLog::Message << "Passed" << TestLog::EndMessage;
+			}
+			else
+			{
+				// failure
+				m_context.getTestContext().getLog() << TestLog::Message << "Failed" << TestLog::EndMessage;
+				success = false;
+			}
+		}
+
+		// empty line to format log nicely
+		m_context.getTestContext().getLog() << TestLog::Message << TestLog::EndMessage;
+
+		// free
+		gl.bindTexture		(textureTarget, 0);
+		gl.deleteTextures	(1, &texId);
+	}
+
+	gl.useProgram(0);
+
+	return success;
+}
+
+std::string TextureSizeCase::genVertexShader() const
+{
+	std::ostringstream	vert;
+
+	vert << "#version 300 es\n"
+			<< "in highp vec4 a_position;\n";
+
+	if (m_isVertexCase)
+	{
+		vert << "out mediump vec4 v_color;\n";
+		vert << "uniform highp " << m_samplerTypeStr << " u_sampler;\n";
+		vert << "uniform highp ivec" << (m_has3DSize ? 3 : 2) << " u_texSize;\n";
+		vert << "uniform highp int u_lod;\n";
+	}
+
+	vert << "void main()\n{\n";
+
+	if (m_isVertexCase)
+		vert << "	v_color = (textureSize(u_sampler, u_lod) == u_texSize ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 1.0));\n";
+
+	vert << "	gl_Position = a_position;\n"
+			<< "}\n";
+
+	return vert.str();
+}
+
+std::string TextureSizeCase::genFragmentShader() const
+{
+	std::ostringstream	frag;
+
+	frag << "#version 300 es\n"
+			<< "layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (m_isVertexCase)
+			frag << "in mediump vec4 v_color;\n";
+
+	if (!m_isVertexCase)
+	{
+		frag << "uniform highp " << m_samplerTypeStr << " u_sampler;\n";
+		frag << "uniform highp ivec" << (m_has3DSize ? 3 : 2) << " u_texSize;\n";
+		frag << "uniform highp int u_lod;\n";
+	}
+
+	frag << "void main()\n{\n";
+
+	if (!m_isVertexCase)
+		frag << "	o_color = (textureSize(u_sampler, u_lod) == u_texSize ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 1.0));\n";
+	else
+		frag << "	o_color = v_color;\n";
+
+	frag << "}\n";
+
+	return frag.str();
+}
+
+glw::GLenum TextureSizeCase::getGLTextureTarget() const
+{
+	switch (m_textureSpec.type)
+	{
+		case TEXTURETYPE_2D:		return GL_TEXTURE_2D;		break;
+		case TEXTURETYPE_CUBE_MAP:	return GL_TEXTURE_CUBE_MAP;	break;
+		case TEXTURETYPE_2D_ARRAY:	return GL_TEXTURE_2D_ARRAY;	break;
+		case TEXTURETYPE_3D:		return GL_TEXTURE_3D;		break;
+		default:					DE_ASSERT(DE_FALSE);		break;
+	}
+	return 0;
+}
+
+ShaderTextureFunctionTests::ShaderTextureFunctionTests (Context& context)
+	: TestCaseGroup(context, "texture_functions", "Texture Access Function Tests")
+{
+}
+
+ShaderTextureFunctionTests::~ShaderTextureFunctionTests (void)
+{
+}
+
+enum CaseFlags
+{
+	VERTEX		= (1<<0),
+	FRAGMENT	= (1<<1),
+	BOTH		= VERTEX|FRAGMENT
+};
+
+struct TexFuncCaseSpec
+{
+	const char*			name;
+	TextureLookupSpec	lookupSpec;
+	TextureSpec			texSpec;
+	TexEvalFunc			evalFunc;
+	deUint32			flags;
+};
+
+#define CASE_SPEC(NAME, FUNC, MINCOORD, MAXCOORD, USEBIAS, MINLOD, MAXLOD, USEOFFSET, OFFSET, TEXSPEC, EVALFUNC, FLAGS) \
+	{ #NAME, TextureLookupSpec(FUNC, MINCOORD, MAXCOORD, USEBIAS, MINLOD, MAXLOD, tcu::Vec3(0.0f), tcu::Vec3(0.0f), tcu::Vec3(0.0f), tcu::Vec3(0.0f), USEOFFSET, OFFSET), TEXSPEC, EVALFUNC, FLAGS }
+#define GRAD_CASE_SPEC(NAME, FUNC, MINCOORD, MAXCOORD, MINDX, MAXDX, MINDY, MAXDY, USEOFFSET, OFFSET, TEXSPEC, EVALFUNC, FLAGS) \
+	{ #NAME, TextureLookupSpec(FUNC, MINCOORD, MAXCOORD, false, 0.0f, 0.0f, MINDX, MAXDX, MINDY, MAXDY, USEOFFSET, OFFSET), TEXSPEC, EVALFUNC, FLAGS }
+
+static void createCaseGroup (TestCaseGroup* parent, const char* groupName, const char* groupDesc, const TexFuncCaseSpec* cases, int numCases)
+{
+	tcu::TestCaseGroup* group = new tcu::TestCaseGroup(parent->getTestContext(), groupName, groupDesc);
+	parent->addChild(group);
+
+	for (int ndx = 0; ndx < numCases; ndx++)
+	{
+		std::string name = cases[ndx].name;
+		if (cases[ndx].flags & VERTEX)
+			group->addChild(new ShaderTextureFunctionCase(parent->getContext(), (name + "_vertex").c_str(), "", cases[ndx].lookupSpec, cases[ndx].texSpec, cases[ndx].evalFunc, true));
+		if (cases[ndx].flags & FRAGMENT)
+			group->addChild(new ShaderTextureFunctionCase(parent->getContext(), (name + "_fragment").c_str(), "", cases[ndx].lookupSpec, cases[ndx].texSpec, cases[ndx].evalFunc, false));
+	}
+}
+
+void ShaderTextureFunctionTests::init (void)
+{
+	// Samplers
+	static const tcu::Sampler	samplerNearestNoMipmap	(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+														 tcu::Sampler::NEAREST, tcu::Sampler::NEAREST,
+														 0.0f /* LOD threshold */, true /* normalized coords */, tcu::Sampler::COMPAREMODE_NONE,
+														 0 /* cmp channel */, tcu::Vec4(0.0f) /* border color */, true /* seamless cube map */);
+	static const tcu::Sampler	samplerLinearNoMipmap	(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+														 tcu::Sampler::LINEAR, tcu::Sampler::LINEAR,
+														 0.0f /* LOD threshold */, true /* normalized coords */, tcu::Sampler::COMPAREMODE_NONE,
+														 0 /* cmp channel */, tcu::Vec4(0.0f) /* border color */, true /* seamless cube map */);
+	static const tcu::Sampler	samplerNearestMipmap	(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+														 tcu::Sampler::NEAREST_MIPMAP_NEAREST, tcu::Sampler::NEAREST,
+														 0.0f /* LOD threshold */, true /* normalized coords */, tcu::Sampler::COMPAREMODE_NONE,
+														 0 /* cmp channel */, tcu::Vec4(0.0f) /* border color */, true /* seamless cube map */);
+	static const tcu::Sampler	samplerLinearMipmap		(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+														 tcu::Sampler::LINEAR_MIPMAP_NEAREST, tcu::Sampler::LINEAR,
+														 0.0f /* LOD threshold */, true /* normalized coords */, tcu::Sampler::COMPAREMODE_NONE,
+														 0 /* cmp channel */, tcu::Vec4(0.0f) /* border color */, true /* seamless cube map */);
+
+	static const tcu::Sampler	samplerShadowNoMipmap	(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+														 tcu::Sampler::NEAREST, tcu::Sampler::NEAREST,
+														 0.0f /* LOD threshold */, true /* normalized coords */, tcu::Sampler::COMPAREMODE_LESS,
+														 0 /* cmp channel */, tcu::Vec4(0.0f) /* border color */, true /* seamless cube map */);
+	static const tcu::Sampler	samplerShadowMipmap		(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+														 tcu::Sampler::NEAREST_MIPMAP_NEAREST, tcu::Sampler::NEAREST,
+														 0.0f /* LOD threshold */, true /* normalized coords */, tcu::Sampler::COMPAREMODE_LESS,
+														 0 /* cmp channel */, tcu::Vec4(0.0f) /* border color */, true /* seamless cube map */);
+
+	static const tcu::Sampler	samplerTexelFetch		(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+														 tcu::Sampler::NEAREST_MIPMAP_NEAREST, tcu::Sampler::NEAREST,
+														 0.0f /* LOD threshold */, false /* non-normalized coords */, tcu::Sampler::COMPAREMODE_NONE,
+														 0 /* cmp channel */, tcu::Vec4(0.0f) /* border color */, true /* seamless cube map */);
+
+	// Default textures.
+	//												Type					Format					W		H		D	L	Sampler
+	static const TextureSpec tex2DFixed				(TEXTURETYPE_2D,		GL_RGBA8,				256,	256,	1,	1,	samplerLinearNoMipmap);
+	static const TextureSpec tex2DFloat				(TEXTURETYPE_2D,		GL_RGBA16F,				256,	256,	1,	1,	samplerLinearNoMipmap);
+	static const TextureSpec tex2DInt				(TEXTURETYPE_2D,		GL_RGBA8I,				256,	256,	1,	1,	samplerNearestNoMipmap);
+	static const TextureSpec tex2DUint				(TEXTURETYPE_2D,		GL_RGBA8UI,				256,	256,	1,	1,	samplerNearestNoMipmap);
+	static const TextureSpec tex2DMipmapFixed		(TEXTURETYPE_2D,		GL_RGBA8,				256,	256,	1,	9,	samplerLinearMipmap);
+	static const TextureSpec tex2DMipmapFloat		(TEXTURETYPE_2D,		GL_RGBA16F,				256,	256,	1,	9,	samplerLinearMipmap);
+	static const TextureSpec tex2DMipmapInt			(TEXTURETYPE_2D,		GL_RGBA8I,				256,	256,	1,	9,	samplerNearestMipmap);
+	static const TextureSpec tex2DMipmapUint		(TEXTURETYPE_2D,		GL_RGBA8UI,				256,	256,	1,	9,	samplerNearestMipmap);
+
+	static const TextureSpec tex2DShadow			(TEXTURETYPE_2D,		GL_DEPTH_COMPONENT16,	256,	256,	1,	9,	samplerShadowNoMipmap);
+	static const TextureSpec tex2DMipmapShadow		(TEXTURETYPE_2D,		GL_DEPTH_COMPONENT16,	256,	256,	1,	9,	samplerShadowMipmap);
+
+	static const TextureSpec tex2DTexelFetchFixed	(TEXTURETYPE_2D,		GL_RGBA8,				256,	256,	1,	9,	samplerTexelFetch);
+	static const TextureSpec tex2DTexelFetchFloat	(TEXTURETYPE_2D,		GL_RGBA16F,				256,	256,	1,	9,	samplerTexelFetch);
+	static const TextureSpec tex2DTexelFetchInt		(TEXTURETYPE_2D,		GL_RGBA8I,				256,	256,	1,	9,	samplerTexelFetch);
+	static const TextureSpec tex2DTexelFetchUint	(TEXTURETYPE_2D,		GL_RGBA8UI,				256,	256,	1,	9,	samplerTexelFetch);
+
+	static const TextureSpec texCubeFixed			(TEXTURETYPE_CUBE_MAP,	GL_RGBA8,	256,	256,	1,	1,	samplerLinearNoMipmap);
+	static const TextureSpec texCubeFloat			(TEXTURETYPE_CUBE_MAP,	GL_RGBA16F,	256,	256,	1,	1,	samplerLinearNoMipmap);
+	static const TextureSpec texCubeInt				(TEXTURETYPE_CUBE_MAP,	GL_RGBA8I,	256,	256,	1,	1,	samplerNearestNoMipmap);
+	static const TextureSpec texCubeUint			(TEXTURETYPE_CUBE_MAP,	GL_RGBA8UI,	256,	256,	1,	1,	samplerNearestNoMipmap);
+	static const TextureSpec texCubeMipmapFixed		(TEXTURETYPE_CUBE_MAP,	GL_RGBA8,	256,	256,	1,	9,	samplerLinearMipmap);
+	static const TextureSpec texCubeMipmapFloat		(TEXTURETYPE_CUBE_MAP,	GL_RGBA16F,	128,	128,	1,	8,	samplerLinearMipmap);
+	static const TextureSpec texCubeMipmapInt		(TEXTURETYPE_CUBE_MAP,	GL_RGBA8I,	256,	256,	1,	9,	samplerNearestMipmap);
+	static const TextureSpec texCubeMipmapUint		(TEXTURETYPE_CUBE_MAP,	GL_RGBA8UI,	256,	256,	1,	9,	samplerNearestMipmap);
+
+	static const TextureSpec texCubeShadow			(TEXTURETYPE_CUBE_MAP,	GL_DEPTH_COMPONENT16,	256,	256,	1,	1,	samplerShadowNoMipmap);
+	static const TextureSpec texCubeMipmapShadow	(TEXTURETYPE_CUBE_MAP,	GL_DEPTH_COMPONENT16,	256,	256,	1,	9,	samplerShadowMipmap);
+
+	static const TextureSpec tex2DArrayFixed		(TEXTURETYPE_2D_ARRAY,	GL_RGBA8,	128,	128,	4,	1,	samplerLinearNoMipmap);
+	static const TextureSpec tex2DArrayFloat		(TEXTURETYPE_2D_ARRAY,	GL_RGBA16F,	128,	128,	4,	1,	samplerLinearNoMipmap);
+	static const TextureSpec tex2DArrayInt			(TEXTURETYPE_2D_ARRAY,	GL_RGBA8I,	128,	128,	4,	1,	samplerNearestNoMipmap);
+	static const TextureSpec tex2DArrayUint			(TEXTURETYPE_2D_ARRAY,	GL_RGBA8UI,	128,	128,	4,	1,	samplerNearestNoMipmap);
+	static const TextureSpec tex2DArrayMipmapFixed	(TEXTURETYPE_2D_ARRAY,	GL_RGBA8,	128,	128,	4,	8,	samplerLinearMipmap);
+	static const TextureSpec tex2DArrayMipmapFloat	(TEXTURETYPE_2D_ARRAY,	GL_RGBA16F,	128,	128,	4,	8,	samplerLinearMipmap);
+	static const TextureSpec tex2DArrayMipmapInt	(TEXTURETYPE_2D_ARRAY,	GL_RGBA8I,	128,	128,	4,	8,	samplerNearestMipmap);
+	static const TextureSpec tex2DArrayMipmapUint	(TEXTURETYPE_2D_ARRAY,	GL_RGBA8UI,	128,	128,	4,	8,	samplerNearestMipmap);
+
+	static const TextureSpec tex2DArrayShadow		(TEXTURETYPE_2D_ARRAY,	GL_DEPTH_COMPONENT16,	128,	128,	4,	1,	samplerShadowNoMipmap);
+	static const TextureSpec tex2DArrayMipmapShadow	(TEXTURETYPE_2D_ARRAY,	GL_DEPTH_COMPONENT16,	128,	128,	4,	8,	samplerShadowMipmap);
+
+	static const TextureSpec tex2DArrayTexelFetchFixed	(TEXTURETYPE_2D_ARRAY,	GL_RGBA8,	128,	128,	4,	8,	samplerTexelFetch);
+	static const TextureSpec tex2DArrayTexelFetchFloat	(TEXTURETYPE_2D_ARRAY,	GL_RGBA16F,	128,	128,	4,	8,	samplerTexelFetch);
+	static const TextureSpec tex2DArrayTexelFetchInt	(TEXTURETYPE_2D_ARRAY,	GL_RGBA8I,	128,	128,	4,	8,	samplerTexelFetch);
+	static const TextureSpec tex2DArrayTexelFetchUint	(TEXTURETYPE_2D_ARRAY,	GL_RGBA8UI,	128,	128,	4,	8,	samplerTexelFetch);
+
+	static const TextureSpec tex3DFixed				(TEXTURETYPE_3D,		GL_RGBA8,	64,		32,		32,	1,	samplerLinearNoMipmap);
+	static const TextureSpec tex3DFloat				(TEXTURETYPE_3D,		GL_RGBA16F,	64,		32,		32,	1,	samplerLinearNoMipmap);
+	static const TextureSpec tex3DInt				(TEXTURETYPE_3D,		GL_RGBA8I,	64,		32,		32,	1,	samplerNearestNoMipmap);
+	static const TextureSpec tex3DUint				(TEXTURETYPE_3D,		GL_RGBA8UI,	64,		32,		32,	1,	samplerNearestNoMipmap);
+	static const TextureSpec tex3DMipmapFixed		(TEXTURETYPE_3D,		GL_RGBA8,	64,		32,		32,	7,	samplerLinearMipmap);
+	static const TextureSpec tex3DMipmapFloat		(TEXTURETYPE_3D,		GL_RGBA16F,	64,		32,		32,	7,	samplerLinearMipmap);
+	static const TextureSpec tex3DMipmapInt			(TEXTURETYPE_3D,		GL_RGBA8I,	64,		32,		32,	7,	samplerNearestMipmap);
+	static const TextureSpec tex3DMipmapUint		(TEXTURETYPE_3D,		GL_RGBA8UI,	64,		32,		32,	7,	samplerNearestMipmap);
+
+	static const TextureSpec tex3DTexelFetchFixed	(TEXTURETYPE_3D,		GL_RGBA8,	64,		32,		32,	7,	samplerTexelFetch);
+	static const TextureSpec tex3DTexelFetchFloat	(TEXTURETYPE_3D,		GL_RGBA16F,	64,		32,		32,	7,	samplerTexelFetch);
+	static const TextureSpec tex3DTexelFetchInt		(TEXTURETYPE_3D,		GL_RGBA8I,	64,		32,		32,	7,	samplerTexelFetch);
+	static const TextureSpec tex3DTexelFetchUint	(TEXTURETYPE_3D,		GL_RGBA8UI,	64,		32,		32,	7,	samplerTexelFetch);
+
+	// texture() cases
+	static const TexFuncCaseSpec textureCases[] =
+	{
+		//		  Name							Function			MinCoord							MaxCoord							Bias?	MinLod	MaxLod	Offset?	Offset		Format					EvalFunc				Flags
+		CASE_SPEC(sampler2d_fixed,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DFixed,				evalTexture2D,			VERTEX),
+		CASE_SPEC(sampler2d_fixed,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2D,			FRAGMENT),
+		CASE_SPEC(sampler2d_float,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DFloat,				evalTexture2D,			VERTEX),
+		CASE_SPEC(sampler2d_float,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2D,			FRAGMENT),
+		CASE_SPEC(isampler2d,					FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DInt,				evalTexture2D,			VERTEX),
+		CASE_SPEC(isampler2d,					FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2D,			FRAGMENT),
+		CASE_SPEC(usampler2d,					FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DUint,				evalTexture2D,			VERTEX),
+		CASE_SPEC(usampler2d,					FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2D,			FRAGMENT),
+
+		CASE_SPEC(sampler2d_bias_fixed,			FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DBias,		FRAGMENT),
+		CASE_SPEC(sampler2d_bias_float,			FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DBias,		FRAGMENT),
+		CASE_SPEC(isampler2d_bias,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DBias,		FRAGMENT),
+		CASE_SPEC(usampler2d_bias,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DBias,		FRAGMENT),
+
+		CASE_SPEC(samplercube_fixed,			FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	texCubeFixed,			evalTextureCube,		VERTEX),
+		CASE_SPEC(samplercube_fixed,			FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	texCubeMipmapFixed,		evalTextureCube,		FRAGMENT),
+		CASE_SPEC(samplercube_float,			FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	texCubeFloat,			evalTextureCube,		VERTEX),
+		CASE_SPEC(samplercube_float,			FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	texCubeMipmapFloat,		evalTextureCube,		FRAGMENT),
+		CASE_SPEC(isamplercube,					FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	texCubeInt,				evalTextureCube,		VERTEX),
+		CASE_SPEC(isamplercube,					FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	texCubeMipmapInt,		evalTextureCube,		FRAGMENT),
+		CASE_SPEC(usamplercube,					FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	texCubeUint,			evalTextureCube,		VERTEX),
+		CASE_SPEC(usamplercube,					FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	texCubeMipmapUint,		evalTextureCube,		FRAGMENT),
+
+		CASE_SPEC(samplercube_bias_fixed,		FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	texCubeMipmapFixed,		evalTextureCubeBias,	FRAGMENT),
+		CASE_SPEC(samplercube_bias_float,		FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	texCubeMipmapFloat,		evalTextureCubeBias,	FRAGMENT),
+		CASE_SPEC(isamplercube_bias,			FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	texCubeMipmapInt,		evalTextureCubeBias,	FRAGMENT),
+		CASE_SPEC(usamplercube_bias,			FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	texCubeMipmapUint,		evalTextureCubeBias,	FRAGMENT),
+
+		CASE_SPEC(sampler2darray_fixed,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayFixed,		evalTexture2DArray,		VERTEX),
+		CASE_SPEC(sampler2darray_fixed,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayMipmapFixed,	evalTexture2DArray,		FRAGMENT),
+		CASE_SPEC(sampler2darray_float,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayFloat,		evalTexture2DArray,		VERTEX),
+		CASE_SPEC(sampler2darray_float,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayMipmapFloat,	evalTexture2DArray,		FRAGMENT),
+		CASE_SPEC(isampler2darray,				FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayInt,			evalTexture2DArray,		VERTEX),
+		CASE_SPEC(isampler2darray,				FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayMipmapInt,	evalTexture2DArray,		FRAGMENT),
+		CASE_SPEC(usampler2darray,				FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayUint,			evalTexture2DArray,		VERTEX),
+		CASE_SPEC(usampler2darray,				FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayMipmapUint,	evalTexture2DArray,		FRAGMENT),
+
+		CASE_SPEC(sampler2darray_bias_fixed,	FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DArrayMipmapFixed,	evalTexture2DArrayBias,	FRAGMENT),
+		CASE_SPEC(sampler2darray_bias_float,	FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DArrayMipmapFloat,	evalTexture2DArrayBias,	FRAGMENT),
+		CASE_SPEC(isampler2darray_bias,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DArrayMipmapInt,	evalTexture2DArrayBias,	FRAGMENT),
+		CASE_SPEC(usampler2darray_bias,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DArrayMipmapUint,	evalTexture2DArrayBias,	FRAGMENT),
+
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DFixed,				evalTexture3D,			VERTEX),
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DMipmapFixed,		evalTexture3D,			FRAGMENT),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DFloat,				evalTexture3D,			VERTEX),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DMipmapFloat,		evalTexture3D,			FRAGMENT),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DInt,				evalTexture3D,			VERTEX),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DMipmapInt,			evalTexture3D,			FRAGMENT),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DUint,				evalTexture3D,			VERTEX),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DMipmapUint,		evalTexture3D,			FRAGMENT),
+
+		CASE_SPEC(sampler3d_bias_fixed,			FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	true,	-2.0f,	1.0f,	false,	IVec3(0),	tex3DMipmapFixed,		evalTexture3DBias,		FRAGMENT),
+		CASE_SPEC(sampler3d_bias_float,			FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	true,	-2.0f,	1.0f,	false,	IVec3(0),	tex3DMipmapFloat,		evalTexture3DBias,		FRAGMENT),
+		CASE_SPEC(isampler3d_bias,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex3DMipmapInt,			evalTexture3DBias,		FRAGMENT),
+		CASE_SPEC(usampler3d_bias,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex3DMipmapUint,		evalTexture3DBias,		FRAGMENT),
+
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DShadow,			evalTexture2DShadow,			VERTEX),
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapShadow,		evalTexture2DShadow,			FRAGMENT),
+		CASE_SPEC(sampler2dshadow_bias,			FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapShadow,		evalTexture2DShadowBias,		FRAGMENT),
+
+		CASE_SPEC(samplercubeshadow,			FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  1.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	texCubeShadow,			evalTextureCubeShadow,			VERTEX),
+		CASE_SPEC(samplercubeshadow,			FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  1.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	texCubeMipmapShadow,	evalTextureCubeShadow,			FRAGMENT),
+		CASE_SPEC(samplercubeshadow_bias,		FUNCTION_TEXTURE,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  1.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	texCubeMipmapShadow,	evalTextureCubeShadowBias,		FRAGMENT),
+
+		CASE_SPEC(sampler2darrayshadow,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  1.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayShadow,		evalTexture2DArrayShadow,		VERTEX),
+		CASE_SPEC(sampler2darrayshadow,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  1.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayMipmapShadow,	evalTexture2DArrayShadow,		FRAGMENT)
+
+		// Not in spec.
+//		CASE_SPEC(sampler2darrayshadow_bias,	(FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  1.0f),	true,	-2.0f,	2.0f,	Vec2(0.0f),	Vec2(0.0f), false,	IVec3(0)),	tex2DArrayMipmapShadow,	evalTexture2DArrayShadowBias,	FRAGMENT)
+	};
+	createCaseGroup(this, "texture", "texture() Tests", textureCases, DE_LENGTH_OF_ARRAY(textureCases));
+
+	// textureOffset() cases
+	// \note _bias variants are not using mipmap thanks to wide allowed range for LOD computation
+	static const TexFuncCaseSpec textureOffsetCases[] =
+	{
+		//		  Name							Function			MinCoord							MaxCoord							Bias?	MinLod	MaxLod	Offset?	Offset				Format					EvalFunc						Flags
+		CASE_SPEC(sampler2d_fixed,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DFixed,				evalTexture2DOffset,			VERTEX),
+		CASE_SPEC(sampler2d_fixed,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapFixed,		evalTexture2DOffset,			FRAGMENT),
+		CASE_SPEC(sampler2d_float,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DFloat,				evalTexture2DOffset,			VERTEX),
+		CASE_SPEC(sampler2d_float,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapFloat,		evalTexture2DOffset,			FRAGMENT),
+		CASE_SPEC(isampler2d,					FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DInt,				evalTexture2DOffset,			VERTEX),
+		CASE_SPEC(isampler2d,					FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapInt,			evalTexture2DOffset,			FRAGMENT),
+		CASE_SPEC(usampler2d,					FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DUint,				evalTexture2DOffset,			VERTEX),
+		CASE_SPEC(usampler2d,					FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapUint,		evalTexture2DOffset,			FRAGMENT),
+
+		CASE_SPEC(sampler2d_bias_fixed,			FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DFixed,				evalTexture2DOffsetBias,		FRAGMENT),
+		CASE_SPEC(sampler2d_bias_float,			FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(7, -8, 0),	tex2DFloat,				evalTexture2DOffsetBias,		FRAGMENT),
+		CASE_SPEC(isampler2d_bias,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DInt,				evalTexture2DOffsetBias,		FRAGMENT),
+		CASE_SPEC(usampler2d_bias,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(7, -8, 0),	tex2DUint,				evalTexture2DOffsetBias,		FRAGMENT),
+
+		CASE_SPEC(sampler2darray_fixed,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DArrayFixed,		evalTexture2DArrayOffset,		VERTEX),
+		CASE_SPEC(sampler2darray_fixed,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DArrayMipmapFixed,	evalTexture2DArrayOffset,		FRAGMENT),
+		CASE_SPEC(sampler2darray_float,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DArrayFloat,		evalTexture2DArrayOffset,		VERTEX),
+		CASE_SPEC(sampler2darray_float,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DArrayMipmapFloat,	evalTexture2DArrayOffset,		FRAGMENT),
+		CASE_SPEC(isampler2darray,				FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DArrayInt,			evalTexture2DArrayOffset,		VERTEX),
+		CASE_SPEC(isampler2darray,				FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DArrayMipmapInt,	evalTexture2DArrayOffset,		FRAGMENT),
+		CASE_SPEC(usampler2darray,				FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DArrayUint,			evalTexture2DArrayOffset,		VERTEX),
+		CASE_SPEC(usampler2darray,				FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DArrayMipmapUint,	evalTexture2DArrayOffset,		FRAGMENT),
+
+		CASE_SPEC(sampler2darray_bias_fixed,	FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DArrayFixed,		evalTexture2DArrayOffsetBias,	FRAGMENT),
+		CASE_SPEC(sampler2darray_bias_float,	FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(7, -8, 0),	tex2DArrayFloat,		evalTexture2DArrayOffsetBias,	FRAGMENT),
+		CASE_SPEC(isampler2darray_bias,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DArrayInt,			evalTexture2DArrayOffsetBias,	FRAGMENT),
+		CASE_SPEC(usampler2darray_bias,			FUNCTION_TEXTURE,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(7, -8, 0),	tex2DArrayUint,			evalTexture2DArrayOffsetBias,	FRAGMENT),
+
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 3),	tex3DFixed,				evalTexture3DOffset,			VERTEX),
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, 3, -8),	tex3DMipmapFixed,		evalTexture3DOffset,			FRAGMENT),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(3, -8, 7),	tex3DFloat,				evalTexture3DOffset,			VERTEX),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 3),	tex3DMipmapFloat,		evalTexture3DOffset,			FRAGMENT),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, 3, -8),	tex3DInt,				evalTexture3DOffset,			VERTEX),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(3, -8, 7),	tex3DMipmapInt,			evalTexture3DOffset,			FRAGMENT),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 3),	tex3DUint,				evalTexture3DOffset,			VERTEX),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, 3, -8),	tex3DMipmapUint,		evalTexture3DOffset,			FRAGMENT),
+
+		CASE_SPEC(sampler3d_bias_fixed,			FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	true,	-2.0f,	1.0f,	true,	IVec3(-8, 7, 3),	tex3DFixed,				evalTexture3DOffsetBias,		FRAGMENT),
+		CASE_SPEC(sampler3d_bias_float,			FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	true,	-2.0f,	1.0f,	true,	IVec3(7, 3, -8),	tex3DFloat,				evalTexture3DOffsetBias,		FRAGMENT),
+		CASE_SPEC(isampler3d_bias,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(3, -8, 7),	tex3DInt,				evalTexture3DOffsetBias,		FRAGMENT),
+		CASE_SPEC(usampler3d_bias,				FUNCTION_TEXTURE,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 3),	tex3DUint,				evalTexture3DOffsetBias,		FRAGMENT),
+
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DShadow,			evalTexture2DShadowOffset,		VERTEX),
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapShadow,		evalTexture2DShadowOffset,		FRAGMENT),
+		CASE_SPEC(sampler2dshadow_bias,			FUNCTION_TEXTURE,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DShadow,			evalTexture2DShadowOffsetBias,	FRAGMENT)
+	};
+	createCaseGroup(this, "textureoffset", "textureOffset() Tests", textureOffsetCases, DE_LENGTH_OF_ARRAY(textureOffsetCases));
+
+	// textureProj() cases
+	// \note Currently uses constant divider!
+	static const TexFuncCaseSpec textureProjCases[] =
+	{
+		//		  Name							Function				MinCoord							MaxCoord							Bias?	MinLod	MaxLod	Offset?	Offset		Format					EvalFunc				Flags
+		CASE_SPEC(sampler2d_vec3_fixed,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DFixed,				evalTexture2DProj3,		VERTEX),
+		CASE_SPEC(sampler2d_vec3_fixed,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DProj3,		FRAGMENT),
+		CASE_SPEC(sampler2d_vec3_float,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DFloat,				evalTexture2DProj3,		VERTEX),
+		CASE_SPEC(sampler2d_vec3_float,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DProj3,		FRAGMENT),
+		CASE_SPEC(isampler2d_vec3,				FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DInt,				evalTexture2DProj3,		VERTEX),
+		CASE_SPEC(isampler2d_vec3,				FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DProj3,		FRAGMENT),
+		CASE_SPEC(usampler2d_vec3,				FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DUint,				evalTexture2DProj3,		VERTEX),
+		CASE_SPEC(usampler2d_vec3,				FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DProj3,		FRAGMENT),
+
+		CASE_SPEC(sampler2d_vec3_bias_fixed,	FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DProj3Bias,	FRAGMENT),
+		CASE_SPEC(sampler2d_vec3_bias_float,	FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DProj3Bias,	FRAGMENT),
+		CASE_SPEC(isampler2d_vec3_bias,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DProj3Bias,	FRAGMENT),
+		CASE_SPEC(usampler2d_vec3_bias,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DProj3Bias,	FRAGMENT),
+
+		CASE_SPEC(sampler2d_vec4_fixed,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DFixed,				evalTexture2DProj,		VERTEX),
+		CASE_SPEC(sampler2d_vec4_fixed,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DProj,		FRAGMENT),
+		CASE_SPEC(sampler2d_vec4_float,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DFloat,				evalTexture2DProj,		VERTEX),
+		CASE_SPEC(sampler2d_vec4_float,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DProj,		FRAGMENT),
+		CASE_SPEC(isampler2d_vec4,				FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DInt,				evalTexture2DProj,		VERTEX),
+		CASE_SPEC(isampler2d_vec4,				FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DProj,		FRAGMENT),
+		CASE_SPEC(usampler2d_vec4,				FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DUint,				evalTexture2DProj,		VERTEX),
+		CASE_SPEC(usampler2d_vec4,				FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DProj,		FRAGMENT),
+
+		CASE_SPEC(sampler2d_vec4_bias_fixed,	FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DProjBias,	FRAGMENT),
+		CASE_SPEC(sampler2d_vec4_bias_float,	FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DProjBias,	FRAGMENT),
+		CASE_SPEC(isampler2d_vec4_bias,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DProjBias,	FRAGMENT),
+		CASE_SPEC(usampler2d_vec4_bias,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DProjBias,	FRAGMENT),
+
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DFixed,				evalTexture3DProj,		VERTEX),
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DMipmapFixed,		evalTexture3DProj,		FRAGMENT),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DFloat,				evalTexture3DProj,		VERTEX),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DMipmapFloat,		evalTexture3DProj,		FRAGMENT),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DInt,				evalTexture3DProj,		VERTEX),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DMipmapInt,			evalTexture3DProj,		FRAGMENT),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DUint,				evalTexture3DProj,		VERTEX),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DMipmapUint,		evalTexture3DProj,		FRAGMENT),
+
+		CASE_SPEC(sampler3d_bias_fixed,			FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	true,	-2.0f,	1.0f,	false,	IVec3(0),	tex3DMipmapFixed,		evalTexture3DProjBias,	FRAGMENT),
+		CASE_SPEC(sampler3d_bias_float,			FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	true,	-2.0f,	1.0f,	false,	IVec3(0),	tex3DMipmapFloat,		evalTexture3DProjBias,	FRAGMENT),
+		CASE_SPEC(isampler3d_bias,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex3DMipmapInt,			evalTexture3DProjBias,	FRAGMENT),
+		CASE_SPEC(usampler3d_bias,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex3DMipmapUint,		evalTexture3DProjBias,	FRAGMENT),
+
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTUREPROJ,	Vec4( 0.2f, 0.6f,  0.0f,  1.5f),	Vec4(-2.25f, -3.45f, 1.5f,  1.5f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DShadow,			evalTexture2DShadowProj,		VERTEX),
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTUREPROJ,	Vec4( 0.2f, 0.6f,  0.0f,  1.5f),	Vec4(-2.25f, -3.45f, 1.5f,  1.5f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DMipmapShadow,		evalTexture2DShadowProj,		FRAGMENT),
+		CASE_SPEC(sampler2dshadow_bias,			FUNCTION_TEXTUREPROJ,	Vec4( 0.2f, 0.6f,  0.0f,  1.5f),	Vec4(-2.25f, -3.45f, 1.5f,  1.5f),	true,	-2.0f,	2.0f,	false,	IVec3(0),	tex2DMipmapShadow,		evalTexture2DShadowProjBias,	FRAGMENT)
+	};
+	createCaseGroup(this, "textureproj", "textureProj() Tests", textureProjCases, DE_LENGTH_OF_ARRAY(textureProjCases));
+
+	// textureProjOffset() cases
+	// \note Currently uses constant divider!
+	static const TexFuncCaseSpec textureProjOffsetCases[] =
+	{
+		//		  Name							Function				MinCoord							MaxCoord							Bias?	MinLod	MaxLod	Offset?	Offset				Format					EvalFunc						Flags
+		CASE_SPEC(sampler2d_vec3_fixed,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DFixed,				evalTexture2DProj3Offset,		VERTEX),
+		CASE_SPEC(sampler2d_vec3_fixed,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapFixed,		evalTexture2DProj3Offset,		FRAGMENT),
+		CASE_SPEC(sampler2d_vec3_float,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DFloat,				evalTexture2DProj3Offset,		VERTEX),
+		CASE_SPEC(sampler2d_vec3_float,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapFloat,		evalTexture2DProj3Offset,		FRAGMENT),
+		CASE_SPEC(isampler2d_vec3,				FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DInt,				evalTexture2DProj3Offset,		VERTEX),
+		CASE_SPEC(isampler2d_vec3,				FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapInt,			evalTexture2DProj3Offset,		FRAGMENT),
+		CASE_SPEC(usampler2d_vec3,				FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DUint,				evalTexture2DProj3Offset,		VERTEX),
+		CASE_SPEC(usampler2d_vec3,				FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapUint,		evalTexture2DProj3Offset,		FRAGMENT),
+
+		CASE_SPEC(sampler2d_vec3_bias_fixed,	FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DFixed,				evalTexture2DProj3OffsetBias,	FRAGMENT),
+		CASE_SPEC(sampler2d_vec3_bias_float,	FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(7, -8, 0),	tex2DFloat,				evalTexture2DProj3OffsetBias,	FRAGMENT),
+		CASE_SPEC(isampler2d_vec3_bias,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DInt,				evalTexture2DProj3OffsetBias,	FRAGMENT),
+		CASE_SPEC(usampler2d_vec3_bias,			FUNCTION_TEXTUREPROJ3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	true,	-2.0f,	2.0f,	true,	IVec3(7, -8, 0),	tex2DUint,				evalTexture2DProj3OffsetBias,	FRAGMENT),
+
+		CASE_SPEC(sampler2d_vec4_fixed,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DFixed,				evalTexture2DProjOffset,		VERTEX),
+		CASE_SPEC(sampler2d_vec4_fixed,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapFixed,		evalTexture2DProjOffset,		FRAGMENT),
+		CASE_SPEC(sampler2d_vec4_float,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DFloat,				evalTexture2DProjOffset,		VERTEX),
+		CASE_SPEC(sampler2d_vec4_float,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapFloat,		evalTexture2DProjOffset,		FRAGMENT),
+		CASE_SPEC(isampler2d_vec4,				FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DInt,				evalTexture2DProjOffset,		VERTEX),
+		CASE_SPEC(isampler2d_vec4,				FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapInt,			evalTexture2DProjOffset,		FRAGMENT),
+		CASE_SPEC(usampler2d_vec4,				FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DUint,				evalTexture2DProjOffset,		VERTEX),
+		CASE_SPEC(usampler2d_vec4,				FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapUint,		evalTexture2DProjOffset,		FRAGMENT),
+
+		CASE_SPEC(sampler2d_vec4_bias_fixed,	FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DFixed,				evalTexture2DProjOffsetBias,	FRAGMENT),
+		CASE_SPEC(sampler2d_vec4_bias_float,	FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	true,	-2.0f,	2.0f,	true,	IVec3(7, -8, 0),	tex2DFloat,				evalTexture2DProjOffsetBias,	FRAGMENT),
+		CASE_SPEC(isampler2d_vec4_bias,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DInt,				evalTexture2DProjOffsetBias,	FRAGMENT),
+		CASE_SPEC(usampler2d_vec4_bias,			FUNCTION_TEXTUREPROJ,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	true,	-2.0f,	2.0f,	true,	IVec3(7, -8, 0),	tex2DUint,				evalTexture2DProjOffsetBias,	FRAGMENT),
+
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 3),	tex3DFixed,				evalTexture3DProjOffset,		VERTEX),
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	true,	IVec3(7, 3, -8),	tex3DMipmapFixed,		evalTexture3DProjOffset,		FRAGMENT),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	true,	IVec3(3, -8, 7),	tex3DFloat,				evalTexture3DProjOffset,		VERTEX),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 3),	tex3DMipmapFloat,		evalTexture3DProjOffset,		FRAGMENT),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	true,	IVec3(7, 3, -8),	tex3DInt,				evalTexture3DProjOffset,		VERTEX),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	true,	IVec3(3, -8, 7),	tex3DMipmapInt,			evalTexture3DProjOffset,		FRAGMENT),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 3),	tex3DUint,				evalTexture3DProjOffset,		VERTEX),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	0.0f,	0.0f,	true,	IVec3(7, 3, -8),	tex3DMipmapUint,		evalTexture3DProjOffset,		FRAGMENT),
+
+		CASE_SPEC(sampler3d_bias_fixed,			FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 3),	tex3DFixed,				evalTexture3DProjOffsetBias,	FRAGMENT),
+		CASE_SPEC(sampler3d_bias_float,			FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	true,	-2.0f,	2.0f,	true,	IVec3(7, 3, -8),	tex3DFloat,				evalTexture3DProjOffsetBias,	FRAGMENT),
+		CASE_SPEC(isampler3d_bias,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	true,	-2.0f,	2.0f,	true,	IVec3(3, -8, 7),	tex3DInt,				evalTexture3DProjOffsetBias,	FRAGMENT),
+		CASE_SPEC(usampler3d_bias,				FUNCTION_TEXTUREPROJ,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 3),	tex3DUint,				evalTexture3DProjOffsetBias,	FRAGMENT),
+
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTUREPROJ,	Vec4( 0.2f, 0.6f,  0.0f,  1.5f),	Vec4(-2.25f, -3.45f, 1.5f,  1.5f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DShadow,			evalTexture2DShadowProjOffset,		VERTEX),
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTUREPROJ,	Vec4( 0.2f, 0.6f,  0.0f,  1.5f),	Vec4(-2.25f, -3.45f, 1.5f,  1.5f),	false,	0.0f,	0.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapShadow,		evalTexture2DShadowProjOffset,		FRAGMENT),
+		CASE_SPEC(sampler2dshadow_bias,			FUNCTION_TEXTUREPROJ,	Vec4( 0.2f, 0.6f,  0.0f,  1.5f),	Vec4(-2.25f, -3.45f, 1.5f,  1.5f),	true,	-2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DShadow,			evalTexture2DShadowProjOffsetBias,	FRAGMENT)
+	};
+	createCaseGroup(this, "textureprojoffset", "textureOffsetProj() Tests", textureProjOffsetCases, DE_LENGTH_OF_ARRAY(textureProjOffsetCases));
+
+	// textureLod() cases
+	static const TexFuncCaseSpec textureLodCases[] =
+	{
+		//		  Name							Function				MinCoord							MaxCoord							Bias?	MinLod	MaxLod	Offset?	Offset		Format					EvalFunc				Flags
+		CASE_SPEC(sampler2d_fixed,				FUNCTION_TEXTURELOD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DLod,		BOTH),
+		CASE_SPEC(sampler2d_float,				FUNCTION_TEXTURELOD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DLod,		BOTH),
+		CASE_SPEC(isampler2d,					FUNCTION_TEXTURELOD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DLod,		BOTH),
+		CASE_SPEC(usampler2d,					FUNCTION_TEXTURELOD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DLod,		BOTH),
+
+		CASE_SPEC(samplercube_fixed,			FUNCTION_TEXTURELOD,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	texCubeMipmapFixed,		evalTextureCubeLod,		BOTH),
+		CASE_SPEC(samplercube_float,			FUNCTION_TEXTURELOD,	Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	texCubeMipmapFloat,		evalTextureCubeLod,		BOTH),
+		CASE_SPEC(isamplercube,					FUNCTION_TEXTURELOD,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	texCubeMipmapInt,		evalTextureCubeLod,		BOTH),
+		CASE_SPEC(usamplercube,					FUNCTION_TEXTURELOD,	Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	texCubeMipmapUint,		evalTextureCubeLod,		BOTH),
+
+		CASE_SPEC(sampler2darray_fixed,			FUNCTION_TEXTURELOD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	-1.0f,	8.0f,	false,	IVec3(0),	tex2DArrayMipmapFixed,	evalTexture2DArrayLod,	BOTH),
+		CASE_SPEC(sampler2darray_float,			FUNCTION_TEXTURELOD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	-1.0f,	8.0f,	false,	IVec3(0),	tex2DArrayMipmapFloat,	evalTexture2DArrayLod,	BOTH),
+		CASE_SPEC(isampler2darray,				FUNCTION_TEXTURELOD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	-1.0f,	8.0f,	false,	IVec3(0),	tex2DArrayMipmapInt,	evalTexture2DArrayLod,	BOTH),
+		CASE_SPEC(usampler2darray,				FUNCTION_TEXTURELOD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	-1.0f,	8.0f,	false,	IVec3(0),	tex2DArrayMipmapUint,	evalTexture2DArrayLod,	BOTH),
+
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTURELOD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	-1.0f,	7.0f,	false,	IVec3(0),	tex3DMipmapFixed,		evalTexture3DLod,		BOTH),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTURELOD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	-1.0f,	7.0f,	false,	IVec3(0),	tex3DMipmapFloat,		evalTexture3DLod,		BOTH),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTURELOD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	-1.0f,	7.0f,	false,	IVec3(0),	tex3DMipmapInt,			evalTexture3DLod,		BOTH),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTURELOD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	-1.0f,	7.0f,	false,	IVec3(0),	tex3DMipmapUint,		evalTexture3DLod,		BOTH),
+
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTURELOD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapShadow,		evalTexture2DShadowLod,	BOTH)
+	};
+	createCaseGroup(this, "texturelod", "textureLod() Tests", textureLodCases, DE_LENGTH_OF_ARRAY(textureLodCases));
+
+	// textureLodOffset() cases
+	static const TexFuncCaseSpec textureLodOffsetCases[] =
+	{
+		//		  Name							Function				MinCoord							MaxCoord							Bias?	MinLod	MaxLod	Offset?	Offset				Format					EvalFunc						Flags
+		CASE_SPEC(sampler2d_fixed,				FUNCTION_TEXTURELOD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	-1.0f,	9.0f,	true,	IVec3(-8, 7, 0),	tex2DMipmapFixed,		evalTexture2DLodOffset,			BOTH),
+		CASE_SPEC(sampler2d_float,				FUNCTION_TEXTURELOD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	-1.0f,	9.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapFloat,		evalTexture2DLodOffset,			BOTH),
+		CASE_SPEC(isampler2d,					FUNCTION_TEXTURELOD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	-1.0f,	9.0f,	true,	IVec3(-8, 7, 0),	tex2DMipmapInt,			evalTexture2DLodOffset,			BOTH),
+		CASE_SPEC(usampler2d,					FUNCTION_TEXTURELOD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	false,	-1.0f,	9.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapUint,		evalTexture2DLodOffset,			BOTH),
+
+		CASE_SPEC(sampler2darray_fixed,			FUNCTION_TEXTURELOD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	-1.0f,	8.0f,	true,	IVec3(-8, 7, 0),	tex2DArrayMipmapFixed,	evalTexture2DArrayLodOffset,	BOTH),
+		CASE_SPEC(sampler2darray_float,			FUNCTION_TEXTURELOD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	-1.0f,	8.0f,	true,	IVec3(7, -8, 0),	tex2DArrayMipmapFloat,	evalTexture2DArrayLodOffset,	BOTH),
+		CASE_SPEC(isampler2darray,				FUNCTION_TEXTURELOD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	-1.0f,	8.0f,	true,	IVec3(-8, 7, 0),	tex2DArrayMipmapInt,	evalTexture2DArrayLodOffset,	BOTH),
+		CASE_SPEC(usampler2darray,				FUNCTION_TEXTURELOD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	false,	-1.0f,	8.0f,	true,	IVec3(7, -8, 0),	tex2DArrayMipmapUint,	evalTexture2DArrayLodOffset,	BOTH),
+
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTURELOD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	-1.0f,	7.0f,	true,	IVec3(-8, 7, 3),	tex3DMipmapFixed,		evalTexture3DLodOffset,			BOTH),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTURELOD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	-1.0f,	7.0f,	true,	IVec3(7, 3, -8),	tex3DMipmapFloat,		evalTexture3DLodOffset,			BOTH),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTURELOD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	-1.0f,	7.0f,	true,	IVec3(3, -8, 7),	tex3DMipmapInt,			evalTexture3DLodOffset,			BOTH),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTURELOD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	false,	-1.0f,	7.0f,	true,	IVec3(-8, 7, 3),	tex3DMipmapUint,		evalTexture3DLodOffset,			BOTH),
+
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTURELOD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	false,	-1.0f,	9.0f,	true,	IVec3(-8, 7, 0),	tex2DMipmapShadow,		evalTexture2DShadowLodOffset,	BOTH)
+	};
+	createCaseGroup(this, "texturelodoffset", "textureLodOffset() Tests", textureLodOffsetCases, DE_LENGTH_OF_ARRAY(textureLodOffsetCases));
+
+	// textureProjLod() cases
+	static const TexFuncCaseSpec textureProjLodCases[] =
+	{
+		//		  Name							Function					MinCoord							MaxCoord							Bias?	MinLod	MaxLod	Offset?	Offset		Format					EvalFunc					Flags
+		CASE_SPEC(sampler2d_vec3_fixed,			FUNCTION_TEXTUREPROJLOD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DProjLod3,		BOTH),
+		CASE_SPEC(sampler2d_vec3_float,			FUNCTION_TEXTUREPROJLOD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DProjLod3,		BOTH),
+		CASE_SPEC(isampler2d_vec3,				FUNCTION_TEXTUREPROJLOD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DProjLod3,		BOTH),
+		CASE_SPEC(usampler2d_vec3,				FUNCTION_TEXTUREPROJLOD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DProjLod3,		BOTH),
+
+		CASE_SPEC(sampler2d_vec4_fixed,			FUNCTION_TEXTUREPROJLOD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DProjLod,		BOTH),
+		CASE_SPEC(sampler2d_vec4_float,			FUNCTION_TEXTUREPROJLOD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DProjLod,		BOTH),
+		CASE_SPEC(isampler2d_vec4,				FUNCTION_TEXTUREPROJLOD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DProjLod,		BOTH),
+		CASE_SPEC(usampler2d_vec4,				FUNCTION_TEXTUREPROJLOD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DProjLod,		BOTH),
+
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTUREPROJLOD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	-1.0f,	7.0f,	false,	IVec3(0),	tex3DMipmapFixed,		evalTexture3DProjLod,		BOTH),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTUREPROJLOD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	-1.0f,	7.0f,	false,	IVec3(0),	tex3DMipmapFloat,		evalTexture3DProjLod,		BOTH),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTUREPROJLOD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	-1.0f,	7.0f,	false,	IVec3(0),	tex3DMipmapInt,			evalTexture3DProjLod,		BOTH),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTUREPROJLOD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	-1.0f,	7.0f,	false,	IVec3(0),	tex3DMipmapUint,		evalTexture3DProjLod,		BOTH),
+
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTUREPROJLOD,	Vec4( 0.2f, 0.6f,  0.0f,  1.5f),	Vec4(-2.25f, -3.45f, 1.5f,  1.5f),	false,	-1.0f,	9.0f,	false,	IVec3(0),	tex2DMipmapShadow,		evalTexture2DShadowProjLod,	BOTH)
+	};
+	createCaseGroup(this, "textureprojlod", "textureProjLod() Tests", textureProjLodCases, DE_LENGTH_OF_ARRAY(textureProjLodCases));
+
+	// textureProjLodOffset() cases
+	static const TexFuncCaseSpec textureProjLodOffsetCases[] =
+	{
+		//		  Name							Function					MinCoord							MaxCoord							Bias?	MinLod	MaxLod	Offset?	Offset				Format					EvalFunc								Flags
+		CASE_SPEC(sampler2d_vec3_fixed,			FUNCTION_TEXTUREPROJLOD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	-1.0f,	9.0f,	true,	IVec3(-8, 7, 0),	tex2DMipmapFixed,		evalTexture2DProjLod3Offset,	BOTH),
+		CASE_SPEC(sampler2d_vec3_float,			FUNCTION_TEXTUREPROJLOD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	-1.0f,	9.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapFloat,		evalTexture2DProjLod3Offset,	BOTH),
+		CASE_SPEC(isampler2d_vec3,				FUNCTION_TEXTUREPROJLOD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	-1.0f,	9.0f,	true,	IVec3(-8, 7, 0),	tex2DMipmapInt,			evalTexture2DProjLod3Offset,	BOTH),
+		CASE_SPEC(usampler2d_vec3,				FUNCTION_TEXTUREPROJLOD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	false,	-1.0f,	9.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapUint,		evalTexture2DProjLod3Offset,	BOTH),
+
+		CASE_SPEC(sampler2d_vec4_fixed,			FUNCTION_TEXTUREPROJLOD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	-1.0f,	9.0f,	true,	IVec3(-8, 7, 0),	tex2DMipmapFixed,		evalTexture2DProjLodOffset,		BOTH),
+		CASE_SPEC(sampler2d_vec4_float,			FUNCTION_TEXTUREPROJLOD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	-1.0f,	9.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapFloat,		evalTexture2DProjLodOffset,		BOTH),
+		CASE_SPEC(isampler2d_vec4,				FUNCTION_TEXTUREPROJLOD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	-1.0f,	9.0f,	true,	IVec3(-8, 7, 0),	tex2DMipmapInt,			evalTexture2DProjLodOffset,		BOTH),
+		CASE_SPEC(usampler2d_vec4,				FUNCTION_TEXTUREPROJLOD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	false,	-1.0f,	9.0f,	true,	IVec3(7, -8, 0),	tex2DMipmapUint,		evalTexture2DProjLodOffset,		BOTH),
+
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXTUREPROJLOD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	-1.0f,	7.0f,	true,	IVec3(-8, 7, 3),	tex3DMipmapFixed,		evalTexture3DProjLodOffset,		BOTH),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXTUREPROJLOD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	-1.0f,	7.0f,	true,	IVec3(7, 3, -8),	tex3DMipmapFloat,		evalTexture3DProjLodOffset,		BOTH),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXTUREPROJLOD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	-1.0f,	7.0f,	true,	IVec3(3, -8, 7),	tex3DMipmapInt,			evalTexture3DProjLodOffset,		BOTH),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXTUREPROJLOD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	false,	-1.0f,	7.0f,	true,	IVec3(-8, 7, 3),	tex3DMipmapUint,		evalTexture3DProjLodOffset,		BOTH),
+
+		CASE_SPEC(sampler2dshadow,				FUNCTION_TEXTUREPROJLOD,	Vec4( 0.2f, 0.6f,  0.0f,  1.5f),	Vec4(-2.25f, -3.45f, 1.5f,  1.5f),	false,	-1.0f,	9.0f,	true,	IVec3(-8, 7, 0),	tex2DMipmapShadow,		evalTexture2DShadowProjLodOffset,	BOTH)
+	};
+	createCaseGroup(this, "textureprojlodoffset", "textureProjLodOffset() Tests", textureProjLodOffsetCases, DE_LENGTH_OF_ARRAY(textureProjLodOffsetCases));
+
+	// textureGrad() cases
+	// \note Only one of dudx, dudy, dvdx, dvdy is non-zero since spec allows approximating p from derivates by various methods.
+	static const TexFuncCaseSpec textureGradCases[] =
+	{
+		//		  Name							Function				MinCoord							MaxCoord							MinDx						MaxDx						MinDy						MaxDy						Offset?	Offset		Format					EvalFunc				Flags
+		GRAD_CASE_SPEC(sampler2d_fixed,			FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DGrad,		BOTH),
+		GRAD_CASE_SPEC(sampler2d_float,			FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DGrad,		BOTH),
+		GRAD_CASE_SPEC(isampler2d,				FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DGrad,		BOTH),
+		GRAD_CASE_SPEC(usampler2d,				FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DGrad,		BOTH),
+
+		GRAD_CASE_SPEC(samplercube_fixed,		FUNCTION_TEXTUREGRAD,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	texCubeMipmapFixed,		evalTextureCubeGrad,	BOTH),
+		GRAD_CASE_SPEC(samplercube_float,		FUNCTION_TEXTUREGRAD,	Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	texCubeMipmapFloat,		evalTextureCubeGrad,	BOTH),
+		GRAD_CASE_SPEC(isamplercube,			FUNCTION_TEXTUREGRAD,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	false,	IVec3(0),	texCubeMipmapInt,		evalTextureCubeGrad,	BOTH),
+		GRAD_CASE_SPEC(usamplercube,			FUNCTION_TEXTUREGRAD,	Vec4(-1.0f, -1.0f, -1.01f,  0.0f),	Vec4( 1.0f,  1.0f, -1.01f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	false,	IVec3(0),	texCubeMipmapUint,		evalTextureCubeGrad,	BOTH),
+
+		GRAD_CASE_SPEC(sampler2darray_fixed,	FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DArrayMipmapFixed,	evalTexture2DArrayGrad,	BOTH),
+		GRAD_CASE_SPEC(sampler2darray_float,	FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DArrayMipmapFloat,	evalTexture2DArrayGrad,	BOTH),
+		GRAD_CASE_SPEC(isampler2darray,			FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DArrayMipmapInt,	evalTexture2DArrayGrad,	BOTH),
+		GRAD_CASE_SPEC(usampler2darray,			FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	false,	IVec3(0),	tex2DArrayMipmapUint,	evalTexture2DArrayGrad,	BOTH),
+
+		GRAD_CASE_SPEC(sampler3d_fixed,			FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex3DMipmapFixed,		evalTexture3DGrad,		BOTH),
+		GRAD_CASE_SPEC(sampler3d_float,			FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex3DMipmapFloat,		evalTexture3DGrad,		VERTEX),
+		GRAD_CASE_SPEC(sampler3d_float,			FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.2f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex3DMipmapFloat,		evalTexture3DGrad,		FRAGMENT),
+		GRAD_CASE_SPEC(isampler3d,				FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	false,	IVec3(0),	tex3DMipmapInt,			evalTexture3DGrad,		BOTH),
+		GRAD_CASE_SPEC(usampler3d,				FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	false,	IVec3(0),	tex3DMipmapUint,		evalTexture3DGrad,		VERTEX),
+		GRAD_CASE_SPEC(usampler3d,				FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f, -0.2f),	false,	IVec3(0),	tex3DMipmapUint,		evalTexture3DGrad,		FRAGMENT),
+
+		GRAD_CASE_SPEC(sampler2dshadow,			FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapShadow,		evalTexture2DShadowGrad,		BOTH),
+		GRAD_CASE_SPEC(samplercubeshadow,		FUNCTION_TEXTUREGRAD,	Vec4(-1.0f, -1.0f,  1.01f,  0.0f),	Vec4( 1.0f,  1.0f,  1.01f,  1.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	texCubeMipmapShadow,	evalTextureCubeShadowGrad,		BOTH),
+		GRAD_CASE_SPEC(sampler2darrayshadow,	FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  1.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DArrayMipmapShadow,	evalTexture2DArrayShadowGrad,	VERTEX),
+		GRAD_CASE_SPEC(sampler2darrayshadow,	FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  1.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	false,	IVec3(0),	tex2DArrayMipmapShadow,	evalTexture2DArrayShadowGrad,	FRAGMENT)
+	};
+	createCaseGroup(this, "texturegrad", "textureGrad() Tests", textureGradCases, DE_LENGTH_OF_ARRAY(textureGradCases));
+
+	// textureGradOffset() cases
+	static const TexFuncCaseSpec textureGradOffsetCases[] =
+	{
+		//		  Name							Function				MinCoord							MaxCoord							MinDx						MaxDx						MinDy						MaxDy						Offset?	Offset				Format					EvalFunc							Flags
+		GRAD_CASE_SPEC(sampler2d_fixed,			FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DMipmapFixed,		evalTexture2DGradOffset,			BOTH),
+		GRAD_CASE_SPEC(sampler2d_float,			FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DMipmapFloat,		evalTexture2DGradOffset,			BOTH),
+		GRAD_CASE_SPEC(isampler2d,				FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DMipmapInt,			evalTexture2DGradOffset,			BOTH),
+		GRAD_CASE_SPEC(usampler2d,				FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DMipmapUint,		evalTexture2DGradOffset,			BOTH),
+
+		GRAD_CASE_SPEC(sampler2darray_fixed,	FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DArrayMipmapFixed,	evalTexture2DArrayGradOffset,		BOTH),
+		GRAD_CASE_SPEC(sampler2darray_float,	FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DArrayMipmapFloat,	evalTexture2DArrayGradOffset,		BOTH),
+		GRAD_CASE_SPEC(isampler2darray,			FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DArrayMipmapInt,	evalTexture2DArrayGradOffset,		BOTH),
+		GRAD_CASE_SPEC(usampler2darray,			FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DArrayMipmapUint,	evalTexture2DArrayGradOffset,		BOTH),
+
+		GRAD_CASE_SPEC(sampler3d_fixed,			FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 3),	tex3DMipmapFixed,		evalTexture3DGradOffset,			BOTH),
+		GRAD_CASE_SPEC(sampler3d_float,			FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(7, 3, -8),	tex3DMipmapFloat,		evalTexture3DGradOffset,			VERTEX),
+		GRAD_CASE_SPEC(sampler3d_float,			FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.2f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(3, -8, 7),	tex3DMipmapFloat,		evalTexture3DGradOffset,			FRAGMENT),
+		GRAD_CASE_SPEC(isampler3d,				FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 3),	tex3DMipmapInt,			evalTexture3DGradOffset,			BOTH),
+		GRAD_CASE_SPEC(usampler3d,				FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	true,	IVec3(7, 3, -8),	tex3DMipmapUint,		evalTexture3DGradOffset,			VERTEX),
+		GRAD_CASE_SPEC(usampler3d,				FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -1.4f,  0.1f,  0.0f),	Vec4( 1.5f,  2.3f,  2.3f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f, -0.2f),	true,	IVec3(3, -8, 7),	tex3DMipmapUint,		evalTexture3DGradOffset,			FRAGMENT),
+
+		GRAD_CASE_SPEC(sampler2dshadow,			FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DMipmapShadow,		evalTexture2DShadowGradOffset,		VERTEX),
+		GRAD_CASE_SPEC(sampler2dshadow,			FUNCTION_TEXTUREGRAD,	Vec4(-0.2f, -0.4f,  0.0f,  0.0f),	Vec4( 1.5f,  2.3f,  1.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DMipmapShadow,		evalTexture2DShadowGradOffset,		FRAGMENT),
+		GRAD_CASE_SPEC(sampler2darrayshadow,	FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  1.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DArrayMipmapShadow,	evalTexture2DArrayShadowGradOffset,	VERTEX),
+		GRAD_CASE_SPEC(sampler2darrayshadow,	FUNCTION_TEXTUREGRAD,	Vec4(-1.2f, -0.4f,  -0.5f,  0.0f),	Vec4( 1.5f,  2.3f,  3.5f,  1.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DArrayMipmapShadow,	evalTexture2DArrayShadowGradOffset,	FRAGMENT)
+	};
+	createCaseGroup(this, "texturegradoffset", "textureGradOffset() Tests", textureGradOffsetCases, DE_LENGTH_OF_ARRAY(textureGradOffsetCases));
+
+	// textureProjGrad() cases
+	static const TexFuncCaseSpec textureProjGradCases[] =
+	{
+		//		  Name							Function					MinCoord							MaxCoord							MinDx						MaxDx						MinDy						MaxDy						Offset?	Offset		Format					EvalFunc					Flags
+		GRAD_CASE_SPEC(sampler2d_vec3_fixed,	FUNCTION_TEXTUREPROJGRAD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DProjGrad3,		BOTH),
+		GRAD_CASE_SPEC(sampler2d_vec3_float,	FUNCTION_TEXTUREPROJGRAD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DProjGrad3,		BOTH),
+		GRAD_CASE_SPEC(isampler2d_vec3,			FUNCTION_TEXTUREPROJGRAD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DProjGrad3,		BOTH),
+		GRAD_CASE_SPEC(usampler2d_vec3,			FUNCTION_TEXTUREPROJGRAD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DProjGrad3,		BOTH),
+
+		GRAD_CASE_SPEC(sampler2d_vec4_fixed,	FUNCTION_TEXTUREPROJGRAD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapFixed,		evalTexture2DProjGrad,		BOTH),
+		GRAD_CASE_SPEC(sampler2d_vec4_float,	FUNCTION_TEXTUREPROJGRAD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapFloat,		evalTexture2DProjGrad,		BOTH),
+		GRAD_CASE_SPEC(isampler2d_vec4,			FUNCTION_TEXTUREPROJGRAD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapInt,			evalTexture2DProjGrad,		BOTH),
+		GRAD_CASE_SPEC(usampler2d_vec4,			FUNCTION_TEXTUREPROJGRAD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	false,	IVec3(0),	tex2DMipmapUint,		evalTexture2DProjGrad,		BOTH),
+
+		GRAD_CASE_SPEC(sampler3d_fixed,			FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex3DMipmapFixed,		evalTexture3DProjGrad,		BOTH),
+		GRAD_CASE_SPEC(sampler3d_float,			FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex3DMipmapFloat,		evalTexture3DProjGrad,		VERTEX),
+		GRAD_CASE_SPEC(sampler3d_float,			FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.2f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex3DMipmapFloat,		evalTexture3DProjGrad,		FRAGMENT),
+		GRAD_CASE_SPEC(isampler3d,				FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	false,	IVec3(0),	tex3DMipmapInt,			evalTexture3DProjGrad,		BOTH),
+		GRAD_CASE_SPEC(usampler3d,				FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	false,	IVec3(0),	tex3DMipmapUint,		evalTexture3DProjGrad,		VERTEX),
+		GRAD_CASE_SPEC(usampler3d,				FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f, -0.2f),	false,	IVec3(0),	tex3DMipmapUint,		evalTexture3DProjGrad,		FRAGMENT),
+
+		GRAD_CASE_SPEC(sampler2dshadow,			FUNCTION_TEXTUREPROJGRAD,	Vec4( 0.2f, 0.6f,  0.0f,  -1.5f),	Vec4(-2.25f, -3.45f, -1.5f, -1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	false,	IVec3(0),	tex2DMipmapShadow,		evalTexture2DShadowProjGrad,	VERTEX),
+		GRAD_CASE_SPEC(sampler2dshadow,			FUNCTION_TEXTUREPROJGRAD,	Vec4( 0.2f, 0.6f,  0.0f,  -1.5f),	Vec4(-2.25f, -3.45f, -1.5f, -1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	false,	IVec3(0),	tex2DMipmapShadow,		evalTexture2DShadowProjGrad,	FRAGMENT)
+	};
+	createCaseGroup(this, "textureprojgrad", "textureProjGrad() Tests", textureProjGradCases, DE_LENGTH_OF_ARRAY(textureProjGradCases));
+
+	// textureProjGradOffset() cases
+	static const TexFuncCaseSpec textureProjGradOffsetCases[] =
+	{
+		//		  Name							Function					MinCoord							MaxCoord							MinDx						MaxDx						MinDy						MaxDy						Offset?	Offset				Format					EvalFunc							Flags
+		GRAD_CASE_SPEC(sampler2d_vec3_fixed,	FUNCTION_TEXTUREPROJGRAD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DMipmapFixed,		evalTexture2DProjGrad3Offset,		BOTH),
+		GRAD_CASE_SPEC(sampler2d_vec3_float,	FUNCTION_TEXTUREPROJGRAD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DMipmapFloat,		evalTexture2DProjGrad3Offset,		BOTH),
+		GRAD_CASE_SPEC(isampler2d_vec3,			FUNCTION_TEXTUREPROJGRAD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DMipmapInt,			evalTexture2DProjGrad3Offset,		BOTH),
+		GRAD_CASE_SPEC(usampler2d_vec3,			FUNCTION_TEXTUREPROJGRAD3,	Vec4(-0.3f, -0.6f,  1.5f,  0.0f),	Vec4(2.25f, 3.45f,  1.5f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DMipmapUint,		evalTexture2DProjGrad3Offset,		BOTH),
+
+		GRAD_CASE_SPEC(sampler2d_vec4_fixed,	FUNCTION_TEXTUREPROJGRAD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DMipmapFixed,		evalTexture2DProjGradOffset,		BOTH),
+		GRAD_CASE_SPEC(sampler2d_vec4_float,	FUNCTION_TEXTUREPROJGRAD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DMipmapFloat,		evalTexture2DProjGradOffset,		BOTH),
+		GRAD_CASE_SPEC(isampler2d_vec4,			FUNCTION_TEXTUREPROJGRAD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DMipmapInt,			evalTexture2DProjGradOffset,		BOTH),
+		GRAD_CASE_SPEC(usampler2d_vec4,			FUNCTION_TEXTUREPROJGRAD,	Vec4(-0.3f, -0.6f,  0.0f,  1.5f),	Vec4(2.25f, 3.45f,  0.0f,  1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DMipmapUint,		evalTexture2DProjGradOffset,		BOTH),
+
+		GRAD_CASE_SPEC(sampler3d_fixed,			FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 3),	tex3DMipmapFixed,		evalTexture3DProjGradOffset,		BOTH),
+		GRAD_CASE_SPEC(sampler3d_float,			FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(7, 3, -8),	tex3DMipmapFloat,		evalTexture3DProjGradOffset,		VERTEX),
+		GRAD_CASE_SPEC(sampler3d_float,			FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.2f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(3, -8, 7),	tex3DMipmapFloat,		evalTexture3DProjGradOffset,		FRAGMENT),
+		GRAD_CASE_SPEC(isampler3d,				FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3(-0.2f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 3),	tex3DMipmapInt,			evalTexture3DProjGradOffset,		BOTH),
+		GRAD_CASE_SPEC(usampler3d,				FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.2f,  0.0f),	true,	IVec3(7, 3, -8),	tex3DMipmapUint,		evalTexture3DProjGradOffset,		VERTEX),
+		GRAD_CASE_SPEC(usampler3d,				FUNCTION_TEXTUREPROJGRAD,	Vec4(0.9f, 1.05f, -0.08f, -0.75f),	Vec4(-1.13f, -1.7f, -1.7f, -0.75f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f, -0.2f),	true,	IVec3(3, -8, 7),	tex3DMipmapUint,		evalTexture3DProjGradOffset,		FRAGMENT),
+
+		GRAD_CASE_SPEC(sampler2dshadow,			FUNCTION_TEXTUREPROJGRAD,	Vec4( 0.2f, 0.6f,  0.0f,  -1.5f),	Vec4(-2.25f, -3.45f, -1.5f, -1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.2f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	true,	IVec3(-8, 7, 0),	tex2DMipmapShadow,		evalTexture2DShadowProjGradOffset,	VERTEX),
+		GRAD_CASE_SPEC(sampler2dshadow,			FUNCTION_TEXTUREPROJGRAD,	Vec4( 0.2f, 0.6f,  0.0f,  -1.5f),	Vec4(-2.25f, -3.45f, -1.5f, -1.5f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f,  0.0f,  0.0f),	Vec3( 0.0f, -0.2f,  0.0f),	true,	IVec3(7, -8, 0),	tex2DMipmapShadow,		evalTexture2DShadowProjGradOffset,	FRAGMENT)
+	};
+	createCaseGroup(this, "textureprojgradoffset", "textureProjGradOffset() Tests", textureProjGradOffsetCases, DE_LENGTH_OF_ARRAY(textureProjGradOffsetCases));
+
+	// texelFetch() cases
+	// \note Level is constant across quad
+	static const TexFuncCaseSpec texelFetchCases[] =
+	{
+		//		  Name							Function				MinCoord							MaxCoord						Bias?	MinLod	MaxLod	Offset?	Offset		Format						EvalFunc				Flags
+		CASE_SPEC(sampler2d_fixed,				FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4(255.9f, 255.9f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DTexelFetchFixed,		evalTexelFetch2D,		BOTH),
+		CASE_SPEC(sampler2d_float,				FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4(127.9f, 127.9f,  0.0f,  0.0f),	false,	1.0f,	1.0f,	false,	IVec3(0),	tex2DTexelFetchFloat,		evalTexelFetch2D,		BOTH),
+		CASE_SPEC(isampler2d,					FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4( 63.9f,  63.9f,  0.0f,  0.0f),	false,	2.0f,	2.0f,	false,	IVec3(0),	tex2DTexelFetchInt,			evalTexelFetch2D,		BOTH),
+		CASE_SPEC(usampler2d,					FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4( 15.9f,  15.9f,  0.0f,  0.0f),	false,	4.0f,	4.0f,	false,	IVec3(0),	tex2DTexelFetchUint,		evalTexelFetch2D,		BOTH),
+
+		CASE_SPEC(sampler2darray_fixed,			FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4(127.9f, 127.9f,  3.9f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex2DArrayTexelFetchFixed,	evalTexelFetch2DArray,	BOTH),
+		CASE_SPEC(sampler2darray_float,			FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4( 63.9f,  63.9f,  3.9f,  0.0f),	false,	1.0f,	1.0f,	false,	IVec3(0),	tex2DArrayTexelFetchFloat,	evalTexelFetch2DArray,	BOTH),
+		CASE_SPEC(isampler2darray,				FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4( 31.9f,  31.9f,  3.9f,  0.0f),	false,	2.0f,	2.0f,	false,	IVec3(0),	tex2DArrayTexelFetchInt,	evalTexelFetch2DArray,	BOTH),
+		CASE_SPEC(usampler2darray,				FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4( 15.9f,  15.9f,  3.9f,  0.0f),	false,	3.0f,	3.0f,	false,	IVec3(0),	tex2DArrayTexelFetchUint,	evalTexelFetch2DArray,	BOTH),
+
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4(63.9f,  31.9f,  31.9f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DTexelFetchFixed,		evalTexelFetch3D,		BOTH),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4(31.9f,  15.9f,  15.9f,  0.0f),	false,	1.0f,	1.0f,	false,	IVec3(0),	tex3DTexelFetchFloat,		evalTexelFetch3D,		BOTH),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4(15.9f,   7.9f,   7.9f,  0.0f),	false,	2.0f,	2.0f,	false,	IVec3(0),	tex3DTexelFetchInt,			evalTexelFetch3D,		BOTH),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXELFETCH,	Vec4(0.0f, 0.0f, 0.0f, 0.0f),	Vec4(63.9f,  31.9f,  31.9f,  0.0f),	false,	0.0f,	0.0f,	false,	IVec3(0),	tex3DTexelFetchUint,		evalTexelFetch3D,		BOTH)
+	};
+	createCaseGroup(this, "texelfetch", "texelFetch() Tests", texelFetchCases, DE_LENGTH_OF_ARRAY(texelFetchCases));
+
+	// texelFetchOffset() cases
+	static const TexFuncCaseSpec texelFetchOffsetCases[] =
+	{
+		//		  Name							Function				MinCoord							MaxCoord						Bias?	MinLod	MaxLod	Offset?	Offset		Format						EvalFunc				Flags
+		CASE_SPEC(sampler2d_fixed,				FUNCTION_TEXELFETCH,	Vec4( 8.0f, -7.0f, 0.0f, 0.0f),	Vec4(263.9f, 248.9f,  0.0f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DTexelFetchFixed,		evalTexelFetch2D,		BOTH),
+		CASE_SPEC(sampler2d_float,				FUNCTION_TEXELFETCH,	Vec4(-7.0f,  8.0f, 0.0f, 0.0f),	Vec4(120.9f, 135.9f,  0.0f,  0.0f),	false,	1.0f,	1.0f,	true,	IVec3(7, -8, 0),	tex2DTexelFetchFloat,		evalTexelFetch2D,		BOTH),
+		CASE_SPEC(isampler2d,					FUNCTION_TEXELFETCH,	Vec4( 8.0f, -7.0f, 0.0f, 0.0f),	Vec4( 71.9f,  56.9f,  0.0f,  0.0f),	false,	2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DTexelFetchInt,			evalTexelFetch2D,		BOTH),
+		CASE_SPEC(usampler2d,					FUNCTION_TEXELFETCH,	Vec4(-7.0f,  8.0f, 0.0f, 0.0f),	Vec4(  8.9f,  23.9f,  0.0f,  0.0f),	false,	4.0f,	4.0f,	true,	IVec3(7, -8, 0),	tex2DTexelFetchUint,		evalTexelFetch2D,		BOTH),
+
+		CASE_SPEC(sampler2darray_fixed,			FUNCTION_TEXELFETCH,	Vec4( 8.0f, -7.0f, 0.0f, 0.0f),	Vec4(135.9f, 120.9f,  3.9f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 0),	tex2DArrayTexelFetchFixed,	evalTexelFetch2DArray,	BOTH),
+		CASE_SPEC(sampler2darray_float,			FUNCTION_TEXELFETCH,	Vec4(-7.0f,  8.0f, 0.0f, 0.0f),	Vec4( 56.9f,  71.9f,  3.9f,  0.0f),	false,	1.0f,	1.0f,	true,	IVec3(7, -8, 0),	tex2DArrayTexelFetchFloat,	evalTexelFetch2DArray,	BOTH),
+		CASE_SPEC(isampler2darray,				FUNCTION_TEXELFETCH,	Vec4( 8.0f, -7.0f, 0.0f, 0.0f),	Vec4( 39.9f,  24.9f,  3.9f,  0.0f),	false,	2.0f,	2.0f,	true,	IVec3(-8, 7, 0),	tex2DArrayTexelFetchInt,	evalTexelFetch2DArray,	BOTH),
+		CASE_SPEC(usampler2darray,				FUNCTION_TEXELFETCH,	Vec4(-7.0f,  8.0f, 0.0f, 0.0f),	Vec4(  8.9f,  23.9f,  3.9f,  0.0f),	false,	3.0f,	3.0f,	true,	IVec3(7, -8, 0),	tex2DArrayTexelFetchUint,	evalTexelFetch2DArray,	BOTH),
+
+		CASE_SPEC(sampler3d_fixed,				FUNCTION_TEXELFETCH,	Vec4( 8.0f, -7.0f, -3.0f, 0.0f),Vec4(71.9f,  24.9f,  28.9f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 3),	tex3DTexelFetchFixed,		evalTexelFetch3D,		BOTH),
+		CASE_SPEC(sampler3d_float,				FUNCTION_TEXELFETCH,	Vec4(-7.0f, -3.0f,  8.0f, 0.0f),Vec4(24.9f,  12.9f,  23.9f,  0.0f),	false,	1.0f,	1.0f,	true,	IVec3(7, 3, -8),	tex3DTexelFetchFloat,		evalTexelFetch3D,		BOTH),
+		CASE_SPEC(isampler3d,					FUNCTION_TEXELFETCH,	Vec4(-3.0f,  8.0f, -7.0f, 0.0f),Vec4(12.9f,  15.9f,   0.9f,  0.0f),	false,	2.0f,	2.0f,	true,	IVec3(3, -8, 7),	tex3DTexelFetchInt,			evalTexelFetch3D,		BOTH),
+		CASE_SPEC(usampler3d,					FUNCTION_TEXELFETCH,	Vec4( 8.0f, -7.0f, -3.0f, 0.0f),Vec4(71.9f,  24.9f,  28.9f,  0.0f),	false,	0.0f,	0.0f,	true,	IVec3(-8, 7, 3),	tex3DTexelFetchUint,		evalTexelFetch3D,		BOTH)
+	};
+	createCaseGroup(this, "texelfetchoffset", "texelFetchOffset() Tests", texelFetchOffsetCases, DE_LENGTH_OF_ARRAY(texelFetchOffsetCases));
+
+	// textureSize() cases
+	{
+		struct TextureSizeCaseSpec
+		{
+			const char* name;
+			const char* samplerName;
+			TextureSpec textureSpec;
+		} textureSizeCases[] =
+		{
+			{ "sampler2d_fixed",			"sampler2D",				tex2DFixed			},
+			{ "sampler2d_float",			"sampler2D",				tex2DFloat			},
+			{ "isampler2d",					"isampler2D",				tex2DInt			},
+			{ "usampler2d",					"usampler2D",				tex2DUint			},
+			{ "sampler2dshadow",			"sampler2DShadow",			tex2DShadow			},
+			{ "sampler3d_fixed",			"sampler3D",				tex3DFixed			},
+			{ "sampler3d_float",			"sampler3D",				tex3DFloat			},
+			{ "isampler3d",					"isampler3D",				tex3DInt			},
+			{ "usampler3d",					"usampler3D",				tex3DUint			},
+			{ "samplercube_fixed",			"samplerCube",				texCubeFixed		},
+			{ "samplercube_float",			"samplerCube",				texCubeFloat		},
+			{ "isamplercube",				"isamplerCube",				texCubeInt			},
+			{ "usamplercube",				"usamplerCube",				texCubeUint			},
+			{ "samplercubeshadow",			"samplerCubeShadow",		texCubeShadow		},
+			{ "sampler2darray_fixed",		"sampler2DArray",			tex2DArrayFixed		},
+			{ "sampler2darray_float",		"sampler2DArray",			tex2DArrayFloat		},
+			{ "isampler2darray",			"isampler2DArray",			tex2DArrayInt		},
+			{ "usampler2darray",			"usampler2DArray",			tex2DArrayUint		},
+			{ "sampler2darrayshadow",		"sampler2DArrayShadow",		tex2DArrayShadow	},
+		};
+
+		tcu::TestCaseGroup* group = new tcu::TestCaseGroup(m_testCtx, "texturesize", "textureSize() Tests");
+		addChild(group);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(textureSizeCases); ++ndx)
+		{
+			group->addChild(new TextureSizeCase(m_context, (std::string(textureSizeCases[ndx].name) + "_vertex").c_str(),   "", textureSizeCases[ndx].samplerName, textureSizeCases[ndx].textureSpec, true));
+			group->addChild(new TextureSizeCase(m_context, (std::string(textureSizeCases[ndx].name) + "_fragment").c_str(), "", textureSizeCases[ndx].samplerName, textureSizeCases[ndx].textureSpec, false));
+		}
+	}
+
+	// Negative cases.
+	{
+		gls::ShaderLibrary library(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
+		std::vector<tcu::TestNode*> negativeCases = library.loadShaderFile("shaders/invalid_texture_functions.test");
+
+		tcu::TestCaseGroup* group = new tcu::TestCaseGroup(m_testCtx, "invalid", "Invalid texture function usage", negativeCases);
+		addChild(group);
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fShaderTextureFunctionTests.hpp b/modules/gles3/functional/es3fShaderTextureFunctionTests.hpp
new file mode 100644
index 0000000..0d4c91a
--- /dev/null
+++ b/modules/gles3/functional/es3fShaderTextureFunctionTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FSHADERTEXTUREFUNCTIONTESTS_HPP
+#define _ES3FSHADERTEXTUREFUNCTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture access function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class ShaderTextureFunctionTests : public TestCaseGroup
+{
+public:
+									ShaderTextureFunctionTests		(Context& context);
+	virtual							~ShaderTextureFunctionTests		(void);
+
+	virtual void					init							(void);
+
+private:
+									ShaderTextureFunctionTests		(const ShaderTextureFunctionTests&);		// not allowed!
+	ShaderTextureFunctionTests&		operator=						(const ShaderTextureFunctionTests&);		// not allowed!
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSHADERTEXTUREFUNCTIONTESTS_HPP
diff --git a/modules/gles3/functional/es3fStencilTests.cpp b/modules/gles3/functional/es3fStencilTests.cpp
new file mode 100644
index 0000000..d9c613a
--- /dev/null
+++ b/modules/gles3/functional/es3fStencilTests.cpp
@@ -0,0 +1,571 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stencil tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fStencilTests.hpp"
+
+#include "tcuSurface.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "sglrContextUtil.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+#include <vector>
+
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+
+using tcu::Vec3;
+using tcu::IVec2;
+using tcu::IVec4;
+using std::vector;
+using namespace glw;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class StencilShader : public sglr::ShaderProgram
+{
+public:
+	StencilShader (void)
+		: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+									<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+									<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+									<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+									<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
+									<< sglr::pdec::VertexSource("#version 300 es\n"
+																"in highp vec4 a_position;\n"
+																"void main (void)\n"
+																"{\n"
+																"	gl_Position = a_position;\n"
+																"}\n")
+									<< sglr::pdec::FragmentSource("#version 300 es\n"
+																  "uniform highp vec4 u_color;\n"
+																  "layout(location = 0) out mediump vec4 o_color;\n"
+																  "void main (void)\n"
+																  "{\n"
+																  "	o_color = u_color;\n"
+																  "}\n"))
+		, u_color	(getUniformByName("u_color"))
+	{
+	}
+
+	void setColor (sglr::Context& ctx, deUint32 program, const tcu::Vec4& color)
+	{
+		ctx.useProgram(program);
+		ctx.uniform4fv(ctx.getUniformLocation(program, "u_color"), 1, color.getPtr());
+	}
+
+private:
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		{
+			rr::VertexPacket& packet = *packets[packetNdx];
+
+			packet.position = rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		}
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		const tcu::Vec4 color(u_color.value.f4);
+
+		DE_UNREF(packets);
+
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+	}
+
+	const sglr::UniformSlot& u_color;
+};
+
+class StencilOp
+{
+public:
+	enum Type
+	{
+		TYPE_CLEAR_STENCIL = 0,
+		TYPE_CLEAR_DEPTH,
+		TYPE_QUAD,
+
+		TYPE_LAST
+	};
+
+	Type		type;
+	GLenum		stencilTest;
+	int			stencil;	//!< Ref for quad op, clear value for clears
+	deUint32	stencilMask;
+	GLenum		depthTest;
+	float		depth;		//!< Quad depth or clear value
+	GLenum		sFail;
+	GLenum		dFail;
+	GLenum		dPass;
+
+	StencilOp (Type type_, GLenum stencilTest_ = GL_ALWAYS, int stencil_ = 0, GLenum depthTest_ = GL_ALWAYS, float depth_ = 1.0f, GLenum sFail_ = GL_KEEP, GLenum dFail_ = GL_KEEP, GLenum dPass_ = GL_KEEP)
+		: type			(type_)
+		, stencilTest	(stencilTest_)
+		, stencil		(stencil_)
+		, stencilMask	(0xffffffffu)
+		, depthTest		(depthTest_)
+		, depth			(depth_)
+		, sFail			(sFail_)
+		, dFail			(dFail_)
+		, dPass			(dPass_)
+	{
+	}
+
+	static StencilOp clearStencil (int stencil)
+	{
+		StencilOp op(TYPE_CLEAR_STENCIL);
+		op.stencil = stencil;
+		return op;
+	}
+
+	static StencilOp clearDepth (float depth)
+	{
+		StencilOp op(TYPE_CLEAR_DEPTH);
+		op.depth = depth;
+		return op;
+	}
+
+	static StencilOp quad (GLenum stencilTest, int stencil, GLenum depthTest, float depth, GLenum sFail, GLenum dFail, GLenum dPass)
+	{
+		return StencilOp(TYPE_QUAD, stencilTest, stencil, depthTest, depth, sFail, dFail, dPass);
+	}
+};
+
+class StencilCase : public TestCase
+{
+public:
+						StencilCase			(Context& context, const char* name, const char* description);
+	virtual				~StencilCase		(void);
+
+	void				init				(void);
+	void				deinit				(void);
+	IterateResult		iterate				(void);
+
+	virtual void		genOps				(vector<StencilOp>& dst, int stencilBits, int depthBits, int targetStencil) = DE_NULL;
+
+private:
+	void				executeOps			(sglr::Context& context, const IVec4& cell, const vector<StencilOp>& ops);
+	void				visualizeStencil	(sglr::Context& context, int stencilBits, int stencilStep);
+
+	StencilShader		m_shader;
+	deUint32			m_shaderID;
+};
+
+StencilCase::StencilCase (Context& context, const char* name, const char* description)
+	: TestCase		(context, name, description)
+	, m_shaderID	(0)
+{
+}
+
+StencilCase::~StencilCase (void)
+{
+}
+
+void StencilCase::init (void)
+{
+}
+
+void StencilCase::deinit (void)
+{
+}
+
+void StencilCase::executeOps (sglr::Context& context, const IVec4& cell, const vector<StencilOp>& ops)
+{
+	// For quadOps
+	float x0 = 2.0f*((float)cell.x() / (float)context.getWidth())-1.0f;
+	float y0 = 2.0f*((float)cell.y() / (float)context.getHeight())-1.0f;
+	float x1 = x0 + 2.0f*((float)cell.z() / (float)context.getWidth());
+	float y1 = y0 + 2.0f*((float)cell.w() / (float)context.getHeight());
+
+	m_shader.setColor(context, m_shaderID, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	for (vector<StencilOp>::const_iterator i = ops.begin(); i != ops.end(); i++)
+	{
+		const StencilOp& op = *i;
+
+		switch (op.type)
+		{
+			case StencilOp::TYPE_CLEAR_DEPTH:
+				context.enable(GL_SCISSOR_TEST);
+				context.scissor(cell.x(), cell.y(), cell.z(), cell.w());
+				context.clearDepthf(op.depth);
+				context.clear(GL_DEPTH_BUFFER_BIT);
+				context.disable(GL_SCISSOR_TEST);
+				break;
+
+			case StencilOp::TYPE_CLEAR_STENCIL:
+				context.enable(GL_SCISSOR_TEST);
+				context.scissor(cell.x(), cell.y(), cell.z(), cell.w());
+				context.clearStencil(op.stencil);
+				context.clear(GL_STENCIL_BUFFER_BIT);
+				context.disable(GL_SCISSOR_TEST);
+				break;
+
+			case StencilOp::TYPE_QUAD:
+				context.enable(GL_DEPTH_TEST);
+				context.enable(GL_STENCIL_TEST);
+				context.depthFunc(op.depthTest);
+				context.stencilFunc(op.stencilTest, op.stencil, op.stencilMask);
+				context.stencilOp(op.sFail, op.dFail, op.dPass);
+				sglr::drawQuad(context, m_shaderID, Vec3(x0, y0, op.depth), Vec3(x1, y1, op.depth));
+				context.disable(GL_STENCIL_TEST);
+				context.disable(GL_DEPTH_TEST);
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+void StencilCase::visualizeStencil (sglr::Context& context, int stencilBits, int stencilStep)
+{
+	int endVal				= 1<<stencilBits;
+	int numStencilValues	= endVal/stencilStep + 1;
+
+	context.enable(GL_STENCIL_TEST);
+	context.stencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+
+	for (int ndx = 0; ndx < numStencilValues; ndx++)
+	{
+		int			value		= deMin32(ndx*stencilStep, endVal-1);
+		float		colorMix	= (float)value/(float)de::max(1, endVal-1);
+		tcu::Vec4	color		(0.0f, 1.0f-colorMix, colorMix, 1.0f);
+
+		m_shader.setColor(context, m_shaderID, color);
+		context.stencilFunc(GL_EQUAL, value, 0xffffffffu);
+		sglr::drawQuad(context, m_shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
+	}
+}
+
+TestCase::IterateResult StencilCase::iterate (void)
+{
+	const tcu::RenderTarget&	renderTarget		= m_context.getRenderContext().getRenderTarget();
+	int							depthBits			= renderTarget.getDepthBits();
+	int							stencilBits			= renderTarget.getStencilBits();
+
+	int							stencilStep			= stencilBits == 8 ? 8 : 1;
+	int							numStencilValues	= (1<<stencilBits)/stencilStep + 1;
+
+	int							gridSize			= (int)deFloatCeil(deFloatSqrt((float)(numStencilValues+2)));
+
+	int							width				= deMin32(128, renderTarget.getWidth());
+	int							height				= deMin32(128, renderTarget.getHeight());
+
+	tcu::TestLog&				log					= m_testCtx.getLog();
+	de::Random					rnd					(deStringHash(m_name.c_str()));
+	int							viewportX			= rnd.getInt(0, renderTarget.getWidth()-width);
+	int							viewportY			= rnd.getInt(0, renderTarget.getHeight()-height);
+	IVec4						viewport			= IVec4(viewportX, viewportY, width, height);
+
+	tcu::Surface				gles2Frame			(width, height);
+	tcu::Surface				refFrame			(width, height);
+	GLenum						gles2Error;
+
+	const char*					failReason			= DE_NULL;
+
+	// Get ops for stencil values
+	vector<vector<StencilOp> >	ops(numStencilValues+2);
+	{
+		// Values from 0 to max
+		for (int ndx = 0; ndx < numStencilValues; ndx++)
+			genOps(ops[ndx], stencilBits, depthBits, deMin32(ndx*stencilStep, (1<<stencilBits)-1));
+
+		// -1 and max+1
+		genOps(ops[numStencilValues+0], stencilBits, depthBits, 1<<stencilBits);
+		genOps(ops[numStencilValues+1], stencilBits, depthBits, -1);
+	}
+
+	// Compute cells: (x, y, w, h)
+	vector<IVec4>				cells;
+	int							cellWidth			= width/gridSize;
+	int							cellHeight			= height/gridSize;
+	for (int y = 0; y < gridSize; y++)
+	for (int x = 0; x < gridSize; x++)
+		cells.push_back(IVec4(x*cellWidth, y*cellHeight, cellWidth, cellHeight));
+
+	DE_ASSERT(ops.size() <= cells.size());
+
+	// Execute for gles3 context
+	{
+		sglr::GLContext context(m_context.getRenderContext(), log, 0 /* don't log calls or program */, viewport);
+
+		m_shaderID = context.createProgram(&m_shader);
+
+		context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		for (int ndx = 0; ndx < (int)ops.size(); ndx++)
+			executeOps(context, cells[ndx], ops[ndx]);
+
+		visualizeStencil(context, stencilBits, stencilStep);
+
+		gles2Error = context.getError();
+		context.readPixels(gles2Frame, 0, 0, width, height);
+	}
+
+	// Execute for reference context
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), renderTarget.getDepthBits(), renderTarget.getStencilBits(), width, height);
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(m_context.getRenderContext()), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		m_shaderID = context.createProgram(&m_shader);
+
+		context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		for (int ndx = 0; ndx < (int)ops.size(); ndx++)
+			executeOps(context, cells[ndx], ops[ndx]);
+
+		visualizeStencil(context, stencilBits, stencilStep);
+
+		context.readPixels(refFrame, 0, 0, width, height);
+	}
+
+	// Check error
+	bool errorCodeOk = (gles2Error == GL_NO_ERROR);
+	if (!errorCodeOk && !failReason)
+		failReason = "Got unexpected error";
+
+	// Compare images
+	const float		threshold	= 0.02f;
+	bool			imagesOk	= tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, gles2Frame, threshold, tcu::COMPARE_LOG_RESULT);
+
+	if (!imagesOk && !failReason)
+		failReason = "Image comparison failed";
+
+	// Store test result
+	bool isOk = errorCodeOk && imagesOk;
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: failReason);
+
+	return STOP;
+}
+
+StencilTests::StencilTests (Context& context)
+	: TestCaseGroup(context, "stencil", "Stencil Tests")
+{
+}
+
+StencilTests::~StencilTests (void)
+{
+}
+
+typedef void (*GenStencilOpsFunc) (vector<StencilOp>& dst, int stencilBits, int depthBits, int targetStencil);
+
+class SimpleStencilCase : public StencilCase
+{
+public:
+	SimpleStencilCase (Context& context, const char* name, const char* description, GenStencilOpsFunc genOpsFunc)
+		: StencilCase	(context, name, description)
+		, m_genOps		(genOpsFunc)
+	{
+	}
+
+	void genOps (vector<StencilOp>& dst, int stencilBits, int depthBits, int targetStencil)
+	{
+		m_genOps(dst, stencilBits, depthBits, targetStencil);
+	}
+
+private:
+	GenStencilOpsFunc	m_genOps;
+};
+
+void StencilTests::init (void)
+{
+#define STENCIL_CASE(NAME, DESCRIPTION, GEN_OPS_BODY)														\
+	do {																									\
+		struct Gen_##NAME {																					\
+			static void genOps (vector<StencilOp>& dst, int stencilBits, int depthBits, int targetStencil)	\
+			{																								\
+				DE_UNREF(stencilBits && depthBits);															\
+				GEN_OPS_BODY																				\
+			}																								\
+		};																									\
+		addChild(new SimpleStencilCase(m_context, #NAME, DESCRIPTION, Gen_##NAME::genOps));					\
+	} while (deGetFalse());
+
+	STENCIL_CASE(clear, "Stencil clear",
+		{
+			// \note Unused bits are set to 1, clear should mask them out
+			int mask = (1<<stencilBits)-1;
+			dst.push_back(StencilOp::clearStencil(targetStencil | ~mask));
+		});
+
+	// Replace in different points
+	STENCIL_CASE(stencil_fail_replace, "Set stencil on stencil fail",
+		{
+			dst.push_back(StencilOp::quad(GL_NEVER, targetStencil, GL_ALWAYS, 0.0f, GL_REPLACE, GL_KEEP, GL_KEEP));
+		});
+	STENCIL_CASE(depth_fail_replace, "Set stencil on depth fail",
+		{
+			dst.push_back(StencilOp::clearDepth(0.0f));
+			dst.push_back(StencilOp::quad(GL_ALWAYS, targetStencil, GL_LESS, 0.5f, GL_KEEP, GL_REPLACE, GL_KEEP));
+		});
+	STENCIL_CASE(depth_pass_replace, "Set stencil on depth pass",
+		{
+			dst.push_back(StencilOp::quad(GL_ALWAYS, targetStencil, GL_LESS, 0.0f, GL_KEEP, GL_KEEP, GL_REPLACE));
+		});
+
+	// Increment, decrement
+	STENCIL_CASE(incr_stencil_fail, "Increment on stencil fail",
+		{
+			if (targetStencil > 0)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil-1));
+				dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_INCR, GL_KEEP, GL_KEEP));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(decr_stencil_fail, "Decrement on stencil fail",
+		{
+			int maxStencil = (1<<stencilBits)-1;
+			if (targetStencil < maxStencil)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil+1));
+				dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_DECR, GL_KEEP, GL_KEEP));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(incr_wrap_stencil_fail, "Increment (wrap) on stencil fail",
+		{
+			int maxStencil = (1<<stencilBits)-1;
+			dst.push_back(StencilOp::clearStencil((targetStencil-1)&maxStencil));
+			dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_INCR_WRAP, GL_KEEP, GL_KEEP));
+		});
+	STENCIL_CASE(decr_wrap_stencil_fail, "Decrement (wrap) on stencil fail",
+		{
+			int maxStencil = (1<<stencilBits)-1;
+			dst.push_back(StencilOp::clearStencil((targetStencil+1)&maxStencil));
+			dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_DECR_WRAP, GL_KEEP, GL_KEEP));
+		});
+
+	// Zero, Invert
+	STENCIL_CASE(zero_stencil_fail, "Zero on stencil fail",
+		{
+			dst.push_back(StencilOp::clearStencil(targetStencil));
+			dst.push_back(StencilOp::quad(GL_NOTEQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_ZERO, GL_KEEP, GL_KEEP));
+			dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_REPLACE, GL_KEEP, GL_KEEP));
+		});
+	STENCIL_CASE(invert_stencil_fail, "Invert on stencil fail",
+		{
+			int mask = (1<<stencilBits)-1;
+			dst.push_back(StencilOp::clearStencil((~targetStencil)&mask));
+			dst.push_back(StencilOp::quad(GL_EQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_INVERT, GL_KEEP, GL_KEEP));
+		});
+
+	// Comparison modes
+	STENCIL_CASE(cmp_equal, "Equality comparison",
+		{
+			int mask = (1<<stencilBits)-1;
+			int inv  = (~targetStencil)&mask;
+			dst.push_back(StencilOp::clearStencil(inv));
+			dst.push_back(StencilOp::quad(GL_EQUAL, inv, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INVERT));
+			dst.push_back(StencilOp::quad(GL_EQUAL, inv, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INVERT));
+		});
+	STENCIL_CASE(cmp_not_equal, "Equality comparison",
+		{
+			int mask = (1<<stencilBits)-1;
+			int inv  = (~targetStencil)&mask;
+			dst.push_back(StencilOp::clearStencil(inv));
+			dst.push_back(StencilOp::quad(GL_NOTEQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INVERT));
+			dst.push_back(StencilOp::quad(GL_NOTEQUAL, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INVERT));
+		});
+	STENCIL_CASE(cmp_less_than, "Less than comparison",
+		{
+			int maxStencil = (1<<stencilBits)-1;
+			if (targetStencil < maxStencil)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil+1));
+				dst.push_back(StencilOp::quad(GL_LESS, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_DECR));
+				dst.push_back(StencilOp::quad(GL_LESS, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_DECR));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(cmp_less_or_equal, "Less or equal comparison",
+		{
+			int maxStencil = (1<<stencilBits)-1;
+			if (targetStencil < maxStencil)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil+1));
+				dst.push_back(StencilOp::quad(GL_LEQUAL, targetStencil+1, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_DECR));
+				dst.push_back(StencilOp::quad(GL_LEQUAL, targetStencil+1, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_DECR));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(cmp_greater_than, "Greater than comparison",
+		{
+			if (targetStencil > 0)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil-1));
+				dst.push_back(StencilOp::quad(GL_GREATER, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INCR));
+				dst.push_back(StencilOp::quad(GL_GREATER, targetStencil, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INCR));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(cmp_greater_or_equal, "Greater or equal comparison",
+		{
+			if (targetStencil > 0)
+			{
+				dst.push_back(StencilOp::clearStencil(targetStencil-1));
+				dst.push_back(StencilOp::quad(GL_GEQUAL, targetStencil-1, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INCR));
+				dst.push_back(StencilOp::quad(GL_GEQUAL, targetStencil-1, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INCR));
+			}
+			else
+				dst.push_back(StencilOp::clearStencil(targetStencil));
+		});
+	STENCIL_CASE(cmp_mask_equal, "Equality comparison with mask",
+		{
+			int valMask = (1<<stencilBits)-1;
+			int mask	= (1<<7)|(1<<5)|(1<<3)|(1<<1);
+			dst.push_back(StencilOp::clearStencil(~targetStencil));
+			StencilOp op = StencilOp::quad(GL_EQUAL, (~targetStencil | ~mask) & valMask, GL_ALWAYS, 0.0f, GL_KEEP, GL_KEEP, GL_INVERT);
+			op.stencilMask = mask;
+			dst.push_back(op);
+		});
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fStencilTests.hpp b/modules/gles3/functional/es3fStencilTests.hpp
new file mode 100644
index 0000000..3369004
--- /dev/null
+++ b/modules/gles3/functional/es3fStencilTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FSTENCILTESTS_HPP
+#define _ES3FSTENCILTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stencil tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class StencilTests : public TestCaseGroup
+{
+public:
+						StencilTests			(Context& context);
+						~StencilTests			(void);
+
+	void				init					(void);
+
+private:
+						StencilTests			(const StencilTests& other);
+	StencilTests&		operator=				(const StencilTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSTENCILTESTS_HPP
diff --git a/modules/gles3/functional/es3fStringQueryTests.cpp b/modules/gles3/functional/es3fStringQueryTests.cpp
new file mode 100644
index 0000000..ea3a5ea
--- /dev/null
+++ b/modules/gles3/functional/es3fStringQueryTests.cpp
@@ -0,0 +1,214 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief String Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fStringQueryTests.hpp"
+#include "es3fApiCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deString.h"
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+
+using namespace glw; // GLint and other GL types
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+StringQueryTests::StringQueryTests (Context& context)
+	: TestCaseGroup (context, "string", "String Query tests")
+{
+}
+
+StringQueryTests::~StringQueryTests (void)
+{
+}
+
+void StringQueryTests::init (void)
+{
+	using tcu::TestLog;
+
+	ES3F_ADD_API_CASE(renderer, "RENDERER",
+	{
+		const GLubyte* string = glGetString(GL_RENDERER);
+		if (string == NULL)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid string");
+	});
+	ES3F_ADD_API_CASE(vendor, "VENDOR",
+	{
+		const GLubyte* string = glGetString(GL_VENDOR);
+		if (string == NULL)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid string");
+	});
+	ES3F_ADD_API_CASE(version, "VERSION",
+	{
+		const char* string				= (const char*)glGetString(GL_VERSION);
+		const char	referenceString[]	= "OpenGL ES 3.";
+
+		if (string == NULL)
+			TCU_FAIL("Got invalid string");
+
+		if (!deStringBeginsWith(string, referenceString))
+			TCU_FAIL("Got invalid string prefix");
+
+		{
+			std::string tmpString;
+			char		versionDelimiter;
+			int			glMajor				= 0;
+			int			glMinor				= 0;
+			GLint		stateVersionMinor	= 0;
+
+			std::istringstream versionStream(string);
+			versionStream >> tmpString;			// OpenGL
+			versionStream >> tmpString;			// ES
+			versionStream >> glMajor;			// 3
+			versionStream >> std::noskipws;
+			versionStream >> versionDelimiter;	// .
+			versionStream >> glMinor;			// x
+
+			if (!versionStream)
+				TCU_FAIL("Got invalid string format");
+
+			glGetIntegerv(GL_MINOR_VERSION, &stateVersionMinor);
+			if (glMinor != stateVersionMinor)
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: MINOR_VERSION is " << stateVersionMinor << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid version.");
+				return;
+			}
+		}
+	});
+	ES3F_ADD_API_CASE(shading_language_version, "SHADING_LANGUAGE_VERSION",
+	{
+		const char* string				= (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
+		const char	referenceString[]	= "OpenGL ES GLSL ES ";
+
+		if (string == NULL)
+			TCU_FAIL("Got invalid string");
+
+		if (!deStringBeginsWith(string, referenceString))
+			TCU_FAIL("Got invalid string prefix");
+
+		{
+			std::string tmpString;
+			char		versionDelimiter;
+			int			glslMajor			= 0;
+			char		glslMinorDigit1		= 0;
+			char		glslMinorDigit2		= 0;
+			bool		digitsAreValid;
+
+			std::istringstream versionStream(string);
+			versionStream >> tmpString;			// OpenGL
+			versionStream >> tmpString;			// ES
+			versionStream >> tmpString;			// GLSL
+			versionStream >> tmpString;			// ES
+			versionStream >> glslMajor;			// x
+			versionStream >> std::noskipws;
+			versionStream >> versionDelimiter;	// .
+			versionStream >> glslMinorDigit1;	// x
+			versionStream >> glslMinorDigit2;	// x
+
+			digitsAreValid =	glslMinorDigit1 >= '0' && glslMinorDigit1 <= '9' &&
+								glslMinorDigit2 >= '0' && glslMinorDigit2 <= '9';
+
+			if (!versionStream || !digitsAreValid)
+				TCU_FAIL("Got invalid string format");
+		}
+	});
+	ES3F_ADD_API_CASE(extensions, "EXTENSIONS",
+	{
+		const char* extensions_cstring = (const char*)glGetString(GL_EXTENSIONS);
+		if (extensions_cstring == NULL)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid string");
+
+		// split extensions_string at ' '
+
+		std::istringstream extensionStream((std::string)(extensions_cstring));
+		std::vector<std::string> extensions;
+
+		for (;;)
+		{
+			std::string extension;
+			if (std::getline(extensionStream, extension, ' '))
+				extensions.push_back(extension);
+			else
+				break;
+		}
+
+		GLint numExtensions = 0;
+		glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
+		expectError(GL_NO_ERROR);
+
+		if (extensions.size() != (size_t)numExtensions)
+		{
+			m_testCtx.getLog() << TestLog::Message << "// ERROR:  NUM_EXTENSIONS is " << numExtensions << "; got " << extensions.size() << " extensions" << TestLog::EndMessage;
+			if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got non-consistent number of extensions");
+		}
+
+		// all in glGetStringi(GL_EXTENSIONS) in must be in glGetString
+
+		for (int i = 0; i < numExtensions; ++i)
+		{
+			std::string extension((const char*)glGetStringi(GL_EXTENSIONS, i));
+
+			if (std::find(extensions.begin(), extensions.end(), extension) == extensions.end())
+			{
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: extension " << extension << " found with GetStringi was not found in glGetString(GL_EXTENSIONS)" << TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Extension query methods are not consistent.");
+			}
+		}
+
+		// only elements in glGetStringi(GL_EXTENSIONS) can be in glGetString
+
+		for (int i = 0; i < numExtensions; ++i)
+		{
+			std::string extension((const char*)glGetStringi(GL_EXTENSIONS, i));
+
+			std::vector<std::string>::iterator it = std::find(extensions.begin(), extensions.end(), extension);
+			if (it != extensions.end())
+				extensions.erase(it);
+		}
+
+		if (!extensions.empty())
+		{
+			for (size_t ndx = 0; ndx < extensions.size(); ++ndx)
+				m_testCtx.getLog() << TestLog::Message << "// ERROR: extension \"" << extensions[ndx] << "\" found with GetString was not found with GetStringi(GL_EXTENSIONS, ind)" << TestLog::EndMessage;
+
+			if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Extension query methods are not consistent.");
+		}
+
+	});
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fStringQueryTests.hpp b/modules/gles3/functional/es3fStringQueryTests.hpp
new file mode 100644
index 0000000..fc11399
--- /dev/null
+++ b/modules/gles3/functional/es3fStringQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FSTRINGQUERYTESTS_HPP
+#define _ES3FSTRINGQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief String Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class StringQueryTests : public TestCaseGroup
+{
+public:
+							StringQueryTests				(Context& context);
+							~StringQueryTests				(void);
+
+	void					init							(void);
+
+private:
+							StringQueryTests				(const StringQueryTests& other);
+	StringQueryTests&		operator=						(const StringQueryTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSTRINGQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fSyncTests.cpp b/modules/gles3/functional/es3fSyncTests.cpp
new file mode 100644
index 0000000..11b10e0
--- /dev/null
+++ b/modules/gles3/functional/es3fSyncTests.cpp
@@ -0,0 +1,301 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sync tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fSyncTests.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+#include <vector>
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using namespace glw; // GL types
+
+static const int	NUM_CASE_ITERATIONS = 5;
+
+enum WaitCommand
+{
+	COMMAND_WAIT_SYNC			= 1 << 0,
+	COMMAND_CLIENT_WAIT_SYNC	= 1 << 1
+};
+
+enum CaseOptions
+{
+	CASE_FLUSH_BEFORE_WAIT	= 1 << 0,
+	CASE_FINISH_BEFORE_WAIT	= 1 << 1
+};
+
+class FenceSyncCase : public TestCase, private glu::CallLogWrapper
+{
+public:
+						FenceSyncCase		(Context& context, const char* name, const char* description, int numPrimitives, deUint32 waitCommand, deUint32 waitFlags, deUint64 timeout, deUint32 options);
+						~FenceSyncCase		(void);
+
+	void				init				(void);
+	void				deinit				(void);
+	IterateResult		iterate				(void);
+
+private:
+						FenceSyncCase		(const FenceSyncCase& other);
+	FenceSyncCase&		operator=			(const FenceSyncCase& other);
+
+	int					m_numPrimitives;
+	deUint32			m_waitCommand;
+	deUint32			m_waitFlags;
+	deUint64			m_timeout;
+	deUint32			m_caseOptions;
+
+	glu::ShaderProgram*	m_program;
+	GLsync				m_syncObject;
+	int					m_iterNdx;
+	de::Random			m_rnd;
+};
+
+FenceSyncCase::FenceSyncCase (Context& context, const char* name, const char* description, int numPrimitives, deUint32 waitCommand, deUint32 waitFlags, deUint64 timeout, deUint32 options)
+	: TestCase				(context, name, description)
+	, CallLogWrapper		(context.getRenderContext().getFunctions(), context.getTestContext().getLog())
+	, m_numPrimitives		(numPrimitives)
+	, m_waitCommand			(waitCommand)
+	, m_waitFlags			(waitFlags)
+	, m_timeout				(timeout)
+	, m_caseOptions			(options)
+	, m_program				(DE_NULL)
+	, m_syncObject			(DE_NULL)
+	, m_iterNdx				(0)
+	, m_rnd					(deStringHash(name))
+{
+}
+
+FenceSyncCase::~FenceSyncCase (void)
+{
+	FenceSyncCase::deinit();
+}
+
+static void generateVertices (std::vector<float>& dst, int numPrimitives, de::Random& rnd)
+{
+	int numVertices = 3*numPrimitives;
+	dst.resize(numVertices * 4);
+
+	for (int i = 0; i < numVertices; i++)
+	{
+		dst[i*4    ] = rnd.getFloat(-1.0f, 1.0f);	// x
+		dst[i*4 + 1] = rnd.getFloat(-1.0f, 1.0f);	// y
+		dst[i*4 + 2] = rnd.getFloat( 0.0f, 1.0f);	// z
+		dst[i*4 + 3] = 1.0f;						// w
+	}
+}
+
+void FenceSyncCase::init (void)
+{
+	const char*	vertShaderSource =
+				"#version 300 es\n"
+				"layout(location = 0) in mediump vec4 a_position;\n"
+				"\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = a_position;\n"
+				"}\n";
+
+	const char* fragShaderSource =
+				"#version 300 es\n"
+				"layout(location = 0) out mediump vec4 o_color;\n"
+				"\n"
+				"void main (void)\n"
+				"{\n"
+				"	o_color = vec4(0.25, 0.5, 0.75, 1.0);\n"
+				"}\n";
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource));
+
+	if (!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+		TCU_FAIL("Failed to compile shader program");
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); // Initialize test result to pass.
+	GLU_CHECK_MSG("Case initialization finished");
+}
+
+void FenceSyncCase::deinit (void)
+{
+	if (m_program)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+	}
+
+	if (m_syncObject)
+	{
+		glDeleteSync(m_syncObject);
+		m_syncObject = DE_NULL;
+	}
+}
+
+FenceSyncCase::IterateResult FenceSyncCase::iterate (void)
+{
+	TestLog&			log		= m_testCtx.getLog();
+	std::vector<float>	vertices;
+	bool				testOk	= true;
+
+	std::string header = "Case iteration " + de::toString(m_iterNdx+1) + " / " + de::toString(NUM_CASE_ITERATIONS);
+	log << TestLog::Section(header, header);
+
+	enableLogging(true);
+
+	DE_ASSERT		(m_program);
+	glUseProgram	(m_program->getProgram());
+	glEnable		(GL_DEPTH_TEST);
+	glClearColor	(0.3f, 0.3f, 0.3f, 1.0f);
+	glClearDepthf	(1.0f);
+	glClear			(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+	// Generate vertices
+
+	glEnableVertexAttribArray (0);
+	generateVertices		  (vertices, m_numPrimitives, m_rnd);
+	glVertexAttribPointer	  (0, 4, GL_FLOAT, GL_FALSE, 0, &vertices[0]);
+
+	// Draw
+
+	glDrawArrays(GL_TRIANGLES, 0, (int)vertices.size() / 4);
+	log << TestLog::Message << "// Primitives drawn." << TestLog::EndMessage;
+
+	// Create sync object
+
+	m_syncObject = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+	GLU_CHECK_MSG ("Sync object created");
+	log << TestLog::Message << "// Sync object created." << TestLog::EndMessage;
+
+	if (m_caseOptions & CASE_FLUSH_BEFORE_WAIT)
+		glFlush();
+	if (m_caseOptions & CASE_FINISH_BEFORE_WAIT)
+		glFinish();
+
+	// Wait for sync object
+
+	GLenum waitValue = 0;
+
+	if (m_waitCommand & COMMAND_WAIT_SYNC)
+	{
+		DE_ASSERT(m_timeout == GL_TIMEOUT_IGNORED);
+		DE_ASSERT(m_waitFlags == 0);
+		glWaitSync(m_syncObject, m_waitFlags, m_timeout);
+		GLU_CHECK_MSG ("glWaitSync called");
+		log << TestLog::Message << "// Wait command glWaitSync called with GL_TIMEOUT_IGNORED." << TestLog::EndMessage;
+	}
+	if (m_waitCommand & COMMAND_CLIENT_WAIT_SYNC)
+	{
+		waitValue = glClientWaitSync(m_syncObject, m_waitFlags, m_timeout);
+		GLU_CHECK_MSG ("glClientWaitSync called");
+		log << TestLog::Message << "// glClientWaitSync return value:" << TestLog::EndMessage;
+		switch (waitValue)
+		{
+			case GL_ALREADY_SIGNALED:	 log << TestLog::Message << "// GL_ALREADY_SIGNALED"	<< TestLog::EndMessage; break;
+			case GL_TIMEOUT_EXPIRED:	 log << TestLog::Message << "// GL_TIMEOUT_EXPIRED"		<< TestLog::EndMessage; break;
+			case GL_CONDITION_SATISFIED: log << TestLog::Message << "// GL_CONDITION_SATISFIED"	<< TestLog::EndMessage; break;
+			case GL_WAIT_FAILED:		 log << TestLog::Message << "// GL_WAIT_FAILED"			<< TestLog::EndMessage; testOk = false; break;
+			default:					 log << TestLog::EndSection; TCU_FAIL("// Illegal return value!");
+		}
+	}
+
+	glFinish();
+
+	if (m_caseOptions & CASE_FINISH_BEFORE_WAIT && waitValue != GL_ALREADY_SIGNALED)
+	{
+		testOk = false;
+		log << TestLog::Message << "// Expected glClientWaitSync to return GL_ALREADY_SIGNALED." << TestLog::EndMessage;
+	}
+
+	// Delete sync object
+
+	if (m_syncObject)
+	{
+		glDeleteSync(m_syncObject);
+		m_syncObject = DE_NULL;
+		GLU_CHECK_MSG ("Sync object deleted");
+		log << TestLog::Message << "// Sync object deleted." << TestLog::EndMessage;
+	}
+
+	// Evaluate test result
+
+	log << TestLog::Message << "// Test result: " << (testOk ? "Passed!" : "Failed!") << TestLog::EndMessage;
+
+	if (!testOk)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		log << TestLog::EndSection;
+		return STOP;
+	}
+
+	log << TestLog::Message << "// Sync objects created and deleted successfully." << TestLog::EndMessage << TestLog::EndSection;
+
+	return (++m_iterNdx < NUM_CASE_ITERATIONS) ? CONTINUE : STOP;
+}
+
+SyncTests::SyncTests (Context& context)
+	: TestCaseGroup(context, "fence_sync", "Fence Sync Tests")
+{
+}
+
+SyncTests::~SyncTests (void)
+{
+}
+
+void SyncTests::init (void)
+{
+	// Fence sync tests.
+
+	addChild(new FenceSyncCase(m_context, "wait_sync_smalldraw",	"",	10,		COMMAND_WAIT_SYNC,	0, GL_TIMEOUT_IGNORED,	0));
+	addChild(new FenceSyncCase(m_context, "wait_sync_largedraw",	"",	100000,	COMMAND_WAIT_SYNC,	0, GL_TIMEOUT_IGNORED,	0));
+
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_smalldraw",			"",	10,		COMMAND_CLIENT_WAIT_SYNC,	0, 0,	0));
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_largedraw",			"",	100000,	COMMAND_CLIENT_WAIT_SYNC,	0, 0,	0));
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_timeout_smalldraw",	"",	10,		COMMAND_CLIENT_WAIT_SYNC,	0, 10,	0));
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_timeout_largedraw",	"",	100000,	COMMAND_CLIENT_WAIT_SYNC,	0, 10,	0));
+
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_flush_auto",	"",	100000, COMMAND_CLIENT_WAIT_SYNC,	GL_SYNC_FLUSH_COMMANDS_BIT, 0,	0));
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_flush_manual",	"",	100000, COMMAND_CLIENT_WAIT_SYNC,	0,							0,	CASE_FLUSH_BEFORE_WAIT));
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_noflush",		"",	100000, COMMAND_CLIENT_WAIT_SYNC,	0,							0,	0));
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_finish",		"",	100000, COMMAND_CLIENT_WAIT_SYNC,	0,							0,	CASE_FINISH_BEFORE_WAIT));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fSyncTests.hpp b/modules/gles3/functional/es3fSyncTests.hpp
new file mode 100644
index 0000000..3ed8e79
--- /dev/null
+++ b/modules/gles3/functional/es3fSyncTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FSYNCTESTS_HPP
+#define _ES3FSYNCTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sync tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class SyncTests : public TestCaseGroup
+{
+public:
+					SyncTests	(Context& context);
+					~SyncTests	(void);
+
+	void			init		(void);
+
+private:
+					SyncTests	(const SyncTests& other);
+	SyncTests&		operator=	(const SyncTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FSYNCTESTS_HPP
diff --git a/modules/gles3/functional/es3fTextureFilteringTests.cpp b/modules/gles3/functional/es3fTextureFilteringTests.cpp
new file mode 100644
index 0000000..498e8dc
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureFilteringTests.cpp
@@ -0,0 +1,1736 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTextureFilteringTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTexLookupVerifier.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using namespace gls::TextureTestUtil;
+
+enum
+{
+	TEX2D_VIEWPORT_WIDTH		= 64,
+	TEX2D_VIEWPORT_HEIGHT		= 64,
+	TEX2D_MIN_VIEWPORT_WIDTH	= 64,
+	TEX2D_MIN_VIEWPORT_HEIGHT	= 64,
+
+	TEX3D_VIEWPORT_WIDTH		= 64,
+	TEX3D_VIEWPORT_HEIGHT		= 64,
+	TEX3D_MIN_VIEWPORT_WIDTH	= 64,
+	TEX3D_MIN_VIEWPORT_HEIGHT	= 64
+};
+
+class Texture2DFilteringCase : public tcu::TestCase
+{
+public:
+									Texture2DFilteringCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 internalFormat, int width, int height);
+									Texture2DFilteringCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames);
+									~Texture2DFilteringCase		(void);
+
+	void							init						(void);
+	void							deinit						(void);
+	IterateResult					iterate						(void);
+
+private:
+									Texture2DFilteringCase		(const Texture2DFilteringCase& other);
+	Texture2DFilteringCase&			operator=					(const Texture2DFilteringCase& other);
+
+	glu::RenderContext&				m_renderCtx;
+	const glu::ContextInfo&			m_renderCtxInfo;
+
+	const deUint32					m_minFilter;
+	const deUint32					m_magFilter;
+	const deUint32					m_wrapS;
+	const deUint32					m_wrapT;
+
+	const deUint32					m_internalFormat;
+	const int						m_width;
+	const int						m_height;
+
+	const std::vector<std::string>	m_filenames;
+
+	struct FilterCase
+	{
+		const glu::Texture2D*	texture;
+		tcu::Vec2				minCoord;
+		tcu::Vec2				maxCoord;
+
+		FilterCase (void)
+			: texture(DE_NULL)
+		{
+		}
+
+		FilterCase (const glu::Texture2D* tex_, const tcu::Vec2& minCoord_, const tcu::Vec2& maxCoord_)
+			: texture	(tex_)
+			, minCoord	(minCoord_)
+			, maxCoord	(maxCoord_)
+		{
+		}
+	};
+
+	std::vector<glu::Texture2D*>	m_textures;
+	std::vector<FilterCase>			m_cases;
+
+	TextureRenderer					m_renderer;
+
+	int								m_caseNdx;
+};
+
+Texture2DFilteringCase::Texture2DFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 internalFormat, int width, int height)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(ctxInfo)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_internalFormat	(internalFormat)
+	, m_width			(width)
+	, m_height			(height)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_caseNdx			(0)
+{
+}
+
+Texture2DFilteringCase::Texture2DFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, const std::vector<std::string>& filenames)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(ctxInfo)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_internalFormat	(GL_NONE)
+	, m_width			(0)
+	, m_height			(0)
+	, m_filenames		(filenames)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_caseNdx			(0)
+{
+}
+
+Texture2DFilteringCase::~Texture2DFilteringCase (void)
+{
+	deinit();
+}
+
+void Texture2DFilteringCase::init (void)
+{
+	try
+	{
+		if (!m_filenames.empty())
+		{
+			m_textures.reserve(1);
+			m_textures.push_back(glu::Texture2D::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size(), m_filenames));
+		}
+		else
+		{
+			// Create 2 textures.
+			m_textures.reserve(2);
+			for (int ndx = 0; ndx < 2; ndx++)
+				m_textures.push_back(new glu::Texture2D(m_renderCtx, m_internalFormat, m_width, m_height));
+
+			const bool						mipmaps		= true;
+			const int						numLevels	= mipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;
+			const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+			const tcu::Vec4					cBias		= fmtInfo.valueMin;
+			const tcu::Vec4					cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+			// Fill first gradient texture.
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				tcu::Vec4 gMin = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+				tcu::Vec4 gMax = tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f)*cScale + cBias;
+
+				m_textures[0]->getRefTexture().allocLevel(levelNdx);
+				tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
+			}
+
+			// Fill second with grid texture.
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				deUint32	step	= 0x00ffffff / numLevels;
+				deUint32	rgb		= step*levelNdx;
+				deUint32	colorA	= 0xff000000 | rgb;
+				deUint32	colorB	= 0xff000000 | ~rgb;
+
+				m_textures[1]->getRefTexture().allocLevel(levelNdx);
+				tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+			}
+
+			// Upload.
+			for (std::vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+				(*i)->upload();
+		}
+
+		// Compute cases.
+		{
+			const struct
+			{
+				int		texNdx;
+				float	lodX;
+				float	lodY;
+				float	oX;
+				float	oY;
+			} cases[] =
+			{
+				{ 0,	1.6f,	2.9f,	-1.0f,	-2.7f	},
+				{ 0,	-2.0f,	-1.35f,	-0.2f,	0.7f	},
+				{ 1,	0.14f,	0.275f,	-1.5f,	-1.1f	},
+				{ 1,	-0.92f,	-2.64f,	0.4f,	-0.1f	},
+			};
+
+			const float	viewportW	= (float)de::min<int>(TEX2D_VIEWPORT_WIDTH, m_renderCtx.getRenderTarget().getWidth());
+			const float	viewportH	= (float)de::min<int>(TEX2D_VIEWPORT_HEIGHT, m_renderCtx.getRenderTarget().getHeight());
+
+			for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+			{
+				const int	texNdx	= de::clamp(cases[caseNdx].texNdx, 0, (int)m_textures.size()-1);
+				const float	lodX	= cases[caseNdx].lodX;
+				const float	lodY	= cases[caseNdx].lodY;
+				const float	oX		= cases[caseNdx].oX;
+				const float	oY		= cases[caseNdx].oY;
+				const float	sX		= deFloatExp2(lodX)*viewportW / float(m_textures[texNdx]->getRefTexture().getWidth());
+				const float	sY		= deFloatExp2(lodY)*viewportH / float(m_textures[texNdx]->getRefTexture().getHeight());
+
+				m_cases.push_back(FilterCase(m_textures[texNdx], tcu::Vec2(oX, oY), tcu::Vec2(oX+sX, oY+sY)));
+			}
+		}
+
+		m_caseNdx = 0;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	catch (...)
+	{
+		// Clean up to save memory.
+		Texture2DFilteringCase::deinit();
+		throw;
+	}
+}
+
+void Texture2DFilteringCase::deinit (void)
+{
+	for (std::vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	m_renderer.clear();
+	m_cases.clear();
+}
+
+Texture2DFilteringCase::IterateResult Texture2DFilteringCase::iterate (void)
+{
+	const glw::Functions&			gl			= m_renderCtx.getFunctions();
+	const RandomViewport			viewport	(m_renderCtx.getRenderTarget(), TEX2D_VIEWPORT_WIDTH, TEX2D_VIEWPORT_HEIGHT, deStringHash(getName()) ^ deInt32Hash(m_caseNdx));
+	const tcu::TextureFormat		texFmt		= m_textures[0]->getRefTexture().getFormat();
+	const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFmt);
+	const FilterCase&				curCase		= m_cases[m_caseNdx];
+	const tcu::ScopedLogSection		section		(m_testCtx.getLog(), string("Test") + de::toString(m_caseNdx), string("Test ") + de::toString(m_caseNdx));
+	ReferenceParams					refParams	(TEXTURETYPE_2D);
+	tcu::Surface					rendered	(viewport.width, viewport.height);
+	vector<float>					texCoord;
+
+	if (viewport.width < TEX2D_MIN_VIEWPORT_WIDTH || viewport.height < TEX2D_MIN_VIEWPORT_HEIGHT)
+		throw tcu::NotSupportedError("Too small render target", "", __FILE__, __LINE__);
+
+	// Setup params for reference.
+	refParams.sampler		= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	refParams.samplerType	= getSamplerType(texFmt);
+	refParams.lodMode		= LODMODE_EXACT;
+	refParams.colorBias		= fmtInfo.lookupBias;
+	refParams.colorScale	= fmtInfo.lookupScale;
+
+	// Compute texture coordinates.
+	m_testCtx.getLog() << TestLog::Message << "Texture coordinates: " << curCase.minCoord << " -> " << curCase.maxCoord << TestLog::EndMessage;
+	computeQuadTexCoord2D(texCoord, curCase.minCoord, curCase.maxCoord);
+
+	gl.bindTexture	(GL_TEXTURE_2D, curCase.texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+	m_renderer.renderQuad(0, &texCoord[0], refParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, rendered.getAccess());
+
+	{
+		const bool				isNearestOnly	= m_minFilter == GL_NEAREST && m_magFilter == GL_NEAREST;
+		const tcu::PixelFormat	pixelFormat		= m_renderCtx.getRenderTarget().getPixelFormat();
+		const tcu::IVec4		colorBits		= max(getBitsVec(pixelFormat) - (isNearestOnly ? 1 : 2), tcu::IVec4(0)); // 1 inaccurate bit if nearest only, 2 otherwise
+		tcu::LodPrecision		lodPrecision;
+		tcu::LookupPrecision	lookupPrecision;
+
+		lodPrecision.derivateBits		= 18;
+		lodPrecision.lodBits			= 6;
+		lookupPrecision.colorThreshold	= tcu::computeFixedPointThreshold(colorBits) / refParams.colorScale;
+		lookupPrecision.coordBits		= tcu::IVec3(20,20,0);
+		lookupPrecision.uvwBits			= tcu::IVec3(7,7,0);
+		lookupPrecision.colorMask		= getCompareMask(pixelFormat);
+
+		const bool isHighQuality = verifyTextureResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+													   &texCoord[0], refParams, lookupPrecision, lodPrecision, pixelFormat);
+
+		if (!isHighQuality)
+		{
+			// Evaluate against lower precision requirements.
+			lodPrecision.lodBits	= 4;
+			lookupPrecision.uvwBits	= tcu::IVec3(4,4,0);
+
+			m_testCtx.getLog() << TestLog::Message << "Warning: Verification against high precision requirements failed, trying with lower requirements." << TestLog::EndMessage;
+
+			const bool isOk = verifyTextureResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+												  &texCoord[0], refParams, lookupPrecision, lodPrecision, pixelFormat);
+
+			if (!isOk)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: Verification against low precision requirements failed, failing test case." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+			}
+			else if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Low-quality filtering result");
+		}
+	}
+
+	m_caseNdx += 1;
+	return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
+}
+
+class TextureCubeFilteringCase : public tcu::TestCase
+{
+public:
+									TextureCubeFilteringCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, bool onlySampleFaceInterior, deUint32 internalFormat, int width, int height);
+									TextureCubeFilteringCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, bool onlySampleFaceInterior, const std::vector<std::string>& filenames);
+									~TextureCubeFilteringCase	(void);
+
+	void							init						(void);
+	void							deinit						(void);
+	IterateResult					iterate						(void);
+
+private:
+									TextureCubeFilteringCase	(const TextureCubeFilteringCase& other);
+	TextureCubeFilteringCase&		operator=					(const TextureCubeFilteringCase& other);
+
+	glu::RenderContext&				m_renderCtx;
+	const glu::ContextInfo&			m_renderCtxInfo;
+
+	const deUint32					m_minFilter;
+	const deUint32					m_magFilter;
+	const deUint32					m_wrapS;
+	const deUint32					m_wrapT;
+	const bool						m_onlySampleFaceInterior; //!< If true, we avoid sampling anywhere near a face's edges.
+
+	const deUint32					m_internalFormat;
+	const int						m_width;
+	const int						m_height;
+
+	const std::vector<std::string>	m_filenames;
+
+	struct FilterCase
+	{
+		const glu::TextureCube*	texture;
+		tcu::Vec2				bottomLeft;
+		tcu::Vec2				topRight;
+
+		FilterCase (void)
+			: texture(DE_NULL)
+		{
+		}
+
+		FilterCase (const glu::TextureCube* tex_, const tcu::Vec2& bottomLeft_, const tcu::Vec2& topRight_)
+			: texture	(tex_)
+			, bottomLeft(bottomLeft_)
+			, topRight	(topRight_)
+		{
+		}
+	};
+
+	std::vector<glu::TextureCube*>	m_textures;
+	std::vector<FilterCase>			m_cases;
+
+	TextureRenderer					m_renderer;
+
+	int								m_caseNdx;
+};
+
+TextureCubeFilteringCase::TextureCubeFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, bool onlySampleFaceInterior, deUint32 internalFormat, int width, int height)
+	: TestCase					(testCtx, name, desc)
+	, m_renderCtx				(renderCtx)
+	, m_renderCtxInfo			(ctxInfo)
+	, m_minFilter				(minFilter)
+	, m_magFilter				(magFilter)
+	, m_wrapS					(wrapS)
+	, m_wrapT					(wrapT)
+	, m_onlySampleFaceInterior	(onlySampleFaceInterior)
+	, m_internalFormat			(internalFormat)
+	, m_width					(width)
+	, m_height					(height)
+	, m_renderer				(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_caseNdx					(0)
+{
+}
+
+TextureCubeFilteringCase::TextureCubeFilteringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, bool onlySampleFaceInterior, const std::vector<std::string>& filenames)
+	: TestCase					(testCtx, name, desc)
+	, m_renderCtx				(renderCtx)
+	, m_renderCtxInfo			(ctxInfo)
+	, m_minFilter				(minFilter)
+	, m_magFilter				(magFilter)
+	, m_wrapS					(wrapS)
+	, m_wrapT					(wrapT)
+	, m_onlySampleFaceInterior	(onlySampleFaceInterior)
+	, m_internalFormat			(GL_NONE)
+	, m_width					(0)
+	, m_height					(0)
+	, m_filenames				(filenames)
+	, m_renderer				(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_caseNdx					(0)
+{
+}
+
+TextureCubeFilteringCase::~TextureCubeFilteringCase (void)
+{
+	deinit();
+}
+
+void TextureCubeFilteringCase::init (void)
+{
+	try
+	{
+		if (!m_filenames.empty())
+		{
+			m_textures.reserve(1);
+			m_textures.push_back(glu::TextureCube::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size() / 6, m_filenames));
+		}
+		else
+		{
+			DE_ASSERT(m_width == m_height);
+			m_textures.reserve(2);
+			for (int ndx = 0; ndx < 2; ndx++)
+				m_textures.push_back(new glu::TextureCube(m_renderCtx, m_internalFormat, m_width));
+
+			const int				numLevels	= deLog2Floor32(de::max(m_width, m_height))+1;
+			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+			tcu::Vec4				cBias		= fmtInfo.valueMin;
+			tcu::Vec4				cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+			// Fill first with gradient texture.
+			static const tcu::Vec4 gradients[tcu::CUBEFACE_LAST][2] =
+			{
+				{ tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
+				{ tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
+				{ tcu::Vec4(0.0f, 0.5f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
+				{ tcu::Vec4(0.0f, 0.0f, 0.5f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
+				{ tcu::Vec4(0.0f, 0.0f, 0.0f, 0.5f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
+				{ tcu::Vec4(0.5f, 0.5f, 0.5f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
+			};
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			{
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					m_textures[0]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+					tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0]*cScale + cBias, gradients[face][1]*cScale + cBias);
+				}
+			}
+
+			// Fill second with grid texture.
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			{
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					deUint32	step	= 0x00ffffff / (numLevels*tcu::CUBEFACE_LAST);
+					deUint32	rgb		= step*levelNdx*face;
+					deUint32	colorA	= 0xff000000 | rgb;
+					deUint32	colorB	= 0xff000000 | ~rgb;
+
+					m_textures[1]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+					tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+				}
+			}
+
+			// Upload.
+			for (std::vector<glu::TextureCube*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+				(*i)->upload();
+		}
+
+		// Compute cases
+		{
+			const glu::TextureCube*	tex0	= m_textures[0];
+			const glu::TextureCube* tex1	= m_textures.size() > 1 ? m_textures[1] : tex0;
+
+			if (m_onlySampleFaceInterior)
+			{
+				m_cases.push_back(FilterCase(tex0, tcu::Vec2(-0.8f, -0.8f), tcu::Vec2(0.8f,  0.8f)));	// minification
+				m_cases.push_back(FilterCase(tex0, tcu::Vec2(0.5f, 0.65f), tcu::Vec2(0.8f,  0.8f)));	// magnification
+				m_cases.push_back(FilterCase(tex1, tcu::Vec2(-0.8f, -0.8f), tcu::Vec2(0.8f,  0.8f)));	// minification
+				m_cases.push_back(FilterCase(tex1, tcu::Vec2(0.2f, 0.2f), tcu::Vec2(0.6f,  0.5f)));		// magnification
+			}
+			else
+			{
+				if (m_renderCtx.getRenderTarget().getNumSamples() == 0)
+					m_cases.push_back(FilterCase(tex0, tcu::Vec2(-1.25f, -1.2f), tcu::Vec2(1.2f, 1.25f)));	// minification
+				else
+					m_cases.push_back(FilterCase(tex0, tcu::Vec2(-1.19f, -1.3f), tcu::Vec2(1.1f, 1.35f)));	// minification - w/ tweak to avoid hitting triangle edges with face switchpoint
+
+				m_cases.push_back(FilterCase(tex0, tcu::Vec2(0.8f, 0.8f), tcu::Vec2(1.25f, 1.20f)));	// magnification
+				m_cases.push_back(FilterCase(tex1, tcu::Vec2(-1.19f, -1.3f), tcu::Vec2(1.1f, 1.35f)));	// minification
+				m_cases.push_back(FilterCase(tex1, tcu::Vec2(-1.2f, -1.1f), tcu::Vec2(-0.8f, -0.8f)));	// magnification
+			}
+		}
+
+		m_caseNdx = 0;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	catch (...)
+	{
+		// Clean up to save memory.
+		TextureCubeFilteringCase::deinit();
+		throw;
+	}
+}
+
+void TextureCubeFilteringCase::deinit (void)
+{
+	for (std::vector<glu::TextureCube*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	m_renderer.clear();
+	m_cases.clear();
+}
+
+static const char* getFaceDesc (const tcu::CubeFace face)
+{
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X:	return "-X";
+		case tcu::CUBEFACE_POSITIVE_X:	return "+X";
+		case tcu::CUBEFACE_NEGATIVE_Y:	return "-Y";
+		case tcu::CUBEFACE_POSITIVE_Y:	return "+Y";
+		case tcu::CUBEFACE_NEGATIVE_Z:	return "-Z";
+		case tcu::CUBEFACE_POSITIVE_Z:	return "+Z";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+TextureCubeFilteringCase::IterateResult TextureCubeFilteringCase::iterate (void)
+{
+	const glw::Functions&			gl				= m_renderCtx.getFunctions();
+	const int						viewportSize	= 28;
+	const RandomViewport			viewport		(m_renderCtx.getRenderTarget(), viewportSize, viewportSize, deStringHash(getName()) ^ deInt32Hash(m_caseNdx));
+	const tcu::ScopedLogSection		iterSection		(m_testCtx.getLog(), string("Test") + de::toString(m_caseNdx), string("Test ") + de::toString(m_caseNdx));
+	const FilterCase&				curCase			= m_cases[m_caseNdx];
+	const tcu::TextureFormat&		texFmt			= curCase.texture->getRefTexture().getFormat();
+	const tcu::TextureFormatInfo	fmtInfo			= tcu::getTextureFormatInfo(texFmt);
+	ReferenceParams					sampleParams	(TEXTURETYPE_CUBE);
+
+	if (viewport.width < viewportSize || viewport.height < viewportSize)
+		throw tcu::NotSupportedError("Too small render target", DE_NULL, __FILE__, __LINE__);
+
+	// Setup texture
+	gl.bindTexture	(GL_TEXTURE_CUBE_MAP, curCase.texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+
+	// Other state
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Params for reference computation.
+	sampleParams.sampler					= glu::mapGLSampler(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, m_minFilter, m_magFilter);
+	sampleParams.sampler.seamlessCubeMap	= true;
+	sampleParams.samplerType				= getSamplerType(texFmt);
+	sampleParams.colorBias					= fmtInfo.lookupBias;
+	sampleParams.colorScale					= fmtInfo.lookupScale;
+	sampleParams.lodMode					= LODMODE_EXACT;
+
+	m_testCtx.getLog() << TestLog::Message << "Coordinates: " << curCase.bottomLeft << " -> " << curCase.topRight << TestLog::EndMessage;
+
+	for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+	{
+		const tcu::CubeFace		face		= tcu::CubeFace(faceNdx);
+		tcu::Surface			result		(viewport.width, viewport.height);
+		vector<float>			texCoord;
+
+		computeQuadTexCoordCube(texCoord, face, curCase.bottomLeft, curCase.topRight);
+
+		m_testCtx.getLog() << TestLog::Message << "Face " << getFaceDesc(face) << TestLog::EndMessage;
+
+		// \todo Log texture coordinates.
+
+		m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+
+		glu::readPixels(m_renderCtx, viewport.x, viewport.y, result.getAccess());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels");
+
+		{
+			const bool				isNearestOnly	= m_minFilter == GL_NEAREST && m_magFilter == GL_NEAREST;
+			const tcu::PixelFormat	pixelFormat		= m_renderCtx.getRenderTarget().getPixelFormat();
+			const tcu::IVec4		colorBits		= max(getBitsVec(pixelFormat) - (isNearestOnly ? 1 : 2), tcu::IVec4(0)); // 1 inaccurate bit if nearest only, 2 otherwise
+			tcu::LodPrecision		lodPrecision;
+			tcu::LookupPrecision	lookupPrecision;
+
+			lodPrecision.derivateBits		= 10;
+			lodPrecision.lodBits			= 5;
+			lookupPrecision.colorThreshold	= tcu::computeFixedPointThreshold(colorBits) / sampleParams.colorScale;
+			lookupPrecision.coordBits		= tcu::IVec3(10,10,10);
+			lookupPrecision.uvwBits			= tcu::IVec3(6,6,0);
+			lookupPrecision.colorMask		= getCompareMask(pixelFormat);
+
+			const bool isHighQuality = verifyTextureResult(m_testCtx, result.getAccess(), curCase.texture->getRefTexture(),
+														   &texCoord[0], sampleParams, lookupPrecision, lodPrecision, pixelFormat);
+
+			if (!isHighQuality)
+			{
+				// Evaluate against lower precision requirements.
+				lodPrecision.lodBits	= 4;
+				lookupPrecision.uvwBits	= tcu::IVec3(4,4,0);
+
+				m_testCtx.getLog() << TestLog::Message << "Warning: Verification against high precision requirements failed, trying with lower requirements." << TestLog::EndMessage;
+
+				const bool isOk = verifyTextureResult(m_testCtx, result.getAccess(), curCase.texture->getRefTexture(),
+													  &texCoord[0], sampleParams, lookupPrecision, lodPrecision, pixelFormat);
+
+				if (!isOk)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: Verification against low precision requirements failed, failing test case." << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+				}
+				else if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Low-quality filtering result");
+			}
+		}
+	}
+
+	m_caseNdx += 1;
+	return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
+}
+
+// 2D array filtering
+
+class Texture2DArrayFilteringCase : public TestCase
+{
+public:
+									Texture2DArrayFilteringCase		(Context& context, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 internalFormat, int width, int height, int numLayers);
+									~Texture2DArrayFilteringCase	(void);
+
+	void							init							(void);
+	void							deinit							(void);
+	IterateResult					iterate							(void);
+
+private:
+									Texture2DArrayFilteringCase		(const Texture2DArrayFilteringCase&);
+	Texture2DArrayFilteringCase&	operator=						(const Texture2DArrayFilteringCase&);
+
+	const deUint32					m_minFilter;
+	const deUint32					m_magFilter;
+	const deUint32					m_wrapS;
+	const deUint32					m_wrapT;
+
+	const deUint32					m_internalFormat;
+	const int						m_width;
+	const int						m_height;
+	const int						m_numLayers;
+
+	struct FilterCase
+	{
+		const glu::Texture2DArray*	texture;
+		tcu::Vec2					lod;
+		tcu::Vec2					offset;
+		tcu::Vec2					layerRange;
+
+		FilterCase (void)
+			: texture(DE_NULL)
+		{
+		}
+
+		FilterCase (const glu::Texture2DArray* tex_, const tcu::Vec2& lod_, const tcu::Vec2& offset_, const tcu::Vec2& layerRange_)
+			: texture	(tex_)
+			, lod		(lod_)
+			, offset	(offset_)
+			, layerRange(layerRange_)
+		{
+		}
+	};
+
+	glu::Texture2DArray*			m_gradientTex;
+	glu::Texture2DArray*			m_gridTex;
+
+	TextureRenderer					m_renderer;
+
+	std::vector<FilterCase>			m_cases;
+	int								m_caseNdx;
+};
+
+Texture2DArrayFilteringCase::Texture2DArrayFilteringCase (Context& context, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 internalFormat, int width, int height, int numLayers)
+	: TestCase			(context, name, desc)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_internalFormat	(internalFormat)
+	, m_width			(width)
+	, m_height			(height)
+	, m_numLayers		(numLayers)
+	, m_gradientTex		(DE_NULL)
+	, m_gridTex			(DE_NULL)
+	, m_renderer		(m_context.getRenderContext(), context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_caseNdx			(0)
+{
+}
+
+Texture2DArrayFilteringCase::~Texture2DArrayFilteringCase (void)
+{
+	Texture2DArrayFilteringCase::deinit();
+}
+
+void Texture2DArrayFilteringCase::init (void)
+{
+	try
+	{
+		const tcu::TextureFormat		texFmt		= glu::mapGLInternalFormat(m_internalFormat);
+		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFmt);
+		const tcu::Vec4					cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+		const tcu::Vec4					cBias		= fmtInfo.valueMin;
+		const int						numLevels	= deLog2Floor32(de::max(m_width, m_height)) + 1;
+
+		// Create textures.
+		m_gradientTex	= new glu::Texture2DArray(m_context.getRenderContext(), m_internalFormat, m_width, m_height, m_numLayers);
+		m_gridTex		= new glu::Texture2DArray(m_context.getRenderContext(), m_internalFormat, m_width, m_height, m_numLayers);
+
+		const tcu::IVec4 levelSwz[] =
+		{
+			tcu::IVec4(0,1,2,3),
+			tcu::IVec4(2,1,3,0),
+			tcu::IVec4(3,0,1,2),
+			tcu::IVec4(1,3,2,0),
+		};
+
+		// Fill first gradient texture (gradient direction varies between layers).
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			m_gradientTex->getRefTexture().allocLevel(levelNdx);
+
+			const tcu::PixelBufferAccess levelBuf = m_gradientTex->getRefTexture().getLevel(levelNdx);
+
+			for (int layerNdx = 0; layerNdx < m_numLayers; layerNdx++)
+			{
+				const tcu::IVec4	swz		= levelSwz[layerNdx%DE_LENGTH_OF_ARRAY(levelSwz)];
+				const tcu::Vec4		gMin	= tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f).swizzle(swz[0],swz[1],swz[2],swz[3])*cScale + cBias;
+				const tcu::Vec4		gMax	= tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f).swizzle(swz[0],swz[1],swz[2],swz[3])*cScale + cBias;
+
+				tcu::fillWithComponentGradients(tcu::getSubregion(levelBuf, 0, 0, layerNdx, levelBuf.getWidth(), levelBuf.getHeight(), 1), gMin, gMax);
+			}
+		}
+
+		// Fill second with grid texture (each layer has unique colors).
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			m_gridTex->getRefTexture().allocLevel(levelNdx);
+
+			const tcu::PixelBufferAccess levelBuf = m_gridTex->getRefTexture().getLevel(levelNdx);
+
+			for (int layerNdx = 0; layerNdx < m_numLayers; layerNdx++)
+			{
+				const deUint32	step	= 0x00ffffff / (numLevels*m_numLayers - 1);
+				const deUint32	rgb		= step * (levelNdx + layerNdx*numLevels);
+				const deUint32	colorA	= 0xff000000 | rgb;
+				const deUint32	colorB	= 0xff000000 | ~rgb;
+
+				tcu::fillWithGrid(tcu::getSubregion(levelBuf, 0, 0, layerNdx, levelBuf.getWidth(), levelBuf.getHeight(), 1),
+								  4, tcu::RGBA(colorA).toVec()*cScale + cBias, tcu::RGBA(colorB).toVec()*cScale + cBias);
+			}
+		}
+
+		// Upload.
+		m_gradientTex->upload();
+		m_gridTex->upload();
+
+		// Test cases
+		m_cases.push_back(FilterCase(m_gradientTex,	tcu::Vec2( 1.5f,  2.8f  ),	tcu::Vec2(-1.0f, -2.7f), tcu::Vec2(-0.5f, float(m_numLayers)+0.5f)));
+		m_cases.push_back(FilterCase(m_gridTex,		tcu::Vec2( 0.2f,  0.175f),	tcu::Vec2(-2.0f, -3.7f), tcu::Vec2(-0.5f, float(m_numLayers)+0.5f)));
+		m_cases.push_back(FilterCase(m_gridTex,		tcu::Vec2(-0.8f, -2.3f  ),	tcu::Vec2( 0.2f, -0.1f), tcu::Vec2(float(m_numLayers)+0.5f, -0.5f)));
+
+		// Level rounding - only in single-sample configs as multisample configs may produce smooth transition at the middle.
+		if (m_context.getRenderTarget().getNumSamples() == 0)
+			m_cases.push_back(FilterCase(m_gradientTex,	tcu::Vec2(-2.0f, -1.5f  ),	tcu::Vec2(-0.1f,  0.9f), tcu::Vec2(1.50001f, 1.49999f)));
+
+		m_caseNdx = 0;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	catch (...)
+	{
+		// Clean up to save memory.
+		Texture2DArrayFilteringCase::deinit();
+		throw;
+	}
+}
+
+void Texture2DArrayFilteringCase::deinit (void)
+{
+	delete m_gradientTex;
+	delete m_gridTex;
+
+	m_gradientTex	= DE_NULL;
+	m_gridTex		= DE_NULL;
+
+	m_renderer.clear();
+	m_cases.clear();
+}
+
+Texture2DArrayFilteringCase::IterateResult Texture2DArrayFilteringCase::iterate (void)
+{
+	const glw::Functions&			gl			= m_context.getRenderContext().getFunctions();
+	const RandomViewport			viewport	(m_context.getRenderTarget(), TEX3D_VIEWPORT_WIDTH, TEX3D_VIEWPORT_HEIGHT, deStringHash(getName()) ^ deInt32Hash(m_caseNdx));
+	const FilterCase&				curCase		= m_cases[m_caseNdx];
+	const tcu::TextureFormat		texFmt		= curCase.texture->getRefTexture().getFormat();
+	const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFmt);
+	const tcu::ScopedLogSection		section		(m_testCtx.getLog(), string("Test") + de::toString(m_caseNdx), string("Test ") + de::toString(m_caseNdx));
+	ReferenceParams					refParams	(TEXTURETYPE_2D_ARRAY);
+	tcu::Surface					rendered	(viewport.width, viewport.height);
+	tcu::Vec3						texCoord[4];
+
+	if (viewport.width < TEX3D_MIN_VIEWPORT_WIDTH || viewport.height < TEX3D_MIN_VIEWPORT_HEIGHT)
+		throw tcu::NotSupportedError("Too small render target", "", __FILE__, __LINE__);
+
+	// Setup params for reference.
+	refParams.sampler		= glu::mapGLSampler(m_wrapS, m_wrapT, m_wrapT, m_minFilter, m_magFilter);
+	refParams.samplerType	= getSamplerType(texFmt);
+	refParams.lodMode		= LODMODE_EXACT;
+	refParams.colorBias		= fmtInfo.lookupBias;
+	refParams.colorScale	= fmtInfo.lookupScale;
+
+	// Compute texture coordinates.
+	m_testCtx.getLog() << TestLog::Message << "Approximate lod per axis = " << curCase.lod << ", offset = " << curCase.offset << TestLog::EndMessage;
+
+	{
+		const float	lodX	= curCase.lod.x();
+		const float	lodY	= curCase.lod.y();
+		const float	oX		= curCase.offset.x();
+		const float	oY		= curCase.offset.y();
+		const float	sX		= deFloatExp2(lodX)*float(viewport.width)	/ float(m_gradientTex->getRefTexture().getWidth());
+		const float	sY		= deFloatExp2(lodY)*float(viewport.height)	/ float(m_gradientTex->getRefTexture().getHeight());
+		const float	l0		= curCase.layerRange.x();
+		const float	l1		= curCase.layerRange.y();
+
+		texCoord[0] = tcu::Vec3(oX,		oY,		l0);
+		texCoord[1] = tcu::Vec3(oX,		oY+sY,	l0*0.5f + l1*0.5f);
+		texCoord[2] = tcu::Vec3(oX+sX,	oY,		l0*0.5f + l1*0.5f);
+		texCoord[3] = tcu::Vec3(oX+sX,	oY+sY,	l1);
+	}
+
+	gl.bindTexture	(GL_TEXTURE_2D_ARRAY, curCase.texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T,		m_wrapT);
+
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+	m_renderer.renderQuad(0, (const float*)&texCoord[0], refParams);
+	glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, rendered.getAccess());
+
+	{
+		const bool				isNearestOnly	= m_minFilter == GL_NEAREST && m_magFilter == GL_NEAREST;
+		const tcu::PixelFormat	pixelFormat		= m_context.getRenderTarget().getPixelFormat();
+		const tcu::IVec4		colorBits		= max(getBitsVec(pixelFormat) - (isNearestOnly ? 1 : 2), tcu::IVec4(0)); // 1 inaccurate bit if nearest only, 2 otherwise
+		tcu::LodPrecision		lodPrecision;
+		tcu::LookupPrecision	lookupPrecision;
+
+		lodPrecision.derivateBits		= 18;
+		lodPrecision.lodBits			= 6;
+		lookupPrecision.colorThreshold	= tcu::computeFixedPointThreshold(colorBits) / refParams.colorScale;
+		lookupPrecision.coordBits		= tcu::IVec3(20,20,20);
+		lookupPrecision.uvwBits			= tcu::IVec3(7,7,0);
+		lookupPrecision.colorMask		= getCompareMask(pixelFormat);
+
+		const bool isHighQuality = verifyTextureResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+													   (const float*)&texCoord[0], refParams, lookupPrecision, lodPrecision, pixelFormat);
+
+		if (!isHighQuality)
+		{
+			// Evaluate against lower precision requirements.
+			lodPrecision.lodBits	= 4;
+			lookupPrecision.uvwBits	= tcu::IVec3(4,4,0);
+
+			m_testCtx.getLog() << TestLog::Message << "Warning: Verification against high precision requirements failed, trying with lower requirements." << TestLog::EndMessage;
+
+			const bool isOk = verifyTextureResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+												  (const float*)&texCoord[0], refParams, lookupPrecision, lodPrecision, pixelFormat);
+
+			if (!isOk)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: Verification against low precision requirements failed, failing test case." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+			}
+			else if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Low-quality filtering result");
+		}
+	}
+
+	m_caseNdx += 1;
+	return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
+}
+
+// 3D filtering
+
+class Texture3DFilteringCase : public TestCase
+{
+public:
+									Texture3DFilteringCase		(Context& context, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 wrapR, deUint32 internalFormat, int width, int height, int depth);
+									~Texture3DFilteringCase		(void);
+
+	void							init						(void);
+	void							deinit						(void);
+	IterateResult					iterate						(void);
+
+private:
+									Texture3DFilteringCase		(const Texture3DFilteringCase& other);
+	Texture3DFilteringCase&			operator=					(const Texture3DFilteringCase& other);
+
+	const deUint32					m_minFilter;
+	const deUint32					m_magFilter;
+	const deUint32					m_wrapS;
+	const deUint32					m_wrapT;
+	const deUint32					m_wrapR;
+
+	const deUint32					m_internalFormat;
+	const int						m_width;
+	const int						m_height;
+	const int						m_depth;
+
+	struct FilterCase
+	{
+		const glu::Texture3D*	texture;
+		tcu::Vec3				lod;
+		tcu::Vec3				offset;
+
+		FilterCase (void)
+			: texture(DE_NULL)
+		{
+		}
+
+		FilterCase (const glu::Texture3D* tex_, const tcu::Vec3& lod_, const tcu::Vec3& offset_)
+			: texture	(tex_)
+			, lod		(lod_)
+			, offset	(offset_)
+		{
+		}
+	};
+
+	glu::Texture3D*					m_gradientTex;
+	glu::Texture3D*					m_gridTex;
+
+	TextureRenderer					m_renderer;
+
+	std::vector<FilterCase>			m_cases;
+	int								m_caseNdx;
+};
+
+Texture3DFilteringCase::Texture3DFilteringCase (Context& context, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 wrapR, deUint32 internalFormat, int width, int height, int depth)
+	: TestCase			(context, name, desc)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_wrapR			(wrapR)
+	, m_internalFormat	(internalFormat)
+	, m_width			(width)
+	, m_height			(height)
+	, m_depth			(depth)
+	, m_gradientTex		(DE_NULL)
+	, m_gridTex			(DE_NULL)
+	, m_renderer		(m_context.getRenderContext(), context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_caseNdx			(0)
+{
+}
+
+Texture3DFilteringCase::~Texture3DFilteringCase (void)
+{
+	Texture3DFilteringCase::deinit();
+}
+
+void Texture3DFilteringCase::init (void)
+{
+	try
+	{
+		const tcu::TextureFormat		texFmt		= glu::mapGLInternalFormat(m_internalFormat);
+		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFmt);
+		const tcu::Vec4					cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+		const tcu::Vec4					cBias		= fmtInfo.valueMin;
+		const int						numLevels	= deLog2Floor32(de::max(de::max(m_width, m_height), m_depth)) + 1;
+
+		// Create textures.
+		m_gradientTex	= new glu::Texture3D(m_context.getRenderContext(), m_internalFormat, m_width, m_height, m_depth);
+		m_gridTex		= new glu::Texture3D(m_context.getRenderContext(), m_internalFormat, m_width, m_height, m_depth);
+
+		// Fill first gradient texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			tcu::Vec4 gMin = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			tcu::Vec4 gMax = tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f)*cScale + cBias;
+
+			m_gradientTex->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithComponentGradients(m_gradientTex->getRefTexture().getLevel(levelNdx), gMin, gMax);
+		}
+
+		// Fill second with grid texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			deUint32	step	= 0x00ffffff / numLevels;
+			deUint32	rgb		= step*levelNdx;
+			deUint32	colorA	= 0xff000000 | rgb;
+			deUint32	colorB	= 0xff000000 | ~rgb;
+
+			m_gridTex->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithGrid(m_gridTex->getRefTexture().getLevel(levelNdx), 4, tcu::RGBA(colorA).toVec()*cScale + cBias, tcu::RGBA(colorB).toVec()*cScale + cBias);
+		}
+
+		// Upload.
+		m_gradientTex->upload();
+		m_gridTex->upload();
+
+		// Test cases
+		m_cases.push_back(FilterCase(m_gradientTex,	tcu::Vec3(1.5f, 2.8f, 1.0f),	tcu::Vec3(-1.0f, -2.7f, -2.275f)));
+		m_cases.push_back(FilterCase(m_gradientTex,	tcu::Vec3(-2.0f, -1.5f, -1.8f),	tcu::Vec3(-0.1f, 0.9f, -0.25f)));
+		m_cases.push_back(FilterCase(m_gridTex,		tcu::Vec3(0.2f, 0.175f, 0.3f),	tcu::Vec3(-2.0f, -3.7f, -1.825f)));
+		m_cases.push_back(FilterCase(m_gridTex,		tcu::Vec3(-0.8f, -2.3f, -2.5f),	tcu::Vec3(0.2f, -0.1f, 1.325f)));
+
+		m_caseNdx = 0;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	catch (...)
+	{
+		// Clean up to save memory.
+		Texture3DFilteringCase::deinit();
+		throw;
+	}
+}
+
+void Texture3DFilteringCase::deinit (void)
+{
+	delete m_gradientTex;
+	delete m_gridTex;
+
+	m_gradientTex	= DE_NULL;
+	m_gridTex		= DE_NULL;
+
+	m_renderer.clear();
+	m_cases.clear();
+}
+
+Texture3DFilteringCase::IterateResult Texture3DFilteringCase::iterate (void)
+{
+	const glw::Functions&			gl			= m_context.getRenderContext().getFunctions();
+	const RandomViewport			viewport	(m_context.getRenderTarget(), TEX3D_VIEWPORT_WIDTH, TEX3D_VIEWPORT_HEIGHT, deStringHash(getName()) ^ deInt32Hash(m_caseNdx));
+	const FilterCase&				curCase		= m_cases[m_caseNdx];
+	const tcu::TextureFormat		texFmt		= curCase.texture->getRefTexture().getFormat();
+	const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFmt);
+	const tcu::ScopedLogSection		section		(m_testCtx.getLog(), string("Test") + de::toString(m_caseNdx), string("Test ") + de::toString(m_caseNdx));
+	ReferenceParams					refParams	(TEXTURETYPE_3D);
+	tcu::Surface					rendered	(viewport.width, viewport.height);
+	tcu::Vec3						texCoord[4];
+
+	if (viewport.width < TEX3D_MIN_VIEWPORT_WIDTH || viewport.height < TEX3D_MIN_VIEWPORT_HEIGHT)
+		throw tcu::NotSupportedError("Too small render target", "", __FILE__, __LINE__);
+
+	// Setup params for reference.
+	refParams.sampler		= glu::mapGLSampler(m_wrapS, m_wrapT, m_wrapR, m_minFilter, m_magFilter);
+	refParams.samplerType	= getSamplerType(texFmt);
+	refParams.lodMode		= LODMODE_EXACT;
+	refParams.colorBias		= fmtInfo.lookupBias;
+	refParams.colorScale	= fmtInfo.lookupScale;
+
+	// Compute texture coordinates.
+	m_testCtx.getLog() << TestLog::Message << "Approximate lod per axis = " << curCase.lod << ", offset = " << curCase.offset << TestLog::EndMessage;
+
+	{
+		const float	lodX	= curCase.lod.x();
+		const float	lodY	= curCase.lod.y();
+		const float	lodZ	= curCase.lod.z();
+		const float	oX		= curCase.offset.x();
+		const float	oY		= curCase.offset.y();
+		const float oZ		= curCase.offset.z();
+		const float	sX		= deFloatExp2(lodX)*float(viewport.width)							/ float(m_gradientTex->getRefTexture().getWidth());
+		const float	sY		= deFloatExp2(lodY)*float(viewport.height)							/ float(m_gradientTex->getRefTexture().getHeight());
+		const float	sZ		= deFloatExp2(lodZ)*float(de::max(viewport.width, viewport.height))	/ float(m_gradientTex->getRefTexture().getDepth());
+
+		texCoord[0] = tcu::Vec3(oX,		oY,		oZ);
+		texCoord[1] = tcu::Vec3(oX,		oY+sY,	oZ + sZ*0.5f);
+		texCoord[2] = tcu::Vec3(oX+sX,	oY,		oZ + sZ*0.5f);
+		texCoord[3] = tcu::Vec3(oX+sX,	oY+sY,	oZ + sZ);
+	}
+
+	gl.bindTexture	(GL_TEXTURE_3D, curCase.texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R,		m_wrapR);
+
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+	m_renderer.renderQuad(0, (const float*)&texCoord[0], refParams);
+	glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, rendered.getAccess());
+
+	{
+		const bool				isNearestOnly	= m_minFilter == GL_NEAREST && m_magFilter == GL_NEAREST;
+		const tcu::PixelFormat	pixelFormat		= m_context.getRenderTarget().getPixelFormat();
+		const tcu::IVec4		colorBits		= max(getBitsVec(pixelFormat) - (isNearestOnly ? 1 : 2), tcu::IVec4(0)); // 1 inaccurate bit if nearest only, 2 otherwise
+		tcu::LodPrecision		lodPrecision;
+		tcu::LookupPrecision	lookupPrecision;
+
+		lodPrecision.derivateBits		= 18;
+		lodPrecision.lodBits			= 6;
+		lookupPrecision.colorThreshold	= tcu::computeFixedPointThreshold(colorBits) / refParams.colorScale;
+		lookupPrecision.coordBits		= tcu::IVec3(20,20,20);
+		lookupPrecision.uvwBits			= tcu::IVec3(7,7,7);
+		lookupPrecision.colorMask		= getCompareMask(pixelFormat);
+
+		const bool isHighQuality = verifyTextureResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+													   (const float*)&texCoord[0], refParams, lookupPrecision, lodPrecision, pixelFormat);
+
+		if (!isHighQuality)
+		{
+			// Evaluate against lower precision requirements.
+			lodPrecision.lodBits	= 4;
+			lookupPrecision.uvwBits	= tcu::IVec3(4,4,4);
+
+			m_testCtx.getLog() << TestLog::Message << "Warning: Verification against high precision requirements failed, trying with lower requirements." << TestLog::EndMessage;
+
+			const bool isOk = verifyTextureResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+												  (const float*)&texCoord[0], refParams, lookupPrecision, lodPrecision, pixelFormat);
+
+			if (!isOk)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: Verification against low precision requirements failed, failing test case." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+			}
+			else if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Low-quality filtering result");
+		}
+	}
+
+	m_caseNdx += 1;
+	return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
+}
+
+TextureFilteringTests::TextureFilteringTests (Context& context)
+	: TestCaseGroup(context, "filtering", "Texture Filtering Tests")
+{
+}
+
+TextureFilteringTests::~TextureFilteringTests (void)
+{
+}
+
+void TextureFilteringTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE },
+		{ "repeat",		GL_REPEAT },
+		{ "mirror",		GL_MIRRORED_REPEAT }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} minFilterModes[] =
+	{
+		{ "nearest",				GL_NEAREST					},
+		{ "linear",					GL_LINEAR					},
+		{ "nearest_mipmap_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
+		{ "linear_mipmap_nearest",	GL_LINEAR_MIPMAP_NEAREST	},
+		{ "nearest_mipmap_linear",	GL_NEAREST_MIPMAP_LINEAR	},
+		{ "linear_mipmap_linear",	GL_LINEAR_MIPMAP_LINEAR		}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} magFilterModes[] =
+	{
+		{ "nearest",	GL_NEAREST },
+		{ "linear",		GL_LINEAR }
+	};
+
+	static const struct
+	{
+		int width;
+		int height;
+	} sizes2D[] =
+	{
+		{   4,	  8 },
+		{  32,	 64 },
+		{ 128,	128	},
+		{   3,	  7 },
+		{  31,	 55 },
+		{ 127,	 99 }
+	};
+
+	static const struct
+	{
+		int width;
+		int height;
+	} sizesCube[] =
+	{
+		{   8,   8 },
+		{  64,  64 },
+		{ 128, 128 },
+		{   7,   7 },
+		{  63,  63 }
+	};
+
+	static const struct
+	{
+		int width;
+		int height;
+		int numLayers;
+	} sizes2DArray[] =
+	{
+		{   4,   8,   8 },
+		{  32,  64,  16 },
+		{ 128,  32,  64 },
+		{   3,   7,   5 },
+		{  63,  63,  63 }
+	};
+
+	static const struct
+	{
+		int width;
+		int height;
+		int depth;
+	} sizes3D[] =
+	{
+		{   4,   8,   8 },
+		{  32,  64,  16 },
+		{ 128,  32,  64 },
+		{   3,   7,   5 },
+		{  63,  63,  63 }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		format;
+	} filterableFormatsByType[] =
+	{
+		{ "rgba16f",		GL_RGBA16F			},
+		{ "r11f_g11f_b10f",	GL_R11F_G11F_B10F	},
+		{ "rgb9_e5",		GL_RGB9_E5			},
+		{ "rgba8",			GL_RGBA8			},
+		{ "rgba8_snorm",	GL_RGBA8_SNORM		},
+		{ "rgb565",			GL_RGB565			},
+		{ "rgba4",			GL_RGBA4			},
+		{ "rgb5_a1",		GL_RGB5_A1			},
+		{ "srgb8_alpha8",	GL_SRGB8_ALPHA8		},
+		{ "rgb10_a2",		GL_RGB10_A2			}
+	};
+
+	// 2D texture filtering.
+	{
+		tcu::TestCaseGroup* group2D = new tcu::TestCaseGroup(m_testCtx, "2d", "2D Texture Filtering");
+		addChild(group2D);
+
+		// Formats.
+		tcu::TestCaseGroup* formatsGroup = new tcu::TestCaseGroup(m_testCtx, "formats", "2D Texture Formats");
+		group2D->addChild(formatsGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(filterableFormatsByType); fmtNdx++)
+		{
+			for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
+			{
+				deUint32		minFilter	= minFilterModes[filterNdx].mode;
+				const char*		filterName	= minFilterModes[filterNdx].name;
+				deUint32		format		= filterableFormatsByType[fmtNdx].format;
+				const char*		formatName	= filterableFormatsByType[fmtNdx].name;
+				bool			isMipmap	= minFilter != GL_NEAREST && minFilter != GL_LINEAR;
+				deUint32		magFilter	= isMipmap ? GL_LINEAR : minFilter;
+				string			name		= string(formatName) + "_" + filterName;
+				deUint32		wrapS		= GL_REPEAT;
+				deUint32		wrapT		= GL_REPEAT;
+				int				width		= 64;
+				int				height		= 64;
+
+				formatsGroup->addChild(new Texture2DFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																  name.c_str(), "",
+																  minFilter, magFilter,
+																  wrapS, wrapT,
+																  format,
+																  width, height));
+			}
+		}
+
+		// ETC1 format.
+		{
+			std::vector<std::string> filenames;
+			for (int i = 0; i <= 7; i++)
+				filenames.push_back(string("data/etc1/photo_helsinki_mip_") + de::toString(i) + ".pkm");
+
+			for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
+			{
+				deUint32		minFilter	= minFilterModes[filterNdx].mode;
+				const char*		filterName	= minFilterModes[filterNdx].name;
+				bool			isMipmap	= minFilter != GL_NEAREST && minFilter != GL_LINEAR;
+				deUint32		magFilter	= isMipmap ? GL_LINEAR : minFilter;
+				string			name		= string("etc1_rgb8_") + filterName;
+				deUint32		wrapS		= GL_REPEAT;
+				deUint32		wrapT		= GL_REPEAT;
+
+				formatsGroup->addChild(new Texture2DFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																  name.c_str(), "",
+																  minFilter, magFilter,
+																  wrapS, wrapT,
+																  filenames));
+			}
+		}
+
+		// Sizes.
+		tcu::TestCaseGroup* sizesGroup = new tcu::TestCaseGroup(m_testCtx, "sizes", "Texture Sizes");
+		group2D->addChild(sizesGroup);
+		for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizes2D); sizeNdx++)
+		{
+			for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
+			{
+				deUint32		minFilter	= minFilterModes[filterNdx].mode;
+				const char*		filterName	= minFilterModes[filterNdx].name;
+				deUint32		format		= GL_RGBA8;
+				bool			isMipmap	= minFilter != GL_NEAREST && minFilter != GL_LINEAR;
+				deUint32		magFilter	= isMipmap ? GL_LINEAR : minFilter;
+				deUint32		wrapS		= GL_REPEAT;
+				deUint32		wrapT		= GL_REPEAT;
+				int				width		= sizes2D[sizeNdx].width;
+				int				height		= sizes2D[sizeNdx].height;
+				string			name		= de::toString(width) + "x" + de::toString(height) + "_" + filterName;
+
+				sizesGroup->addChild(new Texture2DFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																name.c_str(), "",
+																minFilter, magFilter,
+																wrapS, wrapT,
+																format,
+																width, height));
+			}
+		}
+
+		// Wrap modes.
+		tcu::TestCaseGroup* combinationsGroup = new tcu::TestCaseGroup(m_testCtx, "combinations", "Filter and wrap mode combinations");
+		group2D->addChild(combinationsGroup);
+		for (int minFilterNdx = 0; minFilterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); minFilterNdx++)
+		{
+			for (int magFilterNdx = 0; magFilterNdx < DE_LENGTH_OF_ARRAY(magFilterModes); magFilterNdx++)
+			{
+				for (int wrapSNdx = 0; wrapSNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapSNdx++)
+				{
+					for (int wrapTNdx = 0; wrapTNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapTNdx++)
+					{
+						deUint32		minFilter	= minFilterModes[minFilterNdx].mode;
+						deUint32		magFilter	= magFilterModes[magFilterNdx].mode;
+						deUint32		format		= GL_RGBA8;
+						deUint32		wrapS		= wrapModes[wrapSNdx].mode;
+						deUint32		wrapT		= wrapModes[wrapTNdx].mode;
+						int				width		= 63;
+						int				height		= 57;
+						string			name		= string(minFilterModes[minFilterNdx].name) + "_" + magFilterModes[magFilterNdx].name + "_" + wrapModes[wrapSNdx].name + "_" + wrapModes[wrapTNdx].name;
+
+						combinationsGroup->addChild(new Texture2DFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																			   name.c_str(), "",
+																			   minFilter, magFilter,
+																			   wrapS, wrapT,
+																			   format,
+																			   width, height));
+					}
+				}
+			}
+		}
+	}
+
+	// Cube map texture filtering.
+	{
+		tcu::TestCaseGroup* groupCube = new tcu::TestCaseGroup(m_testCtx, "cube", "Cube Map Texture Filtering");
+		addChild(groupCube);
+
+		// Formats.
+		tcu::TestCaseGroup* formatsGroup = new tcu::TestCaseGroup(m_testCtx, "formats", "2D Texture Formats");
+		groupCube->addChild(formatsGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(filterableFormatsByType); fmtNdx++)
+		{
+			for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
+			{
+				deUint32		minFilter	= minFilterModes[filterNdx].mode;
+				const char*		filterName	= minFilterModes[filterNdx].name;
+				deUint32		format		= filterableFormatsByType[fmtNdx].format;
+				const char*		formatName	= filterableFormatsByType[fmtNdx].name;
+				bool			isMipmap	= minFilter != GL_NEAREST && minFilter != GL_LINEAR;
+				deUint32		magFilter	= isMipmap ? GL_LINEAR : minFilter;
+				string			name		= string(formatName) + "_" + filterName;
+				deUint32		wrapS		= GL_REPEAT;
+				deUint32		wrapT		= GL_REPEAT;
+				int				width		= 64;
+				int				height		= 64;
+
+				formatsGroup->addChild(new TextureCubeFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																	name.c_str(), "",
+																	minFilter, magFilter,
+																	wrapS, wrapT,
+																	false /* always sample exterior as well */,
+																	format,
+																	width, height));
+			}
+		}
+
+		// ETC1 format.
+		{
+			static const char* faceExt[] = { "neg_x", "pos_x", "neg_y", "pos_y", "neg_z", "pos_z" };
+
+			const int		numLevels	= 7;
+			vector<string>	filenames;
+			for (int level = 0; level < numLevels; level++)
+				for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+					filenames.push_back(string("data/etc1/skybox_") + faceExt[face] + "_mip_" + de::toString(level) + ".pkm");
+
+			for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
+			{
+				deUint32		minFilter	= minFilterModes[filterNdx].mode;
+				const char*		filterName	= minFilterModes[filterNdx].name;
+				bool			isMipmap	= minFilter != GL_NEAREST && minFilter != GL_LINEAR;
+				deUint32		magFilter	= isMipmap ? GL_LINEAR : minFilter;
+				string			name		= string("etc1_rgb8_") + filterName;
+				deUint32		wrapS		= GL_REPEAT;
+				deUint32		wrapT		= GL_REPEAT;
+
+				formatsGroup->addChild(new TextureCubeFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																	name.c_str(), "",
+																	minFilter, magFilter,
+																	wrapS, wrapT,
+																	false /* always sample exterior as well */,
+																	filenames));
+			}
+		}
+
+		// Sizes.
+		tcu::TestCaseGroup* sizesGroup = new tcu::TestCaseGroup(m_testCtx, "sizes", "Texture Sizes");
+		groupCube->addChild(sizesGroup);
+		for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizesCube); sizeNdx++)
+		{
+			for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
+			{
+				deUint32		minFilter	= minFilterModes[filterNdx].mode;
+				const char*		filterName	= minFilterModes[filterNdx].name;
+				deUint32		format		= GL_RGBA8;
+				bool			isMipmap	= minFilter != GL_NEAREST && minFilter != GL_LINEAR;
+				deUint32		magFilter	= isMipmap ? GL_LINEAR : minFilter;
+				deUint32		wrapS		= GL_REPEAT;
+				deUint32		wrapT		= GL_REPEAT;
+				int				width		= sizesCube[sizeNdx].width;
+				int				height		= sizesCube[sizeNdx].height;
+				string			name		= de::toString(width) + "x" + de::toString(height) + "_" + filterName;
+
+				sizesGroup->addChild(new TextureCubeFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																  name.c_str(), "",
+																  minFilter, magFilter,
+																  wrapS, wrapT,
+																  false,
+																  format,
+																  width, height));
+			}
+		}
+
+		// Filter/wrap mode combinations.
+		tcu::TestCaseGroup* combinationsGroup = new tcu::TestCaseGroup(m_testCtx, "combinations", "Filter and wrap mode combinations");
+		groupCube->addChild(combinationsGroup);
+		for (int minFilterNdx = 0; minFilterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); minFilterNdx++)
+		{
+			for (int magFilterNdx = 0; magFilterNdx < DE_LENGTH_OF_ARRAY(magFilterModes); magFilterNdx++)
+			{
+				for (int wrapSNdx = 0; wrapSNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapSNdx++)
+				{
+					for (int wrapTNdx = 0; wrapTNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapTNdx++)
+					{
+						deUint32		minFilter	= minFilterModes[minFilterNdx].mode;
+						deUint32		magFilter	= magFilterModes[magFilterNdx].mode;
+						deUint32		format		= GL_RGBA8;
+						deUint32		wrapS		= wrapModes[wrapSNdx].mode;
+						deUint32		wrapT		= wrapModes[wrapTNdx].mode;
+						int				width		= 63;
+						int				height		= 63;
+						string			name		= string(minFilterModes[minFilterNdx].name) + "_" + magFilterModes[magFilterNdx].name + "_" + wrapModes[wrapSNdx].name + "_" + wrapModes[wrapTNdx].name;
+
+						combinationsGroup->addChild(new TextureCubeFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																				 name.c_str(), "",
+																				 minFilter, magFilter,
+																				 wrapS, wrapT,
+																				 false,
+																				 format,
+																				 width, height));
+					}
+				}
+			}
+		}
+
+		// Cases with no visible cube edges.
+		tcu::TestCaseGroup* onlyFaceInteriorGroup = new tcu::TestCaseGroup(m_testCtx, "no_edges_visible", "Don't sample anywhere near a face's edges");
+		groupCube->addChild(onlyFaceInteriorGroup);
+
+		for (int isLinearI = 0; isLinearI <= 1; isLinearI++)
+		{
+			bool		isLinear	= isLinearI != 0;
+			deUint32	filter		= isLinear ? GL_LINEAR : GL_NEAREST;
+
+			onlyFaceInteriorGroup->addChild(new TextureCubeFilteringCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																		 isLinear ? "linear" : "nearest", "",
+																		 filter, filter,
+																		 GL_REPEAT, GL_REPEAT,
+																		 true,
+																		 GL_RGBA8,
+																		 63, 63));
+		}
+	}
+
+	// 2D array texture filtering.
+	{
+		tcu::TestCaseGroup* const group2DArray = new tcu::TestCaseGroup(m_testCtx, "2d_array", "2D Array Texture Filtering");
+		addChild(group2DArray);
+
+		// Formats.
+		tcu::TestCaseGroup* const formatsGroup = new tcu::TestCaseGroup(m_testCtx, "formats", "2D Array Texture Formats");
+		group2DArray->addChild(formatsGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(filterableFormatsByType); fmtNdx++)
+		{
+			for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
+			{
+				deUint32		minFilter	= minFilterModes[filterNdx].mode;
+				const char*		filterName	= minFilterModes[filterNdx].name;
+				deUint32		format		= filterableFormatsByType[fmtNdx].format;
+				const char*		formatName	= filterableFormatsByType[fmtNdx].name;
+				bool			isMipmap	= minFilter != GL_NEAREST && minFilter != GL_LINEAR;
+				deUint32		magFilter	= isMipmap ? GL_LINEAR : minFilter;
+				string			name		= string(formatName) + "_" + filterName;
+				deUint32		wrapS		= GL_REPEAT;
+				deUint32		wrapT		= GL_REPEAT;
+				int				width		= 128;
+				int				height		= 128;
+				int				numLayers	= 8;
+
+				formatsGroup->addChild(new Texture2DArrayFilteringCase(m_context,
+																	   name.c_str(), "",
+																	   minFilter, magFilter,
+																	   wrapS, wrapT,
+																	   format,
+																	   width, height, numLayers));
+			}
+		}
+
+		// Sizes.
+		tcu::TestCaseGroup* sizesGroup = new tcu::TestCaseGroup(m_testCtx, "sizes", "Texture Sizes");
+		group2DArray->addChild(sizesGroup);
+		for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizes2DArray); sizeNdx++)
+		{
+			for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
+			{
+				deUint32		minFilter	= minFilterModes[filterNdx].mode;
+				const char*		filterName	= minFilterModes[filterNdx].name;
+				deUint32		format		= GL_RGBA8;
+				bool			isMipmap	= minFilter != GL_NEAREST && minFilter != GL_LINEAR;
+				deUint32		magFilter	= isMipmap ? GL_LINEAR : minFilter;
+				deUint32		wrapS		= GL_REPEAT;
+				deUint32		wrapT		= GL_REPEAT;
+				int				width		= sizes2DArray[sizeNdx].width;
+				int				height		= sizes2DArray[sizeNdx].height;
+				int				numLayers	= sizes2DArray[sizeNdx].numLayers;
+				string			name		= de::toString(width) + "x" + de::toString(height) + "x" + de::toString(numLayers) + "_" + filterName;
+
+				sizesGroup->addChild(new Texture2DArrayFilteringCase(m_context,
+																	 name.c_str(), "",
+																	 minFilter, magFilter,
+																	 wrapS, wrapT,
+																	 format,
+																	 width, height, numLayers));
+			}
+		}
+
+		// Wrap modes.
+		tcu::TestCaseGroup* const combinationsGroup = new tcu::TestCaseGroup(m_testCtx, "combinations", "Filter and wrap mode combinations");
+		group2DArray->addChild(combinationsGroup);
+		for (int minFilterNdx = 0; minFilterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); minFilterNdx++)
+		{
+			for (int magFilterNdx = 0; magFilterNdx < DE_LENGTH_OF_ARRAY(magFilterModes); magFilterNdx++)
+			{
+				for (int wrapSNdx = 0; wrapSNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapSNdx++)
+				{
+					for (int wrapTNdx = 0; wrapTNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapTNdx++)
+					{
+						deUint32		minFilter	= minFilterModes[minFilterNdx].mode;
+						deUint32		magFilter	= magFilterModes[magFilterNdx].mode;
+						deUint32		format		= GL_RGBA8;
+						deUint32		wrapS		= wrapModes[wrapSNdx].mode;
+						deUint32		wrapT		= wrapModes[wrapTNdx].mode;
+						int				width		= 123;
+						int				height		= 107;
+						int				numLayers	= 7;
+						string			name		= string(minFilterModes[minFilterNdx].name) + "_" + magFilterModes[magFilterNdx].name + "_" + wrapModes[wrapSNdx].name + "_" + wrapModes[wrapTNdx].name;
+
+						combinationsGroup->addChild(new Texture2DArrayFilteringCase(m_context,
+																					name.c_str(), "",
+																					minFilter, magFilter,
+																					wrapS, wrapT,
+																					format,
+																					width, height, numLayers));
+					}
+				}
+			}
+		}
+	}
+
+	// 3D texture filtering.
+	{
+		tcu::TestCaseGroup* group3D = new tcu::TestCaseGroup(m_testCtx, "3d", "3D Texture Filtering");
+		addChild(group3D);
+
+		// Formats.
+		tcu::TestCaseGroup* formatsGroup = new tcu::TestCaseGroup(m_testCtx, "formats", "3D Texture Formats");
+		group3D->addChild(formatsGroup);
+		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(filterableFormatsByType); fmtNdx++)
+		{
+			for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
+			{
+				deUint32		minFilter	= minFilterModes[filterNdx].mode;
+				const char*		filterName	= minFilterModes[filterNdx].name;
+				deUint32		format		= filterableFormatsByType[fmtNdx].format;
+				const char*		formatName	= filterableFormatsByType[fmtNdx].name;
+				bool			isMipmap	= minFilter != GL_NEAREST && minFilter != GL_LINEAR;
+				deUint32		magFilter	= isMipmap ? GL_LINEAR : minFilter;
+				string			name		= string(formatName) + "_" + filterName;
+				deUint32		wrapS		= GL_REPEAT;
+				deUint32		wrapT		= GL_REPEAT;
+				deUint32		wrapR		= GL_REPEAT;
+				int				width		= 64;
+				int				height		= 64;
+				int				depth		= 64;
+
+				formatsGroup->addChild(new Texture3DFilteringCase(m_context,
+																  name.c_str(), "",
+																  minFilter, magFilter,
+																  wrapS, wrapT, wrapR,
+																  format,
+																  width, height, depth));
+			}
+		}
+
+		// Sizes.
+		tcu::TestCaseGroup* sizesGroup = new tcu::TestCaseGroup(m_testCtx, "sizes", "Texture Sizes");
+		group3D->addChild(sizesGroup);
+		for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizes3D); sizeNdx++)
+		{
+			for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
+			{
+				deUint32		minFilter	= minFilterModes[filterNdx].mode;
+				const char*		filterName	= minFilterModes[filterNdx].name;
+				deUint32		format		= GL_RGBA8;
+				bool			isMipmap	= minFilter != GL_NEAREST && minFilter != GL_LINEAR;
+				deUint32		magFilter	= isMipmap ? GL_LINEAR : minFilter;
+				deUint32		wrapS		= GL_REPEAT;
+				deUint32		wrapT		= GL_REPEAT;
+				deUint32		wrapR		= GL_REPEAT;
+				int				width		= sizes3D[sizeNdx].width;
+				int				height		= sizes3D[sizeNdx].height;
+				int				depth		= sizes3D[sizeNdx].depth;
+				string			name		= de::toString(width) + "x" + de::toString(height) + "x" + de::toString(depth) + "_" + filterName;
+
+				sizesGroup->addChild(new Texture3DFilteringCase(m_context,
+																name.c_str(), "",
+																minFilter, magFilter,
+																wrapS, wrapT, wrapR,
+																format,
+																width, height, depth));
+			}
+		}
+
+		// Wrap modes.
+		tcu::TestCaseGroup* combinationsGroup = new tcu::TestCaseGroup(m_testCtx, "combinations", "Filter and wrap mode combinations");
+		group3D->addChild(combinationsGroup);
+		for (int minFilterNdx = 0; minFilterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); minFilterNdx++)
+		{
+			for (int magFilterNdx = 0; magFilterNdx < DE_LENGTH_OF_ARRAY(magFilterModes); magFilterNdx++)
+			{
+				for (int wrapSNdx = 0; wrapSNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapSNdx++)
+				{
+					for (int wrapTNdx = 0; wrapTNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapTNdx++)
+					{
+						for (int wrapRNdx = 0; wrapRNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapRNdx++)
+						{
+							deUint32		minFilter	= minFilterModes[minFilterNdx].mode;
+							deUint32		magFilter	= magFilterModes[magFilterNdx].mode;
+							deUint32		format		= GL_RGBA8;
+							deUint32		wrapS		= wrapModes[wrapSNdx].mode;
+							deUint32		wrapT		= wrapModes[wrapTNdx].mode;
+							deUint32		wrapR		= wrapModes[wrapRNdx].mode;
+							int				width		= 63;
+							int				height		= 57;
+							int				depth		= 67;
+							string			name		= string(minFilterModes[minFilterNdx].name) + "_" + magFilterModes[magFilterNdx].name + "_" + wrapModes[wrapSNdx].name + "_" + wrapModes[wrapTNdx].name + "_" + wrapModes[wrapRNdx].name;
+
+							combinationsGroup->addChild(new Texture3DFilteringCase(m_context,
+																				   name.c_str(), "",
+																				   minFilter, magFilter,
+																				   wrapS, wrapT, wrapR,
+																				   format,
+																				   width, height, depth));
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTextureFilteringTests.hpp b/modules/gles3/functional/es3fTextureFilteringTests.hpp
new file mode 100644
index 0000000..13d24f9
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureFilteringTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FTEXTUREFILTERINGTESTS_HPP
+#define _ES3FTEXTUREFILTERINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class TextureFilteringTests : public TestCaseGroup
+{
+public:
+								TextureFilteringTests		(Context& context);
+								~TextureFilteringTests		(void);
+
+	void						init						(void);
+
+private:
+								TextureFilteringTests		(const TextureFilteringTests& other);
+	TextureFilteringTests&		operator=					(const TextureFilteringTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTEXTUREFILTERINGTESTS_HPP
diff --git a/modules/gles3/functional/es3fTextureFormatTests.cpp b/modules/gles3/functional/es3fTextureFormatTests.cpp
new file mode 100644
index 0000000..7b42a51
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureFormatTests.cpp
@@ -0,0 +1,1492 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format tests.
+ *
+ * Constants:
+ *  + nearest-neighbor filtering
+ *  + no mipmaps
+ *  + full texture coordinate range (but not outside) tested
+ *  + accessed from fragment shader
+ *  + texture unit 0
+ *  + named texture object
+ *
+ * Variables:
+ *  + texture format
+ *  + texture type: 2D or cubemap
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTextureFormatTests.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using namespace deqp::gls;
+using namespace deqp::gls::TextureTestUtil;
+using tcu::Sampler;
+
+// Texture2DFormatCase
+
+class Texture2DFormatCase : public tcu::TestCase
+{
+public:
+							Texture2DFormatCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height);
+							Texture2DFormatCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 internalFormat, int width, int height);
+							~Texture2DFormatCase	(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							Texture2DFormatCase		(const Texture2DFormatCase& other);
+	Texture2DFormatCase&	operator=				(const Texture2DFormatCase& other);
+
+	glu::RenderContext&		m_renderCtx;
+
+	deUint32				m_format;
+	deUint32				m_dataType;
+	int						m_width;
+	int						m_height;
+
+	glu::Texture2D*			m_texture;
+	TextureRenderer			m_renderer;
+};
+
+Texture2DFormatCase::Texture2DFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(format)
+	, m_dataType	(dataType)
+	, m_width		(width)
+	, m_height		(height)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture2DFormatCase::Texture2DFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 internalFormat, int width, int height)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(internalFormat)
+	, m_dataType	(GL_NONE)
+	, m_width		(width)
+	, m_height		(height)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture2DFormatCase::~Texture2DFormatCase (void)
+{
+	deinit();
+}
+
+void Texture2DFormatCase::init (void)
+{
+	TestLog&				log		= m_testCtx.getLog();
+	tcu::TextureFormat		fmt		= m_dataType ? glu::mapGLTransferFormat(m_format, m_dataType) : glu::mapGLInternalFormat(m_format);
+	tcu::TextureFormatInfo	spec	= tcu::getTextureFormatInfo(fmt);
+	std::ostringstream		fmtName;
+
+	if (m_dataType)
+		fmtName << glu::getPixelFormatStr(m_format) << ", " << glu::getTypeStr(m_dataType);
+	else
+		fmtName << glu::getPixelFormatStr(m_format);
+
+	log << TestLog::Message << "2D texture, " << fmtName.str() << ", " << m_width << "x" << m_height
+							<< ",\n  fill with " << formatGradient(&spec.valueMin, &spec.valueMax) << " gradient"
+		<< TestLog::EndMessage;
+
+	m_texture = m_dataType != GL_NONE
+			  ? new glu::Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height)	// Implicit internal format.
+			  : new glu::Texture2D(m_renderCtx, m_format, m_width, m_height);				// Explicit internal format.
+
+	// Fill level 0.
+	m_texture->getRefTexture().allocLevel(0);
+	tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), spec.valueMin, spec.valueMax);
+}
+
+void Texture2DFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Texture2DFormatCase::IterateResult Texture2DFormatCase::iterate (void)
+{
+	TestLog&				log					= m_testCtx.getLog();
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_width, m_height, deStringHash(getName()));
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	ReferenceParams			renderParams		(TEXTURETYPE_2D);
+	tcu::TextureFormatInfo	spec				= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+	const deUint32			wrapS				= GL_CLAMP_TO_EDGE;
+	const deUint32			wrapT				= GL_CLAMP_TO_EDGE;
+	const deUint32			minFilter			= GL_NEAREST;
+	const deUint32			magFilter			= GL_NEAREST;
+
+	renderParams.flags			|= RenderParams::LOG_ALL;
+	renderParams.samplerType	= getSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.sampler		= Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	renderParams.colorScale		= spec.lookupScale;
+	renderParams.colorBias		= spec.lookupBias;
+
+	computeQuadTexCoord2D(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	log << TestLog::Message << "Texture parameters:"
+							<< "\n  WRAP_S = " << glu::getTextureParameterValueStr(GL_TEXTURE_WRAP_S, wrapS)
+							<< "\n  WRAP_T = " << glu::getTextureParameterValueStr(GL_TEXTURE_WRAP_T, wrapT)
+							<< "\n  MIN_FILTER = " << glu::getTextureParameterValueStr(GL_TEXTURE_MIN_FILTER, minFilter)
+							<< "\n  MAG_FILTER = " << glu::getTextureParameterValueStr(GL_TEXTURE_MAG_FILTER, magFilter)
+		<< TestLog::EndMessage;
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], renderParams);
+
+	// Compare and log.
+	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+// TextureCubeFormatCase
+
+class TextureCubeFormatCase : public tcu::TestCase
+{
+public:
+							TextureCubeFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height);
+							TextureCubeFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 internalFormat, int width, int height);
+							~TextureCubeFormatCase	(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							TextureCubeFormatCase	(const TextureCubeFormatCase& other);
+	TextureCubeFormatCase&	operator=				(const TextureCubeFormatCase& other);
+
+	bool					testFace				(tcu::CubeFace face);
+
+	glu::RenderContext&		m_renderCtx;
+
+	deUint32				m_format;
+	deUint32				m_dataType;
+	int						m_width;
+	int						m_height;
+
+	glu::TextureCube*		m_texture;
+	TextureRenderer			m_renderer;
+
+	int						m_curFace;
+	bool					m_isOk;
+};
+
+TextureCubeFormatCase::TextureCubeFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(format)
+	, m_dataType	(dataType)
+	, m_width		(width)
+	, m_height		(height)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_curFace		(0)
+	, m_isOk		(false)
+{
+}
+
+TextureCubeFormatCase::TextureCubeFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 internalFormat, int width, int height)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(internalFormat)
+	, m_dataType	(GL_NONE)
+	, m_width		(width)
+	, m_height		(height)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_curFace		(0)
+	, m_isOk		(false)
+{
+}
+
+TextureCubeFormatCase::~TextureCubeFormatCase (void)
+{
+	deinit();
+}
+
+void TextureCubeFormatCase::init (void)
+{
+	TestLog&				log		= m_testCtx.getLog();
+	tcu::TextureFormat		fmt		= m_dataType ? glu::mapGLTransferFormat(m_format, m_dataType) : glu::mapGLInternalFormat(m_format);
+	tcu::TextureFormatInfo	spec	= tcu::getTextureFormatInfo(fmt);
+	std::ostringstream		fmtName;
+
+	if (m_dataType)
+		fmtName << glu::getPixelFormatStr(m_format) << ", " << glu::getTypeStr(m_dataType);
+	else
+		fmtName << glu::getPixelFormatStr(m_format);
+
+	log << TestLog::Message << "Cube map texture, " << fmtName.str() << ", " << m_width << "x" << m_height
+							<< ",\n  fill with " << formatGradient(&spec.valueMin, &spec.valueMax) << " gradient"
+		<< TestLog::EndMessage;
+
+	DE_ASSERT(m_width == m_height);
+	m_texture = m_dataType != GL_NONE
+			  ? new glu::TextureCube(m_renderCtx, m_format, m_dataType, m_width)	// Implicit internal format.
+		      : new glu::TextureCube(m_renderCtx, m_format, m_width);				// Explicit internal format.
+
+	// Fill level 0.
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+	{
+		tcu::Vec4 gMin, gMax;
+
+		switch (face)
+		{
+			case 0: gMin = spec.valueMin.swizzle(0, 1, 2, 3); gMax = spec.valueMax.swizzle(0, 1, 2, 3); break;
+			case 1: gMin = spec.valueMin.swizzle(2, 1, 0, 3); gMax = spec.valueMax.swizzle(2, 1, 0, 3); break;
+			case 2: gMin = spec.valueMin.swizzle(1, 2, 0, 3); gMax = spec.valueMax.swizzle(1, 2, 0, 3); break;
+			case 3: gMin = spec.valueMax.swizzle(0, 1, 2, 3); gMax = spec.valueMin.swizzle(0, 1, 2, 3); break;
+			case 4: gMin = spec.valueMax.swizzle(2, 1, 0, 3); gMax = spec.valueMin.swizzle(2, 1, 0, 3); break;
+			case 5: gMin = spec.valueMax.swizzle(1, 2, 0, 3); gMax = spec.valueMin.swizzle(1, 2, 0, 3); break;
+			default:
+				DE_ASSERT(false);
+		}
+
+		m_texture->getRefTexture().allocLevel((tcu::CubeFace)face, 0);
+		tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevelFace(0, (tcu::CubeFace)face), gMin, gMax);
+	}
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Initialize iteration state.
+	m_curFace	= 0;
+	m_isOk		= true;
+}
+
+void TextureCubeFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+bool TextureCubeFormatCase::testFace (tcu::CubeFace face)
+{
+	TestLog&				log					= m_testCtx.getLog();
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_width, m_height, deStringHash(getName())+(deUint32)face);
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	ReferenceParams			renderParams		(TEXTURETYPE_CUBE);
+	tcu::TextureFormatInfo	spec				= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	renderParams.samplerType				= getSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.sampler					= Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	renderParams.sampler.seamlessCubeMap	= true;
+	renderParams.colorScale					= spec.lookupScale;
+	renderParams.colorBias					= spec.lookupBias;
+
+	// Log render info on first face.
+	if (face == tcu::CUBEFACE_NEGATIVE_X)
+		renderParams.flags |= RenderParams::LOG_ALL;
+
+	computeQuadTexCoordCube(texCoord, face);
+
+	// \todo [2011-10-28 pyry] Image set name / section?
+	log << TestLog::Message << face << TestLog::EndMessage;
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], renderParams);
+
+	// Compare and log.
+	return compareImages(log, referenceFrame, renderedFrame, threshold);
+}
+
+TextureCubeFormatCase::IterateResult TextureCubeFormatCase::iterate (void)
+{
+	// Execute test for all faces.
+	if (!testFace((tcu::CubeFace)m_curFace))
+		m_isOk = false;
+
+	m_curFace += 1;
+
+	if (m_curFace == tcu::CUBEFACE_LAST)
+	{
+		m_testCtx.setTestResult(m_isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								m_isOk ? "Pass"					: "Image comparison failed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+// Texture2DArrayFormatCase
+
+class Texture2DArrayFormatCase : public tcu::TestCase
+{
+public:
+										Texture2DArrayFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, int numLayers);
+										Texture2DArrayFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 internalFormat, int width, int height, int numLayers);
+										~Texture2DArrayFormatCase	(void);
+
+	void								init						(void);
+	void								deinit						(void);
+	IterateResult						iterate						(void);
+
+private:
+										Texture2DArrayFormatCase	(const Texture2DArrayFormatCase& other);
+	Texture2DArrayFormatCase&			operator=					(const Texture2DArrayFormatCase& other);
+
+	bool								testLayer					(int layerNdx);
+
+	glu::RenderContext&					m_renderCtx;
+
+	deUint32							m_format;
+	deUint32							m_dataType;
+	int									m_width;
+	int									m_height;
+	int									m_numLayers;
+
+	glu::Texture2DArray*				m_texture;
+	TextureTestUtil::TextureRenderer	m_renderer;
+
+	int									m_curLayer;
+};
+
+Texture2DArrayFormatCase::Texture2DArrayFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, int numLayers)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(format)
+	, m_dataType	(dataType)
+	, m_width		(width)
+	, m_height		(height)
+	, m_numLayers	(numLayers)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_curLayer	(0)
+{
+}
+
+Texture2DArrayFormatCase::Texture2DArrayFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 internalFormat, int width, int height, int numLayers)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(internalFormat)
+	, m_dataType	(GL_NONE)
+	, m_width		(width)
+	, m_height		(height)
+	, m_numLayers	(numLayers)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_curLayer	(0)
+{
+}
+
+Texture2DArrayFormatCase::~Texture2DArrayFormatCase (void)
+{
+	deinit();
+}
+
+void Texture2DArrayFormatCase::init (void)
+{
+	m_texture = m_dataType != GL_NONE
+			  ? new glu::Texture2DArray(m_renderCtx, m_format, m_dataType, m_width, m_height, m_numLayers)	// Implicit internal format.
+			  : new glu::Texture2DArray(m_renderCtx, m_format, m_width, m_height, m_numLayers);				// Explicit internal format.
+
+	tcu::TextureFormatInfo spec = tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	// Fill level 0.
+	m_texture->getRefTexture().allocLevel(0);
+	tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), spec.valueMin, spec.valueMax);
+
+	// Initialize state.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	m_curLayer = 0;
+}
+
+void Texture2DArrayFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+bool Texture2DArrayFormatCase::testLayer (int layerNdx)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_width, m_height, deStringHash(getName()));
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	ReferenceParams			renderParams		(TEXTURETYPE_2D_ARRAY);
+	tcu::TextureFormatInfo	spec				= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	renderParams.samplerType	= getSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.sampler		= Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	renderParams.colorScale		= spec.lookupScale;
+	renderParams.colorBias		= spec.lookupBias;
+
+	computeQuadTexCoord2DArray(texCoord, layerNdx, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D_ARRAY, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], renderParams);
+
+	// Compare and log.
+	return compareImages(log, (string("Layer" + de::toString(layerNdx))).c_str(), (string("Layer " + de::toString(layerNdx))).c_str(), referenceFrame, renderedFrame, threshold);
+}
+
+Texture2DArrayFormatCase::IterateResult Texture2DArrayFormatCase::iterate (void)
+{
+	// Execute test for all layers.
+	bool isOk = testLayer(m_curLayer);
+
+	if (!isOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+	m_curLayer += 1;
+
+	return m_curLayer < m_texture->getRefTexture().getNumLayers() ? CONTINUE : STOP;
+}
+
+// Texture2DFormatCase
+
+class Texture3DFormatCase : public tcu::TestCase
+{
+public:
+										Texture3DFormatCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, int depth);
+										Texture3DFormatCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 internalFormat, int width, int height, int depth);
+										~Texture3DFormatCase	(void);
+
+	void								init					(void);
+	void								deinit					(void);
+	IterateResult						iterate					(void);
+
+private:
+										Texture3DFormatCase		(const Texture3DFormatCase& other);
+	Texture3DFormatCase&				operator=				(const Texture3DFormatCase& other);
+
+	bool								testSlice				(int sliceNdx);
+
+	glu::RenderContext&					m_renderCtx;
+
+	deUint32							m_format;
+	deUint32							m_dataType;
+	int									m_width;
+	int									m_height;
+	int									m_depth;
+
+	glu::Texture3D*						m_texture;
+	TextureTestUtil::TextureRenderer	m_renderer;
+
+	int									m_curSlice;
+};
+
+Texture3DFormatCase::Texture3DFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, int depth)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(format)
+	, m_dataType	(dataType)
+	, m_width		(width)
+	, m_height		(height)
+	, m_depth		(depth)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_curSlice	(0)
+{
+}
+
+Texture3DFormatCase::Texture3DFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 internalFormat, int width, int height, int depth)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(internalFormat)
+	, m_dataType	(GL_NONE)
+	, m_width		(width)
+	, m_height		(height)
+	, m_depth		(depth)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_curSlice	(0)
+{
+}
+
+Texture3DFormatCase::~Texture3DFormatCase (void)
+{
+	deinit();
+}
+
+void Texture3DFormatCase::init (void)
+{
+	m_texture = m_dataType != GL_NONE
+			  ? new glu::Texture3D(m_renderCtx, m_format, m_dataType, m_width, m_height, m_depth)	// Implicit internal format.
+			  : new glu::Texture3D(m_renderCtx, m_format, m_width, m_height, m_depth);				// Explicit internal format.
+
+	tcu::TextureFormatInfo spec = tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	// Fill level 0.
+	m_texture->getRefTexture().allocLevel(0);
+	tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), spec.valueMin, spec.valueMax);
+
+	// Initialize state.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	m_curSlice = 0;
+}
+
+void Texture3DFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+bool Texture3DFormatCase::testSlice (int sliceNdx)
+{
+	TestLog&				log					= m_testCtx.getLog();
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_width, m_height, deStringHash(getName()));
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	ReferenceParams			renderParams		(TEXTURETYPE_3D);
+	tcu::TextureFormatInfo	spec				= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+	float					r					= ((float)sliceNdx + 0.5f) / (float)m_depth;
+
+	renderParams.samplerType	= getSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.sampler		= Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	renderParams.colorScale		= spec.lookupScale;
+	renderParams.colorBias		= spec.lookupBias;
+
+	computeQuadTexCoord3D(texCoord, tcu::Vec3(0.0f, 0.0f, r), tcu::Vec3(1.0f, 1.0f, r), tcu::IVec3(0,1,2));
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_3D, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], renderParams);
+
+	// Compare and log.
+	return compareImages(log, (string("Slice" + de::toString(sliceNdx))).c_str(), (string("Slice " + de::toString(sliceNdx))).c_str(), referenceFrame, renderedFrame, threshold);
+}
+
+Texture3DFormatCase::IterateResult Texture3DFormatCase::iterate (void)
+{
+	// Execute test for all slices.
+	bool isOk = testSlice(m_curSlice);
+
+	if (!isOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+	m_curSlice += 1;
+
+	return m_curSlice < m_texture->getRefTexture().getDepth() ? CONTINUE : STOP;
+}
+
+// Compressed2FormatCase
+
+class Compressed2DFormatCase : public tcu::TestCase
+{
+public:
+										Compressed2DFormatCase		(tcu::TestContext&					testCtx,
+																	 glu::RenderContext&				renderCtx,
+																	 const glu::ContextInfo&			renderCtxInfo,
+																	 const char*						name,
+																	 const char*						description,
+																	 tcu::CompressedTexture::Format		format,
+																	 deUint32							randomSeed,
+																	 int								width,
+																	 int								height);
+										~Compressed2DFormatCase		(void);
+
+	void								init						(void);
+	void								deinit						(void);
+	IterateResult						iterate						(void);
+
+private:
+										Compressed2DFormatCase		(const Compressed2DFormatCase& other);
+	Compressed2DFormatCase&				operator=					(const Compressed2DFormatCase& other);
+
+	glu::RenderContext&					m_renderCtx;
+	const glu::ContextInfo&				m_renderCtxInfo;
+
+	tcu::CompressedTexture::Format		m_format;
+
+	deUint32							m_randomSeed;
+	int									m_width;
+	int									m_height;
+
+	glu::Texture2D*						m_texture;
+	TextureTestUtil::TextureRenderer	m_renderer;
+};
+
+Compressed2DFormatCase::Compressed2DFormatCase (tcu::TestContext&				testCtx,
+												glu::RenderContext&				renderCtx,
+												const glu::ContextInfo&			renderCtxInfo,
+												const char*						name,
+												const char*						description,
+												tcu::CompressedTexture::Format	format,
+												deUint32						randomSeed,
+												int								width,
+												int								height)
+	: TestCase			(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_format			(format)
+	, m_randomSeed		(randomSeed)
+	, m_width			(width)
+	, m_height			(height)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Compressed2DFormatCase::~Compressed2DFormatCase (void)
+{
+	deinit();
+}
+
+void Compressed2DFormatCase::init (void)
+{
+	// Create texture.
+	tcu::CompressedTexture	compressedTexture	(m_format, m_width, m_height);
+	int						dataSize			= compressedTexture.getDataSize();
+	deUint8*				data				= (deUint8*)compressedTexture.getData();
+	de::Random				rnd					(m_randomSeed);
+
+	for (int i = 0; i < dataSize; i++)
+		data[i] = rnd.getUint32() & 0xff;
+
+	m_texture = new glu::Texture2D(m_renderCtx, m_renderCtxInfo, 1, &compressedTexture);
+}
+
+void Compressed2DFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Compressed2DFormatCase::IterateResult Compressed2DFormatCase::iterate (void)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_texture->getRefTexture().getWidth(), m_texture->getRefTexture().getHeight(), deStringHash(getName()));
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	ReferenceParams			renderParams		(TEXTURETYPE_2D);
+	tcu::TextureFormatInfo	spec				= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	renderParams.samplerType	= getSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.sampler		= Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	renderParams.colorScale		= spec.lookupScale;
+	renderParams.colorBias		= spec.lookupBias;
+
+	computeQuadTexCoord2D(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], renderParams);
+
+	// Compare and log.
+	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+// CompressedCubeFormatCase
+
+class CompressedCubeFormatCase : public tcu::TestCase
+{
+public:
+										CompressedCubeFormatCase	(tcu::TestContext& testCtx,
+																	 glu::RenderContext& renderCtx,
+																	 const glu::ContextInfo& renderCtxInfo,
+																	 const char* name,
+																	 const char* description,
+																	 tcu::CompressedTexture::Format format,
+																	 deUint32 randomSeed,
+																	 int width,
+																	 int height);
+										~CompressedCubeFormatCase	(void);
+
+	void								init						(void);
+	void								deinit						(void);
+	IterateResult						iterate						(void);
+
+private:
+										CompressedCubeFormatCase	(const CompressedCubeFormatCase& other);
+	CompressedCubeFormatCase&			operator=					(const CompressedCubeFormatCase& other);
+
+	bool								testFace					(tcu::CubeFace face);
+
+	glu::RenderContext&					m_renderCtx;
+	const glu::ContextInfo&				m_renderCtxInfo;
+
+	tcu::CompressedTexture::Format		m_format;
+
+	deUint32							m_randomSeed;
+	int									m_width;
+	int									m_height;
+
+	glu::TextureCube*					m_texture;
+	TextureTestUtil::TextureRenderer	m_renderer;
+
+	int									m_curFace;
+	bool								m_isOk;
+};
+
+CompressedCubeFormatCase::CompressedCubeFormatCase (tcu::TestContext& testCtx,
+													glu::RenderContext& renderCtx,
+													const glu::ContextInfo& renderCtxInfo,
+													const char* name,
+													const char* description,
+													tcu::CompressedTexture::Format format,
+													deUint32 randomSeed,
+													int width,
+													int height)
+	: TestCase			(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_format			(format)
+	, m_randomSeed		(randomSeed)
+	, m_width			(width)
+	, m_height			(height)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_curFace			(0)
+	, m_isOk			(false)
+{
+}
+
+CompressedCubeFormatCase::~CompressedCubeFormatCase (void)
+{
+	deinit();
+}
+
+void CompressedCubeFormatCase::init (void)
+{
+	vector<tcu::CompressedTexture>	levels	(tcu::CUBEFACE_LAST);
+	de::Random						rnd		(m_randomSeed);
+
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+	{
+		levels[face].setStorage(m_format, m_width, m_height);
+
+		int			dataSize	= levels[face].getDataSize();
+		deUint8*	data		= (deUint8*)levels[face].getData();
+
+		for (int i = 0; i < dataSize; i++)
+			data[i] = rnd.getUint32() & 0xff;
+	}
+
+	m_texture = new glu::TextureCube(m_renderCtx, m_renderCtxInfo, 1, &levels[0]);
+
+	m_curFace	= 0;
+	m_isOk		= true;
+}
+
+void CompressedCubeFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+bool CompressedCubeFormatCase::testFace (tcu::CubeFace face)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_texture->getRefTexture().getSize(), m_texture->getRefTexture().getSize(), deStringHash(getName())+(deUint32)face);
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	ReferenceParams			renderParams		(TEXTURETYPE_CUBE);
+	tcu::TextureFormatInfo	spec				= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	renderParams.samplerType				= getSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.sampler					= Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	renderParams.sampler.seamlessCubeMap	= true;
+	renderParams.colorScale					= spec.lookupScale;
+	renderParams.colorBias					= spec.lookupBias;
+
+	computeQuadTexCoordCube(texCoord, face);
+
+	log << TestLog::Message << face << TestLog::EndMessage;
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], renderParams);
+
+	// Compare and log.
+	return compareImages(log, referenceFrame, renderedFrame, threshold);
+}
+
+CompressedCubeFormatCase::IterateResult CompressedCubeFormatCase::iterate (void)
+{
+	// Execute test for all faces.
+	if (!testFace((tcu::CubeFace)m_curFace))
+		m_isOk = false;
+
+	m_curFace += 1;
+
+	if (m_curFace == tcu::CUBEFACE_LAST)
+	{
+		m_testCtx.setTestResult(m_isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								m_isOk ? "Pass"					: "Image comparison failed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+// Texture2DFileCase
+
+class Texture2DFileCase : public tcu::TestCase
+{
+public:
+								Texture2DFileCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, const std::vector<std::string>& filenames);
+								~Texture2DFileCase		(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+								Texture2DFileCase		(const Texture2DFileCase& other);
+	Texture2DFileCase&			operator=				(const Texture2DFileCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	std::vector<std::string>	m_filenames;
+
+	glu::Texture2D*				m_texture;
+	TextureRenderer				m_renderer;
+};
+
+Texture2DFileCase::Texture2DFileCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, const std::vector<std::string>& filenames)
+	: TestCase			(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_filenames		(filenames)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture2DFileCase::~Texture2DFileCase (void)
+{
+	deinit();
+}
+
+void Texture2DFileCase::init (void)
+{
+	// Create texture.
+	m_texture = glu::Texture2D::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size(), m_filenames);
+}
+
+void Texture2DFileCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Texture2DFileCase::IterateResult Texture2DFileCase::iterate (void)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_texture->getRefTexture().getWidth(), m_texture->getRefTexture().getHeight(), deStringHash(getName()));
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+
+	computeQuadTexCoord2D(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_2D);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
+
+	// Compute reference.
+	ReferenceParams refParams(TEXTURETYPE_2D);
+	refParams.sampler = Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], refParams);
+
+	// Compare and log.
+	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+// TextureCubeFileCase
+
+class TextureCubeFileCase : public tcu::TestCase
+{
+public:
+								TextureCubeFileCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, const std::vector<std::string>& filenames);
+								~TextureCubeFileCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+								TextureCubeFileCase		(const TextureCubeFileCase& other);
+	TextureCubeFileCase&		operator=				(const TextureCubeFileCase& other);
+
+	bool						testFace				(tcu::CubeFace face);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	std::vector<std::string>	m_filenames;
+
+	glu::TextureCube*			m_texture;
+	TextureRenderer				m_renderer;
+
+	int							m_curFace;
+	bool						m_isOk;
+};
+
+TextureCubeFileCase::TextureCubeFileCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, const std::vector<std::string>& filenames)
+	: TestCase			(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_filenames		(filenames)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_curFace			(0)
+	, m_isOk			(false)
+{
+}
+
+TextureCubeFileCase::~TextureCubeFileCase (void)
+{
+	deinit();
+}
+
+void TextureCubeFileCase::init (void)
+{
+	// Create texture.
+	DE_ASSERT(m_filenames.size() % 6 == 0);
+	m_texture = glu::TextureCube::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size()/6, m_filenames);
+
+	m_curFace	= 0;
+	m_isOk		= true;
+}
+
+void TextureCubeFileCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+bool TextureCubeFileCase::testFace (tcu::CubeFace face)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_texture->getRefTexture().getSize(), m_texture->getRefTexture().getSize(), deStringHash(getName())+(deUint32)face);
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	Sampler					sampler				(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+
+	computeQuadTexCoordCube(texCoord, face);
+
+	// \todo [2011-10-28 pyry] Image set name / section?
+	log << TestLog::Message << face << TestLog::EndMessage;
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_CUBE);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], ReferenceParams(TEXTURETYPE_CUBE, sampler));
+
+	// Compare and log.
+	return compareImages(log, referenceFrame, renderedFrame, threshold);
+}
+
+TextureCubeFileCase::IterateResult TextureCubeFileCase::iterate (void)
+{
+	// Execute test for all faces.
+	if (!testFace((tcu::CubeFace)m_curFace))
+		m_isOk = false;
+
+	m_curFace += 1;
+
+	if (m_curFace == tcu::CUBEFACE_LAST)
+	{
+		m_testCtx.setTestResult(m_isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								m_isOk ? "Pass"					: "Image comparison failed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+// TextureFormatTests
+
+TextureFormatTests::TextureFormatTests (Context& context)
+	: TestCaseGroup(context, "format", "Texture Format Tests")
+{
+}
+
+TextureFormatTests::~TextureFormatTests (void)
+{
+}
+
+vector<string> toStringVector (const char* const* str, int numStr)
+{
+	vector<string> v;
+	v.resize(numStr);
+	for (int i = 0; i < numStr; i++)
+		v[i] = str[i];
+	return v;
+}
+
+void TextureFormatTests::init (void)
+{
+	tcu::TestCaseGroup* unsizedGroup	= DE_NULL;
+	tcu::TestCaseGroup*	sizedGroup		= DE_NULL;
+	tcu::TestCaseGroup* compressedGroup	= DE_NULL;
+	addChild((unsizedGroup		= new tcu::TestCaseGroup(m_testCtx, "unsized",		"Unsized formats")));
+	addChild((sizedGroup		= new tcu::TestCaseGroup(m_testCtx, "sized",		"Sized formats")));
+	addChild((compressedGroup	= new tcu::TestCaseGroup(m_testCtx, "compressed",	"Compressed formats")));
+
+	tcu::TestCaseGroup*	sized2DGroup		= DE_NULL;
+	tcu::TestCaseGroup*	sizedCubeGroup		= DE_NULL;
+	tcu::TestCaseGroup*	sized2DArrayGroup	= DE_NULL;
+	tcu::TestCaseGroup*	sized3DGroup		= DE_NULL;
+	sizedGroup->addChild((sized2DGroup			= new tcu::TestCaseGroup(m_testCtx, "2d",			"Sized formats (2D)")));
+	sizedGroup->addChild((sizedCubeGroup		= new tcu::TestCaseGroup(m_testCtx, "cube",			"Sized formats (Cubemap)")));
+	sizedGroup->addChild((sized2DArrayGroup		= new tcu::TestCaseGroup(m_testCtx, "2d_array",		"Sized formats (2D Array)")));
+	sizedGroup->addChild((sized3DGroup			= new tcu::TestCaseGroup(m_testCtx, "3d",			"Sized formats (3D)")));
+
+	struct
+	{
+		const char*	name;
+		deUint32		format;
+		deUint32		dataType;
+	} texFormats[] =
+	{
+		{ "alpha",							GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "luminance",						GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "luminance_alpha",				GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
+		{ "rgb_unsigned_short_5_6_5",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgb_unsigned_byte",				GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba_unsigned_short_4_4_4_4",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgba_unsigned_short_5_5_5_1",	GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
+		{ "rgba_unsigned_byte",				GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+	{
+		deUint32	format			= texFormats[formatNdx].format;
+		deUint32	dataType		= texFormats[formatNdx].dataType;
+		string	nameBase		= texFormats[formatNdx].name;
+		string	descriptionBase	= string(glu::getPixelFormatName(format)) + ", " + glu::getTypeName(dataType);
+
+		unsizedGroup->addChild(new Texture2DFormatCase			(m_testCtx, m_context.getRenderContext(),	(nameBase + "_2d_pot").c_str(),			(descriptionBase + ", GL_TEXTURE_2D").c_str(),			format, dataType, 128, 128));
+		unsizedGroup->addChild(new Texture2DFormatCase			(m_testCtx, m_context.getRenderContext(),	(nameBase + "_2d_npot").c_str(),		(descriptionBase + ", GL_TEXTURE_2D").c_str(),			format, dataType,  63, 112));
+		unsizedGroup->addChild(new TextureCubeFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_cube_pot").c_str(),		(descriptionBase + ", GL_TEXTURE_CUBE_MAP").c_str(),	format, dataType,  64,  64));
+		unsizedGroup->addChild(new TextureCubeFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_cube_npot").c_str(),		(descriptionBase + ", GL_TEXTURE_CUBE_MAP").c_str(),	format, dataType,  57,  57));
+		unsizedGroup->addChild(new Texture2DArrayFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_2d_array_pot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D_ARRAY").c_str(),	format, dataType,  64,  64,  8));
+		unsizedGroup->addChild(new Texture2DArrayFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_2d_array_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D_ARRAY").c_str(),	format, dataType,  63,  57,  7));
+		unsizedGroup->addChild(new Texture3DFormatCase			(m_testCtx, m_context.getRenderContext(),	(nameBase + "_3d_pot").c_str(),			(descriptionBase + ", GL_TEXTURE_3D").c_str(),			format, dataType,   8,  32, 16));
+		unsizedGroup->addChild(new Texture3DFormatCase			(m_testCtx, m_context.getRenderContext(),	(nameBase + "_3d_npot").c_str(),		(descriptionBase + ", GL_TEXTURE_3D").c_str(),			format, dataType,  11,  31,  7));
+	}
+
+	struct
+	{
+		const char*	name;
+		deUint32		internalFormat;
+	} sizedColorFormats[] =
+	{
+		{ "rgba32f",			GL_RGBA32F,			},
+		{ "rgba32i",			GL_RGBA32I,			},
+		{ "rgba32ui",			GL_RGBA32UI,		},
+		{ "rgba16f",			GL_RGBA16F,			},
+		{ "rgba16i",			GL_RGBA16I,			},
+		{ "rgba16ui",			GL_RGBA16UI,		},
+		{ "rgba8",				GL_RGBA8,			},
+		{ "rgba8i",				GL_RGBA8I,			},
+		{ "rgba8ui",			GL_RGBA8UI,			},
+		{ "srgb8_alpha8",		GL_SRGB8_ALPHA8,	},
+		{ "rgb10_a2",			GL_RGB10_A2,		},
+		{ "rgb10_a2ui",			GL_RGB10_A2UI,		},
+		{ "rgba4",				GL_RGBA4,			},
+		{ "rgb5_a1",			GL_RGB5_A1,			},
+		{ "rgba8_snorm",		GL_RGBA8_SNORM,		},
+		{ "rgb8",				GL_RGB8,			},
+		{ "rgb565",				GL_RGB565,			},
+		{ "r11f_g11f_b10f",		GL_R11F_G11F_B10F,	},
+		{ "rgb32f",				GL_RGB32F,			},
+		{ "rgb32i",				GL_RGB32I,			},
+		{ "rgb32ui",			GL_RGB32UI,			},
+		{ "rgb16f",				GL_RGB16F,			},
+		{ "rgb16i",				GL_RGB16I,			},
+		{ "rgb16ui",			GL_RGB16UI,			},
+		{ "rgb8_snorm",			GL_RGB8_SNORM,		},
+		{ "rgb8i",				GL_RGB8I,			},
+		{ "rgb8ui",				GL_RGB8UI,			},
+		{ "srgb8",				GL_SRGB8,			},
+		{ "rgb9_e5",			GL_RGB9_E5,			},
+		{ "rg32f",				GL_RG32F,			},
+		{ "rg32i",				GL_RG32I,			},
+		{ "rg32ui",				GL_RG32UI,			},
+		{ "rg16f",				GL_RG16F,			},
+		{ "rg16i",				GL_RG16I,			},
+		{ "rg16ui",				GL_RG16UI,			},
+		{ "rg8",				GL_RG8,				},
+		{ "rg8i",				GL_RG8I,			},
+		{ "rg8ui",				GL_RG8UI,			},
+		{ "rg8_snorm",			GL_RG8_SNORM,		},
+		{ "r32f",				GL_R32F,			},
+		{ "r32i",				GL_R32I,			},
+		{ "r32ui",				GL_R32UI,			},
+		{ "r16f",				GL_R16F,			},
+		{ "r16i",				GL_R16I,			},
+		{ "r16ui",				GL_R16UI,			},
+		{ "r8",					GL_R8,				},
+		{ "r8i",				GL_R8I,				},
+		{ "r8ui",				GL_R8UI,			},
+		{ "r8_snorm",			GL_R8_SNORM,		}
+	};
+
+	struct
+	{
+		const char*	name;
+		deUint32		internalFormat;
+	} sizedDepthStencilFormats[] =
+	{
+		// Depth and stencil formats
+		{ "depth_component32f",	GL_DEPTH_COMPONENT32F	},
+		{ "depth_component24",	GL_DEPTH_COMPONENT24	},
+		{ "depth_component16",	GL_DEPTH_COMPONENT16	},
+		{ "depth32f_stencil8",	GL_DEPTH32F_STENCIL8	},
+		{ "depth24_stencil8",	GL_DEPTH24_STENCIL8		}
+	};
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(sizedColorFormats); formatNdx++)
+	{
+		deUint32	internalFormat	= sizedColorFormats[formatNdx].internalFormat;
+		string	nameBase		= sizedColorFormats[formatNdx].name;
+		string	descriptionBase	= glu::getPixelFormatName(internalFormat);
+
+		sized2DGroup->addChild		(new Texture2DFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_pot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D").c_str(),			internalFormat, 128, 128));
+		sized2DGroup->addChild		(new Texture2DFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D").c_str(),			internalFormat,  63, 112));
+		sizedCubeGroup->addChild	(new TextureCubeFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_pot").c_str(),	(descriptionBase + ", GL_TEXTURE_CUBE_MAP").c_str(),	internalFormat,  64,  64));
+		sizedCubeGroup->addChild	(new TextureCubeFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_CUBE_MAP").c_str(),	internalFormat,  57,  57));
+		sized2DArrayGroup->addChild	(new Texture2DArrayFormatCase	(m_testCtx, m_context.getRenderContext(),	(nameBase + "_pot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D_ARRAY").c_str(),	internalFormat,  64,  64,  8));
+		sized2DArrayGroup->addChild	(new Texture2DArrayFormatCase	(m_testCtx, m_context.getRenderContext(),	(nameBase + "_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D_ARRAY").c_str(),	internalFormat,  63,  57,  7));
+		sized3DGroup->addChild		(new Texture3DFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_pot").c_str(),	(descriptionBase + ", GL_TEXTURE_3D").c_str(),			internalFormat,   8,  32, 16));
+		sized3DGroup->addChild		(new Texture3DFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_3D").c_str(),			internalFormat,  11,  31,  7));
+	}
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(sizedDepthStencilFormats); formatNdx++)
+	{
+		deUint32	internalFormat	= sizedDepthStencilFormats[formatNdx].internalFormat;
+		string	nameBase		= sizedDepthStencilFormats[formatNdx].name;
+		string	descriptionBase	= glu::getPixelFormatName(internalFormat);
+
+		sized2DGroup->addChild		(new Texture2DFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_pot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D").c_str(),			internalFormat, 128, 128));
+		sized2DGroup->addChild		(new Texture2DFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D").c_str(),			internalFormat,  63, 112));
+		sizedCubeGroup->addChild	(new TextureCubeFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_pot").c_str(),	(descriptionBase + ", GL_TEXTURE_CUBE_MAP").c_str(),	internalFormat,  64,  64));
+		sizedCubeGroup->addChild	(new TextureCubeFormatCase		(m_testCtx, m_context.getRenderContext(),	(nameBase + "_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_CUBE_MAP").c_str(),	internalFormat,  57,  57));
+		sized2DArrayGroup->addChild	(new Texture2DArrayFormatCase	(m_testCtx, m_context.getRenderContext(),	(nameBase + "_pot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D_ARRAY").c_str(),	internalFormat,  64,  64,  8));
+		sized2DArrayGroup->addChild	(new Texture2DArrayFormatCase	(m_testCtx, m_context.getRenderContext(),	(nameBase + "_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_2D_ARRAY").c_str(),	internalFormat,  63,  57,  7));
+	}
+
+	// ETC-1 compressed formats.
+	{
+		static const char* filenames[] =
+		{
+			"data/etc1/photo_helsinki_mip_0.pkm",
+			"data/etc1/photo_helsinki_mip_1.pkm",
+			"data/etc1/photo_helsinki_mip_2.pkm",
+			"data/etc1/photo_helsinki_mip_3.pkm",
+			"data/etc1/photo_helsinki_mip_4.pkm",
+			"data/etc1/photo_helsinki_mip_5.pkm",
+			"data/etc1/photo_helsinki_mip_6.pkm",
+			"data/etc1/photo_helsinki_mip_7.pkm"
+		};
+		compressedGroup->addChild(new Texture2DFileCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), "etc1_2d_pot", "GL_ETC1_RGB8_OES, GL_TEXTURE_2D", toStringVector(filenames, DE_LENGTH_OF_ARRAY(filenames))));
+	}
+
+	{
+		vector<string> filenames;
+		filenames.push_back("data/etc1/photo_helsinki_113x89.pkm");
+		compressedGroup->addChild(new Texture2DFileCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), "etc1_2d_npot", "GL_ETC1_RGB8_OES, GL_TEXTURE_2D", filenames));
+	}
+
+	{
+		static const char* faceExt[] = { "neg_x", "pos_x", "neg_y", "pos_y", "neg_z", "pos_z" };
+
+		const int		potNumLevels	= 7;
+		vector<string>	potFilenames;
+		for (int level = 0; level < potNumLevels; level++)
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+				potFilenames.push_back(string("data/etc1/skybox_") + faceExt[face] + "_mip_" + de::toString(level) + ".pkm");
+
+		compressedGroup->addChild(new TextureCubeFileCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), "etc1_cube_pot", "GL_ETC1_RGB8_OES, GL_TEXTURE_CUBE_MAP", potFilenames));
+
+		vector<string> npotFilenames;
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			npotFilenames.push_back(string("data/etc1/skybox_61x61_") + faceExt[face] + ".pkm");
+
+		compressedGroup->addChild(new TextureCubeFileCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), "etc1_cube_npot", "GL_ETC_RGB8_OES, GL_TEXTURE_CUBE_MAP", npotFilenames));
+	}
+
+	// ETC-2 and EAC compressed formats.
+	struct {
+		const char*						descriptionBase;
+		const char*						nameBase;
+		tcu::CompressedTexture::Format	format;
+	} etc2Formats[] =
+	{
+		{ "GL_COMPRESSED_R11_EAC",							"eac_r11",							tcu::CompressedTexture::EAC_R11,						},
+		{ "GL_COMPRESSED_SIGNED_R11_EAC",					"eac_signed_r11",					tcu::CompressedTexture::EAC_SIGNED_R11,					},
+		{ "GL_COMPRESSED_RG11_EAC",							"eac_rg11",							tcu::CompressedTexture::EAC_RG11,						},
+		{ "GL_COMPRESSED_SIGNED_RG11_EAC",					"eac_signed_rg11",					tcu::CompressedTexture::EAC_SIGNED_RG11,				},
+		{ "GL_COMPRESSED_RGB8_ETC2",						"etc2_rgb8",						tcu::CompressedTexture::ETC2_RGB8,						},
+		{ "GL_COMPRESSED_SRGB8_ETC2",						"etc2_srgb8",						tcu::CompressedTexture::ETC2_SRGB8,						},
+		{ "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2",	"etc2_rgb8_punchthrough_alpha1",	tcu::CompressedTexture::ETC2_RGB8_PUNCHTHROUGH_ALPHA1,	},
+		{ "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2",	"etc2_srgb8_punchthrough_alpha1",	tcu::CompressedTexture::ETC2_SRGB8_PUNCHTHROUGH_ALPHA1,	},
+		{ "GL_COMPRESSED_RGBA8_ETC2_EAC",					"etc2_eac_rgba8",					tcu::CompressedTexture::ETC2_EAC_RGBA8,					},
+		{ "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC",			"etc2_eac_srgb8_alpha8",			tcu::CompressedTexture::ETC2_EAC_SRGB8_ALPHA8,			}
+	};
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(etc2Formats); formatNdx++)
+	{
+		string descriptionBase	= etc2Formats[formatNdx].descriptionBase;
+		string nameBase			= etc2Formats[formatNdx].nameBase;
+
+		compressedGroup->addChild(new Compressed2DFormatCase	(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), (nameBase + "_2d_pot").c_str(),		(descriptionBase + ", GL_TEXTURE_2D").c_str(),			etc2Formats[formatNdx].format,	1,	128,	64));
+		compressedGroup->addChild(new CompressedCubeFormatCase	(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), (nameBase + "_cube_pot").c_str(),		(descriptionBase + ", GL_TEXTURE_CUBE_MAP").c_str(),	etc2Formats[formatNdx].format,	1,	64,		64));
+		compressedGroup->addChild(new Compressed2DFormatCase	(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), (nameBase + "_2d_npot").c_str(),		(descriptionBase + ", GL_TEXTURE_2D").c_str(),			etc2Formats[formatNdx].format,	1,	51,		65));
+		compressedGroup->addChild(new CompressedCubeFormatCase	(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), (nameBase + "_cube_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_CUBE_MAP").c_str(),	etc2Formats[formatNdx].format,	1,	51,		51));
+	}
+
+
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTextureFormatTests.hpp b/modules/gles3/functional/es3fTextureFormatTests.hpp
new file mode 100644
index 0000000..319c38e
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureFormatTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FTEXTUREFORMATTESTS_HPP
+#define _ES3FTEXTUREFORMATTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class TextureFormatTests : public TestCaseGroup
+{
+public:
+							TextureFormatTests		(Context& context);
+							~TextureFormatTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureFormatTests		(const TextureFormatTests& other);
+	TextureFormatTests&		operator=				(const TextureFormatTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTEXTUREFORMATTESTS_HPP
diff --git a/modules/gles3/functional/es3fTextureMipmapTests.cpp b/modules/gles3/functional/es3fTextureMipmapTests.cpp
new file mode 100644
index 0000000..02aede9
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureMipmapTests.cpp
@@ -0,0 +1,2733 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Mipmapping tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTextureMipmapTests.hpp"
+
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuMatrixUtil.hpp"
+#include "tcuTexLookupVerifier.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "tcuImageIO.hpp"
+
+using std::vector;
+using std::string;
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec4;
+using namespace gls::TextureTestUtil;
+
+static float getMinLodForCell (int cellNdx)
+{
+	static const float s_values[] =
+	{
+		1.0f,
+		3.5f,
+		2.0f,
+		-2.0f,
+		0.0f,
+		3.0f,
+		10.0f,
+		4.8f,
+		5.8f,
+		5.7f,
+		-1.9f,
+		4.0f,
+		6.5f,
+		7.1f,
+		-1e10,
+		1000.f
+	};
+	return s_values[cellNdx % DE_LENGTH_OF_ARRAY(s_values)];
+}
+
+static float getMaxLodForCell (int cellNdx)
+{
+	static const float s_values[] =
+	{
+		0.0f,
+		0.2f,
+		0.7f,
+		0.4f,
+		1.3f,
+		0.0f,
+		0.5f,
+		1.2f,
+		-2.0f,
+		1.0f,
+		0.1f,
+		0.3f,
+		2.7f,
+		1.2f,
+		10.0f,
+		-1000.f,
+		1e10f
+	};
+	return s_values[cellNdx % DE_LENGTH_OF_ARRAY(s_values)];
+}
+
+enum CoordType
+{
+	COORDTYPE_BASIC,		//!< texCoord = translateScale(position).
+	COORDTYPE_BASIC_BIAS,	//!< Like basic, but with bias values.
+	COORDTYPE_AFFINE,		//!< texCoord = translateScaleRotateShear(position).
+	COORDTYPE_PROJECTED,	//!< Projected coordinates, w != 1
+
+	COORDTYPE_LAST
+};
+
+// Texture2DMipmapCase
+
+class Texture2DMipmapCase : public tcu::TestCase
+{
+public:
+
+								Texture2DMipmapCase			(tcu::TestContext&			testCtx,
+															 glu::RenderContext&		renderCtx,
+															 const glu::ContextInfo&	renderCtxInfo,
+															 const char*				name,
+															 const char*				desc,
+															 CoordType					coordType,
+															 deUint32					minFilter,
+															 deUint32					wrapS,
+															 deUint32					wrapT,
+															 deUint32					format,
+															 deUint32					dataType,
+															 int						width,
+															 int						height);
+								~Texture2DMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Texture2DMipmapCase			(const Texture2DMipmapCase& other);
+	Texture2DMipmapCase&		operator=					(const Texture2DMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	CoordType					m_coordType;
+	deUint32					m_minFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	deUint32					m_format;
+	deUint32					m_dataType;
+	int							m_width;
+	int							m_height;
+
+	glu::Texture2D*				m_texture;
+	TextureRenderer				m_renderer;
+};
+
+Texture2DMipmapCase::Texture2DMipmapCase (tcu::TestContext&			testCtx,
+										  glu::RenderContext&		renderCtx,
+										  const glu::ContextInfo&	renderCtxInfo,
+										  const char*				name,
+										  const char*				desc,
+										  CoordType					coordType,
+										  deUint32					minFilter,
+										  deUint32					wrapS,
+										  deUint32					wrapT,
+										  deUint32					format,
+										  deUint32					dataType,
+										  int						width,
+										  int						height)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_coordType		(coordType)
+	, m_minFilter		(minFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_width			(width)
+	, m_height			(height)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture2DMipmapCase::~Texture2DMipmapCase (void)
+{
+	deinit();
+}
+
+void Texture2DMipmapCase::init (void)
+{
+	if (m_coordType == COORDTYPE_PROJECTED && m_renderCtx.getRenderTarget().getNumSamples() > 0)
+		throw tcu::NotSupportedError("Projected lookup validation not supported in multisample config");
+
+	m_texture = new glu::Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+
+	int numLevels = deLog2Floor32(de::max(m_width, m_height))+1;
+
+	// Fill texture with colored grid.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		deUint32	step		= 0xff / (numLevels-1);
+		deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+		deUint32	dec			= 0xff - inc;
+		deUint32	rgb			= (inc << 16) | (dec << 8) | 0xff;
+		deUint32	color		= 0xff000000 | rgb;
+
+		m_texture->getRefTexture().allocLevel(levelNdx);
+		tcu::clear(m_texture->getRefTexture().getLevel(levelNdx), toVec4(tcu::RGBA(color)));
+	}
+}
+
+void Texture2DMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+static void getBasicTexCoord2D (std::vector<float>& dst, int cellNdx)
+{
+	static const struct
+	{
+		Vec2 bottomLeft;
+		Vec2 topRight;
+	} s_basicCoords[] =
+	{
+		{ Vec2(-0.1f,  0.1f), Vec2( 0.8f,  1.0f) },
+		{ Vec2(-0.3f, -0.6f), Vec2( 0.7f,  0.4f) },
+		{ Vec2(-0.3f,  0.6f), Vec2( 0.7f, -0.9f) },
+		{ Vec2(-0.8f,  0.6f), Vec2( 0.7f, -0.9f) },
+
+		{ Vec2(-0.5f, -0.5f), Vec2( 1.5f,  1.5f) },
+		{ Vec2( 1.0f, -1.0f), Vec2(-1.3f,  1.0f) },
+		{ Vec2( 1.2f, -1.0f), Vec2(-1.3f,  1.6f) },
+		{ Vec2( 2.2f, -1.1f), Vec2(-1.3f,  0.8f) },
+
+		{ Vec2(-1.5f,  1.6f), Vec2( 1.7f, -1.4f) },
+		{ Vec2( 2.0f,  1.6f), Vec2( 2.3f, -1.4f) },
+		{ Vec2( 1.3f, -2.6f), Vec2(-2.7f,  2.9f) },
+		{ Vec2(-0.8f, -6.6f), Vec2( 6.0f, -0.9f) },
+
+		{ Vec2( -8.0f,   9.0f), Vec2(  8.3f,  -7.0f) },
+		{ Vec2(-16.0f,  10.0f), Vec2( 18.3f,  24.0f) },
+		{ Vec2( 30.2f,  55.0f), Vec2(-24.3f,  -1.6f) },
+		{ Vec2(-33.2f,  64.1f), Vec2( 32.1f, -64.1f) },
+	};
+
+	DE_ASSERT(de::inBounds(cellNdx, 0, DE_LENGTH_OF_ARRAY(s_basicCoords)));
+
+	const Vec2& bottomLeft	= s_basicCoords[cellNdx].bottomLeft;
+	const Vec2& topRight	= s_basicCoords[cellNdx].topRight;
+
+	computeQuadTexCoord2D(dst, bottomLeft, topRight);
+}
+
+static void getAffineTexCoord2D (std::vector<float>& dst, int cellNdx)
+{
+	// Use basic coords as base.
+	getBasicTexCoord2D(dst, cellNdx);
+
+	// Rotate based on cell index.
+	float		angle		= 2.0f*DE_PI * ((float)cellNdx / 16.0f);
+	tcu::Mat2	rotMatrix	= tcu::rotationMatrix(angle);
+
+	// Second and third row are sheared.
+	float		shearX		= de::inRange(cellNdx, 4, 11) ? (float)(15-cellNdx) / 16.0f : 0.0f;
+	tcu::Mat2	shearMatrix	= tcu::shearMatrix(tcu::Vec2(shearX, 0.0f));
+
+	tcu::Mat2	transform	= rotMatrix * shearMatrix;
+	Vec2		p0			= transform * Vec2(dst[0], dst[1]);
+	Vec2		p1			= transform * Vec2(dst[2], dst[3]);
+	Vec2		p2			= transform * Vec2(dst[4], dst[5]);
+	Vec2		p3			= transform * Vec2(dst[6], dst[7]);
+
+	dst[0] = p0.x();	dst[1] = p0.y();
+	dst[2] = p1.x();	dst[3] = p1.y();
+	dst[4] = p2.x();	dst[5] = p2.y();
+	dst[6] = p3.x();	dst[7] = p3.y();
+}
+
+Texture2DMipmapCase::IterateResult Texture2DMipmapCase::iterate (void)
+{
+	const glw::Functions&		gl					= m_renderCtx.getFunctions();
+
+	const tcu::Texture2D&		refTexture			= m_texture->getRefTexture();
+
+	const deUint32				magFilter			= GL_NEAREST;
+	const int					texWidth			= refTexture.getWidth();
+	const int					texHeight			= refTexture.getHeight();
+	const int					defViewportWidth	= texWidth*4;
+	const int					defViewportHeight	= texHeight*4;
+
+	const RandomViewport		viewport			(m_renderCtx.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+	ReferenceParams				sampleParams		(TEXTURETYPE_2D);
+	vector<float>				texCoord;
+
+	const bool					isProjected			= m_coordType == COORDTYPE_PROJECTED;
+	const bool					useLodBias			= m_coordType == COORDTYPE_BASIC_BIAS;
+
+	tcu::Surface				renderedFrame		(viewport.width, viewport.height);
+
+	// Viewport is divided into 4x4 grid.
+	int							gridWidth			= 4;
+	int							gridHeight			= 4;
+	int							cellWidth			= viewport.width / gridWidth;
+	int							cellHeight			= viewport.height / gridHeight;
+
+	// Bail out if rendertarget is too small.
+	if (viewport.width < defViewportWidth/2 || viewport.height < defViewportHeight/2)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Sampling parameters.
+	sampleParams.sampler		= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, magFilter);
+	sampleParams.samplerType	= gls::TextureTestUtil::getSamplerType(m_texture->getRefTexture().getFormat());
+	sampleParams.flags			= (isProjected ? ReferenceParams::PROJECTED : 0) | (useLodBias ? ReferenceParams::USE_BIAS : 0);
+	sampleParams.lodMode		= LODMODE_EXACT; // Use ideal lod.
+
+	// Upload texture data.
+	m_texture->upload();
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture	(GL_TEXTURE_2D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Bias values.
+	static const float s_bias[] = { 1.0f, -2.0f, 0.8f, -0.5f, 1.5f, 0.9f, 2.0f, 4.0f };
+
+	// Projection values.
+	static const Vec4 s_projections[] =
+	{
+		Vec4(1.2f, 1.0f, 0.7f, 1.0f),
+		Vec4(1.3f, 0.8f, 0.6f, 2.0f),
+		Vec4(0.8f, 1.0f, 1.7f, 0.6f),
+		Vec4(1.2f, 1.0f, 1.7f, 1.5f)
+	};
+
+	// Render cells.
+	for (int gridY = 0; gridY < gridHeight; gridY++)
+	{
+		for (int gridX = 0; gridX < gridWidth; gridX++)
+		{
+			const int		curX		= cellWidth*gridX;
+			const int		curY		= cellHeight*gridY;
+			const int		curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+			const int		curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+			const int		cellNdx		= gridY*gridWidth + gridX;
+
+			// Compute texcoord.
+			switch (m_coordType)
+			{
+				case COORDTYPE_BASIC_BIAS:	// Fall-through.
+				case COORDTYPE_PROJECTED:
+				case COORDTYPE_BASIC:		getBasicTexCoord2D	(texCoord, cellNdx);	break;
+				case COORDTYPE_AFFINE:		getAffineTexCoord2D	(texCoord, cellNdx);	break;
+				default:					DE_ASSERT(DE_FALSE);
+			}
+
+			if (isProjected)
+				sampleParams.w = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+
+			if (useLodBias)
+				sampleParams.bias = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+
+			// Render with GL.
+			gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+			m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+		}
+	}
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log.
+	{
+		const tcu::PixelFormat&	pixelFormat		= m_renderCtx.getRenderTarget().getPixelFormat();
+		const bool				isTrilinear		= m_minFilter == GL_NEAREST_MIPMAP_LINEAR || m_minFilter == GL_LINEAR_MIPMAP_LINEAR;
+		tcu::Surface			referenceFrame	(viewport.width, viewport.height);
+		tcu::Surface			errorMask		(viewport.width, viewport.height);
+		tcu::LookupPrecision	lookupPrec;
+		tcu::LodPrecision		lodPrec;
+		int						numFailedPixels	= 0;
+
+		lookupPrec.coordBits		= tcu::IVec3(20, 20, 0);
+		lookupPrec.uvwBits			= tcu::IVec3(16, 16, 0); // Doesn't really matter since pixels are unicolored.
+		lookupPrec.colorThreshold	= tcu::computeFixedPointThreshold(max(getBitsVec(pixelFormat) - (isTrilinear ? 2 : 1), tcu::IVec4(0)));
+		lookupPrec.colorMask		= getCompareMask(pixelFormat);
+		lodPrec.derivateBits		= 10;
+		lodPrec.lodBits				= isProjected ? 6 : 8;
+
+		for (int gridY = 0; gridY < gridHeight; gridY++)
+		{
+			for (int gridX = 0; gridX < gridWidth; gridX++)
+			{
+				const int		curX		= cellWidth*gridX;
+				const int		curY		= cellHeight*gridY;
+				const int		curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+				const int		curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+				const int		cellNdx		= gridY*gridWidth + gridX;
+
+				// Compute texcoord.
+				switch (m_coordType)
+				{
+					case COORDTYPE_BASIC_BIAS:	// Fall-through.
+					case COORDTYPE_PROJECTED:
+					case COORDTYPE_BASIC:		getBasicTexCoord2D	(texCoord, cellNdx);	break;
+					case COORDTYPE_AFFINE:		getAffineTexCoord2D	(texCoord, cellNdx);	break;
+					default:					DE_ASSERT(DE_FALSE);
+				}
+
+				if (isProjected)
+					sampleParams.w = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+
+				if (useLodBias)
+					sampleParams.bias = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+
+				// Render ideal result
+				sampleTexture(SurfaceAccess(referenceFrame, pixelFormat, curX, curY, curW, curH),
+							  refTexture, &texCoord[0], sampleParams);
+
+				// Compare this cell
+				numFailedPixels += computeTextureLookupDiff(tcu::getSubregion(renderedFrame.getAccess(), curX, curY, curW, curH),
+															tcu::getSubregion(referenceFrame.getAccess(), curX, curY, curW, curH),
+															tcu::getSubregion(errorMask.getAccess(), curX, curY, curW, curH),
+															m_texture->getRefTexture(), &texCoord[0], sampleParams,
+															lookupPrec, lodPrec, m_testCtx.getWatchDog());
+			}
+		}
+
+		if (numFailedPixels > 0)
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Image verification failed, found " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+		m_testCtx.getLog() << TestLog::ImageSet("Result", "Verification result")
+							<< TestLog::Image("Rendered", "Rendered image", renderedFrame);
+
+		if (numFailedPixels > 0)
+		{
+			m_testCtx.getLog() << TestLog::Image("Reference", "Ideal reference", referenceFrame)
+								<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+		}
+
+		m_testCtx.getLog() << TestLog::EndImageSet;
+
+		{
+			const bool isOk = numFailedPixels == 0;
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image verification failed");
+		}
+	}
+
+	return STOP;
+}
+
+// TextureCubeMipmapCase
+
+class TextureCubeMipmapCase : public tcu::TestCase
+{
+public:
+
+								TextureCubeMipmapCase		(tcu::TestContext&			testCtx,
+															 glu::RenderContext&		renderCtx,
+															 const glu::ContextInfo&	renderCtxInfo,
+															 const char*				name,
+															 const char*				desc,
+															 CoordType					coordType,
+															 deUint32					minFilter,
+															 deUint32					wrapS,
+															 deUint32					wrapT,
+															 deUint32					format,
+															 deUint32					dataType,
+															 int						size);
+								~TextureCubeMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								TextureCubeMipmapCase		(const TextureCubeMipmapCase& other);
+	TextureCubeMipmapCase&		operator=					(const TextureCubeMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_renderCtxInfo;
+
+	CoordType					m_coordType;
+	deUint32					m_minFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	deUint32					m_format;
+	deUint32					m_dataType;
+	int							m_size;
+
+	glu::TextureCube*			m_texture;
+	TextureRenderer				m_renderer;
+};
+
+TextureCubeMipmapCase::TextureCubeMipmapCase (tcu::TestContext&			testCtx,
+											  glu::RenderContext&		renderCtx,
+											  const glu::ContextInfo&	renderCtxInfo,
+											  const char*				name,
+											  const char*				desc,
+											  CoordType					coordType,
+											  deUint32					minFilter,
+											  deUint32					wrapS,
+											  deUint32					wrapT,
+											  deUint32					format,
+											  deUint32					dataType,
+											  int						size)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_coordType		(coordType)
+	, m_minFilter		(minFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_size			(size)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+TextureCubeMipmapCase::~TextureCubeMipmapCase (void)
+{
+	deinit();
+}
+
+void TextureCubeMipmapCase::init (void)
+{
+	if (m_coordType == COORDTYPE_PROJECTED && m_renderCtx.getRenderTarget().getNumSamples() > 0)
+		throw tcu::NotSupportedError("Projected lookup validation not supported in multisample config");
+
+	m_texture = new glu::TextureCube(m_renderCtx, m_format, m_dataType, m_size);
+
+	int numLevels = deLog2Floor32(m_size)+1;
+
+	// Fill texture with colored grid.
+	for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+	{
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			deUint32	step		= 0xff / (numLevels-1);
+			deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+			deUint32	dec			= 0xff - inc;
+			deUint32	rgb			= 0;
+
+			switch (faceNdx)
+			{
+				case 0: rgb = (inc << 16) | (dec << 8) | 255; break;
+				case 1: rgb = (255 << 16) | (inc << 8) | dec; break;
+				case 2: rgb = (dec << 16) | (255 << 8) | inc; break;
+				case 3: rgb = (dec << 16) | (inc << 8) | 255; break;
+				case 4: rgb = (255 << 16) | (dec << 8) | inc; break;
+				case 5: rgb = (inc << 16) | (255 << 8) | dec; break;
+			}
+
+			deUint32	color		= 0xff000000 | rgb;
+
+			m_texture->getRefTexture().allocLevel((tcu::CubeFace)faceNdx, levelNdx);
+			tcu::clear(m_texture->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)faceNdx), toVec4(tcu::RGBA(color)));
+		}
+	}
+}
+
+void TextureCubeMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+static void randomPartition (vector<IVec4>& dst, de::Random& rnd, int x, int y, int width, int height)
+{
+	const int minWidth	= 8;
+	const int minHeight	= 8;
+
+	bool	partition		= rnd.getFloat() > 0.4f;
+	bool	partitionX		= partition && width > minWidth && rnd.getBool();
+	bool	partitionY		= partition && height > minHeight && !partitionX;
+
+	if (partitionX)
+	{
+		int split = width/2 + rnd.getInt(-width/4, +width/4);
+		randomPartition(dst, rnd, x, y, split, height);
+		randomPartition(dst, rnd, x+split, y, width-split, height);
+	}
+	else if (partitionY)
+	{
+		int split = height/2 + rnd.getInt(-height/4, +height/4);
+		randomPartition(dst, rnd, x, y, width, split);
+		randomPartition(dst, rnd, x, y+split, width, height-split);
+	}
+	else
+		dst.push_back(IVec4(x, y, width, height));
+}
+
+static void computeGridLayout (vector<IVec4>& dst, int width, int height)
+{
+	de::Random rnd(7);
+	randomPartition(dst, rnd, 0, 0, width, height);
+}
+
+TextureCubeMipmapCase::IterateResult TextureCubeMipmapCase::iterate (void)
+{
+	const deUint32			magFilter			= GL_NEAREST;
+	const int				texWidth			= m_texture->getRefTexture().getSize();
+	const int				texHeight			= m_texture->getRefTexture().getSize();
+	const int				defViewportWidth	= texWidth*2;
+	const int				defViewportHeight	= texHeight*2;
+
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	const RandomViewport	viewport			(m_renderCtx.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+
+	const bool				isProjected			= m_coordType == COORDTYPE_PROJECTED;
+	const bool				useLodBias			= m_coordType == COORDTYPE_BASIC_BIAS;
+
+	vector<float>			texCoord;
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+
+	// Bail out if rendertarget is too small.
+	if (viewport.width < defViewportWidth/2 || viewport.height < defViewportHeight/2)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Upload texture data.
+	m_texture->upload();
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture	(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Compute grid.
+	vector<IVec4> gridLayout;
+	computeGridLayout(gridLayout, viewport.width, viewport.height);
+
+	// Bias values.
+	static const float s_bias[] = { 1.0f, -2.0f, 0.8f, -0.5f, 1.5f, 0.9f, 2.0f, 4.0f };
+
+	// Projection values \note Less agressive than in 2D case due to smaller quads.
+	static const Vec4 s_projections[] =
+	{
+		Vec4(1.2f, 1.0f, 0.7f, 1.0f),
+		Vec4(1.3f, 0.8f, 0.6f, 1.1f),
+		Vec4(0.8f, 1.0f, 1.2f, 0.8f),
+		Vec4(1.2f, 1.0f, 1.3f, 0.9f)
+	};
+
+	// Render with GL
+	for (int cellNdx = 0; cellNdx < (int)gridLayout.size(); cellNdx++)
+	{
+		const int			curX		= gridLayout[cellNdx].x();
+		const int			curY		= gridLayout[cellNdx].y();
+		const int			curW		= gridLayout[cellNdx].z();
+		const int			curH		= gridLayout[cellNdx].w();
+		const tcu::CubeFace	cubeFace	= (tcu::CubeFace)(cellNdx % tcu::CUBEFACE_LAST);
+		RenderParams		params		(TEXTURETYPE_CUBE);
+
+		DE_ASSERT(m_coordType != COORDTYPE_AFFINE); // Not supported.
+		computeQuadTexCoordCube(texCoord, cubeFace);
+
+		if (isProjected)
+		{
+			params.flags	|= ReferenceParams::PROJECTED;
+			params.w		 = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+		}
+
+		if (useLodBias)
+		{
+			params.flags	|= ReferenceParams::USE_BIAS;
+			params.bias		 = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+		}
+
+		// Render with GL.
+		gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+		m_renderer.renderQuad(0, &texCoord[0], params);
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+
+	// Read result.
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels");
+
+	// Render reference and compare
+	{
+		tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+		tcu::Surface			errorMask			(viewport.width, viewport.height);
+		int						numFailedPixels		= 0;
+		ReferenceParams			params				(TEXTURETYPE_CUBE);
+		tcu::LookupPrecision	lookupPrec;
+		tcu::LodPrecision		lodPrec;
+
+		// Params for rendering reference
+		params.sampler					= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, magFilter);
+		params.sampler.seamlessCubeMap	= true;
+		params.lodMode					= LODMODE_EXACT;
+
+		// Comparison parameters
+		lookupPrec.colorMask			= getCompareMask(m_renderCtx.getRenderTarget().getPixelFormat());
+		lookupPrec.colorThreshold		= tcu::computeFixedPointThreshold(max(getBitsVec(m_renderCtx.getRenderTarget().getPixelFormat())-2, IVec4(0)));
+		lookupPrec.coordBits			= isProjected ? tcu::IVec3(8) : tcu::IVec3(10);
+		lookupPrec.uvwBits				= tcu::IVec3(5,5,0);
+		lodPrec.derivateBits			= 10;
+		lodPrec.lodBits					= isProjected ? 4 : 6;
+
+		for (int cellNdx = 0; cellNdx < (int)gridLayout.size(); cellNdx++)
+		{
+			const int				curX		= gridLayout[cellNdx].x();
+			const int				curY		= gridLayout[cellNdx].y();
+			const int				curW		= gridLayout[cellNdx].z();
+			const int				curH		= gridLayout[cellNdx].w();
+			const tcu::CubeFace		cubeFace	= (tcu::CubeFace)(cellNdx % tcu::CUBEFACE_LAST);
+
+			DE_ASSERT(m_coordType != COORDTYPE_AFFINE); // Not supported.
+			computeQuadTexCoordCube(texCoord, cubeFace);
+
+			if (isProjected)
+			{
+				params.flags	|= ReferenceParams::PROJECTED;
+				params.w		 = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+			}
+
+			if (useLodBias)
+			{
+				params.flags	|= ReferenceParams::USE_BIAS;
+				params.bias		 = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+			}
+
+			// Render ideal reference.
+			{
+				SurfaceAccess idealDst(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat(), curX, curY, curW, curH);
+				sampleTexture(idealDst, m_texture->getRefTexture(), &texCoord[0], params);
+			}
+
+			// Compare this cell
+			numFailedPixels += computeTextureLookupDiff(tcu::getSubregion(renderedFrame.getAccess(), curX, curY, curW, curH),
+														tcu::getSubregion(referenceFrame.getAccess(), curX, curY, curW, curH),
+														tcu::getSubregion(errorMask.getAccess(), curX, curY, curW, curH),
+														m_texture->getRefTexture(), &texCoord[0], params,
+														lookupPrec, lodPrec, m_testCtx.getWatchDog());
+		}
+
+		if (numFailedPixels > 0)
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Image verification failed, found " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+		m_testCtx.getLog() << TestLog::ImageSet("Result", "Verification result")
+						   << TestLog::Image("Rendered", "Rendered image", renderedFrame);
+
+		if (numFailedPixels > 0)
+		{
+			m_testCtx.getLog() << TestLog::Image("Reference", "Ideal reference", referenceFrame)
+							   << TestLog::Image("ErrorMask", "Error mask", errorMask);
+		}
+
+		m_testCtx.getLog() << TestLog::EndImageSet;
+
+		{
+			const bool isOk = numFailedPixels == 0;
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image verification failed");
+		}
+	}
+
+	return STOP;
+}
+
+// Texture2DGenMipmapCase
+
+class Texture2DGenMipmapCase : public tcu::TestCase
+{
+public:
+
+								Texture2DGenMipmapCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 hint, int width, int height);
+								~Texture2DGenMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Texture2DGenMipmapCase		(const Texture2DGenMipmapCase& other);
+	Texture2DGenMipmapCase&		operator=					(const Texture2DGenMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+
+	deUint32					m_format;
+	deUint32					m_dataType;
+	deUint32					m_hint;
+	int							m_width;
+	int							m_height;
+
+	glu::Texture2D*				m_texture;
+	TextureRenderer				m_renderer;
+};
+
+Texture2DGenMipmapCase::Texture2DGenMipmapCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 hint, int width, int height)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_hint			(hint)
+	, m_width			(width)
+	, m_height			(height)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture2DGenMipmapCase::~Texture2DGenMipmapCase (void)
+{
+	deinit();
+}
+
+void Texture2DGenMipmapCase::init (void)
+{
+	DE_ASSERT(!m_texture);
+	m_texture = new glu::Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+}
+
+void Texture2DGenMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Texture2DGenMipmapCase::IterateResult Texture2DGenMipmapCase::iterate (void)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+
+	const deUint32			minFilter			= GL_NEAREST_MIPMAP_NEAREST;
+	const deUint32			magFilter			= GL_NEAREST;
+	const deUint32			wrapS				= GL_CLAMP_TO_EDGE;
+	const deUint32			wrapT				= GL_CLAMP_TO_EDGE;
+
+	const int				numLevels			= deLog2Floor32(de::max(m_width, m_height))+1;
+	const tcu::Sampler		sampler				= glu::mapGLSampler(wrapS, wrapT, minFilter, magFilter);
+
+	tcu::Texture2D			resultTexture		(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), m_texture->getRefTexture().getWidth(), m_texture->getRefTexture().getHeight());
+
+	vector<float>			texCoord;
+
+	// Initialize texture level 0 with colored grid.
+	m_texture->getRefTexture().allocLevel(0);
+	tcu::fillWithGrid(m_texture->getRefTexture().getLevel(0), 8, tcu::Vec4(1.0f, 0.5f, 0.0f, 0.5f), tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
+
+	// Upload data and setup params.
+	m_texture->upload();
+
+	gl.bindTexture	(GL_TEXTURE_2D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Generate mipmap.
+	gl.hint(GL_GENERATE_MIPMAP_HINT, m_hint);
+	gl.generateMipmap(GL_TEXTURE_2D);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenerateMipmap()");
+
+	// Use (0, 0) -> (1, 1) texture coordinates.
+	computeQuadTexCoord2D(texCoord, Vec2(0.0f, 0.0f), Vec2(1.0f, 1.0f));
+
+	// Fetch resulting texture by rendering.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		const int				levelWidth		= de::max(1, m_width >> levelNdx);
+		const int				levelHeight		= de::max(1, m_height >> levelNdx);
+		const RandomViewport	viewport		(m_renderCtx.getRenderTarget(), levelWidth, levelHeight, deStringHash(getName()) + levelNdx);
+
+		gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+		m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_2D);
+
+		resultTexture.allocLevel(levelNdx);
+		glu::readPixels(m_renderCtx, viewport.x, viewport.y, resultTexture.getLevel(levelNdx));
+	}
+
+	// Compare results
+	{
+		const IVec4			framebufferBits		= max(getBitsVec(m_renderCtx.getRenderTarget().getPixelFormat())-2, IVec4(0));
+		const IVec4			formatBits			= tcu::getTextureFormatBitDepth(glu::mapGLTransferFormat(m_format, m_dataType));
+		const tcu::BVec4	formatMask			= greaterThan(formatBits, IVec4(0));
+		const IVec4			cmpBits				= select(min(framebufferBits, formatBits), framebufferBits, formatMask);
+		GenMipmapPrecision	comparePrec;
+
+		comparePrec.colorMask		= getCompareMask(m_renderCtx.getRenderTarget().getPixelFormat());
+		comparePrec.colorThreshold	= tcu::computeFixedPointThreshold(cmpBits);
+		comparePrec.filterBits		= tcu::IVec3(4, 4, 0);
+
+		const qpTestResult compareResult = compareGenMipmapResult(m_testCtx.getLog(), resultTexture, m_texture->getRefTexture(), comparePrec);
+
+		m_testCtx.setTestResult(compareResult, compareResult == QP_TEST_RESULT_PASS				? "Pass" :
+											   compareResult == QP_TEST_RESULT_QUALITY_WARNING	? "Low-quality method used"	:
+											   compareResult == QP_TEST_RESULT_FAIL				? "Image comparison failed"	: "");
+	}
+
+	return STOP;
+}
+
+// TextureCubeGenMipmapCase
+
+class TextureCubeGenMipmapCase : public tcu::TestCase
+{
+public:
+
+								TextureCubeGenMipmapCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 hint, int size);
+								~TextureCubeGenMipmapCase		(void);
+
+	void						init							(void);
+	void						deinit							(void);
+	IterateResult				iterate							(void);
+
+private:
+								TextureCubeGenMipmapCase		(const TextureCubeGenMipmapCase& other);
+	TextureCubeGenMipmapCase&	operator=						(const TextureCubeGenMipmapCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+
+	deUint32					m_format;
+	deUint32					m_dataType;
+	deUint32					m_hint;
+	int							m_size;
+
+	glu::TextureCube*			m_texture;
+	TextureRenderer				m_renderer;
+};
+
+TextureCubeGenMipmapCase::TextureCubeGenMipmapCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 hint, int size)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_hint			(hint)
+	, m_size			(size)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+TextureCubeGenMipmapCase::~TextureCubeGenMipmapCase (void)
+{
+	deinit();
+}
+
+void TextureCubeGenMipmapCase::init (void)
+{
+	if (m_renderCtx.getRenderTarget().getWidth() < 3*m_size || m_renderCtx.getRenderTarget().getHeight() < 2*m_size)
+		throw tcu::NotSupportedError("Render target size must be at least (" + de::toString(3*m_size) + ", " + de::toString(2*m_size) + ")");
+
+	DE_ASSERT(!m_texture);
+	m_texture = new glu::TextureCube(m_renderCtx, m_format, m_dataType, m_size);
+}
+
+void TextureCubeGenMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+TextureCubeGenMipmapCase::IterateResult TextureCubeGenMipmapCase::iterate (void)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+
+	const deUint32			minFilter			= GL_NEAREST_MIPMAP_NEAREST;
+	const deUint32			magFilter			= GL_NEAREST;
+	const deUint32			wrapS				= GL_CLAMP_TO_EDGE;
+	const deUint32			wrapT				= GL_CLAMP_TO_EDGE;
+
+	tcu::TextureCube		resultTexture		(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), m_size);
+
+	const int				numLevels			= deLog2Floor32(m_size)+1;
+	tcu::Sampler			sampler				= glu::mapGLSampler(wrapS, wrapT, minFilter, magFilter);
+	vector<float>			texCoord;
+
+	sampler.seamlessCubeMap = true;
+
+	// Initialize texture level 0 with colored grid.
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+	{
+		Vec4 ca, cb; // Grid colors.
+
+		switch (face)
+		{
+			case 0: ca = Vec4(1.0f, 0.3f, 0.0f, 0.7f); cb = Vec4(0.0f, 0.0f, 1.0f, 1.0f); break;
+			case 1: ca = Vec4(0.0f, 1.0f, 0.5f, 0.5f); cb = Vec4(1.0f, 0.0f, 0.0f, 1.0f); break;
+			case 2: ca = Vec4(0.7f, 0.0f, 1.0f, 0.3f); cb = Vec4(0.0f, 1.0f, 0.0f, 1.0f); break;
+			case 3: ca = Vec4(0.0f, 0.3f, 1.0f, 1.0f); cb = Vec4(1.0f, 0.0f, 0.0f, 0.7f); break;
+			case 4: ca = Vec4(1.0f, 0.0f, 0.5f, 1.0f); cb = Vec4(0.0f, 1.0f, 0.0f, 0.5f); break;
+			case 5: ca = Vec4(0.7f, 1.0f, 0.0f, 1.0f); cb = Vec4(0.0f, 0.0f, 1.0f, 0.3f); break;
+		}
+
+		m_texture->getRefTexture().allocLevel((tcu::CubeFace)face, 0);
+		fillWithGrid(m_texture->getRefTexture().getLevelFace(0, (tcu::CubeFace)face), 8, ca, cb);
+	}
+
+	// Upload data and setup params.
+	m_texture->upload();
+
+	gl.bindTexture	(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Generate mipmap.
+	gl.hint(GL_GENERATE_MIPMAP_HINT, m_hint);
+	gl.generateMipmap(GL_TEXTURE_CUBE_MAP);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenerateMipmap()");
+
+	// Render all levels.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		const int	levelWidth	= de::max(1, m_size >> levelNdx);
+		const int	levelHeight	= de::max(1, m_size >> levelNdx);
+
+		for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+		{
+			const RandomViewport	viewport	(m_renderCtx.getRenderTarget(), levelWidth*3, levelHeight*2, deStringHash(getName()) ^ deInt32Hash(levelNdx + faceNdx));
+			const tcu::CubeFace		face		= tcu::CubeFace(faceNdx);
+
+			computeQuadTexCoordCube(texCoord, face);
+
+			gl.viewport(viewport.x, viewport.y, levelWidth, levelHeight);
+			m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_CUBE);
+
+			resultTexture.allocLevel(face, levelNdx);
+			glu::readPixels(m_renderCtx, viewport.x, viewport.y, resultTexture.getLevelFace(levelNdx, face));
+		}
+	}
+
+	// Compare results
+	{
+		const IVec4			framebufferBits		= max(getBitsVec(m_renderCtx.getRenderTarget().getPixelFormat())-2, IVec4(0));
+		const IVec4			formatBits			= tcu::getTextureFormatBitDepth(glu::mapGLTransferFormat(m_format, m_dataType));
+		const tcu::BVec4	formatMask			= greaterThan(formatBits, IVec4(0));
+		const IVec4			cmpBits				= select(min(framebufferBits, formatBits), framebufferBits, formatMask);
+		GenMipmapPrecision	comparePrec;
+
+		comparePrec.colorMask		= getCompareMask(m_renderCtx.getRenderTarget().getPixelFormat());
+		comparePrec.colorThreshold	= tcu::computeFixedPointThreshold(cmpBits);
+		comparePrec.filterBits		= tcu::IVec3(4, 4, 0);
+
+		const qpTestResult compareResult = compareGenMipmapResult(m_testCtx.getLog(), resultTexture, m_texture->getRefTexture(), comparePrec);
+
+		m_testCtx.setTestResult(compareResult, compareResult == QP_TEST_RESULT_PASS				? "Pass" :
+											   compareResult == QP_TEST_RESULT_QUALITY_WARNING	? "Low-quality method used"	:
+											   compareResult == QP_TEST_RESULT_FAIL				? "Image comparison failed"	: "");
+	}
+
+	return STOP;
+}
+
+// Texture3DMipmapCase
+
+class Texture3DMipmapCase : public TestCase
+{
+public:
+
+								Texture3DMipmapCase			(Context&					context,
+															 const char*				name,
+															 const char*				desc,
+															 CoordType					coordType,
+															 deUint32					minFilter,
+															 deUint32					wrapS,
+															 deUint32					wrapT,
+															 deUint32					wrapR,
+															 deUint32					format,
+															 int						width,
+															 int						height,
+															 int						depth);
+								~Texture3DMipmapCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Texture3DMipmapCase			(const Texture3DMipmapCase& other);
+	Texture3DMipmapCase&		operator=					(const Texture3DMipmapCase& other);
+
+	CoordType					m_coordType;
+	deUint32					m_minFilter;
+	deUint32					m_wrapS;
+	deUint32					m_wrapT;
+	deUint32					m_wrapR;
+	deUint32					m_internalFormat;
+	int							m_width;
+	int							m_height;
+	int							m_depth;
+
+	glu::Texture3D*						m_texture;
+	TextureTestUtil::TextureRenderer	m_renderer;
+};
+
+Texture3DMipmapCase::Texture3DMipmapCase (Context& context, const char* name, const char* desc, CoordType coordType, deUint32 minFilter, deUint32 wrapS, deUint32 wrapT, deUint32 wrapR, deUint32 format, int width, int height, int depth)
+	: TestCase			(context, name, desc)
+	, m_coordType		(coordType)
+	, m_minFilter		(minFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_wrapR			(wrapR)
+	, m_internalFormat	(format)
+	, m_width			(width)
+	, m_height			(height)
+	, m_depth			(depth)
+	, m_texture			(DE_NULL)
+	, m_renderer		(context.getRenderContext(), m_context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture3DMipmapCase::~Texture3DMipmapCase (void)
+{
+	Texture3DMipmapCase::deinit();
+}
+
+void Texture3DMipmapCase::init (void)
+{
+	const tcu::TextureFormat&		texFmt			= glu::mapGLInternalFormat(m_internalFormat);
+	tcu::TextureFormatInfo			fmtInfo			= tcu::getTextureFormatInfo(texFmt);
+	const tcu::Vec4&				cScale			= fmtInfo.lookupScale;
+	const tcu::Vec4&				cBias			= fmtInfo.lookupBias;
+	int								numLevels		= deLog2Floor32(de::max(de::max(m_width, m_height), m_depth))+1;
+
+	if (m_coordType == COORDTYPE_PROJECTED && m_context.getRenderTarget().getNumSamples() > 0)
+		throw tcu::NotSupportedError("Projected lookup validation not supported in multisample config");
+
+	m_texture = new glu::Texture3D(m_context.getRenderContext(), m_internalFormat, m_width, m_height, m_depth);
+
+	// Fill texture with colored grid.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		deUint32	step		= 0xff / (numLevels-1);
+		deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+		deUint32	dec			= 0xff - inc;
+		deUint32	rgb			= (0xff << 16) | (dec << 8) | inc;
+		deUint32	color		= 0xff000000 | rgb;
+
+		m_texture->getRefTexture().allocLevel(levelNdx);
+		tcu::clear(m_texture->getRefTexture().getLevel(levelNdx), tcu::RGBA(color).toVec()*cScale + cBias);
+	}
+
+	m_texture->upload();
+}
+
+void Texture3DMipmapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+static void getBasicTexCoord3D (std::vector<float>& dst, int cellNdx)
+{
+	static const struct
+	{
+		float sScale;
+		float sBias;
+		float tScale;
+		float tBias;
+		float rScale;
+		float rBias;
+	} s_params[] =
+	{
+	//		sScale	sBias	tScale	tBias	rScale	rBias
+		{	 0.9f,	-0.1f,	 0.7f,	 0.3f,	 0.8f,	 0.9f	},
+		{	 1.2f,	-0.1f,	 1.1f,	 0.3f,	 1.0f,	 0.9f	},
+		{	 1.5f,	 0.7f,	 0.9f,	-0.3f,	 1.1f,	 0.1f	},
+		{	 1.2f,	 0.7f,	-2.3f,	-0.3f,	 1.1f,	 0.2f	},
+		{	 1.1f,	 0.8f,	-1.3f,	-0.3f,	 2.9f,	 0.9f	},
+		{	 3.4f,	 0.8f,	 4.0f,	 0.0f,	-3.3f,	-1.0f	},
+		{	-3.4f,	-0.1f,	-4.0f,	 0.0f,	-5.1f,	 1.0f	},
+		{	-4.0f,	-0.1f,	 3.4f,	 0.1f,	 5.7f,	 0.0f	},
+		{	-5.6f,	 0.0f,	 0.5f,	 1.2f,	 3.9f,	 4.0f	},
+		{	 5.0f,	-2.0f,	 3.1f,	 1.2f,	 5.1f,	 0.2f	},
+		{	 2.5f,	-2.0f,	 6.3f,	 3.0f,	 5.1f,	 0.2f	},
+		{	-8.3f,	 0.0f,	 7.1f,	 3.0f,	 2.0f,	 0.2f	},
+		{    3.8f,	 0.0f,	 9.7f,	 1.0f,	 7.0f,	 0.7f	},
+		{	13.3f,	 0.0f,	 7.1f,	 3.0f,	 2.0f,	 0.2f	},
+		{   16.0f,	 8.0f,	12.7f,	 1.0f,	17.1f,	 0.7f	},
+		{	15.3f,	 0.0f,	20.1f,	 3.0f,	33.0f,	 3.2f	}
+	};
+
+	float sScale	= s_params[cellNdx%DE_LENGTH_OF_ARRAY(s_params)].sScale;
+	float sBias		= s_params[cellNdx%DE_LENGTH_OF_ARRAY(s_params)].sBias;
+	float tScale	= s_params[cellNdx%DE_LENGTH_OF_ARRAY(s_params)].tScale;
+	float tBias		= s_params[cellNdx%DE_LENGTH_OF_ARRAY(s_params)].tBias;
+	float rScale	= s_params[cellNdx%DE_LENGTH_OF_ARRAY(s_params)].rScale;
+	float rBias		= s_params[cellNdx%DE_LENGTH_OF_ARRAY(s_params)].rBias;
+
+	dst.resize(3*4);
+
+	dst[0] = sBias;			dst[ 1] = tBias;			dst[ 2] = rBias;
+	dst[3] = sBias;			dst[ 4] = tBias+tScale;		dst[ 5] = rBias+rScale*0.5f;
+	dst[6] = sBias+sScale;	dst[ 7] = tBias;			dst[ 8] = rBias+rScale*0.5f;
+	dst[9] = sBias+sScale;	dst[10] = tBias+tScale;		dst[11] = rBias+rScale;
+}
+
+static void getAffineTexCoord3D (std::vector<float>& dst, int cellNdx)
+{
+	// Use basic coords as base.
+	getBasicTexCoord3D(dst, cellNdx);
+
+	// Rotate based on cell index.
+	float		angleX		= 0.0f + 2.0f*DE_PI * ((float)cellNdx / 16.0f);
+	float		angleY		= 1.0f + 2.0f*DE_PI * ((float)cellNdx / 32.0f);
+	tcu::Mat3	rotMatrix	= tcu::rotationMatrixX(angleX) * tcu::rotationMatrixY(angleY);
+
+	Vec3		p0			= rotMatrix * Vec3(dst[0], dst[ 1], dst[ 2]);
+	Vec3		p1			= rotMatrix * Vec3(dst[3], dst[ 4], dst[ 5]);
+	Vec3		p2			= rotMatrix * Vec3(dst[6], dst[ 7], dst[ 8]);
+	Vec3		p3			= rotMatrix * Vec3(dst[9], dst[10], dst[11]);
+
+	dst[0] = p0.x();	dst[ 1] = p0.y();	dst[ 2] = p0.z();
+	dst[3] = p1.x();	dst[ 4] = p1.y();	dst[ 5] = p1.z();
+	dst[6] = p2.x();	dst[ 7] = p2.y();	dst[ 8] = p2.z();
+	dst[9] = p3.x();	dst[10] = p3.y();	dst[11] = p3.z();
+}
+
+Texture3DMipmapCase::IterateResult Texture3DMipmapCase::iterate (void)
+{
+	const glw::Functions&			gl					= m_context.getRenderContext().getFunctions();
+
+	const tcu::Texture3D&			refTexture			= m_texture->getRefTexture();
+	const tcu::TextureFormat&		texFmt				= refTexture.getFormat();
+	const tcu::TextureFormatInfo	fmtInfo				= tcu::getTextureFormatInfo(texFmt);
+	const int						texWidth			= refTexture.getWidth();
+	const int						texHeight			= refTexture.getHeight();
+	const deUint32					magFilter			= GL_NEAREST;
+
+	const tcu::RenderTarget&		renderTarget		= m_context.getRenderContext().getRenderTarget();
+	const RandomViewport			viewport			(renderTarget, texWidth*4, texHeight*4, deStringHash(getName()));
+
+	const bool						isProjected			= m_coordType == COORDTYPE_PROJECTED;
+	const bool						useLodBias			= m_coordType == COORDTYPE_BASIC_BIAS;
+
+	// Viewport is divided into 4x4 grid.
+	const int						gridWidth			= 4;
+	const int						gridHeight			= 4;
+	const int						cellWidth			= viewport.width / gridWidth;
+	const int						cellHeight			= viewport.height / gridHeight;
+
+	ReferenceParams					sampleParams		(TEXTURETYPE_3D);
+
+	tcu::Surface					renderedFrame		(viewport.width, viewport.height);
+	vector<float>					texCoord;
+
+	// Sampling parameters.
+	sampleParams.sampler		= glu::mapGLSampler(m_wrapS, m_wrapT, m_wrapR, m_minFilter, magFilter);
+	sampleParams.samplerType	= gls::TextureTestUtil::getSamplerType(texFmt);
+	sampleParams.colorBias		= fmtInfo.lookupBias;
+	sampleParams.colorScale		= fmtInfo.lookupScale;
+	sampleParams.flags			= (isProjected ? ReferenceParams::PROJECTED : 0) | (useLodBias ? ReferenceParams::USE_BIAS : 0);
+
+	// Bind texture and setup sampler parameters.
+	gl.bindTexture	(GL_TEXTURE_3D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R,		m_wrapR);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Bias values.
+	static const float s_bias[] = { 1.0f, -2.0f, 0.8f, -0.5f, 1.5f, 0.9f, 2.0f, 4.0f };
+
+	// Projection values.
+	static const Vec4 s_projections[] =
+	{
+		Vec4(1.2f, 1.0f, 0.7f, 1.0f),
+		Vec4(1.3f, 0.8f, 0.6f, 2.0f),
+		Vec4(0.8f, 1.0f, 1.7f, 0.6f),
+		Vec4(1.2f, 1.0f, 1.7f, 1.5f)
+	};
+
+	// Render cells.
+	for (int gridY = 0; gridY < gridHeight; gridY++)
+	{
+		for (int gridX = 0; gridX < gridWidth; gridX++)
+		{
+			const int		curX		= cellWidth*gridX;
+			const int		curY		= cellHeight*gridY;
+			const int		curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+			const int		curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+			const int		cellNdx		= gridY*gridWidth + gridX;
+
+			// Compute texcoord.
+			switch (m_coordType)
+			{
+				case COORDTYPE_BASIC_BIAS:	// Fall-through.
+				case COORDTYPE_PROJECTED:
+				case COORDTYPE_BASIC:		getBasicTexCoord3D	(texCoord, cellNdx);	break;
+				case COORDTYPE_AFFINE:		getAffineTexCoord3D	(texCoord, cellNdx);	break;
+				default:					DE_ASSERT(DE_FALSE);
+			}
+
+			// Set projection.
+			if (isProjected)
+				sampleParams.w = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+
+			// Set LOD bias.
+			if (useLodBias)
+				sampleParams.bias = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+
+			// Render with GL.
+			gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+			m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+		}
+	}
+
+	// Read result.
+	glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log
+	{
+		const tcu::PixelFormat&	pixelFormat		= m_context.getRenderTarget().getPixelFormat();
+		const bool				isTrilinear		= m_minFilter == GL_NEAREST_MIPMAP_LINEAR || m_minFilter == GL_LINEAR_MIPMAP_LINEAR;
+		tcu::Surface			referenceFrame	(viewport.width, viewport.height);
+		tcu::Surface			errorMask		(viewport.width, viewport.height);
+		tcu::LookupPrecision	lookupPrec;
+		tcu::LodPrecision		lodPrec;
+		int						numFailedPixels	= 0;
+
+		lookupPrec.coordBits		= tcu::IVec3(20, 20, 20);
+		lookupPrec.uvwBits			= tcu::IVec3(16, 16, 16); // Doesn't really matter since pixels are unicolored.
+		lookupPrec.colorThreshold	= tcu::computeFixedPointThreshold(max(getBitsVec(pixelFormat) - (isTrilinear ? 2 : 1), tcu::IVec4(0)));
+		lookupPrec.colorMask		= getCompareMask(pixelFormat);
+		lodPrec.derivateBits		= 10;
+		lodPrec.lodBits				= isProjected ? 6 : 8;
+
+		for (int gridY = 0; gridY < gridHeight; gridY++)
+		{
+			for (int gridX = 0; gridX < gridWidth; gridX++)
+			{
+				const int		curX		= cellWidth*gridX;
+				const int		curY		= cellHeight*gridY;
+				const int		curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+				const int		curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+				const int		cellNdx		= gridY*gridWidth + gridX;
+
+				switch (m_coordType)
+				{
+					case COORDTYPE_BASIC_BIAS:	// Fall-through.
+					case COORDTYPE_PROJECTED:
+					case COORDTYPE_BASIC:		getBasicTexCoord3D	(texCoord, cellNdx);	break;
+					case COORDTYPE_AFFINE:		getAffineTexCoord3D	(texCoord, cellNdx);	break;
+					default:					DE_ASSERT(DE_FALSE);
+				}
+
+				if (isProjected)
+					sampleParams.w = s_projections[cellNdx % DE_LENGTH_OF_ARRAY(s_projections)];
+
+				if (useLodBias)
+					sampleParams.bias = s_bias[cellNdx % DE_LENGTH_OF_ARRAY(s_bias)];
+
+				// Render ideal result
+				sampleTexture(SurfaceAccess(referenceFrame, pixelFormat, curX, curY, curW, curH),
+							  refTexture, &texCoord[0], sampleParams);
+
+				// Compare this cell
+				numFailedPixels += computeTextureLookupDiff(tcu::getSubregion(renderedFrame.getAccess(), curX, curY, curW, curH),
+															tcu::getSubregion(referenceFrame.getAccess(), curX, curY, curW, curH),
+															tcu::getSubregion(errorMask.getAccess(), curX, curY, curW, curH),
+															m_texture->getRefTexture(), &texCoord[0], sampleParams,
+															lookupPrec, lodPrec, m_testCtx.getWatchDog());
+			}
+		}
+
+		if (numFailedPixels > 0)
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Image verification failed, found " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+		m_testCtx.getLog() << TestLog::ImageSet("Result", "Verification result")
+							<< TestLog::Image("Rendered", "Rendered image", renderedFrame);
+
+		if (numFailedPixels > 0)
+		{
+			m_testCtx.getLog() << TestLog::Image("Reference", "Ideal reference", referenceFrame)
+								<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+		}
+
+		m_testCtx.getLog() << TestLog::EndImageSet;
+
+		{
+			const bool isOk = numFailedPixels == 0;
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image verification failed");
+		}
+	}
+
+	return STOP;
+}
+
+// Texture2DLodControlCase + test cases
+
+class Texture2DLodControlCase : public TestCase
+{
+public:
+
+										Texture2DLodControlCase		(Context& context, const char* name, const char* desc, deUint32 minFilter);
+										~Texture2DLodControlCase	(void);
+
+	void								init						(void);
+	void								deinit						(void);
+	IterateResult						iterate						(void);
+
+protected:
+	virtual void						setTextureParams			(int cellNdx)							= DE_NULL;
+	virtual void						getReferenceParams			(ReferenceParams& params, int cellNdx)	= DE_NULL;
+
+	const int							m_texWidth;
+	const int							m_texHeight;
+
+private:
+										Texture2DLodControlCase		(const Texture2DLodControlCase& other);
+	Texture2DLodControlCase&			operator=					(const Texture2DLodControlCase& other);
+
+	deUint32							m_minFilter;
+
+	glu::Texture2D*						m_texture;
+	TextureTestUtil::TextureRenderer	m_renderer;
+};
+
+Texture2DLodControlCase::Texture2DLodControlCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+	: TestCase		(context, name, desc)
+	, m_texWidth	(64)
+	, m_texHeight	(64)
+	, m_minFilter	(minFilter)
+	, m_texture		(DE_NULL)
+	, m_renderer	(context.getRenderContext(), m_context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture2DLodControlCase::~Texture2DLodControlCase (void)
+{
+	Texture2DLodControlCase::deinit();
+}
+
+void Texture2DLodControlCase::init (void)
+{
+	const deUint32	format		= GL_RGBA8;
+	int				numLevels	= deLog2Floor32(de::max(m_texWidth, m_texHeight))+1;
+
+	m_texture = new glu::Texture2D(m_context.getRenderContext(), format, m_texWidth, m_texHeight);
+
+	// Fill texture with colored grid.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		deUint32	step		= 0xff / (numLevels-1);
+		deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+		deUint32	dec			= 0xff - inc;
+		deUint32	rgb			= (inc << 16) | (dec << 8) | 0xff;
+		deUint32	color		= 0xff000000 | rgb;
+
+		m_texture->getRefTexture().allocLevel(levelNdx);
+		tcu::clear(m_texture->getRefTexture().getLevel(levelNdx), tcu::RGBA(color).toVec());
+	}
+}
+
+void Texture2DLodControlCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Texture2DLodControlCase::IterateResult Texture2DLodControlCase::iterate (void)
+{
+	const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
+
+	const deUint32				wrapS				= GL_REPEAT;
+	const deUint32				wrapT				= GL_REPEAT;
+	const deUint32				magFilter			= GL_NEAREST;
+
+	const tcu::Texture2D&		refTexture			= m_texture->getRefTexture();
+	const int					texWidth			= refTexture.getWidth();
+	const int					texHeight			= refTexture.getHeight();
+
+	const tcu::RenderTarget&	renderTarget		= m_context.getRenderContext().getRenderTarget();
+	const RandomViewport		viewport			(renderTarget, texWidth*4, texHeight*4, deStringHash(getName()));
+
+	ReferenceParams				sampleParams		(gls::TextureTestUtil::TEXTURETYPE_2D, glu::mapGLSampler(wrapS, wrapT, m_minFilter, magFilter));
+	vector<float>				texCoord;
+	tcu::Surface				renderedFrame		(viewport.width, viewport.height);
+
+	// Viewport is divided into 4x4 grid.
+	const int					gridWidth			= 4;
+	const int					gridHeight			= 4;
+	const int					cellWidth			= viewport.width / gridWidth;
+	const int					cellHeight			= viewport.height / gridHeight;
+
+	// Upload texture data.
+	m_texture->upload();
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture	(GL_TEXTURE_2D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Render cells.
+	for (int gridY = 0; gridY < gridHeight; gridY++)
+	{
+		for (int gridX = 0; gridX < gridWidth; gridX++)
+		{
+			int				curX		= cellWidth*gridX;
+			int				curY		= cellHeight*gridY;
+			int				curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+			int				curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+			int				cellNdx		= gridY*gridWidth + gridX;
+
+			// Compute texcoord.
+			getBasicTexCoord2D(texCoord, cellNdx);
+
+			// Render with GL.
+			setTextureParams(cellNdx);
+			gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+			m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+		}
+	}
+
+	glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels");
+
+	// Compare and log.
+	{
+		const tcu::PixelFormat&	pixelFormat		= m_context.getRenderTarget().getPixelFormat();
+		const bool				isTrilinear		= m_minFilter == GL_NEAREST_MIPMAP_LINEAR || m_minFilter == GL_LINEAR_MIPMAP_LINEAR;
+		tcu::Surface			referenceFrame	(viewport.width, viewport.height);
+		tcu::Surface			errorMask		(viewport.width, viewport.height);
+		tcu::LookupPrecision	lookupPrec;
+		tcu::LodPrecision		lodPrec;
+		int						numFailedPixels	= 0;
+
+		lookupPrec.coordBits		= tcu::IVec3(20, 20, 0);
+		lookupPrec.uvwBits			= tcu::IVec3(16, 16, 0); // Doesn't really matter since pixels are unicolored.
+		lookupPrec.colorThreshold	= tcu::computeFixedPointThreshold(max(getBitsVec(pixelFormat) - (isTrilinear ? 2 : 1), tcu::IVec4(0)));
+		lookupPrec.colorMask		= getCompareMask(pixelFormat);
+		lodPrec.derivateBits		= 10;
+		lodPrec.lodBits				= 8;
+
+		for (int gridY = 0; gridY < gridHeight; gridY++)
+		{
+			for (int gridX = 0; gridX < gridWidth; gridX++)
+			{
+				const int		curX		= cellWidth*gridX;
+				const int		curY		= cellHeight*gridY;
+				const int		curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+				const int		curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+				const int		cellNdx		= gridY*gridWidth + gridX;
+
+				getBasicTexCoord2D(texCoord, cellNdx);
+				getReferenceParams(sampleParams, cellNdx);
+
+				// Render ideal result
+				sampleTexture(SurfaceAccess(referenceFrame, pixelFormat, curX, curY, curW, curH),
+							  refTexture, &texCoord[0], sampleParams);
+
+				// Compare this cell
+				numFailedPixels += computeTextureLookupDiff(tcu::getSubregion(renderedFrame.getAccess(), curX, curY, curW, curH),
+															tcu::getSubregion(referenceFrame.getAccess(), curX, curY, curW, curH),
+															tcu::getSubregion(errorMask.getAccess(), curX, curY, curW, curH),
+															m_texture->getRefTexture(), &texCoord[0], sampleParams,
+															lookupPrec, lodPrec, m_testCtx.getWatchDog());
+			}
+		}
+
+		if (numFailedPixels > 0)
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Image verification failed, found " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+		m_testCtx.getLog() << TestLog::ImageSet("Result", "Verification result")
+							<< TestLog::Image("Rendered", "Rendered image", renderedFrame);
+
+		if (numFailedPixels > 0)
+		{
+			m_testCtx.getLog() << TestLog::Image("Reference", "Ideal reference", referenceFrame)
+								<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+		}
+
+		m_testCtx.getLog() << TestLog::EndImageSet;
+
+		{
+			const bool isOk = numFailedPixels == 0;
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image verification failed");
+		}
+	}
+
+	return STOP;
+}
+
+class Texture2DMinLodCase : public Texture2DLodControlCase
+{
+public:
+	Texture2DMinLodCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: Texture2DLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+		gl.texParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, getMinLodForCell(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.minLod = getMinLodForCell(cellNdx);
+	}
+};
+
+class Texture2DMaxLodCase : public Texture2DLodControlCase
+{
+public:
+	Texture2DMaxLodCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: Texture2DLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+		gl.texParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, getMaxLodForCell(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.maxLod = getMaxLodForCell(cellNdx);
+	}
+};
+
+class Texture2DBaseLevelCase : public Texture2DLodControlCase
+{
+public:
+	Texture2DBaseLevelCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: Texture2DLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	int getBaseLevel (int cellNdx) const
+	{
+		const int	numLevels	= deLog2Floor32(de::max(m_texWidth, m_texHeight))+1;
+		const int	baseLevel	= (deInt32Hash(cellNdx) ^ deStringHash(getName()) ^ 0xac2f274a) % numLevels;
+
+		return baseLevel;
+	}
+
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, getBaseLevel(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.baseLevel = getBaseLevel(cellNdx);
+	}
+};
+
+class Texture2DMaxLevelCase : public Texture2DLodControlCase
+{
+public:
+	Texture2DMaxLevelCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: Texture2DLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	int getMaxLevel (int cellNdx) const
+	{
+		const int		numLevels	= deLog2Floor32(de::max(m_texWidth, m_texHeight))+1;
+		const int		maxLevel	= (deInt32Hash(cellNdx) ^ deStringHash(getName()) ^ 0x82cfa4e) % numLevels;
+
+		return maxLevel;
+	}
+
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, getMaxLevel(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.maxLevel = getMaxLevel(cellNdx);
+	}
+};
+
+// TextureCubeLodControlCase + test cases
+
+class TextureCubeLodControlCase : public TestCase
+{
+public:
+
+										TextureCubeLodControlCase	(Context& context, const char* name, const char* desc, deUint32 minFilter);
+										~TextureCubeLodControlCase	(void);
+
+	void								init						(void);
+	void								deinit						(void);
+	IterateResult						iterate						(void);
+
+protected:
+	virtual void						setTextureParams			(int cellNdx)							= DE_NULL;
+	virtual void						getReferenceParams			(ReferenceParams& params, int cellNdx)	= DE_NULL;
+
+	const int							m_texSize;
+
+private:
+										TextureCubeLodControlCase	(const TextureCubeLodControlCase& other);
+	TextureCubeLodControlCase&			operator=					(const TextureCubeLodControlCase& other);
+
+	deUint32							m_minFilter;
+
+	glu::TextureCube*					m_texture;
+	TextureTestUtil::TextureRenderer	m_renderer;
+};
+
+TextureCubeLodControlCase::TextureCubeLodControlCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+	: TestCase			(context, name, desc)
+	, m_texSize			(64)
+	, m_minFilter		(minFilter)
+	, m_texture			(DE_NULL)
+	, m_renderer		(context.getRenderContext(), context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+TextureCubeLodControlCase::~TextureCubeLodControlCase (void)
+{
+	deinit();
+}
+
+void TextureCubeLodControlCase::init (void)
+{
+	const deUint32	format		= GL_RGBA8;
+	const int		numLevels	= deLog2Floor32(m_texSize)+1;
+
+	m_texture = new glu::TextureCube(m_context.getRenderContext(), format, m_texSize);
+
+	// Fill texture with colored grid.
+	for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+	{
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			deUint32	step		= 0xff / (numLevels-1);
+			deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+			deUint32	dec			= 0xff - inc;
+			deUint32	rgb			= 0;
+
+			switch (faceNdx)
+			{
+				case 0: rgb = (inc << 16) | (dec << 8) | 255; break;
+				case 1: rgb = (255 << 16) | (inc << 8) | dec; break;
+				case 2: rgb = (dec << 16) | (255 << 8) | inc; break;
+				case 3: rgb = (dec << 16) | (inc << 8) | 255; break;
+				case 4: rgb = (255 << 16) | (dec << 8) | inc; break;
+				case 5: rgb = (inc << 16) | (255 << 8) | dec; break;
+			}
+
+			deUint32	color		= 0xff000000 | rgb;
+
+			m_texture->getRefTexture().allocLevel((tcu::CubeFace)faceNdx, levelNdx);
+			tcu::clear(m_texture->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)faceNdx), tcu::RGBA(color).toVec());
+		}
+	}
+}
+
+void TextureCubeLodControlCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+TextureCubeLodControlCase::IterateResult TextureCubeLodControlCase::iterate (void)
+{
+	const deUint32			wrapS				= GL_CLAMP_TO_EDGE;
+	const deUint32			wrapT				= GL_CLAMP_TO_EDGE;
+	const deUint32			magFilter			= GL_NEAREST;
+
+	const int				texWidth			= m_texture->getRefTexture().getSize();
+	const int				texHeight			= m_texture->getRefTexture().getSize();
+
+	const int				defViewportWidth	= texWidth*2;
+	const int				defViewportHeight	= texHeight*2;
+
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const RandomViewport	viewport			(m_context.getRenderTarget(), defViewportWidth, defViewportHeight, deStringHash(getName()));
+
+	vector<float>			texCoord;
+
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+
+	// Upload texture data.
+	m_texture->upload();
+
+	// Bind gradient texture and setup sampler parameters.
+	gl.bindTexture	(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Compute grid.
+	vector<tcu::IVec4> gridLayout;
+	computeGridLayout(gridLayout, viewport.width, viewport.height);
+
+	for (int cellNdx = 0; cellNdx < (int)gridLayout.size(); cellNdx++)
+	{
+		const int			curX		= gridLayout[cellNdx].x();
+		const int			curY		= gridLayout[cellNdx].y();
+		const int			curW		= gridLayout[cellNdx].z();
+		const int			curH		= gridLayout[cellNdx].w();
+		const tcu::CubeFace	cubeFace	= (tcu::CubeFace)(cellNdx % tcu::CUBEFACE_LAST);
+		RenderParams		params		(TEXTURETYPE_CUBE);
+
+		TextureTestUtil::computeQuadTexCoordCube(texCoord, cubeFace);
+
+		setTextureParams(cellNdx);
+
+		// Render with GL.
+		gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+		m_renderer.renderQuad(0, &texCoord[0], params);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+	}
+
+	// Read result.
+	glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, renderedFrame.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels");
+
+	// Render reference and compare
+	{
+		tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+		tcu::Surface			errorMask			(viewport.width, viewport.height);
+		int						numFailedPixels		= 0;
+		ReferenceParams			params				(TEXTURETYPE_CUBE);
+		tcu::LookupPrecision	lookupPrec;
+		tcu::LodPrecision		lodPrec;
+
+		// Params for rendering reference
+		params.sampler					= glu::mapGLSampler(wrapS, wrapT, m_minFilter, magFilter);
+		params.sampler.seamlessCubeMap	= true;
+		params.lodMode					= LODMODE_EXACT;
+
+		// Comparison parameters
+		lookupPrec.colorMask			= getCompareMask(m_context.getRenderTarget().getPixelFormat());
+		lookupPrec.colorThreshold		= tcu::computeFixedPointThreshold(max(getBitsVec(m_context.getRenderTarget().getPixelFormat())-2, IVec4(0)));
+		lookupPrec.coordBits			= tcu::IVec3(10);
+		lookupPrec.uvwBits				= tcu::IVec3(5,5,0);
+		lodPrec.derivateBits			= 10;
+		lodPrec.lodBits					= 6;
+
+		for (int cellNdx = 0; cellNdx < (int)gridLayout.size(); cellNdx++)
+		{
+			const int				curX		= gridLayout[cellNdx].x();
+			const int				curY		= gridLayout[cellNdx].y();
+			const int				curW		= gridLayout[cellNdx].z();
+			const int				curH		= gridLayout[cellNdx].w();
+			const tcu::CubeFace		cubeFace	= (tcu::CubeFace)(cellNdx % tcu::CUBEFACE_LAST);
+
+			computeQuadTexCoordCube(texCoord, cubeFace);
+			getReferenceParams(params, cellNdx);
+
+			// Render ideal reference.
+			{
+				SurfaceAccess idealDst(referenceFrame, m_context.getRenderTarget().getPixelFormat(), curX, curY, curW, curH);
+				sampleTexture(idealDst, m_texture->getRefTexture(), &texCoord[0], params);
+			}
+
+			// Compare this cell
+			numFailedPixels += computeTextureLookupDiff(tcu::getSubregion(renderedFrame.getAccess(), curX, curY, curW, curH),
+														tcu::getSubregion(referenceFrame.getAccess(), curX, curY, curW, curH),
+														tcu::getSubregion(errorMask.getAccess(), curX, curY, curW, curH),
+														m_texture->getRefTexture(), &texCoord[0], params,
+														lookupPrec, lodPrec, m_testCtx.getWatchDog());
+		}
+
+		if (numFailedPixels > 0)
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Image verification failed, found " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+		m_testCtx.getLog() << TestLog::ImageSet("Result", "Verification result")
+						   << TestLog::Image("Rendered", "Rendered image", renderedFrame);
+
+		if (numFailedPixels > 0)
+		{
+			m_testCtx.getLog() << TestLog::Image("Reference", "Ideal reference", referenceFrame)
+							   << TestLog::Image("ErrorMask", "Error mask", errorMask);
+		}
+
+		m_testCtx.getLog() << TestLog::EndImageSet;
+
+		{
+			const bool isOk = numFailedPixels == 0;
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image verification failed");
+		}
+	}
+
+	return STOP;
+}
+
+class TextureCubeMinLodCase : public TextureCubeLodControlCase
+{
+public:
+	TextureCubeMinLodCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: TextureCubeLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+		gl.texParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_LOD, getMinLodForCell(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.minLod = getMinLodForCell(cellNdx);
+	}
+};
+
+class TextureCubeMaxLodCase : public TextureCubeLodControlCase
+{
+public:
+	TextureCubeMaxLodCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: TextureCubeLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+		gl.texParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LOD, getMaxLodForCell(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.maxLod = getMaxLodForCell(cellNdx);
+	}
+};
+
+class TextureCubeBaseLevelCase : public TextureCubeLodControlCase
+{
+public:
+	TextureCubeBaseLevelCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: TextureCubeLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	int getBaseLevel (int cellNdx) const
+	{
+		const int	numLevels	= deLog2Floor32(m_texSize)+1;
+		const int	baseLevel	= (deInt32Hash(cellNdx) ^ deStringHash(getName()) ^ 0x23fae13) % numLevels;
+
+		return baseLevel;
+	}
+
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+		gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, getBaseLevel(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.baseLevel = getBaseLevel(cellNdx);
+	}
+};
+
+class TextureCubeMaxLevelCase : public TextureCubeLodControlCase
+{
+public:
+	TextureCubeMaxLevelCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: TextureCubeLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	int getMaxLevel (int cellNdx) const
+	{
+		const int	numLevels	= deLog2Floor32(m_texSize)+1;
+		const int	maxLevel	= (deInt32Hash(cellNdx) ^ deStringHash(getName()) ^ 0x974e21) % numLevels;
+
+		return maxLevel;
+	}
+
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+		gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, getMaxLevel(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.maxLevel = getMaxLevel(cellNdx);
+	}
+};
+
+// Texture3DLodControlCase + test cases
+
+class Texture3DLodControlCase : public TestCase
+{
+public:
+
+										Texture3DLodControlCase		(Context& context, const char* name, const char* desc, deUint32 minFilter);
+										~Texture3DLodControlCase	(void);
+
+	void								init						(void);
+	void								deinit						(void);
+	IterateResult						iterate						(void);
+
+protected:
+	virtual void						setTextureParams			(int cellNdx)						= DE_NULL;
+	virtual void						getReferenceParams			(ReferenceParams& params, int cellNdx)	= DE_NULL;
+
+	const int							m_texWidth;
+	const int							m_texHeight;
+	const int							m_texDepth;
+
+private:
+										Texture3DLodControlCase		(const Texture3DLodControlCase& other);
+	Texture3DLodControlCase&			operator=					(const Texture3DLodControlCase& other);
+
+	deUint32							m_minFilter;
+
+	glu::Texture3D*						m_texture;
+	TextureTestUtil::TextureRenderer	m_renderer;
+};
+
+Texture3DLodControlCase::Texture3DLodControlCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+	: TestCase			(context, name, desc)
+	, m_texWidth		(32)
+	, m_texHeight		(32)
+	, m_texDepth		(32)
+	, m_minFilter		(minFilter)
+	, m_texture			(DE_NULL)
+	, m_renderer		(context.getRenderContext(), m_context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture3DLodControlCase::~Texture3DLodControlCase (void)
+{
+	Texture3DLodControlCase::deinit();
+}
+
+void Texture3DLodControlCase::init (void)
+{
+	const deUint32					format			= GL_RGBA8;
+	const tcu::TextureFormat&		texFmt			= glu::mapGLInternalFormat(format);
+	tcu::TextureFormatInfo			fmtInfo			= tcu::getTextureFormatInfo(texFmt);
+	const tcu::Vec4&				cScale			= fmtInfo.lookupScale;
+	const tcu::Vec4&				cBias			= fmtInfo.lookupBias;
+	int								numLevels		= deLog2Floor32(de::max(de::max(m_texWidth, m_texHeight), m_texDepth))+1;
+
+	m_texture = new glu::Texture3D(m_context.getRenderContext(), format, m_texWidth, m_texHeight, m_texDepth);
+
+	// Fill texture with colored grid.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		deUint32	step		= 0xff / (numLevels-1);
+		deUint32	inc			= deClamp32(step*levelNdx, 0x00, 0xff);
+		deUint32	dec			= 0xff - inc;
+		deUint32	rgb			= (inc << 16) | (dec << 8) | 0xff;
+		deUint32	color		= 0xff000000 | rgb;
+
+		m_texture->getRefTexture().allocLevel(levelNdx);
+		tcu::clear(m_texture->getRefTexture().getLevel(levelNdx), tcu::RGBA(color).toVec()*cScale + cBias);
+	}
+
+	m_texture->upload();
+}
+
+void Texture3DLodControlCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Texture3DLodControlCase::IterateResult Texture3DLodControlCase::iterate (void)
+{
+	const glw::Functions&			gl					= m_context.getRenderContext().getFunctions();
+
+	const deUint32					wrapS				= GL_CLAMP_TO_EDGE;
+	const deUint32					wrapT				= GL_CLAMP_TO_EDGE;
+	const deUint32					wrapR				= GL_CLAMP_TO_EDGE;
+	const deUint32					magFilter			= GL_NEAREST;
+	const tcu::Texture3D&			refTexture			= m_texture->getRefTexture();
+	const tcu::TextureFormat&		texFmt				= refTexture.getFormat();
+	const tcu::TextureFormatInfo	fmtInfo				= tcu::getTextureFormatInfo(texFmt);
+	const int						texWidth			= refTexture.getWidth();
+	const int						texHeight			= refTexture.getHeight();
+
+	const tcu::RenderTarget&		renderTarget		= m_context.getRenderContext().getRenderTarget();
+	const RandomViewport			viewport			(renderTarget, texWidth*4, texHeight*4, deStringHash(getName()));
+
+	// Viewport is divided into 4x4 grid.
+	const int						gridWidth			= 4;
+	const int						gridHeight			= 4;
+	const int						cellWidth			= viewport.width / gridWidth;
+	const int						cellHeight			= viewport.height / gridHeight;
+
+	tcu::Surface					renderedFrame		(viewport.width, viewport.height);
+	vector<float>					texCoord;
+	ReferenceParams					sampleParams		(gls::TextureTestUtil::TEXTURETYPE_3D);
+
+	// Sampling parameters.
+	sampleParams.sampler		= glu::mapGLSampler(wrapS, wrapT, wrapR, m_minFilter, magFilter);
+	sampleParams.samplerType	= gls::TextureTestUtil::getSamplerType(texFmt);
+	sampleParams.colorBias		= fmtInfo.lookupBias;
+	sampleParams.colorScale		= fmtInfo.lookupScale;
+
+	// Bind texture and setup sampler parameters.
+	gl.bindTexture	(GL_TEXTURE_3D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S,		wrapS);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T,		wrapT);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R,		wrapR);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER,	magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After texture setup");
+
+	// Render cells.
+	for (int gridY = 0; gridY < gridHeight; gridY++)
+	{
+		for (int gridX = 0; gridX < gridWidth; gridX++)
+		{
+			int		curX		= cellWidth*gridX;
+			int		curY		= cellHeight*gridY;
+			int		curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+			int		curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+			int		cellNdx		= gridY*gridWidth + gridX;
+
+			// Compute texcoord.
+			getBasicTexCoord3D(texCoord, cellNdx);
+
+			setTextureParams(cellNdx);
+
+			// Render with GL.
+			gl.viewport(viewport.x+curX, viewport.y+curY, curW, curH);
+			m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+		}
+	}
+
+	// Read result.
+	glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compare and log
+	{
+		const tcu::PixelFormat&	pixelFormat		= m_context.getRenderTarget().getPixelFormat();
+		const bool				isTrilinear		= m_minFilter == GL_NEAREST_MIPMAP_LINEAR || m_minFilter == GL_LINEAR_MIPMAP_LINEAR;
+		tcu::Surface			referenceFrame	(viewport.width, viewport.height);
+		tcu::Surface			errorMask		(viewport.width, viewport.height);
+		tcu::LookupPrecision	lookupPrec;
+		tcu::LodPrecision		lodPrec;
+		int						numFailedPixels	= 0;
+
+		lookupPrec.coordBits		= tcu::IVec3(20, 20, 20);
+		lookupPrec.uvwBits			= tcu::IVec3(16, 16, 16); // Doesn't really matter since pixels are unicolored.
+		lookupPrec.colorThreshold	= tcu::computeFixedPointThreshold(max(getBitsVec(pixelFormat) - (isTrilinear ? 2 : 1), tcu::IVec4(0)));
+		lookupPrec.colorMask		= getCompareMask(pixelFormat);
+		lodPrec.derivateBits		= 10;
+		lodPrec.lodBits				= 8;
+
+		for (int gridY = 0; gridY < gridHeight; gridY++)
+		{
+			for (int gridX = 0; gridX < gridWidth; gridX++)
+			{
+				const int		curX		= cellWidth*gridX;
+				const int		curY		= cellHeight*gridY;
+				const int		curW		= gridX+1 == gridWidth ? (viewport.width-curX) : cellWidth;
+				const int		curH		= gridY+1 == gridHeight ? (viewport.height-curY) : cellHeight;
+				const int		cellNdx		= gridY*gridWidth + gridX;
+
+				getBasicTexCoord3D(texCoord, cellNdx);
+				getReferenceParams(sampleParams, cellNdx);
+
+				// Render ideal result
+				sampleTexture(SurfaceAccess(referenceFrame, pixelFormat, curX, curY, curW, curH),
+							  refTexture, &texCoord[0], sampleParams);
+
+				// Compare this cell
+				numFailedPixels += computeTextureLookupDiff(tcu::getSubregion(renderedFrame.getAccess(), curX, curY, curW, curH),
+															tcu::getSubregion(referenceFrame.getAccess(), curX, curY, curW, curH),
+															tcu::getSubregion(errorMask.getAccess(), curX, curY, curW, curH),
+															m_texture->getRefTexture(), &texCoord[0], sampleParams,
+															lookupPrec, lodPrec, m_testCtx.getWatchDog());
+			}
+		}
+
+		if (numFailedPixels > 0)
+			m_testCtx.getLog() << TestLog::Message << "ERROR: Image verification failed, found " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+		m_testCtx.getLog() << TestLog::ImageSet("Result", "Verification result")
+							<< TestLog::Image("Rendered", "Rendered image", renderedFrame);
+
+		if (numFailedPixels > 0)
+		{
+			m_testCtx.getLog() << TestLog::Image("Reference", "Ideal reference", referenceFrame)
+								<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+		}
+
+		m_testCtx.getLog() << TestLog::EndImageSet;
+
+		{
+			const bool isOk = numFailedPixels == 0;
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Image verification failed");
+		}
+	}
+
+	return STOP;
+}
+
+class Texture3DMinLodCase : public Texture3DLodControlCase
+{
+public:
+	Texture3DMinLodCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: Texture3DLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+		gl.texParameterf(GL_TEXTURE_3D, GL_TEXTURE_MIN_LOD, getMinLodForCell(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.minLod = getMinLodForCell(cellNdx);
+	}
+};
+
+class Texture3DMaxLodCase : public Texture3DLodControlCase
+{
+public:
+	Texture3DMaxLodCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: Texture3DLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+		gl.texParameterf(GL_TEXTURE_3D, GL_TEXTURE_MAX_LOD, getMaxLodForCell(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.maxLod = getMaxLodForCell(cellNdx);
+	}
+};
+
+class Texture3DBaseLevelCase : public Texture3DLodControlCase
+{
+public:
+	Texture3DBaseLevelCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: Texture3DLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	int getBaseLevel (int cellNdx) const
+	{
+		const int	numLevels	= deLog2Floor32(de::max(m_texWidth, de::max(m_texHeight, m_texDepth)))+1;
+		const int	baseLevel	= (deInt32Hash(cellNdx) ^ deStringHash(getName()) ^ 0x7347e9) % numLevels;
+
+		return baseLevel;
+	}
+
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+		gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, getBaseLevel(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.baseLevel = getBaseLevel(cellNdx);
+	}
+};
+
+class Texture3DMaxLevelCase : public Texture3DLodControlCase
+{
+public:
+	Texture3DMaxLevelCase (Context& context, const char* name, const char* desc, deUint32 minFilter)
+		: Texture3DLodControlCase(context, name, desc, minFilter)
+	{
+	}
+
+protected:
+	int getMaxLevel (int cellNdx) const
+	{
+		const int	numLevels	= deLog2Floor32(de::max(m_texWidth, de::max(m_texHeight, m_texDepth)))+1;
+		const int	maxLevel	= (deInt32Hash(cellNdx) ^ deStringHash(getName()) ^ 0x9111e7) % numLevels;
+
+		return maxLevel;
+	}
+
+	void setTextureParams (int cellNdx)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+		gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, getMaxLevel(cellNdx));
+	}
+
+	void getReferenceParams (ReferenceParams& params, int cellNdx)
+	{
+		params.maxLevel = getMaxLevel(cellNdx);
+	}
+};
+
+TextureMipmapTests::TextureMipmapTests (Context& context)
+	: TestCaseGroup(context, "mipmap", "Mipmapping tests")
+{
+}
+
+TextureMipmapTests::~TextureMipmapTests (void)
+{
+}
+
+void TextureMipmapTests::init (void)
+{
+	tcu::TestCaseGroup* group2D		= new tcu::TestCaseGroup(m_testCtx, "2d",	"2D Texture Mipmapping");
+	tcu::TestCaseGroup*	groupCube	= new tcu::TestCaseGroup(m_testCtx, "cube",	"Cube Map Mipmapping");
+	tcu::TestCaseGroup*	group3D		= new tcu::TestCaseGroup(m_testCtx, "3d",	"3D Texture Mipmapping");
+	addChild(group2D);
+	addChild(groupCube);
+	addChild(group3D);
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE },
+		{ "repeat",		GL_REPEAT },
+		{ "mirror",		GL_MIRRORED_REPEAT }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} minFilterModes[] =
+	{
+		{ "nearest_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
+		{ "linear_nearest",		GL_LINEAR_MIPMAP_NEAREST	},
+		{ "nearest_linear",		GL_NEAREST_MIPMAP_LINEAR	},
+		{ "linear_linear",		GL_LINEAR_MIPMAP_LINEAR		}
+	};
+
+	static const struct
+	{
+		CoordType		type;
+		const char*		name;
+		const char*		desc;
+	} coordTypes[] =
+	{
+		{ COORDTYPE_BASIC,		"basic",		"Mipmapping with translated and scaled coordinates" },
+		{ COORDTYPE_AFFINE,		"affine",		"Mipmapping with affine coordinate transform"		},
+		{ COORDTYPE_PROJECTED,	"projected",	"Mipmapping with perspective projection"			}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		format;
+		deUint32		dataType;
+	} formats[] =
+	{
+		{ "a8",			GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "la88",		GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
+		{ "rgb565",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgba5551",	GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		hint;
+	} genHints[] =
+	{
+		{ "fastest",	GL_FASTEST },
+		{ "nicest",		GL_NICEST }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		int				width;
+		int				height;
+	} tex2DSizes[] =
+	{
+		{ DE_NULL,		64, 64 }, // Default.
+		{ "npot",		63, 57 },
+		{ "non_square",	32, 64 }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		int				width;
+		int				height;
+		int				depth;
+	} tex3DSizes[] =
+	{
+		{ DE_NULL,		32, 32, 32 }, // Default.
+		{ "npot",		33, 29, 27 }
+	};
+
+	const int cubeMapSize = 64;
+
+	static const struct
+	{
+		CoordType		type;
+		const char*		name;
+		const char*		desc;
+	} cubeCoordTypes[] =
+	{
+		{ COORDTYPE_BASIC,		"basic",		"Mipmapping with translated and scaled coordinates" },
+		{ COORDTYPE_PROJECTED,	"projected",	"Mipmapping with perspective projection"			},
+		{ COORDTYPE_BASIC_BIAS,	"bias",			"User-supplied bias value"							}
+	};
+
+	// 2D cases.
+	for (int coordType = 0; coordType < DE_LENGTH_OF_ARRAY(coordTypes); coordType++)
+	{
+		tcu::TestCaseGroup* coordTypeGroup = new tcu::TestCaseGroup(m_testCtx, coordTypes[coordType].name, coordTypes[coordType].desc);
+		group2D->addChild(coordTypeGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+		{
+			for (int wrapMode = 0; wrapMode < DE_LENGTH_OF_ARRAY(wrapModes); wrapMode++)
+			{
+				// Add non_square variants to basic cases only.
+				int sizeEnd = coordTypes[coordType].type == COORDTYPE_BASIC ? DE_LENGTH_OF_ARRAY(tex2DSizes) : 1;
+
+				for (int size = 0; size < sizeEnd; size++)
+				{
+					std::ostringstream name;
+					name << minFilterModes[minFilter].name
+						 << "_" << wrapModes[wrapMode].name;
+
+					if (tex2DSizes[size].name)
+						name << "_" << tex2DSizes[size].name;
+
+					coordTypeGroup->addChild(new Texture2DMipmapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+																	 name.str().c_str(), "",
+																	 coordTypes[coordType].type,
+																	 minFilterModes[minFilter].mode,
+																	 wrapModes[wrapMode].mode,
+																	 wrapModes[wrapMode].mode,
+																	 GL_RGBA, GL_UNSIGNED_BYTE,
+																	 tex2DSizes[size].width, tex2DSizes[size].height));
+				}
+			}
+		}
+	}
+
+	// 2D bias variants.
+	{
+		tcu::TestCaseGroup* biasGroup = new tcu::TestCaseGroup(m_testCtx, "bias", "User-supplied bias value");
+		group2D->addChild(biasGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			biasGroup->addChild(new Texture2DMipmapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+														minFilterModes[minFilter].name, "",
+														COORDTYPE_BASIC_BIAS,
+														minFilterModes[minFilter].mode,
+														GL_REPEAT, GL_REPEAT,
+														GL_RGBA, GL_UNSIGNED_BYTE,
+														tex2DSizes[0].width, tex2DSizes[0].height));
+	}
+
+	// 2D mipmap generation variants.
+	{
+		tcu::TestCaseGroup* genMipmapGroup = new tcu::TestCaseGroup(m_testCtx, "generate", "Mipmap generation tests");
+		group2D->addChild(genMipmapGroup);
+
+		for (int format = 0; format < DE_LENGTH_OF_ARRAY(formats); format++)
+		{
+			for (int size = 0; size < DE_LENGTH_OF_ARRAY(tex2DSizes); size++)
+			{
+				for (int hint = 0; hint < DE_LENGTH_OF_ARRAY(genHints); hint++)
+				{
+					std::ostringstream name;
+					name << formats[format].name;
+
+					if (tex2DSizes[size].name)
+						name << "_" << tex2DSizes[size].name;
+
+					name << "_" << genHints[hint].name;
+
+					genMipmapGroup->addChild(new Texture2DGenMipmapCase(m_testCtx, m_context.getRenderContext(), name.str().c_str(), "",
+																		formats[format].format, formats[format].dataType, genHints[hint].hint,
+																		tex2DSizes[size].width, tex2DSizes[size].height));
+				}
+			}
+		}
+	}
+
+	// 2D LOD controls.
+	{
+		// MIN_LOD
+		tcu::TestCaseGroup* minLodGroup = new tcu::TestCaseGroup(m_testCtx, "min_lod", "Lod control: min lod");
+		group2D->addChild(minLodGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			minLodGroup->addChild(new Texture2DMinLodCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+
+		// MAX_LOD
+		tcu::TestCaseGroup* maxLodGroup = new tcu::TestCaseGroup(m_testCtx, "max_lod", "Lod control: max lod");
+		group2D->addChild(maxLodGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			maxLodGroup->addChild(new Texture2DMaxLodCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+
+		// BASE_LEVEL
+		tcu::TestCaseGroup* baseLevelGroup = new tcu::TestCaseGroup(m_testCtx, "base_level", "Base level");
+		group2D->addChild(baseLevelGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			baseLevelGroup->addChild(new Texture2DBaseLevelCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+
+		// MAX_LEVEL
+		tcu::TestCaseGroup* maxLevelGroup = new tcu::TestCaseGroup(m_testCtx, "max_level", "Max level");
+		group2D->addChild(maxLevelGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			maxLevelGroup->addChild(new Texture2DMaxLevelCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+	}
+
+	// Cubemap cases.
+	for (int coordType = 0; coordType < DE_LENGTH_OF_ARRAY(cubeCoordTypes); coordType++)
+	{
+		tcu::TestCaseGroup* coordTypeGroup = new tcu::TestCaseGroup(m_testCtx, cubeCoordTypes[coordType].name, cubeCoordTypes[coordType].desc);
+		groupCube->addChild(coordTypeGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+		{
+			coordTypeGroup->addChild(new TextureCubeMipmapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(),
+															   minFilterModes[minFilter].name, "",
+															   cubeCoordTypes[coordType].type,
+															   minFilterModes[minFilter].mode,
+															   GL_CLAMP_TO_EDGE,
+															   GL_CLAMP_TO_EDGE,
+															   GL_RGBA, GL_UNSIGNED_BYTE, cubeMapSize));
+		}
+	}
+
+	// Cubemap mipmap generation variants.
+	{
+		tcu::TestCaseGroup* genMipmapGroup = new tcu::TestCaseGroup(m_testCtx, "generate", "Mipmap generation tests");
+		groupCube->addChild(genMipmapGroup);
+
+		for (int format = 0; format < DE_LENGTH_OF_ARRAY(formats); format++)
+		{
+			for (int hint = 0; hint < DE_LENGTH_OF_ARRAY(genHints); hint++)
+			{
+				std::ostringstream name;
+				name << formats[format].name
+					 << "_" << genHints[hint].name;
+
+				genMipmapGroup->addChild(new TextureCubeGenMipmapCase(m_testCtx, m_context.getRenderContext(), name.str().c_str(), "", formats[format].format, formats[format].dataType, genHints[hint].hint, cubeMapSize));
+			}
+		}
+	}
+
+	// Cubemap LOD controls.
+	{
+		// MIN_LOD
+		tcu::TestCaseGroup* minLodGroup = new tcu::TestCaseGroup(m_testCtx, "min_lod", "Lod control: min lod");
+		groupCube->addChild(minLodGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			minLodGroup->addChild(new TextureCubeMinLodCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+
+		// MAX_LOD
+		tcu::TestCaseGroup* maxLodGroup = new tcu::TestCaseGroup(m_testCtx, "max_lod", "Lod control: max lod");
+		groupCube->addChild(maxLodGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			maxLodGroup->addChild(new TextureCubeMaxLodCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+
+		// BASE_LEVEL
+		tcu::TestCaseGroup* baseLevelGroup = new tcu::TestCaseGroup(m_testCtx, "base_level", "Base level");
+		groupCube->addChild(baseLevelGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			baseLevelGroup->addChild(new TextureCubeBaseLevelCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+
+		// MAX_LEVEL
+		tcu::TestCaseGroup* maxLevelGroup = new tcu::TestCaseGroup(m_testCtx, "max_level", "Max level");
+		groupCube->addChild(maxLevelGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			maxLevelGroup->addChild(new TextureCubeMaxLevelCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+	}
+
+	// 3D cases.
+	for (int coordType = 0; coordType < DE_LENGTH_OF_ARRAY(coordTypes); coordType++)
+	{
+		tcu::TestCaseGroup* coordTypeGroup = new tcu::TestCaseGroup(m_testCtx, coordTypes[coordType].name, coordTypes[coordType].desc);
+		group3D->addChild(coordTypeGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+		{
+			for (int wrapMode = 0; wrapMode < DE_LENGTH_OF_ARRAY(wrapModes); wrapMode++)
+			{
+				// Add other size variants to basic cases only.
+				int sizeEnd = coordTypes[coordType].type == COORDTYPE_BASIC ? DE_LENGTH_OF_ARRAY(tex3DSizes) : 1;
+
+				for (int size = 0; size < sizeEnd; size++)
+				{
+					std::ostringstream name;
+					name << minFilterModes[minFilter].name
+						 << "_" << wrapModes[wrapMode].name;
+
+					if (tex3DSizes[size].name)
+						name << "_" << tex3DSizes[size].name;
+
+					coordTypeGroup->addChild(new Texture3DMipmapCase(m_context,
+																	 name.str().c_str(), "",
+																	 coordTypes[coordType].type,
+																	 minFilterModes[minFilter].mode,
+																	 wrapModes[wrapMode].mode,
+																	 wrapModes[wrapMode].mode,
+																	 wrapModes[wrapMode].mode,
+																	 GL_RGBA8,
+																	 tex3DSizes[size].width, tex3DSizes[size].height, tex3DSizes[size].depth));
+				}
+			}
+		}
+	}
+
+	// 3D bias variants.
+	{
+		tcu::TestCaseGroup* biasGroup = new tcu::TestCaseGroup(m_testCtx, "bias", "User-supplied bias value");
+		group3D->addChild(biasGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			biasGroup->addChild(new Texture3DMipmapCase(m_context,
+														minFilterModes[minFilter].name, "",
+														COORDTYPE_BASIC_BIAS,
+														minFilterModes[minFilter].mode,
+														GL_REPEAT, GL_REPEAT, GL_REPEAT,
+														GL_RGBA8,
+														tex3DSizes[0].width, tex3DSizes[0].height, tex3DSizes[0].depth));
+	}
+
+	// 3D LOD controls.
+	{
+		// MIN_LOD
+		tcu::TestCaseGroup* minLodGroup = new tcu::TestCaseGroup(m_testCtx, "min_lod", "Lod control: min lod");
+		group3D->addChild(minLodGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			minLodGroup->addChild(new Texture3DMinLodCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+
+		// MAX_LOD
+		tcu::TestCaseGroup* maxLodGroup = new tcu::TestCaseGroup(m_testCtx, "max_lod", "Lod control: max lod");
+		group3D->addChild(maxLodGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			maxLodGroup->addChild(new Texture3DMaxLodCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+
+		// BASE_LEVEL
+		tcu::TestCaseGroup* baseLevelGroup = new tcu::TestCaseGroup(m_testCtx, "base_level", "Base level");
+		group3D->addChild(baseLevelGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			baseLevelGroup->addChild(new Texture3DBaseLevelCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+
+		// MAX_LEVEL
+		tcu::TestCaseGroup* maxLevelGroup = new tcu::TestCaseGroup(m_testCtx, "max_level", "Max level");
+		group3D->addChild(maxLevelGroup);
+
+		for (int minFilter = 0; minFilter < DE_LENGTH_OF_ARRAY(minFilterModes); minFilter++)
+			maxLevelGroup->addChild(new Texture3DMaxLevelCase(m_context, minFilterModes[minFilter].name, "", minFilterModes[minFilter].mode));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTextureMipmapTests.hpp b/modules/gles3/functional/es3fTextureMipmapTests.hpp
new file mode 100644
index 0000000..8a4e424
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureMipmapTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FTEXTUREMIPMAPTESTS_HPP
+#define _ES3FTEXTUREMIPMAPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Mipmapping tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class TextureMipmapTests : public TestCaseGroup
+{
+public:
+							TextureMipmapTests		(Context& context);
+							~TextureMipmapTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureMipmapTests		(const TextureMipmapTests& other);
+	TextureMipmapTests&		operator=				(const TextureMipmapTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTEXTUREMIPMAPTESTS_HPP
diff --git a/modules/gles3/functional/es3fTextureShadowTests.cpp b/modules/gles3/functional/es3fTextureShadowTests.cpp
new file mode 100644
index 0000000..3c4bc21
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureShadowTests.cpp
@@ -0,0 +1,1114 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shadow texture lookup tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTextureShadowTests.hpp"
+#include "gluTexture.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTexCompareVerifier.hpp"
+#include "deString.h"
+#include "deStringUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using namespace deqp::gls::TextureTestUtil;
+
+enum
+{
+	TEX2D_VIEWPORT_WIDTH		= 64,
+	TEX2D_VIEWPORT_HEIGHT		= 64,
+	TEX2D_MIN_VIEWPORT_WIDTH	= 64,
+	TEX2D_MIN_VIEWPORT_HEIGHT	= 64
+};
+
+static bool isFloatingPointDepthFormat (const tcu::TextureFormat& format)
+{
+	// Only two depth and depth-stencil formats are floating point
+	return	(format.order == tcu::TextureFormat::D && format.type == tcu::TextureFormat::FLOAT) ||
+			(format.order == tcu::TextureFormat::DS && format.type == tcu::TextureFormat::FLOAT_UNSIGNED_INT_24_8_REV);
+}
+
+static void clampFloatingPointTexture (const tcu::PixelBufferAccess& access)
+{
+	DE_ASSERT(isFloatingPointDepthFormat(access.getFormat()));
+
+	for (int z = 0; z < access.getDepth(); ++z)
+	for (int y = 0; y < access.getHeight(); ++y)
+	for (int x = 0; x < access.getWidth(); ++x)
+		access.setPixDepth( de::clamp(access.getPixDepth(x, y, z), 0.0f, 1.0f), x, y, z);
+}
+
+static void clampFloatingPointTexture (tcu::Texture2D& target)
+{
+	for (int level = 0; level < target.getNumLevels(); ++level)
+		if (!target.isLevelEmpty(level))
+			clampFloatingPointTexture(target.getLevel(level));
+}
+
+static void clampFloatingPointTexture (tcu::Texture2DArray& target)
+{
+	for (int level = 0; level < target.getNumLevels(); ++level)
+		if (!target.isLevelEmpty(level))
+			clampFloatingPointTexture(target.getLevel(level));
+}
+
+static void clampFloatingPointTexture (tcu::TextureCube& target)
+{
+	for (int level = 0; level < target.getNumLevels(); ++level)
+		for (int face = tcu::CUBEFACE_NEGATIVE_X; face < tcu::CUBEFACE_LAST; ++face)
+			clampFloatingPointTexture(target.getLevelFace(level, (tcu::CubeFace)face));
+}
+
+template<typename TextureType>
+bool verifyTexCompareResult (tcu::TestContext&						testCtx,
+							 const tcu::ConstPixelBufferAccess&		result,
+							 const TextureType&						src,
+							 const float*							texCoord,
+							 const ReferenceParams&					sampleParams,
+							 const tcu::TexComparePrecision&		comparePrec,
+							 const tcu::LodPrecision&				lodPrec,
+							 const tcu::PixelFormat&				pixelFormat)
+{
+	tcu::TestLog&	log					= testCtx.getLog();
+	tcu::Surface	reference			(result.getWidth(), result.getHeight());
+	tcu::Surface	errorMask			(result.getWidth(), result.getHeight());
+	const tcu::Vec3	nonShadowThreshold	= tcu::computeFixedPointThreshold(getBitsVec(pixelFormat)-1).swizzle(1,2,3);
+	int				numFailedPixels;
+
+	// sampleTexture() expects source image to be the same state as it would be in a GL implementation, that is
+	// the floating point depth values should be in [0, 1] range as data is clamped during texture upload. Since
+	// we don't have a separate "uploading" phase and just reuse the buffer we used for GL-upload, do the clamping
+	// here if necessary.
+
+	if (isFloatingPointDepthFormat(src.getFormat()))
+	{
+		TextureType clampedSource(src);
+
+		clampFloatingPointTexture(clampedSource);
+
+		// sample clamped values
+
+		sampleTexture(SurfaceAccess(reference, pixelFormat), clampedSource, texCoord, sampleParams);
+		numFailedPixels = computeTextureCompareDiff(result, reference.getAccess(), errorMask.getAccess(), clampedSource, texCoord, sampleParams, comparePrec, lodPrec, nonShadowThreshold);
+	}
+	else
+	{
+		// sample raw values (they are guaranteed to be in [0, 1] range as the format cannot represent any other values)
+
+		sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
+		numFailedPixels = computeTextureCompareDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, comparePrec, lodPrec, nonShadowThreshold);
+	}
+
+	if (numFailedPixels > 0)
+		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+	log << TestLog::ImageSet("VerifyResult", "Verification result")
+		<< TestLog::Image("Rendered", "Rendered image", result);
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Image("Reference", "Ideal reference image", reference)
+			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+	}
+
+	log << TestLog::EndImageSet;
+
+	return numFailedPixels == 0;
+}
+
+class Texture2DShadowCase : public TestCase
+{
+public:
+									Texture2DShadowCase			(Context& context, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, int width, int height, deUint32 compareFunc);
+									~Texture2DShadowCase		(void);
+
+	void							init						(void);
+	void							deinit						(void);
+	IterateResult					iterate						(void);
+
+private:
+									Texture2DShadowCase			(const Texture2DShadowCase& other);
+	Texture2DShadowCase&			operator=					(const Texture2DShadowCase& other);
+
+	const deUint32					m_minFilter;
+	const deUint32					m_magFilter;
+	const deUint32					m_wrapS;
+	const deUint32					m_wrapT;
+	const deUint32					m_format;
+	const int						m_width;
+	const int						m_height;
+	const deUint32					m_compareFunc;
+
+	struct FilterCase
+	{
+		const glu::Texture2D*	texture;
+		tcu::Vec2				minCoord;
+		tcu::Vec2				maxCoord;
+		float					ref;
+
+		FilterCase (void)
+			: texture	(DE_NULL)
+			, ref		(0.0f)
+		{
+		}
+
+		FilterCase (const glu::Texture2D* tex_, const float ref_, const tcu::Vec2& minCoord_, const tcu::Vec2& maxCoord_)
+			: texture	(tex_)
+			, minCoord	(minCoord_)
+			, maxCoord	(maxCoord_)
+			, ref		(ref_)
+		{
+		}
+	};
+
+	std::vector<glu::Texture2D*>	m_textures;
+	std::vector<FilterCase>			m_cases;
+
+	TextureRenderer					m_renderer;
+
+	int								m_caseNdx;
+};
+
+Texture2DShadowCase::Texture2DShadowCase (Context& context, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, int width, int height, deUint32 compareFunc)
+	: TestCase			(context, name, desc)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_width			(width)
+	, m_height			(height)
+	, m_compareFunc		(compareFunc)
+	, m_renderer		(context.getRenderContext(), context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_caseNdx			(0)
+{
+}
+
+Texture2DShadowCase::~Texture2DShadowCase (void)
+{
+	deinit();
+}
+
+void Texture2DShadowCase::init (void)
+{
+	try
+	{
+		// Create 2 textures.
+		m_textures.reserve(2);
+		m_textures.push_back(new glu::Texture2D(m_context.getRenderContext(), m_format, m_width, m_height));
+		m_textures.push_back(new glu::Texture2D(m_context.getRenderContext(), m_format, m_width, m_height));
+
+		int numLevels = m_textures[0]->getRefTexture().getNumLevels();
+
+		// Fill first gradient texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			m_textures[0]->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), tcu::Vec4(-0.5f, -0.5f, -0.5f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f));
+		}
+
+		// Fill second with grid texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			deUint32	step	= 0x00ffffff / numLevels;
+			deUint32	rgb		= step*levelNdx;
+			deUint32	colorA	= 0xff000000 | rgb;
+			deUint32	colorB	= 0xff000000 | ~rgb;
+
+			m_textures[1]->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA)), toVec4(tcu::RGBA(colorB)));
+		}
+
+		// Upload.
+		for (std::vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+			(*i)->upload();
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		Texture2DShadowCase::deinit();
+		throw;
+	}
+
+	// Compute cases.
+	{
+		const float refInRangeUpper		= (m_compareFunc == GL_EQUAL || m_compareFunc == GL_NOTEQUAL) ? 1.0f : 0.5f;
+		const float refInRangeLower		= (m_compareFunc == GL_EQUAL || m_compareFunc == GL_NOTEQUAL) ? 0.0f : 0.5f;
+		const float refOutOfBoundsUpper	= 1.1f;		// !< lookup function should clamp values to [0, 1] range
+		const float refOutOfBoundsLower	= -0.1f;
+
+		const struct
+		{
+			int		texNdx;
+			float	ref;
+			float	lodX;
+			float	lodY;
+			float	oX;
+			float	oY;
+		} cases[] =
+		{
+			{ 0,	refInRangeUpper,		1.6f,	2.9f,	-1.0f,	-2.7f	},
+			{ 0,	refInRangeLower,		-2.0f,	-1.35f,	-0.2f,	0.7f	},
+			{ 1,	refInRangeUpper,		0.14f,	0.275f,	-1.5f,	-1.1f	},
+			{ 1,	refInRangeLower,		-0.92f,	-2.64f,	0.4f,	-0.1f	},
+			{ 1,	refOutOfBoundsUpper,	-0.39f,	-0.52f,	0.65f,	0.87f	},
+			{ 1,	refOutOfBoundsLower,	-1.55f,	0.65f,	0.35f,	0.91f	},
+		};
+
+		const float	viewportW	= (float)de::min<int>(TEX2D_VIEWPORT_WIDTH, m_context.getRenderTarget().getWidth());
+		const float	viewportH	= (float)de::min<int>(TEX2D_VIEWPORT_HEIGHT, m_context.getRenderTarget().getHeight());
+
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+		{
+			const int	texNdx	= de::clamp(cases[caseNdx].texNdx, 0, (int)m_textures.size()-1);
+			const float ref		= cases[caseNdx].ref;
+			const float	lodX	= cases[caseNdx].lodX;
+			const float	lodY	= cases[caseNdx].lodY;
+			const float	oX		= cases[caseNdx].oX;
+			const float	oY		= cases[caseNdx].oY;
+			const float	sX		= deFloatExp2(lodX)*viewportW / float(m_textures[texNdx]->getRefTexture().getWidth());
+			const float	sY		= deFloatExp2(lodY)*viewportH / float(m_textures[texNdx]->getRefTexture().getHeight());
+
+			m_cases.push_back(FilterCase(m_textures[texNdx], ref, tcu::Vec2(oX, oY), tcu::Vec2(oX+sX, oY+sY)));
+		}
+	}
+
+	m_caseNdx = 0;
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void Texture2DShadowCase::deinit (void)
+{
+	for (std::vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	m_renderer.clear();
+	m_cases.clear();
+}
+
+Texture2DShadowCase::IterateResult Texture2DShadowCase::iterate (void)
+{
+	const glw::Functions&			gl				= m_context.getRenderContext().getFunctions();
+	const RandomViewport			viewport		(m_context.getRenderTarget(), TEX2D_VIEWPORT_WIDTH, TEX2D_VIEWPORT_HEIGHT, deStringHash(getName()) ^ deInt32Hash(m_caseNdx));
+	const FilterCase&				curCase			= m_cases[m_caseNdx];
+	const tcu::ScopedLogSection		section			(m_testCtx.getLog(), string("Test") + de::toString(m_caseNdx), string("Test ") + de::toString(m_caseNdx));
+	ReferenceParams					sampleParams	(TEXTURETYPE_2D);
+	tcu::Surface					rendered		(viewport.width, viewport.height);
+	vector<float>					texCoord;
+
+	if (viewport.width < TEX2D_MIN_VIEWPORT_WIDTH || viewport.height < TEX2D_MIN_VIEWPORT_HEIGHT)
+		throw tcu::NotSupportedError("Too small render target", "", __FILE__, __LINE__);
+
+	// Setup params for reference.
+	sampleParams.sampler			= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	sampleParams.sampler.compare	= glu::mapGLCompareFunc(m_compareFunc);
+	sampleParams.samplerType		= SAMPLERTYPE_SHADOW;
+	sampleParams.lodMode			= LODMODE_EXACT;
+	sampleParams.ref				= curCase.ref;
+
+	m_testCtx.getLog() << TestLog::Message << "Compare reference value =  " << sampleParams.ref << TestLog::EndMessage;
+
+	// Compute texture coordinates.
+	m_testCtx.getLog() << TestLog::Message << "Texture coordinates: " << curCase.minCoord << " -> " << curCase.maxCoord << TestLog::EndMessage;
+	computeQuadTexCoord2D(texCoord, curCase.minCoord, curCase.maxCoord);
+
+	gl.bindTexture	(GL_TEXTURE_2D, curCase.texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,		m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,		m_magFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,			m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,			m_wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,	GL_COMPARE_REF_TO_TEXTURE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC,	m_compareFunc);
+
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+	m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+	glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, rendered.getAccess());
+
+	{
+		const tcu::PixelFormat		pixelFormat		= m_context.getRenderTarget().getPixelFormat();
+		tcu::LodPrecision			lodPrecision;
+		tcu::TexComparePrecision	texComparePrecision;
+
+		lodPrecision.derivateBits			= 18;
+		lodPrecision.lodBits				= 6;
+		texComparePrecision.coordBits		= tcu::IVec3(20,20,0);
+		texComparePrecision.uvwBits			= tcu::IVec3(7,7,0);
+		texComparePrecision.pcfBits			= 5;
+		texComparePrecision.referenceBits	= 16;
+		texComparePrecision.resultBits		= pixelFormat.redBits-1;
+
+		const bool isHighQuality = verifyTexCompareResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+														  &texCoord[0], sampleParams, texComparePrecision, lodPrecision, pixelFormat);
+
+		if (!isHighQuality)
+		{
+			m_testCtx.getLog() << TestLog::Message << "Warning: Verification assuming high-quality PCF filtering failed." << TestLog::EndMessage;
+
+			lodPrecision.lodBits			= 4;
+			texComparePrecision.uvwBits		= tcu::IVec3(4,4,0);
+			texComparePrecision.pcfBits		= 0;
+
+			const bool isOk = verifyTexCompareResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+													 &texCoord[0], sampleParams, texComparePrecision, lodPrecision, pixelFormat);
+
+			if (!isOk)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: Verification against low precision requirements failed, failing test case." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+			}
+			else if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Low-quality result");
+		}
+	}
+
+	m_caseNdx += 1;
+	return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
+}
+
+class TextureCubeShadowCase : public TestCase
+{
+public:
+								TextureCubeShadowCase		(Context& context, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, int size, deUint32 compareFunc);
+								~TextureCubeShadowCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								TextureCubeShadowCase		(const TextureCubeShadowCase& other);
+	TextureCubeShadowCase&		operator=					(const TextureCubeShadowCase& other);
+
+	const deUint32				m_minFilter;
+	const deUint32				m_magFilter;
+	const deUint32				m_wrapS;
+	const deUint32				m_wrapT;
+
+	const deUint32				m_format;
+	const int					m_size;
+
+	const deUint32				m_compareFunc;
+
+	struct FilterCase
+	{
+		const glu::TextureCube*	texture;
+		tcu::Vec2				bottomLeft;
+		tcu::Vec2				topRight;
+		float					ref;
+
+		FilterCase (void)
+			: texture	(DE_NULL)
+			, ref		(0.0f)
+		{
+		}
+
+		FilterCase (const glu::TextureCube* tex_, const float ref_, const tcu::Vec2& bottomLeft_, const tcu::Vec2& topRight_)
+			: texture	(tex_)
+			, bottomLeft(bottomLeft_)
+			, topRight	(topRight_)
+			, ref		(ref_)
+		{
+		}
+	};
+
+	glu::TextureCube*			m_gradientTex;
+	glu::TextureCube*			m_gridTex;
+	std::vector<FilterCase>		m_cases;
+
+	TextureRenderer				m_renderer;
+
+	int							m_caseNdx;
+};
+
+TextureCubeShadowCase::TextureCubeShadowCase (Context& context, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, int size, deUint32 compareFunc)
+	: TestCase			(context, name, desc)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_size			(size)
+	, m_compareFunc		(compareFunc)
+	, m_gradientTex		(DE_NULL)
+	, m_gridTex			(DE_NULL)
+	, m_renderer		(context.getRenderContext(), m_context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_caseNdx			(0)
+{
+}
+
+TextureCubeShadowCase::~TextureCubeShadowCase (void)
+{
+	TextureCubeShadowCase::deinit();
+}
+
+void TextureCubeShadowCase::init (void)
+{
+	try
+	{
+		DE_ASSERT(!m_gradientTex && !m_gridTex);
+
+		int						numLevels	= deLog2Floor32(m_size)+1;
+		tcu::TextureFormat		texFmt		= glu::mapGLInternalFormat(m_format);
+		tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFmt);
+		tcu::Vec4				cBias		= fmtInfo.valueMin;
+		tcu::Vec4				cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+		// Create textures.
+		m_gradientTex	= new glu::TextureCube(m_context.getRenderContext(), m_format, m_size);
+		m_gridTex		= new glu::TextureCube(m_context.getRenderContext(), m_format, m_size);
+
+		// Fill first with gradient texture.
+		static const tcu::Vec4 gradients[tcu::CUBEFACE_LAST][2] =
+		{
+			{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
+			{ tcu::Vec4( 0.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
+			{ tcu::Vec4(-1.0f,  0.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
+			{ tcu::Vec4(-1.0f, -1.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
+			{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
+			{ tcu::Vec4( 0.0f,  0.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
+		};
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				m_gradientTex->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+				tcu::fillWithComponentGradients(m_gradientTex->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0]*cScale + cBias, gradients[face][1]*cScale + cBias);
+			}
+		}
+
+		// Fill second with grid texture.
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				deUint32	step	= 0x00ffffff / (numLevels*tcu::CUBEFACE_LAST);
+				deUint32	rgb		= step*levelNdx*face;
+				deUint32	colorA	= 0xff000000 | rgb;
+				deUint32	colorB	= 0xff000000 | ~rgb;
+
+				m_gridTex->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+				tcu::fillWithGrid(m_gridTex->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+			}
+		}
+
+		// Upload.
+		m_gradientTex->upload();
+		m_gridTex->upload();
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		TextureCubeShadowCase::deinit();
+		throw;
+	}
+
+	// Compute cases
+	{
+		const float refInRangeUpper		= (m_compareFunc == GL_EQUAL || m_compareFunc == GL_NOTEQUAL) ? 1.0f : 0.5f;
+		const float refInRangeLower		= (m_compareFunc == GL_EQUAL || m_compareFunc == GL_NOTEQUAL) ? 0.0f : 0.5f;
+		const float refOutOfBoundsUpper	= 1.1f;
+		const float refOutOfBoundsLower	= -0.1f;
+		const bool	singleSample		= m_context.getRenderTarget().getNumSamples() == 0;
+
+		if (singleSample)
+			m_cases.push_back(FilterCase(m_gradientTex,	refInRangeUpper, tcu::Vec2(-1.25f, -1.2f), tcu::Vec2(1.2f, 1.25f)));	// minification
+		else
+			m_cases.push_back(FilterCase(m_gradientTex,	refInRangeUpper, tcu::Vec2(-1.19f, -1.3f), tcu::Vec2(1.1f, 1.35f)));	// minification - w/ tuned coordinates to avoid hitting triangle edges
+
+		m_cases.push_back(FilterCase(m_gradientTex,	refInRangeLower,		tcu::Vec2(0.8f, 0.8f), tcu::Vec2(1.25f, 1.20f)));	// magnification
+		m_cases.push_back(FilterCase(m_gridTex,		refInRangeUpper,		tcu::Vec2(-1.19f, -1.3f), tcu::Vec2(1.1f, 1.35f)));	// minification
+		m_cases.push_back(FilterCase(m_gridTex,		refInRangeLower,		tcu::Vec2(-1.2f, -1.1f), tcu::Vec2(-0.8f, -0.8f)));	// magnification
+		m_cases.push_back(FilterCase(m_gridTex,		refOutOfBoundsUpper,	tcu::Vec2(-0.61f, -0.1f), tcu::Vec2(0.9f, 1.18f)));	// reference value clamp, upper
+
+		if (singleSample)
+			m_cases.push_back(FilterCase(m_gridTex,	refOutOfBoundsLower, tcu::Vec2(-0.75f, 1.0f), tcu::Vec2(0.05f, 0.75f)));	// reference value clamp, lower
+		else
+			m_cases.push_back(FilterCase(m_gridTex,	refOutOfBoundsLower, tcu::Vec2(-0.75f, 1.0f), tcu::Vec2(0.25f, 0.75f)));	// reference value clamp, lower
+	}
+
+	m_caseNdx = 0;
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void TextureCubeShadowCase::deinit (void)
+{
+	delete m_gradientTex;
+	delete m_gridTex;
+
+	m_gradientTex	= DE_NULL;
+	m_gridTex		= DE_NULL;
+
+	m_renderer.clear();
+	m_cases.clear();
+}
+
+static const char* getFaceDesc (const tcu::CubeFace face)
+{
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X:	return "-X";
+		case tcu::CUBEFACE_POSITIVE_X:	return "+X";
+		case tcu::CUBEFACE_NEGATIVE_Y:	return "-Y";
+		case tcu::CUBEFACE_POSITIVE_Y:	return "+Y";
+		case tcu::CUBEFACE_NEGATIVE_Z:	return "-Z";
+		case tcu::CUBEFACE_POSITIVE_Z:	return "+Z";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+TextureCubeShadowCase::IterateResult TextureCubeShadowCase::iterate (void)
+{
+	const glw::Functions&			gl				= m_context.getRenderContext().getFunctions();
+	const int						viewportSize	= 28;
+	const RandomViewport			viewport		(m_context.getRenderTarget(), viewportSize, viewportSize, deStringHash(getName()) ^ deInt32Hash(m_caseNdx));
+	const tcu::ScopedLogSection		iterSection		(m_testCtx.getLog(), string("Test") + de::toString(m_caseNdx), string("Test ") + de::toString(m_caseNdx));
+	const FilterCase&				curCase			= m_cases[m_caseNdx];
+	ReferenceParams					sampleParams	(TEXTURETYPE_CUBE);
+
+	if (viewport.width < viewportSize || viewport.height < viewportSize)
+		throw tcu::NotSupportedError("Too small render target", DE_NULL, __FILE__, __LINE__);
+
+	// Setup texture
+	gl.bindTexture	(GL_TEXTURE_CUBE_MAP, curCase.texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_MODE,	GL_COMPARE_REF_TO_TEXTURE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_FUNC,	m_compareFunc);
+
+	// Other state
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Params for reference computation.
+	sampleParams.sampler					= glu::mapGLSampler(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, m_minFilter, m_magFilter);
+	sampleParams.sampler.seamlessCubeMap	= true;
+	sampleParams.sampler.compare			= glu::mapGLCompareFunc(m_compareFunc);
+	sampleParams.samplerType				= SAMPLERTYPE_SHADOW;
+	sampleParams.lodMode					= LODMODE_EXACT;
+	sampleParams.ref						= curCase.ref;
+
+	m_testCtx.getLog()
+		<< TestLog::Message
+		<< "Compare reference value =  " << sampleParams.ref << "\n"
+		<< "Coordinates: " << curCase.bottomLeft << " -> " << curCase.topRight
+		<< TestLog::EndMessage;
+
+	for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+	{
+		const tcu::CubeFace		face		= tcu::CubeFace(faceNdx);
+		tcu::Surface			result		(viewport.width, viewport.height);
+		vector<float>			texCoord;
+
+		computeQuadTexCoordCube(texCoord, face, curCase.bottomLeft, curCase.topRight);
+
+		m_testCtx.getLog() << TestLog::Message << "Face " << getFaceDesc(face) << TestLog::EndMessage;
+
+		// \todo Log texture coordinates.
+
+		m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
+
+		glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, result.getAccess());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels");
+
+		{
+			const tcu::PixelFormat		pixelFormat		= m_context.getRenderTarget().getPixelFormat();
+			tcu::LodPrecision			lodPrecision;
+			tcu::TexComparePrecision	texComparePrecision;
+
+			lodPrecision.derivateBits			= 10;
+			lodPrecision.lodBits				= 5;
+			texComparePrecision.coordBits		= tcu::IVec3(10,10,10);
+			texComparePrecision.uvwBits			= tcu::IVec3(6,6,0);
+			texComparePrecision.pcfBits			= 5;
+			texComparePrecision.referenceBits	= 16;
+			texComparePrecision.resultBits		= pixelFormat.redBits-1;
+
+			const bool isHighQuality = verifyTexCompareResult(m_testCtx, result.getAccess(), curCase.texture->getRefTexture(),
+															  &texCoord[0], sampleParams, texComparePrecision, lodPrecision, pixelFormat);
+
+			if (!isHighQuality)
+			{
+				m_testCtx.getLog() << TestLog::Message << "Warning: Verification assuming high-quality PCF filtering failed." << TestLog::EndMessage;
+
+				lodPrecision.lodBits			= 4;
+				texComparePrecision.uvwBits		= tcu::IVec3(4,4,0);
+				texComparePrecision.pcfBits		= 0;
+
+				const bool isOk = verifyTexCompareResult(m_testCtx, result.getAccess(), curCase.texture->getRefTexture(),
+														 &texCoord[0], sampleParams, texComparePrecision, lodPrecision, pixelFormat);
+
+				if (!isOk)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: Verification against low precision requirements failed, failing test case." << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+				}
+				else if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Low-quality result");
+			}
+		}
+	}
+
+	m_caseNdx += 1;
+	return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
+}
+
+class Texture2DArrayShadowCase : public TestCase
+{
+public:
+								Texture2DArrayShadowCase	(Context& context, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, int width, int height, int numLayers, deUint32 compareFunc);
+								~Texture2DArrayShadowCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								Texture2DArrayShadowCase	(const Texture2DArrayShadowCase& other);
+	Texture2DArrayShadowCase&	operator=					(const Texture2DArrayShadowCase& other);
+
+	const deUint32				m_minFilter;
+	const deUint32				m_magFilter;
+	const deUint32				m_wrapS;
+	const deUint32				m_wrapT;
+
+	const deUint32				m_format;
+	const int					m_width;
+	const int					m_height;
+	const int					m_numLayers;
+
+	const deUint32				m_compareFunc;
+
+	struct FilterCase
+	{
+		const glu::Texture2DArray*	texture;
+		tcu::Vec3					minCoord;
+		tcu::Vec3					maxCoord;
+		float						ref;
+
+		FilterCase (void)
+			: texture	(DE_NULL)
+			, ref		(0.0f)
+		{
+		}
+
+		FilterCase (const glu::Texture2DArray* tex_, float ref_, const tcu::Vec3& minCoord_, const tcu::Vec3& maxCoord_)
+			: texture	(tex_)
+			, minCoord	(minCoord_)
+			, maxCoord	(maxCoord_)
+			, ref		(ref_)
+		{
+		}
+	};
+
+	glu::Texture2DArray*		m_gradientTex;
+	glu::Texture2DArray*		m_gridTex;
+	std::vector<FilterCase>		m_cases;
+
+	TextureRenderer				m_renderer;
+
+	int							m_caseNdx;
+};
+
+Texture2DArrayShadowCase::Texture2DArrayShadowCase (Context& context, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 format, int width, int height, int numLayers, deUint32 compareFunc)
+	: TestCase			(context, name, desc)
+	, m_minFilter		(minFilter)
+	, m_magFilter		(magFilter)
+	, m_wrapS			(wrapS)
+	, m_wrapT			(wrapT)
+	, m_format			(format)
+	, m_width			(width)
+	, m_height			(height)
+	, m_numLayers		(numLayers)
+	, m_compareFunc		(compareFunc)
+	, m_gradientTex		(DE_NULL)
+	, m_gridTex			(DE_NULL)
+	, m_renderer		(context.getRenderContext(), context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+	, m_caseNdx			(0)
+{
+}
+
+Texture2DArrayShadowCase::~Texture2DArrayShadowCase (void)
+{
+	Texture2DArrayShadowCase::deinit();
+}
+
+void Texture2DArrayShadowCase::init (void)
+{
+	try
+	{
+		tcu::TextureFormat		texFmt		= glu::mapGLInternalFormat(m_format);
+		tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFmt);
+		tcu::Vec4				cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+		tcu::Vec4				cBias		= fmtInfo.valueMin;
+		int						numLevels	= deLog2Floor32(de::max(m_width, m_height)) + 1;
+
+		// Create textures.
+		m_gradientTex	= new glu::Texture2DArray(m_context.getRenderContext(), m_format, m_width, m_height, m_numLayers);
+		m_gridTex		= new glu::Texture2DArray(m_context.getRenderContext(), m_format, m_width, m_height, m_numLayers);
+
+		// Fill first gradient texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			tcu::Vec4 gMin = tcu::Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
+			tcu::Vec4 gMax = tcu::Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;
+
+			m_gradientTex->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithComponentGradients(m_gradientTex->getRefTexture().getLevel(levelNdx), gMin, gMax);
+		}
+
+		// Fill second with grid texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			deUint32	step	= 0x00ffffff / numLevels;
+			deUint32	rgb		= step*levelNdx;
+			deUint32	colorA	= 0xff000000 | rgb;
+			deUint32	colorB	= 0xff000000 | ~rgb;
+
+			m_gridTex->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithGrid(m_gridTex->getRefTexture().getLevel(levelNdx), 4, tcu::RGBA(colorA).toVec()*cScale + cBias, tcu::RGBA(colorB).toVec()*cScale + cBias);
+		}
+
+		// Upload.
+		m_gradientTex->upload();
+		m_gridTex->upload();
+	}
+	catch (...)
+	{
+		// Clean up to save memory.
+		Texture2DArrayShadowCase::deinit();
+		throw;
+	}
+
+	// Compute cases.
+	{
+		const float refInRangeUpper		= (m_compareFunc == GL_EQUAL || m_compareFunc == GL_NOTEQUAL) ? 1.0f : 0.5f;
+		const float refInRangeLower		= (m_compareFunc == GL_EQUAL || m_compareFunc == GL_NOTEQUAL) ? 0.0f : 0.5f;
+		const float refOutOfBoundsUpper	= 1.1f;		// !< lookup function should clamp values to [0, 1] range
+		const float refOutOfBoundsLower	= -0.1f;
+
+		const struct
+		{
+			int		texNdx;
+			float	ref;
+			float	lodX;
+			float	lodY;
+			float	oX;
+			float	oY;
+		} cases[] =
+		{
+			{ 0,	refInRangeUpper,		1.6f,	2.9f,	-1.0f,	-2.7f	},
+			{ 0,	refInRangeLower,		-2.0f,	-1.35f,	-0.2f,	0.7f	},
+			{ 1,	refInRangeUpper,		0.14f,	0.275f,	-1.5f,	-1.1f	},
+			{ 1,	refInRangeLower,		-0.92f,	-2.64f,	0.4f,	-0.1f	},
+			{ 1,	refOutOfBoundsUpper,	-0.49f,	-0.22f,	0.45f,	0.97f	},
+			{ 1,	refOutOfBoundsLower,	-0.85f,	0.75f,	0.25f,	0.61f	},
+		};
+
+		const float	viewportW	= (float)de::min<int>(TEX2D_VIEWPORT_WIDTH, m_context.getRenderTarget().getWidth());
+		const float	viewportH	= (float)de::min<int>(TEX2D_VIEWPORT_HEIGHT, m_context.getRenderTarget().getHeight());
+
+		const float	minLayer	= -0.5f;
+		const float	maxLayer	= (float)m_numLayers;
+
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+		{
+			const glu::Texture2DArray*	tex		= cases[caseNdx].texNdx > 0 ? m_gridTex : m_gradientTex;
+			const float					ref		= cases[caseNdx].ref;
+			const float					lodX	= cases[caseNdx].lodX;
+			const float					lodY	= cases[caseNdx].lodY;
+			const float					oX		= cases[caseNdx].oX;
+			const float					oY		= cases[caseNdx].oY;
+			const float					sX		= deFloatExp2(lodX)*viewportW / float(tex->getRefTexture().getWidth());
+			const float					sY		= deFloatExp2(lodY)*viewportH / float(tex->getRefTexture().getHeight());
+
+			m_cases.push_back(FilterCase(tex, ref, tcu::Vec3(oX, oY, minLayer), tcu::Vec3(oX+sX, oY+sY, maxLayer)));
+		}
+	}
+
+	m_caseNdx = 0;
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void Texture2DArrayShadowCase::deinit (void)
+{
+	delete m_gradientTex;
+	delete m_gridTex;
+
+	m_gradientTex	= DE_NULL;
+	m_gridTex		= DE_NULL;
+
+	m_renderer.clear();
+	m_cases.clear();
+}
+
+Texture2DArrayShadowCase::IterateResult Texture2DArrayShadowCase::iterate (void)
+{
+	const glw::Functions&			gl				= m_context.getRenderContext().getFunctions();
+	const RandomViewport			viewport		(m_context.getRenderTarget(), TEX2D_VIEWPORT_WIDTH, TEX2D_VIEWPORT_HEIGHT, deStringHash(getName()) ^ deInt32Hash(m_caseNdx));
+	const FilterCase&				curCase			= m_cases[m_caseNdx];
+	const tcu::ScopedLogSection		section			(m_testCtx.getLog(), string("Test") + de::toString(m_caseNdx), string("Test ") + de::toString(m_caseNdx));
+	ReferenceParams					sampleParams	(TEXTURETYPE_2D_ARRAY);
+	tcu::Surface					rendered		(viewport.width, viewport.height);
+
+	const float						texCoord[]		=
+	{
+		curCase.minCoord.x(), curCase.minCoord.y(), curCase.minCoord.z(),
+		curCase.minCoord.x(), curCase.maxCoord.y(), (curCase.minCoord.z() + curCase.maxCoord.z()) / 2.0f,
+		curCase.maxCoord.x(), curCase.minCoord.y(), (curCase.minCoord.z() + curCase.maxCoord.z()) / 2.0f,
+		curCase.maxCoord.x(), curCase.maxCoord.y(), curCase.maxCoord.z()
+	};
+
+	if (viewport.width < TEX2D_MIN_VIEWPORT_WIDTH || viewport.height < TEX2D_MIN_VIEWPORT_HEIGHT)
+		throw tcu::NotSupportedError("Too small render target", "", __FILE__, __LINE__);
+
+	// Setup params for reference.
+	sampleParams.sampler			= glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	sampleParams.sampler.compare	= glu::mapGLCompareFunc(m_compareFunc);
+	sampleParams.samplerType		= SAMPLERTYPE_SHADOW;
+	sampleParams.lodMode			= LODMODE_EXACT;
+	sampleParams.ref				= curCase.ref;
+
+	m_testCtx.getLog()
+		<< TestLog::Message
+		<< "Compare reference value =  " << sampleParams.ref << "\n"
+		<< "Texture coordinates: " << curCase.minCoord << " -> " << curCase.maxCoord
+		<< TestLog::EndMessage;
+
+	gl.bindTexture	(GL_TEXTURE_2D_ARRAY, curCase.texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE,	GL_COMPARE_REF_TO_TEXTURE);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_FUNC,	m_compareFunc);
+
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+	m_renderer.renderQuad(0, &texCoord[0], sampleParams);
+	glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, rendered.getAccess());
+
+	{
+		const tcu::PixelFormat		pixelFormat		= m_context.getRenderTarget().getPixelFormat();
+		tcu::LodPrecision			lodPrecision;
+		tcu::TexComparePrecision	texComparePrecision;
+
+		lodPrecision.derivateBits			= 18;
+		lodPrecision.lodBits				= 6;
+		texComparePrecision.coordBits		= tcu::IVec3(20,20,20);
+		texComparePrecision.uvwBits			= tcu::IVec3(7,7,7);
+		texComparePrecision.pcfBits			= 5;
+		texComparePrecision.referenceBits	= 16;
+		texComparePrecision.resultBits		= pixelFormat.redBits-1;
+
+		const bool isHighQuality = verifyTexCompareResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+														  &texCoord[0], sampleParams, texComparePrecision, lodPrecision, pixelFormat);
+
+		if (!isHighQuality)
+		{
+			m_testCtx.getLog() << TestLog::Message << "Warning: Verification assuming high-quality PCF filtering failed." << TestLog::EndMessage;
+
+			lodPrecision.lodBits			= 4;
+			texComparePrecision.uvwBits		= tcu::IVec3(4,4,4);
+			texComparePrecision.pcfBits		= 0;
+
+			const bool isOk = verifyTexCompareResult(m_testCtx, rendered.getAccess(), curCase.texture->getRefTexture(),
+													 &texCoord[0], sampleParams, texComparePrecision, lodPrecision, pixelFormat);
+
+			if (!isOk)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: Verification against low precision requirements failed, failing test case." << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+			}
+			else if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Low-quality result");
+		}
+	}
+
+	m_caseNdx += 1;
+	return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
+}
+
+TextureShadowTests::TextureShadowTests (Context& context)
+	: TestCaseGroup(context, "shadow", "Shadow texture lookup tests")
+{
+}
+
+TextureShadowTests::~TextureShadowTests (void)
+{
+}
+
+void TextureShadowTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		deUint32		format;
+	} formats[] =
+	{
+		{ "depth_component16",	GL_DEPTH_COMPONENT16	},
+		{ "depth_component32f",	GL_DEPTH_COMPONENT32F	},
+		{ "depth24_stencil8",	GL_DEPTH24_STENCIL8		}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		minFilter;
+		deUint32		magFilter;
+	} filters[] =
+	{
+		{ "nearest",				GL_NEAREST,					GL_NEAREST	},
+		{ "linear",					GL_LINEAR,					GL_LINEAR	},
+		{ "nearest_mipmap_nearest",	GL_NEAREST_MIPMAP_NEAREST,	GL_LINEAR	},
+		{ "linear_mipmap_nearest",	GL_LINEAR_MIPMAP_NEAREST,	GL_LINEAR	},
+		{ "nearest_mipmap_linear",	GL_NEAREST_MIPMAP_LINEAR,	GL_LINEAR	},
+		{ "linear_mipmap_linear",	GL_LINEAR_MIPMAP_LINEAR,	GL_LINEAR	}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		func;
+	} compareFuncs[] =
+	{
+		{ "less_or_equal",		GL_LEQUAL	},
+		{ "greater_or_equal",	GL_GEQUAL	},
+		{ "less",				GL_LESS		},
+		{ "greater",			GL_GREATER	},
+		{ "equal",				GL_EQUAL	},
+		{ "not_equal",			GL_NOTEQUAL	},
+		{ "always",				GL_ALWAYS	},
+		{ "never",				GL_NEVER	}
+	};
+
+	// 2D cases.
+	{
+		tcu::TestCaseGroup* group2D = new tcu::TestCaseGroup(m_testCtx, "2d", "2D texture shadow lookup tests");
+		addChild(group2D);
+
+		for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(filters); filterNdx++)
+		{
+			tcu::TestCaseGroup* filterGroup = new tcu::TestCaseGroup(m_testCtx, filters[filterNdx].name, "");
+			group2D->addChild(filterGroup);
+
+			for (int compareNdx = 0; compareNdx < DE_LENGTH_OF_ARRAY(compareFuncs); compareNdx++)
+			{
+				for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
+				{
+					deUint32		minFilter		= filters[filterNdx].minFilter;
+					deUint32		magFilter		= filters[filterNdx].magFilter;
+					deUint32		format			= formats[formatNdx].format;
+					deUint32		compareFunc		= compareFuncs[compareNdx].func;
+					const deUint32	wrapS			= GL_REPEAT;
+					const deUint32	wrapT			= GL_REPEAT;
+					const int		width			= 32;
+					const int		height			= 64;
+					string			name			= string(compareFuncs[compareNdx].name) + "_" + formats[formatNdx].name;
+
+					filterGroup->addChild(new Texture2DShadowCase(m_context, name.c_str(), "", minFilter, magFilter, wrapS, wrapT, format, width, height, compareFunc));
+				}
+			}
+		}
+	}
+
+	// Cubemap cases.
+	{
+		tcu::TestCaseGroup* groupCube = new tcu::TestCaseGroup(m_testCtx, "cube", "Cube map texture shadow lookup tests");
+		addChild(groupCube);
+
+		for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(filters); filterNdx++)
+		{
+			tcu::TestCaseGroup* filterGroup = new tcu::TestCaseGroup(m_testCtx, filters[filterNdx].name, "");
+			groupCube->addChild(filterGroup);
+
+			for (int compareNdx = 0; compareNdx < DE_LENGTH_OF_ARRAY(compareFuncs); compareNdx++)
+			{
+				for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
+				{
+					deUint32		minFilter		= filters[filterNdx].minFilter;
+					deUint32		magFilter		= filters[filterNdx].magFilter;
+					deUint32		format			= formats[formatNdx].format;
+					deUint32		compareFunc		= compareFuncs[compareNdx].func;
+					const deUint32	wrapS			= GL_REPEAT;
+					const deUint32	wrapT			= GL_REPEAT;
+					const int		size			= 32;
+					string			name			= string(compareFuncs[compareNdx].name) + "_" + formats[formatNdx].name;
+
+					filterGroup->addChild(new TextureCubeShadowCase(m_context, name.c_str(), "", minFilter, magFilter, wrapS, wrapT, format, size, compareFunc));
+				}
+			}
+		}
+	}
+
+	// 2D array cases.
+	{
+		tcu::TestCaseGroup* group2DArray = new tcu::TestCaseGroup(m_testCtx, "2d_array", "2D texture array shadow lookup tests");
+		addChild(group2DArray);
+
+		for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(filters); filterNdx++)
+		{
+			tcu::TestCaseGroup* filterGroup = new tcu::TestCaseGroup(m_testCtx, filters[filterNdx].name, "");
+			group2DArray->addChild(filterGroup);
+
+			for (int compareNdx = 0; compareNdx < DE_LENGTH_OF_ARRAY(compareFuncs); compareNdx++)
+			{
+				for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
+				{
+					deUint32		minFilter		= filters[filterNdx].minFilter;
+					deUint32		magFilter		= filters[filterNdx].magFilter;
+					deUint32		format			= formats[formatNdx].format;
+					deUint32		compareFunc		= compareFuncs[compareNdx].func;
+					const deUint32	wrapS			= GL_REPEAT;
+					const deUint32	wrapT			= GL_REPEAT;
+					const int		width			= 32;
+					const int		height			= 64;
+					const int		numLayers		= 8;
+					string			name			= string(compareFuncs[compareNdx].name) + "_" + formats[formatNdx].name;
+
+					filterGroup->addChild(new Texture2DArrayShadowCase(m_context, name.c_str(), "", minFilter, magFilter, wrapS, wrapT, format, width, height, numLayers, compareFunc));
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTextureShadowTests.hpp b/modules/gles3/functional/es3fTextureShadowTests.hpp
new file mode 100644
index 0000000..0293491
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureShadowTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FTEXTURESHADOWTESTS_HPP
+#define _ES3FTEXTURESHADOWTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shadow texture lookup tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class TextureShadowTests : public TestCaseGroup
+{
+public:
+							TextureShadowTests		(Context& context);
+							~TextureShadowTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureShadowTests		(const TextureShadowTests& other);
+	TextureShadowTests&		operator=				(const TextureShadowTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTEXTURESHADOWTESTS_HPP
diff --git a/modules/gles3/functional/es3fTextureSizeTests.cpp b/modules/gles3/functional/es3fTextureSizeTests.cpp
new file mode 100644
index 0000000..13eac43
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureSizeTests.cpp
@@ -0,0 +1,422 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture size tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTextureSizeTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using tcu::Sampler;
+using namespace glu;
+using namespace gls::TextureTestUtil;
+
+class Texture2DSizeCase : public tcu::TestCase
+{
+public:
+							Texture2DSizeCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps);
+							~Texture2DSizeCase		(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							Texture2DSizeCase		(const Texture2DSizeCase& other);
+	Texture2DSizeCase&		operator=				(const Texture2DSizeCase& other);
+
+	glu::RenderContext&		m_renderCtx;
+
+	deUint32				m_format;
+	deUint32				m_dataType;
+	int						m_width;
+	int						m_height;
+	bool					m_useMipmaps;
+
+	glu::Texture2D*			m_texture;
+	TextureRenderer			m_renderer;
+};
+
+Texture2DSizeCase::Texture2DSizeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(format)
+	, m_dataType	(dataType)
+	, m_width		(width)
+	, m_height		(height)
+	, m_useMipmaps	(mipmaps)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+Texture2DSizeCase::~Texture2DSizeCase (void)
+{
+	Texture2DSizeCase::deinit();
+}
+
+void Texture2DSizeCase::init (void)
+{
+	DE_ASSERT(!m_texture);
+	m_texture = new Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+
+	int numLevels = m_useMipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;
+
+	// Fill levels.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		m_texture->getRefTexture().allocLevel(levelNdx);
+		tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(levelNdx), tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f));
+	}
+}
+
+void Texture2DSizeCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Texture2DSizeCase::IterateResult Texture2DSizeCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	TestLog&				log				= m_testCtx.getLog();
+	RandomViewport			viewport		(m_renderCtx.getRenderTarget(), 128, 128, deStringHash(getName()));
+	tcu::Surface			renderedFrame	(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame	(viewport.width, viewport.height);
+	tcu::RGBA				threshold		= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(7,7,7,7);
+	deUint32				wrapS			= GL_CLAMP_TO_EDGE;
+	deUint32				wrapT			= GL_CLAMP_TO_EDGE;
+	deUint32				minFilter		= m_useMipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST;
+	deUint32				magFilter		= GL_NEAREST;
+	vector<float>			texCoord;
+
+	computeQuadTexCoord2D(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_2D);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], ReferenceParams(TEXTURETYPE_2D, mapGLSampler(wrapS, wrapT, minFilter, magFilter)));
+
+	// Compare and log.
+	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+class TextureCubeSizeCase : public tcu::TestCase
+{
+public:
+							TextureCubeSizeCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps);
+							~TextureCubeSizeCase	(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							TextureCubeSizeCase		(const TextureCubeSizeCase& other);
+	TextureCubeSizeCase&	operator=				(const TextureCubeSizeCase& other);
+
+	bool					testFace				(tcu::CubeFace face);
+
+	glu::RenderContext&		m_renderCtx;
+
+	deUint32				m_format;
+	deUint32				m_dataType;
+	int						m_width;
+	int						m_height;
+	bool					m_useMipmaps;
+
+	glu::TextureCube*		m_texture;
+	TextureRenderer			m_renderer;
+
+	int						m_curFace;
+	bool					m_isOk;
+};
+
+TextureCubeSizeCase::TextureCubeSizeCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 format, deUint32 dataType, int width, int height, bool mipmaps)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(format)
+	, m_dataType	(dataType)
+	, m_width		(width)
+	, m_height		(height)
+	, m_useMipmaps	(mipmaps)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_MEDIUMP)
+	, m_curFace		(0)
+	, m_isOk		(false)
+{
+}
+
+TextureCubeSizeCase::~TextureCubeSizeCase (void)
+{
+	TextureCubeSizeCase::deinit();
+}
+
+void TextureCubeSizeCase::init (void)
+{
+	DE_ASSERT(!m_texture);
+	DE_ASSERT(m_width == m_height);
+	m_texture = new TextureCube(m_renderCtx, m_format, m_dataType, m_width);
+
+	static const tcu::Vec4 gradients[tcu::CUBEFACE_LAST][2] =
+	{
+		{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
+		{ tcu::Vec4( 0.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
+		{ tcu::Vec4(-1.0f,  0.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
+		{ tcu::Vec4(-1.0f, -1.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
+		{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
+		{ tcu::Vec4( 0.0f,  0.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
+	};
+
+	int numLevels = m_useMipmaps ? deLog2Floor32(de::max(m_width, m_height))+1 : 1;
+
+	// Fill levels.
+	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+	{
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			m_texture->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+			fillWithComponentGradients(m_texture->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0], gradients[face][1]);
+		}
+	}
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Initialize iteration state.
+	m_curFace	= 0;
+	m_isOk		= true;
+}
+
+void TextureCubeSizeCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+bool TextureCubeSizeCase::testFace (tcu::CubeFace face)
+{
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	TestLog&				log				= m_testCtx.getLog();
+	RandomViewport			viewport		(m_renderCtx.getRenderTarget(), 128, 128, deStringHash(getName())+(deUint32)face);
+	tcu::Surface			renderedFrame	(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame	(viewport.width, viewport.height);
+	tcu::RGBA				threshold		= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(7,7,7,7);
+	deUint32				wrapS			= GL_CLAMP_TO_EDGE;
+	deUint32				wrapT			= GL_CLAMP_TO_EDGE;
+	deUint32				minFilter		= m_useMipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST;
+	deUint32				magFilter		= GL_NEAREST;
+	vector<float>			texCoord;
+
+	computeQuadTexCoordCube(texCoord, face);
+
+	// \todo [2011-10-28 pyry] Image set name / section?
+	log << TestLog::Message << face << TestLog::EndMessage;
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture->getGLTexture());
+
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, wrapS);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, wrapT);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, minFilter);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	m_renderer.renderQuad(0, &texCoord[0], TEXTURETYPE_CUBE);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compute reference.
+	Sampler sampler = mapGLSampler(wrapS, wrapT, minFilter, magFilter);
+	sampler.seamlessCubeMap = true;
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], ReferenceParams(TEXTURETYPE_CUBE, sampler));
+
+	// Compare and log.
+	return compareImages(log, referenceFrame, renderedFrame, threshold);
+}
+
+TextureCubeSizeCase::IterateResult TextureCubeSizeCase::iterate (void)
+{
+	// Execute test for all faces.
+	if (!testFace((tcu::CubeFace)m_curFace))
+		m_isOk = false;
+
+	m_curFace += 1;
+
+	if (m_curFace == tcu::CUBEFACE_LAST)
+	{
+		m_testCtx.setTestResult(m_isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								m_isOk ? "Pass"					: "Image comparison failed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+TextureSizeTests::TextureSizeTests (Context& context)
+	: TestCaseGroup(context, "size", "Texture Size Tests")
+{
+}
+
+TextureSizeTests::~TextureSizeTests (void)
+{
+}
+
+void TextureSizeTests::init (void)
+{
+	struct
+	{
+		int	width;
+		int	height;
+	} sizes2D[] =
+	{
+		{   64,   64 }, // Spec-mandated minimum.
+		{   65,   63 },
+		{  512,  512 },
+		{ 1024, 1024 },
+		{ 2048, 2048 }
+	};
+
+	struct
+	{
+		int	width;
+		int	height;
+	} sizesCube[] =
+	{
+		{  15,  15 },
+		{  16,  16 }, // Spec-mandated minimum
+		{  64,  64 },
+		{ 128, 128 },
+		{ 256, 256 },
+		{ 512, 512 }
+	};
+
+	struct
+	{
+		const char*	name;
+		deUint32	format;
+		deUint32	dataType;
+	} formats[] =
+	{
+		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	// 2D cases.
+	tcu::TestCaseGroup* group2D = new tcu::TestCaseGroup(m_testCtx, "2d", "2D Texture Size Tests");
+	addChild(group2D);
+	for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizes2D); sizeNdx++)
+	{
+		int		width	= sizes2D[sizeNdx].width;
+		int		height	= sizes2D[sizeNdx].height;
+		bool	isPOT	= deIsPowerOfTwo32(width) && deIsPowerOfTwo32(height);
+
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
+		{
+			for (int mipmap = 0; mipmap < (isPOT ? 2 : 1); mipmap++)
+			{
+				std::ostringstream name;
+				name << width << "x" << height << "_" << formats[formatNdx].name << (mipmap ? "_mipmap" : "");
+
+				group2D->addChild(new Texture2DSizeCase(m_testCtx, m_context.getRenderContext(), name.str().c_str(), "",
+														formats[formatNdx].format, formats[formatNdx].dataType,
+														width, height, mipmap != 0));
+			}
+		}
+	}
+
+	// Cubemap cases.
+	tcu::TestCaseGroup* groupCube = new tcu::TestCaseGroup(m_testCtx, "cube", "Cubemap Texture Size Tests");
+	addChild(groupCube);
+	for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizesCube); sizeNdx++)
+	{
+		int		width	= sizesCube[sizeNdx].width;
+		int		height	= sizesCube[sizeNdx].height;
+		bool	isPOT	= deIsPowerOfTwo32(width) && deIsPowerOfTwo32(height);
+
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
+		{
+			for (int mipmap = 0; mipmap < (isPOT ? 2 : 1); mipmap++)
+			{
+				std::ostringstream name;
+				name << width << "x" << height << "_" << formats[formatNdx].name << (mipmap ? "_mipmap" : "");
+
+				groupCube->addChild(new TextureCubeSizeCase(m_testCtx, m_context.getRenderContext(), name.str().c_str(), "",
+															formats[formatNdx].format, formats[formatNdx].dataType,
+															width, height, mipmap != 0));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTextureSizeTests.hpp b/modules/gles3/functional/es3fTextureSizeTests.hpp
new file mode 100644
index 0000000..fc45e23
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureSizeTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FTEXTURESIZETESTS_HPP
+#define _ES3FTEXTURESIZETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture size tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class TextureSizeTests : public TestCaseGroup
+{
+public:
+							TextureSizeTests		(Context& context);
+							~TextureSizeTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureSizeTests		(const TextureSizeTests& other);
+	TextureSizeTests&		operator=				(const TextureSizeTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTEXTURESIZETESTS_HPP
diff --git a/modules/gles3/functional/es3fTextureSpecificationTests.cpp b/modules/gles3/functional/es3fTextureSpecificationTests.cpp
new file mode 100644
index 0000000..b42d4d5
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureSpecificationTests.cpp
@@ -0,0 +1,4532 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture specification tests.
+ *
+ * \todo [pyry] Following tests are missing:
+ *  - Specify mipmap incomplete texture, use without mipmaps, re-specify
+ *    as complete and render.
+ *  - Randomly re-specify levels to eventually reach mipmap-complete texture.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTextureSpecificationTests.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVectorUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "sglrContextUtil.hpp"
+#include "sglrContextWrapper.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+// \todo [2012-04-29 pyry] Should be named SglrUtil
+#include "es3fFboTestUtil.hpp"
+
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using std::pair;
+using tcu::TestLog;
+using tcu::Vec4;
+using tcu::IVec4;
+using tcu::UVec4;
+using namespace FboTestUtil;
+
+tcu::TextureFormat mapGLUnsizedInternalFormat (deUint32 internalFormat)
+{
+	using tcu::TextureFormat;
+	switch (internalFormat)
+	{
+		case GL_ALPHA:				return TextureFormat(TextureFormat::A,		TextureFormat::UNORM_INT8);
+		case GL_LUMINANCE:			return TextureFormat(TextureFormat::L,		TextureFormat::UNORM_INT8);
+		case GL_LUMINANCE_ALPHA:	return TextureFormat(TextureFormat::LA,		TextureFormat::UNORM_INT8);
+		case GL_RGB:				return TextureFormat(TextureFormat::RGB,	TextureFormat::UNORM_INT8);
+		case GL_RGBA:				return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8);
+		default:
+			throw tcu::InternalError(string("Can't map GL unsized internal format (") + tcu::toHex(internalFormat).toString() + ") to texture format");
+	}
+}
+
+enum
+{
+	VIEWPORT_WIDTH	= 256,
+	VIEWPORT_HEIGHT	= 256
+};
+
+static inline int maxLevelCount (int width, int height)
+{
+	return (int)deLog2Floor32(de::max(width, height))+1;
+}
+
+static inline int maxLevelCount (int width, int height, int depth)
+{
+	return (int)deLog2Floor32(de::max(width, de::max(height, depth)))+1;
+}
+
+template <int Size>
+static tcu::Vector<float, Size> randomVector (de::Random& rnd, const tcu::Vector<float, Size>& minVal = tcu::Vector<float, Size>(0.0f), const tcu::Vector<float, Size>& maxVal = tcu::Vector<float, Size>(1.0f))
+{
+	tcu::Vector<float, Size> res;
+	for (int ndx = 0; ndx < Size; ndx++)
+		res[ndx] = rnd.getFloat(minVal[ndx], maxVal[ndx]);
+	return res;
+}
+
+static const deUint32 s_cubeMapFaces[] =
+{
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+};
+
+static tcu::IVec4 getPixelFormatCompareDepth (const tcu::PixelFormat& pixelFormat, tcu::TextureFormat textureFormat)
+{
+	switch (textureFormat.order)
+	{
+		case tcu::TextureFormat::L:
+		case tcu::TextureFormat::LA:
+			return tcu::IVec4(pixelFormat.redBits, pixelFormat.redBits, pixelFormat.redBits, pixelFormat.alphaBits);
+		default:
+			return tcu::IVec4(pixelFormat.redBits, pixelFormat.greenBits, pixelFormat.blueBits, pixelFormat.alphaBits);
+	}
+}
+
+static tcu::UVec4 computeCompareThreshold (const tcu::PixelFormat& pixelFormat, tcu::TextureFormat textureFormat)
+{
+	const IVec4		texFormatBits		= tcu::getTextureFormatBitDepth(textureFormat);
+	const IVec4		pixelFormatBits		= getPixelFormatCompareDepth(pixelFormat, textureFormat);
+	const IVec4		accurateFmtBits		= min(pixelFormatBits, texFormatBits);
+	const IVec4		compareBits			= select(accurateFmtBits, IVec4(8), greaterThan(accurateFmtBits, IVec4(0))) - 1;
+
+	return (IVec4(1) << (8-compareBits)).asUint();
+}
+
+class TextureSpecCase : public TestCase, public sglr::ContextWrapper
+{
+public:
+							TextureSpecCase		(Context& context, const char* name, const char* desc);
+							~TextureSpecCase	(void);
+
+	IterateResult			iterate				(void);
+
+protected:
+	virtual void			createTexture		(void)																	= DE_NULL;
+	virtual void			verifyTexture		(sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext)	= DE_NULL;
+
+	// Utilities.
+	void					renderTex			(tcu::Surface& dst, deUint32 program, int width, int height);
+	void					readPixels			(tcu::Surface& dst, int x, int y, int width, int height);
+
+private:
+							TextureSpecCase		(const TextureSpecCase& other);
+	TextureSpecCase&		operator=			(const TextureSpecCase& other);
+};
+
+TextureSpecCase::TextureSpecCase (Context& context, const char* name, const char* desc)
+	: TestCase(context, name, desc)
+{
+}
+
+TextureSpecCase::~TextureSpecCase (void)
+{
+}
+
+TextureSpecCase::IterateResult TextureSpecCase::iterate (void)
+{
+	glu::RenderContext&			renderCtx				= TestCase::m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget			= renderCtx.getRenderTarget();
+	tcu::TestLog&				log						= m_testCtx.getLog();
+
+	if (renderTarget.getWidth() < VIEWPORT_WIDTH || renderTarget.getHeight() < VIEWPORT_HEIGHT)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	// Context size, and viewport for GLES3
+	de::Random		rnd			(deStringHash(getName()));
+	int				width		= deMin32(renderTarget.getWidth(),	VIEWPORT_WIDTH);
+	int				height		= deMin32(renderTarget.getHeight(),	VIEWPORT_HEIGHT);
+	int				x			= rnd.getInt(0, renderTarget.getWidth()		- width);
+	int				y			= rnd.getInt(0, renderTarget.getHeight()	- height);
+
+	// Contexts.
+	sglr::GLContext					gles3Context	(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(x, y, width, height));
+	sglr::ReferenceContextBuffers	refBuffers		(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), 0 /* depth */, 0 /* stencil */, width, height);
+	sglr::ReferenceContext			refContext		(sglr::ReferenceContextLimits(renderCtx), refBuffers.getColorbuffer(), refBuffers.getDepthbuffer(), refBuffers.getStencilbuffer());
+
+	// Clear color buffer.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles3Context);
+		glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
+		glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	}
+
+	// Construct texture using both GLES3 and reference contexts.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles3Context);
+		createTexture();
+		TCU_CHECK(glGetError() == GL_NO_ERROR);
+	}
+
+	// Initialize case result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Disable logging.
+	gles3Context.enableLogging(0);
+
+	// Verify results.
+	verifyTexture(gles3Context, refContext);
+
+	return STOP;
+}
+
+void TextureSpecCase::renderTex (tcu::Surface& dst, deUint32 program, int width, int height)
+{
+	int		targetW		= getWidth();
+	int		targetH		= getHeight();
+
+	float	w			= (float)width	/ (float)targetW;
+	float	h			= (float)height	/ (float)targetH;
+
+	sglr::drawQuad(*getCurrentContext(), program, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(-1.0f + w*2.0f, -1.0f + h*2.0f, 0.0f));
+
+	// Read pixels back.
+	readPixels(dst, 0, 0, width, height);
+}
+
+void TextureSpecCase::readPixels (tcu::Surface& dst, int x, int y, int width, int height)
+{
+	dst.setSize(width, height);
+	glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr());
+}
+
+class Texture2DSpecCase : public TextureSpecCase
+{
+public:
+							Texture2DSpecCase	(Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int width, int height, int numLevels);
+							~Texture2DSpecCase	(void);
+
+protected:
+	virtual void			verifyTexture		(sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext);
+
+	tcu::TextureFormat		m_texFormat;
+	tcu::TextureFormatInfo	m_texFormatInfo;
+	int						m_width;
+	int						m_height;
+	int						m_numLevels;
+};
+
+Texture2DSpecCase::Texture2DSpecCase (Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int width, int height, int numLevels)
+	: TextureSpecCase		(context, name, desc)
+	, m_texFormat			(format)
+	, m_texFormatInfo		(tcu::getTextureFormatInfo(format))
+	, m_width				(width)
+	, m_height				(height)
+	, m_numLevels			(numLevels)
+{
+}
+
+Texture2DSpecCase::~Texture2DSpecCase (void)
+{
+}
+
+void Texture2DSpecCase::verifyTexture (sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext)
+{
+	Texture2DShader shader			(DataTypes() << glu::getSampler2DType(m_texFormat), glu::TYPE_FLOAT_VEC4);
+	deUint32		shaderIDgles	= gles3Context.createProgram(&shader);
+	deUint32		shaderIDRef		= refContext.createProgram(&shader);
+
+	shader.setTexScaleBias(0, m_texFormatInfo.lookupScale, m_texFormatInfo.lookupBias);
+
+	// Set state.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		sglr::Context* ctx = ndx ? static_cast<sglr::Context*>(&refContext) : static_cast<sglr::Context*>(&gles3Context);
+
+		setContext(ctx);
+
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL,	m_numLevels-1);
+	}
+
+	for (int levelNdx = 0; levelNdx < m_numLevels; levelNdx++)
+	{
+		int				levelW		= de::max(1, m_width >> levelNdx);
+		int				levelH		= de::max(1, m_height >> levelNdx);
+		tcu::Surface	reference;
+		tcu::Surface	result;
+
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			tcu::Surface&	dst			= ndx ? reference									: result;
+			sglr::Context*	ctx			= ndx ? static_cast<sglr::Context*>(&refContext)	: static_cast<sglr::Context*>(&gles3Context);
+			deUint32		shaderID	= ndx ? shaderIDRef									: shaderIDgles;
+
+			setContext(ctx);
+			shader.setUniforms(*ctx, shaderID);
+			renderTex(dst, shaderID, levelW, levelH);
+		}
+
+		UVec4			threshold	= computeCompareThreshold(m_context.getRenderTarget().getPixelFormat(), m_texFormat);
+		string			levelStr	= de::toString(levelNdx);
+		string			name		= string("Level") + levelStr;
+		string			desc		= string("Level ") + levelStr;
+		bool			isOk		= tcu::intThresholdCompare(m_testCtx.getLog(), name.c_str(), desc.c_str(), reference.getAccess(), result.getAccess(), threshold,
+															   levelNdx == 0 ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);
+
+		if (!isOk)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+			break;
+		}
+	}
+}
+
+class TextureCubeSpecCase : public TextureSpecCase
+{
+public:
+							TextureCubeSpecCase		(Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int size, int numLevels);
+							~TextureCubeSpecCase	(void);
+
+protected:
+	virtual void			verifyTexture			(sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext);
+
+	tcu::TextureFormat		m_texFormat;
+	tcu::TextureFormatInfo	m_texFormatInfo;
+	int						m_size;
+	int						m_numLevels;
+};
+
+TextureCubeSpecCase::TextureCubeSpecCase (Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int size, int numLevels)
+	: TextureSpecCase		(context, name, desc)
+	, m_texFormat			(format)
+	, m_texFormatInfo		(tcu::getTextureFormatInfo(format))
+	, m_size				(size)
+	, m_numLevels			(numLevels)
+{
+}
+
+TextureCubeSpecCase::~TextureCubeSpecCase (void)
+{
+}
+
+void TextureCubeSpecCase::verifyTexture (sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext)
+{
+	TextureCubeShader	shader			(glu::getSamplerCubeType(m_texFormat), glu::TYPE_FLOAT_VEC4);
+	deUint32			shaderIDgles	= gles3Context.createProgram(&shader);
+	deUint32			shaderIDRef		= refContext.createProgram(&shader);
+
+	shader.setTexScaleBias(m_texFormatInfo.lookupScale, m_texFormatInfo.lookupBias);
+
+	// Set state.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		sglr::Context* ctx = ndx ? static_cast<sglr::Context*>(&refContext) : static_cast<sglr::Context*>(&gles3Context);
+
+		setContext(ctx);
+
+		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL,	m_numLevels-1);
+	}
+
+	for (int levelNdx = 0; levelNdx < m_numLevels; levelNdx++)
+	{
+		int		levelSize	= de::max(1, m_size >> levelNdx);
+		bool	isOk		= true;
+
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			tcu::Surface	reference;
+			tcu::Surface	result;
+
+			if (levelSize <= 2)
+				continue; // Fuzzy compare doesn't work for images this small.
+
+			shader.setFace((tcu::CubeFace)face);
+
+			for (int ndx = 0; ndx < 2; ndx++)
+			{
+				tcu::Surface&	dst			= ndx ? reference									: result;
+				sglr::Context*	ctx			= ndx ? static_cast<sglr::Context*>(&refContext)	: static_cast<sglr::Context*>(&gles3Context);
+				deUint32		shaderID	= ndx ? shaderIDRef									: shaderIDgles;
+
+				setContext(ctx);
+				shader.setUniforms(*ctx, shaderID);
+				renderTex(dst, shaderID, levelSize, levelSize);
+			}
+
+			const float		threshold	= 0.02f;
+			string			faceStr		= de::toString((tcu::CubeFace)face);
+			string			levelStr	= de::toString(levelNdx);
+			string			name		= string("Level") + levelStr;
+			string			desc		= string("Level ") + levelStr + ", face " + faceStr;
+			bool			isFaceOk	= tcu::fuzzyCompare(m_testCtx.getLog(), name.c_str(), desc.c_str(), reference, result, threshold,
+															levelNdx == 0 ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);
+
+			if (!isFaceOk)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+				isOk = false;
+				break;
+			}
+		}
+
+		if (!isOk)
+			break;
+	}
+}
+
+class Texture2DArraySpecCase : public TextureSpecCase
+{
+public:
+							Texture2DArraySpecCase	(Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int width, int height, int numLayers, int numLevels);
+							~Texture2DArraySpecCase	(void);
+
+protected:
+	virtual void			verifyTexture			(sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext);
+
+	tcu::TextureFormat		m_texFormat;
+	tcu::TextureFormatInfo	m_texFormatInfo;
+	int						m_width;
+	int						m_height;
+	int						m_numLayers;
+	int						m_numLevels;
+};
+
+Texture2DArraySpecCase::Texture2DArraySpecCase (Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int width, int height, int numLayers, int numLevels)
+	: TextureSpecCase		(context, name, desc)
+	, m_texFormat			(format)
+	, m_texFormatInfo		(tcu::getTextureFormatInfo(format))
+	, m_width				(width)
+	, m_height				(height)
+	, m_numLayers			(numLayers)
+	, m_numLevels			(numLevels)
+{
+}
+
+Texture2DArraySpecCase::~Texture2DArraySpecCase (void)
+{
+}
+
+void Texture2DArraySpecCase::verifyTexture (sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext)
+{
+	Texture2DArrayShader	shader			(glu::getSampler2DArrayType(m_texFormat), glu::TYPE_FLOAT_VEC4);
+	deUint32				shaderIDgles	= gles3Context.createProgram(&shader);
+	deUint32				shaderIDRef		= refContext.createProgram(&shader);
+
+	shader.setTexScaleBias(m_texFormatInfo.lookupScale, m_texFormatInfo.lookupBias);
+
+	// Set state.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		sglr::Context* ctx = ndx ? static_cast<sglr::Context*>(&refContext) : static_cast<sglr::Context*>(&gles3Context);
+
+		setContext(ctx);
+
+		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_R,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL,	m_numLevels-1);
+	}
+
+	for (int layerNdx = 0; layerNdx < m_numLayers; layerNdx++)
+	{
+		bool layerOk = true;
+
+		shader.setLayer(layerNdx);
+
+		for (int levelNdx = 0; levelNdx < m_numLevels; levelNdx++)
+		{
+			int				levelW		= de::max(1, m_width	>> levelNdx);
+			int				levelH		= de::max(1, m_height	>> levelNdx);
+			tcu::Surface	reference;
+			tcu::Surface	result;
+
+			for (int ndx = 0; ndx < 2; ndx++)
+			{
+				tcu::Surface&	dst			= ndx ? reference									: result;
+				sglr::Context*	ctx			= ndx ? static_cast<sglr::Context*>(&refContext)	: static_cast<sglr::Context*>(&gles3Context);
+				deUint32		shaderID	= ndx ? shaderIDRef									: shaderIDgles;
+
+				setContext(ctx);
+				shader.setUniforms(*ctx, shaderID);
+				renderTex(dst, shaderID, levelW, levelH);
+			}
+
+			UVec4			threshold	= computeCompareThreshold(m_context.getRenderTarget().getPixelFormat(), m_texFormat);
+			string			levelStr	= de::toString(levelNdx);
+			string			layerStr	= de::toString(layerNdx);
+			string			name		= string("Layer") + layerStr + "Level" + levelStr;
+			string			desc		= string("Layer ") + layerStr + ", Level " + levelStr;
+			bool			depthOk		= tcu::intThresholdCompare(m_testCtx.getLog(), name.c_str(), desc.c_str(), reference.getAccess(), result.getAccess(), threshold,
+																   (levelNdx == 0 && layerNdx == 0) ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);
+
+			if (!depthOk)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+				layerOk = false;
+				break;
+			}
+		}
+
+		if (!layerOk)
+			break;
+	}
+}
+
+class Texture3DSpecCase : public TextureSpecCase
+{
+public:
+							Texture3DSpecCase	(Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int width, int height, int depth, int numLevels);
+							~Texture3DSpecCase	(void);
+
+protected:
+	virtual void			verifyTexture		(sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext);
+
+	tcu::TextureFormat		m_texFormat;
+	tcu::TextureFormatInfo	m_texFormatInfo;
+	int						m_width;
+	int						m_height;
+	int						m_depth;
+	int						m_numLevels;
+};
+
+Texture3DSpecCase::Texture3DSpecCase (Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int width, int height, int depth, int numLevels)
+	: TextureSpecCase		(context, name, desc)
+	, m_texFormat			(format)
+	, m_texFormatInfo		(tcu::getTextureFormatInfo(format))
+	, m_width				(width)
+	, m_height				(height)
+	, m_depth				(depth)
+	, m_numLevels			(numLevels)
+{
+}
+
+Texture3DSpecCase::~Texture3DSpecCase (void)
+{
+}
+
+void Texture3DSpecCase::verifyTexture (sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext)
+{
+	Texture3DShader shader			(glu::getSampler3DType(m_texFormat), glu::TYPE_FLOAT_VEC4);
+	deUint32		shaderIDgles	= gles3Context.createProgram(&shader);
+	deUint32		shaderIDRef		= refContext.createProgram(&shader);
+
+	shader.setTexScaleBias(m_texFormatInfo.lookupScale, m_texFormatInfo.lookupBias);
+
+	// Set state.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		sglr::Context* ctx = ndx ? static_cast<sglr::Context*>(&refContext) : static_cast<sglr::Context*>(&gles3Context);
+
+		setContext(ctx);
+
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL,	m_numLevels-1);
+	}
+
+	for (int levelNdx = 0; levelNdx < m_numLevels; levelNdx++)
+	{
+		int		levelW		= de::max(1, m_width	>> levelNdx);
+		int		levelH		= de::max(1, m_height	>> levelNdx);
+		int		levelD		= de::max(1, m_depth	>> levelNdx);
+		bool	levelOk		= true;
+
+		for (int depth = 0; depth < levelD; depth++)
+		{
+			tcu::Surface	reference;
+			tcu::Surface	result;
+
+			shader.setDepth(((float)depth + 0.5f) / (float)levelD);
+
+			for (int ndx = 0; ndx < 2; ndx++)
+			{
+				tcu::Surface&	dst			= ndx ? reference									: result;
+				sglr::Context*	ctx			= ndx ? static_cast<sglr::Context*>(&refContext)	: static_cast<sglr::Context*>(&gles3Context);
+				deUint32		shaderID	= ndx ? shaderIDRef									: shaderIDgles;
+
+				setContext(ctx);
+				shader.setUniforms(*ctx, shaderID);
+				renderTex(dst, shaderID, levelW, levelH);
+			}
+
+			UVec4			threshold	= computeCompareThreshold(m_context.getRenderTarget().getPixelFormat(), m_texFormat);
+			string			levelStr	= de::toString(levelNdx);
+			string			sliceStr	= de::toString(depth);
+			string			name		= string("Level") + levelStr + "Slice" + sliceStr;
+			string			desc		= string("Level ") + levelStr + ", Slice " + sliceStr;
+			bool			depthOk		= tcu::intThresholdCompare(m_testCtx.getLog(), name.c_str(), desc.c_str(), reference.getAccess(), result.getAccess(), threshold,
+																   (levelNdx == 0 && depth == 0) ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);
+
+			if (!depthOk)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+				levelOk = false;
+				break;
+			}
+		}
+
+		if (!levelOk)
+			break;
+	}
+}
+
+// Basic TexImage2D() with 2D texture usage
+class BasicTexImage2DCase : public Texture2DSpecCase
+{
+public:
+	// Unsized internal format.
+	BasicTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), width, height, maxLevelCount(width, height))
+		, m_internalFormat	(format)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+	// Sized internal format.
+	BasicTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, maxLevelCount(width, height))
+		, m_internalFormat	(internalFormat)
+		, m_format			(GL_NONE)
+		, m_dataType		(GL_NONE)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			levelData.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_internalFormat, levelW, levelH, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Basic TexImage2D() with cubemap usage
+class BasicTexImageCubeCase : public TextureCubeSpecCase
+{
+public:
+	// Unsized formats.
+	BasicTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int size)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), size, deLog2Floor32(size)+1)
+		, m_internalFormat		(format)
+		, m_format				(format)
+		, m_dataType			(dataType)
+	{
+	}
+
+	// Sized internal formats.
+	BasicTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, deLog2Floor32(size)+1)
+		, m_internalFormat		(internalFormat)
+		, m_format				(GL_NONE)
+		, m_dataType			(GL_NONE)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int levelSize = de::max(1, m_size >> ndx);
+
+			levelData.setSize(levelSize, levelSize);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				Vec4 gMin = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+				Vec4 gMax = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+				tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+				glTexImage2D(s_cubeMapFaces[face], ndx, m_internalFormat, levelSize, levelSize, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
+			}
+		}
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Basic TexImage3D() with 2D array texture usage
+class BasicTexImage2DArrayCase : public Texture2DArraySpecCase
+{
+public:
+	BasicTexImage2DArrayCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height, int numLayers)
+		: Texture2DArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, numLayers, maxLevelCount(width, height))
+		, m_internalFormat			(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32				tex			= 0;
+		tcu::TextureLevel		levelData	(m_texFormat);
+		de::Random				rnd			(deStringHash(getName()));
+		glu::TransferFormat		transferFmt	= glu::getTransferFormat(m_texFormat);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width	>> ndx);
+			int		levelH		= de::max(1, m_height	>> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			levelData.setSize(levelW, levelH, m_numLayers);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, ndx, m_internalFormat, levelW, levelH, m_numLayers, 0, transferFmt.format, transferFmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Basic TexImage3D() with 3D texture usage
+class BasicTexImage3DCase : public Texture3DSpecCase
+{
+public:
+	BasicTexImage3DCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height, int depth)
+		: Texture3DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, depth, maxLevelCount(width, height, depth))
+		, m_internalFormat	(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32				tex			= 0;
+		tcu::TextureLevel		levelData	(m_texFormat);
+		de::Random				rnd			(deStringHash(getName()));
+		glu::TransferFormat		transferFmt	= glu::getTransferFormat(m_texFormat);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_3D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width	>> ndx);
+			int		levelH		= de::max(1, m_height	>> ndx);
+			int		levelD		= de::max(1, m_depth	>> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			levelData.setSize(levelW, levelH, levelD);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage3D(GL_TEXTURE_3D, ndx, m_internalFormat, levelW, levelH, levelD, 0, transferFmt.format, transferFmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Randomized 2D texture specification using TexImage2D
+class RandomOrderTexImage2DCase : public Texture2DSpecCase
+{
+public:
+	RandomOrderTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), width, height, maxLevelCount(width, height))
+		, m_internalFormat	(format)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+	RandomOrderTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, maxLevelCount(width, height))
+		, m_internalFormat	(internalFormat)
+		, m_format			(GL_NONE)
+		, m_dataType		(GL_NONE)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		vector<int>			levels		(m_numLevels);
+
+		for (int i = 0; i < m_numLevels; i++)
+			levels[i] = i;
+		rnd.shuffle(levels.begin(), levels.end());
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelNdx	= levels[ndx];
+			int		levelW		= de::max(1, m_width	>> levelNdx);
+			int		levelH		= de::max(1, m_height	>> levelNdx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			levelData.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage2D(GL_TEXTURE_2D, levelNdx, m_internalFormat, levelW, levelH, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Randomized cubemap texture specification using TexImage2D
+class RandomOrderTexImageCubeCase : public TextureCubeSpecCase
+{
+public:
+	RandomOrderTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int size)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), size, deLog2Floor32(size)+1)
+		, m_internalFormat		(GL_NONE)
+		, m_format				(format)
+		, m_dataType			(dataType)
+	{
+	}
+
+	RandomOrderTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, deLog2Floor32(size)+1)
+		, m_internalFormat		(internalFormat)
+		, m_format				(GL_NONE)
+		, m_dataType			(GL_NONE)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// Level-face pairs.
+		vector<pair<int, tcu::CubeFace> >	images	(m_numLevels*6);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+				images[ndx*6 + face] = std::make_pair(ndx, (tcu::CubeFace)face);
+
+		rnd.shuffle(images.begin(), images.end());
+
+		for (int ndx = 0; ndx < (int)images.size(); ndx++)
+		{
+			int				levelNdx	= images[ndx].first;
+			tcu::CubeFace	face		= images[ndx].second;
+			int				levelSize	= de::max(1, m_size >> levelNdx);
+			Vec4			gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4			gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			levelData.setSize(levelSize, levelSize);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage2D(s_cubeMapFaces[face], levelNdx, m_internalFormat, levelSize, levelSize, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// TexImage2D() unpack alignment case.
+class TexImage2DAlignCase : public Texture2DSpecCase
+{
+public:
+	TexImage2DAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height, int numLevels, int alignment)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), width, height, numLevels)
+		, m_internalFormat	(format)
+		, m_format			(format)
+		, m_dataType		(dataType)
+		, m_alignment		(alignment)
+	{
+	}
+
+	TexImage2DAlignCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height, int numLevels, int alignment)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, numLevels)
+		, m_internalFormat	(internalFormat)
+		, m_format			(GL_NONE)
+		, m_dataType		(GL_NONE)
+		, m_alignment		(alignment)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		vector<deUint8>		data;
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*(m_texFormatInfo.valueMax-m_texFormatInfo.valueMin) + m_texFormatInfo.valueMin;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*(m_texFormatInfo.valueMax-m_texFormatInfo.valueMin) + m_texFormatInfo.valueMin;
+			int		rowPitch	= deAlign32(levelW*m_texFormat.getPixelSize(), m_alignment);
+			int		cellSize	= de::max(1, de::min(levelW >> 2, levelH >> 2));
+
+			data.resize(rowPitch*levelH);
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, levelW, levelH, 1, rowPitch, 0, &data[0]), cellSize, colorA, colorB);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_internalFormat, levelW, levelH, 0, m_format, m_dataType, &data[0]);
+		}
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+	int			m_alignment;
+};
+
+// TexImage2D() unpack alignment case.
+class TexImageCubeAlignCase : public TextureCubeSpecCase
+{
+public:
+	TexImageCubeAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int size, int numLevels, int alignment)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), size, numLevels)
+		, m_internalFormat		(format)
+		, m_format				(format)
+		, m_dataType			(dataType)
+		, m_alignment			(alignment)
+	{
+	}
+
+	TexImageCubeAlignCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size, int numLevels, int alignment)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, numLevels)
+		, m_internalFormat		(internalFormat)
+		, m_format				(GL_NONE)
+		, m_dataType			(GL_NONE)
+		, m_alignment			(alignment)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		vector<deUint8>		data;
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelSize	= de::max(1, m_size >> ndx);
+			int		rowPitch	= deAlign32(m_texFormat.getPixelSize()*levelSize, m_alignment);
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*(m_texFormatInfo.valueMax-m_texFormatInfo.valueMin) + m_texFormatInfo.valueMin;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*(m_texFormatInfo.valueMax-m_texFormatInfo.valueMin) + m_texFormatInfo.valueMin;
+			int		cellSize	= de::max(1, levelSize >> 2);
+
+			data.resize(rowPitch*levelSize);
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, levelSize, levelSize, 1, rowPitch, 0, &data[0]), cellSize, colorA, colorB);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+				glTexImage2D(s_cubeMapFaces[face], ndx, m_internalFormat, levelSize, levelSize, 0, m_format, m_dataType, &data[0]);
+		}
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+	int			m_alignment;
+};
+
+// TexImage2D() unpack parameters case.
+class TexImage2DParamsCase : public Texture2DSpecCase
+{
+public:
+	TexImage2DParamsCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height, int rowLength, int skipRows, int skipPixels, int alignment)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, 1)
+		, m_internalFormat	(internalFormat)
+		, m_rowLength		(rowLength)
+		, m_skipRows		(skipRows)
+		, m_skipPixels		(skipPixels)
+		, m_alignment		(alignment)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		int						rowLength		= m_rowLength > 0 ? m_rowLength : m_width;
+		int						rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
+		int						height			= m_height + m_skipRows;
+		deUint32				tex				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		// Fill data with grid.
+		data.resize(rowPitch*height);
+		{
+			Vec4	cScale		= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias		= m_texFormatInfo.valueMin;
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, 1, rowPitch, 0, &data[0] + m_skipRows*rowPitch + m_skipPixels*pixelSize), 4, colorA, colorB);
+		}
+
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_width, m_height, 0, transferFmt.format, transferFmt.dataType, &data[0]);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_rowLength;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+};
+
+// TexImage3D() unpack parameters case.
+class TexImage3DParamsCase : public Texture3DSpecCase
+{
+public:
+	TexImage3DParamsCase (Context&		context,
+						   const char*	name,
+						   const char*	desc,
+						   deUint32		internalFormat,
+						   int			width,
+						   int			height,
+						   int			depth,
+						   int			imageHeight,
+						   int			rowLength,
+						   int			skipImages,
+						   int			skipRows,
+						   int			skipPixels,
+						   int			alignment)
+		: Texture3DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, depth, 1)
+		, m_internalFormat	(internalFormat)
+		, m_imageHeight		(imageHeight)
+		, m_rowLength		(rowLength)
+		, m_skipImages		(skipImages)
+		, m_skipRows		(skipRows)
+		, m_skipPixels		(skipPixels)
+		, m_alignment		(alignment)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		int						rowLength		= m_rowLength > 0 ? m_rowLength : m_width;
+		int						rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
+		int						imageHeight		= m_imageHeight > 0 ? m_imageHeight : m_height;
+		int						slicePitch		= imageHeight*rowPitch;
+		deUint32				tex				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		// Fill data with grid.
+		data.resize(slicePitch*(m_depth+m_skipImages));
+		{
+			Vec4	cScale		= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias		= m_texFormatInfo.valueMin;
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, m_depth, rowPitch, slicePitch, &data[0] + m_skipImages*slicePitch + m_skipRows*rowPitch + m_skipPixels*pixelSize), 4, colorA, colorB);
+		}
+
+		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	m_imageHeight);
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	m_skipImages);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_3D, tex);
+		glTexImage3D(GL_TEXTURE_3D, 0, m_internalFormat, m_width, m_height, m_depth, 0, transferFmt.format, transferFmt.dataType, &data[0]);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_imageHeight;
+	int			m_rowLength;
+	int			m_skipImages;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+};
+
+// Basic TexSubImage2D() with 2D texture usage
+class BasicTexSubImage2DCase : public Texture2DSpecCase
+{
+public:
+	BasicTexSubImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), width, height, maxLevelCount(width, height))
+		, m_internalFormat	(format)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+	BasicTexSubImage2DCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, maxLevelCount(width, height))
+		, m_internalFormat	(internalFormat)
+		, m_format			(GL_NONE)
+		, m_dataType		(GL_NONE)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		tcu::TextureLevel	data		(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// First specify full texture.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			data.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_internalFormat, levelW, levelH, 0, m_format, m_dataType, data.getAccess().getDataPtr());
+		}
+
+		// Re-specify parts of each level.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			int		w			= rnd.getInt(1, levelW);
+			int		h			= rnd.getInt(1, levelH);
+			int		x			= rnd.getInt(0, levelW-w);
+			int		y			= rnd.getInt(0, levelH-h);
+
+			Vec4	colorA		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	colorB		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			int		cellSize	= rnd.getInt(2, 16);
+
+			data.setSize(w, h);
+			tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);
+
+			glTexSubImage2D(GL_TEXTURE_2D, ndx, x, y, w, h, m_format, m_dataType, data.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Basic TexSubImage2D() with cubemap usage
+class BasicTexSubImageCubeCase : public TextureCubeSpecCase
+{
+public:
+	BasicTexSubImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int size)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), size, deLog2Floor32(size)+1)
+		, m_internalFormat		(format)
+		, m_format				(format)
+		, m_dataType			(dataType)
+	{
+	}
+
+	BasicTexSubImageCubeCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, deLog2Floor32(size)+1)
+		, m_internalFormat		(internalFormat)
+		, m_format				(GL_NONE)
+		, m_dataType			(GL_NONE)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		tcu::TextureLevel	data		(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int levelSize = de::max(1, m_size >> ndx);
+
+			data.setSize(levelSize, levelSize);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				Vec4 gMin = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+				Vec4 gMax = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+				tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);
+
+				glTexImage2D(s_cubeMapFaces[face], ndx, m_internalFormat, levelSize, levelSize, 0, m_format, m_dataType, data.getAccess().getDataPtr());
+			}
+		}
+
+		// Re-specify parts of each face and level.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int levelSize = de::max(1, m_size >> ndx);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				int		w			= rnd.getInt(1, levelSize);
+				int		h			= rnd.getInt(1, levelSize);
+				int		x			= rnd.getInt(0, levelSize-w);
+				int		y			= rnd.getInt(0, levelSize-h);
+
+				Vec4	colorA		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+				Vec4	colorB		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+				int		cellSize	= rnd.getInt(2, 16);
+
+				data.setSize(w, h);
+				tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);
+
+				glTexSubImage2D(s_cubeMapFaces[face], ndx, x, y, w, h, m_format, m_dataType, data.getAccess().getDataPtr());
+			}
+		}
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// TexSubImage2D() unpack parameters case.
+class TexSubImage2DParamsCase : public Texture2DSpecCase
+{
+public:
+	TexSubImage2DParamsCase (Context&		context,
+							 const char*	name,
+							 const char*	desc,
+							 deUint32		internalFormat,
+							 int			width,
+							 int			height,
+							 int			subX,
+							 int			subY,
+							 int			subW,
+							 int			subH,
+							 int			rowLength,
+							 int			skipRows,
+							 int			skipPixels,
+							 int			alignment)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, 1)
+		, m_internalFormat	(internalFormat)
+		, m_subX			(subX)
+		, m_subY			(subY)
+		, m_subW			(subW)
+		, m_subH			(subH)
+		, m_rowLength		(rowLength)
+		, m_skipRows		(skipRows)
+		, m_skipPixels		(skipPixels)
+		, m_alignment		(alignment)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		deUint32				tex				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+
+		// First fill texture with gradient.
+		data.resize(deAlign32(m_width*pixelSize, 4)*m_height);
+		tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, 1, deAlign32(m_width*pixelSize, 4), 0, &data[0]), m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_width, m_height, 0, transferFmt.format, transferFmt.dataType, &data[0]);
+
+		// Fill data with grid.
+		{
+			int		rowLength	= m_rowLength > 0 ? m_rowLength : m_subW;
+			int		rowPitch	= deAlign32(rowLength*pixelSize, m_alignment);
+			int		height		= m_subH + m_skipRows;
+			Vec4	cScale		= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias		= m_texFormatInfo.valueMin;
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			data.resize(rowPitch*height);
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_subW, m_subH, 1, rowPitch, 0, &data[0] + m_skipRows*rowPitch + m_skipPixels*pixelSize), 4, colorA, colorB);
+		}
+
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+		glTexSubImage2D(GL_TEXTURE_2D, 0, m_subX, m_subY, m_subW, m_subH, transferFmt.format, transferFmt.dataType, &data[0]);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_subX;
+	int			m_subY;
+	int			m_subW;
+	int			m_subH;
+	int			m_rowLength;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+};
+
+// Basic TexSubImage3D() with 3D texture usage
+class BasicTexSubImage3DCase : public Texture3DSpecCase
+{
+public:
+	BasicTexSubImage3DCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height, int depth)
+		: Texture3DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, depth, maxLevelCount(width, height, depth))
+		, m_internalFormat	(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32				tex				= 0;
+		tcu::TextureLevel		data			(m_texFormat);
+		de::Random				rnd				(deStringHash(getName()));
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_3D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// First specify full texture.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			int		levelD		= de::max(1, m_depth >> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			data.setSize(levelW, levelH, levelD);
+			tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);
+
+			glTexImage3D(GL_TEXTURE_3D, ndx, m_internalFormat, levelW, levelH, levelD, 0, transferFmt.format, transferFmt.dataType, data.getAccess().getDataPtr());
+		}
+
+		// Re-specify parts of each level.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			int		levelD		= de::max(1, m_depth >> ndx);
+
+			int		w			= rnd.getInt(1, levelW);
+			int		h			= rnd.getInt(1, levelH);
+			int		d			= rnd.getInt(1, levelD);
+			int		x			= rnd.getInt(0, levelW-w);
+			int		y			= rnd.getInt(0, levelH-h);
+			int		z			= rnd.getInt(0, levelD-d);
+
+			Vec4	colorA		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	colorB		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			int		cellSize	= rnd.getInt(2, 16);
+
+			data.setSize(w, h, d);
+			tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);
+
+			glTexSubImage3D(GL_TEXTURE_3D, ndx, x, y, z, w, h, d, transferFmt.format, transferFmt.dataType, data.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// TexSubImage2D() to texture initialized with empty data
+class TexSubImage2DEmptyTexCase : public Texture2DSpecCase
+{
+public:
+	TexSubImage2DEmptyTexCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), width, height, maxLevelCount(width, height))
+		, m_internalFormat	(format)
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+	TexSubImage2DEmptyTexCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, maxLevelCount(width, height))
+		, m_internalFormat	(internalFormat)
+		, m_format			(GL_NONE)
+		, m_dataType		(GL_NONE)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		tcu::TextureLevel	data		(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// First allocate storage for each level.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_internalFormat, levelW, levelH, 0, m_format, m_dataType, DE_NULL);
+		}
+
+		// Specify pixel data to all levels using glTexSubImage2D()
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			data.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);
+
+			glTexSubImage2D(GL_TEXTURE_2D, ndx, 0, 0, levelW, levelH, m_format, m_dataType, data.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// TexSubImage2D() to empty cubemap texture
+class TexSubImageCubeEmptyTexCase : public TextureCubeSpecCase
+{
+public:
+	TexSubImageCubeEmptyTexCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int size)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), size, deLog2Floor32(size)+1)
+		, m_internalFormat		(format)
+		, m_format				(format)
+		, m_dataType			(dataType)
+	{
+	}
+
+	TexSubImageCubeEmptyTexCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, deLog2Floor32(size)+1)
+		, m_internalFormat		(internalFormat)
+		, m_format				(GL_NONE)
+		, m_dataType			(GL_NONE)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		tcu::TextureLevel	data		(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// Specify storage for each level.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int levelSize = de::max(1, m_size >> ndx);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+				glTexImage2D(s_cubeMapFaces[face], ndx, m_internalFormat, levelSize, levelSize, 0, m_format, m_dataType, DE_NULL);
+		}
+
+		// Specify data using glTexSubImage2D()
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int levelSize = de::max(1, m_size >> ndx);
+
+			data.setSize(levelSize, levelSize);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				Vec4 gMin = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+				Vec4 gMax = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+				tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);
+
+				glTexSubImage2D(s_cubeMapFaces[face], ndx, 0, 0, levelSize, levelSize, m_format, m_dataType, data.getAccess().getDataPtr());
+			}
+		}
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// TexSubImage2D() unpack alignment with 2D texture
+class TexSubImage2DAlignCase : public Texture2DSpecCase
+{
+public:
+	TexSubImage2DAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height, int subX, int subY, int subW, int subH, int alignment)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), width, height, 1)
+		, m_internalFormat	(format)
+		, m_format			(format)
+		, m_dataType		(dataType)
+		, m_subX			(subX)
+		, m_subY			(subY)
+		, m_subW			(subW)
+		, m_subH			(subH)
+		, m_alignment		(alignment)
+	{
+	}
+
+	TexSubImage2DAlignCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height, int subX, int subY, int subW, int subH, int alignment)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, 1)
+		, m_internalFormat	(internalFormat)
+		, m_format			(GL_NONE)
+		, m_dataType		(GL_NONE)
+		, m_subX			(subX)
+		, m_subY			(subY)
+		, m_subW			(subW)
+		, m_subH			(subH)
+		, m_alignment		(alignment)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		vector<deUint8>		data;
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+
+		// Specify base level.
+		data.resize(m_texFormat.getPixelSize()*m_width*m_height);
+		tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, 1, &data[0]), Vec4(0.0f), Vec4(1.0f));
+
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_width, m_height, 0, m_format, m_dataType, &data[0]);
+
+		// Re-specify subrectangle.
+		int rowPitch = deAlign32(m_texFormat.getPixelSize()*m_subW, m_alignment);
+		data.resize(rowPitch*m_subH);
+		tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_subW, m_subH, 1, rowPitch, 0, &data[0]), 4, Vec4(1.0f, 0.0f, 0.0f, 1.0f), Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);
+		glTexSubImage2D(GL_TEXTURE_2D, 0, m_subX, m_subY, m_subW, m_subH, m_format, m_dataType, &data[0]);
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+	int			m_subX;
+	int			m_subY;
+	int			m_subW;
+	int			m_subH;
+	int			m_alignment;
+};
+
+// TexSubImage2D() unpack alignment with cubemap texture
+class TexSubImageCubeAlignCase : public TextureCubeSpecCase
+{
+public:
+	TexSubImageCubeAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int size, int subX, int subY, int subW, int subH, int alignment)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), size, 1)
+		, m_internalFormat		(format)
+		, m_format				(format)
+		, m_dataType			(dataType)
+		, m_subX				(subX)
+		, m_subY				(subY)
+		, m_subW				(subW)
+		, m_subH				(subH)
+		, m_alignment			(alignment)
+	{
+	}
+
+	TexSubImageCubeAlignCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size, int subX, int subY, int subW, int subH, int alignment)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, 1)
+		, m_internalFormat		(internalFormat)
+		, m_format				(GL_NONE)
+		, m_dataType			(GL_NONE)
+		, m_subX				(subX)
+		, m_subY				(subY)
+		, m_subW				(subW)
+		, m_subH				(subH)
+		, m_alignment			(alignment)
+	{
+		glu::TransferFormat fmt = glu::getTransferFormat(m_texFormat);
+		m_format	= fmt.format;
+		m_dataType	= fmt.dataType;
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32			tex			= 0;
+		vector<deUint8>		data;
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+
+		// Specify base level.
+		data.resize(m_texFormat.getPixelSize()*m_size*m_size);
+		tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_size, m_size, 1, &data[0]), Vec4(0.0f), Vec4(1.0f));
+
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			glTexImage2D(s_cubeMapFaces[face], 0, m_internalFormat, m_size, m_size, 0, m_format, m_dataType, &data[0]);
+
+		// Re-specify subrectangle.
+		int rowPitch = deAlign32(m_texFormat.getPixelSize()*m_subW, m_alignment);
+		data.resize(rowPitch*m_subH);
+		tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_subW, m_subH, 1, rowPitch, 0, &data[0]), 4, Vec4(1.0f, 0.0f, 0.0f, 1.0f), Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			glTexSubImage2D(s_cubeMapFaces[face], 0, m_subX, m_subY, m_subW, m_subH, m_format, m_dataType, &data[0]);
+	}
+
+	deUint32	m_internalFormat;
+	deUint32	m_format;
+	deUint32	m_dataType;
+	int			m_subX;
+	int			m_subY;
+	int			m_subW;
+	int			m_subH;
+	int			m_alignment;
+};
+
+// TexSubImage3D() unpack parameters case.
+class TexSubImage3DParamsCase : public Texture3DSpecCase
+{
+public:
+	TexSubImage3DParamsCase (Context&		context,
+							 const char*	name,
+							 const char*	desc,
+							 deUint32		internalFormat,
+							 int			width,
+							 int			height,
+							 int			depth,
+							 int			subX,
+							 int			subY,
+							 int			subZ,
+							 int			subW,
+							 int			subH,
+							 int			subD,
+							 int			imageHeight,
+							 int			rowLength,
+							 int			skipImages,
+							 int			skipRows,
+							 int			skipPixels,
+							 int			alignment)
+		: Texture3DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, depth, 1)
+		, m_internalFormat	(internalFormat)
+		, m_subX			(subX)
+		, m_subY			(subY)
+		, m_subZ			(subZ)
+		, m_subW			(subW)
+		, m_subH			(subH)
+		, m_subD			(subD)
+		, m_imageHeight		(imageHeight)
+		, m_rowLength		(rowLength)
+		, m_skipImages		(skipImages)
+		, m_skipRows		(skipRows)
+		, m_skipPixels		(skipPixels)
+		, m_alignment		(alignment)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		deUint32				tex				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_3D, tex);
+
+		// Fill with gradient.
+		{
+			int		rowPitch		= deAlign32(pixelSize*m_width,  4);
+			int		slicePitch		= rowPitch*m_height;
+
+			data.resize(slicePitch*m_depth);
+			tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, m_depth, rowPitch, slicePitch, &data[0]), m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+		}
+
+		glTexImage3D(GL_TEXTURE_3D, 0, m_internalFormat, m_width, m_height, m_depth, 0, transferFmt.format, transferFmt.dataType, &data[0]);
+
+		// Fill data with grid.
+		{
+			int		rowLength		= m_rowLength > 0 ? m_rowLength : m_subW;
+			int		rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
+			int		imageHeight		= m_imageHeight > 0 ? m_imageHeight : m_subH;
+			int		slicePitch		= imageHeight*rowPitch;
+			Vec4	cScale			= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias			= m_texFormatInfo.valueMin;
+			Vec4	colorA			= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB			= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			data.resize(slicePitch*(m_depth+m_skipImages));
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_subW, m_subH, m_subD, rowPitch, slicePitch, &data[0] + m_skipImages*slicePitch + m_skipRows*rowPitch + m_skipPixels*pixelSize), 4, colorA, colorB);
+		}
+
+		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	m_imageHeight);
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	m_skipImages);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+		glTexSubImage3D(GL_TEXTURE_3D, 0, m_subX, m_subY, m_subZ, m_subW, m_subH, m_subD, transferFmt.format, transferFmt.dataType, &data[0]);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_subX;
+	int			m_subY;
+	int			m_subZ;
+	int			m_subW;
+	int			m_subH;
+	int			m_subD;
+	int			m_imageHeight;
+	int			m_rowLength;
+	int			m_skipImages;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+};
+
+// Basic CopyTexImage2D() with 2D texture usage
+class BasicCopyTexImage2DCase : public Texture2DSpecCase
+{
+public:
+	BasicCopyTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLTransferFormat(internalFormat, GL_UNSIGNED_BYTE), width, height, maxLevelCount(width, height))
+		, m_internalFormat	(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
+		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
+		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
+		tcu::TextureFormat			fmt				= mapGLUnsizedInternalFormat(m_internalFormat);
+		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
+		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
+		deUint32					tex				= 0;
+		de::Random					rnd				(deStringHash(getName()));
+		GradientShader				shader			(glu::TYPE_FLOAT_VEC4);
+		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);
+
+		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
+			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);
+
+		// Fill render target with gradient.
+		shader.setGradient(*getCurrentContext(), shaderID, Vec4(0.0f), Vec4(1.0f));
+		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			int		x			= rnd.getInt(0, getWidth()	- levelW);
+			int		y			= rnd.getInt(0, getHeight()	- levelH);
+
+			glCopyTexImage2D(GL_TEXTURE_2D, ndx, m_internalFormat, x, y, levelW, levelH, 0);
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Basic CopyTexImage2D() with cubemap usage
+class BasicCopyTexImageCubeCase : public TextureCubeSpecCase
+{
+public:
+	BasicCopyTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLTransferFormat(internalFormat, GL_UNSIGNED_BYTE), size, deLog2Floor32(size)+1)
+		, m_internalFormat		(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
+		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
+		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
+		tcu::TextureFormat			fmt				= mapGLUnsizedInternalFormat(m_internalFormat);
+		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
+		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
+		deUint32					tex				= 0;
+		de::Random					rnd				(deStringHash(getName()));
+		GradientShader				shader			(glu::TYPE_FLOAT_VEC4);
+		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);
+
+		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
+			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);
+
+		// Fill render target with gradient.
+		shader.setGradient(*getCurrentContext(), shaderID, Vec4(0.0f), Vec4(1.0f));
+		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int levelSize = de::max(1, m_size >> ndx);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				int x = rnd.getInt(0, getWidth()	- levelSize);
+				int y = rnd.getInt(0, getHeight()	- levelSize);
+
+				glCopyTexImage2D(s_cubeMapFaces[face], ndx, m_internalFormat, x, y, levelSize, levelSize, 0);
+			}
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Basic CopyTexSubImage2D() with 2D texture usage
+class BasicCopyTexSubImage2DCase : public Texture2DSpecCase
+{
+public:
+	BasicCopyTexSubImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), width, height, maxLevelCount(width, height))
+		, m_format			(format)
+		, m_dataType		(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
+		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
+		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
+		tcu::TextureFormat			fmt				= glu::mapGLTransferFormat(m_format, m_dataType);
+		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
+		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
+		deUint32					tex				= 0;
+		tcu::TextureLevel			data			(fmt);
+		de::Random					rnd				(deStringHash(getName()));
+		GradientShader				shader			(glu::TYPE_FLOAT_VEC4);
+		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);
+
+		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
+			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		// First specify full texture.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			Vec4	colorA		= randomVector<4>(rnd);
+			Vec4	colorB		= randomVector<4>(rnd);
+			int		cellSize	= rnd.getInt(2, 16);
+
+			data.setSize(levelW, levelH);
+			tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, data.getAccess().getDataPtr());
+		}
+
+		// Fill render target with gradient.
+		shader.setGradient(*getCurrentContext(), shaderID, Vec4(0.0f), Vec4(1.0f));
+		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));
+
+		// Re-specify parts of each level.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+
+			int		w			= rnd.getInt(1, levelW);
+			int		h			= rnd.getInt(1, levelH);
+			int		xo			= rnd.getInt(0, levelW-w);
+			int		yo			= rnd.getInt(0, levelH-h);
+
+			int		x			= rnd.getInt(0, getWidth() - w);
+			int		y			= rnd.getInt(0, getHeight() - h);
+
+			glCopyTexSubImage2D(GL_TEXTURE_2D, ndx, xo, yo, x, y, w, h);
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Basic CopyTexSubImage2D() with cubemap usage
+class BasicCopyTexSubImageCubeCase : public TextureCubeSpecCase
+{
+public:
+	BasicCopyTexSubImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int size)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLTransferFormat(format, dataType), size, deLog2Floor32(size)+1)
+		, m_format				(format)
+		, m_dataType			(dataType)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
+		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
+		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
+		tcu::TextureFormat			fmt				= glu::mapGLTransferFormat(m_format, m_dataType);
+		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
+		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
+		deUint32					tex				= 0;
+		tcu::TextureLevel			data			(fmt);
+		de::Random					rnd				(deStringHash(getName()));
+		GradientShader				shader			(glu::TYPE_FLOAT_VEC4);
+		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);
+
+		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
+			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int levelSize = de::max(1, m_size >> ndx);
+
+			data.setSize(levelSize, levelSize);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				Vec4	colorA		= randomVector<4>(rnd);
+				Vec4	colorB		= randomVector<4>(rnd);
+				int		cellSize	= rnd.getInt(2, 16);
+
+				tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);
+				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelSize, levelSize, 0, m_format, m_dataType, data.getAccess().getDataPtr());
+			}
+		}
+
+		// Fill render target with gradient.
+		shader.setGradient(*getCurrentContext(), shaderID, Vec4(0.0f), Vec4(1.0f));
+		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));
+
+		// Re-specify parts of each face and level.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int levelSize = de::max(1, m_size >> ndx);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				int		w			= rnd.getInt(1, levelSize);
+				int		h			= rnd.getInt(1, levelSize);
+				int		xo			= rnd.getInt(0, levelSize-w);
+				int		yo			= rnd.getInt(0, levelSize-h);
+
+				int		x			= rnd.getInt(0, getWidth() - w);
+				int		y			= rnd.getInt(0, getHeight() - h);
+
+				glCopyTexSubImage2D(s_cubeMapFaces[face], ndx, xo, yo, x, y, w, h);
+			}
+		}
+	}
+
+	deUint32	m_format;
+	deUint32	m_dataType;
+};
+
+// Basic glTexStorage2D() with 2D texture usage
+class BasicTexStorage2DCase : public Texture2DSpecCase
+{
+public:
+	BasicTexStorage2DCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height, int numLevels)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, numLevels)
+		, m_internalFormat	(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat		fmt				= glu::mapGLInternalFormat(m_internalFormat);
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(fmt);
+		deUint32				tex				= 0;
+		tcu::TextureLevel		levelData		(fmt);
+		de::Random				rnd				(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glTexStorage2D(GL_TEXTURE_2D, m_numLevels, m_internalFormat, m_width, m_height);
+
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width >> ndx);
+			int		levelH		= de::max(1, m_height >> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			levelData.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexSubImage2D(GL_TEXTURE_2D, ndx, 0, 0, levelW, levelH, transferFmt.format, transferFmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Basic glTexStorage2D() with cubemap usage
+class BasicTexStorageCubeCase : public TextureCubeSpecCase
+{
+public:
+	BasicTexStorageCubeCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size, int numLevels)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, numLevels)
+		, m_internalFormat		(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		tcu::TextureFormat		fmt				= glu::mapGLInternalFormat(m_internalFormat);
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(fmt);
+		deUint32				tex				= 0;
+		tcu::TextureLevel		levelData		(fmt);
+		de::Random				rnd				(deStringHash(getName()));
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glTexStorage2D(GL_TEXTURE_CUBE_MAP, m_numLevels, m_internalFormat, m_size, m_size);
+
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int levelSize = de::max(1, m_size >> ndx);
+
+			levelData.setSize(levelSize, levelSize);
+
+			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+			{
+				Vec4 gMin = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+				Vec4 gMax = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+				tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+				glTexSubImage2D(s_cubeMapFaces[face], ndx, 0, 0, levelSize, levelSize, transferFmt.format, transferFmt.dataType, levelData.getAccess().getDataPtr());
+			}
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Basic glTexStorage3D() with 2D array texture usage
+class BasicTexStorage2DArrayCase : public Texture2DArraySpecCase
+{
+public:
+	BasicTexStorage2DArrayCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height, int numLayers, int numLevels)
+		: Texture2DArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, numLayers, numLevels)
+		, m_internalFormat			(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32				tex			= 0;
+		tcu::TextureLevel		levelData	(m_texFormat);
+		de::Random				rnd			(deStringHash(getName()));
+		glu::TransferFormat		transferFmt	= glu::getTransferFormat(m_texFormat);
+
+		glGenTextures	(1, &tex);
+		glBindTexture	(GL_TEXTURE_2D_ARRAY, tex);
+		glTexStorage3D	(GL_TEXTURE_2D_ARRAY, m_numLevels, m_internalFormat, m_width, m_height, m_numLayers);
+
+		glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width	>> ndx);
+			int		levelH		= de::max(1, m_height	>> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			levelData.setSize(levelW, levelH, m_numLayers);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexSubImage3D(GL_TEXTURE_2D_ARRAY, ndx, 0, 0, 0, levelW, levelH, m_numLayers, transferFmt.format, transferFmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Basic TexStorage3D() with 3D texture usage
+class BasicTexStorage3DCase : public Texture3DSpecCase
+{
+public:
+	BasicTexStorage3DCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int width, int height, int depth, int numLevels)
+		: Texture3DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, depth, numLevels)
+		, m_internalFormat	(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32				tex			= 0;
+		tcu::TextureLevel		levelData	(m_texFormat);
+		de::Random				rnd			(deStringHash(getName()));
+		glu::TransferFormat		transferFmt	= glu::getTransferFormat(m_texFormat);
+
+		glGenTextures	(1, &tex);
+		glBindTexture	(GL_TEXTURE_3D, tex);
+		glTexStorage3D	(GL_TEXTURE_3D, m_numLevels, m_internalFormat, m_width, m_height, m_depth);
+
+		glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_width	>> ndx);
+			int		levelH		= de::max(1, m_height	>> ndx);
+			int		levelD		= de::max(1, m_depth	>> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			levelData.setSize(levelW, levelH, levelD);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexSubImage3D(GL_TEXTURE_3D, ndx, 0, 0, 0, levelW, levelH, levelD, transferFmt.format, transferFmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Pixel buffer object cases.
+
+// TexImage2D() from pixel buffer object.
+class TexImage2DBufferCase : public Texture2DSpecCase
+{
+public:
+	TexImage2DBufferCase (Context&		context,
+						  const char*	name,
+						  const char*	desc,
+						  deUint32		internalFormat,
+						  int			width,
+						  int			height,
+						  int			rowLength,
+						  int			skipRows,
+						  int			skipPixels,
+						  int			alignment,
+						  int			offset)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, 1)
+		, m_internalFormat	(internalFormat)
+		, m_rowLength		(rowLength)
+		, m_skipRows		(skipRows)
+		, m_skipPixels		(skipPixels)
+		, m_alignment		(alignment)
+		, m_offset			(offset)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		int						rowLength		= m_rowLength > 0 ? m_rowLength : m_width + m_skipPixels;
+		int						rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
+		int						height			= m_height + m_skipRows;
+		deUint32				buf				= 0;
+		deUint32				tex				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		// Fill data with grid.
+		data.resize(rowPitch*height + m_offset);
+		{
+			Vec4	cScale		= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias		= m_texFormatInfo.valueMin;
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, 1, rowPitch, 0, &data[0] + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
+		}
+
+		// Create buffer and upload.
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER, (int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_width, m_height, 0, transferFmt.format, transferFmt.dataType, (const void*)(deUintptr)m_offset);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_rowLength;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+	int			m_offset;
+};
+
+// TexImage2D() cubemap from pixel buffer object case
+class TexImageCubeBufferCase : public TextureCubeSpecCase
+{
+public:
+	TexImageCubeBufferCase (Context&	context,
+							const char*	name,
+							const char*	desc,
+							deUint32	internalFormat,
+							int			size,
+							int			rowLength,
+							int			skipRows,
+							int			skipPixels,
+							int			alignment,
+							int			offset)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, 1)
+		, m_internalFormat		(internalFormat)
+		, m_rowLength			(rowLength)
+		, m_skipRows			(skipRows)
+		, m_skipPixels			(skipPixels)
+		, m_alignment			(alignment)
+		, m_offset				(offset)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		de::Random					rnd			(deStringHash(getName()));
+		deUint32					tex			= 0;
+		glu::TransferFormat			fmt			= glu::getTransferFormat(m_texFormat);
+		const int					pixelSize	= m_texFormat.getPixelSize();
+		const int					rowLength	= m_rowLength > 0 ? m_rowLength : m_size + m_skipPixels;
+		const int					rowPitch	= deAlign32(rowLength*pixelSize, m_alignment);
+		const int					height		= m_size + m_skipRows;
+		vector<vector<deUint8> >	data		(DE_LENGTH_OF_ARRAY(s_cubeMapFaces));
+
+		DE_ASSERT(m_numLevels == 1);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+		{
+			deUint32 buf = 0;
+
+			{
+				const Vec4 gMin = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+				const Vec4 gMax = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+				data[face].resize(rowPitch*height + m_offset);
+				tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_size, m_size, 1, rowPitch, 0, &data[face][0] + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), gMin, gMax);
+			}
+
+			// Create buffer and upload.
+			glGenBuffers(1, &buf);
+			glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
+			glBufferData(GL_PIXEL_UNPACK_BUFFER, (int)data[face].size(), &data[face][0], GL_STATIC_DRAW);
+
+			glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+			glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+			glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+			glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+
+			glTexImage2D(s_cubeMapFaces[face], 0, m_internalFormat, m_size, m_size, 0, fmt.format, fmt.dataType, (const void*)(deUintptr)m_offset);
+		}
+	}
+
+	deUint32	m_internalFormat;
+	int			m_rowLength;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+	int			m_offset;
+};
+
+// TexImage3D() 2D array from pixel buffer object.
+class TexImage2DArrayBufferCase : public Texture2DArraySpecCase
+{
+public:
+	TexImage2DArrayBufferCase (Context&		context,
+							   const char*	name,
+							   const char*	desc,
+							   deUint32		internalFormat,
+							   int			width,
+							   int			height,
+							   int			depth,
+							   int			imageHeight,
+							   int			rowLength,
+							   int			skipImages,
+							   int			skipRows,
+							   int			skipPixels,
+							   int			alignment,
+							   int			offset)
+		: Texture2DArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, depth, 1)
+		, m_internalFormat			(internalFormat)
+		, m_imageHeight				(imageHeight)
+		, m_rowLength				(rowLength)
+		, m_skipImages				(skipImages)
+		, m_skipRows				(skipRows)
+		, m_skipPixels				(skipPixels)
+		, m_alignment				(alignment)
+		, m_offset					(offset)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		int						rowLength		= m_rowLength > 0 ? m_rowLength : m_width;
+		int						rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
+		int						imageHeight		= m_imageHeight > 0 ? m_imageHeight : m_height;
+		int						slicePitch		= imageHeight*rowPitch;
+		deUint32				tex				= 0;
+		deUint32				buf				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		// Fill data with grid.
+		data.resize(slicePitch*(m_numLayers+m_skipImages) + m_offset);
+		{
+			Vec4	cScale		= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias		= m_texFormatInfo.valueMin;
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, m_numLayers, rowPitch, slicePitch, &data[0] + m_skipImages*slicePitch + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
+		}
+
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER, (int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	m_imageHeight);
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	m_skipImages);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+		glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, m_internalFormat, m_width, m_height, m_numLayers, 0, transferFmt.format, transferFmt.dataType, (const void*)(deUintptr)m_offset);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_imageHeight;
+	int			m_rowLength;
+	int			m_skipImages;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+	int			m_offset;
+};
+
+// TexImage3D() from pixel buffer object.
+class TexImage3DBufferCase : public Texture3DSpecCase
+{
+public:
+	TexImage3DBufferCase (Context&		context,
+						  const char*	name,
+						  const char*	desc,
+						  deUint32		internalFormat,
+						  int			width,
+						  int			height,
+						  int			depth,
+						  int			imageHeight,
+						  int			rowLength,
+						  int			skipImages,
+						  int			skipRows,
+						  int			skipPixels,
+						  int			alignment,
+						  int			offset)
+		: Texture3DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, depth, 1)
+		, m_internalFormat	(internalFormat)
+		, m_imageHeight		(imageHeight)
+		, m_rowLength		(rowLength)
+		, m_skipImages		(skipImages)
+		, m_skipRows		(skipRows)
+		, m_skipPixels		(skipPixels)
+		, m_alignment		(alignment)
+		, m_offset			(offset)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		int						rowLength		= m_rowLength > 0 ? m_rowLength : m_width;
+		int						rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
+		int						imageHeight		= m_imageHeight > 0 ? m_imageHeight : m_height;
+		int						slicePitch		= imageHeight*rowPitch;
+		deUint32				tex				= 0;
+		deUint32				buf				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		// Fill data with grid.
+		data.resize(slicePitch*(m_depth+m_skipImages) + m_offset);
+		{
+			Vec4	cScale		= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias		= m_texFormatInfo.valueMin;
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, m_depth, rowPitch, slicePitch, &data[0] + m_skipImages*slicePitch + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
+		}
+
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER, (int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	m_imageHeight);
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	m_skipImages);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_3D, tex);
+		glTexImage3D(GL_TEXTURE_3D, 0, m_internalFormat, m_width, m_height, m_depth, 0, transferFmt.format, transferFmt.dataType, (const void*)(deUintptr)m_offset);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_imageHeight;
+	int			m_rowLength;
+	int			m_skipImages;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+	int			m_offset;
+};
+
+// TexSubImage2D() PBO case.
+class TexSubImage2DBufferCase : public Texture2DSpecCase
+{
+public:
+	TexSubImage2DBufferCase (Context&		context,
+							 const char*	name,
+							 const char*	desc,
+							 deUint32		internalFormat,
+							 int			width,
+							 int			height,
+							 int			subX,
+							 int			subY,
+							 int			subW,
+							 int			subH,
+							 int			rowLength,
+							 int			skipRows,
+							 int			skipPixels,
+							 int			alignment,
+							 int			offset)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, 1)
+		, m_internalFormat	(internalFormat)
+		, m_subX			(subX)
+		, m_subY			(subY)
+		, m_subW			(subW)
+		, m_subH			(subH)
+		, m_rowLength		(rowLength)
+		, m_skipRows		(skipRows)
+		, m_skipPixels		(skipPixels)
+		, m_alignment		(alignment)
+		, m_offset			(offset)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		deUint32				tex				= 0;
+		deUint32				buf				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+
+		// First fill texture with gradient.
+		data.resize(deAlign32(m_width*pixelSize, 4)*m_height);
+		tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, 1, deAlign32(m_width*pixelSize, 4), 0, &data[0]), m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_width, m_height, 0, transferFmt.format, transferFmt.dataType, &data[0]);
+
+		// Fill data with grid.
+		{
+			int		rowLength	= m_rowLength > 0 ? m_rowLength : m_subW;
+			int		rowPitch	= deAlign32(rowLength*pixelSize, m_alignment);
+			int		height		= m_subH + m_skipRows;
+			Vec4	cScale		= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias		= m_texFormatInfo.valueMin;
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			data.resize(rowPitch*height + m_offset);
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_subW, m_subH, 1, rowPitch, 0, &data[0] + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
+		}
+
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER,	buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER,	(int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+		glTexSubImage2D(GL_TEXTURE_2D, 0, m_subX, m_subY, m_subW, m_subH, transferFmt.format, transferFmt.dataType, (const void*)(deUintptr)m_offset);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_subX;
+	int			m_subY;
+	int			m_subW;
+	int			m_subH;
+	int			m_rowLength;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+	int			m_offset;
+};
+
+// TexSubImage2D() cubemap PBO case.
+class TexSubImageCubeBufferCase : public TextureCubeSpecCase
+{
+public:
+	TexSubImageCubeBufferCase	(Context&		context,
+								 const char*	name,
+								 const char*	desc,
+								 deUint32		internalFormat,
+								 int			size,
+								 int			subX,
+								 int			subY,
+								 int			subW,
+								 int			subH,
+								 int			rowLength,
+								 int			skipRows,
+								 int			skipPixels,
+								 int			alignment,
+								 int			offset)
+		: TextureCubeSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, 1)
+		, m_internalFormat		(internalFormat)
+		, m_subX				(subX)
+		, m_subY				(subY)
+		, m_subW				(subW)
+		, m_subH				(subH)
+		, m_rowLength			(rowLength)
+		, m_skipRows			(skipRows)
+		, m_skipPixels			(skipPixels)
+		, m_alignment			(alignment)
+		, m_offset				(offset)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		de::Random				rnd				(deStringHash(getName()));
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		deUint32				tex				= 0;
+		deUint32				buf				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+
+		// Fill faces with different gradients.
+
+		data.resize(deAlign32(m_size*pixelSize, 4)*m_size);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
+		{
+			const Vec4 gMin = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			const Vec4 gMax = randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_size, m_size, 1, deAlign32(m_size*pixelSize, 4), 0, &data[0]), gMin, gMax);
+
+			glTexImage2D(s_cubeMapFaces[face], 0, m_internalFormat, m_size, m_size, 0, transferFmt.format, transferFmt.dataType, &data[0]);
+		}
+
+		// Fill data with grid.
+		{
+			int		rowLength	= m_rowLength > 0 ? m_rowLength : m_subW;
+			int		rowPitch	= deAlign32(rowLength*pixelSize, m_alignment);
+			int		height		= m_subH + m_skipRows;
+			Vec4	cScale		= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias		= m_texFormatInfo.valueMin;
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			data.resize(rowPitch*height + m_offset);
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_subW, m_subH, 1, rowPitch, 0, &data[0] + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
+		}
+
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER,	buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER,	(int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+			glTexSubImage2D(s_cubeMapFaces[face], 0, m_subX, m_subY, m_subW, m_subH, transferFmt.format, transferFmt.dataType, (const void*)(deUintptr)m_offset);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_subX;
+	int			m_subY;
+	int			m_subW;
+	int			m_subH;
+	int			m_rowLength;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+	int			m_offset;
+};
+
+// TexSubImage3D() 2D array PBO case.
+class TexSubImage2DArrayBufferCase : public Texture2DArraySpecCase
+{
+public:
+	TexSubImage2DArrayBufferCase (Context&		context,
+								 const char*	name,
+								 const char*	desc,
+								 deUint32		internalFormat,
+								 int			width,
+								 int			height,
+								 int			depth,
+								 int			subX,
+								 int			subY,
+								 int			subZ,
+								 int			subW,
+								 int			subH,
+								 int			subD,
+								 int			imageHeight,
+								 int			rowLength,
+								 int			skipImages,
+								 int			skipRows,
+								 int			skipPixels,
+								 int			alignment,
+								 int			offset)
+		: Texture2DArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, depth, 1)
+		, m_internalFormat			(internalFormat)
+		, m_subX					(subX)
+		, m_subY					(subY)
+		, m_subZ					(subZ)
+		, m_subW					(subW)
+		, m_subH					(subH)
+		, m_subD					(subD)
+		, m_imageHeight				(imageHeight)
+		, m_rowLength				(rowLength)
+		, m_skipImages				(skipImages)
+		, m_skipRows				(skipRows)
+		, m_skipPixels				(skipPixels)
+		, m_alignment				(alignment)
+		, m_offset					(offset)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		deUint32				tex				= 0;
+		deUint32				buf				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+
+		// Fill with gradient.
+		{
+			int		rowPitch		= deAlign32(pixelSize*m_width,  4);
+			int		slicePitch		= rowPitch*m_height;
+
+			data.resize(slicePitch*m_numLayers);
+			tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, m_numLayers, rowPitch, slicePitch, &data[0]), m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+		}
+
+		glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, m_internalFormat, m_width, m_height, m_numLayers, 0, transferFmt.format, transferFmt.dataType, &data[0]);
+
+		// Fill data with grid.
+		{
+			int		rowLength		= m_rowLength > 0 ? m_rowLength : m_subW;
+			int		rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
+			int		imageHeight		= m_imageHeight > 0 ? m_imageHeight : m_subH;
+			int		slicePitch		= imageHeight*rowPitch;
+			Vec4	cScale			= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias			= m_texFormatInfo.valueMin;
+			Vec4	colorA			= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB			= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			data.resize(slicePitch*(m_numLayers+m_skipImages) + m_offset);
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_subW, m_subH, m_subD, rowPitch, slicePitch, &data[0] + m_skipImages*slicePitch + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
+		}
+
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER,	buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER,	(int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	m_imageHeight);
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	m_skipImages);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+		glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, m_subX, m_subY, m_subZ, m_subW, m_subH, m_subD, transferFmt.format, transferFmt.dataType, (const void*)(deIntptr)m_offset);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_subX;
+	int			m_subY;
+	int			m_subZ;
+	int			m_subW;
+	int			m_subH;
+	int			m_subD;
+	int			m_imageHeight;
+	int			m_rowLength;
+	int			m_skipImages;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+	int			m_offset;
+};
+
+// TexSubImage3D() PBO case.
+class TexSubImage3DBufferCase : public Texture3DSpecCase
+{
+public:
+	TexSubImage3DBufferCase (Context&		context,
+							 const char*	name,
+							 const char*	desc,
+							 deUint32		internalFormat,
+							 int			width,
+							 int			height,
+							 int			depth,
+							 int			subX,
+							 int			subY,
+							 int			subZ,
+							 int			subW,
+							 int			subH,
+							 int			subD,
+							 int			imageHeight,
+							 int			rowLength,
+							 int			skipImages,
+							 int			skipRows,
+							 int			skipPixels,
+							 int			alignment,
+							 int			offset)
+		: Texture3DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), width, height, depth, 1)
+		, m_internalFormat	(internalFormat)
+		, m_subX			(subX)
+		, m_subY			(subY)
+		, m_subZ			(subZ)
+		, m_subW			(subW)
+		, m_subH			(subH)
+		, m_subD			(subD)
+		, m_imageHeight		(imageHeight)
+		, m_rowLength		(rowLength)
+		, m_skipImages		(skipImages)
+		, m_skipRows		(skipRows)
+		, m_skipPixels		(skipPixels)
+		, m_alignment		(alignment)
+		, m_offset			(offset)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		deUint32				tex				= 0;
+		deUint32				buf				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_3D, tex);
+
+		// Fill with gradient.
+		{
+			int		rowPitch		= deAlign32(pixelSize*m_width,  4);
+			int		slicePitch		= rowPitch*m_height;
+
+			data.resize(slicePitch*m_depth);
+			tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, m_depth, rowPitch, slicePitch, &data[0]), m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+		}
+
+		glTexImage3D(GL_TEXTURE_3D, 0, m_internalFormat, m_width, m_height, m_depth, 0, transferFmt.format, transferFmt.dataType, &data[0]);
+
+		// Fill data with grid.
+		{
+			int		rowLength		= m_rowLength > 0 ? m_rowLength : m_subW;
+			int		rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
+			int		imageHeight		= m_imageHeight > 0 ? m_imageHeight : m_subH;
+			int		slicePitch		= imageHeight*rowPitch;
+			Vec4	cScale			= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias			= m_texFormatInfo.valueMin;
+			Vec4	colorA			= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB			= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			data.resize(slicePitch*(m_depth+m_skipImages) + m_offset);
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_subW, m_subH, m_subD, rowPitch, slicePitch, &data[0] + m_skipImages*slicePitch + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
+		}
+
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER,	buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER,	(int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	m_imageHeight);
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	m_skipImages);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+		glTexSubImage3D(GL_TEXTURE_3D, 0, m_subX, m_subY, m_subZ, m_subW, m_subH, m_subD, transferFmt.format, transferFmt.dataType, (const void*)(deIntptr)m_offset);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_subX;
+	int			m_subY;
+	int			m_subZ;
+	int			m_subW;
+	int			m_subH;
+	int			m_subD;
+	int			m_imageHeight;
+	int			m_rowLength;
+	int			m_skipImages;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+	int			m_offset;
+};
+
+// TexImage2D() depth case.
+class TexImage2DDepthCase : public Texture2DSpecCase
+{
+public:
+	TexImage2DDepthCase (Context&		context,
+						 const char*	name,
+						 const char*	desc,
+						 deUint32		internalFormat,
+						 int			imageWidth,
+						 int			imageHeight)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageWidth, imageHeight, maxLevelCount(imageWidth, imageHeight))
+		, m_internalFormat	(internalFormat)
+	{
+		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
+		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
+		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
+	}
+
+	void createTexture (void)
+	{
+		glu::TransferFormat	fmt			= glu::getTransferFormat(m_texFormat);
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(m_texFormat);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+		GLU_CHECK();
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			const int   levelW		= de::max(1, m_width >> ndx);
+			const int   levelH		= de::max(1, m_height >> ndx);
+			const Vec4  gMin		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const Vec4  gMax		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+
+			levelData.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_internalFormat, levelW, levelH, 0, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	const deUint32 m_internalFormat;
+};
+
+// TexImage3D() depth case.
+class TexImage2DArrayDepthCase : public Texture2DArraySpecCase
+{
+public:
+	TexImage2DArrayDepthCase (Context&		context,
+							  const char*	name,
+							  const char*	desc,
+							  deUint32		internalFormat,
+							  int			imageWidth,
+							  int			imageHeight,
+							  int			numLayers)
+		: Texture2DArraySpecCase(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageWidth, imageHeight, numLayers, maxLevelCount(imageWidth, imageHeight))
+		, m_internalFormat		(internalFormat)
+	{
+		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
+		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
+		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
+	}
+
+	void createTexture (void)
+	{
+		glu::TransferFormat	fmt			= glu::getTransferFormat(m_texFormat);
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(m_texFormat);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+		GLU_CHECK();
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			const int   levelW		= de::max(1, m_width >> ndx);
+			const int   levelH		= de::max(1, m_height >> ndx);
+			const Vec4  gMin		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const Vec4  gMax		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+
+			levelData.setSize(levelW, levelH, m_numLayers);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, ndx, m_internalFormat, levelW, levelH, m_numLayers, 0, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	const deUint32 m_internalFormat;
+};
+
+// TexSubImage2D() depth case.
+class TexSubImage2DDepthCase : public Texture2DSpecCase
+{
+public:
+	TexSubImage2DDepthCase (Context&	context,
+							const char*	name,
+							const char*	desc,
+							deUint32	internalFormat,
+							int			imageWidth,
+							int			imageHeight)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageWidth, imageHeight, maxLevelCount(imageWidth, imageHeight))
+		, m_internalFormat	(internalFormat)
+	{
+		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
+		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
+		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
+	}
+
+	void createTexture (void)
+	{
+		glu::TransferFormat	fmt			= glu::getTransferFormat(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(m_texFormat);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+		GLU_CHECK();
+
+		// First specify full texture.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			const int   levelW		= de::max(1, m_width >> ndx);
+			const int   levelH		= de::max(1, m_height >> ndx);
+			const Vec4  gMin		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const Vec4  gMax		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+
+			levelData.setSize(levelW, levelH);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage2D(GL_TEXTURE_2D, ndx, m_internalFormat, levelW, levelH, 0, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
+		}
+
+		// Re-specify parts of each level.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			const int	levelW		= de::max(1, m_width >> ndx);
+			const int	levelH		= de::max(1, m_height >> ndx);
+
+			const int	w			= rnd.getInt(1, levelW);
+			const int	h			= rnd.getInt(1, levelH);
+			const int	x			= rnd.getInt(0, levelW-w);
+			const int	y			= rnd.getInt(0, levelH-h);
+
+			const Vec4	colorA		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+			const Vec4	colorB		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const int	cellSize	= rnd.getInt(2, 16);
+
+			levelData.setSize(w, h);
+			tcu::fillWithGrid(levelData.getAccess(), cellSize, colorA, colorB);
+
+			glTexSubImage2D(GL_TEXTURE_2D, ndx, x, y, w, h, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	const deUint32 m_internalFormat;
+};
+
+// TexSubImage3D() depth case.
+class TexSubImage2DArrayDepthCase : public Texture2DArraySpecCase
+{
+public:
+	TexSubImage2DArrayDepthCase (Context&		context,
+								 const char*	name,
+								 const char*	desc,
+								 deUint32		internalFormat,
+								 int			imageWidth,
+								 int			imageHeight,
+								 int			numLayers)
+		: Texture2DArraySpecCase(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageWidth, imageHeight, numLayers, maxLevelCount(imageWidth, imageHeight))
+		, m_internalFormat		(internalFormat)
+	{
+		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
+		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
+		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
+	}
+
+	void createTexture (void)
+	{
+		glu::TransferFormat	fmt			= glu::getTransferFormat(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(m_texFormat);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+		GLU_CHECK();
+
+		// First specify full texture.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			const int   levelW		= de::max(1, m_width >> ndx);
+			const int   levelH		= de::max(1, m_height >> ndx);
+			const Vec4  gMin		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const Vec4  gMax		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+
+			levelData.setSize(levelW, levelH, m_numLayers);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage3D(GL_TEXTURE_2D_ARRAY, ndx, m_internalFormat, levelW, levelH, m_numLayers, 0, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
+		}
+
+		// Re-specify parts of each level.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			const int	levelW		= de::max(1, m_width >> ndx);
+			const int	levelH		= de::max(1, m_height >> ndx);
+
+			const int	w			= rnd.getInt(1, levelW);
+			const int	h			= rnd.getInt(1, levelH);
+			const int	d			= rnd.getInt(1, m_numLayers);
+			const int	x			= rnd.getInt(0, levelW-w);
+			const int	y			= rnd.getInt(0, levelH-h);
+			const int	z			= rnd.getInt(0, m_numLayers-d);
+
+			const Vec4	colorA		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+			const Vec4	colorB		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const int	cellSize	= rnd.getInt(2, 16);
+
+			levelData.setSize(w, h, d);
+			tcu::fillWithGrid(levelData.getAccess(), cellSize, colorA, colorB);
+
+			glTexSubImage3D(GL_TEXTURE_2D_ARRAY, ndx, x, y, z, w, h, d, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	const deUint32 m_internalFormat;
+};
+
+// TexImage2D() depth case with pbo.
+class TexImage2DDepthBufferCase : public Texture2DSpecCase
+{
+public:
+	TexImage2DDepthBufferCase (Context&		context,
+							   const char*	name,
+							   const char*	desc,
+							   deUint32		internalFormat,
+							   int			imageWidth,
+							   int			imageHeight)
+		: Texture2DSpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageWidth, imageHeight, 1)
+		, m_internalFormat	(internalFormat)
+	{
+		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
+		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
+		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
+	}
+
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		int						rowLength		= m_width;
+		int						alignment		= 4;
+		int						rowPitch		= deAlign32(rowLength*pixelSize, alignment);
+		int						height			= m_height;
+		deUint32				buf				= 0;
+		deUint32				tex				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		// Fill data with gradient
+		data.resize(rowPitch*height);
+		{
+			const Vec4 gMin = Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const Vec4 gMax = Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+
+			tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, 1, rowPitch, 0, &data[0]), gMin, gMax);
+		}
+
+		// Create buffer and upload.
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER, (int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		0);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	0);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		alignment);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_width, m_height, 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+		glDeleteBuffers(1, &buf);
+	}
+
+	const deUint32 m_internalFormat;
+};
+
+// TexImage3D() depth case with pbo.
+class TexImage2DArrayDepthBufferCase : public Texture2DArraySpecCase
+{
+public:
+	TexImage2DArrayDepthBufferCase (Context&	context,
+									const char*	name,
+									const char*	desc,
+									deUint32	internalFormat,
+									int			imageWidth,
+									int			imageHeight,
+									int			numLayers)
+		: Texture2DArraySpecCase(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageWidth, imageHeight, numLayers, 1)
+		, m_internalFormat		(internalFormat)
+	{
+		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
+		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
+		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
+	}
+
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		int						rowLength		= m_width;
+		int						alignment		= 4;
+		int						rowPitch		= deAlign32(rowLength*pixelSize, alignment);
+		int						imageHeight		= m_height;
+		int						slicePitch		= imageHeight*rowPitch;
+		deUint32				tex				= 0;
+		deUint32				buf				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		// Fill data with grid.
+		data.resize(slicePitch*m_numLayers);
+		{
+			const Vec4 gMin = Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const Vec4 gMax = Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+
+			tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_width, m_height, m_numLayers, rowPitch, slicePitch, &data[0]), gMin, gMax);
+		}
+
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER, (int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	imageHeight);
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	0);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		0);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	0);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		alignment);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+		glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, m_internalFormat, m_width, m_height, m_numLayers, 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+		glDeleteBuffers(1, &buf);
+	}
+
+	const deUint32 m_internalFormat;
+};
+
+TextureSpecificationTests::TextureSpecificationTests (Context& context)
+	: TestCaseGroup(context, "specification", "Texture Specification Tests")
+{
+}
+
+TextureSpecificationTests::~TextureSpecificationTests (void)
+{
+}
+
+void TextureSpecificationTests::init (void)
+{
+	struct
+	{
+		const char*	name;
+		deUint32	format;
+		deUint32	dataType;
+	} unsizedFormats[] =
+	{
+		{ "alpha_unsigned_byte",			GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "luminance_unsigned_byte",		GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "luminance_alpha_unsigned_byte",	GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
+		{ "rgb_unsigned_short_5_6_5",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgb_unsigned_byte",				GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba_unsigned_short_4_4_4_4",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgba_unsigned_short_5_5_5_1",	GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
+		{ "rgba_unsigned_byte",				GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	struct
+	{
+		const char*	name;
+		deUint32	internalFormat;
+	} colorFormats[] =
+	{
+		{ "rgba32f",			GL_RGBA32F,			},
+		{ "rgba32i",			GL_RGBA32I,			},
+		{ "rgba32ui",			GL_RGBA32UI,		},
+		{ "rgba16f",			GL_RGBA16F,			},
+		{ "rgba16i",			GL_RGBA16I,			},
+		{ "rgba16ui",			GL_RGBA16UI,		},
+		{ "rgba8",				GL_RGBA8,			},
+		{ "rgba8i",				GL_RGBA8I,			},
+		{ "rgba8ui",			GL_RGBA8UI,			},
+		{ "srgb8_alpha8",		GL_SRGB8_ALPHA8,	},
+		{ "rgb10_a2",			GL_RGB10_A2,		},
+		{ "rgb10_a2ui",			GL_RGB10_A2UI,		},
+		{ "rgba4",				GL_RGBA4,			},
+		{ "rgb5_a1",			GL_RGB5_A1,			},
+		{ "rgba8_snorm",		GL_RGBA8_SNORM,		},
+		{ "rgb8",				GL_RGB8,			},
+		{ "rgb565",				GL_RGB565,			},
+		{ "r11f_g11f_b10f",		GL_R11F_G11F_B10F,	},
+		{ "rgb32f",				GL_RGB32F,			},
+		{ "rgb32i",				GL_RGB32I,			},
+		{ "rgb32ui",			GL_RGB32UI,			},
+		{ "rgb16f",				GL_RGB16F,			},
+		{ "rgb16i",				GL_RGB16I,			},
+		{ "rgb16ui",			GL_RGB16UI,			},
+		{ "rgb8_snorm",			GL_RGB8_SNORM,		},
+		{ "rgb8i",				GL_RGB8I,			},
+		{ "rgb8ui",				GL_RGB8UI,			},
+		{ "srgb8",				GL_SRGB8,			},
+		{ "rgb9_e5",			GL_RGB9_E5,			},
+		{ "rg32f",				GL_RG32F,			},
+		{ "rg32i",				GL_RG32I,			},
+		{ "rg32ui",				GL_RG32UI,			},
+		{ "rg16f",				GL_RG16F,			},
+		{ "rg16i",				GL_RG16I,			},
+		{ "rg16ui",				GL_RG16UI,			},
+		{ "rg8",				GL_RG8,				},
+		{ "rg8i",				GL_RG8I,			},
+		{ "rg8ui",				GL_RG8UI,			},
+		{ "rg8_snorm",			GL_RG8_SNORM,		},
+		{ "r32f",				GL_R32F,			},
+		{ "r32i",				GL_R32I,			},
+		{ "r32ui",				GL_R32UI,			},
+		{ "r16f",				GL_R16F,			},
+		{ "r16i",				GL_R16I,			},
+		{ "r16ui",				GL_R16UI,			},
+		{ "r8",					GL_R8,				},
+		{ "r8i",				GL_R8I,				},
+		{ "r8ui",				GL_R8UI,			},
+		{ "r8_snorm",			GL_R8_SNORM,		}
+	};
+
+	static const struct
+	{
+		const char*	name;
+		deUint32	internalFormat;
+	} depthStencilFormats[] =
+	{
+		// Depth and stencil formats
+		{ "depth_component32f",	GL_DEPTH_COMPONENT32F	},
+		{ "depth_component24",	GL_DEPTH_COMPONENT24	},
+		{ "depth_component16",	GL_DEPTH_COMPONENT16	},
+		{ "depth32f_stencil8",	GL_DEPTH32F_STENCIL8	},
+		{ "depth24_stencil8",	GL_DEPTH24_STENCIL8		}
+	};
+
+	// Basic TexImage2D usage.
+	{
+		tcu::TestCaseGroup* basicTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_teximage2d", "Basic glTexImage2D() usage");
+		addChild(basicTexImageGroup);
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const char*	fmtName		= colorFormats[formatNdx].name;
+			deUint32	format		= colorFormats[formatNdx].internalFormat;
+			const int	tex2DWidth	= 64;
+			const int	tex2DHeight	= 128;
+			const int	texCubeSize	= 64;
+
+			basicTexImageGroup->addChild(new BasicTexImage2DCase	(m_context,	(string(fmtName) + "_2d").c_str(),		"",	format, tex2DWidth, tex2DHeight));
+			basicTexImageGroup->addChild(new BasicTexImageCubeCase	(m_context,	(string(fmtName) + "_cube").c_str(),	"",	format, texCubeSize));
+		}
+	}
+
+	// Randomized TexImage2D order.
+	{
+		tcu::TestCaseGroup* randomTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "random_teximage2d", "Randomized glTexImage2D() usage");
+		addChild(randomTexImageGroup);
+
+		de::Random rnd(9);
+
+		// 2D cases.
+		for (int ndx = 0; ndx < 10; ndx++)
+		{
+			int		formatNdx	= rnd.getInt(0, DE_LENGTH_OF_ARRAY(colorFormats)-1);
+			int		width		= 1 << rnd.getInt(2, 8);
+			int		height		= 1 << rnd.getInt(2, 8);
+
+			randomTexImageGroup->addChild(new RandomOrderTexImage2DCase(m_context, (string("2d_") + de::toString(ndx)).c_str(), "", colorFormats[formatNdx].internalFormat, width, height));
+		}
+
+		// Cubemap cases.
+		for (int ndx = 0; ndx < 10; ndx++)
+		{
+			int		formatNdx	= rnd.getInt(0, DE_LENGTH_OF_ARRAY(colorFormats)-1);
+			int		size		= 1 << rnd.getInt(2, 8);
+
+			randomTexImageGroup->addChild(new RandomOrderTexImageCubeCase(m_context, (string("cube_") + de::toString(ndx)).c_str(), "", colorFormats[formatNdx].internalFormat, size));
+		}
+	}
+
+	// TexImage2D unpack alignment.
+	{
+		tcu::TestCaseGroup* alignGroup = new tcu::TestCaseGroup(m_testCtx, "teximage2d_align", "glTexImage2D() unpack alignment tests");
+		addChild(alignGroup);
+
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_r8_4_8",			"",	GL_R8,			 4,  8, 4, 8));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_r8_63_1",			"",	GL_R8,			63, 30, 1, 1));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_r8_63_2",			"",	GL_R8,			63, 30, 1, 2));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_r8_63_4",			"",	GL_R8,			63, 30, 1, 4));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_r8_63_8",			"",	GL_R8,			63, 30, 1, 8));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4_51_1",		"",	GL_RGBA4,		51, 30, 1, 1));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4_51_2",		"",	GL_RGBA4,		51, 30, 1, 2));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4_51_4",		"",	GL_RGBA4,		51, 30, 1, 4));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4_51_8",		"",	GL_RGBA4,		51, 30, 1, 8));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb8_39_1",			"",	GL_RGB8,		39, 43, 1, 1));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb8_39_2",			"",	GL_RGB8,		39, 43, 1, 2));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb8_39_4",			"",	GL_RGB8,		39, 43, 1, 4));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb8_39_8",			"",	GL_RGB8,		39, 43, 1, 8));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8_47_1",		"",	GL_RGBA8,		47, 27, 1, 1));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8_47_2",		"",	GL_RGBA8,		47, 27, 1, 2));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8_47_4",		"",	GL_RGBA8,		47, 27, 1, 4));
+		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8_47_8",		"",	GL_RGBA8,		47, 27, 1, 8));
+
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_r8_4_8",			"",	GL_R8,			 4, 3, 8));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_r8_63_1",			"",	GL_R8,			63, 1, 1));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_r8_63_2",			"",	GL_R8,			63, 1, 2));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_r8_63_4",			"",	GL_R8,			63, 1, 4));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_r8_63_8",			"",	GL_R8,			63, 1, 8));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4_51_1",		"",	GL_RGBA4,		51, 1, 1));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4_51_2",		"",	GL_RGBA4,		51, 1, 2));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4_51_4",		"",	GL_RGBA4,		51, 1, 4));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4_51_8",		"",	GL_RGBA4,		51, 1, 8));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb8_39_1",		"",	GL_RGB8,		39, 1, 1));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb8_39_2",		"",	GL_RGB8,		39, 1, 2));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb8_39_4",		"",	GL_RGB8,		39, 1, 4));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb8_39_8",		"",	GL_RGB8,		39, 1, 8));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8_47_1",		"",	GL_RGBA8,		47, 1, 1));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8_47_2",		"",	GL_RGBA8,		47, 1, 2));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8_47_4",		"",	GL_RGBA8,		47, 1, 4));
+		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8_47_8",		"",	GL_RGBA8,		47, 1, 8));
+	}
+
+	// glTexImage2D() unpack parameter cases.
+	{
+		tcu::TestCaseGroup* paramGroup = new tcu::TestCaseGroup(m_testCtx, "teximage2d_unpack_params", "glTexImage2D() pixel transfer mode cases");
+		addChild(paramGroup);
+
+		static const struct
+		{
+			const char*	name;
+			deUint32	format;
+			int			width;
+			int			height;
+			int			rowLength;
+			int			skipRows;
+			int			skipPixels;
+			int			alignment;
+		} cases[] =
+		{
+			{ "rgb8_alignment",		GL_RGB8,	31,	30,	0,	0,	0,	2 },
+			{ "rgb8_row_length",	GL_RGB8,	31,	30,	50,	0,	0,	4 },
+			{ "rgb8_skip_rows",		GL_RGB8,	31,	30,	0,	3,	0,	4 },
+			{ "rgb8_skip_pixels",	GL_RGB8,	31,	30,	36,	0,	5,	4 },
+			{ "r8_complex1",		GL_R8,		31, 30, 64, 1,	3,	1 },
+			{ "r8_complex2",		GL_R8,		31, 30, 64, 1,	3,	2 },
+			{ "r8_complex3",		GL_R8,		31, 30, 64, 1,	3,	4 },
+			{ "r8_complex4",		GL_R8,		31, 30, 64, 1,	3,	8 },
+			{ "rgba8_complex1",		GL_RGBA8,	56,	61,	69,	0,	0,	8 },
+			{ "rgba8_complex2",		GL_RGBA8,	56,	61,	69,	0,	7,	8 },
+			{ "rgba8_complex3",		GL_RGBA8,	56,	61,	69,	3,	0,	8 },
+			{ "rgba8_complex4",		GL_RGBA8,	56,	61,	69,	3,	7,	8 },
+			{ "rgba32f_complex",	GL_RGBA32F,	19,	10,	27,	1,	7,	8 }
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
+			paramGroup->addChild(new TexImage2DParamsCase(m_context, cases[ndx].name, "",
+														  cases[ndx].format,
+														  cases[ndx].width,
+														  cases[ndx].height,
+														  cases[ndx].rowLength,
+														  cases[ndx].skipRows,
+														  cases[ndx].skipPixels,
+														  cases[ndx].alignment));
+	}
+
+	// glTexImage2D() pbo cases.
+	{
+		tcu::TestCaseGroup* pboGroup = new tcu::TestCaseGroup(m_testCtx, "teximage2d_pbo", "glTexImage2D() from PBO");
+		addChild(pboGroup);
+
+		// Parameter cases
+		static const struct
+		{
+			const char*	name;
+			deUint32	format;
+			int			width;
+			int			height;
+			int			rowLength;
+			int			skipRows;
+			int			skipPixels;
+			int			alignment;
+			int			offset;
+		} parameterCases[] =
+		{
+			{ "rgb8_offset",		GL_RGB8,	31,	30,	0,	0,	0,	4,	67 },
+			{ "rgb8_alignment",		GL_RGB8,	31,	30,	0,	0,	0,	2,	0 },
+			{ "rgb8_row_length",	GL_RGB8,	31,	30,	50,	0,	0,	4,	0 },
+			{ "rgb8_skip_rows",		GL_RGB8,	31,	30,	0,	3,	0,	4,	0 },
+			{ "rgb8_skip_pixels",	GL_RGB8,	31,	30,	36,	0,	5,	4,	0 }
+		};
+
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const string	fmtName		= colorFormats[formatNdx].name;
+			const deUint32	format		= colorFormats[formatNdx].internalFormat;
+			const int		tex2DWidth	= 65;
+			const int		tex2DHeight	= 37;
+			const int		texCubeSize	= 64;
+
+			pboGroup->addChild(new TexImage2DBufferCase		(m_context,	(fmtName + "_2d").c_str(),		"", format, tex2DWidth, tex2DHeight, 0, 0, 0, 4, 0));
+			pboGroup->addChild(new TexImageCubeBufferCase	(m_context,	(fmtName + "_cube").c_str(),	"", format, texCubeSize, 0, 0, 0, 4, 0));
+		}
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(parameterCases); ndx++)
+		{
+			pboGroup->addChild(new TexImage2DBufferCase(m_context, (string(parameterCases[ndx].name) + "_2d").c_str(), "",
+														parameterCases[ndx].format,
+														parameterCases[ndx].width,
+														parameterCases[ndx].height,
+														parameterCases[ndx].rowLength,
+														parameterCases[ndx].skipRows,
+														parameterCases[ndx].skipPixels,
+														parameterCases[ndx].alignment,
+														parameterCases[ndx].offset));
+			pboGroup->addChild(new TexImageCubeBufferCase(m_context, (string(parameterCases[ndx].name) + "_cube").c_str(), "",
+														parameterCases[ndx].format,
+														parameterCases[ndx].width,
+														parameterCases[ndx].rowLength,
+														parameterCases[ndx].skipRows,
+														parameterCases[ndx].skipPixels,
+														parameterCases[ndx].alignment,
+														parameterCases[ndx].offset));
+		}
+	}
+
+	// glTexImage2D() depth cases.
+	{
+		tcu::TestCaseGroup* shadow2dGroup = new tcu::TestCaseGroup(m_testCtx, "teximage2d_depth", "glTexImage2D() with depth or depth/stencil format");
+		addChild(shadow2dGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
+		{
+			const int tex2DWidth	= 64;
+			const int tex2DHeight	= 128;
+
+			shadow2dGroup->addChild(new TexImage2DDepthCase(m_context, depthStencilFormats[ndx].name, "", depthStencilFormats[ndx].internalFormat, tex2DWidth, tex2DHeight));
+		}
+	}
+
+	// glTexImage2D() depth cases with pbo.
+	{
+		tcu::TestCaseGroup* shadow2dGroup = new tcu::TestCaseGroup(m_testCtx, "teximage2d_depth_pbo", "glTexImage2D() with depth or depth/stencil format with pbo");
+		addChild(shadow2dGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
+		{
+			const int tex2DWidth	= 64;
+			const int tex2DHeight	= 128;
+
+			shadow2dGroup->addChild(new TexImage2DDepthBufferCase(m_context, depthStencilFormats[ndx].name, "", depthStencilFormats[ndx].internalFormat, tex2DWidth, tex2DHeight));
+		}
+	}
+
+	// Basic TexSubImage2D usage.
+	{
+		tcu::TestCaseGroup* basicTexSubImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_texsubimage2d", "Basic glTexSubImage2D() usage");
+		addChild(basicTexSubImageGroup);
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const char*	fmtName		= colorFormats[formatNdx].name;
+			deUint32	format		= colorFormats[formatNdx].internalFormat;
+			const int	tex2DWidth	= 64;
+			const int	tex2DHeight	= 128;
+			const int	texCubeSize	= 64;
+
+			basicTexSubImageGroup->addChild(new BasicTexSubImage2DCase		(m_context,	(string(fmtName) + "_2d").c_str(),		"",	format, tex2DWidth, tex2DHeight));
+			basicTexSubImageGroup->addChild(new BasicTexSubImageCubeCase	(m_context,	(string(fmtName) + "_cube").c_str(),	"",	format, texCubeSize));
+		}
+	}
+
+	// TexSubImage2D to empty texture.
+	{
+		tcu::TestCaseGroup* texSubImageEmptyTexGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage2d_empty_tex", "glTexSubImage2D() to texture that has storage but no data");
+		addChild(texSubImageEmptyTexGroup);
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(unsizedFormats); formatNdx++)
+		{
+			const char*	fmtName		= unsizedFormats[formatNdx].name;
+			deUint32	format		= unsizedFormats[formatNdx].format;
+			deUint32	dataType	= unsizedFormats[formatNdx].dataType;
+			const int	tex2DWidth	= 64;
+			const int	tex2DHeight	= 32;
+			const int	texCubeSize	= 32;
+
+			texSubImageEmptyTexGroup->addChild(new TexSubImage2DEmptyTexCase	(m_context,	(string(fmtName) + "_2d").c_str(),		"",	format, dataType, tex2DWidth, tex2DHeight));
+			texSubImageEmptyTexGroup->addChild(new TexSubImageCubeEmptyTexCase	(m_context,	(string(fmtName) + "_cube").c_str(),	"",	format, dataType, texCubeSize));
+		}
+	}
+
+	// TexSubImage2D alignment cases.
+	{
+		tcu::TestCaseGroup* alignGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage2d_align", "glTexSubImage2D() unpack alignment tests");
+		addChild(alignGroup);
+
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_r8_1_1",			"",	GL_R8,			64, 64, 13, 17,  1,  6, 1));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_r8_1_2",			"",	GL_R8,			64, 64, 13, 17,  1,  6, 2));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_r8_1_4",			"",	GL_R8,			64, 64, 13, 17,  1,  6, 4));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_r8_1_8",			"",	GL_R8,			64, 64, 13, 17,  1,  6, 8));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_r8_63_1",			"",	GL_R8,			64, 64,  1,  9, 63, 30, 1));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_r8_63_2",			"",	GL_R8,			64, 64,  1,  9, 63, 30, 2));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_r8_63_4",			"",	GL_R8,			64, 64,  1,  9, 63, 30, 4));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_r8_63_8",			"",	GL_R8,			64, 64,  1,  9, 63, 30, 8));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4_51_1",		"",	GL_RGBA4,		64, 64,  7, 29, 51, 30, 1));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4_51_2",		"",	GL_RGBA4,		64, 64,  7, 29, 51, 30, 2));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4_51_4",		"",	GL_RGBA4,		64, 64,  7, 29, 51, 30, 4));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4_51_8",		"",	GL_RGBA4,		64, 64,  7, 29, 51, 30, 8));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb8_39_1",			"",	GL_RGB8,		64, 64, 11,  8, 39, 43, 1));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb8_39_2",			"",	GL_RGB8,		64, 64, 11,  8, 39, 43, 2));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb8_39_4",			"",	GL_RGB8,		64, 64, 11,  8, 39, 43, 4));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb8_39_8",			"",	GL_RGB8,		64, 64, 11,  8, 39, 43, 8));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8_47_1",		"",	GL_RGBA8,		64, 64, 10,  1, 47, 27, 1));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8_47_2",		"",	GL_RGBA8,		64, 64, 10,  1, 47, 27, 2));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8_47_4",		"",	GL_RGBA8,		64, 64, 10,  1, 47, 27, 4));
+		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8_47_8",		"",	GL_RGBA8,		64, 64, 10,  1, 47, 27, 8));
+
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_r8_1_1",			"",	GL_R8,			64, 13, 17,  1,  6, 1));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_r8_1_2",			"",	GL_R8,			64, 13, 17,  1,  6, 2));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_r8_1_4",			"",	GL_R8,			64, 13, 17,  1,  6, 4));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_r8_1_8",			"",	GL_R8,			64, 13, 17,  1,  6, 8));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_r8_63_1",			"",	GL_R8,			64,  1,  9, 63, 30, 1));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_r8_63_2",			"",	GL_R8,			64,  1,  9, 63, 30, 2));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_r8_63_4",			"",	GL_R8,			64,  1,  9, 63, 30, 4));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_r8_63_8",			"",	GL_R8,			64,  1,  9, 63, 30, 8));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4_51_1",		"",	GL_RGBA4,		64,  7, 29, 51, 30, 1));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4_51_2",		"",	GL_RGBA4,		64,  7, 29, 51, 30, 2));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4_51_4",		"",	GL_RGBA4,		64,  7, 29, 51, 30, 4));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4_51_8",		"",	GL_RGBA4,		64,  7, 29, 51, 30, 8));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb8_39_1",		"",	GL_RGB8,		64, 11,  8, 39, 43, 1));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb8_39_2",		"",	GL_RGB8,		64, 11,  8, 39, 43, 2));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb8_39_4",		"",	GL_RGB8,		64, 11,  8, 39, 43, 4));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb8_39_8",		"",	GL_RGB8,		64, 11,  8, 39, 43, 8));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8_47_1",		"",	GL_RGBA8,		64, 10,  1, 47, 27, 1));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8_47_2",		"",	GL_RGBA8,		64, 10,  1, 47, 27, 2));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8_47_4",		"",	GL_RGBA8,		64, 10,  1, 47, 27, 4));
+		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8_47_8",		"",	GL_RGBA8,		64, 10,  1, 47, 27, 8));
+	}
+
+	// glTexSubImage2D() pixel transfer mode cases.
+	{
+		tcu::TestCaseGroup* paramGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage2d_unpack_params", "glTexSubImage2D() pixel transfer mode cases");
+		addChild(paramGroup);
+
+		static const struct
+		{
+			const char*	name;
+			deUint32	format;
+			int			width;
+			int			height;
+			int			subX;
+			int			subY;
+			int			subW;
+			int			subH;
+			int			rowLength;
+			int			skipRows;
+			int			skipPixels;
+			int			alignment;
+		} cases[] =
+		{
+			{ "rgb8_alignment",		GL_RGB8,	54,	60,	11,	7,	31,	30,	0,	0,	0,	2 },
+			{ "rgb8_row_length",	GL_RGB8,	54,	60,	11,	7,	31,	30,	50,	0,	0,	4 },
+			{ "rgb8_skip_rows",		GL_RGB8,	54,	60,	11,	7,	31,	30,	0,	3,	0,	4 },
+			{ "rgb8_skip_pixels",	GL_RGB8,	54,	60,	11,	7,	31,	30,	36,	0,	5,	4 },
+			{ "r8_complex1",		GL_R8,		54,	60,	11,	7,	31, 30, 64, 1,	3,	1 },
+			{ "r8_complex2",		GL_R8,		54,	60,	11,	7,	31, 30, 64, 1,	3,	2 },
+			{ "r8_complex3",		GL_R8,		54,	60,	11,	7,	31, 30, 64, 1,	3,	4 },
+			{ "r8_complex4",		GL_R8,		54,	60,	11,	7,	31, 30, 64, 1,	3,	8 },
+			{ "rgba8_complex1",		GL_RGBA8,	92,	84,	13,	19,	56,	61,	69,	0,	0,	8 },
+			{ "rgba8_complex2",		GL_RGBA8,	92,	84,	13,	19,	56,	61,	69,	0,	7,	8 },
+			{ "rgba8_complex3",		GL_RGBA8,	92,	84,	13,	19,	56,	61,	69,	3,	0,	8 },
+			{ "rgba8_complex4",		GL_RGBA8,	92,	84,	13,	19,	56,	61,	69,	3,	7,	8 },
+			{ "rgba32f_complex",	GL_RGBA32F,	92,	84,	13,	19,	56,	61,	69,	3,	7,	8 }
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
+			paramGroup->addChild(new TexSubImage2DParamsCase(m_context, cases[ndx].name, "",
+															 cases[ndx].format,
+															 cases[ndx].width,
+															 cases[ndx].height,
+															 cases[ndx].subX,
+															 cases[ndx].subY,
+															 cases[ndx].subW,
+															 cases[ndx].subH,
+															 cases[ndx].rowLength,
+															 cases[ndx].skipRows,
+															 cases[ndx].skipPixels,
+															 cases[ndx].alignment));
+	}
+
+	// glTexSubImage2D() PBO cases.
+	{
+		tcu::TestCaseGroup* pboGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage2d_pbo", "glTexSubImage2D() pixel buffer object tests");
+		addChild(pboGroup);
+
+		static const struct
+		{
+			const char*	name;
+			deUint32	format;
+			int			width;
+			int			height;
+			int			subX;
+			int			subY;
+			int			subW;
+			int			subH;
+			int			rowLength;
+			int			skipRows;
+			int			skipPixels;
+			int			alignment;
+			int			offset;
+		} paramCases[] =
+		{
+			{ "rgb8_offset",		GL_RGB8,	54,	60,	11,	7,	31,	30,	0,	0,	0,	4,	67 },
+			{ "rgb8_alignment",		GL_RGB8,	54,	60,	11,	7,	31,	30,	0,	0,	0,	2,	0 },
+			{ "rgb8_row_length",	GL_RGB8,	54,	60,	11,	7,	31,	30,	50,	0,	0,	4,	0 },
+			{ "rgb8_skip_rows",		GL_RGB8,	54,	60,	11,	7,	31,	30,	0,	3,	0,	4,	0 },
+			{ "rgb8_skip_pixels",	GL_RGB8,	54,	60,	11,	7,	31,	30,	36,	0,	5,	4,	0 }
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorFormats); ndx++)
+		{
+			pboGroup->addChild(new TexSubImage2DBufferCase(m_context, (std::string(colorFormats[ndx].name) + "_2d").c_str(), "",
+														   colorFormats[ndx].internalFormat,
+														   54,	// Width
+														   60,	// Height
+														   11,	// Sub X
+														   7,	// Sub Y
+														   31,	// Sub W
+														   30,	// Sub H
+														   0,	// Row len
+														   0,	// Skip rows
+														   0,	// Skip pixels
+														   4,	// Alignment
+														   0	/* offset */));
+			pboGroup->addChild(new TexSubImageCubeBufferCase(m_context, (std::string(colorFormats[ndx].name) + "_cube").c_str(), "",
+														   colorFormats[ndx].internalFormat,
+														   64,	// Size
+														   11,	// Sub X
+														   7,	// Sub Y
+														   31,	// Sub W
+														   30,	// Sub H
+														   0,	// Row len
+														   0,	// Skip rows
+														   0,	// Skip pixels
+														   4,	// Alignment
+														   0	/* offset */));
+		}
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(paramCases); ndx++)
+		{
+			pboGroup->addChild(new TexSubImage2DBufferCase(m_context, (std::string(paramCases[ndx].name) + "_2d").c_str(), "",
+														   paramCases[ndx].format,
+														   paramCases[ndx].width,
+														   paramCases[ndx].height,
+														   paramCases[ndx].subX,
+														   paramCases[ndx].subY,
+														   paramCases[ndx].subW,
+														   paramCases[ndx].subH,
+														   paramCases[ndx].rowLength,
+														   paramCases[ndx].skipRows,
+														   paramCases[ndx].skipPixels,
+														   paramCases[ndx].alignment,
+														   paramCases[ndx].offset));
+			pboGroup->addChild(new TexSubImageCubeBufferCase(m_context, (std::string(paramCases[ndx].name) + "_cube").c_str(), "",
+														   paramCases[ndx].format,
+														   paramCases[ndx].width,
+														   paramCases[ndx].subX,
+														   paramCases[ndx].subY,
+														   paramCases[ndx].subW,
+														   paramCases[ndx].subH,
+														   paramCases[ndx].rowLength,
+														   paramCases[ndx].skipRows,
+														   paramCases[ndx].skipPixels,
+														   paramCases[ndx].alignment,
+														   paramCases[ndx].offset));
+		}
+	}
+
+	// glTexSubImage2D() depth cases.
+	{
+		tcu::TestCaseGroup* shadow2dGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage2d_depth", "glTexSubImage2D() with depth or depth/stencil format");
+		addChild(shadow2dGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
+		{
+			const int	tex2DWidth	= 64;
+			const int	tex2DHeight	= 32;
+
+			shadow2dGroup->addChild(new TexSubImage2DDepthCase(m_context, depthStencilFormats[ndx].name, "", depthStencilFormats[ndx].internalFormat, tex2DWidth, tex2DHeight));
+		}
+	}
+
+	// Basic glCopyTexImage2D() cases
+	{
+		tcu::TestCaseGroup* copyTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_copyteximage2d", "Basic glCopyTexImage2D() usage");
+		addChild(copyTexImageGroup);
+
+		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_alpha",				"",	GL_ALPHA,			128, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_luminance",			"",	GL_LUMINANCE,		128, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	128, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_rgb",				"",	GL_RGB,				128, 64));
+		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_rgba",				"",	GL_RGBA,			128, 64));
+
+		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_alpha",			"",	GL_ALPHA,			64));
+		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_luminance",		"",	GL_LUMINANCE,		64));
+		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	64));
+		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_rgb",				"",	GL_RGB,				64));
+		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_rgba",			"",	GL_RGBA,			64));
+	}
+
+	// Basic glCopyTexSubImage2D() cases
+	{
+		tcu::TestCaseGroup* copyTexSubImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_copytexsubimage2d", "Basic glCopyTexSubImage2D() usage");
+		addChild(copyTexSubImageGroup);
+
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_alpha",				"",	GL_ALPHA,			GL_UNSIGNED_BYTE, 128, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_luminance",			"",	GL_LUMINANCE,		GL_UNSIGNED_BYTE, 128, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE, 128, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_rgb",				"",	GL_RGB,				GL_UNSIGNED_BYTE, 128, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_rgba",				"",	GL_RGBA,			GL_UNSIGNED_BYTE, 128, 64));
+
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_alpha",			"",	GL_ALPHA,			GL_UNSIGNED_BYTE, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_luminance",		"",	GL_LUMINANCE,		GL_UNSIGNED_BYTE, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_rgb",				"",	GL_RGB,				GL_UNSIGNED_BYTE, 64));
+		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_rgba",			"",	GL_RGBA,			GL_UNSIGNED_BYTE, 64));
+	}
+
+	// Basic TexImage3D usage.
+	{
+		tcu::TestCaseGroup* basicTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_teximage3d", "Basic glTexImage3D() usage");
+		addChild(basicTexImageGroup);
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const char*	fmtName				= colorFormats[formatNdx].name;
+			deUint32	format				= colorFormats[formatNdx].internalFormat;
+			const int	tex2DArrayWidth		= 57;
+			const int	tex2DArrayHeight	= 44;
+			const int	tex2DArrayLevels	= 5;
+			const int	tex3DWidth			= 63;
+			const int	tex3DHeight			= 29;
+			const int	tex3DDepth			= 11;
+
+			basicTexImageGroup->addChild(new BasicTexImage2DArrayCase	(m_context,	(string(fmtName) + "_2d_array").c_str(),	"",	format, tex2DArrayWidth, tex2DArrayHeight, tex2DArrayLevels));
+			basicTexImageGroup->addChild(new BasicTexImage3DCase		(m_context,	(string(fmtName) + "_3d").c_str(),			"",	format, tex3DWidth, tex3DHeight, tex3DDepth));
+		}
+	}
+
+	// glTexImage3D() unpack params cases.
+	{
+		tcu::TestCaseGroup* paramGroup = new tcu::TestCaseGroup(m_testCtx, "teximage3d_unpack_params", "glTexImage3D() unpack parameters");
+		addChild(paramGroup);
+
+		static const struct
+		{
+			const char*	name;
+			deUint32	format;
+			int			width;
+			int			height;
+			int			depth;
+			int			imageHeight;
+			int			rowLength;
+			int			skipImages;
+			int			skipRows;
+			int			skipPixels;
+			int			alignment;
+		} cases[] =
+		{
+			{ "rgb8_image_height",	GL_RGB8,	23,	19,	8,	26,	0,	0,	0,	0,	4 },
+			{ "rgb8_row_length",	GL_RGB8,	23,	19,	8,	0,	27,	0,	0,	0,	4 },
+			{ "rgb8_skip_images",	GL_RGB8,	23,	19,	8,	0,	0,	3,	0,	0,	4 },
+			{ "rgb8_skip_rows",		GL_RGB8,	23,	19,	8,	22,	0,	0,	3,	0,	4 },
+			{ "rgb8_skip_pixels",	GL_RGB8,	23,	19,	8,	0,	25,	0,	0,	2,	4 },
+			{ "r8_complex1",		GL_R8,		13, 17, 11,	23,	15,	2,	3,	1,	1 },
+			{ "r8_complex2",		GL_R8,		13, 17, 11,	23,	15,	2,	3,	1,	2 },
+			{ "r8_complex3",		GL_R8,		13, 17, 11,	23,	15,	2,	3,	1,	4 },
+			{ "r8_complex4",		GL_R8,		13, 17, 11,	23,	15,	2,	3,	1,	8 },
+			{ "rgba8_complex1",		GL_RGBA8,	11,	20,	8,	25,	14,	0,	0,	0,	8 },
+			{ "rgba8_complex2",		GL_RGBA8,	11,	20,	8,	25,	14,	0,	2,	0,	8 },
+			{ "rgba8_complex3",		GL_RGBA8,	11,	20,	8,	25,	14,	0,	0,	3,	8 },
+			{ "rgba8_complex4",		GL_RGBA8,	11,	20,	8,	25,	14,	0,	2,	3,	8 },
+			{ "rgba32f_complex",	GL_RGBA32F,	11,	20,	8,	25,	14,	0,	2,	3,	8 }
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
+			paramGroup->addChild(new TexImage3DParamsCase(m_context, cases[ndx].name, "",
+														  cases[ndx].format,
+														  cases[ndx].width,
+														  cases[ndx].height,
+														  cases[ndx].depth,
+														  cases[ndx].imageHeight,
+														  cases[ndx].rowLength,
+														  cases[ndx].skipImages,
+														  cases[ndx].skipRows,
+														  cases[ndx].skipPixels,
+														  cases[ndx].alignment));
+	}
+
+	// glTexImage3D() pbo cases.
+	{
+		tcu::TestCaseGroup* pboGroup = new tcu::TestCaseGroup(m_testCtx, "teximage3d_pbo", "glTexImage3D() from PBO");
+		addChild(pboGroup);
+
+		// Parameter cases
+		static const struct
+		{
+			const char*	name;
+			deUint32	format;
+			int			width;
+			int			height;
+			int			depth;
+			int			imageHeight;
+			int			rowLength;
+			int			skipImages;
+			int			skipRows;
+			int			skipPixels;
+			int			alignment;
+			int			offset;
+		} parameterCases[] =
+		{
+			{ "rgb8_offset",		GL_RGB8,	23,	19,	8,	0,	0,	0,	0,	0,	1,	67 },
+			{ "rgb8_alignment",		GL_RGB8,	23,	19,	8,	0,	0,	0,	0,	0,	2,	0 },
+			{ "rgb8_image_height",	GL_RGB8,	23,	19,	8,	26,	0,	0,	0,	0,	4,	0 },
+			{ "rgb8_row_length",	GL_RGB8,	23,	19,	8,	0,	27,	0,	0,	0,	4,	0 },
+			{ "rgb8_skip_images",	GL_RGB8,	23,	19,	8,	0,	0,	3,	0,	0,	4,	0 },
+			{ "rgb8_skip_rows",		GL_RGB8,	23,	19,	8,	22,	0,	0,	3,	0,	4,	0 },
+			{ "rgb8_skip_pixels",	GL_RGB8,	23,	19,	8,	0,	25,	0,	0,	2,	4,	0 }
+		};
+
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const string	fmtName		= colorFormats[formatNdx].name;
+			const deUint32	format		= colorFormats[formatNdx].internalFormat;
+			const int		tex3DWidth	= 11;
+			const int		tex3DHeight	= 20;
+			const int		tex3DDepth	= 8;
+
+			pboGroup->addChild(new TexImage2DArrayBufferCase	(m_context, (fmtName + "_2d_array").c_str(),	"", format, tex3DWidth, tex3DHeight, tex3DDepth, 0, 0, 0, 0, 0, 4, 0));
+			pboGroup->addChild(new TexImage3DBufferCase			(m_context, (fmtName + "_3d").c_str(),			"", format, tex3DWidth, tex3DHeight, tex3DDepth, 0, 0, 0, 0, 0, 4, 0));
+		}
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(parameterCases); ndx++)
+		{
+			pboGroup->addChild(new TexImage2DArrayBufferCase(m_context, (string(parameterCases[ndx].name) + "_2d_array").c_str(), "",
+														parameterCases[ndx].format,
+														parameterCases[ndx].width,
+														parameterCases[ndx].depth,
+														parameterCases[ndx].height,
+														parameterCases[ndx].imageHeight,
+														parameterCases[ndx].rowLength,
+														parameterCases[ndx].skipImages,
+														parameterCases[ndx].skipRows,
+														parameterCases[ndx].skipPixels,
+														parameterCases[ndx].alignment,
+														parameterCases[ndx].offset));
+			pboGroup->addChild(new TexImage3DBufferCase(m_context, (string(parameterCases[ndx].name) + "_3d").c_str(), "",
+														parameterCases[ndx].format,
+														parameterCases[ndx].width,
+														parameterCases[ndx].depth,
+														parameterCases[ndx].height,
+														parameterCases[ndx].imageHeight,
+														parameterCases[ndx].rowLength,
+														parameterCases[ndx].skipImages,
+														parameterCases[ndx].skipRows,
+														parameterCases[ndx].skipPixels,
+														parameterCases[ndx].alignment,
+														parameterCases[ndx].offset));
+		}
+	}
+
+	// glTexImage3D() depth cases.
+	{
+		tcu::TestCaseGroup* shadow3dGroup = new tcu::TestCaseGroup(m_testCtx, "teximage3d_depth", "glTexImage3D() with depth or depth/stencil format");
+		addChild(shadow3dGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
+		{
+			const int	tex3DWidth	= 32;
+			const int	tex3DHeight	= 64;
+			const int	tex3DDepth	= 8;
+
+			shadow3dGroup->addChild(new TexImage2DArrayDepthCase(m_context, (std::string(depthStencilFormats[ndx].name) + "_2d_array").c_str(), "", depthStencilFormats[ndx].internalFormat, tex3DWidth, tex3DHeight, tex3DDepth));
+		}
+	}
+
+	// glTexImage3D() depth cases with pbo.
+	{
+		tcu::TestCaseGroup* shadow3dGroup = new tcu::TestCaseGroup(m_testCtx, "teximage3d_depth_pbo", "glTexImage3D() with depth or depth/stencil format with pbo");
+		addChild(shadow3dGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
+		{
+			const int	tex3DWidth	= 32;
+			const int	tex3DHeight	= 64;
+			const int	tex3DDepth	= 8;
+
+			shadow3dGroup->addChild(new TexImage2DArrayDepthBufferCase(m_context, (std::string(depthStencilFormats[ndx].name) + "_2d_array").c_str(), "", depthStencilFormats[ndx].internalFormat, tex3DWidth, tex3DHeight, tex3DDepth));
+		}
+	}
+
+	// Basic TexSubImage3D usage.
+	{
+		tcu::TestCaseGroup* basicTexSubImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_texsubimage3d", "Basic glTexSubImage3D() usage");
+		addChild(basicTexSubImageGroup);
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const char*	fmtName		= colorFormats[formatNdx].name;
+			deUint32	format		= colorFormats[formatNdx].internalFormat;
+			const int	tex3DWidth	= 32;
+			const int	tex3DHeight	= 64;
+			const int	tex3DDepth	= 8;
+
+			basicTexSubImageGroup->addChild(new BasicTexSubImage3DCase(m_context, (string(fmtName) + "_3d").c_str(), "", format, tex3DWidth, tex3DHeight, tex3DDepth));
+		}
+	}
+
+	// glTexSubImage3D() unpack params cases.
+	{
+		tcu::TestCaseGroup* paramGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage3d_unpack_params", "glTexSubImage3D() unpack parameters");
+		addChild(paramGroup);
+
+		static const struct
+		{
+			const char*	name;
+			deUint32	format;
+			int			width;
+			int			height;
+			int			depth;
+			int			subX;
+			int			subY;
+			int			subZ;
+			int			subW;
+			int			subH;
+			int			subD;
+			int			imageHeight;
+			int			rowLength;
+			int			skipImages;
+			int			skipRows;
+			int			skipPixels;
+			int			alignment;
+		} cases[] =
+		{
+			{ "rgb8_image_height",	GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	26,	0,	0,	0,	0,	4 },
+			{ "rgb8_row_length",	GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	0,	27,	0,	0,	0,	4 },
+			{ "rgb8_skip_images",	GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	0,	0,	3,	0,	0,	4 },
+			{ "rgb8_skip_rows",		GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	22,	0,	0,	3,	0,	4 },
+			{ "rgb8_skip_pixels",	GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	0,	25,	0,	0,	2,	4 },
+			{ "r8_complex1",		GL_R8,		15,	20,	11,	1,	1,	0,	13, 17, 11,	23,	15,	2,	3,	1,	1 },
+			{ "r8_complex2",		GL_R8,		15,	20,	11,	1,	1,	0,	13, 17, 11,	23,	15,	2,	3,	1,	2 },
+			{ "r8_complex3",		GL_R8,		15,	20,	11,	1,	1,	0,	13, 17, 11,	23,	15,	2,	3,	1,	4 },
+			{ "r8_complex4",		GL_R8,		15,	20,	11,	1,	1,	0,	13, 17, 11,	23,	15,	2,	3,	1,	8 },
+			{ "rgba8_complex1",		GL_RGBA8,	15,	25,	10,	0,	5,	1,	11,	20,	8,	25,	14,	0,	0,	0,	8 },
+			{ "rgba8_complex2",		GL_RGBA8,	15,	25,	10,	0,	5,	1,	11,	20,	8,	25,	14,	0,	2,	0,	8 },
+			{ "rgba8_complex3",		GL_RGBA8,	15,	25,	10,	0,	5,	1,	11,	20,	8,	25,	14,	0,	0,	3,	8 },
+			{ "rgba8_complex4",		GL_RGBA8,	15,	25,	10,	0,	5,	1,	11,	20,	8,	25,	14,	0,	2,	3,	8 },
+			{ "rgba32f_complex",	GL_RGBA32F,	15,	25,	10,	0,	5,	1,	11,	20,	8,	25,	14,	0,	2,	3,	8 }
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
+			paramGroup->addChild(new TexSubImage3DParamsCase(m_context, cases[ndx].name, "",
+															 cases[ndx].format,
+															 cases[ndx].width,
+															 cases[ndx].height,
+															 cases[ndx].depth,
+															 cases[ndx].subX,
+															 cases[ndx].subY,
+															 cases[ndx].subZ,
+															 cases[ndx].subW,
+															 cases[ndx].subH,
+															 cases[ndx].subD,
+															 cases[ndx].imageHeight,
+															 cases[ndx].rowLength,
+															 cases[ndx].skipImages,
+															 cases[ndx].skipRows,
+															 cases[ndx].skipPixels,
+															 cases[ndx].alignment));
+	}
+
+	// glTexSubImage3D() PBO cases.
+	{
+		tcu::TestCaseGroup* pboGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage3d_pbo", "glTexSubImage3D() pixel buffer object tests");
+		addChild(pboGroup);
+
+		static const struct
+		{
+			const char*	name;
+			deUint32	format;
+			int			width;
+			int			height;
+			int			depth;
+			int			subX;
+			int			subY;
+			int			subZ;
+			int			subW;
+			int			subH;
+			int			subD;
+			int			imageHeight;
+			int			rowLength;
+			int			skipImages;
+			int			skipRows;
+			int			skipPixels;
+			int			alignment;
+			int			offset;
+		} paramCases[] =
+		{
+			{ "rgb8_offset",		GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	0,	0,	0,	0,	0,	4,	67 },
+			{ "rgb8_image_height",	GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	26,	0,	0,	0,	0,	4,	0 },
+			{ "rgb8_row_length",	GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	0,	27,	0,	0,	0,	4,	0 },
+			{ "rgb8_skip_images",	GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	0,	0,	3,	0,	0,	4,	0 },
+			{ "rgb8_skip_rows",		GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	22,	0,	0,	3,	0,	4,	0 },
+			{ "rgb8_skip_pixels",	GL_RGB8,	26, 25, 10,	1,	2,	1,	23,	19,	8,	0,	25,	0,	0,	2,	4,	0 }
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorFormats); ndx++)
+		{
+			pboGroup->addChild(new TexSubImage2DArrayBufferCase(m_context, (std::string(colorFormats[ndx].name) + "_2d_array").c_str(), "",
+														   colorFormats[ndx].internalFormat,
+														   26,	// Width
+														   25,	// Height
+														   10,	// Depth
+														   1,	// Sub X
+														   2,	// Sub Y
+														   0,	// Sub Z
+														   23,	// Sub W
+														   19,	// Sub H
+														   8,	// Sub D
+														   0,	// Image height
+														   0,	// Row length
+														   0,	// Skip images
+														   0,	// Skip rows
+														   0,	// Skip pixels
+														   4,	// Alignment
+														   0	/* offset */));
+			pboGroup->addChild(new TexSubImage3DBufferCase(m_context, (std::string(colorFormats[ndx].name) + "_3d").c_str(), "",
+														   colorFormats[ndx].internalFormat,
+														   26,	// Width
+														   25,	// Height
+														   10,	// Depth
+														   1,	// Sub X
+														   2,	// Sub Y
+														   0,	// Sub Z
+														   23,	// Sub W
+														   19,	// Sub H
+														   8,	// Sub D
+														   0,	// Image height
+														   0,	// Row length
+														   0,	// Skip images
+														   0,	// Skip rows
+														   0,	// Skip pixels
+														   4,	// Alignment
+														   0	/* offset */));
+		}
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(paramCases); ndx++)
+		{
+			pboGroup->addChild(new TexSubImage2DArrayBufferCase(m_context, (std::string(paramCases[ndx].name) + "_2d_array").c_str(), "",
+														   paramCases[ndx].format,
+														   paramCases[ndx].width,
+														   paramCases[ndx].height,
+														   paramCases[ndx].depth,
+														   paramCases[ndx].subX,
+														   paramCases[ndx].subY,
+														   paramCases[ndx].subZ,
+														   paramCases[ndx].subW,
+														   paramCases[ndx].subH,
+														   paramCases[ndx].subD,
+														   paramCases[ndx].imageHeight,
+														   paramCases[ndx].rowLength,
+														   paramCases[ndx].skipImages,
+														   paramCases[ndx].skipRows,
+														   paramCases[ndx].skipPixels,
+														   paramCases[ndx].alignment,
+														   paramCases[ndx].offset));
+			pboGroup->addChild(new TexSubImage3DBufferCase(m_context, (std::string(paramCases[ndx].name) + "_3d").c_str(), "",
+														   paramCases[ndx].format,
+														   paramCases[ndx].width,
+														   paramCases[ndx].height,
+														   paramCases[ndx].depth,
+														   paramCases[ndx].subX,
+														   paramCases[ndx].subY,
+														   paramCases[ndx].subZ,
+														   paramCases[ndx].subW,
+														   paramCases[ndx].subH,
+														   paramCases[ndx].subD,
+														   paramCases[ndx].imageHeight,
+														   paramCases[ndx].rowLength,
+														   paramCases[ndx].skipImages,
+														   paramCases[ndx].skipRows,
+														   paramCases[ndx].skipPixels,
+														   paramCases[ndx].alignment,
+														   paramCases[ndx].offset));
+		}
+	}
+
+	// glTexSubImage3D() depth cases.
+	{
+		tcu::TestCaseGroup* shadow3dGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage3d_depth", "glTexSubImage3D() with depth or depth/stencil format");
+		addChild(shadow3dGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
+		{
+			const int	tex2DArrayWidth		= 57;
+			const int	tex2DArrayHeight	= 44;
+			const int	tex2DArrayLevels	= 5;
+
+			shadow3dGroup->addChild(new TexSubImage2DArrayDepthCase(m_context, (std::string(depthStencilFormats[ndx].name) + "_2d_array").c_str(), "", depthStencilFormats[ndx].internalFormat, tex2DArrayWidth, tex2DArrayHeight, tex2DArrayLevels));
+		}
+	}
+
+	// glTexStorage2D() cases.
+	{
+		tcu::TestCaseGroup* texStorageGroup = new tcu::TestCaseGroup(m_testCtx, "texstorage2d", "Basic glTexStorage2D() usage");
+		addChild(texStorageGroup);
+
+		// All formats.
+		tcu::TestCaseGroup* formatGroup = new tcu::TestCaseGroup(m_testCtx, "format", "glTexStorage2D() with all formats");
+		texStorageGroup->addChild(formatGroup);
+
+		// Color formats.
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const char*	fmtName			= colorFormats[formatNdx].name;
+			deUint32	internalFormat	= colorFormats[formatNdx].internalFormat;
+			const int	tex2DWidth		= 117;
+			const int	tex2DHeight		= 97;
+			int			tex2DLevels		= maxLevelCount(tex2DWidth, tex2DHeight);
+			const int	cubeSize		= 57;
+			int			cubeLevels		= maxLevelCount(cubeSize, cubeSize);
+
+			formatGroup->addChild(new BasicTexStorage2DCase		(m_context, (string(fmtName) + "_2d").c_str(),		"", internalFormat, tex2DWidth, tex2DHeight, tex2DLevels));
+			formatGroup->addChild(new BasicTexStorageCubeCase	(m_context, (string(fmtName) + "_cube").c_str(),	"", internalFormat, cubeSize, cubeLevels));
+		}
+
+		// Depth / stencil formats.
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); formatNdx++)
+		{
+			const char*	fmtName			= depthStencilFormats[formatNdx].name;
+			deUint32	internalFormat	= depthStencilFormats[formatNdx].internalFormat;
+			const int	tex2DWidth		= 117;
+			const int	tex2DHeight		= 97;
+			int			tex2DLevels		= maxLevelCount(tex2DWidth, tex2DHeight);
+			const int	cubeSize		= 57;
+			int			cubeLevels		= maxLevelCount(cubeSize, cubeSize);
+
+			formatGroup->addChild(new BasicTexStorage2DCase		(m_context, (string(fmtName) + "_2d").c_str(),		"", internalFormat, tex2DWidth, tex2DHeight, tex2DLevels));
+			formatGroup->addChild(new BasicTexStorageCubeCase	(m_context, (string(fmtName) + "_cube").c_str(),	"", internalFormat, cubeSize, cubeLevels));
+		}
+
+		// Sizes.
+		static const struct
+		{
+			int				width;
+			int				height;
+			int				levels;
+		} tex2DSizes[] =
+		{
+			//	W	H	L
+			{	1,	1,	1 },
+			{	2,	2,	2 },
+			{	64,	32,	7 },
+			{	32,	64,	4 },
+			{	57,	63,	1 },
+			{	57,	63,	2 },
+			{	57,	63,	6 }
+		};
+		static const struct
+		{
+			int		size;
+			int		levels;
+		} cubeSizes[] =
+		{
+			//	S	L
+			{	1,	1 },
+			{	2,	2 },
+			{	57,	1 },
+			{	57,	2 },
+			{	57,	6 },
+			{	64,	4 },
+			{	64,	7 },
+		};
+
+		tcu::TestCaseGroup* sizeGroup = new tcu::TestCaseGroup(m_testCtx, "size", "glTexStorage2D() with various sizes");
+		texStorageGroup->addChild(sizeGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(tex2DSizes); ndx++)
+		{
+			const deUint32		format		= GL_RGBA8;
+			int					width		= tex2DSizes[ndx].width;
+			int					height		= tex2DSizes[ndx].height;
+			int					levels		= tex2DSizes[ndx].levels;
+			string				name		= string("2d_") + de::toString(width) + "x" + de::toString(height) + "_" + de::toString(levels) + "_levels";
+
+			sizeGroup->addChild(new BasicTexStorage2DCase(m_context, name.c_str(), "", format, width, height, levels));
+		}
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cubeSizes); ndx++)
+		{
+			const deUint32		format		= GL_RGBA8;
+			int					size		= cubeSizes[ndx].size;
+			int					levels		= cubeSizes[ndx].levels;
+			string				name		= string("cube_") + de::toString(size) + "x" + de::toString(size) + "_" + de::toString(levels) + "_levels";
+
+			sizeGroup->addChild(new BasicTexStorageCubeCase(m_context, name.c_str(), "", format, size, levels));
+		}
+	}
+
+	// glTexStorage3D() cases.
+	{
+		tcu::TestCaseGroup* texStorageGroup = new tcu::TestCaseGroup(m_testCtx, "texstorage3d", "Basic glTexStorage3D() usage");
+		addChild(texStorageGroup);
+
+		// All formats.
+		tcu::TestCaseGroup* formatGroup = new tcu::TestCaseGroup(m_testCtx, "format", "glTexStorage3D() with all formats");
+		texStorageGroup->addChild(formatGroup);
+
+		// Color formats.
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const char*	fmtName				= colorFormats[formatNdx].name;
+			deUint32	internalFormat		= colorFormats[formatNdx].internalFormat;
+			const int	tex2DArrayWidth		= 57;
+			const int	tex2DArrayHeight	= 13;
+			const int	tex2DArrayLayers	= 7;
+			int			tex2DArrayLevels	= maxLevelCount(tex2DArrayWidth, tex2DArrayHeight);
+			const int	tex3DWidth			= 59;
+			const int	tex3DHeight			= 37;
+			const int	tex3DDepth			= 11;
+			int			tex3DLevels			= maxLevelCount(tex3DWidth, tex3DHeight, tex3DDepth);
+
+			formatGroup->addChild(new BasicTexStorage2DArrayCase	(m_context, (string(fmtName) + "_2d_array").c_str(),	"", internalFormat, tex2DArrayWidth, tex2DArrayHeight, tex2DArrayLayers, tex2DArrayLevels));
+			formatGroup->addChild(new BasicTexStorage3DCase			(m_context, (string(fmtName) + "_3d").c_str(),			"", internalFormat, tex3DWidth, tex3DHeight, tex3DDepth, tex3DLevels));
+		}
+
+		// Depth/stencil formats (only 2D texture array is supported).
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); formatNdx++)
+		{
+			const char*	fmtName				= depthStencilFormats[formatNdx].name;
+			deUint32	internalFormat		= depthStencilFormats[formatNdx].internalFormat;
+			const int	tex2DArrayWidth		= 57;
+			const int	tex2DArrayHeight	= 13;
+			const int	tex2DArrayLayers	= 7;
+			int			tex2DArrayLevels	= maxLevelCount(tex2DArrayWidth, tex2DArrayHeight);
+
+			formatGroup->addChild(new BasicTexStorage2DArrayCase	(m_context, (string(fmtName) + "_2d_array").c_str(),	"", internalFormat, tex2DArrayWidth, tex2DArrayHeight, tex2DArrayLayers, tex2DArrayLevels));
+		}
+
+		// Sizes.
+		static const struct
+		{
+			int				width;
+			int				height;
+			int				layers;
+			int				levels;
+		} tex2DArraySizes[] =
+		{
+			//	W	H	La	Le
+			{	1,	1,	1,	1 },
+			{	2,	2,	2,	2 },
+			{	64,	32,	3,	7 },
+			{	32,	64,	3,	4 },
+			{	57,	63,	5,	1 },
+			{	57,	63,	5,	2 },
+			{	57,	63,	5,	6 }
+		};
+		static const struct
+		{
+			int				width;
+			int				height;
+			int				depth;
+			int				levels;
+		} tex3DSizes[] =
+		{
+			//	W	H	D	L
+			{	1,	1,	1,	1 },
+			{	2,	2,	2,	2 },
+			{	64,	32,	16,	7 },
+			{	32,	64,	16,	4 },
+			{	32,	16,	64,	4 },
+			{	57,	63,	11,	1 },
+			{	57,	63,	11,	2 },
+			{	57,	63,	11,	6 }
+		};
+
+		tcu::TestCaseGroup* sizeGroup = new tcu::TestCaseGroup(m_testCtx, "size", "glTexStorage2D() with various sizes");
+		texStorageGroup->addChild(sizeGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(tex2DArraySizes); ndx++)
+		{
+			const deUint32		format		= GL_RGBA8;
+			int					width		= tex2DArraySizes[ndx].width;
+			int					height		= tex2DArraySizes[ndx].height;
+			int					layers		= tex2DArraySizes[ndx].layers;
+			int					levels		= tex2DArraySizes[ndx].levels;
+			string				name		= string("2d_array_") + de::toString(width) + "x" + de::toString(height) + "x" + de::toString(layers) + "_" + de::toString(levels) + "_levels";
+
+			sizeGroup->addChild(new BasicTexStorage2DArrayCase(m_context, name.c_str(), "", format, width, height, layers, levels));
+		}
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(tex3DSizes); ndx++)
+		{
+			const deUint32		format		= GL_RGBA8;
+			int					width		= tex3DSizes[ndx].width;
+			int					height		= tex3DSizes[ndx].height;
+			int					depth		= tex3DSizes[ndx].depth;
+			int					levels		= tex3DSizes[ndx].levels;
+			string				name		= string("3d_") + de::toString(width) + "x" + de::toString(height) + "x" + de::toString(depth) + "_" + de::toString(levels) + "_levels";
+
+			sizeGroup->addChild(new BasicTexStorage3DCase(m_context, name.c_str(), "", format, width, height, depth, levels));
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTextureSpecificationTests.hpp b/modules/gles3/functional/es3fTextureSpecificationTests.hpp
new file mode 100644
index 0000000..22ba249
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureSpecificationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FTEXTURESPECIFICATIONTESTS_HPP
+#define _ES3FTEXTURESPECIFICATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture specification tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class TextureSpecificationTests : public TestCaseGroup
+{
+public:
+									TextureSpecificationTests		(Context& context);
+									~TextureSpecificationTests		(void);
+
+	void							init							(void);
+
+private:
+									TextureSpecificationTests		(const TextureSpecificationTests& other);
+	TextureSpecificationTests&		operator=						(const TextureSpecificationTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTEXTURESPECIFICATIONTESTS_HPP
diff --git a/modules/gles3/functional/es3fTextureStateQueryTests.cpp b/modules/gles3/functional/es3fTextureStateQueryTests.cpp
new file mode 100644
index 0000000..314119a
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureStateQueryTests.cpp
@@ -0,0 +1,777 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTextureStateQueryTests.hpp"
+#include "es3fApiCase.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+
+using namespace glw; // GLint and other GL types
+using namespace deqp::gls;
+using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace TextureParamVerifiers
+{
+
+// TexParamVerifier
+
+class TexParamVerifier : protected glu::CallLogWrapper
+{
+public:
+						TexParamVerifier	(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
+	virtual				~TexParamVerifier	(); // make GCC happy
+
+	const char*			getTestNamePostfix	(void) const;
+
+	virtual void		verifyInteger		(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)	= DE_NULL;
+	virtual void		verifyFloat			(tcu::TestContext& testCtx, GLenum target, GLenum name, GLfloat reference)	= DE_NULL;
+private:
+	const char*	const	m_testNamePostfix;
+};
+
+TexParamVerifier::TexParamVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
+	: glu::CallLogWrapper	(gl, log)
+	, m_testNamePostfix		(testNamePostfix)
+{
+	enableLogging(true);
+}
+TexParamVerifier::~TexParamVerifier ()
+{
+}
+
+const char* TexParamVerifier::getTestNamePostfix (void) const
+{
+	return m_testNamePostfix;
+}
+
+class GetTexParameterIVerifier : public TexParamVerifier
+{
+public:
+			GetTexParameterIVerifier	(const glw::Functions& gl, tcu::TestLog& log);
+
+	void	verifyInteger				(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference);
+	void	verifyFloat					(tcu::TestContext& testCtx, GLenum target, GLenum name, GLfloat reference);
+};
+
+GetTexParameterIVerifier::GetTexParameterIVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: TexParamVerifier(gl, log, "_gettexparameteri")
+{
+}
+
+void GetTexParameterIVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetTexParameteriv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid texture param value");
+	}
+}
+
+void GetTexParameterIVerifier::verifyFloat (tcu::TestContext& testCtx, GLenum target, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLint> state;
+	glGetTexParameteriv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	const GLint expectedGLStateMax = StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<GLint>(reference);
+	const GLint expectedGLStateMin = StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<GLint>(reference);
+
+	if (state < expectedGLStateMin || state > expectedGLStateMax)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected in range [" << expectedGLStateMin << ", " << expectedGLStateMax << "]; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid texture param value");
+	}
+}
+
+class GetTexParameterFVerifier : public TexParamVerifier
+{
+public:
+			GetTexParameterFVerifier	(const glw::Functions& gl, tcu::TestLog& log);
+
+	void	verifyInteger				(tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference);
+	void	verifyFloat					(tcu::TestContext& testCtx, GLenum target, GLenum name, GLfloat reference);
+};
+
+GetTexParameterFVerifier::GetTexParameterFVerifier (const glw::Functions& gl, tcu::TestLog& log)
+	: TexParamVerifier(gl, log, "_gettexparameterf")
+{
+}
+
+void GetTexParameterFVerifier::verifyInteger (tcu::TestContext& testCtx, GLenum target, GLenum name, GLint reference)
+{
+	using tcu::TestLog;
+
+	const GLfloat referenceAsFloat = GLfloat(reference);
+	DE_ASSERT(reference == GLint(referenceAsFloat)); // reference integer must have 1:1 mapping to float for this to work. Reference value is always such value in these tests
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetTexParameterfv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != referenceAsFloat)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << referenceAsFloat << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+void GetTexParameterFVerifier::verifyFloat (tcu::TestContext& testCtx, GLenum target, GLenum name, GLfloat reference)
+{
+	using tcu::TestLog;
+
+	StateQueryMemoryWriteGuard<GLfloat> state;
+	glGetTexParameterfv(target, name, &state);
+
+	if (!state.verifyValidity(testCtx))
+		return;
+
+	if (state != reference)
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: expected " << reference << "; got " << state << TestLog::EndMessage;
+
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
+	}
+}
+
+} // TextureParamVerifiers
+
+namespace
+{
+
+using namespace TextureParamVerifiers;
+
+// Tests
+
+class IsTextureCase : public ApiCase
+{
+public:
+	IsTextureCase (Context& context, const char* name, const char* description, GLenum textureTarget)
+		: ApiCase			(context, name, description)
+		, m_textureTarget	(textureTarget)
+	{
+	}
+
+	void test (void)
+	{
+		using tcu::TestLog;
+
+		GLuint textureId = 0;
+		glGenTextures(1, &textureId);
+		glBindTexture(m_textureTarget, textureId);
+		expectError(GL_NO_ERROR);
+
+		checkBooleans(glIsTexture(textureId), GL_TRUE);
+
+		glDeleteTextures(1, &textureId);
+		expectError(GL_NO_ERROR);
+
+		checkBooleans(glIsTexture(textureId), GL_FALSE);
+	}
+
+protected:
+	GLenum										m_textureTarget;
+};
+
+class TextureCase : public ApiCase
+{
+public:
+	TextureCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget)
+		: ApiCase			(context, name, description)
+		, m_textureTarget	(textureTarget)
+		, m_verifier		(verifier)
+	{
+	}
+
+	virtual void testTexture (void) = DE_NULL;
+
+	void test (void)
+	{
+		GLuint textureId = 0;
+		glGenTextures(1, &textureId);
+		glBindTexture(m_textureTarget, textureId);
+		expectError(GL_NO_ERROR);
+
+		testTexture();
+
+		glDeleteTextures(1, &textureId);
+		expectError(GL_NO_ERROR);
+	}
+
+protected:
+	GLenum				m_textureTarget;
+	TexParamVerifier*	m_verifier;
+};
+
+class TextureSwizzleCase : public TextureCase
+{
+public:
+	TextureSwizzleCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget, GLenum valueName, GLenum initialValue)
+		: TextureCase		(context, verifier, name, description, textureTarget)
+		, m_valueName		(valueName)
+		, m_initialValue	(initialValue)
+	{
+	}
+
+	void testTexture (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_valueName, m_initialValue);
+		expectError(GL_NO_ERROR);
+
+		const GLenum swizzleValues[] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_ZERO, GL_ONE};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(swizzleValues); ++ndx)
+		{
+			glTexParameteri(m_textureTarget, m_valueName, swizzleValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_valueName, swizzleValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		//check unit conversions with float
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(swizzleValues); ++ndx)
+		{
+			glTexParameterf(m_textureTarget, m_valueName, (GLfloat)swizzleValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_valueName, swizzleValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	GLenum m_valueName;
+	GLenum m_initialValue;
+};
+
+class TextureWrapCase : public TextureCase
+{
+public:
+	TextureWrapCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget, GLenum valueName)
+		: TextureCase	(context, verifier, name, description, textureTarget)
+		, m_valueName	(valueName)
+	{
+	}
+
+	void testTexture (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_valueName, GL_REPEAT);
+		expectError(GL_NO_ERROR);
+
+		const GLenum wrapValues[] = {GL_CLAMP_TO_EDGE, GL_REPEAT, GL_MIRRORED_REPEAT};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(wrapValues); ++ndx)
+		{
+			glTexParameteri(m_textureTarget, m_valueName, wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_valueName, wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		//check unit conversions with float
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(wrapValues); ++ndx)
+		{
+			glTexParameterf(m_textureTarget, m_valueName, (GLfloat)wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_valueName, wrapValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+
+private:
+	GLenum	m_valueName;
+};
+
+class TextureMagFilterCase : public TextureCase
+{
+public:
+	TextureMagFilterCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget)
+		: TextureCase(context, verifier, name, description, textureTarget)
+	{
+	}
+
+	void testTexture (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		expectError(GL_NO_ERROR);
+
+		const GLenum magValues[] = {GL_NEAREST, GL_LINEAR};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(magValues); ++ndx)
+		{
+			glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, magValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MAG_FILTER, magValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		//check unit conversions with float
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(magValues); ++ndx)
+		{
+			glTexParameterf(m_textureTarget, GL_TEXTURE_MAG_FILTER, (GLfloat)magValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MAG_FILTER, magValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class TextureMinFilterCase : public TextureCase
+{
+public:
+	TextureMinFilterCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget)
+		: TextureCase(context, verifier, name, description, textureTarget)
+	{
+	}
+
+	void testTexture (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
+		expectError(GL_NO_ERROR);
+
+		const GLenum minValues[] = {GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(minValues); ++ndx)
+		{
+			glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, minValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MIN_FILTER, minValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		//check unit conversions with float
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(minValues); ++ndx)
+		{
+			glTexParameterf(m_textureTarget, GL_TEXTURE_MIN_FILTER, (GLfloat)minValues[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_MIN_FILTER, minValues[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class TextureLODCase : public TextureCase
+{
+public:
+	TextureLODCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget, GLenum lodTarget, int initialValue)
+		: TextureCase		(context, verifier, name, description, textureTarget)
+		, m_lodTarget		(lodTarget)
+		, m_initialValue	(initialValue)
+	{
+	}
+
+	void testTexture (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_lodTarget, m_initialValue);
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 60;
+		for (int ndx = 0; ndx < numIterations; ++ndx)
+		{
+			const GLfloat ref = rnd.getFloat(-64000, 64000);
+
+			glTexParameterf(m_textureTarget, m_lodTarget, ref);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyFloat(m_testCtx, m_textureTarget, m_lodTarget, ref);
+			expectError(GL_NO_ERROR);
+		}
+
+		// check unit conversions with int
+
+		for (int ndx = 0; ndx < numIterations; ++ndx)
+		{
+			const GLint ref = rnd.getInt(-64000, 64000);
+
+			glTexParameteri(m_textureTarget, m_lodTarget, ref);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyFloat(m_testCtx, m_textureTarget, m_lodTarget, (GLfloat)ref);
+			expectError(GL_NO_ERROR);
+		}
+	}
+private:
+	GLenum	m_lodTarget;
+	int		m_initialValue;
+};
+
+class TextureLevelCase : public TextureCase
+{
+public:
+	TextureLevelCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget, GLenum levelTarget, int initialValue)
+		: TextureCase		(context, verifier, name, description, textureTarget)
+		, m_levelTarget		(levelTarget)
+		, m_initialValue	(initialValue)
+	{
+	}
+
+	void testTexture (void)
+	{
+		de::Random rnd(0xabcdef);
+
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_levelTarget, m_initialValue);
+		expectError(GL_NO_ERROR);
+
+		const int numIterations = 60;
+		for (int ndx = 0; ndx < numIterations; ++ndx)
+		{
+			const GLint ref = rnd.getInt(0, 64000);
+
+			glTexParameteri(m_textureTarget, m_levelTarget, ref);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_levelTarget, ref);
+			expectError(GL_NO_ERROR);
+		}
+
+		// check unit conversions with float
+
+		const float nonSignificantOffsets[] = {-0.45f, -0.25f, 0, 0.45f}; // offsets O so that for any integers z in Z, o in O roundToClosestInt(z+o)==z
+
+		const int numConversionIterations = 30;
+		for (int ndx = 0; ndx < numConversionIterations; ++ndx)
+		{
+			const GLint ref = rnd.getInt(0, 64000);
+
+			for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(nonSignificantOffsets); ++offsetNdx)
+			{
+				glTexParameterf(m_textureTarget, m_levelTarget, ((GLfloat)ref) + nonSignificantOffsets[offsetNdx]);
+				expectError(GL_NO_ERROR);
+
+				m_verifier->verifyInteger(m_testCtx, m_textureTarget, m_levelTarget, ref);
+				expectError(GL_NO_ERROR);
+			}
+		}
+	}
+private:
+	GLenum	m_levelTarget;
+	int		m_initialValue;
+};
+
+class TextureCompareModeCase : public TextureCase
+{
+public:
+	TextureCompareModeCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget)
+		: TextureCase(context, verifier, name, description, textureTarget)
+	{
+	}
+
+	void testTexture (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_COMPARE_MODE, GL_NONE);
+		expectError(GL_NO_ERROR);
+
+		const GLenum modes[] = {GL_COMPARE_REF_TO_TEXTURE, GL_NONE};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(modes); ++ndx)
+		{
+			glTexParameteri(m_textureTarget, GL_TEXTURE_COMPARE_MODE, modes[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_COMPARE_MODE, modes[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		// with float too
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(modes); ++ndx)
+		{
+			glTexParameterf(m_textureTarget, GL_TEXTURE_COMPARE_MODE, (GLfloat)modes[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_COMPARE_MODE, modes[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class TextureCompareFuncCase : public TextureCase
+{
+public:
+	TextureCompareFuncCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget)
+		: TextureCase(context, verifier, name, description, textureTarget)
+	{
+	}
+
+	void testTexture (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
+		expectError(GL_NO_ERROR);
+
+		const GLenum compareFuncs[] = {GL_LEQUAL, GL_GEQUAL, GL_LESS, GL_GREATER, GL_EQUAL, GL_NOTEQUAL, GL_ALWAYS, GL_NEVER};
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(compareFuncs); ++ndx)
+		{
+			glTexParameteri(m_textureTarget, GL_TEXTURE_COMPARE_FUNC, compareFuncs[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_COMPARE_FUNC, compareFuncs[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+
+		// with float too
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(compareFuncs); ++ndx)
+		{
+			glTexParameterf(m_textureTarget, GL_TEXTURE_COMPARE_FUNC, (GLfloat)compareFuncs[ndx]);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_COMPARE_FUNC, compareFuncs[ndx]);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class TextureImmutableLevelsCase : public TextureCase
+{
+public:
+	TextureImmutableLevelsCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget)
+		: TextureCase(context, verifier, name, description, textureTarget)
+	{
+	}
+
+	void testTexture (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_IMMUTABLE_LEVELS, 0);
+		expectError(GL_NO_ERROR);
+
+		for (int level = 1; level <= 8; ++level)
+		{
+			GLuint textureID = 0;
+			glGenTextures(1, &textureID);
+			glBindTexture(m_textureTarget, textureID);
+			expectError(GL_NO_ERROR);
+
+			if (m_textureTarget == GL_TEXTURE_2D_ARRAY || m_textureTarget == GL_TEXTURE_3D)
+				glTexStorage3D(m_textureTarget, level, GL_RGB8, 256, 256, 256);
+			else
+				glTexStorage2D(m_textureTarget, level, GL_RGB8, 256, 256);
+			expectError(GL_NO_ERROR);
+
+			m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_IMMUTABLE_LEVELS, level);
+			glDeleteTextures(1, &textureID);
+			expectError(GL_NO_ERROR);
+		}
+	}
+};
+
+class TextureImmutableFormatCase : public TextureCase
+{
+public:
+	TextureImmutableFormatCase (Context& context, TexParamVerifier* verifier, const char* name, const char* description, GLenum textureTarget)
+		: TextureCase(context, verifier, name, description, textureTarget)
+	{
+	}
+
+	void testTexture (void)
+	{
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_IMMUTABLE_FORMAT, 0);
+		expectError(GL_NO_ERROR);
+
+		const GLenum formats[] =
+		{
+			GL_RGBA32I, GL_RGBA32UI, GL_RGBA16I, GL_RGBA16UI, GL_RGBA8, GL_RGBA8I,
+			GL_RGBA8UI, GL_SRGB8_ALPHA8, GL_RGB10_A2, GL_RGB10_A2UI, GL_RGBA4,
+			GL_RGB5_A1, GL_RGB8, GL_RGB565, GL_RG32I, GL_RG32UI, GL_RG16I, GL_RG16UI,
+			GL_RG8, GL_RG8I, GL_RG8UI, GL_R32I, GL_R32UI, GL_R16I, GL_R16UI, GL_R8,
+			GL_R8I, GL_R8UI,
+
+			GL_RGBA32F, GL_RGBA16F, GL_RGBA8_SNORM, GL_RGB32F,
+			GL_RGB32I, GL_RGB32UI, GL_RGB16F, GL_RGB16I, GL_RGB16UI, GL_RGB8_SNORM,
+			GL_RGB8I, GL_RGB8UI, GL_SRGB8, GL_R11F_G11F_B10F, GL_RGB9_E5, GL_RG32F,
+			GL_RG16F, GL_RG8_SNORM, GL_R32F, GL_R16F, GL_R8_SNORM,
+		};
+
+		const GLenum non3dFormats[] =
+		{
+			GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT16,
+			GL_DEPTH32F_STENCIL8, GL_DEPTH24_STENCIL8
+		};
+
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); ++formatNdx)
+			testSingleFormat(formats[formatNdx]);
+
+		if (m_textureTarget != GL_TEXTURE_3D)
+			for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(non3dFormats); ++formatNdx)
+				testSingleFormat(non3dFormats[formatNdx]);
+	}
+
+	void testSingleFormat (GLenum format)
+	{
+		GLuint textureID = 0;
+		glGenTextures(1, &textureID);
+		glBindTexture(m_textureTarget, textureID);
+		expectError(GL_NO_ERROR);
+
+		if (m_textureTarget == GL_TEXTURE_2D_ARRAY || m_textureTarget == GL_TEXTURE_3D)
+			glTexStorage3D(m_textureTarget, 1, format, 32, 32, 32);
+		else
+			glTexStorage2D(m_textureTarget, 1, format, 32, 32);
+		expectError(GL_NO_ERROR);
+
+		m_verifier->verifyInteger(m_testCtx, m_textureTarget, GL_TEXTURE_IMMUTABLE_FORMAT, 1);
+		glDeleteTextures(1, &textureID);
+		expectError(GL_NO_ERROR);
+	}
+};
+
+
+} // anonymous
+
+#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
+	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
+	{																							\
+		TexParamVerifier* verifier = VERIFIERS[_verifierNdx];									\
+		CODE_BLOCK;																				\
+	}
+
+TextureStateQueryTests::TextureStateQueryTests (Context& context)
+	: TestCaseGroup		(context, "texture", "Texture State Query tests")
+	, m_verifierInt		(DE_NULL)
+	, m_verifierFloat	(DE_NULL)
+{
+}
+
+TextureStateQueryTests::~TextureStateQueryTests (void)
+{
+	deinit();
+}
+
+void TextureStateQueryTests::init (void)
+{
+	using namespace TextureParamVerifiers;
+
+	DE_ASSERT(m_verifierInt == DE_NULL);
+	DE_ASSERT(m_verifierFloat == DE_NULL);
+
+	m_verifierInt		= new GetTexParameterIVerifier(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	m_verifierFloat		= new GetTexParameterFVerifier(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+
+	TexParamVerifier* verifiers[] = {m_verifierInt, m_verifierFloat};
+
+	const struct
+	{
+		const char*	name;
+		GLenum		textureTarget;
+	} textureTargets[] =
+	{
+		{ "texture_2d",			GL_TEXTURE_2D},
+		{ "texture_3d",			GL_TEXTURE_3D},
+		{ "texture_2d_array",	GL_TEXTURE_2D_ARRAY},
+		{ "texture_cube_map",	GL_TEXTURE_CUBE_MAP}
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(textureTargets); ++ndx)
+	{
+		addChild(new IsTextureCase(m_context, (std::string(textureTargets[ndx].name) + "_is_texture").c_str(), "IsTexture",	textureTargets[ndx].textureTarget));
+
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureSwizzleCase	(m_context, verifier, (std::string(textureTargets[ndx].name)	+ "_texture_swizzle_r"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_SWIZZLE_R",		textureTargets[ndx].textureTarget, GL_TEXTURE_SWIZZLE_R, GL_RED)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureSwizzleCase	(m_context, verifier, (std::string(textureTargets[ndx].name)	+ "_texture_swizzle_g"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_SWIZZLE_G",		textureTargets[ndx].textureTarget, GL_TEXTURE_SWIZZLE_G, GL_GREEN)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureSwizzleCase	(m_context, verifier, (std::string(textureTargets[ndx].name)	+ "_texture_swizzle_b"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_SWIZZLE_B",		textureTargets[ndx].textureTarget, GL_TEXTURE_SWIZZLE_B, GL_BLUE)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureSwizzleCase	(m_context, verifier, (std::string(textureTargets[ndx].name)	+ "_texture_swizzle_a"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_SWIZZLE_A",		textureTargets[ndx].textureTarget, GL_TEXTURE_SWIZZLE_A, GL_ALPHA)));
+
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureWrapCase		(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_wrap_s"			+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_WRAP_S",		textureTargets[ndx].textureTarget, GL_TEXTURE_WRAP_S)));
+		if (textureTargets[ndx].textureTarget == GL_TEXTURE_2D ||
+			textureTargets[ndx].textureTarget == GL_TEXTURE_3D ||
+			textureTargets[ndx].textureTarget == GL_TEXTURE_CUBE_MAP)
+			FOR_EACH_VERIFIER(verifiers, addChild(new TextureWrapCase	(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_wrap_t"			+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_WRAP_T",		textureTargets[ndx].textureTarget, GL_TEXTURE_WRAP_T)));
+
+		if (textureTargets[ndx].textureTarget == GL_TEXTURE_3D)
+			FOR_EACH_VERIFIER(verifiers, addChild(new TextureWrapCase	(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_wrap_r"			+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_WRAP_R",		textureTargets[ndx].textureTarget, GL_TEXTURE_WRAP_R)));
+
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureMagFilterCase	(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_mag_filter"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MAG_FILTER",	textureTargets[ndx].textureTarget)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureMinFilterCase	(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_min_filter"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MIN_FILTER",	textureTargets[ndx].textureTarget)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureLODCase		(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_min_lod"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MIN_LOD",		textureTargets[ndx].textureTarget, GL_TEXTURE_MIN_LOD, -1000)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureLODCase		(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_max_lod"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MAX_LOD",		textureTargets[ndx].textureTarget, GL_TEXTURE_MAX_LOD,  1000)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureLevelCase		(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_base_level"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_BASE_LEVEL",	textureTargets[ndx].textureTarget, GL_TEXTURE_BASE_LEVEL, 0)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureLevelCase		(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_max_level"		+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_MAX_LEVEL",		textureTargets[ndx].textureTarget, GL_TEXTURE_MAX_LEVEL, 1000)));
+
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureCompareModeCase(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_compare_mode"	+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_COMPARE_MODE",	textureTargets[ndx].textureTarget)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureCompareFuncCase(m_context, verifier,	(std::string(textureTargets[ndx].name)	+ "_texture_compare_func"	+ verifier->getTestNamePostfix()).c_str(), "TEXTURE_COMPARE_FUNC",	textureTargets[ndx].textureTarget)));
+
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureImmutableLevelsCase(m_context, verifier,	(std::string(textureTargets[ndx].name) + "_texture_immutable_levels" + verifier->getTestNamePostfix()).c_str(), "TEXTURE_IMMUTABLE_LEVELS",	textureTargets[ndx].textureTarget)));
+		FOR_EACH_VERIFIER(verifiers, addChild(new TextureImmutableFormatCase(m_context, verifier,	(std::string(textureTargets[ndx].name) + "_texture_immutable_format" + verifier->getTestNamePostfix()).c_str(), "TEXTURE_IMMUTABLE_FORMAT",	textureTargets[ndx].textureTarget)));
+	}
+}
+
+void TextureStateQueryTests::deinit (void)
+{
+	if (m_verifierInt)
+	{
+		delete m_verifierInt;
+		m_verifierInt = NULL;
+	}
+	if (m_verifierFloat)
+	{
+		delete m_verifierFloat;
+		m_verifierFloat = NULL;
+	}
+
+	this->TestCaseGroup::deinit();
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTextureStateQueryTests.hpp b/modules/gles3/functional/es3fTextureStateQueryTests.hpp
new file mode 100644
index 0000000..b33badb
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureStateQueryTests.hpp
@@ -0,0 +1,64 @@
+#ifndef _ES3FTEXTURESTATEQUERYTESTS_HPP
+#define _ES3FTEXTURESTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture State Query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace TextureParamVerifiers
+{
+
+class GetTexParameterIVerifier;
+class GetTexParameterFVerifier;
+
+} // TextureParamVerifiers
+
+class TextureStateQueryTests : public TestCaseGroup
+{
+public:
+																TextureStateQueryTests	(Context& context);
+																~TextureStateQueryTests	(void);
+
+	void														init					(void);
+	void														deinit					(void);
+
+private:
+																TextureStateQueryTests	(const TextureStateQueryTests& other);
+	TextureStateQueryTests&										operator=				(const TextureStateQueryTests& other);
+
+	TextureParamVerifiers::GetTexParameterIVerifier*			m_verifierInt;
+	TextureParamVerifiers::GetTexParameterFVerifier*			m_verifierFloat;
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTEXTURESTATEQUERYTESTS_HPP
diff --git a/modules/gles3/functional/es3fTextureSwizzleTests.cpp b/modules/gles3/functional/es3fTextureSwizzleTests.cpp
new file mode 100644
index 0000000..fb02ed9
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureSwizzleTests.cpp
@@ -0,0 +1,344 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture swizzle tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTextureSwizzleTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTexture.hpp"
+#include "gluRenderContext.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deString.h"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+using namespace deqp::gls;
+using namespace deqp::gls::TextureTestUtil;
+
+static int swizzle (const tcu::RGBA& c, deUint32 swz)
+{
+	switch (swz)
+	{
+		case GL_RED:	return c.getRed();
+		case GL_GREEN:	return c.getGreen();
+		case GL_BLUE:	return c.getBlue();
+		case GL_ALPHA:	return c.getAlpha();
+		case GL_ZERO:	return 0;
+		case GL_ONE:	return (1<<8)-1;
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static void swizzle (tcu::Surface& surface, deUint32 swzR, deUint32 swzG, deUint32 swzB, deUint32 swzA)
+{
+	for (int y = 0; y < surface.getHeight(); y++)
+	{
+		for (int x = 0; x < surface.getWidth(); x++)
+		{
+			tcu::RGBA p = surface.getPixel(x, y);
+			surface.setPixel(x, y, tcu::RGBA(swizzle(p, swzR), swizzle(p, swzG), swizzle(p, swzB), swizzle(p, swzA)));
+		}
+	}
+}
+
+class Texture2DSwizzleCase : public TestCase
+{
+public:
+							Texture2DSwizzleCase	(Context& context, const char* name, const char* description, deUint32 internalFormat, deUint32 format, deUint32 dataType, deUint32 swizzleR, deUint32 swizzleG, deUint32 swizzleB, deUint32 swizzleA);
+							~Texture2DSwizzleCase	(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							Texture2DSwizzleCase	(const Texture2DSwizzleCase& other);
+	Texture2DSwizzleCase&	operator=				(const Texture2DSwizzleCase& other);
+
+	deUint32				m_internalFormat;
+	deUint32				m_format;
+	deUint32				m_dataType;
+	deUint32				m_swizzleR;
+	deUint32				m_swizzleG;
+	deUint32				m_swizzleB;
+	deUint32				m_swizzleA;
+
+	glu::Texture2D*			m_texture;
+	TextureRenderer			m_renderer;
+};
+
+Texture2DSwizzleCase::Texture2DSwizzleCase (Context& context, const char* name, const char* description, deUint32 internalFormat, deUint32 format, deUint32 dataType, deUint32 swizzleR, deUint32 swizzleG, deUint32 swizzleB, deUint32 swizzleA)
+	: TestCase			(context, name, description)
+	, m_internalFormat	(internalFormat)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_swizzleR		(swizzleR)
+	, m_swizzleG		(swizzleG)
+	, m_swizzleB		(swizzleB)
+	, m_swizzleA		(swizzleA)
+	, m_texture			(DE_NULL)
+	, m_renderer		(context.getRenderContext(), context.getTestContext(), glu::GLSL_VERSION_300_ES, glu::PRECISION_HIGHP)
+{
+}
+
+Texture2DSwizzleCase::~Texture2DSwizzleCase (void)
+{
+	deinit();
+}
+
+void Texture2DSwizzleCase::init (void)
+{
+	int	width	= de::min(128, m_context.getRenderContext().getRenderTarget().getWidth());
+	int	height	= de::min(128, m_context.getRenderContext().getRenderTarget().getHeight());
+
+	m_texture = (m_internalFormat == m_format) ? new glu::Texture2D(m_context.getRenderContext(), m_format, m_dataType, width, height)
+											   : new glu::Texture2D(m_context.getRenderContext(), m_internalFormat, width, height);
+
+	tcu::TextureFormatInfo spec = tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	// Fill level 0.
+	m_texture->getRefTexture().allocLevel(0);
+	tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), spec.valueMin, spec.valueMax);
+}
+
+void Texture2DSwizzleCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+Texture2DSwizzleCase::IterateResult Texture2DSwizzleCase::iterate (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	TestLog&				log					= m_testCtx.getLog();
+	RandomViewport			viewport			(m_context.getRenderContext().getRenderTarget(), m_texture->getRefTexture().getWidth(), m_texture->getRefTexture().getHeight(), deStringHash(getName()));
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_context.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	ReferenceParams			renderParams		(TEXTURETYPE_2D);
+	tcu::TextureFormatInfo	spec				= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	renderParams.samplerType	= getSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.sampler		= tcu::Sampler(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::NEAREST, tcu::Sampler::NEAREST);
+	renderParams.colorScale		= spec.lookupScale;
+	renderParams.colorBias		= spec.lookupBias;
+
+	computeQuadTexCoord2D(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+
+	// Setup texture swizzle.
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R,	m_swizzleR);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G,	m_swizzleG);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B,	m_swizzleB);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A,	m_swizzleA);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compute reference
+	{
+		const tcu::PixelFormat pixelFormat = m_context.getRenderTarget().getPixelFormat();
+
+		// Do initial rendering to RGBA8 in order to keep alpha
+		sampleTexture(SurfaceAccess(referenceFrame, tcu::PixelFormat(8,8,8,8)), m_texture->getRefTexture(), &texCoord[0], renderParams);
+
+		// Swizzle channels
+		swizzle(referenceFrame, m_swizzleR, m_swizzleG, m_swizzleB, m_swizzleA);
+
+		// Convert to destination format
+		if (pixelFormat != tcu::PixelFormat(8,8,8,8))
+		{
+			for (int y = 0; y < referenceFrame.getHeight(); y++)
+			{
+				for (int x = 0; x < referenceFrame.getWidth(); x++)
+				{
+					tcu::RGBA p = referenceFrame.getPixel(x, y);
+					referenceFrame.setPixel(x, y, pixelFormat.convertColor(p));
+				}
+			}
+		}
+	}
+
+	// Compare and log.
+	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+TextureSwizzleTests::TextureSwizzleTests (Context& context)
+	: TestCaseGroup(context, "swizzle", "Texture Swizzle Tests")
+{
+}
+
+TextureSwizzleTests::~TextureSwizzleTests (void)
+{
+}
+
+void TextureSwizzleTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		deUint32		internalFormat;
+		deUint32		format;
+		deUint32		dataType;
+	} formats[] =
+	{
+		{ "alpha",				GL_ALPHA,				GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "luminance",			GL_LUMINANCE,			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "luminance_alpha",	GL_LUMINANCE_ALPHA,		GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
+		{ "red",				GL_R8,					GL_RED,				GL_UNSIGNED_BYTE },
+		{ "rg",					GL_RG8,					GL_RG,				GL_UNSIGNED_BYTE },
+		{ "rgb",				GL_RGB8,				GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba",				GL_RGBA8,				GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		channel;
+	} channels[] =
+	{
+		{ "r",	GL_TEXTURE_SWIZZLE_R },
+		{ "g",	GL_TEXTURE_SWIZZLE_G },
+		{ "b",	GL_TEXTURE_SWIZZLE_B },
+		{ "a",	GL_TEXTURE_SWIZZLE_A }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		swizzle;
+	} swizzles[] =
+	{
+		{ "red",		GL_RED		},
+		{ "green",		GL_GREEN	},
+		{ "blue",		GL_BLUE		},
+		{ "alpha",		GL_ALPHA	},
+		{ "zero",		GL_ZERO		},
+		{ "one",		GL_ONE		}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		swzR;
+		deUint32		swzG;
+		deUint32		swzB;
+		deUint32		swzA;
+	} swizzleCases[] =
+	{
+		{ "all_red",			GL_RED,		GL_RED,		GL_RED,		GL_RED		},
+		{ "all_green",			GL_GREEN,	GL_GREEN,	GL_GREEN,	GL_GREEN	},
+		{ "all_blue",			GL_BLUE,	GL_BLUE,	GL_BLUE,	GL_BLUE		},
+		{ "all_alpha",			GL_ALPHA,	GL_ALPHA,	GL_ALPHA,	GL_ALPHA	},
+		{ "all_zero",			GL_ZERO,	GL_ZERO,	GL_ZERO,	GL_ZERO		},
+		{ "all_one",			GL_ONE,		GL_ONE,		GL_ONE,		GL_ONE		},
+		{ "bgra",				GL_BLUE,	GL_GREEN,	GL_RED,		GL_ALPHA	},
+		{ "abgr",				GL_ALPHA,	GL_BLUE,	GL_GREEN,	GL_RED		},
+		{ "one_one_red_green",	GL_ONE,		GL_ONE,		GL_RED,		GL_GREEN	}
+	};
+
+	static const deUint32 defaultSwizzles[] = { GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA };
+
+	// All swizzles applied to each channel.
+	tcu::TestCaseGroup* singleChannelGroup = new tcu::TestCaseGroup(m_testCtx, "single_channel", "Single-channel swizzle");
+	addChild(singleChannelGroup);
+	for (int chanNdx = 0; chanNdx < DE_LENGTH_OF_ARRAY(channels); chanNdx++)
+	{
+		for (int swzNdx = 0; swzNdx < DE_LENGTH_OF_ARRAY(swizzles); swzNdx++)
+		{
+			if (swizzles[swzNdx].swizzle == defaultSwizzles[chanNdx])
+				continue; // No need to test default case.
+
+			string		name	= string(channels[chanNdx].name) + "_" + swizzles[swzNdx].name;
+			deUint32	swz		= swizzles[swzNdx].swizzle;
+			deUint32	swzR	= (chanNdx == 0) ? swz : defaultSwizzles[0];
+			deUint32	swzG	= (chanNdx == 1) ? swz : defaultSwizzles[1];
+			deUint32	swzB	= (chanNdx == 2) ? swz : defaultSwizzles[2];
+			deUint32	swzA	= (chanNdx == 3) ? swz : defaultSwizzles[3];
+
+			singleChannelGroup->addChild(new Texture2DSwizzleCase(m_context, name.c_str(), "Single-channel swizzle", GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, swzR, swzG, swzB, swzA));
+		}
+	}
+
+	// Swizzles for all formats.
+	tcu::TestCaseGroup* multiChannelGroup = new tcu::TestCaseGroup(m_testCtx, "multi_channel", "Multi-channel swizzle");
+	addChild(multiChannelGroup);
+	for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(formats); fmtNdx++)
+	{
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(swizzleCases); caseNdx++)
+		{
+			string		name		= string(formats[fmtNdx].name) + "_" + swizzleCases[caseNdx].name;
+			deUint32	swzR		= swizzleCases[caseNdx].swzR;
+			deUint32	swzG		= swizzleCases[caseNdx].swzG;
+			deUint32	swzB		= swizzleCases[caseNdx].swzB;
+			deUint32	swzA		= swizzleCases[caseNdx].swzA;
+			deUint32	intFormat	= formats[fmtNdx].internalFormat;
+			deUint32	format		= formats[fmtNdx].format;
+			deUint32	dataType	= formats[fmtNdx].dataType;
+
+			multiChannelGroup->addChild(new Texture2DSwizzleCase(m_context, name.c_str(), "Multi-channel swizzle", intFormat, format, dataType, swzR, swzG, swzB, swzA));
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTextureSwizzleTests.hpp b/modules/gles3/functional/es3fTextureSwizzleTests.hpp
new file mode 100644
index 0000000..be24e65
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureSwizzleTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FTEXTURESWIZZLETESTS_HPP
+#define _ES3FTEXTURESWIZZLETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture swizzle tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class TextureSwizzleTests : public TestCaseGroup
+{
+public:
+							TextureSwizzleTests		(Context& context);
+							~TextureSwizzleTests	(void);
+
+	void					init					(void);
+
+private:
+							TextureSwizzleTests		(const TextureSwizzleTests& other);
+	TextureSwizzleTests&	operator=				(const TextureSwizzleTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTEXTURESWIZZLETESTS_HPP
diff --git a/modules/gles3/functional/es3fTextureUnitTests.cpp b/modules/gles3/functional/es3fTextureUnitTests.cpp
new file mode 100644
index 0000000..542fce4
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureUnitTests.cpp
@@ -0,0 +1,1278 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture unit usage tests.
+ *
+ * \todo [2012-07-12 nuutti] Come up with a good way to make these tests faster.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTextureUnitTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuRenderTarget.hpp"
+#include "sglrContextUtil.hpp"
+#include "sglrReferenceContext.hpp"
+#include "sglrGLContext.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::Mat3;
+using tcu::Mat4;
+using std::vector;
+using std::string;
+using namespace glw; // GL types
+
+namespace deqp
+{
+
+using namespace gls::TextureTestUtil;
+
+namespace gles3
+{
+namespace Functional
+{
+
+static const int VIEWPORT_WIDTH				= 128;
+static const int VIEWPORT_HEIGHT			= 128;
+
+static const int TEXTURE_WIDTH_2D			= 128;
+static const int TEXTURE_HEIGHT_2D			= 128;
+
+// \note Cube map texture size is larger in order to make minifications possible - otherwise would need to display different faces at same time.
+static const int TEXTURE_WIDTH_CUBE			= 256;
+static const int TEXTURE_HEIGHT_CUBE		= 256;
+
+static const int TEXTURE_WIDTH_2D_ARRAY		= 64;
+static const int TEXTURE_HEIGHT_2D_ARRAY	= 64;
+static const int TEXTURE_LAYERS_2D_ARRAY	= 4;
+
+static const int TEXTURE_WIDTH_3D			= 32;
+static const int TEXTURE_HEIGHT_3D			= 32;
+static const int TEXTURE_DEPTH_3D			= 32;
+
+static const int GRID_CELL_SIZE				= 8;
+
+static const GLenum s_testSizedInternalFormats[] =
+{
+	GL_RGBA32F,
+	GL_RGBA32I,
+	GL_RGBA32UI,
+	GL_RGBA16F,
+	GL_RGBA16I,
+	GL_RGBA16UI,
+	GL_RGBA8,
+	GL_RGBA8I,
+	GL_RGBA8UI,
+	GL_SRGB8_ALPHA8,
+	GL_RGB10_A2,
+	GL_RGB10_A2UI,
+	GL_RGBA4,
+	GL_RGB5_A1,
+	GL_RGBA8_SNORM,
+	GL_RGB8,
+	GL_RGB565,
+	GL_R11F_G11F_B10F,
+	GL_RGB32F,
+	GL_RGB32I,
+	GL_RGB32UI,
+	GL_RGB16F,
+	GL_RGB16I,
+	GL_RGB16UI,
+	GL_RGB8_SNORM,
+	GL_RGB8I,
+	GL_RGB8UI,
+	GL_SRGB8,
+	GL_RGB9_E5,
+	GL_RG32F,
+	GL_RG32I,
+	GL_RG32UI,
+	GL_RG16F,
+	GL_RG16I,
+	GL_RG16UI,
+	GL_RG8,
+	GL_RG8I,
+	GL_RG8UI,
+	GL_RG8_SNORM,
+	GL_R32F,
+	GL_R32I,
+	GL_R32UI,
+	GL_R16F,
+	GL_R16I,
+	GL_R16UI,
+	GL_R8,
+	GL_R8I,
+	GL_R8UI,
+	GL_R8_SNORM
+};
+
+static const GLenum s_testWrapModes[] =
+{
+	GL_CLAMP_TO_EDGE,
+	GL_REPEAT,
+	GL_MIRRORED_REPEAT,
+};
+
+static const GLenum s_testMinFilters[] =
+{
+	GL_NEAREST,
+	GL_LINEAR,
+	GL_NEAREST_MIPMAP_NEAREST,
+	GL_LINEAR_MIPMAP_NEAREST,
+	GL_NEAREST_MIPMAP_LINEAR,
+	GL_LINEAR_MIPMAP_LINEAR
+};
+
+static const GLenum s_testNonMipmapMinFilters[] =
+{
+	GL_NEAREST,
+	GL_LINEAR
+};
+
+static const GLenum s_testNearestMinFilters[] =
+{
+	GL_NEAREST,
+	GL_NEAREST_MIPMAP_NEAREST
+};
+
+static const GLenum s_testMagFilters[] =
+{
+	GL_NEAREST,
+	GL_LINEAR
+};
+
+static const GLenum s_cubeFaceTargets[] =
+{
+	GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+	GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+	GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
+};
+
+// Extend a 3x3 transformation matrix to an equivalent 4x4 transformation matrix (i.e. 1.0 in right-down cell, 0.0's in other new cells).
+static Mat4 matExtend3To4 (const Mat3& mat)
+{
+	Mat4 res;
+	for (int rowNdx = 0; rowNdx < 3; rowNdx++)
+	{
+		Vec3 row = mat.getRow(rowNdx);
+		res.setRow(rowNdx, Vec4(row.x(), row.y(), row.z(), 0.0f));
+	}
+	res.setRow(3, Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	return res;
+}
+
+static string generateMultiTexFragmentShader (int numUnits, const vector<GLenum>& unitTypes, const vector<glu::DataType>& samplerTypes)
+{
+	// The fragment shader calculates the average of a set of textures.
+
+	string samplersStr;
+	string matricesStr;
+	string scalesStr;
+	string biasesStr;
+	string lookupsStr;
+
+	string colorMultiplier = "(1.0/" + de::toString(numUnits) + ".0)";
+
+	for (int ndx = 0; ndx < numUnits; ndx++)
+	{
+		string ndxStr				= de::toString(ndx);
+		string samplerName			= "u_sampler" + ndxStr;
+		string transformationName	= "u_trans" + ndxStr;
+		string scaleName			= "u_texScale" + ndxStr;
+		string biasName				= "u_texBias" + ndxStr;
+
+		samplersStr += string("") + "uniform highp " + glu::getDataTypeName(samplerTypes[ndx]) + " " + samplerName + ";\n";
+		matricesStr += "uniform highp mat4 " + transformationName + ";\n";
+		scalesStr += "uniform highp vec4 " + scaleName + ";\n";
+		biasesStr += "uniform highp vec4 " + biasName + ";\n";
+
+		string lookupCoord = transformationName + "*vec4(v_coord, 1.0, 1.0)";
+
+		if (unitTypes[ndx] == GL_TEXTURE_2D)
+			lookupCoord = "vec2(" + lookupCoord + ")";
+		else
+			lookupCoord = "vec3(" + lookupCoord + ")";
+
+		lookupsStr += "\tcolor += " + colorMultiplier + "*(vec4(texture(" + samplerName + ", " + lookupCoord + "))*" + scaleName + " + " + biasName + ");\n";
+	}
+
+	return "#version 300 es\n"
+		   "layout(location = 0) out mediump vec4 o_color;\n" +
+		   samplersStr +
+		   matricesStr +
+		   scalesStr +
+		   biasesStr +
+		   "in highp vec2 v_coord;\n"
+		   "\n"
+		   "void main (void)\n"
+		   "{\n"
+		   "	mediump vec4 color = vec4(0.0);\n" +
+		   lookupsStr +
+		   "	o_color = color;\n"
+		   "}\n";
+}
+
+static sglr::pdec::ShaderProgramDeclaration generateShaderProgramDeclaration (int numUnits, const vector<GLenum>& unitTypes, const vector<glu::DataType>& samplerTypes)
+{
+	sglr::pdec::ShaderProgramDeclaration decl;
+
+	decl << sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT);
+
+	for (int ndx = 0; ndx < numUnits; ++ndx)
+	{
+		string samplerName			= "u_sampler" + de::toString(ndx);
+		string transformationName	= "u_trans" + de::toString(ndx);
+		string scaleName			= "u_texScale" + de::toString(ndx);
+		string biasName				= "u_texBias" + de::toString(ndx);
+
+		decl << sglr::pdec::Uniform(samplerName, samplerTypes[ndx]);
+		decl << sglr::pdec::Uniform(transformationName, glu::TYPE_FLOAT_MAT4);
+		decl << sglr::pdec::Uniform(scaleName, glu::TYPE_FLOAT_VEC4);
+		decl << sglr::pdec::Uniform(biasName, glu::TYPE_FLOAT_VEC4);
+	}
+
+	decl << sglr::pdec::VertexSource("#version 300 es\n"
+									 "in highp vec4 a_position;\n"
+									 "in highp vec2 a_coord;\n"
+									 "out highp vec2 v_coord;\n"
+									 "\n"
+									 "void main (void)\n"
+									 "{\n"
+									 "	gl_Position = a_position;\n"
+									 "	v_coord = a_coord;\n"
+									 "}\n");
+	decl << sglr::pdec::FragmentSource(generateMultiTexFragmentShader(numUnits, unitTypes, samplerTypes));
+
+	return decl;
+}
+
+// Calculates values that will be used in calculateLod().
+static tcu::Vector<tcu::Vec2, 3> calculateLodDerivateParts (const Mat4& transformation)
+{
+	// Calculate transformed coordinates of three screen corners.
+	Vec3 trans00 = (transformation * Vec4(0.0f, 0.0f, 1.0f, 1.0f)).xyz();
+	Vec3 trans01 = (transformation * Vec4(0.0f, 1.0f, 1.0f, 1.0f)).xyz();
+	Vec3 trans10 = (transformation * Vec4(1.0f, 0.0f, 1.0f, 1.0f)).xyz();
+
+	return tcu::Vector<tcu::Vec2, 3>(Vec2(trans10.x() - trans00.x(), trans01.x() - trans00.x()),
+									 Vec2(trans10.y() - trans00.y(), trans01.y() - trans00.y()),
+									 Vec2(trans10.z() - trans00.z(), trans01.z() - trans00.z()));
+}
+
+// Calculates the maximum allowed lod from derivates
+static float calculateLodMax(const tcu::Vector<tcu::Vec2, 3>& derivateParts, const tcu::IVec3& textureSize, const Vec2& screenDerivate)
+{
+	float dudx = derivateParts[0].x() * (float)textureSize.x() * screenDerivate.x();
+	float dudy = derivateParts[0].y() * (float)textureSize.x() * screenDerivate.y();
+	float dvdx = derivateParts[1].x() * (float)textureSize.y() * screenDerivate.x();
+	float dvdy = derivateParts[1].y() * (float)textureSize.y() * screenDerivate.y();
+	float dwdx = derivateParts[2].x() * (float)textureSize.z() * screenDerivate.x();
+	float dwdy = derivateParts[2].y() * (float)textureSize.z() * screenDerivate.y();
+
+	const float mu = de::max(de::abs(dudx), de::abs(dudy));
+	const float mv = de::max(de::abs(dvdx), de::abs(dvdy));
+	const float mw = de::max(de::abs(dwdx), de::abs(dwdy));
+	return deFloatLog2(mu + mv + mw);
+}
+
+// Calculates the minimum allowed lod from derivates
+static float calculateLodMin(const tcu::Vector<tcu::Vec2, 3>& derivateParts, const tcu::IVec3& textureSize, const Vec2& screenDerivate)
+{
+	float dudx = derivateParts[0].x() * (float)textureSize.x() * screenDerivate.x();
+	float dudy = derivateParts[0].y() * (float)textureSize.x() * screenDerivate.y();
+	float dvdx = derivateParts[1].x() * (float)textureSize.y() * screenDerivate.x();
+	float dvdy = derivateParts[1].y() * (float)textureSize.y() * screenDerivate.y();
+	float dwdx = derivateParts[2].x() * (float)textureSize.z() * screenDerivate.x();
+	float dwdy = derivateParts[2].y() * (float)textureSize.z() * screenDerivate.y();
+
+	const float mu = de::max(de::abs(dudx), de::abs(dudy));
+	const float mv = de::max(de::abs(dvdx), de::abs(dvdy));
+	const float mw = de::max(de::abs(dwdx), de::abs(dwdy));
+	return deFloatLog2(de::max(mu, de::max(mv, mw)));
+}
+
+class MultiTexShader : public sglr::ShaderProgram
+{
+public:
+							MultiTexShader	(deUint32 randSeed,
+											 int numUnits,
+											 const vector<GLenum>& unitTypes,
+											 const vector<glu::DataType>& samplerTypes,
+											 const vector<Vec4>& texScales,
+											 const vector<Vec4>& texBiases,
+											 const vector<int>& num2dArrayLayers); // \note 2d array layer "coordinate" isn't normalized, so this is needed here.
+
+	void					setUniforms		(sglr::Context& context, deUint32 program) const;
+	void					makeSafeLods	(const vector<IVec3>& textureSizes, const IVec2& viewportSize); // Modifies texture coordinates so that LODs aren't too close to x.5 or 0.0 .
+
+private:
+	void					shadeVertices	(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void					shadeFragments	(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+	int									m_numUnits;
+	vector<GLenum>						m_unitTypes;		// 2d, cube map, 2d array or 3d.
+	vector<Vec4>						m_texScales;
+	vector<Vec4>						m_texBiases;
+	vector<Mat4>						m_transformations;
+	vector<tcu::Vector<tcu::Vec2, 3> >	m_lodDerivateParts;	// Parts of lod derivates; computed in init(), used in eval().
+};
+
+MultiTexShader::MultiTexShader (deUint32 randSeed,
+								int numUnits,
+								const vector<GLenum>& unitTypes,
+								const vector<glu::DataType>& samplerTypes,
+								const vector<Vec4>& texScales,
+								const vector<Vec4>& texBiases,
+								const vector<int>& num2dArrayLayers)
+		: sglr::ShaderProgram	(generateShaderProgramDeclaration(numUnits, unitTypes, samplerTypes))
+		, m_numUnits		(numUnits)
+		, m_unitTypes		(unitTypes)
+		, m_texScales		(texScales)
+		, m_texBiases		(texBiases)
+{
+	// 2d-to-cube-face transformations.
+	// \note 2d coordinates range from 0 to 1 and cube face coordinates from -1 to 1, so scaling is done as well.
+	static const float s_cubeTransforms[][3*3] =
+	{
+		// Face -X: (x, y, 1) -> (-1, -(2*y-1), +(2*x-1))
+		{  0.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   2.0f,  0.0f, -1.0f },
+		// Face +X: (x, y, 1) -> (+1, -(2*y-1), -(2*x-1))
+		{  0.0f,  0.0f,  1.0f,
+		   0.0f, -2.0f,  1.0f,
+		  -2.0f,  0.0f,  1.0f },
+		// Face -Y: (x, y, 1) -> (+(2*x-1), -1, -(2*y-1))
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f },
+		// Face +Y: (x, y, 1) -> (+(2*x-1), +1, +(2*y-1))
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f,  0.0f,  1.0f,
+		   0.0f,  2.0f, -1.0f },
+		// Face -Z: (x, y, 1) -> (-(2*x-1), -(2*y-1), -1)
+		{ -2.0f,  0.0f,  1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   0.0f,  0.0f, -1.0f },
+		// Face +Z: (x, y, 1) -> (+(2*x-1), -(2*y-1), +1)
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   0.0f,  0.0f,  1.0f }
+	};
+
+	// Generate transformation matrices.
+
+	de::Random rnd(randSeed);
+
+	m_transformations.reserve(m_numUnits);
+	m_lodDerivateParts.reserve(m_numUnits);
+
+	int tex2dArrayNdx = 0; // Keep track of 2d texture array index.
+
+	DE_ASSERT((int)m_unitTypes.size() == m_numUnits);
+
+	for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
+	{
+		if (m_unitTypes[unitNdx] == GL_TEXTURE_2D)
+		{
+			float rotAngle				= rnd.getFloat(0.0f, 2.0f*DE_PI);
+			float xScaleFactor			= rnd.getFloat(0.7f, 1.5f);
+			float yScaleFactor			= rnd.getFloat(0.7f, 1.5f);
+			float xShearAmount			= rnd.getFloat(0.0f, 0.5f);
+			float yShearAmount			= rnd.getFloat(0.0f, 0.5f);
+			float xTranslationAmount	= rnd.getFloat(-0.5f, 0.5f);
+			float yTranslationAmount	= rnd.getFloat(-0.5f, 0.5f);
+
+			static const float tempOffsetData[3*3] = // For temporarily centering the coordinates to get nicer transformations.
+			{
+				1.0f,  0.0f, -0.5f,
+				0.0f,  1.0f, -0.5f,
+				0.0f,  0.0f,  1.0f
+			};
+			float rotTransfData[3*3] =
+			{
+				deFloatCos(rotAngle),	-deFloatSin(rotAngle),	0.0f,
+				deFloatSin(rotAngle),	deFloatCos(rotAngle),	0.0f,
+				0.0f,					0.0f,					1.0f
+			};
+			float scaleTransfData[3*3] =
+			{
+				xScaleFactor,	0.0f,			0.0f,
+				0.0f,			yScaleFactor,	0.0f,
+				0.0f,			0.0f,			1.0f
+			};
+			float xShearTransfData[3*3] =
+			{
+				1.0f,			xShearAmount,	0.0f,
+				0.0f,			1.0f,			0.0f,
+				0.0f,			0.0f,			1.0f
+			};
+			float yShearTransfData[3*3] =
+			{
+				1.0f,			0.0f,			0.0f,
+				yShearAmount,	1.0f,			0.0f,
+				0.0f,			0.0f,			1.0f
+			};
+			float translationTransfData[3*3] =
+			{
+				1.0f,	0.0f,	xTranslationAmount,
+				0.0f,	1.0f,	yTranslationAmount,
+				0.0f,	0.0f,	1.0f
+			};
+
+			Mat4 transformation = matExtend3To4(Mat3(tempOffsetData) *
+												Mat3(translationTransfData) *
+												Mat3(rotTransfData) *
+												Mat3(scaleTransfData) *
+												Mat3(xShearTransfData) *
+												Mat3(yShearTransfData) *
+												(Mat3(tempOffsetData) * (-1.0f)));
+
+			m_lodDerivateParts.push_back(calculateLodDerivateParts(transformation));
+			m_transformations.push_back(transformation);
+		}
+		else if (m_unitTypes[unitNdx] == GL_TEXTURE_CUBE_MAP)
+		{
+			DE_STATIC_ASSERT((int)tcu::CUBEFACE_LAST == DE_LENGTH_OF_ARRAY(s_cubeTransforms));
+
+			float planarTransData[3*3];
+
+			// In case of a cube map, we only want to render one face, so the transformation needs to be restricted - only enlarging scaling is done.
+
+			for (int i = 0; i < DE_LENGTH_OF_ARRAY(planarTransData); i++)
+			{
+				if (i == 0 || i == 4)
+					planarTransData[i] = rnd.getFloat(0.1f, 0.9f); // Two first diagonal cells control the scaling.
+				else if (i == 8)
+					planarTransData[i] = 1.0f;
+				else
+					planarTransData[i] = 0.0f;
+			}
+
+			int		faceNdx			= rnd.getInt(0, (int)tcu::CUBEFACE_LAST - 1);
+			Mat3	planarTrans		(planarTransData);												// Planar, face-agnostic transformation.
+			Mat4	finalTrans		= matExtend3To4(Mat3(s_cubeTransforms[faceNdx]) * planarTrans);	// Final transformation from planar to cube map coordinates, including the transformation just generated.
+			Mat4	planarTrans4x4	= matExtend3To4(planarTrans);
+
+			m_lodDerivateParts.push_back(calculateLodDerivateParts(planarTrans4x4));
+			m_transformations.push_back(finalTrans);
+		}
+		else
+		{
+			DE_ASSERT(m_unitTypes[unitNdx] == GL_TEXTURE_3D || m_unitTypes[unitNdx] == GL_TEXTURE_2D_ARRAY);
+
+			float transData[4*4];
+
+			for (int i = 0; i < 4*4; i++)
+			{
+				float sign = rnd.getBool() ? 1.0f : -1.0f;
+				transData[i] = rnd.getFloat(0.7f, 1.4f) * sign;
+			}
+
+			Mat4 transformation(transData);
+
+			if (m_unitTypes[unitNdx] == GL_TEXTURE_2D_ARRAY)
+			{
+				// Z direction: Translate by 0.5 and scale by layer amount.
+
+				float numLayers = (float)num2dArrayLayers[tex2dArrayNdx];
+
+				static const float zTranslationTransfData[4*4] =
+				{
+					1.0f, 0.0f, 0.0f, 0.0f,
+					0.0f, 1.0f, 0.0f, 0.0f,
+					0.0f, 0.0f, 1.0f, 0.5f,
+					0.0f, 0.0f, 0.0f, 1.0f
+				};
+
+				float zScaleTransfData[4*4] =
+				{
+					1.0f,		0.0f,		0.0f,		0.0f,
+					0.0f,		1.0f,		0.0f,		0.0f,
+					0.0f,		0.0f,		numLayers,	0.0f,
+					0.0f,		0.0f,		0.0f,		1.0f
+				};
+
+				transformation = transformation * Mat4(zScaleTransfData) * Mat4(zTranslationTransfData);
+
+				tex2dArrayNdx++;
+			}
+
+			m_lodDerivateParts.push_back(calculateLodDerivateParts(transformation));
+			m_transformations.push_back(Mat4(transformation));
+		}
+	}
+}
+
+void MultiTexShader::setUniforms (sglr::Context& ctx, deUint32 program) const
+{
+	ctx.useProgram(program);
+
+	// Sampler and matrix uniforms.
+
+	for (int ndx = 0; ndx < m_numUnits; ndx++)
+	{
+		string			ndxStr		= de::toString(ndx);
+
+		ctx.uniform1i(ctx.getUniformLocation(program, ("u_sampler" + ndxStr).c_str()), ndx);
+		ctx.uniformMatrix4fv(ctx.getUniformLocation(program, ("u_trans" + ndxStr).c_str()), 1, GL_FALSE, (GLfloat*)&m_transformations[ndx].getColumnMajorData()[0]);
+		ctx.uniform4fv(ctx.getUniformLocation(program, ("u_texScale" + ndxStr).c_str()), 1, m_texScales[ndx].getPtr());
+		ctx.uniform4fv(ctx.getUniformLocation(program, ("u_texBias" + ndxStr).c_str()), 1, m_texBiases[ndx].getPtr());
+	}
+}
+
+void MultiTexShader::makeSafeLods (const vector<IVec3>& textureSizes, const IVec2& viewportSize)
+{
+	DE_ASSERT((int)textureSizes.size() == m_numUnits);
+
+	static const float shrinkScaleMat2dData[3*3] =
+	{
+		0.95f,	0.0f,	0.0f,
+		0.0f,	0.95f,	0.0f,
+		0.0f,	0.0f,	1.0f
+	};
+	static const float shrinkScaleMat3dData[3*3] =
+	{
+		0.95f,	0.0f,	0.0f,
+		0.0f,	0.95f,	0.0f,
+		0.0f,	0.0f,	0.95f
+	};
+	Mat4 shrinkScaleMat2d = matExtend3To4(Mat3(shrinkScaleMat2dData));
+	Mat4 shrinkScaleMat3d = matExtend3To4(Mat3(shrinkScaleMat3dData));
+
+	Vec2 screenDerivate(1.0f / (float)viewportSize.x(), 1.0f / (float)viewportSize.y());
+
+	for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
+	{
+		// As long as LOD is too close to 0.0 or is positive and too close to a something-and-a-half (0.5, 1.5, 2.5 etc) or allowed lod range could round to different levels, zoom in a little to get a safer LOD.
+		for (;;)
+		{
+			const float threshold = 0.1f;
+			const float epsilon	= 0.01f;
+
+			const float lodMax = calculateLodMax(m_lodDerivateParts[unitNdx], textureSizes[unitNdx], screenDerivate);
+			const float lodMin = calculateLodMin(m_lodDerivateParts[unitNdx], textureSizes[unitNdx], screenDerivate);
+
+			const deInt32 maxLevel = (lodMax + epsilon < 0.5f) ? (0) : (deCeilFloatToInt32(lodMax + epsilon + 0.5f) - 1);
+			const deInt32 minLevel = (lodMin - epsilon < 0.5f) ? (0) : (deCeilFloatToInt32(lodMin - epsilon + 0.5f) - 1);
+
+			if (de::abs(lodMax) < threshold || (lodMax > 0.0f && de::abs(deFloatFrac(lodMax) - 0.5f) < threshold) ||
+				de::abs(lodMin) < threshold || (lodMin > 0.0f && de::abs(deFloatFrac(lodMin) - 0.5f) < threshold) ||
+				maxLevel != minLevel)
+			{
+				m_transformations[unitNdx] = (m_unitTypes[unitNdx] == GL_TEXTURE_3D ? shrinkScaleMat3d : shrinkScaleMat2d) * m_transformations[unitNdx];
+				m_lodDerivateParts[unitNdx] = calculateLodDerivateParts(m_transformations[unitNdx]);
+			}
+			else
+				break;
+		}
+	}
+}
+
+void MultiTexShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::VertexPacket& packet = *(packets[packetNdx]);
+
+		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
+	}
+}
+
+void MultiTexShader::shadeFragments	(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	DE_ASSERT((int)m_unitTypes.size() == m_numUnits);
+	DE_ASSERT((int)m_transformations.size() == m_numUnits);
+	DE_ASSERT((int)m_lodDerivateParts.size() == m_numUnits);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::FragmentPacket& packet				= packets[packetNdx];
+		const float			colorMultiplier		= 1.0f / (float)m_numUnits;
+		Vec4				outColors[4]		= { Vec4(0.0f), Vec4(0.0f), Vec4(0.0f), Vec4(0.0f) };
+
+		for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
+		{
+			tcu::Vec4 texSamples[4];
+
+			// Read tex coords
+			const tcu::Vec2 texCoords[4] =
+			{
+				rr::readTriangleVarying<float>(packet, context, 0, 0).xy(),
+				rr::readTriangleVarying<float>(packet, context, 0, 1).xy(),
+				rr::readTriangleVarying<float>(packet, context, 0, 2).xy(),
+				rr::readTriangleVarying<float>(packet, context, 0, 3).xy(),
+			};
+
+			// Transform
+			tcu::Vec3 coords3D[4] =
+			{
+				(m_transformations[unitNdx] * Vec4(texCoords[0].x(), texCoords[0].y(), 1.0f, 1.0f)).xyz(),
+				(m_transformations[unitNdx] * Vec4(texCoords[1].x(), texCoords[1].y(), 1.0f, 1.0f)).xyz(),
+				(m_transformations[unitNdx] * Vec4(texCoords[2].x(), texCoords[2].y(), 1.0f, 1.0f)).xyz(),
+				(m_transformations[unitNdx] * Vec4(texCoords[3].x(), texCoords[3].y(), 1.0f, 1.0f)).xyz(),
+			};
+
+			// To 2D
+			const tcu::Vec2 coords2D[4] =
+			{
+				coords3D[0].xy(),
+				coords3D[1].xy(),
+				coords3D[2].xy(),
+				coords3D[3].xy(),
+			};
+
+			// Sample
+			switch (m_unitTypes[unitNdx])
+			{
+				case GL_TEXTURE_2D:			m_uniforms[4*unitNdx].sampler.tex2D->sample4(texSamples, coords2D);			break;
+				case GL_TEXTURE_CUBE_MAP:	m_uniforms[4*unitNdx].sampler.texCube->sample4(texSamples, coords3D);		break;
+				case GL_TEXTURE_2D_ARRAY:	m_uniforms[4*unitNdx].sampler.tex2DArray->sample4(texSamples, coords3D);	break;
+				case GL_TEXTURE_3D:			m_uniforms[4*unitNdx].sampler.tex3D->sample4(texSamples, coords3D);			break;
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+
+			// Add to sum
+			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+				outColors[fragNdx] += colorMultiplier * (texSamples[fragNdx]*m_texScales[unitNdx] + m_texBiases[unitNdx]);
+		}
+
+		// output
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, outColors[fragNdx]);
+	}
+}
+
+class TextureUnitCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASE_ONLY_2D = 0,
+		CASE_ONLY_CUBE,
+		CASE_ONLY_2D_ARRAY,
+		CASE_ONLY_3D,
+		CASE_MIXED,
+
+		CASE_LAST
+	};
+								TextureUnitCase		(Context& context, const char* name, const char* desc, int numUnits /* \note If non-positive, use all units */, CaseType caseType, deUint32 randSeed);
+								~TextureUnitCase	(void);
+
+	void						init				(void);
+	void						deinit				(void);
+	IterateResult				iterate				(void);
+
+private:
+	struct TextureParameters
+	{
+		GLenum internalFormat;
+		GLenum wrapModeS;
+		GLenum wrapModeT;
+		GLenum wrapModeR;
+		GLenum minFilter;
+		GLenum magFilter;
+	};
+
+									TextureUnitCase			(const TextureUnitCase& other);
+	TextureUnitCase&				operator=				(const TextureUnitCase& other);
+
+	void							upload2dTexture			(int texNdx, sglr::Context& context);
+	void							uploadCubeTexture		(int texNdx, sglr::Context& context);
+	void							upload2dArrayTexture	(int texNdx, sglr::Context& context);
+	void							upload3dTexture			(int texNdx, sglr::Context& context);
+
+	void							render					(sglr::Context& context);
+
+	const int						m_numUnitsParam;
+	const CaseType					m_caseType;
+	const deUint32					m_randSeed;
+
+	int								m_numTextures;	//!< \note Needed in addition to m_numUnits since same texture may be bound to many texture units.
+	int								m_numUnits;		//!< = m_numUnitsParam > 0 ? m_numUnitsParam : implementationDefinedMaximum
+
+	vector<GLenum>					m_textureTypes;
+	vector<TextureParameters>		m_textureParams;
+	vector<tcu::Texture2D*>			m_textures2d;
+	vector<tcu::TextureCube*>		m_texturesCube;
+	vector<tcu::Texture2DArray*>	m_textures2dArray;
+	vector<tcu::Texture3D*>			m_textures3d;
+	vector<int>						m_unitTextures;	//!< Which texture is used in a particular unit.
+	vector<int>						m_ndxTexType;	//!< Index of a texture in m_textures2d, m_texturesCube, m_textures2dArray or m_textures3d, depending on texture type.
+	MultiTexShader*					m_shader;
+};
+
+TextureUnitCase::TextureUnitCase (Context& context, const char* name, const char* desc, int numUnits, CaseType caseType, deUint32 randSeed)
+	: TestCase			(context, tcu::NODETYPE_SELF_VALIDATE, name, desc)
+	, m_numUnitsParam	(numUnits)
+	, m_caseType		(caseType)
+	, m_randSeed		(randSeed)
+	, m_shader			(DE_NULL)
+{
+}
+
+TextureUnitCase::~TextureUnitCase (void)
+{
+	TextureUnitCase::deinit();
+}
+
+void TextureUnitCase::deinit (void)
+{
+	for (vector<tcu::Texture2D*>::iterator i = m_textures2d.begin(); i != m_textures2d.end(); i++)
+		delete *i;
+	m_textures2d.clear();
+
+	for (vector<tcu::TextureCube*>::iterator i = m_texturesCube.begin(); i != m_texturesCube.end(); i++)
+		delete *i;
+	m_texturesCube.clear();
+
+	for (vector<tcu::Texture2DArray*>::iterator i = m_textures2dArray.begin(); i != m_textures2dArray.end(); i++)
+		delete *i;
+	m_textures2dArray.clear();
+
+	for (vector<tcu::Texture3D*>::iterator i = m_textures3d.begin(); i != m_textures3d.end(); i++)
+		delete *i;
+	m_textures3d.clear();
+
+	delete m_shader;
+	m_shader = DE_NULL;
+}
+
+void TextureUnitCase::init (void)
+{
+	m_numUnits = m_numUnitsParam > 0 ? m_numUnitsParam : m_context.getContextInfo().getInt(GL_MAX_TEXTURE_IMAGE_UNITS);
+
+	// Make the textures.
+
+	try
+	{
+		tcu::TestLog&	log	= m_testCtx.getLog();
+		de::Random		rnd	(m_randSeed);
+
+		if (rnd.getFloat() < 0.7f)
+			m_numTextures = m_numUnits;											// In most cases use one unit per texture.
+		else
+			m_numTextures = rnd.getInt(deMax32(1, m_numUnits - 2), m_numUnits);	// Sometimes assign same texture to multiple units.
+
+		log << tcu::TestLog::Message << ("Using " + de::toString(m_numUnits) + " texture unit(s) and " + de::toString(m_numTextures) + " texture(s)").c_str() << tcu::TestLog::EndMessage;
+
+		m_textureTypes.reserve(m_numTextures);
+		m_textureParams.reserve(m_numTextures);
+		m_ndxTexType.reserve(m_numTextures);
+
+		// Generate textures.
+
+		for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+		{
+			// Either fixed or randomized target types, and randomized parameters for every texture.
+
+			TextureParameters	params;
+
+			DE_STATIC_ASSERT(CASE_ONLY_2D == 0 && CASE_MIXED + 1 == CASE_LAST);
+
+			int						texType			= m_caseType == CASE_MIXED ? rnd.getInt(0, (int)CASE_MIXED - 1) : (int)m_caseType;
+			bool					is2dTex			= texType == 0;
+			bool					isCubeTex		= texType == 1;
+			bool					is2dArrayTex	= texType == 2;
+			bool					is3dTex			= texType == 3;
+
+			DE_ASSERT(is2dTex || isCubeTex || is2dArrayTex || is3dTex);
+
+			GLenum					type			= is2dTex		? GL_TEXTURE_2D		: isCubeTex ? GL_TEXTURE_CUBE_MAP	: is2dArrayTex ? GL_TEXTURE_2D_ARRAY		: GL_TEXTURE_3D;
+			const int				texWidth		= is2dTex		? TEXTURE_WIDTH_2D	: isCubeTex ? TEXTURE_WIDTH_CUBE	: is2dArrayTex ? TEXTURE_WIDTH_2D_ARRAY		: TEXTURE_WIDTH_3D;
+			const int				texHeight		= is2dTex		? TEXTURE_HEIGHT_2D	: isCubeTex ? TEXTURE_HEIGHT_CUBE	: is2dArrayTex ? TEXTURE_HEIGHT_2D_ARRAY	: TEXTURE_HEIGHT_3D;
+
+			const int				texDepth		= is3dTex ? TEXTURE_DEPTH_3D : 1;
+			const int				texLayers		= is2dArrayTex ? TEXTURE_LAYERS_2D_ARRAY : 1;
+
+			bool					mipmaps			= (deIsPowerOfTwo32(texWidth) && deIsPowerOfTwo32(texHeight) && deIsPowerOfTwo32(texDepth));
+			int						numLevels		= mipmaps ? deLog2Floor32(de::max(de::max(texWidth, texHeight), texDepth))+1 : 1;
+
+			params.internalFormat = s_testSizedInternalFormats[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testSizedInternalFormats) - 1)];
+
+			bool					isFilterable	= glu::isGLInternalColorFormatFilterable(params.internalFormat);
+
+			params.wrapModeS = s_testWrapModes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testWrapModes) - 1)];
+			params.wrapModeT = s_testWrapModes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testWrapModes) - 1)];
+			params.wrapModeR = s_testWrapModes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testWrapModes) - 1)];
+
+			params.magFilter = isFilterable ? s_testMagFilters[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testMagFilters) - 1)] : GL_NEAREST;
+
+			if (mipmaps)
+				params.minFilter = isFilterable ?
+					s_testMinFilters			[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testMinFilters) - 1)] :
+					s_testNearestMinFilters		[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testNearestMinFilters) - 1)];
+			else
+				params.minFilter = isFilterable ?
+					s_testNonMipmapMinFilters	[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testNonMipmapMinFilters) - 1)] :
+					GL_NEAREST;
+
+			m_textureTypes.push_back(type);
+			m_textureParams.push_back(params);
+
+			// Create new texture.
+
+			tcu::TextureFormat texFormat = glu::mapGLInternalFormat((deUint32)params.internalFormat);
+
+			if (is2dTex)
+			{
+				m_ndxTexType.push_back((int)m_textures2d.size()); // Remember the index this texture has in the 2d texture vector.
+				m_textures2d.push_back(new tcu::Texture2D(texFormat, texWidth, texHeight));
+			}
+			else if (isCubeTex)
+			{
+				m_ndxTexType.push_back((int)m_texturesCube.size()); // Remember the index this texture has in the cube texture vector.
+				DE_ASSERT(texWidth == texHeight);
+				m_texturesCube.push_back(new tcu::TextureCube(texFormat, texWidth));
+			}
+			else if (is2dArrayTex)
+			{
+				m_ndxTexType.push_back((int)m_textures2dArray.size()); // Remember the index this texture has in the 2d array texture vector.
+				m_textures2dArray.push_back(new tcu::Texture2DArray(texFormat, texWidth, texHeight, texLayers));
+			}
+			else
+			{
+				m_ndxTexType.push_back((int)m_textures3d.size()); // Remember the index this texture has in the 3d vector.
+				m_textures3d.push_back(new tcu::Texture3D(texFormat, texWidth, texHeight, texDepth));
+			}
+
+			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFormat);
+			Vec4					cBias		= fmtInfo.valueMin;
+			Vec4					cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+			// Fill with grid texture.
+
+			int numFaces = isCubeTex ? (int)tcu::CUBEFACE_LAST : 1;
+
+			for (int face = 0; face < numFaces; face++)
+			{
+				deUint32 rgb	= rnd.getUint32() & 0x00ffffff;
+				deUint32 alpha	= 0xff000000;
+
+				deUint32 colorA = alpha | rgb;
+				deUint32 colorB = alpha | ((~rgb) & 0x00ffffff);
+
+				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+				{
+					if (is2dTex)
+						m_textures2d.back()->allocLevel(levelNdx);
+					else if (isCubeTex)
+						m_texturesCube.back()->allocLevel((tcu::CubeFace)face, levelNdx);
+					else if (is2dArrayTex)
+						m_textures2dArray.back()->allocLevel(levelNdx);
+					else
+						m_textures3d.back()->allocLevel(levelNdx);
+
+					int curCellSize = deMax32(1, GRID_CELL_SIZE >> levelNdx); // \note Scale grid cell size for mipmaps.
+
+					tcu::PixelBufferAccess access = is2dTex			? m_textures2d.back()->getLevel(levelNdx)
+												  : isCubeTex		? m_texturesCube.back()->getLevelFace(levelNdx, (tcu::CubeFace)face)
+												  : is2dArrayTex	? m_textures2dArray.back()->getLevel(levelNdx)
+												  :					  m_textures3d.back()->getLevel(levelNdx);
+
+					tcu::fillWithGrid(access, curCellSize, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+				}
+			}
+		}
+
+		// Assign a texture index to each unit.
+
+		m_unitTextures.reserve(m_numUnits);
+
+		// \note Every texture is used at least once.
+		for (int i = 0; i < m_numTextures; i++)
+			m_unitTextures.push_back(i);
+
+		// Assign a random texture to remaining units.
+		while ((int)m_unitTextures.size() < m_numUnits)
+			m_unitTextures.push_back(rnd.getInt(0, m_numTextures - 1));
+
+		rnd.shuffle(m_unitTextures.begin(), m_unitTextures.end());
+
+		// Generate information for shader.
+
+		vector<GLenum>			unitTypes;
+		vector<Vec4>			texScales;
+		vector<Vec4>			texBiases;
+		vector<glu::DataType>	samplerTypes;
+		vector<int>				num2dArrayLayers;
+
+		unitTypes.reserve(m_numUnits);
+		texScales.reserve(m_numUnits);
+		texBiases.reserve(m_numUnits);
+		samplerTypes.reserve(m_numUnits);
+		num2dArrayLayers.reserve(m_numUnits);
+
+		for (int i = 0; i < m_numUnits; i++)
+		{
+			int						texNdx		= m_unitTextures[i];
+			GLenum					type		= m_textureTypes[texNdx];
+			tcu::TextureFormat		fmt			= glu::mapGLInternalFormat(m_textureParams[texNdx].internalFormat);
+			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(fmt);
+
+			unitTypes.push_back(type);
+
+			if (type == GL_TEXTURE_2D_ARRAY)
+				num2dArrayLayers.push_back(m_textures2dArray[m_ndxTexType[texNdx]]->getNumLayers());
+
+			texScales.push_back(fmtInfo.lookupScale);
+			texBiases.push_back(fmtInfo.lookupBias);
+
+			switch (type)
+			{
+				case GL_TEXTURE_2D:			samplerTypes.push_back(glu::getSampler2DType(fmt));			break;
+				case GL_TEXTURE_CUBE_MAP:	samplerTypes.push_back(glu::getSamplerCubeType(fmt));		break;
+				case GL_TEXTURE_2D_ARRAY:	samplerTypes.push_back(glu::getSampler2DArrayType(fmt));	break;
+				case GL_TEXTURE_3D:			samplerTypes.push_back(glu::getSampler3DType(fmt));			break;
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+		}
+
+		// Create shader.
+
+		DE_ASSERT(m_shader == DE_NULL);
+		m_shader = new MultiTexShader(rnd.getUint32(), m_numUnits, unitTypes, samplerTypes, texScales, texBiases, num2dArrayLayers);
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		TextureUnitCase::deinit();
+		throw;
+	}
+}
+
+TextureUnitCase::IterateResult TextureUnitCase::iterate (void)
+{
+	glu::RenderContext&			renderCtx			= m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget		= renderCtx.getRenderTarget();
+	tcu::TestLog&				log					= m_testCtx.getLog();
+	de::Random					rnd					(m_randSeed);
+
+	int							viewportWidth		= deMin32(VIEWPORT_WIDTH, renderTarget.getWidth());
+	int							viewportHeight		= deMin32(VIEWPORT_HEIGHT, renderTarget.getHeight());
+	int							viewportX			= rnd.getInt(0, renderTarget.getWidth() - viewportWidth);
+	int							viewportY			= rnd.getInt(0, renderTarget.getHeight() - viewportHeight);
+
+	tcu::Surface				gles3Frame			(viewportWidth, viewportHeight);
+	tcu::Surface				refFrame			(viewportWidth, viewportHeight);
+
+	{
+		// First we do some tricks to make the LODs safer wrt. precision issues. See MultiTexShader::makeSafeLods().
+
+		vector<IVec3> texSizes;
+		texSizes.reserve(m_numUnits);
+
+		for (int i = 0; i < m_numUnits; i++)
+		{
+			int		texNdx			= m_unitTextures[i];
+			int		texNdxInType	= m_ndxTexType[texNdx];
+			GLenum	type			= m_textureTypes[texNdx];
+
+			switch (type)
+			{
+				case GL_TEXTURE_2D:			texSizes.push_back(IVec3(m_textures2d[texNdxInType]->getWidth(),		m_textures2d[texNdxInType]->getHeight(),		0));										break;
+				case GL_TEXTURE_CUBE_MAP:	texSizes.push_back(IVec3(m_texturesCube[texNdxInType]->getSize(),		m_texturesCube[texNdxInType]->getSize(),		0));										break;
+				case GL_TEXTURE_2D_ARRAY:	texSizes.push_back(IVec3(m_textures2dArray[texNdxInType]->getWidth(),	m_textures2dArray[texNdxInType]->getHeight(),	0));										break;
+				case GL_TEXTURE_3D:			texSizes.push_back(IVec3(m_textures3d[texNdxInType]->getWidth(),		m_textures3d[texNdxInType]->getHeight(),		m_textures3d[texNdxInType]->getDepth()));	break;
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+		}
+
+		m_shader->makeSafeLods(texSizes, IVec2(viewportWidth, viewportHeight));
+	}
+
+	// Render using GLES3.
+	{
+		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS|sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(viewportX, viewportY, viewportWidth, viewportHeight));
+
+		render(context);
+
+		context.readPixels(gles3Frame, 0, 0, viewportWidth, viewportHeight);
+	}
+
+	// Render reference image.
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), 0 /* depth */, 0 /* stencil */, viewportWidth, viewportHeight);
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		render(context);
+
+		context.readPixels(refFrame, 0, 0, viewportWidth, viewportHeight);
+	}
+
+	// Compare images.
+	const float		threshold	= 0.001f;
+	bool			isOk		= tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, gles3Frame, threshold, tcu::COMPARE_LOG_RESULT);
+
+	// Store test result.
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+void TextureUnitCase::upload2dTexture (int texNdx, sglr::Context& context)
+{
+	int						ndx2d		= m_ndxTexType[texNdx];
+	const tcu::Texture2D*	texture		= m_textures2d[ndx2d];
+	glu::TransferFormat		formatGl	= glu::getTransferFormat(glu::mapGLInternalFormat(m_textureParams[texNdx].internalFormat));
+
+	context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+	for (int levelNdx = 0; levelNdx < texture->getNumLevels(); levelNdx++)
+	{
+		if (texture->isLevelEmpty(levelNdx))
+			continue;
+
+		tcu::ConstPixelBufferAccess		access	= texture->getLevel(levelNdx);
+		int								width	= access.getWidth();
+		int								height	= access.getHeight();
+
+		DE_ASSERT(access.getRowPitch() == access.getFormat().getPixelSize()*width);
+
+		context.texImage2D(GL_TEXTURE_2D, levelNdx, m_textureParams[texNdx].internalFormat, width, height, 0 /* border */, formatGl.format, formatGl.dataType, access.getDataPtr());
+		GLU_EXPECT_NO_ERROR(context.getError(), "Set 2d texture image data");
+	}
+}
+
+void TextureUnitCase::uploadCubeTexture (int texNdx, sglr::Context& context)
+{
+	int							ndxCube		= m_ndxTexType[texNdx];
+	const tcu::TextureCube*		texture		= m_texturesCube[ndxCube];
+	glu::TransferFormat			formatGl	= glu::getTransferFormat(glu::mapGLInternalFormat(m_textureParams[texNdx].internalFormat));
+
+	context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+	for (int face = 0; face < (int)tcu::CUBEFACE_LAST; face++)
+	{
+		for (int levelNdx = 0; levelNdx < texture->getNumLevels(); levelNdx++)
+		{
+			if (texture->isLevelEmpty((tcu::CubeFace)face, levelNdx))
+				continue;
+
+			tcu::ConstPixelBufferAccess		access	= texture->getLevelFace(levelNdx, (tcu::CubeFace)face);
+			int								width	= access.getWidth();
+			int								height	= access.getHeight();
+
+			DE_ASSERT(access.getRowPitch() == access.getFormat().getPixelSize()*width);
+
+			context.texImage2D(s_cubeFaceTargets[face], levelNdx, m_textureParams[texNdx].internalFormat, width, height, 0 /* border */, formatGl.format, formatGl.dataType, access.getDataPtr());
+			GLU_EXPECT_NO_ERROR(context.getError(), "Set cube map image data");
+		}
+	}
+}
+
+void TextureUnitCase::upload2dArrayTexture (int texNdx, sglr::Context& context)
+{
+	int							ndx2dArray	= m_ndxTexType[texNdx];
+	const tcu::Texture2DArray*	texture		= m_textures2dArray[ndx2dArray];
+	glu::TransferFormat			formatGl	= glu::getTransferFormat(glu::mapGLInternalFormat(m_textureParams[texNdx].internalFormat));
+
+	context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+	for (int levelNdx = 0; levelNdx < texture->getNumLevels(); levelNdx++)
+	{
+		if (texture->isLevelEmpty(levelNdx))
+			continue;
+
+		tcu::ConstPixelBufferAccess	access	= texture->getLevel(levelNdx);
+		int							width	= access.getWidth();
+		int							height	= access.getHeight();
+		int							layers	= access.getDepth();
+
+		DE_ASSERT(access.getRowPitch() == access.getFormat().getPixelSize()*width);
+		DE_ASSERT(access.getSlicePitch() == access.getFormat().getPixelSize()*width*height);
+
+		context.texImage3D(GL_TEXTURE_2D_ARRAY, levelNdx, m_textureParams[texNdx].internalFormat, width, height, layers, 0 /* border */, formatGl.format, formatGl.dataType, access.getDataPtr());
+		GLU_EXPECT_NO_ERROR(context.getError(), "Set 2d array texture image data");
+	}
+}
+
+void TextureUnitCase::upload3dTexture (int texNdx, sglr::Context& context)
+{
+	int							ndx3d		= m_ndxTexType[texNdx];
+	const tcu::Texture3D*		texture		= m_textures3d[ndx3d];
+	glu::TransferFormat			formatGl	= glu::getTransferFormat(glu::mapGLInternalFormat(m_textureParams[texNdx].internalFormat));
+
+	context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+	for (int levelNdx = 0; levelNdx < texture->getNumLevels(); levelNdx++)
+	{
+		if (texture->isLevelEmpty(levelNdx))
+			continue;
+
+		tcu::ConstPixelBufferAccess	access	= texture->getLevel(levelNdx);
+		int							width	= access.getWidth();
+		int							height	= access.getHeight();
+		int							depth	= access.getDepth();
+
+		DE_ASSERT(access.getRowPitch() == access.getFormat().getPixelSize()*width);
+		DE_ASSERT(access.getSlicePitch() == access.getFormat().getPixelSize()*width*height);
+
+		context.texImage3D(GL_TEXTURE_3D, levelNdx, m_textureParams[texNdx].internalFormat, width, height, depth, 0 /* border */, formatGl.format, formatGl.dataType, access.getDataPtr());
+		GLU_EXPECT_NO_ERROR(context.getError(), "Set 3d texture image data");
+	}
+}
+
+void TextureUnitCase::render (sglr::Context& context)
+{
+	// Setup textures.
+
+	vector<deUint32>	textureGLNames;
+	vector<bool>		isTextureSetUp(m_numTextures, false); // \note Same texture may be bound to multiple units, but we only want to set up parameters and data once per texture.
+
+	textureGLNames.resize(m_numTextures);
+	context.genTextures(m_numTextures, &textureGLNames[0]);
+	GLU_EXPECT_NO_ERROR(context.getError(), "Generate textures");
+
+	for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
+	{
+		int texNdx = m_unitTextures[unitNdx];
+
+		// Bind texture to unit.
+		context.activeTexture(GL_TEXTURE0 + unitNdx);
+		GLU_EXPECT_NO_ERROR(context.getError(), "Set active texture");
+		context.bindTexture(m_textureTypes[texNdx], textureGLNames[texNdx]);
+		GLU_EXPECT_NO_ERROR(context.getError(), "Bind texture");
+
+		if (!isTextureSetUp[texNdx])
+		{
+			// Binding this texture for first time, so set parameters and data.
+
+			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_WRAP_S, m_textureParams[texNdx].wrapModeS);
+			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_WRAP_T, m_textureParams[texNdx].wrapModeT);
+			if (m_textureTypes[texNdx] == GL_TEXTURE_3D)
+				context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_WRAP_R, m_textureParams[texNdx].wrapModeR);
+			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_MIN_FILTER, m_textureParams[texNdx].minFilter);
+			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_MAG_FILTER, m_textureParams[texNdx].magFilter);
+			GLU_EXPECT_NO_ERROR(context.getError(), "Set texture parameters");
+
+			switch (m_textureTypes[texNdx])
+			{
+				case GL_TEXTURE_2D:			upload2dTexture(texNdx, context);		break;
+				case GL_TEXTURE_CUBE_MAP:	uploadCubeTexture(texNdx, context);		break;
+				case GL_TEXTURE_2D_ARRAY:	upload2dArrayTexture(texNdx, context);	break;
+				case GL_TEXTURE_3D:			upload3dTexture(texNdx, context);		break;
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+
+			isTextureSetUp[texNdx] = true; // Don't set up this texture's parameters and data again later.
+		}
+	}
+
+	GLU_EXPECT_NO_ERROR(context.getError(), "Set textures");
+
+	// Setup shader
+
+	deUint32 shaderID = context.createProgram(m_shader);
+
+	// Draw.
+
+	context.clearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	m_shader->setUniforms(context, shaderID);
+	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
+	GLU_EXPECT_NO_ERROR(context.getError(), "Draw");
+
+	// Delete previously generated texture names.
+
+	context.deleteTextures(m_numTextures, &textureGLNames[0]);
+	GLU_EXPECT_NO_ERROR(context.getError(), "Delete textures");
+}
+
+TextureUnitTests::TextureUnitTests (Context& context)
+	: TestCaseGroup(context, "units", "Texture Unit Usage Tests")
+{
+}
+
+TextureUnitTests::~TextureUnitTests (void)
+{
+}
+
+void TextureUnitTests::init (void)
+{
+	const int numTestsPerGroup = 10;
+
+	static const int unitCounts[] =
+	{
+		2,
+		4,
+		8,
+		-1 // \note Negative stands for the implementation-specified maximum.
+	};
+
+	for (int unitCountNdx = 0; unitCountNdx < DE_LENGTH_OF_ARRAY(unitCounts); unitCountNdx++)
+	{
+		int numUnits = unitCounts[unitCountNdx];
+
+		string countGroupName = (unitCounts[unitCountNdx] < 0 ? "all" : de::toString(numUnits)) + "_units";
+
+		tcu::TestCaseGroup* countGroup = new tcu::TestCaseGroup(m_testCtx, countGroupName.c_str(), "");
+		addChild(countGroup);
+
+		DE_STATIC_ASSERT((int)TextureUnitCase::CASE_ONLY_2D == 0);
+
+		for (int caseType = (int)TextureUnitCase::CASE_ONLY_2D; caseType < (int)TextureUnitCase::CASE_LAST; caseType++)
+		{
+			const char* caseTypeGroupName = (TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_ONLY_2D		? "only_2d"
+										  : (TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_ONLY_CUBE		? "only_cube"
+										  : (TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_ONLY_2D_ARRAY	? "only_2d_array"
+										  : (TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_ONLY_3D		? "only_3d"
+										  : (TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_MIXED			? "mixed"
+										  : DE_NULL;
+
+			DE_ASSERT(caseTypeGroupName != DE_NULL);
+
+			tcu::TestCaseGroup* caseTypeGroup = new tcu::TestCaseGroup(m_testCtx, caseTypeGroupName, "");
+			countGroup->addChild(caseTypeGroup);
+
+			for (int testNdx = 0; testNdx < numTestsPerGroup; testNdx++)
+				caseTypeGroup->addChild(new TextureUnitCase(m_context, de::toString(testNdx).c_str(), "", numUnits, (TextureUnitCase::CaseType)caseType, deUint32Hash((deUint32)testNdx)));
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTextureUnitTests.hpp b/modules/gles3/functional/es3fTextureUnitTests.hpp
new file mode 100644
index 0000000..743a45d
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureUnitTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FTEXTUREUNITTESTS_HPP
+#define _ES3FTEXTUREUNITTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture unit usage tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class TextureUnitTests : public TestCaseGroup
+{
+public:
+						TextureUnitTests	(Context& context);
+						~TextureUnitTests	(void);
+
+	void				init				(void);
+
+private:
+						TextureUnitTests	(const TextureUnitTests& other);
+	TextureUnitTests&	operator=			(const TextureUnitTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTEXTUREUNITTESTS_HPP
diff --git a/modules/gles3/functional/es3fTextureWrapTests.cpp b/modules/gles3/functional/es3fTextureWrapTests.cpp
new file mode 100644
index 0000000..a472467
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureWrapTests.cpp
@@ -0,0 +1,567 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture wrap mode tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTextureWrapTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuCompressedTexture.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTexLookupVerifier.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMemory.h"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using tcu::TestLog;
+using tcu::CompressedTexture;
+using std::vector;
+using std::string;
+using tcu::Sampler;
+using namespace glu;
+using namespace gls::TextureTestUtil;
+
+//! Checks whether any ASTC version (LDR, HDR, full) is supported.
+static inline bool isASTCSupported (const glu::ContextInfo& contextInfo)
+{
+	const vector<string>& extensions = contextInfo.getExtensions();
+
+	for (int extNdx = 0; extNdx < (int)extensions.size(); extNdx++)
+	{
+		const string& ext = extensions[extNdx];
+
+		if (ext == "GL_KHR_texture_compression_astc_ldr" ||
+			ext == "GL_KHR_texture_compression_astc_hdr" ||
+			ext == "GL_OES_texture_compression_astc")
+			return true;
+	}
+
+	return false;
+}
+
+enum
+{
+	VIEWPORT_WIDTH		= 256,
+	VIEWPORT_HEIGHT		= 256
+};
+
+class TextureWrapCase : public tcu::TestCase
+{
+public:
+									TextureWrapCase			(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, deUint32 format, deUint32 dataType, deUint32 wrapS, deUint32 wrapT, deUint32 minFilter, deUint32 magFilter, int width, int height);
+									TextureWrapCase			(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, deUint32 wrapS, deUint32 wrapT, deUint32 minFilter, deUint32 magFilter, const std::vector<std::string>& filenames);
+									TextureWrapCase			(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, CompressedTexture::Format compressedFormat, deUint32 wrapS, deUint32 wrapT, deUint32 minFilter, deUint32 magFilter, int width, int height);
+									~TextureWrapCase		(void);
+
+	void							init					(void);
+	void							deinit					(void);
+	IterateResult					iterate					(void);
+
+private:
+									TextureWrapCase			(const TextureWrapCase& other);
+	TextureWrapCase&				operator=				(const TextureWrapCase& other);
+
+	struct Case
+	{
+		tcu::Vec2 bottomLeft;
+		tcu::Vec2 topRight;
+
+		Case (void) {}
+		Case (const tcu::Vec2& bl, const tcu::Vec2& tr) : bottomLeft(bl), topRight(tr) {}
+	};
+
+	glu::RenderContext&				m_renderCtx;
+	const glu::ContextInfo&			m_renderCtxInfo;
+
+	const deUint32					m_format;
+	const deUint32					m_dataType;
+	const CompressedTexture::Format	m_compressedFormat;
+	const deUint32					m_wrapS;
+	const deUint32					m_wrapT;
+	const deUint32					m_minFilter;
+	const deUint32					m_magFilter;
+
+	int								m_width;
+	int								m_height;
+	const std::vector<std::string>	m_filenames;
+
+	vector<Case>					m_cases;
+	int								m_caseNdx;
+
+	glu::Texture2D*					m_texture;
+	TextureRenderer					m_renderer;
+};
+
+TextureWrapCase::TextureWrapCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, deUint32 format, deUint32 dataType, deUint32 wrapS, deUint32 wrapT, deUint32 minFilter, deUint32 magFilter, int width, int height)
+	: TestCase				(testCtx, name, description)
+	, m_renderCtx			(renderCtx)
+	, m_renderCtxInfo		(ctxInfo)
+	, m_format				(format)
+	, m_dataType			(dataType)
+	, m_compressedFormat	(CompressedTexture::FORMAT_LAST)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_width				(width)
+	, m_height				(height)
+	, m_caseNdx				(0)
+	, m_texture				(DE_NULL)
+	, m_renderer			(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+TextureWrapCase::TextureWrapCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, deUint32 wrapS, deUint32 wrapT, deUint32 minFilter, deUint32 magFilter, const std::vector<std::string>& filenames)
+	: TestCase				(testCtx, name, description)
+	, m_renderCtx			(renderCtx)
+	, m_renderCtxInfo		(ctxInfo)
+	, m_format				(GL_NONE)
+	, m_dataType			(GL_NONE)
+	, m_compressedFormat	(CompressedTexture::FORMAT_LAST)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_width				(0)
+	, m_height				(0)
+	, m_filenames			(filenames)
+	, m_caseNdx				(0)
+	, m_texture				(DE_NULL)
+	, m_renderer			(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+TextureWrapCase::TextureWrapCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, CompressedTexture::Format compressedFormat, deUint32 wrapS, deUint32 wrapT, deUint32 minFilter, deUint32 magFilter, int width, int height)
+	: TestCase				(testCtx, name, description)
+	, m_renderCtx			(renderCtx)
+	, m_renderCtxInfo		(ctxInfo)
+	, m_format				(GL_NONE)
+	, m_dataType			(GL_NONE)
+	, m_compressedFormat	(compressedFormat)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_width				(width)
+	, m_height				(height)
+	, m_caseNdx				(0)
+	, m_texture				(DE_NULL)
+	, m_renderer			(renderCtx, testCtx, glu::GLSL_VERSION_300_ES, glu::PRECISION_MEDIUMP)
+{
+}
+
+
+TextureWrapCase::~TextureWrapCase (void)
+{
+	deinit();
+}
+
+void TextureWrapCase::init (void)
+{
+	// Load or generate texture.
+
+	if (!m_filenames.empty())
+	{
+		// Load compressed texture from file.
+
+		DE_ASSERT(m_width == 0 && m_height == 0 && m_format == GL_NONE && m_dataType == GL_NONE);
+
+		m_texture	= glu::Texture2D::create(m_renderCtx, m_renderCtxInfo, m_testCtx.getArchive(), (int)m_filenames.size(), m_filenames);
+		m_width		= m_texture->getRefTexture().getWidth();
+		m_height	= m_texture->getRefTexture().getHeight();
+	}
+	else if (m_compressedFormat != CompressedTexture::FORMAT_LAST)
+	{
+		// Generate compressed texture.
+
+		DE_ASSERT(m_format == GL_NONE && m_dataType == GL_NONE);
+
+		if (tcu::isEtcFormat(m_compressedFormat))
+		{
+			// Create ETC texture. Any content is valid.
+
+			tcu::CompressedTexture	compressedTexture	(m_compressedFormat, m_width, m_height);
+			const int				dataSize			= compressedTexture.getDataSize();
+			deUint8* const			data				= (deUint8*)compressedTexture.getData();
+			de::Random				rnd					(deStringHash(getName()));
+
+			for (int i = 0; i < dataSize; i++)
+				data[i] = rnd.getUint32() & 0xff;
+
+			m_texture = new glu::Texture2D(m_renderCtx, m_renderCtxInfo, 1, &compressedTexture);
+		}
+		else if (tcu::isASTCFormat(m_compressedFormat))
+		{
+			// Create ASTC texture by picking from a set of pre-generated blocks.
+
+			static const int		BLOCK_SIZE				= 16;
+			static const deUint8	blocks[][BLOCK_SIZE]	=
+			{
+				// \note All of the following blocks are valid in LDR mode.
+				{ 252,	253,	255,	255,	255,	255,	255,	255,	8,		71,		90,		78,		22,		17,		26,		66,		},
+				{ 252,	253,	255,	255,	255,	255,	255,	255,	220,	74,		139,	235,	249,	6,		145,	125		},
+				{ 252,	253,	255,	255,	255,	255,	255,	255,	223,	251,	28,		206,	54,		251,	160,	174		},
+				{ 252,	253,	255,	255,	255,	255,	255,	255,	39,		4,		153,	219,	180,	61,		51,		37		},
+				{ 67,	2,		0,		254,	1,		0,		64,		215,	83,		211,	159,	105,	41,		140,	50,		2		},
+				{ 67,	130,	0,		170,	84,		255,	65,		215,	83,		211,	159,	105,	41,		140,	50,		2		},
+				{ 67,	2,		129,	38,		51,		229,	95,		215,	83,		211,	159,	105,	41,		140,	50,		2		},
+				{ 67,	130,	193,	56,		213,	144,	95,		215,	83,		211,	159,	105,	41,		140,	50,		2		}
+			};
+
+			if (!isASTCSupported(m_renderCtxInfo)) // \note Any level of ASTC support is enough, since we're only using LDR blocks.
+				throw tcu::NotSupportedError("ASTC not supported");
+
+			tcu::CompressedTexture	compressedTexture	(m_compressedFormat, m_width, m_height);
+			const int				dataSize			= compressedTexture.getDataSize();
+			deUint8* const			data				= (deUint8*)compressedTexture.getData();
+			de::Random				rnd					(deStringHash(getName()));
+			DE_ASSERT(dataSize % BLOCK_SIZE == 0);
+
+			for (int i = 0; i < dataSize/BLOCK_SIZE; i++)
+				deMemcpy(&data[i*BLOCK_SIZE], &blocks[rnd.getInt(0, DE_LENGTH_OF_ARRAY(blocks)-1)][0], BLOCK_SIZE);
+
+			m_texture = new glu::Texture2D(m_renderCtx, m_renderCtxInfo, 1, &compressedTexture);
+		}
+		else
+			DE_ASSERT(false);
+	}
+	else
+	{
+		m_texture = new Texture2D(m_renderCtx, m_format, m_dataType, m_width, m_height);
+
+		// Fill level 0.
+		m_texture->getRefTexture().allocLevel(0);
+		tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), tcu::Vec4(-0.5f, -0.5f, -0.5f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f));
+
+		m_texture->upload();
+	}
+
+	// Sub-cases.
+
+	m_cases.push_back(Case(tcu::Vec2(-1.5f, -3.0f), tcu::Vec2(1.5f, 2.5f)));
+	m_cases.push_back(Case(tcu::Vec2(-0.5f, 0.75f), tcu::Vec2(0.25f, 1.25f)));
+	DE_ASSERT(m_caseNdx == 0);
+
+	// Initialize to success, set to failure later if needed.
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void TextureWrapCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+TextureWrapCase::IterateResult TextureWrapCase::iterate (void)
+{
+	const glw::Functions&			gl								= m_renderCtx.getFunctions();
+	TestLog&						log								= m_testCtx.getLog();
+	const RandomViewport			viewport						(m_renderCtx.getRenderTarget(), VIEWPORT_WIDTH, VIEWPORT_HEIGHT, deStringHash(getName()) + m_caseNdx);
+	tcu::Surface					renderedFrame					(viewport.width, viewport.height);
+	tcu::Surface					referenceFrame					(viewport.width, viewport.height);
+	ReferenceParams					refParams						(TEXTURETYPE_2D);
+	const tcu::TextureFormat		texFormat						= m_texture->getRefTexture().getFormat();
+	vector<float>					texCoord;
+	const tcu::TextureFormatInfo	texFormatInfo					= tcu::getTextureFormatInfo(texFormat);
+	// \note For non-sRGB ASTC formats, the values are fp16 in range [0..1], not the range assumed given by tcu::getTextureFormatInfo().
+	const bool						useDefaultColorScaleAndBias		= !tcu::isASTCFormat(m_compressedFormat) || tcu::isASTCSRGBFormat(m_compressedFormat);
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+
+	// Setup filtering and wrap modes.
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Parameters for reference images.
+	refParams.sampler		= mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	refParams.lodMode		= LODMODE_EXACT;
+	refParams.samplerType	= getSamplerType(m_texture->getRefTexture().getFormat());
+	refParams.colorScale	= useDefaultColorScaleAndBias ? texFormatInfo.lookupScale	: tcu::Vec4(1.0f);
+	refParams.colorBias		= useDefaultColorScaleAndBias ? texFormatInfo.lookupBias	: tcu::Vec4(0.0f);
+
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+	computeQuadTexCoord2D(texCoord, m_cases[m_caseNdx].bottomLeft, m_cases[m_caseNdx].topRight);
+	m_renderer.renderQuad(0, &texCoord[0], refParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	{
+		const tcu::ScopedLogSection		section			(log, string("Test") + de::toString(m_caseNdx), string("Test ") + de::toString(m_caseNdx));
+		const bool						isNearestOnly	= m_minFilter == GL_NEAREST && m_magFilter == GL_NEAREST;
+		const bool						isSRGB			= texFormat.order == tcu::TextureFormat::sRGB || texFormat.order == tcu::TextureFormat::sRGBA;
+		const tcu::PixelFormat			pixelFormat		= m_renderCtx.getRenderTarget().getPixelFormat();
+		const tcu::IVec4				colorBits		= tcu::max(getBitsVec(pixelFormat) - (isNearestOnly && !isSRGB ? 1 : 2), tcu::IVec4(0));
+		tcu::LodPrecision				lodPrecision;
+		tcu::LookupPrecision			lookupPrecision;
+
+		lodPrecision.derivateBits		= 18;
+		lodPrecision.lodBits			= 5;
+		lookupPrecision.colorThreshold	= tcu::computeFixedPointThreshold(colorBits) / refParams.colorScale;
+		lookupPrecision.coordBits		= tcu::IVec3(20,20,0);
+		lookupPrecision.uvwBits			= tcu::IVec3(5,5,0);
+		lookupPrecision.colorMask		= getCompareMask(pixelFormat);
+
+		log << TestLog::Message << "Note: lookup coordinates: bottom-left " << m_cases[m_caseNdx].bottomLeft << ", top-right " << m_cases[m_caseNdx].topRight << TestLog::EndMessage;
+
+		const bool isOk = verifyTextureResult(m_testCtx, renderedFrame.getAccess(), m_texture->getRefTexture(),
+											  &texCoord[0], refParams, lookupPrecision, lodPrecision, pixelFormat);
+
+		if (!isOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+	}
+
+	m_caseNdx++;
+	return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
+}
+
+TextureWrapTests::TextureWrapTests (Context& context)
+	: TestCaseGroup(context, "wrap", "Wrap Mode Tests")
+{
+}
+
+TextureWrapTests::~TextureWrapTests (void)
+{
+}
+
+void TextureWrapTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE },
+		{ "repeat",		GL_REPEAT },
+		{ "mirror",		GL_MIRRORED_REPEAT }
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} filteringModes[] =
+	{
+		{ "nearest",	GL_NEAREST },
+		{ "linear",		GL_LINEAR }
+	};
+
+#define FOR_EACH(ITERATOR, ARRAY, BODY)	\
+	for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++)	\
+		BODY
+
+	// RGBA8 cases.
+	{
+		static const struct
+		{
+			const char*		name;
+			int				width;
+			int				height;
+		} rgba8Sizes[] =
+		{
+			{ "pot",		64, 128 },
+			{ "npot",		63, 112 }
+		};
+
+		{
+			TestCaseGroup* const rgba8Group = new TestCaseGroup(m_context, "rgba8", "");
+			addChild(rgba8Group);
+
+			FOR_EACH(size,		rgba8Sizes,
+			FOR_EACH(wrapS,		wrapModes,
+			FOR_EACH(wrapT,		wrapModes,
+			FOR_EACH(filter,	filteringModes,
+				{
+					const string name = string("") + wrapModes[wrapS].name + "_" + wrapModes[wrapT].name + "_" + filteringModes[filter].name + "_" + rgba8Sizes[size].name;
+					rgba8Group->addChild(new TextureWrapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), name.c_str(), "",
+															 GL_RGBA, GL_UNSIGNED_BYTE,
+															 wrapModes[wrapS].mode,
+															 wrapModes[wrapT].mode,
+															 filteringModes[filter].mode, filteringModes[filter].mode,
+															 rgba8Sizes[size].width, rgba8Sizes[size].height));
+
+				}))));
+		}
+	}
+
+	// ETC1 cases.
+	{
+		TestCaseGroup* const etc1Group = new TestCaseGroup(m_context, "etc1", "");
+		addChild(etc1Group);
+
+		// Power-of-two ETC1 texture
+		std::vector<std::string> potFilenames;
+		potFilenames.push_back("data/etc1/photo_helsinki_mip_0.pkm");
+
+		FOR_EACH(wrapS,		wrapModes,
+		FOR_EACH(wrapT,		wrapModes,
+		FOR_EACH(filter,	filteringModes,
+			{
+				const string name = string("") + wrapModes[wrapS].name + "_" + wrapModes[wrapT].name + "_" + filteringModes[filter].name + "_pot";
+				etc1Group->addChild(new TextureWrapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), name.c_str(), "",
+														wrapModes[wrapS].mode,
+														wrapModes[wrapT].mode,
+														filteringModes[filter].mode, filteringModes[filter].mode,
+														potFilenames));
+
+			})));
+
+		std::vector<std::string> npotFilenames;
+		npotFilenames.push_back("data/etc1/photo_helsinki_113x89.pkm");
+
+		// NPOT ETC1 texture
+		FOR_EACH(wrapS,		wrapModes,
+		FOR_EACH(wrapT,		wrapModes,
+		FOR_EACH(filter,	filteringModes,
+			{
+				const string name = string("") + wrapModes[wrapS].name + "_" + wrapModes[wrapT].name + "_" + filteringModes[filter].name + "_npot";
+				etc1Group->addChild(new TextureWrapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), name.c_str(), "",
+														wrapModes[wrapS].mode,
+														wrapModes[wrapT].mode,
+														filteringModes[filter].mode, filteringModes[filter].mode,
+														npotFilenames));
+			})));
+	}
+
+	// ETC-2 (and EAC) cases.
+	{
+		static const struct
+		{
+			const char*					name;
+			CompressedTexture::Format	format;
+		} etc2Formats[] =
+		{
+			{ "eac_r11",							CompressedTexture::EAC_R11,							},
+			{ "eac_signed_r11",						CompressedTexture::EAC_SIGNED_R11,					},
+			{ "eac_rg11",							CompressedTexture::EAC_RG11,						},
+			{ "eac_signed_rg11",					CompressedTexture::EAC_SIGNED_RG11,					},
+			{ "etc2_rgb8",							CompressedTexture::ETC2_RGB8,						},
+			{ "etc2_srgb8",							CompressedTexture::ETC2_SRGB8,						},
+			{ "etc2_rgb8_punchthrough_alpha1",		CompressedTexture::ETC2_RGB8_PUNCHTHROUGH_ALPHA1,	},
+			{ "etc2_srgb8_punchthrough_alpha1",		CompressedTexture::ETC2_SRGB8_PUNCHTHROUGH_ALPHA1,	},
+			{ "etc2_eac_rgba8",						CompressedTexture::ETC2_EAC_RGBA8,					},
+			{ "etc2_eac_srgb8_alpha8",				CompressedTexture::ETC2_EAC_SRGB8_ALPHA8,			}
+		};
+
+		static const struct
+		{
+			const char*		name;
+			int				width;
+			int				height;
+		} etc2Sizes[] =
+		{
+			{ "pot",	64,		128	},
+			{ "npot",	123,	107	}
+		};
+
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(etc2Formats); formatNdx++)
+		{
+			TestCaseGroup* const formatGroup = new TestCaseGroup(m_context, etc2Formats[formatNdx].name, "");
+			addChild(formatGroup);
+
+			FOR_EACH(size,		etc2Sizes,
+			FOR_EACH(wrapS,		wrapModes,
+			FOR_EACH(wrapT,		wrapModes,
+			FOR_EACH(filter,	filteringModes,
+				{
+					const string name = string("") + wrapModes[wrapS].name + "_" + wrapModes[wrapT].name + "_" + filteringModes[filter].name + "_" + etc2Sizes[size].name;
+					formatGroup->addChild(new TextureWrapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), name.c_str(), "",
+															  etc2Formats[formatNdx].format,
+															  wrapModes[wrapS].mode,
+															  wrapModes[wrapT].mode,
+															  filteringModes[filter].mode, filteringModes[filter].mode,
+															  etc2Sizes[size].width, etc2Sizes[size].height));
+				}))));
+		}
+	}
+
+	// ASTC cases.
+	{
+		for (int formatI = 0; formatI < CompressedTexture::FORMAT_LAST; formatI++)
+		{
+			const CompressedTexture::Format format = (CompressedTexture::Format)formatI;
+			if (!tcu::isASTCFormat(format))
+				continue;
+
+			{
+				const tcu::IVec3		blockSize		= tcu::getASTCBlockSize(format);
+				const string			formatName		= "astc_" + de::toString(blockSize.x()) + "x" + de::toString(blockSize.y()) + (tcu::isASTCSRGBFormat(format) ? "_srgb" : "");
+				TestCaseGroup* const	formatGroup		= new TestCaseGroup(m_context, formatName.c_str(), "");
+				addChild(formatGroup);
+
+				DE_ASSERT(blockSize.z() == 1);
+
+				// \note This array is NOT static.
+				const struct
+				{
+					const char*		name;
+					int				width;
+					int				height;
+				} formatSizes[] =
+				{
+					{ "divisible",		blockSize.x()*10,		blockSize.y()*10	},
+					{ "not_divisible",	blockSize.x()*10+1,		blockSize.y()*10+1	},
+				};
+
+				FOR_EACH(size,		formatSizes,
+				FOR_EACH(wrapS,		wrapModes,
+				FOR_EACH(wrapT,		wrapModes,
+				FOR_EACH(filter,	filteringModes,
+					{
+						string name = string("") + wrapModes[wrapS].name + "_" + wrapModes[wrapT].name + "_" + filteringModes[filter].name + "_" + formatSizes[size].name;
+						formatGroup->addChild(new TextureWrapCase(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), name.c_str(), "",
+																  format,
+																  wrapModes[wrapS].mode,
+																  wrapModes[wrapT].mode,
+																  filteringModes[filter].mode, filteringModes[filter].mode,
+																  formatSizes[size].width, formatSizes[size].height));
+					}))));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTextureWrapTests.hpp b/modules/gles3/functional/es3fTextureWrapTests.hpp
new file mode 100644
index 0000000..11a5c7f
--- /dev/null
+++ b/modules/gles3/functional/es3fTextureWrapTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FTEXTUREWRAPTESTS_HPP
+#define _ES3FTEXTUREWRAPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture wrap mode tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class TextureWrapTests : public TestCaseGroup
+{
+public:
+							TextureWrapTests		(Context& context);
+							~TextureWrapTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureWrapTests		(const TextureWrapTests& other);
+	TextureWrapTests&		operator=				(const TextureWrapTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTEXTUREWRAPTESTS_HPP
diff --git a/modules/gles3/functional/es3fTransformFeedbackTests.cpp b/modules/gles3/functional/es3fTransformFeedbackTests.cpp
new file mode 100644
index 0000000..9967e4e
--- /dev/null
+++ b/modules/gles3/functional/es3fTransformFeedbackTests.cpp
@@ -0,0 +1,1792 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Transform feedback tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fTransformFeedbackTests.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVector.hpp"
+#include "tcuFormatUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluVarType.hpp"
+#include "gluVarTypeUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluObjectWrapper.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMemory.h"
+#include "deString.h"
+
+#include <set>
+#include <map>
+#include <algorithm>
+
+using std::string;
+using std::vector;
+using std::set;
+
+using std::map;
+using std::set;
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+namespace TransformFeedback
+{
+
+enum
+{
+	VIEWPORT_WIDTH			= 128,
+	VIEWPORT_HEIGHT			= 128,
+	BUFFER_GUARD_MULTIPLIER = 2		//!< stride*BUFFER_GUARD_MULTIPLIER bytes are added to the end of tf buffer and used to check for overruns.
+};
+
+enum Interpolation
+{
+	INTERPOLATION_SMOOTH = 0,
+	INTERPOLATION_FLAT,
+	INTERPOLATION_CENTROID,
+
+	INTERPOLATION_LAST
+};
+
+static const char* getInterpolationName (Interpolation interp)
+{
+	switch (interp)
+	{
+		case INTERPOLATION_SMOOTH:		return "smooth";
+		case INTERPOLATION_FLAT:		return "flat";
+		case INTERPOLATION_CENTROID:	return "centroid";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+struct Varying
+{
+						Varying				(const char* name_, const glu::VarType& type_, Interpolation interp_)
+							: name			(name_)
+							, type			(type_)
+							, interpolation	(interp_)
+						{
+						}
+
+	std::string			name;				//!< Variable name.
+	glu::VarType		type;				//!< Variable type.
+	Interpolation		interpolation;		//!< Interpolation mode (smooth, flat, centroid).
+};
+
+struct VaryingNameEquals
+{
+					VaryingNameEquals	(const std::string& name_) : name(name_) {}
+	bool			operator()			(const Varying& var) const { return var.name == name; }
+
+	std::string		name;
+};
+
+struct Attribute
+{
+	Attribute (const std::string& name_, const glu::VarType& type_, int offset_)
+		: name		(name_)
+		, type		(type_)
+		, offset	(offset_)
+	{
+	}
+
+	std::string			name;
+	glu::VarType		type;
+	int					offset;
+};
+
+struct AttributeNameEquals
+{
+					AttributeNameEquals	(const std::string& name_) : name(name_) {}
+	bool			operator()			(const Attribute& attr) const { return attr.name == name; }
+
+	std::string		name;
+};
+
+struct Output
+{
+	Output (void)
+		: bufferNdx	(0)
+		, offset	(0)
+	{
+	}
+
+	std::string					name;
+	glu::VarType				type;
+	int							bufferNdx;
+	int							offset;
+	vector<const Attribute*>	inputs;
+};
+
+struct DrawCall
+{
+				DrawCall (int numElements_, bool tfEnabled_)
+					: numElements				(numElements_)
+					, transformFeedbackEnabled	(tfEnabled_)
+				{
+				}
+
+				DrawCall (void)
+					: numElements				(0)
+					, transformFeedbackEnabled	(false)
+				{
+				}
+
+	int			numElements;
+	bool		transformFeedbackEnabled;
+};
+
+std::ostream& operator<< (std::ostream& str, const DrawCall& call)
+{
+	return str << "(" << call.numElements << ", " << (call.transformFeedbackEnabled ? "resumed" : "paused") << ")";
+}
+
+class ProgramSpec
+{
+public:
+									ProgramSpec						(void);
+									~ProgramSpec					(void);
+
+	glu::StructType*				createStruct					(const char* name);
+	void							addVarying						(const char* name, const glu::VarType& type, Interpolation interp);
+	void							addTransformFeedbackVarying		(const char* name);
+
+	const vector<glu::StructType*>&	getStructs						(void) const { return m_structs;	}
+	const vector<Varying>&			getVaryings						(void) const { return m_varyings;	}
+	const vector<string>&			getTransformFeedbackVaryings	(void) const { return m_transformFeedbackVaryings; }
+	bool							isPointSizeUsed					(void) const;
+
+private:
+									ProgramSpec						(const ProgramSpec& other);
+	ProgramSpec&					operator=						(const ProgramSpec& other);
+
+	vector<glu::StructType*>		m_structs;
+	vector<Varying>					m_varyings;
+	vector<string>					m_transformFeedbackVaryings;
+};
+
+// ProgramSpec
+
+ProgramSpec::ProgramSpec (void)
+{
+}
+
+ProgramSpec::~ProgramSpec (void)
+{
+	for (vector<glu::StructType*>::iterator i = m_structs.begin(); i != m_structs.end(); i++)
+		delete *i;
+}
+
+glu::StructType* ProgramSpec::createStruct (const char* name)
+{
+	m_structs.reserve(m_structs.size()+1);
+	m_structs.push_back(new glu::StructType(name));
+	return m_structs.back();
+}
+
+void ProgramSpec::addVarying (const char* name, const glu::VarType& type, Interpolation interp)
+{
+	m_varyings.push_back(Varying(name, type, interp));
+}
+
+void ProgramSpec::addTransformFeedbackVarying (const char* name)
+{
+	m_transformFeedbackVaryings.push_back(name);
+}
+
+bool ProgramSpec::isPointSizeUsed (void) const
+{
+	return std::find(m_transformFeedbackVaryings.begin(), m_transformFeedbackVaryings.end(), "gl_PointSize") != m_transformFeedbackVaryings.end();
+}
+
+static bool isProgramSupported (const glw::Functions& gl, const ProgramSpec& spec, deUint32 tfMode)
+{
+	int		maxVertexAttribs			= 0;
+	int		maxTfInterleavedComponents	= 0;
+	int		maxTfSeparateAttribs		= 0;
+	int		maxTfSeparateComponents		= 0;
+
+	gl.getIntegerv(GL_MAX_VERTEX_ATTRIBS,								&maxVertexAttribs);
+	gl.getIntegerv(GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,	&maxTfInterleavedComponents);
+	gl.getIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,			&maxTfSeparateAttribs);
+	gl.getIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS,		&maxTfSeparateComponents);
+
+	// Check vertex attribs.
+	int totalVertexAttribs	= 1 /* a_position */ + (spec.isPointSizeUsed() ? 1 : 0);
+	for (vector<Varying>::const_iterator var = spec.getVaryings().begin(); var != spec.getVaryings().end(); var++)
+	{
+		for (glu::VectorTypeIterator vecIter = glu::VectorTypeIterator::begin(&var->type); vecIter != glu::VectorTypeIterator::end(&var->type); vecIter++)
+			totalVertexAttribs += 1;
+	}
+
+	if (totalVertexAttribs > maxVertexAttribs)
+		return false; // Vertex attribute count exceeded.
+
+	// Check varyings.
+	int totalTfComponents	= 0;
+	int totalTfAttribs		= 0;
+	for (vector<string>::const_iterator iter = spec.getTransformFeedbackVaryings().begin(); iter != spec.getTransformFeedbackVaryings().end(); iter++)
+	{
+		const string&	name			= *iter;
+		int				numComponents	= 0;
+
+		if (name == "gl_Position")
+			numComponents = 4;
+		else if (name == "gl_PointSize")
+			numComponents = 1;
+		else
+		{
+			string						varName		= glu::parseVariableName(name.c_str());
+			const Varying&				varying		= *std::find_if(spec.getVaryings().begin(), spec.getVaryings().end(), VaryingNameEquals(varName));
+			glu::TypeComponentVector	varPath;
+
+			glu::parseTypePath(name.c_str(), varying.type, varPath);
+			numComponents = glu::getVarType(varying.type, varPath).getScalarSize();
+		}
+
+		if (tfMode == GL_SEPARATE_ATTRIBS && numComponents > maxTfSeparateComponents)
+			return false; // Per-attribute component count exceeded.
+
+		totalTfComponents	+= numComponents;
+		totalTfAttribs		+= 1;
+	}
+
+	if (tfMode == GL_SEPARATE_ATTRIBS && totalTfAttribs > maxTfSeparateAttribs)
+		return false;
+
+	if (tfMode == GL_INTERLEAVED_ATTRIBS && totalTfComponents > maxTfInterleavedComponents)
+		return false;
+
+	return true;
+}
+
+// Program
+
+static std::string getAttributeName (const char* varyingName, const glu::TypeComponentVector& path)
+{
+	std::ostringstream str;
+
+	str << "a_" << (deStringBeginsWith(varyingName, "v_") ? varyingName+2 : varyingName);
+
+	for (glu::TypeComponentVector::const_iterator iter = path.begin(); iter != path.end(); iter++)
+	{
+		const char* prefix = DE_NULL;
+
+		switch (iter->type)
+		{
+			case glu::VarTypeComponent::STRUCT_MEMBER:		prefix = "_m";	break;
+			case glu::VarTypeComponent::ARRAY_ELEMENT:		prefix = "_e";	break;
+			case glu::VarTypeComponent::MATRIX_COLUMN:		prefix = "_c";	break;
+			case glu::VarTypeComponent::VECTOR_COMPONENT:	prefix = "_s";	break;
+			default:
+				DE_ASSERT(false);
+		}
+
+		str << prefix << iter->index;
+	}
+
+	return str.str();
+}
+
+static void genShaderSources (const ProgramSpec& spec, std::string& vertSource, std::string& fragSource, bool pointSizeRequired)
+{
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	bool				addPointSize	= spec.isPointSizeUsed();
+
+	vtx << "#version 300 es\n"
+		<< "in highp vec4 a_position;\n";
+	frag << "#version 300 es\n"
+		 << "layout(location = 0) out mediump vec4 o_color;\n"
+		 << "uniform highp vec4 u_scale;\n"
+		 << "uniform highp vec4 u_bias;\n";
+
+	if (addPointSize)
+		vtx << "in highp float a_pointSize;\n";
+
+	// Declare attributes.
+	for (vector<Varying>::const_iterator var = spec.getVaryings().begin(); var != spec.getVaryings().end(); var++)
+	{
+		const char*			name	= var->name.c_str();
+		const glu::VarType&	type	= var->type;
+
+		for (glu::VectorTypeIterator vecIter = glu::VectorTypeIterator::begin(&type); vecIter != glu::VectorTypeIterator::end(&type); vecIter++)
+		{
+			glu::VarType	attribType	= glu::getVarType(type, vecIter.getPath());
+			string			attribName	= getAttributeName(name, vecIter.getPath());
+
+			vtx << "in " << glu::declare(attribType, attribName.c_str()) << ";\n";
+		}
+	}
+
+	// Declare vayrings.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		const char*			inout	= ndx ? "in" : "out";
+		std::ostringstream&	str		= ndx ? frag : vtx;
+
+		// Declare structs that have type name.
+		for (vector<glu::StructType*>::const_iterator structIter = spec.getStructs().begin(); structIter != spec.getStructs().end(); structIter++)
+		{
+			const glu::StructType* structPtr = *structIter;
+			if (structPtr->hasTypeName())
+				str << glu::declare(structPtr) << ";\n";
+		}
+
+		for (vector<Varying>::const_iterator var = spec.getVaryings().begin(); var != spec.getVaryings().end(); var++)
+			str << getInterpolationName(var->interpolation) << " " << inout << " " << glu::declare(var->type, var->name.c_str()) << ";\n";
+	}
+
+	vtx << "\nvoid main (void)\n{\n"
+		<< "\tgl_Position = a_position;\n";
+	frag << "\nvoid main (void)\n{\n"
+		 << "\thighp vec4 res = vec4(0.0);\n";
+
+	if (addPointSize)
+		vtx << "\tgl_PointSize = a_pointSize;\n";
+	else if (pointSizeRequired)
+		vtx << "\tgl_PointSize = 1.0;\n";
+
+	// Generate assignments / usage.
+	for (vector<Varying>::const_iterator var = spec.getVaryings().begin(); var != spec.getVaryings().end(); var++)
+	{
+		const char*			name	= var->name.c_str();
+		const glu::VarType&	type	= var->type;
+
+		for (glu::VectorTypeIterator vecIter = glu::VectorTypeIterator::begin(&type); vecIter != glu::VectorTypeIterator::end(&type); vecIter++)
+		{
+			glu::VarType	subType		= glu::getVarType(type, vecIter.getPath());
+			string			attribName	= getAttributeName(name, vecIter.getPath());
+
+			DE_ASSERT(subType.isBasicType() && glu::isDataTypeScalarOrVector(subType.getBasicType()));
+
+			// Vertex: assign from attribute.
+			vtx << "\t" << name << vecIter << " = " << attribName << ";\n";
+
+			// Fragment: add to res variable.
+			int scalarSize = glu::getDataTypeScalarSize(subType.getBasicType());
+
+			frag << "\tres += ";
+			if (scalarSize == 1)		frag << "vec4(" << name << vecIter << ")";
+			else if (scalarSize == 2)	frag << "vec2(" << name << vecIter << ").xxyy";
+			else if (scalarSize == 3)	frag << "vec3(" << name << vecIter << ").xyzx";
+			else if (scalarSize == 4)	frag << "vec4(" << name << vecIter << ")";
+
+			frag << ";\n";
+		}
+	}
+
+	frag << "\to_color = res * u_scale + u_bias;\n";
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	vertSource = vtx.str();
+	fragSource = frag.str();
+}
+
+static glu::ShaderProgram* createVertexCaptureProgram (const glu::RenderContext& context, const ProgramSpec& spec, deUint32 bufferMode, deUint32 primitiveType)
+{
+	std::string vertSource, fragSource;
+
+	genShaderSources(spec, vertSource, fragSource, primitiveType == GL_POINTS /* Is point size required? */);
+
+	return new glu::ShaderProgram(context, glu::ProgramSources()
+										   << glu::VertexSource(vertSource)
+										   << glu::FragmentSource(fragSource)
+										   << glu::TransformFeedbackVaryings<vector<string>::const_iterator>(spec.getTransformFeedbackVaryings().begin(), spec.getTransformFeedbackVaryings().end())
+										   << glu::TransformFeedbackMode(bufferMode));
+}
+
+// Helpers.
+
+static void computeInputLayout (vector<Attribute>& attributes, int& inputStride, const vector<Varying>& varyings, bool usePointSize)
+{
+	inputStride = 0;
+
+	// Add position.
+	attributes.push_back(Attribute("a_position", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), inputStride));
+	inputStride += 4*sizeof(deUint32);
+
+	if (usePointSize)
+	{
+		attributes.push_back(Attribute("a_pointSize", glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), inputStride));
+		inputStride += 1*sizeof(deUint32);
+	}
+
+	// Compute attribute vector.
+	for (vector<Varying>::const_iterator var = varyings.begin(); var != varyings.end(); var++)
+	{
+		for (glu::VectorTypeIterator vecIter = glu::VectorTypeIterator::begin(&var->type); vecIter != glu::VectorTypeIterator::end(&var->type); vecIter++)
+		{
+			glu::VarType	type	= vecIter.getType();
+			string			name	= getAttributeName(var->name.c_str(), vecIter.getPath());
+
+			attributes.push_back(Attribute(name, type, inputStride));
+			inputStride += glu::getDataTypeScalarSize(type.getBasicType())*sizeof(deUint32);
+		}
+	}
+}
+
+static void computeTransformFeedbackOutputs (vector<Output>& transformFeedbackOutputs, const vector<Attribute>& attributes, const vector<Varying>& varyings, const vector<string>& transformFeedbackVaryings, deUint32 bufferMode)
+{
+	int accumulatedSize = 0;
+
+	transformFeedbackOutputs.resize(transformFeedbackVaryings.size());
+	for (int varNdx = 0; varNdx < (int)transformFeedbackVaryings.size(); varNdx++)
+	{
+		const string&	name		= transformFeedbackVaryings[varNdx];
+		int				bufNdx		= (bufferMode == GL_SEPARATE_ATTRIBS ? varNdx : 0);
+		int				offset		= (bufferMode == GL_SEPARATE_ATTRIBS ? 0 : accumulatedSize);
+		Output&			output		= transformFeedbackOutputs[varNdx];
+
+		output.name			= name;
+		output.bufferNdx	= bufNdx;
+		output.offset		= offset;
+
+		if (name == "gl_Position")
+		{
+			const Attribute* posIn = &(*std::find_if(attributes.begin(), attributes.end(), AttributeNameEquals("a_position")));
+			output.type = posIn->type;
+			output.inputs.push_back(posIn);
+		}
+		else if (name == "gl_PointSize")
+		{
+			const Attribute* sizeIn = &(*std::find_if(attributes.begin(), attributes.end(), AttributeNameEquals("a_pointSize")));
+			output.type = sizeIn->type;
+			output.inputs.push_back(sizeIn);
+		}
+		else
+		{
+			string						varName		= glu::parseVariableName(name.c_str());
+			const Varying&				varying		= *std::find_if(varyings.begin(), varyings.end(), VaryingNameEquals(varName));
+			glu::TypeComponentVector	varPath;
+
+			glu::parseTypePath(name.c_str(), varying.type, varPath);
+
+			output.type = glu::getVarType(varying.type, varPath);
+
+			// Add all vectorized attributes as inputs.
+			for (glu::VectorTypeIterator iter = glu::VectorTypeIterator::begin(&output.type); iter != glu::VectorTypeIterator::end(&output.type); iter++)
+			{
+				// Full path.
+				glu::TypeComponentVector fullPath(varPath.size() + iter.getPath().size());
+
+				std::copy(varPath.begin(), varPath.end(), fullPath.begin());
+				std::copy(iter.getPath().begin(), iter.getPath().end(), fullPath.begin()+varPath.size());
+
+				string				attribName	= getAttributeName(varName.c_str(), fullPath);
+				const Attribute*	attrib		= &(*std::find_if(attributes.begin(), attributes.end(), AttributeNameEquals(attribName)));
+
+				output.inputs.push_back(attrib);
+			}
+		}
+
+		accumulatedSize += output.type.getScalarSize()*sizeof(deUint32);
+	}
+}
+
+static void genAttributeData (const Attribute& attrib, deUint8* basePtr, int stride, int numElements, de::Random& rnd)
+{
+	const int		elementSize	= (int)sizeof(deUint32);
+	bool			isFloat		= glu::isDataTypeFloatOrVec(attrib.type.getBasicType());
+	bool			isInt		= glu::isDataTypeIntOrIVec(attrib.type.getBasicType());
+	bool			isUint		= glu::isDataTypeIntOrIVec(attrib.type.getBasicType());
+	glu::Precision	precision	= attrib.type.getPrecision();
+	int				numComps	= glu::getDataTypeScalarSize(attrib.type.getBasicType());
+
+	for (int elemNdx = 0; elemNdx < numElements; elemNdx++)
+	{
+		for (int compNdx = 0; compNdx < numComps; compNdx++)
+		{
+			int offset = attrib.offset+elemNdx*stride+compNdx*elementSize;
+			if (isFloat)
+			{
+				float* comp = (float*)(basePtr+offset);
+				switch (precision)
+				{
+					case glu::PRECISION_LOWP:		*comp = 0.0f + 0.25f*(float)rnd.getInt(0, 4);	break;
+					case glu::PRECISION_MEDIUMP:	*comp = rnd.getFloat(-1e3f, 1e3f);				break;
+					case glu::PRECISION_HIGHP:		*comp = rnd.getFloat(-1e5f, 1e5f);				break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+			else if (isInt)
+			{
+				int* comp = (int*)(basePtr+offset);
+				switch (precision)
+				{
+					case glu::PRECISION_LOWP:		*comp = (int)(rnd.getUint32()&0xff) << 24 >> 24;	break;
+					case glu::PRECISION_MEDIUMP:	*comp = (int)(rnd.getUint32()&0xffff) << 16 >> 16;	break;
+					case glu::PRECISION_HIGHP:		*comp = (int)rnd.getUint32();						break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+			else if (isUint)
+			{
+				deUint32* comp = (deUint32*)(basePtr+offset);
+				switch (precision)
+				{
+					case glu::PRECISION_LOWP:		*comp = rnd.getUint32()&0xff;	break;
+					case glu::PRECISION_MEDIUMP:	*comp = rnd.getUint32()&0xffff;	break;
+					case glu::PRECISION_HIGHP:		*comp = rnd.getUint32();		break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+	}
+}
+
+static void genInputData (const vector<Attribute>& attributes, int numInputs, int inputStride, deUint8* inputBasePtr, de::Random& rnd)
+{
+	// Random positions.
+	const Attribute& position = *std::find_if(attributes.begin(), attributes.end(), AttributeNameEquals("a_position"));
+
+	for (int ndx = 0; ndx < numInputs; ndx++)
+	{
+		deUint8* ptr = inputBasePtr + position.offset + inputStride*ndx;
+		*((float*)(ptr+ 0)) = rnd.getFloat(-1.2f, 1.2f);
+		*((float*)(ptr+ 4)) = rnd.getFloat(-1.2f, 1.2f);
+		*((float*)(ptr+ 8)) = rnd.getFloat(-1.2f, 1.2f);
+		*((float*)(ptr+12)) = rnd.getFloat(0.1f, 2.0f);
+	}
+
+	// Point size.
+	vector<Attribute>::const_iterator pointSizePos = std::find_if(attributes.begin(), attributes.end(), AttributeNameEquals("a_pointSize"));
+	if (pointSizePos != attributes.end())
+	{
+		for (int ndx = 0; ndx < numInputs; ndx++)
+		{
+			deUint8* ptr = inputBasePtr + pointSizePos->offset + inputStride*ndx;
+			*((float*)ptr) = rnd.getFloat(1.0f, 8.0f);
+		}
+	}
+
+	// Random data for rest of components.
+	for (vector<Attribute>::const_iterator attrib = attributes.begin(); attrib != attributes.end(); attrib++)
+	{
+		if (attrib->name == "a_position" || attrib->name == "a_pointSize")
+			continue;
+
+		genAttributeData(*attrib, inputBasePtr, inputStride, numInputs, rnd);
+	}
+}
+
+static deUint32 getTransformFeedbackOutputCount (deUint32 primitiveType, int numElements)
+{
+	switch (primitiveType)
+	{
+		case GL_TRIANGLES:			return numElements - numElements%3;
+		case GL_TRIANGLE_STRIP:		return de::max(0, numElements-2)*3;
+		case GL_TRIANGLE_FAN:		return de::max(0, numElements-2)*3;
+		case GL_LINES:				return numElements - numElements%2;
+		case GL_LINE_STRIP:			return de::max(0, numElements-1)*2;
+		case GL_LINE_LOOP:			return numElements > 1 ? numElements*2 : 0;
+		case GL_POINTS:				return numElements;
+
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static deUint32 getTransformFeedbackPrimitiveCount (deUint32 primitiveType, int numElements)
+{
+	switch (primitiveType)
+	{
+		case GL_TRIANGLES:			return numElements/3;
+		case GL_TRIANGLE_STRIP:		return de::max(0, numElements-2);
+		case GL_TRIANGLE_FAN:		return de::max(0, numElements-2);
+		case GL_LINES:				return numElements/2;
+		case GL_LINE_STRIP:			return de::max(0, numElements-1);
+		case GL_LINE_LOOP:			return numElements > 1 ? numElements : 0;
+		case GL_POINTS:				return numElements;
+
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static deUint32 getTransformFeedbackPrimitiveMode (deUint32 primitiveType)
+{
+	switch (primitiveType)
+	{
+		case GL_TRIANGLES:
+		case GL_TRIANGLE_STRIP:
+		case GL_TRIANGLE_FAN:
+			return GL_TRIANGLES;
+
+		case GL_LINES:
+		case GL_LINE_LOOP:
+		case GL_LINE_STRIP:
+			return GL_LINES;
+
+		case GL_POINTS:
+			return GL_POINTS;
+
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static int getAttributeIndex (deUint32 primitiveType, int numInputs, int outNdx)
+{
+	switch (primitiveType)
+	{
+		case GL_TRIANGLES:			return outNdx;
+		case GL_LINES:				return outNdx;
+		case GL_POINTS:				return outNdx;
+
+		case GL_TRIANGLE_STRIP:
+		{
+			int triNdx = outNdx/3;
+			int vtxNdx = outNdx%3;
+			return (triNdx%2 != 0 && vtxNdx < 2) ? (triNdx+1-vtxNdx) : (triNdx+vtxNdx);
+		}
+
+		case GL_TRIANGLE_FAN:
+			return (outNdx%3 != 0) ? (outNdx/3 + outNdx%3) : 0;
+
+		case GL_LINE_STRIP:
+			return outNdx/2 + outNdx%2;
+
+		case GL_LINE_LOOP:
+		{
+			int inNdx = outNdx/2 + outNdx%2;
+			return inNdx < numInputs ? inNdx : 0;
+		}
+
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static bool compareTransformFeedbackOutput (tcu::TestLog& log, deUint32 primitiveType, const Output& output, int numInputs, const deUint8* inBasePtr, int inStride, const deUint8* outBasePtr, int outStride)
+{
+	bool		isOk		= true;
+	int			outOffset	= output.offset;
+
+	for (int attrNdx = 0; attrNdx < (int)output.inputs.size(); attrNdx++)
+	{
+		const Attribute&	attribute		= *output.inputs[attrNdx];
+		glu::DataType		type			= attribute.type.getBasicType();
+		int					numComponents	= glu::getDataTypeScalarSize(type);
+		glu::Precision		precision		= attribute.type.getPrecision();
+		glu::DataType		scalarType		= glu::getDataTypeScalarType(type);
+		int					numOutputs		= getTransformFeedbackOutputCount(primitiveType, numInputs);
+
+		for (int outNdx = 0; outNdx < numOutputs; outNdx++)
+		{
+			int inNdx = getAttributeIndex(primitiveType, numInputs, outNdx);
+
+			for (int compNdx = 0; compNdx < numComponents; compNdx++)
+			{
+				const deUint8*	inPtr	= inBasePtr + inStride*inNdx + attribute.offset + compNdx*sizeof(deUint32);
+				const deUint8*	outPtr	= outBasePtr + outStride*outNdx + outOffset + compNdx*sizeof(deUint32);
+				deUint32		inVal	= *(const deUint32*)inPtr;
+				deUint32		outVal	= *(const deUint32*)outPtr;
+				bool			isEqual	= false;
+
+				if (scalarType == glu::TYPE_FLOAT)
+				{
+					// ULP comparison is used for highp and mediump. Lowp uses threshold-comparison.
+					switch (precision)
+					{
+						case glu::PRECISION_HIGHP:		isEqual = de::abs((int)inVal - (int)outVal) < 2;				break;
+						case glu::PRECISION_MEDIUMP:	isEqual = de::abs((int)inVal - (int)outVal) < 2+(1<<13);		break;
+						case glu::PRECISION_LOWP:
+						{
+							float inF	= *(const float*)inPtr;
+							float outF	= *(const float*)outPtr;
+							isEqual = de::abs(inF - outF) < 0.1f;
+							break;
+						}
+						default:
+							DE_ASSERT(false);
+					}
+				}
+				else
+					isEqual = (inVal == outVal); // Bit-exact match required for integer types.
+
+				if (!isEqual)
+				{
+					log << TestLog::Message << "Mismatch in " << output.name << " (" << attribute.name << "), output = " << outNdx << ", input = " << inNdx << ", component = " << compNdx << TestLog::EndMessage;
+					isOk = false;
+					break;
+				}
+			}
+
+			if (!isOk)
+				break;
+		}
+
+		if (!isOk)
+			break;
+
+		outOffset += numComponents*sizeof(deUint32);
+	}
+
+	return isOk;
+}
+
+static int computeTransformFeedbackPrimitiveCount (deUint32 primitiveType, const DrawCall* first, const DrawCall* end)
+{
+	int primCount = 0;
+
+	for (const DrawCall* call = first; call != end; ++call)
+	{
+		if (call->transformFeedbackEnabled)
+			primCount += getTransformFeedbackPrimitiveCount(primitiveType, call->numElements);
+	}
+
+	return primCount;
+}
+
+static void writeBufferGuard (const glw::Functions& gl, deUint32 target, int bufferSize, int guardSize)
+{
+	deUint8* ptr = (deUint8*)gl.mapBufferRange(target, bufferSize, guardSize, GL_MAP_WRITE_BIT);
+	if (ptr)
+		deMemset(ptr, 0xcd, guardSize);
+	gl.unmapBuffer(target);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "guardband write");
+}
+
+static bool verifyGuard (const deUint8* ptr, int guardSize)
+{
+	for (int ndx = 0; ndx < guardSize; ndx++)
+	{
+		if (ptr[ndx] != 0xcd)
+			return false;
+	}
+	return true;
+}
+
+static void logTransformFeedbackVaryings (TestLog& log, const glw::Functions& gl, deUint32 program)
+{
+	int numTfVaryings	= 0;
+	int	maxNameLen		= 0;
+
+	gl.getProgramiv(program, GL_TRANSFORM_FEEDBACK_VARYINGS, &numTfVaryings);
+	gl.getProgramiv(program, GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, &maxNameLen);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Query TF varyings");
+
+	log << TestLog::Message << "GL_TRANSFORM_FEEDBACK_VARYINGS = " << numTfVaryings << TestLog::EndMessage;
+
+	vector<char> nameBuf(maxNameLen+1);
+
+	for (int ndx = 0; ndx < numTfVaryings; ndx++)
+	{
+		glw::GLsizei	size	= 0;
+		glw::GLenum		type	= 0;
+
+		gl.getTransformFeedbackVarying(program, ndx, (glw::GLsizei)nameBuf.size(), DE_NULL, &size, &type, &nameBuf[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetTransformFeedbackVarying()");
+
+		const glu::DataType	dataType	= glu::getDataTypeFromGLType(type);
+		const std::string	typeName	= dataType != glu::TYPE_LAST ? std::string(glu::getDataTypeName(dataType))
+																	 : (std::string("unknown(") + tcu::toHex(type).toString() + ")");
+
+		log << TestLog::Message << (const char*)&nameBuf[0] << ": " << typeName << "[" << size << "]" << TestLog::EndMessage;
+	}
+}
+
+class TransformFeedbackCase : public TestCase
+{
+public:
+								TransformFeedbackCase		(Context& context, const char* name, const char* desc, deUint32 bufferMode, deUint32 primitiveType);
+								~TransformFeedbackCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+protected:
+	ProgramSpec					m_progSpec;
+	deUint32					m_bufferMode;
+	deUint32					m_primitiveType;
+
+private:
+								TransformFeedbackCase		(const TransformFeedbackCase& other);
+	TransformFeedbackCase&		operator=					(const TransformFeedbackCase& other);
+
+	bool						runTest						(const DrawCall* first, const DrawCall* end, deUint32 seed);
+
+	// Derived from ProgramSpec in init()
+	int							m_inputStride;
+	vector<Attribute>			m_attributes;
+	vector<Output>				m_transformFeedbackOutputs;
+	vector<int>					m_bufferStrides;
+
+	// GL state.
+	glu::ShaderProgram*			m_program;
+	glu::TransformFeedback*		m_transformFeedback;
+	vector<deUint32>			m_outputBuffers;
+
+	int							m_iterNdx;
+};
+
+TransformFeedbackCase::TransformFeedbackCase (Context& context, const char* name, const char* desc, deUint32 bufferMode, deUint32 primitiveType)
+	: TestCase				(context, name, desc)
+	, m_bufferMode			(bufferMode)
+	, m_primitiveType		(primitiveType)
+	, m_inputStride			(0)
+	, m_program				(DE_NULL)
+	, m_transformFeedback	(DE_NULL)
+	, m_iterNdx				(0)
+{
+}
+
+TransformFeedbackCase::~TransformFeedbackCase (void)
+{
+	TransformFeedbackCase::deinit();
+}
+
+static bool hasArraysInTFVaryings (const ProgramSpec& spec)
+{
+	for (vector<string>::const_iterator tfVar = spec.getTransformFeedbackVaryings().begin(); tfVar != spec.getTransformFeedbackVaryings().end(); ++tfVar)
+	{
+		string							varName	= glu::parseVariableName(tfVar->c_str());
+		vector<Varying>::const_iterator	varIter	= std::find_if(spec.getVaryings().begin(), spec.getVaryings().end(), VaryingNameEquals(varName));
+
+		if (varName == "gl_Position" || varName == "gl_PointSize")
+			continue;
+
+		DE_ASSERT(varIter != spec.getVaryings().end());
+
+		if (varIter->type.isArrayType())
+			return true;
+	}
+
+	return false;
+}
+
+void TransformFeedbackCase::init (void)
+{
+	TestLog&				log	= m_testCtx.getLog();
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+
+	DE_ASSERT(!m_program);
+	m_program = createVertexCaptureProgram(m_context.getRenderContext(), m_progSpec, m_bufferMode, m_primitiveType);
+
+	log << *m_program;
+	if (!m_program->isOk())
+	{
+		const bool linkFail = m_program->getShaderInfo(glu::SHADERTYPE_VERTEX).compileOk &&
+							  m_program->getShaderInfo(glu::SHADERTYPE_FRAGMENT).compileOk &&
+							  !m_program->getProgramInfo().linkOk;
+
+		if (linkFail)
+		{
+			if (!isProgramSupported(gl, m_progSpec, m_bufferMode))
+				throw tcu::NotSupportedError("Implementation limits execeeded", "", __FILE__, __LINE__);
+			else if (hasArraysInTFVaryings(m_progSpec))
+				throw tcu::NotSupportedError("Capturing arrays is not supported (undefined in specification)", "", __FILE__, __LINE__);
+			else
+				throw tcu::TestError("Link failed", "", __FILE__, __LINE__);
+		}
+		else
+			throw tcu::TestError("Compile failed", "", __FILE__, __LINE__);
+	}
+
+	log << TestLog::Message << "Transform feedback varyings: " << tcu::formatArray(m_progSpec.getTransformFeedbackVaryings().begin(), m_progSpec.getTransformFeedbackVaryings().end()) << TestLog::EndMessage;
+
+	// Print out transform feedback points reported by GL.
+	log << TestLog::Message << "Transform feedback varyings reported by compiler:" << TestLog::EndMessage;
+	logTransformFeedbackVaryings(log, gl, m_program->getProgram());
+
+	// Compute input specification.
+	computeInputLayout(m_attributes, m_inputStride, m_progSpec.getVaryings(), m_progSpec.isPointSizeUsed());
+
+	// Build list of varyings used in transform feedback.
+	computeTransformFeedbackOutputs(m_transformFeedbackOutputs, m_attributes, m_progSpec.getVaryings(), m_progSpec.getTransformFeedbackVaryings(), m_bufferMode);
+	DE_ASSERT(!m_transformFeedbackOutputs.empty());
+
+	// Buffer strides.
+	DE_ASSERT(m_bufferStrides.empty());
+	if (m_bufferMode == GL_SEPARATE_ATTRIBS)
+	{
+		for (vector<Output>::const_iterator outIter = m_transformFeedbackOutputs.begin(); outIter != m_transformFeedbackOutputs.end(); outIter++)
+			m_bufferStrides.push_back(outIter->type.getScalarSize()*sizeof(deUint32));
+	}
+	else
+	{
+		int totalSize = 0;
+		for (vector<Output>::const_iterator outIter = m_transformFeedbackOutputs.begin(); outIter != m_transformFeedbackOutputs.end(); outIter++)
+			totalSize += outIter->type.getScalarSize()*sizeof(deUint32);
+
+		m_bufferStrides.push_back(totalSize);
+	}
+
+	// \note Actual storage is allocated in iterate().
+	m_outputBuffers.resize(m_bufferStrides.size());
+	gl.genBuffers((glw::GLsizei)m_outputBuffers.size(), &m_outputBuffers[0]);
+
+	DE_ASSERT(!m_transformFeedback);
+	m_transformFeedback = new glu::TransformFeedback(m_context.getRenderContext());
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "init");
+
+	m_iterNdx = 0;
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void TransformFeedbackCase::deinit (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (!m_outputBuffers.empty())
+	{
+		gl.deleteBuffers((glw::GLsizei)m_outputBuffers.size(), &m_outputBuffers[0]);
+		m_outputBuffers.clear();
+	}
+
+	delete m_transformFeedback;
+	m_transformFeedback = DE_NULL;
+
+	delete m_program;
+	m_program = DE_NULL;
+
+	// Clean up state.
+	m_attributes.clear();
+	m_transformFeedbackOutputs.clear();
+	m_bufferStrides.clear();
+	m_inputStride = 0;
+}
+
+TransformFeedbackCase::IterateResult TransformFeedbackCase::iterate (void)
+{
+	// Test cases.
+	static const DrawCall s_elemCount1[]	= { DrawCall(1, true) };
+	static const DrawCall s_elemCount2[]	= { DrawCall(2, true) };
+	static const DrawCall s_elemCount3[]	= { DrawCall(3, true) };
+	static const DrawCall s_elemCount4[]	= { DrawCall(4, true) };
+	static const DrawCall s_elemCount123[]	= { DrawCall(123, true) };
+	static const DrawCall s_basicPause1[]	= { DrawCall(64, true), DrawCall(64, false), DrawCall(64, true) };
+	static const DrawCall s_basicPause2[]	= { DrawCall(13, true), DrawCall(5, true), DrawCall(17, false), DrawCall(3, true), DrawCall(7, false) };
+	static const DrawCall s_startPaused[]	= { DrawCall(123, false), DrawCall(123, true) };
+	static const DrawCall s_random1[]		= { DrawCall(65, true), DrawCall(135, false), DrawCall(74, true), DrawCall(16, false), DrawCall(226, false), DrawCall(9, true), DrawCall(174, false) };
+	static const DrawCall s_random2[]		= { DrawCall(217, true), DrawCall(171, true), DrawCall(147, true), DrawCall(152, false), DrawCall(55, true) };
+
+	static const struct
+	{
+		const DrawCall*		calls;
+		int					numCalls;
+	} s_iterations[] =
+	{
+#define ITER(ARR) { ARR, DE_LENGTH_OF_ARRAY(ARR) }
+		ITER(s_elemCount1),
+		ITER(s_elemCount2),
+		ITER(s_elemCount3),
+		ITER(s_elemCount4),
+		ITER(s_elemCount123),
+		ITER(s_basicPause1),
+		ITER(s_basicPause2),
+		ITER(s_startPaused),
+		ITER(s_random1),
+		ITER(s_random2)
+#undef ITER
+	};
+
+	TestLog&				log				= m_testCtx.getLog();
+	bool					isOk			= true;
+	deUint32				seed			= deStringHash(getName()) ^ deInt32Hash(m_iterNdx);
+	int						numIterations	= DE_LENGTH_OF_ARRAY(s_iterations);
+	const DrawCall*			first			= s_iterations[m_iterNdx].calls;
+	const DrawCall*			end				= s_iterations[m_iterNdx].calls + s_iterations[m_iterNdx].numCalls;
+
+	std::string				sectionName		= std::string("Iteration") + de::toString(m_iterNdx+1);
+	std::string				sectionDesc		= std::string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(numIterations);
+	tcu::ScopedLogSection	section			(log, sectionName, sectionDesc);
+
+	log << TestLog::Message << "Testing " << s_iterations[m_iterNdx].numCalls << " draw calls, (element count, TF state): " << tcu::formatArray(first, end) << TestLog::EndMessage;
+
+	isOk = runTest(first, end, seed);
+
+	if (!isOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result comparison failed");
+
+	m_iterNdx += 1;
+	return (isOk && m_iterNdx < numIterations) ? CONTINUE : STOP;
+}
+
+bool TransformFeedbackCase::runTest (const DrawCall* first, const DrawCall* end, deUint32 seed)
+{
+	TestLog&				log				= m_testCtx.getLog();
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	de::Random				rnd				(seed);
+	int						numInputs		= 0;		//!< Sum of element counts in calls.
+	int						numOutputs		= 0;		//!< Sum of output counts for calls that have transform feedback enabled.
+	int						width			= m_context.getRenderContext().getRenderTarget().getWidth();
+	int						height			= m_context.getRenderContext().getRenderTarget().getHeight();
+	int						viewportW		= de::min((int)VIEWPORT_WIDTH, width);
+	int						viewportH		= de::min((int)VIEWPORT_HEIGHT, height);
+	int						viewportX		= rnd.getInt(0, width-viewportW);
+	int						viewportY		= rnd.getInt(0, height-viewportH);
+	tcu::Surface			frameWithTf		(viewportW, viewportH);
+	tcu::Surface			frameWithoutTf	(viewportW, viewportH);
+	glu::Query				primitiveQuery	(m_context.getRenderContext());
+	bool					outputsOk		= true;
+	bool					imagesOk		= true;
+	bool					queryOk			= true;
+
+	// Compute totals.
+	for (const DrawCall* call = first; call != end; call++)
+	{
+		numInputs	+= call->numElements;
+		numOutputs	+= call->transformFeedbackEnabled ? getTransformFeedbackOutputCount(m_primitiveType, call->numElements) : 0;
+	}
+
+	// Input data.
+	vector<deUint8> inputData(m_inputStride*numInputs);
+	genInputData(m_attributes, numInputs, m_inputStride, &inputData[0], rnd);
+
+	gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_transformFeedback->get());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTransformFeedback()");
+
+	// Allocate storage for transform feedback output buffers and bind to targets.
+	for (int bufNdx = 0; bufNdx < (int)m_outputBuffers.size(); bufNdx++)
+	{
+		deUint32		buffer		= m_outputBuffers[bufNdx];
+		int				stride		= m_bufferStrides[bufNdx];
+		int				target		= bufNdx;
+		int				size		= stride*numOutputs;
+		int				guardSize	= stride*BUFFER_GUARD_MULTIPLIER;
+		const deUint32	usage		= GL_DYNAMIC_READ;
+
+		gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buffer);
+		gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, size+guardSize, DE_NULL, usage);
+		writeBufferGuard(gl, GL_TRANSFORM_FEEDBACK_BUFFER, size, guardSize);
+
+		// \todo [2012-07-30 pyry] glBindBufferRange()?
+		gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, target, buffer);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "transform feedback buffer setup");
+	}
+
+	// Setup attributes.
+	for (vector<Attribute>::const_iterator attrib = m_attributes.begin(); attrib != m_attributes.end(); attrib++)
+	{
+		int				loc				= gl.getAttribLocation(m_program->getProgram(), attrib->name.c_str());
+		glu::DataType	scalarType		= glu::getDataTypeScalarType(attrib->type.getBasicType());
+		int				numComponents	= glu::getDataTypeScalarSize(attrib->type.getBasicType());
+		const void*		ptr				= &inputData[0] + attrib->offset;
+
+		if (loc >= 0)
+		{
+			gl.enableVertexAttribArray(loc);
+
+			if (scalarType == glu::TYPE_FLOAT)		gl.vertexAttribPointer	(loc, numComponents, GL_FLOAT, GL_FALSE, m_inputStride, ptr);
+			else if (scalarType == glu::TYPE_INT)	gl.vertexAttribIPointer	(loc, numComponents, GL_INT, m_inputStride, ptr);
+			else if (scalarType == glu::TYPE_UINT)	gl.vertexAttribIPointer	(loc, numComponents, GL_UNSIGNED_INT, m_inputStride, ptr);
+		}
+	}
+
+	// Setup viewport.
+	gl.viewport(viewportX, viewportY, viewportW, viewportH);
+
+	// Setup program.
+	gl.useProgram(m_program->getProgram());
+
+	gl.uniform4fv(gl.getUniformLocation(m_program->getProgram(), "u_scale"),	1, tcu::Vec4(0.01f).getPtr());
+	gl.uniform4fv(gl.getUniformLocation(m_program->getProgram(), "u_bias"),		1, tcu::Vec4(0.5f).getPtr());
+
+	// Enable query.
+	gl.beginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, *primitiveQuery);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN)");
+
+	// Draw.
+	{
+		int		offset		= 0;
+		bool	tfEnabled	= true;
+
+		gl.clear(GL_COLOR_BUFFER_BIT);
+
+		gl.beginTransformFeedback(getTransformFeedbackPrimitiveMode(m_primitiveType));
+
+		for (const DrawCall* call = first; call != end; call++)
+		{
+			// Pause or resume transform feedback if necessary.
+			if (call->transformFeedbackEnabled != tfEnabled)
+			{
+				if (call->transformFeedbackEnabled)
+					gl.resumeTransformFeedback();
+				else
+					gl.pauseTransformFeedback();
+				tfEnabled = call->transformFeedbackEnabled;
+			}
+
+			gl.drawArrays(m_primitiveType, offset, call->numElements);
+			offset += call->numElements;
+		}
+
+		// Resume feedback before finishing it.
+		if (!tfEnabled)
+			gl.resumeTransformFeedback();
+
+		gl.endTransformFeedback();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "render");
+	}
+
+	gl.endQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN)");
+
+	// Check and log query status right after submit
+	{
+		deUint32 available = GL_FALSE;
+		gl.getQueryObjectuiv(*primitiveQuery, GL_QUERY_RESULT_AVAILABLE, &available);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetQueryObjectuiv()");
+
+		log << TestLog::Message << "GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN status after submit: " << (available != GL_FALSE ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+	}
+
+	// Compare result buffers.
+	for (int bufferNdx = 0; bufferNdx < (int)m_outputBuffers.size(); bufferNdx++)
+	{
+		deUint32		buffer		= m_outputBuffers[bufferNdx];
+		int				stride		= m_bufferStrides[bufferNdx];
+		int				size		= stride*numOutputs;
+		int				guardSize	= stride*BUFFER_GUARD_MULTIPLIER;
+		const void*		bufPtr		= DE_NULL;
+
+		// Bind buffer for reading.
+		gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buffer);
+		bufPtr = gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, size+guardSize, GL_MAP_READ_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "mapping buffer");
+
+		// Verify all output variables that are written to this buffer.
+		for (vector<Output>::const_iterator out = m_transformFeedbackOutputs.begin(); out != m_transformFeedbackOutputs.end(); out++)
+		{
+			if (out->bufferNdx != bufferNdx)
+				continue;
+
+			int inputOffset		= 0;
+			int	outputOffset	= 0;
+
+			// Process all draw calls and check ones with transform feedback enabled.
+			for (const DrawCall* call = first; call != end; call++)
+			{
+				if (call->transformFeedbackEnabled)
+				{
+					const deUint8*	inputPtr	= &inputData[0] + inputOffset*m_inputStride;
+					const deUint8*	outputPtr	= (const deUint8*)bufPtr + outputOffset*stride;
+
+					if (!compareTransformFeedbackOutput(log, m_primitiveType, *out, call->numElements, inputPtr, m_inputStride, outputPtr, stride))
+					{
+						outputsOk = false;
+						break;
+					}
+				}
+
+				inputOffset		+= call->numElements;
+				outputOffset	+= call->transformFeedbackEnabled ? getTransformFeedbackOutputCount(m_primitiveType, call->numElements) : 0;
+			}
+		}
+
+		// Verify guardband.
+		if (!verifyGuard((const deUint8*)bufPtr + size, guardSize))
+		{
+			log << TestLog::Message << "Error: Transform feedback buffer overrun detected" << TestLog::EndMessage;
+			outputsOk = false;
+		}
+
+		gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
+	}
+
+	// Check status after mapping buffers.
+	{
+		const bool	mustBeReady		= !m_outputBuffers.empty(); // Mapping buffer forces synchronization.
+		const int	expectedCount	= computeTransformFeedbackPrimitiveCount(m_primitiveType, first, end);
+		deUint32	available		= GL_FALSE;
+		deUint32	numPrimitives	= 0;
+
+		gl.getQueryObjectuiv(*primitiveQuery, GL_QUERY_RESULT_AVAILABLE, &available);
+		gl.getQueryObjectuiv(*primitiveQuery, GL_QUERY_RESULT, &numPrimitives);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetQueryObjectuiv()");
+
+		if (!mustBeReady && available == GL_FALSE)
+		{
+			log << TestLog::Message << "ERROR: GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN result not available after mapping buffers!" << TestLog::EndMessage;
+			queryOk = false;
+		}
+
+		log << TestLog::Message << "GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN = " << numPrimitives << TestLog::EndMessage;
+
+		if ((int)numPrimitives != expectedCount)
+		{
+			log << TestLog::Message << "ERROR: Expected " << expectedCount << " primitives!" << TestLog::EndMessage;
+			queryOk = false;
+		}
+	}
+
+	// Clear transform feedback state.
+	gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+	for (int bufNdx = 0; bufNdx < (int)m_outputBuffers.size(); bufNdx++)
+	{
+		gl.bindBuffer		(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
+		gl.bindBufferBase	(GL_TRANSFORM_FEEDBACK_BUFFER, bufNdx, 0);
+	}
+
+	// Read back rendered image.
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, frameWithTf.getAccess());
+
+	// Render without transform feedback.
+	{
+		int offset = 0;
+
+		gl.clear(GL_COLOR_BUFFER_BIT);
+
+		for (const DrawCall* call = first; call != end; call++)
+		{
+			gl.drawArrays(m_primitiveType, offset, call->numElements);
+			offset += call->numElements;
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "render");
+		glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, frameWithoutTf.getAccess());
+	}
+
+	// Compare images with and without transform feedback.
+	imagesOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", frameWithoutTf, frameWithTf, tcu::RGBA(1, 1, 1, 1), tcu::COMPARE_LOG_ON_ERROR);
+
+	if (imagesOk)
+		m_testCtx.getLog() << TestLog::Message << "Rendering result comparison between TF enabled and TF disabled passed." << TestLog::EndMessage;
+	else
+		m_testCtx.getLog() << TestLog::Message << "ERROR: Rendering result comparison between TF enabled and TF disabled failed!" << TestLog::EndMessage;
+
+	return outputsOk && imagesOk && queryOk;
+}
+
+// Test cases.
+
+class PositionCase : public TransformFeedbackCase
+{
+public:
+	PositionCase (Context& context, const char* name, const char* desc, deUint32 bufferType, deUint32 primitiveType)
+		: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
+	{
+		m_progSpec.addTransformFeedbackVarying("gl_Position");
+	}
+};
+
+class PointSizeCase : public TransformFeedbackCase
+{
+public:
+	PointSizeCase (Context& context, const char* name, const char* desc, deUint32 bufferType, deUint32 primitiveType)
+		: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
+	{
+		m_progSpec.addTransformFeedbackVarying("gl_PointSize");
+	}
+};
+
+class BasicTypeCase : public TransformFeedbackCase
+{
+public:
+	BasicTypeCase (Context& context, const char* name, const char* desc, deUint32 bufferType, deUint32 primitiveType, glu::DataType type, glu::Precision precision, Interpolation interpolation)
+		: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
+	{
+		m_progSpec.addVarying("v_varA", glu::VarType(type, precision), interpolation);
+		m_progSpec.addVarying("v_varB", glu::VarType(type, precision), interpolation);
+
+		m_progSpec.addTransformFeedbackVarying("v_varA");
+		m_progSpec.addTransformFeedbackVarying("v_varB");
+	}
+};
+
+class BasicArrayCase : public TransformFeedbackCase
+{
+public:
+	BasicArrayCase (Context& context, const char* name, const char* desc, deUint32 bufferType, deUint32 primitiveType, glu::DataType type, glu::Precision precision, Interpolation interpolation)
+		: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
+	{
+		if (glu::isDataTypeMatrix(type) || m_bufferMode == GL_SEPARATE_ATTRIBS)
+		{
+			// \note For matrix types we need to use reduced array sizes or otherwise we will exceed maximum attribute (16)
+			//		 or transform feedback component count (64).
+			//		 On separate attribs mode maximum component count per varying is 4.
+			m_progSpec.addVarying("v_varA", glu::VarType(glu::VarType(type, precision), 1), interpolation);
+			m_progSpec.addVarying("v_varB", glu::VarType(glu::VarType(type, precision), 2), interpolation);
+		}
+		else
+		{
+			m_progSpec.addVarying("v_varA", glu::VarType(glu::VarType(type, precision), 3), interpolation);
+			m_progSpec.addVarying("v_varB", glu::VarType(glu::VarType(type, precision), 4), interpolation);
+		}
+
+		m_progSpec.addTransformFeedbackVarying("v_varA");
+		m_progSpec.addTransformFeedbackVarying("v_varB");
+	}
+};
+
+class ArrayElementCase : public TransformFeedbackCase
+{
+public:
+	ArrayElementCase (Context& context, const char* name, const char* desc, deUint32 bufferType, deUint32 primitiveType, glu::DataType type, glu::Precision precision, Interpolation interpolation)
+		: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
+	{
+		m_progSpec.addVarying("v_varA", glu::VarType(glu::VarType(type, precision), 3), interpolation);
+		m_progSpec.addVarying("v_varB", glu::VarType(glu::VarType(type, precision), 4), interpolation);
+
+		m_progSpec.addTransformFeedbackVarying("v_varA[1]");
+		m_progSpec.addTransformFeedbackVarying("v_varB[0]");
+		m_progSpec.addTransformFeedbackVarying("v_varB[3]");
+	}
+};
+
+class RandomCase : public TransformFeedbackCase
+{
+public:
+	RandomCase (Context& context, const char* name, const char* desc, deUint32 bufferType, deUint32 primitiveType, deUint32 seed)
+		: TransformFeedbackCase	(context, name, desc, bufferType, primitiveType)
+		, m_seed				(seed)
+	{
+	}
+
+	void init (void)
+	{
+		// \note Hard-coded indices and hackery are used when indexing this, beware.
+		static const glu::DataType typeCandidates[] =
+		{
+			glu::TYPE_FLOAT,
+			glu::TYPE_FLOAT_VEC2,
+			glu::TYPE_FLOAT_VEC3,
+			glu::TYPE_FLOAT_VEC4,
+			glu::TYPE_INT,
+			glu::TYPE_INT_VEC2,
+			glu::TYPE_INT_VEC3,
+			glu::TYPE_INT_VEC4,
+			glu::TYPE_UINT,
+			glu::TYPE_UINT_VEC2,
+			glu::TYPE_UINT_VEC3,
+			glu::TYPE_UINT_VEC4,
+
+			glu::TYPE_FLOAT_MAT2,
+			glu::TYPE_FLOAT_MAT2X3,
+			glu::TYPE_FLOAT_MAT2X4,
+
+			glu::TYPE_FLOAT_MAT3X2,
+			glu::TYPE_FLOAT_MAT3,
+			glu::TYPE_FLOAT_MAT3X4,
+
+			glu::TYPE_FLOAT_MAT4X2,
+			glu::TYPE_FLOAT_MAT4X3,
+			glu::TYPE_FLOAT_MAT4
+		};
+
+		static const glu::Precision precisions[] =
+		{
+			glu::PRECISION_LOWP,
+			glu::PRECISION_MEDIUMP,
+			glu::PRECISION_HIGHP
+		};
+
+		static const Interpolation interpModes[] =
+		{
+			INTERPOLATION_FLAT,
+			INTERPOLATION_SMOOTH,
+			INTERPOLATION_CENTROID
+		};
+
+		const int	maxAttributeVectors					= 16;
+//		const int	maxTransformFeedbackComponents		= 64; // \note It is enough to limit attribute set size.
+		bool		isSeparateMode						= m_bufferMode == GL_SEPARATE_ATTRIBS;
+		int			maxTransformFeedbackVars			= isSeparateMode ? 4 : maxAttributeVectors;
+		const float	arrayWeight							= 0.3f;
+		const float	positionWeight						= 0.7f;
+		const float	pointSizeWeight						= 0.1f;
+		const float	captureFullArrayWeight				= 0.5f;
+
+		de::Random	rnd									(m_seed);
+		bool		usePosition							= rnd.getFloat() < positionWeight;
+		bool		usePointSize						= rnd.getFloat() < pointSizeWeight;
+		int			numAttribVectorsToUse				= rnd.getInt(1, maxAttributeVectors - 1/*position*/ - (usePointSize ? 1 : 0));
+
+		int			numAttributeVectors					= 0;
+		int			varNdx								= 0;
+
+		// Generate varyings.
+		while (numAttributeVectors < numAttribVectorsToUse)
+		{
+			int						maxVecs		= isSeparateMode ? de::min(2 /*at most 2*mat2*/, numAttribVectorsToUse-numAttributeVectors) : numAttribVectorsToUse-numAttributeVectors;
+			const glu::DataType*	begin		= &typeCandidates[0];
+			const glu::DataType*	end			= begin + (maxVecs >= 4 ? 21 :
+														   maxVecs >= 3 ? 18 :
+														   maxVecs >= 2 ? (isSeparateMode ? 13 : 15) : 12);
+
+			glu::DataType			type		= rnd.choose<glu::DataType>(begin, end);
+			glu::Precision			precision	= rnd.choose<glu::Precision>(&precisions[0], &precisions[0]+DE_LENGTH_OF_ARRAY(precisions));
+			Interpolation			interp		= glu::getDataTypeScalarType(type) == glu::TYPE_FLOAT
+												? rnd.choose<Interpolation>(&interpModes[0], &interpModes[0]+DE_LENGTH_OF_ARRAY(interpModes))
+												: INTERPOLATION_FLAT;
+			int						numVecs		= glu::isDataTypeMatrix(type) ? glu::getDataTypeMatrixNumColumns(type) : 1;
+			int						numComps	= glu::getDataTypeScalarSize(type);
+			int						maxArrayLen	= de::max(1, isSeparateMode ? 4/numComps : maxVecs/numVecs);
+			bool					useArray	= rnd.getFloat() < arrayWeight;
+			int						arrayLen	= useArray ? rnd.getInt(1, maxArrayLen) : 1;
+			std::string				name		= "v_var" + de::toString(varNdx);
+
+			if (useArray)
+				m_progSpec.addVarying(name.c_str(), glu::VarType(glu::VarType(type, precision), arrayLen), interp);
+			else
+				m_progSpec.addVarying(name.c_str(), glu::VarType(type, precision), interp);
+
+			numAttributeVectors	+= arrayLen*numVecs;
+			varNdx				+= 1;
+		}
+
+		// Generate transform feedback candidate set.
+		vector<string> tfCandidates;
+
+		if (usePosition)	tfCandidates.push_back("gl_Position");
+		if (usePointSize)	tfCandidates.push_back("gl_PointSize");
+
+		for (int ndx = 0; ndx < varNdx /* num varyings */; ndx++)
+		{
+			const Varying& var = m_progSpec.getVaryings()[ndx];
+
+			if (var.type.isArrayType())
+			{
+				const bool captureFull = rnd.getFloat() < captureFullArrayWeight;
+
+				if (captureFull)
+					tfCandidates.push_back(var.name);
+				else
+				{
+					const int numElem = var.type.getArraySize();
+					for (int elemNdx = 0; elemNdx < numElem; elemNdx++)
+						tfCandidates.push_back(var.name + "[" + de::toString(elemNdx) + "]");
+				}
+			}
+			else
+				tfCandidates.push_back(var.name);
+		}
+
+		// Pick random selection.
+		vector<string> tfVaryings(de::min((int)tfCandidates.size(), maxTransformFeedbackVars));
+		rnd.choose(tfCandidates.begin(), tfCandidates.end(), tfVaryings.begin(), (int)tfVaryings.size());
+		rnd.shuffle(tfVaryings.begin(), tfVaryings.end());
+
+		for (vector<string>::const_iterator var = tfVaryings.begin(); var != tfVaryings.end(); var++)
+			m_progSpec.addTransformFeedbackVarying(var->c_str());
+
+		TransformFeedbackCase::init();
+	}
+
+private:
+	deUint32 m_seed;
+};
+
+} // TransformFeedback
+
+using namespace TransformFeedback;
+
+TransformFeedbackTests::TransformFeedbackTests (Context& context)
+	: TestCaseGroup(context, "transform_feedback", "Transform feedback tests")
+{
+}
+
+TransformFeedbackTests::~TransformFeedbackTests (void)
+{
+}
+
+void TransformFeedbackTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		deUint32		mode;
+	} bufferModes[] =
+	{
+		{ "separate",		GL_SEPARATE_ATTRIBS		},
+		{ "interleaved",	GL_INTERLEAVED_ATTRIBS	}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		type;
+	} primitiveTypes[] =
+	{
+		{ "points",			GL_POINTS			},
+		{ "lines",			GL_LINES			},
+		{ "triangles",		GL_TRIANGLES		}
+
+		// Not supported by GLES3.
+//		{ "line_strip",		GL_LINE_STRIP		},
+//		{ "line_loop",		GL_LINE_LOOP		},
+//		{ "triangle_fan",	GL_TRIANGLE_FAN		},
+//		{ "triangle_strip",	GL_TRIANGLE_STRIP	}
+	};
+
+	static const glu::DataType basicTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_FLOAT_VEC2,
+		glu::TYPE_FLOAT_VEC3,
+		glu::TYPE_FLOAT_VEC4,
+		glu::TYPE_FLOAT_MAT2,
+		glu::TYPE_FLOAT_MAT2X3,
+		glu::TYPE_FLOAT_MAT2X4,
+		glu::TYPE_FLOAT_MAT3X2,
+		glu::TYPE_FLOAT_MAT3,
+		glu::TYPE_FLOAT_MAT3X4,
+		glu::TYPE_FLOAT_MAT4X2,
+		glu::TYPE_FLOAT_MAT4X3,
+		glu::TYPE_FLOAT_MAT4,
+		glu::TYPE_INT,
+		glu::TYPE_INT_VEC2,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_INT_VEC4,
+		glu::TYPE_UINT,
+		glu::TYPE_UINT_VEC2,
+		glu::TYPE_UINT_VEC3,
+		glu::TYPE_UINT_VEC4
+	};
+
+	static const glu::Precision precisions[] =
+	{
+		glu::PRECISION_LOWP,
+		glu::PRECISION_MEDIUMP,
+		glu::PRECISION_HIGHP
+	};
+
+	static const struct
+	{
+		const char*		name;
+		Interpolation	interp;
+	} interpModes[] =
+	{
+		{ "smooth",		INTERPOLATION_SMOOTH	},
+		{ "flat",		INTERPOLATION_FLAT		},
+		{ "centroid",	INTERPOLATION_CENTROID	}
+	};
+
+	// .position
+	{
+		tcu::TestCaseGroup* positionGroup = new tcu::TestCaseGroup(m_testCtx, "position", "gl_Position capture using transform feedback");
+		addChild(positionGroup);
+
+		for (int primitiveType = 0; primitiveType < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveType++)
+		{
+			for (int bufferMode = 0; bufferMode < DE_LENGTH_OF_ARRAY(bufferModes); bufferMode++)
+			{
+				string name = string(primitiveTypes[primitiveType].name) + "_" + bufferModes[bufferMode].name;
+				positionGroup->addChild(new PositionCase(m_context, name.c_str(), "", bufferModes[bufferMode].mode, primitiveTypes[primitiveType].type));
+			}
+		}
+	}
+
+	// .point_size
+	{
+		tcu::TestCaseGroup* pointSizeGroup = new tcu::TestCaseGroup(m_testCtx, "point_size", "gl_PointSize capture using transform feedback");
+		addChild(pointSizeGroup);
+
+		for (int primitiveType = 0; primitiveType < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveType++)
+		{
+			for (int bufferMode = 0; bufferMode < DE_LENGTH_OF_ARRAY(bufferModes); bufferMode++)
+			{
+				string name = string(primitiveTypes[primitiveType].name) + "_" + bufferModes[bufferMode].name;
+				pointSizeGroup->addChild(new PointSizeCase(m_context, name.c_str(), "", bufferModes[bufferMode].mode, primitiveTypes[primitiveType].type));
+			}
+		}
+	}
+
+	// .basic_type
+	{
+		tcu::TestCaseGroup* basicTypeGroup = new tcu::TestCaseGroup(m_testCtx, "basic_types", "Basic types in transform feedback");
+		addChild(basicTypeGroup);
+
+		for (int bufferModeNdx = 0; bufferModeNdx < DE_LENGTH_OF_ARRAY(bufferModes); bufferModeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup	= new tcu::TestCaseGroup(m_testCtx, bufferModes[bufferModeNdx].name, "");
+			deUint32			bufferMode	= bufferModes[bufferModeNdx].mode;
+			basicTypeGroup->addChild(modeGroup);
+
+			for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveTypeNdx++)
+			{
+				tcu::TestCaseGroup* primitiveGroup	= new tcu::TestCaseGroup(m_testCtx, primitiveTypes[primitiveTypeNdx].name, "");
+				deUint32			primitiveType	= primitiveTypes[primitiveTypeNdx].type;
+				modeGroup->addChild(primitiveGroup);
+
+				for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(basicTypes); typeNdx++)
+				{
+					glu::DataType		type		= basicTypes[typeNdx];
+					bool				isFloat		= glu::getDataTypeScalarType(type) == glu::TYPE_FLOAT;
+
+					for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+					{
+						glu::Precision precision = precisions[precNdx];
+
+						string name = string(glu::getPrecisionName(precision)) + "_" + glu::getDataTypeName(type);
+						primitiveGroup->addChild(new BasicTypeCase(m_context, name.c_str(), "", bufferMode, primitiveType, type, precision, isFloat ? INTERPOLATION_SMOOTH : INTERPOLATION_FLAT));
+					}
+				}
+			}
+		}
+	}
+
+	// .array
+	{
+		tcu::TestCaseGroup* arrayGroup = new tcu::TestCaseGroup(m_testCtx, "array", "Capturing whole array in TF");
+		addChild(arrayGroup);
+
+		for (int bufferModeNdx = 0; bufferModeNdx < DE_LENGTH_OF_ARRAY(bufferModes); bufferModeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup	= new tcu::TestCaseGroup(m_testCtx, bufferModes[bufferModeNdx].name, "");
+			deUint32			bufferMode	= bufferModes[bufferModeNdx].mode;
+			arrayGroup->addChild(modeGroup);
+
+			for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveTypeNdx++)
+			{
+				tcu::TestCaseGroup* primitiveGroup	= new tcu::TestCaseGroup(m_testCtx, primitiveTypes[primitiveTypeNdx].name, "");
+				deUint32			primitiveType	= primitiveTypes[primitiveTypeNdx].type;
+				modeGroup->addChild(primitiveGroup);
+
+				for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(basicTypes); typeNdx++)
+				{
+					glu::DataType		type		= basicTypes[typeNdx];
+					bool				isFloat		= glu::getDataTypeScalarType(type) == glu::TYPE_FLOAT;
+
+					for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+					{
+						glu::Precision precision = precisions[precNdx];
+
+						string name = string(glu::getPrecisionName(precision)) + "_" + glu::getDataTypeName(type);
+						primitiveGroup->addChild(new BasicArrayCase(m_context, name.c_str(), "", bufferMode, primitiveType, type, precision, isFloat ? INTERPOLATION_SMOOTH : INTERPOLATION_FLAT));
+					}
+				}
+			}
+		}
+	}
+
+	// .array_element
+	{
+		tcu::TestCaseGroup* arrayElemGroup = new tcu::TestCaseGroup(m_testCtx, "array_element", "Capturing single array element in TF");
+		addChild(arrayElemGroup);
+
+		for (int bufferModeNdx = 0; bufferModeNdx < DE_LENGTH_OF_ARRAY(bufferModes); bufferModeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup	= new tcu::TestCaseGroup(m_testCtx, bufferModes[bufferModeNdx].name, "");
+			deUint32			bufferMode	= bufferModes[bufferModeNdx].mode;
+			arrayElemGroup->addChild(modeGroup);
+
+			for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveTypeNdx++)
+			{
+				tcu::TestCaseGroup* primitiveGroup	= new tcu::TestCaseGroup(m_testCtx, primitiveTypes[primitiveTypeNdx].name, "");
+				deUint32			primitiveType	= primitiveTypes[primitiveTypeNdx].type;
+				modeGroup->addChild(primitiveGroup);
+
+				for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(basicTypes); typeNdx++)
+				{
+					glu::DataType		type		= basicTypes[typeNdx];
+					bool				isFloat		= glu::getDataTypeScalarType(type) == glu::TYPE_FLOAT;
+
+					for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+					{
+						glu::Precision precision = precisions[precNdx];
+
+						string name = string(glu::getPrecisionName(precision)) + "_" + glu::getDataTypeName(type);
+						primitiveGroup->addChild(new ArrayElementCase(m_context, name.c_str(), "", bufferMode, primitiveType, type, precision, isFloat ? INTERPOLATION_SMOOTH : INTERPOLATION_FLAT));
+					}
+				}
+			}
+		}
+	}
+
+	// .interpolation
+	{
+		tcu::TestCaseGroup* interpolationGroup = new tcu::TestCaseGroup(m_testCtx, "interpolation", "Different interpolation modes in transform feedback varyings");
+		addChild(interpolationGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(interpModes); modeNdx++)
+		{
+			Interpolation		interp		= interpModes[modeNdx].interp;
+			tcu::TestCaseGroup*	modeGroup	= new tcu::TestCaseGroup(m_testCtx, interpModes[modeNdx].name, "");
+
+			interpolationGroup->addChild(modeGroup);
+
+			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+			{
+				glu::Precision precision = precisions[precNdx];
+
+				for (int primitiveType = 0; primitiveType < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveType++)
+				{
+					for (int bufferMode = 0; bufferMode < DE_LENGTH_OF_ARRAY(bufferModes); bufferMode++)
+					{
+						string name = string(glu::getPrecisionName(precision)) + "_vec4_" + primitiveTypes[primitiveType].name + "_" + bufferModes[bufferMode].name;
+						modeGroup->addChild(new BasicTypeCase(m_context, name.c_str(), "", bufferModes[bufferMode].mode, primitiveTypes[primitiveType].type, glu::TYPE_FLOAT_VEC4, precision, interp));
+					}
+				}
+			}
+		}
+	}
+
+	// .random
+	{
+		tcu::TestCaseGroup* randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Randomized transform feedback cases");
+		addChild(randomGroup);
+
+		for (int bufferModeNdx = 0; bufferModeNdx < DE_LENGTH_OF_ARRAY(bufferModes); bufferModeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup	= new tcu::TestCaseGroup(m_testCtx, bufferModes[bufferModeNdx].name, "");
+			deUint32			bufferMode	= bufferModes[bufferModeNdx].mode;
+			randomGroup->addChild(modeGroup);
+
+			for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveTypeNdx++)
+			{
+				tcu::TestCaseGroup* primitiveGroup	= new tcu::TestCaseGroup(m_testCtx, primitiveTypes[primitiveTypeNdx].name, "");
+				deUint32			primitiveType	= primitiveTypes[primitiveTypeNdx].type;
+				modeGroup->addChild(primitiveGroup);
+
+				for (int ndx = 0; ndx < 10; ndx++)
+				{
+					deUint32 seed = deInt32Hash(bufferMode) ^ deInt32Hash(primitiveType) ^ deInt32Hash(ndx);
+					primitiveGroup->addChild(new RandomCase(m_context, de::toString(ndx+1).c_str(), "", bufferMode, primitiveType, seed));
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fTransformFeedbackTests.hpp b/modules/gles3/functional/es3fTransformFeedbackTests.hpp
new file mode 100644
index 0000000..1cbf6cf
--- /dev/null
+++ b/modules/gles3/functional/es3fTransformFeedbackTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FTRANSFORMFEEDBACKTESTS_HPP
+#define _ES3FTRANSFORMFEEDBACKTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Transform feedback tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class TransformFeedbackTests : public TestCaseGroup
+{
+public:
+								TransformFeedbackTests		(Context& context);
+								~TransformFeedbackTests		(void);
+
+	void						init						(void);
+
+private:
+								TransformFeedbackTests		(const TransformFeedbackTests& other);
+	TransformFeedbackTests&		operator=					(const TransformFeedbackTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FTRANSFORMFEEDBACKTESTS_HPP
diff --git a/modules/gles3/functional/es3fUniformApiTests.cpp b/modules/gles3/functional/es3fUniformApiTests.cpp
new file mode 100644
index 0000000..30245f4
--- /dev/null
+++ b/modules/gles3/functional/es3fUniformApiTests.cpp
@@ -0,0 +1,2966 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Uniform API tests.
+ *
+ * \todo [2013-02-26 nuutti] Much duplication between this and ES2.
+ *							 Utilities to glshared?
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fUniformApiTests.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluVarType.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluTexture.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuCommandLine.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+#include "deSharedPtr.hpp"
+#include "deMemory.h"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <set>
+#include <cstring>
+
+using namespace glw;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using tcu::ScopedLogSection;
+using glu::ShaderProgram;
+using glu::StructType;
+using de::Random;
+using de::SharedPtr;
+
+typedef bool (* dataTypePredicate)(glu::DataType);
+
+static const int MAX_RENDER_WIDTH			= 32;
+static const int MAX_RENDER_HEIGHT			= 32;
+static const int MAX_NUM_SAMPLER_UNIFORMS	= 16;
+
+static const glu::DataType s_testDataTypes[] =
+{
+	glu::TYPE_FLOAT,
+	glu::TYPE_FLOAT_VEC2,
+	glu::TYPE_FLOAT_VEC3,
+	glu::TYPE_FLOAT_VEC4,
+	glu::TYPE_FLOAT_MAT2,
+	glu::TYPE_FLOAT_MAT2X3,
+	glu::TYPE_FLOAT_MAT2X4,
+	glu::TYPE_FLOAT_MAT3X2,
+	glu::TYPE_FLOAT_MAT3,
+	glu::TYPE_FLOAT_MAT3X4,
+	glu::TYPE_FLOAT_MAT4X2,
+	glu::TYPE_FLOAT_MAT4X3,
+	glu::TYPE_FLOAT_MAT4,
+
+	glu::TYPE_INT,
+	glu::TYPE_INT_VEC2,
+	glu::TYPE_INT_VEC3,
+	glu::TYPE_INT_VEC4,
+
+	glu::TYPE_UINT,
+	glu::TYPE_UINT_VEC2,
+	glu::TYPE_UINT_VEC3,
+	glu::TYPE_UINT_VEC4,
+
+	glu::TYPE_BOOL,
+	glu::TYPE_BOOL_VEC2,
+	glu::TYPE_BOOL_VEC3,
+	glu::TYPE_BOOL_VEC4,
+
+	glu::TYPE_SAMPLER_2D,
+	glu::TYPE_SAMPLER_CUBE
+	// \note We don't test all sampler types here.
+};
+
+static inline int getGLInt (const glw::Functions& funcs, const deUint32 name)
+{
+	int val = -1;
+	funcs.getIntegerv(name, &val);
+	return val;
+}
+
+static inline tcu::Vec4 vec4FromPtr (const float* const ptr)
+{
+	tcu::Vec4 result;
+	for (int i = 0; i < 4; i++)
+		result[i] = ptr[i];
+	return result;
+}
+
+static inline string beforeLast (const string& str, const char c)
+{
+	return str.substr(0, str.find_last_of(c));
+}
+
+static inline void fillWithColor (const tcu::PixelBufferAccess& access, const tcu::Vec4& color)
+{
+	for (int z = 0; z < access.getDepth(); z++)
+	for (int y = 0; y < access.getHeight(); y++)
+	for (int x = 0; x < access.getWidth(); x++)
+		access.setPixel(color, x, y, z);
+}
+
+static inline int getSamplerNumLookupDimensions (const glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_SAMPLER_2D:
+		case glu::TYPE_INT_SAMPLER_2D:
+		case glu::TYPE_UINT_SAMPLER_2D:
+			return 2;
+
+		case glu::TYPE_SAMPLER_3D:
+		case glu::TYPE_INT_SAMPLER_3D:
+		case glu::TYPE_UINT_SAMPLER_3D:
+		case glu::TYPE_SAMPLER_2D_SHADOW:
+		case glu::TYPE_SAMPLER_2D_ARRAY:
+		case glu::TYPE_INT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_SAMPLER_CUBE:
+		case glu::TYPE_INT_SAMPLER_CUBE:
+		case glu::TYPE_UINT_SAMPLER_CUBE:
+			return 3;
+
+		case glu::TYPE_SAMPLER_CUBE_SHADOW:
+		case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
+			return 4;
+
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static inline glu::DataType getSamplerLookupReturnType (const glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_SAMPLER_2D:
+		case glu::TYPE_SAMPLER_CUBE:
+		case glu::TYPE_SAMPLER_2D_ARRAY:
+		case glu::TYPE_SAMPLER_3D:
+			return glu::TYPE_FLOAT_VEC4;
+
+		case glu::TYPE_UINT_SAMPLER_2D:
+		case glu::TYPE_UINT_SAMPLER_CUBE:
+		case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_UINT_SAMPLER_3D:
+			return glu::TYPE_UINT_VEC4;
+
+		case glu::TYPE_INT_SAMPLER_2D:
+		case glu::TYPE_INT_SAMPLER_CUBE:
+		case glu::TYPE_INT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_INT_SAMPLER_3D:
+			return glu::TYPE_INT_VEC4;
+
+		case glu::TYPE_SAMPLER_2D_SHADOW:
+		case glu::TYPE_SAMPLER_CUBE_SHADOW:
+		case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
+			return glu::TYPE_FLOAT;
+
+		default:
+			DE_ASSERT(false);
+			return glu::TYPE_LAST;
+	}
+}
+
+template<glu::DataType T>
+static bool dataTypeEquals (const glu::DataType t)
+{
+	return t == T;
+}
+
+template<int N>
+static bool dataTypeIsMatrixWithNRows (const glu::DataType t)
+{
+	return glu::isDataTypeMatrix(t) && glu::getDataTypeMatrixNumRows(t) == N;
+}
+
+static bool typeContainsMatchingBasicType (const glu::VarType& type, const dataTypePredicate predicate)
+{
+	if (type.isBasicType())
+		return predicate(type.getBasicType());
+	else if (type.isArrayType())
+		return typeContainsMatchingBasicType(type.getElementType(), predicate);
+	else
+	{
+		DE_ASSERT(type.isStructType());
+		const StructType& structType = *type.getStructPtr();
+		for (int i = 0; i < structType.getNumMembers(); i++)
+			if (typeContainsMatchingBasicType(structType.getMember(i).getType(), predicate))
+				return true;
+		return false;
+	}
+}
+
+static void getDistinctSamplerTypes (vector<glu::DataType>& dst, const glu::VarType& type)
+{
+	if (type.isBasicType())
+	{
+		const glu::DataType basicType = type.getBasicType();
+		if (glu::isDataTypeSampler(basicType) && std::find(dst.begin(), dst.end(), basicType) == dst.end())
+			dst.push_back(basicType);
+	}
+	else if (type.isArrayType())
+		getDistinctSamplerTypes(dst, type.getElementType());
+	else
+	{
+		DE_ASSERT(type.isStructType());
+		const StructType& structType = *type.getStructPtr();
+		for (int i = 0; i < structType.getNumMembers(); i++)
+			getDistinctSamplerTypes(dst, structType.getMember(i).getType());
+	}
+}
+
+static int getNumSamplersInType (const glu::VarType& type)
+{
+	if (type.isBasicType())
+		return glu::isDataTypeSampler(type.getBasicType()) ? 1 : 0;
+	else if (type.isArrayType())
+		return getNumSamplersInType(type.getElementType()) * type.getArraySize();
+	else
+	{
+		DE_ASSERT(type.isStructType());
+		const StructType& structType = *type.getStructPtr();
+		int sum = 0;
+		for (int i = 0; i < structType.getNumMembers(); i++)
+			sum += getNumSamplersInType(structType.getMember(i).getType());
+		return sum;
+	}
+}
+
+static glu::VarType generateRandomType (const int maxDepth, int& curStructIdx, vector<const StructType*>& structTypesDst, Random& rnd)
+{
+	const bool isStruct		= maxDepth > 0 && rnd.getFloat() < 0.2f;
+	const bool isArray		= rnd.getFloat() < 0.3f;
+
+	if (isStruct)
+	{
+		const int			numMembers = rnd.getInt(1, 5);
+		StructType* const	structType = new StructType(("structType" + de::toString(curStructIdx++)).c_str());
+
+		for (int i = 0; i < numMembers; i++)
+			structType->addMember(("m" + de::toString(i)).c_str(), generateRandomType(maxDepth-1, curStructIdx, structTypesDst, rnd));
+
+		structTypesDst.push_back(structType);
+		return isArray ? glu::VarType(glu::VarType(structType), rnd.getInt(1, 5)) : glu::VarType(structType);
+	}
+	else
+	{
+		const glu::DataType		basicType = (glu::DataType)s_testDataTypes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testDataTypes)-1)];
+		const glu::Precision	precision = glu::isDataTypeBoolOrBVec(basicType) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		return isArray ? glu::VarType(glu::VarType(basicType, precision), rnd.getInt(1, 5)) : glu::VarType(basicType, precision);
+	}
+}
+
+namespace
+{
+
+struct VarValue
+{
+	glu::DataType type;
+
+	union
+	{
+		float		floatV[4*4]; // At most mat4. \note Matrices here are column-major.
+		deInt32		intV[4];
+		deUint32	uintV[4];
+		bool		boolV[4];
+		struct
+		{
+			int		unit;
+			union
+			{
+				float		floatV[4];
+				deInt32		intV[4];
+				deUint32	uintV[4];
+			} fillColor;
+		} samplerV;
+	} val;
+};
+
+enum CaseShaderType
+{
+	CASESHADERTYPE_VERTEX = 0,
+	CASESHADERTYPE_FRAGMENT,
+	CASESHADERTYPE_BOTH,
+
+	CASESHADERTYPE_LAST
+};
+
+struct Uniform
+{
+	string			name;
+	glu::VarType	type;
+
+	Uniform (const char* const name_, const glu::VarType& type_) : name(name_), type(type_) {}
+};
+
+// A set of uniforms, along with related struct types.
+class UniformCollection
+{
+public:
+	int					getNumUniforms		(void) const					{ return (int)m_uniforms.size();	}
+	int					getNumStructTypes	(void) const					{ return (int)m_structTypes.size();	}
+	Uniform&			getUniform			(const int ndx)					{ return m_uniforms[ndx];			}
+	const Uniform&		getUniform			(const int ndx) const			{ return m_uniforms[ndx];			}
+	const StructType*	getStructType		(const int ndx) const			{ return m_structTypes[ndx];		}
+	void				addUniform			(const Uniform& uniform)		{ m_uniforms.push_back(uniform);	}
+	void				addStructType		(const StructType* const type)	{ m_structTypes.push_back(type);	}
+
+	UniformCollection	(void) {}
+	~UniformCollection	(void)
+	{
+		for (int i = 0; i < (int)m_structTypes.size(); i++)
+			delete m_structTypes[i];
+	}
+
+	// Add the contents of m_uniforms and m_structTypes to receiver, and remove them from this one.
+	// \note receiver takes ownership of the struct types.
+	void moveContents (UniformCollection& receiver)
+	{
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			receiver.addUniform(m_uniforms[i]);
+		m_uniforms.clear();
+
+		for (int i = 0; i < (int)m_structTypes.size(); i++)
+			receiver.addStructType(m_structTypes[i]);
+		m_structTypes.clear();
+	}
+
+	bool containsMatchingBasicType (const dataTypePredicate predicate) const
+	{
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			if (typeContainsMatchingBasicType(m_uniforms[i].type, predicate))
+				return true;
+		return false;
+	}
+
+	vector<glu::DataType> getSamplerTypes (void) const
+	{
+		vector<glu::DataType> samplerTypes;
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			getDistinctSamplerTypes(samplerTypes, m_uniforms[i].type);
+		return samplerTypes;
+	}
+
+	bool containsSeveralSamplerTypes (void) const
+	{
+		return getSamplerTypes().size() > 1;
+	}
+
+	int getNumSamplers (void) const
+	{
+		int sum = 0;
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			sum += getNumSamplersInType(m_uniforms[i].type);
+		return sum;
+	}
+
+	static UniformCollection* basic (const glu::DataType type, const char* const nameSuffix = "")
+	{
+		UniformCollection* const	res		= new UniformCollection;
+		const glu::Precision		prec	= glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		res->m_uniforms.push_back(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(type, prec)));
+		return res;
+	}
+
+	static UniformCollection* basicArray (const glu::DataType type, const char* const nameSuffix = "")
+	{
+		UniformCollection* const	res		= new UniformCollection;
+		const glu::Precision		prec	= glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		res->m_uniforms.push_back(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(glu::VarType(type, prec), 3)));
+		return res;
+	}
+
+	static UniformCollection* basicStruct (const glu::DataType type0, const glu::DataType type1, const bool containsArrays, const char* const nameSuffix = "")
+	{
+		UniformCollection* const	res		= new UniformCollection;
+		const glu::Precision		prec0	= glu::isDataTypeBoolOrBVec(type0) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		const glu::Precision		prec1	= glu::isDataTypeBoolOrBVec(type1) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+
+		StructType* const structType = new StructType((string("structType") + nameSuffix).c_str());
+		structType->addMember("m0", glu::VarType(type0, prec0));
+		structType->addMember("m1", glu::VarType(type1, prec1));
+		if (containsArrays)
+		{
+			structType->addMember("m2", glu::VarType(glu::VarType(type0, prec0), 3));
+			structType->addMember("m3", glu::VarType(glu::VarType(type1, prec1), 3));
+		}
+
+		res->addStructType(structType);
+		res->addUniform(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(structType)));
+
+		return res;
+	}
+
+	static UniformCollection* structInArray (const glu::DataType type0, const glu::DataType type1, const bool containsArrays, const char* const nameSuffix = "")
+	{
+		UniformCollection* const res = basicStruct(type0, type1, containsArrays, nameSuffix);
+		res->getUniform(0).type = glu::VarType(res->getUniform(0).type, 3);
+		return res;
+	}
+
+	static UniformCollection* nestedArraysStructs (const glu::DataType type0, const glu::DataType type1, const char* const nameSuffix = "")
+	{
+		UniformCollection* const res		= new UniformCollection;
+		const glu::Precision prec0			= glu::isDataTypeBoolOrBVec(type0) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		const glu::Precision prec1			= glu::isDataTypeBoolOrBVec(type1) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		StructType* const structType		= new StructType((string("structType") + nameSuffix).c_str());
+		StructType* const subStructType		= new StructType((string("subStructType") + nameSuffix).c_str());
+		StructType* const subSubStructType	= new StructType((string("subSubStructType") + nameSuffix).c_str());
+
+		subSubStructType->addMember("mss0", glu::VarType(type0, prec0));
+		subSubStructType->addMember("mss1", glu::VarType(type1, prec1));
+
+		subStructType->addMember("ms0", glu::VarType(type1, prec1));
+		subStructType->addMember("ms1", glu::VarType(glu::VarType(type0, prec0), 2));
+		subStructType->addMember("ms2", glu::VarType(glu::VarType(subSubStructType), 2));
+
+		structType->addMember("m0", glu::VarType(type0, prec0));
+		structType->addMember("m1", glu::VarType(subStructType));
+		structType->addMember("m2", glu::VarType(type1, prec1));
+
+		res->addStructType(subSubStructType);
+		res->addStructType(subStructType);
+		res->addStructType(structType);
+
+		res->addUniform(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(structType)));
+
+		return res;
+	}
+
+	static UniformCollection* multipleBasic (const char* const nameSuffix = "")
+	{
+		static const glu::DataType	types[]	= { glu::TYPE_FLOAT, glu::TYPE_INT_VEC3, glu::TYPE_UINT_VEC4, glu::TYPE_FLOAT_MAT3, glu::TYPE_BOOL_VEC2 };
+		UniformCollection* const	res		= new UniformCollection;
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(types); i++)
+		{
+			UniformCollection* const sub = basic(types[i], ("_" + de::toString(i) + nameSuffix).c_str());
+			sub->moveContents(*res);
+			delete sub;
+		}
+
+		return res;
+	}
+
+	static UniformCollection* multipleBasicArray (const char* const nameSuffix = "")
+	{
+		static const glu::DataType	types[]	= { glu::TYPE_FLOAT, glu::TYPE_INT_VEC3, glu::TYPE_BOOL_VEC2 };
+		UniformCollection* const	res		= new UniformCollection;
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(types); i++)
+		{
+			UniformCollection* const sub = basicArray(types[i], ("_" + de::toString(i) + nameSuffix).c_str());
+			sub->moveContents(*res);
+			delete sub;
+		}
+
+		return res;
+	}
+
+	static UniformCollection* multipleNestedArraysStructs (const char* const nameSuffix = "")
+	{
+		static const glu::DataType	types0[]	= { glu::TYPE_FLOAT,		glu::TYPE_INT,		glu::TYPE_BOOL_VEC4 };
+		static const glu::DataType	types1[]	= { glu::TYPE_FLOAT_VEC4,	glu::TYPE_INT_VEC4,	glu::TYPE_BOOL };
+		UniformCollection* const	res			= new UniformCollection;
+
+		DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types0) == DE_LENGTH_OF_ARRAY(types1));
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(types0); i++)
+		{
+			UniformCollection* const sub = nestedArraysStructs(types0[i], types1[i], ("_" + de::toString(i) + nameSuffix).c_str());
+			sub->moveContents(*res);
+			delete sub;
+		}
+
+		return res;
+	}
+
+	static UniformCollection* random (const deUint32 seed)
+	{
+		Random						rnd			(seed);
+		const int					numUniforms	= rnd.getInt(1, 5);
+		int							structIdx	= 0;
+		UniformCollection* const	res			= new UniformCollection;
+
+		for (int i = 0; i < numUniforms; i++)
+		{
+			vector<const StructType*>	structTypes;
+			Uniform						uniform(("u_var" + de::toString(i)).c_str(), glu::VarType());
+
+			// \note Discard uniforms that would cause number of samplers to exceed MAX_NUM_SAMPLER_UNIFORMS.
+			do
+			{
+				for (int j = 0; j < (int)structTypes.size(); j++)
+					delete structTypes[j];
+				structTypes.clear();
+				uniform.type = (("u_var" + de::toString(i)).c_str(), generateRandomType(3, structIdx, structTypes, rnd));
+			} while (res->getNumSamplers() + getNumSamplersInType(uniform.type) > MAX_NUM_SAMPLER_UNIFORMS);
+
+			res->addUniform(uniform);
+			for (int j = 0; j < (int)structTypes.size(); j++)
+				res->addStructType(structTypes[j]);
+		}
+
+		return res;
+	}
+
+private:
+	// \note Copying these would be cumbersome, since deep-copying both m_uniforms and m_structTypes
+	// would mean that we'd need to update pointers from uniforms to point to the new structTypes.
+	// When the same UniformCollection is needed in several places, a SharedPtr is used instead.
+								UniformCollection	(const UniformCollection&); // Not allowed.
+	UniformCollection&			operator=			(const UniformCollection&); // Not allowed.
+
+	vector<Uniform>				m_uniforms;
+	vector<const StructType*>	m_structTypes;
+};
+
+}; // anonymous
+
+static VarValue getSamplerFillValue (const VarValue& sampler)
+{
+	DE_ASSERT(glu::isDataTypeSampler(sampler.type));
+
+	VarValue result;
+	result.type = getSamplerLookupReturnType(sampler.type);
+
+	switch (result.type)
+	{
+		case glu::TYPE_FLOAT_VEC4:
+			for (int i = 0; i < 4; i++)
+				result.val.floatV[i] = sampler.val.samplerV.fillColor.floatV[i];
+			break;
+		case glu::TYPE_UINT_VEC4:
+			for (int i = 0; i < 4; i++)
+				result.val.uintV[i] = sampler.val.samplerV.fillColor.uintV[i];
+			break;
+		case glu::TYPE_INT_VEC4:
+			for (int i = 0; i < 4; i++)
+				result.val.intV[i] = sampler.val.samplerV.fillColor.intV[i];
+			break;
+		case glu::TYPE_FLOAT:
+			result.val.floatV[0] = sampler.val.samplerV.fillColor.floatV[0];
+			break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	return result;
+}
+
+static VarValue getSamplerUnitValue (const VarValue& sampler)
+{
+	DE_ASSERT(glu::isDataTypeSampler(sampler.type));
+
+	VarValue result;
+	result.type = glu::TYPE_INT;
+	result.val.intV[0] = sampler.val.samplerV.unit;
+
+	return result;
+}
+
+static glu::DataType getDataTypeTransposedMatrix (const glu::DataType original)
+{
+	return glu::getDataTypeMatrix(glu::getDataTypeMatrixNumRows(original), glu::getDataTypeMatrixNumColumns(original));
+}
+
+static VarValue getTransposeMatrix (const VarValue& original)
+{
+	DE_ASSERT(glu::isDataTypeMatrix(original.type));
+
+	const int	rows = glu::getDataTypeMatrixNumRows(original.type);
+	const int	cols = glu::getDataTypeMatrixNumColumns(original.type);
+	VarValue	result;
+	result.type = getDataTypeTransposedMatrix(original.type);
+
+	for (int i = 0; i < rows; i++)
+	for (int j = 0; j < cols; j++)
+		result.val.floatV[i*cols + j] = original.val.floatV[j*rows + i];
+
+	return result;
+}
+
+static string shaderVarValueStr (const VarValue& value)
+{
+	const int			numElems = glu::getDataTypeScalarSize(value.type);
+	std::ostringstream	result;
+
+	if (numElems > 1)
+		result << glu::getDataTypeName(value.type) << "(";
+
+	for (int i = 0; i < numElems; i++)
+	{
+		if (i > 0)
+			result << ", ";
+
+		if (glu::isDataTypeFloatOrVec(value.type) || glu::isDataTypeMatrix(value.type))
+			result << de::floatToString(value.val.floatV[i], 2);
+		else if (glu::isDataTypeIntOrIVec((value.type)))
+			result << de::toString(value.val.intV[i]);
+		else if (glu::isDataTypeUintOrUVec((value.type)))
+			result << de::toString(value.val.uintV[i]) << "u";
+		else if (glu::isDataTypeBoolOrBVec((value.type)))
+			result << (value.val.boolV[i] ? "true" : "false");
+		else if (glu::isDataTypeSampler((value.type)))
+			result << shaderVarValueStr(getSamplerFillValue(value));
+		else
+			DE_ASSERT(false);
+	}
+
+	if (numElems > 1)
+		result << ")";
+
+	return result.str();
+}
+
+static string apiVarValueStr (const VarValue& value)
+{
+	const int			numElems = glu::getDataTypeScalarSize(value.type);
+	std::ostringstream	result;
+
+	if (numElems > 1)
+		result << "(";
+
+	for (int i = 0; i < numElems; i++)
+	{
+		if (i > 0)
+			result << ", ";
+
+		if (glu::isDataTypeFloatOrVec(value.type) || glu::isDataTypeMatrix(value.type))
+			result << de::floatToString(value.val.floatV[i], 2);
+		else if (glu::isDataTypeIntOrIVec((value.type)))
+			result << de::toString(value.val.intV[i]);
+		else if (glu::isDataTypeUintOrUVec((value.type)))
+			result << de::toString(value.val.uintV[i]);
+		else if (glu::isDataTypeBoolOrBVec((value.type)))
+			result << (value.val.boolV[i] ? "true" : "false");
+		else if (glu::isDataTypeSampler((value.type)))
+			result << value.val.samplerV.unit;
+		else
+			DE_ASSERT(false);
+	}
+
+	if (numElems > 1)
+		result << ")";
+
+	return result.str();
+}
+
+static VarValue generateRandomVarValue (const glu::DataType type, Random& rnd, int samplerUnit = -1 /* Used if type is a sampler type. \note Samplers' unit numbers are not randomized. */)
+{
+	const int	numElems = glu::getDataTypeScalarSize(type);
+	VarValue	result;
+	result.type = type;
+
+	DE_ASSERT((samplerUnit >= 0) == (glu::isDataTypeSampler(type)));
+
+	if (glu::isDataTypeFloatOrVec(type) || glu::isDataTypeMatrix(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.floatV[i] = rnd.getFloat(-10.0f, 10.0f);
+	}
+	else if (glu::isDataTypeIntOrIVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.intV[i] = rnd.getInt(-10, 10);
+	}
+	else if (glu::isDataTypeUintOrUVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.uintV[i] = (deUint32)rnd.getInt(0, 10);
+	}
+	else if (glu::isDataTypeBoolOrBVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.boolV[i] = rnd.getBool();
+	}
+	else if (glu::isDataTypeSampler(type))
+	{
+		const glu::DataType		texResultType		= getSamplerLookupReturnType(type);
+		const glu::DataType		texResultScalarType	= glu::getDataTypeScalarType(texResultType);
+		const int				texResultNumDims	= glu::getDataTypeScalarSize(texResultType);
+
+		result.val.samplerV.unit = samplerUnit;
+
+		for (int i = 0; i < texResultNumDims; i++)
+		{
+			switch (texResultScalarType)
+			{
+				case glu::TYPE_FLOAT:	result.val.samplerV.fillColor.floatV[i]		= rnd.getFloat(0.0f, 1.0f);		break;
+				case glu::TYPE_INT:		result.val.samplerV.fillColor.intV[i]		= rnd.getInt(-10, 10);			break;
+				case glu::TYPE_UINT:	result.val.samplerV.fillColor.uintV[i]		= (deUint32)rnd.getInt(0, 10);	break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+	return result;
+}
+
+static VarValue generateZeroVarValue (const glu::DataType type)
+{
+	const int	numElems = glu::getDataTypeScalarSize(type);
+	VarValue	result;
+	result.type = type;
+
+	if (glu::isDataTypeFloatOrVec(type) || glu::isDataTypeMatrix(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.floatV[i] = 0.0f;
+	}
+	else if (glu::isDataTypeIntOrIVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.intV[i] = 0;
+	}
+	else if (glu::isDataTypeUintOrUVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.uintV[i] = 0u;
+	}
+	else if (glu::isDataTypeBoolOrBVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.boolV[i] = false;
+	}
+	else if (glu::isDataTypeSampler(type))
+	{
+		const glu::DataType		texResultType		= getSamplerLookupReturnType(type);
+		const glu::DataType		texResultScalarType	= glu::getDataTypeScalarType(texResultType);
+		const int				texResultNumDims	= glu::getDataTypeScalarSize(texResultType);
+
+		result.val.samplerV.unit = 0;
+
+		for (int i = 0; i < texResultNumDims; i++)
+		{
+			switch (texResultScalarType)
+			{
+				case glu::TYPE_FLOAT:	result.val.samplerV.fillColor.floatV[i]		= 0.12f * (float)i;	break;
+				case glu::TYPE_INT:		result.val.samplerV.fillColor.intV[i]		= -2 + i;			break;
+				case glu::TYPE_UINT:	result.val.samplerV.fillColor.uintV[i]		= 4 + i;			break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+	return result;
+}
+
+static bool apiVarValueEquals (const VarValue& a, const VarValue& b)
+{
+	const int		size			= glu::getDataTypeScalarSize(a.type);
+	const float		floatThreshold	= 0.05f;
+
+	DE_ASSERT(a.type == b.type);
+
+	if (glu::isDataTypeFloatOrVec(a.type) || glu::isDataTypeMatrix(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (de::abs(a.val.floatV[i] - b.val.floatV[i]) >= floatThreshold)
+				return false;
+	}
+	else if (glu::isDataTypeIntOrIVec(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (a.val.intV[i] != b.val.intV[i])
+				return false;
+	}
+	else if (glu::isDataTypeUintOrUVec(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (a.val.uintV[i] != b.val.uintV[i])
+				return false;
+	}
+	else if (glu::isDataTypeBoolOrBVec(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (a.val.boolV[i] != b.val.boolV[i])
+				return false;
+	}
+	else if (glu::isDataTypeSampler(a.type))
+	{
+		if (a.val.samplerV.unit != b.val.samplerV.unit)
+			return false;
+	}
+	else
+		DE_ASSERT(false);
+
+	return true;
+}
+
+static VarValue getRandomBoolRepresentation (const VarValue& boolValue, const glu::DataType targetScalarType, Random& rnd)
+{
+	DE_ASSERT(glu::isDataTypeBoolOrBVec(boolValue.type));
+
+	const int				size		= glu::getDataTypeScalarSize(boolValue.type);
+	const glu::DataType		targetType	= size == 1 ? targetScalarType : glu::getDataTypeVector(targetScalarType, size);
+	VarValue				result;
+	result.type = targetType;
+
+	switch (targetScalarType)
+	{
+		case glu::TYPE_INT:
+			for (int i = 0; i < size; i++)
+			{
+				if (boolValue.val.boolV[i])
+				{
+					result.val.intV[i] = rnd.getInt(-10, 10);
+					if (result.val.intV[i] == 0)
+						result.val.intV[i] = 1;
+				}
+				else
+					result.val.intV[i] = 0;
+			}
+			break;
+
+		case glu::TYPE_UINT:
+			for (int i = 0; i < size; i++)
+			{
+				if (boolValue.val.boolV[i])
+					result.val.uintV[i] = rnd.getInt(1, 10);
+				else
+					result.val.uintV[i] = 0;
+			}
+			break;
+
+		case glu::TYPE_FLOAT:
+			for (int i = 0; i < size; i++)
+			{
+				if (boolValue.val.boolV[i])
+				{
+					result.val.floatV[i] = rnd.getFloat(-10.0f, 10.0f);
+					if (result.val.floatV[i] == 0.0f)
+						result.val.floatV[i] = 1.0f;
+				}
+				else
+					result.val.floatV[i] = 0;
+			}
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	return result;
+}
+
+static const char* getCaseShaderTypeName (const CaseShaderType type)
+{
+	switch (type)
+	{
+		case CASESHADERTYPE_VERTEX:		return "vertex";
+		case CASESHADERTYPE_FRAGMENT:	return "fragment";
+		case CASESHADERTYPE_BOTH:		return "both";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+static CaseShaderType randomCaseShaderType (const deUint32 seed)
+{
+	return (CaseShaderType)Random(seed).getInt(0, CASESHADERTYPE_LAST-1);
+}
+
+class UniformCase : public TestCase, protected glu::CallLogWrapper
+{
+public:
+	enum Feature
+	{
+		// ARRAYUSAGE_ONLY_MIDDLE_INDEX: only middle index of each array is used in shader. If not given, use all indices.
+		FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX	= 1<<0,
+
+		// UNIFORMFUNC_VALUE: use pass-by-value versions of uniform assignment funcs, e.g. glUniform1f(), where possible. If not given, use pass-by-pointer versions.
+		FEATURE_UNIFORMFUNC_VALUE				= 1<<1,
+
+		// MATRIXMODE_ROWMAJOR: pass matrices to GL in row major form. If not given, use column major.
+		FEATURE_MATRIXMODE_ROWMAJOR				= 1<<2,
+
+		// ARRAYASSIGN: how basic-type arrays are assigned with glUniform*(). If none given, assign each element of an array separately.
+		FEATURE_ARRAYASSIGN_FULL				= 1<<3, //!< Assign all elements of an array with one glUniform*().
+		FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO		= 1<<4, //!< Assign two elements per one glUniform*().
+
+		// UNIFORMUSAGE_EVERY_OTHER: use about half of the uniforms. If not given, use all uniforms (except that some array indices may be omitted according to ARRAYUSAGE).
+		FEATURE_UNIFORMUSAGE_EVERY_OTHER		= 1<<5,
+
+		// BOOLEANAPITYPE: type used to pass booleans to and from GL api. If none given, use float.
+		FEATURE_BOOLEANAPITYPE_INT				= 1<<6,
+		FEATURE_BOOLEANAPITYPE_UINT				= 1<<7,
+
+		// UNIFORMVALUE_ZERO: use zero-valued uniforms. If not given, use random uniform values.
+		FEATURE_UNIFORMVALUE_ZERO				= 1<<8,
+
+		// ARRAY_FIRST_ELEM_NAME_NO_INDEX: in certain API functions, when referring to the first element of an array, use just the array name without [0] at the end.
+		FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX	= 1<<9
+	};
+
+								UniformCase		(Context& context, const char* name, const char* description, CaseShaderType caseType, const SharedPtr<const UniformCollection>& uniformCollection);
+								UniformCase		(Context& context, const char* name, const char* description, CaseShaderType caseType, const SharedPtr<const UniformCollection>& uniformCollection, deUint32 features);
+								UniformCase		(Context& context, const char* name, const char* description, deUint32 seed); // \note Randomizes caseType, uniformCollection and features.
+	virtual						~UniformCase	(void);
+
+	virtual void				init			(void);
+	virtual void				deinit			(void);
+
+	IterateResult				iterate			(void);
+
+protected:
+	// A basic uniform is a uniform (possibly struct or array member) whose type is a basic type (e.g. float, ivec4, sampler2d).
+	struct BasicUniform
+	{
+		string			name;
+		glu::DataType	type;
+		bool			isUsedInShader;
+		VarValue		finalValue;	//!< The value we ultimately want to set for this uniform.
+
+		string			rootName;	//!< If this is a member of a basic-typed array, rootName is the name of that array with "[0]" appended. Otherwise it equals name.
+		int				elemNdx;	//!< If this is a member of a basic-typed array, elemNdx is the index in that array. Otherwise -1.
+		int				rootSize;	//!< If this is a member of a basic-typed array, rootSize is the size of that array. Otherwise 1.
+
+		BasicUniform (const char* const		name_,
+					  const glu::DataType	type_,
+					  const bool			isUsedInShader_,
+					  const VarValue&		finalValue_,
+					  const char* const		rootName_	= DE_NULL,
+					  const int				elemNdx_	= -1,
+					  const int				rootSize_	= 1)
+					  : name			(name_)
+					  , type			(type_)
+					  , isUsedInShader	(isUsedInShader_)
+					  , finalValue		(finalValue_)
+					  , rootName		(rootName_ == DE_NULL ? name_ : rootName_)
+					  , elemNdx			(elemNdx_)
+					  , rootSize		(rootSize_)
+					 {
+					 }
+
+		static vector<BasicUniform>::const_iterator findWithName (const vector<BasicUniform>& vec, const char* const name)
+		{
+			for (vector<BasicUniform>::const_iterator it = vec.begin(); it != vec.end(); it++)
+			{
+				if (it->name == name)
+					return it;
+			}
+			return vec.end();
+		}
+	};
+
+	// Reference values for info that is expected to be reported by glGetActiveUniform() or glGetActiveUniformsiv().
+	struct BasicUniformReportRef
+	{
+		string			name;
+		// \note minSize and maxSize are for arrays and can be distinct since implementations are allowed, but not required, to trim the inactive end indices of arrays.
+		int				minSize;
+		int				maxSize;
+		glu::DataType	type;
+		bool			isUsedInShader;
+
+		BasicUniformReportRef (const char* const name_, const int minS, const int maxS, const glu::DataType type_, const bool used)
+			: name(name_), minSize(minS), maxSize(maxS), type(type_), isUsedInShader(used) { DE_ASSERT(minSize <= maxSize); }
+		BasicUniformReportRef (const char* const name_, const glu::DataType type_, const bool used)
+			: name(name_), minSize(1), maxSize(1), type(type_), isUsedInShader(used) {}
+	};
+
+	// Info that is actually reported by glGetActiveUniform() or glGetActiveUniformsiv().
+	struct BasicUniformReportGL
+	{
+		string			name;
+		int				nameLength; // \note Whether this includes the null byte depends on whether it was queried with glGetActiveUniform() or glGetActiveUniformsiv().
+		int				size;
+		glu::DataType	type;
+
+		int				index;
+
+		BasicUniformReportGL (const char* const name_, const int nameLength_, const int size_, const glu::DataType type_, const int index_)
+			: name(name_), nameLength(nameLength_), size(size_), type(type_), index(index_) {}
+
+		static vector<BasicUniformReportGL>::const_iterator findWithName (const vector<BasicUniformReportGL>& vec, const char* const name)
+		{
+			for (vector<BasicUniformReportGL>::const_iterator it = vec.begin(); it != vec.end(); it++)
+			{
+				if (it->name == name)
+					return it;
+			}
+			return vec.end();
+		}
+	};
+
+	// Query info with glGetActiveUniform() and check validity.
+	bool						getActiveUniforms						(vector<BasicUniformReportGL>& dst, const vector<BasicUniformReportRef>& ref, deUint32 programGL);
+	// Query info with glGetUniformIndices() + glGetActiveUniformsiv() and check validity.
+	bool						getActiveUniformsiv						(vector<BasicUniformReportGL>& dst, const vector<BasicUniformReportRef>& ref, deUint32 programGL);
+	// Compare infos returned by glGetActiveUniform() and glGetUniformIndices() + glGetActiveUniformsiv().
+	bool						uniformVsUniformsivComparison			(const vector<BasicUniformReportGL>& uniformsResult, const vector<BasicUniformReportGL>& uniformsivResult);
+	// Get uniform values with glGetUniform*() and put to valuesDst. Uniforms that get -1 from glGetUniformLocation() get glu::TYPE_INVALID.
+	bool						getUniforms								(vector<VarValue>& valuesDst, const vector<BasicUniform>& basicUniforms, deUint32 programGL);
+	// Check that every uniform has the default (zero) value.
+	bool						checkUniformDefaultValues				(const vector<VarValue>& values, const vector<BasicUniform>& basicUniforms);
+	// Assign the basicUniforms[].finalValue values for uniforms. \note rnd parameter is for booleans (true can be any nonzero value).
+	void						assignUniforms							(const vector<BasicUniform>& basicUniforms, deUint32 programGL, Random& rnd);
+	// Compare the uniform values given in values (obtained with glGetUniform*()) with the basicUniform.finalValue values.
+	bool						compareUniformValues					(const vector<VarValue>& values, const vector<BasicUniform>& basicUniforms);
+	// Render and check that all pixels are white (i.e. all uniform comparisons passed).
+	bool						renderTest								(const vector<BasicUniform>& basicUniforms, const ShaderProgram& program, Random& rnd);
+
+	virtual bool				test									(const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd) = 0;
+
+	const deUint32								m_features;
+	const SharedPtr<const UniformCollection>	m_uniformCollection;
+
+private:
+	static deUint32				randomFeatures							(deUint32 seed);
+
+	// Generates the basic uniforms, based on the uniform with name varName and type varType, in the same manner as are expected
+	// to be returned by glGetActiveUniform(), e.g. generates a name like var[0] for arrays, and recursively generates struct member names.
+	void						generateBasicUniforms					(vector<BasicUniform>&				basicUniformsDst,
+																		 vector<BasicUniformReportRef>&		basicUniformReportsDst,
+																		 const glu::VarType&				varType,
+																		 const char*						varName,
+																		 bool								isParentActive,
+																		 int&								samplerUnitCounter,
+																		 Random&							rnd) const;
+
+	void						writeUniformDefinitions					(std::ostringstream& dst) const;
+	void						writeUniformCompareExpr					(std::ostringstream& dst, const BasicUniform& uniform) const;
+	void						writeUniformComparisons					(std::ostringstream& dst, const vector<BasicUniform>& basicUniforms, const char* variableName) const;
+
+	string						generateVertexSource					(const vector<BasicUniform>& basicUniforms) const;
+	string						generateFragmentSource					(const vector<BasicUniform>& basicUniforms) const;
+
+	void						setupTexture							(const VarValue& value);
+
+	const CaseShaderType						m_caseShaderType;
+
+	vector<glu::Texture2D*>						m_textures2d;
+	vector<glu::TextureCube*>					m_texturesCube;
+	vector<deUint32>							m_filledTextureUnits;
+};
+
+deUint32 UniformCase::randomFeatures (const deUint32 seed)
+{
+	static const deUint32 arrayUsageChoices[]		= { 0, FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX										};
+	static const deUint32 uniformFuncChoices[]		= { 0, FEATURE_UNIFORMFUNC_VALUE												};
+	static const deUint32 matrixModeChoices[]		= { 0, FEATURE_MATRIXMODE_ROWMAJOR												};
+	static const deUint32 arrayAssignChoices[]		= { 0, FEATURE_ARRAYASSIGN_FULL,			FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO	};
+	static const deUint32 uniformUsageChoices[]		= { 0, FEATURE_UNIFORMUSAGE_EVERY_OTHER											};
+	static const deUint32 booleanApiTypeChoices[]	= { 0, FEATURE_BOOLEANAPITYPE_INT,			FEATURE_BOOLEANAPITYPE_UINT			};
+	static const deUint32 uniformValueChoices[]		= { 0, FEATURE_UNIFORMVALUE_ZERO												};
+
+	Random rnd(seed);
+
+	deUint32 result = 0;
+
+#define ARRAY_CHOICE(ARR) (ARR[rnd.getInt(0, DE_LENGTH_OF_ARRAY(ARR)-1)])
+
+	result |= ARRAY_CHOICE(arrayUsageChoices);
+	result |= ARRAY_CHOICE(uniformFuncChoices);
+	result |= ARRAY_CHOICE(matrixModeChoices);
+	result |= ARRAY_CHOICE(arrayAssignChoices);
+	result |= ARRAY_CHOICE(uniformUsageChoices);
+	result |= ARRAY_CHOICE(booleanApiTypeChoices);
+	result |= ARRAY_CHOICE(uniformValueChoices);
+
+#undef ARRAY_CHOICE
+
+	return result;
+}
+
+UniformCase::UniformCase (Context& context, const char* const name, const char* const description, const CaseShaderType caseShaderType, const SharedPtr<const UniformCollection>& uniformCollection, const deUint32 features)
+	: TestCase				(context, name, description)
+	, CallLogWrapper		(context.getRenderContext().getFunctions(), m_testCtx.getLog())
+	, m_features			(features)
+	, m_uniformCollection	(uniformCollection)
+	, m_caseShaderType		(caseShaderType)
+{
+}
+
+UniformCase::UniformCase (Context& context, const char* const name, const char* const description, const CaseShaderType caseShaderType, const SharedPtr<const UniformCollection>& uniformCollection)
+	: TestCase				(context, name, description)
+	, CallLogWrapper		(context.getRenderContext().getFunctions(), m_testCtx.getLog())
+	, m_features			(0)
+	, m_uniformCollection	(uniformCollection)
+	, m_caseShaderType		(caseShaderType)
+{
+}
+
+UniformCase::UniformCase (Context& context, const char* name, const char* description, const deUint32 seed)
+	: TestCase				(context, name, description)
+	, CallLogWrapper		(context.getRenderContext().getFunctions(), m_testCtx.getLog())
+	, m_features			(randomFeatures(seed))
+	, m_uniformCollection	(UniformCollection::random(seed))
+	, m_caseShaderType		(randomCaseShaderType(seed))
+{
+}
+
+void UniformCase::init (void)
+{
+	{
+		const glw::Functions&	funcs						= m_context.getRenderContext().getFunctions();
+		const int				numSamplerUniforms			= m_uniformCollection->getNumSamplers();
+		const int				vertexTexUnitsRequired		= m_caseShaderType != CASESHADERTYPE_FRAGMENT ? numSamplerUniforms : 0;
+		const int				fragmentTexUnitsRequired	= m_caseShaderType != CASESHADERTYPE_VERTEX ? numSamplerUniforms : 0;
+		const int				combinedTexUnitsRequired	= vertexTexUnitsRequired + fragmentTexUnitsRequired;
+		const int				vertexTexUnitsSupported		= getGLInt(funcs, GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS);
+		const int				fragmentTexUnitsSupported	= getGLInt(funcs, GL_MAX_TEXTURE_IMAGE_UNITS);
+		const int				combinedTexUnitsSupported	= getGLInt(funcs, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+
+		DE_ASSERT(numSamplerUniforms <= MAX_NUM_SAMPLER_UNIFORMS);
+
+		if (vertexTexUnitsRequired > vertexTexUnitsSupported)
+			throw tcu::NotSupportedError(de::toString(vertexTexUnitsRequired) + " vertex texture units required, " + de::toString(vertexTexUnitsSupported) + " supported");
+		if (fragmentTexUnitsRequired > fragmentTexUnitsSupported)
+			throw tcu::NotSupportedError(de::toString(fragmentTexUnitsRequired) + " fragment texture units required, " + de::toString(fragmentTexUnitsSupported) + " supported");
+		if (combinedTexUnitsRequired > combinedTexUnitsSupported)
+			throw tcu::NotSupportedError(de::toString(combinedTexUnitsRequired) + " combined texture units required, " + de::toString(combinedTexUnitsSupported) + " supported");
+	}
+
+	enableLogging(true);
+}
+
+void UniformCase::deinit (void)
+{
+	for (int i = 0; i < (int)m_textures2d.size(); i++)
+		delete m_textures2d[i];
+	m_textures2d.clear();
+
+	for (int i = 0; i < (int)m_texturesCube.size(); i++)
+		delete m_texturesCube[i];
+	m_texturesCube.clear();
+
+	m_filledTextureUnits.clear();
+}
+
+UniformCase::~UniformCase (void)
+{
+	UniformCase::deinit();
+}
+
+void UniformCase::generateBasicUniforms (vector<BasicUniform>& basicUniformsDst, vector<BasicUniformReportRef>& basicUniformReportsDst, const glu::VarType& varType, const char* const varName, const bool isParentActive, int& samplerUnitCounter, Random& rnd) const
+{
+	if (varType.isBasicType())
+	{
+		const bool				isActive	= isParentActive && (m_features & FEATURE_UNIFORMUSAGE_EVERY_OTHER ? basicUniformsDst.size() % 2 == 0 : true);
+		const glu::DataType		type		= varType.getBasicType();
+		const VarValue			value		= m_features & FEATURE_UNIFORMVALUE_ZERO	? generateZeroVarValue(type)
+											: glu::isDataTypeSampler(type)				? generateRandomVarValue(type, rnd, samplerUnitCounter++)
+											: generateRandomVarValue(varType.getBasicType(), rnd);
+
+		basicUniformsDst.push_back(BasicUniform(varName, varType.getBasicType(), isActive, value));
+		basicUniformReportsDst.push_back(BasicUniformReportRef(varName, varType.getBasicType(), isActive));
+	}
+	else if (varType.isArrayType())
+	{
+		const int		size			= varType.getArraySize();
+		const string	arrayRootName	= string("") + varName + "[0]";
+		vector<bool>	isElemActive;
+
+		for (int elemNdx = 0; elemNdx < varType.getArraySize(); elemNdx++)
+		{
+			const string	indexedName		= string("") + varName + "[" + de::toString(elemNdx) + "]";
+			const bool		isCurElemActive	= isParentActive																						&&
+											  (m_features & FEATURE_UNIFORMUSAGE_EVERY_OTHER			? basicUniformsDst.size() % 2 == 0	: true)	&&
+											  (m_features & FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX		? elemNdx == size/2					: true);
+
+			isElemActive.push_back(isCurElemActive);
+
+			if (varType.getElementType().isBasicType())
+			{
+				// \note We don't want separate entries in basicUniformReportsDst for elements of basic-type arrays.
+				const glu::DataType	elemBasicType	= varType.getElementType().getBasicType();
+				const VarValue		value			= m_features & FEATURE_UNIFORMVALUE_ZERO	? generateZeroVarValue(elemBasicType)
+													: glu::isDataTypeSampler(elemBasicType)		? generateRandomVarValue(elemBasicType, rnd, samplerUnitCounter++)
+													: generateRandomVarValue(elemBasicType, rnd);
+
+				basicUniformsDst.push_back(BasicUniform(indexedName.c_str(), elemBasicType, isCurElemActive, value, arrayRootName.c_str(), elemNdx, size));
+			}
+			else
+				generateBasicUniforms(basicUniformsDst, basicUniformReportsDst, varType.getElementType(), indexedName.c_str(), isCurElemActive, samplerUnitCounter, rnd);
+		}
+
+		if (varType.getElementType().isBasicType())
+		{
+			int minSize;
+			for (minSize = varType.getArraySize(); minSize > 0 && !isElemActive[minSize-1]; minSize--);
+
+			basicUniformReportsDst.push_back(BasicUniformReportRef(arrayRootName.c_str(), minSize, size, varType.getElementType().getBasicType(), isParentActive && minSize > 0));
+		}
+	}
+	else
+	{
+		DE_ASSERT(varType.isStructType());
+
+		const StructType& structType = *varType.getStructPtr();
+
+		for (int i = 0; i < structType.getNumMembers(); i++)
+		{
+			const glu::StructMember&	member			= structType.getMember(i);
+			const string				memberFullName	= string("") + varName + "." + member.getName();
+
+			generateBasicUniforms(basicUniformsDst, basicUniformReportsDst, member.getType(), memberFullName.c_str(), isParentActive, samplerUnitCounter, rnd);
+		}
+	}
+}
+
+void UniformCase::writeUniformDefinitions (std::ostringstream& dst) const
+{
+	for (int i = 0; i < (int)m_uniformCollection->getNumStructTypes(); i++)
+		dst << glu::declare(m_uniformCollection->getStructType(i)) << ";\n";
+
+	for (int i = 0; i < (int)m_uniformCollection->getNumUniforms(); i++)
+		dst << "uniform " << glu::declare(m_uniformCollection->getUniform(i).type, m_uniformCollection->getUniform(i).name.c_str()) << ";\n";
+
+	dst << "\n";
+
+	{
+		static const struct
+		{
+			dataTypePredicate	requiringTypes[2];
+			const char*			definition;
+		} compareFuncs[] =
+		{
+			{ { glu::isDataTypeFloatOrVec,				glu::isDataTypeMatrix				}, "mediump float compare_float    (mediump float a, mediump float b)  { return abs(a - b) < 0.05 ? 1.0 : 0.0; }"																		},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_VEC2>,	dataTypeIsMatrixWithNRows<2>		}, "mediump float compare_vec2     (mediump vec2 a, mediump vec2 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y); }"														},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_VEC3>,	dataTypeIsMatrixWithNRows<3>		}, "mediump float compare_vec3     (mediump vec3 a, mediump vec3 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y)*compare_float(a.z, b.z); }"								},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_VEC4>,	dataTypeIsMatrixWithNRows<4>		}, "mediump float compare_vec4     (mediump vec4 a, mediump vec4 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y)*compare_float(a.z, b.z)*compare_float(a.w, b.w); }"		},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat2     (mediump mat2 a, mediump mat2 b)    { return compare_vec2(a[0], b[0])*compare_vec2(a[1], b[1]); }"													},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT2X3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat2x3   (mediump mat2x3 a, mediump mat2x3 b){ return compare_vec3(a[0], b[0])*compare_vec3(a[1], b[1]); }"													},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT2X4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat2x4   (mediump mat2x4 a, mediump mat2x4 b){ return compare_vec4(a[0], b[0])*compare_vec4(a[1], b[1]); }"													},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT3X2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat3x2   (mediump mat3x2 a, mediump mat3x2 b){ return compare_vec2(a[0], b[0])*compare_vec2(a[1], b[1])*compare_vec2(a[2], b[2]); }"							},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat3     (mediump mat3 a, mediump mat3 b)    { return compare_vec3(a[0], b[0])*compare_vec3(a[1], b[1])*compare_vec3(a[2], b[2]); }"							},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT3X4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat3x4   (mediump mat3x4 a, mediump mat3x4 b){ return compare_vec4(a[0], b[0])*compare_vec4(a[1], b[1])*compare_vec4(a[2], b[2]); }"							},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT4X2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat4x2   (mediump mat4x2 a, mediump mat4x2 b){ return compare_vec2(a[0], b[0])*compare_vec2(a[1], b[1])*compare_vec2(a[2], b[2])*compare_vec2(a[3], b[3]); }"	},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT4X3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat4x3   (mediump mat4x3 a, mediump mat4x3 b){ return compare_vec3(a[0], b[0])*compare_vec3(a[1], b[1])*compare_vec3(a[2], b[2])*compare_vec3(a[3], b[3]); }"	},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat4     (mediump mat4 a, mediump mat4 b)    { return compare_vec4(a[0], b[0])*compare_vec4(a[1], b[1])*compare_vec4(a[2], b[2])*compare_vec4(a[3], b[3]); }"	},
+			{ { dataTypeEquals<glu::TYPE_INT>,			dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_int      (mediump int a, mediump int b)      { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_INT_VEC2>,		dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_ivec2    (mediump ivec2 a, mediump ivec2 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_INT_VEC3>,		dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_ivec3    (mediump ivec3 a, mediump ivec3 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_INT_VEC4>,		dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_ivec4    (mediump ivec4 a, mediump ivec4 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_UINT>,			dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_uint     (mediump uint a, mediump uint b)    { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_UINT_VEC2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_uvec2    (mediump uvec2 a, mediump uvec2 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_UINT_VEC3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_uvec3    (mediump uvec3 a, mediump uvec3 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_UINT_VEC4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_uvec4    (mediump uvec4 a, mediump uvec4 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL>,			dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bool     (bool a, bool b)                    { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL_VEC2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bvec2    (bvec2 a, bvec2 b)                  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL_VEC3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bvec3    (bvec3 a, bvec3 b)                  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL_VEC4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bvec4    (bvec4 a, bvec4 b)                  { return a == b ? 1.0 : 0.0; }"																					}
+		};
+
+		const vector<glu::DataType> samplerTypes = m_uniformCollection->getSamplerTypes();
+
+		for (int compFuncNdx = 0; compFuncNdx < DE_LENGTH_OF_ARRAY(compareFuncs); compFuncNdx++)
+		{
+			const dataTypePredicate		(&typeReq)[2]			= compareFuncs[compFuncNdx].requiringTypes;
+			bool						containsTypeSampler		= false;
+
+			for (int i = 0; i < (int)samplerTypes.size(); i++)
+			{
+				if (glu::isDataTypeSampler(samplerTypes[i]))
+				{
+					const glu::DataType retType = getSamplerLookupReturnType(samplerTypes[i]);
+					if (typeReq[0](retType) || typeReq[1](retType))
+					{
+						containsTypeSampler = true;
+						break;
+					}
+				}
+			}
+
+			if (containsTypeSampler || m_uniformCollection->containsMatchingBasicType(typeReq[0]) || m_uniformCollection->containsMatchingBasicType(typeReq[1]))
+				dst << compareFuncs[compFuncNdx].definition << "\n";
+		}
+	}
+}
+
+void UniformCase::writeUniformCompareExpr (std::ostringstream& dst, const BasicUniform& uniform) const
+{
+	if (glu::isDataTypeSampler(uniform.type))
+		dst << "compare_" << glu::getDataTypeName(getSamplerLookupReturnType(uniform.type)) << "(texture(" << uniform.name << ", vec" << getSamplerNumLookupDimensions(uniform.type) << "(0.0))";
+	else
+		dst << "compare_" << glu::getDataTypeName(uniform.type) << "(" << uniform.name;
+
+	dst << ", " << shaderVarValueStr(uniform.finalValue) << ")";
+}
+
+void UniformCase::writeUniformComparisons (std::ostringstream& dst, const vector<BasicUniform>& basicUniforms, const char* const variableName) const
+{
+	for (int i = 0; i < (int)basicUniforms.size(); i++)
+	{
+		const BasicUniform& unif = basicUniforms[i];
+
+		if (unif.isUsedInShader)
+		{
+			dst << "\t" << variableName << " *= ";
+			writeUniformCompareExpr(dst, basicUniforms[i]);
+			dst << ";\n";
+		}
+		else
+			dst << "\t// UNUSED: " << basicUniforms[i].name << "\n";
+	}
+}
+
+string UniformCase::generateVertexSource (const vector<BasicUniform>& basicUniforms) const
+{
+	const bool			isVertexCase = m_caseShaderType == CASESHADERTYPE_VERTEX || m_caseShaderType == CASESHADERTYPE_BOTH;
+	std::ostringstream	result;
+
+	result << "#version 300 es\n"
+			  "in highp vec4 a_position;\n"
+			  "out mediump float v_vtxOut;\n"
+			  "\n";
+
+	if (isVertexCase)
+		writeUniformDefinitions(result);
+
+	result << "\n"
+			  "void main (void)\n"
+			  "{\n"
+			  "	gl_Position = a_position;\n"
+			  "	v_vtxOut = 1.0;\n";
+
+	if (isVertexCase)
+		writeUniformComparisons(result, basicUniforms, "v_vtxOut");
+
+	result << "}\n";
+
+	return result.str();
+}
+
+string UniformCase::generateFragmentSource (const vector<BasicUniform>& basicUniforms) const
+{
+	const bool			isFragmentCase = m_caseShaderType == CASESHADERTYPE_FRAGMENT || m_caseShaderType == CASESHADERTYPE_BOTH;
+	std::ostringstream	result;
+
+	result << "#version 300 es\n"
+			  "in mediump float v_vtxOut;\n"
+			  "\n";
+
+	if (isFragmentCase)
+		writeUniformDefinitions(result);
+
+	result << "\n"
+			  "layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+			  "\n"
+			  "void main (void)\n"
+			  "{\n"
+			  "	mediump float result = v_vtxOut;\n";
+
+	if (isFragmentCase)
+		writeUniformComparisons(result, basicUniforms, "result");
+
+	result << "	dEQP_FragColor = vec4(result, result, result, 1.0);\n"
+			  "}\n";
+
+	return result.str();
+}
+
+void UniformCase::setupTexture (const VarValue& value)
+{
+	// \note No handling for samplers other than 2D or cube.
+
+	enableLogging(false);
+
+	DE_ASSERT(getSamplerLookupReturnType(value.type) == glu::TYPE_FLOAT_VEC4);
+
+	const int						width			= 32;
+	const int						height			= 32;
+	const tcu::Vec4					color			= vec4FromPtr(&value.val.samplerV.fillColor.floatV[0]);
+
+	if (value.type == glu::TYPE_SAMPLER_2D)
+	{
+		glu::Texture2D* texture		= new glu::Texture2D(m_context.getRenderContext(), GL_RGBA, GL_UNSIGNED_BYTE, width, height);
+		tcu::Texture2D& refTexture	= texture->getRefTexture();
+		m_textures2d.push_back(texture);
+
+		refTexture.allocLevel(0);
+		fillWithColor(refTexture.getLevel(0), color);
+
+		GLU_CHECK_CALL(glActiveTexture(GL_TEXTURE0 + value.val.samplerV.unit));
+		m_filledTextureUnits.push_back(value.val.samplerV.unit);
+		texture->upload();
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+	}
+	else if (value.type == glu::TYPE_SAMPLER_CUBE)
+	{
+		DE_ASSERT(width == height);
+
+		glu::TextureCube* texture		= new glu::TextureCube(m_context.getRenderContext(), GL_RGBA, GL_UNSIGNED_BYTE, width);
+		tcu::TextureCube& refTexture	= texture->getRefTexture();
+		m_texturesCube.push_back(texture);
+
+		for (int face = 0; face < (int)tcu::CUBEFACE_LAST; face++)
+		{
+			refTexture.allocLevel((tcu::CubeFace)face, 0);
+			fillWithColor(refTexture.getLevelFace(0, (tcu::CubeFace)face), color);
+		}
+
+		GLU_CHECK_CALL(glActiveTexture(GL_TEXTURE0 + value.val.samplerV.unit));
+		m_filledTextureUnits.push_back(value.val.samplerV.unit);
+		texture->upload();
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+
+	}
+	else
+		DE_ASSERT(false);
+
+	enableLogging(true);
+}
+
+bool UniformCase::getActiveUniforms (vector<BasicUniformReportGL>& basicUniformReportsDst, const vector<BasicUniformReportRef>& basicUniformReportsRef, const deUint32 programGL)
+{
+	TestLog&			log						= m_testCtx.getLog();
+	GLint				numActiveUniforms		= 0;
+	GLint				uniformMaxNameLength	= 0;
+	vector<char>		nameBuffer;
+	bool				success					= true;
+
+	GLU_CHECK_CALL(glGetProgramiv(programGL, GL_ACTIVE_UNIFORMS, &numActiveUniforms));
+	log << TestLog::Message << "// Number of active uniforms reported: " << numActiveUniforms << TestLog::EndMessage;
+	GLU_CHECK_CALL(glGetProgramiv(programGL, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformMaxNameLength));
+	log << TestLog::Message << "// Maximum uniform name length reported: " << uniformMaxNameLength << TestLog::EndMessage;
+	nameBuffer.resize(uniformMaxNameLength);
+
+	for (int unifNdx = 0; unifNdx < numActiveUniforms; unifNdx++)
+	{
+		GLsizei					reportedNameLength	= 0;
+		GLint					reportedSize		= -1;
+		GLenum					reportedTypeGL		= GL_NONE;
+
+		GLU_CHECK_CALL(glGetActiveUniform(programGL, (GLuint)unifNdx, (GLsizei)uniformMaxNameLength, &reportedNameLength, &reportedSize, &reportedTypeGL, &nameBuffer[0]));
+
+		const glu::DataType		reportedType		= glu::getDataTypeFromGLType(reportedTypeGL);
+		const string			reportedNameStr		(&nameBuffer[0]);
+
+		TCU_CHECK_MSG(reportedType != glu::TYPE_LAST, "Invalid uniform type");
+
+		log << TestLog::Message << "// Got name = " << reportedNameStr << ", name length = " << reportedNameLength << ", size = " << reportedSize << ", type = " << glu::getDataTypeName(reportedType) << TestLog::EndMessage;
+
+		if ((GLsizei)reportedNameStr.length() != reportedNameLength)
+		{
+			log << TestLog::Message << "// FAILURE: wrong name length reported, should be " << reportedNameStr.length() << TestLog::EndMessage;
+			success = false;
+		}
+
+		if (!deStringBeginsWith(reportedNameStr.c_str(), "gl_")) // Ignore built-in uniforms.
+		{
+			int referenceNdx;
+			for (referenceNdx = 0; referenceNdx < (int)basicUniformReportsRef.size(); referenceNdx++)
+			{
+				if (basicUniformReportsRef[referenceNdx].name == reportedNameStr)
+					break;
+			}
+
+			if (referenceNdx >= (int)basicUniformReportsRef.size())
+			{
+				log << TestLog::Message << "// FAILURE: invalid non-built-in uniform name reported" << TestLog::EndMessage;
+				success = false;
+			}
+			else
+			{
+				const BasicUniformReportRef& reference = basicUniformReportsRef[referenceNdx];
+
+				DE_ASSERT(reference.type != glu::TYPE_LAST);
+				DE_ASSERT(reference.minSize >= 1 || (reference.minSize == 0 && !reference.isUsedInShader));
+				DE_ASSERT(reference.minSize <= reference.maxSize);
+
+				if (BasicUniformReportGL::findWithName(basicUniformReportsDst, reportedNameStr.c_str()) != basicUniformReportsDst.end())
+				{
+					log << TestLog::Message << "// FAILURE: same uniform name reported twice" << TestLog::EndMessage;
+					success = false;
+				}
+
+				basicUniformReportsDst.push_back(BasicUniformReportGL(reportedNameStr.c_str(), reportedNameLength, reportedSize, reportedType, unifNdx));
+
+				if (reportedType != reference.type)
+				{
+					log << TestLog::Message << "// FAILURE: wrong type reported, should be " << glu::getDataTypeName(reference.type) << TestLog::EndMessage;
+					success = false;
+				}
+				if (reportedSize < reference.minSize || reportedSize > reference.maxSize)
+				{
+					log << TestLog::Message
+						<< "// FAILURE: wrong size reported, should be "
+						<< (reference.minSize == reference.maxSize ? de::toString(reference.minSize) : "in the range [" + de::toString(reference.minSize) + ", " + de::toString(reference.maxSize) + "]")
+						<< TestLog::EndMessage;
+
+					success = false;
+				}
+			}
+		}
+	}
+
+	for (int i = 0; i < (int)basicUniformReportsRef.size(); i++)
+	{
+		const BasicUniformReportRef& expected = basicUniformReportsRef[i];
+		if (expected.isUsedInShader && BasicUniformReportGL::findWithName(basicUniformReportsDst, expected.name.c_str()) == basicUniformReportsDst.end())
+		{
+			log << TestLog::Message << "// FAILURE: uniform with name " << expected.name << " was not reported by GL" << TestLog::EndMessage;
+			success = false;
+		}
+	}
+
+	return success;
+}
+
+bool UniformCase::getActiveUniformsiv (vector<BasicUniformReportGL>& basicUniformReportsDst, const vector<BasicUniformReportRef>& basicUniformReportsRef, const deUint32 programGL)
+{
+	TestLog&				log				= m_testCtx.getLog();
+	vector<string>			queryNames		(basicUniformReportsRef.size());
+	vector<const char*>		queryNamesC		(basicUniformReportsRef.size());
+	vector<GLuint>			uniformIndices	(basicUniformReportsRef.size());
+	vector<deUint32>		validUniformIndices; // This shall have the same contents, and in same order, as uniformIndices, but with GL_INVALID_INDEX entries removed.
+	bool					success			= true;
+
+	for (int i = 0; i < (int)basicUniformReportsRef.size(); i++)
+	{
+		const string& name = basicUniformReportsRef[i].name;
+		queryNames[i]	= m_features & FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX && name[name.size()-1] == ']' ? beforeLast(name, '[') : name;
+		queryNamesC[i]	= queryNames[i].c_str();
+	}
+
+	GLU_CHECK_CALL(glGetUniformIndices(programGL, (GLsizei)basicUniformReportsRef.size(), &queryNamesC[0], &uniformIndices[0]));
+
+	for (int i = 0; i < (int)uniformIndices.size(); i++)
+	{
+		if (uniformIndices[i] != GL_INVALID_INDEX)
+			validUniformIndices.push_back(uniformIndices[i]);
+		else
+		{
+			if (basicUniformReportsRef[i].isUsedInShader)
+			{
+				log << TestLog::Message << "// FAILURE: uniform with name " << basicUniformReportsRef[i].name << " received GL_INVALID_INDEX" << TestLog::EndMessage;
+				success = false;
+			}
+		}
+	}
+
+	if (!validUniformIndices.empty())
+	{
+		vector<GLint> uniformNameLengthBuf	(validUniformIndices.size());
+		vector<GLint> uniformSizeBuf		(validUniformIndices.size());
+		vector<GLint> uniformTypeBuf		(validUniformIndices.size());
+
+		GLU_CHECK_CALL(glGetActiveUniformsiv(programGL, (GLsizei)validUniformIndices.size(), &validUniformIndices[0], GL_UNIFORM_NAME_LENGTH,	&uniformNameLengthBuf[0]));
+		GLU_CHECK_CALL(glGetActiveUniformsiv(programGL, (GLsizei)validUniformIndices.size(), &validUniformIndices[0], GL_UNIFORM_SIZE,			&uniformSizeBuf[0]));
+		GLU_CHECK_CALL(glGetActiveUniformsiv(programGL, (GLsizei)validUniformIndices.size(), &validUniformIndices[0], GL_UNIFORM_TYPE,			&uniformTypeBuf[0]));
+
+		{
+			int validNdx = -1; // Keeps the corresponding index to validUniformIndices while unifNdx is the index to uniformIndices.
+			for (int unifNdx = 0; unifNdx < (int)uniformIndices.size(); unifNdx++)
+			{
+				if (uniformIndices[unifNdx] == GL_INVALID_INDEX)
+					continue;
+
+				validNdx++;
+
+				const BasicUniformReportRef&	reference			= basicUniformReportsRef[unifNdx];
+				const int						reportedIndex		= validUniformIndices[validNdx];
+				const int						reportedNameLength	= (int)uniformNameLengthBuf[validNdx];
+				const int						reportedSize		= (int)uniformSizeBuf[validNdx];
+				const glu::DataType				reportedType		= glu::getDataTypeFromGLType((deUint32)uniformTypeBuf[validNdx]);
+
+				TCU_CHECK_MSG(reportedType != glu::TYPE_LAST, "Invalid uniform type");
+
+				log << TestLog::Message
+					<< "// Got name length = " << reportedNameLength
+					<< ", size = " << reportedSize
+					<< ", type = " << glu::getDataTypeName(reportedType)
+					<< " for the uniform at index " << reportedIndex << " (" << reference.name << ")"
+					<< TestLog::EndMessage;
+
+				DE_ASSERT(reference.type != glu::TYPE_LAST);
+				DE_ASSERT(reference.minSize >= 1 || (reference.minSize == 0 && !reference.isUsedInShader));
+				DE_ASSERT(reference.minSize <= reference.maxSize);
+				basicUniformReportsDst.push_back(BasicUniformReportGL(reference.name.c_str(), reportedNameLength, reportedSize, reportedType, reportedIndex));
+
+				if (reportedNameLength != (int)reference.name.length() + 1)
+				{
+					log << TestLog::Message << "// FAILURE: wrong name length reported, should be " << reference.name.length() + 1 << TestLog::EndMessage;
+					success = false;
+				}
+
+				if (reportedType != reference.type)
+				{
+					log << TestLog::Message << "// FAILURE: wrong type reported, should be " << glu::getDataTypeName(reference.type) << TestLog::EndMessage;
+					success = false;
+				}
+
+				if (reportedSize < reference.minSize || reportedSize > reference.maxSize)
+				{
+					log << TestLog::Message
+						<< "// FAILURE: wrong size reported, should be "
+						<< (reference.minSize == reference.maxSize ? de::toString(reference.minSize) : "in the range [" + de::toString(reference.minSize) + ", " + de::toString(reference.maxSize) + "]")
+						<< TestLog::EndMessage;
+
+					success = false;
+				}
+			}
+		}
+	}
+
+	return success;
+}
+
+bool UniformCase::uniformVsUniformsivComparison (const vector<BasicUniformReportGL>& uniformResults, const vector<BasicUniformReportGL>& uniformsivResults)
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		success		= true;
+
+	for (int uniformResultNdx = 0; uniformResultNdx < (int)uniformResults.size(); uniformResultNdx++)
+	{
+		const BasicUniformReportGL&							uniformResult		= uniformResults[uniformResultNdx];
+		const string&										uniformName			= uniformResult.name;
+		const vector<BasicUniformReportGL>::const_iterator	uniformsivResultIt	= BasicUniformReportGL::findWithName(uniformsivResults, uniformName.c_str());
+
+		if (uniformsivResultIt != uniformsivResults.end())
+		{
+			const BasicUniformReportGL& uniformsivResult = *uniformsivResultIt;
+
+			log << TestLog::Message << "// Checking uniform " << uniformName << TestLog::EndMessage;
+
+			if (uniformResult.index != uniformsivResult.index)
+			{
+				log << TestLog::Message << "// FAILURE: glGetActiveUniform() and glGetUniformIndices() gave different indices for uniform " << uniformName << TestLog::EndMessage;
+				success = false;
+			}
+			if (uniformResult.nameLength + 1 != uniformsivResult.nameLength)
+			{
+				log << TestLog::Message << "// FAILURE: glGetActiveUniform() and glGetActiveUniformsiv() gave incompatible name lengths for uniform " << uniformName << TestLog::EndMessage;
+				success = false;
+			}
+			if (uniformResult.size != uniformsivResult.size)
+			{
+				log << TestLog::Message << "// FAILURE: glGetActiveUniform() and glGetActiveUniformsiv() gave different sizes for uniform " << uniformName << TestLog::EndMessage;
+				success = false;
+			}
+			if (uniformResult.type != uniformsivResult.type)
+			{
+				log << TestLog::Message << "// FAILURE: glGetActiveUniform() and glGetActiveUniformsiv() gave different types for uniform " << uniformName << TestLog::EndMessage;
+				success = false;
+			}
+		}
+		else
+		{
+			log << TestLog::Message << "// FAILURE: uniform " << uniformName << " was reported active by glGetActiveUniform() but not by glGetUniformIndices()" << TestLog::EndMessage;
+			success = false;
+		}
+	}
+
+	for (int uniformsivResultNdx = 0; uniformsivResultNdx < (int)uniformsivResults.size(); uniformsivResultNdx++)
+	{
+		const BasicUniformReportGL&							uniformsivResult	= uniformsivResults[uniformsivResultNdx];
+		const string&										uniformsivName		= uniformsivResult.name;
+		const vector<BasicUniformReportGL>::const_iterator	uniformsResultIt	= BasicUniformReportGL::findWithName(uniformsivResults, uniformsivName.c_str());
+
+		if (uniformsResultIt == uniformsivResults.end())
+		{
+			log << TestLog::Message << "// FAILURE: uniform " << uniformsivName << " was reported active by glGetUniformIndices() but not by glGetActiveUniform()" << TestLog::EndMessage;
+			success = false;
+		}
+	}
+
+	return success;
+}
+
+bool UniformCase::getUniforms (vector<VarValue>& valuesDst, const vector<BasicUniform>& basicUniforms, const deUint32 programGL)
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		success		= true;
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform		= basicUniforms[unifNdx];
+		const string			queryName	= m_features & FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX && uniform.elemNdx == 0 ? beforeLast(uniform.name, '[') : uniform.name;
+		const int				location	= glGetUniformLocation(programGL, queryName.c_str());
+		const int				size		= glu::getDataTypeScalarSize(uniform.type);
+		VarValue				value;
+
+		deMemset(&value, 0xcd, sizeof(value)); // Initialize to known garbage.
+
+		if (location == -1)
+		{
+			value.type = glu::TYPE_INVALID;
+			valuesDst.push_back(value);
+			if (uniform.isUsedInShader)
+			{
+				log << TestLog::Message << "// FAILURE: " << uniform.name << " was used in shader, but has location -1" << TestLog::EndMessage;
+				success = false;
+			}
+			continue;
+		}
+
+		value.type = uniform.type;
+
+		DE_STATIC_ASSERT(sizeof(GLint) == sizeof(value.val.intV[0]));
+		DE_STATIC_ASSERT(sizeof(GLuint) == sizeof(value.val.uintV[0]));
+		DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(value.val.floatV[0]));
+
+		if (glu::isDataTypeFloatOrVec(uniform.type) || glu::isDataTypeMatrix(uniform.type))
+			GLU_CHECK_CALL(glGetUniformfv(programGL, location, &value.val.floatV[0]));
+		else if (glu::isDataTypeIntOrIVec(uniform.type))
+			GLU_CHECK_CALL(glGetUniformiv(programGL, location, &value.val.intV[0]));
+		else if (glu::isDataTypeUintOrUVec(uniform.type))
+			GLU_CHECK_CALL(glGetUniformuiv(programGL, location, &value.val.uintV[0]));
+		else if (glu::isDataTypeBoolOrBVec(uniform.type))
+		{
+			if (m_features & FEATURE_BOOLEANAPITYPE_INT)
+			{
+				GLU_CHECK_CALL(glGetUniformiv(programGL, location, &value.val.intV[0]));
+				for (int i = 0; i < size; i++)
+					value.val.boolV[i] = value.val.intV[i] != 0;
+			}
+			else if (m_features & FEATURE_BOOLEANAPITYPE_UINT)
+			{
+				GLU_CHECK_CALL(glGetUniformuiv(programGL, location, &value.val.uintV[0]));
+				for (int i = 0; i < size; i++)
+					value.val.boolV[i] = value.val.uintV[i] != 0;
+			}
+			else // Default: use float.
+			{
+				GLU_CHECK_CALL(glGetUniformfv(programGL, location, &value.val.floatV[0]));
+				for (int i = 0; i < size; i++)
+					value.val.boolV[i] = value.val.floatV[i] != 0.0f;
+			}
+		}
+		else if (glu::isDataTypeSampler(uniform.type))
+		{
+			GLint unit = -1;
+			GLU_CHECK_CALL(glGetUniformiv(programGL, location, &unit));
+			value.val.samplerV.unit = unit;
+		}
+		else
+			DE_ASSERT(false);
+
+		valuesDst.push_back(value);
+
+		log << TestLog::Message << "// Got " << uniform.name << " value " << apiVarValueStr(value) << TestLog::EndMessage;
+	}
+
+	return success;
+}
+
+bool UniformCase::checkUniformDefaultValues (const vector<VarValue>& values, const vector<BasicUniform>& basicUniforms)
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		success		= true;
+
+	DE_ASSERT(values.size() == basicUniforms.size());
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform		= basicUniforms[unifNdx];
+		const VarValue&			unifValue	= values[unifNdx];
+		const int				valSize		= glu::getDataTypeScalarSize(uniform.type);
+
+		log << TestLog::Message << "// Checking uniform " << uniform.name << TestLog::EndMessage;
+
+		if (unifValue.type == glu::TYPE_INVALID) // This happens when glGetUniformLocation() returned -1.
+			continue;
+
+#define CHECK_UNIFORM(VAR_VALUE_MEMBER, ZERO)																								\
+	do																																		\
+	{																																		\
+		for (int i = 0; i < valSize; i++)																									\
+		{																																	\
+			if (unifValue.val.VAR_VALUE_MEMBER[i] != ZERO)																					\
+			{																																\
+				log << TestLog::Message << "// FAILURE: uniform " << uniform.name << " has non-zero initial value" << TestLog::EndMessage;	\
+				success = false;																											\
+			}																																\
+		}																																	\
+	} while (false)
+
+		if (glu::isDataTypeFloatOrVec(uniform.type) || glu::isDataTypeMatrix(uniform.type))
+			CHECK_UNIFORM(floatV, 0.0f);
+		else if (glu::isDataTypeIntOrIVec(uniform.type))
+			CHECK_UNIFORM(intV, 0);
+		else if (glu::isDataTypeUintOrUVec(uniform.type))
+			CHECK_UNIFORM(uintV, 0);
+		else if (glu::isDataTypeBoolOrBVec(uniform.type))
+			CHECK_UNIFORM(boolV, false);
+		else if (glu::isDataTypeSampler(uniform.type))
+		{
+			if (unifValue.val.samplerV.unit != 0)
+			{
+				log << TestLog::Message << "// FAILURE: uniform " << uniform.name << " has non-zero initial value" << TestLog::EndMessage;
+				success = false;
+			}
+		}
+		else
+			DE_ASSERT(false);
+
+#undef CHECK_UNIFORM
+	}
+
+	return success;
+}
+
+void UniformCase::assignUniforms (const vector<BasicUniform>& basicUniforms, deUint32 programGL, Random& rnd)
+{
+	TestLog&				log				= m_testCtx.getLog();
+	const bool				transpose		= (m_features & FEATURE_MATRIXMODE_ROWMAJOR) != 0;
+	const GLboolean			transposeGL		= transpose ? GL_TRUE : GL_FALSE;
+	const glu::DataType		boolApiType		= m_features & FEATURE_BOOLEANAPITYPE_INT	? glu::TYPE_INT
+											: m_features & FEATURE_BOOLEANAPITYPE_UINT	? glu::TYPE_UINT
+											:											  glu::TYPE_FLOAT;
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform				= basicUniforms[unifNdx];
+		const bool				isArrayMember		= uniform.elemNdx >= 0;
+		const string			queryName			= m_features & FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX && uniform.elemNdx == 0 ? beforeLast(uniform.name, '[') : uniform.name;
+		const int				numValuesToAssign	= !isArrayMember									? 1
+													: m_features & FEATURE_ARRAYASSIGN_FULL				? (uniform.elemNdx == 0			? uniform.rootSize	: 0)
+													: m_features & FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO	? (uniform.elemNdx % 2 == 0		? 2					: 0)
+													: /* Default: assign array elements separately */	  1;
+
+		DE_ASSERT(numValuesToAssign >= 0);
+		DE_ASSERT(numValuesToAssign == 1 || isArrayMember);
+
+		if (numValuesToAssign == 0)
+		{
+			log << TestLog::Message << "// Uniform " << uniform.name << " is covered by another glUniform*v() call to the same array" << TestLog::EndMessage;
+			continue;
+		}
+
+		const int			location			= glGetUniformLocation(programGL, queryName.c_str());
+		const int			typeSize			= glu::getDataTypeScalarSize(uniform.type);
+		const bool			assignByValue		= m_features & FEATURE_UNIFORMFUNC_VALUE && !glu::isDataTypeMatrix(uniform.type) && numValuesToAssign == 1;
+		vector<VarValue>	valuesToAssign;
+
+		for (int i = 0; i < numValuesToAssign; i++)
+		{
+			const string	curName = isArrayMember ? beforeLast(uniform.rootName, '[') + "[" + de::toString(uniform.elemNdx+i) + "]" : uniform.name;
+			VarValue		unifValue;
+
+			if (isArrayMember)
+			{
+				const vector<BasicUniform>::const_iterator elemUnif = BasicUniform::findWithName(basicUniforms, curName.c_str());
+				if (elemUnif == basicUniforms.end())
+					continue;
+				unifValue = elemUnif->finalValue;
+			}
+			else
+				unifValue = uniform.finalValue;
+
+			const VarValue apiValue = glu::isDataTypeBoolOrBVec(unifValue.type)	? getRandomBoolRepresentation(unifValue, boolApiType, rnd)
+									: glu::isDataTypeSampler(unifValue.type)	? getSamplerUnitValue(unifValue)
+									: unifValue;
+
+			valuesToAssign.push_back(glu::isDataTypeMatrix(apiValue.type) && transpose ? getTransposeMatrix(apiValue) : apiValue);
+
+			if (glu::isDataTypeBoolOrBVec(uniform.type))
+				log << TestLog::Message << "// Using type " << glu::getDataTypeName(boolApiType) << " to set boolean value " << apiVarValueStr(unifValue) << " for " << curName << TestLog::EndMessage;
+			else if (glu::isDataTypeSampler(uniform.type))
+				log << TestLog::Message << "// Texture for the sampler uniform " << curName << " will be filled with color " << apiVarValueStr(getSamplerFillValue(uniform.finalValue)) << TestLog::EndMessage;
+		}
+
+		DE_ASSERT(!valuesToAssign.empty());
+
+		if (glu::isDataTypeFloatOrVec(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+			{
+				const float* const ptr = &valuesToAssign[0].val.floatV[0];
+
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glUniform1f(location, ptr[0]));							break;
+					case 2: GLU_CHECK_CALL(glUniform2f(location, ptr[0], ptr[1]));					break;
+					case 3: GLU_CHECK_CALL(glUniform3f(location, ptr[0], ptr[1], ptr[2]));			break;
+					case 4: GLU_CHECK_CALL(glUniform4f(location, ptr[0], ptr[1], ptr[2], ptr[3]));	break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+			else
+			{
+				vector<float> buffer(valuesToAssign.size() * typeSize);
+				for (int i = 0; i < (int)buffer.size(); i++)
+					buffer[i] = valuesToAssign[i / typeSize].val.floatV[i % typeSize];
+
+				DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(buffer[0]));
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glUniform1fv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 2: GLU_CHECK_CALL(glUniform2fv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 3: GLU_CHECK_CALL(glUniform3fv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 4: GLU_CHECK_CALL(glUniform4fv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+		else if (glu::isDataTypeMatrix(valuesToAssign[0].type))
+		{
+			DE_ASSERT(!assignByValue);
+
+			vector<float> buffer(valuesToAssign.size() * typeSize);
+			for (int i = 0; i < (int)buffer.size(); i++)
+				buffer[i] = valuesToAssign[i / typeSize].val.floatV[i % typeSize];
+
+			DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(buffer[0]));
+			switch (uniform.type)
+			{
+				case glu::TYPE_FLOAT_MAT2:		GLU_CHECK_CALL(glUniformMatrix2fv	(location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT3:		GLU_CHECK_CALL(glUniformMatrix3fv	(location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT4:		GLU_CHECK_CALL(glUniformMatrix4fv	(location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT2X3:	GLU_CHECK_CALL(glUniformMatrix2x3fv	(location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT2X4:	GLU_CHECK_CALL(glUniformMatrix2x4fv	(location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT3X2:	GLU_CHECK_CALL(glUniformMatrix3x2fv	(location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT3X4:	GLU_CHECK_CALL(glUniformMatrix3x4fv	(location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT4X2:	GLU_CHECK_CALL(glUniformMatrix4x2fv	(location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT4X3:	GLU_CHECK_CALL(glUniformMatrix4x3fv	(location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+		else if (glu::isDataTypeIntOrIVec(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+			{
+				const deInt32* const ptr = &valuesToAssign[0].val.intV[0];
+
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glUniform1i(location, ptr[0]));							break;
+					case 2: GLU_CHECK_CALL(glUniform2i(location, ptr[0], ptr[1]));					break;
+					case 3: GLU_CHECK_CALL(glUniform3i(location, ptr[0], ptr[1], ptr[2]));			break;
+					case 4: GLU_CHECK_CALL(glUniform4i(location, ptr[0], ptr[1], ptr[2], ptr[3]));	break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+			else
+			{
+				vector<deInt32> buffer(valuesToAssign.size() * typeSize);
+				for (int i = 0; i < (int)buffer.size(); i++)
+					buffer[i] = valuesToAssign[i / typeSize].val.intV[i % typeSize];
+
+				DE_STATIC_ASSERT(sizeof(GLint) == sizeof(buffer[0]));
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glUniform1iv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 2: GLU_CHECK_CALL(glUniform2iv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 3: GLU_CHECK_CALL(glUniform3iv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 4: GLU_CHECK_CALL(glUniform4iv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+		else if (glu::isDataTypeUintOrUVec(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+			{
+				const deUint32* const ptr = &valuesToAssign[0].val.uintV[0];
+
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glUniform1ui(location, ptr[0]));							break;
+					case 2: GLU_CHECK_CALL(glUniform2ui(location, ptr[0], ptr[1]));					break;
+					case 3: GLU_CHECK_CALL(glUniform3ui(location, ptr[0], ptr[1], ptr[2]));			break;
+					case 4: GLU_CHECK_CALL(glUniform4ui(location, ptr[0], ptr[1], ptr[2], ptr[3]));	break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+			else
+			{
+				vector<deUint32> buffer(valuesToAssign.size() * typeSize);
+				for (int i = 0; i < (int)buffer.size(); i++)
+					buffer[i] = valuesToAssign[i / typeSize].val.intV[i % typeSize];
+
+				DE_STATIC_ASSERT(sizeof(GLuint) == sizeof(buffer[0]));
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glUniform1uiv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 2: GLU_CHECK_CALL(glUniform2uiv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 3: GLU_CHECK_CALL(glUniform3uiv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 4: GLU_CHECK_CALL(glUniform4uiv(location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+		else if (glu::isDataTypeSampler(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+				GLU_CHECK_CALL(glUniform1i(location, uniform.finalValue.val.samplerV.unit));
+			else
+			{
+				const GLint unit = uniform.finalValue.val.samplerV.unit;
+				GLU_CHECK_CALL(glUniform1iv(location, (GLsizei)valuesToAssign.size(), &unit));
+			}
+		}
+		else
+			DE_ASSERT(false);
+	}
+}
+
+bool UniformCase::compareUniformValues (const vector<VarValue>& values, const vector<BasicUniform>& basicUniforms)
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		success		= true;
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform		= basicUniforms[unifNdx];
+		const VarValue&			unifValue	= values[unifNdx];
+
+		log << TestLog::Message << "// Checking uniform " << uniform.name << TestLog::EndMessage;
+
+		if (unifValue.type == glu::TYPE_INVALID) // This happens when glGetUniformLocation() returned -1.
+			continue;
+
+		if (!apiVarValueEquals(unifValue, uniform.finalValue))
+		{
+			log << TestLog::Message << "// FAILURE: value obtained with glGetUniform*() for uniform " << uniform.name << " differs from value set with glUniform*()" << TestLog::EndMessage;
+			success = false;
+		}
+	}
+
+	return success;
+}
+
+bool UniformCase::renderTest (const vector<BasicUniform>& basicUniforms, const ShaderProgram& program, Random& rnd)
+{
+	TestLog&					log				= m_testCtx.getLog();
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderTarget();
+	const int					viewportW		= de::min(renderTarget.getWidth(),	MAX_RENDER_WIDTH);
+	const int					viewportH		= de::min(renderTarget.getHeight(),	MAX_RENDER_HEIGHT);
+	const int					viewportX		= rnd.getInt(0, renderTarget.getWidth()		- viewportW);
+	const int					viewportY		= rnd.getInt(0, renderTarget.getHeight()	- viewportH);
+	tcu::Surface				renderedImg		(viewportW, viewportH);
+
+	// Assert that no two samplers of different types have the same texture unit - this is an error in GL.
+	for (int i = 0; i < (int)basicUniforms.size(); i++)
+	{
+		if (glu::isDataTypeSampler(basicUniforms[i].type))
+		{
+			for (int j = 0; j < i; j++)
+			{
+				if (glu::isDataTypeSampler(basicUniforms[j].type) && basicUniforms[i].type != basicUniforms[j].type)
+					DE_ASSERT(basicUniforms[i].finalValue.val.samplerV.unit != basicUniforms[j].finalValue.val.samplerV.unit);
+			}
+		}
+	}
+
+	for (int i = 0; i < (int)basicUniforms.size(); i++)
+	{
+		if (glu::isDataTypeSampler(basicUniforms[i].type) && std::find(m_filledTextureUnits.begin(), m_filledTextureUnits.end(), basicUniforms[i].finalValue.val.samplerV.unit) == m_filledTextureUnits.end())
+		{
+			log << TestLog::Message << "// Filling texture at unit " << apiVarValueStr(basicUniforms[i].finalValue) << " with color " << shaderVarValueStr(basicUniforms[i].finalValue) << TestLog::EndMessage;
+			setupTexture(basicUniforms[i].finalValue);
+		}
+	}
+
+	GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportW, viewportH));
+
+	{
+		static const float position[] =
+		{
+			-1.0f, -1.0f, 0.0f, 1.0f,
+			-1.0f, +1.0f, 0.0f, 1.0f,
+			+1.0f, -1.0f, 0.0f, 1.0f,
+			+1.0f, +1.0f, 0.0f, 1.0f
+		};
+		static const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+		const int posLoc = glGetAttribLocation(program.getProgram(), "a_position");
+		glEnableVertexAttribArray(posLoc);
+		glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &position[0]);
+		GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_SHORT, &indices[0]));
+	}
+
+	glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedImg.getAccess());
+
+	int numFailedPixels = 0;
+	for (int y = 0; y < renderedImg.getHeight(); y++)
+	{
+		for (int x = 0; x < renderedImg.getWidth(); x++)
+		{
+			if (renderedImg.getPixel(x, y) != tcu::RGBA::white)
+				numFailedPixels += 1;
+		}
+	}
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Image("RenderedImage", "Rendered image", renderedImg);
+		log << TestLog::Message << "FAILURE: image comparison failed, got " << numFailedPixels << " non-white pixels" << TestLog::EndMessage;
+		return false;
+	}
+	else
+	{
+		log << TestLog::Message << "Success: got all-white pixels (all uniforms have correct values)" << TestLog::EndMessage;
+		return true;
+	}
+}
+
+UniformCase::IterateResult UniformCase::iterate (void)
+{
+	Random							rnd				(deStringHash(getName()) ^ (deUint32)m_context.getTestContext().getCommandLine().getBaseSeed());
+	TestLog&						log				= m_testCtx.getLog();
+	vector<BasicUniform>			basicUniforms;
+	vector<BasicUniformReportRef>	basicUniformReportsRef;
+
+	{
+		int samplerUnitCounter = 0;
+		for (int i = 0; i < (int)m_uniformCollection->getNumUniforms(); i++)
+			generateBasicUniforms(basicUniforms, basicUniformReportsRef, m_uniformCollection->getUniform(i).type, m_uniformCollection->getUniform(i).name.c_str(), true, samplerUnitCounter, rnd);
+	}
+
+	const string					vertexSource	= generateVertexSource(basicUniforms);
+	const string					fragmentSource	= generateFragmentSource(basicUniforms);
+	const ShaderProgram				program			(m_context.getRenderContext(), glu::makeVtxFragSources(vertexSource, fragmentSource));
+
+	log << program;
+
+	if (!program.isOk())
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		return STOP;
+	}
+
+	GLU_CHECK_CALL(glUseProgram(program.getProgram()));
+
+	const bool success = test(basicUniforms, basicUniformReportsRef, program, rnd);
+	m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							success ? "Passed"				: "Failed");
+
+	return STOP;
+}
+
+class UniformInfoQueryCase : public UniformCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_UNIFORM = 0,			//!< Check info returned by glGetActiveUniform().
+		CASETYPE_INDICES_UNIFORMSIV,	//!< Check info returned by glGetUniformIndices() + glGetActiveUniformsiv().
+		CASETYPE_CONSISTENCY,			//!< Query info with both above methods, and check consistency.
+
+		CASETYPE_LAST
+	};
+
+						UniformInfoQueryCase	(Context& context, const char* name, const char* description, CaseShaderType shaderType, const SharedPtr<const UniformCollection>& uniformCollection, CaseType caseType, deUint32 additionalFeatures = 0);
+	bool				test					(const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd);
+
+	static const char*	getCaseTypeName			(CaseType caseType);
+	static const char*	getCaseTypeDescription	(CaseType caseType);
+
+private:
+	const CaseType		m_caseType;
+};
+
+const char* UniformInfoQueryCase::getCaseTypeName (const CaseType caseType)
+{
+	switch (caseType)
+	{
+		case CASETYPE_UNIFORM:				return "active_uniform";
+		case CASETYPE_INDICES_UNIFORMSIV:	return "indices_active_uniformsiv";
+		case CASETYPE_CONSISTENCY:			return "consistency";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+const char* UniformInfoQueryCase::getCaseTypeDescription (const CaseType caseType)
+{
+	switch (caseType)
+	{
+		case CASETYPE_UNIFORM:				return "Test glGetActiveUniform()";
+		case CASETYPE_INDICES_UNIFORMSIV:	return "Test glGetUniformIndices() along with glGetActiveUniformsiv()";
+		case CASETYPE_CONSISTENCY:			return "Check consistency between results from glGetActiveUniform() and glGetUniformIndices() + glGetActiveUniformsiv()";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+UniformInfoQueryCase::UniformInfoQueryCase (Context& context, const char* const name, const char* const description, const CaseShaderType shaderType, const SharedPtr<const UniformCollection>& uniformCollection, const CaseType caseType, const deUint32 additionalFeatures)
+	: UniformCase	(context, name, description, shaderType, uniformCollection, additionalFeatures)
+	, m_caseType	(caseType)
+{
+}
+
+bool UniformInfoQueryCase::test (const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd)
+{
+	DE_UNREF(basicUniforms);
+	DE_UNREF(rnd);
+
+	const deUint32					programGL	= program.getProgram();
+	TestLog&						log			= m_testCtx.getLog();
+	vector<BasicUniformReportGL>	basicUniformReportsUniform;
+	vector<BasicUniformReportGL>	basicUniformReportsUniformsiv;
+
+	if (m_caseType == CASETYPE_UNIFORM || m_caseType == CASETYPE_CONSISTENCY)
+	{
+		bool success = false;
+
+		{
+			const ScopedLogSection section(log, "InfoGetActiveUniform", "Uniform information queries with glGetActiveUniform()");
+			success = getActiveUniforms(basicUniformReportsUniform, basicUniformReportsRef, programGL);
+		}
+
+		if (!success)
+		{
+			if (m_caseType == CASETYPE_UNIFORM)
+				return false;
+			else
+			{
+				DE_ASSERT(m_caseType == CASETYPE_CONSISTENCY);
+				log << TestLog::Message << "// Note: this is a consistency case, so ignoring above failure(s)" << TestLog::EndMessage;
+			}
+		}
+	}
+
+	if (m_caseType == CASETYPE_INDICES_UNIFORMSIV || m_caseType == CASETYPE_CONSISTENCY)
+	{
+		bool success = false;
+
+		{
+			const ScopedLogSection section(log, "InfoGetActiveUniformsiv", "Uniform information queries with glGetUniformIndices() and glGetActiveUniformsiv()");
+			success = getActiveUniformsiv(basicUniformReportsUniformsiv, basicUniformReportsRef, programGL);
+		}
+
+		if (!success)
+		{
+			if (m_caseType == CASETYPE_INDICES_UNIFORMSIV)
+				return false;
+			else
+			{
+				DE_ASSERT(m_caseType == CASETYPE_CONSISTENCY);
+				log << TestLog::Message << "// Note: this is a consistency case, so ignoring above failure(s)" << TestLog::EndMessage;
+			}
+		}
+	}
+
+	if (m_caseType == CASETYPE_CONSISTENCY)
+	{
+		bool success = false;
+
+		{
+			const ScopedLogSection section(log, "CompareUniformVsUniformsiv", "Comparison of results from glGetActiveUniform() and glGetActiveUniformsiv()");
+			success = uniformVsUniformsivComparison(basicUniformReportsUniform, basicUniformReportsUniformsiv);
+		}
+
+		if (!success)
+			return false;
+	}
+
+	return true;
+}
+
+class UniformValueCase : public UniformCase
+{
+public:
+	enum ValueToCheck
+	{
+		VALUETOCHECK_INITIAL = 0,		//!< Verify the initial values of the uniforms (i.e. check that they're zero).
+		VALUETOCHECK_ASSIGNED,			//!< Assign values to uniforms with glUniform*(), and check those.
+
+		VALUETOCHECK_LAST
+	};
+	enum CheckMethod
+	{
+		CHECKMETHOD_GET_UNIFORM = 0,	//!< Check values with glGetUniform*().
+		CHECKMETHOD_RENDER,				//!< Check values by rendering with the value-checking shader.
+
+		CHECKMETHOD_LAST
+	};
+	enum AssignMethod
+	{
+		ASSIGNMETHOD_POINTER = 0,
+		ASSIGNMETHOD_VALUE,
+
+		ASSIGNMETHOD_LAST
+	};
+
+						UniformValueCase			(Context&									context,
+													 const char*								name,
+													 const char*								description,
+													 CaseShaderType								shaderType,
+													 const SharedPtr<const UniformCollection>&	uniformCollection,
+													 ValueToCheck								valueToCheck,
+													 CheckMethod								checkMethod,
+													 AssignMethod								assignMethod,
+													 deUint32									additionalFeatures = 0);
+
+	bool				test						(const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd);
+
+	static const char*	getValueToCheckName			(ValueToCheck valueToCheck);
+	static const char*	getValueToCheckDescription	(ValueToCheck valueToCheck);
+	static const char*	getCheckMethodName			(CheckMethod checkMethod);
+	static const char*	getCheckMethodDescription	(CheckMethod checkMethod);
+	static const char*	getAssignMethodName			(AssignMethod checkMethod);
+	static const char*	getAssignMethodDescription	(AssignMethod checkMethod);
+
+private:
+	const ValueToCheck	m_valueToCheck;
+	const CheckMethod	m_checkMethod;
+};
+
+const char* UniformValueCase::getValueToCheckName (const ValueToCheck valueToCheck)
+{
+	switch (valueToCheck)
+	{
+		case VALUETOCHECK_INITIAL:	return "initial";
+		case VALUETOCHECK_ASSIGNED:	return "assigned";
+		default: DE_ASSERT(false);	return DE_NULL;
+	}
+}
+
+const char* UniformValueCase::getValueToCheckDescription (const ValueToCheck valueToCheck)
+{
+	switch (valueToCheck)
+{
+		case VALUETOCHECK_INITIAL:	return "Check initial uniform values (zeros)";
+		case VALUETOCHECK_ASSIGNED:	return "Check assigned uniform values";
+		default: DE_ASSERT(false);	return DE_NULL;
+	}
+}
+
+const char* UniformValueCase::getCheckMethodName (const CheckMethod checkMethod)
+{
+	switch (checkMethod)
+	{
+		case CHECKMETHOD_GET_UNIFORM:	return "get_uniform";
+		case CHECKMETHOD_RENDER:		return "render";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+const char* UniformValueCase::getCheckMethodDescription (const CheckMethod checkMethod)
+{
+	switch (checkMethod)
+	{
+		case CHECKMETHOD_GET_UNIFORM:	return "Verify values with glGetUniform*()";
+		case CHECKMETHOD_RENDER:		return "Verify values by rendering";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+const char* UniformValueCase::getAssignMethodName (const AssignMethod assignMethod)
+{
+	switch (assignMethod)
+	{
+		case ASSIGNMETHOD_POINTER:		return "by_pointer";
+		case ASSIGNMETHOD_VALUE:		return "by_value";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+const char* UniformValueCase::getAssignMethodDescription (const AssignMethod assignMethod)
+{
+	switch (assignMethod)
+	{
+		case ASSIGNMETHOD_POINTER:		return "Assign values by-pointer";
+		case ASSIGNMETHOD_VALUE:		return "Assign values by-value";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+UniformValueCase::UniformValueCase (Context&									context,
+									const char* const							name,
+									const char* const							description,
+									const CaseShaderType						shaderType,
+									const SharedPtr<const UniformCollection>&	uniformCollection,
+									const ValueToCheck							valueToCheck,
+									const CheckMethod							checkMethod,
+									const AssignMethod							assignMethod,
+									const deUint32								additionalFeatures)
+	: UniformCase		(context, name, description, shaderType, uniformCollection,
+						 (valueToCheck == VALUETOCHECK_INITIAL ? FEATURE_UNIFORMVALUE_ZERO : 0) | (assignMethod == ASSIGNMETHOD_VALUE ? FEATURE_UNIFORMFUNC_VALUE : 0) | additionalFeatures)
+	, m_valueToCheck	(valueToCheck)
+	, m_checkMethod		(checkMethod)
+{
+	DE_ASSERT(!(assignMethod == ASSIGNMETHOD_LAST && valueToCheck == VALUETOCHECK_ASSIGNED));
+}
+
+bool UniformValueCase::test (const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd)
+{
+	DE_UNREF(basicUniformReportsRef);
+
+	const deUint32	programGL	= program.getProgram();
+	TestLog&		log			= m_testCtx.getLog();
+
+	if (m_valueToCheck == VALUETOCHECK_ASSIGNED)
+	{
+		const ScopedLogSection section(log, "UniformAssign", "Uniform value assignments");
+		assignUniforms(basicUniforms, programGL, rnd);
+	}
+	else
+		DE_ASSERT(m_valueToCheck == VALUETOCHECK_INITIAL);
+
+	if (m_checkMethod == CHECKMETHOD_GET_UNIFORM)
+	{
+		vector<VarValue> values;
+
+		{
+			const ScopedLogSection section(log, "GetUniforms", "Uniform value query");
+			const bool success = getUniforms(values, basicUniforms, program.getProgram());
+
+			if (!success)
+				return false;
+		}
+
+		if (m_valueToCheck == VALUETOCHECK_ASSIGNED)
+		{
+			const ScopedLogSection section(log, "ValueCheck", "Verify that the reported values match the assigned values");
+			const bool success = compareUniformValues(values, basicUniforms);
+
+			if (!success)
+				return false;
+		}
+		else
+		{
+			DE_ASSERT(m_valueToCheck == VALUETOCHECK_INITIAL);
+
+			const ScopedLogSection section(log, "ValueCheck", "Verify that the uniforms have correct initial values (zeros)");
+			const bool success = checkUniformDefaultValues(values, basicUniforms);
+
+			if (!success)
+				return false;
+		}
+	}
+	else
+	{
+		DE_ASSERT(m_checkMethod == CHECKMETHOD_RENDER);
+
+		const ScopedLogSection section(log, "RenderTest", "Render test");
+		const bool success = renderTest(basicUniforms, program, rnd);
+
+		if (!success)
+			return false;
+	}
+
+	return true;
+}
+
+class RandomUniformCase : public UniformCase
+{
+public:
+						RandomUniformCase		(Context& m_context, const char* name, const char* description, deUint32 seed);
+
+	bool				test					(const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd);
+};
+
+RandomUniformCase::RandomUniformCase (Context& context, const char* const name, const char* const description, const deUint32 seed)
+	: UniformCase (context, name, description, seed ^ (deUint32)context.getTestContext().getCommandLine().getBaseSeed())
+{
+}
+
+bool RandomUniformCase::test (const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd)
+{
+	// \note Different sampler types may not be bound to same unit when rendering.
+	const bool		renderingPossible						= (m_features & FEATURE_UNIFORMVALUE_ZERO) == 0 || !m_uniformCollection->containsSeveralSamplerTypes();
+
+	bool			performGetActiveUniforms				= rnd.getBool();
+	const bool		performGetActiveUniformsiv				= rnd.getBool();
+	const bool		performUniformVsUniformsivComparison	= performGetActiveUniforms && performGetActiveUniformsiv && rnd.getBool();
+	const bool		performGetUniforms						= rnd.getBool();
+	const bool		performCheckUniformDefaultValues		= performGetUniforms && rnd.getBool();
+	const bool		performAssignUniforms					= rnd.getBool();
+	const bool		performCompareUniformValues				= performGetUniforms && performAssignUniforms && rnd.getBool();
+	const bool		performRenderTest						= renderingPossible && performAssignUniforms && rnd.getBool();
+	const deUint32	programGL								= program.getProgram();
+	TestLog&		log										= m_testCtx.getLog();
+
+	if (!(performGetActiveUniforms || performGetActiveUniformsiv || performUniformVsUniformsivComparison || performGetUniforms || performCheckUniformDefaultValues || performAssignUniforms || performCompareUniformValues || performRenderTest))
+		performGetActiveUniforms = true; // Do something at least.
+
+#define PERFORM_AND_CHECK(CALL, SECTION_NAME, SECTION_DESCRIPTION)						\
+	do																					\
+	{																					\
+		const ScopedLogSection section(log, (SECTION_NAME), (SECTION_DESCRIPTION));		\
+		const bool success = (CALL);													\
+		if (!success)																	\
+			return false;																\
+	} while (false)
+
+	{
+		vector<BasicUniformReportGL> reportsUniform;
+		vector<BasicUniformReportGL> reportsUniformsiv;
+
+		if (performGetActiveUniforms)
+			PERFORM_AND_CHECK(getActiveUniforms(reportsUniform, basicUniformReportsRef, programGL), "InfoGetActiveUniform", "Uniform information queries with glGetActiveUniform()");
+		if (performGetActiveUniformsiv)
+			PERFORM_AND_CHECK(getActiveUniformsiv(reportsUniformsiv, basicUniformReportsRef, programGL), "InfoGetActiveUniformsiv", "Uniform information queries with glGetIndices() and glGetActiveUniformsiv()");
+		if (performUniformVsUniformsivComparison)
+			PERFORM_AND_CHECK(uniformVsUniformsivComparison(reportsUniform, reportsUniformsiv), "CompareUniformVsUniformsiv", "Comparison of results from glGetActiveUniform() and glGetActiveUniformsiv()");
+	}
+
+	{
+		vector<VarValue> uniformDefaultValues;
+
+		if (performGetUniforms)
+			PERFORM_AND_CHECK(getUniforms(uniformDefaultValues, basicUniforms, programGL), "GetUniformDefaults", "Uniform default value query");
+		if (performCheckUniformDefaultValues)
+			PERFORM_AND_CHECK(checkUniformDefaultValues(uniformDefaultValues, basicUniforms), "DefaultValueCheck", "Verify that the uniforms have correct initial values (zeros)");
+	}
+
+	{
+		vector<VarValue> uniformValues;
+
+		if (performAssignUniforms)
+		{
+			const ScopedLogSection section(log, "UniformAssign", "Uniform value assignments");
+			assignUniforms(basicUniforms, programGL, rnd);
+		}
+		if (performCompareUniformValues)
+		{
+			PERFORM_AND_CHECK(getUniforms(uniformValues, basicUniforms, programGL), "GetUniforms", "Uniform value query");
+			PERFORM_AND_CHECK(compareUniformValues(uniformValues, basicUniforms), "ValueCheck", "Verify that the reported values match the assigned values");
+		}
+	}
+
+	if (performRenderTest)
+		PERFORM_AND_CHECK(renderTest(basicUniforms, program, rnd), "RenderTest", "Render test");
+
+#undef PERFORM_AND_CHECK
+
+	return true;
+}
+
+UniformApiTests::UniformApiTests (Context& context)
+	: TestCaseGroup(context, "uniform_api", "Uniform API Tests")
+{
+}
+
+UniformApiTests::~UniformApiTests (void)
+{
+}
+
+namespace
+{
+
+// \note Although this is only used in UniformApiTest::init, it needs to be defined here as it's used as a template argument.
+struct UniformCollectionCase
+{
+	string								namePrefix;
+	SharedPtr<const UniformCollection>	uniformCollection;
+
+	UniformCollectionCase (const char* const name, const UniformCollection* uniformCollection_)
+		: namePrefix			(name ? name + string("_") : "")
+		, uniformCollection		(uniformCollection_)
+	{
+	}
+};
+
+} // anonymous
+
+void UniformApiTests::init (void)
+{
+	// Generate sets of UniformCollections that are used by several cases.
+
+	enum
+	{
+		UNIFORMCOLLECTIONS_BASIC = 0,
+		UNIFORMCOLLECTIONS_BASIC_ARRAY,
+		UNIFORMCOLLECTIONS_BASIC_STRUCT,
+		UNIFORMCOLLECTIONS_STRUCT_IN_ARRAY,
+		UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT,
+		UNIFORMCOLLECTIONS_NESTED_STRUCTS_ARRAYS,
+		UNIFORMCOLLECTIONS_MULTIPLE_BASIC,
+		UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY,
+		UNIFORMCOLLECTIONS_MULTIPLE_NESTED_STRUCTS_ARRAYS,
+
+		UNIFORMCOLLECTIONS_LAST
+	};
+
+	struct UniformCollectionGroup
+	{
+		string							name;
+		vector<UniformCollectionCase>	cases;
+	} defaultUniformCollections[UNIFORMCOLLECTIONS_LAST];
+
+	defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC].name							= "basic";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_ARRAY].name						= "basic_array";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_STRUCT].name						= "basic_struct";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_STRUCT_IN_ARRAY].name					= "struct_in_array";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT].name					= "array_in_struct";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_NESTED_STRUCTS_ARRAYS].name			= "nested_structs_arrays";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC].name					= "multiple_basic";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY].name				= "multiple_basic_array";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_NESTED_STRUCTS_ARRAYS].name	= "multiple_nested_structs_arrays";
+
+	for (int dataTypeNdx = 0; dataTypeNdx < DE_LENGTH_OF_ARRAY(s_testDataTypes); dataTypeNdx++)
+	{
+		const glu::DataType		dataType	= s_testDataTypes[dataTypeNdx];
+		const char* const		typeName	= glu::getDataTypeName(dataType);
+
+		defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC].cases.push_back(UniformCollectionCase(typeName, UniformCollection::basic(dataType)));
+
+		if (glu::isDataTypeScalar(dataType)													||
+			(glu::isDataTypeVector(dataType) && glu::getDataTypeScalarSize(dataType) == 4)	||
+			dataType == glu::TYPE_FLOAT_MAT4												||
+			dataType == glu::TYPE_SAMPLER_2D)
+			defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_ARRAY].cases.push_back(UniformCollectionCase(typeName, UniformCollection::basicArray(dataType)));
+
+		if (glu::isDataTypeScalar(dataType)		||
+			dataType == glu::TYPE_FLOAT_MAT4	||
+			dataType == glu::TYPE_SAMPLER_2D)
+		{
+			const glu::DataType		secondDataType	= glu::isDataTypeScalar(dataType)	? glu::getDataTypeVector(dataType, 4)
+													: dataType == glu::TYPE_FLOAT_MAT4	? glu::TYPE_FLOAT_MAT2
+													: dataType == glu::TYPE_SAMPLER_2D	? glu::TYPE_SAMPLER_CUBE
+													: glu::TYPE_LAST;
+			DE_ASSERT(secondDataType != glu::TYPE_LAST);
+			const char* const		secondTypeName	= glu::getDataTypeName(secondDataType);
+			const string			name			= string("") + typeName + "_" + secondTypeName;
+
+			defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_STRUCT].cases.push_back			(UniformCollectionCase(name.c_str(), UniformCollection::basicStruct(dataType, secondDataType, false)));
+			defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT].cases.push_back		(UniformCollectionCase(name.c_str(), UniformCollection::basicStruct(dataType, secondDataType, true)));
+			defaultUniformCollections[UNIFORMCOLLECTIONS_STRUCT_IN_ARRAY].cases.push_back		(UniformCollectionCase(name.c_str(), UniformCollection::structInArray(dataType, secondDataType, false)));
+			defaultUniformCollections[UNIFORMCOLLECTIONS_NESTED_STRUCTS_ARRAYS].cases.push_back	(UniformCollectionCase(name.c_str(), UniformCollection::nestedArraysStructs(dataType, secondDataType)));
+		}
+	}
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC].cases.push_back					(UniformCollectionCase(DE_NULL, UniformCollection::multipleBasic()));
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY].cases.push_back				(UniformCollectionCase(DE_NULL, UniformCollection::multipleBasicArray()));
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_NESTED_STRUCTS_ARRAYS].cases.push_back	(UniformCollectionCase(DE_NULL, UniformCollection::multipleNestedArraysStructs()));
+
+	// Info-query cases (check info returned by e.g. glGetActiveUniforms()).
+
+	{
+		TestCaseGroup* const infoQueryGroup = new TestCaseGroup(m_context, "info_query", "Test uniform info querying functions");
+		addChild(infoQueryGroup);
+		for (int caseTypeI = 0; caseTypeI < (int)UniformInfoQueryCase::CASETYPE_LAST; caseTypeI++)
+		{
+			const UniformInfoQueryCase::CaseType	caseType		= (UniformInfoQueryCase::CaseType)caseTypeI;
+			TestCaseGroup* const					caseTypeGroup	= new TestCaseGroup(m_context, UniformInfoQueryCase::getCaseTypeName(caseType), UniformInfoQueryCase::getCaseTypeDescription(caseType));
+			infoQueryGroup->addChild(caseTypeGroup);
+
+			for (int collectionGroupNdx = 0; collectionGroupNdx < (int)UNIFORMCOLLECTIONS_LAST; collectionGroupNdx++)
+			{
+				const int numArrayFirstElemNameCases = caseType == UniformInfoQueryCase::CASETYPE_INDICES_UNIFORMSIV && collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY ? 2 : 1;
+
+				for (int referToFirstArrayElemWithoutIndexI = 0; referToFirstArrayElemWithoutIndexI < numArrayFirstElemNameCases; referToFirstArrayElemWithoutIndexI++)
+				{
+					const UniformCollectionGroup&	collectionGroup			= defaultUniformCollections[collectionGroupNdx];
+					const string					collectionGroupName		= collectionGroup.name + (referToFirstArrayElemWithoutIndexI == 0 ? "" : "_first_elem_without_brackets");
+					TestCaseGroup* const			collectionTestGroup		= new TestCaseGroup(m_context, collectionGroupName.c_str(), "");
+					caseTypeGroup->addChild(collectionTestGroup);
+
+					for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+					{
+						const UniformCollectionCase& collectionCase = collectionGroup.cases[collectionNdx];
+
+						for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+						{
+							const string								name				= collectionCase.namePrefix + getCaseShaderTypeName((CaseShaderType)shaderType);
+							const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+
+							collectionTestGroup->addChild(new UniformInfoQueryCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection, (UniformInfoQueryCase::CaseType)caseType,
+																				   referToFirstArrayElemWithoutIndexI == 0 ? 0 : UniformCase::FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX));
+						}
+					}
+				}
+			}
+
+			// Info-querying cases when unused uniforms are present.
+
+			{
+				TestCaseGroup* const unusedUniformsGroup = new TestCaseGroup(m_context, "unused_uniforms", "Test with unused uniforms");
+				caseTypeGroup->addChild(unusedUniformsGroup);
+
+				const UniformCollectionGroup& collectionGroup = defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT];
+
+				for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+				{
+					const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+					const string								collName			= collectionCase.namePrefix;
+					const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+
+					for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+					{
+						const string name = collName + getCaseShaderTypeName((CaseShaderType)shaderType);
+						unusedUniformsGroup->addChild(new UniformInfoQueryCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection, (UniformInfoQueryCase::CaseType)caseType,
+																			   UniformCase::FEATURE_UNIFORMUSAGE_EVERY_OTHER | UniformCase::FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX));
+					}
+				}
+			}
+		}
+	}
+
+	// Cases testing uniform values.
+
+	{
+		TestCaseGroup* const valueGroup = new TestCaseGroup(m_context, "value", "Uniform value tests");
+		addChild(valueGroup);
+
+		// Cases checking uniforms' initial values (all must be zeros), with glGetUniform*() or by rendering.
+
+		{
+			TestCaseGroup* const initialValuesGroup = new TestCaseGroup(m_context,
+																		UniformValueCase::getValueToCheckName(UniformValueCase::VALUETOCHECK_INITIAL),
+																		UniformValueCase::getValueToCheckDescription(UniformValueCase::VALUETOCHECK_INITIAL));
+			valueGroup->addChild(initialValuesGroup);
+
+			for (int checkMethodI = 0; checkMethodI < (int)UniformValueCase::CHECKMETHOD_LAST; checkMethodI++)
+			{
+				const UniformValueCase::CheckMethod		checkMethod			= (UniformValueCase::CheckMethod)checkMethodI;
+				TestCaseGroup* const					checkMethodGroup	= new TestCaseGroup(m_context, UniformValueCase::getCheckMethodName(checkMethod), UniformValueCase::getCheckMethodDescription(checkMethod));
+				initialValuesGroup->addChild(checkMethodGroup);
+
+				for (int collectionGroupNdx = 0; collectionGroupNdx < (int)UNIFORMCOLLECTIONS_LAST; collectionGroupNdx++)
+				{
+					const UniformCollectionGroup&	collectionGroup		= defaultUniformCollections[collectionGroupNdx];
+					TestCaseGroup* const			collectionTestGroup	= new TestCaseGroup(m_context, collectionGroup.name.c_str(), "");
+					checkMethodGroup->addChild(collectionTestGroup);
+
+					for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+					{
+						const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+						const string								collName			= collectionCase.namePrefix;
+						const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+						const bool									containsBooleans	= uniformCollection->containsMatchingBasicType(glu::isDataTypeBoolOrBVec);
+						const bool									varyBoolApiType		= checkMethod == UniformValueCase::CHECKMETHOD_GET_UNIFORM && containsBooleans &&
+																						  (collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC || collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY);
+						const int									numBoolVariations	= varyBoolApiType ? 3 : 1;
+
+						if (checkMethod == UniformValueCase::CHECKMETHOD_RENDER && uniformCollection->containsSeveralSamplerTypes())
+							continue; // \note Samplers' initial API values (i.e. their texture units) are 0, and no two samplers of different types shall have same unit when rendering.
+
+						for (int booleanTypeI = 0; booleanTypeI < numBoolVariations; booleanTypeI++)
+						{
+							const deUint32		booleanTypeFeat	= booleanTypeI == 1 ? UniformCase::FEATURE_BOOLEANAPITYPE_INT
+																: booleanTypeI == 2 ? UniformCase::FEATURE_BOOLEANAPITYPE_UINT
+																: 0;
+							const char* const	booleanTypeName	= booleanTypeI == 1 ? "int"
+																: booleanTypeI == 2 ? "uint"
+																: "float";
+							const string		nameWithApiType	= varyBoolApiType ? collName + "api_" + booleanTypeName + "_" : collName;
+
+							for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+							{
+								const string name = nameWithApiType + getCaseShaderTypeName((CaseShaderType)shaderType);
+								collectionTestGroup->addChild(new UniformValueCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																				   UniformValueCase::VALUETOCHECK_INITIAL, checkMethod, UniformValueCase::ASSIGNMETHOD_LAST, booleanTypeFeat));
+							}
+						}
+					}
+				}
+			}
+		}
+
+		// Cases that first assign values to each uniform, then check the values with glGetUniform*() or by rendering.
+
+		{
+			TestCaseGroup* const assignedValuesGroup = new TestCaseGroup(m_context,
+																		UniformValueCase::getValueToCheckName(UniformValueCase::VALUETOCHECK_ASSIGNED),
+																		UniformValueCase::getValueToCheckDescription(UniformValueCase::VALUETOCHECK_ASSIGNED));
+			valueGroup->addChild(assignedValuesGroup);
+
+			for (int assignMethodI = 0; assignMethodI < (int)UniformValueCase::ASSIGNMETHOD_LAST; assignMethodI++)
+			{
+				const UniformValueCase::AssignMethod	assignMethod		= (UniformValueCase::AssignMethod)assignMethodI;
+				TestCaseGroup* const					assignMethodGroup	= new TestCaseGroup(m_context, UniformValueCase::getAssignMethodName(assignMethod), UniformValueCase::getAssignMethodDescription(assignMethod));
+				assignedValuesGroup->addChild(assignMethodGroup);
+
+				for (int checkMethodI = 0; checkMethodI < (int)UniformValueCase::CHECKMETHOD_LAST; checkMethodI++)
+				{
+					const UniformValueCase::CheckMethod		checkMethod			= (UniformValueCase::CheckMethod)checkMethodI;
+					TestCaseGroup* const					checkMethodGroup	= new TestCaseGroup(m_context, UniformValueCase::getCheckMethodName(checkMethod), UniformValueCase::getCheckMethodDescription(checkMethod));
+					assignMethodGroup->addChild(checkMethodGroup);
+
+					for (int collectionGroupNdx = 0; collectionGroupNdx < (int)UNIFORMCOLLECTIONS_LAST; collectionGroupNdx++)
+					{
+						const int numArrayFirstElemNameCases = checkMethod == UniformValueCase::CHECKMETHOD_GET_UNIFORM && collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY ? 2 : 1;
+
+						for (int referToFirstArrayElemWithoutIndexI = 0; referToFirstArrayElemWithoutIndexI < numArrayFirstElemNameCases; referToFirstArrayElemWithoutIndexI++)
+						{
+							const UniformCollectionGroup&	collectionGroup			= defaultUniformCollections[collectionGroupNdx];
+							const string					collectionGroupName		= collectionGroup.name + (referToFirstArrayElemWithoutIndexI == 0 ? "" : "_first_elem_without_brackets");
+							TestCaseGroup* const			collectionTestGroup		= new TestCaseGroup(m_context, collectionGroupName.c_str(), "");
+							checkMethodGroup->addChild(collectionTestGroup);
+
+							for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+							{
+								const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+								const string								collName			= collectionCase.namePrefix;
+								const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+								const bool									containsBooleans	= uniformCollection->containsMatchingBasicType(glu::isDataTypeBoolOrBVec);
+								const bool									varyBoolApiType		= checkMethod == UniformValueCase::CHECKMETHOD_GET_UNIFORM && containsBooleans &&
+																								  (collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC || collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY);
+								const int									numBoolVariations	= varyBoolApiType ? 3 : 1;
+								const bool									containsMatrices	= uniformCollection->containsMatchingBasicType(glu::isDataTypeMatrix);
+								const bool									varyMatrixMode		= containsMatrices &&
+																								  (collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC || collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY);
+								const int									numMatVariations	= varyMatrixMode ? 2 : 1;
+
+								if (containsMatrices && assignMethod != UniformValueCase::ASSIGNMETHOD_POINTER)
+									continue;
+
+								for (int booleanTypeI = 0; booleanTypeI < numBoolVariations; booleanTypeI++)
+								{
+									const deUint32		booleanTypeFeat		= booleanTypeI == 1 ? UniformCase::FEATURE_BOOLEANAPITYPE_INT
+																			: booleanTypeI == 2 ? UniformCase::FEATURE_BOOLEANAPITYPE_UINT
+																			: 0;
+									const char* const	booleanTypeName		= booleanTypeI == 1 ? "int"
+																			: booleanTypeI == 2 ? "uint"
+																			: "float";
+									const string		nameWithBoolType	= varyBoolApiType ? collName + "api_" + booleanTypeName + "_" : collName;
+
+									for (int matrixTypeI = 0; matrixTypeI < numMatVariations; matrixTypeI++)
+									{
+										const string nameWithMatrixType = nameWithBoolType + (matrixTypeI == 1 ? "row_major_" : "");
+
+										for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+										{
+											const string	name							= nameWithMatrixType + getCaseShaderTypeName((CaseShaderType)shaderType);
+											const deUint32	arrayFirstElemNameNoIndexFeat	= referToFirstArrayElemWithoutIndexI == 0 ? 0 : UniformCase::FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX;
+
+											collectionTestGroup->addChild(new UniformValueCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																							   UniformValueCase::VALUETOCHECK_ASSIGNED, checkMethod, assignMethod,
+																							   booleanTypeFeat | arrayFirstElemNameNoIndexFeat | (matrixTypeI == 1 ? UniformCase::FEATURE_MATRIXMODE_ROWMAJOR : 0)));
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+
+			// Cases assign multiple basic-array elements with one glUniform*v() (i.e. the count parameter is bigger than 1).
+
+			{
+				static const struct
+				{
+					UniformCase::Feature	arrayAssignMode;
+					const char*				name;
+					const char*				description;
+				} arrayAssignGroups[] =
+				{
+					{ UniformCase::FEATURE_ARRAYASSIGN_FULL,			"basic_array_assign_full",		"Assign entire basic-type arrays per glUniform*v() call"			},
+					{ UniformCase::FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO,	"basic_array_assign_partial",	"Assign two elements of a basic-type array per glUniform*v() call"	}
+				};
+
+				for (int arrayAssignGroupNdx = 0; arrayAssignGroupNdx < DE_LENGTH_OF_ARRAY(arrayAssignGroups); arrayAssignGroupNdx++)
+				{
+					UniformCase::Feature	arrayAssignMode		= arrayAssignGroups[arrayAssignGroupNdx].arrayAssignMode;
+					const char* const		groupName			= arrayAssignGroups[arrayAssignGroupNdx].name;
+					const char* const		groupDesc			= arrayAssignGroups[arrayAssignGroupNdx].description;
+
+					TestCaseGroup* const curArrayAssignGroup = new TestCaseGroup(m_context, groupName, groupDesc);
+					assignedValuesGroup->addChild(curArrayAssignGroup);
+
+					static const int basicArrayCollectionGroups[] = { UNIFORMCOLLECTIONS_BASIC_ARRAY, UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT, UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY };
+
+					for (int collectionGroupNdx = 0; collectionGroupNdx < DE_LENGTH_OF_ARRAY(basicArrayCollectionGroups); collectionGroupNdx++)
+					{
+						const UniformCollectionGroup&	collectionGroup		= defaultUniformCollections[basicArrayCollectionGroups[collectionGroupNdx]];
+						TestCaseGroup* const			collectionTestGroup	= new TestCaseGroup(m_context, collectionGroup.name.c_str(), "");
+						curArrayAssignGroup->addChild(collectionTestGroup);
+
+						for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+						{
+							const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+							const string								collName			= collectionCase.namePrefix;
+							const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+
+							for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+							{
+								const string name = collName + getCaseShaderTypeName((CaseShaderType)shaderType);
+								collectionTestGroup->addChild(new UniformValueCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																				   UniformValueCase::VALUETOCHECK_ASSIGNED, UniformValueCase::CHECKMETHOD_GET_UNIFORM, UniformValueCase::ASSIGNMETHOD_POINTER,
+																				   arrayAssignMode));
+							}
+						}
+					}
+				}
+			}
+
+			// Value checking cases when unused uniforms are present.
+
+			{
+				TestCaseGroup* const unusedUniformsGroup = new TestCaseGroup(m_context, "unused_uniforms", "Test with unused uniforms");
+				assignedValuesGroup->addChild(unusedUniformsGroup);
+
+				const UniformCollectionGroup& collectionGroup = defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT];
+
+				for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+				{
+					const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+					const string								collName			= collectionCase.namePrefix;
+					const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+
+					for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+					{
+						const string name = collName + getCaseShaderTypeName((CaseShaderType)shaderType);
+						unusedUniformsGroup->addChild(new UniformValueCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																		   UniformValueCase::VALUETOCHECK_ASSIGNED, UniformValueCase::CHECKMETHOD_GET_UNIFORM, UniformValueCase::ASSIGNMETHOD_POINTER,
+																		   UniformCase::FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX | UniformCase::FEATURE_UNIFORMUSAGE_EVERY_OTHER));
+					}
+				}
+			}
+		}
+	}
+
+	// Random cases.
+
+	{
+		const int		numRandomCases		= 100;
+		TestCaseGroup*	const randomGroup	= new TestCaseGroup(m_context, "random", "Random cases");
+		addChild(randomGroup);
+
+		for (int ndx = 0; ndx < numRandomCases; ndx++)
+			randomGroup->addChild(new RandomUniformCase(m_context, de::toString(ndx).c_str(), "", (deUint32)ndx));
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fUniformApiTests.hpp b/modules/gles3/functional/es3fUniformApiTests.hpp
new file mode 100644
index 0000000..a034fdd
--- /dev/null
+++ b/modules/gles3/functional/es3fUniformApiTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FUNIFORMAPITESTS_HPP
+#define _ES3FUNIFORMAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Uniform API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class UniformApiTests : public TestCaseGroup
+{
+public:
+							UniformApiTests		(Context& context);
+							~UniformApiTests	(void);
+
+	void					init				(void);
+
+private:
+							UniformApiTests		(const UniformApiTests& other);
+	UniformApiTests&		operator=			(const UniformApiTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FUNIFORMAPITESTS_HPP
diff --git a/modules/gles3/functional/es3fUniformBlockTests.cpp b/modules/gles3/functional/es3fUniformBlockTests.cpp
new file mode 100644
index 0000000..2671945
--- /dev/null
+++ b/modules/gles3/functional/es3fUniformBlockTests.cpp
@@ -0,0 +1,732 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Uniform block tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fUniformBlockTests.hpp"
+#include "glsUniformBlockCase.hpp"
+#include "glsRandomUniformBlockCase.hpp"
+#include "tcuCommandLine.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+using gls::UniformBlockCase;
+using gls::RandomUniformBlockCase;
+using namespace gls::ub;
+
+static void createRandomCaseGroup (tcu::TestCaseGroup* parentGroup, Context& context, const char* groupName, const char* description, UniformBlockCase::BufferMode bufferMode, deUint32 features, int numCases, deUint32 baseSeed)
+{
+	tcu::TestCaseGroup* group = new tcu::TestCaseGroup(context.getTestContext(), groupName, description);
+	parentGroup->addChild(group);
+
+	baseSeed += (deUint32)context.getTestContext().getCommandLine().getBaseSeed();
+
+	for (int ndx = 0; ndx < numCases; ndx++)
+		group->addChild(new RandomUniformBlockCase(context.getTestContext(), context.getRenderContext(), glu::GLSL_VERSION_300_ES,
+												   de::toString(ndx).c_str(), "", bufferMode, features, (deUint32)ndx+baseSeed));
+}
+
+class BlockBasicTypeCase : public UniformBlockCase
+{
+public:
+	BlockBasicTypeCase (Context& context, const char* name, const char* description, const VarType& type, deUint32 layoutFlags, int numInstances)
+		: UniformBlockCase(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_300_ES, BUFFERMODE_PER_BLOCK)
+	{
+		UniformBlock& block = m_interface.allocBlock("Block");
+		block.addUniform(Uniform("var", type, 0));
+		block.setFlags(layoutFlags);
+
+		if (numInstances > 0)
+		{
+			block.setArraySize(numInstances);
+			block.setInstanceName("block");
+		}
+	}
+};
+
+static void createBlockBasicTypeCases (tcu::TestCaseGroup* group, Context& context, const char* name, const VarType& type, deUint32 layoutFlags, int numInstances = 0)
+{
+	group->addChild(new BlockBasicTypeCase(context, (string(name) + "_vertex").c_str(),		"", type, layoutFlags|DECLARE_VERTEX,					numInstances));
+	group->addChild(new BlockBasicTypeCase(context, (string(name) + "_fragment").c_str(),	"", type, layoutFlags|DECLARE_FRAGMENT,					numInstances));
+
+	if (!(layoutFlags & LAYOUT_PACKED))
+		group->addChild(new BlockBasicTypeCase(context, (string(name) + "_both").c_str(),		"", type, layoutFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	numInstances));
+}
+
+class BlockSingleStructCase : public UniformBlockCase
+{
+public:
+	BlockSingleStructCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: UniformBlockCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_300_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_INT_VEC3, PRECISION_HIGH), UNUSED_BOTH); // First member is unused.
+		typeS.addMember("b", VarType(VarType(glu::TYPE_FLOAT_MAT3, PRECISION_MEDIUM), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, PRECISION_HIGH));
+
+		UniformBlock& block = m_interface.allocBlock("Block");
+		block.addUniform(Uniform("s", VarType(&typeS), 0));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class BlockSingleStructArrayCase : public UniformBlockCase
+{
+public:
+	BlockSingleStructArrayCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: UniformBlockCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_300_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_INT_VEC3, PRECISION_HIGH), UNUSED_BOTH);
+		typeS.addMember("b", VarType(VarType(glu::TYPE_FLOAT_MAT3, PRECISION_MEDIUM), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, PRECISION_HIGH));
+
+		UniformBlock& block = m_interface.allocBlock("Block");
+		block.addUniform(Uniform("u", VarType(glu::TYPE_UINT, PRECISION_LOW)));
+		block.addUniform(Uniform("s", VarType(VarType(&typeS), 3)));
+		block.addUniform(Uniform("v", VarType(glu::TYPE_FLOAT_VEC4, PRECISION_MEDIUM)));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class BlockSingleNestedStructCase : public UniformBlockCase
+{
+public:
+	BlockSingleNestedStructCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: UniformBlockCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_300_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_INT_VEC3, PRECISION_HIGH));
+		typeS.addMember("b", VarType(VarType(glu::TYPE_FLOAT_MAT3, PRECISION_MEDIUM), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, PRECISION_HIGH), UNUSED_BOTH);
+
+		StructType& typeT = m_interface.allocStruct("T");
+		typeT.addMember("a", VarType(glu::TYPE_FLOAT_MAT3, PRECISION_MEDIUM));
+		typeT.addMember("b", VarType(&typeS));
+
+		UniformBlock& block = m_interface.allocBlock("Block");
+		block.addUniform(Uniform("s", VarType(&typeS), 0));
+		block.addUniform(Uniform("v", VarType(glu::TYPE_FLOAT_VEC2, PRECISION_LOW), UNUSED_BOTH));
+		block.addUniform(Uniform("t", VarType(&typeT), 0));
+		block.addUniform(Uniform("u", VarType(glu::TYPE_UINT, PRECISION_HIGH), 0));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class BlockSingleNestedStructArrayCase : public UniformBlockCase
+{
+public:
+	BlockSingleNestedStructArrayCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: UniformBlockCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_300_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_INT_VEC3, PRECISION_HIGH));
+		typeS.addMember("b", VarType(VarType(glu::TYPE_INT_VEC2, PRECISION_MEDIUM), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, PRECISION_HIGH), UNUSED_BOTH);
+
+		StructType& typeT = m_interface.allocStruct("T");
+		typeT.addMember("a", VarType(glu::TYPE_FLOAT_MAT3, PRECISION_MEDIUM));
+		typeT.addMember("b", VarType(VarType(&typeS), 3));
+
+		UniformBlock& block = m_interface.allocBlock("Block");
+		block.addUniform(Uniform("s", VarType(&typeS), 0));
+		block.addUniform(Uniform("v", VarType(glu::TYPE_FLOAT_VEC2, PRECISION_LOW), UNUSED_BOTH));
+		block.addUniform(Uniform("t", VarType(VarType(&typeT), 2), 0));
+		block.addUniform(Uniform("u", VarType(glu::TYPE_UINT, PRECISION_HIGH), 0));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class BlockMultiBasicTypesCase : public UniformBlockCase
+{
+public:
+	BlockMultiBasicTypesCase (Context& context, const char* name, const char* description, deUint32 flagsA, deUint32 flagsB, BufferMode bufferMode, int numInstances)
+		: UniformBlockCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_300_ES, bufferMode)
+		, m_flagsA			(flagsA)
+		, m_flagsB			(flagsB)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		UniformBlock& blockA = m_interface.allocBlock("BlockA");
+		blockA.addUniform(Uniform("a", VarType(glu::TYPE_FLOAT, PRECISION_HIGH)));
+		blockA.addUniform(Uniform("b", VarType(glu::TYPE_UINT_VEC3, PRECISION_LOW), UNUSED_BOTH));
+		blockA.addUniform(Uniform("c", VarType(glu::TYPE_FLOAT_MAT2, PRECISION_MEDIUM)));
+		blockA.setInstanceName("blockA");
+		blockA.setFlags(m_flagsA);
+
+		UniformBlock& blockB = m_interface.allocBlock("BlockB");
+		blockB.addUniform(Uniform("a", VarType(glu::TYPE_FLOAT_MAT3, PRECISION_MEDIUM)));
+		blockB.addUniform(Uniform("b", VarType(glu::TYPE_INT_VEC2, PRECISION_LOW)));
+		blockB.addUniform(Uniform("c", VarType(glu::TYPE_FLOAT_VEC4, PRECISION_HIGH), UNUSED_BOTH));
+		blockB.addUniform(Uniform("d", VarType(glu::TYPE_BOOL, 0)));
+		blockB.setInstanceName("blockB");
+		blockB.setFlags(m_flagsB);
+
+		if (m_numInstances > 0)
+		{
+			blockA.setArraySize(m_numInstances);
+			blockB.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_flagsA;
+	deUint32	m_flagsB;
+	int			m_numInstances;
+};
+
+class BlockMultiNestedStructCase : public UniformBlockCase
+{
+public:
+	BlockMultiNestedStructCase (Context& context, const char* name, const char* description, deUint32 flagsA, deUint32 flagsB, BufferMode bufferMode, int numInstances)
+		: UniformBlockCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_300_ES, bufferMode)
+		, m_flagsA			(flagsA)
+		, m_flagsB			(flagsB)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_FLOAT_MAT3, PRECISION_LOW));
+		typeS.addMember("b", VarType(VarType(glu::TYPE_INT_VEC2, PRECISION_MEDIUM), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, PRECISION_HIGH));
+
+		StructType& typeT = m_interface.allocStruct("T");
+		typeT.addMember("a", VarType(glu::TYPE_UINT, PRECISION_MEDIUM), UNUSED_BOTH);
+		typeT.addMember("b", VarType(&typeS));
+		typeT.addMember("c", VarType(glu::TYPE_BOOL_VEC4, 0));
+
+		UniformBlock& blockA = m_interface.allocBlock("BlockA");
+		blockA.addUniform(Uniform("a", VarType(glu::TYPE_FLOAT, PRECISION_HIGH)));
+		blockA.addUniform(Uniform("b", VarType(&typeS)));
+		blockA.addUniform(Uniform("c", VarType(glu::TYPE_UINT_VEC3, PRECISION_LOW), UNUSED_BOTH));
+		blockA.setInstanceName("blockA");
+		blockA.setFlags(m_flagsA);
+
+		UniformBlock& blockB = m_interface.allocBlock("BlockB");
+		blockB.addUniform(Uniform("a", VarType(glu::TYPE_FLOAT_MAT2, PRECISION_MEDIUM)));
+		blockB.addUniform(Uniform("b", VarType(&typeT)));
+		blockB.addUniform(Uniform("c", VarType(glu::TYPE_BOOL_VEC4, 0), UNUSED_BOTH));
+		blockB.addUniform(Uniform("d", VarType(glu::TYPE_BOOL, 0)));
+		blockB.setInstanceName("blockB");
+		blockB.setFlags(m_flagsB);
+
+		if (m_numInstances > 0)
+		{
+			blockA.setArraySize(m_numInstances);
+			blockB.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_flagsA;
+	deUint32	m_flagsB;
+	int			m_numInstances;
+};
+
+UniformBlockTests::UniformBlockTests (Context& context)
+	: TestCaseGroup(context, "ubo", "Uniform Block tests")
+{
+}
+
+UniformBlockTests::~UniformBlockTests (void)
+{
+}
+
+void UniformBlockTests::init (void)
+{
+	static const glu::DataType basicTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_FLOAT_VEC2,
+		glu::TYPE_FLOAT_VEC3,
+		glu::TYPE_FLOAT_VEC4,
+		glu::TYPE_INT,
+		glu::TYPE_INT_VEC2,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_INT_VEC4,
+		glu::TYPE_UINT,
+		glu::TYPE_UINT_VEC2,
+		glu::TYPE_UINT_VEC3,
+		glu::TYPE_UINT_VEC4,
+		glu::TYPE_BOOL,
+		glu::TYPE_BOOL_VEC2,
+		glu::TYPE_BOOL_VEC3,
+		glu::TYPE_BOOL_VEC4,
+		glu::TYPE_FLOAT_MAT2,
+		glu::TYPE_FLOAT_MAT3,
+		glu::TYPE_FLOAT_MAT4,
+		glu::TYPE_FLOAT_MAT2X3,
+		glu::TYPE_FLOAT_MAT2X4,
+		glu::TYPE_FLOAT_MAT3X2,
+		glu::TYPE_FLOAT_MAT3X4,
+		glu::TYPE_FLOAT_MAT4X2,
+		glu::TYPE_FLOAT_MAT4X3
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		flags;
+	} precisionFlags[] =
+	{
+		{ "lowp",		PRECISION_LOW		},
+		{ "mediump",	PRECISION_MEDIUM	},
+		{ "highp",		PRECISION_HIGH		}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		flags;
+	} layoutFlags[] =
+	{
+		{ "shared",		LAYOUT_SHARED	},
+		{ "packed",		LAYOUT_PACKED	},
+		{ "std140",		LAYOUT_STD140	}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		flags;
+	} matrixFlags[] =
+	{
+		{ "row_major",		LAYOUT_ROW_MAJOR	},
+		{ "column_major",	LAYOUT_COLUMN_MAJOR }
+	};
+
+	static const struct
+	{
+		const char*							name;
+		UniformBlockCase::BufferMode		mode;
+	} bufferModes[] =
+	{
+		{ "per_block_buffer",	UniformBlockCase::BUFFERMODE_PER_BLOCK },
+		{ "single_buffer",		UniformBlockCase::BUFFERMODE_SINGLE	}
+	};
+
+	// ubo.single_basic_type
+	{
+		tcu::TestCaseGroup* singleBasicTypeGroup = new tcu::TestCaseGroup(m_testCtx, "single_basic_type", "Single basic variable in single buffer");
+		addChild(singleBasicTypeGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			singleBasicTypeGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				glu::DataType	type		= basicTypes[basicTypeNdx];
+				const char*		typeName	= glu::getDataTypeName(type);
+
+				if (glu::isDataTypeBoolOrBVec(type))
+					createBlockBasicTypeCases(layoutGroup, m_context, typeName, VarType(type, 0), layoutFlags[layoutFlagNdx].flags);
+				else
+				{
+					for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisionFlags); precNdx++)
+						createBlockBasicTypeCases(layoutGroup, m_context, (string(precisionFlags[precNdx].name) + "_" + typeName).c_str(),
+												  VarType(type, precisionFlags[precNdx].flags), layoutFlags[layoutFlagNdx].flags);
+				}
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+					{
+						for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisionFlags); precNdx++)
+							createBlockBasicTypeCases(layoutGroup, m_context, (string(matrixFlags[matFlagNdx].name) + "_" + precisionFlags[precNdx].name + "_" + typeName).c_str(),
+													  VarType(type, precisionFlags[precNdx].flags), layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags);
+					}
+				}
+			}
+		}
+	}
+
+	// ubo.single_basic_array
+	{
+		tcu::TestCaseGroup* singleBasicArrayGroup = new tcu::TestCaseGroup(m_testCtx, "single_basic_array", "Single basic array variable in single buffer");
+		addChild(singleBasicArrayGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			singleBasicArrayGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				glu::DataType	type		= basicTypes[basicTypeNdx];
+				const char*		typeName	= glu::getDataTypeName(type);
+				const int		arraySize	= 3;
+
+				createBlockBasicTypeCases(layoutGroup, m_context, typeName,
+										  VarType(VarType(type, glu::isDataTypeBoolOrBVec(type) ? 0 : PRECISION_HIGH), arraySize),
+										  layoutFlags[layoutFlagNdx].flags);
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+						createBlockBasicTypeCases(layoutGroup, m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(),
+												  VarType(VarType(type, PRECISION_HIGH), arraySize),
+												  layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags);
+				}
+			}
+		}
+	}
+
+	// ubo.single_struct
+	{
+		tcu::TestCaseGroup* singleStructGroup = new tcu::TestCaseGroup(m_testCtx, "single_struct", "Single struct in uniform block");
+		addChild(singleStructGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			singleStructGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == UniformBlockCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockSingleStructCase(m_context, (baseName + "_vertex").c_str(),	"", baseFlags|DECLARE_VERTEX,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+					modeGroup->addChild(new BlockSingleStructCase(m_context, (baseName + "_fragment").c_str(),	"", baseFlags|DECLARE_FRAGMENT,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+
+					if (!(baseFlags & LAYOUT_PACKED))
+						modeGroup->addChild(new BlockSingleStructCase(m_context, (baseName + "_both").c_str(),		"", baseFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.single_struct_array
+	{
+		tcu::TestCaseGroup* singleStructArrayGroup = new tcu::TestCaseGroup(m_testCtx, "single_struct_array", "Struct array in one uniform block");
+		addChild(singleStructArrayGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			singleStructArrayGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == UniformBlockCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockSingleStructArrayCase(m_context, (baseName + "_vertex").c_str(),	"", baseFlags|DECLARE_VERTEX,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+					modeGroup->addChild(new BlockSingleStructArrayCase(m_context, (baseName + "_fragment").c_str(),	"", baseFlags|DECLARE_FRAGMENT,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+
+					if (!(baseFlags & LAYOUT_PACKED))
+						modeGroup->addChild(new BlockSingleStructArrayCase(m_context, (baseName + "_both").c_str(),		"", baseFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.single_nested_struct
+	{
+		tcu::TestCaseGroup* singleNestedStructGroup = new tcu::TestCaseGroup(m_testCtx, "single_nested_struct", "Nested struct in one uniform block");
+		addChild(singleNestedStructGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			singleNestedStructGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == UniformBlockCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockSingleNestedStructCase(m_context, (baseName + "_vertex").c_str(),		"", baseFlags|DECLARE_VERTEX,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+					modeGroup->addChild(new BlockSingleNestedStructCase(m_context, (baseName + "_fragment").c_str(),	"", baseFlags|DECLARE_FRAGMENT,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+
+					if (!(baseFlags & LAYOUT_PACKED))
+						modeGroup->addChild(new BlockSingleNestedStructCase(m_context, (baseName + "_both").c_str(),		"", baseFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.single_nested_struct_array
+	{
+		tcu::TestCaseGroup* singleNestedStructArrayGroup = new tcu::TestCaseGroup(m_testCtx, "single_nested_struct_array", "Nested struct array in one uniform block");
+		addChild(singleNestedStructArrayGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			singleNestedStructArrayGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == UniformBlockCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockSingleNestedStructArrayCase(m_context, (baseName + "_vertex").c_str(),		"", baseFlags|DECLARE_VERTEX,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+					modeGroup->addChild(new BlockSingleNestedStructArrayCase(m_context, (baseName + "_fragment").c_str(),	"", baseFlags|DECLARE_FRAGMENT,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+
+					if (!(baseFlags & LAYOUT_PACKED))
+						modeGroup->addChild(new BlockSingleNestedStructArrayCase(m_context, (baseName + "_both").c_str(),		"", baseFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.instance_array_basic_type
+	{
+		tcu::TestCaseGroup* instanceArrayBasicTypeGroup = new tcu::TestCaseGroup(m_testCtx, "instance_array_basic_type", "Single basic variable in instance array");
+		addChild(instanceArrayBasicTypeGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			instanceArrayBasicTypeGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				glu::DataType	type			= basicTypes[basicTypeNdx];
+				const char*		typeName		= glu::getDataTypeName(type);
+				const int		numInstances	= 3;
+
+				createBlockBasicTypeCases(layoutGroup, m_context, typeName,
+										  VarType(type, glu::isDataTypeBoolOrBVec(type) ? 0 : PRECISION_HIGH),
+										  layoutFlags[layoutFlagNdx].flags, numInstances);
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+						createBlockBasicTypeCases(layoutGroup, m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(),
+												  VarType(type, PRECISION_HIGH), layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags,
+												  numInstances);
+				}
+			}
+		}
+	}
+
+	// ubo.multi_basic_types
+	{
+		tcu::TestCaseGroup* multiBasicTypesGroup = new tcu::TestCaseGroup(m_testCtx, "multi_basic_types", "Multiple buffers with basic types");
+		addChild(multiBasicTypesGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			multiBasicTypesGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockMultiBasicTypesCase(m_context, (baseName + "_vertex").c_str(),		"", baseFlags|DECLARE_VERTEX,					baseFlags|DECLARE_VERTEX,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+					modeGroup->addChild(new BlockMultiBasicTypesCase(m_context, (baseName + "_fragment").c_str(),	"", baseFlags|DECLARE_FRAGMENT,					baseFlags|DECLARE_FRAGMENT,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+
+					if (!(baseFlags & LAYOUT_PACKED))
+						modeGroup->addChild(new BlockMultiBasicTypesCase(m_context, (baseName + "_both").c_str(),		"", baseFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	baseFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	bufferModes[modeNdx].mode, isArray ? 3 : 0));
+
+					modeGroup->addChild(new BlockMultiBasicTypesCase(m_context, (baseName + "_mixed").c_str(),		"", baseFlags|DECLARE_VERTEX,					baseFlags|DECLARE_FRAGMENT,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.multi_nested_struct
+	{
+		tcu::TestCaseGroup* multiNestedStructGroup = new tcu::TestCaseGroup(m_testCtx, "multi_nested_struct", "Multiple buffers with nested structs");
+		addChild(multiNestedStructGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			multiNestedStructGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockMultiNestedStructCase(m_context, (baseName + "_vertex").c_str(),	"", baseFlags|DECLARE_VERTEX,					baseFlags|DECLARE_VERTEX,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+					modeGroup->addChild(new BlockMultiNestedStructCase(m_context, (baseName + "_fragment").c_str(),	"", baseFlags|DECLARE_FRAGMENT,					baseFlags|DECLARE_FRAGMENT,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+
+					if (!(baseFlags & LAYOUT_PACKED))
+						modeGroup->addChild(new BlockMultiNestedStructCase(m_context, (baseName + "_both").c_str(),		"", baseFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	baseFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	bufferModes[modeNdx].mode, isArray ? 3 : 0));
+
+					modeGroup->addChild(new BlockMultiNestedStructCase(m_context, (baseName + "_mixed").c_str(),	"", baseFlags|DECLARE_VERTEX,					baseFlags|DECLARE_FRAGMENT,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.random
+	{
+		const deUint32	allShaders		= FEATURE_VERTEX_BLOCKS|FEATURE_FRAGMENT_BLOCKS|FEATURE_SHARED_BLOCKS;
+		const deUint32	allLayouts		= FEATURE_PACKED_LAYOUT|FEATURE_SHARED_LAYOUT|FEATURE_STD140_LAYOUT;
+		const deUint32	allBasicTypes	= FEATURE_VECTORS|FEATURE_MATRICES;
+		const deUint32	unused			= FEATURE_UNUSED_MEMBERS|FEATURE_UNUSED_UNIFORMS;
+		const deUint32	matFlags		= FEATURE_MATRIX_LAYOUT;
+		const deUint32	allFeatures		= ~FEATURE_ARRAYS_OF_ARRAYS;
+
+		tcu::TestCaseGroup* randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Random Uniform Block cases");
+		addChild(randomGroup);
+
+		// Basic types.
+		createRandomCaseGroup(randomGroup, m_context, "scalar_types",	"Scalar types only, per-block buffers",				UniformBlockCase::BUFFERMODE_PER_BLOCK,	allShaders|allLayouts|unused,										25, 0);
+		createRandomCaseGroup(randomGroup, m_context, "vector_types",	"Scalar and vector types only, per-block buffers",	UniformBlockCase::BUFFERMODE_PER_BLOCK,	allShaders|allLayouts|unused|FEATURE_VECTORS,						25, 25);
+		createRandomCaseGroup(randomGroup, m_context, "basic_types",	"All basic types, per-block buffers",				UniformBlockCase::BUFFERMODE_PER_BLOCK, allShaders|allLayouts|unused|allBasicTypes|matFlags,				25, 50);
+		createRandomCaseGroup(randomGroup, m_context, "basic_arrays",	"Arrays, per-block buffers",						UniformBlockCase::BUFFERMODE_PER_BLOCK,	allShaders|allLayouts|unused|allBasicTypes|matFlags|FEATURE_ARRAYS,	25, 50);
+
+		createRandomCaseGroup(randomGroup, m_context, "basic_instance_arrays",					"Basic instance arrays, per-block buffers",				UniformBlockCase::BUFFERMODE_PER_BLOCK,	allShaders|allLayouts|unused|allBasicTypes|matFlags|FEATURE_INSTANCE_ARRAYS,								25, 75);
+		createRandomCaseGroup(randomGroup, m_context, "nested_structs",							"Nested structs, per-block buffers",					UniformBlockCase::BUFFERMODE_PER_BLOCK,	allShaders|allLayouts|unused|allBasicTypes|matFlags|FEATURE_STRUCTS,										25, 100);
+		createRandomCaseGroup(randomGroup, m_context, "nested_structs_arrays",					"Nested structs, arrays, per-block buffers",			UniformBlockCase::BUFFERMODE_PER_BLOCK,	allShaders|allLayouts|unused|allBasicTypes|matFlags|FEATURE_STRUCTS|FEATURE_ARRAYS,							25, 150);
+		createRandomCaseGroup(randomGroup, m_context, "nested_structs_instance_arrays",			"Nested structs, instance arrays, per-block buffers",	UniformBlockCase::BUFFERMODE_PER_BLOCK,	allShaders|allLayouts|unused|allBasicTypes|matFlags|FEATURE_STRUCTS|FEATURE_INSTANCE_ARRAYS,				25, 125);
+		createRandomCaseGroup(randomGroup, m_context, "nested_structs_arrays_instance_arrays",	"Nested structs, instance arrays, per-block buffers",	UniformBlockCase::BUFFERMODE_PER_BLOCK,	allShaders|allLayouts|unused|allBasicTypes|matFlags|FEATURE_STRUCTS|FEATURE_ARRAYS|FEATURE_INSTANCE_ARRAYS,	25, 175);
+
+		createRandomCaseGroup(randomGroup, m_context, "all_per_block_buffers",	"All random features, per-block buffers",	UniformBlockCase::BUFFERMODE_PER_BLOCK,	allFeatures,	50, 200);
+		createRandomCaseGroup(randomGroup, m_context, "all_shared_buffer",		"All random features, shared buffer",		UniformBlockCase::BUFFERMODE_SINGLE,	allFeatures,	50, 250);
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fUniformBlockTests.hpp b/modules/gles3/functional/es3fUniformBlockTests.hpp
new file mode 100644
index 0000000..6ce251a
--- /dev/null
+++ b/modules/gles3/functional/es3fUniformBlockTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FUNIFORMBLOCKTESTS_HPP
+#define _ES3FUNIFORMBLOCKTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Uniform block tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class UniformBlockTests : public TestCaseGroup
+{
+public:
+							UniformBlockTests		(Context& context);
+							~UniformBlockTests		(void);
+
+	void					init					(void);
+
+private:
+							UniformBlockTests		(const UniformBlockTests& other);
+	UniformBlockTests&		operator=				(const UniformBlockTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FUNIFORMBLOCKTESTS_HPP
diff --git a/modules/gles3/functional/es3fVertexArrayObjectTests.cpp b/modules/gles3/functional/es3fVertexArrayObjectTests.cpp
new file mode 100644
index 0000000..776239e
--- /dev/null
+++ b/modules/gles3/functional/es3fVertexArrayObjectTests.cpp
@@ -0,0 +1,1615 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array object tests
+ *//*--------------------------------------------------------------------*/
+#include "es3fVertexArrayObjectTests.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "deRandom.hpp"
+#include "deString.h"
+#include "deMemory.h"
+
+#include <vector>
+#include <string>
+#include <memory>
+
+#include "glw.h"
+
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+namespace
+{
+struct Attribute
+{
+				Attribute (void);
+	GLboolean	enabled;
+	GLint		size;
+	GLint		stride;
+	GLenum		type;
+	GLboolean	integer;
+	GLint		divisor;
+	GLint		offset;
+	GLboolean	normalized;
+
+	int			bufferNdx;
+};
+
+struct VertexArrayState
+{
+						VertexArrayState	(void);
+
+	vector<Attribute>	attributes;
+	int					elementArrayBuffer;
+};
+
+VertexArrayState::VertexArrayState (void)
+	: elementArrayBuffer(-1)
+{
+}
+
+Attribute::Attribute (void)
+	: enabled		(GL_FALSE)
+	, size			(1)
+	, stride		(0)
+	, type			(GL_FLOAT)
+	, integer		(GL_FALSE)
+	, divisor		(0)
+	, offset		(0)
+	, normalized	(GL_FALSE)
+	, bufferNdx		(0)
+{
+}
+
+struct BufferSpec
+{
+	int		count;
+	int		size;
+	int		componentCount;
+	int		stride;
+	int		offset;
+
+	GLenum	type;
+
+	int		intRangeMin;
+	int		intRangeMax;
+
+	float	floatRangeMin;
+	float	floatRangeMax;
+};
+
+struct Spec
+{
+						Spec	(void);
+
+	int					count;
+	int					instances;
+	bool				useDrawElements;
+	GLenum				indexType;
+	int					indexOffset;
+	int					indexRangeMin;
+	int					indexRangeMax;
+	int					indexCount;
+	VertexArrayState	state;
+	VertexArrayState	vao;
+	vector<BufferSpec>	buffers;
+};
+
+Spec::Spec (void)
+	: count				(-1)
+	, instances			(-1)
+	, useDrawElements	(false)
+	, indexType			(GL_NONE)
+	, indexOffset		(-1)
+	, indexRangeMin		(-1)
+	, indexRangeMax		(-1)
+	, indexCount		(-1)
+{
+}
+
+} // anonymous
+
+class VertexArrayObjectTest : public TestCase
+{
+public:
+
+							VertexArrayObjectTest	(Context& context, const Spec& spec, const char* name, const char* description);
+							~VertexArrayObjectTest	(void);
+	virtual void			init					(void);
+	virtual void			deinit					(void);
+	virtual IterateResult	iterate					(void);
+
+private:
+	Spec					m_spec;
+	tcu::TestLog&			m_log;
+	vector<GLuint>			m_buffers;
+	glu::ShaderProgram*		m_vaoProgram;
+	glu::ShaderProgram*		m_stateProgram;
+	de::Random				m_random;
+	deUint8*				m_indices;
+
+	void					logVertexArrayState (tcu::TestLog& log, const VertexArrayState& state, const std::string& msg);
+	deUint8*				createRandomBufferData	(const BufferSpec& buffer);
+	deUint8*				generateIndices			(void);
+	glu::ShaderProgram*		createProgram			(const VertexArrayState& state);
+	void					setState				(const VertexArrayState& state);
+	void					render					(tcu::Surface& vaoResult, tcu::Surface& defaultResult);
+	void					makeDrawCall			(const VertexArrayState& state);
+	void					genReferences			(tcu::Surface& vaoRef, tcu::Surface& defaultRef);
+
+							VertexArrayObjectTest	(const VertexArrayObjectTest&);
+	VertexArrayObjectTest&	operator=				(const VertexArrayObjectTest&);
+};
+
+VertexArrayObjectTest::VertexArrayObjectTest (Context& context, const Spec& spec, const char* name, const char* description)
+	: TestCase			(context, name, description)
+	, m_spec			(spec)
+	, m_log				(context.getTestContext().getLog())
+	, m_vaoProgram		(NULL)
+	, m_stateProgram	(NULL)
+	, m_random			(deStringHash(name))
+	, m_indices			(NULL)
+{
+	// Makes zero to zero mapping for buffers
+	m_buffers.push_back(0);
+}
+
+VertexArrayObjectTest::~VertexArrayObjectTest (void)
+{
+}
+
+void VertexArrayObjectTest::logVertexArrayState (tcu::TestLog& log, const VertexArrayState& state, const std::string& msg)
+{
+	std::stringstream message;
+
+	message << msg << "\n";
+	message << "GL_ELEMENT_ARRAY_BUFFER : " << state.elementArrayBuffer << "\n";
+
+	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
+	{
+		message
+		<< "attribute : " << attribNdx << "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_ENABLED : " << (state.attributes[attribNdx].enabled ? "GL_TRUE" : "GL_FALSE") <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_SIZE : " << state.attributes[attribNdx].size <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_STRIDE : " << state.attributes[attribNdx].stride <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_TYPE : " << state.attributes[attribNdx].type <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_NORMALIZED : " << (state.attributes[attribNdx].normalized ? "GL_TRUE" : "GL_FALSE") <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_INTEGER : " << (state.attributes[attribNdx].integer ? "GL_TRUE" : "GL_FALSE") <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_DIVISOR : " << state.attributes[attribNdx].divisor <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_POINTER : " << state.attributes[attribNdx].offset <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : " << m_buffers[state.attributes[attribNdx].bufferNdx] <<  "\n";
+	}
+	log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;
+}
+
+
+void VertexArrayObjectTest::init (void)
+{
+	// \note [mika] Index 0 is reserved for 0 buffer
+	for (int bufferNdx = 0; bufferNdx < (int)m_spec.buffers.size(); bufferNdx++)
+	{
+		deUint8* data = createRandomBufferData(m_spec.buffers[bufferNdx]);
+
+		try
+		{
+			GLuint buffer;
+			GLU_CHECK_CALL(glGenBuffers(1, &buffer));
+			m_buffers.push_back(buffer);
+
+			GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, buffer));
+			GLU_CHECK_CALL(glBufferData(GL_ARRAY_BUFFER, m_spec.buffers[bufferNdx].size, data, GL_DYNAMIC_DRAW));
+			GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
+
+		} catch (...) {
+			delete[] data;
+			throw;
+		}
+
+		delete[] data;
+	}
+
+	m_vaoProgram	= createProgram(m_spec.vao);
+	m_log << tcu::TestLog::Message << "Program used with Vertex Array Object" << tcu::TestLog::EndMessage;
+	m_log << *m_vaoProgram;
+	m_stateProgram	= createProgram(m_spec.state);
+	m_log << tcu::TestLog::Message << "Program used with Vertex Array State" << tcu::TestLog::EndMessage;
+	m_log << *m_stateProgram;
+
+	if (!m_vaoProgram->isOk() || !m_stateProgram->isOk())
+		TCU_FAIL("Failed to compile shaders");
+
+	if (m_spec.useDrawElements && (m_spec.vao.elementArrayBuffer == 0 || m_spec.state.elementArrayBuffer == 0))
+		m_indices = generateIndices();
+}
+
+void VertexArrayObjectTest::deinit (void)
+{
+	GLU_CHECK_CALL(glDeleteBuffers((GLsizei)m_buffers.size(), &(m_buffers[0])));
+	m_buffers.clear();
+	delete m_vaoProgram;
+	delete m_stateProgram;
+	delete[] m_indices;
+}
+
+deUint8* VertexArrayObjectTest::generateIndices (void)
+{
+	int typeSize = 0;
+	switch (m_spec.indexType)
+	{
+		case GL_UNSIGNED_INT:	typeSize = sizeof(GLuint);		break;
+		case GL_UNSIGNED_SHORT:	typeSize = sizeof(GLushort);	break;
+		case GL_UNSIGNED_BYTE:	typeSize = sizeof(GLubyte);		break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	deUint8* indices = new deUint8[m_spec.indexCount * typeSize];
+
+	for (int i = 0; i < m_spec.indexCount; i++)
+	{
+		deUint8* pos = indices + typeSize * i;
+
+		switch (m_spec.indexType)
+		{
+			case GL_UNSIGNED_INT:
+			{
+				GLuint v = (GLuint)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
+				deMemcpy(pos, &v, sizeof(v));
+				break;
+			}
+
+			case GL_UNSIGNED_SHORT:
+			{
+				GLushort v = (GLushort)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
+				deMemcpy(pos, &v, sizeof(v));
+				break;
+			}
+
+			case GL_UNSIGNED_BYTE:
+			{
+				GLubyte v = (GLubyte)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
+				deMemcpy(pos, &v, sizeof(v));
+				break;
+			}
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	return indices;
+}
+
+deUint8* VertexArrayObjectTest::createRandomBufferData (const BufferSpec& buffer)
+{
+	deUint8* data = new deUint8[buffer.size];
+
+	int stride;
+
+	if (buffer.stride != 0)
+	{
+		stride = buffer.stride;
+	}
+	else
+	{
+		switch (buffer.type)
+		{
+			case GL_FLOAT:			stride = buffer.componentCount * sizeof(GLfloat);	break;
+			case GL_INT:			stride = buffer.componentCount * sizeof(GLint);		break;
+			case GL_UNSIGNED_INT:	stride = buffer.componentCount * sizeof(GLuint);	break;
+			case GL_SHORT:			stride = buffer.componentCount * sizeof(GLshort);	break;
+			case GL_UNSIGNED_SHORT:	stride = buffer.componentCount * sizeof(GLushort);	break;
+			case GL_BYTE:			stride = buffer.componentCount * sizeof(GLbyte);	break;
+			case GL_UNSIGNED_BYTE:	stride = buffer.componentCount * sizeof(GLubyte);	break;
+
+			default:
+				stride = 0;
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+
+	deUint8* itr = data;
+
+	for (int pos = 0; pos < buffer.count; pos++)
+	{
+		deUint8* componentItr = itr;
+		for (int componentNdx = 0; componentNdx < buffer.componentCount; componentNdx++)
+		{
+			switch (buffer.type)
+			{
+				case GL_FLOAT:
+				{
+					float v = buffer.floatRangeMin + (buffer.floatRangeMax - buffer.floatRangeMin) * m_random.getFloat();
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_INT:
+				{
+					GLint v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_UNSIGNED_INT:
+				{
+					GLuint v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_SHORT:
+				{
+					GLshort v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_UNSIGNED_SHORT:
+				{
+					GLushort v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_BYTE:
+				{
+					GLbyte v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_UNSIGNED_BYTE:
+				{
+					GLubyte v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				default:
+					DE_ASSERT(false);
+			};
+		}
+
+		itr += stride;
+	}
+
+	return data;
+}
+
+glu::ShaderProgram* VertexArrayObjectTest::createProgram (const VertexArrayState& state)
+{
+	std::stringstream vertexShaderStream;
+	std::stringstream value;
+
+	vertexShaderStream << "#version 300 es\n";
+
+	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
+	{
+		if (state.attributes[attribNdx].integer)
+			vertexShaderStream << "layout(location = " << attribNdx << ") in mediump ivec4 a_attrib" << attribNdx << ";\n";
+		else
+			vertexShaderStream << "layout(location = " << attribNdx << ") in mediump vec4 a_attrib" << attribNdx << ";\n";
+
+		if (state.attributes[attribNdx].integer)
+		{
+			float scale = 0.0f;
+
+			switch (state.attributes[0].type)
+			{
+				case GL_SHORT:			scale  = (1.0f/((1u<<14)-1));	break;
+				case GL_UNSIGNED_SHORT:	scale  = (1.0f/((1u<<15)-1));	break;
+				case GL_INT:			scale  = (1.0f/((1u<<30)-1));	break;
+				case GL_UNSIGNED_INT:	scale  = (1.0f/((1u<<31)-1));	break;
+				case GL_BYTE:			scale  = (1.0f/((1u<<6)-1));	break;
+				case GL_UNSIGNED_BYTE:	scale  = (1.0f/((1u<<7)-1));	break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+			value << (attribNdx != 0 ? " + " : "" ) << scale << " * vec4(a_attrib" << attribNdx << ")";
+		}
+		else if (state.attributes[attribNdx].type != GL_FLOAT && !state.attributes[attribNdx].normalized)
+		{
+			float scale = 0.0f;
+
+			switch (state.attributes[0].type)
+			{
+				case GL_SHORT:			scale  = (0.5f/((1u<<14)-1));	break;
+				case GL_UNSIGNED_SHORT:	scale  = (0.5f/((1u<<15)-1));	break;
+				case GL_INT:			scale  = (0.5f/((1u<<30)-1));	break;
+				case GL_UNSIGNED_INT:	scale  = (0.5f/((1u<<31)-1));	break;
+				case GL_BYTE:			scale  = (0.5f/((1u<<6)-1));	break;
+				case GL_UNSIGNED_BYTE:	scale  = (0.5f/((1u<<7)-1));	break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+			value << (attribNdx != 0 ? " + " : "" ) << scale << " * a_attrib" << attribNdx;
+		}
+		else
+			value << (attribNdx != 0 ? " + " : "" ) << "a_attrib" << attribNdx;
+	}
+
+	vertexShaderStream
+		<< "out mediump vec4 v_value;\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "\tv_value = " << value.str() << ";\n";
+
+	if (state.attributes[0].integer)
+	{
+		float scale = 0.0f;
+
+		switch (state.attributes[0].type)
+		{
+			case GL_SHORT:			scale  = (1.0f/((1u<<14)-1));	break;
+			case GL_UNSIGNED_SHORT:	scale  = (1.0f/((1u<<15)-1));	break;
+			case GL_INT:			scale  = (1.0f/((1u<<30)-1));	break;
+			case GL_UNSIGNED_INT:	scale  = (1.0f/((1u<<31)-1));	break;
+			case GL_BYTE:			scale  = (1.0f/((1u<<6)-1));	break;
+			case GL_UNSIGNED_BYTE:	scale  = (1.0f/((1u<<7)-1));	break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+
+		vertexShaderStream
+			<< "\tgl_Position = vec4(" << scale << " * " <<  "vec3(a_attrib0.xyz), 1.0);\n"
+			<< "}";
+	}
+	else
+	{
+		if (state.attributes[0].normalized || state.attributes[0].type == GL_FLOAT)
+		{
+			vertexShaderStream
+				<< "\tgl_Position = vec4(a_attrib0.xyz, 1.0);\n"
+				<< "}";
+		}
+		else
+		{
+			float scale = 0.0f;
+
+			switch (state.attributes[0].type)
+			{
+				case GL_SHORT:			scale  = (1.0f/((1u<<14)-1));	break;
+				case GL_UNSIGNED_SHORT:	scale  = (1.0f/((1u<<15)-1));	break;
+				case GL_INT:			scale  = (1.0f/((1u<<30)-1));	break;
+				case GL_UNSIGNED_INT:	scale  = (1.0f/((1u<<31)-1));	break;
+				case GL_BYTE:			scale  = (1.0f/((1u<<6)-1));	break;
+				case GL_UNSIGNED_BYTE:	scale  = (1.0f/((1u<<7)-1));	break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+
+			scale *= 0.5;
+
+			vertexShaderStream
+				<< "\tgl_Position = vec4(" << scale << " * " <<  "a_attrib0.xyz, 1.0);\n"
+				<< "}";
+		}
+	}
+
+	const char* fragmentShader =
+	"#version 300 es\n"
+	"in mediump vec4 v_value;\n"
+	"layout(location = 0) out mediump vec4 fragColor;\n"
+	"void main (void)\n"
+	"{\n"
+	"\tfragColor = vec4(v_value.xyz, 1.0);\n"
+	"}";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderStream.str(), fragmentShader));
+}
+
+void VertexArrayObjectTest::setState (const VertexArrayState& state)
+{
+	GLU_CHECK_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffers[state.elementArrayBuffer]));
+
+	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
+	{
+		GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_buffers[state.attributes[attribNdx].bufferNdx]));
+		if (state.attributes[attribNdx].enabled)
+			GLU_CHECK_CALL(glEnableVertexAttribArray(attribNdx));
+		else
+			GLU_CHECK_CALL(glDisableVertexAttribArray(attribNdx));
+
+		if (state.attributes[attribNdx].integer)
+			GLU_CHECK_CALL(glVertexAttribIPointer(attribNdx, state.attributes[attribNdx].size, state.attributes[attribNdx].type, state.attributes[attribNdx].stride, (const GLvoid*)((GLintptr)state.attributes[attribNdx].offset)));
+		else
+			GLU_CHECK_CALL(glVertexAttribPointer(attribNdx, state.attributes[attribNdx].size, state.attributes[attribNdx].type, state.attributes[attribNdx].normalized, state.attributes[attribNdx].stride, (const GLvoid*)((GLintptr)state.attributes[attribNdx].offset)));
+
+		GLU_CHECK_CALL(glVertexAttribDivisor(attribNdx, state.attributes[attribNdx].divisor));
+	}
+}
+
+void VertexArrayObjectTest::makeDrawCall (const VertexArrayState& state)
+{
+	GLU_CHECK_CALL(glClearColor(0.7f, 0.7f, 0.7f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	if (m_spec.useDrawElements)
+	{
+		if (state.elementArrayBuffer == 0)
+		{
+			if (m_spec.instances == 0)
+				GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, m_spec.count, m_spec.indexType, m_indices));
+			else
+				GLU_CHECK_CALL(glDrawElementsInstanced(GL_TRIANGLES, m_spec.count, m_spec.indexType, m_indices, m_spec.instances));
+		}
+		else
+		{
+			if (m_spec.instances == 0)
+				GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, m_spec.count, m_spec.indexType, (GLvoid*)((GLintptr)m_spec.indexOffset)));
+			else
+				GLU_CHECK_CALL(glDrawElementsInstanced(GL_TRIANGLES, m_spec.count, m_spec.indexType, (GLvoid*)((GLintptr)m_spec.indexOffset), m_spec.instances));
+		}
+	}
+	else
+	{
+		if (m_spec.instances == 0)
+			GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, m_spec.count));
+		else
+			GLU_CHECK_CALL(glDrawArraysInstanced(GL_TRIANGLES, 0, m_spec.count, m_spec.instances));
+	}
+}
+
+void VertexArrayObjectTest::render (tcu::Surface& vaoResult, tcu::Surface& defaultResult)
+{
+	GLuint vao = 0;
+
+	GLU_CHECK_CALL(glGenVertexArrays(1, &vao));
+	GLU_CHECK_CALL(glBindVertexArray(vao));
+	setState(m_spec.vao);
+	GLU_CHECK_CALL(glBindVertexArray(0));
+
+	setState(m_spec.state);
+
+	GLU_CHECK_CALL(glBindVertexArray(vao));
+	GLU_CHECK_CALL(glUseProgram(m_vaoProgram->getProgram()));
+	makeDrawCall(m_spec.vao);
+	glu::readPixels(m_context.getRenderContext(), 0, 0, vaoResult.getAccess());
+	setState(m_spec.vao);
+	GLU_CHECK_CALL(glBindVertexArray(0));
+
+	GLU_CHECK_CALL(glUseProgram(m_stateProgram->getProgram()));
+	makeDrawCall(m_spec.state);
+	glu::readPixels(m_context.getRenderContext(), 0, 0, defaultResult.getAccess());
+}
+
+void VertexArrayObjectTest::genReferences (tcu::Surface& vaoRef, tcu::Surface& defaultRef)
+{
+	setState(m_spec.vao);
+	GLU_CHECK_CALL(glUseProgram(m_vaoProgram->getProgram()));
+	makeDrawCall(m_spec.vao);
+	glu::readPixels(m_context.getRenderContext(), 0, 0, vaoRef.getAccess());
+
+	setState(m_spec.state);
+	GLU_CHECK_CALL(glUseProgram(m_stateProgram->getProgram()));
+	makeDrawCall(m_spec.state);
+	glu::readPixels(m_context.getRenderContext(), 0, 0, defaultRef.getAccess());
+}
+
+TestCase::IterateResult VertexArrayObjectTest::iterate (void)
+{
+	tcu::Surface	vaoReference	(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
+	tcu::Surface	stateReference	(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
+
+	tcu::Surface	vaoResult		(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
+	tcu::Surface	stateResult		(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
+
+	bool			isOk;
+
+	logVertexArrayState(m_log, m_spec.vao, "Vertex Array Object State");
+	logVertexArrayState(m_log, m_spec.state, "OpenGL Vertex Array State");
+	genReferences(stateReference, vaoReference);
+	render(stateResult, vaoResult);
+
+	isOk = tcu::pixelThresholdCompare (m_log, "Results", "Comparison result from rendering with Vertex Array State", stateReference, stateResult, tcu::RGBA(0,0,0,0), tcu::COMPARE_LOG_RESULT);
+	isOk = isOk && tcu::pixelThresholdCompare (m_log, "Results", "Comparison result from rendering with Vertex Array Object", vaoReference, vaoResult, tcu::RGBA(0,0,0,0), tcu::COMPARE_LOG_RESULT);
+
+	if (isOk)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		return STOP;
+	}
+}
+
+class MultiVertexArrayObjectTest : public TestCase
+{
+public:
+
+							MultiVertexArrayObjectTest	(Context& context, const char* name, const char* description);
+							~MultiVertexArrayObjectTest	(void);
+	virtual void			init						(void);
+	virtual void			deinit						(void);
+	virtual IterateResult	iterate						(void);
+
+private:
+	Spec					m_spec;
+	tcu::TestLog&			m_log;
+	vector<GLuint>			m_buffers;
+	glu::ShaderProgram*		m_vaoProgram;
+	glu::ShaderProgram*		m_stateProgram;
+	de::Random				m_random;
+	deUint8*				m_indices;
+
+	void					logVertexArrayState			(tcu::TestLog& log, const VertexArrayState& state, const std::string& msg);
+	deUint8*				createRandomBufferData		(const BufferSpec& buffer);
+	deUint8*				generateIndices				(void);
+	glu::ShaderProgram*		createProgram				(const VertexArrayState& state);
+	void					setState					(const VertexArrayState& state);
+	void					render						(tcu::Surface& vaoResult, tcu::Surface& defaultResult);
+	void					makeDrawCall				(const VertexArrayState& state);
+	void					genReferences				(tcu::Surface& vaoRef, tcu::Surface& defaultRef);
+
+							MultiVertexArrayObjectTest	(const MultiVertexArrayObjectTest&);
+	MultiVertexArrayObjectTest&	operator=				(const MultiVertexArrayObjectTest&);
+};
+
+MultiVertexArrayObjectTest::MultiVertexArrayObjectTest (Context& context, const char* name, const char* description)
+	: TestCase			(context, name, description)
+	, m_log				(context.getTestContext().getLog())
+	, m_vaoProgram		(NULL)
+	, m_stateProgram	(NULL)
+	, m_random			(deStringHash(name))
+	, m_indices			(NULL)
+{
+	// Makes zero to zero mapping for buffers
+	m_buffers.push_back(0);
+}
+
+MultiVertexArrayObjectTest::~MultiVertexArrayObjectTest (void)
+{
+}
+
+void MultiVertexArrayObjectTest::logVertexArrayState (tcu::TestLog& log, const VertexArrayState& state, const std::string& msg)
+{
+	std::stringstream message;
+
+	message << msg << "\n";
+	message << "GL_ELEMENT_ARRAY_BUFFER : " << state.elementArrayBuffer << "\n";
+
+	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
+	{
+		message
+		<< "attribute : " << attribNdx << "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_ENABLED : " << (state.attributes[attribNdx].enabled ? "GL_TRUE" : "GL_FALSE") <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_SIZE : " << state.attributes[attribNdx].size <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_STRIDE : " << state.attributes[attribNdx].stride <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_TYPE : " << state.attributes[attribNdx].type <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_NORMALIZED : " << (state.attributes[attribNdx].normalized ? "GL_TRUE" : "GL_FALSE") <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_INTEGER : " << (state.attributes[attribNdx].integer ? "GL_TRUE" : "GL_FALSE") <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_DIVISOR : " << state.attributes[attribNdx].divisor <<  "\n"
+		<< "\tGL_VERTEX_ATTRIB_ARRAY_POINTER : " << state.attributes[attribNdx].offset <<  "\n"
+		<< "\t GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : " << m_buffers[state.attributes[attribNdx].bufferNdx] <<  "\n";
+	}
+	log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;
+}
+
+
+void MultiVertexArrayObjectTest::init (void)
+{
+	GLint attribCount;
+
+	GLU_CHECK_CALL(glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribCount));
+
+	m_spec.useDrawElements			= false;
+	m_spec.instances				= 0;
+	m_spec.count					= 24;
+	m_spec.indexOffset				= 0;
+	m_spec.indexRangeMin			= 0;
+	m_spec.indexRangeMax			= 0;
+	m_spec.indexType				= GL_NONE;
+	m_spec.indexCount				= 0;
+	m_spec.vao.elementArrayBuffer	= 0;
+	m_spec.state.elementArrayBuffer	= 0;
+
+	for (int attribNdx = 0; attribNdx < attribCount; attribNdx++)
+	{
+		BufferSpec shortCoordBuffer48 = { 48, 2*384, 4, 0, 0, GL_SHORT, -32768, 32768, 0.0f, 0.0f };
+		m_spec.buffers.push_back(shortCoordBuffer48);
+
+		m_spec.state.attributes.push_back(Attribute());
+		m_spec.state.attributes[attribNdx].enabled		= (m_random.getInt(0, 4) == 0) ? GL_FALSE : GL_TRUE;
+		m_spec.state.attributes[attribNdx].size			= m_random.getInt(2,4);
+		m_spec.state.attributes[attribNdx].stride		= 2*m_random.getInt(1, 3);
+		m_spec.state.attributes[attribNdx].type			= GL_SHORT;
+		m_spec.state.attributes[attribNdx].integer		= m_random.getBool();
+		m_spec.state.attributes[attribNdx].divisor		= m_random.getInt(0, 1);
+		m_spec.state.attributes[attribNdx].offset		= 2*m_random.getInt(0, 2);
+		m_spec.state.attributes[attribNdx].normalized	= m_random.getBool();
+		m_spec.state.attributes[attribNdx].bufferNdx	= attribNdx+1;
+
+		if (attribNdx == 0)
+		{
+			m_spec.state.attributes[attribNdx].divisor	= 0;
+			m_spec.state.attributes[attribNdx].enabled	= GL_TRUE;
+			m_spec.state.attributes[attribNdx].size		= 2;
+		}
+
+		m_spec.vao.attributes.push_back(Attribute());
+		m_spec.vao.attributes[attribNdx].enabled		= (m_random.getInt(0, 4) == 0) ? GL_FALSE : GL_TRUE;
+		m_spec.vao.attributes[attribNdx].size			= m_random.getInt(2,4);
+		m_spec.vao.attributes[attribNdx].stride			= 2*m_random.getInt(1, 3);
+		m_spec.vao.attributes[attribNdx].type			= GL_SHORT;
+		m_spec.vao.attributes[attribNdx].integer		= m_random.getBool();
+		m_spec.vao.attributes[attribNdx].divisor		= m_random.getInt(0, 1);
+		m_spec.vao.attributes[attribNdx].offset			= 2*m_random.getInt(0, 2);
+		m_spec.vao.attributes[attribNdx].normalized		= m_random.getBool();
+		m_spec.vao.attributes[attribNdx].bufferNdx		= attribCount - attribNdx;
+
+		if (attribNdx == 0)
+		{
+			m_spec.vao.attributes[attribNdx].divisor	= 0;
+			m_spec.vao.attributes[attribNdx].enabled	= GL_TRUE;
+			m_spec.vao.attributes[attribNdx].size		= 2;
+		}
+
+	}
+
+	// \note [mika] Index 0 is reserved for 0 buffer
+	for (int bufferNdx = 0; bufferNdx < (int)m_spec.buffers.size(); bufferNdx++)
+	{
+		deUint8* data = createRandomBufferData(m_spec.buffers[bufferNdx]);
+
+		try
+		{
+			GLuint buffer;
+			GLU_CHECK_CALL(glGenBuffers(1, &buffer));
+			m_buffers.push_back(buffer);
+
+			GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, buffer));
+			GLU_CHECK_CALL(glBufferData(GL_ARRAY_BUFFER, m_spec.buffers[bufferNdx].size, data, GL_DYNAMIC_DRAW));
+			GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
+
+		} catch (...) {
+			delete[] data;
+			throw;
+		}
+
+		delete[] data;
+	}
+
+	m_vaoProgram	= createProgram(m_spec.vao);
+	m_log << tcu::TestLog::Message << "Program used with Vertex Array Object" << tcu::TestLog::EndMessage;
+	m_log << *m_vaoProgram;
+	m_stateProgram	= createProgram(m_spec.state);
+	m_log << tcu::TestLog::Message << "Program used with Vertex Array State" << tcu::TestLog::EndMessage;
+	m_log << *m_stateProgram;
+
+	if (!m_vaoProgram->isOk() || !m_stateProgram->isOk())
+		TCU_FAIL("Failed to compile shaders");
+
+	if (m_spec.useDrawElements && (m_spec.vao.elementArrayBuffer == 0 || m_spec.state.elementArrayBuffer == 0))
+		m_indices = generateIndices();
+}
+
+void MultiVertexArrayObjectTest::deinit (void)
+{
+	GLU_CHECK_CALL(glDeleteBuffers((GLsizei)m_buffers.size(), &(m_buffers[0])));
+	m_buffers.clear();
+	delete m_vaoProgram;
+	delete m_stateProgram;
+	delete[] m_indices;
+}
+
+deUint8* MultiVertexArrayObjectTest::generateIndices (void)
+{
+	int typeSize = 0;
+	switch (m_spec.indexType)
+	{
+		case GL_UNSIGNED_INT:	typeSize = sizeof(GLuint);		break;
+		case GL_UNSIGNED_SHORT:	typeSize = sizeof(GLushort);	break;
+		case GL_UNSIGNED_BYTE:	typeSize = sizeof(GLubyte);		break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	deUint8* indices = new deUint8[m_spec.indexCount * typeSize];
+
+	for (int i = 0; i < m_spec.indexCount; i++)
+	{
+		deUint8* pos = indices + typeSize * i;
+
+		switch (m_spec.indexType)
+		{
+			case GL_UNSIGNED_INT:
+			{
+				GLuint v = (GLuint)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
+				deMemcpy(pos, &v, sizeof(v));
+				break;
+			}
+
+			case GL_UNSIGNED_SHORT:
+			{
+				GLushort v = (GLushort)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
+				deMemcpy(pos, &v, sizeof(v));
+				break;
+			}
+
+			case GL_UNSIGNED_BYTE:
+			{
+				GLubyte v = (GLubyte)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
+				deMemcpy(pos, &v, sizeof(v));
+				break;
+			}
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	return indices;
+}
+
+deUint8* MultiVertexArrayObjectTest::createRandomBufferData (const BufferSpec& buffer)
+{
+	deUint8* data = new deUint8[buffer.size];
+
+	int stride;
+
+	if (buffer.stride != 0)
+	{
+		stride = buffer.stride;
+	}
+	else
+	{
+		switch (buffer.type)
+		{
+			case GL_FLOAT:			stride = buffer.componentCount * sizeof(GLfloat);	break;
+			case GL_INT:			stride = buffer.componentCount * sizeof(GLint);		break;
+			case GL_UNSIGNED_INT:	stride = buffer.componentCount * sizeof(GLuint);	break;
+			case GL_SHORT:			stride = buffer.componentCount * sizeof(GLshort);	break;
+			case GL_UNSIGNED_SHORT:	stride = buffer.componentCount * sizeof(GLushort);	break;
+			case GL_BYTE:			stride = buffer.componentCount * sizeof(GLbyte);	break;
+			case GL_UNSIGNED_BYTE:	stride = buffer.componentCount * sizeof(GLubyte);	break;
+
+			default:
+				stride = 0;
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+
+	deUint8* itr = data;
+
+	for (int pos = 0; pos < buffer.count; pos++)
+	{
+		deUint8* componentItr = itr;
+		for (int componentNdx = 0; componentNdx < buffer.componentCount; componentNdx++)
+		{
+			switch (buffer.type)
+			{
+				case GL_FLOAT:
+				{
+					float v = buffer.floatRangeMin + (buffer.floatRangeMax - buffer.floatRangeMin) * m_random.getFloat();
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_INT:
+				{
+					GLint v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_UNSIGNED_INT:
+				{
+					GLuint v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_SHORT:
+				{
+					GLshort v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_UNSIGNED_SHORT:
+				{
+					GLushort v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_BYTE:
+				{
+					GLbyte v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				case GL_UNSIGNED_BYTE:
+				{
+					GLubyte v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
+					deMemcpy(componentItr, &v, sizeof(v));
+					componentItr += sizeof(v);
+					break;
+				}
+
+				default:
+					DE_ASSERT(false);
+			};
+		}
+
+		itr += stride;
+	}
+
+	return data;
+}
+
+glu::ShaderProgram* MultiVertexArrayObjectTest::createProgram (const VertexArrayState& state)
+{
+	std::stringstream vertexShaderStream;
+	std::stringstream value;
+
+	vertexShaderStream << "#version 300 es\n";
+
+	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
+	{
+		if (state.attributes[attribNdx].integer)
+			vertexShaderStream << "layout(location = " << attribNdx << ") in mediump ivec4 a_attrib" << attribNdx << ";\n";
+		else
+			vertexShaderStream << "layout(location = " << attribNdx << ") in mediump vec4 a_attrib" << attribNdx << ";\n";
+
+		if (state.attributes[attribNdx].integer)
+		{
+			float scale = 0.0f;
+
+			switch (state.attributes[0].type)
+			{
+				case GL_SHORT:			scale  = (1.0f/((1u<<14)-1));	break;
+				case GL_UNSIGNED_SHORT:	scale  = (1.0f/((1u<<15)-1));	break;
+				case GL_INT:			scale  = (1.0f/((1u<<30)-1));	break;
+				case GL_UNSIGNED_INT:	scale  = (1.0f/((1u<<31)-1));	break;
+				case GL_BYTE:			scale  = (1.0f/((1u<<6)-1));	break;
+				case GL_UNSIGNED_BYTE:	scale  = (1.0f/((1u<<7)-1));	break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+			value << (attribNdx != 0 ? " + " : "" ) << scale << " * vec4(a_attrib" << attribNdx << ")";
+		}
+		else if (state.attributes[attribNdx].type != GL_FLOAT && !state.attributes[attribNdx].normalized)
+		{
+			float scale = 0.0f;
+
+			switch (state.attributes[0].type)
+			{
+				case GL_SHORT:			scale  = (0.5f/((1u<<14)-1));	break;
+				case GL_UNSIGNED_SHORT:	scale  = (0.5f/((1u<<15)-1));	break;
+				case GL_INT:			scale  = (0.5f/((1u<<30)-1));	break;
+				case GL_UNSIGNED_INT:	scale  = (0.5f/((1u<<31)-1));	break;
+				case GL_BYTE:			scale  = (0.5f/((1u<<6)-1));	break;
+				case GL_UNSIGNED_BYTE:	scale  = (0.5f/((1u<<7)-1));	break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+			value << (attribNdx != 0 ? " + " : "" ) << scale << " * a_attrib" << attribNdx;
+		}
+		else
+			value << (attribNdx != 0 ? " + " : "" ) << "a_attrib" << attribNdx;
+	}
+
+	vertexShaderStream
+		<< "out mediump vec4 v_value;\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "\tv_value = " << value.str() << ";\n";
+
+	if (state.attributes[0].integer)
+	{
+		float scale = 0.0f;
+
+		switch (state.attributes[0].type)
+		{
+			case GL_SHORT:			scale  = (1.0f/((1u<<14)-1));	break;
+			case GL_UNSIGNED_SHORT:	scale  = (1.0f/((1u<<15)-1));	break;
+			case GL_INT:			scale  = (1.0f/((1u<<30)-1));	break;
+			case GL_UNSIGNED_INT:	scale  = (1.0f/((1u<<31)-1));	break;
+			case GL_BYTE:			scale  = (1.0f/((1u<<6)-1));	break;
+			case GL_UNSIGNED_BYTE:	scale  = (1.0f/((1u<<7)-1));	break;
+
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+
+		vertexShaderStream
+			<< "\tgl_Position = vec4(" << scale << " * " <<  "a_attrib0.xyz, 1.0);\n"
+			<< "}";
+	}
+	else
+	{
+		if (state.attributes[0].normalized || state.attributes[0].type == GL_FLOAT)
+		{
+			vertexShaderStream
+				<< "\tgl_Position = vec4(a_attrib0.xyz, 1.0);\n"
+				<< "}";
+		}
+		else
+		{
+			float scale = 0.0f;
+
+			switch (state.attributes[0].type)
+			{
+				case GL_SHORT:			scale  = (1.0f/((1u<<14)-1));	break;
+				case GL_UNSIGNED_SHORT:	scale  = (1.0f/((1u<<15)-1));	break;
+				case GL_INT:			scale  = (1.0f/((1u<<30)-1));	break;
+				case GL_UNSIGNED_INT:	scale  = (1.0f/((1u<<31)-1));	break;
+				case GL_BYTE:			scale  = (1.0f/((1u<<6)-1));	break;
+				case GL_UNSIGNED_BYTE:	scale  = (1.0f/((1u<<7)-1));	break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+
+			scale *= 0.5;
+
+			vertexShaderStream
+				<< "\tgl_Position = vec4(" << scale << " * " <<  "vec3(a_attrib0.xyz), 1.0);\n"
+				<< "}";
+		}
+	}
+
+	const char* fragmentShader =
+	"#version 300 es\n"
+	"in mediump vec4 v_value;\n"
+	"layout(location = 0) out mediump vec4 fragColor;\n"
+	"void main (void)\n"
+	"{\n"
+	"\tfragColor = vec4(v_value.xyz, 1.0);\n"
+	"}";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderStream.str(), fragmentShader));
+}
+
+void MultiVertexArrayObjectTest::setState (const VertexArrayState& state)
+{
+	GLU_CHECK_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffers[state.elementArrayBuffer]));
+
+	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
+	{
+		GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_buffers[state.attributes[attribNdx].bufferNdx]));
+		if (state.attributes[attribNdx].enabled)
+			GLU_CHECK_CALL(glEnableVertexAttribArray(attribNdx));
+		else
+			GLU_CHECK_CALL(glDisableVertexAttribArray(attribNdx));
+
+		if (state.attributes[attribNdx].integer)
+			GLU_CHECK_CALL(glVertexAttribIPointer(attribNdx, state.attributes[attribNdx].size, state.attributes[attribNdx].type, state.attributes[attribNdx].stride, (const GLvoid*)((GLintptr)state.attributes[attribNdx].offset)));
+		else
+			GLU_CHECK_CALL(glVertexAttribPointer(attribNdx, state.attributes[attribNdx].size, state.attributes[attribNdx].type, state.attributes[attribNdx].normalized, state.attributes[attribNdx].stride, (const GLvoid*)((GLintptr)state.attributes[attribNdx].offset)));
+
+		GLU_CHECK_CALL(glVertexAttribDivisor(attribNdx, state.attributes[attribNdx].divisor));
+	}
+}
+
+void MultiVertexArrayObjectTest::makeDrawCall (const VertexArrayState& state)
+{
+	GLU_CHECK_CALL(glClearColor(0.7f, 0.7f, 0.7f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+
+	if (m_spec.useDrawElements)
+	{
+		if (state.elementArrayBuffer == 0)
+		{
+			if (m_spec.instances == 0)
+				GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, m_spec.count, m_spec.indexType, m_indices));
+			else
+				GLU_CHECK_CALL(glDrawElementsInstanced(GL_TRIANGLES, m_spec.count, m_spec.indexType, m_indices, m_spec.instances));
+		}
+		else
+		{
+			if (m_spec.instances == 0)
+				GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, m_spec.count, m_spec.indexType, (GLvoid*)((GLintptr)m_spec.indexOffset)));
+			else
+				GLU_CHECK_CALL(glDrawElementsInstanced(GL_TRIANGLES, m_spec.count, m_spec.indexType, (GLvoid*)((GLintptr)m_spec.indexOffset), m_spec.instances));
+		}
+	}
+	else
+	{
+		if (m_spec.instances == 0)
+			GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, m_spec.count));
+		else
+			GLU_CHECK_CALL(glDrawArraysInstanced(GL_TRIANGLES, 0, m_spec.count, m_spec.instances));
+	}
+}
+
+void MultiVertexArrayObjectTest::render (tcu::Surface& vaoResult, tcu::Surface& defaultResult)
+{
+	GLuint vao = 0;
+
+	GLU_CHECK_CALL(glGenVertexArrays(1, &vao));
+	GLU_CHECK_CALL(glBindVertexArray(vao));
+	setState(m_spec.vao);
+	GLU_CHECK_CALL(glBindVertexArray(0));
+
+	setState(m_spec.state);
+
+	GLU_CHECK_CALL(glBindVertexArray(vao));
+	GLU_CHECK_CALL(glUseProgram(m_vaoProgram->getProgram()));
+	makeDrawCall(m_spec.vao);
+	glu::readPixels(m_context.getRenderContext(), 0, 0, vaoResult.getAccess());
+	setState(m_spec.vao);
+	GLU_CHECK_CALL(glBindVertexArray(0));
+
+	GLU_CHECK_CALL(glUseProgram(m_stateProgram->getProgram()));
+	makeDrawCall(m_spec.state);
+	glu::readPixels(m_context.getRenderContext(), 0, 0, defaultResult.getAccess());
+}
+
+void MultiVertexArrayObjectTest::genReferences (tcu::Surface& vaoRef, tcu::Surface& defaultRef)
+{
+	setState(m_spec.vao);
+	GLU_CHECK_CALL(glUseProgram(m_vaoProgram->getProgram()));
+	makeDrawCall(m_spec.vao);
+	glu::readPixels(m_context.getRenderContext(), 0, 0, vaoRef.getAccess());
+
+	setState(m_spec.state);
+	GLU_CHECK_CALL(glUseProgram(m_stateProgram->getProgram()));
+	makeDrawCall(m_spec.state);
+	glu::readPixels(m_context.getRenderContext(), 0, 0, defaultRef.getAccess());
+}
+
+TestCase::IterateResult MultiVertexArrayObjectTest::iterate (void)
+{
+	tcu::Surface	vaoReference	(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
+	tcu::Surface	stateReference	(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
+
+	tcu::Surface	vaoResult		(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
+	tcu::Surface	stateResult		(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
+
+	bool			isOk;
+
+	logVertexArrayState(m_log, m_spec.vao, "Vertex Array Object State");
+	logVertexArrayState(m_log, m_spec.state, "OpenGL Vertex Array State");
+	genReferences(stateReference, vaoReference);
+	render(stateResult, vaoResult);
+
+	isOk = tcu::pixelThresholdCompare (m_log, "Results", "Comparison result from rendering with Vertex Array State", stateReference, stateResult, tcu::RGBA(0,0,0,0), tcu::COMPARE_LOG_RESULT);
+	isOk = isOk && tcu::pixelThresholdCompare (m_log, "Results", "Comparison result from rendering with Vertex Array Object", vaoReference, vaoResult, tcu::RGBA(0,0,0,0), tcu::COMPARE_LOG_RESULT);
+
+	if (isOk)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		return STOP;
+	}
+}
+
+VertexArrayObjectTestGroup::VertexArrayObjectTestGroup (Context& context)
+	: TestCaseGroup(context, "vertex_array_objects", "Vertex array object test cases")
+{
+}
+
+VertexArrayObjectTestGroup::~VertexArrayObjectTestGroup (void)
+{
+}
+
+void VertexArrayObjectTestGroup::init (void)
+{
+	BufferSpec floatCoordBuffer48_1 = { 48, 384, 2, 0, 0, GL_FLOAT, 0, 0, -1.0f, 1.0f };
+	BufferSpec floatCoordBuffer48_2 = { 48, 384, 2, 0, 0, GL_FLOAT, 0, 0, -1.0f, 1.0f };
+
+	BufferSpec shortCoordBuffer48 = { 48, 192, 2, 0, 0, GL_SHORT, -32768, 32768, 0.0f, 0.0f };
+
+	// Different buffer
+	{
+		Spec spec;
+
+		VertexArrayState state;
+
+		state.attributes.push_back(Attribute());
+
+		state.attributes[0].enabled		= true;
+		state.attributes[0].size		= 2;
+		state.attributes[0].stride		= 0;
+		state.attributes[0].type		= GL_FLOAT;
+		state.attributes[0].integer		= GL_FALSE;
+		state.attributes[0].divisor		= 0;
+		state.attributes[0].offset		= 0;
+		state.attributes[0].normalized	= GL_FALSE;
+
+		state.elementArrayBuffer = 0;
+
+		spec.buffers.push_back(floatCoordBuffer48_1);
+		spec.buffers.push_back(floatCoordBuffer48_2);
+
+		spec.useDrawElements	= false;
+		spec.instances			= 0;
+		spec.count				= 48;
+		spec.vao				= state;
+		spec.state				= state;
+		spec.indexOffset		= 0;
+		spec.indexRangeMin		= 0;
+		spec.indexRangeMax		= 0;
+		spec.indexType			= GL_NONE;
+		spec.indexCount			= 0;
+
+		spec.state.attributes[0].bufferNdx	= 1;
+		spec.vao.attributes[0].bufferNdx	= 2;
+		addChild(new VertexArrayObjectTest(m_context, spec, "diff_buffer", "diff_buffer"));
+	}
+	// Different size
+	{
+		Spec spec;
+
+		VertexArrayState state;
+
+		state.attributes.push_back(Attribute());
+
+		state.attributes[0].enabled		= true;
+		state.attributes[0].size		= 2;
+		state.attributes[0].stride		= 0;
+		state.attributes[0].type		= GL_FLOAT;
+		state.attributes[0].integer		= GL_FALSE;
+		state.attributes[0].divisor		= 0;
+		state.attributes[0].offset		= 0;
+		state.attributes[0].normalized	= GL_FALSE;
+		state.attributes[0].bufferNdx	= 1;
+
+		state.elementArrayBuffer = 0;
+
+		spec.buffers.push_back(floatCoordBuffer48_1);
+
+		spec.useDrawElements	= false;
+		spec.instances			= 0;
+		spec.count				= 24;
+		spec.vao				= state;
+		spec.state				= state;
+		spec.indexOffset		= 0;
+		spec.indexRangeMin		= 0;
+		spec.indexRangeMax		= 0;
+		spec.indexType			= GL_NONE;
+		spec.indexCount			= 0;
+
+		spec.state.attributes[0].size		= 2;
+		spec.vao.attributes[0].size			= 3;
+		addChild(new VertexArrayObjectTest(m_context, spec, "diff_size", "diff_size"));
+	}
+
+	// Different stride
+	{
+		Spec spec;
+
+		VertexArrayState state;
+
+		state.attributes.push_back(Attribute());
+
+		state.attributes[0].enabled		= true;
+		state.attributes[0].size		= 2;
+		state.attributes[0].stride		= 0;
+		state.attributes[0].type		= GL_SHORT;
+		state.attributes[0].integer		= GL_FALSE;
+		state.attributes[0].divisor		= 0;
+		state.attributes[0].offset		= 0;
+		state.attributes[0].normalized	= GL_TRUE;
+		state.attributes[0].bufferNdx	= 1;
+
+		state.elementArrayBuffer = 0;
+
+		spec.buffers.push_back(shortCoordBuffer48);
+
+		spec.useDrawElements	= false;
+		spec.instances			= 0;
+		spec.count				= 24;
+		spec.vao				= state;
+		spec.state				= state;
+		spec.indexOffset		= 0;
+		spec.indexRangeMin		= 0;
+		spec.indexRangeMax		= 0;
+		spec.indexType			= GL_NONE;
+		spec.indexCount			= 0;
+
+		spec.vao.attributes[0].stride	= 2;
+		spec.state.attributes[0].stride	= 4;
+		addChild(new VertexArrayObjectTest(m_context, spec, "diff_stride", "diff_stride"));
+	}
+
+	// Different types
+	{
+		Spec spec;
+
+		VertexArrayState state;
+
+		state.attributes.push_back(Attribute());
+
+		state.attributes[0].enabled		= true;
+		state.attributes[0].size		= 2;
+		state.attributes[0].stride		= 0;
+		state.attributes[0].type		= GL_SHORT;
+		state.attributes[0].integer		= GL_FALSE;
+		state.attributes[0].divisor		= 0;
+		state.attributes[0].offset		= 0;
+		state.attributes[0].normalized	= GL_TRUE;
+		state.attributes[0].bufferNdx	= 1;
+
+		state.elementArrayBuffer = 0;
+
+		spec.buffers.push_back(shortCoordBuffer48);
+
+		spec.useDrawElements	= false;
+		spec.instances			= 0;
+		spec.count				= 24;
+		spec.vao				= state;
+		spec.state				= state;
+		spec.indexOffset		= 0;
+		spec.indexRangeMin		= 0;
+		spec.indexRangeMax		= 0;
+		spec.indexType			= GL_NONE;
+		spec.indexCount			= 0;
+
+		spec.vao.attributes[0].type		= GL_SHORT;
+		spec.state.attributes[0].type	= GL_BYTE;
+		addChild(new VertexArrayObjectTest(m_context, spec, "diff_type", "diff_type"));
+	}
+	// Different "integer"
+	{
+		Spec spec;
+
+		VertexArrayState state;
+
+		state.attributes.push_back(Attribute());
+
+		state.attributes[0].enabled		= true;
+		state.attributes[0].size		= 2;
+		state.attributes[0].stride		= 0;
+		state.attributes[0].type		= GL_BYTE;
+		state.attributes[0].integer		= GL_TRUE;
+		state.attributes[0].divisor		= 0;
+		state.attributes[0].offset		= 0;
+		state.attributes[0].normalized	= GL_FALSE;
+		state.attributes[0].bufferNdx	= 1;
+
+		state.elementArrayBuffer = 0;
+
+		spec.buffers.push_back(shortCoordBuffer48);
+
+		spec.useDrawElements	= false;
+		spec.count				= 24;
+		spec.vao				= state;
+		spec.state				= state;
+		spec.instances			= 0;
+		spec.indexOffset		= 0;
+		spec.indexRangeMin		= 0;
+		spec.indexRangeMax		= 0;
+		spec.indexType			= GL_NONE;
+		spec.indexCount			= 0;
+
+		spec.state.attributes[0].integer	= GL_FALSE;
+		spec.vao.attributes[0].integer		= GL_TRUE;
+		addChild(new VertexArrayObjectTest(m_context, spec, "diff_integer", "diff_integer"));
+	}
+	// Different divisor
+	{
+		Spec spec;
+
+		VertexArrayState state;
+
+		state.attributes.push_back(Attribute());
+		state.attributes.push_back(Attribute());
+
+		state.attributes[0].enabled		= true;
+		state.attributes[0].size		= 2;
+		state.attributes[0].stride		= 0;
+		state.attributes[0].type		= GL_SHORT;
+		state.attributes[0].integer		= GL_FALSE;
+		state.attributes[0].divisor		= 0;
+		state.attributes[0].offset		= 0;
+		state.attributes[0].normalized	= GL_TRUE;
+		state.attributes[0].bufferNdx	= 1;
+
+		state.attributes[1].enabled		= true;
+		state.attributes[1].size		= 4;
+		state.attributes[1].stride		= 0;
+		state.attributes[1].type		= GL_FLOAT;
+		state.attributes[1].integer		= GL_FALSE;
+		state.attributes[1].divisor		= 0;
+		state.attributes[1].offset		= 0;
+		state.attributes[1].normalized	= GL_FALSE;
+		state.attributes[1].bufferNdx	= 2;
+
+		state.elementArrayBuffer = 0;
+
+		spec.buffers.push_back(shortCoordBuffer48);
+		spec.buffers.push_back(floatCoordBuffer48_1);
+
+		spec.useDrawElements	= false;
+		spec.instances			= 10;
+		spec.count				= 12;
+		spec.vao				= state;
+		spec.state				= state;
+		spec.indexOffset		= 0;
+		spec.indexRangeMin		= 0;
+		spec.indexRangeMax		= 0;
+		spec.indexType			= GL_NONE;
+		spec.indexCount			= 0;
+
+		spec.vao.attributes[1].divisor		= 3;
+		spec.state.attributes[1].divisor	= 2;
+
+		addChild(new VertexArrayObjectTest(m_context, spec, "diff_divisor", "diff_divisor"));
+	}
+	// Different offset
+	{
+		Spec spec;
+
+		VertexArrayState state;
+
+		state.attributes.push_back(Attribute());
+
+		state.attributes[0].enabled		= true;
+		state.attributes[0].size		= 2;
+		state.attributes[0].stride		= 0;
+		state.attributes[0].type		= GL_SHORT;
+		state.attributes[0].integer		= GL_FALSE;
+		state.attributes[0].divisor		= 0;
+		state.attributes[0].offset		= 0;
+		state.attributes[0].normalized	= GL_TRUE;
+		state.attributes[0].bufferNdx	= 1;
+
+		state.elementArrayBuffer = 0;
+
+		spec.buffers.push_back(shortCoordBuffer48);
+
+		spec.useDrawElements	= false;
+		spec.instances			= 0;
+		spec.count				= 24;
+		spec.vao				= state;
+		spec.state				= state;
+		spec.indexOffset		= 0;
+		spec.indexRangeMin		= 0;
+		spec.indexRangeMax		= 0;
+		spec.indexType			= GL_NONE;
+		spec.indexCount			= 0;
+
+		spec.vao.attributes[0].offset	= 2;
+		spec.state.attributes[0].offset	= 4;
+		addChild(new VertexArrayObjectTest(m_context, spec, "diff_offset", "diff_offset"));
+	}
+	// Different normalize
+	{
+		Spec spec;
+
+		VertexArrayState state;
+
+		state.attributes.push_back(Attribute());
+
+		state.attributes[0].enabled		= true;
+		state.attributes[0].size		= 2;
+		state.attributes[0].stride		= 0;
+		state.attributes[0].type		= GL_SHORT;
+		state.attributes[0].integer		= GL_FALSE;
+		state.attributes[0].divisor		= 0;
+		state.attributes[0].offset		= 0;
+		state.attributes[0].normalized	= GL_TRUE;
+		state.attributes[0].bufferNdx	= 1;
+
+		state.elementArrayBuffer = 0;
+
+		spec.buffers.push_back(shortCoordBuffer48);
+
+		spec.useDrawElements	= false;
+		spec.instances			= 0;
+		spec.count				= 48;
+		spec.vao				= state;
+		spec.state				= state;
+		spec.indexOffset		= 0;
+		spec.indexRangeMin		= 0;
+		spec.indexRangeMax		= 0;
+		spec.indexType			= GL_NONE;
+		spec.indexCount			= 0;
+
+		spec.vao.attributes[0].normalized	= GL_TRUE;
+		spec.state.attributes[0].normalized	= GL_FALSE;;
+		addChild(new VertexArrayObjectTest(m_context, spec, "diff_normalize", "diff_normalize"));
+	}
+	// DrawElements with buffer / Pointer
+	{
+		Spec spec;
+
+		VertexArrayState state;
+
+		state.attributes.push_back(Attribute());
+
+		state.attributes[0].enabled		= true;
+		state.attributes[0].size		= 2;
+		state.attributes[0].stride		= 0;
+		state.attributes[0].type		= GL_FLOAT;
+		state.attributes[0].integer		= GL_FALSE;
+		state.attributes[0].divisor		= 0;
+		state.attributes[0].offset		= 0;
+		state.attributes[0].normalized	= GL_TRUE;
+		state.attributes[0].bufferNdx	= 1;
+
+		state.elementArrayBuffer = 0;
+
+		spec.buffers.push_back(floatCoordBuffer48_1);
+
+		BufferSpec indexBuffer = { 24, 192, 1, 0, 0, GL_UNSIGNED_SHORT, 0, 48, 0.0f, 0.0f };
+		spec.buffers.push_back(indexBuffer);
+
+		spec.useDrawElements	= true;
+		spec.count				= 24;
+		spec.vao				= state;
+		spec.state				= state;
+		spec.instances			= 0;
+		spec.indexOffset		= 0;
+		spec.indexRangeMin		= 0;
+		spec.indexRangeMax		= 48;
+		spec.indexType			= GL_UNSIGNED_SHORT;
+		spec.indexCount			= 24;
+
+		spec.state.elementArrayBuffer	= 0;
+		spec.vao.elementArrayBuffer		= 2;
+		addChild(new VertexArrayObjectTest(m_context, spec, "diff_indices", "diff_indices"));
+	}
+	// Use all attributes
+
+	addChild(new MultiVertexArrayObjectTest(m_context, "all_attributes", "all_attributes"));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fVertexArrayObjectTests.hpp b/modules/gles3/functional/es3fVertexArrayObjectTests.hpp
new file mode 100644
index 0000000..cda986f
--- /dev/null
+++ b/modules/gles3/functional/es3fVertexArrayObjectTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES3FVERTEXARRAYOBJECTTESTS_HPP
+#define _ES3FVERTEXARRAYOBJECTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex Array Object tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class VertexArrayObjectTestGroup : public TestCaseGroup
+{
+public:
+									VertexArrayObjectTestGroup		(Context& context);
+	virtual							~VertexArrayObjectTestGroup		(void);
+	virtual void					init							(void);
+
+private:
+									VertexArrayObjectTestGroup		(const VertexArrayObjectTestGroup&);
+	VertexArrayObjectTestGroup&		operator=						(const VertexArrayObjectTestGroup&);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FVERTEXARRAYOBJECTTESTS_HPP
diff --git a/modules/gles3/functional/es3fVertexArrayTest.cpp b/modules/gles3/functional/es3fVertexArrayTest.cpp
new file mode 100644
index 0000000..de70fa7
--- /dev/null
+++ b/modules/gles3/functional/es3fVertexArrayTest.cpp
@@ -0,0 +1,1094 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array and buffer tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fVertexArrayTest.hpp"
+#include "glsVertexArrayTests.hpp"
+
+#include <sstream>
+
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class SingleVertexArrayUsageGroup : public TestCaseGroup
+{
+public:
+									SingleVertexArrayUsageGroup		(Context& context, Array::Usage usage);
+	virtual							~SingleVertexArrayUsageGroup	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayUsageGroup		(const SingleVertexArrayUsageGroup& other);
+	SingleVertexArrayUsageGroup&	operator=						(const SingleVertexArrayUsageGroup& other);
+
+	Array::Usage					m_usage;
+};
+
+SingleVertexArrayUsageGroup::SingleVertexArrayUsageGroup (Context& context, Array::Usage usage)
+	: TestCaseGroup	(context, Array::usageTypeToString(usage).c_str(), Array::usageTypeToString(usage).c_str())
+	, m_usage		(usage)
+{
+}
+
+SingleVertexArrayUsageGroup::~SingleVertexArrayUsageGroup (void)
+{
+}
+
+template<class T>
+static std::string typeToString (T t)
+{
+	std::stringstream strm;
+	strm << t;
+	return strm.str();
+}
+
+void SingleVertexArrayUsageGroup::init (void)
+{
+	int					counts[]		= {1, 256};
+	int					strides[]		= {0, -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_FIXED, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE};
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+		{
+			for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+			{
+				const int			stride	= (strides[strideNdx] < 0 ? Array::inputTypeSize(inputTypes[inputTypeNdx]) * 2 : strides[strideNdx]);
+				const bool			aligned	= (stride % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0;
+				const std::string	name	= "stride" + typeToString(stride) + "_" + Array::inputTypeToString(inputTypes[inputTypeNdx]) + "_quads" + typeToString(counts[countNdx]);
+
+				MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																Array::OUTPUTTYPE_VEC2,
+																Array::STORAGE_BUFFER,
+																m_usage,
+																2,
+																0,
+																stride,
+																false,
+																GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+				MultiVertexArrayTest::Spec spec;
+				spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+				spec.drawCount	= counts[countNdx];
+				spec.first		= 0;
+				spec.arrays.push_back(arraySpec);
+
+				if (aligned)
+					addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+			}
+		}
+	}
+}
+
+class SingleVertexArrayUsageTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayUsageTests		(Context& context);
+	virtual							~SingleVertexArrayUsageTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayUsageTests		(const SingleVertexArrayUsageTests& other);
+	SingleVertexArrayUsageTests&	operator=						(const SingleVertexArrayUsageTests& other);
+};
+
+SingleVertexArrayUsageTests::SingleVertexArrayUsageTests (Context& context)
+	: TestCaseGroup(context, "usages", "Single vertex atribute, usage")
+{
+}
+
+SingleVertexArrayUsageTests::~SingleVertexArrayUsageTests (void)
+{
+}
+
+void SingleVertexArrayUsageTests::init (void)
+{
+	// Test usage
+	Array::Usage		usages[]		= { Array::USAGE_STATIC_DRAW, Array::USAGE_STREAM_DRAW, Array::USAGE_DYNAMIC_DRAW, Array::USAGE_STATIC_COPY, Array::USAGE_STREAM_COPY, Array::USAGE_DYNAMIC_COPY, Array::USAGE_STATIC_READ, Array::USAGE_STREAM_READ, Array::USAGE_DYNAMIC_READ };
+	for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usages); usageNdx++)
+	{
+		addChild(new SingleVertexArrayUsageGroup(m_context, usages[usageNdx]));
+	}
+}
+
+class SingleVertexArrayStrideGroup : public TestCaseGroup
+{
+public:
+									SingleVertexArrayStrideGroup	(Context& context, Array::InputType type);
+	virtual							~SingleVertexArrayStrideGroup	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayStrideGroup	(const SingleVertexArrayStrideGroup& other);
+	SingleVertexArrayStrideGroup&	operator=						(const SingleVertexArrayStrideGroup& other);
+
+	Array::InputType				m_type;
+};
+
+SingleVertexArrayStrideGroup::SingleVertexArrayStrideGroup (Context& context, Array::InputType type)
+	: TestCaseGroup	(context, Array::inputTypeToString(type).c_str(), Array::inputTypeToString(type).c_str())
+	, m_type		(type)
+{
+}
+
+SingleVertexArrayStrideGroup::~SingleVertexArrayStrideGroup (void)
+{
+}
+
+void SingleVertexArrayStrideGroup::init (void)
+{
+	Array::Storage		storages[]		= {Array::STORAGE_USER, Array::STORAGE_BUFFER};
+	int					counts[]		= {1, 256};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int storageNdx = 0; storageNdx < DE_LENGTH_OF_ARRAY(storages); storageNdx++)
+	{
+		for (int componentCount = 2; componentCount < 5; componentCount++)
+		{
+			for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+			{
+				for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+				{
+					const bool	packed			= m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10;
+					const int	stride			= (strides[strideNdx] < 0) ? ((packed) ? (16) : (Array::inputTypeSize(m_type) * componentCount)) : (strides[strideNdx]);
+					const int	alignment		= (packed) ? (Array::inputTypeSize(m_type) * componentCount) : (Array::inputTypeSize(m_type));
+					const bool	bufferUnaligned	= (storages[storageNdx] == Array::STORAGE_BUFFER) && (stride % alignment) != 0;
+
+					std::string name = Array::storageToString(storages[storageNdx]) + "_stride" + typeToString(stride) + "_components" + typeToString(componentCount) + "_quads" + typeToString(counts[countNdx]);
+
+					if((m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10) && componentCount != 4)
+						continue;
+
+					MultiVertexArrayTest::Spec::ArraySpec arraySpec(m_type,
+																	Array::OUTPUTTYPE_VEC4,
+																	storages[storageNdx],
+																	Array::USAGE_DYNAMIC_DRAW,
+																	componentCount,
+																	0,
+																	stride,
+																	false,
+																	GLValue::getMinValue(m_type),
+																	GLValue::getMaxValue(m_type));
+
+					MultiVertexArrayTest::Spec spec;
+					spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+					spec.drawCount	= counts[countNdx];
+					spec.first		= 0;
+					spec.arrays.push_back(arraySpec);
+
+					if (!bufferUnaligned)
+						addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayStrideTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayStrideTests	(Context& context);
+	virtual							~SingleVertexArrayStrideTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayStrideTests	(const SingleVertexArrayStrideTests& other);
+	SingleVertexArrayStrideTests&	operator=						(const SingleVertexArrayStrideTests& other);
+};
+
+SingleVertexArrayStrideTests::SingleVertexArrayStrideTests (Context& context)
+	: TestCaseGroup(context, "strides", "Single stride vertex atribute")
+{
+}
+
+SingleVertexArrayStrideTests::~SingleVertexArrayStrideTests (void)
+{
+}
+
+void SingleVertexArrayStrideTests::init (void)
+{
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE, /*Array::INPUTTYPE_UNSIGNED_SHORT, Array::INPUTTYPE_UNSIGNED_BYTE,*/ Array::INPUTTYPE_FIXED, Array::INPUTTYPE_INT_2_10_10_10 };
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		addChild(new SingleVertexArrayStrideGroup(m_context, inputTypes[inputTypeNdx]));
+	}
+}
+
+class SingleVertexArrayFirstGroup : public TestCaseGroup
+{
+public:
+									SingleVertexArrayFirstGroup	(Context& context, Array::InputType type);
+	virtual							~SingleVertexArrayFirstGroup	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayFirstGroup	(const SingleVertexArrayFirstGroup& other);
+	SingleVertexArrayFirstGroup&	operator=						(const SingleVertexArrayFirstGroup& other);
+	Array::InputType				m_type;
+};
+
+SingleVertexArrayFirstGroup::SingleVertexArrayFirstGroup (Context& context, Array::InputType type)
+	: TestCaseGroup	(context, Array::inputTypeToString(type).c_str(), Array::inputTypeToString(type).c_str())
+	, m_type		(type)
+{
+}
+
+SingleVertexArrayFirstGroup::~SingleVertexArrayFirstGroup (void)
+{
+}
+
+void SingleVertexArrayFirstGroup::init (void)
+{
+	int					counts[]		= {5, 256};
+	int					firsts[]		= {6, 24};
+	int					offsets[]		= {1, 16, 17};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(offsets); offsetNdx++)
+	{
+		for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+		{
+			for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+			{
+				for (int firstNdx = 0; firstNdx < DE_LENGTH_OF_ARRAY(firsts); firstNdx++)
+				{
+					const bool	packed			= m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10;
+					const int	componentCount	= (packed) ? (4) : (2);
+					const int	stride			= (strides[strideNdx] < 0) ? ((packed) ? (8) : (Array::inputTypeSize(m_type) * componentCount)) : (strides[strideNdx]);
+					const int	alignment		= (packed) ? (Array::inputTypeSize(m_type) * componentCount) : (Array::inputTypeSize(m_type));
+					const bool	aligned			= ((stride % alignment) == 0) && ((offsets[offsetNdx] % alignment) == 0);
+					std::string name			= "first" + typeToString(firsts[firstNdx]) + "_offset" + typeToString(offsets[offsetNdx]) + "_stride" + typeToString(stride) + "_quads" + typeToString(counts[countNdx]);
+
+					MultiVertexArrayTest::Spec::ArraySpec arraySpec(m_type,
+																	Array::OUTPUTTYPE_VEC2,
+																	Array::STORAGE_BUFFER,
+																	Array::USAGE_DYNAMIC_DRAW,
+																	componentCount,
+																	offsets[offsetNdx],
+																	stride,
+																	false,
+																	GLValue::getMinValue(m_type),
+																	GLValue::getMaxValue(m_type));
+
+					MultiVertexArrayTest::Spec spec;
+					spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+					spec.drawCount	= counts[countNdx];
+					spec.first		= firsts[firstNdx];
+					spec.arrays.push_back(arraySpec);
+
+					if (aligned)
+						addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayFirstTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayFirstTests	(Context& context);
+	virtual							~SingleVertexArrayFirstTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayFirstTests	(const SingleVertexArrayFirstTests& other);
+	SingleVertexArrayFirstTests&	operator=						(const SingleVertexArrayFirstTests& other);
+};
+
+SingleVertexArrayFirstTests::SingleVertexArrayFirstTests (Context& context)
+	: TestCaseGroup(context, "first", "Single vertex attribute, different first values to drawArrays")
+{
+}
+
+SingleVertexArrayFirstTests::~SingleVertexArrayFirstTests (void)
+{
+}
+
+void SingleVertexArrayFirstTests::init (void)
+{
+	// Test offset with different input types, component counts and storage, Usage(?)
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_INT_2_10_10_10 };
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		addChild(new SingleVertexArrayFirstGroup(m_context, inputTypes[inputTypeNdx]));
+	}
+}
+
+class SingleVertexArrayOffsetGroup : public TestCaseGroup
+{
+public:
+									SingleVertexArrayOffsetGroup	(Context& context, Array::InputType type);
+	virtual							~SingleVertexArrayOffsetGroup	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayOffsetGroup	(const SingleVertexArrayOffsetGroup& other);
+	SingleVertexArrayOffsetGroup&	operator=						(const SingleVertexArrayOffsetGroup& other);
+	Array::InputType				m_type;
+};
+
+SingleVertexArrayOffsetGroup::SingleVertexArrayOffsetGroup (Context& context, Array::InputType type)
+	: TestCaseGroup	(context, Array::inputTypeToString(type).c_str(), Array::inputTypeToString(type).c_str())
+	, m_type		(type)
+{
+}
+
+SingleVertexArrayOffsetGroup::~SingleVertexArrayOffsetGroup (void)
+{
+}
+
+void SingleVertexArrayOffsetGroup::init (void)
+{
+	int					counts[]		= {1, 256};
+	int					offsets[]		= {1, 4, 17, 32};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(offsets); offsetNdx++)
+	{
+		for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+		{
+			for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+			{
+				const bool			packed			= m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10;
+				const int			componentCount	= (packed) ? (4) : (2);
+				const int			stride			= (strides[strideNdx] < 0 ? Array::inputTypeSize(m_type) * componentCount : strides[strideNdx]);
+				const int			alignment		= (packed) ? (Array::inputTypeSize(m_type) * componentCount) : (Array::inputTypeSize(m_type));
+				const bool			aligned			= ((stride % alignment) == 0) && ((offsets[offsetNdx] % alignment) == 0);
+				const std::string	name			= "offset" + typeToString(offsets[offsetNdx]) + "_stride" + typeToString(stride) + "_quads" + typeToString(counts[countNdx]);
+
+				MultiVertexArrayTest::Spec::ArraySpec arraySpec(m_type,
+																Array::OUTPUTTYPE_VEC2,
+																Array::STORAGE_BUFFER,
+																Array::USAGE_DYNAMIC_DRAW,
+																componentCount,
+																offsets[offsetNdx],
+																stride,
+																false,
+																GLValue::getMinValue(m_type),
+																GLValue::getMaxValue(m_type));
+
+				MultiVertexArrayTest::Spec spec;
+				spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+				spec.drawCount	= counts[countNdx];
+				spec.first		= 0;
+				spec.arrays.push_back(arraySpec);
+
+				if (aligned)
+					addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+			}
+		}
+	}
+}
+
+class SingleVertexArrayOffsetTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayOffsetTests	(Context& context);
+	virtual							~SingleVertexArrayOffsetTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayOffsetTests	(const SingleVertexArrayOffsetTests& other);
+	SingleVertexArrayOffsetTests&	operator=						(const SingleVertexArrayOffsetTests& other);
+};
+
+SingleVertexArrayOffsetTests::SingleVertexArrayOffsetTests (Context& context)
+	: TestCaseGroup(context, "offset", "Single vertex atribute offset element")
+{
+}
+
+SingleVertexArrayOffsetTests::~SingleVertexArrayOffsetTests (void)
+{
+}
+
+void SingleVertexArrayOffsetTests::init (void)
+{
+	// Test offset with different input types, component counts and storage, Usage(?)
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_INT_2_10_10_10 };
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		addChild(new SingleVertexArrayOffsetGroup(m_context, inputTypes[inputTypeNdx]));
+	}
+}
+
+class SingleVertexArrayNormalizeGroup : public TestCaseGroup
+{
+public:
+										SingleVertexArrayNormalizeGroup		(Context& context, Array::InputType type);
+	virtual								~SingleVertexArrayNormalizeGroup	(void);
+
+	virtual void						init								(void);
+
+private:
+										SingleVertexArrayNormalizeGroup		(const SingleVertexArrayNormalizeGroup& other);
+	SingleVertexArrayNormalizeGroup&	operator=							(const SingleVertexArrayNormalizeGroup& other);
+	Array::InputType					m_type;
+};
+
+SingleVertexArrayNormalizeGroup::SingleVertexArrayNormalizeGroup (Context& context, Array::InputType type)
+	: TestCaseGroup	(context, Array::inputTypeToString(type).c_str(), Array::inputTypeToString(type).c_str())
+	, m_type		(type)
+{
+}
+
+SingleVertexArrayNormalizeGroup::~SingleVertexArrayNormalizeGroup (void)
+{
+}
+
+void SingleVertexArrayNormalizeGroup::init (void)
+{
+	int					counts[]		= {1, 256};
+
+	for (int componentCount = 2; componentCount < 5; componentCount++)
+	{
+		for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+		{
+			if((m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10) && componentCount != 4)
+				continue;
+
+			std::string name = "components" + typeToString(componentCount) + "_quads" + typeToString(counts[countNdx]);
+
+			MultiVertexArrayTest::Spec::ArraySpec arraySpec(m_type,
+															Array::OUTPUTTYPE_VEC4,
+															Array::STORAGE_USER,
+															Array::USAGE_DYNAMIC_DRAW,
+															componentCount,
+															0,
+															0,
+															true,
+															GLValue::getMinValue(m_type),
+															GLValue::getMaxValue(m_type));
+
+			MultiVertexArrayTest::Spec spec;
+			spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+			spec.drawCount	= counts[countNdx];
+			spec.first		= 0;
+			spec.arrays.push_back(arraySpec);
+
+			addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+		}
+	}
+}
+
+class SingleVertexArrayNormalizeTests : public TestCaseGroup
+{
+public:
+										SingleVertexArrayNormalizeTests		(Context& context);
+	virtual								~SingleVertexArrayNormalizeTests	(void);
+
+	virtual void						init								(void);
+
+private:
+										SingleVertexArrayNormalizeTests		(const SingleVertexArrayNormalizeTests& other);
+	SingleVertexArrayNormalizeTests&	operator=							(const SingleVertexArrayNormalizeTests& other);
+};
+
+SingleVertexArrayNormalizeTests::SingleVertexArrayNormalizeTests (Context& context)
+	: TestCaseGroup(context, "normalize", "Single normalize vertex atribute")
+{
+}
+
+SingleVertexArrayNormalizeTests::~SingleVertexArrayNormalizeTests (void)
+{
+}
+
+void SingleVertexArrayNormalizeTests::init (void)
+{
+	// Test normalization with different input types, component counts and storage
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_UNSIGNED_SHORT, Array::INPUTTYPE_UNSIGNED_BYTE, Array::INPUTTYPE_FIXED, Array::INPUTTYPE_UNSIGNED_INT, Array::INPUTTYPE_INT, Array::INPUTTYPE_HALF , Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10, Array::INPUTTYPE_INT_2_10_10_10 };
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		addChild(new SingleVertexArrayNormalizeGroup(m_context, inputTypes[inputTypeNdx]));
+	}
+}
+
+class SingleVertexArrayOutputTypeGroup : public TestCaseGroup
+{
+public:
+										SingleVertexArrayOutputTypeGroup	(Context& context, Array::InputType type);
+	virtual								~SingleVertexArrayOutputTypeGroup	(void);
+
+	virtual void						init								(void);
+
+private:
+										SingleVertexArrayOutputTypeGroup	(const SingleVertexArrayOutputTypeGroup& other);
+	SingleVertexArrayOutputTypeGroup&	operator=							(const SingleVertexArrayOutputTypeGroup& other);
+	Array::InputType					m_type;
+};
+
+SingleVertexArrayOutputTypeGroup::SingleVertexArrayOutputTypeGroup (Context& context, Array::InputType type)
+	: TestCaseGroup	(context, Array::inputTypeToString(type).c_str(), Array::inputTypeToString(type).c_str())
+	, m_type		(type)
+{
+}
+
+SingleVertexArrayOutputTypeGroup::~SingleVertexArrayOutputTypeGroup (void)
+{
+}
+
+void SingleVertexArrayOutputTypeGroup::init (void)
+{
+	Array::OutputType	outputTypes[]	= {Array::OUTPUTTYPE_VEC2, Array::OUTPUTTYPE_VEC3, Array::OUTPUTTYPE_VEC4, Array::OUTPUTTYPE_IVEC2, Array::OUTPUTTYPE_IVEC3, Array::OUTPUTTYPE_IVEC4, Array::OUTPUTTYPE_UVEC2, Array::OUTPUTTYPE_UVEC3, Array::OUTPUTTYPE_UVEC4 };
+	Array::Storage		storages[]		= {Array::STORAGE_USER};
+	int					counts[]		= {1, 256};
+
+	for (int outputTypeNdx = 0; outputTypeNdx < DE_LENGTH_OF_ARRAY(outputTypes); outputTypeNdx++)
+	{
+		for (int storageNdx = 0; storageNdx < DE_LENGTH_OF_ARRAY(storages); storageNdx++)
+		{
+			for (int componentCount = 2; componentCount < 5; componentCount++)
+			{
+				for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+				{
+					std::string name = "components" + typeToString(componentCount) + "_" + Array::outputTypeToString(outputTypes[outputTypeNdx]) + "_quads" + typeToString(counts[countNdx]);
+
+					const bool inputIsSignedInteger		= m_type == Array::INPUTTYPE_INT || m_type == Array::INPUTTYPE_SHORT || m_type == Array::INPUTTYPE_BYTE;
+					const bool inputIsUnignedInteger	= m_type == Array::INPUTTYPE_UNSIGNED_INT || m_type == Array::INPUTTYPE_UNSIGNED_SHORT || m_type == Array::INPUTTYPE_UNSIGNED_BYTE;
+					const bool outputIsSignedInteger	= outputTypes[outputTypeNdx] == Array::OUTPUTTYPE_IVEC2 || outputTypes[outputTypeNdx] == Array::OUTPUTTYPE_IVEC3 || outputTypes[outputTypeNdx] == Array::OUTPUTTYPE_IVEC4;
+					const bool outputIsUnsignedInteger	= outputTypes[outputTypeNdx] == Array::OUTPUTTYPE_UVEC2 || outputTypes[outputTypeNdx] == Array::OUTPUTTYPE_UVEC3 || outputTypes[outputTypeNdx] == Array::OUTPUTTYPE_UVEC4;
+
+					// If input type is float type and output type is int type skip
+					if ((m_type == Array::INPUTTYPE_FLOAT ||  m_type == Array::INPUTTYPE_HALF || m_type == Array::INPUTTYPE_FIXED) && (outputTypes[outputTypeNdx] >= Array::OUTPUTTYPE_INT))
+						continue;
+
+					if((m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10) && (outputTypes[outputTypeNdx] >= Array::OUTPUTTYPE_INT))
+						continue;
+
+					if((m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10) && componentCount != 4)
+						continue;
+
+					// Loading signed data as unsigned causes undefined values and vice versa
+					if (inputIsSignedInteger && outputIsUnsignedInteger)
+						continue;
+					if (inputIsUnignedInteger && outputIsSignedInteger)
+						continue;
+
+					MultiVertexArrayTest::Spec::ArraySpec arraySpec(m_type,
+																	outputTypes[outputTypeNdx],
+																	storages[storageNdx],
+																	Array::USAGE_DYNAMIC_DRAW,
+																	componentCount,
+																	0,
+																	0,
+																	false,
+																	GLValue::getMinValue(m_type),
+																	GLValue::getMaxValue(m_type));
+
+					MultiVertexArrayTest::Spec spec;
+					spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+					spec.drawCount	= counts[countNdx];
+					spec.first		= 0;
+					spec.arrays.push_back(arraySpec);
+
+					addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayOutputTypeTests : public TestCaseGroup
+{
+public:
+										SingleVertexArrayOutputTypeTests	(Context& context);
+	virtual								~SingleVertexArrayOutputTypeTests	(void);
+
+	virtual void						init								(void);
+
+private:
+										SingleVertexArrayOutputTypeTests	(const SingleVertexArrayOutputTypeTests& other);
+	SingleVertexArrayOutputTypeTests&	operator=							(const SingleVertexArrayOutputTypeTests& other);
+};
+
+SingleVertexArrayOutputTypeTests::SingleVertexArrayOutputTypeTests (Context& context)
+	: TestCaseGroup(context, "output_types", "Single output type vertex atribute")
+{
+}
+
+SingleVertexArrayOutputTypeTests::~SingleVertexArrayOutputTypeTests (void)
+{
+}
+
+void SingleVertexArrayOutputTypeTests::init (void)
+{
+	// Test output types with different input types, component counts and storage, Usage?, Precision?, float?
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_UNSIGNED_SHORT, Array::INPUTTYPE_UNSIGNED_BYTE, Array::INPUTTYPE_FIXED, Array::INPUTTYPE_UNSIGNED_INT, Array::INPUTTYPE_INT, Array::INPUTTYPE_HALF, Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10, Array::INPUTTYPE_INT_2_10_10_10 };
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		addChild(new SingleVertexArrayOutputTypeGroup(m_context, inputTypes[inputTypeNdx]));
+	}
+}
+
+
+class SingleVertexArrayTestGroup : public TestCaseGroup
+{
+public:
+									SingleVertexArrayTestGroup	(Context& context);
+	virtual							~SingleVertexArrayTestGroup	(void);
+
+	virtual void					init						(void);
+
+private:
+									SingleVertexArrayTestGroup	(const SingleVertexArrayTestGroup& other);
+	SingleVertexArrayTestGroup&		operator=					(const SingleVertexArrayTestGroup& other);
+};
+
+SingleVertexArrayTestGroup::SingleVertexArrayTestGroup (Context& context)
+	: TestCaseGroup(context, "single_attribute", "Single vertex atribute")
+{
+}
+
+SingleVertexArrayTestGroup::~SingleVertexArrayTestGroup (void)
+{
+}
+
+void SingleVertexArrayTestGroup::init (void)
+{
+	addChild(new SingleVertexArrayStrideTests(m_context));
+	addChild(new SingleVertexArrayNormalizeTests(m_context));
+	addChild(new SingleVertexArrayOutputTypeTests(m_context));
+	addChild(new SingleVertexArrayUsageTests(m_context));
+	addChild(new SingleVertexArrayOffsetTests(m_context));
+	addChild(new SingleVertexArrayFirstTests(m_context));
+}
+
+class MultiVertexArrayCountTests : public TestCaseGroup
+{
+public:
+									MultiVertexArrayCountTests	(Context& context);
+	virtual							~MultiVertexArrayCountTests	(void);
+
+	virtual void					init						(void);
+
+private:
+									MultiVertexArrayCountTests	(const MultiVertexArrayCountTests& other);
+	MultiVertexArrayCountTests&		operator=					(const MultiVertexArrayCountTests& other);
+
+	std::string						getTestName					(const MultiVertexArrayTest::Spec& spec);
+};
+
+MultiVertexArrayCountTests::MultiVertexArrayCountTests (Context& context)
+	: TestCaseGroup(context, "attribute_count", "Attribute counts")
+{
+}
+
+MultiVertexArrayCountTests::~MultiVertexArrayCountTests (void)
+{
+}
+
+std::string MultiVertexArrayCountTests::getTestName (const MultiVertexArrayTest::Spec& spec)
+{
+	std::stringstream name;
+	name
+		<< spec.arrays.size();
+
+	return name.str();
+}
+
+void MultiVertexArrayCountTests::init (void)
+{
+	// Test attribute counts
+	int arrayCounts[] = {2, 3, 4, 5, 6, 7, 8};
+
+	for (int arrayCountNdx = 0; arrayCountNdx < DE_LENGTH_OF_ARRAY(arrayCounts); arrayCountNdx++)
+	{
+		MultiVertexArrayTest::Spec spec;
+
+		spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+		spec.drawCount	= 256;
+		spec.first		= 0;
+
+		for (int arrayNdx = 0; arrayNdx < arrayCounts[arrayCountNdx]; arrayNdx++)
+		{
+			MultiVertexArrayTest::Spec::ArraySpec arraySpec(Array::INPUTTYPE_FLOAT,
+															Array::OUTPUTTYPE_VEC2,
+															Array::STORAGE_USER,
+															Array::USAGE_DYNAMIC_DRAW,
+															2,
+															0,
+															0,
+															false,
+															GLValue::getMinValue(Array::INPUTTYPE_FLOAT),
+															GLValue::getMaxValue(Array::INPUTTYPE_FLOAT));
+
+			spec.arrays.push_back(arraySpec);
+		}
+
+		std::string name = getTestName(spec);
+		std::string desc = getTestName(spec);
+
+		addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), desc.c_str()));
+	}
+}
+
+class MultiVertexArrayStorageTests : public TestCaseGroup
+{
+public:
+									MultiVertexArrayStorageTests	(Context& context);
+	virtual							~MultiVertexArrayStorageTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									MultiVertexArrayStorageTests	(const MultiVertexArrayStorageTests& other);
+	MultiVertexArrayStorageTests&	operator=						(const MultiVertexArrayStorageTests& other);
+
+	void							addStorageCases 				(MultiVertexArrayTest::Spec spec, int depth);
+	std::string						getTestName						(const MultiVertexArrayTest::Spec& spec);
+};
+
+MultiVertexArrayStorageTests::MultiVertexArrayStorageTests (Context& context)
+	: TestCaseGroup(context, "storage", "Attribute storages")
+{
+}
+
+MultiVertexArrayStorageTests::~MultiVertexArrayStorageTests (void)
+{
+}
+
+std::string MultiVertexArrayStorageTests::getTestName (const MultiVertexArrayTest::Spec& spec)
+{
+	std::stringstream name;
+	name
+		<< spec.arrays.size();
+
+	for (int arrayNdx = 0; arrayNdx < (int)spec.arrays.size(); arrayNdx++)
+	{
+		name
+			<< "_"
+			<< Array::storageToString(spec.arrays[arrayNdx].storage);
+	}
+
+	return name.str();
+}
+
+void MultiVertexArrayStorageTests::addStorageCases (MultiVertexArrayTest::Spec spec, int depth)
+{
+	if (depth == 0)
+	{
+		// Skip trivial case, used elsewhere
+		bool ok = false;
+		for (int arrayNdx = 0; arrayNdx < (int)spec.arrays.size(); arrayNdx++)
+		{
+			if (spec.arrays[arrayNdx].storage != Array::STORAGE_USER)
+			{
+				ok = true;
+				break;
+			}
+		}
+
+		if (!ok)
+			return;
+
+		std::string name = getTestName(spec);
+		std::string desc = getTestName(spec);
+
+		addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), desc.c_str()));
+		return;
+	}
+
+	Array::Storage storages[] = {Array::STORAGE_USER, Array::STORAGE_BUFFER};
+	for (int storageNdx = 0; storageNdx < DE_LENGTH_OF_ARRAY(storages); storageNdx++)
+	{
+		MultiVertexArrayTest::Spec::ArraySpec arraySpec(Array::INPUTTYPE_FLOAT,
+														Array::OUTPUTTYPE_VEC2,
+														storages[storageNdx],
+														Array::USAGE_DYNAMIC_DRAW,
+														2,
+														0,
+														0,
+														false,
+														GLValue::getMinValue(Array::INPUTTYPE_FLOAT),
+														GLValue::getMaxValue(Array::INPUTTYPE_FLOAT));
+
+		MultiVertexArrayTest::Spec _spec = spec;
+		_spec.arrays.push_back(arraySpec);
+		addStorageCases(_spec, depth-1);
+	}
+}
+
+
+void MultiVertexArrayStorageTests::init (void)
+{
+	// Test different storages
+	int arrayCounts[] = {3};
+
+	MultiVertexArrayTest::Spec spec;
+
+	spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+	spec.drawCount	= 256;
+	spec.first		= 0;
+
+	for (int arrayCountNdx = 0; arrayCountNdx < DE_LENGTH_OF_ARRAY(arrayCounts); arrayCountNdx++)
+		addStorageCases(spec, arrayCounts[arrayCountNdx]);
+}
+
+class MultiVertexArrayStrideTests : public TestCaseGroup
+{
+public:
+									MultiVertexArrayStrideTests		(Context& context);
+	virtual							~MultiVertexArrayStrideTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									MultiVertexArrayStrideTests		(const MultiVertexArrayStrideTests& other);
+	MultiVertexArrayStrideTests&	operator=						(const MultiVertexArrayStrideTests& other);
+
+	void							addStrideCases 					(MultiVertexArrayTest::Spec spec, int depth);
+	std::string						getTestName						(const MultiVertexArrayTest::Spec& spec);
+};
+
+MultiVertexArrayStrideTests::MultiVertexArrayStrideTests (Context& context)
+	: TestCaseGroup(context, "stride", "Strides")
+{
+}
+
+MultiVertexArrayStrideTests::~MultiVertexArrayStrideTests (void)
+{
+}
+
+std::string MultiVertexArrayStrideTests::getTestName (const MultiVertexArrayTest::Spec& spec)
+{
+	std::stringstream name;
+
+	name
+		<< spec.arrays.size();
+
+	for (int arrayNdx = 0; arrayNdx < (int)spec.arrays.size(); arrayNdx++)
+	{
+		name
+			<< "_"
+			<< Array::inputTypeToString(spec.arrays[arrayNdx].inputType)
+			<< spec.arrays[arrayNdx].componentCount << "_"
+			<< spec.arrays[arrayNdx].stride;
+	}
+
+	return name.str();
+}
+
+void MultiVertexArrayStrideTests::init (void)
+{
+	// Test different strides, with multiple arrays, input types??
+	int arrayCounts[] = {3};
+
+	MultiVertexArrayTest::Spec spec;
+
+	spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+	spec.drawCount	= 256;
+	spec.first		= 0;
+
+	for (int arrayCountNdx = 0; arrayCountNdx < DE_LENGTH_OF_ARRAY(arrayCounts); arrayCountNdx++)
+		addStrideCases(spec, arrayCounts[arrayCountNdx]);
+}
+
+void MultiVertexArrayStrideTests::addStrideCases (MultiVertexArrayTest::Spec spec, int depth)
+{
+	if (depth == 0)
+	{
+		std::string name = getTestName(spec);
+		std::string desc = getTestName(spec);
+		addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), desc.c_str()));
+		return;
+	}
+
+	int strides[]	= {0, -1, 17, 32};
+
+	for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+	{
+		const int componentCount = 2;
+		MultiVertexArrayTest::Spec::ArraySpec arraySpec(Array::INPUTTYPE_FLOAT,
+														Array::OUTPUTTYPE_VEC2,
+														Array::STORAGE_USER,
+														Array::USAGE_DYNAMIC_DRAW,
+														componentCount,
+														0,
+														(strides[strideNdx] >= 0 ? strides[strideNdx] : componentCount * Array::inputTypeSize(Array::INPUTTYPE_FLOAT)),
+														false,
+														GLValue::getMinValue(Array::INPUTTYPE_FLOAT),
+														GLValue::getMaxValue(Array::INPUTTYPE_FLOAT));
+
+		MultiVertexArrayTest::Spec _spec = spec;
+		_spec.arrays.push_back(arraySpec);
+		addStrideCases(_spec, depth-1);
+	}
+}
+
+class MultiVertexArrayOutputTests : public TestCaseGroup
+{
+public:
+										MultiVertexArrayOutputTests		(Context& context);
+	virtual								~MultiVertexArrayOutputTests	(void);
+
+	virtual void						init								(void);
+
+private:
+										MultiVertexArrayOutputTests		(const MultiVertexArrayOutputTests& other);
+	MultiVertexArrayOutputTests&	operator=							(const MultiVertexArrayOutputTests& other);
+
+	void								addInputTypeCases 					(MultiVertexArrayTest::Spec spec, int depth);
+	std::string							getTestName							(const MultiVertexArrayTest::Spec& spec);
+};
+
+MultiVertexArrayOutputTests::MultiVertexArrayOutputTests (Context& context)
+	: TestCaseGroup(context, "input_types", "input types")
+{
+}
+
+MultiVertexArrayOutputTests::~MultiVertexArrayOutputTests (void)
+{
+}
+
+std::string MultiVertexArrayOutputTests::getTestName (const MultiVertexArrayTest::Spec& spec)
+{
+	std::stringstream name;
+
+	name
+		<< spec.arrays.size();
+
+	for (int arrayNdx = 0; arrayNdx < (int)spec.arrays.size(); arrayNdx++)
+	{
+		name
+			<< "_"
+			<< Array::inputTypeToString(spec.arrays[arrayNdx].inputType)
+			<< spec.arrays[arrayNdx].componentCount << "_"
+			<< Array::outputTypeToString(spec.arrays[arrayNdx].outputType);
+	}
+
+	return name.str();
+}
+
+void MultiVertexArrayOutputTests::init (void)
+{
+	// Test different input types, with multiple arrays
+	int arrayCounts[] = {3};
+
+	MultiVertexArrayTest::Spec spec;
+
+	spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+	spec.drawCount	= 256;
+	spec.first		= 0;
+
+	for (int arrayCountNdx = 0; arrayCountNdx < DE_LENGTH_OF_ARRAY(arrayCounts); arrayCountNdx++)
+		addInputTypeCases(spec, arrayCounts[arrayCountNdx]);
+}
+
+void MultiVertexArrayOutputTests::addInputTypeCases (MultiVertexArrayTest::Spec spec, int depth)
+{
+	if (depth == 0)
+	{
+		std::string name = getTestName(spec);
+		std::string desc = getTestName(spec);
+		addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), desc.c_str()));
+		return;
+	}
+
+	Array::InputType inputTypes[] = {Array::INPUTTYPE_FIXED, Array::INPUTTYPE_BYTE, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_UNSIGNED_BYTE, Array::INPUTTYPE_UNSIGNED_SHORT};
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+														Array::OUTPUTTYPE_VEC2,
+														Array::STORAGE_USER,
+														Array::USAGE_DYNAMIC_DRAW,
+														2,
+														0,
+														0,
+														false,
+														GLValue::getMinValue(inputTypes[inputTypeNdx]),
+														GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+		MultiVertexArrayTest::Spec _spec = spec;
+		_spec.arrays.push_back(arraySpec);
+		addInputTypeCases(_spec, depth-1);
+	}
+}
+
+class MultiVertexArrayTestGroup : public TestCaseGroup
+{
+public:
+									MultiVertexArrayTestGroup	(Context& context);
+	virtual							~MultiVertexArrayTestGroup	(void);
+
+	virtual void					init						(void);
+
+private:
+									MultiVertexArrayTestGroup	(const MultiVertexArrayTestGroup& other);
+	MultiVertexArrayTestGroup&		operator=					(const MultiVertexArrayTestGroup& other);
+};
+
+MultiVertexArrayTestGroup::MultiVertexArrayTestGroup (Context& context)
+	: TestCaseGroup(context, "multiple_attributes", "Multiple vertex atributes")
+{
+}
+
+MultiVertexArrayTestGroup::~MultiVertexArrayTestGroup (void)
+{
+}
+
+void MultiVertexArrayTestGroup::init (void)
+{
+	addChild(new MultiVertexArrayCountTests(m_context));
+	addChild(new MultiVertexArrayStorageTests(m_context));
+	addChild(new MultiVertexArrayStrideTests(m_context));
+	addChild(new MultiVertexArrayOutputTests(m_context));
+}
+
+VertexArrayTestGroup::VertexArrayTestGroup (Context& context)
+	: TestCaseGroup(context, "vertex_arrays", "Vertex array and array tests")
+{
+}
+
+VertexArrayTestGroup::~VertexArrayTestGroup (void)
+{
+}
+
+void VertexArrayTestGroup::init (void)
+{
+	addChild(new SingleVertexArrayTestGroup(m_context));
+	addChild(new MultiVertexArrayTestGroup(m_context));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fVertexArrayTest.hpp b/modules/gles3/functional/es3fVertexArrayTest.hpp
new file mode 100644
index 0000000..d3f2de8
--- /dev/null
+++ b/modules/gles3/functional/es3fVertexArrayTest.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FVERTEXARRAYTEST_HPP
+#define _ES3FVERTEXARRAYTEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array and buffer tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class VertexArrayTestGroup : public TestCaseGroup
+{
+public:
+							VertexArrayTestGroup	(Context& context);
+	virtual					~VertexArrayTestGroup	(void);
+
+	virtual void			init					(void);
+
+private:
+							VertexArrayTestGroup	(const VertexArrayTestGroup& other);
+	VertexArrayTestGroup&	operator=				(const VertexArrayTestGroup& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FVERTEXARRAYTEST_HPP
diff --git a/modules/gles3/functional/es3fVertexTextureTests.cpp b/modules/gles3/functional/es3fVertexTextureTests.cpp
new file mode 100644
index 0000000..7528abf
--- /dev/null
+++ b/modules/gles3/functional/es3fVertexTextureTests.cpp
@@ -0,0 +1,2009 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex texture tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3fVertexTextureTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+
+#include <string>
+#include <vector>
+
+#include <limits>
+
+#include "glw.h"
+
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::Mat3;
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+
+using namespace gls::TextureTestUtil;
+
+using gls::TextureTestUtil::TEXTURETYPE_2D;
+using gls::TextureTestUtil::TEXTURETYPE_CUBE;
+using gls::TextureTestUtil::TEXTURETYPE_2D_ARRAY;
+using gls::TextureTestUtil::TEXTURETYPE_3D;
+
+namespace gles3
+{
+namespace Functional
+{
+
+static const int WIDTH_2D_ARRAY				= 128;
+static const int HEIGHT_2D_ARRAY			= 128;
+static const int LAYERS_2D_ARRAY			= 8;
+
+static const int WIDTH_3D					= 64;
+static const int HEIGHT_3D					= 64;
+static const int DEPTH_3D					= 64;
+
+// The 2D case draws four images.
+static const int MAX_2D_RENDER_WIDTH		= 128*2;
+static const int MAX_2D_RENDER_HEIGHT		= 128*2;
+
+// The cube map case draws four 3-by-2 image groups.
+static const int MAX_CUBE_RENDER_WIDTH		= 28*2*3;
+static const int MAX_CUBE_RENDER_HEIGHT		= 28*2*2;
+
+static const int MAX_2D_ARRAY_RENDER_WIDTH	= 128*2;
+static const int MAX_2D_ARRAY_RENDER_HEIGHT	= 128*2;
+
+static const int MAX_3D_RENDER_WIDTH		= 128*2;
+static const int MAX_3D_RENDER_HEIGHT		= 128*2;
+
+static const int GRID_SIZE_2D				= 127;
+static const int GRID_SIZE_CUBE				= 63;
+static const int GRID_SIZE_2D_ARRAY			= 127;
+static const int GRID_SIZE_3D				= 127;
+
+// Helpers for making texture coordinates "safe", i.e. move them further from coordinate bounary.
+
+// Moves x towards the closest K+targetFraction, where K is an integer.
+// E.g. moveTowardsFraction(x, 0.5f) moves x away from integer boundaries.
+static inline float moveTowardsFraction (float x, float targetFraction)
+{
+	const float strictness = 0.5f;
+	DE_ASSERT(0.0f < strictness && strictness <= 1.0f);
+	DE_ASSERT(de::inBounds(targetFraction, 0.0f, 1.0f));
+	const float y = x + 0.5f - targetFraction;
+	return deFloatFloor(y) + deFloatFrac(y)*(1.0f-strictness) + strictness*0.5f - 0.5f + targetFraction;
+}
+
+static inline float safeCoord (float raw, int scale, float fraction)
+{
+	const float scaleFloat = (float)scale;
+	return moveTowardsFraction(raw*scaleFloat, fraction) / scaleFloat;
+}
+
+template <int Size>
+static inline tcu::Vector<float, Size> safeCoords (const tcu::Vector<float, Size>& raw, const tcu::Vector<int, Size>& scale, const tcu::Vector<float, Size>& fraction)
+{
+	tcu::Vector<float, Size> result;
+	for (int i = 0; i < Size; i++)
+		result[i] = safeCoord(raw[i], scale[i], fraction[i]);
+	return result;
+}
+
+static inline Vec2 safe2DTexCoords (const Vec2& raw, const IVec2& textureSize)
+{
+	return safeCoords(raw, textureSize, Vec2(0.5f));
+}
+
+static inline Vec3 safe2DArrayTexCoords (const Vec3& raw, const IVec3& textureSize)
+{
+	return safeCoords(raw, textureSize, Vec3(0.5f, 0.5f, 0.0f));
+}
+
+static inline Vec3 safe3DTexCoords (const Vec3& raw, const IVec3& textureSize)
+{
+	return safeCoords(raw, textureSize, Vec3(0.5f));
+}
+
+namespace
+{
+
+struct Rect
+{
+			Rect	(int x_, int y_, int w_, int h_) : x(x_), y(y_), w(w_), h(h_) {}
+	IVec2	pos		(void) const { return IVec2(x, y); }
+	IVec2	size	(void) const { return IVec2(w, h); }
+
+	int		x;
+	int		y;
+	int		w;
+	int		h;
+};
+
+template <TextureType> struct TexTypeTcuClass;
+template <> struct TexTypeTcuClass<TEXTURETYPE_2D>			{ typedef tcu::Texture2D		t; };
+template <> struct TexTypeTcuClass<TEXTURETYPE_CUBE>		{ typedef tcu::TextureCube		t; };
+template <> struct TexTypeTcuClass<TEXTURETYPE_2D_ARRAY>	{ typedef tcu::Texture2DArray	t; };
+template <> struct TexTypeTcuClass<TEXTURETYPE_3D>			{ typedef tcu::Texture3D		t; };
+
+template <TextureType> struct TexTypeSizeDims;
+template <> struct TexTypeSizeDims<TEXTURETYPE_2D>			{ enum { V = 2 }; };
+template <> struct TexTypeSizeDims<TEXTURETYPE_CUBE>		{ enum { V = 2 }; };
+template <> struct TexTypeSizeDims<TEXTURETYPE_2D_ARRAY>	{ enum { V = 3 }; };
+template <> struct TexTypeSizeDims<TEXTURETYPE_3D>			{ enum { V = 3 }; };
+
+template <TextureType> struct TexTypeCoordDims;
+template <> struct TexTypeCoordDims<TEXTURETYPE_2D>			{ enum { V = 2 }; };
+template <> struct TexTypeCoordDims<TEXTURETYPE_CUBE>		{ enum { V = 3 }; };
+template <> struct TexTypeCoordDims<TEXTURETYPE_2D_ARRAY>	{ enum { V = 3 }; };
+template <> struct TexTypeCoordDims<TEXTURETYPE_3D>			{ enum { V = 3 }; };
+
+template <TextureType TexType> struct TexTypeSizeIVec		{ typedef tcu::Vector<int,		TexTypeSizeDims<TexType>::V>	t; };
+template <TextureType TexType> struct TexTypeCoordVec		{ typedef tcu::Vector<float,	TexTypeCoordDims<TexType>::V>	t; };
+
+template <TextureType> struct TexTypeCoordParams;
+
+template <> struct
+TexTypeCoordParams<TEXTURETYPE_2D>
+{
+	Vec2 scale;
+	Vec2 bias;
+
+	TexTypeCoordParams (const Vec2& scale_, const Vec2& bias_) : scale(scale_), bias(bias_) {}
+};
+
+template <> struct
+TexTypeCoordParams<TEXTURETYPE_CUBE>
+{
+	Vec2			scale;
+	Vec2			bias;
+	tcu::CubeFace	face;
+
+	TexTypeCoordParams (const Vec2& scale_, const Vec2& bias_, tcu::CubeFace face_) : scale(scale_), bias(bias_), face(face_) {}
+};
+
+template <> struct
+TexTypeCoordParams<TEXTURETYPE_2D_ARRAY>
+{
+	Mat3 transform;
+
+	TexTypeCoordParams (const Mat3& transform_) : transform(transform_) {}
+};
+
+template <> struct
+TexTypeCoordParams<TEXTURETYPE_3D>
+{
+	Mat3 transform;
+
+	TexTypeCoordParams (const Mat3& transform_) : transform(transform_) {}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Quad grid class containing position and texture coordinate data.
+ *
+ * A quad grid of size S means a grid consisting of S*S quads (S rows and
+ * S columns). The quads are rectangles with main axis aligned sides, and
+ * each consists of two triangles. Note that although there are only
+ * (S+1)*(S+1) distinct vertex positions, there are S*S*4 distinct vertices
+ * because we want texture coordinates to be constant across the vertices
+ * of a quad (to avoid interpolation issues), and thus each quad needs its
+ * own 4 vertices.
+ *
+ * Pointers returned by get*Ptr() are suitable for gl calls such as
+ * glVertexAttribPointer() (for position and tex coord) or glDrawElements()
+ * (for indices).
+ *//*--------------------------------------------------------------------*/
+template <TextureType TexType>
+class PosTexCoordQuadGrid
+{
+private:
+	enum { TEX_COORD_DIMS = TexTypeCoordDims <TexType>::V };
+	typedef typename TexTypeCoordVec<TexType>::t	TexCoordVec;
+	typedef typename TexTypeSizeIVec<TexType>::t	TexSizeIVec;
+	typedef TexTypeCoordParams<TexType>				TexCoordParams;
+
+public:
+							PosTexCoordQuadGrid		(int gridSize, const IVec2& renderSize, const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords);
+
+	int						getSize					(void) const { return m_gridSize; }
+	Vec4					getQuadLDRU				(int col, int row) const; //!< Vec4(leftX, downY, rightX, upY)
+	const TexCoordVec&		getQuadTexCoord			(int col, int row) const;
+
+	int						getNumIndices			(void) const { return m_gridSize*m_gridSize*3*2; }
+	const float*			getPositionPtr			(void) const { DE_STATIC_ASSERT(sizeof(Vec2) == 2*sizeof(float)); return (float*)&m_positions[0]; }
+	const float*			getTexCoordPtr			(void) const { DE_STATIC_ASSERT(sizeof(TexCoordVec) == TEX_COORD_DIMS*(int)sizeof(float)); return (float*)&m_texCoords[0]; }
+	const deUint16*			getIndexPtr				(void) const { return &m_indices[0]; }
+
+private:
+	void					initializeTexCoords		(const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords);
+
+	const int				m_gridSize;
+	vector<Vec2>			m_positions;
+	vector<TexCoordVec>		m_texCoords;
+	vector<deUint16>		m_indices;
+};
+
+template <TextureType TexType>
+Vec4 PosTexCoordQuadGrid<TexType>::getQuadLDRU (int col, int row) const
+{
+	int ndx00 = (row*m_gridSize + col) * 4;
+	int ndx11 = ndx00 + 3;
+
+	return Vec4(m_positions[ndx00].x(),
+				m_positions[ndx00].y(),
+				m_positions[ndx11].x(),
+				m_positions[ndx11].y());
+}
+
+template <TextureType TexType>
+const typename TexTypeCoordVec<TexType>::t& PosTexCoordQuadGrid<TexType>::getQuadTexCoord (int col, int row) const
+{
+	return m_texCoords[(row*m_gridSize + col) * 4];
+}
+
+template <TextureType TexType>
+PosTexCoordQuadGrid<TexType>::PosTexCoordQuadGrid (int gridSize, const IVec2& renderSize, const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
+	: m_gridSize(gridSize)
+{
+	DE_ASSERT(m_gridSize > 0 && m_gridSize*m_gridSize <= (int)std::numeric_limits<deUint16>::max() + 1);
+
+	const float gridSizeFloat = (float)m_gridSize;
+
+	m_positions.reserve(m_gridSize*m_gridSize*4);
+	m_indices.reserve(m_gridSize*m_gridSize*3*2);
+
+	for (int y = 0; y < m_gridSize; y++)
+	for (int x = 0; x < m_gridSize; x++)
+	{
+		float fx0 = (float)(x+0) / gridSizeFloat;
+		float fx1 = (float)(x+1) / gridSizeFloat;
+		float fy0 = (float)(y+0) / gridSizeFloat;
+		float fy1 = (float)(y+1) / gridSizeFloat;
+
+		Vec2 quadVertices[4] = { Vec2(fx0, fy0), Vec2(fx1, fy0), Vec2(fx0, fy1), Vec2(fx1, fy1) };
+
+		int firstNdx = (int)m_positions.size();
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(quadVertices); i++)
+			m_positions.push_back(safeCoords(quadVertices[i], renderSize, Vec2(0.0f)) * 2.0f - 1.0f);
+
+		m_indices.push_back(firstNdx + 0);
+		m_indices.push_back(firstNdx + 1);
+		m_indices.push_back(firstNdx + 2);
+
+		m_indices.push_back(firstNdx + 1);
+		m_indices.push_back(firstNdx + 3);
+		m_indices.push_back(firstNdx + 2);
+	}
+
+	m_texCoords.reserve(m_gridSize*m_gridSize*4);
+	initializeTexCoords(textureSize, texCoordParams, useSafeTexCoords);
+
+	DE_ASSERT((int)m_positions.size() == m_gridSize*m_gridSize*4);
+	DE_ASSERT((int)m_indices.size() == m_gridSize*m_gridSize*3*2);
+	DE_ASSERT((int)m_texCoords.size() == m_gridSize*m_gridSize*4);
+}
+
+template <>
+void PosTexCoordQuadGrid<TEXTURETYPE_2D>::initializeTexCoords (const IVec2& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
+{
+	DE_ASSERT(m_texCoords.empty());
+
+	const float gridSizeFloat = (float)m_gridSize;
+
+	for (int y = 0; y < m_gridSize; y++)
+	for (int x = 0; x < m_gridSize; x++)
+	{
+		Vec2 rawCoord = Vec2((float)x / gridSizeFloat, (float)y / gridSizeFloat) * texCoordParams.scale + texCoordParams.bias;
+
+		for (int i = 0; i < 4; i++)
+			m_texCoords.push_back(useSafeTexCoords ? safe2DTexCoords(rawCoord, textureSize) : rawCoord);
+	}
+}
+
+template <>
+void PosTexCoordQuadGrid<TEXTURETYPE_CUBE>::initializeTexCoords (const IVec2& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
+{
+	DE_ASSERT(m_texCoords.empty());
+
+	const float		gridSizeFloat	= (float)m_gridSize;
+	vector<float>	texBoundaries;
+	computeQuadTexCoordCube(texBoundaries, texCoordParams.face);
+	const Vec3		coordA			= Vec3(texBoundaries[0], texBoundaries[1], texBoundaries[2]);
+	const Vec3		coordB			= Vec3(texBoundaries[3], texBoundaries[4], texBoundaries[5]);
+	const Vec3		coordC			= Vec3(texBoundaries[6], texBoundaries[7], texBoundaries[8]);
+	const Vec3		coordAB			= coordB - coordA;
+	const Vec3		coordAC			= coordC - coordA;
+
+	for (int y = 0; y < m_gridSize; y++)
+	for (int x = 0; x < m_gridSize; x++)
+	{
+		const Vec2 rawFaceCoord		= texCoordParams.scale * Vec2((float)x / gridSizeFloat, (float)y / gridSizeFloat) + texCoordParams.bias;
+		const Vec2 safeFaceCoord	= useSafeTexCoords ? safe2DTexCoords(rawFaceCoord, textureSize) : rawFaceCoord;
+		const Vec3 texCoord			= coordA + coordAC*safeFaceCoord.x() + coordAB*safeFaceCoord.y();
+
+		for (int i = 0; i < 4; i++)
+			m_texCoords.push_back(texCoord);
+	}
+}
+
+template <>
+void PosTexCoordQuadGrid<TEXTURETYPE_2D_ARRAY>::initializeTexCoords (const IVec3& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
+{
+	DE_ASSERT(m_texCoords.empty());
+
+	const float gridSizeFloat = (float)m_gridSize;
+
+	for (int y = 0; y < m_gridSize; y++)
+	for (int x = 0; x < m_gridSize; x++)
+	{
+		const Vec3 rawCoord = texCoordParams.transform * Vec3((float)x / gridSizeFloat, (float)y / gridSizeFloat, 1.0f);
+
+		for (int i = 0; i < 4; i++)
+			m_texCoords.push_back(useSafeTexCoords ? safe2DArrayTexCoords(rawCoord, textureSize) : rawCoord);
+	}
+}
+
+template <>
+void PosTexCoordQuadGrid<TEXTURETYPE_3D>::initializeTexCoords (const IVec3& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
+{
+	DE_ASSERT(m_texCoords.empty());
+
+	const float gridSizeFloat = (float)m_gridSize;
+
+	for (int y = 0; y < m_gridSize; y++)
+	for (int x = 0; x < m_gridSize; x++)
+	{
+		Vec3 rawCoord = texCoordParams.transform * Vec3((float)x / gridSizeFloat, (float)y / gridSizeFloat, 1.0f);
+
+		for (int i = 0; i < 4; i++)
+			m_texCoords.push_back(useSafeTexCoords ? safe3DTexCoords(rawCoord, textureSize) : rawCoord);
+	}
+}
+
+} // anonymous
+
+static inline bool isLevelNearest (deUint32 filter)
+{
+	return filter == GL_NEAREST || filter == GL_NEAREST_MIPMAP_NEAREST || filter == GL_NEAREST_MIPMAP_LINEAR;
+}
+
+static inline IVec2 getTextureSize (const glu::Texture2D& tex)
+{
+	const tcu::Texture2D& ref = tex.getRefTexture();
+	return IVec2(ref.getWidth(), ref.getHeight());
+}
+
+static inline IVec2 getTextureSize (const glu::TextureCube& tex)
+{
+	const tcu::TextureCube& ref = tex.getRefTexture();
+	return IVec2(ref.getSize(), ref.getSize());
+}
+
+static inline IVec3 getTextureSize (const glu::Texture2DArray& tex)
+{
+	const tcu::Texture2DArray& ref = tex.getRefTexture();
+	return IVec3(ref.getWidth(), ref.getHeight(), ref.getNumLayers());
+}
+
+static inline IVec3 getTextureSize (const glu::Texture3D& tex)
+{
+	const tcu::Texture3D& ref = tex.getRefTexture();
+	return IVec3(ref.getWidth(), ref.getHeight(), ref.getDepth());
+}
+
+template <TextureType TexType>
+static void setPixelColors (const vector<Vec4>& quadColors, const Rect& region, const PosTexCoordQuadGrid<TexType>& grid, tcu::Surface& dst)
+{
+	const int gridSize = grid.getSize();
+
+	for (int y = 0; y < gridSize; y++)
+	for (int x = 0; x < gridSize; x++)
+	{
+		const Vec4	color	= quadColors[y*gridSize + x];
+		const Vec4	ldru	= grid.getQuadLDRU(x, y) * 0.5f + 0.5f; // [-1, 1] -> [0, 1]
+		const int	ix0		= deCeilFloatToInt32(ldru.x() * (float)region.w - 0.5f);
+		const int	ix1		= deCeilFloatToInt32(ldru.z() * (float)region.w - 0.5f);
+		const int	iy0		= deCeilFloatToInt32(ldru.y() * (float)region.h - 0.5f);
+		const int	iy1		= deCeilFloatToInt32(ldru.w() * (float)region.h - 0.5f);
+
+		for (int iy = iy0; iy < iy1; iy++)
+		for (int ix = ix0; ix < ix1; ix++)
+		{
+			DE_ASSERT(deInBounds32(ix + region.x, 0, dst.getWidth()));
+			DE_ASSERT(deInBounds32(iy + region.y, 0, dst.getHeight()));
+
+			dst.setPixel(ix + region.x, iy + region.y, toRGBA(color));
+		}
+	}
+}
+
+static inline Vec4 sample (const tcu::Texture2D&		tex, const Vec2& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), lod); }
+static inline Vec4 sample (const tcu::TextureCube&		tex, const Vec3& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), coord.z(), lod); }
+static inline Vec4 sample (const tcu::Texture2DArray&	tex, const Vec3& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), coord.z(), lod); }
+static inline Vec4 sample (const tcu::Texture3D&		tex, const Vec3& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), coord.z(), lod); }
+
+template <TextureType TexType>
+void computeReference (const typename TexTypeTcuClass<TexType>::t& texture, float lod, const tcu::Sampler& sampler, const PosTexCoordQuadGrid<TexType>& grid, tcu::Surface& dst, const Rect& dstRegion)
+{
+	const int		gridSize	= grid.getSize();
+	vector<Vec4>	quadColors	(gridSize*gridSize);
+
+	for (int y = 0; y < gridSize; y++)
+	for (int x = 0; x < gridSize; x++)
+	{
+		const int										ndx		= y*gridSize + x;
+		const typename TexTypeCoordVec<TexType>::t&		coord	= grid.getQuadTexCoord(x, y);
+
+		quadColors[ndx] = sample(texture, coord, lod, sampler);
+	}
+
+	setPixelColors(quadColors, dstRegion, grid, dst);
+}
+
+static bool compareImages (const glu::RenderContext& renderCtx, tcu::TestLog& log, const tcu::Surface& ref, const tcu::Surface& res)
+{
+	DE_ASSERT(renderCtx.getRenderTarget().getNumSamples() == 0);
+
+	const tcu::RGBA threshold = renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(15,15,15,15);
+	return tcu::pixelThresholdCompare(log, "Result", "Image compare result", ref, res, threshold, tcu::COMPARE_LOG_RESULT);
+}
+
+class Vertex2DTextureCase : public TestCase
+{
+public:
+								Vertex2DTextureCase		(Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT);
+								~Vertex2DTextureCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+	typedef PosTexCoordQuadGrid<TEXTURETYPE_2D> Grid;
+
+								Vertex2DTextureCase		(const Vertex2DTextureCase& other);
+	Vertex2DTextureCase&		operator=				(const Vertex2DTextureCase& other);
+
+	float						calculateLod			(const Vec2& texScale, const Vec2& dstSize, int textureNdx) const;
+	void						setupShaderInputs		(int textureNdx, float lod, const Grid& grid) const;
+	void						renderCell				(int textureNdx, float lod, const Grid& grid) const;
+	void						computeReferenceCell	(int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;
+
+	const deUint32				m_minFilter;
+	const deUint32				m_magFilter;
+	const deUint32				m_wrapS;
+	const deUint32				m_wrapT;
+
+	const glu::ShaderProgram*	m_program;
+	glu::Texture2D*				m_textures[2];	// 2 textures, a gradient texture and a grid texture.
+};
+
+Vertex2DTextureCase::Vertex2DTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT)
+	: TestCase				(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_program				(DE_NULL)
+{
+	m_textures[0] = DE_NULL;
+	m_textures[1] = DE_NULL;
+}
+
+Vertex2DTextureCase::~Vertex2DTextureCase(void)
+{
+	Vertex2DTextureCase::deinit();
+}
+
+void Vertex2DTextureCase::init (void)
+{
+	const char* const vertexShader =
+		"#version 300 es\n"
+		"in highp vec2 a_position;\n"
+		"in highp vec2 a_texCoord;\n"
+		"uniform highp sampler2D u_texture;\n"
+		"uniform highp float u_lod;\n"
+		"out mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		"	v_color = textureLod(u_texture, a_texCoord, u_lod);\n"
+		"}\n";
+
+	const char* const fragmentShader =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+		"in mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	dEQP_FragColor = v_color;\n"
+		"}\n";
+
+	if (m_context.getRenderTarget().getNumSamples() != 0)
+		throw tcu::NotSupportedError("MSAA config not supported by this test");
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShader, fragmentShader));
+
+	if(!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+
+		GLint maxVertexTextures;
+		glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);
+
+		if (maxVertexTextures < 1)
+			throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
+		else
+			TCU_FAIL("Failed to compile shader");
+	}
+
+	// Make the textures.
+	try
+	{
+		// Compute suitable power-of-two sizes (for mipmaps).
+		const int texWidth		= 1 << deLog2Ceil32(MAX_2D_RENDER_WIDTH / 2);
+		const int texHeight		= 1 << deLog2Ceil32(MAX_2D_RENDER_HEIGHT / 2);
+
+		for (int i = 0; i < 2; i++)
+		{
+			DE_ASSERT(!m_textures[i]);
+			m_textures[i] = new glu::Texture2D(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight);
+		}
+
+		const bool						mipmaps		= (deIsPowerOfTwo32(texWidth) && deIsPowerOfTwo32(texHeight));
+		const int						numLevels	= mipmaps ? deLog2Floor32(de::max(texWidth, texHeight))+1 : 1;
+		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+		const Vec4						cBias		= fmtInfo.valueMin;
+		const Vec4						cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+		// Fill first with gradient texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			const Vec4 gMin = Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
+			const Vec4 gMax = Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;
+
+			m_textures[0]->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
+		}
+
+		// Fill second with grid texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			const deUint32 step		= 0x00ffffff / numLevels;
+			const deUint32 rgb		= step*levelNdx;
+			const deUint32 colorA	= 0xff000000 | rgb;
+			const deUint32 colorB	= 0xff000000 | ~rgb;
+
+			m_textures[1]->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+		}
+
+		// Upload.
+		for (int i = 0; i < 2; i++)
+			m_textures[i]->upload();
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		Vertex2DTextureCase::deinit();
+		throw;
+	}
+}
+
+void Vertex2DTextureCase::deinit (void)
+{
+	for (int i = 0; i < 2; i++)
+	{
+		delete m_textures[i];
+		m_textures[i] = DE_NULL;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+float Vertex2DTextureCase::calculateLod (const Vec2& texScale, const Vec2& dstSize, int textureNdx) const
+{
+	const tcu::Texture2D&		refTexture	= m_textures[textureNdx]->getRefTexture();
+	const Vec2					srcSize		= Vec2((float)refTexture.getWidth(), (float)refTexture.getHeight());
+	const Vec2					sizeRatio	= texScale*srcSize / dstSize;
+
+	// \note In this particular case dv/dx and du/dy are zero, simplifying the expression.
+	return deFloatLog2(de::max(sizeRatio.x(), sizeRatio.y()));
+}
+
+Vertex2DTextureCase::IterateResult Vertex2DTextureCase::iterate (void)
+{
+	const int	viewportWidth		= deMin32(m_context.getRenderTarget().getWidth(), MAX_2D_RENDER_WIDTH);
+	const int	viewportHeight		= deMin32(m_context.getRenderTarget().getHeight(), MAX_2D_RENDER_HEIGHT);
+
+	const int	viewportXOffsetMax	= m_context.getRenderTarget().getWidth() - viewportWidth;
+	const int	viewportYOffsetMax	= m_context.getRenderTarget().getHeight() - viewportHeight;
+
+	de::Random	rnd					(deStringHash(getName()));
+
+	const int	viewportXOffset		= rnd.getInt(0, viewportXOffsetMax);
+	const int	viewportYOffset		= rnd.getInt(0, viewportYOffsetMax);
+
+	glUseProgram(m_program->getProgram());
+
+	// Divide viewport into 4 cells.
+	const int leftWidth		= viewportWidth / 2;
+	const int rightWidth	= viewportWidth - leftWidth;
+	const int bottomHeight	= viewportHeight / 2;
+	const int topHeight		= viewportHeight - bottomHeight;
+
+	// Clear.
+	glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	// Texture scaling and offsetting vectors.
+	const Vec2 texMinScale		(+1.8f, +1.8f);
+	const Vec2 texMinOffset		(-0.3f, -0.2f);
+	const Vec2 texMagScale		(+0.3f, +0.3f);
+	const Vec2 texMagOffset		(+0.9f, +0.8f);
+
+	// Surface for the reference image.
+	tcu::Surface refImage(viewportWidth, viewportHeight);
+
+	{
+		const struct Render
+		{
+			const Rect	region;
+			int			textureNdx;
+			const Vec2	texCoordScale;
+			const Vec2	texCoordOffset;
+			Render (const Rect& r, int tN, const Vec2& tS, const Vec2& tO) : region(r), textureNdx(tN), texCoordScale(tS), texCoordOffset(tO) {}
+		} renders[] =
+		{
+			Render(Rect(0,				0,				leftWidth,	bottomHeight),	0, texMinScale, texMinOffset),
+			Render(Rect(leftWidth,		0,				rightWidth,	bottomHeight),	0, texMagScale, texMagOffset),
+			Render(Rect(0,				bottomHeight,	leftWidth,	topHeight),		1, texMinScale, texMinOffset),
+			Render(Rect(leftWidth,		bottomHeight,	rightWidth,	topHeight),		1, texMagScale, texMagOffset)
+		};
+
+		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
+		{
+			const Render&	rend				= renders[renderNdx];
+			const float		lod					= calculateLod(rend.texCoordScale, rend.region.size().asFloat(), rend.textureNdx);
+			const bool		useSafeTexCoords	= isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
+			const Grid		grid				(GRID_SIZE_2D, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
+												 TexTypeCoordParams<TEXTURETYPE_2D>(rend.texCoordScale, rend.texCoordOffset), useSafeTexCoords);
+
+			glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
+			renderCell				(rend.textureNdx, lod, grid);
+			computeReferenceCell	(rend.textureNdx, lod, grid, refImage, rend.region);
+		}
+	}
+
+	// Read back rendered results.
+	tcu::Surface resImage(viewportWidth, viewportHeight);
+	glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());
+
+	glUseProgram(0);
+
+	// Compare and log.
+	{
+		const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Image comparison failed");
+	}
+
+	return STOP;
+}
+
+void Vertex2DTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
+{
+	const deUint32 programID = m_program->getProgram();
+
+	// SETUP ATTRIBUTES.
+
+	{
+		const int positionLoc = glGetAttribLocation(programID, "a_position");
+		if (positionLoc != -1)
+		{
+			glEnableVertexAttribArray(positionLoc);
+			glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
+		}
+	}
+
+	{
+		const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
+		if (texCoordLoc != -1)
+		{
+			glEnableVertexAttribArray(texCoordLoc);
+			glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
+		}
+	}
+
+	// SETUP UNIFORMS.
+
+	{
+		const int lodLoc = glGetUniformLocation(programID, "u_lod");
+		if (lodLoc != -1)
+			glUniform1f(lodLoc, lod);
+	}
+
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_2D, m_textures[textureNdx]->getGLTexture());
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	{
+		const int texLoc = glGetUniformLocation(programID, "u_texture");
+		if (texLoc != -1)
+			glUniform1i(texLoc, 0);
+	}
+}
+
+// Renders one sub-image with given parameters.
+void Vertex2DTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
+{
+	setupShaderInputs(textureNdx, lod, grid);
+	glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
+}
+
+void Vertex2DTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
+{
+	computeReference(m_textures[textureNdx]->getRefTexture(), lod, glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter), grid, dst, dstRegion);
+}
+
+class VertexCubeTextureCase : public TestCase
+{
+public:
+								VertexCubeTextureCase	(Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT);
+								~VertexCubeTextureCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+	typedef PosTexCoordQuadGrid<TEXTURETYPE_CUBE> Grid;
+
+								VertexCubeTextureCase	(const VertexCubeTextureCase& other);
+	VertexCubeTextureCase&		operator=				(const VertexCubeTextureCase& other);
+
+	float						calculateLod			(const Vec2& texScale, const Vec2& dstSize, int textureNdx) const;
+	void						setupShaderInputs		(int textureNdx, float lod, const Grid& grid) const;
+	void						renderCell				(int textureNdx, float lod, const Grid& grid) const;
+	void						computeReferenceCell	(int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;
+
+	const deUint32				m_minFilter;
+	const deUint32				m_magFilter;
+	const deUint32				m_wrapS;
+	const deUint32				m_wrapT;
+
+	const glu::ShaderProgram*	m_program;
+	glu::TextureCube*			m_textures[2];	// 2 textures, a gradient texture and a grid texture.
+};
+
+VertexCubeTextureCase::VertexCubeTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT)
+	: TestCase				(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_program				(DE_NULL)
+{
+	m_textures[0] = DE_NULL;
+	m_textures[1] = DE_NULL;
+}
+
+VertexCubeTextureCase::~VertexCubeTextureCase(void)
+{
+	VertexCubeTextureCase::deinit();
+}
+
+void VertexCubeTextureCase::init (void)
+{
+	const char* const vertexShader =
+		"#version 300 es\n"
+		"in highp vec2 a_position;\n"
+		"in highp vec3 a_texCoord;\n"
+		"uniform highp samplerCube u_texture;\n"
+		"uniform highp float u_lod;\n"
+		"out mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		"	v_color = textureLod(u_texture, a_texCoord, u_lod);\n"
+		"}\n";
+
+	const char* const fragmentShader =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+		"in mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	dEQP_FragColor = v_color;\n"
+		"}\n";
+
+	if (m_context.getRenderTarget().getNumSamples() != 0)
+		throw tcu::NotSupportedError("MSAA config not supported by this test");
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShader, fragmentShader));
+
+	if(!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+
+		GLint maxVertexTextures;
+		glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);
+
+		if (maxVertexTextures < 1)
+			throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
+		else
+			TCU_FAIL("Failed to compile shader");
+	}
+
+	// Make the textures.
+	try
+	{
+		// Compute suitable power-of-two sizes (for mipmaps).
+		const int texWidth		= 1 << deLog2Ceil32(MAX_CUBE_RENDER_WIDTH / 3 / 2);
+		const int texHeight		= 1 << deLog2Ceil32(MAX_CUBE_RENDER_HEIGHT / 2 / 2);
+
+		DE_ASSERT(texWidth == texHeight);
+		DE_UNREF(texHeight);
+
+		for (int i = 0; i < 2; i++)
+		{
+			DE_ASSERT(!m_textures[i]);
+			m_textures[i] = new glu::TextureCube(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth);
+		}
+
+		const bool						mipmaps		= deIsPowerOfTwo32(texWidth) != DE_FALSE;
+		const int						numLevels	= mipmaps ? deLog2Floor32(texWidth)+1 : 1;
+		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+		const Vec4						cBias		= fmtInfo.valueMin;
+		const Vec4						cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+		// Fill first with gradient texture.
+		static const Vec4 gradients[tcu::CUBEFACE_LAST][2] =
+		{
+			{ Vec4(-1.0f, -1.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
+			{ Vec4( 0.0f, -1.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
+			{ Vec4(-1.0f,  0.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
+			{ Vec4(-1.0f, -1.0f,  0.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
+			{ Vec4(-1.0f, -1.0f, -1.0f, 0.0f), Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
+			{ Vec4( 0.0f,  0.0f,  0.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
+		};
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				m_textures[0]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+				tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0]*cScale + cBias, gradients[face][1]*cScale + cBias);
+			}
+		}
+
+		// Fill second with grid texture.
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+			{
+				const deUint32 step		= 0x00ffffff / (numLevels*tcu::CUBEFACE_LAST);
+				const deUint32 rgb		= step*levelNdx*face;
+				const deUint32 colorA	= 0xff000000 | rgb;
+				const deUint32 colorB	= 0xff000000 | ~rgb;
+
+				m_textures[1]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
+				tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+			}
+		}
+
+		// Upload.
+		for (int i = 0; i < 2; i++)
+			m_textures[i]->upload();
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		VertexCubeTextureCase::deinit();
+		throw;
+	}
+}
+
+void VertexCubeTextureCase::deinit (void)
+{
+	for (int i = 0; i < 2; i++)
+	{
+		delete m_textures[i];
+		m_textures[i] = DE_NULL;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+float VertexCubeTextureCase::calculateLod (const Vec2& texScale, const Vec2& dstSize, int textureNdx) const
+{
+	const tcu::TextureCube&		refTexture	= m_textures[textureNdx]->getRefTexture();
+	const Vec2					srcSize		= Vec2((float)refTexture.getSize(), (float)refTexture.getSize());
+	const Vec2					sizeRatio	= texScale*srcSize / dstSize;
+
+	// \note In this particular case, dv/dx and du/dy are zero, simplifying the expression.
+	return deFloatLog2(de::max(sizeRatio.x(), sizeRatio.y()));
+}
+
+VertexCubeTextureCase::IterateResult VertexCubeTextureCase::iterate (void)
+{
+	const int	viewportWidth		= deMin32(m_context.getRenderTarget().getWidth(), MAX_CUBE_RENDER_WIDTH);
+	const int	viewportHeight		= deMin32(m_context.getRenderTarget().getHeight(), MAX_CUBE_RENDER_HEIGHT);
+
+	const int	viewportXOffsetMax	= m_context.getRenderTarget().getWidth() - viewportWidth;
+	const int	viewportYOffsetMax	= m_context.getRenderTarget().getHeight() - viewportHeight;
+
+	de::Random	rnd					(deStringHash(getName()));
+
+	const int	viewportXOffset		= rnd.getInt(0, viewportXOffsetMax);
+	const int	viewportYOffset		= rnd.getInt(0, viewportYOffsetMax);
+
+	glUseProgram(m_program->getProgram());
+
+	// Divide viewport into 4 areas.
+	const int leftWidth		= viewportWidth / 2;
+	const int rightWidth	= viewportWidth - leftWidth;
+	const int bottomHeight	= viewportHeight / 2;
+	const int topHeight		= viewportHeight - bottomHeight;
+
+	// Clear.
+	glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	// Texture scaling and offsetting vectors.
+	const Vec2 texMinScale		(1.0f, 1.0f);
+	const Vec2 texMinOffset		(0.0f, 0.0f);
+	const Vec2 texMagScale		(0.3f, 0.3f);
+	const Vec2 texMagOffset		(0.5f, 0.3f);
+
+	// Surface for the reference image.
+	tcu::Surface refImage(viewportWidth, viewportHeight);
+
+	// Each of the four areas is divided into 6 cells.
+	const int defCellWidth	= viewportWidth / 2 / 3;
+	const int defCellHeight	= viewportHeight / 2 / 2;
+
+	for (int i = 0; i < tcu::CUBEFACE_LAST; i++)
+	{
+		const int	cellOffsetX			= defCellWidth * (i % 3);
+		const int	cellOffsetY			= defCellHeight * (i / 3);
+		const bool	isRightmostCell		= i == 2 || i == 5;
+		const bool	isTopCell			= i >= 3;
+		const int	leftCellWidth		= isRightmostCell	? leftWidth		- cellOffsetX : defCellWidth;
+		const int	rightCellWidth		= isRightmostCell	? rightWidth	- cellOffsetX : defCellWidth;
+		const int	bottomCellHeight	= isTopCell			? bottomHeight	- cellOffsetY : defCellHeight;
+		const int	topCellHeight		= isTopCell			? topHeight		- cellOffsetY : defCellHeight;
+
+		const struct Render
+		{
+			const Rect	region;
+			int			textureNdx;
+			const Vec2	texCoordScale;
+			const Vec2	texCoordOffset;
+			Render (const Rect& r, int tN, const Vec2& tS, const Vec2& tO) : region(r), textureNdx(tN), texCoordScale(tS), texCoordOffset(tO) {}
+		} renders[] =
+		{
+			Render(Rect(cellOffsetX + 0,			cellOffsetY + 0,				leftCellWidth,	bottomCellHeight),	0, texMinScale, texMinOffset),
+			Render(Rect(cellOffsetX + leftWidth,	cellOffsetY + 0,				rightCellWidth,	bottomCellHeight),	0, texMagScale, texMagOffset),
+			Render(Rect(cellOffsetX + 0,			cellOffsetY + bottomHeight,		leftCellWidth,	topCellHeight),		1, texMinScale, texMinOffset),
+			Render(Rect(cellOffsetX + leftWidth,	cellOffsetY + bottomHeight,		rightCellWidth,	topCellHeight),		1, texMagScale, texMagOffset)
+		};
+
+		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
+		{
+			const Render&	rend				= renders[renderNdx];
+			const float		lod					= calculateLod(rend.texCoordScale, rend.region.size().asFloat(), rend.textureNdx);
+			const bool		useSafeTexCoords	= isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
+			const Grid		grid				(GRID_SIZE_CUBE, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
+												 TexTypeCoordParams<TEXTURETYPE_CUBE>(rend.texCoordScale, rend.texCoordOffset, (tcu::CubeFace)i), useSafeTexCoords);
+
+			glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
+			renderCell				(rend.textureNdx, lod, grid);
+			computeReferenceCell	(rend.textureNdx, lod, grid, refImage, rend.region);
+		}
+	}
+
+	// Read back rendered results.
+	tcu::Surface resImage(viewportWidth, viewportHeight);
+	glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());
+
+	glUseProgram(0);
+
+	// Compare and log.
+	{
+		const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Image comparison failed");
+	}
+
+	return STOP;
+}
+
+void VertexCubeTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
+{
+	const deUint32 programID = m_program->getProgram();
+
+	// SETUP ATTRIBUTES.
+
+	{
+		const int positionLoc = glGetAttribLocation(programID, "a_position");
+		if (positionLoc != -1)
+		{
+			glEnableVertexAttribArray(positionLoc);
+			glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
+		}
+	}
+
+	{
+		const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
+		if (texCoordLoc != -1)
+		{
+			glEnableVertexAttribArray(texCoordLoc);
+			glVertexAttribPointer(texCoordLoc, 3, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
+		}
+	}
+
+	// SETUP UNIFORMS.
+
+	{
+		const int lodLoc = glGetUniformLocation(programID, "u_lod");
+		if (lodLoc != -1)
+			glUniform1f(lodLoc, lod);
+	}
+
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_CUBE_MAP, m_textures[textureNdx]->getGLTexture());
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	{
+		const int texLoc = glGetUniformLocation(programID, "u_texture");
+		if (texLoc != -1)
+			glUniform1i(texLoc, 0);
+	}
+}
+
+// Renders one cube face with given parameters.
+void VertexCubeTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
+{
+	setupShaderInputs(textureNdx, lod, grid);
+	glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
+}
+
+// Computes reference for one cube face with given parameters.
+void VertexCubeTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
+{
+	tcu::Sampler sampler = glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
+	sampler.seamlessCubeMap = true;
+	computeReference(m_textures[textureNdx]->getRefTexture(), lod, sampler, grid, dst, dstRegion);
+}
+
+class Vertex2DArrayTextureCase : public TestCase
+{
+public:
+								Vertex2DArrayTextureCase	(Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT);
+								~Vertex2DArrayTextureCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+	typedef PosTexCoordQuadGrid<TEXTURETYPE_2D_ARRAY> Grid;
+
+								Vertex2DArrayTextureCase	(const Vertex2DArrayTextureCase& other);
+	Vertex2DArrayTextureCase&	operator=					(const Vertex2DArrayTextureCase& other);
+
+	float						calculateLod				(const Mat3& transf, const Vec2& dstSize, int textureNdx) const;
+	void						setupShaderInputs			(int textureNdx, float lod, const Grid& grid) const;
+	void						renderCell					(int textureNdx, float lod, const Grid& grid) const;
+	void						computeReferenceCell		(int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;
+
+	const deUint32				m_minFilter;
+	const deUint32				m_magFilter;
+	const deUint32				m_wrapS;
+	const deUint32				m_wrapT;
+
+	const glu::ShaderProgram*	m_program;
+	glu::Texture2DArray*		m_textures[2];	// 2 textures, a gradient texture and a grid texture.
+};
+
+Vertex2DArrayTextureCase::Vertex2DArrayTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT)
+	: TestCase				(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_program				(DE_NULL)
+{
+	m_textures[0] = DE_NULL;
+	m_textures[1] = DE_NULL;
+}
+
+Vertex2DArrayTextureCase::~Vertex2DArrayTextureCase(void)
+{
+	Vertex2DArrayTextureCase::deinit();
+}
+
+void Vertex2DArrayTextureCase::init (void)
+{
+	const char* const vertexShaderSource =
+		"#version 300 es\n"
+		"in highp vec2 a_position;\n"
+		"in highp vec3 a_texCoord;\n"
+		"uniform highp sampler2DArray u_texture;\n"
+		"uniform highp float u_lod;\n"
+		"out mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		"	v_color = textureLod(u_texture, a_texCoord, u_lod);\n"
+		"}\n";
+
+	const char* const fragmentShaderSource =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+		"in mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	dEQP_FragColor = v_color;\n"
+		"}\n";
+
+	if (m_context.getRenderTarget().getNumSamples() != 0)
+		throw tcu::NotSupportedError("MSAA config not supported by this test");
+
+	// Create shader.
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+	if(!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+
+		GLint maxVertexTextures;
+		glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);
+
+		if (maxVertexTextures < 1)
+			throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
+		else
+			TCU_FAIL("Failed to compile shader");
+	}
+
+	// Make the textures.
+
+	try
+	{
+		const int texWidth	= WIDTH_2D_ARRAY;
+		const int texHeight	= HEIGHT_2D_ARRAY;
+		const int texLayers	= LAYERS_2D_ARRAY;
+
+		for (int i = 0; i < 2; i++)
+		{
+			DE_ASSERT(!m_textures[i]);
+			m_textures[i] = new glu::Texture2DArray(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight, texLayers);
+		}
+
+		const int						numLevels	= deLog2Floor32(de::max(texWidth, texHeight)) + 1;
+		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+		const Vec4						cBias		= fmtInfo.valueMin;
+		const Vec4						cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+		// Fill first with gradient texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			const Vec4 gMin = Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
+			const Vec4 gMax = Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;
+
+			m_textures[0]->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
+		}
+
+		// Fill second with grid texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			const deUint32 step		= 0x00ffffff / numLevels;
+			const deUint32 rgb		= step*levelNdx;
+			const deUint32 colorA	= 0xff000000 | rgb;
+			const deUint32 colorB	= 0xff000000 | ~rgb;
+
+			m_textures[1]->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+		}
+
+		// Upload.
+		for (int i = 0; i < 2; i++)
+			m_textures[i]->upload();
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		Vertex2DArrayTextureCase::deinit();
+		throw;
+	}
+}
+
+void Vertex2DArrayTextureCase::deinit (void)
+{
+	for (int i = 0; i < 2; i++)
+	{
+		delete m_textures[i];
+		m_textures[i] = DE_NULL;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+float Vertex2DArrayTextureCase::calculateLod (const Mat3& transf, const Vec2& dstSize, int textureNdx) const
+{
+	const tcu::Texture2DArray&	refTexture	= m_textures[textureNdx]->getRefTexture();
+	const int					texWidth	= refTexture.getWidth();
+	const int					texHeight	= refTexture.getHeight();
+
+	// Calculate transformed coordinates of three screen corners.
+	const Vec2					trans00		= (transf * Vec3(0.0f, 0.0f, 1.0f)).xy();
+	const Vec2					trans01		= (transf * Vec3(0.0f, 1.0f, 1.0f)).xy();
+	const Vec2					trans10		= (transf * Vec3(1.0f, 0.0f, 1.0f)).xy();
+
+	// Derivates.
+	const float dudx = (trans10.x() - trans00.x()) * (float)texWidth / dstSize.x();
+	const float dudy = (trans01.x() - trans00.x()) * (float)texWidth / dstSize.y();
+	const float dvdx = (trans10.y() - trans00.y()) * (float)texHeight / dstSize.x();
+	const float dvdy = (trans01.y() - trans00.y()) * (float)texHeight / dstSize.y();
+
+	return deFloatLog2(deFloatSqrt(de::max(dudx*dudx + dvdx*dvdx, dudy*dudy + dvdy*dvdy)));
+}
+
+Vertex2DArrayTextureCase::IterateResult Vertex2DArrayTextureCase::iterate (void)
+{
+	const int	viewportWidth		= deMin32(m_context.getRenderTarget().getWidth(), MAX_2D_ARRAY_RENDER_WIDTH);
+	const int	viewportHeight		= deMin32(m_context.getRenderTarget().getHeight(), MAX_2D_ARRAY_RENDER_HEIGHT);
+
+	const int	viewportXOffsetMax	= m_context.getRenderTarget().getWidth() - viewportWidth;
+	const int	viewportYOffsetMax	= m_context.getRenderTarget().getHeight() - viewportHeight;
+
+	de::Random	rnd					(deStringHash(getName()));
+
+	const int	viewportXOffset		= rnd.getInt(0, viewportXOffsetMax);
+	const int	viewportYOffset		= rnd.getInt(0, viewportYOffsetMax);
+
+	glUseProgram(m_program->getProgram());
+
+	// Divide viewport into 4 cells.
+	const int leftWidth		= viewportWidth / 2;
+	const int rightWidth	= viewportWidth - leftWidth;
+	const int bottomHeight	= viewportHeight / 2;
+	const int topHeight		= viewportHeight - bottomHeight;
+
+	// Clear.
+	glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	// Shear by layer count to get all layers visible.
+	static const float layerShearTransfData[] =
+	{
+		1.0f,					0.0f, 0.0f,
+		0.0f,					1.0f, 0.0f,
+		(float)LAYERS_2D_ARRAY, 0.0f, 0.0f
+	};
+
+	// Minification and magnification transformations.
+	static const float texMinTransfData[] =
+	{
+		2.1f,  0.0f, -0.3f,
+		0.0f,  2.1f, -0.3f,
+		0.0f,  0.0f,  1.0f
+	};
+	static const float texMagTransfData[] =
+	{
+		0.4f,  0.0f,  0.8f,
+		0.0f,  0.4f,  0.8f,
+		0.0f,  0.0f,  1.0f
+	};
+
+	// Transformation matrices for minification and magnification.
+	const Mat3 texMinTransf = Mat3(layerShearTransfData) * Mat3(texMinTransfData);
+	const Mat3 texMagTransf = Mat3(layerShearTransfData) * Mat3(texMagTransfData);
+
+	// Surface for the reference image.
+	tcu::Surface refImage(viewportWidth, viewportHeight);
+
+	{
+		const struct Render
+		{
+			const Rect	region;
+			int			textureNdx;
+			const Mat3	texTransform;
+			Render (const Rect& r, int tN, const Mat3& tT) : region(r), textureNdx(tN), texTransform(tT) {}
+		} renders[] =
+		{
+			Render(Rect(0,				0,				leftWidth,	bottomHeight),	0, texMinTransf),
+			Render(Rect(leftWidth,		0,				rightWidth,	bottomHeight),	0, texMagTransf),
+			Render(Rect(0,				bottomHeight,	leftWidth,	topHeight),		1, texMinTransf),
+			Render(Rect(leftWidth,		bottomHeight,	rightWidth,	topHeight),		1, texMagTransf)
+		};
+
+		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
+		{
+			const Render&	rend				= renders[renderNdx];
+			const float		lod					= calculateLod(rend.texTransform, rend.region.size().asFloat(), rend.textureNdx);
+			const bool		useSafeTexCoords	= isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
+			const Grid		grid				(GRID_SIZE_2D_ARRAY, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
+												 TexTypeCoordParams<TEXTURETYPE_2D_ARRAY>(rend.texTransform), useSafeTexCoords);
+
+			glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
+			renderCell				(rend.textureNdx, lod, grid);
+			computeReferenceCell	(rend.textureNdx, lod, grid, refImage, rend.region);
+		}
+	}
+
+	// Read back rendered results.
+	tcu::Surface resImage(viewportWidth, viewportHeight);
+	glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());
+
+	glUseProgram(0);
+
+	// Compare and log.
+	{
+		const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Image comparison failed");
+	}
+
+	return STOP;
+}
+
+void Vertex2DArrayTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
+{
+	const deUint32 programID = m_program->getProgram();
+
+	// SETUP ATTRIBUTES.
+
+	{
+		const int positionLoc = glGetAttribLocation(programID, "a_position");
+		if (positionLoc != -1)
+		{
+			glEnableVertexAttribArray(positionLoc);
+			glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
+		}
+	}
+
+	{
+		const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
+		if (texCoordLoc != -1)
+		{
+			glEnableVertexAttribArray(texCoordLoc);
+			glVertexAttribPointer(texCoordLoc, 3, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
+		}
+	}
+
+	// SETUP UNIFORMS.
+
+	{
+		const int lodLoc = glGetUniformLocation(programID, "u_lod");
+		if (lodLoc != -1)
+			glUniform1f(lodLoc, lod);
+	}
+
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_2D_ARRAY, m_textures[textureNdx]->getGLTexture());
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S,		m_wrapS);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T,		m_wrapT);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	{
+		const int texLoc = glGetUniformLocation(programID, "u_texture");
+		if (texLoc != -1)
+			glUniform1i(texLoc, 0);
+	}
+}
+
+// Renders one sub-image with given parameters.
+void Vertex2DArrayTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
+{
+	setupShaderInputs(textureNdx, lod, grid);
+	glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
+}
+
+// Computes reference for one sub-image with given parameters.
+void Vertex2DArrayTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
+{
+	computeReference(m_textures[textureNdx]->getRefTexture(), lod, glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter), grid, dst, dstRegion);
+}
+
+class Vertex3DTextureCase : public TestCase
+{
+public:
+								Vertex3DTextureCase		(Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 wrapR);
+								~Vertex3DTextureCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+	IterateResult				iterate					(void);
+
+private:
+	typedef PosTexCoordQuadGrid<TEXTURETYPE_3D> Grid;
+
+								Vertex3DTextureCase		(const Vertex3DTextureCase& other);
+	Vertex3DTextureCase&		operator=				(const Vertex3DTextureCase& other);
+
+	float						calculateLod			(const Mat3& transf, const Vec2& dstSize, int textureNdx) const;
+	void						setupShaderInputs		(int textureNdx, float lod, const Grid& grid) const;
+	void						renderCell				(int textureNdx, float lod, const Grid& grid) const;
+	void						computeReferenceCell	(int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;
+
+	const deUint32				m_minFilter;
+	const deUint32				m_magFilter;
+	const deUint32				m_wrapS;
+	const deUint32				m_wrapT;
+	const deUint32				m_wrapR;
+
+	const glu::ShaderProgram*	m_program;
+	glu::Texture3D*				m_textures[2];	// 2 textures, a gradient texture and a grid texture.
+};
+
+Vertex3DTextureCase::Vertex3DTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 wrapR)
+	: TestCase				(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_wrapR				(wrapR)
+	, m_program				(DE_NULL)
+{
+	m_textures[0] = DE_NULL;
+	m_textures[1] = DE_NULL;
+}
+
+Vertex3DTextureCase::~Vertex3DTextureCase(void)
+{
+	Vertex3DTextureCase::deinit();
+}
+
+void Vertex3DTextureCase::init (void)
+{
+	const char* const vertexShaderSource =
+		"#version 300 es\n"
+		"in highp vec2 a_position;\n"
+		"in highp vec3 a_texCoord;\n"
+		"uniform highp sampler3D u_texture;\n"
+		"uniform highp float u_lod;\n"
+		"out mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		"	v_color = textureLod(u_texture, a_texCoord, u_lod);\n"
+		"}\n";
+
+	const char* const fragmentShaderSource =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+		"in mediump vec4 v_color;\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	dEQP_FragColor = v_color;\n"
+		"}\n";
+
+	if (m_context.getRenderTarget().getNumSamples() != 0)
+		throw tcu::NotSupportedError("MSAA config not supported by this test");
+
+	// Create shader.
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+	if(!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+
+		GLint maxVertexTextures;
+		glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);
+
+		if (maxVertexTextures < 1)
+			throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
+		else
+			TCU_FAIL("Failed to compile shader");
+	}
+
+	// Make the textures.
+
+	try
+	{
+		const int texWidth	= WIDTH_3D;
+		const int texHeight	= HEIGHT_3D;
+		const int texDepth	= DEPTH_3D;
+
+		for (int i = 0; i < 2; i++)
+		{
+			DE_ASSERT(!m_textures[i]);
+			m_textures[i] = new glu::Texture3D(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight, texDepth);
+		}
+
+		const int						numLevels	= deLog2Floor32(de::max(de::max(texWidth, texHeight), texDepth)) + 1;
+		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
+		const Vec4						cBias		= fmtInfo.valueMin;
+		const Vec4						cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
+
+		// Fill first with gradient texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			const Vec4 gMin = Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
+			const Vec4 gMax = Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;
+
+			m_textures[0]->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
+		}
+
+		// Fill second with grid texture.
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			const deUint32 step		= 0x00ffffff / numLevels;
+			const deUint32 rgb		= step*levelNdx;
+			const deUint32 colorA	= 0xff000000 | rgb;
+			const deUint32 colorB	= 0xff000000 | ~rgb;
+
+			m_textures[1]->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
+		}
+
+		// Upload.
+		for (int i = 0; i < 2; i++)
+			m_textures[i]->upload();
+	}
+	catch (const std::exception&)
+	{
+		// Clean up to save memory.
+		Vertex3DTextureCase::deinit();
+		throw;
+	}
+}
+
+void Vertex3DTextureCase::deinit (void)
+{
+	for (int i = 0; i < 2; i++)
+	{
+		delete m_textures[i];
+		m_textures[i] = DE_NULL;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+float Vertex3DTextureCase::calculateLod (const Mat3& transf, const Vec2& dstSize, int textureNdx) const
+{
+	const tcu::Texture3D&	refTexture	= m_textures[textureNdx]->getRefTexture();
+	const int				srcWidth	= refTexture.getWidth();
+	const int				srcHeight	= refTexture.getHeight();
+	const int				srcDepth	= refTexture.getDepth();
+
+	// Calculate transformed coordinates of three screen corners.
+	const Vec3				trans00		= transf * Vec3(0.0f, 0.0f, 1.0f);
+	const Vec3				trans01		= transf * Vec3(0.0f, 1.0f, 1.0f);
+	const Vec3				trans10		= transf * Vec3(1.0f, 0.0f, 1.0f);
+
+	// Derivates.
+	const float dudx = (trans10.x() - trans00.x()) * (float)srcWidth / dstSize.x();
+	const float dudy = (trans01.x() - trans00.x()) * (float)srcWidth / dstSize.y();
+	const float dvdx = (trans10.y() - trans00.y()) * (float)srcHeight / dstSize.x();
+	const float dvdy = (trans01.y() - trans00.y()) * (float)srcHeight / dstSize.y();
+	const float dwdx = (trans10.z() - trans00.z()) * (float)srcDepth / dstSize.x();
+	const float dwdy = (trans01.z() - trans00.z()) * (float)srcDepth / dstSize.y();
+
+	return deFloatLog2(deFloatSqrt(de::max(dudx*dudx + dvdx*dvdx + dwdx*dwdx, dudy*dudy + dvdy*dvdy + dwdy*dwdy)));
+}
+
+Vertex3DTextureCase::IterateResult Vertex3DTextureCase::iterate (void)
+{
+	const int	viewportWidth		= deMin32(m_context.getRenderTarget().getWidth(), MAX_3D_RENDER_WIDTH);
+	const int	viewportHeight		= deMin32(m_context.getRenderTarget().getHeight(), MAX_3D_RENDER_HEIGHT);
+
+	const int	viewportXOffsetMax	= m_context.getRenderTarget().getWidth() - viewportWidth;
+	const int	viewportYOffsetMax	= m_context.getRenderTarget().getHeight() - viewportHeight;
+
+	de::Random	rnd					(deStringHash(getName()));
+
+	const int	viewportXOffset		= rnd.getInt(0, viewportXOffsetMax);
+	const int	viewportYOffset		= rnd.getInt(0, viewportYOffsetMax);
+
+	glUseProgram(m_program->getProgram());
+
+	// Divide viewport into 4 cells.
+	const int leftWidth		= viewportWidth / 2;
+	const int rightWidth		= viewportWidth - leftWidth;
+	const int bottomHeight	= viewportHeight / 2;
+	const int topHeight		= viewportHeight - bottomHeight;
+
+	// Clear.
+	glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	// Shear to get all slices visible.
+	static const float depthShearTransfData[] =
+	{
+		1.0f, 0.0f, 0.0f,
+		0.0f, 1.0f, 0.0f,
+		1.0f, 1.0f, 0.0f
+	};
+
+	// Minification and magnification transformations.
+	static const float texMinTransfData[] =
+	{
+		2.2f,  0.0f, -0.3f,
+		0.0f,  2.2f, -0.3f,
+		0.0f,  0.0f,  1.0f
+	};
+	static const float texMagTransfData[] =
+	{
+		0.4f,  0.0f,  0.8f,
+		0.0f,  0.4f,  0.8f,
+		0.0f,  0.0f,  1.0f
+	};
+
+	// Transformation matrices for minification and magnification.
+	const Mat3 texMinTransf = Mat3(depthShearTransfData) * Mat3(texMinTransfData);
+	const Mat3 texMagTransf = Mat3(depthShearTransfData) * Mat3(texMagTransfData);
+
+	// Surface for the reference image.
+	tcu::Surface refImage(viewportWidth, viewportHeight);
+
+	{
+		const struct Render
+		{
+			const Rect	region;
+			int			textureNdx;
+			const Mat3		texTransform;
+			Render (const Rect& r, int tN, const Mat3& tT) : region(r), textureNdx(tN), texTransform(tT) {}
+		} renders[] =
+		{
+			Render(Rect(0,				0,				leftWidth,	bottomHeight),	0, texMinTransf),
+			Render(Rect(leftWidth,		0,				rightWidth,	bottomHeight),	0, texMagTransf),
+			Render(Rect(0,				bottomHeight,	leftWidth,	topHeight),		1, texMinTransf),
+			Render(Rect(leftWidth,		bottomHeight,	rightWidth,	topHeight),		1, texMagTransf)
+		};
+
+		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
+		{
+			const Render&	rend				= renders[renderNdx];
+			const float		lod					= calculateLod(rend.texTransform, rend.region.size().asFloat(), rend.textureNdx);
+			const bool		useSafeTexCoords	= isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
+			const Grid		grid				(GRID_SIZE_3D, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
+												 TexTypeCoordParams<TEXTURETYPE_3D>(rend.texTransform), useSafeTexCoords);
+
+			glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
+			renderCell				(rend.textureNdx, lod, grid);
+			computeReferenceCell	(rend.textureNdx, lod, grid, refImage, rend.region);
+		}
+	}
+
+	// Read back rendered results.
+	tcu::Surface resImage(viewportWidth, viewportHeight);
+	glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());
+
+	glUseProgram(0);
+
+	// Compare and log.
+	{
+		const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Image comparison failed");
+	}
+
+	return STOP;
+}
+
+void Vertex3DTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
+{
+	const deUint32 programID = m_program->getProgram();
+
+	// SETUP ATTRIBUTES.
+
+	{
+		const int positionLoc = glGetAttribLocation(programID, "a_position");
+		if (positionLoc != -1)
+		{
+			glEnableVertexAttribArray(positionLoc);
+			glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
+		}
+	}
+
+	{
+		const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
+		if (texCoordLoc != -1)
+		{
+			glEnableVertexAttribArray(texCoordLoc);
+			glVertexAttribPointer(texCoordLoc, 3, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
+		}
+	}
+
+	// SETUP UNIFORMS.
+
+	{
+		const int lodLoc = glGetUniformLocation(programID, "u_lod");
+		if (lodLoc != -1)
+			glUniform1f(lodLoc, lod);
+	}
+
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_3D, m_textures[textureNdx]->getGLTexture());
+	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S,		m_wrapS);
+	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R,		m_wrapR);
+	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+
+	{
+		const int texLoc = glGetUniformLocation(programID, "u_texture");
+		if (texLoc != -1)
+			glUniform1i(texLoc, 0);
+	}
+}
+
+// Renders one sub-image with given parameters.
+void Vertex3DTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
+{
+	setupShaderInputs(textureNdx, lod, grid);
+	glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
+}
+
+// Computes reference for one sub-image with given parameters.
+void Vertex3DTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
+{
+	computeReference(m_textures[textureNdx]->getRefTexture(), lod, glu::mapGLSampler(m_wrapS, m_wrapT, m_wrapR, m_minFilter, m_magFilter), grid, dst, dstRegion);
+}
+
+VertexTextureTests::VertexTextureTests (Context& context)
+	: TestCaseGroup(context, "vertex", "Vertex Texture Tests")
+{
+}
+
+VertexTextureTests::~VertexTextureTests(void)
+{
+}
+
+void VertexTextureTests::init (void)
+{
+	// 2D and cube map groups, and their filtering and wrap sub-groups.
+	TestCaseGroup* const group2D				= new TestCaseGroup(m_context, "2d",			"2D Vertex Texture Tests");
+	TestCaseGroup* const groupCube				= new TestCaseGroup(m_context, "cube",			"Cube Map Vertex Texture Tests");
+	TestCaseGroup* const group2DArray			= new TestCaseGroup(m_context, "2d_array",		"2D Array Vertex Texture Tests");
+	TestCaseGroup* const group3D				= new TestCaseGroup(m_context, "3d",			"3D Vertex Texture Tests");
+	TestCaseGroup* const filteringGroup2D		= new TestCaseGroup(m_context, "filtering",		"2D Vertex Texture Filtering Tests");
+	TestCaseGroup* const wrapGroup2D			= new TestCaseGroup(m_context, "wrap",			"2D Vertex Texture Wrap Tests");
+	TestCaseGroup* const filteringGroupCube		= new TestCaseGroup(m_context, "filtering",		"Cube Map Vertex Texture Filtering Tests");
+	TestCaseGroup* const wrapGroupCube			= new TestCaseGroup(m_context, "wrap",			"Cube Map Vertex Texture Wrap Tests");
+	TestCaseGroup* const filteringGroup2DArray	= new TestCaseGroup(m_context, "filtering",		"2D Array Vertex Texture Filtering Tests");
+	TestCaseGroup* const wrapGroup2DArray		= new TestCaseGroup(m_context, "wrap",			"2D Array Vertex Texture Wrap Tests");
+	TestCaseGroup* const filteringGroup3D		= new TestCaseGroup(m_context, "filtering",		"3D Vertex Texture Filtering Tests");
+	TestCaseGroup* const wrapGroup3D			= new TestCaseGroup(m_context, "wrap",			"3D Vertex Texture Wrap Tests");
+
+	group2D->addChild(filteringGroup2D);
+	group2D->addChild(wrapGroup2D);
+	groupCube->addChild(filteringGroupCube);
+	groupCube->addChild(wrapGroupCube);
+	group2DArray->addChild(filteringGroup2DArray);
+	group2DArray->addChild(wrapGroup2DArray);
+	group3D->addChild(filteringGroup3D);
+	group3D->addChild(wrapGroup3D);
+
+	addChild(group2D);
+	addChild(groupCube);
+	addChild(group2DArray);
+	addChild(group3D);
+
+	static const struct
+	{
+		const char*		name;
+		GLenum			mode;
+	} wrapModes[] =
+	{
+		{ "clamp",		GL_CLAMP_TO_EDGE	},
+		{ "repeat",		GL_REPEAT			},
+		{ "mirror",		GL_MIRRORED_REPEAT	}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		GLenum			mode;
+	} minFilterModes[] =
+	{
+		{ "nearest",				GL_NEAREST					},
+		{ "linear",					GL_LINEAR					},
+		{ "nearest_mipmap_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
+		{ "linear_mipmap_nearest",	GL_LINEAR_MIPMAP_NEAREST	},
+		{ "nearest_mipmap_linear",	GL_NEAREST_MIPMAP_LINEAR	},
+		{ "linear_mipmap_linear",	GL_LINEAR_MIPMAP_LINEAR		}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		GLenum			mode;
+	} magFilterModes[] =
+	{
+		{ "nearest",	GL_NEAREST	},
+		{ "linear",		GL_LINEAR	}
+	};
+
+#define FOR_EACH(ITERATOR, ARRAY, BODY)	\
+	for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++)	\
+		BODY
+
+	// 2D cases.
+
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+		{
+			const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;
+
+			filteringGroup2D->addChild(new Vertex2DTextureCase(m_context,
+															   name.c_str(), "",
+															   minFilterModes[minFilter].mode,
+															   magFilterModes[magFilter].mode,
+															   wrapModes[wrapMode].mode,
+															   wrapModes[wrapMode].mode));
+		})));
+
+	FOR_EACH(wrapSMode,		wrapModes,
+	FOR_EACH(wrapTMode,		wrapModes,
+		{
+			const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name;
+
+			wrapGroup2D->addChild(new Vertex2DTextureCase(m_context,
+														  name.c_str(), "",
+														  GL_LINEAR_MIPMAP_LINEAR,
+														  GL_LINEAR,
+														  wrapModes[wrapSMode].mode,
+														  wrapModes[wrapTMode].mode));
+		}));
+
+	// Cube map cases.
+
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+		{
+			const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;
+
+			filteringGroupCube->addChild(new VertexCubeTextureCase(m_context,
+																   name.c_str(), "",
+																   minFilterModes[minFilter].mode,
+																   magFilterModes[magFilter].mode,
+																   wrapModes[wrapMode].mode,
+																   wrapModes[wrapMode].mode));
+		})));
+
+	FOR_EACH(wrapSMode,		wrapModes,
+	FOR_EACH(wrapTMode,		wrapModes,
+		{
+			const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name;
+
+			wrapGroupCube->addChild(new VertexCubeTextureCase(m_context,
+															  name.c_str(), "",
+															  GL_LINEAR_MIPMAP_LINEAR,
+															  GL_LINEAR,
+															  wrapModes[wrapSMode].mode,
+															  wrapModes[wrapTMode].mode));
+		}));
+
+	// 2D array cases.
+
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+		{
+			const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;
+
+			filteringGroup2DArray->addChild(new Vertex2DArrayTextureCase(m_context,
+																		 name.c_str(), "",
+																		 minFilterModes[minFilter].mode,
+																		 magFilterModes[magFilter].mode,
+																		 wrapModes[wrapMode].mode,
+																		 wrapModes[wrapMode].mode));
+		})));
+
+	FOR_EACH(wrapSMode,		wrapModes,
+	FOR_EACH(wrapTMode,		wrapModes,
+		{
+			const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name;
+
+			wrapGroup2DArray->addChild(new Vertex2DArrayTextureCase(m_context,
+																	name.c_str(), "",
+																	GL_LINEAR_MIPMAP_LINEAR,
+																	GL_LINEAR,
+																	wrapModes[wrapSMode].mode,
+																	wrapModes[wrapTMode].mode));
+		}));
+
+	// 3D cases.
+
+	FOR_EACH(minFilter,		minFilterModes,
+	FOR_EACH(magFilter,		magFilterModes,
+	FOR_EACH(wrapMode,		wrapModes,
+		{
+			const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;
+
+			filteringGroup3D->addChild(new Vertex3DTextureCase(m_context,
+															   name.c_str(), "",
+															   minFilterModes[minFilter].mode,
+															   magFilterModes[magFilter].mode,
+															   wrapModes[wrapMode].mode,
+															   wrapModes[wrapMode].mode,
+															   wrapModes[wrapMode].mode));
+		})));
+
+	FOR_EACH(wrapSMode,		wrapModes,
+	FOR_EACH(wrapTMode,		wrapModes,
+	FOR_EACH(wrapRMode,		wrapModes,
+		{
+			const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name + "_" + wrapModes[wrapRMode].name;
+
+			wrapGroup3D->addChild(new Vertex3DTextureCase(m_context,
+														  name.c_str(), "",
+														  GL_LINEAR_MIPMAP_LINEAR,
+														  GL_LINEAR,
+														  wrapModes[wrapSMode].mode,
+														  wrapModes[wrapTMode].mode,
+														  wrapModes[wrapRMode].mode));
+		})));
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles3/functional/es3fVertexTextureTests.hpp b/modules/gles3/functional/es3fVertexTextureTests.hpp
new file mode 100644
index 0000000..0204a2c
--- /dev/null
+++ b/modules/gles3/functional/es3fVertexTextureTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3FVERTEXTEXTURETESTS_HPP
+#define _ES3FVERTEXTEXTURETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex-side texture tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Functional
+{
+
+class VertexTextureTests : public TestCaseGroup
+{
+public:
+								VertexTextureTests			(Context& context);
+								~VertexTextureTests			(void);
+
+	void						init						(void);
+
+private:
+								VertexTextureTests			(const VertexTextureTests& other);
+	VertexTextureTests&			operator=					(const VertexTextureTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES3FVERTEXTEXTURETESTS_HPP
diff --git a/modules/gles3/gles3.cmake b/modules/gles3/gles3.cmake
new file mode 100644
index 0000000..f69339d
--- /dev/null
+++ b/modules/gles3/gles3.cmake
@@ -0,0 +1,3 @@
+if (DEQP_SUPPORT_GLES3)
+	add_subdirectory(gles3)
+endif ()
diff --git a/modules/gles3/performance/CMakeLists.txt b/modules/gles3/performance/CMakeLists.txt
new file mode 100644
index 0000000..896954e
--- /dev/null
+++ b/modules/gles3/performance/CMakeLists.txt
@@ -0,0 +1,37 @@
+# dEQP-GLES3.performance
+
+set(DEQP_GLES3_PERFORMANCE_SRCS
+	es3pBlendTests.cpp
+	es3pBlendTests.hpp
+	es3pPerformanceTests.cpp
+	es3pPerformanceTests.hpp
+	es3pShaderControlStatementTests.cpp
+	es3pShaderControlStatementTests.hpp
+	es3pTextureCases.cpp
+	es3pTextureCases.hpp
+	es3pTextureCountTests.cpp
+	es3pTextureCountTests.hpp
+	es3pTextureFilteringTests.cpp
+	es3pTextureFilteringTests.hpp
+	es3pTextureFormatTests.cpp
+	es3pTextureFormatTests.hpp
+	es3pShaderCompilerTests.cpp
+	es3pShaderCompilerTests.hpp
+	es3pShaderOptimizationTests.cpp
+	es3pShaderOptimizationTests.hpp
+	es3pShaderCompilationCases.hpp
+	es3pShaderCompilationCases.cpp
+	es3pRedundantStateChangeTests.cpp
+	es3pRedundantStateChangeTests.hpp
+	es3pStateChangeTests.cpp
+	es3pStateChangeTests.hpp
+	es3pStateChangeCallTests.cpp
+	es3pStateChangeCallTests.hpp
+	es3pBufferDataUploadTests.cpp
+	es3pBufferDataUploadTests.hpp
+	es3pShaderOperatorTests.cpp
+	es3pShaderOperatorTests.hpp
+	)
+
+add_library(deqp-gles3-performance STATIC ${DEQP_GLES3_PERFORMANCE_SRCS})
+target_link_libraries(deqp-gles3-performance deqp-gl-shared glutil tcutil ${DEQP_GLES3_LIBRARIES})
diff --git a/modules/gles3/performance/es3pBlendTests.cpp b/modules/gles3/performance/es3pBlendTests.cpp
new file mode 100644
index 0000000..f084e3c
--- /dev/null
+++ b/modules/gles3/performance/es3pBlendTests.cpp
@@ -0,0 +1,176 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Blend performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pBlendTests.hpp"
+#include "glsShaderPerformanceCase.hpp"
+#include "tcuTestLog.hpp"
+#include "gluStrUtil.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+using namespace gls;
+using namespace glw; // GL types
+using tcu::Vec4;
+using tcu::TestLog;
+
+class BlendCase : public ShaderPerformanceCase
+{
+public:
+						BlendCase			(Context& context, const char* name, const char* description, GLenum modeRGB, GLenum modeAlpha, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
+						~BlendCase			(void);
+
+	void				init				(void);
+
+private:
+	void				setupRenderState	(void);
+
+	GLenum				m_modeRGB;
+	GLenum				m_modeAlpha;
+	GLenum				m_srcRGB;
+	GLenum				m_dstRGB;
+	GLenum				m_srcAlpha;
+	GLenum				m_dstAlpha;
+};
+
+BlendCase::BlendCase (Context& context, const char* name, const char* description, GLenum modeRGB, GLenum modeAlpha, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha)
+	: ShaderPerformanceCase	(context.getTestContext(), context.getRenderContext(), name, description, CASETYPE_FRAGMENT)
+	, m_modeRGB				(modeRGB)
+	, m_modeAlpha			(modeAlpha)
+	, m_srcRGB				(srcRGB)
+	, m_dstRGB				(dstRGB)
+	, m_srcAlpha			(srcAlpha)
+	, m_dstAlpha			(dstAlpha)
+{
+}
+
+BlendCase::~BlendCase (void)
+{
+}
+
+void BlendCase::init (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	log << TestLog::Message << "modeRGB: " << glu::getBlendEquationStr(m_modeRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "modeAlpha: " << glu::getBlendEquationStr(m_modeAlpha) << TestLog::EndMessage;
+	log << TestLog::Message << "srcRGB: " << glu::getBlendFactorStr(m_srcRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "dstRGB: " << glu::getBlendFactorStr(m_dstRGB) << TestLog::EndMessage;
+	log << TestLog::Message << "srcAlpha: " << glu::getBlendFactorStr(m_srcAlpha) << TestLog::EndMessage;
+	log << TestLog::Message << "dstAlpha: " << glu::getBlendFactorStr(m_dstAlpha) << TestLog::EndMessage;
+
+	m_vertShaderSource =
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in mediump vec4 a_color;\n"
+		"out mediump vec4 v_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_color = a_color;\n"
+		"}\n";
+	m_fragShaderSource =
+		"#version 300 es\n"
+		"in mediump vec4 v_color;\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"	o_color = v_color;\n"
+		"}\n";
+
+	m_attributes.push_back(AttribSpec("a_color", Vec4(0.0f, 0.5f, 0.5f, 1.0f),
+												 Vec4(0.5f, 1.0f, 0.0f, 0.5f),
+												 Vec4(0.5f, 0.0f, 1.0f, 0.5f),
+												 Vec4(1.0f, 0.5f, 0.5f, 0.0f)));
+
+	ShaderPerformanceCase::init();
+}
+
+void BlendCase::setupRenderState (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	gl.enable(GL_BLEND);
+	gl.blendEquationSeparate(m_modeRGB, m_modeAlpha);
+	gl.blendFuncSeparate(m_srcRGB, m_dstRGB, m_srcAlpha, m_dstAlpha);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "After render state setup");
+}
+
+BlendTests::BlendTests (Context& context)
+	: TestCaseGroup(context, "blend", "Blend Performance Tests")
+{
+}
+
+BlendTests::~BlendTests (void)
+{
+}
+
+void BlendTests::init (void)
+{
+	static const struct
+	{
+		const char*	name;
+		GLenum		modeRGB;
+		GLenum		modeAlpha;
+		GLenum		srcRGB;
+		GLenum		dstRGB;
+		GLenum		srcAlpha;
+		GLenum		dstAlpha;
+	} cases[] =
+	{
+		// Single blend func, factor one.
+		{ "add",						GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE,		GL_ONE,		GL_ONE,		GL_ONE		},
+		{ "subtract",					GL_FUNC_SUBTRACT,			GL_FUNC_SUBTRACT,			GL_ONE,		GL_ONE,		GL_ONE,		GL_ONE		},
+		{ "reverse_subtract",			GL_FUNC_REVERSE_SUBTRACT,	GL_FUNC_REVERSE_SUBTRACT,	GL_ONE,		GL_ONE,		GL_ONE,		GL_ONE		},
+		{ "min",						GL_MIN,						GL_MIN,						GL_ONE,		GL_ONE,		GL_ONE,		GL_ONE		},
+		{ "max",						GL_MAX,						GL_MAX,						GL_ONE,		GL_ONE,		GL_ONE,		GL_ONE		},
+
+		// Porter-duff modes that can be implemented.
+		{ "dst_atop",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE_MINUS_DST_ALPHA,		GL_SRC_ALPHA,				GL_ONE,					GL_ZERO					},
+		{ "dst_in",						GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ZERO,					GL_SRC_ALPHA,				GL_ZERO,				GL_SRC_ALPHA			},
+		{ "dst_out",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ZERO,					GL_ONE_MINUS_SRC_ALPHA,		GL_ZERO,				GL_ONE_MINUS_SRC_ALPHA	},
+		{ "dst_over",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE_MINUS_DST_ALPHA,		GL_ONE,						GL_ONE,					GL_ONE_MINUS_SRC_ALPHA	},
+		{ "src_atop",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_DST_ALPHA,				GL_ONE_MINUS_SRC_ALPHA,		GL_ZERO,				GL_ONE					},
+		{ "src_in",						GL_FUNC_ADD,				GL_FUNC_ADD,				GL_DST_ALPHA,				GL_ZERO,					GL_DST_ALPHA,			GL_ZERO					},
+		{ "src_out",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE_MINUS_DST_ALPHA,		GL_ZERO,					GL_ONE_MINUS_DST_ALPHA,	GL_ZERO					},
+		{ "src_over",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE,						GL_ONE_MINUS_SRC_ALPHA,		GL_ONE,					GL_ONE_MINUS_SRC_ALPHA	},
+		{ "multiply",					GL_FUNC_ADD,				GL_FUNC_ADD,				GL_DST_COLOR,				GL_ZERO,					GL_DST_ALPHA,			GL_ZERO					},
+		{ "screen",						GL_FUNC_ADD,				GL_FUNC_ADD,				GL_ONE,						GL_ONE_MINUS_SRC_COLOR,		GL_ONE,					GL_ONE_MINUS_SRC_ALPHA	},
+
+		{ "alpha_saturate",				GL_FUNC_ADD,				GL_FUNC_ADD,				GL_SRC_ALPHA_SATURATE,		GL_ONE,						GL_ONE,					GL_ONE					},
+	};
+
+	for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+		addChild(new BlendCase(m_context, cases[caseNdx].name, "", cases[caseNdx].modeRGB, cases[caseNdx].modeAlpha, cases[caseNdx].srcRGB, cases[caseNdx].dstRGB, cases[caseNdx].srcAlpha, cases[caseNdx].dstAlpha));
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pBlendTests.hpp b/modules/gles3/performance/es3pBlendTests.hpp
new file mode 100644
index 0000000..6850cf8
--- /dev/null
+++ b/modules/gles3/performance/es3pBlendTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PBLENDTESTS_HPP
+#define _ES3PBLENDTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Blend performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class BlendTests : public TestCaseGroup
+{
+public:
+					BlendTests		(Context& context);
+					~BlendTests		(void);
+
+	void			init			(void);
+
+private:
+					BlendTests		(const BlendTests& other);
+	BlendTests&		operator=		(const BlendTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PBLENDTESTS_HPP
diff --git a/modules/gles3/performance/es3pBufferDataUploadTests.cpp b/modules/gles3/performance/es3pBufferDataUploadTests.cpp
new file mode 100644
index 0000000..c9758c4
--- /dev/null
+++ b/modules/gles3/performance/es3pBufferDataUploadTests.cpp
@@ -0,0 +1,6990 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer data upload performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pBufferDataUploadTests.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuSurface.hpp"
+#include "tcuCPUWarmup.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluStrUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluObjectWrapper.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deClock.h"
+#include "deMath.h"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deMemory.h"
+#include "deThread.h"
+
+#include <algorithm>
+#include <iomanip>
+#include <limits>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+namespace
+{
+
+static const char* const s_dummyVertexShader =		"#version 300 es\n"
+													"in highp vec4 a_position;\n"
+													"void main (void)\n"
+													"{\n"
+													"	gl_Position = a_position;\n"
+													"}\n";
+
+static const char* const s_dummyFragnentShader =	"#version 300 es\n"
+													"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+													"void main (void)\n"
+													"{\n"
+													"	dEQP_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+													"}\n";
+
+static const char* const s_colorVertexShader =		"#version 300 es\n"
+													"in highp vec4 a_position;\n"
+													"in highp vec4 a_color;\n"
+													"out highp vec4 v_color;\n"
+													"void main (void)\n"
+													"{\n"
+													"	gl_Position = a_position;\n"
+													"	v_color = a_color;\n"
+													"}\n";
+
+static const char* const s_colorFragmentShader =	"#version 300 es\n"
+													"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+													"in mediump vec4 v_color;\n"
+													"void main (void)\n"
+													"{\n"
+													"	dEQP_FragColor = v_color;\n"
+													"}\n";
+
+template <typename TrueType, int cond>
+struct EnableIf
+{
+	typedef TrueType Type;
+};
+
+template <typename TrueType>
+struct EnableIf<TrueType, 0>
+{
+};
+
+template <typename TrueType, int cond>
+struct EnableIfNot
+{
+};
+
+template <typename TrueType>
+struct EnableIfNot<TrueType, 0>
+{
+	typedef TrueType Type;
+};
+
+struct SingleOperationDuration
+{
+	deUint64 totalDuration;
+	deUint64 fitResponseDuration; // used for fitting
+};
+
+struct MapBufferRangeDuration
+{
+	deUint64 mapDuration;
+	deUint64 unmapDuration;
+	deUint64 writeDuration;
+	deUint64 allocDuration;
+	deUint64 totalDuration;
+
+	deUint64 fitResponseDuration;
+};
+
+struct MapBufferRangeDurationNoAlloc
+{
+	deUint64 mapDuration;
+	deUint64 unmapDuration;
+	deUint64 writeDuration;
+	deUint64 totalDuration;
+
+	deUint64 fitResponseDuration;
+};
+
+struct MapBufferRangeFlushDuration
+{
+	deUint64 mapDuration;
+	deUint64 unmapDuration;
+	deUint64 writeDuration;
+	deUint64 flushDuration;
+	deUint64 allocDuration;
+	deUint64 totalDuration;
+
+	deUint64 fitResponseDuration;
+};
+
+struct MapBufferRangeFlushDurationNoAlloc
+{
+	deUint64 mapDuration;
+	deUint64 unmapDuration;
+	deUint64 writeDuration;
+	deUint64 flushDuration;
+	deUint64 totalDuration;
+
+	deUint64 fitResponseDuration;
+};
+
+struct RenderReadDuration
+{
+	deUint64 renderDuration;
+	deUint64 readDuration;
+	deUint64 renderReadDuration;
+	deUint64 totalDuration;
+
+	deUint64 fitResponseDuration;
+};
+
+struct UnrelatedUploadRenderReadDuration
+{
+	deUint64 renderDuration;
+	deUint64 readDuration;
+	deUint64 renderReadDuration;
+	deUint64 totalDuration;
+
+	deUint64 fitResponseDuration;
+};
+
+struct UploadRenderReadDuration
+{
+	deUint64 uploadDuration;
+	deUint64 renderDuration;
+	deUint64 readDuration;
+	deUint64 totalDuration;
+	deUint64 renderReadDuration;
+
+	deUint64 fitResponseDuration;
+};
+
+struct UploadRenderReadDurationWithUnrelatedUploadSize
+{
+	deUint64 uploadDuration;
+	deUint64 renderDuration;
+	deUint64 readDuration;
+	deUint64 totalDuration;
+	deUint64 renderReadDuration;
+
+	deUint64 fitResponseDuration;
+};
+
+struct RenderUploadRenderReadDuration
+{
+	deUint64 firstRenderDuration;
+	deUint64 uploadDuration;
+	deUint64 secondRenderDuration;
+	deUint64 readDuration;
+	deUint64 totalDuration;
+	deUint64 renderReadDuration;
+
+	deUint64 fitResponseDuration;
+};
+
+template <typename SampleT>
+struct UploadSampleResult
+{
+	typedef SampleT SampleType;
+
+	int			bufferSize;
+	int			allocatedSize;
+	int			writtenSize;
+	SampleType	duration;
+};
+
+template <typename SampleT>
+struct RenderSampleResult
+{
+	typedef SampleT SampleType;
+
+	int			uploadedDataSize;
+	int			renderDataSize;
+	int			unrelatedDataSize;
+	int			numVertices;
+	SampleT		duration;
+};
+
+struct SingleOperationStatistics
+{
+	float minTime;
+	float maxTime;
+	float medianTime;
+	float min2DecileTime;		// !< minimum value in the 2nd decile
+	float max9DecileTime;		// !< maximum value in the 9th decile
+};
+
+struct SingleCallStatistics
+{
+	SingleOperationStatistics	result;
+
+	float						medianRate;
+	float						maxDiffTime;
+	float						maxDiff9DecileTime;
+	float						medianDiffTime;
+
+	float						maxRelDiffTime;
+	float						max9DecileRelDiffTime;
+	float						medianRelDiffTime;
+};
+
+struct MapCallStatistics
+{
+	SingleOperationStatistics	map;
+	SingleOperationStatistics	unmap;
+	SingleOperationStatistics	write;
+	SingleOperationStatistics	alloc;
+	SingleOperationStatistics	result;
+
+	float						medianRate;
+	float						maxDiffTime;
+	float						maxDiff9DecileTime;
+	float						medianDiffTime;
+
+	float						maxRelDiffTime;
+	float						max9DecileRelDiffTime;
+	float						medianRelDiffTime;
+};
+
+struct MapFlushCallStatistics
+{
+	SingleOperationStatistics	map;
+	SingleOperationStatistics	unmap;
+	SingleOperationStatistics	write;
+	SingleOperationStatistics	flush;
+	SingleOperationStatistics	alloc;
+	SingleOperationStatistics	result;
+
+	float						medianRate;
+	float						maxDiffTime;
+	float						maxDiff9DecileTime;
+	float						medianDiffTime;
+
+	float						maxRelDiffTime;
+	float						max9DecileRelDiffTime;
+	float						medianRelDiffTime;
+};
+
+struct RenderReadStatistics
+{
+	SingleOperationStatistics	render;
+	SingleOperationStatistics	read;
+	SingleOperationStatistics	result;
+	SingleOperationStatistics	total;
+
+	float						medianRate;
+	float						maxDiffTime;
+	float						maxDiff9DecileTime;
+	float						medianDiffTime;
+
+	float						maxRelDiffTime;
+	float						max9DecileRelDiffTime;
+	float						medianRelDiffTime;
+};
+
+struct UploadRenderReadStatistics
+{
+	SingleOperationStatistics	upload;
+	SingleOperationStatistics	render;
+	SingleOperationStatistics	read;
+	SingleOperationStatistics	result;
+	SingleOperationStatistics	total;
+
+	float						medianRate;
+	float						maxDiffTime;
+	float						maxDiff9DecileTime;
+	float						medianDiffTime;
+
+	float						maxRelDiffTime;
+	float						max9DecileRelDiffTime;
+	float						medianRelDiffTime;
+};
+
+struct RenderUploadRenderReadStatistics
+{
+	SingleOperationStatistics	firstRender;
+	SingleOperationStatistics	upload;
+	SingleOperationStatistics	secondRender;
+	SingleOperationStatistics	read;
+	SingleOperationStatistics	result;
+	SingleOperationStatistics	total;
+
+	float						medianRate;
+	float						maxDiffTime;
+	float						maxDiff9DecileTime;
+	float						medianDiffTime;
+
+	float						maxRelDiffTime;
+	float						max9DecileRelDiffTime;
+	float						medianRelDiffTime;
+};
+
+template <typename T>
+struct SampleTypeTraits
+{
+};
+
+template <>
+struct SampleTypeTraits<SingleOperationDuration>
+{
+	typedef SingleCallStatistics StatsType;
+
+	enum { HAS_MAP_STATS		= 0	};
+	enum { HAS_UNMAP_STATS		= 0	};
+	enum { HAS_WRITE_STATS		= 0	};
+	enum { HAS_FLUSH_STATS		= 0	};
+	enum { HAS_ALLOC_STATS		= 0	};
+	enum { LOG_CONTRIBUTIONS	= 0	};
+};
+
+template <>
+struct SampleTypeTraits<MapBufferRangeDuration>
+{
+	typedef MapCallStatistics StatsType;
+
+	enum { HAS_MAP_STATS		= 1	};
+	enum { HAS_UNMAP_STATS		= 1	};
+	enum { HAS_WRITE_STATS		= 1	};
+	enum { HAS_FLUSH_STATS		= 0	};
+	enum { HAS_ALLOC_STATS		= 1	};
+	enum { LOG_CONTRIBUTIONS	= 1	};
+};
+
+template <>
+struct SampleTypeTraits<MapBufferRangeDurationNoAlloc>
+{
+	typedef MapCallStatistics StatsType;
+
+	enum { HAS_MAP_STATS		= 1	};
+	enum { HAS_UNMAP_STATS		= 1	};
+	enum { HAS_WRITE_STATS		= 1	};
+	enum { HAS_FLUSH_STATS		= 0	};
+	enum { HAS_ALLOC_STATS		= 0	};
+	enum { LOG_CONTRIBUTIONS	= 1	};
+};
+
+template <>
+struct SampleTypeTraits<MapBufferRangeFlushDuration>
+{
+	typedef MapFlushCallStatistics StatsType;
+
+	enum { HAS_MAP_STATS		= 1	};
+	enum { HAS_UNMAP_STATS		= 1	};
+	enum { HAS_WRITE_STATS		= 1	};
+	enum { HAS_FLUSH_STATS		= 1	};
+	enum { HAS_ALLOC_STATS		= 1	};
+	enum { LOG_CONTRIBUTIONS	= 1	};
+};
+
+template <>
+struct SampleTypeTraits<MapBufferRangeFlushDurationNoAlloc>
+{
+	typedef MapFlushCallStatistics StatsType;
+
+	enum { HAS_MAP_STATS		= 1	};
+	enum { HAS_UNMAP_STATS		= 1	};
+	enum { HAS_WRITE_STATS		= 1	};
+	enum { HAS_FLUSH_STATS		= 1	};
+	enum { HAS_ALLOC_STATS		= 0	};
+	enum { LOG_CONTRIBUTIONS	= 1	};
+};
+
+template <>
+struct SampleTypeTraits<RenderReadDuration>
+{
+	typedef RenderReadStatistics StatsType;
+
+	enum { HAS_RENDER_STATS			= 1	};
+	enum { HAS_READ_STATS			= 1	};
+	enum { HAS_UPLOAD_STATS			= 0	};
+	enum { HAS_TOTAL_STATS			= 1	};
+	enum { HAS_FIRST_RENDER_STATS	= 0	};
+	enum { HAS_SECOND_RENDER_STATS	= 0	};
+
+	enum { LOG_CONTRIBUTIONS	= 1	};
+};
+
+template <>
+struct SampleTypeTraits<UnrelatedUploadRenderReadDuration>
+{
+	typedef RenderReadStatistics StatsType;
+
+	enum { HAS_RENDER_STATS			= 1	};
+	enum { HAS_READ_STATS			= 1	};
+	enum { HAS_UPLOAD_STATS			= 0	};
+	enum { HAS_TOTAL_STATS			= 1	};
+	enum { HAS_FIRST_RENDER_STATS	= 0	};
+	enum { HAS_SECOND_RENDER_STATS	= 0	};
+
+	enum { LOG_CONTRIBUTIONS	= 1	};
+};
+
+template <>
+struct SampleTypeTraits<UploadRenderReadDuration>
+{
+	typedef UploadRenderReadStatistics StatsType;
+
+	enum { HAS_RENDER_STATS			= 1	};
+	enum { HAS_READ_STATS			= 1	};
+	enum { HAS_UPLOAD_STATS			= 1	};
+	enum { HAS_TOTAL_STATS			= 1	};
+	enum { HAS_FIRST_RENDER_STATS	= 0	};
+	enum { HAS_SECOND_RENDER_STATS	= 0	};
+
+	enum { LOG_CONTRIBUTIONS			= 1	};
+	enum { LOG_UNRELATED_UPLOAD_SIZE	= 0 };
+};
+
+template <>
+struct SampleTypeTraits<UploadRenderReadDurationWithUnrelatedUploadSize>
+{
+	typedef UploadRenderReadStatistics StatsType;
+
+	enum { HAS_RENDER_STATS			= 1	};
+	enum { HAS_READ_STATS			= 1	};
+	enum { HAS_UPLOAD_STATS			= 1	};
+	enum { HAS_TOTAL_STATS			= 1	};
+	enum { HAS_FIRST_RENDER_STATS	= 0	};
+	enum { HAS_SECOND_RENDER_STATS	= 0	};
+
+	enum { LOG_CONTRIBUTIONS			= 1	};
+	enum { LOG_UNRELATED_UPLOAD_SIZE	= 1 };
+};
+
+template <>
+struct SampleTypeTraits<RenderUploadRenderReadDuration>
+{
+	typedef RenderUploadRenderReadStatistics StatsType;
+
+	enum { HAS_RENDER_STATS			= 0	};
+	enum { HAS_READ_STATS			= 1	};
+	enum { HAS_UPLOAD_STATS			= 1	};
+	enum { HAS_TOTAL_STATS			= 1	};
+	enum { HAS_FIRST_RENDER_STATS	= 1	};
+	enum { HAS_SECOND_RENDER_STATS	= 1	};
+
+	enum { LOG_CONTRIBUTIONS			= 1	};
+	enum { LOG_UNRELATED_UPLOAD_SIZE	= 1 };
+};
+
+struct TheilSenLineFit
+{
+	float coefficient;
+	float coefficient60ConfidenceUpper;
+	float coefficient60ConfidenceLower;
+	float offset;
+	float offset60ConfidenceUpper;
+	float offset60ConfidenceLower;
+};
+
+struct UploadSampleAnalyzeResult
+{
+	float transferRateMedian;
+	float transferRateAtRange;
+	float transferRateAtInfinity;
+};
+
+struct RenderSampleAnalyzeResult
+{
+	float renderRateMedian;
+	float renderRateAtRange;
+	float renderRateAtInfinity;
+};
+
+class UnmapFailureError : public std::exception
+{
+public:
+	UnmapFailureError (void) : std::exception() {}
+};
+
+static std::string getHumanReadableByteSize (int numBytes)
+{
+	std::ostringstream buf;
+
+	if (numBytes < 1024)
+		buf << numBytes << " byte(s)";
+	else if (numBytes < 1024 * 1024)
+		buf << de::floatToString(numBytes/1024.0f, 1) << " KiB";
+	else
+		buf << de::floatToString(numBytes/1024.0f/1024.0f, 1) << " MiB";
+
+	return buf.str();
+}
+
+static deUint64 medianTimeMemcpy (void* dst, const void* src, int numBytes)
+{
+	// Time used by memcpy is assumed to be asymptotically linear
+
+	// With large numBytes, the probability of context switch or other random
+	// event is high. Apply memcpy in parts and report how much time would
+	// memcpy have used with the median transfer rate.
+
+	// Less than 1MiB, no need to do anything special
+	if (numBytes < 1048576)
+	{
+		deUint64 startTime;
+		deUint64 endTime;
+
+		deYield();
+
+		startTime = deGetMicroseconds();
+		deMemcpy(dst, src, numBytes);
+		endTime = deGetMicroseconds();
+
+		return endTime - startTime;
+	}
+	else
+	{
+		// Do memcpy in multiple parts
+
+		const int	numSections		= 5;
+		const int	sectionAlign	= 16;
+
+		int			sectionStarts[numSections+1];
+		int			sectionLens[numSections];
+		deUint64	sectionTimes[numSections];
+		deUint64	medianTime;
+		deUint64	bestTime		= 0;
+
+		for (int sectionNdx = 0; sectionNdx < numSections; ++sectionNdx)
+			sectionStarts[sectionNdx] = deAlign32((numBytes * sectionNdx / numSections), sectionAlign);
+		sectionStarts[numSections] = numBytes;
+
+		for (int sectionNdx = 0; sectionNdx < numSections; ++sectionNdx)
+			sectionLens[sectionNdx] = sectionStarts[sectionNdx+1] - sectionStarts[sectionNdx];
+
+		// Memcpy is usually called after mapbuffer range which may take
+		// a lot of time. To prevent power management from kicking in during
+		// copy, warm up more.
+		{
+			deYield();
+			tcu::warmupCPU();
+			deYield();
+		}
+
+		for (int sectionNdx = 0; sectionNdx < numSections; ++sectionNdx)
+		{
+			deUint64 startTime;
+			deUint64 endTime;
+
+			startTime = deGetMicroseconds();
+			deMemcpy((deUint8*)dst + sectionStarts[sectionNdx], (const deUint8*)src + sectionStarts[sectionNdx], sectionLens[sectionNdx]);
+			endTime = deGetMicroseconds();
+
+			sectionTimes[sectionNdx] = endTime - startTime;
+
+			if (!bestTime || sectionTimes[sectionNdx] < bestTime)
+				bestTime = sectionTimes[sectionNdx];
+
+			// Detect if write takes 50% longer than it should, and warm up if that happened
+			if (sectionNdx != numSections-1 && (float)sectionTimes[sectionNdx] > 1.5f * bestTime)
+			{
+				deYield();
+				tcu::warmupCPU();
+				deYield();
+			}
+		}
+
+		std::sort(sectionTimes, sectionTimes + numSections);
+
+		if ((numSections % 2) == 0)
+			medianTime = (sectionTimes[numSections / 2 - 1] + sectionTimes[numSections / 2]) / 2;
+		else
+			medianTime = sectionTimes[numSections / 2];
+
+		return medianTime*numSections;
+	}
+}
+
+static float dummyCalculation (float initial, int workSize)
+{
+	float	a = initial;
+	int		b = 123;
+
+	for (int ndx = 0; ndx < workSize; ++ndx)
+	{
+		a = deFloatCos(a + (float)b);
+		b = (b + 63) % 107 + de::abs((int)(a*10.0f));
+	}
+
+	return a + (float)b;
+}
+
+static void busyWait (int microseconds)
+{
+	const deUint64	maxSingleWaitTime	= 1000; // 1ms
+	const deUint64	endTime				= deGetMicroseconds() + microseconds;
+	float			dummy				= *tcu::warmupCPUInternal::g_dummy.m_v;
+	int				workSize			= 500;
+
+	// exponentially increase work, cap to 1ms
+	while (deGetMicroseconds() < endTime)
+	{
+		const deUint64	startTime		= deGetMicroseconds();
+		deUint64		totalTime;
+
+		dummy = dummyCalculation(dummy, workSize);
+
+		totalTime = deGetMicroseconds() - startTime;
+
+		if (totalTime >= maxSingleWaitTime)
+			break;
+		else
+			workSize *= 2;
+	}
+
+	// "wait"
+	while (deGetMicroseconds() < endTime)
+		dummy = dummyCalculation(dummy, workSize);
+
+	*tcu::warmupCPUInternal::g_dummy.m_v = dummy;
+}
+
+// Sample from given values using linear interpolation at a given position as if values were laid to range [0, 1]
+template <typename T>
+static float linearSample (const std::vector<T>& values, float position)
+{
+	DE_ASSERT(position >= 0.0f);
+	DE_ASSERT(position <= 1.0f);
+
+	const float	floatNdx			= ((int)values.size() - 1) * position;
+	const int	lowerNdx			= (int)deFloatFloor(floatNdx);
+	const int	higherNdx			= lowerNdx + 1;
+	const float	interpolationFactor = floatNdx - (float)lowerNdx;
+
+	DE_ASSERT(lowerNdx >= 0 && lowerNdx < (int)values.size());
+	DE_ASSERT(higherNdx >= 0 && higherNdx < (int)values.size());
+	DE_ASSERT(interpolationFactor >= 0 && interpolationFactor < 1.0f);
+
+	return tcu::mix((float)values[lowerNdx], (float)values[higherNdx], interpolationFactor);
+}
+
+static TheilSenLineFit theilSenLinearRegression (const std::vector<tcu::Vec2>& dataPoints)
+{
+	DE_ASSERT(!dataPoints.empty());
+
+	// \note Based on gls::theilSenEstimator()
+	// Siegel's variation
+
+	const float			epsilon					= 1e-6f;
+	int					numDataPoints			= (int)dataPoints.size();
+	std::vector<float>	medianSlopes;
+	std::vector<float>	pointwiseOffsets;
+	TheilSenLineFit		result;
+
+	// Compute the median slope via each element
+	for (int i = 0; i < numDataPoints; i++)
+	{
+		const tcu::Vec2&	ptA		= dataPoints[i];
+		std::vector<float>	slopes;
+
+		for (int j = 0; j < numDataPoints; j++)
+		{
+			const tcu::Vec2& ptB = dataPoints[j];
+
+			if (de::abs(ptA.x() - ptB.x()) > epsilon)
+				slopes.push_back((ptA.y() - ptB.y()) / (ptA.x() - ptB.x()));
+		}
+
+		std::sort(slopes.begin(), slopes.end());
+		medianSlopes.push_back(linearSample(slopes, 0.5f));
+	}
+
+	DE_ASSERT(!medianSlopes.empty());
+
+	// Find the median of the pairwise coefficients.
+	std::sort(medianSlopes.begin(), medianSlopes.end());
+	result.coefficient = linearSample(medianSlopes, 0.5f);
+
+	// Compute the offsets corresponding to the median coefficient, for all data points.
+	for (int i = 0; i < numDataPoints; i++)
+		pointwiseOffsets.push_back(dataPoints[i].y() - result.coefficient*dataPoints[i].x());
+
+	// Find the median of the offsets.
+	std::sort(pointwiseOffsets.begin(), pointwiseOffsets.end());
+	result.offset = linearSample(pointwiseOffsets, 0.5f);
+
+	// calculate 60% confidence intervals
+	{
+		result.coefficient60ConfidenceLower = linearSample(medianSlopes, 0.2f);
+		result.coefficient60ConfidenceUpper = linearSample(medianSlopes, 0.8f);
+
+		result.offset60ConfidenceLower = linearSample(pointwiseOffsets, 0.2f);
+		result.offset60ConfidenceUpper = linearSample(pointwiseOffsets, 0.8f);
+	}
+
+	return result;
+}
+
+template <typename T>
+SingleOperationStatistics calculateSingleOperationStatistics (const std::vector<T>& samples, deUint64 T::SampleType::*target)
+{
+	SingleOperationStatistics	stats;
+	std::vector<deUint64>		values(samples.size());
+
+	for (int ndx = 0; ndx < (int)samples.size(); ++ndx)
+		values[ndx] = samples[ndx].duration.*target;
+
+	std::sort(values.begin(), values.end());
+
+	stats.minTime			= (float)values.front();
+	stats.maxTime			= (float)values.back();
+	stats.medianTime		= linearSample(values, 0.5f);
+	stats.min2DecileTime	= linearSample(values, 0.1f);
+	stats.max9DecileTime	= linearSample(values, 0.9f);
+
+	return stats;
+}
+
+template <typename StatisticsType, typename SampleType>
+void calculateBasicStatistics (StatisticsType& stats, const TheilSenLineFit& fit, const std::vector<SampleType>& samples, int SampleType::*predictor)
+{
+	std::vector<deUint64> values(samples.size());
+
+	for (int ndx = 0; ndx < (int)samples.size(); ++ndx)
+		values[ndx] = samples[ndx].duration.fitResponseDuration;
+
+	// median rate
+	{
+		std::vector<float> processingRates(samples.size());
+
+		for (int ndx = 0; ndx < (int)samples.size(); ++ndx)
+		{
+			const float timeInSeconds = values[ndx] / 1000.0f / 1000.0f;
+			processingRates[ndx] = samples[ndx].*predictor / timeInSeconds;
+		}
+
+		std::sort(processingRates.begin(), processingRates.end());
+
+		stats.medianRate = linearSample(processingRates, 0.5f);
+	}
+
+	// results compared to the approximation
+	{
+		std::vector<float> timeDiffs(samples.size());
+
+		for (int ndx = 0; ndx < (int)samples.size(); ++ndx)
+		{
+			const float prediction	= samples[ndx].*predictor * fit.coefficient + fit.offset;
+			const float actual		= (float)values[ndx];
+			timeDiffs[ndx] = actual - prediction;
+		}
+		std::sort(timeDiffs.begin(), timeDiffs.end());
+
+		stats.maxDiffTime			= timeDiffs.back();
+		stats.maxDiff9DecileTime	= linearSample(timeDiffs, 0.9f);
+		stats.medianDiffTime		= linearSample(timeDiffs, 0.5f);
+	}
+
+	// relative comparison to the approximation
+	{
+		std::vector<float> relativeDiffs(samples.size());
+
+		for (int ndx = 0; ndx < (int)samples.size(); ++ndx)
+		{
+			const float prediction	= samples[ndx].*predictor * fit.coefficient + fit.offset;
+			const float actual		= (float)values[ndx];
+
+			// Ignore cases where we predict negative times, or if
+			// ratio would be (nearly) infinite: ignore if predicted
+			// time is less than 1 microsecond
+			if (prediction < 1.0f)
+				relativeDiffs[ndx] = 0.0f;
+			else
+				relativeDiffs[ndx] = (actual - prediction) / prediction;
+		}
+		std::sort(relativeDiffs.begin(), relativeDiffs.end());
+
+		stats.maxRelDiffTime		= relativeDiffs.back();
+		stats.max9DecileRelDiffTime	= linearSample(relativeDiffs, 0.9f);
+		stats.medianRelDiffTime		= linearSample(relativeDiffs, 0.5f);
+	}
+
+	// values calculated using sorted timings
+
+	std::sort(values.begin(), values.end());
+
+	stats.result.minTime = (float)values.front();
+	stats.result.maxTime = (float)values.back();
+	stats.result.medianTime = linearSample(values, 0.5f);
+	stats.result.min2DecileTime = linearSample(values, 0.1f);
+	stats.result.max9DecileTime = linearSample(values, 0.9f);
+}
+
+template <typename StatisticsType, typename SampleType>
+void calculateBasicTransferStatistics (StatisticsType& stats, const TheilSenLineFit& fit, const std::vector<SampleType>& samples)
+{
+	calculateBasicStatistics(stats, fit, samples, &SampleType::writtenSize);
+}
+
+template <typename StatisticsType, typename SampleType>
+void calculateBasicRenderStatistics (StatisticsType& stats, const TheilSenLineFit& fit, const std::vector<SampleType>& samples)
+{
+	calculateBasicStatistics(stats, fit, samples, &SampleType::renderDataSize);
+}
+
+static SingleCallStatistics calculateSampleStatistics (const TheilSenLineFit& fit, const std::vector<UploadSampleResult<SingleOperationDuration> >& samples)
+{
+	SingleCallStatistics stats;
+
+	calculateBasicTransferStatistics(stats, fit, samples);
+
+	return stats;
+}
+
+static MapCallStatistics calculateSampleStatistics (const TheilSenLineFit& fit, const std::vector<UploadSampleResult<MapBufferRangeDuration> >& samples)
+{
+	MapCallStatistics stats;
+
+	calculateBasicTransferStatistics(stats, fit, samples);
+
+	stats.map	= calculateSingleOperationStatistics(samples, &MapBufferRangeDuration::mapDuration);
+	stats.unmap	= calculateSingleOperationStatistics(samples, &MapBufferRangeDuration::unmapDuration);
+	stats.write	= calculateSingleOperationStatistics(samples, &MapBufferRangeDuration::writeDuration);
+	stats.alloc	= calculateSingleOperationStatistics(samples, &MapBufferRangeDuration::allocDuration);
+
+	return stats;
+}
+
+static MapFlushCallStatistics calculateSampleStatistics (const TheilSenLineFit& fit, const std::vector<UploadSampleResult<MapBufferRangeFlushDuration> >& samples)
+{
+	MapFlushCallStatistics stats;
+
+	calculateBasicTransferStatistics(stats, fit, samples);
+
+	stats.map	= calculateSingleOperationStatistics(samples, &MapBufferRangeFlushDuration::mapDuration);
+	stats.unmap	= calculateSingleOperationStatistics(samples, &MapBufferRangeFlushDuration::unmapDuration);
+	stats.write	= calculateSingleOperationStatistics(samples, &MapBufferRangeFlushDuration::writeDuration);
+	stats.flush	= calculateSingleOperationStatistics(samples, &MapBufferRangeFlushDuration::flushDuration);
+	stats.alloc	= calculateSingleOperationStatistics(samples, &MapBufferRangeFlushDuration::allocDuration);
+
+	return stats;
+}
+
+static MapCallStatistics calculateSampleStatistics (const TheilSenLineFit& fit, const std::vector<UploadSampleResult<MapBufferRangeDurationNoAlloc> >& samples)
+{
+	MapCallStatistics stats;
+
+	calculateBasicTransferStatistics(stats, fit, samples);
+
+	stats.map	= calculateSingleOperationStatistics(samples, &MapBufferRangeDurationNoAlloc::mapDuration);
+	stats.unmap	= calculateSingleOperationStatistics(samples, &MapBufferRangeDurationNoAlloc::unmapDuration);
+	stats.write	= calculateSingleOperationStatistics(samples, &MapBufferRangeDurationNoAlloc::writeDuration);
+
+	return stats;
+}
+
+static MapFlushCallStatistics calculateSampleStatistics (const TheilSenLineFit& fit, const std::vector<UploadSampleResult<MapBufferRangeFlushDurationNoAlloc> >& samples)
+{
+	MapFlushCallStatistics stats;
+
+	calculateBasicTransferStatistics(stats, fit, samples);
+
+	stats.map	= calculateSingleOperationStatistics(samples, &MapBufferRangeFlushDurationNoAlloc::mapDuration);
+	stats.unmap	= calculateSingleOperationStatistics(samples, &MapBufferRangeFlushDurationNoAlloc::unmapDuration);
+	stats.write	= calculateSingleOperationStatistics(samples, &MapBufferRangeFlushDurationNoAlloc::writeDuration);
+	stats.flush	= calculateSingleOperationStatistics(samples, &MapBufferRangeFlushDurationNoAlloc::flushDuration);
+
+	return stats;
+}
+
+static RenderReadStatistics calculateSampleStatistics (const TheilSenLineFit& fit, const std::vector<RenderSampleResult<RenderReadDuration> >& samples)
+{
+	RenderReadStatistics stats;
+
+	calculateBasicRenderStatistics(stats, fit, samples);
+
+	stats.render	= calculateSingleOperationStatistics(samples, &RenderReadDuration::renderDuration);
+	stats.read		= calculateSingleOperationStatistics(samples, &RenderReadDuration::readDuration);
+	stats.total		= calculateSingleOperationStatistics(samples, &RenderReadDuration::totalDuration);
+
+	return stats;
+}
+
+static RenderReadStatistics calculateSampleStatistics (const TheilSenLineFit& fit, const std::vector<RenderSampleResult<UnrelatedUploadRenderReadDuration> >& samples)
+{
+	RenderReadStatistics stats;
+
+	calculateBasicRenderStatistics(stats, fit, samples);
+
+	stats.render	= calculateSingleOperationStatistics(samples, &UnrelatedUploadRenderReadDuration::renderDuration);
+	stats.read		= calculateSingleOperationStatistics(samples, &UnrelatedUploadRenderReadDuration::readDuration);
+	stats.total		= calculateSingleOperationStatistics(samples, &UnrelatedUploadRenderReadDuration::totalDuration);
+
+	return stats;
+}
+
+static UploadRenderReadStatistics calculateSampleStatistics (const TheilSenLineFit& fit, const std::vector<RenderSampleResult<UploadRenderReadDuration> >& samples)
+{
+	UploadRenderReadStatistics stats;
+
+	calculateBasicRenderStatistics(stats, fit, samples);
+
+	stats.upload	= calculateSingleOperationStatistics(samples, &UploadRenderReadDuration::uploadDuration);
+	stats.render	= calculateSingleOperationStatistics(samples, &UploadRenderReadDuration::renderDuration);
+	stats.read		= calculateSingleOperationStatistics(samples, &UploadRenderReadDuration::readDuration);
+	stats.total		= calculateSingleOperationStatistics(samples, &UploadRenderReadDuration::totalDuration);
+
+	return stats;
+}
+
+static UploadRenderReadStatistics calculateSampleStatistics (const TheilSenLineFit& fit, const std::vector<RenderSampleResult<UploadRenderReadDurationWithUnrelatedUploadSize> >& samples)
+{
+	UploadRenderReadStatistics stats;
+
+	calculateBasicRenderStatistics(stats, fit, samples);
+
+	stats.upload	= calculateSingleOperationStatistics(samples, &UploadRenderReadDurationWithUnrelatedUploadSize::uploadDuration);
+	stats.render	= calculateSingleOperationStatistics(samples, &UploadRenderReadDurationWithUnrelatedUploadSize::renderDuration);
+	stats.read		= calculateSingleOperationStatistics(samples, &UploadRenderReadDurationWithUnrelatedUploadSize::readDuration);
+	stats.total		= calculateSingleOperationStatistics(samples, &UploadRenderReadDurationWithUnrelatedUploadSize::totalDuration);
+
+	return stats;
+}
+
+static RenderUploadRenderReadStatistics calculateSampleStatistics (const TheilSenLineFit& fit, const std::vector<RenderSampleResult<RenderUploadRenderReadDuration> >& samples)
+{
+	RenderUploadRenderReadStatistics stats;
+
+	calculateBasicRenderStatistics(stats, fit, samples);
+
+	stats.firstRender	= calculateSingleOperationStatistics(samples, &RenderUploadRenderReadDuration::firstRenderDuration);
+	stats.upload		= calculateSingleOperationStatistics(samples, &RenderUploadRenderReadDuration::uploadDuration);
+	stats.secondRender	= calculateSingleOperationStatistics(samples, &RenderUploadRenderReadDuration::secondRenderDuration);
+	stats.read			= calculateSingleOperationStatistics(samples, &RenderUploadRenderReadDuration::readDuration);
+	stats.total			= calculateSingleOperationStatistics(samples, &RenderUploadRenderReadDuration::totalDuration);
+
+	return stats;
+}
+
+template <typename DurationType>
+static TheilSenLineFit fitLineToSamples (const std::vector<UploadSampleResult<DurationType> >& samples, int beginNdx, int endNdx, int step, deUint64 DurationType::*target = &DurationType::fitResponseDuration)
+{
+	std::vector<tcu::Vec2> samplePoints;
+
+	for (int sampleNdx = beginNdx; sampleNdx < endNdx; sampleNdx += step)
+	{
+		tcu::Vec2 point;
+
+		point.x() = (float)(samples[sampleNdx].writtenSize);
+		point.y() = (float)(samples[sampleNdx].duration.*target);
+
+		samplePoints.push_back(point);
+	}
+
+	return theilSenLinearRegression(samplePoints);
+}
+
+template <typename DurationType>
+static TheilSenLineFit fitLineToSamples (const std::vector<RenderSampleResult<DurationType> >& samples, int beginNdx, int endNdx, int step, deUint64 DurationType::*target = &DurationType::fitResponseDuration)
+{
+	std::vector<tcu::Vec2> samplePoints;
+
+	for (int sampleNdx = beginNdx; sampleNdx < endNdx; sampleNdx += step)
+	{
+		tcu::Vec2 point;
+
+		point.x() = (float)(samples[sampleNdx].renderDataSize);
+		point.y() = (float)(samples[sampleNdx].duration.*target);
+
+		samplePoints.push_back(point);
+	}
+
+	return theilSenLinearRegression(samplePoints);
+}
+
+template <typename T>
+static TheilSenLineFit fitLineToSamples (const std::vector<T>& samples, int beginNdx, int endNdx, deUint64 T::SampleType::*target = &T::SampleType::fitResponseDuration)
+{
+	return fitLineToSamples(samples, beginNdx, endNdx, 1, target);
+}
+
+template <typename T>
+static TheilSenLineFit fitLineToSamples (const std::vector<T>& samples, deUint64 T::SampleType::*target = &T::SampleType::fitResponseDuration)
+{
+	return fitLineToSamples(samples, 0, (int)samples.size(), target);
+}
+
+static float getAreaBetweenLines (float xmin, float xmax, float lineAOffset, float lineACoefficient, float lineBOffset, float lineBCoefficient)
+{
+	const float lineAMin		= lineAOffset + lineACoefficient * xmin;
+	const float lineAMax		= lineAOffset + lineACoefficient * xmax;
+	const float lineBMin		= lineBOffset + lineBCoefficient * xmin;
+	const float lineBMax		= lineBOffset + lineBCoefficient * xmax;
+	const bool	aOverBAtBegin	= (lineAMin > lineBMin);
+	const bool	aOverBAtEnd		= (lineAMax > lineBMax);
+
+	if (aOverBAtBegin == aOverBAtEnd)
+	{
+		// lines do not intersect
+
+		const float midpoint	= (xmin + xmax) / 2.0f;
+		const float width		= (xmax - xmin);
+
+		const float lineAHeight	= lineAOffset + lineACoefficient * midpoint;
+		const float lineBHeight	= lineBOffset + lineBCoefficient * midpoint;
+
+		return width * de::abs(lineAHeight - lineBHeight);
+	}
+	else
+	{
+
+		// lines intersect
+
+		const float approachCoeffient	= de::abs(lineACoefficient - lineBCoefficient);
+		const float epsilon				= 0.0001f;
+		const float leftHeight			= de::abs(lineAMin - lineBMin);
+		const float rightHeight			= de::abs(lineAMax - lineBMax);
+
+		if (approachCoeffient < epsilon)
+			return 0.0f;
+
+		return (0.5f * leftHeight * (leftHeight / approachCoeffient)) + (0.5f * rightHeight * (rightHeight / approachCoeffient));
+	}
+}
+
+template <typename T>
+static float calculateSampleFitLinearity (const std::vector<T>& samples, int T::*predictor)
+{
+	// Compare the fitted line of first half of the samples to the fitted line of
+	// the second half of the samples. Calculate a AABB that fully contains every
+	// sample's x component and both fit lines in this range. Calculate the ratio
+	// of the area between the lines and the AABB.
+
+	const float				epsilon				= 1.e-6f;
+	const int				midPoint			= (int)samples.size() / 2;
+	const TheilSenLineFit	startApproximation	= fitLineToSamples(samples, 0, midPoint, &T::SampleType::fitResponseDuration);
+	const TheilSenLineFit	endApproximation	= fitLineToSamples(samples, midPoint, (int)samples.size(), &T::SampleType::fitResponseDuration);
+
+	const float				aabbMinX			= (float)(samples.front().*predictor);
+	const float				aabbMinY			= de::min(startApproximation.offset + startApproximation.coefficient*aabbMinX, endApproximation.offset + endApproximation.coefficient*aabbMinX);
+	const float				aabbMaxX			= (float)(samples.back().*predictor);
+	const float				aabbMaxY			= de::max(startApproximation.offset + startApproximation.coefficient*aabbMaxX, endApproximation.offset + endApproximation.coefficient*aabbMaxX);
+
+	const float				aabbArea			= (aabbMaxX - aabbMinX) * (aabbMaxY - aabbMinY);
+	const float				areaBetweenLines	= getAreaBetweenLines(aabbMinX, aabbMaxX, startApproximation.offset, startApproximation.coefficient, endApproximation.offset, endApproximation.coefficient);
+	const float				errorAreaRatio		= (aabbArea < epsilon) ? (1.0f) : (areaBetweenLines / aabbArea);
+
+	return de::clamp(1.0f - errorAreaRatio, 0.0f, 1.0f);
+}
+
+template <typename DurationType>
+static float calculateSampleFitLinearity (const std::vector<UploadSampleResult<DurationType> >& samples)
+{
+	return calculateSampleFitLinearity(samples, &UploadSampleResult<DurationType>::writtenSize);
+}
+
+template <typename DurationType>
+static float calculateSampleFitLinearity (const std::vector<RenderSampleResult<DurationType> >& samples)
+{
+	return calculateSampleFitLinearity(samples, &RenderSampleResult<DurationType>::renderDataSize);
+}
+
+template <typename T>
+static float calculateSampleTemporalStability (const std::vector<T>& samples, int T::*predictor)
+{
+	// Samples are sampled in the following order: 1) even samples (in random order) 2) odd samples (in random order)
+	// Compare the fitted line of even samples to the fitted line of the odd samples. Calculate a AABB that fully
+	// contains every sample's x component and both fit lines in this range. Calculate the ratio of the area between
+	// the lines and the AABB.
+
+	const float				epsilon				= 1.e-6f;
+	const TheilSenLineFit	evenApproximation	= fitLineToSamples(samples, 0, (int)samples.size(), 2, &T::SampleType::fitResponseDuration);
+	const TheilSenLineFit	oddApproximation	= fitLineToSamples(samples, 1, (int)samples.size(), 2, &T::SampleType::fitResponseDuration);
+
+	const float				aabbMinX			= (float)(samples.front().*predictor);
+	const float				aabbMinY			= de::min(evenApproximation.offset + evenApproximation.coefficient*aabbMinX, oddApproximation.offset + oddApproximation.coefficient*aabbMinX);
+	const float				aabbMaxX			= (float)(samples.back().*predictor);
+	const float				aabbMaxY			= de::max(evenApproximation.offset + evenApproximation.coefficient*aabbMaxX, oddApproximation.offset + oddApproximation.coefficient*aabbMaxX);
+
+	const float				aabbArea			= (aabbMaxX - aabbMinX) * (aabbMaxY - aabbMinY);
+	const float				areaBetweenLines	= getAreaBetweenLines(aabbMinX, aabbMaxX, evenApproximation.offset, evenApproximation.coefficient, oddApproximation.offset, oddApproximation.coefficient);
+	const float				errorAreaRatio		= (aabbArea < epsilon) ? (1.0f) : (areaBetweenLines / aabbArea);
+
+	return de::clamp(1.0f - errorAreaRatio, 0.0f, 1.0f);
+}
+
+template <typename DurationType>
+static float calculateSampleTemporalStability (const std::vector<UploadSampleResult<DurationType> >& samples)
+{
+	return calculateSampleTemporalStability(samples, &UploadSampleResult<DurationType>::writtenSize);
+}
+
+template <typename DurationType>
+static float calculateSampleTemporalStability (const std::vector<RenderSampleResult<DurationType> >& samples)
+{
+	return calculateSampleTemporalStability(samples, &RenderSampleResult<DurationType>::renderDataSize);
+}
+
+template <typename DurationType>
+static void bucketizeSamplesUniformly (const std::vector<UploadSampleResult<DurationType> >& samples, std::vector<UploadSampleResult<DurationType> >* buckets, int numBuckets, int& minBufferSize, int& maxBufferSize)
+{
+	minBufferSize = 0;
+	maxBufferSize = 0;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		DE_ASSERT(samples[sampleNdx].allocatedSize != 0);
+
+		if (!minBufferSize || samples[sampleNdx].allocatedSize < minBufferSize)
+			minBufferSize = samples[sampleNdx].allocatedSize;
+		if (!maxBufferSize || samples[sampleNdx].allocatedSize > maxBufferSize)
+			maxBufferSize = samples[sampleNdx].allocatedSize;
+	}
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float bucketNdxFloat	= (samples[sampleNdx].allocatedSize - minBufferSize) / (float)(maxBufferSize - minBufferSize) * numBuckets;
+		const int bucketNdx			= de::clamp((int)deFloatFloor(bucketNdxFloat), 0, numBuckets-1);
+
+		buckets[bucketNdx].push_back(samples[sampleNdx]);
+	}
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_MAP_STATS>::Type logMapRangeStats (tcu::TestLog& log, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	log	<< tcu::TestLog::Float("MapRangeMin", "MapRange: Min time", "us", QP_KEY_TAG_TIME, stats.map.minTime)
+		<< tcu::TestLog::Float("MapRangeMax", "MapRange: Max time", "us", QP_KEY_TAG_TIME, stats.map.maxTime)
+		<< tcu::TestLog::Float("MapRangeMin90", "MapRange: 90%-Min time", "us", QP_KEY_TAG_TIME, stats.map.min2DecileTime)
+		<< tcu::TestLog::Float("MapRangeMax90", "MapRange: 90%-Max time", "us", QP_KEY_TAG_TIME, stats.map.max9DecileTime)
+		<< tcu::TestLog::Float("MapRangeMedian", "MapRange: Median time", "us", QP_KEY_TAG_TIME, stats.map.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_UNMAP_STATS>::Type logUnmapStats (tcu::TestLog& log, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	log	<< tcu::TestLog::Float("UnmapMin", "Unmap: Min time", "us", QP_KEY_TAG_TIME, stats.unmap.minTime)
+		<< tcu::TestLog::Float("UnmapMax", "Unmap: Max time", "us", QP_KEY_TAG_TIME, stats.unmap.maxTime)
+		<< tcu::TestLog::Float("UnmapMin90", "Unmap: 90%-Min time", "us", QP_KEY_TAG_TIME, stats.unmap.min2DecileTime)
+		<< tcu::TestLog::Float("UnmapMax90", "Unmap: 90%-Max time", "us", QP_KEY_TAG_TIME, stats.unmap.max9DecileTime)
+		<< tcu::TestLog::Float("UnmapMedian", "Unmap: Median time", "us", QP_KEY_TAG_TIME, stats.unmap.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_WRITE_STATS>::Type logWriteStats (tcu::TestLog& log, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	log	<< tcu::TestLog::Float("WriteMin", "Write: Min time", "us", QP_KEY_TAG_TIME, stats.write.minTime)
+		<< tcu::TestLog::Float("WriteMax", "Write: Max time", "us", QP_KEY_TAG_TIME, stats.write.maxTime)
+		<< tcu::TestLog::Float("WriteMin90", "Write: 90%-Min time", "us", QP_KEY_TAG_TIME, stats.write.min2DecileTime)
+		<< tcu::TestLog::Float("WriteMax90", "Write: 90%-Max time", "us", QP_KEY_TAG_TIME, stats.write.max9DecileTime)
+		<< tcu::TestLog::Float("WriteMedian", "Write: Median time", "us", QP_KEY_TAG_TIME, stats.write.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_FLUSH_STATS>::Type logFlushStats (tcu::TestLog& log, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	log	<< tcu::TestLog::Float("FlushMin", "Flush: Min time", "us", QP_KEY_TAG_TIME, stats.flush.minTime)
+		<< tcu::TestLog::Float("FlushMax", "Flush: Max time", "us", QP_KEY_TAG_TIME, stats.flush.maxTime)
+		<< tcu::TestLog::Float("FlushMin90", "Flush: 90%-Min time", "us", QP_KEY_TAG_TIME, stats.flush.min2DecileTime)
+		<< tcu::TestLog::Float("FlushMax90", "Flush: 90%-Max time", "us", QP_KEY_TAG_TIME, stats.flush.max9DecileTime)
+		<< tcu::TestLog::Float("FlushMedian", "Flush: Median time", "us", QP_KEY_TAG_TIME, stats.flush.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_ALLOC_STATS>::Type logAllocStats (tcu::TestLog& log, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	log	<< tcu::TestLog::Float("AllocMin", "Alloc: Min time", "us", QP_KEY_TAG_TIME, stats.alloc.minTime)
+		<< tcu::TestLog::Float("AllocMax", "Alloc: Max time", "us", QP_KEY_TAG_TIME, stats.alloc.maxTime)
+		<< tcu::TestLog::Float("AllocMin90", "Alloc: 90%-Min time", "us", QP_KEY_TAG_TIME, stats.alloc.min2DecileTime)
+		<< tcu::TestLog::Float("AllocMax90", "Alloc: 90%-Max time", "us", QP_KEY_TAG_TIME, stats.alloc.max9DecileTime)
+		<< tcu::TestLog::Float("AllocMedian", "Alloc: Median time", "us", QP_KEY_TAG_TIME, stats.alloc.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_MAP_STATS>::Type logMapRangeStats (tcu::TestLog& log, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_UNMAP_STATS>::Type logUnmapStats (tcu::TestLog& log, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_WRITE_STATS>::Type logWriteStats (tcu::TestLog& log, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_FLUSH_STATS>::Type logFlushStats (tcu::TestLog& log, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_ALLOC_STATS>::Type logAllocStats (tcu::TestLog& log, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_MAP_STATS>::Type logMapContribution (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::mapDuration);
+	log	<< tcu::TestLog::Float("MapConstantCost", "Map: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("MapLinearCost", "Map: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("MapMedianCost", "Map: Median cost", "us", QP_KEY_TAG_TIME, stats.map.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_UNMAP_STATS>::Type logUnmapContribution (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::unmapDuration);
+	log	<< tcu::TestLog::Float("UnmapConstantCost", "Unmap: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("UnmapLinearCost", "Unmap: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("UnmapMedianCost", "Unmap: Median cost", "us", QP_KEY_TAG_TIME, stats.unmap.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_WRITE_STATS>::Type logWriteContribution (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::writeDuration);
+	log	<< tcu::TestLog::Float("WriteConstantCost", "Write: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("WriteLinearCost", "Write: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("WriteMedianCost", "Write: Median cost", "us", QP_KEY_TAG_TIME, stats.write.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_FLUSH_STATS>::Type logFlushContribution (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::flushDuration);
+	log	<< tcu::TestLog::Float("FlushConstantCost", "Flush: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("FlushLinearCost", "Flush: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("FlushMedianCost", "Flush: Median cost", "us", QP_KEY_TAG_TIME, stats.flush.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_ALLOC_STATS>::Type logAllocContribution (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::allocDuration);
+	log	<< tcu::TestLog::Float("AllocConstantCost", "Alloc: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("AllocLinearCost", "Alloc: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("AllocMedianCost", "Alloc: Median cost", "us", QP_KEY_TAG_TIME, stats.alloc.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_RENDER_STATS>::Type logRenderContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::renderDuration);
+	log	<< tcu::TestLog::Float("DrawCallConstantCost", "DrawCall: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("DrawCallLinearCost", "DrawCall: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("DrawCallMedianCost", "DrawCall: Median cost", "us", QP_KEY_TAG_TIME, stats.render.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_READ_STATS>::Type logReadContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::readDuration);
+	log	<< tcu::TestLog::Float("ReadConstantCost", "Read: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("ReadLinearCost", "Read: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("ReadMedianCost", "Read: Median cost", "us", QP_KEY_TAG_TIME, stats.read.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_UPLOAD_STATS>::Type logUploadContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::uploadDuration);
+	log	<< tcu::TestLog::Float("UploadConstantCost", "Upload: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("UploadLinearCost", "Upload: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("UploadMedianCost", "Upload: Median cost", "us", QP_KEY_TAG_TIME, stats.upload.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_TOTAL_STATS>::Type logTotalContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::totalDuration);
+	log	<< tcu::TestLog::Float("TotalConstantCost", "Total: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("TotalLinearCost", "Total: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("TotalMedianCost", "Total: Median cost", "us", QP_KEY_TAG_TIME, stats.total.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_FIRST_RENDER_STATS>::Type logFirstRenderContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::firstRenderDuration);
+	log	<< tcu::TestLog::Float("FirstDrawCallConstantCost", "First DrawCall: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("FirstDrawCallLinearCost", "First DrawCall: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("FirstDrawCallMedianCost", "First DrawCall: Median cost", "us", QP_KEY_TAG_TIME, stats.firstRender.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIf<void, SampleTypeTraits<SampleType>::HAS_SECOND_RENDER_STATS>::Type logSecondRenderContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	const TheilSenLineFit contributionFitting = fitLineToSamples(samples, &SampleType::secondRenderDuration);
+	log	<< tcu::TestLog::Float("SecondDrawCallConstantCost", "Second DrawCall: Approximated contant cost", "us", QP_KEY_TAG_TIME, contributionFitting.offset)
+		<< tcu::TestLog::Float("SecondDrawCallLinearCost", "Second DrawCall: Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, contributionFitting.coefficient * 1024.0f * 1024.0f)
+		<< tcu::TestLog::Float("SecondDrawCallMedianCost", "Second DrawCall: Median cost", "us", QP_KEY_TAG_TIME, stats.secondRender.medianTime);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_MAP_STATS>::Type logMapContribution (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_UNMAP_STATS>::Type logUnmapContribution (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_WRITE_STATS>::Type logWriteContribution (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_FLUSH_STATS>::Type logFlushContribution (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_ALLOC_STATS>::Type logAllocContribution (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_RENDER_STATS>::Type logRenderContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_READ_STATS>::Type logReadContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_UPLOAD_STATS>::Type logUploadContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_TOTAL_STATS>::Type logTotalContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_FIRST_RENDER_STATS>::Type logFirstRenderContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+template <typename SampleType>
+static typename EnableIfNot<void, SampleTypeTraits<SampleType>::HAS_SECOND_RENDER_STATS>::Type logSecondRenderContribution (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples, const typename SampleTypeTraits<SampleType>::StatsType& stats)
+{
+	DE_UNREF(log);
+	DE_UNREF(samples);
+	DE_UNREF(stats);
+}
+
+void logSampleList (tcu::TestLog& log, const TheilSenLineFit& theilSenFitting, const std::vector<UploadSampleResult<SingleOperationDuration> >& samples)
+{
+	log << tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("WrittenSize",		"Written size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("BufferSize",		"Buffer size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("UploadTime",		"Upload time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FitResidual",		"Fit residual",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float fitResidual = samples[sampleNdx].duration.fitResponseDuration - (theilSenFitting.offset + theilSenFitting.coefficient * samples[sampleNdx].writtenSize);
+		log	<< tcu::TestLog::Sample
+			<< samples[sampleNdx].writtenSize
+			<< samples[sampleNdx].bufferSize
+			<< (int)samples[sampleNdx].duration.totalDuration
+			<< fitResidual
+			<< tcu::TestLog::EndSample;
+	}
+
+	log << tcu::TestLog::EndSampleList;
+}
+
+void logSampleList (tcu::TestLog& log, const TheilSenLineFit& theilSenFitting, const std::vector<UploadSampleResult<MapBufferRangeDuration> >& samples)
+{
+	log << tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("WrittenSize",		"Written size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("BufferSize",		"Buffer size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("TotalTime",			"Total time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("AllocTime",			"Alloc time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("MapTime",			"Map time",				"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("UnmapTime",			"Unmap time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("WriteTime",			"Write time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FitResidual",		"Fit residual",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float fitResidual = samples[sampleNdx].duration.fitResponseDuration - (theilSenFitting.offset + theilSenFitting.coefficient * samples[sampleNdx].writtenSize);
+		log	<< tcu::TestLog::Sample
+			<< samples[sampleNdx].writtenSize
+			<< samples[sampleNdx].bufferSize
+			<< (int)samples[sampleNdx].duration.totalDuration
+			<< (int)samples[sampleNdx].duration.allocDuration
+			<< (int)samples[sampleNdx].duration.mapDuration
+			<< (int)samples[sampleNdx].duration.unmapDuration
+			<< (int)samples[sampleNdx].duration.writeDuration
+			<< fitResidual
+			<< tcu::TestLog::EndSample;
+	}
+
+	log << tcu::TestLog::EndSampleList;
+}
+
+void logSampleList (tcu::TestLog& log, const TheilSenLineFit& theilSenFitting, const std::vector<UploadSampleResult<MapBufferRangeDurationNoAlloc> >& samples)
+{
+	log << tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("WrittenSize",		"Written size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("BufferSize",		"Buffer size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("TotalTime",			"Total time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("MapTime",			"Map time",				"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("UnmapTime",			"Unmap time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("WriteTime",			"Write time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FitResidual",		"Fit residual",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float fitResidual = samples[sampleNdx].duration.fitResponseDuration - (theilSenFitting.offset + theilSenFitting.coefficient * samples[sampleNdx].writtenSize);
+		log	<< tcu::TestLog::Sample
+			<< samples[sampleNdx].writtenSize
+			<< samples[sampleNdx].bufferSize
+			<< (int)samples[sampleNdx].duration.totalDuration
+			<< (int)samples[sampleNdx].duration.mapDuration
+			<< (int)samples[sampleNdx].duration.unmapDuration
+			<< (int)samples[sampleNdx].duration.writeDuration
+			<< fitResidual
+			<< tcu::TestLog::EndSample;
+	}
+
+	log << tcu::TestLog::EndSampleList;
+}
+
+void logSampleList (tcu::TestLog& log, const TheilSenLineFit& theilSenFitting, const std::vector<UploadSampleResult<MapBufferRangeFlushDuration> >& samples)
+{
+	log << tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("WrittenSize",		"Written size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("BufferSize",		"Buffer size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("TotalTime",			"Total time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("AllocTime",			"Alloc time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("MapTime",			"Map time",				"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("UnmapTime",			"Unmap time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("WriteTime",			"Write time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FlushTime",			"Flush time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FitResidual",		"Fit residual",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float fitResidual = samples[sampleNdx].duration.fitResponseDuration - (theilSenFitting.offset + theilSenFitting.coefficient * samples[sampleNdx].writtenSize);
+		log	<< tcu::TestLog::Sample
+			<< samples[sampleNdx].writtenSize
+			<< samples[sampleNdx].bufferSize
+			<< (int)samples[sampleNdx].duration.totalDuration
+			<< (int)samples[sampleNdx].duration.allocDuration
+			<< (int)samples[sampleNdx].duration.mapDuration
+			<< (int)samples[sampleNdx].duration.unmapDuration
+			<< (int)samples[sampleNdx].duration.writeDuration
+			<< (int)samples[sampleNdx].duration.flushDuration
+			<< fitResidual
+			<< tcu::TestLog::EndSample;
+	}
+
+	log << tcu::TestLog::EndSampleList;
+}
+
+void logSampleList (tcu::TestLog& log, const TheilSenLineFit& theilSenFitting, const std::vector<UploadSampleResult<MapBufferRangeFlushDurationNoAlloc> >& samples)
+{
+	log << tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("WrittenSize",		"Written size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("BufferSize",		"Buffer size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("TotalTime",			"Total time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("MapTime",			"Map time",				"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("UnmapTime",			"Unmap time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("WriteTime",			"Write time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FlushTime",			"Flush time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FitResidual",		"Fit residual",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float fitResidual = samples[sampleNdx].duration.fitResponseDuration - (theilSenFitting.offset + theilSenFitting.coefficient * samples[sampleNdx].writtenSize);
+		log	<< tcu::TestLog::Sample
+			<< samples[sampleNdx].writtenSize
+			<< samples[sampleNdx].bufferSize
+			<< (int)samples[sampleNdx].duration.totalDuration
+			<< (int)samples[sampleNdx].duration.mapDuration
+			<< (int)samples[sampleNdx].duration.unmapDuration
+			<< (int)samples[sampleNdx].duration.writeDuration
+			<< (int)samples[sampleNdx].duration.flushDuration
+			<< fitResidual
+			<< tcu::TestLog::EndSample;
+	}
+
+	log << tcu::TestLog::EndSampleList;
+}
+
+void logSampleList (tcu::TestLog& log, const TheilSenLineFit& theilSenFitting, const std::vector<RenderSampleResult<RenderReadDuration> >& samples)
+{
+	log << tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("DataSize",			"Data processed",		"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("VertexCount",		"Number of vertices",	"vertices",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("TotalTime",			"Total time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("DrawCallTime",		"Draw call time",		"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("ReadTime",			"ReadPixels time",		"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FitResidual",		"Fit residual",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float fitResidual = samples[sampleNdx].duration.fitResponseDuration - (theilSenFitting.offset + theilSenFitting.coefficient * samples[sampleNdx].renderDataSize);
+		log	<< tcu::TestLog::Sample
+			<< samples[sampleNdx].renderDataSize
+			<< samples[sampleNdx].numVertices
+			<< (int)samples[sampleNdx].duration.renderReadDuration
+			<< (int)samples[sampleNdx].duration.renderDuration
+			<< (int)samples[sampleNdx].duration.readDuration
+			<< fitResidual
+			<< tcu::TestLog::EndSample;
+	}
+
+	log << tcu::TestLog::EndSampleList;
+}
+
+void logSampleList (tcu::TestLog& log, const TheilSenLineFit& theilSenFitting, const std::vector<RenderSampleResult<UnrelatedUploadRenderReadDuration> >& samples)
+{
+	log << tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("DataSize",				"Data processed",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("VertexCount",			"Number of vertices",		"vertices",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("UnrelatedUploadSize",	"Unrelated upload size",	"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("TotalTime",				"Total time",				"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("DrawCallTime",			"Draw call time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("ReadTime",				"ReadPixels time",			"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FitResidual",			"Fit residual",				"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float fitResidual = samples[sampleNdx].duration.fitResponseDuration - (theilSenFitting.offset + theilSenFitting.coefficient * samples[sampleNdx].renderDataSize);
+		log	<< tcu::TestLog::Sample
+			<< samples[sampleNdx].renderDataSize
+			<< samples[sampleNdx].numVertices
+			<< samples[sampleNdx].unrelatedDataSize
+			<< (int)samples[sampleNdx].duration.renderReadDuration
+			<< (int)samples[sampleNdx].duration.renderDuration
+			<< (int)samples[sampleNdx].duration.readDuration
+			<< fitResidual
+			<< tcu::TestLog::EndSample;
+	}
+
+	log << tcu::TestLog::EndSampleList;
+}
+
+void logSampleList (tcu::TestLog& log, const TheilSenLineFit& theilSenFitting, const std::vector<RenderSampleResult<UploadRenderReadDuration> >& samples)
+{
+	log << tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("DataSize",			"Data processed",					"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("UploadSize",		"Data uploaded",					"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("VertexCount",		"Number of vertices",				"vertices",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("DrawReadTime",		"Draw call and ReadPixels time",	"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("TotalTime",			"Total time",						"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("Upload time",		"Upload time",						"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("DrawCallTime",		"Draw call time",					"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("ReadTime",			"ReadPixels time",					"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FitResidual",		"Fit residual",						"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float fitResidual = samples[sampleNdx].duration.fitResponseDuration - (theilSenFitting.offset + theilSenFitting.coefficient * samples[sampleNdx].renderDataSize);
+		log	<< tcu::TestLog::Sample
+			<< samples[sampleNdx].renderDataSize
+			<< samples[sampleNdx].uploadedDataSize
+			<< samples[sampleNdx].numVertices
+			<< (int)samples[sampleNdx].duration.renderReadDuration
+			<< (int)samples[sampleNdx].duration.totalDuration
+			<< (int)samples[sampleNdx].duration.uploadDuration
+			<< (int)samples[sampleNdx].duration.renderDuration
+			<< (int)samples[sampleNdx].duration.readDuration
+			<< fitResidual
+			<< tcu::TestLog::EndSample;
+	}
+
+	log << tcu::TestLog::EndSampleList;
+}
+
+void logSampleList (tcu::TestLog& log, const TheilSenLineFit& theilSenFitting, const std::vector<RenderSampleResult<UploadRenderReadDurationWithUnrelatedUploadSize> >& samples)
+{
+	log << tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("DataSize",				"Data processed",					"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("UploadSize",			"Data uploaded",					"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("VertexCount",			"Number of vertices",				"vertices",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("UnrelatedUploadSize",	"Unrelated upload size",			"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("DrawReadTime",			"Draw call and ReadPixels time",	"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("TotalTime",				"Total time",						"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("Upload time",			"Upload time",						"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("DrawCallTime",			"Draw call time",					"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("ReadTime",				"ReadPixels time",					"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FitResidual",			"Fit residual",						"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float fitResidual = samples[sampleNdx].duration.fitResponseDuration - (theilSenFitting.offset + theilSenFitting.coefficient * samples[sampleNdx].renderDataSize);
+		log	<< tcu::TestLog::Sample
+			<< samples[sampleNdx].renderDataSize
+			<< samples[sampleNdx].uploadedDataSize
+			<< samples[sampleNdx].numVertices
+			<< samples[sampleNdx].unrelatedDataSize
+			<< (int)samples[sampleNdx].duration.renderReadDuration
+			<< (int)samples[sampleNdx].duration.totalDuration
+			<< (int)samples[sampleNdx].duration.uploadDuration
+			<< (int)samples[sampleNdx].duration.renderDuration
+			<< (int)samples[sampleNdx].duration.readDuration
+			<< fitResidual
+			<< tcu::TestLog::EndSample;
+	}
+
+	log << tcu::TestLog::EndSampleList;
+}
+
+void logSampleList (tcu::TestLog& log, const TheilSenLineFit& theilSenFitting, const std::vector<RenderSampleResult<RenderUploadRenderReadDuration> >& samples)
+{
+	log << tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("DataSize",				"Data processed",						"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("UploadSize",			"Data uploaded",						"bytes",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("VertexCount",			"Number of vertices",					"vertices",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("DrawReadTime",			"Second draw call and ReadPixels time",	"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("TotalTime",				"Total time",							"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FirstDrawCallTime",		"First draw call time",					"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("Upload time",			"Upload time",							"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("SecondDrawCallTime",	"Second draw call time",				"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("ReadTime",				"ReadPixels time",						"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("FitResidual",			"Fit residual",							"us",		QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)samples.size(); ++sampleNdx)
+	{
+		const float fitResidual = samples[sampleNdx].duration.fitResponseDuration - (theilSenFitting.offset + theilSenFitting.coefficient * samples[sampleNdx].renderDataSize);
+		log	<< tcu::TestLog::Sample
+			<< samples[sampleNdx].renderDataSize
+			<< samples[sampleNdx].uploadedDataSize
+			<< samples[sampleNdx].numVertices
+			<< (int)samples[sampleNdx].duration.renderReadDuration
+			<< (int)samples[sampleNdx].duration.totalDuration
+			<< (int)samples[sampleNdx].duration.firstRenderDuration
+			<< (int)samples[sampleNdx].duration.uploadDuration
+			<< (int)samples[sampleNdx].duration.secondRenderDuration
+			<< (int)samples[sampleNdx].duration.readDuration
+			<< fitResidual
+			<< tcu::TestLog::EndSample;
+	}
+
+	log << tcu::TestLog::EndSampleList;
+}
+
+template <typename SampleType>
+static UploadSampleAnalyzeResult analyzeSampleResults (tcu::TestLog& log, const std::vector<UploadSampleResult<SampleType> >& samples, bool logBucketPerformance)
+{
+	// Assume data is linear with some outliers, fit a line
+	const TheilSenLineFit									theilSenFitting						= fitLineToSamples(samples);
+	const typename SampleTypeTraits<SampleType>::StatsType	resultStats							= calculateSampleStatistics(theilSenFitting, samples);
+	float													approximatedTransferRate;
+	float													approximatedTransferRateNoConstant;
+
+	// Output raw samples
+	{
+		const tcu::ScopedLogSection	section(log, "Samples", "Samples");
+		logSampleList(log, theilSenFitting, samples);
+	}
+
+	// Calculate results for different ranges
+	if (logBucketPerformance)
+	{
+		const int										numBuckets				= 4;
+		int												minBufferSize			= 0;
+		int												maxBufferSize			= 0;
+		std::vector<UploadSampleResult<SampleType> >	buckets[numBuckets];
+
+		bucketizeSamplesUniformly(samples, &buckets[0], numBuckets, minBufferSize, maxBufferSize);
+
+		for (int bucketNdx = 0; bucketNdx < numBuckets; ++bucketNdx)
+		{
+			if (buckets[bucketNdx].empty())
+				continue;
+
+			// Print a nice result summary
+
+			const int												bucketRangeMin	= minBufferSize + (int)(( bucketNdx    / (float)numBuckets) * (maxBufferSize - minBufferSize));
+			const int												bucketRangeMax	= minBufferSize + (int)(((bucketNdx+1) / (float)numBuckets) * (maxBufferSize - minBufferSize));
+			const typename SampleTypeTraits<SampleType>::StatsType	stats			= calculateSampleStatistics(theilSenFitting, buckets[bucketNdx]);
+			const tcu::ScopedLogSection								section			(log, "BufferSizeRange", std::string("Transfer performance with buffer size in range [").append(getHumanReadableByteSize(bucketRangeMin).append(", ").append(getHumanReadableByteSize(bucketRangeMax).append("]"))));
+
+			logMapRangeStats<SampleType>(log, stats);
+			logUnmapStats<SampleType>(log, stats);
+			logWriteStats<SampleType>(log, stats);
+			logFlushStats<SampleType>(log, stats);
+			logAllocStats<SampleType>(log, stats);
+
+			log	<< tcu::TestLog::Float("Min", "Total: Min time", "us", QP_KEY_TAG_TIME, stats.result.minTime)
+				<< tcu::TestLog::Float("Max", "Total: Max time", "us", QP_KEY_TAG_TIME, stats.result.maxTime)
+				<< tcu::TestLog::Float("Min90", "Total: 90%-Min time", "us", QP_KEY_TAG_TIME, stats.result.min2DecileTime)
+				<< tcu::TestLog::Float("Max90", "Total: 90%-Max time", "us", QP_KEY_TAG_TIME, stats.result.max9DecileTime)
+				<< tcu::TestLog::Float("Median", "Total: Median time", "us", QP_KEY_TAG_TIME, stats.result.medianTime)
+				<< tcu::TestLog::Float("MedianTransfer", "Median transfer rate", "MB / s", QP_KEY_TAG_PERFORMANCE, stats.medianRate / 1024.0f / 1024.0f)
+				<< tcu::TestLog::Float("MaxDiff", "Max difference to approximated", "us", QP_KEY_TAG_TIME, stats.maxDiffTime)
+				<< tcu::TestLog::Float("Max90Diff", "90%-Max difference to approximated", "us", QP_KEY_TAG_TIME, stats.maxDiff9DecileTime)
+				<< tcu::TestLog::Float("MedianDiff", "Median difference to approximated", "us", QP_KEY_TAG_TIME, stats.medianDiffTime)
+				<< tcu::TestLog::Float("MaxRelDiff", "Max relative difference to approximated", "%", QP_KEY_TAG_NONE, stats.maxRelDiffTime * 100.0f)
+				<< tcu::TestLog::Float("Max90RelDiff", "90%-Max relative difference to approximated", "%", QP_KEY_TAG_NONE, stats.max9DecileRelDiffTime * 100.0f)
+				<< tcu::TestLog::Float("MedianRelDiff", "Median relative difference to approximated", "%", QP_KEY_TAG_NONE, stats.medianRelDiffTime * 100.0f);
+		}
+	}
+
+	// Contributions
+	if (SampleTypeTraits<SampleType>::LOG_CONTRIBUTIONS)
+	{
+		const tcu::ScopedLogSection	section(log, "Contribution", "Contributions");
+
+		logMapContribution(log, samples, resultStats);
+		logUnmapContribution(log, samples, resultStats);
+		logWriteContribution(log, samples, resultStats);
+		logFlushContribution(log, samples, resultStats);
+		logAllocContribution(log, samples, resultStats);
+	}
+
+	// Print results
+	{
+		const tcu::ScopedLogSection	section(log, "Results", "Results");
+
+		const int	medianBufferSize					= (samples.front().bufferSize + samples.back().bufferSize) / 2;
+		const float	approximatedTransferTime			= (theilSenFitting.offset + theilSenFitting.coefficient * medianBufferSize) / 1000.0f / 1000.0f;
+		const float	approximatedTransferTimeNoConstant	= (theilSenFitting.coefficient * medianBufferSize) / 1000.0f / 1000.0f;
+		const float	sampleLinearity						= calculateSampleFitLinearity(samples);
+		const float	sampleTemporalStability				= calculateSampleTemporalStability(samples);
+
+		approximatedTransferRateNoConstant				= medianBufferSize / approximatedTransferTimeNoConstant;
+		approximatedTransferRate						= medianBufferSize / approximatedTransferTime;
+
+		log	<< tcu::TestLog::Float("ResultLinearity", "Sample linearity", "%", QP_KEY_TAG_QUALITY, sampleLinearity * 100.0f)
+			<< tcu::TestLog::Float("SampleTemporalStability", "Sample temporal stability", "%", QP_KEY_TAG_QUALITY, sampleTemporalStability * 100.0f)
+			<< tcu::TestLog::Float("ApproximatedConstantCost", "Approximated contant cost", "us", QP_KEY_TAG_TIME, theilSenFitting.offset)
+			<< tcu::TestLog::Float("ApproximatedConstantCostConfidence60Lower", "Approximated contant cost 60% confidence lower limit", "us", QP_KEY_TAG_TIME, theilSenFitting.offset60ConfidenceLower)
+			<< tcu::TestLog::Float("ApproximatedConstantCostConfidence60Upper", "Approximated contant cost 60% confidence upper limit", "us", QP_KEY_TAG_TIME, theilSenFitting.offset60ConfidenceUpper)
+			<< tcu::TestLog::Float("ApproximatedLinearCost", "Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, theilSenFitting.coefficient * 1024.0f * 1024.0f)
+			<< tcu::TestLog::Float("ApproximatedLinearCostConfidence60Lower", "Approximated linear cost 60% confidence lower limit", "us / MB", QP_KEY_TAG_TIME, theilSenFitting.coefficient60ConfidenceLower * 1024.0f * 1024.0f)
+			<< tcu::TestLog::Float("ApproximatedLinearCostConfidence60Upper", "Approximated linear cost 60% confidence upper limit", "us / MB", QP_KEY_TAG_TIME, theilSenFitting.coefficient60ConfidenceUpper * 1024.0f * 1024.0f)
+			<< tcu::TestLog::Float("ApproximatedTransferRate", "Approximated transfer rate", "MB / s", QP_KEY_TAG_PERFORMANCE, approximatedTransferRate / 1024.0f / 1024.0f)
+			<< tcu::TestLog::Float("ApproximatedTransferRateNoConstant", "Approximated transfer rate without constant cost", "MB / s", QP_KEY_TAG_PERFORMANCE, approximatedTransferRateNoConstant / 1024.0f / 1024.0f)
+			<< tcu::TestLog::Float("SampleMedianTime", "Median sample time", "us", QP_KEY_TAG_TIME, resultStats.result.medianTime)
+			<< tcu::TestLog::Float("SampleMedianTransfer", "Median transfer rate", "MB / s", QP_KEY_TAG_PERFORMANCE, resultStats.medianRate / 1024.0f / 1024.0f);
+	}
+
+	// return approximated transfer rate
+	{
+		UploadSampleAnalyzeResult result;
+
+		result.transferRateMedian = resultStats.medianRate;
+		result.transferRateAtRange = approximatedTransferRate;
+		result.transferRateAtInfinity = approximatedTransferRateNoConstant;
+
+		return result;
+	}
+}
+
+template <typename SampleType>
+static RenderSampleAnalyzeResult analyzeSampleResults (tcu::TestLog& log, const std::vector<RenderSampleResult<SampleType> >& samples)
+{
+	// Assume data is linear with some outliers, fit a line
+	const TheilSenLineFit									theilSenFitting						= fitLineToSamples(samples);
+	const typename SampleTypeTraits<SampleType>::StatsType	resultStats							= calculateSampleStatistics(theilSenFitting, samples);
+	float													approximatedProcessingRate;
+	float													approximatedProcessingRateNoConstant;
+
+	// output raw samples
+	{
+		const tcu::ScopedLogSection	section(log, "Samples", "Samples");
+		logSampleList(log, theilSenFitting, samples);
+	}
+
+	// Contributions
+	if (SampleTypeTraits<SampleType>::LOG_CONTRIBUTIONS)
+	{
+		const tcu::ScopedLogSection	section(log, "Contribution", "Contributions");
+
+		logFirstRenderContribution(log, samples, resultStats);
+		logUploadContribution(log, samples, resultStats);
+		logRenderContribution(log, samples, resultStats);
+		logSecondRenderContribution(log, samples, resultStats);
+		logReadContribution(log, samples, resultStats);
+		logTotalContribution(log, samples, resultStats);
+	}
+
+	// print results
+	{
+		const tcu::ScopedLogSection	section(log, "Results", "Results");
+
+		const int	medianDataSize						= (samples.front().renderDataSize + samples.back().renderDataSize) / 2;
+		const float	approximatedRenderTime				= (theilSenFitting.offset + theilSenFitting.coefficient * medianDataSize) / 1000.0f / 1000.0f;
+		const float	approximatedRenderTimeNoConstant	= (theilSenFitting.coefficient * medianDataSize) / 1000.0f / 1000.0f;
+		const float	sampleLinearity						= calculateSampleFitLinearity(samples);
+		const float	sampleTemporalStability				= calculateSampleTemporalStability(samples);
+
+		approximatedProcessingRateNoConstant			= medianDataSize / approximatedRenderTimeNoConstant;
+		approximatedProcessingRate						= medianDataSize / approximatedRenderTime;
+
+		log	<< tcu::TestLog::Float("ResultLinearity", "Sample linearity", "%", QP_KEY_TAG_QUALITY, sampleLinearity * 100.0f)
+			<< tcu::TestLog::Float("SampleTemporalStability", "Sample temporal stability", "%", QP_KEY_TAG_QUALITY, sampleTemporalStability * 100.0f)
+			<< tcu::TestLog::Float("ApproximatedConstantCost", "Approximated contant cost", "us", QP_KEY_TAG_TIME, theilSenFitting.offset)
+			<< tcu::TestLog::Float("ApproximatedConstantCostConfidence60Lower", "Approximated contant cost 60% confidence lower limit", "us", QP_KEY_TAG_TIME, theilSenFitting.offset60ConfidenceLower)
+			<< tcu::TestLog::Float("ApproximatedConstantCostConfidence60Upper", "Approximated contant cost 60% confidence upper limit", "us", QP_KEY_TAG_TIME, theilSenFitting.offset60ConfidenceUpper)
+			<< tcu::TestLog::Float("ApproximatedLinearCost", "Approximated linear cost", "us / MB", QP_KEY_TAG_TIME, theilSenFitting.coefficient * 1024.0f * 1024.0f)
+			<< tcu::TestLog::Float("ApproximatedLinearCostConfidence60Lower", "Approximated linear cost 60% confidence lower limit", "us / MB", QP_KEY_TAG_TIME, theilSenFitting.coefficient60ConfidenceLower * 1024.0f * 1024.0f)
+			<< tcu::TestLog::Float("ApproximatedLinearCostConfidence60Upper", "Approximated linear cost 60% confidence upper limit", "us / MB", QP_KEY_TAG_TIME, theilSenFitting.coefficient60ConfidenceUpper * 1024.0f * 1024.0f)
+			<< tcu::TestLog::Float("ApproximatedProcessRate", "Approximated processing rate", "MB / s", QP_KEY_TAG_PERFORMANCE, approximatedProcessingRate / 1024.0f / 1024.0f)
+			<< tcu::TestLog::Float("ApproximatedProcessRateNoConstant", "Approximated processing rate without constant cost", "MB / s", QP_KEY_TAG_PERFORMANCE, approximatedProcessingRateNoConstant / 1024.0f / 1024.0f)
+			<< tcu::TestLog::Float("SampleMedianTime", "Median sample time", "us", QP_KEY_TAG_TIME, resultStats.result.medianTime)
+			<< tcu::TestLog::Float("SampleMedianProcess", "Median processing rate", "MB / s", QP_KEY_TAG_PERFORMANCE, resultStats.medianRate / 1024.0f / 1024.0f);
+	}
+
+	// return approximated render rate
+	{
+		RenderSampleAnalyzeResult result;
+
+		result.renderRateMedian		= resultStats.medianRate;
+		result.renderRateAtRange	= approximatedProcessingRate;
+		result.renderRateAtInfinity = approximatedProcessingRateNoConstant;
+
+		return result;
+	}
+	return RenderSampleAnalyzeResult();
+}
+
+static void generateTwoPassRandomIterationOrder (std::vector<int>& iterationOrder, int numSamples)
+{
+	de::Random	rnd			(0xabc);
+	const int	midPoint	= (numSamples+1) / 2;		// !< ceil(m_numSamples / 2)
+
+	DE_ASSERT((int)iterationOrder.size() == numSamples);
+
+	// Two "passes" over range, randomize order in both passes
+	// This allows to us detect if iterations are not independent
+	// (first run and later run samples differ significantly?)
+
+	for (int sampleNdx = 0; sampleNdx < midPoint; ++sampleNdx)
+		iterationOrder[sampleNdx] = sampleNdx * 2;
+	for (int sampleNdx = midPoint; sampleNdx < numSamples; ++sampleNdx)
+		iterationOrder[sampleNdx] = (sampleNdx - midPoint) * 2 + 1;
+
+	for (int ndx = 0; ndx < midPoint; ++ndx)
+		std::swap(iterationOrder[ndx], iterationOrder[rnd.getInt(0, midPoint - 1)]);
+	for (int ndx = midPoint; ndx < (int)iterationOrder.size(); ++ndx)
+		std::swap(iterationOrder[ndx], iterationOrder[rnd.getInt(midPoint, (int)iterationOrder.size()-1)]);
+}
+
+template <typename SampleType>
+class BasicBufferCase : public TestCase
+{
+public:
+
+	enum Flags
+	{
+		FLAG_ALLOCATE_LARGER_BUFFER = 0x01,
+	};
+							BasicBufferCase		(Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, int numSamples, int flags);
+							~BasicBufferCase	(void);
+
+	virtual void			init				(void);
+	virtual void			deinit				(void);
+
+protected:
+	IterateResult			iterate				(void);
+
+	virtual bool			runSample			(int iteration, UploadSampleResult<SampleType>& sample) = 0;
+	virtual void			logAndSetTestResult	(const std::vector<UploadSampleResult<SampleType> >& results) = 0;
+
+	void					disableGLWarmup		(void);
+	void					waitGLResults		(void);
+
+	enum
+	{
+		DUMMY_RENDER_AREA_SIZE = 32
+	};
+
+	glu::ShaderProgram*		m_dummyProgram;
+	deInt32					m_dummyProgramPosLoc;
+	deUint32				m_bufferID;
+
+	const int				m_numSamples;
+	const int				m_bufferSizeMin;
+	const int				m_bufferSizeMax;
+	const bool				m_allocateLargerBuffer;
+
+private:
+	int						m_iteration;
+	std::vector<int>		m_iterationOrder;
+	std::vector<UploadSampleResult<SampleType> > m_results;
+
+	bool					m_useGL;
+	int						m_bufferRandomizerTimer;
+};
+
+template <typename SampleType>
+BasicBufferCase<SampleType>::BasicBufferCase (Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, int numSamples, int flags)
+	: TestCase					(context, tcu::NODETYPE_PERFORMANCE, name, desc)
+	, m_dummyProgram			(DE_NULL)
+	, m_dummyProgramPosLoc		(-1)
+	, m_bufferID				(0)
+	, m_numSamples				(numSamples)
+	, m_bufferSizeMin			(bufferSizeMin)
+	, m_bufferSizeMax			(bufferSizeMax)
+	, m_allocateLargerBuffer	((flags & FLAG_ALLOCATE_LARGER_BUFFER) != 0)
+	, m_iteration				(0)
+	, m_iterationOrder			(numSamples)
+	, m_results					(numSamples)
+	, m_useGL					(true)
+	, m_bufferRandomizerTimer	(0)
+{
+	// "randomize" iteration order. Deterministic, patternless
+	generateTwoPassRandomIterationOrder(m_iterationOrder, m_numSamples);
+
+	// choose buffer sizes
+	for (int sampleNdx = 0; sampleNdx < m_numSamples; ++sampleNdx)
+	{
+		const int rawBufferSize			= (int)deFloatFloor(bufferSizeMin + (bufferSizeMax - bufferSizeMin) * ((float)(sampleNdx + 1) / m_numSamples));
+		const int bufferSize			= deAlign32(rawBufferSize, 16);
+		const int allocatedBufferSize	= deAlign32((m_allocateLargerBuffer) ? ((int)(bufferSize * 1.5f)) : (bufferSize), 16);
+
+		m_results[sampleNdx].bufferSize		= bufferSize;
+		m_results[sampleNdx].allocatedSize	= allocatedBufferSize;
+		m_results[sampleNdx].writtenSize	= -1;
+	}
+}
+
+template <typename SampleType>
+BasicBufferCase<SampleType>::~BasicBufferCase (void)
+{
+	deinit();
+}
+
+template <typename SampleType>
+void BasicBufferCase<SampleType>::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (!m_useGL)
+		return;
+
+	// \note Viewport size is not checked, it won't matter if the render target actually is smaller hhan DUMMY_RENDER_AREA_SIZE
+
+	// dummy shader
+
+	m_dummyProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_dummyVertexShader) << glu::FragmentSource(s_dummyFragnentShader));
+	if (!m_dummyProgram->isOk())
+	{
+		m_testCtx.getLog() << *m_dummyProgram;
+		throw tcu::TestError("failed to build shader program");
+	}
+
+	m_dummyProgramPosLoc = gl.getAttribLocation(m_dummyProgram->getProgram(), "a_position");
+	if (m_dummyProgramPosLoc == -1)
+		throw tcu::TestError("a_position location was -1");
+}
+
+template <typename SampleType>
+void BasicBufferCase<SampleType>::deinit (void)
+{
+	if (m_bufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_bufferID);
+		m_bufferID = 0;
+	}
+
+	delete m_dummyProgram;
+	m_dummyProgram = DE_NULL;
+}
+
+template <typename SampleType>
+TestCase::IterateResult BasicBufferCase<SampleType>::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	static bool				buffersWarmedUp	= false;
+
+	static const deUint32	usages[] =
+	{
+		GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY,
+		GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY,
+		GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, GL_DYNAMIC_COPY,
+	};
+
+	// Allocate some random sized buffers and remove them to
+	// make sure the first samples too have some buffers removed
+	// just before their allocation. This is only needed by the
+	// the first test.
+
+	if (m_useGL && !buffersWarmedUp)
+	{
+		const int					numRandomBuffers				= 6;
+		const int					numRepeats						= 10;
+		const int					maxBufferSize					= 16777216;
+		const std::vector<deUint8>	zeroData						(maxBufferSize, 0x00);
+		de::Random					rnd								(0x1234);
+		deUint32					bufferIDs[numRandomBuffers]		= {0};
+
+		gl.useProgram(m_dummyProgram->getProgram());
+		gl.viewport(0, 0, DUMMY_RENDER_AREA_SIZE, DUMMY_RENDER_AREA_SIZE);
+		gl.enableVertexAttribArray(m_dummyProgramPosLoc);
+
+		for (int ndx = 0; ndx < numRepeats; ++ndx)
+		{
+			// Create buffer and maybe draw from it
+			for (int randomBufferNdx = 0; randomBufferNdx < numRandomBuffers; ++randomBufferNdx)
+			{
+				const int		randomSize	= deAlign32(rnd.getInt(1, maxBufferSize), 4*4);
+				const deUint32	usage		= usages[rnd.getUint32() % (deUint32)DE_LENGTH_OF_ARRAY(usages)];
+
+				gl.genBuffers(1, &bufferIDs[randomBufferNdx]);
+				gl.bindBuffer(GL_ARRAY_BUFFER, bufferIDs[randomBufferNdx]);
+				gl.bufferData(GL_ARRAY_BUFFER, randomSize, &zeroData[0], usage);
+
+				if (rnd.getBool())
+				{
+					gl.vertexAttribPointer(m_dummyProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+					gl.drawArrays(GL_POINTS, 0, 1);
+					gl.drawArrays(GL_POINTS, randomSize / (int)sizeof(float[4]) - 1, 1);
+				}
+			}
+
+			for (int randomBufferNdx = 0; randomBufferNdx < numRandomBuffers; ++randomBufferNdx)
+				gl.deleteBuffers(1, &bufferIDs[randomBufferNdx]);
+
+			waitGLResults();
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer gen");
+
+			m_testCtx.touchWatchdog();
+		}
+
+		buffersWarmedUp = true;
+		return CONTINUE;
+	}
+	else if (m_useGL && m_bufferRandomizerTimer++ % 8 == 0)
+	{
+		// Do some random buffer operations to every now and then
+		// to make sure the previous test iterations won't affect
+		// following test runs.
+
+		const int					numRandomBuffers				= 3;
+		const int					maxBufferSize					= 16777216;
+		const std::vector<deUint8>	zeroData						(maxBufferSize, 0x00);
+		de::Random					rnd								(0x1234 + 0xabc * m_bufferRandomizerTimer);
+
+		// BufferData
+		{
+			deUint32 bufferIDs[numRandomBuffers] = {0};
+
+			for (int randomBufferNdx = 0; randomBufferNdx < numRandomBuffers; ++randomBufferNdx)
+			{
+				const int		randomSize	= deAlign32(rnd.getInt(1, maxBufferSize), 4*4);
+				const deUint32	usage		= usages[rnd.getUint32() % (deUint32)DE_LENGTH_OF_ARRAY(usages)];
+
+				gl.genBuffers(1, &bufferIDs[randomBufferNdx]);
+				gl.bindBuffer(GL_ARRAY_BUFFER, bufferIDs[randomBufferNdx]);
+				gl.bufferData(GL_ARRAY_BUFFER, randomSize, &zeroData[0], usage);
+			}
+
+			for (int randomBufferNdx = 0; randomBufferNdx < numRandomBuffers; ++randomBufferNdx)
+				gl.deleteBuffers(1, &bufferIDs[randomBufferNdx]);
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "buffer ops");
+
+		// Do some memory mappings
+		{
+			deUint32 bufferIDs[numRandomBuffers] = {0};
+
+			for (int randomBufferNdx = 0; randomBufferNdx < numRandomBuffers; ++randomBufferNdx)
+			{
+				const int		randomSize	= deAlign32(rnd.getInt(1, maxBufferSize), 4*4);
+				const deUint32	usage		= usages[rnd.getUint32() % (deUint32)DE_LENGTH_OF_ARRAY(usages)];
+				void*			ptr;
+
+				gl.genBuffers(1, &bufferIDs[randomBufferNdx]);
+				gl.bindBuffer(GL_ARRAY_BUFFER, bufferIDs[randomBufferNdx]);
+				gl.bufferData(GL_ARRAY_BUFFER, randomSize, &zeroData[0], usage);
+
+				gl.vertexAttribPointer(m_dummyProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+				gl.drawArrays(GL_POINTS, 0, 1);
+				gl.drawArrays(GL_POINTS, randomSize / (int)sizeof(float[4]) - 1, 1);
+
+				if (rnd.getBool())
+					waitGLResults();
+
+				ptr = gl.mapBufferRange(GL_ARRAY_BUFFER, 0, randomSize, GL_MAP_WRITE_BIT);
+				if (ptr)
+				{
+					medianTimeMemcpy(ptr, &zeroData[0], randomSize);
+					gl.unmapBuffer(GL_ARRAY_BUFFER);
+				}
+			}
+
+			for (int randomBufferNdx = 0; randomBufferNdx < numRandomBuffers; ++randomBufferNdx)
+				gl.deleteBuffers(1, &bufferIDs[randomBufferNdx]);
+
+			waitGLResults();
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "buffer maps");
+		return CONTINUE;
+	}
+	else
+	{
+		const int	currentIteration	= m_iteration;
+		const int	sampleNdx			= m_iterationOrder[currentIteration];
+		const bool	sampleRunSuccessful	= runSample(currentIteration, m_results[sampleNdx]);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "post runSample()");
+
+		// Retry failed samples
+		if (!sampleRunSuccessful)
+			return CONTINUE;
+
+		if (++m_iteration >= m_numSamples)
+		{
+			logAndSetTestResult(m_results);
+			return STOP;
+		}
+		else
+			return CONTINUE;
+	}
+}
+
+template <typename SampleType>
+void BasicBufferCase<SampleType>::disableGLWarmup (void)
+{
+	m_useGL = false;
+}
+
+template <typename SampleType>
+void BasicBufferCase<SampleType>::waitGLResults (void)
+{
+	tcu::Surface dummySurface(DUMMY_RENDER_AREA_SIZE, DUMMY_RENDER_AREA_SIZE);
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dummySurface.getAccess());
+}
+
+template <typename SampleType>
+class BasicUploadCase : public BasicBufferCase<SampleType>
+{
+public:
+	enum CaseType
+	{
+		CASE_NO_BUFFERS = 0,
+		CASE_NEW_BUFFER,
+		CASE_UNSPECIFIED_BUFFER,
+		CASE_SPECIFIED_BUFFER,
+		CASE_USED_BUFFER,
+		CASE_USED_LARGER_BUFFER,
+
+		CASE_LAST
+	};
+
+	enum CaseFlags
+	{
+		FLAG_DONT_LOG_BUFFER_INFO				= 0x01,
+		FLAG_RESULT_BUFFER_UNSPECIFIED_CONTENT	= 0x02,
+	};
+
+	enum ResultType
+	{
+		RESULT_MEDIAN_TRANSFER_RATE = 0,
+		RESULT_ASYMPTOTIC_TRANSFER_RATE,
+	};
+
+						BasicUploadCase		(Context& context,
+											 const char* name,
+											 const char* desc,
+											 int bufferSizeMin,
+											 int bufferSizeMax,
+											 int numSamples,
+											 deUint32 bufferUsage,
+											 CaseType caseType,
+											 ResultType resultType,
+											 int flags = 0);
+
+						~BasicUploadCase	(void);
+
+	virtual void		init				(void);
+	virtual void		deinit				(void);
+
+private:
+	bool				runSample			(int iteration, UploadSampleResult<SampleType>& sample);
+	void				createBuffer		(int bufferSize, int iteration);
+	void				deleteBuffer		(int bufferSize);
+	void				useBuffer			(int bufferSize);
+
+	virtual void		testBufferUpload	(UploadSampleResult<SampleType>& result, int writeSize) = 0;
+	void				logAndSetTestResult	(const std::vector<UploadSampleResult<SampleType> >& results);
+
+	deUint32			m_dummyBufferID;
+
+protected:
+	const CaseType		m_caseType;
+	const ResultType	m_resultType;
+	const deUint32		m_bufferUsage;
+	const bool			m_logBufferInfo;
+	const bool			m_bufferUnspecifiedContent;
+	std::vector<deUint8> m_zeroData;
+
+	using BasicBufferCase<SampleType>::m_testCtx;
+	using BasicBufferCase<SampleType>::m_context;
+
+	using BasicBufferCase<SampleType>::DUMMY_RENDER_AREA_SIZE;
+	using BasicBufferCase<SampleType>::m_dummyProgram;
+	using BasicBufferCase<SampleType>::m_dummyProgramPosLoc;
+	using BasicBufferCase<SampleType>::m_bufferID;
+	using BasicBufferCase<SampleType>::m_numSamples;
+	using BasicBufferCase<SampleType>::m_bufferSizeMin;
+	using BasicBufferCase<SampleType>::m_bufferSizeMax;
+	using BasicBufferCase<SampleType>::m_allocateLargerBuffer;
+};
+
+template <typename SampleType>
+BasicUploadCase<SampleType>::BasicUploadCase (Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, int numSamples, deUint32 bufferUsage, CaseType caseType, ResultType resultType, int flags)
+	: BasicBufferCase<SampleType>	(context, name, desc, bufferSizeMin, bufferSizeMax, numSamples, (caseType == CASE_USED_LARGER_BUFFER) ? (BasicBufferCase<SampleType>::FLAG_ALLOCATE_LARGER_BUFFER) : (0))
+	, m_dummyBufferID				(0)
+	, m_caseType					(caseType)
+	, m_resultType					(resultType)
+	, m_bufferUsage					(bufferUsage)
+	, m_logBufferInfo				((flags & FLAG_DONT_LOG_BUFFER_INFO) == 0)
+	, m_bufferUnspecifiedContent	((flags & FLAG_RESULT_BUFFER_UNSPECIFIED_CONTENT) != 0)
+	, m_zeroData					()
+{
+	DE_ASSERT(m_caseType < CASE_LAST);
+}
+
+template <typename SampleType>
+BasicUploadCase<SampleType>::~BasicUploadCase (void)
+{
+	deinit();
+}
+
+template <typename SampleType>
+void BasicUploadCase<SampleType>::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	BasicBufferCase<SampleType>::init();
+
+	// zero buffer as upload source
+	m_zeroData.resize(m_bufferSizeMax, 0x00);
+
+	// dummy buffer
+
+	gl.genBuffers(1, &m_dummyBufferID);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Gen buf");
+
+	// log basic info
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing performance with " << m_numSamples << " test samples. Sample order is randomized. All samples at even positions (first = 0) are tested before samples at odd positions.\n"
+		<< "Buffer sizes are in range [" << getHumanReadableByteSize(m_bufferSizeMin) << ", " << getHumanReadableByteSize(m_bufferSizeMax) << "]."
+		<< tcu::TestLog::EndMessage;
+
+	if (m_logBufferInfo)
+	{
+		switch (m_caseType)
+		{
+			case CASE_NO_BUFFERS:
+				break;
+
+			case CASE_NEW_BUFFER:
+				m_testCtx.getLog() << tcu::TestLog::Message << "Target buffer is generated but not specified (i.e glBufferData() not called)." << tcu::TestLog::EndMessage;
+				break;
+
+			case CASE_UNSPECIFIED_BUFFER:
+				m_testCtx.getLog() << tcu::TestLog::Message << "Target buffer is allocated with glBufferData(NULL)." << tcu::TestLog::EndMessage;
+				break;
+
+			case CASE_SPECIFIED_BUFFER:
+				m_testCtx.getLog() << tcu::TestLog::Message << "Target buffer contents are specified prior testing with glBufferData(data)." << tcu::TestLog::EndMessage;
+				break;
+
+			case CASE_USED_BUFFER:
+				m_testCtx.getLog() << tcu::TestLog::Message << "Target buffer has been used in drawing before testing." << tcu::TestLog::EndMessage;
+				break;
+
+			case CASE_USED_LARGER_BUFFER:
+				m_testCtx.getLog() << tcu::TestLog::Message << "Target buffer is larger and has been used in drawing before testing." << tcu::TestLog::EndMessage;
+				break;
+
+			default:
+				DE_ASSERT(false);
+				break;
+		}
+	}
+
+	if (m_resultType == RESULT_MEDIAN_TRANSFER_RATE)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Test result is the median transfer rate of the test samples." << tcu::TestLog::EndMessage;
+	else if (m_resultType == RESULT_ASYMPTOTIC_TRANSFER_RATE)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Test result is the asymptotic transfer rate as the buffer size approaches infinity." << tcu::TestLog::EndMessage;
+	else
+		DE_ASSERT(false);
+}
+
+template <typename SampleType>
+void BasicUploadCase<SampleType>::deinit (void)
+{
+	if (m_dummyBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_dummyBufferID);
+		m_dummyBufferID = 0;
+	}
+
+	m_zeroData.clear();
+
+	BasicBufferCase<SampleType>::deinit();
+}
+
+template <typename SampleType>
+bool BasicUploadCase<SampleType>::runSample (int iteration, UploadSampleResult<SampleType>& sample)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const int				allocatedBufferSize	= sample.allocatedSize;
+	const int				bufferSize			= sample.bufferSize;
+
+	if (m_caseType != CASE_NO_BUFFERS)
+		createBuffer(iteration, allocatedBufferSize);
+
+	// warmup CPU before the test to make sure the power management governor
+	// keeps us in the "high performance" mode
+	{
+		deYield();
+		tcu::warmupCPU();
+		deYield();
+	}
+
+	testBufferUpload(sample, bufferSize);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer upload sample");
+
+	if (m_caseType != CASE_NO_BUFFERS)
+		deleteBuffer(bufferSize);
+
+	return true;
+}
+
+template <typename SampleType>
+void BasicUploadCase<SampleType>::createBuffer (int iteration, int bufferSize)
+{
+	DE_ASSERT(!m_bufferID);
+	DE_ASSERT(m_caseType != CASE_NO_BUFFERS);
+
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// create buffer
+
+	if (m_caseType == CASE_NO_BUFFERS)
+		return;
+
+	// create empty buffer
+
+	gl.genBuffers(1, &m_bufferID);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_bufferID);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer gen");
+
+	if (m_caseType == CASE_NEW_BUFFER)
+	{
+		// upload something else first, this should reduce noise in samples
+
+		de::Random					rng				(0xbadc * iteration);
+		const int					sizeDelta		= rng.getInt(0, 2097140);
+		const int					dummyUploadSize = deAlign32(1048576 + sizeDelta, 4*4); // Vary buffer size to make sure it is always reallocated
+		const std::vector<deUint8>	dummyData		(dummyUploadSize, 0x20);
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_dummyBufferID);
+		gl.bufferData(GL_ARRAY_BUFFER, dummyUploadSize, &dummyData[0], m_bufferUsage);
+
+		// make sure upload won't interfere with the test
+		useBuffer(dummyUploadSize);
+
+		// don't kill the buffer so that the following upload cannot potentially reuse the buffer
+
+		return;
+	}
+
+	// specify it
+
+	if (m_caseType == CASE_UNSPECIFIED_BUFFER)
+		gl.bufferData(GL_ARRAY_BUFFER, bufferSize, DE_NULL, m_bufferUsage);
+	else
+	{
+		const std::vector<deUint8> dummyData(bufferSize, 0x20);
+		gl.bufferData(GL_ARRAY_BUFFER, bufferSize, &dummyData[0], m_bufferUsage);
+	}
+
+	if (m_caseType == CASE_UNSPECIFIED_BUFFER || m_caseType == CASE_SPECIFIED_BUFFER)
+		return;
+
+	// use it and make sure it is uploaded
+
+	useBuffer(bufferSize);
+	DE_ASSERT(m_caseType == CASE_USED_BUFFER || m_caseType == CASE_USED_LARGER_BUFFER);
+}
+
+template <typename SampleType>
+void BasicUploadCase<SampleType>::deleteBuffer (int bufferSize)
+{
+	DE_ASSERT(m_bufferID);
+	DE_ASSERT(m_caseType != CASE_NO_BUFFERS);
+
+	// render from the buffer to make sure it actually made it to the gpu. This is to
+	// make sure that if the upload actually happens later or is happening right now in
+	// the background, it will not interfere with further test runs
+
+	// if buffer contains unspecified content, sourcing data from it results in undefined
+	// results, possibly including program termination. Specify all data to prevent such
+	// case from happening
+
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_bufferID);
+
+	if (m_bufferUnspecifiedContent)
+	{
+		const std::vector<deUint8> dummyData(bufferSize, 0x20);
+		gl.bufferData(GL_ARRAY_BUFFER, bufferSize, &dummyData[0], m_bufferUsage);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "re-specify buffer");
+	}
+
+	useBuffer(bufferSize);
+
+	gl.deleteBuffers(1, &m_bufferID);
+	m_bufferID = 0;
+}
+
+template <typename SampleType>
+void BasicUploadCase<SampleType>::useBuffer (int bufferSize)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.useProgram(m_dummyProgram->getProgram());
+
+	gl.viewport(0, 0, DUMMY_RENDER_AREA_SIZE, DUMMY_RENDER_AREA_SIZE);
+	gl.vertexAttribPointer(m_dummyProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(m_dummyProgramPosLoc);
+
+	// use whole buffer to make sure buffer is uploaded by drawing first and last
+	DE_ASSERT(bufferSize % (int)sizeof(float[4]) == 0);
+	gl.drawArrays(GL_POINTS, 0, 1);
+	gl.drawArrays(GL_POINTS, bufferSize / (int)sizeof(float[4]) - 1, 1);
+
+	BasicBufferCase<SampleType>::waitGLResults();
+}
+
+template <typename SampleType>
+void BasicUploadCase<SampleType>::logAndSetTestResult (const std::vector<UploadSampleResult<SampleType> >& results)
+{
+	const UploadSampleAnalyzeResult	analysis	= analyzeSampleResults(m_testCtx.getLog(), results, true);
+
+	// with small buffers, report the median transfer rate of the samples
+	// with large buffers, report the expected preformance of infinitely large buffers
+	const float						rate		= (m_resultType == RESULT_ASYMPTOTIC_TRANSFER_RATE) ? (analysis.transferRateAtInfinity) : (analysis.transferRateMedian);
+
+	if (rate == std::numeric_limits<float>::infinity())
+	{
+		// sample times are 1) invalid or 2) timer resolution too low
+		// report speed 0 bytes / s since real value cannot be determined
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(0.0f, 2).c_str());
+	}
+	else
+	{
+		// report transfer rate in MB / s
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(rate / 1024.0f / 1024.0f, 2).c_str());
+	}
+}
+
+class ReferenceMemcpyCase : public BasicUploadCase<SingleOperationDuration>
+{
+public:
+				ReferenceMemcpyCase		(Context& ctx, const char* name, const char* desc, int minBufferSize, int maxBufferSize, int numSamples, bool largeBuffersCase);
+				~ReferenceMemcpyCase	(void);
+
+	void		init					(void);
+	void		deinit					(void);
+private:
+	void		testBufferUpload		(UploadSampleResult<SingleOperationDuration>& result, int bufferSize);
+
+	std::vector<deUint8> m_dstBuf;
+};
+
+ReferenceMemcpyCase::ReferenceMemcpyCase (Context& ctx, const char* name, const char* desc, int minBufferSize, int maxBufferSize, int numSamples, bool largeBuffersCase)
+	: BasicUploadCase<SingleOperationDuration>	(ctx, name, desc, minBufferSize, maxBufferSize, numSamples, 0, CASE_NO_BUFFERS, (largeBuffersCase) ? (RESULT_ASYMPTOTIC_TRANSFER_RATE) : (RESULT_MEDIAN_TRANSFER_RATE))
+	, m_dstBuf									()
+{
+	disableGLWarmup();
+}
+
+ReferenceMemcpyCase::~ReferenceMemcpyCase (void)
+{
+}
+
+void ReferenceMemcpyCase::init (void)
+{
+	// Describe what the test tries to do
+	m_testCtx.getLog() << tcu::TestLog::Message << "Testing performance of memcpy()." << tcu::TestLog::EndMessage;
+
+	m_dstBuf.resize(m_bufferSizeMax, 0x00);
+
+	BasicUploadCase<SingleOperationDuration>::init();
+}
+
+void ReferenceMemcpyCase::deinit (void)
+{
+	m_dstBuf.clear();
+	BasicUploadCase<SingleOperationDuration>::deinit();
+}
+
+void ReferenceMemcpyCase::testBufferUpload (UploadSampleResult<SingleOperationDuration>& result, int bufferSize)
+{
+	// write
+	result.duration.totalDuration = medianTimeMemcpy(&m_dstBuf[0], &m_zeroData[0], bufferSize);
+	result.duration.fitResponseDuration = result.duration.totalDuration;
+
+	result.writtenSize = bufferSize;
+}
+
+class BufferDataUploadCase : public BasicUploadCase<SingleOperationDuration>
+{
+public:
+				BufferDataUploadCase	(Context& ctx, const char* name, const char* desc, int minBufferSize, int maxBufferSize, int numSamples, deUint32 bufferUsage, CaseType caseType);
+				~BufferDataUploadCase	(void);
+
+	void		init					(void);
+private:
+	void		testBufferUpload		(UploadSampleResult<SingleOperationDuration>& result, int bufferSize);
+};
+
+BufferDataUploadCase::BufferDataUploadCase (Context& ctx, const char* name, const char* desc, int minBufferSize, int maxBufferSize, int numSamples, deUint32 bufferUsage, CaseType caseType)
+	: BasicUploadCase<SingleOperationDuration>(ctx, name, desc, minBufferSize, maxBufferSize, numSamples, bufferUsage, caseType, RESULT_MEDIAN_TRANSFER_RATE)
+{
+}
+
+BufferDataUploadCase::~BufferDataUploadCase (void)
+{
+}
+
+void BufferDataUploadCase::init (void)
+{
+	// Describe what the test tries to do
+	m_testCtx.getLog() << tcu::TestLog::Message << "Testing glBufferData() function." << tcu::TestLog::EndMessage;
+
+	BasicUploadCase<SingleOperationDuration>::init();
+}
+
+void BufferDataUploadCase::testBufferUpload (UploadSampleResult<SingleOperationDuration>& result, int bufferSize)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_bufferID);
+
+	// upload
+	{
+		deUint64 startTime;
+		deUint64 endTime;
+
+		startTime = deGetMicroseconds();
+		gl.bufferData(GL_ARRAY_BUFFER, bufferSize, &m_zeroData[0], m_bufferUsage);
+		endTime = deGetMicroseconds();
+
+		result.duration.totalDuration = endTime - startTime;
+		result.duration.fitResponseDuration = result.duration.totalDuration;
+		result.writtenSize = bufferSize;
+	}
+}
+
+class BufferSubDataUploadCase : public BasicUploadCase<SingleOperationDuration>
+{
+public:
+	enum Flags
+	{
+		FLAG_FULL_UPLOAD			= 0x01,
+		FLAG_PARTIAL_UPLOAD			= 0x02,
+		FLAG_INVALIDATE_BEFORE_USE	= 0x04,
+	};
+
+				BufferSubDataUploadCase		(Context& ctx, const char* name, const char* desc, int minBufferSize, int maxBufferSize, int numSamples, deUint32 bufferUsage, CaseType parentCase, int flags);
+				~BufferSubDataUploadCase	(void);
+
+	void		init						(void);
+private:
+	void		testBufferUpload			(UploadSampleResult<SingleOperationDuration>& result, int bufferSize);
+
+	const bool	m_fullUpload;
+	const bool	m_invalidateBeforeUse;
+};
+
+BufferSubDataUploadCase::BufferSubDataUploadCase (Context& ctx, const char* name, const char* desc, int minBufferSize, int maxBufferSize, int numSamples, deUint32 bufferUsage, CaseType parentCase, int flags)
+	: BasicUploadCase<SingleOperationDuration>	(ctx, name, desc, minBufferSize, maxBufferSize, numSamples, bufferUsage, parentCase, RESULT_MEDIAN_TRANSFER_RATE)
+	, m_fullUpload								((flags & FLAG_FULL_UPLOAD) != 0)
+	, m_invalidateBeforeUse						((flags & FLAG_INVALIDATE_BEFORE_USE) != 0)
+{
+	DE_ASSERT((flags & (FLAG_FULL_UPLOAD | FLAG_PARTIAL_UPLOAD)) != 0);
+	DE_ASSERT((flags & (FLAG_FULL_UPLOAD | FLAG_PARTIAL_UPLOAD)) != (FLAG_FULL_UPLOAD | FLAG_PARTIAL_UPLOAD));
+}
+
+BufferSubDataUploadCase::~BufferSubDataUploadCase (void)
+{
+}
+
+void BufferSubDataUploadCase::init (void)
+{
+	// Describe what the test tries to do
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing glBufferSubData() function call performance. "
+		<< ((m_fullUpload) ? ("The whole buffer is updated with glBufferSubData. ") : ("Half of the buffer data is updated with glBufferSubData. "))
+		<< ((m_invalidateBeforeUse) ? ("The buffer is cleared with glBufferData(..., NULL) before glBufferSubData upload.") : ("")) << "\n"
+		<< tcu::TestLog::EndMessage;
+
+	BasicUploadCase<SingleOperationDuration>::init();
+}
+
+void BufferSubDataUploadCase::testBufferUpload (UploadSampleResult<SingleOperationDuration>& result, int bufferSize)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_bufferID);
+
+	// "invalidate", upload null
+	if (m_invalidateBeforeUse)
+		gl.bufferData(GL_ARRAY_BUFFER, bufferSize, DE_NULL, m_bufferUsage);
+
+	// upload
+	{
+		deUint64 startTime;
+		deUint64 endTime;
+
+		startTime = deGetMicroseconds();
+
+		if (m_fullUpload)
+			gl.bufferSubData(GL_ARRAY_BUFFER, 0, bufferSize, &m_zeroData[0]);
+		else
+		{
+			// upload to buffer center
+			gl.bufferSubData(GL_ARRAY_BUFFER, bufferSize / 4, bufferSize / 2, &m_zeroData[0]);
+		}
+
+		endTime = deGetMicroseconds();
+
+		result.duration.totalDuration = endTime - startTime;
+		result.duration.fitResponseDuration = result.duration.totalDuration;
+
+		if (m_fullUpload)
+			result.writtenSize = bufferSize;
+		else
+			result.writtenSize = bufferSize / 2;
+	}
+}
+
+class MapBufferRangeCase : public BasicUploadCase<MapBufferRangeDuration>
+{
+public:
+	enum Flags
+	{
+		FLAG_PARTIAL						= 0x01,
+		FLAG_MANUAL_INVALIDATION			= 0x02,
+		FLAG_USE_UNUSED_UNSPECIFIED_BUFFER	= 0x04,
+		FLAG_USE_UNUSED_SPECIFIED_BUFFER	= 0x08,
+	};
+
+					MapBufferRangeCase			(Context& ctx, const char* name, const char* desc, int minBufferSize, int maxBufferSize, int numSamples, deUint32 bufferUsage, deUint32 mapFlags, int caseFlags);
+					~MapBufferRangeCase			(void);
+
+	void			init						(void);
+private:
+	static CaseType getBaseCaseType				(int caseFlags);
+	static int		getBaseFlags				(deUint32 mapFlags, int caseFlags);
+
+	void			testBufferUpload			(UploadSampleResult<MapBufferRangeDuration>& result, int bufferSize);
+	void			attemptBufferMap			(UploadSampleResult<MapBufferRangeDuration>& result, int bufferSize);
+
+	const bool		m_manualInvalidation;
+	const bool		m_fullUpload;
+	const bool		m_useUnusedUnspecifiedBuffer;
+	const bool		m_useUnusedSpecifiedBuffer;
+	const deUint32	m_mapFlags;
+	int				m_unmapFailures;
+};
+
+MapBufferRangeCase::MapBufferRangeCase (Context& ctx, const char* name, const char* desc, int minBufferSize, int maxBufferSize, int numSamples, deUint32 bufferUsage, deUint32 mapFlags, int caseFlags)
+	: BasicUploadCase<MapBufferRangeDuration>	(ctx, name, desc, minBufferSize, maxBufferSize, numSamples, bufferUsage, getBaseCaseType(caseFlags), RESULT_MEDIAN_TRANSFER_RATE, getBaseFlags(mapFlags, caseFlags))
+	, m_manualInvalidation						((caseFlags&FLAG_MANUAL_INVALIDATION) != 0)
+	, m_fullUpload								((caseFlags&FLAG_PARTIAL) == 0)
+	, m_useUnusedUnspecifiedBuffer				((caseFlags&FLAG_USE_UNUSED_UNSPECIFIED_BUFFER) != 0)
+	, m_useUnusedSpecifiedBuffer				((caseFlags&FLAG_USE_UNUSED_SPECIFIED_BUFFER) != 0)
+	, m_mapFlags								(mapFlags)
+	, m_unmapFailures							(0)
+{
+	DE_ASSERT(!(m_useUnusedUnspecifiedBuffer && m_useUnusedSpecifiedBuffer));
+	DE_ASSERT(!((m_useUnusedUnspecifiedBuffer || m_useUnusedSpecifiedBuffer) && m_manualInvalidation));
+}
+
+MapBufferRangeCase::~MapBufferRangeCase (void)
+{
+}
+
+void MapBufferRangeCase::init (void)
+{
+	// Describe what the test tries to do
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing glMapBufferRange() and glUnmapBuffer() function call performance.\n"
+		<< ((m_fullUpload) ? ("The whole buffer is mapped.") : ("Half of the buffer is mapped.")) << "\n"
+		<< ((m_useUnusedUnspecifiedBuffer) ? ("The buffer has not been used before mapping and is allocated with unspecified contents.\n") : (""))
+		<< ((m_useUnusedSpecifiedBuffer) ? ("The buffer has not been used before mapping and is allocated with specified contents.\n") : (""))
+		<< ((!m_useUnusedSpecifiedBuffer && !m_useUnusedUnspecifiedBuffer) ? ("The buffer has previously been used in a drawing operation.\n") : (""))
+		<< ((m_manualInvalidation) ? ("The buffer is cleared with glBufferData(..., NULL) before mapping.\n") : (""))
+		<< "Map bits:\n"
+		<< ((m_mapFlags & GL_MAP_WRITE_BIT) ? ("\tGL_MAP_WRITE_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_READ_BIT) ? ("\tGL_MAP_READ_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_INVALIDATE_RANGE_BIT) ? ("\tGL_MAP_INVALIDATE_RANGE_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_INVALIDATE_BUFFER_BIT) ? ("\tGL_MAP_INVALIDATE_BUFFER_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_UNSYNCHRONIZED_BIT) ? ("\tGL_MAP_UNSYNCHRONIZED_BIT\n") : (""))
+		<< tcu::TestLog::EndMessage;
+
+	BasicUploadCase<MapBufferRangeDuration>::init();
+}
+
+MapBufferRangeCase::CaseType MapBufferRangeCase::getBaseCaseType (int caseFlags)
+{
+	if ((caseFlags & FLAG_USE_UNUSED_UNSPECIFIED_BUFFER) == 0 && (caseFlags & FLAG_USE_UNUSED_SPECIFIED_BUFFER) == 0)
+		return CASE_USED_BUFFER;
+	else
+		return CASE_NEW_BUFFER;
+}
+
+int MapBufferRangeCase::getBaseFlags (deUint32 mapFlags, int caseFlags)
+{
+	int flags = FLAG_DONT_LOG_BUFFER_INFO;
+
+	// If buffer contains unspecified data when it is sourced (i.e drawn)
+	// results are undefined, and system errors may occur. Signal parent
+	// class to take this into account
+	if (caseFlags & FLAG_PARTIAL)
+	{
+		if ((mapFlags & GL_MAP_INVALIDATE_BUFFER_BIT) != 0			||
+			(caseFlags & FLAG_MANUAL_INVALIDATION) != 0				||
+			(caseFlags & FLAG_USE_UNUSED_UNSPECIFIED_BUFFER) != 0)
+		{
+			flags |= FLAG_RESULT_BUFFER_UNSPECIFIED_CONTENT;
+		}
+	}
+
+	return flags;
+}
+
+void MapBufferRangeCase::testBufferUpload (UploadSampleResult<MapBufferRangeDuration>& result, int bufferSize)
+{
+	const int unmapFailureThreshold = 4;
+
+	for (; m_unmapFailures < unmapFailureThreshold; ++m_unmapFailures)
+	{
+		try
+		{
+			attemptBufferMap(result, bufferSize);
+			return;
+		}
+		catch (UnmapFailureError&)
+		{
+		}
+	}
+
+	throw tcu::TestError("Unmapping failures exceeded limit");
+}
+
+void MapBufferRangeCase::attemptBufferMap (UploadSampleResult<MapBufferRangeDuration>& result, int bufferSize)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_bufferID);
+
+	if (m_fullUpload)
+		result.writtenSize = bufferSize;
+	else
+		result.writtenSize = bufferSize / 2;
+
+	// Create unused buffer
+
+	if (m_manualInvalidation || m_useUnusedUnspecifiedBuffer)
+	{
+		deUint64 startTime;
+		deUint64 endTime;
+
+		// "invalidate" or allocate, upload null
+		startTime = deGetMicroseconds();
+		gl.bufferData(GL_ARRAY_BUFFER, bufferSize, DE_NULL, m_bufferUsage);
+		endTime = deGetMicroseconds();
+
+		result.duration.allocDuration = endTime - startTime;
+	}
+	else if (m_useUnusedSpecifiedBuffer)
+	{
+		deUint64 startTime;
+		deUint64 endTime;
+
+		// Specify buffer contents
+		startTime = deGetMicroseconds();
+		gl.bufferData(GL_ARRAY_BUFFER, bufferSize, &m_zeroData[0], m_bufferUsage);
+		endTime = deGetMicroseconds();
+
+		result.duration.allocDuration = endTime - startTime;
+	}
+	else
+	{
+		// No alloc, no time
+		result.duration.allocDuration = 0;
+	}
+
+	// upload
+	{
+		void* mapPtr;
+
+		// Map
+		{
+			deUint64 startTime;
+			deUint64 endTime;
+
+			startTime = deGetMicroseconds();
+			if (m_fullUpload)
+				mapPtr = gl.mapBufferRange(GL_ARRAY_BUFFER, 0, result.writtenSize, m_mapFlags);
+			else
+			{
+				// upload to buffer center
+				mapPtr = gl.mapBufferRange(GL_ARRAY_BUFFER, bufferSize / 4, result.writtenSize, m_mapFlags);
+			}
+			endTime = deGetMicroseconds();
+
+			if (!mapPtr)
+				throw tcu::Exception("MapBufferRange returned NULL");
+
+			result.duration.mapDuration = endTime - startTime;
+		}
+
+		// Write
+		{
+			result.duration.writeDuration = medianTimeMemcpy(mapPtr, &m_zeroData[0], result.writtenSize);
+		}
+
+		// Unmap
+		{
+			deUint64		startTime;
+			deUint64		endTime;
+			glw::GLboolean	unmapSuccessful;
+
+			startTime = deGetMicroseconds();
+			unmapSuccessful = gl.unmapBuffer(GL_ARRAY_BUFFER);
+			endTime = deGetMicroseconds();
+
+			// if unmapping fails, just try again later
+			if (!unmapSuccessful)
+				throw UnmapFailureError();
+
+			result.duration.unmapDuration = endTime - startTime;
+		}
+
+		result.duration.totalDuration = result.duration.mapDuration + result.duration.writeDuration + result.duration.unmapDuration + result.duration.allocDuration;
+		result.duration.fitResponseDuration = result.duration.totalDuration;
+	}
+}
+
+class MapBufferRangeFlushCase : public BasicUploadCase<MapBufferRangeFlushDuration>
+{
+public:
+	enum Flags
+	{
+		FLAG_PARTIAL						= 0x01,
+		FLAG_FLUSH_IN_PARTS					= 0x02,
+		FLAG_USE_UNUSED_UNSPECIFIED_BUFFER	= 0x04,
+		FLAG_USE_UNUSED_SPECIFIED_BUFFER	= 0x08,
+		FLAG_FLUSH_PARTIAL					= 0x10,
+	};
+
+					MapBufferRangeFlushCase		(Context& ctx, const char* name, const char* desc, int minBufferSize, int maxBufferSize, int numSamples, deUint32 bufferUsage, deUint32 mapFlags, int caseFlags);
+					~MapBufferRangeFlushCase	(void);
+
+	void			init						(void);
+private:
+	static CaseType getBaseCaseType				(int caseFlags);
+	static int		getBaseFlags				(deUint32 mapFlags, int caseFlags);
+
+	void			testBufferUpload			(UploadSampleResult<MapBufferRangeFlushDuration>& result, int bufferSize);
+	void			attemptBufferMap			(UploadSampleResult<MapBufferRangeFlushDuration>& result, int bufferSize);
+
+	const bool		m_fullUpload;
+	const bool		m_flushInParts;
+	const bool		m_flushPartial;
+	const bool		m_useUnusedUnspecifiedBuffer;
+	const bool		m_useUnusedSpecifiedBuffer;
+	const deUint32	m_mapFlags;
+	int				m_unmapFailures;
+};
+
+MapBufferRangeFlushCase::MapBufferRangeFlushCase (Context& ctx, const char* name, const char* desc, int minBufferSize, int maxBufferSize, int numSamples, deUint32 bufferUsage, deUint32 mapFlags, int caseFlags)
+	: BasicUploadCase<MapBufferRangeFlushDuration>	(ctx, name, desc, minBufferSize, maxBufferSize, numSamples, bufferUsage, getBaseCaseType(caseFlags), RESULT_MEDIAN_TRANSFER_RATE, getBaseFlags(mapFlags, caseFlags))
+	, m_fullUpload									((caseFlags&FLAG_PARTIAL) == 0)
+	, m_flushInParts								((caseFlags&FLAG_FLUSH_IN_PARTS) != 0)
+	, m_flushPartial								((caseFlags&FLAG_FLUSH_PARTIAL) != 0)
+	, m_useUnusedUnspecifiedBuffer					((caseFlags&FLAG_USE_UNUSED_UNSPECIFIED_BUFFER) != 0)
+	, m_useUnusedSpecifiedBuffer					((caseFlags&FLAG_USE_UNUSED_SPECIFIED_BUFFER) != 0)
+	, m_mapFlags									(mapFlags)
+	, m_unmapFailures								(0)
+{
+	DE_ASSERT(!(m_flushPartial && m_flushInParts));
+	DE_ASSERT(!(m_flushPartial && !m_fullUpload));
+}
+
+MapBufferRangeFlushCase::~MapBufferRangeFlushCase (void)
+{
+}
+
+void MapBufferRangeFlushCase::init (void)
+{
+	// Describe what the test tries to do
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing glMapBufferRange(), glFlushMappedBufferRange() and glUnmapBuffer() function call performance.\n"
+		<< ((m_fullUpload) ? ("The whole buffer is mapped.") : ("Half of the buffer is mapped.")) << "\n"
+		<< ((m_flushInParts) ?
+			("The mapped range is partitioned to 4 subranges and each partition is flushed separately.") :
+			(m_flushPartial) ?
+				("Half of the buffer range is flushed.") :
+				("The whole mapped range is flushed in one flush call.")) << "\n"
+		<< ((m_useUnusedUnspecifiedBuffer) ? ("The buffer has not been used before mapping and is allocated with unspecified contents.\n") : (""))
+		<< ((m_useUnusedSpecifiedBuffer) ? ("The buffer has not been used before mapping and is allocated with specified contents.\n") : (""))
+		<< ((!m_useUnusedSpecifiedBuffer && !m_useUnusedUnspecifiedBuffer) ? ("The buffer has previously been used in a drawing operation.\n") : (""))
+		<< "Map bits:\n"
+		<< ((m_mapFlags & GL_MAP_WRITE_BIT) ? ("\tGL_MAP_WRITE_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_READ_BIT) ? ("\tGL_MAP_READ_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_INVALIDATE_RANGE_BIT) ? ("\tGL_MAP_INVALIDATE_RANGE_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_INVALIDATE_BUFFER_BIT) ? ("\tGL_MAP_INVALIDATE_BUFFER_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_UNSYNCHRONIZED_BIT) ? ("\tGL_MAP_UNSYNCHRONIZED_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_FLUSH_EXPLICIT_BIT) ? ("\tGL_MAP_FLUSH_EXPLICIT_BIT\n") : (""))
+		<< tcu::TestLog::EndMessage;
+
+	BasicUploadCase<MapBufferRangeFlushDuration>::init();
+}
+
+MapBufferRangeFlushCase::CaseType MapBufferRangeFlushCase::getBaseCaseType (int caseFlags)
+{
+	if ((caseFlags & FLAG_USE_UNUSED_UNSPECIFIED_BUFFER) == 0 && (caseFlags & FLAG_USE_UNUSED_SPECIFIED_BUFFER) == 0)
+		return CASE_USED_BUFFER;
+	else
+		return CASE_NEW_BUFFER;
+}
+
+int MapBufferRangeFlushCase::getBaseFlags (deUint32 mapFlags, int caseFlags)
+{
+	int flags = FLAG_DONT_LOG_BUFFER_INFO;
+
+	// If buffer contains unspecified data when it is sourced (i.e drawn)
+	// results are undefined, and system errors may occur. Signal parent
+	// class to take this into account
+	if (caseFlags & FLAG_PARTIAL)
+	{
+		if ((mapFlags & GL_MAP_INVALIDATE_BUFFER_BIT) != 0			||
+			(caseFlags & FLAG_USE_UNUSED_UNSPECIFIED_BUFFER) != 0	||
+			(caseFlags & FLAG_FLUSH_PARTIAL) != 0)
+		{
+			flags |= FLAG_RESULT_BUFFER_UNSPECIFIED_CONTENT;
+		}
+	}
+
+	return flags;
+}
+
+void MapBufferRangeFlushCase::testBufferUpload (UploadSampleResult<MapBufferRangeFlushDuration>& result, int bufferSize)
+{
+	const int unmapFailureThreshold = 4;
+
+	for (; m_unmapFailures < unmapFailureThreshold; ++m_unmapFailures)
+	{
+		try
+		{
+			attemptBufferMap(result, bufferSize);
+			return;
+		}
+		catch (UnmapFailureError&)
+		{
+		}
+	}
+
+	throw tcu::TestError("Unmapping failures exceeded limit");
+}
+
+void MapBufferRangeFlushCase::attemptBufferMap (UploadSampleResult<MapBufferRangeFlushDuration>& result, int bufferSize)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const int				mappedSize	= (m_fullUpload) ? (bufferSize) : (bufferSize / 2);
+
+	if (m_fullUpload && !m_flushPartial)
+		result.writtenSize = bufferSize;
+	else
+		result.writtenSize = bufferSize / 2;
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_bufferID);
+
+	// Create unused buffer
+
+	if (m_useUnusedUnspecifiedBuffer)
+	{
+		deUint64 startTime;
+		deUint64 endTime;
+
+		// Don't specify contents
+		startTime = deGetMicroseconds();
+		gl.bufferData(GL_ARRAY_BUFFER, bufferSize, DE_NULL, m_bufferUsage);
+		endTime = deGetMicroseconds();
+
+		result.duration.allocDuration = endTime - startTime;
+	}
+	else if (m_useUnusedSpecifiedBuffer)
+	{
+		deUint64 startTime;
+		deUint64 endTime;
+
+		// Specify buffer contents
+		startTime = deGetMicroseconds();
+		gl.bufferData(GL_ARRAY_BUFFER, bufferSize, &m_zeroData[0], m_bufferUsage);
+		endTime = deGetMicroseconds();
+
+		result.duration.allocDuration = endTime - startTime;
+	}
+	else
+	{
+		// No alloc, no time
+		result.duration.allocDuration = 0;
+	}
+
+	// upload
+	{
+		void* mapPtr;
+
+		// Map
+		{
+			deUint64 startTime;
+			deUint64 endTime;
+
+			startTime = deGetMicroseconds();
+			if (m_fullUpload)
+				mapPtr = gl.mapBufferRange(GL_ARRAY_BUFFER, 0, mappedSize, m_mapFlags);
+			else
+			{
+				// upload to buffer center
+				mapPtr = gl.mapBufferRange(GL_ARRAY_BUFFER, bufferSize / 4, mappedSize, m_mapFlags);
+			}
+			endTime = deGetMicroseconds();
+
+			if (!mapPtr)
+				throw tcu::Exception("MapBufferRange returned NULL");
+
+			result.duration.mapDuration = endTime - startTime;
+		}
+
+		// Write
+		{
+			if (!m_flushPartial)
+				result.duration.writeDuration = medianTimeMemcpy(mapPtr, &m_zeroData[0], result.writtenSize);
+			else
+				result.duration.writeDuration = medianTimeMemcpy((deUint8*)mapPtr + bufferSize / 4, &m_zeroData[0], result.writtenSize);
+		}
+
+		// Flush
+		{
+			deUint64	startTime;
+			deUint64	endTime;
+
+			startTime = deGetMicroseconds();
+
+			if (m_flushPartial)
+				gl.flushMappedBufferRange(GL_ARRAY_BUFFER, mappedSize/4, mappedSize/2);
+			else if (!m_flushInParts)
+				gl.flushMappedBufferRange(GL_ARRAY_BUFFER, 0, mappedSize);
+			else
+			{
+				const int p1 = 0;
+				const int p2 = mappedSize / 3;
+				const int p3 = mappedSize / 2;
+				const int p4 = mappedSize * 2 / 4;
+				const int p5 = mappedSize;
+
+				// flush in mixed order
+				gl.flushMappedBufferRange(GL_ARRAY_BUFFER, p2,	p3-p2);
+				gl.flushMappedBufferRange(GL_ARRAY_BUFFER, p1,	p2-p1);
+				gl.flushMappedBufferRange(GL_ARRAY_BUFFER, p4,	p5-p4);
+				gl.flushMappedBufferRange(GL_ARRAY_BUFFER, p3,	p4-p3);
+			}
+
+			endTime = deGetMicroseconds();
+
+			result.duration.flushDuration = endTime - startTime;
+		}
+
+		// Unmap
+		{
+			deUint64		startTime;
+			deUint64		endTime;
+			glw::GLboolean	unmapSuccessful;
+
+			startTime = deGetMicroseconds();
+			unmapSuccessful = gl.unmapBuffer(GL_ARRAY_BUFFER);
+			endTime = deGetMicroseconds();
+
+			// if unmapping fails, just try again later
+			if (!unmapSuccessful)
+				throw UnmapFailureError();
+
+			result.duration.unmapDuration = endTime - startTime;
+		}
+
+		result.duration.totalDuration = result.duration.mapDuration + result.duration.writeDuration + result.duration.flushDuration + result.duration.unmapDuration + result.duration.allocDuration;
+		result.duration.fitResponseDuration = result.duration.totalDuration;
+	}
+}
+
+template <typename SampleType>
+class ModifyAfterBasicCase : public BasicBufferCase<SampleType>
+{
+public:
+						ModifyAfterBasicCase	(Context& context, const char* name, const char* description, int bufferSizeMin, int bufferSizeMax, deUint32 usage, bool bufferUnspecifiedAfterTest);
+						~ModifyAfterBasicCase	(void);
+
+	void				init					(void);
+	void				deinit					(void);
+
+protected:
+	void				drawBufferRange			(int begin, int end);
+
+private:
+	enum
+	{
+		NUM_SAMPLES = 20,
+	};
+
+
+	bool				runSample				(int iteration, UploadSampleResult<SampleType>& sample);
+	bool				prepareAndRunTest		(int iteration, UploadSampleResult<SampleType>& result, int bufferSize);
+	void				logAndSetTestResult		(const std::vector<UploadSampleResult<SampleType> >& results);
+
+	virtual void		testWithBufferSize		(UploadSampleResult<SampleType>& result, int bufferSize) = 0;
+
+	int					m_unmappingErrors;
+
+protected:
+	const bool			m_bufferUnspecifiedAfterTest;
+	const deUint32		m_bufferUsage;
+	std::vector<deUint8> m_zeroData;
+
+	using BasicBufferCase<SampleType>::m_testCtx;
+	using BasicBufferCase<SampleType>::m_context;
+
+	using BasicBufferCase<SampleType>::DUMMY_RENDER_AREA_SIZE;
+	using BasicBufferCase<SampleType>::m_dummyProgram;
+	using BasicBufferCase<SampleType>::m_dummyProgramPosLoc;
+	using BasicBufferCase<SampleType>::m_bufferID;
+	using BasicBufferCase<SampleType>::m_numSamples;
+	using BasicBufferCase<SampleType>::m_bufferSizeMin;
+	using BasicBufferCase<SampleType>::m_bufferSizeMax;
+	using BasicBufferCase<SampleType>::m_allocateLargerBuffer;
+};
+
+template <typename SampleType>
+ModifyAfterBasicCase<SampleType>::ModifyAfterBasicCase (Context& context, const char* name, const char* description, int bufferSizeMin, int bufferSizeMax, deUint32 usage, bool bufferUnspecifiedAfterTest)
+	: BasicBufferCase<SampleType>	(context, name, description, bufferSizeMin, bufferSizeMax, NUM_SAMPLES, 0)
+	, m_unmappingErrors				(0)
+	, m_bufferUnspecifiedAfterTest	(bufferUnspecifiedAfterTest)
+	, m_bufferUsage					(usage)
+	, m_zeroData					()
+{
+}
+
+template <typename SampleType>
+ModifyAfterBasicCase<SampleType>::~ModifyAfterBasicCase (void)
+{
+	BasicBufferCase<SampleType>::deinit();
+}
+
+template <typename SampleType>
+void ModifyAfterBasicCase<SampleType>::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// init parent
+
+	BasicBufferCase<SampleType>::init();
+
+	// upload source
+	m_zeroData.resize(m_bufferSizeMax, 0x00);
+
+	// log basic info
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing performance with " << (int)NUM_SAMPLES << " test samples. Sample order is randomized. All samples at even positions (first = 0) are tested before samples at odd positions.\n"
+		<< "Buffer sizes are in range [" << getHumanReadableByteSize(m_bufferSizeMin) << ", " << getHumanReadableByteSize(m_bufferSizeMax) << "]."
+		<< tcu::TestLog::EndMessage;
+
+	// log which transfer rate is the test result and buffer info
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Test result is the median transfer rate of the test samples.\n"
+		<< "Buffer usage = " << glu::getUsageName(m_bufferUsage)
+		<< tcu::TestLog::EndMessage;
+
+	// Set state for drawing so that we don't have to change these during the iteration
+	{
+		gl.useProgram(m_dummyProgram->getProgram());
+		gl.viewport(0, 0, DUMMY_RENDER_AREA_SIZE, DUMMY_RENDER_AREA_SIZE);
+		gl.enableVertexAttribArray(m_dummyProgramPosLoc);
+	}
+}
+
+template <typename SampleType>
+void ModifyAfterBasicCase<SampleType>::deinit (void)
+{
+	m_zeroData.clear();
+
+	BasicBufferCase<SampleType>::deinit();
+}
+
+template <typename SampleType>
+void ModifyAfterBasicCase<SampleType>::drawBufferRange (int begin, int end)
+{
+	DE_ASSERT(begin % (int)sizeof(float[4]) == 0);
+	DE_ASSERT(end % (int)sizeof(float[4]) == 0);
+
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// use given range
+	gl.drawArrays(GL_POINTS, begin / (int)sizeof(float[4]), 1);
+	gl.drawArrays(GL_POINTS, end / (int)sizeof(float[4]) - 1, 1);
+}
+
+template <typename SampleType>
+bool ModifyAfterBasicCase<SampleType>::runSample (int iteration, UploadSampleResult<SampleType>& sample)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const int				bufferSize			= sample.bufferSize;
+	bool					testOk;
+
+	testOk = prepareAndRunTest(iteration, sample, bufferSize);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer upload sample");
+
+	if (!testOk)
+	{
+		const int unmapFailureThreshold = 4;
+
+		// only unmapping error can cause iteration failure
+		if (++m_unmappingErrors >= unmapFailureThreshold)
+			throw tcu::TestError("Too many unmapping errors, cannot continue.");
+
+		// just try again
+		return false;
+	}
+
+	return true;
+}
+
+template <typename SampleType>
+bool ModifyAfterBasicCase<SampleType>::prepareAndRunTest (int iteration, UploadSampleResult<SampleType>& result, int bufferSize)
+{
+	DE_UNREF(iteration);
+
+	DE_ASSERT(!m_bufferID);
+	DE_ASSERT(deIsAligned32(bufferSize, 4*4)); // aligned to vec4
+
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	bool						testRunOk		= true;
+	bool						unmappingFailed	= false;
+
+	// Upload initial buffer to the GPU...
+	gl.genBuffers(1, &m_bufferID);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_bufferID);
+	gl.bufferData(GL_ARRAY_BUFFER, bufferSize, &m_zeroData[0], m_bufferUsage);
+
+	// ...use it...
+	gl.vertexAttribPointer(m_dummyProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	drawBufferRange(0, bufferSize);
+
+	// ..and make sure it is uploaded
+	BasicBufferCase<SampleType>::waitGLResults();
+
+	// warmup CPU before the test to make sure the power management governor
+	// keeps us in the "high performance" mode
+	{
+		deYield();
+		tcu::warmupCPU();
+		deYield();
+	}
+
+	// test
+	try
+	{
+		// buffer is uploaded to the GPU. Draw from it.
+		drawBufferRange(0, bufferSize);
+
+		// and test upload
+		testWithBufferSize(result, bufferSize);
+	}
+	catch (UnmapFailureError&)
+	{
+		testRunOk = false;
+		unmappingFailed = true;
+	}
+
+	// clean up: make sure buffer is not in upload queue and delete it
+
+	// sourcing unspecified data causes undefined results, possibly program termination
+	if (m_bufferUnspecifiedAfterTest || unmappingFailed)
+		gl.bufferData(GL_ARRAY_BUFFER, bufferSize, &m_zeroData[0], m_bufferUsage);
+
+	drawBufferRange(0, bufferSize);
+	BasicBufferCase<SampleType>::waitGLResults();
+
+	gl.deleteBuffers(1, &m_bufferID);
+	m_bufferID = 0;
+
+	return testRunOk;
+}
+
+template <typename SampleType>
+void ModifyAfterBasicCase<SampleType>::logAndSetTestResult (const std::vector<UploadSampleResult<SampleType> >& results)
+{
+	const UploadSampleAnalyzeResult analysis = analyzeSampleResults(m_testCtx.getLog(), results, false);
+
+	// Return median transfer rate of the samples
+
+	if (analysis.transferRateMedian == std::numeric_limits<float>::infinity())
+	{
+		// sample times are 1) invalid or 2) timer resolution too low
+		// report speed 0 bytes / s since real value cannot be determined
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(0.0f, 2).c_str());
+	}
+	else
+	{
+		// report transfer rate in MB / s
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(analysis.transferRateMedian / 1024.0f / 1024.0f, 2).c_str());
+	}
+}
+
+class ModifyAfterWithBufferDataCase : public ModifyAfterBasicCase<SingleOperationDuration>
+{
+public:
+
+	enum CaseFlags
+	{
+		FLAG_RESPECIFY_SIZE		= 0x1,
+		FLAG_UPLOAD_REPEATED	= 0x2,
+	};
+
+					ModifyAfterWithBufferDataCase	(Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, deUint32 usage, int flags);
+					~ModifyAfterWithBufferDataCase	(void);
+
+	void			init							(void);
+	void			deinit							(void);
+private:
+	void			testWithBufferSize				(UploadSampleResult<SingleOperationDuration>& result, int bufferSize);
+
+	enum
+	{
+		NUM_REPEATS = 2
+	};
+
+	const bool		m_respecifySize;
+	const bool		m_repeatedUpload;
+	const float		m_sizeDifferenceFactor;
+};
+
+ModifyAfterWithBufferDataCase::ModifyAfterWithBufferDataCase (Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, deUint32 usage, int flags)
+	: ModifyAfterBasicCase<SingleOperationDuration> (context, name, desc, bufferSizeMin, bufferSizeMax, usage, false)
+	, m_respecifySize								((flags & FLAG_RESPECIFY_SIZE) != 0)
+	, m_repeatedUpload								((flags & FLAG_UPLOAD_REPEATED) != 0)
+	, m_sizeDifferenceFactor						(1.3f)
+{
+	DE_ASSERT(!(m_repeatedUpload && m_respecifySize));
+}
+
+ModifyAfterWithBufferDataCase::~ModifyAfterWithBufferDataCase (void)
+{
+	deinit();
+}
+
+void ModifyAfterWithBufferDataCase::init (void)
+{
+	// Log the purpose of the test
+
+	if (m_repeatedUpload)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Testing performance of BufferData() command after \"specify buffer contents - draw buffer\" command pair is repeated " << (int)NUM_REPEATS << " times." << tcu::TestLog::EndMessage;
+	else
+		m_testCtx.getLog() << tcu::TestLog::Message << "Testing performance of BufferData() command after a draw command that sources data from the target buffer." << tcu::TestLog::EndMessage;
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< ((m_respecifySize) ?
+			("Buffer size is increased and contents are modified with BufferData().\n") :
+			("Buffer contents are modified with BufferData().\n"))
+		<< tcu::TestLog::EndMessage;
+
+	// init parent
+	ModifyAfterBasicCase<SingleOperationDuration>::init();
+
+	// make sure our zeroBuffer is large enough
+	if (m_respecifySize)
+	{
+		const int largerBufferSize = deAlign32((int)(m_bufferSizeMax * m_sizeDifferenceFactor), 4*4);
+		m_zeroData.resize(largerBufferSize, 0x00);
+	}
+}
+
+void ModifyAfterWithBufferDataCase::deinit (void)
+{
+	ModifyAfterBasicCase<SingleOperationDuration>::deinit();
+}
+
+void ModifyAfterWithBufferDataCase::testWithBufferSize (UploadSampleResult<SingleOperationDuration>& result, int bufferSize)
+{
+	// always draw the same amount to make compares between cases sensible
+	const int					drawStart			= deAlign32(bufferSize / 4, 4*4);
+	const int					drawEnd				= deAlign32(bufferSize * 3 / 4, 4*4);
+
+	const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
+	const int					largerBufferSize	= deAlign32((int)(bufferSize * m_sizeDifferenceFactor), 4*4);
+	const int					newBufferSize		= (m_respecifySize) ? (largerBufferSize) : (bufferSize);
+	deUint64					startTime;
+	deUint64					endTime;
+
+	// repeat upload-draw
+	if (m_repeatedUpload)
+	{
+		for (int repeatNdx = 0; repeatNdx < NUM_REPEATS; ++repeatNdx)
+		{
+			gl.bufferData(GL_ARRAY_BUFFER, newBufferSize, &m_zeroData[0], m_bufferUsage);
+			drawBufferRange(drawStart, drawEnd);
+		}
+	}
+
+	// test upload
+	startTime = deGetMicroseconds();
+	gl.bufferData(GL_ARRAY_BUFFER, newBufferSize, &m_zeroData[0], m_bufferUsage);
+	endTime = deGetMicroseconds();
+
+	result.duration.totalDuration = endTime - startTime;
+	result.duration.fitResponseDuration = result.duration.totalDuration;
+	result.writtenSize = newBufferSize;
+}
+
+class ModifyAfterWithBufferSubDataCase : public ModifyAfterBasicCase<SingleOperationDuration>
+{
+public:
+
+	enum CaseFlags
+	{
+		FLAG_PARTIAL			= 0x1,
+		FLAG_UPLOAD_REPEATED	= 0x2,
+	};
+
+					ModifyAfterWithBufferSubDataCase	(Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, deUint32 usage, int flags);
+					~ModifyAfterWithBufferSubDataCase	(void);
+
+	void			init								(void);
+	void			deinit								(void);
+private:
+	void			testWithBufferSize					(UploadSampleResult<SingleOperationDuration>& result, int bufferSize);
+
+	enum
+	{
+		NUM_REPEATS = 2
+	};
+
+	const bool		m_partialUpload;
+	const bool		m_repeatedUpload;
+};
+
+ModifyAfterWithBufferSubDataCase::ModifyAfterWithBufferSubDataCase (Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, deUint32 usage, int flags)
+	: ModifyAfterBasicCase<SingleOperationDuration>	(context, name, desc, bufferSizeMin, bufferSizeMax, usage, false)
+	, m_partialUpload								((flags & FLAG_PARTIAL) != 0)
+	, m_repeatedUpload								((flags & FLAG_UPLOAD_REPEATED) != 0)
+{
+}
+
+ModifyAfterWithBufferSubDataCase::~ModifyAfterWithBufferSubDataCase (void)
+{
+	deinit();
+}
+
+void ModifyAfterWithBufferSubDataCase::init (void)
+{
+	// Log the purpose of the test
+
+	if (m_repeatedUpload)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Testing performance of BufferSubData() command after \"specify buffer contents - draw buffer\" command pair is repeated " << (int)NUM_REPEATS << " times." << tcu::TestLog::EndMessage;
+	else
+		m_testCtx.getLog() << tcu::TestLog::Message << "Testing performance of BufferSubData() command after a draw command that sources data from the target buffer." << tcu::TestLog::EndMessage;
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< ((m_partialUpload) ?
+			("Half of the buffer contents are modified.\n") :
+			("Buffer contents are fully respecified.\n"))
+		<< tcu::TestLog::EndMessage;
+
+	ModifyAfterBasicCase<SingleOperationDuration>::init();
+}
+
+void ModifyAfterWithBufferSubDataCase::deinit (void)
+{
+	ModifyAfterBasicCase<SingleOperationDuration>::deinit();
+}
+
+void ModifyAfterWithBufferSubDataCase::testWithBufferSize (UploadSampleResult<SingleOperationDuration>& result, int bufferSize)
+{
+	// always draw the same amount to make compares between cases sensible
+	const int					drawStart			= deAlign32(bufferSize / 4, 4*4);
+	const int					drawEnd				= deAlign32(bufferSize * 3 / 4, 4*4);
+
+	const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
+	const int					subdataOffset		= deAlign32((m_partialUpload) ? (bufferSize / 4) : (0), 4*4);
+	const int					subdataSize			= deAlign32((m_partialUpload) ? (bufferSize / 2) : (bufferSize), 4*4);
+	deUint64					startTime;
+	deUint64					endTime;
+
+	// make upload-draw stream
+	if (m_repeatedUpload)
+	{
+		for (int repeatNdx = 0; repeatNdx < NUM_REPEATS; ++repeatNdx)
+		{
+			gl.bufferSubData(GL_ARRAY_BUFFER, subdataOffset, subdataSize, &m_zeroData[0]);
+			drawBufferRange(drawStart, drawEnd);
+		}
+	}
+
+	// test upload
+	startTime = deGetMicroseconds();
+	gl.bufferSubData(GL_ARRAY_BUFFER, subdataOffset, subdataSize, &m_zeroData[0]);
+	endTime = deGetMicroseconds();
+
+	result.duration.totalDuration = endTime - startTime;
+	result.duration.fitResponseDuration = result.duration.totalDuration;
+	result.writtenSize = subdataSize;
+}
+
+class ModifyAfterWithMapBufferRangeCase : public ModifyAfterBasicCase<MapBufferRangeDurationNoAlloc>
+{
+public:
+
+	enum CaseFlags
+	{
+		FLAG_PARTIAL = 0x1,
+	};
+
+					ModifyAfterWithMapBufferRangeCase	(Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, deUint32 usage, int flags, deUint32 glMapFlags);
+					~ModifyAfterWithMapBufferRangeCase	(void);
+
+	void			init								(void);
+	void			deinit								(void);
+private:
+	static bool		isBufferUnspecifiedAfterUpload		(int flags, deUint32 mapFlags);
+	void			testWithBufferSize					(UploadSampleResult<MapBufferRangeDurationNoAlloc>& result, int bufferSize);
+
+	const bool		m_partialUpload;
+	const deUint32	m_mapFlags;
+};
+
+ModifyAfterWithMapBufferRangeCase::ModifyAfterWithMapBufferRangeCase (Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, deUint32 usage, int flags, deUint32 glMapFlags)
+	: ModifyAfterBasicCase<MapBufferRangeDurationNoAlloc>	(context, name, desc, bufferSizeMin, bufferSizeMax, usage, isBufferUnspecifiedAfterUpload(flags, glMapFlags))
+	, m_partialUpload										((flags & FLAG_PARTIAL) != 0)
+	, m_mapFlags											(glMapFlags)
+{
+}
+
+ModifyAfterWithMapBufferRangeCase::~ModifyAfterWithMapBufferRangeCase (void)
+{
+	deinit();
+}
+
+void ModifyAfterWithMapBufferRangeCase::init (void)
+{
+	// Log the purpose of the test
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing performance of MapBufferRange() command after a draw command that sources data from the target buffer.\n"
+		<< ((m_partialUpload) ?
+			("Half of the buffer is mapped.\n") :
+			("Whole buffer is mapped.\n"))
+		<< "Map bits:\n"
+		<< ((m_mapFlags & GL_MAP_WRITE_BIT) ? ("\tGL_MAP_WRITE_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_READ_BIT) ? ("\tGL_MAP_READ_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_INVALIDATE_RANGE_BIT) ? ("\tGL_MAP_INVALIDATE_RANGE_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_INVALIDATE_BUFFER_BIT) ? ("\tGL_MAP_INVALIDATE_BUFFER_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_UNSYNCHRONIZED_BIT) ? ("\tGL_MAP_UNSYNCHRONIZED_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_FLUSH_EXPLICIT_BIT) ? ("\tGL_MAP_FLUSH_EXPLICIT_BIT\n") : (""))
+		<< tcu::TestLog::EndMessage;
+
+	ModifyAfterBasicCase<MapBufferRangeDurationNoAlloc>::init();
+}
+
+void ModifyAfterWithMapBufferRangeCase::deinit (void)
+{
+	ModifyAfterBasicCase<MapBufferRangeDurationNoAlloc>::deinit();
+}
+
+bool ModifyAfterWithMapBufferRangeCase::isBufferUnspecifiedAfterUpload (int flags, deUint32 mapFlags)
+{
+	if ((flags & FLAG_PARTIAL) != 0 && ((mapFlags & GL_MAP_INVALIDATE_BUFFER_BIT) != 0))
+		return true;
+
+	return false;
+}
+
+void ModifyAfterWithMapBufferRangeCase::testWithBufferSize (UploadSampleResult<MapBufferRangeDurationNoAlloc>& result, int bufferSize)
+{
+	const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
+	const int					subdataOffset		= deAlign32((m_partialUpload) ? (bufferSize / 4) : (0), 4*4);
+	const int					subdataSize			= deAlign32((m_partialUpload) ? (bufferSize / 2) : (bufferSize), 4*4);
+	void*						mapPtr;
+
+	// map
+	{
+		deUint64 startTime;
+		deUint64 endTime;
+
+		startTime = deGetMicroseconds();
+		mapPtr = gl.mapBufferRange(GL_ARRAY_BUFFER, subdataOffset, subdataSize, m_mapFlags);
+		endTime = deGetMicroseconds();
+
+		if (!mapPtr)
+			throw tcu::TestError("mapBufferRange returned null");
+
+		result.duration.mapDuration = endTime - startTime;
+	}
+
+	// write
+	{
+		result.duration.writeDuration = medianTimeMemcpy(mapPtr, &m_zeroData[0], subdataSize);
+	}
+
+	// unmap
+	{
+		deUint64		startTime;
+		deUint64		endTime;
+		glw::GLboolean	unmapSucceeded;
+
+		startTime = deGetMicroseconds();
+		unmapSucceeded = gl.unmapBuffer(GL_ARRAY_BUFFER);
+		endTime = deGetMicroseconds();
+
+		if (unmapSucceeded != GL_TRUE)
+			throw UnmapFailureError();
+
+		result.duration.unmapDuration = endTime - startTime;
+	}
+
+	result.duration.totalDuration = result.duration.mapDuration + result.duration.writeDuration + result.duration.unmapDuration;
+	result.duration.fitResponseDuration = result.duration.totalDuration;
+	result.writtenSize = subdataSize;
+}
+
+class ModifyAfterWithMapBufferFlushCase : public ModifyAfterBasicCase<MapBufferRangeFlushDurationNoAlloc>
+{
+public:
+
+	enum CaseFlags
+	{
+		FLAG_PARTIAL = 0x1,
+	};
+
+					ModifyAfterWithMapBufferFlushCase	(Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, deUint32 usage, int flags, deUint32 glMapFlags);
+					~ModifyAfterWithMapBufferFlushCase	(void);
+
+	void			init								(void);
+	void			deinit								(void);
+private:
+	static bool		isBufferUnspecifiedAfterUpload		(int flags, deUint32 mapFlags);
+	void			testWithBufferSize					(UploadSampleResult<MapBufferRangeFlushDurationNoAlloc>& result, int bufferSize);
+
+	const bool		m_partialUpload;
+	const deUint32	m_mapFlags;
+};
+
+ModifyAfterWithMapBufferFlushCase::ModifyAfterWithMapBufferFlushCase (Context& context, const char* name, const char* desc, int bufferSizeMin, int bufferSizeMax, deUint32 usage, int flags, deUint32 glMapFlags)
+	: ModifyAfterBasicCase<MapBufferRangeFlushDurationNoAlloc>	(context, name, desc, bufferSizeMin, bufferSizeMax, usage, isBufferUnspecifiedAfterUpload(flags, glMapFlags))
+	, m_partialUpload											((flags & FLAG_PARTIAL) != 0)
+	, m_mapFlags												(glMapFlags)
+{
+}
+
+ModifyAfterWithMapBufferFlushCase::~ModifyAfterWithMapBufferFlushCase (void)
+{
+	deinit();
+}
+
+void ModifyAfterWithMapBufferFlushCase::init (void)
+{
+	// Log the purpose of the test
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing performance of MapBufferRange() command after a draw command that sources data from the target buffer.\n"
+		<< ((m_partialUpload) ?
+			("Half of the buffer is mapped.\n") :
+			("Whole buffer is mapped.\n"))
+		<< "Map bits:\n"
+		<< ((m_mapFlags & GL_MAP_WRITE_BIT) ? ("\tGL_MAP_WRITE_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_READ_BIT) ? ("\tGL_MAP_READ_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_INVALIDATE_RANGE_BIT) ? ("\tGL_MAP_INVALIDATE_RANGE_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_INVALIDATE_BUFFER_BIT) ? ("\tGL_MAP_INVALIDATE_BUFFER_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_UNSYNCHRONIZED_BIT) ? ("\tGL_MAP_UNSYNCHRONIZED_BIT\n") : (""))
+		<< ((m_mapFlags & GL_MAP_FLUSH_EXPLICIT_BIT) ? ("\tGL_MAP_FLUSH_EXPLICIT_BIT\n") : (""))
+		<< tcu::TestLog::EndMessage;
+
+	ModifyAfterBasicCase<MapBufferRangeFlushDurationNoAlloc>::init();
+}
+
+void ModifyAfterWithMapBufferFlushCase::deinit (void)
+{
+	ModifyAfterBasicCase<MapBufferRangeFlushDurationNoAlloc>::deinit();
+}
+
+bool ModifyAfterWithMapBufferFlushCase::isBufferUnspecifiedAfterUpload (int flags, deUint32 mapFlags)
+{
+	if ((flags & FLAG_PARTIAL) != 0 && ((mapFlags & GL_MAP_INVALIDATE_BUFFER_BIT) != 0))
+		return true;
+
+	return false;
+}
+
+void ModifyAfterWithMapBufferFlushCase::testWithBufferSize (UploadSampleResult<MapBufferRangeFlushDurationNoAlloc>& result, int bufferSize)
+{
+	const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
+	const int					subdataOffset		= deAlign32((m_partialUpload) ? (bufferSize / 4) : (0), 4*4);
+	const int					subdataSize			= deAlign32((m_partialUpload) ? (bufferSize / 2) : (bufferSize), 4*4);
+	void*						mapPtr;
+
+	// map
+	{
+		deUint64 startTime;
+		deUint64 endTime;
+
+		startTime = deGetMicroseconds();
+		mapPtr = gl.mapBufferRange(GL_ARRAY_BUFFER, subdataOffset, subdataSize, m_mapFlags);
+		endTime = deGetMicroseconds();
+
+		if (!mapPtr)
+			throw tcu::TestError("mapBufferRange returned null");
+
+		result.duration.mapDuration = endTime - startTime;
+	}
+
+	// write
+	{
+		result.duration.writeDuration = medianTimeMemcpy(mapPtr, &m_zeroData[0], subdataSize);
+	}
+
+	// flush
+	{
+		deUint64 startTime;
+		deUint64 endTime;
+
+		startTime = deGetMicroseconds();
+		gl.flushMappedBufferRange(GL_ARRAY_BUFFER, 0, subdataSize);
+		endTime = deGetMicroseconds();
+
+		result.duration.flushDuration = endTime - startTime;
+	}
+
+	// unmap
+	{
+		deUint64		startTime;
+		deUint64		endTime;
+		glw::GLboolean	unmapSucceeded;
+
+		startTime = deGetMicroseconds();
+		unmapSucceeded = gl.unmapBuffer(GL_ARRAY_BUFFER);
+		endTime = deGetMicroseconds();
+
+		if (unmapSucceeded != GL_TRUE)
+			throw UnmapFailureError();
+
+		result.duration.unmapDuration = endTime - startTime;
+	}
+
+	result.duration.totalDuration = result.duration.mapDuration + result.duration.writeDuration + result.duration.unmapDuration + result.duration.flushDuration;
+	result.duration.fitResponseDuration = result.duration.totalDuration;
+	result.writtenSize = subdataSize;
+}
+
+enum DrawMethod
+{
+	DRAWMETHOD_DRAW_ARRAYS = 0,
+	DRAWMETHOD_DRAW_ELEMENTS,
+
+	DRAWMETHOD_LAST
+};
+
+enum TargetBuffer
+{
+	TARGETBUFFER_VERTEX = 0,
+	TARGETBUFFER_INDEX,
+
+	TARGETBUFFER_LAST
+};
+
+enum BufferState
+{
+	BUFFERSTATE_NEW = 0,
+	BUFFERSTATE_EXISTING,
+
+	BUFFERSTATE_LAST
+};
+
+enum UploadMethod
+{
+	UPLOADMETHOD_BUFFER_DATA = 0,
+	UPLOADMETHOD_BUFFER_SUB_DATA,
+	UPLOADMETHOD_MAP_BUFFER_RANGE,
+
+	UPLOADMETHOD_LAST
+};
+
+enum UnrelatedBufferType
+{
+	UNRELATEDBUFFERTYPE_NONE = 0,
+	UNRELATEDBUFFERTYPE_VERTEX,
+
+	UNRELATEDBUFFERTYPE_LAST
+};
+
+enum UploadRange
+{
+	UPLOADRANGE_FULL = 0,
+	UPLOADRANGE_PARTIAL,
+
+	UPLOADRANGE_LAST
+};
+
+struct LayeredGridSpec
+{
+	int gridWidth;
+	int gridHeight;
+	int gridLayers;
+};
+
+static int getLayeredGridNumVertices (const LayeredGridSpec& scene)
+{
+	return scene.gridWidth * scene.gridHeight * scene.gridLayers * 6;
+}
+
+static void generateLayeredGridVertexAttribData4C4V (std::vector<tcu::Vec4>& vertexData, const LayeredGridSpec& scene)
+{
+	// interleave color & vertex data
+	const tcu::Vec4 green	(0.0f, 1.0f, 0.0f, 0.7f);
+	const tcu::Vec4 yellow	(1.0f, 1.0f, 0.0f, 0.8f);
+
+	vertexData.resize(getLayeredGridNumVertices(scene) * 2);
+
+	for (int cellY = 0; cellY < scene.gridHeight; ++cellY)
+	for (int cellX = 0; cellX < scene.gridWidth; ++cellX)
+	for (int cellZ = 0; cellZ < scene.gridLayers; ++cellZ)
+	{
+		const tcu::Vec4	color		= (((cellX + cellY + cellZ) % 2) == 0) ? (green) : (yellow);
+		const float		cellLeft	= (float(cellX  ) / scene.gridWidth  - 0.5f) * 2.0f;
+		const float		cellRight	= (float(cellX+1) / scene.gridWidth  - 0.5f) * 2.0f;
+		const float		cellTop		= (float(cellY+1) / scene.gridHeight - 0.5f) * 2.0f;
+		const float		cellBottom	= (float(cellY  ) / scene.gridHeight - 0.5f) * 2.0f;
+
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 +  0] = color;
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 +  1] = tcu::Vec4(cellLeft, cellTop, 0.0f, 1.0f);
+
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 +  2] = color;
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 +  3] = tcu::Vec4(cellLeft, cellBottom, 0.0f, 1.0f);
+
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 +  4] = color;
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 +  5] = tcu::Vec4(cellRight, cellBottom, 0.0f, 1.0f);
+
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 +  6] = color;
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 +  7] = tcu::Vec4(cellLeft, cellTop, 0.0f, 1.0f);
+
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 +  8] = color;
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 +  9] = tcu::Vec4(cellRight, cellBottom, 0.0f, 1.0f);
+
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 + 10] = color;
+		vertexData[(cellY * scene.gridWidth * scene.gridLayers + cellX * scene.gridLayers + cellZ) * 12 + 11] = tcu::Vec4(cellRight, cellTop, 0.0f, 1.0f);
+	}
+}
+
+static void generateLayeredGridIndexData (std::vector<deUint32>& indexData, const LayeredGridSpec& scene)
+{
+	indexData.resize(getLayeredGridNumVertices(scene) * 2);
+
+	for (int ndx = 0; ndx < scene.gridLayers * scene.gridHeight * scene.gridWidth * 6; ++ndx)
+		indexData[ndx] = ndx;
+}
+
+class RenderPerformanceTestBase : public TestCase
+{
+public:
+							RenderPerformanceTestBase	(Context& context, const char* name, const char* description);
+							~RenderPerformanceTestBase	(void);
+
+protected:
+	void					init						(void);
+	void					deinit						(void);
+
+	void					waitGLResults				(void) const;
+	void					setupVertexAttribs			(void) const;
+
+	enum
+	{
+		RENDER_AREA_SIZE = 128
+	};
+
+private:
+	glu::ShaderProgram*		m_renderProgram;
+	int						m_colorLoc;
+	int						m_positionLoc;
+};
+
+RenderPerformanceTestBase::RenderPerformanceTestBase (Context& context, const char* name, const char* description)
+	: TestCase			(context, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_renderProgram	(DE_NULL)
+	, m_colorLoc		(0)
+	, m_positionLoc		(0)
+{
+}
+
+RenderPerformanceTestBase::~RenderPerformanceTestBase (void)
+{
+	deinit();
+}
+
+void RenderPerformanceTestBase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	m_renderProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_colorVertexShader) << glu::FragmentSource(s_colorFragmentShader));
+	if (!m_renderProgram->isOk())
+	{
+		m_testCtx.getLog() << *m_renderProgram;
+		throw tcu::TestError("could not build program");
+	}
+
+	m_colorLoc = gl.getAttribLocation(m_renderProgram->getProgram(), "a_color");
+	m_positionLoc = gl.getAttribLocation(m_renderProgram->getProgram(), "a_position");
+
+	if (m_colorLoc == -1)
+		throw tcu::TestError("Location of attribute a_color was -1");
+	if (m_positionLoc == -1)
+		throw tcu::TestError("Location of attribute a_position was -1");
+}
+
+void RenderPerformanceTestBase::deinit (void)
+{
+	delete m_renderProgram;
+	m_renderProgram = DE_NULL;
+}
+
+void RenderPerformanceTestBase::setupVertexAttribs (void) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// buffers are bound
+
+	gl.enableVertexAttribArray(m_colorLoc);
+	gl.enableVertexAttribArray(m_positionLoc);
+
+	gl.vertexAttribPointer(m_colorLoc,    4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(8 * sizeof(float)), (const tcu::Vec4*)DE_NULL + 0);
+	gl.vertexAttribPointer(m_positionLoc, 4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(8 * sizeof(float)), (const tcu::Vec4*)DE_NULL + 1);
+
+	gl.useProgram(m_renderProgram->getProgram());
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set up rendering");
+}
+
+void RenderPerformanceTestBase::waitGLResults (void) const
+{
+	tcu::Surface dummySurface(RENDER_AREA_SIZE, RENDER_AREA_SIZE);
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dummySurface.getAccess());
+}
+
+template <typename SampleType>
+class RenderCase : public RenderPerformanceTestBase
+{
+public:
+									RenderCase						(Context& context, const char* name, const char* description, DrawMethod drawMethod);
+									~RenderCase						(void);
+
+protected:
+	void							init							(void);
+	void							deinit							(void);
+
+private:
+	IterateResult					iterate							(void);
+
+protected:
+	struct SampleResult
+	{
+		LayeredGridSpec					scene;
+		RenderSampleResult<SampleType>	result;
+	};
+
+	int								getMinWorkloadSize				(void) const;
+	int								getMaxWorkloadSize				(void) const;
+	int								getMinWorkloadDataSize			(void) const;
+	int								getMaxWorkloadDataSize			(void) const;
+	int								getVertexDataSize				(void) const;
+	int								getNumSamples					(void) const;
+	void							uploadScene						(const LayeredGridSpec& scene);
+
+	virtual void					runSample						(SampleResult& sample) = 0;
+	virtual void					logAndSetTestResult				(const std::vector<SampleResult>& results);
+
+	void							mapResultsToRenderRateFormat	(std::vector<RenderSampleResult<SampleType> >& dst, const std::vector<SampleResult>& src) const;
+
+	const DrawMethod				m_drawMethod;
+
+private:
+	glw::GLuint						m_attributeBufferID;
+	glw::GLuint						m_indexBufferID;
+	int								m_iterationNdx;
+	std::vector<int>				m_iterationOrder;
+	std::vector<SampleResult>		m_results;
+	int								m_numUnmapFailures;
+};
+
+template <typename SampleType>
+RenderCase<SampleType>::RenderCase (Context& context, const char* name, const char* description, DrawMethod drawMethod)
+	: RenderPerformanceTestBase	(context, name, description)
+	, m_drawMethod				(drawMethod)
+	, m_attributeBufferID		(0)
+	, m_indexBufferID			(0)
+	, m_iterationNdx			(0)
+	, m_numUnmapFailures		(0)
+{
+	DE_ASSERT(drawMethod < DRAWMETHOD_LAST);
+}
+
+template <typename SampleType>
+RenderCase<SampleType>::~RenderCase (void)
+{
+	deinit();
+}
+
+template <typename SampleType>
+void RenderCase<SampleType>::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	RenderPerformanceTestBase::init();
+
+	// requirements
+
+	if (m_context.getRenderTarget().getWidth() < RENDER_AREA_SIZE ||
+		m_context.getRenderTarget().getHeight() < RENDER_AREA_SIZE)
+		throw tcu::NotSupportedError("Test case requires " + de::toString<int>(RENDER_AREA_SIZE) + "x" + de::toString<int>(RENDER_AREA_SIZE) + " render target");
+
+	// gl state
+
+	gl.viewport(0, 0, RENDER_AREA_SIZE, RENDER_AREA_SIZE);
+
+	// enable bleding to prevent grid layers from being discarded
+	gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	gl.blendEquation(GL_FUNC_ADD);
+	gl.enable(GL_BLEND);
+
+	// generate iterations
+
+	{
+		const int gridSizes[] = { 20, 26, 32, 38, 44, 50, 56, 62, 68, 74, 80,  86,  92,  98,  104, 110, 116, 122, 128 };
+
+		for (int gridNdx = 0; gridNdx < DE_LENGTH_OF_ARRAY(gridSizes); ++gridNdx)
+		{
+			m_results.push_back(SampleResult());
+
+			m_results.back().scene.gridHeight = gridSizes[gridNdx];
+			m_results.back().scene.gridWidth = gridSizes[gridNdx];
+			m_results.back().scene.gridLayers = 5;
+
+			m_results.back().result.numVertices = getLayeredGridNumVertices(m_results.back().scene);
+
+			// test cases set these, initialize to dummy values
+			m_results.back().result.renderDataSize = -1;
+			m_results.back().result.uploadedDataSize = -1;
+			m_results.back().result.unrelatedDataSize = -1;
+		}
+	}
+
+	// randomize iteration order
+	{
+		m_iterationOrder.resize(m_results.size());
+		generateTwoPassRandomIterationOrder(m_iterationOrder, (int)m_iterationOrder.size());
+	}
+}
+
+template <typename SampleType>
+void RenderCase<SampleType>::deinit (void)
+{
+	RenderPerformanceTestBase::deinit();
+
+	if (m_attributeBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_attributeBufferID);
+		m_attributeBufferID = 0;
+	}
+
+	if (m_indexBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_indexBufferID);
+		m_indexBufferID = 0;
+	}
+}
+
+template <typename SampleType>
+typename RenderCase<SampleType>::IterateResult RenderCase<SampleType>::iterate (void)
+{
+	const int		unmapFailureThreshold	= 3;
+	const int		currentIteration		= m_iterationNdx;
+	const int		currentConfigNdx		= m_iterationOrder[currentIteration];
+	SampleResult&	currentSample			= m_results[currentConfigNdx];
+
+	try
+	{
+		runSample(currentSample);
+		++m_iterationNdx;
+	}
+	catch (const UnmapFailureError& ex)
+	{
+		DE_UNREF(ex);
+		++m_numUnmapFailures;
+	}
+
+	if (m_numUnmapFailures > unmapFailureThreshold)
+		throw tcu::TestError("Got too many unmap errors");
+
+	if (m_iterationNdx < (int)m_iterationOrder.size())
+		return CONTINUE;
+
+	logAndSetTestResult(m_results);
+	return STOP;
+}
+
+template <typename SampleType>
+int RenderCase<SampleType>::getMinWorkloadSize (void) const
+{
+	int result = getLayeredGridNumVertices(m_results[0].scene);
+
+	for (int ndx = 1; ndx < (int)m_results.size(); ++ndx)
+	{
+		const int workloadSize = getLayeredGridNumVertices(m_results[ndx].scene);
+		result = de::min(result, workloadSize);
+	}
+
+	return result;
+}
+
+template <typename SampleType>
+int RenderCase<SampleType>::getMaxWorkloadSize (void) const
+{
+	int result = getLayeredGridNumVertices(m_results[0].scene);
+
+	for (int ndx = 1; ndx < (int)m_results.size(); ++ndx)
+	{
+		const int workloadSize = getLayeredGridNumVertices(m_results[ndx].scene);
+		result = de::max(result, workloadSize);
+	}
+
+	return result;
+}
+
+template <typename SampleType>
+int RenderCase<SampleType>::getMinWorkloadDataSize (void) const
+{
+	return getMinWorkloadSize() * getVertexDataSize();
+}
+
+template <typename SampleType>
+int RenderCase<SampleType>::getMaxWorkloadDataSize (void) const
+{
+	return getMaxWorkloadSize() * getVertexDataSize();
+}
+
+template <typename SampleType>
+int RenderCase<SampleType>::getVertexDataSize (void) const
+{
+	const int numVectors	= 2;
+	const int vec4Size		= 4 * sizeof(float);
+
+	return numVectors * vec4Size;
+}
+
+template <typename SampleType>
+int RenderCase<SampleType>::getNumSamples (void) const
+{
+	return (int)m_results.size();
+}
+
+template <typename SampleType>
+void RenderCase<SampleType>::uploadScene (const LayeredGridSpec& scene)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// vertex buffer
+	{
+		std::vector<tcu::Vec4> vertexData;
+
+		generateLayeredGridVertexAttribData4C4V(vertexData, scene);
+
+		if (m_attributeBufferID == 0)
+			gl.genBuffers(1, &m_attributeBufferID);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_attributeBufferID);
+		gl.bufferData(GL_ARRAY_BUFFER, (int)(vertexData.size() * sizeof(tcu::Vec4)), &vertexData[0], GL_STATIC_DRAW);
+	}
+
+	// index buffer
+	if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+	{
+		std::vector<deUint32> indexData;
+
+		generateLayeredGridIndexData(indexData, scene);
+
+		if (m_indexBufferID == 0)
+			gl.genBuffers(1, &m_indexBufferID);
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferID);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (int)(indexData.size() * sizeof(deUint32)), &indexData[0], GL_STATIC_DRAW);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "create buffers");
+}
+
+template <typename SampleType>
+void RenderCase<SampleType>::logAndSetTestResult (const std::vector<SampleResult>& results)
+{
+	std::vector<RenderSampleResult<SampleType> > mappedResults;
+
+	mapResultsToRenderRateFormat(mappedResults, results);
+
+	{
+		const RenderSampleAnalyzeResult	analysis	= analyzeSampleResults(m_testCtx.getLog(), mappedResults);
+		const float						rate		= analysis.renderRateAtRange;
+
+		if (rate == std::numeric_limits<float>::infinity())
+		{
+			// sample times are 1) invalid or 2) timer resolution too low
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(0.0f, 2).c_str());
+		}
+		else
+		{
+			// report transfer rate in millions of MiB/s
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(rate / 1024.0f / 1024.0f, 2).c_str());
+		}
+	}
+}
+
+template <typename SampleType>
+void RenderCase<SampleType>::mapResultsToRenderRateFormat (std::vector<RenderSampleResult<SampleType> >& dst, const std::vector<SampleResult>& src) const
+{
+	dst.resize(src.size());
+
+	for (int ndx = 0; ndx < (int)src.size(); ++ndx)
+		dst[ndx] = src[ndx].result;
+}
+
+class ReferenceRenderTimeCase : public RenderCase<RenderReadDuration>
+{
+public:
+			ReferenceRenderTimeCase		(Context& context, const char* name, const char* description, DrawMethod drawMethod);
+
+private:
+	void	init						(void);
+	void	runSample					(SampleResult& sample);
+};
+
+ReferenceRenderTimeCase::ReferenceRenderTimeCase (Context& context, const char* name, const char* description, DrawMethod drawMethod)
+	: RenderCase<RenderReadDuration>	(context, name, description, drawMethod)
+{
+}
+
+void ReferenceRenderTimeCase::init (void)
+{
+	const char* const targetFunctionName = (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS) ? ("drawArrays") : ("drawElements");
+
+	// init parent
+	RenderCase<RenderReadDuration>::init();
+
+	// log
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Measuring the time used in " << targetFunctionName << " and readPixels call with different rendering workloads.\n"
+		<< getNumSamples() << " test samples. Sample order is randomized.\n"
+		<< "All samples at even positions (first = 0) are tested before samples at odd positions.\n"
+		<< "Generated workload is multiple viewport-covering grids with varying number of cells, each cell is two separate triangles.\n"
+		<< "Workload sizes are in the range ["
+			<< getMinWorkloadSize() << ",  "
+			<< getMaxWorkloadSize() << "] vertices (["
+			<< getHumanReadableByteSize(getMinWorkloadDataSize()) << ","
+			<< getHumanReadableByteSize(getMaxWorkloadDataSize()) << "] to be processed).\n"
+		<< "Test result is the approximated total processing rate in MiB / s.\n"
+		<< ((m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS) ? ("Note that index array size is not included in the processed size.\n") : (""))
+		<< "Note! Test result should only be used as a baseline reference result for buffer.data_upload.* test group results."
+		<< tcu::TestLog::EndMessage;
+}
+
+void ReferenceRenderTimeCase::runSample (SampleResult& sample)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	tcu::Surface			resultSurface	(RENDER_AREA_SIZE, RENDER_AREA_SIZE);
+	const int				numVertices		= getLayeredGridNumVertices(sample.scene);
+	const glu::Buffer		arrayBuffer		(m_context.getRenderContext());
+	const glu::Buffer		indexBuffer		(m_context.getRenderContext());
+	const glu::Buffer		unrelatedBuffer	(m_context.getRenderContext());
+	std::vector<tcu::Vec4>	vertexData;
+	std::vector<deUint32>	indexData;
+	deUint64				startTime;
+	deUint64				endTime;
+
+	// generate and upload buffers
+
+	generateLayeredGridVertexAttribData4C4V(vertexData, sample.scene);
+	gl.bindBuffer(GL_ARRAY_BUFFER, *arrayBuffer);
+	gl.bufferData(GL_ARRAY_BUFFER, (int)(vertexData.size() * sizeof(tcu::Vec4)), &vertexData[0], GL_STATIC_DRAW);
+
+	if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+	{
+		generateLayeredGridIndexData(indexData, sample.scene);
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, *indexBuffer);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (int)(indexData.size() * sizeof(deUint32)), &indexData[0], GL_STATIC_DRAW);
+	}
+
+	setupVertexAttribs();
+
+	// make sure data is uploaded
+
+	if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+		gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+	else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+		gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+	else
+		DE_ASSERT(false);
+	waitGLResults();
+
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	waitGLResults();
+
+	tcu::warmupCPU();
+
+	// Measure both draw and associated readpixels
+	{
+		startTime = deGetMicroseconds();
+
+		if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+			gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+		else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+			gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+		else
+			DE_ASSERT(false);
+
+		endTime = deGetMicroseconds();
+
+		sample.result.duration.renderDuration = endTime - startTime;
+	}
+
+	{
+		startTime = deGetMicroseconds();
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+		endTime = deGetMicroseconds();
+
+		sample.result.duration.readDuration = endTime - startTime;
+	}
+
+	sample.result.renderDataSize = getVertexDataSize() * sample.result.numVertices;
+	sample.result.uploadedDataSize = 0;
+	sample.result.unrelatedDataSize = 0;
+	sample.result.duration.renderReadDuration = sample.result.duration.renderDuration + sample.result.duration.readDuration;
+	sample.result.duration.totalDuration = sample.result.duration.renderDuration + sample.result.duration.readDuration;
+	sample.result.duration.fitResponseDuration = sample.result.duration.renderReadDuration;
+}
+
+class UnrelatedUploadRenderTimeCase : public RenderCase<UnrelatedUploadRenderReadDuration>
+{
+public:
+									UnrelatedUploadRenderTimeCase	(Context& context, const char* name, const char* description, DrawMethod drawMethod, UploadMethod unrelatedUploadMethod);
+
+private:
+	void							init							(void);
+	void							runSample						(SampleResult& sample);
+
+	const UploadMethod				m_unrelatedUploadMethod;
+};
+
+UnrelatedUploadRenderTimeCase::UnrelatedUploadRenderTimeCase (Context& context, const char* name, const char* description, DrawMethod drawMethod, UploadMethod unrelatedUploadMethod)
+	: RenderCase<UnrelatedUploadRenderReadDuration>	(context, name, description, drawMethod)
+	, m_unrelatedUploadMethod						(unrelatedUploadMethod)
+{
+	DE_ASSERT(m_unrelatedUploadMethod < UPLOADMETHOD_LAST);
+}
+
+void UnrelatedUploadRenderTimeCase::init (void)
+{
+	const char* const	targetFunctionName	= (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS) ? ("drawArrays") : ("drawElements");
+	tcu::MessageBuilder	message				(&m_testCtx.getLog());
+
+	// init parent
+	RenderCase<UnrelatedUploadRenderReadDuration>::init();
+
+	// log
+
+	message
+		<< "Measuring the time used in " << targetFunctionName << " and readPixels call with different rendering workloads.\n"
+		<< "Uploading an unrelated buffer just before issuing the rendering command with "
+			<< ((m_unrelatedUploadMethod != UPLOADMETHOD_BUFFER_DATA)		? ("bufferData")		:
+				(m_unrelatedUploadMethod != UPLOADMETHOD_BUFFER_SUB_DATA)	? ("bufferSubData")		:
+				(m_unrelatedUploadMethod != UPLOADMETHOD_MAP_BUFFER_RANGE)	? ("mapBufferRange")	:
+				((const char*)DE_NULL))
+			<< ".\n"
+		<< getNumSamples() << " test samples. Sample order is randomized.\n"
+		<< "All samples at even positions (first = 0) are tested before samples at odd positions.\n"
+		<< "Generated workload is multiple viewport-covering grids with varying number of cells, each cell is two separate triangles.\n"
+		<< "Workload sizes are in the range ["
+			<< getMinWorkloadSize() << ",  "
+			<< getMaxWorkloadSize() << "] vertices (["
+			<< getHumanReadableByteSize(getMinWorkloadDataSize()) << ","
+			<< getHumanReadableByteSize(getMaxWorkloadDataSize()) << "] to be processed).\n"
+		<< "Unrelated upload sizes are in the range ["
+			<< getHumanReadableByteSize(getMinWorkloadDataSize()) << ", "
+			<< getHumanReadableByteSize(getMaxWorkloadDataSize()) << "]\n"
+		<< "Test result is the approximated total processing rate in MiB / s.\n"
+		<< ((m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS) ? ("Note that index array size is not included in the processed size.\n") : (""))
+		<< "Note that the data size and the time used in the unrelated upload is not included in the results.\n"
+		<< "Note! Test result may not be useful as is but instead should be compared against the reference.* group and upload_and_draw.*_and_unrelated_upload group results.\n"
+		<< tcu::TestLog::EndMessage;
+}
+
+void UnrelatedUploadRenderTimeCase::runSample (SampleResult& sample)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	tcu::Surface			resultSurface		(RENDER_AREA_SIZE, RENDER_AREA_SIZE);
+	const int				numVertices			= getLayeredGridNumVertices(sample.scene);
+	const glu::Buffer		arrayBuffer			(m_context.getRenderContext());
+	const glu::Buffer		indexBuffer			(m_context.getRenderContext());
+	const glu::Buffer		unrelatedBuffer		(m_context.getRenderContext());
+	int						unrelatedUploadSize	= -1;
+	int						renderUploadSize;
+	std::vector<tcu::Vec4>	vertexData;
+	std::vector<deUint32>	indexData;
+	deUint64				startTime;
+	deUint64				endTime;
+
+	// generate and upload buffers
+
+	generateLayeredGridVertexAttribData4C4V(vertexData, sample.scene);
+	renderUploadSize = (int)(vertexData.size() * sizeof(tcu::Vec4));
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, *arrayBuffer);
+	gl.bufferData(GL_ARRAY_BUFFER, renderUploadSize, &vertexData[0], GL_STATIC_DRAW);
+
+	if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+	{
+		generateLayeredGridIndexData(indexData, sample.scene);
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, *indexBuffer);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (int)(indexData.size() * sizeof(deUint32)), &indexData[0], GL_STATIC_DRAW);
+	}
+
+	setupVertexAttribs();
+
+	// make sure data is uploaded
+
+	if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+		gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+	else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+		gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+	else
+		DE_ASSERT(false);
+	waitGLResults();
+
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	waitGLResults();
+
+	tcu::warmupCPU();
+
+	// Unrelated upload
+	if (m_unrelatedUploadMethod == UPLOADMETHOD_BUFFER_DATA)
+	{
+		unrelatedUploadSize = (int)(vertexData.size() * sizeof(tcu::Vec4));
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, *unrelatedBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, unrelatedUploadSize, &vertexData[0], GL_STATIC_DRAW);
+	}
+	else if (m_unrelatedUploadMethod == UPLOADMETHOD_BUFFER_SUB_DATA)
+	{
+		unrelatedUploadSize = (int)(vertexData.size() * sizeof(tcu::Vec4));
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, *unrelatedBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, unrelatedUploadSize, DE_NULL, GL_STATIC_DRAW);
+		gl.bufferSubData(GL_ARRAY_BUFFER, 0, unrelatedUploadSize, &vertexData[0]);
+	}
+	else if (m_unrelatedUploadMethod == UPLOADMETHOD_MAP_BUFFER_RANGE)
+	{
+		void*			mapPtr;
+		glw::GLboolean	unmapSuccessful;
+
+		unrelatedUploadSize = (int)(vertexData.size() * sizeof(tcu::Vec4));
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, *unrelatedBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, unrelatedUploadSize, DE_NULL, GL_STATIC_DRAW);
+
+		mapPtr = gl.mapBufferRange(GL_ARRAY_BUFFER, 0, unrelatedUploadSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
+		if (!mapPtr)
+			throw tcu::Exception("MapBufferRange returned NULL");
+
+		deMemcpy(mapPtr, &vertexData[0], unrelatedUploadSize);
+
+		// if unmapping fails, just try again later
+		unmapSuccessful = gl.unmapBuffer(GL_ARRAY_BUFFER);
+		if (!unmapSuccessful)
+			throw UnmapFailureError();
+	}
+	else
+		DE_ASSERT(false);
+
+	DE_ASSERT(unrelatedUploadSize != -1);
+
+	// Measure both draw and associated readpixels
+	{
+		startTime = deGetMicroseconds();
+
+		if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+			gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+		else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+			gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+		else
+			DE_ASSERT(false);
+
+		endTime = deGetMicroseconds();
+
+		sample.result.duration.renderDuration = endTime - startTime;
+	}
+
+	{
+		startTime = deGetMicroseconds();
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+		endTime = deGetMicroseconds();
+
+		sample.result.duration.readDuration = endTime - startTime;
+	}
+
+	sample.result.renderDataSize = getVertexDataSize() * sample.result.numVertices;
+	sample.result.uploadedDataSize = renderUploadSize;
+	sample.result.unrelatedDataSize = unrelatedUploadSize;
+	sample.result.duration.renderReadDuration = sample.result.duration.renderDuration + sample.result.duration.readDuration;
+	sample.result.duration.totalDuration = sample.result.duration.renderDuration + sample.result.duration.readDuration;
+	sample.result.duration.fitResponseDuration = sample.result.duration.renderReadDuration;
+}
+
+class ReferenceReadPixelsTimeCase : public TestCase
+{
+public:
+					ReferenceReadPixelsTimeCase		(Context& context, const char* name, const char* description);
+
+private:
+	void			init							(void);
+	IterateResult	iterate							(void);
+	void			logAndSetTestResult				(void);
+
+	enum
+	{
+		RENDER_AREA_SIZE = 128
+	};
+
+	const int			m_numSamples;
+	int					m_sampleNdx;
+	std::vector<int>	m_samples;
+};
+
+ReferenceReadPixelsTimeCase::ReferenceReadPixelsTimeCase (Context& context, const char* name, const char* description)
+	: TestCase		(context, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_numSamples	(20)
+	, m_sampleNdx	(0)
+	, m_samples		(m_numSamples)
+{
+}
+
+void ReferenceReadPixelsTimeCase::init (void)
+{
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Measuring the time used in a single readPixels call with " << m_numSamples << " test samples.\n"
+		<< "Test result is the median of the samples in microseconds.\n"
+		<< "Note! Test result should only be used as a baseline reference result for buffer.data_upload.* test group results."
+		<< tcu::TestLog::EndMessage;
+}
+
+ReferenceReadPixelsTimeCase::IterateResult ReferenceReadPixelsTimeCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	tcu::Surface			resultSurface	(RENDER_AREA_SIZE, RENDER_AREA_SIZE);
+	deUint64				startTime;
+	deUint64				endTime;
+
+	deYield();
+	tcu::warmupCPU();
+	deYield();
+
+	// "Render" something and wait for it
+	gl.clearColor(0.0f, 1.0f, m_sampleNdx / float(m_numSamples), 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	// wait for results
+	glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+
+	// measure time used in readPixels
+	startTime = deGetMicroseconds();
+	glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+	endTime = deGetMicroseconds();
+
+	m_samples[m_sampleNdx] = (int)(endTime - startTime);
+
+	if (++m_sampleNdx < m_numSamples)
+		return CONTINUE;
+
+	logAndSetTestResult();
+	return STOP;
+}
+
+void ReferenceReadPixelsTimeCase::logAndSetTestResult (void)
+{
+	// Log sample list
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::SampleList("Samples", "Samples")
+			<< tcu::TestLog::SampleInfo
+			<< tcu::TestLog::ValueInfo("ReadTime", "ReadPixels time", "us", QP_SAMPLE_VALUE_TAG_RESPONSE)
+			<< tcu::TestLog::EndSampleInfo;
+
+		for (int sampleNdx = 0; sampleNdx < (int)m_samples.size(); ++sampleNdx)
+			m_testCtx.getLog()
+				<< tcu::TestLog::Sample
+				<< m_samples[sampleNdx]
+				<< tcu::TestLog::EndSample;
+
+		m_testCtx.getLog() << tcu::TestLog::EndSampleList;
+	}
+
+	// Log median
+	{
+		float median;
+		float limit60Low;
+		float limit60Up;
+
+		std::sort(m_samples.begin(), m_samples.end());
+		median		= linearSample(m_samples, 0.5f);
+		limit60Low	= linearSample(m_samples, 0.2f);
+		limit60Up	= linearSample(m_samples, 0.8f);
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Float("Median", "Median", "us", QP_KEY_TAG_TIME, median)
+			<< tcu::TestLog::Message
+			<< "60 % of samples within range:\n"
+			<< tcu::TestLog::EndMessage
+			<< tcu::TestLog::Float("Low60Range", "Lower", "us", QP_KEY_TAG_TIME, limit60Low)
+			<< tcu::TestLog::Float("High60Range", "Upper", "us", QP_KEY_TAG_TIME, limit60Up);
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(median, 2).c_str());
+	}
+}
+
+template <typename SampleType>
+class GenericUploadRenderTimeCase : public RenderCase<SampleType>
+{
+public:
+	typedef typename RenderCase<SampleType>::SampleResult SampleResult;
+
+							GenericUploadRenderTimeCase	(Context&				context,
+														 const char*			name,
+														 const char*			description,
+														 DrawMethod				method,
+														 TargetBuffer			targetBuffer,
+														 UploadMethod			uploadMethod,
+														 BufferState			bufferState,
+														 UploadRange			uploadRange,
+														 UnrelatedBufferType	unrelatedBufferType);
+
+private:
+	void						init					(void);
+	void						runSample				(SampleResult& sample);
+
+	using RenderCase<SampleType>::RENDER_AREA_SIZE;
+
+	const TargetBuffer			m_targetBuffer;
+	const BufferState			m_bufferState;
+	const UploadMethod			m_uploadMethod;
+	const UnrelatedBufferType	m_unrelatedBufferType;
+	const UploadRange			m_uploadRange;
+
+	using RenderCase<SampleType>::m_context;
+	using RenderCase<SampleType>::m_testCtx;
+	using RenderCase<SampleType>::m_drawMethod;
+};
+
+template <typename SampleType>
+GenericUploadRenderTimeCase<SampleType>::GenericUploadRenderTimeCase (Context&				context,
+																	  const char*			name,
+																	  const char*			description,
+																	  DrawMethod			method,
+																	  TargetBuffer			targetBuffer,
+																	  UploadMethod			uploadMethod,
+																	  BufferState			bufferState,
+																	  UploadRange			uploadRange,
+																	  UnrelatedBufferType	unrelatedBufferType)
+	: RenderCase<SampleType>	(context, name, description, method)
+	, m_targetBuffer			(targetBuffer)
+	, m_bufferState				(bufferState)
+	, m_uploadMethod			(uploadMethod)
+	, m_unrelatedBufferType		(unrelatedBufferType)
+	, m_uploadRange				(uploadRange)
+{
+	DE_ASSERT(m_targetBuffer < TARGETBUFFER_LAST);
+	DE_ASSERT(m_bufferState < BUFFERSTATE_LAST);
+	DE_ASSERT(m_uploadMethod < UPLOADMETHOD_LAST);
+	DE_ASSERT(m_unrelatedBufferType < UNRELATEDBUFFERTYPE_LAST);
+	DE_ASSERT(m_uploadRange < UPLOADRANGE_LAST);
+}
+
+template <typename SampleType>
+void GenericUploadRenderTimeCase<SampleType>::init (void)
+{
+	// init parent
+	RenderCase<SampleType>::init();
+
+	// log
+	{
+		const char* const	targetFunctionName		= (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS) ? ("drawArrays") : ("drawElements");
+		const int			perVertexSize			= (m_targetBuffer == TARGETBUFFER_INDEX) ? (sizeof(deUint32)) : (sizeof(tcu::Vec4[2]));
+		const int			fullMinUploadSize		= RenderCase<SampleType>::getMinWorkloadSize() * perVertexSize;
+		const int			fullMaxUploadSize		= RenderCase<SampleType>::getMaxWorkloadSize() * perVertexSize;
+		const int			minUploadSize			= (m_uploadRange == UPLOADRANGE_FULL) ? (fullMinUploadSize) : (deAlign32(fullMinUploadSize/2, 4));
+		const int			maxUploadSize			= (m_uploadRange == UPLOADRANGE_FULL) ? (fullMaxUploadSize) : (deAlign32(fullMaxUploadSize/2, 4));
+		const int			minUnrelatedUploadSize	= RenderCase<SampleType>::getMinWorkloadSize() * sizeof(tcu::Vec4[2]);
+		const int			maxUnrelatedUploadSize	= RenderCase<SampleType>::getMaxWorkloadSize() * sizeof(tcu::Vec4[2]);
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Measuring the time used in " << targetFunctionName << " and readPixels call with different rendering workloads.\n"
+			<< "The "
+				<< ((m_targetBuffer == TARGETBUFFER_INDEX) ? ("index") : ("vertex attrib"))
+				<< " buffer "
+				<< ((m_bufferState == BUFFERSTATE_NEW) ? ("") : ("contents "))
+				<< "sourced by the rendering command "
+				<< ((m_bufferState == BUFFERSTATE_NEW)		? ("is uploaded ") :
+					(m_uploadRange == UPLOADRANGE_FULL)		? ("are specified ") :
+					(m_uploadRange == UPLOADRANGE_PARTIAL)	? ("are updated (partial upload) ") :
+					((const char*)DE_NULL))
+				<< "just before issuing the rendering command.\n"
+			<< ((m_bufferState == BUFFERSTATE_EXISTING) ? ("The buffer has been used in rendering.\n") : ("The buffer is generated just before uploading.\n"))
+			<< "Buffer "
+				<< ((m_bufferState == BUFFERSTATE_NEW)		? ("is uploaded") :
+					(m_uploadRange == UPLOADRANGE_FULL)		? ("contents are specified") :
+					(m_uploadRange == UPLOADRANGE_PARTIAL)	? ("contents are partially updated") :
+					((const char*)DE_NULL))
+				<< " with "
+				<< ((m_uploadMethod == UPLOADMETHOD_BUFFER_DATA) ? ("bufferData") : (m_uploadMethod == UPLOADMETHOD_BUFFER_SUB_DATA) ? ("bufferSubData") : ("mapBufferRange"))
+				<< " command. Usage of the target buffer is DYNAMIC_DRAW.\n"
+			<< ((m_uploadMethod == UPLOADMETHOD_MAP_BUFFER_RANGE) ? ("Mapping buffer with bits MAP_WRITE_BIT | MAP_INVALIDATE_RANGE_BIT | MAP_INVALIDATE_BUFFER_BIT | MAP_UNSYNCHRONIZED_BIT\n") : (""))
+			<< ((m_unrelatedBufferType == UNRELATEDBUFFERTYPE_VERTEX) ? ("Uploading an unrelated buffer just before issuing the rendering command with bufferData.\n") : (""))
+			<< RenderCase<SampleType>::getNumSamples() << " test samples. Sample order is randomized.\n"
+			<< "All samples at even positions (first = 0) are tested before samples at odd positions.\n"
+			<< "Generated workload is multiple viewport-covering grids with varying number of cells, each cell is two separate triangles.\n"
+			<< "Workload sizes are in the range ["
+				<< RenderCase<SampleType>::getMinWorkloadSize() << ",  "
+				<< RenderCase<SampleType>::getMaxWorkloadSize() << "] vertices "
+				<< "(["
+				<< getHumanReadableByteSize(RenderCase<SampleType>::getMinWorkloadDataSize()) << ","
+				<< getHumanReadableByteSize(RenderCase<SampleType>::getMaxWorkloadDataSize()) << "] to be processed).\n"
+			<< "Upload sizes are in the range ["
+				<< getHumanReadableByteSize(minUploadSize) << ","
+				<< getHumanReadableByteSize(maxUploadSize) << "].\n"
+			<< ((m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS) ?
+				("Unrelated upload sizes are in the range [" + getHumanReadableByteSize(minUnrelatedUploadSize) + ", " + getHumanReadableByteSize(maxUnrelatedUploadSize) + "]\n") :
+				(""))
+			<< "Test result is the approximated processing rate in MiB / s.\n"
+			<< "Note that while upload time is measured, the time used is not included in the results.\n"
+			<< ((m_unrelatedBufferType == UNRELATEDBUFFERTYPE_VERTEX) ? ("Note that the data size and the time used in the unrelated upload is not included in the results.\n") : (""))
+			<< ((m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS) ? ("Note that index array size is not included in the processed size.\n") : (""))
+			<< "Note! Test result may not be useful as is but instead should be compared against the reference.* group and other upload_and_draw.* group results.\n"
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+template <typename SampleType>
+void GenericUploadRenderTimeCase<SampleType>::runSample (SampleResult& sample)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const glu::Buffer		arrayBuffer			(m_context.getRenderContext());
+	const glu::Buffer		indexBuffer			(m_context.getRenderContext());
+	const glu::Buffer		unrelatedBuffer		(m_context.getRenderContext());
+	const int				numVertices			= getLayeredGridNumVertices(sample.scene);
+	tcu::Surface			resultSurface		(RENDER_AREA_SIZE, RENDER_AREA_SIZE);
+	deUint64				startTime;
+	deUint64				endTime;
+	std::vector<tcu::Vec4>	vertexData;
+	std::vector<deUint32>	indexData;
+
+	// create data
+
+	generateLayeredGridVertexAttribData4C4V(vertexData, sample.scene);
+	if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+		generateLayeredGridIndexData(indexData, sample.scene);
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, *arrayBuffer);
+	gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, *indexBuffer);
+	RenderCase<SampleType>::setupVertexAttribs();
+
+	// target should be an exisiting buffer? Draw from it once to make sure it exists on the gpu
+
+	if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS && m_bufferState == BUFFERSTATE_EXISTING)
+	{
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(vertexData.size() * sizeof(tcu::Vec4)), &vertexData[0], GL_DYNAMIC_DRAW);
+		gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+	}
+	else if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS && m_bufferState == BUFFERSTATE_NEW)
+	{
+		// do not touch the vertex buffer
+	}
+	else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS && m_bufferState == BUFFERSTATE_EXISTING)
+	{
+		// hint that the target buffer will be modified soon
+		const glw::GLenum vertexDataUsage	= (m_targetBuffer == TARGETBUFFER_VERTEX) ? (GL_DYNAMIC_DRAW) : (GL_STATIC_DRAW);
+		const glw::GLenum indexDataUsage	= (m_targetBuffer == TARGETBUFFER_INDEX) ? (GL_DYNAMIC_DRAW) : (GL_STATIC_DRAW);
+
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(vertexData.size() * sizeof(tcu::Vec4)), &vertexData[0], vertexDataUsage);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indexData.size() * sizeof(deUint32)), &indexData[0], indexDataUsage);
+		gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+	}
+	else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS && m_bufferState == BUFFERSTATE_NEW)
+	{
+		if (m_targetBuffer == TARGETBUFFER_VERTEX)
+		{
+			// make the index buffer present on the gpu
+			// use another vertex buffer to keep original buffer in unused state
+			const glu::Buffer vertexCopyBuffer(m_context.getRenderContext());
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, *vertexCopyBuffer);
+			RenderCase<SampleType>::setupVertexAttribs();
+
+			gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(vertexData.size() * sizeof(tcu::Vec4)), &vertexData[0], GL_STATIC_DRAW);
+			gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indexData.size() * sizeof(deUint32)), &indexData[0], GL_STATIC_DRAW);
+			gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+
+			// restore original state
+			gl.bindBuffer(GL_ARRAY_BUFFER, *arrayBuffer);
+			RenderCase<SampleType>::setupVertexAttribs();
+		}
+		else if (m_targetBuffer == TARGETBUFFER_INDEX)
+		{
+			// make the vertex buffer present on the gpu
+			gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(vertexData.size() * sizeof(tcu::Vec4)), &vertexData[0], GL_STATIC_DRAW);
+			gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+		}
+		else
+			DE_ASSERT(false);
+	}
+	else
+		DE_ASSERT(false);
+
+	RenderCase<SampleType>::waitGLResults();
+	GLU_EXPECT_NO_ERROR(gl.getError(), "post buffer prepare");
+
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	RenderCase<SampleType>::waitGLResults();
+
+	tcu::warmupCPU();
+
+	// upload
+
+	{
+		glw::GLenum		target;
+		glw::GLsizeiptr	size;
+		glw::GLintptr	offset = 0;
+		const void*		source;
+
+		if (m_targetBuffer == TARGETBUFFER_VERTEX && m_uploadRange == UPLOADRANGE_FULL)
+		{
+			target	= GL_ARRAY_BUFFER;
+			size	= (glw::GLsizeiptr)(vertexData.size() * sizeof(tcu::Vec4));
+			source	= &vertexData[0];
+		}
+		else if (m_targetBuffer == TARGETBUFFER_INDEX && m_uploadRange == UPLOADRANGE_FULL)
+		{
+			target	= GL_ELEMENT_ARRAY_BUFFER;
+			size	= (glw::GLsizeiptr)(indexData.size() * sizeof(deUint32));
+			source	= &indexData[0];
+		}
+		else if (m_targetBuffer == TARGETBUFFER_VERTEX && m_uploadRange == UPLOADRANGE_PARTIAL)
+		{
+			DE_ASSERT(m_bufferState == BUFFERSTATE_EXISTING);
+
+			target	= GL_ARRAY_BUFFER;
+			size	= (glw::GLsizeiptr)deAlign32((int)(vertexData.size() * sizeof(tcu::Vec4)) / 2, 4);
+			offset	= (glw::GLintptr)deAlign32((int)size / 2, 4);
+			source	= (const deUint8*)&vertexData[0] + offset;
+		}
+		else if (m_targetBuffer == TARGETBUFFER_INDEX && m_uploadRange == UPLOADRANGE_PARTIAL)
+		{
+			DE_ASSERT(m_bufferState == BUFFERSTATE_EXISTING);
+
+			// upload to 25% - 75% range
+			target	= GL_ELEMENT_ARRAY_BUFFER;
+			size	= (glw::GLsizeiptr)deAlign32((glw::GLsizeiptr)((int)(indexData.size() * sizeof(deUint32))) / 2, 4);
+			offset	= (glw::GLintptr)deAlign32((int)size / 2, 4);
+			source	= (const deUint8*)&indexData[0] + offset;
+		}
+		else
+		{
+			DE_ASSERT(false);
+			return;
+		}
+
+		startTime = deGetMicroseconds();
+
+		if (m_uploadMethod == UPLOADMETHOD_BUFFER_DATA)
+			gl.bufferData(target, size, source, GL_DYNAMIC_DRAW);
+		else if (m_uploadMethod == UPLOADMETHOD_BUFFER_SUB_DATA)
+		{
+			// create buffer storage
+			if (m_bufferState == BUFFERSTATE_NEW)
+				gl.bufferData(target, size, DE_NULL, GL_DYNAMIC_DRAW);
+			gl.bufferSubData(target, offset, size, source);
+		}
+		else if (m_uploadMethod == UPLOADMETHOD_MAP_BUFFER_RANGE)
+		{
+			void*			mapPtr;
+			glw::GLboolean	unmapSuccessful;
+
+			// create buffer storage
+			if (m_bufferState == BUFFERSTATE_NEW)
+				gl.bufferData(target, size, DE_NULL, GL_DYNAMIC_DRAW);
+
+			mapPtr = gl.mapBufferRange(target, offset, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
+			if (!mapPtr)
+				throw tcu::Exception("MapBufferRange returned NULL");
+
+			deMemcpy(mapPtr, source, (int)size);
+
+			// if unmapping fails, just try again later
+			unmapSuccessful = gl.unmapBuffer(target);
+			if (!unmapSuccessful)
+				throw UnmapFailureError();
+		}
+		else
+			DE_ASSERT(false);
+
+		endTime = deGetMicroseconds();
+
+		sample.result.uploadedDataSize = (int)size;
+		sample.result.duration.uploadDuration = endTime - startTime;
+	}
+
+	// unrelated
+	if (m_unrelatedBufferType == UNRELATEDBUFFERTYPE_VERTEX)
+	{
+		const int unrelatedUploadSize = (int)(vertexData.size() * sizeof(tcu::Vec4));
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, *unrelatedBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, unrelatedUploadSize, &vertexData[0], GL_STATIC_DRAW);
+		// Attibute pointers are not modified, no need restore state
+
+		sample.result.unrelatedDataSize = unrelatedUploadSize;
+	}
+
+	// draw
+	{
+		startTime = deGetMicroseconds();
+
+		if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+			gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+		else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+			gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+		else
+			DE_ASSERT(false);
+
+		endTime = deGetMicroseconds();
+
+		sample.result.duration.renderDuration = endTime - startTime;
+	}
+
+	// read
+	{
+		startTime = deGetMicroseconds();
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+		endTime = deGetMicroseconds();
+
+		sample.result.duration.readDuration = endTime - startTime;
+	}
+
+	// set results
+
+	sample.result.renderDataSize = RenderCase<SampleType>::getVertexDataSize() * sample.result.numVertices;
+
+	sample.result.duration.renderReadDuration = sample.result.duration.renderDuration + sample.result.duration.readDuration;
+	sample.result.duration.totalDuration = sample.result.duration.uploadDuration + sample.result.duration.renderDuration + sample.result.duration.readDuration;
+	sample.result.duration.fitResponseDuration = sample.result.duration.renderReadDuration;
+}
+
+class BufferInUseRenderTimeCase : public RenderCase<RenderUploadRenderReadDuration>
+{
+public:
+	enum MapFlags
+	{
+		MAPFLAG_NONE = 0,
+		MAPFLAG_INVALIDATE_BUFFER,
+		MAPFLAG_INVALIDATE_RANGE,
+
+		MAPFLAG_LAST
+	};
+	enum UploadBufferTarget
+	{
+		UPLOADBUFFERTARGET_DIFFERENT_BUFFER = 0,
+		UPLOADBUFFERTARGET_SAME_BUFFER,
+
+		UPLOADBUFFERTARGET_LAST
+	};
+								BufferInUseRenderTimeCase	(Context&			context,
+															 const char*		name,
+															 const char*		description,
+															 DrawMethod			method,
+															 MapFlags			mapFlags,
+															 TargetBuffer		targetBuffer,
+															 UploadMethod		uploadMethod,
+															 UploadRange		uploadRange,
+															 UploadBufferTarget	uploadTarget);
+
+private:
+	void						init						(void);
+	void						runSample					(SampleResult& sample);
+
+	const TargetBuffer			m_targetBuffer;
+	const UploadMethod			m_uploadMethod;
+	const UploadRange			m_uploadRange;
+	const MapFlags				m_mapFlags;
+	const UploadBufferTarget	m_uploadBufferTarget;
+};
+
+BufferInUseRenderTimeCase::BufferInUseRenderTimeCase (Context&				context,
+													  const char*			name,
+													  const char*			description,
+													  DrawMethod			method,
+													  MapFlags				mapFlags,
+													  TargetBuffer			targetBuffer,
+													  UploadMethod			uploadMethod,
+													  UploadRange			uploadRange,
+													  UploadBufferTarget	uploadTarget)
+	: RenderCase<RenderUploadRenderReadDuration>	(context, name, description, method)
+	, m_targetBuffer								(targetBuffer)
+	, m_uploadMethod								(uploadMethod)
+	, m_uploadRange									(uploadRange)
+	, m_mapFlags									(mapFlags)
+	, m_uploadBufferTarget							(uploadTarget)
+{
+	DE_ASSERT(m_targetBuffer < TARGETBUFFER_LAST);
+	DE_ASSERT(m_uploadMethod < UPLOADMETHOD_LAST);
+	DE_ASSERT(m_uploadRange < UPLOADRANGE_LAST);
+	DE_ASSERT(m_mapFlags < MAPFLAG_LAST);
+	DE_ASSERT(m_uploadBufferTarget < UPLOADBUFFERTARGET_LAST);
+}
+
+void BufferInUseRenderTimeCase::init (void)
+{
+	RenderCase<RenderUploadRenderReadDuration>::init();
+
+	// log
+	{
+		const char* const	targetFunctionName		= (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS) ? ("drawArrays") : ("drawElements");
+		const char* const	uploadFunctionName		= (m_uploadMethod == UPLOADMETHOD_BUFFER_DATA) ? ("bufferData") : (m_uploadMethod == UPLOADMETHOD_BUFFER_SUB_DATA) ? ("bufferSubData") : ("mapBufferRange");
+		const bool			isReferenceCase			= (m_uploadBufferTarget == UPLOADBUFFERTARGET_DIFFERENT_BUFFER);
+		tcu::MessageBuilder	message					(&m_testCtx.getLog());
+
+		message	<< "Measuring the time used in " << targetFunctionName << " call, a buffer upload, "
+				<< targetFunctionName << " call using the uploaded buffer and readPixels call with different upload sizes.\n";
+
+		if (isReferenceCase)
+			message << "Rendering:\n"
+					<< "    before test: create and use buffers B and C\n"
+					<< "    first draw: render using buffer B\n"
+					<< ((m_uploadRange == UPLOADRANGE_FULL)		? ("    upload: respecify buffer C contents\n")	:
+						(m_uploadRange == UPLOADRANGE_PARTIAL)	? ("    upload: modify buffer C contents\n")	:
+						((const char*)DE_NULL))
+					<< "    second draw: render using buffer C\n"
+					<< "    read: readPixels\n";
+		else
+			message << "Rendering:\n"
+					<< "    before test: create and use buffer B\n"
+					<< "    first draw: render using buffer B\n"
+					<< ((m_uploadRange == UPLOADRANGE_FULL)		? ("    upload: respecify buffer B contents\n")	:
+						(m_uploadRange == UPLOADRANGE_PARTIAL)	? ("    upload: modify buffer B contents\n")	:
+						((const char*)DE_NULL))
+					<< "    second draw: render using buffer B\n"
+					<< "    read: readPixels\n";
+
+		message	<< "Uploading using " << uploadFunctionName
+					<< ((m_mapFlags == MAPFLAG_INVALIDATE_RANGE)	? (", flags = MAP_WRITE_BIT | MAP_INVALIDATE_RANGE_BIT")	:
+						(m_mapFlags == MAPFLAG_INVALIDATE_BUFFER)	? (", flags = MAP_WRITE_BIT | MAP_INVALIDATE_BUFFER_BIT")	:
+						(m_mapFlags == MAPFLAG_NONE)				? ("")														:
+						((const char*)DE_NULL))
+					<< "\n"
+				<< getNumSamples() << " test samples. Sample order is randomized.\n"
+				<< "All samples at even positions (first = 0) are tested before samples at odd positions.\n"
+				<< "Workload sizes are in the range ["
+					<< getMinWorkloadSize() << ",  "
+					<< getMaxWorkloadSize() << "] vertices "
+					<< "(["
+					<< getHumanReadableByteSize(getMinWorkloadDataSize()) << ","
+					<< getHumanReadableByteSize(getMaxWorkloadDataSize()) << "] to be processed).\n"
+				<< "Test result is the approximated processing rate in MiB / s of the second draw call and the readPixels call.\n";
+
+		if (isReferenceCase)
+			message	<< "Note! Test result should only be used as a baseline reference result for buffer.render_after_upload.draw_modify_draw test group results.";
+		else
+			message	<< "Note! Test result may not be useful as is but instead should be compared against the buffer.render_after_upload.reference.draw_upload_draw group results.\n";
+
+		message << tcu::TestLog::EndMessage;
+	}
+}
+
+void BufferInUseRenderTimeCase::runSample (SampleResult& sample)
+{
+	const glw::Functions&	gl						= m_context.getRenderContext().getFunctions();
+	const glu::Buffer		arrayBuffer				(m_context.getRenderContext());
+	const glu::Buffer		indexBuffer				(m_context.getRenderContext());
+	const glu::Buffer		alternativeUploadBuffer	(m_context.getRenderContext());
+	const int				numVertices				= getLayeredGridNumVertices(sample.scene);
+	tcu::Surface			resultSurface			(RENDER_AREA_SIZE, RENDER_AREA_SIZE);
+	deUint64				startTime;
+	deUint64				endTime;
+	std::vector<tcu::Vec4>	vertexData;
+	std::vector<deUint32>	indexData;
+
+	// create data
+
+	generateLayeredGridVertexAttribData4C4V(vertexData, sample.scene);
+	if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+		generateLayeredGridIndexData(indexData, sample.scene);
+
+	// make buffers used
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, *arrayBuffer);
+	gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, *indexBuffer);
+	setupVertexAttribs();
+
+	if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+	{
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(vertexData.size() * sizeof(tcu::Vec4)), &vertexData[0], GL_STREAM_DRAW);
+		gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+	}
+	else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+	{
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(vertexData.size() * sizeof(tcu::Vec4)), &vertexData[0], GL_STREAM_DRAW);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indexData.size() * sizeof(deUint32)), &indexData[0], GL_STREAM_DRAW);
+		gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+	}
+	else
+		DE_ASSERT(false);
+
+	// another pair of buffers for reference case
+	if (m_uploadBufferTarget == UPLOADBUFFERTARGET_DIFFERENT_BUFFER)
+	{
+		if (m_targetBuffer == TARGETBUFFER_VERTEX)
+		{
+			gl.bindBuffer(GL_ARRAY_BUFFER, *alternativeUploadBuffer);
+			gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(vertexData.size() * sizeof(tcu::Vec4)), &vertexData[0], GL_STREAM_DRAW);
+
+			setupVertexAttribs();
+			gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+		}
+		else if (m_targetBuffer == TARGETBUFFER_INDEX)
+		{
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, *alternativeUploadBuffer);
+			gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indexData.size() * sizeof(deUint32)), &indexData[0], GL_STREAM_DRAW);
+			gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+		}
+		else
+			DE_ASSERT(false);
+
+		// restore state
+		gl.bindBuffer(GL_ARRAY_BUFFER, *arrayBuffer);
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, *indexBuffer);
+		setupVertexAttribs();
+	}
+
+	waitGLResults();
+	GLU_EXPECT_NO_ERROR(gl.getError(), "post buffer prepare");
+
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	waitGLResults();
+
+	tcu::warmupCPU();
+
+	// first draw
+	{
+		startTime = deGetMicroseconds();
+
+		if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+			gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+		else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+			gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+		else
+			DE_ASSERT(false);
+
+		endTime = deGetMicroseconds();
+
+		sample.result.duration.firstRenderDuration = endTime - startTime;
+	}
+
+	// upload
+	{
+		glw::GLenum		target;
+		glw::GLsizeiptr	size;
+		glw::GLintptr	offset = 0;
+		const void*		source;
+
+		if (m_targetBuffer == TARGETBUFFER_VERTEX && m_uploadRange == UPLOADRANGE_FULL)
+		{
+			target	= GL_ARRAY_BUFFER;
+			size	= (glw::GLsizeiptr)(vertexData.size() * sizeof(tcu::Vec4));
+			source	= &vertexData[0];
+		}
+		else if (m_targetBuffer == TARGETBUFFER_INDEX && m_uploadRange == UPLOADRANGE_FULL)
+		{
+			target	= GL_ELEMENT_ARRAY_BUFFER;
+			size	= (glw::GLsizeiptr)(indexData.size() * sizeof(deUint32));
+			source	= &indexData[0];
+		}
+		else if (m_targetBuffer == TARGETBUFFER_VERTEX && m_uploadRange == UPLOADRANGE_PARTIAL)
+		{
+			target	= GL_ARRAY_BUFFER;
+			size	= (glw::GLsizeiptr)deAlign32((int)(vertexData.size() * sizeof(tcu::Vec4)) / 2, 4);
+			offset	= (glw::GLintptr)deAlign32((int)size / 2, 4);
+			source	= (const deUint8*)&vertexData[0] + offset;
+		}
+		else if (m_targetBuffer == TARGETBUFFER_INDEX && m_uploadRange == UPLOADRANGE_PARTIAL)
+		{
+			// upload to 25% - 75% range
+			target	= GL_ELEMENT_ARRAY_BUFFER;
+			size	= (glw::GLsizeiptr)deAlign32((glw::GLsizeiptr)((int)(indexData.size() * sizeof(deUint32))) / 2, 4);
+			offset	= (glw::GLintptr)deAlign32((int)size / 2, 4);
+			source	= (const deUint8*)&indexData[0] + offset;
+		}
+		else
+		{
+			DE_ASSERT(false);
+			return;
+		}
+
+		// reference case? don't modify the buffer in use
+		if (m_uploadBufferTarget == UPLOADBUFFERTARGET_DIFFERENT_BUFFER)
+			gl.bindBuffer(target, *alternativeUploadBuffer);
+
+		startTime = deGetMicroseconds();
+
+		if (m_uploadMethod == UPLOADMETHOD_BUFFER_DATA)
+			gl.bufferData(target, size, source, GL_STREAM_DRAW);
+		else if (m_uploadMethod == UPLOADMETHOD_BUFFER_SUB_DATA)
+			gl.bufferSubData(target, offset, size, source);
+		else if (m_uploadMethod == UPLOADMETHOD_MAP_BUFFER_RANGE)
+		{
+			const int		mapFlags	= (m_mapFlags == MAPFLAG_INVALIDATE_BUFFER)	? (GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)	:
+										  (m_mapFlags == MAPFLAG_INVALIDATE_RANGE)	? (GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT)	:
+										  (-1);
+			void*			mapPtr;
+			glw::GLboolean	unmapSuccessful;
+
+			mapPtr = gl.mapBufferRange(target, offset, size, mapFlags);
+			if (!mapPtr)
+				throw tcu::Exception("MapBufferRange returned NULL");
+
+			deMemcpy(mapPtr, source, (int)size);
+
+			// if unmapping fails, just try again later
+			unmapSuccessful = gl.unmapBuffer(target);
+			if (!unmapSuccessful)
+				throw UnmapFailureError();
+		}
+		else
+			DE_ASSERT(false);
+
+		endTime = deGetMicroseconds();
+
+		sample.result.uploadedDataSize = (int)size;
+		sample.result.duration.uploadDuration = endTime - startTime;
+	}
+
+	// second draw
+	{
+		// Source vertex data from alternative buffer in refernce case
+		if (m_uploadBufferTarget == UPLOADBUFFERTARGET_DIFFERENT_BUFFER && m_targetBuffer == TARGETBUFFER_VERTEX)
+			setupVertexAttribs();
+
+		startTime = deGetMicroseconds();
+
+		if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+			gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+		else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+			gl.drawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_INT, DE_NULL);
+		else
+			DE_ASSERT(false);
+
+		endTime = deGetMicroseconds();
+
+		sample.result.duration.secondRenderDuration = endTime - startTime;
+	}
+
+	// read
+	{
+		startTime = deGetMicroseconds();
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+		endTime = deGetMicroseconds();
+
+		sample.result.duration.readDuration = endTime - startTime;
+	}
+
+	// set results
+
+	sample.result.renderDataSize = getVertexDataSize() * sample.result.numVertices;
+
+	sample.result.duration.renderReadDuration	= sample.result.duration.secondRenderDuration + sample.result.duration.readDuration;
+	sample.result.duration.totalDuration		= sample.result.duration.firstRenderDuration +
+												  sample.result.duration.uploadDuration +
+												  sample.result.duration.secondRenderDuration +
+												  sample.result.duration.readDuration;
+	sample.result.duration.fitResponseDuration	= sample.result.duration.renderReadDuration;
+}
+
+class UploadWaitDrawCase : public RenderPerformanceTestBase
+{
+public:
+	struct Sample
+	{
+		int			numFrames;
+		deUint64	uploadCallEndTime;
+	};
+	struct Result
+	{
+		deUint64	uploadDuration;
+		deUint64	renderDuration;
+		deUint64	readDuration;
+		deUint64	renderReadDuration;
+
+		deUint64	timeBeforeUse;
+	};
+
+							UploadWaitDrawCase				(Context&		context,
+															 const char*	name,
+															 const char*	description,
+															 DrawMethod		drawMethod,
+															 TargetBuffer	targetBuffer,
+															 UploadMethod	uploadMethod,
+															 BufferState	bufferState);
+							~UploadWaitDrawCase				(void);
+
+private:
+	void					init							(void);
+	void					deinit							(void);
+	IterateResult			iterate							(void);
+
+	void					uploadBuffer					(Sample& sample, Result& result);
+	void					drawFromBuffer					(Sample& sample, Result& result);
+	void					reuseAndDeleteBuffer			(void);
+	void					logAndSetTestResult				(void);
+	void					logSamples						(void);
+	void					drawMisc						(void);
+	int						findStabilizationSample			(deUint64 (Result::*target), const char* description);
+	bool					checkSampleTemporalStability	(deUint64 (Result::*target), const char* description);
+
+	const DrawMethod		m_drawMethod;
+	const TargetBuffer		m_targetBuffer;
+	const UploadMethod		m_uploadMethod;
+	const BufferState		m_bufferState;
+
+	const int				m_numSamplesPerSwap;
+	const int				m_numMaxSwaps;
+
+	int						m_frameNdx;
+	int						m_sampleNdx;
+	int						m_numVertices;
+
+	std::vector<tcu::Vec4>	m_vertexData;
+	std::vector<deUint32>	m_indexData;
+	std::vector<Sample>		m_samples;
+	std::vector<Result>		m_results;
+	std::vector<int>		m_iterationOrder;
+
+	deUint32				m_vertexBuffer;
+	deUint32				m_indexBuffer;
+	deUint32				m_miscBuffer;
+	int						m_numMiscVertices;
+};
+
+UploadWaitDrawCase::UploadWaitDrawCase (Context&		context,
+										const char*		name,
+										const char*		description,
+										DrawMethod		drawMethod,
+										TargetBuffer	targetBuffer,
+										UploadMethod	uploadMethod,
+										BufferState		bufferState)
+	: RenderPerformanceTestBase	(context, name, description)
+	, m_drawMethod				(drawMethod)
+	, m_targetBuffer			(targetBuffer)
+	, m_uploadMethod			(uploadMethod)
+	, m_bufferState				(bufferState)
+	, m_numSamplesPerSwap		(10)
+	, m_numMaxSwaps				(4)
+	, m_frameNdx				(0)
+	, m_sampleNdx				(0)
+	, m_numVertices				(-1)
+	, m_vertexBuffer			(0)
+	, m_indexBuffer				(0)
+	, m_miscBuffer				(0)
+	, m_numMiscVertices			(-1)
+{
+}
+
+UploadWaitDrawCase::~UploadWaitDrawCase (void)
+{
+	deinit();
+}
+
+void UploadWaitDrawCase::init (void)
+{
+	const glw::Functions&	gl						= m_context.getRenderContext().getFunctions();
+	const int				vertexAttribSize		= (int)sizeof(tcu::Vec4) * 2; // color4, position4
+	const int				vertexIndexSize			= (int)sizeof(deUint32);
+	const int				vertexUploadDataSize	= (m_targetBuffer == TARGETBUFFER_VERTEX) ? (vertexAttribSize) : (vertexIndexSize);
+
+	RenderPerformanceTestBase::init();
+
+	// requirements
+
+	if (m_context.getRenderTarget().getWidth() < RENDER_AREA_SIZE ||
+		m_context.getRenderTarget().getHeight() < RENDER_AREA_SIZE)
+		throw tcu::NotSupportedError("Test case requires " + de::toString<int>(RENDER_AREA_SIZE) + "x" + de::toString<int>(RENDER_AREA_SIZE) + " render target");
+
+	// gl state
+
+	gl.viewport(0, 0, RENDER_AREA_SIZE, RENDER_AREA_SIZE);
+
+	// enable bleding to prevent grid layers from being discarded
+
+	gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	gl.blendEquation(GL_FUNC_ADD);
+	gl.enable(GL_BLEND);
+
+	// scene
+
+	{
+		LayeredGridSpec scene;
+
+		// create ~8MB workload with similar characteristics as in the other test
+		// => makes comparison to other results more straightforward
+		scene.gridWidth = 93;
+		scene.gridHeight = 93;
+		scene.gridLayers = 5;
+
+		generateLayeredGridVertexAttribData4C4V(m_vertexData, scene);
+		generateLayeredGridIndexData(m_indexData, scene);
+		m_numVertices = getLayeredGridNumVertices(scene);
+	}
+
+	// buffers
+
+	if (m_bufferState == BUFFERSTATE_NEW)
+	{
+		if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+		{
+			// reads from two buffers, prepare the static buffer
+
+			if (m_targetBuffer == TARGETBUFFER_VERTEX)
+			{
+				// index buffer is static, use another vertex buffer to keep original buffer in unused state
+				const glu::Buffer vertexCopyBuffer(m_context.getRenderContext());
+
+				gl.genBuffers(1, &m_indexBuffer);
+				gl.bindBuffer(GL_ARRAY_BUFFER, *vertexCopyBuffer);
+				gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
+				gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(m_vertexData.size() * sizeof(tcu::Vec4)), &m_vertexData[0], GL_STATIC_DRAW);
+				gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(m_indexData.size() * sizeof(deUint32)), &m_indexData[0], GL_STATIC_DRAW);
+
+				setupVertexAttribs();
+				gl.drawElements(GL_TRIANGLES, m_numVertices, GL_UNSIGNED_INT, DE_NULL);
+			}
+			else if (m_targetBuffer == TARGETBUFFER_INDEX)
+			{
+				// vertex buffer is static
+				gl.genBuffers(1, &m_vertexBuffer);
+				gl.bindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
+				gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(m_vertexData.size() * sizeof(tcu::Vec4)), &m_vertexData[0], GL_STATIC_DRAW);
+
+				setupVertexAttribs();
+				gl.drawArrays(GL_TRIANGLES, 0, m_numVertices);
+			}
+			else
+				DE_ASSERT(false);
+		}
+	}
+	else if (m_bufferState == BUFFERSTATE_EXISTING)
+	{
+		const glw::GLenum vertexUsage	= (m_targetBuffer == TARGETBUFFER_VERTEX) ? (GL_STATIC_DRAW) : (GL_STATIC_DRAW);
+		const glw::GLenum indexUsage	= (m_targetBuffer == TARGETBUFFER_INDEX) ? (GL_STATIC_DRAW) : (GL_STATIC_DRAW);
+
+		gl.genBuffers(1, &m_vertexBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(m_vertexData.size() * sizeof(tcu::Vec4)), &m_vertexData[0], vertexUsage);
+
+		if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+		{
+			gl.genBuffers(1, &m_indexBuffer);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
+			gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(m_indexData.size() * sizeof(deUint32)), &m_indexData[0], indexUsage);
+		}
+
+		setupVertexAttribs();
+
+		if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+			gl.drawArrays(GL_TRIANGLES, 0, m_numVertices);
+		else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+			gl.drawElements(GL_TRIANGLES, m_numVertices, GL_UNSIGNED_INT, DE_NULL);
+		else
+			DE_ASSERT(false);
+	}
+	else
+		DE_ASSERT(false);
+
+	// misc draw buffer
+	{
+		std::vector<tcu::Vec4>	vertexData;
+		LayeredGridSpec			scene;
+
+		// create ~1.5MB workload with similar characteristics
+		scene.gridWidth = 40;
+		scene.gridHeight = 40;
+		scene.gridLayers = 5;
+
+		generateLayeredGridVertexAttribData4C4V(vertexData, scene);
+
+		gl.genBuffers(1, &m_miscBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_miscBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(sizeof(tcu::Vec4) * vertexData.size()), &vertexData[0], GL_STATIC_DRAW);
+
+		m_numMiscVertices = getLayeredGridNumVertices(scene);
+	}
+
+	// iterations
+	{
+		m_samples.resize((m_numMaxSwaps+1) * m_numSamplesPerSwap);
+		m_results.resize((m_numMaxSwaps+1) * m_numSamplesPerSwap);
+
+		for (int numSwaps = 0; numSwaps <= m_numMaxSwaps; ++numSwaps)
+		for (int sampleNdx = 0; sampleNdx < m_numSamplesPerSwap; ++sampleNdx)
+		{
+			const int index = numSwaps*m_numSamplesPerSwap + sampleNdx;
+
+			m_samples[index].numFrames = numSwaps;
+		}
+
+		m_iterationOrder.resize(m_samples.size());
+		generateTwoPassRandomIterationOrder(m_iterationOrder, (int)m_samples.size());
+	}
+
+	// log
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Measuring time used in " << ((m_drawMethod == DRAWMETHOD_DRAW_ARRAYS) ? ("drawArrays") : ("drawElements")) << " and readPixels call.\n"
+		<< "Drawing using a buffer that has been uploaded N frames ago. Testing with N within range [0, " << m_numMaxSwaps << "].\n"
+		<< "Uploaded buffer is a " << ((m_targetBuffer == TARGETBUFFER_VERTEX) ? ("vertex attribute") : ("index")) << " buffer.\n"
+		<< "Uploading using "
+			<< ((m_uploadMethod == UPLOADMETHOD_BUFFER_DATA)		? ("bufferData")																							:
+				(m_uploadMethod == UPLOADMETHOD_BUFFER_SUB_DATA)	? ("bufferSubData")																							:
+				(m_uploadMethod == UPLOADMETHOD_MAP_BUFFER_RANGE)	? ("mapBufferRange, flags = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT")	:
+				((const char*)DE_NULL))
+			<< "\n"
+		<< "Upload size is " << getHumanReadableByteSize(m_numVertices * vertexUploadDataSize) << ".\n"
+		<< ((m_bufferState == BUFFERSTATE_EXISTING) ? ("All test samples use the same buffer object.\n") : (""))
+		<< "Test result is the number of frames (swaps) required for the render time to stabilize.\n"
+		<< "Assuming combined time used in the draw call and readPixels call is stabilizes to a constant value.\n"
+		<< tcu::TestLog::EndMessage;
+}
+
+void UploadWaitDrawCase::deinit (void)
+{
+	RenderPerformanceTestBase::deinit();
+
+	if (m_vertexBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_vertexBuffer);
+		m_vertexBuffer = 0;
+	}
+	if (m_indexBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_indexBuffer);
+		m_indexBuffer = 0;
+	}
+	if (m_miscBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_miscBuffer);
+		m_miscBuffer = 0;
+	}
+}
+
+UploadWaitDrawCase::IterateResult UploadWaitDrawCase::iterate (void)
+{
+	const glw::Functions&	gl								= m_context.getRenderContext().getFunctions();
+	const int				betweenIterationDummyFrameCount = 5; // draw misc between test samples
+	const int				frameNdx						= m_frameNdx++;
+	const int				currentSampleNdx				= m_iterationOrder[m_sampleNdx];
+
+	// Simulate work for about 8ms
+	busyWait(8000);
+
+	// Dummy rendering during dummy frames
+	if (frameNdx != m_samples[currentSampleNdx].numFrames)
+	{
+		// draw similar from another buffer
+		drawMisc();
+	}
+
+	if (frameNdx == 0)
+	{
+		// upload and start the clock
+		uploadBuffer(m_samples[currentSampleNdx], m_results[currentSampleNdx]);
+	}
+
+	if (frameNdx == m_samples[currentSampleNdx].numFrames) // \note: not else if, m_samples[currentSampleNdx].numFrames can be 0
+	{
+		// draw using the uploaded buffer
+		drawFromBuffer(m_samples[currentSampleNdx], m_results[currentSampleNdx]);
+
+		// re-use buffer for something else to make sure test iteration do not affect each other
+		if (m_bufferState == BUFFERSTATE_NEW)
+			reuseAndDeleteBuffer();
+	}
+	else if (frameNdx == m_samples[currentSampleNdx].numFrames + betweenIterationDummyFrameCount)
+	{
+		// next sample
+		++m_sampleNdx;
+		m_frameNdx = 0;
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "post-iterate");
+
+	if (m_sampleNdx < (int)m_samples.size())
+		return CONTINUE;
+
+	logAndSetTestResult();
+	return STOP;
+}
+
+void UploadWaitDrawCase::uploadBuffer (Sample& sample, Result& result)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	deUint64				startTime;
+	deUint64				endTime;
+	glw::GLenum				target;
+	glw::GLsizeiptr			size;
+	const void*				source;
+
+	// data source
+
+	if (m_targetBuffer == TARGETBUFFER_VERTEX)
+	{
+		DE_ASSERT((m_vertexBuffer == 0) == (m_bufferState == BUFFERSTATE_NEW));
+
+		target	= GL_ARRAY_BUFFER;
+		size	= (glw::GLsizeiptr)(m_vertexData.size() * sizeof(tcu::Vec4));
+		source	= &m_vertexData[0];
+	}
+	else if (m_targetBuffer == TARGETBUFFER_INDEX)
+	{
+		DE_ASSERT((m_indexBuffer == 0) == (m_bufferState == BUFFERSTATE_NEW));
+
+		target	= GL_ELEMENT_ARRAY_BUFFER;
+		size	= (glw::GLsizeiptr)(m_indexData.size() * sizeof(deUint32));
+		source	= &m_indexData[0];
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return;
+	}
+
+	// gen buffer
+
+	if (m_bufferState == BUFFERSTATE_NEW)
+	{
+		if (m_targetBuffer == TARGETBUFFER_VERTEX)
+		{
+			gl.genBuffers(1, &m_vertexBuffer);
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
+		}
+		else if (m_targetBuffer == TARGETBUFFER_INDEX)
+		{
+			gl.genBuffers(1, &m_indexBuffer);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
+		}
+		else
+			DE_ASSERT(false);
+
+		if (m_uploadMethod == UPLOADMETHOD_BUFFER_SUB_DATA ||
+			m_uploadMethod == UPLOADMETHOD_MAP_BUFFER_RANGE)
+		{
+			gl.bufferData(target, size, DE_NULL, GL_STATIC_DRAW);
+		}
+	}
+	else if (m_bufferState == BUFFERSTATE_EXISTING)
+	{
+		if (m_targetBuffer == TARGETBUFFER_VERTEX)
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
+		else if (m_targetBuffer == TARGETBUFFER_INDEX)
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
+		else
+			DE_ASSERT(false);
+	}
+	else
+		DE_ASSERT(false);
+
+	// upload
+
+	startTime = deGetMicroseconds();
+
+	if (m_uploadMethod == UPLOADMETHOD_BUFFER_DATA)
+		gl.bufferData(target, size, source, GL_STATIC_DRAW);
+	else if (m_uploadMethod == UPLOADMETHOD_BUFFER_SUB_DATA)
+		gl.bufferSubData(target, 0, size, source);
+	else if (m_uploadMethod == UPLOADMETHOD_MAP_BUFFER_RANGE)
+	{
+		void*			mapPtr;
+		glw::GLboolean	unmapSuccessful;
+
+		mapPtr = gl.mapBufferRange(target, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
+		if (!mapPtr)
+			throw tcu::Exception("MapBufferRange returned NULL");
+
+		deMemcpy(mapPtr, source, (int)size);
+
+		// if unmapping fails, just try again later
+		unmapSuccessful = gl.unmapBuffer(target);
+		if (!unmapSuccessful)
+			throw UnmapFailureError();
+	}
+	else
+		DE_ASSERT(false);
+
+	endTime = deGetMicroseconds();
+
+	sample.uploadCallEndTime = endTime;
+	result.uploadDuration = endTime - startTime;
+}
+
+void UploadWaitDrawCase::drawFromBuffer (Sample& sample, Result& result)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	tcu::Surface			resultSurface	(RENDER_AREA_SIZE, RENDER_AREA_SIZE);
+	deUint64				startTime;
+	deUint64				endTime;
+
+	DE_ASSERT(m_vertexBuffer != 0);
+	if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+		DE_ASSERT(m_indexBuffer == 0);
+	else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+		DE_ASSERT(m_indexBuffer != 0);
+	else
+		DE_ASSERT(false);
+
+	// draw
+	{
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
+		if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
+
+		setupVertexAttribs();
+
+		// microseconds passed since return from upload call
+		result.timeBeforeUse = deGetMicroseconds() - sample.uploadCallEndTime;
+
+		startTime = deGetMicroseconds();
+
+		if (m_drawMethod == DRAWMETHOD_DRAW_ARRAYS)
+			gl.drawArrays(GL_TRIANGLES, 0, m_numVertices);
+		else if (m_drawMethod == DRAWMETHOD_DRAW_ELEMENTS)
+			gl.drawElements(GL_TRIANGLES, m_numVertices, GL_UNSIGNED_INT, DE_NULL);
+		else
+			DE_ASSERT(false);
+
+		endTime = deGetMicroseconds();
+
+		result.renderDuration = endTime - startTime;
+	}
+
+	// read
+	{
+		startTime = deGetMicroseconds();
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());
+		endTime = deGetMicroseconds();
+
+		result.readDuration = endTime - startTime;
+	}
+
+	result.renderReadDuration = result.renderDuration + result.readDuration;
+}
+
+void UploadWaitDrawCase::reuseAndDeleteBuffer (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_targetBuffer == TARGETBUFFER_INDEX)
+	{
+		// respecify and delete index buffer
+		static const deUint32 indices[3] = {1, 3, 8};
+
+		DE_ASSERT(m_indexBuffer != 0);
+
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+		gl.drawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, DE_NULL);
+		gl.deleteBuffers(1, &m_indexBuffer);
+		m_indexBuffer = 0;
+	}
+	else if (m_targetBuffer == TARGETBUFFER_VERTEX)
+	{
+		// respecify and delete vertex buffer
+		static const tcu::Vec4 coloredTriangle[6] =
+		{
+			tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f), tcu::Vec4(-0.4f, -0.4f, 0.0f, 1.0f),
+			tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f), tcu::Vec4(-0.2f,  0.4f, 0.0f, 1.0f),
+			tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f), tcu::Vec4( 0.8f, -0.1f, 0.0f, 1.0f),
+		};
+
+		DE_ASSERT(m_vertexBuffer != 0);
+
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(coloredTriangle), coloredTriangle, GL_STATIC_DRAW);
+		gl.drawArrays(GL_TRIANGLES, 0, 3);
+		gl.deleteBuffers(1, &m_vertexBuffer);
+		m_vertexBuffer = 0;
+	}
+
+	waitGLResults();
+}
+
+void UploadWaitDrawCase::logAndSetTestResult (void)
+{
+	int		uploadStabilization;
+	int		renderReadStabilization;
+	int		renderStabilization;
+	int		readStabilization;
+	bool	temporallyStable;
+
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "Samples", "Result samples");
+		logSamples();
+	}
+
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "Stabilization", "Sample stability");
+
+		// log stabilization points
+		renderReadStabilization	= findStabilizationSample(&Result::renderReadDuration, "Combined draw and read");
+		uploadStabilization		= findStabilizationSample(&Result::uploadDuration, "Upload time");
+		renderStabilization		= findStabilizationSample(&Result::renderDuration, "Draw call time");
+		readStabilization		= findStabilizationSample(&Result::readDuration, "ReadPixels time");
+
+		temporallyStable		= true;
+		temporallyStable		&= checkSampleTemporalStability(&Result::renderReadDuration, "Combined draw and read");
+		temporallyStable		&= checkSampleTemporalStability(&Result::uploadDuration, "Upload time");
+		temporallyStable		&= checkSampleTemporalStability(&Result::renderDuration, "Draw call time");
+		temporallyStable		&= checkSampleTemporalStability(&Result::readDuration, "ReadPixels time");
+	}
+
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "Results", "Results");
+
+		// Check result sanily
+		if (uploadStabilization != 0)
+			m_testCtx.getLog() << tcu::TestLog::Message << "Warning! Upload times are not stable, test result may not be accurate." << tcu::TestLog::EndMessage;
+		if (!temporallyStable)
+			m_testCtx.getLog() << tcu::TestLog::Message << "Warning! Time samples do not seem to be temporally stable, sample times seem to drift to one direction during test execution." << tcu::TestLog::EndMessage;
+
+		// render & read
+		if (renderReadStabilization == -1)
+			m_testCtx.getLog() << tcu::TestLog::Message << "Combined time used in draw call and ReadPixels did not stabilize." << tcu::TestLog::EndMessage;
+		else
+			m_testCtx.getLog() << tcu::TestLog::Integer("RenderReadStabilizationPoint", "Combined draw call and ReadPixels call time stabilization time", "frames", QP_KEY_TAG_TIME, renderReadStabilization);
+
+		// draw call
+		if (renderStabilization == -1)
+			m_testCtx.getLog() << tcu::TestLog::Message << "Time used in draw call did not stabilize." << tcu::TestLog::EndMessage;
+		else
+			m_testCtx.getLog() << tcu::TestLog::Integer("DrawCallStabilizationPoint", "Draw call time stabilization time", "frames", QP_KEY_TAG_TIME, renderStabilization);
+
+		// readpixels
+		if (readStabilization == -1)
+			m_testCtx.getLog() << tcu::TestLog::Message << "Time used in ReadPixels did not stabilize." << tcu::TestLog::EndMessage;
+		else
+			m_testCtx.getLog() << tcu::TestLog::Integer("ReadPixelsStabilizationPoint", "ReadPixels call time stabilization time", "frames", QP_KEY_TAG_TIME, readStabilization);
+
+		// Report renderReadStabilization
+		if (renderReadStabilization != -1)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(renderReadStabilization).c_str());
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::toString(m_numMaxSwaps).c_str()); // don't report -1
+	}
+}
+
+void UploadWaitDrawCase::logSamples (void)
+{
+	// Inverse m_iterationOrder
+
+	std::vector<int> runOrder(m_iterationOrder.size());
+	for (int ndx = 0; ndx < (int)m_iterationOrder.size(); ++ndx)
+		runOrder[m_iterationOrder[ndx]] = ndx;
+
+	// Log samples
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::SampleList("Samples", "Samples")
+		<< tcu::TestLog::SampleInfo
+		<< tcu::TestLog::ValueInfo("NumSwaps",		"SwapBuffers before use",			"",		QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("Delay",			"Time before use",					"us",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("RunOrder",		"Sample run order",					"",		QP_SAMPLE_VALUE_TAG_PREDICTOR)
+		<< tcu::TestLog::ValueInfo("DrawReadTime",	"Draw call and ReadPixels time",	"us",	QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("TotalTime",		"Total time",						"us",	QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("Upload time",	"Upload time",						"us",	QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("DrawCallTime",	"Draw call time",					"us",	QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::ValueInfo("ReadTime",		"ReadPixels time",					"us",	QP_SAMPLE_VALUE_TAG_RESPONSE)
+		<< tcu::TestLog::EndSampleInfo;
+
+	for (int sampleNdx = 0; sampleNdx < (int)m_samples.size(); ++sampleNdx)
+		m_testCtx.getLog()
+			<< tcu::TestLog::Sample
+			<< m_samples[sampleNdx].numFrames
+			<< (int)m_results[sampleNdx].timeBeforeUse
+			<< runOrder[sampleNdx]
+			<< (int)m_results[sampleNdx].renderReadDuration
+			<< (int)(m_results[sampleNdx].renderReadDuration + m_results[sampleNdx].uploadDuration)
+			<< (int)m_results[sampleNdx].uploadDuration
+			<< (int)m_results[sampleNdx].renderDuration
+			<< (int)m_results[sampleNdx].readDuration
+			<< tcu::TestLog::EndSample;
+
+	m_testCtx.getLog() << tcu::TestLog::EndSampleList;
+}
+
+void UploadWaitDrawCase::drawMisc (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_miscBuffer);
+	setupVertexAttribs();
+	gl.drawArrays(GL_TRIANGLES, 0, m_numMiscVertices);
+}
+
+struct DistributionCompareResult
+{
+	bool	equal;
+	float	standardDeviations;
+};
+
+template <typename Comparer>
+static float sumOfRanks (const std::vector<deUint64>& testSamples, const std::vector<deUint64>& allSamples, const Comparer& comparer)
+{
+	float sum = 0;
+
+	for (int sampleNdx = 0; sampleNdx < (int)testSamples.size(); ++sampleNdx)
+	{
+		const deUint64	testSample		= testSamples[sampleNdx];
+		const int		lowerIndex		= (int)(std::lower_bound(allSamples.begin(), allSamples.end(), testSample, comparer) - allSamples.begin());
+		const int		upperIndex		= (int)(std::upper_bound(allSamples.begin(), allSamples.end(), testSample, comparer) - allSamples.begin());
+		const int		lowerRank		= lowerIndex + 1;	// convert zero-indexed to rank
+		const int		upperRank		= upperIndex;		// convert zero-indexed to rank, upperIndex is last equal + 1
+		const float		rankMidpoint	= (lowerRank + upperRank) / 2.0f;
+
+		sum += rankMidpoint;
+	}
+
+	return sum;
+}
+
+template <typename Comparer>
+static DistributionCompareResult distributionCompare (const std::vector<deUint64>& orderedObservationsA, const std::vector<deUint64>& orderedObservationsB, const Comparer& comparer)
+{
+	// Mann–Whitney U test
+
+	const int				n1			= (int)orderedObservationsA.size();
+	const int				n2			= (int)orderedObservationsB.size();
+	std::vector<deUint64>	allSamples	(n1 + n2);
+
+	std::copy(orderedObservationsA.begin(), orderedObservationsA.end(), allSamples.begin());
+	std::copy(orderedObservationsB.begin(), orderedObservationsB.end(), allSamples.begin() + n1);
+	std::sort(allSamples.begin(), allSamples.end());
+
+	{
+		const float					R1		= sumOfRanks(orderedObservationsA, allSamples, comparer);
+
+		const float					U1		= n1*n2 + n1*(n1 + 1)/2 - R1;
+		const float					U2		= (n1 * n2) - U1;
+		const float					U		= de::min(U1, U2);
+
+		// \note: sample sizes might not be large enough to expect normal distribution but we do it anyway
+
+		const float					mU		= n1*n2 / 2.0f;
+		const float					sigmaU	= deFloatSqrt((n1*n2*(n1+n2+1)) / 12.0f);
+		const float					z		= (U - mU) / sigmaU;
+
+		DistributionCompareResult	result;
+
+		result.equal				= (de::abs(z) <= 1.96f); // accept within 95% confidence interval
+		result.standardDeviations	= z;
+
+		return result;
+	}
+}
+
+template <typename T>
+struct ThresholdComparer
+{
+	float	relativeThreshold;
+	T		absoluteThreshold;
+
+	bool operator() (const T& a, const T& b) const
+	{
+		const float diff = de::abs((float)a - (float)b);
+
+		// thresholds
+		if (diff <= (float)absoluteThreshold)
+			return false;
+		if (diff <= a*relativeThreshold ||
+			diff <= b*relativeThreshold)
+			return false;
+
+		// cmp
+		return a < b;
+	}
+};
+
+int UploadWaitDrawCase::findStabilizationSample (deUint64 (UploadWaitDrawCase::Result::*target), const char* description)
+{
+	std::vector<std::vector<deUint64> >	sampleObservations(m_numMaxSwaps+1);
+	ThresholdComparer<deUint64>			comparer;
+
+	comparer.relativeThreshold = 0.15f;	// 15%
+	comparer.absoluteThreshold = 100;	// (us), assumed sampling precision
+
+	// get observations and order them
+
+	for (int swapNdx = 0; swapNdx <= m_numMaxSwaps; ++swapNdx)
+	{
+		int insertNdx = 0;
+
+		sampleObservations[swapNdx].resize(m_numSamplesPerSwap);
+
+		for (int ndx = 0; ndx < (int)m_samples.size(); ++ndx)
+			if (m_samples[ndx].numFrames == swapNdx)
+				sampleObservations[swapNdx][insertNdx++] = m_results[ndx].*target;
+
+		DE_ASSERT(insertNdx == m_numSamplesPerSwap);
+
+		std::sort(sampleObservations[swapNdx].begin(), sampleObservations[swapNdx].end());
+	}
+
+	// find stabilization point
+
+	for (int sampleNdx = m_numMaxSwaps-1; sampleNdx != -1; --sampleNdx )
+	{
+		// Distribution is equal to all following distributions
+		for (int cmpTargetDistribution = sampleNdx+1; cmpTargetDistribution <= m_numMaxSwaps; ++cmpTargetDistribution)
+		{
+			// Stable section ends here?
+			const DistributionCompareResult result = distributionCompare(sampleObservations[sampleNdx], sampleObservations[cmpTargetDistribution], comparer);
+			if (!result.equal)
+			{
+				// Last two samples are not equal? Samples never stabilized
+				if (sampleNdx == m_numMaxSwaps-1)
+				{
+					m_testCtx.getLog()
+						<< tcu::TestLog::Message
+						<< description << ": Samples with swap count " << sampleNdx << " and " << cmpTargetDistribution << " do not seem to have the same distribution:\n"
+						<< "\tDifference in standard deviations: " << result.standardDeviations << "\n"
+						<< "\tSwap count " << sampleNdx << " median: " << linearSample(sampleObservations[sampleNdx], 0.5f) << "\n"
+						<< "\tSwap count " << cmpTargetDistribution << " median: " << linearSample(sampleObservations[cmpTargetDistribution], 0.5f) << "\n"
+						<< tcu::TestLog::EndMessage;
+					return -1;
+				}
+				else
+				{
+					m_testCtx.getLog()
+						<< tcu::TestLog::Message
+						<< description << ": Samples with swap count " << sampleNdx << " and " << cmpTargetDistribution << " do not seem to have the same distribution:\n"
+						<< "\tSamples with swap count " << sampleNdx << " are not part of the tail of stable results.\n"
+						<< "\tDifference in standard deviations: " << result.standardDeviations << "\n"
+						<< "\tSwap count " << sampleNdx << " median: " << linearSample(sampleObservations[sampleNdx], 0.5f) << "\n"
+						<< "\tSwap count " << cmpTargetDistribution << " median: " << linearSample(sampleObservations[cmpTargetDistribution], 0.5f) << "\n"
+						<< tcu::TestLog::EndMessage;
+
+					return sampleNdx+1;
+				}
+			}
+		}
+	}
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< description << ": All samples seem to have the same distribution"
+		<< tcu::TestLog::EndMessage;
+
+	// all distributions equal
+	return 0;
+}
+
+bool UploadWaitDrawCase::checkSampleTemporalStability (deUint64 (UploadWaitDrawCase::Result::*target), const char* description)
+{
+	// Try to find correlation with sample order and sample times
+
+	const int				numDataPoints	= (int)m_iterationOrder.size();
+	std::vector<tcu::Vec2>	dataPoints		(m_iterationOrder.size());
+	TheilSenLineFit			lineFit;
+
+	for (int ndx = 0; ndx < (int)m_iterationOrder.size(); ++ndx)
+	{
+		dataPoints[m_iterationOrder[ndx]].x() = (float)ndx;
+		dataPoints[m_iterationOrder[ndx]].y() = (float)(m_results[m_iterationOrder[ndx]].*target);
+	}
+
+	lineFit = theilSenLinearRegression(dataPoints);
+
+	// Difference of more than 25% of the offset along the whole sample range
+	if (de::abs(lineFit.coefficient) * numDataPoints > de::abs(lineFit.offset) * 0.25f)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< description << ": Correlation with data point observation order and result time. Results are not temporally stable, observations are not independent.\n"
+			<< "\tCoefficient: " << lineFit.coefficient << " (us / observation)\n"
+			<< tcu::TestLog::EndMessage;
+
+		return false;
+	}
+	else
+		return true;
+}
+
+} // anonymous
+
+BufferDataUploadTests::BufferDataUploadTests (Context& context)
+	: TestCaseGroup(context, "data_upload", "Buffer data upload performance tests")
+{
+}
+
+BufferDataUploadTests::~BufferDataUploadTests (void)
+{
+}
+
+void BufferDataUploadTests::init (void)
+{
+	static const struct BufferUsage
+	{
+		const char* name;
+		deUint32	usage;
+		bool		primaryUsage;
+	} bufferUsages[] =
+	{
+		{ "stream_draw",	GL_STREAM_DRAW,		true	},
+		{ "stream_read",	GL_STREAM_READ,		false	},
+		{ "stream_copy",	GL_STREAM_COPY,		false	},
+		{ "static_draw",	GL_STATIC_DRAW,		true	},
+		{ "static_read",	GL_STATIC_READ,		false	},
+		{ "static_copy",	GL_STATIC_COPY,		false	},
+		{ "dynamic_draw",	GL_DYNAMIC_DRAW,	true	},
+		{ "dynamic_read",	GL_DYNAMIC_READ,	false	},
+		{ "dynamic_copy",	GL_DYNAMIC_COPY,	false	},
+	};
+
+	tcu::TestCaseGroup* const referenceGroup			= new tcu::TestCaseGroup(m_testCtx, "reference",			"Reference functions");
+	tcu::TestCaseGroup* const functionCallGroup			= new tcu::TestCaseGroup(m_testCtx, "function_call",		"Function call timing");
+	tcu::TestCaseGroup* const modifyAfterUseGroup		= new tcu::TestCaseGroup(m_testCtx, "modify_after_use",		"Function call time after buffer has been used");
+	tcu::TestCaseGroup* const renderAfterUploadGroup	= new tcu::TestCaseGroup(m_testCtx, "render_after_upload",	"Function call time of draw commands after buffer has been modified");
+
+	addChild(referenceGroup);
+	addChild(functionCallGroup);
+	addChild(modifyAfterUseGroup);
+	addChild(renderAfterUploadGroup);
+
+	// .reference
+	{
+		static const struct BufferSizeRange
+		{
+			const char* name;
+			int			minBufferSize;
+			int			maxBufferSize;
+			int			numSamples;
+			bool		largeBuffersCase;
+		} sizeRanges[] =
+		{
+			{ "small_buffers", 0,		1 << 18,	64,		false	}, // !< 0kB - 256kB
+			{ "large_buffers", 1 << 18,	1 << 24,	32,		true	}, // !< 256kB - 16MB
+		};
+
+		for (int bufferSizeRangeNdx = 0; bufferSizeRangeNdx < DE_LENGTH_OF_ARRAY(sizeRanges); ++bufferSizeRangeNdx)
+		{
+			referenceGroup->addChild(new ReferenceMemcpyCase(m_context,
+															 std::string("memcpy_").append(sizeRanges[bufferSizeRangeNdx].name).c_str(),
+															 "Test memcpy performance",
+															 sizeRanges[bufferSizeRangeNdx].minBufferSize,
+															 sizeRanges[bufferSizeRangeNdx].maxBufferSize,
+															 sizeRanges[bufferSizeRangeNdx].numSamples,
+															 sizeRanges[bufferSizeRangeNdx].largeBuffersCase));
+		}
+	}
+
+	// .function_call
+	{
+		const int minBufferSize		= 0;		// !< 0kiB
+		const int maxBufferSize		= 1 << 24;	// !< 16MiB
+		const int numDataSamples	= 25;
+		const int numMapSamples		= 25;
+
+		tcu::TestCaseGroup* const bufferDataMethodGroup		= new tcu::TestCaseGroup(m_testCtx, "buffer_data", "Use glBufferData");
+		tcu::TestCaseGroup* const bufferSubDataMethodGroup	= new tcu::TestCaseGroup(m_testCtx, "buffer_sub_data", "Use glBufferSubData");
+		tcu::TestCaseGroup* const mapBufferRangeMethodGroup	= new tcu::TestCaseGroup(m_testCtx, "map_buffer_range", "Use glMapBufferRange");
+
+		functionCallGroup->addChild(bufferDataMethodGroup);
+		functionCallGroup->addChild(bufferSubDataMethodGroup);
+		functionCallGroup->addChild(mapBufferRangeMethodGroup);
+
+		// .buffer_data
+		{
+			static const struct TargetCase
+			{
+				tcu::TestCaseGroup*				group;
+				BufferDataUploadCase::CaseType	caseType;
+				bool							allUsages;
+			} targetCases[] =
+			{
+				{ new tcu::TestCaseGroup(m_testCtx, "new_buffer",				"Target new buffer"),							BufferDataUploadCase::CASE_NEW_BUFFER,			true	},
+				{ new tcu::TestCaseGroup(m_testCtx, "unspecified_buffer",		"Target new unspecified buffer"),				BufferDataUploadCase::CASE_UNSPECIFIED_BUFFER,	true	},
+				{ new tcu::TestCaseGroup(m_testCtx, "specified_buffer",			"Target new specified buffer"),					BufferDataUploadCase::CASE_SPECIFIED_BUFFER,	true	},
+				{ new tcu::TestCaseGroup(m_testCtx, "used_buffer",				"Target buffer that was used in draw"),			BufferDataUploadCase::CASE_USED_BUFFER,			true	},
+				{ new tcu::TestCaseGroup(m_testCtx, "larger_used_buffer",		"Target larger buffer that was used in draw"),	BufferDataUploadCase::CASE_USED_LARGER_BUFFER,	false	},
+			};
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targetCases); ++targetNdx)
+			{
+				bufferDataMethodGroup->addChild(targetCases[targetNdx].group);
+
+				for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(bufferUsages); ++usageNdx)
+					if (bufferUsages[usageNdx].primaryUsage || targetCases[targetNdx].allUsages)
+						targetCases[targetNdx].group->addChild(new BufferDataUploadCase(m_context,
+																						std::string("usage_").append(bufferUsages[usageNdx].name).c_str(),
+																						std::string("Test with usage = ").append(bufferUsages[usageNdx].name).c_str(),
+																						minBufferSize,
+																						maxBufferSize,
+																						numDataSamples,
+																						bufferUsages[usageNdx].usage,
+																						targetCases[targetNdx].caseType));
+			}
+		}
+
+		// .buffer_sub_data
+		{
+			static const struct FlagCase
+			{
+				tcu::TestCaseGroup*					group;
+				BufferSubDataUploadCase::CaseType	parentCase;
+				bool								allUsages;
+				int									flags;
+			} flagCases[] =
+			{
+				{ new tcu::TestCaseGroup(m_testCtx, "used_buffer_full_upload",					    ""),															BufferSubDataUploadCase::CASE_USED_BUFFER,	true,	BufferSubDataUploadCase::FLAG_FULL_UPLOAD															},
+				{ new tcu::TestCaseGroup(m_testCtx, "used_buffer_invalidate_before_full_upload",    "Clear buffer with bufferData(...,NULL) before sub data call"),	BufferSubDataUploadCase::CASE_USED_BUFFER,	false,	BufferSubDataUploadCase::FLAG_FULL_UPLOAD    | BufferSubDataUploadCase::FLAG_INVALIDATE_BEFORE_USE	},
+				{ new tcu::TestCaseGroup(m_testCtx, "used_buffer_partial_upload",                   ""),															BufferSubDataUploadCase::CASE_USED_BUFFER,	true,	BufferSubDataUploadCase::FLAG_PARTIAL_UPLOAD														},
+				{ new tcu::TestCaseGroup(m_testCtx, "used_buffer_invalidate_before_partial_upload", "Clear buffer with bufferData(...,NULL) before sub data call"),	BufferSubDataUploadCase::CASE_USED_BUFFER,	false,	BufferSubDataUploadCase::FLAG_PARTIAL_UPLOAD | BufferSubDataUploadCase::FLAG_INVALIDATE_BEFORE_USE	},
+			};
+
+			for (int flagNdx = 0; flagNdx < DE_LENGTH_OF_ARRAY(flagCases); ++flagNdx)
+			{
+				bufferSubDataMethodGroup->addChild(flagCases[flagNdx].group);
+
+				for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(bufferUsages); ++usageNdx)
+					if (bufferUsages[usageNdx].primaryUsage || flagCases[flagNdx].allUsages)
+							flagCases[flagNdx].group->addChild(new BufferSubDataUploadCase(m_context,
+																						   std::string("usage_").append(bufferUsages[usageNdx].name).c_str(),
+																						   std::string("Test with usage = ").append(bufferUsages[usageNdx].name).c_str(),
+																						   minBufferSize,
+																						   maxBufferSize,
+																						   numDataSamples,
+																						   bufferUsages[usageNdx].usage,
+																						   flagCases[flagNdx].parentCase,
+																						   flagCases[flagNdx].flags));
+			}
+		}
+
+		// .map_buffer_range
+		{
+			static const struct FlagCase
+			{
+				const char*	name;
+				bool		usefulForUnusedBuffers;
+				bool		allUsages;
+				int			glFlags;
+				int			caseFlags;
+			} flagCases[] =
+			{
+				{ "flag_write_full",										true,	true,	GL_MAP_WRITE_BIT,																0																				},
+				{ "flag_write_partial",										true,	true,	GL_MAP_WRITE_BIT,																MapBufferRangeCase::FLAG_PARTIAL												},
+				{ "flag_read_write_full",									true,	true,	GL_MAP_WRITE_BIT | GL_MAP_READ_BIT,												0																				},
+				{ "flag_read_write_partial",								true,	true,	GL_MAP_WRITE_BIT | GL_MAP_READ_BIT,												MapBufferRangeCase::FLAG_PARTIAL												},
+				{ "flag_invalidate_range_full",								true,	false,	GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT,									0																				},
+				{ "flag_invalidate_range_partial",							true,	false,	GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT,									MapBufferRangeCase::FLAG_PARTIAL												},
+				{ "flag_invalidate_buffer_full",							true,	false,	GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT,								0																				},
+				{ "flag_invalidate_buffer_partial",							true,	false,	GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT,								MapBufferRangeCase::FLAG_PARTIAL												},
+				{ "flag_write_full_manual_invalidate_buffer",				false,	false,	GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT,									MapBufferRangeCase::FLAG_MANUAL_INVALIDATION									},
+				{ "flag_write_partial_manual_invalidate_buffer",			false,	false,	GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT,									MapBufferRangeCase::FLAG_PARTIAL | MapBufferRangeCase::FLAG_MANUAL_INVALIDATION	},
+				{ "flag_unsynchronized_full",								true,	false,	GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT,									0																				},
+				{ "flag_unsynchronized_partial",							true,	false,	GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT,									MapBufferRangeCase::FLAG_PARTIAL												},
+				{ "flag_unsynchronized_and_invalidate_buffer_full",			true,	false,	GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT,	0																				},
+				{ "flag_unsynchronized_and_invalidate_buffer_partial",		true,	false,	GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT,	MapBufferRangeCase::FLAG_PARTIAL												},
+			};
+			static const struct FlushCases
+			{
+				const char*	name;
+				int			glFlags;
+				int			caseFlags;
+			} flushCases[] =
+			{
+				{ "flag_flush_explicit_map_full",					GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT,	0												},
+				{ "flag_flush_explicit_map_partial",				GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT,	MapBufferRangeFlushCase::FLAG_PARTIAL			},
+				{ "flag_flush_explicit_map_full_flush_in_parts",	GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT,	MapBufferRangeFlushCase::FLAG_FLUSH_IN_PARTS	},
+				{ "flag_flush_explicit_map_full_flush_partial",		GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT,	MapBufferRangeFlushCase::FLAG_FLUSH_PARTIAL		},
+			};
+			static const struct MapTestGroup
+			{
+				int					flags;
+				bool				unusedBufferCase;
+				tcu::TestCaseGroup* group;
+			} groups[] =
+			{
+				{ MapBufferRangeCase::FLAG_USE_UNUSED_UNSPECIFIED_BUFFER,	true,	new tcu::TestCaseGroup(m_testCtx, "new_unspecified_buffer", "Test with unused, unspecified buffers"),				},
+				{ MapBufferRangeCase::FLAG_USE_UNUSED_SPECIFIED_BUFFER,		true,	new tcu::TestCaseGroup(m_testCtx, "new_specified_buffer", "Test with unused, specified buffers"),					},
+				{ 0,														false,	new tcu::TestCaseGroup(m_testCtx, "used_buffer", "Test with used (data has been sourced from a buffer) buffers")	},
+			};
+
+			// we OR same flags to both range and flushRange cases, make sure it is legal
+			DE_STATIC_ASSERT((int)MapBufferRangeCase::FLAG_USE_UNUSED_SPECIFIED_BUFFER == (int)MapBufferRangeFlushCase::FLAG_USE_UNUSED_SPECIFIED_BUFFER);
+			DE_STATIC_ASSERT((int)MapBufferRangeCase::FLAG_USE_UNUSED_UNSPECIFIED_BUFFER == (int)MapBufferRangeFlushCase::FLAG_USE_UNUSED_UNSPECIFIED_BUFFER);
+
+			for (int groupNdx = 0; groupNdx < DE_LENGTH_OF_ARRAY(groups); ++groupNdx)
+			{
+				tcu::TestCaseGroup* const bufferTypeGroup = groups[groupNdx].group;
+
+				mapBufferRangeMethodGroup->addChild(bufferTypeGroup);
+
+				for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(flagCases); ++caseNdx)
+				{
+					if (groups[groupNdx].unusedBufferCase && !flagCases[caseNdx].usefulForUnusedBuffers)
+						continue;
+
+					tcu::TestCaseGroup* const bufferUsageGroup = new tcu::TestCaseGroup(m_testCtx, flagCases[caseNdx].name, "");
+					bufferTypeGroup->addChild(bufferUsageGroup);
+
+					for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(bufferUsages); ++usageNdx)
+						if (bufferUsages[usageNdx].primaryUsage || flagCases[caseNdx].allUsages)
+							bufferUsageGroup->addChild(new MapBufferRangeCase(m_context,
+																			  bufferUsages[usageNdx].name,
+																			  std::string("Test with usage = ").append(bufferUsages[usageNdx].name).c_str(),
+																			  minBufferSize,
+																			  maxBufferSize,
+																			  numMapSamples,
+																			  bufferUsages[usageNdx].usage,
+																			  flagCases[caseNdx].glFlags,
+																			  flagCases[caseNdx].caseFlags | groups[groupNdx].flags));
+				}
+
+				for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(flushCases); ++caseNdx)
+				{
+					tcu::TestCaseGroup* const bufferUsageGroup = new tcu::TestCaseGroup(m_testCtx, flushCases[caseNdx].name, "");
+					bufferTypeGroup->addChild(bufferUsageGroup);
+
+					for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(bufferUsages); ++usageNdx)
+						if (bufferUsages[usageNdx].primaryUsage)
+							bufferUsageGroup->addChild(new MapBufferRangeFlushCase(m_context,
+																				   bufferUsages[usageNdx].name,
+																				   std::string("Test with usage = ").append(bufferUsages[usageNdx].name).c_str(),
+																				   minBufferSize,
+																				   maxBufferSize,
+																				   numMapSamples,
+																				   bufferUsages[usageNdx].usage,
+																				   flushCases[caseNdx].glFlags,
+																				   flushCases[caseNdx].caseFlags | groups[groupNdx].flags));
+				}
+			}
+		}
+	}
+
+	// .modify_after_use
+	{
+		const int minBufferSize	= 0;		// !< 0kiB
+		const int maxBufferSize	= 1 << 24;	// !< 16MiB
+
+		static const struct Usage
+		{
+			const char* name;
+			const char* description;
+			deUint32	usage;
+		} usages[] =
+		{
+			{ "static_draw",	"Test with GL_STATIC_DRAW",		GL_STATIC_DRAW	},
+			{ "dynamic_draw",	"Test with GL_DYNAMIC_DRAW",	GL_DYNAMIC_DRAW	},
+			{ "stream_draw",	"Test with GL_STREAM_DRAW",		GL_STREAM_DRAW },
+
+		};
+
+		for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usages); ++usageNdx)
+		{
+			tcu::TestCaseGroup* const usageGroup = new tcu::TestCaseGroup(m_testCtx, usages[usageNdx].name, usages[usageNdx].description);
+			modifyAfterUseGroup->addChild(usageGroup);
+
+			usageGroup->addChild(new ModifyAfterWithBufferDataCase		(m_context, "buffer_data",							"Respecify buffer contents after use",					minBufferSize, maxBufferSize, usages[usageNdx].usage, 0));
+			usageGroup->addChild(new ModifyAfterWithBufferDataCase		(m_context, "buffer_data_different_size",			"Respecify buffer contents and size after use",			minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithBufferDataCase::FLAG_RESPECIFY_SIZE));
+			usageGroup->addChild(new ModifyAfterWithBufferDataCase		(m_context, "buffer_data_repeated",					"Respecify buffer contents after upload and use",		minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithBufferDataCase::FLAG_UPLOAD_REPEATED));
+
+			usageGroup->addChild(new ModifyAfterWithBufferSubDataCase	(m_context, "buffer_sub_data_full",					"Respecify buffer contents after use",					minBufferSize, maxBufferSize, usages[usageNdx].usage, 0));
+			usageGroup->addChild(new ModifyAfterWithBufferSubDataCase	(m_context, "buffer_sub_data_partial",				"Respecify buffer contents partially use",				minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithBufferSubDataCase::FLAG_PARTIAL));
+			usageGroup->addChild(new ModifyAfterWithBufferSubDataCase	(m_context, "buffer_sub_data_full_repeated",		"Respecify buffer contents after upload and use",		minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithBufferSubDataCase::FLAG_UPLOAD_REPEATED));
+			usageGroup->addChild(new ModifyAfterWithBufferSubDataCase	(m_context, "buffer_sub_data_partial_repeated",		"Respecify buffer contents partially upload and use",	minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithBufferSubDataCase::FLAG_UPLOAD_REPEATED | ModifyAfterWithBufferSubDataCase::FLAG_PARTIAL));
+
+			usageGroup->addChild(new ModifyAfterWithMapBufferRangeCase	(m_context, "map_flag_write_full",					"Respecify buffer contents after use",					minBufferSize, maxBufferSize, usages[usageNdx].usage, 0,												GL_MAP_WRITE_BIT));
+			usageGroup->addChild(new ModifyAfterWithMapBufferRangeCase	(m_context, "map_flag_write_partial",				"Respecify buffer contents partially after use",		minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithMapBufferRangeCase::FLAG_PARTIAL,	GL_MAP_WRITE_BIT));
+			usageGroup->addChild(new ModifyAfterWithMapBufferRangeCase	(m_context, "map_flag_read_write_full",				"Respecify buffer contents after use",					minBufferSize, maxBufferSize, usages[usageNdx].usage, 0,												GL_MAP_READ_BIT | GL_MAP_WRITE_BIT));
+			usageGroup->addChild(new ModifyAfterWithMapBufferRangeCase	(m_context, "map_flag_read_write_partial",			"Respecify buffer contents partially after use",		minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithMapBufferRangeCase::FLAG_PARTIAL,	GL_MAP_READ_BIT | GL_MAP_WRITE_BIT));
+			usageGroup->addChild(new ModifyAfterWithMapBufferRangeCase	(m_context, "map_flag_invalidate_range_full",		"Respecify buffer contents after use",					minBufferSize, maxBufferSize, usages[usageNdx].usage, 0,												GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT));
+			usageGroup->addChild(new ModifyAfterWithMapBufferRangeCase	(m_context, "map_flag_invalidate_range_partial",	"Respecify buffer contents partially after use",		minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithMapBufferRangeCase::FLAG_PARTIAL,	GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT));
+			usageGroup->addChild(new ModifyAfterWithMapBufferRangeCase	(m_context, "map_flag_invalidate_buffer_full",		"Respecify buffer contents after use",					minBufferSize, maxBufferSize, usages[usageNdx].usage, 0,												GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT));
+			usageGroup->addChild(new ModifyAfterWithMapBufferRangeCase	(m_context, "map_flag_invalidate_buffer_partial",	"Respecify buffer contents partially after use",		minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithMapBufferRangeCase::FLAG_PARTIAL,	GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT));
+			usageGroup->addChild(new ModifyAfterWithMapBufferRangeCase	(m_context, "map_flag_unsynchronized_full",			"Respecify buffer contents after use",					minBufferSize, maxBufferSize, usages[usageNdx].usage, 0,												GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT));
+			usageGroup->addChild(new ModifyAfterWithMapBufferRangeCase	(m_context, "map_flag_unsynchronized_partial",		"Respecify buffer contents partially after use",		minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithMapBufferRangeCase::FLAG_PARTIAL,	GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT));
+
+			usageGroup->addChild(new ModifyAfterWithMapBufferFlushCase	(m_context, "map_flag_flush_explicit_full",			"Respecify buffer contents after use",					minBufferSize, maxBufferSize, usages[usageNdx].usage, 0,												GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT));
+			usageGroup->addChild(new ModifyAfterWithMapBufferFlushCase	(m_context, "map_flag_flush_explicit_partial",		"Respecify buffer contents partially after use",		minBufferSize, maxBufferSize, usages[usageNdx].usage, ModifyAfterWithMapBufferFlushCase::FLAG_PARTIAL,	GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT));
+		}
+	}
+
+	// .render_after_upload
+	{
+		// .reference
+		{
+			tcu::TestCaseGroup* const renderReferenceGroup = new tcu::TestCaseGroup(m_testCtx, "reference", "Baseline results");
+			renderAfterUploadGroup->addChild(renderReferenceGroup);
+
+			// .draw
+			{
+				tcu::TestCaseGroup* const drawGroup = new tcu::TestCaseGroup(m_testCtx, "draw", "Time usage of functions with non-modified buffers");
+				renderReferenceGroup->addChild(drawGroup);
+
+				// Time consumed by readPixels
+				drawGroup->addChild(new ReferenceReadPixelsTimeCase	(m_context, "read_pixels",		"Measure time consumed by readPixels() function call"));
+
+				// Time consumed by rendering
+				drawGroup->addChild(new ReferenceRenderTimeCase		(m_context, "draw_arrays",		"Measure time consumed by drawArrays() function call",		DRAWMETHOD_DRAW_ARRAYS));
+				drawGroup->addChild(new ReferenceRenderTimeCase		(m_context, "draw_elements",	"Measure time consumed by drawElements() function call",	DRAWMETHOD_DRAW_ELEMENTS));
+			}
+
+			// .draw_upload_draw
+			{
+				static const struct
+				{
+					const char*		name;
+					const char*		description;
+					DrawMethod		drawMethod;
+					TargetBuffer	targetBuffer;
+					bool			partial;
+				} uploadTargets[] =
+				{
+					{
+						"draw_arrays_upload_vertices",
+						"Measure time consumed by drawArrays, vertex attribute upload, another drawArrays, and readPixels function calls.",
+						DRAWMETHOD_DRAW_ARRAYS,
+						TARGETBUFFER_VERTEX,
+						false
+					},
+					{
+						"draw_arrays_upload_vertices_partial",
+						"Measure time consumed by drawArrays, partial vertex attribute upload, another drawArrays, and readPixels function calls.",
+						DRAWMETHOD_DRAW_ARRAYS,
+						TARGETBUFFER_VERTEX,
+						true
+					},
+					{
+						"draw_elements_upload_vertices",
+						"Measure time consumed by drawElements, vertex attribute upload, another drawElements, and readPixels function calls.",
+						DRAWMETHOD_DRAW_ELEMENTS,
+						TARGETBUFFER_VERTEX,
+						false
+					},
+					{
+						"draw_elements_upload_indices",
+						"Measure time consumed by drawElements, index upload, another drawElements, and readPixels function calls.",
+						DRAWMETHOD_DRAW_ELEMENTS,
+						TARGETBUFFER_INDEX,
+						false
+					},
+					{
+						"draw_elements_upload_indices_partial",
+						"Measure time consumed by drawElements, partial index upload, another drawElements, and readPixels function calls.",
+						DRAWMETHOD_DRAW_ELEMENTS,
+						TARGETBUFFER_INDEX,
+						true
+					},
+				};
+				static const struct
+				{
+					const char*							name;
+					const char*							description;
+					UploadMethod						uploadMethod;
+					BufferInUseRenderTimeCase::MapFlags	mapFlags;
+					bool								supportsPartialUpload;
+				} uploadMethods[] =
+				{
+					{ "buffer_data",						"bufferData",		UPLOADMETHOD_BUFFER_DATA,		BufferInUseRenderTimeCase::MAPFLAG_NONE,				false	},
+					{ "buffer_sub_data",					"bufferSubData",	UPLOADMETHOD_BUFFER_SUB_DATA,	BufferInUseRenderTimeCase::MAPFLAG_NONE,				true	},
+					{ "map_buffer_range_invalidate_range",	"mapBufferRange",	UPLOADMETHOD_MAP_BUFFER_RANGE,	BufferInUseRenderTimeCase::MAPFLAG_INVALIDATE_RANGE,	true	},
+					{ "map_buffer_range_invalidate_buffer",	"mapBufferRange",	UPLOADMETHOD_MAP_BUFFER_RANGE,	BufferInUseRenderTimeCase::MAPFLAG_INVALIDATE_BUFFER,	false	},
+				};
+
+				tcu::TestCaseGroup* const drawUploadDrawGroup = new tcu::TestCaseGroup(m_testCtx, "draw_upload_draw", "Time usage of functions draw, upload and another draw");
+				renderReferenceGroup->addChild(drawUploadDrawGroup);
+
+				for (int uploadTargetNdx = 0; uploadTargetNdx < DE_LENGTH_OF_ARRAY(uploadTargets); ++uploadTargetNdx)
+				for (int uploadMethodNdx = 0; uploadMethodNdx < DE_LENGTH_OF_ARRAY(uploadMethods); ++uploadMethodNdx)
+				{
+					const std::string name = std::string() + uploadTargets[uploadTargetNdx].name + "_with_" + uploadMethods[uploadMethodNdx].name;
+
+					if (uploadTargets[uploadTargetNdx].partial && !uploadMethods[uploadMethodNdx].supportsPartialUpload)
+						continue;
+
+					drawUploadDrawGroup->addChild(new BufferInUseRenderTimeCase(m_context,
+																				name.c_str(),
+																				uploadTargets[uploadTargetNdx].description,
+																				uploadTargets[uploadTargetNdx].drawMethod,
+																				uploadMethods[uploadMethodNdx].mapFlags,
+																				uploadTargets[uploadTargetNdx].targetBuffer,
+																				uploadMethods[uploadMethodNdx].uploadMethod,
+																				(uploadTargets[uploadTargetNdx].partial) ? (UPLOADRANGE_PARTIAL) : (UPLOADRANGE_FULL),
+																				BufferInUseRenderTimeCase::UPLOADBUFFERTARGET_DIFFERENT_BUFFER));
+				}
+			}
+		}
+
+		// .upload_unrelated_and_draw
+		{
+			static const struct
+			{
+				const char*		name;
+				const char*		description;
+				DrawMethod		drawMethod;
+			} drawMethods[] =
+			{
+				{ "draw_arrays",	"drawArrays",	DRAWMETHOD_DRAW_ARRAYS		},
+				{ "draw_elements",	"drawElements",	DRAWMETHOD_DRAW_ELEMENTS	},
+			};
+
+			static const struct
+			{
+				const char*		name;
+				UploadMethod	uploadMethod;
+			} uploadMethods[] =
+			{
+				{ "buffer_data",		UPLOADMETHOD_BUFFER_DATA		},
+				{ "buffer_sub_data",	UPLOADMETHOD_BUFFER_SUB_DATA	},
+				{ "map_buffer_range",	UPLOADMETHOD_MAP_BUFFER_RANGE	},
+			};
+
+			tcu::TestCaseGroup* const uploadUnrelatedGroup = new tcu::TestCaseGroup(m_testCtx, "upload_unrelated_and_draw", "Time usage of functions after an unrelated upload");
+			renderAfterUploadGroup->addChild(uploadUnrelatedGroup);
+
+			for (int drawMethodNdx = 0; drawMethodNdx < DE_LENGTH_OF_ARRAY(drawMethods); ++drawMethodNdx)
+			for (int uploadMethodNdx = 0; uploadMethodNdx < DE_LENGTH_OF_ARRAY(uploadMethods); ++uploadMethodNdx)
+			{
+				const std::string name = std::string() + drawMethods[drawMethodNdx].name + "_upload_unrelated_with_" + uploadMethods[uploadMethodNdx].name;
+				const std::string desc = std::string() + "Measure time consumed by " + drawMethods[drawMethodNdx].description + " function call after an unrelated upload";
+
+				// Time consumed by rendering command after an unrelated upload
+
+				uploadUnrelatedGroup->addChild(new UnrelatedUploadRenderTimeCase(m_context, name.c_str(), desc.c_str(), drawMethods[drawMethodNdx].drawMethod, uploadMethods[uploadMethodNdx].uploadMethod));
+			}
+		}
+
+		// .upload_and_draw
+		{
+			static const struct
+			{
+				const char*			name;
+				const char*			description;
+				BufferState			bufferState;
+				UnrelatedBufferType	unrelatedBuffer;
+				bool				supportsPartialUpload;
+			} bufferConfigs[] =
+			{
+				{ "used_buffer",						"Upload to an used buffer",											BUFFERSTATE_EXISTING,	UNRELATEDBUFFERTYPE_NONE,	true	},
+				{ "new_buffer",							"Upload to a new buffer",											BUFFERSTATE_NEW,		UNRELATEDBUFFERTYPE_NONE,	false	},
+				{ "used_buffer_and_unrelated_upload",	"Upload to an used buffer and an unrelated buffer and then draw",	BUFFERSTATE_EXISTING,	UNRELATEDBUFFERTYPE_VERTEX,	true	},
+				{ "new_buffer_and_unrelated_upload",	"Upload to a new buffer and an unrelated buffer and then draw",		BUFFERSTATE_NEW,		UNRELATEDBUFFERTYPE_VERTEX,	false	},
+			};
+
+			tcu::TestCaseGroup* const uploadAndDrawGroup = new tcu::TestCaseGroup(m_testCtx, "upload_and_draw", "Time usage of rendering functions with modified buffers");
+			renderAfterUploadGroup->addChild(uploadAndDrawGroup);
+
+			// .used_buffer
+			// .new_buffer
+			// .used_buffer_and_unrelated_upload
+			// .new_buffer_and_unrelated_upload
+			for (int stateNdx = 0; stateNdx < DE_LENGTH_OF_ARRAY(bufferConfigs); ++stateNdx)
+			{
+				static const struct
+				{
+					const char*		name;
+					const char*		description;
+					DrawMethod		drawMethod;
+					TargetBuffer	targetBuffer;
+					bool			partial;
+				} uploadTargets[] =
+				{
+					{
+						"draw_arrays_upload_vertices",
+						"Measure time consumed by vertex attribute upload, drawArrays, and readPixels function calls",
+						DRAWMETHOD_DRAW_ARRAYS,
+						TARGETBUFFER_VERTEX,
+						false
+					},
+					{
+						"draw_arrays_upload_vertices_partial",
+						"Measure time consumed by partial vertex attribute upload, drawArrays, and readPixels function calls",
+						DRAWMETHOD_DRAW_ARRAYS,
+						TARGETBUFFER_VERTEX,
+						true
+					},
+					{
+						"draw_elements_upload_vertices",
+						"Measure time consumed by vertex attribute upload, drawElements, and readPixels function calls",
+						DRAWMETHOD_DRAW_ELEMENTS,
+						TARGETBUFFER_VERTEX,
+						false
+					},
+					{
+						"draw_elements_upload_indices",
+						"Measure time consumed by index upload, drawElements, and readPixels function calls",
+						DRAWMETHOD_DRAW_ELEMENTS,
+						TARGETBUFFER_INDEX,
+						false
+					},
+					{
+						"draw_elements_upload_indices_partial",
+						"Measure time consumed by partial index upload, drawElements, and readPixels function calls",
+						DRAWMETHOD_DRAW_ELEMENTS,
+						TARGETBUFFER_INDEX,
+						true
+					},
+				};
+				static const struct
+				{
+					const char*		name;
+					const char*		description;
+					UploadMethod	uploadMethod;
+					bool			supportsPartialUpload;
+				} uploadMethods[] =
+				{
+					{ "buffer_data",		"bufferData",		UPLOADMETHOD_BUFFER_DATA,		false	},
+					{ "buffer_sub_data",	"bufferSubData",	UPLOADMETHOD_BUFFER_SUB_DATA,	true	},
+					{ "map_buffer_range",	"mapBufferRange",	UPLOADMETHOD_MAP_BUFFER_RANGE,	true	},
+				};
+
+				tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, bufferConfigs[stateNdx].name, bufferConfigs[stateNdx].description);
+				uploadAndDrawGroup->addChild(group);
+
+				for (int uploadTargetNdx = 0; uploadTargetNdx < DE_LENGTH_OF_ARRAY(uploadTargets); ++uploadTargetNdx)
+				for (int uploadMethodNdx = 0; uploadMethodNdx < DE_LENGTH_OF_ARRAY(uploadMethods); ++uploadMethodNdx)
+				{
+					const std::string name = std::string() + uploadTargets[uploadTargetNdx].name + "_with_" + uploadMethods[uploadMethodNdx].name;
+
+					if (uploadTargets[uploadTargetNdx].partial && !uploadMethods[uploadMethodNdx].supportsPartialUpload)
+						continue;
+					if (uploadTargets[uploadTargetNdx].partial && !bufferConfigs[stateNdx].supportsPartialUpload)
+						continue;
+
+					// Don't log unrelated buffer information to samples if there is no such buffer
+
+					if (bufferConfigs[stateNdx].unrelatedBuffer == UNRELATEDBUFFERTYPE_NONE)
+					{
+						typedef UploadRenderReadDuration				SampleType;
+						typedef GenericUploadRenderTimeCase<SampleType>	TestType;
+
+						group->addChild(new TestType(m_context,
+													 name.c_str(),
+													 uploadTargets[uploadTargetNdx].description,
+													 uploadTargets[uploadTargetNdx].drawMethod,
+													 uploadTargets[uploadTargetNdx].targetBuffer,
+													 uploadMethods[uploadMethodNdx].uploadMethod,
+													 bufferConfigs[stateNdx].bufferState,
+													 (uploadTargets[uploadTargetNdx].partial) ? (UPLOADRANGE_PARTIAL) : (UPLOADRANGE_FULL),
+													 bufferConfigs[stateNdx].unrelatedBuffer));
+					}
+					else
+					{
+						typedef UploadRenderReadDurationWithUnrelatedUploadSize	SampleType;
+						typedef GenericUploadRenderTimeCase<SampleType>			TestType;
+
+						group->addChild(new TestType(m_context,
+													 name.c_str(),
+													 uploadTargets[uploadTargetNdx].description,
+													 uploadTargets[uploadTargetNdx].drawMethod,
+													 uploadTargets[uploadTargetNdx].targetBuffer,
+													 uploadMethods[uploadMethodNdx].uploadMethod,
+													 bufferConfigs[stateNdx].bufferState,
+													 (uploadTargets[uploadTargetNdx].partial) ? (UPLOADRANGE_PARTIAL) : (UPLOADRANGE_FULL),
+													 bufferConfigs[stateNdx].unrelatedBuffer));
+					}
+				}
+			}
+		}
+
+		// .draw_modify_draw
+		{
+			static const struct
+			{
+				const char*		name;
+				const char*		description;
+				DrawMethod		drawMethod;
+				TargetBuffer	targetBuffer;
+				bool			partial;
+			} uploadTargets[] =
+			{
+				{
+					"draw_arrays_upload_vertices",
+					"Measure time consumed by drawArrays, vertex attribute upload, another drawArrays, and readPixels function calls.",
+					DRAWMETHOD_DRAW_ARRAYS,
+					TARGETBUFFER_VERTEX,
+					false
+				},
+				{
+					"draw_arrays_upload_vertices_partial",
+					"Measure time consumed by drawArrays, partial vertex attribute upload, another drawArrays, and readPixels function calls.",
+					DRAWMETHOD_DRAW_ARRAYS,
+					TARGETBUFFER_VERTEX,
+					true
+				},
+				{
+					"draw_elements_upload_vertices",
+					"Measure time consumed by drawElements, vertex attribute upload, another drawElements, and readPixels function calls.",
+					DRAWMETHOD_DRAW_ELEMENTS,
+					TARGETBUFFER_VERTEX,
+					false
+				},
+				{
+					"draw_elements_upload_indices",
+					"Measure time consumed by drawElements, index upload, another drawElements, and readPixels function calls.",
+					DRAWMETHOD_DRAW_ELEMENTS,
+					TARGETBUFFER_INDEX,
+					false
+				},
+				{
+					"draw_elements_upload_indices_partial",
+					"Measure time consumed by drawElements, partial index upload, another drawElements, and readPixels function calls.",
+					DRAWMETHOD_DRAW_ELEMENTS,
+					TARGETBUFFER_INDEX,
+					true
+				},
+			};
+			static const struct
+			{
+				const char*							name;
+				const char*							description;
+				UploadMethod						uploadMethod;
+				BufferInUseRenderTimeCase::MapFlags	mapFlags;
+				bool								supportsPartialUpload;
+			} uploadMethods[] =
+			{
+				{ "buffer_data",						"bufferData",		UPLOADMETHOD_BUFFER_DATA,		BufferInUseRenderTimeCase::MAPFLAG_NONE,				false	},
+				{ "buffer_sub_data",					"bufferSubData",	UPLOADMETHOD_BUFFER_SUB_DATA,	BufferInUseRenderTimeCase::MAPFLAG_NONE,				true	},
+				{ "map_buffer_range_invalidate_range",	"mapBufferRange",	UPLOADMETHOD_MAP_BUFFER_RANGE,	BufferInUseRenderTimeCase::MAPFLAG_INVALIDATE_RANGE,	true	},
+				{ "map_buffer_range_invalidate_buffer",	"mapBufferRange",	UPLOADMETHOD_MAP_BUFFER_RANGE,	BufferInUseRenderTimeCase::MAPFLAG_INVALIDATE_BUFFER,	false	},
+			};
+
+			tcu::TestCaseGroup* const drawModifyDrawGroup = new tcu::TestCaseGroup(m_testCtx, "draw_modify_draw", "Time used in rendering functions with modified buffers while original buffer is still in use");
+			renderAfterUploadGroup->addChild(drawModifyDrawGroup);
+
+			for (int uploadTargetNdx = 0; uploadTargetNdx < DE_LENGTH_OF_ARRAY(uploadTargets); ++uploadTargetNdx)
+			for (int uploadMethodNdx = 0; uploadMethodNdx < DE_LENGTH_OF_ARRAY(uploadMethods); ++uploadMethodNdx)
+			{
+				const std::string name = std::string() + uploadTargets[uploadTargetNdx].name + "_with_" + uploadMethods[uploadMethodNdx].name;
+
+				if (uploadTargets[uploadTargetNdx].partial && !uploadMethods[uploadMethodNdx].supportsPartialUpload)
+					continue;
+
+				drawModifyDrawGroup->addChild(new BufferInUseRenderTimeCase(m_context,
+																			name.c_str(),
+																			uploadTargets[uploadTargetNdx].description,
+																			uploadTargets[uploadTargetNdx].drawMethod,
+																			uploadMethods[uploadMethodNdx].mapFlags,
+																			uploadTargets[uploadTargetNdx].targetBuffer,
+																			uploadMethods[uploadMethodNdx].uploadMethod,
+																			(uploadTargets[uploadTargetNdx].partial) ? (UPLOADRANGE_PARTIAL) : (UPLOADRANGE_FULL),
+																			BufferInUseRenderTimeCase::UPLOADBUFFERTARGET_SAME_BUFFER));
+			}
+		}
+
+		// .upload_wait_draw
+		{
+			static const struct
+			{
+				const char*	name;
+				const char*	description;
+				BufferState	bufferState;
+			} bufferStates[] =
+			{
+				{ "new_buffer",		"Uploading to just generated name",	BUFFERSTATE_NEW			},
+				{ "used_buffer",	"Uploading to a used buffer",		BUFFERSTATE_EXISTING	},
+			};
+			static const struct
+			{
+				const char*		name;
+				const char*		description;
+				DrawMethod		drawMethod;
+				TargetBuffer	targetBuffer;
+			} uploadTargets[] =
+			{
+				{ "draw_arrays_vertices",	"Upload vertex data, draw with drawArrays",		DRAWMETHOD_DRAW_ARRAYS,		TARGETBUFFER_VERTEX	},
+				{ "draw_elements_vertices",	"Upload vertex data, draw with drawElements",	DRAWMETHOD_DRAW_ELEMENTS,	TARGETBUFFER_VERTEX	},
+				{ "draw_elements_indices",	"Upload index data, draw with drawElements",	DRAWMETHOD_DRAW_ELEMENTS,	TARGETBUFFER_INDEX	},
+			};
+			static const struct
+			{
+				const char*		name;
+				const char*		description;
+				UploadMethod	uploadMethod;
+			} uploadMethods[] =
+			{
+				{ "buffer_data",		"bufferData",		UPLOADMETHOD_BUFFER_DATA		},
+				{ "buffer_sub_data",	"bufferSubData",	UPLOADMETHOD_BUFFER_SUB_DATA	},
+				{ "map_buffer_range",	"mapBufferRange",	UPLOADMETHOD_MAP_BUFFER_RANGE	},
+			};
+
+			tcu::TestCaseGroup* const uploadSwapDrawGroup = new tcu::TestCaseGroup(m_testCtx, "upload_wait_draw", "Time used in rendering functions after a buffer upload N frames ago");
+			renderAfterUploadGroup->addChild(uploadSwapDrawGroup);
+
+			for (int bufferStateNdx = 0; bufferStateNdx < DE_LENGTH_OF_ARRAY(bufferStates); ++bufferStateNdx)
+			{
+				tcu::TestCaseGroup* const bufferGroup = new tcu::TestCaseGroup(m_testCtx, bufferStates[bufferStateNdx].name, bufferStates[bufferStateNdx].description);
+				uploadSwapDrawGroup->addChild(bufferGroup);
+
+				for (int uploadTargetNdx = 0; uploadTargetNdx < DE_LENGTH_OF_ARRAY(uploadTargets); ++uploadTargetNdx)
+				for (int uploadMethodNdx = 0; uploadMethodNdx < DE_LENGTH_OF_ARRAY(uploadMethods); ++uploadMethodNdx)
+				{
+					const std::string name = std::string() + uploadTargets[uploadTargetNdx].name + "_with_" + uploadMethods[uploadMethodNdx].name;
+
+					bufferGroup->addChild(new UploadWaitDrawCase(m_context,
+																 name.c_str(),
+																 uploadTargets[uploadTargetNdx].description,
+																 uploadTargets[uploadTargetNdx].drawMethod,
+																 uploadTargets[uploadTargetNdx].targetBuffer,
+																 uploadMethods[uploadMethodNdx].uploadMethod,
+																 bufferStates[bufferStateNdx].bufferState));
+				}
+			}
+		}
+	}
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pBufferDataUploadTests.hpp b/modules/gles3/performance/es3pBufferDataUploadTests.hpp
new file mode 100644
index 0000000..236b5f4
--- /dev/null
+++ b/modules/gles3/performance/es3pBufferDataUploadTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PBUFFERDATAUPLOADTESTS_HPP
+#define _ES3PBUFFERDATAUPLOADTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer data upload performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class BufferDataUploadTests : public TestCaseGroup
+{
+public:
+							BufferDataUploadTests	(Context& context);
+							~BufferDataUploadTests	(void);
+
+	void					init					(void);
+
+private:
+							BufferDataUploadTests	(const BufferDataUploadTests& other);
+	BufferDataUploadTests&	operator=				(const BufferDataUploadTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PBUFFERDATAUPLOADTESTS_HPP
diff --git a/modules/gles3/performance/es3pPerformanceTests.cpp b/modules/gles3/performance/es3pPerformanceTests.cpp
new file mode 100644
index 0000000..f7377cc
--- /dev/null
+++ b/modules/gles3/performance/es3pPerformanceTests.cpp
@@ -0,0 +1,138 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pPerformanceTests.hpp"
+
+#include "es3pBlendTests.hpp"
+#include "es3pTextureFormatTests.hpp"
+#include "es3pTextureFilteringTests.hpp"
+#include "es3pTextureCountTests.hpp"
+#include "es3pShaderOperatorTests.hpp"
+#include "es3pShaderControlStatementTests.hpp"
+#include "es3pShaderCompilerTests.hpp"
+#include "es3pShaderOptimizationTests.hpp"
+#include "es3pRedundantStateChangeTests.hpp"
+#include "es3pStateChangeCallTests.hpp"
+#include "es3pStateChangeTests.hpp"
+#include "es3pBufferDataUploadTests.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+// TextureTestGroup
+
+class TextureTestGroup : public TestCaseGroup
+{
+public:
+	TextureTestGroup (Context& context)
+		: TestCaseGroup(context, "texture", "Texture Performance Tests")
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new TextureFormatTests		(m_context));
+		addChild(new TextureFilteringTests	(m_context));
+		addChild(new TextureCountTests		(m_context));
+	}
+};
+
+// ShadersTestGroup
+
+class ShadersTestGroup : public TestCaseGroup
+{
+public:
+	ShadersTestGroup (Context& context)
+		: TestCaseGroup(context, "shader", "Shader Performance Tests")
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new ShaderOperatorTests			(m_context));
+		addChild(new ShaderControlStatementTests	(m_context));
+	}
+};
+
+// APITests
+
+class APITests : public TestCaseGroup
+{
+public:
+	APITests (Context& context)
+		: TestCaseGroup(context, "api", "API Performance Tests")
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new StateChangeCallTests		(m_context));
+		addChild(new StateChangeTests			(m_context));
+		addChild(new RedundantStateChangeTests	(m_context));
+	}
+};
+
+// BufferTestGroup
+
+class BufferTestGroup : public TestCaseGroup
+{
+public:
+	BufferTestGroup (Context& context)
+		: TestCaseGroup(context, "buffer", "Buffer Performance Tests")
+	{
+	}
+
+	virtual void init (void)
+	{
+		addChild(new BufferDataUploadTests	(m_context));
+	}
+};
+
+// PerformanceTests
+
+PerformanceTests::PerformanceTests (Context& context)
+	: TestCaseGroup(context, "performance", "Performance Tests")
+{
+}
+
+PerformanceTests::~PerformanceTests (void)
+{
+}
+
+void PerformanceTests::init (void)
+{
+	addChild(new BlendTests				(m_context));
+	addChild(new TextureTestGroup		(m_context));
+	addChild(new ShadersTestGroup		(m_context));
+	addChild(new ShaderCompilerTests	(m_context));
+	addChild(new APITests				(m_context));
+	addChild(new BufferTestGroup		(m_context));
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pPerformanceTests.hpp b/modules/gles3/performance/es3pPerformanceTests.hpp
new file mode 100644
index 0000000..9400129
--- /dev/null
+++ b/modules/gles3/performance/es3pPerformanceTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PPERFORMANCETESTS_HPP
+#define _ES3PPERFORMANCETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class PerformanceTests : public TestCaseGroup
+{
+public:
+						PerformanceTests	(Context& context);
+						~PerformanceTests	(void);
+
+	void				init				(void);
+
+private:
+						PerformanceTests	(const PerformanceTests& other);
+	PerformanceTests&	operator=			(const PerformanceTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PPERFORMANCETESTS_HPP
diff --git a/modules/gles3/performance/es3pRedundantStateChangeTests.cpp b/modules/gles3/performance/es3pRedundantStateChangeTests.cpp
new file mode 100644
index 0000000..70bde0a
--- /dev/null
+++ b/modules/gles3/performance/es3pRedundantStateChangeTests.cpp
@@ -0,0 +1,1511 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Redundant state change performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pRedundantStateChangeTests.hpp"
+#include "glsStateChangePerfTestCases.hpp"
+#include "gluShaderProgram.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+using namespace glw; // GL types
+
+namespace
+{
+
+enum
+{
+	VIEWPORT_WIDTH	= 24,
+	VIEWPORT_HEIGHT	= 24
+};
+
+class RedundantStateChangeCase : public gls::StateChangePerformanceCase
+{
+public:
+					RedundantStateChangeCase	(Context& context, int drawCallCount, int triangleCount, bool drawArrays, bool useIndexBuffer, const char* name, const char* description);
+					~RedundantStateChangeCase	(void);
+
+protected:
+	virtual void	renderTest					(const glw::Functions& gl);
+	virtual void	renderReference				(const glw::Functions& gl);
+	virtual void	changeState					(const glw::Functions& gl) = 0;
+};
+
+RedundantStateChangeCase::RedundantStateChangeCase (Context& context, int drawCallCount, int triangleCount, bool drawArrays, bool useIndexBuffer, const char* name, const char* description)
+	: gls::StateChangePerformanceCase(context.getTestContext(), context.getRenderContext(), name, description,
+									  (useIndexBuffer	? DRAWTYPE_INDEXED_BUFFER	:
+									   drawArrays		? DRAWTYPE_NOT_INDEXED		:
+														  DRAWTYPE_INDEXED_USER_PTR), drawCallCount, triangleCount)
+{
+	DE_ASSERT(!useIndexBuffer || !drawArrays);
+}
+
+RedundantStateChangeCase::~RedundantStateChangeCase (void)
+{
+}
+
+void RedundantStateChangeCase::renderTest (const glw::Functions& gl)
+{
+	for (int callNdx = 0; callNdx < m_callCount; callNdx++)
+	{
+		changeState(gl);
+		callDraw(gl);
+	}
+}
+
+void RedundantStateChangeCase::renderReference (const glw::Functions& gl)
+{
+	changeState(gl);
+
+	for (int callNdx = 0; callNdx < m_callCount; callNdx++)
+		callDraw(gl);
+}
+
+} // anonymous
+
+RedundantStateChangeTests::RedundantStateChangeTests (Context& context)
+	: TestCaseGroup(context, "redundant_state_change_draw", "Test performance with redundant sate changes between rendering.")
+{
+}
+
+RedundantStateChangeTests::~RedundantStateChangeTests (void)
+{
+}
+
+#define MACRO_BLOCK(...) __VA_ARGS__
+
+#define ADD_TESTCASE(NAME, DESC, DRAWARRAYS, INDEXBUFFER, INIT_FUNC, CHANGE_FUNC)\
+do {\
+	class RedundantStateChangeCase_ ## NAME : public RedundantStateChangeCase\
+	{\
+	public:\
+			RedundantStateChangeCase_ ## NAME (Context& context, int drawCallCount, int triangleCount, const char* name, const char* description)\
+				: RedundantStateChangeCase(context, drawCallCount, triangleCount, (DRAWARRAYS), (INDEXBUFFER), name, description)\
+			{}\
+		virtual void setupInitialState (const glw::Functions& gl)\
+		{\
+			INIT_FUNC\
+		}\
+		virtual void changeState (const glw::Functions& gl)\
+		{\
+			CHANGE_FUNC\
+		}\
+	};\
+	manySmallCallsGroup->addChild	(new RedundantStateChangeCase_ ## NAME (m_context,1000,2,#NAME,(DESC)));\
+	fewBigCallsGroup->addChild		(new RedundantStateChangeCase_ ## NAME (m_context,10,200,#NAME,(DESC)));\
+} while (0);
+
+void RedundantStateChangeTests::init (void)
+{
+	tcu::TestCaseGroup* const	manySmallCallsGroup	= new tcu::TestCaseGroup(m_testCtx, "many_small_calls",	"1000 calls, 2 triangles in each");
+	tcu::TestCaseGroup* const	fewBigCallsGroup	= new tcu::TestCaseGroup(m_testCtx, "few_big_calls",	"10 calls, 200 triangles in each");
+
+	addChild(manySmallCallsGroup);
+	addChild(fewBigCallsGroup);
+
+	ADD_TESTCASE(blend, "Enable/Disable blending.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_BLEND);
+		})
+	)
+
+	ADD_TESTCASE(depth_test, "Enable/Disable depth test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.depthFunc(GL_LEQUAL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthFunc()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_DEPTH_TEST);
+		})
+	)
+
+	ADD_TESTCASE(stencil_test, "Enable/Disable stencil test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_STENCIL_TEST);
+		})
+	)
+
+	ADD_TESTCASE(scissor_test, "Enable/Disable scissor test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.scissor(2, 3, 12, 13);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glScissor()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_SCISSOR_TEST);
+		})
+	)
+
+	ADD_TESTCASE(dither, "Enable/Disable dithering.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_DITHER);
+		})
+	)
+
+	ADD_TESTCASE(culling, "Enable/Disable culling.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.frontFace(GL_CW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glFrontFace()");
+
+			gl.cullFace(GL_FRONT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glCullFace()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_CULL_FACE);
+		})
+	)
+
+	ADD_TESTCASE(rasterizer_discard, "Disable RASTERIZER_DISCARD.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.disable(GL_RASTERIZER_DISCARD);
+		})
+	)
+
+	ADD_TESTCASE(primitive_restart_fixed_index, "Enable PRIMITIVE_RESTART_FIXED_INDEX.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.enable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
+		})
+	)
+
+	ADD_TESTCASE(depth_func, "Change depth func.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_DEPTH_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.depthFunc(GL_GEQUAL);
+		})
+	)
+
+
+	ADD_TESTCASE(depth_mask, "Toggle depth mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_DEPTH_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.depthFunc(GL_LEQUAL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthFunc()");
+		}),
+		MACRO_BLOCK({
+			gl.depthMask(GL_FALSE);
+		})
+	)
+
+	ADD_TESTCASE(depth_rangef, "Change depth range.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.depthRangef(0.0f, 1.0f);
+		})
+	)
+
+	ADD_TESTCASE(blend_equation, "Change blend equation.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.blendEquation(GL_FUNC_SUBTRACT);
+		})
+	)
+
+	ADD_TESTCASE(blend_func, "Change blend function.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+		})
+	)
+
+	ADD_TESTCASE(polygon_offset, "Change polygon offset.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_POLYGON_OFFSET_FILL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.polygonOffset(0.0f, 0.0f);
+		})
+	)
+
+	ADD_TESTCASE(sample_coverage, "Sample coverage.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.sampleCoverage(0.25f, GL_TRUE);
+		})
+	)
+
+	ADD_TESTCASE(viewport, "Change viewport.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.viewport(10, 11, 5, 6);
+		})
+	)
+
+	ADD_TESTCASE(scissor, "Change scissor box.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_SCISSOR_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.scissor(17, 13, 5, 8);
+		})
+	)
+
+	ADD_TESTCASE(color_mask, "Change color mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.colorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
+		})
+	)
+
+	ADD_TESTCASE(cull_face, "Change culling mode.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_CULL_FACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.cullFace(GL_FRONT);
+		})
+	)
+
+	ADD_TESTCASE(front_face, "Change front face.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_CULL_FACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			gl.frontFace(GL_CCW);
+		})
+	)
+
+	ADD_TESTCASE(stencil_mask, "Change stencil mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			gl.stencilMask(0xDD);
+		})
+	)
+
+	ADD_TESTCASE(stencil_func, "Change stencil func.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			gl.stencilFunc(GL_LEQUAL, 0, 0xFF);
+		})
+	)
+
+	ADD_TESTCASE(stencil_op, "Change stencil op.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+		})
+	)
+
+	ADD_TESTCASE(bind_array_buffer, "Change array buffer and refresh vertex attrib pointer.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.bindAttribLocation(m_programs[0]->getProgram(), 0, "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindAttribLocation()");
+			gl.linkProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glLinkProgram()");
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+			gl.enableVertexAttribArray(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+		})
+	)
+
+	ADD_TESTCASE(element_array_buffer, "Change element array buffer.",
+		false,
+		true,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireIndexBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+		}),
+		MACRO_BLOCK({
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffers[0]);
+		})
+	)
+
+	ADD_TESTCASE(bind_texture, "Change texture binding.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+		})
+	)
+
+	ADD_TESTCASE(use_program, "Change used program.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.useProgram(m_programs[0]->getProgram());
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_min_filter, "Change texture parameter min filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_mag_filter, "Change texture parameter mag filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_wrap, "Change texture parameter wrap filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+		})
+	)
+
+	ADD_TESTCASE(bind_framebuffer, "Change framebuffer.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requireFramebuffers(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()");
+		}),
+		MACRO_BLOCK({
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffers[0]);
+		})
+	)
+
+	ADD_TESTCASE(blend_color, "Change blend color.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.blendFunc(GL_CONSTANT_COLOR, GL_CONSTANT_COLOR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBlendFunc()");
+		}),
+		MACRO_BLOCK({
+			gl.blendColor(0.75f, 0.75f, 0.75f, 0.75f);
+		})
+	)
+
+	ADD_TESTCASE(sampler, "Change sampler binding.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+			requireSamplers(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.bindSampler(0, m_samplers[0]);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Sampler setup");
+		}),
+		MACRO_BLOCK({
+			gl.bindSampler(0, m_samplers[0]);
+		})
+	)
+
+	ADD_TESTCASE(bind_vertex_array, "Change vertex array binding.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+			requireVertexArrays(1);
+
+			gl.bindAttribLocation(m_programs[0]->getProgram(), 0, "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindAttribLocation()");
+			gl.linkProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glLinkProgram()");
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+			gl.bindVertexArray(m_vertexArrays[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindVertexArray()");
+			gl.enableVertexAttribArray(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			gl.bindVertexArray(m_vertexArrays[0]);
+		})
+	)
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pRedundantStateChangeTests.hpp b/modules/gles3/performance/es3pRedundantStateChangeTests.hpp
new file mode 100644
index 0000000..489c0b7
--- /dev/null
+++ b/modules/gles3/performance/es3pRedundantStateChangeTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PREDUNDANTSTATECHANGETESTS_HPP
+#define _ES3PREDUNDANTSTATECHANGETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Redundant state change performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class RedundantStateChangeTests : public TestCaseGroup
+{
+public:
+								RedundantStateChangeTests	(Context& context);
+								~RedundantStateChangeTests	(void);
+
+	void						init						(void);
+
+private:
+								RedundantStateChangeTests	(const RedundantStateChangeTests& other);
+	RedundantStateChangeTests&	operator=					(const RedundantStateChangeTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PREDUNDANTSTATECHANGETESTS_HPP
diff --git a/modules/gles3/performance/es3pShaderCompilationCases.cpp b/modules/gles3/performance/es3pShaderCompilationCases.cpp
new file mode 100644
index 0000000..c646d8f
--- /dev/null
+++ b/modules/gles3/performance/es3pShaderCompilationCases.cpp
@@ -0,0 +1,3152 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader compilation performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pShaderCompilationCases.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuPlatform.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuCPUWarmup.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluTexture.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deClock.h"
+#include "deMath.h"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <map>
+#include <algorithm>
+#include <limits>
+#include <iomanip>
+
+using tcu::TestLog;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::Mat3;
+using tcu::Mat4;
+using std::string;
+using std::vector;
+using namespace glw; // GL types
+
+namespace deqp
+{
+
+namespace gles3
+{
+
+namespace Performance
+{
+
+static const bool	WARMUP_CPU_AT_BEGINNING_OF_CASE					= false;
+static const bool	WARMUP_CPU_BEFORE_EACH_MEASUREMENT				= true;
+
+static const int	MAX_VIEWPORT_WIDTH								= 64;
+static const int	MAX_VIEWPORT_HEIGHT								= 64;
+
+static const int	DEFAULT_MINIMUM_MEASUREMENT_COUNT				= 15;
+static const float	RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD	= 0.05f;
+
+// Texture size for the light shader and texture lookup shader cases.
+static const int	TEXTURE_WIDTH									= 64;
+static const int	TEXTURE_HEIGHT									= 64;
+
+template <typename T>
+inline string toStringWithPadding (T value, int minLength)
+{
+	std::ostringstream s;
+	s << std::setfill('0') << std::setw(minLength) << value;
+	return s.str();
+}
+
+// Add some whitespace and comments to str. They should depend on uniqueNumber.
+static string strWithWhiteSpaceAndComments (const string& str, deUint32 uniqueNumber)
+{
+	string res("");
+
+	// Find the first newline.
+	int firstLineEndNdx = 0;
+	while (firstLineEndNdx < (int)str.size() && str[firstLineEndNdx] != '\n')
+	{
+		res += str[firstLineEndNdx];
+		firstLineEndNdx++;
+	}
+	res += '\n';
+	DE_ASSERT(firstLineEndNdx < (int)str.size());
+
+	// Add the whitespaces and comments just after the first line.
+
+	de::Random		rnd		(uniqueNumber);
+	int				numWS	= rnd.getInt(10, 20);
+
+	for (int i = 0; i < numWS; i++)
+		res += " \t\n"[rnd.getInt(0, 2)];
+
+	res += "/* unique comment " + de::toString(uniqueNumber) + " */\n";
+	res += "// unique comment " + de::toString(uniqueNumber) + "\n";
+
+	for (int i = 0; i < numWS; i++)
+		res += " \t\n"[rnd.getInt(0, 2)];
+
+	// Add the rest of the string.
+	res.append(&str.c_str()[firstLineEndNdx + 1]);
+
+	return res;
+}
+
+//! Helper for computing relative magnitudes while avoiding division by zero.
+static float hackySafeRelativeResult (float x, float y)
+{
+	// \note A possible case is that x is standard deviation, and y is average
+	//		 (or similarly for median or some such). So, if y is 0, that
+	//		 probably means that x is also 0(ish) (because in practice we're
+	//		 dealing with non-negative values, in which case an average of 0
+	//		 implies that the samples are all 0 - note that the same isn't
+	//		 strictly true for things like median) so a relative result of 0
+	//		 wouldn't be that far from the truth.
+	return y == 0.0f ? 0.0f : x/y;
+}
+
+template <typename T>
+static float vectorFloatAverage (const vector<T>& v)
+{
+	DE_ASSERT(!v.empty());
+	float result = 0.0f;
+	for (int i = 0; i < (int)v.size(); i++)
+		result += (float)v[i];
+	return result / (float)v.size();
+}
+
+template <typename T>
+static float vectorFloatMedian (const vector<T>& v)
+{
+	DE_ASSERT(!v.empty());
+	vector<T> temp = v;
+	std::sort(temp.begin(), temp.end());
+	return temp.size() % 2 == 0
+		   ? 0.5f * ((float)temp[temp.size()/2-1] + (float)temp[temp.size()/2])
+		   : (float)temp[temp.size()/2];
+}
+
+template <typename T>
+static float vectorFloatMinimum (const vector<T>& v)
+{
+	DE_ASSERT(!v.empty());
+	return (float)*std::min_element(v.begin(), v.end());
+}
+
+template <typename T>
+static float vectorFloatMaximum (const vector<T>& v)
+{
+	DE_ASSERT(!v.empty());
+	return (float)*std::max_element(v.begin(), v.end());
+}
+
+template <typename T>
+static float vectorFloatStandardDeviation (const vector<T>& v)
+{
+	float average	= vectorFloatAverage(v);
+	float result	= 0.0f;
+	for (int i = 0; i < (int)v.size(); i++)
+	{
+		float d = (float)v[i] - average;
+		result += d*d;
+	}
+	return deFloatSqrt(result/(float)v.size());
+}
+
+template <typename T>
+static float vectorFloatRelativeStandardDeviation (const vector<T>& v)
+{
+	return hackySafeRelativeResult(vectorFloatStandardDeviation(v), vectorFloatAverage(v));
+}
+
+template <typename T>
+static float vectorFloatMedianAbsoluteDeviation (const vector<T>& v)
+{
+	float			median				= vectorFloatMedian(v);
+	vector<float>	absoluteDeviations	(v.size());
+
+	for (int i = 0; i < (int)v.size(); i++)
+		absoluteDeviations[i] = deFloatAbs((float)v[i] - median);
+
+	return vectorFloatMedian(absoluteDeviations);
+}
+
+template <typename T>
+static float vectorFloatRelativeMedianAbsoluteDeviation (const vector<T>& v)
+{
+	return hackySafeRelativeResult(vectorFloatMedianAbsoluteDeviation(v), vectorFloatMedian(v));
+}
+
+template <typename T>
+static float vectorFloatMaximumMinusMinimum (const vector<T>& v)
+{
+	return vectorFloatMaximum(v) - vectorFloatMinimum(v);
+}
+
+template <typename T>
+static float vectorFloatRelativeMaximumMinusMinimum (const vector<T>& v)
+{
+	return hackySafeRelativeResult(vectorFloatMaximumMinusMinimum(v), vectorFloatMaximum(v));
+}
+
+template <typename T>
+static vector<T> vectorLowestPercentage (const vector<T>& v, float factor)
+{
+	DE_ASSERT(0.0f < factor && factor <= 1.0f);
+
+	int			targetSize	= (int)(deFloatCeil(factor*(float)v.size()));
+	vector<T>	temp		= v;
+	std::sort(temp.begin(), temp.end());
+
+	while ((int)temp.size() > targetSize)
+		temp.pop_back();
+
+	return temp;
+}
+
+template <typename T>
+static float vectorFloatFirstQuartile (const vector<T>& v)
+{
+	return vectorFloatMedian(vectorLowestPercentage(v, 0.5f));
+}
+
+// Helper function for combining 4 tcu::Vec4's into one tcu::Vector<float, 16>.
+static tcu::Vector<float, 16> combineVec4ToVec16 (const Vec4& a0, const Vec4& a1, const Vec4& a2, const Vec4& a3)
+{
+	tcu::Vector<float, 16> result;
+
+	for (int vecNdx = 0; vecNdx < 4; vecNdx++)
+	{
+		const Vec4& srcVec = vecNdx == 0 ? a0 : vecNdx == 1 ? a1 : vecNdx == 2 ? a2 : a3;
+		for (int i = 0; i < 4; i++)
+			result[vecNdx*4 + i] = srcVec[i];
+	}
+
+	return result;
+}
+
+// Helper function for extending an n-sized (n <= 16) vector to a 16-sized vector (padded with zeros).
+template <int Size>
+static tcu::Vector<float, 16> vecTo16 (const tcu::Vector<float, Size>& vec)
+{
+	DE_STATIC_ASSERT(Size <= 16);
+
+	tcu::Vector<float, 16> res(0.0f);
+
+	for (int i = 0; i < Size; i++)
+		res[i] = vec[i];
+
+	return res;
+}
+
+// Helper function for extending an n-sized (n <= 16) array to a 16-sized vector (padded with zeros).
+template <int Size>
+static tcu::Vector<float, 16> arrTo16 (const tcu::Array<float, Size>& arr)
+{
+	DE_STATIC_ASSERT(Size <= 16);
+
+	tcu::Vector<float, 16> res(0.0f);
+
+	for(int i = 0; i < Size; i++)
+		res[i] = arr[i];
+
+	return res;
+}
+
+static string getShaderInfoLog (const glw::Functions& gl, deUint32 shader)
+{
+	string			result;
+	int				infoLogLen;
+	vector<char>	infoLogBuf;
+
+	gl.getShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen);
+	infoLogBuf.resize(infoLogLen + 1);
+	gl.getShaderInfoLog(shader, infoLogLen + 1, DE_NULL, &infoLogBuf[0]);
+	result = &infoLogBuf[0];
+
+	return result;
+}
+
+static string getProgramInfoLog (const glw::Functions& gl, deUint32 program)
+{
+	string			result;
+	int				infoLogLen;
+	vector<char>	infoLogBuf;
+
+	gl.getProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
+	infoLogBuf.resize(infoLogLen + 1);
+	gl.getProgramInfoLog(program, infoLogLen + 1, DE_NULL, &infoLogBuf[0]);
+	result = &infoLogBuf[0];
+
+	return result;
+}
+
+enum LightType
+{
+	LIGHT_DIRECTIONAL = 0,
+	LIGHT_POINT,
+
+	LIGHT_LAST,
+};
+
+enum LoopType
+{
+	LOOP_TYPE_STATIC = 0,
+	LOOP_TYPE_UNIFORM,
+	LOOP_TYPE_DYNAMIC,
+
+	LOOP_LAST
+};
+
+// For texture lookup cases: which texture lookups are inside a conditional statement.
+enum ConditionalUsage
+{
+	CONDITIONAL_USAGE_NONE = 0,		// No conditional statements.
+	CONDITIONAL_USAGE_FIRST_HALF,	// First numLookUps/2 lookups are inside a conditional statement.
+	CONDITIONAL_USAGE_EVERY_OTHER,	// First, third etc. lookups are inside conditional statements.
+
+	CONDITIONAL_USAGE_LAST
+};
+
+enum ConditionalType
+{
+	CONDITIONAL_TYPE_STATIC = 0,
+	CONDITIONAL_TYPE_UNIFORM,
+	CONDITIONAL_TYPE_DYNAMIC,
+
+	CONDITIONAL_TYPE_LAST
+};
+
+// For the invalid shader compilation tests; what kind of invalidity a shader shall contain.
+enum ShaderValidity
+{
+	SHADER_VALIDITY_VALID = 0,
+	SHADER_VALIDITY_INVALID_CHAR,
+	SHADER_VALIDITY_SEMANTIC_ERROR,
+
+	SHADER_VALIDITY_LAST
+};
+
+class ShaderCompilerCase : public TestCase
+{
+public:
+	struct AttribSpec
+	{
+		string					name;
+		tcu::Vector<float, 16>	value;
+
+		AttribSpec (const string& n, const tcu::Vector<float, 16>& v) : name(n), value(v) {}
+	};
+
+	struct UniformSpec
+	{
+		enum Type
+		{
+			TYPE_FLOAT = 0,
+			TYPE_VEC2,
+			TYPE_VEC3,
+			TYPE_VEC4,
+
+			TYPE_MAT3,
+			TYPE_MAT4,
+
+			TYPE_TEXTURE_UNIT,
+
+			TYPE_LAST
+		};
+
+		string					name;
+		Type					type;
+		tcu::Vector<float, 16>	value;
+
+		UniformSpec (const string& n, Type t, float v)							: name(n), type(t), value(v) {}
+		UniformSpec (const string& n, Type t, const tcu::Vector<float, 16>& v)	: name(n), type(t), value(v) {}
+	};
+
+								ShaderCompilerCase		(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments);
+								~ShaderCompilerCase		(void);
+
+	void						init					(void);
+
+	IterateResult				iterate					(void);
+
+protected:
+	struct ProgramContext
+	{
+		string					vertShaderSource;
+		string					fragShaderSource;
+		vector<AttribSpec>		vertexAttributes;
+		vector<UniformSpec>		uniforms;
+	};
+
+	deUint32					getSpecializationID		(int measurementNdx) const;		// Return an ID that depends on the case ID, current measurement index and time; used to specialize attribute names etc. (avoid shader caching).
+	virtual ProgramContext		generateShaderData		(int measurementNdx) const = 0;	// Generate shader sources and inputs. Attribute etc. names depend on above name specialization.
+
+private:
+	struct Measurement
+	{
+		// \note All times in microseconds. 32-bit integers would probably suffice (would need over an hour of test case runtime to overflow), but better safe than sorry.
+		deInt64 sourceSetTime;
+		deInt64 vertexCompileTime;
+		deInt64 fragmentCompileTime;
+		deInt64 programLinkTime;
+		deInt64 firstInputSetTime;
+		deInt64 firstDrawTime;
+
+		deInt64 secondInputSetTime;
+		deInt64 secondDrawTime;
+
+		deInt64 firstPhase				(void) const { return sourceSetTime + vertexCompileTime + fragmentCompileTime + programLinkTime + firstInputSetTime + firstDrawTime; }
+		deInt64 secondPhase				(void) const { return secondInputSetTime + secondDrawTime; }
+
+		deInt64 totalTimeWithoutDraw	(void) const { return firstPhase() - de::min(secondPhase(), firstInputSetTime + firstDrawTime); }
+
+		Measurement (deInt64 sourceSetTime_,
+					 deInt64 vertexCompileTime_,
+					 deInt64 fragmentCompileTime_,
+					 deInt64 programLinkTime_,
+					 deInt64 firstInputSetTime_,
+					 deInt64 firstDrawTime_,
+					 deInt64 secondInputSetTime_,
+					 deInt64 secondDrawTime_)
+			: sourceSetTime			(sourceSetTime_)
+			, vertexCompileTime		(vertexCompileTime_)
+			, fragmentCompileTime	(fragmentCompileTime_)
+			, programLinkTime		(programLinkTime_)
+			, firstInputSetTime		(firstInputSetTime_)
+			, firstDrawTime			(firstDrawTime_)
+			, secondInputSetTime	(secondInputSetTime_)
+			, secondDrawTime		(secondDrawTime_)
+		{
+		}
+	};
+
+	struct ShadersAndProgram
+	{
+		deUint32 vertShader;
+		deUint32 fragShader;
+		deUint32 program;
+	};
+
+	struct Logs
+	{
+		string vert;
+		string frag;
+		string link;
+	};
+
+	struct BuildInfo
+	{
+		bool vertCompileSuccess;
+		bool fragCompileSuccess;
+		bool linkSuccess;
+
+		Logs logs;
+	};
+
+	ShadersAndProgram			createShadersAndProgram		(void) const;
+	void						setShaderSources			(deUint32 vertShader, deUint32 fragShader, const ProgramContext&) const;
+	bool						compileShader				(deUint32 shader) const;
+	bool						linkAndUseProgram			(deUint32 program) const;
+	void						setShaderInputs				(deUint32 program, const ProgramContext&) const;							// Set attribute pointers and uniforms.
+	void						draw						(void) const;																// Clear, draw and finish.
+	void						cleanup						(const ShadersAndProgram&, const ProgramContext&, bool linkSuccess) const;	// Do GL deinitializations.
+
+	Logs						getLogs						(const ShadersAndProgram&) const;
+	void						logProgramData				(const BuildInfo&, const ProgramContext&) const;
+	bool						goodEnoughMeasurements		(const vector<Measurement>& measurements) const;
+
+	int							m_viewportWidth;
+	int							m_viewportHeight;
+
+	bool						m_avoidCache;				// If true, avoid caching between measurements as well (and not only between test cases).
+	bool						m_addWhitespaceAndComments;	// If true, add random whitespace and comments to the source (good caching should ignore those).
+	deUint32					m_startHash;				// A hash from case id and time, at the time of construction.
+
+	int							m_minimumMeasurementCount;
+	int							m_maximumMeasurementCount;
+};
+
+class ShaderCompilerLightCase : public ShaderCompilerCase
+{
+public:
+							ShaderCompilerLightCase		(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, int numLights, LightType lightType);
+							~ShaderCompilerLightCase	(void);
+
+	void					init						(void);
+	void					deinit						(void);
+
+protected:
+	ProgramContext			generateShaderData			(int measurementNdx) const;
+
+private:
+	int						m_numLights;
+	bool					m_isVertexCase;
+	LightType				m_lightType;
+	glu::Texture2D*			m_texture;
+};
+
+class ShaderCompilerTextureCase : public ShaderCompilerCase
+{
+public:
+									ShaderCompilerTextureCase	(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType);
+									~ShaderCompilerTextureCase	(void);
+
+	void							init						(void);
+	void							deinit						(void);
+
+protected:
+	ProgramContext					generateShaderData			(int measurementNdx) const;
+
+private:
+	int								m_numLookups;
+	vector<glu::Texture2D*>			m_textures;
+	ConditionalUsage				m_conditionalUsage;
+	ConditionalType					m_conditionalType;
+};
+
+class ShaderCompilerLoopCase : public ShaderCompilerCase
+{
+public:
+						ShaderCompilerLoopCase	(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, LoopType type, int numLoopIterations, int nestingDepth);
+						~ShaderCompilerLoopCase	(void);
+
+protected:
+	ProgramContext		generateShaderData		(int measurementNdx) const;
+
+private:
+	int					m_numLoopIterations;
+	int					m_nestingDepth;
+	bool				m_isVertexCase;
+	LoopType			m_type;
+};
+
+class ShaderCompilerOperCase : public ShaderCompilerCase
+{
+public:
+						ShaderCompilerOperCase	(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, const char* oper, int numOperations);
+						~ShaderCompilerOperCase	(void);
+
+protected:
+	ProgramContext		generateShaderData		(int measurementNdx) const;
+
+private:
+	string				m_oper;
+	int					m_numOperations;
+	bool				m_isVertexCase;
+};
+
+class ShaderCompilerMandelbrotCase : public ShaderCompilerCase
+{
+public:
+						ShaderCompilerMandelbrotCase	(Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, int numFractalIterations);
+						~ShaderCompilerMandelbrotCase	(void);
+
+protected:
+	ProgramContext		generateShaderData				(int measurementNdx) const;
+
+private:
+	int					m_numFractalIterations;
+};
+
+class InvalidShaderCompilerCase : public TestCase
+{
+public:
+	// \note Similar to the ShaderValidity enum, but doesn't have a VALID type.
+	enum InvalidityType
+	{
+		INVALIDITY_INVALID_CHAR = 0,
+		INVALIDITY_SEMANTIC_ERROR,
+
+		INVALIDITY_LAST
+	};
+
+						InvalidShaderCompilerCase	(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType);
+						~InvalidShaderCompilerCase	(void);
+
+	IterateResult		iterate						(void);
+
+protected:
+	struct ProgramContext
+	{
+		string vertShaderSource;
+		string fragShaderSource;
+	};
+
+	deUint32				getSpecializationID		(int measurementNdx) const;			// Return an ID that depends on the case ID, current measurement index and time; used to specialize attribute names etc. (avoid shader caching).
+	virtual ProgramContext	generateShaderSources	(int measurementNdx) const = 0;		// Generate shader sources. Attribute etc. names depend on above name specialization.
+
+	InvalidityType			m_invalidityType;
+
+private:
+	struct Measurement
+	{
+		// \note All times in microseconds. 32-bit integers would probably suffice (would need over an hour of test case runtime to overflow), but better safe than sorry.
+		deInt64 sourceSetTime;
+		deInt64 vertexCompileTime;
+		deInt64 fragmentCompileTime;
+
+		deInt64 totalTime (void) const { return sourceSetTime + vertexCompileTime + fragmentCompileTime; }
+
+		Measurement (deInt64 sourceSetTime_,
+					 deInt64 vertexCompileTime_,
+					 deInt64 fragmentCompileTime_)
+			: sourceSetTime			(sourceSetTime_)
+			, vertexCompileTime		(vertexCompileTime_)
+			, fragmentCompileTime	(fragmentCompileTime_)
+		{
+		}
+	};
+
+	struct Shaders
+	{
+		deUint32 vertShader;
+		deUint32 fragShader;
+	};
+
+	struct Logs
+	{
+		string vert;
+		string frag;
+	};
+
+	struct BuildInfo
+	{
+		bool vertCompileSuccess;
+		bool fragCompileSuccess;
+
+		Logs logs;
+	};
+
+	Shaders						createShaders			(void) const;
+	void						setShaderSources		(const Shaders&, const ProgramContext&) const;
+	bool						compileShader			(deUint32 shader) const;
+	void						cleanup					(const Shaders&) const;
+
+	Logs						getLogs					(const Shaders&) const;
+	void						logProgramData			(const BuildInfo&, const ProgramContext&) const;
+	bool						goodEnoughMeasurements	(const vector<Measurement>& measurements) const;
+
+	deUint32					m_startHash; // A hash from case id and time, at the time of construction.
+
+	int							m_minimumMeasurementCount;
+	int							m_maximumMeasurementCount;
+};
+
+class InvalidShaderCompilerLightCase : public InvalidShaderCompilerCase
+{
+public:
+							InvalidShaderCompilerLightCase	(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool isVertexCase, int numLights, LightType lightType);
+							~InvalidShaderCompilerLightCase	(void);
+
+protected:
+	ProgramContext			generateShaderSources			(int measurementNdx) const;
+
+private:
+	bool					m_isVertexCase;
+	int						m_numLights;
+	LightType				m_lightType;
+};
+
+class InvalidShaderCompilerTextureCase : public InvalidShaderCompilerCase
+{
+public:
+							InvalidShaderCompilerTextureCase	(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType);
+							~InvalidShaderCompilerTextureCase	(void);
+
+protected:
+	ProgramContext			generateShaderSources				(int measurementNdx) const;
+
+private:
+	int						m_numLookups;
+	ConditionalUsage		m_conditionalUsage;
+	ConditionalType			m_conditionalType;
+};
+
+class InvalidShaderCompilerLoopCase : public InvalidShaderCompilerCase
+{
+public:
+						InvalidShaderCompilerLoopCase	(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool , LoopType type, int numLoopIterations, int nestingDepth);
+						~InvalidShaderCompilerLoopCase	(void);
+
+protected:
+	ProgramContext		generateShaderSources			(int measurementNdx) const;
+
+private:
+	bool				m_isVertexCase;
+	int					m_numLoopIterations;
+	int					m_nestingDepth;
+	LoopType			m_type;
+};
+
+class InvalidShaderCompilerOperCase : public InvalidShaderCompilerCase
+{
+public:
+						InvalidShaderCompilerOperCase	(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool isVertexCase, const char* oper, int numOperations);
+						~InvalidShaderCompilerOperCase	(void);
+
+protected:
+	ProgramContext		generateShaderSources			(int measurementNdx) const;
+
+private:
+	bool				m_isVertexCase;
+	string				m_oper;
+	int					m_numOperations;
+};
+
+class InvalidShaderCompilerMandelbrotCase : public InvalidShaderCompilerCase
+{
+public:
+						InvalidShaderCompilerMandelbrotCase		(Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, int numFractalIterations);
+						~InvalidShaderCompilerMandelbrotCase	(void);
+
+protected:
+	ProgramContext		generateShaderSources					(int measurementNdx) const;
+
+private:
+	int					m_numFractalIterations;
+};
+
+static string getNameSpecialization (deUint32 id)
+{
+	return "_" + toStringWithPadding(id, 10);
+}
+
+// Substitute StringTemplate parameters for attribute/uniform/varying name and constant expression specialization as well as possible shader compilation error causes.
+static string specializeShaderSource (const string& shaderSourceTemplate, deUint32 cacheAvoidanceID, ShaderValidity validity)
+{
+	std::map<string, string> params;
+	params["NAME_SPEC"]			= getNameSpecialization(cacheAvoidanceID);
+	params["FLOAT01"]			= de::floatToString((float)cacheAvoidanceID / (float)(std::numeric_limits<deUint32>::max()), 6);
+	params["SEMANTIC_ERROR"]	= validity != SHADER_VALIDITY_SEMANTIC_ERROR	? "" : "\tfloat invalid = sin(1.0, 2.0);\n";
+	params["INVALID_CHAR"]		= validity != SHADER_VALIDITY_INVALID_CHAR		? "" : "@\n"; // \note Some implementations crash when the invalid character is the last character in the source, so use newline.
+
+	return tcu::StringTemplate(shaderSourceTemplate).specialize(params);
+}
+
+// Function for generating the vertex shader of a (directional or point) light case.
+static string lightVertexTemplate (int numLights, bool isVertexCase, LightType lightType)
+{
+	string resultTemplate;
+
+	resultTemplate +=
+		"#version 300 es\n"
+		"in highp vec4 a_position${NAME_SPEC};\n"
+		"in mediump vec3 a_normal${NAME_SPEC};\n"
+		"in mediump vec4 a_texCoord0${NAME_SPEC};\n"
+		"uniform mediump vec3 u_material_ambientColor${NAME_SPEC};\n"
+		"uniform mediump vec4 u_material_diffuseColor${NAME_SPEC};\n"
+		"uniform mediump vec3 u_material_emissiveColor${NAME_SPEC};\n"
+		"uniform mediump vec3 u_material_specularColor${NAME_SPEC};\n"
+		"uniform mediump float u_material_shininess${NAME_SPEC};\n";
+
+	for (int lightNdx = 0; lightNdx < numLights; lightNdx++)
+	{
+		string ndxStr = de::toString(lightNdx);
+
+		resultTemplate +=
+			"uniform mediump vec3 u_light" + ndxStr + "_color${NAME_SPEC};\n"
+			"uniform mediump vec3 u_light" + ndxStr + "_direction${NAME_SPEC};\n";
+
+		if (lightType == LIGHT_POINT)
+			resultTemplate +=
+				"uniform mediump vec4 u_light" + ndxStr + "_position${NAME_SPEC};\n"
+				"uniform mediump float u_light" + ndxStr + "_constantAttenuation${NAME_SPEC};\n"
+				"uniform mediump float u_light" + ndxStr + "_linearAttenuation${NAME_SPEC};\n"
+				"uniform mediump float u_light" + ndxStr + "_quadraticAttenuation${NAME_SPEC};\n";
+	}
+
+	resultTemplate +=
+		"uniform highp mat4 u_mvpMatrix${NAME_SPEC};\n"
+		"uniform highp mat4 u_modelViewMatrix${NAME_SPEC};\n"
+		"uniform mediump mat3 u_normalMatrix${NAME_SPEC};\n"
+		"uniform mediump mat4 u_texCoordMatrix0${NAME_SPEC};\n"
+		"out mediump vec4 v_color${NAME_SPEC};\n"
+		"out mediump vec2 v_texCoord0${NAME_SPEC};\n";
+
+	if (!isVertexCase)
+	{
+		resultTemplate += "out mediump vec3 v_eyeNormal${NAME_SPEC};\n";
+
+		if (lightType == LIGHT_POINT)
+			resultTemplate +=
+				"out mediump vec3 v_directionToLight${NAME_SPEC}[" + de::toString(numLights) + "];\n"
+				"out mediump float v_distanceToLight${NAME_SPEC}[" + de::toString(numLights) + "];\n";
+	}
+
+	resultTemplate +=
+		"mediump vec3 direction (mediump vec4 from, mediump vec4 to)\n"
+		"{\n"
+		"	return vec3(to.xyz * from.w - from.xyz * to.w);\n"
+		"}\n"
+		"\n"
+		"mediump vec3 computeLighting (\n"
+		"	mediump vec3 directionToLight,\n"
+		"	mediump vec3 halfVector,\n"
+		"	mediump vec3 normal,\n"
+		"	mediump vec3 lightColor,\n"
+		"	mediump vec3 diffuseColor,\n"
+		"	mediump vec3 specularColor,\n"
+		"	mediump float shininess)\n"
+		"{\n"
+		"	mediump float normalDotDirection  = max(dot(normal, directionToLight), 0.0);\n"
+		"	mediump vec3  color               = normalDotDirection * diffuseColor * lightColor;\n"
+		"\n"
+		"	if (normalDotDirection != 0.0)\n"
+		"		color += pow(max(dot(normal, halfVector), 0.0), shininess) * specularColor * lightColor;\n"
+		"\n"
+		"	return color;\n"
+		"}\n"
+		"\n";
+
+	if (lightType == LIGHT_POINT)
+		resultTemplate +=
+			"mediump float computeDistanceAttenuation (mediump float distToLight, mediump float constAtt, mediump float linearAtt, mediump float quadraticAtt)\n"
+			"{\n"
+			"	return 1.0 / (constAtt + linearAtt * distToLight + quadraticAtt * distToLight * distToLight);\n"
+			"}\n"
+			"\n";
+
+	resultTemplate +=
+		"void main (void)\n"
+		"{\n"
+		"	highp vec4 position = a_position${NAME_SPEC};\n"
+		"	highp vec3 normal = a_normal${NAME_SPEC};\n"
+		"	gl_Position = u_mvpMatrix${NAME_SPEC} * position * (0.95 + 0.05*${FLOAT01});\n"
+		"	v_texCoord0${NAME_SPEC} = (u_texCoordMatrix0${NAME_SPEC} * a_texCoord0${NAME_SPEC}).xy;\n"
+		"	mediump vec4 color = vec4(u_material_emissiveColor${NAME_SPEC}, u_material_diffuseColor${NAME_SPEC}.a);\n"
+		"\n"
+		"	highp vec4 eyePosition = u_modelViewMatrix${NAME_SPEC} * position;\n"
+		"	mediump vec3 eyeNormal = normalize(u_normalMatrix${NAME_SPEC} * normal);\n";
+
+	if (!isVertexCase)
+		resultTemplate += "\tv_eyeNormal${NAME_SPEC} = eyeNormal;\n";
+
+	resultTemplate += "\n";
+
+	for (int lightNdx = 0; lightNdx < numLights; lightNdx++)
+	{
+		string ndxStr = de::toString(lightNdx);
+
+		resultTemplate +=
+			"	/* Light " + ndxStr + " */\n";
+
+		if (lightType == LIGHT_POINT)
+		{
+			resultTemplate +=
+				"	mediump float distanceToLight" + ndxStr + " = distance(eyePosition, u_light" + ndxStr + "_position${NAME_SPEC});\n"
+				"	mediump vec3 directionToLight" + ndxStr + " = normalize(direction(eyePosition, u_light" + ndxStr + "_position${NAME_SPEC}));\n";
+
+			if (isVertexCase)
+				resultTemplate +=
+					"	mediump vec3 halfVector" + ndxStr + " = normalize(directionToLight" + ndxStr + " + vec3(0.0, 0.0, 1.0));\n"
+					"	color.rgb += computeLighting(directionToLight" + ndxStr + ", halfVector" + ndxStr + ", eyeNormal, u_light" + ndxStr + "_color${NAME_SPEC}, u_material_diffuseColor${NAME_SPEC}.rgb, "
+					"u_material_specularColor${NAME_SPEC}, u_material_shininess${NAME_SPEC}) * computeDistanceAttenuation(distanceToLight" + ndxStr + ", u_light" + ndxStr + "_constantAttenuation${NAME_SPEC}, "
+					"u_light" + ndxStr + "_linearAttenuation${NAME_SPEC}, u_light" + ndxStr + "_quadraticAttenuation${NAME_SPEC});\n";
+			else
+				resultTemplate +=
+					"	v_directionToLight${NAME_SPEC}[" + ndxStr + "] = directionToLight" + ndxStr + ";\n"
+					"	v_distanceToLight${NAME_SPEC}[" + ndxStr + "]  = distanceToLight" + ndxStr + ";\n";
+		}
+		else if (lightType == LIGHT_DIRECTIONAL)
+		{
+			if (isVertexCase)
+				resultTemplate +=
+					"	mediump vec3 directionToLight" + ndxStr + " = -u_light" + ndxStr + "_direction${NAME_SPEC};\n"
+					"	mediump vec3 halfVector" + ndxStr + " = normalize(directionToLight" + ndxStr + " + vec3(0.0, 0.0, 1.0));\n"
+					"	color.rgb += computeLighting(directionToLight" + ndxStr + ", halfVector" + ndxStr + ", eyeNormal, u_light" + ndxStr + "_color${NAME_SPEC}, u_material_diffuseColor${NAME_SPEC}.rgb, u_material_specularColor${NAME_SPEC}, u_material_shininess${NAME_SPEC});\n";
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		resultTemplate += "\n";
+	}
+
+	resultTemplate +=
+		"	v_color${NAME_SPEC} = color;\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the fragment shader of a (directional or point) light case.
+static string lightFragmentTemplate (int numLights, bool isVertexCase, LightType lightType)
+{
+	string resultTemplate;
+
+	resultTemplate +=
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_color;\n";
+
+	if (!isVertexCase)
+	{
+		resultTemplate +=
+			"uniform mediump vec3 u_material_ambientColor${NAME_SPEC};\n"
+			"uniform mediump vec4 u_material_diffuseColor${NAME_SPEC};\n"
+			"uniform mediump vec3 u_material_emissiveColor${NAME_SPEC};\n"
+			"uniform mediump vec3 u_material_specularColor${NAME_SPEC};\n"
+			"uniform mediump float u_material_shininess${NAME_SPEC};\n";
+
+		for (int lightNdx = 0; lightNdx < numLights; lightNdx++)
+		{
+			string ndxStr = de::toString(lightNdx);
+
+			resultTemplate +=
+				"uniform mediump vec3 u_light" + ndxStr + "_color${NAME_SPEC};\n"
+				"uniform mediump vec3 u_light" + ndxStr + "_direction${NAME_SPEC};\n";
+
+			if (lightType == LIGHT_POINT)
+				resultTemplate +=
+					"uniform mediump vec4 u_light" + ndxStr + "_position${NAME_SPEC};\n"
+					"uniform mediump float u_light" + ndxStr + "_constantAttenuation${NAME_SPEC};\n"
+					"uniform mediump float u_light" + ndxStr + "_linearAttenuation${NAME_SPEC};\n"
+					"uniform mediump float u_light" + ndxStr + "_quadraticAttenuation${NAME_SPEC};\n";
+		}
+	}
+
+	resultTemplate +=
+		"uniform sampler2D u_sampler0${NAME_SPEC};\n"
+		"in mediump vec4 v_color${NAME_SPEC};\n"
+		"in mediump vec2 v_texCoord0${NAME_SPEC};\n";
+
+	if (!isVertexCase)
+	{
+		resultTemplate +=
+			"in mediump vec3 v_eyeNormal${NAME_SPEC};\n";
+
+		if (lightType == LIGHT_POINT)
+			resultTemplate +=
+				"in mediump vec3 v_directionToLight${NAME_SPEC}[" + de::toString(numLights) + "];\n"
+				"in mediump float v_distanceToLight${NAME_SPEC}[" + de::toString(numLights) + "];\n";
+
+		resultTemplate +=
+			"mediump vec3 direction (mediump vec4 from, mediump vec4 to)\n"
+			"{\n"
+			"	return vec3(to.xyz * from.w - from.xyz * to.w);\n"
+			"}\n"
+			"\n";
+
+		resultTemplate +=
+			"mediump vec3 computeLighting (\n"
+			"	mediump vec3 directionToLight,\n"
+			"	mediump vec3 halfVector,\n"
+			"	mediump vec3 normal,\n"
+			"	mediump vec3 lightColor,\n"
+			"	mediump vec3 diffuseColor,\n"
+			"	mediump vec3 specularColor,\n"
+			"	mediump float shininess)\n"
+			"{\n"
+			"	mediump float normalDotDirection  = max(dot(normal, directionToLight), 0.0);\n"
+			"	mediump vec3  color               = normalDotDirection * diffuseColor * lightColor;\n"
+			"\n"
+			"	if (normalDotDirection != 0.0)\n"
+			"		color += pow(max(dot(normal, halfVector), 0.0), shininess) * specularColor * lightColor;\n"
+			"\n"
+			"	return color;\n"
+			"}\n"
+			"\n";
+
+		if (lightType == LIGHT_POINT)
+			resultTemplate +=
+				"mediump float computeDistanceAttenuation (mediump float distToLight, mediump float constAtt, mediump float linearAtt, mediump float quadraticAtt)\n"
+				"{\n"
+				"	return 1.0 / (constAtt + linearAtt * distToLight + quadraticAtt * distToLight * distToLight);\n"
+				"}\n"
+				"\n";
+	}
+
+	resultTemplate +=
+		"void main (void)\n"
+		"{\n"
+		"	mediump vec2 texCoord0 = v_texCoord0${NAME_SPEC}.xy;\n"
+		"	mediump vec4 color = v_color${NAME_SPEC};\n";
+
+	if (!isVertexCase)
+	{
+		resultTemplate +=
+			"	mediump vec3 eyeNormal = normalize(v_eyeNormal${NAME_SPEC});\n"
+			"\n";
+
+		for (int lightNdx = 0; lightNdx < numLights; lightNdx++)
+		{
+			string ndxStr = de::toString(lightNdx);
+
+			resultTemplate +=
+				"	/* Light " + ndxStr + " */\n";
+
+			if (lightType == LIGHT_POINT)
+				resultTemplate +=
+					"	mediump vec3 directionToLight" + ndxStr + " = normalize(v_directionToLight${NAME_SPEC}[" + ndxStr + "]);\n"
+					"	mediump float distanceToLight" + ndxStr + " = v_distanceToLight${NAME_SPEC}[" + ndxStr + "];\n"
+					"	mediump vec3 halfVector" + ndxStr + " = normalize(directionToLight" + ndxStr + " + vec3(0.0, 0.0, 1.0));\n"
+					"	color.rgb += computeLighting(directionToLight" + ndxStr + ", halfVector" + ndxStr + ", eyeNormal, u_light" + ndxStr + "_color${NAME_SPEC}, u_material_diffuseColor${NAME_SPEC}.rgb, "
+					"u_material_specularColor${NAME_SPEC}, u_material_shininess${NAME_SPEC}) * computeDistanceAttenuation(distanceToLight" + ndxStr + ", u_light" + ndxStr + "_constantAttenuation${NAME_SPEC}, "
+					"u_light" + ndxStr + "_linearAttenuation${NAME_SPEC}, u_light" + ndxStr + "_quadraticAttenuation${NAME_SPEC});\n"
+					"\n";
+			else if (lightType == LIGHT_DIRECTIONAL)
+				resultTemplate +=
+					"	mediump vec3 directionToLight" + ndxStr + " = -u_light" + ndxStr + "_direction${NAME_SPEC};\n"
+					"	mediump vec3 halfVector" + ndxStr + " = normalize(directionToLight" + ndxStr + " + vec3(0.0, 0.0, 1.0));\n"
+					"	color.rgb += computeLighting(directionToLight" + ndxStr + ", halfVector" + ndxStr + ", eyeNormal, u_light" + ndxStr + "_color${NAME_SPEC}, u_material_diffuseColor${NAME_SPEC}.rgb, u_material_specularColor${NAME_SPEC}, u_material_shininess${NAME_SPEC});\n"
+					"\n";
+			else
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+
+	resultTemplate +=
+		"	color *= texture(u_sampler0${NAME_SPEC}, texCoord0);\n"
+		"	o_color = color + ${FLOAT01};\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the shader attributes of a (directional or point) light case.
+static vector<ShaderCompilerCase::AttribSpec> lightShaderAttributes (const string& nameSpecialization)
+{
+	vector<ShaderCompilerCase::AttribSpec> result;
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_position" + nameSpecialization,
+													combineVec4ToVec16(Vec4(-1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4(-1.0f,  1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  0.0f,  1.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_normal" + nameSpecialization,
+													combineVec4ToVec16(Vec4(0.0f, 0.0f, -1.0f, 0.0f),
+																	   Vec4(0.0f, 0.0f, -1.0f, 0.0f),
+																	   Vec4(0.0f, 0.0f, -1.0f, 0.0f),
+																	   Vec4(0.0f, 0.0f, -1.0f, 0.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_texCoord0" + nameSpecialization,
+													combineVec4ToVec16(Vec4(0.0f, 0.0f, 0.0f, 0.0f),
+																	   Vec4(1.0f, 0.0f, 0.0f, 0.0f),
+																	   Vec4(0.0f, 1.0f, 0.0f, 0.0f),
+																	   Vec4(1.0f, 1.0f, 0.0f, 0.0f))));
+
+	return result;
+}
+
+// Function for generating the shader uniforms of a (directional or point) light case.
+static vector<ShaderCompilerCase::UniformSpec> lightShaderUniforms (const string& nameSpecialization, int numLights, LightType lightType)
+{
+	vector<ShaderCompilerCase::UniformSpec> result;
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_material_ambientColor" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_VEC3,
+													 vecTo16(Vec3(0.5f, 0.7f, 0.9f))));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_material_diffuseColor" + nameSpecialization,
+													 ShaderCompilerCase:: UniformSpec::TYPE_VEC4,
+													 vecTo16(Vec4(0.3f, 0.4f, 0.5f, 1.0f))));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_material_emissiveColor" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_VEC3,
+													 vecTo16(Vec3(0.7f, 0.2f, 0.2f))));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_material_specularColor" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_VEC3,
+													 vecTo16(Vec3(0.2f, 0.6f, 1.0f))));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_material_shininess" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+													 0.8f));
+
+	for (int lightNdx = 0; lightNdx < numLights; lightNdx++)
+	{
+		string ndxStr = de::toString(lightNdx);
+
+		result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_color" + nameSpecialization,
+														 ShaderCompilerCase::UniformSpec::TYPE_VEC3,
+														 vecTo16(Vec3(0.8f, 0.6f, 0.3f))));
+
+		result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_direction" + nameSpecialization,
+														 ShaderCompilerCase::UniformSpec::TYPE_VEC3,
+														 vecTo16(Vec3(0.2f, 0.3f, 0.4f))));
+
+		if (lightType == LIGHT_POINT)
+		{
+			result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_position" + nameSpecialization,
+															 ShaderCompilerCase::UniformSpec::TYPE_VEC4,
+															 vecTo16(Vec4(1.0f, 0.6f, 0.3f, 0.2f))));
+
+			result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_constantAttenuation" + nameSpecialization,
+															 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+															 0.6f));
+
+			result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_linearAttenuation" + nameSpecialization,
+															 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+															 0.5f));
+
+			result.push_back(ShaderCompilerCase::UniformSpec("u_light" + ndxStr + "_quadraticAttenuation" + nameSpecialization,
+															 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+															 0.4f));
+		}
+	}
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_mvpMatrix" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_MAT4,
+													 arrTo16(Mat4(1.0f).getColumnMajorData())));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_modelViewMatrix" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_MAT4,
+													 arrTo16(Mat4(1.0f).getColumnMajorData())));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_normalMatrix" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_MAT3,
+													 arrTo16(Mat3(1.0f).getColumnMajorData())));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_texCoordMatrix0" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_MAT4,
+													 arrTo16(Mat4(1.0f).getColumnMajorData())));
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_sampler0" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_TEXTURE_UNIT,
+													 0.0f));
+
+	return result;
+}
+
+// Function for generating a vertex shader with a for loop.
+static string loopVertexTemplate (LoopType type, bool isVertexCase, int numLoopIterations, int nestingDepth)
+{
+	string resultTemplate;
+	string loopBound		= type == LOOP_TYPE_STATIC	? de::toString(numLoopIterations)
+							: type == LOOP_TYPE_UNIFORM	? "int(u_loopBound${NAME_SPEC})"
+							: type == LOOP_TYPE_DYNAMIC	? "int(a_loopBound${NAME_SPEC})"
+							: "";
+
+	DE_ASSERT(!loopBound.empty());
+
+	resultTemplate +=
+		"#version 300 es\n"
+		"in highp vec4 a_position${NAME_SPEC};\n";
+
+	if (type == LOOP_TYPE_DYNAMIC)
+		resultTemplate +=
+			"in mediump float a_loopBound${NAME_SPEC};\n";
+
+	resultTemplate +=
+		"in mediump vec4 a_value${NAME_SPEC};\n"
+		"out mediump vec4 v_value${NAME_SPEC};\n";
+
+	if (isVertexCase)
+	{
+		if (type == LOOP_TYPE_UNIFORM)
+			resultTemplate += "uniform mediump float u_loopBound${NAME_SPEC};\n";
+
+		resultTemplate +=
+			"\n"
+			"void main()\n"
+			"{\n"
+			"	gl_Position = a_position${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+			"	mediump vec4 value = a_value${NAME_SPEC};\n";
+
+		for (int i = 0; i < nestingDepth; i++)
+		{
+			string iterName = "i" + de::toString(i);
+			resultTemplate += string(i + 1, '\t') + "for (int " + iterName + " = 0; " + iterName + " < " + loopBound + "; " + iterName + "++)\n";
+		}
+
+		resultTemplate += string(nestingDepth + 1, '\t') + "value *= a_value${NAME_SPEC};\n";
+
+		resultTemplate +=
+			"	v_value${NAME_SPEC} = value;\n";
+	}
+	else
+	{
+		if (type == LOOP_TYPE_DYNAMIC)
+			resultTemplate +=
+				"out mediump float v_loopBound${NAME_SPEC};\n";
+
+		resultTemplate +=
+			"\n"
+			"void main()\n"
+			"{\n"
+			"	gl_Position = a_position${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+			"	v_value${NAME_SPEC} = a_value${NAME_SPEC};\n";
+
+		if (type == LOOP_TYPE_DYNAMIC)
+			resultTemplate +=
+				"	v_loopBound${NAME_SPEC} = a_loopBound${NAME_SPEC};\n";
+	}
+
+	resultTemplate +=
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating a fragment shader with a for loop.
+static string loopFragmentTemplate (LoopType type, bool isVertexCase, int numLoopIterations, int nestingDepth)
+{
+	string resultTemplate;
+	string loopBound		= type == LOOP_TYPE_STATIC	? de::toString(numLoopIterations)
+							: type == LOOP_TYPE_UNIFORM	? "int(u_loopBound${NAME_SPEC})"
+							: type == LOOP_TYPE_DYNAMIC	? "int(v_loopBound${NAME_SPEC})"
+							: "";
+
+	DE_ASSERT(!loopBound.empty());
+
+	resultTemplate +=
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"in mediump vec4 v_value${NAME_SPEC};\n";
+
+	if (!isVertexCase)
+	{
+		if (type == LOOP_TYPE_DYNAMIC)
+			resultTemplate +=
+				"in mediump float v_loopBound${NAME_SPEC};\n";
+		else if (type == LOOP_TYPE_UNIFORM)
+			resultTemplate +=
+				"uniform mediump float u_loopBound${NAME_SPEC};\n";
+
+		resultTemplate +=
+			"\n"
+			"void main()\n"
+			"{\n"
+			"	mediump vec4 value = v_value${NAME_SPEC};\n";
+
+		for (int i = 0; i < nestingDepth; i++)
+		{
+			string iterName = "i" + de::toString(i);
+			resultTemplate += string(i + 1, '\t') + "for (int " + iterName + " = 0; " + iterName + " < " + loopBound + "; " + iterName + "++)\n";
+		}
+
+		resultTemplate += string(nestingDepth + 1, '\t') + "value *= v_value${NAME_SPEC};\n";
+
+		resultTemplate +=
+			"	o_color = value + ${FLOAT01};\n";
+	}
+	else
+		resultTemplate +=
+			"\n"
+			"void main()\n"
+			"{\n"
+			"	o_color = v_value${NAME_SPEC} + ${FLOAT01};\n";
+
+	resultTemplate +=
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the shader attributes for a loop case.
+static vector<ShaderCompilerCase::AttribSpec> loopShaderAttributes (const string& nameSpecialization, LoopType type, int numLoopIterations)
+{
+	vector<ShaderCompilerCase::AttribSpec> result;
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_position" + nameSpecialization,
+													combineVec4ToVec16(Vec4(-1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4(-1.0f,  1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  0.0f,  1.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_value" + nameSpecialization,
+													combineVec4ToVec16(Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f))));
+
+	if (type == LOOP_TYPE_DYNAMIC)
+		result.push_back(ShaderCompilerCase::AttribSpec("a_loopBound" + nameSpecialization,
+														combineVec4ToVec16(Vec4((float)numLoopIterations, 0.0f, 0.0f, 0.0f),
+																		   Vec4((float)numLoopIterations, 0.0f, 0.0f, 0.0f),
+																		   Vec4((float)numLoopIterations, 0.0f, 0.0f, 0.0f),
+																		   Vec4((float)numLoopIterations, 0.0f, 0.0f, 0.0f))));
+
+	return result;
+}
+
+static vector<ShaderCompilerCase::UniformSpec> loopShaderUniforms (const string& nameSpecialization, LoopType type, int numLoopIterations)
+{
+	vector<ShaderCompilerCase::UniformSpec> result;
+
+	if (type == LOOP_TYPE_UNIFORM)
+		result.push_back(ShaderCompilerCase::UniformSpec("u_loopBound" + nameSpecialization,
+														 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+														 (float)numLoopIterations));
+
+	return result;
+}
+
+// Function for generating the shader attributes for a case with only one attribute value in addition to the position attribute.
+static vector<ShaderCompilerCase::AttribSpec> singleValueShaderAttributes (const string& nameSpecialization)
+{
+	vector<ShaderCompilerCase::AttribSpec> result;
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_position" + nameSpecialization,
+													combineVec4ToVec16(Vec4(-1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4(-1.0f,  1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  0.0f,  1.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_value" + nameSpecialization,
+													combineVec4ToVec16(Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  1.0f,  1.0f))));
+
+	return result;
+}
+
+// Function for generating a vertex shader with a binary operation chain.
+static string binaryOpVertexTemplate (int numOperations, const char* op)
+{
+	string resultTemplate;
+
+	resultTemplate +=
+		"#version 300 es\n"
+		"in highp vec4 a_position${NAME_SPEC};\n"
+		"in mediump vec4 a_value${NAME_SPEC};\n"
+		"out mediump vec4 v_value${NAME_SPEC};\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = a_position${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+		"	mediump vec4 value = ";
+
+	for (int i = 0; i < numOperations; i++)
+		resultTemplate += string(i > 0 ? op : "") + "a_value${NAME_SPEC}";
+
+	resultTemplate +=
+		";\n"
+		"	v_value${NAME_SPEC} = value;\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating a fragment shader with a binary operation chain.
+static string binaryOpFragmentTemplate (int numOperations, const char* op)
+{
+	string resultTemplate;
+
+	resultTemplate +=
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"in mediump vec4 v_value${NAME_SPEC};\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	mediump vec4 value = ";
+
+	for (int i = 0; i < numOperations; i++)
+		resultTemplate += string(i > 0 ? op : "") + "v_value${NAME_SPEC}";
+
+	resultTemplate +=
+		";\n"
+		"	o_color = value + ${FLOAT01};\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating a vertex that takes one attribute in addition to position and just passes it to the fragment shader as a varying.
+static string singleVaryingVertexTemplate (void)
+{
+	const char* resultTemplate =
+		"#version 300 es\n"
+		"in highp vec4 a_position${NAME_SPEC};\n"
+		"in mediump vec4 a_value${NAME_SPEC};\n"
+		"out mediump vec4 v_value${NAME_SPEC};\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = a_position${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+		"	v_value${NAME_SPEC} = a_value${NAME_SPEC};\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating a fragment shader that takes a single varying and uses it as the color.
+static string singleVaryingFragmentTemplate (void)
+{
+	const char* resultTemplate =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"in mediump vec4 v_value${NAME_SPEC};\n"
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	o_color = v_value${NAME_SPEC} + ${FLOAT01};\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the vertex shader of a texture lookup case.
+static string textureLookupVertexTemplate (ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+{
+	string	resultTemplate;
+	bool	conditionVaryingNeeded = conditionalUsage != CONDITIONAL_USAGE_NONE && conditionalType == CONDITIONAL_TYPE_DYNAMIC;
+
+	resultTemplate +=
+		"#version 300 es\n"
+		"in highp vec4 a_position${NAME_SPEC};\n"
+		"in mediump vec2 a_coords${NAME_SPEC};\n"
+		"out mediump vec2 v_coords${NAME_SPEC};\n";
+
+	if (conditionVaryingNeeded)
+		resultTemplate +=
+			"in mediump float a_condition${NAME_SPEC};\n"
+			"out mediump float v_condition${NAME_SPEC};\n";
+
+	resultTemplate +=
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = a_position${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+		"	v_coords${NAME_SPEC} = a_coords${NAME_SPEC};\n";
+
+	if (conditionVaryingNeeded)
+		resultTemplate +=
+			"	v_condition${NAME_SPEC} = a_condition${NAME_SPEC};\n";
+
+	resultTemplate +=
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the fragment shader of a texture lookup case.
+static string textureLookupFragmentTemplate (int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+{
+	string resultTemplate;
+
+	resultTemplate +=
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"in mediump vec2 v_coords${NAME_SPEC};\n";
+
+	if (conditionalUsage != CONDITIONAL_USAGE_NONE && conditionalType == CONDITIONAL_TYPE_DYNAMIC)
+		resultTemplate +=
+			"in mediump float v_condition${NAME_SPEC};\n";
+
+	for (int i = 0; i < numLookups; i++)
+		resultTemplate +=
+			"uniform sampler2D u_sampler" + de::toString(i) + "${NAME_SPEC};\n";
+
+	if (conditionalUsage != CONDITIONAL_USAGE_NONE && conditionalType == CONDITIONAL_TYPE_UNIFORM)
+		resultTemplate +=
+			"uniform mediump float u_condition${NAME_SPEC};\n";
+
+	resultTemplate +=
+		"\n"
+		"void main()\n"
+		"{\n"
+		"	mediump vec4 color = vec4(0.0);\n";
+
+	const char* conditionalTerm = conditionalType == CONDITIONAL_TYPE_STATIC	? "1.0 > 0.0"
+								: conditionalType == CONDITIONAL_TYPE_UNIFORM	? "u_condition${NAME_SPEC} > 0.0"
+								: conditionalType == CONDITIONAL_TYPE_DYNAMIC	? "v_condition${NAME_SPEC} > 0.0"
+								: DE_NULL;
+
+	DE_ASSERT(conditionalTerm != DE_NULL);
+
+	if (conditionalUsage == CONDITIONAL_USAGE_FIRST_HALF)
+		resultTemplate += string("") +
+			"	if (" + conditionalTerm + ")\n"
+			"	{\n";
+
+	for (int i = 0; i < numLookups; i++)
+	{
+		if (conditionalUsage == CONDITIONAL_USAGE_FIRST_HALF)
+		{
+			if (i < (numLookups + 1) / 2)
+				resultTemplate += "\t";
+		}
+		else if (conditionalUsage == CONDITIONAL_USAGE_EVERY_OTHER)
+		{
+			if (i % 2 == 0)
+				resultTemplate += string("") +
+					"	if (" + conditionalTerm + ")\n"
+					"\t";
+		}
+
+		resultTemplate +=
+			"	color += texture(u_sampler" + de::toString(i) + "${NAME_SPEC}, v_coords${NAME_SPEC});\n";
+
+		if (conditionalUsage == CONDITIONAL_USAGE_FIRST_HALF && i == (numLookups - 1) / 2)
+			resultTemplate += "\t}\n";
+	}
+
+	resultTemplate +=
+		"	o_color = color/" + de::toString(numLookups) + ".0 + ${FLOAT01};\n" +
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+// Function for generating the shader attributes of a texture lookup case.
+static vector<ShaderCompilerCase::AttribSpec> textureLookupShaderAttributes (const string& nameSpecialization, ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+{
+	vector<ShaderCompilerCase::AttribSpec> result;
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_position" + nameSpecialization,
+													combineVec4ToVec16(Vec4(-1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4(-1.0f,  1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  0.0f,  1.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_coords" + nameSpecialization,
+													combineVec4ToVec16(Vec4(0.0f, 0.0f, 0.0f, 0.0f),
+																	   Vec4(0.0f, 1.0f, 0.0f, 0.0f),
+																	   Vec4(1.0f, 0.0f, 0.0f, 0.0f),
+																	   Vec4(1.0f, 1.0f, 0.0f, 0.0f))));
+
+	if (conditionalUsage != CONDITIONAL_USAGE_NONE && conditionalType == CONDITIONAL_TYPE_DYNAMIC)
+		result.push_back(ShaderCompilerCase::AttribSpec("a_condition" + nameSpecialization,
+														combineVec4ToVec16(Vec4(1.0f), Vec4(1.0f), Vec4(1.0f), Vec4(1.0f))));
+
+	return result;
+}
+
+// Function for generating the shader uniforms of a texture lookup case.
+static vector<ShaderCompilerCase::UniformSpec> textureLookupShaderUniforms (const string& nameSpecialization, int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+{
+	vector<ShaderCompilerCase::UniformSpec> result;
+
+	for (int i = 0; i < numLookups; i++)
+		result.push_back(ShaderCompilerCase::UniformSpec("u_sampler" + de::toString(i) + nameSpecialization,
+														 ShaderCompilerCase::UniformSpec::TYPE_TEXTURE_UNIT,
+														 (float)i));
+
+	if (conditionalUsage != CONDITIONAL_USAGE_NONE && conditionalType == CONDITIONAL_TYPE_UNIFORM)
+		result.push_back(ShaderCompilerCase::UniformSpec("u_condition" + nameSpecialization,
+														 ShaderCompilerCase::UniformSpec::TYPE_FLOAT,
+														 1.0f));
+
+	return result;
+}
+
+static string mandelbrotVertexTemplate (void)
+{
+	const char* resultTemplate =
+		"#version 300 es\n"
+		"uniform highp mat4 u_mvp${NAME_SPEC};\n"
+		"\n"
+		"in highp vec4 a_vertex${NAME_SPEC};\n"
+		"in highp vec4 a_coord${NAME_SPEC};\n"
+		"\n"
+		"out mediump vec2 v_coord${NAME_SPEC};\n"
+		"\n"
+		"void main(void)\n"
+		"{\n"
+		"	gl_Position = u_mvp${NAME_SPEC} * a_vertex${NAME_SPEC} * (0.95 + 0.05*${FLOAT01});\n"
+		"\n"
+		"	float xMin = -2.0;\n"
+		"	float xMax = +0.5;\n"
+		"	float yMin = -1.5;\n"
+		"	float yMax = +1.5;\n"
+		"\n"
+		"	v_coord${NAME_SPEC}.x = a_coord${NAME_SPEC}.x * (xMax - xMin) + xMin;\n"
+		"	v_coord${NAME_SPEC}.y = a_coord${NAME_SPEC}.y * (yMax - yMin) + yMin;\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+static string mandelbrotFragmentTemplate (int numFractalIterations)
+{
+	string resultTemplate =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"in mediump vec2 v_coord${NAME_SPEC};\n"
+		"\n"
+		"precision mediump float;\n"
+		"\n"
+		"#define NUM_ITERS " + de::toString(numFractalIterations) + "\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	vec2 coords = v_coord${NAME_SPEC};\n"
+		"	float u_limit = 2.0 * 2.0;\n"
+		"	vec2 tmp = vec2(0, 0);\n"
+		"	int iter;\n"
+		"\n"
+		"	for (iter = 0; iter < NUM_ITERS; iter++)\n"
+		"	{\n"
+		"		tmp = vec2((tmp.x + tmp.y) * (tmp.x - tmp.y), 2.0 * (tmp.x * tmp.y)) + coords;\n"
+		"\n"
+		"		if (dot(tmp, tmp) > u_limit)\n"
+		"			break;\n"
+		"	}\n"
+		"\n"
+		"	vec3 color = vec3(float(iter) * (1.0 / float(NUM_ITERS)));\n"
+		"\n"
+		"	o_color = vec4(color, 1.0) + ${FLOAT01};\n"
+		"${SEMANTIC_ERROR}"
+		"}\n"
+		"${INVALID_CHAR}";
+
+	return resultTemplate;
+}
+
+static vector<ShaderCompilerCase::AttribSpec> mandelbrotShaderAttributes (const string& nameSpecialization)
+{
+	vector<ShaderCompilerCase::AttribSpec> result;
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_vertex" + nameSpecialization,
+													combineVec4ToVec16(Vec4(-1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4(-1.0f,  1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f, -1.0f,  0.0f,  1.0f),
+																	   Vec4( 1.0f,  1.0f,  0.0f,  1.0f))));
+
+	result.push_back(ShaderCompilerCase::AttribSpec("a_coord" + nameSpecialization,
+													combineVec4ToVec16(Vec4(0.0f, 0.0f, 0.0f, 1.0f),
+																	   Vec4(0.0f, 1.0f, 0.0f, 1.0f),
+																	   Vec4(1.0f, 0.0f, 0.0f, 1.0f),
+																	   Vec4(1.0f, 1.0f, 0.0f, 1.0f))));
+
+	return result;
+}
+
+static vector<ShaderCompilerCase::UniformSpec> mandelbrotShaderUniforms (const string& nameSpecialization)
+{
+	vector<ShaderCompilerCase::UniformSpec> result;
+
+	result.push_back(ShaderCompilerCase::UniformSpec("u_mvp" + nameSpecialization,
+													 ShaderCompilerCase::UniformSpec::TYPE_MAT4,
+													 arrTo16(Mat4(1.0f).getColumnMajorData())));
+
+	return result;
+}
+
+ShaderCompilerCase::ShaderCompilerCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments)
+	: TestCase								(context, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_viewportWidth						(0)
+	, m_viewportHeight						(0)
+	, m_avoidCache							(avoidCache)
+	, m_addWhitespaceAndComments			(addWhitespaceAndComments)
+	, m_startHash							((deUint32)(deUint64Hash(deGetTime()) ^ deUint64Hash(deGetMicroseconds()) ^ deInt32Hash(caseID)))
+{
+	int cmdLineIterCount = context.getTestContext().getCommandLine().getTestIterationCount();
+	m_minimumMeasurementCount = cmdLineIterCount > 0 ? cmdLineIterCount : DEFAULT_MINIMUM_MEASUREMENT_COUNT;
+	m_maximumMeasurementCount = m_minimumMeasurementCount*3;
+}
+
+ShaderCompilerCase::~ShaderCompilerCase (void)
+{
+}
+
+deUint32 ShaderCompilerCase::getSpecializationID (int measurementNdx) const
+{
+	if (m_avoidCache)
+		return m_startHash ^ (deUint32)deInt32Hash((deInt32)measurementNdx);
+	else
+		return m_startHash;
+}
+
+void ShaderCompilerCase::init (void)
+{
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderContext().getRenderTarget();
+
+	m_viewportWidth		= deMin32(MAX_VIEWPORT_WIDTH, renderTarget.getWidth());
+	m_viewportHeight	= deMin32(MAX_VIEWPORT_HEIGHT, renderTarget.getHeight());
+
+	gl.viewport(0, 0, m_viewportWidth, m_viewportHeight);
+}
+
+ShaderCompilerCase::ShadersAndProgram ShaderCompilerCase::createShadersAndProgram (void) const
+{
+	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();
+	ShadersAndProgram		result;
+
+	result.vertShader	= gl.createShader(GL_VERTEX_SHADER);
+	result.fragShader	= gl.createShader(GL_FRAGMENT_SHADER);
+	result.program		= gl.createProgram();
+
+	gl.attachShader(result.program, result.vertShader);
+	gl.attachShader(result.program, result.fragShader);
+
+	return result;
+}
+
+void ShaderCompilerCase::setShaderSources (deUint32 vertShader, deUint32 fragShader, const ProgramContext& progCtx) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	const char* vertShaderSourceCStr = progCtx.vertShaderSource.c_str();
+	const char* fragShaderSourceCStr = progCtx.fragShaderSource.c_str();
+	gl.shaderSource(vertShader, 1, &vertShaderSourceCStr, DE_NULL);
+	gl.shaderSource(fragShader, 1, &fragShaderSourceCStr, DE_NULL);
+}
+
+bool ShaderCompilerCase::compileShader (deUint32 shader) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	GLint status;
+	gl.compileShader(shader);
+	gl.getShaderiv(shader, GL_COMPILE_STATUS, &status);
+	return status != 0;
+}
+
+bool ShaderCompilerCase::linkAndUseProgram (deUint32 program) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	GLint linkStatus;
+
+	gl.linkProgram(program);
+	gl.getProgramiv(program, GL_LINK_STATUS, &linkStatus);
+
+	if (linkStatus != 0)
+		gl.useProgram(program);
+
+	return linkStatus != 0;
+}
+
+void ShaderCompilerCase::setShaderInputs (deUint32 program, const ProgramContext& progCtx) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Setup attributes.
+
+	for (int attribNdx = 0; attribNdx < (int)progCtx.vertexAttributes.size(); attribNdx++)
+	{
+		int location = gl.getAttribLocation(program, progCtx.vertexAttributes[attribNdx].name.c_str());
+		if (location >= 0)
+		{
+			gl.enableVertexAttribArray(location);
+			gl.vertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, 0, progCtx.vertexAttributes[attribNdx].value.getPtr());
+		}
+	}
+
+	// Setup uniforms.
+
+	for (int uniformNdx = 0; uniformNdx < (int)progCtx.uniforms.size(); uniformNdx++)
+	{
+		int location = gl.getUniformLocation(program, progCtx.uniforms[uniformNdx].name.c_str());
+		if (location >= 0)
+		{
+			const float* floatPtr = progCtx.uniforms[uniformNdx].value.getPtr();
+
+			switch (progCtx.uniforms[uniformNdx].type)
+			{
+				case UniformSpec::TYPE_FLOAT:			gl.uniform1fv(location, 1, floatPtr);								break;
+				case UniformSpec::TYPE_VEC2:			gl.uniform2fv(location, 1, floatPtr);								break;
+				case UniformSpec::TYPE_VEC3:			gl.uniform3fv(location, 1, floatPtr);								break;
+				case UniformSpec::TYPE_VEC4:			gl.uniform4fv(location, 1, floatPtr);								break;
+				case UniformSpec::TYPE_MAT3:			gl.uniformMatrix3fv(location, 1, GL_FALSE, floatPtr);				break;
+				case UniformSpec::TYPE_MAT4:			gl.uniformMatrix4fv(location, 1, GL_FALSE, floatPtr);				break;
+				case UniformSpec::TYPE_TEXTURE_UNIT:	gl.uniform1i(location, (GLint)deRoundFloatToInt32(*floatPtr));		break;
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+		}
+	}
+}
+
+void ShaderCompilerCase::draw (void) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	static const deUint8 indices[] =
+	{
+		0, 1, 2,
+		2, 1, 3
+	};
+
+	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	gl.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, indices);
+
+	// \note Read one pixel to force compilation.
+	deUint32 pixel;
+	gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
+}
+
+void ShaderCompilerCase::cleanup (const ShadersAndProgram& shadersAndProgram, const ProgramContext& progCtx, bool linkSuccess) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (linkSuccess)
+	{
+		for (int attribNdx = 0; attribNdx < (int)progCtx.vertexAttributes.size(); attribNdx++)
+		{
+			int location = gl.getAttribLocation(shadersAndProgram.program, progCtx.vertexAttributes[attribNdx].name.c_str());
+			if (location >= 0)
+				gl.disableVertexAttribArray(location);
+		}
+	}
+
+	gl.useProgram(0);
+	gl.detachShader(shadersAndProgram.program, shadersAndProgram.vertShader);
+	gl.detachShader(shadersAndProgram.program, shadersAndProgram.fragShader);
+	gl.deleteShader(shadersAndProgram.vertShader);
+	gl.deleteShader(shadersAndProgram.fragShader);
+	gl.deleteProgram(shadersAndProgram.program);
+}
+
+void ShaderCompilerCase::logProgramData (const BuildInfo& buildInfo, const ProgramContext& progCtx) const
+{
+	m_testCtx.getLog() << TestLog::ShaderProgram(buildInfo.linkSuccess, buildInfo.logs.link)
+					   << TestLog::Shader(QP_SHADER_TYPE_VERTEX,	progCtx.vertShaderSource, buildInfo.vertCompileSuccess, buildInfo.logs.vert)
+					   << TestLog::Shader(QP_SHADER_TYPE_FRAGMENT,	progCtx.fragShaderSource, buildInfo.fragCompileSuccess, buildInfo.logs.frag)
+					   << TestLog::EndShaderProgram;
+}
+
+ShaderCompilerCase::Logs ShaderCompilerCase::getLogs (const ShadersAndProgram& shadersAndProgram) const
+{
+	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();
+	Logs					result;
+
+	result.vert = getShaderInfoLog(gl, shadersAndProgram.vertShader);
+	result.frag = getShaderInfoLog(gl, shadersAndProgram.fragShader);
+	result.link = getProgramInfoLog(gl, shadersAndProgram.program);
+
+	return result;
+}
+
+bool ShaderCompilerCase::goodEnoughMeasurements (const vector<Measurement>& measurements) const
+{
+	if ((int)measurements.size() < m_minimumMeasurementCount)
+		return false;
+	else
+	{
+		if ((int)measurements.size() >= m_maximumMeasurementCount)
+			return true;
+		else
+		{
+			vector<deInt64> totalTimesWithoutDraw;
+			for (int i = 0; i < (int)measurements.size(); i++)
+				totalTimesWithoutDraw.push_back(measurements[i].totalTimeWithoutDraw());
+			return vectorFloatRelativeMedianAbsoluteDeviation(vectorLowestPercentage(totalTimesWithoutDraw, 0.5f)) < RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD;
+		}
+	}
+}
+
+ShaderCompilerCase::IterateResult ShaderCompilerCase::iterate (void)
+{
+	// Before actual measurements, compile and draw with a dummy shader to avoid possible initial slowdowns in the actual test.
+	{
+		deUint32		specID = getSpecializationID(0);
+		ProgramContext	progCtx;
+		progCtx.vertShaderSource = specializeShaderSource(singleVaryingVertexTemplate(), specID, SHADER_VALIDITY_VALID);
+		progCtx.fragShaderSource = specializeShaderSource(singleVaryingFragmentTemplate(), specID, SHADER_VALIDITY_VALID);
+		progCtx.vertexAttributes = singleValueShaderAttributes(getNameSpecialization(specID));
+
+		ShadersAndProgram shadersAndProgram = createShadersAndProgram();
+		setShaderSources(shadersAndProgram.vertShader, shadersAndProgram.fragShader, progCtx);
+
+		BuildInfo buildInfo;
+		buildInfo.vertCompileSuccess	= compileShader(shadersAndProgram.vertShader);
+		buildInfo.fragCompileSuccess	= compileShader(shadersAndProgram.fragShader);
+		buildInfo.linkSuccess			= linkAndUseProgram(shadersAndProgram.program);
+		if (!(buildInfo.vertCompileSuccess && buildInfo.fragCompileSuccess && buildInfo.linkSuccess))
+		{
+			buildInfo.logs = getLogs(shadersAndProgram);
+			logProgramData(buildInfo, progCtx);
+			cleanup(shadersAndProgram, progCtx, buildInfo.linkSuccess);
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compilation failed");
+			return STOP;
+		}
+		setShaderInputs(shadersAndProgram.program, progCtx);
+		draw();
+		cleanup(shadersAndProgram, progCtx, buildInfo.linkSuccess);
+	}
+
+	vector<Measurement>		measurements;
+	// \note These are logged after measurements are done.
+	ProgramContext			latestProgramContext;
+	BuildInfo				latestBuildInfo;
+
+	if (WARMUP_CPU_AT_BEGINNING_OF_CASE)
+		tcu::warmupCPU();
+
+	// Actual test measurements.
+	while (!goodEnoughMeasurements(measurements))
+	{
+		// Create shaders, compile & link, set shader inputs and draw. Time measurement is done at relevant points.
+		// \note Setting inputs and drawing are done twice in order to find out the time for actual compiling.
+
+		// \note Shader data (sources and inputs) are generated and GL shader and program objects are created before any time measurements.
+		ProgramContext		progCtx				= generateShaderData((int)measurements.size());
+		ShadersAndProgram	shadersAndProgram	= createShadersAndProgram();
+		BuildInfo			buildInfo;
+
+		if (m_addWhitespaceAndComments)
+		{
+			const deUint32 hash = m_startHash ^ (deUint32)deInt32Hash((deInt32)measurements.size());
+			progCtx.vertShaderSource = strWithWhiteSpaceAndComments(progCtx.vertShaderSource, hash);
+			progCtx.fragShaderSource = strWithWhiteSpaceAndComments(progCtx.fragShaderSource, hash);
+		}
+
+		if (WARMUP_CPU_BEFORE_EACH_MEASUREMENT)
+			tcu::warmupCPU();
+
+		// \note Do NOT do anything too hefty between the first and last deGetMicroseconds() here (other than the gl calls); it would disturb the measurement.
+
+		deUint64 startTime = deGetMicroseconds();
+
+		setShaderSources(shadersAndProgram.vertShader, shadersAndProgram.fragShader, progCtx);
+		deUint64 shaderSourceSetEndTime = deGetMicroseconds();
+
+		buildInfo.vertCompileSuccess = compileShader(shadersAndProgram.vertShader);
+		deUint64 vertexShaderCompileEndTime = deGetMicroseconds();
+
+		buildInfo.fragCompileSuccess = compileShader(shadersAndProgram.fragShader);
+		deUint64 fragmentShaderCompileEndTime = deGetMicroseconds();
+
+		buildInfo.linkSuccess = linkAndUseProgram(shadersAndProgram.program);
+		deUint64 programLinkEndTime = deGetMicroseconds();
+
+		// Check compilation and linking status here, after all compilation and linking gl calls are made.
+		if (!(buildInfo.vertCompileSuccess && buildInfo.fragCompileSuccess && buildInfo.linkSuccess))
+		{
+			buildInfo.logs = getLogs(shadersAndProgram);
+			logProgramData(buildInfo, progCtx);
+			cleanup(shadersAndProgram, progCtx, buildInfo.linkSuccess);
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compilation failed");
+			return STOP;
+		}
+
+		setShaderInputs(shadersAndProgram.program, progCtx);
+		deUint64 firstShaderInputSetEndTime = deGetMicroseconds();
+
+		// Draw for the first time.
+		draw();
+		deUint64 firstDrawEndTime = deGetMicroseconds();
+
+		// Set inputs and draw again.
+
+		setShaderInputs(shadersAndProgram.program, progCtx);
+		deUint64 secondShaderInputSetEndTime = deGetMicroseconds();
+
+		draw();
+		deUint64 secondDrawEndTime = deGetMicroseconds();
+
+		// De-initializations (detach shaders etc.).
+
+		buildInfo.logs = getLogs(shadersAndProgram);
+		cleanup(shadersAndProgram, progCtx, buildInfo.linkSuccess);
+
+		// Output measurement log later (after last measurement).
+
+		measurements.push_back(Measurement((deInt64)(shaderSourceSetEndTime			- startTime),
+										   (deInt64)(vertexShaderCompileEndTime		- shaderSourceSetEndTime),
+										   (deInt64)(fragmentShaderCompileEndTime	- vertexShaderCompileEndTime),
+										   (deInt64)(programLinkEndTime				- fragmentShaderCompileEndTime),
+										   (deInt64)(firstShaderInputSetEndTime		- programLinkEndTime),
+										   (deInt64)(firstDrawEndTime				- firstShaderInputSetEndTime),
+										   (deInt64)(secondShaderInputSetEndTime	- firstDrawEndTime),
+										   (deInt64)(secondDrawEndTime				- secondShaderInputSetEndTime)));
+
+		latestBuildInfo			= buildInfo;
+		latestProgramContext	= progCtx;
+
+		m_testCtx.touchWatchdog(); // \note Measurements may take a while in a bad case.
+	}
+
+	// End of test case, log information about measurements.
+	{
+		TestLog& log = m_testCtx.getLog();
+
+		vector<deInt64> sourceSetTimes;
+		vector<deInt64> vertexCompileTimes;
+		vector<deInt64> fragmentCompileTimes;
+		vector<deInt64> programLinkTimes;
+		vector<deInt64> firstInputSetTimes;
+		vector<deInt64> firstDrawTimes;
+		vector<deInt64> secondInputTimes;
+		vector<deInt64> secondDrawTimes;
+		vector<deInt64> firstPhaseTimes;
+		vector<deInt64> secondPhaseTimes;
+		vector<deInt64> totalTimesWithoutDraw;
+		vector<deInt64> specializationTimes;
+
+		if (!m_avoidCache)
+			log << TestLog::Message << "Note: Testing cache hits, so the medians and averages exclude the first iteration." << TestLog::EndMessage;
+
+		log << TestLog::Message << "Note: \"Specialization time\" means first draw time minus second draw time." << TestLog::EndMessage
+			<< TestLog::Message << "Note: \"Compilation time\" means the time up to (and including) linking, plus specialization time." << TestLog::EndMessage;
+
+		log << TestLog::Section("IterationMeasurements", "Iteration measurements of compilation and linking times");
+
+		DE_ASSERT((int)measurements.size() > (m_avoidCache ? 0 : 1));
+
+		for (int ndx = 0; ndx < (int)measurements.size(); ndx++)
+		{
+			const Measurement& curMeas = measurements[ndx];
+
+			// Subtract time of second phase (second input setup and draw) from first (from start to end of first draw).
+			// \note Cap if second phase seems unreasonably high (higher than first input set and draw).
+			deInt64 timeWithoutDraw		= curMeas.totalTimeWithoutDraw();
+
+			// Specialization time = first draw - second draw time. Again, cap at 0 if second draw was longer than first draw.
+			deInt64 specializationTime	= de::max<deInt64>(0, curMeas.firstDrawTime - curMeas.secondDrawTime);
+
+			if (ndx > 0 || m_avoidCache) // \note When allowing cache hits, don't account for the first measurement when calculating median or average.
+			{
+				sourceSetTimes.push_back		(curMeas.sourceSetTime);
+				vertexCompileTimes.push_back	(curMeas.vertexCompileTime);
+				fragmentCompileTimes.push_back	(curMeas.fragmentCompileTime);
+				programLinkTimes.push_back		(curMeas.programLinkTime);
+				firstInputSetTimes.push_back	(curMeas.firstInputSetTime);
+				firstDrawTimes.push_back		(curMeas.firstDrawTime);
+				firstPhaseTimes.push_back		(curMeas.firstPhase());
+				secondDrawTimes.push_back		(curMeas.secondDrawTime);
+				secondInputTimes.push_back		(curMeas.secondInputSetTime);
+				secondPhaseTimes.push_back		(curMeas.secondPhase());
+				totalTimesWithoutDraw.push_back	(timeWithoutDraw);
+				specializationTimes.push_back	(specializationTime);
+			}
+
+			// Log this measurement.
+			log << TestLog::Float("Measurement" + de::toString(ndx) + "CompilationTime",
+								  "Measurement " + de::toString(ndx) + " compilation time",
+								  "ms", QP_KEY_TAG_TIME, timeWithoutDraw / 1000.0f)
+				<< TestLog::Float("Measurement" + de::toString(ndx) + "SpecializationTime",
+								  "Measurement " + de::toString(ndx) + " specialization time",
+								  "ms", QP_KEY_TAG_TIME, specializationTime / 1000.0f);
+		}
+
+		// Log some statistics.
+
+		for (int entireRangeOrLowestHalf = 0; entireRangeOrLowestHalf < 2; entireRangeOrLowestHalf++)
+		{
+			bool				isEntireRange				= entireRangeOrLowestHalf == 0;
+			string				statNamePrefix				= isEntireRange ? "" : "LowestHalf";
+			vector<deInt64>		rangeTotalTimes				= isEntireRange ? totalTimesWithoutDraw	: vectorLowestPercentage(totalTimesWithoutDraw,	0.5f);
+			vector<deInt64>		rangeSpecializationTimes	= isEntireRange ? specializationTimes	: vectorLowestPercentage(specializationTimes,	0.5f);
+
+#define LOG_COMPILE_SPECIALIZE_TIME_STAT(NAME, DESC, FUNC)																													\
+	log << TestLog::Float(statNamePrefix + "CompilationTime" + (NAME), (DESC) + string(" of compilation time"), "ms", QP_KEY_TAG_TIME, (FUNC)(rangeTotalTimes)/1000.0f)		\
+		<< TestLog::Float(statNamePrefix + "SpecializationTime" + (NAME), (DESC) + string(" of specialization time"), "ms", QP_KEY_TAG_TIME, (FUNC)(rangeSpecializationTimes)/1000.0f)
+
+#define LOG_COMPILE_SPECIALIZE_RELATIVE_STAT(NAME, DESC, FUNC)																										\
+	log << TestLog::Float(statNamePrefix + "CompilationTime" + (NAME), (DESC) + string(" of compilation time"), "", QP_KEY_TAG_NONE, (FUNC)(rangeTotalTimes))		\
+		<< TestLog::Float(statNamePrefix + "SpecializationTime" + (NAME), (DESC) + string(" of specialization time"), "", QP_KEY_TAG_NONE, (FUNC)(rangeSpecializationTimes))
+
+			log << TestLog::Message << "\nStatistics computed from "
+									<< (isEntireRange ? "all" : "only the lowest 50%")
+									<< " of the above measurements:"
+									<< TestLog::EndMessage;
+
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("Median",							"Median",								vectorFloatMedian);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("Average",							"Average",								vectorFloatAverage);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("Minimum",							"Minimum",								vectorFloatMinimum);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("Maximum",							"Maximum",								vectorFloatMaximum);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("MedianAbsoluteDeviation",			"Median absolute deviation",			vectorFloatMedianAbsoluteDeviation);
+			LOG_COMPILE_SPECIALIZE_RELATIVE_STAT	("RelativeMedianAbsoluteDeviation",	"Relative median absolute deviation",	vectorFloatRelativeMedianAbsoluteDeviation);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("StandardDeviation",				"Standard deviation",					vectorFloatStandardDeviation);
+			LOG_COMPILE_SPECIALIZE_RELATIVE_STAT	("RelativeStandardDeviation",		"Relative standard deviation",			vectorFloatRelativeStandardDeviation);
+			LOG_COMPILE_SPECIALIZE_TIME_STAT		("MaxMinusMin",						"Max-min",								vectorFloatMaximumMinusMinimum);
+			LOG_COMPILE_SPECIALIZE_RELATIVE_STAT	("RelativeMaxMinusMin",				"Relative max-min",						vectorFloatRelativeMaximumMinusMinimum);
+
+#undef LOG_COMPILE_SPECIALIZE_RELATIVE_STAT
+#undef LOG_COMPILE_SPECIALIZE_TIME_STAT
+
+			if (!isEntireRange && vectorFloatRelativeMedianAbsoluteDeviation(rangeTotalTimes) > RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD)
+				log << TestLog::Message << "\nWARNING: couldn't achieve relative median absolute deviation under threshold value "
+										<< RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD
+										<< " for compilation time of the lowest 50% of measurements" << TestLog::EndMessage;
+		}
+
+		log << TestLog::EndSection; // End section IterationMeasurements
+
+		for (int medianOrAverage = 0; medianOrAverage < 2; medianOrAverage++)
+		{
+			typedef float (*VecFunc)(const vector<deInt64>&);
+
+			bool	isMedian						= medianOrAverage == 0;
+			string	singular						= isMedian ? "Median" : "Average";
+			string	plural							= singular + "s";
+			VecFunc func							= isMedian ? (VecFunc) vectorFloatMedian<deInt64> : (VecFunc) vectorFloatAverage<deInt64>;
+
+			log << TestLog::Section(plural + "PerPhase", plural + " per phase");
+
+			for (int entireRangeOrLowestHalf = 0; entireRangeOrLowestHalf < 2; entireRangeOrLowestHalf++)
+			{
+				bool	isEntireRange	= entireRangeOrLowestHalf == 0;
+				string	statNamePrefix	= isEntireRange ? "" : "LowestHalf";
+				float	rangeSizeRatio	= isEntireRange ? 1.0f : 0.5f;
+
+#define LOG_TIME(NAME, DESC, DATA) log << TestLog::Float(statNamePrefix + (NAME) + singular, singular + " of " + (DESC), "ms", QP_KEY_TAG_TIME, func(vectorLowestPercentage((DATA), rangeSizeRatio))/1000.0f);
+
+				log << TestLog::Message << (isEntireRange ? "For all measurements:" : "\nFor only the lowest 50% of the measurements:") << TestLog::EndMessage;
+				LOG_TIME("ShaderSourceSetTime",			"shader source set time",			sourceSetTimes);
+				LOG_TIME("VertexShaderCompileTime",		"vertex shader compile time",		vertexCompileTimes);
+				LOG_TIME("FragmentShaderCompileTime",	"fragment shader compile time",		fragmentCompileTimes);
+				LOG_TIME("ProgramLinkTime",				"program link time",				programLinkTimes);
+				LOG_TIME("FirstShaderInputSetTime",		"first shader input set time",		firstInputSetTimes);
+				LOG_TIME("FirstDrawTime",				"first draw time",					firstDrawTimes);
+				LOG_TIME("SecondShaderInputSetTime",	"second shader input set time",		secondInputTimes);
+				LOG_TIME("SecondDrawTime",				"second draw time",					secondDrawTimes);
+
+#undef LOG_TIME
+			}
+
+			log << TestLog::EndSection;
+		}
+
+		// Set result.
+
+		{
+			log << TestLog::Message << "Note: test result is the first quartile (i.e. median of the lowest half of measurements) of compilation times" << TestLog::EndMessage;
+			float result = vectorFloatFirstQuartile(totalTimesWithoutDraw) / 1000.0f;
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(result, 2).c_str());
+		}
+
+		// Log shaders.
+
+		if (m_avoidCache || m_addWhitespaceAndComments)
+		{
+			string msg = "Note: the following shaders are the ones from the last iteration; ";
+
+			if (m_avoidCache)
+				msg += "variables' names and some constant expressions";
+			if (m_addWhitespaceAndComments)
+				msg += string(m_avoidCache ? " as well as " : "") + "whitespace and comments";
+
+			msg += " differ between iterations.";
+
+			log << TestLog::Message << msg.c_str() << TestLog::EndMessage;
+		}
+
+		logProgramData(latestBuildInfo, latestProgramContext);
+
+		return STOP;
+	}
+}
+
+ShaderCompilerLightCase::ShaderCompilerLightCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, int numLights, LightType lightType)
+	: ShaderCompilerCase	(context, name, description, caseID, avoidCache, addWhitespaceAndComments)
+	, m_numLights			(numLights)
+	, m_isVertexCase		(isVertexCase)
+	, m_lightType			(lightType)
+	, m_texture				(DE_NULL)
+{
+}
+
+ShaderCompilerLightCase::~ShaderCompilerLightCase (void)
+{
+	ShaderCompilerLightCase::deinit();
+}
+
+void ShaderCompilerLightCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+}
+
+void ShaderCompilerLightCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Setup texture.
+
+	DE_ASSERT(m_texture == DE_NULL);
+
+	m_texture = new glu::Texture2D(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, TEXTURE_WIDTH, TEXTURE_HEIGHT);
+
+	tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	m_texture->getRefTexture().allocLevel(0);
+	tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), fmtInfo.valueMin, fmtInfo.valueMax);
+
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	m_texture->upload();
+
+	ShaderCompilerCase::init();
+}
+
+ShaderCompilerCase::ProgramContext ShaderCompilerLightCase::generateShaderData (int measurementNdx) const
+{
+	deUint32		specID		= getSpecializationID(measurementNdx);
+	string			nameSpec	= getNameSpecialization(specID);
+	ProgramContext	result;
+
+	result.vertShaderSource		= specializeShaderSource(lightVertexTemplate(m_numLights, m_isVertexCase, m_lightType), specID, SHADER_VALIDITY_VALID);
+	result.fragShaderSource		= specializeShaderSource(lightFragmentTemplate(m_numLights, m_isVertexCase, m_lightType), specID, SHADER_VALIDITY_VALID);
+	result.vertexAttributes		= lightShaderAttributes(nameSpec);
+	result.uniforms				= lightShaderUniforms(nameSpec, m_numLights, m_lightType);
+
+	return result;
+}
+
+ShaderCompilerTextureCase::ShaderCompilerTextureCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+	: ShaderCompilerCase	(context, name, description, caseID, avoidCache, addWhitespaceAndComments)
+	, m_numLookups			(numLookups)
+	, m_conditionalUsage	(conditionalUsage)
+	, m_conditionalType		(conditionalType)
+{
+}
+
+ShaderCompilerTextureCase::~ShaderCompilerTextureCase (void)
+{
+	ShaderCompilerTextureCase::deinit();
+}
+
+void ShaderCompilerTextureCase::deinit (void)
+{
+	for (vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+}
+
+void ShaderCompilerTextureCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Setup texture.
+
+	DE_ASSERT(m_textures.empty());
+
+	m_textures.reserve(m_numLookups);
+
+	for (int i = 0; i < m_numLookups; i++)
+	{
+		glu::Texture2D*			tex		= new glu::Texture2D(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, TEXTURE_WIDTH, TEXTURE_HEIGHT);
+		tcu::TextureFormatInfo	fmtInfo	= tcu::getTextureFormatInfo(tex->getRefTexture().getFormat());
+
+		tex->getRefTexture().allocLevel(0);
+		tcu::fillWithComponentGradients(tex->getRefTexture().getLevel(0), fmtInfo.valueMin, fmtInfo.valueMax);
+
+		gl.activeTexture(GL_TEXTURE0 + i);
+		gl.bindTexture(GL_TEXTURE_2D, tex->getGLTexture());
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+		tex->upload();
+
+		m_textures.push_back(tex);
+	}
+
+	ShaderCompilerCase::init();
+}
+
+ShaderCompilerCase::ProgramContext ShaderCompilerTextureCase::generateShaderData (int measurementNdx) const
+{
+	deUint32		specID		= getSpecializationID(measurementNdx);
+	string			nameSpec	= getNameSpecialization(specID);
+	ProgramContext	result;
+
+	result.vertShaderSource		= specializeShaderSource(textureLookupVertexTemplate(m_conditionalUsage, m_conditionalType), specID, SHADER_VALIDITY_VALID);
+	result.fragShaderSource		= specializeShaderSource(textureLookupFragmentTemplate(m_numLookups, m_conditionalUsage, m_conditionalType), specID, SHADER_VALIDITY_VALID);
+	result.vertexAttributes		= textureLookupShaderAttributes(nameSpec, m_conditionalUsage, m_conditionalType);
+	result.uniforms				= textureLookupShaderUniforms(nameSpec, m_numLookups, m_conditionalUsage, m_conditionalType);
+
+	return result;
+}
+
+ShaderCompilerLoopCase::ShaderCompilerLoopCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, LoopType type, int numLoopIterations, int nestingDepth)
+	: ShaderCompilerCase	(context, name, description, caseID, avoidCache, addWhitespaceAndComments)
+	, m_numLoopIterations	(numLoopIterations)
+	, m_nestingDepth		(nestingDepth)
+	, m_isVertexCase		(isVertexCase)
+	, m_type				(type)
+{
+}
+
+ShaderCompilerLoopCase::~ShaderCompilerLoopCase (void)
+{
+}
+
+ShaderCompilerCase::ProgramContext ShaderCompilerLoopCase::generateShaderData (int measurementNdx) const
+{
+	deUint32		specID		= getSpecializationID(measurementNdx);
+	string			nameSpec	= getNameSpecialization(specID);
+	ProgramContext	result;
+
+	result.vertShaderSource		= specializeShaderSource(loopVertexTemplate(m_type, m_isVertexCase, m_numLoopIterations, m_nestingDepth), specID, SHADER_VALIDITY_VALID);
+	result.fragShaderSource		= specializeShaderSource(loopFragmentTemplate(m_type, m_isVertexCase, m_numLoopIterations, m_nestingDepth), specID, SHADER_VALIDITY_VALID);
+
+	result.vertexAttributes		= loopShaderAttributes(nameSpec, m_type, m_numLoopIterations);
+	result.uniforms				= loopShaderUniforms(nameSpec, m_type, m_numLoopIterations);
+
+	return result;
+}
+
+ShaderCompilerOperCase::ShaderCompilerOperCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, bool isVertexCase, const char* oper, int numOperations)
+	: ShaderCompilerCase	(context, name, description, caseID, avoidCache, addWhitespaceAndComments)
+	, m_oper				(oper)
+	, m_numOperations		(numOperations)
+	, m_isVertexCase		(isVertexCase)
+{
+}
+
+ShaderCompilerOperCase::~ShaderCompilerOperCase (void)
+{
+}
+
+ShaderCompilerCase::ProgramContext ShaderCompilerOperCase::generateShaderData (int measurementNdx) const
+{
+	deUint32		specID		= getSpecializationID(measurementNdx);
+	string			nameSpec	= getNameSpecialization(specID);
+	ProgramContext	result;
+
+	if (m_isVertexCase)
+	{
+		result.vertShaderSource = specializeShaderSource(binaryOpVertexTemplate(m_numOperations, m_oper.c_str()), specID, SHADER_VALIDITY_VALID);
+		result.fragShaderSource = specializeShaderSource(singleVaryingFragmentTemplate(), specID, SHADER_VALIDITY_VALID);
+	}
+	else
+	{
+		result.vertShaderSource = specializeShaderSource(singleVaryingVertexTemplate(), specID, SHADER_VALIDITY_VALID);
+		result.fragShaderSource = specializeShaderSource(binaryOpFragmentTemplate(m_numOperations, m_oper.c_str()), specID, SHADER_VALIDITY_VALID);
+	}
+
+	result.vertexAttributes = singleValueShaderAttributes(nameSpec);
+
+	result.uniforms.clear(); // No uniforms used.
+
+	return result;
+}
+
+ShaderCompilerMandelbrotCase::ShaderCompilerMandelbrotCase (Context& context, const char* name, const char* description, int caseID, bool avoidCache, bool addWhitespaceAndComments, int numFractalIterations)
+	: ShaderCompilerCase		(context, name, description, caseID, avoidCache, addWhitespaceAndComments)
+	, m_numFractalIterations	(numFractalIterations)
+{
+}
+
+ShaderCompilerMandelbrotCase::~ShaderCompilerMandelbrotCase (void)
+{
+}
+
+ShaderCompilerCase::ProgramContext ShaderCompilerMandelbrotCase::generateShaderData (int measurementNdx) const
+{
+	deUint32		specID		= getSpecializationID(measurementNdx);
+	string			nameSpec	= getNameSpecialization(specID);
+	ProgramContext	result;
+
+	result.vertShaderSource = specializeShaderSource(mandelbrotVertexTemplate(), specID, SHADER_VALIDITY_VALID);
+	result.fragShaderSource = specializeShaderSource(mandelbrotFragmentTemplate(m_numFractalIterations), specID, SHADER_VALIDITY_VALID);
+
+	result.vertexAttributes = mandelbrotShaderAttributes(nameSpec);
+	result.uniforms = mandelbrotShaderUniforms(nameSpec);
+
+	return result;
+}
+
+InvalidShaderCompilerCase::InvalidShaderCompilerCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType)
+	: TestCase						(context, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_invalidityType				(invalidityType)
+	, m_startHash					((deUint32)(deUint64Hash(deGetTime()) ^ deUint64Hash(deGetMicroseconds()) ^ deInt32Hash(caseID)))
+{
+	int cmdLineIterCount = context.getTestContext().getCommandLine().getTestIterationCount();
+	m_minimumMeasurementCount = cmdLineIterCount > 0 ? cmdLineIterCount : DEFAULT_MINIMUM_MEASUREMENT_COUNT;
+	m_maximumMeasurementCount = 3*m_minimumMeasurementCount;
+}
+
+InvalidShaderCompilerCase::~InvalidShaderCompilerCase (void)
+{
+}
+
+deUint32 InvalidShaderCompilerCase::getSpecializationID (int measurementNdx) const
+{
+	return m_startHash ^ (deUint32)deInt32Hash((deInt32)measurementNdx);
+}
+
+InvalidShaderCompilerCase::Shaders InvalidShaderCompilerCase::createShaders (void) const
+{
+	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();
+	Shaders					result;
+
+	result.vertShader = gl.createShader(GL_VERTEX_SHADER);
+	result.fragShader = gl.createShader(GL_FRAGMENT_SHADER);
+
+	return result;
+}
+
+void InvalidShaderCompilerCase::setShaderSources (const Shaders& shaders, const ProgramContext& progCtx) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	const char* vertShaderSourceCStr = progCtx.vertShaderSource.c_str();
+	const char* fragShaderSourceCStr = progCtx.fragShaderSource.c_str();
+	gl.shaderSource(shaders.vertShader, 1, &vertShaderSourceCStr, DE_NULL);
+	gl.shaderSource(shaders.fragShader, 1, &fragShaderSourceCStr, DE_NULL);
+}
+
+bool InvalidShaderCompilerCase::compileShader (deUint32 shader) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	GLint status;
+	gl.compileShader(shader);
+	gl.getShaderiv(shader, GL_COMPILE_STATUS, &status);
+	return status != 0;
+}
+
+void InvalidShaderCompilerCase::logProgramData (const BuildInfo& buildInfo, const ProgramContext& progCtx) const
+{
+	m_testCtx.getLog() << TestLog::ShaderProgram(false, "(No linking done)")
+					   << TestLog::Shader(QP_SHADER_TYPE_VERTEX,	progCtx.vertShaderSource, buildInfo.vertCompileSuccess, buildInfo.logs.vert)
+					   << TestLog::Shader(QP_SHADER_TYPE_FRAGMENT,	progCtx.fragShaderSource, buildInfo.fragCompileSuccess, buildInfo.logs.frag)
+					   << TestLog::EndShaderProgram;
+}
+
+InvalidShaderCompilerCase::Logs InvalidShaderCompilerCase::getLogs (const Shaders& shaders) const
+{
+	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();
+	Logs					result;
+
+	result.vert = getShaderInfoLog(gl, shaders.vertShader);
+	result.frag = getShaderInfoLog(gl, shaders.fragShader);
+
+	return result;
+}
+
+void InvalidShaderCompilerCase::cleanup (const Shaders& shaders) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.deleteShader(shaders.vertShader);
+	gl.deleteShader(shaders.fragShader);
+}
+
+bool InvalidShaderCompilerCase::goodEnoughMeasurements (const vector<Measurement>& measurements) const
+{
+	if ((int)measurements.size() < m_minimumMeasurementCount)
+		return false;
+	else
+	{
+		if ((int)measurements.size() >= m_maximumMeasurementCount)
+			return true;
+		else
+		{
+			vector<deInt64> totalTimes;
+			for (int i = 0; i < (int)measurements.size(); i++)
+				totalTimes.push_back(measurements[i].totalTime());
+			return vectorFloatRelativeMedianAbsoluteDeviation(vectorLowestPercentage(totalTimes, 0.5f)) < RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD;
+		}
+	}
+}
+
+InvalidShaderCompilerCase::IterateResult InvalidShaderCompilerCase::iterate (void)
+{
+	ShaderValidity shaderValidity = m_invalidityType == INVALIDITY_INVALID_CHAR		? SHADER_VALIDITY_INVALID_CHAR
+								  : m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+								  : SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	// Before actual measurements, compile a dummy shader to avoid possible initial slowdowns in the actual test.
+	{
+		deUint32		specID = getSpecializationID(0);
+		ProgramContext	progCtx;
+		progCtx.vertShaderSource = specializeShaderSource(singleVaryingVertexTemplate(), specID, shaderValidity);
+		progCtx.fragShaderSource = specializeShaderSource(singleVaryingFragmentTemplate(), specID, shaderValidity);
+
+		Shaders shaders = createShaders();
+		setShaderSources(shaders, progCtx);
+
+		BuildInfo buildInfo;
+		buildInfo.vertCompileSuccess = compileShader(shaders.vertShader);
+		buildInfo.fragCompileSuccess = compileShader(shaders.fragShader);
+		if (buildInfo.vertCompileSuccess || buildInfo.fragCompileSuccess)
+		{
+			buildInfo.logs = getLogs(shaders);
+			logProgramData(buildInfo, progCtx);
+			cleanup(shaders);
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compilation of a shader erroneously succeeded");
+			return STOP;
+		}
+		cleanup(shaders);
+	}
+
+	vector<Measurement>		measurements;
+	// \note These are logged after measurements are done.
+	ProgramContext			latestProgramContext;
+	BuildInfo				latestBuildInfo;
+
+	if (WARMUP_CPU_AT_BEGINNING_OF_CASE)
+		tcu::warmupCPU();
+
+	// Actual test measurements.
+	while (!goodEnoughMeasurements(measurements))
+	{
+		// Create shader and compile. Measure time.
+
+		// \note Shader sources are generated and GL shader objects are created before any time measurements.
+		ProgramContext	progCtx		= generateShaderSources((int)measurements.size());
+		Shaders			shaders		= createShaders();
+		BuildInfo		buildInfo;
+
+		if (WARMUP_CPU_BEFORE_EACH_MEASUREMENT)
+			tcu::warmupCPU();
+
+		// \note Do NOT do anything too hefty between the first and last deGetMicroseconds() here (other than the gl calls); it would disturb the measurement.
+
+		deUint64 startTime = deGetMicroseconds();
+
+		setShaderSources(shaders, progCtx);
+		deUint64 shaderSourceSetEndTime = deGetMicroseconds();
+
+		buildInfo.vertCompileSuccess = compileShader(shaders.vertShader);
+		deUint64 vertexShaderCompileEndTime = deGetMicroseconds();
+
+		buildInfo.fragCompileSuccess = compileShader(shaders.fragShader);
+		deUint64 fragmentShaderCompileEndTime = deGetMicroseconds();
+
+		buildInfo.logs = getLogs(shaders);
+
+		// Both shader compilations should have failed.
+		if (buildInfo.vertCompileSuccess || buildInfo.fragCompileSuccess)
+		{
+			logProgramData(buildInfo, progCtx);
+			cleanup(shaders);
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compilation of a shader erroneously succeeded");
+			return STOP;
+		}
+
+		// De-initializations (delete shaders).
+
+		cleanup(shaders);
+
+		// Output measurement log later (after last measurement).
+
+		measurements.push_back(Measurement((deInt64)(shaderSourceSetEndTime			- startTime),
+										   (deInt64)(vertexShaderCompileEndTime		- shaderSourceSetEndTime),
+										   (deInt64)(fragmentShaderCompileEndTime	- vertexShaderCompileEndTime)));
+
+		latestBuildInfo			= buildInfo;
+		latestProgramContext	= progCtx;
+
+		m_testCtx.touchWatchdog(); // \note Measurements may take a while in a bad case.
+	}
+
+	// End of test case, log information about measurements.
+	{
+		TestLog& log = m_testCtx.getLog();
+
+		vector<deInt64> sourceSetTimes;
+		vector<deInt64> vertexCompileTimes;
+		vector<deInt64> fragmentCompileTimes;
+		vector<deInt64> totalTimes;
+
+		log << TestLog::Section("IterationMeasurements", "Iteration measurements of compilation times");
+
+		for (int ndx = 0; ndx < (int)measurements.size(); ndx++)
+		{
+			sourceSetTimes.push_back		(measurements[ndx].sourceSetTime);
+			vertexCompileTimes.push_back	(measurements[ndx].vertexCompileTime);
+			fragmentCompileTimes.push_back	(measurements[ndx].fragmentCompileTime);
+			totalTimes.push_back			(measurements[ndx].totalTime());
+
+			// Log this measurement.
+			log << TestLog::Float("Measurement" + de::toString(ndx) + "Time",
+								  "Measurement " + de::toString(ndx) + " time",
+								  "ms", QP_KEY_TAG_TIME, measurements[ndx].totalTime()/1000.0f);
+		}
+
+		// Log some statistics.
+
+		for (int entireRangeOrLowestHalf = 0; entireRangeOrLowestHalf < 2; entireRangeOrLowestHalf++)
+		{
+			bool				isEntireRange	= entireRangeOrLowestHalf == 0;
+			string				statNamePrefix	= isEntireRange ? "" : "LowestHalf";
+			vector<deInt64>		rangeTimes		= isEntireRange ? totalTimes : vectorLowestPercentage(totalTimes, 0.5f);
+
+			log << TestLog::Message << "\nStatistics computed from "
+									<< (isEntireRange ? "all" : "only the lowest 50%")
+									<< " of the above measurements:"
+									<< TestLog::EndMessage;
+
+#define LOG_TIME_STAT(NAME, DESC, FUNC)			log << TestLog::Float(statNamePrefix + "TotalTime" + (NAME), (DESC) + string(" of total time"), "ms",	QP_KEY_TAG_TIME, (FUNC)(rangeTimes)/1000.0f)
+#define LOG_RELATIVE_STAT(NAME, DESC, FUNC)		log << TestLog::Float(statNamePrefix + "TotalTime" + (NAME), (DESC) + string(" of total time"), "",		QP_KEY_TAG_NONE, (FUNC)(rangeTimes))
+
+			LOG_TIME_STAT		("Median",							"Median",								vectorFloatMedian);
+			LOG_TIME_STAT		("Average",							"Average",								vectorFloatAverage);
+			LOG_TIME_STAT		("Minimum",							"Minimum",								vectorFloatMinimum);
+			LOG_TIME_STAT		("Maximum",							"Maximum",								vectorFloatMaximum);
+			LOG_TIME_STAT		("MedianAbsoluteDeviation",			"Median absolute deviation",			vectorFloatMedianAbsoluteDeviation);
+			LOG_RELATIVE_STAT	("RelativeMedianAbsoluteDeviation",	"Relative median absolute deviation",	vectorFloatRelativeMedianAbsoluteDeviation);
+			LOG_TIME_STAT		("StandardDeviation",				"Standard deviation",					vectorFloatStandardDeviation);
+			LOG_RELATIVE_STAT	("RelativeStandardDeviation",		"Relative standard deviation",			vectorFloatRelativeStandardDeviation);
+			LOG_TIME_STAT		("MaxMinusMin",						"Max-min",								vectorFloatMaximumMinusMinimum);
+			LOG_RELATIVE_STAT	("RelativeMaxMinusMin",				"Relative max-min",						vectorFloatRelativeMaximumMinusMinimum);
+
+#undef LOG_TIME_STAT
+#undef LOG_RELATIVE_STAT
+
+			if (!isEntireRange && vectorFloatRelativeMedianAbsoluteDeviation(rangeTimes) > RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD)
+				log << TestLog::Message << "\nWARNING: couldn't achieve relative median absolute deviation under threshold value " << RELATIVE_MEDIAN_ABSOLUTE_DEVIATION_THRESHOLD << TestLog::EndMessage;
+		}
+
+		log << TestLog::EndSection; // End section IterationMeasurements
+
+		for (int medianOrAverage = 0; medianOrAverage < 2; medianOrAverage++)
+		{
+			typedef float (*VecFunc)(const vector<deInt64>&);
+
+			bool	isMedian						= medianOrAverage == 0;
+			string	singular						= isMedian ? "Median" : "Average";
+			string	plural							= singular + "s";
+			VecFunc func							= isMedian ? (VecFunc) vectorFloatMedian<deInt64> : (VecFunc) vectorFloatAverage<deInt64>;
+
+			log << TestLog::Section(plural + "PerPhase", plural + " per phase");
+
+			for (int entireRangeOrLowestHalf = 0; entireRangeOrLowestHalf < 2; entireRangeOrLowestHalf++)
+			{
+				bool	isEntireRange	= entireRangeOrLowestHalf == 0;
+				string	statNamePrefix	= isEntireRange ? "" : "LowestHalf";
+				float	rangeSizeRatio	= isEntireRange ? 1.0f : 0.5f;
+
+#define LOG_TIME(NAME, DESC, DATA) log << TestLog::Float(statNamePrefix + (NAME) + singular, singular + " of " + (DESC), "ms", QP_KEY_TAG_TIME, func(vectorLowestPercentage((DATA), rangeSizeRatio))/1000.0f);
+
+				log << TestLog::Message << (isEntireRange ? "For all measurements:" : "\nFor only the lowest 50% of the measurements:") << TestLog::EndMessage;
+				LOG_TIME("ShaderSourceSetTime",			"shader source set time",			sourceSetTimes);
+				LOG_TIME("VertexShaderCompileTime",		"vertex shader compile time",		vertexCompileTimes);
+				LOG_TIME("FragmentShaderCompileTime",	"fragment shader compile time",		fragmentCompileTimes);
+
+#undef LOG_TIME
+			}
+
+			log << TestLog::EndSection;
+		}
+
+		// Set result.
+
+		{
+			log << TestLog::Message << "Note: test result is the first quartile (i.e. median of the lowest half of measurements) of total times" << TestLog::EndMessage;
+			float result = vectorFloatFirstQuartile(totalTimes) / 1000.0f;
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(result, 2).c_str());
+		}
+
+		// Log shaders.
+
+		log << TestLog::Message << "Note: the following shaders are the ones from the last iteration; variables' names and some constant expressions differ between iterations." << TestLog::EndMessage;
+
+		logProgramData(latestBuildInfo, latestProgramContext);
+
+		return STOP;
+	}
+}
+
+InvalidShaderCompilerLightCase::InvalidShaderCompilerLightCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool isVertexCase, int numLights, LightType lightType)
+	: InvalidShaderCompilerCase	(context, name, description, caseID, invalidityType)
+	, m_isVertexCase			(isVertexCase)
+	, m_numLights				(numLights)
+	, m_lightType				(lightType)
+{
+}
+
+InvalidShaderCompilerLightCase::~InvalidShaderCompilerLightCase (void)
+{
+}
+
+InvalidShaderCompilerCase::ProgramContext InvalidShaderCompilerLightCase::generateShaderSources (int measurementNdx) const
+{
+	deUint32		specID			= getSpecializationID(measurementNdx);
+	ProgramContext	result;
+	ShaderValidity	shaderValidity	= m_invalidityType == INVALIDITY_INVALID_CHAR	? SHADER_VALIDITY_INVALID_CHAR
+									: m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+									: SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	result.vertShaderSource = specializeShaderSource(lightVertexTemplate(m_numLights, m_isVertexCase, m_lightType), specID, shaderValidity);
+	result.fragShaderSource = specializeShaderSource(lightFragmentTemplate(m_numLights, m_isVertexCase, m_lightType), specID, shaderValidity);
+
+	return result;
+}
+
+InvalidShaderCompilerTextureCase::InvalidShaderCompilerTextureCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, int numLookups, ConditionalUsage conditionalUsage, ConditionalType conditionalType)
+	: InvalidShaderCompilerCase	(context, name, description, caseID, invalidityType)
+	, m_numLookups				(numLookups)
+	, m_conditionalUsage		(conditionalUsage)
+	, m_conditionalType			(conditionalType)
+{
+}
+
+InvalidShaderCompilerTextureCase::~InvalidShaderCompilerTextureCase (void)
+{
+}
+
+InvalidShaderCompilerCase::ProgramContext InvalidShaderCompilerTextureCase::generateShaderSources (int measurementNdx) const
+{
+	deUint32		specID			= getSpecializationID(measurementNdx);
+	ProgramContext	result;
+	ShaderValidity	shaderValidity	= m_invalidityType == INVALIDITY_INVALID_CHAR	? SHADER_VALIDITY_INVALID_CHAR
+									: m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+									: SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	result.vertShaderSource = specializeShaderSource(textureLookupVertexTemplate(m_conditionalUsage, m_conditionalType), specID, shaderValidity);
+	result.fragShaderSource = specializeShaderSource(textureLookupFragmentTemplate(m_numLookups, m_conditionalUsage, m_conditionalType), specID, shaderValidity);
+
+	return result;
+}
+
+InvalidShaderCompilerLoopCase::InvalidShaderCompilerLoopCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool isVertexCase, LoopType type, int numLoopIterations, int nestingDepth)
+	: InvalidShaderCompilerCase	(context, name, description, caseID, invalidityType)
+	, m_isVertexCase			(isVertexCase)
+	, m_numLoopIterations		(numLoopIterations)
+	, m_nestingDepth			(nestingDepth)
+	, m_type					(type)
+{
+}
+
+InvalidShaderCompilerLoopCase::~InvalidShaderCompilerLoopCase (void)
+{
+}
+
+InvalidShaderCompilerCase::ProgramContext InvalidShaderCompilerLoopCase::generateShaderSources (int measurementNdx) const
+{
+	deUint32		specID			= getSpecializationID(measurementNdx);
+	ProgramContext	result;
+	ShaderValidity	shaderValidity	= m_invalidityType == INVALIDITY_INVALID_CHAR	? SHADER_VALIDITY_INVALID_CHAR
+									: m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+									: SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	result.vertShaderSource = specializeShaderSource(loopVertexTemplate(m_type, m_isVertexCase, m_numLoopIterations, m_nestingDepth), specID, shaderValidity);
+	result.fragShaderSource = specializeShaderSource(loopFragmentTemplate(m_type, m_isVertexCase, m_numLoopIterations, m_nestingDepth), specID, shaderValidity);
+
+	return result;
+}
+
+InvalidShaderCompilerOperCase::InvalidShaderCompilerOperCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, bool isVertexCase, const char* oper, int numOperations)
+	: InvalidShaderCompilerCase	(context, name, description, caseID, invalidityType)
+	, m_isVertexCase			(isVertexCase)
+	, m_oper					(oper)
+	, m_numOperations			(numOperations)
+{
+}
+
+InvalidShaderCompilerOperCase::~InvalidShaderCompilerOperCase (void)
+{
+}
+
+InvalidShaderCompilerCase::ProgramContext InvalidShaderCompilerOperCase::generateShaderSources (int measurementNdx) const
+{
+	deUint32		specID			= getSpecializationID(measurementNdx);
+	ProgramContext	result;
+	ShaderValidity	shaderValidity	= m_invalidityType == INVALIDITY_INVALID_CHAR	? SHADER_VALIDITY_INVALID_CHAR
+									: m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+									: SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	if (m_isVertexCase)
+	{
+		result.vertShaderSource = specializeShaderSource(binaryOpVertexTemplate(m_numOperations, m_oper.c_str()), specID, shaderValidity);
+		result.fragShaderSource = specializeShaderSource(singleVaryingFragmentTemplate(), specID, shaderValidity);
+	}
+	else
+	{
+		result.vertShaderSource = specializeShaderSource(singleVaryingVertexTemplate(), specID, shaderValidity);
+		result.fragShaderSource = specializeShaderSource(binaryOpFragmentTemplate(m_numOperations, m_oper.c_str()), specID, shaderValidity);
+	}
+
+	return result;
+}
+
+InvalidShaderCompilerMandelbrotCase::InvalidShaderCompilerMandelbrotCase (Context& context, const char* name, const char* description, int caseID, InvalidityType invalidityType, int numFractalIterations)
+	: InvalidShaderCompilerCase	(context, name, description, caseID, invalidityType)
+	, m_numFractalIterations	(numFractalIterations)
+{
+}
+
+InvalidShaderCompilerMandelbrotCase::~InvalidShaderCompilerMandelbrotCase (void)
+{
+}
+
+InvalidShaderCompilerCase::ProgramContext InvalidShaderCompilerMandelbrotCase::generateShaderSources (int measurementNdx) const
+{
+	deUint32		specID			= getSpecializationID(measurementNdx);
+	ProgramContext	result;
+	ShaderValidity	shaderValidity	= m_invalidityType == INVALIDITY_INVALID_CHAR	? SHADER_VALIDITY_INVALID_CHAR
+									: m_invalidityType == INVALIDITY_SEMANTIC_ERROR	? SHADER_VALIDITY_SEMANTIC_ERROR
+									: SHADER_VALIDITY_LAST;
+
+	DE_ASSERT(shaderValidity != SHADER_VALIDITY_LAST);
+
+	result.vertShaderSource = specializeShaderSource(mandelbrotVertexTemplate(), specID, shaderValidity);
+	result.fragShaderSource = specializeShaderSource(mandelbrotFragmentTemplate(m_numFractalIterations), specID, shaderValidity);
+
+	return result;
+}
+
+void addShaderCompilationPerformanceCases (TestCaseGroup& parentGroup)
+{
+	Context&	context		= parentGroup.getContext();
+	int			caseID		= 0; // Increment this after adding each case. Used for avoiding cache hits between cases.
+
+	TestCaseGroup* validGroup			= new TestCaseGroup(context, "valid_shader",	"Valid Shader Compiler Cases");
+	TestCaseGroup* invalidGroup			= new TestCaseGroup(context, "invalid_shader",	"Invalid Shader Compiler Cases");
+	TestCaseGroup* cacheGroup			= new TestCaseGroup(context, "cache",			"Allow shader caching");
+	parentGroup.addChild(validGroup);
+	parentGroup.addChild(invalidGroup);
+	parentGroup.addChild(cacheGroup);
+
+	TestCaseGroup* invalidCharGroup		= new TestCaseGroup(context, "invalid_char",	"Invalid Character Shader Compiler Cases");
+	TestCaseGroup* semanticErrorGroup	= new TestCaseGroup(context, "semantic_error",	"Semantic Error Shader Compiler Cases");
+	invalidGroup->addChild(invalidCharGroup);
+	invalidGroup->addChild(semanticErrorGroup);
+
+	// Lighting shader compilation cases.
+
+	{
+		static const int lightCounts[] = { 1, 2, 4, 8 };
+
+		TestCaseGroup* validLightingGroup			= new TestCaseGroup(context, "lighting", "Shader Compiler Lighting Cases");
+		TestCaseGroup* invalidCharLightingGroup		= new TestCaseGroup(context, "lighting", "Invalid Character Shader Compiler Lighting Cases");
+		TestCaseGroup* semanticErrorLightingGroup	= new TestCaseGroup(context, "lighting", "Semantic Error Shader Compiler Lighting Cases");
+		TestCaseGroup* cacheLightingGroup			= new TestCaseGroup(context, "lighting", "Shader Compiler Lighting Cache Cases");
+		validGroup->addChild(validLightingGroup);
+		invalidCharGroup->addChild(invalidCharLightingGroup);
+		semanticErrorGroup->addChild(semanticErrorLightingGroup);
+		cacheGroup->addChild(cacheLightingGroup);
+
+		for (int lightType = 0; lightType < (int)LIGHT_LAST; lightType++)
+		{
+			const char* lightTypeName = lightType == (int)LIGHT_DIRECTIONAL	? "directional"
+									  : lightType == (int)LIGHT_POINT		? "point"
+									  : DE_NULL;
+
+			DE_ASSERT(lightTypeName != DE_NULL);
+
+			for (int isFrag = 0; isFrag <= 1; isFrag++)
+			{
+				bool		isVertex	= isFrag == 0;
+				const char*	vertFragStr	= isVertex ? "vertex" : "fragment";
+
+				for (int lightCountNdx = 0; lightCountNdx < DE_LENGTH_OF_ARRAY(lightCounts); lightCountNdx++)
+				{
+					int numLights = lightCounts[lightCountNdx];
+
+					string caseName = string("") + lightTypeName + "_" + de::toString(numLights) + "_lights_" + vertFragStr;
+
+					// Valid shader case, no-cache and cache versions.
+
+					validLightingGroup->addChild(new ShaderCompilerLightCase(context, caseName.c_str(), "", caseID++, true  /* avoid cache */, false, isVertex, numLights, (LightType)lightType));
+					cacheLightingGroup->addChild(new ShaderCompilerLightCase(context, caseName.c_str(), "", caseID++, false /* allow cache */, false, isVertex, numLights, (LightType)lightType));
+
+					// Invalid shader cases.
+
+					for (int invalidityType = 0; invalidityType < (int)InvalidShaderCompilerCase::INVALIDITY_LAST; invalidityType++)
+					{
+						TestCaseGroup* curInvalidGroup	= invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_INVALID_CHAR		? invalidCharLightingGroup
+														: invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_SEMANTIC_ERROR	? semanticErrorLightingGroup
+														: DE_NULL;
+
+						DE_ASSERT(curInvalidGroup != DE_NULL);
+
+						curInvalidGroup->addChild(new InvalidShaderCompilerLightCase(context, caseName.c_str(), "", caseID++, (InvalidShaderCompilerCase::InvalidityType)invalidityType, isVertex, numLights, (LightType)lightType));
+					}
+				}
+			}
+		}
+	}
+
+	// Texture lookup shader compilation cases.
+
+	{
+		static const int texLookupCounts[] = { 1, 2, 4, 8 };
+
+		TestCaseGroup* validTexGroup			= new TestCaseGroup(context, "texture", "Shader Compiler Texture Lookup Cases");
+		TestCaseGroup* invalidCharTexGroup		= new TestCaseGroup(context, "texture", "Invalid Character Shader Compiler Texture Lookup Cases");
+		TestCaseGroup* semanticErrorTexGroup	= new TestCaseGroup(context, "texture", "Semantic Error Shader Compiler Texture Lookup Cases");
+		TestCaseGroup* cacheTexGroup			= new TestCaseGroup(context, "texture", "Shader Compiler Texture Lookup Cache Cases");
+		validGroup->addChild(validTexGroup);
+		invalidCharGroup->addChild(invalidCharTexGroup);
+		semanticErrorGroup->addChild(semanticErrorTexGroup);
+		cacheGroup->addChild(cacheTexGroup);
+
+		for (int conditionalUsage = 0; conditionalUsage < (int)CONDITIONAL_USAGE_LAST; conditionalUsage++)
+		{
+			const char* conditionalUsageName = conditionalUsage == (int)CONDITIONAL_USAGE_NONE			? "no_conditionals"
+											 : conditionalUsage == (int)CONDITIONAL_USAGE_FIRST_HALF	? "first_half"
+											 : conditionalUsage == (int)CONDITIONAL_USAGE_EVERY_OTHER	? "every_other"
+											 : DE_NULL;
+
+			DE_ASSERT(conditionalUsageName != DE_NULL);
+
+			int lastConditionalType = conditionalUsage == (int)CONDITIONAL_USAGE_NONE ? 1 : (int)CONDITIONAL_TYPE_LAST;
+
+			for (int conditionalType = 0; conditionalType < lastConditionalType; conditionalType++)
+			{
+				const char* conditionalTypeName = conditionalType == (int)CONDITIONAL_TYPE_STATIC	? "static_conditionals"
+												: conditionalType == (int)CONDITIONAL_TYPE_UNIFORM	? "uniform_conditionals"
+												: conditionalType == (int)CONDITIONAL_TYPE_DYNAMIC	? "dynamic_conditionals"
+												: DE_NULL;
+
+				DE_ASSERT(conditionalTypeName != DE_NULL);
+
+				for (int lookupCountNdx = 0; lookupCountNdx < DE_LENGTH_OF_ARRAY(texLookupCounts); lookupCountNdx++)
+				{
+					int numLookups = texLookupCounts[lookupCountNdx];
+
+					string caseName = de::toString(numLookups) + "_lookups_" + conditionalUsageName + (conditionalUsage == (int)CONDITIONAL_USAGE_NONE ? "" : string("_") + conditionalTypeName);
+
+					// Valid shader case, no-cache and cache versions.
+
+					validTexGroup->addChild(new ShaderCompilerTextureCase(context, caseName.c_str(), "", caseID++, true  /* avoid cache */, false, numLookups, (ConditionalUsage)conditionalUsage, (ConditionalType)conditionalType));
+					cacheTexGroup->addChild(new ShaderCompilerTextureCase(context, caseName.c_str(), "", caseID++, false /* allow cache */, false, numLookups, (ConditionalUsage)conditionalUsage, (ConditionalType)conditionalType));
+
+					// Invalid shader cases.
+
+					for (int invalidityType = 0; invalidityType < (int)InvalidShaderCompilerCase::INVALIDITY_LAST; invalidityType++)
+					{
+						TestCaseGroup* curInvalidGroup	= invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_INVALID_CHAR		? invalidCharTexGroup
+														: invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_SEMANTIC_ERROR	? semanticErrorTexGroup
+														: DE_NULL;
+
+						DE_ASSERT(curInvalidGroup != DE_NULL);
+
+						curInvalidGroup->addChild(new InvalidShaderCompilerTextureCase(context, caseName.c_str(), "", caseID++, (InvalidShaderCompilerCase::InvalidityType)invalidityType, numLookups, (ConditionalUsage)conditionalUsage, (ConditionalType)conditionalType));
+					}
+				}
+			}
+		}
+	}
+
+	// Loop shader compilation cases.
+
+	{
+		static const int loopIterCounts[]		= { 10, 100, 1000 };
+		static const int maxLoopNestingDepth	= 3;
+		static const int maxTotalLoopIterations	= 2000; // If <loop iteration count> ** <loop nesting depth> (where ** is exponentiation) exceeds this, don't generate the case.
+
+		TestCaseGroup* validLoopGroup			= new TestCaseGroup(context, "loop", "Shader Compiler Loop Cases");
+		TestCaseGroup* invalidCharLoopGroup		= new TestCaseGroup(context, "loop", "Invalid Character Shader Compiler Loop Cases");
+		TestCaseGroup* semanticErrorLoopGroup	= new TestCaseGroup(context, "loop", "Semantic Error Shader Compiler Loop Cases");
+		TestCaseGroup* cacheLoopGroup			= new TestCaseGroup(context, "loop", "Shader Compiler Loop Cache Cases");
+		validGroup->addChild(validLoopGroup);
+		invalidCharGroup->addChild(invalidCharLoopGroup);
+		semanticErrorGroup->addChild(semanticErrorLoopGroup);
+		cacheGroup->addChild(cacheLoopGroup);
+
+		for (int loopType = 0; loopType < (int)LOOP_LAST; loopType++)
+		{
+			const char* loopTypeName = loopType == (int)LOOP_TYPE_STATIC	? "static"
+									 : loopType == (int)LOOP_TYPE_UNIFORM	? "uniform"
+									 : loopType == (int)LOOP_TYPE_DYNAMIC	? "dynamic"
+									 : DE_NULL;
+
+			DE_ASSERT(loopTypeName != DE_NULL);
+
+			TestCaseGroup* validLoopTypeGroup			= new TestCaseGroup(context, loopTypeName, "");
+			TestCaseGroup* invalidCharLoopTypeGroup		= new TestCaseGroup(context, loopTypeName, "");
+			TestCaseGroup* semanticErrorLoopTypeGroup	= new TestCaseGroup(context, loopTypeName, "");
+			TestCaseGroup* cacheLoopTypeGroup			= new TestCaseGroup(context, loopTypeName, "");
+			validLoopGroup->addChild(validLoopTypeGroup);
+			invalidCharLoopGroup->addChild(invalidCharLoopTypeGroup);
+			semanticErrorLoopGroup->addChild(semanticErrorLoopTypeGroup);
+			cacheLoopGroup->addChild(cacheLoopTypeGroup);
+
+			for (int isFrag = 0; isFrag <= 1; isFrag++)
+			{
+				bool		isVertex	= isFrag == 0;
+				const char*	vertFragStr	= isVertex ? "vertex" : "fragment";
+
+				// \note Non-static loop cases with different iteration counts have identical shaders, so only make one of each.
+				int loopIterCountMaxNdx = loopType != (int)LOOP_TYPE_STATIC ? 1 : DE_LENGTH_OF_ARRAY(loopIterCounts);
+
+				for (int nestingDepth = 1; nestingDepth <= maxLoopNestingDepth; nestingDepth++)
+				{
+					for (int loopIterCountNdx = 0; loopIterCountNdx < loopIterCountMaxNdx; loopIterCountNdx++)
+					{
+						int numIterations = loopIterCounts[loopIterCountNdx];
+
+						if (deFloatPow((float)numIterations, (float)nestingDepth) > (float)maxTotalLoopIterations)
+							continue; // Don't generate too heavy tasks.
+
+						string validCaseName = de::toString(numIterations) + "_iterations_" + de::toString(nestingDepth) + "_levels_" + vertFragStr;
+
+						// Valid shader case, no-cache and cache versions.
+
+						validLoopTypeGroup->addChild(new ShaderCompilerLoopCase(context, validCaseName.c_str(), "", caseID++, true  /* avoid cache */, false, isVertex, (LoopType)loopType, numIterations, nestingDepth));
+						cacheLoopTypeGroup->addChild(new ShaderCompilerLoopCase(context, validCaseName.c_str(), "", caseID++, false /* allow cache */, false, isVertex, (LoopType)loopType, numIterations, nestingDepth));
+
+						// Invalid shader cases.
+
+						for (int invalidityType = 0; invalidityType < (int)InvalidShaderCompilerCase::INVALIDITY_LAST; invalidityType++)
+						{
+							TestCaseGroup* curInvalidGroup	= invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_INVALID_CHAR		? invalidCharLoopTypeGroup
+															: invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_SEMANTIC_ERROR	? semanticErrorLoopTypeGroup
+															: DE_NULL;
+
+							DE_ASSERT(curInvalidGroup != DE_NULL);
+
+							string invalidCaseName = de::toString(nestingDepth) + "_levels_" + vertFragStr;
+
+							if (loopType == (int)LOOP_TYPE_STATIC)
+								invalidCaseName = de::toString(numIterations) + "_iterations_" + invalidCaseName; // \note For invalid, non-static loop cases the iteration count means nothing (since no uniforms or attributes are set).
+
+							curInvalidGroup->addChild(new InvalidShaderCompilerLoopCase(context, invalidCaseName.c_str(), "", caseID++, (InvalidShaderCompilerCase::InvalidityType)invalidityType, isVertex, (LoopType)loopType, numIterations, nestingDepth));
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// Multiplication shader compilation cases.
+
+	{
+		static const int multiplicationCounts[] = { 10, 100, 1000 };
+
+		TestCaseGroup* validMulGroup			= new TestCaseGroup(context, "multiplication", "Shader Compiler Multiplication Cases");
+		TestCaseGroup* invalidCharMulGroup		= new TestCaseGroup(context, "multiplication", "Invalid Character Shader Compiler Multiplication Cases");
+		TestCaseGroup* semanticErrorMulGroup	= new TestCaseGroup(context, "multiplication", "Semantic Error Shader Compiler Multiplication Cases");
+		TestCaseGroup* cacheMulGroup			= new TestCaseGroup(context, "multiplication", "Shader Compiler Multiplication Cache Cases");
+		validGroup->addChild(validMulGroup);
+		invalidCharGroup->addChild(invalidCharMulGroup);
+		semanticErrorGroup->addChild(semanticErrorMulGroup);
+		cacheGroup->addChild(cacheMulGroup);
+
+		for (int isFrag = 0; isFrag <= 1; isFrag++)
+		{
+			bool		isVertex	= isFrag == 0;
+			const char*	vertFragStr	= isVertex ? "vertex" : "fragment";
+
+			for (int operCountNdx = 0; operCountNdx < DE_LENGTH_OF_ARRAY(multiplicationCounts); operCountNdx++)
+			{
+				int numOpers = multiplicationCounts[operCountNdx];
+
+				string caseName = de::toString(numOpers) + "_operations_" + vertFragStr;
+
+				// Valid shader case, no-cache and cache versions.
+
+				validMulGroup->addChild(new ShaderCompilerOperCase(context, caseName.c_str(), "", caseID++, true  /* avoid cache */, false, isVertex, "*", numOpers));
+				cacheMulGroup->addChild(new ShaderCompilerOperCase(context, caseName.c_str(), "", caseID++, false /* allow cache */, false, isVertex, "*", numOpers));
+
+				// Invalid shader cases.
+
+				for (int invalidityType = 0; invalidityType < (int)InvalidShaderCompilerCase::INVALIDITY_LAST; invalidityType++)
+				{
+					TestCaseGroup* curInvalidGroup	= invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_INVALID_CHAR		? invalidCharMulGroup
+													: invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_SEMANTIC_ERROR	? semanticErrorMulGroup
+													: DE_NULL;
+
+					DE_ASSERT(curInvalidGroup != DE_NULL);
+
+					curInvalidGroup->addChild(new InvalidShaderCompilerOperCase(context, caseName.c_str(), "", caseID++, (InvalidShaderCompilerCase::InvalidityType)invalidityType, isVertex, "*", numOpers));
+				}
+			}
+		}
+	}
+
+	// Mandelbrot shader compilation cases.
+
+	{
+		static const int mandelbrotIterationCounts[] = { 32, 64, 128 };
+
+		TestCaseGroup* validMandelbrotGroup			= new TestCaseGroup(context, "mandelbrot", "Shader Compiler Mandelbrot Fractal Cases");
+		TestCaseGroup* invalidCharMandelbrotGroup	= new TestCaseGroup(context, "mandelbrot", "Invalid Character Shader Compiler Mandelbrot Fractal Cases");
+		TestCaseGroup* semanticErrorMandelbrotGroup	= new TestCaseGroup(context, "mandelbrot", "Semantic Error Shader Compiler Mandelbrot Fractal Cases");
+		TestCaseGroup* cacheMandelbrotGroup			= new TestCaseGroup(context, "mandelbrot", "Shader Compiler Mandelbrot Fractal Cache Cases");
+		validGroup->addChild(validMandelbrotGroup);
+		invalidCharGroup->addChild(invalidCharMandelbrotGroup);
+		semanticErrorGroup->addChild(semanticErrorMandelbrotGroup);
+		cacheGroup->addChild(cacheMandelbrotGroup);
+
+		for (int iterCountNdx = 0; iterCountNdx < DE_LENGTH_OF_ARRAY(mandelbrotIterationCounts); iterCountNdx++)
+		{
+			int		numFractalIterations	= mandelbrotIterationCounts[iterCountNdx];
+			string	caseName				= de::toString(numFractalIterations) + "_iterations";
+
+			// Valid shader case, no-cache and cache versions.
+
+			validMandelbrotGroup->addChild(new ShaderCompilerMandelbrotCase(context, caseName.c_str(), "", caseID++, true  /* avoid cache */, false, numFractalIterations));
+			cacheMandelbrotGroup->addChild(new ShaderCompilerMandelbrotCase(context, caseName.c_str(), "", caseID++, false /* allow cache */, false, numFractalIterations));
+
+			// Invalid shader cases.
+
+			for (int invalidityType = 0; invalidityType < (int)InvalidShaderCompilerCase::INVALIDITY_LAST; invalidityType++)
+			{
+				TestCaseGroup* curInvalidGroup	= invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_INVALID_CHAR		? invalidCharMandelbrotGroup
+												: invalidityType == (int)InvalidShaderCompilerCase::INVALIDITY_SEMANTIC_ERROR	? semanticErrorMandelbrotGroup
+												: DE_NULL;
+
+				DE_ASSERT(curInvalidGroup != DE_NULL);
+
+				curInvalidGroup->addChild(new InvalidShaderCompilerMandelbrotCase(context, caseName.c_str(), "", caseID++, (InvalidShaderCompilerCase::InvalidityType)invalidityType, numFractalIterations));
+			}
+		}
+	}
+
+	// Cases testing cache behaviour when whitespace and comments are added.
+
+	{
+		TestCaseGroup* whitespaceCommentCacheGroup = new TestCaseGroup(context, "cache_whitespace_comment", "Cases testing the effect of whitespace and comments on caching");
+		parentGroup.addChild(whitespaceCommentCacheGroup);
+
+		// \note Add just a small subset of the cases that were added above for the main performance tests.
+
+		// Cases with both vertex and fragment variants.
+		for (int isFrag = 0; isFrag <= 1; isFrag++)
+		{
+			bool	isVertex		= isFrag == 0;
+			string	vtxFragSuffix	= isVertex ? "_vertex" : "_fragment";
+			string	dirLightName	= "directional_2_lights" + vtxFragSuffix;
+			string	loopName		= "static_loop_100_iterations" + vtxFragSuffix;
+			string	multCase		= "multiplication_100_operations" + vtxFragSuffix;
+
+			whitespaceCommentCacheGroup->addChild(new ShaderCompilerLightCase(context, dirLightName.c_str(), "", caseID++, false, true, isVertex, 2, LIGHT_DIRECTIONAL));
+			whitespaceCommentCacheGroup->addChild(new ShaderCompilerLoopCase(context, loopName.c_str(), "", caseID++, false, true, isVertex, LOOP_TYPE_STATIC, 100, 1));
+			whitespaceCommentCacheGroup->addChild(new ShaderCompilerOperCase(context, multCase.c_str(), "", caseID++, false, true, isVertex, "*", 100));
+		}
+
+		// Cases that don't have vertex and fragment variants.
+		whitespaceCommentCacheGroup->addChild(new ShaderCompilerTextureCase(context, "texture_4_lookups", "", caseID++, false, true, 4, CONDITIONAL_USAGE_NONE, CONDITIONAL_TYPE_STATIC));
+		whitespaceCommentCacheGroup->addChild(new ShaderCompilerMandelbrotCase(context, "mandelbrot_32_operations", "", caseID++, false, true, 32));
+	}
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pShaderCompilationCases.hpp b/modules/gles3/performance/es3pShaderCompilationCases.hpp
new file mode 100644
index 0000000..f3e0994
--- /dev/null
+++ b/modules/gles3/performance/es3pShaderCompilationCases.hpp
@@ -0,0 +1,42 @@
+#ifndef _ES3PSHADERCOMPILATIONCASES_HPP
+#define _ES3PSHADERCOMPILATIONCASES_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader compilation performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+void addShaderCompilationPerformanceCases (TestCaseGroup& parentGroup);
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PSHADERCOMPILATIONCASES_HPP
diff --git a/modules/gles3/performance/es3pShaderCompilerTests.cpp b/modules/gles3/performance/es3pShaderCompilerTests.cpp
new file mode 100644
index 0000000..8f57877
--- /dev/null
+++ b/modules/gles3/performance/es3pShaderCompilerTests.cpp
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader compiler-related performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pShaderCompilerTests.hpp"
+#include "es3pShaderCompilationCases.hpp"
+#include "es3pShaderOptimizationTests.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+ShaderCompilerTests::ShaderCompilerTests (Context& context)
+	: TestCaseGroup(context, "compiler", "Shader Compiler Performance Tests")
+{
+}
+
+ShaderCompilerTests::~ShaderCompilerTests (void)
+{
+}
+
+void ShaderCompilerTests::init (void)
+{
+	addShaderCompilationPerformanceCases(*this);
+
+	addChild(new ShaderOptimizationTests(m_context));
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pShaderCompilerTests.hpp b/modules/gles3/performance/es3pShaderCompilerTests.hpp
new file mode 100644
index 0000000..5d04834
--- /dev/null
+++ b/modules/gles3/performance/es3pShaderCompilerTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PSHADERCOMPILERTESTS_HPP
+#define _ES3PSHADERCOMPILERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader compiler-related performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class ShaderCompilerTests : public TestCaseGroup
+{
+public:
+							ShaderCompilerTests		(Context& context);
+							~ShaderCompilerTests	(void);
+
+	void					init					(void);
+
+private:
+							ShaderCompilerTests		(const ShaderCompilerTests& other);
+	ShaderCompilerTests&	operator=				(const ShaderCompilerTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PSHADERCOMPILERTESTS_HPP
diff --git a/modules/gles3/performance/es3pShaderControlStatementTests.cpp b/modules/gles3/performance/es3pShaderControlStatementTests.cpp
new file mode 100644
index 0000000..e827b95
--- /dev/null
+++ b/modules/gles3/performance/es3pShaderControlStatementTests.cpp
@@ -0,0 +1,977 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader control statement performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pShaderControlStatementTests.hpp"
+#include "glsShaderPerformanceCase.hpp"
+#include "tcuTestLog.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <string>
+#include <vector>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+using namespace gls;
+using namespace glw; // GL types
+using tcu::Vec4;
+using tcu::TestLog;
+using std::string;
+using std::vector;
+
+// Writes the workload expression used in conditional tests.
+static void writeConditionalWorkload (std::ostringstream& stream, const char* resultName, const char* operandName)
+{
+	const int numMultiplications = 64;
+
+	stream << resultName << " = ";
+
+	for (int i = 0; i < numMultiplications; i++)
+	{
+		if (i > 0)
+			stream << "*";
+
+		stream << operandName;
+	}
+
+	stream << ";";
+}
+
+// Writes the workload expression used in loop tests (one iteration).
+static void writeLoopWorkload (std::ostringstream& stream, const char* resultName, const char* operandName)
+{
+	const int numMultiplications = 8;
+
+	stream << resultName << " = ";
+
+	for (int i = 0; i < numMultiplications; i++)
+	{
+		if (i > 0)
+			stream << " * ";
+
+		stream << "(" << resultName << " + " << operandName << ")";
+	}
+
+	stream << ";";
+}
+
+// The type of decision to be made in a conditional expression.
+// \note In fragment cases with DECISION_ATTRIBUTE, the value in the expression will actually be a varying.
+enum DecisionType
+{
+	DECISION_STATIC = 0,
+	DECISION_UNIFORM,
+	DECISION_ATTRIBUTE,
+
+	DECISION_LAST
+};
+
+class ControlStatementCase :  public ShaderPerformanceCase
+{
+public:
+	ControlStatementCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, gls::PerfCaseType caseType)
+		: ShaderPerformanceCase(testCtx, renderCtx, name, description, caseType)
+	{
+	}
+
+	void init (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Using additive blending." << TestLog::EndMessage;
+		ShaderPerformanceCase::init();
+	}
+
+	void setupRenderState (void)
+	{
+		const glw::Functions& gl = m_renderCtx.getFunctions();
+
+		gl.enable(GL_BLEND);
+		gl.blendEquation(GL_FUNC_ADD);
+		gl.blendFunc(GL_ONE, GL_ONE);
+	}
+};
+
+class ConditionalCase : public ControlStatementCase
+{
+public:
+	enum BranchResult
+	{
+		BRANCH_TRUE = 0,
+		BRANCH_FALSE,
+		BRANCH_MIXED,
+
+		BRANCH_LAST
+	};
+
+	enum WorkloadDivision
+	{
+		WORKLOAD_DIVISION_EVEN = 0,		//! Both true and false branches contain same amount of computation.
+		WORKLOAD_DIVISION_TRUE_HEAVY,	//! True branch contains more computation.
+		WORKLOAD_DIVISION_FALSE_HEAVY,	//! False branch contains more computation.
+
+		WORKLOAD_DIVISION_LAST
+	};
+
+						ConditionalCase		(Context& context, const char* name, const char* description, DecisionType decisionType, BranchResult branchType, WorkloadDivision workloadDivision, bool isVertex);
+						~ConditionalCase	(void);
+
+	void				init				(void);
+	void				deinit				(void);
+	void				setupProgram		(deUint32 program);
+
+private:
+	DecisionType		m_decisionType;
+	BranchResult		m_branchType;
+	WorkloadDivision	m_workloadDivision;
+
+	vector<float>		m_comparisonValueArray; // Will contain per-vertex comparison values if using mixed branch type in vertex case.
+	deUint32			m_arrayBuffer;
+};
+
+ConditionalCase::ConditionalCase (Context& context, const char* name, const char* description, DecisionType decisionType, BranchResult branchType, WorkloadDivision workloadDivision, bool isVertex)
+	: ControlStatementCase			(context.getTestContext(), context.getRenderContext(), name, description, isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT)
+	, m_decisionType				(decisionType)
+	, m_branchType					(branchType)
+	, m_workloadDivision			(workloadDivision)
+	, m_arrayBuffer					(0)
+{
+}
+
+void ConditionalCase::init (void)
+{
+	bool			isVertexCase		= m_caseType == CASETYPE_VERTEX;
+
+	bool			isStaticCase		= m_decisionType == DECISION_STATIC;
+	bool			isUniformCase		= m_decisionType == DECISION_UNIFORM;
+	bool			isAttributeCase		= m_decisionType == DECISION_ATTRIBUTE;
+
+	DE_ASSERT(isStaticCase || isUniformCase || isAttributeCase);
+
+	bool			isConditionTrue		= m_branchType == BRANCH_TRUE;
+	bool			isConditionFalse	= m_branchType == BRANCH_FALSE;
+	bool			isConditionMixed	= m_branchType == BRANCH_MIXED;
+
+	DE_ASSERT(isConditionTrue || isConditionFalse || isConditionMixed);
+	DE_UNREF(isConditionFalse);
+
+	DE_ASSERT(isAttributeCase || !isConditionMixed); // The branch taken can vary between executions only if using attribute input.
+
+	const char*		staticCompareValueStr	= isConditionTrue	? "1.0" : "-1.0";
+	const char*		compareValueStr			= isStaticCase		? staticCompareValueStr :
+											  isUniformCase		? "u_compareValue" :
+											  isVertexCase		? "a_compareValue" :
+																  "v_compareValue";
+
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op		= isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n";
+	vtx << "in highp vec4 a_position;\n";	// Position attribute.
+	vtx << "in mediump vec4 a_value0;\n";	// Input for workload calculations of "true" branch.
+	vtx << "in mediump vec4 a_value1;\n";	// Input for workload calculations of "false" branch.
+
+	frag << "#version 300 es\n";
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	// Value to be used in the conditional expression.
+	if (isAttributeCase)
+		vtx << "in mediump float a_compareValue;\n";
+	else if (isUniformCase)
+		op << "uniform mediump float u_compareValue;\n";
+
+	// Varyings.
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "out mediump vec4 v_value0;\n";
+		vtx << "out mediump vec4 v_value1;\n";
+		frag << "in mediump vec4 v_value0;\n";
+		frag << "in mediump vec4 v_value1;\n";
+
+		if (isAttributeCase)
+		{
+			vtx << "out mediump float v_compareValue;\n";
+			frag << "in mediump float v_compareValue;\n";
+		}
+	}
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	op << "	mediump vec4 res;\n";
+
+	string condition;
+
+	if (isConditionMixed && !isVertexCase)
+		condition = string("") + "fract(" + compareValueStr + ") < 0.5"; // Comparison result varies with high frequency.
+	else
+		condition = string("") + compareValueStr + " > 0.0";
+
+	op << "	if (" << condition << ")\n";
+	op << "	{\n";
+
+	op << "\t\t";
+	if (m_workloadDivision == WORKLOAD_DIVISION_EVEN || m_workloadDivision == WORKLOAD_DIVISION_TRUE_HEAVY)
+		writeConditionalWorkload(op, "res", isVertexCase ? "a_value0" : "v_value0"); // Workload calculation for the "true" branch.
+	else
+		op << "res = " << (isVertexCase ? "a_value0" : "v_value0") << ";";
+	op << "\n";
+
+	op << "	}\n";
+	op << "	else\n";
+	op << "	{\n";
+
+	op << "\t\t";
+	if (m_workloadDivision == WORKLOAD_DIVISION_EVEN || m_workloadDivision == WORKLOAD_DIVISION_FALSE_HEAVY)
+		writeConditionalWorkload(op, "res", isVertexCase ? "a_value1" : "v_value1"); // Workload calculations for the "false" branch.
+	else
+		op << "res = " << (isVertexCase ? "a_value1" : "v_value1") << ";";
+	op << "\n";
+
+	op << "	}\n";
+
+	if (isVertexCase)
+	{
+		// Put result to color variable.
+		vtx << "	v_color = res;\n";
+		frag << "	o_color = v_color;\n";
+	}
+	else
+	{
+		// Transfer inputs to fragment shader through varyings.
+		if (isAttributeCase)
+			vtx << "	v_compareValue = a_compareValue;\n";
+		vtx << "	v_value0 = a_value0;\n";
+		vtx << "	v_value1 = a_value1;\n";
+
+		frag << "	o_color = res;\n"; // Put result to color variable.
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource = vtx.str();
+	m_fragShaderSource = frag.str();
+
+	if (isAttributeCase)
+	{
+		if (!isConditionMixed)
+		{
+			// Every execution takes the same branch.
+
+			float value = isConditionTrue ? +1.0f : -1.0f;
+			m_attributes.push_back(AttribSpec("a_compareValue",	Vec4(value, 0.0f, 0.0f, 0.0f),
+																Vec4(value, 0.0f, 0.0f, 0.0f),
+																Vec4(value, 0.0f, 0.0f, 0.0f),
+																Vec4(value, 0.0f, 0.0f, 0.0f)));
+		}
+		else if (isVertexCase)
+		{
+			// Vertex case, not every execution takes the same branch.
+
+			const int	numComponents	= 4;
+			int			numVertices		= (getGridWidth() + 1) * (getGridHeight() + 1);
+
+			// setupProgram() will later bind this array as an attribute.
+			m_comparisonValueArray.resize(numVertices * numComponents);
+
+			// Make every second vertex take the true branch, and every second the false branch.
+			for (int i = 0; i < (int)m_comparisonValueArray.size(); i++)
+			{
+				if (i % numComponents == 0)
+					m_comparisonValueArray[i] = (i / numComponents) % 2 == 0 ? +1.0f : -1.0f;
+				else
+					m_comparisonValueArray[i] = 0.0f;
+			}
+		}
+		else // isConditionMixed && !isVertexCase
+		{
+			// Fragment case, not every execution takes the same branch.
+			// \note fract(a_compareValue) < 0.5 will be true for every second column of fragments.
+
+			float minValue = 0.0f;
+			float maxValue = (float)getViewportWidth()*0.5f;
+			m_attributes.push_back(AttribSpec("a_compareValue",	Vec4(minValue, 0.0f, 0.0f, 0.0f),
+																Vec4(maxValue, 0.0f, 0.0f, 0.0f),
+																Vec4(minValue, 0.0f, 0.0f, 0.0f),
+																Vec4(maxValue, 0.0f, 0.0f, 0.0f)));
+		}
+	}
+
+	m_attributes.push_back(AttribSpec("a_value0",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
+													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
+													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
+													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));
+
+	m_attributes.push_back(AttribSpec("a_value1",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
+													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
+													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
+													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));
+
+	ControlStatementCase::init();
+}
+
+void ConditionalCase::setupProgram (deUint32 program)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_decisionType == DECISION_UNIFORM)
+	{
+		int location = gl.getUniformLocation(program, "u_compareValue");
+		gl.uniform1f(location, m_branchType == BRANCH_TRUE ? +1.0f : -1.0f);
+	}
+	else if (m_decisionType == DECISION_ATTRIBUTE && m_branchType == BRANCH_MIXED && m_caseType == CASETYPE_VERTEX)
+	{
+		// Setup per-vertex comparison values calculated in init().
+
+		const int	numComponents			= 4;
+		int			compareAttribLocation	= gl.getAttribLocation(program, "a_compareValue");
+
+		DE_ASSERT((int)m_comparisonValueArray.size() == numComponents * (getGridWidth() + 1) * (getGridHeight() + 1));
+
+		gl.genBuffers(1, &m_arrayBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(m_comparisonValueArray.size()*sizeof(float)), &m_comparisonValueArray[0], GL_STATIC_DRAW);
+		gl.enableVertexAttribArray(compareAttribLocation);
+		gl.vertexAttribPointer(compareAttribLocation, (GLint)numComponents, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Setup program state");
+}
+
+ConditionalCase::~ConditionalCase (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_arrayBuffer != 0)
+	{
+		gl.deleteBuffers(1, &m_arrayBuffer);
+		m_arrayBuffer = 0;
+	}
+}
+
+void ConditionalCase::deinit (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	m_comparisonValueArray.clear();
+
+	if (m_arrayBuffer != 0)
+	{
+		gl.deleteBuffers(1, &m_arrayBuffer);
+		m_arrayBuffer = 0;
+	}
+
+	ShaderPerformanceCase::deinit();
+}
+
+class LoopCase : public ControlStatementCase
+{
+public:
+	enum LoopType
+	{
+		LOOP_FOR = 0,
+		LOOP_WHILE,
+		LOOP_DO_WHILE,
+
+		LOOP_LAST
+	};
+					LoopCase			(Context& context, const char* name, const char* description, LoopType type, DecisionType decisionType, bool isLoopBoundStable, bool isVertex);
+					~LoopCase			(void);
+
+	void			init				(void);
+	void			deinit				(void);
+	void			setupProgram		(deUint32 program);
+
+private:
+	DecisionType	m_decisionType;
+	LoopType		m_type;
+
+	bool			m_isLoopBoundStable;	// Whether loop bound is same in all executions.
+	vector<float>	m_boundArray;			// Will contain per-vertex loop bounds if using non-stable attribute in vertex case.
+	deUint32		m_arrayBuffer;
+};
+
+LoopCase::LoopCase (Context& context, const char* name, const char* description, LoopType type, DecisionType decisionType, bool isLoopBoundStable, bool isVertex)
+	: ControlStatementCase	(context.getTestContext(), context.getRenderContext(), name, description, isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT)
+	, m_decisionType		(decisionType)
+	, m_type				(type)
+	, m_isLoopBoundStable	(isLoopBoundStable)
+	, m_arrayBuffer			(0)
+{
+}
+
+void LoopCase::init (void)
+{
+	bool				isVertexCase	= m_caseType == CASETYPE_VERTEX;
+
+	bool				isStaticCase	= m_decisionType == DECISION_STATIC;
+	bool				isUniformCase	= m_decisionType == DECISION_UNIFORM;
+	bool				isAttributeCase	= m_decisionType == DECISION_ATTRIBUTE;
+
+	DE_ASSERT(isStaticCase || isUniformCase || isAttributeCase);
+
+	DE_ASSERT(m_type == LOOP_FOR		||
+			  m_type == LOOP_WHILE		||
+			  m_type == LOOP_DO_WHILE);
+
+	DE_ASSERT(isAttributeCase || m_isLoopBoundStable); // The loop bound count can vary between executions only if using attribute input.
+
+	// \note The fractional part is .5 (instead of .0) so that these can be safely used as loop bounds.
+	const float			loopBound				= 10.5f;
+	const float			unstableBoundLow		= 5.5f;
+	const float			unstableBoundHigh		= 15.5f;
+	static const char*	loopBoundStr			= "10.5";
+	static const char*	unstableBoundLowStr		= "5.5";
+	static const char*	unstableBoundHighStr	= "15.5";
+
+	const char*			boundValueStr		= isStaticCase			? loopBoundStr :
+											  isUniformCase			? "u_bound" :
+											  isVertexCase			? "a_bound" :
+											  m_isLoopBoundStable	? "v_bound" :
+																	  "loopBound";
+
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op		= isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n";
+	vtx << "in highp vec4 a_position;\n";	// Position attribute.
+	vtx << "in mediump vec4 a_value;\n";	// Input for workload calculations.
+
+	frag << "#version 300 es\n";
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	// Value to be used as the loop iteration count.
+	if (isAttributeCase)
+		vtx << "in mediump float a_bound;\n";
+	else if (isUniformCase)
+		op << "uniform mediump float u_bound;\n";
+
+	// Varyings.
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "out mediump vec4 v_value;\n";
+		frag << "in mediump vec4 v_value;\n";
+
+		if (isAttributeCase)
+		{
+			vtx << "out mediump float v_bound;\n";
+			frag << "in mediump float v_bound;\n";
+		}
+	}
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	op << "	mediump vec4 res = vec4(0.0);\n";
+
+	if (!m_isLoopBoundStable && !isVertexCase)
+	{
+		// Choose the actual loop bound based on v_bound.
+		// \note Loop bound will vary with high frequency between fragment columns, given appropriate range for v_bound.
+		op << "	mediump float loopBound = fract(v_bound) < 0.5 ? " << unstableBoundLowStr << " : " << unstableBoundHighStr << ";\n";
+	}
+
+	// Start a for, while or do-while loop.
+	if (m_type == LOOP_FOR)
+		op << "	for (mediump float i = 0.0; i < " << boundValueStr << "; i++)\n";
+	else
+	{
+		op << "	mediump float i = 0.0;\n";
+		if (m_type == LOOP_WHILE)
+			op << "	while (i < " << boundValueStr << ")\n";
+		else // LOOP_DO_WHILE
+			op << "	do\n";
+	}
+	op << "	{\n";
+
+	// Workload calculations inside the loop.
+	op << "\t\t";
+	writeLoopWorkload(op, "res", isVertexCase ? "a_value" : "v_value");
+	op << "\n";
+
+	// Only "for" has counter increment in the loop head.
+	if (m_type != LOOP_FOR)
+		op << "		i++;\n";
+
+	// End the loop.
+	if (m_type == LOOP_DO_WHILE)
+		op << "	} while (i < " << boundValueStr << ");\n";
+	else
+		op << "	}\n";
+
+	if (isVertexCase)
+	{
+		// Put result to color variable.
+		vtx << "	v_color = res;\n";
+		frag << "	o_color = v_color;\n";
+	}
+	else
+	{
+		// Transfer inputs to fragment shader through varyings.
+		if (isAttributeCase)
+			vtx << "	v_bound = a_bound;\n";
+		vtx << "	v_value = a_value;\n";
+
+		frag << "	o_color = res;\n"; // Put result to color variable.
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource = vtx.str();
+	m_fragShaderSource = frag.str();
+
+	if (isAttributeCase)
+	{
+		if (m_isLoopBoundStable)
+		{
+			// Every execution has same number of iterations.
+
+			m_attributes.push_back(AttribSpec("a_bound",	Vec4(loopBound, 0.0f, 0.0f, 0.0f),
+															Vec4(loopBound, 0.0f, 0.0f, 0.0f),
+															Vec4(loopBound, 0.0f, 0.0f, 0.0f),
+															Vec4(loopBound, 0.0f, 0.0f, 0.0f)));
+		}
+		else if (isVertexCase)
+		{
+			// Vertex case, with non-constant number of iterations.
+
+			const int	numComponents	= 4;
+			int			numVertices		= (getGridWidth() + 1) * (getGridHeight() + 1);
+
+			// setupProgram() will later bind this array as an attribute.
+			m_boundArray.resize(numVertices * numComponents);
+
+			// Vary between low and high loop bounds; they should average to loopBound however.
+			for (int i = 0; i < (int)m_boundArray.size(); i++)
+			{
+				if (i % numComponents == 0)
+					m_boundArray[i] = (i / numComponents) % 2 == 0 ? unstableBoundLow : unstableBoundHigh;
+				else
+					m_boundArray[i] = 0.0f;
+			}
+		}
+		else // !m_isLoopBoundStable && !isVertexCase
+		{
+			// Fragment case, with non-constant number of iterations.
+			// \note fract(a_bound) < 0.5 will be true for every second fragment.
+
+			float minValue = 0.0f;
+			float maxValue = (float)getViewportWidth()*0.5f;
+			m_attributes.push_back(AttribSpec("a_bound",	Vec4(minValue, 0.0f, 0.0f, 0.0f),
+															Vec4(maxValue, 0.0f, 0.0f, 0.0f),
+															Vec4(minValue, 0.0f, 0.0f, 0.0f),
+															Vec4(maxValue, 0.0f, 0.0f, 0.0f)));
+		}
+	}
+
+	m_attributes.push_back(AttribSpec("a_value",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
+													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
+													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
+													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));
+
+	ControlStatementCase::init();
+}
+
+void LoopCase::setupProgram (deUint32 program)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_decisionType == DECISION_UNIFORM)
+	{
+		const float loopBound = 10.5f;
+
+		int location = gl.getUniformLocation(program, "u_bound");
+		gl.uniform1f(location, loopBound);
+	}
+	else if (m_decisionType == DECISION_ATTRIBUTE && !m_isLoopBoundStable && m_caseType == CASETYPE_VERTEX)
+	{
+		// Setup per-vertex loop bounds calculated in init().
+
+		const int	numComponents		= 4;
+		int			boundAttribLocation	= gl.getAttribLocation(program, "a_bound");
+
+		DE_ASSERT((int)m_boundArray.size() == numComponents * (getGridWidth() + 1) * (getGridHeight() + 1));
+
+		gl.genBuffers(1, &m_arrayBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(m_boundArray.size()*sizeof(float)), &m_boundArray[0], GL_STATIC_DRAW);
+		gl.enableVertexAttribArray(boundAttribLocation);
+		gl.vertexAttribPointer(boundAttribLocation, (GLint)numComponents, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Setup program state");
+}
+
+LoopCase::~LoopCase (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_arrayBuffer)
+	{
+		gl.deleteBuffers(1, &m_arrayBuffer);
+		m_arrayBuffer = 0;
+	}
+}
+
+void LoopCase::deinit (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	m_boundArray.clear();
+
+	if (m_arrayBuffer)
+	{
+		gl.deleteBuffers(1, &m_arrayBuffer);
+		m_arrayBuffer = 0;
+	}
+
+	ShaderPerformanceCase::deinit();
+}
+
+// A reference case, same calculations as the actual tests but without control statements.
+class WorkloadReferenceCase : public ControlStatementCase
+{
+public:
+							WorkloadReferenceCase		(Context& context, const char* name, const char* description, bool isVertex);
+
+	void					init						(void);
+
+protected:
+	virtual void			writeWorkload				(std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const = 0;
+};
+
+WorkloadReferenceCase::WorkloadReferenceCase (Context& context, const char* name, const char* description, bool isVertex)
+	: ControlStatementCase(context.getTestContext(), context.getRenderContext(), name, description, isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT)
+{
+}
+
+void WorkloadReferenceCase::init (void)
+{
+	bool isVertexCase = m_caseType == CASETYPE_VERTEX;
+
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op			= isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n";
+	vtx << "in highp vec4 a_position;\n";	// Position attribute.
+	vtx << "in mediump vec4 a_value;\n";	// Value for workload calculations.
+
+	frag << "#version 300 es\n";
+	frag << "layout(location = 0) out mediump vec4 o_color;\n";
+
+	// Varyings.
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		vtx << "out mediump vec4 v_value;\n";
+		frag << "in mediump vec4 v_value;\n";
+	}
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+	vtx << "	gl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	op << "\tmediump vec4 res;\n";
+	writeWorkload(op, "res", isVertexCase ? "a_value" : "v_value");
+
+	if (isVertexCase)
+	{
+		// Put result to color variable.
+		vtx << "	v_color = res;\n";
+		frag << "	o_color = v_color;\n";
+	}
+	else
+	{
+		vtx << "	v_value = a_value;\n";	// Transfer input to fragment shader through varying.
+		frag << "	o_color = res;\n";	// Put result to color variable.
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	m_vertShaderSource = vtx.str();
+	m_fragShaderSource = frag.str();
+
+	m_attributes.push_back(AttribSpec("a_value",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
+													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
+													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
+													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));
+
+	ControlStatementCase::init();
+}
+
+class LoopWorkloadReferenceCase : public WorkloadReferenceCase
+{
+public:
+	LoopWorkloadReferenceCase (Context& context, const char* name, const char* description, bool isAttributeStable, bool isVertex)
+		: WorkloadReferenceCase		(context, name, description, isVertex)
+		, m_isAttributeStable		(isAttributeStable)
+	{
+	}
+
+protected:
+	void writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const;
+
+private:
+	bool m_isAttributeStable;
+};
+
+void LoopWorkloadReferenceCase::writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const
+{
+	const int	loopIterations	= 11;
+	bool		isVertexCase	= m_caseType == CASETYPE_VERTEX;
+
+	dst << "\t" << resultVariableName << " = vec4(0.0);\n";
+
+	for (int i = 0; i < loopIterations; i++)
+	{
+		dst << "\t";
+		writeLoopWorkload(dst, resultVariableName, inputVariableName);
+		dst << "\n";
+	}
+
+	if (!isVertexCase && !m_isAttributeStable)
+	{
+		// Corresponds to the fract() done in a real test's fragment case with non-stable attribute.
+		dst << "	res.x = fract(res.x);\n";
+	}
+}
+
+class ConditionalWorkloadReferenceCase : public WorkloadReferenceCase
+{
+public:
+	ConditionalWorkloadReferenceCase (Context& context, const char* name, const char* description, bool isAttributeStable, bool isVertex)
+		: WorkloadReferenceCase		(context, name, description, isVertex)
+		, m_isAttributeStable		(isAttributeStable)
+	{
+	}
+
+protected:
+	void writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const;
+
+private:
+	bool m_isAttributeStable;
+};
+
+void ConditionalWorkloadReferenceCase::writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const
+{
+	bool isVertexCase = m_caseType == CASETYPE_VERTEX;
+
+	dst << "\t";
+	writeConditionalWorkload(dst, resultVariableName, inputVariableName);
+	dst << "\n";
+
+	if (!isVertexCase && !m_isAttributeStable)
+	{
+		// Corresponds to the fract() done in a real test's fragment case with non-stable attribute.
+		dst << "	res.x = fract(res.x);\n";
+	}
+}
+
+// A workload reference case for e.g. a conditional case with a branch with no computation.
+class EmptyWorkloadReferenceCase : public WorkloadReferenceCase
+{
+public:
+	EmptyWorkloadReferenceCase	(Context& context, const char* name, const char* description, bool isVertex)
+		: WorkloadReferenceCase (context, name, description, isVertex)
+	{
+	}
+
+protected:
+	void writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const
+	{
+		dst << "\t" << resultVariableName << " = " << inputVariableName << ";\n";
+	}
+};
+
+ShaderControlStatementTests::ShaderControlStatementTests (Context& context)
+	: TestCaseGroup(context, "control_statement", "Control Statement Performance Tests")
+{
+}
+
+ShaderControlStatementTests::~ShaderControlStatementTests (void)
+{
+}
+
+void ShaderControlStatementTests::init (void)
+{
+	// Conditional cases (if-else).
+
+	tcu::TestCaseGroup* ifElseGroup = new tcu::TestCaseGroup(m_testCtx, "if_else", "if-else Conditional Performance Tests");
+	addChild(ifElseGroup);
+
+	for (int isFrag = 0; isFrag <= 1; isFrag++)
+	{
+		bool isVertex = isFrag == 0;
+		ShaderPerformanceCaseGroup* vertexOrFragmentGroup = new ShaderPerformanceCaseGroup(m_testCtx, isVertex ? "vertex" : "fragment", "");
+		ifElseGroup->addChild(vertexOrFragmentGroup);
+
+		DE_STATIC_ASSERT(DECISION_STATIC == 0);
+		for (int decisionType = (int)DECISION_STATIC; decisionType < (int)DECISION_LAST; decisionType++)
+		{
+			const char* decisionName = decisionType == (int)DECISION_STATIC		? "static" :
+										decisionType == (int)DECISION_UNIFORM	? "uniform" :
+										decisionType == (int)DECISION_ATTRIBUTE	? (isVertex ? "attribute" : "varying") :
+																					DE_NULL;
+			DE_ASSERT(decisionName != DE_NULL);
+
+			for (int workloadDivision = 0; workloadDivision < ConditionalCase::WORKLOAD_DIVISION_LAST; workloadDivision++)
+			{
+				const char* workloadDivisionSuffix = workloadDivision == (int)ConditionalCase::WORKLOAD_DIVISION_EVEN			? "" :
+													 workloadDivision == (int)ConditionalCase::WORKLOAD_DIVISION_TRUE_HEAVY		? "_with_heavier_true" :
+													 workloadDivision == (int)ConditionalCase::WORKLOAD_DIVISION_FALSE_HEAVY	? "_with_heavier_false" :
+																																  DE_NULL;
+				DE_ASSERT(workloadDivisionSuffix != DE_NULL);
+
+				DE_STATIC_ASSERT(ConditionalCase::BRANCH_TRUE == 0);
+				for (int branchResult = (int)ConditionalCase::BRANCH_TRUE; branchResult < (int)ConditionalCase::BRANCH_LAST; branchResult++)
+				{
+					if (decisionType != (int)DECISION_ATTRIBUTE && branchResult == (int)ConditionalCase::BRANCH_MIXED)
+						continue;
+
+					const char* branchResultName = branchResult == (int)ConditionalCase::BRANCH_TRUE	? "true" :
+												   branchResult == (int)ConditionalCase::BRANCH_FALSE	? "false" :
+												   branchResult == (int)ConditionalCase::BRANCH_MIXED	? "mixed" :
+																										  DE_NULL;
+					DE_ASSERT(branchResultName != DE_NULL);
+
+					string caseName = string("") + decisionName + "_" + branchResultName + workloadDivisionSuffix;
+
+					vertexOrFragmentGroup->addChild(new ConditionalCase(m_context, caseName.c_str(), "",
+																		(DecisionType)decisionType, (ConditionalCase::BranchResult)branchResult,
+																		(ConditionalCase::WorkloadDivision)workloadDivision, isVertex));
+				}
+			}
+		}
+
+		if (isVertex)
+			vertexOrFragmentGroup->addChild(new ConditionalWorkloadReferenceCase(m_context, "reference", "", true, isVertex));
+		else
+		{
+			// Only fragment case with BRANCH_MIXED has an additional fract() call.
+			vertexOrFragmentGroup->addChild(new ConditionalWorkloadReferenceCase(m_context, "reference_unmixed", "", true, isVertex));
+			vertexOrFragmentGroup->addChild(new ConditionalWorkloadReferenceCase(m_context, "reference_mixed", "", false, isVertex));
+		}
+
+		vertexOrFragmentGroup->addChild(new EmptyWorkloadReferenceCase(m_context, "reference_empty", "", isVertex));
+	}
+
+	// Loop cases.
+
+	static const struct
+	{
+		LoopCase::LoopType	type;
+		const char*			name;
+		const char*			description;
+	} loopGroups[] =
+	{
+		{LoopCase::LOOP_FOR,		"for",		"for Loop Performance Tests"},
+		{LoopCase::LOOP_WHILE,		"while",	"while Loop Performance Tests"},
+		{LoopCase::LOOP_DO_WHILE,	"do_while",	"do-while Loop Performance Tests"}
+	};
+
+	for (int groupNdx = 0; groupNdx < DE_LENGTH_OF_ARRAY(loopGroups); groupNdx++)
+	{
+		tcu::TestCaseGroup* currentLoopGroup = new tcu::TestCaseGroup(m_testCtx, loopGroups[groupNdx].name, loopGroups[groupNdx].description);
+		addChild(currentLoopGroup);
+
+		for (int isFrag = 0; isFrag <= 1; isFrag++)
+		{
+			bool isVertex = isFrag == 0;
+			ShaderPerformanceCaseGroup* vertexOrFragmentGroup = new ShaderPerformanceCaseGroup(m_testCtx, isVertex ? "vertex" : "fragment", "");
+			currentLoopGroup->addChild(vertexOrFragmentGroup);
+
+			DE_STATIC_ASSERT(DECISION_STATIC == 0);
+			for (int decisionType = (int)DECISION_STATIC; decisionType < (int)DECISION_LAST; decisionType++)
+			{
+				const char* decisionName = decisionType == (int)DECISION_STATIC		? "static" :
+										   decisionType == (int)DECISION_UNIFORM	? "uniform" :
+										   decisionType == (int)DECISION_ATTRIBUTE	? (isVertex ? "attribute" : "varying") :
+																					  DE_NULL;
+				DE_ASSERT(decisionName != DE_NULL);
+
+				if (decisionType == (int)DECISION_ATTRIBUTE)
+				{
+					vertexOrFragmentGroup->addChild(new LoopCase(m_context, (string(decisionName) + "_stable").c_str(), "", loopGroups[groupNdx].type, (DecisionType)decisionType, true, isVertex));
+					vertexOrFragmentGroup->addChild(new LoopCase(m_context, (string(decisionName) + "_unstable").c_str(), "", loopGroups[groupNdx].type, (DecisionType)decisionType, false, isVertex));
+				}
+				else
+					vertexOrFragmentGroup->addChild(new LoopCase(m_context, decisionName, "", loopGroups[groupNdx].type, (DecisionType)decisionType, true, isVertex));
+
+			}
+
+			if (isVertex)
+				vertexOrFragmentGroup->addChild(new LoopWorkloadReferenceCase(m_context, "reference", "", true, isVertex));
+			else
+			{
+				// Only fragment case with unstable attribute has an additional fract() call.
+				vertexOrFragmentGroup->addChild(new LoopWorkloadReferenceCase(m_context, "reference_stable", "", true, isVertex));
+				vertexOrFragmentGroup->addChild(new LoopWorkloadReferenceCase(m_context, "reference_unstable", "", false, isVertex));
+			}
+		}
+	}
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pShaderControlStatementTests.hpp b/modules/gles3/performance/es3pShaderControlStatementTests.hpp
new file mode 100644
index 0000000..25498ce
--- /dev/null
+++ b/modules/gles3/performance/es3pShaderControlStatementTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PSHADERCONTROLSTATEMENTTESTS_HPP
+#define _ES3PSHADERCONTROLSTATEMENTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader control statement performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class ShaderControlStatementTests : public TestCaseGroup
+{
+public:
+									ShaderControlStatementTests		(Context& context);
+									~ShaderControlStatementTests	(void);
+
+	void							init							(void);
+
+private:
+									ShaderControlStatementTests		(const ShaderControlStatementTests& other);
+	ShaderControlStatementTests&	operator=						(const ShaderControlStatementTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PSHADERCONTROLSTATEMENTTESTS_HPP
diff --git a/modules/gles3/performance/es3pShaderOperatorTests.cpp b/modules/gles3/performance/es3pShaderOperatorTests.cpp
new file mode 100644
index 0000000..f4254b1
--- /dev/null
+++ b/modules/gles3/performance/es3pShaderOperatorTests.cpp
@@ -0,0 +1,2319 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader operator performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pShaderOperatorTests.hpp"
+#include "glsCalibration.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuSurface.hpp"
+#include "deStringUtil.hpp"
+#include "deSharedPtr.hpp"
+#include "deClock.h"
+#include "deMath.h"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <map>
+#include <algorithm>
+#include <limits>
+#include <set>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+using namespace gls;
+using namespace glu;
+using tcu::Vec2;
+using tcu::Vec4;
+using tcu::TestLog;
+using de::SharedPtr;
+
+using std::string;
+using std::vector;
+
+#define MEASUREMENT_FAIL() throw tcu::InternalError("Unable to get sensible measurements for estimation", DE_NULL, __FILE__, __LINE__)
+
+// Number of measurements in OperatorPerformanceCase for each workload size, unless specified otherwise by a command line argument.
+static const int	DEFAULT_NUM_MEASUREMENTS_PER_WORKLOAD	= 3;
+// How many different workload sizes are used by OperatorPerformanceCase.
+static const int	NUM_WORKLOADS							= 8;
+// Maximum workload size that can be attempted. In a sensible case, this most likely won't be reached.
+static const int	MAX_WORKLOAD_SIZE						= 1<<29;
+
+// BinaryOpCase-specific constants for shader generation.
+static const int	BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS	= 4;
+static const int	BINARY_OPERATOR_CASE_SMALL_PROGRAM_UNROLL_AMOUNT	= 2;
+static const int	BINARY_OPERATOR_CASE_BIG_PROGRAM_UNROLL_AMOUNT		= 4;
+
+// FunctionCase-specific constants for shader generation.
+static const int	FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS			= 4;
+
+static const char* const s_swizzles[][4] =
+{
+	{ "x", "yx", "yzx", "wzyx" },
+	{ "y", "zy", "wyz", "xwzy" },
+	{ "z", "wy", "zxy", "yzwx" },
+	{ "w", "xw", "yxw", "zyxw" }
+};
+
+template <int N>
+static tcu::Vector<float, N> mean (const vector<tcu::Vector<float, N> >& data)
+{
+	tcu::Vector<float, N> sum(0.0f);
+	for (int i = 0; i < (int)data.size(); i++)
+		sum += data[i];
+	return sum / tcu::Vector<float, N>((float)data.size());
+}
+
+static void uniformNfv (const glw::Functions& gl, int n, int location, int count, const float* data)
+{
+	switch (n)
+	{
+		case 1: gl.uniform1fv(location, count, data); break;
+		case 2: gl.uniform2fv(location, count, data); break;
+		case 3: gl.uniform3fv(location, count, data); break;
+		case 4: gl.uniform4fv(location, count, data); break;
+		default: DE_ASSERT(false);
+	}
+}
+
+static void uniformNiv (const glw::Functions& gl, int n, int location, int count, const int* data)
+{
+	switch (n)
+	{
+		case 1: gl.uniform1iv(location, count, data); break;
+		case 2: gl.uniform2iv(location, count, data); break;
+		case 3: gl.uniform3iv(location, count, data); break;
+		case 4: gl.uniform4iv(location, count, data); break;
+		default: DE_ASSERT(false);
+	}
+}
+
+static void uniformMatrixNfv (const glw::Functions& gl, int n, int location, int count, const float* data)
+{
+	switch (n)
+	{
+		case 2: gl.uniformMatrix2fv(location, count, GL_FALSE, &data[0]); break;
+		case 3: gl.uniformMatrix3fv(location, count, GL_FALSE, &data[0]); break;
+		case 4: gl.uniformMatrix4fv(location, count, GL_FALSE, &data[0]); break;
+		default: DE_ASSERT(false);
+	}
+}
+
+static glu::DataType getDataTypeFloatOrVec (int size)
+{
+	return size == 1 ? glu::TYPE_FLOAT : glu::getDataTypeFloatVec(size);
+}
+
+static int getIterationCountOrDefault (const tcu::CommandLine& cmdLine, int def)
+{
+	const int cmdLineVal = cmdLine.getTestIterationCount();
+	return cmdLineVal > 0 ? cmdLineVal : def;
+}
+
+static string lineParamsString (const LineParameters& params)
+{
+	return "y = " + de::toString(params.offset) + " + " + de::toString(params.coefficient) + "*x";
+}
+
+namespace
+{
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Abstract class for measuring shader operator performance.
+ *
+ * This class draws multiple times with different workload sizes (set
+ * via a uniform, by subclass). Time for each frame is measured, and the
+ * slope of the workload size vs frame time data is estimated. This slope
+ * tells us the estimated increase in frame time caused by a workload
+ * increase of 1 unit (what 1 workload unit means is up to subclass).
+ *
+ * Generally, the shaders contain not just the operation we're interested
+ * in (e.g. addition) but also some other stuff (e.g. loop overhead). To
+ * eliminate this cost, we actually do the stuff described in the above
+ * paragraph with multiple programs (usually two), which contain different
+ * kinds of workload (e.g. different loop contents). Then we can (in
+ * theory) compute the cost of just one operation in a subclass-dependent
+ * manner.
+ *
+ * At this point, the result tells us the increase in frame time caused
+ * by the addition of one operation. Dividing this by the amount of
+ * draw calls in a frame, and further by the amount of vertices or
+ * fragments in a draw call, we get the time cost of one operation.
+ *
+ * In reality, there sometimes isn't just a trivial linear dependence
+ * between workload size and frame time. Instead, there tends to be some
+ * amount of initial "free" operations. That is, it may be that all
+ * workload sizes below some positive integer C yield the same frame time,
+ * and only workload sizes beyond C increase the frame time in a supposedly
+ * linear manner. Graphically, this means that there graph consists of two
+ * parts: a horizontal left part, and a linearly increasing right part; the
+ * right part starts where the left parts ends. The principal task of these
+ * tests is to look at the slope of the increasing right part. Additionally
+ * an estimate for the amount of initial free operations is calculated.
+ * Note that it is also normal to get graphs where the horizontal left part
+ * is of zero width, i.e. there are no free operations.
+ *//*--------------------------------------------------------------------*/
+class OperatorPerformanceCase : public tcu::TestCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_VERTEX = 0,
+		CASETYPE_FRAGMENT,
+
+		CASETYPE_LAST
+	};
+
+	struct InitialCalibration
+	{
+		int initialNumCalls;
+		InitialCalibration (void) : initialNumCalls(1) {}
+	};
+
+	typedef SharedPtr<InitialCalibration> InitialCalibrationStorage;
+
+								OperatorPerformanceCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description,
+															 CaseType caseType, int numWorkloads, const InitialCalibrationStorage& initialCalibrationStorage);
+								~OperatorPerformanceCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+
+	IterateResult				iterate						(void);
+
+	struct AttribSpec
+	{
+		AttribSpec (const char* name_, const tcu::Vec4& p00_, const tcu::Vec4& p01_, const tcu::Vec4& p10_, const tcu::Vec4& p11_)
+			: name		(name_)
+			, p00		(p00_)
+			, p01		(p01_)
+			, p10		(p10_)
+			, p11		(p11_)
+		{
+		}
+
+		AttribSpec (void) {}
+
+		std::string		name;
+		tcu::Vec4		p00;	//!< Bottom left.
+		tcu::Vec4		p01;	//!< Bottom right.
+		tcu::Vec4		p10;	//!< Top left.
+		tcu::Vec4		p11;	//!< Top right.
+	};
+
+protected:
+	struct ProgramContext
+	{
+		string				vertShaderSource;
+		string				fragShaderSource;
+		vector<AttribSpec>	attributes;
+
+		string				description;
+
+		ProgramContext (void) {}
+		ProgramContext (const string& vs, const string& fs, const vector<AttribSpec>& attrs, const string& desc)
+			: vertShaderSource(vs), fragShaderSource(fs), attributes(attrs), description(desc) {}
+	};
+
+	virtual vector<ProgramContext>	generateProgramData					(void) const = 0;
+	//! Sets program-specific uniforms that don't depend on the workload size.
+	virtual void					setGeneralUniforms					(deUint32 program) const = 0;
+	//! Sets the uniform(s) that specifies the workload size in the shader.
+	virtual void					setWorkloadSizeUniform				(deUint32 program, int workload) const = 0;
+	//! Computes the cost of a single operation, given the workload costs per program.
+	virtual float					computeSingleOperationTime			(const vector<float>& perProgramWorkloadCosts) const = 0;
+	//! Logs a human-readable description of what computeSingleOperationTime does.
+	virtual void					logSingleOperationCalculationInfo	(void) const = 0;
+
+	glu::RenderContext&				m_renderCtx;
+
+	CaseType						m_caseType;
+
+private:
+	enum State
+	{
+		STATE_CALIBRATING = 0,		//!< Calibrate draw call count, using first program in m_programs, with workload size 1.
+		STATE_FIND_HIGH_WORKLOAD,	//!< Find an appropriate lower bound for the highest workload size we intend to use (one with high-enough frame time compared to workload size 1) for each program.
+		STATE_MEASURING,			//!< Do actual measurements, for each program in m_programs.
+		STATE_REPORTING,			//!< Measurements are done; calculate results and log.
+		STATE_FINISHED,				//!< All done.
+
+		STATE_LAST
+	};
+
+	struct WorkloadRecord
+	{
+		int				workloadSize;
+		vector<float>	frameTimes; //!< In microseconds.
+
+				WorkloadRecord	(int workloadSize_)						: workloadSize(workloadSize_) {}
+		bool	operator<		(const WorkloadRecord& other) const		{ return this->workloadSize < other.workloadSize; }
+		void	addFrameTime	(float time)							{ frameTimes.push_back(time); }
+		float	getMedianTime	(void) const
+		{
+			vector<float> times = frameTimes;
+			std::sort(times.begin(), times.end());
+			return times.size() % 2 == 0 ?
+					(times[times.size()/2-1] + times[times.size()/2])*0.5f :
+					times[times.size()/2];
+		}
+	};
+
+	void								prepareProgram				(int progNdx);					//!< Sets attributes and uniforms for m_programs[progNdx].
+	void								prepareWorkload				(int progNdx, int workload);	//!< Calls setWorkloadSizeUniform and draws, in case the implementation does some draw-time compilation.
+	void								prepareNextRound			(void);							//!< Increases workload and/or updates m_state.
+	void								render						(int numDrawCalls);
+	deUint64							renderAndMeasure			(int numDrawCalls);
+	void								adjustAndLogGridAndViewport	(void);							//!< Log grid and viewport sizes, after possibly reducing them to reduce draw time.
+
+	vector<Vec2>						getWorkloadMedianDataPoints	(int progNdx) const; //!< [ Vec2(r.workloadSize, r.getMedianTime()) for r in m_workloadRecords[progNdx] ]
+
+	const int							m_numMeasurementsPerWorkload;
+	const int							m_numWorkloads;				//!< How many different workload sizes are used for measurement for each program.
+
+	int									m_workloadNdx;				//!< Runs from 0 to m_numWorkloads-1.
+
+	int									m_workloadMeasurementNdx;
+	vector<vector<WorkloadRecord> >		m_workloadRecordsFindHigh;	//!< The measurements done during STATE_FIND_HIGH_WORKLOAD.
+	vector<vector<WorkloadRecord> >		m_workloadRecords;			//!< The measurements of each program in m_programs. Generated during STATE_MEASURING, into index specified by m_measureProgramNdx.
+
+	State								m_state;
+	int									m_measureProgramNdx;		//!< When m_state is STATE_FIND_HIGH_WORKLOAD or STATE_MEASURING, this tells which program in m_programs is being measured.
+
+	vector<int>							m_highWorkloadSizes;		//!< The first workload size encountered during STATE_FIND_HIGH_WORKLOAD that was determined suitable, for each program.
+
+	TheilSenCalibrator					m_calibrator;
+	InitialCalibrationStorage			m_initialCalibrationStorage;
+
+	int									m_viewportWidth;
+	int									m_viewportHeight;
+	int									m_gridSizeX;
+	int									m_gridSizeY;
+
+	vector<ProgramContext>				m_programData;
+	vector<SharedPtr<ShaderProgram> >	m_programs;
+
+	std::vector<deUint32>				m_attribBuffers;
+};
+
+static inline float triangleInterpolate (float v0, float v1, float v2, float x, float y)
+{
+	return v0 + (v2-v0)*x + (v1-v0)*y;
+}
+
+static inline float triQuadInterpolate (float x, float y, const tcu::Vec4& quad)
+{
+	// \note Top left fill rule.
+	if (x + y < 1.0f)
+		return triangleInterpolate(quad.x(), quad.y(), quad.z(), x, y);
+	else
+		return triangleInterpolate(quad.w(), quad.z(), quad.y(), 1.0f-x, 1.0f-y);
+}
+
+static inline int getNumVertices (int gridSizeX, int gridSizeY)
+{
+	return gridSizeX * gridSizeY * 2 * 3;
+}
+
+static void generateVertices (std::vector<float>& dst, int gridSizeX, int gridSizeY, const OperatorPerformanceCase::AttribSpec& spec)
+{
+	const int numComponents = 4;
+
+	DE_ASSERT(gridSizeX >= 1 && gridSizeY >= 1);
+	dst.resize(getNumVertices(gridSizeX, gridSizeY) * numComponents);
+
+	{
+		int dstNdx = 0;
+
+		for (int baseY = 0; baseY < gridSizeY; baseY++)
+		for (int baseX = 0; baseX < gridSizeX; baseX++)
+		{
+			const float xf0 = (float)(baseX + 0) / (float)gridSizeX;
+			const float yf0 = (float)(baseY + 0) / (float)gridSizeY;
+			const float xf1 = (float)(baseX + 1) / (float)gridSizeX;
+			const float yf1 = (float)(baseY + 1) / (float)gridSizeY;
+
+#define ADD_VERTEX(XF, YF)										\
+	for (int compNdx = 0; compNdx < numComponents; compNdx++)	\
+		dst[dstNdx++] = triQuadInterpolate((XF), (YF), tcu::Vec4(spec.p00[compNdx], spec.p01[compNdx], spec.p10[compNdx], spec.p11[compNdx]))
+
+			ADD_VERTEX(xf0, yf0);
+			ADD_VERTEX(xf1, yf0);
+			ADD_VERTEX(xf0, yf1);
+
+			ADD_VERTEX(xf1, yf0);
+			ADD_VERTEX(xf1, yf1);
+			ADD_VERTEX(xf0, yf1);
+
+#undef ADD_VERTEX
+		}
+	}
+}
+
+static float intersectionX (const gls::LineParameters& a, const gls::LineParameters& b)
+{
+	return (a.offset - b.offset) / (b.coefficient - a.coefficient);
+}
+
+static int numDistinctX (const vector<Vec2>& data)
+{
+	std::set<float> xs;
+	for (int i = 0; i < (int)data.size(); i++)
+		xs.insert(data[i].x());
+	return (int)xs.size();
+}
+
+static gls::LineParameters simpleLinearRegression (const vector<Vec2>& data)
+{
+	const Vec2	mid					= mean(data);
+
+	float		slopeNumerator		= 0.0f;
+	float		slopeDenominator	= 0.0f;
+
+	for (int i = 0; i < (int)data.size(); i++)
+	{
+		const Vec2 diff = data[i] - mid;
+
+		slopeNumerator		+= diff.x()*diff.y();
+		slopeDenominator	+= diff.x()*diff.x();
+	}
+
+	const float slope	= slopeNumerator / slopeDenominator;
+	const float offset	= mid.y() - slope*mid.x();
+
+	return gls::LineParameters(offset, slope);
+}
+
+static float simpleLinearRegressionError (const vector<Vec2>& data)
+{
+	if (numDistinctX(data) <= 2)
+		return 0.0f;
+	else
+	{
+		const gls::LineParameters	estimator	= simpleLinearRegression(data);
+		float						error		= 0.0f;
+
+		for (int i = 0; i < (int)data.size(); i++)
+		{
+			const float estY = estimator.offset + estimator.coefficient*data[i].x();
+			const float diff = estY - data[i].y();
+			error += diff*diff;
+		}
+
+		return error / (float)data.size();
+	}
+}
+
+static float verticalVariance (const vector<Vec2>& data)
+{
+	if (numDistinctX(data) <= 2)
+		return 0.0f;
+	else
+	{
+		const float		meanY = mean(data).y();
+		float			error = 0.0f;
+
+		for (int i = 0; i < (int)data.size(); i++)
+		{
+			const float diff = meanY - data[i].y();
+			error += diff*diff;
+		}
+
+		return error / (float)data.size();
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Find the x coord that divides the input data into two slopes.
+ *
+ * The operator performance measurements tend to produce results where
+ * we get small operation counts "for free" (e.g. because the operations
+ * are performed during some memory transfer overhead or something),
+ * resulting in a curve with two parts: an initial horizontal line segment,
+ * and a rising line.
+ *
+ * This function finds the x coordinate that divides the input data into
+ * two parts such that the sum of the mean square errors for the
+ * least-squares estimated lines for the two parts is minimized, under the
+ * additional condition that the left line is horizontal.
+ *
+ * This function returns a number X s.t. { pt | pt is in data, pt.x >= X }
+ * is the right line, and the rest of data is the left line.
+ *//*--------------------------------------------------------------------*/
+static float findSlopePivotX (const vector<Vec2>& data)
+{
+	std::set<float> xCoords;
+	for (int i = 0; i < (int)data.size(); i++)
+		xCoords.insert(data[i].x());
+
+	float			lowestError		= std::numeric_limits<float>::infinity();
+	float			bestPivotX		= -std::numeric_limits<float>::infinity();
+
+	for (std::set<float>::const_iterator pivotX = xCoords.begin(); pivotX != xCoords.end(); ++pivotX)
+	{
+		vector<Vec2> leftData;
+		vector<Vec2> rightData;
+		for (int i = 0; i < (int)data.size(); i++)
+		{
+			if (data[i].x() < *pivotX)
+				leftData.push_back(data[i]);
+			else
+				rightData.push_back(data[i]);
+		}
+
+		if (numDistinctX(rightData) < 3) // We don't trust the right data if there's too little of it.
+			break;
+
+		{
+			const float totalError = verticalVariance(leftData) + simpleLinearRegressionError(rightData);
+
+			if (totalError < lowestError)
+			{
+				lowestError = totalError;
+				bestPivotX = *pivotX;
+			}
+		}
+	}
+
+	DE_ASSERT(lowestError < std::numeric_limits<float>::infinity());
+
+	return bestPivotX;
+}
+
+struct SegmentedEstimator
+{
+	float					pivotX; //!< Value returned by findSlopePivotX, or -infinity if only single line.
+	gls::LineParameters		left;
+	gls::LineParameters		right;
+	SegmentedEstimator (const gls::LineParameters& l, const gls::LineParameters& r, float pivotX_) : pivotX(pivotX_), left(l), right(r) {}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Compute line estimators for (potentially) two-segment data.
+ *
+ * Splits the given data into left and right parts (using findSlopePivotX)
+ * and returns the line estimates for them.
+ *
+ * Sometimes, however (especially in fragment shader cases) the data is
+ * in fact not segmented, but a straight line. This function attempts to
+ * detect if this the case, and if so, sets left.offset = right.offset and
+ * left.slope = 0, meaning essentially that the initial "flat" part of the
+ * data has zero width.
+ *//*--------------------------------------------------------------------*/
+static SegmentedEstimator computeSegmentedEstimator (const vector<Vec2>& data)
+{
+	const float		pivotX = findSlopePivotX(data);
+	vector<Vec2>	leftData;
+	vector<Vec2>	rightData;
+
+	for (int i = 0; i < (int)data.size(); i++)
+	{
+		if (data[i].x() < pivotX)
+			leftData.push_back(data[i]);
+		else
+			rightData.push_back(data[i]);
+	}
+
+	{
+		const gls::LineParameters leftLine		= gls::theilSenEstimator(leftData);
+		const gls::LineParameters rightLine		= gls::theilSenEstimator(rightData);
+
+		if (numDistinctX(leftData) < 2 || leftLine.coefficient > rightLine.coefficient*0.5f)
+		{
+			// Left data doesn't seem credible; assume the data is just a single line.
+			const gls::LineParameters entireLine = gls::theilSenEstimator(data);
+			return SegmentedEstimator(gls::LineParameters(entireLine.offset, 0.0f), entireLine, -std::numeric_limits<float>::infinity());
+		}
+		else
+			return SegmentedEstimator(leftLine, rightLine, pivotX);
+	}
+}
+
+OperatorPerformanceCase::OperatorPerformanceCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description,
+												  CaseType caseType, int numWorkloads, const InitialCalibrationStorage& initialCalibrationStorage)
+	: tcu::TestCase					(testCtx, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_renderCtx					(renderCtx)
+	, m_caseType					(caseType)
+	, m_numMeasurementsPerWorkload	(getIterationCountOrDefault(m_testCtx.getCommandLine(), DEFAULT_NUM_MEASUREMENTS_PER_WORKLOAD))
+	, m_numWorkloads				(numWorkloads)
+	, m_workloadNdx					(-1)
+	, m_workloadMeasurementNdx		(-1)
+	, m_state						(STATE_LAST)
+	, m_measureProgramNdx			(-1)
+	, m_initialCalibrationStorage	(initialCalibrationStorage)
+	, m_viewportWidth				(caseType == CASETYPE_VERTEX	? 32	: renderCtx.getRenderTarget().getWidth())
+	, m_viewportHeight				(caseType == CASETYPE_VERTEX	? 32	: renderCtx.getRenderTarget().getHeight())
+	, m_gridSizeX					(caseType == CASETYPE_FRAGMENT	? 1		: 100)
+	, m_gridSizeY					(caseType == CASETYPE_FRAGMENT	? 1		: 100)
+{
+	DE_ASSERT(m_numWorkloads > 0);
+}
+
+OperatorPerformanceCase::~OperatorPerformanceCase (void)
+{
+	if (!m_attribBuffers.empty())
+	{
+		m_renderCtx.getFunctions().deleteBuffers((glw::GLsizei)m_attribBuffers.size(), &m_attribBuffers[0]);
+		m_attribBuffers.clear();
+	}
+}
+
+static void logRenderTargetInfo (TestLog& log, const tcu::RenderTarget& renderTarget)
+{
+	log << TestLog::Section("RenderTarget", "Render target")
+		<< TestLog::Message << "size: " << renderTarget.getWidth() << "x" << renderTarget.getHeight() << TestLog::EndMessage
+		<< TestLog::Message << "bits:"
+							<< " R" << renderTarget.getPixelFormat().redBits
+							<< " G" << renderTarget.getPixelFormat().greenBits
+							<< " B" << renderTarget.getPixelFormat().blueBits
+							<< " A" << renderTarget.getPixelFormat().alphaBits
+							<< " D" << renderTarget.getDepthBits()
+							<< " S" << renderTarget.getStencilBits()
+							<< TestLog::EndMessage;
+
+	if (renderTarget.getNumSamples() != 0)
+		log << TestLog::Message << renderTarget.getNumSamples() << "x MSAA" << TestLog::EndMessage;
+	else
+		log << TestLog::Message << "No MSAA" << TestLog::EndMessage;
+
+	log << TestLog::EndSection;
+}
+
+vector<Vec2> OperatorPerformanceCase::getWorkloadMedianDataPoints (int progNdx) const
+{
+	const vector<WorkloadRecord>&	records = m_workloadRecords[progNdx];
+	vector<Vec2>					result;
+
+	for (int i = 0; i < (int)records.size(); i++)
+		result.push_back(Vec2((float)records[i].workloadSize, records[i].getMedianTime()));
+
+	return result;
+}
+
+void OperatorPerformanceCase::prepareProgram (int progNdx)
+{
+	DE_ASSERT(progNdx < (int)m_programs.size());
+	DE_ASSERT(m_programData.size() == m_programs.size());
+
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	const ShaderProgram&	program		= *m_programs[progNdx];
+
+	vector<AttribSpec>		attributes	= m_programData[progNdx].attributes;
+
+	attributes.push_back(AttribSpec("a_position",
+									Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
+									Vec4( 1.0f, -1.0f, 0.0f, 1.0f),
+									Vec4(-1.0f,  1.0f, 0.0f, 1.0f),
+									Vec4( 1.0f,  1.0f, 0.0f, 1.0f)));
+
+	DE_ASSERT(program.isOk());
+
+	// Generate vertices.
+	if (!m_attribBuffers.empty())
+		gl.deleteBuffers((glw::GLsizei)m_attribBuffers.size(), &m_attribBuffers[0]);
+	m_attribBuffers.resize(attributes.size(), 0);
+	gl.genBuffers((glw::GLsizei)m_attribBuffers.size(), &m_attribBuffers[0]);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenBuffers()");
+
+	for (int attribNdx = 0; attribNdx < (int)attributes.size(); attribNdx++)
+	{
+		std::vector<float> vertices;
+		generateVertices(vertices, m_gridSizeX, m_gridSizeY, attributes[attribNdx]);
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_attribBuffers[attribNdx]);
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(vertices.size()*sizeof(float)), &vertices[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Upload buffer data");
+	}
+
+	// Setup attribute bindings.
+	for (int attribNdx = 0; attribNdx < (int)attributes.size(); attribNdx++)
+	{
+		int location = gl.getAttribLocation(program.getProgram(), attributes[attribNdx].name.c_str());
+
+		if (location >= 0)
+		{
+			gl.enableVertexAttribArray(location);
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_attribBuffers[attribNdx]);
+			gl.vertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		}
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Setup vertex input state");
+
+	gl.useProgram(program.getProgram());
+	setGeneralUniforms(program.getProgram());
+	gl.viewport(0, 0, m_viewportWidth, m_viewportHeight);
+}
+
+void OperatorPerformanceCase::prepareWorkload (int progNdx, int workload)
+{
+	setWorkloadSizeUniform(m_programs[progNdx]->getProgram(), workload);
+	render(m_calibrator.getCallCount());
+}
+
+void OperatorPerformanceCase::prepareNextRound (void)
+{
+	DE_ASSERT(m_state == STATE_CALIBRATING			||
+			  m_state == STATE_FIND_HIGH_WORKLOAD	||
+			  m_state == STATE_MEASURING);
+
+	TestLog& log = m_testCtx.getLog();
+
+	if (m_state == STATE_CALIBRATING && m_calibrator.getState() == TheilSenCalibrator::STATE_FINISHED)
+	{
+		m_measureProgramNdx = 0;
+		m_state = STATE_FIND_HIGH_WORKLOAD;
+	}
+
+	if (m_state == STATE_CALIBRATING)
+		prepareWorkload(0, 1);
+	else if (m_state == STATE_FIND_HIGH_WORKLOAD)
+	{
+		vector<WorkloadRecord>& records = m_workloadRecordsFindHigh[m_measureProgramNdx];
+
+		if (records.empty() || records.back().getMedianTime() < 2.0f*records[0].getMedianTime())
+		{
+			int workloadSize;
+
+			if (records.empty())
+				workloadSize = 1;
+			else
+			{
+				workloadSize = records.back().workloadSize*2;
+
+				if (workloadSize > MAX_WORKLOAD_SIZE)
+				{
+					log << TestLog::Message << "Even workload size " << records.back().workloadSize
+											<< " doesn't give high enough frame time for program " << m_measureProgramNdx
+											<< ". Can't get sensible result." << TestLog::EndMessage;
+					MEASUREMENT_FAIL();
+				}
+			}
+
+			records.push_back(WorkloadRecord(workloadSize));
+			prepareWorkload(0, workloadSize);
+			m_workloadMeasurementNdx = 0;
+		}
+		else
+		{
+			m_highWorkloadSizes[m_measureProgramNdx] = records.back().workloadSize;
+			m_measureProgramNdx++;
+
+			if (m_measureProgramNdx >= (int)m_programs.size())
+			{
+				m_state = STATE_MEASURING;
+				m_workloadNdx = -1;
+				m_measureProgramNdx = 0;
+			}
+
+			prepareProgram(m_measureProgramNdx);
+			prepareNextRound();
+		}
+	}
+	else
+	{
+		m_workloadNdx++;
+
+		if (m_workloadNdx < m_numWorkloads)
+		{
+			DE_ASSERT(m_numWorkloads > 1);
+			const int highWorkload	= m_highWorkloadSizes[m_measureProgramNdx];
+			const int workload		= highWorkload > m_numWorkloads ?
+										1 + m_workloadNdx*(highWorkload-1)/(m_numWorkloads-1) :
+										1 + m_workloadNdx;
+
+			prepareWorkload(m_measureProgramNdx, workload);
+
+			m_workloadMeasurementNdx = 0;
+
+			m_workloadRecords[m_measureProgramNdx].push_back(WorkloadRecord(workload));
+		}
+		else
+		{
+			m_measureProgramNdx++;
+
+			if (m_measureProgramNdx < (int)m_programs.size())
+			{
+				m_workloadNdx = -1;
+				m_workloadMeasurementNdx = 0;
+				prepareProgram(m_measureProgramNdx);
+				prepareNextRound();
+			}
+			else
+				m_state = STATE_REPORTING;
+		}
+	}
+}
+
+void OperatorPerformanceCase::init (void)
+{
+	TestLog&				log		= m_testCtx.getLog();
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+
+	// Validate that we have sane grid and viewport setup.
+	DE_ASSERT(de::inBounds(m_gridSizeX, 1, 256) && de::inBounds(m_gridSizeY, 1, 256));
+	TCU_CHECK(de::inRange(m_viewportWidth,	1, m_renderCtx.getRenderTarget().getWidth()) &&
+			  de::inRange(m_viewportHeight,	1, m_renderCtx.getRenderTarget().getHeight()));
+
+	logRenderTargetInfo(log, m_renderCtx.getRenderTarget());
+
+	log << TestLog::Message << "Using additive blending." << TestLog::EndMessage;
+	gl.enable(GL_BLEND);
+	gl.blendEquation(GL_FUNC_ADD);
+	gl.blendFunc(GL_ONE, GL_ONE);
+
+	// Generate programs.
+	DE_ASSERT(m_programs.empty());
+	m_programData = generateProgramData();
+	DE_ASSERT(!m_programData.empty());
+
+	for (int progNdx = 0; progNdx < (int)m_programData.size(); progNdx++)
+	{
+		const string& vert = m_programData[progNdx].vertShaderSource;
+		const string& frag = m_programData[progNdx].fragShaderSource;
+
+		m_programs.push_back(SharedPtr<ShaderProgram>(new ShaderProgram(m_renderCtx, glu::makeVtxFragSources(vert, frag))));
+
+		if (!m_programs.back()->isOk())
+		{
+			log << *m_programs.back();
+			TCU_FAIL("Compile failed");
+		}
+	}
+
+	// Log all programs.
+	for (int progNdx = 0; progNdx < (int)m_programs.size(); progNdx++)
+		log << TestLog::Section("Program" + de::toString(progNdx), "Program " + de::toString(progNdx))
+				<< TestLog::Message << m_programData[progNdx].description << TestLog::EndMessage
+				<< *m_programs[progNdx]
+			<< TestLog::EndSection;
+
+	m_highWorkloadSizes.resize(m_programData.size());
+	m_workloadRecordsFindHigh.resize(m_programData.size());
+	m_workloadRecords.resize(m_programData.size());
+
+	m_calibrator.clear(CalibratorParameters(m_initialCalibrationStorage->initialNumCalls, 10 /* calibrate iteration frames */, 2000.0f /* calibrate iteration shortcut threshold (ms) */, 16 /* max calibrate iterations */,
+											1000.0f/30.0f /* frame time (ms) */, 1000.0f/60.0f /* frame time cap (ms) */, 1000.0f /* target measure duration (ms) */));
+	m_state = STATE_CALIBRATING;
+
+	prepareProgram(0);
+	prepareNextRound();
+}
+
+void OperatorPerformanceCase::deinit (void)
+{
+	if (!m_attribBuffers.empty())
+	{
+		m_renderCtx.getFunctions().deleteBuffers((glw::GLsizei)m_attribBuffers.size(), &m_attribBuffers[0]);
+		m_attribBuffers.clear();
+	}
+
+	m_programs.clear();
+}
+
+void OperatorPerformanceCase::render (int numDrawCalls)
+{
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	const int				numVertices		= getNumVertices(m_gridSizeX, m_gridSizeY);
+
+	for (int callNdx = 0; callNdx < numDrawCalls; callNdx++)
+		gl.drawArrays(GL_TRIANGLES, 0, numVertices);
+
+	glu::readPixels(m_renderCtx, 0, 0, tcu::Surface(1, 1).getAccess()); // \note Serves as a more reliable replacement for glFinish().
+}
+
+deUint64 OperatorPerformanceCase::renderAndMeasure (int numDrawCalls)
+{
+	const deUint64 startTime = deGetMicroseconds();
+	render(numDrawCalls);
+	return deGetMicroseconds() - startTime;
+}
+
+void OperatorPerformanceCase::adjustAndLogGridAndViewport (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	// If call count is just 1, and the target frame time still wasn't reached, reduce grid or viewport size.
+	if (m_calibrator.getCallCount() == 1)
+	{
+		const gls::MeasureState&	calibratorMeasure	= m_calibrator.getMeasureState();
+		const float					drawCallTime		= (float)calibratorMeasure.getTotalTime() / (float)calibratorMeasure.frameTimes.size();
+		const float					targetDrawCallTime	= m_calibrator.getParameters().targetFrameTimeUs;
+		const float					targetRatio			= targetDrawCallTime / drawCallTime;
+
+		if (targetRatio < 0.95f)
+		{
+			// Reduce grid or viewport size assuming draw call time scales proportionally.
+			if (m_caseType == CASETYPE_VERTEX)
+			{
+				const float targetRatioSqrt = deFloatSqrt(targetRatio);
+				m_gridSizeX = (int)(targetRatioSqrt * (float)m_gridSizeX);
+				m_gridSizeY = (int)(targetRatioSqrt * (float)m_gridSizeY);
+				TCU_CHECK_MSG(m_gridSizeX >= 1 && m_gridSizeY >= 1, "Can't decrease grid size enough to achieve low-enough draw times");
+				log << TestLog::Message << "Note: triangle grid size reduced from original; it's now smaller than during calibration." << TestLog::EndMessage;
+			}
+			else
+			{
+				const float targetRatioSqrt = deFloatSqrt(targetRatio);
+				m_viewportWidth  = (int)(targetRatioSqrt * (float)m_viewportWidth);
+				m_viewportHeight = (int)(targetRatioSqrt * (float)m_viewportHeight);
+				TCU_CHECK_MSG(m_viewportWidth >= 1 && m_viewportHeight >= 1, "Can't decrease viewport size enough to achieve low-enough draw times");
+				log << TestLog::Message << "Note: viewport size reduced from original; it's now smaller than during calibration." << TestLog::EndMessage;
+			}
+		}
+	}
+
+	prepareProgram(0);
+
+	// Log grid and viewport sizes.
+	log << TestLog::Message << "Grid size: " << m_gridSizeX << "x" << m_gridSizeY << TestLog::EndMessage;
+	log << TestLog::Message << "Viewport: " << m_viewportWidth << "x" << m_viewportHeight << TestLog::EndMessage;
+}
+
+OperatorPerformanceCase::IterateResult OperatorPerformanceCase::iterate (void)
+{
+	const TheilSenCalibrator::State calibratorState = m_calibrator.getState();
+
+	if (calibratorState != TheilSenCalibrator::STATE_FINISHED)
+	{
+		if (calibratorState == TheilSenCalibrator::STATE_RECOMPUTE_PARAMS)
+			m_calibrator.recomputeParameters();
+		else if (calibratorState == TheilSenCalibrator::STATE_MEASURE)
+			m_calibrator.recordIteration(renderAndMeasure(m_calibrator.getCallCount()));
+		else
+			DE_ASSERT(false);
+
+		if (m_calibrator.getState() == TheilSenCalibrator::STATE_FINISHED)
+		{
+			logCalibrationInfo(m_testCtx.getLog(), m_calibrator);
+			adjustAndLogGridAndViewport();
+			prepareNextRound();
+			m_initialCalibrationStorage->initialNumCalls = m_calibrator.getCallCount();
+		}
+	}
+	else if (m_state == STATE_FIND_HIGH_WORKLOAD || m_state == STATE_MEASURING)
+	{
+		if (m_workloadMeasurementNdx < m_numMeasurementsPerWorkload)
+		{
+			vector<WorkloadRecord>& records = m_state == STATE_FIND_HIGH_WORKLOAD ? m_workloadRecordsFindHigh[m_measureProgramNdx] : m_workloadRecords[m_measureProgramNdx];
+			records.back().addFrameTime((float)renderAndMeasure(m_calibrator.getCallCount()));
+			m_workloadMeasurementNdx++;
+		}
+		else
+			prepareNextRound();
+	}
+	else
+	{
+		DE_ASSERT(m_state == STATE_REPORTING);
+
+		TestLog&	log				= m_testCtx.getLog();
+		const int	drawCallCount	= m_calibrator.getCallCount();
+
+		{
+			// Compute per-program estimators for measurements.
+			vector<SegmentedEstimator> estimators;
+			for (int progNdx = 0; progNdx < (int)m_programs.size(); progNdx++)
+				estimators.push_back(computeSegmentedEstimator(getWorkloadMedianDataPoints(progNdx)));
+
+			// Log measurements and their estimators for all programs.
+			for (int progNdx = 0; progNdx < (int)m_programs.size(); progNdx++)
+			{
+				const SegmentedEstimator&	estimator	= estimators[progNdx];
+				const string				progNdxStr	= de::toString(progNdx);
+				vector<WorkloadRecord>		records		= m_workloadRecords[progNdx];
+				std::sort(records.begin(), records.end());
+
+				{
+					const tcu::ScopedLogSection section(log,
+														"Program" + progNdxStr + "Measurements",
+														"Measurements for program " + progNdxStr);
+
+					// Sample list of individual frame times.
+
+					log << TestLog::SampleList("Program" + progNdxStr + "IndividualFrameTimes", "Individual frame times")
+						<< TestLog::SampleInfo << TestLog::ValueInfo("Workload",	"Workload",		"",		QP_SAMPLE_VALUE_TAG_PREDICTOR)
+											   << TestLog::ValueInfo("FrameTime",	"Frame time",	"us",	QP_SAMPLE_VALUE_TAG_RESPONSE)
+						<< TestLog::EndSampleInfo;
+
+					for (int i = 0; i < (int)records.size(); i++)
+						for (int j = 0; j < (int)records[i].frameTimes.size(); j++)
+							log << TestLog::Sample << records[i].workloadSize << records[i].frameTimes[j] << TestLog::EndSample;
+
+					log << TestLog::EndSampleList;
+
+					// Sample list of median frame times.
+
+					log << TestLog::SampleList("Program" + progNdxStr + "MedianFrameTimes", "Median frame times")
+						<< TestLog::SampleInfo << TestLog::ValueInfo("Workload",		"Workload",				"",		QP_SAMPLE_VALUE_TAG_PREDICTOR)
+											   << TestLog::ValueInfo("MedianFrameTime",	"Median frame time",	"us",	QP_SAMPLE_VALUE_TAG_RESPONSE)
+						<< TestLog::EndSampleInfo;
+
+					for (int i = 0; i < (int)records.size(); i++)
+						log << TestLog::Sample << records[i].workloadSize << records[i].getMedianTime() << TestLog::EndSample;
+
+					log << TestLog::EndSampleList;
+
+					log << TestLog::Float("Program" + progNdxStr + "WorkloadCostEstimate", "Workload cost estimate", "us / workload", QP_KEY_TAG_TIME, estimator.right.coefficient);
+
+					if (estimator.pivotX > -std::numeric_limits<float>::infinity())
+						log << TestLog::Message << "Note: the data points with x coordinate greater than or equal to " << estimator.pivotX
+												<< " seem to form a rising line, and the rest of data points seem to form a near-horizontal line" << TestLog::EndMessage
+							<< TestLog::Message << "Note: the left line is estimated to be " << lineParamsString(estimator.left)
+												<< " and the right line " << lineParamsString(estimator.right) << TestLog::EndMessage;
+					else
+						log << TestLog::Message << "Note: the data seem to form a single line: " << lineParamsString(estimator.right) << TestLog::EndMessage;
+				}
+			}
+
+			for (int progNdx = 0; progNdx < (int)m_programs.size(); progNdx++)
+			{
+				if (estimators[progNdx].right.coefficient <= 0.0f)
+				{
+					log << TestLog::Message << "Slope of measurements for program " << progNdx << " isn't positive. Can't get sensible result." << TestLog::EndMessage;
+					MEASUREMENT_FAIL();
+				}
+			}
+
+			// \note For each estimator, .right.coefficient is the increase in draw time (in microseconds) when
+			// incrementing shader workload size by 1, when D draw calls are done, with a vertex/fragment count
+			// of R.
+			//
+			// The measurements of any single program can't tell us the final result (time of single operation),
+			// so we use computeSingleOperationTime to compute it from multiple programs' measurements in a
+			// subclass-defined manner.
+			//
+			// After that, microseconds per operation can be calculated as singleOperationTime / (D * R).
+
+			{
+				vector<float>	perProgramSlopes;
+				for (int i = 0; i < (int)m_programs.size(); i++)
+					perProgramSlopes.push_back(estimators[i].right.coefficient);
+
+				logSingleOperationCalculationInfo();
+
+				const float		maxSlope				= *std::max_element(perProgramSlopes.begin(), perProgramSlopes.end());
+				const float		usecsPerFramePerOp		= computeSingleOperationTime(perProgramSlopes);
+				const int		vertexOrFragmentCount	= m_caseType == CASETYPE_VERTEX ?
+															getNumVertices(m_gridSizeX, m_gridSizeY) :
+															m_viewportWidth*m_viewportHeight;
+				const double	usecsPerDrawCallPerOp	= usecsPerFramePerOp / (double)drawCallCount;
+				const double	usecsPerSingleOp		= usecsPerDrawCallPerOp / (double)vertexOrFragmentCount;
+				const double	megaOpsPerSecond		= (double)(drawCallCount*vertexOrFragmentCount) / usecsPerFramePerOp;
+				const int		numFreeOps				= de::max(0, (int)deFloatFloor(intersectionX(estimators[0].left,
+																									 LineParameters(estimators[0].right.offset,
+																													usecsPerFramePerOp))));
+
+				log << TestLog::Integer("VertexOrFragmentCount",
+										"R = " + string(m_caseType == CASETYPE_VERTEX ? "Vertex" : "Fragment") + " count",
+										"", QP_KEY_TAG_NONE, vertexOrFragmentCount)
+
+					<< TestLog::Integer("DrawCallsPerFrame", "D = Draw calls per frame", "", QP_KEY_TAG_NONE, drawCallCount)
+
+					<< TestLog::Integer("VerticesOrFragmentsPerFrame",
+										"R*D = " + string(m_caseType == CASETYPE_VERTEX ? "Vertices" : "Fragments") + " per frame",
+										"", QP_KEY_TAG_NONE, vertexOrFragmentCount*drawCallCount)
+
+					<< TestLog::Float("TimePerFramePerOp",
+									  "Estimated cost of R*D " + string(m_caseType == CASETYPE_VERTEX ? "vertices" : "fragments")
+									  + " (i.e. one frame) with one shader operation",
+									  "us", QP_KEY_TAG_TIME, (float)usecsPerFramePerOp)
+
+					<< TestLog::Float("TimePerDrawcallPerOp",
+									  "Estimated cost of one draw call with one shader operation",
+									  "us", QP_KEY_TAG_TIME, (float)usecsPerDrawCallPerOp)
+
+					<< TestLog::Float("TimePerSingleOp",
+									  "Estimated cost of a single shader operation",
+									  "us", QP_KEY_TAG_TIME, (float)usecsPerSingleOp);
+
+				// \note Sometimes, when the operation is free or very cheap, it can happen that the shader with the operation runs,
+				//		 for some reason, a bit faster than the shader without the operation, and thus we get a negative result. The
+				//		 following threshold values for accepting a negative or almost-zero result are rather quick and dirty.
+				if (usecsPerFramePerOp <= -0.1f*maxSlope)
+				{
+					log << TestLog::Message << "Got strongly negative result." << TestLog::EndMessage;
+					MEASUREMENT_FAIL();
+				}
+				else if (usecsPerFramePerOp <= 0.001*maxSlope)
+				{
+					log << TestLog::Message << "Cost of operation seems to be approximately zero." << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+				}
+				else
+				{
+					log << TestLog::Float("OpsPerSecond",
+										  "Operations per second",
+										  "Million/s", QP_KEY_TAG_PERFORMANCE, (float)megaOpsPerSecond)
+
+						<< TestLog::Integer("NumFreeOps",
+											"Estimated number of \"free\" operations",
+											"", QP_KEY_TAG_PERFORMANCE, numFreeOps);
+
+					m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)megaOpsPerSecond, 2).c_str());
+				}
+
+				m_state = STATE_FINISHED;
+			}
+		}
+
+		return STOP;
+	}
+
+	return CONTINUE;
+}
+
+// Binary operator case.
+class BinaryOpCase : public OperatorPerformanceCase
+{
+public:
+						BinaryOpCase				(Context& context, const char* name, const char* description, const char* op,
+													 glu::DataType type, glu::Precision precision, bool useSwizzle, bool isVertex, const InitialCalibrationStorage& initialCalibration);
+
+protected:
+	vector<ProgramContext>	generateProgramData					(void) const;
+	void					setGeneralUniforms					(deUint32 program) const;
+	void					setWorkloadSizeUniform				(deUint32 program, int numOperations) const;
+	float					computeSingleOperationTime			(const vector<float>& perProgramOperationCosts) const;
+	void					logSingleOperationCalculationInfo	(void) const;
+
+private:
+	enum ProgramID
+	{
+		// \note 0-based sequential numbering is relevant, because these are also used as vector indices.
+		// \note The first program should be the heaviest, because OperatorPerformanceCase uses it to reduce grid/viewport size when going too slow.
+		PROGRAM_WITH_BIGGER_LOOP = 0,
+		PROGRAM_WITH_SMALLER_LOOP,
+
+		PROGRAM_LAST
+	};
+
+	ProgramContext			generateSingleProgramData		(ProgramID) const;
+
+	const string			m_op;
+	const glu::DataType		m_type;
+	const glu::Precision	m_precision;
+	const bool				m_useSwizzle;
+};
+
+BinaryOpCase::BinaryOpCase (Context& context, const char* name, const char* description, const char* op,
+							glu::DataType type, glu::Precision precision, bool useSwizzle, bool isVertex, const InitialCalibrationStorage& initialCalibration)
+	: OperatorPerformanceCase	(context.getTestContext(), context.getRenderContext(), name, description,
+								 isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT, NUM_WORKLOADS, initialCalibration)
+	, m_op						(op)
+	, m_type					(type)
+	, m_precision				(precision)
+	, m_useSwizzle				(useSwizzle)
+{
+}
+
+BinaryOpCase::ProgramContext BinaryOpCase::generateSingleProgramData (ProgramID programID) const
+{
+	DE_ASSERT(glu::isDataTypeFloatOrVec(m_type) || glu::isDataTypeIntOrIVec(m_type));
+
+	const bool			isVertexCase	= m_caseType == CASETYPE_VERTEX;
+	const char* const	precision		= glu::getPrecisionName(m_precision);
+	const char* const	inputPrecision	= glu::isDataTypeIntOrIVec(m_type) && m_precision == glu::PRECISION_LOWP ? "mediump" : precision;
+	const char* const	typeName		= getDataTypeName(m_type);
+
+	std::ostringstream	vtx;
+	std::ostringstream	frag;
+	std::ostringstream&	op				= isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n";
+	frag << "#version 300 es\n"
+		 << "layout (location = 0) out mediump vec4 o_color;\n";
+
+	// Attributes.
+	vtx << "in highp vec4 a_position;\n";
+	for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS+1; i++)
+		vtx << "in " << inputPrecision << " vec4 a_in" << i << ";\n";
+
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS+1; i++)
+		{
+			vtx << "out " << inputPrecision << " vec4 v_in" << i << ";\n";
+			frag << "in " << inputPrecision << " vec4 v_in" << i << ";\n";
+		}
+	}
+
+	op << "uniform mediump int u_numLoopIterations;\n";
+	if (isVertexCase)
+		op << "uniform mediump float u_zero;\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+
+	if (!isVertexCase)
+		vtx << "\tgl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Expression inputs.
+	const char* const prefix = isVertexCase ? "a_" : "v_";
+	for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS+1; i++)
+	{
+		const int	inSize		= getDataTypeScalarSize(m_type);
+		const bool	isInt		= de::inRange<int>(m_type, TYPE_INT, TYPE_INT_VEC4);
+		const bool	cast		= isInt || (!m_useSwizzle && m_type != TYPE_FLOAT_VEC4);
+
+		op << "\t" << precision << " " << typeName << " in" << i << " = ";
+
+		if (cast)
+			op << typeName << "(";
+
+		op << prefix << "in" << i;
+
+		if (m_useSwizzle)
+			op << "." << s_swizzles[i % DE_LENGTH_OF_ARRAY(s_swizzles)][inSize-1];
+
+		if (cast)
+			op << ")";
+
+		op << ";\n";
+	}
+
+	// Operation accumulation variables.
+	for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS; i++)
+	{
+		op << "\t" << precision << " " << typeName << " acc" << i << "a" << " = in" << i+0 << ";\n";
+		op << "\t" << precision << " " << typeName << " acc" << i << "b" << " = in" << i+1 << ";\n";
+	}
+
+	// Loop, with expressions in it.
+	op << "\tfor (int i = 0; i < u_numLoopIterations; i++)\n";
+	op << "\t{\n";
+	{
+		const int unrollAmount = programID == PROGRAM_WITH_SMALLER_LOOP ? BINARY_OPERATOR_CASE_SMALL_PROGRAM_UNROLL_AMOUNT : BINARY_OPERATOR_CASE_BIG_PROGRAM_UNROLL_AMOUNT;
+		for (int unrollNdx = 0; unrollNdx < unrollAmount; unrollNdx++)
+		{
+			for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS; i++)
+			{
+				if (i > 0 || unrollNdx > 0)
+					op << "\n";
+				op << "\t\tacc" << i << "a = acc" << i << "b " << m_op << " acc" << i << "a" << ";\n";
+				op << "\t\tacc" << i << "b = acc" << i << "a " << m_op << " acc" << i << "b" << ";\n";
+			}
+		}
+	}
+	op << "\t}\n";
+	op << "\n";
+
+	// Result variable (sum of accumulation variables).
+	op << "\t" << precision << " " << typeName << " res =";
+	for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS; i++)
+		op << (i > 0 ? " "+m_op : "") << " acc" << i << "b";
+	op << ";\n";
+
+	// Convert to color.
+	op << "\tmediump vec4 color = ";
+	if (m_type == TYPE_FLOAT_VEC4)
+		op << "res";
+	else
+	{
+		int size = getDataTypeScalarSize(m_type);
+		op << "vec4(res";
+
+		for (int i = size; i < 4; i++)
+			op << ", " << (i == 3 ? "1.0" : "0.0");
+
+		op << ")";
+	}
+	op << ";\n";
+	op << "\t" << (isVertexCase ? "v_color" : "o_color") << " = color;\n";
+
+	if (isVertexCase)
+	{
+		vtx << "	gl_Position = a_position + u_zero*color;\n";
+		frag << "	o_color = v_color;\n";
+	}
+	else
+	{
+		for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS+1; i++)
+			vtx << "	v_in" << i << " = a_in" << i << ";\n";
+	}
+
+	vtx << "}\n";
+	frag << "}\n";
+
+	{
+		vector<AttribSpec> attributes;
+		for (int i = 0; i < BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS+1; i++)
+			attributes.push_back(AttribSpec(("a_in" + de::toString(i)).c_str(),
+											Vec4(2.0f, 2.0f, 2.0f, 1.0f).swizzle((i+0)%4, (i+1)%4, (i+2)%4, (i+3)%4),
+											Vec4(1.0f, 2.0f, 1.0f, 2.0f).swizzle((i+0)%4, (i+1)%4, (i+2)%4, (i+3)%4),
+											Vec4(2.0f, 1.0f, 2.0f, 2.0f).swizzle((i+0)%4, (i+1)%4, (i+2)%4, (i+3)%4),
+											Vec4(1.0f, 1.0f, 2.0f, 1.0f).swizzle((i+0)%4, (i+1)%4, (i+2)%4, (i+3)%4)));
+
+		{
+			string description = "This is the program with the ";
+
+			description += programID == PROGRAM_WITH_SMALLER_LOOP	? "smaller"
+						 : programID == PROGRAM_WITH_BIGGER_LOOP	? "bigger"
+						 : DE_NULL;
+
+			description += " loop.\n"
+						   "Note: workload size for this program means the number of loop iterations.";
+
+			return ProgramContext(vtx.str(), frag.str(), attributes, description);
+		}
+	}
+}
+
+vector<BinaryOpCase::ProgramContext> BinaryOpCase::generateProgramData (void) const
+{
+	vector<ProgramContext> progData;
+	for (int i = 0; i < PROGRAM_LAST; i++)
+		progData.push_back(generateSingleProgramData((ProgramID)i));
+	return progData;
+}
+
+void BinaryOpCase::setGeneralUniforms (deUint32 program) const
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.uniform1f(gl.getUniformLocation(program, "u_zero"), 0.0f);
+}
+
+void BinaryOpCase::setWorkloadSizeUniform (deUint32 program, int numLoopIterations) const
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.uniform1i(gl.getUniformLocation(program, "u_numLoopIterations"), numLoopIterations);
+}
+
+float BinaryOpCase::computeSingleOperationTime (const vector<float>& perProgramOperationCosts) const
+{
+	DE_ASSERT(perProgramOperationCosts.size() == PROGRAM_LAST);
+
+	const int		baseNumOpsInsideLoop				= 2 * BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS;
+	const int		numOpsInsideLoopInSmallProgram		= baseNumOpsInsideLoop * BINARY_OPERATOR_CASE_SMALL_PROGRAM_UNROLL_AMOUNT;
+	const int		numOpsInsideLoopInBigProgram		= baseNumOpsInsideLoop * BINARY_OPERATOR_CASE_BIG_PROGRAM_UNROLL_AMOUNT;
+	DE_STATIC_ASSERT(numOpsInsideLoopInBigProgram > numOpsInsideLoopInSmallProgram);
+	const int		opDiff								= numOpsInsideLoopInBigProgram - numOpsInsideLoopInSmallProgram;
+	const float		programOperationCostDiff			= perProgramOperationCosts[PROGRAM_WITH_BIGGER_LOOP] - perProgramOperationCosts[PROGRAM_WITH_SMALLER_LOOP];
+
+	return programOperationCostDiff / (float)opDiff;
+}
+
+void BinaryOpCase::logSingleOperationCalculationInfo (void) const
+{
+	const int			baseNumOpsInsideLoop			= 2 * BINARY_OPERATOR_CASE_NUM_INDEPENDENT_CALCULATIONS;
+	const int			numOpsInsideLoopInSmallProgram	= baseNumOpsInsideLoop * BINARY_OPERATOR_CASE_SMALL_PROGRAM_UNROLL_AMOUNT;
+	const int			numOpsInsideLoopInBigProgram	= baseNumOpsInsideLoop * BINARY_OPERATOR_CASE_BIG_PROGRAM_UNROLL_AMOUNT;
+	const int			opDiff							= numOpsInsideLoopInBigProgram - numOpsInsideLoopInSmallProgram;
+	const char* const	opName							= m_op == "+" ? "addition"
+														: m_op == "-" ? "subtraction"
+														: m_op == "*" ? "multiplication"
+														: m_op == "/" ? "division"
+														: DE_NULL;
+	DE_ASSERT(opName != DE_NULL);
+
+	m_testCtx.getLog() << TestLog::Message << "Note: the bigger program contains " << opDiff << " more "
+										   << opName << " operations in one loop iteration than the small program; "
+										   << "cost of one operation is calculated as (cost_of_bigger_workload - cost_of_smaller_workload) / " << opDiff
+										   << TestLog::EndMessage;
+}
+
+// Built-in function case.
+class FunctionCase : public OperatorPerformanceCase
+{
+public:
+	enum
+	{
+		MAX_PARAMS = 3
+	};
+
+						FunctionCase			(Context&							context,
+												 const char*						name,
+												 const char*						description,
+												 const char*						func,
+												 glu::DataType						returnType,
+												 const glu::DataType				paramTypes[MAX_PARAMS],
+												 const Vec4&						attribute,
+												 int								modifyParamNdx, //!< Add a compile-time constant (2.0) to the parameter at this index. This is ignored if negative.
+												 bool								useNearlyConstantINputs, //!< Function inputs shouldn't be much bigger than 'attribute'.
+												 glu::Precision						precision,
+												 bool								isVertex,
+												 const InitialCalibrationStorage&	initialCalibration);
+
+protected:
+	vector<ProgramContext>	generateProgramData					(void) const;
+	void					setGeneralUniforms					(deUint32 program) const;
+	void					setWorkloadSizeUniform				(deUint32 program, int numOperations) const;
+	float					computeSingleOperationTime			(const vector<float>& perProgramOperationCosts) const;
+	void					logSingleOperationCalculationInfo	(void) const;
+
+private:
+	enum ProgramID
+	{
+		// \note 0-based sequential numbering is relevant, because these are also used as vector indices.
+		// \note The first program should be the heaviest, because OperatorPerformanceCase uses it to reduce grid/viewport size when going too slow.
+		PROGRAM_WITH_FUNCTION_CALLS = 0,
+		PROGRAM_WITHOUT_FUNCTION_CALLS,
+
+		PROGRAM_LAST
+	};
+
+	//! Forms a "sum" expression from aExpr and bExpr; for booleans, this is "equal(a,b)", otherwise actual sum.
+	static string		sumExpr						(const string& aExpr, const string& bExpr, glu::DataType type);
+	//! Forms an expression used to increment an input value in the shader. If type is boolean, this is just
+	//! baseExpr; otherwise, baseExpr is modified by multiplication or division by a loop index,
+	//! to prevent simple compiler optimizations. See m_useNearlyConstantInputs for more explanation.
+	static string		incrementExpr				(const string& baseExpr, glu::DataType type, bool divide);
+
+	ProgramContext		generateSingleProgramData	(ProgramID) const;
+
+	const string			m_func;
+	const glu::DataType		m_returnType;
+	glu::DataType			m_paramTypes[MAX_PARAMS];
+	// \note m_modifyParamNdx, if not negative, specifies the index of the parameter to which a
+	//		 compile-time constant (2.0) is added. This is a quick and dirty way to deal with
+	//		 functions like clamp or smoothstep that require that a certain parameter is
+	//		 greater than a certain other parameter.
+	const int				m_modifyParamNdx;
+	// \note m_useNearlyConstantInputs determines whether the inputs given to the function
+	//		 should increase (w.r.t m_attribute) only by very small amounts. This is relevant
+	//		 for functions like asin, which requires its inputs to be in a specific range.
+	//		 In practice, this affects whether expressions used to increment the input
+	//		 variables use division instead of multiplication; normally, multiplication is used,
+	//		 but it's hard to keep the increments very small that way, and division shouldn't
+	//		 be the default, since for many functions (probably not asin, luckily), division
+	//		 is too heavy and dominates time-wise.
+	const bool				m_useNearlyConstantInputs;
+	const Vec4				m_attribute;
+	const glu::Precision	m_precision;
+};
+
+FunctionCase::FunctionCase (Context&							context,
+							const char*							name,
+							const char*							description,
+							const char*							func,
+							glu::DataType						returnType,
+							const glu::DataType					paramTypes[MAX_PARAMS],
+							const Vec4&							attribute,
+							int									modifyParamNdx,
+							bool								useNearlyConstantInputs,
+							glu::Precision						precision,
+							bool								isVertex,
+							const InitialCalibrationStorage&	initialCalibration)
+	: OperatorPerformanceCase	(context.getTestContext(), context.getRenderContext(), name, description,
+								 isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT, NUM_WORKLOADS, initialCalibration)
+	, m_func					(func)
+	, m_returnType				(returnType)
+	, m_modifyParamNdx			(modifyParamNdx)
+	, m_useNearlyConstantInputs	(useNearlyConstantInputs)
+	, m_attribute				(attribute)
+	, m_precision				(precision)
+{
+	for (int i = 0; i < MAX_PARAMS; i++)
+		m_paramTypes[i] = paramTypes[i];
+}
+
+string FunctionCase::sumExpr (const string& aExpr, const string& bExpr, glu::DataType type)
+{
+	if (glu::isDataTypeBoolOrBVec(type))
+	{
+		if (type == glu::TYPE_BOOL)
+			return "(" + aExpr + " == " + bExpr + ")";
+		else
+			return "equal(" + aExpr + ", " + bExpr + ")";
+	}
+	else
+		return "(" + aExpr + " + " + bExpr + ")";
+}
+
+string FunctionCase::incrementExpr (const string& baseExpr, glu::DataType type, bool divide)
+{
+	const string mulOrDiv = divide ? "/" : "*";
+
+	return glu::isDataTypeBoolOrBVec(type)	? baseExpr
+		 : glu::isDataTypeIntOrIVec(type)	? "(" + baseExpr + mulOrDiv + "(i+1))"
+		 :									  "(" + baseExpr + mulOrDiv + "float(i+1))";
+}
+
+FunctionCase::ProgramContext FunctionCase::generateSingleProgramData (ProgramID programID) const
+{
+	const bool			isVertexCase			= m_caseType == CASETYPE_VERTEX;
+	const char* const	precision				= glu::getPrecisionName(m_precision);
+	const char* const	returnTypeName			= getDataTypeName(m_returnType);
+	const string		returnPrecisionMaybe	= glu::isDataTypeBoolOrBVec(m_returnType) ? "" : string() + precision + " ";
+	const char*			inputPrecision			= DE_NULL;
+	const bool			isMatrixReturn			= isDataTypeMatrix(m_returnType);
+	int					numParams				= 0;
+	const char*			paramTypeNames[MAX_PARAMS];
+	string				paramPrecisionsMaybe[MAX_PARAMS];
+
+	for (int i = 0; i < MAX_PARAMS; i++)
+	{
+		paramTypeNames[i]			= getDataTypeName(m_paramTypes[i]);
+		paramPrecisionsMaybe[i]		= glu::isDataTypeBoolOrBVec(m_paramTypes[i]) ? "" : string() + precision + " ";
+
+		if (inputPrecision == DE_NULL && isDataTypeIntOrIVec(m_paramTypes[i]) && m_precision == glu::PRECISION_LOWP)
+			inputPrecision = "mediump";
+
+		if (m_paramTypes[i] != TYPE_INVALID)
+			numParams = i+1;
+	}
+
+	DE_ASSERT(numParams > 0);
+
+	if (inputPrecision == DE_NULL)
+		inputPrecision = precision;
+
+	int						numAttributes	= FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS + numParams - 1;
+	std::ostringstream		vtx;
+	std::ostringstream		frag;
+	std::ostringstream&		op				= isVertexCase ? vtx : frag;
+
+	vtx << "#version 300 es\n";
+	frag << "#version 300 es\n"
+		 << "layout (location = 0) out mediump vec4 o_color;\n";
+
+	// Attributes.
+	vtx << "in highp vec4 a_position;\n";
+	for (int i = 0; i < numAttributes; i++)
+		vtx << "in " << inputPrecision << " vec4 a_in" << i << ";\n";
+
+	if (isVertexCase)
+	{
+		vtx << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+	else
+	{
+		for (int i = 0; i < numAttributes; i++)
+		{
+			vtx << "out " << inputPrecision << " vec4 v_in" << i << ";\n";
+			frag << "in " << inputPrecision << " vec4 v_in" << i << ";\n";
+		}
+	}
+
+	op << "uniform mediump int u_numLoopIterations;\n";
+	if (isVertexCase)
+		op << "uniform mediump float u_zero;\n";
+
+	for (int paramNdx = 0; paramNdx < numParams; paramNdx++)
+		op << "uniform " << paramPrecisionsMaybe[paramNdx] << paramTypeNames[paramNdx] << " u_inc" << (char)('A'+paramNdx) << ";\n";
+
+	vtx << "\n";
+	vtx << "void main()\n";
+	vtx << "{\n";
+
+	if (!isVertexCase)
+		vtx << "\tgl_Position = a_position;\n";
+
+	frag << "\n";
+	frag << "void main()\n";
+	frag << "{\n";
+
+	// Function call input and return value accumulation variables.
+	{
+		const char* const inPrefix = isVertexCase ? "a_" : "v_";
+
+		for (int calcNdx = 0; calcNdx < FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS; calcNdx++)
+		{
+			for (int paramNdx = 0; paramNdx < numParams; paramNdx++)
+			{
+				const glu::DataType		paramType	= m_paramTypes[paramNdx];
+				const bool				mustCast	= paramType != glu::TYPE_FLOAT_VEC4;
+
+				op << "\t" << paramPrecisionsMaybe[paramNdx] << paramTypeNames[paramNdx] << " in" << calcNdx << (char)('a'+paramNdx) << " = ";
+
+				if (mustCast)
+					op << paramTypeNames[paramNdx] << "(";
+
+				if (glu::isDataTypeMatrix(paramType))
+				{
+					static const char* const	swizzles[3]		= { "x", "xy", "xyz" };
+					const int					numRows			= glu::getDataTypeMatrixNumRows(paramType);
+					const int					numCols			= glu::getDataTypeMatrixNumColumns(paramType);
+					const string				swizzle			= numRows < 4 ? string() + "." + swizzles[numRows-1] : "";
+
+					for (int i = 0; i < numCols; i++)
+						op << (i > 0 ? ", " : "") << inPrefix << "in" << calcNdx+paramNdx << swizzle;
+				}
+				else
+				{
+					op << inPrefix << "in" << calcNdx+paramNdx;
+
+					if (paramNdx == m_modifyParamNdx)
+					{
+						DE_ASSERT(glu::isDataTypeFloatOrVec(paramType));
+						op << " + 2.0";
+					}
+				}
+
+				if (mustCast)
+					op << ")";
+
+				op << ";\n";
+			}
+
+			op << "\t" << returnPrecisionMaybe << returnTypeName << " res" << calcNdx << " = " << returnTypeName << "(0);\n";
+		}
+	}
+
+	// Loop with expressions in it.
+	op << "\tfor (int i = 0; i < u_numLoopIterations; i++)\n";
+	op << "\t{\n";
+	for (int calcNdx = 0; calcNdx < FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS; calcNdx++)
+	{
+		if (calcNdx > 0)
+			op << "\n";
+
+		op << "\t\t{\n";
+
+		for (int inputNdx = 0; inputNdx < numParams; inputNdx++)
+		{
+			const string inputName	= "in" + de::toString(calcNdx) + (char)('a'+inputNdx);
+			const string incName	= string() + "u_inc" + (char)('A'+inputNdx);
+			const string incExpr	= incrementExpr(incName, m_paramTypes[inputNdx], m_useNearlyConstantInputs);
+
+			op << "\t\t\t" << inputName << " = " << sumExpr(inputName, incExpr, m_paramTypes[inputNdx]) << ";\n";
+		}
+
+		op << "\t\t\t" << returnPrecisionMaybe << returnTypeName << " eval" << calcNdx << " = ";
+
+		if (programID == PROGRAM_WITH_FUNCTION_CALLS)
+		{
+			op << m_func << "(";
+
+			for (int paramNdx = 0; paramNdx < numParams; paramNdx++)
+			{
+				if (paramNdx > 0)
+					op << ", ";
+
+				op << "in" << calcNdx << (char)('a'+paramNdx);
+			}
+
+			op << ")";
+		}
+		else
+		{
+			DE_ASSERT(programID == PROGRAM_WITHOUT_FUNCTION_CALLS);
+			op << returnTypeName << "(1)";
+		}
+
+		op << ";\n";
+
+		{
+			const string resName	= "res" + de::toString(calcNdx);
+			const string evalName	= "eval" + de::toString(calcNdx);
+			const string incExpr	= incrementExpr(evalName, m_returnType, m_useNearlyConstantInputs);
+
+			op << "\t\t\tres" << calcNdx << " = " << sumExpr(resName, incExpr, m_returnType) << ";\n";
+		}
+
+		op << "\t\t}\n";
+	}
+	op << "\t}\n";
+	op << "\n";
+
+	// Result variables.
+	for (int inputNdx = 0; inputNdx < numParams; inputNdx++)
+	{
+		op << "\t" << paramPrecisionsMaybe[inputNdx] << paramTypeNames[inputNdx] << " sumIn" << (char)('A'+inputNdx) << " = ";
+		{
+			string expr = string() + "in0" + (char)('a'+inputNdx);
+			for (int i = 1; i < FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS; i++)
+				expr = sumExpr(expr, string() + "in" + de::toString(i) + (char)('a'+inputNdx), m_paramTypes[inputNdx]);
+			op << expr;
+		}
+		op << ";\n";
+	}
+
+	op << "\t" << returnPrecisionMaybe << returnTypeName << " sumRes = ";
+	{
+		string expr = "res0";
+		for (int i = 1; i < FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS; i++)
+			expr = sumExpr(expr, "res" + de::toString(i), m_returnType);
+		op << expr;
+	}
+	op << ";\n";
+
+	{
+		glu::DataType finalResultDataType = glu::TYPE_LAST;
+
+		if (glu::isDataTypeMatrix(m_returnType))
+		{
+			finalResultDataType = m_returnType;
+
+			op << "\t" << precision << " " << returnTypeName << " finalRes = ";
+
+			for (int inputNdx = 0; inputNdx < numParams; inputNdx++)
+			{
+				DE_ASSERT(m_paramTypes[inputNdx] == m_returnType);
+				op << "sumIn" << (char)('A'+inputNdx) << " + ";
+			}
+			op << "sumRes;\n";
+		}
+		else
+		{
+			int numFinalResComponents = glu::getDataTypeScalarSize(m_returnType);
+			for (int inputNdx = 0; inputNdx < numParams; inputNdx++)
+				numFinalResComponents = de::max(numFinalResComponents, glu::getDataTypeScalarSize(m_paramTypes[inputNdx]));
+
+			finalResultDataType = getDataTypeFloatOrVec(numFinalResComponents);
+
+			{
+				const string finalResType = glu::getDataTypeName(finalResultDataType);
+				op << "\t" << precision << " " << finalResType << " finalRes = ";
+				for (int inputNdx = 0; inputNdx < numParams; inputNdx++)
+					op << finalResType << "(sumIn" << (char)('A'+inputNdx) << ") + ";
+				op << finalResType << "(sumRes);\n";
+			}
+		}
+
+		// Convert to color.
+		op << "\tmediump vec4 color = ";
+		if (finalResultDataType == TYPE_FLOAT_VEC4)
+			op << "finalRes";
+		else
+		{
+			int size = isMatrixReturn ? getDataTypeMatrixNumRows(finalResultDataType) : getDataTypeScalarSize(finalResultDataType);
+
+			op << "vec4(";
+
+			if (isMatrixReturn)
+			{
+				for (int i = 0; i < getDataTypeMatrixNumColumns(finalResultDataType); i++)
+				{
+					if (i > 0)
+						op << " + ";
+					op << "finalRes[" << i << "]";
+				}
+			}
+			else
+				op << "finalRes";
+
+			for (int i = size; i < 4; i++)
+				op << ", " << (i == 3 ? "1.0" : "0.0");
+
+			op << ")";
+		}
+		op << ";\n";
+		op << "\t" << (isVertexCase ? "v_color" : "o_color") << " = color;\n";
+
+		if (isVertexCase)
+		{
+			vtx << "	gl_Position = a_position + u_zero*color;\n";
+			frag << "	o_color = v_color;\n";
+		}
+		else
+		{
+			for (int i = 0; i < numAttributes; i++)
+				vtx << "	v_in" << i << " = a_in" << i << ";\n";
+		}
+
+		vtx << "}\n";
+		frag << "}\n";
+	}
+
+	{
+		vector<AttribSpec> attributes;
+		for (int i = 0; i < numAttributes; i++)
+			attributes.push_back(AttribSpec(("a_in" + de::toString(i)).c_str(),
+											m_attribute.swizzle((i+0)%4, (i+1)%4, (i+2)%4, (i+3)%4),
+											m_attribute.swizzle((i+1)%4, (i+2)%4, (i+3)%4, (i+0)%4),
+											m_attribute.swizzle((i+2)%4, (i+3)%4, (i+0)%4, (i+1)%4),
+											m_attribute.swizzle((i+3)%4, (i+0)%4, (i+1)%4, (i+2)%4)));
+
+		{
+			string description = "This is the program ";
+
+			description += programID == PROGRAM_WITHOUT_FUNCTION_CALLS	? "without"
+						 : programID == PROGRAM_WITH_FUNCTION_CALLS		? "with"
+						 : DE_NULL;
+
+			description += " '" + m_func + "' function calls.\n"
+						   "Note: workload size for this program means the number of loop iterations.";
+
+			return ProgramContext(vtx.str(), frag.str(), attributes, description);
+		}
+	}
+}
+
+vector<FunctionCase::ProgramContext> FunctionCase::generateProgramData (void) const
+{
+	vector<ProgramContext> progData;
+	for (int i = 0; i < PROGRAM_LAST; i++)
+		progData.push_back(generateSingleProgramData((ProgramID)i));
+	return progData;
+}
+
+void FunctionCase::setGeneralUniforms (deUint32 program) const
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	gl.uniform1f(gl.getUniformLocation(program, "u_zero"), 0.0f);
+
+	for (int paramNdx = 0; paramNdx < MAX_PARAMS; paramNdx++)
+	{
+		if (m_paramTypes[paramNdx] != glu::TYPE_INVALID)
+		{
+			const glu::DataType		paramType	= m_paramTypes[paramNdx];
+			const int				scalarSize	= glu::getDataTypeScalarSize(paramType);
+			const int				location	= gl.getUniformLocation(program, (string() + "u_inc" + (char)('A'+paramNdx)).c_str());
+
+			if (glu::isDataTypeFloatOrVec(paramType))
+			{
+				float values[4];
+				for (int i = 0; i < DE_LENGTH_OF_ARRAY(values); i++)
+					values[i] = (float)paramNdx*0.01f + (float)i*0.001f; // Arbitrary small values.
+				uniformNfv(gl, scalarSize, location, 1, &values[0]);
+			}
+			else if (glu::isDataTypeIntOrIVec(paramType))
+			{
+				int values[4];
+				for (int i = 0; i < DE_LENGTH_OF_ARRAY(values); i++)
+					values[i] = paramNdx*100 + i; // Arbitrary values.
+				uniformNiv(gl, scalarSize, location, 1, &values[0]);
+			}
+			else if (glu::isDataTypeBoolOrBVec(paramType))
+			{
+				int values[4];
+				for (int i = 0; i < DE_LENGTH_OF_ARRAY(values); i++)
+					values[i] = (paramNdx >> i) & 1; // Arbitrary values.
+				uniformNiv(gl, scalarSize, location, 1, &values[0]);
+			}
+			else if (glu::isDataTypeMatrix(paramType))
+			{
+				const int size = glu::getDataTypeMatrixNumRows(paramType);
+				DE_ASSERT(size == glu::getDataTypeMatrixNumColumns(paramType));
+				float values[4*4];
+				for (int i = 0; i < DE_LENGTH_OF_ARRAY(values); i++)
+					values[i] = (float)paramNdx*0.01f + (float)i*0.001f; // Arbitrary values.
+				uniformMatrixNfv(gl, size, location, 1, &values[0]);
+			}
+			else
+				DE_ASSERT(false);
+		}
+	}
+}
+
+void FunctionCase::setWorkloadSizeUniform (deUint32 program, int numLoopIterations) const
+{
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+	const int				loc		= gl.getUniformLocation(program, "u_numLoopIterations");
+
+	gl.uniform1i(loc, numLoopIterations);
+}
+
+float FunctionCase::computeSingleOperationTime (const vector<float>& perProgramOperationCosts) const
+{
+	DE_ASSERT(perProgramOperationCosts.size() == PROGRAM_LAST);
+	const int		numFunctionCalls			= FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS;
+	const float		programOperationCostDiff	= perProgramOperationCosts[PROGRAM_WITH_FUNCTION_CALLS] - perProgramOperationCosts[PROGRAM_WITHOUT_FUNCTION_CALLS];
+
+	return programOperationCostDiff / (float)numFunctionCalls;
+}
+
+void FunctionCase::logSingleOperationCalculationInfo (void) const
+{
+	const int numFunctionCalls = FUNCTION_CASE_NUM_INDEPENDENT_CALCULATIONS;
+
+	m_testCtx.getLog() << TestLog::Message << "Note: program " << (int)PROGRAM_WITH_FUNCTION_CALLS << " contains "
+										   << numFunctionCalls << " calls to '" << m_func << "' in one loop iteration; "
+										   << "cost of one operation is calculated as "
+										   << "(cost_of_workload_with_calls - cost_of_workload_without_calls) / " << numFunctionCalls << TestLog::EndMessage;
+}
+
+} // anonymous
+
+ShaderOperatorTests::ShaderOperatorTests (Context& context)
+	: TestCaseGroup(context, "operator", "Operator Performance Tests")
+{
+}
+
+ShaderOperatorTests::~ShaderOperatorTests (void)
+{
+}
+
+void ShaderOperatorTests::init (void)
+{
+	// Binary operator cases
+
+	static const DataType binaryOpTypes[] =
+	{
+		TYPE_FLOAT,
+		TYPE_FLOAT_VEC2,
+		TYPE_FLOAT_VEC3,
+		TYPE_FLOAT_VEC4,
+		TYPE_INT,
+		TYPE_INT_VEC2,
+		TYPE_INT_VEC3,
+		TYPE_INT_VEC4,
+	};
+	static const Precision precisions[] =
+	{
+		PRECISION_LOWP,
+		PRECISION_MEDIUMP,
+		PRECISION_HIGHP
+	};
+	static const struct
+	{
+		const char*		name;
+		const char*		op;
+		bool			swizzle;
+	} binaryOps[] =
+	{
+		{ "add",		"+",		false	},
+		{ "sub",		"-",		true	},
+		{ "mul",		"*",		false	},
+		{ "div",		"/",		true	}
+	};
+
+	tcu::TestCaseGroup* const binaryOpsGroup = new tcu::TestCaseGroup(m_testCtx, "binary_operator", "Binary Operator Performance Tests");
+	addChild(binaryOpsGroup);
+
+	for (int opNdx = 0; opNdx < DE_LENGTH_OF_ARRAY(binaryOps); opNdx++)
+	{
+		tcu::TestCaseGroup* const opGroup = new tcu::TestCaseGroup(m_testCtx, binaryOps[opNdx].name, "");
+		binaryOpsGroup->addChild(opGroup);
+
+		for (int isFrag = 0; isFrag <= 1; isFrag++)
+		{
+			const BinaryOpCase::InitialCalibrationStorage	shaderGroupCalibrationStorage	(new BinaryOpCase::InitialCalibration);
+			const bool										isVertex						= isFrag == 0;
+			tcu::TestCaseGroup* const						shaderGroup						= new tcu::TestCaseGroup(m_testCtx, isVertex ? "vertex" : "fragment", "");
+			opGroup->addChild(shaderGroup);
+
+			for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(binaryOpTypes); typeNdx++)
+			{
+				for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
+				{
+					const DataType		type			= binaryOpTypes[typeNdx];
+					const Precision		precision		= precisions[precNdx];
+					const char* const	op				= binaryOps[opNdx].op;
+					const bool			useSwizzle		= binaryOps[opNdx].swizzle;
+					std::ostringstream	name;
+
+					name << getPrecisionName(precision) << "_" << getDataTypeName(type);
+
+					shaderGroup->addChild(new BinaryOpCase(m_context, name.str().c_str(), "", op, type, precision, useSwizzle, isVertex, shaderGroupCalibrationStorage));
+				}
+			}
+		}
+	}
+
+	// Built-in function cases.
+
+	// Non-specific (i.e. includes gentypes) parameter types for the functions.
+	enum ValueType
+	{
+		VALUE_NONE			= 0,
+		VALUE_FLOAT			= (1<<0),	// float scalar
+		VALUE_FLOAT_VEC		= (1<<1),	// float vector
+		VALUE_FLOAT_VEC34	= (1<<2),	// float vector of size 3 or 4
+		VALUE_FLOAT_GENTYPE	= (1<<3),	// float scalar/vector
+		VALUE_VEC3			= (1<<4),	// vec3 only
+		VALUE_VEC4			= (1<<5),	// vec4 only
+		VALUE_MATRIX		= (1<<6),	// matrix
+		VALUE_BOOL			= (1<<7),	// boolean scalar
+		VALUE_BOOL_VEC		= (1<<8),	// boolean vector
+		VALUE_BOOL_VEC4		= (1<<9),	// bvec4 only
+		VALUE_BOOL_GENTYPE	= (1<<10),	// boolean scalar/vector
+		VALUE_INT			= (1<<11),	// int scalar
+		VALUE_INT_VEC		= (1<<12),	// int vector
+		VALUE_INT_VEC4		= (1<<13),	// ivec4 only
+		VALUE_INT_GENTYPE	= (1<<14),	// int scalar/vector
+
+		// Shorthands.
+		N				= VALUE_NONE,
+		F				= VALUE_FLOAT,
+		FV				= VALUE_FLOAT_VEC,
+		VL				= VALUE_FLOAT_VEC34, // L for "large"
+		GT				= VALUE_FLOAT_GENTYPE,
+		V3				= VALUE_VEC3,
+		V4				= VALUE_VEC4,
+		M				= VALUE_MATRIX,
+		B				= VALUE_BOOL,
+		BV				= VALUE_BOOL_VEC,
+		B4				= VALUE_BOOL_VEC4,
+		BGT				= VALUE_BOOL_GENTYPE,
+		I				= VALUE_INT,
+		IV				= VALUE_INT_VEC,
+		I4				= VALUE_INT_VEC4,
+		IGT				= VALUE_INT_GENTYPE,
+
+		VALUE_ANY_FLOAT			= VALUE_FLOAT		|	VALUE_FLOAT_VEC		|	VALUE_FLOAT_GENTYPE		| VALUE_VEC3 | VALUE_VEC4 | VALUE_FLOAT_VEC34,
+		VALUE_ANY_INT			= VALUE_INT			|	VALUE_INT_VEC		|	VALUE_INT_GENTYPE		| VALUE_INT_VEC4,
+		VALUE_ANY_BOOL			= VALUE_BOOL		|	VALUE_BOOL_VEC		|	VALUE_BOOL_GENTYPE		| VALUE_BOOL_VEC4,
+
+		VALUE_ANY_GENTYPE		= VALUE_FLOAT_VEC	|	VALUE_FLOAT_GENTYPE	|	VALUE_FLOAT_VEC34	|
+								  VALUE_BOOL_VEC	|	VALUE_BOOL_GENTYPE	|
+								  VALUE_INT_VEC		|	VALUE_INT_GENTYPE	|
+								  VALUE_MATRIX
+	};
+	enum PrecisionMask
+	{
+		PRECMASK_NA				= 0,						//!< Precision not applicable (booleans)
+		PRECMASK_LOWP			= (1<<PRECISION_LOWP),
+		PRECMASK_MEDIUMP		= (1<<PRECISION_MEDIUMP),
+		PRECMASK_HIGHP			= (1<<PRECISION_HIGHP),
+
+		PRECMASK_MEDIUMP_HIGHP	= (1<<PRECISION_MEDIUMP) | (1<<PRECISION_HIGHP),
+		PRECMASK_ALL			= (1<<PRECISION_LOWP) | (1<<PRECISION_MEDIUMP) | (1<<PRECISION_HIGHP)
+	};
+
+	static const DataType floatTypes[] =
+	{
+		TYPE_FLOAT,
+		TYPE_FLOAT_VEC2,
+		TYPE_FLOAT_VEC3,
+		TYPE_FLOAT_VEC4
+	};
+	static const DataType intTypes[] =
+	{
+		TYPE_INT,
+		TYPE_INT_VEC2,
+		TYPE_INT_VEC3,
+		TYPE_INT_VEC4
+	};
+	static const DataType boolTypes[] =
+	{
+		TYPE_BOOL,
+		TYPE_BOOL_VEC2,
+		TYPE_BOOL_VEC3,
+		TYPE_BOOL_VEC4
+	};
+	static const DataType matrixTypes[] =
+	{
+		TYPE_FLOAT_MAT2,
+		TYPE_FLOAT_MAT3,
+		TYPE_FLOAT_MAT4
+	};
+
+	tcu::TestCaseGroup* const angleAndTrigonometryGroup		= new tcu::TestCaseGroup(m_testCtx, "angle_and_trigonometry",	"Built-In Angle and Trigonometry Function Performance Tests");
+	tcu::TestCaseGroup* const exponentialGroup				= new tcu::TestCaseGroup(m_testCtx, "exponential",				"Built-In Exponential Function Performance Tests");
+	tcu::TestCaseGroup* const commonFunctionsGroup			= new tcu::TestCaseGroup(m_testCtx, "common_functions",			"Built-In Common Function Performance Tests");
+	tcu::TestCaseGroup* const geometricFunctionsGroup		= new tcu::TestCaseGroup(m_testCtx, "geometric",				"Built-In Geometric Function Performance Tests");
+	tcu::TestCaseGroup* const matrixFunctionsGroup			= new tcu::TestCaseGroup(m_testCtx, "matrix",					"Built-In Matrix Function Performance Tests");
+	tcu::TestCaseGroup* const floatCompareGroup				= new tcu::TestCaseGroup(m_testCtx, "float_compare",			"Built-In Floating Point Comparison Function Performance Tests");
+	tcu::TestCaseGroup* const intCompareGroup				= new tcu::TestCaseGroup(m_testCtx, "int_compare",				"Built-In Integer Comparison Function Performance Tests");
+	tcu::TestCaseGroup* const boolCompareGroup				= new tcu::TestCaseGroup(m_testCtx, "bool_compare",				"Built-In Boolean Comparison Function Performance Tests");
+
+	addChild(angleAndTrigonometryGroup);
+	addChild(exponentialGroup);
+	addChild(commonFunctionsGroup);
+	addChild(geometricFunctionsGroup);
+	addChild(matrixFunctionsGroup);
+	addChild(floatCompareGroup);
+	addChild(intCompareGroup);
+	addChild(boolCompareGroup);
+
+	// Some attributes to be used as parameters for the functions.
+	const Vec4 attrPos		= Vec4( 2.3f,  1.9f,  0.8f,  0.7f);
+	const Vec4 attrNegPos	= Vec4(-1.3f,  2.5f, -3.5f,	 4.3f);
+	const Vec4 attrSmall	= Vec4(-0.9f,  0.8f, -0.4f,	 0.2f);
+	const Vec4 attrBig		= Vec4( 1.3f,  2.4f,  3.0f,	 4.0f);
+
+	// \todo The following functions and variants are missing, and should be added in the future:
+	//		 - modf (has an output parameter, not currently handled by test code)
+	//		 - functions with uint/uvec* return or parameter types
+	//		 - non-matrix <-> matrix functions (outerProduct etc.)
+	// \note Remember to update test spec when these are added.
+
+	// Function name, return type and parameter type information; also, what attribute should be used in the test.
+	// \note Different versions of the same function (i.e. with the same group name) can be defined by putting them successively in this array.
+	// \note In order to reduce case count and thus total execution time, we don't test all input type combinations for every function.
+	static const struct
+	{
+		tcu::TestCaseGroup*					parentGroup;
+		const char*							groupName;
+		const char*							func;
+		const ValueType						types[FunctionCase::MAX_PARAMS + 1]; // Return type and parameter types, in that order.
+		const Vec4&							attribute;
+		int									modifyParamNdx;
+		bool								useNearlyConstantInputs;
+		bool								booleanCase;
+		PrecisionMask						precMask;
+	} functionCaseGroups[] =
+	{
+		{ angleAndTrigonometryGroup,	"radians",			"radians",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"degrees",			"degrees",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"sin",				"sin",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"cos",				"cos",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"tan",				"tan",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"asin",				"asin",				{ F,  F,  N,  N  }, attrSmall,		-1, true,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"acos",				"acos",				{ F,  F,  N,  N  }, attrSmall,		-1, true,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"atan2",			"atan",				{ F,  F,  F,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"atan",				"atan",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"sinh",				"sinh",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"cosh",				"cosh",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"tanh",				"tanh",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"asinh",			"asinh",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"acosh",			"acosh",			{ F,  F,  N,  N  }, attrBig,		-1, false,	false,	PRECMASK_ALL			},
+		{ angleAndTrigonometryGroup,	"atanh",			"atanh",			{ F,  F,  N,  N  }, attrSmall,		-1, true,	false,	PRECMASK_ALL			},
+
+		{ exponentialGroup,				"pow",				"pow",				{ F,  F,  F,  N  }, attrPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"exp",				"exp",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"log",				"log",				{ F,  F,  N,  N  }, attrPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"exp2",				"exp2",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"log2",				"log2",				{ F,  F,  N,  N  }, attrPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"sqrt",				"sqrt",				{ F,  F,  N,  N  }, attrPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ exponentialGroup,				"inversesqrt",		"inversesqrt",		{ F,  F,  N,  N  }, attrPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ commonFunctionsGroup,			"abs",				"abs",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"abs",				"abs",				{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"sign",				"sign",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"sign",				"sign",				{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"floor",			"floor",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"floor",			"floor",			{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"trunc",			"trunc",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"trunc",			"trunc",			{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"round",			"round",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"round",			"round",			{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"roundEven",		"roundEven",		{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"roundEven",		"roundEven",		{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"ceil",				"ceil",				{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"ceil",				"ceil",				{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"fract",			"fract",			{ F,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"fract",			"fract",			{ V4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"mod",				"mod",				{ GT, GT, GT, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"min",				"min",				{ F,  F,  F,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"min",				"min",				{ V4, V4, V4, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"max",				"max",				{ F,  F,  F,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"max",				"max",				{ V4, V4, V4, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"clamp",			"clamp",			{ F,  F,  F,  F  }, attrSmall,		 2, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"clamp",			"clamp",			{ V4, V4, V4, V4 }, attrSmall,		 2, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"mix",				"mix",				{ F,  F,  F,  F  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"mix",				"mix",				{ V4, V4, V4, V4 }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"mix",				"mix",				{ F,  F,  F,  B  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"mix",				"mix",				{ V4, V4, V4, B4 }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"step",				"step",				{ F,  F,  F,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"step",				"step",				{ V4, V4, V4, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"smoothstep",		"smoothstep",		{ F,  F,  F,  F  }, attrSmall,		 1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"smoothstep",		"smoothstep",		{ V4, V4, V4, V4 }, attrSmall,		 1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"isnan",			"isnan",			{ B,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"isnan",			"isnan",			{ B4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"isinf",			"isinf",			{ B,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"isinf",			"isinf",			{ B4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"floatBitsToInt",	"floatBitsToInt",	{ I,  F,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"floatBitsToInt",	"floatBitsToInt",	{ I4, V4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ commonFunctionsGroup,			"intBitsToFloat",	"intBitsToFloat",	{ F,  I,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_MEDIUMP_HIGHP	},
+		{ commonFunctionsGroup,			"intBitsToFloat",	"intBitsToFloat",	{ V4, I4, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ geometricFunctionsGroup,		"length",			"length",			{ F,  VL, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"distance",			"distance",			{ F,  VL, VL, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"dot",				"dot",				{ F,  VL, VL, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"cross",			"cross",			{ V3, V3, V3, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"normalize",		"normalize",		{ VL, VL, N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"faceforward",		"faceforward",		{ VL, VL, VL, VL }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"reflect",			"reflect",			{ VL, VL, VL, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ geometricFunctionsGroup,		"refract",			"refract",			{ VL, VL, VL, F  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ matrixFunctionsGroup,			"matrixCompMult",	"matrixCompMult",	{ M,  M,  M,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ matrixFunctionsGroup,			"transpose",		"transpose",		{ M,  M,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ matrixFunctionsGroup,			"inverse",			"inverse",			{ M,  M,  N,  N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ floatCompareGroup,			"lessThan",			"lessThan",			{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ floatCompareGroup,			"lessThanEqual",	"lessThanEqual",	{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ floatCompareGroup,			"greaterThan",		"greaterThan",		{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ floatCompareGroup,			"greaterThanEqual",	"greaterThanEqual",	{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ floatCompareGroup,			"equal",			"equal",			{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ floatCompareGroup,			"notEqual",			"notEqual",			{ BV, FV, FV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ intCompareGroup,				"lessThan",			"lessThan",			{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ intCompareGroup,				"lessThanEqual",	"lessThanEqual",	{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ intCompareGroup,				"greaterThan",		"greaterThan",		{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ intCompareGroup,				"greaterThanEqual",	"greaterThanEqual",	{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ intCompareGroup,				"equal",			"equal",			{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+		{ intCompareGroup,				"notEqual",			"notEqual",			{ BV, IV, IV, N  }, attrNegPos,		-1, false,	false,	PRECMASK_ALL			},
+
+		{ boolCompareGroup,				"equal",			"equal",			{ BV, BV, BV, N  }, attrNegPos,		-1, false,	true,	PRECMASK_MEDIUMP		},
+		{ boolCompareGroup,				"notEqual",			"notEqual",			{ BV, BV, BV, N  }, attrNegPos,		-1, false,	true,	PRECMASK_MEDIUMP		},
+		{ boolCompareGroup,				"any",				"any",				{ B,  BV, N,  N  }, attrNegPos,		-1, false,	true,	PRECMASK_MEDIUMP		},
+		{ boolCompareGroup,				"all",				"all",				{ B,  BV, N,  N  }, attrNegPos,		-1, false,	true,	PRECMASK_MEDIUMP		},
+		{ boolCompareGroup,				"not",				"not",				{ BV, BV, N,  N  }, attrNegPos,		-1, false,	true,	PRECMASK_MEDIUMP		}
+	};
+
+	// vertexSubGroup and fragmentSubGroup are the groups where the various vertex/fragment cases of a single function are added.
+	// \note These are defined here so that different versions (different entries in the functionCaseGroups array) of the same function can be put in the same group.
+	tcu::TestCaseGroup*							vertexSubGroup		= DE_NULL;
+	tcu::TestCaseGroup*							fragmentSubGroup	= DE_NULL;
+	FunctionCase::InitialCalibrationStorage		vertexSubGroupCalibrationStorage;
+	FunctionCase::InitialCalibrationStorage		fragmentSubGroupCalibrationStorage;
+	for (int funcNdx = 0; funcNdx < DE_LENGTH_OF_ARRAY(functionCaseGroups); funcNdx++)
+	{
+		tcu::TestCaseGroup* const	parentGroup					= functionCaseGroups[funcNdx].parentGroup;
+		const char* const			groupName					= functionCaseGroups[funcNdx].groupName;
+		const char* const			groupFunc					= functionCaseGroups[funcNdx].func;
+		const ValueType* const		funcTypes					= functionCaseGroups[funcNdx].types;
+		const Vec4&					groupAttribute				= functionCaseGroups[funcNdx].attribute;
+		const int					modifyParamNdx				= functionCaseGroups[funcNdx].modifyParamNdx;
+		const bool					useNearlyConstantInputs		= functionCaseGroups[funcNdx].useNearlyConstantInputs;
+		const bool					booleanCase					= functionCaseGroups[funcNdx].booleanCase;
+		const PrecisionMask			precMask					= functionCaseGroups[funcNdx].precMask;
+
+		// If this is a new function and not just a different version of the previously defined function, create a new group.
+		if (funcNdx == 0 || parentGroup != functionCaseGroups[funcNdx-1].parentGroup || string(groupName) != functionCaseGroups[funcNdx-1].groupName)
+		{
+			tcu::TestCaseGroup* const funcGroup = new tcu::TestCaseGroup(m_testCtx, groupName, "");
+			functionCaseGroups[funcNdx].parentGroup->addChild(funcGroup);
+
+			vertexSubGroup		= new tcu::TestCaseGroup(m_testCtx, "vertex", "");
+			fragmentSubGroup	= new tcu::TestCaseGroup(m_testCtx, "fragment", "");
+
+			funcGroup->addChild(vertexSubGroup);
+			funcGroup->addChild(fragmentSubGroup);
+
+			vertexSubGroupCalibrationStorage	= FunctionCase::InitialCalibrationStorage(new FunctionCase::InitialCalibration);
+			fragmentSubGroupCalibrationStorage	= FunctionCase::InitialCalibrationStorage(new FunctionCase::InitialCalibration);
+		}
+
+		DE_ASSERT(vertexSubGroup != DE_NULL);
+		DE_ASSERT(fragmentSubGroup != DE_NULL);
+
+		// Find the type size range of parameters (e.g. from 2 to 4 in case of vectors).
+		int genTypeFirstSize	= 1;
+		int genTypeLastSize		= 1;
+
+		// Find the first return value or parameter with a gentype (if any) and set sizes accordingly.
+		// \note Assumes only matching sizes gentypes are to be found, e.g. no "genType func (vec param)"
+		for (int i = 0; i < FunctionCase::MAX_PARAMS + 1 && genTypeLastSize == 1; i++)
+		{
+			switch (funcTypes[i])
+			{
+				case VALUE_FLOAT_VEC:
+				case VALUE_BOOL_VEC:
+				case VALUE_INT_VEC:			// \note Fall-through.
+					genTypeFirstSize = 2;
+					genTypeLastSize = 4;
+					break;
+				case VALUE_FLOAT_VEC34:
+					genTypeFirstSize = 3;
+					genTypeLastSize = 4;
+					break;
+				case VALUE_FLOAT_GENTYPE:
+				case VALUE_BOOL_GENTYPE:
+				case VALUE_INT_GENTYPE:		// \note Fall-through.
+					genTypeFirstSize = 1;
+					genTypeLastSize = 4;
+					break;
+				case VALUE_MATRIX:
+					genTypeFirstSize = 2;
+					genTypeLastSize = 4;
+					break;
+				// If none of the above, keep looping.
+				default:
+					break;
+			}
+		}
+
+		// Create a case for each possible size of the gentype.
+		for (int curSize = genTypeFirstSize; curSize <= genTypeLastSize; curSize++)
+		{
+			// Determine specific types for return value and the parameters, according to curSize. Non-gentypes not affected by curSize.
+			DataType types[FunctionCase::MAX_PARAMS + 1];
+			for (int i = 0; i < FunctionCase::MAX_PARAMS + 1; i++)
+			{
+				if (funcTypes[i] == VALUE_NONE)
+					types[i] = TYPE_INVALID;
+				else
+				{
+					int isFloat	= funcTypes[i] & VALUE_ANY_FLOAT;
+					int isBool	= funcTypes[i] & VALUE_ANY_BOOL;
+					int isInt	= funcTypes[i] & VALUE_ANY_INT;
+					int isMat	= funcTypes[i] == VALUE_MATRIX;
+					int inSize	= (funcTypes[i] & VALUE_ANY_GENTYPE)	? curSize
+								: funcTypes[i] == VALUE_VEC3			? 3
+								: funcTypes[i] == VALUE_VEC4			? 4
+								: funcTypes[i] == VALUE_BOOL_VEC4		? 4
+								: funcTypes[i] == VALUE_INT_VEC4		? 4
+								: 1;
+					int			typeArrayNdx = isMat ? inSize - 2 : inSize - 1; // \note No matrices of size 1.
+
+					types[i]	= isFloat	? floatTypes[typeArrayNdx]
+								: isBool	? boolTypes[typeArrayNdx]
+								: isInt		? intTypes[typeArrayNdx]
+								: isMat		? matrixTypes[typeArrayNdx]
+								: TYPE_LAST;
+				}
+
+				DE_ASSERT(types[i] != TYPE_LAST);
+			}
+
+			// Array for just the parameter types.
+			DataType paramTypes[FunctionCase::MAX_PARAMS];
+			for (int i = 0; i < FunctionCase::MAX_PARAMS; i++)
+				paramTypes[i] = types[i+1];
+
+			for (int prec = (int)PRECISION_LOWP; prec < (int)PRECISION_LAST; prec++)
+			{
+				if ((precMask & (1 << prec)) == 0)
+					continue;
+
+				const string		precisionPrefix = booleanCase ? "" : (string(getPrecisionName((Precision)prec)) + "_");
+				std::ostringstream	caseName;
+
+				caseName << precisionPrefix;
+
+				// Write the name of each distinct parameter data type into the test case name.
+				for (int i = 1; i < FunctionCase::MAX_PARAMS + 1 && types[i] != TYPE_INVALID; i++)
+				{
+					if (i == 1 || types[i] != types[i-1])
+					{
+						if (i > 1)
+							caseName << "_";
+
+						caseName << getDataTypeName(types[i]);
+					}
+				}
+
+				for (int fragI = 0; fragI <= 1; fragI++)
+				{
+					const bool					vert	= fragI == 0;
+					tcu::TestCaseGroup* const	group	= vert ? vertexSubGroup : fragmentSubGroup;
+					group->addChild	(new FunctionCase(m_context,
+													  caseName.str().c_str(), "",
+													  groupFunc,
+													  types[0], paramTypes,
+													  groupAttribute, modifyParamNdx, useNearlyConstantInputs,
+													  (Precision)prec, vert,
+													  vert ? vertexSubGroupCalibrationStorage : fragmentSubGroupCalibrationStorage));
+				}
+			}
+		}
+	}
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pShaderOperatorTests.hpp b/modules/gles3/performance/es3pShaderOperatorTests.hpp
new file mode 100644
index 0000000..aaa804e
--- /dev/null
+++ b/modules/gles3/performance/es3pShaderOperatorTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PSHADEROPERATORTESTS_HPP
+#define _ES3PSHADEROPERATORTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader operator performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class ShaderOperatorTests : public TestCaseGroup
+{
+public:
+							ShaderOperatorTests		(Context& context);
+							~ShaderOperatorTests	(void);
+
+	void					init					(void);
+
+private:
+							ShaderOperatorTests		(const ShaderOperatorTests& other);
+	ShaderOperatorTests&	operator=				(const ShaderOperatorTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES3PSHADEROPERATORTESTS_HPP
diff --git a/modules/gles3/performance/es3pShaderOptimizationTests.cpp b/modules/gles3/performance/es3pShaderOptimizationTests.cpp
new file mode 100644
index 0000000..8c63efa
--- /dev/null
+++ b/modules/gles3/performance/es3pShaderOptimizationTests.cpp
@@ -0,0 +1,1023 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Optimized vs unoptimized shader performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pShaderOptimizationTests.hpp"
+#include "glsShaderPerformanceMeasurer.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "tcuStringTemplate.hpp"
+#include "deSharedPtr.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+
+#include "glwFunctions.hpp"
+
+#include <vector>
+#include <string>
+#include <map>
+
+using glu::ShaderProgram;
+using tcu::TestLog;
+using tcu::Vec4;
+using de::SharedPtr;
+using de::toString;
+
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+
+using gls::ShaderPerformanceMeasurer;
+
+namespace gles3
+{
+namespace Performance
+{
+
+static inline std::map<string, string> singleMap (const string& key, const string& value)
+{
+	std::map<string, string> res;
+	res[key] = value;
+	return res;
+}
+
+static inline string repeat (const string& str, int numRepeats, const string& delim = "")
+{
+	string result = str;
+	for (int i = 1; i < numRepeats; i++)
+		result += delim + str;
+	return result;
+}
+
+static inline string repeatIndexedTemplate (const string& strTempl, int numRepeats, const string& delim = "", int ndxStart = 0)
+{
+	const tcu::StringTemplate	templ(strTempl);
+	string						result;
+	std::map<string, string>	params;
+
+	for (int i = 0; i < numRepeats; i++)
+	{
+		params["PREV_NDX"]	= toString(i + ndxStart - 1);
+		params["NDX"]		= toString(i + ndxStart);
+
+		result += (i > 0 ? delim : "") + templ.specialize(params);
+	}
+
+	return result;
+}
+
+namespace
+{
+
+enum CaseShaderType
+{
+	CASESHADERTYPE_VERTEX = 0,
+	CASESHADERTYPE_FRAGMENT,
+
+	CASESHADERTYPE_LAST
+};
+
+static inline string getShaderPrecision (CaseShaderType shaderType)
+{
+	switch (shaderType)
+	{
+		case CASESHADERTYPE_VERTEX:		return "highp";
+		case CASESHADERTYPE_FRAGMENT:	return "highp";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+struct ProgramData
+{
+	glu::ProgramSources			sources;
+	vector<gls::AttribSpec>		attributes; //!< \note Shouldn't contain a_position; that one is set by gls::ShaderPerformanceMeasurer.
+
+	ProgramData (void) {}
+	ProgramData (const glu::ProgramSources& sources_, const vector<gls::AttribSpec>& attributes_ = vector<gls::AttribSpec>())	: sources(sources_), attributes(attributes_)	{}
+	ProgramData (const glu::ProgramSources& sources_, const gls::AttribSpec& attribute)											: sources(sources_), attributes(1, attribute)	{}
+};
+
+//! Shader boilerplate helper; most cases have similar basic shader structure.
+static inline ProgramData defaultProgramData (CaseShaderType shaderType, const string& funcDefs, const string& mainStatements)
+{
+	const bool		isVertexCase	= shaderType == CASESHADERTYPE_VERTEX;
+	const bool		isFragmentCase	= shaderType == CASESHADERTYPE_FRAGMENT;
+	const string	vtxPrec			= getShaderPrecision(CASESHADERTYPE_VERTEX);
+	const string	fragPrec		= getShaderPrecision(CASESHADERTYPE_FRAGMENT);
+
+	return ProgramData(glu::ProgramSources() << glu::VertexSource(		"#version 300 es\n"
+																		"in " + vtxPrec + " vec4 a_position;\n"
+																		"in " + vtxPrec + " vec4 a_value;\n"
+																		"out " + fragPrec + " vec4 v_value;\n"
+																		+ (isVertexCase ? funcDefs : "") +
+																		"void main (void)\n"
+																		"{\n"
+																		"	gl_Position = a_position;\n"
+																		"	" + vtxPrec + " vec4 value = a_value;\n"
+																		+ (isVertexCase ? mainStatements : "") +
+																		"	v_value = value;\n"
+																		"}\n")
+
+											 << glu::FragmentSource(	"#version 300 es\n"
+																		"layout (location = 0) out " + fragPrec + " vec4 o_color;\n"
+																		"in " + fragPrec + " vec4 v_value;\n"
+																		+ (isFragmentCase ? funcDefs : "") +
+																		"void main (void)\n"
+																		"{\n"
+																		"	" + fragPrec + " vec4 value = v_value;\n"
+																		+ (isFragmentCase ? mainStatements : "") +
+																		"	o_color = value;\n"
+																		"}\n"),
+					  gls::AttribSpec("a_value",
+									  Vec4(1.0f, 0.0f, 0.0f, 0.0f),
+									  Vec4(0.0f, 1.0f, 0.0f, 0.0f),
+									  Vec4(0.0f, 0.0f, 1.0f, 0.0f),
+									  Vec4(0.0f, 0.0f, 0.0f, 1.0f)));
+}
+
+static inline ProgramData defaultProgramData (CaseShaderType shaderType, const string& mainStatements)
+{
+	return defaultProgramData(shaderType, "", mainStatements);
+}
+
+class ShaderOptimizationCase : public TestCase
+{
+public:
+	ShaderOptimizationCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType)
+		: TestCase				(context, tcu::NODETYPE_PERFORMANCE, name, description)
+		, m_caseShaderType		(caseShaderType)
+		, m_state				(STATE_LAST)
+		, m_measurer			(context.getRenderContext(), caseShaderType == CASESHADERTYPE_VERTEX	? gls::CASETYPE_VERTEX
+														   : caseShaderType == CASESHADERTYPE_FRAGMENT	? gls::CASETYPE_FRAGMENT
+														   : gls::CASETYPE_LAST)
+		, m_unoptimizedResult	(-1.0f, -1.0f)
+		, m_optimizedResult		(-1.0f, -1.0f)
+	{
+	}
+
+	virtual ~ShaderOptimizationCase (void) {}
+
+	void			init		(void);
+	IterateResult	iterate		(void);
+
+protected:
+	virtual ProgramData		generateProgramData (bool optimized) const = 0;
+
+	const CaseShaderType	m_caseShaderType;
+
+private:
+	enum State
+	{
+		STATE_INIT_UNOPTIMIZED = 0,
+		STATE_MEASURE_UNOPTIMIZED,
+		STATE_INIT_OPTIMIZED,
+		STATE_MEASURE_OPTIMIZED,
+		STATE_FINISHED,
+
+		STATE_LAST
+	};
+
+	ProgramData&						programData		(bool optimized) { return optimized ? m_optimizedData		: m_unoptimizedData;		}
+	SharedPtr<const ShaderProgram>&		program			(bool optimized) { return optimized ? m_optimizedProgram	: m_unoptimizedProgram;		}
+	ShaderPerformanceMeasurer::Result&	result			(bool optimized) { return optimized ? m_optimizedResult		: m_unoptimizedResult;		}
+
+	State								m_state;
+	ShaderPerformanceMeasurer			m_measurer;
+
+	ProgramData							m_unoptimizedData;
+	ProgramData							m_optimizedData;
+	SharedPtr<const ShaderProgram>		m_unoptimizedProgram;
+	SharedPtr<const ShaderProgram>		m_optimizedProgram;
+	ShaderPerformanceMeasurer::Result	m_unoptimizedResult;
+	ShaderPerformanceMeasurer::Result	m_optimizedResult;
+};
+
+void ShaderOptimizationCase::init (void)
+{
+	const glu::RenderContext&	renderCtx	= m_context.getRenderContext();
+	TestLog&					log			= m_testCtx.getLog();
+
+	m_measurer.logParameters(log);
+
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		const bool optimized = ndx == 1;
+
+		programData(optimized) = generateProgramData(optimized);
+
+		for (int i = 0; i < (int)programData(optimized).attributes.size(); i++)
+			DE_ASSERT(programData(optimized).attributes[i].name != "a_position"); // \note Position attribute is set by m_measurer.
+
+		program(optimized) = SharedPtr<const ShaderProgram>(new ShaderProgram(renderCtx, programData(optimized).sources));
+
+		{
+			const tcu::ScopedLogSection section(log, optimized ? "OptimizedProgram"			: "UnoptimizedProgram",
+													 optimized ? "Hand-optimized program"	: "Unoptimized program");
+			log << *program(optimized);
+		}
+
+		if (!program(optimized)->isOk())
+			TCU_FAIL("Shader compilation failed");
+	}
+
+	m_state = STATE_INIT_UNOPTIMIZED;
+}
+
+ShaderOptimizationCase::IterateResult ShaderOptimizationCase::iterate (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (m_state == STATE_INIT_UNOPTIMIZED || m_state == STATE_INIT_OPTIMIZED)
+	{
+		const bool optimized = m_state == STATE_INIT_OPTIMIZED;
+		m_measurer.init(program(optimized)->getProgram(), programData(optimized).attributes, 1);
+		m_state = optimized ? STATE_MEASURE_OPTIMIZED : STATE_MEASURE_UNOPTIMIZED;
+
+		return CONTINUE;
+	}
+	else if (m_state == STATE_MEASURE_UNOPTIMIZED || m_state == STATE_MEASURE_OPTIMIZED)
+	{
+		m_measurer.iterate();
+
+		if (m_measurer.isFinished())
+		{
+			const bool						optimized	= m_state == STATE_MEASURE_OPTIMIZED;
+			const tcu::ScopedLogSection		section		(log, optimized ? "OptimizedResult"									: "UnoptimizedResult",
+															  optimized ? "Measurement results for hand-optimized program"	: "Measurement result for unoptimized program");
+			m_measurer.logMeasurementInfo(log);
+			result(optimized) = m_measurer.getResult();
+			m_measurer.deinit();
+			m_state = optimized ? STATE_FINISHED : STATE_INIT_OPTIMIZED;
+		}
+
+		return CONTINUE;
+	}
+	else
+	{
+		DE_ASSERT(m_state == STATE_FINISHED);
+
+		const float			unoptimizedRelevantResult	= m_caseShaderType == CASESHADERTYPE_VERTEX ? m_unoptimizedResult.megaVertPerSec	: m_unoptimizedResult.megaFragPerSec;
+		const float			optimizedRelevantResult		= m_caseShaderType == CASESHADERTYPE_VERTEX ? m_optimizedResult.megaVertPerSec		: m_optimizedResult.megaFragPerSec;
+		const char* const	relevantResultName			= m_caseShaderType == CASESHADERTYPE_VERTEX ? "vertex"								: "fragment";
+		const float			ratio						= unoptimizedRelevantResult / optimizedRelevantResult;
+		const int			handOptimizationGain		= (int)deFloatRound(100.0f/ratio) - 100;
+
+		log << TestLog::Message << "Unoptimized / optimized " << relevantResultName << " performance ratio: " << ratio << TestLog::EndMessage;
+
+		if (handOptimizationGain >= 0)
+			log << TestLog::Message << "Note: " << handOptimizationGain << "% performance gain was achieved with hand-optimized version" << TestLog::EndMessage;
+		else
+			log << TestLog::Message << "Note: hand-optimization degraded performance by " << -handOptimizationGain << "%" << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(ratio, 2).c_str());
+
+		return STOP;
+	}
+}
+
+class LoopUnrollCase : public ShaderOptimizationCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_INDEPENDENT = 0,
+		CASETYPE_DEPENDENT,
+
+		CASETYPE_LAST
+	};
+
+	LoopUnrollCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, CaseType caseType, int numRepetitions)
+		: ShaderOptimizationCase	(context, name, description, caseShaderType)
+		, m_numRepetitions			(numRepetitions)
+		, m_caseType				(caseType)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		const string repetition = optimized ? repeatIndexedTemplate("\t" + expressionTemplate(m_caseType) + ";\n", m_numRepetitions)
+											: loop(m_numRepetitions, expressionTemplate(m_caseType));
+
+		return defaultProgramData(m_caseShaderType, "\t" + getShaderPrecision(m_caseShaderType) + " vec4 valueOrig = value;\n" + repetition);
+	}
+
+private:
+	const int		m_numRepetitions;
+	const CaseType	m_caseType;
+
+	static inline string expressionTemplate (CaseType caseType)
+	{
+		switch (caseType)
+		{
+			case CASETYPE_INDEPENDENT:	return "value += sin(float(${NDX}+1)*valueOrig)";
+			case CASETYPE_DEPENDENT:	return "value = sin(value)";
+			default:
+				DE_ASSERT(false);
+				return DE_NULL;
+		}
+	}
+
+	static inline string loop (int iterations, const string& innerExpr)
+	{
+		return "\tfor (int i = 0; i < " + toString(iterations) + "; i++)\n\t\t" + tcu::StringTemplate(innerExpr).specialize(singleMap("NDX", "i")) + ";\n";
+	}
+};
+
+class LoopInvariantCodeMotionCase : public ShaderOptimizationCase
+{
+public:
+	LoopInvariantCodeMotionCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, int numLoopIterations)
+		: ShaderOptimizationCase	(context, name, description, caseShaderType)
+		, m_numLoopIterations		(numLoopIterations)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		float scale = 0.0f;
+		for (int i = 0; i < m_numLoopIterations; i++)
+			scale += 3.2f*(float)i + 4.6f;
+		scale = 1.0f / scale;
+
+		const string precision		= getShaderPrecision(m_caseShaderType);
+		const string statements		= optimized ?	"	" + precision + " vec4 valueOrig = value;\n"
+													"	" + precision + " vec4 y = sin(cos(sin(valueOrig)));\n"
+													"	for (int i = 0; i < " + toString(m_numLoopIterations) + "; i++)\n"
+													"	{\n"
+													"		" + precision + " float x = 3.2*float(i) + 4.6;\n"
+													"		value += x*y;\n"
+													"	}\n"
+													"	value *= " + toString(scale) + ";\n"
+
+												:	"	" + precision + " vec4 valueOrig = value;\n"
+													"	for (int i = 0; i < " + toString(m_numLoopIterations) + "; i++)\n"
+													"	{\n"
+													"		" + precision + " float x = 3.2*float(i) + 4.6;\n"
+													"		" + precision + " vec4 y = sin(cos(sin(valueOrig)));\n"
+													"		value += x*y;\n"
+													"	}\n"
+													"	value *= " + toString(scale) + ";\n";
+
+		return defaultProgramData(m_caseShaderType, statements);
+	}
+
+private:
+	const int m_numLoopIterations;
+};
+
+class FunctionInliningCase : public ShaderOptimizationCase
+{
+public:
+	FunctionInliningCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, int callNestingDepth)
+		: ShaderOptimizationCase	(context, name, description, caseShaderType)
+		, m_callNestingDepth		(callNestingDepth)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		const string precision				= getShaderPrecision(m_caseShaderType);
+		const string expression				= "value*vec4(0.8, 0.7, 0.6, 0.9)";
+		const string maybeFuncDefs			= optimized ? "" : funcDefinitions(m_callNestingDepth, precision, expression);
+		const string mainValueStatement		= (optimized ? "\tvalue = " + expression : "\tvalue = func" + toString(m_callNestingDepth-1) + "(value)") + ";\n";
+
+		return defaultProgramData(m_caseShaderType, maybeFuncDefs, mainValueStatement);
+	}
+
+private:
+	const int m_callNestingDepth;
+
+	static inline string funcDefinitions (int callNestingDepth, const string& precision, const string& expression)
+	{
+		string result = precision + " vec4 func0 (" + precision + " vec4 value) { return " + expression + "; }\n";
+
+		for (int i = 1; i < callNestingDepth; i++)
+			result += precision + " vec4 func" + toString(i) + " (" + precision + " vec4 v) { return func" + toString(i-1) + "(v); }\n";
+
+		return result;
+	}
+};
+
+class ConstantPropagationCase : public ShaderOptimizationCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_BUILT_IN_FUNCTIONS = 0,
+		CASETYPE_ARRAY,
+		CASETYPE_STRUCT,
+
+		CASETYPE_LAST
+	};
+
+	ConstantPropagationCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, CaseType caseType, bool useConstantExpressionsOnly)
+		: ShaderOptimizationCase			(context, name, description, caseShaderType)
+		, m_caseType						(caseType)
+		, m_useConstantExpressionsOnly		(useConstantExpressionsOnly)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		const bool		isVertexCase	= m_caseShaderType == CASESHADERTYPE_VERTEX;
+		const string	precision		= getShaderPrecision(m_caseShaderType);
+		const string	statements		= m_caseType == CASETYPE_BUILT_IN_FUNCTIONS		? builtinFunctionsCaseStatements	(optimized, m_useConstantExpressionsOnly, precision, isVertexCase)
+										: m_caseType == CASETYPE_ARRAY					? arrayCaseStatements				(optimized, m_useConstantExpressionsOnly, precision, isVertexCase)
+										: m_caseType == CASETYPE_STRUCT					? structCaseStatements				(optimized, m_useConstantExpressionsOnly, precision, isVertexCase)
+										: DE_NULL;
+
+		return defaultProgramData(m_caseShaderType, statements);
+	}
+
+private:
+	const CaseType	m_caseType;
+	const bool		m_useConstantExpressionsOnly;
+
+	static inline string builtinFunctionsCaseStatements (bool optimized, bool constantExpressionsOnly, const string& precision, bool useHeavierWorkload)
+	{
+		const string	constMaybe = constantExpressionsOnly ? "const " : "";
+		const int		numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	value = vec4(0.4, 0.5, 0.6, 0.7) * value; // NOTE: factor doesn't necessarily match the one in unoptimized shader, but shouldn't make a difference performance-wise\n"
+
+						 :	"	" + constMaybe + precision + " vec4 a = vec4(sin(0.7), cos(0.2), sin(0.9), abs(-0.5));\n"
+							"	" + constMaybe + precision + " vec4 b = cos(a) + fract(3.0*a.xzzw);\n"
+							"	" + constMaybe + "bvec4 c = bvec4(true, false, true, true);\n"
+							"	" + constMaybe + precision + " vec4 d = exp(b + vec4(c));\n"
+							"	" + constMaybe + precision + " vec4 e0 = inversesqrt(mix(d+a, d+b, a));\n"
+							+ repeatIndexedTemplate("	" + constMaybe + precision + " vec4 e${NDX} = sin(sin(sin(sin(e${PREV_NDX}))));\n", numSinRows, "", 1) +
+							"	" + constMaybe + precision + " vec4 f = abs(e" + toString(numSinRows) + ");\n" +
+							"	value = f*value;\n";
+	}
+
+	static inline string arrayCaseStatements (bool optimized, bool constantExpressionsOnly, const string& precision, bool useHeavierWorkload)
+	{
+		const string	constMaybe = constantExpressionsOnly ? "const " : "";
+		const int		numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	value = vec4(0.4, 0.5, 0.6, 0.7) * value; // NOTE: factor doesn't necessarily match the one in unoptimized shader, but shouldn't make a difference performance-wise\n"
+
+						 :	"	const int arrLen = 4;\n"
+							+ (constantExpressionsOnly ?
+								"	const " + precision + " vec4 arr[arrLen] =\n"
+								"		vec4[](vec4(0.1, 0.5, 0.9, 1.3),\n"
+								"		       vec4(0.2, 0.6, 1.0, 1.4),\n"
+								"		       vec4(0.3, 0.7, 1.1, 1.5),\n"
+								"		       vec4(0.4, 0.8, 1.2, 1.6));\n"
+
+							 :	"	" + precision + " vec4 arr[arrLen];\n"
+								"	arr[0] = vec4(0.1, 0.5, 0.9, 1.3);\n"
+								"	arr[1] = vec4(0.2, 0.6, 1.0, 1.4);\n"
+								"	arr[2] = vec4(0.3, 0.7, 1.1, 1.5);\n"
+								"	arr[3] = vec4(0.4, 0.8, 1.2, 1.6);\n"
+							) +
+							"	" + constMaybe + precision + " vec4 a = (arr[0] + arr[1] + arr[2] + arr[3]) * (1.0 / float(arr.length()));\n"
+							"	" + constMaybe + precision + " vec4 b0 = cos(sin(a));\n"
+							+ repeatIndexedTemplate("	" + constMaybe + precision + " vec4 b${NDX} = sin(sin(sin(sin(b${PREV_NDX}))));\n", numSinRows, "", 1) +
+							"	" + constMaybe + precision + " vec4 c = abs(b" + toString(numSinRows) + ");\n" +
+							"	value = c*value;\n";
+	}
+
+	static inline string structCaseStatements (bool optimized, bool constantExpressionsOnly, const string& precision, bool useHeavierWorkload)
+	{
+		const string	constMaybe = constantExpressionsOnly ? "const " : "";
+		const int		numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	value = vec4(0.4, 0.5, 0.6, 0.7) * value; // NOTE: factor doesn't necessarily match the one in unoptimized shader, but shouldn't make a difference performance-wise\n"
+
+						 :	"	struct S\n"
+							"	{\n"
+							"		" + precision + " vec4 a;\n"
+							"		" + precision + " vec4 b;\n"
+							"		" + precision + " vec4 c;\n"
+							"		" + precision + " vec4 d;\n"
+							"	};\n"
+							"\n"
+							"	" + constMaybe + "S s =\n"
+							"		S(vec4(0.1, 0.5, 0.9, 1.3),\n"
+							"		  vec4(0.2, 0.6, 1.0, 1.4),\n"
+							"		  vec4(0.3, 0.7, 1.1, 1.5),\n"
+							"		  vec4(0.4, 0.8, 1.2, 1.6));\n"
+							"	" + constMaybe + precision + " vec4 a = (s.a + s.b + s.c + s.d) * 0.25;\n"
+							"	" + constMaybe + precision + " vec4 b0 = cos(sin(a));\n"
+							+ repeatIndexedTemplate("	" + constMaybe + precision + " vec4 b${NDX} = sin(sin(sin(sin(b${PREV_NDX}))));\n", numSinRows, "", 1) +
+							"	" + constMaybe + precision + " vec4 c = abs(b" + toString(numSinRows) + ");\n" +
+							"	value = c*value;\n";
+	}
+};
+
+class CommonSubexpressionCase : public ShaderOptimizationCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_SINGLE_STATEMENT = 0,
+		CASETYPE_MULTIPLE_STATEMENTS,
+		CASETYPE_STATIC_BRANCH,
+		CASETYPE_LOOP,
+
+		CASETYPE_LAST
+	};
+
+	CommonSubexpressionCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, CaseType caseType)
+		: ShaderOptimizationCase	(context, name, description, caseShaderType)
+		, m_caseType				(caseType)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		const bool		isVertexCase	= m_caseShaderType == CASESHADERTYPE_VERTEX;
+		const string	precision		= getShaderPrecision(m_caseShaderType);
+		const string	statements		= m_caseType == CASETYPE_SINGLE_STATEMENT		? singleStatementCaseStatements		(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_MULTIPLE_STATEMENTS	? multipleStatementsCaseStatements	(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_STATIC_BRANCH			? staticBranchCaseStatements		(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_LOOP					? loopCaseStatements				(optimized, precision, isVertexCase)
+										: DE_NULL;
+
+		return defaultProgramData(m_caseShaderType, statements);
+	}
+
+private:
+	const CaseType m_caseType;
+
+	static inline string singleStatementCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numTopLevelRepeats = useHeavierWorkload ? 4 : 1;
+
+		return optimized ?	"	" + precision + " vec4 s = sin(value);\n"
+							"	" + precision + " vec4 cs = cos(s);\n"
+							"	" + precision + " vec4 d = fract(s + cs) + sqrt(s + exp(cs));\n"
+							"	value = " + repeat("d", numTopLevelRepeats, "+") + ";\n"
+
+						 :	"	value = " + repeat("fract(sin(value) + cos(sin(value))) + sqrt(sin(value) + exp(cos(sin(value))))", numTopLevelRepeats, "\n\t      + ") + ";\n";
+	}
+
+	static inline string multipleStatementsCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numTopLevelRepeats = useHeavierWorkload ? 4 : 2;
+		DE_ASSERT(numTopLevelRepeats >= 2);
+
+		return optimized ?	"	" + precision + " vec4 a = sin(value) + cos(exp(value));\n"
+							"	" + precision + " vec4 b = cos(cos(a));\n"
+							"	a = fract(exp(sqrt(b)));\n"
+							"\n"
+							+ repeat("\tvalue += a*b;\n", numTopLevelRepeats)
+
+						 :	repeatIndexedTemplate(	"	" + precision + " vec4 a${NDX} = sin(value) + cos(exp(value));\n"
+													"	" + precision + " vec4 b${NDX} = cos(cos(a${NDX}));\n"
+													"	a${NDX} = fract(exp(sqrt(b${NDX})));\n"
+													"\n",
+													numTopLevelRepeats) +
+
+							repeatIndexedTemplate(	"	value += a${NDX}*b${NDX};\n", numTopLevelRepeats);
+	}
+
+	static inline string staticBranchCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numTopLevelRepeats = useHeavierWorkload ? 4 : 2;
+		DE_ASSERT(numTopLevelRepeats >= 2);
+
+		if (optimized)
+		{
+			return "	" + precision + " vec4 a = sin(value) + cos(exp(value));\n"
+				   "	" + precision + " vec4 b = cos(a);\n"
+				   "	b = cos(b);\n"
+				   "	a = fract(exp(sqrt(b)));\n"
+				   "\n"
+				   + repeat("	value += a*b;\n", numTopLevelRepeats);
+		}
+		else
+		{
+			string result;
+
+			for (int i = 0; i < numTopLevelRepeats; i++)
+			{
+				result +=	"	" + precision + " vec4 a" + toString(i) + " = sin(value) + cos(exp(value));\n"
+							"	" + precision + " vec4 b" + toString(i) + " = cos(a" + toString(i) + ");\n";
+
+				if (i % 3 == 0)
+					result +=	"	if (1 < 2)\n"
+								"		b" + toString(i) + " = cos(b" + toString(i) + ");\n";
+				else if (i % 3 == 1)
+					result +=	"	b" + toString(i) + " = cos(b" + toString(i) + ");\n";
+				else if (i % 3 == 2)
+					result +=	"	if (2 < 1);\n"
+								"	else\n"
+								"		b" + toString(i) + " = cos(b" + toString(i) + ");\n";
+				else
+					DE_ASSERT(false);
+
+				result +=	"	a" + toString(i) + " = fract(exp(sqrt(b" + toString(i) + ")));\n\n";
+			}
+
+			result += repeatIndexedTemplate("	value += a${NDX}*b${NDX};\n", numTopLevelRepeats);
+
+			return result;
+		}
+	}
+
+	static inline string loopCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numLoopIterations = useHeavierWorkload ? 32 : 4;
+
+		return optimized ?	"	" + precision + " vec4 acc = value;\n"
+							"	for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"		acc = sin(acc);\n"
+							"\n"
+							"	value += acc;\n"
+							"	value += acc;\n"
+
+						 :	"	" + precision + " vec4 acc0 = value;\n"
+							"	for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"		acc0 = sin(acc0);\n"
+							"\n"
+							"	" + precision + " vec4 acc1 = value;\n"
+							"	for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"		acc1 = sin(acc1);\n"
+							"\n"
+							"	value += acc0;\n"
+							"	value += acc1;\n";
+	}
+};
+
+class DeadCodeEliminationCase : public ShaderOptimizationCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_DEAD_BRANCH_SIMPLE = 0,
+		CASETYPE_DEAD_BRANCH_COMPLEX,
+		CASETYPE_DEAD_BRANCH_COMPLEX_NO_CONST,
+		CASETYPE_DEAD_BRANCH_FUNC_CALL,
+		CASETYPE_UNUSED_VALUE_BASIC,
+		CASETYPE_UNUSED_VALUE_LOOP,
+		CASETYPE_UNUSED_VALUE_DEAD_BRANCH,
+		CASETYPE_UNUSED_VALUE_AFTER_RETURN,
+		CASETYPE_UNUSED_VALUE_MUL_ZERO,
+
+		CASETYPE_LAST
+	};
+
+	DeadCodeEliminationCase (Context& context, const char* name, const char* description, CaseShaderType caseShaderType, CaseType caseType)
+		: ShaderOptimizationCase	(context, name, description, caseShaderType)
+		, m_caseType				(caseType)
+	{
+	}
+
+protected:
+	ProgramData generateProgramData (bool optimized) const
+	{
+		const bool		isVertexCase	= m_caseShaderType == CASESHADERTYPE_VERTEX;
+		const string	precision		= getShaderPrecision(m_caseShaderType);
+		const string	funcDefs		= m_caseType == CASETYPE_DEAD_BRANCH_FUNC_CALL		? deadBranchFuncCallCaseFuncDefs		(optimized, precision)
+										: m_caseType == CASETYPE_UNUSED_VALUE_AFTER_RETURN	? unusedValueAfterReturnCaseFuncDefs	(optimized, precision, isVertexCase)
+										: "";
+
+		const string	statements		= m_caseType == CASETYPE_DEAD_BRANCH_SIMPLE				? deadBranchSimpleCaseStatements			(optimized, isVertexCase)
+										: m_caseType == CASETYPE_DEAD_BRANCH_COMPLEX			? deadBranchComplexCaseStatements			(optimized, precision, true,	isVertexCase)
+										: m_caseType == CASETYPE_DEAD_BRANCH_COMPLEX_NO_CONST	? deadBranchComplexCaseStatements			(optimized, precision, false,	isVertexCase)
+										: m_caseType == CASETYPE_DEAD_BRANCH_FUNC_CALL			? deadBranchFuncCallCaseStatements			(optimized, isVertexCase)
+										: m_caseType == CASETYPE_UNUSED_VALUE_BASIC				? unusedValueBasicCaseStatements			(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_UNUSED_VALUE_LOOP				? unusedValueLoopCaseStatements				(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_UNUSED_VALUE_DEAD_BRANCH		? unusedValueDeadBranchCaseStatements		(optimized, precision, isVertexCase)
+										: m_caseType == CASETYPE_UNUSED_VALUE_AFTER_RETURN		? unusedValueAfterReturnCaseStatements		()
+										: m_caseType == CASETYPE_UNUSED_VALUE_MUL_ZERO			? unusedValueMulZeroCaseStatements			(optimized, precision, isVertexCase)
+										: DE_NULL;
+
+		return defaultProgramData(m_caseShaderType, funcDefs, statements);
+	}
+
+private:
+	const CaseType m_caseType;
+
+	static inline string deadBranchSimpleCaseStatements (bool optimized, bool useHeavierWorkload)
+	{
+		const int numLoopIterations = useHeavierWorkload ? 16 : 4;
+
+		return optimized ?	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+
+						 :	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	if (2 < 1)\n"
+							"	{\n"
+							"		value = cos(exp(sin(value))*log(sqrt(value)));\n"
+							"		for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"			value = sin(value);\n"
+							"	}\n";
+	}
+
+	static inline string deadBranchComplexCaseStatements (bool optimized, const string& precision, bool useConst, bool useHeavierWorkload)
+	{
+		const string	constMaybe			= useConst ? "const " : "";
+		const int		numLoopIterations	= useHeavierWorkload ? 16 : 4;
+
+		return optimized ?	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+
+						 :	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	" + constMaybe + precision + " vec4 a = vec4(sin(0.7), cos(0.2), sin(0.9), abs(-0.5));\n"
+							"	" + constMaybe + precision + " vec4 b = cos(a) + fract(3.0*a.xzzw);\n"
+							"	" + constMaybe + "bvec4 c = bvec4(true, false, true, true);\n"
+							"	" + constMaybe + precision + " vec4 d = exp(b + vec4(c));\n"
+							"	" + constMaybe + precision + " vec4 e = 1.8*abs(sin(sin(inversesqrt(mix(d+a, d+b, a)))));\n"
+							"	if (e.x > 1.0)\n"
+							"	{\n"
+							"		value = cos(exp(sin(value))*log(sqrt(value)));\n"
+							"		for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"			value = sin(value);\n"
+							"	}\n";
+	}
+
+	static inline string deadBranchFuncCallCaseFuncDefs (bool optimized, const string& precision)
+	{
+		return optimized ? "" : precision + " float func (" + precision + " float x) { return 2.0*x; }\n";
+	}
+
+	static inline string deadBranchFuncCallCaseStatements (bool optimized, bool useHeavierWorkload)
+	{
+		const int numLoopIterations = useHeavierWorkload ? 16 : 4;
+
+		return optimized ?	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+
+						 :	"	value = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	if (func(0.3) > 1.0)\n"
+							"	{\n"
+							"		value = cos(exp(sin(value))*log(sqrt(value)));\n"
+							"		for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"			value = sin(value);\n"
+							"	}\n";
+	}
+
+	static inline string unusedValueBasicCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	value = used;\n"
+
+						 :	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	" + precision + " vec4 unused = cos(exp(sin(value))*log(sqrt(value))) + used;\n"
+							+ repeat("	unused = sin(sin(sin(sin(unused))));\n", numSinRows) +
+							"	value = used;\n";
+	}
+
+	static inline string unusedValueLoopCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numLoopIterations = useHeavierWorkload ? 16 : 4;
+
+		return optimized ?	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	value = used;\n"
+
+						 :	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	" + precision + " vec4 unused = cos(exp(sin(value))*log(sqrt(value)));\n"
+							"	for (int i = 0; i < " + toString(numLoopIterations) + "; i++)\n"
+							"		unused = sin(unused + used);\n"
+							"	value = used;\n";
+	}
+
+	static inline string unusedValueAfterReturnCaseFuncDefs (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	precision + " vec4 func (" + precision + " vec4 v)\n"
+							"{\n"
+							"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * v;\n"
+							"	return used;\n"
+							"}\n"
+
+						 :	precision + " vec4 func (" + precision + " vec4 v)\n"
+							"{\n"
+							"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * v;\n"
+							"	" + precision + " vec4 unused = cos(exp(sin(v))*log(sqrt(v)));\n"
+							+ repeat("	unused = sin(sin(sin(sin(unused))));\n", numSinRows) +
+							"	return used;\n"
+							"	used = used*unused;"
+							"	return used;\n"
+							"}\n";
+	}
+
+	static inline string unusedValueAfterReturnCaseStatements (void)
+	{
+		return "	value = func(value);\n";
+	}
+
+	static inline string unusedValueDeadBranchCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	value = used;\n"
+
+						 :	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	" + precision + " vec4 unused = cos(exp(sin(value))*log(sqrt(value)));\n"
+							+ repeat("	unused = sin(sin(sin(sin(unused))));\n", numSinRows) +
+							"	if (2 < 1)\n"
+							"		used = used*unused;\n"
+							"	value = used;\n";
+	}
+
+	static inline string unusedValueMulZeroCaseStatements (bool optimized, const string& precision, bool useHeavierWorkload)
+	{
+		const int numSinRows = useHeavierWorkload ? 12 : 1;
+
+		return optimized ?	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	value = used;\n"
+
+						 :	"	" + precision + " vec4 used = vec4(0.6, 0.7, 0.8, 0.9) * value;\n"
+							"	" + precision + " vec4 unused = cos(exp(sin(value))*log(sqrt(value)));\n"
+							+ repeat("	unused = sin(sin(sin(sin(unused))));\n", numSinRows) +
+							"	value = used + unused*float(1-1);\n";
+	}
+};
+
+} // anonymous
+
+ShaderOptimizationTests::ShaderOptimizationTests (Context& context)
+	: TestCaseGroup(context, "optimization", "Shader Optimization Performance Tests")
+{
+}
+
+ShaderOptimizationTests::~ShaderOptimizationTests (void)
+{
+}
+
+void ShaderOptimizationTests::init (void)
+{
+	TestCaseGroup* const unrollGroup					= new TestCaseGroup(m_context, "loop_unrolling",					"Loop Unrolling Cases");
+	TestCaseGroup* const loopInvariantCodeMotionGroup	= new TestCaseGroup(m_context, "loop_invariant_code_motion",		"Loop-Invariant Code Motion Cases");
+	TestCaseGroup* const inlineGroup					= new TestCaseGroup(m_context, "function_inlining",					"Function Inlining Cases");
+	TestCaseGroup* const constantPropagationGroup		= new TestCaseGroup(m_context, "constant_propagation",				"Constant Propagation Cases");
+	TestCaseGroup* const commonSubexpressionGroup		= new TestCaseGroup(m_context, "common_subexpression_elimination",	"Common Subexpression Elimination Cases");
+	TestCaseGroup* const deadCodeEliminationGroup		= new TestCaseGroup(m_context, "dead_code_elimination",				"Dead Code Elimination Cases");
+	addChild(unrollGroup);
+	addChild(loopInvariantCodeMotionGroup);
+	addChild(inlineGroup);
+	addChild(constantPropagationGroup);
+	addChild(commonSubexpressionGroup);
+	addChild(deadCodeEliminationGroup);
+
+	for (int caseShaderTypeI = 0; caseShaderTypeI < CASESHADERTYPE_LAST; caseShaderTypeI++)
+	{
+		const CaseShaderType	caseShaderType			= (CaseShaderType)caseShaderTypeI;
+		const char* const		caseShaderTypeSuffix	= caseShaderType == CASESHADERTYPE_VERTEX		? "_vertex"
+														: caseShaderType == CASESHADERTYPE_FRAGMENT		? "_fragment"
+														: DE_NULL;
+
+		// Loop unrolling cases.
+
+		{
+			static const int loopIterationCounts[] = { 4, 8, 32 };
+
+			for (int caseTypeI = 0; caseTypeI < LoopUnrollCase::CASETYPE_LAST; caseTypeI++)
+			{
+				const LoopUnrollCase::CaseType	caseType		= (LoopUnrollCase::CaseType)caseTypeI;
+				const string					caseTypeName	= caseType == LoopUnrollCase::CASETYPE_INDEPENDENT	? "independent_iterations"
+																: caseType == LoopUnrollCase::CASETYPE_DEPENDENT	? "dependent_iterations"
+																: DE_NULL;
+				const string					caseTypeDesc	= caseType == LoopUnrollCase::CASETYPE_INDEPENDENT	? "loop iterations don't depend on each other"
+																: caseType == LoopUnrollCase::CASETYPE_DEPENDENT	? "loop iterations depend on each other"
+																: DE_NULL;
+
+				for (int loopIterNdx = 0; loopIterNdx < DE_LENGTH_OF_ARRAY(loopIterationCounts); loopIterNdx++)
+				{
+					const int			loopIterations	= loopIterationCounts[loopIterNdx];
+					const string		name			= caseTypeName + "_" + toString(loopIterations) + caseShaderTypeSuffix;
+					const string		description		= toString(loopIterations) + " iterations; " + caseTypeDesc;
+
+					unrollGroup->addChild(new LoopUnrollCase(m_context, name.c_str(), description.c_str(), caseShaderType, caseType, loopIterations));
+				}
+			}
+		}
+
+		// Loop-invariant code motion cases.
+
+		{
+			static const int loopIterationCounts[] = { 4, 8, 32 };
+
+			for (int loopIterNdx = 0; loopIterNdx < DE_LENGTH_OF_ARRAY(loopIterationCounts); loopIterNdx++)
+			{
+				const int		loopIterations	= loopIterationCounts[loopIterNdx];
+				const string	name			= toString(loopIterations) + "_iterations" + caseShaderTypeSuffix;
+
+				loopInvariantCodeMotionGroup->addChild(new LoopInvariantCodeMotionCase(m_context, name.c_str(), "", caseShaderType, loopIterations));
+			}
+		}
+
+		// Function inlining cases.
+
+		{
+			static const int callNestingDepths[] = { 4, 8, 32 };
+
+			for (int nestDepthNdx = 0; nestDepthNdx < DE_LENGTH_OF_ARRAY(callNestingDepths); nestDepthNdx++)
+			{
+				const int		nestingDepth	= callNestingDepths[nestDepthNdx];
+				const string	name			= toString(nestingDepth) + "_nested" + caseShaderTypeSuffix;
+
+				inlineGroup->addChild(new FunctionInliningCase(m_context, name.c_str(), "", caseShaderType, nestingDepth));
+			}
+		}
+
+		// Constant propagation cases.
+
+		for (int caseTypeI = 0; caseTypeI < ConstantPropagationCase::CASETYPE_LAST; caseTypeI++)
+		{
+			const ConstantPropagationCase::CaseType		caseType		= (ConstantPropagationCase::CaseType)caseTypeI;
+			const string								caseTypeName	= caseType == ConstantPropagationCase::CASETYPE_BUILT_IN_FUNCTIONS		? "built_in_functions"
+																		: caseType == ConstantPropagationCase::CASETYPE_ARRAY					? "array"
+																		: caseType == ConstantPropagationCase::CASETYPE_STRUCT					? "struct"
+																		: DE_NULL;
+
+			for (int constantExpressionsOnlyI = 0; constantExpressionsOnlyI <= 1; constantExpressionsOnlyI++)
+			{
+				const bool		constantExpressionsOnly		= constantExpressionsOnlyI != 0;
+				const string	name						= caseTypeName + (constantExpressionsOnly ? "" : "_no_const") + caseShaderTypeSuffix;
+
+				constantPropagationGroup->addChild(new ConstantPropagationCase(m_context, name.c_str(), "", caseShaderType, caseType, constantExpressionsOnly));
+			}
+		}
+
+		// Common subexpression cases.
+
+		for (int caseTypeI = 0; caseTypeI < CommonSubexpressionCase::CASETYPE_LAST; caseTypeI++)
+		{
+			const CommonSubexpressionCase::CaseType		caseType		= (CommonSubexpressionCase::CaseType)caseTypeI;
+
+			const string								caseTypeName	= caseType == CommonSubexpressionCase::CASETYPE_SINGLE_STATEMENT		? "single_statement"
+																		: caseType == CommonSubexpressionCase::CASETYPE_MULTIPLE_STATEMENTS		? "multiple_statements"
+																		: caseType == CommonSubexpressionCase::CASETYPE_STATIC_BRANCH			? "static_branch"
+																		: caseType == CommonSubexpressionCase::CASETYPE_LOOP					? "loop"
+																		: DE_NULL;
+
+			const string								description		= caseType == CommonSubexpressionCase::CASETYPE_SINGLE_STATEMENT		? "A single statement containing multiple uses of same subexpression"
+																		: caseType == CommonSubexpressionCase::CASETYPE_MULTIPLE_STATEMENTS		? "Multiple statements performing same computations"
+																		: caseType == CommonSubexpressionCase::CASETYPE_STATIC_BRANCH			? "Multiple statements including a static conditional"
+																		: caseType == CommonSubexpressionCase::CASETYPE_LOOP					? "Multiple loops performing the same computations"
+																		: DE_NULL;
+
+			commonSubexpressionGroup->addChild(new CommonSubexpressionCase(m_context, (caseTypeName + caseShaderTypeSuffix).c_str(), description.c_str(), caseShaderType, caseType));
+		}
+
+		// Dead code elimination cases.
+
+		for (int caseTypeI = 0; caseTypeI < DeadCodeEliminationCase::CASETYPE_LAST; caseTypeI++)
+		{
+			const DeadCodeEliminationCase::CaseType		caseType				= (DeadCodeEliminationCase::CaseType)caseTypeI;
+			const char* const							caseTypeName			= caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_SIMPLE				? "dead_branch_simple"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_COMPLEX				? "dead_branch_complex"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_COMPLEX_NO_CONST	? "dead_branch_complex_no_const"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_FUNC_CALL			? "dead_branch_func_call"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_BASIC				? "unused_value_basic"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_LOOP				? "unused_value_loop"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_DEAD_BRANCH		? "unused_value_dead_branch"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_AFTER_RETURN		? "unused_value_after_return"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_MUL_ZERO			? "unused_value_mul_zero"
+																				: DE_NULL;
+
+			const char* const							caseTypeDescription		= caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_SIMPLE				? "Do computation inside a branch that is never taken (condition is simple false constant expression)"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_COMPLEX				? "Do computation inside a branch that is never taken (condition is complex false constant expression)"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_COMPLEX_NO_CONST	? "Do computation inside a branch that is never taken (condition is complex false expression, not constant expression but still compile-time computable)"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_DEAD_BRANCH_FUNC_CALL			? "Do computation inside a branch that is never taken (condition is compile-time computable false expression containing function call to a simple inlineable function)"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_BASIC				? "Compute a value that is never used even statically"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_LOOP				? "Compute a value, using a loop, that is never used even statically"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_DEAD_BRANCH		? "Compute a value that is used only inside a statically dead branch"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_AFTER_RETURN		? "Compute a value that is used only after a return statement"
+																				: caseType == DeadCodeEliminationCase::CASETYPE_UNUSED_VALUE_MUL_ZERO			? "Compute a value that is used but multiplied by a zero constant expression"
+																				: DE_NULL;
+
+			deadCodeEliminationGroup->addChild(new DeadCodeEliminationCase(m_context, (string() + caseTypeName + caseShaderTypeSuffix).c_str(), caseTypeDescription, caseShaderType, caseType));
+		}
+	}
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pShaderOptimizationTests.hpp b/modules/gles3/performance/es3pShaderOptimizationTests.hpp
new file mode 100644
index 0000000..104e68e
--- /dev/null
+++ b/modules/gles3/performance/es3pShaderOptimizationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PSHADEROPTIMIZATIONTESTS_HPP
+#define _ES3PSHADEROPTIMIZATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Optimized vs unoptimized shader performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class ShaderOptimizationTests : public TestCaseGroup
+{
+public:
+								ShaderOptimizationTests		(Context& context);
+								~ShaderOptimizationTests	(void);
+
+	void						init						(void);
+
+private:
+								ShaderOptimizationTests		(const ShaderOptimizationTests& other);
+	ShaderOptimizationTests&	operator=					(const ShaderOptimizationTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PSHADEROPTIMIZATIONTESTS_HPP
diff --git a/modules/gles3/performance/es3pStateChangeCallTests.cpp b/modules/gles3/performance/es3pStateChangeCallTests.cpp
new file mode 100644
index 0000000..616f0dd
--- /dev/null
+++ b/modules/gles3/performance/es3pStateChangeCallTests.cpp
@@ -0,0 +1,1048 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State change call performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pStateChangeCallTests.hpp"
+#include "glsStateChangePerfTestCases.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+using namespace glw;
+
+StateChangeCallTests::StateChangeCallTests (Context& context)
+	: TestCaseGroup(context, "state_change_only", "Test cost of state change calls without rendering anything")
+{
+}
+
+StateChangeCallTests::~StateChangeCallTests (void)
+{
+}
+
+#define ARG_LIST(...) __VA_ARGS__
+
+#define ADD_ARG_CASE1(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				gl.FUNCNAME(arg0);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+#define ADD_ARG_CASE2(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0, TYPE1, ARGS1)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			const TYPE1 args1[] = ARGS1;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				const TYPE1		arg1		= args1[baseNdx%DE_LENGTH_OF_ARRAY(args1)];\
+				gl.FUNCNAME(arg0, arg1);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+#define ADD_ARG_CASE3(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0, TYPE1, ARGS1, TYPE2, ARGS2)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			const TYPE1 args1[] = ARGS1;\
+			const TYPE2 args2[] = ARGS2;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				const TYPE1		arg1		= args1[baseNdx%DE_LENGTH_OF_ARRAY(args1)];\
+				const TYPE2		arg2		= args2[baseNdx%DE_LENGTH_OF_ARRAY(args2)];\
+				gl.FUNCNAME(arg0, arg1, arg2);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+#define ADD_ARG_CASE4(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0, TYPE1, ARGS1, TYPE2, ARGS2, TYPE3, ARGS3)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			const TYPE1 args1[] = ARGS1;\
+			const TYPE2 args2[] = ARGS2;\
+			const TYPE3 args3[] = ARGS3;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				const TYPE1		arg1		= args1[baseNdx%DE_LENGTH_OF_ARRAY(args1)];\
+				const TYPE2		arg2		= args2[baseNdx%DE_LENGTH_OF_ARRAY(args2)];\
+				const TYPE3		arg3		= args3[baseNdx%DE_LENGTH_OF_ARRAY(args3)];\
+				gl.FUNCNAME(arg0, arg1, arg2, arg3);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+#define ADD_ARG_CASE5(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0, TYPE1, ARGS1, TYPE2, ARGS2, TYPE3, ARGS3, TYPE4, ARGS4)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			const TYPE1 args1[] = ARGS1;\
+			const TYPE2 args2[] = ARGS2;\
+			const TYPE3 args3[] = ARGS3;\
+			const TYPE4 args4[] = ARGS4;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				const TYPE1		arg1		= args1[baseNdx%DE_LENGTH_OF_ARRAY(args1)];\
+				const TYPE2		arg2		= args2[baseNdx%DE_LENGTH_OF_ARRAY(args2)];\
+				const TYPE3		arg3		= args3[baseNdx%DE_LENGTH_OF_ARRAY(args3)];\
+				const TYPE4		arg4		= args4[baseNdx%DE_LENGTH_OF_ARRAY(args4)];\
+				gl.FUNCNAME(arg0, arg1, arg2, arg3, arg4);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+#define ADD_ARG_CASE6(NAME, DESCRIPTION, FUNCNAME, TYPE0, ARGS0, TYPE1, ARGS1, TYPE2, ARGS2, TYPE3, ARGS3, TYPE4, ARGS4, TYPE5, ARGS5)\
+do {\
+	class StateChangeCallTest_ ## NAME : public gls::StateChangeCallPerformanceCase\
+	{\
+	public:\
+		StateChangeCallTest_ ## NAME (Context& context, const char* name, const char* description)\
+			: gls::StateChangeCallPerformanceCase(context.getTestContext(), context.getRenderContext(), name, description)\
+		{\
+		}\
+		virtual void execCalls (const glw::Functions& gl, int iterNdx, int callCount)\
+		{\
+			const TYPE0 args0[] = ARGS0;\
+			const TYPE1 args1[] = ARGS1;\
+			const TYPE2 args2[] = ARGS2;\
+			const TYPE3 args3[] = ARGS3;\
+			const TYPE4 args4[] = ARGS4;\
+			const TYPE5 args5[] = ARGS5;\
+			for (int callNdx = 0; callNdx < callCount; callNdx++)\
+			{\
+				const int		baseNdx		= iterNdx*callCount + callNdx;\
+				const TYPE0		arg0		= args0[baseNdx%DE_LENGTH_OF_ARRAY(args0)];\
+				const TYPE1		arg1		= args1[baseNdx%DE_LENGTH_OF_ARRAY(args1)];\
+				const TYPE2		arg2		= args2[baseNdx%DE_LENGTH_OF_ARRAY(args2)];\
+				const TYPE3		arg3		= args3[baseNdx%DE_LENGTH_OF_ARRAY(args3)];\
+				const TYPE4		arg4		= args4[baseNdx%DE_LENGTH_OF_ARRAY(args4)];\
+				const TYPE5		arg5		= args5[baseNdx%DE_LENGTH_OF_ARRAY(args5)];\
+				gl.FUNCNAME(arg0, arg1, arg2, arg3, arg4, arg5);\
+			}\
+		}\
+	};\
+	addChild(new StateChangeCallTest_ ## NAME (m_context, #NAME, DESCRIPTION));\
+}while (0);
+
+void StateChangeCallTests::init (void)
+{
+	ADD_ARG_CASE1(enable, "Test cost of glEnable() calls",
+		enable,
+		GLenum,
+		ARG_LIST({
+			GL_CULL_FACE,
+			GL_POLYGON_OFFSET_FILL,
+			GL_SAMPLE_ALPHA_TO_COVERAGE,
+			GL_SAMPLE_COVERAGE,
+			GL_SCISSOR_TEST,
+			GL_STENCIL_TEST,
+			GL_DEPTH_TEST,
+			GL_BLEND,
+			GL_DITHER,
+			GL_RASTERIZER_DISCARD,
+			GL_PRIMITIVE_RESTART_FIXED_INDEX
+		})
+	);
+
+	ADD_ARG_CASE1(disable, "Test cost of glDisable() calls",
+		disable,
+		GLenum,
+		ARG_LIST({
+			GL_CULL_FACE,
+			GL_POLYGON_OFFSET_FILL,
+			GL_SAMPLE_ALPHA_TO_COVERAGE,
+			GL_SAMPLE_COVERAGE,
+			GL_SCISSOR_TEST,
+			GL_STENCIL_TEST,
+			GL_DEPTH_TEST,
+			GL_BLEND,
+			GL_DITHER,
+			GL_RASTERIZER_DISCARD,
+			GL_PRIMITIVE_RESTART_FIXED_INDEX
+		})
+	);
+
+	ADD_ARG_CASE1(depth_func, "Test cost of glDepthFunc() calls",
+		depthFunc,
+		GLenum,
+		ARG_LIST({
+			GL_NEVER,
+			GL_ALWAYS,
+			GL_LESS,
+			GL_LEQUAL,
+			GL_EQUAL,
+			GL_GREATER,
+			GL_GEQUAL,
+			GL_NOTEQUAL
+		})
+	);
+
+	ADD_ARG_CASE1(depth_mask, "Test cost of glDepthMask() calls",
+		depthMask,
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		})
+	);
+
+	ADD_ARG_CASE1(stencil_mask, "Test cost of glStencilMask() calls",
+		stencilMask,
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		})
+	);
+
+	ADD_ARG_CASE1(clear_depth, "Test cost of glClearDepth() calls",
+		clearDepthf,
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			0.5f,
+			1.0f
+		})
+	);
+
+	ADD_ARG_CASE1(clear_stencil, "Test cost of glClearStencil() calls",
+		clearStencil,
+		GLint,
+		ARG_LIST({
+			0,
+			128,
+			28
+		})
+	);
+
+	ADD_ARG_CASE1(line_width, "Test cost of glLineWidth() calls",
+		lineWidth,
+		GLfloat,
+		ARG_LIST({
+			1.0f,
+			0.5f,
+			10.0f
+		})
+	);
+
+	ADD_ARG_CASE1(cull_face, "Test cost of glCullFace() calls",
+		cullFace,
+		GLenum,
+		ARG_LIST({
+			GL_FRONT,
+			GL_BACK,
+			GL_FRONT_AND_BACK
+		})
+	);
+
+	ADD_ARG_CASE1(front_face, "Test cost of glFrontFace() calls",
+		frontFace,
+		GLenum,
+		ARG_LIST({
+			GL_CCW,
+			GL_CW
+		})
+	);
+
+	ADD_ARG_CASE1(blend_equation, "Test cost of glBlendEquation() calls",
+		blendEquation,
+		GLenum,
+		ARG_LIST({
+			GL_FUNC_ADD,
+			GL_FUNC_SUBTRACT,
+			GL_FUNC_REVERSE_SUBTRACT
+		})
+	);
+
+	ADD_ARG_CASE1(enable_vertex_attrib_array, "Test cost of glEnableVertexAttribArray() calls",
+		enableVertexAttribArray,
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			2,
+			3,
+			4,
+			5,
+			6,
+			7,
+		})
+	);
+
+	ADD_ARG_CASE1(disable_vertex_attrib_array, "Test cost of glDisableVertexAttribArray() calls",
+		disableVertexAttribArray,
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			2,
+			3,
+			4,
+			5,
+			6,
+			7,
+		})
+	);
+
+	ADD_ARG_CASE1(use_program, "Test cost of glUseProgram() calls. Note: Uses only program 0.",
+		useProgram,
+		GLuint,
+		ARG_LIST({
+			0,
+		})
+	);
+
+	ADD_ARG_CASE1(active_texture, "Test cost of glActiveTexture() calls",
+		activeTexture,
+		GLenum,
+		ARG_LIST({
+			GL_TEXTURE0,
+			GL_TEXTURE1,
+			GL_TEXTURE2,
+			GL_TEXTURE3,
+			GL_TEXTURE4,
+			GL_TEXTURE5,
+			GL_TEXTURE6,
+			GL_TEXTURE7
+		})
+	);
+
+	ADD_ARG_CASE2(depth_range, "Test cost of glDepthRangef() calls",
+		depthRangef,
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f
+		}),
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f
+		})
+	);
+
+	ADD_ARG_CASE2(polygon_offset, "Test cost of glPolygonOffset() calls",
+		polygonOffset,
+		GLfloat,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			10.0f
+		}),
+		GLfloat,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			1000.0f
+		})
+	);
+
+	ADD_ARG_CASE2(sample_coverage, "Test cost of glSampleCoverage() calls",
+		sampleCoverage,
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			0.67f
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		})
+	);
+
+	ADD_ARG_CASE2(blend_func, "Test cost of glBlendFunc() calls",
+		blendFunc,
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		})
+	);
+
+	ADD_ARG_CASE2(blend_equation_separate, "Test cost of glBlendEquationSeparate() calls",
+		blendEquationSeparate,
+		GLenum,
+		ARG_LIST({
+			GL_FUNC_ADD,
+			GL_FUNC_SUBTRACT,
+			GL_FUNC_REVERSE_SUBTRACT
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_FUNC_ADD,
+			GL_FUNC_SUBTRACT,
+			GL_FUNC_REVERSE_SUBTRACT
+		})
+	);
+
+	ADD_ARG_CASE2(stencil_mask_separate, "Test cost of glStencilMaskSeparate() calls",
+		stencilMaskSeparate,
+		GLenum,
+		ARG_LIST({
+			GL_FRONT,
+			GL_BACK,
+			GL_FRONT_AND_BACK
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		})
+	);
+
+	ADD_ARG_CASE2(bind_buffer, "Test cost of glBindBuffer() calls. Note: Uses only buffer 0",
+		bindBuffer,
+		GLenum,
+		ARG_LIST({
+			GL_ELEMENT_ARRAY_BUFFER,
+			GL_ARRAY_BUFFER
+		}),
+		GLuint,
+		ARG_LIST({
+			0
+		})
+	);
+
+	ADD_ARG_CASE2(bind_texture, "Test cost of glBindTexture() calls. Note: Uses only texture 0",
+		bindTexture,
+		GLenum,
+		ARG_LIST({
+			GL_TEXTURE_2D,
+			GL_TEXTURE_CUBE_MAP
+		}),
+		GLuint,
+		ARG_LIST({
+			0
+		})
+	);
+
+	ADD_ARG_CASE2(bind_sampler, "Test cost of glBindSampler() calls. Note: Uses only sampler 0",
+		bindSampler,
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			2,
+			3,
+			4,
+			5,
+			6,
+			7
+		}),
+		GLuint,
+		ARG_LIST({
+			0
+		})
+	);
+
+	ADD_ARG_CASE1(bind_vertex_array, "Test cost of glBindVertexArray() calls. Note: Uses only VAO 0",
+		bindVertexArray,
+		GLuint,
+		ARG_LIST({
+			0
+		})
+	);
+
+	ADD_ARG_CASE2(hint, "Test cost of glHint() calls",
+		hint,
+		GLenum,
+		ARG_LIST({
+			GL_GENERATE_MIPMAP_HINT
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_FASTEST,
+			GL_NICEST,
+			GL_DONT_CARE
+		})
+	);
+
+	ADD_ARG_CASE3(stencil_func, "Test cost of glStencilFunc() calls",
+		stencilFunc,
+		GLenum,
+		ARG_LIST({
+			GL_NEVER,
+			GL_ALWAYS,
+			GL_LESS,
+			GL_LEQUAL,
+			GL_EQUAL,
+			GL_GEQUAL,
+			GL_GREATER,
+			GL_NOTEQUAL
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			255,
+			128,
+			7
+		}),
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			255,
+			128,
+			7,
+			0xFFFFFFFF
+		})
+	);
+
+	ADD_ARG_CASE3(stencil_op, "Test cost of glStencilOp() calls",
+		stencilOp,
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		})
+	);
+
+	ADD_ARG_CASE4(viewport, "Test cost of glViewport() calls",
+		viewport,
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		})
+	);
+
+	ADD_ARG_CASE4(scissor, "Test cost of glScissor() calls",
+		scissor,
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			100,
+			1145235
+		})
+	);
+
+	ADD_ARG_CASE4(stencil_func_separate, "Test cost of glStencilFuncSeparate() calls",
+		stencilFuncSeparate,
+		GLenum,
+		ARG_LIST({
+			GL_FRONT,
+			GL_BACK,
+			GL_FRONT_AND_BACK
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_NEVER,
+			GL_ALWAYS,
+			GL_LESS,
+			GL_LEQUAL,
+			GL_EQUAL,
+			GL_GEQUAL,
+			GL_GREATER,
+			GL_NOTEQUAL
+		}),
+		GLint,
+		ARG_LIST({
+			0,
+			1,
+			255,
+			128,
+			7
+		}),
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			255,
+			128,
+			7,
+			0xFFFFFFFF
+		})
+	);
+
+	ADD_ARG_CASE4(stencil_op_separatae, "Test cost of glStencilOpSeparate() calls",
+		stencilOpSeparate,
+		GLenum,
+		ARG_LIST({
+			GL_FRONT,
+			GL_BACK,
+			GL_FRONT_AND_BACK
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_KEEP,
+			GL_ZERO,
+			GL_REPLACE,
+			GL_INCR,
+			GL_DECR,
+			GL_INVERT,
+			GL_INCR_WRAP,
+			GL_DECR_WRAP
+		})
+	);
+
+	ADD_ARG_CASE4(blend_func_separate, "Test cost of glBlendFuncSeparate() calls",
+		blendFuncSeparate,
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_ZERO,
+			GL_ONE,
+			GL_SRC_COLOR,
+			GL_ONE_MINUS_SRC_COLOR,
+			GL_DST_COLOR,
+			GL_ONE_MINUS_DST_COLOR,
+			GL_SRC_ALPHA,
+			GL_ONE_MINUS_SRC_ALPHA,
+			GL_DST_ALPHA,
+			GL_ONE_MINUS_DST_ALPHA,
+			GL_CONSTANT_COLOR,
+			GL_ONE_MINUS_CONSTANT_COLOR,
+			GL_CONSTANT_ALPHA,
+			GL_ONE_MINUS_CONSTANT_ALPHA
+		})
+	);
+
+	ADD_ARG_CASE4(color_mask, "Test cost of glColorMask() calls",
+		colorMask,
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_TRUE,
+			GL_FALSE
+		})
+	);
+
+	ADD_ARG_CASE4(clear_color, "Test cost of glClearColor() calls",
+		clearColor,
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			0.33f
+		}),
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			0.33f
+		}),
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			0.33f
+		}),
+		GLclampf,
+		ARG_LIST({
+			0.0f,
+			1.0f,
+			0.5f,
+			0.33f
+		})
+	);
+
+	ADD_ARG_CASE6(vertex_attrib_pointer, "Test cost of glVertexAttribPointer() calls",
+		vertexAttribPointer,
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			2,
+			3,
+			4,
+			5,
+			6,
+			7
+		}),
+		GLint,
+		ARG_LIST({
+			1,
+			2,
+			3,
+			4
+		}),
+		GLenum,
+		ARG_LIST({
+			GL_UNSIGNED_BYTE,
+			GL_BYTE,
+			GL_UNSIGNED_SHORT,
+			GL_SHORT,
+			GL_FLOAT
+		}),
+		GLboolean,
+		ARG_LIST({
+			GL_FALSE,
+			GL_TRUE
+		}),
+		GLsizei,
+		ARG_LIST({
+			0,
+			2,
+			4
+		}),
+		void*,
+		ARG_LIST({
+			(void*)(deUintptr)(0x0FF),
+			(void*)(deUintptr)(0x0EF)
+		})
+	);
+
+	ADD_ARG_CASE2(vertex_attrib_divisor, "Test cost of glVertexAttribDivisor() calls",
+		vertexAttribDivisor,
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			2,
+			3,
+			4,
+			5,
+			6,
+			7
+		}),
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			3,
+			7,
+			11,
+			127,
+			256
+		})
+	);
+
+	ADD_ARG_CASE3(bind_buffer_base, "Test cost of glBindBufferBase() calls. Note: uses only buffer 0.",
+		bindBufferBase,
+		GLuint,
+		ARG_LIST({
+			GL_UNIFORM_BUFFER
+		}),
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			2,
+			3
+		}),
+		GLuint,
+		ARG_LIST({
+			0
+		})
+	);
+
+	ADD_ARG_CASE5(bind_buffer_range, "Test cost of glBindBufferRange() calls. Note: uses only buffer 0.",
+		bindBufferRange,
+		GLuint,
+		ARG_LIST({
+			GL_UNIFORM_BUFFER
+		}),
+		GLuint,
+		ARG_LIST({
+			0,
+			1,
+			2,
+			3
+		}),
+		GLuint,
+		ARG_LIST({
+			0
+		}),
+		GLintptr,
+		ARG_LIST({
+			0
+		}),
+		GLsizeiptr,
+		ARG_LIST({
+			0
+		})
+	);
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pStateChangeCallTests.hpp b/modules/gles3/performance/es3pStateChangeCallTests.hpp
new file mode 100644
index 0000000..fd0ff6f
--- /dev/null
+++ b/modules/gles3/performance/es3pStateChangeCallTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PSTATECHANGECALLTESTS_HPP
+#define _ES3PSTATECHANGECALLTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State change call performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class StateChangeCallTests : public TestCaseGroup
+{
+public:
+							StateChangeCallTests	(Context& context);
+							~StateChangeCallTests	(void);
+
+	void					init					(void);
+
+private:
+							StateChangeCallTests	(const StateChangeCallTests& other);
+	StateChangeCallTests&	operator=				(const StateChangeCallTests& other);
+};
+
+} // Performance
+} // gles2
+} // deqp
+
+#endif // _ES3PSTATECHANGECALLTESTS_HPP
diff --git a/modules/gles3/performance/es3pStateChangeTests.cpp b/modules/gles3/performance/es3pStateChangeTests.cpp
new file mode 100644
index 0000000..32eaae0
--- /dev/null
+++ b/modules/gles3/performance/es3pStateChangeTests.cpp
@@ -0,0 +1,1705 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State change performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pStateChangeTests.hpp"
+#include "glsStateChangePerfTestCases.hpp"
+#include "gluShaderProgram.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+using namespace glw; // GL types
+
+namespace
+{
+
+enum
+{
+	VIEWPORT_WIDTH	= 24,
+	VIEWPORT_HEIGHT	= 24
+};
+
+class StateChangeCase : public gls::StateChangePerformanceCase
+{
+public:
+					StateChangeCase			(Context& context, int drawCallCount, int triangleCount, bool drawArrays, bool useIndexBuffer, const char* name, const char* description);
+					~StateChangeCase		(void);
+
+protected:
+	virtual void	renderTest				(const glw::Functions& gl);
+	virtual void	renderReference			(const glw::Functions& gl);
+
+	virtual void	changeState				(const glw::Functions& gl, int stateId) = 0;
+};
+
+StateChangeCase::StateChangeCase (Context& context, int drawCallCount, int triangleCount, bool drawArrays, bool useIndexBuffer, const char* name, const char* description)
+	: gls::StateChangePerformanceCase(context.getTestContext(), context.getRenderContext(), name, description,
+									  (useIndexBuffer	? DRAWTYPE_INDEXED_BUFFER	:
+									   drawArrays		? DRAWTYPE_NOT_INDEXED		:
+														  DRAWTYPE_INDEXED_USER_PTR), drawCallCount, triangleCount)
+{
+	DE_ASSERT(!useIndexBuffer || !drawArrays);
+}
+
+StateChangeCase::~StateChangeCase (void)
+{
+}
+
+void StateChangeCase::renderTest (const glw::Functions& gl)
+{
+	for (int callNdx = 0; callNdx < m_callCount; callNdx++)
+	{
+		changeState(gl, 0);
+		callDraw(gl);
+
+		changeState(gl, 1);
+		callDraw(gl);
+	}
+}
+
+void StateChangeCase::renderReference (const glw::Functions& gl)
+{
+	changeState(gl, 0);
+
+	for (int callNdx = 0; callNdx < m_callCount; callNdx++)
+		callDraw(gl);
+
+	changeState(gl, 1);
+
+	for (int callNdx = 0; callNdx < m_callCount; callNdx++)
+		callDraw(gl);
+}
+
+} // anonymous
+
+StateChangeTests::StateChangeTests (Context& context)
+	: TestCaseGroup(context, "state_change_draw", "Test state change perfomance with draw calls.")
+{
+}
+
+StateChangeTests::~StateChangeTests (void)
+{
+}
+
+#define MACRO_BLOCK(...) __VA_ARGS__
+
+#define ADD_TESTCASE(NAME, DESC, DRAWARRAYS, INDEXBUFFER, INIT_FUNC, CHANGE_FUNC)\
+do {\
+	class StateChangeCase_ ## NAME : public StateChangeCase\
+	{\
+	public:\
+			StateChangeCase_ ## NAME (Context& context, int drawCallCount, int triangleCount, const char* name, const char* description)\
+				: StateChangeCase(context, drawCallCount, triangleCount, (DRAWARRAYS), (INDEXBUFFER), name, description)\
+			{}\
+		virtual void setupInitialState (const glw::Functions& gl)\
+		{\
+			INIT_FUNC\
+		}\
+		virtual void changeState (const glw::Functions& gl, int stateId)\
+		{\
+			CHANGE_FUNC\
+		}\
+	};\
+	manySmallCallsGroup->addChild	(new StateChangeCase_ ## NAME (m_context,1000,2,#NAME,(DESC)));\
+	fewBigCallsGroup->addChild		(new StateChangeCase_ ## NAME (m_context,10,200,#NAME,(DESC)));\
+} while (0);
+
+void StateChangeTests::init (void)
+{
+	tcu::TestCaseGroup* const	manySmallCallsGroup	= new tcu::TestCaseGroup(m_testCtx, "many_small_calls",	"1000 calls, 2 triangles in each");
+	tcu::TestCaseGroup* const	fewBigCallsGroup	= new tcu::TestCaseGroup(m_testCtx, "few_big_calls",	"10 calls, 200 triangles in each");
+
+	addChild(manySmallCallsGroup);
+	addChild(fewBigCallsGroup);
+
+	ADD_TESTCASE(blend, "Enable/Disable blending.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_BLEND);
+			else if (stateId == 1)
+				gl.disable(GL_BLEND);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(depth_test, "Enable/Disable depth test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.depthFunc(GL_LEQUAL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthFunc()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_DEPTH_TEST);
+			else if (stateId == 1)
+				gl.disable(GL_DEPTH_TEST);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(stencil_test, "Enable/Disable stencil test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_STENCIL_TEST);
+			else if (stateId == 1)
+				gl.disable(GL_STENCIL_TEST);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(scissor_test, "Enable/Disable scissor test.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.scissor(2, 3, 12, 13);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glScissor()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_SCISSOR_TEST);
+			else if (stateId == 1)
+				gl.disable(GL_SCISSOR_TEST);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(dither, "Enable/Disable dithering.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_DITHER);
+			else if (stateId == 1)
+				gl.disable(GL_DITHER);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(culling, "Enable/Disable culling.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.frontFace(GL_CW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glFrontFace()");
+
+			gl.cullFace(GL_FRONT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glCullFace()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_CULL_FACE);
+			else if (stateId == 1)
+				gl.disable(GL_CULL_FACE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(rasterizer_discard, "Enable/Disable RASTERIZER_DISCARD.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_RASTERIZER_DISCARD);
+			else if (stateId == 1)
+				gl.disable(GL_RASTERIZER_DISCARD);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(primitive_restart_fixed_index, "Enable/Disable PRIMITIVE_RESTART_FIXED_INDEX.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.enable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
+			else if (stateId == 1)
+				gl.disable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(depth_func, "Change depth func.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_DEPTH_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.depthFunc(GL_GEQUAL);
+			else if (stateId == 1)
+				gl.depthFunc(GL_LEQUAL);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+
+	ADD_TESTCASE(depth_mask, "Toggle depth mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_DEPTH_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.depthFunc(GL_LEQUAL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthFunc()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.depthMask(GL_FALSE);
+			else if (stateId == 1)
+				gl.depthMask(GL_TRUE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(depth_rangef, "Change depth range.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.depthRangef(0.0f, 1.0f);
+			else if (stateId == 1)
+				gl.depthRangef(0.25f, 0.75f);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(blend_equation, "Change blend equation.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.blendEquation(GL_FUNC_SUBTRACT);
+			else if (stateId == 1)
+				gl.blendEquation(GL_FUNC_ADD);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(blend_func, "Change blend function.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+			else if (stateId == 1)
+				gl.blendFunc(GL_ONE, GL_ONE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(polygon_offset, "Change polygon offset.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_POLYGON_OFFSET_FILL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.polygonOffset(0.0f, 0.0f);
+			else if (stateId == 1)
+				gl.polygonOffset(0.1f, 0.1f);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(sample_coverage, "Sample coverage.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.sampleCoverage(0.25f, GL_TRUE);
+			else if (stateId == 1)
+				gl.sampleCoverage(0.75f, GL_FALSE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(viewport, "Change viewport.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.viewport(10, 11, 5, 6);
+			else if (stateId == 1)
+				gl.viewport(2, 3, 17, 14);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(scissor, "Change scissor box.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_SCISSOR_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.scissor(17, 13, 5, 8);
+			else if (stateId == 1)
+				gl.scissor(7, 3, 13, 13);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(color_mask, "Change color mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.colorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
+			else if (stateId == 1)
+				gl.colorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(cull_face, "Change culling mode.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_CULL_FACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.cullFace(GL_FRONT);
+			else if (stateId == 1)
+				gl.cullFace(GL_BACK);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(front_face, "Change front face.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_CULL_FACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.frontFace(GL_CCW);
+			else if (stateId == 1)
+				gl.frontFace(GL_CW);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(stencil_mask, "Change stencil mask.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.stencilMask(0xDD);
+			else if (stateId == 1)
+				gl.stencilMask(~0xDD);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(stencil_func, "Change stencil func.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilOp()");
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.stencilFunc(GL_LEQUAL, 0, 0xFF);
+			else if (stateId == 1)
+				gl.stencilFunc(GL_GEQUAL, 0, 0x00);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(stencil_op, "Change stencil op.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_STENCIL_TEST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.stencilFunc(GL_LEQUAL, 0, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glStencilFunc()");
+
+			gl.clearStencil(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClearStencil()");
+
+			gl.clear(GL_STENCIL_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glClear()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.stencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+			else if (stateId == 1)
+				gl.stencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(bind_array_buffer, "Change array buffer and refresh vertex attrib pointer.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(2);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.bindAttribLocation(m_programs[0]->getProgram(), 0, "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindAttribLocation()");
+			gl.linkProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glLinkProgram()");
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+			gl.enableVertexAttribArray(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+			{
+				gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+				gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			}
+			else if (stateId == 1)
+			{
+				gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[1]);
+				gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			}
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(element_array_buffer, "Change element array buffer.",
+		false,
+		true,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireIndexBuffers(2);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffers[0]);
+			else if (stateId == 1)
+				gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffers[1]);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(bind_texture, "Change texture binding.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(2);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			else if (stateId == 1)
+				gl.bindTexture(GL_TEXTURE_2D, m_textures[1]);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(use_program, "Change used program.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(2);
+
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			{
+				GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+				gl.useProgram(m_programs[0]->getProgram());
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+				gl.uniform1i(samplerLoc, 0);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+			}
+
+			{
+				GLint samplerLoc = gl.getUniformLocation(m_programs[1]->getProgram(), "u_sampler");
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+				gl.useProgram(m_programs[1]->getProgram());
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+				gl.uniform1i(samplerLoc, 0);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+			}
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.useProgram(m_programs[0]->getProgram());
+			else if (stateId == 1)
+				gl.useProgram(m_programs[1]->getProgram());
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_min_filter, "Change texture parameter min filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+			else if (stateId == 1)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_mag_filter, "Change texture parameter mag filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+			else if (stateId == 1)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(tex_parameter_wrap, "Change texture parameter wrap filter.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+			else if (stateId == 1)
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(bind_framebuffer, "Change framebuffer.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requireFramebuffers(2);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffers[0]);
+			else if (stateId == 1)
+				gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffers[1]);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(blend_color, "Change blend color.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			gl.enable(GL_BLEND);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable()");
+
+			gl.blendFunc(GL_CONSTANT_COLOR, GL_CONSTANT_COLOR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBlendFunc()");
+		}),
+		MACRO_BLOCK({
+			if (stateId == 0)
+				gl.blendColor(0.25f, 0.25f, 0.25f, 0.25f);
+			else if (stateId == 1)
+				gl.blendColor(0.75f, 0.75f, 0.75f, 0.75f);
+			else
+				DE_ASSERT(false);
+		})
+	)
+
+	ADD_TESTCASE(sampler, "Change sampler binding.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(1);
+			requireTextures(1);
+			requirePrograms(1);
+			requireSamplers(2);
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+			GLint coordLoc = gl.getAttribLocation(m_programs[0]->getProgram(), "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+			gl.enableVertexAttribArray(coordLoc);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+			gl.vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+
+			for (int ndx = 0; ndx < 2; ndx++)
+			{
+				gl.bindSampler(0, m_samplers[ndx]);
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ndx == 0 ? GL_NEAREST : GL_LINEAR);
+				gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ndx == 0 ? GL_LINEAR  : GL_NEAREST);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "Sampler setup");
+			}
+		}),
+		MACRO_BLOCK({
+			DE_ASSERT(de::inBounds(stateId, 0, 2));
+			gl.bindSampler(0, m_samplers[stateId]);
+		})
+	)
+
+	ADD_TESTCASE(bind_vertex_array, "Change vertex array binding.",
+		true,
+		false,
+		MACRO_BLOCK({
+			requireCoordBuffers(2);
+			requireTextures(1);
+			requirePrograms(1);
+			requireVertexArrays(2);
+
+			gl.bindAttribLocation(m_programs[0]->getProgram(), 0, "a_coord");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindAttribLocation()");
+			gl.linkProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glLinkProgram()");
+
+			gl.useProgram(m_programs[0]->getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+			for (int ndx = 0; ndx < 2; ndx++)
+			{
+				gl.bindVertexArray(m_vertexArrays[ndx]);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glBindVertexArray()");
+				gl.enableVertexAttribArray(0);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glEnableVertexAttribArray()");
+				gl.bindBuffer(GL_ARRAY_BUFFER, m_coordBuffers[ndx]);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+				gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glVertexAttribPointer()");
+			}
+
+			GLint samplerLoc = gl.getUniformLocation(m_programs[0]->getProgram(), "u_sampler");
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textures[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+			gl.uniform1i(samplerLoc, 0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i()");
+
+			gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport()");
+		}),
+		MACRO_BLOCK({
+			DE_ASSERT(de::inRange(stateId, 0, 2));
+			gl.bindVertexArray(m_vertexArrays[stateId]);
+		})
+	)
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pStateChangeTests.hpp b/modules/gles3/performance/es3pStateChangeTests.hpp
new file mode 100644
index 0000000..7810178
--- /dev/null
+++ b/modules/gles3/performance/es3pStateChangeTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PSTATECHANGETESTS_HPP
+#define _ES3PSTATECHANGETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State change performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class StateChangeTests : public TestCaseGroup
+{
+public:
+						StateChangeTests	(Context& context);
+						~StateChangeTests	(void);
+
+	void				init				(void);
+
+private:
+						StateChangeTests	(const StateChangeTests& other);
+	StateChangeTests&	operator=			(const StateChangeTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PSTATECHANGETESTS_HPP
diff --git a/modules/gles3/performance/es3pTextureCases.cpp b/modules/gles3/performance/es3pTextureCases.cpp
new file mode 100644
index 0000000..a80086e
--- /dev/null
+++ b/modules/gles3/performance/es3pTextureCases.cpp
@@ -0,0 +1,256 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pTextureCases.hpp"
+#include "glsShaderPerformanceCase.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluStrUtil.hpp"
+
+#include "deStringUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+using namespace gls;
+using namespace glw; // GL types
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec4;
+using std::string;
+using std::vector;
+using tcu::TestLog;
+
+Texture2DRenderCase::Texture2DRenderCase (Context&			context,
+										  const char*		name,
+										  const char*		description,
+										  deUint32			internalFormat,
+										  deUint32			wrapS,
+										  deUint32			wrapT,
+										  deUint32			minFilter,
+										  deUint32			magFilter,
+										  const tcu::Mat3&	coordTransform,
+										  int				numTextures,
+										  bool				powerOfTwo)
+	: ShaderPerformanceCase	(context.getTestContext(), context.getRenderContext(), name, description, CASETYPE_FRAGMENT)
+	, m_internalFormat		(internalFormat)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_coordTransform		(coordTransform)
+	, m_numTextures			(numTextures)
+	, m_powerOfTwo			(powerOfTwo)
+{
+}
+
+Texture2DRenderCase::~Texture2DRenderCase (void)
+{
+	for (vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+}
+
+static inline int roundDownToPowerOfTwo (int val)
+{
+	DE_ASSERT(val >= 0);
+	int l0 = deClz32(val);
+	return val & ~((1<<(31-l0))-1);
+}
+
+void Texture2DRenderCase::init (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	const tcu::TextureFormat		texFormat	= glu::mapGLInternalFormat(m_internalFormat);
+	const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFormat);
+	const glu::Precision			samplerPrec	= (texFormat.type == tcu::TextureFormat::FLOAT			||
+												   texFormat.type == tcu::TextureFormat::UNSIGNED_INT32	||
+												   texFormat.type == tcu::TextureFormat::SIGNED_INT32)
+												? glu::PRECISION_HIGHP : glu::PRECISION_MEDIUMP;
+	const glu::DataType				samplerType	= glu::getSampler2DType(texFormat);
+	const bool						isIntUint	= samplerType == glu::TYPE_INT_SAMPLER_2D || samplerType == glu::TYPE_UINT_SAMPLER_2D;
+
+	int								width		= m_renderCtx.getRenderTarget().getWidth();
+	int								height		= m_renderCtx.getRenderTarget().getHeight();
+
+	if (m_powerOfTwo)
+	{
+		width	= roundDownToPowerOfTwo(width);
+		height	= roundDownToPowerOfTwo(height);
+	}
+
+	bool mipmaps = m_minFilter == GL_NEAREST_MIPMAP_NEAREST ||
+				   m_minFilter == GL_NEAREST_MIPMAP_LINEAR	||
+				   m_minFilter == GL_LINEAR_MIPMAP_NEAREST	||
+				   m_minFilter == GL_LINEAR_MIPMAP_LINEAR;
+
+	DE_ASSERT(m_powerOfTwo || (!mipmaps && m_wrapS == GL_CLAMP_TO_EDGE && m_wrapT == GL_CLAMP_TO_EDGE));
+
+	Vec2 p00 = (m_coordTransform * Vec3(0.0f, 0.0f, 1.0f)).swizzle(0,1);
+	Vec2 p10 = (m_coordTransform * Vec3(1.0f, 0.0f, 1.0f)).swizzle(0,1);
+	Vec2 p01 = (m_coordTransform * Vec3(0.0f, 1.0f, 1.0f)).swizzle(0,1);
+	Vec2 p11 = (m_coordTransform * Vec3(1.0f, 1.0f, 1.0f)).swizzle(0,1);
+
+	m_attributes.push_back(AttribSpec("a_coords", Vec4(p00.x(), p00.y(), 0.0f, 0.0f),
+												  Vec4(p10.x(), p10.y(), 0.0f, 0.0f),
+												  Vec4(p01.x(), p01.y(), 0.0f, 0.0f),
+												  Vec4(p11.x(), p11.y(), 0.0f, 0.0f)));
+
+	log << TestLog::Message << "Size: " << width << "x" << height << TestLog::EndMessage;
+	log << TestLog::Message << "Format: " <<glu::getPixelFormatName(m_internalFormat) << TestLog::EndMessage;
+	log << TestLog::Message << "Coords: " << p00 << ", " << p10 << ", " << p01 << ", " << p11 << TestLog::EndMessage;
+	log << TestLog::Message << "Wrap: " << glu::getTextureWrapModeStr(m_wrapS) << " / " << glu::getTextureWrapModeStr(m_wrapT) << TestLog::EndMessage;
+	log << TestLog::Message << "Filter: " << glu::getTextureFilterStr(m_minFilter) << " / " << glu::getTextureFilterStr(m_magFilter) << TestLog::EndMessage;
+	log << TestLog::Message << "Mipmaps: " << (mipmaps ? "true" : "false") << TestLog::EndMessage;
+	log << TestLog::Message << "Using additive blending." << TestLog::EndMessage;
+
+	// Use same viewport size as texture size.
+	setViewportSize(width, height);
+
+	m_vertShaderSource =
+		"#version 300 es\n"
+		"in highp vec4 a_position;\n"
+		"in mediump vec2 a_coords;\n"
+		"out mediump vec2 v_coords;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_coords = a_coords;\n"
+		"}\n";
+
+	std::ostringstream fragSrc;
+	fragSrc << "#version 300 es\n";
+	fragSrc << "layout(location = 0) out mediump vec4 o_color;\n";
+	fragSrc << "in mediump vec2 v_coords;\n";
+
+	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+		fragSrc << "uniform " << glu::getPrecisionName(samplerPrec) << " " << glu::getDataTypeName(samplerType) << " u_sampler" << texNdx << ";\n";
+
+	fragSrc << "void main (void)\n"
+			<< "{\n";
+
+	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+	{
+		if (texNdx == 0)
+			fragSrc << "\t" << glu::getPrecisionName(samplerPrec) << " vec4 r = ";
+		else
+			fragSrc << "\tr += ";
+
+		if (isIntUint)
+			fragSrc << "vec4(";
+
+		fragSrc << "texture(u_sampler" << texNdx << ", v_coords)";
+
+		if (isIntUint)
+			fragSrc << ")";
+
+		fragSrc << ";\n";
+	}
+
+	fragSrc << "	o_color = r;\n"
+			<< "}\n";
+
+	m_fragShaderSource = fragSrc.str();
+
+	m_textures.reserve(m_numTextures);
+	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+	{
+		static const IVec4 swizzles[] = { IVec4(0,1,2,3), IVec4(1,2,3,0), IVec4(2,3,0,1), IVec4(3,0,1,2),
+										  IVec4(3,2,1,0), IVec4(2,1,0,3), IVec4(1,0,3,2), IVec4(0,3,2,1) };
+		const IVec4& sw = swizzles[texNdx % DE_LENGTH_OF_ARRAY(swizzles)];
+
+		glu::Texture2D* texture = new glu::Texture2D(m_renderCtx, m_internalFormat, width, height);
+		m_textures.push_back(texture);
+
+		// Fill levels.
+		int numLevels = mipmaps ? texture->getRefTexture().getNumLevels() : 1;
+		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+		{
+			// \todo [2013-06-02 pyry] Values are not scaled back to 0..1 range in shaders.
+			texture->getRefTexture().allocLevel(levelNdx);
+			tcu::fillWithComponentGradients(texture->getRefTexture().getLevel(levelNdx),
+											fmtInfo.valueMin.swizzle(sw[0], sw[1], sw[2], sw[3]),
+											fmtInfo.valueMax.swizzle(sw[0], sw[1], sw[2], sw[3]));
+		}
+
+		texture->upload();
+	}
+
+	ShaderPerformanceCase::init();
+}
+
+void Texture2DRenderCase::deinit (void)
+{
+	for (vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
+		delete *i;
+	m_textures.clear();
+
+	ShaderPerformanceCase::deinit();
+}
+
+void Texture2DRenderCase::setupProgram (deUint32 program)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+	{
+		int samplerLoc = gl.getUniformLocation(program, (string("u_sampler") + de::toString(texNdx)).c_str());
+		gl.uniform1i(samplerLoc, texNdx);
+	}
+}
+
+void Texture2DRenderCase::setupRenderState (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	// Setup additive blending.
+	gl.enable(GL_BLEND);
+	gl.blendFunc(GL_ONE, GL_ONE);
+	gl.blendEquation(GL_FUNC_ADD);
+
+	// Setup textures.
+	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
+	{
+		gl.activeTexture(GL_TEXTURE0 + texNdx);
+		gl.bindTexture(GL_TEXTURE_2D, m_textures[texNdx]->getGLTexture());
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
+	}
+}
+
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pTextureCases.hpp b/modules/gles3/performance/es3pTextureCases.hpp
new file mode 100644
index 0000000..f5ded65
--- /dev/null
+++ b/modules/gles3/performance/es3pTextureCases.hpp
@@ -0,0 +1,78 @@
+#ifndef _ES3PTEXTURECASES_HPP
+#define _ES3PTEXTURECASES_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+#include "glsShaderPerformanceCase.hpp"
+#include "tcuMatrix.hpp"
+#include "gluTexture.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class Texture2DRenderCase : public gls::ShaderPerformanceCase
+{
+public:
+									Texture2DRenderCase			(Context&				context,
+																 const char*			name,
+																 const char*			description,
+																 deUint32				internalFormat,
+																 deUint32				wrapS,
+																 deUint32				wrapT,
+																 deUint32				minFilter,
+																 deUint32				magFilter,
+																 const tcu::Mat3&		coordTransform,
+																 int					numTextures,
+																 bool					powerOfTwo);
+									~Texture2DRenderCase		(void);
+
+	void							init						(void);
+	void							deinit						(void);
+
+private:
+	void							setupProgram				(deUint32 program);
+	void							setupRenderState			(void);
+
+	const deUint32					m_internalFormat;
+	const deUint32					m_wrapS;
+	const deUint32					m_wrapT;
+	const deUint32					m_minFilter;
+	const deUint32					m_magFilter;
+	const tcu::Mat3					m_coordTransform;
+	const int						m_numTextures;
+	const bool						m_powerOfTwo;
+
+	std::vector<glu::Texture2D*>	m_textures;
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PTEXTURECASES_HPP
diff --git a/modules/gles3/performance/es3pTextureCountTests.cpp b/modules/gles3/performance/es3pTextureCountTests.cpp
new file mode 100644
index 0000000..8ead678
--- /dev/null
+++ b/modules/gles3/performance/es3pTextureCountTests.cpp
@@ -0,0 +1,88 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture count performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pTextureCountTests.hpp"
+#include "es3pTextureCases.hpp"
+#include "gluStrUtil.hpp"
+
+#include "deStringUtil.hpp"
+
+#include "glwEnums.hpp"
+
+using std::string;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+TextureCountTests::TextureCountTests (Context& context)
+	: TestCaseGroup(context, "count", "Texture Count Performance Tests")
+{
+}
+
+TextureCountTests::~TextureCountTests (void)
+{
+}
+
+void TextureCountTests::init (void)
+{
+	static const struct
+	{
+		const char*	name;
+		deUint32	internalFormat;
+	} texFormats[] =
+	{
+		{ "rgb565",		GL_RGB565	},
+		{ "rgb8",		GL_RGB8		},
+		{ "rgba8",		GL_RGBA8	},
+		{ "rgba8ui",	GL_RGBA8UI	},
+		{ "rg16f",		GL_RG16F	},
+		{ "rgba16f",	GL_RGBA16F	},
+		{ "rgba32f",	GL_RGBA32F	}
+	};
+	static const int texCounts[] = { 1, 2, 4, 8 };
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+	{
+		for (int cntNdx = 0; cntNdx < DE_LENGTH_OF_ARRAY(texCounts); cntNdx++)
+		{
+			deUint32	format			= texFormats[formatNdx].internalFormat;
+			deUint32	wrapS			= GL_CLAMP_TO_EDGE;
+			deUint32	wrapT			= GL_CLAMP_TO_EDGE;
+			deUint32	minFilter		= GL_NEAREST;
+			deUint32	magFilter		= GL_NEAREST;
+			int			numTextures		= texCounts[cntNdx];
+			string		name			= string(texFormats[formatNdx].name) + "_" + de::toString(numTextures);
+			string		description 	= glu::getPixelFormatName(format);
+
+			addChild(new Texture2DRenderCase(m_context, name.c_str(), description.c_str(), format, wrapS, wrapT, minFilter, magFilter, tcu::Mat3(), numTextures, false /* npot */));
+		}
+	}
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pTextureCountTests.hpp b/modules/gles3/performance/es3pTextureCountTests.hpp
new file mode 100644
index 0000000..2f670ef
--- /dev/null
+++ b/modules/gles3/performance/es3pTextureCountTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PTEXTURECOUNTTESTS_HPP
+#define _ES3PTEXTURECOUNTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture count performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class TextureCountTests : public TestCaseGroup
+{
+public:
+							TextureCountTests		(Context& context);
+							~TextureCountTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureCountTests		(const TextureCountTests& other);
+	TextureCountTests&		operator=				(const TextureCountTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PTEXTURECOUNTTESTS_HPP
diff --git a/modules/gles3/performance/es3pTextureFilteringTests.cpp b/modules/gles3/performance/es3pTextureFilteringTests.cpp
new file mode 100644
index 0000000..4af9882
--- /dev/null
+++ b/modules/gles3/performance/es3pTextureFilteringTests.cpp
@@ -0,0 +1,101 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pTextureFilteringTests.hpp"
+#include "es3pTextureCases.hpp"
+#include "tcuMatrixUtil.hpp"
+
+#include "glwEnums.hpp"
+
+using std::string;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+TextureFilteringTests::TextureFilteringTests (Context& context)
+	: TestCaseGroup(context, "filter", "Texture Filtering Performance Tests")
+{
+}
+
+TextureFilteringTests::~TextureFilteringTests (void)
+{
+}
+
+void TextureFilteringTests::init (void)
+{
+	static const struct
+	{
+		const char*	name;
+		deUint32	internalFormat;
+	} texFormats[] =
+	{
+		{ "rgb565",		GL_RGB565	},
+		{ "rgba8",		GL_RGBA8	},
+		{ "rg16f",		GL_RG16F	},
+		{ "rgba16f",	GL_RGBA16F	}
+	};
+	static const struct
+	{
+		const char*	name;
+		deUint32	filter;
+		bool		minify;
+	} cases[] =
+	{
+		{ "nearest",				GL_NEAREST,					true	},
+		{ "nearest",				GL_NEAREST,					false	},
+		{ "linear",					GL_LINEAR,					true	},
+		{ "linear",					GL_LINEAR,					false	},
+		{ "nearest_mipmap_nearest",	GL_NEAREST_MIPMAP_NEAREST,	true	},
+		{ "nearest_mipmap_linear",	GL_NEAREST_MIPMAP_LINEAR,	true	},
+		{ "linear_mipmap_nearest",	GL_LINEAR_MIPMAP_NEAREST,	true	},
+		{ "linear_mipmap_linear",	GL_LINEAR_MIPMAP_LINEAR,	true	}
+	};
+
+	tcu::Mat3 minTransform	= tcu::translationMatrix(tcu::Vec2(-0.3f, -0.6f)) * tcu::Mat3(tcu::Vec3(1.7f, 2.3f, 1.0f));
+	tcu::Mat3 magTransform	= tcu::translationMatrix(tcu::Vec2( 0.3f,  0.4f)) * tcu::Mat3(tcu::Vec3(0.3f, 0.2f, 1.0f));
+
+	for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+	{
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+		{
+			deUint32	format		= texFormats[formatNdx].internalFormat;
+			deUint32	minFilter	= cases[caseNdx].filter;
+			deUint32	magFilter	= (minFilter == GL_NEAREST || minFilter == GL_LINEAR) ? minFilter : GL_LINEAR;
+			deUint32	wrapS		= GL_REPEAT;
+			deUint32	wrapT		= GL_REPEAT;
+			int			numTextures	= 1;
+			bool		minify		= cases[caseNdx].minify;
+			string		name		= string(cases[caseNdx].name) + (minify ? "_minify_" : "_magnify_") + texFormats[formatNdx].name;
+
+			addChild(new Texture2DRenderCase(m_context, name.c_str(), "", format, wrapS, wrapT, minFilter, magFilter, minify ? minTransform : magTransform, numTextures, true /* pot */));
+		}
+	}
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pTextureFilteringTests.hpp b/modules/gles3/performance/es3pTextureFilteringTests.hpp
new file mode 100644
index 0000000..790cf65
--- /dev/null
+++ b/modules/gles3/performance/es3pTextureFilteringTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PTEXTUREFILTERINGTESTS_HPP
+#define _ES3PTEXTUREFILTERINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture filtering performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class TextureFilteringTests : public TestCaseGroup
+{
+public:
+								TextureFilteringTests		(Context& context);
+								~TextureFilteringTests		(void);
+
+	void						init						(void);
+
+private:
+								TextureFilteringTests		(const TextureFilteringTests& other);
+	TextureFilteringTests&		operator=					(const TextureFilteringTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PTEXTUREFILTERINGTESTS_HPP
diff --git a/modules/gles3/performance/es3pTextureFormatTests.cpp b/modules/gles3/performance/es3pTextureFormatTests.cpp
new file mode 100644
index 0000000..2488db7
--- /dev/null
+++ b/modules/gles3/performance/es3pTextureFormatTests.cpp
@@ -0,0 +1,124 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3pTextureFormatTests.hpp"
+#include "es3pTextureCases.hpp"
+#include "gluStrUtil.hpp"
+
+#include "glwEnums.hpp"
+
+using std::string;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+TextureFormatTests::TextureFormatTests (Context& context)
+	: TestCaseGroup(context, "format", "Texture Format Performance Tests")
+{
+}
+
+TextureFormatTests::~TextureFormatTests (void)
+{
+}
+
+void TextureFormatTests::init (void)
+{
+	struct
+	{
+		const char*	name;
+		deUint32	internalFormat;
+	} texFormats[] =
+	{
+		{ "rgba32f",			GL_RGBA32F,			},
+		{ "rgba32i",			GL_RGBA32I,			},
+		{ "rgba32ui",			GL_RGBA32UI,		},
+		{ "rgba16f",			GL_RGBA16F,			},
+		{ "rgba16i",			GL_RGBA16I,			},
+		{ "rgba16ui",			GL_RGBA16UI,		},
+		{ "rgba8",				GL_RGBA8,			},
+		{ "rgba8i",				GL_RGBA8I,			},
+		{ "rgba8ui",			GL_RGBA8UI,			},
+		{ "srgb8_alpha8",		GL_SRGB8_ALPHA8,	},
+		{ "rgb10_a2",			GL_RGB10_A2,		},
+		{ "rgb10_a2ui",			GL_RGB10_A2UI,		},
+		{ "rgba4",				GL_RGBA4,			},
+		{ "rgb5_a1",			GL_RGB5_A1,			},
+		{ "rgba8_snorm",		GL_RGBA8_SNORM,		},
+		{ "rgb8",				GL_RGB8,			},
+		{ "rgb565",				GL_RGB565,			},
+		{ "r11f_g11f_b10f",		GL_R11F_G11F_B10F,	},
+		{ "rgb32f",				GL_RGB32F,			},
+		{ "rgb32i",				GL_RGB32I,			},
+		{ "rgb32ui",			GL_RGB32UI,			},
+		{ "rgb16f",				GL_RGB16F,			},
+		{ "rgb16i",				GL_RGB16I,			},
+		{ "rgb16ui",			GL_RGB16UI,			},
+		{ "rgb8_snorm",			GL_RGB8_SNORM,		},
+		{ "rgb8i",				GL_RGB8I,			},
+		{ "rgb8ui",				GL_RGB8UI,			},
+		{ "srgb8",				GL_SRGB8,			},
+		{ "rgb9_e5",			GL_RGB9_E5,			},
+		{ "rg32f",				GL_RG32F,			},
+		{ "rg32i",				GL_RG32I,			},
+		{ "rg32ui",				GL_RG32UI,			},
+		{ "rg16f",				GL_RG16F,			},
+		{ "rg16i",				GL_RG16I,			},
+		{ "rg16ui",				GL_RG16UI,			},
+		{ "rg8",				GL_RG8,				},
+		{ "rg8i",				GL_RG8I,			},
+		{ "rg8ui",				GL_RG8UI,			},
+		{ "rg8_snorm",			GL_RG8_SNORM,		},
+		{ "r32f",				GL_R32F,			},
+		{ "r32i",				GL_R32I,			},
+		{ "r32ui",				GL_R32UI,			},
+		{ "r16f",				GL_R16F,			},
+		{ "r16i",				GL_R16I,			},
+		{ "r16ui",				GL_R16UI,			},
+		{ "r8",					GL_R8,				},
+		{ "r8i",				GL_R8I,				},
+		{ "r8ui",				GL_R8UI,			},
+		{ "r8_snorm",			GL_R8_SNORM,		}
+	};
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+	{
+		deUint32	format			= texFormats[formatNdx].internalFormat;
+		string		nameBase		= texFormats[formatNdx].name;
+		deUint32	wrapS			= GL_CLAMP_TO_EDGE;
+		deUint32	wrapT			= GL_CLAMP_TO_EDGE;
+		deUint32	minFilter		= GL_NEAREST;
+		deUint32	magFilter		= GL_NEAREST;
+		int			numTextures		= 1;
+		string		descriptionBase	= glu::getPixelFormatName(format);
+
+		addChild(new Texture2DRenderCase(m_context, nameBase.c_str(), descriptionBase.c_str(), format, wrapS, wrapT, minFilter, magFilter, tcu::Mat3(), numTextures, false /* npot */));
+	}
+}
+
+} // Performance
+} // gles3
+} // deqp
diff --git a/modules/gles3/performance/es3pTextureFormatTests.hpp b/modules/gles3/performance/es3pTextureFormatTests.hpp
new file mode 100644
index 0000000..b589a11
--- /dev/null
+++ b/modules/gles3/performance/es3pTextureFormatTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3PTEXTUREFORMATTESTS_HPP
+#define _ES3PTEXTUREFORMATTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Performance
+{
+
+class TextureFormatTests : public TestCaseGroup
+{
+public:
+							TextureFormatTests		(Context& context);
+							~TextureFormatTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureFormatTests		(const TextureFormatTests& other);
+	TextureFormatTests&		operator=				(const TextureFormatTests& other);
+};
+
+} // Performance
+} // gles3
+} // deqp
+
+#endif // _ES3PTEXTUREFORMATTESTS_HPP
diff --git a/modules/gles3/scripts/gen-conversions.py b/modules/gles3/scripts/gen-conversions.py
new file mode 100644
index 0000000..1acc046
--- /dev/null
+++ b/modules/gles3/scripts/gen-conversions.py
@@ -0,0 +1,374 @@
+import sys
+import random
+import operator
+import itertools
+
+from genutil import *
+
+random.seed(1234567)
+indices = xrange(sys.maxint)
+
+# Constructors:
+#
+# - scalars types
+#   * int <-> float <-> bool (also float(float) etc.)
+#   * to bool: zero means false, others true
+#   * from bool: false==0, true==1
+#   * \todo [petri] float<->int rounding rules?
+# - scalar type from vector
+#   * choose the first component
+# - vectors & matrices
+#   * vector from scalar: broadcast to all components
+#   * matrix from scalar: broadcast scalar to diagonal, other components zero
+#   * vector from vector: copy existing components
+#     + illegal: vector from smaller vector
+#   * mat from mat: copy existing components, other components from identity matrix
+#   * from components: consumed by-component in column-major order, must have same
+#     number of components,
+#     + note: vec4(mat2) valid
+#     \todo [petri] Implement!
+# - notes:
+#   * type conversions are always allowed: mat3(ivec3, bvec3, bool, int, float) is valid!
+#
+# Accessors:
+#
+# - vector components
+#   * .xyzw, .rgba, .stpq
+#   * illegal to mix
+#   * now allowed for scalar types
+#   * legal to chain: vec4.rgba.xyzw.stpq
+#   * illegal to select more than 4 components
+#   * array indexing with [] operator
+#   * can also write!
+# - matrix columns
+#   * [] accessor
+#   * note: mat4[0].x = 1.0; vs mat4[0][0] = 1.0; ??
+#   * out-of-bounds accesses
+#
+# \todo [petri] Accessors!
+#
+# Spec issues:
+#
+# - constructing larger vector from smaller: vec3(vec2) ?
+# - base type and size conversion at same time: vec4(bool), int(vec3) allowed?
+
+def combineVec(comps):
+	res = []
+	for ndx in range(len(comps[0])):
+#		for x in comps:
+#			print x[ndx].toFloat().getScalars() ,
+		scalars = reduce(operator.add, [x[ndx].toFloat().getScalars() for x in comps])
+#		print "->", scalars
+		res.append(Vec.fromScalarList(scalars))
+	return res
+
+def combineIVec(comps):
+	res = []
+	for ndx in range(len(comps[0])):
+		res.append(Vec.fromScalarList(reduce(operator.add, [x[ndx].toInt().getScalars() for x in comps])))
+	return res
+
+def combineUVec(comps):
+	return [x.toUint() for x in combineIVec(comps)]
+
+def combineBVec(comps):
+	res = []
+	for ndx in range(len(comps[0])):
+		res.append(Vec.fromScalarList(reduce(operator.add, [x[ndx].toBool().getScalars() for x in comps])))
+	return res
+
+def combineMat(numCols, numRows, comps):
+	res = []
+	for ndx in range(len(comps[0])):
+		scalars = reduce(operator.add, [x[ndx].toFloat().getScalars() for x in comps])
+		res.append(Mat(numCols, numRows, scalars))
+	return res
+
+def combineMat2(comps):		return combineMat(2, 2, comps)
+def combineMat2x3(comps):	return combineMat(2, 3, comps)
+def combineMat2x4(comps):	return combineMat(2, 4, comps)
+def combineMat3x2(comps):	return combineMat(3, 2, comps)
+def combineMat3(comps):		return combineMat(3, 3, comps)
+def combineMat3x4(comps):	return combineMat(3, 4, comps)
+def combineMat4x2(comps):	return combineMat(4, 2, comps)
+def combineMat4x3(comps):	return combineMat(4, 3, comps)
+def combineMat4(comps):		return combineMat(4, 4, comps)
+
+# 0 \+ [f*f for f in lst]
+# r = 0 \+ [f in lst -> f*f]
+# r = 0 \+ lst
+
+# Templates.
+
+s_simpleCaseTemplate = """
+case ${{NAME}}
+	version 300 es
+	values
+	{
+		${{VALUES}}
+	}
+
+	both ""
+		#version 300 es
+		precision mediump float;
+		precision mediump int;
+
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			${{OP}}
+			${OUTPUT}
+		}
+	""
+end
+"""[1:]
+
+s_simpleIllegalCaseTemplate = """
+case ${{NAME}}
+	version 300 es
+	expect compile_fail
+	values {}
+
+	both ""
+		#version 300 es
+		precision mediump float;
+		precision mediump int;
+
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			${{OP}}
+			${OUTPUT}
+		}
+	""
+end
+"""[1:]
+
+class SimpleCase(ShaderCase):
+	def __init__(self, name, inputs, outputs, op):
+		self.name		= name
+		self.inputs		= inputs
+		self.outputs	= outputs
+		self.op			= op
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"VALUES":	genValues(self.inputs, self.outputs),
+			"OP":		self.op
+		}
+		return fillTemplate(s_simpleCaseTemplate, params)
+
+class ConversionCase(ShaderCase):
+	def __init__(self, inValues, convFunc):
+		outValues = convFunc(inValues)
+		inType	= inValues[0].typeString()
+		outType	= outValues[0].typeString()
+		self.name		= "%s_to_%s" % (inType, outType)
+		self.op			= "out0 = %s(in0);" % outType
+		self.inputs		= [("%s in0" % inType, inValues)]
+		self.outputs	= [("%s out0" % outType, outValues)]
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"VALUES":	genValues(self.inputs, self.outputs),
+			"OP":		self.op
+		}
+		return fillTemplate(s_simpleCaseTemplate, params)
+
+class IllegalConversionCase(ShaderCase):
+	def __init__(self, inValue, outValue):
+		inType	= inValue.typeString()
+		outType	= outValue.typeString()
+		self.name		= "%s_to_%s" % (inType, outType)
+		self.op			= "%s in0 = %s;\n%s out0 = %s(in0);" % (inType, str(inValue), outType, outType)
+		self.inType		= inType
+		self.outType	= outType
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"OP":		self.op
+		}
+		return fillTemplate(s_simpleIllegalCaseTemplate, params)
+
+class CombineCase(ShaderCase):
+	def __init__(self, inComps, combFunc):
+		self.inComps	= inComps
+		self.outValues	= combFunc(inComps)
+		self.outType	= self.outValues[0].typeString()
+		inTypes = [values[0].typeString() for values in inComps]
+		self.name		= "%s_to_%s" % ("_".join(inTypes), self.outType)
+		self.inputs		= [("%s in%s" % (comp[0].typeString(), ndx), comp) for (comp, ndx) in zip(inComps, indices)]
+		self.outputs	= [("%s out0" % self.outType, self.outValues)]
+		self.op			= "out0 = %s(%s);" % (self.outType, ", ".join(["in%d" % x for x in range(len(inComps))]))
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"VALUES":	genValues(self.inputs, self.outputs),
+			"OP":		self.op
+		}
+		return fillTemplate(s_simpleCaseTemplate, params)
+
+# CASE DECLARATIONS
+
+def toPos (value):
+	if isinstance(value, list):
+		return [toPos(x) for x in value]
+	else:
+		return GenMath.abs(value)
+
+inFloat	= [Scalar(x) for x in [0.0, 1.0, 2.0, 3.5, -0.5, -8.25, -20.125, 36.8125]]
+inInt	= [Scalar(x) for x in [0, 1, 2, 5, 8, 11, -12, -66, -192, 255]]
+inUint	= [Uint(x) for x in [0, 2, 3, 8, 9, 12, 10, 45, 193, 255]]
+inBool	= [Scalar(x) for x in [True, False]]
+
+inVec4	= [Vec4(0.0, 0.5, 0.75, 0.825), Vec4(1.0, 1.25, 1.125, 1.75),
+		   Vec4(-0.5, -2.25, -4.875, 9.0), Vec4(-32.0, 64.0, -51.0, 24.0),
+		   Vec4(-0.75, -1.0/31.0, 1.0/19.0, 1.0/4.0)]
+inVec3	= toVec3(inVec4)
+inVec2	= toVec2(inVec4)
+inIVec4	= toIVec4(inVec4)
+inIVec3	= toIVec3(inVec4)
+inIVec2	= toIVec2(inVec4)
+inBVec4	= [Vec4(True, False, False, True), Vec4(False, False, False, True), Vec4(False, True, False, False), Vec4(True, True, True, True), Vec4(False, False, False, False)]
+inBVec3	= toBVec3(inBVec4)
+inBVec2	= toBVec2(inBVec4)
+inUVec4	= toUVec4(toPos(inVec4))
+inUVec3 = toUVec3(toPos(inVec3))
+inUVec2 = toUVec2(toPos(inVec2))
+
+# \todo [petri] Enable large values when epsilon adapts to the values.
+inMat4	= [Mat4(1.0, 0.0, 0.0, 0.0,  0.0, 1.0, 0.0, 0.0,  0.0, 0.0, 1.0, 0.0,  0.0, 0.0, 0.0, 1.0),
+		   Mat4(6.5, 12.5, -0.75, 9.975,  32.0, 1.0/48.0, -8.425, -6.542,  1.0/8.0, 1.0/16.0, 1.0/32.0, 1.0/64.0,  -6.725, -0.5, -0.0125, 9.975),
+		   #Mat4(128.0, 256.0, -512.0, -1024.0,  2048.0, -4096.0, 8192.0, -8192.0,  192.0, -384.0, 768.0, -1536.0,  8192.0, -8192.0, 6144.0, -6144.0)
+		   ]
+inMat2	= [Mat2(1.0, 0.0,  0.0, 1.0),
+		   Mat2(6.5, 12.5,  -0.75, 9.975),
+		   Mat2(6.5, 12.5,  -0.75, 9.975),
+		   Mat2(8.0, 16.0,  -24.0, -16.0),
+		   Mat2(1.0/8.0, 1.0/16.0,  1.0/32.0, 1.0/64.0),
+		   Mat2(-18.725, -0.5,  -0.0125, 19.975),
+		   #Mat2(128.0, -4096.0,  192.0, -1536.0),
+		   #Mat2(-1536.0, 8192.0,  6144.0, -6144.0)
+		   ]
+
+inMat4x3	= toMat4x3(inMat4)
+inMat4x2	= toMat4x2(inMat4)
+inMat3x4	= toMat3x4(inMat4)
+inMat3		= toMat3(inMat4)
+inMat3x2	= toMat3x2(inMat4)
+inMat2x4	= toMat2x4(inMat4)
+inMat2x3	= toMat2x3(inMat4)
+
+def genConversionCases(inValueList, convFuncList):
+	combinations = list(itertools.product(inValueList, convFuncList))
+	return [ConversionCase(inValues, convFunc) for (inValues, convFunc) in combinations]
+
+def genIllegalConversionCases(inValueList, outValueList):
+	inValues	= [x[0] for x in inValueList]
+	outValues	= [x[0] for x in outValueList]
+	combinations = list(itertools.product(inValues, outValues))
+	return [IllegalConversionCase(inVal, outVal) for (inVal, outVal) in combinations]
+
+def shuffleSubLists(outer):
+	return [shuffled(inner) for inner in outer]
+
+# Generate all combinations of CombineCases.
+# inTupleList	a list of tuples of value-lists
+# combFuncList	a list of comb* functions to combine
+def genComponentCases(inCompLists, combFuncList):
+	res = []
+	for comps in inCompLists:
+		maxLen = reduce(max, [len(values) for values in comps])
+		comps = [repeatToLength(values, maxLen) for values in comps]
+		comps = [shuffled(values) for values in comps]
+		for combFunc in combFuncList:
+			res += [CombineCase(comps, combFunc)]
+	return res
+
+allConversionCases = []
+
+# Scalar-to-scalar conversions.
+allConversionCases.append(CaseGroup("scalar_to_scalar", "Scalar to Scalar Conversions",
+	genConversionCases([inFloat, inInt, inUint, inBool], [toFloat, toInt, toBool]) +\
+	genConversionCases([toPos(inFloat), toPos(inInt), inUint, inBool], [toUint])))
+
+# Scalar-to-vector conversions.
+allConversionCases.append(CaseGroup("scalar_to_vector", "Scalar to Vector Conversions",
+	genConversionCases([inFloat, inInt, inUint, inBool], [toVec2, toVec3, toVec4, toIVec2, toIVec3, toIVec4, toBVec2, toBVec3, toBVec4]) +\
+	genConversionCases([toPos(inFloat), toPos(inInt), inUint, inBool], [toUVec2, toUVec3, toUVec4])))
+
+# Vector-to-scalar conversions.
+allConversionCases.append(CaseGroup("vector_to_scalar", "Vector to Scalar Conversions",
+	genConversionCases([inVec2, inVec3, inVec4, inIVec2, inIVec3, inIVec4, inUVec2, inUVec3, inUVec4, inBVec2, inBVec3, inBVec4], [toFloat, toInt, toBool]) +\
+	genConversionCases([toPos(inVec2), toPos(inVec3), toPos(inVec4), toPos(inIVec2), toPos(inIVec3), toPos(inIVec4), inUVec2, inUVec3, inUVec4, inBVec2, inBVec3, inBVec4], [toUint])))
+
+# Illegal vector-to-vector conversions (to longer vec).
+allConversionCases.append(CaseGroup("vector_illegal", "Illegal Vector Conversions",
+	genIllegalConversionCases([inVec2, inIVec2, inUVec2, inBVec2], [inVec3, inIVec3, inUVec3, inBVec3, inVec4, inIVec4, inUVec4, inBVec4]) +\
+	genIllegalConversionCases([inVec3, inIVec3, inUVec3, inBVec3], [inVec4, inIVec4, inUVec4, inBVec4])))
+
+# Vector-to-vector conversions (type conversions, downcasts).
+allConversionCases.append(CaseGroup("vector_to_vector", "Vector to Vector Conversions",
+	genConversionCases([inVec4, inIVec4, inUVec4, inBVec4], [toVec4, toVec3, toVec2, toIVec4, toIVec3, toIVec2, toBVec4, toBVec3, toBVec2]) +\
+	genConversionCases([toPos(inVec4), toPos(inIVec4), inUVec4, inBVec4], [toUVec4, toUVec3, toUVec2]) +\
+	genConversionCases([inVec3, inIVec3, inUVec3, inBVec3], [toVec3, toVec2, toIVec3, toIVec2, toBVec3, toBVec2]) +\
+	genConversionCases([toPos(inVec3), toPos(inIVec3), inUVec3, inBVec3], [toUVec3, toUVec2]) +\
+	genConversionCases([inVec2, inIVec2, inUVec2, inBVec2], [toVec2, toIVec2, toBVec2]) +\
+	genConversionCases([toPos(inVec2), toPos(inIVec2), inUVec2, inBVec2], [toUVec2])))
+
+# Scalar-to-matrix.
+allConversionCases.append(CaseGroup("scalar_to_matrix", "Scalar to Matrix Conversions",
+	genConversionCases([inFloat, inInt, inUint, inBool], [toMat4, toMat4x3, toMat4x2, toMat3x4, toMat3, toMat3x2, toMat2x4, toMat2x3, toMat2])))
+
+# Vector-to-matrix.
+#allConversionCases += genConversionCases([inVec4, inIVec4, inBVec4], [toMat4])
+#allConversionCases += genConversionCases([inVec3, inIVec3, inBVec3], [toMat3])
+#allConversionCases += genConversionCases([inVec2, inIVec2, inBVec2], [toMat2])
+
+# Matrix-to-matrix.
+allConversionCases.append(CaseGroup("matrix_to_matrix", "Matrix to Matrix Conversions",
+	genConversionCases([inMat4, inMat4x3, inMat4x2, inMat3x4, inMat3, inMat3x2, inMat2x4, inMat2x3, inMat2], [toMat4, toMat4x3, toMat4x2, toMat3x4, toMat3, toMat3x2, toMat2x4, toMat2x3, toMat2])))
+
+# Vector-from-components, matrix-from-components.
+in2Comp 	= [[inFloat, inFloat], [inInt, inInt], [inUint, inUint], [inBool, inBool], [inFloat, inInt], [inFloat, inBool], [inInt, inBool], [inInt, inUint], [inUint, inFloat]]
+in3Comp 	= [[inFloat, inFloat, inFloat], [inInt, inInt, inInt], [inUint, inUint, inUint], [inBool, inBool, inBool], [inBool, inFloat, inInt], [inVec2, inBool], [inBVec2, inFloat], [inBVec2, inInt], [inBool, inIVec2], [inFloat, inUVec2]]
+in4Comp 	= [[inVec2, inVec2], [inBVec2, inBVec2], [inFloat, inFloat, inFloat, inFloat], [inInt, inInt, inInt, inInt], [inUint, inUint, inUint, inUint], [inBool, inBool, inBool, inBool], [inBool, inFloat, inInt, inBool], [inVec2, inIVec2], [inVec2, inBVec2], [inBVec3, inFloat], [inVec3, inFloat], [inInt, inIVec2, inInt], [inBool, inFloat, inIVec2], [inFloat, inUVec3], [inInt, inUVec2, inBool]]
+in6Comp 	= [[inVec3, inVec3], [inBVec3, inBVec3], [inFloat, inFloat, inFloat, inFloat, inFloat, inFloat], [inInt, inInt, inInt, inInt, inInt, inInt], [inBool, inBool, inBool, inBool, inBool, inBool], [inBool, inFloat, inInt, inBool, inFloat, inInt], [inVec3, inIVec3], [inVec2, inBVec4], [inBVec3, inFloat, inIVec2], [inVec3, inFloat, inBVec2]]
+in8Comp 	= [[inVec3, inVec3, inVec2], [inIVec3, inIVec3, inIVec2], [inVec2, inIVec2, inFloat, inFloat, inInt, inBool], [inBool, inFloat, inInt, inVec2, inBool, inBVec2], [inBool, inBVec2, inInt, inVec4], [inFloat, inBVec4, inIVec2, inBool]]
+in9Comp 	= [[inVec3, inVec3, inVec3], [inIVec3, inIVec3, inIVec3], [inVec2, inIVec2, inFloat, inFloat, inInt, inBool, inBool], [inBool, inFloat, inInt, inVec2, inBool, inBVec2, inFloat], [inBool, inBVec2, inInt, inVec4, inBool], [inFloat, inBVec4, inIVec2, inBool, inBool]]
+in12Comp 	= [[inVec4, inVec4, inVec4], [inIVec4, inIVec4, inIVec4], [inVec2, inIVec2, inFloat, inFloat, inFloat, inInt, inInt, inBool, inBool, inBool], [inBool, inFloat, inInt, inVec3, inBool, inBVec3, inFloat, inBool], [inBool, inBVec4, inInt, inVec4, inBool, inFloat], [inFloat, inBVec4, inIVec4, inBool, inBool, inInt]]
+in16Comp	= [[inVec4, inVec4, inVec4, inVec4], [inIVec4, inIVec4, inIVec4, inIVec4], [inBVec4, inBVec4, inBVec4, inBVec4], [inFloat, inIVec3, inBVec3, inVec4, inIVec2, inFloat, inVec2]]
+
+allConversionCases.append(CaseGroup("vector_combine", "Vector Combine Constructors",
+	genComponentCases(in4Comp, [combineVec, combineIVec, combineBVec]) +\
+	genComponentCases(toPos(in4Comp), [combineUVec]) +\
+	genComponentCases(in3Comp, [combineVec, combineIVec, combineBVec]) +\
+	genComponentCases(toPos(in3Comp), [combineUVec]) +\
+	genComponentCases(in2Comp, [combineVec, combineIVec, combineBVec]) +\
+	genComponentCases(toPos(in2Comp), [combineUVec])))
+
+allConversionCases.append(CaseGroup("matrix_combine", "Matrix Combine Constructors",
+	genComponentCases(in4Comp,	[combineMat2])		+\
+	genComponentCases(in6Comp,	[combineMat2x3])	+\
+	genComponentCases(in8Comp,	[combineMat2x4])	+\
+	genComponentCases(in6Comp,	[combineMat3x2])	+\
+	genComponentCases(in9Comp,	[combineMat3])		+\
+	genComponentCases(in12Comp,	[combineMat3x4])	+\
+	genComponentCases(in8Comp,	[combineMat4x2])	+\
+	genComponentCases(in12Comp,	[combineMat4x3])	+\
+	genComponentCases(in16Comp, [combineMat4])
+	))
+
+# Main program.
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	writeAllCases("conversions.test", allConversionCases)
diff --git a/modules/gles3/scripts/gen-invalid-texture-funcs.py b/modules/gles3/scripts/gen-invalid-texture-funcs.py
new file mode 100644
index 0000000..9cc5f99
--- /dev/null
+++ b/modules/gles3/scripts/gen-invalid-texture-funcs.py
@@ -0,0 +1,144 @@
+import sys
+import string
+from genutil import *
+
+# Templates
+
+INVALID_TEXTURE_FUNC_TEMPLATE = """
+case ${{NAME}}
+	expect compile_fail
+	values {}
+	version 300 es
+
+	both ""
+		#version 300 es
+		precision mediump float;
+		${DECLARATIONS}
+		uniform mediump ${{SAMPLERTYPE}} s;
+
+		void main()
+		{
+			${SETUP}
+			${POSITION_FRAG_COLOR} = vec4(${{LOOKUP}});
+			${OUTPUT}
+		}
+	""
+end
+"""[1:-1]
+
+# Classes
+
+def getValueExpr (argType):
+	return "%s(0)" % argType
+
+class InvalidTexFuncCase(ShaderCase):
+	def __init__(self, funcname, args):
+		self.name		= string.join([s.lower() for s in [funcname] + args], "_")
+		self.funcname	= funcname
+		self.args		= args
+
+	def __str__(self):
+		samplerType	= self.args[0]
+
+		lookup = self.funcname + "(s"
+		for arg in self.args[1:]:
+			lookup += ", %s" % getValueExpr(arg)
+		lookup += ")"
+
+		params = { "NAME": self.name, "SAMPLERTYPE": samplerType, "LOOKUP": lookup }
+		return fillTemplate(INVALID_TEXTURE_FUNC_TEMPLATE, params)
+
+# Invalid lookup cases
+# \note Does not include cases that don't make sense
+
+INVALID_TEX_FUNC_CASES = [
+	# texture
+	InvalidTexFuncCase("texture",				["sampler3DShadow",	"vec4"]),
+	InvalidTexFuncCase("texture",				["sampler2DArrayShadow", "vec4", "float"]),
+
+	# textureProj
+	InvalidTexFuncCase("textureProj",			["samplerCube", "vec4"]),
+	InvalidTexFuncCase("textureProj",			["isamplerCube", "vec4"]),
+	InvalidTexFuncCase("textureProj",			["usamplerCube", "vec4"]),
+	InvalidTexFuncCase("textureProj",			["samplerCube", "vec4", "float"]),
+	InvalidTexFuncCase("textureProj",			["isamplerCube", "vec4", "float"]),
+	InvalidTexFuncCase("textureProj",			["usamplerCube", "vec4", "float"]),
+	InvalidTexFuncCase("textureProj",			["sampler2DArrayShadow", "vec4"]),
+	InvalidTexFuncCase("textureProj",			["sampler2DArrayShadow", "vec4", "float"]),
+
+	# textureLod
+	InvalidTexFuncCase("textureLod",			["samplerCubeShadow", "vec4", "float"]),
+	InvalidTexFuncCase("textureLod",			["sampler2DArrayShadow", "vec4", "float"]),
+
+	# textureOffset
+	InvalidTexFuncCase("textureOffset",			["samplerCube", "vec3", "ivec2"]),
+	InvalidTexFuncCase("textureOffset",			["isamplerCube", "vec3", "ivec2"]),
+	InvalidTexFuncCase("textureOffset",			["usamplerCube", "vec3", "ivec2"]),
+	InvalidTexFuncCase("textureOffset",			["samplerCube", "vec3", "ivec3"]),
+	InvalidTexFuncCase("textureOffset",			["isamplerCube", "vec3", "ivec3"]),
+	InvalidTexFuncCase("textureOffset",			["usamplerCube", "vec3", "ivec3"]),
+	InvalidTexFuncCase("textureOffset",			["samplerCube", "vec3", "ivec2", "float"]),
+	InvalidTexFuncCase("textureOffset",			["samplerCube", "vec3", "ivec3", "float"]),
+	InvalidTexFuncCase("textureOffset",			["sampler2DArray", "vec3", "ivec3"]),
+	InvalidTexFuncCase("textureOffset",			["sampler2DArray", "vec3", "ivec3", "float"]),
+	InvalidTexFuncCase("textureOffset",			["samplerCubeShadow", "vec4", "ivec2"]),
+	InvalidTexFuncCase("textureOffset",			["samplerCubeShadow", "vec4", "ivec3"]),
+	InvalidTexFuncCase("textureOffset",			["sampler2DArrayShadow", "vec4", "ivec2"]),
+	InvalidTexFuncCase("textureOffset",			["sampler2DArrayShadow", "vec4", "ivec2", "float"]),
+
+	# texelFetch
+	InvalidTexFuncCase("texelFetch",			["samplerCube", "ivec3", "int"]),
+	InvalidTexFuncCase("texelFetch",			["isamplerCube", "ivec3", "int"]),
+	InvalidTexFuncCase("texelFetch",			["usamplerCube", "ivec3", "int"]),
+	InvalidTexFuncCase("texelFetch",			["sampler2DShadow", "ivec2", "int"]),
+	InvalidTexFuncCase("texelFetch",			["samplerCubeShadow", "ivec3", "int"]),
+	InvalidTexFuncCase("texelFetch",			["sampler2DArrayShadow", "ivec3", "int"]),
+
+	# texelFetchOffset
+	InvalidTexFuncCase("texelFetch",			["samplerCube", "ivec3", "int", "ivec3"]),
+	InvalidTexFuncCase("texelFetch",			["sampler2DShadow", "ivec2", "int", "ivec2"]),
+	InvalidTexFuncCase("texelFetch",			["samplerCubeShadow", "ivec3", "int", "ivec3"]),
+	InvalidTexFuncCase("texelFetch",			["sampler2DArrayShadow", "ivec3", "int", "ivec3"]),
+
+	# textureProjOffset
+	InvalidTexFuncCase("textureProjOffset",		["samplerCube", "vec4", "ivec2"]),
+	InvalidTexFuncCase("textureProjOffset",		["samplerCube", "vec4", "ivec3"]),
+	InvalidTexFuncCase("textureProjOffset",		["samplerCubeShadow", "vec4", "ivec3"]),
+	InvalidTexFuncCase("textureProjOffset",		["sampler2DArrayShadow", "vec4", "ivec2"]),
+	InvalidTexFuncCase("textureProjOffset",		["sampler2DArrayShadow", "vec4", "ivec3"]),
+
+	# textureLodOffset
+	InvalidTexFuncCase("textureLodOffset",		["samplerCube", "vec3", "float", "ivec2"]),
+	InvalidTexFuncCase("textureLodOffset",		["samplerCube", "vec3", "float", "ivec3"]),
+	InvalidTexFuncCase("textureLodOffset",		["samplerCubeShadow", "vec3", "float", "ivec3"]),
+	InvalidTexFuncCase("textureLodOffset",		["sampler2DArrayShadow", "vec3", "float", "ivec2"]),
+	InvalidTexFuncCase("textureLodOffset",		["sampler2DArrayShadow", "vec3", "float", "ivec3"]),
+
+	# textureProjLod
+	InvalidTexFuncCase("textureProjLod",		["samplerCube", "vec4", "float"]),
+	InvalidTexFuncCase("textureProjLod",		["sampler2DArray", "vec4", "float"]),
+	InvalidTexFuncCase("textureProjLod",		["sampler2DArrayShadow", "vec4", "float"]),
+
+	# textureGrad
+	InvalidTexFuncCase("textureGrad",			["sampler2DArray", "vec3", "vec3", "vec3"]),
+
+	# textureGradOffset
+	InvalidTexFuncCase("textureGradOffset",		["samplerCube", "vec3", "vec3", "vec3", "ivec2"]),
+	InvalidTexFuncCase("textureGradOffset",		["samplerCube", "vec3", "vec3", "vec3", "ivec3"]),
+	InvalidTexFuncCase("textureGradOffset",		["samplerCubeShadow", "vec4", "vec3", "vec3", "ivec2"]),
+	InvalidTexFuncCase("textureGradOffset",		["samplerCubeShadow", "vec4", "vec3", "vec3", "ivec3"]),
+
+	# textureProjGrad
+	InvalidTexFuncCase("textureProjGrad",		["samplerCube", "vec4", "vec3", "vec3"]),
+	InvalidTexFuncCase("textureProjGrad",		["sampler2DArray", "vec4", "vec2", "vec2"]),
+
+	# textureProjGradOffset
+	InvalidTexFuncCase("textureProjGradOffset",	["samplerCube", "vec4", "vec3", "vec3", "ivec2"]),
+	InvalidTexFuncCase("textureProjGradOffset",	["samplerCube", "vec4", "vec3", "vec3", "ivec3"]),
+	InvalidTexFuncCase("textureProjGradOffset",	["sampler2DArray", "vec4", "vec2", "vec2", "ivec2"]),
+	InvalidTexFuncCase("textureProjGradOffset",	["sampler2DArray", "vec4", "vec2", "vec2", "ivec3"])
+]
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	writeAllCases("invalid_texture_functions.test", INVALID_TEX_FUNC_CASES)
diff --git a/modules/gles3/scripts/gen-keywords.py b/modules/gles3/scripts/gen-keywords.py
new file mode 100644
index 0000000..04876de
--- /dev/null
+++ b/modules/gles3/scripts/gen-keywords.py
@@ -0,0 +1,108 @@
+import sys
+from genutil import *
+
+# \todo [arttu 2012-12-20] Current set tests variable names only, add function names, structure names, and field selectors.
+
+# Templates
+
+identifierCaseTemplate = """
+case ${{NAME}}
+	expect compile_fail
+	values {}
+	version 300 es
+
+	both ""
+		#version 300 es
+		precision mediump float;
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			float ${{IDENTIFIER}} = 1.0;
+			${OUTPUT}
+		}
+	""
+end
+"""[1:-1]
+
+# Classes
+
+class IdentifierCase(ShaderCase):
+	def __init__(self, name, identifier):
+		self.name 		= name
+		self.identifier = identifier
+
+	def __str__(self):
+		params = { 	"NAME"			: self.name,
+					"IDENTIFIER"	: self.identifier }
+		return fillTemplate(identifierCaseTemplate, params)
+
+# Declarations
+
+KEYWORDS = [
+	"const", "uniform",	"layout", "centroid", "flat", "smooth",	"break", "continue", "do",
+	"for", "while", "switch", "case", "default","if", "else", "in", "out", "inout",	"float",
+	"int", "void", "bool", "true", "false", "invariant", "discard", "return", "mat2", "mat3",
+	"mat4",	"mat2x2", "mat2x3", "mat2x4", "mat3x2", "mat3x3", "mat3x4", "mat4x2", "mat4x3", "mat4x4",
+	"vec2", "vec3", "vec4", "ivec2", "ivec3", "ivec4", "bvec2", "bvec3", "bvec4", "uint", "uvec2",
+	"uvec3", "uvec4", "lowp", "mediump", "highp", "precision", "sampler2D", "sampler3D", "samplerCube",
+	"sampler2DShadow", "samplerCubeShadow", "sampler2DArray", "sampler2DArrayShadow", "isampler2D",
+	"isampler3D", "isamplerCube", "isampler2DArray", "usampler2D", "usampler3D", "usamplerCube",
+	"usampler2DArray", "struct"
+]
+
+RESERVED_KEYWORDS = [
+	"attribute", "varying", "coherent", "restrict", "readonly", "writeonly",
+	"resource", "atomic_uint", "noperspective",	"patch", "sample", "subroutine", "common",
+	"partition", "active", "asm", "class", "union", "enum", "typedef", "template", "this",
+	"goto", "inline", "noinline", "volatile", "public", "static", "extern", "external", "interface",
+	"long", "short", "double", "half", "fixed", "unsigned", "superp", "input", "output",
+	"hvec2", "hvec3", "hvec4", "dvec2", "dvec3", "dvec4", "fvec2", "fvec3", "fvec4", "sampler3DRect",
+	"filter", "image1D", "image2D", "image3D", "imageCube", "iimage1D", "iimage2D", "iimage3D",
+	"iimageCube", "uimage1D", "uimage2D", "uimage3D", "uimageCube", "image1DArray", "image2DArray",
+	"iimage1DArray", "iimage2DArray", "uimage1DArray", "uimage2DArray", "image1DShadow", "image2DShadow",
+	"image1DArrayShadow", "image2DArrayShadow", "imageBuffer", "iimageBuffer", "uimageBuffer",
+	"sampler1D", "sampler1DShadow", "sampler1DArray", "sampler1DArrayShadow", "isampler1D",
+	"isampler1DArray", "usampler1D", "usampler1DArray", "sampler2DRect", "sampler2DRectShadow",
+	"isampler2DRect", "usampler2DRect", "samplerBuffer", "isamplerBuffer", "usamplerBuffer",
+	"sampler2DMS", "isampler2DMS", "usampler2DMS", "sampler2DMSArray", "isampler2DMSArray",
+	"usampler2DMSArray", "sizeof", "cast", "namespace", "using"
+]
+
+INVALID_IDENTIFIERS = [
+	("two_underscores_begin", 	"__invalid"),
+	("two_underscores_middle", 	"in__valid"),
+	("two_underscores_end",		"invalid__"),
+	("gl_begin", 				"gl_Invalid"),
+	("digit", 					"0123"),
+	("digit_begin",				"0invalid"),
+	("max_length",				"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdX"),
+]
+
+# Keyword usage
+
+keywords 			= []
+reservedKeywords 	= []
+invalidIdentifiers 	= []
+
+for keyword in KEYWORDS:
+	keywords.append(IdentifierCase(keyword, keyword)) 			# Keywords
+
+for keyword in RESERVED_KEYWORDS:
+	reservedKeywords.append(IdentifierCase(keyword, keyword))	# Reserved keywords
+
+for (name, identifier) in INVALID_IDENTIFIERS:
+	invalidIdentifiers.append(IdentifierCase(name, identifier)) # Invalid identifiers
+
+keywordCases = [
+	CaseGroup("keywords", 				"Usage of keywords as identifiers.", 			keywords),
+	CaseGroup("reserved_keywords",		"Usage of reserved keywords as identifiers.", 	reservedKeywords),
+	CaseGroup("invalid_identifiers",	"Usage of invalid identifiers.", 				invalidIdentifiers)
+]
+
+# Main program
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	writeAllCases("keywords.test", keywordCases)
diff --git a/modules/gles3/scripts/gen-qualification_order.py b/modules/gles3/scripts/gen-qualification_order.py
new file mode 100644
index 0000000..4bb5fc6
--- /dev/null
+++ b/modules/gles3/scripts/gen-qualification_order.py
@@ -0,0 +1,233 @@
+import sys
+import itertools
+from collections import namedtuple
+from genutil import *
+
+# Templates
+
+declarationTemplate = """
+case ${{NAME}}
+	${{COMPILE_FAIL}}
+	values {}
+
+	vertex ""
+		#version 300 es
+		precision mediump float;
+		in highp vec4 dEQP_Position;
+
+		${{VARIABLE_VTX}}
+
+		void main()
+		{
+			x0 = 1.0;
+			x1 = 2.0;
+			gl_Position = dEQP_Position;
+		}
+	""
+
+	fragment ""
+		#version 300 es
+		precision mediump float;
+		layout(location = 0) out mediump vec4 dEQP_FragColor;
+
+		${{VARIABLE_FRG}}
+
+		void main()
+		{
+			float result = (x0 + x1 + x2) / 3.0;
+			dEQP_FragColor = vec4(result, result, result, 1.0);
+		}
+	""
+end
+"""[1:-1]
+
+parameterTemplate = """
+case ${{NAME}}
+	${{COMPILE_FAIL}}
+	version 300 es
+	values {}
+
+	both ""
+		#version 300 es
+		precision mediump float;
+		${DECLARATIONS}
+
+		float foo0 (${{PARAMETER0}})
+		{
+			return x + 1.0;
+		}
+
+		void foo1 (${{PARAMETER1}})
+		{
+			x = 1.0;
+		}
+
+		float foo2 (${{PARAMETER2}})
+		{
+			return x + 1.0;
+		}
+
+		void main()
+		{
+			${SETUP}
+			float result;
+			foo1(result);
+			float x0 = foo0(1.0);
+			foo2(result);
+			${OUTPUT}
+		}
+	""
+end
+"""[1:-1]
+
+# Classes
+
+class DeclarationCase(ShaderCase):
+	def __init__(self, compileFail, invariantInput, paramList):
+		self.compileFail	= 'expect compile_fail' if compileFail else 'expect pass'
+		self.name			= ''
+		var0				= ''
+		var1				= ''
+		var2				= ''
+
+		for p in paramList:
+			self.name += p.name
+			if paramList.index(p) != len(paramList)-1:
+				self.name += '_'
+
+			var0 += p.vars[0] + ' '
+			var1 += p.vars[1] + ' '
+			var2 += p.vars[2] + ' '
+
+		if invariantInput:
+			self.name += "_invariant_input"
+
+		var0 += 'float x0;\n'
+		var1 += 'float x1;\n'
+		var2 += 'float x2;'
+
+		variables = (var0 + var1 + var2).strip()
+		variables			= variables.replace("  ", " ")
+		self.variableVtx	= variables.replace("anon_centroid", "out")
+		self.variableFrg	= variables.replace("anon_centroid", "in")
+		self.variableVtx	= self.variableVtx.replace("centroid", "centroid out")
+		self.variableFrg	= self.variableFrg.replace("centroid", "centroid in")
+
+		self.variableFrg	= self.variableFrg.replace("invariant", "")	# input variable cannot be invariant...
+		if invariantInput:
+			self.variableFrg = "invariant " + self.variableFrg			# ...unless we are doing a negative test
+
+	def __str__(self):
+		params = {
+			"NAME"			: self.name,
+			"COMPILE_FAIL"	: self.compileFail,
+			"VARIABLE_VTX"	: self.variableVtx,
+			"VARIABLE_FRG"	: self.variableFrg
+		}
+		return fillTemplate(declarationTemplate, params)
+
+class ParameterCase(ShaderCase):
+	def __init__(self, compileFail, paramList):
+		self.compileFail	= "expect compile_fail" if compileFail else "expect pass"
+		self.name			= ''
+		self.param0			= ''
+		self.param1			= ''
+		self.param2			= ''
+
+		for p in paramList:
+			self.name += p.name
+			if paramList.index(p) != len(paramList)-1:
+				self.name += '_'
+
+			self.param0 += p.vars[0] + ' '
+			self.param1 += p.vars[1] + ' '
+			self.param2 += p.vars[2] + ' '
+
+		self.param0 += 'float x'
+		self.param1 += 'float x'
+		self.param2 += 'float x'
+		self.param0	= self.param0.replace("  ", " ")
+		self.param1	= self.param1.replace("  ", " ")
+		self.param2	= self.param2.replace("  ", " ")
+
+	def __str__(self):
+		params = {
+			"NAME"			: self.name,
+			"COMPILE_FAIL"	: self.compileFail,
+			"PARAMETER0"	: self.param0,
+			"PARAMETER1"	: self.param1,
+			"PARAMETER2"	: self.param2,
+		}
+		return fillTemplate(parameterTemplate, params)
+
+# Declarations
+
+CaseFormat			= namedtuple('CaseFormat', 'name vars')
+
+DECL_INVARIANT		= CaseFormat("invariant", 	[ "invariant", 	"",					"" ])
+DECL_INTERPOLATION	= CaseFormat("interp", 		[ "smooth", 	"flat", 			"" ])
+DECL_STORAGE		= CaseFormat("storage", 	[ "centroid", 	"anon_centroid", 	"uniform" ])
+DECL_PRECISION		= CaseFormat("precision", 	[ "lowp", 		"mediump",			"highp" ])
+
+PARAM_STORAGE		= CaseFormat("storage",		[ "const", 		"", 				""])
+PARAM_PARAMETER 	= CaseFormat("parameter",	[ "in", 		"out", 				"inout" ])
+PARAM_PRECISION		= CaseFormat("precision",	[ "lowp", 		"mediump",			"highp" ])
+
+# Order of qualification tests
+
+validDeclarationCases	= []
+invalidDeclarationCases = []
+validParameterCases		= []
+invalidParameterCases	= []
+
+declFormats = [
+	[DECL_INVARIANT, DECL_INTERPOLATION, DECL_STORAGE, DECL_PRECISION],
+	[DECL_INTERPOLATION, DECL_STORAGE, DECL_PRECISION],
+	[DECL_INVARIANT, DECL_INTERPOLATION, DECL_STORAGE],
+	[DECL_INVARIANT, DECL_STORAGE, DECL_PRECISION],
+	[DECL_STORAGE, DECL_PRECISION],
+	[DECL_INTERPOLATION, DECL_STORAGE],
+	[DECL_INVARIANT, DECL_STORAGE]
+]
+
+paramFormats = [
+	[PARAM_STORAGE, PARAM_PARAMETER, PARAM_PRECISION],
+	[PARAM_STORAGE, PARAM_PARAMETER],
+	[PARAM_STORAGE, PARAM_PRECISION],
+	[PARAM_PARAMETER, PARAM_PRECISION]
+]
+print len(paramFormats)
+
+for f in declFormats:
+	for p in itertools.permutations(f):
+		if list(p) == f:
+			validDeclarationCases.append(DeclarationCase(False, False, p))	# Correct order
+		else:
+			invalidDeclarationCases.append(DeclarationCase(True, False, p))	# Incorrect order
+
+for f in declFormats:
+	invalidDeclarationCases.append(DeclarationCase(True, True, f))	# Correct order but invariant is not allowed as and input parameter
+
+for f in paramFormats:
+	for p in itertools.permutations(f):
+		if list(p) == f:
+			validParameterCases.append(ParameterCase(False, p))	# Correct order
+		else:
+			invalidParameterCases.append(ParameterCase(True, p))	# Incorrect order
+
+qualificationOrderCases = [
+	CaseGroup("variables",	"Order of qualification in variable declarations.", children = [
+		CaseGroup("valid", 		"Valid orderings.", 	validDeclarationCases),
+		CaseGroup("invalid",	"Invalid orderings.", 	invalidDeclarationCases)
+	]),
+	CaseGroup("parameters", "Order of qualification in function parameters.", children = [
+		CaseGroup("valid", 		"Valid orderings.", 	validParameterCases),
+		CaseGroup("invalid",	"Invalid orderings.", 	invalidParameterCases)
+	])
+]
+
+# Main program
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	writeAllCases("qualification_order.test", qualificationOrderCases)
diff --git a/modules/gles3/scripts/gen-swizzles.py b/modules/gles3/scripts/gen-swizzles.py
new file mode 100644
index 0000000..70ba842
--- /dev/null
+++ b/modules/gles3/scripts/gen-swizzles.py
@@ -0,0 +1,278 @@
+import sys
+import random
+import operator
+import itertools
+
+from genutil import *
+
+random.seed(1234567)
+indices = xrange(sys.maxint)
+
+# Swizzles:
+# - vector components
+#   * int, float, bool vectors
+#   * .xyzw, .rgba, .stpq
+#   * illegal to mix
+#   * not allowed for scalar types
+#   * legal to chain: vec4.rgba.xyzw.stpq
+#   * illegal to select more than 4 components
+#
+# Subscripts:
+# - array-like indexing with [] operator
+#   * vectors, matrices
+# - read & write
+# - vectors components
+#   * [] accessor
+# - matrix columns
+#   * [] accessor
+#   * note: mat4[0].x = 1.0; vs mat4[0][0] = 1.0; ??
+#   * out-of-bounds accesses
+
+#
+# - vector swizzles
+#   * all vector types (bvec2..4, ivec2..4, vec2..4)
+#   * all precisions (lowp, mediump, highp)
+#   * all component names (xyzw, rgba, stpq)
+#   * broadcast each, reverse, N random
+# - component-masked writes
+#   * all vector types (bvec2..4, ivec2..4, vec2..4)
+#   * all precisions (lowp, mediump, highp)
+#   * all component names (xyzw, rgba, stpq)
+#   * all possible subsets
+#   * all input types (attribute, varying, uniform, tmp)
+#   -> a few hundred cases
+# - concatenated swizzles
+
+#
+VECTOR_TYPES	= [ "vec2", "vec3", "vec4", "ivec2", "ivec3", "ivec4", "bvec2", "bvec3", "bvec4" ]
+PRECISION_TYPES	= [ "lowp", "mediump", "highp" ]
+INPUT_TYPES		= [ "uniform", "varying", "attribute", "tmp" ]
+SWIZZLE_NAMES	= [ "xyzw", "stpq", "rgba" ]
+
+def getDataTypeScalarSize (dt):
+	return {
+		"float":	1,
+		"vec2":		2,
+		"vec3":		3,
+		"vec4":		4,
+		"int":		1,
+		"ivec2":	2,
+		"ivec3":	3,
+		"ivec4":	4,
+		"bool":		1,
+		"bvec2":	2,
+		"bvec3":	3,
+		"bvec4":	4,
+		"mat2":		4,
+		"mat3":		9,
+		"mat4":		16
+	}[dt]
+
+if False:
+	class Combinations:
+		def __init__(self, *args):
+			self.lists				= list(args)
+			self.numLists			= len(args)
+			self.numCombinations	= reduce(operator.mul, map(len, self.lists), 1)
+			print self.lists
+			print self.numCombinations
+
+		def iterate(self):
+			return [tuple(map(lambda x: x[0], self.lists))]
+
+	combinations = Combinations(INPUT_TYPES, VECTOR_TYPES, PRECISION_TYPES)
+	print combinations.iterate()
+	for (inputType, dataType, precision) in combinations.iterate():
+		scalarSize = getDataTypeScalarSize(dataType)
+		print inputType, precision, dataType
+
+def getSwizzlesForWidth(width):
+	if (width == 2):
+		return [(0,), (0,0), (0,1), (1,0), (1,0,1), (0,1,0,0), (1,1,1,1)]
+	elif (width == 3):
+		return [(0,), (2,), (0,2), (2,2), (0,1,2), (2,1,0), (0,0,0), (2,2,2), (2,2,1), (1,0,1), (0,2,0), (0,1,1,0), (2,2,2,2)]
+	elif (width == 4):
+		return [(0,), (3,), (3,0), (3,2), (3,3,3), (1,1,3), (3,2,1), (0,1,2,3), (3,2,1,0), (0,0,0,0), (1,1,1,1), (3,3,3,3), (3,2,2,3), (3,3,3,1), (0,1,0,0), (2,2,3,2)]
+	else:
+		assert False
+
+# Templates.
+
+s_swizzleCaseTemplate = """
+case ${{NAME}}
+	version 300
+	values
+	{
+		${{VALUES}}
+	}
+
+	both ""
+		#version 300 es
+		precision mediump float;
+
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			${{OP}}
+			${OUTPUT}
+		}
+	""
+end
+"""[1:]
+
+s_simpleIllegalCaseTemplate = """
+case ${{NAME}}
+	version 300
+	expect compile_fail
+	values {}
+
+	both ""
+		#version 300 es
+		precision mediump float;
+		precision mediump int;
+
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			${{OP}}
+			${OUTPUT}
+		}
+	""
+end
+"""[1:]
+
+class SwizzleCase(ShaderCase):
+	def __init__(self, name, precision, dataType, swizzle, inputs, outputs):
+		self.name		= name
+		self.precision	= precision
+		self.dataType	= dataType
+		self.swizzle	= swizzle
+		self.inputs		= inputs
+		self.outputs	= outputs
+		self.op			= "out0 = in0.%s;" % swizzle
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"VALUES":	genValues(self.inputs, self.outputs),
+			"OP":		self.op
+		}
+		return fillTemplate(s_swizzleCaseTemplate, params)
+
+# CASE DECLARATIONS
+
+inFloat	= [Scalar(x) for x in [0.0, 1.0, 2.0, 3.5, -0.5, -20.125, 36.8125]]
+inInt	= [Scalar(x) for x in [0, 1, 2, 5, 8, 11, -12, -66, -192, 255]]
+inBool	= [Scalar(x) for x in [True, False]]
+
+inVec4	= [Vec4(0.0, 0.5, 0.75, 0.825), Vec4(1.0, 1.25, 1.125, 1.75),
+		   Vec4(-0.5, -2.25, -4.875, 9.0), Vec4(-32.0, 64.0, -51.0, 24.0),
+		   Vec4(-0.75, -1.0/31.0, 1.0/19.0, 1.0/4.0)]
+inVec3	= toVec3(inVec4)
+inVec2	= toVec2(inVec4)
+inIVec4	= toIVec4(inVec4)
+inIVec3	= toIVec3(inVec4)
+inIVec2	= toIVec2(inVec4)
+inBVec4	= [Vec4(True, False, False, True), Vec4(False, False, False, True), Vec4(False, True, False, False), Vec4(True, True, True, True), Vec4(False, False, False, False)]
+inBVec3	= toBVec3(inBVec4)
+inBVec2	= toBVec2(inBVec4)
+
+# \todo [petri] Enable large values when epsilon adapts to the values.
+inMat4	= [Mat4(1.0, 0.0, 0.0, 0.0,  0.0, 1.0, 0.0, 0.0,  0.0, 0.0, 1.0, 0.0,  0.0, 0.0, 0.0, 1.0),
+		   Mat4(6.5, 12.5, -0.75, 9.975,  32.0, 1.0/48.0, -8.425, -6.542,  1.0/8.0, 1.0/16.0, 1.0/32.0, 1.0/64.0,  -6.725, -0.5, -0.0125, 9.975),
+		   #Mat4(128.0, 256.0, -512.0, -1024.0,  2048.0, -4096.0, 8192.0, -8192.0,  192.0, -384.0, 768.0, -1536.0,  8192.0, -8192.0, 6144.0, -6144.0)
+		   ]
+inMat3	= [Mat3(1.0, 0.0, 0.0,  0.0, 1.0, 0.0,  0.0, 0.0, 1.0),
+		   Mat3(6.5, 12.5, -0.75,  32.0, 1.0/32.0, 1.0/64.0,  1.0/8.0, 1.0/16.0, 1.0/32.0),
+		   #Mat3(-18.725, -0.5, -0.0125,  19.975, -0.25, -17.75,  9.25, 65.125, -21.425),
+		   #Mat3(128.0, -4096.0, -8192.0,  192.0, 768.0, -1536.0,  8192.0, 6144.0, -6144.0)
+		   ]
+inMat2	= [Mat2(1.0, 0.0,  0.0, 1.0),
+		   Mat2(6.5, 12.5,  -0.75, 9.975),
+		   Mat2(6.5, 12.5,  -0.75, 9.975),
+		   Mat2(8.0, 16.0,  -24.0, -16.0),
+		   Mat2(1.0/8.0, 1.0/16.0,  1.0/32.0, 1.0/64.0),
+		   Mat2(-18.725, -0.5,  -0.0125, 19.975),
+		   #Mat2(128.0, -4096.0,  192.0, -1536.0),
+		   #Mat2(-1536.0, 8192.0,  6144.0, -6144.0)
+		   ]
+
+INPUTS = {
+	"float":	inFloat,
+	"vec2":		inVec2,
+	"vec3":		inVec3,
+	"vec4":		inVec4,
+	"int":		inInt,
+	"ivec2":	inIVec2,
+	"ivec3":	inIVec3,
+	"ivec4":	inIVec4,
+	"bool":		inBool,
+	"bvec2":	inBVec2,
+	"bvec3":	inBVec3,
+	"bvec4":	inBVec4,
+	"mat2":		inMat2,
+	"mat3":		inMat3,
+	"mat4":		inMat4
+}
+
+def genConversionCases(inValueList, convFuncList):
+	combinations = list(itertools.product(inValueList, convFuncList))
+	return [ConversionCase(inValues, convFunc) for (inValues, convFunc) in combinations]
+
+allCases = []
+
+# Vector swizzles.
+
+vectorSwizzleCases = []
+
+# \todo [petri] Uses fixed precision.
+for dataType in VECTOR_TYPES:
+	scalarSize = getDataTypeScalarSize(dataType)
+	precision = "mediump"
+	for swizzleComponents in SWIZZLE_NAMES:
+		for swizzleIndices in getSwizzlesForWidth(scalarSize):
+			swizzle = "".join(map(lambda x: swizzleComponents[x], swizzleIndices))
+			#print "%s %s .%s" % (precision, dataType, swizzle)
+			caseName = "%s_%s_%s" % (precision, dataType, swizzle)
+			inputs = INPUTS[dataType]
+			outputs = map(lambda x: x.swizzle(swizzleIndices), inputs)
+			outType = outputs[0].typeString()
+			vectorSwizzleCases.append(SwizzleCase(caseName, precision, dataType, swizzle, [("%s in0" % dataType, inputs)], [("%s out0" % outType, outputs)]))
+
+# ??
+#for dataType in VECTOR_TYPES:
+#	scalarSize = getDataTypeScalarSize(dataType)
+#	for precision in PRECISION_TYPES:
+#		for swizzleIndices in getSwizzlesForWidth(scalarSize):
+#			swizzle = "".join(map(lambda x: "xyzw"[x], swizzleIndices))
+#			#print "%s %s .%s" % (precision, dataType, swizzle)
+#			caseName = "%s_%s_%s" % (precision, dataType, swizzle)
+#			inputs = INPUTS[dataType]
+#			outputs = map(lambda x: x.swizzle(swizzleIndices), inputs)
+#			vectorSwizzleCases.append(SwizzleCase(caseName, precision, dataType, swizzle, [("in0", inputs)], [("out0", outputs)]))
+
+allCases.append(CaseGroup("vector_swizzles", "Vector Swizzles", vectorSwizzleCases))
+
+# Swizzles:
+# - vector components
+#   * int, float, bool vectors
+#   * .xyzw, .rgba, .stpq
+#   * illegal to mix
+#   * not allowed for scalar types
+#   * legal to chain: vec4.rgba.xyzw.stpq
+#   * illegal to select more than 4 components
+
+# TODO: precisions!!
+
+#allCases.append(CaseGroup("vector_swizzles", "Vector Swizzles",
+#	genSwizzleCase([inVec2, inVec3, inVec4],
+
+# Main program.
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	writeAllCases("swizzles.test", allCases)
diff --git a/modules/gles3/scripts/genutil.py b/modules/gles3/scripts/genutil.py
new file mode 100644
index 0000000..d17f811
--- /dev/null
+++ b/modules/gles3/scripts/genutil.py
@@ -0,0 +1,791 @@
+import re
+import math
+import random
+
+PREAMBLE = """
+# WARNING: This file is auto-generated. Do NOT modify it manually, but rather
+# modify the generating script file. Otherwise changes will be lost!
+"""[1:]
+
+class CaseGroup(object):
+	def __init__(self, name, description, children):
+		self.name			= name
+		self.description	= description
+		self.children		= children
+
+class ShaderCase(object):
+	def __init__(self):
+		pass
+
+g_processedCases = {}
+
+def indentTextBlock(text, indent):
+	indentStr = indent * "\t"
+	lines = text.split("\n")
+	lines = [indentStr + line for line in lines]
+	lines = [ ["", line][line.strip() != ""] for line in lines]
+	return "\n".join(lines)
+
+def writeCase(f, case, indent, prefix):
+	print "    %s" % (prefix + case.name)
+	if isinstance(case, CaseGroup):
+		f.write(indentTextBlock('group %s "%s"\n\n' % (case.name, case.description), indent))
+		for child in case.children:
+			writeCase(f, child, indent + 1, prefix + case.name + ".")
+		f.write(indentTextBlock("\nend # %s\n" % case.name, indent))
+	else:
+		# \todo [petri] Fix hack.
+		fullPath = prefix + case.name
+		assert (fullPath not in g_processedCases)
+		g_processedCases[fullPath] = None
+		f.write(indentTextBlock(str(case) + "\n", indent))
+
+def writeAllCases(fileName, caseList):
+	# Write all cases to file.
+	print "  %s.." % fileName
+	f = file(fileName, "wb")
+	f.write(PREAMBLE + "\n")
+	for case in caseList:
+		writeCase(f, case, 0, "")
+	f.close()
+
+	print "done! (%d cases written)" % len(g_processedCases)
+
+# Template operations.
+
+def genValues(inputs, outputs):
+	res = []
+	for (name, values) in inputs:
+		res.append("input %s = [ %s ];" % (name, " | ".join([str(v) for v in values]).lower()))
+	for (name, values) in outputs:
+		res.append("output %s = [ %s ];" % (name, " | ".join([str(v) for v in values]).lower()))
+	return ("\n".join(res))
+
+def fillTemplate(template, params):
+	s = template
+
+	for (key, value) in params.items():
+		m = re.search(r"^(\s*)\$\{\{%s\}\}$" % key, s, re.M)
+		if m is not None:
+			start = m.start(0)
+			end = m.end(0)
+			ws = m.group(1)
+			if value is not None:
+				repl = "\n".join(["%s%s" % (ws, line) for line in value.split("\n")])
+				s = s[:start] + repl + s[end:]
+			else:
+				s = s[:start] + s[end+1:] # drop the whole line
+		else:
+			s = s.replace("${{%s}}" % key, value)
+	return s
+
+# Return shuffled version of list
+def shuffled(lst):
+	tmp = lst[:]
+	random.shuffle(tmp)
+	return tmp
+
+def repeatToLength(lst, toLength):
+	return (toLength / len(lst)) * lst + lst[: toLength % len(lst)]
+
+# Helpers to convert a list of Scalar/Vec values into another type.
+
+def toFloat(lst):	return [Scalar(float(v.x)) for v in lst]
+def toInt(lst):		return [Scalar(int(v.x)) for v in lst]
+def toUint(lst):	return [Uint(int(v.x)) for v in lst]
+def toBool(lst):	return [Scalar(bool(v.x)) for v in lst]
+def toVec4(lst):	return [v.toFloat().toVec4() for v in lst]
+def toVec3(lst):	return [v.toFloat().toVec3() for v in lst]
+def toVec2(lst):	return [v.toFloat().toVec2() for v in lst]
+def toIVec4(lst):	return [v.toInt().toVec4() for v in lst]
+def toIVec3(lst):	return [v.toInt().toVec3() for v in lst]
+def toIVec2(lst):	return [v.toInt().toVec2() for v in lst]
+def toBVec4(lst):	return [v.toBool().toVec4() for v in lst]
+def toBVec3(lst):	return [v.toBool().toVec3() for v in lst]
+def toBVec2(lst):	return [v.toBool().toVec2() for v in lst]
+def toUVec4(lst):	return [v.toUint().toUVec4() for v in lst]
+def toUVec3(lst):	return [v.toUint().toUVec3() for v in lst]
+def toUVec2(lst):	return [v.toUint().toUVec2() for v in lst]
+def toMat2(lst):	return [v.toMat2() for v in lst]
+def toMat2x3(lst):	return [v.toMat2x3() for v in lst]
+def toMat2x4(lst):	return [v.toMat2x4() for v in lst]
+def toMat3x2(lst):	return [v.toMat3x2() for v in lst]
+def toMat3(lst):	return [v.toMat3() for v in lst]
+def toMat3x4(lst):	return [v.toMat3x4() for v in lst]
+def toMat4x2(lst):	return [v.toMat4x2() for v in lst]
+def toMat4x3(lst):	return [v.toMat4x3() for v in lst]
+def toMat4(lst):	return [v.toMat4() for v in lst]
+
+# Random value generation.
+
+class GenRandom(object):
+	def __init__(self):
+		pass
+
+	def uniformVec4(self, count, mn, mx):
+		ret = [Vec4(random.uniform(mn, mx), random.uniform(mn, mx), random.uniform(mn, mx), random.uniform(mn, mx)) for x in xrange(count)]
+		ret[0].x = mn
+		ret[1].x = mx
+		ret[2].x = (mn + mx) * 0.5
+		return ret
+
+	def uniformBVec4(self, count):
+		ret = [Vec4(random.random() >= 0.5, random.random() >= 0.5, random.random() >= 0.5, random.random() >= 0.5) for x in xrange(count)]
+		ret[0].x = True
+		ret[1].x = False
+		return ret
+
+#	def uniform(self,
+
+# Math operating on Scalar/Vector types.
+
+def glslSign(a):			return 0.0 if (a == 0) else +1.0 if (a > 0.0) else -1.0
+def glslMod(x, y):			return x - y*math.floor(x/y)
+def glslClamp(x, mn, mx):	return mn if (x < mn) else mx if (x > mx) else x
+
+class GenMath(object):
+	@staticmethod
+	def unary(func):	return lambda val: val.applyUnary(func)
+
+	@staticmethod
+	def binary(func):	return lambda a, b: (b.expandVec(a)).applyBinary(func, a.expandVec(b))
+
+	@staticmethod
+	def frac(val):		return val.applyUnary(lambda x: x - math.floor(x))
+
+	@staticmethod
+	def exp2(val):		return val.applyUnary(lambda x: math.pow(2.0, x))
+
+	@staticmethod
+	def log2(val):		return val.applyUnary(lambda x: math.log(x, 2.0))
+
+	@staticmethod
+	def rsq(val):		return val.applyUnary(lambda x: 1.0 / math.sqrt(x))
+
+	@staticmethod
+	def sign(val):		return val.applyUnary(glslSign)
+
+	@staticmethod
+	def isEqual(a, b):	return Scalar(a.isEqual(b))
+
+	@staticmethod
+	def isNotEqual(a, b):	return Scalar(not a.isEqual(b))
+
+	@staticmethod
+	def step(a, b):		return (b.expandVec(a)).applyBinary(lambda edge, x: [1.0, 0.0][x < edge], a.expandVec(b))
+
+	@staticmethod
+	def length(a):		return a.length()
+
+	@staticmethod
+	def distance(a, b):	return a.distance(b)
+
+	@staticmethod
+	def dot(a, b):		return a.dot(b)
+
+	@staticmethod
+	def cross(a, b):	return a.cross(b)
+
+	@staticmethod
+	def normalize(a):	return a.normalize()
+
+	@staticmethod
+	def boolAny(a):		return a.boolAny()
+
+	@staticmethod
+	def boolAll(a):		return a.boolAll()
+
+	@staticmethod
+	def boolNot(a):		return a.boolNot()
+
+	@staticmethod
+	def abs(a):			return a.abs()
+
+# ..
+
+class Scalar(object):
+	def __init__(self, x):
+		self.x = x
+
+	def applyUnary(self, func):			return Scalar(func(self.x))
+	def applyBinary(self, func, other):	return Scalar(func(self.x, other.x))
+
+	def isEqual(self, other):	assert isinstance(other, Scalar); return (self.x == other.x)
+
+	def expandVec(self, val):	return val
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.x)
+	def toVec3(self):			return Vec3(self.x, self.x, self.x)
+	def toVec4(self):			return Vec4(self.x, self.x, self.x, self.x)
+	def toUVec2(self):			return UVec2(self.x, self.x)
+	def toUVec3(self):			return UVec3(self.x, self.x, self.x)
+	def toUVec4(self):			return UVec4(self.x, self.x, self.x, self.x)
+	def toMat2(self):			return Mat.fromScalar(2, 2, float(self.x))
+	def toMat2x3(self):			return Mat.fromScalar(2, 3, float(self.x))
+	def toMat2x4(self):			return Mat.fromScalar(2, 4, float(self.x))
+	def toMat3x2(self):			return Mat.fromScalar(3, 2, float(self.x))
+	def toMat3(self):			return Mat.fromScalar(3, 3, float(self.x))
+	def toMat3x4(self):			return Mat.fromScalar(3, 4, float(self.x))
+	def toMat4x2(self):			return Mat.fromScalar(4, 2, float(self.x))
+	def toMat4x3(self):			return Mat.fromScalar(4, 3, float(self.x))
+	def toMat4(self):			return Mat.fromScalar(4, 4, float(self.x))
+
+	def toFloat(self):			return Scalar(float(self.x))
+	def toInt(self):			return Scalar(int(self.x))
+	def toUint(self):			return Uint(int(self.x))
+	def toBool(self):			return Scalar(bool(self.x))
+
+	def getNumScalars(self):	return 1
+	def getScalars(self):		return [self.x]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bool"
+		elif isinstance(self.x, int):
+			return "int"
+		elif isinstance(self.x, float):
+			return "float"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ""
+
+	def __str__(self):
+		return str(self.x).lower()
+
+	def __float__(self):
+		return float(self.x)
+
+	def length(self):
+		return Scalar(abs(self.x))
+
+	def distance(self, v):
+		assert isinstance(v, Scalar)
+		return Scalar(abs(self.x - v.x))
+
+	def dot(self, v):
+		assert isinstance(v, Scalar)
+		return Scalar(self.x * v.x)
+
+	def normalize(self):
+		return Scalar(glslSign(self.x))
+
+	def abs(self):
+		if isinstance(self.x, bool):
+			return Scalar(self.x)
+		else:
+			return Scalar(abs(self.x))
+
+	def __neg__(self):
+		return Scalar(-self.x)
+
+	def __add__(self, val):
+		assert isinstance(val, Scalar)
+		return Scalar(self.x + val.x)
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			return Scalar(self.x * val.x)
+		elif isinstance(val, Vec2):
+			return Vec2(self.x * val.x, self.x * val.y)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x * val.x, self.x * val.y, self.x * val.z)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x * val.x, self.x * val.y, self.x * val.z, self.x * val.w)
+		else:
+			assert False
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Scalar(self.x / val.x)
+		elif isinstance(val, Vec2):
+			return Vec2(self.x / val.x, self.x / val.y)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x / val.x, self.x / val.y, self.x / val.z)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x / val.x, self.x / val.y, self.x / val.z, self.x / val.w)
+		else:
+			assert False
+
+class Uint(Scalar):
+	def __init__(self, x):
+		assert x >= 0
+		self.x = x
+
+	def typeString(self):
+		return "uint"
+
+	def abs(self):
+		return Scalar.abs(self).toUint()
+
+	def __neg__(self):
+		return Scalar.__neg__(self).toUint()
+
+	def __add__(self, val):
+		return Scalar.__add__(self, val).toUint()
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		return Scalar.__mul__(self, val).toUint()
+
+	def __div__(self, val):
+		return Scalar.__div__(self, val).toUint()
+
+class Vec(object):
+	@staticmethod
+	def fromScalarList(lst):
+		assert (len(lst) >= 1 and len(lst) <= 4)
+		if (len(lst) == 1):		return Scalar(lst[0])
+		elif (len(lst) == 2):	return Vec2(lst[0], lst[1])
+		elif (len(lst) == 3):	return Vec3(lst[0], lst[1], lst[2])
+		else:					return Vec4(lst[0], lst[1], lst[2], lst[3])
+
+	def isEqual(self, other):
+		assert isinstance(other, Vec);
+		return (self.getScalars() == other.getScalars())
+
+	def length(self):
+		return Scalar(math.sqrt(self.dot(self).x))
+
+	def normalize(self):
+		return self * Scalar(1.0 / self.length().x)
+
+	def swizzle(self, indexList):
+		inScalars = self.getScalars()
+		outScalars = map(lambda ndx: inScalars[ndx], indexList)
+		return Vec.fromScalarList(outScalars)
+
+	def __init__(self):
+		pass
+
+class Vec2(Vec):
+	def __init__(self, x, y):
+		assert(x.__class__ == y.__class__)
+		self.x = x
+		self.y = y
+
+	def applyUnary(self, func):			return Vec2(func(self.x), func(self.y))
+	def applyBinary(self, func, other):	return Vec2(func(self.x, other.x), func(self.y, other.y))
+
+	def expandVec(self, val):	return val.toVec2()
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.y)
+	def toVec3(self):			return Vec3(self.x, self.y, 0.0)
+	def toVec4(self):			return Vec4(self.x, self.y, 0.0, 0.0)
+	def toUVec2(self):			return UVec2(self.x, self.y)
+	def toUVec3(self):			return UVec3(self.x, self.y, 0.0)
+	def toUVec4(self):			return UVec4(self.x, self.y, 0.0, 0.0)
+	def toMat2(self):			return Mat2(float(self.x), 0.0, 0.0, float(self.y));
+
+	def toFloat(self):			return Vec2(float(self.x), float(self.y))
+	def toInt(self):			return Vec2(int(self.x), int(self.y))
+	def toUint(self):			return UVec2(int(self.x), int(self.y))
+	def toBool(self):			return Vec2(bool(self.x), bool(self.y))
+
+	def getNumScalars(self):	return 2
+	def getScalars(self):		return [self.x, self.y]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bvec2"
+		elif isinstance(self.x, int):
+			return "ivec2"
+		elif isinstance(self.x, float):
+			return "vec2"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ".xyxy"
+
+	def __str__(self):
+		if isinstance(self.x, bool):
+			return "bvec2(%s, %s)" % (str(self.x).lower(), str(self.y).lower())
+		elif isinstance(self.x, int):
+			return "ivec2(%i, %i)" % (self.x, self.y)
+		elif isinstance(self.x, float):
+			return "vec2(%s, %s)" % (self.x, self.y)
+		else:
+			assert False
+
+	def distance(self, v):
+		assert isinstance(v, Vec2)
+		return (self - v).length()
+
+	def dot(self, v):
+		assert isinstance(v, Vec2)
+		return Scalar(self.x*v.x + self.y*v.y)
+
+	def abs(self):
+		if isinstance(self.x, bool):
+			return Vec2(self.x, self.y)
+		else:
+			return Vec2(abs(self.x), abs(self.y))
+
+	def __neg__(self):
+		return Vec2(-self.x, -self.y)
+
+	def __add__(self, val):
+		if isinstance(val, Scalar):
+			return Vec2(self.x + val, self.y + val)
+		elif isinstance(val, Vec2):
+			return Vec2(self.x + val.x, self.y + val.y)
+		else:
+			assert False
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			val = val.toVec2()
+		assert isinstance(val, Vec2)
+		return Vec2(self.x * val.x, self.y * val.y)
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Vec2(self.x / val.x, self.y / val.x)
+		else:
+			assert isinstance(val, Vec2)
+			return Vec2(self.x / val.x, self.y / val.y)
+
+	def boolAny(self):	return Scalar(self.x or self.y)
+	def boolAll(self):	return Scalar(self.x and self.y)
+	def boolNot(self):	return Vec2(not self.x, not self.y)
+
+class UVec2(Vec2):
+	def __init__(self, x, y):
+		assert isinstance(x, int) and isinstance(y, int)
+		assert x >= 0 and y >= 0
+		Vec2.__init__(self, x, y)
+
+	def typeString(self):
+		return "uvec2"
+
+	def __str__(self):
+		return "uvec2(%i, %i)" % (self.x, self.y)
+
+	def abs(self):
+		return Vec2.abs(self).toUint()
+
+class Vec3(Vec):
+	def __init__(self, x, y, z):
+		assert((x.__class__ == y.__class__) and (x.__class__ == z.__class__))
+		self.x = x
+		self.y = y
+		self.z = z
+
+	def applyUnary(self, func):			return Vec3(func(self.x), func(self.y), func(self.z))
+	def applyBinary(self, func, other):	return Vec3(func(self.x, other.x), func(self.y, other.y), func(self.z, other.z))
+
+	def expandVec(self, val):	return val.toVec3()
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.y)
+	def toVec3(self):			return Vec3(self.x, self.y, self.z)
+	def toVec4(self):			return Vec4(self.x, self.y, self.z, 0.0)
+	def toUVec2(self):			return UVec2(self.x, self.y)
+	def toUVec3(self):			return UVec3(self.x, self.y, self.z)
+	def toUVec4(self):			return UVec4(self.x, self.y, self.z, 0.0)
+	def toMat3(self):			return Mat3(float(self.x), 0.0, 0.0,  0.0, float(self.y), 0.0,  0.0, 0.0, float(self.z));
+
+	def toFloat(self):			return Vec3(float(self.x), float(self.y), float(self.z))
+	def toInt(self):			return Vec3(int(self.x), int(self.y), int(self.z))
+	def toUint(self):			return UVec3(int(self.x), int(self.y), int(self.z))
+	def toBool(self):			return Vec3(bool(self.x), bool(self.y), bool(self.z))
+
+	def getNumScalars(self):	return 3
+	def getScalars(self):		return [self.x, self.y, self.z]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bvec3"
+		elif isinstance(self.x, int):
+			return "ivec3"
+		elif isinstance(self.x, float):
+			return "vec3"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ".xyzx"
+
+	def __str__(self):
+		if isinstance(self.x, bool):
+			return "bvec3(%s, %s, %s)" % (str(self.x).lower(), str(self.y).lower(), str(self.z).lower())
+		elif isinstance(self.x, int):
+			return "ivec3(%i, %i, %i)" % (self.x, self.y, self.z)
+		elif isinstance(self.x, float):
+			return "vec3(%s, %s, %s)" % (self.x, self.y, self.z)
+		else:
+			assert False
+
+	def distance(self, v):
+		assert isinstance(v, Vec3)
+		return (self - v).length()
+
+	def dot(self, v):
+		assert isinstance(v, Vec3)
+		return Scalar(self.x*v.x + self.y*v.y + self.z*v.z)
+
+	def cross(self, v):
+		assert isinstance(v, Vec3)
+		return Vec3(self.y*v.z - v.y*self.z,
+					self.z*v.x - v.z*self.x,
+					self.x*v.y - v.x*self.y)
+
+	def abs(self):
+		if isinstance(self.x, bool):
+			return Vec3(self.x, self.y, self.z)
+		else:
+			return Vec3(abs(self.x), abs(self.y), abs(self.z))
+
+	def __neg__(self):
+		return Vec3(-self.x, -self.y, -self.z)
+
+	def __add__(self, val):
+		if isinstance(val, Scalar):
+			return Vec3(self.x + val, self.y + val)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x + val.x, self.y + val.y, self.z + val.z)
+		else:
+			assert False
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			val = val.toVec3()
+		assert isinstance(val, Vec3)
+		return Vec3(self.x * val.x, self.y * val.y, self.z * val.z)
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Vec3(self.x / val.x, self.y / val.x, self.z / val.x)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x / val.x, self.y / val.y, self.z / val.z)
+		else:
+			assert False
+
+	def boolAny(self):	return Scalar(self.x or self.y or self.z)
+	def boolAll(self):	return Scalar(self.x and self.y and self.z)
+	def boolNot(self):	return Vec3(not self.x, not self.y, not self.z)
+
+class UVec3(Vec3):
+	def __init__(self, x, y, z):
+		assert isinstance(x, int) and isinstance(y, int) and isinstance(z, int)
+		assert x >= 0 and y >= 0 and z >= 0
+		Vec3.__init__(self, x, y, z)
+
+	def typeString(self):
+		return "uvec3"
+
+	def __str__(self):
+		return "uvec3(%i, %i, %i)" % (self.x, self.y, self.z)
+
+	def abs(self):
+		return Vec3.abs(self).toUint()
+
+class Vec4(Vec):
+	def __init__(self, x, y, z, w):
+		assert((x.__class__ == y.__class__) and (x.__class__ == z.__class__) and (x.__class__ == w.__class__))
+		self.x = x
+		self.y = y
+		self.z = z
+		self.w = w
+
+	def applyUnary(self, func):			return Vec4(func(self.x), func(self.y), func(self.z), func(self.w))
+	def applyBinary(self, func, other):	return Vec4(func(self.x, other.x), func(self.y, other.y), func(self.z, other.z), func(self.w, other.w))
+
+	def expandVec(self, val):	return val.toVec4()
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.y)
+	def toVec3(self):			return Vec3(self.x, self.y, self.z)
+	def toVec4(self):			return Vec4(self.x, self.y, self.z, self.w)
+	def toUVec2(self):			return UVec2(self.x, self.y)
+	def toUVec3(self):			return UVec3(self.x, self.y, self.z)
+	def toUVec4(self):			return UVec4(self.x, self.y, self.z, self.w)
+	def toMat2(self):			return Mat2(float(self.x), float(self.y), float(self.z), float(self.w))
+	def toMat4(self):			return Mat4(float(self.x), 0.0, 0.0, 0.0,  0.0, float(self.y), 0.0, 0.0,  0.0, 0.0, float(self.z), 0.0,  0.0, 0.0, 0.0, float(self.w));
+
+	def toFloat(self):			return Vec4(float(self.x), float(self.y), float(self.z), float(self.w))
+	def toInt(self):			return Vec4(int(self.x), int(self.y), int(self.z), int(self.w))
+	def toUint(self):			return UVec4(int(self.x), int(self.y), int(self.z), int(self.w))
+	def toBool(self):			return Vec4(bool(self.x), bool(self.y), bool(self.z), bool(self.w))
+
+	def getNumScalars(self):	return 4
+	def getScalars(self):		return [self.x, self.y, self.z, self.w]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bvec4"
+		elif isinstance(self.x, int):
+			return "ivec4"
+		elif isinstance(self.x, float):
+			return "vec4"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ""
+
+	def __str__(self):
+		if isinstance(self.x, bool):
+			return "bvec4(%s, %s, %s, %s)" % (str(self.x).lower(), str(self.y).lower(), str(self.z).lower(), str(self.w).lower())
+		elif isinstance(self.x, int):
+			return "ivec4(%i, %i, %i, %i)" % (self.x, self.y, self.z, self.w)
+		elif isinstance(self.x, float):
+			return "vec4(%s, %s, %s, %s)" % (self.x, self.y, self.z, self.w)
+		else:
+			assert False
+
+	def distance(self, v):
+		assert isinstance(v, Vec4)
+		return (self - v).length()
+
+	def dot(self, v):
+		assert isinstance(v, Vec4)
+		return Scalar(self.x*v.x + self.y*v.y + self.z*v.z + self.w*v.w)
+
+	def abs(self):
+		if isinstance(self.x, bool):
+			return Vec4(self.x, self.y, self.z, self.w)
+		else:
+			return Vec4(abs(self.x), abs(self.y), abs(self.z), abs(self.w))
+
+	def __neg__(self):
+		return Vec4(-self.x, -self.y, -self.z, -self.w)
+
+	def __add__(self, val):
+		if isinstance(val, Scalar):
+			return Vec3(self.x + val, self.y + val)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x + val.x, self.y + val.y, self.z + val.z, self.w + val.w)
+		else:
+			assert False
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			val = val.toVec4()
+		assert isinstance(val, Vec4)
+		return Vec4(self.x * val.x, self.y * val.y, self.z * val.z, self.w * val.w)
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Vec4(self.x / val.x, self.y / val.x, self.z / val.x, self.w / val.x)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x / val.x, self.y / val.y, self.z / val.z, self.w / val.w)
+		else:
+			assert False
+
+	def boolAny(self):	return Scalar(self.x or self.y or self.z or self.w)
+	def boolAll(self):	return Scalar(self.x and self.y and self.z and self.w)
+	def boolNot(self):	return Vec4(not self.x, not self.y, not self.z, not self.w)
+
+class UVec4(Vec4):
+	def __init__(self, x, y, z, w):
+		assert isinstance(x, int) and isinstance(y, int) and isinstance(z, int) and isinstance(w, int)
+		assert x >= 0 and y >= 0 and z >= 0 and w >= 0
+		Vec4.__init__(self, x, y, z, w)
+
+	def typeString(self):
+		return "uvec4"
+
+	def __str__(self):
+		return "uvec4(%i, %i, %i, %i)" % (self.x, self.y, self.z, self.w)
+
+	def abs(self):
+		return Vec4.abs(self).toUint()
+
+# \note Column-major storage.
+class Mat(object):
+	def __init__ (self, numCols, numRows, scalars):
+		assert len(scalars) == numRows*numCols
+		self.numCols	= numCols
+		self.numRows	= numRows
+		self.scalars	= scalars
+
+	@staticmethod
+	def fromScalar (numCols, numRows, scalar):
+		scalars = []
+		for col in range(0, numCols):
+			for row in range(0, numRows):
+				scalars.append(scalar if col == row else 0.0)
+		return Mat(numCols, numRows, scalars)
+
+	@staticmethod
+	def identity (numCols, numRows):
+		return Mat.fromScalar(numCols, numRows, 1.0)
+
+	def get (self, colNdx, rowNdx):
+		assert 0 <= colNdx and colNdx < self.numCols
+		assert 0 <= rowNdx and rowNdx < self.numRows
+		return self.scalars[colNdx*self.numRows + rowNdx]
+
+	def set (self, colNdx, rowNdx, scalar):
+		assert 0 <= colNdx and colNdx < self.numCols
+		assert 0 <= rowNdx and rowNdx < self.numRows
+		self.scalars[colNdx*self.numRows + rowNdx] = scalar
+
+	def toMatrix (self, numCols, numRows):
+		res = Mat.identity(numCols, numRows)
+		for col in range(0, min(self.numCols, numCols)):
+			for row in range(0, min(self.numRows, numRows)):
+				res.set(col, row, self.get(col, row))
+		return res
+
+	def toMat2 (self):		return self.toMatrix(2, 2)
+	def toMat2x3 (self):	return self.toMatrix(2, 3)
+	def toMat2x4 (self):	return self.toMatrix(2, 4)
+	def toMat3x2 (self):	return self.toMatrix(3, 2)
+	def toMat3 (self):		return self.toMatrix(3, 3)
+	def toMat3x4 (self):	return self.toMatrix(3, 4)
+	def toMat4x2 (self):	return self.toMatrix(4, 2)
+	def toMat4x3 (self):	return self.toMatrix(4, 3)
+	def toMat4 (self):		return self.toMatrix(4, 4)
+
+	def typeString(self):
+		if self.numRows == self.numCols:
+			return "mat%d" % self.numRows
+		else:
+			return "mat%dx%d" % (self.numCols, self.numRows)
+
+	def __str__(self):
+		return "%s(%s)" % (self.typeString(), ", ".join(["%s" % s for s in self.scalars]))
+
+	def isTypeEqual (self, other):
+		return isinstance(other, Mat) and self.numRows == other.numRows and self.numCols == other.numCols
+
+	def isEqual(self, other):
+		assert self.isTypeEqual(other)
+		return (self.scalars == other.scalars)
+
+	def compMul(self, val):
+		assert self.isTypeEqual(val)
+		return Mat(self.numRows, self.numCols, [self.scalars(i) * val.scalars(i) for i in range(self.numRows*self.numCols)])
+
+class Mat2(Mat):
+	def __init__(self, m00, m01, m10, m11):
+		Mat.__init__(self, 2, 2, [m00, m10, m01, m11])
+
+class Mat3(Mat):
+	def __init__(self, m00, m01, m02, m10, m11, m12, m20, m21, m22):
+		Mat.__init__(self, 3, 3, [m00, m10, m20,
+								  m01, m11, m21,
+								  m02, m12, m22])
+
+class Mat4(Mat):
+	def __init__(self, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33):
+		Mat.__init__(self, 4, 4, [m00, m10, m20, m30,
+								  m01, m11, m21, m31,
+								  m02, m12, m22, m32,
+								  m03, m13, m23, m33])
diff --git a/modules/gles3/stress/CMakeLists.txt b/modules/gles3/stress/CMakeLists.txt
new file mode 100644
index 0000000..437da2b
--- /dev/null
+++ b/modules/gles3/stress/CMakeLists.txt
@@ -0,0 +1,27 @@
+# dEQP-GLES3.stress
+
+set(DEQP_GLES3_STRESS_SRCS
+	es3sStressTests.hpp
+	es3sStressTests.cpp
+	es3sMemoryTests.hpp
+	es3sMemoryTests.cpp
+	es3sOcclusionQueryTests.hpp
+	es3sOcclusionQueryTests.cpp
+	es3sSyncTests.hpp
+	es3sSyncTests.cpp
+	es3sLongRunningTests.hpp
+	es3sLongRunningTests.cpp
+	es3sSpecialFloatTests.hpp
+	es3sSpecialFloatTests.cpp
+	es3sDrawTests.hpp
+	es3sDrawTests.cpp
+	es3sVertexArrayTests.hpp
+	es3sVertexArrayTests.cpp
+	es3sLongShaderTests.hpp
+	es3sLongShaderTests.cpp
+	es3sLongRunningShaderTests.cpp
+	es3sLongRunningShaderTests.hpp
+	)
+
+add_library(deqp-gles3-stress STATIC ${DEQP_GLES3_STRESS_SRCS})
+target_link_libraries(deqp-gles3-stress deqp-gl-shared glutil tcutil ${DEQP_GLES3_LIBRARIES})
diff --git a/modules/gles3/stress/es3sDrawTests.cpp b/modules/gles3/stress/es3sDrawTests.cpp
new file mode 100644
index 0000000..821ba92
--- /dev/null
+++ b/modules/gles3/stress/es3sDrawTests.cpp
@@ -0,0 +1,690 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Draw stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es3sDrawTests.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluStrUtil.hpp"
+#include "glsDrawTest.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+
+#include <set>
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+namespace
+{
+
+static const char* const s_vertexSource =		"#version 300 es\n"
+												"in highp vec4 a_position;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"}\n";
+static const char* const s_fragmentSource =		"#version 300 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;\n"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+												"}\n";
+
+class DrawInvalidRangeCase : public TestCase
+{
+public:
+						DrawInvalidRangeCase	(Context& ctx, const char* name, const char* desc, deUint32 min, deUint32 max, bool useLimitMin = false, bool useLimitMax = false);
+						~DrawInvalidRangeCase	(void);
+
+	void				init					(void);
+	void				deinit					(void);
+	IterateResult		iterate					(void);
+
+private:
+	const int			m_min;
+	const int			m_max;
+	const int			m_bufferedElements;
+	const int			m_numIndices;
+	const bool			m_useLimitMin;
+	const bool			m_useLimitMax;
+
+	deUint32			m_buffer;
+	deUint32			m_indexBuffer;
+	glu::ShaderProgram*	m_program;
+};
+
+DrawInvalidRangeCase::DrawInvalidRangeCase (Context& ctx, const char* name, const char* desc, deUint32 min, deUint32 max, bool useLimitMin, bool useLimitMax)
+	: TestCase				(ctx, name, desc)
+	, m_min					(min)
+	, m_max					(max)
+	, m_bufferedElements	(128)
+	, m_numIndices			(64)
+	, m_useLimitMin			(useLimitMin)
+	, m_useLimitMax			(useLimitMax)
+	, m_buffer				(0)
+	, m_indexBuffer			(0)
+	, m_program				(DE_NULL)
+{
+}
+
+DrawInvalidRangeCase::~DrawInvalidRangeCase (void)
+{
+	deinit();
+}
+
+void DrawInvalidRangeCase::init (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	std::vector<tcu::Vec4>	data	(m_bufferedElements); // !< some junk data to make sure buffer is really allocated
+	std::vector<deUint32>	indices	(m_numIndices);
+
+	for (int ndx = 0; ndx < m_numIndices; ++ndx)
+		indices[ndx] = ndx;
+
+	gl.genBuffers(1, &m_buffer);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);
+	gl.bufferData(GL_ARRAY_BUFFER, int(m_bufferedElements * sizeof(tcu::Vec4)), &data[0], GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "buffer gen");
+
+	gl.genBuffers(1, &m_indexBuffer);
+	gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
+	gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, int(m_numIndices * sizeof(deUint32)), &indices[0], GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "buffer gen");
+
+	m_program = 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("could not build program");
+	}
+}
+
+void DrawInvalidRangeCase::deinit (void)
+{
+	if (m_buffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buffer);
+		m_buffer = 0;
+	}
+
+	if (m_indexBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_indexBuffer);
+		m_indexBuffer = 0;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+DrawInvalidRangeCase::IterateResult DrawInvalidRangeCase::iterate (void)
+{
+	glu::CallLogWrapper		gl			(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	const deInt32			positionLoc = gl.glGetAttribLocation(m_program->getProgram(), "a_position");
+	tcu::Surface			dst			(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
+	glu::VertexArray		vao			(m_context.getRenderContext());
+
+	deInt64					indexLimit	= 0;
+	deUint32				min			= m_min;
+	deUint32				max			= m_max;
+
+	gl.enableLogging(true);
+
+	if (m_useLimitMin || m_useLimitMax)
+	{
+		gl.glGetInteger64v(GL_MAX_ELEMENT_INDEX, &indexLimit);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "query limit");
+	}
+
+	if (m_useLimitMin)
+	{
+		if ((deUint64)indexLimit > 0xFFFFFFFFULL)
+			min = 0xFFFFFFF0;
+		else
+			min = (deUint32)(indexLimit - 16);
+	}
+
+	if (m_useLimitMax)
+	{
+		if ((deUint64)indexLimit > 0xFFFFFFFFULL)
+			max = 0xFFFFFFFF;
+		else
+			max = (deUint32)indexLimit;
+	}
+
+	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	gl.glClear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup");
+
+	gl.glUseProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");
+
+	gl.glBindVertexArray(*vao);
+	gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
+
+	gl.glBindBuffer(GL_ARRAY_BUFFER, m_buffer);
+	gl.glEnableVertexAttribArray(positionLoc);
+	gl.glVertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set buffer");
+
+	gl.glDrawRangeElements(GL_POINTS, min, max, m_numIndices, GL_UNSIGNED_INT, DE_NULL);
+
+	// Indexing outside range is an error, but it doesnt need to be checked. Causes implementation-dependent behavior.
+	// Even if the indices are in range (m_min = 0), the specification allows partial processing of vertices in the range,
+	// which might cause access over buffer bounds. Causes implementation-dependent behavior.
+
+	// allow errors
+	{
+		const deUint32 error = gl.glGetError();
+
+		if (error != GL_NO_ERROR)
+			m_testCtx.getLog() << tcu::TestLog::Message << "Got error: " << glu::getErrorStr(error) << ", ignoring..." << tcu::TestLog::EndMessage;
+	}
+
+	// read pixels to wait for rendering
+	gl.glFinish();
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+static void genBasicSpec (gls::DrawTestSpec& spec, gls::DrawTestSpec::DrawMethod method)
+{
+	spec.apiType				= glu::ApiType::es(3,0);
+	spec.primitive				= gls::DrawTestSpec::PRIMITIVE_TRIANGLES;
+	spec.primitiveCount			= 5;
+	spec.drawMethod				= method;
+	spec.indexType				= gls::DrawTestSpec::INDEXTYPE_LAST;
+	spec.indexPointerOffset		= 0;
+	spec.indexStorage			= gls::DrawTestSpec::STORAGE_LAST;
+	spec.first					= 0;
+	spec.indexMin				= 0;
+	spec.indexMax				= 0;
+	spec.instanceCount			= 1;
+
+	spec.attribs.resize(2);
+
+	spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+	spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+	spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+	spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+	spec.attribs[0].componentCount			= 4;
+	spec.attribs[0].offset					= 0;
+	spec.attribs[0].stride					= 0;
+	spec.attribs[0].normalize				= false;
+	spec.attribs[0].instanceDivisor			= 0;
+	spec.attribs[0].useDefaultAttribute		= false;
+
+	spec.attribs[1].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+	spec.attribs[1].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+	spec.attribs[1].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+	spec.attribs[1].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+	spec.attribs[1].componentCount			= 2;
+	spec.attribs[1].offset					= 0;
+	spec.attribs[1].stride					= 0;
+	spec.attribs[1].normalize				= false;
+	spec.attribs[1].instanceDivisor			= 0;
+	spec.attribs[1].useDefaultAttribute		= false;
+}
+
+class IndexGroup : public TestCaseGroup
+{
+public:
+									IndexGroup		(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~IndexGroup		(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+IndexGroup::IndexGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+IndexGroup::~IndexGroup (void)
+{
+}
+
+void IndexGroup::init (void)
+{
+	struct IndexTest
+	{
+		gls::DrawTestSpec::Storage		storage;
+		gls::DrawTestSpec::IndexType	type;
+		bool							aligned;
+		int								offsets[3];
+	};
+
+	const IndexTest tests[] =
+	{
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_BYTE,	true,	{ 0, 1, -1 } },
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_SHORT,	true,	{ 0, 2, -1 } },
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_INT,	true,	{ 0, 4, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_SHORT,	false,	{ 1, 3, -1 } },
+		{ gls::DrawTestSpec::STORAGE_USER,		gls::DrawTestSpec::INDEXTYPE_INT,	false,	{ 2, 3, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_BYTE,	true,	{ 0, 1, -1 } },
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_SHORT,	true,	{ 0, 2, -1 } },
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_INT,	true,	{ 0, 4, -1 } },
+
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_SHORT,	false,	{ 1, 3, -1 } },
+		{ gls::DrawTestSpec::STORAGE_BUFFER,	gls::DrawTestSpec::INDEXTYPE_INT,	false,	{ 2, 3, -1 } },
+	};
+
+	gls::DrawTestSpec spec;
+
+	tcu::TestCaseGroup* const	userPtrGroup			= new tcu::TestCaseGroup(m_testCtx, "user_ptr", "user pointer");
+	tcu::TestCaseGroup* const	unalignedUserPtrGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_user_ptr", "unaligned user pointer");
+	tcu::TestCaseGroup* const	bufferGroup				= new tcu::TestCaseGroup(m_testCtx, "buffer", "buffer");
+	tcu::TestCaseGroup* const	unalignedBufferGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_buffer", "unaligned buffer");
+	const bool					isRangedMethod			= (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED || m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX);
+
+	genBasicSpec(spec, m_method);
+
+	this->addChild(userPtrGroup);
+	this->addChild(unalignedUserPtrGroup);
+	this->addChild(bufferGroup);
+	this->addChild(unalignedBufferGroup);
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(tests); ++testNdx)
+	{
+		const IndexTest&				indexTest	= tests[testNdx];
+		tcu::TestCaseGroup*				group		= (indexTest.storage == gls::DrawTestSpec::STORAGE_USER) ? ((indexTest.aligned) ? (userPtrGroup) : (unalignedUserPtrGroup)) : ((indexTest.aligned) ? (bufferGroup) : (unalignedBufferGroup));
+
+		const std::string				name		= std::string("index_") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
+		const std::string				desc		= std::string("index ") + gls::DrawTestSpec::indexTypeToString(indexTest.type) + " in " + gls::DrawTestSpec::storageToString(indexTest.storage);
+		de::MovePtr<gls::DrawTest>		test		(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str()));
+
+		spec.indexType			= indexTest.type;
+		spec.indexStorage		= indexTest.storage;
+
+		if (isRangedMethod)
+		{
+			spec.indexMin = 0;
+			spec.indexMax = 55;
+		}
+
+		for (int iterationNdx = 0; iterationNdx < DE_LENGTH_OF_ARRAY(indexTest.offsets) && indexTest.offsets[iterationNdx] != -1; ++iterationNdx)
+		{
+			const std::string iterationDesc = std::string("offset ") + de::toString(indexTest.offsets[iterationNdx]);
+			spec.indexPointerOffset	= indexTest.offsets[iterationNdx];
+			test->addIteration(spec, iterationDesc.c_str());
+		}
+
+		if (spec.isCompatibilityTest() == gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET ||
+			spec.isCompatibilityTest() == gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+			group->addChild(test.release());
+	}
+}
+
+class MethodGroup : public TestCaseGroup
+{
+public:
+									MethodGroup			(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~MethodGroup		(void);
+
+	void							init				(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+MethodGroup::MethodGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+MethodGroup::~MethodGroup (void)
+{
+}
+
+void MethodGroup::init (void)
+{
+	const bool indexed		= (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS) || (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED) || (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED);
+
+	DE_ASSERT(indexed);
+	DE_UNREF(indexed);
+
+	this->addChild(new IndexGroup(m_context, "indices", "Index tests", m_method));
+}
+
+class RandomGroup : public TestCaseGroup
+{
+public:
+									RandomGroup		(Context& context, const char* name, const char* descr);
+									~RandomGroup	(void);
+
+	void							init			(void);
+};
+
+template <int SIZE>
+struct UniformWeightArray
+{
+	float weights[SIZE];
+
+	UniformWeightArray (void)
+	{
+		for (int i=0; i<SIZE; ++i)
+			weights[i] = 1.0f;
+	}
+};
+
+RandomGroup::RandomGroup (Context& context, const char* name, const char* descr)
+	: TestCaseGroup	(context, name, descr)
+{
+}
+
+RandomGroup::~RandomGroup (void)
+{
+}
+
+void RandomGroup::init (void)
+{
+	const int	numAttempts				= 300;
+
+	const int	attribCounts[]			= { 1, 2, 5 };
+	const float	attribWeights[]			= { 30, 10, 1 };
+	const int	primitiveCounts[]		= { 1, 5, 64 };
+	const float	primitiveCountWeights[]	= { 20, 10, 1 };
+	const int	indexOffsets[]			= { 0, 7, 13 };
+	const float	indexOffsetWeights[]	= { 20, 20, 1 };
+	const int	firsts[]				= { 0, 7, 13 };
+	const float	firstWeights[]			= { 20, 20, 1 };
+	const int	instanceCounts[]		= { 1, 2, 16, 17 };
+	const float	instanceWeights[]		= { 20, 10, 5, 1 };
+	const int	indexMins[]				= { 0, 1, 3, 8 };
+	const int	indexMaxs[]				= { 4, 8, 128, 257 };
+	const float	indexWeights[]			= { 50, 50, 50, 50 };
+	const int	offsets[]				= { 0, 1, 5, 12 };
+	const float	offsetWeights[]			= { 50, 10, 10, 10 };
+	const int	strides[]				= { 0, 7, 16, 17 };
+	const float	strideWeights[]			= { 50, 10, 10, 10 };
+	const int	instanceDivisors[]		= { 0, 1, 3, 129 };
+	const float	instanceDivisorWeights[]= { 70, 30, 10, 10 };
+
+	gls::DrawTestSpec::Primitive primitives[] =
+	{
+		gls::DrawTestSpec::PRIMITIVE_POINTS,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINES,
+		gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINE_LOOP
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(primitives)> primitiveWeights;
+
+	gls::DrawTestSpec::DrawMethod drawMethods[] =
+	{
+		gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INSTANCED,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(drawMethods)> drawMethodWeights;
+
+	gls::DrawTestSpec::IndexType indexTypes[] =
+	{
+		gls::DrawTestSpec::INDEXTYPE_BYTE,
+		gls::DrawTestSpec::INDEXTYPE_SHORT,
+		gls::DrawTestSpec::INDEXTYPE_INT,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(indexTypes)> indexTypeWeights;
+
+	gls::DrawTestSpec::Storage storages[] =
+	{
+		gls::DrawTestSpec::STORAGE_USER,
+		gls::DrawTestSpec::STORAGE_BUFFER,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(storages)> storageWeights;
+
+	gls::DrawTestSpec::InputType inputTypes[] =
+	{
+		gls::DrawTestSpec::INPUTTYPE_FLOAT,
+		gls::DrawTestSpec::INPUTTYPE_FIXED,
+		gls::DrawTestSpec::INPUTTYPE_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_SHORT,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT,
+		gls::DrawTestSpec::INPUTTYPE_INT,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT,
+		gls::DrawTestSpec::INPUTTYPE_HALF,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		gls::DrawTestSpec::INPUTTYPE_INT_2_10_10_10,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(inputTypes)> inputTypeWeights;
+
+	gls::DrawTestSpec::OutputType outputTypes[] =
+	{
+		gls::DrawTestSpec::OUTPUTTYPE_FLOAT,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC4,
+		gls::DrawTestSpec::OUTPUTTYPE_INT,
+		gls::DrawTestSpec::OUTPUTTYPE_UINT,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC4,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC4,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(outputTypes)> outputTypeWeights;
+
+	gls::DrawTestSpec::Usage usages[] =
+	{
+		gls::DrawTestSpec::USAGE_DYNAMIC_DRAW,
+		gls::DrawTestSpec::USAGE_STATIC_DRAW,
+		gls::DrawTestSpec::USAGE_STREAM_DRAW,
+		gls::DrawTestSpec::USAGE_STREAM_READ,
+		gls::DrawTestSpec::USAGE_STREAM_COPY,
+		gls::DrawTestSpec::USAGE_STATIC_READ,
+		gls::DrawTestSpec::USAGE_STATIC_COPY,
+		gls::DrawTestSpec::USAGE_DYNAMIC_READ,
+		gls::DrawTestSpec::USAGE_DYNAMIC_COPY,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(usages)> usageWeights;
+
+	std::set<deUint32>	insertedHashes;
+	size_t				insertedCount = 0;
+
+	for (int ndx = 0; ndx < numAttempts; ++ndx)
+	{
+		de::Random random(0xc551393 + ndx); // random does not depend on previous cases
+
+		int					attributeCount = random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(attribCounts), DE_ARRAY_END(attribCounts), attribWeights);
+		gls::DrawTestSpec	spec;
+
+		spec.apiType				= glu::ApiType::es(3,0);
+		spec.primitive				= random.chooseWeighted<gls::DrawTestSpec::Primitive>	(DE_ARRAY_BEGIN(primitives),		DE_ARRAY_END(primitives),		primitiveWeights.weights);
+		spec.primitiveCount			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(primitiveCounts),	DE_ARRAY_END(primitiveCounts),	primitiveCountWeights);
+		spec.drawMethod				= random.chooseWeighted<gls::DrawTestSpec::DrawMethod>	(DE_ARRAY_BEGIN(drawMethods),		DE_ARRAY_END(drawMethods),		drawMethodWeights.weights);
+		spec.indexType				= random.chooseWeighted<gls::DrawTestSpec::IndexType>	(DE_ARRAY_BEGIN(indexTypes),		DE_ARRAY_END(indexTypes),		indexTypeWeights.weights);
+		spec.indexPointerOffset		= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexOffsets),		DE_ARRAY_END(indexOffsets),		indexOffsetWeights);
+		spec.indexStorage			= random.chooseWeighted<gls::DrawTestSpec::Storage>		(DE_ARRAY_BEGIN(storages),			DE_ARRAY_END(storages),			storageWeights.weights);
+		spec.first					= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(firsts),			DE_ARRAY_END(firsts),			firstWeights);
+		spec.indexMin				= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexMins),			DE_ARRAY_END(indexMins),		indexWeights);
+		spec.indexMax				= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexMaxs),			DE_ARRAY_END(indexMaxs),		indexWeights);
+		spec.instanceCount			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(instanceCounts),	DE_ARRAY_END(instanceCounts),	instanceWeights);
+
+		// check spec is legal
+		if (!spec.valid())
+			continue;
+
+		for (int attrNdx = 0; attrNdx < attributeCount;)
+		{
+			bool valid;
+			gls::DrawTestSpec::AttributeSpec attribSpec;
+
+			attribSpec.inputType			= random.chooseWeighted<gls::DrawTestSpec::InputType>	(DE_ARRAY_BEGIN(inputTypes),		DE_ARRAY_END(inputTypes),		inputTypeWeights.weights);
+			attribSpec.outputType			= random.chooseWeighted<gls::DrawTestSpec::OutputType>	(DE_ARRAY_BEGIN(outputTypes),		DE_ARRAY_END(outputTypes),		outputTypeWeights.weights);
+			attribSpec.storage				= random.chooseWeighted<gls::DrawTestSpec::Storage>		(DE_ARRAY_BEGIN(storages),			DE_ARRAY_END(storages),			storageWeights.weights);
+			attribSpec.usage				= random.chooseWeighted<gls::DrawTestSpec::Usage>		(DE_ARRAY_BEGIN(usages),			DE_ARRAY_END(usages),			usageWeights.weights);
+			attribSpec.componentCount		= random.getInt(1, 4);
+			attribSpec.offset				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), offsetWeights);
+			attribSpec.stride				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(strides), DE_ARRAY_END(strides), strideWeights);
+			attribSpec.normalize			= random.getBool();
+			attribSpec.instanceDivisor		= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(instanceDivisors), DE_ARRAY_END(instanceDivisors), instanceDivisorWeights);
+			attribSpec.useDefaultAttribute	= random.getBool();
+
+			// check spec is legal
+			valid = attribSpec.valid(spec.apiType);
+
+			// we do not want interleaved elements. (Might result in some weird floating point values)
+			if (attribSpec.stride && attribSpec.componentCount * gls::DrawTestSpec::inputTypeSize(attribSpec.inputType) > attribSpec.stride)
+				valid = false;
+
+			// try again if not valid
+			if (valid)
+			{
+				spec.attribs.push_back(attribSpec);
+				++attrNdx;
+			}
+		}
+
+		// Do not collapse all vertex positions to a single positions
+		if (spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+			spec.attribs[0].instanceDivisor = 0;
+
+		// Is render result meaningful?
+		{
+			// Only one vertex
+			if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && spec.indexMin == spec.indexMax && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+			if (spec.attribs[0].useDefaultAttribute && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+
+			// Triangle only on one axis
+			if (spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLES || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP)
+			{
+				if (spec.attribs[0].componentCount == 1)
+					continue;
+				if (spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_FLOAT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_INT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_UINT)
+					continue;
+				if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && (spec.indexMax - spec.indexMin) < 2)
+					continue;
+			}
+		}
+
+		// Add case
+		{
+			deUint32 hash = spec.hash();
+			for (int attrNdx = 0; attrNdx < attributeCount; ++attrNdx)
+				hash = (hash << 2) ^ (deUint32)spec.attribs[attrNdx].hash();
+
+			if (insertedHashes.find(hash) == insertedHashes.end())
+			{
+				// Only unaligned cases
+				if (spec.isCompatibilityTest() == gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET ||
+					spec.isCompatibilityTest() == gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+					this->addChild(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), spec, de::toString(insertedCount).c_str(), spec.getDesc().c_str()));
+				insertedHashes.insert(hash);
+
+				++insertedCount;
+			}
+		}
+	}
+}
+
+} // anonymous
+
+DrawTests::DrawTests (Context& context)
+	: TestCaseGroup(context, "draw", "Draw stress tests")
+{
+}
+
+DrawTests::~DrawTests (void)
+{
+}
+
+void DrawTests::init (void)
+{
+	tcu::TestCaseGroup* const unalignedGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_data", "Test with unaligned data");
+	tcu::TestCaseGroup* const drawRangeGroup	= new tcu::TestCaseGroup(m_testCtx, "draw_range_elements", "Test drawRangeElements");
+
+	addChild(unalignedGroup);
+	addChild(drawRangeGroup);
+
+	// .unaligned_data
+	{
+		const gls::DrawTestSpec::DrawMethod basicMethods[] =
+		{
+			// gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS,
+			gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS,
+			// gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INSTANCED,
+			gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED,
+			gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED,
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(basicMethods); ++ndx)
+		{
+			const std::string name = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
+			const std::string desc = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
+
+			unalignedGroup->addChild(new MethodGroup(m_context, name.c_str(), desc.c_str(), basicMethods[ndx]));
+		}
+
+		// Random
+
+		unalignedGroup->addChild(new RandomGroup(m_context, "random", "random draw commands."));
+	}
+
+	// .draw_range_elements
+	{
+		// use a larger range than the buffer size is
+		drawRangeGroup->addChild(new DrawInvalidRangeCase(m_context, "range_max_over_bounds",							"Range over buffer bounds",	0x00000000,	0x00210000));
+		drawRangeGroup->addChild(new DrawInvalidRangeCase(m_context, "range_max_over_bounds_near_signed_wrap",			"Range over buffer bounds",	0x00000000,	0x7FFFFFFF));
+		drawRangeGroup->addChild(new DrawInvalidRangeCase(m_context, "range_max_over_bounds_near_unsigned_wrap",		"Range over buffer bounds",	0x00000000,	0xFFFFFFFF));
+		drawRangeGroup->addChild(new DrawInvalidRangeCase(m_context, "range_max_over_bounds_near_max",					"Range over buffer bounds",	0x00000000, 0x00000000, false, true));
+
+		drawRangeGroup->addChild(new DrawInvalidRangeCase(m_context, "range_min_max_over_bounds",						"Range over buffer bounds",	0x00200000,	0x00210000));
+		drawRangeGroup->addChild(new DrawInvalidRangeCase(m_context, "range_min_max_over_bounds_near_signed_wrap",		"Range over buffer bounds",	0x7FFFFFF0,	0x7FFFFFFF));
+		drawRangeGroup->addChild(new DrawInvalidRangeCase(m_context, "range_min_max_over_bounds_near_unsigned_wrap",	"Range over buffer bounds",	0xFFFFFFF0,	0xFFFFFFFF));
+		drawRangeGroup->addChild(new DrawInvalidRangeCase(m_context, "range_min_max_over_bounds_near_max",				"Range over buffer bounds",	0x00000000, 0x00000000, true, true));
+	}
+}
+
+} // Stress
+} // gles3
+} // deqp
diff --git a/modules/gles3/stress/es3sDrawTests.hpp b/modules/gles3/stress/es3sDrawTests.hpp
new file mode 100644
index 0000000..0607416
--- /dev/null
+++ b/modules/gles3/stress/es3sDrawTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3SDRAWTESTS_HPP
+#define _ES3SDRAWTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Draw stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+class DrawTests : public TestCaseGroup
+{
+public:
+					DrawTests		(Context& testCtx);
+					~DrawTests		(void);
+
+	void			init			(void);
+
+private:
+					DrawTests		(const DrawTests& other);
+	DrawTests&		operator=		(const DrawTests& other);
+};
+
+} // Stress
+} // gles3
+} // deqp
+
+#endif // _ES3SDRAWTESTS_HPP
diff --git a/modules/gles3/stress/es3sLongRunningShaderTests.cpp b/modules/gles3/stress/es3sLongRunningShaderTests.cpp
new file mode 100644
index 0000000..7fae6ce
--- /dev/null
+++ b/modules/gles3/stress/es3sLongRunningShaderTests.cpp
@@ -0,0 +1,371 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Long running shader stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3sLongRunningShaderTests.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluDrawUtil.hpp"
+
+#include "tcuRenderTarget.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+using tcu::TestLog;
+using tcu::Vec2;
+using std::vector;
+
+namespace
+{
+
+enum LoopType
+{
+	LOOPTYPE_FOR = 0,
+	LOOPTYPE_WHILE,
+	LOOPTYPE_DO_WHILE,
+
+	LOOPTYPE_LAST
+};
+
+enum IterCountType
+{
+	ITERCOUNTTYPE_STATIC = 0,
+	ITERCOUNTTYPE_UNIFORM,
+	ITERCOUNTTYPE_DYNAMIC,
+
+	ITERCOUNTTYPE_LAST
+};
+
+class LongRunningShaderCase : public TestCase
+{
+public:
+	struct Params
+	{
+		const char*			name;
+		const char*			description;
+		glu::ShaderType		shaderType;
+		LoopType			loopType;
+		IterCountType		iterCountType;
+		int					numInvocations;
+		int					minLoopIterCount;
+		int					maxLoopIterCount;
+	};
+
+								LongRunningShaderCase		(Context& context, const Params* params);
+								~LongRunningShaderCase		(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								LongRunningShaderCase		(const LongRunningShaderCase&);
+	LongRunningShaderCase&		operator=					(const LongRunningShaderCase&);
+
+	static glu::ProgramSources	genSources					(const Params& params);
+	static deUint32				getSeed						(const Params& params);
+
+	const Params* const			m_params;
+	const int					m_numCaseIters;
+
+	glu::ShaderProgram*			m_program;
+	int							m_caseIterNdx;
+};
+
+LongRunningShaderCase::LongRunningShaderCase (Context& context, const Params* params)
+	: TestCase			(context, params->name, params->description)
+	, m_params			(params)
+	, m_numCaseIters	(5)
+	, m_program			(DE_NULL)
+	, m_caseIterNdx		(0)
+{
+}
+
+LongRunningShaderCase::~LongRunningShaderCase (void)
+{
+	deinit();
+}
+
+glu::ProgramSources LongRunningShaderCase::genSources (const Params& params)
+{
+	const bool			isVertCase		= params.shaderType == glu::SHADERTYPE_VERTEX;
+	std::ostringstream	vert, frag;
+
+	vert << "#version 300 es\n"
+		 << "in highp vec2 a_position;\n";
+
+	frag << "#version 300 es\n";
+
+	if (params.iterCountType == ITERCOUNTTYPE_DYNAMIC)
+	{
+		vert << "in highp int a_iterCount;\n";
+		if (!isVertCase)
+		{
+			vert << "flat out highp int v_iterCount;\n";
+			frag << "flat in highp int v_iterCount;\n";
+		}
+	}
+	else if (params.iterCountType == ITERCOUNTTYPE_UNIFORM)
+		(isVertCase ? vert : frag) << "uniform highp int u_iterCount;\n";
+
+	if (isVertCase)
+	{
+		vert << "out mediump vec4 v_color;\n";
+		frag << "in mediump vec4 v_color;\n";
+	}
+
+	frag << "out mediump vec4 o_color;\n";
+
+	vert << "\nvoid main (void)\n{\n"
+		 << "	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		 << "	gl_PointSize = 1.0;\n";
+
+	if (!isVertCase && params.iterCountType == ITERCOUNTTYPE_DYNAMIC)
+		vert << "	v_iterCount = a_iterCount;\n";
+
+	frag << "\nvoid main (void)\n{\n";
+
+	{
+		const std::string	iterCount	= params.iterCountType == ITERCOUNTTYPE_DYNAMIC ? (isVertCase ? "a_iterCount" : "v_iterCount")	:
+										  params.iterCountType == ITERCOUNTTYPE_UNIFORM ? "u_iterCount"									:
+										  params.iterCountType == ITERCOUNTTYPE_STATIC	? de::toString(params.maxLoopIterCount)			: "<invalid>";
+		const char* const	body		= "color = cos(sin(color*1.25)*0.8);";
+		std::ostringstream&	op			= isVertCase ? vert : frag;
+
+		op << "	mediump vec4 color = " << (isVertCase ? "a_position.xyxy" : "gl_FragCoord") << ";\n";
+
+		if (params.loopType == LOOPTYPE_FOR)
+		{
+			op << "	for (highp int i = 0; i < " << iterCount << " || " << iterCount << " < 0; ++i)\n"
+			   << "		" << body << "\n";
+		}
+		else if (params.loopType == LOOPTYPE_WHILE)
+		{
+			op << "	highp int i = 0;\n"
+			   << "	while (i < " << iterCount << " || " << iterCount << " < 0) {\n"
+			   << "		i += 1;\n"
+			   << "		" << body << "\n"
+			   << "	}\n";
+		}
+		else
+		{
+			DE_ASSERT(params.loopType == LOOPTYPE_DO_WHILE);
+			op << "	highp int i = 0;\n"
+			   << "	do {\n"
+			   << "		i += 1;\n"
+			   << "		" << body << "\n"
+			   << "	} while (i <= " << iterCount << " || " << iterCount << " < 0);\n";
+		}
+	}
+
+	if (isVertCase)
+	{
+		vert << "	v_color = color;\n";
+		frag << "	o_color = v_color;\n";
+	}
+	else
+		frag << "	o_color = color;\n";
+
+	vert << "}\n";
+	frag << "}\n";
+
+	return glu::ProgramSources() << glu::VertexSource(vert.str()) << glu::FragmentSource(frag.str());
+}
+
+void LongRunningShaderCase::init (void)
+{
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), genSources(*m_params));
+
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+	{
+		deinit();
+		TCU_FAIL("Failed to compile shader program");
+	}
+
+	m_caseIterNdx = 0;
+
+	if (m_params->iterCountType != ITERCOUNTTYPE_STATIC)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Loop iteration counts in range: [" << m_params->minLoopIterCount
+											   << ", " << m_params->maxLoopIterCount << "]"
+						   << TestLog::EndMessage;
+	}
+
+	m_testCtx.getLog() << TestLog::Message << "Number of vertices and fragments: " << m_params->numInvocations << TestLog::EndMessage;
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); // Test will pass or timeout, unless driver/device crashes.
+}
+
+void LongRunningShaderCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+void genPositions (const tcu::RenderTarget& renderTarget, int numPoints, Vec2* positions)
+{
+	const int	width		= renderTarget.getWidth();
+	const int	height		= renderTarget.getHeight();
+
+	if (width*height < numPoints)
+		throw tcu::NotSupportedError("Too small viewport to fit all test points");
+
+	for (int pointNdx = 0; pointNdx < numPoints; pointNdx++)
+	{
+		const int		xi		= pointNdx % width;
+		const int		yi		= pointNdx / height;
+		const float		xf		= 2.0f * ((float(xi) + 0.5f) / float(width)) - 1.0f;
+		const float		yf		= 2.0f * ((float(yi) + 0.5f) / float(height)) - 1.0f;
+
+		positions[pointNdx] = Vec2(xf, yf);
+	}
+}
+
+deUint32 LongRunningShaderCase::getSeed (const Params& params)
+{
+	const deUint32	seed	= deStringHash(params.name)
+							^ deInt32Hash(params.shaderType)
+							^ deInt32Hash(params.loopType)
+							^ deInt32Hash(params.iterCountType)
+							^ deInt32Hash(params.minLoopIterCount)
+							^ deInt32Hash(params.maxLoopIterCount)
+							^ deInt32Hash(params.numInvocations);
+	return seed;
+}
+
+LongRunningShaderCase::IterateResult LongRunningShaderCase::iterate (void)
+{
+	const glw::Functions&			gl				= m_context.getRenderContext().getFunctions();
+	de::Random						rnd				(getSeed(*m_params));
+	vector<Vec2>					positions		(m_params->numInvocations);
+	vector<int>						iterCounts		(m_params->iterCountType == ITERCOUNTTYPE_DYNAMIC ? m_params->numInvocations : 1);
+	vector<glu::VertexArrayBinding>	vertexArrays;
+
+	vertexArrays.push_back(glu::va::Float("a_position", 2, (int)positions.size(), 0, positions[0].getPtr()));
+	if (m_params->iterCountType == ITERCOUNTTYPE_DYNAMIC)
+		vertexArrays.push_back(glu::va::Int32("a_iterCount", 1, (int)iterCounts.size(), 0, &iterCounts[0]));
+
+	genPositions(m_context.getRenderTarget(), (int)positions.size(), &positions[0]);
+
+	for (vector<int>::iterator i = iterCounts.begin(); i != iterCounts.end(); ++i)
+		*i = rnd.getInt(m_params->minLoopIterCount, m_params->maxLoopIterCount);
+
+	gl.useProgram(m_program->getProgram());
+
+	if (m_params->iterCountType == ITERCOUNTTYPE_UNIFORM)
+		gl.uniform1i(gl.getUniformLocation(m_program->getProgram(), "u_iterCount"), iterCounts[0]);
+
+	glu::draw(m_context.getRenderContext(), m_program->getProgram(),
+			  (int)vertexArrays.size(), &vertexArrays[0],
+			  glu::pr::Points(m_params->numInvocations));
+
+	m_caseIterNdx += 1;
+	return (m_caseIterNdx < m_numCaseIters) ? CONTINUE : STOP;
+}
+
+} // anonymous
+
+LongRunningShaderTests::LongRunningShaderTests (Context& context)
+	: TestCaseGroup(context, "long_running_shaders",	"Long-running shader stress tests")
+{
+}
+
+LongRunningShaderTests::~LongRunningShaderTests (void)
+{
+}
+
+void LongRunningShaderTests::init (void)
+{
+	const int	numInvocations	= 4096;
+	const int	shortLoopMin	= 5;
+	const int	shortLoopMax	= 10;
+	const int	mediumLoopMin	= 10000;
+	const int	mediumLoopMax	= 50000;
+	const int	longLoopMin		= 100000;
+	const int	longLoopMax		= 500000;
+
+	static const LongRunningShaderCase::Params s_cases[] =
+	{
+		{ "short_for_vertex",					"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
+		{ "short_for_fragment",					"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
+		{ "short_while_vertex",					"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
+		{ "short_while_fragment",				"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
+		{ "short_do_while_vertex",				"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
+		{ "short_do_while_fragment",			"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
+
+		{ "medium_static_for_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_STATIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
+		{ "medium_static_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_WHILE,		ITERCOUNTTYPE_STATIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
+		{ "medium_uniform_do_while_vertex",		"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_UNIFORM,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
+		{ "medium_uniform_for_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_FOR,		ITERCOUNTTYPE_UNIFORM,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
+
+		{ "medium_dynamic_for_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
+		{ "medium_dynamic_for_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
+		{ "medium_dynamic_while_vertex",		"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
+		{ "medium_dynamic_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
+		{ "medium_dynamic_do_while_vertex",		"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
+		{ "medium_dynamic_do_while_fragment",	"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
+
+		{ "long_static_while_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_WHILE,		ITERCOUNTTYPE_STATIC,	numInvocations,	longLoopMin,	longLoopMax		},
+		{ "long_static_do_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_STATIC,	numInvocations,	longLoopMin,	longLoopMax		},
+		{ "long_uniform_for_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_UNIFORM,	numInvocations,	longLoopMin,	longLoopMax		},
+		{ "long_uniform_do_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_UNIFORM,	numInvocations,	longLoopMin,	longLoopMax		},
+
+		{ "long_dynamic_for_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
+		{ "long_dynamic_for_fragment",			"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
+		{ "long_dynamic_while_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
+		{ "long_dynamic_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
+		{ "long_dynamic_do_while_vertex",		"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
+		{ "long_dynamic_do_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
+
+		{ "infinite_for_vertex",				"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
+		{ "infinite_for_fragment",				"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
+		{ "infinite_while_vertex",				"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
+		{ "infinite_while_fragment",			"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
+		{ "infinite_do_while_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
+		{ "infinite_do_while_fragment",			"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_cases); ndx++)
+		addChild(new LongRunningShaderCase(m_context, &s_cases[ndx]));
+}
+
+} // Stress
+} // gles3
+} // deqp
diff --git a/modules/gles3/stress/es3sLongRunningShaderTests.hpp b/modules/gles3/stress/es3sLongRunningShaderTests.hpp
new file mode 100644
index 0000000..5baa194
--- /dev/null
+++ b/modules/gles3/stress/es3sLongRunningShaderTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3SLONGRUNNINGSHADERTESTS_HPP
+#define _ES3SLONGRUNNINGSHADERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Long running shader stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+class LongRunningShaderTests : public TestCaseGroup
+{
+public:
+								LongRunningShaderTests	(Context& context);
+								~LongRunningShaderTests	(void);
+
+	void						init					(void);
+
+private:
+								LongRunningShaderTests	(const LongRunningShaderTests&);
+	LongRunningShaderTests&		operator=				(const LongRunningShaderTests&);
+};
+
+} // Stress
+} // gles3
+} // deqp
+
+#endif // _ES3SLONGRUNNINGSHADERTESTS_HPP
diff --git a/modules/gles3/stress/es3sLongRunningTests.cpp b/modules/gles3/stress/es3sLongRunningTests.cpp
new file mode 100644
index 0000000..c8c8e4f
--- /dev/null
+++ b/modules/gles3/stress/es3sLongRunningTests.cpp
@@ -0,0 +1,357 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Long-running stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3sLongRunningTests.hpp"
+#include "glsLongStressCase.hpp"
+#include "glsLongStressTestUtil.hpp"
+#include "glwEnums.hpp"
+
+#include <string>
+
+using std::string;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+LongRunningTests::LongRunningTests (Context& context)
+	: TestCaseGroup(context, "long", "Long-running stress tests")
+{
+}
+
+LongRunningTests::~LongRunningTests (void)
+{
+}
+
+void LongRunningTests::init (void)
+{
+	static const int								Mi			= 1<<20;
+	const gls::LongStressTestUtil::ProgramLibrary	progLib		(glu::GLSL_VERSION_300_ES);
+
+	typedef gls::LongStressCase::FeatureProbabilities Probs;
+
+	// Buffer cases.
+
+	{
+		static const struct MemCase
+		{
+			const char* const	nameSuffix;
+			const char* const	descSuffix;
+			const int			limit;
+			const int			redundantBufferFactor;
+			MemCase (const char* n, const char* d, int l, int r) : nameSuffix(n), descSuffix(d), limit(l), redundantBufferFactor(r) {}
+		} memoryLimitCases[] =
+		{
+			MemCase("_low_memory",	"; use a low buffer memory usage limit",	8*Mi,		2),
+			MemCase("_high_memory",	"; use a high buffer memory usage limit",	256*Mi,		64)
+		};
+
+		const std::vector<gls::ProgramContext> contexts(1, progLib.generateBufferContext(4));
+
+		static const struct Case
+		{
+			const char* const	name;
+			const char*	const	desc;
+			const int			redundantBufferFactor; //!< If non-positive, taken from memoryLimitCases.
+			const Probs			probs;
+			Case (const char* const name_, const char* const desc_, int bufFact, const Probs& probs_ = Probs()) : name(name_), desc(desc_), redundantBufferFactor(bufFact), probs(probs_) {}
+		} cases[] =
+		{
+			Case("always_reupload",
+				 "Re-upload buffer data at the beginning of each iteration",
+				 -1,
+				 Probs().pReuploadBuffer(1.0f)),
+
+			Case("always_reupload_bufferdata",
+				 "Re-upload buffer data at the beginning of each iteration, using glBufferData",
+				 -1,
+				 Probs().pReuploadBuffer(1.0f).pReuploadWithBufferData(1.0f)),
+
+			Case("always_delete",
+				 "Delete buffers at the end of each iteration, and re-create at the beginning of the next",
+				 -1,
+				 Probs().pDeleteBuffer(1.0f)),
+
+			Case("wasteful",
+				 "Don't reuse buffers, and only delete them when given memory limit is reached",
+				 2,
+				 Probs().pWastefulBufferMemoryUsage(1.0f)),
+
+			Case("separate_attribute_buffers_wasteful",
+				 "Give each vertex attribute its own buffer",
+				 2,
+				 Probs().pSeparateAttribBuffers(1.0f).pWastefulBufferMemoryUsage(1.0f))
+		};
+
+		TestCaseGroup* const bufferGroup = new TestCaseGroup(m_context, "buffer", "Buffer stress tests");
+		addChild(bufferGroup);
+
+		for (int memoryLimitNdx = 0; memoryLimitNdx < DE_LENGTH_OF_ARRAY(memoryLimitCases); memoryLimitNdx++)
+		{
+			for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+			{
+				const int redundantBufferFactor = cases[caseNdx].redundantBufferFactor > 0 ? cases[caseNdx].redundantBufferFactor : memoryLimitCases[memoryLimitNdx].redundantBufferFactor;
+
+				bufferGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+															  (string() + cases[caseNdx].name + memoryLimitCases[memoryLimitNdx].nameSuffix).c_str(),
+															  (string() + cases[caseNdx].desc + memoryLimitCases[memoryLimitNdx].descSuffix).c_str(),
+															  0 /* tex memory */, memoryLimitCases[memoryLimitNdx].limit,
+															  1 /* draw calls per iteration */, 50000 /* tris per call */,
+															  contexts, cases[caseNdx].probs,
+															  GL_DYNAMIC_DRAW, GL_DYNAMIC_DRAW,
+															  redundantBufferFactor));
+			}
+		}
+	}
+
+	// Texture cases.
+
+	{
+		static const struct MemCase
+		{
+			const char* const	nameSuffix;
+			const char* const	descSuffix;
+			const int			limit;
+			const int			numTextures;
+			MemCase (const char* n, const char* d, int l, int t) : nameSuffix(n), descSuffix(d), limit(l), numTextures(t) {}
+		} memoryLimitCases[] =
+		{
+			MemCase("_low_memory",	"; use a low texture memory usage limit",	8*Mi,		6),
+			MemCase("_high_memory",	"; use a high texture memory usage limit",	256*Mi,		192)
+		};
+
+		static const struct Case
+		{
+			const char* const	name;
+			const char* const	desc;
+			const int			numTextures; //!< If non-positive, taken from memoryLimitCases.
+			const Probs			probs;
+			Case (const char* const name_, const char* const desc_, int numTextures_, const Probs& probs_ = Probs()) : name(name_), desc(desc_), numTextures(numTextures_), probs(probs_) {}
+		} cases[] =
+		{
+			Case("always_reupload",
+				 "Re-upload texture data at the beginning of each iteration",
+				 -1,
+				 Probs().pReuploadTexture(1.0f)),
+
+			Case("always_reupload_teximage",
+				 "Re-upload texture data at the beginning of each iteration, using glTexImage*",
+				 -1,
+				 Probs().pReuploadTexture(1.0f).pReuploadWithTexImage(1.0f)),
+
+			Case("always_delete",
+				 "Delete textures at the end of each iteration, and re-create at the beginning of the next",
+				 -1,
+				 Probs().pDeleteTexture(1.0f)),
+
+			Case("wasteful",
+				 "Don't reuse textures, and only delete them when given memory limit is reached",
+				 6,
+				 Probs().pWastefulTextureMemoryUsage(1.0f))
+		};
+
+		TestCaseGroup* const textureGroup = new TestCaseGroup(m_context, "texture", "Texture stress tests");
+		addChild(textureGroup);
+
+		for (int memoryLimitNdx = 0; memoryLimitNdx < DE_LENGTH_OF_ARRAY(memoryLimitCases); memoryLimitNdx++)
+		{
+			for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+			{
+				const int								numTextures		= cases[caseNdx].numTextures > 0 ? cases[caseNdx].numTextures : memoryLimitCases[memoryLimitNdx].numTextures;
+				const std::vector<gls::ProgramContext>	contexts		(1, progLib.generateTextureContext(numTextures, 512, 512, 0.1f));
+
+				textureGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+																(string() + cases[caseNdx].name + memoryLimitCases[memoryLimitNdx].nameSuffix).c_str(),
+																(string() + cases[caseNdx].desc + memoryLimitCases[memoryLimitNdx].descSuffix).c_str(),
+																memoryLimitCases[memoryLimitNdx].limit, 1*Mi /* buf memory */,
+																1 /* draw calls per iteration */, 10000 /* tris per call */,
+																contexts, cases[caseNdx].probs,
+																GL_STATIC_DRAW, GL_STATIC_DRAW));
+			}
+		}
+	}
+
+	// Draw call cases.
+
+	{
+		const std::vector<gls::ProgramContext> contexts(1, progLib.generateTextureContext(1, 128, 128, 0.5f));
+
+		static const struct Case
+		{
+			const char* const	name;
+			const char* const	desc;
+			const int			drawCallsPerIteration;
+			const int			numTrisPerDrawCall;
+			const Probs			probs;
+			Case (const char* const name_, const char* const desc_, const int calls, const int tris, const Probs& probs_ = Probs())
+				: name(name_), desc(desc_), drawCallsPerIteration(calls), numTrisPerDrawCall(tris), probs(probs_) {}
+		} cases[] =
+		{
+			Case("client_memory_data",
+				 "Use client-memory for index and attribute data, instead of GL buffers",
+				 200, 500,
+				 Probs().pClientMemoryAttributeData(1.0f).pClientMemoryIndexData(1.0f)),
+
+			Case("vary_draw_function",
+				 "Choose between glDrawElements and glDrawArrays each iteration, with uniform probability",
+				 200, 500,
+				 Probs().pUseDrawArrays(0.5f)),
+
+			Case("few_big_calls",
+				 "Per iteration, do a few draw calls with a big number of triangles per call",
+				 2, 50000),
+
+			Case("many_small_calls",
+				 "Per iteration, do many draw calls with a small number of triangles per call",
+				 2000, 50)
+		};
+
+		TestCaseGroup* const drawCallGroup = new TestCaseGroup(m_context, "draw_call", "Draw call stress tests");
+		addChild(drawCallGroup);
+
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+		{
+			drawCallGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+															cases[caseNdx].name, cases[caseNdx].desc,
+															1*Mi /* tex memory */, 2*Mi /* buf memory */,
+															cases[caseNdx].drawCallsPerIteration, cases[caseNdx].numTrisPerDrawCall,
+															contexts, cases[caseNdx].probs,
+															GL_STATIC_DRAW, GL_STATIC_DRAW));
+		}
+	}
+
+	// Shader cases.
+
+	{
+		std::vector<gls::ProgramContext> contexts;
+		contexts.push_back(progLib.generateFragmentPointLightContext(512, 512));
+		contexts.push_back(progLib.generateVertexUniformLoopLightContext(512, 512));
+
+		static const struct Case
+		{
+			const char* const	name;
+			const char* const	desc;
+			const Probs			probs;
+			Case (const char* const name_, const char* const desc_, const Probs& probs_ = Probs()) : name(name_), desc(desc_), probs(probs_) {}
+		} cases[] =
+		{
+			Case("several_programs",
+				 "Use several different programs, choosing between them uniformly on each iteration"),
+
+			Case("several_programs_always_rebuild",
+				 "Use several different programs, choosing between them uniformly on each iteration, and always rebuild the program",
+				 Probs().pRebuildProgram(1.0f))
+		};
+
+		TestCaseGroup* const shaderGroup = new TestCaseGroup(m_context, "program", "Shader program stress tests");
+		addChild(shaderGroup);
+
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(cases); caseNdx++)
+		{
+			shaderGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+														  cases[caseNdx].name, cases[caseNdx].desc,
+														  3*Mi /* tex memory */, 1*Mi /* buf memory */,
+														  1 /* draw calls per iteration */, 10000 /* tris per call */,
+														  contexts, cases[caseNdx].probs,
+														  GL_STATIC_DRAW, GL_STATIC_DRAW));
+		}
+	}
+
+	// Mixed cases.
+
+	{
+		static const struct MemCase
+		{
+			const char* const	nameSuffix;
+			const char* const	descSuffix;
+			const int			texLimit;
+			const int			bufLimit;
+			MemCase (const char* n, const char* d, int t, int b) : nameSuffix(n), descSuffix(d), texLimit(t), bufLimit(b) {}
+		} memoryLimitCases[] =
+		{
+			MemCase("_low_memory",	"; use a low memory usage limit",	8*Mi,	8*Mi),
+			MemCase("_high_memory",	"; use a high memory usage limit",	128*Mi,	128*Mi)
+		};
+
+		TestCaseGroup* const mixedGroup = new TestCaseGroup(m_context, "mixed", "Mixed stress tests");
+		addChild(mixedGroup);
+
+		for (int memoryLimitNdx = 0; memoryLimitNdx < DE_LENGTH_OF_ARRAY(memoryLimitCases); memoryLimitNdx++)
+		{
+			mixedGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+														 (string() + "buffer_texture_wasteful"					+ memoryLimitCases[memoryLimitNdx].nameSuffix).c_str(),
+														 (string() + "Use both buffers and textures wastefully"	+ memoryLimitCases[memoryLimitNdx].descSuffix).c_str(),
+														 memoryLimitCases[memoryLimitNdx].texLimit, memoryLimitCases[memoryLimitNdx].bufLimit,
+														 1 /* draw calls per iteration */, 10000 /* tris per call */,
+														 std::vector<gls::ProgramContext>(1, progLib.generateBufferAndTextureContext(4, 512, 512)),
+														 Probs()
+														 .pReuploadTexture				(0.3f)
+														 .pReuploadWithTexImage			(0.5f)
+														 .pReuploadBuffer				(0.3f)
+														 .pReuploadWithBufferData		(0.5f)
+														 .pDeleteTexture				(0.2f)
+														 .pDeleteBuffer					(0.2f)
+														 .pWastefulTextureMemoryUsage	(0.5f)
+														 .pWastefulBufferMemoryUsage	(0.5f)
+														 .pRandomBufferUploadTarget		(1.0f)
+														 .pRandomBufferUsage			(1.0f),
+														 GL_STATIC_DRAW, GL_STATIC_DRAW));
+
+			{
+				std::vector<gls::ProgramContext> contexts;
+				contexts.push_back(progLib.generateFragmentPointLightContext(512, 512));
+				contexts.push_back(progLib.generateVertexUniformLoopLightContext(512, 512));
+				mixedGroup->addChild(new gls::LongStressCase(m_context.getTestContext(), m_context.getRenderContext(),
+															 (string() + "random"					+ memoryLimitCases[memoryLimitNdx].nameSuffix).c_str(),
+															 (string() + "Highly random behavior"	+ memoryLimitCases[memoryLimitNdx].descSuffix).c_str(),
+															  memoryLimitCases[memoryLimitNdx].texLimit, memoryLimitCases[memoryLimitNdx].bufLimit,
+															 1 /* draw calls per iteration */, 10000 /* tris per call */,
+															 contexts,
+															 Probs()
+															 .pRebuildProgram				(0.3f)
+															 .pReuploadTexture				(0.3f)
+															 .pReuploadWithTexImage			(0.3f)
+															 .pReuploadBuffer				(0.3f)
+															 .pReuploadWithBufferData		(0.3f)
+															 .pDeleteTexture				(0.2f)
+															 .pDeleteBuffer					(0.2f)
+															 .pWastefulTextureMemoryUsage	(0.3f)
+															 .pWastefulBufferMemoryUsage	(0.3f)
+															 .pClientMemoryAttributeData	(0.2f)
+															 .pClientMemoryIndexData		(0.2f)
+															 .pSeparateAttribBuffers		(0.4f)
+															 .pUseDrawArrays				(0.4f)
+															 .pRandomBufferUploadTarget		(1.0f)
+															 .pRandomBufferUsage			(1.0f),
+															 GL_STATIC_DRAW, GL_STATIC_DRAW));
+			}
+		}
+	}
+}
+
+} // Stress
+} // gles3
+} // deqp
diff --git a/modules/gles3/stress/es3sLongRunningTests.hpp b/modules/gles3/stress/es3sLongRunningTests.hpp
new file mode 100644
index 0000000..823e302
--- /dev/null
+++ b/modules/gles3/stress/es3sLongRunningTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3SLONGRUNNINGTESTS_HPP
+#define _ES3SLONGRUNNINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Long-running stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+class LongRunningTests : public TestCaseGroup
+{
+public:
+						LongRunningTests	(Context& context);
+						~LongRunningTests	(void);
+
+	void				init				(void);
+
+private:
+						LongRunningTests	(const LongRunningTests& other);
+	LongRunningTests&	operator=			(const LongRunningTests& other);
+};
+
+} // Stress
+} // gles3
+} // deqp
+
+#endif // _ES3SLONGRUNNINGTESTS_HPP
diff --git a/modules/gles3/stress/es3sLongShaderTests.cpp b/modules/gles3/stress/es3sLongShaderTests.cpp
new file mode 100644
index 0000000..8fef65a
--- /dev/null
+++ b/modules/gles3/stress/es3sLongShaderTests.cpp
@@ -0,0 +1,472 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Long shader compilation stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es3sLongShaderTests.hpp"
+
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+#include "tcuTestLog.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <string>
+#include <set>
+#include <map>
+#include <cmath>
+
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+namespace
+{
+
+enum LongShaderCaseFlags
+{
+	CASE_REQUIRE_LINK_STATUS_OK	= 1
+};
+
+const char* getConstVertShaderSource (void)
+{
+	const char* const src =
+		"#version 300 es\n"
+		"void main ()\n"
+		"{\n"
+		"	gl_Position = vec4(0.0);\n"
+		"}\n";
+
+	return src;
+}
+
+const char* getConstFragShaderSource (void)
+{
+	const char* const src =
+		"#version 300 es\n"
+		"layout(location = 0) out mediump vec4 o_fragColor;\n"
+		"void main ()\n"
+		"{\n"
+		"	o_fragColor = vec4(0.0);\n"
+		"}\n";
+
+	return src;
+}
+
+const char* getConstShaderSource (const glu::ShaderType shaderType)
+{
+	DE_ASSERT(shaderType == glu::SHADERTYPE_VERTEX || shaderType == glu::SHADERTYPE_FRAGMENT);
+
+	if (shaderType == glu::SHADERTYPE_VERTEX)
+		return getConstVertShaderSource();
+	else
+		return getConstFragShaderSource();
+}
+
+typedef std::set<std::string> ShaderScope;
+
+const char variableNamePrefixChars[] = "abcdefghijklmnopqrstuvwxyz";
+
+class NameGenerator
+{
+public:
+	NameGenerator (void)
+		: m_scopeIndices		(1, 0)
+		, m_currentScopeDepth	(1)
+		, m_variableIndex		(0)
+	{
+	}
+
+	void beginScope (void)
+	{
+		m_currentScopeDepth++;
+
+		if (m_scopeIndices.size() < (size_t)m_currentScopeDepth)
+			m_scopeIndices.push_back(0);
+		else
+			m_scopeIndices[m_currentScopeDepth-1]++;
+
+		m_variableIndex = 0;
+	}
+
+	void endScope (void)
+	{
+		DE_ASSERT(m_currentScopeDepth > 1);
+
+		m_currentScopeDepth--;
+	}
+
+	std::string makePrefix (void)
+	{
+		std::string prefix;
+
+		for (int ndx = 0; ndx < m_currentScopeDepth; ndx++)
+		{
+			const int scopeIndex = m_scopeIndices[m_currentScopeDepth-1];
+
+			DE_ASSERT(scopeIndex < DE_LENGTH_OF_ARRAY(variableNamePrefixChars));
+
+			prefix += variableNamePrefixChars[scopeIndex];
+		}
+
+		return prefix;
+	}
+
+	std::string next (void)
+	{
+		m_variableIndex++;
+
+		return makePrefix() + de::toString(m_variableIndex);
+	}
+
+	void makeNames (ShaderScope& scope, const deUint32 count)
+	{
+		for (deUint32 ndx = 0; ndx < count; ndx++)
+			scope.insert(next());
+	}
+
+private:
+	std::vector<int>	m_scopeIndices;
+	int					m_currentScopeDepth;
+	int					m_variableIndex;
+};
+
+struct LongShaderSpec
+{
+	glu::ShaderType	shaderType;
+	deUint32		opsTotal;
+
+	deUint32		variablesPerBlock;
+	deUint32		opsPerExpression;
+
+	LongShaderSpec (const glu::ShaderType shaderTypeInit, const deUint32 opsTotalInit)
+		: shaderType		(shaderTypeInit)
+		, opsTotal			(opsTotalInit)
+		, variablesPerBlock	(deMaxu32(10, (deUint32)std::floor(std::sqrt((double)opsTotal))))
+		, opsPerExpression	(deMinu32(10, variablesPerBlock / 2))
+	{
+	}
+};
+
+// Generator for long test shaders
+
+class LongShaderGenerator
+{
+public:
+								LongShaderGenerator		(de::Random& rnd, const LongShaderSpec& spec);
+
+	glu::ShaderSource			getSource				(void);
+
+private:
+	de::Random					m_rnd;
+	const LongShaderSpec		m_spec;
+
+	NameGenerator				m_nameGen;
+
+	std::vector<std::string>	m_varNames;
+	std::vector<ShaderScope>	m_scopes;
+
+	std::string					m_source;
+
+	void						generateSource			(void);
+
+	std::string					getRandomVariableName	(void);
+	std::string					getShaderOutputName		(void);
+	std::string					makeExpression			(const std::vector<std::string>& varNames, const int numOps);
+
+	void						addIndent				(void);
+	void						addLine					(const std::string& text);
+
+	void						beginBlock				(void);
+	void						endBlock				(void);
+};
+
+LongShaderGenerator::LongShaderGenerator (de::Random& rnd, const LongShaderSpec& spec)
+	: m_rnd			(rnd)
+	, m_spec		(spec)
+{
+	DE_ASSERT(m_spec.shaderType == glu::SHADERTYPE_VERTEX || m_spec.shaderType == glu::SHADERTYPE_FRAGMENT);
+}
+
+glu::ShaderSource LongShaderGenerator::getSource (void)
+{
+	if (m_source.empty())
+		generateSource();
+
+	return glu::ShaderSource(m_spec.shaderType, m_source);
+}
+
+void LongShaderGenerator::generateSource (void)
+{
+	deUint32 currentOpsTotal = 0;
+
+	m_source.clear();
+
+	addLine("#version 300 es");
+
+	if (m_spec.shaderType == glu::SHADERTYPE_FRAGMENT)
+		addLine("layout(location = 0) out mediump vec4 o_fragColor;");
+
+	addLine("void main (void)");
+	beginBlock();
+
+	while (currentOpsTotal < m_spec.opsTotal)
+	{
+		const bool					isLast	= (m_spec.opsTotal <= (currentOpsTotal + m_spec.opsPerExpression));
+		const int					numOps	= isLast ? (m_spec.opsTotal - currentOpsTotal) : m_spec.opsPerExpression;
+		const size_t				numVars	= numOps + 1;
+
+		const std::string			outName	= isLast ? getShaderOutputName() : getRandomVariableName();
+		std::vector<std::string>	inNames	(numVars);
+
+		DE_ASSERT(numVars < m_varNames.size());
+		m_rnd.choose(m_varNames.begin(), m_varNames.end(), inNames.begin(), (int)numVars);
+
+		{
+			std::string expr = makeExpression(inNames, numOps);
+
+			if (isLast)
+				addLine(outName + " = vec4(" + expr + ");");
+			else
+				addLine(outName + " = " + expr + ";");
+		}
+
+		currentOpsTotal += numOps;
+	}
+
+	while (!m_scopes.empty())
+		endBlock();
+}
+
+std::string LongShaderGenerator::getRandomVariableName (void)
+{
+	return m_rnd.choose<std::string>(m_varNames.begin(), m_varNames.end());
+}
+
+std::string LongShaderGenerator::getShaderOutputName (void)
+{
+	return (m_spec.shaderType == glu::SHADERTYPE_VERTEX) ? "gl_Position" : "o_fragColor";
+}
+
+std::string LongShaderGenerator::makeExpression (const std::vector<std::string>& varNames, const int numOps)
+{
+	const std::string	operators	= "+-*/";
+	std::string			expr;
+
+	DE_ASSERT(varNames.size() > (size_t)numOps);
+
+	expr = varNames[0];
+
+	for (int ndx = 1; ndx <= numOps; ndx++)
+	{
+		const std::string	op		= std::string("") + m_rnd.choose<char>(operators.begin(), operators.end());
+		const std::string	varName	= varNames[ndx];
+
+		expr += " " + op + " " + varName;
+	}
+
+	return expr;
+}
+
+
+void LongShaderGenerator::addIndent (void)
+{
+	m_source += std::string(m_scopes.size(), '\t');
+}
+
+void LongShaderGenerator::addLine (const std::string& text)
+{
+	addIndent();
+	m_source += text + "\n";
+}
+
+void LongShaderGenerator::beginBlock (void)
+{
+	ShaderScope scope;
+
+	addLine("{");
+
+	m_nameGen.beginScope();
+	m_nameGen.makeNames(scope, m_spec.variablesPerBlock);
+
+	m_scopes.push_back(scope);
+
+	for (ShaderScope::const_iterator nameIter = scope.begin(); nameIter != scope.end(); nameIter++)
+	{
+		const std::string	varName		= *nameIter;
+		const float			varValue	= m_rnd.getFloat();
+
+		addLine("mediump float " + varName + " = " + de::floatToString(varValue, 5) + "f;");
+		m_varNames.push_back(varName);
+	}
+}
+
+void LongShaderGenerator::endBlock (void)
+{
+	ShaderScope& scope = *(m_scopes.end()-1);
+
+	DE_ASSERT(!m_scopes.empty());
+
+	m_varNames.erase((m_varNames.begin() + (m_varNames.size() - scope.size())), m_varNames.end());
+
+	m_nameGen.endScope();
+	m_scopes.pop_back();
+
+	addLine("}");
+}
+
+} // anonymous
+
+// Stress test case for compilation of large shaders
+
+class LongShaderCompileStressCase : public TestCase
+{
+public:
+							LongShaderCompileStressCase		(Context& context, const char* name, const char* desc, const LongShaderSpec& caseSpec, const deUint32 flags);
+	virtual					~LongShaderCompileStressCase	(void);
+
+	void					init							(void);
+
+	IterateResult			iterate							(void);
+
+	void					verify							(const glu::ShaderProgram& program);
+
+private:
+	const glu::ShaderType	m_shaderType;
+	const deUint32			m_flags;
+	de::Random				m_rnd;
+	LongShaderGenerator		m_gen;
+};
+
+LongShaderCompileStressCase::LongShaderCompileStressCase (Context& context, const char* name, const char* desc, const LongShaderSpec& caseSpec, const deUint32 flags)
+	: TestCase		(context, name, desc)
+	, m_shaderType	(caseSpec.shaderType)
+	, m_flags		(flags)
+	, m_rnd			(deStringHash(name) ^ 0xac9c91d)
+	, m_gen			(m_rnd, caseSpec)
+{
+	DE_ASSERT(m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT);
+}
+
+LongShaderCompileStressCase::~LongShaderCompileStressCase (void)
+{
+}
+
+void LongShaderCompileStressCase::init (void)
+{
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+tcu::TestCase::IterateResult LongShaderCompileStressCase::iterate (void)
+{
+	tcu::TestLog&				log			= m_testCtx.getLog();
+	const glu::ShaderType		otherShader	= (m_shaderType == glu::SHADERTYPE_VERTEX) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
+	glu::ProgramSources			sources;
+
+	sources << m_gen.getSource();
+	sources << glu::ShaderSource(otherShader, getConstShaderSource(otherShader));
+
+	{
+		glu::ShaderProgram program(m_context.getRenderContext(), sources);
+
+		verify(program);
+
+		log << program;
+	}
+
+	return STOP;
+}
+
+void LongShaderCompileStressCase::verify (const glu::ShaderProgram& program)
+{
+	tcu::TestLog&			log			= m_testCtx.getLog();
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const bool				isStrict	= (m_flags & CASE_REQUIRE_LINK_STATUS_OK) != 0;
+	const glw::GLenum		errorCode	= gl.getError();
+
+	if (isStrict && !program.isOk())
+	{
+		log << TestLog::Message << "Fail, expected program to compile and link successfully." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Linking failed");
+	}
+
+	if (program.isOk() && (errorCode != GL_NO_ERROR))
+	{
+		log << TestLog::Message << "Fail, program status OK but a GL error was received (" << errorCode << ")." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Conflicting status");
+	}
+	else if ((errorCode != GL_NO_ERROR) && (errorCode != GL_OUT_OF_MEMORY))
+	{
+		log << TestLog::Message << "Fail, expected GL_NO_ERROR or GL_OUT_OF_MEMORY, received " << errorCode << "." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected GL error");
+	}
+}
+
+LongShaderTests::LongShaderTests (Context& testCtx)
+	: TestCaseGroup(testCtx, "long_shaders", "Long shader compilation stress tests")
+{
+}
+
+LongShaderTests::~LongShaderTests(void)
+{
+}
+
+void LongShaderTests::init (void)
+{
+	const deUint32	requireLinkOkMaxOps	= 1000;
+
+	const deUint32	caseOpCounts[] =
+	{
+		100,
+		1000,
+		10000,
+		100000
+	};
+
+	for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(caseOpCounts); caseNdx++)
+	{
+		for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
+		{
+			const glu::ShaderType	shaderType		= (shaderTypeInt == 0) ? glu::SHADERTYPE_VERTEX : glu::SHADERTYPE_FRAGMENT;
+			const deUint32			opCount			= caseOpCounts[caseNdx];
+			const deUint32			flags			= (opCount <= requireLinkOkMaxOps) ? CASE_REQUIRE_LINK_STATUS_OK : 0;
+
+			const std::string		name			= de::toString(opCount) + "_operations_" + glu::getShaderTypeName(shaderType);
+			const std::string		desc			= std::string("Compile ") + glu::getShaderTypeName(shaderType) + " shader with " + de::toString(opCount) + " operations";
+
+			LongShaderSpec			caseSpec		(shaderType, opCount);
+
+			addChild(new LongShaderCompileStressCase(m_context, name.c_str(), desc.c_str(), caseSpec, flags));
+		}
+	}
+}
+
+} // Stress
+} // gles3
+} // deqp
diff --git a/modules/gles3/stress/es3sLongShaderTests.hpp b/modules/gles3/stress/es3sLongShaderTests.hpp
new file mode 100644
index 0000000..f80ce69
--- /dev/null
+++ b/modules/gles3/stress/es3sLongShaderTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3SLONGSHADERTESTS_HPP
+#define _ES3SLONGSHADERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Long shader compilation stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+class LongShaderTests : public TestCaseGroup
+{
+public:
+						LongShaderTests		(Context& testCtx);
+						~LongShaderTests	(void);
+
+	void				init				(void);
+
+private:
+						LongShaderTests		(const LongShaderTests& other);
+	LongShaderTests&	operator=			(const LongShaderTests& other);
+};
+
+} // Stress
+} // gles3
+} // deqp
+
+#endif // _ES3SLONGSHADERTESTS_HPP
diff --git a/modules/gles3/stress/es3sMemoryTests.cpp b/modules/gles3/stress/es3sMemoryTests.cpp
new file mode 100644
index 0000000..666963e
--- /dev/null
+++ b/modules/gles3/stress/es3sMemoryTests.cpp
@@ -0,0 +1,189 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Memory object stress test
+ *//*--------------------------------------------------------------------*/
+
+#include "es3sMemoryTests.hpp"
+
+#include "glsMemoryStressCase.hpp"
+#include "deRandom.hpp"
+#include "tcuTestLog.hpp"
+
+#include "glw.h"
+
+#include <vector>
+#include <iostream>
+
+using std::vector;
+using tcu::TestLog;
+
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+MemoryTests::MemoryTests (Context& testCtx)
+	: TestCaseGroup(testCtx, "memory", "Memory stress tests")
+{
+}
+
+MemoryTests::~MemoryTests(void)
+{
+}
+
+void MemoryTests::init (void)
+{
+	const int MiB = 1024*1024;
+
+	// Basic tests
+	tcu::TestCaseGroup* basicGroup = new TestCaseGroup(m_context, "basic", "Basic allocation stress tests.");
+
+	// Buffers
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, false, false, false, false,	"buffer_1mb_no_write_no_use",	"1MiB buffer allocations, no data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, true,  false, false, false,	"buffer_1mb_write_no_use",		"1MiB buffer allocations, data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, false, true,  false, false,	"buffer_1mb_no_write_use",		"1MiB buffer allocations, no data writes, data used"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, true,  true,  false, false,	"buffer_1mb_write_use",			"1MiB buffer allocations, data writes, data used"));
+
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, false, false, false, false,	"buffer_8mb_no_write_no_use",	"8MiB buffer allocations, no data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, true,  false, false, false,	"buffer_8mb_write_no_use",		"8MiB buffer allocations, data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, false, true,  false, false,	"buffer_8mb_no_write_use",		"8MiB buffer allocations, no data writes, data used"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, true,  true,  false, false,	"buffer_8mb_write_use",			"8MiB buffer allocations, data writes, data used"));
+
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, false, false, false, false,	"buffer_32mb_no_write_no_use",	"32MiB buffer allocations, no data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, true,  false, false, false,	"buffer_32mb_write_no_use",		"32MiB buffer allocations, data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, false, true,  false, false,	"buffer_32mb_no_write_use",		"32MiB buffer allocations, no data writes, data used"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, true, true,  false, false,	"buffer_32mb_write_use",		"32MiB buffer allocations, data writes, data used"));
+
+	// Textures
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, false, false, false, false,	"texture_512x512_rgba_no_write_no_use",	"512x512 RGBA texture allocations, no data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, true,  false, false, false,	"texture_512x512_rgba_write_no_use",	"512x512 RGBA texture allocations, data writes, no use"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, false, true,  false, false,	"texture_512x512_rgba_no_write_use",	"512x512 RGBA texture allocations, no data writes, data used"));
+	basicGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, true, true,  false, false,	"texture_512x512_rgba_write_use",		"512x512 RGBA texture allocations, data writes, data used"));
+
+	// Random tests
+	tcu::TestCaseGroup*	randomGroup = new TestCaseGroup(m_context, "random", "Random allocation stress tests.");
+
+	// Buffers
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, false, false, false, false,	"buffer_small_no_write_no_use",		"Random small buffer allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, true,  false, false, false,	"buffer_small_write_no_use",		"Random small allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, false, true,  false, false,	"buffer_small_no_write_use",		"Random small allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, true,  true,  false, false,	"buffer_small_write_use",			"Random small allocations, data writes, data used"));
+
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, false, false, false, false,	"buffer_large_no_write_no_use",		"Random large buffer allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, true,  false, false, false,	"buffer_large_write_no_use",		"Random large buffer allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, false, true,  false, false,	"buffer_large_no_write_use",		"Random large buffer allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, true,  true,  false, false,	"buffer_large_write_use",			"Random large buffer allocations, data writes, data used"));
+
+	// Textures
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, false, false, false, false,	"texture_small_rgba_no_write_no_use",	"Small RGBA texture allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, true,  false, false, false,	"texture_small_rgba_write_no_use",		"Small RGBA texture allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, false, true,  false, false,	"texture_small_rgba_no_write_use",		"Small RGBA texture allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, true,  true,  false, false,	"texture_small_rgba_write_use",			"Small RGBA texture allocations, data writes, data used"));
+
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, false, false, false, false,	"texture_large_rgba_no_write_no_use",	"Large RGBA texture allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, true,  false, false, false,	"texture_large_rgba_write_no_use",		"Large RGBA texture allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, false, true,  false, false,	"texture_large_rgba_no_write_use",		"Large RGBA texture allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, true,  true,  false, false,	"texture_large_rgba_write_use",			"Large RGBA texture allocations, data writes, data used"));
+
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, false, false, false, false,	"mixed_small_rgba_no_write_no_use",		"Small RGBA mixed allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, true,  false, false, false,	"mixed_small_rgba_write_no_use",		"Small RGBA mixed allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, false, true,  false, false,	"mixed_small_rgba_no_write_use",		"Small RGBA mixed allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, true,  true,  false, false,	"mixed_small_rgba_write_use",			"Small RGBA mixed allocations, data writes, data used"));
+
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, false, false, false, false,	"mixed_large_rgba_no_write_no_use",		"Large RGBA mixed allocations, no data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, true,  false, false, false,	"mixed_large_rgba_write_no_use",		"Large RGBA mixed allocations, data writes, no use"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, false, true,  false, false,	"mixed_large_rgba_no_write_use",		"Large RGBA mixed allocations, no data writes, data used"));
+	randomGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, true,  true,  false, false,	"mixed_large_rgba_write_use",			"Large RGBA mixed allocations, data writes, data used"));
+
+	addChild(basicGroup);
+	addChild(randomGroup);
+
+	// Basic tests with clear
+	tcu::TestCaseGroup* basicClearGroup	= new TestCaseGroup(m_context, "basic_clear", "Basic allocation stress tests with glClear after OOM.");
+
+	// Buffers
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, false, false, false, true,	"buffer_1mb_no_write_no_use",	"1MiB buffer allocations, no data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, true,  false, false, true,	"buffer_1mb_write_no_use",		"1MiB buffer allocations, data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, false, true,  false, true,	"buffer_1mb_no_write_use",		"1MiB buffer allocations, no data writes, data used"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 1*MiB, 1*MiB, true,  true,  false, true,	"buffer_1mb_write_use",			"1MiB buffer allocations, data writes, data used"));
+
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, false, false, false, true,	"buffer_8mb_no_write_no_use",	"8MiB buffer allocations, no data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, true,  false, false, true,	"buffer_8mb_write_no_use",		"8MiB buffer allocations, data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, false, true,  false, true,	"buffer_8mb_no_write_use",		"8MiB buffer allocations, no data writes, data used"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 8*MiB, 8*MiB, true,  true,  false, true,	"buffer_8mb_write_use",			"8MiB buffer allocations, data writes, data used"));
+
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, false, false, false, true,	"buffer_32mb_no_write_no_use",	"32MiB buffer allocations, no data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, true,  false, false, true,	"buffer_32mb_write_no_use",		"32MiB buffer allocations, data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, false, true,  false, true,	"buffer_32mb_no_write_use",		"32MiB buffer allocations, no data writes, data used"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 32*MiB, 32*MiB, true, true,  false, true,		"buffer_32mb_write_use",		"32MiB buffer allocations, data writes, data used"));
+
+	// Textures
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, false, false, false, true,	"texture_512x512_rgba_no_write_no_use",	"512x512 RGBA texture allocations, no data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, true,  false, false, true,	"texture_512x512_rgba_write_no_use",	"512x512 RGBA texture allocations, data writes, no use"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, false, true,  false, true,	"texture_512x512_rgba_no_write_use",	"512x512 RGBA texture allocations, no data writes, data used"));
+	basicClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 512, 0, 0, true, true,  false, true,	"texture_512x512_rgba_write_use",		"512x512 RGBA texture allocations, data writes, data used"));
+
+	// Random tests
+	tcu::TestCaseGroup*	randomClearGroup = new TestCaseGroup(m_context, "random_clear", "Random allocation stress tests with glClear after OOM.");
+
+	// Buffers
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, false, false, false, true,	"buffer_small_no_write_no_use",		"Random small buffer allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, true,  false, false, true,	"buffer_small_write_no_use",		"Random small allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, false, true,  false, true,	"buffer_small_no_write_use",		"Random small allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 64, 1*MiB, true,  true,  false, true,	"buffer_small_write_use",			"Random small allocations, data writes, data used"));
+
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, false, false, false, true,	"buffer_large_no_write_no_use",		"Random large buffer allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, true,  false, false, true,	"buffer_large_write_no_use",		"Random large buffer allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, false, true,  false, true,	"buffer_large_no_write_use",		"Random large buffer allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_BUFFER, 0, 0, 2*MiB, 32*MiB, true,  true,  false, true,	"buffer_large_write_use",			"Random large buffer allocations, data writes, data used"));
+
+	// Textures
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, false, false, false, true,	"texture_small_rgba_no_write_no_use",	"Small RGBA texture allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, true,  false, false, true,	"texture_small_rgba_write_no_use",		"Small RGBA texture allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, false, true,  false, true,	"texture_small_rgba_no_write_use",		"Small RGBA texture allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 8, 256, 0, 0, true,  true,  false, true,	"texture_small_rgba_write_use",			"Small RGBA texture allocations, data writes, data used"));
+
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, false, false, false, true,	"texture_large_rgba_no_write_no_use",	"Large RGBA texture allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, true,  false, false, true,	"texture_large_rgba_write_no_use",		"Large RGBA texture allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, false, true,  false, true,	"texture_large_rgba_no_write_use",		"Large RGBA texture allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE, 512, 1024, 0, 0, true,  true,  false, true,	"texture_large_rgba_write_use",			"Large RGBA texture allocations, data writes, data used"));
+
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, false, false, false, true,	"mixed_small_rgba_no_write_no_use",		"Small RGBA mixed allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, true,  false, false, true,	"mixed_small_rgba_write_no_use",		"Small RGBA mixed allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, false, true,  false, true,	"mixed_small_rgba_no_write_use",		"Small RGBA mixed allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 8, 256, 64, 1*MiB, true,  true,  false, true,	"mixed_small_rgba_write_use",			"Small RGBA mixed allocations, data writes, data used"));
+
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, false, false, false, true,	"mixed_large_rgba_no_write_no_use",		"Large RGBA mixed allocations, no data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, true,  false, false, true,	"mixed_large_rgba_write_no_use",		"Large RGBA mixed allocations, data writes, no use"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, false, true,  false, true,	"mixed_large_rgba_no_write_use",		"Large RGBA mixed allocations, no data writes, data used"));
+	randomClearGroup->addChild(new MemoryStressCase(m_context.getTestContext(), m_context.getRenderContext(), MEMOBJECTTYPE_TEXTURE|MEMOBJECTTYPE_BUFFER, 512, 1024, 2*MiB, 32*MiB, true,  true,  false, true,	"mixed_large_rgba_write_use",			"Large RGBA mixed allocations, data writes, data used"));
+
+	addChild(basicClearGroup);
+	addChild(randomClearGroup);
+}
+
+} // Stress
+} // gles2
+} // deqp
diff --git a/modules/gles3/stress/es3sMemoryTests.hpp b/modules/gles3/stress/es3sMemoryTests.hpp
new file mode 100644
index 0000000..2cfd44e
--- /dev/null
+++ b/modules/gles3/stress/es3sMemoryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3SMEMORYTESTS_HPP
+#define _ES3SMEMORYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Memory object stress test
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+class MemoryTests : public TestCaseGroup
+{
+public:
+					MemoryTests		(Context& testCtx);
+					~MemoryTests	(void);
+
+	void			init			(void);
+
+private:
+					MemoryTests		(const MemoryTests& other);
+	MemoryTests&	operator=		(const MemoryTests& other);
+};
+
+} // Stress
+} // gles3
+} // deqp
+
+#endif // _ES3SMEMORYTESTS_HPP
diff --git a/modules/gles3/stress/es3sOcclusionQueryTests.cpp b/modules/gles3/stress/es3sOcclusionQueryTests.cpp
new file mode 100644
index 0000000..cebc0de
--- /dev/null
+++ b/modules/gles3/stress/es3sOcclusionQueryTests.cpp
@@ -0,0 +1,303 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Occlusion query stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es3sOcclusionQueryTests.hpp"
+
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "tcuSurface.hpp"
+#include "gluShaderProgram.hpp"
+#include "deClock.h"
+
+#include "glw.h"
+
+#include <vector>
+
+using std::vector;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+static const tcu::Vec4	OCCLUDER_COLOR			= tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f);
+static const tcu::Vec4	TARGET_COLOR			= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+static const int		NUM_CASE_ITERATIONS		= 3;
+static const int		NUM_GENERATED_VERTICES	= 100;
+static const int		WATCHDOG_INTERVAL		= 50; // Touch watchdog every N iterations.
+
+class OcclusionQueryStressCase : public TestCase
+{
+public:
+								OcclusionQueryStressCase		(Context& ctx, const char* name, const char* desc, int m_numOccluderDraws, int m_numOccludersPerDraw, int m_numTargetDraws, int m_numTargetsPerDraw, int m_numQueries, deUint32 m_queryMode);
+								~OcclusionQueryStressCase		(void);
+
+	void						init							(void);
+	void						deinit							(void);
+	IterateResult				iterate							(void);
+
+private:
+								OcclusionQueryStressCase		(const OcclusionQueryStressCase&);
+	OcclusionQueryStressCase&	operator=						(const OcclusionQueryStressCase&);
+
+	int							m_numOccluderDraws;
+	int							m_numOccludersPerDraw;
+	int							m_numTargetDraws;
+	int							m_numTargetsPerDraw;
+	int							m_numQueries;
+	deUint32					m_queryMode;
+
+	glu::RenderContext&			m_renderCtx;
+	glu::ShaderProgram*			m_program;
+	int							m_iterNdx;
+	de::Random					m_rnd;
+
+};
+
+OcclusionQueryStressCase::OcclusionQueryStressCase (Context& ctx, const char* name, const char* desc, int numOccluderDraws, int numOccludersPerDraw, int numTargetDraws, int numTargetsPerDraw, int numQueries, deUint32 queryMode)
+	: TestCase				(ctx, name, desc)
+	, m_numOccluderDraws	(numOccluderDraws)
+	, m_numOccludersPerDraw	(numOccludersPerDraw)
+	, m_numTargetDraws		(numTargetDraws)
+	, m_numTargetsPerDraw	(numTargetsPerDraw)
+	, m_numQueries			(numQueries)
+	, m_queryMode			(queryMode)
+	, m_renderCtx			(ctx.getRenderContext())
+	, m_program				(DE_NULL)
+	, m_iterNdx				(0)
+	, m_rnd					(deStringHash(name))
+{
+}
+
+OcclusionQueryStressCase::~OcclusionQueryStressCase (void)
+{
+	OcclusionQueryStressCase::deinit();
+}
+
+void OcclusionQueryStressCase::init (void)
+{
+	const char*	vertShaderSource =
+				"#version 300 es\n"
+				"layout(location = 0) in mediump vec4 a_position;\n"
+				"\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = a_position;\n"
+				"}\n";
+
+	const char* fragShaderSource =
+				"#version 300 es\n"
+				"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+				"uniform mediump vec4 u_color;\n"
+				"\n"
+				"void main (void)\n"
+				"{\n"
+				"	mediump float depth_gradient = gl_FragCoord.z;\n"
+				"	mediump float bias = 0.1;\n"
+				"	dEQP_FragColor = vec4(u_color.xyz * (depth_gradient + bias), 1.0);\n"
+				"}\n";
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource));
+
+	if (!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+		TCU_FAIL("Failed to compile shader program");
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); // Initialize test result to pass.
+	GLU_CHECK_MSG ("Case initialization finished");
+}
+
+void OcclusionQueryStressCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+
+OcclusionQueryStressCase::IterateResult OcclusionQueryStressCase::iterate (void)
+{
+	tcu::TestLog&				log				 = m_testCtx.getLog();
+	deUint32					colorUnif		 = glGetUniformLocation(m_program->getProgram(), "u_color");
+
+	std::vector<float>			vertices;
+	std::vector<float>			occluderVertices;
+	std::vector<float>			targetVertices;
+	std::vector<deUint32>		queryIds		 (m_numQueries, 0);
+	std::vector<deUint32>		queryResultReady (m_numQueries, 0);
+	std::vector<deUint32>		queryResult		 (m_numQueries, 0);
+
+	std::string					sectionName		("Case iteration " + de::toString(m_iterNdx+1) + "/" + de::toString(NUM_CASE_ITERATIONS));
+	tcu::ScopedLogSection		section			(log, sectionName.c_str(), sectionName.c_str());
+
+	log << tcu::TestLog::Message << "Parameters:\n"
+								 << "- Number of occlusion queries: "		 << m_numQueries		<< ".\n"
+								 << "- Number of occluder draws per query: " << m_numOccluderDraws	<< ", primitives per draw: " << m_numOccludersPerDraw << ".\n"
+								 << "- Number of target draws per query: "	 << m_numTargetDraws	<< ", primitives per draw: " << m_numTargetsPerDraw	  << ".\n"
+		<< tcu::TestLog::EndMessage;
+
+	int numOccluderIndicesPerDraw = 3*m_numOccludersPerDraw;
+	int numTargetIndicesPerDraw = 3*m_numTargetsPerDraw;
+
+	// Generate vertex data
+
+	vertices.resize(4*NUM_GENERATED_VERTICES);
+
+	for (int i = 0; i < NUM_GENERATED_VERTICES; i++)
+	{
+		vertices[4*i    ] = m_rnd.getFloat(-1.0f, 1.0f);
+		vertices[4*i + 1] = m_rnd.getFloat(-1.0f, 1.0f);
+		vertices[4*i + 2] = m_rnd.getFloat(0.0f, 1.0f);
+		vertices[4*i + 3] = 1.0f;
+	}
+
+	// Generate primitives
+
+	occluderVertices.resize(4*numOccluderIndicesPerDraw * m_numOccluderDraws);
+
+	for (int i = 0; i < numOccluderIndicesPerDraw * m_numOccluderDraws; i++)
+	{
+		int vtxNdx = m_rnd.getInt(0, NUM_GENERATED_VERTICES-1);
+		occluderVertices[4*i    ] = vertices[4*vtxNdx];
+		occluderVertices[4*i + 1] = vertices[4*vtxNdx + 1];
+		occluderVertices[4*i + 2] = vertices[4*vtxNdx + 2];
+		occluderVertices[4*i + 3] = vertices[4*vtxNdx + 3];
+	}
+
+	targetVertices.resize(4*numTargetIndicesPerDraw * m_numTargetDraws);
+
+	for (int i = 0; i < numTargetIndicesPerDraw * m_numTargetDraws; i++)
+	{
+		int vtxNdx = m_rnd.getInt(0, NUM_GENERATED_VERTICES-1);
+		targetVertices[4*i    ] = vertices[4*vtxNdx];
+		targetVertices[4*i + 1] = vertices[4*vtxNdx + 1];
+		targetVertices[4*i + 2] = vertices[4*vtxNdx + 2];
+		targetVertices[4*i + 3] = vertices[4*vtxNdx + 3];
+	}
+
+	TCU_CHECK(m_program);
+
+	glClearColor				(0.0f, 0.0f, 0.0f, 1.0f);
+	glClearDepthf				(1.0f);
+	glClear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	glEnable					(GL_DEPTH_TEST);
+	glUseProgram				(m_program->getProgram());
+	glEnableVertexAttribArray	(0);
+
+	deUint64 time = deGetMicroseconds();
+
+	for (int queryIter = 0; queryIter < m_numQueries; queryIter++)
+	{
+		// Draw occluders
+
+		glUniform4f	(colorUnif, OCCLUDER_COLOR.x(), OCCLUDER_COLOR.y(), OCCLUDER_COLOR.z(), OCCLUDER_COLOR.w());
+
+		for (int drawIter = 0; drawIter < m_numOccluderDraws; drawIter++)
+		{
+			glVertexAttribPointer	(0, 4, GL_FLOAT, GL_FALSE, 0, &occluderVertices[drawIter * numOccluderIndicesPerDraw]);
+			glDrawArrays			(GL_TRIANGLES, 0, numOccluderIndicesPerDraw);
+		}
+
+		// Begin occlusion query
+
+		glGenQueries	(1, &queryIds[queryIter]);
+		glBeginQuery	(m_queryMode, queryIds[queryIter]);
+
+		// Draw targets
+
+		glUniform4f	(colorUnif, TARGET_COLOR.x(), TARGET_COLOR.y(), TARGET_COLOR.z(), TARGET_COLOR.w());
+
+		for (int drawIter = 0; drawIter < m_numTargetDraws; drawIter++)
+		{
+			glVertexAttribPointer	(0, 4, GL_FLOAT, GL_FALSE, 0, &targetVertices[drawIter * numTargetIndicesPerDraw]);
+			glDrawArrays			(GL_TRIANGLES, 0, numTargetIndicesPerDraw);
+		}
+
+		// End occlusion query
+
+		glEndQuery		(m_queryMode);
+
+		if ((queryIter % WATCHDOG_INTERVAL) == 0 && m_testCtx.getWatchDog())
+			qpWatchDog_touch(m_testCtx.getWatchDog());
+	}
+
+	glFinish();
+	glDisable(GL_DEPTH_TEST);
+
+	deUint64 dTime = deGetMicroseconds() - time;
+	log << tcu::TestLog::Message << "Total duration: " << dTime/1000 << " ms" << tcu::TestLog::EndMessage;
+
+	// Get results
+
+	for (int queryIter = 0; queryIter < m_numQueries; queryIter++)
+	{
+		glGetQueryObjectuiv(queryIds[queryIter], GL_QUERY_RESULT_AVAILABLE, &queryResultReady[queryIter]);
+
+		if (queryResultReady[queryIter] == GL_TRUE)
+		{
+			glGetQueryObjectuiv(queryIds[queryIter], GL_QUERY_RESULT, &queryResult[queryIter]);
+		}
+		else
+			TCU_FAIL("Occlusion query failed to return a result after glFinish()");
+
+		if ((queryIter % WATCHDOG_INTERVAL) == 0 && m_testCtx.getWatchDog())
+			qpWatchDog_touch(m_testCtx.getWatchDog());
+	}
+
+	glDeleteQueries	(m_numQueries, &queryIds[0]);
+	GLU_CHECK_MSG	("Occlusion queries finished");
+
+	log << tcu::TestLog::Message << "Case passed!" << tcu::TestLog::EndMessage;
+
+	return (++m_iterNdx < NUM_CASE_ITERATIONS) ? CONTINUE : STOP;
+}
+
+
+OcclusionQueryTests::OcclusionQueryTests (Context& testCtx)
+	: TestCaseGroup(testCtx, "occlusion_query", "Occlusion query stress tests")
+{
+}
+
+OcclusionQueryTests::~OcclusionQueryTests(void)
+{
+}
+
+void OcclusionQueryTests::init (void)
+{
+	addChild(new OcclusionQueryStressCase(m_context, "10_queries_2500_triangles_per_query",		"10_queries_2500_triangles_per_query",	  49, 50, 1, 50, 10,	GL_ANY_SAMPLES_PASSED));
+	addChild(new OcclusionQueryStressCase(m_context, "100_queries_2500_triangles_per_query",	"100_queries_2500_triangles_per_query",	  49, 50, 1, 50, 100,	GL_ANY_SAMPLES_PASSED));
+	addChild(new OcclusionQueryStressCase(m_context, "1000_queries_500_triangles_per_query",	"1000_queries_500_triangles_per_query",	  49, 10, 1, 10, 1000,	GL_ANY_SAMPLES_PASSED));
+	addChild(new OcclusionQueryStressCase(m_context, "10000_queries_20_triangles_per_query",	"10000_queries_20_triangles_per_query",   1,  19, 1, 1,  10000,	GL_ANY_SAMPLES_PASSED));
+}
+
+} // Stress
+} // gles3
+} // deqp
diff --git a/modules/gles3/stress/es3sOcclusionQueryTests.hpp b/modules/gles3/stress/es3sOcclusionQueryTests.hpp
new file mode 100644
index 0000000..3869699
--- /dev/null
+++ b/modules/gles3/stress/es3sOcclusionQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3SOCCLUSIONQUERYTESTS_HPP
+#define _ES3SOCCLUSIONQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Occlusion query stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+class OcclusionQueryTests : public TestCaseGroup
+{
+public:
+							OcclusionQueryTests		(Context& testCtx);
+							~OcclusionQueryTests	(void);
+
+	void					init					(void);
+
+private:
+							OcclusionQueryTests		(const OcclusionQueryTests& other);
+	OcclusionQueryTests&	operator=				(const OcclusionQueryTests& other);
+};
+
+} // Stress
+} // gles3
+} // deqp
+
+#endif // _ES3SOCCLUSIONQUERYTESTS_HPP
diff --git a/modules/gles3/stress/es3sSpecialFloatTests.cpp b/modules/gles3/stress/es3sSpecialFloatTests.cpp
new file mode 100644
index 0000000..dc2fca9
--- /dev/null
+++ b/modules/gles3/stress/es3sSpecialFloatTests.cpp
@@ -0,0 +1,2145 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Special float stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3sSpecialFloatTests.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+#include "deRandom.hpp"
+
+#include <limits>
+#include <sstream>
+
+using namespace glw;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+namespace
+{
+
+static const int		TEST_CANVAS_SIZE		= 256;
+static const int		TEST_TEXTURE_SIZE		= 128;
+static const int		TEST_TEXTURE_CUBE_SIZE	= 32;
+static const deUint32	s_specialFloats[]		=
+{
+	0x00000000,	//          zero
+	0x80000000,	// negative zero
+	0x3F800000,	//          one
+	0xBF800000,	// negative one
+	0x00800000,	// minimum positive normalized value
+	0x80800000,	// maximum negative normalized value
+	0x00000001,	// minimum positive denorm value
+	0x80000001,	// maximum negative denorm value
+	0x7F7FFFFF,	// maximum finite value.
+	0xFF7FFFFF,	// minimum finite value.
+	0x7F800000,	//  inf
+	0xFF800000,	// -inf
+	0x34000000,	//          epsilon
+	0xB4000000,	// negative epsilon
+	0x7FC00000,	//          quiet_NaN
+	0xFFC00000,	// negative quiet_NaN
+	0x7FC00001,	//          signaling_NaN
+	0xFFC00001,	// negative signaling_NaN
+	0x7FEAAAAA,	//          quiet payloaded NaN		(payload of repeated pattern of 101010...)
+	0xFFEAAAAA,	// negative quiet payloaded NaN		( .. )
+	0x7FAAAAAA,	//          signaling payloaded NaN	( .. )
+	0xFFAAAAAA,	// negative signaling payloaded NaN	( .. )
+};
+
+static const char* const s_colorPassthroughFragmentShaderSource	=	"#version 300 es\n"
+																	"layout(location = 0) out mediump vec4 fragColor;\n"
+																	"in mediump vec4 v_out;\n"
+																	"void main ()\n"
+																	"{\n"
+																	"	fragColor = v_out;\n"
+																	"}\n";
+static const char* const s_attrPassthroughVertexShaderSource	=	"#version 300 es\n"
+																	"in highp vec4 a_pos;\n"
+																	"in highp vec4 a_attr;\n"
+																	"out highp vec4 v_attr;\n"
+																	"void main ()\n"
+																	"{\n"
+																	"	v_attr = a_attr;\n"
+																	"	gl_Position = a_pos;\n"
+																	"}\n";
+
+class RenderCase : public TestCase
+{
+public:
+	enum RenderTargetType
+	{
+		RENDERTARGETTYPE_SCREEN,
+		RENDERTARGETTYPE_FBO
+	};
+
+								RenderCase			(Context& context, const char* name, const char* desc, RenderTargetType renderTargetType = RENDERTARGETTYPE_SCREEN);
+	virtual						~RenderCase			(void);
+
+	virtual void				init				(void);
+	virtual void				deinit				(void);
+
+protected:
+	bool						checkResultImage	(const tcu::Surface& result);
+	bool						drawTestPattern		(bool useTexture);
+
+	virtual std::string			genVertexSource		(void) const = 0;
+	virtual std::string			genFragmentSource	(void) const = 0;
+
+	const glu::ShaderProgram*	m_program;
+	const RenderTargetType		m_renderTargetType;
+};
+
+RenderCase::RenderCase (Context& context, const char* name, const char* desc, RenderTargetType renderTargetType)
+	: TestCase				(context, name, desc)
+	, m_program				(DE_NULL)
+	, m_renderTargetType	(renderTargetType)
+{
+}
+
+RenderCase::~RenderCase (void)
+{
+	deinit();
+}
+
+void RenderCase::init (void)
+{
+	const int width	 = m_context.getRenderTarget().getWidth();
+	const int height = m_context.getRenderTarget().getHeight();
+
+	// check target size
+	if (m_renderTargetType == RENDERTARGETTYPE_SCREEN)
+	{
+		if (width < TEST_CANVAS_SIZE || height < TEST_CANVAS_SIZE)
+			throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(TEST_CANVAS_SIZE) + "x" + de::toString(TEST_CANVAS_SIZE));
+	}
+	else if (m_renderTargetType == RENDERTARGETTYPE_FBO)
+	{
+		GLint maxTexSize = 0;
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
+
+		if (maxTexSize < TEST_CANVAS_SIZE)
+			throw tcu::NotSupportedError(std::string("GL_MAX_TEXTURE_SIZE must be at least ") + de::toString(TEST_CANVAS_SIZE));
+	}
+	else
+		DE_ASSERT(false);
+
+	// gen shader
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Creating test shader." << tcu::TestLog::EndMessage;
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource()) << glu::FragmentSource(genFragmentSource()));
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+		throw tcu::TestError("shader compile failed");
+}
+
+void RenderCase::deinit (void)
+{
+	if (m_program)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+	}
+}
+
+bool RenderCase::checkResultImage (const tcu::Surface& result)
+{
+	tcu::Surface	errorMask	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	bool			error		= false;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output image." << tcu::TestLog::EndMessage;
+
+	for (int y = 0; y < TEST_CANVAS_SIZE; ++y)
+	for (int x = 0; x < TEST_CANVAS_SIZE; ++x)
+	{
+		const tcu::RGBA col = result.getPixel(x, y);
+
+		if (col.getGreen() == 255)
+			errorMask.setPixel(x, y, tcu::RGBA::green);
+		else
+		{
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+			error = true;
+		}
+	}
+
+	if (error)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Result image has missing or invalid pixels" << tcu::TestLog::EndMessage;
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Results", "Result verification")
+			<< tcu::TestLog::Image("Result",		"Result",		result)
+			<< tcu::TestLog::Image("Error mask",	"Error mask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+	}
+	else
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Results", "Result verification")
+			<< tcu::TestLog::Image("Result", "Result", result)
+			<< tcu::TestLog::EndImageSet;
+	}
+
+	return !error;
+}
+
+bool RenderCase::drawTestPattern (bool useTexture)
+{
+	static const tcu::Vec4 fullscreenQuad[4] =
+	{
+		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),
+	};
+	const char* const	vertexSource		=	"#version 300 es\n"
+												"in highp vec4 a_pos;\n"
+												"out mediump vec4 v_position;\n"
+												"void main ()\n"
+												"{\n"
+												"	v_position = a_pos;\n"
+												"	gl_Position = a_pos;\n"
+												"}\n";
+	const char* const	fragmentSourceNoTex	=	"#version 300 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;\n"
+												"in mediump vec4 v_position;\n"
+												"void main ()\n"
+												"{\n"
+												"	fragColor = vec4((v_position.x + 1.0) * 0.5, 1.0, 1.0, 1.0);\n"
+												"}\n";
+	const char* const	fragmentSourceTex	=	"#version 300 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;\n"
+												"uniform mediump sampler2D u_sampler;\n"
+												"in mediump vec4 v_position;\n"
+												"void main ()\n"
+												"{\n"
+												"	fragColor = texture(u_sampler, v_position.xy);\n"
+												"}\n";
+	const char* const	fragmentSource		=	(useTexture) ? (fragmentSourceTex) : (fragmentSourceNoTex);
+	const tcu::RGBA		formatThreshold		= m_context.getRenderTarget().getPixelFormat().getColorThreshold();
+
+	tcu::Surface		resultImage			(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::Surface		errorMask			(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	bool				error				=	false;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a test pattern to detect " << ((useTexture) ? ("texture sampling") : ("")) << " side-effects." << tcu::TestLog::EndMessage;
+
+	// draw pattern
+	{
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const glu::ShaderProgram	patternProgram	(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexSource) << glu::FragmentSource(fragmentSource));
+		const GLint					positionLoc		= gl.getAttribLocation(patternProgram.getProgram(), "a_pos");
+		GLuint						textureID		= 0;
+
+		if (useTexture)
+		{
+			const int textureSize = 32;
+			std::vector<tcu::Vector<deUint8, 4> > buffer(textureSize*textureSize);
+
+			for (int x = 0; x < textureSize; ++x)
+			for (int y = 0; y < textureSize; ++y)
+			{
+				// sum of two axis aligned gradients. Each gradient is 127 at the edges and 0 at the center.
+				// pattern is symmetric (x and y) => no discontinuity near boundary => no need to worry of results with LINEAR filtering near boundaries
+				const deUint8 redComponent = (deUint8)de::clamp(de::abs((float)x / (float)textureSize - 0.5f) * 255.0f + de::abs((float)y / (float)textureSize - 0.5f) * 255.0f, 0.0f, 255.0f);
+
+				buffer[x * textureSize + y] = tcu::Vector<deUint8, 4>(redComponent, 255, 255, 255);
+			}
+
+			gl.genTextures(1, &textureID);
+			gl.bindTexture(GL_TEXTURE_2D, textureID);
+			gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, textureSize, textureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer[0].getPtr());
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		}
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(patternProgram.getProgram());
+
+		if (useTexture)
+			gl.uniform1i(gl.getUniformLocation(patternProgram.getProgram(), "u_sampler"), 0);
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &fullscreenQuad[0]);
+
+		gl.enableVertexAttribArray(positionLoc);
+		gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+		gl.disableVertexAttribArray(positionLoc);
+
+		gl.useProgram(0);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "RenderCase::drawTestPattern");
+
+		if (textureID)
+			gl.deleteTextures(1, &textureID);
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+	}
+
+	// verify pattern
+	for (int y = 0; y < TEST_CANVAS_SIZE; ++y)
+	for (int x = 0; x < TEST_CANVAS_SIZE; ++x)
+	{
+		const float			texGradientPosX = deFloatFrac((float)x * 2.0f / (float)TEST_CANVAS_SIZE);
+		const float			texGradientPosY = deFloatFrac((float)y * 2.0f / (float)TEST_CANVAS_SIZE);
+		const deUint8		texRedComponent = (deUint8)de::clamp(de::abs(texGradientPosX - 0.5f) * 255.0f + de::abs(texGradientPosY - 0.5f) * 255.0f, 0.0f, 255.0f);
+
+		const tcu::RGBA		refColTexture	= tcu::RGBA(texRedComponent, 255, 255, 255);
+		const tcu::RGBA		refColGradient	= tcu::RGBA((int)((float)x / (float)TEST_CANVAS_SIZE * 255.0f), 255, 255, 255);
+		const tcu::RGBA&	refCol			= (useTexture) ? (refColTexture) : (refColGradient);
+
+		const int			colorThreshold	= 10;
+		const tcu::RGBA		col				= resultImage.getPixel(x, y);
+		const tcu::IVec4	colorDiff		= tcu::abs(col.toIVec() - refCol.toIVec());
+
+		if (colorDiff.x() > formatThreshold.getRed()   + colorThreshold ||
+			colorDiff.y() > formatThreshold.getGreen() + colorThreshold ||
+			colorDiff.z() > formatThreshold.getBlue()  + colorThreshold)
+		{
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+			error = true;
+		}
+		else
+			errorMask.setPixel(x, y, tcu::RGBA::green);
+	}
+
+	// report error
+	if (error)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Test pattern has missing/invalid pixels" << tcu::TestLog::EndMessage;
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Results", "Result verification")
+			<< tcu::TestLog::Image("Result",		"Result",		resultImage)
+			<< tcu::TestLog::Image("Error mask",	"Error mask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+	}
+	else
+		m_testCtx.getLog() << tcu::TestLog::Message << "No side-effects found." << tcu::TestLog::EndMessage;
+
+	return !error;
+}
+
+class FramebufferRenderCase : public RenderCase
+{
+public:
+	enum FrameBufferType
+	{
+		FBO_DEFAULT = 0,
+		FBO_RGBA4,
+		FBO_RGB5_A1,
+		FBO_RGB565,
+		FBO_RGBA8,
+		FBO_RGB10_A2,
+		FBO_RGBA_FLOAT16,
+		FBO_RGBA_FLOAT32,
+
+		FBO_LAST
+	};
+
+							FramebufferRenderCase	(Context& context, const char* name, const char* desc, FrameBufferType fboType);
+	virtual					~FramebufferRenderCase	(void);
+
+	virtual void			init					(void);
+	virtual void			deinit					(void);
+	IterateResult			iterate					(void);
+
+	virtual void			testFBO					(void) = DE_NULL;
+
+protected:
+	const FrameBufferType	m_fboType;
+
+private:
+	GLuint					m_texID;
+	GLuint					m_fboID;
+};
+
+FramebufferRenderCase::FramebufferRenderCase (Context& context, const char* name, const char* desc, FrameBufferType fboType)
+	: RenderCase	(context, name, desc, (fboType == FBO_DEFAULT) ? (RENDERTARGETTYPE_SCREEN) : (RENDERTARGETTYPE_FBO))
+	, m_fboType		(fboType)
+	, m_texID		(0)
+	, m_fboID		(0)
+{
+	DE_ASSERT(m_fboType < FBO_LAST);
+}
+
+FramebufferRenderCase::~FramebufferRenderCase (void)
+{
+	deinit();
+}
+
+void FramebufferRenderCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// check requirements
+	if (m_fboType == FBO_RGBA_FLOAT16)
+	{
+		if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_color_buffer_half_float") &&
+			!m_context.getContextInfo().isExtensionSupported("GL_EXT_color_buffer_float"))
+			throw tcu::NotSupportedError("Color renderable half float texture required.");
+	}
+	else if (m_fboType == FBO_RGBA_FLOAT32)
+	{
+		if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_color_buffer_float"))
+			throw tcu::NotSupportedError("Color renderable float texture required.");
+	}
+
+	// gen shader
+	RenderCase::init();
+
+	// create render target
+	if (m_fboType == FBO_DEFAULT)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Using default framebuffer." << tcu::TestLog::EndMessage;
+	}
+	else
+	{
+		GLuint internalFormat	= 0;
+		GLuint format			= 0;
+		GLuint type				= 0;
+
+		switch (m_fboType)
+		{
+			case FBO_RGBA4:			internalFormat = GL_RGBA4;		format = GL_RGBA;	type = GL_UNSIGNED_SHORT_4_4_4_4;		break;
+			case FBO_RGB5_A1:		internalFormat = GL_RGB5_A1;	format = GL_RGBA;	type = GL_UNSIGNED_SHORT_5_5_5_1;		break;
+			case FBO_RGB565:		internalFormat = GL_RGB565;		format = GL_RGB;	type = GL_UNSIGNED_SHORT_5_6_5;			break;
+			case FBO_RGBA8:			internalFormat = GL_RGBA8;		format = GL_RGBA;	type = GL_UNSIGNED_BYTE;				break;
+			case FBO_RGB10_A2:		internalFormat = GL_RGB10_A2;	format = GL_RGBA;	type = GL_UNSIGNED_INT_2_10_10_10_REV;	break;
+			case FBO_RGBA_FLOAT16:	internalFormat = GL_RGBA16F;	format = GL_RGBA;	type = GL_HALF_FLOAT;					break;
+			case FBO_RGBA_FLOAT32:	internalFormat = GL_RGBA32F;	format = GL_RGBA;	type = GL_FLOAT;						break;
+
+			default:
+				DE_ASSERT(false);
+				break;
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message
+			<< "Creating fbo. Texture internalFormat = " << glu::getPixelFormatStr(internalFormat)
+			<< ", format = " << glu::getPixelFormatStr(format)
+			<< ", type = " << glu::getTypeStr(type)
+			<< tcu::TestLog::EndMessage;
+
+		// gen texture
+		gl.genTextures(1, &m_texID);
+		gl.bindTexture(GL_TEXTURE_2D, m_texID);
+		gl.texImage2D(GL_TEXTURE_2D, 0, internalFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, 0, format, type, DE_NULL);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "texture init");
+
+		// gen fbo
+		gl.genFramebuffers(1, &m_fboID);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
+		gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texID, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "fbo init");
+
+		if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+			throw tcu::NotSupportedError("could not create fbo for testing.");
+	}
+}
+
+void FramebufferRenderCase::deinit (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_texID)
+	{
+		gl.deleteTextures(1, &m_texID);
+		m_texID = 0;
+	}
+
+	if (m_fboID)
+	{
+		gl.deleteFramebuffers(1, &m_fboID);
+		m_fboID = 0;
+	}
+}
+
+FramebufferRenderCase::IterateResult FramebufferRenderCase::iterate (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// bind fbo (or don't if we are using default)
+	if (m_fboID)
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
+
+	// do something with special floats
+	testFBO();
+
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats as vertex attributes
+ *
+ * Tests that special floats transferred to the shader using vertex
+ * attributes do not change the results of normal floating point
+ * calculations. Special floats are put to 4-vector's x and y components and
+ * value 1.0 is put to z and w. The resulting fragment's green channel
+ * should be 1.0 everywhere.
+ *
+ * After the calculation test a test pattern is drawn to detect possible
+ * floating point operation anomalies.
+ *//*--------------------------------------------------------------------*/
+class VertexAttributeCase : public RenderCase
+{
+public:
+	enum Storage
+	{
+		STORAGE_BUFFER = 0,
+		STORAGE_CLIENT,
+
+		STORAGE_LAST
+	};
+	enum ShaderType
+	{
+		TYPE_VERTEX = 0,
+		TYPE_FRAGMENT,
+
+		TYPE_LAST
+	};
+
+						VertexAttributeCase			(Context& context, const char* name, const char* desc, Storage storage, ShaderType type);
+						~VertexAttributeCase		(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+private:
+	std::string			genVertexSource				(void) const;
+	std::string			genFragmentSource			(void) const;
+
+	const Storage		m_storage;
+	const ShaderType	m_type;
+	GLuint				m_positionVboID;
+	GLuint				m_attribVboID;
+	GLuint				m_elementVboID;
+};
+
+VertexAttributeCase::VertexAttributeCase (Context& context, const char* name, const char* desc, Storage storage, ShaderType type)
+	: RenderCase			(context, name, desc)
+	, m_storage				(storage)
+	, m_type				(type)
+	, m_positionVboID		(0)
+	, m_attribVboID			(0)
+	, m_elementVboID		(0)
+{
+	DE_ASSERT(storage < STORAGE_LAST);
+	DE_ASSERT(type < TYPE_LAST);
+}
+
+VertexAttributeCase::~VertexAttributeCase (void)
+{
+	deinit();
+}
+
+void VertexAttributeCase::init (void)
+{
+	RenderCase::init();
+
+	// init gl resources
+	if (m_storage == STORAGE_BUFFER)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.genBuffers(1, &m_positionVboID);
+		gl.genBuffers(1, &m_attribVboID);
+		gl.genBuffers(1, &m_elementVboID);
+	}
+}
+
+void VertexAttributeCase::deinit (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	RenderCase::deinit();
+
+	if (m_attribVboID)
+	{
+		gl.deleteBuffers(1, &m_attribVboID);
+		m_attribVboID = 0;
+	}
+
+	if (m_positionVboID)
+	{
+		gl.deleteBuffers(1, &m_positionVboID);
+		m_positionVboID = 0;
+	}
+
+	if (m_elementVboID)
+	{
+		gl.deleteBuffers(1, &m_elementVboID);
+		m_elementVboID = 0;
+	}
+}
+
+VertexAttributeCase::IterateResult VertexAttributeCase::iterate (void)
+{
+	// Create a [s_specialFloats] X [s_specialFloats] grid of vertices with each vertex having 2 [s_specialFloats] values
+	// and calculate some basic operations with the floating point values. If all goes well, nothing special should happen
+
+	std::vector<tcu::Vec4>	gridVertices	(DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats));
+	std::vector<tcu::UVec4>	gridAttributes	(DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats));
+	std::vector<deUint16>	indices			((DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * 6);
+	tcu::Surface			resultImage		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+	{
+		const deUint32	one		= 0x3F800000;
+		const float		posX	= (float)x / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) - 1] to [-1, 1]
+		const float		posY	= (float)y / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f;
+
+		gridVertices[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y]	= tcu::Vec4(posX, posY, 0.0f, 1.0f);
+		gridAttributes[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y]	= tcu::UVec4(s_specialFloats[x], s_specialFloats[y], one, one);
+	}
+
+	// tiles
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++y)
+	{
+		const int baseNdx = (x * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) + y) * 6;
+
+		indices[baseNdx + 0] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+		indices[baseNdx + 1] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+		indices[baseNdx + 2] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+
+		indices[baseNdx + 3] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+		indices[baseNdx + 4] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+		indices[baseNdx + 5] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a grid with the shader. Setting a_attr for each vertex to (special, special, 1, 1)." << tcu::TestLog::EndMessage;
+
+	// Draw grid
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				attribLoc	= gl.getAttribLocation(m_program->getProgram(), "a_attr");
+
+		if (m_storage == STORAGE_BUFFER)
+		{
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_positionVboID);
+			gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(gridVertices.size() * sizeof(tcu::Vec4)), &gridVertices[0], GL_STATIC_DRAW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate");
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_attribVboID);
+			gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(gridAttributes.size() * sizeof(tcu::UVec4)), &gridAttributes[0], GL_STATIC_DRAW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate");
+
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementVboID);
+			gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indices.size() * sizeof(deUint16)), &indices[0], GL_STATIC_DRAW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate");
+		}
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+
+		if (m_storage == STORAGE_BUFFER)
+		{
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_positionVboID);
+			gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_attribVboID);
+			gl.vertexAttribPointer(attribLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+
+			gl.enableVertexAttribArray(positionLoc);
+			gl.enableVertexAttribArray(attribLoc);
+			gl.drawElements(GL_TRIANGLES, (glw::GLsizei)(indices.size()), GL_UNSIGNED_SHORT, DE_NULL);
+			gl.disableVertexAttribArray(positionLoc);
+			gl.disableVertexAttribArray(attribLoc);
+
+			gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+		}
+		else if (m_storage == STORAGE_CLIENT)
+		{
+			gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+			gl.vertexAttribPointer(attribLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridAttributes[0]);
+
+			gl.enableVertexAttribArray(positionLoc);
+			gl.enableVertexAttribArray(attribLoc);
+			gl.drawElements(GL_TRIANGLES, (glw::GLsizei)(indices.size()), GL_UNSIGNED_SHORT, &indices[0]);
+			gl.disableVertexAttribArray(positionLoc);
+			gl.disableVertexAttribArray(attribLoc);
+		}
+		else
+			DE_ASSERT(false);
+
+		gl.useProgram(0);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate");
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+	}
+
+	// verify everywhere was drawn (all pixels have Green = 255)
+	if (!checkResultImage(resultImage))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing or invalid fragments");
+		return STOP;
+	}
+
+	// test drawing still works
+	if (!drawTestPattern(false))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "test pattern failed");
+		return STOP;
+	}
+
+	// all ok
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+std::string VertexAttributeCase::genVertexSource (void) const
+{
+	if (m_type == TYPE_VERTEX)
+		return
+			"#version 300 es\n"
+			"in highp vec4 a_pos;\n"
+			"in highp vec4 a_attr;\n"
+			"out mediump vec4 v_out;\n"
+			"void main ()\n"
+			"{\n"
+			"	highp vec2 a1 = a_attr.xz + a_attr.yw; // add\n"
+			"	highp vec2 a2 = a_attr.xz - a_attr.yw; // sub\n"
+			"	highp vec2 a3 = a_attr.xz * a_attr.yw; // mul\n"
+			"	highp vec2 a4 = a_attr.xz / a_attr.yw; // div\n"
+			"	highp vec2 a5 = a_attr.xz + a_attr.yw * a_attr.xz; // fma\n"
+			"\n"
+			"	highp float green = 1.0 - abs((a1.y + a2.y + a3.y + a4.y + a5.y) - 6.0);\n"
+			"	v_out = vec4(a1.x*a3.x + a2.x*a4.x, green, a5.x, 1.0);\n"
+			"	gl_Position = a_pos;\n"
+			"}\n";
+	else
+		return s_attrPassthroughVertexShaderSource;
+}
+
+std::string VertexAttributeCase::genFragmentSource (void) const
+{
+	if (m_type == TYPE_VERTEX)
+		return s_colorPassthroughFragmentShaderSource;
+	else
+		return
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"in highp vec4 v_attr;\n"
+			"void main ()\n"
+			"{\n"
+			"	highp vec2 a1 = v_attr.xz + v_attr.yw; // add\n"
+			"	highp vec2 a2 = v_attr.xz - v_attr.yw; // sub\n"
+			"	highp vec2 a3 = v_attr.xz * v_attr.yw; // mul\n"
+			"	highp vec2 a4 = v_attr.xz / v_attr.yw; // div\n"
+			"	highp vec2 a5 = v_attr.xz + v_attr.yw * v_attr.xz; // fma\n"
+			"	highp vec2 a6 = dFdx(v_attr.xz);\n"
+			"\n"
+			"	highp float green = 1.0 - abs((a1.y + a2.y + a3.y + a4.y + a5.y + a6.y) - 6.0);\n"
+			"	fragColor = vec4(a1.x*a3.x + a2.x*a4.x, green, a5.x+a6.x, 1.0);\n"
+			"}\n";
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats as uniforms
+ *
+ * Tests that special floats transferred to the shader as uniforms do
+ * not change the results of normal floating point calculations. Special
+ * floats are put to 4-vector's x and y components and value 1.0 is put to
+ * z and w. The resulting fragment's green channel should be 1.0
+ * everywhere.
+ *
+ * After the calculation test a test pattern is drawn to detect possible
+ * floating point operation anomalies.
+ *//*--------------------------------------------------------------------*/
+class UniformCase : public RenderCase
+{
+public:
+	enum ShaderType
+	{
+		TYPE_VERTEX = 0,
+		TYPE_FRAGMENT,
+	};
+
+						UniformCase					(Context& context, const char* name, const char* desc, ShaderType type);
+						~UniformCase				(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+private:
+	std::string			genVertexSource				(void) const;
+	std::string			genFragmentSource			(void) const;
+
+	const ShaderType	m_type;
+};
+
+UniformCase::UniformCase (Context& context, const char* name, const char* desc, ShaderType type)
+	: RenderCase	(context, name, desc)
+	, m_type		(type)
+{
+}
+
+UniformCase::~UniformCase (void)
+{
+	deinit();
+}
+
+void UniformCase::init (void)
+{
+	RenderCase::init();
+}
+
+void UniformCase::deinit (void)
+{
+	RenderCase::deinit();
+}
+
+UniformCase::IterateResult UniformCase::iterate (void)
+{
+	// Create a [s_specialFloats] X [s_specialFloats] grid of tile with each tile having 2 [s_specialFloats] values
+	// and calculate some basic operations with the floating point values. If all goes well, nothing special should happen
+
+	std::vector<tcu::Vec4>	gridVertices	((DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1));
+	std::vector<deUint16>	indices			(DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats) * 6);
+	tcu::Surface			resultImage		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++y)
+	{
+		const float		posX	= (float)x / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) ] to [-1, 1]
+		const float		posY	= (float)y / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f;
+
+		gridVertices[x * (DE_LENGTH_OF_ARRAY(s_specialFloats)+1) + y] = tcu::Vec4(posX, posY, 0.0f, 1.0f);
+	}
+
+	// tiles
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+	{
+		const int baseNdx = (x * (DE_LENGTH_OF_ARRAY(s_specialFloats)) + y) * 6;
+
+		indices[baseNdx + 0] = (x+0) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+0);
+		indices[baseNdx + 1] = (x+1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+1);
+		indices[baseNdx + 2] = (x+1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+0);
+
+		indices[baseNdx + 3] = (x+0) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+0);
+		indices[baseNdx + 4] = (x+1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+1);
+		indices[baseNdx + 5] = (x+0) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+1);
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a grid with the shader. Setting u_special for vertex each tile to (special, special, 1, 1)." << tcu::TestLog::EndMessage;
+
+	// Draw grid
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				specialLoc	= gl.getUniformLocation(m_program->getProgram(), "u_special");
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+		gl.enableVertexAttribArray(positionLoc);
+
+		for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+		for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+		{
+			const deUint32		one				= 0x3F800000;
+			const tcu::UVec4	uniformValue	= tcu::UVec4(s_specialFloats[x], s_specialFloats[y], one, one);
+			const int			indexIndex		= (x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y) * 6;
+
+			gl.uniform4fv(specialLoc, 1, (const float*)uniformValue.getPtr());
+			gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, &indices[indexIndex]);
+		}
+
+
+		gl.disableVertexAttribArray(positionLoc);
+
+		gl.useProgram(0);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "UniformCase::iterate");
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+	}
+
+	// verify everywhere was drawn (all pixels have Green = 255)
+	if (!checkResultImage(resultImage))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing or invalid fragments");
+		return STOP;
+	}
+
+	// test drawing still works
+	if (!drawTestPattern(false))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "test pattern failed");
+		return STOP;
+	}
+
+	// all ok
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+std::string UniformCase::genVertexSource (void) const
+{
+	if (m_type == TYPE_VERTEX)
+		return
+			"#version 300 es\n"
+			"in highp vec4 a_pos;\n"
+			"uniform highp vec4 u_special;\n"
+			"out mediump vec4 v_out;\n"
+			"void main ()\n"
+			"{\n"
+			"	highp vec2 a1 = u_special.xz + u_special.yw; // add\n"
+			"	highp vec2 a2 = u_special.xz - u_special.yw; // sub\n"
+			"	highp vec2 a3 = u_special.xz * u_special.yw; // mul\n"
+			"	highp vec2 a4 = u_special.xz / u_special.yw; // div\n"
+			"	highp vec2 a5 = u_special.xz + u_special.yw * u_special.xz; // fma\n"
+			"\n"
+			"	highp float green = 1.0 - abs((a1.y + a2.y + a3.y + a4.y + a5.y) - 6.0);\n"
+			"	v_out = vec4(a1.x*a3.x + a2.x*a4.x, green, a5.x, 1.0);\n"
+			"	gl_Position = a_pos;\n"
+			"}\n";
+	else
+		return
+			"#version 300 es\n"
+			"in highp vec4 a_pos;\n"
+			"void main ()\n"
+			"{\n"
+			"	gl_Position = a_pos;\n"
+			"}\n";
+}
+
+std::string UniformCase::genFragmentSource (void) const
+{
+	if (m_type == TYPE_VERTEX)
+		return s_colorPassthroughFragmentShaderSource;
+	else
+		return
+			"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"uniform highp vec4 u_special;\n"
+			"void main ()\n"
+			"{\n"
+			"	highp vec2 a1 = u_special.xz + u_special.yw; // add\n"
+			"	highp vec2 a2 = u_special.xz - u_special.yw; // sub\n"
+			"	highp vec2 a3 = u_special.xz * u_special.yw; // mul\n"
+			"	highp vec2 a4 = u_special.xz / u_special.yw; // div\n"
+			"	highp vec2 a5 = u_special.xz + u_special.yw * u_special.xz; // fma\n"
+			"	highp vec2 a6 = mod(u_special.xz, u_special.yw);\n"
+			"	highp vec2 a7 = mix(u_special.xz, u_special.yw, a6);\n"
+			"\n"
+			"	highp float green = 1.0 - abs((a1.y + a2.y + a3.y + a4.y + a5.y + a6.y + a7.y) - 7.0);\n"
+			"	fragColor = vec4(a1.x*a3.x, green, a5.x*a4.x + a2.x*a7.x, 1.0);\n"
+			"}\n";
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats in floating point textures
+ *
+ * Tests that sampling special floats from a floating point texture
+ * does not affect the values of other color components of the sample. Test
+ * samples a RG texture with R channel filled with special floats and G
+ * channel filled with test values.
+ *
+ * Tests that linear sampling using compare mode = COMPARE_REF_TO_TEXTURE
+ * of a floating point depth texture containing special floating point
+ * values does not produce values outside the [0, 1] range.
+ *
+ * After the calculation test a test pattern is drawn to detect possible
+ * texture sampling anomalies.
+ *//*--------------------------------------------------------------------*/
+class TextureCase : public RenderCase
+{
+public:
+	enum ShaderType
+	{
+		TYPE_VERTEX = 0,
+		TYPE_FRAGMENT,
+
+		TYPE_LAST
+	};
+	enum TextureType
+	{
+		TEXTURE_FLOAT = 0,
+		TEXTURE_DEPTH,
+
+		TEXTURE_LAST
+	};
+	enum UploadType
+	{
+		UPLOAD_CLIENT = 0,
+		UPLOAD_PBO,
+
+		UPLOAD_LAST
+	};
+
+						TextureCase					(Context& context, const char* name, const char* desc, ShaderType type, TextureType texType, UploadType uploadType);
+						~TextureCase				(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+private:
+	std::string			genVertexSource				(void) const;
+	std::string			genFragmentSource			(void) const;
+
+	const ShaderType	m_type;
+	const TextureType	m_textureType;
+	const UploadType	m_uploadType;
+	GLuint				m_textureID;
+	GLuint				m_pboID;
+};
+
+TextureCase::TextureCase (Context& context, const char* name, const char* desc, ShaderType type, TextureType texType, UploadType uploadType)
+	: RenderCase	(context, name, desc)
+	, m_type		(type)
+	, m_textureType	(texType)
+	, m_uploadType	(uploadType)
+	, m_textureID	(0)
+	, m_pboID		(0)
+{
+	DE_ASSERT(type < TYPE_LAST);
+	DE_ASSERT(texType < TEXTURE_LAST);
+	DE_ASSERT(uploadType < UPLOAD_LAST);
+}
+
+TextureCase::~TextureCase (void)
+{
+	deinit();
+}
+
+void TextureCase::init (void)
+{
+	// requirements
+	{
+		GLint maxTextureSize = 0;
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+		if (maxTextureSize < TEST_TEXTURE_SIZE)
+			throw tcu::NotSupportedError(std::string("GL_MAX_TEXTURE_SIZE must be at least ") + de::toString(TEST_TEXTURE_SIZE));
+	}
+
+	RenderCase::init();
+
+	// gen texture
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		de::Random				rnd			(12345);
+
+		gl.genTextures(1, &m_textureID);
+		gl.bindTexture(GL_TEXTURE_2D, m_textureID);
+
+		if (m_uploadType == UPLOAD_PBO)
+		{
+			gl.genBuffers(1, &m_pboID);
+			gl.bindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pboID);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "TextureCase::init");
+		}
+
+		if (m_textureType == TEXTURE_FLOAT)
+		{
+			std::vector<deUint32>	texData	(TEST_TEXTURE_SIZE*TEST_TEXTURE_SIZE*2);
+			const deUint32*			dataPtr = (m_uploadType == UPLOAD_CLIENT) ? (&texData[0]) : (DE_NULL);
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating a 2D 2-component float texture. Pixel contents are of form (special, 1)." << tcu::TestLog::EndMessage;
+
+			// set green channel to 1.0
+			for (int x = 0; x < TEST_TEXTURE_SIZE; ++x)
+			for (int y = 0; y < TEST_TEXTURE_SIZE; ++y)
+			{
+				texData[(x * TEST_TEXTURE_SIZE + y) * 2 + 0] = rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats));
+				texData[(x * TEST_TEXTURE_SIZE + y) * 2 + 1] = 0x3F800000;	// one
+			}
+
+			if (m_uploadType == UPLOAD_PBO)
+				gl.bufferData(GL_PIXEL_UNPACK_BUFFER, (glw::GLsizeiptr)(texData.size() * sizeof(deUint32)), &texData[0], GL_STATIC_DRAW);
+
+			gl.texImage2D(GL_TEXTURE_2D, 0, GL_RG32F, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, 0, GL_RG, GL_FLOAT, dataPtr);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "TextureCase::init");
+		}
+		else if (m_textureType == TEXTURE_DEPTH)
+		{
+			std::vector<deUint32>	texData	(TEST_TEXTURE_SIZE*TEST_TEXTURE_SIZE);
+			const deUint32*			dataPtr = (m_uploadType == UPLOAD_CLIENT) ? (&texData[0]) : (DE_NULL);
+
+			m_testCtx.getLog() << tcu::TestLog::Message
+				<< "Creating a 2D depth texture and filling it with special floating point values.\n"
+				<< "  TEXTURE_COMPARE_MODE = COMPARE_REF_TO_TEXTURE"
+				<< tcu::TestLog::EndMessage;
+
+			for (int x = 0; x < TEST_TEXTURE_SIZE; ++x)
+			for (int y = 0; y < TEST_TEXTURE_SIZE; ++y)
+				texData[x * TEST_TEXTURE_SIZE + y] = rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats));
+
+			if (m_uploadType == UPLOAD_PBO)
+				gl.bufferData(GL_PIXEL_UNPACK_BUFFER, (glw::GLsizeiptr)(texData.size() * sizeof(deUint32)), &texData[0], GL_STATIC_DRAW);
+
+			gl.texImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, 0, GL_DEPTH_COMPONENT, GL_FLOAT, dataPtr);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "TextureCase::init");
+		}
+		else
+			DE_ASSERT(false);
+
+		if (m_uploadType == UPLOAD_PBO)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "PBO used for image upload." << tcu::TestLog::EndMessage;
+
+			gl.bindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+			gl.deleteBuffers(1, &m_pboID);
+			m_pboID = 0;
+		}
+	}
+}
+
+void TextureCase::deinit (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	RenderCase::deinit();
+
+	if (m_textureID)
+	{
+		gl.deleteTextures(1, &m_textureID);
+		m_textureID = 0;
+	}
+	if (m_pboID)
+	{
+		gl.deleteBuffers(1, &m_pboID);
+		m_pboID = 0;
+	}
+}
+
+TextureCase::IterateResult TextureCase::iterate (void)
+{
+	// Draw a grid and texture it with a floating point texture containing special values. If all goes well, nothing special should happen
+
+	const int				gridSize		= 16;
+	std::vector<tcu::Vec4>	gridVertices	(gridSize * gridSize);
+	std::vector<tcu::Vec2>	gridTexCoords	(gridSize * gridSize);
+	std::vector<deUint16>	indices			((gridSize - 1) * (gridSize - 1) * 6);
+	tcu::Surface			resultImage		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int x = 0; x < gridSize; ++x)
+	for (int y = 0; y < gridSize; ++y)
+	{
+		const float posX		= (float)x / ((float)gridSize - 1.0f) * 2.0f - 1.0f; // map from [0, gridSize - 1] to [-1, 1]
+		const float posY		= (float)y / ((float)gridSize - 1.0f) * 2.0f - 1.0f;
+		const float texCoordX	= deFloatPow(2.0f, (float)x - (float)gridSize / 2.0f);
+		const float texCoordY	= deFloatPow(2.0f, (float)y - (float)gridSize / 2.0f);
+
+		gridVertices[x * gridSize + y]	= tcu::Vec4(posX, posY, 0.0f, 1.0f);
+		gridTexCoords[x * gridSize + y]	= tcu::Vec2(texCoordX, texCoordY);
+	}
+
+	// tiles
+	for (int x = 0; x < gridSize - 1; ++x)
+	for (int y = 0; y < gridSize - 1; ++y)
+	{
+		const int baseNdx = (x * (gridSize - 1) + y) * 6;
+
+		indices[baseNdx + 0] = (x+0) * gridSize + (y+0);
+		indices[baseNdx + 1] = (x+1) * gridSize + (y+1);
+		indices[baseNdx + 2] = (x+1) * gridSize + (y+0);
+
+		indices[baseNdx + 3] = (x+0) * gridSize + (y+0);
+		indices[baseNdx + 4] = (x+1) * gridSize + (y+1);
+		indices[baseNdx + 5] = (x+0) * gridSize + (y+1);
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a textured grid with the shader." << tcu::TestLog::EndMessage;
+
+	// Draw grid
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				texCoordLoc	= gl.getAttribLocation(m_program->getProgram(), "a_attr");
+		const GLint				samplerLoc	= gl.getUniformLocation(m_program->getProgram(), "u_sampler");
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+
+		gl.uniform1i(samplerLoc, 0);
+		gl.bindTexture(GL_TEXTURE_2D, m_textureID);
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+		gl.vertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, &gridTexCoords[0]);
+
+		gl.enableVertexAttribArray(positionLoc);
+		gl.enableVertexAttribArray(texCoordLoc);
+		gl.drawElements(GL_TRIANGLES, (glw::GLsizei)(indices.size()), GL_UNSIGNED_SHORT, &indices[0]);
+		gl.disableVertexAttribArray(positionLoc);
+		gl.disableVertexAttribArray(texCoordLoc);
+
+		gl.useProgram(0);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "TextureCase::iterate");
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+	}
+
+	// verify everywhere was drawn (all pixels have Green = 255)
+	if (!checkResultImage(resultImage))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing or invalid fragments");
+		return STOP;
+	}
+
+	// test drawing and textures still works
+	if (!drawTestPattern(true))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "test pattern failed");
+		return STOP;
+	}
+
+	// all ok
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+std::string TextureCase::genVertexSource (void) const
+{
+	// vertex shader is passthrough, fragment does the calculations
+	if (m_type == TYPE_FRAGMENT)
+		return s_attrPassthroughVertexShaderSource;
+
+	// vertex shader does the calculations
+	std::ostringstream buf;
+	buf <<	"#version 300 es\n"
+			"in highp vec4 a_pos;\n"
+			"in highp vec2 a_attr;\n"
+			"out mediump vec4 v_out;\n";
+
+	if (m_textureType == TEXTURE_FLOAT)
+		buf <<	"uniform highp sampler2D u_sampler;\n";
+	else if (m_textureType == TEXTURE_DEPTH)
+		buf <<	"uniform highp sampler2DShadow u_sampler;\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf <<	"void main ()\n"
+			"{\n";
+
+	if (m_textureType == TEXTURE_FLOAT)
+		buf <<	"	v_out = vec4(textureLod(u_sampler, a_attr, 0.0).rg, 1.0, 1.0);\n";
+	else if (m_textureType == TEXTURE_DEPTH)
+		buf <<	"	highp float a1 = textureLod(u_sampler, vec3(a_attr, 0.0), 0.0);\n"
+				"	v_out = vec4(a1, (a1 > 1.0 || a1 < 0.0) ? (0.0) : (1.0), 1.0, 1.0);\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf <<	"	gl_Position = a_pos;\n"
+			"}\n";
+	return buf.str();
+}
+
+std::string TextureCase::genFragmentSource (void) const
+{
+	// fragment shader is passthrough
+	if (m_type == TYPE_VERTEX)
+		return s_colorPassthroughFragmentShaderSource;
+
+	// fragment shader does the calculations
+	std::ostringstream buf;
+	buf <<	"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n";
+
+	if (m_textureType == TEXTURE_FLOAT)
+		buf <<	"uniform highp sampler2D u_sampler;\n";
+	else if (m_textureType == TEXTURE_DEPTH)
+		buf <<	"uniform highp sampler2DShadow u_sampler;\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf <<	"in highp vec4 v_attr;\n"
+			"void main ()\n"
+			"{\n";
+
+	if (m_textureType == TEXTURE_FLOAT)
+		buf <<	"	highp vec2 a1 = texture(u_sampler, v_attr.xy).rg;\n"
+				"	fragColor = vec4(a1.x, a1.y, 1.0, 1.0);\n";
+	else if (m_textureType == TEXTURE_DEPTH)
+		buf <<	"	highp float a1 = texture(u_sampler, vec3(v_attr.xy, 0.0));\n"
+				"	fragColor = vec4(a1, (a1 > 1.0 || a1 < 0.0) ? (0.0) : (1.0), 1.0, 1.0);\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "}\n";
+	return buf.str();
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats as texture samping arguments
+ *
+ * Tests that special floats given as texture coordinates or LOD levels
+ * to sampling functions do not return invalid values (values not in the
+ * texture). Every texel's green component is 1.0.
+ *
+ * After the calculation test a test pattern is drawn to detect possible
+ * texture sampling anomalies.
+ *//*--------------------------------------------------------------------*/
+class TextureSamplerCase : public RenderCase
+{
+public:
+	enum ShaderType
+	{
+		TYPE_VERTEX = 0,
+		TYPE_FRAGMENT,
+
+		TYPE_LAST
+	};
+	enum TestType
+	{
+		TEST_TEX_COORD = 0,
+		TEST_LOD,
+		TEST_GRAD,
+		TEST_TEX_COORD_CUBE,
+
+		TEST_LAST
+	};
+
+						TextureSamplerCase			(Context& context, const char* name, const char* desc, ShaderType type, TestType testType);
+						~TextureSamplerCase			(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+private:
+	std::string			genVertexSource				(void) const;
+	std::string			genFragmentSource			(void) const;
+
+	const ShaderType	m_type;
+	const TestType		m_testType;
+	GLuint				m_textureID;
+};
+
+TextureSamplerCase::TextureSamplerCase (Context& context, const char* name, const char* desc, ShaderType type, TestType testType)
+	: RenderCase	(context, name, desc)
+	, m_type		(type)
+	, m_testType	(testType)
+	, m_textureID	(0)
+{
+	DE_ASSERT(type < TYPE_LAST);
+	DE_ASSERT(testType < TEST_LAST);
+}
+
+TextureSamplerCase::~TextureSamplerCase (void)
+{
+	deinit();
+}
+
+void TextureSamplerCase::init (void)
+{
+	// requirements
+	{
+		GLint maxTextureSize = 0;
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+		if (maxTextureSize < TEST_TEXTURE_SIZE)
+			throw tcu::NotSupportedError(std::string("GL_MAX_TEXTURE_SIZE must be at least ") + de::toString(TEST_TEXTURE_SIZE));
+	}
+
+	RenderCase::init();
+
+	// gen texture
+	{
+		const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+		std::vector<deUint8>	texData	(TEST_TEXTURE_SIZE*TEST_TEXTURE_SIZE*4);
+		de::Random				rnd		(12345);
+
+		gl.genTextures(1, &m_textureID);
+
+		for (int x = 0; x < TEST_TEXTURE_SIZE; ++x)
+		for (int y = 0; y < TEST_TEXTURE_SIZE; ++y)
+		{
+			// RGBA8, green and alpha channel are always 255 for verification
+			texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 0] = rnd.getUint32() & 0xFF;
+			texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 1] = 0xFF;
+			texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 2] = rnd.getUint32() & 0xFF;
+			texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 3] = 0xFF;
+		}
+
+		if (m_testType == TEST_TEX_COORD)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating a 2D texture with a test pattern." << tcu::TestLog::EndMessage;
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textureID);
+			gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData[0]);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::init");
+		}
+		else if (m_testType == TEST_LOD || m_testType == TEST_GRAD)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating a mipmapped 2D texture with a test pattern." << tcu::TestLog::EndMessage;
+
+			gl.bindTexture(GL_TEXTURE_2D, m_textureID);
+
+			for (int level = 0; (TEST_TEXTURE_SIZE >> level); ++level)
+				gl.texImage2D(GL_TEXTURE_2D, level, GL_RGBA8, TEST_TEXTURE_SIZE >> level, TEST_TEXTURE_SIZE >> level, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData[0]);
+
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::init");
+		}
+		else if (m_testType == TEST_TEX_COORD_CUBE)
+		{
+			DE_STATIC_ASSERT(TEST_TEXTURE_CUBE_SIZE <= TEST_TEXTURE_SIZE);
+
+			static const GLenum faces[] =
+			{
+				GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+				GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+				GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+				GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+			};
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating a cube map with a test pattern." << tcu::TestLog::EndMessage;
+
+			gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_textureID);
+
+			for (int faceNdx = 0; faceNdx < DE_LENGTH_OF_ARRAY(faces); ++faceNdx)
+				gl.texImage2D(faces[faceNdx], 0, GL_RGBA8, TEST_TEXTURE_CUBE_SIZE, TEST_TEXTURE_CUBE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData[0]);
+
+			gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::init");
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+void TextureSamplerCase::deinit (void)
+{
+	RenderCase::deinit();
+
+	if (m_textureID)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.deleteTextures(1, &m_textureID);
+		m_textureID = 0;
+	}
+}
+
+TextureSamplerCase::IterateResult TextureSamplerCase::iterate (void)
+{
+	// Draw a grid and texture it with a texture and sample it using special special values. The result samples should all have the green channel at 255 as per the test image.
+
+	std::vector<tcu::Vec4>	gridVertices	(DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats));
+	std::vector<tcu::UVec2>	gridTexCoords	(DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats));
+	std::vector<deUint16>	indices			((DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * 6);
+	tcu::Surface			resultImage		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+	{
+		const float posX = (float)x / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) - 1] to [-1, 1]
+		const float posY = (float)y / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f;
+
+		gridVertices[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y]	= tcu::Vec4(posX, posY, 0.0f, 1.0f);
+		gridTexCoords[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y]	= tcu::UVec2(s_specialFloats[x], s_specialFloats[y]);
+	}
+
+	// tiles
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++x)
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++y)
+	{
+		const int baseNdx = (x * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) + y) * 6;
+
+		indices[baseNdx + 0] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+		indices[baseNdx + 1] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+		indices[baseNdx + 2] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+
+		indices[baseNdx + 3] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0);
+		indices[baseNdx + 4] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+		indices[baseNdx + 5] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1);
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a textured grid with the shader. Sampling from the texture using special floating point values." << tcu::TestLog::EndMessage;
+
+	// Draw grid
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				texCoordLoc	= gl.getAttribLocation(m_program->getProgram(), "a_attr");
+		const GLint				samplerLoc	= gl.getUniformLocation(m_program->getProgram(), "u_sampler");
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+
+		gl.uniform1i(samplerLoc, 0);
+		if (m_testType != TEST_TEX_COORD_CUBE)
+			gl.bindTexture(GL_TEXTURE_2D, m_textureID);
+		else
+			gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_textureID);
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+		gl.vertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, &gridTexCoords[0]);
+
+		gl.enableVertexAttribArray(positionLoc);
+		gl.enableVertexAttribArray(texCoordLoc);
+		gl.drawElements(GL_TRIANGLES, (glw::GLsizei)(indices.size()), GL_UNSIGNED_SHORT, &indices[0]);
+		gl.disableVertexAttribArray(positionLoc);
+		gl.disableVertexAttribArray(texCoordLoc);
+
+		gl.useProgram(0);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::iterate");
+
+		glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+	}
+
+	// verify everywhere was drawn and samples were from the texture (all pixels have Green = 255)
+	if (!checkResultImage(resultImage))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing or invalid fragments");
+		return STOP;
+	}
+
+	// test drawing and textures still works
+	if (!drawTestPattern(true))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "test pattern failed");
+		return STOP;
+	}
+
+	// all ok
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+std::string TextureSamplerCase::genVertexSource (void) const
+{
+	// vertex shader is passthrough, fragment does the calculations
+	if (m_type == TYPE_FRAGMENT)
+		return s_attrPassthroughVertexShaderSource;
+
+	// vertex shader does the calculations
+	std::ostringstream buf;
+	buf <<	"#version 300 es\n"
+			"in highp vec4 a_pos;\n"
+			"in highp vec2 a_attr;\n";
+
+	if (m_testType != TEST_TEX_COORD_CUBE)
+		buf <<	"uniform highp sampler2D u_sampler;\n";
+	else
+		buf <<	"uniform highp samplerCube u_sampler;\n";
+
+	buf <<	"out mediump vec4 v_out;\n"
+			"void main ()\n"
+			"{\n";
+
+	if (m_testType == TEST_TEX_COORD)
+		buf <<	"	v_out = textureLod(u_sampler, a_attr, 0.0);\n";
+	else if (m_testType == TEST_LOD)
+		buf <<	"	v_out = textureLod(u_sampler, a_attr, a_attr.x);\n";
+	else if (m_testType == TEST_GRAD)
+		buf <<	"	v_out = textureGrad(u_sampler, a_attr, a_attr, a_attr.yx);\n";
+	else if (m_testType == TEST_TEX_COORD_CUBE)
+		buf <<	"	v_out = textureLod(u_sampler, vec3(a_attr, a_attr.x + a_attr.y), 0.0);\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf <<	"\n"
+			"	gl_Position = a_pos;\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string TextureSamplerCase::genFragmentSource (void) const
+{
+	// fragment shader is passthrough
+	if (m_type == TYPE_VERTEX)
+		return s_colorPassthroughFragmentShaderSource;
+
+	// fragment shader does the calculations
+	std::ostringstream buf;
+	buf <<	"#version 300 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n";
+
+	if (m_testType != TEST_TEX_COORD_CUBE)
+		buf <<	"uniform highp sampler2D u_sampler;\n";
+	else
+		buf <<	"uniform highp samplerCube u_sampler;\n";
+
+	buf <<	"in highp vec4 v_attr;\n"
+			"void main ()\n"
+			"{\n";
+
+	if (m_testType == TEST_TEX_COORD)
+		buf << "	fragColor = texture(u_sampler, v_attr.xy);\n";
+	else if (m_testType == TEST_LOD)
+		buf << "	fragColor = texture(u_sampler, v_attr.xy, v_attr.x);\n";
+	else if (m_testType == TEST_GRAD)
+		buf <<	"	fragColor = textureGrad(u_sampler, v_attr.xy, v_attr.xy, v_attr.yx);\n";
+	else if (m_testType == TEST_TEX_COORD_CUBE)
+		buf <<	"	fragColor = texture(u_sampler, vec3(v_attr.xy,v_attr.x + v_attr.y));\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats as fragment shader outputs
+ *
+ * Tests that outputting special floats from a fragment shader does not change
+ * the normal floating point values of outputted from a fragment shader. Special
+ * floats are outputted in the green component, normal floating point values
+ * in the red and blue component. Potential changes are tested by rendering
+ * test pattern two times with different floating point values. The resulting
+ * images' red and blue channels should be equal.
+ *//*--------------------------------------------------------------------*/
+class OutputCase : public FramebufferRenderCase
+{
+public:
+						OutputCase				(Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type);
+						~OutputCase				(void);
+
+	void				testFBO					(void);
+
+private:
+	std::string			genVertexSource			(void) const;
+	std::string			genFragmentSource		(void) const;
+};
+
+OutputCase::OutputCase (Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type)
+	: FramebufferRenderCase	(context, name, desc, type)
+{
+}
+
+OutputCase::~OutputCase (void)
+{
+	deinit();
+}
+
+void OutputCase::testFBO (void)
+{
+	// Create a 1 X [s_specialFloats] grid of tiles (stripes).
+	std::vector<tcu::Vec4>	gridVertices	((DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) * 2);
+	std::vector<deUint16>	indices			(DE_LENGTH_OF_ARRAY(s_specialFloats) * 6);
+	tcu::TextureFormat		textureFormat	(tcu::TextureFormat::RGBA, (m_fboType == FBO_RGBA_FLOAT16 || m_fboType == FBO_RGBA_FLOAT32) ? (tcu::TextureFormat::FLOAT) : (tcu::TextureFormat::UNORM_INT8));
+	tcu::TextureLevel		specialImage	(textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::TextureLevel		normalImage		(textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++y)
+	{
+		const float posY = (float)y / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) ] to [-1, 1]
+
+		gridVertices[y * 2 + 0] = tcu::Vec4(-1.0, posY, 0.0f, 1.0f);
+		gridVertices[y * 2 + 1] = tcu::Vec4( 1.0, posY, 0.0f, 1.0f);
+	}
+
+	// tiles
+	for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+	{
+		const int baseNdx = y * 6;
+
+		indices[baseNdx + 0] = (y + 0) * 2;
+		indices[baseNdx + 1] = (y + 1) * 2;
+		indices[baseNdx + 2] = (y + 1) * 2 + 1;
+
+		indices[baseNdx + 3] = (y + 0) * 2;
+		indices[baseNdx + 4] = (y + 1) * 2 + 1;
+		indices[baseNdx + 5] = (y + 0) * 2 + 1;
+	}
+
+	// Draw grids
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				specialLoc	= gl.getUniformLocation(m_program->getProgram(), "u_special");
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "pre-draw");
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+		gl.enableVertexAttribArray(positionLoc);
+
+		// draw 2 passes. Special and normal.
+		for (int passNdx = 0; passNdx < 2; ++passNdx)
+		{
+			const bool specialPass	= (passNdx == 0);
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Pass " << passNdx << ": Drawing stripes with the shader. Setting u_special for each stripe to (" << ((specialPass) ? ("special") : ("1.0")) << ")." << tcu::TestLog::EndMessage;
+
+			// draw stripes
+			gl.clear(GL_COLOR_BUFFER_BIT);
+			for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y)
+			{
+				const deUint32	one				= 0x3F800000;
+				const deUint32	special			= s_specialFloats[y];
+				const deUint32	uniformValue	= (specialPass) ? (special) : (one);
+				const int		indexIndex		= y * 6;
+
+				gl.uniform1fv(specialLoc, 1, (const float*)&uniformValue);
+				gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, &indices[indexIndex]);
+			}
+
+			gl.finish();
+			glu::readPixels(m_context.getRenderContext(), 0, 0, ((specialPass) ? (specialImage) : (normalImage)).getAccess());
+		}
+
+		gl.disableVertexAttribArray(positionLoc);
+		gl.useProgram(0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "OutputCase::iterate");
+	}
+
+	// Check results
+	{
+		tcu::Surface		errorMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		const tcu::RGBA		badPixelColor	= tcu::RGBA::red;
+		const tcu::RGBA		okPixelColor	= tcu::RGBA::green;
+		int					badPixels		= 0;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Checking passes have identical red and blue channels and the green channel is correct in the constant pass." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < specialImage.getHeight(); ++y)
+		for (int x = 0; x < specialImage.getWidth(); ++x)
+		{
+			const float		greenThreshold	= 0.1f;
+			const tcu::Vec4	cNormal			= normalImage.getAccess().getPixel(x, y);
+			const tcu::Vec4	cSpecial		= specialImage.getAccess().getPixel(x, y);
+
+			if (cNormal.x() != cSpecial.x() || cNormal.z() != cSpecial.z() || cNormal.y() < 1.0f - greenThreshold)
+			{
+				++badPixels;
+				errorMask.setPixel(x, y, badPixelColor);
+			}
+			else
+				errorMask.setPixel(x, y, okPixelColor);
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Found " << badPixels << " invalid pixel(s)." << tcu::TestLog::EndMessage;
+
+		if (badPixels)
+		{
+			m_testCtx.getLog()
+					<< tcu::TestLog::ImageSet("Results", "Result verification")
+					<< tcu::TestLog::Image("Image with special green channel",	"Image with special green channel",		specialImage)
+					<< tcu::TestLog::Image("Image with constant green channel",	"Image with constant green channel",	normalImage)
+					<< tcu::TestLog::Image("Error Mask",						"Error Mask",							errorMask)
+					<< tcu::TestLog::EndImageSet;
+
+			// all ok?
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+		}
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+}
+
+std::string OutputCase::genVertexSource (void) const
+{
+	return
+		"#version 300 es\n"
+		"in highp vec4 a_pos;\n"
+		"out highp vec2 v_pos;\n"
+		"void main ()\n"
+		"{\n"
+		"	gl_Position = a_pos;\n"
+		"	v_pos = a_pos.xy;\n"
+		"}\n";
+}
+
+std::string OutputCase::genFragmentSource (void) const
+{
+	return
+		"#version 300 es\n"
+		"layout(location = 0) out highp vec4 fragColor;\n"
+		"uniform highp float u_special;\n"
+		"in highp vec2 v_pos;\n"
+		"void main ()\n"
+		"{\n"
+		"	fragColor = vec4((v_pos.x + 1.0) * 0.5, u_special, (v_pos.y + 1.0) * 0.5, 1.0);\n"
+		"}\n";
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests special floats in blending
+ *
+ * Tests special floats as alpha and color components with various blending
+ * modes. Test draws a test pattern and then does various blend operations
+ * with special float values. After the blending test another test pattern
+ * is drawn to detect possible blending anomalies. Test patterns should be
+ * identical.
+ *//*--------------------------------------------------------------------*/
+class BlendingCase : public FramebufferRenderCase
+{
+public:
+						BlendingCase			(Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type);
+						~BlendingCase			(void);
+
+	void				testFBO					(void);
+
+private:
+	void				drawTestImage			(tcu::PixelBufferAccess dst, GLuint uColorLoc, int maxVertexIndex);
+
+	std::string			genVertexSource			(void) const;
+	std::string			genFragmentSource		(void) const;
+};
+
+BlendingCase::BlendingCase (Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type)
+	: FramebufferRenderCase	(context, name, desc, type)
+{
+}
+
+BlendingCase::~BlendingCase (void)
+{
+	deinit();
+}
+
+void BlendingCase::testFBO (void)
+{
+	static const GLenum equations[] =
+	{
+		GL_FUNC_ADD,
+		GL_FUNC_SUBTRACT,
+		GL_FUNC_REVERSE_SUBTRACT,
+		GL_MIN,
+		GL_MAX
+	};
+	static const GLenum functions[] =
+	{
+		GL_ZERO,
+		GL_ONE,
+		GL_SRC_COLOR,
+		GL_ONE_MINUS_SRC_COLOR,
+		GL_SRC_ALPHA,
+		GL_ONE_MINUS_SRC_ALPHA,
+	};
+
+	// Create a [BlendFuncs] X [s_specialFloats] grid of tiles. ( BlendFuncs = equations x functions )
+
+	const int						numBlendFuncs	= DE_LENGTH_OF_ARRAY(equations) * DE_LENGTH_OF_ARRAY(functions);
+	std::vector<tcu::Vec4>			gridVertices	((numBlendFuncs + 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1));
+	std::vector<deUint16>			indices			(numBlendFuncs * DE_LENGTH_OF_ARRAY(s_specialFloats) * 6);
+	tcu::TextureFormat				textureFormat	(tcu::TextureFormat::RGBA, (m_fboType == FBO_RGBA_FLOAT16 || m_fboType == FBO_RGBA_FLOAT32) ? (tcu::TextureFormat::FLOAT) : (tcu::TextureFormat::UNORM_INT8));
+	tcu::TextureLevel				beforeImage		(textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+	tcu::TextureLevel				afterImage		(textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+
+	// vertices
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++x)
+	for (int y = 0; y < numBlendFuncs + 1; ++y)
+	{
+		const float		posX	= (float)x / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f; // map from [0, len(s_specialFloats)] to [-1, 1]
+		const float		posY	= (float)y / (float)numBlendFuncs * 2.0f - 1.0f;
+
+		gridVertices[x * (numBlendFuncs + 1) + y]	= tcu::Vec4(posX, posY, 0.0f, 1.0f);
+	}
+
+	// tiles
+	for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+	for (int y = 0; y < numBlendFuncs; ++y)
+	{
+		const int baseNdx = (x * numBlendFuncs + y) * 6;
+
+		indices[baseNdx + 0] = (x+0) * (numBlendFuncs + 1) + (y+0);
+		indices[baseNdx + 1] = (x+1) * (numBlendFuncs + 1) + (y+1);
+		indices[baseNdx + 2] = (x+1) * (numBlendFuncs + 1) + (y+0);
+
+		indices[baseNdx + 3] = (x+0) * (numBlendFuncs + 1) + (y+0);
+		indices[baseNdx + 4] = (x+1) * (numBlendFuncs + 1) + (y+1);
+		indices[baseNdx + 5] = (x+0) * (numBlendFuncs + 1) + (y+1);
+	}
+
+	// Draw tiles
+	{
+		const int				numPasses	= 5;
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const GLint				positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_pos");
+		const GLint				specialLoc	= gl.getUniformLocation(m_program->getProgram(), "u_special");
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		gl.useProgram(m_program->getProgram());
+		gl.enable(GL_BLEND);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "pre-draw");
+
+		gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]);
+		gl.enableVertexAttribArray(positionLoc);
+
+		// draw "before" image
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing pre-draw pattern." << tcu::TestLog::EndMessage;
+		drawTestImage(beforeImage.getAccess(), specialLoc, (int)gridVertices.size() - 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "pre-draw pattern");
+
+		// draw multiple passes with special floats
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		for (int passNdx = 0; passNdx < numPasses; ++passNdx)
+		{
+			de::Random rnd(123 + 567 * passNdx);
+
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "Pass " << passNdx << ": Drawing tiles with the shader.\n"
+				<< "\tVarying u_special for each tile.\n"
+				<< "\tVarying blend function and blend equation for each tile.\n"
+				<< tcu::TestLog::EndMessage;
+
+			// draw tiles
+			for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x)
+			for (int y = 0; y < numBlendFuncs; ++y)
+			{
+				const GLenum		blendEquation		= equations[y % DE_LENGTH_OF_ARRAY(equations)];
+				const GLenum		blendFunction		= functions[y / DE_LENGTH_OF_ARRAY(equations)];
+				const GLenum		blendFunctionDst	= rnd.choose<GLenum>(DE_ARRAY_BEGIN(functions), DE_ARRAY_END(functions));
+				const int			indexIndex			= (x * numBlendFuncs + y) * 6;
+
+				// "rnd.get"s are run in a deterministic order
+				const deUint32		componentR			= rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats));
+				const deUint32		componentG			= rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats));
+				const deUint32		componentB			= rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats));
+				const deUint32		componentA			= rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats));
+				const tcu::UVec4	uniformValue		= tcu::UVec4(componentR, componentG, componentB, componentA);
+
+				gl.uniform4fv(specialLoc, 1, (const float*)uniformValue.getPtr());
+				gl.blendEquation(blendEquation);
+				gl.blendFunc(blendFunction, blendFunctionDst);
+				gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, &indices[indexIndex]);
+			}
+		}
+		GLU_EXPECT_NO_ERROR(gl.getError(), "special passes");
+
+		// draw "after" image
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing post-draw pattern." << tcu::TestLog::EndMessage;
+		drawTestImage(afterImage.getAccess(), specialLoc, (int)gridVertices.size() - 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "post-draw pattern");
+
+		gl.disableVertexAttribArray(positionLoc);
+		gl.useProgram(0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "OutputCase::iterate");
+	}
+
+	// Check results
+	{
+		tcu::Surface		errorMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
+		const tcu::RGBA		badPixelColor	= tcu::RGBA::red;
+		const tcu::RGBA		okPixelColor	= tcu::RGBA::green;
+		int					badPixels		= 0;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Checking patterns are identical." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < beforeImage.getHeight(); ++y)
+		for (int x = 0; x < beforeImage.getWidth(); ++x)
+		{
+			const tcu::Vec4	cBefore	= beforeImage.getAccess().getPixel(x, y);
+			const tcu::Vec4	cAfter	= afterImage.getAccess().getPixel(x, y);
+
+			if (cBefore != cAfter)
+			{
+				++badPixels;
+				errorMask.setPixel(x, y, badPixelColor);
+			}
+			else
+				errorMask.setPixel(x, y, okPixelColor);
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Found " << badPixels << " invalid pixel(s)." << tcu::TestLog::EndMessage;
+
+		if (badPixels)
+		{
+			m_testCtx.getLog()
+					<< tcu::TestLog::ImageSet("Results", "Result verification")
+					<< tcu::TestLog::Image("Pattern drawn before special float blending",	"Pattern drawn before special float blending",		beforeImage)
+					<< tcu::TestLog::Image("Pattern drawn after special float blending",	"Pattern drawn after special float blending",		afterImage)
+					<< tcu::TestLog::EndImageSet;
+
+			// all ok?
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+		}
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+}
+
+void BlendingCase::drawTestImage (tcu::PixelBufferAccess dst, GLuint uColorLoc, int maxVertexIndex)
+{
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+	de::Random				rnd	(123);
+
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	gl.blendEquation(GL_FUNC_ADD);
+	gl.blendFunc(GL_ONE, GL_ONE);
+
+	for (int tri = 0; tri < 20; ++tri)
+	{
+		tcu::Vec4 color;
+		color.x() = rnd.getFloat();
+		color.y() = rnd.getFloat();
+		color.z() = rnd.getFloat();
+		color.w() = rnd.getFloat();
+		gl.uniform4fv(uColorLoc, 1, color.getPtr());
+
+		deUint16 indices[3];
+		indices[0] = rnd.getInt(0, maxVertexIndex);
+		indices[1] = rnd.getInt(0, maxVertexIndex);
+		indices[2] = rnd.getInt(0, maxVertexIndex);
+
+		gl.drawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, indices);
+	}
+
+	gl.finish();
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst);
+}
+
+std::string BlendingCase::genVertexSource (void) const
+{
+	return
+		"#version 300 es\n"
+		"in highp vec4 a_pos;\n"
+		"void main ()\n"
+		"{\n"
+		"	gl_Position = a_pos;\n"
+		"}\n";
+}
+
+std::string BlendingCase::genFragmentSource (void) const
+{
+	return
+		"#version 300 es\n"
+		"layout(location = 0) out highp vec4 fragColor;\n"
+		"uniform highp vec4 u_special;\n"
+		"void main ()\n"
+		"{\n"
+		"	fragColor = u_special;\n"
+		"}\n";
+}
+
+} //anonymous
+
+SpecialFloatTests::SpecialFloatTests (Context& context)
+	: TestCaseGroup(context, "special_float", "Special float tests")
+{
+}
+
+SpecialFloatTests::~SpecialFloatTests (void)
+{
+}
+
+void SpecialFloatTests::init (void)
+{
+	tcu::TestCaseGroup* const vertexGroup		= new tcu::TestCaseGroup(m_testCtx, "vertex",		"Run vertex shader with special float values");
+	tcu::TestCaseGroup* const fragmentGroup		= new tcu::TestCaseGroup(m_testCtx, "fragment",		"Run fragment shader with special float values");
+	tcu::TestCaseGroup* const framebufferGroup	= new tcu::TestCaseGroup(m_testCtx, "framebuffer",	"Test framebuffers containing special float values");
+
+	// .vertex
+	{
+		vertexGroup->addChild(new VertexAttributeCase	(m_context, "attribute_buffer",			"special attribute values in a buffer",					VertexAttributeCase::STORAGE_BUFFER, VertexAttributeCase::TYPE_VERTEX));
+		vertexGroup->addChild(new VertexAttributeCase	(m_context, "attribute_client",			"special attribute values in a client storage",			VertexAttributeCase::STORAGE_CLIENT, VertexAttributeCase::TYPE_VERTEX));
+		vertexGroup->addChild(new UniformCase			(m_context, "uniform",					"special uniform values",								UniformCase::TYPE_VERTEX));
+		vertexGroup->addChild(new TextureCase			(m_context, "texture",					"texture with special floating point values",			TextureCase::TYPE_VERTEX, TextureCase::TEXTURE_FLOAT, TextureCase::UPLOAD_CLIENT));
+		vertexGroup->addChild(new TextureCase			(m_context, "texture_pbo",				"texture (via pbo) with special floating point values",	TextureCase::TYPE_VERTEX, TextureCase::TEXTURE_FLOAT, TextureCase::UPLOAD_PBO));
+		vertexGroup->addChild(new TextureCase			(m_context, "texture_shadow",			"shadow texture with special floating point values",	TextureCase::TYPE_VERTEX, TextureCase::TEXTURE_DEPTH, TextureCase::UPLOAD_CLIENT));
+		vertexGroup->addChild(new TextureSamplerCase	(m_context, "sampler_tex_coord",		"special texture coords",								TextureSamplerCase::TYPE_VERTEX, TextureSamplerCase::TEST_TEX_COORD));
+		vertexGroup->addChild(new TextureSamplerCase	(m_context, "sampler_tex_coord_cube",	"special texture coords to cubemap",					TextureSamplerCase::TYPE_VERTEX, TextureSamplerCase::TEST_TEX_COORD_CUBE));
+		vertexGroup->addChild(new TextureSamplerCase	(m_context, "sampler_lod",				"special texture lod",									TextureSamplerCase::TYPE_VERTEX, TextureSamplerCase::TEST_LOD));
+		vertexGroup->addChild(new TextureSamplerCase	(m_context, "sampler_grad",				"special texture grad",									TextureSamplerCase::TYPE_VERTEX, TextureSamplerCase::TEST_GRAD));
+
+		addChild(vertexGroup);
+	}
+
+	// .fragment
+	{
+		fragmentGroup->addChild(new VertexAttributeCase	(m_context, "attribute_buffer",			"special attribute values in a buffer",					VertexAttributeCase::STORAGE_BUFFER, VertexAttributeCase::TYPE_FRAGMENT));
+		fragmentGroup->addChild(new VertexAttributeCase	(m_context, "attribute_client",			"special attribute values in a client storage",			VertexAttributeCase::STORAGE_CLIENT, VertexAttributeCase::TYPE_FRAGMENT));
+		fragmentGroup->addChild(new UniformCase			(m_context, "uniform",					"special uniform values",								UniformCase::TYPE_FRAGMENT));
+		fragmentGroup->addChild(new TextureCase			(m_context, "texture",					"texture with special floating point values",			TextureCase::TYPE_FRAGMENT, TextureCase::TEXTURE_FLOAT, TextureCase::UPLOAD_CLIENT));
+		fragmentGroup->addChild(new TextureCase			(m_context, "texture_pbo",				"texture (via pbo) with special floating point values",	TextureCase::TYPE_FRAGMENT, TextureCase::TEXTURE_FLOAT, TextureCase::UPLOAD_PBO));
+		fragmentGroup->addChild(new TextureCase			(m_context, "texture_shadow",			"shadow texture with special floating point values",	TextureCase::TYPE_FRAGMENT, TextureCase::TEXTURE_DEPTH, TextureCase::UPLOAD_CLIENT));
+		fragmentGroup->addChild(new TextureSamplerCase	(m_context, "sampler_tex_coord",		"special texture coords",								TextureSamplerCase::TYPE_FRAGMENT, TextureSamplerCase::TEST_TEX_COORD));
+		fragmentGroup->addChild(new TextureSamplerCase	(m_context, "sampler_tex_coord_cube",	"special texture coords to cubemap",					TextureSamplerCase::TYPE_FRAGMENT, TextureSamplerCase::TEST_TEX_COORD_CUBE));
+		fragmentGroup->addChild(new TextureSamplerCase	(m_context, "sampler_lod",				"special texture lod",									TextureSamplerCase::TYPE_FRAGMENT, TextureSamplerCase::TEST_LOD));
+		fragmentGroup->addChild(new TextureSamplerCase	(m_context, "sampler_grad",				"special texture grad",									TextureSamplerCase::TYPE_FRAGMENT, TextureSamplerCase::TEST_GRAD));
+
+		addChild(fragmentGroup);
+	}
+
+	// .framebuffer
+	{
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_default",			"write special floating point values to default framebuffer",	FramebufferRenderCase::FBO_DEFAULT));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_rgba4",				"write special floating point values to RGBA4 framebuffer",		FramebufferRenderCase::FBO_RGBA4));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_rgb5_a1",			"write special floating point values to RGB5_A1 framebuffer",	FramebufferRenderCase::FBO_RGB5_A1));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_rgb565",				"write special floating point values to RGB565 framebuffer",	FramebufferRenderCase::FBO_RGB565));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_rgba8",				"write special floating point values to RGBA8 framebuffer",		FramebufferRenderCase::FBO_RGBA8));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_rgb10_a2",			"write special floating point values to RGB10_A2 framebuffer",	FramebufferRenderCase::FBO_RGB10_A2));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_float16",			"write special floating point values to float16 framebuffer",	FramebufferRenderCase::FBO_RGBA_FLOAT16));
+		framebufferGroup->addChild(new OutputCase		(m_context, "write_float32",			"write special floating point values to float32 framebuffer",	FramebufferRenderCase::FBO_RGBA_FLOAT32));
+
+		framebufferGroup->addChild(new BlendingCase		(m_context, "blend_default",			"blend special floating point values in a default framebuffer",	FramebufferRenderCase::FBO_DEFAULT));
+		framebufferGroup->addChild(new BlendingCase		(m_context, "blend_rgba8",				"blend special floating point values in a RGBA8 framebuffer",	FramebufferRenderCase::FBO_RGBA8));
+		framebufferGroup->addChild(new BlendingCase		(m_context, "blend_float16",			"blend special floating point values in a float16 framebuffer",	FramebufferRenderCase::FBO_RGBA_FLOAT16));
+
+		addChild(framebufferGroup);
+	}
+}
+
+} // Stress
+} // gles3
+} // deqp
diff --git a/modules/gles3/stress/es3sSpecialFloatTests.hpp b/modules/gles3/stress/es3sSpecialFloatTests.hpp
new file mode 100644
index 0000000..6f044fd
--- /dev/null
+++ b/modules/gles3/stress/es3sSpecialFloatTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3SSPECIALFLOATTESTS_HPP
+#define _ES3SSPECIALFLOATTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Special float stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+class SpecialFloatTests : public TestCaseGroup
+{
+public:
+						SpecialFloatTests	(Context& context);
+						~SpecialFloatTests	(void);
+
+	void				init				(void);
+
+private:
+						SpecialFloatTests	(const SpecialFloatTests& other);
+	SpecialFloatTests&	operator=			(const SpecialFloatTests& other);
+};
+
+} // Stress
+} // gles3
+} // deqp
+
+#endif // _ES3SSPECIALFLOATTESTS_HPP
diff --git a/modules/gles3/stress/es3sStressTests.cpp b/modules/gles3/stress/es3sStressTests.cpp
new file mode 100644
index 0000000..f4421da
--- /dev/null
+++ b/modules/gles3/stress/es3sStressTests.cpp
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3sStressTests.hpp"
+
+#include "es3sMemoryTests.hpp"
+#include "es3sOcclusionQueryTests.hpp"
+#include "es3sSyncTests.hpp"
+#include "es3sLongRunningTests.hpp"
+#include "es3sSpecialFloatTests.hpp"
+#include "es3sDrawTests.hpp"
+#include "es3sVertexArrayTests.hpp"
+#include "es3sLongShaderTests.hpp"
+#include "es3sLongRunningShaderTests.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+StressTests::StressTests (Context& context)
+	: TestCaseGroup(context, "stress", "Stress tests")
+{
+}
+
+StressTests::~StressTests (void)
+{
+}
+
+void StressTests::init (void)
+{
+	addChild(new MemoryTests			(m_context));
+	addChild(new OcclusionQueryTests	(m_context));
+	addChild(new SyncTests				(m_context));
+	addChild(new LongRunningTests		(m_context));
+	addChild(new SpecialFloatTests		(m_context));
+	addChild(new DrawTests				(m_context));
+	addChild(new VertexArrayTests		(m_context));
+	addChild(new LongShaderTests		(m_context));
+	addChild(new LongRunningShaderTests	(m_context));
+}
+
+} // Stress
+} // gles3
+} // deqp
diff --git a/modules/gles3/stress/es3sStressTests.hpp b/modules/gles3/stress/es3sStressTests.hpp
new file mode 100644
index 0000000..fddff49
--- /dev/null
+++ b/modules/gles3/stress/es3sStressTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3SSTRESSTESTS_HPP
+#define _ES3SSTRESSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+class StressTests : public TestCaseGroup
+{
+public:
+					StressTests		(Context& context);
+					~StressTests	(void);
+
+	void			init			(void);
+
+private:
+					StressTests		(const StressTests& other);
+	StressTests&	operator=		(const StressTests& other);
+};
+
+} // Stress
+} // gles3
+} // deqp
+
+#endif // _ES3SSTRESSTESTS_HPP
diff --git a/modules/gles3/stress/es3sSyncTests.cpp b/modules/gles3/stress/es3sSyncTests.cpp
new file mode 100644
index 0000000..a3d1c06
--- /dev/null
+++ b/modules/gles3/stress/es3sSyncTests.cpp
@@ -0,0 +1,292 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sync stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es3sSyncTests.hpp"
+
+#include "tcuTestLog.hpp"
+#include "deRandom.hpp"
+#include "tcuVector.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluRenderContext.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+#include <vector>
+
+#include "glw.h"
+
+using tcu::TestLog;
+using std::vector;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+static const int NUM_CASE_ITERATIONS = 1;
+
+enum WaitCommand
+{
+	COMMAND_WAIT_SYNC			= 1 << 0,
+	COMMAND_CLIENT_WAIT_SYNC	= 1 << 1
+};
+
+class FenceSyncCase : public TestCase, public glu::CallLogWrapper
+{
+public:
+						FenceSyncCase		(Context& context, const char* name, const char* description, int numPrimitives, deUint32 waitCommand);
+						~FenceSyncCase		(void);
+
+	void				init				(void);
+	void				deinit				(void);
+	IterateResult		iterate				(void);
+
+private:
+						FenceSyncCase		(const FenceSyncCase& other);
+	FenceSyncCase&		operator=			(const FenceSyncCase& other);
+
+	int					m_numSyncs;
+	deUint32			m_waitCommand;
+
+	glu::ShaderProgram*	m_program;
+	vector<GLsync>		m_syncObjects;
+	int					m_iterNdx;
+	de::Random			m_rnd;
+};
+
+FenceSyncCase::FenceSyncCase (Context& context, const char* name, const char* description, int numSyncs, deUint32 waitCommand)
+	: TestCase			(context, name, description)
+	, CallLogWrapper	(context.getRenderContext().getFunctions(), context.getTestContext().getLog())
+	, m_numSyncs		(numSyncs)
+	, m_waitCommand		(waitCommand)
+	, m_program			(DE_NULL)
+	, m_syncObjects		(DE_NULL)
+	, m_iterNdx			(0)
+	, m_rnd				(deStringHash(name))
+{
+}
+
+FenceSyncCase::~FenceSyncCase (void)
+{
+	FenceSyncCase::deinit();
+}
+
+static void generateVertices (std::vector<float>& dst, int numPrimitives, de::Random& rnd)
+{
+	int numVertices = 3*numPrimitives;
+	dst.resize(numVertices * 4);
+
+	for (int i = 0; i < numVertices; i++)
+	{
+		dst[i*4    ] = rnd.getFloat(-1.0f, 1.0f);	// x
+		dst[i*4 + 1] = rnd.getFloat(-1.0f, 1.0f);	// y
+		dst[i*4 + 2] = rnd.getFloat( 0.0f, 1.0f);	// z
+		dst[i*4 + 3] = 1.0f;						// w
+	}
+}
+
+void FenceSyncCase::init (void)
+{
+	const char*	vertShaderSource =
+				"#version 300 es\n"
+				"layout(location = 0) in mediump vec4 a_position;\n"
+				"\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = a_position;\n"
+				"}\n";
+
+	const char* fragShaderSource =
+				"#version 300 es\n"
+				"layout(location = 0) out mediump vec4 o_color;\n"
+				"\n"
+				"void main (void)\n"
+				"{\n"
+				"	o_color = vec4(0.25, 0.5, 0.75, 1.0);\n"
+				"}\n";
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource));
+
+	if (!m_program->isOk())
+	{
+		m_testCtx.getLog() << *m_program;
+		TCU_FAIL("Failed to compile shader program");
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); // Initialize test result to pass.
+	GLU_CHECK_MSG ("Case initialization finished");
+}
+
+void FenceSyncCase::deinit (void)
+{
+	if (m_program)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+	}
+
+	for (int i = 0; i < (int)m_syncObjects.size(); i++)
+		if (m_syncObjects[i])
+			glDeleteSync(m_syncObjects[i]);
+
+	m_syncObjects.erase(m_syncObjects.begin(), m_syncObjects.end());
+}
+
+FenceSyncCase::IterateResult FenceSyncCase::iterate (void)
+{
+	TestLog&				log		= m_testCtx.getLog();
+	std::vector<float>		vertices;
+	bool					testOk	= true;
+
+	std::string				header	= "Case iteration " + de::toString(m_iterNdx+1) + " / " + de::toString(NUM_CASE_ITERATIONS);
+	tcu::ScopedLogSection	section	(log, header, header);
+
+	enableLogging(true);
+
+	TCU_CHECK		(m_program);
+	glUseProgram	(m_program->getProgram());
+	glEnable		(GL_DEPTH_TEST);
+	glClearColor	(0.3f, 0.3f, 0.3f, 1.0f);
+	glClearDepthf	(1.0f);
+	glClear			(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+	// Generate vertices
+
+	glEnableVertexAttribArray	(0);
+	generateVertices			(vertices, m_numSyncs, m_rnd);
+	glVertexAttribPointer		(0, 4, GL_FLOAT, GL_FALSE, 0, &vertices[0]);
+	m_syncObjects.resize		(m_numSyncs);
+
+	// Perform draws and create sync objects
+
+	enableLogging(false);
+	log << TestLog::Message << "// NOT LOGGED: " << m_numSyncs << " glDrawArrays and glFenceSync calls done here." << TestLog::EndMessage;
+
+	for (int i = 0; i < m_numSyncs; i++)
+	{
+		glDrawArrays(GL_TRIANGLES, i*3, 3);
+		m_syncObjects[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+		GLU_CHECK_MSG("Sync object created");
+	}
+	enableLogging(true);
+	log << TestLog::Message << "// Draws performed, sync objects created." << TestLog::EndMessage;
+
+	// Wait for sync objects
+
+	m_rnd.shuffle(m_syncObjects.begin(), m_syncObjects.end());
+
+	enableLogging(false);
+	if (m_waitCommand & COMMAND_WAIT_SYNC)
+		log << TestLog::Message << "// NOT LOGGED: " << m_numSyncs << " glWaitSync calls done here." << TestLog::EndMessage;
+	else if (m_waitCommand & COMMAND_CLIENT_WAIT_SYNC)
+		log << TestLog::Message << "// NOT LOGGED: " << m_numSyncs << " glClientWaitSync calls done here." << TestLog::EndMessage;
+
+	for (int i = 0; i < m_numSyncs; i++)
+	{
+		GLenum waitValue = 0;
+
+		if (m_waitCommand & COMMAND_WAIT_SYNC)
+		{
+			glWaitSync(m_syncObjects[i], 0, GL_TIMEOUT_IGNORED);
+			GLU_CHECK_MSG("glWaitSync called");
+		}
+		else if (m_waitCommand & COMMAND_CLIENT_WAIT_SYNC)
+		{
+			waitValue = glClientWaitSync(m_syncObjects[i], 0, 100);
+			GLU_CHECK_MSG("glClientWaitSync called");
+			switch (waitValue)
+			{
+				case GL_ALREADY_SIGNALED:	 break;
+				case GL_TIMEOUT_EXPIRED:	 break;
+				case GL_CONDITION_SATISFIED: break;
+				case GL_WAIT_FAILED:		 log << TestLog::Message << "// glClientWaitSync returned GL_WAIT_FAILED"	<< TestLog::EndMessage; testOk = false; break;
+				default:					 TCU_FAIL("glClientWaitSync returned an unknown return value.");
+			}
+		}
+	}
+	enableLogging(true);
+
+	glFinish();
+
+	// Delete sync objects
+
+	enableLogging(false);
+	log << TestLog::Message << "// NOT LOGGED: " << m_numSyncs << " glDeleteSync calls done here." << TestLog::EndMessage;
+
+	for (int i = 0; i < (int)m_syncObjects.size(); i++)
+	{
+		if (m_syncObjects[i])
+		{
+			glDeleteSync(m_syncObjects[i]);
+			GLU_CHECK_MSG("Sync object deleted");
+		}
+	}
+
+	enableLogging(true);
+
+	m_syncObjects.erase(m_syncObjects.begin(), m_syncObjects.end());
+
+	// Evaluate test result
+
+	log << TestLog::Message << "// Test result: " << (testOk ? "Passed!" : "Failed!") << TestLog::EndMessage;
+
+	if (!testOk)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		return STOP;
+	}
+
+	log << TestLog::Message << "// Sync objects created and deleted successfully." << TestLog::EndMessage;
+
+	return (++m_iterNdx < NUM_CASE_ITERATIONS) ? CONTINUE : STOP;
+}
+
+SyncTests::SyncTests (Context& context)
+	: TestCaseGroup(context, "fence_sync", "Fence Sync Tests")
+{
+}
+
+SyncTests::~SyncTests (void)
+{
+}
+
+void SyncTests::init (void)
+{
+	// Fence sync stress tests.
+
+	addChild(new FenceSyncCase(m_context, "wait_sync_10_syncs",				"",	10,		COMMAND_WAIT_SYNC));
+	addChild(new FenceSyncCase(m_context, "wait_sync_1000_syncs",			"",	1000,	COMMAND_WAIT_SYNC));
+	addChild(new FenceSyncCase(m_context, "wait_sync_10000_syncs",			"",	10000,	COMMAND_WAIT_SYNC));
+
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_10_syncs",		"",	10,		COMMAND_CLIENT_WAIT_SYNC));
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_1000_syncs",	"",	1000,	COMMAND_CLIENT_WAIT_SYNC));
+	addChild(new FenceSyncCase(m_context, "client_wait_sync_10000_syncs",	"",	10000,	COMMAND_CLIENT_WAIT_SYNC));
+}
+
+} // Stress
+} // gles3
+} // deqp
diff --git a/modules/gles3/stress/es3sSyncTests.hpp b/modules/gles3/stress/es3sSyncTests.hpp
new file mode 100644
index 0000000..febadb5
--- /dev/null
+++ b/modules/gles3/stress/es3sSyncTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3SSYNCTESTS_HPP
+#define _ES3SSYNCTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sync stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+class SyncTests : public TestCaseGroup
+{
+public:
+					SyncTests	(Context& context);
+					~SyncTests	(void);
+
+	void			init		(void);
+
+private:
+					SyncTests	(const SyncTests& other);
+	SyncTests&		operator=	(const SyncTests& other);
+};
+
+} // Stress
+} // gles3
+} // deqp
+
+#endif // _ES3SSYNCTESTS_HPP
diff --git a/modules/gles3/stress/es3sVertexArrayTests.cpp b/modules/gles3/stress/es3sVertexArrayTests.cpp
new file mode 100644
index 0000000..587a3f0
--- /dev/null
+++ b/modules/gles3/stress/es3sVertexArrayTests.cpp
@@ -0,0 +1,483 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array and buffer unaligned access stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es3sVertexArrayTests.hpp"
+#include "glsVertexArrayTests.hpp"
+
+#include <sstream>
+
+using namespace deqp::gls;
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+namespace
+{
+
+
+class SingleVertexArrayUsageGroup : public TestCaseGroup
+{
+public:
+									SingleVertexArrayUsageGroup		(Context& context, Array::Usage usage);
+	virtual							~SingleVertexArrayUsageGroup	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayUsageGroup		(const SingleVertexArrayUsageGroup& other);
+	SingleVertexArrayUsageGroup&	operator=						(const SingleVertexArrayUsageGroup& other);
+
+	Array::Usage					m_usage;
+};
+
+SingleVertexArrayUsageGroup::SingleVertexArrayUsageGroup (Context& context, Array::Usage usage)
+	: TestCaseGroup	(context, Array::usageTypeToString(usage).c_str(), Array::usageTypeToString(usage).c_str())
+	, m_usage		(usage)
+{
+}
+
+SingleVertexArrayUsageGroup::~SingleVertexArrayUsageGroup (void)
+{
+}
+
+template<class T>
+static std::string typeToString (T t)
+{
+	std::stringstream strm;
+	strm << t;
+	return strm.str();
+}
+
+void SingleVertexArrayUsageGroup::init (void)
+{
+	int					counts[]		= {1, 256};
+	int					strides[]		= {0, -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_FIXED, Array::INPUTTYPE_SHORT, Array::INPUTTYPE_BYTE};
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+		{
+			for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+			{
+				const int			stride	= (strides[strideNdx] < 0 ? Array::inputTypeSize(inputTypes[inputTypeNdx]) * 2 : strides[strideNdx]);
+				const bool			aligned	= (stride % Array::inputTypeSize(inputTypes[inputTypeNdx])) == 0;
+				const std::string	name	= "stride" + typeToString(stride) + "_" + Array::inputTypeToString(inputTypes[inputTypeNdx]) + "_quads" + typeToString(counts[countNdx]);
+
+				MultiVertexArrayTest::Spec::ArraySpec arraySpec(inputTypes[inputTypeNdx],
+																Array::OUTPUTTYPE_VEC2,
+																Array::STORAGE_BUFFER,
+																m_usage,
+																2,
+																0,
+																stride,
+																false,
+																GLValue::getMinValue(inputTypes[inputTypeNdx]),
+																GLValue::getMaxValue(inputTypes[inputTypeNdx]));
+
+				MultiVertexArrayTest::Spec spec;
+				spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+				spec.drawCount	= counts[countNdx];
+				spec.first		= 0;
+				spec.arrays.push_back(arraySpec);
+
+				if (!aligned)
+					addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+			}
+		}
+	}
+}
+
+class SingleVertexArrayUsageTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayUsageTests		(Context& context);
+	virtual							~SingleVertexArrayUsageTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayUsageTests		(const SingleVertexArrayUsageTests& other);
+	SingleVertexArrayUsageTests&	operator=						(const SingleVertexArrayUsageTests& other);
+};
+
+SingleVertexArrayUsageTests::SingleVertexArrayUsageTests (Context& context)
+	: TestCaseGroup(context, "usages", "Single vertex atribute, usage")
+{
+}
+
+SingleVertexArrayUsageTests::~SingleVertexArrayUsageTests (void)
+{
+}
+
+void SingleVertexArrayUsageTests::init (void)
+{
+	// Test usage
+	Array::Usage		usages[]		= { Array::USAGE_STATIC_DRAW, Array::USAGE_STREAM_DRAW, Array::USAGE_DYNAMIC_DRAW, Array::USAGE_STATIC_COPY, Array::USAGE_STREAM_COPY, Array::USAGE_DYNAMIC_COPY, Array::USAGE_STATIC_READ, Array::USAGE_STREAM_READ, Array::USAGE_DYNAMIC_READ };
+	for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usages); usageNdx++)
+	{
+		addChild(new SingleVertexArrayUsageGroup(m_context, usages[usageNdx]));
+	}
+}
+
+class SingleVertexArrayStrideGroup : public TestCaseGroup
+{
+public:
+									SingleVertexArrayStrideGroup	(Context& context, Array::InputType type);
+	virtual							~SingleVertexArrayStrideGroup	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayStrideGroup	(const SingleVertexArrayStrideGroup& other);
+	SingleVertexArrayStrideGroup&	operator=						(const SingleVertexArrayStrideGroup& other);
+
+	Array::InputType				m_type;
+};
+
+SingleVertexArrayStrideGroup::SingleVertexArrayStrideGroup (Context& context, Array::InputType type)
+	: TestCaseGroup	(context, Array::inputTypeToString(type).c_str(), Array::inputTypeToString(type).c_str())
+	, m_type		(type)
+{
+}
+
+SingleVertexArrayStrideGroup::~SingleVertexArrayStrideGroup (void)
+{
+}
+
+void SingleVertexArrayStrideGroup::init (void)
+{
+	Array::Storage		storages[]		= {Array::STORAGE_USER, Array::STORAGE_BUFFER};
+	int					counts[]		= {1, 256};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int storageNdx = 0; storageNdx < DE_LENGTH_OF_ARRAY(storages); storageNdx++)
+	{
+		for (int componentCount = 2; componentCount < 5; componentCount++)
+		{
+			for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+			{
+				for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+				{
+					const bool	packed			= m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10;
+					const int	stride			= (strides[strideNdx] < 0) ? ((packed) ? (16) : (Array::inputTypeSize(m_type) * componentCount)) : (strides[strideNdx]);
+					const int	alignment		= (packed) ? (Array::inputTypeSize(m_type) * componentCount) : (Array::inputTypeSize(m_type));
+					const bool	bufferUnaligned	= (storages[storageNdx] == Array::STORAGE_BUFFER) && (stride % alignment) != 0;
+
+					std::string name = Array::storageToString(storages[storageNdx]) + "_stride" + typeToString(stride) + "_components" + typeToString(componentCount) + "_quads" + typeToString(counts[countNdx]);
+
+					if((m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10) && componentCount != 4)
+						continue;
+
+					MultiVertexArrayTest::Spec::ArraySpec arraySpec(m_type,
+																	Array::OUTPUTTYPE_VEC4,
+																	storages[storageNdx],
+																	Array::USAGE_DYNAMIC_DRAW,
+																	componentCount,
+																	0,
+																	stride,
+																	false,
+																	GLValue::getMinValue(m_type),
+																	GLValue::getMaxValue(m_type));
+
+					MultiVertexArrayTest::Spec spec;
+					spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+					spec.drawCount	= counts[countNdx];
+					spec.first		= 0;
+					spec.arrays.push_back(arraySpec);
+
+					if (bufferUnaligned)
+						addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayStrideTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayStrideTests	(Context& context);
+	virtual							~SingleVertexArrayStrideTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayStrideTests	(const SingleVertexArrayStrideTests& other);
+	SingleVertexArrayStrideTests&	operator=						(const SingleVertexArrayStrideTests& other);
+};
+
+SingleVertexArrayStrideTests::SingleVertexArrayStrideTests (Context& context)
+	: TestCaseGroup(context, "strides", "Single stride vertex atribute")
+{
+}
+
+SingleVertexArrayStrideTests::~SingleVertexArrayStrideTests (void)
+{
+}
+
+void SingleVertexArrayStrideTests::init (void)
+{
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_SHORT, /*Array::INPUTTYPE_UNSIGNED_SHORT, Array::INPUTTYPE_UNSIGNED_BYTE,*/ Array::INPUTTYPE_FIXED, Array::INPUTTYPE_INT_2_10_10_10 };
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		addChild(new SingleVertexArrayStrideGroup(m_context, inputTypes[inputTypeNdx]));
+	}
+}
+
+class SingleVertexArrayFirstGroup : public TestCaseGroup
+{
+public:
+									SingleVertexArrayFirstGroup	(Context& context, Array::InputType type);
+	virtual							~SingleVertexArrayFirstGroup	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayFirstGroup	(const SingleVertexArrayFirstGroup& other);
+	SingleVertexArrayFirstGroup&	operator=						(const SingleVertexArrayFirstGroup& other);
+	Array::InputType				m_type;
+};
+
+SingleVertexArrayFirstGroup::SingleVertexArrayFirstGroup (Context& context, Array::InputType type)
+	: TestCaseGroup	(context, Array::inputTypeToString(type).c_str(), Array::inputTypeToString(type).c_str())
+	, m_type		(type)
+{
+}
+
+SingleVertexArrayFirstGroup::~SingleVertexArrayFirstGroup (void)
+{
+}
+
+void SingleVertexArrayFirstGroup::init (void)
+{
+	int					counts[]		= {5, 256};
+	int					firsts[]		= {6, 24};
+	int					offsets[]		= {1, 17};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(offsets); offsetNdx++)
+	{
+		for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+		{
+			for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+			{
+				for (int firstNdx = 0; firstNdx < DE_LENGTH_OF_ARRAY(firsts); firstNdx++)
+				{
+					const bool	packed			= m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10;
+					const int	componentCount	= (packed) ? (4) : (2);
+					const int	stride			= (strides[strideNdx] < 0) ? ((packed) ? (8) : (Array::inputTypeSize(m_type) * componentCount)) : (strides[strideNdx]);
+					const int	alignment		= (packed) ? (Array::inputTypeSize(m_type) * componentCount) : (Array::inputTypeSize(m_type));
+					const bool	aligned			= ((stride % alignment) == 0) && ((offsets[offsetNdx] % alignment) == 0);
+					std::string name			= "first" + typeToString(firsts[firstNdx]) + "_offset" + typeToString(offsets[offsetNdx]) + "_stride" + typeToString(stride) + "_quads" + typeToString(counts[countNdx]);
+
+					MultiVertexArrayTest::Spec::ArraySpec arraySpec(m_type,
+																	Array::OUTPUTTYPE_VEC2,
+																	Array::STORAGE_BUFFER,
+																	Array::USAGE_DYNAMIC_DRAW,
+																	componentCount,
+																	offsets[offsetNdx],
+																	stride,
+																	false,
+																	GLValue::getMinValue(m_type),
+																	GLValue::getMaxValue(m_type));
+
+					MultiVertexArrayTest::Spec spec;
+					spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+					spec.drawCount	= counts[countNdx];
+					spec.first		= firsts[firstNdx];
+					spec.arrays.push_back(arraySpec);
+
+					if (!aligned)
+						addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+				}
+			}
+		}
+	}
+}
+
+class SingleVertexArrayFirstTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayFirstTests	(Context& context);
+	virtual							~SingleVertexArrayFirstTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayFirstTests	(const SingleVertexArrayFirstTests& other);
+	SingleVertexArrayFirstTests&	operator=						(const SingleVertexArrayFirstTests& other);
+};
+
+SingleVertexArrayFirstTests::SingleVertexArrayFirstTests (Context& context)
+	: TestCaseGroup(context, "first", "Single vertex attribute, different first values to drawArrays")
+{
+}
+
+SingleVertexArrayFirstTests::~SingleVertexArrayFirstTests (void)
+{
+}
+
+void SingleVertexArrayFirstTests::init (void)
+{
+	// Test offset with different input types, component counts and storage, Usage(?)
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_INT_2_10_10_10 };
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		addChild(new SingleVertexArrayFirstGroup(m_context, inputTypes[inputTypeNdx]));
+	}
+}
+
+class SingleVertexArrayOffsetGroup : public TestCaseGroup
+{
+public:
+									SingleVertexArrayOffsetGroup	(Context& context, Array::InputType type);
+	virtual							~SingleVertexArrayOffsetGroup	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayOffsetGroup	(const SingleVertexArrayOffsetGroup& other);
+	SingleVertexArrayOffsetGroup&	operator=						(const SingleVertexArrayOffsetGroup& other);
+	Array::InputType				m_type;
+};
+
+SingleVertexArrayOffsetGroup::SingleVertexArrayOffsetGroup (Context& context, Array::InputType type)
+	: TestCaseGroup	(context, Array::inputTypeToString(type).c_str(), Array::inputTypeToString(type).c_str())
+	, m_type		(type)
+{
+}
+
+SingleVertexArrayOffsetGroup::~SingleVertexArrayOffsetGroup (void)
+{
+}
+
+void SingleVertexArrayOffsetGroup::init (void)
+{
+	int					counts[]		= {1, 256};
+	int					offsets[]		= {1, 4, 17, 32};
+	int					strides[]		= {/*0,*/ -1, 17, 32}; // Tread negative value as sizeof input. Same as 0, but done outside of GL.
+
+	for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(offsets); offsetNdx++)
+	{
+		for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(counts); countNdx++)
+		{
+			for (int strideNdx = 0; strideNdx < DE_LENGTH_OF_ARRAY(strides); strideNdx++)
+			{
+				const bool			packed			= m_type == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_type == Array::INPUTTYPE_INT_2_10_10_10;
+				const int			componentCount	= (packed) ? (4) : (2);
+				const int			stride			= (strides[strideNdx] < 0 ? Array::inputTypeSize(m_type) * componentCount : strides[strideNdx]);
+				const int			alignment		= (packed) ? (Array::inputTypeSize(m_type) * componentCount) : (Array::inputTypeSize(m_type));
+				const bool			aligned			= ((stride % alignment) == 0) && ((offsets[offsetNdx] % alignment) == 0);
+				const std::string	name			= "offset" + typeToString(offsets[offsetNdx]) + "_stride" + typeToString(strides[strideNdx]) + "_quads" + typeToString(counts[countNdx]);
+
+				MultiVertexArrayTest::Spec::ArraySpec arraySpec(m_type,
+																Array::OUTPUTTYPE_VEC2,
+																Array::STORAGE_BUFFER,
+																Array::USAGE_DYNAMIC_DRAW,
+																componentCount,
+																offsets[offsetNdx],
+																stride,
+																false,
+																GLValue::getMinValue(m_type),
+																GLValue::getMaxValue(m_type));
+
+				MultiVertexArrayTest::Spec spec;
+				spec.primitive	= Array::PRIMITIVE_TRIANGLES;
+				spec.drawCount	= counts[countNdx];
+				spec.first		= 0;
+				spec.arrays.push_back(arraySpec);
+
+				if (!aligned)
+					addChild(new MultiVertexArrayTest(m_testCtx, m_context.getRenderContext(), spec, name.c_str(), name.c_str()));
+			}
+		}
+	}
+}
+
+class SingleVertexArrayOffsetTests : public TestCaseGroup
+{
+public:
+									SingleVertexArrayOffsetTests	(Context& context);
+	virtual							~SingleVertexArrayOffsetTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									SingleVertexArrayOffsetTests	(const SingleVertexArrayOffsetTests& other);
+	SingleVertexArrayOffsetTests&	operator=						(const SingleVertexArrayOffsetTests& other);
+};
+
+SingleVertexArrayOffsetTests::SingleVertexArrayOffsetTests (Context& context)
+	: TestCaseGroup(context, "offset", "Single vertex atribute offset element")
+{
+}
+
+SingleVertexArrayOffsetTests::~SingleVertexArrayOffsetTests (void)
+{
+}
+
+void SingleVertexArrayOffsetTests::init (void)
+{
+	// Test offset with different input types, component counts and storage, Usage(?)
+	Array::InputType	inputTypes[]	= {Array::INPUTTYPE_FLOAT, Array::INPUTTYPE_INT_2_10_10_10 };
+
+	for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); inputTypeNdx++)
+	{
+		addChild(new SingleVertexArrayOffsetGroup(m_context, inputTypes[inputTypeNdx]));
+	}
+}
+
+} // anonymous
+
+VertexArrayTests::VertexArrayTests (Context& context)
+	: TestCaseGroup(context, "vertex_arrays", "Vertex array and array tests")
+{
+}
+
+VertexArrayTests::~VertexArrayTests (void)
+{
+}
+
+void VertexArrayTests::init (void)
+{
+	tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "single_attribute", "Single attribute");
+	addChild(group);
+
+	// .single_attribute
+	{
+		group->addChild(new SingleVertexArrayStrideTests(m_context));
+		group->addChild(new SingleVertexArrayUsageTests(m_context));
+		group->addChild(new SingleVertexArrayOffsetTests(m_context));
+		group->addChild(new SingleVertexArrayFirstTests(m_context));
+	}
+}
+
+} // Stress
+} // gles3
+} // deqp
diff --git a/modules/gles3/stress/es3sVertexArrayTests.hpp b/modules/gles3/stress/es3sVertexArrayTests.hpp
new file mode 100644
index 0000000..077157d
--- /dev/null
+++ b/modules/gles3/stress/es3sVertexArrayTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES3SVERTEXARRAYTESTS_HPP
+#define _ES3SVERTEXARRAYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array and buffer unaligned access stress tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+namespace Stress
+{
+
+class VertexArrayTests : public TestCaseGroup
+{
+public:
+						VertexArrayTests	(Context& testCtx);
+						~VertexArrayTests	(void);
+
+	void				init				(void);
+
+private:
+						VertexArrayTests	(const VertexArrayTests& other);
+	VertexArrayTests&	operator=			(const VertexArrayTests& other);
+};
+
+} // Stress
+} // gles3
+} // deqp
+
+#endif // _ES3SVERTEXARRAYTESTS_HPP
diff --git a/modules/gles3/tes3Context.cpp b/modules/gles3/tes3Context.cpp
new file mode 100644
index 0000000..466b7fb
--- /dev/null
+++ b/modules/gles3/tes3Context.cpp
@@ -0,0 +1,76 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.0 test context.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3Context.hpp"
+#include "gluRenderContext.hpp"
+#include "gluRenderConfig.hpp"
+#include "gluFboRenderContext.hpp"
+#include "gluContextInfo.hpp"
+#include "tcuCommandLine.hpp"
+#include "glwWrapper.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+
+Context::Context (tcu::TestContext& testCtx)
+	: m_testCtx		(testCtx)
+	, m_renderCtx	(DE_NULL)
+	, m_contextInfo	(DE_NULL)
+{
+	try
+	{
+		m_renderCtx		= glu::createDefaultRenderContext(m_testCtx.getPlatform(), m_testCtx.getCommandLine(), glu::ApiType::es(3,0));
+		m_contextInfo	= glu::ContextInfo::create(*m_renderCtx);
+
+		// Set up function table for transparent wrapper.
+		glw::setCurrentThreadFunctions(&m_renderCtx->getFunctions());
+	}
+	catch (...)
+	{
+		glw::setCurrentThreadFunctions(DE_NULL);
+
+		delete m_contextInfo;
+		delete m_renderCtx;
+
+		throw;
+	}
+}
+
+Context::~Context (void)
+{
+	// Remove functions from wrapper.
+	glw::setCurrentThreadFunctions(DE_NULL);
+
+	delete m_contextInfo;
+	delete m_renderCtx;
+}
+
+const tcu::RenderTarget& Context::getRenderTarget (void) const
+{
+	return m_renderCtx->getRenderTarget();
+}
+
+} // gles3
+} // deqp
diff --git a/modules/gles3/tes3Context.hpp b/modules/gles3/tes3Context.hpp
new file mode 100644
index 0000000..bffb7ae
--- /dev/null
+++ b/modules/gles3/tes3Context.hpp
@@ -0,0 +1,65 @@
+#ifndef _TES3CONTEXT_HPP
+#define _TES3CONTEXT_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.0 test context.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestContext.hpp"
+
+namespace glu
+{
+class RenderContext;
+class ContextInfo;
+}
+
+namespace tcu
+{
+class RenderTarget;
+}
+
+namespace deqp
+{
+namespace gles3
+{
+
+class Context
+{
+public:
+									Context					(tcu::TestContext& testCtx);
+									~Context				(void);
+
+	tcu::TestContext&				getTestContext			(void)			{ return m_testCtx;			}
+	glu::RenderContext&				getRenderContext		(void)			{ return *m_renderCtx;		}
+	const glu::ContextInfo&			getContextInfo			(void) const	{ return *m_contextInfo;	}
+	const tcu::RenderTarget&		getRenderTarget			(void) const;
+
+private:
+	tcu::TestContext&				m_testCtx;
+	glu::RenderContext*				m_renderCtx;
+	glu::ContextInfo*				m_contextInfo;
+};
+
+} // gles3
+} // deqp
+
+#endif // _TES3CONTEXT_HPP
diff --git a/modules/gles3/tes3InfoTests.cpp b/modules/gles3/tes3InfoTests.cpp
new file mode 100644
index 0000000..5f15a3e
--- /dev/null
+++ b/modules/gles3/tes3InfoTests.cpp
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Platform Information and Capability Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3InfoTests.hpp"
+#include "tcuTestLog.hpp"
+#include "gluDefs.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <string>
+#include <vector>
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace gles3
+{
+
+class QueryStringCase : public TestCase
+{
+public:
+	QueryStringCase (Context& context, const char* name, const char* description, deUint32 query)
+		: TestCase	(context, name, description)
+		, m_query	(query)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+		const char*				result	= (const char*)gl.getString(m_query);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetString() failed");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << result << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		return STOP;
+	}
+
+private:
+	deUint32 m_query;
+};
+
+class QueryExtensionsCase : public TestCase
+{
+public:
+	QueryExtensionsCase (Context& context)
+		: TestCase	(context, "extensions", "Supported Extensions")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const vector<string> extensions = m_context.getContextInfo().getExtensions();
+
+		for (vector<string>::const_iterator i = extensions.begin(); i != extensions.end(); i++)
+			m_testCtx.getLog() << tcu::TestLog::Message << *i << tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		return STOP;
+	}
+};
+
+class RenderTargetInfoCase : public TestCase
+{
+public:
+	RenderTargetInfoCase (Context& context)
+		: TestCase	(context, "render_target", "Render Target Info")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const tcu::RenderTarget&	rt		= m_context.getRenderTarget();
+		const tcu::PixelFormat&		pf		= rt.getPixelFormat();
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Integer("RedBits",		"Red channel bits",		"", QP_KEY_TAG_NONE,	pf.redBits)
+			<< tcu::TestLog::Integer("GreenBits",	"Green channel bits",	"",	QP_KEY_TAG_NONE,	pf.greenBits)
+			<< tcu::TestLog::Integer("BlueBits",	"Blue channel bits",	"",	QP_KEY_TAG_NONE,	pf.blueBits)
+			<< tcu::TestLog::Integer("AlphaBits",	"Alpha channel bits",	"",	QP_KEY_TAG_NONE,	pf.alphaBits)
+			<< tcu::TestLog::Integer("DepthBits",	"Depth bits",			"",	QP_KEY_TAG_NONE,	rt.getDepthBits())
+			<< tcu::TestLog::Integer("StencilBits",	"Stencil bits",			"",	QP_KEY_TAG_NONE,	rt.getStencilBits())
+			<< tcu::TestLog::Integer("NumSamples",	"Number of samples",	"",	QP_KEY_TAG_NONE,	rt.getNumSamples())
+			<< tcu::TestLog::Integer("Width",		"Width",				"",	QP_KEY_TAG_NONE,	rt.getWidth())
+			<< tcu::TestLog::Integer("Height",		"Height",				"",	QP_KEY_TAG_NONE,	rt.getHeight());
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+InfoTests::InfoTests (Context& context)
+	: TestCaseGroup(context, "info", "Platform information queries")
+{
+}
+
+InfoTests::~InfoTests (void)
+{
+}
+
+void InfoTests::init (void)
+{
+	addChild(new QueryStringCase(m_context, "vendor",					"Vendor String",					GL_VENDOR));
+	addChild(new QueryStringCase(m_context, "renderer",					"Renderer String",					GL_RENDERER));
+	addChild(new QueryStringCase(m_context, "version",					"Version String",					GL_VERSION));
+	addChild(new QueryStringCase(m_context, "shading_language_version",	"Shading Language Version String",	GL_SHADING_LANGUAGE_VERSION));
+	addChild(new QueryExtensionsCase(m_context));
+	addChild(new RenderTargetInfoCase(m_context));
+}
+
+} // gles2
+} // deqp
diff --git a/modules/gles3/tes3InfoTests.hpp b/modules/gles3/tes3InfoTests.hpp
new file mode 100644
index 0000000..7a3f2c9
--- /dev/null
+++ b/modules/gles3/tes3InfoTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TES3INFOTESTS_HPP
+#define _TES3INFOTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Platform Information and Capability Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes3TestCase.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+
+class InfoTests : public TestCaseGroup
+{
+public:
+				InfoTests		(Context& context);
+				~InfoTests		(void);
+
+	void		init			(void);
+};
+
+} // gles3
+} // deqp
+
+#endif // _TES3INFOTESTS_HPP
diff --git a/modules/gles3/tes3TestCase.cpp b/modules/gles3/tes3TestCase.cpp
new file mode 100644
index 0000000..26f1ec2
--- /dev/null
+++ b/modules/gles3/tes3TestCase.cpp
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.0 test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCase.hpp"
diff --git a/modules/gles3/tes3TestCase.hpp b/modules/gles3/tes3TestCase.hpp
new file mode 100644
index 0000000..043d2f2
--- /dev/null
+++ b/modules/gles3/tes3TestCase.hpp
@@ -0,0 +1,86 @@
+#ifndef _TES3TESTCASE_HPP
+#define _TES3TESTCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.0 test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "tes3Context.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+
+class TestCaseGroup : public tcu::TestCaseGroup
+{
+public:
+						TestCaseGroup		(Context& context, const char* name, const char* description);
+						TestCaseGroup		(Context& context, const char* name, const char* description, const std::vector<TestNode*>& children);
+	virtual				~TestCaseGroup		(void) {}
+
+	Context&			getContext			(void) { return m_context; }
+
+protected:
+	Context&			m_context;
+};
+
+class TestCase : public tcu::TestCase
+{
+public:
+						TestCase			(Context& context, const char* name, const char* description);
+						TestCase			(Context& context, tcu::TestNodeType nodeType, const char* name, const char* description);
+	virtual				~TestCase			(void) {}
+
+protected:
+	Context&			m_context;
+};
+
+inline TestCaseGroup::TestCaseGroup (Context& context, const char* name, const char* description)
+	: tcu::TestCaseGroup	(context.getTestContext(), name, description)
+	, m_context				(context)
+{
+}
+
+inline TestCaseGroup::TestCaseGroup (Context& context, const char* name, const char* description, const std::vector<TestNode*>& children)
+	: tcu::TestCaseGroup	(context.getTestContext(), name, description, children)
+	, m_context				(context)
+{
+}
+
+inline TestCase::TestCase (Context& context, const char* name, const char* description)
+	: tcu::TestCase			(context.getTestContext(), name, description)
+	, m_context				(context)
+{
+}
+
+inline TestCase::TestCase (Context& context, tcu::TestNodeType nodeType, const char* name, const char* description)
+	: tcu::TestCase			(context.getTestContext(), nodeType, name, description)
+	, m_context				(context)
+{
+}
+
+} // gles3
+} // deqp
+
+#endif // _TES3TESTCASE_HPP
diff --git a/modules/gles3/tes3TestCaseWrapper.cpp b/modules/gles3/tes3TestCaseWrapper.cpp
new file mode 100644
index 0000000..76cb78d
--- /dev/null
+++ b/modules/gles3/tes3TestCaseWrapper.cpp
@@ -0,0 +1,109 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.0 Test Case Wrapper.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestCaseWrapper.hpp"
+#include "gluStateReset.hpp"
+#include "tcuTestLog.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+
+using tcu::TestLog;
+
+TestCaseWrapper::TestCaseWrapper (tcu::TestContext& testCtx, glu::RenderContext& renderCtx)
+	: tcu::TestCaseWrapper	(testCtx)
+	, m_renderCtx			(renderCtx)
+{
+	TCU_CHECK(contextSupports(renderCtx.getType(), glu::ApiType::es(3,0)));
+}
+
+TestCaseWrapper::~TestCaseWrapper (void)
+{
+}
+
+bool TestCaseWrapper::initTestCase (tcu::TestCase* testCase)
+{
+	return tcu::TestCaseWrapper::initTestCase(testCase);
+}
+
+bool TestCaseWrapper::deinitTestCase (tcu::TestCase* testCase)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (!tcu::TestCaseWrapper::deinitTestCase(testCase))
+		return false;
+
+	try
+	{
+		// Reset state
+		glu::resetState(m_renderCtx);
+	}
+	catch (const std::exception& e)
+	{
+		log << e;
+		log << TestLog::Message << "Error in state reset, test program will terminate." << TestLog::EndMessage;
+		return false;
+	}
+
+	return true;
+}
+
+tcu::TestNode::IterateResult TestCaseWrapper::iterateTestCase (tcu::TestCase* testCase)
+{
+	tcu::TestCase::IterateResult result = tcu::TestNode::STOP;
+
+	// Clear to surrender-blue
+	{
+		const glw::Functions& gl = m_renderCtx.getFunctions();
+		gl.clearColor(0.125f, 0.25f, 0.5f, 1.f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+	}
+
+	result = tcu::TestCaseWrapper::iterateTestCase(testCase);
+
+	// Call implementation specific post-iterate routine (usually handles native events and swaps buffers)
+	try
+	{
+		m_renderCtx.postIterate();
+		return result;
+	}
+	catch (const tcu::ResourceError& e)
+	{
+		m_testCtx.getLog() << e;
+		m_testCtx.setTestResult(QP_TEST_RESULT_RESOURCE_ERROR, "Resource error in context post-iteration routine");
+		return tcu::TestNode::STOP;
+	}
+	catch (const std::exception& e)
+	{
+		m_testCtx.getLog() << e;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Error in context post-iteration routine");
+		return tcu::TestNode::STOP;
+	}
+}
+
+} // gles3
+} // deqp
diff --git a/modules/gles3/tes3TestCaseWrapper.hpp b/modules/gles3/tes3TestCaseWrapper.hpp
new file mode 100644
index 0000000..6ac92fb
--- /dev/null
+++ b/modules/gles3/tes3TestCaseWrapper.hpp
@@ -0,0 +1,54 @@
+#ifndef _TES3TESTCASEWRAPPER_HPP
+#define _TES3TESTCASEWRAPPER_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.0 Test Case Wrapper.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCaseWrapper.hpp"
+#include "gluRenderContext.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+
+class TestCaseWrapper : public tcu::TestCaseWrapper
+{
+public:
+											TestCaseWrapper			(tcu::TestContext& testCtx, glu::RenderContext& renderCtx);
+	virtual									~TestCaseWrapper		(void);
+
+	virtual bool							initTestCase			(tcu::TestCase* testCase);
+
+	// If deinit returns false, test execution will be aborted.
+	virtual bool							deinitTestCase			(tcu::TestCase* testCase);
+	virtual tcu::TestNode::IterateResult	iterateTestCase			(tcu::TestCase* testCase);
+
+private:
+	glu::RenderContext&						m_renderCtx;
+};
+
+} // gles3
+} // deqp
+
+#endif // _TES3TESTCASEWRAPPER_HPP
diff --git a/modules/gles3/tes3TestPackage.cpp b/modules/gles3/tes3TestPackage.cpp
new file mode 100644
index 0000000..6f569f9
--- /dev/null
+++ b/modules/gles3/tes3TestPackage.cpp
@@ -0,0 +1,105 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.0 Test Package
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestPackage.hpp"
+#include "tes3InfoTests.hpp"
+#include "es3fFunctionalTests.hpp"
+#include "es3aAccuracyTests.hpp"
+#include "es3sStressTests.hpp"
+#include "es3pPerformanceTests.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+
+PackageContext::PackageContext (tcu::TestContext& testCtx)
+	: m_context		(DE_NULL)
+	, m_caseWrapper	(DE_NULL)
+{
+	try
+	{
+		m_context		= new Context(testCtx);
+		m_caseWrapper	= new TestCaseWrapper(testCtx, m_context->getRenderContext());
+	}
+	catch (...)
+	{
+		delete m_caseWrapper;
+		delete m_context;
+
+		throw;
+	}
+}
+
+PackageContext::~PackageContext (void)
+{
+	delete m_caseWrapper;
+	delete m_context;
+}
+
+TestPackage::TestPackage (tcu::TestContext& testCtx)
+	: tcu::TestPackage	(testCtx, "dEQP-GLES3", "dEQP OpenGL ES 3.0 Tests")
+	, m_packageCtx		(DE_NULL)
+	, m_archive			(testCtx.getRootArchive(), "gles3/")
+{
+}
+
+TestPackage::~TestPackage (void)
+{
+	// Destroy children first since destructors may access context.
+	TestNode::deinit();
+	delete m_packageCtx;
+}
+
+void TestPackage::init (void)
+{
+	try
+	{
+		// Create context
+		m_packageCtx = new PackageContext(m_testCtx);
+
+		// Add main test groups
+		addChild(new InfoTests						(m_packageCtx->getContext()));
+		addChild(new Functional::FunctionalTests	(m_packageCtx->getContext()));
+		addChild(new Accuracy::AccuracyTests		(m_packageCtx->getContext()));
+		addChild(new Performance::PerformanceTests	(m_packageCtx->getContext()));
+		addChild(new Stress::StressTests			(m_packageCtx->getContext()));
+	}
+	catch (...)
+	{
+		delete m_packageCtx;
+		m_packageCtx = DE_NULL;
+
+		throw;
+	}
+}
+
+void TestPackage::deinit (void)
+{
+	TestNode::deinit();
+	delete m_packageCtx;
+	m_packageCtx = DE_NULL;
+}
+
+} // gles3
+} // deqp
diff --git a/modules/gles3/tes3TestPackage.hpp b/modules/gles3/tes3TestPackage.hpp
new file mode 100644
index 0000000..c58b15d
--- /dev/null
+++ b/modules/gles3/tes3TestPackage.hpp
@@ -0,0 +1,71 @@
+#ifndef _TES3TESTPACKAGE_HPP
+#define _TES3TESTPACKAGE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.0 Test Package
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestPackage.hpp"
+#include "tes3TestCaseWrapper.hpp"
+#include "tes3Context.hpp"
+#include "tcuResource.hpp"
+
+namespace deqp
+{
+namespace gles3
+{
+
+class PackageContext
+{
+public:
+									PackageContext			(tcu::TestContext& testCtx);
+									~PackageContext			(void);
+
+	Context&						getContext				(void) { return *m_context;		}
+	tcu::TestCaseWrapper&			getTestCaseWrapper		(void) { return *m_caseWrapper; }
+
+private:
+	Context*						m_context;
+	TestCaseWrapper*				m_caseWrapper;
+};
+
+class TestPackage : public tcu::TestPackage
+{
+public:
+									TestPackage				(tcu::TestContext& testCtx);
+	virtual							~TestPackage			(void);
+
+	virtual void					init					(void);
+	virtual void					deinit					(void);
+
+	tcu::TestCaseWrapper&			getTestCaseWrapper		(void) { return m_packageCtx->getTestCaseWrapper(); }
+	tcu::Archive&					getArchive				(void) { return m_archive; }
+
+private:
+	PackageContext*					m_packageCtx;
+	tcu::ResourcePrefix				m_archive;
+};
+
+} // gles3
+} // deqp
+
+#endif // _TES3TESTPACKAGE_HPP
diff --git a/modules/gles3/tes3TestPackageEntry.cpp b/modules/gles3/tes3TestPackageEntry.cpp
new file mode 100644
index 0000000..fc0c4f1
--- /dev/null
+++ b/modules/gles3/tes3TestPackageEntry.cpp
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.0 Test Package Entry Point.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes3TestPackage.hpp"
+
+// Register package to test executor.
+
+static tcu::TestPackage* createTestPackage (tcu::TestContext& testCtx)
+{
+	return new deqp::gles3::TestPackage(testCtx);
+}
+
+tcu::TestPackageDescriptor g_gles3PackageDescriptor("dEQP-GLES3", createTestPackage);
diff --git a/modules/gles31/CMakeLists.txt b/modules/gles31/CMakeLists.txt
new file mode 100644
index 0000000..1bdf423
--- /dev/null
+++ b/modules/gles31/CMakeLists.txt
@@ -0,0 +1,41 @@
+# dEQP-GLES31
+
+include_directories(
+	../glshared
+	.				# For child modules
+	)
+
+add_subdirectory(functional)
+add_subdirectory(stress)
+
+include_directories(
+	functional
+	stress
+	)
+
+set(DEQP_GLES31_SRCS
+	tes31Context.cpp
+	tes31Context.hpp
+	tes31InfoTests.cpp
+	tes31InfoTests.hpp
+	tes31TestCase.cpp
+	tes31TestCase.hpp
+	tes31TestCaseWrapper.cpp
+	tes31TestCaseWrapper.hpp
+	tes31TestPackage.cpp
+	tes31TestPackage.hpp
+	)
+
+set(DEQP_GLES31_LIBS
+	deqp-gles31-functional
+	deqp-gles31-stress
+	tcutil
+	glutil
+	${DEQP_GLES3_LIBRARIES}
+	)
+
+add_deqp_module(deqp-gles31 "${DEQP_GLES31_SRCS}" "${DEQP_GLES31_LIBS}" tes31TestPackageEntry.cpp)
+
+# Data directories
+add_data_dir(deqp-gles31 ../../data/gles31/data		gles31/data)
+add_data_dir(deqp-gles31 ../../data/gles31/shaders	gles31/shaders)
diff --git a/modules/gles31/functional/CMakeLists.txt b/modules/gles31/functional/CMakeLists.txt
new file mode 100644
index 0000000..8197733
--- /dev/null
+++ b/modules/gles31/functional/CMakeLists.txt
@@ -0,0 +1,133 @@
+# dEQP-GLES31.functional
+
+set(DEQP_GLES31_FUNCTIONAL_SRCS
+	es31fBasicComputeShaderTests.cpp
+	es31fBasicComputeShaderTests.hpp
+	es31fBuiltinPrecisionTests.cpp
+	es31fBuiltinPrecisionTests.hpp
+	es31fComputeShaderBuiltinVarTests.cpp
+	es31fComputeShaderBuiltinVarTests.hpp
+	es31fFunctionalTests.cpp
+	es31fFunctionalTests.hpp
+	es31fDrawTests.cpp
+	es31fDrawTests.hpp
+	es31fGeometryShaderTests.cpp
+	es31fGeometryShaderTests.hpp
+	es31fShaderSharedVarTests.cpp
+	es31fShaderSharedVarTests.hpp
+	es31fTessellationTests.cpp
+	es31fTessellationTests.hpp
+	es31fAtomicCounterTests.hpp
+	es31fAtomicCounterTests.cpp
+	es31fSampleShadingTests.cpp
+	es31fSampleShadingTests.hpp
+	es31fSampleVariableTests.cpp
+	es31fSampleVariableTests.hpp
+	es31fShaderMultisampleInterpolationTests.cpp
+	es31fShaderMultisampleInterpolationTests.hpp
+	es31fShaderMultisampleInterpolationStateQueryTests.cpp
+	es31fShaderMultisampleInterpolationStateQueryTests.hpp
+	es31fLayoutBindingTests.cpp
+	es31fLayoutBindingTests.hpp
+	es31fSeparateShaderTests.cpp
+	es31fSeparateShaderTests.hpp
+	es31fShaderAtomicOpTests.cpp
+	es31fShaderAtomicOpTests.hpp
+	es31fShaderImageLoadStoreTests.cpp
+	es31fShaderImageLoadStoreTests.hpp
+	es31fSSBOLayoutCase.cpp
+	es31fSSBOLayoutCase.hpp
+	es31fSSBOLayoutTests.cpp
+	es31fSSBOLayoutTests.hpp
+	es31fSSBOArrayLengthTests.hpp
+	es31fSSBOArrayLengthTests.cpp
+	es31fShaderCommonFunctionTests.cpp
+	es31fShaderCommonFunctionTests.hpp
+	es31fShaderPackingFunctionTests.cpp
+	es31fShaderPackingFunctionTests.hpp
+	es31fShaderIntegerFunctionTests.cpp
+	es31fShaderIntegerFunctionTests.hpp
+	es31fStencilTexturingTests.cpp
+	es31fStencilTexturingTests.hpp
+	es31fShaderStateQueryTests.hpp
+	es31fShaderStateQueryTests.cpp
+	es31fShaderTextureSizeTests.hpp
+	es31fShaderTextureSizeTests.cpp
+	es31fSynchronizationTests.hpp
+	es31fSynchronizationTests.cpp
+	es31fFboTestUtil.hpp
+	es31fFboTestUtil.cpp
+	es31fTextureFormatTests.hpp
+	es31fTextureFormatTests.cpp
+	es31fTextureLevelStateQueryTests.hpp
+	es31fTextureLevelStateQueryTests.cpp
+	es31fTextureSpecificationTests.hpp
+	es31fTextureSpecificationTests.cpp
+	es31fIntegerStateQueryTests.cpp
+	es31fIntegerStateQueryTests.hpp
+	es31fInternalFormatQueryTests.cpp
+	es31fInternalFormatQueryTests.hpp
+	es31fTextureMultisampleTests.cpp
+	es31fTextureMultisampleTests.hpp
+	es31fUniformLocationTests.cpp
+	es31fUniformLocationTests.hpp
+	es31fMultisampleTests.cpp
+	es31fMultisampleTests.hpp
+	es31fMultisampleShaderRenderCase.cpp
+	es31fMultisampleShaderRenderCase.hpp
+	es31fIndirectComputeDispatchTests.cpp
+	es31fIndirectComputeDispatchTests.hpp
+	es31fVertexAttributeBindingTests.cpp
+	es31fVertexAttributeBindingTests.hpp
+	es31fVertexAttributeBindingStateQueryTests.cpp
+	es31fVertexAttributeBindingStateQueryTests.hpp
+	es31fProgramUniformTests.cpp
+	es31fProgramUniformTests.hpp
+	es31fProgramInterfaceDefinition.cpp
+	es31fProgramInterfaceDefinition.hpp
+	es31fProgramInterfaceDefinitionUtil.cpp
+	es31fProgramInterfaceDefinitionUtil.hpp
+	es31fProgramInterfaceQueryTests.cpp
+	es31fProgramInterfaceQueryTests.hpp
+	es31fProgramInterfaceQueryTestCase.cpp
+	es31fProgramInterfaceQueryTestCase.hpp
+	es31fOpaqueTypeIndexingTests.cpp
+	es31fOpaqueTypeIndexingTests.hpp
+	es31fAdvancedBlendTests.cpp
+	es31fAdvancedBlendTests.hpp
+	es31fTessellationGeometryInteractionTests.cpp
+	es31fTessellationGeometryInteractionTests.hpp
+	es31fUniformBlockTests.cpp
+	es31fUniformBlockTests.hpp
+	es31fDebugTests.cpp
+	es31fDebugTests.hpp
+	es31fFboNoAttachmentTests.cpp
+	es31fFboNoAttachmentTests.hpp
+	es31fNegativeTestShared.cpp
+	es31fNegativeTestShared.hpp
+	es31fNegativeBufferApiTests.cpp
+	es31fNegativeBufferApiTests.hpp
+	es31fNegativeTextureApiTests.cpp
+	es31fNegativeTextureApiTests.hpp
+	es31fNegativeShaderApiTests.cpp
+	es31fNegativeShaderApiTests.hpp
+	es31fNegativeFragmentApiTests.cpp
+	es31fNegativeFragmentApiTests.hpp
+	es31fNegativeVertexArrayApiTests.cpp
+	es31fNegativeVertexArrayApiTests.hpp
+	es31fNegativeStateApiTests.cpp
+	es31fNegativeStateApiTests.hpp
+	es31fTextureGatherTests.cpp
+	es31fTextureGatherTests.hpp
+	es31fTextureFormatTests.cpp
+	es31fTextureFormatTests.hpp
+	es31fTextureBufferTests.cpp
+	es31fTextureBufferTests.hpp
+	es31fShaderBuiltinConstantTests.cpp
+	es31fShaderBuiltinConstantTests.hpp
+	es31fShaderHelperInvocationTests.cpp
+	es31fShaderHelperInvocationTests.hpp
+	)
+
+add_library(deqp-gles31-functional STATIC ${DEQP_GLES31_FUNCTIONAL_SRCS})
+target_link_libraries(deqp-gles31-functional deqp-gl-shared glutil glutil-sglr tcutil referencerenderer ${DEQP_OPENGL_LIBRARIES})
diff --git a/modules/gles31/functional/es31fAdvancedBlendTests.cpp b/modules/gles31/functional/es31fAdvancedBlendTests.cpp
new file mode 100644
index 0000000..8e4b1df
--- /dev/null
+++ b/modules/gles31/functional/es31fAdvancedBlendTests.cpp
@@ -0,0 +1,597 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Advanced blending (GL_KHR_blend_equation_advanced) tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fAdvancedBlendTests.hpp"
+#include "gluStrUtil.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluContextInfo.hpp"
+#include "gluShaderProgram.hpp"
+#include "tcuPixelFormat.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuStringTemplate.hpp"
+#include "deRandom.hpp"
+#include "rrFragmentOperations.hpp"
+#include "sglrReferenceUtils.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <string>
+#include <vector>
+
+namespace deqp
+{
+
+using gls::FragmentOpUtil::IntegerQuad;
+using gls::FragmentOpUtil::ReferenceQuadRenderer;
+using tcu::TextureLevel;
+using tcu::Vec2;
+using tcu::Vec4;
+using tcu::UVec4;
+using tcu::TestLog;
+using tcu::TextureFormat;
+using std::string;
+using std::vector;
+using std::map;
+
+namespace gles31
+{
+namespace Functional
+{
+
+namespace
+{
+
+enum
+{
+	MAX_VIEWPORT_WIDTH		= 128,
+	MAX_VIEWPORT_HEIGHT		= 128
+};
+
+enum RenderTargetType
+{
+	RENDERTARGETTYPE_DEFAULT	= 0,	//!< Default framebuffer
+	RENDERTARGETTYPE_SRGB_FBO,
+	RENDERTARGETTYPE_MSAA_FBO,
+
+	RENDERTARGETTYPE_LAST
+};
+
+class AdvancedBlendCase : public TestCase
+{
+public:
+							AdvancedBlendCase	(Context& context, const char* name, const char* desc, deUint32 mode, int overdrawCount, bool coherent, RenderTargetType rtType);
+
+							~AdvancedBlendCase	(void);
+
+	void					init				(void);
+	void					deinit				(void);
+
+	IterateResult			iterate		(void);
+
+private:
+							AdvancedBlendCase	(const AdvancedBlendCase&);
+	AdvancedBlendCase&		operator=			(const AdvancedBlendCase&);
+
+	const deUint32			m_blendMode;
+	const int				m_overdrawCount;
+	const bool				m_coherentBlending;
+	const RenderTargetType	m_rtType;
+	const int				m_numIters;
+
+	deUint32				m_colorRbo;
+	deUint32				m_fbo;
+
+	deUint32				m_resolveColorRbo;
+	deUint32				m_resolveFbo;
+
+	glu::ShaderProgram*		m_program;
+
+	ReferenceQuadRenderer*	m_referenceRenderer;
+	TextureLevel*			m_refColorBuffer;
+
+	const int				m_renderWidth;
+	const int				m_renderHeight;
+	const int				m_viewportWidth;
+	const int				m_viewportHeight;
+
+	int						m_iterNdx;
+};
+
+AdvancedBlendCase::AdvancedBlendCase (Context&			context,
+									  const char*		name,
+									  const char*		desc,
+									  deUint32			mode,
+									  int				overdrawCount,
+									  bool				coherent,
+									  RenderTargetType	rtType)
+	: TestCase				(context, name, desc)
+	, m_blendMode			(mode)
+	, m_overdrawCount		(overdrawCount)
+	, m_coherentBlending	(coherent)
+	, m_rtType				(rtType)
+	, m_numIters			(5)
+	, m_colorRbo			(0)
+	, m_fbo					(0)
+	, m_resolveColorRbo		(0)
+	, m_resolveFbo			(0)
+	, m_program				(DE_NULL)
+	, m_referenceRenderer	(DE_NULL)
+	, m_refColorBuffer		(DE_NULL)
+	, m_renderWidth			(rtType != RENDERTARGETTYPE_DEFAULT ? 2*MAX_VIEWPORT_WIDTH	: m_context.getRenderTarget().getWidth())
+	, m_renderHeight		(rtType != RENDERTARGETTYPE_DEFAULT ? 2*MAX_VIEWPORT_HEIGHT	: m_context.getRenderTarget().getHeight())
+	, m_viewportWidth		(de::min<int>(m_renderWidth,	MAX_VIEWPORT_WIDTH))
+	, m_viewportHeight		(de::min<int>(m_renderHeight,	MAX_VIEWPORT_HEIGHT))
+	, m_iterNdx				(0)
+{
+}
+
+const char* getBlendLayoutQualifier (rr::BlendEquationAdvanced equation)
+{
+	static const char* s_qualifiers[] =
+	{
+		"blend_support_multiply",
+		"blend_support_screen",
+		"blend_support_overlay",
+		"blend_support_darken",
+		"blend_support_lighten",
+		"blend_support_colordodge",
+		"blend_support_colorburn",
+		"blend_support_hardlight",
+		"blend_support_softlight",
+		"blend_support_difference",
+		"blend_support_exclusion",
+		"blend_support_hsl_hue",
+		"blend_support_hsl_saturation",
+		"blend_support_hsl_color",
+		"blend_support_hsl_luminosity",
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qualifiers) == rr::BLENDEQUATION_ADVANCED_LAST);
+	DE_ASSERT(de::inBounds<int>(equation, 0, rr::BLENDEQUATION_ADVANCED_LAST));
+	return s_qualifiers[equation];
+}
+
+glu::ProgramSources getBlendProgramSrc (rr::BlendEquationAdvanced equation)
+{
+	static const char*	s_vertSrc	= "#version 310 es\n"
+									  "in highp vec4 a_position;\n"
+									  "in mediump vec4 a_color;\n"
+									  "out mediump vec4 v_color;\n"
+									  "void main()\n"
+									  "{\n"
+									  "	gl_Position = a_position;\n"
+									  "	v_color = a_color;\n"
+									  "}\n";
+	static const char*	s_fragSrc	= "#version 310 es\n"
+									  "#extension GL_KHR_blend_equation_advanced : require\n"
+									  "in mediump vec4 v_color;\n"
+									  "layout(${SUPPORT_QUALIFIER}) out;\n"
+									  "layout(location = 0) out mediump vec4 o_color;\n"
+									  "void main()\n"
+									  "{\n"
+									  "	o_color = v_color;\n"
+									  "}\n";
+
+	map<string, string> args;
+
+	args["SUPPORT_QUALIFIER"] = getBlendLayoutQualifier(equation);
+
+	return glu::ProgramSources()
+		<< glu::VertexSource(s_vertSrc)
+		<< glu::FragmentSource(tcu::StringTemplate(s_fragSrc).specialize(args));
+}
+
+void AdvancedBlendCase::init (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const bool				useFbo			= m_rtType != RENDERTARGETTYPE_DEFAULT;
+	const bool				useSRGB			= m_rtType == RENDERTARGETTYPE_SRGB_FBO;
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_KHR_blend_equation_advanced"))
+		throw tcu::NotSupportedError("GL_KHR_blend_equation_advanced is not supported", DE_NULL, __FILE__, __LINE__);
+
+	if (m_coherentBlending && !m_context.getContextInfo().isExtensionSupported("GL_KHR_blend_equation_advanced_coherent"))
+		throw tcu::NotSupportedError("GL_KHR_blend_equation_advanced_coherent is not supported", DE_NULL, __FILE__, __LINE__);
+
+	TCU_CHECK(gl.blendBarrierKHR);
+
+	DE_ASSERT(!m_program);
+	DE_ASSERT(!m_referenceRenderer);
+	DE_ASSERT(!m_refColorBuffer);
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), getBlendProgramSrc(sglr::rr_util::mapGLBlendEquationAdvanced(m_blendMode)));
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+	{
+		delete m_program;
+		m_program = DE_NULL;
+		TCU_FAIL("Compile failed");
+	}
+
+	m_referenceRenderer	= new ReferenceQuadRenderer;
+	m_refColorBuffer	= new TextureLevel(TextureFormat(useSRGB ? TextureFormat::sRGBA : TextureFormat::RGBA, TextureFormat::UNORM_INT8), m_viewportWidth, m_viewportHeight);
+
+	if (useFbo)
+	{
+		const deUint32	format		= useSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8;
+		const int		numSamples	= m_rtType == RENDERTARGETTYPE_MSAA_FBO ? 4 : 0;
+
+		m_testCtx.getLog() << TestLog::Message << "Using FBO of size (" << m_renderWidth << ", " << m_renderHeight << ") with format "
+											   << glu::getPixelFormatStr(format) << " and " << numSamples << " samples"
+						   << TestLog::EndMessage;
+
+		gl.genRenderbuffers(1, &m_colorRbo);
+		gl.bindRenderbuffer(GL_RENDERBUFFER, m_colorRbo);
+		gl.renderbufferStorageMultisample(GL_RENDERBUFFER, numSamples, format, m_renderWidth, m_renderHeight);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create color RBO");
+
+		gl.genFramebuffers(1, &m_fbo);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorRbo);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create FBO");
+
+		TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+		if (numSamples > 0)
+		{
+			// Create resolve FBO
+			gl.genRenderbuffers(1, &m_resolveColorRbo);
+			gl.bindRenderbuffer(GL_RENDERBUFFER, m_resolveColorRbo);
+			gl.renderbufferStorageMultisample(GL_RENDERBUFFER, 0, format, m_renderWidth, m_renderHeight);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create resolve color RBO");
+
+			gl.genFramebuffers(1, &m_resolveFbo);
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
+			gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_resolveColorRbo);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create FBO");
+
+			TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+		}
+	}
+	else
+		DE_ASSERT(m_rtType == RENDERTARGETTYPE_DEFAULT);
+
+	m_iterNdx = 0;
+}
+
+AdvancedBlendCase::~AdvancedBlendCase (void)
+{
+	AdvancedBlendCase::deinit();
+}
+
+void AdvancedBlendCase::deinit (void)
+{
+	delete m_program;
+	delete m_referenceRenderer;
+	delete m_refColorBuffer;
+
+	m_program			= DE_NULL;
+	m_referenceRenderer	= DE_NULL;
+	m_refColorBuffer	= DE_NULL;
+
+	if (m_colorRbo || m_fbo)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+		if (m_colorRbo != 0)
+		{
+			gl.deleteRenderbuffers(1, &m_colorRbo);
+			m_colorRbo = 0;
+		}
+
+		if (m_fbo != 0)
+		{
+			gl.deleteFramebuffers(1, &m_fbo);
+			m_fbo = 0;
+		}
+
+		if (m_resolveColorRbo)
+		{
+			gl.deleteRenderbuffers(1, &m_resolveColorRbo);
+			m_resolveColorRbo = 0;
+		}
+
+		if (m_resolveFbo)
+		{
+			gl.deleteRenderbuffers(1, &m_resolveFbo);
+			m_resolveFbo = 0;
+		}
+	}
+}
+
+static tcu::Vec4 randomColor (de::Random* rnd)
+{
+	const float rgbValues[]		= { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f };
+	const float alphaValues[]	= { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f };
+
+	const float r = rnd->choose<float>(DE_ARRAY_BEGIN(rgbValues), DE_ARRAY_END(rgbValues));
+	const float g = rnd->choose<float>(DE_ARRAY_BEGIN(rgbValues), DE_ARRAY_END(rgbValues));
+	const float b = rnd->choose<float>(DE_ARRAY_BEGIN(rgbValues), DE_ARRAY_END(rgbValues));
+	const float a = rnd->choose<float>(DE_ARRAY_BEGIN(alphaValues), DE_ARRAY_END(alphaValues));
+	return tcu::Vec4(r, g, b, a);
+}
+
+static tcu::ConstPixelBufferAccess getLinearAccess (const tcu::ConstPixelBufferAccess& access)
+{
+	if (access.getFormat().order == TextureFormat::sRGBA)
+		return tcu::ConstPixelBufferAccess(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8),
+										   access.getWidth(), access.getHeight(), access.getDepth(),
+										   access.getRowPitch(), access.getSlicePitch(), access.getDataPtr());
+	else
+		return access;
+}
+
+AdvancedBlendCase::IterateResult AdvancedBlendCase::iterate (void)
+{
+	const glu::RenderContext&		renderCtx		= m_context.getRenderContext();
+	const glw::Functions&			gl				= renderCtx.getFunctions();
+	de::Random						rnd				(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
+	const int						viewportX		= rnd.getInt(0, m_renderWidth - m_viewportWidth);
+	const int						viewportY		= rnd.getInt(0, m_renderHeight - m_viewportHeight);
+	const bool						useFbo			= m_rtType != RENDERTARGETTYPE_DEFAULT;
+	const bool						requiresResolve	= m_rtType == RENDERTARGETTYPE_MSAA_FBO;
+	const int						numQuads		= m_overdrawCount+1;
+	TextureLevel					renderedImg		(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), m_viewportWidth, m_viewportHeight);
+	vector<Vec4>					colors			(numQuads*4);
+
+	for (vector<Vec4>::iterator col = colors.begin(); col != colors.end(); ++col)
+		*col = randomColor(&rnd);
+
+	// Render with GL.
+	{
+		const deUint32		program				= m_program->getProgram();
+		const int			posLoc				= gl.getAttribLocation(program, "a_position");
+		const int			colorLoc			= gl.getAttribLocation(program, "a_color");
+		const glu::Buffer	indexBuffer			(renderCtx);
+		const glu::Buffer	positionBuffer		(renderCtx);
+		const glu::Buffer	colorBuffer			(renderCtx);
+		vector<Vec2>		positions			(numQuads*4);
+		vector<deUint16>	indices				(numQuads*6);
+		const deUint16		singleQuadIndices[]	= { 0, 2, 1, 1, 2, 3 };
+		const Vec2			singleQuadPos[]		=
+		{
+			Vec2(-1.0f, -1.0f),
+			Vec2(-1.0f, +1.0f),
+			Vec2(+1.0f, -1.0f),
+			Vec2(+1.0f, +1.0f),
+		};
+
+		TCU_CHECK(posLoc >= 0 && colorLoc >= 0);
+
+		for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
+		{
+			std::copy(DE_ARRAY_BEGIN(singleQuadPos), DE_ARRAY_END(singleQuadPos), &positions[quadNdx*4]);
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(singleQuadIndices); ndx++)
+				indices[quadNdx*6 + ndx] = (deUint16)(quadNdx*4 + singleQuadIndices[ndx]);
+		}
+
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, *indexBuffer);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indices.size()*sizeof(indices[0])), &indices[0], GL_STATIC_DRAW);
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, *positionBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(positions.size()*sizeof(positions[0])), &positions[0], GL_STATIC_DRAW);
+		gl.enableVertexAttribArray(posLoc);
+		gl.vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, *colorBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(colors.size()*sizeof(colors[0])), &colors[0], GL_STATIC_DRAW);
+		gl.enableVertexAttribArray(colorLoc);
+		gl.vertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create buffers");
+
+		gl.useProgram(program);
+		gl.viewport(viewportX, viewportY, m_viewportWidth, m_viewportHeight);
+		gl.blendEquation(m_blendMode);
+		if (m_coherentBlending)
+			gl.enable(GL_BLEND_ADVANCED_COHERENT_KHR);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set render state");
+
+		gl.disable(GL_BLEND);
+		gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, DE_NULL);
+		gl.enable(GL_BLEND);
+
+		if (m_coherentBlending)
+		{
+			gl.drawElements(GL_TRIANGLES, 6*(numQuads-1), GL_UNSIGNED_SHORT, (const void*)(deUintptr)(6*sizeof(deUint16)));
+		}
+		else
+		{
+			for (int quadNdx = 1; quadNdx < numQuads; quadNdx++)
+			{
+				gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void*)(deUintptr)(quadNdx*6*sizeof(deUint16)));
+				gl.blendBarrierKHR();
+			}
+		}
+
+		gl.flush();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Render failed");
+	}
+
+	// Render reference.
+	{
+		rr::FragmentOperationState		referenceState;
+		const tcu::PixelBufferAccess	colorAccess		= gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess());
+		const tcu::PixelBufferAccess	nullAccess		(TextureFormat(), 0, 0, 0, DE_NULL);
+		IntegerQuad						quad;
+
+		if (!useFbo && m_context.getRenderTarget().getPixelFormat().alphaBits == 0)
+		{
+			// Emulate lack of alpha by clearing to 1 and masking out alpha writes
+			tcu::clear(*m_refColorBuffer, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+			referenceState.colorMask = tcu::BVec4(true, true, true, false);
+		}
+
+		referenceState.blendEquationAdvaced	= sglr::rr_util::mapGLBlendEquationAdvanced(m_blendMode);
+
+		quad.posA = tcu::IVec2(0, 0);
+		quad.posB = tcu::IVec2(m_viewportWidth-1, m_viewportHeight-1);
+
+		for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
+		{
+			referenceState.blendMode = quadNdx == 0 ? rr::BLENDMODE_NONE : rr::BLENDMODE_ADVANCED;
+			std::copy(&colors[4*quadNdx], &colors[4*quadNdx] + 4, &quad.color[0]);
+			m_referenceRenderer->render(colorAccess, nullAccess /* no depth */, nullAccess /* no stencil */, quad, referenceState);
+		}
+	}
+
+	if (requiresResolve)
+	{
+		gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolveFbo);
+		gl.blitFramebuffer(0, 0, m_renderWidth, m_renderHeight, 0, 0, m_renderWidth, m_renderHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Resolve blit failed");
+
+		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_resolveFbo);
+	}
+
+	glu::readPixels(renderCtx, viewportX, viewportY, renderedImg.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
+
+	if (requiresResolve)
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+
+	{
+		const bool	isHSLMode	= m_blendMode == GL_HSL_HUE_KHR			||
+								  m_blendMode == GL_HSL_SATURATION_KHR	||
+								  m_blendMode == GL_HSL_COLOR_KHR		||
+								  m_blendMode == GL_HSL_LUMINOSITY_KHR;
+		bool		comparePass	= false;
+
+		if (isHSLMode)
+		{
+			// Compensate for more demanding HSL code by using fuzzy comparison.
+			const float threshold = 0.002f;
+			comparePass = tcu::fuzzyCompare(m_testCtx.getLog(), "CompareResult", "Image Comparison Result",
+											getLinearAccess(m_refColorBuffer->getAccess()),
+											renderedImg.getAccess(),
+											threshold, tcu::COMPARE_LOG_RESULT);
+		}
+		else
+		{
+		const UVec4 compareThreshold = (useFbo ? tcu::PixelFormat(8, 8, 8, 8) : m_context.getRenderTarget().getPixelFormat()).getColorThreshold().toIVec().asUint()
+									 * UVec4(5) / UVec4(2) + UVec4(3 * m_overdrawCount);
+
+			comparePass = tcu::bilinearCompare(m_testCtx.getLog(), "CompareResult", "Image Comparison Result",
+											  getLinearAccess(m_refColorBuffer->getAccess()),
+											  renderedImg.getAccess(),
+											  tcu::RGBA(compareThreshold[0], compareThreshold[1], compareThreshold[2], compareThreshold[3]),
+											  tcu::COMPARE_LOG_RESULT);
+		}
+
+		if (!comparePass)
+		{
+			m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+			return STOP;
+		}
+	}
+
+	m_iterNdx += 1;
+
+	if (m_iterNdx < m_numIters)
+		return CONTINUE;
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+}
+
+} // anonymous
+
+AdvancedBlendTests::AdvancedBlendTests (Context& context)
+	: TestCaseGroup(context, "blend_equation_advanced", "GL_KHR_blend_equation_advanced Tests")
+{
+}
+
+AdvancedBlendTests::~AdvancedBlendTests (void)
+{
+}
+
+void AdvancedBlendTests::init (void)
+{
+	static const struct
+	{
+		deUint32	mode;
+		const char*	name;
+	} s_blendModes[] =
+	{
+		{ GL_MULTIPLY_KHR,			"multiply"			},
+		{ GL_SCREEN_KHR,			"screen"			},
+		{ GL_OVERLAY_KHR,			"overlay"			},
+		{ GL_DARKEN_KHR,			"darken"			},
+		{ GL_LIGHTEN_KHR,			"lighten"			},
+		{ GL_COLORDODGE_KHR,		"colordodge"		},
+		{ GL_COLORBURN_KHR,			"colorburn"			},
+		{ GL_HARDLIGHT_KHR,			"hardlight"			},
+		{ GL_SOFTLIGHT_KHR,			"softlight"			},
+		{ GL_DIFFERENCE_KHR,		"difference"		},
+		{ GL_EXCLUSION_KHR,			"exclusion"			},
+		{ GL_HSL_HUE_KHR,			"hsl_hue"			},
+		{ GL_HSL_SATURATION_KHR,	"hsl_saturation"	},
+		{ GL_HSL_COLOR_KHR,			"hsl_color"			},
+		{ GL_HSL_LUMINOSITY_KHR,	"hsl_luminosity"	}
+	};
+
+	tcu::TestCaseGroup* const	basicGroup			= new tcu::TestCaseGroup(m_testCtx, "basic",			"Single quad only");
+	tcu::TestCaseGroup* const	srgbGroup			= new tcu::TestCaseGroup(m_testCtx, "srgb",				"Advanced blending with sRGB FBO");
+	tcu::TestCaseGroup* const	msaaGroup			= new tcu::TestCaseGroup(m_testCtx, "msaa",				"Advanced blending with MSAA FBO");
+	tcu::TestCaseGroup* const	barrierGroup		= new tcu::TestCaseGroup(m_testCtx, "barrier",			"Multiple overlapping quads with blend barriers");
+	tcu::TestCaseGroup* const	coherentGroup		= new tcu::TestCaseGroup(m_testCtx, "coherent",			"Overlapping quads with coherent blending");
+	tcu::TestCaseGroup* const	coherentMsaaGroup	= new tcu::TestCaseGroup(m_testCtx, "coherent_msaa",	"Overlapping quads with coherent blending with MSAA FBO");
+
+	addChild(basicGroup);
+	addChild(srgbGroup);
+	addChild(msaaGroup);
+	addChild(barrierGroup);
+	addChild(coherentGroup);
+	addChild(coherentMsaaGroup);
+
+	for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(s_blendModes); modeNdx++)
+	{
+		const char* const		name		= s_blendModes[modeNdx].name;
+		const char* const		desc		= "";
+		const deUint32			mode		= s_blendModes[modeNdx].mode;
+
+		basicGroup->addChild		(new AdvancedBlendCase(m_context, name, desc, mode, 1, false,	RENDERTARGETTYPE_DEFAULT));
+		srgbGroup->addChild			(new AdvancedBlendCase(m_context, name, desc, mode, 1, false,	RENDERTARGETTYPE_SRGB_FBO));
+		msaaGroup->addChild			(new AdvancedBlendCase(m_context, name, desc, mode, 1, false,	RENDERTARGETTYPE_MSAA_FBO));
+		barrierGroup->addChild		(new AdvancedBlendCase(m_context, name, desc, mode, 4, false,	RENDERTARGETTYPE_DEFAULT));
+		coherentGroup->addChild		(new AdvancedBlendCase(m_context, name, desc, mode, 4, true,	RENDERTARGETTYPE_DEFAULT));
+		coherentMsaaGroup->addChild	(new AdvancedBlendCase(m_context, name, desc, mode, 4, true,	RENDERTARGETTYPE_MSAA_FBO));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fAdvancedBlendTests.hpp b/modules/gles31/functional/es31fAdvancedBlendTests.hpp
new file mode 100644
index 0000000..b6620ca
--- /dev/null
+++ b/modules/gles31/functional/es31fAdvancedBlendTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FADVANCEDBLENDTESTS_HPP
+#define _ES31FADVANCEDBLENDTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Advanced blending (GL_KHR_blend_equation_advanced) tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class AdvancedBlendTests : public TestCaseGroup
+{
+public:
+							AdvancedBlendTests		(Context& context);
+							~AdvancedBlendTests		(void);
+
+	void					init					(void);
+
+private:
+							AdvancedBlendTests		(const AdvancedBlendTests&);
+	AdvancedBlendTests&		operator=				(const AdvancedBlendTests&);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FADVANCEDBLENDTESTS_HPP
diff --git a/modules/gles31/functional/es31fAtomicCounterTests.cpp b/modules/gles31/functional/es31fAtomicCounterTests.cpp
new file mode 100644
index 0000000..878b2ae
--- /dev/null
+++ b/modules/gles31/functional/es31fAtomicCounterTests.cpp
@@ -0,0 +1,1546 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Basic Compute Shader Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fAtomicCounterTests.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluRenderContext.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "tcuTestLog.hpp"
+
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deMemory.h"
+
+#include <vector>
+#include <string>
+
+using namespace glw;
+using tcu::TestLog;
+
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+class AtomicCounterTest : public TestCase
+{
+public:
+	enum Operation
+	{
+		OPERATION_INC = (1<<0),
+		OPERATION_DEC = (1<<1),
+		OPERATION_GET = (1<<2)
+	};
+
+	enum OffsetType
+	{
+		OFFSETTYPE_NONE = 0,
+		OFFSETTYPE_BASIC,
+		OFFSETTYPE_REVERSE,
+		OFFSETTYPE_FIRST_AUTO,
+		OFFSETTYPE_DEFAULT_AUTO,
+		OFFSETTYPE_RESET_DEFAULT,
+		OFFSETTYPE_INVALID,
+		OFFSETTYPE_INVALID_OVERLAPPING,
+		OFFSETTYPE_INVALID_DEFAULT
+	};
+
+	enum BindingType
+	{
+		BINDINGTYPE_BASIC = 0,
+		BINDINGTYPE_INVALID,
+		BINDINGTYPE_INVALID_DEFAULT
+	};
+
+	struct TestSpec
+	{
+		TestSpec (void)
+			: atomicCounterCount	(0)
+			, operations			((Operation)0)
+			, callCount				(0)
+			, useBranches			(false)
+			, threadCount			(0)
+			, offsetType			(OFFSETTYPE_NONE)
+			, bindingType			(BINDINGTYPE_BASIC)
+		{
+		}
+
+		int			atomicCounterCount;
+		Operation	operations;
+		int			callCount;
+		bool		useBranches;
+		int			threadCount;
+		OffsetType	offsetType;
+		BindingType	bindingType;
+	};
+
+						AtomicCounterTest		(Context& context, const char* name, const char* description, const TestSpec& spec);
+						~AtomicCounterTest		(void);
+
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+private:
+	const TestSpec		m_spec;
+
+	bool				checkAndLogCounterValues	(TestLog& log, const vector<deUint32>& counters) const;
+	bool				checkAndLogCallValues		(TestLog& log, const vector<deUint32>& increments, const vector<deUint32>& decrements, const vector<deUint32>& preGets, const vector<deUint32>& postGets, const vector<deUint32>& gets) const;
+	void				splitBuffer					(const vector<deUint32>& buffer, vector<deUint32>& increments, vector<deUint32>& decrements, vector<deUint32>& preGets, vector<deUint32>& postGets, vector<deUint32>& gets) const;
+	deUint32			getInitialValue				(void) const { return m_spec.callCount * m_spec.threadCount + 1; }
+
+	static string		generateShaderSource		(const TestSpec& spec);
+	static void			getCountersValues			(vector<deUint32>& counterValues, const vector<deUint32>& values, int ndx, int counterCount);
+	static bool			checkRange					(TestLog& log, const vector<deUint32>& values, const vector<deUint32>& min, const vector<deUint32>& max);
+	static bool			checkUniquenessAndLinearity	(TestLog& log, const vector<deUint32>& values);
+	static bool			checkPath					(const vector<deUint32>& increments, const vector<deUint32>& decrements, int initialValue, const TestSpec& spec);
+
+	int					getOperationCount			(void) const;
+
+	AtomicCounterTest&	operator=					(const AtomicCounterTest&);
+						AtomicCounterTest			(const AtomicCounterTest&);
+};
+
+int AtomicCounterTest::getOperationCount (void) const
+{
+	int count = 0;
+
+	if (m_spec.operations & OPERATION_INC)
+		count++;
+
+	if (m_spec.operations & OPERATION_DEC)
+		count++;
+
+	if (m_spec.operations == OPERATION_GET)
+		count++;
+	else if (m_spec.operations & OPERATION_GET)
+		count += 2;
+
+	return count;
+}
+
+AtomicCounterTest::AtomicCounterTest (Context& context, const char* name, const char* description, const TestSpec& spec)
+	: TestCase	(context, name, description)
+	, m_spec	(spec)
+{
+}
+
+AtomicCounterTest::~AtomicCounterTest (void)
+{
+}
+
+void AtomicCounterTest::init (void)
+{
+}
+
+void AtomicCounterTest::deinit (void)
+{
+}
+
+string AtomicCounterTest::generateShaderSource (const TestSpec& spec)
+{
+	std::ostringstream src;
+
+	src
+		<<  "#version 310 es\n"
+		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n";
+
+	{
+		bool wroteLayout = false;
+
+		switch (spec.bindingType)
+		{
+			case BINDINGTYPE_INVALID_DEFAULT:
+				src << "layout(binding=10000";
+				wroteLayout = true;
+				break;
+
+			default:
+				// Do nothing
+				break;
+		}
+
+		switch (spec.offsetType)
+		{
+			case OFFSETTYPE_DEFAULT_AUTO:
+				if (!wroteLayout)
+					src << "layout(binding=1, ";
+				else
+					src << ", ";
+
+				src << "offset=4";
+				wroteLayout = true;
+				break;
+
+			case OFFSETTYPE_RESET_DEFAULT:
+				DE_ASSERT(spec.atomicCounterCount > 2);
+
+				if (!wroteLayout)
+					src << "layout(binding=1, ";
+				else
+					src << ", ";
+
+				src << "offset=" << (4 * spec.atomicCounterCount/2);
+				wroteLayout = true;
+				break;
+
+			case OFFSETTYPE_INVALID_DEFAULT:
+				if (!wroteLayout)
+					src << "layout(binding=1, ";
+				else
+					src << ", ";
+
+				src << "offset=1";
+				wroteLayout = true;
+				break;
+
+			default:
+				// Do nothing
+				break;
+		}
+
+		if (wroteLayout)
+			src << ") uniform atomic_uint;\n";
+	}
+
+	src
+	<< "layout(binding = 1, std430) buffer Output {\n";
+
+	if ((spec.operations & OPERATION_GET) != 0 && spec.operations != OPERATION_GET)
+		src << "	uint preGet[" << spec.threadCount * spec.atomicCounterCount * spec.callCount << "];\n";
+
+	if ((spec.operations & OPERATION_INC) != 0)
+		src << "	uint increment[" << spec.threadCount * spec.atomicCounterCount * spec.callCount << "];\n";
+
+	if ((spec.operations & OPERATION_DEC) != 0)
+		src << "	uint decrement[" << spec.threadCount * spec.atomicCounterCount * spec.callCount << "];\n";
+
+	if ((spec.operations & OPERATION_GET) != 0 && spec.operations != OPERATION_GET)
+		src << "	uint postGet[" << spec.threadCount * spec.atomicCounterCount * spec.callCount << "];\n";
+
+	if (spec.operations == OPERATION_GET)
+		src << "	uint get[" << spec.threadCount * spec.atomicCounterCount * spec.callCount << "];\n";
+
+	src << "} sb_in;\n\n";
+
+	for (int counterNdx = 0; counterNdx < spec.atomicCounterCount; counterNdx++)
+	{
+		bool layoutStarted = false;
+
+		if (spec.offsetType == OFFSETTYPE_RESET_DEFAULT && counterNdx == spec.atomicCounterCount/2)
+			src << "layout(binding=1, offset=0) uniform atomic_uint;\n";
+
+		switch (spec.bindingType)
+		{
+			case BINDINGTYPE_BASIC:
+				layoutStarted = true;
+				src << "layout(binding=1";
+				break;
+
+			case BINDINGTYPE_INVALID:
+				layoutStarted = true;
+				src << "layout(binding=10000";
+				break;
+
+			case BINDINGTYPE_INVALID_DEFAULT:
+				// Nothing
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		switch (spec.offsetType)
+		{
+			case OFFSETTYPE_NONE:
+				if (layoutStarted)
+					src << ") ";
+
+				src << "uniform atomic_uint counter" << counterNdx << ";\n";
+
+				break;
+
+			case OFFSETTYPE_BASIC:
+				if (!layoutStarted)
+					src << "layout(";
+				else
+					src << ", ";
+
+				src << "offset=" << (counterNdx * 4) << ") uniform atomic_uint counter" << counterNdx << ";\n";
+
+				break;
+
+			case OFFSETTYPE_INVALID_DEFAULT:
+				if (layoutStarted)
+					src << ") ";
+
+				src << "uniform atomic_uint counter" << counterNdx << ";\n";
+
+				break;
+
+			case OFFSETTYPE_INVALID:
+				if (!layoutStarted)
+					src << "layout(";
+				else
+					src << ", ";
+
+				src << "offset=" << (1 + counterNdx * 2) << ") uniform atomic_uint counter" << counterNdx << ";\n";
+
+				break;
+
+			case OFFSETTYPE_INVALID_OVERLAPPING:
+				if (!layoutStarted)
+					src << "layout(";
+				else
+					src << ", ";
+
+				src << "offset=0) uniform atomic_uint counter" << counterNdx << ";\n";
+
+				break;
+
+			case OFFSETTYPE_REVERSE:
+				if (!layoutStarted)
+					src << "layout(";
+				else
+					src << ", ";
+
+				src << "offset=" << (spec.atomicCounterCount - counterNdx - 1) * 4 << ") uniform atomic_uint counter" << (spec.atomicCounterCount - counterNdx - 1) << ";\n";
+
+				break;
+
+			case OFFSETTYPE_FIRST_AUTO:
+				DE_ASSERT(spec.atomicCounterCount > 2);
+
+				if (counterNdx + 1 == spec.atomicCounterCount)
+				{
+					if (!layoutStarted)
+						src << "layout(";
+					else
+						src << ", ";
+
+					src << "offset=0) uniform atomic_uint counter0;\n";
+				}
+				else if (counterNdx == 0)
+				{
+					if (!layoutStarted)
+						src << "layout(";
+					else
+						src << ", ";
+
+					src << "offset=4) uniform atomic_uint counter1;\n";
+				}
+				else
+				{
+					if (layoutStarted)
+						src << ") ";
+
+					src << "uniform atomic_uint counter" << (counterNdx + 1) << ";\n";
+				}
+
+				break;
+
+			case OFFSETTYPE_DEFAULT_AUTO:
+				if (counterNdx + 1 == spec.atomicCounterCount)
+				{
+					if (!layoutStarted)
+						src << "layout(";
+					else
+						src << ", ";
+
+					src << "offset=0) uniform atomic_uint counter0;\n";
+				}
+				else
+				{
+					if (layoutStarted)
+						src << ") ";
+
+					src << "uniform atomic_uint counter" << (counterNdx + 1) << ";\n";
+				}
+
+				break;
+
+			case OFFSETTYPE_RESET_DEFAULT:
+				if (layoutStarted)
+					src << ") ";
+
+				if (counterNdx < spec.atomicCounterCount/2)
+					src << "uniform atomic_uint counter" << (counterNdx + spec.atomicCounterCount/2) << ";\n";
+				else
+					src << "uniform atomic_uint counter" << (counterNdx - spec.atomicCounterCount/2) << ";\n";
+
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	src
+	<< "\n"
+	<< "void main (void)\n"
+	<< "{\n";
+
+	if (spec.callCount > 1)
+		src << "\tfor (uint i = 0u; i < " << spec.callCount << "u; i++)\n";
+
+	src
+	<< "\t{\n"
+	<< "\t\tuint id = (gl_GlobalInvocationID.x";
+
+	if (spec.callCount > 1)
+		src << " * "<< spec.callCount << "u";
+
+	if (spec.callCount > 1)
+		src << " + i)";
+	else
+		src << ")";
+
+	if  (spec.atomicCounterCount > 1)
+		src << " * " << spec.atomicCounterCount << "u";
+
+	src << ";\n";
+
+	for (int counterNdx = 0; counterNdx < spec.atomicCounterCount; counterNdx++)
+	{
+		if ((spec.operations & OPERATION_GET) != 0 && spec.operations != OPERATION_GET)
+			src << "\t\tsb_in.preGet[id + " << counterNdx << "u] = atomicCounter(counter" << counterNdx << ");\n";
+
+		if (spec.useBranches && ((spec.operations & (OPERATION_INC|OPERATION_DEC)) == (OPERATION_INC|OPERATION_DEC)))
+		{
+			src
+			<< "\t\tif (((gl_GlobalInvocationID.x" << (spec.callCount > 1 ? " + i" : "") << ") % 2u) == 0u)\n"
+			<< "\t\t{\n"
+			<< "\t\t\tsb_in.increment[id + " << counterNdx << "u] = atomicCounterIncrement(counter" << counterNdx << ");\n"
+			<< "\t\t\tsb_in.decrement[id + " << counterNdx << "u] = uint(-1);\n"
+			<< "\t\t}\n"
+			<< "\t\telse\n"
+			<< "\t\t{\n"
+			<< "\t\t\tsb_in.decrement[id + " << counterNdx << "u] = atomicCounterDecrement(counter" << counterNdx << ") + 1u;\n"
+			<< "\t\t\tsb_in.increment[id + " << counterNdx << "u] = uint(-1);\n"
+			<< "\t\t}\n";
+		}
+		else
+		{
+			if ((spec.operations & OPERATION_INC) != 0)
+			{
+				if (spec.useBranches)
+				{
+					src
+					<< "\t\tif (((gl_GlobalInvocationID.x" << (spec.callCount > 1 ? " + i" : "") << ") % 2u) == 0u)\n"
+					<< "\t\t{\n"
+					<< "\t\t\tsb_in.increment[id + " << counterNdx << "u] = atomicCounterIncrement(counter" << counterNdx << ");\n"
+					<< "\t\t}\n"
+					<< "\t\telse\n"
+					<< "\t\t{\n"
+					<< "\t\t\tsb_in.increment[id + " << counterNdx << "u] = uint(-1);\n"
+					<< "\t\t}\n";
+
+				}
+				else
+					src << "\t\tsb_in.increment[id + " << counterNdx << "u] = atomicCounterIncrement(counter" << counterNdx << ");\n";
+			}
+
+			if ((spec.operations & OPERATION_DEC) != 0)
+			{
+				if (spec.useBranches)
+				{
+					src
+					<< "\t\tif (((gl_GlobalInvocationID.x" << (spec.callCount > 1 ? " + i" : "") << ") % 2u) == 0u)\n"
+					<< "\t\t{\n"
+					<< "\t\t\tsb_in.decrement[id + " << counterNdx << "u] = atomicCounterDecrement(counter" << counterNdx << ") + 1u;\n"
+					<< "\t\t}\n"
+					<< "\t\telse\n"
+					<< "\t\t{\n"
+					<< "\t\t\tsb_in.decrement[id + " << counterNdx << "u] = uint(-1);\n"
+					<< "\t\t}\n";
+
+				}
+				else
+					src << "\t\tsb_in.decrement[id + " << counterNdx << "u] = atomicCounterDecrement(counter" << counterNdx << ") + 1u;\n";
+			}
+		}
+
+		if ((spec.operations & OPERATION_GET) != 0 && spec.operations != OPERATION_GET)
+			src << "\t\tsb_in.postGet[id + " << counterNdx << "u] = atomicCounter(counter" << counterNdx << ");\n";
+
+		if ((spec.operations == OPERATION_GET) != 0)
+		{
+			if (spec.useBranches)
+			{
+				src
+				<< "\t\tif (((gl_GlobalInvocationID.x" << (spec.callCount > 1 ? " + i" : "") << ") % 2u) == 0u)\n"
+				<< "\t\t{\n"
+				<< "\t\t\tsb_in.get[id + " << counterNdx << "u] = atomicCounter(counter" << counterNdx << ");\n"
+				<< "\t\t}\n"
+				<< "\t\telse\n"
+				<< "\t\t{\n"
+				<< "\t\t\tsb_in.get[id + " << counterNdx << "u] = uint(-1);\n"
+				<< "\t\t}\n";
+			}
+			else
+				src << "\t\tsb_in.get[id + " << counterNdx << "u] = atomicCounter(counter" << counterNdx << ");\n";
+		}
+	}
+
+	src
+	<< "\t}\n"
+	<< "}\n";
+
+	return src.str();
+}
+
+bool AtomicCounterTest::checkAndLogCounterValues (TestLog& log, const vector<deUint32>& counters) const
+{
+	tcu::ScopedLogSection	counterSection	(log, "Counter info", "Show initial value, current value and expected value of each counter.");
+	bool					isOk			= true;
+
+	// Check that atomic counters have sensible results
+	for (int counterNdx = 0; counterNdx < (int)counters.size(); counterNdx++)
+	{
+		const deUint32	value			= counters[counterNdx];
+		const deUint32	initialValue	= getInitialValue();
+		deUint32		expectedValue	= (deUint32)-1;
+
+		if ((m_spec.operations & OPERATION_INC) != 0 && (m_spec.operations & OPERATION_DEC) == 0)
+			expectedValue = initialValue + (m_spec.useBranches ? m_spec.threadCount*m_spec.callCount - m_spec.threadCount*m_spec.callCount/2 : m_spec.threadCount*m_spec.callCount);
+
+		if ((m_spec.operations & OPERATION_INC) == 0 && (m_spec.operations & OPERATION_DEC) != 0)
+			expectedValue = initialValue - (m_spec.useBranches ? m_spec.threadCount*m_spec.callCount - m_spec.threadCount*m_spec.callCount/2 : m_spec.threadCount*m_spec.callCount);
+
+		if ((m_spec.operations & OPERATION_INC) != 0 && (m_spec.operations & OPERATION_DEC) != 0)
+			expectedValue = initialValue + (m_spec.useBranches ? m_spec.threadCount*m_spec.callCount - m_spec.threadCount*m_spec.callCount/2 : 0) - (m_spec.useBranches ? m_spec.threadCount*m_spec.callCount/2 : 0);
+
+		if ((m_spec.operations & OPERATION_INC) == 0 && (m_spec.operations & OPERATION_DEC) == 0)
+			expectedValue = initialValue;
+
+		log << TestLog::Message << "atomic_uint counter" << counterNdx << " initial value: " << initialValue << ", value: " << value << ", expected: " << expectedValue << (value == expectedValue ? "" : ", failed!") << TestLog::EndMessage;
+
+		if (value != expectedValue)
+			isOk = false;
+	}
+
+	return isOk;
+}
+
+void AtomicCounterTest::splitBuffer (const vector<deUint32>& buffer, vector<deUint32>& increments, vector<deUint32>& decrements, vector<deUint32>& preGets, vector<deUint32>& postGets, vector<deUint32>& gets) const
+{
+	const int bufferValueCount	= m_spec.callCount * m_spec.threadCount * m_spec.atomicCounterCount;
+
+	int firstPreGet				= -1;
+	int firstPostGet			= -1;
+	int	firstGet				= -1;
+	int firstInc				= -1;
+	int firstDec				= -1;
+
+	increments.clear();
+	decrements.clear();
+	preGets.clear();
+	postGets.clear();
+	gets.clear();
+
+	if (m_spec.operations == OPERATION_GET)
+		firstGet = 0;
+	else if (m_spec.operations == OPERATION_INC)
+		firstInc = 0;
+	else if (m_spec.operations == OPERATION_DEC)
+		firstDec = 0;
+	else if (m_spec.operations == (OPERATION_GET|OPERATION_INC))
+	{
+		firstPreGet		= 0;
+		firstInc		= bufferValueCount;
+		firstPostGet	= bufferValueCount * 2;
+	}
+	else if (m_spec.operations == (OPERATION_GET|OPERATION_DEC))
+	{
+		firstPreGet		= 0;
+		firstDec		= bufferValueCount;
+		firstPostGet	= bufferValueCount * 2;
+	}
+	else if (m_spec.operations == (OPERATION_GET|OPERATION_DEC|OPERATION_INC))
+	{
+		firstPreGet		= 0;
+		firstInc		= bufferValueCount;
+		firstDec		= bufferValueCount * 2;
+		firstPostGet	= bufferValueCount * 3;
+	}
+	else if (m_spec.operations == (OPERATION_DEC|OPERATION_INC))
+	{
+		firstInc		= 0;
+		firstDec		= bufferValueCount;
+	}
+	else
+		DE_ASSERT(false);
+
+	for (int threadNdx = 0; threadNdx < m_spec.threadCount; threadNdx++)
+	{
+		for (int callNdx = 0; callNdx < m_spec.callCount; callNdx++)
+		{
+			for (int counterNdx = 0; counterNdx < m_spec.atomicCounterCount; counterNdx++)
+			{
+				const int id = ((threadNdx * m_spec.callCount) + callNdx) * m_spec.atomicCounterCount + counterNdx;
+
+				if (firstInc != -1)
+					increments.push_back(buffer[firstInc + id]);
+
+				if (firstDec != -1)
+					decrements.push_back(buffer[firstDec + id]);
+
+				if (firstPreGet != -1)
+					preGets.push_back(buffer[firstPreGet + id]);
+
+				if (firstPostGet != -1)
+					postGets.push_back(buffer[firstPostGet + id]);
+
+				if (firstGet != -1)
+					gets.push_back(buffer[firstGet + id]);
+			}
+		}
+	}
+}
+
+void AtomicCounterTest::getCountersValues (vector<deUint32>& counterValues, const vector<deUint32>& values, int ndx, int counterCount)
+{
+	counterValues.resize(values.size()/counterCount, 0);
+
+	DE_ASSERT(values.size() % counterCount == 0);
+
+	for (int valueNdx = 0; valueNdx < (int)counterValues.size(); valueNdx++)
+		counterValues[valueNdx] = values[valueNdx * counterCount + ndx];
+}
+
+bool AtomicCounterTest::checkRange (TestLog& log, const vector<deUint32>& values, const vector<deUint32>& min, const vector<deUint32>& max)
+{
+	int failedCount = 0;
+
+	DE_ASSERT(values.size() == min.size());
+	DE_ASSERT(values.size() == max.size());
+
+	for (int valueNdx = 0; valueNdx < (int)values.size(); valueNdx++)
+	{
+		if (values[valueNdx] != (deUint32)-1)
+		{
+			if (!deInRange32(values[valueNdx], min[valueNdx], max[valueNdx]))
+			{
+				if (failedCount < 20)
+					log << TestLog::Message << "Value " << values[valueNdx] << " not in range [" << min[valueNdx] << ", " << max[valueNdx] << "]." << TestLog::EndMessage;
+				failedCount++;
+			}
+		}
+	}
+
+	if (failedCount > 20)
+		log << TestLog::Message << "Number of values not in range: " << failedCount << ", displaying first 20 values." << TestLog::EndMessage;
+
+	return failedCount == 0;
+}
+
+bool AtomicCounterTest::checkUniquenessAndLinearity (TestLog& log, const vector<deUint32>& values)
+{
+	vector<deUint32>	counts;
+	int					failedCount	= 0;
+	deUint32			minValue	= (deUint32)-1;
+	deUint32			maxValue	= 0;
+
+	DE_ASSERT(!values.empty());
+
+	for (int valueNdx = 0; valueNdx < (int)values.size(); valueNdx++)
+	{
+		if (values[valueNdx] != (deUint32)-1)
+		{
+			minValue = std::min(minValue, values[valueNdx]);
+			maxValue = std::max(maxValue, values[valueNdx]);
+		}
+	}
+
+	counts.resize(maxValue - minValue + 1, 0);
+
+	for (int valueNdx = 0; valueNdx < (int)values.size(); valueNdx++)
+	{
+		if (values[valueNdx] != (deUint32)-1)
+			counts[values[valueNdx] - minValue]++;
+	}
+
+	for (int countNdx = 0; countNdx < (int)counts.size(); countNdx++)
+	{
+		if (counts[countNdx] != 1)
+		{
+			if (failedCount < 20)
+				log << TestLog::Message << "Value " << (minValue + countNdx) << " is not unique. Returned " << counts[countNdx] << " times." << TestLog::EndMessage;
+
+			failedCount++;
+		}
+	}
+
+	if (failedCount > 20)
+		log << TestLog::Message << "Number of values not unique: " << failedCount << ", displaying first 20 values." << TestLog::EndMessage;
+
+	return failedCount == 0;
+}
+
+bool AtomicCounterTest::checkPath (const vector<deUint32>& increments, const vector<deUint32>& decrements, int initialValue, const TestSpec& spec)
+{
+	const deUint32		lastValue	= initialValue + (spec.useBranches ? spec.threadCount*spec.callCount - spec.threadCount*spec.callCount/2 : 0) - (spec.useBranches ? spec.threadCount*spec.callCount/2 : 0);
+	bool				isOk		= true;
+
+	vector<deUint32>	incrementCounts;
+	vector<deUint32>	decrementCounts;
+
+	deUint32			minValue = 0xFFFFFFFFu;
+	deUint32			maxValue = 0;
+
+	for (int valueNdx = 0; valueNdx < (int)increments.size(); valueNdx++)
+	{
+		if (increments[valueNdx] != (deUint32)-1)
+		{
+			minValue = std::min(minValue, increments[valueNdx]);
+			maxValue = std::max(maxValue, increments[valueNdx]);
+		}
+	}
+
+	for (int valueNdx = 0; valueNdx < (int)decrements.size(); valueNdx++)
+	{
+		if (decrements[valueNdx] != (deUint32)-1)
+		{
+			minValue = std::min(minValue, decrements[valueNdx]);
+			maxValue = std::max(maxValue, decrements[valueNdx]);
+		}
+	}
+
+	minValue = std::min(minValue, (deUint32)initialValue);
+	maxValue = std::max(maxValue, (deUint32)initialValue);
+
+	incrementCounts.resize(maxValue - minValue + 1, 0);
+	decrementCounts.resize(maxValue - minValue + 1, 0);
+
+	for (int valueNdx = 0; valueNdx < (int)increments.size(); valueNdx++)
+	{
+		if (increments[valueNdx] != (deUint32)-1)
+			incrementCounts[increments[valueNdx] - minValue]++;
+	}
+
+	for (int valueNdx = 0; valueNdx < (int)decrements.size(); valueNdx++)
+	{
+		if (decrements[valueNdx] != (deUint32)-1)
+			decrementCounts[decrements[valueNdx] - minValue]++;
+	}
+
+	int pos = initialValue - minValue;
+
+	while (incrementCounts[pos] + decrementCounts[pos] != 0)
+	{
+		if (incrementCounts[pos] > 0 && pos >= (int)(lastValue - minValue))
+		{
+			// If can increment and incrementation would move us away from result value, increment
+			incrementCounts[pos]--;
+			pos++;
+		}
+		else if (decrementCounts[pos] > 0)
+		{
+			// If can, decrement
+			decrementCounts[pos]--;
+			pos--;
+		}
+		else if (incrementCounts[pos] > 0)
+		{
+			// If increment moves closer to result value and can't decrement, increment
+			incrementCounts[pos]--;
+			pos++;
+		}
+		else
+			DE_ASSERT(false);
+
+		if (pos < 0 || pos >= (int)incrementCounts.size())
+			break;
+	}
+
+	if (minValue + pos != lastValue)
+		isOk = false;
+
+	for (int valueNdx = 0; valueNdx < (int)incrementCounts.size(); valueNdx++)
+	{
+		if (incrementCounts[valueNdx] != 0)
+			isOk = false;
+	}
+
+	for (int valueNdx = 0; valueNdx < (int)decrementCounts.size(); valueNdx++)
+	{
+		if (decrementCounts[valueNdx] != 0)
+			isOk = false;
+	}
+
+	return isOk;
+}
+
+bool AtomicCounterTest::checkAndLogCallValues (TestLog& log, const vector<deUint32>& increments, const vector<deUint32>& decrements, const vector<deUint32>& preGets, const vector<deUint32>& postGets, const vector<deUint32>& gets) const
+{
+	bool isOk = true;
+
+	for (int counterNdx = 0; counterNdx < m_spec.atomicCounterCount; counterNdx++)
+	{
+		vector<deUint32> counterIncrements;
+		vector<deUint32> counterDecrements;
+		vector<deUint32> counterPreGets;
+		vector<deUint32> counterPostGets;
+		vector<deUint32> counterGets;
+
+		getCountersValues(counterIncrements,	increments,	counterNdx, m_spec.atomicCounterCount);
+		getCountersValues(counterDecrements,	decrements,	counterNdx, m_spec.atomicCounterCount);
+		getCountersValues(counterPreGets,		preGets,	counterNdx, m_spec.atomicCounterCount);
+		getCountersValues(counterPostGets,		postGets,	counterNdx, m_spec.atomicCounterCount);
+		getCountersValues(counterGets,			gets,		counterNdx, m_spec.atomicCounterCount);
+
+		if (m_spec.operations == OPERATION_GET)
+		{
+			tcu::ScopedLogSection valueCheck(log, ("counter" + de::toString(counterNdx) + " value check").c_str(), ("Check that counter" + de::toString(counterNdx) + " values haven't changed.").c_str());
+			int changedValues = 0;
+
+			for (int valueNdx = 0; valueNdx < (int)gets.size(); valueNdx++)
+			{
+				if ((!m_spec.useBranches || gets[valueNdx] != (deUint32)-1) && gets[valueNdx] != getInitialValue())
+				{
+					if (changedValues < 20)
+						log << TestLog::Message << "atomicCounter(counter" << counterNdx << ") returned " << gets[valueNdx] << " expected " << getInitialValue() << TestLog::EndMessage;
+					isOk = false;
+					changedValues++;
+				}
+			}
+
+			if (changedValues == 0)
+				log << TestLog::Message << "All values returned by atomicCounter(counter" << counterNdx << ") match initial value " << getInitialValue() <<  "." << TestLog::EndMessage;
+			else if (changedValues > 20)
+				log << TestLog::Message << "Total number of invalid values returned by atomicCounter(counter" << counterNdx << ") " << changedValues << " displaying first 20 values." <<  TestLog::EndMessage;
+		}
+		else if ((m_spec.operations & (OPERATION_INC|OPERATION_DEC)) == (OPERATION_INC|OPERATION_DEC))
+		{
+			tcu::ScopedLogSection valueCheck(log, ("counter" + de::toString(counterNdx) + " path check").c_str(), ("Check that there is order in which counter" + de::toString(counterNdx) + " increments and decrements could have happened.").c_str());
+			if (!checkPath(counterIncrements, counterDecrements, getInitialValue(), m_spec))
+			{
+				isOk = false;
+				log << TestLog::Message << "No possible order of calls to atomicCounterIncrement(counter" << counterNdx << ") and atomicCounterDecrement(counter" << counterNdx << ") found." << TestLog::EndMessage;
+			}
+			else
+				log << TestLog::Message << "Found possible order of calls to atomicCounterIncrement(counter" << counterNdx << ") and atomicCounterDecrement(counter" << counterNdx << ")." << TestLog::EndMessage;
+		}
+		else if ((m_spec.operations & OPERATION_INC) != 0)
+		{
+			{
+				tcu::ScopedLogSection uniquenesCheck(log, ("counter" + de::toString(counterNdx) + " check uniqueness and linearity").c_str(), ("Check that counter" + de::toString(counterNdx) + " returned only unique and linear values.").c_str());
+
+				if (!checkUniquenessAndLinearity(log, counterIncrements))
+				{
+					isOk = false;
+					log << TestLog::Message << "atomicCounterIncrement(counter" << counterNdx << ") returned non unique values." << TestLog::EndMessage;
+				}
+				else
+					log << TestLog::Message << "atomicCounterIncrement(counter" << counterNdx << ") returned only unique values." << TestLog::EndMessage;
+			}
+
+			if (isOk && ((m_spec.operations & OPERATION_GET) != 0))
+			{
+				tcu::ScopedLogSection uniquenesCheck(log, ("counter" + de::toString(counterNdx) + " check range").c_str(), ("Check that counter" + de::toString(counterNdx) + " returned only values values between previous and next atomicCounter(counter" + de::toString(counterNdx) + ").").c_str());
+
+				if (!checkRange(log, counterIncrements, counterPreGets, counterPostGets))
+				{
+					isOk = false;
+					log << TestLog::Message << "atomicCounterIncrement(counter" << counterNdx << ") returned value that is not between previous and next call to atomicCounter(counter" << counterNdx << ")." << TestLog::EndMessage;
+				}
+				else
+					log << TestLog::Message << "atomicCounterIncrement(counter" << counterNdx << ") returned only values between previous and next call to atomicCounter(counter" << counterNdx << ")." << TestLog::EndMessage;
+			}
+		}
+		else if ((m_spec.operations & OPERATION_DEC) != 0)
+		{
+			{
+				tcu::ScopedLogSection uniquenesCheck(log, ("counter" + de::toString(counterNdx) + " check uniqueness and linearity").c_str(), ("Check that counter" + de::toString(counterNdx) + " returned only unique and linear values.").c_str());
+
+				if (!checkUniquenessAndLinearity(log, counterDecrements))
+				{
+					isOk = false;
+					log << TestLog::Message << "atomicCounterDecrement(counter" << counterNdx << ") returned non unique values." << TestLog::EndMessage;
+				}
+				else
+					log << TestLog::Message << "atomicCounterDecrement(counter" << counterNdx << ") returned only unique values." << TestLog::EndMessage;
+			}
+
+			if (isOk && ((m_spec.operations & OPERATION_GET) != 0))
+			{
+				tcu::ScopedLogSection uniquenesCheck(log, ("counter" + de::toString(counterNdx) + " check range").c_str(), ("Check that counter" + de::toString(counterNdx) + " returned only values values between previous and next atomicCounter(counter" + de::toString(counterNdx) + ".").c_str());
+
+				if (!checkRange(log, counterDecrements, counterPostGets, counterPreGets))
+				{
+					isOk = false;
+					log << TestLog::Message << "atomicCounterDecrement(counter" << counterNdx << ") returned value that is not between previous and next call to atomicCounter(counter" << counterNdx << ")." << TestLog::EndMessage;
+				}
+				else
+					log << TestLog::Message << "atomicCounterDecrement(counter" << counterNdx << ") returned only values between previous and next call to atomicCounter(counter" << counterNdx << ")." << TestLog::EndMessage;
+			}
+		}
+	}
+
+	return isOk;
+}
+
+TestCase::IterateResult AtomicCounterTest::iterate (void)
+{
+	const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
+	TestLog&					log					= m_testCtx.getLog();
+	const glu::Buffer			counterBuffer		(m_context.getRenderContext());
+	const glu::Buffer			outputBuffer		(m_context.getRenderContext());
+	const glu::ShaderProgram	program				(m_context.getRenderContext(), glu::ProgramSources() << glu::ShaderSource(glu::SHADERTYPE_COMPUTE, generateShaderSource(m_spec)));
+
+	const deInt32				counterBufferSize	= m_spec.atomicCounterCount * 4;
+	const deInt32				ssoSize				= m_spec.atomicCounterCount * m_spec.callCount * m_spec.threadCount * 4 * getOperationCount();
+
+	log << program;
+
+	if (m_spec.offsetType == OFFSETTYPE_INVALID || m_spec.offsetType == OFFSETTYPE_INVALID_DEFAULT || m_spec.bindingType == BINDINGTYPE_INVALID || m_spec.bindingType == BINDINGTYPE_INVALID_DEFAULT || m_spec.offsetType == OFFSETTYPE_INVALID_OVERLAPPING)
+	{
+		if (program.isOk())
+		{
+			log << TestLog::Message << "Expected program to fail, but compilation passed." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile succeeded");
+			return STOP;
+		}
+		else
+		{
+			log << TestLog::Message << "Compilation failed as expected." << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Compile failed");
+			return STOP;
+		}
+	}
+	else if (!program.isOk())
+	{
+		log << TestLog::Message << "Compile failed." << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		return STOP;
+	}
+
+	gl.useProgram(program.getProgram());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+	// Create output buffer
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, ssoSize, NULL, GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create output buffer");
+
+	// Create atomic counter buffer
+	{
+		vector<deUint32> data(m_spec.atomicCounterCount, getInitialValue());
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *counterBuffer);
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, counterBufferSize, &(data[0]), GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create buffer for atomic counters");
+	}
+
+	// Bind output buffer
+	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *outputBuffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup output buffer");
+
+	// Bind atomic counter buffer
+	gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 1, *counterBuffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup atomic counter buffer");
+
+	// Dispath compute
+	gl.dispatchCompute(m_spec.threadCount, 1, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+	gl.finish();
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish()");
+
+	vector<deUint32> output(ssoSize/4, 0);
+	vector<deUint32> counters(m_spec.atomicCounterCount, 0);
+
+	// Read back output buffer
+	{
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+
+		void* ptr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, (GLsizeiptr)(output.size() * sizeof(deUint32)), GL_MAP_READ_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+
+		deMemcpy(&(output[0]), ptr, (int)output.size() * sizeof(deUint32));
+
+		if (!gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER))
+		{
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
+			TCU_CHECK_MSG(false, "Mapped buffer corrupted");
+		}
+
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+	}
+
+	// Read back counter buffer
+	{
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *counterBuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+
+		void* ptr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, (GLsizeiptr)(counters.size() * sizeof(deUint32)), GL_MAP_READ_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+
+		deMemcpy(&(counters[0]), ptr, (int)counters.size() * sizeof(deUint32));
+
+		if (!gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER))
+		{
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
+			TCU_CHECK_MSG(false, "Mapped buffer corrupted");
+		}
+
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+	}
+
+	bool isOk = true;
+
+	if (!checkAndLogCounterValues(log, counters))
+		isOk = false;
+
+	{
+		vector<deUint32> increments;
+		vector<deUint32> decrements;
+		vector<deUint32> preGets;
+		vector<deUint32> postGets;
+		vector<deUint32> gets;
+
+		splitBuffer(output, increments, decrements, preGets, postGets, gets);
+
+		if (!checkAndLogCallValues(log, increments, decrements, preGets, postGets, gets))
+			isOk = false;
+	}
+
+	if (isOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+
+	return STOP;
+}
+
+string specToTestName (const AtomicCounterTest::TestSpec& spec)
+{
+	std::ostringstream stream;
+
+	stream << spec.atomicCounterCount	<< (spec.atomicCounterCount == 1 ? "_counter" : "_counters");
+	stream << "_" << spec.callCount		<< (spec.callCount == 1 ? "_call" : "_calls");
+	stream << "_" << spec.threadCount	<< (spec.threadCount == 1 ? "_thread" : "_threads");
+
+	return stream.str();
+}
+
+string specToTestDescription (const AtomicCounterTest::TestSpec& spec)
+{
+	std::ostringstream	stream;
+	bool				firstOperation = 0;
+
+	stream
+	<< "Test ";
+
+	if ((spec.operations & AtomicCounterTest::OPERATION_GET) != 0)
+	{
+		stream << "atomicCounter()";
+		firstOperation = false;
+	}
+
+	if ((spec.operations & AtomicCounterTest::OPERATION_INC) != 0)
+	{
+		if (!firstOperation)
+			stream << ", ";
+
+		stream << " atomicCounterIncrement()";
+		firstOperation = false;
+	}
+
+	if ((spec.operations & AtomicCounterTest::OPERATION_DEC) != 0)
+	{
+		if (!firstOperation)
+			stream << ", ";
+
+		stream << " atomicCounterDecrement()";
+		firstOperation = false;
+	}
+
+	stream << " calls with ";
+
+	if (spec.useBranches)
+		stream << " branches, ";
+
+	stream << spec.atomicCounterCount << " atomic counters, " << spec.callCount << " calls and " << spec.threadCount << " threads.";
+
+	return stream.str();
+}
+
+string operationToName (const AtomicCounterTest::Operation& operations, bool useBranch)
+{
+	std::ostringstream	stream;
+	bool				first = true;
+
+	if ((operations & AtomicCounterTest::OPERATION_GET) != 0)
+	{
+		stream << "get";
+		first = false;
+	}
+
+	if ((operations & AtomicCounterTest::OPERATION_INC) != 0)
+	{
+		if (!first)
+			stream << "_";
+
+		stream << "inc";
+		first = false;
+	}
+
+	if ((operations & AtomicCounterTest::OPERATION_DEC) != 0)
+	{
+		if (!first)
+			stream << "_";
+
+		stream << "dec";
+		first = false;
+	}
+
+	if (useBranch)
+		stream << "_branch";
+
+	return stream.str();
+}
+
+string operationToDescription (const AtomicCounterTest::Operation& operations, bool useBranch)
+{
+	std::ostringstream	stream;
+	bool				firstOperation = 0;
+
+	stream
+	<< "Test ";
+
+	if ((operations & AtomicCounterTest::OPERATION_GET) != 0)
+	{
+		stream << "atomicCounter()";
+		firstOperation = false;
+	}
+
+	if ((operations & AtomicCounterTest::OPERATION_INC) != 0)
+	{
+		if (!firstOperation)
+			stream << ", ";
+
+		stream << " atomicCounterIncrement()";
+		firstOperation = false;
+	}
+
+	if ((operations & AtomicCounterTest::OPERATION_DEC) != 0)
+	{
+		if (!firstOperation)
+			stream << ", ";
+
+		stream << " atomicCounterDecrement()";
+		firstOperation = false;
+	}
+
+
+	if (useBranch)
+		stream << " calls with branches.";
+	else
+		stream << ".";
+
+	return stream.str();
+}
+
+string layoutTypesToName (const AtomicCounterTest::BindingType& bindingType, const AtomicCounterTest::OffsetType& offsetType)
+{
+	std::ostringstream	stream;
+
+	switch (bindingType)
+	{
+		case AtomicCounterTest::BINDINGTYPE_BASIC:
+			// Nothing
+			break;
+
+		case AtomicCounterTest::BINDINGTYPE_INVALID:
+			stream << "invalid_binding";
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	if (bindingType != AtomicCounterTest::BINDINGTYPE_BASIC && offsetType != AtomicCounterTest::OFFSETTYPE_NONE)
+		stream << "_";
+
+	switch (offsetType)
+	{
+		case AtomicCounterTest::OFFSETTYPE_BASIC:
+			stream << "basic_offset";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_REVERSE:
+			stream << "reverse_offset";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_INVALID:
+			stream << "invalid_offset";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_FIRST_AUTO:
+			stream << "first_offset_set";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_DEFAULT_AUTO:
+			stream << "default_offset_set";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_RESET_DEFAULT:
+			stream << "reset_default_offset";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_NONE:
+			// Do nothing
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	return stream.str();
+}
+
+string layoutTypesToDesc (const AtomicCounterTest::BindingType& bindingType, const AtomicCounterTest::OffsetType& offsetType)
+{
+	std::ostringstream	stream;
+
+	switch (bindingType)
+	{
+		case AtomicCounterTest::BINDINGTYPE_BASIC:
+			stream << "Test using atomic counters with explicit layout bindings and";
+			break;
+
+		case AtomicCounterTest::BINDINGTYPE_INVALID:
+			stream << "Test using atomic counters with invalid explicit layout bindings and";
+			break;
+
+		case AtomicCounterTest::BINDINGTYPE_INVALID_DEFAULT:
+			stream << "Test using atomic counters with invalid default layout binding and";
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	switch (offsetType)
+	{
+		case AtomicCounterTest::OFFSETTYPE_NONE:
+			stream << " no explicit offsets.";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_BASIC:
+			stream << "explicit continuos offsets.";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_REVERSE:
+			stream << "reversed explicit offsets.";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_INVALID:
+			stream << "invalid explicit offsets.";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_FIRST_AUTO:
+			stream << "only first counter with explicit offset.";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_DEFAULT_AUTO:
+			stream << "default offset.";
+			break;
+
+		case AtomicCounterTest::OFFSETTYPE_RESET_DEFAULT:
+			stream << "default offset specified twice.";
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	return stream.str();
+}
+
+} // Anonymous
+
+AtomicCounterTests::AtomicCounterTests (Context& context)
+	: TestCaseGroup(context, "atomic_counter", "Atomic counter tests")
+{
+	// Runtime use tests
+	{
+		const int counterCounts[] =
+		{
+			1, 4, 8
+		};
+
+		const int callCounts[] =
+		{
+			1, 5, 100
+		};
+
+		const int threadCounts[] =
+		{
+			1, 10, 5000
+		};
+
+		const AtomicCounterTest::Operation operations[] =
+		{
+			AtomicCounterTest::OPERATION_GET,
+			AtomicCounterTest::OPERATION_INC,
+			AtomicCounterTest::OPERATION_DEC,
+
+			(AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_INC|AtomicCounterTest::OPERATION_GET),
+			(AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_DEC|AtomicCounterTest::OPERATION_GET),
+
+			(AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_INC|AtomicCounterTest::OPERATION_DEC),
+			(AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_INC|AtomicCounterTest::OPERATION_DEC|AtomicCounterTest::OPERATION_GET)
+		};
+
+		for (int operationNdx = 0; operationNdx < DE_LENGTH_OF_ARRAY(operations); operationNdx++)
+		{
+			const AtomicCounterTest::Operation operation = operations[operationNdx];
+
+			for (int branch = 0; branch < 2; branch++)
+			{
+				const bool useBranch = (branch == 1);
+
+				TestCaseGroup* operationGroup = new TestCaseGroup(m_context, operationToName(operation, useBranch).c_str(), operationToDescription(operation, useBranch).c_str());
+
+				for (int counterCountNdx = 0; counterCountNdx < DE_LENGTH_OF_ARRAY(counterCounts); counterCountNdx++)
+				{
+					const int counterCount = counterCounts[counterCountNdx];
+
+					for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); callCountNdx++)
+					{
+						const int callCount = callCounts[callCountNdx];
+
+						for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+						{
+							const int threadCount = threadCounts[threadCountNdx];
+
+							if (threadCount * callCount * counterCount > 10000)
+								continue;
+
+							if (useBranch && threadCount * callCount == 1)
+								continue;
+
+							AtomicCounterTest::TestSpec spec;
+
+							spec.atomicCounterCount = counterCount;
+							spec.operations			= operation;
+							spec.callCount			= callCount;
+							spec.useBranches		= useBranch;
+							spec.threadCount		= threadCount;
+							spec.bindingType		= AtomicCounterTest::BINDINGTYPE_BASIC;
+							spec.offsetType			= AtomicCounterTest::OFFSETTYPE_NONE;
+
+							operationGroup->addChild(new AtomicCounterTest(m_context, specToTestName(spec).c_str(), specToTestDescription(spec).c_str(), spec));
+						}
+					}
+				}
+
+				addChild(operationGroup);
+			}
+		}
+	}
+
+	{
+		TestCaseGroup* layoutGroup = new TestCaseGroup(m_context, "layout", "Layout qualifier tests.");
+
+		const int counterCounts[]	= { 1, 8 };
+		const int callCounts[]		= { 1, 5 };
+		const int threadCounts[]	= { 1, 1000 };
+
+		const AtomicCounterTest::Operation operations[] =
+		{
+			(AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_INC|AtomicCounterTest::OPERATION_GET),
+			(AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_DEC|AtomicCounterTest::OPERATION_GET),
+			(AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_INC|AtomicCounterTest::OPERATION_DEC)
+		};
+
+		const AtomicCounterTest::OffsetType offsetTypes[] =
+		{
+			AtomicCounterTest::OFFSETTYPE_REVERSE,
+			AtomicCounterTest::OFFSETTYPE_FIRST_AUTO,
+			AtomicCounterTest::OFFSETTYPE_DEFAULT_AUTO,
+			AtomicCounterTest::OFFSETTYPE_RESET_DEFAULT
+		};
+
+		for (int offsetTypeNdx = 0; offsetTypeNdx < DE_LENGTH_OF_ARRAY(offsetTypes); offsetTypeNdx++)
+		{
+			const AtomicCounterTest::OffsetType offsetType = offsetTypes[offsetTypeNdx];
+
+			TestCaseGroup* layoutQualifierGroup = new TestCaseGroup(m_context, layoutTypesToName(AtomicCounterTest::BINDINGTYPE_BASIC, offsetType).c_str(), layoutTypesToDesc(AtomicCounterTest::BINDINGTYPE_BASIC, offsetType).c_str());
+
+			for (int operationNdx = 0; operationNdx < DE_LENGTH_OF_ARRAY(operations); operationNdx++)
+			{
+				const AtomicCounterTest::Operation operation = operations[operationNdx];
+
+				TestCaseGroup* operationGroup = new TestCaseGroup(m_context, operationToName(operation, false).c_str(), operationToDescription(operation, false).c_str());
+
+				for (int counterCountNdx = 0; counterCountNdx < DE_LENGTH_OF_ARRAY(counterCounts); counterCountNdx++)
+				{
+					const int counterCount = counterCounts[counterCountNdx];
+
+					if (offsetType == AtomicCounterTest::OFFSETTYPE_FIRST_AUTO && counterCount < 3)
+						continue;
+
+					if (offsetType == AtomicCounterTest::OFFSETTYPE_DEFAULT_AUTO && counterCount < 2)
+						continue;
+
+					if (offsetType == AtomicCounterTest::OFFSETTYPE_RESET_DEFAULT && counterCount < 2)
+						continue;
+
+					if (offsetType == AtomicCounterTest::OFFSETTYPE_REVERSE && counterCount < 2)
+						continue;
+
+					for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); callCountNdx++)
+					{
+						const int callCount = callCounts[callCountNdx];
+
+						for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts); threadCountNdx++)
+						{
+							const int threadCount = threadCounts[threadCountNdx];
+
+							AtomicCounterTest::TestSpec spec;
+
+							spec.atomicCounterCount = counterCount;
+							spec.operations			= operation;
+							spec.callCount			= callCount;
+							spec.useBranches		= false;
+							spec.threadCount		= threadCount;
+							spec.bindingType		= AtomicCounterTest::BINDINGTYPE_BASIC;
+							spec.offsetType			= offsetType;
+
+							operationGroup->addChild(new AtomicCounterTest(m_context, specToTestName(spec).c_str(), specToTestDescription(spec).c_str(), spec));
+						}
+					}
+				}
+				layoutQualifierGroup->addChild(operationGroup);
+			}
+			layoutGroup->addChild(layoutQualifierGroup);
+		}
+
+		{
+			TestCaseGroup* invalidGroup = new TestCaseGroup(m_context, "invalid", "Test invalid layouts");
+
+			{
+				AtomicCounterTest::TestSpec spec;
+
+				spec.atomicCounterCount = 1;
+				spec.operations			= AtomicCounterTest::OPERATION_INC;
+				spec.callCount			= 1;
+				spec.useBranches		= false;
+				spec.threadCount		= 1;
+				spec.bindingType		= AtomicCounterTest::BINDINGTYPE_INVALID;
+				spec.offsetType			= AtomicCounterTest::OFFSETTYPE_NONE;
+
+				invalidGroup->addChild(new AtomicCounterTest(m_context, "invalid_binding", "Test layout qualifiers with invalid binding.", spec));
+			}
+
+			{
+				AtomicCounterTest::TestSpec spec;
+
+				spec.atomicCounterCount = 1;
+				spec.operations			= AtomicCounterTest::OPERATION_INC;
+				spec.callCount			= 1;
+				spec.useBranches		= false;
+				spec.threadCount		= 1;
+				spec.bindingType		= AtomicCounterTest::BINDINGTYPE_INVALID_DEFAULT;
+				spec.offsetType			= AtomicCounterTest::OFFSETTYPE_NONE;
+
+				invalidGroup->addChild(new AtomicCounterTest(m_context, "invalid_default_binding", "Test layout qualifiers with invalid default binding.", spec));
+			}
+
+			{
+				AtomicCounterTest::TestSpec spec;
+
+				spec.atomicCounterCount = 1;
+				spec.operations			= AtomicCounterTest::OPERATION_INC;
+				spec.callCount			= 1;
+				spec.useBranches		= false;
+				spec.threadCount		= 1;
+				spec.bindingType		= AtomicCounterTest::BINDINGTYPE_BASIC;
+				spec.offsetType			= AtomicCounterTest::OFFSETTYPE_INVALID;
+
+				invalidGroup->addChild(new AtomicCounterTest(m_context, "invalid_offset_align", "Test layout qualifiers with invalid alignment offset.", spec));
+			}
+
+			{
+				AtomicCounterTest::TestSpec spec;
+
+				spec.atomicCounterCount = 2;
+				spec.operations			= AtomicCounterTest::OPERATION_INC;
+				spec.callCount			= 1;
+				spec.useBranches		= false;
+				spec.threadCount		= 1;
+				spec.bindingType		= AtomicCounterTest::BINDINGTYPE_BASIC;
+				spec.offsetType			= AtomicCounterTest::OFFSETTYPE_INVALID_OVERLAPPING;
+
+				invalidGroup->addChild(new AtomicCounterTest(m_context, "invalid_offset_overlap", "Test layout qualifiers with invalid overlapping offset.", spec));
+			}
+
+			{
+				AtomicCounterTest::TestSpec spec;
+
+				spec.atomicCounterCount = 1;
+				spec.operations			= AtomicCounterTest::OPERATION_INC;
+				spec.callCount			= 1;
+				spec.useBranches		= false;
+				spec.threadCount		= 1;
+				spec.bindingType		= AtomicCounterTest::BINDINGTYPE_BASIC;
+				spec.offsetType			= AtomicCounterTest::OFFSETTYPE_INVALID_DEFAULT;
+
+				invalidGroup->addChild(new AtomicCounterTest(m_context, "invalid_default_offset", "Test layout qualifiers with invalid default offset.", spec));
+			}
+
+			layoutGroup->addChild(invalidGroup);
+		}
+
+		addChild(layoutGroup);
+	}
+}
+
+AtomicCounterTests::~AtomicCounterTests (void)
+{
+}
+
+void AtomicCounterTests::init (void)
+{
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fAtomicCounterTests.hpp b/modules/gles31/functional/es31fAtomicCounterTests.hpp
new file mode 100644
index 0000000..a8ec4d1
--- /dev/null
+++ b/modules/gles31/functional/es31fAtomicCounterTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FATOMICCOUNTERTESTS_HPP
+#define _ES31FATOMICCOUNTERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Basic atomic counter test
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class AtomicCounterTests : public TestCaseGroup
+{
+public:
+						AtomicCounterTests	(Context& context);
+						~AtomicCounterTests	(void);
+
+	void				init				(void);
+
+private:
+						AtomicCounterTests	(const AtomicCounterTests& other);
+	AtomicCounterTests&	operator=			(const AtomicCounterTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FATOMICCOUNTERTESTS_HPP
diff --git a/modules/gles31/functional/es31fBasicComputeShaderTests.cpp b/modules/gles31/functional/es31fBasicComputeShaderTests.cpp
new file mode 100644
index 0000000..1357751
--- /dev/null
+++ b/modules/gles31/functional/es31fBasicComputeShaderTests.cpp
@@ -0,0 +1,1634 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Basic Compute Shader Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fBasicComputeShaderTests.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluRenderContext.hpp"
+#include "gluProgramInterfaceQuery.hpp"
+#include "gluContextInfo.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "tcuTestLog.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMemory.h"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+using namespace glu;
+
+//! Utility for mapping buffers.
+class BufferMemMap
+{
+public:
+	BufferMemMap (const glw::Functions& gl, deUint32 target, int offset, int size, deUint32 access)
+		: m_gl		(gl)
+		, m_target	(target)
+		, m_ptr		(DE_NULL)
+	{
+		m_ptr = gl.mapBufferRange(target, offset, size, access);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+		TCU_CHECK(m_ptr);
+	}
+
+	~BufferMemMap (void)
+	{
+		m_gl.unmapBuffer(m_target);
+	}
+
+	void*	getPtr		(void) const { return m_ptr; }
+	void*	operator*	(void) const { return m_ptr; }
+
+private:
+							BufferMemMap			(const BufferMemMap& other);
+	BufferMemMap&			operator=				(const BufferMemMap& other);
+
+	const glw::Functions&	m_gl;
+	const deUint32			m_target;
+	void*					m_ptr;
+};
+
+namespace
+{
+
+class EmptyComputeShaderCase : public TestCase
+{
+public:
+	EmptyComputeShaderCase (Context& context)
+		: TestCase(context, "empty", "Empty shader")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const ShaderProgram program(m_context.getRenderContext(),
+			ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE,
+				"#version 310 es\n"
+				"layout (local_size_x = 1) in;\n"
+				"void main (void) {}\n"
+				));
+
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		gl.useProgram(program.getProgram());
+		gl.dispatchCompute(1, 1, 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+class UBOToSSBOInvertCase : public TestCase
+{
+public:
+	UBOToSSBOInvertCase (Context& context, const char* name, const char* description, int numValues, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
+		: TestCase		(context, name, description)
+		, m_numValues	(numValues)
+		, m_localSize	(localSize)
+		, m_workSize	(workSize)
+	{
+		DE_ASSERT(m_numValues % (m_workSize[0]*m_workSize[1]*m_workSize[2]*m_localSize[0]*m_localSize[1]*m_localSize[2]) == 0);
+	}
+
+	IterateResult iterate (void)
+	{
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
+			<< "uniform Input {\n"
+			<< "    uint values[" << m_numValues << "];\n"
+			<< "} ub_in;\n"
+			<< "layout(binding = 1) buffer Output {\n"
+			<< "    uint values[" << m_numValues << "];\n"
+			<< "} sb_out;\n"
+			<< "void main (void) {\n"
+			<< "    uvec3 size           = gl_NumWorkGroups * gl_WorkGroupSize;\n"
+			<< "    uint numValuesPerInv = uint(ub_in.values.length()) / (size.x*size.y*size.z);\n"
+			<< "    uint groupNdx        = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
+			<< "    uint offset          = numValuesPerInv*groupNdx;\n"
+			<< "\n"
+			<< "    for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
+			<< "        sb_out.values[offset + ndx] = ~ub_in.values[offset + ndx];\n"
+			<< "}\n";
+
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
+		const Buffer				inputBuffer		(m_context.getRenderContext());
+		const Buffer				outputBuffer	(m_context.getRenderContext());
+		std::vector<deUint32>		inputValues		(m_numValues);
+
+		// Compute input values.
+		{
+			de::Random rnd(0x111223f);
+			for (int ndx = 0; ndx < (int)inputValues.size(); ndx++)
+				inputValues[ndx] = rnd.getUint32();
+		}
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Input buffer setup
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM_BLOCK, "Input");
+			const InterfaceBlockInfo	blockInfo	= getProgramInterfaceBlockInfo(gl, program.getProgram(), GL_UNIFORM_BLOCK, blockIndex);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM, "Input.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_UNIFORM, valueIndex);
+
+			gl.bindBuffer(GL_UNIFORM_BUFFER, *inputBuffer);
+			gl.bufferData(GL_UNIFORM_BUFFER, (glw::GLsizeiptr)blockInfo.dataSize, DE_NULL, GL_STATIC_DRAW);
+
+			{
+				const BufferMemMap bufMap(gl, GL_UNIFORM_BUFFER, 0, (int)blockInfo.dataSize, GL_MAP_WRITE_BIT);
+
+				for (deUint32 ndx = 0; ndx < de::min(valueInfo.arraySize, (deUint32)inputValues.size()); ndx++)
+					*(deUint32*)((deUint8*)bufMap.getPtr() + valueInfo.offset + ndx*valueInfo.arrayStride) = inputValues[ndx];
+			}
+
+			gl.uniformBlockBinding(program.getProgram(), blockIndex, 0);
+			gl.bindBufferBase(GL_UNIFORM_BUFFER, 0, *inputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Input buffer setup failed");
+		}
+
+		// Output buffer setup
+		{
+			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+
+			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());
+			for (deUint32 ndx = 0; ndx < valueInfo.arraySize; ndx++)
+			{
+				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx));
+				const deUint32	ref		= ~inputValues[ndx];
+
+				if (res != ref)
+					throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(ndx) + "]");
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const int			m_numValues;
+	const tcu::IVec3	m_localSize;
+	const tcu::IVec3	m_workSize;
+};
+
+class CopyInvertSSBOCase : public TestCase
+{
+public:
+	CopyInvertSSBOCase (Context& context, const char* name, const char* description, int numValues, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
+		: TestCase		(context, name, description)
+		, m_numValues	(numValues)
+		, m_localSize	(localSize)
+		, m_workSize	(workSize)
+	{
+		DE_ASSERT(m_numValues % (m_workSize[0]*m_workSize[1]*m_workSize[2]*m_localSize[0]*m_localSize[1]*m_localSize[2]) == 0);
+	}
+
+	IterateResult iterate (void)
+	{
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
+			<< "layout(binding = 0) buffer Input {\n"
+			<< "    uint values[" << m_numValues << "];\n"
+			<< "} sb_in;\n"
+			<< "layout (binding = 1) buffer Output {\n"
+			<< "    uint values[" << m_numValues << "];\n"
+			<< "} sb_out;\n"
+			<< "void main (void) {\n"
+			<< "    uvec3 size           = gl_NumWorkGroups * gl_WorkGroupSize;\n"
+			<< "    uint numValuesPerInv = uint(sb_in.values.length()) / (size.x*size.y*size.z);\n"
+			<< "    uint groupNdx        = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
+			<< "    uint offset          = numValuesPerInv*groupNdx;\n"
+			<< "\n"
+			<< "    for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
+			<< "        sb_out.values[offset + ndx] = ~sb_in.values[offset + ndx];\n"
+			<< "}\n";
+
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
+		const Buffer				inputBuffer		(m_context.getRenderContext());
+		const Buffer				outputBuffer	(m_context.getRenderContext());
+		std::vector<deUint32>		inputValues		(m_numValues);
+
+		// Compute input values.
+		{
+			de::Random rnd(0x124fef);
+			for (int ndx = 0; ndx < (int)inputValues.size(); ndx++)
+				inputValues[ndx] = rnd.getUint32();
+		}
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Input buffer setup
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Input");
+			const InterfaceBlockInfo	blockInfo	= getProgramInterfaceBlockInfo(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Input.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *inputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, (glw::GLsizeiptr)blockInfo.dataSize, DE_NULL, GL_STATIC_DRAW);
+
+			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());
+
+			{
+				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, (int)blockInfo.dataSize, GL_MAP_WRITE_BIT);
+
+				for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
+					*(deUint32*)((deUint8*)bufMap.getPtr() + valueInfo.offset + ndx*valueInfo.arrayStride) = inputValues[ndx];
+			}
+
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, blockInfo.bufferBinding, *inputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Input buffer setup failed");
+		}
+
+		// Output buffer setup
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const InterfaceBlockInfo	blockInfo	= getProgramInterfaceBlockInfo(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockInfo.dataSize, DE_NULL, GL_STREAM_READ);
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, blockInfo.bufferBinding, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+
+			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());
+			for (deUint32 ndx = 0; ndx < valueInfo.arraySize; ndx++)
+			{
+				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx));
+				const deUint32	ref		= ~inputValues[ndx];
+
+				if (res != ref)
+					throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(ndx) + "]");
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const int			m_numValues;
+	const tcu::IVec3	m_localSize;
+	const tcu::IVec3	m_workSize;
+};
+
+class InvertSSBOInPlaceCase : public TestCase
+{
+public:
+	InvertSSBOInPlaceCase (Context& context, const char* name, const char* description, int numValues, bool isSized, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
+		: TestCase		(context, name, description)
+		, m_numValues	(numValues)
+		, m_isSized		(isSized)
+		, m_localSize	(localSize)
+		, m_workSize	(workSize)
+	{
+		DE_ASSERT(m_numValues % (m_workSize[0]*m_workSize[1]*m_workSize[2]*m_localSize[0]*m_localSize[1]*m_localSize[2]) == 0);
+	}
+
+	IterateResult iterate (void)
+	{
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
+			<< "layout(binding = 0) buffer InOut {\n"
+			<< "    uint values[" << (m_isSized ? de::toString(m_numValues) : string("")) << "];\n"
+			<< "} sb_inout;\n"
+			<< "void main (void) {\n"
+			<< "    uvec3 size           = gl_NumWorkGroups * gl_WorkGroupSize;\n"
+			<< "    uint numValuesPerInv = uint(sb_inout.values.length()) / (size.x*size.y*size.z);\n"
+			<< "    uint groupNdx        = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
+			<< "    uint offset          = numValuesPerInv*groupNdx;\n"
+			<< "\n"
+			<< "    for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
+			<< "        sb_inout.values[offset + ndx] = ~sb_inout.values[offset + ndx];\n"
+			<< "}\n";
+
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		const Buffer				outputBuffer	(m_context.getRenderContext());
+		const deUint32				valueIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "InOut.values");
+		const InterfaceVariableInfo	valueInfo		= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+		const deUint32				blockSize		= valueInfo.arrayStride*(deUint32)m_numValues;
+		std::vector<deUint32>		inputValues		(m_numValues);
+
+		// Compute input values.
+		{
+			de::Random rnd(0x82ce7f);
+			for (int ndx = 0; ndx < (int)inputValues.size(); ndx++)
+				inputValues[ndx] = rnd.getUint32();
+		}
+
+		TCU_CHECK(valueInfo.arraySize == (deUint32)(m_isSized ? m_numValues : 0));
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Output buffer setup
+		{
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_DRAW);
+
+			{
+				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, (int)blockSize, GL_MAP_WRITE_BIT);
+
+				for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
+					*(deUint32*)((deUint8*)bufMap.getPtr() + valueInfo.offset + ndx*valueInfo.arrayStride) = inputValues[ndx];
+			}
+
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare
+		{
+			const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+
+			for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
+			{
+				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx));
+				const deUint32	ref		= ~inputValues[ndx];
+
+				if (res != ref)
+					throw tcu::TestError(string("Comparison failed for InOut.values[") + de::toString(ndx) + "]");
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const int			m_numValues;
+	const bool			m_isSized;
+	const tcu::IVec3	m_localSize;
+	const tcu::IVec3	m_workSize;
+};
+
+class WriteToMultipleSSBOCase : public TestCase
+{
+public:
+	WriteToMultipleSSBOCase (Context& context, const char* name, const char* description, int numValues, bool isSized, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
+		: TestCase		(context, name, description)
+		, m_numValues	(numValues)
+		, m_isSized		(isSized)
+		, m_localSize	(localSize)
+		, m_workSize	(workSize)
+	{
+		DE_ASSERT(m_numValues % (m_workSize[0]*m_workSize[1]*m_workSize[2]*m_localSize[0]*m_localSize[1]*m_localSize[2]) == 0);
+	}
+
+	IterateResult iterate (void)
+	{
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
+			<< "layout(binding = 0) buffer Out0 {\n"
+			<< "    uint values[" << (m_isSized ? de::toString(m_numValues) : string("")) << "];\n"
+			<< "} sb_out0;\n"
+			<< "layout(binding = 1) buffer Out1 {\n"
+			<< "    uint values[" << (m_isSized ? de::toString(m_numValues) : string("")) << "];\n"
+			<< "} sb_out1;\n"
+			<< "void main (void) {\n"
+			<< "    uvec3 size      = gl_NumWorkGroups * gl_WorkGroupSize;\n"
+			<< "    uint groupNdx   = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
+			<< "\n"
+			<< "    {\n"
+			<< "        uint numValuesPerInv = uint(sb_out0.values.length()) / (size.x*size.y*size.z);\n"
+			<< "        uint offset          = numValuesPerInv*groupNdx;\n"
+			<< "\n"
+			<< "        for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
+			<< "            sb_out0.values[offset + ndx] = offset + ndx;\n"
+			<< "    }\n"
+			<< "    {\n"
+			<< "        uint numValuesPerInv = uint(sb_out1.values.length()) / (size.x*size.y*size.z);\n"
+			<< "        uint offset          = numValuesPerInv*groupNdx;\n"
+			<< "\n"
+			<< "        for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
+			<< "            sb_out1.values[offset + ndx] = uint(sb_out1.values.length()) - offset - ndx;\n"
+			<< "    }\n"
+			<< "}\n";
+
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		const Buffer				outputBuffer0	(m_context.getRenderContext());
+		const deUint32				value0Index		= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Out0.values");
+		const InterfaceVariableInfo	value0Info		= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, value0Index);
+		const deUint32				block0Size		= value0Info.arrayStride*(deUint32)m_numValues;
+
+		const Buffer				outputBuffer1	(m_context.getRenderContext());
+		const deUint32				value1Index		= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Out1.values");
+		const InterfaceVariableInfo	value1Info		= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, value1Index);
+		const deUint32				block1Size		= value1Info.arrayStride*(deUint32)m_numValues;
+
+		TCU_CHECK(value0Info.arraySize == (deUint32)(m_isSized ? m_numValues : 0));
+		TCU_CHECK(value1Info.arraySize == (deUint32)(m_isSized ? m_numValues : 0));
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Output buffer setup
+		{
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer0);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, block0Size, DE_NULL, GL_STREAM_DRAW);
+
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer setup failed");
+		}
+		{
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer1);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, block1Size, DE_NULL, GL_STREAM_DRAW);
+
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *outputBuffer1);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer0);
+		{
+			const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, block0Size, GL_MAP_READ_BIT);
+
+			for (deUint32 ndx = 0; ndx < (deUint32)m_numValues; ndx++)
+			{
+				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + value0Info.offset + value0Info.arrayStride*ndx));
+				const deUint32	ref		= ndx;
+
+				if (res != ref)
+					throw tcu::TestError(string("Comparison failed for Out0.values[") + de::toString(ndx) + "] res=" + de::toString(res) + " ref=" + de::toString(ref));
+			}
+		}
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer1);
+		{
+			const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, block1Size, GL_MAP_READ_BIT);
+
+			for (deUint32 ndx = 0; ndx < (deUint32)m_numValues; ndx++)
+			{
+				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + value1Info.offset + value1Info.arrayStride*ndx));
+				const deUint32	ref		= m_numValues - ndx;
+
+				if (res != ref)
+					throw tcu::TestError(string("Comparison failed for Out1.values[") + de::toString(ndx) + "] res=" + de::toString(res) + " ref=" + de::toString(ref));
+			}
+		}
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const int			m_numValues;
+	const bool			m_isSized;
+	const tcu::IVec3	m_localSize;
+	const tcu::IVec3	m_workSize;
+};
+
+class SSBOLocalBarrierCase : public TestCase
+{
+public:
+	SSBOLocalBarrierCase (Context& context, const char* name, const char* description, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
+		: TestCase		(context, name, description)
+		, m_localSize	(localSize)
+		, m_workSize	(workSize)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const Buffer				outputBuffer	(m_context.getRenderContext());
+		const int					workGroupSize	= m_localSize[0]*m_localSize[1]*m_localSize[2];
+		const int					workGroupCount	= m_workSize[0]*m_workSize[1]*m_workSize[2];
+		const int					numValues		= workGroupSize*workGroupCount;
+
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
+			<< "layout(binding = 0) buffer Output {\n"
+			<< "    coherent uint values[" << numValues << "];\n"
+			<< "} sb_out;\n\n"
+			<< "shared uint offsets[" << workGroupSize << "];\n\n"
+			<< "void main (void) {\n"
+			<< "    uint localSize  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
+			<< "    uint globalNdx  = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+			<< "    uint globalOffs = localSize*globalNdx;\n"
+			<< "    uint localOffs  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_LocalInvocationID.z + gl_WorkGroupSize.x*gl_LocalInvocationID.y + gl_LocalInvocationID.x;\n"
+			<< "\n"
+			<< "    sb_out.values[globalOffs + localOffs] = globalOffs;\n"
+			<< "    memoryBarrierBuffer();\n"
+			<< "    sb_out.values[globalOffs + ((localOffs+1u)%localSize)] += localOffs;\n"
+			<< "    memoryBarrierBuffer();\n"
+			<< "    sb_out.values[globalOffs + ((localOffs+2u)%localSize)] += localOffs;\n"
+			<< "}\n";
+
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ComputeSource(src.str()));
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Output buffer setup
+		{
+			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+
+			for (int groupNdx = 0; groupNdx < workGroupCount; groupNdx++)
+			{
+				for (int localOffs = 0; localOffs < workGroupSize; localOffs++)
+				{
+					const int		globalOffs	= groupNdx*workGroupSize;
+					const deUint32	res			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*(globalOffs + localOffs)));
+					const int		offs0		= localOffs-1 < 0 ? ((localOffs+workGroupSize-1)%workGroupSize) : ((localOffs-1)%workGroupSize);
+					const int		offs1		= localOffs-2 < 0 ? ((localOffs+workGroupSize-2)%workGroupSize) : ((localOffs-2)%workGroupSize);
+					const deUint32	ref			= (deUint32)(globalOffs + offs0 + offs1);
+
+					if (res != ref)
+						throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(globalOffs + localOffs) + "]");
+				}
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const tcu::IVec3	m_localSize;
+	const tcu::IVec3	m_workSize;
+};
+
+class SSBOBarrierCase : public TestCase
+{
+public:
+	SSBOBarrierCase (Context& context, const char* name, const char* description, const tcu::IVec3& workSize)
+		: TestCase		(context, name, description)
+		, m_workSize	(workSize)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const ShaderProgram program0(m_context.getRenderContext(), ProgramSources() <<
+			ComputeSource("#version 310 es\n"
+						  "layout (local_size_x = 1) in;\n"
+						  "uniform uint u_baseVal;\n"
+						  "layout(binding = 1) buffer Output {\n"
+						  "    uint values[];\n"
+						  "};\n"
+						  "void main (void) {\n"
+						  "    uint offset = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+						  "    values[offset] = u_baseVal+offset;\n"
+						  "}\n"));
+		const ShaderProgram program1(m_context.getRenderContext(), ProgramSources() <<
+			ComputeSource("#version 310 es\n"
+						  "layout (local_size_x = 1) in;\n"
+						  "uniform uint u_baseVal;\n"
+						  "layout(binding = 1) buffer Input {\n"
+						  "    uint values[];\n"
+						  "};\n"
+						  "layout(binding = 0) buffer Output {\n"
+						  "    coherent uint sum;\n"
+						  "};\n"
+						  "void main (void) {\n"
+						  "    uint offset = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+						  "    uint value  = values[offset];\n"
+						  "    atomicAdd(sum, value);\n"
+						  "}\n"));
+
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const Buffer				tempBuffer		(m_context.getRenderContext());
+		const Buffer				outputBuffer	(m_context.getRenderContext());
+		const deUint32				baseValue		= 127;
+
+		m_testCtx.getLog() << program0 << program1;
+		if (!program0.isOk() || !program1.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;
+
+		// Temp buffer setup
+		{
+			const deUint32				valueIndex		= gl.getProgramResourceIndex(program0.getProgram(), GL_BUFFER_VARIABLE, "values[0]");
+			const InterfaceVariableInfo	valueInfo		= getProgramInterfaceVariableInfo(gl, program0.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+			const deUint32				bufferSize		= valueInfo.arrayStride*m_workSize[0]*m_workSize[1]*m_workSize[2];
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *tempBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, (glw::GLsizeiptr)bufferSize, DE_NULL, GL_STATIC_DRAW);
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *tempBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Temp buffer setup failed");
+		}
+
+		// Output buffer setup
+		{
+			const deUint32		blockIndex		= gl.getProgramResourceIndex(program1.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int			blockSize		= getProgramResourceInt(gl, program1.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
+
+			{
+				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_WRITE_BIT);
+				deMemset(bufMap.getPtr(), 0, blockSize);
+			}
+
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.useProgram(program0.getProgram());
+		gl.uniform1ui(gl.getUniformLocation(program0.getProgram(), "u_baseVal"), baseValue);
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
+		gl.memoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
+		gl.useProgram(program1.getProgram());
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to dispatch commands");
+
+		// Read back and compare
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program1.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int					blockSize	= getProgramResourceInt(gl, program1.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program1.getProgram(), GL_BUFFER_VARIABLE, "sum");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program1.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+
+			const deUint32				res			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset));
+			deUint32					ref			= 0;
+
+			for (int ndx = 0; ndx < m_workSize[0]*m_workSize[1]*m_workSize[2]; ndx++)
+				ref += baseValue + (deUint32)ndx;
+
+			if (res != ref)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed, expected " << ref << ", got " << res << TestLog::EndMessage;
+				throw tcu::TestError("Comparison failed");
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const tcu::IVec3	m_workSize;
+};
+
+class BasicSharedVarCase : public TestCase
+{
+public:
+	BasicSharedVarCase (Context& context, const char* name, const char* description, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
+		: TestCase		(context, name, description)
+		, m_localSize	(localSize)
+		, m_workSize	(workSize)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const Buffer				outputBuffer	(m_context.getRenderContext());
+		const int					workGroupSize	= m_localSize[0]*m_localSize[1]*m_localSize[2];
+		const int					workGroupCount	= m_workSize[0]*m_workSize[1]*m_workSize[2];
+		const int					numValues		= workGroupSize*workGroupCount;
+
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
+			<< "layout(binding = 0) buffer Output {\n"
+			<< "    uint values[" << numValues << "];\n"
+			<< "} sb_out;\n\n"
+			<< "shared uint offsets[" << workGroupSize << "];\n\n"
+			<< "void main (void) {\n"
+			<< "    uint localSize  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
+			<< "    uint globalNdx  = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+			<< "    uint globalOffs = localSize*globalNdx;\n"
+			<< "    uint localOffs  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_LocalInvocationID.z + gl_WorkGroupSize.x*gl_LocalInvocationID.y + gl_LocalInvocationID.x;\n"
+			<< "\n"
+			<< "    offsets[localSize-localOffs-1u] = globalOffs + localOffs*localOffs;\n"
+			<< "    memoryBarrierShared();\n"
+			<< "    sb_out.values[globalOffs + localOffs] = offsets[localOffs];\n"
+			<< "}\n";
+
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Output buffer setup
+		{
+			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+
+			for (int groupNdx = 0; groupNdx < workGroupCount; groupNdx++)
+			{
+				for (int localOffs = 0; localOffs < workGroupSize; localOffs++)
+				{
+					const int		globalOffs	= groupNdx*workGroupSize;
+					const deUint32	res			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*(globalOffs + localOffs)));
+					const deUint32	ref			= (deUint32)(globalOffs + (workGroupSize-localOffs-1)*(workGroupSize-localOffs-1));
+
+					if (res != ref)
+						throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(globalOffs + localOffs) + "]");
+				}
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const tcu::IVec3	m_localSize;
+	const tcu::IVec3	m_workSize;
+};
+
+class SharedVarAtomicOpCase : public TestCase
+{
+public:
+	SharedVarAtomicOpCase (Context& context, const char* name, const char* description, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
+		: TestCase		(context, name, description)
+		, m_localSize	(localSize)
+		, m_workSize	(workSize)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const Buffer				outputBuffer	(m_context.getRenderContext());
+		const int					workGroupSize	= m_localSize[0]*m_localSize[1]*m_localSize[2];
+		const int					workGroupCount	= m_workSize[0]*m_workSize[1]*m_workSize[2];
+		const int					numValues		= workGroupSize*workGroupCount;
+
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
+			<< "layout(binding = 0) buffer Output {\n"
+			<< "    uint values[" << numValues << "];\n"
+			<< "} sb_out;\n\n"
+			<< "shared uint count;\n\n"
+			<< "void main (void) {\n"
+			<< "    uint localSize  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
+			<< "    uint globalNdx  = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+			<< "    uint globalOffs = localSize*globalNdx;\n"
+			<< "\n"
+			<< "    count = 0u;\n"
+			<< "    memoryBarrierShared();\n"
+			<< "    uint oldVal = atomicAdd(count, 1u);\n"
+			<< "    sb_out.values[globalOffs+oldVal] = oldVal+1u;\n"
+			<< "}\n";
+
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Output buffer setup
+		{
+			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+
+			for (int groupNdx = 0; groupNdx < workGroupCount; groupNdx++)
+			{
+				for (int localOffs = 0; localOffs < workGroupSize; localOffs++)
+				{
+					const int		globalOffs	= groupNdx*workGroupSize;
+					const deUint32	res			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*(globalOffs + localOffs)));
+					const deUint32	ref			= (deUint32)(localOffs+1);
+
+					if (res != ref)
+						throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(globalOffs + localOffs) + "]");
+				}
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const tcu::IVec3	m_localSize;
+	const tcu::IVec3	m_workSize;
+};
+
+class CopyImageToSSBOCase : public TestCase
+{
+public:
+	CopyImageToSSBOCase (Context& context, const char* name, const char* description, const tcu::IVec2& localSize, const tcu::IVec2& imageSize)
+		: TestCase		(context, name, description)
+		, m_localSize	(localSize)
+		, m_imageSize	(imageSize)
+	{
+		DE_ASSERT(m_imageSize[0] % m_localSize[0] == 0);
+		DE_ASSERT(m_imageSize[1] % m_localSize[1] == 0);
+	}
+
+	IterateResult iterate (void)
+	{
+
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ") in;\n"
+			<< "layout(r32ui, binding = 1) readonly uniform highp uimage2D u_srcImg;\n"
+			<< "layout(binding = 0) buffer Output {\n"
+			<< "    uint values[" << (m_imageSize[0]*m_imageSize[1]) << "];\n"
+			<< "} sb_out;\n\n"
+			<< "void main (void) {\n"
+			<< "    uint stride = gl_NumWorkGroups.x*gl_WorkGroupSize.x;\n"
+			<< "    uint value  = imageLoad(u_srcImg, ivec2(gl_GlobalInvocationID.xy)).x;\n"
+			<< "    sb_out.values[gl_GlobalInvocationID.y*stride + gl_GlobalInvocationID.x] = value;\n"
+			<< "}\n";
+
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const Buffer				outputBuffer	(m_context.getRenderContext());
+		const Texture				inputTexture	(m_context.getRenderContext());
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
+		const tcu::IVec2			workSize		= m_imageSize / m_localSize;
+		de::Random					rnd				(0xab2c7);
+		vector<deUint32>			inputValues		(m_imageSize[0]*m_imageSize[1]);
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << workSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Input values
+		for (vector<deUint32>::iterator i = inputValues.begin(); i != inputValues.end(); ++i)
+			*i = rnd.getUint32();
+
+		// Input image setup
+		gl.bindTexture(GL_TEXTURE_2D, *inputTexture);
+		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_imageSize[0], m_imageSize[1]);
+		gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_imageSize[0], m_imageSize[1], GL_RED_INTEGER, GL_UNSIGNED_INT, &inputValues[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading image data failed");
+
+		// Bind to unit 1
+		gl.bindImageTexture(1, *inputTexture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Image setup failed");
+
+		// Output buffer setup
+		{
+			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.dispatchCompute(workSize[0], workSize[1], 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+
+			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());
+
+			for (deUint32 ndx = 0; ndx < valueInfo.arraySize; ndx++)
+			{
+				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx));
+				const deUint32	ref		= inputValues[ndx];
+
+				if (res != ref)
+					throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(ndx) + "]");
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const tcu::IVec2	m_localSize;
+	const tcu::IVec2	m_imageSize;
+};
+
+class CopySSBOToImageCase : public TestCase
+{
+public:
+	CopySSBOToImageCase (Context& context, const char* name, const char* description, const tcu::IVec2& localSize, const tcu::IVec2& imageSize)
+		: TestCase		(context, name, description)
+		, m_localSize	(localSize)
+		, m_imageSize	(imageSize)
+	{
+		DE_ASSERT(m_imageSize[0] % m_localSize[0] == 0);
+		DE_ASSERT(m_imageSize[1] % m_localSize[1] == 0);
+	}
+
+	IterateResult iterate (void)
+	{
+
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ") in;\n"
+			<< "layout(r32ui, binding = 1) writeonly uniform highp uimage2D u_dstImg;\n"
+			<< "buffer Input {\n"
+			<< "    uint values[" << (m_imageSize[0]*m_imageSize[1]) << "];\n"
+			<< "} sb_in;\n\n"
+			<< "void main (void) {\n"
+			<< "    uint stride = gl_NumWorkGroups.x*gl_WorkGroupSize.x;\n"
+			<< "    uint value  = sb_in.values[gl_GlobalInvocationID.y*stride + gl_GlobalInvocationID.x];\n"
+			<< "    imageStore(u_dstImg, ivec2(gl_GlobalInvocationID.xy), uvec4(value, 0, 0, 0));\n"
+			<< "}\n";
+
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const Buffer				inputBuffer		(m_context.getRenderContext());
+		const Texture				outputTexture	(m_context.getRenderContext());
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
+		const tcu::IVec2			workSize		= m_imageSize / m_localSize;
+		de::Random					rnd				(0x77238ac2);
+		vector<deUint32>			inputValues		(m_imageSize[0]*m_imageSize[1]);
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << workSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Input values
+		for (vector<deUint32>::iterator i = inputValues.begin(); i != inputValues.end(); ++i)
+			*i = rnd.getUint32();
+
+		// Input buffer setup
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Input");
+			const InterfaceBlockInfo	blockInfo	= getProgramInterfaceBlockInfo(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Input.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *inputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, (glw::GLsizeiptr)blockInfo.dataSize, DE_NULL, GL_STATIC_DRAW);
+
+			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());
+
+			{
+				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, (int)blockInfo.dataSize, GL_MAP_WRITE_BIT);
+
+				for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
+					*(deUint32*)((deUint8*)bufMap.getPtr() + valueInfo.offset + ndx*valueInfo.arrayStride) = inputValues[ndx];
+			}
+
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, blockInfo.bufferBinding, *inputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Input buffer setup failed");
+		}
+
+		// Output image setup
+		gl.bindTexture(GL_TEXTURE_2D, *outputTexture);
+		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_imageSize[0], m_imageSize[1]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading image data failed");
+
+		// Bind to unit 1
+		gl.bindImageTexture(1, *outputTexture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Image setup failed");
+
+		// Dispatch compute workload
+		gl.dispatchCompute(workSize[0], workSize[1], 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare
+		{
+			Framebuffer			fbo			(m_context.getRenderContext());
+			vector<deUint32>	pixels		(inputValues.size()*4);
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+			gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *outputTexture, 0);
+			TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+			// \note In ES3 we have to use GL_RGBA_INTEGER
+			gl.readBuffer(GL_COLOR_ATTACHMENT0);
+			gl.readPixels(0, 0, m_imageSize[0], m_imageSize[1], GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");
+
+			for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
+			{
+				const deUint32	res		= pixels[ndx*4];
+				const deUint32	ref		= inputValues[ndx];
+
+				if (res != ref)
+					throw tcu::TestError(string("Comparison failed for pixel ") + de::toString(ndx));
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const tcu::IVec2	m_localSize;
+	const tcu::IVec2	m_imageSize;
+};
+
+class ImageAtomicOpCase : public TestCase
+{
+public:
+	ImageAtomicOpCase (Context& context, const char* name, const char* description, int localSize, const tcu::IVec2& imageSize)
+		: TestCase		(context, name, description)
+		, m_localSize	(localSize)
+		, m_imageSize	(imageSize)
+	{
+	}
+
+	void init (void)
+	{
+		if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
+			throw tcu::NotSupportedError("Test requires OES_shader_image_atomic extension");
+	}
+
+	IterateResult iterate (void)
+	{
+
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "#extension GL_OES_shader_image_atomic : require\n"
+			<< "layout (local_size_x = " << m_localSize << ") in;\n"
+			<< "layout(r32ui, binding = 1) uniform highp uimage2D u_dstImg;\n"
+			<< "buffer Input {\n"
+			<< "    uint values[" << (m_imageSize[0]*m_imageSize[1]*m_localSize) << "];\n"
+			<< "} sb_in;\n\n"
+			<< "void main (void) {\n"
+			<< "    uint stride = gl_NumWorkGroups.x*gl_WorkGroupSize.x;\n"
+			<< "    uint value  = sb_in.values[gl_GlobalInvocationID.y*stride + gl_GlobalInvocationID.x];\n"
+			<< "\n"
+			<< "    if (gl_LocalInvocationIndex == 0u)\n"
+			<< "        imageStore(u_dstImg, ivec2(gl_WorkGroupID.xy), uvec4(0));\n"
+			<< "    memoryBarrierImage();\n"
+			<< "    imageAtomicAdd(u_dstImg, ivec2(gl_WorkGroupID.xy), value);\n"
+			<< "}\n";
+
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const Buffer				inputBuffer		(m_context.getRenderContext());
+		const Texture				outputTexture	(m_context.getRenderContext());
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
+		de::Random					rnd				(0x77238ac2);
+		vector<deUint32>			inputValues		(m_imageSize[0]*m_imageSize[1]*m_localSize);
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_imageSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Input values
+		for (vector<deUint32>::iterator i = inputValues.begin(); i != inputValues.end(); ++i)
+			*i = rnd.getUint32();
+
+		// Input buffer setup
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Input");
+			const InterfaceBlockInfo	blockInfo	= getProgramInterfaceBlockInfo(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Input.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *inputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, (glw::GLsizeiptr)blockInfo.dataSize, DE_NULL, GL_STATIC_DRAW);
+
+			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());
+
+			{
+				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, (int)blockInfo.dataSize, GL_MAP_WRITE_BIT);
+
+				for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
+					*(deUint32*)((deUint8*)bufMap.getPtr() + valueInfo.offset + ndx*valueInfo.arrayStride) = inputValues[ndx];
+			}
+
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, blockInfo.bufferBinding, *inputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Input buffer setup failed");
+		}
+
+		// Output image setup
+		gl.bindTexture(GL_TEXTURE_2D, *outputTexture);
+		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_imageSize[0], m_imageSize[1]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading image data failed");
+
+		// Bind to unit 1
+		gl.bindImageTexture(1, *outputTexture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Image setup failed");
+
+		// Dispatch compute workload
+		gl.dispatchCompute(m_imageSize[0], m_imageSize[1], 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare
+		{
+			Framebuffer			fbo			(m_context.getRenderContext());
+			vector<deUint32>	pixels		(m_imageSize[0]*m_imageSize[1]*4);
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+			gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *outputTexture, 0);
+			TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+			// \note In ES3 we have to use GL_RGBA_INTEGER
+			gl.readBuffer(GL_COLOR_ATTACHMENT0);
+			gl.readPixels(0, 0, m_imageSize[0], m_imageSize[1], GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");
+
+			for (int pixelNdx = 0; pixelNdx < (int)inputValues.size()/m_localSize; pixelNdx++)
+			{
+				const deUint32	res		= pixels[pixelNdx*4];
+				deUint32		ref		= 0;
+
+				for (int offs = 0; offs < m_localSize; offs++)
+					ref += inputValues[pixelNdx*m_localSize + offs];
+
+				if (res != ref)
+					throw tcu::TestError(string("Comparison failed for pixel ") + de::toString(pixelNdx));
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const int			m_localSize;
+	const tcu::IVec2	m_imageSize;
+};
+
+class ImageBarrierCase : public TestCase
+{
+public:
+	ImageBarrierCase (Context& context, const char* name, const char* description, const tcu::IVec2& workSize)
+		: TestCase		(context, name, description)
+		, m_workSize	(workSize)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const ShaderProgram program0(m_context.getRenderContext(), ProgramSources() <<
+			ComputeSource("#version 310 es\n"
+						  "layout (local_size_x = 1) in;\n"
+						  "uniform uint u_baseVal;\n"
+						  "layout(r32ui, binding = 2) writeonly uniform highp uimage2D u_img;\n"
+						  "void main (void) {\n"
+						  "    uint offset = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+						  "    imageStore(u_img, ivec2(gl_WorkGroupID.xy), uvec4(offset+u_baseVal, 0, 0, 0));\n"
+						  "}\n"));
+		const ShaderProgram program1(m_context.getRenderContext(), ProgramSources() <<
+			ComputeSource("#version 310 es\n"
+						  "layout (local_size_x = 1) in;\n"
+						  "layout(r32ui, binding = 2) readonly uniform highp uimage2D u_img;\n"
+						  "layout(binding = 0) buffer Output {\n"
+						  "    coherent uint sum;\n"
+						  "};\n"
+						  "void main (void) {\n"
+						  "    uint value = imageLoad(u_img, ivec2(gl_WorkGroupID.xy)).x;\n"
+						  "    atomicAdd(sum, value);\n"
+						  "}\n"));
+
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const Texture				tempTexture		(m_context.getRenderContext());
+		const Buffer				outputBuffer	(m_context.getRenderContext());
+		const deUint32				baseValue		= 127;
+
+		m_testCtx.getLog() << program0 << program1;
+		if (!program0.isOk() || !program1.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;
+
+		// Temp texture setup
+		gl.bindTexture(GL_TEXTURE_2D, *tempTexture);
+		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_workSize[0], m_workSize[1]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading image data failed");
+
+		// Bind to unit 2
+		gl.bindImageTexture(2, *tempTexture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Image setup failed");
+
+		// Output buffer setup
+		{
+			const deUint32		blockIndex		= gl.getProgramResourceIndex(program1.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int			blockSize		= getProgramResourceInt(gl, program1.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
+
+			{
+				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_WRITE_BIT);
+				deMemset(bufMap.getPtr(), 0, blockSize);
+			}
+
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.useProgram(program0.getProgram());
+		gl.uniform1ui(gl.getUniformLocation(program0.getProgram(), "u_baseVal"), baseValue);
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], 1);
+		gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+		gl.useProgram(program1.getProgram());
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to dispatch commands");
+
+		// Read back and compare
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program1.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int					blockSize	= getProgramResourceInt(gl, program1.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program1.getProgram(), GL_BUFFER_VARIABLE, "sum");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program1.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+
+			const deUint32				res			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset));
+			deUint32					ref			= 0;
+
+			for (int ndx = 0; ndx < m_workSize[0]*m_workSize[1]; ndx++)
+				ref += baseValue + (deUint32)ndx;
+
+			if (res != ref)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed, expected " << ref << ", got " << res << TestLog::EndMessage;
+				throw tcu::TestError("Comparison failed");
+			}
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const tcu::IVec2	m_workSize;
+};
+
+class AtomicCounterCase : public TestCase
+{
+public:
+	AtomicCounterCase (Context& context, const char* name, const char* description, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
+		: TestCase		(context, name, description)
+		, m_localSize	(localSize)
+		, m_workSize	(workSize)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+		const Buffer				outputBuffer	(m_context.getRenderContext());
+		const Buffer				counterBuffer	(m_context.getRenderContext());
+		const int					workGroupSize	= m_localSize[0]*m_localSize[1]*m_localSize[2];
+		const int					workGroupCount	= m_workSize[0]*m_workSize[1]*m_workSize[2];
+		const int					numValues		= workGroupSize*workGroupCount;
+
+		std::ostringstream src;
+		src << "#version 310 es\n"
+			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
+			<< "layout(binding = 0) buffer Output {\n"
+			<< "    uint values[" << numValues << "];\n"
+			<< "} sb_out;\n\n"
+			<< "layout(binding = 0, offset = 0) uniform atomic_uint u_count;\n\n"
+			<< "void main (void) {\n"
+			<< "    uint localSize  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
+			<< "    uint globalNdx  = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+			<< "    uint globalOffs = localSize*globalNdx;\n"
+			<< "    uint localOffs  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_LocalInvocationID.z + gl_WorkGroupSize.x*gl_LocalInvocationID.y + gl_LocalInvocationID.x;\n"
+			<< "\n"
+			<< "    uint oldVal = atomicCounterIncrement(u_count);\n"
+			<< "    sb_out.values[globalOffs+localOffs] = oldVal;\n"
+			<< "}\n";
+
+		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ComputeSource(src.str()));
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;
+
+		gl.useProgram(program.getProgram());
+
+		// Atomic counter buffer setup
+		{
+			const deUint32	uniformIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_count");
+			const deUint32	bufferIndex		= getProgramResourceUint(gl, program.getProgram(), GL_UNIFORM, uniformIndex, GL_ATOMIC_COUNTER_BUFFER_INDEX);
+			const deUint32	bufferSize		= getProgramResourceUint(gl, program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferIndex, GL_BUFFER_DATA_SIZE);
+
+			gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, *counterBuffer);
+			gl.bufferData(GL_ATOMIC_COUNTER_BUFFER, bufferSize, DE_NULL, GL_STREAM_READ);
+
+			{
+				const BufferMemMap memMap(gl, GL_ATOMIC_COUNTER_BUFFER, 0, bufferSize, GL_MAP_WRITE_BIT);
+				deMemset(memMap.getPtr(), 0, (int)bufferSize);
+			}
+
+			gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, *counterBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Atomic counter buffer setup failed");
+		}
+
+		// Output buffer setup
+		{
+			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+		}
+
+		// Dispatch compute workload
+		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+		// Read back and compare atomic counter
+		{
+			const deUint32		uniformIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_count");
+			const deUint32		uniformOffset	= getProgramResourceUint(gl, program.getProgram(), GL_UNIFORM, uniformIndex, GL_OFFSET);
+			const deUint32		bufferIndex		= getProgramResourceUint(gl, program.getProgram(), GL_UNIFORM, uniformIndex, GL_ATOMIC_COUNTER_BUFFER_INDEX);
+			const deUint32		bufferSize		= getProgramResourceUint(gl, program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferIndex, GL_BUFFER_DATA_SIZE);
+			const BufferMemMap	bufMap			(gl, GL_ATOMIC_COUNTER_BUFFER, 0, bufferSize, GL_MAP_READ_BIT);
+
+			const deUint32		resVal			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + uniformOffset));
+
+			if (resVal != (deUint32)numValues)
+				throw tcu::TestError("Invalid atomic counter value");
+		}
+
+		// Read back and compare SSBO
+		{
+			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
+			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+			deUint32					valSum		= 0;
+			deUint32					refSum		= 0;
+
+			for (int valNdx = 0; valNdx < numValues; valNdx++)
+			{
+				const deUint32 res = *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*valNdx));
+
+				valSum += res;
+				refSum += (deUint32)valNdx;
+
+				if (!de::inBounds<deUint32>(res, 0, (deUint32)numValues))
+					throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(valNdx) + "]");
+			}
+
+			if (valSum != refSum)
+				throw tcu::TestError("Total sum of values in Output.values doesn't match");
+		}
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	const tcu::IVec3	m_localSize;
+	const tcu::IVec3	m_workSize;
+};
+
+} // anonymous
+
+BasicComputeShaderTests::BasicComputeShaderTests (Context& context)
+	: TestCaseGroup(context, "basic", "Basic Compute Shader Tests")
+{
+}
+
+BasicComputeShaderTests::~BasicComputeShaderTests (void)
+{
+}
+
+void BasicComputeShaderTests::init (void)
+{
+	addChild(new EmptyComputeShaderCase(m_context));
+
+	addChild(new UBOToSSBOInvertCase	(m_context, "ubo_to_ssbo_single_invocation",			"Copy from UBO to SSBO, inverting bits",	256,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
+	addChild(new UBOToSSBOInvertCase	(m_context, "ubo_to_ssbo_single_group",					"Copy from UBO to SSBO, inverting bits",	1024,	tcu::IVec3(2,1,4),	tcu::IVec3(1,1,1)));
+	addChild(new UBOToSSBOInvertCase	(m_context, "ubo_to_ssbo_multiple_invocations",			"Copy from UBO to SSBO, inverting bits",	1024,	tcu::IVec3(1,1,1),	tcu::IVec3(2,4,1)));
+	addChild(new UBOToSSBOInvertCase	(m_context, "ubo_to_ssbo_multiple_groups",				"Copy from UBO to SSBO, inverting bits",	1024,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));
+
+	addChild(new CopyInvertSSBOCase		(m_context, "copy_ssbo_single_invocation",				"Copy between SSBOs, inverting bits",	256,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
+	addChild(new CopyInvertSSBOCase		(m_context, "copy_ssbo_multiple_invocations",			"Copy between SSBOs, inverting bits",	1024,	tcu::IVec3(1,1,1),	tcu::IVec3(2,4,1)));
+	addChild(new CopyInvertSSBOCase		(m_context, "copy_ssbo_multiple_groups",				"Copy between SSBOs, inverting bits",	1024,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));
+
+	addChild(new InvertSSBOInPlaceCase	(m_context, "ssbo_rw_single_invocation",				"Read and write same SSBO",				256,	true,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
+	addChild(new InvertSSBOInPlaceCase	(m_context, "ssbo_rw_multiple_groups",					"Read and write same SSBO",				1024,	true,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));
+
+	addChild(new InvertSSBOInPlaceCase	(m_context, "ssbo_unsized_arr_single_invocation",		"Read and write same SSBO",				256,	false,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
+	addChild(new InvertSSBOInPlaceCase	(m_context, "ssbo_unsized_arr_multiple_groups",			"Read and write same SSBO",				1024,	false,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));
+
+	addChild(new WriteToMultipleSSBOCase(m_context, "write_multiple_arr_single_invocation",		"Write to multiple SSBOs",				256,	true,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
+	addChild(new WriteToMultipleSSBOCase(m_context, "write_multiple_arr_multiple_groups",		"Write to multiple SSBOs",				1024,	true,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));
+
+	addChild(new WriteToMultipleSSBOCase(m_context, "write_multiple_unsized_arr_single_invocation",	"Write to multiple SSBOs",			256,	false,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
+	addChild(new WriteToMultipleSSBOCase(m_context, "write_multiple_unsized_arr_multiple_groups",	"Write to multiple SSBOs",			1024,	false,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));
+
+	addChild(new SSBOLocalBarrierCase	(m_context, "ssbo_local_barrier_single_invocation",		"SSBO local barrier usage",				tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
+	addChild(new SSBOLocalBarrierCase	(m_context, "ssbo_local_barrier_single_group",			"SSBO local barrier usage",				tcu::IVec3(3,2,5),	tcu::IVec3(1,1,1)));
+	addChild(new SSBOLocalBarrierCase	(m_context, "ssbo_local_barrier_multiple_groups",		"SSBO local barrier usage",				tcu::IVec3(3,4,1),	tcu::IVec3(2,7,3)));
+
+	addChild(new SSBOBarrierCase		(m_context, "ssbo_cmd_barrier_single",					"SSBO memory barrier usage",			tcu::IVec3(1,1,1)));
+	addChild(new SSBOBarrierCase		(m_context, "ssbo_cmd_barrier_multiple",				"SSBO memory barrier usage",			tcu::IVec3(11,5,7)));
+
+	addChild(new BasicSharedVarCase		(m_context, "shared_var_single_invocation",				"Basic shared variable usage",			tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
+	addChild(new BasicSharedVarCase		(m_context, "shared_var_single_group",					"Basic shared variable usage",			tcu::IVec3(3,2,5),	tcu::IVec3(1,1,1)));
+	addChild(new BasicSharedVarCase		(m_context, "shared_var_multiple_invocations",			"Basic shared variable usage",			tcu::IVec3(1,1,1),	tcu::IVec3(2,5,4)));
+	addChild(new BasicSharedVarCase		(m_context, "shared_var_multiple_groups",				"Basic shared variable usage",			tcu::IVec3(3,4,1),	tcu::IVec3(2,7,3)));
+
+	addChild(new SharedVarAtomicOpCase	(m_context, "shared_atomic_op_single_invocation",		"Atomic operation with shared var",		tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
+	addChild(new SharedVarAtomicOpCase	(m_context, "shared_atomic_op_single_group",			"Atomic operation with shared var",		tcu::IVec3(3,2,5),	tcu::IVec3(1,1,1)));
+	addChild(new SharedVarAtomicOpCase	(m_context, "shared_atomic_op_multiple_invocations",	"Atomic operation with shared var",		tcu::IVec3(1,1,1),	tcu::IVec3(2,5,4)));
+	addChild(new SharedVarAtomicOpCase	(m_context, "shared_atomic_op_multiple_groups",			"Atomic operation with shared var",		tcu::IVec3(3,4,1),	tcu::IVec3(2,7,3)));
+
+	addChild(new CopyImageToSSBOCase	(m_context, "copy_image_to_ssbo_small",					"Image to SSBO copy",					tcu::IVec2(1,1),	tcu::IVec2(64,64)));
+	addChild(new CopyImageToSSBOCase	(m_context, "copy_image_to_ssbo_large",					"Image to SSBO copy",					tcu::IVec2(2,4),	tcu::IVec2(512,512)));
+
+	addChild(new CopySSBOToImageCase	(m_context, "copy_ssbo_to_image_small",					"SSBO to image copy",					tcu::IVec2(1,1),	tcu::IVec2(64,64)));
+	addChild(new CopySSBOToImageCase	(m_context, "copy_ssbo_to_image_large",					"SSBO to image copy",					tcu::IVec2(2,4),	tcu::IVec2(512,512)));
+
+	addChild(new ImageAtomicOpCase		(m_context, "image_atomic_op_local_size_1",				"Atomic operation with image",			1,	tcu::IVec2(64,64)));
+	addChild(new ImageAtomicOpCase		(m_context, "image_atomic_op_local_size_8",				"Atomic operation with image",			8,	tcu::IVec2(64,64)));
+
+	addChild(new ImageBarrierCase		(m_context, "image_barrier_single",						"Image barrier",						tcu::IVec2(1,1)));
+	addChild(new ImageBarrierCase		(m_context, "image_barrier_multiple",					"Image barrier",						tcu::IVec2(64,64)));
+
+	addChild(new AtomicCounterCase		(m_context, "atomic_counter_single_invocation",			"Basic atomic counter test",			tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
+	addChild(new AtomicCounterCase		(m_context, "atomic_counter_single_group",				"Basic atomic counter test",			tcu::IVec3(3,2,5),	tcu::IVec3(1,1,1)));
+	addChild(new AtomicCounterCase		(m_context, "atomic_counter_multiple_invocations",		"Basic atomic counter test",			tcu::IVec3(1,1,1),	tcu::IVec3(2,5,4)));
+	addChild(new AtomicCounterCase		(m_context, "atomic_counter_multiple_groups",			"Basic atomic counter test",			tcu::IVec3(3,4,1),	tcu::IVec3(2,7,3)));
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fBasicComputeShaderTests.hpp b/modules/gles31/functional/es31fBasicComputeShaderTests.hpp
new file mode 100644
index 0000000..5fe1acc
--- /dev/null
+++ b/modules/gles31/functional/es31fBasicComputeShaderTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FBASICCOMPUTESHADERTESTS_HPP
+#define _ES31FBASICCOMPUTESHADERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Basic Compute Shader Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class BasicComputeShaderTests : public TestCaseGroup
+{
+public:
+								BasicComputeShaderTests		(Context& context);
+								~BasicComputeShaderTests	(void);
+
+	void						init						(void);
+
+private:
+								BasicComputeShaderTests		(const BasicComputeShaderTests& other);
+	BasicComputeShaderTests&	operator=					(const BasicComputeShaderTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FBASICCOMPUTESHADERTESTS_HPP
diff --git a/modules/gles31/functional/es31fBuiltinPrecisionTests.cpp b/modules/gles31/functional/es31fBuiltinPrecisionTests.cpp
new file mode 100644
index 0000000..620706b
--- /dev/null
+++ b/modules/gles31/functional/es31fBuiltinPrecisionTests.cpp
@@ -0,0 +1,70 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tests for precision and range of GLSL builtins and types.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fBuiltinPrecisionTests.hpp"
+
+#include "deUniquePtr.hpp"
+#include "glsBuiltinPrecisionTests.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace bpt = gls::BuiltinPrecisionTests;
+
+TestCaseGroup* createBuiltinPrecisionTests (Context& context)
+{
+	TestCaseGroup*							group		= new TestCaseGroup(
+		context, "precision", "Builtin precision tests");
+	std::vector<glu::ShaderType>			shaderTypes;
+	de::MovePtr<const bpt::CaseFactories>	es3Cases		= bpt::createES3BuiltinCases();
+	de::MovePtr<const bpt::CaseFactories>	es31Cases		= bpt::createES31BuiltinCases();
+
+	shaderTypes.push_back(glu::SHADERTYPE_COMPUTE);
+
+	bpt::addBuiltinPrecisionTests(context.getTestContext(),
+								  context.getRenderContext(),
+								  *es3Cases,
+								  shaderTypes,
+								  *group);
+
+	shaderTypes.clear();
+	shaderTypes.push_back(glu::SHADERTYPE_VERTEX);
+	shaderTypes.push_back(glu::SHADERTYPE_FRAGMENT);
+	shaderTypes.push_back(glu::SHADERTYPE_COMPUTE);
+
+	bpt::addBuiltinPrecisionTests(context.getTestContext(),
+								  context.getRenderContext(),
+								  *es31Cases,
+								  shaderTypes,
+								  *group);
+	return group;
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fBuiltinPrecisionTests.hpp b/modules/gles31/functional/es31fBuiltinPrecisionTests.hpp
new file mode 100644
index 0000000..17fb49b
--- /dev/null
+++ b/modules/gles31/functional/es31fBuiltinPrecisionTests.hpp
@@ -0,0 +1,42 @@
+#ifndef _ES31FBUILTINPRECISIONTESTS_HPP
+#define _ES31FBUILTINPRECISIONTESTS_HPP
+
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tests for precision and range of GLSL builtins and types.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+TestCaseGroup* createBuiltinPrecisionTests (Context& context);
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FBUILTINPRECISIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fComputeShaderBuiltinVarTests.cpp b/modules/gles31/functional/es31fComputeShaderBuiltinVarTests.cpp
new file mode 100644
index 0000000..d074306
--- /dev/null
+++ b/modules/gles31/functional/es31fComputeShaderBuiltinVarTests.cpp
@@ -0,0 +1,456 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Compute Shader Built-in variable tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fComputeShaderBuiltinVarTests.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluProgramInterfaceQuery.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deSharedPtr.hpp"
+#include "deStringUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <map>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using std::map;
+using tcu::TestLog;
+using tcu::UVec3;
+using tcu::IVec3;
+
+using namespace glu;
+
+template<typename T, int Size>
+struct LexicalCompareVec
+{
+	inline bool operator() (const tcu::Vector<T, Size>& a, const tcu::Vector<T, Size>& b) const
+	{
+		for (int ndx = 0; ndx < Size; ndx++)
+		{
+			if (a[ndx] < b[ndx])
+				return true;
+			else if (a[ndx] > b[ndx])
+				return false;
+		}
+		return false;
+	}
+};
+
+typedef de::SharedPtr<glu::ShaderProgram>										ShaderProgramSp;
+typedef std::map<tcu::UVec3, ShaderProgramSp, LexicalCompareVec<deUint32, 3> >	LocalSizeProgramMap;
+
+class ComputeBuiltinVarCase : public TestCase
+{
+public:
+							ComputeBuiltinVarCase	(Context& context, const char* name, const char* varName, DataType varType);
+							~ComputeBuiltinVarCase	(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+	virtual UVec3			computeReference		(const UVec3& numWorkGroups, const UVec3& workGroupSize, const UVec3& workGroupID, const UVec3& localInvocationID) const = 0;
+
+protected:
+	struct SubCase
+	{
+		UVec3		localSize;
+		UVec3		numWorkGroups;
+
+		SubCase (void) {}
+		SubCase (const UVec3& localSize_, const UVec3& numWorkGroups_) : localSize(localSize_), numWorkGroups(numWorkGroups_) {}
+	};
+
+	vector<SubCase>			m_subCases;
+
+private:
+							ComputeBuiltinVarCase	(const ComputeBuiltinVarCase& other);
+	ComputeBuiltinVarCase&	operator=				(const ComputeBuiltinVarCase& other);
+
+	deUint32				getProgram				(const UVec3& localSize);
+
+	const string			m_varName;
+	const DataType			m_varType;
+
+	LocalSizeProgramMap		m_progMap;
+	int						m_subCaseNdx;
+};
+
+ComputeBuiltinVarCase::ComputeBuiltinVarCase (Context& context, const char* name, const char* varName, DataType varType)
+	: TestCase		(context, name, varName)
+	, m_varName		(varName)
+	, m_varType		(varType)
+	, m_subCaseNdx	(0)
+{
+}
+
+ComputeBuiltinVarCase::~ComputeBuiltinVarCase (void)
+{
+	ComputeBuiltinVarCase::deinit();
+}
+
+void ComputeBuiltinVarCase::init (void)
+{
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	m_subCaseNdx = 0;
+}
+
+void ComputeBuiltinVarCase::deinit (void)
+{
+	m_progMap.clear();
+}
+
+static string genBuiltinVarSource (const string& varName, DataType varType, const UVec3& localSize)
+{
+	std::ostringstream src;
+
+	src << "#version 310 es\n"
+		<< "layout (local_size_x = " << localSize.x() << ", local_size_y = " << localSize.y() << ", local_size_z = " << localSize.z() << ") in;\n"
+		<< "uniform highp uvec2 u_stride;\n"
+		<< "layout(binding = 0) buffer Output\n"
+		<< "{\n"
+		<< "	" << glu::getDataTypeName(varType) << " result[];\n"
+		<< "} sb_out;\n"
+		<< "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	highp uint offset = u_stride.x*gl_GlobalInvocationID.z + u_stride.y*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
+		<< "	sb_out.result[offset] = " << varName << ";\n"
+		<< "}\n";
+
+	return src.str();
+}
+
+deUint32 ComputeBuiltinVarCase::getProgram (const UVec3& localSize)
+{
+	LocalSizeProgramMap::const_iterator cachePos = m_progMap.find(localSize);
+	if (cachePos != m_progMap.end())
+		return cachePos->second->getProgram();
+	else
+	{
+		ShaderProgramSp program(new ShaderProgram(m_context.getRenderContext(),
+												  ProgramSources() << ComputeSource(genBuiltinVarSource(m_varName, m_varType, localSize))));
+
+		// Log all compiled programs.
+		m_testCtx.getLog() << *program;
+		if (!program->isOk())
+			throw tcu::TestError("Compile failed");
+
+		m_progMap[localSize] = program;
+		return program->getProgram();
+	}
+}
+
+static inline UVec3 readResultVec (const deUint32* ptr, int numComps)
+{
+	UVec3 res;
+	for (int ndx = 0; ndx < numComps; ndx++)
+		res[ndx] = ptr[ndx];
+	return res;
+}
+
+static inline bool compareComps (const UVec3& a, const UVec3& b, int numComps)
+{
+	DE_ASSERT(numComps == 1 || numComps == 3);
+	return numComps == 3 ? tcu::allEqual(a, b) : a.x() == b.x();
+}
+
+struct LogComps
+{
+	const UVec3&	v;
+	int				numComps;
+
+	LogComps (const UVec3& v_, int numComps_) : v(v_), numComps(numComps_) {}
+};
+
+static inline std::ostream& operator<< (std::ostream& str, const LogComps& c)
+{
+	DE_ASSERT(c.numComps == 1 || c.numComps == 3);
+	return c.numComps == 3 ? str << c.v : str << c.v.x();
+}
+
+ComputeBuiltinVarCase::IterateResult ComputeBuiltinVarCase::iterate (void)
+{
+	const tcu::ScopedLogSection		section			(m_testCtx.getLog(), string("Iteration") + de::toString(m_subCaseNdx), string("Iteration ") + de::toString(m_subCaseNdx));
+	const glw::Functions&			gl				= m_context.getRenderContext().getFunctions();
+	const SubCase&					subCase			= m_subCases[m_subCaseNdx];
+	const deUint32					program			= getProgram(subCase.localSize);
+
+	const tcu::UVec3				globalSize		= subCase.localSize*subCase.numWorkGroups;
+	const tcu::UVec2				stride			(globalSize[0]*globalSize[1], globalSize[0]);
+	const deUint32					numInvocations	= subCase.localSize[0]*subCase.localSize[1]*subCase.localSize[2]*subCase.numWorkGroups[0]*subCase.numWorkGroups[1]*subCase.numWorkGroups[2];
+
+	const deUint32					outVarIndex		= gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "Output.result");
+	const InterfaceVariableInfo		outVarInfo		= getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, outVarIndex);
+	const deUint32					bufferSize		= numInvocations*outVarInfo.arrayStride;
+	Buffer							outputBuffer	(m_context.getRenderContext());
+
+	TCU_CHECK(outVarInfo.arraySize == 0); // Unsized variable.
+
+	m_testCtx.getLog() << TestLog::Message << "Number of work groups = " << subCase.numWorkGroups << TestLog::EndMessage
+					   << TestLog::Message << "Work group size = " << subCase.localSize << TestLog::EndMessage;
+
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, (glw::GLsizeiptr)bufferSize, DE_NULL, GL_STREAM_READ);
+	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer setup failed");
+
+	gl.useProgram(program);
+	gl.uniform2uiv(gl.getUniformLocation(program, "u_stride"), 1, stride.getPtr());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Program setup failed");
+
+	gl.dispatchCompute(subCase.numWorkGroups[0], subCase.numWorkGroups[1], subCase.numWorkGroups[2]);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute() failed");
+
+	{
+		const void*	ptr				= gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, bufferSize, GL_MAP_READ_BIT);
+		int			numFailed		= 0;
+		const int	numScalars		= getDataTypeScalarSize(m_varType);
+		const int	maxLogPrints	= 10;
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange() failed");
+		TCU_CHECK(ptr);
+
+		for (deUint32 groupZ = 0; groupZ < subCase.numWorkGroups.z(); groupZ++)
+		for (deUint32 groupY = 0; groupY < subCase.numWorkGroups.y(); groupY++)
+		for (deUint32 groupX = 0; groupX < subCase.numWorkGroups.x(); groupX++)
+		for (deUint32 localZ = 0; localZ < subCase.localSize.z(); localZ++)
+		for (deUint32 localY = 0; localY < subCase.localSize.y(); localY++)
+		for (deUint32 localX = 0; localX < subCase.localSize.x(); localX++)
+		{
+			const UVec3			refGroupID		(groupX, groupY, groupZ);
+			const UVec3			refLocalID		(localX, localY, localZ);
+			const UVec3			refGlobalID		= refGroupID * subCase.localSize + refLocalID;
+			const deUint32		refOffset		= stride.x()*refGlobalID.z() + stride.y()*refGlobalID.y() + refGlobalID.x();
+			const UVec3			refValue		= computeReference(subCase.numWorkGroups, subCase.localSize, refGroupID, refLocalID);
+
+			const deUint32*		resPtr			= (const deUint32*)((const deUint8*)ptr + refOffset*outVarInfo.arrayStride);
+			const UVec3			resValue		= readResultVec(resPtr, numScalars);
+
+			if (!compareComps(refValue, resValue, numScalars))
+			{
+				if (numFailed < maxLogPrints)
+					m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed at offset " << refOffset
+														   << ": expected " << LogComps(refValue, numScalars)
+														   << ", got " << LogComps(resValue, numScalars)
+									   << TestLog::EndMessage;
+				else if (numFailed == maxLogPrints)
+					m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+				numFailed += 1;
+			}
+		}
+
+		m_testCtx.getLog() << TestLog::Message << (numInvocations-numFailed) << " / " << numInvocations << " values passed" << TestLog::EndMessage;
+
+		if (numFailed > 0)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Comparison failed");
+
+		gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+	}
+
+	m_subCaseNdx += 1;
+	return (m_subCaseNdx < (int)m_subCases.size() && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS) ? CONTINUE : STOP;
+}
+
+// Test cases
+
+class NumWorkGroupsCase : public ComputeBuiltinVarCase
+{
+public:
+	NumWorkGroupsCase (Context& context)
+		: ComputeBuiltinVarCase(context, "num_work_groups", "gl_NumWorkGroups", TYPE_UINT_VEC3)
+	{
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(52,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,39,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,1,78)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(4,7,11)));
+		m_subCases.push_back(SubCase(UVec3(2,3,4), UVec3(4,7,11)));
+	}
+
+	UVec3 computeReference (const UVec3& numWorkGroups, const UVec3& workGroupSize, const UVec3& workGroupID, const UVec3& localInvocationID) const
+	{
+		DE_UNREF(numWorkGroups);
+		DE_UNREF(workGroupSize);
+		DE_UNREF(workGroupID);
+		DE_UNREF(localInvocationID);
+		return numWorkGroups;
+	}
+};
+
+class WorkGroupSizeCase : public ComputeBuiltinVarCase
+{
+public:
+	WorkGroupSizeCase (Context& context)
+		: ComputeBuiltinVarCase(context, "work_group_size", "gl_WorkGroupSize", TYPE_UINT_VEC3)
+	{
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(2,7,3)));
+		m_subCases.push_back(SubCase(UVec3(2,1,1), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(2,1,1), UVec3(1,3,5)));
+		m_subCases.push_back(SubCase(UVec3(1,3,1), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,7), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,7), UVec3(3,3,1)));
+		m_subCases.push_back(SubCase(UVec3(11,3,8), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(11,3,8), UVec3(3,1,2)));
+	}
+
+	UVec3 computeReference (const UVec3& numWorkGroups, const UVec3& workGroupSize, const UVec3& workGroupID, const UVec3& localInvocationID) const
+	{
+		DE_UNREF(numWorkGroups);
+		DE_UNREF(workGroupID);
+		DE_UNREF(localInvocationID);
+		return workGroupSize;
+	}
+};
+
+class WorkGroupIDCase : public ComputeBuiltinVarCase
+{
+public:
+	WorkGroupIDCase (Context& context)
+		: ComputeBuiltinVarCase(context, "work_group_id", "gl_WorkGroupID", TYPE_UINT_VEC3)
+	{
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(52,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,39,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,1,78)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(4,7,11)));
+		m_subCases.push_back(SubCase(UVec3(2,3,4), UVec3(4,7,11)));
+	}
+
+	UVec3 computeReference (const UVec3& numWorkGroups, const UVec3& workGroupSize, const UVec3& workGroupID, const UVec3& localInvocationID) const
+	{
+		DE_UNREF(numWorkGroups);
+		DE_UNREF(workGroupSize);
+		DE_UNREF(localInvocationID);
+		return workGroupID;
+	}
+};
+
+class LocalInvocationIDCase : public ComputeBuiltinVarCase
+{
+public:
+	LocalInvocationIDCase (Context& context)
+		: ComputeBuiltinVarCase(context, "local_invocation_id", "gl_LocalInvocationID", TYPE_UINT_VEC3)
+	{
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(2,7,3)));
+		m_subCases.push_back(SubCase(UVec3(2,1,1), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(2,1,1), UVec3(1,3,5)));
+		m_subCases.push_back(SubCase(UVec3(1,3,1), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,7), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,7), UVec3(3,3,1)));
+		m_subCases.push_back(SubCase(UVec3(11,3,8), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(11,3,8), UVec3(3,1,2)));
+	}
+
+	UVec3 computeReference (const UVec3& numWorkGroups, const UVec3& workGroupSize, const UVec3& workGroupID, const UVec3& localInvocationID) const
+	{
+		DE_UNREF(numWorkGroups);
+		DE_UNREF(workGroupSize);
+		DE_UNREF(workGroupID);
+		return localInvocationID;
+	}
+};
+
+class GlobalInvocationIDCase : public ComputeBuiltinVarCase
+{
+public:
+	GlobalInvocationIDCase (Context& context)
+		: ComputeBuiltinVarCase(context, "global_invocation_id", "gl_GlobalInvocationID", TYPE_UINT_VEC3)
+	{
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(52,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,39,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,1,78)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(4,7,11)));
+		m_subCases.push_back(SubCase(UVec3(2,3,4), UVec3(4,7,11)));
+		m_subCases.push_back(SubCase(UVec3(11,3,8), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(11,3,8), UVec3(3,1,2)));
+	}
+
+	UVec3 computeReference (const UVec3& numWorkGroups, const UVec3& workGroupSize, const UVec3& workGroupID, const UVec3& localInvocationID) const
+	{
+		DE_UNREF(numWorkGroups);
+		return workGroupID * workGroupSize + localInvocationID;
+	}
+};
+
+class LocalInvocationIndexCase : public ComputeBuiltinVarCase
+{
+public:
+	LocalInvocationIndexCase (Context& context)
+		: ComputeBuiltinVarCase(context, "local_invocation_index", "gl_LocalInvocationIndex", TYPE_UINT)
+	{
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(1,39,1)));
+		m_subCases.push_back(SubCase(UVec3(1,1,1), UVec3(4,7,11)));
+		m_subCases.push_back(SubCase(UVec3(2,3,4), UVec3(4,7,11)));
+		m_subCases.push_back(SubCase(UVec3(11,3,8), UVec3(1,1,1)));
+		m_subCases.push_back(SubCase(UVec3(11,3,8), UVec3(3,1,2)));
+	}
+
+	UVec3 computeReference (const UVec3& numWorkGroups, const UVec3& workGroupSize, const UVec3& workGroupID, const UVec3& localInvocationID) const
+	{
+		DE_UNREF(workGroupID);
+		DE_UNREF(numWorkGroups);
+		return UVec3(localInvocationID.z()*workGroupSize.x()*workGroupSize.y() + localInvocationID.y()*workGroupSize.x() + localInvocationID.x(), 0, 0);
+	}
+};
+
+ComputeShaderBuiltinVarTests::ComputeShaderBuiltinVarTests (Context& context)
+	: TestCaseGroup(context, "compute", "Compute Shader Builtin Variables")
+{
+}
+
+ComputeShaderBuiltinVarTests::~ComputeShaderBuiltinVarTests (void)
+{
+}
+
+void ComputeShaderBuiltinVarTests::init (void)
+{
+	addChild(new NumWorkGroupsCase			(m_context));
+	addChild(new WorkGroupSizeCase			(m_context));
+	addChild(new WorkGroupIDCase			(m_context));
+	addChild(new LocalInvocationIDCase		(m_context));
+	addChild(new GlobalInvocationIDCase		(m_context));
+	addChild(new LocalInvocationIndexCase	(m_context));
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fComputeShaderBuiltinVarTests.hpp b/modules/gles31/functional/es31fComputeShaderBuiltinVarTests.hpp
new file mode 100644
index 0000000..6b3953b
--- /dev/null
+++ b/modules/gles31/functional/es31fComputeShaderBuiltinVarTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FCOMPUTESHADERBUILTINVARTESTS_HPP
+#define _ES31FCOMPUTESHADERBUILTINVARTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Compute Shader Built-in variable tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ComputeShaderBuiltinVarTests : public TestCaseGroup
+{
+public:
+									ComputeShaderBuiltinVarTests	(Context& context);
+									~ComputeShaderBuiltinVarTests	(void);
+
+	void							init							(void);
+
+private:
+									ComputeShaderBuiltinVarTests	(const ComputeShaderBuiltinVarTests& other);
+	ComputeShaderBuiltinVarTests&	operator=						(const ComputeShaderBuiltinVarTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FCOMPUTESHADERBUILTINVARTESTS_HPP
diff --git a/modules/gles31/functional/es31fDebugTests.cpp b/modules/gles31/functional/es31fDebugTests.cpp
new file mode 100644
index 0000000..078197e
--- /dev/null
+++ b/modules/gles31/functional/es31fDebugTests.cpp
@@ -0,0 +1,1889 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Debug output (KHR_debug) tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fDebugTests.hpp"
+
+#include "es31fNegativeTestShared.hpp"
+#include "es31fNegativeBufferApiTests.hpp"
+#include "es31fNegativeTextureApiTests.hpp"
+#include "es31fNegativeShaderApiTests.hpp"
+#include "es31fNegativeFragmentApiTests.hpp"
+#include "es31fNegativeVertexArrayApiTests.hpp"
+#include "es31fNegativeStateApiTests.hpp"
+
+#include "deUniquePtr.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deSTLUtil.hpp"
+#include "deMutex.hpp"
+#include "deThread.h"
+
+#include "gluRenderContext.hpp"
+#include "gluContextInfo.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluStrUtil.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include "tes31Context.hpp"
+#include "tcuTestContext.hpp"
+#include "tcuCommandLine.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+using namespace glw;
+
+using NegativeTestShared::NegativeTestContext;
+using NegativeTestShared::FunctionContainer;
+using std::string;
+using std::vector;
+
+GLenum debugTypes[] =
+{
+	GL_DEBUG_TYPE_ERROR,
+	GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR,
+	GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR,
+	GL_DEBUG_TYPE_PORTABILITY,
+	GL_DEBUG_TYPE_PERFORMANCE,
+	GL_DEBUG_TYPE_OTHER,
+	GL_DEBUG_TYPE_MARKER,
+	GL_DEBUG_TYPE_PUSH_GROUP,
+	GL_DEBUG_TYPE_POP_GROUP,
+};
+
+GLenum debugSeverities[] =
+{
+	GL_DEBUG_SEVERITY_HIGH,
+    GL_DEBUG_SEVERITY_MEDIUM,
+    GL_DEBUG_SEVERITY_LOW,
+    GL_DEBUG_SEVERITY_NOTIFICATION,
+};
+
+void emitMessages(NegativeTestContext& ctx, GLenum source)
+{
+	for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(debugTypes); typeNdx++)
+	{
+		for (int severityNdx = 0; severityNdx < DE_LENGTH_OF_ARRAY(debugSeverities); severityNdx++)
+		{
+			const GLenum type		= debugTypes[typeNdx];
+			const GLenum severity	= debugSeverities[severityNdx];
+			const string msg		= string("Application generated message with type ") + glu::getDebugMessageTypeName(type)
+									  + " and severity " + glu::getDebugMessageSeverityName(severity);
+
+			// Use severity as ID, guaranteed unique
+			ctx.glDebugMessageInsert(source, type, severity, severity, -1, msg.c_str());
+			ctx.expectMessage(source, type);
+		}
+	}
+}
+
+void application_messages (NegativeTestContext& ctx)
+{
+	ctx.beginSection("Messages with source of GL_DEBUG_SOURCE_APPLICATION");
+	emitMessages(ctx, GL_DEBUG_SOURCE_APPLICATION);
+	ctx.endSection();
+}
+
+void thirdparty_messages (NegativeTestContext& ctx)
+{
+	ctx.beginSection("Messages with source of GL_DEBUG_SOURCE_THIRD_PARTY");
+	emitMessages(ctx, GL_DEBUG_SOURCE_THIRD_PARTY);
+	ctx.endSection();
+}
+
+void push_pop_messages (NegativeTestContext& ctx)
+{
+	ctx.beginSection("Push/Pop Debug Group");
+
+	ctx.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Application group 1");
+	ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP);
+	ctx.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 2, -1, "Application group 1-1");
+	ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP);
+	ctx.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 3, -1, "Application group 1-1-1");
+	ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP);
+	ctx.glPopDebugGroup();
+	ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP);
+	ctx.glPopDebugGroup();
+	ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP);
+
+	ctx.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 4, -1, "Application group 1-2");
+	ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP);
+	ctx.glPopDebugGroup();
+	ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP);
+
+	ctx.glPushDebugGroup(GL_DEBUG_SOURCE_THIRD_PARTY, 4, -1, "3rd Party group 1-3");
+	ctx.expectMessage(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_PUSH_GROUP);
+	ctx.glPopDebugGroup();
+	ctx.expectMessage(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_POP_GROUP);
+	ctx.glPopDebugGroup();
+	ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP);
+
+	ctx.glPushDebugGroup(GL_DEBUG_SOURCE_THIRD_PARTY, 4, -1, "3rd Party group 2");
+	ctx.expectMessage(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_PUSH_GROUP);
+	ctx.glPopDebugGroup();
+	ctx.expectMessage(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_POP_GROUP);
+
+	ctx.endSection();
+}
+
+vector<FunctionContainer> getUserMessageFuncs (void)
+{
+	FunctionContainer funcs[] =
+	{
+		{ application_messages,	"application_messages", "Externally generated messages from the application"	},
+		{ thirdparty_messages,	"third_party_messages",	"Externally generated messages from a third party"		},
+		{ push_pop_messages,	"push_pop_stack",		"Messages from pushing/popping debug groups"			},
+	};
+
+	return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
+}
+
+} // Anonymous
+
+namespace
+{
+
+using std::string;
+using std::vector;
+using std::set;
+using std::map;
+using de::MovePtr;
+
+using glw::GLenum;
+using glw::GLuint;
+using glw::GLsizei;
+
+using tcu::ResultCollector;
+using tcu::TestLog;
+using glu::CallLogWrapper;
+
+using NegativeTestShared::TestFunc;
+using NegativeTestShared::FunctionContainer;
+using NegativeTestShared::NegativeTestContext;
+
+// Data required to uniquely identify a debug message
+struct MessageID
+{
+	GLenum source;
+	GLenum type;
+	GLuint id;
+
+	MessageID (void) : source(GL_NONE), type(GL_NONE), id(0) {}
+	MessageID (GLenum source_, GLenum type_, GLuint id_) : source(source_), type(type_), id(id_) {}
+
+	bool operator== (const MessageID& rhs) const { return source == rhs.source && type == rhs.type && id == rhs.id;}
+	bool operator!= (const MessageID& rhs) const { return source != rhs.source || type != rhs.type || id != rhs.id;}
+	bool operator<  (const MessageID& rhs) const
+	{
+		return source < rhs.source || (source == rhs.source && (type < rhs.type || (type == rhs.type && id < rhs.id)));
+	}
+};
+
+std::ostream& operator<< (std::ostream& str, const MessageID &id)
+{
+	return str << glu::getDebugMessageSourceStr(id.source) << ", "	<< glu::getDebugMessageTypeStr(id.type) << ", " << id.id;
+}
+
+// All info from a single debug message
+struct MessageData
+{
+	MessageID	id;
+	GLenum		severity;
+	string		message;
+
+	MessageData (void) : id(MessageID()), severity(GL_NONE) {}
+	MessageData (const MessageID& id_, GLenum severity_, const string& message_) : id(id_) , severity(severity_) , message(message_) {}
+};
+
+extern "C" typedef void GLW_APIENTRY DebugCallbackFunc(GLenum, GLenum, GLuint, GLenum, GLsizei, const char*, void*);
+
+// Base class
+class BaseCase : public NegativeTestShared::ErrorCase
+{
+public:
+								BaseCase			(Context&					ctx,
+													 const char*				name,
+													 const char*				desc);
+	virtual 					~BaseCase			(void) {}
+
+	virtual IterateResult		iterate				(void) = 0;
+
+	virtual void				expectMessage		(GLenum source, GLenum type);
+	virtual void				expectError			(GLenum error0, GLenum error1);
+
+protected:
+	struct VerificationResult {
+		const qpTestResult	result;
+		const string		resultMessage;
+		const string		logMessage;
+
+		VerificationResult (qpTestResult result_, const string& resultMessage_, const string& logMessage_)
+			: result(result_), resultMessage(resultMessage_), logMessage(logMessage_) {}
+	};
+
+	static DebugCallbackFunc	callbackHandle;
+	virtual void				callback			(GLenum source, GLenum type, GLuint id, GLenum severity, const std::string& message);
+
+
+	VerificationResult			verifyMessageCount	(const MessageID& id, GLenum severity, int refCount, int resCount, bool messageEnabled) const;
+
+	// Verify a single message instance against expected attributes
+	void						verifyMessage		(const MessageData& message, GLenum source, GLenum type, GLuint id, GLenum severity);
+	void						verifyMessage		(const MessageData& message, GLenum source, GLenum type);
+
+	bool						verifyMessageExists	(const MessageData& message, GLenum source, GLenum type);
+	void						verifyMessageGroup	(const MessageData& message, GLenum source, GLenum type);
+	void						verifyMessageString	(const MessageData& message);
+
+	bool						isDebugContext		(void) const;
+
+	tcu::ResultCollector		m_results;
+};
+
+void BaseCase::callbackHandle (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char* message, void* userParam)
+{
+	static_cast<BaseCase*>(userParam)->callback(source, type, id, severity, string(message, &message[length]));
+}
+
+BaseCase::BaseCase (Context& ctx, const char* name, const char* desc)
+	: ErrorCase(ctx, name, desc)
+{
+}
+
+void BaseCase::expectMessage (GLenum source, GLenum type)
+{
+	DE_UNREF(source);
+	DE_UNREF(type);
+}
+
+void BaseCase::expectError (GLenum error0, GLenum error1)
+{
+	if (error0 != GL_NO_ERROR || error1 != GL_NO_ERROR)
+		expectMessage(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR);
+	else
+		expectMessage(GL_DONT_CARE, GL_DONT_CARE);
+}
+
+void BaseCase::callback (GLenum source, GLenum type, GLuint id, GLenum severity, const string& message)
+{
+	DE_UNREF(source);
+	DE_UNREF(type);
+	DE_UNREF(id);
+	DE_UNREF(severity);
+	DE_UNREF(message);
+}
+
+BaseCase::VerificationResult BaseCase::verifyMessageCount (const MessageID& id, GLenum severity, int refCount, int resCount, bool messageEnabled) const
+{
+	std::stringstream log;
+
+	// This message should not be filtered out
+	if (messageEnabled)
+	{
+		if (resCount != refCount)
+		{
+			/*
+			 * Technically nothing requires the implementation to be consistent in terms
+			 * of the messages it produces in most situations, allowing the set of messages
+			 * produced to vary between executions. This function splits messages
+			 * into deterministic and non-deterministic to facilitate handling of such messages.
+			 *
+			 * Non-deterministic messages that are present in differing quantities in filtered and
+			 * unfiltered runs will not fail the test case unless in direct violation of a filter:
+			 * the implementation may produce an arbitrary number of such messages when they are
+			 * not filtered out and none when they are filtered.
+			 *
+			 * A list of error source/type combinations with their assumed behaviour and
+			 * the rationale for expecting such behaviour follows
+			 *
+			 * For API/shader messages we assume that the following types are deterministic:
+			 *   DEBUG_TYPE_ERROR                 Errors specified by spec and should always be produced
+			 *
+			 * For API messages the following types are assumed to be non-deterministic
+			 * and treated as quality warnings since the underlying reported issue does not change between calls:
+			 *   DEBUG_TYPE_DEPRECATED_BEHAVIOR   Reasonable to only report first instance
+             *   DEBUG_TYPE_UNDEFINED_BEHAVIOR    Reasonable to only report first instance
+             *   DEBUG_TYPE_PORTABILITY           Reasonable to only report first instance
+			 *
+			 * For API messages the following types are assumed to be non-deterministic
+			 * and do not affect test results.
+			 *   DEBUG_TYPE_PERFORMANCE           May be tied to arbitrary factors, reasonable to report only first instance
+             *   DEBUG_TYPE_OTHER                 Definition allows arbitrary contents
+			 *
+			 * For 3rd party and application messages the following types are deterministic:
+             *   DEBUG_TYPE_MARKER                Only generated by test
+			 *   DEBUG_TYPE_PUSH_GROUP            Only generated by test
+			 *   DEBUG_TYPE_POP_GROUP             Only generated by test
+			 *   All others                       Only generated by test
+			 *
+			 * All messages with category of window system or other are treated as non-deterministic
+			 * and do not effect test results since they can be assumed to be outside control of
+			 * both the implementation and test case
+			 *
+			 */
+
+			const bool isDeterministic	= id.source == GL_DEBUG_SOURCE_APPLICATION ||
+										  id.source == GL_DEBUG_SOURCE_THIRD_PARTY ||
+										  ((id.source == GL_DEBUG_SOURCE_API || id.source == GL_DEBUG_SOURCE_SHADER_COMPILER) && id.type == GL_DEBUG_TYPE_ERROR);
+
+			const bool canIgnore		= id.source == GL_DEBUG_SOURCE_WINDOW_SYSTEM || id.source == GL_DEBUG_SOURCE_OTHER;
+
+			if (isDeterministic)
+			{
+				if (resCount > refCount)
+				{
+					log << "Extra instances of message were found: (" << id << ") with "
+						<< glu::getDebugMessageSeverityStr(severity)
+						<< " (got " << resCount << ", expected " << refCount << ")";
+					return VerificationResult(QP_TEST_RESULT_FAIL, "Extra instances of a deterministic message were present", log.str());
+				}
+				else
+				{
+					log << "Instances of message were missing: (" << id << ") with "
+						<< glu::getDebugMessageSeverityStr(severity)
+						<< " (got " << resCount << ", expected " << refCount << ")";
+					return VerificationResult(QP_TEST_RESULT_FAIL, "Message missing", log.str());
+				}
+			}
+			else if(!canIgnore)
+			{
+				if (resCount > refCount)
+				{
+					log << "Extra instances of message were found but the message is non-deterministic(warning): (" << id << ") with "
+						<< glu::getDebugMessageSeverityStr(severity)
+						<< " (got " << resCount << ", expected " << refCount << ")";
+					return VerificationResult(QP_TEST_RESULT_QUALITY_WARNING, "Extra instances of a message were present", log.str());
+				}
+				else
+				{
+					log << "Instances of message were missing but the message is non-deterministic(warning): (" << id << ") with "
+						<< glu::getDebugMessageSeverityStr(severity)
+						<< " (got " << resCount << ", expected " << refCount << ")";
+					return VerificationResult(QP_TEST_RESULT_QUALITY_WARNING, "Message missing", log.str());
+				}
+			}
+			else
+			{
+				if (resCount > refCount)
+				{
+					log << "Extra instances of message were found but the message is non-deterministic(ignored): (" << id << ") with "
+						<< glu::getDebugMessageSeverityStr(severity)
+						<< " (got " << resCount << ", expected " << refCount << ")";
+					return VerificationResult(QP_TEST_RESULT_PASS, "", log.str());
+				}
+				else
+				{
+					log << "Instances of message were missing but the message is non-deterministic(ignored): (" << id << ") with "
+						<< glu::getDebugMessageSeverityStr(severity)
+						<< " (got " << resCount << ", expected " << refCount << ")";
+					return VerificationResult(QP_TEST_RESULT_PASS, "", log.str());
+				}
+			}
+		}
+		else // Passed as appropriate
+		{
+			log << "Message was found when expected: ("<< id << ") with "
+				<< glu::getDebugMessageSeverityStr(severity);
+			return VerificationResult(QP_TEST_RESULT_PASS, "", log.str());
+		}
+	}
+	// Message should be filtered out
+	else
+	{
+		// Filtered out
+		if (resCount == 0)
+		{
+			log << "Message was excluded correctly:  (" << id << ") with "
+				<< glu::getDebugMessageSeverityStr(severity);
+			return VerificationResult(QP_TEST_RESULT_PASS, "", log.str());
+		}
+		// Only present in filtered run (ERROR)
+		else if (resCount > 0 && refCount == 0)
+		{
+			log << "A message was not excluded as it should have been: (" << id << ") with "
+				<< glu::getDebugMessageSeverityStr(severity)
+				<< ". This message was not present in the reference run";
+			return VerificationResult(QP_TEST_RESULT_FAIL, "A message was not filtered out", log.str());
+		}
+		// Present in both runs (ERROR)
+		else
+		{
+			log << "A message was not excluded as it should have been: (" << id << ") with "
+				<< glu::getDebugMessageSeverityStr(severity);
+			return VerificationResult(QP_TEST_RESULT_FAIL, "A message was not filtered out", log.str());
+		}
+	}
+}
+
+// Return true if message needs further verification
+bool BaseCase::verifyMessageExists (const MessageData& message, GLenum source, GLenum type)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (source == GL_DONT_CARE || type == GL_DONT_CARE)
+		return false;
+	else if (message.id.source == GL_NONE || message.id.type == GL_NONE)
+	{
+		if (isDebugContext())
+		{
+			m_results.addResult(QP_TEST_RESULT_FAIL, "Message was not reported as expected");
+			log << TestLog::Message << "A message was expected but none was reported" << TestLog::EndMessage;
+		}
+		else
+		{
+			m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Verification accuracy is lacking without a debug context");
+			log << TestLog::Message << "A message was expected but none was reported. Running without a debug context" << TestLog::EndMessage;
+		}
+		return false;
+	}
+	else
+		return true;
+}
+
+void BaseCase::verifyMessageGroup (const MessageData& message, GLenum source, GLenum type)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (message.id.source != source)
+	{
+		m_results.addResult(QP_TEST_RESULT_FAIL, "Incorrect message source");
+		log << TestLog::Message << "Message source was " << glu::getDebugMessageSourceStr(message.id.source)
+			<< " when it should have been "  << glu::getDebugMessageSourceStr(source) << TestLog::EndMessage;
+	}
+
+	if (message.id.type != type)
+	{
+		m_results.addResult(QP_TEST_RESULT_FAIL, "Incorrect message type");
+		log << TestLog::Message << "Message type was " << glu::getDebugMessageTypeStr(message.id.type)
+			<< " when it should have been " << glu::getDebugMessageTypeStr(type) << TestLog::EndMessage;
+	}
+}
+
+void BaseCase::verifyMessageString (const MessageData& message)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	log << TestLog::Message << "Driver says: \"" << message.message << "\"" << TestLog::EndMessage;
+
+	if (message.message.empty())
+	{
+		m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Empty message");
+		log << TestLog::Message << "Message message was empty" << TestLog::EndMessage;
+	}
+}
+
+void BaseCase::verifyMessage (const MessageData& message, GLenum source, GLenum type)
+{
+	if (verifyMessageExists(message, source, type))
+	{
+		verifyMessageString(message);
+		verifyMessageGroup(message, source, type);
+	}
+}
+
+void BaseCase::verifyMessage (const MessageData& message, GLenum source, GLenum type, GLuint id, GLenum severity)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (verifyMessageExists(message, source, type))
+	{
+		verifyMessageString(message);
+		verifyMessageGroup(message, source, type);
+
+		if (message.id.id != id)
+		{
+			m_results.addResult(QP_TEST_RESULT_FAIL, "Incorrect message id");
+			log << TestLog::Message << "Message id was " << message.id.id
+				<< " when it should have been " << id << TestLog::EndMessage;
+		}
+
+		if (message.severity != severity)
+		{
+			m_results.addResult(QP_TEST_RESULT_FAIL, "Incorrect message severity");
+			log << TestLog::Message << "Message severity was " << glu::getDebugMessageSeverityStr(message.severity)
+				<< " when it should have been " << glu::getDebugMessageSeverityStr(severity) << TestLog::EndMessage;
+		}
+	}
+}
+
+bool BaseCase::isDebugContext (void) const
+{
+	return (m_context.getRenderContext().getType().getFlags() & glu::CONTEXT_DEBUG) != 0;
+}
+
+// Generate errors, verify that each error results in a callback call
+class CallbackErrorCase : public BaseCase
+{
+public:
+							CallbackErrorCase	(Context&				ctx,
+												 const char*			name,
+												 const char*			desc,
+												 TestFunc				errorFunc);
+	virtual 				~CallbackErrorCase	(void) {}
+
+	virtual IterateResult	iterate				(void);
+
+	virtual void			expectMessage		(GLenum source, GLenum type);
+
+private:
+	virtual void			callback			(GLenum source, GLenum type, GLuint id, GLenum severity, const string& message);
+
+	const TestFunc			m_errorFunc;
+	MessageData				m_lastMessage;
+};
+
+CallbackErrorCase::CallbackErrorCase (Context&				ctx,
+									  const char*			name,
+									  const char*			desc,
+									  TestFunc				errorFunc)
+	: BaseCase		(ctx, name, desc)
+	, m_errorFunc	(errorFunc)
+{
+}
+
+CallbackErrorCase::IterateResult CallbackErrorCase::iterate (void)
+{
+	TCU_CHECK_AND_THROW(NotSupportedError, m_context.getContextInfo().isExtensionSupported("GL_KHR_debug"), "GL_KHR_debug is not supported");
+
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	tcu::TestLog&			log		= m_testCtx.getLog();
+	NegativeTestContext		context	= NegativeTestContext(*this, m_context.getRenderContext(), log, m_results, true);
+
+	gl.enable(GL_DEBUG_OUTPUT);
+	gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+	gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, false); // disable all
+	gl.debugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, DE_NULL, true); // enable API errors
+	gl.debugMessageControl(GL_DEBUG_SOURCE_APPLICATION, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, true); // enable application messages
+	gl.debugMessageControl(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, true); // enable third party messages
+	gl.debugMessageCallback(callbackHandle, this);
+
+	m_errorFunc(context);
+
+	gl.debugMessageCallback(DE_NULL, DE_NULL);
+	gl.disable(GL_DEBUG_OUTPUT);
+
+	m_results.setTestContextResult(m_testCtx);
+
+	return STOP;
+}
+
+void CallbackErrorCase::expectMessage (GLenum source, GLenum type)
+{
+	verifyMessage(m_lastMessage, source, type);
+	m_lastMessage = MessageData();
+}
+
+void CallbackErrorCase::callback (GLenum source, GLenum type, GLuint id, GLenum severity, const string& message)
+{
+	m_lastMessage = MessageData(MessageID(source, type, id), severity, message);
+}
+
+// Generate errors, verify that each error results in a log entry
+class LogErrorCase : public BaseCase
+{
+public:
+							LogErrorCase	(Context&				context,
+											 const char*			name,
+											 const char*			desc,
+											 TestFunc				errorFunc);
+	virtual 				~LogErrorCase	(void) {}
+
+	virtual IterateResult	iterate			(void);
+
+	virtual void			expectMessage	(GLenum source, GLenum type);
+
+private:
+	const TestFunc			m_errorFunc;
+	MessageData				m_lastMessage;
+};
+
+LogErrorCase::LogErrorCase (Context&	ctx,
+							const char*	name,
+							const char*	desc,
+							TestFunc	errorFunc)
+	: BaseCase		(ctx, name, desc)
+	, m_errorFunc	(errorFunc)
+{
+}
+
+LogErrorCase::IterateResult LogErrorCase::iterate (void)
+{
+	TCU_CHECK_AND_THROW(NotSupportedError, m_context.getContextInfo().isExtensionSupported("GL_KHR_debug"), "GL_KHR_debug is not supported");
+
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	tcu::TestLog&			log		= m_testCtx.getLog();
+	NegativeTestContext		context	= NegativeTestContext(*this, m_context.getRenderContext(), log, m_results, true);
+	GLint					numMsg	= 0;
+
+	gl.enable(GL_DEBUG_OUTPUT);
+	gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+	gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, false); // disable all
+	gl.debugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, DE_NULL, true); // enable API errors
+	gl.debugMessageCallback(DE_NULL, DE_NULL); // enable logging
+	gl.getIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMsg);
+	gl.getDebugMessageLog(numMsg, 0, DE_NULL, DE_NULL, DE_NULL, DE_NULL, DE_NULL, DE_NULL); // clear log
+
+	m_errorFunc(context);
+
+	gl.disable(GL_DEBUG_OUTPUT);
+	m_results.setTestContextResult(m_testCtx);
+
+	return STOP;
+}
+
+void LogErrorCase::expectMessage (GLenum source, GLenum type)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	int						numMsg		= 0;
+	TestLog&				log			= m_testCtx.getLog();
+	MessageData				lastMsg;
+
+	if (source == GL_DONT_CARE || type == GL_DONT_CARE)
+		return;
+
+	gl.getIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMsg);
+
+	if (numMsg == 0)
+	{
+		if (isDebugContext())
+		{
+			m_results.addResult(QP_TEST_RESULT_FAIL, "Error was not reported as expected");
+			log << TestLog::Message << "A message was expected but none was reported (empty message log)" << TestLog::EndMessage;
+		}
+		else
+		{
+			m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Verification accuracy is lacking without a debug context");
+			log << TestLog::Message << "A message was expected but none was reported (empty message log). Running without a debug context" << TestLog::EndMessage;
+		}
+		return;
+	}
+
+	// There may be messages other than the error we are looking for in the log.
+	// Strictly nothing prevents the implementation from producing more than the
+	// required error from an API call with a defined error. however we assume that
+	// since calls that produce an error should not change GL state the implementation
+	// should have nothing else to report.
+	if (numMsg > 1)
+		gl.getDebugMessageLog(numMsg-1, 0, DE_NULL, DE_NULL, DE_NULL, DE_NULL, DE_NULL, DE_NULL); // Clear all but last
+
+	{
+		int  msgLen = 0;
+		gl.getIntegerv(GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH, &msgLen);
+
+		TCU_CHECK_MSG(msgLen >= 0, "Negative message length");
+		TCU_CHECK_MSG(msgLen < 100000, "Excessively long message");
+
+		lastMsg.message.resize(msgLen);
+		gl.getDebugMessageLog(1, msgLen, &lastMsg.id.source, &lastMsg.id.type, &lastMsg.id.id, &lastMsg.severity, &msgLen, &lastMsg.message[0]);
+	}
+
+	log << TestLog::Message << "Driver says: \"" << lastMsg.message << "\"" << TestLog::EndMessage;
+
+	verifyMessage(lastMsg, source, type);
+}
+
+// Generate errors, verify that calling glGetError afterwards produces desired result
+class GetErrorCase : public BaseCase
+{
+public:
+							GetErrorCase	(Context&				ctx,
+											 const char*			name,
+											 const char*			desc,
+											 TestFunc				errorFunc);
+	virtual 				~GetErrorCase	(void) {}
+
+	virtual IterateResult	iterate			(void);
+
+	virtual void			expectMessage	(GLenum source, GLenum type);
+	virtual void			expectError		(glw::GLenum error0, glw::GLenum error1);
+
+private:
+	const TestFunc			m_errorFunc;
+};
+
+GetErrorCase::GetErrorCase (Context&	ctx,
+							const char*	name,
+							const char*	desc,
+							TestFunc	errorFunc)
+	: BaseCase		(ctx, name, desc)
+	, m_errorFunc	(errorFunc)
+{
+}
+
+GetErrorCase::IterateResult GetErrorCase::iterate (void)
+{
+	tcu::TestLog&			log		= m_testCtx.getLog();
+	NegativeTestContext		context	= NegativeTestContext(*this, m_context.getRenderContext(), log, m_results, true);
+
+	m_errorFunc(context);
+
+	m_results.setTestContextResult(m_testCtx);
+
+	return STOP;
+}
+
+void GetErrorCase::expectMessage (GLenum source, GLenum type)
+{
+	DE_UNREF(source);
+	DE_UNREF(type);
+	DE_ASSERT(!"GetErrorCase cannot handle anything other than error codes");
+}
+
+void GetErrorCase::expectError (glw::GLenum error0, glw::GLenum error1)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	TestLog&				log		= m_testCtx.getLog();
+
+	const GLenum			result	= gl.getError();
+
+	if (result != error0 && result != error1)
+	{
+		m_results.addResult(QP_TEST_RESULT_FAIL, "Incorrect error was reported");
+		if (error0 == error1)
+			log << TestLog::Message
+				<< glu::getErrorStr(error0) << " was expected but got "
+				<< glu::getErrorStr(result)
+				<< TestLog::EndMessage;
+		else
+			log << TestLog::Message
+				<< glu::getErrorStr(error0) << " or "
+				<< glu::getErrorStr(error1) << " was expected but got "
+				<< glu::getErrorStr(result)
+				<< TestLog::EndMessage;
+		return;
+	}
+}
+
+// Generate errors, log the types, disable some, regenerate errors, verify correct errors (not)reported
+class FilterCase : public BaseCase
+{
+public:
+								FilterCase		(Context&				ctx,
+												 const char*			name,
+												 const char*			desc,
+												 const vector<TestFunc>	errorFuncs);
+	virtual 					~FilterCase		(void) {}
+
+	virtual IterateResult		iterate			(void);
+
+	virtual void				expectMessage	(GLenum source, GLenum type);
+
+protected:
+	struct MessageFilter
+	{
+		MessageFilter() : source(GL_DONT_CARE), type(GL_DONT_CARE), severity(GL_DONT_CARE), enabled(true) {} // Default to enable all
+		MessageFilter(GLenum source_, GLenum type_, GLenum severity_, const vector<GLuint>& ids_, bool enabled_) : source(source_), type(type_), severity(severity_), ids(ids_), enabled(enabled_) {}
+
+		GLenum			source;
+		GLenum			type;
+		GLenum			severity;
+		vector<GLuint>	ids;
+		bool			enabled;
+	};
+
+	virtual void				callback			(GLenum source, GLenum type, GLuint id, GLenum severity, const string& message);
+
+	vector<MessageData>			genMessages			(bool uselog, const string& desc);
+
+	vector<MessageFilter>		genFilters			(const vector<MessageData>& messages, const vector<MessageFilter>& initial, deUint32 seed, int iterations) const;
+	void						applyFilters		(const vector<MessageFilter>& filters) const;
+	bool						isEnabled			(const vector<MessageFilter>& filters, const MessageData& message) const;
+
+	void						verify				(const vector<MessageData>&		refMessages,
+													 const vector<MessageData>&		filteredMessages,
+													 const vector<MessageFilter>&	filters);
+
+	const vector<TestFunc>		m_errorFuncs;
+
+	vector<MessageData>*		m_currentErrors;
+};
+
+FilterCase::FilterCase (Context&				ctx,
+						const char*				name,
+						const char*				desc,
+						const vector<TestFunc>	errorFuncs)
+	: BaseCase			(ctx, name, desc)
+	, m_errorFuncs		(errorFuncs)
+	, m_currentErrors	(DE_NULL)
+{
+}
+
+FilterCase::IterateResult FilterCase::iterate (void)
+{
+	TCU_CHECK_AND_THROW(NotSupportedError, m_context.getContextInfo().isExtensionSupported("GL_KHR_debug"), "GL_KHR_debug is not supported");
+
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.enable(GL_DEBUG_OUTPUT);
+	gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+	gl.debugMessageCallback(callbackHandle, this);
+	gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, true);
+
+	{
+		const vector<MessageData>	refMessages		= genMessages(true, "Reference run");
+		const MessageFilter			baseFilter		(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, vector<GLuint>(), true);
+		const deUint32				baseSeed		= deStringHash(getName()) ^ m_testCtx.getCommandLine().getBaseSeed();
+		const vector<MessageFilter>	filters			= genFilters(refMessages, vector<MessageFilter>(1, baseFilter), baseSeed, 4);
+		vector<MessageData>			filteredMessages;
+
+		applyFilters(filters);
+
+		// Generate errors
+		filteredMessages = genMessages(false, "Filtered run");
+
+		// Verify
+		verify(refMessages, filteredMessages, filters);
+
+		if (!isDebugContext() && refMessages.empty())
+			m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Verification accuracy is lacking without a debug context");
+	}
+
+	gl.disable(GL_DEBUG_OUTPUT);
+	m_results.setTestContextResult(m_testCtx);
+
+	return STOP;
+}
+
+void FilterCase::expectMessage (GLenum source, GLenum type)
+{
+	DE_UNREF(source);
+	DE_UNREF(type);
+}
+
+void FilterCase::callback (GLenum source, GLenum type, GLuint id, GLenum severity, const string& message)
+{
+	if (m_currentErrors)
+		m_currentErrors->push_back(MessageData(MessageID(source, type, id), severity, message));
+}
+
+vector<MessageData> FilterCase::genMessages (bool uselog, const string& desc)
+{
+	tcu::TestLog&			log			= m_testCtx.getLog();
+	NegativeTestContext		context		= NegativeTestContext(*this, m_context.getRenderContext(), log, m_results, uselog);
+	tcu::ScopedLogSection	section		(log, "message gen", desc);
+	vector<MessageData>		messages;
+
+	m_currentErrors = &messages;
+
+	for (int ndx = 0; ndx < int(m_errorFuncs.size()); ndx++)
+		m_errorFuncs[ndx](context);
+
+	m_currentErrors = DE_NULL;
+
+	return messages;
+}
+
+vector<FilterCase::MessageFilter> FilterCase::genFilters (const vector<MessageData>& messages, const vector<MessageFilter>& initial, deUint32 seed, int iterations) const
+{
+	de::Random				rng				(seed ^ deInt32Hash(deStringHash(getName())));
+
+	set<MessageID>			tempMessageIds;
+	set<GLenum>				tempSources;
+	set<GLenum>				tempTypes;
+	set<GLenum>				tempSeverities;
+
+	if (messages.empty())
+		return initial;
+
+	for (int ndx = 0; ndx < int(messages.size()); ndx++)
+	{
+		const MessageData& msg = messages[ndx];
+
+		tempMessageIds.insert(msg.id);
+		tempSources.insert(msg.id.source);
+		tempTypes.insert(msg.id.type);
+		tempSeverities.insert(msg.severity);
+	}
+
+	{
+		// Fetchable by index
+		const vector<MessageID> messageIds	(tempMessageIds.begin(), tempMessageIds.end());
+		const vector<GLenum>	sources		(tempSources.begin(), tempSources.end());
+		const vector<GLenum>	types		(tempTypes.begin(), tempTypes.end());
+		const vector<GLenum>	severities	(tempSeverities.begin(), tempSeverities.end());
+
+		vector<MessageFilter>	filters		= initial;
+
+		for (int iteration = 0; iteration < iterations; iteration++)
+		{
+			switch(rng.getInt(0, 8)) // Distribute so that per-message randomization (the default branch) is prevalent
+			{
+				case 0:
+				{
+					const GLenum	source	= sources[rng.getInt(0, int(sources.size()-1))];
+					const bool		enabled	= rng.getBool();
+
+					filters.push_back(MessageFilter(source, GL_DONT_CARE, GL_DONT_CARE, vector<GLuint>(), enabled));
+					break;
+				}
+
+				case 1:
+				{
+					const GLenum	type	= types[rng.getUint32()%types.size()];
+					const bool		enabled	= rng.getBool();
+
+					filters.push_back(MessageFilter(GL_DONT_CARE, type, GL_DONT_CARE, vector<GLuint>(), enabled));
+					break;
+				}
+
+				case 2:
+				{
+					const GLenum	severity	= severities[rng.getUint32()%severities.size()];
+					const bool		enabled		= rng.getBool();
+
+					filters.push_back(MessageFilter(GL_DONT_CARE, GL_DONT_CARE, severity, vector<GLuint>(), enabled));
+					break;
+				}
+
+				default:
+				{
+					const int start = rng.getInt(0, int(messageIds.size()));
+
+					for (int itr = 0; itr < 4; itr++)
+					{
+						const MessageID&	id		= messageIds[(start+itr)%messageIds.size()];
+						const bool			enabled = rng.getBool();
+
+						filters.push_back(MessageFilter(id.source, id.type, GL_DONT_CARE, vector<GLuint>(1, id.id), enabled));
+					}
+				}
+			}
+		}
+
+		return filters;
+	}
+}
+
+void FilterCase::applyFilters (const vector<MessageFilter>& filters) const
+{
+	TestLog&					log		= m_testCtx.getLog();
+	const tcu::ScopedLogSection	section	(log, "", "Setting message filters");
+	const glw::Functions&		gl		= m_context.getRenderContext().getFunctions();
+
+	for (size_t filterNdx = 0; filterNdx < filters.size(); filterNdx++)
+	{
+		const MessageFilter& filter = filters[filterNdx];
+
+		if (filter.ids.empty())
+			log << TestLog::Message << "Setting messages with"
+				<< " source " << glu::getDebugMessageSourceStr(filter.source)
+				<< ", type " << glu::getDebugMessageTypeStr(filter.type)
+				<< " and severity " << glu::getDebugMessageSeverityStr(filter.severity)
+				<< (filter.enabled ? " to enabled" : " to disabled")
+				<< TestLog::EndMessage;
+		else
+		{
+			for (size_t ndx = 0; ndx < filter.ids.size(); ndx++)
+				log << TestLog::Message << "Setting message (" << MessageID(filter.source, filter.type, filter.ids[ndx]) << ") to " << (filter.enabled ? "enabled" : "disabled") << TestLog::EndMessage;
+		}
+
+		gl.debugMessageControl(filter.source, filter.type, filter.severity, GLsizei(filter.ids.size()), filter.ids.empty() ? DE_NULL : &filter.ids[0], filter.enabled);
+	}
+}
+
+bool FilterCase::isEnabled (const vector<MessageFilter>& filters, const MessageData& message) const
+{
+	bool retval = true;
+
+	for (size_t filterNdx = 0; filterNdx < filters.size(); filterNdx++)
+	{
+		const MessageFilter&	filter	= filters[filterNdx];
+
+		if (filter.ids.empty())
+		{
+			if (filter.source != GL_DONT_CARE && filter.source != message.id.source)
+				continue;
+
+			if (filter.type != GL_DONT_CARE && filter.type != message.id.type)
+				continue;
+
+			if (filter.severity != GL_DONT_CARE && filter.severity != message.severity)
+				continue;
+		}
+		else
+		{
+			DE_ASSERT(filter.source != GL_DONT_CARE);
+			DE_ASSERT(filter.type != GL_DONT_CARE);
+			DE_ASSERT(filter.severity == GL_DONT_CARE);
+
+			if (filter.source != message.id.source || filter.type != message.id.type)
+				continue;
+
+			if (!de::contains(filter.ids.begin(), filter.ids.end(), message.id.id))
+				continue;
+		}
+
+		retval = filter.enabled;
+	}
+
+	return retval;
+}
+
+struct MessageMeta
+{
+	int		refCount;
+	int		resCount;
+	GLenum	severity;
+
+	MessageMeta (void) : refCount(0), resCount(0), severity(GL_NONE) {}
+};
+
+void FilterCase::verify (const vector<MessageData>& refMessages, const vector<MessageData>& resMessages, const vector<MessageFilter>& filters)
+{
+	TestLog&						log		= m_testCtx.getLog();
+	map<MessageID, MessageMeta>		counts;
+
+	log << TestLog::Section("verification", "Verifying");
+
+	// Gather message counts & severities, report severity mismatches if found
+	for (size_t refNdx = 0; refNdx < refMessages.size(); refNdx++)
+	{
+		const MessageData&	msg  = refMessages[refNdx];
+		MessageMeta&		meta = counts[msg.id];
+
+		if (meta.severity != GL_NONE && meta.severity != msg.severity)
+		{
+			log << TestLog::Message << "A message has variable severity between instances: (" << msg.id << ") with severity "
+				<< glu::getDebugMessageSeverityStr(meta.severity) << " and " << glu::getDebugMessageSeverityStr(msg.severity) << TestLog::EndMessage;
+			m_results.addResult(QP_TEST_RESULT_FAIL, "Message severity changed between instances of the same message");
+		}
+
+		meta.refCount++;
+		meta.severity = msg.severity;
+	}
+
+	for (size_t resNdx = 0; resNdx < resMessages.size(); resNdx++)
+	{
+		const MessageData&	msg  = resMessages[resNdx];
+		MessageMeta&		meta = counts[msg.id];
+
+		if (meta.severity != GL_NONE && meta.severity != msg.severity)
+		{
+			log << TestLog::Message << "A message has variable severity between instances: (" << msg.id << ") with severity "
+				<< glu::getDebugMessageSeverityStr(meta.severity) << " and " << glu::getDebugMessageSeverityStr(msg.severity) << TestLog::EndMessage;
+			m_results.addResult(QP_TEST_RESULT_FAIL, "Message severity changed between instances of the same message");
+		}
+
+		meta.resCount++;
+		meta.severity = msg.severity;
+	}
+
+	for (map<MessageID, MessageMeta>::const_iterator itr = counts.begin(); itr != counts.end(); itr++)
+	{
+		const MessageID&	id			= itr->first;
+		const GLenum		severity	= itr->second.severity;
+
+		const int			refCount	= itr->second.refCount;
+		const int			resCount	= itr->second.resCount;
+		const bool			enabled		= isEnabled(filters, MessageData(id, severity, ""));
+
+		VerificationResult	result		= verifyMessageCount(id, severity, refCount, resCount, enabled);
+
+		log << TestLog::Message << result.logMessage << TestLog::EndMessage;
+
+		if (result.result != QP_TEST_RESULT_PASS)
+			m_results.addResult(result.result, result.resultMessage);
+	}
+
+	log << TestLog::EndSection;
+}
+
+// Filter case that uses debug groups
+class GroupFilterCase : public FilterCase
+{
+public:
+							GroupFilterCase		(Context&				ctx,
+												 const char*			name,
+												 const char*			desc,
+												 const vector<TestFunc>	errorFuncs);
+	virtual 				~GroupFilterCase	(void) {}
+
+	virtual IterateResult	iterate				(void);
+};
+
+GroupFilterCase::GroupFilterCase (Context&					ctx,
+								  const char*				name,
+								  const char*				desc,
+								  const vector<TestFunc>	errorFuncs)
+	: FilterCase(ctx, name, desc, errorFuncs)
+{
+}
+
+template<typename T>
+vector<T> join(const vector<T>& a, const vector<T>&b)
+{
+	vector<T> retval;
+
+	retval.reserve(a.size()+b.size());
+	retval.insert(retval.end(), a.begin(), a.end());
+	retval.insert(retval.end(), b.begin(), b.end());
+	return retval;
+}
+
+GroupFilterCase::IterateResult GroupFilterCase::iterate (void)
+{
+	TCU_CHECK_AND_THROW(NotSupportedError, m_context.getContextInfo().isExtensionSupported("GL_KHR_debug"), "GL_KHR_debug is not supported");
+
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	tcu::TestLog&			log			= m_testCtx.getLog();
+
+	gl.enable(GL_DEBUG_OUTPUT);
+	gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+	gl.debugMessageCallback(callbackHandle, this);
+	gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, true);
+
+	{
+
+		// Generate reference (all errors)
+		const vector<MessageData>	refMessages	 = genMessages(true, "Reference run");
+		const deUint32				baseSeed	 = deStringHash(getName()) ^ m_testCtx.getCommandLine().getBaseSeed();
+		const MessageFilter			baseFilter	 (GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, vector<GLuint>(), true);
+		const vector<MessageFilter>	filter0		 = genFilters(refMessages, vector<MessageFilter>(1, baseFilter), baseSeed, 4);
+		vector<MessageData>			resMessages0;
+
+		applyFilters(filter0);
+
+		resMessages0 = genMessages(false, "Filtered run, default debug group");
+
+		// Initial verification
+		verify(refMessages, resMessages0, filter0);
+
+		{
+			// Generate reference (filters inherited from parent)
+			const vector<MessageFilter> filter1base		= genFilters(refMessages, vector<MessageFilter>(), baseSeed ^ 0xDEADBEEF, 4);
+			const vector<MessageFilter>	filter1full		= join(filter0, filter1base);
+			tcu::ScopedLogSection		section1		(log, "", "Pushing Debug Group");
+			vector<MessageData>			resMessages1;
+
+			gl.pushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Test Group");
+			applyFilters(filter1base);
+
+			// First nested verification
+			resMessages1 = genMessages(false, "Filtered run, pushed one debug group");
+			verify(refMessages, resMessages1, filter1full);
+
+			{
+				// Generate reference (filters iherited again)
+				const vector<MessageFilter>	filter2base		= genFilters(refMessages, vector<MessageFilter>(), baseSeed ^ 0x43211234, 4);
+				const vector<MessageFilter>	filter2full		= join(filter1full, filter2base);
+				tcu::ScopedLogSection		section2		(log, "", "Pushing Debug Group");
+				vector<MessageData>			resMessages2;
+
+				gl.pushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Nested Test Group");
+				applyFilters(filter2base);
+
+				// Second nested verification
+				resMessages2 = genMessages(false, "Filtered run, pushed two debug groups");
+				verify(refMessages, resMessages2, filter2full);
+
+				gl.popDebugGroup();
+			}
+
+			// First restore verification
+			resMessages1 = genMessages(false, "Filtered run, popped second debug group");
+			verify(refMessages, resMessages1, filter1full);
+
+			gl.popDebugGroup();
+		}
+
+		// restore verification
+		resMessages0 = genMessages(false, "Filtered run, popped first debug group");
+		verify(refMessages, resMessages0, filter0);
+
+		if (!isDebugContext() && refMessages.empty())
+			m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Verification accuracy is lacking without a debug context");
+	}
+
+	gl.disable(GL_DEBUG_OUTPUT);
+	m_results.setTestContextResult(m_testCtx);
+	return STOP;
+}
+
+// Basic grouping functionality
+class GroupCase : public BaseCase
+{
+public:
+							GroupCase	(Context&				ctx,
+										 const char*			name,
+										 const char*			desc);
+	virtual					~GroupCase	() {}
+
+	virtual IterateResult	iterate		(void);
+
+private:
+	virtual void			callback	(GLenum source, GLenum type, GLuint id, GLenum severity, const string& message);
+
+	MessageData				m_lastMessage;
+};
+
+GroupCase::GroupCase (Context&				ctx,
+					  const char*			name,
+					  const char*			desc)
+	: BaseCase(ctx, name, desc)
+{
+}
+
+GroupCase::IterateResult GroupCase::iterate (void)
+{
+	TCU_CHECK_AND_THROW(NotSupportedError, m_context.getContextInfo().isExtensionSupported("GL_KHR_debug"), "GL_KHR_debug is not supported");
+
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	tcu::TestLog&			log		= m_testCtx.getLog();
+	glu::CallLogWrapper		wrapper	(gl, log);
+
+	gl.enable(GL_DEBUG_OUTPUT);
+	gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+	gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, false); // disable all
+	gl.debugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, DE_NULL, true); // enable API errors
+	gl.debugMessageControl(GL_DEBUG_SOURCE_APPLICATION, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, true); // enable application messages
+	gl.debugMessageControl(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, true); // enable third party messages
+	gl.debugMessageCallback(callbackHandle, this);
+
+	wrapper.enableLogging(true);
+	wrapper.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1234, -1, "Pushed debug stack");
+	verifyMessage(m_lastMessage, GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP, 1234, GL_DEBUG_SEVERITY_NOTIFICATION);
+	wrapper.glPopDebugGroup();
+	verifyMessage(m_lastMessage, GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP, 1234, GL_DEBUG_SEVERITY_NOTIFICATION);
+
+	wrapper.glPushDebugGroup(GL_DEBUG_SOURCE_THIRD_PARTY, 4231, -1, "Pushed debug stack");
+	verifyMessage(m_lastMessage, GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_PUSH_GROUP, 4231, GL_DEBUG_SEVERITY_NOTIFICATION);
+	wrapper.glPopDebugGroup();
+	verifyMessage(m_lastMessage, GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_POP_GROUP, 4231, GL_DEBUG_SEVERITY_NOTIFICATION);
+
+	gl.debugMessageCallback(DE_NULL, DE_NULL);
+	gl.disable(GL_DEBUG_OUTPUT);
+
+	m_results.setTestContextResult(m_testCtx);
+
+	return STOP;
+}
+
+void GroupCase::callback (GLenum source, GLenum type, GLuint id, GLenum severity, const string& message)
+{
+	m_lastMessage = MessageData(MessageID(source, type, id), severity, message);
+}
+
+// Asynchronous debug output
+class AsyncCase : public BaseCase
+{
+public:
+							AsyncCase			(Context&				ctx,
+												 const char*			name,
+												 const char*			desc,
+												 const vector<TestFunc>	errorFuncs,
+												 bool					useCallbacks);
+	virtual					~AsyncCase			() {}
+
+	virtual IterateResult	iterate				(void);
+
+	virtual void			expectMessage		(glw::GLenum source, glw::GLenum type);
+
+private:
+	struct MessageCount
+	{
+		int received;
+		int expected;
+
+		MessageCount(void) : received(0), expected(0) {}
+	};
+	typedef map<MessageID, MessageCount> MessageCounter;
+
+	enum VerifyState
+	{
+		VERIFY_PASS = 0,
+		VERIFY_MINIMUM,
+		VERIFY_FAIL,
+
+		VERIFY_LAST
+	};
+
+	virtual void			callback			(glw::GLenum source, glw::GLenum type, glw::GLuint id, glw::GLenum severity, const std::string& message);
+	VerifyState				verify				(bool uselog);
+	void					fetchLogMessages	(void);
+
+	const vector<TestFunc>	m_errorFuncs;
+	const bool				m_useCallbacks;
+
+	MessageCounter			m_counts;
+
+	de::Mutex				m_mutex;
+};
+
+AsyncCase::AsyncCase (Context&					ctx,
+					  const char*				name,
+					  const char*				desc,
+					  const vector<TestFunc>	errorFuncs,
+					  bool						useCallbacks)
+	: BaseCase			(ctx, name, desc)
+	, m_errorFuncs		(errorFuncs)
+	, m_useCallbacks	(useCallbacks)
+{
+}
+
+AsyncCase::IterateResult AsyncCase::iterate (void)
+{
+	TCU_CHECK_AND_THROW(NotSupportedError, m_context.getContextInfo().isExtensionSupported("GL_KHR_debug"), "GL_KHR_debug is not supported");
+
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+	tcu::TestLog&		log			= m_testCtx.getLog();
+	NegativeTestContext	context		= NegativeTestContext(*this, m_context.getRenderContext(), log, m_results, true);
+	const int			maxWait		= 10000; // ms
+	const int			warnWait	= 100;
+
+	gl.enable(GL_DEBUG_OUTPUT);
+	gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+	gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, false);
+
+	// Some messages could be dependent on the value of DEBUG_OUTPUT_SYNCHRONOUS so only use API errors which should be generated in all cases
+	gl.debugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, DE_NULL, true);
+
+	if (m_useCallbacks) // will use log otherwise
+		gl.debugMessageCallback(callbackHandle, this);
+	else
+		gl.debugMessageCallback(DE_NULL, DE_NULL);
+
+	// Reference run (synchoronous)
+	{
+		tcu::ScopedLogSection section(log, "reference run", "Reference run (synchronous)");
+
+		for (int ndx = 0; ndx < int(m_errorFuncs.size()); ndx++)
+			m_errorFuncs[ndx](context);
+	}
+
+	if (m_counts.empty())
+	{
+		if (!isDebugContext())
+			m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Need debug context to guarantee implementation behaviour (see command line options)");
+
+		log << TestLog::Message << "Reference run produced no messages, nothing to verify" << TestLog::EndMessage;
+
+		gl.debugMessageCallback(DE_NULL, DE_NULL);
+		gl.disable(GL_DEBUG_OUTPUT);
+
+		m_results.setTestContextResult(m_testCtx);
+		return STOP;
+	}
+
+	for (MessageCounter::iterator itr = m_counts.begin(); itr != m_counts.end(); itr++)
+	{
+		itr->second.expected = itr->second.received;
+		itr->second.received = 0;
+	}
+
+	gl.disable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+
+	// Result run (async)
+	for (int ndx = 0; ndx < int(m_errorFuncs.size()); ndx++)
+		m_errorFuncs[ndx](context);
+
+	// Repatedly try verification, new results may be added to m_receivedMessages at any time
+	{
+		tcu::ScopedLogSection	section			(log, "result run", "Result run (asynchronous)");
+		VerifyState				lastTimelyState = VERIFY_FAIL;
+
+		for (int waited = 0;;)
+		{
+			const VerifyState	pass = verify(false);
+			const int			wait = de::max(50, waited>>2);
+
+			// Pass (possibly due to time limit)
+			if (pass == VERIFY_PASS || (pass == VERIFY_MINIMUM && waited >= maxWait))
+			{
+				verify(true); // log
+
+				// State changed late
+				if (waited >= warnWait && lastTimelyState != pass)
+					m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Async messages were returned to application somewhat slowly");
+
+				log << TestLog::Message << "Passed after ~" << waited << "ms of waiting" << TestLog::EndMessage;
+				break;
+			}
+			// fail
+			else if (waited >= maxWait)
+			{
+				verify(true); // log
+
+				log << TestLog::Message << "Waited for ~" << waited << "ms without getting all expected messages" << TestLog::EndMessage;
+				m_results.addResult(QP_TEST_RESULT_FAIL, "Async messages were not returned to application within a reasonable timeframe");
+				break;
+			}
+
+			if (waited < warnWait)
+				lastTimelyState = pass;
+
+			deSleep(wait);
+			waited += wait;
+
+			if (!m_useCallbacks)
+				fetchLogMessages();
+		}
+	}
+
+	gl.debugMessageCallback(DE_NULL, DE_NULL);
+
+	gl.disable(GL_DEBUG_OUTPUT);
+	m_results.setTestContextResult(m_testCtx);
+
+	return STOP;
+}
+
+void AsyncCase::expectMessage (GLenum source, GLenum type)
+{
+	// Good time to clean up the queue as this should be called after most messages are generated
+	if (!m_useCallbacks)
+		fetchLogMessages();
+
+	DE_UNREF(source);
+	DE_UNREF(type);
+}
+
+void AsyncCase::callback (GLenum source, GLenum type, GLuint id, GLenum severity, const string& message)
+{
+	DE_ASSERT(m_useCallbacks);
+	DE_UNREF(severity);
+	DE_UNREF(message);
+
+	de::ScopedLock lock(m_mutex);
+
+	m_counts[MessageID(source, type, id)].received++;
+}
+
+// Note that we can never guarantee getting all messages back when using logs/fetching as the GL may create more than its log size limit during an arbitrary period of time
+void AsyncCase::fetchLogMessages (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	GLint					numMsg	= 0;
+
+	gl.getIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMsg);
+
+	for(int msgNdx = 0; msgNdx < numMsg; msgNdx++)
+	{
+		int			msgLen = 0;
+		MessageData msg;
+
+		gl.getIntegerv(GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH, &msgLen);
+
+		TCU_CHECK_MSG(msgLen >= 0, "Negative message length");
+		TCU_CHECK_MSG(msgLen < 100000, "Excessively long message");
+
+		msg.message.resize(msgLen);
+		gl.getDebugMessageLog(1, msgLen, &msg.id.source, &msg.id.type, &msg.id.id, &msg.severity, &msgLen, &msg.message[0]);
+
+		{
+			const de::ScopedLock lock(m_mutex); // Don't block during API call
+
+			m_counts[MessageID(msg.id)].received++;
+		}
+	}
+}
+
+AsyncCase::VerifyState AsyncCase::verify (bool uselog)
+{
+	using std::map;
+
+	VerifyState			retval		= VERIFY_PASS;
+	TestLog&			log			= m_testCtx.getLog();
+
+	const de::ScopedLock lock(m_mutex);
+
+	for (map<MessageID, MessageCount>::const_iterator itr = m_counts.begin(); itr != m_counts.end(); itr++)
+	{
+		const MessageID&	id			= itr->first;
+
+		const int			refCount	= itr->second.expected;
+		const int			resCount	= itr->second.received;
+		const bool			enabled		= true;
+
+		VerificationResult	result		= verifyMessageCount(id, GL_DONT_CARE, refCount, resCount, enabled);
+
+		if (uselog)
+			log << TestLog::Message << result.logMessage << TestLog::EndMessage;
+
+		if (result.result == QP_TEST_RESULT_FAIL)
+			retval = VERIFY_FAIL;
+		else if (result.result != QP_TEST_RESULT_PASS && retval == VERIFY_PASS)
+			retval = VERIFY_MINIMUM;
+	}
+
+	return retval;
+}
+
+// Tests debug labels
+class LabelCase : public TestCase
+{
+public:
+							LabelCase	(Context&				ctx,
+										 const char*			name,
+										 const char*			desc,
+										 GLenum					identifier);
+	virtual					~LabelCase	(void) {}
+
+	virtual IterateResult	iterate		(void);
+
+private:
+	GLenum					m_identifier;
+};
+
+LabelCase::LabelCase (Context&		ctx,
+					  const char*			name,
+					  const char*			desc,
+					  GLenum				identifier)
+	: TestCase		(ctx, name, desc)
+	, m_identifier	(identifier)
+{
+}
+
+LabelCase::IterateResult LabelCase::iterate (void)
+{
+	TCU_CHECK_AND_THROW(NotSupportedError, m_context.getContextInfo().isExtensionSupported("GL_KHR_debug"), "GL_KHR_debug is not supported");
+
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const char*	const		msg			= "This is a debug label";
+	GLuint					object		= 0;
+	char					buffer[64];
+	int						outlen		= 0;
+
+	switch(m_identifier)
+	{
+		case GL_BUFFER:
+			gl.genBuffers(1, &object);
+			gl.bindBuffer(GL_ARRAY_BUFFER, object);
+			gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+			break;
+
+		case GL_SHADER:
+			object = gl.createShader(GL_FRAGMENT_SHADER);
+			break;
+
+		case GL_PROGRAM:
+			object = gl.createProgram();
+			break;
+
+		case GL_QUERY:
+			gl.genQueries(1, &object);
+			gl.beginQuery(GL_ANY_SAMPLES_PASSED, object); // Create
+			gl.endQuery(GL_ANY_SAMPLES_PASSED); // Cleanup
+			break;
+
+		case GL_PROGRAM_PIPELINE:
+			gl.genProgramPipelines(1, &object);
+			gl.bindProgramPipeline(object); // Create
+			gl.bindProgramPipeline(0); // Cleanup
+			break;
+
+		case GL_TRANSFORM_FEEDBACK:
+			gl.genTransformFeedbacks(1, &object);
+			gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, object);
+			gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+			break;
+
+		case GL_SAMPLER:
+			gl.genSamplers(1, &object);
+			gl.bindSampler(0, object);
+			gl.bindSampler(0, 0);
+			break;
+
+		case GL_TEXTURE:
+			gl.genTextures(1, &object);
+			gl.bindTexture(GL_TEXTURE_2D, object);
+			gl.bindTexture(GL_TEXTURE_2D, 0);
+			break;
+
+		case GL_RENDERBUFFER:
+			gl.genRenderbuffers(1, &object);
+			gl.bindRenderbuffer(GL_RENDERBUFFER, object);
+			gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
+			break;
+
+		case GL_FRAMEBUFFER:
+			gl.genFramebuffers(1, &object);
+			gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, object);
+			gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer());
+			break;
+
+		default:
+			DE_ASSERT(!"Invalid identifier");
+	}
+
+	gl.objectLabel(m_identifier, object, -1, msg);
+
+	gl.getObjectLabel(m_identifier, object, sizeof(buffer), &outlen, buffer);
+
+	if (outlen == 0)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to query debug label from object");
+	else if (deStringEqual(msg, buffer))
+	{
+		m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\"" << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	else
+	{
+		m_testCtx.getLog() << TestLog::Message << "Query returned wrong string: expected \"" << msg << "\" but got \"" << buffer << "\"" << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query returned wrong label");
+	}
+
+	switch(m_identifier)
+	{
+		case GL_BUFFER:				gl.deleteBuffers(1, &object);				break;
+		case GL_SHADER:				gl.deleteShader(object);					break;
+		case GL_PROGRAM:			gl.deleteProgram(object);					break;
+		case GL_QUERY:				gl.deleteQueries(1, &object);				break;
+		case GL_PROGRAM_PIPELINE:	gl.deleteProgramPipelines(1, &object);		break;
+		case GL_TRANSFORM_FEEDBACK:	gl.deleteTransformFeedbacks(1, &object);	break;
+		case GL_SAMPLER:			gl.deleteSamplers(1, &object);				break;
+		case GL_TEXTURE:			gl.deleteTextures(1, &object);				break;
+		case GL_RENDERBUFFER:		gl.deleteRenderbuffers(1, &object);			break;
+		case GL_FRAMEBUFFER:		gl.deleteFramebuffers(1, &object);			break;
+
+		default:
+			DE_ASSERT(!"Invalid identifier");
+	}
+
+	return STOP;
+}
+
+} // Anonymous
+
+DebugTests::DebugTests (Context& context)
+	: TestCaseGroup(context, "debug", "Debug tests")
+{
+}
+
+enum CaseType
+{
+	CASETYPE_CALLBACK = 0,
+	CASETYPE_LOG,
+	CASETYPE_GETERROR,
+
+	CASETYPE_LAST
+};
+
+tcu::TestNode* createCase (CaseType type, Context& ctx, const char* name, const char* desc, TestFunc function)
+{
+	switch(type)
+	{
+		case CASETYPE_CALLBACK: return new CallbackErrorCase(ctx, name, desc, function);
+		case CASETYPE_LOG:		return new LogErrorCase(ctx, name, desc, function);
+		case CASETYPE_GETERROR: return new GetErrorCase(ctx, name, desc, function);
+
+		default:
+			DE_ASSERT(!"Invalid type");
+	}
+
+	return DE_NULL;
+}
+
+tcu::TestCaseGroup* createChildCases (CaseType type, Context& ctx, const char* name, const char* desc, const vector<FunctionContainer>& funcs)
+{
+	tcu::TestCaseGroup* host = new tcu::TestCaseGroup(ctx.getTestContext(), name, desc);
+
+	for (size_t ndx = 0; ndx < funcs.size(); ndx++)
+			host->addChild(createCase(type, ctx, funcs[ndx].name, funcs[ndx].desc, funcs[ndx].function));
+
+	return host;
+}
+
+void DebugTests::init (void)
+{
+	const vector<FunctionContainer> bufferFuncs		= NegativeTestShared::getNegativeBufferApiTestFunctions();
+	const vector<FunctionContainer> textureFuncs	= NegativeTestShared::getNegativeTextureApiTestFunctions();
+	const vector<FunctionContainer> shaderFuncs		= NegativeTestShared::getNegativeShaderApiTestFunctions();
+	const vector<FunctionContainer> fragmentFuncs	= NegativeTestShared::getNegativeFragmentApiTestFunctions();
+	const vector<FunctionContainer> vaFuncs			= NegativeTestShared::getNegativeVertexArrayApiTestFunctions();
+	const vector<FunctionContainer> stateFuncs		= NegativeTestShared::getNegativeStateApiTestFunctions();
+	const vector<FunctionContainer> externalFuncs	= getUserMessageFuncs();
+
+	{
+		tcu::TestCaseGroup* const	negative	= new tcu::TestCaseGroup(m_testCtx, "negative_coverage", "API error coverage with various reporting methods");
+
+		addChild(negative);
+
+		{
+			tcu::TestCaseGroup* const	host	= new tcu::TestCaseGroup(m_testCtx, "callbacks", "Reporting of standard API errors via callback");
+
+			negative->addChild(host);
+			host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "buffer",			"Negative Buffer API Cases",		bufferFuncs));
+			host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "texture",		"Negative Texture API Cases",		textureFuncs));
+			host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "shader",			"Negative Shader API Cases",		shaderFuncs));
+			host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "fragment",		"Negative Fragment API Cases",		fragmentFuncs));
+			host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "vertex_array",	"Negative Vertex Array API Cases",	vaFuncs));
+			host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "state",			"Negative GL State API Cases",		stateFuncs));
+		}
+
+		{
+			tcu::TestCaseGroup* const	host	= new tcu::TestCaseGroup(m_testCtx, "log", "Reporting of standard API errors via log");
+
+			negative->addChild(host);
+
+			host->addChild(createChildCases(CASETYPE_LOG, m_context, "buffer",				"Negative Buffer API Cases",		bufferFuncs));
+			host->addChild(createChildCases(CASETYPE_LOG, m_context, "texture",				"Negative Texture API Cases",		textureFuncs));
+			host->addChild(createChildCases(CASETYPE_LOG, m_context, "shader",				"Negative Shader API Cases",		shaderFuncs));
+			host->addChild(createChildCases(CASETYPE_LOG, m_context, "fragment",			"Negative Fragment API Cases",		fragmentFuncs));
+			host->addChild(createChildCases(CASETYPE_LOG, m_context, "vertex_array",		"Negative Vertex Array API Cases",	vaFuncs));
+			host->addChild(createChildCases(CASETYPE_LOG, m_context, "state",				"Negative GL State API Cases",		stateFuncs));
+		}
+
+		{
+			tcu::TestCaseGroup* const	host	= new tcu::TestCaseGroup(m_testCtx, "get_error", "Reporting of standard API errors via glGetError");
+
+			negative->addChild(host);
+
+			host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "buffer",			"Negative Buffer API Cases",		bufferFuncs));
+			host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "texture",		"Negative Texture API Cases",		textureFuncs));
+			host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "shader",			"Negative Shader API Cases",		shaderFuncs));
+			host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "fragment",		"Negative Fragment API Cases",		fragmentFuncs));
+			host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "vertex_array",	"Negative Vertex Array API Cases",	vaFuncs));
+			host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "state",			"Negative GL State API Cases",		stateFuncs));
+		}
+	}
+
+	{
+		tcu::TestCaseGroup*			host	= createChildCases(CASETYPE_CALLBACK, m_context, "externally_generated", "Externally Generated Messages", externalFuncs);
+
+		host->addChild(new GroupCase(m_context, "push_pop_consistency", "Push/pop message generation with full message output checking"));
+
+		addChild(host);
+	}
+
+	{
+		vector<FunctionContainer>	containers;
+		vector<TestFunc>			allFuncs;
+
+		de::Random					rng			(0x53941903 ^ m_context.getTestContext().getCommandLine().getBaseSeed());
+
+		containers.insert(containers.end(), bufferFuncs.begin(), bufferFuncs.end());
+		containers.insert(containers.end(), textureFuncs.begin(), textureFuncs.end());
+		containers.insert(containers.end(), externalFuncs.begin(), externalFuncs.end());
+
+		for (size_t ndx = 0; ndx < containers.size(); ndx++)
+			allFuncs.push_back(containers[ndx].function);
+
+		rng.shuffle(allFuncs.begin(), allFuncs.end());
+
+		{
+			tcu::TestCaseGroup* const	filtering				= new tcu::TestCaseGroup(m_testCtx, "error_filters", "Filtering of reported errors");
+			const int					errorFuncsPerCase		= 4;
+			const int					maxFilteringCaseCount	= 32;
+			const int					caseCount				= (int(allFuncs.size()) + errorFuncsPerCase-1) / errorFuncsPerCase;
+
+			addChild(filtering);
+
+			for (int caseNdx = 0; caseNdx < de::min(caseCount, maxFilteringCaseCount); caseNdx++)
+			{
+				const int			start		= caseNdx*errorFuncsPerCase;
+				const int			end			= de::min((caseNdx+1)*errorFuncsPerCase, int(allFuncs.size()));
+				const string		name		= "case_" + de::toString(caseNdx);
+				vector<TestFunc>	funcs		(allFuncs.begin()+start, allFuncs.begin()+end);
+
+				// These produce lots of different message types, thus always include at least one when testing filtering
+				funcs.insert(funcs.end(), externalFuncs[caseNdx%externalFuncs.size()].function);
+
+				filtering->addChild(new FilterCase(m_context, name.c_str(), "DebugMessageControl usage", funcs));
+			}
+		}
+
+		{
+			tcu::TestCaseGroup* const	groups					= new tcu::TestCaseGroup(m_testCtx, "error_groups", "Filtering of reported errors with use of Error Groups");
+			const int					errorFuncsPerCase		= 4;
+			const int					maxFilteringCaseCount	= 16;
+			const int					caseCount				= (int(allFuncs.size()) + errorFuncsPerCase-1) / errorFuncsPerCase;
+
+			addChild(groups);
+
+			for (int caseNdx = 0; caseNdx < caseCount && caseNdx < maxFilteringCaseCount; caseNdx++)
+			{
+				const int			start		= caseNdx*errorFuncsPerCase;
+				const int			end			= de::min((caseNdx+1)*errorFuncsPerCase, int(allFuncs.size()));
+				const string		name		= ("case_" + de::toString(caseNdx)).c_str();
+				vector<TestFunc>	funcs		(&allFuncs[0]+start, &allFuncs[0]+end);
+
+				// These produce lots of different message types, thus always include at least one when testing filtering
+				funcs.insert(funcs.end(), externalFuncs[caseNdx%externalFuncs.size()].function);
+
+				groups->addChild(new GroupFilterCase(m_context, name.c_str(), "Debug Group usage", funcs));
+			}
+		}
+
+		{
+			tcu::TestCaseGroup* const	async				= new tcu::TestCaseGroup(m_testCtx, "async", "Asynchronous message generation");
+			const int					errorFuncsPerCase	= 2;
+			const int					maxAsyncCaseCount	= 16;
+			const int					caseCount			= (int(allFuncs.size()) + errorFuncsPerCase-1) / errorFuncsPerCase;
+
+			addChild(async);
+
+			for (int caseNdx = 0; caseNdx < caseCount && caseNdx < maxAsyncCaseCount; caseNdx++)
+			{
+				const int			start		= caseNdx*errorFuncsPerCase;
+				const int			end			= de::min((caseNdx+1)*errorFuncsPerCase, int(allFuncs.size()));
+				const string		name		= ("case_" + de::toString(caseNdx)).c_str();
+				vector<TestFunc>	funcs		(&allFuncs[0]+start, &allFuncs[0]+end);
+
+				if (caseNdx&0x1)
+					async->addChild(new AsyncCase(m_context, (name+"_callback").c_str(), "Async message generation", funcs, true));
+				else
+					async->addChild(new AsyncCase(m_context, (name+"_log").c_str(), "Async message generation", funcs, false));
+			}
+		}
+	}
+
+	{
+		tcu::TestCaseGroup* const labels = new tcu::TestCaseGroup(m_testCtx, "object_labels", "Labeling objects");
+
+		const struct
+		{
+			GLenum		identifier;
+			const char*	name;
+			const char* desc;
+		} cases[] =
+		{
+			{ GL_BUFFER,				"buffer",				"Debug label on a buffer object"				},
+			{ GL_SHADER,				"shader",				"Debug label on a shader object"				},
+			{ GL_PROGRAM,				"program",				"Debug label on a program object"				},
+			{ GL_QUERY,					"query",				"Debug label on a query object"					},
+			{ GL_PROGRAM_PIPELINE,		"program_pipeline",		"Debug label on a program pipeline object"		},
+			{ GL_TRANSFORM_FEEDBACK,	"transform_feedback",	"Debug label on a transform feedback object"	},
+			{ GL_SAMPLER,				"sampler",				"Debug label on a sampler object"				},
+			{ GL_TEXTURE,				"texture",				"Debug label on a texture object"				},
+			{ GL_RENDERBUFFER,			"renderbuffer",			"Debug label on a renderbuffer object"			},
+			{ GL_FRAMEBUFFER,			"framebuffer",			"Debug label on a framebuffer object"			},
+		};
+
+		addChild(labels);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
+			labels->addChild(new LabelCase(m_context, cases[ndx].name, cases[ndx].desc, cases[ndx].identifier));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fDebugTests.hpp b/modules/gles31/functional/es31fDebugTests.hpp
new file mode 100644
index 0000000..d709716
--- /dev/null
+++ b/modules/gles31/functional/es31fDebugTests.hpp
@@ -0,0 +1,55 @@
+#ifndef _ES31FDEBUGTESTS_HPP
+#define _ES31FDEBUGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Debug output (KHR_debug) tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+#include "glwDefs.hpp"
+#include "gluCallLogWrapper.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class DebugTests : public TestCaseGroup
+{
+public:
+					DebugTests				(Context& context);
+	virtual			~DebugTests				(void) {}
+
+	virtual void	init					(void);
+
+private:
+					DebugTests				(const DebugTests& other);
+	DebugTests&		operator=				(const DebugTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FDEBUGTESTS_HPP
diff --git a/modules/gles31/functional/es31fDrawTests.cpp b/modules/gles31/functional/es31fDrawTests.cpp
new file mode 100644
index 0000000..3ec945c
--- /dev/null
+++ b/modules/gles31/functional/es31fDrawTests.cpp
@@ -0,0 +1,2797 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Drawing tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fDrawTests.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMemory.h"
+#include "tcuRenderTarget.hpp"
+#include "tcuVectorUtil.hpp"
+#include "sglrGLContext.hpp"
+#include "glsDrawTest.hpp"
+#include "gluStrUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluCallLogWrapper.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <set>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+enum TestIterationType
+{
+	TYPE_DRAW_COUNT,		// !< test with 1, 5, and 25 primitives
+	TYPE_INSTANCE_COUNT,	// !< test with 1, 4, and 11 instances
+
+	TYPE_LAST
+};
+
+static const char* s_commonVertexShaderSource =		"#version 310 es\n"
+													"in highp vec4 a_position;\n"
+													"void main (void)\n"
+													"{\n"
+													"	gl_Position = a_position;\n"
+													"}\n";
+static const char* s_commonFragmentShaderSource	=	"#version 310 es\n"
+													"layout(location = 0) out highp vec4 fragColor;\n"
+													"void main (void)\n"
+													"{\n"
+													"	fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+													"}\n";
+
+static const char* s_colorVertexShaderSource =		"#version 310 es\n"
+													"in highp vec4 a_position;\n"
+													"in highp vec4 a_color;\n"
+													"out highp vec4 v_color;\n"
+													"void main (void)\n"
+													"{\n"
+													"	gl_Position = a_position;\n"
+													"	v_color = a_color;\n"
+													"}\n";
+static const char* s_colorFragmentShaderSource	=	"#version 310 es\n"
+													"layout(location = 0) out highp vec4 fragColor;\n"
+													"in highp vec4 v_color;\n"
+													"void main (void)\n"
+													"{\n"
+													"	fragColor = v_color;\n"
+													"}\n";
+struct DrawElementsCommand
+{
+	deUint32 count;
+	deUint32 primCount;
+	deUint32 firstIndex;
+	deInt32  baseVertex;
+	deUint32 reservedMustBeZero;
+};
+DE_STATIC_ASSERT(5 * sizeof(deUint32) == sizeof(DrawElementsCommand)); // tight packing
+
+struct DrawArraysCommand
+{
+	deUint32 count;
+	deUint32 primCount;
+	deUint32 first;
+	deUint32 reservedMustBeZero;
+};
+DE_STATIC_ASSERT(4 * sizeof(deUint32) == sizeof(DrawArraysCommand)); // tight packing
+
+// Verifies image contains only yellow or greeen, or a linear combination
+// of these colors.
+static bool verifyImageYellowGreen (const tcu::Surface& image, tcu::TestLog& log)
+{
+	using tcu::TestLog;
+
+	const tcu::RGBA green		(0, 255, 0, 255);
+	const tcu::RGBA yellow		(255, 255, 0, 255);
+	const int colorThreshold	= 20;
+
+	tcu::Surface error			(image.getWidth(), image.getHeight());
+	bool isOk					= true;
+
+	for (int y = 0; y < image.getHeight(); y++)
+	for (int x = 0; x < image.getWidth(); x++)
+	{
+		const tcu::RGBA pixel = image.getPixel(x, y);
+		bool pixelOk = true;
+
+		// Any pixel with !(G ~= 255) is faulty (not a linear combinations of green and yellow)
+		if (de::abs(pixel.getGreen() - 255) > colorThreshold)
+			pixelOk = false;
+
+		// Any pixel with !(B ~= 0) is faulty (not a linear combinations of green and yellow)
+		if (de::abs(pixel.getBlue() - 0) > colorThreshold)
+			pixelOk = false;
+
+		error.setPixel(x, y, (pixelOk) ? (tcu::RGBA(0, 255, 0, 255)) : (tcu::RGBA(255, 0, 0, 255)));
+		isOk = isOk && pixelOk;
+	}
+
+	if (!isOk)
+	{
+		log << TestLog::Message << "Image verification failed." << TestLog::EndMessage;
+		log << TestLog::ImageSet("Verfication result", "Result of rendering")
+			<< TestLog::Image("Result",		"Result",		image)
+			<< TestLog::Image("ErrorMask",	"Error mask",	error)
+			<< TestLog::EndImageSet;
+	}
+	else
+	{
+		log << TestLog::ImageSet("Verfication result", "Result of rendering")
+			<< TestLog::Image("Result", "Result", image)
+			<< TestLog::EndImageSet;
+	}
+
+	return isOk;
+}
+
+static void addTestIterations (gls::DrawTest* test, gls::DrawTestSpec& spec, TestIterationType type)
+{
+	if (type == TYPE_DRAW_COUNT)
+	{
+		spec.primitiveCount = 1;
+		test->addIteration(spec, "draw count = 1");
+
+		spec.primitiveCount = 5;
+		test->addIteration(spec, "draw count = 5");
+
+		spec.primitiveCount = 25;
+		test->addIteration(spec, "draw count = 25");
+	}
+	else if (type == TYPE_INSTANCE_COUNT)
+	{
+		spec.instanceCount = 1;
+		test->addIteration(spec, "instance count = 1");
+
+		spec.instanceCount = 4;
+		test->addIteration(spec, "instance count = 4");
+
+		spec.instanceCount = 11;
+		test->addIteration(spec, "instance count = 11");
+	}
+	else
+		DE_ASSERT(false);
+}
+
+static void genBasicSpec (gls::DrawTestSpec& spec, gls::DrawTestSpec::DrawMethod method)
+{
+	spec.apiType							= glu::ApiType::es(3,1);
+	spec.primitive							= gls::DrawTestSpec::PRIMITIVE_TRIANGLES;
+	spec.primitiveCount						= 5;
+	spec.drawMethod							= method;
+	spec.indexType							= gls::DrawTestSpec::INDEXTYPE_LAST;
+	spec.indexPointerOffset					= 0;
+	spec.indexStorage						= gls::DrawTestSpec::STORAGE_LAST;
+	spec.first								= 0;
+	spec.indexMin							= 0;
+	spec.indexMax							= 0;
+	spec.instanceCount						= 1;
+	spec.indirectOffset						= 0;
+
+	spec.attribs.resize(2);
+
+	spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+	spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+	spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+	spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+	spec.attribs[0].componentCount			= 4;
+	spec.attribs[0].offset					= 0;
+	spec.attribs[0].stride					= 0;
+	spec.attribs[0].normalize				= false;
+	spec.attribs[0].instanceDivisor			= 0;
+	spec.attribs[0].useDefaultAttribute		= false;
+
+	spec.attribs[1].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+	spec.attribs[1].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+	spec.attribs[1].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+	spec.attribs[1].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+	spec.attribs[1].componentCount			= 2;
+	spec.attribs[1].offset					= 0;
+	spec.attribs[1].stride					= 0;
+	spec.attribs[1].normalize				= false;
+	spec.attribs[1].instanceDivisor			= 0;
+	spec.attribs[1].useDefaultAttribute		= false;
+}
+
+static std::string sizeToString (int size)
+{
+	if (size < 1024)
+		return de::toString(size) + " byte(s)";
+	if (size < 1024*1024)
+		return de::toString(size / 1024) + " KB";
+	return de::toString(size / 1024 / 1024) + " MB";
+}
+
+class AttributeGroup : public TestCaseGroup
+{
+public:
+									AttributeGroup	(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod, gls::DrawTestSpec::Primitive primitive, gls::DrawTestSpec::IndexType indexType, gls::DrawTestSpec::Storage indexStorage);
+									~AttributeGroup	(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+	gls::DrawTestSpec::Primitive	m_primitive;
+	gls::DrawTestSpec::IndexType	m_indexType;
+	gls::DrawTestSpec::Storage		m_indexStorage;
+};
+
+AttributeGroup::AttributeGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod, gls::DrawTestSpec::Primitive primitive, gls::DrawTestSpec::IndexType indexType, gls::DrawTestSpec::Storage indexStorage)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+	, m_primitive		(primitive)
+	, m_indexType		(indexType)
+	, m_indexStorage	(indexStorage)
+{
+}
+
+AttributeGroup::~AttributeGroup (void)
+{
+}
+
+void AttributeGroup::init (void)
+{
+	// Single attribute
+	{
+		gls::DrawTest*		test				= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "single_attribute", "Single attribute array.");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType							= glu::ApiType::es(3,1);
+		spec.primitive							= m_primitive;
+		spec.primitiveCount						= 5;
+		spec.drawMethod							= m_method;
+		spec.indexType							= m_indexType;
+		spec.indexPointerOffset					= 0;
+		spec.indexStorage						= m_indexStorage;
+		spec.first								= 0;
+		spec.indexMin							= 0;
+		spec.indexMax							= 0;
+		spec.instanceCount						= 1;
+		spec.indirectOffset						= 0;
+
+		spec.attribs.resize(1);
+
+		spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount			= 2;
+		spec.attribs[0].offset					= 0;
+		spec.attribs[0].stride					= 0;
+		spec.attribs[0].normalize				= false;
+		spec.attribs[0].instanceDivisor			= 0;
+		spec.attribs[0].useDefaultAttribute		= false;
+
+		addTestIterations(test, spec, TYPE_DRAW_COUNT);
+
+		this->addChild(test);
+	}
+
+	// Multiple attribute
+	{
+		gls::DrawTest*		test				= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "multiple_attributes", "Multiple attribute arrays.");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType							= glu::ApiType::es(3,1);
+		spec.primitive							= m_primitive;
+		spec.primitiveCount						= 5;
+		spec.drawMethod							= m_method;
+		spec.indexType							= m_indexType;
+		spec.indexPointerOffset					= 0;
+		spec.indexStorage						= m_indexStorage;
+		spec.first								= 0;
+		spec.indexMin							= 0;
+		spec.indexMax							= 0;
+		spec.instanceCount						= 1;
+		spec.indirectOffset						= 0;
+
+		spec.attribs.resize(2);
+
+		spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount			= 4;
+		spec.attribs[0].offset					= 0;
+		spec.attribs[0].stride					= 0;
+		spec.attribs[0].normalize				= false;
+		spec.attribs[0].instanceDivisor			= 0;
+		spec.attribs[0].useDefaultAttribute		= false;
+
+		spec.attribs[1].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[1].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[1].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[1].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[1].componentCount			= 2;
+		spec.attribs[1].offset					= 0;
+		spec.attribs[1].stride					= 0;
+		spec.attribs[1].normalize				= false;
+		spec.attribs[1].instanceDivisor			= 0;
+		spec.attribs[1].useDefaultAttribute		= false;
+
+		addTestIterations(test, spec, TYPE_DRAW_COUNT);
+
+		this->addChild(test);
+	}
+
+	// Multiple attribute, second one divided
+	{
+		gls::DrawTest*		test					= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "instanced_attributes", "Instanced attribute array.");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType								= glu::ApiType::es(3,1);
+		spec.primitive								= m_primitive;
+		spec.primitiveCount							= 5;
+		spec.drawMethod								= m_method;
+		spec.indexType								= m_indexType;
+		spec.indexPointerOffset						= 0;
+		spec.indexStorage							= m_indexStorage;
+		spec.first									= 0;
+		spec.indexMin								= 0;
+		spec.indexMax								= 0;
+		spec.instanceCount							= 1;
+		spec.indirectOffset							= 0;
+
+		spec.attribs.resize(3);
+
+		spec.attribs[0].inputType					= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType					= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage						= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage						= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount				= 4;
+		spec.attribs[0].offset						= 0;
+		spec.attribs[0].stride						= 0;
+		spec.attribs[0].normalize					= false;
+		spec.attribs[0].instanceDivisor				= 0;
+		spec.attribs[0].useDefaultAttribute			= false;
+
+		// Add another position component so the instances wont be drawn on each other
+		spec.attribs[1].inputType					= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[1].outputType					= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[1].storage						= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[1].usage						= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[1].componentCount				= 2;
+		spec.attribs[1].offset						= 0;
+		spec.attribs[1].stride						= 0;
+		spec.attribs[1].normalize					= false;
+		spec.attribs[1].instanceDivisor				= 1;
+		spec.attribs[1].useDefaultAttribute			= false;
+		spec.attribs[1].additionalPositionAttribute	= true;
+
+		// Instanced color
+		spec.attribs[2].inputType					= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[2].outputType					= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[2].storage						= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[2].usage						= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[2].componentCount				= 3;
+		spec.attribs[2].offset						= 0;
+		spec.attribs[2].stride						= 0;
+		spec.attribs[2].normalize					= false;
+		spec.attribs[2].instanceDivisor				= 1;
+		spec.attribs[2].useDefaultAttribute			= false;
+
+		addTestIterations(test, spec, TYPE_INSTANCE_COUNT);
+
+		this->addChild(test);
+	}
+
+	// Multiple attribute, second one default
+	{
+		gls::DrawTest*		test				= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "default_attribute", "Attribute specified with glVertexAttrib*.");
+		gls::DrawTestSpec	spec;
+
+		spec.apiType							= glu::ApiType::es(3,1);
+		spec.primitive							= m_primitive;
+		spec.primitiveCount						= 5;
+		spec.drawMethod							= m_method;
+		spec.indexType							= m_indexType;
+		spec.indexPointerOffset					= 0;
+		spec.indexStorage						= m_indexStorage;
+		spec.first								= 0;
+		spec.indexMin							= 0;
+		spec.indexMax							= 0;
+		spec.instanceCount						= 1;
+		spec.indirectOffset						= 0;
+
+		spec.attribs.resize(2);
+
+		spec.attribs[0].inputType				= gls::DrawTestSpec::INPUTTYPE_FLOAT;
+		spec.attribs[0].outputType				= gls::DrawTestSpec::OUTPUTTYPE_VEC2;
+		spec.attribs[0].storage					= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.attribs[0].usage					= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+		spec.attribs[0].componentCount			= 2;
+		spec.attribs[0].offset					= 0;
+		spec.attribs[0].stride					= 0;
+		spec.attribs[0].normalize				= false;
+		spec.attribs[0].instanceDivisor			= 0;
+		spec.attribs[0].useDefaultAttribute		= false;
+
+		struct IOPair
+		{
+			gls::DrawTestSpec::InputType  input;
+			gls::DrawTestSpec::OutputType output;
+			int							  componentCount;
+		} iopairs[] =
+		{
+			{ gls::DrawTestSpec::INPUTTYPE_FLOAT,        gls::DrawTestSpec::OUTPUTTYPE_VEC2,  4 },
+			{ gls::DrawTestSpec::INPUTTYPE_FLOAT,        gls::DrawTestSpec::OUTPUTTYPE_VEC4,  2 },
+			{ gls::DrawTestSpec::INPUTTYPE_INT,          gls::DrawTestSpec::OUTPUTTYPE_IVEC3, 4 },
+			{ gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT, gls::DrawTestSpec::OUTPUTTYPE_UVEC2, 4 },
+		};
+
+		for (int ioNdx = 0; ioNdx < DE_LENGTH_OF_ARRAY(iopairs); ++ioNdx)
+		{
+			const std::string desc = gls::DrawTestSpec::inputTypeToString(iopairs[ioNdx].input) + de::toString(iopairs[ioNdx].componentCount) + " to " + gls::DrawTestSpec::outputTypeToString(iopairs[ioNdx].output);
+
+			spec.attribs[1].inputType			= iopairs[ioNdx].input;
+			spec.attribs[1].outputType			= iopairs[ioNdx].output;
+			spec.attribs[1].storage				= gls::DrawTestSpec::STORAGE_BUFFER;
+			spec.attribs[1].usage				= gls::DrawTestSpec::USAGE_STATIC_DRAW;
+			spec.attribs[1].componentCount		= iopairs[ioNdx].componentCount;
+			spec.attribs[1].offset				= 0;
+			spec.attribs[1].stride				= 0;
+			spec.attribs[1].normalize			= false;
+			spec.attribs[1].instanceDivisor		= 0;
+			spec.attribs[1].useDefaultAttribute	= true;
+
+			test->addIteration(spec, desc.c_str());
+		}
+
+		this->addChild(test);
+	}
+}
+
+class IndexGroup : public TestCaseGroup
+{
+public:
+									IndexGroup		(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~IndexGroup		(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+IndexGroup::IndexGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+IndexGroup::~IndexGroup (void)
+{
+}
+
+void IndexGroup::init (void)
+{
+	struct IndexTest
+	{
+		gls::DrawTestSpec::IndexType	type;
+		int								offsets[3];
+	};
+
+	const IndexTest tests[] =
+	{
+		{ gls::DrawTestSpec::INDEXTYPE_BYTE,	{ 0, 1, -1 } },
+		{ gls::DrawTestSpec::INDEXTYPE_SHORT,	{ 0, 2, -1 } },
+		{ gls::DrawTestSpec::INDEXTYPE_INT,		{ 0, 4, -1 } },
+	};
+
+	gls::DrawTestSpec spec;
+	genBasicSpec(spec, m_method);
+
+	spec.indexStorage = gls::DrawTestSpec::STORAGE_BUFFER;
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(tests); ++testNdx)
+	{
+		const IndexTest&	indexTest	= tests[testNdx];
+
+		const std::string	name		= std::string("index_") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
+		const std::string	desc		= std::string("index ") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
+		gls::DrawTest*		test		= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str());
+
+		spec.indexType			= indexTest.type;
+
+		for (int iterationNdx = 0; iterationNdx < DE_LENGTH_OF_ARRAY(indexTest.offsets) && indexTest.offsets[iterationNdx] != -1; ++iterationNdx)
+		{
+			const std::string iterationDesc = std::string("first vertex ") + de::toString(indexTest.offsets[iterationNdx] / gls::DrawTestSpec::indexTypeSize(indexTest.type));
+			spec.indexPointerOffset	= indexTest.offsets[iterationNdx];
+			test->addIteration(spec, iterationDesc.c_str());
+		}
+
+		addChild(test);
+	}
+}
+
+class BaseVertexGroup : public TestCaseGroup
+{
+public:
+									BaseVertexGroup		(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~BaseVertexGroup	(void);
+
+	void							init				(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+BaseVertexGroup::BaseVertexGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+BaseVertexGroup::~BaseVertexGroup (void)
+{
+}
+
+void BaseVertexGroup::init (void)
+{
+	struct IndexTest
+	{
+		bool							positiveBase;
+		gls::DrawTestSpec::IndexType	type;
+		int								baseVertex[2];
+	};
+
+	const IndexTest tests[] =
+	{
+		{ true,  gls::DrawTestSpec::INDEXTYPE_BYTE,		{  1,  2 } },
+		{ true,  gls::DrawTestSpec::INDEXTYPE_SHORT,	{  1,  2 } },
+		{ true,  gls::DrawTestSpec::INDEXTYPE_INT,		{  1,  2 } },
+		{ false, gls::DrawTestSpec::INDEXTYPE_BYTE,		{ -1, -2 } },
+		{ false, gls::DrawTestSpec::INDEXTYPE_SHORT,	{ -1, -2 } },
+		{ false, gls::DrawTestSpec::INDEXTYPE_INT,		{ -1, -2 } },
+	};
+
+	gls::DrawTestSpec spec;
+	genBasicSpec(spec, m_method);
+
+	spec.indexStorage = gls::DrawTestSpec::STORAGE_BUFFER;
+
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(tests); ++testNdx)
+	{
+		const IndexTest&	indexTest	= tests[testNdx];
+
+		const std::string	name		= std::string("index_") + (indexTest.positiveBase ? "" : "neg_") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
+		const std::string	desc		= std::string("index ") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
+		gls::DrawTest*		test		= new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str());
+
+		spec.indexType			= indexTest.type;
+
+		for (int iterationNdx = 0; iterationNdx < DE_LENGTH_OF_ARRAY(indexTest.baseVertex); ++iterationNdx)
+		{
+			const std::string iterationDesc = std::string("base vertex ") + de::toString(indexTest.baseVertex[iterationNdx]);
+			spec.baseVertex	= indexTest.baseVertex[iterationNdx];
+			test->addIteration(spec, iterationDesc.c_str());
+		}
+
+		addChild(test);
+	}
+}
+
+class FirstGroup : public TestCaseGroup
+{
+public:
+									FirstGroup		(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~FirstGroup		(void);
+
+	void							init			(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+FirstGroup::FirstGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+FirstGroup::~FirstGroup (void)
+{
+}
+
+void FirstGroup::init (void)
+{
+	const int firsts[] =
+	{
+		1, 3, 17
+	};
+
+	gls::DrawTestSpec spec;
+	genBasicSpec(spec, m_method);
+
+	for (int firstNdx = 0; firstNdx < DE_LENGTH_OF_ARRAY(firsts); ++firstNdx)
+	{
+		const std::string	name = std::string("first_") + de::toString(firsts[firstNdx]);
+		const std::string	desc = std::string("first ") + de::toString(firsts[firstNdx]);
+		gls::DrawTest*		test = new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str());
+
+		spec.first = firsts[firstNdx];
+
+		addTestIterations(test, spec, TYPE_DRAW_COUNT);
+
+		this->addChild(test);
+	}
+}
+
+class MethodGroup : public TestCaseGroup
+{
+public:
+									MethodGroup			(Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod);
+									~MethodGroup		(void);
+
+	void							init				(void);
+
+private:
+	gls::DrawTestSpec::DrawMethod	m_method;
+};
+
+MethodGroup::MethodGroup (Context& context, const char* name, const char* descr, gls::DrawTestSpec::DrawMethod drawMethod)
+	: TestCaseGroup		(context, name, descr)
+	, m_method			(drawMethod)
+{
+}
+
+MethodGroup::~MethodGroup (void)
+{
+}
+
+void MethodGroup::init (void)
+{
+	const bool indexed		= (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT);
+	const bool hasFirst		= (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT);
+
+	const gls::DrawTestSpec::Primitive primitive[] =
+	{
+		gls::DrawTestSpec::PRIMITIVE_POINTS,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINES,
+		gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINE_LOOP
+	};
+
+	if (hasFirst)
+	{
+		// First-tests
+		this->addChild(new FirstGroup(m_context, "first", "First tests", m_method));
+	}
+
+	if (indexed)
+	{
+		// Index-tests
+		this->addChild(new IndexGroup(m_context, "indices", "Index tests", m_method));
+		this->addChild(new BaseVertexGroup(m_context, "base_vertex", "Base vertex tests", m_method));
+	}
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(primitive); ++ndx)
+	{
+		const std::string name = gls::DrawTestSpec::primitiveToString(primitive[ndx]);
+		const std::string desc = gls::DrawTestSpec::primitiveToString(primitive[ndx]);
+
+		this->addChild(new AttributeGroup(m_context, name.c_str(), desc.c_str(), m_method, primitive[ndx], gls::DrawTestSpec::INDEXTYPE_SHORT, gls::DrawTestSpec::STORAGE_BUFFER));
+	}
+}
+
+class GridProgram : public sglr::ShaderProgram
+{
+public:
+			GridProgram		(void);
+
+	void	shadeVertices	(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void	shadeFragments	(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+};
+
+GridProgram::GridProgram (void)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_offset", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource("#version 310 es\n"
+														"in highp vec4 a_position;\n"
+														"in highp vec4 a_offset;\n"
+														"in highp vec4 a_color;\n"
+														"out highp vec4 v_color;\n"
+														"void main(void)\n"
+														"{\n"
+														"	gl_Position = a_position + a_offset;\n"
+														"	v_color = a_color;\n"
+														"}\n")
+							<< sglr::pdec::FragmentSource(
+														"#version 310 es\n"
+														"layout(location = 0) out highp vec4 dEQP_FragColor;\n"
+														"in highp vec4 v_color;\n"
+														"void main(void)\n"
+														"{\n"
+														"	dEQP_FragColor = v_color;\n"
+														"}\n"))
+{
+}
+
+void GridProgram::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		packets[ndx]->position = rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx) + rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+		packets[ndx]->outputs[0] = rr::readVertexAttribFloat(inputs[2], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+	}
+}
+
+void GridProgram::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx));
+}
+
+class InstancedGridRenderTest : public TestCase
+{
+public:
+					InstancedGridRenderTest		(Context& context, const char* name, const char* desc, int gridSide, bool useIndices);
+					~InstancedGridRenderTest	(void);
+
+	IterateResult	iterate						(void);
+
+private:
+	void			renderTo					(sglr::Context& ctx, sglr::ShaderProgram& program, tcu::Surface& dst);
+
+	const int		m_gridSide;
+	const bool		m_useIndices;
+};
+
+InstancedGridRenderTest::InstancedGridRenderTest (Context& context, const char* name, const char* desc, int gridSide, bool useIndices)
+	: TestCase		(context, name, desc)
+	, m_gridSide	(gridSide)
+	, m_useIndices	(useIndices)
+{
+}
+
+InstancedGridRenderTest::~InstancedGridRenderTest (void)
+{
+}
+
+InstancedGridRenderTest::IterateResult InstancedGridRenderTest::iterate (void)
+{
+	const int renderTargetWidth  = de::min(1024, m_context.getRenderTarget().getWidth());
+	const int renderTargetHeight = de::min(1024, m_context.getRenderTarget().getHeight());
+
+	sglr::GLContext ctx		(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS | sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(0, 0, renderTargetWidth, renderTargetHeight));
+	tcu::Surface	surface	(renderTargetWidth, renderTargetHeight);
+	GridProgram		program;
+
+	// render
+
+	renderTo(ctx, program, surface);
+
+	// verify image
+	// \note the green/yellow pattern is only for clarity. The test will only verify that all instances were drawn by looking for anything non-green/yellow.
+	if (verifyImageYellowGreen(surface, m_testCtx.getLog()))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result image invalid");
+	return STOP;
+}
+
+void InstancedGridRenderTest::renderTo (sglr::Context& ctx, sglr::ShaderProgram& program, tcu::Surface& dst)
+{
+	const tcu::Vec4 green	(0, 1, 0, 1);
+	const tcu::Vec4 yellow	(1, 1, 0, 1);
+
+	deUint32 vaoID			= 0;
+	deUint32 positionBuf	= 0;
+	deUint32 offsetBuf		= 0;
+	deUint32 colorBuf		= 0;
+	deUint32 indexBuf		= 0;
+	deUint32 drawIndirectBuf= 0;
+	deUint32 programID		= ctx.createProgram(&program);
+	deInt32 posLocation		= ctx.getAttribLocation(programID, "a_position");
+	deInt32 offsetLocation	= ctx.getAttribLocation(programID, "a_offset");
+	deInt32 colorLocation	= ctx.getAttribLocation(programID, "a_color");
+
+	float cellW	= 2.0f / m_gridSide;
+	float cellH	= 2.0f / m_gridSide;
+	const tcu::Vec4 vertexPositions[] =
+	{
+		tcu::Vec4(0,		0,		0, 1),
+		tcu::Vec4(cellW,	0,		0, 1),
+		tcu::Vec4(0,		cellH,	0, 1),
+
+		tcu::Vec4(0,		cellH,	0, 1),
+		tcu::Vec4(cellW,	0,		0, 1),
+		tcu::Vec4(cellW,	cellH,	0, 1),
+	};
+
+	const deUint16 indices[] =
+	{
+		0, 4, 3,
+		2, 1, 5
+	};
+
+	std::vector<tcu::Vec4> offsets;
+	for (int x = 0; x < m_gridSide; ++x)
+	for (int y = 0; y < m_gridSide; ++y)
+		offsets.push_back(tcu::Vec4(x * cellW - 1.0f, y * cellW - 1.0f, 0, 0));
+
+	std::vector<tcu::Vec4> colors;
+	for (int x = 0; x < m_gridSide; ++x)
+	for (int y = 0; y < m_gridSide; ++y)
+		colors.push_back(((x + y) % 2 == 0) ? (green) : (yellow));
+
+	ctx.genVertexArrays(1, &vaoID);
+	ctx.bindVertexArray(vaoID);
+
+	ctx.genBuffers(1, &positionBuf);
+	ctx.bindBuffer(GL_ARRAY_BUFFER, positionBuf);
+	ctx.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
+	ctx.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	ctx.vertexAttribDivisor(posLocation, 0);
+	ctx.enableVertexAttribArray(posLocation);
+
+	ctx.genBuffers(1, &offsetBuf);
+	ctx.bindBuffer(GL_ARRAY_BUFFER, offsetBuf);
+	ctx.bufferData(GL_ARRAY_BUFFER, offsets.size() * sizeof(tcu::Vec4), &offsets[0], GL_STATIC_DRAW);
+	ctx.vertexAttribPointer(offsetLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	ctx.vertexAttribDivisor(offsetLocation, 1);
+	ctx.enableVertexAttribArray(offsetLocation);
+
+	ctx.genBuffers(1, &colorBuf);
+	ctx.bindBuffer(GL_ARRAY_BUFFER, colorBuf);
+	ctx.bufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(tcu::Vec4), &colors[0], GL_STATIC_DRAW);
+	ctx.vertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	ctx.vertexAttribDivisor(colorLocation, 1);
+	ctx.enableVertexAttribArray(colorLocation);
+
+	if (m_useIndices)
+	{
+		ctx.genBuffers(1, &indexBuf);
+		ctx.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuf);
+		ctx.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+	}
+
+	ctx.genBuffers(1, &drawIndirectBuf);
+	ctx.bindBuffer(GL_DRAW_INDIRECT_BUFFER, drawIndirectBuf);
+
+	if (m_useIndices)
+	{
+		DrawElementsCommand command;
+		command.count				= 6;
+		command.primCount			= m_gridSide * m_gridSide;
+		command.firstIndex			= 0;
+		command.baseVertex			= 0;
+		command.reservedMustBeZero	= 0;
+
+		ctx.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(command), &command, GL_STATIC_DRAW);
+	}
+	else
+	{
+		DrawArraysCommand command;
+		command.count				= 6;
+		command.primCount			= m_gridSide * m_gridSide;
+		command.first				= 0;
+		command.reservedMustBeZero	= 0;
+
+		ctx.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(command), &command, GL_STATIC_DRAW);
+	}
+
+	ctx.clearColor(0, 0, 0, 1);
+	ctx.clear(GL_COLOR_BUFFER_BIT);
+
+	ctx.viewport(0, 0, dst.getWidth(), dst.getHeight());
+
+	ctx.useProgram(programID);
+	if (m_useIndices)
+		ctx.drawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, DE_NULL);
+	else
+		ctx.drawArraysIndirect(GL_TRIANGLES, DE_NULL);
+	ctx.useProgram(0);
+
+	glu::checkError(ctx.getError(), "", __FILE__, __LINE__);
+
+	ctx.deleteBuffers(1, &drawIndirectBuf);
+	if (m_useIndices)
+		ctx.deleteBuffers(1, &indexBuf);
+	ctx.deleteBuffers(1, &colorBuf);
+	ctx.deleteBuffers(1, &offsetBuf);
+	ctx.deleteBuffers(1, &positionBuf);
+	ctx.deleteVertexArrays(1, &vaoID);
+	ctx.deleteProgram(programID);
+
+	ctx.finish();
+	ctx.readPixels(dst, 0, 0, dst.getWidth(), dst.getHeight());
+
+	glu::checkError(ctx.getError(), "", __FILE__, __LINE__);
+}
+
+class InstancingGroup : public TestCaseGroup
+{
+public:
+			InstancingGroup		(Context& context, const char* name, const char* descr);
+			~InstancingGroup	(void);
+
+	void	init				(void);
+};
+
+InstancingGroup::InstancingGroup (Context& context, const char* name, const char* descr)
+	: TestCaseGroup	(context, name, descr)
+{
+}
+
+InstancingGroup::~InstancingGroup (void)
+{
+}
+
+void InstancingGroup::init (void)
+{
+	const int gridWidths[] =
+	{
+		2,
+		5,
+		10,
+		32,
+		100,
+	};
+
+	// drawArrays
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(gridWidths); ++ndx)
+	{
+		const std::string name = std::string("draw_arrays_indirect_grid_") + de::toString(gridWidths[ndx]) + "x" + de::toString(gridWidths[ndx]);
+		const std::string desc = std::string("DrawArraysIndirect, Grid size ") + de::toString(gridWidths[ndx]) + "x" + de::toString(gridWidths[ndx]);
+
+		this->addChild(new InstancedGridRenderTest(m_context, name.c_str(), desc.c_str(), gridWidths[ndx], false));
+	}
+
+	// drawElements
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(gridWidths); ++ndx)
+	{
+		const std::string name = std::string("draw_elements_indirect_grid_") + de::toString(gridWidths[ndx]) + "x" + de::toString(gridWidths[ndx]);
+		const std::string desc = std::string("DrawElementsIndirect, Grid size ") + de::toString(gridWidths[ndx]) + "x" + de::toString(gridWidths[ndx]);
+
+		this->addChild(new InstancedGridRenderTest(m_context, name.c_str(), desc.c_str(), gridWidths[ndx], true));
+	}
+}
+
+class ComputeShaderGeneratedCase : public TestCase
+{
+public:
+	enum DrawMethod
+	{
+		DRAWMETHOD_DRAWARRAYS,
+		DRAWMETHOD_DRAWELEMENTS,
+		DRAWMETHOD_LAST
+	};
+
+						ComputeShaderGeneratedCase	(Context& context, const char* name, const char* desc, DrawMethod method, bool computeCmd, bool computeData, bool computeIndices, int gridSize, int drawCallCount);
+						~ComputeShaderGeneratedCase	(void);
+	void				init						(void);
+	void				deinit						(void);
+
+	IterateResult		iterate						(void);
+	std::string			genComputeSource			(bool computeCmd, bool computeData, bool computeIndices) const;
+
+private:
+	void				createDrawCommand			(void);
+	void				createDrawData				(void);
+	void				createDrawIndices			(void);
+
+	virtual void		runComputeShader			(void) = 0;
+	void				renderTo					(tcu::Surface& image);
+
+protected:
+	deUint32			calcDrawBufferSize			(void) const;
+	deUint32			calcIndexBufferSize			(void) const;
+
+	const DrawMethod	m_drawMethod;
+	const bool			m_computeCmd;
+	const bool			m_computeData;
+	const bool			m_computeIndices;
+	const int			m_commandSize;
+	const int			m_numDrawCmds;
+	const int			m_gridSize;
+
+	glw::GLuint			m_cmdBufferID;
+	glw::GLuint			m_dataBufferID;
+	glw::GLuint			m_indexBufferID;
+
+private:
+	glu::ShaderProgram*	m_shaderProgram;
+};
+
+ComputeShaderGeneratedCase::ComputeShaderGeneratedCase (Context& context, const char* name, const char* desc, DrawMethod method, bool computeCmd, bool computeData, bool computeIndices, int gridSize, int drawCallCount)
+	: TestCase			(context, name, desc)
+	, m_drawMethod		(method)
+	, m_computeCmd		(computeCmd)
+	, m_computeData		(computeData)
+	, m_computeIndices	(computeIndices)
+	, m_commandSize		((method==DRAWMETHOD_DRAWARRAYS) ? (sizeof(DrawArraysCommand)) : (sizeof(DrawElementsCommand)))
+	, m_numDrawCmds		(drawCallCount)
+	, m_gridSize		(gridSize)
+	, m_cmdBufferID		(0)
+	, m_dataBufferID	(0)
+	, m_indexBufferID	(0)
+	, m_shaderProgram	(DE_NULL)
+{
+    const int triangleCount	= m_gridSize * m_gridSize * 2;
+
+	DE_ASSERT(method < DRAWMETHOD_LAST);
+	DE_ASSERT(!computeIndices || method == DRAWMETHOD_DRAWELEMENTS);
+	DE_ASSERT(triangleCount % m_numDrawCmds == 0);
+	DE_UNREF(triangleCount);
+}
+
+ComputeShaderGeneratedCase::~ComputeShaderGeneratedCase (void)
+{
+	deinit();
+}
+
+void ComputeShaderGeneratedCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// generate basic shader
+
+	m_shaderProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_colorVertexShaderSource) << glu::FragmentSource(s_colorFragmentShaderSource));
+	m_testCtx.getLog() << *m_shaderProgram;
+
+	if (!m_shaderProgram->isOk())
+		throw tcu::TestError("Failed to compile shader.");
+
+	// gen buffers
+	gl.genBuffers(1, &m_cmdBufferID);
+	gl.genBuffers(1, &m_dataBufferID);
+	gl.genBuffers(1, &m_indexBufferID);
+
+	// check the SSBO buffers are of legal size
+	{
+		const int	commandBufferSize	= m_commandSize * m_numDrawCmds;
+		deInt64		maxSSBOSize			= 0;
+
+		gl.getInteger64v(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxSSBOSize);
+
+		if (m_computeData && (deInt64)calcDrawBufferSize() > maxSSBOSize)
+			throw tcu::NotSupportedError("GL_MAX_SHADER_STORAGE_BLOCK_SIZE is too small for vertex attrib buffers");
+		if (m_computeIndices && (deInt64)calcIndexBufferSize() > maxSSBOSize)
+			throw tcu::NotSupportedError("GL_MAX_SHADER_STORAGE_BLOCK_SIZE is too small for index buffers");
+		if (m_computeCmd && (deInt64)commandBufferSize > maxSSBOSize)
+			throw tcu::NotSupportedError("GL_MAX_SHADER_STORAGE_BLOCK_SIZE is too small for command buffers");
+	}
+}
+
+void ComputeShaderGeneratedCase::deinit (void)
+{
+	if (m_cmdBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_cmdBufferID);
+		m_cmdBufferID = 0;
+	}
+	if (m_dataBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_dataBufferID);
+		m_dataBufferID = 0;
+	}
+	if (m_indexBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_indexBufferID);
+		m_indexBufferID = 0;
+	}
+
+	if (m_shaderProgram)
+	{
+		delete m_shaderProgram;
+		m_shaderProgram = DE_NULL;
+	}
+}
+
+ComputeShaderGeneratedCase::IterateResult ComputeShaderGeneratedCase::iterate (void)
+{
+	const int				renderTargetWidth	= de::min(1024, m_context.getRenderTarget().getWidth());
+	const int				renderTargetHeight	= de::min(1024, m_context.getRenderTarget().getHeight());
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	tcu::Surface			surface				(renderTargetWidth, renderTargetHeight);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Preparing to draw " << m_gridSize << " x " << m_gridSize << " grid." << tcu::TestLog::EndMessage;
+
+	try
+	{
+		// Gen command buffer
+		if (!m_computeCmd)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Uploading draw command buffer." << tcu::TestLog::EndMessage;
+			createDrawCommand();
+		}
+
+		// Gen data buffer
+		if (!m_computeData)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Uploading draw data buffer." << tcu::TestLog::EndMessage;
+			createDrawData();
+		}
+
+		// Gen index buffer
+		if (!m_computeIndices && m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Uploading draw index buffer." << tcu::TestLog::EndMessage;
+			createDrawIndices();
+		}
+
+		// Run compute shader
+		{
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message << "Filling following buffers using compute shader:\n"
+				<< ((m_computeCmd)		? ("\tcommand buffer\n")	: (""))
+				<< ((m_computeData)		? ("\tdata buffer\n")		: (""))
+				<< ((m_computeIndices)	? ("\tindex buffer\n")		: (""))
+				<< tcu::TestLog::EndMessage;
+			runComputeShader();
+		}
+
+		// Ensure data is written to the buffers before we try to read it
+		{
+			const glw::GLuint barriers = ((m_computeCmd)     ? (GL_COMMAND_BARRIER_BIT)             : (0)) |
+										 ((m_computeData)    ? (GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT) : (0)) |
+										 ((m_computeIndices) ? (GL_ELEMENT_ARRAY_BARRIER_BIT)       : (0));
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Memory barrier. Barriers = " << glu::getMemoryBarrierFlagsStr(barriers) << tcu::TestLog::EndMessage;
+			gl.memoryBarrier(barriers);
+		}
+
+		// Draw from buffers
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Drawing from buffers with " << m_numDrawCmds << " draw call(s)." << tcu::TestLog::EndMessage;
+		renderTo(surface);
+	}
+	catch (glu::OutOfMemoryError&)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Got GL_OUT_OF_MEMORY." << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Got GL_OUT_OF_MEMORY");
+		return STOP;
+	}
+
+
+	// verify image
+	// \note the green/yellow pattern is only for clarity. The test will only verify that all grid cells were drawn by looking for anything non-green/yellow.
+	if (verifyImageYellowGreen(surface, m_testCtx.getLog()))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result image invalid");
+	return STOP;
+}
+
+std::string ComputeShaderGeneratedCase::genComputeSource (bool computeCmd, bool computeData, bool computeIndices) const
+{
+	const int cmdLayoutBinding				= 0;
+	const int dataLayoutBinding				= (computeCmd) ? (1) : (0);
+	const int indexLayoutBinding			= (computeCmd && computeData) ? (2) : (computeCmd || computeData) ? (1) : (0);
+
+	std::ostringstream buf;
+
+	buf << "#version 310 es\n\n"
+		<< "precision highp int;\n"
+		<< "precision highp float;\n\n";
+
+	if (computeCmd && m_drawMethod==DRAWMETHOD_DRAWARRAYS)
+		buf	<< "struct DrawArraysIndirectCommand {\n"
+			<< "    uint count;\n"
+			<< "    uint primCount;\n"
+			<< "    uint first;\n"
+			<< "    uint reservedMustBeZero;\n"
+			<< "};\n\n";
+	else if (computeCmd && m_drawMethod==DRAWMETHOD_DRAWELEMENTS)
+		buf	<< "struct DrawElementsIndirectCommand {\n"
+			<< "    uint count;\n"
+			<< "    uint primCount;\n"
+			<< "    uint firstIndex;\n"
+			<< "    int  baseVertex;\n"
+			<< "    uint reservedMustBeZero;\n"
+			<< "};\n\n";
+
+	buf << "layout(local_size_x = 1, local_size_y = 1) in;\n"
+		<< "layout(std430) buffer;\n\n";
+
+	if (computeCmd)
+		buf	<< "layout(binding = " << cmdLayoutBinding << ") writeonly buffer CommandBuffer {\n"
+			<< "    " << ((m_drawMethod==DRAWMETHOD_DRAWARRAYS) ? ("DrawArraysIndirectCommand") : ("DrawElementsIndirectCommand")) << " commands[];\n"
+			<< "};\n";
+	if (computeData)
+		buf	<< "layout(binding = " << dataLayoutBinding << ") writeonly buffer DataBuffer {\n"
+			<< "    vec4 attribs[];\n"
+			<< "};\n";
+	if (computeIndices)
+		buf	<< "layout(binding = " << indexLayoutBinding << ") writeonly buffer IndexBuffer {\n"
+			<< "    uint indices[];\n"
+			<< "};\n";
+
+	buf	<< "\n"
+		<< "void main() {\n"
+		<< "    const uint gridSize      = " << m_gridSize << "u;\n"
+		<< "    const uint triangleCount = gridSize * gridSize * 2u;\n"
+		<< "\n";
+
+	if (computeCmd)
+	{
+		buf	<< "    // command\n"
+			<< "    if (gl_GlobalInvocationID.x < " << m_numDrawCmds << "u && gl_GlobalInvocationID.y == 0u && gl_GlobalInvocationID.z == 0u) {\n"
+			<< "        const uint numDrawCallTris = triangleCount / " << m_numDrawCmds << "u;\n"
+			<< "        uint firstTri              = gl_GlobalInvocationID.x * numDrawCallTris;\n\n"
+			<< "        commands[gl_GlobalInvocationID.x].count                 = numDrawCallTris*3u;\n"
+			<< "        commands[gl_GlobalInvocationID.x].primCount             = 1u;\n";
+
+		if (m_drawMethod==DRAWMETHOD_DRAWARRAYS)
+		{
+			buf	<< "        commands[gl_GlobalInvocationID.x].first                 = firstTri*3u;\n";
+		}
+		else if (m_drawMethod==DRAWMETHOD_DRAWELEMENTS)
+		{
+			buf	<< "        commands[gl_GlobalInvocationID.x].firstIndex            = firstTri*3u;\n";
+			buf	<< "        commands[gl_GlobalInvocationID.x].baseVertex            = 0;\n";
+		}
+
+		buf	<< "        commands[gl_GlobalInvocationID.x].reservedMustBeZero    = 0u;\n"
+			<< "    }\n"
+			<< "\n";
+	}
+
+	if (computeData)
+	{
+		buf	<< "    // vertex attribs\n"
+			<< "    const vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
+			<< "    const vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n";
+
+		if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
+		{
+			buf	<< "    if (gl_GlobalInvocationID.x < gridSize && gl_GlobalInvocationID.y < gridSize && gl_GlobalInvocationID.z == 0u) {\n"
+				<< "        uint        y           = gl_GlobalInvocationID.x;\n"
+				<< "        uint        x           = gl_GlobalInvocationID.y;\n"
+				<< "        float       posX        = (float(x) / float(gridSize)) * 2.0 - 1.0;\n"
+				<< "        float       posY        = (float(y) / float(gridSize)) * 2.0 - 1.0;\n"
+				<< "        const float cellSize    = 2.0 / float(gridSize);\n"
+				<< "        vec4        color       = ((x + y)%2u != 0u) ? (yellow) : (green);\n"
+				<< "\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 0u) * 2u + 0u] = vec4(posX,            posY,            0.0, 1.0);\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 1u) * 2u + 0u] = vec4(posX + cellSize, posY,            0.0, 1.0);\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 2u) * 2u + 0u] = vec4(posX + cellSize, posY + cellSize, 0.0, 1.0);\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 3u) * 2u + 0u] = vec4(posX,            posY,            0.0, 1.0);\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 4u) * 2u + 0u] = vec4(posX + cellSize, posY + cellSize, 0.0, 1.0);\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 5u) * 2u + 0u] = vec4(posX,            posY + cellSize, 0.0, 1.0);\n"
+				<< "\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 0u) * 2u + 1u] = color;\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 1u) * 2u + 1u] = color;\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 2u) * 2u + 1u] = color;\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 3u) * 2u + 1u] = color;\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 4u) * 2u + 1u] = color;\n"
+				<< "        attribs[((y * gridSize + x) * 6u + 5u) * 2u + 1u] = color;\n"
+				<< "    }\n";
+		}
+		else if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
+		{
+			buf	<< "    if (gl_GlobalInvocationID.x < gridSize+1u && gl_GlobalInvocationID.y < gridSize+1u && gl_GlobalInvocationID.z == 0u) {\n"
+				<< "        uint        y           = gl_GlobalInvocationID.x;\n"
+				<< "        uint        x           = gl_GlobalInvocationID.y;\n"
+				<< "        float       posX        = (float(x) / float(gridSize)) * 2.0 - 1.0;\n"
+				<< "        float       posY        = (float(y) / float(gridSize)) * 2.0 - 1.0;\n"
+				<< "\n"
+				<< "        attribs[(y * (gridSize+1u) + x) * 4u + 0u] = vec4(posX, posY, 0.0, 1.0);\n"
+				<< "        attribs[(y * (gridSize+1u) + x) * 4u + 1u] = green;\n"
+				<< "        attribs[(y * (gridSize+1u) + x) * 4u + 2u] = vec4(posX, posY, 0.0, 1.0);\n"
+				<< "        attribs[(y * (gridSize+1u) + x) * 4u + 3u] = yellow;\n"
+				<< "    }\n";
+		}
+
+		buf << "\n";
+	}
+
+	if (computeIndices)
+	{
+		buf	<< "    // indices\n"
+			<< "    if (gl_GlobalInvocationID.x < gridSize && gl_GlobalInvocationID.y < gridSize && gl_GlobalInvocationID.z == 0u) {\n"
+			<< "        uint    y       = gl_GlobalInvocationID.x;\n"
+			<< "        uint    x       = gl_GlobalInvocationID.y;\n"
+			<< "        uint    color   = ((x + y)%2u);\n"
+			<< "\n"
+			<< "        indices[(y * gridSize + x) * 6u + 0u] = ((y+0u) * (gridSize+1u) + (x+0u)) * 2u + color;\n"
+			<< "        indices[(y * gridSize + x) * 6u + 1u] = ((y+1u) * (gridSize+1u) + (x+0u)) * 2u + color;\n"
+			<< "        indices[(y * gridSize + x) * 6u + 2u] = ((y+1u) * (gridSize+1u) + (x+1u)) * 2u + color;\n"
+			<< "        indices[(y * gridSize + x) * 6u + 3u] = ((y+0u) * (gridSize+1u) + (x+0u)) * 2u + color;\n"
+			<< "        indices[(y * gridSize + x) * 6u + 4u] = ((y+1u) * (gridSize+1u) + (x+1u)) * 2u + color;\n"
+			<< "        indices[(y * gridSize + x) * 6u + 5u] = ((y+0u) * (gridSize+1u) + (x+1u)) * 2u + color;\n"
+			<< "    }\n"
+			<< "\n";
+	}
+
+	buf	<< "}\n";
+
+	return buf.str();
+}
+
+void ComputeShaderGeneratedCase::createDrawCommand (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const int				triangleCount	= m_gridSize * m_gridSize * 2;
+	const deUint32			numDrawCallTris	= triangleCount / m_numDrawCmds;
+
+	if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
+	{
+		std::vector<DrawArraysCommand> cmds;
+
+		for (int ndx = 0; ndx < m_numDrawCmds; ++ndx)
+		{
+			const deUint32				firstTri = ndx * numDrawCallTris;
+			DrawArraysCommand			data;
+
+			data.count					= numDrawCallTris*3;
+			data.primCount				= 1;
+			data.first					= firstTri*3;
+			data.reservedMustBeZero		= 0;
+
+			cmds.push_back(data);
+		}
+
+		DE_ASSERT((int)(sizeof(DrawArraysCommand)*cmds.size()) == m_numDrawCmds * m_commandSize);
+
+		gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, m_cmdBufferID);
+		gl.bufferData(GL_DRAW_INDIRECT_BUFFER, (glw::GLsizeiptr)(sizeof(DrawArraysCommand)*cmds.size()), &cmds[0], GL_STATIC_DRAW);
+	}
+	else if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
+	{
+		std::vector<DrawElementsCommand> cmds;
+
+		for (int ndx = 0; ndx < m_numDrawCmds; ++ndx)
+		{
+			const deUint32			firstTri = ndx * numDrawCallTris;
+			DrawElementsCommand		data;
+
+			data.count				= numDrawCallTris*3;
+			data.primCount			= 1;
+			data.firstIndex			= firstTri*3;
+			data.baseVertex			= 0;
+			data.reservedMustBeZero	= 0;
+
+			cmds.push_back(data);
+		}
+
+		DE_ASSERT((int)(sizeof(DrawElementsCommand)*cmds.size()) == m_numDrawCmds * m_commandSize);
+
+		gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, m_cmdBufferID);
+		gl.bufferData(GL_DRAW_INDIRECT_BUFFER, (glw::GLsizeiptr)(sizeof(DrawElementsCommand)*cmds.size()), &cmds[0], GL_STATIC_DRAW);
+	}
+	else
+		DE_ASSERT(false);
+
+	glu::checkError(gl.getError(), "create draw command", __FILE__, __LINE__);
+}
+
+void ComputeShaderGeneratedCase::createDrawData (void)
+{
+	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 glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+
+	if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
+	{
+		// Store elements in the order they are drawn. Interleave color.
+		std::vector<tcu::Vec4> buffer(m_gridSize*m_gridSize*6*2);
+
+		DE_ASSERT(buffer.size() == calcDrawBufferSize());
+
+		for (int y = 0; y < m_gridSize; ++y)
+		for (int x = 0; x < m_gridSize; ++x)
+		{
+			const float 		posX		= (x / (float)m_gridSize) * 2.0f - 1.0f;
+			const float 		posY		= (y / (float)m_gridSize) * 2.0f - 1.0f;
+			const float			cellSize	= 2.0f / (float)m_gridSize;
+			const tcu::Vec4&	color		= ((x + y)%2) ? (yellow) : (green);
+
+			buffer[((y * m_gridSize + x) * 6 + 0) * 2 + 0] = tcu::Vec4(posX,			posY,				0.0f, 1.0f);
+			buffer[((y * m_gridSize + x) * 6 + 1) * 2 + 0] = tcu::Vec4(posX + cellSize,	posY,				0.0f, 1.0f);
+			buffer[((y * m_gridSize + x) * 6 + 2) * 2 + 0] = tcu::Vec4(posX + cellSize,	posY + cellSize,	0.0f, 1.0f);
+			buffer[((y * m_gridSize + x) * 6 + 3) * 2 + 0] = tcu::Vec4(posX,			posY,				0.0f, 1.0f);
+			buffer[((y * m_gridSize + x) * 6 + 4) * 2 + 0] = tcu::Vec4(posX + cellSize, posY + cellSize,	0.0f, 1.0f);
+			buffer[((y * m_gridSize + x) * 6 + 5) * 2 + 0] = tcu::Vec4(posX,			posY + cellSize,	0.0f, 1.0f);
+
+			buffer[((y * m_gridSize + x) * 6 + 0) * 2 + 1] = color;
+			buffer[((y * m_gridSize + x) * 6 + 1) * 2 + 1] = color;
+			buffer[((y * m_gridSize + x) * 6 + 2) * 2 + 1] = color;
+			buffer[((y * m_gridSize + x) * 6 + 3) * 2 + 1] = color;
+			buffer[((y * m_gridSize + x) * 6 + 4) * 2 + 1] = color;
+			buffer[((y * m_gridSize + x) * 6 + 5) * 2 + 1] = color;
+		}
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBufferID);
+		gl.bufferData(GL_ARRAY_BUFFER, (int)(buffer.size() * sizeof(tcu::Vec4)), buffer[0].getPtr(), GL_STATIC_DRAW);
+	}
+	else if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
+	{
+		// Elements are indexed by index buffer. Interleave color. Two vertices per position since 2 colors
+
+		std::vector<tcu::Vec4> buffer((m_gridSize+1)*(m_gridSize+1)*4);
+
+		DE_ASSERT(buffer.size() == calcDrawBufferSize());
+
+		for (int y = 0; y < m_gridSize+1; ++y)
+		for (int x = 0; x < m_gridSize+1; ++x)
+		{
+			const float 		posX		= (x / (float)m_gridSize) * 2.0f - 1.0f;
+			const float 		posY		= (y / (float)m_gridSize) * 2.0f - 1.0f;
+
+			buffer[(y * (m_gridSize+1) + x) * 4 + 0] = tcu::Vec4(posX, posY, 0.0f, 1.0f);
+			buffer[(y * (m_gridSize+1) + x) * 4 + 1] = green;
+			buffer[(y * (m_gridSize+1) + x) * 4 + 2] = tcu::Vec4(posX, posY, 0.0f, 1.0f);
+			buffer[(y * (m_gridSize+1) + x) * 4 + 3] = yellow;
+		}
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBufferID);
+		gl.bufferData(GL_ARRAY_BUFFER, (int)(buffer.size() * sizeof(tcu::Vec4)), buffer[0].getPtr(), GL_STATIC_DRAW);
+	}
+	else
+		DE_ASSERT(false);
+
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+}
+
+void ComputeShaderGeneratedCase::createDrawIndices (void)
+{
+	DE_ASSERT(m_drawMethod == DRAWMETHOD_DRAWELEMENTS);
+
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	std::vector<deUint32>	buffer	(m_gridSize*m_gridSize*6);
+
+	DE_ASSERT(buffer.size() == calcIndexBufferSize());
+
+	for (int y = 0; y < m_gridSize; ++y)
+	for (int x = 0; x < m_gridSize; ++x)
+	{
+		const int color = ((x + y)%2);
+
+		buffer[(y * m_gridSize + x) * 6 + 0] = ((y+0) * (m_gridSize+1) + (x+0)) * 2 + color;
+		buffer[(y * m_gridSize + x) * 6 + 1] = ((y+1) * (m_gridSize+1) + (x+0)) * 2 + color;
+		buffer[(y * m_gridSize + x) * 6 + 2] = ((y+1) * (m_gridSize+1) + (x+1)) * 2 + color;
+		buffer[(y * m_gridSize + x) * 6 + 3] = ((y+0) * (m_gridSize+1) + (x+0)) * 2 + color;
+		buffer[(y * m_gridSize + x) * 6 + 4] = ((y+1) * (m_gridSize+1) + (x+1)) * 2 + color;
+		buffer[(y * m_gridSize + x) * 6 + 5] = ((y+0) * (m_gridSize+1) + (x+1)) * 2 + color;
+	}
+
+	gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferID);
+	gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (int)(buffer.size() * sizeof(deUint32)), &buffer[0], GL_STATIC_DRAW);
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+}
+
+void ComputeShaderGeneratedCase::renderTo (tcu::Surface& dst)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const deInt32			positionLoc = gl.getAttribLocation(m_shaderProgram->getProgram(), "a_position");
+	const deInt32			colorLoc	= gl.getAttribLocation(m_shaderProgram->getProgram(), "a_color");
+	deUint32				vaoID		= 0;
+
+	gl.genVertexArrays(1, &vaoID);
+	gl.bindVertexArray(vaoID);
+
+	// Setup buffers
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBufferID);
+	gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(float), DE_NULL);
+	gl.vertexAttribPointer(colorLoc,    4, GL_FLOAT, GL_FALSE, 8 * sizeof(float), ((const deUint8*)DE_NULL) + 4*sizeof(float));
+	gl.enableVertexAttribArray(positionLoc);
+	gl.enableVertexAttribArray(colorLoc);
+
+	DE_ASSERT(positionLoc != -1);
+	DE_ASSERT(colorLoc != -1);
+
+	if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferID);
+
+	gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, m_cmdBufferID);
+
+	// draw
+
+	gl.clearColor(0, 0, 0, 1);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	gl.viewport(0, 0, dst.getWidth(), dst.getHeight());
+
+	gl.useProgram(m_shaderProgram->getProgram());
+	for (int drawCmdNdx = 0; drawCmdNdx < m_numDrawCmds; ++drawCmdNdx)
+	{
+		const void* offset = ((deUint8*)DE_NULL) + drawCmdNdx*m_commandSize;
+
+		if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
+			gl.drawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, offset);
+		else if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
+			gl.drawArraysIndirect(GL_TRIANGLES, offset);
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+	gl.useProgram(0);
+
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+
+	// free
+
+	gl.deleteVertexArrays(1, &vaoID);
+
+	gl.finish();
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+}
+
+deUint32 ComputeShaderGeneratedCase::calcDrawBufferSize (void) const
+{
+	// returns size in "vec4"s
+	if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
+		return m_gridSize*m_gridSize*6*2;
+	else if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
+		return (m_gridSize+1)*(m_gridSize+1)*4;
+	else
+		DE_ASSERT(DE_FALSE);
+
+	return 0;
+}
+
+deUint32 ComputeShaderGeneratedCase::calcIndexBufferSize (void) const
+{
+	if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
+		return m_gridSize*m_gridSize*6;
+	else
+		return 0;
+}
+
+class ComputeShaderGeneratedCombinedCase : public ComputeShaderGeneratedCase
+{
+public:
+						ComputeShaderGeneratedCombinedCase	(Context& context, const char* name, const char* desc, DrawMethod method, bool computeCmd, bool computeData, bool computeIndices, int gridSize, int numDrawCalls);
+						~ComputeShaderGeneratedCombinedCase	(void);
+
+	void				init								(void);
+	void				deinit								(void);
+
+private:
+	void				runComputeShader					(void);
+
+	glu::ShaderProgram*	m_computeProgram;
+};
+
+ComputeShaderGeneratedCombinedCase::ComputeShaderGeneratedCombinedCase (Context& context, const char* name, const char* desc, DrawMethod method, bool computeCmd, bool computeData, bool computeIndices, int gridSize, int numDrawCalls)
+	: ComputeShaderGeneratedCase(context, name, desc, method, computeCmd, computeData, computeIndices, gridSize, numDrawCalls)
+	, m_computeProgram			(DE_NULL)
+{
+}
+
+ComputeShaderGeneratedCombinedCase::~ComputeShaderGeneratedCombinedCase (void)
+{
+	deinit();
+}
+
+void ComputeShaderGeneratedCombinedCase::init (void)
+{
+	// generate compute shader
+
+	m_computeProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genComputeSource(m_computeCmd, m_computeData, m_computeIndices)));
+	m_testCtx.getLog() << *m_computeProgram;
+
+	if (!m_computeProgram->isOk())
+		throw tcu::TestError("Failed to compile compute shader.");
+
+	// init parent
+	ComputeShaderGeneratedCase::init();
+}
+
+void ComputeShaderGeneratedCombinedCase::deinit (void)
+{
+	// deinit parent
+	ComputeShaderGeneratedCase::deinit();
+
+	if (m_computeProgram)
+	{
+		delete m_computeProgram;
+		m_computeProgram = DE_NULL;
+	}
+}
+
+void ComputeShaderGeneratedCombinedCase::runComputeShader (void)
+{
+	const glw::Functions&	gl									= m_context.getRenderContext().getFunctions();
+	const bool				indexed								= (m_drawMethod == DRAWMETHOD_DRAWELEMENTS);
+	const tcu::IVec3		nullSize							(0, 0, 0);
+	const tcu::IVec3		commandDispatchSize					= (m_computeCmd)				? (tcu::IVec3(m_numDrawCmds, 1, 1))				: (nullSize);
+	const tcu::IVec3		drawElementsDataBufferDispatchSize	= (m_computeData)				? (tcu::IVec3(m_gridSize+1, m_gridSize+1, 1))	: (nullSize);
+	const tcu::IVec3		drawArraysDataBufferDispatchSize	= (m_computeData)				? (tcu::IVec3(m_gridSize,   m_gridSize,   1))	: (nullSize);
+	const tcu::IVec3		indexBufferDispatchSize				= (m_computeIndices && indexed)	? (tcu::IVec3(m_gridSize,   m_gridSize,   1))	: (nullSize);
+
+	const tcu::IVec3		dataBufferDispatchSize				= (m_drawMethod == DRAWMETHOD_DRAWELEMENTS) ? (drawElementsDataBufferDispatchSize) : (drawArraysDataBufferDispatchSize);
+	const tcu::IVec3		dispatchSize						= tcu::max(tcu::max(commandDispatchSize, dataBufferDispatchSize), indexBufferDispatchSize);
+
+	gl.useProgram(m_computeProgram->getProgram());
+	glu::checkError(gl.getError(), "use compute shader", __FILE__, __LINE__);
+
+	// setup buffers
+
+	if (m_computeCmd)
+	{
+		const int			bindingPoint	= 0;
+		const int			bufferSize		= m_commandSize * m_numDrawCmds;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Binding command buffer to binding point " << bindingPoint << tcu::TestLog::EndMessage;
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_cmdBufferID);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for command buffer, size " << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);
+	}
+
+	if (m_computeData)
+	{
+		const int			bindingPoint	= (m_computeCmd) ? (1) : (0);
+		const int			bufferSize		= (int)(calcDrawBufferSize()*sizeof(tcu::Vec4));
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Binding data buffer to binding point " << bindingPoint << tcu::TestLog::EndMessage;
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_dataBufferID);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for data buffer, size " << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);
+	}
+
+	if (m_computeIndices)
+	{
+		const int			bindingPoint	= (m_computeCmd && m_computeData) ? (2) : (m_computeCmd || m_computeData) ? (1) : (0);
+		const int			bufferSize		= (int)(calcIndexBufferSize()*sizeof(deUint32));
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Binding index buffer to binding point " << bindingPoint << tcu::TestLog::EndMessage;
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_indexBufferID);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for index buffer, size " << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);
+	}
+
+	glu::checkError(gl.getError(), "setup buffers", __FILE__, __LINE__);
+
+	// calculate
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Dispatching compute, size = " << dispatchSize << tcu::TestLog::EndMessage;
+	gl.dispatchCompute(dispatchSize.x(), dispatchSize.y(), dispatchSize.z());
+
+	glu::checkError(gl.getError(), "calculate", __FILE__, __LINE__);
+}
+
+class ComputeShaderGeneratedSeparateCase : public ComputeShaderGeneratedCase
+{
+public:
+						ComputeShaderGeneratedSeparateCase	(Context& context, const char* name, const char* desc, DrawMethod method, bool computeCmd, bool computeData, bool computeIndices, int gridSize, int numDrawCalls);
+						~ComputeShaderGeneratedSeparateCase	(void);
+
+	void				init								(void);
+	void				deinit								(void);
+
+private:
+	std::string			genCmdComputeSource					(void);
+	std::string			genDataComputeSource				(void);
+	std::string			genIndexComputeSource				(void);
+	void				runComputeShader					(void);
+
+	glu::ShaderProgram*	m_computeCmdProgram;
+	glu::ShaderProgram*	m_computeDataProgram;
+	glu::ShaderProgram*	m_computeIndicesProgram;
+};
+
+ComputeShaderGeneratedSeparateCase::ComputeShaderGeneratedSeparateCase (Context& context, const char* name, const char* desc, DrawMethod method, bool computeCmd, bool computeData, bool computeIndices, int gridSize, int numDrawCalls)
+	: ComputeShaderGeneratedCase	(context, name, desc, method, computeCmd, computeData, computeIndices, gridSize, numDrawCalls)
+	, m_computeCmdProgram			(DE_NULL)
+	, m_computeDataProgram			(DE_NULL)
+	, m_computeIndicesProgram		(DE_NULL)
+{
+}
+
+ComputeShaderGeneratedSeparateCase::~ComputeShaderGeneratedSeparateCase (void)
+{
+	deinit();
+}
+
+void ComputeShaderGeneratedSeparateCase::init (void)
+{
+	// generate cmd compute shader
+
+	if (m_computeCmd)
+	{
+		m_computeCmdProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genCmdComputeSource()));
+		m_testCtx.getLog() << *m_computeCmdProgram;
+
+		if (!m_computeCmdProgram->isOk())
+			throw tcu::TestError("Failed to compile command compute shader.");
+	}
+
+	// generate data compute shader
+
+	if (m_computeData)
+	{
+		m_computeDataProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genDataComputeSource()));
+		m_testCtx.getLog() << *m_computeDataProgram;
+
+		if (!m_computeDataProgram->isOk())
+			throw tcu::TestError("Failed to compile data compute shader.");
+	}
+
+	// generate index compute shader
+
+	if (m_computeIndices)
+	{
+		m_computeIndicesProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genIndexComputeSource()));
+		m_testCtx.getLog() << *m_computeIndicesProgram;
+
+		if (!m_computeIndicesProgram->isOk())
+			throw tcu::TestError("Failed to compile data compute shader.");
+	}
+
+	// init parent
+	ComputeShaderGeneratedCase::init();
+}
+
+void ComputeShaderGeneratedSeparateCase::deinit (void)
+{
+	// deinit parent
+	ComputeShaderGeneratedCase::deinit();
+
+	if (m_computeCmdProgram)
+	{
+		delete m_computeCmdProgram;
+		m_computeCmdProgram = DE_NULL;
+	}
+	if (m_computeDataProgram)
+	{
+		delete m_computeDataProgram;
+		m_computeDataProgram = DE_NULL;
+	}
+	if (m_computeIndicesProgram)
+	{
+		delete m_computeIndicesProgram;
+		m_computeIndicesProgram = DE_NULL;
+	}
+}
+
+std::string ComputeShaderGeneratedSeparateCase::genCmdComputeSource (void)
+{
+	return ComputeShaderGeneratedCase::genComputeSource(true, false, false);
+}
+
+std::string ComputeShaderGeneratedSeparateCase::genDataComputeSource (void)
+{
+	return ComputeShaderGeneratedCase::genComputeSource(false, true, false);
+}
+
+std::string ComputeShaderGeneratedSeparateCase::genIndexComputeSource (void)
+{
+	return ComputeShaderGeneratedCase::genComputeSource(false, false, true);
+}
+
+void ComputeShaderGeneratedSeparateCase::runComputeShader (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Compute command
+
+	if (m_computeCmd)
+	{
+		const int				bindingPoint			= 0;
+		const tcu::IVec3		commandDispatchSize		(m_numDrawCmds, 1, 1);
+		const int				bufferSize				= m_commandSize * m_numDrawCmds;
+
+		gl.useProgram(m_computeCmdProgram->getProgram());
+
+		// setup buffers
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Binding command buffer to binding point " << bindingPoint << tcu::TestLog::EndMessage;
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_cmdBufferID);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for command buffer, size " << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);
+
+		// calculate
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Dispatching command compute, size = " << commandDispatchSize << tcu::TestLog::EndMessage;
+		gl.dispatchCompute(commandDispatchSize.x(), commandDispatchSize.y(), commandDispatchSize.z());
+
+		glu::checkError(gl.getError(), "calculate cmd", __FILE__, __LINE__);
+	}
+
+	// Compute data
+
+	if (m_computeData)
+	{
+		const int				bindingPoint						= 0;
+		const tcu::IVec3		drawElementsDataBufferDispatchSize	(m_gridSize+1, m_gridSize+1, 1);
+		const tcu::IVec3		drawArraysDataBufferDispatchSize	(m_gridSize,   m_gridSize,   1);
+		const tcu::IVec3		dataBufferDispatchSize				= (m_drawMethod == DRAWMETHOD_DRAWELEMENTS) ? (drawElementsDataBufferDispatchSize) : (drawArraysDataBufferDispatchSize);
+		const int				bufferSize							= (int)(calcDrawBufferSize()*sizeof(tcu::Vec4));
+
+		gl.useProgram(m_computeDataProgram->getProgram());
+
+		// setup buffers
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Binding data buffer to binding point " << bindingPoint << tcu::TestLog::EndMessage;
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_dataBufferID);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for data buffer, size " << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);
+
+		// calculate
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Dispatching data compute, size = " << dataBufferDispatchSize << tcu::TestLog::EndMessage;
+		gl.dispatchCompute(dataBufferDispatchSize.x(), dataBufferDispatchSize.y(), dataBufferDispatchSize.z());
+
+		glu::checkError(gl.getError(), "calculate data", __FILE__, __LINE__);
+	}
+
+	// Compute indices
+
+	if (m_computeIndices)
+	{
+		const int				bindingPoint				= 0;
+		const tcu::IVec3		indexBufferDispatchSize		(m_gridSize, m_gridSize, 1);
+		const int				bufferSize					= (int)(calcIndexBufferSize()*sizeof(deUint32));
+
+		DE_ASSERT(m_drawMethod == DRAWMETHOD_DRAWELEMENTS);
+
+		gl.useProgram(m_computeIndicesProgram->getProgram());
+
+		// setup buffers
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Binding index buffer to binding point " << bindingPoint << tcu::TestLog::EndMessage;
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_indexBufferID);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for index buffer, size " << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);
+
+		// calculate
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Dispatching index compute, size = " << indexBufferDispatchSize << tcu::TestLog::EndMessage;
+		gl.dispatchCompute(indexBufferDispatchSize.x(), indexBufferDispatchSize.y(), indexBufferDispatchSize.z());
+
+		glu::checkError(gl.getError(), "calculate indices", __FILE__, __LINE__);
+	}
+
+	glu::checkError(gl.getError(), "post dispatch", __FILE__, __LINE__);
+}
+
+class ComputeShaderGeneratedGroup : public TestCaseGroup
+{
+public:
+			ComputeShaderGeneratedGroup		(Context& context, const char* name, const char* descr);
+			~ComputeShaderGeneratedGroup	(void);
+
+	void	init							(void);
+};
+
+ComputeShaderGeneratedGroup::ComputeShaderGeneratedGroup (Context& context, const char* name, const char* descr)
+	: TestCaseGroup	(context, name, descr)
+{
+}
+
+ComputeShaderGeneratedGroup::~ComputeShaderGeneratedGroup (void)
+{
+}
+
+void ComputeShaderGeneratedGroup::init (void)
+{
+	const int					gridSize		= 8;
+	tcu::TestCaseGroup* const	separateGroup	= new tcu::TestCaseGroup(m_testCtx, "separate", "Use separate compute shaders for each buffer");
+	tcu::TestCaseGroup* const	combinedGroup	= new tcu::TestCaseGroup(m_testCtx, "combined", "Use combined compute shader for all buffers");
+	tcu::TestCaseGroup* const	largeGroup		= new tcu::TestCaseGroup(m_testCtx, "large",   "Draw shapes with large buffers");
+
+	this->addChild(separateGroup);
+	this->addChild(combinedGroup);
+	this->addChild(largeGroup);
+
+	// .separate
+	{
+		separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, "drawarrays_compute_cmd",							"Command from compute shader",						ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS,		true,	false,	false,	gridSize,	1));
+		separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, "drawarrays_compute_data",						"Data from compute shader",							ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS,		false,	true,	false,	gridSize,	1));
+		separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, "drawarrays_compute_cmd_and_data",				"Command and data from compute shader",				ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS,		true,	true,	false,	gridSize,	1));
+
+		separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, "drawelements_compute_cmd",						"Command from compute shader",						ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	true,	false,	false,	gridSize,	1));
+		separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, "drawelements_compute_data",						"Data from compute shader",							ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	false,	true,	false,	gridSize,	1));
+		separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, "drawelements_compute_indices",					"Indices from compute shader",						ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	false,	false,	true,	gridSize,	1));
+		separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, "drawelements_compute_cmd_and_data",				"Command and data from compute shader",				ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	true,	true,	false,	gridSize,	1));
+		separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, "drawelements_compute_cmd_and_indices",			"Command and indices from compute shader",			ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	true,	false,	true,	gridSize,	1));
+		separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, "drawelements_compute_data_and_indices",			"Data and indices from compute shader",				ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	false,	true,	true,	gridSize,	1));
+		separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, "drawelements_compute_cmd_and_data_and_indices",	"Command, data and indices from compute shader",	ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	true,	true,	true,	gridSize,	1));
+	}
+
+	// .combined
+	{
+		combinedGroup->addChild(new ComputeShaderGeneratedCombinedCase(m_context, "drawarrays_compute_cmd_and_data",				"Command and data from compute shader",				ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS,		true,	true,	false,	gridSize,	1));
+		combinedGroup->addChild(new ComputeShaderGeneratedCombinedCase(m_context, "drawelements_compute_cmd_and_data",				"Command and data from compute shader",				ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	true,	true,	false,	gridSize,	1));
+		combinedGroup->addChild(new ComputeShaderGeneratedCombinedCase(m_context, "drawelements_compute_cmd_and_indices",			"Command and indices from compute shader",			ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	true,	false,	true,	gridSize,	1));
+		combinedGroup->addChild(new ComputeShaderGeneratedCombinedCase(m_context, "drawelements_compute_data_and_indices",			"Data and indices from compute shader",				ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	false,	true,	true,	gridSize,	1));
+		combinedGroup->addChild(new ComputeShaderGeneratedCombinedCase(m_context, "drawelements_compute_cmd_and_data_and_indices",	"Command, data and indices from compute shader",	ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	true,	true,	true,	gridSize,	1));
+	}
+
+	// .large
+	{
+		struct TestSpec
+		{
+			int gridSize;
+			int numDrawCommands;
+		};
+		struct TestMethod
+		{
+			ComputeShaderGeneratedCase::DrawMethod method;
+			bool                                   separateCompute;
+		};
+
+		static const TestSpec specs[] =
+		{
+			{ 100,	1 },		// !< drawArrays array size ~ 1.9 MB
+			{ 200,	1 },		// !< drawArrays array size ~ 7.7 MB
+			{ 500,	1 },		// !< drawArrays array size ~ 48 MB
+			{ 1000,	1 },		// !< drawArrays array size ~ 192 MB
+			{ 1200,	1 },		// !< drawArrays array size ~ 277 MB
+			{ 1500,	1 },		// !< drawArrays array size ~ 430 MB
+
+			{ 100,	8 },		// !< drawArrays array size ~ 1.9 MB
+			{ 200,	8 },		// !< drawArrays array size ~ 7.7 MB
+			{ 500,	8 },		// !< drawArrays array size ~ 48 MB
+			{ 1000,	8 },		// !< drawArrays array size ~ 192 MB
+			{ 1200,	8 },		// !< drawArrays array size ~ 277 MB
+			{ 1500,	8 },		// !< drawArrays array size ~ 430 MB
+
+			{ 100,	200  },		// !< 50 cells per draw call
+			{ 200,	800  },		// !< 50 cells per draw call
+			{ 500,	2500 },		// !< 100 cells per draw call
+			{ 1000,	5000 },		// !< 250 cells per draw call
+		};
+		static const TestMethod methods[] =
+		{
+			{ ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS,	true	},
+			{ ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS,	false	},
+			{ ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	true	},
+			{ ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS,	false	},
+		};
+
+		for (int methodNdx = 0; methodNdx < DE_LENGTH_OF_ARRAY(methods); ++methodNdx)
+		for (int specNdx = 0; specNdx < DE_LENGTH_OF_ARRAY(specs); ++specNdx)
+		{
+			const std::string name = std::string("")
+									+ ((methods[methodNdx].method == ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS) ? ("drawarrays") : ("drawelements"))
+									+ ((methods[methodNdx].separateCompute) ? ("_separate") : ("_combined"))
+									+ "_grid_" + de::toString(specs[specNdx].gridSize) + "x" + de::toString(specs[specNdx].gridSize)
+									+ "_drawcount_" + de::toString(specs[specNdx].numDrawCommands);
+
+			const std::string desc = std::string("Draw grid with ")
+									+ ((methods[methodNdx].method == ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS) ? ("drawarrays indirect") : ("drawelements indirect"))
+									+ " calculating buffers in " + ((methods[methodNdx].separateCompute) ? ("separate") : ("combined")) + " compute shader."
+									+ " Grid size is " + de::toString(specs[specNdx].gridSize) + "x" + de::toString(specs[specNdx].gridSize)
+									+ ", draw count is "  + de::toString(specs[specNdx].numDrawCommands);
+
+			const bool computeIndices = (methods[methodNdx].method == ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS);
+
+			if (methods[methodNdx].separateCompute)
+				largeGroup->addChild(new ComputeShaderGeneratedSeparateCase(m_context, name.c_str(), desc.c_str(), methods[methodNdx].method, false, true, computeIndices, specs[specNdx].gridSize, specs[specNdx].numDrawCommands));
+			else
+				largeGroup->addChild(new ComputeShaderGeneratedCombinedCase(m_context, name.c_str(), desc.c_str(), methods[methodNdx].method, false, true, computeIndices, specs[specNdx].gridSize, specs[specNdx].numDrawCommands));
+		}
+	}
+}
+
+class RandomGroup : public TestCaseGroup
+{
+public:
+			RandomGroup		(Context& context, const char* name, const char* descr);
+			~RandomGroup	(void);
+
+	void	init			(void);
+};
+
+template <int SIZE>
+struct UniformWeightArray
+{
+	float weights[SIZE];
+
+	UniformWeightArray (void)
+	{
+		for (int i=0; i<SIZE; ++i)
+			weights[i] = 1.0f;
+	}
+};
+
+RandomGroup::RandomGroup (Context& context, const char* name, const char* descr)
+	: TestCaseGroup	(context, name, descr)
+{
+}
+
+RandomGroup::~RandomGroup (void)
+{
+}
+
+void RandomGroup::init (void)
+{
+	const int	numAttempts				= 100;
+
+	const int	attribCounts[]			= { 1,   2,   5 };
+	const float	attribWeights[]			= { 30, 10,   1 };
+	const int	primitiveCounts[]		= { 1,   5,  64 };
+	const float	primitiveCountWeights[]	= { 20, 10,   1 };
+	const int	indexOffsets[]			= { 0,   7,  13 };
+	const float	indexOffsetWeights[]	= { 20, 20,   1 };
+	const int	firsts[]				= { 0,   7,  13 };
+	const float	firstWeights[]			= { 20, 20,   1 };
+
+	const int	instanceCounts[]		= { 1,   2,  16,  17 };
+	const float	instanceWeights[]		= { 20, 10,   5,   1 };
+	const int	indexMins[]				= { 0,   1,   3,   8 };
+	const int	indexMaxs[]				= { 4,   8, 128, 257 };
+	const float	indexWeights[]			= { 50, 50,  50,  50 };
+	const int	offsets[]				= { 0,   1,   5,  12 };
+	const float	offsetWeights[]			= { 50, 10,  10,  10 };
+	const int	strides[]				= { 0,   7,  16,  17 };
+	const float	strideWeights[]			= { 50, 10,  10,  10 };
+	const int	instanceDivisors[]		= { 0,   1,   3, 129 };
+	const float	instanceDivisorWeights[]= { 70, 30,  10,  10 };
+
+	const int	indirectOffsets[]		= { 0,   1,   2 };
+	const float indirectOffsetWeigths[]	= { 2,   1,   1 };
+	const int	baseVertices[]			= { 0,   1,  -2,   4,  3 };
+	const float baseVertexWeigths[]		= { 4,   1,   1,   1,  1 };
+
+	gls::DrawTestSpec::Primitive primitives[] =
+	{
+		gls::DrawTestSpec::PRIMITIVE_POINTS,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINES,
+		gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINE_LOOP
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(primitives)> primitiveWeights;
+
+	gls::DrawTestSpec::DrawMethod drawMethods[] =
+	{
+		gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(drawMethods)> drawMethodWeights;
+
+	gls::DrawTestSpec::IndexType indexTypes[] =
+	{
+		gls::DrawTestSpec::INDEXTYPE_BYTE,
+		gls::DrawTestSpec::INDEXTYPE_SHORT,
+		gls::DrawTestSpec::INDEXTYPE_INT,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(indexTypes)> indexTypeWeights;
+
+	gls::DrawTestSpec::InputType inputTypes[] =
+	{
+		gls::DrawTestSpec::INPUTTYPE_FLOAT,
+		gls::DrawTestSpec::INPUTTYPE_FIXED,
+		gls::DrawTestSpec::INPUTTYPE_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_SHORT,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT,
+		gls::DrawTestSpec::INPUTTYPE_INT,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT,
+		gls::DrawTestSpec::INPUTTYPE_HALF,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		gls::DrawTestSpec::INPUTTYPE_INT_2_10_10_10,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(inputTypes)> inputTypeWeights;
+
+	gls::DrawTestSpec::OutputType outputTypes[] =
+	{
+		gls::DrawTestSpec::OUTPUTTYPE_FLOAT,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC4,
+		gls::DrawTestSpec::OUTPUTTYPE_INT,
+		gls::DrawTestSpec::OUTPUTTYPE_UINT,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC4,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC4,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(outputTypes)> outputTypeWeights;
+
+	gls::DrawTestSpec::Usage usages[] =
+	{
+		gls::DrawTestSpec::USAGE_DYNAMIC_DRAW,
+		gls::DrawTestSpec::USAGE_STATIC_DRAW,
+		gls::DrawTestSpec::USAGE_STREAM_DRAW,
+		gls::DrawTestSpec::USAGE_STREAM_READ,
+		gls::DrawTestSpec::USAGE_STREAM_COPY,
+		gls::DrawTestSpec::USAGE_STATIC_READ,
+		gls::DrawTestSpec::USAGE_STATIC_COPY,
+		gls::DrawTestSpec::USAGE_DYNAMIC_READ,
+		gls::DrawTestSpec::USAGE_DYNAMIC_COPY,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(usages)> usageWeights;
+
+	std::set<deUint32>	insertedHashes;
+	size_t				insertedCount = 0;
+
+	for (int ndx = 0; ndx < numAttempts; ++ndx)
+	{
+		de::Random random(0xc551393 + ndx); // random does not depend on previous cases
+
+		int					attributeCount = random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(attribCounts), DE_ARRAY_END(attribCounts), attribWeights);
+		int					drawCommandSize;
+		gls::DrawTestSpec	spec;
+
+		spec.apiType				= glu::ApiType::es(3,1);
+		spec.primitive				= random.chooseWeighted<gls::DrawTestSpec::Primitive>	(DE_ARRAY_BEGIN(primitives),		DE_ARRAY_END(primitives),		primitiveWeights.weights);
+		spec.primitiveCount			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(primitiveCounts),	DE_ARRAY_END(primitiveCounts),	primitiveCountWeights);
+		spec.drawMethod				= random.chooseWeighted<gls::DrawTestSpec::DrawMethod>	(DE_ARRAY_BEGIN(drawMethods),		DE_ARRAY_END(drawMethods),		drawMethodWeights.weights);
+
+		if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT)
+			drawCommandSize = sizeof(deUint32[4]);
+		else if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT)
+			drawCommandSize = sizeof(deUint32[5]);
+		else
+		{
+			DE_ASSERT(DE_FALSE);
+			return;
+		}
+
+		spec.indexType				= random.chooseWeighted<gls::DrawTestSpec::IndexType>	(DE_ARRAY_BEGIN(indexTypes),		DE_ARRAY_END(indexTypes),		indexTypeWeights.weights);
+		spec.indexPointerOffset		= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexOffsets),		DE_ARRAY_END(indexOffsets),		indexOffsetWeights);
+		spec.indexStorage			= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.first					= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(firsts),			DE_ARRAY_END(firsts),			firstWeights);
+		spec.indexMin				= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexMins),			DE_ARRAY_END(indexMins),		indexWeights);
+		spec.indexMax				= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexMaxs),			DE_ARRAY_END(indexMaxs),		indexWeights);
+		spec.instanceCount			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(instanceCounts),	DE_ARRAY_END(instanceCounts),	instanceWeights);
+		spec.indirectOffset			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indirectOffsets),	DE_ARRAY_END(indirectOffsets),	indirectOffsetWeigths) * drawCommandSize;
+		spec.baseVertex				= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(baseVertices),		DE_ARRAY_END(baseVertices),		baseVertexWeigths);
+
+		// check spec is legal
+		if (!spec.valid())
+			continue;
+
+		for (int attrNdx = 0; attrNdx < attributeCount;)
+		{
+			bool valid;
+			gls::DrawTestSpec::AttributeSpec attribSpec;
+
+			attribSpec.inputType			= random.chooseWeighted<gls::DrawTestSpec::InputType>	(DE_ARRAY_BEGIN(inputTypes),		DE_ARRAY_END(inputTypes),		inputTypeWeights.weights);
+			attribSpec.outputType			= random.chooseWeighted<gls::DrawTestSpec::OutputType>	(DE_ARRAY_BEGIN(outputTypes),		DE_ARRAY_END(outputTypes),		outputTypeWeights.weights);
+			attribSpec.storage				= gls::DrawTestSpec::STORAGE_BUFFER;
+			attribSpec.usage				= random.chooseWeighted<gls::DrawTestSpec::Usage>		(DE_ARRAY_BEGIN(usages),			DE_ARRAY_END(usages),			usageWeights.weights);
+			attribSpec.componentCount		= random.getInt(1, 4);
+			attribSpec.offset				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), offsetWeights);
+			attribSpec.stride				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(strides), DE_ARRAY_END(strides), strideWeights);
+			attribSpec.normalize			= random.getBool();
+			attribSpec.instanceDivisor		= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(instanceDivisors), DE_ARRAY_END(instanceDivisors), instanceDivisorWeights);
+			attribSpec.useDefaultAttribute	= random.getBool();
+
+			// check spec is legal
+			valid = attribSpec.valid(spec.apiType);
+
+			// we do not want interleaved elements. (Might result in some weird floating point values)
+			if (attribSpec.stride && attribSpec.componentCount * gls::DrawTestSpec::inputTypeSize(attribSpec.inputType) > attribSpec.stride)
+				valid = false;
+
+			// try again if not valid
+			if (valid)
+			{
+				spec.attribs.push_back(attribSpec);
+				++attrNdx;
+			}
+		}
+
+		// Do not collapse all vertex positions to a single positions
+		if (spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+			spec.attribs[0].instanceDivisor = 0;
+
+		// Is render result meaningful?
+		{
+			// Only one vertex
+			if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && spec.indexMin == spec.indexMax && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+			if (spec.attribs[0].useDefaultAttribute && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+
+			// Triangle only on one axis
+			if (spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLES || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP)
+			{
+				if (spec.attribs[0].componentCount == 1)
+					continue;
+				if (spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_FLOAT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_INT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_UINT)
+					continue;
+				if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && (spec.indexMax - spec.indexMin) < 2)
+					continue;
+			}
+		}
+
+		// Add case
+		{
+			deUint32 hash = spec.hash();
+			for (int attrNdx = 0; attrNdx < attributeCount; ++attrNdx)
+				hash = (hash << 2) ^ (deUint32)spec.attribs[attrNdx].hash();
+
+			if (insertedHashes.find(hash) == insertedHashes.end())
+			{
+				// Only aligned cases
+				if (spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET &&
+					spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+					this->addChild(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), spec, de::toString(insertedCount).c_str(), spec.getDesc().c_str()));
+				insertedHashes.insert(hash);
+
+				++insertedCount;
+			}
+		}
+	}
+}
+
+class BadCommandBufferCase : public TestCase
+{
+public:
+	enum
+	{
+		CommandSize = 20
+	};
+
+					BadCommandBufferCase	(Context& context, const char* name, const char* desc, deUint32 alignment, deUint32 bufferSize, bool writeCommandToBuffer, deUint32 m_expectedError);
+					~BadCommandBufferCase	(void);
+
+	IterateResult	iterate					(void);
+
+private:
+	const deUint32	m_alignment;
+	const deUint32	m_bufferSize;
+	const bool		m_writeCommandToBuffer;
+	const deUint32	m_expectedError;
+};
+
+BadCommandBufferCase::BadCommandBufferCase (Context& context, const char* name, const char* desc, deUint32 alignment, deUint32 bufferSize, bool writeCommandToBuffer, deUint32 expectedError)
+	: TestCase					(context, name, desc)
+	, m_alignment				(alignment)
+	, m_bufferSize				(bufferSize)
+	, m_writeCommandToBuffer	(writeCommandToBuffer)
+	, m_expectedError			(expectedError)
+{
+}
+
+BadCommandBufferCase::~BadCommandBufferCase (void)
+{
+}
+
+BadCommandBufferCase::IterateResult	BadCommandBufferCase::iterate (void)
+{
+	const tcu::Vec4 vertexPositions[] =
+	{
+		tcu::Vec4(0,	0,		0, 1),
+		tcu::Vec4(1,	0,		0, 1),
+		tcu::Vec4(0,	1,		0, 1),
+	};
+
+	const deUint16 indices[] =
+	{
+		0, 2, 1,
+	};
+
+	DE_STATIC_ASSERT(CommandSize == sizeof(DrawElementsCommand));
+
+	sglr::GLContext gl(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(0, 0, 1, 1));
+
+	deUint32 vaoID			= 0;
+	deUint32 positionBuf	= 0;
+	deUint32 indexBuf		= 0;
+	deUint32 drawIndirectBuf= 0;
+	deUint32 error;
+
+	glu::ShaderProgram	program			(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_commonVertexShaderSource) << glu::FragmentSource(s_commonFragmentShaderSource));
+	deUint32			programID		= program.getProgram();
+	deInt32				posLocation		= gl.getAttribLocation(programID, "a_position");
+
+	DrawElementsCommand drawCommand;
+	drawCommand.count				= 3;
+	drawCommand.primCount			= 1;
+	drawCommand.firstIndex			= 0;
+	drawCommand.baseVertex			= 0;
+	drawCommand.reservedMustBeZero	= 0;
+
+	std::vector<deInt8> drawCommandBuffer;
+	drawCommandBuffer.resize(m_bufferSize);
+
+	deMemset(&drawCommandBuffer[0], 0, (int)drawCommandBuffer.size());
+
+	if (m_writeCommandToBuffer)
+	{
+		DE_ASSERT(drawCommandBuffer.size() >= sizeof(drawCommand) + m_alignment);
+		deMemcpy(&drawCommandBuffer[m_alignment], &drawCommand, sizeof(drawCommand));
+	}
+
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+	gl.genVertexArrays(1, &vaoID);
+	gl.bindVertexArray(vaoID);
+
+	gl.genBuffers(1, &positionBuf);
+	gl.bindBuffer(GL_ARRAY_BUFFER, positionBuf);
+	gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
+	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.vertexAttribDivisor(posLocation, 0);
+	gl.enableVertexAttribArray(posLocation);
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+
+	gl.genBuffers(1, &indexBuf);
+	gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuf);
+	gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+
+	gl.genBuffers(1, &drawIndirectBuf);
+	gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, drawIndirectBuf);
+	gl.bufferData(GL_DRAW_INDIRECT_BUFFER, drawCommandBuffer.size(), &drawCommandBuffer[0], GL_STATIC_DRAW);
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+
+	gl.viewport(0, 0, 1, 1);
+
+	gl.useProgram(programID);
+	gl.drawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, (const void*)(deUintptr)m_alignment);
+
+	error = gl.getError();
+
+	gl.useProgram(0);
+
+	gl.deleteBuffers(1, &drawIndirectBuf);
+	gl.deleteBuffers(1, &indexBuf);
+	gl.deleteBuffers(1, &positionBuf);
+	gl.deleteVertexArrays(1, &vaoID);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "drawElementsIndirect generated " << glu::getErrorStr(error) << ", expecting " << glu::getErrorStr(m_expectedError) << "." << tcu::TestLog::EndMessage;
+
+	if (error == m_expectedError)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "\tUnexpected error." << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected error.");
+	}
+
+	return STOP;
+}
+
+class BadAlignmentCase : public BadCommandBufferCase
+{
+public:
+					BadAlignmentCase	(Context& context, const char* name, const char* desc, deUint32 alignment);
+					~BadAlignmentCase	(void);
+};
+
+BadAlignmentCase::BadAlignmentCase (Context& context, const char* name, const char* desc, deUint32 alignment)
+	: BadCommandBufferCase(context, name, desc, alignment, CommandSize+alignment, true, GL_INVALID_VALUE)
+{
+}
+
+BadAlignmentCase::~BadAlignmentCase (void)
+{
+}
+
+class BadBufferRangeCase : public BadCommandBufferCase
+{
+public:
+					BadBufferRangeCase	(Context& context, const char* name, const char* desc, deUint32 offset);
+					~BadBufferRangeCase	(void);
+};
+
+BadBufferRangeCase::BadBufferRangeCase (Context& context, const char* name, const char* desc, deUint32 offset)
+	: BadCommandBufferCase(context, name, desc, offset, CommandSize, false, GL_INVALID_OPERATION)
+{
+}
+
+BadBufferRangeCase::~BadBufferRangeCase (void)
+{
+}
+
+class BadStateCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASE_CLIENT_BUFFER_VERTEXATTR = 0,
+		CASE_CLIENT_BUFFER_COMMAND,
+		CASE_DEFAULT_VAO,
+
+		CASE_CLIENT_LAST
+	};
+
+						BadStateCase	(Context& context, const char* name, const char* desc, CaseType type);
+						~BadStateCase	(void);
+
+	void				init			(void);
+	void				deinit			(void);
+	IterateResult		iterate			(void);
+
+private:
+	const CaseType		m_caseType;
+};
+
+BadStateCase::BadStateCase (Context& context, const char* name, const char* desc, CaseType type)
+	: TestCase			(context, name, desc)
+	, m_caseType		(type)
+{
+	DE_ASSERT(type < CASE_CLIENT_LAST);
+}
+
+BadStateCase::~BadStateCase (void)
+{
+	deinit();
+}
+
+void BadStateCase::init (void)
+{
+}
+
+void BadStateCase::deinit (void)
+{
+}
+
+BadStateCase::IterateResult BadStateCase::iterate (void)
+{
+	const tcu::Vec4 vertexPositions[] =
+	{
+		tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f),
+	};
+
+	const deUint16 indices[] =
+	{
+		0, 2, 1,
+	};
+
+	sglr::GLContext gl(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(0, 0, 1, 1));
+
+	deUint32			error;
+	glu::ShaderProgram	program			(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_commonVertexShaderSource) << glu::FragmentSource(s_commonFragmentShaderSource));
+	deUint32			vaoID			= 0;
+	deUint32			dataBufferID	= 0;
+	deUint32			indexBufferID	= 0;
+	deUint32			cmdBufferID		= 0;
+
+	const deUint32		programID		= program.getProgram();
+	const deInt32		posLocation		= gl.getAttribLocation(programID, "a_position");
+
+	DrawElementsCommand drawCommand;
+	drawCommand.count				= 3;
+	drawCommand.primCount			= 1;
+	drawCommand.firstIndex			= 0;
+	drawCommand.baseVertex			= 0;
+	drawCommand.reservedMustBeZero	= 0;
+
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+
+	if (m_caseType == CASE_CLIENT_BUFFER_VERTEXATTR)
+	{
+		// \note We use default VAO since we use client pointers. Trying indirect draw with default VAO is also an error. => This test does two illegal operations
+
+		gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, vertexPositions);
+		gl.enableVertexAttribArray(posLocation);
+		glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+	}
+	else if (m_caseType == CASE_CLIENT_BUFFER_COMMAND)
+	{
+		gl.genVertexArrays(1, &vaoID);
+		gl.bindVertexArray(vaoID);
+
+		gl.genBuffers(1, &dataBufferID);
+		gl.bindBuffer(GL_ARRAY_BUFFER, dataBufferID);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
+		gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		gl.enableVertexAttribArray(posLocation);
+		glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+	}
+	else if (m_caseType == CASE_DEFAULT_VAO)
+	{
+		gl.genBuffers(1, &dataBufferID);
+		gl.bindBuffer(GL_ARRAY_BUFFER, dataBufferID);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
+		gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		gl.enableVertexAttribArray(posLocation);
+		glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	gl.genBuffers(1, &indexBufferID);
+	gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
+	gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+
+	if (m_caseType != CASE_CLIENT_BUFFER_COMMAND)
+	{
+		gl.genBuffers(1, &cmdBufferID);
+		gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, cmdBufferID);
+		gl.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(drawCommand), &drawCommand, GL_STATIC_DRAW);
+		glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+	}
+
+	gl.viewport(0, 0, 1, 1);
+
+	gl.useProgram(programID);
+	gl.drawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, (m_caseType != CASE_CLIENT_BUFFER_COMMAND) ? (DE_NULL) : (&drawCommand));
+
+	error = gl.getError();
+
+	gl.bindVertexArray(0);
+	gl.useProgram(0);
+
+	if (error == GL_INVALID_OPERATION)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Unexpected error. Expected GL_INVALID_OPERATION, got " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected error.");
+	}
+
+	return STOP;
+}
+
+class BadDrawModeCase : public TestCase
+{
+public:
+	enum DrawType
+	{
+		DRAW_ARRAYS = 0,
+		DRAW_ELEMENTS,
+		DRAW_ELEMENTS_BAD_INDEX,
+
+		DRAW_LAST
+	};
+
+						BadDrawModeCase	(Context& context, const char* name, const char* desc, DrawType type);
+						~BadDrawModeCase(void);
+
+	void				init			(void);
+	void				deinit			(void);
+	IterateResult		iterate			(void);
+
+private:
+	const DrawType		m_drawType;
+};
+
+BadDrawModeCase::BadDrawModeCase (Context& context, const char* name, const char* desc, DrawType type)
+	: TestCase			(context, name, desc)
+	, m_drawType		(type)
+{
+	DE_ASSERT(type < DRAW_LAST);
+}
+
+BadDrawModeCase::~BadDrawModeCase (void)
+{
+	deinit();
+}
+
+void BadDrawModeCase::init (void)
+{
+}
+
+void BadDrawModeCase::deinit (void)
+{
+}
+
+BadDrawModeCase::IterateResult BadDrawModeCase::iterate (void)
+{
+	const tcu::Vec4 vertexPositions[] =
+	{
+		tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f),
+	};
+
+	const deUint16 indices[] =
+	{
+		0, 2, 1,
+	};
+
+	sglr::GLContext gl(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(0, 0, 1, 1));
+
+	deUint32			error;
+	glu::ShaderProgram	program			(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_commonVertexShaderSource) << glu::FragmentSource(s_commonFragmentShaderSource));
+	deUint32			vaoID			= 0;
+	deUint32			dataBufferID	= 0;
+	deUint32			indexBufferID	= 0;
+	deUint32			cmdBufferID		= 0;
+
+	const deUint32		programID		= program.getProgram();
+	const deInt32		posLocation		= gl.getAttribLocation(programID, "a_position");
+	const glw::GLenum	mode			= (m_drawType == DRAW_ELEMENTS_BAD_INDEX) ? (GL_TRIANGLES) : (0x123);
+	const glw::GLenum	indexType		= (m_drawType == DRAW_ELEMENTS_BAD_INDEX) ? (0x123) : (GL_UNSIGNED_SHORT);
+
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+
+	// vao
+
+	gl.genVertexArrays(1, &vaoID);
+	gl.bindVertexArray(vaoID);
+
+	// va
+
+	gl.genBuffers(1, &dataBufferID);
+	gl.bindBuffer(GL_ARRAY_BUFFER, dataBufferID);
+	gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
+	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(posLocation);
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+
+	// index
+
+	if (m_drawType == DRAW_ELEMENTS)
+	{
+		gl.genBuffers(1, &indexBufferID);
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+		glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+	}
+
+	// cmd
+
+	gl.genBuffers(1, &cmdBufferID);
+	gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, cmdBufferID);
+	if (m_drawType == DRAW_ELEMENTS || m_drawType == DRAW_ELEMENTS_BAD_INDEX)
+	{
+		DrawElementsCommand drawCommand;
+		drawCommand.count				= 3;
+		drawCommand.primCount			= 1;
+		drawCommand.firstIndex			= 0;
+		drawCommand.baseVertex			= 0;
+		drawCommand.reservedMustBeZero	= 0;
+
+		gl.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(drawCommand), &drawCommand, GL_STATIC_DRAW);
+	}
+	else if (m_drawType == DRAW_ARRAYS)
+	{
+		DrawArraysCommand drawCommand;
+		drawCommand.count				= 3;
+		drawCommand.primCount			= 1;
+		drawCommand.first				= 0;
+		drawCommand.reservedMustBeZero	= 0;
+
+		gl.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(drawCommand), &drawCommand, GL_STATIC_DRAW);
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+	glu::checkError(gl.getError(), "", __FILE__, __LINE__);
+
+	gl.viewport(0, 0, 1, 1);
+	gl.useProgram(programID);
+	if (m_drawType == DRAW_ELEMENTS || m_drawType == DRAW_ELEMENTS_BAD_INDEX)
+		gl.drawElementsIndirect(mode, indexType, DE_NULL);
+	else if (m_drawType == DRAW_ARRAYS)
+		gl.drawArraysIndirect(mode, DE_NULL);
+	else
+		DE_ASSERT(DE_FALSE);
+
+	error = gl.getError();
+	gl.useProgram(0);
+
+	if (error == GL_INVALID_ENUM)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Unexpected error. Expected GL_INVALID_ENUM, got " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected error.");
+	}
+
+	return STOP;
+}
+
+class NegativeGroup : public TestCaseGroup
+{
+public:
+			NegativeGroup	(Context& context, const char* name, const char* descr);
+			~NegativeGroup	(void);
+
+	void	init			(void);
+};
+
+NegativeGroup::NegativeGroup (Context& context, const char* name, const char* descr)
+	: TestCaseGroup	(context, name, descr)
+{
+}
+
+NegativeGroup::~NegativeGroup (void)
+{
+}
+
+void NegativeGroup::init (void)
+{
+	// invalid alignment
+	addChild(new BadAlignmentCase	(m_context, "command_bad_alignment_1",								"Bad command alignment",					1));
+	addChild(new BadAlignmentCase	(m_context, "command_bad_alignment_2",								"Bad command alignment",					2));
+	addChild(new BadAlignmentCase	(m_context, "command_bad_alignment_3",								"Bad command alignment",					3));
+
+	// command only partially or not at all in the buffer
+	addChild(new BadBufferRangeCase	(m_context, "command_offset_partially_in_buffer",					"Command not fully in the buffer range",	BadBufferRangeCase::CommandSize - 16));
+	addChild(new BadBufferRangeCase	(m_context, "command_offset_not_in_buffer",							"Command not in the buffer range",			BadBufferRangeCase::CommandSize));
+	addChild(new BadBufferRangeCase	(m_context, "command_offset_not_in_buffer_unsigned32_wrap",			"Command not in the buffer range",			0xFFFFFFFC));
+	addChild(new BadBufferRangeCase	(m_context, "command_offset_not_in_buffer_signed32_wrap",			"Command not in the buffer range",			0x7FFFFFFC));
+
+	// use with client data and default vao
+	addChild(new BadStateCase		(m_context, "client_vertex_attrib_array",							"Vertex attrib array in the client memory",	BadStateCase::CASE_CLIENT_BUFFER_VERTEXATTR));
+	addChild(new BadStateCase		(m_context, "client_command_array",									"Command array in the client memory",		BadStateCase::CASE_CLIENT_BUFFER_COMMAND));
+	addChild(new BadStateCase		(m_context, "default_vao",											"Use with default vao",						BadStateCase::CASE_DEFAULT_VAO));
+
+	// invalid mode & type
+	addChild(new BadDrawModeCase	(m_context, "invalid_mode_draw_arrays",								"Call DrawArraysIndirect with bad mode",	BadDrawModeCase::DRAW_ARRAYS));
+	addChild(new BadDrawModeCase	(m_context, "invalid_mode_draw_elements",							"Call DrawelementsIndirect with bad mode",	BadDrawModeCase::DRAW_ELEMENTS));
+	addChild(new BadDrawModeCase	(m_context, "invalid_type_draw_elements",							"Call DrawelementsIndirect with bad type",	BadDrawModeCase::DRAW_ELEMENTS_BAD_INDEX));
+}
+
+} // anonymous
+
+DrawTests::DrawTests (Context& context)
+	: TestCaseGroup(context, "draw_indirect", "Indirect drawing tests")
+{
+}
+
+DrawTests::~DrawTests (void)
+{
+}
+
+void DrawTests::init (void)
+{
+	// Basic
+	{
+		const gls::DrawTestSpec::DrawMethod basicMethods[] =
+		{
+			gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT,
+			gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT,
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(basicMethods); ++ndx)
+		{
+			const std::string name = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
+			const std::string desc = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
+
+			this->addChild(new MethodGroup(m_context, name.c_str(), desc.c_str(), basicMethods[ndx]));
+		}
+	}
+
+	// extreme instancing
+
+	this->addChild(new InstancingGroup(m_context, "instancing", "draw tests with a large instance count."));
+
+	// compute shader generated commands
+
+	this->addChild(new ComputeShaderGeneratedGroup(m_context, "compute_interop", "draw tests with a draw command generated in compute shader."));
+
+	// Random
+
+	this->addChild(new RandomGroup(m_context, "random", "random draw commands."));
+
+	// negative
+
+	this->addChild(new NegativeGroup(m_context, "negative", "invalid draw commands with defined error codes."));
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fDrawTests.hpp b/modules/gles31/functional/es31fDrawTests.hpp
new file mode 100644
index 0000000..27d75ad
--- /dev/null
+++ b/modules/gles31/functional/es31fDrawTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FDRAWTESTS_HPP
+#define _ES31FDRAWTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Drawing tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class DrawTests : public TestCaseGroup
+{
+public:
+						DrawTests		(Context& context);
+						~DrawTests		(void);
+
+	void				init			(void);
+
+private:
+						DrawTests		(const DrawTests& other);
+	DrawTests&			operator=		(const DrawTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FDRAWTESTS_HPP
diff --git a/modules/gles31/functional/es31fFboNoAttachmentTests.cpp b/modules/gles31/functional/es31fFboNoAttachmentTests.cpp
new file mode 100644
index 0000000..17c4514
--- /dev/null
+++ b/modules/gles31/functional/es31fFboNoAttachmentTests.cpp
@@ -0,0 +1,671 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer without attachments (GL_ARB_framebuffer_no_attachments) tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fFboNoAttachmentTests.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include "gluRenderContext.hpp"
+#include "gluDefs.hpp"
+#include "gluShaderProgram.hpp"
+
+#include "tcuTestContext.hpp"
+#include "tcuVectorType.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuCommandLine.hpp"
+
+#include "deMemory.h"
+#include "deRandom.hpp"
+#include "deString.h"
+#include "deStringUtil.hpp"
+
+#include <string>
+#include <vector>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+using namespace glw;
+
+using tcu::IVec2;
+using tcu::TestLog;
+
+using std::stringstream;
+using std::string;
+using std::vector;
+
+bool checkFramebufferSize (TestLog& log, const glu::RenderContext& renderCtx, GLuint framebuffer, const IVec2& size)
+{
+	const glw::Functions&		gl				= renderCtx.getFunctions();
+
+	const char* const			vertexSource	= "#version 310 es\n"
+												  "in layout(location = 0) highp vec2 a_position;\n\n"
+												  "void main()\n"
+												  "{\n"
+												  "	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+												  "}\n";
+
+	const char* const			fragmentSource	= "#version 310 es\n"
+												  "uniform layout(location = 0) highp ivec2 u_expectedSize;\n"
+												  "out layout(location = 0) mediump vec4 f_color;\n\n"
+												  "void main()\n"
+												  "{\n"
+												  "	if (ivec2(gl_FragCoord.xy) != u_expectedSize) discard;\n"
+												  "	f_color = vec4(1.0, 0.5, 0.25, 1.0);\n"
+												  "}\n";
+
+	const glu::ShaderProgram	program			(renderCtx, glu::makeVtxFragSources(vertexSource, fragmentSource));
+	GLuint						query			= 0;
+	GLuint						insidePassed	= 0;
+	GLuint						outsideXPassed	= 0;
+	GLuint						outsideYPassed	= 0;
+
+	if (!program.isOk())
+		log << program;
+
+	TCU_CHECK(program.isOk());
+
+	gl.useProgram(program.getProgram());
+	gl.enable(GL_DEPTH_TEST);
+	gl.depthFunc(GL_ALWAYS);
+	gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
+	gl.viewport(0, 0, size.x()*2, size.y()*2); // Oversized viewport so that it will not accidentally limit us to the correct size
+
+	log << TestLog::Message << "Using " << size.x()*2 << "x" << size.y()*2 << " viewport" << TestLog::EndMessage;
+	log << TestLog::Message << "Discarding fragments outside pixel of interest" << TestLog::EndMessage;
+	log << TestLog::Message << "Using occlusion query to check for rendered fragments" << TestLog::EndMessage;
+
+	TCU_CHECK(gl.checkFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+	// Render
+	{
+		const float data[] =
+		{
+			 1.0f,  1.0f,
+			 1.0f, -1.0f,
+			-1.0f,  1.0f,
+			-1.0f,  1.0f,
+			 1.0f, -1.0f,
+			-1.0f, -1.0f,
+		};
+
+		GLuint vertexArray	= 0;
+		GLuint vertexBuffer	= 0;
+
+		gl.genQueries(1, &query);
+		gl.genVertexArrays(1, &vertexArray);
+		gl.bindVertexArray(vertexArray);
+
+		gl.genBuffers(1, &vertexBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
+
+		gl.enableVertexAttribArray(0);
+		gl.vertexAttribPointer(0, 2, GL_FLOAT, false, 0, DE_NULL);
+
+		gl.uniform2i(0, size.x()-1, size.y()-1);
+		gl.beginQuery(GL_ANY_SAMPLES_PASSED, query);
+		gl.drawArrays(GL_TRIANGLES, 0, 6);
+		gl.endQuery(GL_ANY_SAMPLES_PASSED);
+		gl.getQueryObjectuiv(query, GL_QUERY_RESULT, &insidePassed);
+		log << TestLog::Message << "A fragment was not discarded at (" << size.x()-1 << ", " << size.y()-1 << "). "
+			<< "Occlusion query reports it was " << (insidePassed > 0 ? "rendered." : "not rendered") << TestLog::EndMessage;
+
+		gl.uniform2i(0, size.x(), size.y()-1);
+		gl.beginQuery(GL_ANY_SAMPLES_PASSED, query);
+		gl.drawArrays(GL_TRIANGLES, 0, 6);
+		gl.endQuery(GL_ANY_SAMPLES_PASSED);
+		gl.getQueryObjectuiv(query, GL_QUERY_RESULT, &outsideXPassed);
+		log << TestLog::Message << "A fragment was not discarded at (" << size.x() << ", " << size.y()-1 << "). "
+			<< "Occlusion query reports it was " << (outsideXPassed > 0 ? "rendered." : "not rendered") << TestLog::EndMessage;
+
+		gl.uniform2i(0, size.x()-1, size.y());
+		gl.beginQuery(GL_ANY_SAMPLES_PASSED, query);
+		gl.drawArrays(GL_TRIANGLES, 0, 6);
+		gl.endQuery(GL_ANY_SAMPLES_PASSED);
+		gl.getQueryObjectuiv(query, GL_QUERY_RESULT, &outsideYPassed);
+		log << TestLog::Message << "A fragment was not discarded at (" << size.x()-1 << ", " << size.y() << "). "
+			<< "Occlusion query reports it was " << (outsideYPassed > 0 ? "rendered." : "not rendered") << TestLog::EndMessage;
+
+		gl.disableVertexAttribArray(0);
+		gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+		gl.bindVertexArray(0);
+		gl.deleteBuffers(1, &vertexBuffer);
+		gl.deleteVertexArrays(1, &vertexArray);
+	}
+
+	gl.deleteQueries(1, &query);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Query failed");
+
+	return insidePassed && !outsideXPassed && !outsideYPassed;
+}
+
+bool checkFramebufferRenderable (TestLog& log, const glu::RenderContext& renderCtx, GLuint framebuffer, const IVec2& size)
+{
+	const glw::Functions&		gl				= renderCtx.getFunctions();
+
+	const char* const			vertexSource	= "#version 310 es\n"
+												  "in layout(location = 0) highp vec2 a_position;\n\n"
+												  "void main()\n"
+												  "{\n"
+												  "	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+												  "}\n";
+
+	const char* const			fragmentSource	= "#version 310 es\n"
+												  "out layout(location = 0) mediump vec4 f_color;\n\n"
+												  "void main()\n"
+												  "{\n"
+												  "	f_color = vec4(1.0, 0.5, 0.25, 1.0);\n"
+												  "}\n";
+
+	const glu::ShaderProgram	program			(renderCtx, glu::makeVtxFragSources(vertexSource, fragmentSource));
+	GLuint						query			= 0;
+
+	if (!program.isOk())
+		log << program;
+
+	TCU_CHECK(program.isOk());
+
+	gl.useProgram(program.getProgram());
+	gl.enable(GL_DEPTH_TEST);
+	gl.depthFunc(GL_ALWAYS);
+	gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
+	gl.viewport(0, 0, size.x(), size.y());
+
+	TCU_CHECK(gl.checkFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+	log << TestLog::Message << "Rendering full framebuffer quad with color ouput, verifying output presence with occlusion query" << TestLog::EndMessage;
+
+	// Render
+	{
+		const float data[] =
+		{
+			 1.0f,  1.0f,
+			 1.0f, -1.0f,
+			-1.0f,  1.0f,
+			-1.0f,  1.0f,
+			 1.0f, -1.0f,
+			-1.0f, -1.0f,
+		};
+
+		GLuint vertexArray	= 0;
+		GLuint vertexBuffer	= 0;
+
+		gl.genQueries(1, &query);
+		gl.genVertexArrays(1, &vertexArray);
+		gl.bindVertexArray(vertexArray);
+
+		gl.genBuffers(1, &vertexBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
+
+		gl.enableVertexAttribArray(0);
+		gl.vertexAttribPointer(0, 2, GL_FLOAT, false, 0, DE_NULL);
+
+		gl.beginQuery(GL_ANY_SAMPLES_PASSED, query);
+		gl.drawArrays(GL_TRIANGLES, 0, 6);
+		gl.endQuery(GL_ANY_SAMPLES_PASSED);
+
+		gl.disableVertexAttribArray(0);
+		gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+		gl.bindVertexArray(0);
+		gl.deleteBuffers(1, &vertexBuffer);
+		gl.deleteVertexArrays(1, &vertexArray);
+	}
+
+	// Read
+	{
+		GLuint passed = 0;
+
+		gl.getQueryObjectuiv(query, GL_QUERY_RESULT, &passed);
+		gl.deleteQueries(1, &query);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Query failed");
+
+		if (passed)
+			log << TestLog::Message << "Query passed" << TestLog::EndMessage;
+		else
+			log << TestLog::Message << "Query did not pass" << TestLog::EndMessage;
+
+		return passed != 0;
+	}
+}
+
+class FramebufferCompletenessCase : public tcu::TestCase
+{
+public:
+								FramebufferCompletenessCase		(tcu::TestContext&			testCtx,
+																 const glu::RenderContext&	renderCtx,
+																 const char*				name,
+																 const char*				desc);
+	virtual						~FramebufferCompletenessCase	 (void) {}
+	virtual IterateResult		iterate							(void);
+
+private:
+	const glu::RenderContext&	m_renderCtx;
+	tcu::ResultCollector		m_results;
+};
+
+FramebufferCompletenessCase::FramebufferCompletenessCase (tcu::TestContext&			testCtx,
+														  const glu::RenderContext&	renderCtx,
+														  const char*				name,
+														  const char*				desc)
+	: TestCase		(testCtx, name, desc)
+	, m_renderCtx	(renderCtx)
+{
+}
+
+FramebufferCompletenessCase::IterateResult FramebufferCompletenessCase::iterate (void)
+{
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	GLuint					framebuffer	= 0;
+
+	gl.genFramebuffers(1, &framebuffer);
+	gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
+
+	m_results.check(gl.checkFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE, "Framebuffer was incorrectly reported as complete when it had no width, height or attachments");
+
+	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 16);
+	m_results.check(gl.checkFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE, "Framebuffer was incorrectly reported as complete when it only had a width");
+
+	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, 16);
+	m_results.check(gl.checkFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Framebuffer not reported as complete when it had width and height set");
+
+	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 0);
+	m_results.check(gl.checkFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE, "Framebuffer was incorrectly reported as complete when it only had a height");
+
+	gl.deleteFramebuffers(1, &framebuffer);
+
+	m_results.setTestContextResult(m_testCtx);
+	return STOP;
+}
+
+struct FboSpec
+{
+	int width;
+	int height;
+	int samples;
+
+	FboSpec(int width_, int height_, int samples_) : width(width_), height(height_), samples(samples_){}
+};
+
+class SizeCase : public tcu::TestCase
+{
+public:
+								SizeCase	(tcu::TestContext&			testCtx,
+											 const glu::RenderContext&	renderCtx,
+											 const char*				name,
+											 const char*				desc,
+											 const FboSpec&				spec);
+	virtual						~SizeCase	(void) {}
+
+	virtual IterateResult		iterate		(void);
+
+	enum
+	{
+		USE_MAXIMUM = -1
+	};
+private:
+	int							getWidth	(void) const;
+	int							getHeight	(void) const;
+	int							getSamples	(void) const;
+
+	const glu::RenderContext&	m_renderCtx;
+
+	const FboSpec				m_spec;
+};
+
+SizeCase::SizeCase (tcu::TestContext&			testCtx,
+					const glu::RenderContext&	renderCtx,
+					const char*					name,
+					const char*					desc,
+					const FboSpec&				spec)
+	: TestCase		(testCtx, name, desc)
+	, m_renderCtx	(renderCtx)
+	, m_spec		(spec)
+{
+}
+
+SizeCase::IterateResult SizeCase::iterate (void)
+{
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	TestLog&				log			= m_testCtx.getLog();
+	GLuint					framebuffer	= 0;
+	const int				width		= getWidth();
+	const int				height		= getHeight();
+	const int				samples		= getSamples();
+
+	gl.genFramebuffers(1, &framebuffer);
+	gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
+	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, width);
+	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, height);
+	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_SAMPLES, samples);
+
+	log << TestLog::Message << "Verifying " << width << "x" << height << " framebuffer with " << samples << "x multisampling" << TestLog::EndMessage;
+
+	if(checkFramebufferRenderable(log, m_renderCtx, framebuffer, IVec2(width, height)) && checkFramebufferSize(log, m_renderCtx, framebuffer, IVec2(width, height)))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Framebuffer did not behave as expected");
+
+	gl.deleteFramebuffers(1, &framebuffer);
+
+	return STOP;
+}
+
+int SizeCase::getWidth (void) const
+{
+	if (m_spec.width != USE_MAXIMUM)
+		return m_spec.width;
+	else
+	{
+		const glw::Functions&	gl		= m_renderCtx.getFunctions();
+		GLint					width	= 0;
+
+		gl.getIntegerv(GL_MAX_FRAMEBUFFER_WIDTH, &width);
+
+		return width;
+	}
+}
+
+int SizeCase::getHeight (void) const
+{
+	if (m_spec.height != USE_MAXIMUM)
+		return m_spec.height;
+	else
+	{
+		const glw::Functions&	gl		= m_renderCtx.getFunctions();
+		GLint					height	= 0;
+
+		gl.getIntegerv(GL_MAX_FRAMEBUFFER_HEIGHT, &height);
+
+		return height;
+	}
+}
+
+int SizeCase::getSamples (void) const
+{
+	if (m_spec.samples != USE_MAXIMUM)
+		return m_spec.samples;
+	else
+	{
+		const glw::Functions&	gl		= m_renderCtx.getFunctions();
+		GLint					samples	= 0;
+
+		gl.getIntegerv(GL_MAX_FRAMEBUFFER_SAMPLES, &samples);
+
+		return samples;
+	}
+}
+
+class AttachmentInteractionCase : public tcu::TestCase
+{
+public:
+								AttachmentInteractionCase	(tcu::TestContext&			testCtx,
+															 const glu::RenderContext&	renderCtx,
+															 const char*				name,
+															 const char*				desc,
+															 const FboSpec&				defaultSpec,
+															 const FboSpec&				attachmentSpec);
+	virtual						~AttachmentInteractionCase	(void) {}
+
+	virtual IterateResult		iterate						(void);
+
+private:
+	const glu::RenderContext&	m_renderCtx;
+	const FboSpec				m_defaultSpec;
+	const FboSpec				m_attachmentSpec;
+};
+
+AttachmentInteractionCase::AttachmentInteractionCase (tcu::TestContext&			testCtx,
+													  const glu::RenderContext&	renderCtx,
+													  const char*				name,
+													  const char*				desc,
+													  const FboSpec&			defaultSpec,
+													  const FboSpec&			attachmentSpec)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_defaultSpec		(defaultSpec)
+	, m_attachmentSpec	(attachmentSpec)
+{
+}
+
+AttachmentInteractionCase::IterateResult AttachmentInteractionCase::iterate (void)
+{
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	TestLog&				log			= m_testCtx.getLog();
+	GLuint					framebuffer	= 0;
+	GLuint					renderbuffer= 0;
+
+	gl.genFramebuffers(1, &framebuffer);
+	gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
+	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, m_defaultSpec.width);
+	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, m_defaultSpec.height);
+	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_SAMPLES, m_defaultSpec.samples);
+
+	gl.genRenderbuffers(1, &renderbuffer);
+	gl.bindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+	gl.renderbufferStorageMultisample(GL_RENDERBUFFER, m_attachmentSpec.samples, GL_RGBA8, m_attachmentSpec.width, m_attachmentSpec.height);
+	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
+
+	log << TestLog::Message << "Verifying " << m_attachmentSpec.width << "x" << m_attachmentSpec.height << " framebuffer with " << m_attachmentSpec.samples << "x multisampling"
+		<< " and defaults set to " << m_defaultSpec.width << "x" << m_defaultSpec.height << " with " << m_defaultSpec.samples << "x multisampling" << TestLog::EndMessage;
+
+	if(checkFramebufferRenderable(log, m_renderCtx, framebuffer, IVec2(m_attachmentSpec.width, m_attachmentSpec.height))
+	   && checkFramebufferSize(log, m_renderCtx, framebuffer, IVec2(m_attachmentSpec.width, m_attachmentSpec.height)))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Framebuffer did not behave as expected");
+
+	gl.deleteRenderbuffers(1, &renderbuffer);
+	gl.deleteFramebuffers(1, &framebuffer);
+
+	return STOP;
+}
+
+} // Anonymous
+
+tcu::TestCaseGroup* createFboNoAttachmentTests(Context& context)
+{
+	const glu::RenderContext&	renderCtx	= context.getRenderContext();
+	tcu::TestContext&			testCtx		= context.getTestContext();
+
+	const int					maxWidth	= 2048; // MAX_FRAMEBUFFER_WIDTH in ES 3.1
+	const int					maxHeight	= 2048; // MAX_FRAMEBUFFER_HEIGHT in ES 3.1
+	const int					maxSamples	= 4;
+
+	tcu::TestCaseGroup* const	root		= new tcu::TestCaseGroup(testCtx, "no_attachments", "Framebuffer without attachments");
+
+	// Size
+	{
+		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(testCtx, "size", "Basic functionality tests with varying default size");
+
+		root->addChild(group);
+
+		for (int width = 16; width <= maxWidth; width *= 4)
+		{
+			for (int height = 16; height <= maxHeight; height *= 4)
+			{
+				const FboSpec	spec (width, height, 0);
+				stringstream	name;
+
+				name << width << "x" << height;
+
+				group->addChild(new SizeCase(testCtx, renderCtx, name.str().c_str(), name.str().c_str(), spec));
+			}
+		}
+	}
+
+	// NPOT size
+	{
+		const FboSpec specs[] =
+		{
+			// Square
+			FboSpec(1,    1,    0),
+			FboSpec(3,    3,    0),
+			FboSpec(15,   15,   0),
+			FboSpec(17,   17,   0),
+			FboSpec(31,   31,   0),
+			FboSpec(33,   33,   0),
+			FboSpec(63,   63,   0),
+			FboSpec(65,   65,   0),
+			FboSpec(127,  127,  0),
+			FboSpec(129,  129,  0),
+			FboSpec(255,  255,  0),
+			FboSpec(257,  257,  0),
+			FboSpec(511,  511,  0),
+			FboSpec(513,  513,  0),
+			FboSpec(1023, 1023, 0),
+			FboSpec(1025, 1025, 0),
+			FboSpec(2047, 2047, 0),
+
+			// Non-square
+			FboSpec(15,   511,  0),
+			FboSpec(127,  15,   0),
+			FboSpec(129,  127,  0),
+			FboSpec(511,  127,  0),
+			FboSpec(2047, 1025, 0),
+		};
+		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(testCtx, "npot_size", "Basic functionality with Non-power-of-two size");
+
+		root->addChild(group);
+
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(specs); caseNdx++)
+		{
+			const FboSpec&	spec = specs[caseNdx];
+			stringstream	name;
+
+			name << spec.width << "x" << spec.height;
+
+			group->addChild(new SizeCase(testCtx, renderCtx, name.str().c_str(), name.str().c_str(), spec));
+		}
+	}
+
+	// Multisample
+	{
+		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(testCtx, "multisample", "Basic functionality with multisampled fbo");
+
+		root->addChild(group);
+
+		for (int samples = 0; samples <= maxSamples; samples++)
+		{
+			const FboSpec	spec (128, 128, samples);
+			stringstream	name;
+
+			name << "samples" << samples;
+
+			group->addChild(new SizeCase(testCtx, renderCtx, name.str().c_str(), name.str().c_str(), spec));
+		}
+	}
+
+	// Randomized
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(testCtx, "random", "Randomized size & multisampling");
+		de::Random					rng		(0xF0E1E2D3 ^ testCtx.getCommandLine().getBaseSeed());
+
+		root->addChild(group);
+
+		for (int caseNdx = 0; caseNdx < 16; caseNdx++)
+		{
+			const int		width	= rng.getInt(1, maxWidth);
+			const int		height	= rng.getInt(1, maxHeight);
+			const int		samples = rng.getInt(0, maxSamples);
+			const FboSpec	spec	(width, height, samples);
+			const string	name	= de::toString(caseNdx);
+
+			group->addChild(new SizeCase(testCtx, renderCtx, name.c_str(), name.c_str(), spec));
+		}
+	}
+
+	// Normal fbo with defaults set
+	{
+		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(testCtx, "interaction", "Interaction of default parameters with normal fbo");
+
+		root->addChild(group);
+
+		const FboSpec specs[][2] =
+		{
+			{ FboSpec(256,  256,  0), FboSpec(128,  128,  1) },
+			{ FboSpec(256,  256,  1), FboSpec(128,  128,  0) },
+			{ FboSpec(256,  256,  0), FboSpec(512,  512,  2) },
+			{ FboSpec(256,  256,  2), FboSpec(128,  512,  0) },
+			{ FboSpec(127,  127,  0), FboSpec(129,  129,  0) },
+			{ FboSpec(17,   512,  4), FboSpec(16,   16,   2) },
+			{ FboSpec(2048, 2048, 4), FboSpec(1,    1,    0) },
+			{ FboSpec(1,    1,    0), FboSpec(2048, 2048, 4) },
+		};
+
+		for (int specNdx = 0; specNdx < DE_LENGTH_OF_ARRAY(specs); specNdx++)
+		{
+			const FboSpec& baseSpec = specs[specNdx][0];
+			const FboSpec& altSpec	= specs[specNdx][1];
+			stringstream baseSpecName, altSpecName;
+
+			baseSpecName << baseSpec.width << "x" << baseSpec.height << "ms" << baseSpec.samples;
+			altSpecName << altSpec.width << "x" << altSpec.height << "ms" << altSpec.samples;
+
+			{
+				const string name = baseSpecName.str() + "_default_" + altSpecName.str();
+
+				group->addChild(new AttachmentInteractionCase(testCtx, renderCtx, name.c_str(), name.c_str(), altSpec, baseSpec));
+			}
+		}
+	}
+
+	// Maximums
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(testCtx, "maximums", "Maximum dimensions");
+
+		root->addChild(group);
+		group->addChild(new SizeCase(testCtx, renderCtx, "width",	"Maximum width",		  FboSpec(SizeCase::USE_MAXIMUM,	128,					0)));
+		group->addChild(new SizeCase(testCtx, renderCtx, "height",	"Maximum height",		  FboSpec(128,						SizeCase::USE_MAXIMUM,  0)));
+		group->addChild(new SizeCase(testCtx, renderCtx, "size",	"Maximum size",			  FboSpec(SizeCase::USE_MAXIMUM,	SizeCase::USE_MAXIMUM,  0)));
+		group->addChild(new SizeCase(testCtx, renderCtx, "samples", "Maximum samples",		  FboSpec(128,						128,					SizeCase::USE_MAXIMUM)));
+		group->addChild(new SizeCase(testCtx, renderCtx, "all",		"Maximum size & samples", FboSpec(SizeCase::USE_MAXIMUM,	SizeCase::USE_MAXIMUM,  SizeCase::USE_MAXIMUM)));
+	}
+
+	return root;
+}
+
+tcu::TestCaseGroup* createFboNoAttachmentCompletenessTests(Context& context)
+{
+	TestCaseGroup* const group = new TestCaseGroup(context, "completeness", "Completeness tests");
+
+	group->addChild(new FramebufferCompletenessCase(context.getTestContext(), context.getRenderContext(), "no_attachments", "No attachments completeness"));
+
+	return group;
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fFboNoAttachmentTests.hpp b/modules/gles31/functional/es31fFboNoAttachmentTests.hpp
new file mode 100644
index 0000000..a007a49
--- /dev/null
+++ b/modules/gles31/functional/es31fFboNoAttachmentTests.hpp
@@ -0,0 +1,43 @@
+#ifndef _ES31FFBONOATTACHMENTTESTS_HPP
+#define _ES31FFBONOATTACHMENTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer without attachments (GL_ARB_framebuffer_no_attachments) tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+tcu::TestCaseGroup* createFboNoAttachmentTests(Context& context);
+tcu::TestCaseGroup* createFboNoAttachmentCompletenessTests(Context& context);
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FFBONOATTACHMENTTESTS_HPP
diff --git a/modules/gles31/functional/es31fFboTestUtil.cpp b/modules/gles31/functional/es31fFboTestUtil.cpp
new file mode 100644
index 0000000..ac4572d
--- /dev/null
+++ b/modules/gles31/functional/es31fFboTestUtil.cpp
@@ -0,0 +1,228 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fFboTestUtil.hpp"
+#include "sglrContextUtil.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <limits>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace FboTestUtil
+{
+
+using std::string;
+using std::vector;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+
+static rr::GenericVecType mapDataTypeToGenericVecType(glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_FLOAT_VEC4:	return rr::GENERICVECTYPE_FLOAT;
+		case glu::TYPE_INT_VEC4:	return rr::GENERICVECTYPE_INT32;
+		case glu::TYPE_UINT_VEC4:	return rr::GENERICVECTYPE_UINT32;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return rr::GENERICVECTYPE_LAST;
+	}
+}
+
+template <typename T>
+static tcu::Vector<T, 4> castVectorSaturate (const tcu::Vec4& in)
+{
+	return tcu::Vector<T, 4>((in.x() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.x() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.x()))),
+	                         (in.y() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.y() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.y()))),
+							 (in.z() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.z() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.z()))),
+							 (in.w() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.w() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.w()))));
+}
+
+TextureCubeArrayShader::TextureCubeArrayShader (glu::DataType samplerType, glu::DataType outputType)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
+							<< sglr::pdec::Uniform("u_coordMat", glu::TYPE_FLOAT_MAT3)
+							<< sglr::pdec::Uniform("u_sampler0", samplerType)
+							<< sglr::pdec::Uniform("u_scale", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::Uniform("u_bias", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::VertexSource(
+									"#version 310 es\n"
+									"#extension GL_EXT_texture_cube_map_array : require\n"
+									"in highp vec4 a_position;\n"
+									"in mediump vec2 a_coord;\n"
+									"uniform mat3 u_coordMat;\n"
+									"out highp vec3 v_coord;\n"
+									"void main (void)\n"
+									"{\n"
+									"	gl_Position = a_position;\n"
+									"	v_coord = u_coordMat * vec3(a_coord, 1.0);\n"
+									"}\n")
+							<< sglr::pdec::FragmentSource(
+									string("") +
+									"#version 310 es\n"
+									"#extension GL_EXT_texture_cube_map_array : require\n"
+									"uniform highp " + glu::getDataTypeName(samplerType) + " u_sampler0;\n"
+									"uniform highp vec4 u_scale;\n"
+									"uniform highp vec4 u_bias;\n"
+									"uniform highp int u_layer;\n"
+									"in highp vec3 v_coord;\n"
+									"layout(location = 0) out highp " + glu::getDataTypeName(outputType) + " o_color;\n"
+									"void main (void)\n"
+									"{\n"
+									"	o_color = " + glu::getDataTypeName(outputType) + "(vec4(texture(u_sampler0, vec4(v_coord, u_layer))) * u_scale + u_bias);\n"
+									"}\n"))
+	, m_texScale	(1.0f)
+	, m_texBias		(0.0f)
+	, m_layer		(0)
+	, m_outputType	(outputType)
+{
+}
+
+void TextureCubeArrayShader::setLayer (int layer)
+{
+	m_layer = layer;
+}
+
+void TextureCubeArrayShader::setFace (tcu::CubeFace face)
+{
+	static const float s_cubeTransforms[][3*3] =
+	{
+		// Face -X: (x, y, 1) -> (-1, -(2*y-1), +(2*x-1))
+		{  0.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   2.0f,  0.0f, -1.0f },
+		// Face +X: (x, y, 1) -> (+1, -(2*y-1), -(2*x-1))
+		{  0.0f,  0.0f,  1.0f,
+		   0.0f, -2.0f,  1.0f,
+		  -2.0f,  0.0f,  1.0f },
+		// Face -Y: (x, y, 1) -> (+(2*x-1), -1, -(2*y-1))
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f },
+		// Face +Y: (x, y, 1) -> (+(2*x-1), +1, +(2*y-1))
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f,  0.0f,  1.0f,
+		   0.0f,  2.0f, -1.0f },
+		// Face -Z: (x, y, 1) -> (-(2*x-1), -(2*y-1), -1)
+		{ -2.0f,  0.0f,  1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   0.0f,  0.0f, -1.0f },
+		// Face +Z: (x, y, 1) -> (+(2*x-1), -(2*y-1), +1)
+		{  2.0f,  0.0f, -1.0f,
+		   0.0f, -2.0f,  1.0f,
+		   0.0f,  0.0f,  1.0f }
+	};
+	DE_ASSERT(de::inBounds<int>(face, 0, tcu::CUBEFACE_LAST));
+	m_coordMat = tcu::Mat3(s_cubeTransforms[face]);
+}
+
+void TextureCubeArrayShader::setTexScaleBias (const Vec4& scale, const Vec4& bias)
+{
+	m_texScale	= scale;
+	m_texBias	= bias;
+}
+
+void TextureCubeArrayShader::setUniforms (sglr::Context& gl, deUint32 program) const
+{
+	gl.useProgram(program);
+
+	gl.uniform1i(gl.getUniformLocation(program, "u_sampler0"), 0);
+	gl.uniformMatrix3fv(gl.getUniformLocation(program, "u_coordMat"), 1, GL_FALSE, m_coordMat.getColumnMajorData().getPtr());
+	gl.uniform1i(gl.getUniformLocation(program, "u_layer"), m_layer);
+	gl.uniform4fv(gl.getUniformLocation(program, "u_scale"), 1, m_texScale.getPtr());
+	gl.uniform4fv(gl.getUniformLocation(program, "u_bias"), 1, m_texBias.getPtr());
+}
+
+void TextureCubeArrayShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	tcu::Mat3 texCoordMat = tcu::Mat3(m_uniforms[0].value.m3);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::VertexPacket&	packet	= *packets[packetNdx];
+		const tcu::Vec2		a_coord = rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx).xy();
+		const tcu::Vec3		v_coord = texCoordMat * tcu::Vec3(a_coord.x(), a_coord.y(), 1.0f);
+
+		packet.position = rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
+		packet.outputs[0] = tcu::Vec4(v_coord.x(), v_coord.y(), v_coord.z(), 0.0f);
+	}
+}
+
+void TextureCubeArrayShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4 texScale (m_uniforms[2].value.f4);
+	const tcu::Vec4 texBias	 (m_uniforms[3].value.f4);
+
+	tcu::Vec4 texCoords[4];
+	tcu::Vec4 colors[4];
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const sglr::rc::TextureCubeArray* tex = m_uniforms[1].sampler.texCubeArray;
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4	coord = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
+			texCoords[fragNdx] = tcu::Vec4(coord.x(), coord.y(), coord.z(), (float)m_layer);
+		}
+
+		tex->sample4(colors, texCoords);
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		{
+			const tcu::Vec4		color	= colors[fragNdx] * texScale + texBias;
+			const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
+			const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);
+
+			if (m_outputType == glu::TYPE_FLOAT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+			else if (m_outputType == glu::TYPE_INT_VEC4)	rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
+			else if (m_outputType == glu::TYPE_UINT_VEC4)	rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
+			else
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+} // FboTestUtil
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fFboTestUtil.hpp b/modules/gles31/functional/es31fFboTestUtil.hpp
new file mode 100644
index 0000000..50d554c
--- /dev/null
+++ b/modules/gles31/functional/es31fFboTestUtil.hpp
@@ -0,0 +1,84 @@
+#ifndef _ES31FFBOTESTUTIL_HPP
+#define _ES31FFBOTESTUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief FBO test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "sglrContext.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuTexture.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace FboTestUtil
+{
+
+// \todo [2012-04-29 pyry] Clean up and name as SglrUtil
+
+// Helper class for constructing DataType vectors.
+struct DataTypes
+{
+	std::vector<glu::DataType> vec;
+	DataTypes& operator<< (glu::DataType type) { vec.push_back(type); return *this; }
+};
+
+// Shaders.
+
+class TextureCubeArrayShader : public sglr::ShaderProgram
+{
+public:
+						TextureCubeArrayShader	(glu::DataType samplerType, glu::DataType outputType);
+						~TextureCubeArrayShader	(void) {}
+
+	void				setLayer				(int layer);
+	void				setFace					(tcu::CubeFace face);
+	void				setTexScaleBias			(const tcu::Vec4& scale, const tcu::Vec4& bias);
+
+	void				setUniforms				(sglr::Context& context, deUint32 program) const;
+
+	void				shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void				shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+private:
+	tcu::Vec4			m_texScale;
+	tcu::Vec4			m_texBias;
+	int					m_layer;
+	tcu::Mat3			m_coordMat;
+
+	const glu::DataType	m_outputType;
+};
+
+} // FboTestUtil
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FFBOTESTUTIL_HPP
diff --git a/modules/gles31/functional/es31fFunctionalTests.cpp b/modules/gles31/functional/es31fFunctionalTests.cpp
new file mode 100644
index 0000000..3b07e6e
--- /dev/null
+++ b/modules/gles31/functional/es31fFunctionalTests.cpp
@@ -0,0 +1,312 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Functional Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fFunctionalTests.hpp"
+
+#include "glsShaderLibrary.hpp"
+#include "es31fBasicComputeShaderTests.hpp"
+#include "es31fComputeShaderBuiltinVarTests.hpp"
+#include "es31fDrawTests.hpp"
+#include "es31fShaderSharedVarTests.hpp"
+#include "es31fAtomicCounterTests.hpp"
+#include "es31fShaderAtomicOpTests.hpp"
+#include "es31fShaderImageLoadStoreTests.hpp"
+#include "es31fTessellationTests.hpp"
+#include "es31fSSBOLayoutTests.hpp"
+#include "es31fSSBOArrayLengthTests.hpp"
+#include "es31fShaderCommonFunctionTests.hpp"
+#include "es31fShaderPackingFunctionTests.hpp"
+#include "es31fShaderIntegerFunctionTests.hpp"
+#include "es31fStencilTexturingTests.hpp"
+#include "es31fShaderTextureSizeTests.hpp"
+#include "es31fShaderStateQueryTests.hpp"
+#include "es31fLayoutBindingTests.hpp"
+#include "es31fTextureLevelStateQueryTests.hpp"
+#include "es31fIntegerStateQueryTests.hpp"
+#include "es31fInternalFormatQueryTests.hpp"
+#include "es31fTextureFormatTests.hpp"
+#include "es31fTextureSpecificationTests.hpp"
+#include "es31fTextureMultisampleTests.hpp"
+#include "es31fMultisampleTests.hpp"
+#include "es31fSynchronizationTests.hpp"
+#include "es31fGeometryShaderTests.hpp"
+#include "es31fSampleShadingTests.hpp"
+#include "es31fSampleVariableTests.hpp"
+#include "es31fIndirectComputeDispatchTests.hpp"
+#include "es31fVertexAttributeBindingTests.hpp"
+#include "es31fVertexAttributeBindingStateQueryTests.hpp"
+#include "es31fShaderMultisampleInterpolationTests.hpp"
+#include "es31fShaderMultisampleInterpolationStateQueryTests.hpp"
+#include "es31fProgramUniformTests.hpp"
+#include "es31fOpaqueTypeIndexingTests.hpp"
+#include "es31fAdvancedBlendTests.hpp"
+#include "es31fSeparateShaderTests.hpp"
+#include "es31fUniformLocationTests.hpp"
+#include "es31fBuiltinPrecisionTests.hpp"
+#include "es31fTessellationGeometryInteractionTests.hpp"
+#include "es31fUniformBlockTests.hpp"
+#include "es31fDebugTests.hpp"
+#include "es31fFboNoAttachmentTests.hpp"
+#include "es31fProgramInterfaceQueryTests.hpp"
+#include "es31fTextureGatherTests.hpp"
+#include "es31fTextureFormatTests.hpp"
+#include "es31fTextureBufferTests.hpp"
+#include "es31fShaderBuiltinConstantTests.hpp"
+#include "es31fShaderHelperInvocationTests.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderLibraryTest : public TestCaseGroup
+{
+public:
+	ShaderLibraryTest (Context& context, const char* name, const char* description)
+		: TestCaseGroup	(context, name, description)
+		, m_filename	(name + std::string(".test"))
+	{
+	}
+
+	ShaderLibraryTest (Context& context, const char* filename, const char* name, const char* description)
+		: TestCaseGroup	(context, name, description)
+		, m_filename	(filename)
+	{
+	}
+
+	void init (void)
+	{
+		gls::ShaderLibrary			shaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
+		std::string					fileName	= "shaders/" + m_filename;
+		std::vector<tcu::TestNode*>	children	= shaderLibrary.loadShaderFile(fileName.c_str());
+
+		for (int i = 0; i < (int)children.size(); i++)
+			addChild(children[i]);
+	}
+
+private:
+	const std::string m_filename;
+};
+
+class ShaderBuiltinVarTests : public TestCaseGroup
+{
+public:
+	ShaderBuiltinVarTests (Context& context)
+		: TestCaseGroup(context, "builtin_var", "Shader Built-in Variable Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new ComputeShaderBuiltinVarTests(m_context));
+	}
+};
+
+class ShaderBuiltinFunctionTests : public TestCaseGroup
+{
+public:
+	ShaderBuiltinFunctionTests (Context& context)
+		: TestCaseGroup(context, "builtin_functions", "Built-in Function Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new ShaderCommonFunctionTests	(m_context));
+		addChild(new ShaderPackingFunctionTests	(m_context));
+		addChild(new ShaderIntegerFunctionTests	(m_context));
+		addChild(new ShaderTextureSizeTests		(m_context));
+		addChild(createBuiltinPrecisionTests	(m_context));
+	}
+};
+
+class ShaderLinkageTests : public TestCaseGroup
+{
+public:
+	ShaderLinkageTests (Context& context)
+		: TestCaseGroup(context,  "linkage", "Linkage Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new ShaderLibraryTest(m_context, "linkage_geometry.test", "geometry", "Geometry shader"));
+		addChild(new ShaderLibraryTest(m_context, "linkage_tessellation.test", "tessellation", "Tessellation shader"));
+		addChild(new ShaderLibraryTest(m_context, "linkage_tessellation_geometry.test", "tessellation_geometry", "Tessellation and geometry shader"));
+		addChild(new ShaderLibraryTest(m_context, "linkage_shader_storage_block.test", "shader_storage_block", "Shader storage blocks"));
+		addChild(new ShaderLibraryTest(m_context, "linkage_io_block.test", "io_block", "Shader io blocks"));
+	}
+};
+
+class ShaderTests : public TestCaseGroup
+{
+public:
+	ShaderTests (Context& context)
+		: TestCaseGroup(context, "shaders", "Shading Language Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new ShaderBuiltinVarTests				(m_context));
+		addChild(new ShaderBuiltinFunctionTests			(m_context));
+		addChild(new SampleVariableTests				(m_context));
+		addChild(new ShaderMultisampleInterpolationTests(m_context));
+		addChild(new OpaqueTypeIndexingTests			(m_context));
+		addChild(new ShaderLibraryTest					(m_context, "functions", "Function Tests"));
+		addChild(new ShaderLibraryTest					(m_context, "arrays_of_arrays", "Arrays of Arrays Tests"));
+		addChild(new ShaderLinkageTests					(m_context));
+		addChild(new ShaderBuiltinConstantTests			(m_context));
+		addChild(new ShaderHelperInvocationTests		(m_context));
+		addChild(new ShaderLibraryTest					(m_context, "implicit_conversions", "GL_EXT_shader_implicit_conversions Tests"));
+	}
+};
+
+class ComputeTests : public TestCaseGroup
+{
+public:
+	ComputeTests (Context& context)
+		: TestCaseGroup(context, "compute", "Compute Shader Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new BasicComputeShaderTests		(m_context));
+		addChild(new ShaderSharedVarTests			(m_context));
+		addChild(new IndirectComputeDispatchTests	(m_context));
+	}
+};
+
+class SSBOTests : public TestCaseGroup
+{
+public:
+	SSBOTests (Context& context)
+		: TestCaseGroup(context, "ssbo", "Shader Storage Buffer Object Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new SSBOLayoutTests			(m_context));
+		addChild(new ShaderAtomicOpTests		(m_context, "atomic", ATOMIC_OPERAND_BUFFER_VARIABLE));
+		addChild(new SSBOArrayLengthTests		(m_context));
+	}
+};
+
+class TextureTests : public TestCaseGroup
+{
+public:
+	TextureTests (Context& context)
+		: TestCaseGroup(context, "texture", "Texture tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new TextureFormatTests			(m_context));
+		addChild(new TextureSpecificationTests	(m_context));
+		addChild(new TextureMultisampleTests	(m_context));
+		addChild(new TextureGatherTests			(m_context));
+		addChild(createTextureBufferTests		(m_context));
+	}
+};
+
+class StateQueryTests : public TestCaseGroup
+{
+public:
+	StateQueryTests (Context& context)
+		: TestCaseGroup(context, "state_query", "State query tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new IntegerStateQueryTests							(m_context));
+		addChild(new TextureLevelStateQueryTests					(m_context));
+		addChild(new ShaderStateQueryTests							(m_context));
+		addChild(new InternalFormatQueryTests						(m_context));
+		addChild(new VertexAttributeBindingStateQueryTests			(m_context));
+		addChild(new ShaderMultisampleInterpolationStateQueryTests	(m_context));
+	}
+};
+
+class FboTests : public TestCaseGroup
+{
+public:
+	FboTests (Context& context)
+		: TestCaseGroup(context, "fbo", "Framebuffer Object Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(createFboNoAttachmentTests				(m_context));
+		addChild(createFboNoAttachmentCompletenessTests	(m_context));
+	}
+};
+
+FunctionalTests::FunctionalTests (Context& context)
+	: TestCaseGroup(context, "functional", "Functionality Tests")
+{
+}
+
+FunctionalTests::~FunctionalTests (void)
+{
+}
+
+void FunctionalTests::init (void)
+{
+	addChild(new ShaderTests							(m_context));
+	addChild(new ComputeTests							(m_context));
+	addChild(new DrawTests								(m_context));
+	addChild(new TessellationTests						(m_context));
+	addChild(new SSBOTests								(m_context));
+	addChild(new UniformBlockTests						(m_context));
+	addChild(new ShaderImageLoadStoreTests				(m_context));
+	addChild(new AtomicCounterTests						(m_context));
+	addChild(new StencilTexturingTests					(m_context));
+	addChild(new TextureTests							(m_context));
+	addChild(new StateQueryTests						(m_context));
+	addChild(new MultisampleTests						(m_context));
+	addChild(new SynchronizationTests					(m_context));
+	addChild(new GeometryShaderTests					(m_context));
+	addChild(new SampleShadingTests						(m_context));
+	addChild(new VertexAttributeBindingTests			(m_context));
+	addChild(new ProgramUniformTests					(m_context));
+	addChild(new AdvancedBlendTests						(m_context));
+	addChild(createSeparateShaderTests					(m_context));
+	addChild(new UniformLocationTests					(m_context));
+	addChild(new TessellationGeometryInteractionTests	(m_context));
+	addChild(new DebugTests								(m_context));
+	addChild(new FboTests								(m_context));
+	addChild(new ProgramInterfaceQueryTests				(m_context));
+	addChild(new LayoutBindingTests						(m_context));
+
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fFunctionalTests.hpp b/modules/gles31/functional/es31fFunctionalTests.hpp
new file mode 100644
index 0000000..aa6562b
--- /dev/null
+++ b/modules/gles31/functional/es31fFunctionalTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FFUNCTIONALTESTS_HPP
+#define _ES31FFUNCTIONALTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Functional Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class FunctionalTests : public TestCaseGroup
+{
+public:
+						FunctionalTests		(Context& context);
+						~FunctionalTests	(void);
+
+	void				init				(void);
+
+private:
+						FunctionalTests		(const FunctionalTests& other);
+	FunctionalTests&	operator=			(const FunctionalTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FFUNCTIONALTESTS_HPP
diff --git a/modules/gles31/functional/es31fGeometryShaderTests.cpp b/modules/gles31/functional/es31fGeometryShaderTests.cpp
new file mode 100644
index 0000000..a8401aa
--- /dev/null
+++ b/modules/gles31/functional/es31fGeometryShaderTests.cpp
@@ -0,0 +1,6189 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Geometry shader tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fGeometryShaderTests.hpp"
+
+#include "gluRenderContext.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluContextInfo.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTextureUtil.hpp"
+#include "glsStateQueryUtil.hpp"
+
+#include "gluStrUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+#include "deMemory.h"
+
+#include "sglrContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceUtils.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <algorithm>
+
+using namespace glw;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+const int TEST_CANVAS_SIZE = 256;
+
+static const char* const s_commonShaderSourceVertex =		"#version 310 es\n"
+															"in highp vec4 a_position;\n"
+															"in highp vec4 a_color;\n"
+															"out highp vec4 v_geom_FragColor;\n"
+															"void main (void)\n"
+															"{\n"
+															"	gl_Position = a_position;\n"
+															"	gl_PointSize = 1.0;\n"
+															"	v_geom_FragColor = a_color;\n"
+															"}\n";
+static const char* const s_commonShaderSourceFragment =		"#version 310 es\n"
+															"layout(location = 0) out mediump vec4 fragColor;\n"
+															"in mediump vec4 v_frag_FragColor;\n"
+															"void main (void)\n"
+															"{\n"
+															"	fragColor = v_frag_FragColor;\n"
+															"}\n";
+static const char* const s_expandShaderSourceGeometryBody =	"in highp vec4 v_geom_FragColor[];\n"
+															"out highp vec4 v_frag_FragColor;\n"
+															"\n"
+															"void main (void)\n"
+															"{\n"
+															"	const highp vec4 offset0 = vec4(-0.07, -0.01, 0.0, 0.0);\n"
+															"	const highp vec4 offset1 = vec4( 0.03, -0.03, 0.0, 0.0);\n"
+															"	const highp vec4 offset2 = vec4(-0.01,  0.08, 0.0, 0.0);\n"
+															"	      highp vec4 yoffset = float(gl_PrimitiveIDIn) * vec4(0.02, 0.1, 0.0, 0.0);\n"
+															"\n"
+															"	for (highp int ndx = 0; ndx < gl_in.length(); ndx++)\n"
+															"	{\n"
+															"		gl_Position = gl_in[ndx].gl_Position + offset0 + yoffset;\n"
+															"		gl_PrimitiveID = gl_PrimitiveIDIn;\n"
+															"		v_frag_FragColor = v_geom_FragColor[ndx];\n"
+															"		EmitVertex();\n"
+															"\n"
+															"		gl_Position = gl_in[ndx].gl_Position + offset1 + yoffset;\n"
+															"		gl_PrimitiveID = gl_PrimitiveIDIn;\n"
+															"		v_frag_FragColor = v_geom_FragColor[ndx];\n"
+															"		EmitVertex();\n"
+															"\n"
+															"		gl_Position = gl_in[ndx].gl_Position + offset2 + yoffset;\n"
+															"		gl_PrimitiveID = gl_PrimitiveIDIn;\n"
+															"		v_frag_FragColor = v_geom_FragColor[ndx];\n"
+															"		EmitVertex();\n"
+															"		EndPrimitive();\n"
+															"	}\n"
+															"}\n";
+
+std::string inputTypeToGLString (rr::GeometryShaderInputType inputType)
+{
+	switch (inputType)
+	{
+		case rr::GEOMETRYSHADERINPUTTYPE_POINTS:				return "points";
+		case rr::GEOMETRYSHADERINPUTTYPE_LINES:					return "lines";
+		case rr::GEOMETRYSHADERINPUTTYPE_LINES_ADJACENCY:		return "lines_adjacency";
+		case rr::GEOMETRYSHADERINPUTTYPE_TRIANGLES:				return "triangles";
+		case rr::GEOMETRYSHADERINPUTTYPE_TRIANGLES_ADJACENCY:	return "triangles_adjacency";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return "error";
+	}
+}
+
+std::string outputTypeToGLString (rr::GeometryShaderOutputType outputType)
+{
+	switch (outputType)
+	{
+		case rr::GEOMETRYSHADEROUTPUTTYPE_POINTS:				return "points";
+		case rr::GEOMETRYSHADEROUTPUTTYPE_LINE_STRIP:			return "line_strip";
+		case rr::GEOMETRYSHADEROUTPUTTYPE_TRIANGLE_STRIP:		return "triangle_strip";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return "error";
+	}
+}
+
+std::string primitiveTypeToString(GLenum primitive)
+{
+	switch (primitive)
+	{
+		case GL_POINTS:						 return "points";
+		case GL_LINES:					   	 return "lines";
+		case GL_LINE_LOOP:				   	 return "line_loop";
+		case GL_LINE_STRIP:				   	 return "line_strip";
+		case GL_LINES_ADJACENCY:		   	 return "lines_adjacency";
+		case GL_LINE_STRIP_ADJACENCY:	   	 return "line_strip_adjacency";
+		case GL_TRIANGLES:				   	 return "triangles";
+		case GL_TRIANGLE_STRIP:			   	 return "triangle_strip";
+		case GL_TRIANGLE_FAN:			   	 return "triangle_fan";
+		case GL_TRIANGLES_ADJACENCY:	   	 return "triangles_adjacency";
+		case GL_TRIANGLE_STRIP_ADJACENCY:  	 return "triangle_strip_adjacency";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return "error";
+	}
+}
+
+struct OutputCountPatternSpec
+{
+						OutputCountPatternSpec (int count);
+						OutputCountPatternSpec (int count0, int count1);
+
+	std::vector<int>	pattern;
+};
+
+OutputCountPatternSpec::OutputCountPatternSpec (int count)
+{
+	pattern.push_back(count);
+}
+
+OutputCountPatternSpec::OutputCountPatternSpec (int count0, int count1)
+{
+	pattern.push_back(count0);
+	pattern.push_back(count1);
+}
+
+class VertexExpanderShader : public sglr::ShaderProgram
+{
+public:
+				VertexExpanderShader	(rr::GeometryShaderInputType inputType, rr::GeometryShaderOutputType outputType);
+
+	void		shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void		shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+	void		shadePrimitives			(rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const;
+
+private:
+	size_t		calcOutputVertices		(rr::GeometryShaderInputType inputType) const;
+	std::string	genGeometrySource		(rr::GeometryShaderInputType inputType, rr::GeometryShaderOutputType outputType) const;
+};
+
+VertexExpanderShader::VertexExpanderShader (rr::GeometryShaderInputType inputType, rr::GeometryShaderOutputType outputType)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToGeometryVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::GeometryToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(s_commonShaderSourceVertex)
+							<< sglr::pdec::FragmentSource(s_commonShaderSourceFragment)
+							<< sglr::pdec::GeometryShaderDeclaration(inputType, outputType, calcOutputVertices(inputType))
+							<< sglr::pdec::GeometrySource(genGeometrySource(inputType, outputType).c_str()))
+{
+}
+
+void VertexExpanderShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		packets[ndx]->position = rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+		packets[ndx]->pointSize = 1.0f;
+		packets[ndx]->outputs[0] = rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+	}
+}
+
+void VertexExpanderShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packets[packetNdx], context, 0, fragNdx));
+}
+
+void VertexExpanderShader::shadePrimitives (rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const
+{
+	DE_UNREF(invocationID);
+
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	for (int verticeNdx = 0; verticeNdx < verticesIn; ++verticeNdx)
+	{
+		const tcu::Vec4 offsets[] =
+		{
+			tcu::Vec4(-0.07f, -0.01f, 0.0f, 0.0f),
+			tcu::Vec4( 0.03f, -0.03f, 0.0f, 0.0f),
+			tcu::Vec4(-0.01f,  0.08f, 0.0f, 0.0f)
+		};
+		const tcu::Vec4 yoffset = float(packets[ndx].primitiveIDIn) * tcu::Vec4(0.02f, 0.1f, 0, 0);
+
+		// Create new primitive at every input vertice
+		const rr::VertexPacket* vertex = packets[ndx].vertices[verticeNdx];
+
+		output.EmitVertex(vertex->position + offsets[0] + yoffset, vertex->pointSize, vertex->outputs, packets[ndx].primitiveIDIn);
+		output.EmitVertex(vertex->position + offsets[1] + yoffset, vertex->pointSize, vertex->outputs, packets[ndx].primitiveIDIn);
+		output.EmitVertex(vertex->position + offsets[2] + yoffset, vertex->pointSize, vertex->outputs, packets[ndx].primitiveIDIn);
+		output.EndPrimitive();
+	}
+}
+
+size_t VertexExpanderShader::calcOutputVertices (rr::GeometryShaderInputType inputType) const
+{
+	switch (inputType)
+	{
+		case rr::GEOMETRYSHADERINPUTTYPE_POINTS:				return 1 * 3;
+		case rr::GEOMETRYSHADERINPUTTYPE_LINES:					return 2 * 3;
+		case rr::GEOMETRYSHADERINPUTTYPE_LINES_ADJACENCY:		return 4 * 3;
+		case rr::GEOMETRYSHADERINPUTTYPE_TRIANGLES:				return 3 * 3;
+		case rr::GEOMETRYSHADERINPUTTYPE_TRIANGLES_ADJACENCY:	return 6 * 3;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+std::string	VertexExpanderShader::genGeometrySource (rr::GeometryShaderInputType inputType, rr::GeometryShaderOutputType outputType) const
+{
+	std::ostringstream str;
+
+	str << "#version 310 es\n";
+	str << "#extension GL_EXT_geometry_shader : require\n";
+	str << "layout(" << inputTypeToGLString(inputType) << ") in;\n";
+	str << "layout(" << outputTypeToGLString(outputType) << ", max_vertices = " << calcOutputVertices(inputType) << ") out;";
+	str << "\n";
+	str << s_expandShaderSourceGeometryBody;
+
+	return str.str();
+}
+
+class VertexEmitterShader : public sglr::ShaderProgram
+{
+public:
+				VertexEmitterShader		(int emitCountA, int endCountA, int emitCountB, int endCountB, rr::GeometryShaderOutputType outputType);
+
+	void		shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void		shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+	void		shadePrimitives			(rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const;
+
+private:
+	std::string	genGeometrySource		(int emitCountA, int endCountA, int emitCountB, int endCountB, rr::GeometryShaderOutputType outputType) const;
+
+	int			m_emitCountA;
+	int			m_endCountA;
+	int			m_emitCountB;
+	int			m_endCountB;
+};
+
+VertexEmitterShader::VertexEmitterShader (int emitCountA, int endCountA, int emitCountB, int endCountB, rr::GeometryShaderOutputType outputType)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToGeometryVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::GeometryToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(s_commonShaderSourceVertex)
+							<< sglr::pdec::FragmentSource(s_commonShaderSourceFragment)
+							<< sglr::pdec::GeometryShaderDeclaration(rr::GEOMETRYSHADERINPUTTYPE_POINTS, outputType, emitCountA + emitCountB)
+							<< sglr::pdec::GeometrySource(genGeometrySource(emitCountA, endCountA, emitCountB, endCountB, outputType).c_str()))
+	, m_emitCountA		(emitCountA)
+	, m_endCountA		(endCountA)
+	, m_emitCountB		(emitCountB)
+	, m_endCountB		(endCountB)
+{
+}
+
+void VertexEmitterShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		packets[ndx]->position = rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+		packets[ndx]->pointSize = 1.0f;
+		packets[ndx]->outputs[0] = rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+	}
+}
+
+void VertexEmitterShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packets[packetNdx], context, 0, fragNdx));
+}
+
+void VertexEmitterShader::shadePrimitives (rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const
+{
+	DE_UNREF(verticesIn);
+	DE_UNREF(invocationID);
+
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		const tcu::Vec4 positions[] =
+		{
+			tcu::Vec4(-0.5f,   0.5f, 0.0f, 0.0f),
+			tcu::Vec4( 0.0f,   0.1f, 0.0f, 0.0f),
+			tcu::Vec4( 0.5f,   0.5f, 0.0f, 0.0f),
+			tcu::Vec4( 0.7f,  -0.2f, 0.0f, 0.0f),
+			tcu::Vec4( 0.2f,   0.2f, 0.0f, 0.0f),
+			tcu::Vec4( 0.4f,  -0.3f, 0.0f, 0.0f),
+		};
+
+		// Create new primitive at this point
+		const rr::VertexPacket* vertex = packets[ndx].vertices[0];
+
+		for (int i = 0; i < m_emitCountA; ++i)
+			output.EmitVertex(vertex->position + positions[i], vertex->pointSize, vertex->outputs, packets[ndx].primitiveIDIn);
+
+		for (int i = 0; i < m_endCountA; ++i)
+			output.EndPrimitive();
+
+		for (int i = 0; i < m_emitCountB; ++i)
+			output.EmitVertex(vertex->position + positions[m_emitCountA + i], vertex->pointSize, vertex->outputs, packets[ndx].primitiveIDIn);
+
+		for (int i = 0; i < m_endCountB; ++i)
+			output.EndPrimitive();
+	}
+}
+
+std::string	VertexEmitterShader::genGeometrySource (int emitCountA, int endCountA, int emitCountB, int endCountB, rr::GeometryShaderOutputType outputType) const
+{
+	std::ostringstream str;
+
+	str << "#version 310 es\n";
+	str << "#extension GL_EXT_geometry_shader : require\n";
+	str << "layout(points) in;\n";
+	str << "layout(" << outputTypeToGLString(outputType) << ", max_vertices = " << (emitCountA+emitCountB) << ") out;";
+	str << "\n";
+
+	str <<	"in highp vec4 v_geom_FragColor[];\n"
+			"out highp vec4 v_frag_FragColor;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	const highp vec4 position0 = vec4(-0.5,  0.5, 0.0, 0.0);\n"
+			"	const highp vec4 position1 = vec4( 0.0,  0.1, 0.0, 0.0);\n"
+			"	const highp vec4 position2 = vec4( 0.5,  0.5, 0.0, 0.0);\n"
+			"	const highp vec4 position3 = vec4( 0.7, -0.2, 0.0, 0.0);\n"
+			"	const highp vec4 position4 = vec4( 0.2,  0.2, 0.0, 0.0);\n"
+			"	const highp vec4 position5 = vec4( 0.4, -0.3, 0.0, 0.0);\n"
+			"\n";
+
+	for (int i = 0; i < emitCountA; ++i)
+		str <<	"	gl_Position = gl_in[0].gl_Position + position" << i << ";\n"
+				"	gl_PrimitiveID = gl_PrimitiveIDIn;\n"
+				"	v_frag_FragColor = v_geom_FragColor[0];\n"
+				"	EmitVertex();\n"
+				"\n";
+
+	for (int i = 0; i < endCountA; ++i)
+		str << "	EndPrimitive();\n";
+
+	for (int i = 0; i < emitCountB; ++i)
+		str <<	"	gl_Position = gl_in[0].gl_Position + position" << (emitCountA + i) << ";\n"
+				"	gl_PrimitiveID = gl_PrimitiveIDIn;\n"
+				"	v_frag_FragColor = v_geom_FragColor[0];\n"
+				"	EmitVertex();\n"
+				"\n";
+
+	for (int i = 0; i < endCountB; ++i)
+		str << "	EndPrimitive();\n";
+
+
+	str << "}\n";
+
+	return str.str();
+}
+
+class VertexVaryingShader : public sglr::ShaderProgram
+{
+public:
+												VertexVaryingShader		(int vertexOut, int geometryOut);
+
+	void										shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void										shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+	void										shadePrimitives			(rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const;
+
+private:
+	static sglr::pdec::ShaderProgramDeclaration genProgramDeclaration	(int vertexOut, int geometryOut);
+
+	const int									m_vertexOut;
+	const int									m_geometryOut;
+};
+
+VertexVaryingShader::VertexVaryingShader (int vertexOut, int geometryOut)
+	: sglr::ShaderProgram	(genProgramDeclaration(vertexOut, geometryOut))
+	, m_vertexOut			(vertexOut)
+	, m_geometryOut			(geometryOut)
+{
+}
+
+void VertexVaryingShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	// vertex shader is no-op
+	if (m_vertexOut == -1)
+		return;
+
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		const tcu::Vec4 color = rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+
+		packets[ndx]->position = rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+		packets[ndx]->pointSize = 1.0f;
+
+		switch (m_vertexOut)
+		{
+			case 0:
+				break;
+
+			case 1:
+				packets[ndx]->outputs[0] = color;
+				break;
+
+			case 2:
+				packets[ndx]->outputs[0] = color * 0.5f;
+				packets[ndx]->outputs[1] = color.swizzle(2,1,0,3) * 0.5f;
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+void VertexVaryingShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		switch (m_geometryOut)
+		{
+			case 0:
+				for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+					rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+				break;
+
+			case 1:
+				for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+					rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx));
+				break;
+
+			case 2:
+				for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+					rr::writeFragmentOutput(context, packetNdx, fragNdx, 0,   rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx)
+					                                                        + rr::readTriangleVarying<float>(packets[packetNdx], context, 1, fragNdx).swizzle(1, 0, 2, 3));
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+void VertexVaryingShader::shadePrimitives (rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const
+{
+	DE_UNREF(invocationID);
+
+	const tcu::Vec4 vertexOffset(-0.2f, -0.2f, 0, 0);
+
+	if (m_vertexOut == -1)
+	{
+		// vertex is a no-op
+		const tcu::Vec4 inputColor = tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+		rr::GenericVec4	outputs[2];
+
+		// output color
+		switch (m_geometryOut)
+		{
+			case 0:
+				break;
+
+			case 1:
+				outputs[0] = inputColor;
+				break;
+
+			case 2:
+				outputs[0] = inputColor * 0.5f;
+				outputs[1] = inputColor.swizzle(1, 0, 2, 3) * 0.5f;
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+
+		for (int ndx = 0; ndx < numPackets; ++ndx)
+		{
+			output.EmitVertex(tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f) + vertexOffset, 1.0f, outputs, packets[ndx].primitiveIDIn);
+			output.EmitVertex(tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f) + vertexOffset, 1.0f, outputs, packets[ndx].primitiveIDIn);
+			output.EmitVertex(tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f) + vertexOffset, 1.0f, outputs, packets[ndx].primitiveIDIn);
+			output.EndPrimitive();
+		}
+	}
+	else
+	{
+		// vertex is not a no-op
+		for (int ndx = 0; ndx < numPackets; ++ndx)
+		{
+			for (int verticeNdx = 0; verticeNdx < verticesIn; ++verticeNdx)
+			{
+				tcu::Vec4		inputColor;
+				rr::GenericVec4	outputs[2];
+
+				// input color
+				switch (m_vertexOut)
+				{
+					case 0:
+						inputColor = tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+						break;
+
+					case 1:
+						inputColor = packets[ndx].vertices[verticeNdx]->outputs[0].get<float>();
+						break;
+
+					case 2:
+						inputColor = (packets[ndx].vertices[verticeNdx]->outputs[0].get<float>() * 0.5f)
+								   + (packets[ndx].vertices[verticeNdx]->outputs[1].get<float>().swizzle(2, 1, 0, 3) * 0.5f);
+						break;
+
+					default:
+						DE_ASSERT(DE_FALSE);
+				}
+
+				// output color
+				switch (m_geometryOut)
+				{
+					case 0:
+						break;
+
+					case 1:
+						outputs[0] = inputColor;
+						break;
+
+					case 2:
+						outputs[0] = inputColor * 0.5f;
+						outputs[1] = inputColor.swizzle(1, 0, 2, 3) * 0.5f;
+						break;
+
+					default:
+						DE_ASSERT(DE_FALSE);
+				}
+
+				output.EmitVertex(packets[ndx].vertices[verticeNdx]->position + vertexOffset, packets[ndx].vertices[verticeNdx]->pointSize, outputs, packets[ndx].primitiveIDIn);
+			}
+			output.EndPrimitive();
+		}
+	}
+}
+
+sglr::pdec::ShaderProgramDeclaration VertexVaryingShader::genProgramDeclaration	(int vertexOut, int geometryOut)
+{
+	sglr::pdec::ShaderProgramDeclaration	decl;
+	std::ostringstream						vertexSource;
+	std::ostringstream						fragmentSource;
+	std::ostringstream						geometrySource;
+
+	decl
+		<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+		<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT);
+
+	for (int i = 0; i < vertexOut; ++i)
+		decl << sglr::pdec::VertexToGeometryVarying(rr::GENERICVECTYPE_FLOAT);
+	for (int i = 0; i < geometryOut; ++i)
+		decl << sglr::pdec::GeometryToFragmentVarying(rr::GENERICVECTYPE_FLOAT);
+
+	decl
+		<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+		<< sglr::pdec::GeometryShaderDeclaration(rr::GEOMETRYSHADERINPUTTYPE_TRIANGLES, rr::GEOMETRYSHADEROUTPUTTYPE_TRIANGLE_STRIP, 3);
+
+	// vertexSource
+
+	vertexSource << "#version 310 es\n"
+					"in highp vec4 a_position;\n"
+					"in highp vec4 a_color;\n";
+
+	// no-op case?
+	if (vertexOut == -1)
+	{
+		vertexSource << "void main (void)\n"
+						"{\n"
+						"}\n";
+	}
+	else
+	{
+		for (int i = 0; i < vertexOut; ++i)
+			vertexSource << "out highp vec4 v_geom_" << i << ";\n";
+
+		vertexSource << "void main (void)\n"
+						"{\n"
+						"\tgl_Position = a_position;\n"
+						"\tgl_PointSize = 1.0;\n";
+		switch (vertexOut)
+		{
+			case 0:
+				break;
+
+			case 1:
+				vertexSource << "\tv_geom_0 = a_color;\n";
+				break;
+
+			case 2:
+				vertexSource << "\tv_geom_0 = a_color * 0.5;\n";
+				vertexSource << "\tv_geom_1 = a_color.zyxw * 0.5;\n";
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+		vertexSource << "}\n";
+	}
+
+	// fragmentSource
+
+	fragmentSource <<	"#version 310 es\n"
+						"layout(location = 0) out mediump vec4 fragColor;\n";
+
+	for (int i = 0; i < geometryOut; ++i)
+		fragmentSource << "in mediump vec4 v_frag_" << i << ";\n";
+
+	fragmentSource <<	"void main (void)\n"
+						"{\n";
+	switch (geometryOut)
+	{
+		case 0:
+			fragmentSource << "\tfragColor = vec4(1.0, 0.0, 0.0, 1.0);\n";
+			break;
+
+		case 1:
+			fragmentSource << "\tfragColor = v_frag_0;\n";
+			break;
+
+		case 2:
+			fragmentSource << "\tfragColor = v_frag_0 + v_frag_1.yxzw;\n";
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+	fragmentSource << "}\n";
+
+	// geometrySource
+
+	geometrySource <<	"#version 310 es\n"
+						"#extension GL_EXT_geometry_shader : require\n"
+						"layout(triangles) in;\n"
+						"layout(triangle_strip, max_vertices = 3) out;\n";
+
+	for (int i = 0; i < vertexOut; ++i)
+		geometrySource << "in highp vec4 v_geom_" << i << "[];\n";
+	for (int i = 0; i < geometryOut; ++i)
+		geometrySource << "out highp vec4 v_frag_" << i << ";\n";
+
+	geometrySource <<	"void main (void)\n"
+						"{\n"
+						"\thighp vec4 offset = vec4(-0.2, -0.2, 0.0, 0.0);\n"
+						"\thighp vec4 inputColor;\n\n";
+
+	for (int vertexNdx = 0; vertexNdx < 3; ++vertexNdx)
+	{
+		if (vertexOut == -1)
+		{
+			// vertex is a no-op
+			geometrySource <<	"\tinputColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+								"\tgl_Position = vec4(" << ((vertexNdx==0) ? ("0.0, 0.0") : ((vertexNdx==1) ? ("1.0, 0.0") : ("1.0, 1.0"))) << ", 0.0, 1.0) + offset;\n"
+								"\tgl_PrimitiveID = gl_PrimitiveIDIn;\n";
+		}
+		else
+		{
+			switch (vertexOut)
+			{
+				case 0:
+					geometrySource << "\tinputColor = vec4(1.0, 0.0, 0.0, 1.0);\n";
+					break;
+
+				case 1:
+					geometrySource << "\tinputColor = v_geom_0[" << vertexNdx << "];\n";
+					break;
+
+				case 2:
+					geometrySource << "\tinputColor = v_geom_0[" << vertexNdx << "] * 0.5 + v_geom_1[" << vertexNdx << "].zyxw * 0.5;\n";
+					break;
+
+				default:
+					DE_ASSERT(DE_FALSE);
+			}
+			geometrySource <<	"\tgl_Position = gl_in[" << vertexNdx << "].gl_Position + offset;\n"
+								"\tgl_PrimitiveID = gl_PrimitiveIDIn;\n";
+		}
+
+		switch (geometryOut)
+		{
+			case 0:
+				break;
+
+			case 1:
+				geometrySource << "\tv_frag_0 = inputColor;\n";
+				break;
+
+			case 2:
+				geometrySource << "\tv_frag_0 = inputColor * 0.5;\n";
+				geometrySource << "\tv_frag_1 = inputColor.yxzw * 0.5;\n";
+				break;
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+
+		geometrySource << "\tEmitVertex();\n\n";
+	}
+
+	geometrySource <<	"\tEndPrimitive();\n"
+						"}\n";
+
+	decl
+		<< sglr::pdec::VertexSource(vertexSource.str().c_str())
+		<< sglr::pdec::FragmentSource(fragmentSource.str().c_str())
+		<< sglr::pdec::GeometrySource(geometrySource.str().c_str());
+	return decl;
+}
+
+class OutputCountShader : public sglr::ShaderProgram
+{
+public:
+									OutputCountShader		(const OutputCountPatternSpec& spec);
+
+	void							shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void							shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+	void							shadePrimitives			(rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const;
+
+private:
+	std::string						genGeometrySource		(const OutputCountPatternSpec& spec) const;
+	size_t							getPatternEmitCount		(const OutputCountPatternSpec& spec) const;
+
+	const int						m_patternLength;
+	const int						m_patternMaxEmitCount;
+	const OutputCountPatternSpec	m_spec;
+};
+
+OutputCountShader::OutputCountShader (const OutputCountPatternSpec& spec)
+	: sglr::ShaderProgram	(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToGeometryVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::GeometryToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(s_commonShaderSourceVertex)
+							<< sglr::pdec::FragmentSource(s_commonShaderSourceFragment)
+							<< sglr::pdec::GeometryShaderDeclaration(rr::GEOMETRYSHADERINPUTTYPE_POINTS, rr::GEOMETRYSHADEROUTPUTTYPE_TRIANGLE_STRIP, getPatternEmitCount(spec))
+							<< sglr::pdec::GeometrySource(genGeometrySource(spec).c_str()))
+	, m_patternLength		((int)spec.pattern.size())
+	, m_patternMaxEmitCount	((int)getPatternEmitCount(spec))
+	, m_spec				(spec)
+{
+}
+
+void OutputCountShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		packets[ndx]->position = rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+		packets[ndx]->pointSize = 1.0f;
+		packets[ndx]->outputs[0] = rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+	}
+}
+
+void OutputCountShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packets[packetNdx], context, 0, fragNdx));
+}
+
+void OutputCountShader::shadePrimitives (rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const
+{
+	DE_UNREF(verticesIn);
+	DE_UNREF(invocationID);
+
+	const float rowHeight	= 2.0f / (float)m_patternLength;
+	const float colWidth	= 2.0f / (float)m_patternMaxEmitCount;
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		// Create triangle strip at this point
+		const rr::VertexPacket*	vertex		= packets[packetNdx].vertices[0];
+		const int				emitCount	= m_spec.pattern[packets[packetNdx].primitiveIDIn];
+
+		for (int ndx = 0; ndx < emitCount / 2; ++ndx)
+		{
+			output.EmitVertex(vertex->position + tcu::Vec4(2 * ndx * colWidth, 0.0,       0.0, 0.0), vertex->pointSize, vertex->outputs, packets[packetNdx].primitiveIDIn);
+			output.EmitVertex(vertex->position + tcu::Vec4(2 * ndx * colWidth, rowHeight, 0.0, 0.0), vertex->pointSize, vertex->outputs, packets[packetNdx].primitiveIDIn);
+		}
+		output.EndPrimitive();
+	}
+}
+
+std::string	OutputCountShader::genGeometrySource (const OutputCountPatternSpec& spec) const
+{
+	std::ostringstream str;
+
+	// draw row with a triangle strip, always make rectangles
+	for (int ndx = 0; ndx < (int)spec.pattern.size(); ++ndx)
+		DE_ASSERT(spec.pattern[ndx] % 2 == 0);
+
+	str << "#version 310 es\n";
+	str << "#extension GL_EXT_geometry_shader : require\n";
+	str << "layout(points) in;\n";
+	str << "layout(triangle_strip, max_vertices = " << getPatternEmitCount(spec) << ") out;";
+	str << "\n";
+
+	str <<	"in highp vec4 v_geom_FragColor[];\n"
+			"out highp vec4 v_frag_FragColor;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	const highp float rowHeight = 2.0 / float(" << spec.pattern.size() << ");\n"
+			"	const highp float colWidth = 2.0 / float(" << getPatternEmitCount(spec) << ");\n"
+			"\n";
+
+	str <<	"	highp int emitCount = ";
+	for (int ndx = 0; ndx < (int)spec.pattern.size() - 1; ++ndx)
+		str << "(gl_PrimitiveIDIn == " << ndx << ") ? (" << spec.pattern[ndx] << ") : (";
+	str <<	spec.pattern[(int)spec.pattern.size() - 1]
+		<<	((spec.pattern.size() == 1) ? ("") : (")"))
+		<<	";\n";
+
+	str <<	"	for (highp int ndx = 0; ndx < emitCount / 2; ndx++)\n"
+			"	{\n"
+			"		gl_Position = gl_in[0].gl_Position + vec4(float(ndx) * 2.0 * colWidth, 0.0, 0.0, 0.0);\n"
+			"		v_frag_FragColor = v_geom_FragColor[0];\n"
+			"		EmitVertex();\n"
+			"\n"
+			"		gl_Position = gl_in[0].gl_Position + vec4(float(ndx) * 2.0 * colWidth, rowHeight, 0.0, 0.0);\n"
+			"		v_frag_FragColor = v_geom_FragColor[0];\n"
+			"		EmitVertex();\n"
+			"	}\n"
+			"}\n";
+
+	return str.str();
+}
+
+size_t OutputCountShader::getPatternEmitCount (const OutputCountPatternSpec& spec) const
+{
+	return *std::max_element(spec.pattern.begin(), spec.pattern.end());
+}
+
+class BuiltinVariableShader : public sglr::ShaderProgram
+{
+public:
+	enum VariableTest
+	{
+		TEST_POINT_SIZE = 0,
+		TEST_PRIMITIVE_ID_IN,
+		TEST_PRIMITIVE_ID,
+
+		TEST_LAST
+	};
+
+						BuiltinVariableShader	(VariableTest test);
+
+	void				shadeVertices			(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void				shadeFragments			(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+	void				shadePrimitives			(rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const;
+
+	static const char*	getTestAttributeName	(VariableTest test);
+
+private:
+	std::string			genGeometrySource		(VariableTest test) const;
+	std::string			genVertexSource			(VariableTest test) const;
+	std::string			genFragmentSource		(VariableTest test) const;
+
+	const VariableTest	m_test;
+};
+
+BuiltinVariableShader::BuiltinVariableShader (VariableTest test)
+	: sglr::ShaderProgram	(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute(getTestAttributeName(test), rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToGeometryVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::GeometryToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(genVertexSource(test))
+							<< sglr::pdec::FragmentSource(genFragmentSource(test))
+							<< sglr::pdec::GeometryShaderDeclaration(rr::GEOMETRYSHADERINPUTTYPE_POINTS,
+																	 ((test == TEST_POINT_SIZE) ? (rr::GEOMETRYSHADEROUTPUTTYPE_POINTS) : (rr::GEOMETRYSHADEROUTPUTTYPE_TRIANGLE_STRIP)),
+																	 ((test == TEST_POINT_SIZE) ? (1) : (3)))
+							<< sglr::pdec::GeometrySource(genGeometrySource(test).c_str()))
+	, m_test				(test)
+{
+}
+
+void BuiltinVariableShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		packets[ndx]->position = rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+		packets[ndx]->pointSize = 1.0f;
+		packets[ndx]->outputs[0] = rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+	}
+}
+
+void BuiltinVariableShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4 red			= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4 green		= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 blue		= tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f);
+	const tcu::Vec4 yellow		= tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 colors[4]	= { yellow, red, green, blue };
+
+	if (m_test == TEST_POINT_SIZE || m_test == TEST_PRIMITIVE_ID_IN)
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packets[packetNdx], context, 0, fragNdx));
+	}
+	else if (m_test == TEST_PRIMITIVE_ID)
+	{
+		const tcu::Vec4 color = colors[context.primitiveID % 4];
+
+		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+}
+
+void BuiltinVariableShader::shadePrimitives (rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const
+{
+	DE_UNREF(verticesIn);
+	DE_UNREF(invocationID);
+
+	const tcu::Vec4 red			= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4 green		= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 blue		= tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f);
+	const tcu::Vec4 yellow		= tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 colors[4]	= { red, green, blue, yellow };
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const rr::VertexPacket*	vertex = packets[packetNdx].vertices[0];
+
+		if (m_test == TEST_POINT_SIZE)
+		{
+			rr::GenericVec4	fragColor;
+			const float		pointSize = vertex->outputs[0].get<float>().x() + 1.0f;
+
+			fragColor = tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);
+			output.EmitVertex(vertex->position, pointSize, &fragColor, packets[packetNdx].primitiveIDIn);
+		}
+		else if (m_test == TEST_PRIMITIVE_ID_IN)
+		{
+			rr::GenericVec4	fragColor;
+			fragColor = colors[packets[packetNdx].primitiveIDIn % 4];
+
+			output.EmitVertex(vertex->position + tcu::Vec4(0.05f, 0.0f,  0.0f, 0.0f), 1.0f, &fragColor, packets[packetNdx].primitiveIDIn);
+			output.EmitVertex(vertex->position - tcu::Vec4(0.05f, 0.0f,  0.0f, 0.0f), 1.0f, &fragColor, packets[packetNdx].primitiveIDIn);
+			output.EmitVertex(vertex->position + tcu::Vec4(0.0f,  0.05f, 0.0f, 0.0f), 1.0f, &fragColor, packets[packetNdx].primitiveIDIn);
+		}
+		else if (m_test == TEST_PRIMITIVE_ID)
+		{
+			const int primitiveID = (int)deFloatFloor(vertex->outputs[0].get<float>().x()) + 3;
+
+			output.EmitVertex(vertex->position + tcu::Vec4(0.05f, 0.0f,  0.0f, 0.0f), 1.0f, vertex->outputs, primitiveID);
+			output.EmitVertex(vertex->position - tcu::Vec4(0.05f, 0.0f,  0.0f, 0.0f), 1.0f, vertex->outputs, primitiveID);
+			output.EmitVertex(vertex->position + tcu::Vec4(0.0f,  0.05f, 0.0f, 0.0f), 1.0f, vertex->outputs, primitiveID);
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		output.EndPrimitive();
+	}
+}
+
+const char* BuiltinVariableShader::getTestAttributeName (VariableTest test)
+{
+	switch (test)
+	{
+		case TEST_POINT_SIZE:			return "a_pointSize";
+		case TEST_PRIMITIVE_ID_IN:		return "";
+		case TEST_PRIMITIVE_ID:			return "a_primitiveID";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return "";
+	}
+}
+
+std::string BuiltinVariableShader::genGeometrySource (VariableTest test) const
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n";
+
+	if (test == TEST_POINT_SIZE)
+		buf << "#extension GL_EXT_geometry_point_size : require\n";
+
+	buf << "layout(points) in;\n";
+
+	if (test == TEST_POINT_SIZE)
+		buf << "layout(points, max_vertices = 1) out;\n";
+	else
+		buf << "layout(triangle_strip, max_vertices = 3) out;\n";
+
+	if (test == TEST_POINT_SIZE)
+		buf << "in highp vec4 v_geom_pointSize[];\n";
+	else if (test == TEST_PRIMITIVE_ID)
+		buf << "in highp vec4 v_geom_primitiveID[];\n";
+
+	if (test != TEST_PRIMITIVE_ID)
+		buf << "out highp vec4 v_frag_FragColor;\n";
+
+	buf <<	"\n"
+			"void main (void)\n"
+			"{\n";
+
+	if (test == TEST_POINT_SIZE)
+	{
+		buf <<	"	gl_Position = gl_in[0].gl_Position;\n"
+				"	gl_PointSize = v_geom_pointSize[0].x + 1.0;\n"
+				"	v_frag_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+				"	EmitVertex();\n";
+	}
+	else if (test == TEST_PRIMITIVE_ID_IN)
+	{
+		buf <<	"	const highp vec4 red = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	const highp vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"	const highp vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);\n"
+				"	const highp vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
+				"	const highp vec4 colors[4] = vec4[4](red, green, blue, yellow);\n"
+				"\n"
+				"	gl_Position = gl_in[0].gl_Position + vec4(0.05, 0.0, 0.0, 0.0);\n"
+				"	v_frag_FragColor = colors[gl_PrimitiveIDIn % 4];\n"
+				"	EmitVertex();\n"
+				"\n"
+				"	gl_Position = gl_in[0].gl_Position - vec4(0.05, 0.0, 0.0, 0.0);\n"
+				"	v_frag_FragColor = colors[gl_PrimitiveIDIn % 4];\n"
+				"	EmitVertex();\n"
+				"\n"
+				"	gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.05, 0.0, 0.0);\n"
+				"	v_frag_FragColor = colors[gl_PrimitiveIDIn % 4];\n"
+				"	EmitVertex();\n";
+	}
+	else if (test == TEST_PRIMITIVE_ID)
+	{
+		buf <<	"	gl_Position = gl_in[0].gl_Position + vec4(0.05, 0.0, 0.0, 0.0);\n"
+				"	gl_PrimitiveID = int(floor(v_geom_primitiveID[0].x)) + 3;\n"
+				"	EmitVertex();\n"
+				"\n"
+				"	gl_Position = gl_in[0].gl_Position - vec4(0.05, 0.0, 0.0, 0.0);\n"
+				"	gl_PrimitiveID = int(floor(v_geom_primitiveID[0].x)) + 3;\n"
+				"	EmitVertex();\n"
+				"\n"
+				"	gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.05, 0.0, 0.0);\n"
+				"	gl_PrimitiveID = int(floor(v_geom_primitiveID[0].x)) + 3;\n"
+				"	EmitVertex();\n"
+				"\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "}\n";
+
+	return buf.str();
+}
+
+std::string BuiltinVariableShader::genVertexSource (VariableTest test) const
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"in highp vec4 a_position;\n";
+
+	if (test == TEST_POINT_SIZE)
+		buf << "in highp vec4 a_pointSize;\n";
+	else if (test == TEST_PRIMITIVE_ID)
+		buf << "in highp vec4 a_primitiveID;\n";
+
+	if (test == TEST_POINT_SIZE)
+		buf << "out highp vec4 v_geom_pointSize;\n";
+	else if (test == TEST_PRIMITIVE_ID)
+		buf << "out highp vec4 v_geom_primitiveID;\n";
+
+	buf <<	"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	gl_PointSize = 1.0;\n";
+
+	if (test == TEST_POINT_SIZE)
+		buf << "	v_geom_pointSize = a_pointSize;\n";
+	else if (test == TEST_PRIMITIVE_ID)
+		buf << "	v_geom_primitiveID = a_primitiveID;\n";
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+std::string BuiltinVariableShader::genFragmentSource (VariableTest test) const
+{
+	if (test == TEST_POINT_SIZE || test == TEST_PRIMITIVE_ID_IN)
+		return s_commonShaderSourceFragment;
+	else if (test == TEST_PRIMITIVE_ID)
+	{
+		return	"#version 310 es\n"
+				"layout(location = 0) out mediump vec4 fragColor;\n"
+				"void main (void)\n"
+				"{\n"
+				"	const mediump vec4 red = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	const mediump vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"	const mediump vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);\n"
+				"	const mediump vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
+				"	const mediump vec4 colors[4] = vec4[4](yellow, red, green, blue);\n"
+				"	fragColor = colors[gl_PrimitiveID % 4];\n"
+				"}\n";
+	}
+	else
+	{
+		DE_ASSERT(DE_FALSE);
+		return DE_NULL;
+	}
+}
+
+class VaryingOutputCountShader : public sglr::ShaderProgram
+{
+public:
+	enum VaryingSource
+	{
+		READ_ATTRIBUTE = 0,
+		READ_UNIFORM,
+		READ_TEXTURE,
+
+		READ_LAST
+	};
+
+	enum
+	{
+		EMIT_COUNT_VERTEX_0 = 6,
+		EMIT_COUNT_VERTEX_1 = 0,
+		EMIT_COUNT_VERTEX_2 = -1,
+		EMIT_COUNT_VERTEX_3 = 10,
+	};
+
+								VaryingOutputCountShader	(VaryingSource source, int maxEmitCount, bool instanced);
+
+	void						shadeVertices				(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void						shadeFragments				(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+	void						shadePrimitives				(rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const;
+
+	static const char*			getAttributeName			(VaryingSource test);
+
+private:
+	static std::string			genGeometrySource			(VaryingSource test, int maxEmitCount, bool instanced);
+	static std::string			genVertexSource				(VaryingSource test);
+
+	const VaryingSource			m_test;
+	const sglr::UniformSlot&	m_sampler;
+	const sglr::UniformSlot&	m_emitCount;
+	const int					m_maxEmitCount;
+	const bool					m_instanced;
+};
+
+VaryingOutputCountShader::VaryingOutputCountShader (VaryingSource source, int maxEmitCount, bool instanced)
+	: sglr::ShaderProgram	(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::Uniform("u_sampler", glu::TYPE_SAMPLER_2D)
+							<< sglr::pdec::Uniform("u_emitCount", glu::TYPE_INT_VEC4)
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute(getAttributeName(source), rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToGeometryVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::GeometryToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(genVertexSource(source))
+							<< sglr::pdec::FragmentSource(s_commonShaderSourceFragment)
+							<< sglr::pdec::GeometryShaderDeclaration(rr::GEOMETRYSHADERINPUTTYPE_POINTS,
+																	 rr::GEOMETRYSHADEROUTPUTTYPE_TRIANGLE_STRIP,
+																	 maxEmitCount,
+																	 (instanced) ? (4) : (1))
+							<< sglr::pdec::GeometrySource(genGeometrySource(source, maxEmitCount, instanced).c_str()))
+	, m_test				(source)
+	, m_sampler				(getUniformByName("u_sampler"))
+	, m_emitCount			(getUniformByName("u_emitCount"))
+	, m_maxEmitCount		(maxEmitCount)
+	, m_instanced			(instanced)
+{
+}
+
+void VaryingOutputCountShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		packets[ndx]->position = rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+		packets[ndx]->outputs[0] = rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+	}
+}
+
+void VaryingOutputCountShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packets[packetNdx], context, 0, fragNdx));
+}
+
+void VaryingOutputCountShader::shadePrimitives (rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const
+{
+	DE_UNREF(verticesIn);
+
+	const tcu::Vec4 red			= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4 green		= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 blue		= tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f);
+	const tcu::Vec4 yellow		= tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 colors[4]	= { red, green, blue, yellow };
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const rr::VertexPacket*	vertex		= packets[packetNdx].vertices[0];
+		int						emitCount	= 0;
+		tcu::Vec4				color		= tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f);
+
+		if (m_test == READ_ATTRIBUTE)
+		{
+			emitCount = (int)vertex->outputs[0].get<float>()[(m_instanced) ? (invocationID) : (0)];
+			color = tcu::Vec4((emitCount < 10) ? (0.0f) : (1.0f), (emitCount > 10) ? (0.0f) : (1.0f), 1.0f, 1.0f);
+		}
+		else if (m_test == READ_UNIFORM)
+		{
+			const int primitiveNdx = (m_instanced) ? (invocationID) : ((int)vertex->outputs[0].get<float>().x());
+
+			DE_ASSERT(primitiveNdx >= 0);
+			DE_ASSERT(primitiveNdx < 4);
+
+			emitCount = m_emitCount.value.i4[primitiveNdx];
+			color = colors[primitiveNdx];
+		}
+		else if (m_test == READ_TEXTURE)
+		{
+			const int			primitiveNdx	= (m_instanced) ? (invocationID) : ((int)vertex->outputs[0].get<float>().x());
+			const tcu::Vec2		texCoord		= tcu::Vec2(1.0f / 8.0f + primitiveNdx / 4.0f, 0.5f);
+			const tcu::Vec4		texColor		= m_sampler.sampler.tex2D->sample(texCoord.x(), texCoord.y(), 0.0f);
+
+			DE_ASSERT(primitiveNdx >= 0);
+			DE_ASSERT(primitiveNdx < 4);
+
+			color = colors[primitiveNdx];
+			emitCount = 0;
+
+			if (texColor.x() > 0.0f)
+				emitCount += (EMIT_COUNT_VERTEX_0 == -1) ? (m_maxEmitCount) : (EMIT_COUNT_VERTEX_0);
+			if (texColor.y() > 0.0f)
+				emitCount += (EMIT_COUNT_VERTEX_1 == -1) ? (m_maxEmitCount) : (EMIT_COUNT_VERTEX_1);
+			if (texColor.z() > 0.0f)
+				emitCount += (EMIT_COUNT_VERTEX_2 == -1) ? (m_maxEmitCount) : (EMIT_COUNT_VERTEX_2);
+			if (texColor.w() > 0.0f)
+				emitCount += (EMIT_COUNT_VERTEX_3 == -1) ? (m_maxEmitCount) : (EMIT_COUNT_VERTEX_3);
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		for (int ndx = 0; ndx < (int)emitCount / 2; ++ndx)
+		{
+			const float		angle			= (float(ndx) + 0.5f) / float(emitCount / 2) * 3.142f;
+			const tcu::Vec4 basePosition	= (m_instanced) ?
+												(vertex->position + tcu::Vec4(deFloatCos(float(invocationID)), deFloatSin(float(invocationID)), 0.0f, 0.0f) * 0.5f) :
+												(vertex->position);
+			const tcu::Vec4	position0		= basePosition + tcu::Vec4(deFloatCos(angle),  deFloatSin(angle), 0.0f, 0.0f) * 0.15f;
+			const tcu::Vec4	position1		= basePosition + tcu::Vec4(deFloatCos(angle), -deFloatSin(angle), 0.0f, 0.0f) * 0.15f;
+			rr::GenericVec4	fragColor;
+
+			fragColor = color;
+
+			output.EmitVertex(position0, 0.0f, &fragColor, packets[packetNdx].primitiveIDIn);
+			output.EmitVertex(position1, 0.0f, &fragColor, packets[packetNdx].primitiveIDIn);
+		}
+
+		output.EndPrimitive();
+	}
+}
+
+const char* VaryingOutputCountShader::getAttributeName (VaryingSource test)
+{
+	switch (test)
+	{
+		case READ_ATTRIBUTE:	return "a_emitCount";
+		case READ_UNIFORM:		return "a_vertexNdx";
+		case READ_TEXTURE:		return "a_vertexNdx";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return "";
+	}
+}
+
+std::string VaryingOutputCountShader::genGeometrySource (VaryingSource test, int maxEmitCount, bool instanced)
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n"
+			"layout(points" << ((instanced) ? (",invocations=4") : ("")) << ") in;\n"
+			"layout(triangle_strip, max_vertices = " << maxEmitCount << ") out;\n";
+
+	if (test == READ_ATTRIBUTE)
+		buf <<	"in highp vec4 v_geom_emitCount[];\n";
+	else if (test == READ_UNIFORM)
+		buf <<	"in highp vec4 v_geom_vertexNdx[];\n"
+				"uniform highp ivec4 u_emitCount;\n";
+	else
+		buf <<	"in highp vec4 v_geom_vertexNdx[];\n"
+				"uniform highp sampler2D u_sampler;\n";
+
+	buf <<	"out highp vec4 v_frag_FragColor;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n";
+
+	// emit count
+
+	if (test == READ_ATTRIBUTE)
+	{
+		buf <<	"	highp vec4 attrEmitCounts = v_geom_emitCount[0];\n"
+				"	mediump int emitCount = int(attrEmitCounts[" << ((instanced) ? ("gl_InvocationID") : ("0")) << "]);\n";
+	}
+	else if (test == READ_UNIFORM)
+	{
+		buf <<	"	mediump int primitiveNdx = " << ((instanced) ? ("gl_InvocationID") : ("int(v_geom_vertexNdx[0].x)")) << ";\n"
+				"	mediump int emitCount = u_emitCount[primitiveNdx];\n";
+	}
+	else if (test == READ_TEXTURE)
+	{
+		buf <<	"	highp float primitiveNdx = " << ((instanced) ? ("float(gl_InvocationID)") : ("v_geom_vertexNdx[0].x")) << ";\n"
+				"	highp vec2 texCoord = vec2(1.0 / 8.0 + primitiveNdx / 4.0, 0.5);\n"
+				"	highp vec4 texColor = texture(u_sampler, texCoord);\n"
+				"	mediump int emitCount = 0;\n"
+				"	if (texColor.x > 0.0)\n"
+				"		emitCount += " << ((EMIT_COUNT_VERTEX_0 == -1) ? (maxEmitCount) : (EMIT_COUNT_VERTEX_0)) << ";\n"
+				"	if (texColor.y > 0.0)\n"
+				"		emitCount += " << ((EMIT_COUNT_VERTEX_1 == -1) ? (maxEmitCount) : (EMIT_COUNT_VERTEX_1)) << ";\n"
+				"	if (texColor.z > 0.0)\n"
+				"		emitCount += " << ((EMIT_COUNT_VERTEX_2 == -1) ? (maxEmitCount) : (EMIT_COUNT_VERTEX_2)) << ";\n"
+				"	if (texColor.w > 0.0)\n"
+				"		emitCount += " << ((EMIT_COUNT_VERTEX_3 == -1) ? (maxEmitCount) : (EMIT_COUNT_VERTEX_3)) << ";\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	// color
+
+	if (test == READ_ATTRIBUTE)
+	{
+		// We don't want color to be compile time constant
+		buf <<	"	highp vec4 color = vec4((emitCount < 10) ? (0.0) : (1.0), (emitCount > 10) ? (0.0) : (1.0), 1.0, 1.0);\n";
+	}
+	else if (test == READ_UNIFORM || test == READ_TEXTURE)
+	{
+		buf <<	"\n"
+				"	const highp vec4 red = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	const highp vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"	const highp vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);\n"
+				"	const highp vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
+				"	const highp vec4 colors[4] = vec4[4](red, green, blue, yellow);\n"
+				"	highp vec4 color = colors[int(primitiveNdx)];\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf <<	"\n"
+			"	highp vec4 basePos = " << ((instanced) ? ("gl_in[0].gl_Position + 0.5 * vec4(cos(float(gl_InvocationID)), sin(float(gl_InvocationID)), 0.0, 0.0)") : ("gl_in[0].gl_Position")) << ";\n"
+			"	for (mediump int i = 0; i < emitCount / 2; i++)\n"
+			"	{\n"
+			"		highp float angle = (float(i) + 0.5) / float(emitCount / 2) * 3.142;\n"
+			"		gl_Position = basePos + vec4(cos(angle),  sin(angle), 0.0, 0.0) * 0.15;\n"
+			"		v_frag_FragColor = color;\n"
+			"		EmitVertex();\n"
+			"		gl_Position = basePos + vec4(cos(angle), -sin(angle), 0.0, 0.0) * 0.15;\n"
+			"		v_frag_FragColor = color;\n"
+			"		EmitVertex();\n"
+			"	}"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string VaryingOutputCountShader::genVertexSource (VaryingSource test)
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"in highp vec4 a_position;\n";
+
+	if (test == READ_ATTRIBUTE)
+	{
+		buf << "in highp vec4 a_emitCount;\n";
+		buf << "out highp vec4 v_geom_emitCount;\n";
+	}
+	else if (test == READ_UNIFORM || test == READ_TEXTURE)
+	{
+		buf << "in highp vec4 a_vertexNdx;\n";
+		buf << "out highp vec4 v_geom_vertexNdx;\n";
+	}
+
+	buf <<	"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n";
+
+	if (test == READ_ATTRIBUTE)
+		buf << "	v_geom_emitCount = a_emitCount;\n";
+	else if (test == READ_UNIFORM || test == READ_TEXTURE)
+		buf << "	v_geom_vertexNdx = a_vertexNdx;\n";
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+class InvocationCountShader : public sglr::ShaderProgram
+{
+public:
+	enum OutputCase
+	{
+		CASE_FIXED_OUTPUT_COUNTS = 0,
+		CASE_DIFFERENT_OUTPUT_COUNTS,
+
+		CASE_LAST
+	};
+
+						InvocationCountShader		 (int numInvocations, OutputCase testCase);
+
+private:
+	void				shadeVertices				(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void				shadeFragments				(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+	void				shadePrimitives				(rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const;
+
+	static std::string	genGeometrySource			(int numInvocations, OutputCase testCase);
+	static size_t		getNumVertices				(int numInvocations, OutputCase testCase);
+
+	const int			m_numInvocations;
+	const OutputCase	m_testCase;
+};
+
+InvocationCountShader::InvocationCountShader (int numInvocations, OutputCase testCase)
+	: sglr::ShaderProgram	(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexToGeometryVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::GeometryToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(s_commonShaderSourceVertex)
+							<< sglr::pdec::FragmentSource(s_commonShaderSourceFragment)
+							<< sglr::pdec::GeometryShaderDeclaration(rr::GEOMETRYSHADERINPUTTYPE_POINTS,
+																	 rr::GEOMETRYSHADEROUTPUTTYPE_TRIANGLE_STRIP,
+																	 getNumVertices(numInvocations, testCase),
+																	 numInvocations)
+							<< sglr::pdec::GeometrySource(genGeometrySource(numInvocations, testCase).c_str()))
+	, m_numInvocations		(numInvocations)
+	, m_testCase			(testCase)
+{
+}
+
+void InvocationCountShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		packets[ndx]->position = rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+		packets[ndx]->outputs[0] = rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+	}
+}
+
+void InvocationCountShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packets[packetNdx], context, 0, fragNdx));
+}
+
+void InvocationCountShader::shadePrimitives (rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const
+{
+	DE_UNREF(verticesIn);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const float				l_angle		= float(invocationID) / float(m_numInvocations) * 5.5f;
+		const float				l_radius	= 0.6f;
+
+		const rr::VertexPacket*	vertex		= packets[packetNdx].vertices[0];
+
+		if (m_testCase == CASE_FIXED_OUTPUT_COUNTS)
+		{
+			const tcu::Vec4			position0	= vertex->position + tcu::Vec4(deFloatCos(l_angle)      * (l_radius - 0.1f), deFloatSin(l_angle)      * (l_radius - 0.1f), 0.0f, 0.0f);
+			const tcu::Vec4			position1	= vertex->position + tcu::Vec4(deFloatCos(l_angle+0.1f) * l_radius,          deFloatSin(l_angle+0.1f) * l_radius,          0.0f, 0.0f);
+			const tcu::Vec4			position2	= vertex->position + tcu::Vec4(deFloatCos(l_angle-0.1f) * l_radius,          deFloatSin(l_angle-0.1f) * l_radius,          0.0f, 0.0f);
+
+			rr::GenericVec4			tipColor;
+			rr::GenericVec4			baseColor;
+
+			tipColor  = tcu::Vec4(1.0, 1.0, 0.0, 1.0) * packets[packetNdx].vertices[0]->outputs[0].get<float>();
+			baseColor = tcu::Vec4(1.0, 0.0, 0.0, 1.0) * packets[packetNdx].vertices[0]->outputs[0].get<float>();
+
+			output.EmitVertex(position0, 0.0f, &tipColor, packets[packetNdx].primitiveIDIn);
+			output.EmitVertex(position1, 0.0f, &baseColor, packets[packetNdx].primitiveIDIn);
+			output.EmitVertex(position2, 0.0f, &baseColor, packets[packetNdx].primitiveIDIn);
+			output.EndPrimitive();
+		}
+		else if (m_testCase == CASE_DIFFERENT_OUTPUT_COUNTS)
+		{
+			const tcu::Vec4 color			= tcu::Vec4(float(invocationID % 2), (((invocationID / 2) % 2) == 0) ? (1.0f) : (0.0f), 1.0f, 1.0f);
+			const tcu::Vec4 basePosition	= vertex->position + tcu::Vec4(deFloatCos(l_angle) * l_radius, deFloatSin(l_angle) * l_radius, 0.0f, 0.0f);
+			const int		numNgonVtx		= invocationID + 3;
+
+			rr::GenericVec4	outColor;
+			outColor = color;
+
+			for (int ndx = 0; ndx + 1 < numNgonVtx; ndx += 2)
+			{
+				const float subAngle = (float(ndx) + 1.0f) / float(numNgonVtx) * 3.141f;
+
+				output.EmitVertex(basePosition + tcu::Vec4(deFloatCos(subAngle) * 0.1f, deFloatSin(subAngle) *  0.1f, 0.0f, 0.0f), 0.0f, &outColor, packets[packetNdx].primitiveIDIn);
+				output.EmitVertex(basePosition + tcu::Vec4(deFloatCos(subAngle) * 0.1f, deFloatSin(subAngle) * -0.1f, 0.0f, 0.0f), 0.0f, &outColor, packets[packetNdx].primitiveIDIn);
+			}
+
+			if ((numNgonVtx % 2) == 1)
+				output.EmitVertex(basePosition + tcu::Vec4(-0.1f, 0.0f, 0.0f, 0.0f), 0.0f, &outColor, packets[packetNdx].primitiveIDIn);
+
+			output.EndPrimitive();
+		}
+	}
+}
+
+std::string InvocationCountShader::genGeometrySource (int numInvocations, OutputCase testCase)
+{
+	const int			maxVertices = (int)getNumVertices(numInvocations, testCase);
+	std::ostringstream	buf;
+
+	buf	<<	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n"
+			"layout(points, invocations = " << numInvocations << ") in;\n"
+			"layout(triangle_strip, max_vertices = " << maxVertices << ") out;\n"
+			"\n"
+			"in highp vec4 v_geom_FragColor[];\n"
+			"out highp vec4 v_frag_FragColor;\n"
+			"\n"
+			"void main ()\n"
+			"{\n"
+			"	highp float l_angle = float(gl_InvocationID) / float(" << numInvocations << ") * 5.5;\n"
+			"	highp float l_radius = 0.6;\n"
+			"\n";
+
+	if (testCase == CASE_FIXED_OUTPUT_COUNTS)
+	{
+		buf <<	"	v_frag_FragColor = vec4(1.0, 1.0, 0.0, 1.0) * v_geom_FragColor[0];\n"
+				"	gl_Position = gl_in[0].gl_Position + vec4(cos(l_angle) * (l_radius - 0.1), sin(l_angle) * (l_radius - 0.1), 0.0, 0.0);\n"
+				"	EmitVertex();\n"
+				"\n"
+				"	v_frag_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * v_geom_FragColor[0];\n"
+				"	gl_Position = gl_in[0].gl_Position + vec4(cos(l_angle+0.1) * l_radius, sin(l_angle+0.1) * l_radius, 0.0, 0.0);\n"
+				"	EmitVertex();\n"
+				"\n"
+				"	v_frag_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * v_geom_FragColor[0];\n"
+				"	gl_Position = gl_in[0].gl_Position + vec4(cos(l_angle-0.1) * l_radius, sin(l_angle-0.1) * l_radius, 0.0, 0.0);\n"
+				"	EmitVertex();\n";
+	}
+	else if (testCase == CASE_DIFFERENT_OUTPUT_COUNTS)
+	{
+		buf <<	"	highp vec4 l_color = vec4(float(gl_InvocationID % 2), (((gl_InvocationID / 2) % 2) == 0) ? (1.0) : (0.0), 1.0, 1.0);\n"
+				"	highp vec4 basePosition = gl_in[0].gl_Position + vec4(cos(l_angle) * l_radius, sin(l_angle) * l_radius, 0.0, 0.0);\n"
+				"	mediump int numNgonVtx = gl_InvocationID + 3;\n"
+				"\n"
+				"	for (int ndx = 0; ndx + 1 < numNgonVtx; ndx += 2)\n"
+				"	{\n"
+				"		highp float sub_angle = (float(ndx) + 1.0) / float(numNgonVtx) * 3.141;\n"
+				"\n"
+				"		v_frag_FragColor = l_color;\n"
+				"		gl_Position = basePosition + vec4(cos(sub_angle) * 0.1, sin(sub_angle) * 0.1, 0.0, 0.0);\n"
+				"		EmitVertex();\n"
+				"\n"
+				"		v_frag_FragColor = l_color;\n"
+				"		gl_Position = basePosition + vec4(cos(sub_angle) * 0.1, sin(sub_angle) * -0.1, 0.0, 0.0);\n"
+				"		EmitVertex();\n"
+				"	}\n"
+				"	if ((numNgonVtx % 2) == 1)\n"
+				"	{\n"
+				"		v_frag_FragColor = l_color;\n"
+				"		gl_Position = basePosition + vec4(-0.1, 0.0, 0.0, 0.0);\n"
+				"		EmitVertex();\n"
+				"	}\n";
+	}
+	else
+		DE_ASSERT(false);
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+size_t InvocationCountShader::getNumVertices (int numInvocations, OutputCase testCase)
+{
+	switch (testCase)
+	{
+		case CASE_FIXED_OUTPUT_COUNTS:			return 3;
+		case CASE_DIFFERENT_OUTPUT_COUNTS:		return (size_t)(2 + numInvocations);
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+class InstancedExpansionShader : public sglr::ShaderProgram
+{
+public:
+						InstancedExpansionShader	(int numInvocations);
+
+private:
+	void				shadeVertices				(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void				shadeFragments				(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+	void				shadePrimitives				(rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const;
+
+	static std::string	genVertexSource				(void);
+	static std::string	genFragmentSource			(void);
+	static std::string	genGeometrySource			(int numInvocations);
+
+	const int			m_numInvocations;
+};
+
+InstancedExpansionShader::InstancedExpansionShader (int numInvocations)
+	: sglr::ShaderProgram	(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexAttribute("a_offset", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::VertexSource(genVertexSource())
+							<< sglr::pdec::FragmentSource(genFragmentSource())
+							<< sglr::pdec::GeometryShaderDeclaration(rr::GEOMETRYSHADERINPUTTYPE_POINTS,
+																	 rr::GEOMETRYSHADEROUTPUTTYPE_TRIANGLE_STRIP,
+																	 4,
+																	 numInvocations)
+							<< sglr::pdec::GeometrySource(genGeometrySource(numInvocations).c_str()))
+	, m_numInvocations		(numInvocations)
+{
+}
+
+void InstancedExpansionShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int ndx = 0; ndx < numPackets; ++ndx)
+	{
+		packets[ndx]->position =	rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx) +
+									rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
+	}
+}
+
+void InstancedExpansionShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	DE_UNREF(packets);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+}
+
+void InstancedExpansionShader::shadePrimitives (rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const
+{
+	DE_UNREF(verticesIn);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const rr::VertexPacket*	vertex			= packets[packetNdx].vertices[0];
+		const tcu::Vec4			basePosition	= vertex->position;
+		const float				phase			= float(invocationID) / float(m_numInvocations) * 6.3f;
+		const tcu::Vec4			centerPosition	= basePosition + tcu::Vec4(deFloatCos(phase), deFloatSin(phase), 0.0f, 0.0f) * 0.1f;
+
+		output.EmitVertex(centerPosition + tcu::Vec4( 0.0f,  -0.1f, 0.0f, 0.0f), 0.0f, DE_NULL, packets[packetNdx].primitiveIDIn);
+		output.EmitVertex(centerPosition + tcu::Vec4(-0.05f,  0.0f, 0.0f, 0.0f), 0.0f, DE_NULL, packets[packetNdx].primitiveIDIn);
+		output.EmitVertex(centerPosition + tcu::Vec4( 0.05f,  0.0f, 0.0f, 0.0f), 0.0f, DE_NULL, packets[packetNdx].primitiveIDIn);
+		output.EndPrimitive();
+	}
+}
+
+std::string InstancedExpansionShader::genVertexSource (void)
+{
+	return	"#version 310 es\n"
+			"in highp vec4 a_position;\n"
+			"in highp vec4 a_offset;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position + a_offset;\n"
+			"}\n";
+}
+
+std::string InstancedExpansionShader::genFragmentSource (void)
+{
+	return	"#version 310 es\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+			"}\n";
+}
+
+std::string InstancedExpansionShader::genGeometrySource (int numInvocations)
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n"
+			"layout(points,invocations=" << numInvocations << ") in;\n"
+			"layout(triangle_strip, max_vertices = 3) out;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	highp vec4 basePosition = gl_in[0].gl_Position;\n"
+			"	highp float phase = float(gl_InvocationID) / float(" << numInvocations << ") * 6.3;\n"
+			"	highp vec4 centerPosition = basePosition + 0.1 * vec4(cos(phase), sin(phase), 0.0, 0.0);\n"
+			"\n"
+			"	gl_Position = centerPosition + vec4( 0.00, -0.1, 0.0, 0.0);\n"
+			"	EmitVertex();\n"
+			"	gl_Position = centerPosition + vec4(-0.05,  0.0, 0.0, 0.0);\n"
+			"	EmitVertex();\n"
+			"	gl_Position = centerPosition + vec4( 0.05,  0.0, 0.0, 0.0);\n"
+			"	EmitVertex();\n"
+			"}\n";
+
+	return buf.str();
+}
+
+class GeometryShaderRenderTest : public TestCase
+{
+public:
+	enum Flag
+	{
+		FLAG_DRAW_INSTANCED		= 1,
+		FLAG_USE_INDICES		= 2,
+		FLAG_USE_RESTART_INDEX	= 4,
+	};
+
+									GeometryShaderRenderTest 	(Context& context, const char* name, const char* desc, GLenum inputPrimitives, GLenum outputPrimitives, const char* dataAttributeName, int flags = 0);
+	virtual							~GeometryShaderRenderTest	(void);
+
+	void							init						(void);
+	void							deinit						(void);
+
+	IterateResult					iterate 					(void);
+	bool							compare						(void);
+
+	virtual sglr::ShaderProgram&	getProgram					(void) = 0;
+
+protected:
+	virtual void					genVertexAttribData			(void);
+	void							renderWithContext			(sglr::Context& ctx, sglr::ShaderProgram& program, tcu::Surface& dstSurface);
+	virtual void					preRender					(sglr::Context& ctx, GLuint programID);
+	virtual void					postRender					(sglr::Context& ctx, GLuint programID);
+
+	int								m_numDrawVertices;
+	int								m_numDrawInstances;
+	int								m_vertexAttrDivisor;
+
+	const GLenum					m_inputPrimitives;
+	const GLenum					m_outputPrimitives;
+	const char* const				m_dataAttributeName;
+	const int						m_flags;
+
+	tcu::IVec2						m_viewportSize;
+	int								m_interationCount;
+
+	tcu::Surface*					m_glResult;
+	tcu::Surface*					m_refResult;
+
+	sglr::ReferenceContextBuffers*	m_refBuffers;
+	sglr::ReferenceContext*			m_refContext;
+	sglr::Context*					m_glContext;
+
+	std::vector<tcu::Vec4>			m_vertexPosData;
+	std::vector<tcu::Vec4>			m_vertexAttrData;
+	std::vector<deUint16>			m_indices;
+};
+
+GeometryShaderRenderTest::GeometryShaderRenderTest (Context& context, const char* name, const char* desc, GLenum inputPrimitives, GLenum outputPrimitives, const char* dataAttributeName, int flags)
+	: TestCase				(context, name, desc)
+	, m_numDrawVertices		(0)
+	, m_numDrawInstances	(0)
+	, m_vertexAttrDivisor	(0)
+	, m_inputPrimitives		(inputPrimitives)
+	, m_outputPrimitives	(outputPrimitives)
+	, m_dataAttributeName	(dataAttributeName)
+	, m_flags				(flags)
+	, m_viewportSize		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE)
+	, m_interationCount		(0)
+	, m_glResult			(DE_NULL)
+	, m_refResult			(DE_NULL)
+	, m_refBuffers			(DE_NULL)
+	, m_refContext			(DE_NULL)
+	, m_glContext			(DE_NULL)
+{
+	// Disallow instanced drawElements
+	DE_ASSERT(((m_flags & FLAG_DRAW_INSTANCED) == 0) || ((m_flags & FLAG_USE_INDICES) == 0));
+	// Disallow restart without indices
+	DE_ASSERT(!(((m_flags & FLAG_USE_RESTART_INDEX) != 0) && ((m_flags & FLAG_USE_INDICES) == 0)));
+}
+
+GeometryShaderRenderTest::~GeometryShaderRenderTest (void)
+{
+	deinit();
+}
+
+void GeometryShaderRenderTest::init (void)
+{
+	// requirements
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+
+	// gen resources
+	{
+		sglr::ReferenceContextLimits limits;
+
+		m_glResult		= new tcu::Surface(m_viewportSize.x(), m_viewportSize.y());
+		m_refResult		= new tcu::Surface(m_viewportSize.x(), m_viewportSize.y());
+
+		m_refBuffers	= new sglr::ReferenceContextBuffers(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, m_viewportSize.x(), m_viewportSize.y());
+		m_refContext	= new sglr::ReferenceContext(limits, m_refBuffers->getColorbuffer(), m_refBuffers->getDepthbuffer(), m_refBuffers->getStencilbuffer());
+		m_glContext		= new sglr::GLContext(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS | sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(0, 0, m_viewportSize.x(), m_viewportSize.y()));
+	}
+}
+
+void GeometryShaderRenderTest::deinit (void)
+{
+	delete m_glResult;
+	delete m_refResult;
+
+	m_glResult = DE_NULL;
+	m_refResult = DE_NULL;
+
+	delete m_refContext;
+	delete m_glContext;
+	delete m_refBuffers;
+
+	m_refBuffers = DE_NULL;
+	m_refContext = DE_NULL;
+	m_glContext = DE_NULL;
+}
+
+tcu::TestCase::IterateResult GeometryShaderRenderTest::iterate (void)
+{
+	// init() must be called
+	DE_ASSERT(m_glContext);
+	DE_ASSERT(m_refContext);
+
+	const int iteration = m_interationCount++;
+
+	if (iteration == 0)
+	{
+		// Check requirements
+		const int width	 = m_context.getRenderTarget().getWidth();
+		const int height = m_context.getRenderTarget().getHeight();
+
+		if (width < m_viewportSize.x() || height < m_viewportSize.y())
+			throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(m_viewportSize.x()) + "x" + de::toString(m_viewportSize.y()));
+
+		// Gen data
+		genVertexAttribData();
+
+		return CONTINUE;
+	}
+	else if (iteration == 1)
+	{
+		// Render
+		sglr::ShaderProgram& program = getProgram();
+
+		renderWithContext(*m_glContext, program, *m_glResult);
+		renderWithContext(*m_refContext, program, *m_refResult);
+
+		return CONTINUE;
+	}
+	else
+	{
+		if (compare())
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+		return STOP;
+	}
+}
+
+bool GeometryShaderRenderTest::compare (void)
+{
+	using tcu::TestLog;
+
+	if (m_context.getRenderTarget().getNumSamples() > 1)
+	{
+		return tcu::fuzzyCompare(m_testCtx.getLog(), "Compare Results", "Compare Results", m_refResult->getAccess(), m_glResult->getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);
+	}
+	else
+	{
+		tcu::Surface	errorMask				(m_viewportSize.x(), m_viewportSize.y());
+		const tcu::RGBA	green					(0, 255, 0, 255);
+		const tcu::RGBA	red						(255, 0, 0, 255);
+		const int		colorComponentThreshold	= 20;
+		bool			testResult				= true;
+
+		for (int x = 1; x + 1 < m_viewportSize.x(); ++x)
+		for (int y = 1; y + 1 < m_viewportSize.y(); ++y)
+		{
+			bool found = false;
+			const tcu::RGBA refcolor = m_refResult->getPixel(x, y);
+
+			// Got to find similar pixel near this pixel (3x3 kernel)
+			for (int dx = -1; dx <= 1; ++dx)
+			for (int dy = -1; dy <= 1; ++dy)
+			{
+				const tcu::RGBA		testColor	= m_glResult->getPixel(x + dx, y + dy);
+				const tcu::IVec4	colDiff		= tcu::abs(testColor.toIVec() - refcolor.toIVec());
+
+				const int			maxColDiff	= de::max(de::max(colDiff.x(), colDiff.y()), colDiff.z()); // check RGB channels
+
+				if (maxColDiff <= colorComponentThreshold)
+					found = true;
+			}
+
+			if (!found)
+				testResult = false;
+
+			errorMask.setPixel(x, y, (found) ? (green) : (red));
+		}
+
+		if (testResult)
+		{
+			m_testCtx.getLog()	<< TestLog::ImageSet("Compare result", "Result of rendering")
+								<< TestLog::Image("Result", "Result", *m_glResult)
+								<< TestLog::EndImageSet;
+			m_testCtx.getLog() << TestLog::Message << "Image compare ok." << TestLog::EndMessage;
+		}
+		else
+		{
+			m_testCtx.getLog()	<< TestLog::ImageSet("Compare result", "Result of rendering")
+								<< TestLog::Image("Result",		"Result",		*m_glResult)
+								<< TestLog::Image("Reference",	"Reference",	*m_refResult)
+								<< TestLog::Image("ErrorMask",	"Error mask",	errorMask)
+								<< TestLog::EndImageSet;
+			m_testCtx.getLog() << TestLog::Message << "Image compare failed." << TestLog::EndMessage;
+		}
+
+		return testResult;
+	}
+}
+
+void GeometryShaderRenderTest::genVertexAttribData (void)
+{
+	// Create 1 X 2 grid in triangle strip adjacent - order
+	const float scale = 0.3f;
+	const tcu::Vec4 offset(-0.5f, -0.2f, 0.0f, 1.0f);
+
+	m_vertexPosData.resize(12);
+	m_vertexPosData[ 0] = tcu::Vec4( 0,  0, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[ 1] = tcu::Vec4(-1, -1, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[ 2] = tcu::Vec4( 0, -1, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[ 3] = tcu::Vec4( 1,  1, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[ 4] = tcu::Vec4( 1,  0, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[ 5] = tcu::Vec4( 0, -2, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[ 6] = tcu::Vec4( 1, -1, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[ 7] = tcu::Vec4( 2,  1, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[ 8] = tcu::Vec4( 2,  0, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[ 9] = tcu::Vec4( 1, -2, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[10] = tcu::Vec4( 2, -1, 0.0f, 0.0f) * scale + offset;
+	m_vertexPosData[11] = tcu::Vec4( 3,  0, 0.0f, 0.0f) * scale + offset;
+
+	// Red and white
+	m_vertexAttrData.resize(12);
+	for (int i = 0; i < 12; ++i)
+		m_vertexAttrData[i] = (i % 2 == 0) ? tcu::Vec4(1, 1, 1, 1) : tcu::Vec4(1, 0, 0, 1);
+
+	m_numDrawVertices = 12;
+}
+
+void GeometryShaderRenderTest::renderWithContext (sglr::Context& ctx, sglr::ShaderProgram& program, tcu::Surface& dstSurface)
+{
+#define CHECK_GL_CTX_ERRORS() glu::checkError(ctx.getError(), DE_NULL, __FILE__, __LINE__)
+
+	const GLuint	programId		= ctx.createProgram(&program);
+	const GLint		attrPosLoc		= ctx.getAttribLocation(programId, "a_position");
+	const GLint		attrColLoc		= ctx.getAttribLocation(programId, m_dataAttributeName);
+	GLuint			vaoId			= 0;
+	GLuint			vertexPosBuf	= 0;
+	GLuint			vertexAttrBuf	= 0;
+	GLuint			elementArrayBuf	= 0;
+
+	ctx.genVertexArrays(1, &vaoId);
+	ctx.bindVertexArray(vaoId);
+
+	if (attrPosLoc != -1)
+	{
+		ctx.genBuffers(1, &vertexPosBuf);
+		ctx.bindBuffer(GL_ARRAY_BUFFER, vertexPosBuf);
+		ctx.bufferData(GL_ARRAY_BUFFER, m_vertexPosData.size() * sizeof(tcu::Vec4), &m_vertexPosData[0], GL_STATIC_DRAW);
+		ctx.vertexAttribPointer(attrPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		ctx.enableVertexAttribArray(attrPosLoc);
+	}
+
+	if (attrColLoc != -1)
+	{
+		ctx.genBuffers(1, &vertexAttrBuf);
+		ctx.bindBuffer(GL_ARRAY_BUFFER, vertexAttrBuf);
+		ctx.bufferData(GL_ARRAY_BUFFER, m_vertexAttrData.size() * sizeof(tcu::Vec4), &m_vertexAttrData[0], GL_STATIC_DRAW);
+		ctx.vertexAttribPointer(attrColLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		ctx.enableVertexAttribArray(attrColLoc);
+
+		if (m_vertexAttrDivisor)
+			ctx.vertexAttribDivisor(attrColLoc, m_vertexAttrDivisor);
+	}
+
+	if (m_flags & FLAG_USE_INDICES)
+	{
+		ctx.genBuffers(1, &elementArrayBuf);
+		ctx.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementArrayBuf);
+		ctx.bufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof(deUint16), &m_indices[0], GL_STATIC_DRAW);
+	}
+
+	ctx.clearColor(0, 0, 0, 1);
+	ctx.clear(GL_COLOR_BUFFER_BIT);
+
+	ctx.viewport(0, 0, m_viewportSize.x(), m_viewportSize.y());
+	CHECK_GL_CTX_ERRORS();
+
+	ctx.useProgram(programId);
+	CHECK_GL_CTX_ERRORS();
+
+	preRender(ctx, programId);
+	CHECK_GL_CTX_ERRORS();
+
+	if (m_flags & FLAG_USE_RESTART_INDEX)
+	{
+		ctx.enable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
+		CHECK_GL_CTX_ERRORS();
+	}
+
+	if (m_flags & FLAG_USE_INDICES)
+		ctx.drawElements(m_inputPrimitives, m_numDrawVertices, GL_UNSIGNED_SHORT, DE_NULL);
+	else if (m_flags & FLAG_DRAW_INSTANCED)
+		ctx.drawArraysInstanced(m_inputPrimitives, 0, m_numDrawVertices, m_numDrawInstances);
+	else
+		ctx.drawArrays(m_inputPrimitives, 0, m_numDrawVertices);
+
+	CHECK_GL_CTX_ERRORS();
+
+	if (m_flags & FLAG_USE_RESTART_INDEX)
+	{
+		ctx.disable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
+		CHECK_GL_CTX_ERRORS();
+	}
+
+	postRender(ctx, programId);
+	CHECK_GL_CTX_ERRORS();
+
+	ctx.useProgram(0);
+
+	if (attrPosLoc != -1)
+		ctx.disableVertexAttribArray(attrPosLoc);
+	if (attrColLoc != -1)
+		ctx.disableVertexAttribArray(attrColLoc);
+
+	if (vertexPosBuf)
+		ctx.deleteBuffers(1, &vertexPosBuf);
+	if (vertexAttrBuf)
+		ctx.deleteBuffers(1, &vertexAttrBuf);
+	if (elementArrayBuf)
+		ctx.deleteBuffers(1, &elementArrayBuf);
+
+	ctx.deleteVertexArrays(1, &vaoId);
+
+	CHECK_GL_CTX_ERRORS();
+
+	ctx.finish();
+	ctx.readPixels(dstSurface, 0, 0, m_viewportSize.x(), m_viewportSize.y());
+
+#undef CHECK_GL_CTX_ERRORS
+}
+
+void GeometryShaderRenderTest::preRender (sglr::Context& ctx, GLuint programID)
+{
+	DE_UNREF(ctx);
+	DE_UNREF(programID);
+}
+
+void GeometryShaderRenderTest::postRender (sglr::Context& ctx, GLuint programID)
+{
+	DE_UNREF(ctx);
+	DE_UNREF(programID);
+}
+
+class GeometryExpanderRenderTest : public GeometryShaderRenderTest
+{
+public:
+									GeometryExpanderRenderTest 	(Context& context, const char* name, const char* desc, GLenum inputPrimitives, GLenum outputPrimitives);
+	virtual							~GeometryExpanderRenderTest	(void);
+
+	sglr::ShaderProgram&			getProgram					(void);
+
+private:
+	VertexExpanderShader			m_program;
+};
+
+GeometryExpanderRenderTest::GeometryExpanderRenderTest (Context& context, const char* name, const char* desc, GLenum inputPrimitives, GLenum outputPrimitives)
+	: GeometryShaderRenderTest	(context, name, desc, inputPrimitives, outputPrimitives, "a_color")
+	, m_program					(sglr::rr_util::mapGLGeometryShaderInputType(inputPrimitives), sglr::rr_util::mapGLGeometryShaderOutputType(outputPrimitives))
+{
+}
+
+GeometryExpanderRenderTest::~GeometryExpanderRenderTest (void)
+{
+}
+
+sglr::ShaderProgram& GeometryExpanderRenderTest::getProgram (void)
+{
+	return m_program;
+}
+
+class EmitTest : public GeometryShaderRenderTest
+{
+public:
+							EmitTest				(Context& context, const char* name, const char* desc, int emitCountA, int endCountA, int emitCountB, int endCountB, GLenum outputType);
+
+	sglr::ShaderProgram&	getProgram				(void);
+private:
+	void					genVertexAttribData		(void);
+
+	VertexEmitterShader		m_program;
+};
+
+EmitTest::EmitTest (Context& context, const char* name, const char* desc, int emitCountA, int endCountA, int emitCountB, int endCountB, GLenum outputType)
+	: GeometryShaderRenderTest	(context, name, desc, GL_POINTS, outputType, "a_color")
+	, m_program					(emitCountA, endCountA, emitCountB, endCountB, sglr::rr_util::mapGLGeometryShaderOutputType(outputType))
+{
+}
+
+sglr::ShaderProgram& EmitTest::getProgram (void)
+{
+	return m_program;
+}
+
+void EmitTest::genVertexAttribData (void)
+{
+	m_vertexPosData.resize(1);
+	m_vertexPosData[0] = tcu::Vec4(0, 0, 0, 1);
+
+	m_vertexAttrData.resize(1);
+	m_vertexAttrData[0] = tcu::Vec4(1, 1, 1, 1);
+
+	m_numDrawVertices = 1;
+}
+
+class VaryingTest : public GeometryShaderRenderTest
+{
+public:
+							VaryingTest				(Context& context, const char* name, const char* desc, int vertexOut, int geometryOut);
+
+	sglr::ShaderProgram&	getProgram				(void);
+private:
+	void					genVertexAttribData		(void);
+
+	VertexVaryingShader		m_program;
+};
+
+VaryingTest::VaryingTest (Context& context, const char* name, const char* desc, int vertexOut, int geometryOut)
+	: GeometryShaderRenderTest	(context, name, desc, GL_TRIANGLES, GL_TRIANGLE_STRIP, "a_color")
+	, m_program					(vertexOut, geometryOut)
+{
+}
+
+sglr::ShaderProgram& VaryingTest::getProgram (void)
+{
+	return m_program;
+}
+
+void VaryingTest::genVertexAttribData (void)
+{
+	m_vertexPosData.resize(3);
+	m_vertexPosData[0] = tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f);
+	m_vertexPosData[1] = tcu::Vec4(0.0f, 0.5f, 0.0f, 1.0f);
+	m_vertexPosData[2] = tcu::Vec4(0.1f, 0.0f, 0.0f, 1.0f);
+
+	m_vertexAttrData.resize(3);
+	m_vertexAttrData[0] = tcu::Vec4(0.7f, 0.4f, 0.6f, 1.0f);
+	m_vertexAttrData[1] = tcu::Vec4(0.9f, 0.2f, 0.5f, 1.0f);
+	m_vertexAttrData[2] = tcu::Vec4(0.1f, 0.8f, 0.3f, 1.0f);
+
+	m_numDrawVertices = 3;
+}
+
+class TriangleStripAdjacencyVertexCountTest : public GeometryExpanderRenderTest
+{
+public:
+				TriangleStripAdjacencyVertexCountTest	(Context& context, const char* name, const char* desc, int numInputVertices);
+
+private:
+	void		genVertexAttribData						(void);
+
+	int			m_numInputVertices;
+};
+
+TriangleStripAdjacencyVertexCountTest::TriangleStripAdjacencyVertexCountTest (Context& context, const char* name, const char* desc, int numInputVertices)
+	: GeometryExpanderRenderTest	(context, name, desc, GL_TRIANGLE_STRIP_ADJACENCY, GL_TRIANGLE_STRIP)
+	, m_numInputVertices			(numInputVertices)
+{
+}
+
+void TriangleStripAdjacencyVertexCountTest::genVertexAttribData (void)
+{
+	this->GeometryShaderRenderTest::genVertexAttribData();
+	m_numDrawVertices = m_numInputVertices;
+}
+
+class NegativeDrawCase : public TestCase
+{
+public:
+							NegativeDrawCase 	(Context& context, const char* name, const char* desc, GLenum inputType, GLenum inputPrimitives);
+							~NegativeDrawCase	(void);
+
+	void					init				(void);
+	void					deinit				(void);
+
+	IterateResult			iterate 			(void);
+
+private:
+	sglr::Context*			m_ctx;
+	VertexExpanderShader*	m_program;
+	GLenum					m_inputType;
+	GLenum					m_inputPrimitives;
+};
+
+NegativeDrawCase::NegativeDrawCase (Context& context, const char* name, const char* desc, GLenum inputType, GLenum inputPrimitives)
+	: TestCase			(context, name, desc)
+	, m_ctx				(DE_NULL)
+	, m_program			(DE_NULL)
+	, m_inputType		(inputType)
+	, m_inputPrimitives	(inputPrimitives)
+{
+}
+
+NegativeDrawCase::~NegativeDrawCase (void)
+{
+	deinit();
+}
+
+void NegativeDrawCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+
+	m_ctx		= new sglr::GLContext(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS | sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(0, 0, 1, 1));
+	m_program	= new VertexExpanderShader(sglr::rr_util::mapGLGeometryShaderInputType(m_inputType), rr::GEOMETRYSHADEROUTPUTTYPE_POINTS);
+}
+
+void NegativeDrawCase::deinit (void)
+{
+	delete m_ctx;
+	delete m_program;
+
+	m_ctx = NULL;
+	m_program = DE_NULL;
+}
+
+NegativeDrawCase::IterateResult NegativeDrawCase::iterate (void)
+{
+	const GLuint	programId		= m_ctx->createProgram(m_program);
+	const GLint		attrPosLoc		= m_ctx->getAttribLocation(programId, "a_position");
+	const tcu::Vec4 vertexPosData	(0, 0, 0, 1);
+
+	GLuint vaoId		= 0;
+	GLuint vertexPosBuf = 0;
+	GLenum errorCode	= 0;
+
+	m_ctx->genVertexArrays(1, &vaoId);
+	m_ctx->bindVertexArray(vaoId);
+
+	m_ctx->genBuffers(1, &vertexPosBuf);
+	m_ctx->bindBuffer(GL_ARRAY_BUFFER, vertexPosBuf);
+	m_ctx->bufferData(GL_ARRAY_BUFFER, sizeof(tcu::Vec4), vertexPosData.m_data, GL_STATIC_DRAW);
+	m_ctx->vertexAttribPointer(attrPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	m_ctx->enableVertexAttribArray(attrPosLoc);
+
+	m_ctx->clearColor(0, 0, 0, 1);
+	m_ctx->clear(GL_COLOR_BUFFER_BIT);
+
+	m_ctx->viewport(0, 0, 1, 1);
+
+	m_ctx->useProgram(programId);
+
+	// no errors before
+	glu::checkError(m_ctx->getError(), "", __FILE__, __LINE__);
+
+	m_ctx->drawArrays(m_inputPrimitives, 0, 1);
+
+	errorCode = m_ctx->getError();
+	if (errorCode != GL_INVALID_OPERATION)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Expected GL_INVALID_OPERATION, got " << glu::getErrorStr(errorCode) << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong error code");
+	}
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+
+	m_ctx->useProgram(0);
+
+	m_ctx->disableVertexAttribArray(attrPosLoc);
+	m_ctx->deleteBuffers(1, &vertexPosBuf);
+
+	m_ctx->deleteVertexArrays(1, &vaoId);
+
+	return STOP;
+}
+
+class OutputCountCase : public GeometryShaderRenderTest
+{
+public:
+									OutputCountCase			(Context& context, const char* name, const char* desc, const OutputCountPatternSpec&);
+private:
+	void							init					(void);
+	void							deinit					(void);
+
+	sglr::ShaderProgram&			getProgram				(void);
+	void							genVertexAttribData		(void);
+
+	const int						m_primitiveCount;
+	OutputCountShader*				m_program;
+	OutputCountPatternSpec			m_spec;
+};
+
+OutputCountCase::OutputCountCase (Context& context, const char* name, const char* desc, const OutputCountPatternSpec& spec)
+	: GeometryShaderRenderTest	(context, name, desc, GL_POINTS, GL_TRIANGLE_STRIP, "a_color")
+	, m_primitiveCount			((int)spec.pattern.size())
+	, m_program					(DE_NULL)
+	, m_spec					(spec)
+{
+}
+
+void OutputCountCase::init (void)
+{
+	// Check requirements and adapt to them
+	{
+		const int	componentsPerVertex	= 4 + 4; // vec4 pos, vec4 color
+		const int	testVertices		= *std::max_element(m_spec.pattern.begin(), m_spec.pattern.end());
+		glw::GLint	maxVertices			= 0;
+		glw::GLint	maxComponents		= 0;
+
+		// check the extension before querying anything
+		if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+			throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &maxVertices);
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, &maxComponents);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_GEOMETRY_OUTPUT_VERTICES = " << maxVertices << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = " << maxComponents << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "Components per vertex = " << componentsPerVertex << tcu::TestLog::EndMessage;
+
+		if (testVertices == -1)
+		{
+			// "max vertices"-case
+			DE_ASSERT((int)m_spec.pattern.size() == 1);
+			m_spec.pattern[0] = de::min(maxVertices, maxComponents / componentsPerVertex);
+
+			// make sure size is dividable by 2, as OutputShader requires
+			m_spec.pattern[0] = m_spec.pattern[0] & ~0x00000001;
+
+			if (m_spec.pattern[0] == 0)
+				throw tcu::InternalError("Pattern size is invalid.");
+		}
+		else
+		{
+			// normal case
+			if (testVertices > maxVertices)
+				throw tcu::NotSupportedError(de::toString(testVertices) + " output vertices required.");
+			if (testVertices * componentsPerVertex > maxComponents)
+				throw tcu::NotSupportedError(de::toString(testVertices * componentsPerVertex) + " output components required.");
+		}
+	}
+
+	// Log what the test tries to do
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << (int)m_spec.pattern.size() << " row(s).\nOne geometry shader invocation generates one row.\nRow sizes:" << tcu::TestLog::EndMessage;
+	for (int ndx = 0; ndx < (int)m_spec.pattern.size(); ++ndx)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Row " << ndx << ": " << m_spec.pattern[ndx] << " vertices." << tcu::TestLog::EndMessage;
+
+	// Gen shader
+	DE_ASSERT(!m_program);
+	m_program = new OutputCountShader(m_spec);
+
+	// Case init
+	GeometryShaderRenderTest::init();
+}
+
+void OutputCountCase::deinit (void)
+{
+	if (m_program)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+	}
+
+	GeometryShaderRenderTest::deinit();
+}
+
+sglr::ShaderProgram& OutputCountCase::getProgram (void)
+{
+	return *m_program;
+}
+
+void OutputCountCase::genVertexAttribData (void)
+{
+	m_vertexPosData.resize(m_primitiveCount);
+	m_vertexAttrData.resize(m_primitiveCount);
+
+	for (int ndx = 0; ndx < m_primitiveCount; ++ndx)
+	{
+		m_vertexPosData[ndx] = tcu::Vec4(-1.0f, ((float)ndx) / m_primitiveCount * 2.0f - 1.0f, 0.0f, 1.0f);
+		m_vertexAttrData[ndx] = (ndx % 2 == 0) ? tcu::Vec4(1, 1, 1, 1) : tcu::Vec4(1, 0, 0, 1);
+	}
+
+	m_numDrawVertices = m_primitiveCount;
+}
+
+class BuiltinVariableRenderTest : public GeometryShaderRenderTest
+{
+public:
+												BuiltinVariableRenderTest	(Context& context, const char* name, const char* desc, BuiltinVariableShader::VariableTest test, int flags = 0);
+
+private:
+	void										init						(void);
+
+	sglr::ShaderProgram&						getProgram					(void);
+	void										genVertexAttribData			(void);
+
+	BuiltinVariableShader						m_program;
+	const BuiltinVariableShader::VariableTest	m_test;
+};
+
+BuiltinVariableRenderTest::BuiltinVariableRenderTest (Context& context, const char* name, const char* desc, BuiltinVariableShader::VariableTest test, int flags)
+	: GeometryShaderRenderTest	(context, name, desc, GL_POINTS, GL_POINTS, BuiltinVariableShader::getTestAttributeName(test), flags)
+	, m_program					(test)
+	, m_test					(test)
+{
+}
+
+void BuiltinVariableRenderTest::init (void)
+{
+	// Requirements
+	if (m_test == BuiltinVariableShader::TEST_POINT_SIZE)
+	{
+		const float requiredPointSize = 5.0f;
+
+		tcu::Vec2 range = tcu::Vec2(1.0f, 1.0f);
+
+		if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_point_size"))
+			throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_point_size extension");
+
+		m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_POINT_SIZE_RANGE, range.getPtr());
+		if (range.y() < requiredPointSize)
+			throw tcu::NotSupportedError("Test case requires point size " + de::toString(requiredPointSize));
+	}
+
+	// Shader init
+	GeometryShaderRenderTest::init();
+}
+
+sglr::ShaderProgram& BuiltinVariableRenderTest::getProgram (void)
+{
+	return m_program;
+}
+
+void BuiltinVariableRenderTest::genVertexAttribData (void)
+{
+	m_vertexPosData.resize(4);
+	m_vertexPosData[0] = tcu::Vec4( 0.5f,  0.0f, 0.0f, 1.0f);
+	m_vertexPosData[1] = tcu::Vec4( 0.0f,  0.5f, 0.0f, 1.0f);
+	m_vertexPosData[2] = tcu::Vec4(-0.7f, -0.1f, 0.0f, 1.0f);
+	m_vertexPosData[3] = tcu::Vec4(-0.1f, -0.7f, 0.0f, 1.0f);
+
+	m_vertexAttrData.resize(4);
+	m_vertexAttrData[0] = tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f);
+	m_vertexAttrData[1] = tcu::Vec4(1.0f, 0.0f, 0.0f, 0.0f);
+	m_vertexAttrData[2] = tcu::Vec4(2.0f, 0.0f, 0.0f, 0.0f);
+	m_vertexAttrData[3] = tcu::Vec4(3.0f, 0.0f, 0.0f, 0.0f);
+
+	// Only used by primitive ID restart test
+	m_indices.resize(4);
+	m_indices[0] = 3;
+	m_indices[1] = 2;
+	m_indices[2] = 0xFFFF; // restart
+	m_indices[3] = 1;
+
+	m_numDrawVertices = 4;
+}
+
+class LayeredRenderCase : public TestCase
+{
+public:
+	enum LayeredRenderTargetType
+	{
+		TARGET_CUBE = 0,
+		TARGET_3D,
+		TARGET_1D_ARRAY,
+		TARGET_2D_ARRAY,
+		TARGET_2D_MS_ARRAY,
+
+		TARGET_LAST
+	};
+	enum TestType
+	{
+		TEST_DEFAULT_LAYER,						// !< draw to default layer
+		TEST_SINGLE_LAYER,						// !< draw to single layer
+		TEST_ALL_LAYERS,						// !< draw all layers
+		TEST_DIFFERENT_LAYERS,					// !< draw different content to different layers
+		TEST_INVOCATION_PER_LAYER,				// !< draw to all layers, one invocation per layer
+		TEST_MULTIPLE_LAYERS_PER_INVOCATION,	// !< draw to all layers, multiple invocations write to multiple layers
+		TEST_LAYER_ID,							// !< draw to all layers, verify gl_Layer fragment input
+		TEST_LAYER_PROVOKING_VERTEX,			// !< draw primitive with vertices in different layers, check which layer it was drawn to
+
+		TEST_LAST
+	};
+										LayeredRenderCase			(Context& context, const char* name, const char* desc, LayeredRenderTargetType target, TestType test);
+										~LayeredRenderCase			(void);
+
+	void								init						(void);
+	void								deinit						(void);
+	IterateResult						iterate						(void);
+
+private:
+	void								initTexture					(void);
+	void								initFbo						(void);
+	void								initRenderShader			(void);
+	void								initSamplerShader			(void);
+
+	std::string							genFragmentSource			(void) const;
+	std::string							genGeometrySource			(void) const;
+	std::string							genSamplerFragmentSource	(void) const;
+
+	void								renderToTexture				(void);
+	void								sampleTextureLayer			(tcu::Surface& dst, int layer);
+	bool								verifyLayerContent			(const tcu::Surface& layer, int layerNdx);
+	bool								verifyImageSingleColoredRow (const tcu::Surface& layer, float rowWidthRatio, const tcu::Vec4& color, bool logging = true);
+	bool								verifyEmptyImage			(const tcu::Surface& layer, bool logging = true);
+	bool								verifyProvokingVertexLayers	(const tcu::Surface& layer0, const tcu::Surface& layer1);
+
+	static int							getTargetLayers				(LayeredRenderTargetType target);
+	static glw::GLenum					getTargetTextureTarget		(LayeredRenderTargetType target);
+	static tcu::IVec3					getTargetDimensions			(LayeredRenderTargetType target);
+	static tcu::IVec2					getResolveDimensions		(LayeredRenderTargetType target);
+
+	const LayeredRenderTargetType		m_target;
+	const TestType						m_test;
+	const int							m_numLayers;
+	const int							m_targetLayer;
+	const tcu::IVec2					m_resolveDimensions;
+
+	int									m_iteration;
+	bool								m_allLayersOk;
+
+	glw::GLuint							m_texture;
+	glw::GLuint							m_fbo;
+	glu::ShaderProgram*					m_renderShader;
+	glu::ShaderProgram*					m_samplerShader;
+
+	glw::GLint							m_samplerSamplerLoc;
+	glw::GLint							m_samplerLayerLoc;
+
+	glw::GLenum							m_provokingVertex;
+};
+
+LayeredRenderCase::LayeredRenderCase (Context& context, const char* name, const char* desc, LayeredRenderTargetType target, TestType test)
+	: TestCase				(context, name, desc)
+	, m_target				(target)
+	, m_test				(test)
+	, m_numLayers			(getTargetLayers(target))
+	, m_targetLayer			(m_numLayers / 2)
+	, m_resolveDimensions	(getResolveDimensions(target))
+	, m_iteration			(0)
+	, m_allLayersOk			(true)
+	, m_texture				(0)
+	, m_fbo					(0)
+	, m_renderShader		(DE_NULL)
+	, m_samplerShader		(DE_NULL)
+	, m_samplerSamplerLoc	(-1)
+	, m_samplerLayerLoc		(-1)
+	, m_provokingVertex		(0)
+{
+}
+
+LayeredRenderCase::~LayeredRenderCase (void)
+{
+	deinit();
+}
+
+void LayeredRenderCase::init (void)
+{
+	// Requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+
+	if (m_target == TARGET_2D_MS_ARRAY && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
+		throw tcu::NotSupportedError("Test requires OES_texture_storage_multisample_2d_array extension");
+
+	if (m_context.getRenderTarget().getWidth() < m_resolveDimensions.x() || m_context.getRenderTarget().getHeight() < m_resolveDimensions.y())
+		throw tcu::NotSupportedError("Render target size must be at least " + de::toString(m_resolveDimensions.x()) + "x" + de::toString(m_resolveDimensions.y()));
+
+	// log what the test tries to do
+
+	if (m_test == TEST_DEFAULT_LAYER)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Rendering to the default layer." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_SINGLE_LAYER)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Rendering to a single layer." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_ALL_LAYERS)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Rendering to all layers." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_DIFFERENT_LAYERS)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Outputting different number of vertices to each layer." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_INVOCATION_PER_LAYER)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Using a different invocation to output to each layer." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_MULTIPLE_LAYERS_PER_INVOCATION)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Outputting to each layer from multiple invocations." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_LAYER_ID)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Using gl_Layer in fragment shader." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_LAYER_PROVOKING_VERTEX)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying LAYER_PROVOKING_VERTEX." << tcu::TestLog::EndMessage;
+	else
+		DE_ASSERT(false);
+
+	// init resources
+
+	initTexture();
+	initFbo();
+	initRenderShader();
+	initSamplerShader();
+}
+
+void LayeredRenderCase::deinit (void)
+{
+	if (m_texture)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texture);
+		m_texture = 0;
+	}
+
+	if (m_fbo)
+	{
+		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_fbo);
+		m_fbo = 0;
+	}
+
+	delete m_renderShader;
+	delete m_samplerShader;
+
+	m_renderShader = DE_NULL;
+	m_samplerShader = DE_NULL;
+}
+
+LayeredRenderCase::IterateResult LayeredRenderCase::iterate (void)
+{
+	++m_iteration;
+
+	if (m_iteration == 1)
+	{
+		if (m_test == TEST_LAYER_PROVOKING_VERTEX)
+		{
+			// which layer the implementation claims to render to
+
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint> state;
+
+			m_context.getRenderContext().getFunctions().getIntegerv(GL_LAYER_PROVOKING_VERTEX, &state);
+			GLU_EXPECT_NO_ERROR(m_context.getRenderContext().getFunctions().getError(), "getInteger(GL_LAYER_PROVOKING_VERTEX)");
+
+			if (!state.verifyValidity(m_testCtx))
+				return STOP;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "GL_LAYER_PROVOKING_VERTEX = " << glu::getProvokingVertexStr(state) << tcu::TestLog::EndMessage;
+
+			if (state != GL_FIRST_VERTEX_CONVENTION &&
+				state != GL_LAST_VERTEX_CONVENTION &&
+				state != GL_UNDEFINED_VERTEX)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "getInteger(GL_LAYER_PROVOKING_VERTEX) returned illegal value. Got " << state << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected provoking vertex value");
+				return STOP;
+			}
+
+			m_provokingVertex = (glw::GLenum)state;
+		}
+
+		// render to texture
+		{
+			const tcu::ScopedLogSection section(m_testCtx.getLog(), "RenderToTexture", "Render to layered texture");
+
+			// render to layered texture with the geometry shader
+			renderToTexture();
+		}
+
+		return CONTINUE;
+	}
+	else if (m_test == TEST_LAYER_PROVOKING_VERTEX && m_provokingVertex == GL_UNDEFINED_VERTEX)
+	{
+		// Verification requires information from another layers, layers not independent
+		{
+			const tcu::ScopedLogSection	section		(m_testCtx.getLog(), "VerifyLayers", "Verify layers 0 and 1");
+			tcu::Surface				layer0		(m_resolveDimensions.x(), m_resolveDimensions.y());
+			tcu::Surface				layer1		(m_resolveDimensions.x(), m_resolveDimensions.y());
+
+			// sample layer to frame buffer
+			sampleTextureLayer(layer0, 0);
+			sampleTextureLayer(layer1, 1);
+
+			m_allLayersOk &= verifyProvokingVertexLayers(layer0, layer1);
+		}
+
+		// Other layers empty
+		for (int layerNdx = 2; layerNdx < m_numLayers; ++layerNdx)
+		{
+			const tcu::ScopedLogSection	section		(m_testCtx.getLog(), "VerifyLayer", "Verify layer " + de::toString(layerNdx));
+			tcu::Surface				layer		(m_resolveDimensions.x(), m_resolveDimensions.y());
+
+			// sample layer to frame buffer
+			sampleTextureLayer(layer, layerNdx);
+
+			// verify
+			m_allLayersOk &= verifyEmptyImage(layer);
+		}
+	}
+	else
+	{
+		// Layers independent
+
+		const int					layerNdx	= m_iteration - 2;
+		const tcu::ScopedLogSection	section		(m_testCtx.getLog(), "VerifyLayer", "Verify layer " + de::toString(layerNdx));
+		tcu::Surface				layer		(m_resolveDimensions.x(), m_resolveDimensions.y());
+
+		// sample layer to frame buffer
+		sampleTextureLayer(layer, layerNdx);
+
+		// verify
+		m_allLayersOk &= verifyLayerContent(layer, layerNdx);
+
+		if (layerNdx < m_numLayers-1)
+			return CONTINUE;
+	}
+
+	// last iteration
+	if (m_allLayersOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Detected invalid layer content");
+
+	return STOP;
+}
+
+void LayeredRenderCase::initTexture (void)
+{
+	DE_ASSERT(!m_texture);
+
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	const tcu::IVec3			texSize			= getTargetDimensions(m_target);
+	const tcu::TextureFormat	texFormat		= glu::mapGLInternalFormat(GL_RGBA8);
+	const glu::TransferFormat	transferFormat	= glu::getTransferFormat(texFormat);
+
+	gl.genTextures(1, &m_texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "gen texture");
+
+	switch (m_target)
+	{
+		case TARGET_CUBE:
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating cubemap texture, size = " << texSize.x() << "x" << texSize.y() << tcu::TestLog::EndMessage;
+			gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_texture);
+			gl.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA8, texSize.x(), texSize.y(), 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+			gl.texImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA8, texSize.x(), texSize.y(), 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+			gl.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA8, texSize.x(), texSize.y(), 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+			gl.texImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA8, texSize.x(), texSize.y(), 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+			gl.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA8, texSize.x(), texSize.y(), 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+			gl.texImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA8, texSize.x(), texSize.y(), 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+			break;
+
+		case TARGET_3D:
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating 3d texture, size = " << texSize.x() << "x" << texSize.y() << "x" << texSize.z() << tcu::TestLog::EndMessage;
+			gl.bindTexture(GL_TEXTURE_3D, m_texture);
+			gl.texImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, texSize.x(), texSize.y(), texSize.z(), 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+			break;
+
+		case TARGET_1D_ARRAY:
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating 1d texture array, size = " << texSize.x() << ", layers = " << texSize.y() << tcu::TestLog::EndMessage;
+			gl.bindTexture(GL_TEXTURE_1D_ARRAY, m_texture);
+			gl.texImage2D(GL_TEXTURE_1D_ARRAY, 0, GL_RGBA8, texSize.x(), texSize.y(), 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+			break;
+
+		case TARGET_2D_ARRAY:
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating 2d texture array, size = " << texSize.x() << "x" << texSize.y() << ", layers = " << texSize.z() << tcu::TestLog::EndMessage;
+			gl.bindTexture(GL_TEXTURE_2D_ARRAY, m_texture);
+			gl.texImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, texSize.x(), texSize.y(), texSize.z(), 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+			break;
+
+		case TARGET_2D_MS_ARRAY:
+		{
+			const int numSamples = 2;
+
+			int maxSamples = 0;
+			gl.getIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &maxSamples);
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Creating 2d multisample texture array, size = " << texSize.x() << "x" << texSize.y() << ", layers = " << texSize.z() << ", samples = " << numSamples << tcu::TestLog::EndMessage;
+
+			if (numSamples > maxSamples)
+				throw tcu::NotSupportedError("Test requires " + de::toString(numSamples) + " color texture samples." );
+
+			gl.bindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, m_texture);
+			gl.texStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, numSamples, GL_RGBA8, texSize.x(), texSize.y(), texSize.z(), GL_TRUE);
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "tex image");
+
+	// Multisample textures don't use filters
+	if (getTargetTextureTarget(m_target) != GL_TEXTURE_2D_MULTISAMPLE_ARRAY)
+	{
+		gl.texParameteri(getTargetTextureTarget(m_target), GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		gl.texParameteri(getTargetTextureTarget(m_target), GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		gl.texParameteri(getTargetTextureTarget(m_target), GL_TEXTURE_WRAP_S, GL_REPEAT);
+		gl.texParameteri(getTargetTextureTarget(m_target), GL_TEXTURE_WRAP_T, GL_REPEAT);
+		gl.texParameteri(getTargetTextureTarget(m_target), GL_TEXTURE_WRAP_R, GL_REPEAT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "tex filter");
+	}
+}
+
+void LayeredRenderCase::initFbo (void)
+{
+	DE_ASSERT(!m_fbo);
+
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Creating FBO" << tcu::TestLog::EndMessage;
+
+	gl.genFramebuffers(1, &m_fbo);
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+	gl.framebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0);
+	gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "setup fbo");
+}
+
+void LayeredRenderCase::initRenderShader (void)
+{
+	const tcu::ScopedLogSection section(m_testCtx.getLog(), "RenderToTextureShader", "Create layered rendering shader program");
+
+	static const char* const positionVertex =	"#version 310 es\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n"
+												"}\n";
+
+	m_renderShader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(positionVertex) << glu::FragmentSource(genFragmentSource()) << glu::GeometrySource(genGeometrySource()));
+	m_testCtx.getLog() << *m_renderShader;
+
+	if (!m_renderShader->isOk())
+		throw tcu::TestError("failed to build render shader");
+}
+
+void LayeredRenderCase::initSamplerShader (void)
+{
+	const tcu::ScopedLogSection section(m_testCtx.getLog(), "TextureSamplerShader", "Create shader sampler program");
+
+	static const char* const positionVertex =	"#version 310 es\n"
+												"in highp vec4 a_position;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"}\n";
+
+	m_samplerShader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+																			<< glu::VertexSource(positionVertex)
+																			<< glu::FragmentSource(genSamplerFragmentSource()));
+
+	m_testCtx.getLog() << *m_samplerShader;
+
+	if (!m_samplerShader->isOk())
+		throw tcu::TestError("failed to build sampler shader");
+
+	m_samplerSamplerLoc = m_context.getRenderContext().getFunctions().getUniformLocation(m_samplerShader->getProgram(), "u_sampler");
+	if (m_samplerSamplerLoc == -1)
+		throw tcu::TestError("u_sampler uniform location = -1");
+
+	m_samplerLayerLoc = m_context.getRenderContext().getFunctions().getUniformLocation(m_samplerShader->getProgram(), "u_layer");
+	if (m_samplerLayerLoc == -1)
+		throw tcu::TestError("u_layer uniform location = -1");
+}
+
+std::string LayeredRenderCase::genFragmentSource (void) const
+{
+	static const char* const fragmentLayerIdShader =	"#version 310 es\n"
+														"#extension GL_EXT_geometry_shader : require\n"
+														"layout(location = 0) out mediump vec4 fragColor;\n"
+														"void main (void)\n"
+														"{\n"
+														"	fragColor = vec4(((gl_Layer % 2) == 1) ? 1.0 : 0.5,\n"
+														"	                 (((gl_Layer / 2) % 2) == 1) ? 1.0 : 0.5,\n"
+														"	                 (gl_Layer == 0) ? 1.0 : 0.0,\n"
+														"	                 1.0);\n"
+														"}\n";
+
+	if (m_test != TEST_LAYER_ID)
+		return std::string(s_commonShaderSourceFragment);
+	else
+		return std::string(fragmentLayerIdShader);
+}
+
+std::string LayeredRenderCase::genGeometrySource (void) const
+{
+	// TEST_DIFFERENT_LAYERS:				draw 0 quad to first layer, 1 to second, etc.
+	// TEST_ALL_LAYERS:						draw 1 quad to all layers
+	// TEST_MULTIPLE_LAYERS_PER_INVOCATION:	draw 1 triangle to "current layer" and 1 triangle to another layer
+	// else:								draw 1 quad to some single layer
+	const int			maxVertices =		(m_test == TEST_DIFFERENT_LAYERS) ? ((2 + m_numLayers-1) * m_numLayers) :
+											(m_test == TEST_ALL_LAYERS || m_test == TEST_LAYER_ID) ? (m_numLayers * 4) :
+											(m_test == TEST_MULTIPLE_LAYERS_PER_INVOCATION) ? (6) :
+											(m_test == TEST_LAYER_PROVOKING_VERTEX) ? (6) :
+											(4);
+	std::ostringstream	buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n";
+
+	if (m_test == TEST_INVOCATION_PER_LAYER || m_test == TEST_MULTIPLE_LAYERS_PER_INVOCATION)
+		buf << "layout(points, invocations=" << m_numLayers << ") in;\n";
+	else
+		buf << "layout(points) in;\n";
+
+	buf <<	"layout(triangle_strip, max_vertices = " << maxVertices << ") out;\n"
+			"out highp vec4 v_frag_FragColor;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n";
+
+	if (m_test == TEST_DEFAULT_LAYER)
+	{
+		buf <<	"	const highp vec4 white = vec4(1.0, 1.0, 1.0, 1.0);\n\n"
+				"	gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4(-1.0,  1.0, 0.0, 1.0);\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4( 0.0, -1.0, 0.0, 1.0);\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4( 0.0,  1.0, 0.0, 1.0);\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n";
+	}
+	else if (m_test == TEST_SINGLE_LAYER)
+	{
+		buf <<	"	const highp vec4 white = vec4(1.0, 1.0, 1.0, 1.0);\n\n"
+				"	gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
+				"	gl_Layer = " << m_targetLayer << ";\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4(-1.0,  1.0, 0.0, 1.0);\n"
+				"	gl_Layer = " << m_targetLayer << ";\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4( 0.0, -1.0, 0.0, 1.0);\n"
+				"	gl_Layer = " << m_targetLayer << ";\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4( 0.0,  1.0, 0.0, 1.0);\n"
+				"	gl_Layer = " << m_targetLayer << ";\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n";
+	}
+	else if (m_test == TEST_ALL_LAYERS || m_test == TEST_LAYER_ID)
+	{
+		DE_ASSERT(m_numLayers <= 6);
+
+		buf <<	"	const highp vec4 white   = vec4(1.0, 1.0, 1.0, 1.0);\n"
+				"	const highp vec4 red     = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	const highp vec4 green   = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"	const highp vec4 blue    = vec4(0.0, 0.0, 1.0, 1.0);\n"
+				"	const highp vec4 yellow  = vec4(1.0, 1.0, 0.0, 1.0);\n"
+				"	const highp vec4 magenta = vec4(1.0, 0.0, 1.0, 1.0);\n"
+				"	const highp vec4 colors[6] = vec4[6](white, red, green, blue, yellow, magenta);\n\n"
+				"	for (mediump int layerNdx = 0; layerNdx < " << m_numLayers << "; ++layerNdx)\n"
+				"	{\n"
+				"		gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
+				"		gl_Layer = layerNdx;\n"
+				"		v_frag_FragColor = colors[layerNdx];\n"
+				"		EmitVertex();\n\n"
+				"		gl_Position = vec4(-1.0,  1.0, 0.0, 1.0);\n"
+				"		gl_Layer = layerNdx;\n"
+				"		v_frag_FragColor = colors[layerNdx];\n"
+				"		EmitVertex();\n\n"
+				"		gl_Position = vec4( 0.0, -1.0, 0.0, 1.0);\n"
+				"		gl_Layer = layerNdx;\n"
+				"		v_frag_FragColor = colors[layerNdx];\n"
+				"		EmitVertex();\n\n"
+				"		gl_Position = vec4( 0.0,  1.0, 0.0, 1.0);\n"
+				"		gl_Layer = layerNdx;\n"
+				"		v_frag_FragColor = colors[layerNdx];\n"
+				"		EmitVertex();\n"
+				"		EndPrimitive();\n"
+				"	}\n";
+	}
+	else if (m_test == TEST_DIFFERENT_LAYERS)
+	{
+		DE_ASSERT(m_numLayers <= 6);
+
+		buf <<	"	const highp vec4 white = vec4(1.0, 1.0, 1.0, 1.0);\n\n"
+				"	for (mediump int layerNdx = 0; layerNdx < " << m_numLayers << "; ++layerNdx)\n"
+				"	{\n"
+				"		for (mediump int colNdx = 0; colNdx <= layerNdx; ++colNdx)\n"
+				"		{\n"
+				"			highp float posX = float(colNdx) / float(" << m_numLayers << ") * 2.0 - 1.0;\n\n"
+				"			gl_Position = vec4(posX,  1.0, 0.0, 1.0);\n"
+				"			gl_Layer = layerNdx;\n"
+				"			v_frag_FragColor = white;\n"
+				"			EmitVertex();\n\n"
+				"			gl_Position = vec4(posX, -1.0, 0.0, 1.0);\n"
+				"			gl_Layer = layerNdx;\n"
+				"			v_frag_FragColor = white;\n"
+				"			EmitVertex();\n"
+				"		}\n"
+				"		EndPrimitive();\n"
+				"	}\n";
+	}
+	else if (m_test == TEST_INVOCATION_PER_LAYER)
+	{
+		buf <<	"	const highp vec4 white   = vec4(1.0, 1.0, 1.0, 1.0);\n"
+				"	const highp vec4 red     = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	const highp vec4 green   = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"	const highp vec4 blue    = vec4(0.0, 0.0, 1.0, 1.0);\n"
+				"	const highp vec4 yellow  = vec4(1.0, 1.0, 0.0, 1.0);\n"
+				"	const highp vec4 magenta = vec4(1.0, 0.0, 1.0, 1.0);\n"
+				"	const highp vec4 colors[6] = vec4[6](white, red, green, blue, yellow, magenta);\n"
+				"\n"
+				"	gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
+				"	gl_Layer = gl_InvocationID;\n"
+				"	v_frag_FragColor = colors[gl_InvocationID];\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4(-1.0,  1.0, 0.0, 1.0);\n"
+				"	gl_Layer = gl_InvocationID;\n"
+				"	v_frag_FragColor = colors[gl_InvocationID];\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4( 0.0, -1.0, 0.0, 1.0);\n"
+				"	gl_Layer = gl_InvocationID;\n"
+				"	v_frag_FragColor = colors[gl_InvocationID];\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4( 0.0,  1.0, 0.0, 1.0);\n"
+				"	gl_Layer = gl_InvocationID;\n"
+				"	v_frag_FragColor = colors[gl_InvocationID];\n"
+				"	EmitVertex();\n"
+				"	EndPrimitive();\n";
+	}
+	else if (m_test == TEST_MULTIPLE_LAYERS_PER_INVOCATION)
+	{
+		buf <<	"	const highp vec4 white = vec4(1.0, 1.0, 1.0, 1.0);\n"
+				"\n"
+				"	mediump int layerA = gl_InvocationID;\n"
+				"	mediump int layerB = (gl_InvocationID + 1) % " << m_numLayers << ";\n"
+				"	highp float aEnd = float(layerA) / float(" << m_numLayers << ") * 2.0 - 1.0;\n"
+				"	highp float bEnd = float(layerB) / float(" << m_numLayers << ") * 2.0 - 1.0;\n"
+				"\n"
+				"	gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
+				"	gl_Layer = layerA;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4(-1.0,  1.0, 0.0, 1.0);\n"
+				"	gl_Layer = layerA;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4(aEnd, -1.0, 0.0, 1.0);\n"
+				"	gl_Layer = layerA;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	EndPrimitive();\n"
+				"\n"
+				"	gl_Position = vec4(-1.0,  1.0, 0.0, 1.0);\n"
+				"	gl_Layer = layerB;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4(bEnd,  1.0, 0.0, 1.0);\n"
+				"	gl_Layer = layerB;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4(bEnd, -1.0, 0.0, 1.0);\n"
+				"	gl_Layer = layerB;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	EndPrimitive();\n";
+	}
+	else if (m_test == TEST_LAYER_PROVOKING_VERTEX)
+	{
+		buf <<	"	const highp vec4 white = vec4(1.0, 1.0, 1.0, 1.0);\n\n"
+				"	gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
+				"	gl_Layer = 0;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4(-1.0,  1.0, 0.0, 1.0);\n"
+				"	gl_Layer = 1;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4( 0.0, -1.0, 0.0, 1.0);\n"
+				"	gl_Layer = 1;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	EndPrimitive();\n\n"
+				"	gl_Position = vec4(-1.0,  1.0, 0.0, 1.0);\n"
+				"	gl_Layer = 0;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4( 0.0, -1.0, 0.0, 1.0);\n"
+				"	gl_Layer = 1;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n\n"
+				"	gl_Position = vec4( 0.0,  1.0, 0.0, 1.0);\n"
+				"	gl_Layer = 1;\n"
+				"	v_frag_FragColor = white;\n"
+				"	EmitVertex();\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+std::string LayeredRenderCase::genSamplerFragmentSource (void) const
+{
+	std::ostringstream buf;
+
+	buf << "#version 310 es\n";
+	if (m_target == TARGET_2D_MS_ARRAY)
+		buf << "#extension GL_OES_texture_storage_multisample_2d_array : require\n";
+	buf << "layout(location = 0) out mediump vec4 fragColor;\n";
+
+	switch (m_target)
+	{
+		case TARGET_CUBE:			buf << "uniform highp samplerCube u_sampler;\n";		break;
+		case TARGET_3D:				buf << "uniform highp sampler3D u_sampler;\n";			break;
+		case TARGET_2D_ARRAY:		buf << "uniform highp sampler2DArray u_sampler;\n";		break;
+		case TARGET_1D_ARRAY:		buf << "uniform highp sampler1DArray u_sampler;\n";		break;
+		case TARGET_2D_MS_ARRAY:	buf << "uniform highp sampler2DMSArray u_sampler;\n";	break;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	buf <<	"uniform highp int u_layer;\n"
+			"void main (void)\n"
+			"{\n";
+
+	switch (m_target)
+	{
+		case TARGET_CUBE:
+			buf <<	"	highp vec2 facepos = 2.0 * gl_FragCoord.xy / vec2(ivec2(" << m_resolveDimensions.x() << ", " << m_resolveDimensions.y() << ")) - vec2(1.0, 1.0);\n"
+					"	if (u_layer == 0)\n"
+					"		fragColor = textureLod(u_sampler, vec3(1.0, -facepos.y, -facepos.x), 0.0);\n"
+					"	else if (u_layer == 1)\n"
+					"		fragColor = textureLod(u_sampler, vec3(-1.0, -facepos.y, facepos.x), 0.0);\n"
+					"	else if (u_layer == 2)\n"
+					"		fragColor = textureLod(u_sampler, vec3(facepos.x, 1.0, facepos.y), 0.0);\n"
+					"	else if (u_layer == 3)\n"
+					"		fragColor = textureLod(u_sampler, vec3(facepos.x, -1.0, -facepos.y), 0.0);\n"
+					"	else if (u_layer == 4)\n"
+					"		fragColor = textureLod(u_sampler, vec3(facepos.x, -facepos.y, 1.0), 0.0);\n"
+					"	else if (u_layer == 5)\n"
+					"		fragColor = textureLod(u_sampler, vec3(-facepos.x, -facepos.y, -1.0), 0.0);\n"
+					"	else\n"
+					"		fragColor = vec4(1.0, 0.0, 1.0, 1.0);\n";
+			break;
+
+		case TARGET_3D:
+		case TARGET_2D_ARRAY:
+		case TARGET_2D_MS_ARRAY:
+			buf <<	"	highp ivec2 screenpos = ivec2(floor(gl_FragCoord.xy));\n"
+					"	fragColor = texelFetch(u_sampler, ivec3(screenpos, u_layer), 0);\n";
+			break;
+
+		case TARGET_1D_ARRAY:
+			buf <<	"	highp ivec2 screenpos = ivec2(floor(gl_FragCoord.xy));\n"
+					"	fragColor = texelFetch(u_sampler, ivec2(screenpos.x, u_layer), 0);\n";
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+	buf <<	"}\n";
+	return buf.str();
+}
+
+void LayeredRenderCase::renderToTexture (void)
+{
+	const tcu::IVec3		texSize		= getTargetDimensions(m_target);
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	glu::VertexArray		vao			(m_context.getRenderContext());
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering to texture" << tcu::TestLog::EndMessage;
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	gl.viewport(0, 0, texSize.x(), texSize.y());
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	gl.bindVertexArray(*vao);
+	gl.useProgram(m_renderShader->getProgram());
+	gl.drawArrays(GL_POINTS, 0, 1);
+	gl.useProgram(0);
+	gl.bindVertexArray(0);
+	gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "render");
+}
+
+void LayeredRenderCase::sampleTextureLayer (tcu::Surface& dst, int layer)
+{
+	DE_ASSERT(dst.getWidth() == m_resolveDimensions.x());
+	DE_ASSERT(dst.getHeight() == m_resolveDimensions.y());
+
+	static const tcu::Vec4 fullscreenQuad[4] =
+	{
+		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),
+	};
+
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const int				positionLoc	= gl.getAttribLocation(m_samplerShader->getProgram(), "a_position");
+	glu::VertexArray		vao			(m_context.getRenderContext());
+	glu::Buffer				buf			(m_context.getRenderContext());
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Sampling from texture layer " << layer << tcu::TestLog::EndMessage;
+
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	gl.viewport(0, 0, m_resolveDimensions.x(), m_resolveDimensions.y());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, *buf);
+	gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "buf");
+
+	gl.bindVertexArray(*vao);
+	gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(positionLoc);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");
+
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(getTargetTextureTarget(m_target), m_texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind texture");
+
+	gl.useProgram(m_samplerShader->getProgram());
+	gl.uniform1i(m_samplerLayerLoc, layer);
+	gl.uniform1i(m_samplerSamplerLoc, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "setup program");
+
+	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
+
+	gl.useProgram(0);
+	gl.bindVertexArray(0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clean");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+}
+
+bool LayeredRenderCase::verifyLayerContent (const tcu::Surface& layer, int layerNdx)
+{
+	const tcu::Vec4 white   = tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);
+	const tcu::Vec4 red     = tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
+	const tcu::Vec4 green   = tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 blue    = tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f);
+	const tcu::Vec4 yellow  = tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4 magenta = tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f);
+	const tcu::Vec4 colors[6] = { white, red, green, blue, yellow, magenta };
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying layer contents" << tcu::TestLog::EndMessage;
+
+	switch (m_test)
+	{
+		case TEST_DEFAULT_LAYER:
+			if (layerNdx == 0)
+				return verifyImageSingleColoredRow(layer, 0.5f, white);
+			else
+				return verifyEmptyImage(layer);
+
+		case TEST_SINGLE_LAYER:
+			if (layerNdx == m_targetLayer)
+				return verifyImageSingleColoredRow(layer, 0.5f, white);
+			else
+				return verifyEmptyImage(layer);
+
+		case TEST_ALL_LAYERS:
+		case TEST_INVOCATION_PER_LAYER:
+			return verifyImageSingleColoredRow(layer, 0.5f, colors[layerNdx]);
+
+		case TEST_DIFFERENT_LAYERS:
+		case TEST_MULTIPLE_LAYERS_PER_INVOCATION:
+			if (layerNdx == 0)
+				return verifyEmptyImage(layer);
+			else
+				return verifyImageSingleColoredRow(layer, layerNdx / (float)m_numLayers, white);
+
+		case TEST_LAYER_ID:
+		{
+			const tcu::Vec4 layerColor((layerNdx % 2 == 1) ? (1.0f) : (0.5f),
+									   ((layerNdx/2) % 2 == 1) ? (1.0f) : (0.5f),
+									   (layerNdx == 0) ? (1.0f) : (0.0f),
+									   1.0f);
+			return verifyImageSingleColoredRow(layer, 0.5f, layerColor);
+		}
+
+		case TEST_LAYER_PROVOKING_VERTEX:
+			if (m_provokingVertex == GL_FIRST_VERTEX_CONVENTION)
+			{
+				if (layerNdx == 0)
+					return verifyImageSingleColoredRow(layer, 0.5f, white);
+				else
+					return verifyEmptyImage(layer);
+			}
+			else if (m_provokingVertex == GL_LAST_VERTEX_CONVENTION)
+			{
+				if (layerNdx == 1)
+					return verifyImageSingleColoredRow(layer, 0.5f, white);
+				else
+					return verifyEmptyImage(layer);
+			}
+			else
+			{
+				DE_ASSERT(false);
+				return false;
+			}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return false;
+	};
+}
+
+bool LayeredRenderCase::verifyImageSingleColoredRow (const tcu::Surface& layer, float rowWidthRatio, const tcu::Vec4& barColor, bool logging)
+{
+	DE_ASSERT(rowWidthRatio > 0.0f);
+
+	const int		barLength			= (int)(rowWidthRatio*layer.getWidth());
+	const int		barLengthThreshold	= 1;
+	tcu::Surface	errorMask			(layer.getWidth(), layer.getHeight());
+	bool			allPixelsOk			= true;
+
+	if (logging)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Expecting all pixels with distance less or equal to (about) " << barLength << " pixels from left border to be of color " << barColor.swizzle(0,1,2) << "." << tcu::TestLog::EndMessage;
+
+	tcu::clear(errorMask.getAccess(), tcu::RGBA::green.toIVec());
+
+	for (int y = 0; y < layer.getHeight(); ++y)
+	for (int x = 0; x < layer.getWidth(); ++x)
+	{
+		const tcu::RGBA color		= layer.getPixel(x, y);
+		const tcu::RGBA refColor	= tcu::RGBA(barColor);
+		const int		threshold	= 8;
+		const bool		isBlack		= color.getRed() <= threshold || color.getGreen() <= threshold || color.getBlue() <= threshold;
+		const bool		isColor		= tcu::allEqual(tcu::lessThan(tcu::abs(color.toIVec().swizzle(0, 1, 2) - refColor.toIVec().swizzle(0, 1, 2)), tcu::IVec3(threshold, threshold, threshold)), tcu::BVec3(true, true, true));
+
+		bool			isOk;
+
+		if (x <= barLength - barLengthThreshold)
+			isOk = isColor;
+		else if (x >= barLength + barLengthThreshold)
+			isOk = isBlack;
+		else
+			isOk = isColor || isBlack;
+
+		allPixelsOk &= isOk;
+
+		if (!isOk)
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+	}
+
+	if (allPixelsOk)
+	{
+		if (logging)
+			m_testCtx.getLog()	<< tcu::TestLog::Message << "Image is valid." << tcu::TestLog::EndMessage
+								<< tcu::TestLog::ImageSet("LayerContent", "Layer content")
+								<< tcu::TestLog::Image("Layer", "Layer", layer)
+								<< tcu::TestLog::EndImageSet;
+		return true;
+	}
+	else
+	{
+		if (logging)
+			m_testCtx.getLog()	<< tcu::TestLog::Message << "Image verification failed. Got unexpected pixels." << tcu::TestLog::EndMessage
+								<< tcu::TestLog::ImageSet("LayerContent", "Layer content")
+								<< tcu::TestLog::Image("Layer",		"Layer",	layer)
+								<< tcu::TestLog::Image("ErrorMask",	"Errors",	errorMask)
+								<< tcu::TestLog::EndImageSet;
+		return false;
+	}
+
+	if (logging)
+		m_testCtx.getLog() << tcu::TestLog::Image("LayerContent", "Layer content", layer);
+
+	return allPixelsOk;
+}
+
+bool LayeredRenderCase::verifyEmptyImage (const tcu::Surface& layer, bool logging)
+{
+	// Expect black
+	if (logging)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Expecting empty image" << tcu::TestLog::EndMessage;
+
+	for (int y = 0; y < layer.getHeight(); ++y)
+	for (int x = 0; x < layer.getWidth(); ++x)
+	{
+		const tcu::RGBA color		= layer.getPixel(x, y);
+		const int		threshold	= 8;
+		const bool		isBlack		= color.getRed() <= threshold || color.getGreen() <= threshold || color.getBlue() <= threshold;
+
+		if (!isBlack)
+		{
+			if (logging)
+				m_testCtx.getLog()	<< tcu::TestLog::Message
+									<< "Found (at least) one bad pixel at " << x << "," << y << ". Pixel color is not background color."
+									<< tcu::TestLog::EndMessage
+									<< tcu::TestLog::ImageSet("LayerContent", "Layer content")
+									<< tcu::TestLog::Image("Layer", "Layer", layer)
+									<< tcu::TestLog::EndImageSet;
+			return false;
+		}
+	}
+
+	if (logging)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Image is valid" << tcu::TestLog::EndMessage;
+
+	return true;
+}
+
+bool LayeredRenderCase::verifyProvokingVertexLayers (const tcu::Surface& layer0, const tcu::Surface& layer1)
+{
+	const tcu::Vec4	white			= tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);
+	const bool		layer0Empty		= verifyEmptyImage(layer0, false);
+	const bool		layer1Empty		= verifyEmptyImage(layer1, false);
+	bool			error			= false;
+
+	// Both images could contain something if the quad triangles get assigned to different layers
+	m_testCtx.getLog() << tcu::TestLog::Message << "Expecting non-empty layers, or non-empty layer." << tcu::TestLog::EndMessage;
+
+	if (layer0Empty == true && layer1Empty == true)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Got empty images." << tcu::TestLog::EndMessage;
+		error = true;
+	}
+
+	// log images always
+	m_testCtx.getLog()
+		<< tcu::TestLog::ImageSet("LayerContent", "Layer content")
+		<< tcu::TestLog::Image("Layer", "Layer0", layer0)
+		<< tcu::TestLog::Image("Layer", "Layer1", layer1)
+		<< tcu::TestLog::EndImageSet;
+
+	if (error)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage;
+	else
+		m_testCtx.getLog() << tcu::TestLog::Message << "Image is valid." << tcu::TestLog::EndMessage;
+
+	return !error;
+}
+
+int LayeredRenderCase::getTargetLayers (LayeredRenderTargetType target)
+{
+	switch (target)
+	{
+		case TARGET_CUBE:			return 6;
+		case TARGET_3D:				return 4;
+		case TARGET_1D_ARRAY:		return 4;
+		case TARGET_2D_ARRAY:		return 4;
+		case TARGET_2D_MS_ARRAY:	return 2;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+glw::GLenum LayeredRenderCase::getTargetTextureTarget (LayeredRenderTargetType target)
+{
+	switch (target)
+	{
+		case TARGET_CUBE:			return GL_TEXTURE_CUBE_MAP;
+		case TARGET_3D:				return GL_TEXTURE_3D;
+		case TARGET_1D_ARRAY:		return GL_TEXTURE_1D_ARRAY;
+		case TARGET_2D_ARRAY:		return GL_TEXTURE_2D_ARRAY;
+		case TARGET_2D_MS_ARRAY:	return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+tcu::IVec3 LayeredRenderCase::getTargetDimensions (LayeredRenderTargetType target)
+{
+	switch (target)
+	{
+		case TARGET_CUBE:			return tcu::IVec3(64, 64, 0);
+		case TARGET_3D:				return tcu::IVec3(64, 64, 4);
+		case TARGET_1D_ARRAY:		return tcu::IVec3(64, 4, 0);
+		case TARGET_2D_ARRAY:		return tcu::IVec3(64, 64, 4);
+		case TARGET_2D_MS_ARRAY:	return tcu::IVec3(64, 64, 2);
+		default:
+			DE_ASSERT(DE_FALSE);
+			return tcu::IVec3(0, 0, 0);
+	}
+}
+
+tcu::IVec2 LayeredRenderCase::getResolveDimensions (LayeredRenderTargetType target)
+{
+	switch (target)
+	{
+		case TARGET_CUBE:			return tcu::IVec2(64, 64);
+		case TARGET_3D:				return tcu::IVec2(64, 64);
+		case TARGET_1D_ARRAY:		return tcu::IVec2(64, 1);
+		case TARGET_2D_ARRAY:		return tcu::IVec2(64, 64);
+		case TARGET_2D_MS_ARRAY:	return tcu::IVec2(64, 64);
+		default:
+			DE_ASSERT(DE_FALSE);
+			return tcu::IVec2(0, 0);
+	}
+}
+
+class VaryingOutputCountCase : public GeometryShaderRenderTest
+{
+public:
+	enum ShaderInstancingMode
+	{
+		MODE_WITHOUT_INSTANCING = 0,
+		MODE_WITH_INSTANCING,
+
+		MODE_LAST
+	};
+													VaryingOutputCountCase			(Context& context, const char* name, const char* desc, VaryingOutputCountShader::VaryingSource test, ShaderInstancingMode mode);
+private:
+	void											init							(void);
+	void											deinit							(void);
+	void											preRender						(sglr::Context& ctx, GLuint programID);
+
+	sglr::ShaderProgram&							getProgram						(void);
+	void											genVertexAttribData				(void);
+	void											genVertexDataWithoutInstancing	(void);
+	void											genVertexDataWithInstancing		(void);
+
+	VaryingOutputCountShader*						m_program;
+	const VaryingOutputCountShader::VaryingSource	m_test;
+	const ShaderInstancingMode						m_mode;
+	int												m_maxEmitCount;
+};
+
+VaryingOutputCountCase::VaryingOutputCountCase (Context& context, const char* name, const char* desc, VaryingOutputCountShader::VaryingSource test, ShaderInstancingMode mode)
+	: GeometryShaderRenderTest	(context, name, desc, GL_POINTS, GL_TRIANGLE_STRIP, VaryingOutputCountShader::getAttributeName(test))
+	, m_program					(DE_NULL)
+	, m_test					(test)
+	, m_mode					(mode)
+	, m_maxEmitCount			(0)
+{
+	DE_ASSERT(mode < MODE_LAST);
+}
+
+void VaryingOutputCountCase::init (void)
+{
+	// Check requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+
+	if (m_test == VaryingOutputCountShader::READ_TEXTURE)
+	{
+		glw::GLint maxTextures = 0;
+
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS, &maxTextures);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS = " << maxTextures << tcu::TestLog::EndMessage;
+
+		if (maxTextures < 1)
+			throw tcu::NotSupportedError("Geometry shader texture units required");
+	}
+
+	// Get max emit count
+	{
+		const int	componentsPerVertex	= 4 + 4; // vec4 pos, vec4 color
+		glw::GLint	maxVertices			= 0;
+		glw::GLint	maxComponents		= 0;
+
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &maxVertices);
+		m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, &maxComponents);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_GEOMETRY_OUTPUT_VERTICES = " << maxVertices << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = " << maxComponents << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "Components per vertex = " << componentsPerVertex << tcu::TestLog::EndMessage;
+
+		if (maxVertices < 256)
+			throw tcu::TestError("MAX_GEOMETRY_OUTPUT_VERTICES was less than minimum required (256)");
+		if (maxComponents < 1024)
+			throw tcu::TestError("MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS was less than minimum required (1024)");
+
+		m_maxEmitCount = de::min(maxVertices, maxComponents / componentsPerVertex);
+	}
+
+	// Log what the test tries to do
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Rendering 4 n-gons with n = "
+		<< ((VaryingOutputCountShader::EMIT_COUNT_VERTEX_0 == -1) ? (m_maxEmitCount) : (VaryingOutputCountShader::EMIT_COUNT_VERTEX_0)) << ", "
+		<< ((VaryingOutputCountShader::EMIT_COUNT_VERTEX_1 == -1) ? (m_maxEmitCount) : (VaryingOutputCountShader::EMIT_COUNT_VERTEX_1)) << ", "
+		<< ((VaryingOutputCountShader::EMIT_COUNT_VERTEX_2 == -1) ? (m_maxEmitCount) : (VaryingOutputCountShader::EMIT_COUNT_VERTEX_2)) << ", and "
+		<< ((VaryingOutputCountShader::EMIT_COUNT_VERTEX_3 == -1) ? (m_maxEmitCount) : (VaryingOutputCountShader::EMIT_COUNT_VERTEX_3)) << ".\n"
+		<< "N is supplied to the geomery shader with "
+		<< ((m_test == VaryingOutputCountShader::READ_ATTRIBUTE) ? ("attribute") : (m_test == VaryingOutputCountShader::READ_UNIFORM) ? ("uniform") : ("texture"))
+		<< tcu::TestLog::EndMessage;
+
+	// Gen shader
+	{
+		const bool instanced = (m_mode == MODE_WITH_INSTANCING);
+
+		DE_ASSERT(!m_program);
+		m_program = new VaryingOutputCountShader(m_test, m_maxEmitCount, instanced);
+	}
+
+	// Case init
+	GeometryShaderRenderTest::init();
+}
+
+void VaryingOutputCountCase::deinit (void)
+{
+	if (m_program)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+	}
+
+	GeometryShaderRenderTest::deinit();
+}
+
+void VaryingOutputCountCase::preRender (sglr::Context& ctx, GLuint programID)
+{
+	if (m_test == VaryingOutputCountShader::READ_UNIFORM)
+	{
+		const int		location		= ctx.getUniformLocation(programID, "u_emitCount");
+		const deInt32	emitCount[4]	= { 6, 0, m_maxEmitCount, 10 };
+
+		if (location == -1)
+			throw tcu::TestError("uniform location of u_emitCount was -1.");
+
+		ctx.uniform4iv(location, 1, emitCount);
+	}
+	else if (m_test == VaryingOutputCountShader::READ_TEXTURE)
+	{
+		const deUint8 data[4*4] =
+		{
+			255,   0,   0,   0,
+			  0, 255,   0,   0,
+			  0,   0, 255,   0,
+			  0,   0,   0, 255,
+		};
+		const int	location	= ctx.getUniformLocation(programID, "u_sampler");
+		GLuint		texID		= 0;
+
+		if (location == -1)
+			throw tcu::TestError("uniform location of u_sampler was -1.");
+		ctx.uniform1i(location, 0);
+
+		// \note we don't need to explicitly delete the texture, the sglr context will delete it
+		ctx.genTextures(1, &texID);
+		ctx.bindTexture(GL_TEXTURE_2D, texID);
+		ctx.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+		ctx.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		ctx.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	}
+}
+
+sglr::ShaderProgram& VaryingOutputCountCase::getProgram (void)
+{
+	return *m_program;
+}
+
+void VaryingOutputCountCase::genVertexAttribData (void)
+{
+	if (m_mode == MODE_WITHOUT_INSTANCING)
+		genVertexDataWithoutInstancing();
+	else if (m_mode == MODE_WITH_INSTANCING)
+		genVertexDataWithInstancing();
+	else
+		DE_ASSERT(false);
+}
+
+void VaryingOutputCountCase::genVertexDataWithoutInstancing (void)
+{
+	m_numDrawVertices = 4;
+
+	m_vertexPosData.resize(4);
+	m_vertexAttrData.resize(4);
+
+	m_vertexPosData[0] = tcu::Vec4( 0.5f,  0.0f, 0.0f, 1.0f);
+	m_vertexPosData[1] = tcu::Vec4( 0.0f,  0.5f, 0.0f, 1.0f);
+	m_vertexPosData[2] = tcu::Vec4(-0.7f, -0.1f, 0.0f, 1.0f);
+	m_vertexPosData[3] = tcu::Vec4(-0.1f, -0.7f, 0.0f, 1.0f);
+
+	if (m_test == VaryingOutputCountShader::READ_ATTRIBUTE)
+	{
+		m_vertexAttrData[0] = tcu::Vec4(((VaryingOutputCountShader::EMIT_COUNT_VERTEX_0 == -1) ? ((float)m_maxEmitCount) : ((float)VaryingOutputCountShader::EMIT_COUNT_VERTEX_0)), 0.0f, 0.0f, 0.0f);
+		m_vertexAttrData[1] = tcu::Vec4(((VaryingOutputCountShader::EMIT_COUNT_VERTEX_1 == -1) ? ((float)m_maxEmitCount) : ((float)VaryingOutputCountShader::EMIT_COUNT_VERTEX_1)), 0.0f, 0.0f, 0.0f);
+		m_vertexAttrData[2] = tcu::Vec4(((VaryingOutputCountShader::EMIT_COUNT_VERTEX_2 == -1) ? ((float)m_maxEmitCount) : ((float)VaryingOutputCountShader::EMIT_COUNT_VERTEX_2)), 0.0f, 0.0f, 0.0f);
+		m_vertexAttrData[3] = tcu::Vec4(((VaryingOutputCountShader::EMIT_COUNT_VERTEX_3 == -1) ? ((float)m_maxEmitCount) : ((float)VaryingOutputCountShader::EMIT_COUNT_VERTEX_3)), 0.0f, 0.0f, 0.0f);
+	}
+	else
+	{
+		m_vertexAttrData[0] = tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f);
+		m_vertexAttrData[1] = tcu::Vec4(1.0f, 0.0f, 0.0f, 0.0f);
+		m_vertexAttrData[2] = tcu::Vec4(2.0f, 0.0f, 0.0f, 0.0f);
+		m_vertexAttrData[3] = tcu::Vec4(3.0f, 0.0f, 0.0f, 0.0f);
+	}
+}
+
+void VaryingOutputCountCase::genVertexDataWithInstancing (void)
+{
+	m_numDrawVertices = 1;
+
+	m_vertexPosData.resize(1);
+	m_vertexAttrData.resize(1);
+
+	m_vertexPosData[0] = tcu::Vec4(0.0f,  0.0f, 0.0f, 1.0f);
+
+	if (m_test == VaryingOutputCountShader::READ_ATTRIBUTE)
+	{
+		const int emitCounts[] =
+		{
+			(VaryingOutputCountShader::EMIT_COUNT_VERTEX_0 == -1) ? (m_maxEmitCount) : (VaryingOutputCountShader::EMIT_COUNT_VERTEX_0),
+			(VaryingOutputCountShader::EMIT_COUNT_VERTEX_1 == -1) ? (m_maxEmitCount) : (VaryingOutputCountShader::EMIT_COUNT_VERTEX_1),
+			(VaryingOutputCountShader::EMIT_COUNT_VERTEX_2 == -1) ? (m_maxEmitCount) : (VaryingOutputCountShader::EMIT_COUNT_VERTEX_2),
+			(VaryingOutputCountShader::EMIT_COUNT_VERTEX_3 == -1) ? (m_maxEmitCount) : (VaryingOutputCountShader::EMIT_COUNT_VERTEX_3),
+		};
+
+		m_vertexAttrData[0] = tcu::Vec4((float)emitCounts[0], (float)emitCounts[1], (float)emitCounts[2], (float)emitCounts[3]);
+	}
+	else
+	{
+		// not used
+		m_vertexAttrData[0] = tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f);
+	}
+}
+
+class GeometryProgramQueryCase : public TestCase
+{
+public:
+	struct ProgramCase
+	{
+		const char*	description;
+		const char*	header;
+		int			value;
+	};
+
+						GeometryProgramQueryCase			(Context& context, const char* name, const char* description, glw::GLenum target);
+
+	void				init								(void);
+	IterateResult		iterate								(void);
+
+private:
+	void				expectProgramValue					(deUint32 program, int value);
+	void				expectQueryError					(deUint32 program);
+
+	const glw::GLenum	m_target;
+
+protected:
+	std::vector<ProgramCase> m_cases;
+};
+
+GeometryProgramQueryCase::GeometryProgramQueryCase (Context& context, const char* name, const char* description, glw::GLenum target)
+	: TestCase	(context, name, description)
+	, m_target	(target)
+{
+}
+
+void GeometryProgramQueryCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+}
+
+GeometryProgramQueryCase::IterateResult GeometryProgramQueryCase::iterate (void)
+{
+	static const char* const s_vertexSource =			"#version 310 es\n"
+														"void main ()\n"
+														"{\n"
+														"	gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+														"}\n";
+	static const char* const s_fragmentSource =			"#version 310 es\n"
+														"layout(location = 0) out mediump vec4 fragColor;\n"
+														"void main ()\n"
+														"{\n"
+														"	fragColor = vec4(0.0, 0.0, 0.0, 0.0);\n"
+														"}\n";
+	static const char* const s_geometryBody =			"void main ()\n"
+														"{\n"
+														"	gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+														"	EmitVertex();\n"
+														"}\n";
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// default cases
+	for (int ndx = 0; ndx < (int)m_cases.size(); ++ndx)
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "Case", m_cases[ndx].description);
+		const std::string			geometrySource	= m_cases[ndx].header + std::string(s_geometryBody);
+		const glu::ShaderProgram	program			(m_context.getRenderContext(),
+													glu::ProgramSources()
+														<< glu::VertexSource(s_vertexSource)
+														<< glu::FragmentSource(s_fragmentSource)
+														<< glu::GeometrySource(geometrySource));
+
+		m_testCtx.getLog() << program;
+		expectProgramValue(program.getProgram(), m_cases[ndx].value);
+	}
+
+	// no geometry shader -case (INVALID OP)
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "NoGeometryShader", "No geometry shader");
+		const glu::ShaderProgram	program			(m_context.getRenderContext(),
+													glu::ProgramSources()
+														<< glu::VertexSource(s_vertexSource)
+														<< glu::FragmentSource(s_fragmentSource));
+
+		m_testCtx.getLog() << program;
+		expectQueryError(program.getProgram());
+	}
+
+	// not linked -case (INVALID OP)
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "NotLinkedProgram", "Shader program not linked");
+		const std::string			geometrySource	= "#version 310 es\nlayout (triangles) in;\n" + std::string(s_geometryBody);
+
+		glu::Shader					vertexShader	(m_context.getRenderContext(), glu::SHADERTYPE_VERTEX);
+		glu::Shader					fragmentShader	(m_context.getRenderContext(), glu::SHADERTYPE_FRAGMENT);
+		glu::Shader					geometryShader	(m_context.getRenderContext(), glu::SHADERTYPE_GEOMETRY);
+		glu::Program				program			(m_context.getRenderContext());
+
+		const char* const			geometrySourceArray[1] = { geometrySource.c_str() };
+
+		vertexShader.setSources(1, &s_vertexSource, DE_NULL);
+		fragmentShader.setSources(1, &s_fragmentSource, DE_NULL);
+		geometryShader.setSources(1, geometrySourceArray, DE_NULL);
+
+		vertexShader.compile();
+		fragmentShader.compile();
+		geometryShader.compile();
+
+		if (!vertexShader.getCompileStatus()   ||
+			!fragmentShader.getCompileStatus() ||
+			!geometryShader.getCompileStatus())
+			throw tcu::TestError("Failed to compile shader");
+
+		program.attachShader(vertexShader.getShader());
+		program.attachShader(fragmentShader.getShader());
+		program.attachShader(geometryShader.getShader());
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Creating a program with geometry shader, but not linking it" << tcu::TestLog::EndMessage;
+
+		expectQueryError(program.getProgram());
+	}
+
+	return STOP;
+}
+
+void GeometryProgramQueryCase::expectProgramValue (deUint32 program, int value)
+{
+	const glw::Functions&										gl		= m_context.getRenderContext().getFunctions();
+	gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint>	state;
+
+	gl.getProgramiv(program, m_target, &state);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "getProgramiv");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << glu::getProgramParamStr(m_target) << " = " << state << tcu::TestLog::EndMessage;
+
+	if (state != value)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << value << ", got " << state << tcu::TestLog::EndMessage;
+
+		// don't overwrite error
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
+	}
+}
+
+void GeometryProgramQueryCase::expectQueryError (deUint32 program)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	glw::GLint				dummy;
+	glw::GLenum				errorCode;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Querying " << glu::getProgramParamStr(m_target) << ", expecting INVALID_OPERATION" << tcu::TestLog::EndMessage;
+	gl.getProgramiv(program, m_target, &dummy);
+
+	errorCode = gl.getError();
+
+	if (errorCode != GL_INVALID_OPERATION)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected INVALID_OPERATION, got " << glu::getErrorStr(errorCode) << tcu::TestLog::EndMessage;
+
+		// don't overwrite error
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected error code");
+	}
+}
+
+class GeometryShaderInvocationsQueryCase : public GeometryProgramQueryCase
+{
+public:
+	GeometryShaderInvocationsQueryCase(Context& context, const char* name, const char* description);
+};
+
+GeometryShaderInvocationsQueryCase::GeometryShaderInvocationsQueryCase(Context& context, const char* name, const char* description)
+	: GeometryProgramQueryCase(context, name, description, GL_GEOMETRY_SHADER_INVOCATIONS)
+{
+	// 2 normal cases
+	m_cases.resize(2);
+
+	m_cases[0].description	= "Default value";
+	m_cases[0].header		= "#version 310 es\n#extension GL_EXT_geometry_shader : require\nlayout (triangles) in;\nlayout (points, max_vertices = 3) out;\n";
+	m_cases[0].value		= 1;
+
+	m_cases[1].description	= "Value declared";
+	m_cases[1].header		= "#version 310 es\n#extension GL_EXT_geometry_shader : require\nlayout (triangles, invocations=2) in;\nlayout (points, max_vertices = 3) out;\n";
+	m_cases[1].value		= 2;
+}
+
+class GeometryShaderVerticesQueryCase : public GeometryProgramQueryCase
+{
+public:
+	GeometryShaderVerticesQueryCase(Context& context, const char* name, const char* description);
+};
+
+GeometryShaderVerticesQueryCase::GeometryShaderVerticesQueryCase(Context& context, const char* name, const char* description)
+	: GeometryProgramQueryCase(context, name, description, GL_GEOMETRY_LINKED_VERTICES_OUT_EXT)
+{
+	m_cases.resize(1);
+
+	m_cases[0].description	= "max_vertices = 1";
+	m_cases[0].header		= "#version 310 es\n#extension GL_EXT_geometry_shader : require\nlayout (triangles) in;\nlayout (points, max_vertices = 1) out;\n";
+	m_cases[0].value		= 1;
+}
+
+class GeometryShaderInputQueryCase : public GeometryProgramQueryCase
+{
+public:
+	GeometryShaderInputQueryCase(Context& context, const char* name, const char* description);
+};
+
+GeometryShaderInputQueryCase::GeometryShaderInputQueryCase(Context& context, const char* name, const char* description)
+	: GeometryProgramQueryCase(context, name, description, GL_GEOMETRY_LINKED_INPUT_TYPE_EXT)
+{
+	m_cases.resize(3);
+
+	m_cases[0].description	= "Triangles";
+	m_cases[0].header		= "#version 310 es\n#extension GL_EXT_geometry_shader : require\nlayout (triangles) in;\nlayout (points, max_vertices = 3) out;\n";
+	m_cases[0].value		= GL_TRIANGLES;
+
+	m_cases[1].description	= "Lines";
+	m_cases[1].header		= "#version 310 es\n#extension GL_EXT_geometry_shader : require\nlayout (lines) in;\nlayout (points, max_vertices = 3) out;\n";
+	m_cases[1].value		= GL_LINES;
+
+	m_cases[2].description	= "Points";
+	m_cases[2].header		= "#version 310 es\n#extension GL_EXT_geometry_shader : require\nlayout (points) in;\nlayout (points, max_vertices = 3) out;\n";
+	m_cases[2].value		= GL_POINTS;
+}
+
+class GeometryShaderOutputQueryCase : public GeometryProgramQueryCase
+{
+public:
+	GeometryShaderOutputQueryCase(Context& context, const char* name, const char* description);
+};
+
+GeometryShaderOutputQueryCase::GeometryShaderOutputQueryCase(Context& context, const char* name, const char* description)
+	: GeometryProgramQueryCase(context, name, description, GL_GEOMETRY_LINKED_OUTPUT_TYPE_EXT)
+{
+	m_cases.resize(3);
+
+	m_cases[0].description	= "Triangle strip";
+	m_cases[0].header		= "#version 310 es\n#extension GL_EXT_geometry_shader : require\nlayout (triangles) in;\nlayout (triangle_strip, max_vertices = 3) out;\n";
+	m_cases[0].value		= GL_TRIANGLE_STRIP;
+
+	m_cases[1].description	= "Lines";
+	m_cases[1].header		= "#version 310 es\n#extension GL_EXT_geometry_shader : require\nlayout (triangles) in;\nlayout (line_strip, max_vertices = 3) out;\n";
+	m_cases[1].value		= GL_LINE_STRIP;
+
+	m_cases[2].description	= "Points";
+	m_cases[2].header		= "#version 310 es\n#extension GL_EXT_geometry_shader : require\nlayout (triangles) in;\nlayout (points, max_vertices = 3) out;\n";
+	m_cases[2].value		= GL_POINTS;
+}
+
+class ImplementationLimitCase : public TestCase
+{
+public:
+						ImplementationLimitCase	(Context& context, const char* name, const char* description, glw::GLenum target, int minValue);
+
+	void				init					(void);
+	IterateResult		iterate					(void);
+
+	const glw::GLenum	m_target;
+	const int			m_minValue;
+};
+
+ImplementationLimitCase::ImplementationLimitCase (Context& context, const char* name, const char* description, glw::GLenum target, int minValue)
+	: TestCase		(context, name, description)
+	, m_target		(target)
+	, m_minValue	(minValue)
+{
+}
+
+void ImplementationLimitCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+}
+
+ImplementationLimitCase::IterateResult ImplementationLimitCase::iterate (void)
+{
+	gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint>	state;
+	glu::CallLogWrapper											gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	gl.enableLogging(true);
+	gl.glGetIntegerv(m_target, &state);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "getIntegerv()");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << glu::getGettableStateStr(m_target) << " = " << state << tcu::TestLog::EndMessage;
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	if (state.verifyValidity(m_testCtx) && state < m_minValue)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Minimum value = " << m_minValue << ", got " << state << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got a value less than minimum value");
+	}
+
+	return STOP;
+}
+
+class LayerProvokingVertexQueryCase : public TestCase
+{
+public:
+					LayerProvokingVertexQueryCase	(Context& context, const char* name, const char* description);
+
+	void			init							(void);
+	IterateResult	iterate							(void);
+};
+
+LayerProvokingVertexQueryCase::LayerProvokingVertexQueryCase(Context& context, const char* name, const char* description)
+	: TestCase(context, name, description)
+{
+}
+
+void LayerProvokingVertexQueryCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+}
+
+LayerProvokingVertexQueryCase::IterateResult LayerProvokingVertexQueryCase::iterate (void)
+{
+	gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint>	state;
+	glu::CallLogWrapper											gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	gl.enableLogging(true);
+	gl.glGetIntegerv(GL_LAYER_PROVOKING_VERTEX, &state);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "getIntegerv(LAYER_PROVOKING_VERTEX)");
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	if (state.verifyValidity(m_testCtx))
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "LAYER_PROVOKING_VERTEX = " << glu::getProvokingVertexStr(state) << tcu::TestLog::EndMessage;
+
+		if (state != GL_FIRST_VERTEX_CONVENTION &&
+			state != GL_LAST_VERTEX_CONVENTION &&
+			state != GL_UNDEFINED_VERTEX)
+		{
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "getInteger(GL_LAYER_PROVOKING_VERTEX) returned illegal value. Got "
+				<< state << "\n"
+				<< "Expected any of {FIRST_VERTEX_CONVENTION, LAST_VERTEX_CONVENTION, UNDEFINED_VERTEX}."
+				<< tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected provoking vertex value");
+		}
+	}
+
+	return STOP;
+}
+
+class GeometryInvocationCase : public GeometryShaderRenderTest
+{
+public:
+	enum OutputCase
+	{
+		CASE_FIXED_OUTPUT_COUNTS = 0,
+		CASE_DIFFERENT_OUTPUT_COUNTS,
+
+		CASE_LAST
+	};
+
+								GeometryInvocationCase	(Context& context, const char* name, const char* description, int numInvocations, OutputCase testCase);
+								~GeometryInvocationCase	(void);
+
+	void						init					(void);
+	void						deinit					(void);
+
+private:
+	sglr::ShaderProgram&		getProgram				(void);
+	void						genVertexAttribData		(void);
+
+	static InvocationCountShader::OutputCase mapToShaderCaseType (OutputCase testCase);
+
+	const OutputCase			m_testCase;
+	int							m_numInvocations;
+	InvocationCountShader*		m_program;
+};
+
+GeometryInvocationCase::GeometryInvocationCase (Context& context, const char* name, const char* description, int numInvocations, OutputCase testCase)
+	: GeometryShaderRenderTest	(context, name, description, GL_POINTS, GL_TRIANGLE_STRIP, "a_color")
+	, m_testCase				(testCase)
+	, m_numInvocations			(numInvocations)
+	, m_program					(DE_NULL)
+{
+	DE_ASSERT(m_testCase < CASE_LAST);
+}
+
+GeometryInvocationCase::~GeometryInvocationCase	(void)
+{
+	deinit();
+}
+
+void GeometryInvocationCase::init (void)
+{
+	const glw::Functions&	gl								= m_context.getRenderContext().getFunctions();
+	int						maxGeometryShaderInvocations	= 0;
+	int						maxComponents					= 0;
+
+	// requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+
+	gl.getIntegerv(GL_MAX_GEOMETRY_SHADER_INVOCATIONS, &maxGeometryShaderInvocations);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "getIntegerv(GL_MAX_GEOMETRY_SHADER_INVOCATIONS)");
+
+	gl.getIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, &maxComponents);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "getIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS)");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_GEOMETRY_SHADER_INVOCATIONS = " << maxGeometryShaderInvocations << tcu::TestLog::EndMessage;
+
+	// set target num invocations
+
+	if (m_numInvocations == -1)
+		m_numInvocations = maxGeometryShaderInvocations;
+	else if (maxGeometryShaderInvocations < m_numInvocations)
+		throw tcu::NotSupportedError("Test requires larger GL_MAX_GEOMETRY_SHADER_INVOCATIONS");
+
+	if (m_testCase == CASE_DIFFERENT_OUTPUT_COUNTS)
+	{
+		const int maxEmitCount	= m_numInvocations + 2;
+		const int numComponents	= 8; // pos + color
+		if (maxEmitCount * numComponents > maxComponents)
+			throw tcu::NotSupportedError("Test requires larger GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS");
+	}
+
+	// Log what the test tries to do
+
+	if (m_testCase == CASE_FIXED_OUTPUT_COUNTS)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Rendering triangles in a partial circle formation with a geometry shader. Each triangle is generated by a separate invocation.\n"
+			<< "Drawing 2 points, each generating " << m_numInvocations << " triangles."
+			<< tcu::TestLog::EndMessage;
+	}
+	else if (m_testCase == CASE_DIFFERENT_OUTPUT_COUNTS)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Rendering n-gons in a partial circle formation with a geometry shader. Each n-gon is generated by a separate invocation.\n"
+			<< "Drawing 2 points, each generating " << m_numInvocations << " n-gons."
+			<< tcu::TestLog::EndMessage;
+	}
+	else
+		DE_ASSERT(false);
+
+	// resources
+
+	m_program = new InvocationCountShader(m_numInvocations, mapToShaderCaseType(m_testCase));
+
+	GeometryShaderRenderTest::init();
+}
+
+void GeometryInvocationCase::deinit (void)
+{
+	if (m_program)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+	}
+
+	GeometryShaderRenderTest::deinit();
+}
+
+sglr::ShaderProgram& GeometryInvocationCase::getProgram (void)
+{
+	return *m_program;
+}
+
+void GeometryInvocationCase::genVertexAttribData (void)
+{
+	m_vertexPosData.resize(2);
+	m_vertexPosData[0] = tcu::Vec4(0.0f,-0.3f, 0.0f, 1.0f);
+	m_vertexPosData[1] = tcu::Vec4(0.2f, 0.3f, 0.0f, 1.0f);
+
+	m_vertexAttrData.resize(2);
+	m_vertexAttrData[0] = tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);
+	m_vertexAttrData[1] = tcu::Vec4(0.8f, 0.8f, 0.8f, 1.0f);
+	m_numDrawVertices = 2;
+}
+
+InvocationCountShader::OutputCase GeometryInvocationCase::mapToShaderCaseType (OutputCase testCase)
+{
+	switch (testCase)
+	{
+		case CASE_FIXED_OUTPUT_COUNTS:			return InvocationCountShader::CASE_FIXED_OUTPUT_COUNTS;
+		case CASE_DIFFERENT_OUTPUT_COUNTS:		return InvocationCountShader::CASE_DIFFERENT_OUTPUT_COUNTS;
+		default:
+			DE_ASSERT(false);
+			return InvocationCountShader::CASE_LAST;
+	}
+}
+
+class DrawInstancedGeometryInstancedCase : public GeometryShaderRenderTest
+{
+public:
+								DrawInstancedGeometryInstancedCase	(Context& context, const char* name, const char* description, int numInstances, int numInvocations);
+								~DrawInstancedGeometryInstancedCase	(void);
+
+private:
+	void						init								(void);
+	sglr::ShaderProgram&		getProgram							(void);
+	void						genVertexAttribData					(void);
+
+	const int					m_numInstances;
+	const int					m_numInvocations;
+	InstancedExpansionShader	m_program;
+};
+
+DrawInstancedGeometryInstancedCase::DrawInstancedGeometryInstancedCase (Context& context, const char* name, const char* description, int numInstances, int numInvocations)
+	: GeometryShaderRenderTest	(context, name, description, GL_POINTS, GL_TRIANGLE_STRIP, "a_offset", FLAG_DRAW_INSTANCED)
+	, m_numInstances			(numInstances)
+	, m_numInvocations			(numInvocations)
+	, m_program					(numInvocations)
+{
+}
+
+DrawInstancedGeometryInstancedCase::~DrawInstancedGeometryInstancedCase (void)
+{
+}
+
+void DrawInstancedGeometryInstancedCase::init (void)
+{
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Rendering a single point with " << m_numInstances << " instances. "
+		<< "Each geometry shader is invoked " << m_numInvocations << " times for each primitive. "
+		<< tcu::TestLog::EndMessage;
+
+	GeometryShaderRenderTest::init();
+}
+
+sglr::ShaderProgram& DrawInstancedGeometryInstancedCase::getProgram (void)
+{
+	return m_program;
+}
+
+void DrawInstancedGeometryInstancedCase::genVertexAttribData (void)
+{
+	m_numDrawVertices = 1;
+	m_numDrawInstances = m_numInstances;
+	m_vertexAttrDivisor = 1;
+
+	m_vertexPosData.resize(1);
+	m_vertexAttrData.resize(8);
+
+	m_vertexPosData[0] = tcu::Vec4( 0.0f,  0.0f, 0.0f, 1.0f);
+
+	m_vertexAttrData[0] = tcu::Vec4( 0.5f,  0.0f, 0.0f, 0.0f);
+	m_vertexAttrData[1] = tcu::Vec4( 0.0f,  0.5f, 0.0f, 0.0f);
+	m_vertexAttrData[2] = tcu::Vec4(-0.7f, -0.1f, 0.0f, 0.0f);
+	m_vertexAttrData[3] = tcu::Vec4(-0.1f, -0.7f, 0.0f, 0.0f);
+	m_vertexAttrData[4] = tcu::Vec4(-0.8f, -0.7f, 0.0f, 0.0f);
+	m_vertexAttrData[5] = tcu::Vec4(-0.9f,  0.6f, 0.0f, 0.0f);
+	m_vertexAttrData[6] = tcu::Vec4(-0.8f,  0.3f, 0.0f, 0.0f);
+	m_vertexAttrData[7] = tcu::Vec4(-0.1f,  0.1f, 0.0f, 0.0f);
+
+	DE_ASSERT(m_numInstances <= (int)m_vertexAttrData.size());
+}
+
+class GeometryProgramLimitCase : public TestCase
+{
+public:
+						GeometryProgramLimitCase	(Context& context, const char* name, const char* description, glw::GLenum apiName, const std::string& glslName, int limit);
+
+private:
+	void				init						(void);
+	IterateResult		iterate						(void);
+
+	const glw::GLenum	m_apiName;
+	const std::string	m_glslName;
+	const int			m_limit;
+};
+
+GeometryProgramLimitCase::GeometryProgramLimitCase (Context& context, const char* name, const char* description, glw::GLenum apiName, const std::string& glslName, int limit)
+	: TestCase		(context, name, description)
+	, m_apiName		(apiName)
+	, m_glslName	(glslName)
+	, m_limit		(limit)
+{
+}
+
+void GeometryProgramLimitCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+}
+
+GeometryProgramLimitCase::IterateResult GeometryProgramLimitCase::iterate (void)
+{
+	int limit;
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// query limit
+	{
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint>	state;
+		glu::CallLogWrapper											gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+		gl.enableLogging(true);
+		gl.glGetIntegerv(m_apiName, &state);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "getIntegerv()");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << glu::getGettableStateStr(m_apiName) << " = " << state << tcu::TestLog::EndMessage;
+
+		if (!state.verifyValidity(m_testCtx))
+			return STOP;
+
+		if (state < m_limit)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Minimum value = " << m_limit << ", got " << state << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got a value less than minimum value");
+			return STOP;
+		}
+
+		limit = state;
+	}
+
+	// verify limit is the same in GLSL
+	{
+		static const char* const vertexSource =		"#version 310 es\n"
+													"void main ()\n"
+													"{\n"
+													"	gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\n"
+													"}\n";
+		static const char* const fragmentSource =	"#version 310 es\n"
+													"layout(location = 0) out mediump vec4 fragColor;\n"
+													"void main ()\n"
+													"{\n"
+													"	fragColor = vec4(0.0, 0.0, 0.0, 0.0);\n"
+													"}\n";
+		const std::string geometrySource =			"#version 310 es\n"
+													"#extension GL_EXT_geometry_shader : require\n"
+													"layout(points) in;\n"
+													"layout(points, max_vertices = 1) out;\n"
+													"void main ()\n"
+													"{\n"
+													"	// Building the shader will fail if the constant value is not the expected\n"
+													"	const mediump int cArraySize = (gl_" + m_glslName + " == " + de::toString(limit) + ") ? (1) : (-1);\n"
+													"	float[cArraySize] fArray;\n"
+													"	fArray[0] = 0.0f;\n"
+													"	gl_Position = vec4(0.0, 0.0, 0.0, fArray[0]);\n"
+													"	EmitVertex();\n"
+													"}\n";
+
+		const de::UniquePtr<glu::ShaderProgram> program(new glu::ShaderProgram(m_context.getRenderContext(),
+																			   glu::ProgramSources()
+																				<< glu::VertexSource(vertexSource)
+																				<< glu::FragmentSource(fragmentSource)
+																				<< glu::GeometrySource(geometrySource)));
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Building a test shader to verify GLSL constant " << m_glslName << " value." << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << *program;
+
+		if (!program->isOk())
+		{
+			// compile failed, assume static assert failed
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader build failed");
+			return STOP;
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Build ok" << tcu::TestLog::EndMessage;
+	}
+
+	return STOP;
+}
+
+class PrimitivesGeneratedQueryCase : public TestCase
+{
+public:
+	enum QueryTest
+	{
+		TEST_NO_GEOMETRY			= 0,
+		TEST_NO_AMPLIFICATION,
+		TEST_AMPLIFICATION,
+		TEST_PARTIAL_PRIMITIVES,
+		TEST_INSTANCED,
+
+		TEST_LAST
+	};
+
+						PrimitivesGeneratedQueryCase	(Context& context, const char* name, const char* description, QueryTest test);
+						~PrimitivesGeneratedQueryCase	(void);
+
+private:
+	void				init							(void);
+	void				deinit							(void);
+	IterateResult		iterate							(void);
+
+	glu::ShaderProgram*	genProgram						(void);
+
+	const QueryTest		m_test;
+	glu::ShaderProgram*	m_program;
+};
+
+PrimitivesGeneratedQueryCase::PrimitivesGeneratedQueryCase (Context& context, const char* name, const char* description, QueryTest test)
+	: TestCase	(context, name, description)
+	, m_test	(test)
+	, m_program	(DE_NULL)
+{
+	DE_ASSERT(m_test < TEST_LAST);
+}
+
+PrimitivesGeneratedQueryCase::~PrimitivesGeneratedQueryCase (void)
+{
+	deinit();
+}
+
+void PrimitivesGeneratedQueryCase::init (void)
+{
+	// requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Geometry shader tests require GL_EXT_geometry_shader extension");
+
+	// log what test tries to do
+
+	if (m_test == TEST_NO_GEOMETRY)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Querying PRIMITIVES_GENERATED while rendering without a geometry shader." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_NO_AMPLIFICATION)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Querying PRIMITIVES_GENERATED while rendering with a non-amplifying geometry shader." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_AMPLIFICATION)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Querying PRIMITIVES_GENERATED while rendering with a (3x) amplifying geometry shader." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_PARTIAL_PRIMITIVES)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Querying PRIMITIVES_GENERATED while rendering with a geometry shader that emits also partial primitives." << tcu::TestLog::EndMessage;
+	else if (m_test == TEST_INSTANCED)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Querying PRIMITIVES_GENERATED while rendering with a instanced geometry shader." << tcu::TestLog::EndMessage;
+	else
+		DE_ASSERT(false);
+
+	// resources
+
+	m_program = genProgram();
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build program");
+}
+
+void PrimitivesGeneratedQueryCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+PrimitivesGeneratedQueryCase::IterateResult PrimitivesGeneratedQueryCase::iterate (void)
+{
+	glw::GLuint primitivesGenerated = 0xDEBADBAD;
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Drawing 8 points, setting a_one for each to value (1.0, 1.0, 1.0, 1.0)"
+		<< tcu::TestLog::EndMessage;
+
+	{
+		static const tcu::Vec4 vertexData[8*2] =
+		{
+			tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f),	tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+			tcu::Vec4(0.1f, 0.0f, 0.0f, 1.0f),	tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+			tcu::Vec4(0.2f, 0.0f, 0.0f, 1.0f),	tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+			tcu::Vec4(0.3f, 0.0f, 0.0f, 1.0f),	tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+			tcu::Vec4(0.4f, 0.0f, 0.0f, 1.0f),	tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+			tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f),	tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+			tcu::Vec4(0.6f, 0.0f, 0.0f, 1.0f),	tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+			tcu::Vec4(0.7f, 0.0f, 0.0f, 1.0f),	tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+		};
+
+		const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+		const glu::VertexArray	vao					(m_context.getRenderContext());
+		const glu::Buffer		buffer				(m_context.getRenderContext());
+		const glu::Query		query				(m_context.getRenderContext());
+		const int				positionLocation	= gl.getAttribLocation(m_program->getProgram(), "a_position");
+		const int				oneLocation			= gl.getAttribLocation(m_program->getProgram(), "a_one");
+
+		gl.bindVertexArray(*vao);
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, *buffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (int)sizeof(vertexData), vertexData, GL_STATIC_DRAW);
+
+		gl.vertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 2 * sizeof(tcu::Vec4), DE_NULL);
+		gl.enableVertexAttribArray(positionLocation);
+
+		if (oneLocation != -1)
+		{
+			gl.vertexAttribPointer(oneLocation, 4, GL_FLOAT, GL_FALSE, 2 * sizeof(tcu::Vec4), (const tcu::Vec4*)DE_NULL + 1);
+			gl.enableVertexAttribArray(oneLocation);
+		}
+
+		gl.useProgram(m_program->getProgram());
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "setup render");
+
+		gl.beginQuery(GL_PRIMITIVES_GENERATED, *query);
+		gl.drawArrays(GL_POINTS, 0, 8);
+		gl.endQuery(GL_PRIMITIVES_GENERATED);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "render and query");
+
+		gl.getQueryObjectuiv(*query, GL_QUERY_RESULT, &primitivesGenerated);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "get query result");
+	}
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "GL_PRIMITIVES_GENERATED = " << primitivesGenerated
+		<< tcu::TestLog::EndMessage;
+
+	{
+		const deUint32 expectedGenerated = (m_test == TEST_AMPLIFICATION) ? (3*8) : (m_test == TEST_INSTANCED) ? (8*(3+1)) : (8);
+
+		if (expectedGenerated == primitivesGenerated)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong result for GL_PRIMITIVES_GENERATED");
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "Got unexpected result for GL_PRIMITIVES_GENERATED. Expected " << expectedGenerated << ", got " << primitivesGenerated
+				<< tcu::TestLog::EndMessage;
+		}
+	}
+
+	return STOP;
+}
+
+glu::ShaderProgram* PrimitivesGeneratedQueryCase::genProgram (void)
+{
+	static const char* const vertexSource =		"#version 310 es\n"
+												"in highp vec4 a_position;\n"
+												"in highp vec4 a_one;\n"
+												"out highp vec4 v_one;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"	v_one = a_one;\n"
+												"}\n";
+	static const char* const fragmentSource =	"#version 310 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;\n"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+												"}\n";
+	std::ostringstream geometrySource;
+	glu::ProgramSources sources;
+
+	if (m_test != TEST_NO_GEOMETRY)
+	{
+		geometrySource <<	"#version 310 es\n"
+							"#extension GL_EXT_geometry_shader : require\n"
+							"layout(points" << ((m_test == TEST_INSTANCED) ? (", invocations = 3") : ("")) << ") in;\n"
+							"layout(triangle_strip, max_vertices = 7) out;\n"
+							"in highp vec4 v_one[];\n"
+							"void main (void)\n"
+							"{\n"
+							"	// always taken\n"
+							"	if (v_one[0].x != 0.0)\n"
+							"	{\n"
+							"		gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.1, 0.0, 0.0);\n"
+							"		EmitVertex();\n"
+							"		gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);\n"
+							"		EmitVertex();\n"
+							"		gl_Position = gl_in[0].gl_Position - vec4(0.1, 0.0, 0.0, 0.0);\n"
+							"		EmitVertex();\n"
+							"		EndPrimitive();\n"
+							"	}\n";
+
+		if (m_test == TEST_AMPLIFICATION)
+		{
+			geometrySource <<	"\n"
+								"	// always taken\n"
+								"	if (v_one[0].y != 0.0)\n"
+								"	{\n"
+								"		gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.1, 0.0, 0.0);\n"
+								"		EmitVertex();\n"
+								"		gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);\n"
+								"		EmitVertex();\n"
+								"		gl_Position = gl_in[0].gl_Position - vec4(0.0, 0.1, 0.0, 0.0);\n"
+								"		EmitVertex();\n"
+								"		gl_Position = gl_in[0].gl_Position - vec4(0.1, 0.0, 0.0, 0.0);\n"
+								"		EmitVertex();\n"
+								"	}\n";
+		}
+		else if (m_test == TEST_PARTIAL_PRIMITIVES)
+		{
+			geometrySource <<	"\n"
+								"	// always taken\n"
+								"	if (v_one[0].y != 0.0)\n"
+								"	{\n"
+								"		gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.1, 0.0, 0.0);\n"
+								"		EmitVertex();\n"
+								"		gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);\n"
+								"		EmitVertex();\n"
+								"\n"
+								"		// never taken\n"
+								"		if (v_one[0].z < 0.0)\n"
+								"		{\n"
+								"			gl_Position = gl_in[0].gl_Position - vec4(0.1, 0.0, 0.0, 0.0);\n"
+								"			EmitVertex();\n"
+								"		}\n"
+								"	}\n";
+		}
+		else if (m_test == TEST_INSTANCED)
+		{
+			geometrySource <<	"\n"
+								"	// taken once\n"
+								"	if (v_one[0].y > float(gl_InvocationID) + 0.5)\n"
+								"	{\n"
+								"		gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.1, 0.0, 0.0);\n"
+								"		EmitVertex();\n"
+								"		gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);\n"
+								"		EmitVertex();\n"
+								"		gl_Position = gl_in[0].gl_Position - vec4(0.1, 0.0, 0.0, 0.0);\n"
+								"		EmitVertex();\n"
+								"	}\n";
+		}
+
+		geometrySource <<	"}\n";
+	}
+
+	sources << glu::VertexSource(vertexSource);
+	sources << glu::FragmentSource(fragmentSource);
+
+	if (!geometrySource.str().empty())
+		sources << glu::GeometrySource(geometrySource.str());
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), sources);
+}
+
+class GeometryShaderFeartureTestCase : public TestCase
+{
+public:
+					GeometryShaderFeartureTestCase	(Context& context, const char* name, const char* description);
+
+	void			init							(void);
+};
+
+GeometryShaderFeartureTestCase::GeometryShaderFeartureTestCase (Context& context, const char* name, const char* description)
+	: TestCase(context, name, description)
+{
+}
+
+void GeometryShaderFeartureTestCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("test requires GL_EXT_geometry_shader extension");
+}
+
+class FramebufferDefaultLayersCase : public GeometryShaderFeartureTestCase
+{
+public:
+					FramebufferDefaultLayersCase	(Context& context, const char* name, const char* description);
+	IterateResult	iterate							(void);
+};
+
+FramebufferDefaultLayersCase::FramebufferDefaultLayersCase (Context& context, const char* name, const char* description)
+	: GeometryShaderFeartureTestCase(context, name, description)
+{
+}
+
+FramebufferDefaultLayersCase::IterateResult FramebufferDefaultLayersCase::iterate (void)
+{
+	glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	gl.enableLogging(true);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "Default", "Default value");
+		const glu::Framebuffer		fbo				(m_context.getRenderContext());
+		glw::GLint					defaultLayers	= -1;
+
+		gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, *fbo);
+		gl.glGetFramebufferParameteriv(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_LAYERS, &defaultLayers);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "getFramebufferParameteriv");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_FRAMEBUFFER_DEFAULT_LAYERS = " << defaultLayers << tcu::TestLog::EndMessage;
+
+		if (defaultLayers != 0)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected 0, got " << defaultLayers << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid layer count");
+		}
+	}
+
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "SetTo12", "Set default layers to 12");
+		const glu::Framebuffer		fbo				(m_context.getRenderContext());
+		glw::GLint					defaultLayers	= -1;
+
+		gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, *fbo);
+		gl.glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_LAYERS, 12);
+		gl.glGetFramebufferParameteriv(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_LAYERS, &defaultLayers);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "getFramebufferParameteriv");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_FRAMEBUFFER_DEFAULT_LAYERS = " << defaultLayers << tcu::TestLog::EndMessage;
+
+		if (defaultLayers != 12)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected 12, got " << defaultLayers << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid layer count");
+		}
+	}
+
+	return STOP;
+}
+
+class FramebufferAttachmentLayeredCase : public GeometryShaderFeartureTestCase
+{
+public:
+					FramebufferAttachmentLayeredCase	(Context& context, const char* name, const char* description);
+	IterateResult	iterate								(void);
+};
+
+FramebufferAttachmentLayeredCase::FramebufferAttachmentLayeredCase (Context& context, const char* name, const char* description)
+	: GeometryShaderFeartureTestCase(context, name, description)
+{
+}
+
+FramebufferAttachmentLayeredCase::IterateResult FramebufferAttachmentLayeredCase::iterate (void)
+{
+	enum CaseType
+	{
+		TEXTURE_3D,
+		TEXTURE_2D_ARRAY,
+		TEXTURE_CUBE,
+		TEXTURE_2D_MS_ARRAY,
+		TEXTURE_3D_LAYER,
+		TEXTURE_2D_ARRAY_LAYER,
+	};
+
+	static const struct TextureType
+	{
+		const char*	name;
+		const char*	description;
+		bool		layered;
+		CaseType	type;
+	} textureTypes[] =
+	{
+		{ "3D",				"3D texture",			true,	TEXTURE_3D				},
+		{ "2DArray",		"2D array",				true,	TEXTURE_2D_ARRAY		},
+		{ "Cube",			"Cube map",				true,	TEXTURE_CUBE			},
+		{ "2DMSArray",		"2D multisample array",	true,	TEXTURE_2D_MS_ARRAY		},
+		{ "3DLayer",		"3D texture layer ",	false,	TEXTURE_3D_LAYER		},
+		{ "2DArrayLayer",	"2D array layer ",		false,	TEXTURE_2D_ARRAY_LAYER	},
+	};
+
+	glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	gl.enableLogging(true);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(textureTypes); ++ndx)
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), textureTypes[ndx].name, textureTypes[ndx].description);
+		const glu::Framebuffer		fbo				(m_context.getRenderContext());
+		const glu::Texture			texture			(m_context.getRenderContext());
+		glw::GLint					layered			= -1;
+
+		gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, *fbo);
+
+		if (textureTypes[ndx].type == TEXTURE_3D || textureTypes[ndx].type == TEXTURE_3D_LAYER)
+		{
+			gl.glBindTexture(GL_TEXTURE_3D, *texture);
+			gl.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 32, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+			gl.glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+			gl.glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+			if (textureTypes[ndx].type == TEXTURE_3D)
+				gl.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *texture, 0);
+			else
+				gl.glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *texture, 0, 2);
+		}
+		else if (textureTypes[ndx].type == TEXTURE_2D_ARRAY || textureTypes[ndx].type == TEXTURE_2D_ARRAY_LAYER)
+		{
+			gl.glBindTexture(GL_TEXTURE_2D_ARRAY, *texture);
+			gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 32, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+			gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+			gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+			if (textureTypes[ndx].type == TEXTURE_2D_ARRAY)
+				gl.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *texture, 0);
+			else
+				gl.glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *texture, 0, 3);
+		}
+		else if (textureTypes[ndx].type == TEXTURE_CUBE)
+		{
+			gl.glBindTexture(GL_TEXTURE_CUBE_MAP, *texture);
+			for (int face = 0; face < 6; ++face)
+				gl.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+			gl.glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+			gl.glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+			gl.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *texture, 0);
+		}
+		else if (textureTypes[ndx].type == TEXTURE_2D_MS_ARRAY)
+		{
+			// check extension
+			if (!m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "GL_OES_texture_storage_multisample_2d_array not supported, skipping." << tcu::TestLog::EndMessage;
+				continue;
+			}
+
+			gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, *texture);
+			gl.glTexStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, 1, GL_RGBA8, 32, 32, 32, GL_FALSE);
+			gl.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *texture, 0);
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup attachment");
+
+		gl.glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_LAYERED, &layered);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "getFramebufferParameteriv");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_FRAMEBUFFER_ATTACHMENT_LAYERED = " << glu::getBooleanStr(layered) << tcu::TestLog::EndMessage;
+
+		if (layered != GL_TRUE && layered != GL_FALSE)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected boolean, got " << layered << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid boolean");
+		}
+		else if ((layered == GL_TRUE) != textureTypes[ndx].layered)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected " << ((textureTypes[ndx].layered) ? ("GL_TRUE") : ("GL_FALSE")) << ", got " << glu::getBooleanStr(layered) << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid layer count");
+		}
+	}
+
+	return STOP;
+}
+
+class FramebufferIncompleteLayereTargetsCase : public GeometryShaderFeartureTestCase
+{
+public:
+					FramebufferIncompleteLayereTargetsCase	(Context& context, const char* name, const char* description);
+	IterateResult	iterate									(void);
+};
+
+FramebufferIncompleteLayereTargetsCase::FramebufferIncompleteLayereTargetsCase (Context& context, const char* name, const char* description)
+	: GeometryShaderFeartureTestCase(context, name, description)
+{
+}
+
+FramebufferIncompleteLayereTargetsCase::IterateResult FramebufferIncompleteLayereTargetsCase::iterate (void)
+{
+	glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	gl.enableLogging(true);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "LayerAndNonLayer", "Layered and non-layered");
+		const glu::Framebuffer		fbo				(m_context.getRenderContext());
+		const glu::Texture			texture0		(m_context.getRenderContext());
+		const glu::Texture			texture1		(m_context.getRenderContext());
+
+		glw::GLint					fboStatus;
+
+		gl.glBindTexture(GL_TEXTURE_2D_ARRAY, *texture0);
+		gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 32, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+		gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+		gl.glBindTexture(GL_TEXTURE_2D_ARRAY, *texture1);
+		gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 32, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+		gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+		gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, *fbo);
+		gl.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *texture0, 0);
+		gl.glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, *texture1, 0, 0);
+
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup fbo");
+
+		fboStatus = gl.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
+		m_testCtx.getLog() << tcu::TestLog::Message << "Framebuffer status: " << glu::getFramebufferStatusStr(fboStatus) << tcu::TestLog::EndMessage;
+
+		if (fboStatus != GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS, got " << glu::getFramebufferStatusStr(fboStatus) << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid layer count");
+		}
+	}
+
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "DifferentTarget", "Different target");
+		const glu::Framebuffer		fbo				(m_context.getRenderContext());
+		const glu::Texture			texture0		(m_context.getRenderContext());
+		const glu::Texture			texture1		(m_context.getRenderContext());
+
+		glw::GLint					fboStatus;
+
+		gl.glBindTexture(GL_TEXTURE_2D_ARRAY, *texture0);
+		gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 32, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+		gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+		gl.glBindTexture(GL_TEXTURE_3D, *texture1);
+		gl.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 32, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
+		gl.glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		gl.glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+		gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, *fbo);
+		gl.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *texture0, 0);
+		gl.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, *texture1, 0);
+
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup fbo");
+
+		fboStatus = gl.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
+		m_testCtx.getLog() << tcu::TestLog::Message << "Framebuffer status: " << glu::getFramebufferStatusStr(fboStatus) << tcu::TestLog::EndMessage;
+
+		if (fboStatus != GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS, got " << glu::getFramebufferStatusStr(fboStatus) << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid layer count");
+		}
+	}
+
+	return STOP;
+}
+
+class ReferencedByGeometryShaderCase : public GeometryShaderFeartureTestCase
+{
+public:
+					ReferencedByGeometryShaderCase	(Context& context, const char* name, const char* description);
+	IterateResult	iterate							(void);
+};
+
+ReferencedByGeometryShaderCase::ReferencedByGeometryShaderCase (Context& context, const char* name, const char* description)
+	: GeometryShaderFeartureTestCase(context, name, description)
+{
+}
+
+ReferencedByGeometryShaderCase::IterateResult ReferencedByGeometryShaderCase::iterate (void)
+{
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	{
+		static const char* const vertexSource =		"#version 310 es\n"
+													"uniform highp vec4 u_position;\n"
+													"void main (void)\n"
+													"{\n"
+													"	gl_Position = u_position;\n"
+													"}\n";
+		static const char* const fragmentSource =	"#version 310 es\n"
+													"layout(location = 0) out mediump vec4 fragColor;\n"
+													"void main (void)\n"
+													"{\n"
+													"	fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+													"}\n";
+		static const char* const geometrySource =	"#version 310 es\n"
+													"#extension GL_EXT_geometry_shader : require\n"
+													"layout(points) in;\n"
+													"layout(points, max_vertices=1) out;\n"
+													"uniform highp vec4 u_offset;\n"
+													"void main (void)\n"
+													"{\n"
+													"	gl_Position = gl_in[0].gl_Position + u_offset;\n"
+													"	EmitVertex();\n"
+													"}\n";
+
+		const glu::ShaderProgram program(m_context.getRenderContext(), glu::ProgramSources()
+																		<< glu::VertexSource(vertexSource)
+																		<< glu::FragmentSource(fragmentSource)
+																		<< glu::GeometrySource(geometrySource));
+		m_testCtx.getLog() << program;
+
+		{
+			const tcu::ScopedLogSection section		(m_testCtx.getLog(), "UnreferencedUniform", "Unreferenced uniform u_position");
+			glu::CallLogWrapper			gl			(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+			const deUint32				props[1]	= { GL_REFERENCED_BY_GEOMETRY_SHADER };
+			deUint32					resourcePos;
+			glw::GLsizei				length		= 0;
+			glw::GLint					referenced	= 0;
+
+			gl.enableLogging(true);
+
+			resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_position");
+			m_testCtx.getLog() << tcu::TestLog::Message << "u_position resource index: " << resourcePos << tcu::TestLog::EndMessage;
+
+			gl.glGetProgramResourceiv(program.getProgram(), GL_UNIFORM, resourcePos, 1, props, 1, &length, &referenced);
+			m_testCtx.getLog() << tcu::TestLog::Message << "Query GL_REFERENCED_BY_GEOMETRY_SHADER, got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced) << tcu::TestLog::EndMessage;
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "query resource");
+
+			if (length == 0 || referenced != GL_FALSE)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected GL_FALSE." << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected value");
+			}
+		}
+
+		{
+			const tcu::ScopedLogSection section		(m_testCtx.getLog(), "ReferencedUniform", "Referenced uniform u_offset");
+			glu::CallLogWrapper			gl			(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+			const deUint32				props[1]	= { GL_REFERENCED_BY_GEOMETRY_SHADER };
+			deUint32					resourcePos;
+			glw::GLsizei				length		= 0;
+			glw::GLint					referenced	= 0;
+
+			gl.enableLogging(true);
+
+			resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_offset");
+			m_testCtx.getLog() << tcu::TestLog::Message << "u_offset resource index: " << resourcePos << tcu::TestLog::EndMessage;
+
+			gl.glGetProgramResourceiv(program.getProgram(), GL_UNIFORM, resourcePos, 1, props, 1, &length, &referenced);
+			m_testCtx.getLog() << tcu::TestLog::Message << "Query GL_REFERENCED_BY_GEOMETRY_SHADER, got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced) << tcu::TestLog::EndMessage;
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "query resource");
+
+			if (length == 0 || referenced != GL_TRUE)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected GL_TRUE." << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected value");
+			}
+		}
+	}
+
+	return STOP;
+}
+
+class VertexFeedbackCase : public TestCase
+{
+public:
+	enum DrawMethod
+	{
+		METHOD_DRAW_ARRAYS = 0,
+		METHOD_DRAW_ARRAYS_INSTANCED,
+		METHOD_DRAW_ARRAYS_INDIRECT,
+		METHOD_DRAW_ELEMENTS,
+		METHOD_DRAW_ELEMENTS_INSTANCED,
+		METHOD_DRAW_ELEMENTS_INDIRECT,
+
+		METHOD_LAST
+	};
+	enum PrimitiveType
+	{
+		PRIMITIVE_LINE_LOOP = 0,
+		PRIMITIVE_LINE_STRIP,
+		PRIMITIVE_TRIANGLE_STRIP,
+		PRIMITIVE_TRIANGLE_FAN,
+		PRIMITIVE_POINTS,
+
+		PRIMITIVE_LAST
+	};
+
+						VertexFeedbackCase	(Context& context, const char* name, const char* description, DrawMethod method, PrimitiveType output);
+						~VertexFeedbackCase	(void);
+private:
+	void				init				(void);
+	void				deinit				(void);
+	IterateResult		iterate				(void);
+
+	glu::ShaderProgram*	genProgram			(void);
+	deUint32			getOutputPrimitive	(void);
+	deUint32			getBasePrimitive	(void);
+
+	const DrawMethod	m_method;
+	const PrimitiveType	m_output;
+
+	deUint32			m_elementBuf;
+	deUint32			m_arrayBuf;
+	deUint32			m_offsetBuf;
+	deUint32			m_feedbackBuf;
+	deUint32			m_indirectBuffer;
+	glu::ShaderProgram*	m_program;
+	glu::VertexArray*	m_vao;
+};
+
+VertexFeedbackCase::VertexFeedbackCase (Context& context, const char* name, const char* description, DrawMethod method, PrimitiveType output)
+	: TestCase			(context, name, description)
+	, m_method			(method)
+	, m_output			(output)
+	, m_elementBuf		(0)
+	, m_arrayBuf		(0)
+	, m_offsetBuf		(0)
+	, m_feedbackBuf		(0)
+	, m_indirectBuffer	(0)
+	, m_program			(DE_NULL)
+	, m_vao				(DE_NULL)
+{
+	DE_ASSERT(method < METHOD_LAST);
+	DE_ASSERT(output < PRIMITIVE_LAST);
+}
+
+VertexFeedbackCase::~VertexFeedbackCase (void)
+{
+	deinit();
+}
+
+void VertexFeedbackCase::init (void)
+{
+	// requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("test requires GL_EXT_geometry_shader extension");
+
+	// log what test tries to do
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing GL_EXT_geometry_shader transform feedback relaxations.\n"
+		<< "Capturing vertex shader varying, no geometry shader. Invoke with:"
+		<< tcu::TestLog::EndMessage;
+
+	switch (m_method)
+	{
+		case METHOD_DRAW_ARRAYS:				m_testCtx.getLog() << tcu::TestLog::Message << "Draw method: drawArrays"			<< tcu::TestLog::EndMessage;	break;
+		case METHOD_DRAW_ARRAYS_INSTANCED:		m_testCtx.getLog() << tcu::TestLog::Message << "Draw method: drawArraysInstanced"	<< tcu::TestLog::EndMessage;	break;
+		case METHOD_DRAW_ARRAYS_INDIRECT:		m_testCtx.getLog() << tcu::TestLog::Message << "Draw method: drawArraysIndirect"	<< tcu::TestLog::EndMessage;	break;
+		case METHOD_DRAW_ELEMENTS:				m_testCtx.getLog() << tcu::TestLog::Message << "Draw method: drawElements"			<< tcu::TestLog::EndMessage;	break;
+		case METHOD_DRAW_ELEMENTS_INSTANCED:	m_testCtx.getLog() << tcu::TestLog::Message << "Draw method: drawElementsInstanced" << tcu::TestLog::EndMessage;	break;
+		case METHOD_DRAW_ELEMENTS_INDIRECT:		m_testCtx.getLog() << tcu::TestLog::Message << "Draw method: drawElementsIndirect"	<< tcu::TestLog::EndMessage;	break;
+		default:
+			DE_ASSERT(false);
+	}
+	switch (m_output)
+	{
+		case PRIMITIVE_LINE_LOOP:				m_testCtx.getLog() << tcu::TestLog::Message << "Draw primitive: line loop"			<< tcu::TestLog::EndMessage;	break;
+		case PRIMITIVE_LINE_STRIP:				m_testCtx.getLog() << tcu::TestLog::Message << "Draw primitive: line strip"			<< tcu::TestLog::EndMessage;	break;
+		case PRIMITIVE_TRIANGLE_STRIP:			m_testCtx.getLog() << tcu::TestLog::Message << "Draw primitive: triangle strip"		<< tcu::TestLog::EndMessage;	break;
+		case PRIMITIVE_TRIANGLE_FAN:			m_testCtx.getLog() << tcu::TestLog::Message << "Draw primitive: triangle fan"		<< tcu::TestLog::EndMessage;	break;
+		case PRIMITIVE_POINTS:					m_testCtx.getLog() << tcu::TestLog::Message << "Draw primitive: points"				<< tcu::TestLog::EndMessage;	break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	// resources
+
+	{
+		static const deUint16 elementData[] =
+		{
+			0, 1, 2, 3,
+		};
+		static const tcu::Vec4 arrayData[] =
+		{
+			tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f),
+			tcu::Vec4(1.0f, 0.0f, 0.0f, 0.0f),
+			tcu::Vec4(2.0f, 0.0f, 0.0f, 0.0f),
+			tcu::Vec4(3.0f, 0.0f, 0.0f, 0.0f),
+		};
+		static const tcu::Vec4 offsetData[] =
+		{
+			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),
+		};
+
+		const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+		const int				feedbackSize	= 8 * sizeof(float[4]);
+
+		m_vao = new glu::VertexArray(m_context.getRenderContext());
+		gl.bindVertexArray(**m_vao);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set up vao");
+
+		gl.genBuffers(1, &m_elementBuf);
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementBuf);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elementData), &elementData[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buf");
+
+		gl.genBuffers(1, &m_arrayBuf);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuf);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(arrayData), &arrayData[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buf");
+
+		gl.genBuffers(1, &m_offsetBuf);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_offsetBuf);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(offsetData), &offsetData[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buf");
+
+		gl.genBuffers(1, &m_feedbackBuf);
+		gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_feedbackBuf);
+		gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackSize, DE_NULL, GL_DYNAMIC_COPY);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buf");
+
+		m_program = genProgram();
+
+		if (!m_program->isOk())
+		{
+			m_testCtx.getLog() << *m_program;
+			throw tcu::TestError("could not build program");
+		}
+	}
+}
+
+void VertexFeedbackCase::deinit (void)
+{
+	if (m_elementBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_elementBuf);
+		m_elementBuf = 0;
+	}
+
+	if (m_arrayBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_arrayBuf);
+		m_arrayBuf = 0;
+	}
+
+	if (m_offsetBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_offsetBuf);
+		m_offsetBuf = 0;
+	}
+
+	if (m_feedbackBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_feedbackBuf);
+		m_feedbackBuf = 0;
+	}
+
+	if (m_indirectBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_indirectBuffer);
+		m_indirectBuffer = 0;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+
+	delete m_vao;
+	m_vao = DE_NULL;
+}
+
+VertexFeedbackCase::IterateResult VertexFeedbackCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const deUint32			outputPrimitive	= getOutputPrimitive();
+	const deUint32			basePrimitive	= getBasePrimitive();
+
+	const int				posLocation		= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	const int				offsetLocation	= gl.getAttribLocation(m_program->getProgram(), "a_offset");
+
+	if (posLocation == -1)
+		throw tcu::TestError("a_position location was -1");
+	if (offsetLocation == -1)
+		throw tcu::TestError("a_offset location was -1");
+
+	gl.useProgram(m_program->getProgram());
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuf);
+	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(posLocation);
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_offsetBuf);
+	gl.vertexAttribPointer(offsetLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(offsetLocation);
+
+	gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_feedbackBuf);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind buffer base");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Calling BeginTransformFeedback(" << glu::getPrimitiveTypeStr(basePrimitive) << ")" << tcu::TestLog::EndMessage;
+	gl.beginTransformFeedback(basePrimitive);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "beginTransformFeedback");
+
+	switch (m_method)
+	{
+		case METHOD_DRAW_ARRAYS:
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Calling DrawArrays(" << glu::getPrimitiveTypeStr(outputPrimitive) << ", ...)" << tcu::TestLog::EndMessage;
+			gl.drawArrays(outputPrimitive, 0, 4);
+			break;
+		}
+
+		case METHOD_DRAW_ARRAYS_INSTANCED:
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Calling DrawArraysInstanced(" << glu::getPrimitiveTypeStr(outputPrimitive) << ", ...)" << tcu::TestLog::EndMessage;
+			gl.vertexAttribDivisor(offsetLocation, 2);
+			gl.drawArraysInstanced(outputPrimitive, 0, 3, 2);
+			break;
+		}
+
+		case METHOD_DRAW_ELEMENTS:
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Calling DrawElements(" << glu::getPrimitiveTypeStr(outputPrimitive) << ", ...)" << tcu::TestLog::EndMessage;
+			gl.drawElements(outputPrimitive, 4, GL_UNSIGNED_SHORT, DE_NULL);
+			break;
+		}
+
+		case METHOD_DRAW_ELEMENTS_INSTANCED:
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Calling DrawElementsInstanced(" << glu::getPrimitiveTypeStr(outputPrimitive) << ", ...)" << tcu::TestLog::EndMessage;
+			gl.drawElementsInstanced(outputPrimitive, 3, GL_UNSIGNED_SHORT, DE_NULL, 2);
+			break;
+		}
+
+		case METHOD_DRAW_ARRAYS_INDIRECT:
+		{
+			struct DrawArraysIndirectCommand
+			{
+				deUint32 count;
+				deUint32 instanceCount;
+				deUint32 first;
+				deUint32 reservedMustBeZero;
+			} params;
+
+			DE_STATIC_ASSERT(sizeof(DrawArraysIndirectCommand) == sizeof(deUint32[4]));
+
+			params.count = 4;
+			params.instanceCount = 1;
+			params.first = 0;
+			params.reservedMustBeZero = 0;
+
+			gl.genBuffers(1, &m_indirectBuffer);
+			gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, m_indirectBuffer);
+			gl.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(params), &params, GL_STATIC_DRAW);
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Calling DrawElementsIndirect(" << glu::getPrimitiveTypeStr(outputPrimitive) << ", ...)" << tcu::TestLog::EndMessage;
+			gl.drawArraysIndirect(outputPrimitive, DE_NULL);
+			break;
+		}
+
+		case METHOD_DRAW_ELEMENTS_INDIRECT:
+		{
+			struct DrawElementsIndirectCommand
+			{
+				deUint32	count;
+				deUint32	instanceCount;
+				deUint32	firstIndex;
+				deInt32		baseVertex;
+				deUint32	reservedMustBeZero;
+			} params;
+
+			DE_STATIC_ASSERT(sizeof(DrawElementsIndirectCommand) == sizeof(deUint32[5]));
+
+			params.count = 4;
+			params.instanceCount = 1;
+			params.firstIndex = 0;
+			params.baseVertex = 0;
+			params.reservedMustBeZero = 0;
+
+			gl.genBuffers(1, &m_indirectBuffer);
+			gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, m_indirectBuffer);
+			gl.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(params), &params, GL_STATIC_DRAW);
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Calling DrawElementsIndirect(" << glu::getPrimitiveTypeStr(outputPrimitive) << ", ...)" << tcu::TestLog::EndMessage;
+			gl.drawElementsIndirect(outputPrimitive, GL_UNSIGNED_SHORT, DE_NULL);
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
+
+	gl.endTransformFeedback();
+	GLU_EXPECT_NO_ERROR(gl.getError(), "endTransformFeedback");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "No errors." << tcu::TestLog::EndMessage;
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	return STOP;
+}
+
+glu::ShaderProgram* VertexFeedbackCase::genProgram (void)
+{
+	static const char* const vertexSource =		"#version 310 es\n"
+												"in highp vec4 a_position;\n"
+												"in highp vec4 a_offset;\n"
+												"out highp vec4 tf_value;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"	tf_value = a_position + a_offset;\n"
+												"}\n";
+	static const char* const fragmentSource =	"#version 310 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;\n"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(1.0);\n"
+												"}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+																<< glu::VertexSource(vertexSource)
+																<< glu::FragmentSource(fragmentSource)
+																<< glu::TransformFeedbackVarying("tf_value")
+																<< glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS));
+}
+
+deUint32 VertexFeedbackCase::getOutputPrimitive (void)
+{
+	switch(m_output)
+	{
+		case PRIMITIVE_LINE_LOOP:		return GL_LINE_LOOP;
+		case PRIMITIVE_LINE_STRIP:		return GL_LINE_STRIP;
+		case PRIMITIVE_TRIANGLE_STRIP:	return GL_TRIANGLE_STRIP;
+		case PRIMITIVE_TRIANGLE_FAN:	return GL_TRIANGLE_FAN;
+		case PRIMITIVE_POINTS:			return GL_POINTS;
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+deUint32 VertexFeedbackCase::getBasePrimitive (void)
+{
+	switch(m_output)
+	{
+		case PRIMITIVE_LINE_LOOP:		return GL_LINES;
+		case PRIMITIVE_LINE_STRIP:		return GL_LINES;
+		case PRIMITIVE_TRIANGLE_STRIP:	return GL_TRIANGLES;
+		case PRIMITIVE_TRIANGLE_FAN:	return GL_TRIANGLES;
+		case PRIMITIVE_POINTS:			return GL_POINTS;
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+class VertexFeedbackOverflowCase : public TestCase
+{
+public:
+	enum Method
+	{
+		METHOD_DRAW_ARRAYS = 0,
+		METHOD_DRAW_ELEMENTS,
+	};
+
+						VertexFeedbackOverflowCase	(Context& context, const char* name, const char* description, Method method);
+						~VertexFeedbackOverflowCase	(void);
+
+private:
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+	glu::ShaderProgram*	genProgram					(void);
+
+	const Method		m_method;
+
+	deUint32			m_elementBuf;
+	deUint32			m_arrayBuf;
+	deUint32			m_feedbackBuf;
+	glu::ShaderProgram*	m_program;
+	glu::VertexArray*	m_vao;
+};
+
+VertexFeedbackOverflowCase::VertexFeedbackOverflowCase (Context& context, const char* name, const char* description, Method method)
+	: TestCase		(context, name, description)
+	, m_method		(method)
+	, m_elementBuf	(0)
+	, m_arrayBuf	(0)
+	, m_feedbackBuf	(0)
+	, m_program		(DE_NULL)
+	, m_vao			(DE_NULL)
+{
+}
+
+VertexFeedbackOverflowCase::~VertexFeedbackOverflowCase (void)
+{
+	deinit();
+}
+
+void VertexFeedbackOverflowCase::init (void)
+{
+	// requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("test requires GL_EXT_geometry_shader extension");
+
+	// log what test tries to do
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing GL_EXT_geometry_shader transform feedback overflow behavior.\n"
+		<< "Capturing vertex shader varying, rendering 2 triangles. Allocating feedback buffer for 5 vertices."
+		<< tcu::TestLog::EndMessage;
+
+	// resources
+
+	{
+		static const deUint16	elementData[] =
+		{
+			0, 1, 2,
+			0, 1, 2,
+		};
+		static const tcu::Vec4	arrayData[] =
+		{
+			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, 1.0f),
+			tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f),
+		};
+
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+
+		m_vao = new glu::VertexArray(m_context.getRenderContext());
+		gl.bindVertexArray(**m_vao);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set up vao");
+
+		if (m_method == METHOD_DRAW_ELEMENTS)
+		{
+			gl.genBuffers(1, &m_elementBuf);
+			gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementBuf);
+			gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elementData), &elementData[0], GL_STATIC_DRAW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "gen buf");
+		}
+
+		gl.genBuffers(1, &m_arrayBuf);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuf);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(arrayData), &arrayData[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buf");
+
+		{
+			const int					feedbackCount			= 5 * 4; // 5x vec4
+			const std::vector<float>	initialBufferContents	(feedbackCount, -1.0f);
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Filling feeback buffer with dummy value (-1.0)." << tcu::TestLog::EndMessage;
+
+			gl.genBuffers(1, &m_feedbackBuf);
+			gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_feedbackBuf);
+			gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, (int)(sizeof(float) * initialBufferContents.size()), &initialBufferContents[0], GL_DYNAMIC_COPY);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "gen buf");
+		}
+
+		m_program = genProgram();
+
+		if (!m_program->isOk())
+		{
+			m_testCtx.getLog() << *m_program;
+			throw tcu::TestError("could not build program");
+		}
+	}
+}
+
+void VertexFeedbackOverflowCase::deinit (void)
+{
+	if (m_elementBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_elementBuf);
+		m_elementBuf = 0;
+	}
+
+	if (m_arrayBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_arrayBuf);
+		m_arrayBuf = 0;
+	}
+
+	if (m_feedbackBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_feedbackBuf);
+		m_feedbackBuf = 0;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+
+	delete m_vao;
+	m_vao = DE_NULL;
+}
+
+VertexFeedbackOverflowCase::IterateResult VertexFeedbackOverflowCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const int				posLocation		= gl.getAttribLocation(m_program->getProgram(), "a_position");
+
+	if (posLocation == -1)
+		throw tcu::TestError("a_position location was -1");
+
+	gl.useProgram(m_program->getProgram());
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuf);
+	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(posLocation);
+
+	if (m_method == METHOD_DRAW_ELEMENTS)
+	{
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementBuf);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind buffers");
+	}
+
+	gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_feedbackBuf);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind buffer base");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Capturing 2 triangles." << tcu::TestLog::EndMessage;
+
+	gl.beginTransformFeedback(GL_TRIANGLES);
+
+	if (m_method == METHOD_DRAW_ELEMENTS)
+		gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, DE_NULL);
+	else if (m_method == METHOD_DRAW_ARRAYS)
+		gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+	else
+		DE_ASSERT(false);
+
+	gl.endTransformFeedback();
+	GLU_EXPECT_NO_ERROR(gl.getError(), "capture");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying final triangle was not partially written to the feedback buffer." << tcu::TestLog::EndMessage;
+
+	{
+		const void*				ptr		= gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(float[4]) * 5, GL_MAP_READ_BIT);
+		std::vector<float>		feedback;
+		bool					error	= false;
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "mapBufferRange");
+		if (!ptr)
+			throw tcu::TestError("mapBufferRange returned null");
+
+		feedback.resize(5*4);
+		deMemcpy(&feedback[0], ptr, sizeof(float[4]) * 5);
+
+		if (gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER) != GL_TRUE)
+			throw tcu::TestError("unmapBuffer returned false");
+
+		// Verify vertices 0 - 2
+		for (int vertex = 0; vertex < 3; ++vertex)
+		{
+			for (int component = 0; component < 4; ++component)
+			{
+				if (feedback[vertex*4 + component] != 1.0f)
+				{
+					m_testCtx.getLog()
+						<< tcu::TestLog::Message
+						<< "Feedback buffer vertex " << vertex << ", component " << component << ": unexpected value, expected 1.0, got " << feedback[vertex*4 + component]
+						<< tcu::TestLog::EndMessage;
+					error = true;
+				}
+			}
+		}
+
+		// Verify vertices 3 - 4
+		for (int vertex = 3; vertex < 5; ++vertex)
+		{
+			for (int component = 0; component < 4; ++component)
+			{
+				if (feedback[vertex*4 + component] != -1.0f)
+				{
+					m_testCtx.getLog()
+						<< tcu::TestLog::Message
+						<< "Feedback buffer vertex " << vertex << ", component " << component << ": unexpected value, expected -1.0, got " << feedback[vertex*4 + component]
+						<< tcu::TestLog::EndMessage;
+					error = true;
+				}
+			}
+		}
+
+		if (error)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Feedback result validation failed");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+
+	return STOP;
+}
+
+glu::ShaderProgram* VertexFeedbackOverflowCase::genProgram (void)
+{
+	static const char* const vertexSource =		"#version 310 es\n"
+												"in highp vec4 a_position;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"}\n";
+	static const char* const fragmentSource =	"#version 310 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;\n"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(1.0);\n"
+												"}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+																<< glu::VertexSource(vertexSource)
+																<< glu::FragmentSource(fragmentSource)
+																<< glu::TransformFeedbackVarying("gl_Position")
+																<< glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS));
+}
+
+} // anonymous
+
+GeometryShaderTests::GeometryShaderTests (Context& context)
+	: TestCaseGroup(context, "geometry_shading", "Geometry shader tests")
+{
+}
+
+GeometryShaderTests::~GeometryShaderTests (void)
+{
+}
+
+void GeometryShaderTests::init (void)
+{
+	struct PrimitiveTestSpec
+	{
+		deUint32	primitiveType;
+		const char* name;
+		deUint32	outputType;
+	};
+
+	struct EmitTestSpec
+	{
+		deUint32	outputType;
+		int			emitCountA;		//!< primitive A emit count
+		int			endCountA;		//!< primitive A end count
+		int			emitCountB;		//!<
+		int			endCountB;		//!<
+		const char* name;
+	};
+
+	static const struct LayeredTarget
+	{
+		LayeredRenderCase::LayeredRenderTargetType	target;
+		const char*									name;
+		const char*									desc;
+	} layerTargets[] =
+	{
+		{ LayeredRenderCase::TARGET_CUBE,			"cubemap",				"cubemap"						},
+		{ LayeredRenderCase::TARGET_3D,				"3d",					"3D texture"					},
+		{ LayeredRenderCase::TARGET_2D_ARRAY,		"2d_array",				"2D array texture"				},
+		{ LayeredRenderCase::TARGET_2D_MS_ARRAY,	"2d_multisample_array",	"2D multisample array texture"	},
+	};
+
+	tcu::TestCaseGroup* const queryGroup				= new tcu::TestCaseGroup(m_testCtx, "query", "Query tests.");
+	tcu::TestCaseGroup* const basicGroup				= new tcu::TestCaseGroup(m_testCtx, "basic", "Basic tests.");
+	tcu::TestCaseGroup* const inputPrimitiveGroup		= new tcu::TestCaseGroup(m_testCtx, "input", "Different input primitives.");
+	tcu::TestCaseGroup* const conversionPrimitiveGroup	= new tcu::TestCaseGroup(m_testCtx, "conversion", "Different input and output primitives.");
+	tcu::TestCaseGroup* const emitGroup					= new tcu::TestCaseGroup(m_testCtx, "emit", "Different emit counts.");
+	tcu::TestCaseGroup* const varyingGroup				= new tcu::TestCaseGroup(m_testCtx, "varying", "Test varyings.");
+	tcu::TestCaseGroup* const layeredGroup				= new tcu::TestCaseGroup(m_testCtx, "layered", "Layered rendering.");
+	tcu::TestCaseGroup* const instancedGroup			= new tcu::TestCaseGroup(m_testCtx, "instanced", "Instanced rendering.");
+	tcu::TestCaseGroup* const negativeGroup				= new tcu::TestCaseGroup(m_testCtx, "negative", "Negative tests.");
+	tcu::TestCaseGroup* const feedbackGroup				= new tcu::TestCaseGroup(m_testCtx, "vertex_transform_feedback", "Transform feedback.");
+
+	this->addChild(queryGroup);
+	this->addChild(basicGroup);
+	this->addChild(inputPrimitiveGroup);
+	this->addChild(conversionPrimitiveGroup);
+	this->addChild(emitGroup);
+	this->addChild(varyingGroup);
+	this->addChild(layeredGroup);
+	this->addChild(instancedGroup);
+	this->addChild(negativeGroup);
+	this->addChild(feedbackGroup);
+
+	// query test
+	{
+		// limits with a corresponding glsl constant
+		queryGroup->addChild(new GeometryProgramLimitCase(m_context, "max_geometry_input_components",				"", GL_MAX_GEOMETRY_INPUT_COMPONENTS,				"MaxGeometryInputComponents",		64));
+		queryGroup->addChild(new GeometryProgramLimitCase(m_context, "max_geometry_output_components",				"", GL_MAX_GEOMETRY_OUTPUT_COMPONENTS,				"MaxGeometryOutputComponents",		128));
+		queryGroup->addChild(new GeometryProgramLimitCase(m_context, "max_geometry_image_uniforms",					"", GL_MAX_GEOMETRY_IMAGE_UNIFORMS,					"MaxGeometryImageUniforms",			0));
+		queryGroup->addChild(new GeometryProgramLimitCase(m_context, "max_geometry_texture_image_units",			"", GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS,			"MaxGeometryTextureImageUnits",		16));
+		queryGroup->addChild(new GeometryProgramLimitCase(m_context, "max_geometry_output_vertices",				"", GL_MAX_GEOMETRY_OUTPUT_VERTICES,				"MaxGeometryOutputVertices",		256));
+		queryGroup->addChild(new GeometryProgramLimitCase(m_context, "max_geometry_total_output_components",		"", GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS,		"MaxGeometryTotalOutputComponents",	1024));
+		queryGroup->addChild(new GeometryProgramLimitCase(m_context, "max_geometry_uniform_components",				"", GL_MAX_GEOMETRY_UNIFORM_COMPONENTS,				"MaxGeometryUniformComponents",		1024));
+		queryGroup->addChild(new GeometryProgramLimitCase(m_context, "max_geometry_atomic_counters",				"", GL_MAX_GEOMETRY_ATOMIC_COUNTERS,				"MaxGeometryAtomicCounters",		0));
+		queryGroup->addChild(new GeometryProgramLimitCase(m_context, "max_geometry_atomic_counter_buffers",			"", GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS,			"MaxGeometryAtomicCounterBuffers",	0));
+
+		// program queries
+		queryGroup->addChild(new GeometryShaderVerticesQueryCase	(m_context, "geometry_linked_vertices_out",	"GL_GEOMETRY_LINKED_VERTICES_OUT"));
+		queryGroup->addChild(new GeometryShaderInputQueryCase		(m_context, "geometry_linked_input_type",	"GL_GEOMETRY_LINKED_INPUT_TYPE"));
+		queryGroup->addChild(new GeometryShaderOutputQueryCase		(m_context, "geometry_linked_output_type",	"GL_GEOMETRY_LINKED_OUTPUT_TYPE"));
+		queryGroup->addChild(new GeometryShaderInvocationsQueryCase	(m_context, "geometry_shader_invocations",	"GL_GEOMETRY_SHADER_INVOCATIONS"));
+
+		// limits
+		queryGroup->addChild(new ImplementationLimitCase(m_context, "max_geometry_shader_invocations",		"", GL_MAX_GEOMETRY_SHADER_INVOCATIONS,		32));
+        queryGroup->addChild(new ImplementationLimitCase(m_context, "max_geometry_uniform_blocks",			"", GL_MAX_GEOMETRY_UNIFORM_BLOCKS,			12));
+        queryGroup->addChild(new ImplementationLimitCase(m_context, "max_geometry_shader_storage_blocks",	"", GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS,	0));
+
+		// layer_provoking_vertex_ext
+		queryGroup->addChild(new LayerProvokingVertexQueryCase(m_context, "layer_provoking_vertex", "GL_LAYER_PROVOKING_VERTEX"));
+
+		// primitives_generated
+		queryGroup->addChild(new PrimitivesGeneratedQueryCase(m_context, "primitives_generated_no_geometry",		"PRIMITIVES_GENERATED query with no geometry shader",								PrimitivesGeneratedQueryCase::TEST_NO_GEOMETRY));
+		queryGroup->addChild(new PrimitivesGeneratedQueryCase(m_context, "primitives_generated_no_amplification",	"PRIMITIVES_GENERATED query with non amplifying geometry shader",					PrimitivesGeneratedQueryCase::TEST_NO_AMPLIFICATION));
+		queryGroup->addChild(new PrimitivesGeneratedQueryCase(m_context, "primitives_generated_amplification",		"PRIMITIVES_GENERATED query with amplifying geometry shader",						PrimitivesGeneratedQueryCase::TEST_AMPLIFICATION));
+		queryGroup->addChild(new PrimitivesGeneratedQueryCase(m_context, "primitives_generated_partial_primitives", "PRIMITIVES_GENERATED query with geometry shader emitting partial primitives",		PrimitivesGeneratedQueryCase::TEST_PARTIAL_PRIMITIVES));
+		queryGroup->addChild(new PrimitivesGeneratedQueryCase(m_context, "primitives_generated_instanced",			"PRIMITIVES_GENERATED query with instanced geometry shader",						PrimitivesGeneratedQueryCase::TEST_INSTANCED));
+
+		// fbo
+		queryGroup->addChild(new ImplementationLimitCase				(m_context, "max_framebuffer_layers",				"", GL_MAX_FRAMEBUFFER_LAYERS,	256));
+		queryGroup->addChild(new FramebufferDefaultLayersCase			(m_context, "framebuffer_default_layers",			""));
+		queryGroup->addChild(new FramebufferAttachmentLayeredCase		(m_context, "framebuffer_attachment_layered",		""));
+		queryGroup->addChild(new FramebufferIncompleteLayereTargetsCase	(m_context, "framebuffer_incomplete_layer_targets",	""));
+
+		// resource query
+		queryGroup->addChild(new ReferencedByGeometryShaderCase			(m_context, "referenced_by_geometry_shader",	""));
+	}
+
+	// basic tests
+	{
+		basicGroup->addChild(new OutputCountCase			(m_context,	"output_10",				"Output 10 vertices",								OutputCountPatternSpec(10)));
+		basicGroup->addChild(new OutputCountCase			(m_context,	"output_128",				"Output 128 vertices",								OutputCountPatternSpec(128)));
+		basicGroup->addChild(new OutputCountCase			(m_context,	"output_256",				"Output 256 vertices",								OutputCountPatternSpec(256)));
+		basicGroup->addChild(new OutputCountCase			(m_context,	"output_max",				"Output max vertices",								OutputCountPatternSpec(-1)));
+		basicGroup->addChild(new OutputCountCase			(m_context,	"output_10_and_100",		"Output 10 and 100 vertices in two invocations",	OutputCountPatternSpec(10, 100)));
+		basicGroup->addChild(new OutputCountCase			(m_context,	"output_100_and_10",		"Output 100 and 10 vertices in two invocations",	OutputCountPatternSpec(100, 10)));
+		basicGroup->addChild(new OutputCountCase			(m_context,	"output_0_and_128",			"Output 0 and 128 vertices in two invocations",		OutputCountPatternSpec(0, 128)));
+		basicGroup->addChild(new OutputCountCase			(m_context,	"output_128_and_0",			"Output 128 and 0 vertices in two invocations",		OutputCountPatternSpec(128, 0)));
+
+		basicGroup->addChild(new VaryingOutputCountCase		(m_context,	"output_vary_by_attribute",	"Output varying number of vertices",				VaryingOutputCountShader::READ_ATTRIBUTE,	VaryingOutputCountCase::MODE_WITHOUT_INSTANCING));
+		basicGroup->addChild(new VaryingOutputCountCase		(m_context,	"output_vary_by_uniform",	"Output varying number of vertices",				VaryingOutputCountShader::READ_UNIFORM,		VaryingOutputCountCase::MODE_WITHOUT_INSTANCING));
+		basicGroup->addChild(new VaryingOutputCountCase		(m_context,	"output_vary_by_texture",	"Output varying number of vertices",				VaryingOutputCountShader::READ_TEXTURE,		VaryingOutputCountCase::MODE_WITHOUT_INSTANCING));
+
+		basicGroup->addChild(new BuiltinVariableRenderTest	(m_context,	"point_size",				"test gl_PointSize",								BuiltinVariableShader::TEST_POINT_SIZE));
+		basicGroup->addChild(new BuiltinVariableRenderTest	(m_context,	"primitive_id_in",			"test gl_PrimitiveIDIn",							BuiltinVariableShader::TEST_PRIMITIVE_ID_IN));
+		basicGroup->addChild(new BuiltinVariableRenderTest	(m_context,	"primitive_id_in_restarted","test gl_PrimitiveIDIn with primitive restart",		BuiltinVariableShader::TEST_PRIMITIVE_ID_IN, GeometryShaderRenderTest::FLAG_USE_RESTART_INDEX | GeometryShaderRenderTest::FLAG_USE_INDICES));
+		basicGroup->addChild(new BuiltinVariableRenderTest	(m_context,	"primitive_id",				"test gl_PrimitiveID",								BuiltinVariableShader::TEST_PRIMITIVE_ID));
+	}
+
+	// input primitives
+	{
+		static const PrimitiveTestSpec inputPrimitives[] =
+		{
+			{ GL_POINTS,					"points",					GL_POINTS			},
+			{ GL_LINES,						"lines",					GL_LINE_STRIP		},
+			{ GL_LINE_LOOP,					"line_loop",				GL_LINE_STRIP		},
+			{ GL_LINE_STRIP,				"line_strip",				GL_LINE_STRIP		},
+			{ GL_TRIANGLES,					"triangles",				GL_TRIANGLE_STRIP	},
+			{ GL_TRIANGLE_STRIP,			"triangle_strip",			GL_TRIANGLE_STRIP	},
+			{ GL_TRIANGLE_FAN,				"triangle_fan",				GL_TRIANGLE_STRIP	},
+			{ GL_LINES_ADJACENCY,			"lines_adjacency",			GL_LINE_STRIP		},
+			{ GL_LINE_STRIP_ADJACENCY,		"line_strip_adjacency",		GL_LINE_STRIP		},
+			{ GL_TRIANGLES_ADJACENCY,		"triangles_adjacency",		GL_TRIANGLE_STRIP	}
+		};
+
+		tcu::TestCaseGroup* const basicPrimitiveGroup		= new tcu::TestCaseGroup(m_testCtx, "basic_primitive",			"Different input and output primitives.");
+		tcu::TestCaseGroup* const triStripAdjacencyGroup	= new tcu::TestCaseGroup(m_testCtx, "triangle_strip_adjacency",	"Different triangle_strip_adjacency vertex counts.");
+
+		// more basic types
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(inputPrimitives); ++ndx)
+			basicPrimitiveGroup->addChild(new GeometryExpanderRenderTest(m_context, inputPrimitives[ndx].name, inputPrimitives[ndx].name, inputPrimitives[ndx].primitiveType, inputPrimitives[ndx].outputType));
+
+		// triangle strip adjacency with different vtx counts
+		for (int vtxCount = 0; vtxCount <= 12; ++vtxCount)
+		{
+			const std::string name = "vertex_count_" + de::toString(vtxCount);
+			const std::string desc = "Vertex count is " + de::toString(vtxCount);
+
+			triStripAdjacencyGroup->addChild(new TriangleStripAdjacencyVertexCountTest(m_context, name.c_str(), desc.c_str(), vtxCount));
+		}
+
+		inputPrimitiveGroup->addChild(basicPrimitiveGroup);
+		inputPrimitiveGroup->addChild(triStripAdjacencyGroup);
+	}
+
+	// different type conversions
+	{
+		static const PrimitiveTestSpec conversionPrimitives[] =
+		{
+			{ GL_TRIANGLES,		"triangles_to_points",	GL_POINTS			},
+			{ GL_LINES,			"lines_to_points",		GL_POINTS			},
+			{ GL_POINTS,		"points_to_lines",		GL_LINE_STRIP		},
+			{ GL_TRIANGLES,		"triangles_to_lines",	GL_LINE_STRIP		},
+			{ GL_POINTS,		"points_to_triangles",	GL_TRIANGLE_STRIP	},
+			{ GL_LINES,			"lines_to_triangles",	GL_TRIANGLE_STRIP	}
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(conversionPrimitives); ++ndx)
+			conversionPrimitiveGroup->addChild(new GeometryExpanderRenderTest(m_context, conversionPrimitives[ndx].name, conversionPrimitives[ndx].name, conversionPrimitives[ndx].primitiveType, conversionPrimitives[ndx].outputType));
+	}
+
+	// emit different amounts
+	{
+		static const EmitTestSpec emitTests[] =
+		{
+			{ GL_POINTS,			 0,		0,	0,	0,	"points"			},
+			{ GL_POINTS,			 0,		1,	0,	0,	"points"			},
+			{ GL_POINTS,			 1,		1,	0,	0,	"points"			},
+			{ GL_POINTS,			 0,		2,	0,	0,	"points"			},
+			{ GL_POINTS,			 1,		2,	0,	0,	"points"			},
+			{ GL_LINE_STRIP,		 0,		0,	0,	0,	"line_strip"		},
+			{ GL_LINE_STRIP,		 0,		1,	0,	0,	"line_strip"		},
+			{ GL_LINE_STRIP,		 1,		1,	0,	0,	"line_strip"		},
+			{ GL_LINE_STRIP,		 2,		1,	0,	0,	"line_strip"		},
+			{ GL_LINE_STRIP,		 0,		2,	0,	0,	"line_strip"		},
+			{ GL_LINE_STRIP,		 1,		2,	0,	0,	"line_strip"		},
+			{ GL_LINE_STRIP,		 2,		2,	0,	0,	"line_strip"		},
+			{ GL_LINE_STRIP,		 2,		2,	2,	0,	"line_strip"		},
+			{ GL_TRIANGLE_STRIP,	 0,		0,	0,	0,	"triangle_strip"	},
+			{ GL_TRIANGLE_STRIP,	 0,		1,	0,	0,	"triangle_strip"	},
+			{ GL_TRIANGLE_STRIP,	 1,		1,	0,	0,	"triangle_strip"	},
+			{ GL_TRIANGLE_STRIP,	 2,		1,	0,	0,	"triangle_strip"	},
+			{ GL_TRIANGLE_STRIP,	 3,		1,	0,	0,	"triangle_strip"	},
+			{ GL_TRIANGLE_STRIP,	 0,		2,	0,	0,	"triangle_strip"	},
+			{ GL_TRIANGLE_STRIP,	 1,		2,	0,	0,	"triangle_strip"	},
+			{ GL_TRIANGLE_STRIP,	 2,		2,	0,	0,	"triangle_strip"	},
+			{ GL_TRIANGLE_STRIP,	 3,		2,	0,	0,	"triangle_strip"	},
+			{ GL_TRIANGLE_STRIP,	 3,		2,	3,	0,	"triangle_strip"	},
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(emitTests); ++ndx)
+		{
+			std::string name = std::string(emitTests[ndx].name) + "_emit_" + de::toString(emitTests[ndx].emitCountA) + "_end_" + de::toString(emitTests[ndx].endCountA);
+			std::string desc = std::string(emitTests[ndx].name) + " output, emit " + de::toString(emitTests[ndx].emitCountA) + " vertices, call EndPrimitive " + de::toString(emitTests[ndx].endCountA) + " times";
+
+			if (emitTests[ndx].emitCountB)
+			{
+				name += "_emit_" + de::toString(emitTests[ndx].emitCountB) + "_end_" + de::toString(emitTests[ndx].endCountB);
+				desc += ", emit " + de::toString(emitTests[ndx].emitCountB) + " vertices, call EndPrimitive " + de::toString(emitTests[ndx].endCountB) + " times";
+			}
+
+			emitGroup->addChild(new EmitTest(m_context, name.c_str(), desc.c_str(), emitTests[ndx].emitCountA, emitTests[ndx].endCountA, emitTests[ndx].emitCountB, emitTests[ndx].endCountB, emitTests[ndx].outputType));
+		}
+	}
+
+	// varying
+	{
+		struct VaryingTestSpec
+		{
+			int			vertexOutputs;
+			int			geometryOutputs;
+			const char*	name;
+			const char*	desc;
+		};
+
+		static const VaryingTestSpec varyingTests[] =
+		{
+			{ -1, 1, "vertex_no_op_geometry_out_1", "vertex_no_op_geometry_out_1" },
+			{  0, 1, "vertex_out_0_geometry_out_1", "vertex_out_0_geometry_out_1" },
+			{  0, 2, "vertex_out_0_geometry_out_2", "vertex_out_0_geometry_out_2" },
+			{  1, 0, "vertex_out_1_geometry_out_0", "vertex_out_1_geometry_out_0" },
+			{  1, 2, "vertex_out_1_geometry_out_2", "vertex_out_1_geometry_out_2" },
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(varyingTests); ++ndx)
+			varyingGroup->addChild(new VaryingTest(m_context, varyingTests[ndx].name, varyingTests[ndx].desc, varyingTests[ndx].vertexOutputs, varyingTests[ndx].geometryOutputs));
+	}
+
+	// layered
+	{
+		static const struct TestType
+		{
+			LayeredRenderCase::TestType	test;
+			const char*					testPrefix;
+			const char*					descPrefix;
+		} tests[] =
+		{
+			{ LayeredRenderCase::TEST_DEFAULT_LAYER,			"render_with_default_layer_",	"Render to all layers of "					},
+			{ LayeredRenderCase::TEST_SINGLE_LAYER,				"render_to_one_",				"Render to one layer of "					},
+			{ LayeredRenderCase::TEST_ALL_LAYERS,				"render_to_all_",				"Render to all layers of "					},
+			{ LayeredRenderCase::TEST_DIFFERENT_LAYERS,			"render_different_to_",			"Render different data to different layers"	},
+			{ LayeredRenderCase::TEST_LAYER_ID,					"fragment_layer_",				"Read gl_Layer in fragment shader"			},
+			{ LayeredRenderCase::TEST_LAYER_PROVOKING_VERTEX,	"layer_provoking_vertex_",		"Verify LAYER_PROVOKING_VERTEX"				},
+		};
+
+		for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(tests); ++testNdx)
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(layerTargets); ++targetNdx)
+		{
+			const std::string name = std::string(tests[testNdx].testPrefix) + layerTargets[targetNdx].name;
+			const std::string desc = std::string(tests[testNdx].descPrefix) + layerTargets[targetNdx].desc;
+
+			layeredGroup->addChild(new LayeredRenderCase(m_context, name.c_str(), desc.c_str(), layerTargets[targetNdx].target, tests[testNdx].test));
+		}
+	}
+
+	// instanced
+	{
+		static const struct InvocationCase
+		{
+			const char* name;
+			int			numInvocations;
+		} invocationCases[] =
+		{
+			{ "1",		1  },
+			{ "2",		2  },
+			{ "8",		8  },
+			{ "32",		32 },
+			{ "max",	-1 },
+		};
+		static const int numDrawInstances[] = { 2, 4, 8 };
+		static const int numDrawInvocations[] = { 2, 8 };
+
+		// same amount of content to all invocations
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(invocationCases); ++ndx)
+			instancedGroup->addChild(new GeometryInvocationCase(m_context,
+																(std::string("geometry_") + invocationCases[ndx].name + "_invocations").c_str(),
+																(std::string("Geometry shader with ") + invocationCases[ndx].name + " invocation(s)").c_str(),
+																invocationCases[ndx].numInvocations,
+																GeometryInvocationCase::CASE_FIXED_OUTPUT_COUNTS));
+
+		// different amount of content to each invocation
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(invocationCases); ++ndx)
+			if (invocationCases[ndx].numInvocations != 1)
+				instancedGroup->addChild(new GeometryInvocationCase(m_context,
+																	(std::string("geometry_output_different_") + invocationCases[ndx].name + "_invocations").c_str(),
+																	"Geometry shader invocation(s) with different emit counts",
+																	invocationCases[ndx].numInvocations,
+																	GeometryInvocationCase::CASE_DIFFERENT_OUTPUT_COUNTS));
+
+		// invocation per layer
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(layerTargets); ++targetNdx)
+		{
+			const std::string name = std::string("invocation_per_layer_") + layerTargets[targetNdx].name;
+			const std::string desc = std::string("Render to multiple layers with multiple invocations, one invocation per layer, target ") + layerTargets[targetNdx].desc;
+
+			instancedGroup->addChild(new LayeredRenderCase(m_context, name.c_str(), desc.c_str(), layerTargets[targetNdx].target, LayeredRenderCase::TEST_INVOCATION_PER_LAYER));
+		}
+
+		// multiple layers per invocation
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(layerTargets); ++targetNdx)
+		{
+			const std::string name = std::string("multiple_layers_per_invocation_") + layerTargets[targetNdx].name;
+			const std::string desc = std::string("Render to multiple layers with multiple invocations, multiple layers per invocation, target ") + layerTargets[targetNdx].desc;
+
+			instancedGroup->addChild(new LayeredRenderCase(m_context, name.c_str(), desc.c_str(), layerTargets[targetNdx].target, LayeredRenderCase::TEST_MULTIPLE_LAYERS_PER_INVOCATION));
+		}
+
+		// different invocation output counts depending on {uniform, attrib, texture}
+		instancedGroup->addChild(new VaryingOutputCountCase(m_context,	"invocation_output_vary_by_attribute",	"Output varying number of vertices", VaryingOutputCountShader::READ_ATTRIBUTE,	VaryingOutputCountCase::MODE_WITH_INSTANCING));
+		instancedGroup->addChild(new VaryingOutputCountCase(m_context,	"invocation_output_vary_by_uniform",	"Output varying number of vertices", VaryingOutputCountShader::READ_UNIFORM,	VaryingOutputCountCase::MODE_WITH_INSTANCING));
+		instancedGroup->addChild(new VaryingOutputCountCase(m_context,	"invocation_output_vary_by_texture",	"Output varying number of vertices", VaryingOutputCountShader::READ_TEXTURE,	VaryingOutputCountCase::MODE_WITH_INSTANCING));
+
+		// with drawInstanced
+		for (int instanceNdx = 0; instanceNdx < DE_LENGTH_OF_ARRAY(numDrawInstances); ++instanceNdx)
+		for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(numDrawInvocations); ++invocationNdx)
+		{
+			const std::string name = std::string("draw_") + de::toString(numDrawInstances[instanceNdx]) + "_instances_geometry_" + de::toString(numDrawInvocations[invocationNdx]) + "_invocations";
+			const std::string desc = std::string("Draw ") + de::toString(numDrawInstances[instanceNdx]) + " instances, with " + de::toString(numDrawInvocations[invocationNdx]) + " geometry shader invocations.";
+
+			instancedGroup->addChild(new DrawInstancedGeometryInstancedCase(m_context, name.c_str(), desc.c_str(), numDrawInstances[instanceNdx], numDrawInvocations[invocationNdx]));
+		}
+	}
+
+	// negative (wrong types)
+	{
+		struct PrimitiveToInputTypeConversion
+		{
+			GLenum inputType;
+			GLenum primitiveType;
+		};
+
+		static const PrimitiveToInputTypeConversion legalConversions[] =
+		{
+			{ GL_POINTS,				GL_POINTS					},
+			{ GL_LINES,					GL_LINES					},
+			{ GL_LINES,					GL_LINE_LOOP				},
+			{ GL_LINES,					GL_LINE_STRIP				},
+			{ GL_LINES_ADJACENCY,		GL_LINES_ADJACENCY			},
+			{ GL_LINES_ADJACENCY,		GL_LINE_STRIP_ADJACENCY		},
+			{ GL_TRIANGLES,				GL_TRIANGLES				},
+			{ GL_TRIANGLES,				GL_TRIANGLE_STRIP			},
+			{ GL_TRIANGLES,				GL_TRIANGLE_FAN				},
+			{ GL_TRIANGLES_ADJACENCY,	GL_TRIANGLES_ADJACENCY		},
+			{ GL_TRIANGLES_ADJACENCY,	GL_TRIANGLE_STRIP_ADJACENCY	},
+		};
+
+		static const GLenum inputTypes[] =
+		{
+			GL_POINTS,
+			GL_LINES,
+			GL_LINES_ADJACENCY,
+			GL_TRIANGLES,
+			GL_TRIANGLES_ADJACENCY
+		};
+
+		static const GLenum primitiveTypes[] =
+		{
+			GL_POINTS,
+			GL_LINES,
+			GL_LINE_LOOP,
+			GL_LINE_STRIP,
+			GL_LINES_ADJACENCY,
+			GL_LINE_STRIP_ADJACENCY,
+			GL_TRIANGLES,
+			GL_TRIANGLE_STRIP,
+			GL_TRIANGLE_FAN,
+			GL_TRIANGLES_ADJACENCY,
+			GL_TRIANGLE_STRIP_ADJACENCY
+		};
+
+		for (int inputTypeNdx = 0; inputTypeNdx < DE_LENGTH_OF_ARRAY(inputTypes); ++inputTypeNdx)
+		for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); ++primitiveTypeNdx)
+		{
+			const GLenum		inputType		= inputTypes[inputTypeNdx];
+			const GLenum		primitiveType	= primitiveTypes[primitiveTypeNdx];
+			const std::string	name			= std::string("type_") + inputTypeToGLString(sglr::rr_util::mapGLGeometryShaderInputType(inputType)) + "_primitive_" + primitiveTypeToString(primitiveType);
+			const std::string	desc			= std::string("Shader input type ") + inputTypeToGLString(sglr::rr_util::mapGLGeometryShaderInputType(inputType)) + ", draw primitive type " + primitiveTypeToString(primitiveType);
+
+			bool isLegal = false;
+
+			for (int legalNdx = 0; legalNdx < DE_LENGTH_OF_ARRAY(legalConversions); ++legalNdx)
+				if (legalConversions[legalNdx].inputType == inputType && legalConversions[legalNdx].primitiveType == primitiveType)
+					isLegal = true;
+
+			// only illegal
+			if (!isLegal)
+				negativeGroup->addChild(new NegativeDrawCase(m_context, name.c_str(), desc.c_str(), inputType, primitiveType));
+		}
+	}
+
+	// vertex transform feedback
+	{
+		feedbackGroup->addChild(new VertexFeedbackCase(m_context, "capture_vertex_line_loop",				"Capture line loop lines",									VertexFeedbackCase::METHOD_DRAW_ARRAYS,				VertexFeedbackCase::PRIMITIVE_LINE_LOOP));
+		feedbackGroup->addChild(new VertexFeedbackCase(m_context, "capture_vertex_line_strip",				"Capture line strip lines",									VertexFeedbackCase::METHOD_DRAW_ARRAYS,				VertexFeedbackCase::PRIMITIVE_LINE_STRIP));
+		feedbackGroup->addChild(new VertexFeedbackCase(m_context, "capture_vertex_triangle_strip",			"Capture triangle strip triangles",							VertexFeedbackCase::METHOD_DRAW_ARRAYS,				VertexFeedbackCase::PRIMITIVE_TRIANGLE_STRIP));
+		feedbackGroup->addChild(new VertexFeedbackCase(m_context, "capture_vertex_triangle_fan",			"Capture triangle fan triangles",							VertexFeedbackCase::METHOD_DRAW_ARRAYS,				VertexFeedbackCase::PRIMITIVE_TRIANGLE_FAN));
+		feedbackGroup->addChild(new VertexFeedbackCase(m_context, "capture_vertex_draw_arrays",				"Capture primitives generated with drawArrays",				VertexFeedbackCase::METHOD_DRAW_ARRAYS,				VertexFeedbackCase::PRIMITIVE_POINTS));
+		feedbackGroup->addChild(new VertexFeedbackCase(m_context, "capture_vertex_draw_arrays_instanced",	"Capture primitives generated with drawArraysInstanced",	VertexFeedbackCase::METHOD_DRAW_ARRAYS_INSTANCED,	VertexFeedbackCase::PRIMITIVE_POINTS));
+		feedbackGroup->addChild(new VertexFeedbackCase(m_context, "capture_vertex_draw_arrays_indirect",	"Capture primitives generated with drawArraysIndirect",		VertexFeedbackCase::METHOD_DRAW_ARRAYS_INDIRECT,	VertexFeedbackCase::PRIMITIVE_POINTS));
+		feedbackGroup->addChild(new VertexFeedbackCase(m_context, "capture_vertex_draw_elements",			"Capture primitives generated with drawElements",			VertexFeedbackCase::METHOD_DRAW_ELEMENTS,			VertexFeedbackCase::PRIMITIVE_POINTS));
+		feedbackGroup->addChild(new VertexFeedbackCase(m_context, "capture_vertex_draw_elements_instanced",	"Capture primitives generated with drawElementsInstanced",	VertexFeedbackCase::METHOD_DRAW_ELEMENTS_INSTANCED,	VertexFeedbackCase::PRIMITIVE_POINTS));
+		feedbackGroup->addChild(new VertexFeedbackCase(m_context, "capture_vertex_draw_elements_indirect",	"Capture primitives generated with drawElementsIndirect",	VertexFeedbackCase::METHOD_DRAW_ELEMENTS_INDIRECT,	VertexFeedbackCase::PRIMITIVE_POINTS));
+
+		feedbackGroup->addChild(new VertexFeedbackOverflowCase(m_context, "capture_vertex_draw_arrays_overflow_single_buffer",		"Capture triangles to too small a buffer", VertexFeedbackOverflowCase::METHOD_DRAW_ARRAYS));
+		feedbackGroup->addChild(new VertexFeedbackOverflowCase(m_context, "capture_vertex_draw_elements_overflow_single_buffer",	"Capture triangles to too small a buffer", VertexFeedbackOverflowCase::METHOD_DRAW_ELEMENTS));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fGeometryShaderTests.hpp b/modules/gles31/functional/es31fGeometryShaderTests.hpp
new file mode 100644
index 0000000..ec1a079
--- /dev/null
+++ b/modules/gles31/functional/es31fGeometryShaderTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FGEOMETRYSHADERTESTS_HPP
+#define _ES31FGEOMETRYSHADERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Geometry shader tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class GeometryShaderTests : public TestCaseGroup
+{
+public:
+							GeometryShaderTests		(Context& context);
+							~GeometryShaderTests	(void);
+
+	void					init					(void);
+
+private:
+							GeometryShaderTests		(const GeometryShaderTests& other);
+	GeometryShaderTests&	operator=				(const GeometryShaderTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FGEOMETRYSHADERTESTS_HPP
diff --git a/modules/gles31/functional/es31fIndirectComputeDispatchTests.cpp b/modules/gles31/functional/es31fIndirectComputeDispatchTests.cpp
new file mode 100644
index 0000000..768dec8
--- /dev/null
+++ b/modules/gles31/functional/es31fIndirectComputeDispatchTests.cpp
@@ -0,0 +1,506 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Indirect compute dispatch tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fIndirectComputeDispatchTests.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "tcuVector.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuTestLog.hpp"
+#include "deStringUtil.hpp"
+
+#include <vector>
+#include <string>
+#include <map>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using tcu::UVec3;
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using std::map;
+
+// \todo [2014-02-17 pyry] Should be extended with following:
+
+// Negative:
+//  - no active shader program
+//  - indirect negative or not aligned
+//  - indirect + size outside buffer bounds
+//  - no buffer bound to DRAW_INDIRECT_BUFFER
+//  - (implict) buffer mapped
+
+// Robustness:
+//  - lot of small work group launches
+//  - very large work group size
+//  - no synchronization, touched by gpu
+//  - compute program overwiting buffer
+
+namespace
+{
+
+enum
+{
+	RESULT_BLOCK_BASE_SIZE				= (3+1)*(int)sizeof(deUint32),		// uvec3 + uint
+	RESULT_BLOCK_EXPECTED_COUNT_OFFSET	= 0,
+	RESULT_BLOCK_NUM_PASSED_OFFSET		= 3*(int)sizeof(deUint32),
+
+	INDIRECT_COMMAND_SIZE				= 3*(int)sizeof(deUint32)
+};
+
+enum GenBuffer
+{
+	GEN_BUFFER_UPLOAD		= 0,
+	GEN_BUFFER_COMPUTE,
+
+	GEN_BUFFER_LAST
+};
+
+glu::ProgramSources genVerifySources (const UVec3& workGroupSize)
+{
+	static const char* s_verifyDispatchTmpl =
+		"#version 310 es\n"
+		"layout(local_size_x = ${LOCAL_SIZE_X}, local_size_y = ${LOCAL_SIZE_Y}, local_size_z = ${LOCAL_SIZE_Z}) in;\n"
+		"layout(binding = 0, std430) buffer Result\n"
+		"{\n"
+		"    uvec3           expectedGroupCount;\n"
+		"    coherent uint   numPassed;\n"
+		"} result;\n"
+		"void main (void)\n"
+		"{\n"
+		"    if (all(equal(result.expectedGroupCount, gl_NumWorkGroups)))\n"
+		"        atomicAdd(result.numPassed, 1u);\n"
+		"}\n";
+
+	map<string, string> args;
+
+	args["LOCAL_SIZE_X"] = de::toString(workGroupSize.x());
+	args["LOCAL_SIZE_Y"] = de::toString(workGroupSize.y());
+	args["LOCAL_SIZE_Z"] = de::toString(workGroupSize.z());
+
+	return glu::ProgramSources() << glu::ComputeSource(tcu::StringTemplate(s_verifyDispatchTmpl).specialize(args));
+}
+
+class IndirectDispatchCase : public TestCase
+{
+public:
+							IndirectDispatchCase	(Context& context, const char* name, const char* description, GenBuffer genBuffer);
+							~IndirectDispatchCase	(void);
+
+	IterateResult			iterate					(void);
+
+protected:
+	struct DispatchCommand
+	{
+		deIntptr	offset;
+		UVec3		numWorkGroups;
+
+		DispatchCommand (void) : offset(0) {}
+		DispatchCommand (deIntptr offset_, const UVec3& numWorkGroups_) : offset(offset_), numWorkGroups(numWorkGroups_) {}
+	};
+
+	GenBuffer				m_genBuffer;
+	deUintptr				m_bufferSize;
+	UVec3					m_workGroupSize;
+	vector<DispatchCommand>	m_commands;
+
+	void					createCommandBuffer		(deUint32 buffer) const;
+	void					createResultBuffer		(deUint32 buffer) const;
+
+	bool					verifyResultBuffer		(deUint32 buffer);
+
+	void					createCmdBufferUpload	(deUint32 buffer) const;
+	void					createCmdBufferCompute	(deUint32 buffer) const;
+
+private:
+							IndirectDispatchCase	(const IndirectDispatchCase&);
+	IndirectDispatchCase&	operator=				(const IndirectDispatchCase&);
+};
+
+IndirectDispatchCase::IndirectDispatchCase (Context& context, const char* name, const char* description, GenBuffer genBuffer)
+	: TestCase		(context, name, description)
+	, m_genBuffer	(genBuffer)
+	, m_bufferSize	(0)
+{
+}
+
+IndirectDispatchCase::~IndirectDispatchCase (void)
+{
+}
+
+static int getResultBlockAlignedSize (const glw::Functions& gl)
+{
+	const int	baseSize	= RESULT_BLOCK_BASE_SIZE;
+	int			alignment	= 0;
+	gl.getIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &alignment);
+
+	if (alignment == 0 || (baseSize % alignment == 0))
+		return baseSize;
+	else
+		return (baseSize/alignment + 1)*alignment;
+}
+
+void IndirectDispatchCase::createCommandBuffer (deUint32 buffer) const
+{
+	switch (m_genBuffer)
+	{
+		case GEN_BUFFER_UPLOAD:		createCmdBufferUpload	(buffer);		break;
+		case GEN_BUFFER_COMPUTE:	createCmdBufferCompute	(buffer);		break;
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+void IndirectDispatchCase::createCmdBufferUpload (deUint32 buffer) const
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	vector<deUint8>			data	(m_bufferSize);
+
+	for (vector<DispatchCommand>::const_iterator cmdIter = m_commands.begin(); cmdIter != m_commands.end(); ++cmdIter)
+	{
+		DE_STATIC_ASSERT(INDIRECT_COMMAND_SIZE >= sizeof(deUint32)*3);
+		DE_ASSERT(cmdIter->offset >= 0);
+		DE_ASSERT(cmdIter->offset%sizeof(deUint32) == 0);
+		DE_ASSERT(cmdIter->offset + INDIRECT_COMMAND_SIZE <= (deIntptr)m_bufferSize);
+
+		deUint32* const dstPtr = (deUint32*)&data[cmdIter->offset];
+
+		dstPtr[0] = cmdIter->numWorkGroups[0];
+		dstPtr[1] = cmdIter->numWorkGroups[1];
+		dstPtr[2] = cmdIter->numWorkGroups[2];
+	}
+
+	gl.bindBuffer(GL_DISPATCH_INDIRECT_BUFFER, buffer);
+	gl.bufferData(GL_DISPATCH_INDIRECT_BUFFER, (glw::GLsizeiptr)data.size(), &data[0], GL_STATIC_DRAW);
+}
+
+void IndirectDispatchCase::createCmdBufferCompute (deUint32 buffer) const
+{
+	std::ostringstream src;
+
+	// Header
+	src <<
+		"#version 310 es\n"
+		"layout(local_size_x = 1) in;\n"
+		"layout(std430, binding = 1) buffer Out\n"
+		"{\n"
+		"	highp uint data[];\n"
+		"};\n"
+		"void writeCmd (uint offset, uvec3 numWorkGroups)\n"
+		"{\n"
+		"	data[offset+0u] = numWorkGroups.x;\n"
+		"	data[offset+1u] = numWorkGroups.y;\n"
+		"	data[offset+2u] = numWorkGroups.z;\n"
+		"}\n"
+		"void main (void)\n"
+		"{\n";
+
+	// Commands
+	for (vector<DispatchCommand>::const_iterator cmdIter = m_commands.begin(); cmdIter != m_commands.end(); ++cmdIter)
+	{
+		const deUint32 offs = (deUint32)(cmdIter->offset/4);
+		DE_ASSERT((deIntptr)offs*4 == cmdIter->offset);
+
+		src << "\twriteCmd(" << offs << "u, uvec3("
+			<< cmdIter->numWorkGroups.x() << "u, "
+			<< cmdIter->numWorkGroups.y() << "u, "
+			<< cmdIter->numWorkGroups.z() << "u));\n";
+	}
+
+	src << "}\n";
+
+	{
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		glu::ShaderProgram		program		(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(src.str()));
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			TCU_FAIL("Compile failed");
+
+		gl.useProgram(program.getProgram());
+
+		gl.bindBuffer(GL_DISPATCH_INDIRECT_BUFFER, buffer);
+		gl.bufferData(GL_DISPATCH_INDIRECT_BUFFER, (glw::GLsizeiptr)m_bufferSize, DE_NULL, GL_STATIC_DRAW);
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, buffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer setup failed");
+
+		gl.dispatchCompute(1,1,1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute() failed");
+
+		gl.memoryBarrier(GL_COMMAND_BARRIER_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMemoryBarrier(GL_COMMAND_BARRIER_BIT) failed");
+	}
+}
+
+void IndirectDispatchCase::createResultBuffer (deUint32 buffer) const
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const int				resultBlockSize		= getResultBlockAlignedSize(gl);
+	const int				resultBufferSize	= resultBlockSize*(int)m_commands.size();
+	vector<deUint8>			data				(resultBufferSize);
+
+	for (size_t cmdNdx = 0; cmdNdx < m_commands.size(); cmdNdx++)
+	{
+		deUint8* const	dstPtr	= &data[resultBlockSize*cmdNdx];
+
+		*(deUint32*)(dstPtr + RESULT_BLOCK_EXPECTED_COUNT_OFFSET + 0*4)	= m_commands[cmdNdx].numWorkGroups[0];
+		*(deUint32*)(dstPtr + RESULT_BLOCK_EXPECTED_COUNT_OFFSET + 1*4)	= m_commands[cmdNdx].numWorkGroups[1];
+		*(deUint32*)(dstPtr + RESULT_BLOCK_EXPECTED_COUNT_OFFSET + 2*4)	= m_commands[cmdNdx].numWorkGroups[2];
+		*(deUint32*)(dstPtr + RESULT_BLOCK_NUM_PASSED_OFFSET)			= 0;
+	}
+
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, (glw::GLsizei)data.size(), &data[0], GL_STATIC_READ);
+}
+
+deUint32 computeInvocationCount (const UVec3& workGroupSize, const UVec3& numWorkGroups)
+{
+	const int	numInvocationsPerGroup	= workGroupSize[0]*workGroupSize[1]*workGroupSize[2];
+	const int	numGroups				= numWorkGroups[0]*numWorkGroups[1]*numWorkGroups[2];
+
+	return numInvocationsPerGroup*numGroups;
+}
+
+bool IndirectDispatchCase::verifyResultBuffer (deUint32 buffer)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+
+	const int				resultBlockSize		= getResultBlockAlignedSize(gl);
+	const int				resultBufferSize	= resultBlockSize*(int)m_commands.size();
+
+	void*					mapPtr				= DE_NULL;
+	bool					allOk				= true;
+
+	try
+	{
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
+		mapPtr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, resultBufferSize, GL_MAP_READ_BIT);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange() failed");
+		TCU_CHECK(mapPtr);
+
+		for (size_t cmdNdx = 0; cmdNdx < m_commands.size(); cmdNdx++)
+		{
+			const DispatchCommand&	cmd				= m_commands[cmdNdx];
+			const deUint8* const	srcPtr			= (const deUint8*)mapPtr + cmdNdx*resultBlockSize;
+			const deUint32			numPassed		= *(const deUint32*)(srcPtr + RESULT_BLOCK_NUM_PASSED_OFFSET);
+			const deUint32			expectedCount	= computeInvocationCount(m_workGroupSize, cmd.numWorkGroups);
+
+			// Verify numPassed.
+			if (numPassed != expectedCount)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: got invalid result for invocation " << cmdNdx
+													   << ": got numPassed = " << numPassed << ", expected " << expectedCount
+								   << TestLog::EndMessage;
+				allOk = false;
+			}
+		}
+	}
+	catch (...)
+	{
+		if (mapPtr)
+			gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+	}
+
+	gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer() failed");
+
+	return allOk;
+}
+
+IndirectDispatchCase::IterateResult IndirectDispatchCase::iterate (void)
+{
+	const glu::RenderContext&		renderCtx			= m_context.getRenderContext();
+	const glw::Functions&			gl					= renderCtx.getFunctions();
+
+	const glu::ShaderProgram		program				(renderCtx, genVerifySources(m_workGroupSize));
+
+	glu::Buffer						cmdBuffer			(renderCtx);
+	glu::Buffer						resultBuffer		(renderCtx);
+
+	m_testCtx.getLog() << program;
+	TCU_CHECK_MSG(program.isOk(), "Compile failed");
+
+	m_testCtx.getLog() << TestLog::Message << "GL_DISPATCH_INDIRECT_BUFFER size = " << m_bufferSize << TestLog::EndMessage;
+	{
+		tcu::ScopedLogSection section(m_testCtx.getLog(), "Commands", "Indirect Dispatch Commands (" + de::toString(m_commands.size()) + " in total)");
+
+		for (size_t cmdNdx = 0; cmdNdx < m_commands.size(); cmdNdx++)
+			m_testCtx.getLog() << TestLog::Message << cmdNdx << ": " << "offset = " << m_commands[cmdNdx].offset
+												   << ", numWorkGroups = " << m_commands[cmdNdx].numWorkGroups
+							   << TestLog::EndMessage;
+	}
+
+	createResultBuffer(*resultBuffer);
+	createCommandBuffer(*cmdBuffer);
+
+	gl.useProgram(program.getProgram());
+	gl.bindBuffer(GL_DISPATCH_INDIRECT_BUFFER, *cmdBuffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "State setup failed");
+
+	{
+		const int	resultBlockAlignedSize		= getResultBlockAlignedSize(gl);
+		deIntptr	curOffset					= 0;
+
+		for (vector<DispatchCommand>::const_iterator cmdIter = m_commands.begin(); cmdIter != m_commands.end(); ++cmdIter)
+		{
+			gl.bindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, *resultBuffer, (glw::GLintptr)curOffset, resultBlockAlignedSize);
+			gl.dispatchComputeIndirect((glw::GLintptr)cmdIter->offset);
+
+			curOffset += resultBlockAlignedSize;
+		}
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchComputeIndirect() failed");
+
+	if (verifyResultBuffer(*resultBuffer))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid values in result buffer");
+
+	return STOP;
+}
+
+class SingleDispatchCase : public IndirectDispatchCase
+{
+public:
+	SingleDispatchCase (Context& context, const char* name, const char* description, GenBuffer genBuffer, deUintptr bufferSize, deUintptr offset, const UVec3& workGroupSize, const UVec3& numWorkGroups)
+		: IndirectDispatchCase(context, name, description, genBuffer)
+	{
+		m_bufferSize	= bufferSize;
+		m_workGroupSize	= workGroupSize;
+		m_commands.push_back(DispatchCommand(offset, numWorkGroups));
+	}
+};
+
+class MultiDispatchCase : public IndirectDispatchCase
+{
+public:
+	MultiDispatchCase (Context& context, GenBuffer genBuffer)
+		: IndirectDispatchCase(context, "multi_dispatch", "Dispatch multiple compute commands from single buffer", genBuffer)
+	{
+		m_bufferSize	= 1<<10;
+		m_workGroupSize	= UVec3(3,1,2);
+
+		m_commands.push_back(DispatchCommand(0,						UVec3(1,1,1)));
+		m_commands.push_back(DispatchCommand(INDIRECT_COMMAND_SIZE,	UVec3(2,1,1)));
+		m_commands.push_back(DispatchCommand(104,					UVec3(1,3,1)));
+		m_commands.push_back(DispatchCommand(40,					UVec3(1,1,7)));
+		m_commands.push_back(DispatchCommand(52,					UVec3(1,1,4)));
+	}
+};
+
+class MultiDispatchReuseCommandCase : public IndirectDispatchCase
+{
+public:
+	MultiDispatchReuseCommandCase (Context& context, GenBuffer genBuffer)
+		: IndirectDispatchCase(context, "multi_dispatch_reuse_command", "Dispatch multiple compute commands from single buffer", genBuffer)
+	{
+		m_bufferSize	= 1<<10;
+		m_workGroupSize	= UVec3(3,1,2);
+
+		m_commands.push_back(DispatchCommand(0,						UVec3(1,1,1)));
+		m_commands.push_back(DispatchCommand(0,						UVec3(1,1,1)));
+		m_commands.push_back(DispatchCommand(0,						UVec3(1,1,1)));
+		m_commands.push_back(DispatchCommand(104,					UVec3(1,3,1)));
+		m_commands.push_back(DispatchCommand(104,					UVec3(1,3,1)));
+		m_commands.push_back(DispatchCommand(52,					UVec3(1,1,4)));
+		m_commands.push_back(DispatchCommand(52,					UVec3(1,1,4)));
+	}
+};
+
+} // anonymous
+
+IndirectComputeDispatchTests::IndirectComputeDispatchTests (Context& context)
+	: TestCaseGroup(context, "indirect_dispatch", "Indirect dispatch tests")
+{
+}
+
+IndirectComputeDispatchTests::~IndirectComputeDispatchTests (void)
+{
+}
+
+void IndirectComputeDispatchTests::init (void)
+{
+	static const struct
+	{
+		const char*		name;
+		GenBuffer		gen;
+	} s_genBuffer[] =
+	{
+		{ "upload_buffer",		GEN_BUFFER_UPLOAD	},
+		{ "gen_in_compute",		GEN_BUFFER_COMPUTE	}
+	};
+
+	static const struct
+	{
+		const char*	name;
+		const char*	description;
+		deUintptr	bufferSize;
+		deUintptr	offset;
+		UVec3		workGroupSize;
+		UVec3		numWorkGroups;
+	} s_singleDispatchCases[] =
+	{
+	//	Name										Desc											BufferSize					Offs			WorkGroupSize	NumWorkGroups
+		{ "single_invocation",						"Single invocation only from offset 0",			INDIRECT_COMMAND_SIZE,		0,				UVec3(1,1,1),	UVec3(1,1,1) },
+		{ "multiple_groups",						"Multiple groups dispatched from offset 0",		INDIRECT_COMMAND_SIZE,		0,				UVec3(1,1,1),	UVec3(2,3,5) },
+		{ "multiple_groups_multiple_invocations",	"Multiple groups of size 2x3x1 from offset 0",	INDIRECT_COMMAND_SIZE,		0,				UVec3(2,3,1),	UVec3(1,2,3) },
+		{ "small_offset",							"Small offset",									16+INDIRECT_COMMAND_SIZE,	16,				UVec3(1,1,1),	UVec3(1,1,1) },
+		{ "large_offset",							"Large offset",									(2<<20),					(1<<20) + 12,	UVec3(1,1,1),	UVec3(1,1,1) },
+		{ "large_offset_multiple_invocations",		"Large offset, multiple invocations",			(2<<20),					(1<<20) + 12,	UVec3(2,3,1),	UVec3(1,2,3) },
+		{ "empty_command",							"Empty command",								INDIRECT_COMMAND_SIZE,		0,				UVec3(1,1,1),	UVec3(0,0,0) },
+	};
+
+	for (int genNdx = 0; genNdx < DE_LENGTH_OF_ARRAY(s_genBuffer); genNdx++)
+	{
+		const GenBuffer				genBuf		= s_genBuffer[genNdx].gen;
+		tcu::TestCaseGroup* const	genGroup	= new tcu::TestCaseGroup(m_testCtx, s_genBuffer[genNdx].name, "");
+		addChild(genGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_singleDispatchCases); ndx++)
+			genGroup->addChild(new SingleDispatchCase(m_context,
+													  s_singleDispatchCases[ndx].name,
+													  s_singleDispatchCases[ndx].description,
+													  genBuf,
+													  s_singleDispatchCases[ndx].bufferSize,
+													  s_singleDispatchCases[ndx].offset,
+													  s_singleDispatchCases[ndx].workGroupSize,
+													  s_singleDispatchCases[ndx].numWorkGroups));
+
+		genGroup->addChild(new MultiDispatchCase				(m_context, genBuf));
+		genGroup->addChild(new MultiDispatchReuseCommandCase	(m_context, genBuf));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fIndirectComputeDispatchTests.hpp b/modules/gles31/functional/es31fIndirectComputeDispatchTests.hpp
new file mode 100644
index 0000000..873cb57
--- /dev/null
+++ b/modules/gles31/functional/es31fIndirectComputeDispatchTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FINDIRECTCOMPUTEDISPATCHTESTS_HPP
+#define _ES31FINDIRECTCOMPUTEDISPATCHTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Indirect compute dispatch tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class IndirectComputeDispatchTests : public TestCaseGroup
+{
+public:
+									IndirectComputeDispatchTests	(Context& context);
+									~IndirectComputeDispatchTests	(void);
+
+	void							init							(void);
+
+private:
+									IndirectComputeDispatchTests	(const IndirectComputeDispatchTests&);
+	IndirectComputeDispatchTests&	operator=						(const IndirectComputeDispatchTests&);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FINDIRECTCOMPUTEDISPATCHTESTS_HPP
diff --git a/modules/gles31/functional/es31fIntegerStateQueryTests.cpp b/modules/gles31/functional/es31fIntegerStateQueryTests.cpp
new file mode 100644
index 0000000..47b8b3a
--- /dev/null
+++ b/modules/gles31/functional/es31fIntegerStateQueryTests.cpp
@@ -0,0 +1,652 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Integer state query tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fIntegerStateQueryTests.hpp"
+#include "tcuTestLog.hpp"
+#include "gluRenderContext.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluContextInfo.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deRandom.hpp"
+#include "glsStateQueryUtil.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+using gls::StateQueryUtil::StateQueryMemoryWriteGuard;
+
+enum VerifierType
+{
+	VERIFIER_GETBOOLEAN = 0,
+	VERIFIER_GETINTEGER,
+	VERIFIER_GETINTEGER64,
+	VERIFIER_GETFLOAT
+};
+
+static const char* getVerifierSuffix (VerifierType type)
+{
+	switch (type)
+	{
+		case VERIFIER_GETBOOLEAN:	return "getboolean";
+		case VERIFIER_GETINTEGER:	return "getinteger";
+		case VERIFIER_GETINTEGER64:	return "getinteger64";
+		case VERIFIER_GETFLOAT:		return "getfloat";
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static bool verifyValue (glu::CallLogWrapper& gl, glw::GLenum target, int refValue, VerifierType type)
+{
+	switch (type)
+	{
+		case VERIFIER_GETBOOLEAN:
+		{
+			StateQueryMemoryWriteGuard<glw::GLboolean> value;
+			gl.glGetBooleanv(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetBooleanv");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (value != ((refValue == 0) ? (GL_FALSE) : (GL_TRUE)))
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected " << ((refValue == 0) ? (GL_FALSE) : (GL_TRUE)) << ", got " << ((value == 0) ? (GL_FALSE) : (GL_TRUE)) << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		case VERIFIER_GETINTEGER:
+		{
+			StateQueryMemoryWriteGuard<glw::GLint> value;
+			gl.glGetIntegerv(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (value != refValue)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected " << refValue << ", got " << value << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		case VERIFIER_GETINTEGER64:
+		{
+			StateQueryMemoryWriteGuard<glw::GLint64> value;
+			gl.glGetInteger64v(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetInteger64v");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (value != (glw::GLint64)refValue)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected " << refValue << ", got " << value << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		case VERIFIER_GETFLOAT:
+		{
+			StateQueryMemoryWriteGuard<glw::GLfloat> value;
+			gl.glGetFloatv(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetFloatv");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (value != (glw::GLfloat)refValue)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected " << refValue << ", got " << value << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static bool verifyMinValue (glu::CallLogWrapper& gl, glw::GLenum target, int minValue, VerifierType type)
+{
+	switch (type)
+	{
+		case VERIFIER_GETBOOLEAN:
+		{
+			StateQueryMemoryWriteGuard<glw::GLboolean> value;
+			gl.glGetBooleanv(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetBooleanv");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (minValue > 0 && value == GL_FALSE)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected GL_TRUE, got GL_FALSE" << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		case VERIFIER_GETINTEGER:
+		{
+			StateQueryMemoryWriteGuard<glw::GLint> value;
+			gl.glGetIntegerv(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (value < minValue)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected greater or equal to " << minValue << ", got " << value << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		case VERIFIER_GETINTEGER64:
+		{
+			StateQueryMemoryWriteGuard<glw::GLint64> value;
+			gl.glGetInteger64v(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetInteger64v");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (value < minValue)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected greater or equal to " << minValue << ", got " << value << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		case VERIFIER_GETFLOAT:
+		{
+			StateQueryMemoryWriteGuard<glw::GLfloat> value;
+			gl.glGetFloatv(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetFloatv");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (value < minValue)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected greater or equal to " << minValue << ", got " << value << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+static bool verifyMaxValue (glu::CallLogWrapper& gl, glw::GLenum target, int maxValue, VerifierType type)
+{
+	switch (type)
+	{
+		case VERIFIER_GETBOOLEAN:
+		{
+			StateQueryMemoryWriteGuard<glw::GLboolean> value;
+			gl.glGetBooleanv(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetBooleanv");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (maxValue < 0 && value == GL_FALSE)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected GL_TRUE, got GL_FALSE" << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		case VERIFIER_GETINTEGER:
+		{
+			StateQueryMemoryWriteGuard<glw::GLint> value;
+			gl.glGetIntegerv(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (value > maxValue)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected less or equal to " << maxValue << ", got " << value << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		case VERIFIER_GETINTEGER64:
+		{
+			StateQueryMemoryWriteGuard<glw::GLint64> value;
+			gl.glGetInteger64v(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetInteger64v");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (value > maxValue)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected less or equal to " << maxValue << ", got " << value << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		case VERIFIER_GETFLOAT:
+		{
+			StateQueryMemoryWriteGuard<glw::GLfloat> value;
+			gl.glGetFloatv(target, &value);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetFloatv");
+
+			if (value.isUndefined())
+			{
+				gl.getLog() << tcu::TestLog::Message << "Get* did not return a value." << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else if (value > maxValue)
+			{
+				gl.getLog() << tcu::TestLog::Message << "Expected less or equal to " << maxValue << ", got " << value << tcu::TestLog::EndMessage;
+				return false;
+			}
+			else
+				return true;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return DE_NULL;
+	}
+}
+
+class SampleMaskCase : public TestCase
+{
+public:
+					SampleMaskCase	(Context& context, const char* name, const char* desc);
+
+private:
+	void			init			(void);
+	IterateResult	iterate			(void);
+
+	int				m_maxSampleMaskWords;
+};
+
+SampleMaskCase::SampleMaskCase (Context& context, const char* name, const char* desc)
+	: TestCase				(context, name, desc)
+	, m_maxSampleMaskWords	(-1)
+{
+}
+
+void SampleMaskCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &m_maxSampleMaskWords);
+	m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_SAMPLE_MASK_WORDS = " << m_maxSampleMaskWords << tcu::TestLog::EndMessage;
+}
+
+SampleMaskCase::IterateResult SampleMaskCase::iterate (void)
+{
+	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	bool				error	= false;
+
+	gl.enableLogging(true);
+
+	// mask word count ok?
+	if (m_maxSampleMaskWords <= 0)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Minimum value of GL_MAX_SAMPLE_MASK_WORDS is 1. Got " << m_maxSampleMaskWords << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid limit value");
+		return STOP;
+	}
+
+	// initial values
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial values");
+
+		for (int ndx = 0; ndx < m_maxSampleMaskWords; ++ndx)
+		{
+			glw::GLint word = 0;
+			gl.glGetIntegeri_v(GL_SAMPLE_MASK_VALUE, ndx, &word);
+
+			if (word != -1)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Expected all bits set (-1), got " << word << tcu::TestLog::EndMessage;
+				error = true;
+			}
+		}
+	}
+
+	// random masks
+	{
+		const int					numRandomTest	= 20;
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "random", "Random values");
+		de::Random					rnd				(0x4312);
+
+		for (int testNdx = 0; testNdx < numRandomTest; ++testNdx)
+		{
+			const glw::GLint	maskIndex		= (glw::GLint)(rnd.getUint32() % m_maxSampleMaskWords);
+			glw::GLint			mask			= (glw::GLint)(rnd.getUint32());
+			glw::GLint			queriedMask		= 0;
+
+			gl.glSampleMaski(maskIndex, mask);
+			gl.glGetIntegeri_v(GL_SAMPLE_MASK_VALUE, maskIndex, &queriedMask);
+
+			if (mask != queriedMask)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Expected " << mask << ", got " << queriedMask << tcu::TestLog::EndMessage;
+				error = true;
+			}
+		}
+	}
+
+	if (!error)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid mask value");
+
+	return STOP;
+}
+
+class MaxSamplesCase : public TestCase
+{
+public:
+						MaxSamplesCase	(Context& context, const char* name, const char* desc, glw::GLenum target, int minValue, VerifierType verifierType);
+private:
+	IterateResult		iterate			(void);
+
+	const glw::GLenum	m_target;
+	const int			m_minValue;
+	const VerifierType	m_verifierType;
+};
+
+MaxSamplesCase::MaxSamplesCase (Context& context, const char* name, const char* desc, glw::GLenum target, int minValue, VerifierType verifierType)
+	: TestCase			(context, name, desc)
+	, m_target			(target)
+	, m_minValue		(minValue)
+	, m_verifierType	(verifierType)
+{
+}
+
+MaxSamplesCase::IterateResult MaxSamplesCase::iterate (void)
+{
+	glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	gl.enableLogging(true);
+
+	if (verifyMinValue(gl, m_target, m_minValue, m_verifierType))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Value not in legal range");
+	return STOP;
+}
+
+class TexBindingCase : public TestCase
+{
+public:
+						TexBindingCase	(Context& context, const char* name, const char* desc, glw::GLenum texTarget, glw::GLenum bindTarget, VerifierType verifierType);
+private:
+	void				init			(void);
+	IterateResult		iterate			(void);
+
+	const glw::GLenum	m_texTarget;
+	const glw::GLenum	m_bindTarget;
+	const VerifierType	m_verifierType;
+};
+
+TexBindingCase::TexBindingCase (Context& context, const char* name, const char* desc, glw::GLenum texTarget, glw::GLenum bindTarget, VerifierType verifierType)
+	: TestCase			(context, name, desc)
+	, m_texTarget		(texTarget)
+	, m_bindTarget		(bindTarget)
+	, m_verifierType	(verifierType)
+{
+}
+
+void TexBindingCase::init (void)
+{
+	if (m_texTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
+		throw tcu::NotSupportedError("Test requires OES_texture_storage_multisample_2d_array extension");
+}
+
+TexBindingCase::IterateResult TexBindingCase::iterate (void)
+{
+	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	bool				allOk	= true;
+
+	gl.enableLogging(true);
+
+	// initial
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial value");
+
+		allOk &= verifyValue(gl, m_bindTarget, 0, m_verifierType);
+	}
+
+	// bind
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "bind", "After bind");
+
+		glw::GLuint texture;
+
+		gl.glGenTextures(1, &texture);
+		gl.glBindTexture(m_texTarget, texture);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind texture");
+
+		allOk &= verifyValue(gl, m_bindTarget, texture, m_verifierType);
+
+		gl.glDeleteTextures(1, &texture);
+	}
+
+	// after delete
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "bind", "After delete");
+
+		allOk &= verifyValue(gl, m_bindTarget, 0, m_verifierType);
+	}
+
+	if (allOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	return STOP;
+}
+
+class MinimumValueCase : public TestCase
+{
+public:
+						MinimumValueCase	(Context& context, const char* name, const char* desc, glw::GLenum target, int minValue, VerifierType verifierType);
+private:
+	IterateResult		iterate				(void);
+
+	const glw::GLenum	m_target;
+	const int			m_minValue;
+	const VerifierType	m_verifierType;
+};
+
+MinimumValueCase::MinimumValueCase (Context& context, const char* name, const char* desc, glw::GLenum target, int minValue, VerifierType verifierType)
+	: TestCase			(context, name, desc)
+	, m_target			(target)
+	, m_minValue		(minValue)
+	, m_verifierType	(verifierType)
+{
+}
+
+MinimumValueCase::IterateResult MinimumValueCase::iterate (void)
+{
+	glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	gl.enableLogging(true);
+
+	if (verifyMinValue(gl, m_target, m_minValue, m_verifierType))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	return STOP;
+}
+
+class AlignmentCase : public TestCase
+{
+public:
+						AlignmentCase	(Context& context, const char* name, const char* desc, glw::GLenum target, int minValue, VerifierType verifierType);
+private:
+	IterateResult		iterate			(void);
+
+	const glw::GLenum	m_target;
+	const int			m_minValue;
+	const VerifierType	m_verifierType;
+};
+
+AlignmentCase::AlignmentCase (Context& context, const char* name, const char* desc, glw::GLenum target, int minValue, VerifierType verifierType)
+	: TestCase			(context, name, desc)
+	, m_target			(target)
+	, m_minValue		(minValue)
+	, m_verifierType	(verifierType)
+{
+}
+
+AlignmentCase::IterateResult AlignmentCase::iterate (void)
+{
+	glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	gl.enableLogging(true);
+
+	if (verifyMaxValue(gl, m_target, m_minValue, m_verifierType))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	return STOP;
+}
+
+} // anonymous
+
+IntegerStateQueryTests::IntegerStateQueryTests (Context& context)
+	: TestCaseGroup(context, "integer", "Integer state query tests")
+{
+}
+
+IntegerStateQueryTests::~IntegerStateQueryTests (void)
+{
+}
+
+void IntegerStateQueryTests::init (void)
+{
+	// Verifiers
+	const VerifierType verifiers[] = { VERIFIER_GETBOOLEAN, VERIFIER_GETINTEGER, VERIFIER_GETINTEGER64, VERIFIER_GETFLOAT };
+
+#define FOR_EACH_VERIFIER(X) \
+	for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)	\
+	{																						\
+		const char* verifierSuffix = getVerifierSuffix(verifiers[verifierNdx]);				\
+		const VerifierType verifier = verifiers[verifierNdx];								\
+		this->addChild(X);																	\
+	}
+
+	// No additional verifiers for sample_mask_value
+	this->addChild(new SampleMaskCase(m_context, "sample_mask_value", "Test sample mask value"));
+
+	FOR_EACH_VERIFIER(new MaxSamplesCase(m_context,		(std::string() + "max_color_texture_samples_" + verifierSuffix).c_str(),				"Test GL_MAX_COLOR_TEXTURE_SAMPLES",			GL_MAX_COLOR_TEXTURE_SAMPLES,		1,	verifier))
+	FOR_EACH_VERIFIER(new MaxSamplesCase(m_context,		(std::string() + "max_depth_texture_samples_" + verifierSuffix).c_str(),				"Test GL_MAX_DEPTH_TEXTURE_SAMPLES",			GL_MAX_DEPTH_TEXTURE_SAMPLES,		1,	verifier))
+	FOR_EACH_VERIFIER(new MaxSamplesCase(m_context,		(std::string() + "max_integer_samples_" + verifierSuffix).c_str(),						"Test GL_MAX_INTEGER_SAMPLES",					GL_MAX_INTEGER_SAMPLES,				1,	verifier))
+
+	FOR_EACH_VERIFIER(new TexBindingCase(m_context,		(std::string() + "texture_binding_2d_multisample_" + verifierSuffix).c_str(),			"Test TEXTURE_BINDING_2D_MULTISAMPLE",			GL_TEXTURE_2D_MULTISAMPLE,			GL_TEXTURE_BINDING_2D_MULTISAMPLE,			verifier))
+	FOR_EACH_VERIFIER(new TexBindingCase(m_context,		(std::string() + "texture_binding_2d_multisample_array_" + verifierSuffix).c_str(),		"Test TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY",	GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY,	verifier))
+
+	FOR_EACH_VERIFIER(new MinimumValueCase(m_context,	(std::string() + "max_vertex_attrib_relative_offset_" + verifierSuffix).c_str(),		"Test MAX_VERTEX_ATTRIB_RELATIVE_OFFSET",		GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET,	2047,	verifier))
+	FOR_EACH_VERIFIER(new MinimumValueCase(m_context,	(std::string() + "max_vertex_attrib_bindings_" + verifierSuffix).c_str(),				"Test MAX_VERTEX_ATTRIB_BINDINGS",				GL_MAX_VERTEX_ATTRIB_BINDINGS,			16,		verifier))
+	FOR_EACH_VERIFIER(new MinimumValueCase(m_context,	(std::string() + "max_vertex_attrib_stride_" + verifierSuffix).c_str(),					"Test MAX_VERTEX_ATTRIB_STRIDE",				GL_MAX_VERTEX_ATTRIB_STRIDE,			2048,	verifier))
+
+	FOR_EACH_VERIFIER(new AlignmentCase(m_context,		(std::string() + "shader_storage_buffer_offset_alignment_" + verifierSuffix).c_str(),	"Test SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT",	GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT,	256,	verifier))
+
+#undef FOR_EACH_VERIFIER
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fIntegerStateQueryTests.hpp b/modules/gles31/functional/es31fIntegerStateQueryTests.hpp
new file mode 100644
index 0000000..b28d9d6
--- /dev/null
+++ b/modules/gles31/functional/es31fIntegerStateQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FINTEGERSTATEQUERYTESTS_HPP
+#define _ES31FINTEGERSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Integer state query tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class IntegerStateQueryTests : public TestCaseGroup
+{
+public:
+							IntegerStateQueryTests	(Context& context);
+							~IntegerStateQueryTests	(void);
+
+	void					init					(void);
+
+private:
+							IntegerStateQueryTests	(const IntegerStateQueryTests& other);
+	IntegerStateQueryTests&	operator=				(const IntegerStateQueryTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FINTEGERSTATEQUERYTESTS_HPP
diff --git a/modules/gles31/functional/es31fInternalFormatQueryTests.cpp b/modules/gles31/functional/es31fInternalFormatQueryTests.cpp
new file mode 100644
index 0000000..37d85c3
--- /dev/null
+++ b/modules/gles31/functional/es31fInternalFormatQueryTests.cpp
@@ -0,0 +1,313 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Internal format query tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fInternalFormatQueryTests.hpp"
+#include "tcuTestLog.hpp"
+#include "gluRenderContext.hpp"
+#include "gluStrUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+class FormatSamplesCase : public TestCase
+{
+public:
+	enum FormatType
+	{
+		FORMAT_COLOR,
+		FORMAT_INT,
+		FORMAT_DEPTH_STENCIL
+	};
+
+						FormatSamplesCase	(Context& ctx, const char* name, const char* desc, glw::GLenum texTarget, glw::GLenum internalFormat, FormatType type);
+private:
+	void				init				(void);
+	IterateResult		iterate				(void);
+
+	void				testMultisample		(void);
+	void				testSinglesample	(void);
+
+	const glw::GLenum	m_texTarget;
+	const glw::GLenum	m_internalFormat;
+	const FormatType	m_type;
+};
+
+FormatSamplesCase::FormatSamplesCase (Context& ctx, const char* name, const char* desc, glw::GLenum texTarget, glw::GLenum internalFormat, FormatType type)
+	: TestCase			(ctx, name, desc)
+	, m_texTarget		(texTarget)
+	, m_internalFormat	(internalFormat)
+	, m_type			(type)
+{
+}
+
+void FormatSamplesCase::init (void)
+{
+	if (m_texTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
+		throw tcu::NotSupportedError("Test requires OES_texture_storage_multisample_2d_array extension");
+}
+
+FormatSamplesCase::IterateResult FormatSamplesCase::iterate (void)
+{
+	if (m_texTarget == GL_TEXTURE_2D_MULTISAMPLE || m_texTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY || m_texTarget == GL_RENDERBUFFER)
+		testMultisample();
+	else
+		testSinglesample();
+
+	return STOP;
+}
+
+void FormatSamplesCase::testMultisample (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	bool					error			= false;
+	glw::GLint				maxSamples		= 0;
+	glw::GLint				numSampleCounts	= 0;
+
+	// Lowest limit
+	{
+		const glw::GLenum samplesEnum = (m_type == FORMAT_COLOR) ? (GL_MAX_COLOR_TEXTURE_SAMPLES) : (m_type == FORMAT_INT) ? (GL_MAX_INTEGER_SAMPLES) : (GL_MAX_DEPTH_TEXTURE_SAMPLES);
+		m_testCtx.getLog() << tcu::TestLog::Message << "Format must support sample count of " << glu::getGettableStateStr(samplesEnum) << tcu::TestLog::EndMessage;
+
+		gl.getIntegerv(samplesEnum, &maxSamples);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "get MAX_*_SAMPLES");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << glu::getGettableStateStr(samplesEnum) << " = " << maxSamples << tcu::TestLog::EndMessage;
+
+		if (maxSamples < 1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: minimum value of "  << glu::getGettableStateStr(samplesEnum) << " is 1" << tcu::TestLog::EndMessage;
+			error = true;
+		}
+	}
+
+	// Number of sample counts
+	{
+		gl.getInternalformativ(m_texTarget, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_NUM_SAMPLE_COUNTS");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_NUM_SAMPLE_COUNTS = " << numSampleCounts << tcu::TestLog::EndMessage;
+
+		if (numSampleCounts < 1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Format MUST support some multisample configuration, got GL_NUM_SAMPLE_COUNTS = " << numSampleCounts << tcu::TestLog::EndMessage;
+			error = true;
+		}
+	}
+
+	// Sample counts
+	{
+		tcu::MessageBuilder		samplesMsg(&m_testCtx.getLog());
+		std::vector<glw::GLint>	samples;
+
+		if (numSampleCounts > 0)
+		{
+			samples.resize(numSampleCounts, -1);
+
+			gl.getInternalformativ(m_texTarget, m_internalFormat, GL_SAMPLES, numSampleCounts, &samples[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_SAMPLES");
+		}
+		else
+			TCU_FAIL("glGetInternalFormativ() reported 0 supported sample counts");
+
+		// make a pretty log
+
+		samplesMsg << "GL_SAMPLES = [";
+		for (int ndx = 0; ndx < numSampleCounts; ++ndx)
+		{
+			if (ndx)
+				samplesMsg << ", ";
+			samplesMsg << samples[ndx];
+		}
+		samplesMsg << "]" << tcu::TestLog::EndMessage;
+
+		// Samples are in order
+		for (int ndx = 1; ndx < numSampleCounts; ++ndx)
+		{
+			if (samples[ndx-1] <= samples[ndx])
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Samples must be ordered descending." << tcu::TestLog::EndMessage;
+				error = true;
+				break;
+			}
+		}
+
+		// samples are positive
+		for (int ndx = 1; ndx < numSampleCounts; ++ndx)
+		{
+			if (samples[ndx-1] <= 0)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Only positive SAMPLES allowed." << tcu::TestLog::EndMessage;
+				error = true;
+				break;
+			}
+		}
+
+		// maxSamples must be supported
+		if (samples[0] < maxSamples)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: MAX_*_SAMPLES must be supported." << tcu::TestLog::EndMessage;
+			error = true;
+		}
+	}
+
+	// Result
+	if (!error)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid value");
+}
+
+void FormatSamplesCase::testSinglesample (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const int				defValue		= -123;
+	glw::GLint				numSampleCounts	= defValue;
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Quering GL_NUM_SAMPLE_COUNTS with target = " << glu::getInternalFormatTargetName(m_texTarget) << ", internal format = " << glu::getPixelFormatName(m_internalFormat) << "\n"
+		<< "Expecting 0."
+		<< tcu::TestLog::EndMessage;
+
+	gl.getInternalformativ(m_texTarget, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_NUM_SAMPLE_COUNTS");
+
+	if (numSampleCounts == 0)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_NUM_SAMPLE_COUNTS = " << numSampleCounts << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	else if (numSampleCounts == defValue)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "getInternalformativ did not return a value." << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "No value");
+	}
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_NUM_SAMPLE_COUNTS = " << numSampleCounts << ", expected 0" << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid value");
+	}
+}
+
+} // anonymous
+
+InternalFormatQueryTests::InternalFormatQueryTests (Context& context)
+	: TestCaseGroup(context, "internal_format", "Internal format queries")
+{
+}
+
+InternalFormatQueryTests::~InternalFormatQueryTests (void)
+{
+}
+
+void InternalFormatQueryTests::init (void)
+{
+	static const struct InternalFormat
+	{
+		const char*						name;
+		glw::GLenum						format;
+		FormatSamplesCase::FormatType	type;
+	} internalFormats[] =
+	{
+		// color renderable
+		{ "r8",						GL_R8,					FormatSamplesCase::FORMAT_COLOR			},
+		{ "rg8",					GL_RG8,					FormatSamplesCase::FORMAT_COLOR			},
+		{ "rgb8",					GL_RGB8,				FormatSamplesCase::FORMAT_COLOR			},
+		{ "rgb565",					GL_RGB565,				FormatSamplesCase::FORMAT_COLOR			},
+		{ "rgba4",					GL_RGBA4,				FormatSamplesCase::FORMAT_COLOR			},
+		{ "rgb5_a1",				GL_RGB5_A1,				FormatSamplesCase::FORMAT_COLOR			},
+		{ "rgba8",					GL_RGBA8,				FormatSamplesCase::FORMAT_COLOR			},
+		{ "rgb10_a2",				GL_RGB10_A2,			FormatSamplesCase::FORMAT_COLOR			},
+		{ "rgb10_a2ui",				GL_RGB10_A2UI,			FormatSamplesCase::FORMAT_INT			},
+		{ "srgb8_alpha8",			GL_SRGB8_ALPHA8,		FormatSamplesCase::FORMAT_COLOR			},
+		{ "r8i",					GL_R8I,					FormatSamplesCase::FORMAT_INT			},
+		{ "r8ui",					GL_R8UI,				FormatSamplesCase::FORMAT_INT			},
+		{ "r16i",					GL_R16I,				FormatSamplesCase::FORMAT_INT			},
+		{ "r16ui",					GL_R16UI,				FormatSamplesCase::FORMAT_INT			},
+		{ "r32i",					GL_R32I,				FormatSamplesCase::FORMAT_INT			},
+		{ "r32ui",					GL_R32UI,				FormatSamplesCase::FORMAT_INT			},
+		{ "rg8i",					GL_RG8I,				FormatSamplesCase::FORMAT_INT			},
+		{ "rg8ui",					GL_RG8UI,				FormatSamplesCase::FORMAT_INT			},
+		{ "rg16i",					GL_RG16I,				FormatSamplesCase::FORMAT_INT			},
+		{ "rg16ui",					GL_RG16UI,				FormatSamplesCase::FORMAT_INT			},
+		{ "rg32i",					GL_RG32I,				FormatSamplesCase::FORMAT_INT			},
+		{ "rg32ui",					GL_RG32UI,				FormatSamplesCase::FORMAT_INT			},
+		{ "rgba8i",					GL_RGBA8I,				FormatSamplesCase::FORMAT_INT			},
+		{ "rgba8ui",				GL_RGBA8UI,				FormatSamplesCase::FORMAT_INT			},
+		{ "rgba16i",				GL_RGBA16I,				FormatSamplesCase::FORMAT_INT			},
+		{ "rgba16ui",				GL_RGBA16UI,			FormatSamplesCase::FORMAT_INT			},
+		{ "rgba32i",				GL_RGBA32I,				FormatSamplesCase::FORMAT_INT			},
+		{ "rgba32ui",				GL_RGBA32UI,			FormatSamplesCase::FORMAT_INT			},
+
+		// depth renderable
+		{ "depth_component16",		GL_DEPTH_COMPONENT16,	FormatSamplesCase::FORMAT_DEPTH_STENCIL	},
+		{ "depth_component24",		GL_DEPTH_COMPONENT24,	FormatSamplesCase::FORMAT_DEPTH_STENCIL	},
+		{ "depth_component32f",		GL_DEPTH_COMPONENT32F,	FormatSamplesCase::FORMAT_DEPTH_STENCIL	},
+		{ "depth24_stencil8",		GL_DEPTH24_STENCIL8,	FormatSamplesCase::FORMAT_DEPTH_STENCIL	},
+		{ "depth32f_stencil8",		GL_DEPTH32F_STENCIL8,	FormatSamplesCase::FORMAT_DEPTH_STENCIL	},
+
+		// stencil renderable
+		{ "stencil_index8",			GL_STENCIL_INDEX8,		FormatSamplesCase::FORMAT_DEPTH_STENCIL	}
+		// DEPTH24_STENCIL8,  duplicate
+		// DEPTH32F_STENCIL8  duplicate
+	};
+
+	static const struct
+	{
+		const char*	name;
+		deUint32	target;
+	} textureTargets[] =
+	{
+		{ "texture_2d_multisample",			GL_TEXTURE_2D_MULTISAMPLE		},
+		{ "texture_2d_multisample_array",	GL_TEXTURE_2D_MULTISAMPLE_ARRAY	},
+	};
+
+	for (int groupNdx = 0; groupNdx < DE_LENGTH_OF_ARRAY(textureTargets); ++groupNdx)
+	{
+		tcu::TestCaseGroup* const	group		= new tcu::TestCaseGroup(m_testCtx, textureTargets[groupNdx].name, glu::getInternalFormatTargetName(textureTargets[groupNdx].target));
+		const glw::GLenum			texTarget	= textureTargets[groupNdx].target;
+
+		addChild(group);
+
+		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(internalFormats); ++caseNdx)
+		{
+			const std::string name = std::string(internalFormats[caseNdx].name) + "_samples";
+			const std::string desc = std::string("Verify GL_SAMPLES of ") + internalFormats[caseNdx].name;
+
+			group->addChild(new FormatSamplesCase(m_context, name.c_str(), desc.c_str(), texTarget, internalFormats[caseNdx].format, internalFormats[caseNdx].type));
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fInternalFormatQueryTests.hpp b/modules/gles31/functional/es31fInternalFormatQueryTests.hpp
new file mode 100644
index 0000000..0b2de4b
--- /dev/null
+++ b/modules/gles31/functional/es31fInternalFormatQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FINTERNALFORMATQUERYTESTS_HPP
+#define _ES31FINTERNALFORMATQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Internal format query tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class InternalFormatQueryTests : public TestCaseGroup
+{
+public:
+								InternalFormatQueryTests	(Context& context);
+								~InternalFormatQueryTests	(void);
+
+	void						init						(void);
+
+private:
+								InternalFormatQueryTests	(const InternalFormatQueryTests& other);
+	InternalFormatQueryTests&	operator=					(const InternalFormatQueryTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FINTERNALFORMATQUERYTESTS_HPP
diff --git a/modules/gles31/functional/es31fLayoutBindingTests.cpp b/modules/gles31/functional/es31fLayoutBindingTests.cpp
new file mode 100644
index 0000000..c345412
--- /dev/null
+++ b/modules/gles31/functional/es31fLayoutBindingTests.cpp
@@ -0,0 +1,2017 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Basic Layout Binding Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fLayoutBindingTests.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "deString.h"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+enum TestType
+{
+	TESTTYPE_BINDING_SINGLE = 0,
+	TESTTYPE_BINDING_MAX,
+	TESTTYPE_BINDING_MULTIPLE,
+	TESTTYPE_BINDING_ARRAY,
+	TESTTYPE_BINDING_MAX_ARRAY,
+
+	TESTTYPE_BINDING_LAST,
+};
+
+enum ShaderType
+{
+	SHADERTYPE_VERTEX = 0,
+	SHADERTYPE_FRAGMENT,
+	SHADERTYPE_BOTH,
+
+	SHADERTYPE_LAST,
+};
+
+enum
+{
+	MAX_UNIFORM_MULTIPLE_INSTANCES	= 7,
+	MAX_UNIFORM_ARRAY_SIZE			= 7,
+};
+
+std::string generateVertexShader (ShaderType shaderType, const std::string& shaderUniformDeclarations, const std::string& shaderBody)
+{
+	static const char* const s_simpleVertexShaderSource	= 	"#version 310 es\n"
+															"in highp vec4 a_position;\n"
+															"void main (void)\n"
+															"{\n"
+															"	gl_Position = a_position;\n"
+															"}\n";
+
+	switch (shaderType)
+	{
+		case SHADERTYPE_VERTEX:
+		case SHADERTYPE_BOTH:
+		{
+			std::ostringstream vertexShaderSource;
+			vertexShaderSource	<< 	"#version 310 es\n"
+								<< 	"in highp vec4 a_position;\n"
+								<< 	"out highp vec4 v_color;\n"
+								<< 	"uniform highp int u_arrayNdx;\n\n"
+								<< 	shaderUniformDeclarations << "\n"
+								<< 	"void main (void)\n"
+								<<	"{\n"
+								<<	"	highp vec4 color;\n\n"
+								<< 	shaderBody << "\n"
+								<<	"	v_color = color;\n"
+								<<	"	gl_Position = a_position;\n"
+								<<	"}\n";
+
+			return vertexShaderSource.str();
+		}
+
+		case SHADERTYPE_FRAGMENT:
+			return s_simpleVertexShaderSource;
+
+		default:
+			DE_ASSERT(false);
+			return "";
+	}
+}
+
+std::string generateFragmentShader (ShaderType shaderType, const std::string& shaderUniformDeclarations, const std::string& shaderBody)
+{
+	static const char* const s_simpleFragmentShaderSource = "#version 310 es\n"
+															"in highp vec4 v_color;\n"
+															"layout(location = 0) out highp vec4 fragColor;\n"
+															"void main (void)\n"
+															"{\n"
+															"	fragColor = v_color;\n"
+															"}\n";
+
+	switch (shaderType)
+	{
+		case SHADERTYPE_VERTEX:
+			return s_simpleFragmentShaderSource;
+
+		case SHADERTYPE_FRAGMENT:
+		{
+			std::ostringstream fragmentShaderSource;
+			fragmentShaderSource	<<	"#version 310 es\n"
+									<<	"layout(location = 0) out highp vec4 fragColor;\n"
+									<<	"uniform highp int u_arrayNdx;\n\n"
+									<<	shaderUniformDeclarations << "\n"
+									<<	"void main (void)\n"
+									<<	"{\n"
+									<<	"	highp vec4 color;\n\n"
+									<<	shaderBody << "\n"
+									<<	"	fragColor = color;\n"
+									<<	"}\n";
+
+			return fragmentShaderSource.str();
+		}
+		case SHADERTYPE_BOTH:
+		{
+			std::ostringstream fragmentShaderSource;
+			fragmentShaderSource	<<	"#version 310 es\n"
+									<<	"in highp vec4 v_color;\n"
+									<<	"layout(location = 0) out highp vec4 fragColor;\n"
+									<<	"uniform highp int u_arrayNdx;\n\n"
+									<<	shaderUniformDeclarations << "\n"
+									<<	"void main (void)\n"
+									<<	"{\n"
+									<<	"	if (v_color.x > 2.0) discard;\n"
+									<<	"	highp vec4 color;\n\n"
+									<<	shaderBody << "\n"
+									<<	"	fragColor = color;\n"
+									<<	"}\n";
+
+			return fragmentShaderSource.str();
+		}
+
+		default:
+			DE_ASSERT(false);
+			return "";
+	}
+}
+
+std::string getUniformName (const std::string& name, int declNdx)
+{
+	return name + de::toString(declNdx);
+}
+
+std::string getUniformName (const std::string& name, int declNdx, int arrNdx)
+{
+	return name + de::toString(declNdx) + "[" + de::toString(arrNdx) + "]";
+}
+
+Vec4 getRandomColor (de::Random& rnd)
+{
+	const float r = rnd.getFloat(0.2f, 0.9f);
+	const float g = rnd.getFloat(0.2f, 0.9f);
+	const float b = rnd.getFloat(0.2f, 0.9f);
+	return Vec4(r, g, b, 1.0f);
+}
+
+class LayoutBindingRenderCase : public TestCase
+{
+public:
+	enum
+	{
+		TEST_RENDER_WIDTH	= 256,
+		TEST_RENDER_HEIGHT	= 256,
+		TEST_TEXTURE_SIZE	= 1,
+	};
+
+										LayoutBindingRenderCase			(Context&			context,
+																		 const char*		name,
+																		 const char*		desc,
+																		 ShaderType			shaderType,
+																		 TestType			testType,
+																		 glw::GLenum		maxBindingPointEnum,
+																		 glw::GLenum		maxVertexUnitsEnum,
+																		 glw::GLenum		maxFragmentUnitsEnum,
+																		 glw::GLenum		maxCombinedUnitsEnum,
+																		 const std::string& uniformName);
+	virtual								~LayoutBindingRenderCase		(void);
+
+	virtual void 						init							(void);
+	virtual void 						deinit							(void);
+
+protected:
+	virtual glu::ShaderProgram*			generateShaders					(void) const = 0;
+
+	void								initRenderState					(void);
+	bool								drawAndVerifyResult				(const Vec4& expectedColor);
+	void								setTestResult					(bool queryTestPassed, bool imageTestPassed);
+
+	const glu::ShaderProgram*			m_program;
+	const ShaderType					m_shaderType;
+	const TestType						m_testType;
+	const std::string					m_uniformName;
+
+	const glw::GLenum					m_maxBindingPointEnum;
+	const glw::GLenum					m_maxVertexUnitsEnum;
+	const glw::GLenum					m_maxFragmentUnitsEnum;
+	const glw::GLenum					m_maxCombinedUnitsEnum;
+
+	glw::GLuint							m_vertexBuffer;
+	glw::GLuint							m_indexBuffer;
+	glw::GLint							m_shaderProgramLoc;
+	glw::GLint							m_shaderProgramPosLoc;
+	glw::GLint							m_shaderProgramArrayNdxLoc;
+	glw::GLint							m_numBindings;
+
+	std::vector<glw::GLint>				m_bindings;
+
+private:
+	void								initBindingPoints				(int minBindingPoint, int numBindingPoints);
+};
+
+LayoutBindingRenderCase::LayoutBindingRenderCase (Context&				context,
+												  const char*			name,
+												  const char*			desc,
+												  ShaderType			shaderType,
+												  TestType				testType,
+												  glw::GLenum			maxBindingPointEnum,
+												  glw::GLenum			maxVertexUnitsEnum,
+												  glw::GLenum			maxFragmentUnitsEnum,
+												  glw::GLenum			maxCombinedUnitsEnum,
+												  const std::string&	uniformName)
+	: TestCase						(context, name, desc)
+	, m_program						(DE_NULL)
+	, m_shaderType					(shaderType)
+	, m_testType					(testType)
+	, m_uniformName					(uniformName)
+	, m_maxBindingPointEnum			(maxBindingPointEnum)
+	, m_maxVertexUnitsEnum			(maxVertexUnitsEnum)
+	, m_maxFragmentUnitsEnum		(maxFragmentUnitsEnum)
+	, m_maxCombinedUnitsEnum		(maxCombinedUnitsEnum)
+	, m_vertexBuffer				(0)
+	, m_indexBuffer					(0)
+	, m_shaderProgramLoc			(0)
+	, m_shaderProgramPosLoc			(0)
+	, m_shaderProgramArrayNdxLoc	(0)
+	, m_numBindings					(0)
+{
+}
+
+LayoutBindingRenderCase::~LayoutBindingRenderCase (void)
+{
+	deinit();
+}
+
+void LayoutBindingRenderCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	{
+		de::Random				rnd					(deStringHash(getName()) ^ 0xff23a4);
+		glw::GLint				numBindingPoints	= 0;	// Number of available binding points
+		glw::GLint				maxVertexUnits		= 0;	// Available uniforms in the vertex shader
+		glw::GLint				maxFragmentUnits	= 0;	// Available uniforms in the fragment shader
+		glw::GLint				maxCombinedUnits	= 0;	// Available uniforms in all the shader stages combined
+		glw::GLint				maxUnits			= 0;	// Maximum available uniforms for this test
+
+		gl.getIntegerv(m_maxVertexUnitsEnum, &maxVertexUnits);
+		gl.getIntegerv(m_maxFragmentUnitsEnum, &maxFragmentUnits);
+		gl.getIntegerv(m_maxCombinedUnitsEnum, &maxCombinedUnits);
+		gl.getIntegerv(m_maxBindingPointEnum, &numBindingPoints);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Querying available uniform numbers failed");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Maximum units for uniform type in the vertex shader: " << maxVertexUnits << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "Maximum units for uniform type in the fragment shader: " << maxFragmentUnits << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "Maximum combined units for uniform type: " << maxCombinedUnits << tcu::TestLog::EndMessage;
+		m_testCtx.getLog() << tcu::TestLog::Message << "Maximum binding point for uniform type: " << numBindingPoints-1 << tcu::TestLog::EndMessage;
+
+		// Select maximum number of uniforms used for the test
+		switch (m_shaderType)
+		{
+			case SHADERTYPE_VERTEX:
+				maxUnits = maxVertexUnits;
+				break;
+
+			case SHADERTYPE_FRAGMENT:
+				maxUnits = maxFragmentUnits;
+				break;
+
+			case SHADERTYPE_BOTH:
+				maxUnits = maxCombinedUnits/2;
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		// Select the number of uniforms (= bindings) used for this test
+		switch (m_testType)
+		{
+			case TESTTYPE_BINDING_SINGLE:
+			case TESTTYPE_BINDING_MAX:
+				m_numBindings = 1;
+				break;
+
+			case TESTTYPE_BINDING_MULTIPLE:
+				if (maxUnits < 2)
+					throw tcu::NotSupportedError("Not enough uniforms available for test");
+				m_numBindings = rnd.getInt(2, deMin32(MAX_UNIFORM_MULTIPLE_INSTANCES, maxUnits));
+				break;
+
+			case TESTTYPE_BINDING_ARRAY:
+			case TESTTYPE_BINDING_MAX_ARRAY:
+				if (maxUnits < 2)
+					throw tcu::NotSupportedError("Not enough uniforms available for test");
+				m_numBindings = rnd.getInt(2, deMin32(MAX_UNIFORM_ARRAY_SIZE, maxUnits));
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		// Check that we have enough uniforms in different shaders to perform the tests
+		if ( ((m_shaderType == SHADERTYPE_VERTEX) || (m_shaderType == SHADERTYPE_BOTH)) && (maxVertexUnits < m_numBindings) )
+			throw tcu::NotSupportedError("Vertex shader: not enough uniforms available for test");
+		if ( ((m_shaderType == SHADERTYPE_FRAGMENT) || (m_shaderType == SHADERTYPE_BOTH)) && (maxFragmentUnits < m_numBindings) )
+			throw tcu::NotSupportedError("Fragment shader: not enough uniforms available for test");
+		if ( (m_shaderType == SHADERTYPE_BOTH) && (maxCombinedUnits < m_numBindings*2) )
+			throw tcu::NotSupportedError("Not enough uniforms available for test");
+
+		// Check that we have enough binding points to perform the tests
+		if (numBindingPoints < m_numBindings)
+			throw tcu::NotSupportedError("Not enough binding points available for test");
+
+		// Initialize the binding points i.e. populate the two binding point vectors
+		initBindingPoints(0, numBindingPoints);
+	}
+
+	// Generate the shader program - note: this must be done after deciding the binding points
+	DE_ASSERT(!m_program);
+	m_testCtx.getLog() << tcu::TestLog::Message << "Creating test shaders" << tcu::TestLog::EndMessage;
+	m_program = generateShaders();
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+		throw tcu::TestError("Shader compile failed");
+
+	// Setup vertex and index buffers
+	{
+		// Get attribute and uniform locations
+		const deUint32 	program	= m_program->getProgram();
+
+		m_shaderProgramPosLoc		= gl.getAttribLocation(program, "a_position");
+		m_shaderProgramArrayNdxLoc	= gl.getUniformLocation(program, "u_arrayNdx");
+		m_vertexBuffer				= 0;
+		m_indexBuffer				= 0;
+
+		// Setup buffers so that we render one quad covering the whole viewport
+		const Vec3 vertices[] =
+		{
+			Vec3(-1.0f, -1.0f, +1.0f),
+			Vec3(+1.0f, -1.0f, +1.0f),
+			Vec3(+1.0f, +1.0f, +1.0f),
+			Vec3(-1.0f, +1.0f, +1.0f),
+		};
+
+		const deUint16 indices[] =
+		{
+			0, 1, 2,
+			0, 2, 3,
+		};
+
+		TCU_CHECK((m_shaderProgramPosLoc >= 0) && (m_shaderProgramArrayNdxLoc >= 0));
+
+		// Generate and bind index buffer
+		gl.genBuffers(1, &m_indexBuffer);
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (DE_LENGTH_OF_ARRAY(indices)*(glw::GLsizeiptr)sizeof(indices[0])), &indices[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Index buffer setup failed");
+
+		// Generate and bind vertex buffer
+		gl.genBuffers(1, &m_vertexBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (DE_LENGTH_OF_ARRAY(vertices)*(glw::GLsizeiptr)sizeof(vertices[0])), &vertices[0], GL_STATIC_DRAW);
+		gl.enableVertexAttribArray(m_shaderProgramPosLoc);
+		gl.vertexAttribPointer(m_shaderProgramPosLoc, 3, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Vertex buffer setup failed");
+	}
+}
+
+void LayoutBindingRenderCase::deinit (void)
+{
+	if (m_program)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+	}
+
+	if (m_shaderProgramPosLoc)
+		m_context.getRenderContext().getFunctions().disableVertexAttribArray(m_shaderProgramPosLoc);
+
+	if (m_vertexBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_vertexBuffer);
+		m_context.getRenderContext().getFunctions().bindBuffer(GL_ARRAY_BUFFER, 0);
+	}
+
+	if (m_indexBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_indexBuffer);
+		m_context.getRenderContext().getFunctions().bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+	}
+}
+
+void LayoutBindingRenderCase::initBindingPoints (int minBindingPoint, int numBindingPoints)
+{
+	de::Random rnd(deStringHash(getName()) ^ 0xff23a4);
+
+	switch (m_testType)
+	{
+		case TESTTYPE_BINDING_SINGLE:
+		{
+			const int bpoint = rnd.getInt(minBindingPoint, numBindingPoints-1);
+			m_bindings.push_back(bpoint);
+			break;
+		}
+
+		case TESTTYPE_BINDING_MAX:
+			m_bindings.push_back(numBindingPoints-1);
+			break;
+
+		case TESTTYPE_BINDING_MULTIPLE:
+		{
+			// Choose multiple unique binding points from the low and high end of available binding points
+			std::vector<deUint32> lowBindingPoints;
+			std::vector<deUint32> highBindingPoints;
+
+			for (int bpoint = 0; bpoint < numBindingPoints/2; ++bpoint)
+				lowBindingPoints.push_back(bpoint);
+			for (int bpoint = numBindingPoints/2; bpoint < numBindingPoints; ++bpoint)
+				highBindingPoints.push_back(bpoint);
+
+			rnd.shuffle(lowBindingPoints.begin(), lowBindingPoints.end());
+			rnd.shuffle(highBindingPoints.begin(), highBindingPoints.end());
+
+			for (int ndx = 0; ndx < m_numBindings; ++ndx)
+			{
+				if (ndx%2 == 0)
+				{
+					const int bpoint = lowBindingPoints.back();
+					lowBindingPoints.pop_back();
+					m_bindings.push_back(bpoint);
+				}
+				else
+				{
+					const int bpoint = highBindingPoints.back();
+					highBindingPoints.pop_back();
+					m_bindings.push_back(bpoint);
+				}
+
+			}
+			break;
+		}
+
+		case TESTTYPE_BINDING_ARRAY:
+		{
+			const glw::GLint binding = rnd.getInt(minBindingPoint, numBindingPoints-m_numBindings);
+			for (int ndx = 0; ndx < m_numBindings; ++ndx)
+				m_bindings.push_back(binding+ndx);
+			break;
+		}
+
+		case TESTTYPE_BINDING_MAX_ARRAY:
+		{
+			const glw::GLint binding = numBindingPoints-m_numBindings;
+			for (int ndx = 0; ndx < m_numBindings; ++ndx)
+				m_bindings.push_back(binding+ndx);
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+void LayoutBindingRenderCase::initRenderState (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.useProgram(m_program->getProgram());
+	gl.viewport(0, 0, TEST_RENDER_WIDTH, TEST_RENDER_HEIGHT);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set render state");
+}
+
+bool LayoutBindingRenderCase::drawAndVerifyResult (const Vec4& expectedColor)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const tcu::RGBA			threshold	= m_context.getRenderContext().getRenderTarget().getPixelFormat().getColorThreshold();
+	tcu::Surface			reference	(TEST_RENDER_WIDTH, TEST_RENDER_HEIGHT);
+
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	// Draw
+	gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, DE_NULL);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Drawing failed");
+
+	// Verify
+	tcu::Surface result(TEST_RENDER_WIDTH, TEST_RENDER_HEIGHT);
+	m_testCtx.getLog() << TestLog::Message << "Reading pixels" << TestLog::EndMessage;
+	glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels failed");
+
+	tcu::clear(reference.getAccess(), expectedColor);
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output image" << tcu::TestLog::EndMessage;
+
+	return tcu::pixelThresholdCompare(m_testCtx.getLog(), "Render result", "Result verification", reference, result, threshold, tcu::COMPARE_LOG_RESULT);
+}
+
+void LayoutBindingRenderCase::setTestResult (bool queryTestPassed, bool imageTestPassed)
+{
+	if (queryTestPassed && imageTestPassed)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else if (!queryTestPassed && !imageTestPassed)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "One or more binding point queries and image comparisons failed");
+	else if (!queryTestPassed)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "One or more binding point queries failed");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "One or more image comparisons failed");
+}
+
+class LayoutBindingNegativeCase : public TestCase
+{
+public:
+	enum ErrorType
+	{
+		ERRORTYPE_OVER_MAX_UNITS = 0,
+		ERRORTYPE_LESS_THAN_ZERO,
+		ERRORTYPE_CONTRADICTORY,
+
+		ERRORTYPE_LAST,
+	};
+
+										LayoutBindingNegativeCase		(Context&			context,
+																		 const char*		name,
+																		 const char*		desc,
+																		 ShaderType			shaderType,
+																		 TestType			testType,
+																		 ErrorType			errorType,
+																		 glw::GLenum		maxBindingPointEnum,
+																		 glw::GLenum		maxVertexUnitsEnum,
+																		 glw::GLenum		maxFragmentUnitsEnum,
+																		 glw::GLenum		maxCombinedUnitsEnum,
+																		 const std::string& uniformName);
+	virtual								~LayoutBindingNegativeCase		(void);
+
+	virtual void						init							(void);
+	virtual void						deinit							(void);
+	virtual IterateResult				iterate							(void);
+
+protected:
+	virtual glu::ShaderProgram*			generateShaders					(void) const = 0;
+
+	const glu::ShaderProgram*			m_program;
+	const ShaderType					m_shaderType;
+	const TestType						m_testType;
+	const ErrorType						m_errorType;
+	const glw::GLenum					m_maxBindingPointEnum;
+	const glw::GLenum					m_maxVertexUnitsEnum;
+	const glw::GLenum					m_maxFragmentUnitsEnum;
+	const glw::GLenum					m_maxCombinedUnitsEnum;
+	const std::string					m_uniformName;
+	glw::GLint							m_numBindings;
+	std::vector<glw::GLint>				m_vertexShaderBinding;
+	std::vector<glw::GLint>				m_fragmentShaderBinding;
+
+private:
+	void								initBindingPoints				(int minBindingPoint, int numBindingPoints);
+};
+
+LayoutBindingNegativeCase::LayoutBindingNegativeCase (Context&				context,
+													  const char*			name,
+													  const char*			desc,
+													  ShaderType			shaderType,
+													  TestType				testType,
+													  ErrorType				errorType,
+													  glw::GLenum			maxBindingPointEnum,
+													  glw::GLenum			maxVertexUnitsEnum,
+													  glw::GLenum			maxFragmentUnitsEnum,
+													  glw::GLenum			maxCombinedUnitsEnum,
+													  const std::string&	uniformName)
+	: TestCase					(context, name, desc)
+	, m_program					(DE_NULL)
+	, m_shaderType				(shaderType)
+	, m_testType				(testType)
+	, m_errorType				(errorType)
+	, m_maxBindingPointEnum		(maxBindingPointEnum)
+	, m_maxVertexUnitsEnum		(maxVertexUnitsEnum)
+	, m_maxFragmentUnitsEnum	(maxFragmentUnitsEnum)
+	, m_maxCombinedUnitsEnum	(maxCombinedUnitsEnum)
+	, m_uniformName				(uniformName)
+	, m_numBindings				(0)
+{
+}
+
+LayoutBindingNegativeCase::~LayoutBindingNegativeCase (void)
+{
+	deinit();
+}
+
+void LayoutBindingNegativeCase::init (void)
+{
+	// Decide appropriate binding points for the vertex and fragment shaders
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	de::Random				rnd					(deStringHash(getName()) ^ 0xff23a4);
+	glw::GLint				numBindingPoints	= 0;	// Number of binding points
+	glw::GLint				maxVertexUnits		= 0;	// Available uniforms in the vertex shader
+	glw::GLint				maxFragmentUnits	= 0;	// Available uniforms in the fragment shader
+	glw::GLint				maxCombinedUnits	= 0;	// Available uniforms in all the shader stages combined
+	glw::GLint				maxUnits			= 0;	// Maximum available uniforms for this test
+
+	gl.getIntegerv(m_maxVertexUnitsEnum, &maxVertexUnits);
+	gl.getIntegerv(m_maxFragmentUnitsEnum, &maxFragmentUnits);
+	gl.getIntegerv(m_maxCombinedUnitsEnum, &maxCombinedUnits);
+	gl.getIntegerv(m_maxBindingPointEnum, &numBindingPoints);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Querying available uniform numbers failed");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Maximum units for uniform type in the vertex shader: " << maxVertexUnits << tcu::TestLog::EndMessage;
+	m_testCtx.getLog() << tcu::TestLog::Message << "Maximum units for uniform type in the fragment shader: " << maxFragmentUnits << tcu::TestLog::EndMessage;
+	m_testCtx.getLog() << tcu::TestLog::Message << "Maximum combined units for uniform type: " << maxCombinedUnits << tcu::TestLog::EndMessage;
+	m_testCtx.getLog() << tcu::TestLog::Message << "Maximum binding point for uniform type: " << numBindingPoints-1 << tcu::TestLog::EndMessage;
+
+	// Select maximum number of uniforms used for the test
+	switch (m_shaderType)
+	{
+		case SHADERTYPE_VERTEX:
+			maxUnits = maxVertexUnits;
+			break;
+
+		case SHADERTYPE_FRAGMENT:
+			maxUnits = maxFragmentUnits;
+			break;
+
+		case SHADERTYPE_BOTH:
+			maxUnits = maxCombinedUnits/2;
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	// Select the number of uniforms (= bindings) used for this test
+	switch (m_testType)
+	{
+		case TESTTYPE_BINDING_SINGLE:
+		case TESTTYPE_BINDING_MAX:
+			m_numBindings = 1;
+			break;
+
+		case TESTTYPE_BINDING_MULTIPLE:
+		case TESTTYPE_BINDING_ARRAY:
+		case TESTTYPE_BINDING_MAX_ARRAY:
+			if (maxUnits < 2)
+				throw tcu::NotSupportedError("Not enough uniforms available for test");
+			m_numBindings = rnd.getInt(2, deMin32(MAX_UNIFORM_ARRAY_SIZE, maxUnits));
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	// Check that we have enough uniforms in different shaders to perform the tests
+	if ( ((m_shaderType == SHADERTYPE_VERTEX) || (m_shaderType == SHADERTYPE_BOTH)) && (maxVertexUnits < m_numBindings) )
+		throw tcu::NotSupportedError("Vertex shader: not enough uniforms available for test");
+	if ( ((m_shaderType == SHADERTYPE_FRAGMENT) || (m_shaderType == SHADERTYPE_BOTH)) && (maxFragmentUnits < m_numBindings) )
+		throw tcu::NotSupportedError("Fragment shader: not enough uniforms available for test");
+	if ( (m_shaderType == SHADERTYPE_BOTH) && (maxCombinedUnits < m_numBindings*2) )
+		throw tcu::NotSupportedError("Not enough uniforms available for test");
+
+	// Check that we have enough binding points to perform the tests
+	if (numBindingPoints < m_numBindings)
+		throw tcu::NotSupportedError("Not enough binding points available for test");
+
+	// Initialize the binding points i.e. populate the two binding point vectors
+	initBindingPoints(0, numBindingPoints);
+
+	// Generate the shader program - note: this must be done after deciding the binding points
+	DE_ASSERT(!m_program);
+	m_testCtx.getLog() << tcu::TestLog::Message << "Creating test shaders" << tcu::TestLog::EndMessage;
+	m_program = generateShaders();
+	m_testCtx.getLog() << *m_program;
+}
+
+void LayoutBindingNegativeCase::deinit (void)
+{
+	if (m_program)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+	}
+}
+
+TestCase::IterateResult LayoutBindingNegativeCase::iterate (void)
+{
+	bool pass = false;
+	std::string failMessage;
+
+	switch (m_errorType)
+	{
+		case ERRORTYPE_CONTRADICTORY:		// Contradictory binding points should cause a link-time error
+			if (!(m_program->getProgramInfo()).linkOk)
+				pass = true;
+			failMessage = "Test failed - expected a link-time error";
+			break;
+
+		case ERRORTYPE_LESS_THAN_ZERO:		// Out of bounds binding points should cause a compile-time error
+		case ERRORTYPE_OVER_MAX_UNITS:
+			if (!(m_program->getShaderInfo(glu::SHADERTYPE_VERTEX)).compileOk || !(m_program->getShaderInfo(glu::SHADERTYPE_FRAGMENT)).compileOk)
+				pass = true;
+			failMessage = "Test failed - expected a compile-time error";
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	if (pass)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, failMessage.c_str());
+
+	return STOP;
+}
+
+void LayoutBindingNegativeCase::initBindingPoints (int minBindingPoint, int numBindingPoints)
+{
+	de::Random rnd(deStringHash(getName()) ^ 0xff23a4);
+
+	switch (m_errorType)
+	{
+		case ERRORTYPE_OVER_MAX_UNITS:	// Select a binding point that is 1 over the maximum
+		{
+			m_vertexShaderBinding.push_back(numBindingPoints+1-m_numBindings);
+			m_fragmentShaderBinding.push_back(numBindingPoints+1-m_numBindings);
+			break;
+		}
+
+		case ERRORTYPE_LESS_THAN_ZERO:	// Select a random negative binding point
+		{
+			const glw::GLint binding = -rnd.getInt(1, numBindingPoints-m_numBindings);
+			m_vertexShaderBinding.push_back(binding);
+			m_fragmentShaderBinding.push_back(binding);
+			break;
+		}
+
+		case ERRORTYPE_CONTRADICTORY:	// Select two valid, but contradictory binding points
+		{
+			m_vertexShaderBinding.push_back(minBindingPoint);
+			m_fragmentShaderBinding.push_back(numBindingPoints-m_numBindings);
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	// In case we are testing with multiple uniforms populate the rest of the binding points
+	for (int ndx = 1; ndx < m_numBindings; ++ndx)
+	{
+		m_vertexShaderBinding.push_back(m_vertexShaderBinding.front()+ndx);
+		m_fragmentShaderBinding.push_back(m_fragmentShaderBinding.front()+ndx);
+	}
+}
+
+class SamplerBindingRenderCase : public LayoutBindingRenderCase
+{
+public:
+									SamplerBindingRenderCase		(Context& context, const char* name, const char* desc, ShaderType shaderType, TestType testType, glw::GLenum samplerType, glw::GLenum textureType);
+									~SamplerBindingRenderCase		(void);
+
+	void 							init							(void);
+	void 							deinit							(void);
+	IterateResult 					iterate							(void);
+
+private:
+	glu::ShaderProgram*				generateShaders					(void) const;
+	glu::DataType					getSamplerTexCoordType			(void) const;
+	void							initializeTexture				(glw::GLint bindingPoint, glw::GLint textureName, const Vec4& color) const;
+
+	const glw::GLenum				m_samplerType;
+	const glw::GLenum				m_textureType;
+
+	std::vector<glw::GLuint>		m_textures;
+	std::vector<Vec4>				m_textureColors;
+};
+
+
+SamplerBindingRenderCase::SamplerBindingRenderCase (Context&		context,
+													const char*		name,
+													const char*		desc,
+													ShaderType		shaderType,
+													TestType		testType,
+													glw::GLenum		samplerType,
+													glw::GLenum		textureType)
+	: LayoutBindingRenderCase	(context, name, desc, shaderType, testType, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, GL_MAX_TEXTURE_IMAGE_UNITS, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, "u_sampler")
+	, m_samplerType				(samplerType)
+	, m_textureType				(textureType)
+{
+}
+
+SamplerBindingRenderCase::~SamplerBindingRenderCase (void)
+{
+	deinit();
+}
+
+void SamplerBindingRenderCase::init (void)
+{
+	LayoutBindingRenderCase::init();
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	de::Random				rnd		(deStringHash(getName()) ^ 0xff23a4);
+
+
+	// Initialize texture resources
+	m_textures = std::vector<glw::GLuint>(m_numBindings,  0);
+
+	// Texture colors
+	for (int texNdx = 0; texNdx < (int)m_textures.size(); ++texNdx)
+		m_textureColors.push_back(getRandomColor(rnd));
+
+	// Textures
+	gl.genTextures((glw::GLsizei)m_textures.size(), &m_textures[0]);
+
+	for (int texNdx = 0; texNdx < (int)m_textures.size(); ++texNdx)
+		initializeTexture(m_bindings[texNdx], m_textures[texNdx], m_textureColors[texNdx]);
+
+	gl.activeTexture(GL_TEXTURE0);
+}
+
+void SamplerBindingRenderCase::deinit(void)
+{
+	LayoutBindingRenderCase::deinit();
+
+	// Clean up texture data
+	for (int i = 0; i < (int)m_textures.size(); ++i)
+	{
+		if (m_textures[i])
+		{
+			m_context.getRenderContext().getFunctions().deleteTextures(1, &m_textures[i]);
+			m_context.getRenderContext().getFunctions().bindTexture(m_textureType, 0);
+		}
+	}
+}
+
+TestCase::IterateResult SamplerBindingRenderCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const int				iterations		= m_numBindings;
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
+	bool					imageTestPassed	= true;
+	bool					queryTestPassed	= true;
+
+	// Set the viewport and enable the shader program
+	initRenderState();
+
+	for (int iterNdx = 0; iterNdx < iterations; ++iterNdx)
+	{
+		// Set the uniform value indicating the current array index
+		gl.uniform1i(m_shaderProgramArrayNdxLoc, iterNdx);
+
+		// Query binding point
+		const std::string	name	= arrayInstance ? getUniformName(m_uniformName, 0, iterNdx) : getUniformName(m_uniformName, iterNdx);
+		const glw::GLint	binding = m_bindings[iterNdx];
+		glw::GLint			val		= -1;
+
+		gl.getUniformiv(m_program->getProgram(), gl.getUniformLocation(m_program->getProgram(), name.c_str()), &val);
+		m_testCtx.getLog() << tcu::TestLog::Message << "Querying binding point for " << name << ": " << val << " == " << binding << tcu::TestLog::EndMessage;
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Binding point query failed");
+
+		// Draw and verify
+		if (val != binding)
+			queryTestPassed = false;
+		if (!drawAndVerifyResult(m_textureColors[iterNdx]))
+			imageTestPassed = false;
+	}
+
+	setTestResult(queryTestPassed, imageTestPassed);
+	return STOP;
+}
+
+glu::ShaderProgram* SamplerBindingRenderCase::generateShaders (void) const
+{
+	std::ostringstream		shaderUniformDecl;
+	std::ostringstream		shaderBody;
+
+	const std::string		texCoordType	= glu::getDataTypeName(getSamplerTexCoordType());
+	const std::string		samplerType		= glu::getDataTypeName(glu::getDataTypeFromGLType(m_samplerType));
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY) ? true : false;
+	const int				numDeclarations =  arrayInstance ? 1 : m_numBindings;
+
+	// Generate the uniform declarations for the vertex and fragment shaders
+	for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
+	{
+		shaderUniformDecl << "layout(binding = " << m_bindings[declNdx] << ") uniform highp " << samplerType << " "
+			<< (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) : getUniformName(m_uniformName, declNdx)) << ";\n";
+	}
+
+	// Generate the shader body for the vertex and fragment shaders
+	for (int bindNdx = 0; bindNdx < m_numBindings; ++bindNdx)
+	{
+		shaderBody	<< "	" << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx) << ")\n"
+					<< "	{\n"
+					<< "		color = texture(" << (arrayInstance ? getUniformName(m_uniformName, 0, bindNdx) : getUniformName(m_uniformName, bindNdx)) << ", " << texCoordType << "(0.5));\n"
+					<< "	}\n";
+	}
+
+	shaderBody	<< "	else\n"
+				<< "	{\n"
+				<< "		color = vec4(0.0, 0.0, 0.0, 1.0);\n"
+				<< "	}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+					<< glu::VertexSource(generateVertexShader(m_shaderType, shaderUniformDecl.str(), shaderBody.str()))
+					<< glu::FragmentSource(generateFragmentShader(m_shaderType, shaderUniformDecl.str(), shaderBody.str())));
+}
+
+void SamplerBindingRenderCase::initializeTexture (glw::GLint bindingPoint, glw::GLint textureName, const Vec4& color) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.activeTexture(GL_TEXTURE0 + bindingPoint);
+	gl.bindTexture(m_textureType, textureName);
+	gl.texParameteri(m_textureType, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+	switch (m_textureType)
+	{
+		case GL_TEXTURE_2D:
+		{
+			tcu::TextureLevel level(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
+			tcu::clear(level.getAccess(), color);
+			glu::texImage2D(m_context.getRenderContext(), m_textureType, 0, GL_RGBA8, level.getAccess());
+			break;
+		}
+
+		case GL_TEXTURE_3D:
+		{
+			tcu::TextureLevel level(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
+			tcu::clear(level.getAccess(), color);
+			glu::texImage3D(m_context.getRenderContext(), m_textureType, 0, GL_RGBA8, level.getAccess());
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Texture initialization failed");
+}
+
+glu::DataType SamplerBindingRenderCase::getSamplerTexCoordType (void) const
+{
+	switch (m_samplerType)
+	{
+		case GL_SAMPLER_2D:
+			return glu::TYPE_FLOAT_VEC2;
+
+		case GL_SAMPLER_3D:
+			return glu::TYPE_FLOAT_VEC3;
+
+		default:
+			DE_ASSERT(false);
+			return glu::TYPE_INVALID;
+	}
+}
+
+
+class SamplerBindingNegativeCase : public LayoutBindingNegativeCase
+{
+public:
+									SamplerBindingNegativeCase		(Context&		context,
+																	 const char*	name,
+																	 const char*	desc,
+																	 ShaderType		shaderType,
+																	 TestType		testType,
+																	 ErrorType		errorType,
+																	 glw::GLenum	samplerType);
+									~SamplerBindingNegativeCase		(void);
+
+private:
+	glu::ShaderProgram*				generateShaders					(void) const;
+	glu::DataType					getSamplerTexCoordType			(void) const;
+
+	const glw::GLenum				m_samplerType;
+};
+
+SamplerBindingNegativeCase::SamplerBindingNegativeCase (Context&		context,
+														const char*		name,
+														const char*		desc,
+														ShaderType		shaderType,
+														TestType		testType,
+														ErrorType		errorType,
+														glw::GLenum		samplerType)
+	: LayoutBindingNegativeCase		(context, name, desc, shaderType, testType, errorType, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, GL_MAX_TEXTURE_IMAGE_UNITS, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, "u_sampler")
+	, m_samplerType					(samplerType)
+{
+}
+
+SamplerBindingNegativeCase::~SamplerBindingNegativeCase (void)
+{
+	LayoutBindingNegativeCase::deinit();
+}
+
+glu::ShaderProgram*	SamplerBindingNegativeCase::generateShaders	(void) const
+{
+	std::ostringstream		vertexUniformDecl;
+	std::ostringstream		fragmentUniformDecl;
+	std::ostringstream		shaderBody;
+
+	const std::string		texCoordType	= glu::getDataTypeName(getSamplerTexCoordType());
+	const std::string		samplerType		= glu::getDataTypeName(glu::getDataTypeFromGLType(m_samplerType));
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
+	const int				numDeclarations = arrayInstance ? 1 : m_numBindings;
+
+	// Generate the uniform declarations for the vertex and fragment shaders
+	for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
+	{
+		vertexUniformDecl << "layout(binding = " << m_vertexShaderBinding[declNdx] << ") uniform highp " << samplerType
+			<< " " << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) : getUniformName(m_uniformName, declNdx)) << ";\n";
+		fragmentUniformDecl << "layout(binding = " << m_fragmentShaderBinding[declNdx] << ") uniform highp " << samplerType
+			<< " " << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) : getUniformName(m_uniformName, declNdx)) << ";\n";
+	}
+
+	// Generate the shader body for the vertex and fragment shaders
+	for (int bindNdx = 0; bindNdx < m_numBindings; ++bindNdx)
+	{
+		shaderBody	<< "	" << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx) << ")\n"
+					<< "	{\n"
+					<< "		color = texture(" << (arrayInstance ? getUniformName(m_uniformName, 0, bindNdx) : getUniformName(m_uniformName, bindNdx)) << ", " << texCoordType << "(0.5));\n"
+					<< "	}\n";
+	}
+
+	shaderBody	<< "	else\n"
+				<< "	{\n"
+				<< "		color = vec4(0.0, 0.0, 0.0, 1.0);\n"
+				<< "	}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+				<< glu::VertexSource(generateVertexShader(m_shaderType, vertexUniformDecl.str(), shaderBody.str()))
+				<< glu::FragmentSource(generateFragmentShader(m_shaderType, fragmentUniformDecl.str(), shaderBody.str())));
+}
+
+glu::DataType SamplerBindingNegativeCase::getSamplerTexCoordType(void) const
+{
+	switch (m_samplerType)
+	{
+		case GL_SAMPLER_2D:
+			return glu::TYPE_FLOAT_VEC2;
+
+		case GL_SAMPLER_3D:
+			return glu::TYPE_FLOAT_VEC3;
+
+		default:
+			DE_ASSERT(false);
+			return glu::TYPE_INVALID;
+	}
+}
+
+class ImageBindingRenderCase : public LayoutBindingRenderCase
+{
+public:
+											ImageBindingRenderCase			(Context&		context,
+																			 const char*	name,
+																			 const char*	desc,
+																			 ShaderType		shaderType,
+																			 TestType		testType,
+																			 glw::GLenum	imageType,
+																			 glw::GLenum	textureType);
+											~ImageBindingRenderCase			(void);
+
+	void 									init							(void);
+	void 									deinit							(void);
+	IterateResult 							iterate							(void);
+
+private:
+	glu::ShaderProgram*						generateShaders					(void) const;
+	void									initializeImage					(glw::GLint imageBindingPoint, glw::GLint textureBindingPoint, glw::GLint textureName, const Vec4& color) const;
+	glu::DataType							getImageTexCoordType			(void) const;
+
+	const glw::GLenum						m_imageType;
+	const glw::GLenum						m_textureType;
+
+	std::vector<glw::GLuint>				m_textures;
+	std::vector<Vec4>						m_textureColors;
+};
+
+
+ImageBindingRenderCase::ImageBindingRenderCase (Context&		context,
+												const char*		name,
+												const char*		desc,
+												ShaderType		shaderType,
+												TestType		testType,
+												glw::GLenum		imageType,
+												glw::GLenum		textureType)
+	: LayoutBindingRenderCase		(context, name, desc, shaderType, testType, GL_MAX_IMAGE_UNITS, GL_MAX_VERTEX_IMAGE_UNIFORMS, GL_MAX_FRAGMENT_IMAGE_UNIFORMS, GL_MAX_COMBINED_IMAGE_UNIFORMS, "u_image")
+	, m_imageType					(imageType)
+	, m_textureType					(textureType)
+{
+}
+
+ImageBindingRenderCase::~ImageBindingRenderCase (void)
+{
+	deinit();
+}
+
+void ImageBindingRenderCase::init (void)
+{
+	LayoutBindingRenderCase::init();
+
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	de::Random				rnd		(deStringHash(getName()) ^ 0xff23a4);
+
+	// Initialize image / texture resources
+	m_textures = std::vector<glw::GLuint>(m_numBindings,  0);
+
+	// Texture colors
+	for (int texNdx = 0; texNdx < (int)m_textures.size(); ++texNdx)
+		m_textureColors.push_back(getRandomColor(rnd));
+
+	// Image textures
+	gl.genTextures(m_numBindings, &m_textures[0]);
+
+	for (int texNdx = 0; texNdx < (int)m_textures.size(); ++texNdx)
+		initializeImage(m_bindings[texNdx], texNdx, m_textures[texNdx], m_textureColors[texNdx]);
+}
+
+void ImageBindingRenderCase::deinit (void)
+{
+	LayoutBindingRenderCase::deinit();
+
+	// Clean up texture data
+	for (int texNdx = 0; texNdx < (int)m_textures.size(); ++texNdx)
+	{
+		if (m_textures[texNdx])
+		{
+			m_context.getRenderContext().getFunctions().deleteTextures(1, &m_textures[texNdx]);
+			m_context.getRenderContext().getFunctions().bindTexture(m_textureType, 0);
+		}
+	}
+}
+
+TestCase::IterateResult ImageBindingRenderCase::iterate	(void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const int				iterations		= m_numBindings;
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
+	bool					queryTestPassed	= true;
+	bool					imageTestPassed = true;
+
+	// Set the viewport and enable the shader program
+	initRenderState();
+
+	for (int iterNdx = 0; iterNdx < iterations; ++iterNdx)
+	{
+		// Set the uniform value indicating the current array index
+		gl.uniform1i(m_shaderProgramArrayNdxLoc, iterNdx);
+
+		const std::string	name	= (arrayInstance ? getUniformName(m_uniformName, 0, iterNdx) : getUniformName(m_uniformName, iterNdx));
+		const glw::GLint	binding = m_bindings[iterNdx];
+		glw::GLint			val		= -1;
+
+		gl.getUniformiv(m_program->getProgram(), gl.getUniformLocation(m_program->getProgram(), name.c_str()), &val);
+		m_testCtx.getLog() << tcu::TestLog::Message << "Querying binding point for " << name << ": " << val << " == " << binding << tcu::TestLog::EndMessage;
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Binding point query failed");
+
+		// Draw and verify
+		if (val != binding)
+			queryTestPassed = false;
+		if (!drawAndVerifyResult(m_textureColors[iterNdx]))
+			imageTestPassed = false;
+	}
+
+	setTestResult(queryTestPassed, imageTestPassed);
+	return STOP;
+}
+
+void ImageBindingRenderCase::initializeImage (glw::GLint imageBindingPoint, glw::GLint textureBindingPoint, glw::GLint textureName, const Vec4& color) const
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.activeTexture(GL_TEXTURE0 + textureBindingPoint);
+	gl.bindTexture(m_textureType, textureName);
+	gl.texParameteri(m_textureType, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+	switch (m_textureType)
+	{
+		case GL_TEXTURE_2D:
+		{
+			tcu::TextureLevel level(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
+			tcu::clear(level.getAccess(), color);
+			gl.texStorage2D(m_textureType, 1, GL_RGBA8, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
+			gl.texSubImage2D(m_textureType, 0, 0, 0, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, GL_RGBA, GL_UNSIGNED_BYTE, level.getAccess().getDataPtr());
+			break;
+		}
+
+		case GL_TEXTURE_3D:
+		{
+			tcu::TextureLevel level(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
+			tcu::clear(level.getAccess(), color);
+			gl.texStorage3D(m_textureType, 1, GL_RGBA8, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
+			gl.texSubImage3D(m_textureType, 0, 0, 0, 0, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, GL_RGBA, GL_UNSIGNED_BYTE, level.getAccess().getDataPtr());
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	gl.bindTexture(m_textureType, 0);
+	gl.bindImageTexture(imageBindingPoint, textureName, 0, GL_TRUE, 0, GL_READ_ONLY, GL_RGBA8);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Image initialization failed");
+}
+
+glu::ShaderProgram* ImageBindingRenderCase::generateShaders (void) const
+{
+	std::ostringstream		shaderUniformDecl;
+	std::ostringstream		shaderBody;
+
+	const std::string		texCoordType	= glu::getDataTypeName(getImageTexCoordType());
+	const std::string		imageType		= glu::getDataTypeName(glu::getDataTypeFromGLType(m_imageType));
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY) ? true : false;
+	const int				numDeclarations = (arrayInstance ? 1 : m_numBindings);
+
+	// Generate the uniform declarations for the vertex and fragment shaders
+	for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
+	{
+		shaderUniformDecl << "layout(rgba8, binding = " << m_bindings[declNdx] << ") uniform readonly " << imageType
+			<< " " << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) : getUniformName(m_uniformName, declNdx)) << ";\n";
+	}
+
+	// Generate the shader body for the vertex and fragment shaders
+	for (int bindNdx = 0; bindNdx < m_numBindings; ++bindNdx)
+	{
+		shaderBody	<< "	" << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx) << ")\n"
+					<< "	{\n"
+					<< "		color = imageLoad(" << (arrayInstance ? getUniformName(m_uniformName, 0, bindNdx) : getUniformName(m_uniformName, bindNdx)) << ", " << texCoordType << "(0));\n"
+					<< "	}\n";
+	}
+
+	shaderBody	<< "	else\n"
+				<< "	{\n"
+				<< "		color = vec4(0.0, 0.0, 0.0, 1.0);\n"
+				<< "	}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+					<< glu::VertexSource(generateVertexShader(m_shaderType, shaderUniformDecl.str(), shaderBody.str()))
+					<< glu::FragmentSource(generateFragmentShader(m_shaderType, shaderUniformDecl.str(), shaderBody.str())));
+}
+
+glu::DataType ImageBindingRenderCase::getImageTexCoordType(void) const
+{
+	switch (m_imageType)
+	{
+		case GL_IMAGE_2D:
+			return glu::TYPE_INT_VEC2;
+
+		case GL_IMAGE_3D:
+			return glu::TYPE_INT_VEC3;
+
+		default:
+			DE_ASSERT(false);
+			return glu::TYPE_INVALID;
+	}
+}
+
+
+class ImageBindingNegativeCase : public LayoutBindingNegativeCase
+{
+public:
+											ImageBindingNegativeCase		(Context&		context,
+																			 const char*	name,
+																			 const char*	desc,
+																			 ShaderType		shaderType,
+																			 TestType		testType,
+																			 ErrorType		errorType,
+																			 glw::GLenum	imageType);
+											~ImageBindingNegativeCase		(void);
+
+private:
+	glu::ShaderProgram*						generateShaders					(void) const;
+	glu::DataType							getImageTexCoordType			(void) const;
+
+	const glw::GLenum						m_imageType;
+};
+
+ImageBindingNegativeCase::ImageBindingNegativeCase (Context&		context,
+													const char*		name,
+													const char*		desc,
+													ShaderType		shaderType,
+													TestType		testType,
+													ErrorType		errorType,
+													glw::GLenum		imageType)
+	: LayoutBindingNegativeCase		(context, name, desc, shaderType, testType, errorType, GL_MAX_IMAGE_UNITS, GL_MAX_VERTEX_IMAGE_UNIFORMS, GL_MAX_FRAGMENT_IMAGE_UNIFORMS, GL_MAX_COMBINED_IMAGE_UNIFORMS, "u_image")
+	, m_imageType					(imageType)
+{
+}
+
+ImageBindingNegativeCase::~ImageBindingNegativeCase (void)
+{
+	deinit();
+}
+
+glu::ShaderProgram* ImageBindingNegativeCase::generateShaders (void) const
+{
+	std::ostringstream		vertexUniformDecl;
+	std::ostringstream		fragmentUniformDecl;
+	std::ostringstream		shaderBody;
+
+	const std::string		texCoordType	= glu::getDataTypeName(getImageTexCoordType());
+	const std::string		imageType		= glu::getDataTypeName(glu::getDataTypeFromGLType(m_imageType));
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
+	const int				numDeclarations = (arrayInstance ? 1 : m_numBindings);
+
+	// Generate the uniform declarations for the vertex and fragment shaders
+	for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
+	{
+		vertexUniformDecl << "layout(rgba8, binding = " << m_vertexShaderBinding[declNdx] << ") uniform readonly " << imageType
+			<< " " << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) : getUniformName(m_uniformName, declNdx)) << ";\n";
+		fragmentUniformDecl << "layout(rgba8, binding = " << m_fragmentShaderBinding[declNdx] << ") uniform readonly " << imageType
+			<< " " << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) : getUniformName(m_uniformName, declNdx)) << ";\n";
+	}
+
+	// Generate the shader body for the vertex and fragment shaders
+	for (int bindNdx = 0; bindNdx < m_numBindings; ++bindNdx)
+	{
+		shaderBody	<< "	" << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx) << ")\n"
+					<< "	{\n"
+					<< "		color = imageLoad(" << (arrayInstance ? getUniformName(m_uniformName, 0, bindNdx) : getUniformName(m_uniformName, bindNdx)) << ", " << texCoordType << "(0));\n"
+					<< "	}\n";
+	}
+
+	shaderBody	<< "	else\n"
+				<< "	{\n"
+				<< "		color = vec4(0.0, 0.0, 0.0, 1.0);\n"
+				<< "	}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+					<< glu::VertexSource(generateVertexShader(m_shaderType, vertexUniformDecl.str(), shaderBody.str()))
+					<< glu::FragmentSource(generateFragmentShader(m_shaderType, fragmentUniformDecl.str(), shaderBody.str())));
+}
+
+glu::DataType ImageBindingNegativeCase::getImageTexCoordType(void) const
+{
+	switch (m_imageType)
+	{
+		case GL_IMAGE_2D:
+			return glu::TYPE_INT_VEC2;
+
+		case GL_IMAGE_3D:
+			return glu::TYPE_INT_VEC3;
+
+		default:
+			DE_ASSERT(false);
+			return glu::TYPE_INVALID;
+	}
+}
+
+
+class UBOBindingRenderCase : public LayoutBindingRenderCase
+{
+public:
+											UBOBindingRenderCase		(Context&		context,
+																		 const char*	name,
+																		 const char*	desc,
+																		 ShaderType		shaderType,
+																		 TestType		testType);
+											~UBOBindingRenderCase		(void);
+
+	void 									init						(void);
+	void 									deinit						(void);
+	IterateResult 							iterate						(void);
+
+private:
+	glu::ShaderProgram*						generateShaders				(void) const;
+
+	std::vector<deUint32>					m_buffers;
+	std::vector<Vec4>						m_expectedColors;
+};
+
+UBOBindingRenderCase::UBOBindingRenderCase (Context&		context,
+											const char*		name,
+											const char*		desc,
+											ShaderType		shaderType,
+											TestType		testType)
+	: LayoutBindingRenderCase (context, name, desc, shaderType, testType, GL_MAX_UNIFORM_BUFFER_BINDINGS, GL_MAX_VERTEX_UNIFORM_BLOCKS, GL_MAX_FRAGMENT_UNIFORM_BLOCKS, GL_MAX_COMBINED_UNIFORM_BLOCKS, "ColorBlock")
+{
+}
+
+UBOBindingRenderCase::~UBOBindingRenderCase (void)
+{
+	deinit();
+}
+
+void UBOBindingRenderCase::init (void)
+{
+	LayoutBindingRenderCase::init();
+
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	de::Random				rnd		(deStringHash(getName()) ^ 0xff23a4);
+
+	// Initialize UBOs and related data
+	m_buffers = std::vector<glw::GLuint>(m_numBindings,  0);
+	gl.genBuffers((glw::GLsizei)m_buffers.size(), &m_buffers[0]);
+
+	for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
+	{
+			m_expectedColors.push_back(getRandomColor(rnd));
+			m_expectedColors.push_back(getRandomColor(rnd));
+	}
+
+	for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
+	{
+		gl.bindBuffer(GL_UNIFORM_BUFFER, m_buffers[bufNdx]);
+		gl.bufferData(GL_UNIFORM_BUFFER, 2*sizeof(Vec4), &(m_expectedColors[2*bufNdx]), GL_STATIC_DRAW);
+		gl.bindBufferBase(GL_UNIFORM_BUFFER, m_bindings[bufNdx], m_buffers[bufNdx]);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "UBO setup failed");
+}
+
+void UBOBindingRenderCase::deinit (void)
+{
+	LayoutBindingRenderCase::deinit();
+
+	// Clean up UBO data
+	for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
+	{
+		if (m_buffers[bufNdx])
+		{
+			m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buffers[bufNdx]);
+			m_context.getRenderContext().getFunctions().bindBuffer(GL_UNIFORM_BUFFER, 0);
+		}
+	}
+}
+
+TestCase::IterateResult UBOBindingRenderCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const int				iterations		= m_numBindings;
+	const glw::GLenum		prop			= GL_BUFFER_BINDING;
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
+	bool					queryTestPassed	= true;
+	bool					imageTestPassed = true;
+
+	// Set the viewport and enable the shader program
+	initRenderState();
+
+	for (int iterNdx = 0; iterNdx < iterations; ++iterNdx)
+	{
+		// Query binding point
+		const std::string	name	= (arrayInstance ? getUniformName(m_uniformName, 0, iterNdx) : getUniformName(m_uniformName, iterNdx));
+		const glw::GLint	binding = m_bindings[iterNdx];
+		glw::GLint			val		= -1;
+
+		gl.getProgramResourceiv(m_program->getProgram(), GL_UNIFORM_BLOCK, gl.getProgramResourceIndex(m_program->getProgram(), GL_UNIFORM_BLOCK, name.c_str() ), 1, &prop, 1, DE_NULL, &val);
+		m_testCtx.getLog() << tcu::TestLog::Message << "Querying binding point for " << name << ": " << val << " == " << binding << tcu::TestLog::EndMessage;
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Binding point query failed");
+
+		if (val != binding)
+			queryTestPassed = false;
+
+		// Draw twice to render both colors within the UBO
+		for (int drawCycle = 0; drawCycle < 2; ++drawCycle)
+		{
+			// Set the uniform indicating the array index to be used and set the expected color
+			const int arrayNdx = iterNdx*2 + drawCycle;
+			gl.uniform1i(m_shaderProgramArrayNdxLoc, arrayNdx);
+
+			if (!drawAndVerifyResult(m_expectedColors[arrayNdx]))
+				imageTestPassed = false;
+		}
+	}
+
+	setTestResult(queryTestPassed, imageTestPassed);
+	return STOP;
+}
+
+glu::ShaderProgram* UBOBindingRenderCase::generateShaders (void) const
+{
+	std::ostringstream		shaderUniformDecl;
+	std::ostringstream		shaderBody;
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
+	const int				numDeclarations = (arrayInstance ? 1 : m_numBindings);
+
+	// Generate the uniform declarations for the vertex and fragment shaders
+	for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
+	{
+		shaderUniformDecl << "layout(std140, binding = " << m_bindings[declNdx] << ") uniform "
+			<< getUniformName(m_uniformName, declNdx) << "\n"
+			<< "{\n"
+			<< "	highp vec4 color1;\n"
+			<< "	highp vec4 color2;\n"
+			<< "} " << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) : getUniformName("colors", declNdx)) << ";\n";
+	}
+
+	// Generate the shader body for the vertex and fragment shaders
+	for (int bindNdx = 0; bindNdx < m_numBindings*2; ++bindNdx)	// Multiply by two to cover cases for both colors for each UBO
+	{
+		const std::string uname = (arrayInstance ? getUniformName("colors", 0, bindNdx/2) : getUniformName("colors", bindNdx/2));
+		shaderBody	<< "	" << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx) << ")\n"
+					<< "	{\n"
+					<< "		color = " << uname << (bindNdx%2 == 0 ? ".color1" : ".color2") << ";\n"
+					<< "	}\n";
+	}
+
+	shaderBody	<< "	else\n"
+				<< "	{\n"
+				<< "		color = vec4(0.0, 0.0, 0.0, 1.0);\n"
+				<< "	}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+					<< glu::VertexSource(generateVertexShader(m_shaderType, shaderUniformDecl.str(), shaderBody.str()))
+					<< glu::FragmentSource(generateFragmentShader(m_shaderType, shaderUniformDecl.str(), shaderBody.str())));
+}
+
+
+class UBOBindingNegativeCase : public LayoutBindingNegativeCase
+{
+public:
+											UBOBindingNegativeCase			(Context&		context,
+																			 const char*	name,
+																			 const char*	desc,
+																			 ShaderType		shaderType,
+																			 TestType		testType,
+																			 ErrorType		errorType);
+											~UBOBindingNegativeCase			(void);
+
+private:
+	glu::ShaderProgram*						generateShaders					(void) const;
+};
+
+UBOBindingNegativeCase::UBOBindingNegativeCase (Context&		context,
+												const char*		name,
+												const char*		desc,
+												ShaderType		shaderType,
+												TestType		testType,
+												ErrorType		errorType)
+	: LayoutBindingNegativeCase(context, name, desc, shaderType, testType, errorType, GL_MAX_UNIFORM_BUFFER_BINDINGS, GL_MAX_VERTEX_UNIFORM_BLOCKS, GL_MAX_FRAGMENT_UNIFORM_BLOCKS, GL_MAX_COMBINED_UNIFORM_BLOCKS, "ColorBlock")
+{
+}
+
+UBOBindingNegativeCase::~UBOBindingNegativeCase (void)
+{
+	deinit();
+}
+
+glu::ShaderProgram* UBOBindingNegativeCase::generateShaders (void) const
+{
+	std::ostringstream		vertexUniformDecl;
+	std::ostringstream		fragmentUniformDecl;
+	std::ostringstream		shaderBody;
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
+	const int				numDeclarations = (arrayInstance ? 1 : m_numBindings);
+
+	// Generate the uniform declarations for the vertex and fragment shaders
+	for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
+	{
+		vertexUniformDecl << "layout(std140, binding = " << m_vertexShaderBinding[declNdx] << ") uniform "
+			<< getUniformName(m_uniformName, declNdx) << "\n"
+			<< "{\n"
+			<< "	highp vec4 color1;\n"
+			<< "	highp vec4 color2;\n"
+			<< "} " << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) : getUniformName("colors", declNdx)) << ";\n";
+
+		fragmentUniformDecl << "layout(std140, binding = " << m_fragmentShaderBinding[declNdx] << ") uniform "
+			<< getUniformName(m_uniformName, declNdx) << "\n"
+			<< "{\n"
+			<< "	highp vec4 color1;\n"
+			<< "	highp vec4 color2;\n"
+			<< "} " << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) : getUniformName("colors", declNdx)) << ";\n";
+	}
+
+	// Generate the shader body for the vertex and fragment shaders
+	for (int bindNdx = 0; bindNdx < m_numBindings*2; ++bindNdx)	// Multiply by two to cover cases for both colors for each UBO
+	{
+		const std::string uname = (arrayInstance ? getUniformName("colors", 0, m_numBindings) : getUniformName("colors", bindNdx/2));
+		shaderBody	<< "	" << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx) << ")\n"
+					<< "	{\n"
+					<< "		color = " << uname << (bindNdx%2 == 0 ? ".color1" : ".color2") << ";\n"
+					<< "	}\n";
+	}
+
+	shaderBody	<< "	else\n"
+				<< "	{\n"
+				<< "		color = vec4(0.0, 0.0, 0.0, 1.0);\n"
+				<< "	}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+					<< glu::VertexSource(generateVertexShader(m_shaderType, vertexUniformDecl.str(), shaderBody.str()))
+					<< glu::FragmentSource(generateFragmentShader(m_shaderType, fragmentUniformDecl.str(), shaderBody.str())));
+}
+
+
+class SSBOBindingRenderCase : public LayoutBindingRenderCase
+{
+public:
+											SSBOBindingRenderCase		(Context&		context,
+																		 const char*	name,
+																		 const char*	desc,
+																		 ShaderType		shaderType,
+																		 TestType		testType);
+											~SSBOBindingRenderCase		(void);
+
+	void 									init						(void);
+	void 									deinit						(void);
+	IterateResult 							iterate						(void);
+
+private:
+	glu::ShaderProgram*						generateShaders				(void) const;
+
+	std::vector<glw::GLuint>				m_buffers;
+	std::vector<Vec4>						m_expectedColors;
+};
+
+SSBOBindingRenderCase::SSBOBindingRenderCase (Context&		context,
+											  const char*	name,
+											  const char*	desc,
+											  ShaderType	shaderType,
+											  TestType		testType)
+	: LayoutBindingRenderCase (context, name, desc, shaderType, testType, GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, "ColorBuffer")
+{
+}
+
+SSBOBindingRenderCase::~SSBOBindingRenderCase (void)
+{
+	deinit();
+}
+
+void SSBOBindingRenderCase::init (void)
+{
+	LayoutBindingRenderCase::init();
+
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	de::Random				rnd		(deStringHash(getName()) ^ 0xff23a4);
+
+	// Initialize SSBOs and related data
+	m_buffers = std::vector<glw::GLuint>(m_numBindings, 0);
+	gl.genBuffers((glw::GLsizei)m_buffers.size(), &m_buffers[0]);
+
+	for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
+	{
+		m_expectedColors.push_back(getRandomColor(rnd));
+		m_expectedColors.push_back(getRandomColor(rnd));
+	}
+
+	for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
+	{
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_buffers[bufNdx]);
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, 2*sizeof(Vec4), &(m_expectedColors[2*bufNdx]), GL_STATIC_DRAW);
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, m_bindings[bufNdx], m_buffers[bufNdx]);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "SSBO setup failed");
+}
+
+void SSBOBindingRenderCase::deinit (void)
+{
+	LayoutBindingRenderCase::deinit();
+
+	// Clean up SSBO data
+	for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
+	{
+		if (m_buffers[bufNdx])
+		{
+			m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buffers[bufNdx]);
+			m_context.getRenderContext().getFunctions().bindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+			m_buffers[bufNdx] = 0;
+		}
+	}
+}
+
+TestCase::IterateResult SSBOBindingRenderCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const int				iterations		= m_numBindings;
+	const glw::GLenum		prop			= GL_BUFFER_BINDING;
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
+	bool					queryTestPassed	= true;
+	bool					imageTestPassed = true;
+
+	initRenderState();
+
+	for (int iterNdx = 0; iterNdx < iterations; ++iterNdx)
+	{
+		// Query binding point
+		const std::string	name	= (arrayInstance ? getUniformName(m_uniformName, 0, iterNdx) : getUniformName(m_uniformName, iterNdx));
+		const glw::GLint	binding = m_bindings[iterNdx];
+		glw::GLint			val		= -1;
+
+		gl.getProgramResourceiv(m_program->getProgram(), GL_SHADER_STORAGE_BLOCK, gl.getProgramResourceIndex(m_program->getProgram(), GL_SHADER_STORAGE_BLOCK, name.c_str() ), 1, &prop, 1, DE_NULL, &val);
+		m_testCtx.getLog() << tcu::TestLog::Message << "Querying binding point for " << name << ": " << val << " == " << binding << tcu::TestLog::EndMessage;
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Binding point query failed");
+
+		if (val != binding)
+			queryTestPassed = false;
+
+		// Draw twice to render both colors within the SSBO
+		for (int drawCycle = 0; drawCycle < 2; ++drawCycle)
+		{
+			// Set the uniform indicating the array index to be used and set the expected color
+			const int arrayNdx = iterNdx*2 + drawCycle;
+			gl.uniform1i(m_shaderProgramArrayNdxLoc, arrayNdx);
+
+			if (!drawAndVerifyResult(m_expectedColors[arrayNdx]))
+				imageTestPassed = false;
+		}
+	}
+
+	setTestResult(queryTestPassed, imageTestPassed);
+	return STOP;
+}
+
+glu::ShaderProgram* SSBOBindingRenderCase::generateShaders (void) const
+{
+	std::ostringstream		shaderUniformDecl;
+	std::ostringstream		shaderBody;
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
+	const int				numDeclarations = (arrayInstance ? 1 : m_numBindings);
+
+	// Generate the uniform declarations for the vertex and fragment shaders
+	for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
+	{
+		shaderUniformDecl << "layout(std140, binding = " << m_bindings[declNdx] << ") buffer "
+			<< getUniformName(m_uniformName, declNdx) << "\n"
+			<< "{\n"
+			<< "	highp vec4 color1;\n"
+			<< "	highp vec4 color2;\n"
+			<< "} " << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) : getUniformName("colors", declNdx)) << ";\n";
+	}
+
+	// Generate the shader body for the vertex and fragment shaders
+	for (int bindNdx = 0; bindNdx < m_numBindings*2; ++bindNdx)	// Multiply by two to cover cases for both colors for each UBO
+	{
+		const std::string uname = (arrayInstance ? getUniformName("colors", 0, bindNdx/2) : getUniformName("colors", bindNdx/2));
+		shaderBody	<< "	" << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx) << ")\n"
+					<< "	{\n"
+					<< "		color = " << uname << (bindNdx%2 == 0 ? ".color1" : ".color2") << ";\n"
+					<< "	}\n";
+	}
+
+	shaderBody	<< "	else\n"
+				<< "	{\n"
+				<< "		color = vec4(0.0, 0.0, 0.0, 1.0);\n"
+				<< "	}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+					<< glu::VertexSource(generateVertexShader(m_shaderType, shaderUniformDecl.str(), shaderBody.str()))
+					<< glu::FragmentSource(generateFragmentShader(m_shaderType, shaderUniformDecl.str(), shaderBody.str())));
+}
+
+
+class SSBOBindingNegativeCase : public LayoutBindingNegativeCase
+{
+public:
+											SSBOBindingNegativeCase			(Context&		context,
+																			 const char*	name,
+																			 const char*	desc,
+																			 ShaderType		shaderType,
+																			 TestType		testType,
+																			 ErrorType		errorType);
+											~SSBOBindingNegativeCase		(void);
+
+private:
+	glu::ShaderProgram*						generateShaders					(void) const;
+};
+
+SSBOBindingNegativeCase::SSBOBindingNegativeCase (Context& context,
+												  const char* name,
+												  const char* desc,
+												  ShaderType shaderType,
+												  TestType testType,
+												  ErrorType errorType)
+	: LayoutBindingNegativeCase(context, name, desc, shaderType, testType, errorType, GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, "ColorBuffer")
+{
+}
+
+SSBOBindingNegativeCase::~SSBOBindingNegativeCase (void)
+{
+	deinit();
+}
+
+glu::ShaderProgram* SSBOBindingNegativeCase::generateShaders (void) const
+{
+	std::ostringstream		vertexUniformDecl;
+	std::ostringstream		fragmentUniformDecl;
+	std::ostringstream		shaderBody;
+	const bool				arrayInstance	= (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
+	const int				numDeclarations = (arrayInstance ? 1 : m_numBindings);
+
+	// Generate the uniform declarations for the vertex and fragment shaders
+	for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
+	{
+		vertexUniformDecl << "layout(std140, binding = " << m_vertexShaderBinding[declNdx] << ") buffer "
+			<< getUniformName(m_uniformName, declNdx) << "\n"
+			<< "{\n"
+			<< "	highp vec4 color1;\n"
+			<< "	highp vec4 color2;\n"
+			<< "} " << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) : getUniformName("colors", declNdx)) << ";\n";
+
+		fragmentUniformDecl << "layout(std140, binding = " << m_fragmentShaderBinding[declNdx] << ") buffer "
+			<< getUniformName(m_uniformName, declNdx) << "\n"
+			<< "{\n"
+			<< "	highp vec4 color1;\n"
+			<< "	highp vec4 color2;\n"
+			<< "} " << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) : getUniformName("colors", declNdx)) << ";\n";
+	}
+
+	// Generate the shader body for the vertex and fragment shaders
+	for (int bindNdx = 0; bindNdx < m_numBindings*2; ++bindNdx)	// Multiply by two to cover cases for both colors for each UBO
+	{
+		const std::string uname = (arrayInstance ? getUniformName("colors", 0, bindNdx/2) : getUniformName("colors", bindNdx/2));
+		shaderBody	<< "	" << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx) << ")\n"
+					<< "	{\n"
+					<< "		color = " << uname << (bindNdx%2 == 0 ? ".color1" : ".color2") << ";\n"
+					<< "	}\n";
+	}
+
+	shaderBody	<< "	else\n"
+				<< "	{\n"
+				<< "		color = vec4(0.0, 0.0, 0.0, 1.0);\n"
+				<< "	}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+					<< glu::VertexSource(generateVertexShader(m_shaderType, vertexUniformDecl.str(), shaderBody.str()))
+					<< glu::FragmentSource(generateFragmentShader(m_shaderType, fragmentUniformDecl.str(), shaderBody.str())));
+}
+
+
+} // Anonymous
+
+LayoutBindingTests::LayoutBindingTests (Context& context)
+	: TestCaseGroup (context, "layout_binding", "Layout binding tests")
+{
+}
+
+LayoutBindingTests::~LayoutBindingTests (void)
+{
+}
+
+void LayoutBindingTests::init (void)
+{
+	// Render test groups
+	tcu::TestCaseGroup* const samplerBindingTestGroup			= new tcu::TestCaseGroup(m_testCtx, "sampler",		"Test sampler layout binding");
+	tcu::TestCaseGroup* const sampler2dBindingTestGroup			= new tcu::TestCaseGroup(m_testCtx, "sampler2d",	"Test sampler2d layout binding");
+	tcu::TestCaseGroup* const sampler3dBindingTestGroup			= new tcu::TestCaseGroup(m_testCtx, "sampler3d",	"Test sampler3d layout binding");
+
+	tcu::TestCaseGroup* const imageBindingTestGroup				= new tcu::TestCaseGroup(m_testCtx, "image",		"Test image layout binding");
+	tcu::TestCaseGroup* const image2dBindingTestGroup			= new tcu::TestCaseGroup(m_testCtx, "image2d",		"Test image2d layout binding");
+	tcu::TestCaseGroup* const image3dBindingTestGroup			= new tcu::TestCaseGroup(m_testCtx, "image3d",		"Test image3d layout binding");
+
+	tcu::TestCaseGroup* const UBOBindingTestGroup				= new tcu::TestCaseGroup(m_testCtx, "ubo",			"Test UBO layout binding");
+	tcu::TestCaseGroup* const SSBOBindingTestGroup				= new tcu::TestCaseGroup(m_testCtx, "ssbo",			"Test SSBO layout binding");
+
+	// Negative test groups
+	tcu::TestCaseGroup* const negativeBindingTestGroup			= new tcu::TestCaseGroup(m_testCtx, "negative",		"Test layout binding with invalid bindings");
+
+	tcu::TestCaseGroup* const negativeSamplerBindingTestGroup	= new tcu::TestCaseGroup(m_testCtx, "sampler",		"Test sampler layout binding with invalid bindings");
+	tcu::TestCaseGroup* const negativeSampler2dBindingTestGroup	= new tcu::TestCaseGroup(m_testCtx, "sampler2d",	"Test sampler2d layout binding with invalid bindings");
+	tcu::TestCaseGroup* const negativeSampler3dBindingTestGroup	= new tcu::TestCaseGroup(m_testCtx, "sampler3d",	"Test sampler3d layout binding with invalid bindings");
+
+	tcu::TestCaseGroup* const negativeImageBindingTestGroup		= new tcu::TestCaseGroup(m_testCtx, "image",		"Test image layout binding with invalid bindings");
+	tcu::TestCaseGroup* const negativeImage2dBindingTestGroup	= new tcu::TestCaseGroup(m_testCtx, "image2d",		"Test image2d layout binding with invalid bindings");
+	tcu::TestCaseGroup* const negativeImage3dBindingTestGroup	= new tcu::TestCaseGroup(m_testCtx, "image3d",		"Test image3d layout binding with invalid bindings");
+
+	tcu::TestCaseGroup* const negativeUBOBindingTestGroup		= new tcu::TestCaseGroup(m_testCtx, "ubo",			"Test UBO layout binding with invalid bindings");
+	tcu::TestCaseGroup* const negativeSSBOBindingTestGroup		= new tcu::TestCaseGroup(m_testCtx, "ssbo",			"Test SSBO layout binding with invalid bindings");
+
+	static const struct RenderTestType
+	{
+		ShaderType				shaderType;
+		TestType			 	testType;
+		std::string 			name;
+		std::string 			descPostfix;
+	} s_renderTestTypes[] =
+	{
+		{ SHADERTYPE_VERTEX,	TESTTYPE_BINDING_SINGLE, 		"vertex_binding_single",	 	"a single instance" },
+		{ SHADERTYPE_VERTEX,	TESTTYPE_BINDING_MAX,			"vertex_binding_max",			"maximum binding point"	},
+		{ SHADERTYPE_VERTEX,	TESTTYPE_BINDING_MULTIPLE,		"vertex_binding_multiple",		"multiple instances"},
+		{ SHADERTYPE_VERTEX,	TESTTYPE_BINDING_ARRAY,			"vertex_binding_array",			"an array instance" },
+		{ SHADERTYPE_VERTEX,	TESTTYPE_BINDING_MAX_ARRAY,		"vertex_binding_max_array",		"an array instance with maximum binding point" },
+
+		{ SHADERTYPE_FRAGMENT,	TESTTYPE_BINDING_SINGLE, 		"fragment_binding_single",	 	"a single instance" },
+		{ SHADERTYPE_FRAGMENT,	TESTTYPE_BINDING_MAX,			"fragment_binding_max",			"maximum binding point"	},
+		{ SHADERTYPE_FRAGMENT,	TESTTYPE_BINDING_MULTIPLE,		"fragment_binding_multiple",	"multiple instances"},
+		{ SHADERTYPE_FRAGMENT,	TESTTYPE_BINDING_ARRAY,			"fragment_binding_array",		"an array instance" },
+		{ SHADERTYPE_FRAGMENT,	TESTTYPE_BINDING_MAX_ARRAY,		"fragment_binding_max_array",	"an array instance with maximum binding point" },
+	};
+
+	static const struct NegativeTestType
+	{
+		ShaderType								shaderType;
+		TestType								testType;
+		LayoutBindingNegativeCase::ErrorType	errorType;
+		std::string								name;
+		std::string								descPostfix;
+	} s_negativeTestTypes[] =
+	{
+		{ SHADERTYPE_VERTEX,	TESTTYPE_BINDING_SINGLE,		LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,	"vertex_binding_over_max",			"over maximum binding point"},
+		{ SHADERTYPE_FRAGMENT,	TESTTYPE_BINDING_SINGLE,		LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,	"fragment_binding_over_max",		"over maximum binding point"},
+		{ SHADERTYPE_VERTEX,	TESTTYPE_BINDING_SINGLE,		LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,	"vertex_binding_neg",				"negative binding point"},
+		{ SHADERTYPE_FRAGMENT,	TESTTYPE_BINDING_SINGLE,		LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,	"fragment_binding_neg",				"negative binding point"},
+
+		{ SHADERTYPE_VERTEX,	TESTTYPE_BINDING_ARRAY,			LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,	"vertex_binding_over_max_array",	"over maximum binding point"},
+		{ SHADERTYPE_FRAGMENT,	TESTTYPE_BINDING_ARRAY,			LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,	"fragment_binding_over_max_array",	"over maximum binding point"},
+		{ SHADERTYPE_VERTEX,	TESTTYPE_BINDING_ARRAY,			LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,	"vertex_binding_neg_array",			"negative binding point"},
+		{ SHADERTYPE_FRAGMENT,	TESTTYPE_BINDING_ARRAY,			LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,	"fragment_binding_neg_array",		"negative binding point"},
+
+		{ SHADERTYPE_BOTH,		TESTTYPE_BINDING_SINGLE,		LayoutBindingNegativeCase::ERRORTYPE_CONTRADICTORY,		"binding_contradictory",			"contradictory binding points"},
+		{ SHADERTYPE_BOTH,		TESTTYPE_BINDING_ARRAY,			LayoutBindingNegativeCase::ERRORTYPE_CONTRADICTORY,		"binding_contradictory_array",		"contradictory binding points"},
+	};
+
+	// Render tests
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(s_renderTestTypes); ++testNdx)
+	{
+		const RenderTestType& test = s_renderTestTypes[testNdx];
+
+		// Render sampler binding tests
+		sampler2dBindingTestGroup->addChild(new SamplerBindingRenderCase(m_context, test.name.c_str(), ("Sampler2D layout binding with " + test.descPostfix).c_str(), test.shaderType, test.testType, GL_SAMPLER_2D, GL_TEXTURE_2D));
+		sampler3dBindingTestGroup->addChild(new SamplerBindingRenderCase(m_context, test.name.c_str(), ("Sampler3D layout binding with " + test.descPostfix).c_str(), test.shaderType, test.testType, GL_SAMPLER_3D, GL_TEXTURE_3D));
+
+		// Render image binding tests
+		image2dBindingTestGroup->addChild(new ImageBindingRenderCase(m_context, test.name.c_str(), ("Image2D layout binding with " + test.descPostfix).c_str(), test.shaderType, test.testType, GL_IMAGE_2D, GL_TEXTURE_2D));
+		image3dBindingTestGroup->addChild(new ImageBindingRenderCase(m_context, test.name.c_str(), ("Image3D layout binding with " + test.descPostfix).c_str(), test.shaderType, test.testType, GL_IMAGE_3D, GL_TEXTURE_3D));
+
+		// Render UBO binding tests
+		UBOBindingTestGroup->addChild(new UBOBindingRenderCase(m_context, test.name.c_str(), ("UBO layout binding with " + test.descPostfix).c_str(), test.shaderType, test.testType));
+
+		// Render SSBO binding tests
+		SSBOBindingTestGroup->addChild(new SSBOBindingRenderCase(m_context, test.name.c_str(), ("SSBO layout binding with " + test.descPostfix).c_str(), test.shaderType, test.testType));
+	}
+
+	// Negative binding tests
+	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(s_negativeTestTypes); ++testNdx)
+	{
+		const NegativeTestType& test = s_negativeTestTypes[testNdx];
+
+		// Negative sampler binding tests
+		negativeSampler2dBindingTestGroup->addChild(new SamplerBindingNegativeCase(m_context, test.name.c_str(), ("Invalid sampler2d layout binding using " + test.descPostfix).c_str(), test.shaderType, test.testType, test.errorType, GL_SAMPLER_2D));
+		negativeSampler3dBindingTestGroup->addChild(new SamplerBindingNegativeCase(m_context, test.name.c_str(), ("Invalid sampler3d layout binding using " + test.descPostfix).c_str(), test.shaderType, test.testType, test.errorType, GL_SAMPLER_3D));
+
+		// Negative image binding tests
+		negativeImage2dBindingTestGroup->addChild(new ImageBindingNegativeCase(m_context, test.name.c_str(), ("Invalid image2d layout binding using " + test.descPostfix).c_str(), test.shaderType, test.testType, test.errorType, GL_IMAGE_2D));
+		negativeImage3dBindingTestGroup->addChild(new ImageBindingNegativeCase(m_context, test.name.c_str(), ("Invalid image3d layout binding using " + test.descPostfix).c_str(), test.shaderType, test.testType, test.errorType, GL_IMAGE_3D));
+
+		// Negative UBO binding tests
+		negativeUBOBindingTestGroup->addChild(new UBOBindingNegativeCase(m_context, test.name.c_str(), ("Invalid UBO layout binding using " + test.descPostfix).c_str(), test.shaderType, test.testType, test.errorType));
+
+		// Negative SSBO binding tests
+		negativeSSBOBindingTestGroup->addChild(new SSBOBindingNegativeCase(m_context, test.name.c_str(), ("Invalid SSBO layout binding using " + test.descPostfix).c_str(), test.shaderType, test.testType, test.errorType));
+	}
+
+	samplerBindingTestGroup->addChild(sampler2dBindingTestGroup);
+	samplerBindingTestGroup->addChild(sampler3dBindingTestGroup);
+
+	imageBindingTestGroup->addChild(image2dBindingTestGroup);
+	imageBindingTestGroup->addChild(image3dBindingTestGroup);
+
+	negativeSamplerBindingTestGroup->addChild(negativeSampler2dBindingTestGroup);
+	negativeSamplerBindingTestGroup->addChild(negativeSampler3dBindingTestGroup);
+
+	negativeImageBindingTestGroup->addChild(negativeImage2dBindingTestGroup);
+	negativeImageBindingTestGroup->addChild(negativeImage3dBindingTestGroup);
+
+	negativeBindingTestGroup->addChild(negativeSamplerBindingTestGroup);
+	negativeBindingTestGroup->addChild(negativeUBOBindingTestGroup);
+	negativeBindingTestGroup->addChild(negativeSSBOBindingTestGroup);
+	negativeBindingTestGroup->addChild(negativeImageBindingTestGroup);
+
+	addChild(samplerBindingTestGroup);
+	addChild(UBOBindingTestGroup);
+	addChild(SSBOBindingTestGroup);
+	addChild(imageBindingTestGroup);
+	addChild(negativeBindingTestGroup);
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fLayoutBindingTests.hpp b/modules/gles31/functional/es31fLayoutBindingTests.hpp
new file mode 100644
index 0000000..fe23d2b
--- /dev/null
+++ b/modules/gles31/functional/es31fLayoutBindingTests.hpp
@@ -0,0 +1,54 @@
+#ifndef _ES31FLAYOUTBINDINGTESTS_HPP
+#define _ES31FLAYOUTBINDINGTESTS_HPP
+
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Basic layout binding test
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class LayoutBindingTests : public TestCaseGroup
+{
+public:
+						LayoutBindingTests		(Context& context);
+						~LayoutBindingTests		(void);
+
+	void				init					(void);
+
+private:
+	LayoutBindingTests&	operator=				(const LayoutBindingTests&);
+	LayoutBindingTests							(const LayoutBindingTests&);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FLAYOUTBINDINGTESTS_HPP
diff --git a/modules/gles31/functional/es31fMultisampleShaderRenderCase.cpp b/modules/gles31/functional/es31fMultisampleShaderRenderCase.cpp
new file mode 100644
index 0000000..3b16c1b
--- /dev/null
+++ b/modules/gles31/functional/es31fMultisampleShaderRenderCase.cpp
@@ -0,0 +1,765 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample shader render case
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fMultisampleShaderRenderCase.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "gluPixelTransfer.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace MultisampleShaderRenderUtil
+{
+namespace
+{
+
+static const char* const s_vertexSource =	"#version 310 es\n"
+											"in highp vec4 a_position;\n"
+											"out highp vec4 v_position;\n"
+											"void main (void)\n"
+											"{\n"
+											"	gl_Position = a_position;\n"
+											"	v_position = a_position;\n"
+											"}";
+
+} // anonymous
+
+QualityWarning::QualityWarning (const std::string& message)
+	: tcu::Exception(message)
+{
+}
+
+MultisampleRenderCase::MultisampleRenderCase (Context& context, const char* name, const char* desc, int numSamples, RenderTarget target, int renderSize, int flags)
+	: TestCase						(context, name, desc)
+	, m_numRequestedSamples			(numSamples)
+	, m_renderTarget				(target)
+	, m_renderSize					(renderSize)
+	, m_perIterationShader			((flags & FLAG_PER_ITERATION_SHADER) != 0)
+	, m_verifyTextureSampleBuffers	((flags & FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS) != 0 && target == TARGET_TEXTURE)
+	, m_numTargetSamples			(-1)
+	, m_buffer						(0)
+	, m_resolveBuffer				(0)
+	, m_program						(DE_NULL)
+	, m_fbo							(0)
+	, m_fboTexture					(0)
+	, m_textureSamplerProgram		(DE_NULL)
+	, m_fboRbo						(0)
+	, m_resolveFbo					(0)
+	, m_resolveFboTexture			(0)
+	, m_iteration					(0)
+	, m_numIterations				(1)
+	, m_renderMode					(0)
+	, m_renderCount					(0)
+	, m_renderVao					(0)
+	, m_resolveVao					(0)
+{
+	DE_ASSERT(target < TARGET_LAST);
+}
+
+MultisampleRenderCase::~MultisampleRenderCase (void)
+{
+	MultisampleRenderCase::deinit();
+}
+
+void MultisampleRenderCase::init (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	deInt32					queriedSampleCount	= -1;
+
+	// requirements
+
+	switch (m_renderTarget)
+	{
+		case TARGET_DEFAULT:
+		{
+			if (m_context.getRenderTarget().getWidth() < m_renderSize || m_context.getRenderTarget().getHeight() < m_renderSize)
+				throw tcu::NotSupportedError("Test requires render target with size " + de::toString(m_renderSize) + "x" + de::toString(m_renderSize) + " or greater");
+			break;
+		}
+
+		case TARGET_TEXTURE:
+		{
+			deInt32 maxTextureSamples = 0;
+			gl.getInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_SAMPLES, 1, &maxTextureSamples);
+
+			if (m_numRequestedSamples > maxTextureSamples)
+				throw tcu::NotSupportedError("Sample count not supported");
+			break;
+		}
+
+		case TARGET_RENDERBUFFER:
+		{
+			deInt32 maxRboSamples = 0;
+			gl.getInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_SAMPLES, 1, &maxRboSamples);
+
+			if (m_numRequestedSamples > maxRboSamples)
+				throw tcu::NotSupportedError("Sample count not supported");
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	// resources
+
+	{
+		gl.genBuffers(1, &m_buffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buf");
+
+		setupRenderData();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "setup data");
+
+		gl.genVertexArrays(1, &m_renderVao);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen vao");
+
+		// buffer for MSAA texture resolving
+		{
+			static const tcu::Vec4 fullscreenQuad[] =
+			{
+				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),
+			};
+
+			gl.genBuffers(1, &m_resolveBuffer);
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_resolveBuffer);
+			gl.bufferData(GL_ARRAY_BUFFER, (int)sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "setup data");
+		}
+	}
+
+	// msaa targets
+
+	if (m_renderTarget == TARGET_TEXTURE)
+	{
+		const deUint32 textureTarget = (m_numRequestedSamples == 0) ? (GL_TEXTURE_2D) : (GL_TEXTURE_2D_MULTISAMPLE);
+
+		gl.genVertexArrays(1, &m_resolveVao);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen vao");
+
+		gl.genTextures(1, &m_fboTexture);
+		gl.bindTexture(textureTarget, m_fboTexture);
+		if (m_numRequestedSamples == 0)
+		{
+			gl.texStorage2D(textureTarget, 1, GL_RGBA8, m_renderSize, m_renderSize);
+			gl.texParameteri(textureTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+			gl.texParameteri(textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		}
+		else
+			gl.texStorage2DMultisample(textureTarget, m_numRequestedSamples, GL_RGBA8, m_renderSize, m_renderSize, GL_FALSE);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");
+
+		gl.genFramebuffers(1, &m_fbo);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+		gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureTarget, m_fboTexture, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");
+
+		if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+			throw tcu::TestError("fbo not complete");
+
+		if (m_numRequestedSamples != 0)
+		{
+			// for shader
+			gl.getTexLevelParameteriv(GL_TEXTURE_2D_MULTISAMPLE, 0, GL_TEXTURE_SAMPLES, &queriedSampleCount);
+
+			// logging
+			m_testCtx.getLog() << tcu::TestLog::Message << "Asked for " << m_numRequestedSamples << " samples, got " << queriedSampleCount << " samples." << tcu::TestLog::EndMessage;
+
+			// sanity
+			if (queriedSampleCount < m_numRequestedSamples)
+				throw tcu::TestError("Got less texture samples than asked for");
+		}
+
+		// texture sampler shader
+		m_textureSamplerProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_vertexSource) << glu::FragmentSource(genMSSamplerSource(queriedSampleCount)));
+		if (!m_textureSamplerProgram->isOk())
+		{
+			m_testCtx.getLog() << tcu::TestLog::Section("SamplerShader", "Sampler shader") << *m_textureSamplerProgram << tcu::TestLog::EndSection;
+			throw tcu::TestError("could not build program");
+		}
+	}
+	else if (m_renderTarget == TARGET_RENDERBUFFER)
+	{
+		gl.genRenderbuffers(1, &m_fboRbo);
+		gl.bindRenderbuffer(GL_RENDERBUFFER, m_fboRbo);
+		gl.renderbufferStorageMultisample(GL_RENDERBUFFER, m_numRequestedSamples, GL_RGBA8, m_renderSize, m_renderSize);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen rbo");
+
+		gl.genFramebuffers(1, &m_fbo);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_fboRbo);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");
+
+		if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+			throw tcu::TestError("fbo not complete");
+
+		// logging
+		gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &queriedSampleCount);
+		m_testCtx.getLog() << tcu::TestLog::Message << "Asked for " << m_numRequestedSamples << " samples, got " << queriedSampleCount << " samples." << tcu::TestLog::EndMessage;
+
+		// sanity
+		if (queriedSampleCount < m_numRequestedSamples)
+			throw tcu::TestError("Got less renderbuffer samples samples than asked for");
+	}
+
+	// fbo for resolving the multisample fbo
+	if (m_renderTarget != TARGET_DEFAULT)
+	{
+		gl.genTextures(1, &m_resolveFboTexture);
+		gl.bindTexture(GL_TEXTURE_2D, m_resolveFboTexture);
+		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, m_renderSize, m_renderSize);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");
+
+		gl.genFramebuffers(1, &m_resolveFbo);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
+		gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_resolveFboTexture, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");
+
+		if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+			throw tcu::TestError("resolve fbo not complete");
+	}
+
+	// create verifier shader and set targetSampleCount
+
+	{
+		int realSampleCount = -1;
+
+		if (m_renderTarget == TARGET_TEXTURE)
+		{
+			if (m_numRequestedSamples == 0)
+				realSampleCount = 1; // non msaa texture
+			else
+				realSampleCount = de::max(1, queriedSampleCount); // msaa texture
+		}
+		else if (m_renderTarget == TARGET_RENDERBUFFER)
+		{
+			realSampleCount = de::max(1, queriedSampleCount); // msaa rbo
+		}
+		else if (m_renderTarget == TARGET_DEFAULT)
+		{
+			realSampleCount = de::max(1, m_context.getRenderTarget().getNumSamples());
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		// is set and is valid
+		DE_ASSERT(realSampleCount != -1);
+		DE_ASSERT(realSampleCount != 0);
+		m_numTargetSamples = realSampleCount;
+	}
+
+	if (!m_perIterationShader)
+	{
+		m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource(m_numTargetSamples)) << glu::FragmentSource(genFragmentSource(m_numTargetSamples)));
+		m_testCtx.getLog() << tcu::TestLog::Section("RenderShader", "Render shader") << *m_program << tcu::TestLog::EndSection;
+		if (!m_program->isOk())
+			throw tcu::TestError("could not build program");
+
+	}
+}
+
+void MultisampleRenderCase::deinit (void)
+{
+	if (m_buffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buffer);
+		m_buffer = 0;
+	}
+
+	if (m_resolveBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_resolveBuffer);
+		m_resolveBuffer = 0;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+
+	if (m_fbo)
+	{
+		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_fbo);
+		m_fbo = 0;
+	}
+
+	if (m_fboTexture)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_fboTexture);
+		m_fboTexture = 0;
+	}
+
+	delete m_textureSamplerProgram;
+	m_textureSamplerProgram = DE_NULL;
+
+	if (m_fboRbo)
+	{
+		m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_fboRbo);
+		m_fboRbo = 0;
+	}
+
+	if (m_resolveFbo)
+	{
+		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_resolveFbo);
+		m_resolveFbo = 0;
+	}
+
+	if (m_resolveFboTexture)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_resolveFboTexture);
+		m_resolveFboTexture = 0;
+	}
+
+	if (m_renderVao)
+	{
+		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_renderVao);
+		m_renderVao = 0;
+	}
+
+	if (m_resolveVao)
+	{
+		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_resolveVao);
+		m_resolveVao = 0;
+	}
+}
+
+MultisampleRenderCase::IterateResult MultisampleRenderCase::iterate (void)
+{
+	// default value
+	if (m_iteration == 0)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		preTest();
+	}
+
+	drawOneIteration();
+
+	// next iteration
+	++m_iteration;
+	if (m_iteration < m_numIterations)
+		return CONTINUE;
+	else
+	{
+		postTest();
+		return STOP;
+	}
+}
+
+void MultisampleRenderCase::preDraw (void)
+{
+}
+
+void MultisampleRenderCase::postDraw (void)
+{
+}
+
+void MultisampleRenderCase::preTest (void)
+{
+}
+
+void MultisampleRenderCase::postTest (void)
+{
+}
+
+void MultisampleRenderCase::verifyResultImageAndSetResult (const tcu::Surface& resultImage)
+{
+	// verify using case-specific verification
+
+	try
+	{
+		if (!verifyImage(resultImage))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+	}
+	catch (const QualityWarning& ex)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Quality warning, error = " << ex.what() << tcu::TestLog::EndMessage;
+
+		// Failures are more important than warnings
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, ex.what());
+	}
+}
+
+void MultisampleRenderCase::verifyResultBuffersAndSetResult (const std::vector<tcu::Surface>& resultBuffers)
+{
+	// verify using case-specific verification
+
+	try
+	{
+		if (!verifySampleBuffers(resultBuffers))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+	}
+	catch (const QualityWarning& ex)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Quality warning, error = " << ex.what() << tcu::TestLog::EndMessage;
+
+		// Failures are more important than warnings
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+			m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, ex.what());
+	}
+}
+
+std::string	MultisampleRenderCase::getIterationDescription (int iteration) const
+{
+	DE_UNREF(iteration);
+	DE_ASSERT(false);
+	return "";
+}
+
+void MultisampleRenderCase::drawOneIteration (void)
+{
+	const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
+	const std::string			sectionDescription	= (m_numIterations > 1) ? ("Iteration " + de::toString(m_iteration+1) + "/" + de::toString(m_numIterations) + ": " + getIterationDescription(m_iteration)) : ("Test");
+	const tcu::ScopedLogSection	section				(m_testCtx.getLog(), "Iteration" + de::toString(m_iteration), sectionDescription);
+
+	// Per iteration shader?
+	if (m_perIterationShader)
+	{
+		delete m_program;
+		m_program = DE_NULL;
+
+		m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource(m_numTargetSamples)) << glu::FragmentSource(genFragmentSource(m_numTargetSamples)));
+		m_testCtx.getLog() << tcu::TestLog::Section("RenderShader", "Render shader") << *m_program << tcu::TestLog::EndSection;
+		if (!m_program->isOk())
+			throw tcu::TestError("could not build program");
+
+	}
+
+	// render
+	{
+		if (m_renderTarget == TARGET_TEXTURE || m_renderTarget == TARGET_RENDERBUFFER)
+		{
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "bind fbo");
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << m_renderSceneDescription << " with render shader to fbo." << tcu::TestLog::EndMessage;
+		}
+		else
+			m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << m_renderSceneDescription << " with render shader to default framebuffer." << tcu::TestLog::EndMessage;
+
+		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.viewport(0, 0, m_renderSize, m_renderSize);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+		gl.bindVertexArray(m_renderVao);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);
+
+		// set attribs
+		DE_ASSERT(!m_renderAttribs.empty());
+		for (std::map<std::string, Attrib>::const_iterator it = m_renderAttribs.begin(); it != m_renderAttribs.end(); ++it)
+		{
+			const deInt32 location = gl.getAttribLocation(m_program->getProgram(), it->first.c_str());
+
+			if (location != -1)
+			{
+				gl.vertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, it->second.stride, (deUint8*)DE_NULL + it->second.offset);
+				gl.enableVertexAttribArray(location);
+			}
+		}
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set attrib");
+
+		gl.useProgram(m_program->getProgram());
+		preDraw();
+		gl.drawArrays(m_renderMode, 0, m_renderCount);
+		postDraw();
+		gl.useProgram(0);
+		gl.bindVertexArray(0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
+
+		if (m_renderTarget == TARGET_TEXTURE || m_renderTarget == TARGET_RENDERBUFFER)
+			gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	}
+
+	// read
+	{
+		if (m_renderTarget == TARGET_DEFAULT)
+		{
+			tcu::Surface resultImage(m_renderSize, m_renderSize);
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Reading pixels from default framebuffer." << tcu::TestLog::EndMessage;
+
+			// default directly
+			glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
+
+			// set test result
+			verifyResultImageAndSetResult(resultImage);
+		}
+		else if (m_renderTarget == TARGET_RENDERBUFFER)
+		{
+			tcu::Surface resultImage(m_renderSize, m_renderSize);
+
+			// rbo by blitting to non-multisample fbo
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Blitting result from fbo to single sample fbo. (Resolve multisample)" << tcu::TestLog::EndMessage;
+
+			gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
+			gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolveFbo);
+			gl.blitFramebuffer(0, 0, m_renderSize, m_renderSize, 0, 0, m_renderSize, m_renderSize, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "blit resolve");
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Reading pixels from single sample framebuffer." << tcu::TestLog::EndMessage;
+
+			gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_resolveFbo);
+			glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+			// set test result
+			verifyResultImageAndSetResult(resultImage);
+		}
+		else if (m_renderTarget == TARGET_TEXTURE && !m_verifyTextureSampleBuffers)
+		{
+			const deInt32	posLocation		= gl.getAttribLocation(m_textureSamplerProgram->getProgram(), "a_position");
+			const deInt32	samplerLocation	= gl.getUniformLocation(m_textureSamplerProgram->getProgram(), "u_sampler");
+			const deUint32	textureTarget	= (m_numRequestedSamples == 0) ? (GL_TEXTURE_2D) : (GL_TEXTURE_2D_MULTISAMPLE);
+			tcu::Surface	resultImage		(m_renderSize, m_renderSize);
+
+			if (m_numRequestedSamples)
+				m_testCtx.getLog() << tcu::TestLog::Message << "Using sampler shader to sample the multisample texture to single sample framebuffer." << tcu::TestLog::EndMessage;
+			else
+				m_testCtx.getLog() << tcu::TestLog::Message << "Drawing texture to single sample framebuffer. Using sampler shader." << tcu::TestLog::EndMessage;
+
+			if (samplerLocation == -1)
+				throw tcu::TestError("Location u_sampler was -1.");
+
+			// resolve multisample texture by averaging
+			gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+			gl.clear(GL_COLOR_BUFFER_BIT);
+			gl.viewport(0, 0, m_renderSize, m_renderSize);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+			gl.bindVertexArray(m_resolveVao);
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_resolveBuffer);
+			gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+			gl.enableVertexAttribArray(posLocation);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "set attrib");
+
+			gl.activeTexture(GL_TEXTURE0);
+			gl.bindTexture(textureTarget, m_fboTexture);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "bind tex");
+
+			gl.useProgram(m_textureSamplerProgram->getProgram());
+			gl.uniform1i(samplerLocation, 0);
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
+			gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+			gl.useProgram(0);
+			gl.bindVertexArray(0);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Reading pixels from single sample framebuffer." << tcu::TestLog::EndMessage;
+
+			glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+			// set test result
+			verifyResultImageAndSetResult(resultImage);
+		}
+		else if (m_renderTarget == TARGET_TEXTURE && m_verifyTextureSampleBuffers)
+		{
+			const deInt32				posLocation		= gl.getAttribLocation(m_textureSamplerProgram->getProgram(), "a_position");
+			const deInt32				samplerLocation	= gl.getUniformLocation(m_textureSamplerProgram->getProgram(), "u_sampler");
+			const deInt32				sampleLocation	= gl.getUniformLocation(m_textureSamplerProgram->getProgram(), "u_sampleNdx");
+			const deUint32				textureTarget	= (m_numRequestedSamples == 0) ? (GL_TEXTURE_2D) : (GL_TEXTURE_2D_MULTISAMPLE);
+			std::vector<tcu::Surface>	resultBuffers	(m_numTargetSamples);
+
+			if (m_numRequestedSamples)
+				m_testCtx.getLog() << tcu::TestLog::Message << "Reading multisample texture sample buffers." << tcu::TestLog::EndMessage;
+			else
+				m_testCtx.getLog() << tcu::TestLog::Message << "Reading texture." << tcu::TestLog::EndMessage;
+
+			if (samplerLocation == -1)
+				throw tcu::TestError("Location u_sampler was -1.");
+			if (sampleLocation == -1)
+				throw tcu::TestError("Location u_sampleNdx was -1.");
+
+			for (int sampleNdx = 0; sampleNdx < m_numTargetSamples; ++sampleNdx)
+				resultBuffers[sampleNdx].setSize(m_renderSize, m_renderSize);
+
+			// read sample buffers to different surfaces
+			gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+			gl.clear(GL_COLOR_BUFFER_BIT);
+			gl.viewport(0, 0, m_renderSize, m_renderSize);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+			gl.bindVertexArray(m_resolveVao);
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_resolveBuffer);
+			gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+			gl.enableVertexAttribArray(posLocation);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "set attrib");
+
+			gl.activeTexture(GL_TEXTURE0);
+			gl.bindTexture(textureTarget, m_fboTexture);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "bind tex");
+
+			gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
+			gl.useProgram(m_textureSamplerProgram->getProgram());
+			gl.uniform1i(samplerLocation, 0);
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Reading sample buffers" << tcu::TestLog::EndMessage;
+
+			for (int sampleNdx = 0; sampleNdx < m_numTargetSamples; ++sampleNdx)
+			{
+				gl.uniform1i(sampleLocation, sampleNdx);
+				gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
+
+				glu::readPixels(m_context.getRenderContext(), 0, 0, resultBuffers[sampleNdx].getAccess());
+				GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
+			}
+
+			gl.useProgram(0);
+			gl.bindVertexArray(0);
+			gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+
+			// verify sample buffers
+			verifyResultBuffersAndSetResult(resultBuffers);
+		}
+		else
+			DE_ASSERT(false);
+	}
+}
+
+std::string	MultisampleRenderCase::genVertexSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+	return std::string(s_vertexSource);
+}
+
+std::string MultisampleRenderCase::genMSSamplerSource (int numTargetSamples) const
+{
+	if (m_verifyTextureSampleBuffers)
+		return genMSTextureLayerFetchSource(numTargetSamples);
+	else
+		return genMSTextureResolverSource(numTargetSamples);
+}
+
+std::string	MultisampleRenderCase::genMSTextureResolverSource (int numTargetSamples) const
+{
+	// default behavior: average
+
+	const bool			isSingleSampleTarget = (m_numRequestedSamples == 0);
+	std::ostringstream	buf;
+
+	buf <<	"#version 310 es\n"
+			"in mediump vec4 v_position;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"uniform mediump " << ((isSingleSampleTarget) ? ("sampler2D") : ("sampler2DMS")) << " u_sampler;\n"
+			"void main (void)\n"
+			"{\n"
+			"	mediump vec2 relPosition = (v_position.xy + vec2(1.0, 1.0)) / 2.0;\n"
+			"	mediump ivec2 fetchPos = ivec2(floor(relPosition * " << m_renderSize << ".0));\n"
+			"	mediump vec4 colorSum = vec4(0.0, 0.0, 0.0, 0.0);\n"
+			"\n";
+
+	if (isSingleSampleTarget)
+		buf <<	"	colorSum = texelFetch(u_sampler, fetchPos, 0);\n"
+				"\n";
+	else
+		buf <<	"	for (int sampleNdx = 0; sampleNdx < " << numTargetSamples << "; ++sampleNdx)\n"
+				"		colorSum += texelFetch(u_sampler, fetchPos, sampleNdx);\n"
+				"	colorSum /= " << numTargetSamples << ".0;\n"
+				"\n";
+
+	buf <<	"	fragColor = vec4(colorSum.xyz, 1.0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string MultisampleRenderCase::genMSTextureLayerFetchSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	const bool			isSingleSampleTarget = (m_numRequestedSamples == 0);
+	std::ostringstream	buf;
+
+	buf <<	"#version 310 es\n"
+			"in mediump vec4 v_position;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"uniform mediump " << ((isSingleSampleTarget) ? ("sampler2D") : ("sampler2DMS")) << " u_sampler;\n"
+			"uniform mediump int u_sampleNdx;\n"
+			"void main (void)\n"
+			"{\n"
+			"	mediump vec2 relPosition = (v_position.xy + vec2(1.0, 1.0)) / 2.0;\n"
+			"	mediump ivec2 fetchPos = ivec2(floor(relPosition * " << m_renderSize << ".0));\n"
+			"\n"
+			"	mediump vec4 color = texelFetch(u_sampler, fetchPos, u_sampleNdx);\n"
+			"	fragColor = vec4(color.rgb, 1.0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+bool MultisampleRenderCase::verifySampleBuffers (const std::vector<tcu::Surface>& resultBuffers)
+{
+	DE_UNREF(resultBuffers);
+	DE_ASSERT(false);
+	return false;
+}
+
+void MultisampleRenderCase::setupRenderData (void)
+{
+	static const tcu::Vec4 fullscreenQuad[] =
+	{
+		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),
+	};
+
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+
+	m_renderMode = GL_TRIANGLE_STRIP;
+	m_renderCount = 4;
+	m_renderSceneDescription = "quad";
+
+	m_renderAttribs["a_position"].offset = 0;
+	m_renderAttribs["a_position"].stride = sizeof(float[4]);
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);
+	gl.bufferData(GL_ARRAY_BUFFER, (int)sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);
+}
+
+} // MultisampleShaderRenderUtil
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fMultisampleShaderRenderCase.hpp b/modules/gles31/functional/es31fMultisampleShaderRenderCase.hpp
new file mode 100644
index 0000000..fab77f6
--- /dev/null
+++ b/modules/gles31/functional/es31fMultisampleShaderRenderCase.hpp
@@ -0,0 +1,139 @@
+#ifndef _ES31FMULTISAMPLESHADERRENDERCASE_HPP
+#define _ES31FMULTISAMPLESHADERRENDERCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample shader render case
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+#include <map>
+
+namespace tcu
+{
+class Surface;
+} // tcu
+
+namespace glu
+{
+class ShaderProgram;
+} // glu
+
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace MultisampleShaderRenderUtil
+{
+
+class QualityWarning : public tcu::Exception
+{
+public:
+	QualityWarning (const std::string& message);
+};
+
+class MultisampleRenderCase : public TestCase
+{
+public:
+	enum RenderTarget
+	{
+		TARGET_DEFAULT = 0,
+		TARGET_TEXTURE,
+		TARGET_RENDERBUFFER,
+
+		TARGET_LAST
+	};
+	enum Flags
+	{
+		FLAG_PER_ITERATION_SHADER				= 1,
+		FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS	= 2, // !< flag set: each sample layer is verified by verifySampleBuffer
+	};
+
+						MultisampleRenderCase			(Context& context, const char* name, const char* desc, int numSamples, RenderTarget target, int renderSize, int flags = 0);
+	virtual				~MultisampleRenderCase			(void);
+
+	virtual void		init							(void);
+	virtual void		deinit							(void);
+	IterateResult		iterate							(void);
+
+private:
+	virtual void		preDraw							(void);
+	virtual void		postDraw						(void);
+	virtual void		preTest							(void);
+	virtual void		postTest						(void);
+	virtual std::string	getIterationDescription			(int iteration) const;
+
+	void				drawOneIteration				(void);
+	void				verifyResultImageAndSetResult	(const tcu::Surface& resultImage);
+	void				verifyResultBuffersAndSetResult	(const std::vector<tcu::Surface>& resultBuffers);
+	virtual std::string	genVertexSource					(int numTargetSamples) const;
+	virtual std::string	genFragmentSource				(int numTargetSamples) const = 0;
+	std::string			genMSSamplerSource				(int numTargetSamples) const;
+	std::string			genMSTextureResolverSource		(int numTargetSamples) const;
+	std::string			genMSTextureLayerFetchSource	(int numTargetSamples) const;
+	virtual bool		verifyImage						(const tcu::Surface& resultImage) = 0;
+	virtual bool		verifySampleBuffers				(const std::vector<tcu::Surface>& resultBuffers);
+	virtual void		setupRenderData					(void);
+
+protected:
+	struct Attrib
+	{
+		int offset;
+		int stride;
+	};
+
+	const int			m_numRequestedSamples;
+	const RenderTarget	m_renderTarget;
+	const int			m_renderSize;
+	const bool			m_perIterationShader;
+	const bool			m_verifyTextureSampleBuffers;
+	deInt32				m_numTargetSamples;
+
+	deUint32			m_buffer;
+	deUint32			m_resolveBuffer;
+	glu::ShaderProgram*	m_program;
+	deUint32			m_fbo;
+	deUint32			m_fboTexture;
+	glu::ShaderProgram*	m_textureSamplerProgram;
+	deUint32			m_fboRbo;
+	deUint32			m_resolveFbo;
+	deUint32			m_resolveFboTexture;
+	int					m_iteration;
+	int					m_numIterations;
+	deUint32			m_renderMode;
+	deInt32				m_renderCount;
+	deUint32			m_renderVao;
+	deUint32			m_resolveVao;
+
+	std::string			m_renderSceneDescription;
+	std::map<std::string, Attrib> m_renderAttribs;
+};
+
+} // MultisampleShaderRenderUtil
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FMULTISAMPLESHADERRENDERCASE_HPP
diff --git a/modules/gles31/functional/es31fMultisampleTests.cpp b/modules/gles31/functional/es31fMultisampleTests.cpp
new file mode 100644
index 0000000..028eef2
--- /dev/null
+++ b/modules/gles31/functional/es31fMultisampleTests.cpp
@@ -0,0 +1,1033 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fMultisampleTests.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuVector.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+#include "deMath.h"
+
+using namespace glw;
+
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+static std::string sampleMaskToString (const std::vector<deUint32>& bitfield, int numBits)
+{
+	std::string result(numBits, '0');
+
+	// move from back to front and set chars to 1
+	for (int wordNdx = 0; wordNdx < (int)bitfield.size(); ++wordNdx)
+	{
+		for (int bit = 0; bit < 32; ++bit)
+		{
+			const int targetCharNdx = numBits - (wordNdx*32+bit) - 1;
+
+			// beginning of the string reached
+			if (targetCharNdx < 0)
+				return result;
+
+			if ((bitfield[wordNdx] >> bit) & 0x01)
+				result[targetCharNdx] = '1';
+		}
+	}
+
+	return result;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Returns the number of words needed to represent mask of given length
+ *//*--------------------------------------------------------------------*/
+static int getEffectiveSampleMaskWordCount (int highestBitNdx)
+{
+	const int wordSize	= 32;
+	const int maskLen	= highestBitNdx + 1;
+
+	return ((maskLen - 1) / wordSize) + 1; // round_up(mask_len /  wordSize)
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Creates sample mask with all less significant bits than nthBit set
+ *//*--------------------------------------------------------------------*/
+static std::vector<deUint32> genAllSetToNthBitSampleMask (int nthBit)
+{
+	const int				wordSize	= 32;
+	const int				numWords	= getEffectiveSampleMaskWordCount(nthBit - 1);
+	const deUint32			topWordBits	= (deUint32)(nthBit - (numWords - 1) * wordSize);
+	std::vector<deUint32>	mask		(numWords);
+
+	for (int ndx = 0; ndx < numWords - 1; ++ndx)
+		mask[ndx] = 0xFFFFFFFF;
+
+	mask[numWords - 1] = (deUint32)((1ULL << topWordBits) - (deUint32)1);
+	return mask;
+}
+
+class SamplePosQueryCase : public TestCase
+{
+public:
+					SamplePosQueryCase (Context& context, const char* name, const char* desc);
+private:
+	void			init				(void);
+	IterateResult	iterate				(void);
+};
+
+SamplePosQueryCase::SamplePosQueryCase (Context& context, const char* name, const char* desc)
+	: TestCase(context, name, desc)
+{
+}
+
+void SamplePosQueryCase::init (void)
+{
+	if (m_context.getRenderTarget().getNumSamples() == 0)
+		throw tcu::NotSupportedError("No multisample buffers");
+}
+
+SamplePosQueryCase::IterateResult SamplePosQueryCase::iterate (void)
+{
+	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	bool				error	= false;
+
+	gl.enableLogging(true);
+
+	for (int ndx = 0; ndx < m_context.getRenderTarget().getNumSamples(); ++ndx)
+	{
+		tcu::Vec2 samplePos = tcu::Vec2(-1, -1);
+
+		gl.glGetMultisamplefv(GL_SAMPLE_POSITION, ndx, samplePos.getPtr());
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "getMultisamplefv");
+
+		// check value range
+		if (samplePos.x() < 0.0f || samplePos.x() > 1.0f ||
+			samplePos.y() < 0.0f || samplePos.y() > 1.0f)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Sample " << ndx << " is not in valid range [0,1], got " << samplePos << tcu::TestLog::EndMessage;
+			error = true;
+		}
+	}
+
+	if (!error)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid sample pos");
+
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Abstract base class handling common stuff for default fbo multisample cases.
+ *//*--------------------------------------------------------------------*/
+class DefaultFBOMultisampleCase : public TestCase
+{
+public:
+								DefaultFBOMultisampleCase	(Context& context, const char* name, const char* desc, int desiredViewportSize);
+	virtual						~DefaultFBOMultisampleCase	(void);
+
+	virtual void				init						(void);
+	virtual void				deinit						(void);
+
+protected:
+	void						renderTriangle				(const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const;
+	void						renderTriangle				(const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& color) const;
+	void						renderTriangle				(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const;
+	void						renderTriangle				(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const;
+	void						renderQuad					(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& c0, const Vec4& c1, const Vec4& c2, const Vec4& c3) const;
+	void						renderQuad					(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& color) const;
+
+	void						randomizeViewport			(void);
+	void						readImage					(tcu::Surface& dst) const;
+
+	int							m_numSamples;
+
+	int							m_viewportSize;
+
+private:
+								DefaultFBOMultisampleCase	(const DefaultFBOMultisampleCase& other);
+	DefaultFBOMultisampleCase&	operator=					(const DefaultFBOMultisampleCase& other);
+
+	const int					m_desiredViewportSize;
+
+	glu::ShaderProgram*			m_program;
+	int							m_attrPositionLoc;
+	int							m_attrColorLoc;
+
+	int							m_viewportX;
+	int							m_viewportY;
+	de::Random					m_rnd;
+
+	bool						m_initCalled;
+};
+
+DefaultFBOMultisampleCase::DefaultFBOMultisampleCase (Context& context, const char* name, const char* desc, int desiredViewportSize)
+	: TestCase				(context, name, desc)
+	, m_numSamples			(0)
+	, m_viewportSize		(0)
+	, m_desiredViewportSize	(desiredViewportSize)
+	, m_program				(DE_NULL)
+	, m_attrPositionLoc		(-1)
+	, m_attrColorLoc		(-1)
+	, m_viewportX			(0)
+	, m_viewportY			(0)
+	, m_rnd					(deStringHash(name))
+	, m_initCalled			(false)
+{
+}
+
+DefaultFBOMultisampleCase::~DefaultFBOMultisampleCase (void)
+{
+	DefaultFBOMultisampleCase::deinit();
+}
+
+void DefaultFBOMultisampleCase::init (void)
+{
+	static const char* vertShaderSource =
+		"#version 310 es\n"
+		"in highp vec4 a_position;\n"
+		"in mediump vec4 a_color;\n"
+		"out mediump vec4 v_color;\n"
+		"void main()\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_color = a_color;\n"
+		"}\n";
+
+	static const char* fragShaderSource =
+		"#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";
+
+	TestLog&				log	= m_testCtx.getLog();
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+
+	if (m_context.getRenderTarget().getNumSamples() <= 1)
+		throw tcu::NotSupportedError("No multisample buffers");
+
+	m_initCalled = true;
+
+	// Query and log number of samples per pixel.
+
+	gl.getIntegerv(GL_SAMPLES, &m_numSamples);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "getIntegerv(GL_SAMPLES)");
+	log << TestLog::Message << "GL_SAMPLES = " << m_numSamples << TestLog::EndMessage;
+
+	// Prepare program.
+
+	DE_ASSERT(!m_program);
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource));
+	if (!m_program->isOk())
+		throw tcu::TestError("Failed to compile program", DE_NULL, __FILE__, __LINE__);
+
+	m_attrPositionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	m_attrColorLoc		= gl.getAttribLocation(m_program->getProgram(), "a_color");
+	GLU_EXPECT_NO_ERROR(gl.getError(), "getAttribLocation");
+
+	if (m_attrPositionLoc < 0 || m_attrColorLoc < 0)
+	{
+		delete m_program;
+		throw tcu::TestError("Invalid attribute locations", DE_NULL, __FILE__, __LINE__);
+	}
+
+	// Get suitable viewport size.
+
+	m_viewportSize = de::min<int>(m_desiredViewportSize, de::min(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight()));
+	randomizeViewport();
+}
+
+void DefaultFBOMultisampleCase::deinit (void)
+{
+	// Do not try to call GL functions during case list creation
+	if (!m_initCalled)
+		return;
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+void DefaultFBOMultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const
+{
+	const float vertexPositions[] =
+	{
+		p0.x(), p0.y(), p0.z(), 1.0f,
+		p1.x(), p1.y(), p1.z(), 1.0f,
+		p2.x(), p2.y(), p2.z(), 1.0f
+	};
+	const float vertexColors[] =
+	{
+		c0.x(), c0.y(), c0.z(), c0.w(),
+		c1.x(), c1.y(), c1.z(), c1.w(),
+		c2.x(), c2.y(), c2.z(), c2.w(),
+	};
+
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	glu::Buffer				vtxBuf	(m_context.getRenderContext());
+	glu::Buffer				colBuf	(m_context.getRenderContext());
+	glu::VertexArray		vao		(m_context.getRenderContext());
+
+	gl.bindVertexArray(*vao);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bindVertexArray");
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, *vtxBuf);
+	gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), &vertexPositions[0], GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "vtx buf");
+
+	gl.enableVertexAttribArray(m_attrPositionLoc);
+	gl.vertexAttribPointer(m_attrPositionLoc, 4, GL_FLOAT, false, 0, DE_NULL);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "vtx vertexAttribPointer");
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, *colBuf);
+	gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexColors), &vertexColors[0], GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "col buf");
+
+	gl.enableVertexAttribArray(m_attrColorLoc);
+	gl.vertexAttribPointer(m_attrColorLoc, 4, GL_FLOAT, false, 0, DE_NULL);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "col vertexAttribPointer");
+
+	gl.useProgram(m_program->getProgram());
+	gl.drawArrays(GL_TRIANGLES, 0, 3);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");
+}
+
+void DefaultFBOMultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& color) const
+{
+	renderTriangle(p0, p1, p2, color, color, color);
+}
+
+void DefaultFBOMultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const
+{
+	renderTriangle(Vec3(p0.x(), p0.y(), 0.0f),
+				   Vec3(p1.x(), p1.y(), 0.0f),
+				   Vec3(p2.x(), p2.y(), 0.0f),
+				   c0, c1, c2);
+}
+
+void DefaultFBOMultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const
+{
+	renderTriangle(p0, p1, p2, color, color, color);
+}
+
+void DefaultFBOMultisampleCase::renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& c0, const Vec4& c1, const Vec4& c2, const Vec4& c3) const
+{
+	renderTriangle(p0, p1, p2, c0, c1, c2);
+	renderTriangle(p2, p1, p3, c2, c1, c3);
+}
+
+void DefaultFBOMultisampleCase::renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& color) const
+{
+	renderQuad(p0, p1, p2, p3, color, color, color, color);
+}
+
+void DefaultFBOMultisampleCase::randomizeViewport (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	m_viewportX = m_rnd.getInt(0, m_context.getRenderTarget().getWidth()  - m_viewportSize);
+	m_viewportY = m_rnd.getInt(0, m_context.getRenderTarget().getHeight() - m_viewportSize);
+
+	gl.viewport(m_viewportX, m_viewportY, m_viewportSize, m_viewportSize);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "viewport");
+}
+
+void DefaultFBOMultisampleCase::readImage (tcu::Surface& dst) const
+{
+	glu::readPixels(m_context.getRenderContext(), m_viewportX, m_viewportY, dst.getAccess());
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests coverage mask inversion validity.
+ *
+ * Tests that the coverage masks obtained by masks set with glSampleMaski(mask)
+ * and glSampleMaski(~mask) are indeed each others' inverses.
+ *
+ * This is done by drawing a pattern, with varying coverage values,
+ * overlapped by a pattern that has inverted masks and is otherwise
+ * identical. The resulting image is compared to one obtained by drawing
+ * the same pattern but with all-ones coverage masks.
+ *//*--------------------------------------------------------------------*/
+class MaskInvertCase : public DefaultFBOMultisampleCase
+{
+public:
+					MaskInvertCase				(Context& context, const char* name, const char* description);
+					~MaskInvertCase				(void) {}
+
+	void			init						(void);
+	IterateResult	iterate						(void);
+
+private:
+	void			drawPattern					(bool invert) const;
+};
+
+MaskInvertCase::MaskInvertCase (Context& context, const char* name, const char* description)
+	: DefaultFBOMultisampleCase	(context, name, description, 256)
+{
+}
+
+void MaskInvertCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// check the test is even possible
+
+	GLint maxSampleMaskWords = 0;
+	gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
+	if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
+		throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");
+
+	// normal init
+	DefaultFBOMultisampleCase::init();
+}
+
+MaskInvertCase::IterateResult MaskInvertCase::iterate (void)
+{
+	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();
+	TestLog&				log								= m_testCtx.getLog();
+	tcu::Surface			renderedImgNoSampleCoverage		(m_viewportSize, m_viewportSize);
+	tcu::Surface			renderedImgSampleCoverage		(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	gl.enable(GL_BLEND);
+	gl.blendEquation(GL_FUNC_ADD);
+	gl.blendFunc(GL_ONE, GL_ONE);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set blend");
+	log << TestLog::Message << "Additive blending enabled in order to detect (erroneously) overlapping samples" << TestLog::EndMessage;
+
+	log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage;
+	gl.clearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK disabled" << TestLog::EndMessage;
+	drawPattern(false);
+	readImage(renderedImgNoSampleCoverage);
+
+	log << TestLog::Image("RenderedImageNoSampleMask", "Rendered image with GL_SAMPLE_MASK disabled", renderedImgNoSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage;
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	gl.enable(GL_SAMPLE_MASK);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable(GL_SAMPLE_MASK)");
+
+	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK enabled, using non-inverted sample masks" << TestLog::EndMessage;
+	drawPattern(false);
+	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK enabled, using inverted sample masks" << TestLog::EndMessage;
+	drawPattern(true);
+
+	readImage(renderedImgSampleCoverage);
+
+	log << TestLog::Image("RenderedImageSampleMask", "Rendered image with GL_SAMPLE_MASK enabled", renderedImgSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	bool passed = tcu::pixelThresholdCompare(log,
+											 "CoverageVsNoCoverage",
+											 "Comparison of same pattern with GL_SAMPLE_MASK disabled and enabled",
+											 renderedImgNoSampleCoverage,
+											 renderedImgSampleCoverage,
+											 tcu::RGBA(0),
+											 tcu::COMPARE_LOG_ON_ERROR);
+
+	if (passed)
+		log << TestLog::Message << "Success: The two images rendered are identical" << TestLog::EndMessage;
+
+	m_context.getTestContext().setTestResult(passed ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+											 passed ? "Passed"				: "Failed");
+
+	return STOP;
+}
+
+void MaskInvertCase::drawPattern (bool invert) const
+{
+	const int				numTriangles	= 25;
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+
+	for (int triNdx = 0; triNdx < numTriangles; triNdx++)
+	{
+		const float	angle0	= 2.0f*DE_PI * (float)triNdx			/ (float)numTriangles;
+		const float	angle1	= 2.0f*DE_PI * (float)(triNdx + 0.5f)	/ (float)numTriangles;
+		const Vec4	color	= Vec4(0.4f + (float)triNdx/(float)numTriangles*0.6f,
+		                           0.5f + (float)triNdx/(float)numTriangles*0.3f,
+		                           0.6f - (float)triNdx/(float)numTriangles*0.5f,
+		                           0.7f - (float)triNdx/(float)numTriangles*0.7f);
+
+
+		const int			wordCount		= getEffectiveSampleMaskWordCount(m_numSamples - 1);
+		const GLbitfield	finalWordBits	= m_numSamples - 32 * ((m_numSamples-1) / 32);
+		const GLbitfield	finalWordMask	= (GLbitfield)(1ULL << finalWordBits) - 1UL;
+
+		for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
+		{
+			const GLbitfield	rawMask		= (GLbitfield)deUint32Hash(wordNdx * 32 + triNdx);
+			const GLbitfield	mask		= (invert) ? (~rawMask) : (rawMask);
+			const bool			isFinalWord	= (wordNdx + 1) == wordCount;
+			const GLbitfield	maskMask	= (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask prevents setting coverage bits higher than sample count
+
+			gl.sampleMaski(wordNdx, mask & maskMask);
+		}
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
+
+		renderTriangle(Vec2(0.0f, 0.0f),
+					   Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f),
+					   Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f),
+					   color);
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests coverage mask generation proportionality property.
+ *
+ * Tests that the number of coverage bits in a coverage mask set with
+ * glSampleMaski is, on average, proportional to the number of set bits.
+ * Draws multiple frames, each time increasing the number of mask bits set
+ * and checks that the average color is changing appropriately.
+ *//*--------------------------------------------------------------------*/
+class MaskProportionalityCase : public DefaultFBOMultisampleCase
+{
+public:
+					MaskProportionalityCase				(Context& context, const char* name, const char* description);
+					~MaskProportionalityCase			(void) {}
+
+	void			init								(void);
+
+	IterateResult	iterate								(void);
+
+private:
+	int				m_numIterations;
+	int				m_currentIteration;
+
+	deInt32			m_previousIterationColorSum;
+};
+
+MaskProportionalityCase::MaskProportionalityCase (Context& context, const char* name, const char* description)
+	: DefaultFBOMultisampleCase		(context, name, description, 32)
+	, m_numIterations				(-1)
+	, m_currentIteration			(0)
+	, m_previousIterationColorSum	(-1)
+{
+}
+
+void MaskProportionalityCase::init (void)
+{
+	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
+	TestLog&				log	= m_testCtx.getLog();
+
+	// check the test is even possible
+	GLint maxSampleMaskWords = 0;
+	gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
+	if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
+		throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");
+
+	DefaultFBOMultisampleCase::init();
+
+	// set state
+	gl.enable(GL_SAMPLE_MASK);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable(GL_SAMPLE_MASK)");
+	log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage;
+
+	m_numIterations = m_numSamples + 1;
+
+	randomizeViewport(); // \note Using the same viewport for every iteration since coverage mask may depend on window-relative pixel coordinate.
+}
+
+MaskProportionalityCase::IterateResult MaskProportionalityCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	TestLog&				log				= m_testCtx.getLog();
+	tcu::Surface			renderedImg		(m_viewportSize, m_viewportSize);
+	deInt32					numPixels		= (deInt32)renderedImg.getWidth()*(deInt32)renderedImg.getHeight();
+
+	DE_ASSERT(m_numIterations >= 0);
+
+	log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
+	gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	// Draw quad.
+
+	{
+		const Vec2					pt0						(-1.0f, -1.0f);
+		const Vec2					pt1						( 1.0f, -1.0f);
+		const Vec2					pt2						(-1.0f,  1.0f);
+		const Vec2					pt3						( 1.0f,  1.0f);
+		Vec4						quadColor				(1.0f, 0.0f, 0.0f, 1.0f);
+		const std::vector<deUint32>	sampleMask				= genAllSetToNthBitSampleMask(m_currentIteration);
+
+		DE_ASSERT(m_currentIteration <= m_numSamples + 1);
+
+		log << TestLog::Message << "Drawing a red quad using sample mask 0b" << sampleMaskToString(sampleMask, m_numSamples) << TestLog::EndMessage;
+
+		for (int wordNdx = 0; wordNdx < getEffectiveSampleMaskWordCount(m_numSamples - 1); ++wordNdx)
+		{
+			const GLbitfield mask = (wordNdx < (int)sampleMask.size()) ? ((GLbitfield)(sampleMask[wordNdx])) : (0);
+
+			gl.sampleMaski(wordNdx, mask);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
+		}
+
+		renderQuad(pt0, pt1, pt2, pt3, quadColor);
+	}
+
+	// Read ang log image.
+
+	readImage(renderedImg);
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	// Compute average red component in rendered image.
+
+	deInt32 sumRed = 0;
+
+	for (int y = 0; y < renderedImg.getHeight(); y++)
+	for (int x = 0; x < renderedImg.getWidth(); x++)
+		sumRed += renderedImg.getPixel(x, y).getRed();
+
+	log << TestLog::Message << "Average red color component: " << de::floatToString((float)sumRed / 255.0f / (float)numPixels, 2) << TestLog::EndMessage;
+
+	// Check if average color has decreased from previous frame's color.
+
+	if (sumRed < m_previousIterationColorSum)
+	{
+		log << TestLog::Message << "Failure: Current average red color component is lower than previous" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+
+	// Check if coverage mask is not all-zeros if alpha or coverage value is 0 (or 1, if inverted).
+
+	if (m_currentIteration == 0 && sumRed != 0)
+	{
+		log << TestLog::Message << "Failure: Image should be completely black" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+
+	if (m_currentIteration == m_numIterations-1 && sumRed != 0xff*numPixels)
+	{
+		log << TestLog::Message << "Failure: Image should be completely red" << TestLog::EndMessage;
+
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+		return STOP;
+	}
+
+	m_previousIterationColorSum = sumRed;
+
+	m_currentIteration++;
+
+	if (m_currentIteration >= m_numIterations)
+	{
+		log << TestLog::Message << "Success: Number of coverage mask bits set appears to be, on average, proportional to the number of set sample mask bits" << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests coverage mask generation constancy property.
+ *
+ * Tests that the coverage mask created by GL_SAMPLE_MASK is constant at
+ * given pixel coordinates. Draws two quads, with the second one fully
+ * overlapping the first one such that at any given pixel, both quads have
+ * the same coverage mask value. This way, if the constancy property is
+ * fulfilled, only the second quad should be visible.
+ *//*--------------------------------------------------------------------*/
+class MaskConstancyCase : public DefaultFBOMultisampleCase
+{
+public:
+	enum CaseBits
+	{
+		CASEBIT_ALPHA_TO_COVERAGE			= 1,	//!< Use alpha-to-coverage.
+		CASEBIT_SAMPLE_COVERAGE				= 2,	//!< Use sample coverage.
+		CASEBIT_SAMPLE_COVERAGE_INVERTED	= 4,	//!< Inverted sample coverage.
+		CASEBIT_SAMPLE_MASK					= 8,	//!< Use sample mask.
+	};
+
+					MaskConstancyCase			(Context& context, const char* name, const char* description, deUint32 typeBits);
+					~MaskConstancyCase			(void) {}
+
+	void			init						(void);
+	IterateResult	iterate						(void);
+
+private:
+	const bool		m_isAlphaToCoverageCase;
+	const bool		m_isSampleCoverageCase;
+	const bool		m_isInvertedSampleCoverageCase;
+	const bool		m_isSampleMaskCase;
+};
+
+MaskConstancyCase::MaskConstancyCase (Context& context, const char* name, const char* description, deUint32 typeBits)
+	: DefaultFBOMultisampleCase			(context, name, description, 256)
+	, m_isAlphaToCoverageCase			(0 != (typeBits & CASEBIT_ALPHA_TO_COVERAGE))
+	, m_isSampleCoverageCase			(0 != (typeBits & CASEBIT_SAMPLE_COVERAGE))
+	, m_isInvertedSampleCoverageCase	(0 != (typeBits & CASEBIT_SAMPLE_COVERAGE_INVERTED))
+	, m_isSampleMaskCase				(0 != (typeBits & CASEBIT_SAMPLE_MASK))
+{
+	// CASEBIT_SAMPLE_COVERAGE_INVERT => CASEBIT_SAMPLE_COVERAGE
+	DE_ASSERT((typeBits & CASEBIT_SAMPLE_COVERAGE) || ~(typeBits & CASEBIT_SAMPLE_COVERAGE_INVERTED));
+	DE_ASSERT(m_isSampleMaskCase); // no point testing non-sample-mask cases, they are checked already in gles3
+}
+
+void MaskConstancyCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// check the test is even possible
+	if (m_isSampleMaskCase)
+	{
+		GLint maxSampleMaskWords = 0;
+		gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
+		if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
+			throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");
+	}
+
+	// normal init
+	DefaultFBOMultisampleCase::init();
+}
+
+MaskConstancyCase::IterateResult MaskConstancyCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	TestLog&				log				= m_testCtx.getLog();
+	tcu::Surface			renderedImg		(m_viewportSize, m_viewportSize);
+
+	randomizeViewport();
+
+	log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	if (m_isAlphaToCoverageCase)
+	{
+		gl.enable(GL_SAMPLE_ALPHA_TO_COVERAGE);
+		gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_ALPHA_TO_COVERAGE");
+
+		log << TestLog::Message << "GL_SAMPLE_ALPHA_TO_COVERAGE is enabled" << TestLog::EndMessage;
+		log << TestLog::Message << "Color mask is TRUE, TRUE, TRUE, FALSE" << TestLog::EndMessage;
+	}
+
+	if (m_isSampleCoverageCase)
+	{
+		gl.enable(GL_SAMPLE_COVERAGE);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_COVERAGE");
+
+		log << TestLog::Message << "GL_SAMPLE_COVERAGE is enabled" << TestLog::EndMessage;
+	}
+
+	if (m_isSampleMaskCase)
+	{
+		gl.enable(GL_SAMPLE_MASK);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_MASK");
+
+		log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage;
+	}
+
+	log << TestLog::Message
+		<< "Drawing several green quads, each fully overlapped by a red quad with the same "
+		<< (m_isAlphaToCoverageCase ? "alpha" : "")
+		<< (m_isAlphaToCoverageCase && (m_isSampleCoverageCase || m_isSampleMaskCase) ? " and " : "")
+		<< (m_isInvertedSampleCoverageCase ? "inverted " : "")
+		<< (m_isSampleCoverageCase ? "sample coverage" : "")
+		<< (m_isSampleCoverageCase && m_isSampleMaskCase ? " and " : "")
+		<< (m_isSampleMaskCase ? "sample mask" : "")
+		<< " values"
+		<< TestLog::EndMessage;
+
+	const int numQuadRowsCols = m_numSamples*4;
+
+	for (int row = 0; row < numQuadRowsCols; row++)
+	{
+		for (int col = 0; col < numQuadRowsCols; col++)
+		{
+			float		x0			= (float)(col+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float		x1			= (float)(col+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float		y0			= (float)(row+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float		y1			= (float)(row+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			const Vec4	baseGreen	(0.0f, 1.0f, 0.0f, 0.0f);
+			const Vec4	baseRed		(1.0f, 0.0f, 0.0f, 0.0f);
+			Vec4		alpha0		(0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)col / (float)(numQuadRowsCols-1) : 1.0f);
+			Vec4		alpha1		(0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)row / (float)(numQuadRowsCols-1) : 1.0f);
+
+			if (m_isSampleCoverageCase)
+			{
+				float value = (float)(row*numQuadRowsCols + col) / (float)(numQuadRowsCols*numQuadRowsCols-1);
+				gl.sampleCoverage(m_isInvertedSampleCoverageCase ? 1.0f - value : value, m_isInvertedSampleCoverageCase ? GL_TRUE : GL_FALSE);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleCoverage");
+			}
+
+			if (m_isSampleMaskCase)
+			{
+				const int			wordCount		= getEffectiveSampleMaskWordCount(m_numSamples - 1);
+				const GLbitfield	finalWordBits	= m_numSamples - 32 * ((m_numSamples-1) / 32);
+				const GLbitfield	finalWordMask	= (GLbitfield)(1ULL << finalWordBits) - 1UL;
+
+				for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
+				{
+					const GLbitfield	mask		= (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row);
+					const bool			isFinalWord	= (wordNdx + 1) == wordCount;
+					const GLbitfield	maskMask	= (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask prevents setting coverage bits higher than sample count
+
+					gl.sampleMaski(wordNdx, mask & maskMask);
+					GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
+				}
+			}
+
+			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseGreen + alpha0,	baseGreen + alpha1,	baseGreen + alpha0,	baseGreen + alpha1);
+			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseRed + alpha0,	baseRed + alpha1,	baseRed + alpha0,	baseRed + alpha1);
+		}
+	}
+
+	readImage(renderedImg);
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	for (int y = 0; y < renderedImg.getHeight(); y++)
+	for (int x = 0; x < renderedImg.getWidth(); x++)
+	{
+		if (renderedImg.getPixel(x, y).getGreen() > 0)
+		{
+			log << TestLog::Message << "Failure: Non-zero green color component detected - should have been completely overwritten by red quad" << TestLog::EndMessage;
+			m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
+			return STOP;
+		}
+	}
+
+	log << TestLog::Message
+		<< "Success: Coverage mask appears to be constant at a given pixel coordinate with a given "
+		<< (m_isAlphaToCoverageCase ? "alpha" : "")
+		<< (m_isAlphaToCoverageCase && m_isSampleCoverageCase ? " and " : "")
+		<< (m_isSampleCoverageCase ? "coverage value" : "")
+		<< TestLog::EndMessage;
+
+	m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Tests that unused bits of a sample mask have no effect
+ *
+ * Tests that the bits in the sample mask with positions higher than
+ * the number of samples do not have effect. In multisample fragment
+ * operations the sample mask is ANDed with the fragment coverage value.
+ * The coverage value cannot have the corresponding bits set.
+ *
+ * This is done by drawing a quads with varying sample masks and then
+ * redrawing the quads with identical masks but with the mask's high bits
+ * having different values. Only the latter quad pattern should be visible.
+ *//*--------------------------------------------------------------------*/
+class SampleMaskHighBitsCase : public DefaultFBOMultisampleCase
+{
+public:
+					SampleMaskHighBitsCase		(Context& context, const char* name, const char* description);
+					~SampleMaskHighBitsCase		(void) {}
+
+	void			init						(void);
+	IterateResult	iterate						(void);
+};
+
+SampleMaskHighBitsCase::SampleMaskHighBitsCase (Context& context, const char* name, const char* description)
+	: DefaultFBOMultisampleCase(context, name, description, 256)
+{
+}
+
+void SampleMaskHighBitsCase::init (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	GLint					 maxSampleMaskWords	= 0;
+
+	// check the test is even possible
+	gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
+	if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
+		throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");
+
+	// normal init
+	DefaultFBOMultisampleCase::init();
+}
+
+SampleMaskHighBitsCase::IterateResult SampleMaskHighBitsCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	TestLog&				log				= m_testCtx.getLog();
+	tcu::Surface			renderedImg		(m_viewportSize, m_viewportSize);
+	de::Random				rnd				(12345);
+
+	if (m_numSamples % 32 == 0)
+	{
+		log << TestLog::Message << "Sample count is multiple of word size. No unused high bits in sample mask.\nSkipping." << TestLog::EndMessage;
+		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Skipped");
+		return STOP;
+	}
+
+	randomizeViewport();
+
+	log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	gl.enable(GL_SAMPLE_MASK);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_MASK");
+	log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage;
+	log << TestLog::Message << "Drawing several green quads, each fully overlapped by a red quad with the same effective sample mask values" << TestLog::EndMessage;
+
+	const int numQuadRowsCols = m_numSamples*4;
+
+	for (int row = 0; row < numQuadRowsCols; row++)
+	{
+		for (int col = 0; col < numQuadRowsCols; col++)
+		{
+			float				x0				= (float)(col+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float				x1				= (float)(col+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float				y0				= (float)(row+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			float				y1				= (float)(row+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
+			const Vec4			baseGreen		(0.0f, 1.0f, 0.0f, 1.0f);
+			const Vec4			baseRed			(1.0f, 0.0f, 0.0f, 1.0f);
+
+			const int			wordCount		= getEffectiveSampleMaskWordCount(m_numSamples - 1);
+			const GLbitfield	finalWordBits	= m_numSamples - 32 * ((m_numSamples-1) / 32);
+			const GLbitfield	finalWordMask	= (GLbitfield)(1ULL << finalWordBits) - 1UL;
+
+			for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
+			{
+				const GLbitfield	mask		= (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row);
+				const bool			isFinalWord	= (wordNdx + 1) == wordCount;
+				const GLbitfield	maskMask	= (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask is 1 on bits in lower positions than sample count
+				const GLbitfield	highBits	= rnd.getUint32();
+
+				gl.sampleMaski(wordNdx, (mask & maskMask) | (highBits & ~maskMask));
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
+			}
+			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseGreen, baseGreen, baseGreen, baseGreen);
+
+			for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
+			{
+				const GLbitfield	mask		= (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row);
+				const bool			isFinalWord	= (wordNdx + 1) == wordCount;
+				const GLbitfield	maskMask	= (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask is 1 on bits in lower positions than sample count
+				const GLbitfield	highBits	= rnd.getUint32();
+
+				gl.sampleMaski(wordNdx, (mask & maskMask) | (highBits & ~maskMask));
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
+			}
+			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseRed, baseRed, baseRed, baseRed);
+		}
+	}
+
+	readImage(renderedImg);
+
+	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);
+
+	for (int y = 0; y < renderedImg.getHeight(); y++)
+	for (int x = 0; x < renderedImg.getWidth(); x++)
+	{
+		if (renderedImg.getPixel(x, y).getGreen() > 0)
+		{
+			log << TestLog::Message << "Failure: Non-zero green color component detected - should have been completely overwritten by red quad. Mask unused bits have effect." << TestLog::EndMessage;
+			m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Unused mask bits modified mask");
+			return STOP;
+		}
+	}
+
+	log << TestLog::Message << "Success: Coverage mask high bits appear to have no effect." << TestLog::EndMessage;
+	m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
+
+	return STOP;
+}
+
+} // anonymous
+
+MultisampleTests::MultisampleTests (Context& context)
+	: TestCaseGroup(context, "multisample", "Multisample tests")
+{
+}
+
+MultisampleTests::~MultisampleTests (void)
+{
+}
+
+void MultisampleTests::init (void)
+{
+	tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "default_framebuffer", "Test with default framebuffer");
+
+	addChild(group);
+
+	// .default_framebuffer
+	{
+		// sample positions
+		group->addChild(new SamplePosQueryCase			(m_context, "sample_position", "test SAMPLE_POSITION"));
+
+		// sample mask
+		group->addChild(new MaskInvertCase				(m_context, "sample_mask_sum_of_inverses",	"Test that mask and its negation's sum equal the fully set mask"));
+		group->addChild(new MaskProportionalityCase		(m_context, "proportionality_sample_mask",	"Test the proportionality property of GL_SAMPLE_MASK"));
+
+		group->addChild(new MaskConstancyCase			(m_context, "constancy_sample_mask",
+																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_MASK",
+																	MaskConstancyCase::CASEBIT_SAMPLE_MASK));
+		group->addChild(new MaskConstancyCase			(m_context, "constancy_alpha_to_coverage_sample_mask",
+																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE and GL_SAMPLE_MASK",
+																	MaskConstancyCase::CASEBIT_ALPHA_TO_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK));
+		group->addChild(new MaskConstancyCase			(m_context, "constancy_sample_coverage_sample_mask",
+																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_COVERAGE and GL_SAMPLE_MASK",
+																	MaskConstancyCase::CASEBIT_SAMPLE_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK));
+		group->addChild(new MaskConstancyCase			(m_context, "constancy_alpha_to_coverage_sample_coverage_sample_mask",
+																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE, GL_SAMPLE_COVERAGE and GL_SAMPLE_MASK",
+																	MaskConstancyCase::CASEBIT_ALPHA_TO_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK));
+		group->addChild(new SampleMaskHighBitsCase		(m_context, "sample_mask_non_effective_bits",
+																	"Test that values of unused bits of a sample mask (bit index > sample count) have no effect"));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fMultisampleTests.hpp b/modules/gles31/functional/es31fMultisampleTests.hpp
new file mode 100644
index 0000000..c0dddfc
--- /dev/null
+++ b/modules/gles31/functional/es31fMultisampleTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FMULTISAMPLETESTS_HPP
+#define _ES31FMULTISAMPLETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class MultisampleTests : public TestCaseGroup
+{
+public:
+						MultisampleTests	(Context& context);
+						~MultisampleTests	(void);
+
+	void				init				(void);
+
+private:
+						MultisampleTests	(const MultisampleTests& other);
+	MultisampleTests&	operator=			(const MultisampleTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FMULTISAMPLETESTS_HPP
diff --git a/modules/gles31/functional/es31fNegativeBufferApiTests.cpp b/modules/gles31/functional/es31fNegativeBufferApiTests.cpp
new file mode 100644
index 0000000..6e723c8
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeBufferApiTests.cpp
@@ -0,0 +1,1467 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Buffer API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fNegativeBufferApiTests.hpp"
+
+#include "gluCallLogWrapper.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+using tcu::TestLog;
+using glu::CallLogWrapper;
+using namespace glw;
+
+// Buffers
+void bind_buffer (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not one of the allowable values.");
+	ctx.glBindBuffer(-1, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void delete_buffers (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glDeleteBuffers(-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void gen_buffers (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glGenBuffers(-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void buffer_data (NegativeTestContext& ctx)
+{
+	GLuint buffer = 0x1234;
+	ctx.glGenBuffers(1, &buffer);
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, buffer);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER.");
+	ctx.glBufferData(-1, 0, NULL, GL_STREAM_DRAW);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if usage is not GL_STREAM_DRAW, GL_STATIC_DRAW, or GL_DYNAMIC_DRAW.");
+	ctx.glBufferData(GL_ARRAY_BUFFER, 0, NULL, -1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if size is negative.");
+	ctx.glBufferData(GL_ARRAY_BUFFER, -1, NULL, GL_STREAM_DRAW);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, 0);
+	ctx.glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers(1, &buffer);
+}
+
+void buffer_sub_data (NegativeTestContext& ctx)
+{
+	GLuint buffer = 0x1234;
+	ctx.glGenBuffers(1, &buffer);
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, buffer);
+	ctx.glBufferData(GL_ARRAY_BUFFER, 10, 0, GL_STREAM_DRAW);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER.");
+	ctx.glBufferSubData(-1, 1, 1, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, 0);
+	ctx.glBufferSubData(GL_ARRAY_BUFFER, 1, 1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the buffer object being updated is mapped.");
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, buffer);
+	ctx.glMapBufferRange(GL_ARRAY_BUFFER, 0, 5, GL_MAP_READ_BIT);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glBufferSubData(GL_ARRAY_BUFFER, 1, 1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers(1, &buffer);
+}
+
+void buffer_sub_data_size_offset (NegativeTestContext& ctx)
+{
+	GLuint buffer = 0x1234;
+	ctx.glGenBuffers(1, &buffer);
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, buffer);
+	ctx.glBufferData(GL_ARRAY_BUFFER, 10, 0, GL_STREAM_DRAW);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if offset or size is negative, or if together they define a region of memory that extends beyond the buffer object's allocated data store.");
+	ctx.glBufferSubData(GL_ARRAY_BUFFER, -1, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glBufferSubData(GL_ARRAY_BUFFER, -1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glBufferSubData(GL_ARRAY_BUFFER, 1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glBufferSubData(GL_ARRAY_BUFFER, 15, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glBufferSubData(GL_ARRAY_BUFFER, 1, 15, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glBufferSubData(GL_ARRAY_BUFFER, 8, 8, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers(1, &buffer);
+}
+
+void clear (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if any bit other than the three defined bits is set in mask.");
+	ctx.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glClear(0x00000200);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glClear(0x00001000);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glClear(0x00000010);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void read_pixels (NegativeTestContext& ctx)
+{
+	std::vector<GLubyte> ubyteData(4);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the combination of format and type is unsupported.");
+	ctx.glReadPixels(0, 0, 1, 1, GL_LUMINANCE_ALPHA, GL_UNSIGNED_SHORT_4_4_4_4, &ubyteData[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if either width or height is negative.");
+	ctx.glReadPixels(0, 0, -1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glReadPixels(0, 0, 1, -1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glReadPixels(0, 0, -1, -1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	GLuint fbo = 0x1234;
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteFramebuffers(1, &fbo);
+}
+
+void read_pixels_format_mismatch (NegativeTestContext& ctx)
+{
+	std::vector<GLubyte> ubyteData(4);
+	std::vector<GLushort> ushortData(4);
+
+	ctx.beginSection("Unsupported combinations of format and type will generate an INVALID_OPERATION error.");
+	ctx.glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_SHORT_5_6_5, &ushortData[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glReadPixels(0, 0, 1, 1, GL_ALPHA, GL_UNSIGNED_SHORT_5_6_5, &ushortData[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glReadPixels(0, 0, 1, 1, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, &ushortData[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glReadPixels(0, 0, 1, 1, GL_ALPHA, GL_UNSIGNED_SHORT_4_4_4_4, &ushortData[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glReadPixels(0, 0, 1, 1, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, &ushortData[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glReadPixels(0, 0, 1, 1, GL_ALPHA, GL_UNSIGNED_SHORT_5_5_5_1, &ushortData[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_RGBA/GL_UNSIGNED_BYTE is always accepted and the other acceptable pair can be discovered by querying GL_IMPLEMENTATION_COLOR_READ_FORMAT and GL_IMPLEMENTATION_COLOR_READ_TYPE.");
+	ctx.glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+	ctx.expectError(GL_NO_ERROR);
+	GLint readFormat = 0x1234;
+	GLint readType = 0x1234;
+	ctx.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
+	ctx.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
+	ctx.glReadPixels(0, 0, 1, 1, readFormat, readType, &ubyteData[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+}
+
+void read_pixels_fbo_format_mismatch (NegativeTestContext& ctx)
+{
+	std::vector<GLubyte>	ubyteData(4);
+	std::vector<float>		floatData(4);
+	deUint32				fbo = 0x1234;
+	deUint32				texture = 0x1234;
+
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glGenFramebuffers		(1, &fbo);
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+	ctx.glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if currently bound framebuffer format is incompatible with format and type.");
+
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.glReadPixels			(0, 0, 1, 1, GL_RGBA, GL_FLOAT, &floatData[0]);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32I, 32, 32, 0, GL_RGBA_INTEGER, GL_INT, NULL);
+	ctx.glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.glReadPixels			(0, 0, 1, 1, GL_RGBA, GL_FLOAT, &floatData[0]);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32UI, 32, 32, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT, NULL);
+	ctx.glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.glReadPixels			(0, 0, 1, 1, GL_RGBA, GL_FLOAT, &floatData[0]);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if GL_READ_FRAMEBUFFER_BINDING is non-zero, the read framebuffer is complete, and the value of GL_SAMPLE_BUFFERS for the read framebuffer is greater than zero.");
+
+	int			binding			= -1;
+	int			sampleBuffers = 0x1234;
+	deUint32	rbo = 0x1234;
+
+	ctx.glGenRenderbuffers(1, &rbo);
+	ctx.glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+	ctx.glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 32, 32);
+	ctx.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+
+	ctx.glGetIntegerv			(GL_READ_FRAMEBUFFER_BINDING, &binding);
+	ctx.getLog() << TestLog::Message << "// GL_READ_FRAMEBUFFER_BINDING: " << binding << TestLog::EndMessage;
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glGetIntegerv			(GL_SAMPLE_BUFFERS, &sampleBuffers);
+	ctx.getLog() << TestLog::Message << "// GL_SAMPLE_BUFFERS: " << sampleBuffers << TestLog::EndMessage;
+	ctx.expectError				(GL_NO_ERROR);
+
+	if (binding == 0 || sampleBuffers <= 0)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: expected GL_READ_FRAMEBUFFER_BINDING to be non-zero and GL_SAMPLE_BUFFERS to be greater than zero" << TestLog::EndMessage;
+		ctx.fail("Got invalid value");
+	}
+	else
+	{
+		ctx.glReadPixels	(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &ubyteData[0]);
+		ctx.expectError		(GL_INVALID_OPERATION);
+	}
+
+	ctx.endSection();
+
+	ctx.glBindRenderbuffer		(GL_RENDERBUFFER, 0);
+	ctx.glBindTexture			(GL_TEXTURE_2D, 0);
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers	(1, &fbo);
+	ctx.glDeleteTextures		(1, &texture);
+	ctx.glDeleteRenderbuffers	(1, &rbo);
+}
+
+void bind_buffer_range (NegativeTestContext& ctx)
+{
+	deUint32 bufU = 0x1234;
+	ctx.glGenBuffers(1, &bufU);
+	ctx.glBindBuffer(GL_UNIFORM_BUFFER, bufU);
+	ctx.glBufferData(GL_UNIFORM_BUFFER, 16, NULL, GL_STREAM_DRAW);
+
+	deUint32 bufTF = 0x1234;
+	ctx.glGenBuffers(1, &bufTF);
+	ctx.glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, bufTF);
+	ctx.glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_STREAM_DRAW);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_TRANSFORM_FEEDBACK_BUFFER or GL_UNIFORM_BUFFER.");
+	ctx.glBindBufferRange(GL_ARRAY_BUFFER, 0, bufU, 0, 4);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if target is GL_TRANSFORM_FEEDBACK_BUFFER and index is greater than or equal to GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.");
+	int maxTFSize = 0x1234;
+	ctx.glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxTFSize);
+	ctx.glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, maxTFSize, bufTF, 0, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if target is GL_UNIFORM_BUFFER and index is greater than or equal to GL_MAX_UNIFORM_BUFFER_BINDINGS.");
+	int maxUSize = 0x1234;
+	ctx.glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUSize);
+	ctx.glBindBufferRange(GL_UNIFORM_BUFFER, maxUSize, bufU, 0, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if size is less than or equal to zero.");
+	ctx.glBindBufferRange(GL_UNIFORM_BUFFER, 0, bufU, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glBindBufferRange(GL_UNIFORM_BUFFER, 0, bufU, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if target is GL_TRANSFORM_FEEDBACK_BUFFER and size or offset are not multiples of 4.");
+	ctx.glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufTF, 4, 5);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufTF, 5, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufTF, 5, 7);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if target is GL_UNIFORM_BUFFER and offset is not a multiple of GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT.");
+	int alignment = 0x1234;
+	ctx.glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment);
+	ctx.glBindBufferRange(GL_UNIFORM_BUFFER, 0, bufU, alignment+1, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers(1, &bufU);
+	ctx.glDeleteBuffers(1, &bufTF);
+}
+
+void bind_buffer_base (NegativeTestContext& ctx)
+{
+	deUint32 bufU = 0x1234;
+	ctx.glGenBuffers(1, &bufU);
+	ctx.glBindBuffer(GL_UNIFORM_BUFFER, bufU);
+	ctx.glBufferData(GL_UNIFORM_BUFFER, 16, NULL, GL_STREAM_DRAW);
+
+	deUint32 bufTF = 0x1234;
+	ctx.glGenBuffers(1, &bufTF);
+	ctx.glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, bufTF);
+	ctx.glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_STREAM_DRAW);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_TRANSFORM_FEEDBACK_BUFFER or GL_UNIFORM_BUFFER.");
+	ctx.glBindBufferBase(-1, 0, bufU);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBindBufferBase(GL_ARRAY_BUFFER, 0, bufU);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if target is GL_UNIFORM_BUFFER and index is greater than or equal to GL_MAX_UNIFORM_BUFFER_BINDINGS.");
+	int maxUSize = 0x1234;
+	ctx.glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUSize);
+	ctx.glBindBufferBase(GL_UNIFORM_BUFFER, maxUSize, bufU);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if target is GL_TRANSFORM_FEEDBACK_BUFFER andindex is greater than or equal to GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.");
+	int maxTFSize = 0x1234;
+	ctx.glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxTFSize);
+	ctx.glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, maxTFSize, bufTF);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers(1, &bufU);
+	ctx.glDeleteBuffers(1, &bufTF);
+}
+
+void clear_bufferiv (NegativeTestContext& ctx)
+{
+	std::vector<int>		data(32*32);
+	deUint32				fbo = 0x1234;
+	deUint32				texture = 0x1234;
+
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32I, 32, 32, 0, GL_RGBA_INTEGER, GL_INT, NULL);
+	ctx.glGenFramebuffers		(1, &fbo);
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+	ctx.glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if buffer is not an accepted value.");
+	ctx.glClearBufferiv(-1, 0, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glClearBufferiv(GL_FRAMEBUFFER, 0, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if buffer is GL_COLOR, GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT, or GL_FRONT_AND_BACK and drawBuffer is greater than or equal to GL_MAX_DRAW_BUFFERS.");
+	int maxDrawBuffers = 0x1234;
+	ctx.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
+	ctx.glClearBufferiv(GL_COLOR, maxDrawBuffers, &data[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if buffer is GL_DEPTH or GL_DEPTH_STENCIL.");
+	ctx.glClearBufferiv(GL_DEPTH, 1, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glClearBufferiv(GL_DEPTH_STENCIL, 1, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if buffer is GL_STENCIL and drawBuffer is not zero.");
+	ctx.glClearBufferiv(GL_STENCIL, 1, &data[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void clear_bufferuiv (NegativeTestContext& ctx)
+{
+	std::vector<deUint32>	data(32*32);
+	deUint32				fbo = 0x1234;
+	deUint32				texture = 0x1234;
+
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32UI, 32, 32, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT, NULL);
+	ctx.glGenFramebuffers		(1, &fbo);
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+	ctx.glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if buffer is not an accepted value.");
+	ctx.glClearBufferuiv(-1, 0, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glClearBufferuiv(GL_FRAMEBUFFER, 0, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if buffer is GL_COLOR, GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT, or GL_FRONT_AND_BACK and drawBuffer is greater than or equal to GL_MAX_DRAW_BUFFERS.");
+	int maxDrawBuffers = 0x1234;
+	ctx.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
+	ctx.glClearBufferuiv(GL_COLOR, maxDrawBuffers, &data[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if buffer is GL_DEPTH, GL_STENCIL or GL_DEPTH_STENCIL.");
+	ctx.glClearBufferuiv(GL_DEPTH, 1, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glClearBufferuiv(GL_STENCIL, 1, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glClearBufferuiv(GL_DEPTH_STENCIL, 1, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void clear_bufferfv (NegativeTestContext& ctx)
+{
+	std::vector<float>		data(32*32);
+	deUint32				fbo = 0x1234;
+	deUint32				texture = 0x1234;
+
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32F, 32, 32, 0, GL_RGBA, GL_FLOAT, NULL);
+	ctx.glGenFramebuffers		(1, &fbo);
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+	ctx.glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if buffer is not an accepted value.");
+	ctx.glClearBufferfv(-1, 0, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glClearBufferfv(GL_FRAMEBUFFER, 0, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if buffer is GL_COLOR, GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT, or GL_FRONT_AND_BACK and drawBuffer is greater than or equal to GL_MAX_DRAW_BUFFERS.");
+	int maxDrawBuffers = 0x1234;
+	ctx.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
+	ctx.glClearBufferfv(GL_COLOR, maxDrawBuffers, &data[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if buffer is GL_STENCIL or GL_DEPTH_STENCIL.");
+	ctx.glClearBufferfv(GL_STENCIL, 1, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glClearBufferfv(GL_DEPTH_STENCIL, 1, &data[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if buffer is GL_DEPTH and drawBuffer is not zero.");
+	ctx.glClearBufferfv(GL_DEPTH, 1, &data[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void clear_bufferfi (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if buffer is not an accepted value.");
+	ctx.glClearBufferfi(-1, 0, 1.0f, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glClearBufferfi(GL_FRAMEBUFFER, 0, 1.0f, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if buffer is not GL_DEPTH_STENCIL.");
+	ctx.glClearBufferfi(GL_DEPTH, 0, 1.0f, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glClearBufferfi(GL_STENCIL, 0, 1.0f, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glClearBufferfi(GL_COLOR, 0, 1.0f, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if buffer is GL_DEPTH_STENCIL and drawBuffer is not zero.");
+	ctx.glClearBufferfi(GL_DEPTH_STENCIL, 1, 1.0f, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void copy_buffer_sub_data (NegativeTestContext& ctx)
+{
+	deUint32				buf[2];
+	std::vector<float>		data(32*32);
+
+	ctx.glGenBuffers			(2, buf);
+	ctx.glBindBuffer			(GL_COPY_READ_BUFFER, buf[0]);
+	ctx.glBufferData			(GL_COPY_READ_BUFFER, 32, &data[0], GL_DYNAMIC_COPY);
+	ctx.glBindBuffer			(GL_COPY_WRITE_BUFFER, buf[1]);
+	ctx.glBufferData			(GL_COPY_WRITE_BUFFER, 32, &data[0], GL_DYNAMIC_COPY);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if any of readoffset, writeoffset or size is negative.");
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, -4);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, -1, 0, 4);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, -1, 4);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if readoffset + size exceeds the size of the buffer object bound to readtarget.");
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 36);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 24, 0, 16);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 36, 0, 4);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if writeoffset + size exceeds the size of the buffer object bound to writetarget.");
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 36);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 24, 16);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 36, 4);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if the same buffer object is bound to both readtarget and writetarget and the ranges [readoffset, readoffset + size) and [writeoffset, writeoffset + size) overlap.");
+	ctx.glBindBuffer			(GL_COPY_WRITE_BUFFER, buf[0]);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 16, 4);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 4);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 16, 18);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glBindBuffer			(GL_COPY_WRITE_BUFFER, buf[1]);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if zero is bound to readtarget or writetarget.");
+	ctx.glBindBuffer			(GL_COPY_READ_BUFFER, 0);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 16);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glBindBuffer			(GL_COPY_READ_BUFFER, buf[0]);
+	ctx.glBindBuffer			(GL_COPY_WRITE_BUFFER, 0);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 16);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glBindBuffer			(GL_COPY_WRITE_BUFFER, buf[1]);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the buffer object bound to either readtarget or writetarget is mapped.");
+	ctx.glMapBufferRange		(GL_COPY_READ_BUFFER, 0, 4, GL_MAP_READ_BIT);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 16);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer			(GL_COPY_READ_BUFFER);
+
+	ctx.glMapBufferRange		(GL_COPY_WRITE_BUFFER, 0, 4, GL_MAP_READ_BIT);
+	ctx.glCopyBufferSubData		(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 16);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer			(GL_COPY_WRITE_BUFFER);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers(2, buf);
+}
+
+void draw_buffers (NegativeTestContext& ctx)
+{
+	deUint32				fbo = 0x1234;
+	deUint32				texture = 0x1234;
+	int						maxDrawBuffers = 0x1234;
+	ctx.glGetIntegerv		(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
+	std::vector<deUint32>	values(maxDrawBuffers+1);
+	values[0]				= GL_NONE;
+	values[1]				= GL_BACK;
+	values[2]				= GL_COLOR_ATTACHMENT0;
+	values[3]				= GL_DEPTH_ATTACHMENT;
+	std::vector<GLfloat>	data(32*32);
+
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glGenFramebuffers		(1, &fbo);
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+	ctx.glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if one of the values in bufs is not an accepted value.");
+	ctx.glDrawBuffers			(2, &values[2]);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the GL is bound to the default framebuffer and n is not 1.");
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+	ctx.glDrawBuffers			(2, &values[0]);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the GL is bound to the default framebuffer and the value in bufs is one of the GL_COLOR_ATTACHMENTn tokens.");
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+	ctx.glDrawBuffers			(1, &values[2]);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the GL is bound to a framebuffer object and the ith buffer listed in bufs is anything other than GL_NONE or GL_COLOR_ATTACHMENTSi.");
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+	ctx.glDrawBuffers			(1, &values[1]);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is less than 0 or greater than GL_MAX_DRAW_BUFFERS.");
+	ctx.glDrawBuffers			(-1, &values[1]);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glDrawBuffers			(maxDrawBuffers+1, &values[0]);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+	ctx.glDeleteFramebuffers(1, &fbo);
+}
+
+void flush_mapped_buffer_range (NegativeTestContext& ctx)
+{
+	deUint32				buf = 0x1234;
+	std::vector<GLfloat>	data(32);
+
+	ctx.glGenBuffers			(1, &buf);
+	ctx.glBindBuffer			(GL_ARRAY_BUFFER, buf);
+	ctx.glBufferData			(GL_ARRAY_BUFFER, 32, &data[0], GL_STATIC_READ);
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if offset or length is negative, or if offset + length exceeds the size of the mapping.");
+	ctx.glFlushMappedBufferRange(GL_ARRAY_BUFFER, -1, 1);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, -1);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glFlushMappedBufferRange(GL_ARRAY_BUFFER, 12, 8);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glFlushMappedBufferRange(GL_ARRAY_BUFFER, 24, 4);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, 24);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if zero is bound to target.");
+	ctx.glBindBuffer			(GL_ARRAY_BUFFER, 0);
+	ctx.glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, 8);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the buffer bound to target is not mapped, or is mapped without the GL_MAP_FLUSH_EXPLICIT flag.");
+	ctx.glBindBuffer			(GL_ARRAY_BUFFER, buf);
+	ctx.glUnmapBuffer			(GL_ARRAY_BUFFER);
+	ctx.glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, 8);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_WRITE_BIT);
+	ctx.glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, 8);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUnmapBuffer			(GL_ARRAY_BUFFER);
+	ctx.glDeleteBuffers			(1, &buf);
+}
+
+void map_buffer_range (NegativeTestContext& ctx)
+{
+	deUint32				buf = 0x1234;
+	std::vector<GLfloat>	data(32);
+
+	ctx.glGenBuffers			(1, &buf);
+	ctx.glBindBuffer			(GL_ARRAY_BUFFER, buf);
+	ctx.glBufferData			(GL_ARRAY_BUFFER, 32, &data[0], GL_DYNAMIC_COPY);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if either of offset or length is negative.");
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, -1, 1, GL_MAP_READ_BIT);
+	ctx.expectError				(GL_INVALID_VALUE);
+
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 1, -1, GL_MAP_READ_BIT);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if offset + length is greater than the value of GL_BUFFER_SIZE.");
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 33, GL_MAP_READ_BIT);
+	ctx.expectError				(GL_INVALID_VALUE);
+
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 32, 1, GL_MAP_READ_BIT);
+	ctx.expectError				(GL_INVALID_VALUE);
+
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 16, 17, GL_MAP_READ_BIT);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if access has any bits set other than those accepted.");
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_READ_BIT | 0x1000);
+	ctx.expectError				(GL_INVALID_VALUE);
+
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_WRITE_BIT | 0x1000);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the buffer is already in a mapped state.");
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_WRITE_BIT);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 16, 8, GL_MAP_READ_BIT);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer			(GL_ARRAY_BUFFER);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if neither GL_MAP_READ_BIT or GL_MAP_WRITE_BIT is set.");
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_INVALIDATE_RANGE_BIT);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ");
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_INVALIDATE_RANGE_BIT);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if GL_MAP_READ_BIT is set and any of GL_MAP_INVALIDATE_RANGE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT, or GL_MAP_UNSYNCHRONIZED_BIT is set.");
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_READ_BIT | GL_MAP_INVALIDATE_RANGE_BIT);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_READ_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_READ_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if GL_MAP_FLUSH_EXPLICIT_BIT is set and GL_MAP_WRITE_BIT is not set.");
+	ctx.glMapBufferRange		(GL_ARRAY_BUFFER, 0, 16, GL_MAP_READ_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers			(1, &buf);
+}
+
+void read_buffer (NegativeTestContext& ctx)
+{
+	deUint32				fbo = 0x1234;
+	deUint32				texture = 0x1234;
+	int						maxColorAttachments = 0x1234;
+
+	ctx.glGetIntegerv			(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glGenFramebuffers		(1, &fbo);
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+	ctx.glFramebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not GL_BACK, GL_NONE, or GL_COLOR_ATTACHMENTi, where i is less than GL_MAX_COLOR_ATTACHMENTS.");
+	ctx.glReadBuffer			(-1);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.glReadBuffer			(GL_FRAMEBUFFER);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.glReadBuffer			(GL_COLOR_ATTACHMENT0 + maxColorAttachments);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the current framebuffer is the default framebuffer and mode is not GL_NONE or GL_BACK.");
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+	ctx.glReadBuffer			(GL_COLOR_ATTACHMENT0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the current framebuffer is a named framebuffer and mode is not GL_NONE or GL_COLOR_ATTACHMENTi.");
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+	ctx.glReadBuffer			(GL_BACK);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+	ctx.glDeleteFramebuffers(1, &fbo);
+}
+
+void unmap_buffer (NegativeTestContext& ctx)
+{
+	deUint32				buf = 0x1234;
+	std::vector<GLfloat>	data(32);
+
+	ctx.glGenBuffers			(1, &buf);
+	ctx.glBindBuffer			(GL_ARRAY_BUFFER, buf);
+	ctx.glBufferData			(GL_ARRAY_BUFFER, 32, &data[0], GL_DYNAMIC_COPY);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the buffer data store is already in an unmapped state.");
+	ctx.glUnmapBuffer			(GL_ARRAY_BUFFER);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers			(1, &buf);
+}
+// Framebuffer Objects
+
+void bind_framebuffer (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER.");
+	ctx.glBindFramebuffer(-1, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBindFramebuffer(GL_RENDERBUFFER, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void bind_renderbuffer (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+	ctx.glBindRenderbuffer(-1, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBindRenderbuffer(GL_FRAMEBUFFER, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void check_framebuffer_status (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER.");
+	ctx.glCheckFramebufferStatus(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCheckFramebufferStatus(GL_RENDERBUFFER);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void gen_framebuffers (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glGenFramebuffers(-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void gen_renderbuffers (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glGenRenderbuffers(-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void delete_framebuffers (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glDeleteFramebuffers(-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void delete_renderbuffers (NegativeTestContext& ctx)
+{;
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glDeleteRenderbuffers(-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void framebuffer_renderbuffer (NegativeTestContext& ctx)
+{
+	GLuint fbo = 0x1234;
+	GLuint rbo = 0x1234;
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glGenRenderbuffers(1, &rbo);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+	ctx.glFramebufferRenderbuffer(-1, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if renderbuffertarget is not GL_RENDERBUFFER.");
+	ctx.glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+	ctx.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, -1, rbo);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBindRenderbuffer(GL_RENDERBUFFER, 0);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if renderbuffer is neither 0 nor the name of an existing renderbuffer object.");
+	ctx.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, -1);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if zero is bound to target.");
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteRenderbuffers(1, &rbo);
+	ctx.glDeleteFramebuffers(1, &fbo);
+}
+
+void framebuffer_texture2d (NegativeTestContext& ctx)
+{
+	GLuint fbo = 0x1234;
+	GLuint tex2D = 0x1234;
+	GLuint texCube = 0x1234;
+	GLint maxTexSize = 0x1234;
+	GLint maxTexCubeSize = 0x1234;
+
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glGenTextures(1, &tex2D);
+	ctx.glBindTexture(GL_TEXTURE_2D, tex2D);
+	ctx.glGenTextures(1, &texCube);
+	ctx.glBindTexture(GL_TEXTURE_CUBE_MAP, texCube);
+	ctx.glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
+	ctx.glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &maxTexCubeSize);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+	ctx.glFramebufferTexture2D(-1, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if textarget is not an accepted texture target.");
+	ctx.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, -1, tex2D, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0 or larger than log_2 of maximum texture size.");
+	int maxSize = 0x1234;
+	ctx.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	maxSize = deLog2Floor32(maxTexSize) + 1;
+	ctx.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, maxSize);
+	ctx.expectError(GL_INVALID_VALUE);
+	maxSize = deLog2Floor32(maxTexSize) + 1;
+	ctx.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, maxSize);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if texture is neither 0 nor the name of an existing texture object.");
+	ctx.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, -1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if textarget and texture are not compatible.");
+	ctx.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, tex2D, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glDeleteTextures(1, &tex2D);
+
+	ctx.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texCube, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glDeleteTextures(1, &texCube);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if zero is bound to target.");
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteFramebuffers(1, &fbo);
+}
+
+void renderbuffer_storage (NegativeTestContext& ctx)
+{
+	deUint32					rbo = 0x1234;
+	ctx.glGenRenderbuffers		(1, &rbo);
+	ctx.glBindRenderbuffer		(GL_RENDERBUFFER, rbo);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+	ctx.glRenderbufferStorage	(-1, GL_RGBA4, 1, 1);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.glRenderbufferStorage	(GL_FRAMEBUFFER, GL_RGBA4, 1, 1);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if internalformat is not a color-renderable, depth-renderable, or stencil-renderable format.");
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, -1, 1, 1);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGB16F, 1, 1);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA8_SNORM, 1, 1);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is less than zero.");
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, -1, 1);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, 1, -1);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, -1, -1);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_RENDERBUFFER_SIZE.");
+	GLint maxSize = 0x1234;
+	ctx.glGetIntegerv			(GL_MAX_RENDERBUFFER_SIZE, &maxSize);
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, 1, maxSize+1);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, maxSize+1, 1);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_RGBA4, maxSize+1, maxSize+1);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteRenderbuffers(1, &rbo);
+}
+
+void blit_framebuffer (NegativeTestContext& ctx)
+{
+	deUint32					fbo[2];
+	deUint32					rbo[2];
+	deUint32					texture[2];
+
+	ctx.glGenFramebuffers		(2, fbo);
+	ctx.glGenTextures			(2, texture);
+	ctx.glGenRenderbuffers		(2, rbo);
+
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture[0]);
+	ctx.glBindRenderbuffer		(GL_RENDERBUFFER, rbo[0]);
+	ctx.glBindFramebuffer		(GL_READ_FRAMEBUFFER, fbo[0]);
+
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 32, 32);
+	ctx.glFramebufferTexture2D	(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
+	ctx.glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo[0]);
+	ctx.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
+
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture[1]);
+	ctx.glBindRenderbuffer		(GL_RENDERBUFFER, rbo[1]);
+	ctx.glBindFramebuffer		(GL_DRAW_FRAMEBUFFER, fbo[1]);
+
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 32, 32);
+	ctx.glFramebufferTexture2D	(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
+	ctx.glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo[1]);
+	ctx.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST.");
+	ctx.glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_LINEAR);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_LINEAR);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_LINEAR);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if mask contains GL_COLOR_BUFFER_BIT and read buffer format is incompatible with draw buffer format.");
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture[0]);
+
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32UI, 32, 32, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT, NULL);
+	ctx.glFramebufferTexture2D	(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
+	ctx.getLog() << TestLog::Message << "// Read buffer: GL_RGBA32UI, draw buffer: GL_RGBA" << TestLog::EndMessage;
+	ctx.glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32I, 32, 32, 0, GL_RGBA_INTEGER, GL_INT, NULL);
+	ctx.glFramebufferTexture2D	(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
+	ctx.getLog() << TestLog::Message << "// Read buffer: GL_RGBA32I, draw buffer: GL_RGBA" << TestLog::EndMessage;
+	ctx.glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glFramebufferTexture2D	(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture[1]);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32I, 32, 32, 0, GL_RGBA_INTEGER, GL_INT, NULL);
+	ctx.glFramebufferTexture2D	(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
+	ctx.getLog() << TestLog::Message << "// Read buffer: GL_RGBA8, draw buffer: GL_RGBA32I" << TestLog::EndMessage;
+	ctx.glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if filter is GL_LINEAR and the read buffer contains integer data.");
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture[0]);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA32UI, 32, 32, 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT, NULL);
+	ctx.glFramebufferTexture2D	(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glFramebufferTexture2D	(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
+	ctx.getLog() << TestLog::Message << "// Read buffer: GL_RGBA32I, draw buffer: GL_RGBA8" << TestLog::EndMessage;
+	ctx.glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if mask contains GL_DEPTH_BUFFER_BIT or GL_STENCIL_BUFFER_BIT and the source and destination depth and stencil formats do not match.");
+	ctx.glBindRenderbuffer		(GL_RENDERBUFFER, rbo[0]);
+	ctx.glRenderbufferStorage	(GL_RENDERBUFFER, GL_DEPTH32F_STENCIL8, 32, 32);
+	ctx.glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo[0]);
+	ctx.glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glBlitFramebuffer		(0, 0, 16, 16, 0, 0, 16, 16, GL_STENCIL_BUFFER_BIT, GL_NEAREST);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers	(2, fbo);
+	ctx.glDeleteTextures		(2, texture);
+	ctx.glDeleteRenderbuffers	(2, rbo);
+}
+
+void blit_framebuffer_multisample (NegativeTestContext& ctx)
+{
+	deUint32							fbo[2];
+	deUint32							rbo[2];
+
+	ctx.glGenFramebuffers				(2, fbo);
+	ctx.glGenRenderbuffers				(2, rbo);
+
+	ctx.glBindRenderbuffer				(GL_RENDERBUFFER, rbo[0]);
+	ctx.glBindFramebuffer				(GL_READ_FRAMEBUFFER, fbo[0]);
+	ctx.glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 32, 32);
+	ctx.glFramebufferRenderbuffer		(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo[0]);
+	ctx.glCheckFramebufferStatus		(GL_READ_FRAMEBUFFER);
+
+	ctx.glBindRenderbuffer				(GL_RENDERBUFFER, rbo[1]);
+	ctx.glBindFramebuffer				(GL_DRAW_FRAMEBUFFER, fbo[1]);
+
+	ctx.expectError						(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS for the draw buffer is greater than zero.");
+	ctx.glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 32, 32);
+	ctx.glFramebufferRenderbuffer		(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo[1]);
+	ctx.glBlitFramebuffer				(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	ctx.expectError						(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater than zero and the formats of draw and read buffers are not identical.");
+	ctx.glRenderbufferStorage			(GL_RENDERBUFFER, GL_RGBA4, 32, 32);
+	ctx.glFramebufferRenderbuffer		(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo[1]);
+	ctx.glBlitFramebuffer				(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	ctx.expectError						(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater than zero and the source and destination rectangles are not defined with the same (X0, Y0) and (X1, Y1) bounds.");
+	ctx.glRenderbufferStorage			(GL_RENDERBUFFER, GL_RGBA8, 32, 32);
+	ctx.glFramebufferRenderbuffer		(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo[1]);
+	ctx.glBlitFramebuffer				(0, 0, 16, 16, 2, 2, 18, 18, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	ctx.expectError						(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glBindFramebuffer				(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteRenderbuffers			(2, rbo);
+	ctx.glDeleteFramebuffers			(2, fbo);
+}
+
+void framebuffer_texture_layer (NegativeTestContext& ctx)
+{
+	deUint32						fbo = 0x1234;
+	deUint32						tex3D;
+	deUint32						tex2DArray;
+	deUint32						tex2D;
+
+	ctx.glGenFramebuffers			(1, &fbo);
+	ctx.glGenTextures				(1, &tex3D);
+	ctx.glGenTextures				(1, &tex2DArray);
+	ctx.glGenTextures				(1, &tex2D);
+	ctx.glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+
+	ctx.glBindTexture				(GL_TEXTURE_3D, tex3D);
+	ctx.glTexImage3D				(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glBindTexture				(GL_TEXTURE_2D_ARRAY, tex2DArray);
+	ctx.glTexImage3D				(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glBindTexture				(GL_TEXTURE_2D, tex2D);
+	ctx.glTexImage2D				(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+	ctx.glFramebufferTextureLayer	(-1, GL_COLOR_ATTACHMENT0, tex3D, 0, 1);
+	ctx.expectError					(GL_INVALID_ENUM);
+	ctx.glFramebufferTextureLayer	(GL_RENDERBUFFER, GL_COLOR_ATTACHMENT0, tex3D, 0, 1);
+	ctx.expectError					(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if attachment is not one of the accepted tokens.");
+	ctx.glFramebufferTextureLayer	(GL_FRAMEBUFFER, -1, tex3D, 0, 1);
+	ctx.expectError					(GL_INVALID_ENUM);
+	ctx.glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_BACK, tex3D, 0, 1);
+	ctx.expectError					(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if texture is non-zero and not the name of a 3D texture or 2D array texture.");
+	ctx.glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, -1, 0, 0);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 0);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if texture is not zero and layer is negative.");
+	ctx.glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3D, 0, -1);
+	ctx.expectError					(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if texture is not zero and layer is greater than GL_MAX_3D_TEXTURE_SIZE-1 for a 3D texture.");
+	int							max3DTexSize = 0x1234;
+	ctx.glGetIntegerv				(GL_MAX_3D_TEXTURE_SIZE, &max3DTexSize);
+	ctx.glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3D, 0, max3DTexSize);
+	ctx.expectError					(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if texture is not zero and layer is greater than GL_MAX_ARRAY_TEXTURE_LAYERS-1 for a 2D array texture.");
+	int							maxArrayTexLayers = 0x1234;
+	ctx.glGetIntegerv				(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxArrayTexLayers);
+	ctx.glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, maxArrayTexLayers);
+	ctx.expectError					(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if zero is bound to target.");
+	ctx.glBindFramebuffer			(GL_FRAMEBUFFER, 0);
+	ctx.glFramebufferTextureLayer	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3D, 0, 1);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &tex3D);
+	ctx.glDeleteTextures		(1, &tex2DArray);
+	ctx.glDeleteTextures		(1, &tex2D);
+	ctx.glDeleteFramebuffers	(1, &fbo);
+}
+
+void invalidate_framebuffer (NegativeTestContext& ctx)
+{
+	deUint32					fbo = 0x1234;
+	deUint32					texture = 0x1234;
+	deUint32					attachments[2];
+	int							maxColorAttachments = 0x1234;
+	ctx.glGetIntegerv				(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
+	attachments[0]				= GL_COLOR_ATTACHMENT0;
+	attachments[1]				= GL_COLOR_ATTACHMENT0 + maxColorAttachments;
+
+	ctx.glGenFramebuffers			(1, &fbo);
+	ctx.glGenTextures				(1, &texture);
+	ctx.glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+	ctx.glBindTexture				(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D				(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glFramebufferTexture2D		(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.glCheckFramebufferStatus	(GL_FRAMEBUFFER);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER, GL_READ_FRAMEBUFFER or GL_DRAW_FRAMEBUFFER.");
+	ctx.glInvalidateFramebuffer		(-1, 1, &attachments[0]);
+	ctx.expectError					(GL_INVALID_ENUM);
+	ctx.glInvalidateFramebuffer		(GL_BACK, 1, &attachments[0]);
+	ctx.expectError					(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if attachments contains GL_COLOR_ATTACHMENTm and m is greater than or equal to the value of GL_MAX_COLOR_ATTACHMENTS.");
+	ctx.glInvalidateFramebuffer		(GL_FRAMEBUFFER, 1, &attachments[1]);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+	ctx.glDeleteFramebuffers	(1, &fbo);
+}
+
+void invalidate_sub_framebuffer (NegativeTestContext& ctx)
+{
+	deUint32					fbo = 0x1234;
+	deUint32					texture = 0x1234;
+	deUint32					attachments[2];
+	int							maxColorAttachments = 0x1234;
+	ctx.glGetIntegerv				(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
+	attachments[0]				= GL_COLOR_ATTACHMENT0;
+	attachments[1]				= GL_COLOR_ATTACHMENT0 + maxColorAttachments;
+
+	ctx.glGenFramebuffers			(1, &fbo);
+	ctx.glGenTextures				(1, &texture);
+	ctx.glBindFramebuffer			(GL_FRAMEBUFFER, fbo);
+	ctx.glBindTexture				(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D				(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	ctx.glFramebufferTexture2D		(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+	ctx.glCheckFramebufferStatus	(GL_FRAMEBUFFER);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER, GL_READ_FRAMEBUFFER or GL_DRAW_FRAMEBUFFER.");
+	ctx.glInvalidateSubFramebuffer	(-1, 1, &attachments[0], 0, 0, 16, 16);
+	ctx.expectError					(GL_INVALID_ENUM);
+	ctx.glInvalidateSubFramebuffer	(GL_BACK, 1, &attachments[0], 0, 0, 16, 16);
+	ctx.expectError					(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if attachments contains GL_COLOR_ATTACHMENTm and m is greater than or equal to the value of GL_MAX_COLOR_ATTACHMENTS.");
+	ctx.glInvalidateSubFramebuffer	(GL_FRAMEBUFFER, 1, &attachments[1], 0, 0, 16, 16);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+	ctx.glDeleteFramebuffers	(1, &fbo);
+}
+
+void renderbuffer_storage_multisample (NegativeTestContext& ctx)
+{
+	deUint32							rbo = 0x1234;
+	int									maxSamplesSupportedRGBA4 = -1;
+
+	ctx.glGetInternalformativ				(GL_RENDERBUFFER, GL_RGBA4, GL_SAMPLES, 1, &maxSamplesSupportedRGBA4);
+	ctx.glGenRenderbuffers					(1, &rbo);
+	ctx.glBindRenderbuffer					(GL_RENDERBUFFER, rbo);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+	ctx.glRenderbufferStorageMultisample	(-1, 2, GL_RGBA4, 1, 1);
+	ctx.expectError							(GL_INVALID_ENUM);
+	ctx.glRenderbufferStorageMultisample	(GL_FRAMEBUFFER, 2, GL_RGBA4, 1, 1);
+	ctx.expectError							(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if samples is greater than the maximum number of samples supported for internalformat.");
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, maxSamplesSupportedRGBA4+1, GL_RGBA4, 1, 1);
+	ctx.expectError							(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if internalformat is not a color-renderable, depth-renderable, or stencil-renderable format.");
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, -1, 1, 1);
+	ctx.expectError							(GL_INVALID_ENUM);
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, GL_RGB16F, 1, 1);
+	ctx.expectError							(GL_INVALID_ENUM);
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, GL_RGBA8_SNORM, 1, 1);
+	ctx.expectError							(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if internalformat is a signed or unsigned integer format and samples is greater than 0.");
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 1, GL_RGBA8UI, 1, 1);
+	ctx.expectError							(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is less than zero.");
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, GL_RGBA4, -1, 1);
+	ctx.expectError							(GL_INVALID_VALUE);
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, GL_RGBA4, 1, -1);
+	ctx.expectError							(GL_INVALID_VALUE);
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 2, GL_RGBA4, -1, -1);
+	ctx.expectError							(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_RENDERBUFFER_SIZE.");
+	GLint maxSize = 0x1234;
+	ctx.glGetIntegerv						(GL_MAX_RENDERBUFFER_SIZE, &maxSize);
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 4, GL_RGBA4, 1, maxSize+1);
+	ctx.expectError							(GL_INVALID_VALUE);
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 4, GL_RGBA4, maxSize+1, 1);
+	ctx.expectError							(GL_INVALID_VALUE);
+	ctx.glRenderbufferStorageMultisample	(GL_RENDERBUFFER, 4, GL_RGBA4, maxSize+1, maxSize+1);
+	ctx.expectError							(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteRenderbuffers(1, &rbo);
+}
+
+std::vector<FunctionContainer> getNegativeBufferApiTestFunctions ()
+{
+	FunctionContainer funcs[] =
+	{
+		{bind_buffer,						"bind_buffer",						"Invalid glBindBuffer() usage"					  },
+		{delete_buffers,					"delete_buffers",					"Invalid glDeleteBuffers() usage"				  },
+		{gen_buffers,						"gen_buffers",						"Invalid glGenBuffers() usage"					  },
+		{buffer_data,						"buffer_data",						"Invalid glBufferData() usage"					  },
+		{buffer_sub_data,					"buffer_sub_data",					"Invalid glBufferSubData() usage"				  },
+		{buffer_sub_data_size_offset,		"buffer_sub_data_size_offset",		"Invalid glBufferSubData() usage"				  },
+		{clear,								"clear",							"Invalid glClear() usage"						  },
+		{read_pixels,						"read_pixels",						"Invalid glReadPixels() usage"					  },
+		{read_pixels_format_mismatch,		"read_pixels_format_mismatch",		"Invalid glReadPixels() usage"					  },
+		{read_pixels_fbo_format_mismatch,	"read_pixels_fbo_format_mismatch",	"Invalid glReadPixels() usage"					  },
+		{bind_buffer_range,					"bind_buffer_range",				"Invalid glBindBufferRange() usage"				  },
+		{bind_buffer_base,					"bind_buffer_base",					"Invalid glBindBufferBase() usage"				  },
+		{clear_bufferiv,					"clear_bufferiv",					"Invalid glClearBufferiv() usage"				  },
+		{clear_bufferuiv,					"clear_bufferuiv",					"Invalid glClearBufferuiv() usage"				  },
+		{clear_bufferfv,					"clear_bufferfv",					"Invalid glClearBufferfv() usage"				  },
+		{clear_bufferfi,					"clear_bufferfi",					"Invalid glClearBufferfi() usage"				  },
+		{copy_buffer_sub_data,				"copy_buffer_sub_data",				"Invalid glCopyBufferSubData() usage"			  },
+		{draw_buffers,						"draw_buffers",						"Invalid glDrawBuffers() usage"					  },
+		{flush_mapped_buffer_range,			"flush_mapped_buffer_range",		"Invalid glFlushMappedBufferRange() usage"		  },
+		{map_buffer_range,					"map_buffer_range",					"Invalid glMapBufferRange() usage"				  },
+		{read_buffer,						"read_buffer",						"Invalid glReadBuffer() usage"					  },
+		{unmap_buffer,						"unmap_buffer",						"Invalid glUnmapBuffer() usage"					  },
+		{bind_framebuffer,					"bind_framebuffer",					"Invalid glBindFramebuffer() usage"				  },
+		{bind_renderbuffer,					"bind_renderbuffer",				"Invalid glBindRenderbuffer() usage"			  },
+		{check_framebuffer_status,			"check_framebuffer_status",			"Invalid glCheckFramebufferStatus() usage"		  },
+		{gen_framebuffers,					"gen_framebuffers",					"Invalid glGenFramebuffers() usage"				  },
+		{gen_renderbuffers,					"gen_renderbuffers",				"Invalid glGenRenderbuffers() usage"			  },
+		{delete_framebuffers,				"delete_framebuffers",				"Invalid glDeleteFramebuffers() usage"			  },
+		{delete_renderbuffers,				"delete_renderbuffers",				"Invalid glDeleteRenderbuffers() usage"			  },
+		{framebuffer_renderbuffer,			"framebuffer_renderbuffer",			"Invalid glFramebufferRenderbuffer() usage"		  },
+		{framebuffer_texture2d,				"framebuffer_texture2d",			"Invalid glFramebufferTexture2D() usage"		  },
+		{renderbuffer_storage,				"renderbuffer_storage",				"Invalid glRenderbufferStorage() usage"			  },
+		{blit_framebuffer,					"blit_framebuffer",					"Invalid glBlitFramebuffer() usage"				  },
+		{blit_framebuffer_multisample,		"blit_framebuffer_multisample",		"Invalid glBlitFramebuffer() usage"				  },
+		{framebuffer_texture_layer,			"framebuffer_texture_layer",		"Invalid glFramebufferTextureLayer() usage"		  },
+		{invalidate_framebuffer,			"invalidate_framebuffer",			"Invalid glInvalidateFramebuffer() usage"		  },
+		{invalidate_sub_framebuffer,		"invalidate_sub_framebuffer",		"Invalid glInvalidateSubFramebuffer() usage"	  },
+		{renderbuffer_storage_multisample,	"renderbuffer_storage_multisample",	"Invalid glRenderbufferStorageMultisample() usage"},
+	};
+
+	return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
+}
+
+} // NegativeTestShared
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fNegativeBufferApiTests.hpp b/modules/gles31/functional/es31fNegativeBufferApiTests.hpp
new file mode 100644
index 0000000..142f245
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeBufferApiTests.hpp
@@ -0,0 +1,45 @@
+#ifndef _ES31FNEGATIVEBUFFERAPITESTS_HPP
+#define _ES31FNEGATIVEBUFFERAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Buffer API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "es31fNegativeTestShared.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+std::vector<FunctionContainer> getNegativeBufferApiTestFunctions ();
+
+} // NegativeTestShared
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES31FNEGATIVEBUFFERAPITESTS_HPP
diff --git a/modules/gles31/functional/es31fNegativeFragmentApiTests.cpp b/modules/gles31/functional/es31fNegativeFragmentApiTests.cpp
new file mode 100644
index 0000000..263ddf9
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeFragmentApiTests.cpp
@@ -0,0 +1,414 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Fragment Pipe API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fNegativeFragmentApiTests.hpp"
+
+#include "gluCallLogWrapper.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+using tcu::TestLog;
+using glu::CallLogWrapper;
+using namespace glw;
+
+using tcu::TestLog;
+
+void scissor (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if either width or height is negative.");
+	ctx.glScissor(0, 0, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glScissor(0, 0, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glScissor(0, 0, -1, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void depth_func (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if func is not an accepted value.");
+	ctx.glDepthFunc(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void viewport (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if either width or height is negative.");
+	ctx.glViewport(0, 0, -1, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glViewport(0, 0, 1, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glViewport(0, 0, -1, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+// Stencil functions
+void stencil_func (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if func is not one of the eight accepted values.");
+	ctx.glStencilFunc(-1, 0, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void stencil_func_separate (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if face is not GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.");
+	ctx.glStencilFuncSeparate(-1, GL_NEVER, 0, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if func is not one of the eight accepted values.");
+	ctx.glStencilFuncSeparate(GL_FRONT, -1, 0, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void stencil_op (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if sfail, dpfail, or dppass is any value other than the defined symbolic constant values.");
+	ctx.glStencilOp(-1, GL_ZERO, GL_REPLACE);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glStencilOp(GL_KEEP, -1, GL_REPLACE);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glStencilOp(GL_KEEP, GL_ZERO, -1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void stencil_op_separate (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if face is any value other than GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.");
+	ctx.glStencilOpSeparate(-1, GL_KEEP, GL_ZERO, GL_REPLACE);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if sfail, dpfail, or dppass is any value other than the eight defined symbolic constant values.");
+	ctx.glStencilOpSeparate(GL_FRONT, -1, GL_ZERO, GL_REPLACE);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glStencilOpSeparate(GL_FRONT, GL_KEEP, -1, GL_REPLACE);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_ZERO, -1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void stencil_mask_separate (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if face is not GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.");
+	ctx.glStencilMaskSeparate(-1, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+// Blend functions
+void blend_equation (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MAX or GL_MIN.");
+	ctx.glBlendEquation(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void blend_equation_separate (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if modeRGB is not GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MAX or GL_MIN.");
+	ctx.glBlendEquationSeparate(-1, GL_FUNC_ADD);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+	ctx.beginSection("GL_INVALID_ENUM is generated if modeAlpha is not GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MAX or GL_MIN.");
+	ctx.glBlendEquationSeparate(GL_FUNC_ADD, -1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void blend_func (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if either sfactor or dfactor is not an accepted value.");
+	ctx.glBlendFunc(-1, GL_ONE);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBlendFunc(GL_ONE, -1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void blend_func_separate (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if srcRGB, dstRGB, srcAlpha, or dstAlpha is not an accepted value.");
+	ctx.glBlendFuncSeparate(-1, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBlendFuncSeparate(GL_ZERO, -1, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBlendFuncSeparate(GL_ZERO, GL_ONE, -1, GL_ONE_MINUS_SRC_COLOR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBlendFuncSeparate(GL_ZERO, GL_ONE, GL_SRC_COLOR, -1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+// Rasterization API functions
+void cull_face (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glCullFace(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void front_face (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glFrontFace(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void line_width (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if width is less than or equal to 0.");
+	ctx.glLineWidth(0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glLineWidth(-1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+// Asynchronous queries
+void gen_queries (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	GLuint ids = 0;
+	ctx.glGenQueries	(-1, &ids);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void begin_query (NegativeTestContext& ctx)
+{
+	GLuint ids[3];
+	ctx.glGenQueries	(3, ids);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+	ctx.glBeginQuery	(-1, ids[0]);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glBeginQuery is executed while a query object of the same target is already active.");
+	ctx.glBeginQuery	(GL_ANY_SAMPLES_PASSED, ids[0]);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.glBeginQuery	(GL_ANY_SAMPLES_PASSED, ids[1]);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	// \note GL_ANY_SAMPLES_PASSED and GL_ANY_SAMPLES_PASSED_CONSERVATIVE alias to the same target for the purposes of this error.
+	ctx.glBeginQuery	(GL_ANY_SAMPLES_PASSED_CONSERVATIVE, ids[1]);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.glBeginQuery	(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, ids[1]);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.glBeginQuery	(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, ids[2]);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.glEndQuery		(GL_ANY_SAMPLES_PASSED);
+	ctx.glEndQuery		(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if id is 0.");
+	ctx.glBeginQuery	(GL_ANY_SAMPLES_PASSED, 0);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if id not a name returned from a previous call to ctx.glGenQueries, or if such a name has since been deleted with ctx.glDeleteQueries.");
+	ctx.glBeginQuery	(GL_ANY_SAMPLES_PASSED, -1);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.glDeleteQueries	(1, &ids[2]);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.glBeginQuery	(GL_ANY_SAMPLES_PASSED, ids[2]);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if id is the name of an already active query object.");
+	ctx.glBeginQuery	(GL_ANY_SAMPLES_PASSED, ids[0]);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.glBeginQuery	(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, ids[0]);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if id refers to an existing query object whose type does not does not match target.");
+	ctx.glEndQuery		(GL_ANY_SAMPLES_PASSED);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.glBeginQuery	(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, ids[0]);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteQueries	(2, &ids[0]);
+	ctx.expectError		(GL_NO_ERROR);
+}
+
+void end_query (NegativeTestContext& ctx)
+{
+	GLuint id = 0;
+	ctx.glGenQueries	(1, &id);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+	ctx.glEndQuery		(-1);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glEndQuery is executed when a query object of the same target is not active.");
+	ctx.glEndQuery		(GL_ANY_SAMPLES_PASSED);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.glBeginQuery	(GL_ANY_SAMPLES_PASSED, id);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.glEndQuery		(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.glEndQuery		(GL_ANY_SAMPLES_PASSED);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glDeleteQueries	(1, &id);
+	ctx.expectError		(GL_NO_ERROR);
+}
+
+void delete_queries (NegativeTestContext& ctx)
+{
+	GLuint id = 0;
+	ctx.glGenQueries	(1, &id);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glDeleteQueries	(-1, &id);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteQueries	(1, &id);
+}
+
+// Sync objects
+void fence_sync (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if condition is not GL_SYNC_GPU_COMMANDS_COMPLETE.");
+	ctx.glFenceSync(-1, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if flags is not zero.");
+	ctx.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0x0010);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void wait_sync (NegativeTestContext& ctx)
+{
+	GLsync sync = ctx.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if sync is not the name of a sync object.");
+	ctx.glWaitSync(0, 0, GL_TIMEOUT_IGNORED);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if flags is not zero.");
+	ctx.glWaitSync(sync, 0x0010, GL_TIMEOUT_IGNORED);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if timeout is not GL_TIMEOUT_IGNORED.");
+	ctx.glWaitSync(sync, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteSync(sync);
+}
+
+void client_wait_sync (NegativeTestContext& ctx)
+{
+	GLsync sync = ctx.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if sync is not the name of an existing sync object.");
+	ctx.glClientWaitSync (0, 0, 10000);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if flags contains any unsupported flag.");
+	ctx.glClientWaitSync(sync, 0x00000004, 10000);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteSync(sync);
+}
+
+void delete_sync (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if sync is neither zero or the name of a sync object.");
+	ctx.glDeleteSync((GLsync)1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glDeleteSync(0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+}
+
+std::vector<FunctionContainer> getNegativeFragmentApiTestFunctions ()
+{
+	FunctionContainer funcs[] =
+	{
+		{scissor,					"scissor",					"Invalid glScissor() usage"				 },
+		{depth_func,				"depth_func",				"Invalid glDepthFunc() usage"			 },
+		{viewport,					"viewport",					"Invalid glViewport() usage"			 },
+		{stencil_func,				"stencil_func",				"Invalid glStencilFunc() usage"			 },
+		{stencil_func_separate,		"stencil_func_separate",	"Invalid glStencilFuncSeparate() usage"	 },
+		{stencil_op,				"stencil_op",				"Invalid glStencilOp() usage"			 },
+		{stencil_op_separate,		"stencil_op_separate",		"Invalid glStencilOpSeparate() usage"	 },
+		{stencil_mask_separate,		"stencil_mask_separate",	"Invalid glStencilMaskSeparate() usage"	 },
+		{blend_equation,			"blend_equation",			"Invalid glBlendEquation() usage"		 },
+		{blend_equation_separate,	"blend_equation_separate",	"Invalid glBlendEquationSeparate() usage"},
+		{blend_func,				"blend_func",				"Invalid glBlendFunc() usage"			 },
+		{blend_func_separate,		"blend_func_separate",		"Invalid glBlendFuncSeparate() usage"	 },
+		{cull_face,					"cull_face",				"Invalid glCullFace() usage"			 },
+		{front_face,				"front_face",				"Invalid glFrontFace() usage"			 },
+		{line_width,				"line_width",				"Invalid glLineWidth() usage"			 },
+		{gen_queries,				"gen_queries",				"Invalid glGenQueries() usage"			 },
+		{begin_query,				"begin_query",				"Invalid glBeginQuery() usage"			 },
+		{end_query,					"end_query",				"Invalid glEndQuery() usage"			 },
+		{delete_queries,			"delete_queries",			"Invalid glDeleteQueries() usage"		 },
+		{fence_sync,				"fence_sync",				"Invalid glFenceSync() usage"			 },
+		{wait_sync,					"wait_sync",				"Invalid glWaitSync() usage"			 },
+		{client_wait_sync,			"client_wait_sync",			"Invalid glClientWaitSync() usage"		 },
+		{delete_sync,				"delete_sync",				"Invalid glDeleteSync() usage"			 },
+	};
+
+	return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
+}
+
+} // NegativeTestShared
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fNegativeFragmentApiTests.hpp b/modules/gles31/functional/es31fNegativeFragmentApiTests.hpp
new file mode 100644
index 0000000..f64334a
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeFragmentApiTests.hpp
@@ -0,0 +1,45 @@
+#ifndef _ES31FNEGATIVEFRAGMENTAPITESTS_HPP
+#define _ES31FNEGATIVEFRAGMENTAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Fragment Pipe API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "es31fNegativeTestShared.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+std::vector<FunctionContainer> getNegativeFragmentApiTestFunctions ();
+
+} // NegativeTestShared
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES31FNEGATIVEFRAGMENTAPITESTS_HPP
diff --git a/modules/gles31/functional/es31fNegativeShaderApiTests.cpp b/modules/gles31/functional/es31fNegativeShaderApiTests.cpp
new file mode 100644
index 0000000..aa03cdd
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeShaderApiTests.cpp
@@ -0,0 +1,2070 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Shader API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fNegativeShaderApiTests.hpp"
+
+#include "deUniquePtr.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluCallLogWrapper.hpp"
+
+
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+using tcu::TestLog;
+using glu::CallLogWrapper;
+using namespace glw;
+
+static const char* vertexShaderSource		=	"#version 300 es\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vec4(0.0);\n"
+												"}\n\0";
+
+static const char* fragmentShaderSource		=	"#version 300 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(0.0);\n"
+												"}\n\0";
+
+static const char* uniformTestVertSource	=	"#version 300 es\n"
+												"uniform mediump vec4 vec4_v;\n"
+												"uniform mediump mat4 mat4_v;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = mat4_v * vec4_v;\n"
+												"}\n\0";
+
+static const char* uniformTestFragSource	=	"#version 300 es\n"
+												"uniform mediump ivec4 ivec4_f;\n"
+												"uniform mediump uvec4 uvec4_f;\n"
+												"uniform sampler2D sampler_f;\n"
+												"layout(location = 0) out mediump vec4 fragColor;"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor.xy = (vec4(uvec4_f) + vec4(ivec4_f)).xy;\n"
+												"	fragColor.zw = texture(sampler_f, vec2(0.0, 0.0)).zw;\n"
+												"}\n\0";
+
+static const char* uniformBlockVertSource	=	"#version 300 es\n"
+												"layout(shared) uniform Block { lowp float var; };\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vec4(var);\n"
+												"}\n\0";
+
+
+// Shader control commands
+void create_shader (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if shaderType is not an accepted value.");
+	ctx.glCreateShader(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void shader_source (NegativeTestContext& ctx)
+{
+	// \note Shader compilation must be supported.
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+	ctx.glShaderSource(1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count is less than 0.");
+	GLuint shader = ctx.glCreateShader(GL_VERTEX_SHADER);
+	ctx.glShaderSource(shader, -1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if shader is not a shader object.");
+	GLuint program = ctx.glCreateProgram();
+	ctx.glShaderSource(program, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteProgram(program);
+	ctx.glDeleteShader(shader);
+}
+
+void compile_shader (NegativeTestContext& ctx)
+{
+	// \note Shader compilation must be supported.
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+	ctx.glCompileShader(9);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if shader is not a shader object.");
+	GLuint program = ctx.glCreateProgram();
+	ctx.glCompileShader(program);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteProgram(program);
+}
+
+void delete_shader (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+	ctx.glDeleteShader(9);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void shader_binary (NegativeTestContext& ctx)
+{
+	std::vector<deInt32> binaryFormats;
+
+	{
+		deInt32 numFormats = 0x1234;
+		ctx.glGetIntegerv(GL_NUM_SHADER_BINARY_FORMATS, &numFormats);
+
+		if (numFormats == 0)
+			ctx.getLog() << TestLog::Message << "// No supported extensions available." << TestLog::EndMessage;
+		else
+		{
+			binaryFormats.resize(numFormats);
+			ctx.glGetIntegerv(GL_SHADER_BINARY_FORMATS, &binaryFormats[0]);
+		}
+	}
+
+	deBool shaderBinarySupported = !binaryFormats.empty();
+	if (!shaderBinarySupported)
+		ctx.getLog() << TestLog::Message << "// Shader binaries not supported." << TestLog::EndMessage;
+	else
+		ctx.getLog() << TestLog::Message << "// Shader binaries supported" << TestLog::EndMessage;
+
+	GLuint shaders[2];
+	shaders[0]		= ctx.glCreateShader(GL_VERTEX_SHADER);
+	shaders[1]		= ctx.glCreateShader(GL_VERTEX_SHADER);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if binaryFormat is not an accepted value.");
+	ctx.glShaderBinary(1, &shaders[0], -1, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	if (shaderBinarySupported)
+	{
+		ctx.beginSection("GL_INVALID_VALUE is generated if the data pointed to by binary does not match the format specified by binaryFormat.");
+		const GLbyte data = 0x005F;
+		ctx.glShaderBinary(1, &shaders[0], binaryFormats[0], &data, 1);
+		ctx.expectError(GL_INVALID_VALUE);
+		ctx.endSection();
+
+		ctx.beginSection("GL_INVALID_OPERATION is generated if more than one of the handles in shaders refers to the same type of shader, or GL_INVALID_VALUE due to invalid data pointer.");
+		ctx.glShaderBinary(2, &shaders[0], binaryFormats[0], 0, 0);
+		ctx.expectError(GL_INVALID_OPERATION, GL_INVALID_VALUE);
+		ctx.endSection();
+	}
+
+	ctx.glDeleteShader(shaders[0]);
+	ctx.glDeleteShader(shaders[1]);
+}
+
+void attach_shader (NegativeTestContext& ctx)
+{
+	GLuint shader1 = ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLuint shader2 = ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLuint program = ctx.glCreateProgram();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glAttachShader(shader1, shader1);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if shader is not a shader object.");
+	ctx.glAttachShader(program, program);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glAttachShader(shader1, program);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if either program or shader is not a value generated by OpenGL.");
+	ctx.glAttachShader(program, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glAttachShader(-1, shader1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glAttachShader(-1, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if shader is already attached to program.");
+	ctx.glAttachShader(program, shader1);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glAttachShader(program, shader1);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a shader of the same type as shader is already attached to program.");
+	ctx.glAttachShader(program, shader2);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteProgram(program);
+	ctx.glDeleteShader(shader1);
+	ctx.glDeleteShader(shader2);
+}
+
+void detach_shader (NegativeTestContext& ctx)
+{
+	GLuint shader = ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLuint program = ctx.glCreateProgram();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if either program or shader is not a value generated by OpenGL.");
+	ctx.glDetachShader(-1, shader);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glDetachShader(program, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glDetachShader(-1, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glDetachShader(shader, shader);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if shader is not a shader object.");
+	ctx.glDetachShader(program, program);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glDetachShader(shader, program);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if shader is not attached to program.");
+	ctx.glDetachShader(program, shader);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteProgram(program);
+	ctx.glDeleteShader(shader);
+}
+
+void link_program (NegativeTestContext& ctx)
+{
+	GLuint shader = ctx.glCreateShader(GL_VERTEX_SHADER);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glLinkProgram(-1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glLinkProgram(shader);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteShader(shader);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is the currently active program object and transform feedback mode is active.");
+	glu::ShaderProgram			program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	deUint32					buf = 0x1234;
+	deUint32					tfID = 0x1234;
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenTransformFeedbacks		(1, &tfID);
+	ctx.glGenBuffers				(1, &buf);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteTransformFeedbacks	(1, &tfID);
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.endSection();
+}
+
+void use_program (NegativeTestContext& ctx)
+{
+	GLuint shader = ctx.glCreateShader(GL_VERTEX_SHADER);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is neither 0 nor a value generated by OpenGL.");
+	ctx.glUseProgram(-1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glUseProgram(shader);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if transform feedback mode is active and not paused.");
+	glu::ShaderProgram			program1(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	glu::ShaderProgram			program2(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	deUint32					buf = 0x1234;
+	deUint32					tfID = 0x1234;
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenTransformFeedbacks		(1, &tfID);
+	ctx.glGenBuffers				(1, &buf);
+
+	ctx.glUseProgram				(program1.getProgram());
+	ctx.glTransformFeedbackVaryings	(program1.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program1.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.glUseProgram				(program2.getProgram());
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glPauseTransformFeedback	();
+	ctx.glUseProgram				(program2.getProgram());
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteTransformFeedbacks	(1, &tfID);
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+	ctx.glDeleteShader(shader);
+}
+
+void delete_program (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glDeleteProgram(-1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void validate_program (NegativeTestContext& ctx)
+{
+	GLuint shader = ctx.glCreateShader(GL_VERTEX_SHADER);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glValidateProgram(-1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glValidateProgram(shader);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteShader(shader);
+}
+
+void get_program_binary (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram				program			(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	glu::ShaderProgram				programInvalid	(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, ""));
+	GLenum							binaryFormat	= -1;
+	GLsizei							binaryLength	= -1;
+	GLint							binaryPtr		= -1;
+	GLint							bufSize			= -1;
+	GLint							linkStatus		= -1;
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if bufSize is less than the size of GL_PROGRAM_BINARY_LENGTH for program.");
+	ctx.glGetProgramiv		(program.getProgram(), GL_PROGRAM_BINARY_LENGTH,	&bufSize);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.glGetProgramiv		(program.getProgram(), GL_LINK_STATUS,				&linkStatus);
+	ctx.getLog() << TestLog::Message << "// GL_PROGRAM_BINARY_LENGTH = " << bufSize << TestLog::EndMessage;
+	ctx.getLog() << TestLog::Message << "// GL_LINK_STATUS = " << linkStatus << TestLog::EndMessage;
+	ctx.expectError		(GL_NO_ERROR);
+
+	ctx.glGetProgramBinary	(program.getProgram(), 0, &binaryLength, &binaryFormat, &binaryPtr);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	if (bufSize > 0)
+	{
+		ctx.glGetProgramBinary	(program.getProgram(), bufSize-1, &binaryLength, &binaryFormat, &binaryPtr);
+		ctx.expectError		(GL_INVALID_OPERATION);
+	}
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if GL_LINK_STATUS for the program object is false.");
+	ctx.glGetProgramiv		(programInvalid.getProgram(), GL_PROGRAM_BINARY_LENGTH,	&bufSize);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.glGetProgramiv		(programInvalid.getProgram(), GL_LINK_STATUS,			&linkStatus);
+	ctx.getLog() << TestLog::Message << "// GL_PROGRAM_BINARY_LENGTH = " << bufSize << TestLog::EndMessage;
+	ctx.getLog() << TestLog::Message << "// GL_LINK_STATUS = " << linkStatus << TestLog::EndMessage;
+	ctx.expectError		(GL_NO_ERROR);
+
+	ctx.glGetProgramBinary	(programInvalid.getProgram(), bufSize, &binaryLength, &binaryFormat, &binaryPtr);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void program_binary (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram		srcProgram		(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	GLuint					dstProgram		= ctx.glCreateProgram();
+	GLuint					dummyShader		= ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLenum					binaryFormat	= -1;
+	GLsizei					binaryLength	= -1;
+	std::vector<deUint8>	binaryBuf;
+	GLint					bufSize			= -1;
+	GLint					linkStatus		= -1;
+
+	ctx.glGetProgramiv		(srcProgram.getProgram(), GL_PROGRAM_BINARY_LENGTH,	&bufSize);
+	ctx.glGetProgramiv		(srcProgram.getProgram(), GL_LINK_STATUS,			&linkStatus);
+	ctx.getLog() << TestLog::Message << "// GL_PROGRAM_BINARY_LENGTH = " << bufSize << TestLog::EndMessage;
+	ctx.getLog() << TestLog::Message << "// GL_LINK_STATUS = " << linkStatus << TestLog::EndMessage;
+	TCU_CHECK(bufSize > 0);
+	binaryBuf.resize(bufSize);
+	ctx.glGetProgramBinary	(srcProgram.getProgram(), bufSize, &binaryLength, &binaryFormat, &binaryBuf[0]);
+	ctx.expectError		(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not the name of an existing program object.");
+	ctx.glProgramBinary		(dummyShader, binaryFormat, &binaryBuf[0], binaryLength);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if binaryFormat is not a value recognized by the implementation.");
+	ctx.glProgramBinary		(dstProgram, -1, &binaryBuf[0], binaryLength);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteShader(dummyShader);
+	ctx.glDeleteProgram(dstProgram);
+}
+
+void program_parameteri (NegativeTestContext& ctx)
+{
+	GLuint	program	= ctx.glCreateProgram();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not the name of an existing program object.");
+	ctx.glProgramParameteri		(0, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
+	ctx.expectError			(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not GL_PROGRAM_BINARY_RETRIEVABLE_HINT.");
+	ctx.glProgramParameteri		(program, -1, GL_TRUE);
+	ctx.expectError			(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if value is not GL_FALSE or GL_TRUE.");
+	ctx.glProgramParameteri		(program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, 2);
+	ctx.expectError			(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteProgram(program);
+}
+
+void gen_samplers (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	GLuint sampler = 0;
+	ctx.glGenSamplers	(-1, &sampler);
+	ctx.expectError	(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void bind_sampler (NegativeTestContext& ctx)
+{
+	int				maxTexImageUnits = 0x1234;
+	GLuint			sampler = 0;
+	ctx.glGetIntegerv	(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTexImageUnits);
+	ctx.glGenSamplers	(1, &sampler);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if unit is greater than or equal to the value of GL_MAX_COMBIED_TEXTURE_IMAGE_UNITS.");
+	ctx.glBindSampler	(maxTexImageUnits, sampler);
+	ctx.expectError	(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if sampler is not zero or a name previously returned from a call to ctx.glGenSamplers.");
+	ctx.glBindSampler	(1, -1);
+	ctx.expectError	(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if sampler has been deleted by a call to ctx.glDeleteSamplers.");
+	ctx.glDeleteSamplers(1, &sampler);
+	ctx.glBindSampler	(1, sampler);
+	ctx.expectError	(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void delete_samplers (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glDeleteSamplers(-1, 0);
+	ctx.expectError	(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void get_sampler_parameteriv (NegativeTestContext& ctx)
+{
+	int				params = 0x1234;
+	GLuint			sampler = 0;
+	ctx.glGenSamplers	(1, &sampler);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object returned from a previous call to ctx.glGenSamplers.");
+	ctx.glGetSamplerParameteriv	(-1, GL_TEXTURE_MAG_FILTER, &params);
+	ctx.expectError			(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glGetSamplerParameteriv	(sampler, -1, &params);
+	ctx.expectError			(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteSamplers(1, &sampler);
+}
+
+void get_sampler_parameterfv (NegativeTestContext& ctx)
+{
+	float			params;
+	GLuint			sampler = 0;
+	ctx.glGenSamplers	(1, &sampler);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object returned from a previous call to ctx.glGenSamplers.");
+	ctx.glGetSamplerParameterfv	(-1, GL_TEXTURE_MAG_FILTER, &params);
+	ctx.expectError			(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glGetSamplerParameterfv	(sampler, -1, &params);
+	ctx.expectError			(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteSamplers(1, &sampler);
+}
+
+void sampler_parameteri (NegativeTestContext& ctx)
+{
+	GLuint			sampler = 0;
+	ctx.glGenSamplers	(1, &sampler);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object previously returned from a call to ctx.glGenSamplers.");
+	ctx.glSamplerParameteri		(-1, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	ctx.expectError			(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined constant value (based on the value of pname) and does not.");
+	ctx.glSamplerParameteri		(sampler, GL_TEXTURE_WRAP_S, -1);
+	ctx.expectError			(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteSamplers(1, &sampler);
+}
+
+void sampler_parameteriv (NegativeTestContext& ctx)
+{
+	int				params = 0x1234;
+	GLuint			sampler = 0;
+	ctx.glGenSamplers	(1, &sampler);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object previously returned from a call to ctx.glGenSamplers.");
+	params = GL_CLAMP_TO_EDGE;
+	ctx.glSamplerParameteriv	(-1, GL_TEXTURE_WRAP_S, &params);
+	ctx.expectError			(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined constant value (based on the value of pname) and does not.");
+	params = -1;
+	ctx.glSamplerParameteriv	(sampler, GL_TEXTURE_WRAP_S, &params);
+	ctx.expectError			(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteSamplers(1, &sampler);
+}
+
+void sampler_parameterf (NegativeTestContext& ctx)
+{
+	GLuint			sampler = 0;
+	ctx.glGenSamplers	(1, &sampler);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object previously returned from a call to ctx.glGenSamplers.");
+	ctx.glSamplerParameterf		(-1, GL_TEXTURE_MIN_LOD, -1000.0f);
+	ctx.expectError			(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined constant value (based on the value of pname) and does not.");
+	ctx.glSamplerParameterf		(sampler, GL_TEXTURE_WRAP_S, -1.0f);
+	ctx.expectError			(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteSamplers(1, &sampler);
+}
+
+void sampler_parameterfv (NegativeTestContext& ctx)
+{
+	float			params;
+	GLuint			sampler = 0;
+	ctx.glGenSamplers	(1, &sampler);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if sampler is not the name of a sampler object previously returned from a call to ctx.glGenSamplers.");
+	params = -1000.0f;
+	ctx.glSamplerParameterfv	(-1, GL_TEXTURE_WRAP_S, &params);
+	ctx.expectError			(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined constant value (based on the value of pname) and does not.");
+	params = -1.0f;
+	ctx.glSamplerParameterfv	(sampler, GL_TEXTURE_WRAP_S, &params);
+	ctx.expectError			(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteSamplers(1, &sampler);
+}
+
+// Shader data commands
+
+void get_attrib_location (NegativeTestContext& ctx)
+{
+	GLuint programEmpty		= ctx.glCreateProgram();
+	GLuint shader			= ctx.glCreateShader(GL_VERTEX_SHADER);
+
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+	ctx.glBindAttribLocation		(programEmpty, 0, "test");
+	ctx.glGetAttribLocation			(programEmpty, "test");
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a program or shader object.");
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glBindAttribLocation		(program.getProgram(), 0, "test");
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.glGetAttribLocation			(program.getProgram(), "test");
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.glGetAttribLocation			(-2, "test");
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glGetAttribLocation			(shader, "test");
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram				(0);
+	ctx.glDeleteShader				(shader);
+	ctx.glDeleteProgram				(programEmpty);
+}
+
+void get_uniform_location (NegativeTestContext& ctx)
+{
+	GLuint programEmpty = ctx.glCreateProgram();
+	GLuint shader = ctx.glCreateShader(GL_VERTEX_SHADER);
+
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+	ctx.glGetUniformLocation(programEmpty, "test");
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glGetUniformLocation(-2, "test");
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glGetAttribLocation(shader, "test");
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+	ctx.glDeleteProgram(programEmpty);
+	ctx.glDeleteShader(shader);
+}
+
+void bind_attrib_location (NegativeTestContext& ctx)
+{
+	GLuint program = ctx.glCreateProgram();
+	GLuint maxIndex = ctx.getInteger(GL_MAX_VERTEX_ATTRIBS);
+	GLuint shader = ctx.glCreateShader(GL_VERTEX_SHADER);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	ctx.glBindAttribLocation(program, maxIndex, "test");
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if name starts with the reserved prefix \"gl_\".");
+	ctx.glBindAttribLocation(program, maxIndex-1, "gl_test");
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glBindAttribLocation(-1, maxIndex-1, "test");
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glBindAttribLocation(shader, maxIndex-1, "test");
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteProgram(program);
+	ctx.glDeleteShader(shader);
+}
+
+void uniform_block_binding (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformBlockVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram	(program.getProgram());
+
+	GLint			maxUniformBufferBindings	= -1;
+	GLint			numActiveUniforms			= -1;
+	GLint			numActiveBlocks				= -1;
+	ctx.glGetIntegerv	(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings);
+	ctx.glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORMS,			&numActiveUniforms);
+	ctx.glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORM_BLOCKS,	&numActiveBlocks);
+	ctx.getLog() << TestLog::Message << "// GL_MAX_UNIFORM_BUFFER_BINDINGS = " << maxUniformBufferBindings << TestLog::EndMessage;
+	ctx.getLog() << TestLog::Message << "// GL_ACTIVE_UNIFORMS = "				<< numActiveUniforms		<< TestLog::EndMessage;
+	ctx.getLog() << TestLog::Message << "// GL_ACTIVE_UNIFORM_BLOCKS = "		<< numActiveBlocks			<< TestLog::EndMessage;
+	ctx.expectError	(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if uniformBlockIndex is not an active uniform block index of program.");
+	ctx.glUniformBlockBinding(program.getProgram(), -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glUniformBlockBinding(program.getProgram(), 5, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if uniformBlockBinding is greater than or equal to the value of GL_MAX_UNIFORM_BUFFER_BINDINGS.");
+	ctx.glUniformBlockBinding(program.getProgram(), maxUniformBufferBindings, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not the name of a program object generated by the GL.");
+	ctx.glUniformBlockBinding(-1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+// ctx.glUniform*f
+
+void uniformf_invalid_program (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_OPERATION is generated if there is no current program object.");
+	ctx.glUseProgram(0);
+	ctx.glUniform1f(-1, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2f(-1, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3f(-1, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4f(-1, 0.0f, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void uniformf_incompatible_type (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	GLint vec4_v	= ctx.glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+	GLint ivec4_f	= ctx.glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+	GLint uvec4_f	= ctx.glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+	GLint sampler_f	= ctx.glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+	ctx.expectError(GL_NO_ERROR);
+
+	if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the ctx.glUniform command.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1f(vec4_v, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2f(vec4_v, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3f(vec4_v, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4f(vec4_v, 0.0f, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glUniform{1234}f is used to load a uniform variable of type int, ivec2, ivec3, ivec4, unsigned int, uvec2, uvec3, uvec4.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform4f(ivec4_f, 0.0f, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4f(uvec4_f, 0.0f, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than ctx.glUniform1i and ctx.glUniform1iv.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1f(sampler_f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniformf_invalid_location (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1f(-2, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2f(-2, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3f(-2, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4f(-2, 0.0f, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1f(-1, 0.0f);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform2f(-1, 0.0f, 0.0f);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform3f(-1, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform4f(-1, 0.0f, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+// ctx.glUniform*fv
+
+void uniformfv_invalid_program (NegativeTestContext& ctx)
+{
+	std::vector<GLfloat> data(4);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if there is no current program object.");
+	ctx.glUseProgram(0);
+	ctx.glUniform1fv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2fv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3fv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4fv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void uniformfv_incompatible_type (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	GLint vec4_v	= ctx.glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+	GLint ivec4_f	= ctx.glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+	GLint uvec4_f	= ctx.glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+	GLint sampler_f	= ctx.glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+	ctx.expectError(GL_NO_ERROR);
+
+	if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	std::vector<GLfloat> data(4);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the ctx.glUniform command.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1fv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2fv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3fv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4fv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glUniform{1234}fv is used to load a uniform variable of type int, ivec2, ivec3, ivec4, unsigned int, uvec2, uvec3, uvec4.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform4fv(ivec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4fv(uvec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than ctx.glUniform1i and ctx.glUniform1iv.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1fv(sampler_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniformfv_invalid_location (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.expectError(GL_NO_ERROR);
+
+	std::vector<GLfloat> data(4);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1fv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2fv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3fv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4fv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1fv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform2fv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform3fv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform4fv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniformfv_invalid_count (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram	(program.getProgram());
+	GLint vec4_v			= ctx.glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+	ctx.expectError(GL_NO_ERROR);
+
+	if (vec4_v == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	std::vector<GLfloat> data(8);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1fv(vec4_v, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2fv(vec4_v, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3fv(vec4_v, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4fv(vec4_v, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+// ctx.glUniform*i
+
+void uniformi_invalid_program (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_OPERATION is generated if there is no current program object.");
+	ctx.glUseProgram(0);
+	ctx.glUniform1i(-1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2i(-1, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3i(-1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4i(-1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void uniformi_incompatible_type (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	GLint vec4_v	= ctx.glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+	GLint ivec4_f	= ctx.glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+	GLint uvec4_f	= ctx.glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+	GLint sampler_f	= ctx.glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+	ctx.expectError(GL_NO_ERROR);
+
+	if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the ctx.glUniform command.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1i(ivec4_f, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2i(ivec4_f, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3i(ivec4_f, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4i(ivec4_f, 0, 0, 0, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glUniform{1234}i is used to load a uniform variable of type unsigned int, uvec2, uvec3, uvec4, or an array of these.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1i(uvec4_f, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2i(uvec4_f, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3i(uvec4_f, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4i(uvec4_f, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glUniform{1234}i is used to load a uniform variable of type float, vec2, vec3, or vec4.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1i(vec4_v, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2i(vec4_v, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3i(vec4_v, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4i(vec4_v, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniformi_invalid_location (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1i(-2, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2i(-2, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3i(-2, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4i(-2, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1i(-1, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform2i(-1, 0, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform3i(-1, 0, 0, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform4i(-1, 0, 0, 0, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+// ctx.glUniform*iv
+
+void uniformiv_invalid_program (NegativeTestContext& ctx)
+{
+	std::vector<GLint> data(4);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if there is no current program object.");
+	ctx.glUseProgram(0);
+	ctx.glUniform1iv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2iv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3iv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4iv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void uniformiv_incompatible_type (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	GLint vec4_v	= ctx.glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+	GLint ivec4_f	= ctx.glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+	GLint uvec4_f	= ctx.glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+	GLint sampler_f	= ctx.glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+	ctx.expectError(GL_NO_ERROR);
+
+	if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	std::vector<GLint> data(4);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the ctx.glUniform command.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1iv(ivec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2iv(ivec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3iv(ivec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4iv(ivec4_f, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glUniform{1234}iv is used to load a uniform variable of type float, vec2, vec3, or vec4.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1iv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2iv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3iv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4iv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glUniform{1234}iv is used to load a uniform variable of type unsigned int, uvec2, uvec3 or uvec4.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1iv(uvec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2iv(uvec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3iv(uvec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4iv(uvec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniformiv_invalid_location (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.expectError(GL_NO_ERROR);
+
+	std::vector<GLint> data(4);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1iv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2iv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3iv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4iv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1iv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform2iv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform3iv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform4iv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniformiv_invalid_count (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram			(program.getProgram());
+	GLint ivec4_f			= ctx.glGetUniformLocation(program.getProgram(), "ivec4_f"); // ivec4
+	ctx.expectError(GL_NO_ERROR);
+
+	if (ivec4_f == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	std::vector<GLint> data(8);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1iv(ivec4_f, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2iv(ivec4_f, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3iv(ivec4_f, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4iv(ivec4_f, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+// ctx.glUniform{1234}ui
+
+void uniformui_invalid_program (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_OPERATION is generated if there is no current program object.");
+	ctx.glUseProgram(0);
+	ctx.glUniform1ui(-1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2ui(-1, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3ui(-1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4ui(-1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void uniformui_incompatible_type (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	GLint vec4_v	= ctx.glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+	GLint ivec4_f	= ctx.glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+	GLint uvec4_f	= ctx.glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+	GLint sampler_f	= ctx.glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+	ctx.expectError(GL_NO_ERROR);
+
+	if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the ctx.glUniform command.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1ui(uvec4_f, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2ui(uvec4_f, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3ui(uvec4_f, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4ui(uvec4_f, 0, 0, 0, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glUniform{1234}i is used to load a uniform variable of type int, ivec2, ivec3, ivec4, or an array of these.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1ui(ivec4_f, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2ui(ivec4_f, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3ui(ivec4_f, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4ui(ivec4_f, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glUniform{1234}i is used to load a uniform variable of type float, vec2, vec3, or vec4.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1ui(vec4_v, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2ui(vec4_v, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3ui(vec4_v, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4ui(vec4_v, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than ctx.glUniform1i and ctx.glUniform1iv.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1ui(sampler_f, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniformui_invalid_location (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1i(-2, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2i(-2, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3i(-2, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4i(-2, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1i(-1, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform2i(-1, 0, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform3i(-1, 0, 0, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform4i(-1, 0, 0, 0, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+// ctx.glUniform{1234}uiv
+
+void uniformuiv_invalid_program (NegativeTestContext& ctx)
+{
+	std::vector<GLuint> data(4);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if there is no current program object.");
+	ctx.glUseProgram(0);
+	ctx.glUniform1uiv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2uiv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3uiv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4uiv(-1, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void uniformuiv_incompatible_type (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	GLint vec4_v	= ctx.glGetUniformLocation(program.getProgram(), "vec4_v");	// vec4
+	GLint ivec4_f	= ctx.glGetUniformLocation(program.getProgram(), "ivec4_f");	// ivec4
+	GLint uvec4_f	= ctx.glGetUniformLocation(program.getProgram(), "uvec4_f");	// uvec4
+	GLint sampler_f	= ctx.glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+	ctx.expectError(GL_NO_ERROR);
+
+	if (vec4_v == -1 || ivec4_f == -1 || uvec4_f == -1 || sampler_f == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	std::vector<GLuint> data(4);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the ctx.glUniform command.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1uiv(uvec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2uiv(uvec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3uiv(uvec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4uiv(uvec4_f, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glUniform{1234}uiv is used to load a uniform variable of type float, vec2, vec3, or vec4.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1uiv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2uiv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3uiv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4uiv(vec4_v, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if ctx.glUniform{1234}uiv is used to load a uniform variable of type int, ivec2, ivec3 or ivec4.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1uiv(ivec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2uiv(ivec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3uiv(ivec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4uiv(ivec4_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than ctx.glUniform1i and ctx.glUniform1iv.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1uiv(sampler_f, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniformuiv_invalid_location (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.expectError(GL_NO_ERROR);
+
+	std::vector<GLuint> data(4);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1uiv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2uiv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3uiv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4uiv(-2, 1, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1uiv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform2uiv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform3uiv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniform4uiv(-1, 1, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniformuiv_invalid_count (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram			(program.getProgram());
+	int uvec4_f				= ctx.glGetUniformLocation(program.getProgram(), "uvec4_f"); // uvec4
+	ctx.expectError(GL_NO_ERROR);
+
+	if (uvec4_f == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	std::vector<GLuint> data(8);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniform1uiv(uvec4_f, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform2uiv(uvec4_f, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform3uiv(uvec4_f, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniform4uiv(uvec4_f, 2, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+
+// ctx.glUniformMatrix*fv
+
+void uniform_matrixfv_invalid_program (NegativeTestContext& ctx)
+{
+	std::vector<GLfloat> data(16);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if there is no current program object.");
+	ctx.glUseProgram(0);
+	ctx.glUniformMatrix2fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUniformMatrix2x3fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3x2fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix2x4fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4x2fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3x4fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4x3fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void uniform_matrixfv_incompatible_type (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram			(program.getProgram());
+	GLint mat4_v			= ctx.glGetUniformLocation(program.getProgram(), "mat4_v");	// mat4
+	GLint sampler_f			= ctx.glGetUniformLocation(program.getProgram(), "sampler_f");	// sampler2D
+	ctx.expectError(GL_NO_ERROR);
+
+	if (mat4_v == -1 || sampler_f == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	std::vector<GLfloat> data(16);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the size of the uniform variable declared in the shader does not match the size indicated by the ctx.glUniform command.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniformMatrix2fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.glUniformMatrix2x3fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3x2fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix2x4fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4x2fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3x4fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4x3fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a sampler is loaded using a command other than ctx.glUniform1i and ctx.glUniform1iv.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniformMatrix2fv(sampler_f, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3fv(sampler_f, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4fv(sampler_f, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUniformMatrix2x3fv(sampler_f, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3x2fv(sampler_f, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix2x4fv(sampler_f, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4x2fv(sampler_f, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3x4fv(sampler_f, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4x3fv(sampler_f, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniform_matrixfv_invalid_location (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.expectError(GL_NO_ERROR);
+
+	std::vector<GLfloat> data(16);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniformMatrix2fv(-2, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3fv(-2, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4fv(-2, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUniformMatrix2x3fv(-2, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3x2fv(-2, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix2x4fv(-2, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4x2fv(-2, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3x4fv(-2, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4x3fv(-2, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniformMatrix2fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniformMatrix3fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniformMatrix4fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.glUniformMatrix2x3fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniformMatrix3x2fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniformMatrix2x4fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniformMatrix4x2fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniformMatrix3x4fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glUniformMatrix4x3fv(-1, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void uniform_matrixfv_invalid_count (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+
+	ctx.glUseProgram			(program.getProgram());
+	GLint mat4_v			= ctx.glGetUniformLocation(program.getProgram(), "mat4_v"); // mat4
+	ctx.expectError(GL_NO_ERROR);
+
+	if (mat4_v == -1)
+	{
+		ctx.getLog() << TestLog::Message << "// ERROR: Failed to retrieve uniform location" << TestLog::EndMessage;
+		ctx.fail("Failed to retrieve uniform location");
+	}
+
+	std::vector<GLfloat> data(32);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if count is greater than 1 and the indicated uniform variable is not an array variable.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glUniformMatrix2fv(mat4_v, 2, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3fv(mat4_v, 2, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4fv(mat4_v, 2, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glUniformMatrix2x3fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3x2fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix2x4fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4x2fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix3x4fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glUniformMatrix4x3fv(mat4_v, 1, GL_FALSE, &data[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+// Transform feedback
+void gen_transform_feedbacks (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	GLuint id = 0;
+	ctx.glGenTransformFeedbacks(-1, &id);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void bind_transform_feedback (NegativeTestContext& ctx)
+{
+	GLuint						tfID[2];
+	glu::ShaderProgram			program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	deUint32					buf = 0x1234;
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(2, tfID);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_TRANSFORM_FEEDBACK.");
+	ctx.glBindTransformFeedback(-1, tfID[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the transform feedback operation is active on the currently bound transform feedback object, and is not paused.");
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID[0]);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID[1]);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glEndTransformFeedback		();
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram				(0);
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(2, tfID);
+	ctx.expectError				(GL_NO_ERROR);
+}
+
+void delete_transform_feedbacks (NegativeTestContext& ctx)
+{
+	GLuint id = 0;
+	ctx.glGenTransformFeedbacks(1, &id);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glDeleteTransformFeedbacks(-1, &id);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTransformFeedbacks(1, &id);
+}
+
+void begin_transform_feedback (NegativeTestContext& ctx)
+{
+	GLuint						tfID[2];
+	glu::ShaderProgram			program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	deUint32					buf = 0x1234;
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(2, tfID);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID[0]);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if primitiveMode is not one of GL_POINTS, GL_LINES, or GL_TRIANGLES.");
+	ctx.glBeginTransformFeedback	(-1);
+	ctx.expectError					(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if transform feedback is already active.");
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.expectError					(GL_NO_ERROR);
+	ctx.glBeginTransformFeedback	(GL_POINTS);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if any binding point used in transform feedback mode does not have a buffer object bound.");
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if no binding points would be used because no program object is active.");
+	ctx.glUseProgram				(0);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.glUseProgram				(program.getProgram());
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if no binding points would be used because the active program object has specified no varying variables to record.");
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 0, 0, GL_INTERLEAVED_ATTRIBS);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(2, tfID);
+	ctx.expectError					(GL_NO_ERROR);
+}
+
+void pause_transform_feedback (NegativeTestContext& ctx)
+{
+	GLuint						tfID[2];
+	glu::ShaderProgram			program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	deUint32					buf = 0x1234;
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(2, tfID);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID[0]);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the currently bound transform feedback object is not active or is paused.");
+	ctx.glPauseTransformFeedback	();
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.glPauseTransformFeedback	();
+	ctx.expectError					(GL_NO_ERROR);
+	ctx.glPauseTransformFeedback	();
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(2, tfID);
+	ctx.expectError					(GL_NO_ERROR);
+}
+
+void resume_transform_feedback (NegativeTestContext& ctx)
+{
+	GLuint						tfID[2];
+	glu::ShaderProgram			program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	deUint32					buf = 0x1234;
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(2, tfID);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID[0]);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the currently bound transform feedback object is not active or is not paused.");
+	ctx.glResumeTransformFeedback	();
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.glResumeTransformFeedback	();
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.glPauseTransformFeedback	();
+	ctx.glResumeTransformFeedback	();
+	ctx.expectError					(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(2, tfID);
+	ctx.expectError					(GL_NO_ERROR);
+}
+
+void end_transform_feedback (NegativeTestContext& ctx)
+{
+	GLuint						tfID = 0;
+	glu::ShaderProgram			program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	deUint32					buf = 0x1234;
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(1, &tfID);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if transform feedback is not active.");
+	ctx.glEndTransformFeedback		();
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.glEndTransformFeedback		();
+	ctx.expectError					(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(1, &tfID);
+	ctx.expectError					(GL_NO_ERROR);
+}
+
+void get_transform_feedback_varying (NegativeTestContext& ctx)
+{
+	GLuint					tfID = 0;
+	glu::ShaderProgram		program			(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	glu::ShaderProgram		programInvalid	(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, ""));
+	const char* tfVarying	= "gl_Position";
+	int						maxTransformFeedbackVaryings = 0;
+
+	GLsizei					length;
+	GLsizei					size;
+	GLenum					type;
+	char					name[32];
+
+	ctx.glGenTransformFeedbacks				(1, &tfID);
+
+	ctx.glTransformFeedbackVaryings			(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.expectError						(GL_NO_ERROR);
+	ctx.glLinkProgram						(program.getProgram());
+	ctx.expectError						(GL_NO_ERROR);
+
+	ctx.glBindTransformFeedback				(GL_TRANSFORM_FEEDBACK, tfID);
+	ctx.expectError						(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not the name of a program object.");
+	ctx.glGetTransformFeedbackVarying		(-1, 0, 32, &length, &size, &type, &name[0]);
+	ctx.expectError						(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater or equal to the value of GL_TRANSFORM_FEEDBACK_VARYINGS.");
+	ctx.glGetProgramiv						(program.getProgram(), GL_TRANSFORM_FEEDBACK_VARYINGS, &maxTransformFeedbackVaryings);
+	ctx.glGetTransformFeedbackVarying		(program.getProgram(), maxTransformFeedbackVaryings, 32, &length, &size, &type, &name[0]);
+	ctx.expectError						(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION or GL_INVALID_VALUE is generated program has not been linked.");
+	ctx.glGetTransformFeedbackVarying		(programInvalid.getProgram(), 0, 32, &length, &size, &type, &name[0]);
+	ctx.expectError						(GL_INVALID_OPERATION, GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTransformFeedbacks			(1, &tfID);
+	ctx.expectError						(GL_NO_ERROR);
+}
+
+void transform_feedback_varyings (NegativeTestContext& ctx)
+{
+	GLuint					tfID = 0;
+	glu::ShaderProgram		program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	const char* tfVarying	= "gl_Position";
+	GLint					maxTransformFeedbackSeparateAttribs = 0;
+
+	ctx.glGenTransformFeedbacks				(1, &tfID);
+	ctx.expectError						(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not the name of a program object.");
+	ctx.glTransformFeedbackVaryings			(0, 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.expectError						(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if bufferMode is GL_SEPARATE_ATTRIBS and count is greater than GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.");
+	ctx.glGetIntegerv						(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxTransformFeedbackSeparateAttribs);
+	ctx.glTransformFeedbackVaryings			(program.getProgram(), maxTransformFeedbackSeparateAttribs+1, &tfVarying, GL_SEPARATE_ATTRIBS);
+	ctx.expectError						(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTransformFeedbacks			(1, &tfID);
+	ctx.expectError						(GL_NO_ERROR);
+}
+
+std::vector<FunctionContainer> getNegativeShaderApiTestFunctions ()
+{
+	FunctionContainer funcs[] =
+	{
+		{create_shader,							"create_shader",						"Invalid glCreateShader() usage"			   },
+		{shader_source,							"shader_source",						"Invalid glShaderSource() usage"			   },
+		{compile_shader,						"compile_shader",						"Invalid glCompileShader() usage"			   },
+		{delete_shader,							"delete_shader",						"Invalid glDeleteShader() usage"			   },
+		{shader_binary,							"shader_binary",						"Invalid glShaderBinary() usage"			   },
+		{attach_shader,							"attach_shader",						"Invalid glAttachShader() usage"			   },
+		{detach_shader,							"detach_shader",						"Invalid glDetachShader() usage"			   },
+		{link_program,							"link_program",							"Invalid glLinkProgram() usage"				   },
+		{use_program,							"use_program",							"Invalid glUseProgram() usage"				   },
+		{delete_program,						"delete_program",						"Invalid glDeleteProgram() usage"			   },
+		{validate_program,						"validate_program",						"Invalid glValidateProgram() usage"			   },
+		{get_program_binary,					"get_program_binary",					"Invalid glGetProgramBinary() usage"		   },
+		{program_binary,						"program_binary",						"Invalid glProgramBinary() usage"			   },
+		{program_parameteri,					"program_parameteri",					"Invalid glProgramParameteri() usage"		   },
+		{gen_samplers,							"gen_samplers",							"Invalid glGenSamplers() usage"				   },
+		{bind_sampler,							"bind_sampler",							"Invalid glBindSampler() usage"				   },
+		{delete_samplers,						"delete_samplers",						"Invalid glDeleteSamplers() usage"			   },
+		{get_sampler_parameteriv,				"get_sampler_parameteriv",				"Invalid glGetSamplerParameteriv() usage"	   },
+		{get_sampler_parameterfv,				"get_sampler_parameterfv",				"Invalid glGetSamplerParameterfv() usage"	   },
+		{sampler_parameteri,					"sampler_parameteri",					"Invalid glSamplerParameteri() usage"		   },
+		{sampler_parameteriv,					"sampler_parameteriv",					"Invalid glSamplerParameteriv() usage"		   },
+		{sampler_parameterf,					"sampler_parameterf",					"Invalid glSamplerParameterf() usage"		   },
+		{sampler_parameterfv,					"sampler_parameterfv",					"Invalid glSamplerParameterfv() usage"		   },
+		{get_attrib_location,					"get_attrib_location",					"Invalid glGetAttribLocation() usage"		   },
+		{get_uniform_location,					"get_uniform_location",					"Invalid glGetUniformLocation() usage"		   },
+		{bind_attrib_location,					"bind_attrib_location",					"Invalid glBindAttribLocation() usage"		   },
+		{uniform_block_binding,					"uniform_block_binding",				"Invalid glUniformBlockBinding() usage"		   },
+		{uniformf_invalid_program,				"uniformf_invalid_program",				"Invalid glUniform{1234}f() usage"			   },
+		{uniformf_incompatible_type,			"uniformf_incompatible_type",			"Invalid glUniform{1234}f() usage"			   },
+		{uniformf_invalid_location,				"uniformf_invalid_location",			"Invalid glUniform{1234}f() usage"			   },
+		{uniformfv_invalid_program,				"uniformfv_invalid_program",			"Invalid glUniform{1234}fv() usage"			   },
+		{uniformfv_incompatible_type,			"uniformfv_incompatible_type",			"Invalid glUniform{1234}fv() usage"			   },
+		{uniformfv_invalid_location,			"uniformfv_invalid_location",			"Invalid glUniform{1234}fv() usage"			   },
+		{uniformfv_invalid_count,				"uniformfv_invalid_count",				"Invalid glUniform{1234}fv() usage"			   },
+		{uniformi_invalid_program,				"uniformi_invalid_program",				"Invalid glUniform{1234}i() usage"			   },
+		{uniformi_incompatible_type,			"uniformi_incompatible_type",			"Invalid glUniform{1234}i() usage"			   },
+		{uniformi_invalid_location,				"uniformi_invalid_location",			"Invalid glUniform{1234}i() usage"			   },
+		{uniformiv_invalid_program,				"uniformiv_invalid_program",			"Invalid glUniform{1234}iv() usage"			   },
+		{uniformiv_incompatible_type,			"uniformiv_incompatible_type",			"Invalid glUniform{1234}iv() usage"			   },
+		{uniformiv_invalid_location,			"uniformiv_invalid_location",			"Invalid glUniform{1234}iv() usage"			   },
+		{uniformiv_invalid_count,				"uniformiv_invalid_count",				"Invalid glUniform{1234}iv() usage"			   },
+		{uniformui_invalid_program,				"uniformui_invalid_program",			"Invalid glUniform{234}ui() usage"			   },
+		{uniformui_incompatible_type,			"uniformui_incompatible_type",			"Invalid glUniform{1234}ui() usage"			   },
+		{uniformui_invalid_location,			"uniformui_invalid_location",			"Invalid glUniform{1234}ui() usage"			   },
+		{uniformuiv_invalid_program,			"uniformuiv_invalid_program",			"Invalid glUniform{234}uiv() usage"			   },
+		{uniformuiv_incompatible_type,			"uniformuiv_incompatible_type",			"Invalid glUniform{1234}uiv() usage"		   },
+		{uniformuiv_invalid_location,			"uniformuiv_invalid_location",			"Invalid glUniform{1234}uiv() usage"		   },
+		{uniformuiv_invalid_count,				"uniformuiv_invalid_count",				"Invalid glUniform{1234}uiv() usage"		   },
+		{uniform_matrixfv_invalid_program,		"uniform_matrixfv_invalid_program",		"Invalid glUniformMatrix{234}fv() usage"	   },
+		{uniform_matrixfv_incompatible_type,	"uniform_matrixfv_incompatible_type",	"Invalid glUniformMatrix{234}fv() usage"	   },
+		{uniform_matrixfv_invalid_location,		"uniform_matrixfv_invalid_location",	"Invalid glUniformMatrix{234}fv() usage"	   },
+		{uniform_matrixfv_invalid_count,		"uniform_matrixfv_invalid_count",		"Invalid glUniformMatrix{234}fv() usage"	   },
+		{gen_transform_feedbacks,				"gen_transform_feedbacks",				"Invalid glGenTransformFeedbacks() usage"	   },
+		{bind_transform_feedback,				"bind_transform_feedback",				"Invalid glBindTransformFeedback() usage"	   },
+		{delete_transform_feedbacks,			"delete_transform_feedbacks",			"Invalid glDeleteTransformFeedbacks() usage"   },
+		{begin_transform_feedback,				"begin_transform_feedback",				"Invalid glBeginTransformFeedback() usage"	   },
+		{pause_transform_feedback,				"pause_transform_feedback",				"Invalid glPauseTransformFeedback() usage"	   },
+		{resume_transform_feedback,				"resume_transform_feedback",			"Invalid glResumeTransformFeedback() usage"	   },
+		{end_transform_feedback,				"end_transform_feedback",				"Invalid glEndTransformFeedback() usage"	   },
+		{get_transform_feedback_varying,		"get_transform_feedback_varying",		"Invalid glGetTransformFeedbackVarying() usage"},
+		{transform_feedback_varyings,			"transform_feedback_varyings",			"Invalid glTransformFeedbackVaryings() usage"  },
+	};
+
+	return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
+}
+
+} // NegativeTestShared
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fNegativeShaderApiTests.hpp b/modules/gles31/functional/es31fNegativeShaderApiTests.hpp
new file mode 100644
index 0000000..06e6875
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeShaderApiTests.hpp
@@ -0,0 +1,45 @@
+#ifndef _ES31FNEGATIVESHADERAPITESTS_HPP
+#define _ES31FNEGATIVESHADERAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Shader API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "es31fNegativeTestShared.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+std::vector<FunctionContainer> getNegativeShaderApiTestFunctions ();
+
+} // NegativeTestShared
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES31FNEGATIVESHADERAPITESTS_HPP
diff --git a/modules/gles31/functional/es31fNegativeStateApiTests.cpp b/modules/gles31/functional/es31fNegativeStateApiTests.cpp
new file mode 100644
index 0000000..46945a9
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeStateApiTests.cpp
@@ -0,0 +1,1145 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative GL State API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fNegativeStateApiTests.hpp"
+
+#include "gluCallLogWrapper.hpp"
+#include "gluShaderProgram.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+#include "deMemory.h"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+using tcu::TestLog;
+using glu::CallLogWrapper;
+using namespace glw;
+
+static const char* uniformTestVertSource	=	"#version 300 es\n"
+												"uniform mediump vec4 vUnif_vec4;\n"
+												"in mediump vec4 attr;"
+												"layout(shared) uniform Block { mediump vec4 blockVar; };\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vUnif_vec4 + blockVar + attr;\n"
+												"}\n\0";
+static const char* uniformTestFragSource	=	"#version 300 es\n"
+												"uniform mediump ivec4 fUnif_ivec4;\n"
+												"uniform mediump uvec4 fUnif_uvec4;\n"
+												"layout(location = 0) out mediump vec4 fragColor;"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(vec4(fUnif_ivec4) + vec4(fUnif_uvec4));\n"
+												"}\n\0";
+
+// Enabling & disabling states
+void enable (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if cap is not one of the allowed values.");
+	ctx.glEnable(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void disable (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if cap is not one of the allowed values.");
+	ctx.glDisable(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+// Simple state queries
+void get_booleanv (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+	GLboolean params = GL_FALSE;
+	ctx.glGetBooleanv(-1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void get_floatv (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+	GLfloat params = 0.0f;
+	ctx.glGetFloatv(-1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void get_integerv (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+	GLint params = -1;
+	ctx.glGetIntegerv(-1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void get_integer64v (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not one of the allowed values.");
+	GLint64 params = -1;
+	ctx.glGetInteger64v(-1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void get_integeri_v (NegativeTestContext& ctx)
+{
+	GLint data = -1;
+	GLint maxUniformBufferBindings;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if name is not an accepted value.");
+	ctx.glGetIntegeri_v(-1, 0, &data);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is outside of the valid range for the indexed state target.");
+	ctx.glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glGetIntegeri_v(GL_UNIFORM_BUFFER_BINDING, maxUniformBufferBindings, &data);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void get_integer64i_v (NegativeTestContext& ctx)
+{
+	GLint64 data = (GLint64)-1;;
+	GLint maxUniformBufferBindings;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if name is not an accepted value.");
+	ctx.glGetInteger64i_v(-1, 0, &data);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is outside of the valid range for the indexed state target.");
+	ctx.glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glGetInteger64i_v(GL_UNIFORM_BUFFER_START, maxUniformBufferBindings, &data);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void get_string (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if name is not an accepted value.");
+	ctx.glGetString(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void get_stringi (NegativeTestContext& ctx)
+{
+	GLint numExtensions;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if name is not an accepted value.");
+	ctx.glGetStringi(-1, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is outside the valid range for indexed state name.");
+	ctx.glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
+	ctx.glGetStringi(GL_EXTENSIONS, numExtensions);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+// Enumerated state queries: Shaders
+
+void get_attached_shaders (NegativeTestContext& ctx)
+{
+	GLuint shaders[1];
+	GLuint shaderObject = ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLuint program		= ctx.glCreateProgram();
+	GLsizei count[1];
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glGetAttachedShaders(-1, 1, &count[0], &shaders[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glGetAttachedShaders(shaderObject, 1, &count[0], &shaders[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if maxCount is less than 0.");
+	ctx.glGetAttachedShaders(program, -1, &count[0], &shaders[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteShader(shaderObject);
+	ctx.glDeleteProgram(program);
+}
+
+void get_shaderiv (NegativeTestContext& ctx)
+{
+	GLboolean shaderCompilerSupported;
+	ctx.glGetBooleanv(GL_SHADER_COMPILER, &shaderCompilerSupported);
+	ctx.getLog() << TestLog::Message << "// GL_SHADER_COMPILER = " << (shaderCompilerSupported ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+
+	GLuint shader	= ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLuint program	= ctx.glCreateProgram();
+	GLint param[1]	= { -1 };
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glGetShaderiv(shader, -1, &param[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+	ctx.glGetShaderiv(-1, GL_SHADER_TYPE, &param[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if shader does not refer to a shader object.");
+	ctx.glGetShaderiv(program, GL_SHADER_TYPE, &param[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteShader(shader);
+	ctx.glDeleteProgram(program);
+}
+
+void get_shader_info_log (NegativeTestContext& ctx)
+{
+	GLuint shader		= ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLuint program		= ctx.glCreateProgram();
+	GLsizei length[1]	= { -1 };
+	char infoLog[128]	= { 0 };
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+	ctx.glGetShaderInfoLog(-1, 128, &length[0], &infoLog[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if shader is not a shader object.");
+	ctx.glGetShaderInfoLog(program, 128, &length[0], &infoLog[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if maxLength is less than 0.");
+	ctx.glGetShaderInfoLog(shader, -1, &length[0], &infoLog[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteShader(shader);
+	ctx.glDeleteProgram(program);
+}
+
+void get_shader_precision_format (NegativeTestContext& ctx)
+{
+	GLboolean shaderCompilerSupported;
+	ctx.glGetBooleanv(GL_SHADER_COMPILER, &shaderCompilerSupported);
+	ctx.getLog() << TestLog::Message << "// GL_SHADER_COMPILER = " << (shaderCompilerSupported ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+
+	GLint range[2];
+	GLint precision[1];
+
+	deMemset(&range[0], 0xcd, sizeof(range));
+	deMemset(&precision[0], 0xcd, sizeof(precision));
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if shaderType or precisionType is not an accepted value.");
+	ctx.glGetShaderPrecisionFormat (-1, GL_MEDIUM_FLOAT, &range[0], &precision[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetShaderPrecisionFormat (GL_VERTEX_SHADER, -1, &range[0], &precision[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetShaderPrecisionFormat (-1, -1, &range[0], &precision[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void get_shader_source (NegativeTestContext& ctx)
+{
+	GLsizei length[1];
+	char source[1];
+	GLuint program	= ctx.glCreateProgram();
+	GLuint shader	= ctx.glCreateShader(GL_VERTEX_SHADER);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if shader is not a value generated by OpenGL.");
+	ctx.glGetShaderSource(-1, 1, &length[0], &source[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if shader is not a shader object.");
+	ctx.glGetShaderSource(program, 1, &length[0], &source[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if bufSize is less than 0.");
+	ctx.glGetShaderSource(shader, -1, &length[0], &source[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteProgram(program);
+	ctx.glDeleteShader(shader);
+}
+
+// Enumerated state queries: Programs
+
+void get_programiv (NegativeTestContext& ctx)
+{
+	GLuint program	= ctx.glCreateProgram();
+	GLuint shader	= ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLint params[1]	= { -1 };
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glGetProgramiv(program, -1, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glGetProgramiv(-1, GL_LINK_STATUS, &params[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program does not refer to a program object.");
+	ctx.glGetProgramiv(shader, GL_LINK_STATUS, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteProgram(program);
+	ctx.glDeleteShader(shader);
+}
+
+void get_program_info_log (NegativeTestContext& ctx)
+{
+	GLuint program	= ctx.glCreateProgram();
+	GLuint shader	= ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLsizei length[1];
+	char infoLog[1];
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glGetProgramInfoLog (-1, 1, &length[0], &infoLog[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glGetProgramInfoLog (shader, 1, &length[0], &infoLog[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if maxLength is less than 0.");
+	ctx.glGetProgramInfoLog (program, -1, &length[0], &infoLog[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteProgram(program);
+	ctx.glDeleteShader(shader);
+}
+
+// Enumerated state queries: Shader variables
+
+void get_tex_parameterfv (NegativeTestContext& ctx)
+{
+	GLfloat params[1];
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not an accepted value.");
+	ctx.glGetTexParameterfv (-1, GL_TEXTURE_MAG_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetTexParameterfv (GL_TEXTURE_2D, -1, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetTexParameterfv (-1, -1, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void get_tex_parameteriv (NegativeTestContext& ctx)
+{
+	GLint params[1];
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not an accepted value.");
+	ctx.glGetTexParameteriv (-1, GL_TEXTURE_MAG_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetTexParameteriv (GL_TEXTURE_2D, -1, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetTexParameteriv (-1, -1, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void get_uniformfv (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+	ctx.glUseProgram(program.getProgram());
+
+	GLint unif = ctx.glGetUniformLocation(program.getProgram(), "vUnif_vec4");	// vec4
+	if (unif == -1)
+		ctx.fail("Failed to retrieve uniform location");
+
+	GLuint shader		= ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLuint programEmpty = ctx.glCreateProgram();
+	GLfloat params[4];
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glGetUniformfv (-1, unif, &params[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glGetUniformfv (shader, unif, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+	ctx.glGetUniformfv (programEmpty, unif, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if location does not correspond to a valid uniform variable location for the specified program object.");
+	ctx.glGetUniformfv (program.getProgram(), -1, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteShader(shader);
+	ctx.glDeleteProgram(programEmpty);
+}
+
+void get_uniformiv (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+	ctx.glUseProgram(program.getProgram());
+
+	GLint unif = ctx.glGetUniformLocation(program.getProgram(), "fUnif_ivec4");	// ivec4
+	if (unif == -1)
+		ctx.fail("Failed to retrieve uniform location");
+
+	GLuint shader		= ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLuint programEmpty = ctx.glCreateProgram();
+	GLint params[4];
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glGetUniformiv (-1, unif, &params[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glGetUniformiv (shader, unif, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+	ctx.glGetUniformiv (programEmpty, unif, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if location does not correspond to a valid uniform variable location for the specified program object.");
+	ctx.glGetUniformiv (program.getProgram(), -1, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteShader(shader);
+	ctx.glDeleteProgram(programEmpty);
+}
+
+void get_uniformuiv (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+	ctx.glUseProgram(program.getProgram());
+
+	GLint unif = ctx.glGetUniformLocation(program.getProgram(), "fUnif_uvec4");	// uvec4
+	if (unif == -1)
+		ctx.fail("Failed to retrieve uniform location");
+
+	GLuint shader		= ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLuint programEmpty = ctx.glCreateProgram();
+	GLuint params[4];
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glGetUniformuiv (-1, unif, &params[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glGetUniformuiv (shader, unif, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program has not been successfully linked.");
+	ctx.glGetUniformuiv (programEmpty, unif, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if location does not correspond to a valid uniform variable location for the specified program object.");
+	ctx.glGetUniformuiv (program.getProgram(), -1, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteShader(shader);
+	ctx.glDeleteProgram(programEmpty);
+}
+
+void get_active_uniform (NegativeTestContext& ctx)
+{
+	GLuint				shader				= ctx.glCreateShader(GL_VERTEX_SHADER);
+	glu::ShaderProgram	program				(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+	GLint				numActiveUniforms	= -1;
+
+	ctx.glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORMS,	&numActiveUniforms);
+	ctx.getLog() << TestLog::Message << "// GL_ACTIVE_UNIFORMS = " << numActiveUniforms << " (expected 4)." << TestLog::EndMessage;
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glGetActiveUniform(-1, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glGetActiveUniform(shader, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to the number of active uniform variables in program.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.glGetActiveUniform(program.getProgram(), numActiveUniforms, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if bufSize is less than 0.");
+	ctx.glGetActiveUniform(program.getProgram(), 0, -1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+	ctx.glDeleteShader(shader);
+}
+
+void get_active_uniformsiv (NegativeTestContext& ctx)
+{
+	GLuint					shader				= ctx.glCreateShader(GL_VERTEX_SHADER);
+	glu::ShaderProgram		program				(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+	GLuint					dummyUniformIndex	= 1;
+	GLint					dummyParamDst		= -1;
+	GLint					numActiveUniforms	= -1;
+
+	ctx.glUseProgram(program.getProgram());
+
+	ctx.glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORMS, &numActiveUniforms);
+	ctx.getLog() << TestLog::Message << "// GL_ACTIVE_UNIFORMS = " << numActiveUniforms << " (expected 4)." << TestLog::EndMessage;
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glGetActiveUniformsiv(-1, 1, &dummyUniformIndex, GL_UNIFORM_TYPE, &dummyParamDst);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glGetActiveUniformsiv(shader, 1, &dummyUniformIndex, GL_UNIFORM_TYPE, &dummyParamDst);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if any value in uniformIndices is greater than or equal to the value of GL_ACTIVE_UNIFORMS for program.");
+	for (int excess = 0; excess <= 2; excess++)
+	{
+		std::vector<GLuint> invalidUniformIndices;
+		invalidUniformIndices.push_back(1);
+		invalidUniformIndices.push_back(numActiveUniforms-1+excess);
+		invalidUniformIndices.push_back(1);
+
+		std::vector<GLint> dummyParamsDst(invalidUniformIndices.size());
+		ctx.glGetActiveUniformsiv(program.getProgram(), (GLsizei)invalidUniformIndices.size(), &invalidUniformIndices[0], GL_UNIFORM_TYPE, &dummyParamsDst[0]);
+		ctx.expectError(excess == 0 ? GL_NO_ERROR : GL_INVALID_VALUE);
+	}
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted token.");
+	ctx.glGetActiveUniformsiv(program.getProgram(), 1, &dummyUniformIndex, -1, &dummyParamDst);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+	ctx.glDeleteShader(shader);
+}
+
+void get_active_uniform_blockiv (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram	program			(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+	GLint				params			= -1;
+	GLint				numActiveBlocks	= -1;
+
+	ctx.glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORM_BLOCKS,	&numActiveBlocks);
+	ctx.getLog() << TestLog::Message << "// GL_ACTIVE_UNIFORM_BLOCKS = " << numActiveBlocks << " (expected 1)." << TestLog::EndMessage;
+	ctx.expectError		(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if uniformBlockIndex is greater than or equal to the value of GL_ACTIVE_UNIFORM_BLOCKS or is not the index of an active uniform block in program.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glGetActiveUniformBlockiv(program.getProgram(), numActiveBlocks, GL_UNIFORM_BLOCK_BINDING, &params);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not one of the accepted tokens.");
+	ctx.glGetActiveUniformBlockiv(program.getProgram(), 0, -1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void get_active_uniform_block_name (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram	program			(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+	GLsizei				length			= -1;
+	GLint				numActiveBlocks	= -1;
+	GLchar				uniformBlockName[128];
+
+	deMemset(&uniformBlockName[0], 0, sizeof(uniformBlockName));
+
+	ctx.glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORM_BLOCKS,	&numActiveBlocks);
+	ctx.getLog() << TestLog::Message << "// GL_ACTIVE_UNIFORM_BLOCKS = " << numActiveBlocks << " (expected 1)." << TestLog::EndMessage;
+	ctx.expectError		(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if uniformBlockIndex is greater than or equal to the value of GL_ACTIVE_UNIFORM_BLOCKS or is not the index of an active uniform block in program.");
+	ctx.glUseProgram(program.getProgram());
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glGetActiveUniformBlockName(program.getProgram(), numActiveBlocks, (int)sizeof(uniformBlockName), &length, &uniformBlockName[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void get_active_attrib (NegativeTestContext& ctx)
+{
+	GLuint				shader				= ctx.glCreateShader(GL_VERTEX_SHADER);
+	glu::ShaderProgram	program				(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+	GLint				numActiveAttributes	= -1;
+
+	GLsizei				length				= -1;
+	GLint				size				= -1;
+	GLenum				type				= -1;
+	GLchar				name[32];
+
+	deMemset(&name[0], 0, sizeof(name));
+
+	ctx.glGetProgramiv	(program.getProgram(), GL_ACTIVE_ATTRIBUTES,	&numActiveAttributes);
+	ctx.getLog() << TestLog::Message << "// GL_ACTIVE_ATTRIBUTES = " << numActiveAttributes << " (expected 1)." << TestLog::EndMessage;
+
+	ctx.glUseProgram(program.getProgram());
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not a value generated by OpenGL.");
+	ctx.glGetActiveAttrib(-1, 0, 32, &length, &size, &type, &name[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is not a program object.");
+	ctx.glGetActiveAttrib(shader, 0, 32, &length, &size, &type, &name[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_ACTIVE_ATTRIBUTES.");
+	ctx.glGetActiveAttrib(program.getProgram(), numActiveAttributes, (int)sizeof(name), &length, &size, &type, &name[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if bufSize is less than 0.");
+	ctx.glGetActiveAttrib(program.getProgram(), 0, -1, &length, &size, &type, &name[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+	ctx.glDeleteShader(shader);
+}
+
+void get_uniform_indices (NegativeTestContext& ctx)
+{
+	GLuint shader			= ctx.glCreateShader(GL_VERTEX_SHADER);
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(uniformTestVertSource, uniformTestFragSource));
+	GLint numActiveBlocks = -1;
+	const GLchar* uniformName =  "Block.blockVar";
+	GLuint uniformIndices = -1;
+
+	ctx.glGetProgramiv	(program.getProgram(), GL_ACTIVE_UNIFORM_BLOCKS,	&numActiveBlocks);
+	ctx.getLog() << TestLog::Message << "// GL_ACTIVE_UNIFORM_BLOCKS = "		<< numActiveBlocks			<< TestLog::EndMessage;
+	ctx.expectError		(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is a name of shader object.");
+	ctx.glGetUniformIndices(shader, 1, &uniformName, &uniformIndices);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if program is not name of program or shader object.");
+	GLuint invalid = -1;
+	ctx.glGetUniformIndices(invalid, 1, &uniformName, &uniformIndices);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+	ctx.glDeleteShader(shader);
+}
+
+void get_vertex_attribfv (NegativeTestContext& ctx)
+{
+	GLfloat params = 0.0f;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glGetVertexAttribfv(0, -1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	GLint maxVertexAttribs;
+	ctx.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+	ctx.glGetVertexAttribfv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &params);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void get_vertex_attribiv (NegativeTestContext& ctx)
+{
+	GLint params = -1;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glGetVertexAttribiv(0, -1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	GLint maxVertexAttribs;
+	ctx.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+	ctx.glGetVertexAttribiv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &params);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void get_vertex_attribi_iv (NegativeTestContext& ctx)
+{
+	GLint params = -1;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glGetVertexAttribIiv(0, -1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	GLint maxVertexAttribs;
+	ctx.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+	ctx.glGetVertexAttribIiv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &params);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void get_vertex_attribi_uiv (NegativeTestContext& ctx)
+{
+	GLuint params = (GLuint)-1;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glGetVertexAttribIuiv(0, -1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	GLint maxVertexAttribs;
+	ctx.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+	ctx.glGetVertexAttribIuiv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &params);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void get_vertex_attrib_pointerv (NegativeTestContext& ctx)
+{
+	GLvoid* ptr[1] = { DE_NULL };
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glGetVertexAttribPointerv(0, -1, &ptr[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	GLint maxVertexAttribs;
+	ctx.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
+	ctx.glGetVertexAttribPointerv(maxVertexAttribs, GL_VERTEX_ATTRIB_ARRAY_POINTER, &ptr[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void get_frag_data_location (NegativeTestContext& ctx)
+{
+	GLuint shader	= ctx.glCreateShader(GL_VERTEX_SHADER);
+	GLuint program	= ctx.glCreateProgram();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program is the name of a shader object.");
+	ctx.glGetFragDataLocation(shader, "gl_FragColor");
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if program has not been linked.");
+	ctx.glGetFragDataLocation(program, "gl_FragColor");
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteProgram(program);
+	ctx.glDeleteShader(shader);
+}
+
+// Enumerated state queries: Buffers
+
+void get_buffer_parameteriv (NegativeTestContext& ctx)
+{
+	GLint params = -1;
+	GLuint buf;
+	ctx.glGenBuffers(1, &buf);
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, buf);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or value is not an accepted value.");
+	ctx.glGetBufferParameteriv(-1, GL_BUFFER_SIZE, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetBufferParameteriv(GL_ARRAY_BUFFER, -1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetBufferParameteriv(-1, -1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, 0);
+	ctx.glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &params);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers(1, &buf);
+}
+
+void get_buffer_parameteri64v (NegativeTestContext& ctx)
+{
+	GLint64 params = -1;
+	GLuint buf;
+	ctx.glGenBuffers(1, &buf);
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, buf);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or value is not an accepted value.");
+	ctx.glGetBufferParameteri64v(-1, GL_BUFFER_SIZE, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetBufferParameteri64v(GL_ARRAY_BUFFER , -1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetBufferParameteri64v(-1, -1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, 0);
+	ctx.glGetBufferParameteri64v(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &params);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers(1, &buf);
+}
+
+void get_buffer_pointerv (NegativeTestContext& ctx)
+{
+	GLvoid* params = DE_NULL;
+	GLuint buf;
+	ctx.glGenBuffers(1, &buf);
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, buf);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not an accepted value.");
+	ctx.glGetBufferPointerv(GL_ARRAY_BUFFER, -1, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glGetBufferPointerv(-1, GL_BUFFER_MAP_POINTER, &params);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the reserved buffer object name 0 is bound to target.");
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, 0);
+	ctx.glGetBufferPointerv(GL_ARRAY_BUFFER, GL_BUFFER_MAP_POINTER, &params);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers(1, &buf);
+}
+
+void get_framebuffer_attachment_parameteriv (NegativeTestContext& ctx)
+{
+	GLint params[1] = { -1 };
+	GLuint fbo;
+	GLuint rbo[2];
+
+	ctx.glGenFramebuffers			(1, &fbo);
+	ctx.glGenRenderbuffers			(2, rbo);
+
+	ctx.glBindFramebuffer			(GL_FRAMEBUFFER,	fbo);
+	ctx.glBindRenderbuffer			(GL_RENDERBUFFER,	rbo[0]);
+	ctx.glRenderbufferStorage		(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 16, 16);
+	ctx.glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo[0]);
+	ctx.glBindRenderbuffer			(GL_RENDERBUFFER,	rbo[1]);
+	ctx.glRenderbufferStorage		(GL_RENDERBUFFER, GL_STENCIL_INDEX8, 16, 16);
+	ctx.glFramebufferRenderbuffer	(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo[1]);
+	ctx.glCheckFramebufferStatus	(GL_FRAMEBUFFER);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not one of the accepted tokens.");
+	ctx.glGetFramebufferAttachmentParameteriv(-1, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &params[0]);					// TYPE is GL_RENDERBUFFER
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not valid for the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE.");
+	ctx.glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &params[0]);	// TYPE is GL_RENDERBUFFER
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_BACK, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);					// TYPE is GL_FRAMEBUFFER_DEFAULT
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if attachment is GL_DEPTH_STENCIL_ATTACHMENT and different objects are bound to the depth and stencil attachment points of target.");
+	ctx.glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE and pname is not GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME.");
+	ctx.glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);		// TYPE is GL_NONE
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &params[0]);	// TYPE is GL_NONE
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION or GL_INVALID_ENUM is generated if attachment is not one of the accepted values for the current binding of target.");
+	ctx.glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_BACK, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);					// A FBO is bound so GL_BACK is invalid
+	ctx.expectError(GL_INVALID_OPERATION, GL_INVALID_ENUM);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &params[0]);		// Default framebuffer is bound so GL_COLOR_ATTACHMENT0 is invalid
+	ctx.expectError(GL_INVALID_OPERATION, GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteFramebuffers(1, &fbo);
+}
+
+void get_renderbuffer_parameteriv (NegativeTestContext& ctx)
+{
+	GLint params[1] = { -1 };
+	GLuint rbo;
+	ctx.glGenRenderbuffers(1, &rbo);
+	ctx.glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+	ctx.glGetRenderbufferParameteriv(-1, GL_RENDERBUFFER_WIDTH, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not one of the accepted tokens.");
+	ctx.glGetRenderbufferParameteriv(GL_RENDERBUFFER, -1, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteRenderbuffers(1, &rbo);
+	ctx.glBindRenderbuffer(GL_RENDERBUFFER, 0);
+}
+
+void get_internalformativ (NegativeTestContext& ctx)
+{
+	GLint params[16];
+
+	deMemset(&params[0], 0xcd, sizeof(params));
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if bufSize is negative.");
+	ctx.glGetInternalformativ	(GL_RENDERBUFFER, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, -1, &params[0]);
+	ctx.expectError				(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not GL_SAMPLES or GL_NUM_SAMPLE_COUNTS.");
+	ctx.glGetInternalformativ	(GL_RENDERBUFFER, GL_RGBA8, -1, 16, &params[0]);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if internalformat is not color-, depth-, or stencil-renderable.");
+	ctx.glGetInternalformativ	(GL_RENDERBUFFER, GL_RG8_SNORM, GL_NUM_SAMPLE_COUNTS, 16, &params[0]);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.glGetInternalformativ	(GL_RENDERBUFFER, GL_COMPRESSED_RGB8_ETC2, GL_NUM_SAMPLE_COUNTS, 16, &params[0]);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_RENDERBUFFER.");
+	ctx.glGetInternalformativ	(-1, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 16, &params[0]);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.glGetInternalformativ	(GL_FRAMEBUFFER, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 16, &params[0]);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.glGetInternalformativ	(GL_TEXTURE_2D, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 16, &params[0]);
+	ctx.expectError				(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+// Query object queries
+
+void get_queryiv (NegativeTestContext& ctx)
+{
+	GLint params = -1;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not an accepted value.");
+	ctx.glGetQueryiv	(GL_ANY_SAMPLES_PASSED, -1, &params);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.glGetQueryiv	(-1, GL_CURRENT_QUERY, &params);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.glGetQueryiv	(-1, -1, &params);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void get_query_objectuiv (NegativeTestContext& ctx)
+{
+	GLuint params	= -1;
+	GLuint id;
+	ctx.glGenQueries		(1, &id);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if id is not the name of a query object.");
+	ctx.glGetQueryObjectuiv	(-1, GL_QUERY_RESULT_AVAILABLE, &params);
+	ctx.expectError			(GL_INVALID_OPERATION);
+	ctx.getLog() << TestLog::Message << "// Note: " << id << " is not a query object yet, since it hasn't been used by glBeginQuery" << TestLog::EndMessage;
+	ctx.glGetQueryObjectuiv	(id, GL_QUERY_RESULT_AVAILABLE, &params);
+	ctx.expectError			(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glBeginQuery		(GL_ANY_SAMPLES_PASSED, id);
+	ctx.glEndQuery			(GL_ANY_SAMPLES_PASSED);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glGetQueryObjectuiv	(id, -1, &params);
+	ctx.expectError			(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if id is the name of a currently active query object.");
+	ctx.glBeginQuery		(GL_ANY_SAMPLES_PASSED, id);
+	ctx.expectError			(GL_NO_ERROR);
+	ctx.glGetQueryObjectuiv	(id, GL_QUERY_RESULT_AVAILABLE, &params);
+	ctx.expectError			(GL_INVALID_OPERATION);
+	ctx.glEndQuery			(GL_ANY_SAMPLES_PASSED);
+	ctx.expectError			(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glDeleteQueries		(1, &id);
+}
+
+// Sync object queries
+
+void get_synciv (NegativeTestContext& ctx)
+{
+	GLsizei length	= -1;
+	GLint	values[32];
+	GLsync	sync;
+
+	deMemset(&values[0], 0xcd, sizeof(values));
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if sync is not the name of a sync object.");
+	ctx.glGetSynciv	(0, GL_OBJECT_TYPE, 32, &length, &values[0]);
+	ctx.expectError	(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not one of the accepted tokens.");
+	sync = ctx.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+	ctx.expectError	(GL_NO_ERROR);
+	ctx.glGetSynciv	(sync, -1, 32, &length, &values[0]);
+	ctx.expectError	(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+// Enumerated boolean state queries
+
+void is_enabled (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if cap is not an accepted value.");
+	ctx.glIsEnabled(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glIsEnabled(GL_TRIANGLES);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+// Hints
+
+void hint (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if either target or mode is not an accepted value.");
+	ctx.glHint(GL_GENERATE_MIPMAP_HINT, -1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glHint(-1, GL_FASTEST);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glHint(-1, -1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+std::vector<FunctionContainer> getNegativeStateApiTestFunctions ()
+{
+	FunctionContainer funcs[] =
+	{
+		{enable,									"enable",									"Invalid glEnable() usage"							   },
+		{disable,									"disable",									"Invalid glDisable() usage"							   },
+		{get_booleanv,								"get_booleanv",								"Invalid glGetBooleanv() usage"						   },
+		{get_floatv,								"get_floatv",								"Invalid glGetFloatv() usage"						   },
+		{get_integerv,								"get_integerv",								"Invalid glGetIntegerv() usage"						   },
+		{get_integer64v,							"get_integer64v",							"Invalid glGetInteger64v() usage"					   },
+		{get_integeri_v,							"get_integeri_v",							"Invalid glGetIntegeri_v() usage"					   },
+		{get_integer64i_v,							"get_integer64i_v",							"Invalid glGetInteger64i_v() usage"					   },
+		{get_string,								"get_string",								"Invalid glGetString() usage"						   },
+		{get_stringi,								"get_stringi",								"Invalid glGetStringi() usage"						   },
+		{get_attached_shaders,						"get_attached_shaders",						"Invalid glGetAttachedShaders() usage"				   },
+		{get_shaderiv,								"get_shaderiv",								"Invalid glGetShaderiv() usage"						   },
+		{get_shader_info_log,						"get_shader_info_log",						"Invalid glGetShaderInfoLog() usage"				   },
+		{get_shader_precision_format,				"get_shader_precision_format",				"Invalid glGetShaderPrecisionFormat() usage"		   },
+		{get_shader_source,							"get_shader_source",						"Invalid glGetShaderSource() usage"					   },
+		{get_programiv,								"get_programiv",							"Invalid glGetProgramiv() usage"					   },
+		{get_program_info_log,						"get_program_info_log",						"Invalid glGetProgramInfoLog() usage"				   },
+		{get_tex_parameterfv,						"get_tex_parameterfv",						"Invalid glGetTexParameterfv() usage"				   },
+		{get_tex_parameteriv,						"get_tex_parameteriv",						"Invalid glGetTexParameteriv() usage"				   },
+		{get_uniformfv,								"get_uniformfv",							"Invalid glGetUniformfv() usage"					   },
+		{get_uniformiv,								"get_uniformiv",							"Invalid glGetUniformiv() usage"					   },
+		{get_uniformuiv,							"get_uniformuiv",							"Invalid glGetUniformuiv() usage"					   },
+		{get_active_uniform,						"get_active_uniform",						"Invalid glGetActiveUniform() usage"				   },
+		{get_active_uniformsiv,						"get_active_uniformsiv",					"Invalid glGetActiveUniformsiv() usage"				   },
+		{get_active_uniform_blockiv,				"get_active_uniform_blockiv",				"Invalid glGetActiveUniformBlockiv() usage"			   },
+		{get_active_uniform_block_name,				"get_active_uniform_block_name",			"Invalid glGetActiveUniformBlockName() usage"		   },
+		{get_active_attrib,							"get_active_attrib",						"Invalid glGetActiveAttrib() usage"					   },
+		{get_uniform_indices,						"get_uniform_indices",						"Invalid glGetUniformIndices() usage"				   },
+		{get_vertex_attribfv,						"get_vertex_attribfv",						"Invalid glGetVertexAttribfv() usage"				   },
+		{get_vertex_attribiv,						"get_vertex_attribiv",						"Invalid glGetVertexAttribiv() usage"				   },
+		{get_vertex_attribi_iv,						"get_vertex_attribi_iv",					"Invalid glGetVertexAttribIiv() usage"				   },
+		{get_vertex_attribi_uiv,					"get_vertex_attribi_uiv",					"Invalid glGetVertexAttribIuiv() usage"				   },
+		{get_vertex_attrib_pointerv,				"get_vertex_attrib_pointerv",				"Invalid glGetVertexAttribPointerv() usage"			   },
+		{get_frag_data_location,					"get_frag_data_location",					"Invalid glGetFragDataLocation() usage"				   },
+		{get_buffer_parameteriv,					"get_buffer_parameteriv",					"Invalid glGetBufferParameteriv() usage"			   },
+		{get_buffer_parameteri64v,					"get_buffer_parameteri64v",					"Invalid glGetBufferParameteri64v() usage"			   },
+		{get_buffer_pointerv,						"get_buffer_pointerv",						"Invalid glGetBufferPointerv() usage"				   },
+		{get_framebuffer_attachment_parameteriv,	"get_framebuffer_attachment_parameteriv",	"Invalid glGetFramebufferAttachmentParameteriv() usage"},
+		{get_renderbuffer_parameteriv,				"get_renderbuffer_parameteriv",				"Invalid glGetRenderbufferParameteriv() usage"		   },
+		{get_internalformativ,						"get_internalformativ",						"Invalid glGetInternalformativ() usage"				   },
+		{get_queryiv,								"get_queryiv",								"Invalid glGetQueryiv() usage"						   },
+		{get_query_objectuiv,						"get_query_objectuiv",						"Invalid glGetQueryObjectuiv() usage"				   },
+		{get_synciv,								"get_synciv",								"Invalid glGetSynciv() usage"						   },
+		{is_enabled,								"is_enabled",								"Invalid glIsEnabled() usage"						   },
+		{hint,										"hint",										"Invalid glHint() usage"							   },
+	};
+
+	return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
+}
+
+} // NegativeTestShared
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles31/functional/es31fNegativeStateApiTests.hpp b/modules/gles31/functional/es31fNegativeStateApiTests.hpp
new file mode 100644
index 0000000..f566306
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeStateApiTests.hpp
@@ -0,0 +1,45 @@
+#ifndef _ES31FNEGATIVESTATEAPITESTS_HPP
+#define _ES31FNEGATIVESTATEAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative GL State API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "es31fNegativeTestShared.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+std::vector<FunctionContainer> getNegativeStateApiTestFunctions ();
+
+} // NegativeTestShared
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES31FNEGATIVESTATEAPITESTS_HPP
diff --git a/modules/gles31/functional/es31fNegativeTestShared.cpp b/modules/gles31/functional/es31fNegativeTestShared.cpp
new file mode 100644
index 0000000..74bc55b
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeTestShared.cpp
@@ -0,0 +1,116 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shared structures for ES 3.1 negative API tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fNegativeTestShared.hpp"
+
+#include "gluRenderContext.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+using glw::GLenum;
+using tcu::TestLog;
+using std::string;
+
+ErrorCase::ErrorCase (Context& ctx, const char* name, const char* desc)
+	: TestCase(ctx, name, desc)
+{
+}
+
+NegativeTestContext::NegativeTestContext (ErrorCase&			host,
+										  glu::RenderContext&	renderCtx,
+										  tcu::TestLog&			log,
+										  tcu::ResultCollector&	results,
+										  bool					enableLogging_)
+	: glu::CallLogWrapper	(renderCtx.getFunctions(), log)
+	, m_renderCtx			(renderCtx)
+	, m_host				(host)
+	, m_results				(results)
+	, m_openSections		(0)
+{
+	enableLogging(enableLogging_);
+}
+
+NegativeTestContext::~NegativeTestContext ()
+{
+	while (m_openSections--)
+		getLog() << TestLog::EndSection;
+}
+
+void NegativeTestContext::fail (const string& msg)
+{
+	m_results.addResult(QP_TEST_RESULT_FAIL, msg);
+}
+
+int NegativeTestContext::getInteger (GLenum pname) const
+{
+	int retval = 0;
+	m_renderCtx.getFunctions().getIntegerv(pname, &retval);
+	return retval;
+}
+
+void NegativeTestContext::beginSection (const string& desc)
+{
+	if (isLoggingEnabled())
+	{
+		getLog() << TestLog::Section("callstream", desc);
+		m_openSections++;
+	}
+}
+
+void NegativeTestContext::endSection (void)
+{
+	if (isLoggingEnabled())
+	{
+		DE_ASSERT (m_openSections > 0);
+		getLog() << TestLog::EndSection;
+		m_openSections--;
+	}
+}
+
+void NegativeTestContext::expectMessage (GLenum source, GLenum type)
+{
+	m_host.expectMessage(source, type);
+}
+
+void NegativeTestContext::expectError (GLenum error)
+{
+	m_host.expectError(error, error);
+}
+
+void NegativeTestContext::expectError (GLenum error0, GLenum error1)
+{
+	m_host.expectError(error0, error1);
+}
+
+} // NegativeTestShared
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fNegativeTestShared.hpp b/modules/gles31/functional/es31fNegativeTestShared.hpp
new file mode 100644
index 0000000..e4eba1f
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeTestShared.hpp
@@ -0,0 +1,89 @@
+#ifndef _ES31FNEGATIVETESTSHARED_HPP
+#define _ES31FNEGATIVETESTSHARED_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shared structures for ES 3.1 negative API tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "glwDefs.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+class ErrorCase : public TestCase
+{
+public:
+								ErrorCase		(Context& ctx, const char* name, const char* desc);
+	virtual 					~ErrorCase		(void) {}
+
+	virtual void				expectMessage	(glw::GLenum source, glw::GLenum type) = 0;
+	virtual void				expectError		(glw::GLenum error0, glw::GLenum error1) = 0;
+};
+
+class NegativeTestContext : public glu::CallLogWrapper
+{
+public:
+								NegativeTestContext		(ErrorCase& host, glu::RenderContext& renderCtx, tcu::TestLog& log,  tcu::ResultCollector& results, bool enableLog);
+								~NegativeTestContext	();
+
+	const tcu::ResultCollector&	getResults				(void) const;
+
+	void						fail					(const std::string& msg);
+	int							getInteger				(glw::GLenum pname) const;
+	const glu::RenderContext&	getRenderContext		(void) const { return m_renderCtx; }
+	void						beginSection			(const std::string& desc);
+	void						endSection				(void);
+
+	void						expectMessage			(glw::GLenum source, glw::GLenum type);
+	void						expectError				(glw::GLenum error);
+	void						expectError				(glw::GLenum error0, glw::GLenum error1);
+
+private:
+	glu::RenderContext&			m_renderCtx;
+	ErrorCase&					m_host;
+	tcu::ResultCollector&		m_results;
+	int							m_openSections;
+};
+
+typedef void (*TestFunc)(NegativeTestContext& ctx);
+
+struct FunctionContainer
+{
+	TestFunc	function;
+	const char* name;
+	const char* desc;
+};
+
+} // NegativeTestShared
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FNEGATIVETESTSHARED_HPP
diff --git a/modules/gles31/functional/es31fNegativeTextureApiTests.cpp b/modules/gles31/functional/es31fNegativeTextureApiTests.cpp
new file mode 100644
index 0000000..e0fddfa
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeTextureApiTests.cpp
@@ -0,0 +1,3137 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Texture API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fNegativeTextureApiTests.hpp"
+#include "es31fNegativeTestShared.hpp"
+
+#include "gluCallLogWrapper.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+using tcu::TestLog;
+using glu::CallLogWrapper;
+using namespace glw;
+
+static inline int divRoundUp (int a, int b)
+{
+	return a/b + (a%b != 0 ? 1 : 0);
+}
+
+static inline int etc2DataSize (int width, int height)
+{
+	return (int)(divRoundUp(width, 4) * divRoundUp(height, 4) * sizeof(deUint64));
+}
+
+static inline int etc2EacDataSize (int width, int height)
+{
+	return 2 * etc2DataSize(width, height);
+}
+
+static deUint32 cubeFaceToGLFace (tcu::CubeFace face)
+{
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
+		case tcu::CUBEFACE_POSITIVE_X: return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
+		case tcu::CUBEFACE_NEGATIVE_Y: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
+		case tcu::CUBEFACE_POSITIVE_Y: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
+		case tcu::CUBEFACE_NEGATIVE_Z: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
+		case tcu::CUBEFACE_POSITIVE_Z: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return GL_NONE;
+	}
+}
+
+#define FOR_CUBE_FACES(FACE_GL_VAR, BODY)												\
+	do																					\
+	{																					\
+		for (int faceIterTcu = 0; faceIterTcu < tcu::CUBEFACE_LAST; faceIterTcu++)		\
+		{																				\
+			const GLenum FACE_GL_VAR = cubeFaceToGLFace((tcu::CubeFace)faceIterTcu);	\
+			BODY																		\
+		}																				\
+	} while (false)
+
+
+// glActiveTexture
+
+void activetexture (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if texture is not one of GL_TEXTUREi, where i ranges from 0 to (GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1).");
+	ctx.glActiveTexture(-1);
+	ctx.expectError(GL_INVALID_ENUM);
+	int numMaxTextureUnits = ctx.getInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+	ctx.glActiveTexture(GL_TEXTURE0 + numMaxTextureUnits);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+// glBindTexture
+
+void bindtexture (NegativeTestContext& ctx)
+{
+	GLuint texture[2];
+	ctx.glGenTextures(2, texture);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not one of the allowable values.");
+	ctx.glBindTexture(0, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glBindTexture(GL_FRAMEBUFFER, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if texture was previously created with a target that doesn't match that of target.");
+	ctx.glBindTexture(GL_TEXTURE_2D, texture[0]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glBindTexture(GL_TEXTURE_CUBE_MAP, texture[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glBindTexture(GL_TEXTURE_3D, texture[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glBindTexture(GL_TEXTURE_2D_ARRAY, texture[0]);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glBindTexture(GL_TEXTURE_CUBE_MAP, texture[1]);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glBindTexture(GL_TEXTURE_2D, texture[1]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glBindTexture(GL_TEXTURE_3D, texture[1]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glBindTexture(GL_TEXTURE_2D_ARRAY, texture[1]);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(2, texture);
+}
+
+// glCompressedTexImage2D
+
+void compressedteximage2d_invalid_target (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glCompressedTexImage2D(0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void compressedteximage2d_invalid_format (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if internalformat is not a supported format returned in GL_COMPRESSED_TEXTURE_FORMATS.");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void compressedteximage2d_neg_level (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void compressedteximage2d_max_level (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE) for a 2d texture target.");
+	deUint32 log2MaxTextureSize = deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, log2MaxTextureSize, GL_COMPRESSED_RGB8_ETC2, 16, 16, 0, etc2DataSize(16, 16), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE) for a cubemap target.");
+	deUint32 log2MaxCubemapSize = deLog2Floor32(ctx.getInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxCubemapSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void compressedteximage2d_neg_width_height (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is less than 0.");
+
+	ctx.beginSection("GL_TEXTURE_2D target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.endSection();
+}
+
+void compressedteximage2d_max_width_height (NegativeTestContext& ctx)
+{
+	int maxTextureSize = ctx.getInteger(GL_MAX_TEXTURE_SIZE) + 1;
+	int maxCubemapSize = ctx.getInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_TEXTURE_SIZE.");
+
+	ctx.beginSection("GL_TEXTURE_2D target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxTextureSize, 1, 0, etc2EacDataSize(maxTextureSize, 1), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxTextureSize, 0, etc2EacDataSize(1, maxTextureSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxTextureSize, maxTextureSize, 0, etc2EacDataSize(maxTextureSize, maxTextureSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, 1, 0, etc2EacDataSize(maxCubemapSize, 1), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, maxCubemapSize, 0, etc2EacDataSize(1, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxCubemapSize, maxCubemapSize, 0, etc2EacDataSize(maxCubemapSize, maxCubemapSize), 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.endSection();
+}
+
+void compressedteximage2d_invalid_border (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if border is not 0.");
+
+	ctx.beginSection("GL_TEXTURE_2D target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.endSection();
+}
+
+void compressedteximage2d_invalid_size (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if imageSize is not consistent with the format, dimensions, and contents of the specified compressed image data.");
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, 4*4*8, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, 16, 16, 0, 4*4*16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_SIGNED_R11_EAC, 16, 16, 0, 4*4*16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void compressedteximage2d_invalid_buffer_target (NegativeTestContext& ctx)
+{
+	deUint32				buf = 1234;
+	std::vector<GLubyte>	data(64);
+
+	ctx.glGenBuffers			(1, &buf);
+	ctx.glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+	ctx.glBufferData			(GL_PIXEL_UNPACK_BUFFER, 64, &data[0], GL_DYNAMIC_COPY);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and the buffer object's data store is currently mapped.");
+	ctx.glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 32, GL_MAP_WRITE_BIT);
+	ctx.glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, 4, 4, 0, etc2DataSize(4, 4), 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+	ctx.glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, 16, 16, 0, etc2DataSize(16, 16), 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers			(1, &buf);
+}
+
+// glCopyTexImage2D
+
+void copyteximage2d_invalid_target (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glCopyTexImage2D(0, 0, GL_RGB, 0, 0, 64, 64, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void copyteximage2d_invalid_format (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM or GL_INVALID_VALUE is generated if internalformat is not an accepted format.");
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 64, 64, 0);
+	ctx.expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 0, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 0, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 0, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 0, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void copyteximage2d_inequal_width_height_cube (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if target is one of the six cube map 2D image targets and the width and height parameters are not equal.");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 16, 17, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 16, 17, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 16, 17, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 16, 17, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 16, 17, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 16, 17, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void copyteximage2d_neg_level (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, -1, GL_RGB, 0, 0, 64, 64, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void copyteximage2d_max_level (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+	deUint32 log2MaxTextureSize = deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, log2MaxTextureSize, GL_RGB, 0, 0, 64, 64, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+	deUint32 log2MaxCubemapSize = deLog2Floor32(ctx.getInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxCubemapSize, GL_RGB, 0, 0, 16, 16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void copyteximage2d_neg_width_height (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is less than 0.");
+
+	ctx.beginSection("GL_TEXTURE_2D target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, -1, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, -1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, -1, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, -1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, -1, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, -1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, -1, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, -1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, -1, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, -1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, -1, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, -1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, -1, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, -1, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.endSection();
+}
+
+void copyteximage2d_max_width_height (NegativeTestContext& ctx)
+{
+	int maxTextureSize = ctx.getInteger(GL_MAX_TEXTURE_SIZE) + 1;
+	int maxCubemapSize = ctx.getInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_TEXTURE_SIZE.");
+
+	ctx.beginSection("GL_TEXTURE_2D target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, maxTextureSize, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 1, maxTextureSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, maxTextureSize, maxTextureSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 1, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, maxCubemapSize, 1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, maxCubemapSize, maxCubemapSize, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.endSection();
+}
+
+void copyteximage2d_invalid_border (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if border is not 0.");
+
+	ctx.beginSection("GL_TEXTURE_2D target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 0, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 0, 0, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 0, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 0, 0, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 0, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 0, 0, 0, 0, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_2D target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 0, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 0, 0, 0, 0, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 0, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 0, 0, 0, 0, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 0, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 0, 0, 0, 0, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 0, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 0, 0, 0, 0, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.endSection();
+}
+
+void copyteximage2d_incomplete_framebuffer (NegativeTestContext& ctx)
+{
+	GLuint fbo = 0x1234;
+	ctx.glGenFramebuffers		(1, &fbo);
+	ctx.glBindFramebuffer		(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA8, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.endSection();
+
+	ctx.glBindFramebuffer	(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+}
+
+void copytexsubimage2d_invalid_target (NegativeTestContext& ctx)
+{
+	GLuint texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glCopyTexSubImage2D(0, 0, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void copytexsubimage2d_neg_level (NegativeTestContext& ctx)
+{
+	GLuint textures[2];
+	ctx.glGenTextures	(2, &textures[0]);
+	ctx.glBindTexture	(GL_TEXTURE_2D, textures[0]);
+	ctx.glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture	(GL_TEXTURE_CUBE_MAP, textures[1]);
+	FOR_CUBE_FACES(faceGL, ctx.glTexImage2D(faceGL, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0););
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, -1, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	FOR_CUBE_FACES(faceGL,
+	{
+		ctx.glCopyTexSubImage2D(faceGL, -1, 0, 0, 0, 0, 4, 4);
+		ctx.expectError(GL_INVALID_VALUE);
+	});
+	ctx.endSection();
+
+	ctx.glDeleteTextures(2, &textures[0]);
+}
+
+void copytexsubimage2d_max_level (NegativeTestContext& ctx)
+{
+	GLuint textures[2];
+	ctx.glGenTextures	(2, &textures[0]);
+	ctx.glBindTexture	(GL_TEXTURE_2D, textures[0]);
+	ctx.glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture	(GL_TEXTURE_CUBE_MAP, textures[1]);
+	FOR_CUBE_FACES(faceGL, ctx.glTexImage2D(faceGL, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0););
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE) for 2D texture targets.");
+	deUint32 log2MaxTextureSize = deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_SIZE) for cubemap targets.");
+	deUint32 log2MaxCubemapSize = deLog2Floor32(ctx.getInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+	FOR_CUBE_FACES(faceGL,
+	{
+		ctx.glCopyTexSubImage2D(faceGL, log2MaxCubemapSize, 0, 0, 0, 0, 4, 4);
+		ctx.expectError(GL_INVALID_VALUE);
+	});
+	ctx.endSection();
+
+	ctx.glDeleteTextures(2, &textures[0]);
+}
+
+void copytexsubimage2d_neg_offset (NegativeTestContext& ctx)
+{
+	GLuint texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if xoffset < 0 or yoffset < 0.");
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, -1, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, -1, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, -1, -1, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void copytexsubimage2d_invalid_offset (NegativeTestContext& ctx)
+{
+	GLuint texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 14, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 14, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 14, 14, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void copytexsubimage2d_neg_width_height (NegativeTestContext& ctx)
+{
+	GLuint texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D	(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is less than 0.");
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, -1, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void copytexsubimage2d_incomplete_framebuffer (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+
+	GLuint texture[2];
+	GLuint fbo = 0x1234;
+
+	ctx.glGenTextures			(2, texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture[0]);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture			(GL_TEXTURE_CUBE_MAP, texture[1]);
+	ctx.glTexImage2D			(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glTexImage2D			(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glTexImage2D			(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glTexImage2D			(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glTexImage2D			(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glTexImage2D			(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.glDeleteTextures(2, texture);
+
+	ctx.endSection();
+}
+
+// glDeleteTextures
+
+void deletetextures (NegativeTestContext& ctx)
+{
+	GLuint texture = 0x1234;
+	ctx.glGenTextures(1, &texture);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glDeleteTextures(-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+
+	ctx.glBindTexture(GL_TEXTURE_2D, texture);
+	ctx.glDeleteTextures(-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+// glGenerateMipmap
+
+void generatemipmap (NegativeTestContext& ctx)
+{
+	GLuint texture[2];
+	ctx.glGenTextures(2, texture);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP.");
+	ctx.glGenerateMipmap(0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("INVALID_OPERATION is generated if the texture bound to target is not cube complete.");
+	ctx.glBindTexture(GL_TEXTURE_CUBE_MAP, texture[0]);
+	ctx.glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_REPEAT);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glBindTexture(GL_TEXTURE_CUBE_MAP, texture[0]);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the zero level array is stored in a compressed internal format.");
+	ctx.glBindTexture(GL_TEXTURE_2D, texture[1]);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0);
+	ctx.glGenerateMipmap(GL_TEXTURE_2D);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the level base array was not specified with an unsized internal format or a sized internal format that is both color-renderable and texture-filterable.");
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8_SNORM, 0, 0, 0, GL_RGB, GL_BYTE, 0);
+	ctx.glGenerateMipmap(GL_TEXTURE_2D);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_R8I, 0, 0, 0, GL_RED_INTEGER, GL_BYTE, 0);
+	ctx.glGenerateMipmap(GL_TEXTURE_2D);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 0, 0, 0, GL_RGBA, GL_FLOAT, 0);
+	ctx.glGenerateMipmap(GL_TEXTURE_2D);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(2, texture);
+}
+
+// glGenTextures
+
+void gentextures (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glGenTextures(-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+// glPixelStorei
+
+void pixelstorei (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if pname is not an accepted value.");
+	ctx.glPixelStorei(0,1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if a negative row length, pixel skip, or row skip value is specified, or if alignment is specified as other than 1, 2, 4, or 8.");
+	ctx.glPixelStorei(GL_PACK_ROW_LENGTH, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_PACK_SKIP_ROWS, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_PACK_SKIP_PIXELS, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_UNPACK_ROW_LENGTH, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_UNPACK_SKIP_ROWS, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_UNPACK_SKIP_PIXELS, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_UNPACK_SKIP_IMAGES, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_PACK_ALIGNMENT, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_UNPACK_ALIGNMENT, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_PACK_ALIGNMENT, 16);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glPixelStorei(GL_UNPACK_ALIGNMENT, 16);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+// glTexImage2D
+
+void teximage2d (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glTexImage2D(0, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not a type constant.");
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the combination of internalFormat, format and type is invalid.");
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, 1, 1, 0, GL_RGB, GL_UNSIGNED_INT_2_10_10_10_REV, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32UI, 1, 1, 0, GL_RGBA_INTEGER, GL_INT, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void teximage2d_inequal_width_height_cube (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if target is one of the six cube map 2D image targets and the width and height parameters are not equal.");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void teximage2d_neg_level (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glTexImage2D(GL_TEXTURE_2D, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, -1, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void teximage2d_max_level (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+	deUint32 log2MaxTextureSize = deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+	ctx.glTexImage2D(GL_TEXTURE_2D, log2MaxTextureSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+	deUint32 log2MaxCubemapSize = deLog2Floor32(ctx.getInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, log2MaxCubemapSize, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void teximage2d_neg_width_height (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is less than 0.");
+
+	ctx.beginSection("GL_TEXTURE_2D target");
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, -1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, -1, -1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.endSection();
+}
+
+void teximage2d_max_width_height (NegativeTestContext& ctx)
+{
+	int maxTextureSize = ctx.getInteger(GL_MAX_TEXTURE_SIZE) + 1;
+	int maxCubemapSize = ctx.getInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE) + 1;
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_TEXTURE_SIZE.");
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, maxTextureSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, maxTextureSize, maxTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is greater than GL_MAX_CUBE_MAP_TEXTURE_SIZE.");
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_X target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Y target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_POSITIVE_Z target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_X target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Y target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_TEXTURE_CUBE_MAP_NEGATIVE_Z target");
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, maxCubemapSize, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, maxCubemapSize, maxCubemapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.endSection();
+}
+
+void teximage2d_invalid_border (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if border is not 0.");
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, -1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void teximage2d_invalid_buffer_target (NegativeTestContext& ctx)
+{
+	deUint32				buf = 0x1234;
+	deUint32				texture = 0x1234;
+	std::vector<GLubyte>	data(64);
+
+	ctx.glGenBuffers			(1, &buf);
+	ctx.glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+	ctx.glBufferData			(GL_PIXEL_UNPACK_BUFFER, 64, &data[0], GL_DYNAMIC_COPY);
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+	ctx.beginSection("...the buffer object's data store is currently mapped.");
+	ctx.glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 32, GL_MAP_WRITE_BIT);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+	ctx.endSection();
+
+	ctx.beginSection("...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("...data is not evenly divisible into the number of bytes needed to store in memory a datum indicated by type.");
+	ctx.getLog() << TestLog::Message << "// Set byte offset = 3" << TestLog::EndMessage;
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGB5_A1, 4, 4, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (const GLvoid*)3);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+	ctx.endSection();
+
+	ctx.glDeleteBuffers			(1, &buf);
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+// glTexSubImage2D
+
+void texsubimage2d (NegativeTestContext& ctx)
+{
+	deUint32			texture = 0x1234;
+	ctx.glGenTextures		(1, &texture);
+	ctx.glBindTexture		(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D		(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glTexSubImage2D(0, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if format is not an accepted format constant.");
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 4, 4, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not a type constant.");
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the combination of internalFormat of the previously specified texture array, format and type is not valid.");
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_SHORT_5_6_5, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA_INTEGER, GL_UNSIGNED_INT, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, GL_FLOAT, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures	(1, &texture);
+}
+
+void texsubimage2d_neg_level (NegativeTestContext& ctx)
+{
+	deUint32			textures[2];
+	ctx.glGenTextures		(2, &textures[0]);
+	ctx.glBindTexture		(GL_TEXTURE_2D, textures[0]);
+	ctx.glTexImage2D		(GL_TEXTURE_2D, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture		(GL_TEXTURE_2D, textures[1]);
+	FOR_CUBE_FACES(faceGL, ctx.glTexImage2D(faceGL, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0););
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	FOR_CUBE_FACES(faceGL,
+	{
+		ctx.glTexSubImage2D(faceGL, -1, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+		ctx.expectError(GL_INVALID_VALUE);
+	});
+	ctx.endSection();
+
+	ctx.glDeleteTextures(2, &textures[0]);
+}
+
+void texsubimage2d_max_level (NegativeTestContext& ctx)
+{
+	deUint32			textures[2];
+	ctx.glGenTextures		(2, &textures[0]);
+	ctx.glBindTexture		(GL_TEXTURE_2D, textures[0]);
+	ctx.glTexImage2D		(GL_TEXTURE_2D, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture		(GL_TEXTURE_CUBE_MAP, textures[1]);
+	FOR_CUBE_FACES(faceGL, ctx.glTexImage2D(faceGL, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0););
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+	deUint32 log2MaxTextureSize = deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+	deUint32 log2MaxCubemapSize = deLog2Floor32(ctx.getInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+	FOR_CUBE_FACES(faceGL,
+	{
+		ctx.glTexSubImage2D(faceGL, log2MaxCubemapSize, 0, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+		ctx.expectError(GL_INVALID_VALUE);
+	});
+	ctx.endSection();
+
+	ctx.glDeleteTextures(2, &textures[0]);
+}
+
+void texsubimage2d_neg_offset (NegativeTestContext& ctx)
+{
+	deUint32 texture = 0x1234;
+	ctx.glGenTextures(1, &texture);
+	ctx.glBindTexture(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if xoffset or yoffset are negative.");
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, -1, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, -1, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, -1, -1, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void texsubimage2d_invalid_offset (NegativeTestContext& ctx)
+{
+	deUint32			texture = 0x1234;
+	ctx.glGenTextures		(1, &texture);
+	ctx.glBindTexture		(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D		(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 30, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 30, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 30, 30, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures	(1, &texture);
+}
+
+void texsubimage2d_neg_width_height (NegativeTestContext& ctx)
+{
+	deUint32			texture = 0x1234;
+	ctx.glGenTextures		(1, &texture);
+	ctx.glBindTexture		(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D		(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is less than 0.");
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, -1, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -1, -1, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures	(1, &texture);
+}
+
+void texsubimage2d_invalid_buffer_target (NegativeTestContext& ctx)
+{
+	deUint32				buf = 0x1234;
+	deUint32				texture = 0x1234;
+	std::vector<GLubyte>	data(64);
+
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glGenBuffers			(1, &buf);
+	ctx.glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+	ctx.glBufferData			(GL_PIXEL_UNPACK_BUFFER, 64, &data[0], GL_DYNAMIC_COPY);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+	ctx.beginSection("...the buffer object's data store is currently mapped.");
+	ctx.glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 32, GL_MAP_WRITE_BIT);
+	ctx.glTexSubImage2D			(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+	ctx.endSection();
+
+	ctx.beginSection("...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+	ctx.glTexSubImage2D			(GL_TEXTURE_2D, 0, 0, 0, 32, 32, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("...data is not evenly divisible into the number of bytes needed to store in memory a datum indicated by type.");
+	ctx.getLog() << TestLog::Message << "// Set byte offset = 3" << TestLog::EndMessage;
+	ctx.glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, 0);
+	ctx.glTexImage2D			(GL_TEXTURE_2D, 0, GL_RGBA4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+	ctx.glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.glTexSubImage2D			(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (const GLvoid*)3);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+	ctx.endSection();
+
+	ctx.glDeleteBuffers			(1, &buf);
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+// glTexParameteri
+
+void texparameteri (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+	ctx.glTexParameteri(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteri(GL_TEXTURE_2D, 0, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteri(0, 0, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+	ctx.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	GLuint texture = 0x1234;
+	ctx.glGenTextures(1, &texture);
+	ctx.glBindTexture(GL_TEXTURE_2D, texture);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+	ctx.glTexParameteri(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteri(GL_TEXTURE_2D, 0, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteri(0, 0, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+	ctx.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+// glTexParameterf
+
+void texparameterf (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+	ctx.glTexParameterf(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterf(GL_TEXTURE_2D, 0, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterf(0, 0, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+	ctx.glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	GLuint texture = 0x1234;
+	ctx.glGenTextures(1, &texture);
+	ctx.glBindTexture(GL_TEXTURE_2D, texture);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+	ctx.glTexParameterf(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterf(GL_TEXTURE_2D, 0, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterf(0, 0, GL_LINEAR);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+	ctx.glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_NEAREST);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+// glTexParameteriv
+
+void texparameteriv (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+	GLint params[1] = {GL_LINEAR};
+	ctx.glTexParameteriv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteriv(GL_TEXTURE_2D, 0, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteriv(0, 0, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+	params[0] = 0;
+	ctx.glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = GL_REPEAT;
+	ctx.glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = 0;
+	ctx.glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = GL_NEAREST;
+	ctx.glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	GLuint texture = 0x1234;
+	ctx.glGenTextures(1, &texture);
+	ctx.glBindTexture(GL_TEXTURE_2D, texture);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+	params[0] = GL_LINEAR;
+	ctx.glTexParameteriv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteriv(GL_TEXTURE_2D, 0, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameteriv(0, 0, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+	params[0] = 0;
+	ctx.glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = GL_REPEAT;
+	ctx.glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = 0;
+	ctx.glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = GL_NEAREST;
+	ctx.glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+// glTexParameterfv
+
+void texparameterfv (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+	GLfloat params[1] = {GL_LINEAR};
+	ctx.glTexParameterfv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterfv(GL_TEXTURE_2D, 0, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterfv(0, 0, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+	params[0] = 0.0f;
+	ctx.glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = GL_REPEAT;
+	ctx.glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = 0.0f;
+	ctx.glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = GL_NEAREST;
+	ctx.glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	GLuint texture = 0x1234;
+	ctx.glGenTextures(1, &texture);
+	ctx.glBindTexture(GL_TEXTURE_2D, texture);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target or pname is not one of the accepted defined values.");
+	params[0] = GL_LINEAR;
+	ctx.glTexParameterfv(0, GL_TEXTURE_MIN_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterfv(GL_TEXTURE_2D, 0, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexParameterfv(0, 0, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if params should have a defined symbolic constant value (based on the value of pname) and does not.");
+	params[0] = 0.0f;
+	ctx.glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = GL_REPEAT;
+	ctx.glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = 0.0f;
+	ctx.glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	params[0] = GL_NEAREST;
+	ctx.glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &params[0]);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+// glCompressedTexSubImage2D
+
+void compressedtexsubimage2d (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glCompressedTexSubImage2D(0, 0, 0, 0, 0, 0, GL_COMPRESSED_RGB8_ETC2, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 0, etc2EacDataSize(18, 18), 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if format does not match the internal format of the texture image being modified.");
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_COMPRESSED_RGB8_ETC2, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("For ETC2/EAC images GL_INVALID_OPERATION is generated if width is not a multiple of four, and width + xoffset is not equal to the width of the texture level.");
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 4, 0, 10, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(10, 4), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("For ETC2/EAC images GL_INVALID_OPERATION is generated if height is not a multiple of four, and height + yoffset is not equal to the height of the texture level.");
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 4, 4, 10, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 10), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("For ETC2/EAC images GL_INVALID_OPERATION is generated if xoffset or yoffset is not a multiple of four.");
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 1, 4, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 1, 0, 4, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 4, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+void compressedtexsubimage2d_neg_level (NegativeTestContext& ctx)
+{
+	deUint32				textures[2];
+	ctx.glGenTextures			(2, &textures[0]);
+	ctx.glBindTexture			(GL_TEXTURE_2D, textures[0]);
+	ctx.glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 0, etc2EacDataSize(18, 18), 0);
+	ctx.glBindTexture			(GL_TEXTURE_CUBE_MAP, textures[1]);
+	FOR_CUBE_FACES(faceGL, ctx.glCompressedTexImage2D(faceGL, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 0, etc2EacDataSize(18, 18), 0););
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, -1, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	FOR_CUBE_FACES(faceGL,
+	{
+		ctx.glCompressedTexSubImage2D(faceGL, -1, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+		ctx.expectError(GL_INVALID_VALUE);
+	});
+	ctx.endSection();
+
+	ctx.glDeleteTextures(2, &textures[0]);
+}
+
+void compressedtexsubimage2d_max_level (NegativeTestContext& ctx)
+{
+	deUint32				textures[2];
+	ctx.glGenTextures			(2, &textures[0]);
+	ctx.glBindTexture			(GL_TEXTURE_2D, textures[0]);
+	ctx.glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 0, etc2EacDataSize(18, 18), 0);
+	ctx.glBindTexture			(GL_TEXTURE_CUBE_MAP, textures[1]);
+	FOR_CUBE_FACES(faceGL, ctx.glCompressedTexImage2D(faceGL, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 0, etc2EacDataSize(18, 18), 0););
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+	deUint32 log2MaxTextureSize = deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, log2MaxTextureSize, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_CUBE_MAP_TEXTURE_SIZE).");
+	deUint32 log2MaxCubemapSize = deLog2Floor32(ctx.getInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE)) + 1;
+	FOR_CUBE_FACES(faceGL,
+	{
+		ctx.glCompressedTexSubImage2D(faceGL, log2MaxCubemapSize, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+		ctx.expectError(GL_INVALID_VALUE);
+	});
+	ctx.endSection();
+
+	ctx.glDeleteTextures(2, &textures[0]);
+}
+
+void compressedtexsubimage2d_neg_offset (NegativeTestContext& ctx)
+{
+	GLuint texture = 0x1234;
+	ctx.glGenTextures(1, &texture);
+	ctx.glBindTexture(GL_TEXTURE_2D, texture);
+	ctx.glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 8, 8, 0, etc2EacDataSize(8, 8), 0);
+
+	// \note Both GL_INVALID_VALUE and GL_INVALID_OPERATION are valid here since implementation may
+	//		 first check if offsets are valid for certain format and only after that check that they
+	//		 are not negative.
+	ctx.beginSection("GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if xoffset or yoffset are negative.");
+
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, -4, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, -4, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, -4, -4, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void compressedtexsubimage2d_invalid_offset (NegativeTestContext& ctx)
+{
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 12, 0, 8, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(8, 4), 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 12, 4, 8, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 8), 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 12, 12, 8, 8, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(8, 8), 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+void compressedtexsubimage2d_neg_width_height (NegativeTestContext& ctx)
+{
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if width or height is less than 0.");
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -4, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, -4, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -4, -4, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1,		&texture);
+}
+
+void compressedtexsubimage2d_invalid_size (NegativeTestContext& ctx)
+{
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D, texture);
+	ctx.glCompressedTexImage2D	(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if imageSize is not consistent with the format, dimensions, and contents of the specified compressed image data.");
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+
+	ctx.glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 16, 16, GL_COMPRESSED_RGBA8_ETC2_EAC, 4*4*16-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+void compressedtexsubimage2d_invalid_buffer_target (NegativeTestContext& ctx)
+{
+	deUint32					buf = 0x1234;
+	deUint32					texture = 0x1234;
+	std::vector<GLubyte>		data(128);
+
+	ctx.glGenTextures				(1, &texture);
+	ctx.glBindTexture				(GL_TEXTURE_2D, texture);
+	ctx.glCompressedTexImage2D		(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 0, etc2EacDataSize(16, 16), 0);
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glBindBuffer				(GL_PIXEL_UNPACK_BUFFER, buf);
+	ctx.glBufferData				(GL_PIXEL_UNPACK_BUFFER, 128, &data[0], GL_DYNAMIC_COPY);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+	ctx.beginSection("...the buffer object's data store is currently mapped.");
+	ctx.glMapBufferRange			(GL_PIXEL_UNPACK_BUFFER, 0, 128, GL_MAP_WRITE_BIT);
+	ctx.glCompressedTexSubImage2D	(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer				(GL_PIXEL_UNPACK_BUFFER);
+	ctx.endSection();
+
+	ctx.beginSection("...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+	ctx.glCompressedTexSubImage2D	(GL_TEXTURE_2D, 0, 0, 0, 16, 16, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(16, 16), 0);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.endSection();
+	ctx.endSection();
+
+	ctx.glDeleteBuffers			(1, &buf);
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+// glTexImage3D
+
+void teximage3d (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glTexImage3D(0, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexImage3D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not a type constant.");
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if format is not an accepted format constant.");
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, 0, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if internalFormat is not one of the accepted resolution and format symbolic constants.");
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, 0, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if target is GL_TEXTURE_3D and format is GL_DEPTH_COMPONENT, or GL_DEPTH_STENCIL.");
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the combination of internalFormat, format and type is invalid.");
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB5_A1, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB10_A2, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_INT_2_10_10_10_REV, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA32UI, 1, 1, 1, 0, GL_RGBA_INTEGER, GL_INT, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void teximage3d_neg_level (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glTexImage3D(GL_TEXTURE_3D, -1, GL_RGB, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, -1, GL_RGB, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void teximage3d_max_level (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_3D_TEXTURE_SIZE).");
+	deUint32 log2Max3DTextureSize = deLog2Floor32(ctx.getInteger(GL_MAX_3D_TEXTURE_SIZE)) + 1;
+	ctx.glTexImage3D(GL_TEXTURE_3D, log2Max3DTextureSize, GL_RGB, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+	deUint32 log2MaxTextureSize = deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, log2MaxTextureSize, GL_RGB, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void teximage3d_neg_width_height_depth (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height is less than 0.");
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, -1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, -1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, -1, -1, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, -1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 1, -1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 1, 1, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, -1, -1, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void teximage3d_max_width_height_depth (NegativeTestContext& ctx)
+{
+	int max3DTextureSize	= ctx.getInteger(GL_MAX_3D_TEXTURE_SIZE) + 1;
+	int maxTextureSize		= ctx.getInteger(GL_MAX_TEXTURE_SIZE) + 1;
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width, height or depth is greater than GL_MAX_3D_TEXTURE_SIZE.");
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, max3DTextureSize, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, max3DTextureSize, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, max3DTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, max3DTextureSize, max3DTextureSize, max3DTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width, height or depth is greater than GL_MAX_TEXTURE_SIZE.");
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, maxTextureSize, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 1, maxTextureSize, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 1, 1, maxTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, maxTextureSize, maxTextureSize, maxTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void teximage3d_invalid_border (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if border is not 0 or 1.");
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, 1, 1, 1, -1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, 1, 1, 1, 2, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGB, 1, 1, 1, -1, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGB, 1, 1, 1, 2, GL_RGB, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void teximage3d_invalid_buffer_target (NegativeTestContext& ctx)
+{
+	deUint32				buf = 0x1234;
+	deUint32				texture = 0x1234;
+	std::vector<GLubyte>	data(512);
+
+	ctx.glGenBuffers			(1, &buf);
+	ctx.glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+	ctx.glBufferData			(GL_PIXEL_UNPACK_BUFFER, 512, &data[0], GL_DYNAMIC_COPY);
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_3D, texture);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+
+	ctx.beginSection("...the buffer object's data store is currently mapped.");
+	ctx.glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 128, GL_MAP_WRITE_BIT);
+	ctx.glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+	ctx.endSection();
+
+	ctx.beginSection("...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+	ctx.glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGBA, 64, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("...data is not evenly divisible into the number of bytes needed to store in memory a datum indicated by type.");
+	ctx.getLog() << TestLog::Message << "// Set byte offset = 3" << TestLog::EndMessage;
+	ctx.glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGB5_A1, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (const GLvoid*)3);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.endSection();
+
+	ctx.glDeleteBuffers			(1, &buf);
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+// glTexSubImage3D
+
+void texsubimage3d (NegativeTestContext& ctx)
+{
+	deUint32			texture = 0x1234;
+	ctx.glGenTextures		(1, &texture);
+	ctx.glBindTexture		(GL_TEXTURE_3D, texture);
+	ctx.glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glTexSubImage3D(0, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glTexSubImage3D(GL_TEXTURE_2D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if format is not an accepted format constant.");
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 4, 4, 4, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not a type constant.");
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGB, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the combination of internalFormat of the previously specified texture array, format and type is not valid.");
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGB, GL_UNSIGNED_SHORT_5_5_5_1, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA_INTEGER, GL_UNSIGNED_INT, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGB, GL_FLOAT, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures	(1, &texture);
+}
+
+void texsubimage3d_neg_level (NegativeTestContext& ctx)
+{
+	deUint32			textures[2];
+	ctx.glGenTextures		(2, &textures[0]);
+	ctx.glBindTexture		(GL_TEXTURE_3D, textures[0]);
+	ctx.glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture		(GL_TEXTURE_2D_ARRAY, textures[1]);
+	ctx.glTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, -1, 0, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_2D_ARRAY, -1, 0, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures	(2, &textures[0]);
+}
+
+void texsubimage3d_max_level (NegativeTestContext& ctx)
+{
+	deUint32			textures[2];
+	ctx.glGenTextures		(2, &textures[0]);
+	ctx.glBindTexture		(GL_TEXTURE_3D, textures[0]);
+	ctx.glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture		(GL_TEXTURE_2D_ARRAY, textures[1]);
+	ctx.glTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError			(GL_NO_ERROR);
+
+	deUint32 log2Max3DTextureSize	= deLog2Floor32(ctx.getInteger(GL_MAX_3D_TEXTURE_SIZE)) + 1;
+	deUint32 log2MaxTextureSize		= deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_3D_TEXTURE_SIZE).");
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, log2Max3DTextureSize, 0, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+	ctx.glTexSubImage3D(GL_TEXTURE_2D_ARRAY, log2MaxTextureSize, 0, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures	(2, &textures[0]);
+}
+
+void texsubimage3d_neg_offset (NegativeTestContext& ctx)
+{
+	deUint32			textures[2];
+	ctx.glGenTextures		(2, &textures[0]);
+	ctx.glBindTexture		(GL_TEXTURE_3D, textures[0]);
+	ctx.glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture		(GL_TEXTURE_2D_ARRAY, textures[1]);
+	ctx.glTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if xoffset, yoffset or zoffset are negative.");
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, -1, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, -1, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, -1, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, -1, -1, -1, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, -1, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, -1, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, -1, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, -1, -1, -1, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures	(2, &textures[0]);
+}
+
+void texsubimage3d_invalid_offset (NegativeTestContext& ctx)
+{
+	deUint32			texture = 0x1234;
+	ctx.glGenTextures		(1, &texture);
+	ctx.glBindTexture		(GL_TEXTURE_3D, texture);
+	ctx.glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if xoffset + width > texture_width.");
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 2, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if yoffset + height > texture_height.");
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 2, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if zoffset + depth > texture_depth.");
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures	(1, &texture);
+}
+
+void texsubimage3d_neg_width_height (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if width, height or depth is less than 0.");
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, -1, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, -1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, -1, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, -1, -1, -1, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void texsubimage3d_invalid_buffer_target (NegativeTestContext& ctx)
+{
+	deUint32				buf = 0x1234;
+	deUint32				texture = 0x1234;
+	std::vector<GLubyte>	data(512);
+
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_3D, texture);
+	ctx.glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGBA, 16, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glGenBuffers			(1, &buf);
+	ctx.glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+	ctx.glBufferData			(GL_PIXEL_UNPACK_BUFFER, 512, &data[0], GL_DYNAMIC_COPY);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+
+	ctx.beginSection("...the buffer object's data store is currently mapped.");
+	ctx.glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 512, GL_MAP_WRITE_BIT);
+	ctx.glTexSubImage3D			(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+	ctx.endSection();
+
+	ctx.beginSection("...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+	ctx.glTexSubImage3D			(GL_TEXTURE_3D, 0, 0, 0, 0, 16, 16, 16, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("...data is not evenly divisible into the number of bytes needed to store in memory a datum indicated by type.");
+	ctx.getLog() << TestLog::Message << "// Set byte offset = 3" << TestLog::EndMessage;
+	ctx.glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, 0);
+	ctx.glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGBA4, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+	ctx.glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.glTexSubImage3D			(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (const GLvoid*)3);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.endSection();
+
+	ctx.glDeleteBuffers			(1, &buf);
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+// glCopyTexSubImage3D
+
+void copytexsubimage3d (NegativeTestContext& ctx)
+{
+	GLuint texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_3D, texture);
+	ctx.glTexImage3D	(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glCopyTexSubImage3D(0, 0, 0, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void copytexsubimage3d_neg_level (NegativeTestContext& ctx)
+{
+	deUint32			textures[2];
+	ctx.glGenTextures		(2, &textures[0]);
+	ctx.glBindTexture		(GL_TEXTURE_3D, textures[0]);
+	ctx.glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture		(GL_TEXTURE_2D_ARRAY, textures[1]);
+	ctx.glTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, -1, 0, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, -1, 0, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(2, &textures[0]);
+}
+
+void copytexsubimage3d_max_level (NegativeTestContext& ctx)
+{
+	deUint32	log2Max3DTextureSize	= deLog2Floor32(ctx.getInteger(GL_MAX_3D_TEXTURE_SIZE)) + 1;
+	deUint32	log2MaxTextureSize		= deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+
+	deUint32			textures[2];
+	ctx.glGenTextures		(2, &textures[0]);
+	ctx.glBindTexture		(GL_TEXTURE_3D, textures[0]);
+	ctx.glTexImage3D		(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture		(GL_TEXTURE_2D_ARRAY, textures[1]);
+	ctx.glTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.expectError			(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_3D_TEXTURE_SIZE).");
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, log2Max3DTextureSize, 0, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, log2MaxTextureSize, 0, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(2, &textures[0]);
+}
+
+void copytexsubimage3d_neg_offset (NegativeTestContext& ctx)
+{
+	GLuint texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_3D, texture);
+	ctx.glTexImage3D	(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if xoffset, yoffset or zoffset is negative.");
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, 0, -1, 0,  0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, -1, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, -1, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, 0, -1, -1, -1, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void copytexsubimage3d_invalid_offset (NegativeTestContext& ctx)
+{
+	GLuint texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_3D, texture);
+	ctx.glTexImage3D	(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if xoffset + width > texture_width.");
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 1, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if yoffset + height > texture_height.");
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 1, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if zoffset + 1 > texture_depth.");
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 4, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void copytexsubimage3d_neg_width_height (NegativeTestContext& ctx)
+{
+	GLuint texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_3D, texture);
+	ctx.glTexImage3D	(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width < 0.");
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, -4, 4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if height < 0.");
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 4, -4);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void copytexsubimage3d_incomplete_framebuffer (NegativeTestContext& ctx)
+{
+	GLuint fbo = 0x1234;
+	GLuint texture[2];
+
+	ctx.glGenTextures			(2, texture);
+	ctx.glBindTexture			(GL_TEXTURE_3D, texture[0]);
+	ctx.glTexImage3D			(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glBindTexture			(GL_TEXTURE_2D_ARRAY, texture[1]);
+	ctx.glTexImage3D			(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+	ctx.glGenFramebuffers		(1, &fbo);
+	ctx.glBindFramebuffer		(GL_READ_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, 4, 4);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.endSection();
+
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.glDeleteTextures(2, texture);
+}
+
+// glCompressedTexImage3D
+
+void compressedteximage3d (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glCompressedTexImage3D(0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if internalformat is not one of the specific compressed internal formats.");
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+}
+
+void compressedteximage3d_neg_level (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, -1, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void compressedteximage3d_max_level (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+	deUint32 log2MaxTextureSize = deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, log2MaxTextureSize, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void compressedteximage3d_neg_width_height_depth (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if width, height or depth is less than 0.");
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, -1, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, -1, -1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void compressedteximage3d_max_width_height_depth (NegativeTestContext& ctx)
+{
+	int maxTextureSize = ctx.getInteger(GL_MAX_TEXTURE_SIZE) + 1;
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width, height or depth is greater than GL_MAX_TEXTURE_SIZE.");
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxTextureSize, 0, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, maxTextureSize, 0, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, maxTextureSize, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, maxTextureSize, maxTextureSize, maxTextureSize, 0, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void compressedteximage3d_invalid_border (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if border is not 0.");
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, -1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 1, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void compressedteximage3d_invalid_size (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if imageSize is not consistent with the format, dimensions, and contents of the specified compressed image data.");
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0, 0, 0, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, 4*4*8, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGB8_ETC2, 16, 16, 1, 0, 4*4*16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_SIGNED_R11_EAC, 16, 16, 1, 0, 4*4*16, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void compressedteximage3d_invalid_buffer_target (NegativeTestContext& ctx)
+{
+	deUint32				buf = 0x1234;
+	std::vector<GLubyte>	data(512);
+
+	ctx.glGenBuffers			(1, &buf);
+	ctx.glBindBuffer			(GL_PIXEL_UNPACK_BUFFER, buf);
+	ctx.glBufferData			(GL_PIXEL_UNPACK_BUFFER, 64, &data[0], GL_DYNAMIC_COPY);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and the buffer object's data store is currently mapped.");
+	ctx.glMapBufferRange		(GL_PIXEL_UNPACK_BUFFER, 0, 64, GL_MAP_WRITE_BIT);
+	ctx.glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGB8_ETC2, 4, 4, 1, 0, etc2DataSize(4, 4), 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer			(GL_PIXEL_UNPACK_BUFFER);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+	ctx.glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGB8_ETC2, 16, 16, 1, 0, etc2DataSize(16, 16), 0);
+	ctx.expectError				(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteBuffers			(1, &buf);
+}
+
+// glCompressedTexSubImage3D
+
+void compressedtexsubimage3d (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is invalid.");
+	ctx.glCompressedTexSubImage3D(0, 0, 0, 0, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+	ctx.glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 18, 18, 1, 0, etc2EacDataSize(18, 18), 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if format does not match the internal format of the texture image being modified.");
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, 0, GL_COMPRESSED_RGB8_ETC2, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if internalformat is an ETC2/EAC format and target is not GL_TEXTURE_2D_ARRAY.");
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 18, 18, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(18, 18), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("For ETC2/EAC images GL_INVALID_OPERATION is generated if width is not a multiple of four, and width + xoffset is not equal to the width of the texture level.");
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 4, 0, 0, 10, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(10, 4), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("For ETC2/EAC images GL_INVALID_OPERATION is generated if height is not a multiple of four, and height + yoffset is not equal to the height of the texture level.");
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 4, 0, 4, 10, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 10), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.beginSection("For ETC2/EAC images GL_INVALID_OPERATION is generated if xoffset or yoffset is not a multiple of four.");
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 1, 0, 0, 4, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 1, 0, 4, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 1, 1, 0, 4, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+void compressedtexsubimage3d_neg_level (NegativeTestContext& ctx)
+{
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+	ctx.glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is less than 0.");
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, -1, 0, 0, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+void compressedtexsubimage3d_max_level (NegativeTestContext& ctx)
+{
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+	ctx.glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if level is greater than log_2(GL_MAX_TEXTURE_SIZE).");
+	deUint32 log2MaxTextureSize = deLog2Floor32(ctx.getInteger(GL_MAX_TEXTURE_SIZE)) + 1;
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, log2MaxTextureSize, 0, 0, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+void compressedtexsubimage3d_neg_offset (NegativeTestContext& ctx)
+{
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+	ctx.glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if xoffset, yoffset or zoffset are negative.");
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, -4, 0, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, -4, 0, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, -4, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, -4, -4, -4, 0, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+void compressedtexsubimage3d_invalid_offset (NegativeTestContext& ctx)
+{
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+	ctx.glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 4, 4, 1, 0, etc2EacDataSize(4, 4), 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if xoffset + width > texture_width or yoffset + height > texture_height.");
+
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 12, 0, 0, 8, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(8, 4), 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 12, 0, 4, 8, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 8), 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 12, 4, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 12, 12, 12, 8, 8, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(8, 8), 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+void compressedtexsubimage3d_neg_width_height_depth (NegativeTestContext& ctx)
+{
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+	ctx.glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, etc2EacDataSize(16, 16), 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE or GL_INVALID_OPERATION is generated if width, height or depth are negative.");
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, -4, 0, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, -4, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, -4, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, -4, -4, -4, GL_COMPRESSED_RGBA8_ETC2_EAC, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE, GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+void compressedtexsubimage3d_invalid_size (NegativeTestContext& ctx)
+{
+	deUint32				texture = 0x1234;
+	ctx.glGenTextures			(1, &texture);
+	ctx.glBindTexture			(GL_TEXTURE_2D_ARRAY, texture);
+	ctx.glCompressedTexImage3D	(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, 4*4*16, 0);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if imageSize is not consistent with the format, dimensions, and contents of the specified compressed image data.");
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 16, 16, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+
+	ctx.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 16, 16, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, 4*4*16-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+void compressedtexsubimage3d_invalid_buffer_target (NegativeTestContext& ctx)
+{
+	deUint32					buf = 0x1234;
+	deUint32					texture = 0x1234;
+	std::vector<GLubyte>		data(512);
+
+	ctx.glGenTextures				(1, &texture);
+	ctx.glBindTexture				(GL_TEXTURE_2D_ARRAY, texture);
+	ctx.glCompressedTexImage3D		(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 16, 16, 1, 0, etc2EacDataSize(16, 16), 0);
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glBindBuffer				(GL_PIXEL_UNPACK_BUFFER, buf);
+	ctx.glBufferData				(GL_PIXEL_UNPACK_BUFFER, 512, &data[0], GL_DYNAMIC_COPY);
+	ctx.expectError					(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and...");
+	ctx.beginSection("...the buffer object's data store is currently mapped.");
+	ctx.glMapBufferRange			(GL_PIXEL_UNPACK_BUFFER, 0, 512, GL_MAP_WRITE_BIT);
+	ctx.glCompressedTexSubImage3D	(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 4, 4, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(4, 4), 0);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.glUnmapBuffer				(GL_PIXEL_UNPACK_BUFFER);
+	ctx.endSection();
+
+	ctx.beginSection("...the data would be unpacked from the buffer object such that the memory reads required would exceed the data store size.");
+	ctx.glCompressedTexSubImage3D	(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 32, 32, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, etc2EacDataSize(32, 32), 0);
+	ctx.expectError					(GL_INVALID_OPERATION);
+	ctx.endSection();
+	ctx.endSection();
+
+	ctx.glDeleteBuffers			(1, &buf);
+	ctx.glDeleteTextures		(1, &texture);
+}
+
+// glTexStorage2D
+
+void texstorage2d (NegativeTestContext& ctx)
+{
+	deUint32  texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_2D, texture);
+
+	ctx.beginSection("GL_INVALID_ENUM or GL_INVALID_VALUE is generated if internalformat is not a valid sized internal format.");
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, 1, 0, 16, 16);
+	ctx.expectError		(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA_INTEGER, 16, 16);
+	ctx.expectError		(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.");
+	ctx.glTexStorage2D	(0, 1, GL_RGBA8, 16, 16);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.glTexStorage2D	(GL_TEXTURE_3D, 1, GL_RGBA8, 16, 16);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.glTexStorage2D	(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 16, 16);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width or height are less than 1.");
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 0, 16);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 16, 0);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 0, 0);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void texstorage2d_invalid_binding (NegativeTestContext& ctx)
+{
+	ctx.glBindTexture	(GL_TEXTURE_2D, 0);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the default texture object is curently bound to target.");
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 16, 16);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	deUint32		texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_2D, texture);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the texture object currently bound to target already has GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE.");
+	deInt32			immutable = 0x1234;
+	ctx.glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable);
+	ctx.getLog() << TestLog::Message << "// GL_TEXTURE_IMMUTABLE_FORMAT = " << ((immutable != 0) ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 16, 16);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable);
+	ctx.getLog() << TestLog::Message << "// GL_TEXTURE_IMMUTABLE_FORMAT = " << ((immutable != 0) ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, 1, GL_RGBA8, 16, 16);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void texstorage2d_invalid_levels (NegativeTestContext& ctx)
+{
+	deUint32  texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_2D, texture);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if levels is less than 1.");
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, 0, GL_RGBA8, 16, 16);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if levels is greater than floor(log_2(max(width, height))) + 1");
+	deUint32 log2MaxSize = deLog2Floor32(deMax32(16, 4)) + 1 + 1;
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, log2MaxSize, GL_RGBA8, 16, 4);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, log2MaxSize, GL_RGBA8, 4, 16);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.glTexStorage2D	(GL_TEXTURE_2D, log2MaxSize, GL_RGBA8, 16, 16);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+// glTexStorage3D
+
+void texstorage3d (NegativeTestContext& ctx)
+{
+	deUint32 texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_3D, texture);
+
+	ctx.beginSection("GL_INVALID_ENUM or GL_INVALID_VALUE is generated if internalformat is not a valid sized internal format.");
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 1, 0, 4, 4, 4);
+	ctx.expectError		(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA_INTEGER, 4, 4, 4);
+	ctx.expectError		(GL_INVALID_ENUM, GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.");
+	ctx.glTexStorage3D	(0, 1, GL_RGBA8, 4, 4, 4);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.glTexStorage3D	(GL_TEXTURE_CUBE_MAP, 1, GL_RGBA8, 4, 4, 4);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.glTexStorage3D	(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4, 4);
+	ctx.expectError		(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if width, height or depth are less than 1.");
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 0, 4, 4);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 4, 0, 4);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 4, 4, 0);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 0, 0, 0);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void texstorage3d_invalid_binding (NegativeTestContext& ctx)
+{
+	ctx.glBindTexture	(GL_TEXTURE_3D, 0);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the default texture object is curently bound to target.");
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 4, 4, 4);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	deUint32		texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_3D, texture);
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if the texture object currently bound to target already has GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE.");
+	deInt32			immutable = 0x1234;
+	ctx.glGetTexParameteriv(GL_TEXTURE_3D, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable);
+	ctx.getLog() << TestLog::Message << "// GL_TEXTURE_IMMUTABLE_FORMAT = " << ((immutable != 0) ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 4, 4, 4);
+	ctx.expectError		(GL_NO_ERROR);
+	ctx.glGetTexParameteriv(GL_TEXTURE_3D, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable);
+	ctx.getLog() << TestLog::Message << "// GL_TEXTURE_IMMUTABLE_FORMAT = " << ((immutable != 0) ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 1, GL_RGBA8, 4, 4, 4);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+void texstorage3d_invalid_levels (NegativeTestContext& ctx)
+{
+	deUint32  texture = 0x1234;
+	ctx.glGenTextures	(1, &texture);
+	ctx.glBindTexture	(GL_TEXTURE_3D, texture);
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if levels is less than 1.");
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 0, GL_RGBA8, 4, 4, 4);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, 0, GL_RGBA8, 0, 0, 0);
+	ctx.expectError		(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if levels is greater than floor(log_2(max(width, height, depth))) + 1");
+	deUint32 log2MaxSize = deLog2Floor32(8) + 1 + 1;
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, log2MaxSize, GL_RGBA8, 8, 2, 2);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, log2MaxSize, GL_RGBA8, 2, 8, 2);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, log2MaxSize, GL_RGBA8, 2, 2, 8);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.glTexStorage3D	(GL_TEXTURE_3D, log2MaxSize, GL_RGBA8, 8, 8, 8);
+	ctx.expectError		(GL_INVALID_OPERATION);
+	ctx.endSection();
+
+	ctx.glDeleteTextures(1, &texture);
+}
+
+std::vector<FunctionContainer> getNegativeTextureApiTestFunctions()
+{
+	FunctionContainer funcs[] =
+	{
+		{activetexture,									"activetexture",									"Invalid glActiveTexture() usage"		   },
+		{bindtexture,									"bindtexture",										"Invalid glBindTexture() usage"			   },
+		{compressedteximage2d_invalid_target,			"compressedteximage2d_invalid_target",				"Invalid glCompressedTexImage2D() usage"   },
+		{compressedteximage2d_invalid_format,			"compressedteximage2d_invalid_format",				"Invalid glCompressedTexImage2D() usage"   },
+		{compressedteximage2d_neg_level,				"compressedteximage2d_neg_level",					"Invalid glCompressedTexImage2D() usage"   },
+		{compressedteximage2d_max_level,				"compressedteximage2d_max_level",					"Invalid glCompressedTexImage2D() usage"   },
+		{compressedteximage2d_neg_width_height,			"compressedteximage2d_neg_width_height",			"Invalid glCompressedTexImage2D() usage"   },
+		{compressedteximage2d_max_width_height,			"compressedteximage2d_max_width_height",			"Invalid glCompressedTexImage2D() usage"   },
+		{compressedteximage2d_invalid_border,			"compressedteximage2d_invalid_border",				"Invalid glCompressedTexImage2D() usage"   },
+		{compressedteximage2d_invalid_size,				"compressedteximage2d_invalid_size",				"Invalid glCompressedTexImage2D() usage"   },
+		{compressedteximage2d_invalid_buffer_target,	"compressedteximage2d_invalid_buffer_target",		"Invalid glCompressedTexImage2D() usage"   },
+		{copyteximage2d_invalid_target,					"copyteximage2d_invalid_target",					"Invalid glCopyTexImage2D() usage"		   },
+		{copyteximage2d_invalid_format,					"copyteximage2d_invalid_format",					"Invalid glCopyTexImage2D() usage"		   },
+		{copyteximage2d_inequal_width_height_cube,		"copyteximage2d_inequal_width_height_cube",			"Invalid glCopyTexImage2D() usage"		   },
+		{copyteximage2d_neg_level,						"copyteximage2d_neg_level",							"Invalid glCopyTexImage2D() usage"		   },
+		{copyteximage2d_max_level,						"copyteximage2d_max_level",							"Invalid glCopyTexImage2D() usage"		   },
+		{copyteximage2d_neg_width_height,				"copyteximage2d_neg_width_height",					"Invalid glCopyTexImage2D() usage"		   },
+		{copyteximage2d_max_width_height,				"copyteximage2d_max_width_height",					"Invalid glCopyTexImage2D() usage"		   },
+		{copyteximage2d_invalid_border,					"copyteximage2d_invalid_border",					"Invalid glCopyTexImage2D() usage"		   },
+		{copyteximage2d_incomplete_framebuffer,			"copyteximage2d_incomplete_framebuffer",			"Invalid glCopyTexImage2D() usage"		   },
+		{copytexsubimage2d_invalid_target,				"copytexsubimage2d_invalid_target",					"Invalid glCopyTexSubImage2D() usage"	   },
+		{copytexsubimage2d_neg_level,					"copytexsubimage2d_neg_level",						"Invalid glCopyTexSubImage2D() usage"	   },
+		{copytexsubimage2d_max_level,					"copytexsubimage2d_max_level",						"Invalid glCopyTexSubImage2D() usage"	   },
+		{copytexsubimage2d_neg_offset,					"copytexsubimage2d_neg_offset",						"Invalid glCopyTexSubImage2D() usage"	   },
+		{copytexsubimage2d_invalid_offset,				"copytexsubimage2d_invalid_offset",					"Invalid glCopyTexSubImage2D() usage"	   },
+		{copytexsubimage2d_neg_width_height,			"copytexsubimage2d_neg_width_height",				"Invalid glCopyTexSubImage2D() usage"	   },
+		{copytexsubimage2d_incomplete_framebuffer,		"copytexsubimage2d_incomplete_framebuffer",			"Invalid glCopyTexSubImage2D() usage"	   },
+		{deletetextures,								"deletetextures",									"Invalid glDeleteTextures() usage"		   },
+		{generatemipmap,								"generatemipmap",									"Invalid glGenerateMipmap() usage"		   },
+		{gentextures,									"gentextures",										"Invalid glGenTextures() usage"			   },
+		{pixelstorei,									"pixelstorei",										"Invalid glPixelStorei() usage"			   },
+		{teximage2d,									"teximage2d",										"Invalid glTexImage2D() usage"			   },
+		{teximage2d_inequal_width_height_cube,			"teximage2d_inequal_width_height_cube",				"Invalid glTexImage2D() usage"			   },
+		{teximage2d_neg_level,							"teximage2d_neg_level",								"Invalid glTexImage2D() usage"			   },
+		{teximage2d_max_level,							"teximage2d_max_level",								"Invalid glTexImage2D() usage"			   },
+		{teximage2d_neg_width_height,					"teximage2d_neg_width_height",						"Invalid glTexImage2D() usage"			   },
+		{teximage2d_max_width_height,					"teximage2d_max_width_height",						"Invalid glTexImage2D() usage"			   },
+		{teximage2d_invalid_border,						"teximage2d_invalid_border",						"Invalid glTexImage2D() usage"			   },
+		{teximage2d_invalid_buffer_target,				"teximage2d_invalid_buffer_target",					"Invalid glTexImage2D() usage"			   },
+		{texsubimage2d,									"texsubimage2d",									"Invalid glTexSubImage2D() usage"		   },
+		{texsubimage2d_neg_level,						"texsubimage2d_neg_level",							"Invalid glTexSubImage2D() usage"		   },
+		{texsubimage2d_max_level,						"texsubimage2d_max_level",							"Invalid glTexSubImage2D() usage"		   },
+		{texsubimage2d_neg_offset,						"texsubimage2d_neg_offset",							"Invalid glTexSubImage2D() usage"		   },
+		{texsubimage2d_invalid_offset,					"texsubimage2d_invalid_offset",						"Invalid glTexSubImage2D() usage"		   },
+		{texsubimage2d_neg_width_height,				"texsubimage2d_neg_width_height",					"Invalid glTexSubImage2D() usage"		   },
+		{texsubimage2d_invalid_buffer_target,			"texsubimage2d_invalid_buffer_target",				"Invalid glTexSubImage2D() usage"		   },
+		{texparameteri,									"texparameteri",									"Invalid glTexParameteri() usage"		   },
+		{texparameterf,									"texparameterf",									"Invalid glTexParameterf() usage"		   },
+		{texparameteriv,								"texparameteriv",									"Invalid glTexParameteriv() usage"		   },
+		{texparameterfv,								"texparameterfv",									"Invalid glTexParameterfv() usage"		   },
+		{compressedtexsubimage2d,						"compressedtexsubimage2d",							"Invalid glCompressedTexSubImage2D() usage"},
+		{compressedtexsubimage2d_neg_level,				"compressedtexsubimage2d_neg_level",				"Invalid glCompressedTexSubImage2D() usage"},
+		{compressedtexsubimage2d_max_level,				"compressedtexsubimage2d_max_level",				"Invalid glCompressedTexSubImage2D() usage"},
+		{compressedtexsubimage2d_neg_offset,			"compressedtexsubimage2d_neg_offset",				"Invalid glCompressedTexSubImage2D() usage"},
+		{compressedtexsubimage2d_invalid_offset,		"compressedtexsubimage2d_invalid_offset",			"Invalid glCompressedTexSubImage2D() usage"},
+		{compressedtexsubimage2d_neg_width_height,		"compressedtexsubimage2d_neg_width_height",			"Invalid glCompressedTexSubImage2D() usage"},
+		{compressedtexsubimage2d_invalid_size,			"compressedtexsubimage2d_invalid_size",				"Invalid glCompressedTexImage2D() usage"   },
+		{compressedtexsubimage2d_invalid_buffer_target,	"compressedtexsubimage2d_invalid_buffer_target",	"Invalid glCompressedTexSubImage2D() usage"},
+		{teximage3d,									"teximage3d",										"Invalid glTexImage3D() usage"			   },
+		{teximage3d_neg_level,							"teximage3d_neg_level",								"Invalid glTexImage3D() usage"			   },
+		{teximage3d_max_level,							"teximage3d_max_level",								"Invalid glTexImage3D() usage"			   },
+		{teximage3d_neg_width_height_depth,				"teximage3d_neg_width_height_depth",				"Invalid glTexImage3D() usage"			   },
+		{teximage3d_max_width_height_depth,				"teximage3d_max_width_height_depth",				"Invalid glTexImage3D() usage"			   },
+		{teximage3d_invalid_border,						"teximage3d_invalid_border",						"Invalid glTexImage3D() usage"			   },
+		{teximage3d_invalid_buffer_target,				"teximage3d_invalid_buffer_target",					"Invalid glTexImage3D() usage"			   },
+		{texsubimage3d,									"texsubimage3d",									"Invalid glTexSubImage3D() usage"		   },
+		{texsubimage3d_neg_level,						"texsubimage3d_neg_level",							"Invalid glTexSubImage3D() usage"		   },
+		{texsubimage3d_max_level,						"texsubimage3d_max_level",							"Invalid glTexSubImage3D() usage"		   },
+		{texsubimage3d_neg_offset,						"texsubimage3d_neg_offset",							"Invalid glTexSubImage3D() usage"		   },
+		{texsubimage3d_invalid_offset,					"texsubimage3d_invalid_offset",						"Invalid glTexSubImage3D() usage"		   },
+		{texsubimage3d_neg_width_height,				"texsubimage3d_neg_width_height",					"Invalid glTexSubImage3D() usage"		   },
+		{texsubimage3d_invalid_buffer_target,			"texsubimage3d_invalid_buffer_target",				"Invalid glTexSubImage3D() usage"		   },
+		{copytexsubimage3d,								"copytexsubimage3d",								"Invalid glCopyTexSubImage3D() usage"	   },
+		{copytexsubimage3d_neg_level,					"copytexsubimage3d_neg_level",						"Invalid glCopyTexSubImage3D() usage"	   },
+		{copytexsubimage3d_max_level,					"copytexsubimage3d_max_level",						"Invalid glCopyTexSubImage3D() usage"	   },
+		{copytexsubimage3d_neg_offset,					"copytexsubimage3d_neg_offset",						"Invalid glCopyTexSubImage3D() usage"	   },
+		{copytexsubimage3d_invalid_offset,				"copytexsubimage3d_invalid_offset",					"Invalid glCopyTexSubImage3D() usage"	   },
+		{copytexsubimage3d_neg_width_height,			"copytexsubimage3d_neg_width_height",				"Invalid glCopyTexSubImage3D() usage"	   },
+		{copytexsubimage3d_incomplete_framebuffer,		"copytexsubimage3d_incomplete_framebuffer",			"Invalid glCopyTexSubImage3D() usage"	   },
+		{compressedteximage3d,							"compressedteximage3d",								"Invalid glCompressedTexImage3D() usage"   },
+		{compressedteximage3d_neg_level,				"compressedteximage3d_neg_level",					"Invalid glCompressedTexImage3D() usage"   },
+		{compressedteximage3d_max_level,				"compressedteximage3d_max_level",					"Invalid glCompressedTexImage3D() usage"   },
+		{compressedteximage3d_neg_width_height_depth,	"compressedteximage3d_neg_width_height_depth",		"Invalid glCompressedTexImage3D() usage"   },
+		{compressedteximage3d_max_width_height_depth,	"compressedteximage3d_max_width_height_depth",		"Invalid glCompressedTexImage3D() usage"   },
+		{compressedteximage3d_invalid_border,			"compressedteximage3d_invalid_border",				"Invalid glCompressedTexImage3D() usage"   },
+		{compressedteximage3d_invalid_size,				"compressedteximage3d_invalid_size",				"Invalid glCompressedTexImage3D() usage"   },
+		{compressedteximage3d_invalid_buffer_target,	"compressedteximage3d_invalid_buffer_target",		"Invalid glCompressedTexImage3D() usage"   },
+		{compressedtexsubimage3d,						"compressedtexsubimage3d",							"Invalid glCompressedTexSubImage3D() usage"},
+		{compressedtexsubimage3d_neg_level,				"compressedtexsubimage3d_neg_level",				"Invalid glCompressedTexSubImage3D() usage"},
+		{compressedtexsubimage3d_max_level,				"compressedtexsubimage3d_max_level",				"Invalid glCompressedTexSubImage3D() usage"},
+		{compressedtexsubimage3d_neg_offset,			"compressedtexsubimage3d_neg_offset",				"Invalid glCompressedTexSubImage3D() usage"},
+		{compressedtexsubimage3d_invalid_offset,		"compressedtexsubimage3d_invalid_offset",			"Invalid glCompressedTexSubImage3D() usage"},
+		{compressedtexsubimage3d_neg_width_height_depth,"compressedtexsubimage3d_neg_width_height_depth",	"Invalid glCompressedTexSubImage3D() usage"},
+		{compressedtexsubimage3d_invalid_size,			"compressedtexsubimage3d_invalid_size",				"Invalid glCompressedTexSubImage3D() usage"},
+		{compressedtexsubimage3d_invalid_buffer_target,	"compressedtexsubimage3d_invalid_buffer_target",	"Invalid glCompressedTexSubImage3D() usage"},
+		{texstorage2d,									"texstorage2d",										"Invalid glTexStorage2D() usage"		   },
+		{texstorage2d_invalid_binding,					"texstorage2d_invalid_binding",						"Invalid glTexStorage2D() usage"		   },
+		{texstorage2d_invalid_levels,					"texstorage2d_invalid_levels",						"Invalid glTexStorage2D() usage"		   },
+		{texstorage3d,									"texstorage3d",										"Invalid glTexStorage3D() usage"		   },
+		{texstorage3d_invalid_binding,					"texstorage3d_invalid_binding",						"Invalid glTexStorage3D() usage"		   },
+		{texstorage3d_invalid_levels,					"texstorage3d_invalid_levels",						"Invalid glTexStorage3D() usage"		   },
+	};
+
+	return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
+}
+
+} // NegativeTestShared
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fNegativeTextureApiTests.hpp b/modules/gles31/functional/es31fNegativeTextureApiTests.hpp
new file mode 100644
index 0000000..82c1a36
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeTextureApiTests.hpp
@@ -0,0 +1,45 @@
+#ifndef _ES31FNEGATIVETEXTUREAPITESTS_HPP
+#define _ES31FNEGATIVETEXTUREAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Texture API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "es31fNegativeTestShared.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+std::vector<FunctionContainer> getNegativeTextureApiTestFunctions ();
+
+} // NegativeTestShared
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES31FNEGATIVETEXTUREAPITESTS_HPP
diff --git a/modules/gles31/functional/es31fNegativeVertexArrayApiTests.cpp b/modules/gles31/functional/es31fNegativeVertexArrayApiTests.cpp
new file mode 100644
index 0000000..35c401b
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeVertexArrayApiTests.cpp
@@ -0,0 +1,1020 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Vertex Array API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fNegativeVertexArrayApiTests.hpp"
+
+#include "gluCallLogWrapper.hpp"
+#include "gluShaderProgram.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+using tcu::TestLog;
+using glu::CallLogWrapper;
+using namespace glw;
+
+static const char* vertexShaderSource		=	"#version 300 es\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = vec4(0.0);\n"
+												"}\n\0";
+
+static const char* fragmentShaderSource		=	"#version 300 es\n"
+												"layout(location = 0) out mediump vec4 fragColor;"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(0.0);\n"
+												"}\n\0";
+
+void vertex_attribf (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	int maxVertexAttribs = ctx.getInteger(GL_MAX_VERTEX_ATTRIBS);
+	ctx.glVertexAttrib1f(maxVertexAttribs, 0.0f);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glVertexAttrib2f(maxVertexAttribs, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glVertexAttrib3f(maxVertexAttribs, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glVertexAttrib4f(maxVertexAttribs, 0.0f, 0.0f, 0.0f, 0.0f);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void vertex_attribfv (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	int maxVertexAttribs = ctx.getInteger(GL_MAX_VERTEX_ATTRIBS);
+	float v[4] = {0.0f};
+	ctx.glVertexAttrib1fv(maxVertexAttribs, &v[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glVertexAttrib2fv(maxVertexAttribs, &v[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glVertexAttrib3fv(maxVertexAttribs, &v[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glVertexAttrib4fv(maxVertexAttribs, &v[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void vertex_attribi4 (NegativeTestContext& ctx)
+{
+	int maxVertexAttribs	= ctx.getInteger(GL_MAX_VERTEX_ATTRIBS);
+	GLint valInt			= 0;
+	GLuint valUint			= 0;
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	ctx.glVertexAttribI4i(maxVertexAttribs, valInt, valInt, valInt, valInt);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glVertexAttribI4ui(maxVertexAttribs, valUint, valUint, valUint, valUint);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void vertex_attribi4v (NegativeTestContext& ctx)
+{
+	int maxVertexAttribs	= ctx.getInteger(GL_MAX_VERTEX_ATTRIBS);
+	GLint valInt[4]			= { 0 };
+	GLuint valUint[4]		= { 0 };
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	ctx.glVertexAttribI4iv(maxVertexAttribs, &valInt[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glVertexAttribI4uiv(maxVertexAttribs, &valUint[0]);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void vertex_attrib_pointer (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not an accepted value.");
+	ctx.glVertexAttribPointer(0, 1, 0, GL_TRUE, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	int maxVertexAttribs = ctx.getInteger(GL_MAX_VERTEX_ATTRIBS);
+	ctx.glVertexAttribPointer(maxVertexAttribs, 1, GL_BYTE, GL_TRUE, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if size is not 1, 2, 3, or 4.");
+	ctx.glVertexAttribPointer(0, 0, GL_BYTE, GL_TRUE, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if stride is negative.");
+	ctx.glVertexAttribPointer(0, 1, GL_BYTE, GL_TRUE, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if type is GL_INT_2_10_10_10_REV or GL_UNSIGNED_INT_2_10_10_10_REV and size is not 4.");
+	ctx.glVertexAttribPointer(0, 2, GL_INT_2_10_10_10_REV, GL_TRUE, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glVertexAttribPointer(0, 2, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, 0, 0);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.glVertexAttribPointer(0, 4, GL_INT_2_10_10_10_REV, GL_TRUE, 0, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.glVertexAttribPointer(0, 4, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, 0, 0);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated a non-zero vertex array object is bound, zero is bound to the GL_ARRAY_BUFFER buffer object binding point and the pointer argument is not NULL.");
+	GLuint vao = 0;
+	GLbyte offset = 1;
+	ctx.glGenVertexArrays(1, &vao);
+	ctx.glBindVertexArray(vao);
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, 0);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.glVertexAttribPointer(0, 1, GL_BYTE, GL_TRUE, 0, &offset);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glBindVertexArray(0);
+	ctx.glDeleteVertexArrays(1, &vao);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+}
+
+void vertex_attrib_i_pointer (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not an accepted value.");
+	ctx.glVertexAttribIPointer(0, 1, 0, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glVertexAttribIPointer(0, 4, GL_INT_2_10_10_10_REV, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glVertexAttribIPointer(0, 4, GL_UNSIGNED_INT_2_10_10_10_REV, 0, 0);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	int maxVertexAttribs = ctx.getInteger(GL_MAX_VERTEX_ATTRIBS);
+	ctx.glVertexAttribIPointer(maxVertexAttribs, 1, GL_BYTE, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if size is not 1, 2, 3, or 4.");
+	ctx.glVertexAttribIPointer(0, 0, GL_BYTE, 0, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if stride is negative.");
+	ctx.glVertexAttribIPointer(0, 1, GL_BYTE, -1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated a non-zero vertex array object is bound, zero is bound to the GL_ARRAY_BUFFER buffer object binding point and the pointer argument is not NULL.");
+	GLuint vao = 0;
+	GLbyte offset = 1;
+	ctx.glGenVertexArrays(1, &vao);
+	ctx.glBindVertexArray(vao);
+	ctx.glBindBuffer(GL_ARRAY_BUFFER, 0);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.glVertexAttribIPointer(0, 1, GL_BYTE, 0, &offset);
+	ctx.expectError(GL_INVALID_OPERATION);
+
+	ctx.glBindVertexArray(0);
+	ctx.glDeleteVertexArrays(1, &vao);
+	ctx.expectError(GL_NO_ERROR);
+	ctx.endSection();
+}
+
+void enable_vertex_attrib_array (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	int maxVertexAttribs = ctx.getInteger(GL_MAX_VERTEX_ATTRIBS);
+	ctx.glEnableVertexAttribArray(maxVertexAttribs);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void disable_vertex_attrib_array (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	int maxVertexAttribs = ctx.getInteger(GL_MAX_VERTEX_ATTRIBS);
+	ctx.glDisableVertexAttribArray(maxVertexAttribs);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void gen_vertex_arrays (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	GLuint arrays = 0;
+	ctx.glGenVertexArrays(-1, &arrays);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void bind_vertex_array (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_OPERATION is generated if array is not zero or the name of an existing vertex array object.");
+	ctx.glBindVertexArray(-1);
+	ctx.expectError(GL_INVALID_OPERATION);
+	ctx.endSection();
+}
+
+void delete_vertex_arrays (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if n is negative.");
+	ctx.glDeleteVertexArrays(-1, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void vertex_attrib_divisor (NegativeTestContext& ctx)
+{
+	ctx.beginSection("GL_INVALID_VALUE is generated if index is greater than or equal to GL_MAX_VERTEX_ATTRIBS.");
+	int maxVertexAttribs = ctx.getInteger(GL_MAX_VERTEX_ATTRIBS);
+	ctx.glVertexAttribDivisor(maxVertexAttribs, 0);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+}
+
+void draw_arrays (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	ctx.glUseProgram(program.getProgram());
+	GLuint fbo = 0;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawArrays(-1, 0, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count is negative.");
+	ctx.glDrawArrays(GL_POINTS, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawArrays(GL_POINTS, 0, 1);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void draw_arrays_invalid_program (NegativeTestContext& ctx)
+{
+	ctx.glUseProgram(0);
+	GLuint fbo = 0;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawArrays(-1, 0, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count is negative.");
+	ctx.glDrawArrays(GL_POINTS, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawArrays(GL_POINTS, 0, 1);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+}
+
+void draw_arrays_incomplete_primitive (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	ctx.glUseProgram(program.getProgram());
+	GLuint fbo = 0;
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawArrays(-1, 0, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count is negative.");
+	ctx.glDrawArrays(GL_TRIANGLES, 0, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawArrays(GL_TRIANGLES, 0, 1);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void draw_elements (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	ctx.glUseProgram(program.getProgram());
+	GLuint fbo = 0;
+	GLuint buf = 0;
+	GLuint tfID = 0;
+	GLfloat vertices[1];
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawElements(-1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+	ctx.glDrawElements(GL_POINTS, 1, -1, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glDrawElements(GL_POINTS, 1, GL_FLOAT, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count is negative.");
+	ctx.glDrawElements(GL_POINTS, -1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawElements(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(1, &tfID);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.glBeginTransformFeedback	(GL_POINTS);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glDrawElements				(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glPauseTransformFeedback();
+	ctx.glDrawElements				(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(1, &tfID);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void draw_elements_invalid_program (NegativeTestContext& ctx)
+{
+	ctx.glUseProgram(0);
+	GLuint fbo = 0;
+	GLfloat vertices[1];
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawElements(-1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+	ctx.glDrawElements(GL_POINTS, 1, -1, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glDrawElements(GL_POINTS, 1, GL_FLOAT, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count is negative.");
+	ctx.glDrawElements(GL_POINTS, -1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawElements(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+}
+
+void draw_elements_incomplete_primitive (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	ctx.glUseProgram(program.getProgram());
+	GLuint fbo = 0;
+	GLuint buf = 0;
+	GLuint tfID = 0;
+	GLfloat vertices[1];
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawElements(-1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+	ctx.glDrawElements(GL_TRIANGLES, 1, -1, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glDrawElements(GL_TRIANGLES, 1, GL_FLOAT, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count is negative.");
+	ctx.glDrawElements(GL_TRIANGLES, -1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawElements(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(1, &tfID);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glDrawElements				(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glPauseTransformFeedback();
+	ctx.glDrawElements				(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(1, &tfID);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void draw_arrays_instanced (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	ctx.glUseProgram(program.getProgram());
+	GLuint fbo = 0;
+	ctx.glVertexAttribDivisor(0, 1);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawArraysInstanced(-1, 0, 1, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count or primcount are negative.");
+	ctx.glDrawArraysInstanced(GL_POINTS, 0, -1, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glDrawArraysInstanced(GL_POINTS, 0, 1, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawArraysInstanced(GL_POINTS, 0, 1, 1);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void draw_arrays_instanced_invalid_program (NegativeTestContext& ctx)
+{
+	ctx.glUseProgram(0);
+	GLuint fbo = 0;
+	ctx.glVertexAttribDivisor(0, 1);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawArraysInstanced(-1, 0, 1, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count or primcount are negative.");
+	ctx.glDrawArraysInstanced(GL_POINTS, 0, -1, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glDrawArraysInstanced(GL_POINTS, 0, 1, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawArraysInstanced(GL_POINTS, 0, 1, 1);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+}
+
+void draw_arrays_instanced_incomplete_primitive (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	ctx.glUseProgram(program.getProgram());
+	GLuint fbo = 0;
+	ctx.glVertexAttribDivisor(0, 1);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawArraysInstanced(-1, 0, 1, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count or primcount are negative.");
+	ctx.glDrawArraysInstanced(GL_TRIANGLES, 0, -1, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glDrawArraysInstanced(GL_TRIANGLES, 0, 1, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawArraysInstanced(GL_TRIANGLES, 0, 1, 1);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void draw_elements_instanced (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	ctx.glUseProgram(program.getProgram());
+	GLuint fbo = 0;
+	GLuint buf = 0;
+	GLuint tfID = 0;
+	GLfloat vertices[1];
+	ctx.glVertexAttribDivisor(0, 1);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawElementsInstanced(-1, 1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+	ctx.glDrawElementsInstanced(GL_POINTS, 1, -1, vertices, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glDrawElementsInstanced(GL_POINTS, 1, GL_FLOAT, vertices, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count or primcount are negative.");
+	ctx.glDrawElementsInstanced(GL_POINTS, -1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glDrawElementsInstanced(GL_POINTS, 11, GL_UNSIGNED_BYTE, vertices, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawElementsInstanced(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(1, &tfID);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.glBeginTransformFeedback	(GL_POINTS);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glDrawElementsInstanced		(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glPauseTransformFeedback();
+	ctx.glDrawElementsInstanced		(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(1, &tfID);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void draw_elements_instanced_invalid_program (NegativeTestContext& ctx)
+{
+	ctx.glUseProgram(0);
+	GLuint fbo = 0;
+	GLfloat vertices[1];
+	ctx.glVertexAttribDivisor(0, 1);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawElementsInstanced(-1, 1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+	ctx.glDrawElementsInstanced(GL_POINTS, 1, -1, vertices, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glDrawElementsInstanced(GL_POINTS, 1, GL_FLOAT, vertices, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count or primcount are negative.");
+	ctx.glDrawElementsInstanced(GL_POINTS, -1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glDrawElementsInstanced(GL_POINTS, 11, GL_UNSIGNED_BYTE, vertices, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawElementsInstanced(GL_POINTS, 1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+}
+
+void draw_elements_instanced_incomplete_primitive (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	ctx.glUseProgram(program.getProgram());
+	GLuint fbo = 0;
+	GLuint buf = 0;
+	GLuint tfID = 0;
+	GLfloat vertices[1];
+	ctx.glVertexAttribDivisor(0, 1);
+	ctx.expectError(GL_NO_ERROR);
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawElementsInstanced(-1, 1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+	ctx.glDrawElementsInstanced(GL_TRIANGLES, 1, -1, vertices, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glDrawElementsInstanced(GL_TRIANGLES, 1, GL_FLOAT, vertices, 1);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count or primcount are negative.");
+	ctx.glDrawElementsInstanced(GL_TRIANGLES, -1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.glDrawElementsInstanced(GL_TRIANGLES, 11, GL_UNSIGNED_BYTE, vertices, -1);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawElementsInstanced(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(1, &tfID);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glDrawElementsInstanced		(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glPauseTransformFeedback();
+	ctx.glDrawElementsInstanced		(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, vertices, 1);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(1, &tfID);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void draw_range_elements (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	ctx.glUseProgram(program.getProgram());
+	GLuint fbo = 0;
+	GLuint buf = 0;
+	GLuint tfID = 0;
+	GLfloat vertices[1];
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawRangeElements(-1, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+	ctx.glDrawRangeElements(GL_POINTS, 0, 1, 1, -1, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glDrawRangeElements(GL_POINTS, 0, 1, 1, GL_FLOAT, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count is negative.");
+	ctx.glDrawRangeElements(GL_POINTS, 0, 1, -1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if end < start.");
+	ctx.glDrawRangeElements(GL_POINTS, 1, 0, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawRangeElements(GL_POINTS, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(1, &tfID);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.glBeginTransformFeedback	(GL_POINTS);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glDrawRangeElements			(GL_POINTS, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glPauseTransformFeedback();
+	ctx.glDrawRangeElements			(GL_POINTS, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(1, &tfID);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+void draw_range_elements_invalid_program (NegativeTestContext& ctx)
+{
+	ctx.glUseProgram(0);
+	GLuint fbo = 0;
+	GLfloat vertices[1];
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawRangeElements(-1, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+	ctx.glDrawRangeElements(GL_POINTS, 0, 1, 1, -1, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glDrawRangeElements(GL_POINTS, 0, 1, 1, GL_FLOAT, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count is negative.");
+	ctx.glDrawRangeElements(GL_POINTS, 0, 1, -1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if end < start.");
+	ctx.glDrawRangeElements(GL_POINTS, 1, 0, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawRangeElements(GL_POINTS, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+}
+
+void draw_range_elements_incomplete_primitive (NegativeTestContext& ctx)
+{
+	glu::ShaderProgram program(ctx.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+	ctx.glUseProgram(program.getProgram());
+	GLuint fbo = 0;
+	GLuint buf = 0;
+	GLuint tfID = 0;
+	GLfloat vertices[1];
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if mode is not an accepted value.");
+	ctx.glDrawRangeElements(-1, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_ENUM is generated if type is not one of the accepted values.");
+	ctx.glDrawRangeElements(GL_TRIANGLES, 0, 1, 1, -1, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.glDrawRangeElements(GL_TRIANGLES, 0, 1, 1, GL_FLOAT, vertices);
+	ctx.expectError(GL_INVALID_ENUM);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if count is negative.");
+	ctx.glDrawRangeElements(GL_TRIANGLES, 0, 1, -1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_VALUE is generated if end < start.");
+	ctx.glDrawRangeElements(GL_TRIANGLES, 1, 0, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_VALUE);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_FRAMEBUFFER_OPERATION is generated if the currently bound framebuffer is not framebuffer complete.");
+	ctx.glGenFramebuffers(1, &fbo);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+	ctx.glDrawRangeElements(GL_TRIANGLES, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError(GL_INVALID_FRAMEBUFFER_OPERATION);
+	ctx.glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	ctx.glDeleteFramebuffers(1, &fbo);
+	ctx.endSection();
+
+	ctx.beginSection("GL_INVALID_OPERATION is generated if transform feedback is active and not paused.");
+	const char* tfVarying		= "gl_Position";
+
+	ctx.glGenBuffers				(1, &buf);
+	ctx.glGenTransformFeedbacks		(1, &tfID);
+
+	ctx.glUseProgram				(program.getProgram());
+	ctx.glTransformFeedbackVaryings	(program.getProgram(), 1, &tfVarying, GL_INTERLEAVED_ATTRIBS);
+	ctx.glLinkProgram				(program.getProgram());
+	ctx.glBindTransformFeedback		(GL_TRANSFORM_FEEDBACK, tfID);
+	ctx.glBindBuffer				(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
+	ctx.glBufferData				(GL_TRANSFORM_FEEDBACK_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
+	ctx.glBindBufferBase			(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	ctx.glBeginTransformFeedback	(GL_TRIANGLES);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glDrawRangeElements			(GL_TRIANGLES, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError				(GL_INVALID_OPERATION);
+
+	ctx.glPauseTransformFeedback();
+	ctx.glDrawRangeElements			(GL_TRIANGLES, 0, 1, 1, GL_UNSIGNED_BYTE, vertices);
+	ctx.expectError				(GL_NO_ERROR);
+
+	ctx.glEndTransformFeedback		();
+	ctx.glDeleteBuffers				(1, &buf);
+	ctx.glDeleteTransformFeedbacks	(1, &tfID);
+	ctx.expectError				(GL_NO_ERROR);
+	ctx.endSection();
+
+	ctx.glUseProgram(0);
+}
+
+std::vector<FunctionContainer> getNegativeVertexArrayApiTestFunctions ()
+{
+	FunctionContainer funcs[] =
+	{
+		{vertex_attribf,								"vertex_attribf",								"Invalid glVertexAttrib{1234}f() usage"		},
+		{vertex_attribfv,								"vertex_attribfv",								"Invalid glVertexAttrib{1234}fv() usage"	},
+		{vertex_attribi4,								"vertex_attribi4",								"Invalid glVertexAttribI4{i|ui}f() usage"	},
+		{vertex_attribi4v,								"vertex_attribi4v",								"Invalid glVertexAttribI4{i|ui}fv() usage"	},
+		{vertex_attrib_pointer,							"vertex_attrib_pointer",						"Invalid glVertexAttribPointer() usage"		},
+		{vertex_attrib_i_pointer,						"vertex_attrib_i_pointer",						"Invalid glVertexAttribPointer() usage"		},
+		{enable_vertex_attrib_array,					"enable_vertex_attrib_array",					"Invalid glEnableVertexAttribArray() usage"	},
+		{disable_vertex_attrib_array,					"disable_vertex_attrib_array",					"Invalid glDisableVertexAttribArray() usage"},
+		{gen_vertex_arrays,								"gen_vertex_arrays",							"Invalid glGenVertexArrays() usage"			},
+		{bind_vertex_array,								"bind_vertex_array",							"Invalid glBindVertexArray() usage"			},
+		{delete_vertex_arrays,							"delete_vertex_arrays",							"Invalid glDeleteVertexArrays() usage"		},
+		{vertex_attrib_divisor,							"vertex_attrib_divisor",						"Invalid glVertexAttribDivisor() usage"		},
+		{draw_arrays,									"draw_arrays",									"Invalid glDrawArrays() usage"				},
+		{draw_arrays_invalid_program,					"draw_arrays_invalid_program",					"Invalid glDrawArrays() usage"				},
+		{draw_arrays_incomplete_primitive,				"draw_arrays_incomplete_primitive",				"Invalid glDrawArrays() usage"				},
+		{draw_elements,									"draw_elements",								"Invalid glDrawElements() usage"			},
+		{draw_elements_invalid_program,					"draw_elements_invalid_program",				"Invalid glDrawElements() usage"			},
+		{draw_elements_incomplete_primitive,			"draw_elements_incomplete_primitive",			"Invalid glDrawElements() usage"			},
+		{draw_arrays_instanced,							"draw_arrays_instanced",						"Invalid glDrawArraysInstanced() usage"		},
+		{draw_arrays_instanced_invalid_program,			"draw_arrays_instanced_invalid_program",		"Invalid glDrawArraysInstanced() usage"		},
+		{draw_arrays_instanced_incomplete_primitive,	"draw_arrays_instanced_incomplete_primitive",	"Invalid glDrawArraysInstanced() usage"		},
+		{draw_elements_instanced,						"draw_elements_instanced",						"Invalid glDrawElementsInstanced() usage"	},
+		{draw_elements_instanced_invalid_program,		"draw_elements_instanced_invalid_program",		"Invalid glDrawElementsInstanced() usage"	},
+		{draw_elements_instanced_incomplete_primitive,	"draw_elements_instanced_incomplete_primitive",	"Invalid glDrawElementsInstanced() usage"	},
+		{draw_range_elements,							"draw_range_elements",							"Invalid glDrawRangeElements() usage"		},
+		{draw_range_elements_invalid_program,			"draw_range_elements_invalid_program",			"Invalid glDrawRangeElements() usage"		},
+		{draw_range_elements_incomplete_primitive,		"draw_range_elements_incomplete_primitive",		"Invalid glDrawRangeElements() usage"		},
+	};
+
+	return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
+}
+
+} // NegativeTestShared
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fNegativeVertexArrayApiTests.hpp b/modules/gles31/functional/es31fNegativeVertexArrayApiTests.hpp
new file mode 100644
index 0000000..967b52d
--- /dev/null
+++ b/modules/gles31/functional/es31fNegativeVertexArrayApiTests.hpp
@@ -0,0 +1,45 @@
+#ifndef _ES31FNEGATIVEVERTEXARRAYAPITESTS_HPP
+#define _ES31FNEGATIVEVERTEXARRAYAPITESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Negative Vertex Array API tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "es31fNegativeTestShared.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace NegativeTestShared
+{
+
+std::vector<FunctionContainer> getNegativeVertexArrayApiTestFunctions ();
+
+} // NegativeTestShared
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES31FNEGATIVEVERTEXARRAYAPITESTS_HPP
diff --git a/modules/gles31/functional/es31fOpaqueTypeIndexingTests.cpp b/modules/gles31/functional/es31fOpaqueTypeIndexingTests.cpp
new file mode 100644
index 0000000..623f54a
--- /dev/null
+++ b/modules/gles31/functional/es31fOpaqueTypeIndexingTests.cpp
@@ -0,0 +1,1212 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Opaque type (sampler, buffer, atomic counter, ...) indexing tests.
+ *
+ * \todo [2014-03-05 pyry] Extend with following:
+ *  + sampler: different filtering modes, multiple sizes, incomplete textures
+ *  + SSBO: write, atomic op, unsized array .length()
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fOpaqueTypeIndexingTests.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuFormatUtil.hpp"
+#include "tcuVectorUtil.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluProgramInterfaceQuery.hpp"
+#include "gluContextInfo.hpp"
+#include "glsShaderExecUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deUniquePtr.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+
+#include <sstream>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+namespace
+{
+
+using namespace gls::ShaderExecUtil;
+using namespace glu;
+using std::string;
+using std::vector;
+using tcu::TextureFormat;
+using tcu::TestLog;
+
+typedef de::UniquePtr<ShaderExecutor> ShaderExecutorPtr;
+
+enum IndexExprType
+{
+	INDEX_EXPR_TYPE_CONST_LITERAL	= 0,
+	INDEX_EXPR_TYPE_UNIFORM,
+	INDEX_EXPR_TYPE_DYNAMIC_UNIFORM,
+
+	INDEX_EXPR_TYPE_LAST
+};
+
+enum TextureType
+{
+	TEXTURE_TYPE_1D = 0,
+	TEXTURE_TYPE_2D,
+	TEXTURE_TYPE_CUBE,
+	TEXTURE_TYPE_2D_ARRAY,
+	TEXTURE_TYPE_3D,
+
+	TEXTURE_TYPE_LAST
+};
+
+static void declareUniformIndexVars (std::ostream& str, const char* varPrefix, int numVars)
+{
+	for (int varNdx = 0; varNdx < numVars; varNdx++)
+		str << "uniform highp int " << varPrefix << varNdx << ";\n";
+}
+
+static void uploadUniformIndices (const glw::Functions& gl, deUint32 program, const char* varPrefix, int numIndices, const int* indices)
+{
+	for (int varNdx = 0; varNdx < numIndices; varNdx++)
+	{
+		const string	varName		= varPrefix + de::toString(varNdx);
+		const int		loc			= gl.getUniformLocation(program, varName.c_str());
+		TCU_CHECK_MSG(loc >= 0, ("No location assigned for uniform '" + varName + "'").c_str());
+
+		gl.uniform1i(loc, indices[varNdx]);
+	}
+}
+
+template<typename T>
+static T maxElement (const std::vector<T>& elements)
+{
+	T maxElem = elements[0];
+
+	for (size_t ndx = 1; ndx < elements.size(); ndx++)
+		maxElem = de::max(maxElem, elements[ndx]);
+
+	return maxElem;
+}
+
+static TextureType getTextureType (glu::DataType samplerType)
+{
+	switch (samplerType)
+	{
+		case glu::TYPE_SAMPLER_1D:
+		case glu::TYPE_INT_SAMPLER_1D:
+		case glu::TYPE_UINT_SAMPLER_1D:
+		case glu::TYPE_SAMPLER_1D_SHADOW:
+			return TEXTURE_TYPE_1D;
+
+		case glu::TYPE_SAMPLER_2D:
+		case glu::TYPE_INT_SAMPLER_2D:
+		case glu::TYPE_UINT_SAMPLER_2D:
+		case glu::TYPE_SAMPLER_2D_SHADOW:
+			return TEXTURE_TYPE_2D;
+
+		case glu::TYPE_SAMPLER_CUBE:
+		case glu::TYPE_INT_SAMPLER_CUBE:
+		case glu::TYPE_UINT_SAMPLER_CUBE:
+		case glu::TYPE_SAMPLER_CUBE_SHADOW:
+			return TEXTURE_TYPE_CUBE;
+
+		case glu::TYPE_SAMPLER_2D_ARRAY:
+		case glu::TYPE_INT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
+			return TEXTURE_TYPE_2D_ARRAY;
+
+		case glu::TYPE_SAMPLER_3D:
+		case glu::TYPE_INT_SAMPLER_3D:
+		case glu::TYPE_UINT_SAMPLER_3D:
+			return TEXTURE_TYPE_3D;
+
+		default:
+			throw tcu::InternalError("Invalid sampler type");
+	}
+}
+
+static bool isShadowSampler (glu::DataType samplerType)
+{
+	return samplerType == glu::TYPE_SAMPLER_1D_SHADOW		||
+		   samplerType == glu::TYPE_SAMPLER_2D_SHADOW		||
+		   samplerType == glu::TYPE_SAMPLER_2D_ARRAY_SHADOW	||
+		   samplerType == glu::TYPE_SAMPLER_CUBE_SHADOW;
+}
+
+static glu::DataType getSamplerOutputType (glu::DataType samplerType)
+{
+	switch (samplerType)
+	{
+		case glu::TYPE_SAMPLER_1D:
+		case glu::TYPE_SAMPLER_2D:
+		case glu::TYPE_SAMPLER_CUBE:
+		case glu::TYPE_SAMPLER_2D_ARRAY:
+		case glu::TYPE_SAMPLER_3D:
+			return glu::TYPE_FLOAT_VEC4;
+
+		case glu::TYPE_SAMPLER_1D_SHADOW:
+		case glu::TYPE_SAMPLER_2D_SHADOW:
+		case glu::TYPE_SAMPLER_CUBE_SHADOW:
+		case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
+			return glu::TYPE_FLOAT;
+
+		case glu::TYPE_INT_SAMPLER_1D:
+		case glu::TYPE_INT_SAMPLER_2D:
+		case glu::TYPE_INT_SAMPLER_CUBE:
+		case glu::TYPE_INT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_INT_SAMPLER_3D:
+			return glu::TYPE_INT_VEC4;
+
+		case glu::TYPE_UINT_SAMPLER_1D:
+		case glu::TYPE_UINT_SAMPLER_2D:
+		case glu::TYPE_UINT_SAMPLER_CUBE:
+		case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_UINT_SAMPLER_3D:
+			return glu::TYPE_UINT_VEC4;
+
+		default:
+			throw tcu::InternalError("Invalid sampler type");
+	}
+}
+
+static tcu::TextureFormat getSamplerTextureFormat (glu::DataType samplerType)
+{
+	const glu::DataType		outType			= getSamplerOutputType(samplerType);
+	const glu::DataType		outScalarType	= glu::getDataTypeScalarType(outType);
+
+	switch (outScalarType)
+	{
+		case glu::TYPE_FLOAT:
+			if (isShadowSampler(samplerType))
+				return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16);
+			else
+				return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);
+
+		case glu::TYPE_INT:		return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::SIGNED_INT8);
+		case glu::TYPE_UINT:	return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNSIGNED_INT8);
+
+		default:
+			throw tcu::InternalError("Invalid sampler type");
+	}
+}
+
+static glu::DataType getSamplerCoordType (glu::DataType samplerType)
+{
+	const TextureType	texType		= getTextureType(samplerType);
+	int					numCoords	= 0;
+
+	switch (texType)
+	{
+		case TEXTURE_TYPE_1D:		numCoords = 1;	break;
+		case TEXTURE_TYPE_2D:		numCoords = 2;	break;
+		case TEXTURE_TYPE_2D_ARRAY:	numCoords = 3;	break;
+		case TEXTURE_TYPE_CUBE:		numCoords = 3;	break;
+		case TEXTURE_TYPE_3D:		numCoords = 3;	break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	if (isShadowSampler(samplerType))
+		numCoords += 1;
+
+	DE_ASSERT(de::inRange(numCoords, 1, 4));
+
+	return numCoords == 1 ? glu::TYPE_FLOAT : glu::getDataTypeFloatVec(numCoords);
+}
+
+static deUint32 getGLTextureTarget (TextureType texType)
+{
+	switch (texType)
+	{
+		case TEXTURE_TYPE_1D:		return GL_TEXTURE_1D;
+		case TEXTURE_TYPE_2D:		return GL_TEXTURE_2D;
+		case TEXTURE_TYPE_2D_ARRAY:	return GL_TEXTURE_2D_ARRAY;
+		case TEXTURE_TYPE_CUBE:		return GL_TEXTURE_CUBE_MAP;
+		case TEXTURE_TYPE_3D:		return GL_TEXTURE_3D;
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static void setupTexture (const glw::Functions&	gl,
+						  deUint32				texture,
+						  glu::DataType			samplerType,
+						  tcu::TextureFormat	texFormat,
+						  const void*			color)
+{
+	const TextureType			texType		= getTextureType(samplerType);
+	const deUint32				texTarget	= getGLTextureTarget(texType);
+	const deUint32				intFormat	= glu::getInternalFormat(texFormat);
+	const glu::TransferFormat	transferFmt	= glu::getTransferFormat(texFormat);
+
+	// \todo [2014-03-04 pyry] Use larger than 1x1 textures?
+
+	gl.bindTexture(texTarget, texture);
+
+	switch (texType)
+	{
+		case TEXTURE_TYPE_1D:
+			gl.texStorage1D(texTarget, 1, intFormat, 1);
+			gl.texSubImage1D(texTarget, 0, 0, 1, transferFmt.format, transferFmt.dataType, color);
+			break;
+
+		case TEXTURE_TYPE_2D:
+			gl.texStorage2D(texTarget, 1, intFormat, 1, 1);
+			gl.texSubImage2D(texTarget, 0, 0, 0, 1, 1, transferFmt.format, transferFmt.dataType, color);
+			break;
+
+		case TEXTURE_TYPE_2D_ARRAY:
+		case TEXTURE_TYPE_3D:
+			gl.texStorage3D(texTarget, 1, intFormat, 1, 1, 1);
+			gl.texSubImage3D(texTarget, 0, 0, 0, 0, 1, 1, 1, transferFmt.format, transferFmt.dataType, color);
+			break;
+
+		case TEXTURE_TYPE_CUBE:
+			gl.texStorage2D(texTarget, 1, intFormat, 1, 1);
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+				gl.texSubImage2D(glu::getGLCubeFace((tcu::CubeFace)face), 0, 0, 0, 1, 1, transferFmt.format, transferFmt.dataType, color);
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	gl.texParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	if (isShadowSampler(samplerType))
+		gl.texParameteri(texTarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Texture setup failed");
+}
+
+class SamplerIndexingCase : public TestCase
+{
+public:
+							SamplerIndexingCase			(Context& context, const char* name, const char* description, glu::ShaderType shaderType, glu::DataType samplerType, IndexExprType indexExprType);
+							~SamplerIndexingCase		(void);
+
+	void					init						(void);
+	IterateResult			iterate						(void);
+
+private:
+							SamplerIndexingCase			(const SamplerIndexingCase&);
+	SamplerIndexingCase&	operator=					(const SamplerIndexingCase&);
+
+	void					getShaderSpec				(ShaderSpec* spec, int numSamplers, int numLookups, const int* lookupIndices) const;
+
+	const glu::ShaderType	m_shaderType;
+	const glu::DataType		m_samplerType;
+	const IndexExprType		m_indexExprType;
+};
+
+SamplerIndexingCase::SamplerIndexingCase (Context& context, const char* name, const char* description, glu::ShaderType shaderType, glu::DataType samplerType, IndexExprType indexExprType)
+	: TestCase			(context, name, description)
+	, m_shaderType		(shaderType)
+	, m_samplerType		(samplerType)
+	, m_indexExprType	(indexExprType)
+{
+}
+
+SamplerIndexingCase::~SamplerIndexingCase (void)
+{
+}
+
+void SamplerIndexingCase::init (void)
+{
+	const char* extName = "GL_EXT_gpu_shader5";
+
+	if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL &&
+		!m_context.getContextInfo().isExtensionSupported(extName))
+		throw tcu::NotSupportedError(string(extName) + " extension is required for dynamic indexing of sampler arrays");
+}
+
+void SamplerIndexingCase::getShaderSpec (ShaderSpec* spec, int numSamplers, int numLookups, const int* lookupIndices) const
+{
+	const char*			samplersName	= "sampler";
+	const char*			coordsName		= "coords";
+	const char*			indicesPrefix	= "index";
+	const char*			resultPrefix	= "result";
+	const DataType		coordType		= getSamplerCoordType(m_samplerType);
+	const DataType		outType			= getSamplerOutputType(m_samplerType);
+	std::ostringstream	global, code;
+
+	spec->inputs.push_back(Symbol(coordsName, VarType(coordType, PRECISION_HIGHP)));
+
+	if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL)
+		global << "#extension GL_EXT_gpu_shader5 : require\n";
+
+	global <<
+		"uniform highp " << getDataTypeName(m_samplerType) << " " << samplersName << "[" << numSamplers << "];\n";
+
+	if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
+	{
+		for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
+		{
+			const string varName = indicesPrefix + de::toString(lookupNdx);
+			spec->inputs.push_back(Symbol(varName, VarType(TYPE_INT, PRECISION_HIGHP)));
+		}
+	}
+	else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
+		declareUniformIndexVars(global, indicesPrefix, numLookups);
+
+	for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
+	{
+		const string varName = resultPrefix + de::toString(lookupNdx);
+		spec->outputs.push_back(Symbol(varName, VarType(outType, PRECISION_HIGHP)));
+	}
+
+	for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
+	{
+		code << resultPrefix << "" << lookupNdx << " = texture(" << samplersName << "[";
+
+		if (m_indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL)
+			code << lookupIndices[lookupNdx];
+		else
+			code << indicesPrefix << lookupNdx;
+
+		code << "], " << coordsName << ");\n";
+	}
+
+	spec->version				= GLSL_VERSION_310_ES;
+	spec->globalDeclarations	= global.str();
+	spec->source				= code.str();
+}
+
+static void fillTextureData (const tcu::PixelBufferAccess& access, de::Random& rnd)
+{
+	DE_ASSERT(access.getHeight() == 1 && access.getDepth() == 1);
+
+	if (access.getFormat().order == TextureFormat::D)
+	{
+		// \note Texture uses odd values, lookup even values to avoid precision issues.
+		const float values[] = { 0.1f, 0.3f, 0.5f, 0.7f, 0.9f };
+
+		for (int ndx = 0; ndx < access.getWidth(); ndx++)
+			access.setPixDepth(rnd.choose<float>(DE_ARRAY_BEGIN(values), DE_ARRAY_END(values)), ndx, 0);
+	}
+	else
+	{
+		TCU_CHECK_INTERNAL(access.getFormat().order == TextureFormat::RGBA && access.getFormat().getPixelSize() == 4);
+
+		for (int ndx = 0; ndx < access.getWidth(); ndx++)
+			*((deUint32*)access.getDataPtr() + ndx) = rnd.getUint32();
+	}
+}
+
+SamplerIndexingCase::IterateResult SamplerIndexingCase::iterate (void)
+{
+	const int						numInvocations		= 64;
+	const int						numSamplers			= 8;
+	const int						numLookups			= 4;
+	const DataType					coordType			= getSamplerCoordType(m_samplerType);
+	const DataType					outputType			= getSamplerOutputType(m_samplerType);
+	const TextureFormat				texFormat			= getSamplerTextureFormat(m_samplerType);
+	const int						outLookupStride		= numInvocations*getDataTypeScalarSize(outputType);
+	vector<int>						lookupIndices		(numLookups);
+	vector<float>					coords;
+	vector<deUint32>				outData;
+	vector<deUint8>					texData				(numSamplers * texFormat.getPixelSize());
+	const tcu::PixelBufferAccess	refTexAccess		(texFormat, numSamplers, 1, 1, &texData[0]);
+	ShaderSpec						shaderSpec;
+	de::Random						rnd					(deInt32Hash(m_samplerType) ^ deInt32Hash(m_shaderType) ^ deInt32Hash(m_indexExprType));
+
+	for (int ndx = 0; ndx < numLookups; ndx++)
+		lookupIndices[ndx] = rnd.getInt(0, numSamplers-1);
+
+	getShaderSpec(&shaderSpec, numSamplers, numLookups, &lookupIndices[0]);
+
+	coords.resize(numInvocations * getDataTypeScalarSize(coordType));
+
+	if (isShadowSampler(m_samplerType))
+	{
+		// Use different comparison value per invocation.
+		// \note Texture uses odd values, comparison even values.
+		const int	numCoordComps	= getDataTypeScalarSize(coordType);
+		const float	cmpValues[]		= { 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f };
+
+		for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++)
+			coords[invocationNdx*numCoordComps + (numCoordComps-1)] = rnd.choose<float>(DE_ARRAY_BEGIN(cmpValues), DE_ARRAY_END(cmpValues));
+	}
+
+	fillTextureData(refTexAccess, rnd);
+
+	outData.resize(numLookups*outLookupStride);
+
+	{
+		const RenderContext&	renderCtx		= m_context.getRenderContext();
+		const glw::Functions&	gl				= renderCtx.getFunctions();
+		ShaderExecutorPtr		executor		(createExecutor(m_context.getRenderContext(), m_shaderType, shaderSpec));
+		TextureVector			textures		(renderCtx, numSamplers);
+		vector<void*>			inputs;
+		vector<void*>			outputs;
+		vector<int>				expandedIndices;
+		const int				maxIndex		= maxElement(lookupIndices);
+
+		m_testCtx.getLog() << *executor;
+
+		if (!executor->isOk())
+			TCU_FAIL("Compile failed");
+
+		executor->useProgram();
+
+		// \todo [2014-03-05 pyry] Do we want to randomize tex unit assignments?
+		for (int samplerNdx = 0; samplerNdx < numSamplers; samplerNdx++)
+		{
+			const string	samplerName	= string("sampler[") + de::toString(samplerNdx) + "]";
+			const int		samplerLoc	= gl.getUniformLocation(executor->getProgram(), samplerName.c_str());
+
+			if (samplerNdx > maxIndex && samplerLoc < 0)
+				continue; // Unused uniform eliminated by compiler
+
+			TCU_CHECK_MSG(samplerLoc >= 0, (string("No location for uniform '") + samplerName + "' found").c_str());
+
+			gl.activeTexture(GL_TEXTURE0 + samplerNdx);
+			setupTexture(gl, textures[samplerNdx], m_samplerType, texFormat, &texData[samplerNdx*texFormat.getPixelSize()]);
+
+			gl.uniform1i(samplerLoc, samplerNdx);
+		}
+
+		inputs.push_back(&coords[0]);
+
+		if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
+		{
+			expandedIndices.resize(numInvocations * lookupIndices.size());
+			for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
+			{
+				for (int invNdx = 0; invNdx < numInvocations; invNdx++)
+					expandedIndices[lookupNdx*numInvocations + invNdx] = lookupIndices[lookupNdx];
+			}
+
+			for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
+				inputs.push_back(&expandedIndices[lookupNdx*numInvocations]);
+		}
+		else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
+			uploadUniformIndices(gl, executor->getProgram(), "index", numLookups, &lookupIndices[0]);
+
+		for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
+			outputs.push_back(&outData[outLookupStride*lookupNdx]);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");
+
+		executor->execute(numInvocations, &inputs[0], &outputs[0]);
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	if (isShadowSampler(m_samplerType))
+	{
+		const tcu::Sampler	refSampler		(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
+											 tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, 0.0f, false /* non-normalized */,
+											 tcu::Sampler::COMPAREMODE_LESS);
+		const int			numCoordComps	= getDataTypeScalarSize(coordType);
+
+		TCU_CHECK_INTERNAL(getDataTypeScalarSize(outputType) == 1);
+
+		// Each invocation may have different results.
+		for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++)
+		{
+			const float	coord	= coords[invocationNdx*numCoordComps + (numCoordComps-1)];
+
+			for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
+			{
+				const int		texNdx		= lookupIndices[lookupNdx];
+				const float		result		= *((const float*)(const deUint8*)&outData[lookupNdx*outLookupStride + invocationNdx]);
+				const float		reference	= refTexAccess.sample2DCompare(refSampler, tcu::Sampler::NEAREST, coord, (float)texNdx, 0.0f, tcu::IVec3(0));
+
+				if (de::abs(result-reference) > 0.005f)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at invocation " << invocationNdx << ", lookup " << lookupNdx << ": expected "
+														   << reference << ", got " << result
+									   << TestLog::EndMessage;
+
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid lookup result");
+				}
+			}
+		}
+	}
+	else
+	{
+		TCU_CHECK_INTERNAL(getDataTypeScalarSize(outputType) == 4);
+
+		// Validate results from first invocation
+		for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
+		{
+			const int		texNdx	= lookupIndices[lookupNdx];
+			const deUint8*	resPtr	= (const deUint8*)&outData[lookupNdx*outLookupStride];
+			bool			isOk;
+
+			if (outputType == TYPE_FLOAT_VEC4)
+			{
+				const float			threshold		= 1.0f / 256.0f;
+				const tcu::Vec4		reference		= refTexAccess.getPixel(texNdx, 0);
+				const float*		floatPtr		= (const float*)resPtr;
+				const tcu::Vec4		result			(floatPtr[0], floatPtr[1], floatPtr[2], floatPtr[3]);
+
+				isOk = boolAll(lessThanEqual(abs(reference-result), tcu::Vec4(threshold)));
+
+				if (!isOk)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at lookup " << lookupNdx << ": expected "
+														   << reference << ", got " << result
+									   << TestLog::EndMessage;
+				}
+			}
+			else
+			{
+				const tcu::UVec4	reference		= refTexAccess.getPixelUint(texNdx, 0);
+				const deUint32*		uintPtr			= (const deUint32*)resPtr;
+				const tcu::UVec4	result			(uintPtr[0], uintPtr[1], uintPtr[2], uintPtr[3]);
+
+				isOk = boolAll(equal(reference, result));
+
+				if (!isOk)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at lookup " << lookupNdx << ": expected "
+														   << reference << ", got " << result
+									   << TestLog::EndMessage;
+				}
+			}
+
+			if (!isOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid lookup result");
+		}
+
+		// Check results of other invocations against first one
+		for (int invocationNdx = 1; invocationNdx < numInvocations; invocationNdx++)
+		{
+			for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
+			{
+				const deUint32*		refPtr		= &outData[lookupNdx*outLookupStride];
+				const deUint32*		resPtr		= refPtr + invocationNdx*4;
+				bool				isOk		= true;
+
+				for (int ndx = 0; ndx < 4; ndx++)
+					isOk = isOk && (refPtr[ndx] == resPtr[ndx]);
+
+				if (!isOk)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: invocation " << invocationNdx << " result "
+														   << tcu::formatArray(tcu::Format::HexIterator<deUint32>(resPtr), tcu::Format::HexIterator<deUint32>(resPtr+4))
+														   << " for lookup " << lookupNdx << " doesn't match result from first invocation "
+														   << tcu::formatArray(tcu::Format::HexIterator<deUint32>(refPtr), tcu::Format::HexIterator<deUint32>(refPtr+4))
+									   << TestLog::EndMessage;
+
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Inconsistent lookup results");
+				}
+			}
+		}
+	}
+
+	return STOP;
+}
+
+class BlockArrayIndexingCase : public TestCase
+{
+public:
+	enum BlockType
+	{
+		BLOCKTYPE_UNIFORM = 0,
+		BLOCKTYPE_BUFFER,
+
+		BLOCKTYPE_LAST
+	};
+								BlockArrayIndexingCase		(Context& context, const char* name, const char* description, BlockType blockType, IndexExprType indexExprType, ShaderType shaderType);
+								~BlockArrayIndexingCase		(void);
+
+	void						init						(void);
+	IterateResult				iterate						(void);
+
+private:
+								BlockArrayIndexingCase		(const BlockArrayIndexingCase&);
+	BlockArrayIndexingCase&		operator=					(const BlockArrayIndexingCase&);
+
+	void						getShaderSpec				(ShaderSpec* spec, int numInstances, int numReads, const int* readIndices) const;
+
+	const BlockType				m_blockType;
+	const IndexExprType			m_indexExprType;
+	const ShaderType			m_shaderType;
+};
+
+BlockArrayIndexingCase::BlockArrayIndexingCase (Context& context, const char* name, const char* description, BlockType blockType, IndexExprType indexExprType, ShaderType shaderType)
+	: TestCase			(context, name, description)
+	, m_blockType		(blockType)
+	, m_indexExprType	(indexExprType)
+	, m_shaderType		(shaderType)
+{
+}
+
+BlockArrayIndexingCase::~BlockArrayIndexingCase (void)
+{
+}
+
+void BlockArrayIndexingCase::init (void)
+{
+	const char* extName = "GL_EXT_gpu_shader5";
+
+	if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL &&
+		!m_context.getContextInfo().isExtensionSupported(extName))
+		throw tcu::NotSupportedError(string(extName) + " extension is required for dynamic indexing of interface blocks");
+}
+
+void BlockArrayIndexingCase::getShaderSpec (ShaderSpec* spec, int numInstances, int numReads, const int* readIndices) const
+{
+	const int			binding			= 2;
+	const char*			blockName		= "Block";
+	const char*			instanceName	= "block";
+	const char*			indicesPrefix	= "index";
+	const char*			resultPrefix	= "result";
+	const char*			interfaceName	= m_blockType == BLOCKTYPE_UNIFORM ? "uniform" : "buffer";
+	const char*			layout			= m_blockType == BLOCKTYPE_UNIFORM ? "std140" : "std430";
+	std::ostringstream	global, code;
+
+	if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL)
+		global << "#extension GL_EXT_gpu_shader5 : require\n";
+
+	global <<
+		"layout(" << layout << ", binding = " << binding << ") " << interfaceName << " " << blockName << "\n"
+		"{\n"
+		"	uint value;\n"
+		"} " << instanceName << "[" << numInstances << "];\n";
+
+	if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
+	{
+		for (int readNdx = 0; readNdx < numReads; readNdx++)
+		{
+			const string varName = indicesPrefix + de::toString(readNdx);
+			spec->inputs.push_back(Symbol(varName, VarType(TYPE_INT, PRECISION_HIGHP)));
+		}
+	}
+	else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
+		declareUniformIndexVars(global, indicesPrefix, numReads);
+
+	for (int readNdx = 0; readNdx < numReads; readNdx++)
+	{
+		const string varName = resultPrefix + de::toString(readNdx);
+		spec->outputs.push_back(Symbol(varName, VarType(TYPE_UINT, PRECISION_HIGHP)));
+	}
+
+	for (int readNdx = 0; readNdx < numReads; readNdx++)
+	{
+		code << resultPrefix << readNdx << " = " << instanceName << "[";
+
+		if (m_indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL)
+			code << readIndices[readNdx];
+		else
+			code << indicesPrefix << readNdx;
+
+		code << "].value;\n";
+	}
+
+	spec->version				= GLSL_VERSION_310_ES;
+	spec->globalDeclarations	= global.str();
+	spec->source				= code.str();
+}
+
+BlockArrayIndexingCase::IterateResult BlockArrayIndexingCase::iterate (void)
+{
+	const int			numInvocations		= 32;
+	const int			numInstances		= 4;
+	const int			numReads			= 4;
+	vector<int>			readIndices			(numReads);
+	vector<deUint32>	inValues			(numInstances);
+	vector<deUint32>	outValues			(numInvocations*numReads);
+	ShaderSpec			shaderSpec;
+	de::Random			rnd					(deInt32Hash(m_shaderType) ^ deInt32Hash(m_blockType) ^ deInt32Hash(m_indexExprType));
+
+	for (int readNdx = 0; readNdx < numReads; readNdx++)
+		readIndices[readNdx] = rnd.getInt(0, numInstances-1);
+
+	for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		inValues[instanceNdx] = rnd.getUint32();
+
+	getShaderSpec(&shaderSpec, numInstances, numReads, &readIndices[0]);
+
+	{
+		const RenderContext&	renderCtx		= m_context.getRenderContext();
+		const glw::Functions&	gl				= renderCtx.getFunctions();
+		const int				baseBinding		= 2;
+		const BufferVector		buffers			(renderCtx, numInstances);
+		const deUint32			bufTarget		= m_blockType == BLOCKTYPE_BUFFER ? GL_SHADER_STORAGE_BUFFER : GL_UNIFORM_BUFFER;
+		ShaderExecutorPtr		shaderExecutor	(createExecutor(renderCtx, m_shaderType, shaderSpec));
+		vector<int>				expandedIndices;
+		vector<void*>			inputs;
+		vector<void*>			outputs;
+
+		m_testCtx.getLog() << *shaderExecutor;
+
+		if (!shaderExecutor->isOk())
+			TCU_FAIL("Compile failed");
+
+		shaderExecutor->useProgram();
+
+		for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		{
+			gl.bindBuffer(bufTarget, buffers[instanceNdx]);
+			gl.bufferData(bufTarget, (glw::GLsizeiptr)sizeof(deUint32), &inValues[instanceNdx], GL_STATIC_DRAW);
+			gl.bindBufferBase(bufTarget, baseBinding+instanceNdx, buffers[instanceNdx]);
+		}
+
+		if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
+		{
+			expandedIndices.resize(numInvocations * readIndices.size());
+
+			for (int readNdx = 0; readNdx < numReads; readNdx++)
+			{
+				int* dst = &expandedIndices[numInvocations*readNdx];
+				std::fill(dst, dst+numInvocations, readIndices[readNdx]);
+			}
+
+			for (int readNdx = 0; readNdx < numReads; readNdx++)
+				inputs.push_back(&expandedIndices[readNdx*numInvocations]);
+		}
+		else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
+			uploadUniformIndices(gl, shaderExecutor->getProgram(), "index", numReads, &readIndices[0]);
+
+		for (int readNdx = 0; readNdx < numReads; readNdx++)
+			outputs.push_back(&outValues[readNdx*numInvocations]);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");
+
+		shaderExecutor->execute(numInvocations, inputs.empty() ? DE_NULL : &inputs[0], &outputs[0]);
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++)
+	{
+		for (int readNdx = 0; readNdx < numReads; readNdx++)
+		{
+			const deUint32	refValue	= inValues[readIndices[readNdx]];
+			const deUint32	resValue	= outValues[readNdx*numInvocations + invocationNdx];
+
+			if (refValue != resValue)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: at invocation " << invocationNdx
+													   << ", read " << readNdx << ": expected "
+													   << tcu::toHex(refValue) << ", got " << tcu::toHex(resValue)
+								   << TestLog::EndMessage;
+
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid result value");
+			}
+		}
+	}
+
+	return STOP;
+}
+
+class AtomicCounterIndexingCase : public TestCase
+{
+public:
+								AtomicCounterIndexingCase		(Context& context, const char* name, const char* description, IndexExprType indexExprType, ShaderType shaderType);
+								~AtomicCounterIndexingCase		(void);
+
+	void						init							(void);
+	IterateResult				iterate							(void);
+
+private:
+								AtomicCounterIndexingCase		(const AtomicCounterIndexingCase&);
+	AtomicCounterIndexingCase&	operator=						(const AtomicCounterIndexingCase&);
+
+	void						getShaderSpec					(ShaderSpec* spec, int numCounters, int numOps, const int* opIndices) const;
+
+	const IndexExprType			m_indexExprType;
+	const glu::ShaderType		m_shaderType;
+};
+
+AtomicCounterIndexingCase::AtomicCounterIndexingCase (Context& context, const char* name, const char* description, IndexExprType indexExprType, ShaderType shaderType)
+	: TestCase			(context, name, description)
+	, m_indexExprType	(indexExprType)
+	, m_shaderType		(shaderType)
+{
+}
+
+AtomicCounterIndexingCase::~AtomicCounterIndexingCase (void)
+{
+}
+
+void AtomicCounterIndexingCase::init (void)
+{
+	const char* extName = "GL_EXT_gpu_shader5";
+
+	if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL &&
+		!m_context.getContextInfo().isExtensionSupported(extName))
+		throw tcu::NotSupportedError(string(extName) + " extension is required for dynamic indexing of atomic counters");
+
+	if (m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT)
+	{
+		int numAtomicCounterBuffers = 0;
+		m_context.getRenderContext().getFunctions().getIntegerv(m_shaderType == glu::SHADERTYPE_VERTEX ? GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS
+																									   : GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS,
+																&numAtomicCounterBuffers);
+
+		if (numAtomicCounterBuffers == 0)
+			throw tcu::NotSupportedError(string("Atomic counters not supported in ") + glu::getShaderTypeName(m_shaderType) + " shader");
+	}
+}
+
+void AtomicCounterIndexingCase::getShaderSpec (ShaderSpec* spec, int numCounters, int numOps, const int* opIndices) const
+{
+	const char*			indicesPrefix	= "index";
+	const char*			resultPrefix	= "result";
+	std::ostringstream	global, code;
+
+	if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL)
+		global << "#extension GL_EXT_gpu_shader5 : require\n";
+
+	global <<
+		"layout(binding = 0) uniform atomic_uint counter[" << numCounters << "];\n";
+
+	if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
+	{
+		for (int opNdx = 0; opNdx < numOps; opNdx++)
+		{
+			const string varName = indicesPrefix + de::toString(opNdx);
+			spec->inputs.push_back(Symbol(varName, VarType(TYPE_INT, PRECISION_HIGHP)));
+		}
+	}
+	else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
+		declareUniformIndexVars(global, indicesPrefix, numOps);
+
+	for (int opNdx = 0; opNdx < numOps; opNdx++)
+	{
+		const string varName = resultPrefix + de::toString(opNdx);
+		spec->outputs.push_back(Symbol(varName, VarType(TYPE_UINT, PRECISION_HIGHP)));
+	}
+
+	for (int opNdx = 0; opNdx < numOps; opNdx++)
+	{
+		code << resultPrefix << opNdx << " = atomicCounterIncrement(counter[";
+
+		if (m_indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL)
+			code << opIndices[opNdx];
+		else
+			code << indicesPrefix << opNdx;
+
+		code << "]);\n";
+	}
+
+	spec->version				= GLSL_VERSION_310_ES;
+	spec->globalDeclarations	= global.str();
+	spec->source				= code.str();
+}
+
+AtomicCounterIndexingCase::IterateResult AtomicCounterIndexingCase::iterate (void)
+{
+	const RenderContext&	renderCtx			= m_context.getRenderContext();
+	const glw::Functions&	gl					= renderCtx.getFunctions();
+	const Buffer			counterBuffer		(renderCtx);
+
+	const int				numInvocations		= 32;
+	const int				numCounters			= 4;
+	const int				numOps				= 4;
+	vector<int>				opIndices			(numOps);
+	vector<deUint32>		outValues			(numInvocations*numOps);
+	ShaderSpec				shaderSpec;
+	de::Random				rnd					(deInt32Hash(m_shaderType) ^ deInt32Hash(m_indexExprType));
+
+	for (int opNdx = 0; opNdx < numOps; opNdx++)
+		opIndices[opNdx] = rnd.getInt(0, numOps-1);
+
+	getShaderSpec(&shaderSpec, numCounters, numOps, &opIndices[0]);
+
+	{
+		const BufferVector		buffers			(renderCtx, numCounters);
+		ShaderExecutorPtr		shaderExecutor	(createExecutor(renderCtx, m_shaderType, shaderSpec));
+		vector<int>				expandedIndices;
+		vector<void*>			inputs;
+		vector<void*>			outputs;
+
+		m_testCtx.getLog() << *shaderExecutor;
+
+		if (!shaderExecutor->isOk())
+			TCU_FAIL("Compile failed");
+
+		{
+			const int				bufSize		= getProgramResourceInt(gl, shaderExecutor->getProgram(), GL_ATOMIC_COUNTER_BUFFER, 0, GL_BUFFER_DATA_SIZE);
+			const int				maxNdx		= maxElement(opIndices);
+			std::vector<deUint8>	emptyData	(numCounters*4, 0);
+
+			if (bufSize < (maxNdx+1)*4)
+				TCU_FAIL((string("GL reported invalid buffer size " + de::toString(bufSize)).c_str()));
+
+			gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, *counterBuffer);
+			gl.bufferData(GL_ATOMIC_COUNTER_BUFFER, (glw::GLsizeiptr)emptyData.size(), &emptyData[0], GL_STATIC_DRAW);
+			gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, *counterBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Atomic counter buffer initialization failed");
+		}
+
+		shaderExecutor->useProgram();
+
+		if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
+		{
+			expandedIndices.resize(numInvocations * opIndices.size());
+
+			for (int opNdx = 0; opNdx < numOps; opNdx++)
+			{
+				int* dst = &expandedIndices[numInvocations*opNdx];
+				std::fill(dst, dst+numInvocations, opIndices[opNdx]);
+			}
+
+			for (int opNdx = 0; opNdx < numOps; opNdx++)
+				inputs.push_back(&expandedIndices[opNdx*numInvocations]);
+		}
+		else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
+			uploadUniformIndices(gl, shaderExecutor->getProgram(), "index", numOps, &opIndices[0]);
+
+		for (int opNdx = 0; opNdx < numOps; opNdx++)
+			outputs.push_back(&outValues[opNdx*numInvocations]);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");
+
+		shaderExecutor->execute(numInvocations, inputs.empty() ? DE_NULL : &inputs[0], &outputs[0]);
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	{
+		vector<int>				numHits			(numCounters, 0);	// Number of hits per counter.
+		vector<deUint32>		counterValues	(numCounters);
+		vector<vector<bool> >	counterMasks	(numCounters);
+
+		for (int opNdx = 0; opNdx < numOps; opNdx++)
+			numHits[opIndices[opNdx]] += 1;
+
+		// Read counter values
+		{
+			const void* mapPtr = DE_NULL;
+
+			try
+			{
+				mapPtr = gl.mapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, numCounters*4, GL_MAP_READ_BIT);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER)");
+				TCU_CHECK(mapPtr);
+				std::copy((const deUint32*)mapPtr, (const deUint32*)mapPtr + numCounters, &counterValues[0]);
+				gl.unmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
+			}
+			catch (...)
+			{
+				if (mapPtr)
+					gl.unmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
+				throw;
+			}
+		}
+
+		// Verify counter values
+		for (int counterNdx = 0; counterNdx < numCounters; counterNdx++)
+		{
+			const deUint32		refCount	= (deUint32)(numHits[counterNdx]*numInvocations);
+			const deUint32		resCount	= counterValues[counterNdx];
+
+			if (refCount != resCount)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: atomic counter " << counterNdx << " has value " << resCount
+													   << ", expected " << refCount
+								   << TestLog::EndMessage;
+
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid atomic counter value");
+			}
+		}
+
+		// Allocate bitmasks - one bit per each valid result value
+		for (int counterNdx = 0; counterNdx < numCounters; counterNdx++)
+		{
+			const int	counterValue	= numHits[counterNdx]*numInvocations;
+			counterMasks[counterNdx].resize(counterValue, false);
+		}
+
+		// Verify result values from shaders
+		for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++)
+		{
+			for (int opNdx = 0; opNdx < numOps; opNdx++)
+			{
+				const int		counterNdx	= opIndices[opNdx];
+				const deUint32	resValue	= outValues[opNdx*numInvocations + invocationNdx];
+				const bool		rangeOk		= de::inBounds(resValue, 0u, (deUint32)counterMasks[counterNdx].size());
+				const bool		notSeen		= rangeOk && !counterMasks[counterNdx][resValue];
+				const bool		isOk		= rangeOk && notSeen;
+
+				if (!isOk)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at invocation " << invocationNdx
+														   << ", op " << opNdx << ": got invalid result value "
+														   << resValue
+									   << TestLog::EndMessage;
+
+					if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid result value");
+				}
+				else
+				{
+					// Mark as used - no other invocation should see this value from same counter.
+					counterMasks[counterNdx][resValue] = true;
+				}
+			}
+		}
+
+		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		{
+			// Consistency check - all masks should be 1 now
+			for (int counterNdx = 0; counterNdx < numCounters; counterNdx++)
+			{
+				for (vector<bool>::const_iterator i = counterMasks[counterNdx].begin(); i != counterMasks[counterNdx].end(); i++)
+					TCU_CHECK_INTERNAL(*i);
+			}
+		}
+	}
+
+	return STOP;
+}
+
+} // anonymous
+
+OpaqueTypeIndexingTests::OpaqueTypeIndexingTests (Context& context)
+	: TestCaseGroup(context, "opaque_type_indexing", "Opaque Type Indexing Tests")
+{
+}
+
+OpaqueTypeIndexingTests::~OpaqueTypeIndexingTests (void)
+{
+}
+
+void OpaqueTypeIndexingTests::init (void)
+{
+	static const struct
+	{
+		IndexExprType	type;
+		const char*		name;
+		const char*		description;
+	} indexingTypes[] =
+	{
+		{ INDEX_EXPR_TYPE_CONST_LITERAL,	"const_literal",		"Indexing by constant literal expression"		},
+		{ INDEX_EXPR_TYPE_UNIFORM,			"uniform",				"Indexing by uniform value"						},
+		{ INDEX_EXPR_TYPE_DYNAMIC_UNIFORM,	"dynamically_uniform",	"Indexing by dynamically uniform expression"	}
+	};
+
+	static const struct
+	{
+		ShaderType		type;
+		const char*		name;
+	} shaderTypes[] =
+	{
+		{ SHADERTYPE_VERTEX,		"vertex"	},
+		{ SHADERTYPE_FRAGMENT,		"fragment"	},
+		{ SHADERTYPE_COMPUTE,		"compute"	}
+	};
+
+	// .sampler
+	{
+		static const DataType samplerTypes[] =
+		{
+			// \note 1D images will be added by a later extension.
+//			TYPE_SAMPLER_1D,
+			TYPE_SAMPLER_2D,
+			TYPE_SAMPLER_CUBE,
+			TYPE_SAMPLER_2D_ARRAY,
+			TYPE_SAMPLER_3D,
+//			TYPE_SAMPLER_1D_SHADOW,
+			TYPE_SAMPLER_2D_SHADOW,
+			TYPE_SAMPLER_CUBE_SHADOW,
+			TYPE_SAMPLER_2D_ARRAY_SHADOW,
+//			TYPE_INT_SAMPLER_1D,
+			TYPE_INT_SAMPLER_2D,
+			TYPE_INT_SAMPLER_CUBE,
+			TYPE_INT_SAMPLER_2D_ARRAY,
+			TYPE_INT_SAMPLER_3D,
+//			TYPE_UINT_SAMPLER_1D,
+			TYPE_UINT_SAMPLER_2D,
+			TYPE_UINT_SAMPLER_CUBE,
+			TYPE_UINT_SAMPLER_2D_ARRAY,
+			TYPE_UINT_SAMPLER_3D,
+		};
+
+		tcu::TestCaseGroup* const samplerGroup = new tcu::TestCaseGroup(m_testCtx, "sampler", "Sampler Array Indexing Tests");
+		addChild(samplerGroup);
+
+		for (int indexTypeNdx = 0; indexTypeNdx < DE_LENGTH_OF_ARRAY(indexingTypes); indexTypeNdx++)
+		{
+			const IndexExprType			indexExprType	= indexingTypes[indexTypeNdx].type;
+			tcu::TestCaseGroup* const	indexGroup		= new tcu::TestCaseGroup(m_testCtx, indexingTypes[indexTypeNdx].name, indexingTypes[indexTypeNdx].description);
+			samplerGroup->addChild(indexGroup);
+
+			for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(shaderTypes); shaderTypeNdx++)
+			{
+				const ShaderType			shaderType		= shaderTypes[shaderTypeNdx].type;
+				tcu::TestCaseGroup* const	shaderGroup		= new tcu::TestCaseGroup(m_testCtx, shaderTypes[shaderTypeNdx].name, "");
+				indexGroup->addChild(shaderGroup);
+
+				for (int samplerTypeNdx = 0; samplerTypeNdx < DE_LENGTH_OF_ARRAY(samplerTypes); samplerTypeNdx++)
+				{
+					const DataType	samplerType	= samplerTypes[samplerTypeNdx];
+					const char*		samplerName	= getDataTypeName(samplerType);
+					const string	caseName	= de::toLower(samplerName);
+
+					shaderGroup->addChild(new SamplerIndexingCase(m_context, caseName.c_str(), "", shaderType, samplerType, indexExprType));
+				}
+			}
+		}
+	}
+
+	// .ubo / .ssbo / .atomic_counter
+	{
+		tcu::TestCaseGroup* const	uboGroup	= new tcu::TestCaseGroup(m_testCtx, "ubo",				"Uniform Block Instance Array Indexing Tests");
+		tcu::TestCaseGroup* const	ssboGroup	= new tcu::TestCaseGroup(m_testCtx, "ssbo",				"Buffer Block Instance Array Indexing Tests");
+		tcu::TestCaseGroup* const	acGroup		= new tcu::TestCaseGroup(m_testCtx, "atomic_counter",	"Atomic Counter Array Indexing Tests");
+		addChild(uboGroup);
+		addChild(ssboGroup);
+		addChild(acGroup);
+
+		for (int indexTypeNdx = 0; indexTypeNdx < DE_LENGTH_OF_ARRAY(indexingTypes); indexTypeNdx++)
+		{
+			const IndexExprType		indexExprType		= indexingTypes[indexTypeNdx].type;
+			const char*				indexExprName		= indexingTypes[indexTypeNdx].name;
+			const char*				indexExprDesc		= indexingTypes[indexTypeNdx].description;
+
+			for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(shaderTypes); shaderTypeNdx++)
+			{
+				const ShaderType		shaderType		= shaderTypes[shaderTypeNdx].type;
+				const string			name			= string(indexExprName) + "_" + shaderTypes[shaderTypeNdx].name;
+
+				uboGroup->addChild	(new BlockArrayIndexingCase		(m_context, name.c_str(), indexExprDesc, BlockArrayIndexingCase::BLOCKTYPE_UNIFORM,	indexExprType, shaderType));
+				ssboGroup->addChild	(new BlockArrayIndexingCase		(m_context, name.c_str(), indexExprDesc, BlockArrayIndexingCase::BLOCKTYPE_BUFFER,	indexExprType, shaderType));
+				acGroup->addChild	(new AtomicCounterIndexingCase	(m_context, name.c_str(), indexExprDesc, indexExprType, shaderType));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fOpaqueTypeIndexingTests.hpp b/modules/gles31/functional/es31fOpaqueTypeIndexingTests.hpp
new file mode 100644
index 0000000..a0bbdf4
--- /dev/null
+++ b/modules/gles31/functional/es31fOpaqueTypeIndexingTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FOPAQUETYPEINDEXINGTESTS_HPP
+#define _ES31FOPAQUETYPEINDEXINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Opaque type (sampler, buffer, atomic counter, ...) indexing tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class OpaqueTypeIndexingTests : public TestCaseGroup
+{
+public:
+								OpaqueTypeIndexingTests		(Context& context);
+								~OpaqueTypeIndexingTests	(void);
+
+	void						init						(void);
+
+private:
+								OpaqueTypeIndexingTests		(const OpaqueTypeIndexingTests&);
+	OpaqueTypeIndexingTests&	operator=					(const OpaqueTypeIndexingTests&);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FOPAQUETYPEINDEXINGTESTS_HPP
diff --git a/modules/gles31/functional/es31fProgramInterfaceDefinition.cpp b/modules/gles31/functional/es31fProgramInterfaceDefinition.cpp
new file mode 100644
index 0000000..556da0d
--- /dev/null
+++ b/modules/gles31/functional/es31fProgramInterfaceDefinition.cpp
@@ -0,0 +1,496 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Program interface
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fProgramInterfaceDefinition.hpp"
+#include "gluVarType.hpp"
+#include "gluShaderProgram.hpp"
+#include "deSTLUtil.hpp"
+#include "glwEnums.hpp"
+
+#include <set>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace ProgramInterfaceDefinition
+{
+namespace
+{
+
+static const glu::ShaderType s_shaderStageOrder[] =
+{
+	glu::SHADERTYPE_COMPUTE,
+
+	glu::SHADERTYPE_VERTEX,
+	glu::SHADERTYPE_TESSELLATION_CONTROL,
+	glu::SHADERTYPE_TESSELLATION_EVALUATION,
+	glu::SHADERTYPE_GEOMETRY,
+	glu::SHADERTYPE_FRAGMENT
+};
+
+// s_shaderStageOrder does not contain ShaderType_LAST
+DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_shaderStageOrder) == glu::SHADERTYPE_LAST);
+
+static bool containsMatchingSubtype (const glu::VarType& varType, bool (*predicate)(glu::DataType))
+{
+	if (varType.isBasicType() && predicate(varType.getBasicType()))
+		return true;
+
+	if (varType.isArrayType())
+		return containsMatchingSubtype(varType.getElementType(), predicate);
+
+	if (varType.isStructType())
+		for (int memberNdx = 0; memberNdx < varType.getStructPtr()->getNumMembers(); ++memberNdx)
+			if (containsMatchingSubtype(varType.getStructPtr()->getMember(memberNdx).getType(), predicate))
+				return true;
+
+	return false;
+}
+
+static bool containsMatchingSubtype (const std::vector<glu::VariableDeclaration>& decls, bool (*predicate)(glu::DataType))
+{
+	for (int varNdx = 0; varNdx < (int)decls.size(); ++varNdx)
+		if (containsMatchingSubtype(decls[varNdx].varType, predicate))
+			return true;
+	return false;
+}
+
+static bool isOpaqueType (glu::DataType type)
+{
+	return	glu::isDataTypeAtomicCounter(type)	||
+			glu::isDataTypeImage(type)			||
+			glu::isDataTypeSampler(type);
+}
+
+static int getShaderStageIndex (glu::ShaderType stage)
+{
+	const glu::ShaderType* const it = std::find(DE_ARRAY_BEGIN(s_shaderStageOrder), DE_ARRAY_END(s_shaderStageOrder), stage);
+
+	if (it == DE_ARRAY_END(s_shaderStageOrder))
+		return -1;
+	else
+	{
+		const int index = (int)(it - DE_ARRAY_BEGIN(s_shaderStageOrder));
+		return index;
+	}
+}
+
+} // anonymous
+
+Shader::Shader (glu::ShaderType type, glu::GLSLVersion version)
+	: m_shaderType	(type)
+	, m_version		(version)
+{
+}
+
+Shader::~Shader (void)
+{
+}
+
+static bool isIllegalVertexInput (const glu::VarType& varType)
+{
+	// booleans, opaque types, arrays, structs are not allowed as inputs
+	if (!varType.isBasicType())
+		return true;
+	if (glu::isDataTypeBoolOrBVec(varType.getBasicType()))
+		return true;
+	return false;
+}
+
+static bool isIllegalVertexOutput (const glu::VarType& varType, bool insideAStruct = false, bool insideAnArray = false)
+{
+	// booleans, opaque types, arrays of arrays, arrays of structs, array in struct, struct struct are not allowed as vertex outputs
+
+	if (varType.isBasicType())
+	{
+		const bool isOpaqueType = !glu::isDataTypeScalar(varType.getBasicType()) && !glu::isDataTypeVector(varType.getBasicType()) && !glu::isDataTypeMatrix(varType.getBasicType());
+
+		if (glu::isDataTypeBoolOrBVec(varType.getBasicType()))
+			return true;
+
+		if (isOpaqueType)
+			return true;
+
+		return false;
+	}
+	else if (varType.isArrayType())
+	{
+		if (insideAnArray || insideAStruct)
+			return true;
+
+		return isIllegalVertexOutput(varType.getElementType(), insideAStruct, true);
+	}
+	else if (varType.isStructType())
+	{
+		if (insideAnArray || insideAStruct)
+			return true;
+
+		for (int ndx = 0; ndx < varType.getStructPtr()->getNumMembers(); ++ndx)
+			if (isIllegalVertexOutput(varType.getStructPtr()->getMember(ndx).getType(), true, insideAnArray))
+				return true;
+
+		return false;
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return true;
+	}
+}
+
+static bool isIllegalFragmentInput (const glu::VarType& varType)
+{
+	return isIllegalVertexOutput(varType);
+}
+
+static bool isIllegalFragmentOutput (const glu::VarType& varType, bool insideAnArray = false)
+{
+	// booleans, opaque types, matrices, structs, arrays of arrays are not allowed as outputs
+
+	if (varType.isBasicType())
+	{
+		const bool isOpaqueType = !glu::isDataTypeScalar(varType.getBasicType()) && !glu::isDataTypeVector(varType.getBasicType()) && !glu::isDataTypeMatrix(varType.getBasicType());
+
+		if (glu::isDataTypeBoolOrBVec(varType.getBasicType()) || isOpaqueType || glu::isDataTypeMatrix(varType.getBasicType()))
+			return true;
+		return false;
+	}
+	else if (varType.isArrayType())
+	{
+		if (insideAnArray)
+			return true;
+		return isIllegalFragmentOutput(varType.getElementType(), true);
+	}
+	else if (varType.isStructType())
+		return true;
+	else
+	{
+		DE_ASSERT(false);
+		return true;
+	}
+}
+
+static bool isTypeIntegerOrContainsIntegers (const glu::VarType& varType)
+{
+	if (varType.isBasicType())
+		return glu::isDataTypeIntOrIVec(varType.getBasicType()) || glu::isDataTypeUintOrUVec(varType.getBasicType());
+	else if (varType.isArrayType())
+		return isTypeIntegerOrContainsIntegers(varType.getElementType());
+	else if (varType.isStructType())
+	{
+		for (int ndx = 0; ndx < varType.getStructPtr()->getNumMembers(); ++ndx)
+			if (isTypeIntegerOrContainsIntegers(varType.getStructPtr()->getMember(ndx).getType()))
+				return true;
+		return false;
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return true;
+	}
+}
+
+bool Shader::isValid (void) const
+{
+	// Default block variables
+	{
+		for (int varNdx = 0; varNdx < (int)m_defaultBlock.variables.size(); ++varNdx)
+		{
+			// atomic declaration in the default block without binding
+			if (m_defaultBlock.variables[varNdx].layout.binding == -1 &&
+				containsMatchingSubtype(m_defaultBlock.variables[varNdx].varType, glu::isDataTypeAtomicCounter))
+				return false;
+
+			// atomic declaration in a struct
+			if (m_defaultBlock.variables[varNdx].varType.isStructType() &&
+				containsMatchingSubtype(m_defaultBlock.variables[varNdx].varType, glu::isDataTypeAtomicCounter))
+				return false;
+
+			// Unsupported layout qualifiers
+
+			if (m_defaultBlock.variables[varNdx].layout.matrixOrder != glu::MATRIXORDER_LAST)
+				return false;
+
+			if (containsMatchingSubtype(m_defaultBlock.variables[varNdx].varType, glu::isDataTypeSampler))
+			{
+				const glu::Layout layoutWithLocationAndBinding(m_defaultBlock.variables[varNdx].layout.location, m_defaultBlock.variables[varNdx].layout.binding);
+
+				if (m_defaultBlock.variables[varNdx].layout != layoutWithLocationAndBinding)
+					return false;
+			}
+		}
+	}
+
+	// Interface blocks
+	{
+		for (int interfaceNdx = 0; interfaceNdx < (int)m_defaultBlock.interfaceBlocks.size(); ++interfaceNdx)
+		{
+			// ES31 disallows interface block array arrays
+			if (m_defaultBlock.interfaceBlocks[interfaceNdx].dimensions.size() > 1)
+				return false;
+
+			// Interface block arrays must have instance name
+			if (!m_defaultBlock.interfaceBlocks[interfaceNdx].dimensions.empty() && m_defaultBlock.interfaceBlocks[interfaceNdx].instanceName.empty())
+				return false;
+
+			// Opaque types in interface block
+			if (containsMatchingSubtype(m_defaultBlock.interfaceBlocks[interfaceNdx].variables, isOpaqueType))
+				return false;
+		}
+	}
+
+	// Shader type specific
+
+	if (m_shaderType == glu::SHADERTYPE_VERTEX)
+	{
+		for (int varNdx = 0; varNdx < (int)m_defaultBlock.variables.size(); ++varNdx)
+		{
+			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_IN && isIllegalVertexInput(m_defaultBlock.variables[varNdx].varType))
+				return false;
+			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_OUT && isIllegalVertexOutput(m_defaultBlock.variables[varNdx].varType))
+				return false;
+			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_OUT && m_defaultBlock.variables[varNdx].interpolation != glu::INTERPOLATION_FLAT && isTypeIntegerOrContainsIntegers(m_defaultBlock.variables[varNdx].varType))
+				return false;
+		}
+	}
+	else if (m_shaderType == glu::SHADERTYPE_FRAGMENT)
+	{
+		for (int varNdx = 0; varNdx < (int)m_defaultBlock.variables.size(); ++varNdx)
+		{
+			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_IN && isIllegalFragmentInput(m_defaultBlock.variables[varNdx].varType))
+				return false;
+			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_IN && m_defaultBlock.variables[varNdx].interpolation != glu::INTERPOLATION_FLAT && isTypeIntegerOrContainsIntegers(m_defaultBlock.variables[varNdx].varType))
+				return false;
+			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_OUT && isIllegalFragmentOutput(m_defaultBlock.variables[varNdx].varType))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+Program::Program (void)
+	: m_separable	(false)
+	, m_xfbMode		(0)
+{
+}
+
+static void collectStructPtrs (std::set<const glu::StructType*>& dst, const glu::VarType& type)
+{
+	if (type.isArrayType())
+		collectStructPtrs(dst, type.getElementType());
+	else if (type.isStructType())
+	{
+		dst.insert(type.getStructPtr());
+
+		for (int memberNdx = 0; memberNdx < type.getStructPtr()->getNumMembers(); ++memberNdx)
+			collectStructPtrs(dst, type.getStructPtr()->getMember(memberNdx).getType());
+	}
+}
+
+Program::~Program (void)
+{
+	// delete shader struct types, need to be done by the program since shaders might share struct types
+	{
+		std::set<const glu::StructType*> structTypes;
+
+		for (int shaderNdx = 0; shaderNdx < (int)m_shaders.size(); ++shaderNdx)
+		{
+			for (int varNdx = 0; varNdx < (int)m_shaders[shaderNdx]->m_defaultBlock.variables.size(); ++varNdx)
+				collectStructPtrs(structTypes, m_shaders[shaderNdx]->m_defaultBlock.variables[varNdx].varType);
+
+			for (int interfaceNdx = 0; interfaceNdx < (int)m_shaders[shaderNdx]->m_defaultBlock.interfaceBlocks.size(); ++interfaceNdx)
+				for (int varNdx = 0; varNdx < (int)m_shaders[shaderNdx]->m_defaultBlock.interfaceBlocks[interfaceNdx].variables.size(); ++varNdx)
+					collectStructPtrs(structTypes, m_shaders[shaderNdx]->m_defaultBlock.interfaceBlocks[interfaceNdx].variables[varNdx].varType);
+		}
+
+		for (std::set<const glu::StructType*>::iterator it = structTypes.begin(); it != structTypes.end(); ++it)
+			delete *it;
+	}
+
+	for (int shaderNdx = 0; shaderNdx < (int)m_shaders.size(); ++shaderNdx)
+		delete m_shaders[shaderNdx];
+	m_shaders.clear();
+}
+
+Shader* Program::addShader (glu::ShaderType type, glu::GLSLVersion version)
+{
+	Shader* shader;
+
+	// make sure push_back() cannot throw
+	m_shaders.reserve(m_shaders.size() + 1);
+
+	shader = new Shader(type, version);
+	m_shaders.push_back(shader);
+
+	return shader;
+}
+
+void Program::setSeparable (bool separable)
+{
+	m_separable = separable;
+}
+
+bool Program::isSeparable (void) const
+{
+	return m_separable;
+}
+
+const std::vector<Shader*>& Program::getShaders (void) const
+{
+	return m_shaders;
+}
+
+glu::ShaderType Program::getFirstStage (void) const
+{
+	const int	nullValue	= DE_LENGTH_OF_ARRAY(s_shaderStageOrder);
+	int			firstStage	= nullValue;
+
+	for (int shaderNdx = 0; shaderNdx < (int)m_shaders.size(); ++shaderNdx)
+	{
+		const int index = getShaderStageIndex(m_shaders[shaderNdx]->getType());
+		if (index != -1)
+			firstStage = de::min(firstStage, index);
+	}
+
+	if (firstStage == nullValue)
+		return glu::SHADERTYPE_LAST;
+	else
+		return s_shaderStageOrder[firstStage];
+}
+
+glu::ShaderType Program::getLastStage (void) const
+{
+	const int	nullValue	= -1;
+	int			lastStage	= nullValue;
+
+	for (int shaderNdx = 0; shaderNdx < (int)m_shaders.size(); ++shaderNdx)
+	{
+		const int index = getShaderStageIndex(m_shaders[shaderNdx]->getType());
+		if (index != -1)
+			lastStage = de::max(lastStage, index);
+	}
+
+	if (lastStage == nullValue)
+		return glu::SHADERTYPE_LAST;
+	else
+		return s_shaderStageOrder[lastStage];
+}
+
+void Program::addTransformFeedbackVarying (const std::string& varName)
+{
+	m_xfbVaryings.push_back(varName);
+}
+
+const std::vector<std::string>& Program::getTransformFeedbackVaryings (void) const
+{
+	return m_xfbVaryings;
+}
+
+void Program::setTransformFeedbackMode (deUint32 mode)
+{
+	m_xfbMode = mode;
+}
+
+deUint32 Program::getTransformFeedbackMode (void) const
+{
+	return m_xfbMode;
+}
+
+bool Program::isValid (void) const
+{
+	bool computePresent = false;
+
+	if (m_shaders.empty())
+		return false;
+
+	for (int ndx = 0; ndx < (int)m_shaders.size(); ++ndx)
+		if (!m_shaders[ndx]->isValid())
+			return false;
+
+	// same version
+	for (int ndx = 1; ndx < (int)m_shaders.size(); ++ndx)
+		if (m_shaders[0]->getVersion() != m_shaders[ndx]->getVersion())
+			return false;
+
+	// compute present -> no other stages present
+	{
+		bool nonComputePresent = false;
+
+		for (int ndx = 0; ndx < (int)m_shaders.size(); ++ndx)
+		{
+			if (m_shaders[ndx]->getType() == glu::SHADERTYPE_COMPUTE)
+				computePresent = true;
+			else
+				nonComputePresent = true;
+		}
+
+		if (computePresent && nonComputePresent)
+			return false;
+	}
+
+	// must contain both vertex and fragment shaders
+	if (!computePresent && !m_separable)
+	{
+		bool vertexPresent = false;
+		bool fragmentPresent = false;
+
+		for (int ndx = 0; ndx < (int)m_shaders.size(); ++ndx)
+		{
+			if (m_shaders[ndx]->getType() == glu::SHADERTYPE_VERTEX)
+				vertexPresent = true;
+			else if (m_shaders[ndx]->getType() == glu::SHADERTYPE_FRAGMENT)
+				fragmentPresent = true;
+		}
+
+		if (!vertexPresent || !fragmentPresent)
+			return false;
+	}
+
+	// tess.Eval present <=> tess.Control present
+	{
+		bool tessEvalPresent = false;
+		bool tessControlPresent = false;
+
+		for (int ndx = 0; ndx < (int)m_shaders.size(); ++ndx)
+		{
+			if (m_shaders[ndx]->getType() == glu::SHADERTYPE_TESSELLATION_EVALUATION)
+				tessEvalPresent = true;
+			else if (m_shaders[ndx]->getType() == glu::SHADERTYPE_TESSELLATION_CONTROL)
+				tessControlPresent = true;
+		}
+
+		if (tessEvalPresent != tessControlPresent)
+			return false;
+	}
+
+	return true;
+}
+
+} // ProgramInterfaceDefinition
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fProgramInterfaceDefinition.hpp b/modules/gles31/functional/es31fProgramInterfaceDefinition.hpp
new file mode 100644
index 0000000..72db0ae
--- /dev/null
+++ b/modules/gles31/functional/es31fProgramInterfaceDefinition.hpp
@@ -0,0 +1,125 @@
+#ifndef _ES31FPROGRAMINTERFACEDEFINITION_HPP
+#define _ES31FPROGRAMINTERFACEDEFINITION_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Program interface
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluVarType.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+enum ProgramInterface
+{
+	PROGRAMINTERFACE_UNIFORM = 0,
+	PROGRAMINTERFACE_UNIFORM_BLOCK,
+	PROGRAMINTERFACE_ATOMIC_COUNTER_BUFFER,
+	PROGRAMINTERFACE_PROGRAM_INPUT,
+	PROGRAMINTERFACE_PROGRAM_OUTPUT,
+	PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING,
+	PROGRAMINTERFACE_BUFFER_VARIABLE,
+	PROGRAMINTERFACE_SHADER_STORAGE_BLOCK,
+
+	PROGRAMINTERFACE_LAST
+};
+
+namespace ProgramInterfaceDefinition
+{
+
+class Program;
+
+struct DefaultBlock
+{
+	std::vector<glu::VariableDeclaration>	variables;
+	std::vector<glu::InterfaceBlock>		interfaceBlocks;
+};
+
+class Shader
+{
+public:
+	glu::ShaderType					getType			(void) const	{ return m_shaderType;		}
+	glu::GLSLVersion				getVersion		(void) const	{ return m_version;			}
+	bool							isValid			(void) const;
+
+	DefaultBlock&					getDefaultBlock	(void)			{ return m_defaultBlock;	}
+	const DefaultBlock&				getDefaultBlock	(void) const	{ return m_defaultBlock;	}
+
+private:
+									Shader		(glu::ShaderType type, glu::GLSLVersion version);
+									~Shader		(void);
+
+									Shader		(const Shader&);
+	Shader&							operator=	(const Shader&);
+
+	const glu::ShaderType			m_shaderType;
+	const glu::GLSLVersion			m_version;
+	DefaultBlock					m_defaultBlock;
+
+	friend class					Program;
+};
+
+class Program
+{
+public:
+									Program							(void);
+									~Program						(void);
+
+	Shader*							addShader						(glu::ShaderType type, glu::GLSLVersion version);
+
+	void							setSeparable					(bool separable);
+	bool							isSeparable						(void) const;
+
+	const std::vector<Shader*>&		getShaders						(void) const;
+	glu::ShaderType					getFirstStage					(void) const;
+	glu::ShaderType					getLastStage					(void) const;
+
+	void							addTransformFeedbackVarying		(const std::string& varName);
+	const std::vector<std::string>&	getTransformFeedbackVaryings	(void) const;
+	void							setTransformFeedbackMode		(deUint32 mode);
+	deUint32						getTransformFeedbackMode		(void) const;
+
+	bool							isValid							(void) const;
+
+private:
+	Program&						operator=						(const Program&);
+									Program							(const Program&);
+
+	bool							m_separable;
+	std::vector<Shader*>			m_shaders;
+	std::vector<std::string>		m_xfbVaryings;
+	deUint32						m_xfbMode;
+};
+
+} // ProgramInterfaceDefinition
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FPROGRAMINTERFACEDEFINITION_HPP
diff --git a/modules/gles31/functional/es31fProgramInterfaceDefinitionUtil.cpp b/modules/gles31/functional/es31fProgramInterfaceDefinitionUtil.cpp
new file mode 100644
index 0000000..c8a3067
--- /dev/null
+++ b/modules/gles31/functional/es31fProgramInterfaceDefinitionUtil.cpp
@@ -0,0 +1,1322 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Program interface utilities
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fProgramInterfaceDefinitionUtil.hpp"
+#include "es31fProgramInterfaceDefinition.hpp"
+#include "gluVarType.hpp"
+#include "gluVarTypeUtil.hpp"
+#include "gluShaderUtil.hpp"
+#include "deString.h"
+#include "deStringUtil.hpp"
+#include "glwEnums.hpp"
+
+#include <set>
+#include <map>
+#include <sstream>
+#include <vector>
+#include <algorithm>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace ProgramInterfaceDefinition
+{
+
+VariableSearchFilter VariableSearchFilter::intersection (const VariableSearchFilter& a, const VariableSearchFilter& b)
+{
+	const bool storageNonEmpty		= (a.m_storage == b.m_storage) || (a.m_storage == glu::STORAGE_LAST) || (b.m_storage == glu::STORAGE_LAST);
+	const bool shaderTypeNonEmpty	= (a.m_shaderType == b.m_shaderType) || (a.m_shaderType == glu::SHADERTYPE_LAST) || (b.m_shaderType == glu::SHADERTYPE_LAST);
+
+	return VariableSearchFilter((a.m_shaderType == glu::SHADERTYPE_LAST) ? (b.m_shaderType) : (a.m_shaderType),
+								(a.m_storage == glu::STORAGE_LAST) ? (b.m_storage) : (a.m_storage),
+								!storageNonEmpty || !shaderTypeNonEmpty || a.m_null || b.m_null);
+}
+
+} // ProgramInterfaceDefinition
+
+static bool incrementMultiDimensionIndex (std::vector<int>& index, const std::vector<int>& dimensions)
+{
+	int incrementDimensionNdx = (int)(index.size() - 1);
+
+	while (incrementDimensionNdx >= 0)
+	{
+		if (++index[incrementDimensionNdx] == dimensions[incrementDimensionNdx])
+			index[incrementDimensionNdx--] = 0;
+		else
+			break;
+	}
+
+	return (incrementDimensionNdx != -1);
+}
+
+void generateVariableTypeResourceNames (std::vector<std::string>& resources, const std::string& name, const glu::VarType& type, bool isTopLevelBufferVariable)
+{
+	if (type.isBasicType())
+		resources.push_back(name);
+	else if (type.isStructType())
+	{
+		const glu::StructType* structType = type.getStructPtr();
+		for (int ndx = 0; ndx < structType->getNumMembers(); ++ndx)
+			generateVariableTypeResourceNames(resources, name + "." + structType->getMember(ndx).getName(), structType->getMember(ndx).getType(), false);
+	}
+	else if (type.isArrayType())
+	{
+		// Bottom-level arrays of basic types and SSBO top-level arrays of any type procude only first element
+		if (type.getElementType().isBasicType() || isTopLevelBufferVariable)
+			generateVariableTypeResourceNames(resources, name + "[0]", type.getElementType(), false);
+		else
+		{
+			for (int ndx = 0; ndx < type.getArraySize(); ++ndx)
+				generateVariableTypeResourceNames(resources, name + "[" + de::toString(ndx) + "]", type.getElementType(), false);
+		}
+	}
+	else
+		DE_ASSERT(false);
+}
+
+// Program source generation
+
+namespace
+{
+
+using ProgramInterfaceDefinition::VariablePathComponent;
+using ProgramInterfaceDefinition::VariableSearchFilter;
+
+static const char* getShaderTypeDeclarations (glu::ShaderType type)
+{
+	switch (type)
+	{
+		case glu::SHADERTYPE_VERTEX:
+			return	"";
+
+		case glu::SHADERTYPE_FRAGMENT:
+			return	"";
+
+		case glu::SHADERTYPE_GEOMETRY:
+			return	"layout(points) in;\n"
+					"layout(points, max_vertices=3) out;\n";
+
+		case glu::SHADERTYPE_TESSELLATION_CONTROL:
+			return	"layout(vertices=1) out;\n";
+
+		case glu::SHADERTYPE_TESSELLATION_EVALUATION:
+			return	"layout(triangle, point_mode) in;\n";
+
+		case glu::SHADERTYPE_COMPUTE:
+			return	"layout(local_size_x=1) in;\n";
+
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+class StructNameEqualPredicate
+{
+public:
+				StructNameEqualPredicate	(const char* name) : m_name(name) { }
+	bool		operator()					(const glu::StructType* type) { return type->hasTypeName() && (deStringEqual(m_name, type->getTypeName()) == DE_TRUE); }
+private:
+	const char*	m_name;
+};
+
+static void collectNamedStructureDefinitions (std::vector<const glu::StructType*>& dst, const glu::VarType& type)
+{
+	if (type.isBasicType())
+		return;
+	else if (type.isArrayType())
+		return collectNamedStructureDefinitions(dst, type.getElementType());
+	else if (type.isStructType())
+	{
+		if (type.getStructPtr()->hasTypeName())
+		{
+			// must be unique (may share the the same struct)
+			std::vector<const glu::StructType*>::iterator where = std::find_if(dst.begin(), dst.end(), StructNameEqualPredicate(type.getStructPtr()->getTypeName()));
+			if (where != dst.end())
+			{
+				DE_ASSERT(**where == *type.getStructPtr());
+
+				// identical type has been added already, types of members must be added too
+				return;
+			}
+		}
+
+		// Add types of members first
+		for (int ndx = 0; ndx < type.getStructPtr()->getNumMembers(); ++ndx)
+			collectNamedStructureDefinitions(dst, type.getStructPtr()->getMember(ndx).getType());
+
+		dst.push_back(type.getStructPtr());
+	}
+	else
+		DE_ASSERT(false);
+}
+
+static void writeStructureDefinitions (std::ostringstream& buf, const ProgramInterfaceDefinition::DefaultBlock& defaultBlock)
+{
+	std::vector<const glu::StructType*> namedStructs;
+
+	// Collect all structs in post order
+
+	for (int ndx = 0; ndx < (int)defaultBlock.variables.size(); ++ndx)
+		collectNamedStructureDefinitions(namedStructs, defaultBlock.variables[ndx].varType);
+
+	for (int blockNdx = 0; blockNdx < (int)defaultBlock.interfaceBlocks.size(); ++blockNdx)
+		for (int ndx = 0; ndx < (int)defaultBlock.interfaceBlocks[blockNdx].variables.size(); ++ndx)
+			collectNamedStructureDefinitions(namedStructs, defaultBlock.interfaceBlocks[blockNdx].variables[ndx].varType);
+
+	// Write
+
+	for (int structNdx = 0; structNdx < (int)namedStructs.size(); ++structNdx)
+	{
+		buf <<	"struct " << namedStructs[structNdx]->getTypeName() << "\n"
+				"{\n";
+
+		for (int memberNdx = 0; memberNdx < namedStructs[structNdx]->getNumMembers(); ++memberNdx)
+			buf << glu::indent(1) << glu::declare(namedStructs[structNdx]->getMember(memberNdx).getType(), namedStructs[structNdx]->getMember(memberNdx).getName(), 1) << ";\n";
+
+		buf <<	"};\n";
+	}
+
+	if (!namedStructs.empty())
+		buf << "\n";
+}
+
+static void writeInterfaceBlock (std::ostringstream& buf, const glu::InterfaceBlock& interfaceBlock)
+{
+	buf << interfaceBlock.layout;
+
+	if (interfaceBlock.layout != glu::Layout())
+		buf << " ";
+
+	buf	<< glu::getStorageName(interfaceBlock.storage) << " " << interfaceBlock.interfaceName << "\n"
+		<< "{\n";
+
+	for (int ndx = 0; ndx < (int)interfaceBlock.variables.size(); ++ndx)
+		buf << glu::indent(1) << interfaceBlock.variables[ndx] << ";\n";
+
+	buf << "}";
+
+	if (!interfaceBlock.instanceName.empty())
+		buf << " " << interfaceBlock.instanceName;
+
+	for (int dimensionNdx = 0; dimensionNdx < (int)interfaceBlock.dimensions.size(); ++dimensionNdx)
+		buf << "[" << interfaceBlock.dimensions[dimensionNdx] << "]";
+
+	buf << ";\n\n";
+}
+
+static void writeVariableReadAccumulateExpression (std::ostringstream& buf, const std::string& accumulatorName, const std::string& name, const glu::VarType& varType)
+{
+	if (varType.isBasicType())
+	{
+		buf << "\t" << accumulatorName << " += ";
+
+		if (glu::isDataTypeScalar(varType.getBasicType()))
+			buf << "vec4(float(" << name << "))";
+		else if (glu::isDataTypeVector(varType.getBasicType()))
+			buf << "vec4(" << name << ".xyxy)";
+		else if (glu::isDataTypeMatrix(varType.getBasicType()))
+			buf << "vec4(float(" << name << "[0][0]))";
+		else if (glu::isDataTypeSamplerMultisample(varType.getBasicType()))
+			buf << "vec4(float(textureSize(" << name << ").x))";
+		else if (glu::isDataTypeSampler(varType.getBasicType()))
+			buf << "vec4(float(textureSize(" << name << ", 0).x))";
+		else if (glu::isDataTypeImage(varType.getBasicType()))
+			buf << "vec4(float(imageSize(" << name << ").x))";
+		else if (varType.getBasicType() == glu::TYPE_UINT_ATOMIC_COUNTER)
+			buf << "vec4(float(atomicCounterIncrement(" << name << ")))";
+		else
+			DE_ASSERT(false);
+
+		buf << ";\n";
+	}
+	else if (varType.isStructType())
+	{
+		for (int ndx = 0; ndx < varType.getStructPtr()->getNumMembers(); ++ndx)
+			writeVariableReadAccumulateExpression(buf, accumulatorName, name + "." + varType.getStructPtr()->getMember(ndx).getName(), varType.getStructPtr()->getMember(ndx).getType());
+	}
+	else if (varType.isArrayType())
+	{
+		if (varType.getArraySize() != glu::VarType::UNSIZED_ARRAY)
+			for (int ndx = 0; ndx < varType.getArraySize(); ++ndx)
+				writeVariableReadAccumulateExpression(buf, accumulatorName, name + "[" + de::toString(ndx) + "]", varType.getElementType());
+		else
+			writeVariableReadAccumulateExpression(buf, accumulatorName, name + "[8]", varType.getElementType());
+	}
+	else
+		DE_ASSERT(false);
+}
+
+static void writeInterfaceReadAccumulateExpression (std::ostringstream& buf, const std::string& accumulatorName, const glu::InterfaceBlock& block)
+{
+	if (block.dimensions.empty())
+	{
+		const std::string prefix = (block.instanceName.empty()) ? ("") : (block.instanceName + ".");
+
+		for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
+			writeVariableReadAccumulateExpression(buf, accumulatorName, prefix + block.variables[ndx].name, block.variables[ndx].varType);
+	}
+	else
+	{
+		std::vector<int> index(block.dimensions.size(), 0);
+
+		for (;;)
+		{
+			// access element
+			{
+				std::ostringstream name;
+				name << block.instanceName;
+
+				for (int dimensionNdx = 0; dimensionNdx < (int)block.dimensions.size(); ++dimensionNdx)
+					name << "[" << index[dimensionNdx] << "]";
+
+				for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
+					writeVariableReadAccumulateExpression(buf, accumulatorName, name.str() + "." + block.variables[ndx].name, block.variables[ndx].varType);
+			}
+
+			// increment index
+			if (!incrementMultiDimensionIndex(index, block.dimensions))
+				break;
+		}
+	}
+}
+
+static void writeVariableWriteExpression (std::ostringstream& buf, const std::string& sourceVec4Name, const std::string& name, const glu::VarType& varType)
+{
+	if (varType.isBasicType())
+	{
+		buf << "\t" << name << " = ";
+
+		if (glu::isDataTypeScalar(varType.getBasicType()))
+			buf << glu::getDataTypeName(varType.getBasicType()) << "(" << sourceVec4Name << ".y)";
+		else if (glu::isDataTypeVector(varType.getBasicType()) || glu::isDataTypeMatrix(varType.getBasicType()))
+			buf << glu::getDataTypeName(varType.getBasicType()) << "(" << glu::getDataTypeName(glu::getDataTypeScalarType(varType.getBasicType())) << "(" << sourceVec4Name << ".y))";
+		else
+			DE_ASSERT(false);
+
+		buf << ";\n";
+	}
+	else if (varType.isStructType())
+	{
+		for (int ndx = 0; ndx < varType.getStructPtr()->getNumMembers(); ++ndx)
+			writeVariableWriteExpression(buf, sourceVec4Name, name + "." + varType.getStructPtr()->getMember(ndx).getName(), varType.getStructPtr()->getMember(ndx).getType());
+	}
+	else if (varType.isArrayType())
+	{
+		if (varType.getArraySize() != glu::VarType::UNSIZED_ARRAY)
+			for (int ndx = 0; ndx < varType.getArraySize(); ++ndx)
+				writeVariableWriteExpression(buf, sourceVec4Name, name + "[" + de::toString(ndx) + "]", varType.getElementType());
+		else
+			writeVariableWriteExpression(buf, sourceVec4Name, name + "[9]", varType.getElementType());
+	}
+	else
+		DE_ASSERT(false);
+}
+
+static void writeInterfaceWriteExpression (std::ostringstream& buf, const std::string& sourceVec4Name, const glu::InterfaceBlock& block)
+{
+	if (block.dimensions.empty())
+	{
+		const std::string prefix = (block.instanceName.empty()) ? ("") : (block.instanceName + ".");
+
+		for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
+			writeVariableWriteExpression(buf, sourceVec4Name, prefix + block.variables[ndx].name, block.variables[ndx].varType);
+	}
+	else
+	{
+		std::vector<int> index(block.dimensions.size(), 0);
+
+		for (;;)
+		{
+			// access element
+			{
+				std::ostringstream name;
+				name << block.instanceName;
+
+				for (int dimensionNdx = 0; dimensionNdx < (int)block.dimensions.size(); ++dimensionNdx)
+					name << "[" << index[dimensionNdx] << "]";
+
+				for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
+					writeVariableWriteExpression(buf, sourceVec4Name, name.str() + "." + block.variables[ndx].name, block.variables[ndx].varType);
+			}
+
+			// increment index
+			if (!incrementMultiDimensionIndex(index, block.dimensions))
+				break;
+		}
+	}
+}
+
+static bool traverseVariablePath (std::vector<VariablePathComponent>& typePath, const char* pathWithoutName, const glu::VarType& type)
+{
+	glu::VarTokenizer tokenizer(pathWithoutName);
+
+	typePath.push_back(VariablePathComponent(&type));
+
+	if (tokenizer.getToken() == glu::VarTokenizer::TOKEN_END)
+		return true;
+
+	if (type.isStructType() && tokenizer.getToken() == glu::VarTokenizer::TOKEN_PERIOD)
+	{
+		tokenizer.advance();
+
+		// malformed path
+		if (tokenizer.getToken() != glu::VarTokenizer::TOKEN_IDENTIFIER)
+			return false;
+
+		for (int memberNdx = 0; memberNdx < type.getStructPtr()->getNumMembers(); ++memberNdx)
+			if (type.getStructPtr()->getMember(memberNdx).getName() == tokenizer.getIdentifier())
+				return traverseVariablePath(typePath, pathWithoutName + tokenizer.getCurrentTokenEndLocation(), type.getStructPtr()->getMember(memberNdx).getType());
+
+		// malformed path, no such member
+		return false;
+	}
+	else if (type.isArrayType() && tokenizer.getToken() == glu::VarTokenizer::TOKEN_LEFT_BRACKET)
+	{
+		tokenizer.advance();
+
+		// malformed path
+		if (tokenizer.getToken() != glu::VarTokenizer::TOKEN_NUMBER)
+			return false;
+
+		tokenizer.advance();
+		if (tokenizer.getToken() != glu::VarTokenizer::TOKEN_RIGHT_BRACKET)
+			return false;
+
+		return traverseVariablePath(typePath, pathWithoutName + tokenizer.getCurrentTokenEndLocation(), type.getElementType());
+	}
+
+	return false;
+}
+
+static bool traverseVariablePath (std::vector<VariablePathComponent>& typePath, const std::string& path, const glu::VariableDeclaration& var)
+{
+	if (glu::parseVariableName(path.c_str()) != var.name)
+		return false;
+
+	typePath.push_back(VariablePathComponent(&var));
+	return traverseVariablePath(typePath, path.c_str() + var.name.length(), var.varType);
+}
+
+static bool traverseShaderVariablePath (std::vector<VariablePathComponent>& typePath, const ProgramInterfaceDefinition::Shader* shader, const std::string& path, const VariableSearchFilter& filter)
+{
+	// Default block variable?
+	for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().variables.size(); ++varNdx)
+		if (filter.matchesFilter(shader->getDefaultBlock().variables[varNdx]))
+			if (traverseVariablePath(typePath, path, shader->getDefaultBlock().variables[varNdx]))
+				return true;
+
+	// is variable an interface block variable?
+	{
+		const std::string blockName = glu::parseVariableName(path.c_str());
+
+		for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
+		{
+			if (!filter.matchesFilter(shader->getDefaultBlock().interfaceBlocks[interfaceNdx]))
+				continue;
+
+			if (shader->getDefaultBlock().interfaceBlocks[interfaceNdx].interfaceName == blockName)
+			{
+				// resource is a member of a named interface block
+				// \note there is no array index specifier even if the interface is declared as an array of instances
+				const std::string blockMemberPath = path.substr(blockName.size() + 1);
+				const std::string blockMemeberName = glu::parseVariableName(blockMemberPath.c_str());
+
+				for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables.size(); ++varNdx)
+				{
+					if (shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables[varNdx].name == blockMemeberName)
+					{
+						typePath.push_back(VariablePathComponent(&shader->getDefaultBlock().interfaceBlocks[interfaceNdx]));
+						return traverseVariablePath(typePath, blockMemberPath, shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables[varNdx]);
+					}
+				}
+
+				// terminate search
+				return false;
+			}
+			else if (shader->getDefaultBlock().interfaceBlocks[interfaceNdx].instanceName.empty())
+			{
+				const std::string blockMemeberName = glu::parseVariableName(path.c_str());
+
+				// unnamed block contains such variable?
+				for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables.size(); ++varNdx)
+				{
+					if (shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables[varNdx].name == blockMemeberName)
+					{
+						typePath.push_back(VariablePathComponent(&shader->getDefaultBlock().interfaceBlocks[interfaceNdx]));
+						return traverseVariablePath(typePath, path, shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables[varNdx]);
+					}
+				}
+
+				// continue search
+			}
+		}
+	}
+
+	return false;
+}
+
+static bool traverseProgramVariablePath (std::vector<VariablePathComponent>& typePath, const ProgramInterfaceDefinition::Program* program, const std::string& path, const VariableSearchFilter& filter)
+{
+	for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
+	{
+		const ProgramInterfaceDefinition::Shader* shader = program->getShaders()[shaderNdx];
+
+		if (filter.matchesFilter(shader))
+		{
+			// \note modifying output variable even when returning false
+			typePath.clear();
+			if (traverseShaderVariablePath(typePath, shader, path, filter))
+				return true;
+		}
+	}
+
+	return false;
+}
+
+static bool containsSubType (const glu::VarType& complexType, glu::DataType basicType)
+{
+	if (complexType.isBasicType())
+	{
+		return complexType.getBasicType() == basicType;
+	}
+	else if (complexType.isArrayType())
+	{
+		return containsSubType(complexType.getElementType(), basicType);
+	}
+	else if (complexType.isStructType())
+	{
+		for (int ndx = 0; ndx < complexType.getStructPtr()->getNumMembers(); ++ndx)
+			if (containsSubType(complexType.getStructPtr()->getMember(ndx).getType(), basicType))
+				return true;
+		return false;
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return false;
+	}
+}
+
+static int getNumShaderBlocks (const ProgramInterfaceDefinition::Shader* shader, glu::Storage storage)
+{
+	int retVal = 0;
+
+	for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
+	{
+		if (shader->getDefaultBlock().interfaceBlocks[ndx].storage == storage)
+		{
+			int numInstances = 1;
+
+			for (int dimensionNdx = 0; dimensionNdx < (int)shader->getDefaultBlock().interfaceBlocks[ndx].dimensions.size(); ++dimensionNdx)
+				numInstances *= shader->getDefaultBlock().interfaceBlocks[ndx].dimensions[dimensionNdx];
+
+			retVal += numInstances;
+		}
+	}
+
+	return retVal;
+}
+
+static int getNumAtomicCounterBuffers (const ProgramInterfaceDefinition::Shader* shader)
+{
+	std::set<int> buffers;
+
+	for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
+	{
+		if (containsSubType(shader->getDefaultBlock().variables[ndx].varType, glu::TYPE_UINT_ATOMIC_COUNTER))
+		{
+			DE_ASSERT(shader->getDefaultBlock().variables[ndx].layout.binding != -1);
+			buffers.insert(shader->getDefaultBlock().variables[ndx].layout.binding);
+		}
+	}
+
+	return (int)buffers.size();
+}
+
+template <bool B>
+static bool dummyConstantTypeFilter (glu::DataType d)
+{
+	DE_UNREF(d);
+	return B;
+}
+
+static int getNumTypeInstances (const glu::VarType& complexType, bool (*predicate)(glu::DataType))
+{
+	if (complexType.isBasicType())
+	{
+		if (predicate(complexType.getBasicType()))
+			return 1;
+		else
+			return 0;
+	}
+	else if (complexType.isArrayType())
+	{
+		const int arraySize = (complexType.getArraySize() == glu::VarType::UNSIZED_ARRAY) ? (1) : (complexType.getArraySize());
+		return arraySize * getNumTypeInstances(complexType.getElementType(), predicate);
+	}
+	else if (complexType.isStructType())
+	{
+		int sum = 0;
+		for (int ndx = 0; ndx < complexType.getStructPtr()->getNumMembers(); ++ndx)
+			sum += getNumTypeInstances(complexType.getStructPtr()->getMember(ndx).getType(), predicate);
+		return sum;
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return false;
+	}
+}
+
+static int getMappedBasicTypeSum (const glu::VarType& complexType, int (*typeMap)(glu::DataType))
+{
+	if (complexType.isBasicType())
+		return typeMap(complexType.getBasicType());
+	else if (complexType.isArrayType())
+	{
+		const int arraySize = (complexType.getArraySize() == glu::VarType::UNSIZED_ARRAY) ? (1) : (complexType.getArraySize());
+		return arraySize * getMappedBasicTypeSum(complexType.getElementType(), typeMap);
+	}
+	else if (complexType.isStructType())
+	{
+		int sum = 0;
+		for (int ndx = 0; ndx < complexType.getStructPtr()->getNumMembers(); ++ndx)
+			sum += getMappedBasicTypeSum(complexType.getStructPtr()->getMember(ndx).getType(), typeMap);
+		return sum;
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return false;
+	}
+}
+
+static int getNumTypeInstances (const ProgramInterfaceDefinition::Shader* shader, glu::Storage storage, bool (*predicate)(glu::DataType))
+{
+	int retVal = 0;
+
+	for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
+	{
+		if (shader->getDefaultBlock().interfaceBlocks[ndx].storage == storage)
+		{
+			int numInstances = 1;
+
+			for (int dimensionNdx = 0; dimensionNdx < (int)shader->getDefaultBlock().interfaceBlocks[ndx].dimensions.size(); ++dimensionNdx)
+				numInstances *= shader->getDefaultBlock().interfaceBlocks[ndx].dimensions[dimensionNdx];
+
+			for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().interfaceBlocks[ndx].variables.size(); ++varNdx)
+				retVal += numInstances * getNumTypeInstances(shader->getDefaultBlock().interfaceBlocks[ndx].variables[varNdx].varType, predicate);
+		}
+	}
+
+	for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().variables.size(); ++varNdx)
+		if (shader->getDefaultBlock().variables[varNdx].storage == storage)
+			retVal += getNumTypeInstances(shader->getDefaultBlock().variables[varNdx].varType, predicate);
+
+	return retVal;
+}
+
+static int getMappedBasicTypeSum (const ProgramInterfaceDefinition::Shader* shader, glu::Storage storage, int (*typeMap)(glu::DataType))
+{
+	int retVal = 0;
+
+	for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
+	{
+		if (shader->getDefaultBlock().interfaceBlocks[ndx].storage == storage)
+		{
+			int numInstances = 1;
+
+			for (int dimensionNdx = 0; dimensionNdx < (int)shader->getDefaultBlock().interfaceBlocks[ndx].dimensions.size(); ++dimensionNdx)
+				numInstances *= shader->getDefaultBlock().interfaceBlocks[ndx].dimensions[dimensionNdx];
+
+			for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().interfaceBlocks[ndx].variables.size(); ++varNdx)
+				retVal += numInstances * getMappedBasicTypeSum(shader->getDefaultBlock().interfaceBlocks[ndx].variables[varNdx].varType, typeMap);
+		}
+	}
+
+	for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().variables.size(); ++varNdx)
+		if (shader->getDefaultBlock().variables[varNdx].storage == storage)
+			retVal += getMappedBasicTypeSum(shader->getDefaultBlock().variables[varNdx].varType, typeMap);
+
+	return retVal;
+}
+
+static int getNumDataTypeComponents (glu::DataType type)
+{
+	if (glu::isDataTypeScalarOrVector(type) || glu::isDataTypeMatrix(type))
+		return glu::getDataTypeScalarSize(type);
+	else
+		return 0;
+}
+
+static int getNumDataTypeVectors (glu::DataType type)
+{
+	if (glu::isDataTypeScalar(type))
+		return 1;
+	else if (glu::isDataTypeVector(type))
+		return 1;
+	else if (glu::isDataTypeMatrix(type))
+		return glu::getDataTypeMatrixNumColumns(type);
+	else
+		return 0;
+}
+
+static int getNumComponents (const ProgramInterfaceDefinition::Shader* shader, glu::Storage storage)
+{
+	return getMappedBasicTypeSum(shader, storage, getNumDataTypeComponents);
+}
+
+static int getNumVectors (const ProgramInterfaceDefinition::Shader* shader, glu::Storage storage)
+{
+	return getMappedBasicTypeSum(shader, storage, getNumDataTypeVectors);
+}
+
+static int getNumDefaultBlockComponents (const ProgramInterfaceDefinition::Shader* shader, glu::Storage storage)
+{
+	int retVal = 0;
+
+	for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().variables.size(); ++varNdx)
+		if (shader->getDefaultBlock().variables[varNdx].storage == storage)
+			retVal += getMappedBasicTypeSum(shader->getDefaultBlock().variables[varNdx].varType, getNumDataTypeComponents);
+
+	return retVal;
+}
+
+static int getMaxBufferBinding (const ProgramInterfaceDefinition::Shader* shader, glu::Storage storage)
+{
+	int maxBinding = -1;
+
+	for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
+	{
+		if (shader->getDefaultBlock().interfaceBlocks[ndx].storage == storage)
+		{
+			const int	binding			= (shader->getDefaultBlock().interfaceBlocks[ndx].layout.binding == -1) ? (0) : (shader->getDefaultBlock().interfaceBlocks[ndx].layout.binding);
+			int			numInstances	= 1;
+
+			for (int dimensionNdx = 0; dimensionNdx < (int)shader->getDefaultBlock().interfaceBlocks[ndx].dimensions.size(); ++dimensionNdx)
+				numInstances *= shader->getDefaultBlock().interfaceBlocks[ndx].dimensions[dimensionNdx];
+
+			maxBinding = de::max(maxBinding, binding + numInstances - 1);
+		}
+	}
+
+	return (int)maxBinding;
+}
+
+static int getBufferTypeSize (glu::DataType type, glu::MatrixOrder order)
+{
+	// assume vec4 alignments, should produce values greater than or equal to the actual resource usage
+	int numVectors = 0;
+
+	if (glu::isDataTypeScalarOrVector(type))
+		numVectors = 1;
+	else if (glu::isDataTypeMatrix(type) && order == glu::MATRIXORDER_ROW_MAJOR)
+		numVectors = glu::getDataTypeMatrixNumRows(type);
+	else if (glu::isDataTypeMatrix(type) && order != glu::MATRIXORDER_ROW_MAJOR)
+		numVectors = glu::getDataTypeMatrixNumColumns(type);
+	else
+		DE_ASSERT(false);
+
+	return 4 * numVectors;
+}
+
+static int getBufferVariableSize (const glu::VarType& type, glu::MatrixOrder order)
+{
+	if (type.isBasicType())
+		return getBufferTypeSize(type.getBasicType(), order);
+	else if (type.isArrayType())
+	{
+		const int arraySize = (type.getArraySize() == glu::VarType::UNSIZED_ARRAY) ? (1) : (type.getArraySize());
+		return arraySize * getBufferVariableSize(type.getElementType(), order);
+	}
+	else if (type.isStructType())
+	{
+		int sum = 0;
+		for (int ndx = 0; ndx < type.getStructPtr()->getNumMembers(); ++ndx)
+			sum += getBufferVariableSize(type.getStructPtr()->getMember(ndx).getType(), order);
+		return sum;
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return false;
+	}
+}
+
+static int getBufferSize (const glu::InterfaceBlock& block, glu::MatrixOrder blockOrder)
+{
+	int size = 0;
+
+	for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
+		size += getBufferVariableSize(block.variables[ndx].varType, (block.variables[ndx].layout.matrixOrder == glu::MATRIXORDER_LAST) ? (blockOrder) : (block.variables[ndx].layout.matrixOrder));
+
+	return size;
+}
+
+static int getBufferMaxSize (const ProgramInterfaceDefinition::Shader* shader, glu::Storage storage)
+{
+	int maxSize = 0;
+
+	for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
+		if (shader->getDefaultBlock().interfaceBlocks[ndx].storage == storage)
+			maxSize = de::max(maxSize, getBufferSize(shader->getDefaultBlock().interfaceBlocks[ndx], shader->getDefaultBlock().interfaceBlocks[ndx].layout.matrixOrder));
+
+	return (int)maxSize;
+}
+
+static int getAtomicCounterMaxBinding (const ProgramInterfaceDefinition::Shader* shader)
+{
+	int maxBinding = -1;
+
+	for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
+	{
+		if (containsSubType(shader->getDefaultBlock().variables[ndx].varType, glu::TYPE_UINT_ATOMIC_COUNTER))
+		{
+			DE_ASSERT(shader->getDefaultBlock().variables[ndx].layout.binding != -1);
+			maxBinding = de::max(maxBinding, shader->getDefaultBlock().variables[ndx].layout.binding);
+		}
+	}
+
+	return (int)maxBinding;
+}
+
+static int getUniformMaxBinding (const ProgramInterfaceDefinition::Shader* shader, bool (*predicate)(glu::DataType))
+{
+	int maxBinding = -1;
+
+	for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
+		maxBinding = de::max(maxBinding, shader->getDefaultBlock().variables[ndx].layout.binding + getNumTypeInstances(shader->getDefaultBlock().variables[ndx].varType, predicate));
+
+	return (int)maxBinding;
+}
+
+static int getAtomicCounterMaxBufferSize (const ProgramInterfaceDefinition::Shader* shader)
+{
+	std::map<int, int>	bufferSizes;
+	int					maxSize			= 0;
+
+	for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
+	{
+		if (containsSubType(shader->getDefaultBlock().variables[ndx].varType, glu::TYPE_UINT_ATOMIC_COUNTER))
+		{
+			const int bufferBinding	= shader->getDefaultBlock().variables[ndx].layout.binding;
+			const int offset		= (shader->getDefaultBlock().variables[ndx].layout.offset == -1) ? (0) : (shader->getDefaultBlock().variables[ndx].layout.offset);
+			const int size			= offset + 4 * getNumTypeInstances(shader->getDefaultBlock().variables[ndx].varType, glu::isDataTypeAtomicCounter);
+
+			DE_ASSERT(shader->getDefaultBlock().variables[ndx].layout.binding != -1);
+
+			if (bufferSizes.find(bufferBinding) == bufferSizes.end())
+				bufferSizes[bufferBinding] = size;
+			else
+				bufferSizes[bufferBinding] = de::max<int>(bufferSizes[bufferBinding], size);
+		}
+	}
+
+	for (std::map<int, int>::iterator it = bufferSizes.begin(); it != bufferSizes.end(); ++it)
+		maxSize = de::max<int>(maxSize, it->second);
+
+	return maxSize;
+}
+
+static int getNumFeedbackVaryingComponents (const ProgramInterfaceDefinition::Program* program, const std::string& name)
+{
+	std::vector<VariablePathComponent> path;
+
+	if (name == "gl_Position")
+		return 4;
+
+	DE_ASSERT(deStringBeginsWith(name.c_str(), "gl_") == DE_FALSE);
+
+	if (!traverseProgramVariablePath(path, program, name, VariableSearchFilter(glu::SHADERTYPE_VERTEX, glu::STORAGE_OUT)))
+		DE_ASSERT(false); // Program failed validate, invalid operation
+
+	return getMappedBasicTypeSum(*path.back().getVariableType(), getNumDataTypeComponents);
+}
+
+static int getNumXFBComponents (const ProgramInterfaceDefinition::Program* program)
+{
+	int numComponents = 0;
+
+	for (int ndx = 0; ndx < (int)program->getTransformFeedbackVaryings().size(); ++ndx)
+		numComponents += getNumFeedbackVaryingComponents(program, program->getTransformFeedbackVaryings()[ndx]);
+
+	return numComponents;
+}
+
+static int getNumMaxXFBOutputComponents (const ProgramInterfaceDefinition::Program* program)
+{
+	int numComponents = 0;
+
+	for (int ndx = 0; ndx < (int)program->getTransformFeedbackVaryings().size(); ++ndx)
+		numComponents = de::max(numComponents, getNumFeedbackVaryingComponents(program, program->getTransformFeedbackVaryings()[ndx]));
+
+	return numComponents;
+}
+
+} // anonymous
+
+std::vector<std::string> getProgramInterfaceBlockMemberResourceList (const glu::InterfaceBlock& interfaceBlock)
+{
+	const std::string			namePrefix					= (!interfaceBlock.instanceName.empty()) ? (interfaceBlock.interfaceName + ".") : ("");
+	const bool					isTopLevelBufferVariable	= (interfaceBlock.storage == glu::STORAGE_BUFFER);
+	std::vector<std::string>	resources;
+
+	for (int variableNdx = 0; variableNdx < (int)interfaceBlock.variables.size(); ++variableNdx)
+		generateVariableTypeResourceNames(resources, namePrefix + interfaceBlock.variables[variableNdx].name, interfaceBlock.variables[variableNdx].varType, isTopLevelBufferVariable);
+
+	return resources;
+}
+
+std::vector<std::string> getProgramInterfaceResourceList (const ProgramInterfaceDefinition::Program* program, ProgramInterface interface)
+{
+	// The same {uniform (block), buffer (variable)} can exist in multiple shaders, remove duplicates but keep order
+	const bool					removeDuplicated	= (interface == PROGRAMINTERFACE_UNIFORM)			||
+													  (interface == PROGRAMINTERFACE_UNIFORM_BLOCK)		||
+													  (interface == PROGRAMINTERFACE_BUFFER_VARIABLE)	||
+													  (interface == PROGRAMINTERFACE_SHADER_STORAGE_BLOCK);
+	std::vector<std::string>	resources;
+
+	switch (interface)
+	{
+		case PROGRAMINTERFACE_UNIFORM:
+		case PROGRAMINTERFACE_BUFFER_VARIABLE:
+		{
+			const glu::Storage storage = (interface == PROGRAMINTERFACE_UNIFORM) ? (glu::STORAGE_UNIFORM) : (glu::STORAGE_BUFFER);
+
+			for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
+			{
+				const ProgramInterfaceDefinition::Shader* shader = program->getShaders()[shaderNdx];
+
+				for (int variableNdx = 0; variableNdx < (int)shader->getDefaultBlock().variables.size(); ++variableNdx)
+					if (shader->getDefaultBlock().variables[variableNdx].storage == storage)
+						generateVariableTypeResourceNames(resources, shader->getDefaultBlock().variables[variableNdx].name, shader->getDefaultBlock().variables[variableNdx].varType, false);
+
+				for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
+				{
+					const glu::InterfaceBlock& interfaceBlock = shader->getDefaultBlock().interfaceBlocks[interfaceNdx];
+					if (interfaceBlock.storage == storage)
+					{
+						const std::vector<std::string> blockResources = getProgramInterfaceBlockMemberResourceList(interfaceBlock);
+						resources.insert(resources.end(), blockResources.begin(), blockResources.end());
+					}
+				}
+			}
+			break;
+		}
+
+		case PROGRAMINTERFACE_UNIFORM_BLOCK:
+		case PROGRAMINTERFACE_SHADER_STORAGE_BLOCK:
+		{
+			const glu::Storage storage = (interface == PROGRAMINTERFACE_UNIFORM_BLOCK) ? (glu::STORAGE_UNIFORM) : (glu::STORAGE_BUFFER);
+
+			for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
+			{
+				const ProgramInterfaceDefinition::Shader* shader = program->getShaders()[shaderNdx];
+				for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
+				{
+					const glu::InterfaceBlock& interfaceBlock = shader->getDefaultBlock().interfaceBlocks[interfaceNdx];
+					if (interfaceBlock.storage == storage)
+					{
+						std::vector<int> index(interfaceBlock.dimensions.size(), 0);
+
+						for (;;)
+						{
+							// add resource string for each element
+							{
+								std::ostringstream name;
+								name << interfaceBlock.interfaceName;
+
+								for (int dimensionNdx = 0; dimensionNdx < (int)interfaceBlock.dimensions.size(); ++dimensionNdx)
+									name << "[" << index[dimensionNdx] << "]";
+
+								resources.push_back(name.str());
+							}
+
+							// increment index
+							if (!incrementMultiDimensionIndex(index, interfaceBlock.dimensions))
+								break;
+						}
+					}
+				}
+			}
+			break;
+		}
+
+		case PROGRAMINTERFACE_PROGRAM_INPUT:
+		case PROGRAMINTERFACE_PROGRAM_OUTPUT:
+		{
+			const glu::Storage		storage		= (interface == PROGRAMINTERFACE_PROGRAM_INPUT) ? (glu::STORAGE_IN) : (glu::STORAGE_OUT);
+			const glu::ShaderType	shaderType	= (interface == PROGRAMINTERFACE_PROGRAM_INPUT) ? (program->getFirstStage()) : (program->getLastStage());
+
+			for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
+			{
+				const ProgramInterfaceDefinition::Shader* shader = program->getShaders()[shaderNdx];
+
+				if (shader->getType() != shaderType)
+					continue;
+
+				for (int variableNdx = 0; variableNdx < (int)shader->getDefaultBlock().variables.size(); ++variableNdx)
+					if (shader->getDefaultBlock().variables[variableNdx].storage == storage)
+						generateVariableTypeResourceNames(resources, shader->getDefaultBlock().variables[variableNdx].name, shader->getDefaultBlock().variables[variableNdx].varType, false);
+
+				for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
+				{
+					const glu::InterfaceBlock& interfaceBlock = shader->getDefaultBlock().interfaceBlocks[interfaceNdx];
+					if (interfaceBlock.storage == storage)
+					{
+						const std::vector<std::string> blockResources = getProgramInterfaceBlockMemberResourceList(interfaceBlock);
+						resources.insert(resources.end(), blockResources.begin(), blockResources.end());
+					}
+				}
+			}
+
+			break;
+		}
+
+		case PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING:
+		{
+			for (int varyingNdx = 0; varyingNdx < (int)program->getTransformFeedbackVaryings().size(); ++varyingNdx)
+			{
+				const std::string					varyingName = program->getTransformFeedbackVaryings()[varyingNdx];
+				std::vector<VariablePathComponent>	path;
+
+				if (deStringBeginsWith(varyingName.c_str(), "gl_"))
+					resources.push_back(varyingName); // builtin
+				else if (traverseProgramVariablePath(path, program, varyingName, VariableSearchFilter(glu::SHADERTYPE_VERTEX, glu::STORAGE_OUT)))
+					generateVariableTypeResourceNames(resources, varyingName, *path.back().getVariableType(), false);
+				else
+					DE_ASSERT(false); // Program failed validate, invalid operation
+			}
+
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	if (removeDuplicated)
+	{
+		std::set<std::string>		addedVariables;
+		std::vector<std::string>	uniqueResouces;
+
+		for (int ndx = 0; ndx < (int)resources.size(); ++ndx)
+		{
+			if (addedVariables.find(resources[ndx]) == addedVariables.end())
+			{
+				addedVariables.insert(resources[ndx]);
+				uniqueResouces.push_back(resources[ndx]);
+			}
+		}
+
+		uniqueResouces.swap(resources);
+	}
+
+	return resources;
+}
+
+glu::ProgramSources generateProgramInterfaceProgramSources (const ProgramInterfaceDefinition::Program* program)
+{
+	glu::ProgramSources sources;
+
+	DE_ASSERT(program->isValid());
+
+	for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
+	{
+		const ProgramInterfaceDefinition::Shader*	shader						= program->getShaders()[shaderNdx];
+		bool										containsUserDefinedOutputs	= false;
+		bool										containsUserDefinedInputs	= false;
+		std::ostringstream							sourceBuf;
+		std::ostringstream							usageBuf;
+
+		sourceBuf	<< glu::getGLSLVersionDeclaration(shader->getVersion()) << "\n"
+					<< getShaderTypeDeclarations(shader->getType())
+					<< "\n";
+
+		// Struct definitions
+
+		writeStructureDefinitions(sourceBuf, shader->getDefaultBlock());
+
+		// variables in the default scope
+
+		for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
+			sourceBuf << shader->getDefaultBlock().variables[ndx] << ";\n";
+
+		if (!shader->getDefaultBlock().variables.empty())
+			sourceBuf << "\n";
+
+		// Interface blocks
+
+		for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
+			writeInterfaceBlock(sourceBuf, shader->getDefaultBlock().interfaceBlocks[ndx]);
+
+		// Use inputs and outputs so that they won't be removed by the optimizer
+
+		usageBuf <<	"highp vec4 readInputs()\n"
+					"{\n"
+					"	highp vec4 retValue = vec4(0.0);\n";
+
+		// User-defined inputs
+
+		for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
+		{
+			if (shader->getDefaultBlock().variables[ndx].storage == glu::STORAGE_IN ||
+				shader->getDefaultBlock().variables[ndx].storage == glu::STORAGE_UNIFORM)
+			{
+				writeVariableReadAccumulateExpression(usageBuf, "retValue", shader->getDefaultBlock().variables[ndx].name, shader->getDefaultBlock().variables[ndx].varType);
+				containsUserDefinedInputs = true;
+			}
+		}
+
+		for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
+		{
+			const glu::InterfaceBlock& interface = shader->getDefaultBlock().interfaceBlocks[interfaceNdx];
+			if (interface.storage == glu::STORAGE_UNIFORM ||
+				(interface.storage == glu::STORAGE_BUFFER && (interface.memoryAccessQualifierFlags & glu::MEMORYACCESSQUALIFIER_WRITEONLY_BIT) == 0))
+			{
+				writeInterfaceReadAccumulateExpression(usageBuf, "retValue", interface);
+				containsUserDefinedInputs = true;
+			}
+		}
+
+		// Built-in-inputs
+
+		if (!containsUserDefinedInputs)
+		{
+			if (shader->getType() == glu::SHADERTYPE_VERTEX)
+				usageBuf << "	retValue += vec4(float(gl_VertexID));\n";
+			else if (shader->getType() == glu::SHADERTYPE_FRAGMENT)
+				usageBuf << "	retValue += gl_FragCoord;\n";
+			else if (shader->getType() == glu::SHADERTYPE_GEOMETRY)
+				usageBuf << "	retValue += gl_in[0].gl_Position;\n";
+			else if (shader->getType() == glu::SHADERTYPE_TESSELLATION_CONTROL)
+				usageBuf << "	retValue += gl_in[0].gl_Position;\n";
+			else if (shader->getType() == glu::SHADERTYPE_TESSELLATION_EVALUATION)
+				usageBuf << "	retValue += gl_in[0].gl_Position;\n";
+			else if (shader->getType() == glu::SHADERTYPE_COMPUTE)
+				usageBuf << "	retValue += vec4(float(gl_NumWorkGroups.x));\n";
+		}
+
+		usageBuf <<	"	return retValue;\n"
+					"}\n\n";
+
+		usageBuf <<	"void writeOutputs(in highp vec4 dummyValue)\n"
+					"{\n";
+
+		// User-defined outputs
+
+		for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
+		{
+			if (shader->getDefaultBlock().variables[ndx].storage == glu::STORAGE_OUT)
+			{
+				writeVariableWriteExpression(usageBuf, "dummyValue", shader->getDefaultBlock().variables[ndx].name, shader->getDefaultBlock().variables[ndx].varType);
+				containsUserDefinedOutputs = true;
+			}
+		}
+
+		for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
+		{
+			const glu::InterfaceBlock& interface = shader->getDefaultBlock().interfaceBlocks[interfaceNdx];
+			if (interface.storage == glu::STORAGE_BUFFER && (interface.memoryAccessQualifierFlags & glu::MEMORYACCESSQUALIFIER_READONLY_BIT) == 0)
+			{
+				writeInterfaceWriteExpression(usageBuf, "dummyValue", interface);
+				containsUserDefinedOutputs = true;
+			}
+		}
+
+		// Builtin-outputs that must be written to
+
+		if (shader->getType() == glu::SHADERTYPE_VERTEX)
+			usageBuf << "	gl_Position = dummyValue;\n";
+		else if (shader->getType() == glu::SHADERTYPE_GEOMETRY)
+			usageBuf << "	gl_Position = dummyValue;\n"
+						 "	EmitVertex();\n";
+		else if (shader->getType() == glu::SHADERTYPE_TESSELLATION_CONTROL)
+			usageBuf << "	gl_out[gl_InvocationID].gl_Position = dummyValue;\n";
+		else if (shader->getType() == glu::SHADERTYPE_TESSELLATION_EVALUATION)
+			usageBuf << "	gl_Position = dummyValue;\n";
+
+		// Output to sink input data to
+
+		if (!containsUserDefinedOutputs)
+		{
+			if (shader->getType() == glu::SHADERTYPE_FRAGMENT)
+				usageBuf << "	gl_FragDepth = dot(dummyValue.xy, dummyValue.xw);\n";
+			else if (shader->getType() == glu::SHADERTYPE_COMPUTE)
+				usageBuf << "	dummyOutputBlock.dummyValue = dummyValue;\n";
+		}
+
+		usageBuf <<	"}\n\n"
+					"void main()\n"
+					"{\n"
+					"	writeOutputs(readInputs());\n"
+					"}\n";
+
+		// Interface for dummy output
+
+		if (shader->getType() == glu::SHADERTYPE_COMPUTE && !containsUserDefinedOutputs)
+		{
+			sourceBuf	<< "writeonly buffer DummyOutputInterface\n"
+						<< "{\n"
+						<< "	highp vec4 dummyValue;\n"
+						<< "} dummyOutputBlock;\n\n";
+		}
+
+		sources << glu::ShaderSource(shader->getType(), sourceBuf.str() + usageBuf.str());
+	}
+
+	if (program->isSeparable())
+		sources << glu::ProgramSeparable(true);
+
+	for (int ndx = 0; ndx < (int)program->getTransformFeedbackVaryings().size(); ++ndx)
+		sources << glu::TransformFeedbackVarying(program->getTransformFeedbackVaryings()[ndx]);
+
+	if (program->getTransformFeedbackMode())
+		sources << glu::TransformFeedbackMode(program->getTransformFeedbackMode());
+
+	return sources;
+}
+
+bool findProgramVariablePathByPathName (std::vector<VariablePathComponent>& typePath, const ProgramInterfaceDefinition::Program* program, const std::string& pathName, const VariableSearchFilter& filter)
+{
+	std::vector<VariablePathComponent> modifiedPath;
+
+	if (!traverseProgramVariablePath(modifiedPath, program, pathName, filter))
+		return false;
+
+	// modify param only on success
+	typePath.swap(modifiedPath);
+	return true;
+}
+
+ProgramInterfaceDefinition::ShaderResourceUsage getShaderResourceUsage (const ProgramInterfaceDefinition::Shader* shader)
+{
+	ProgramInterfaceDefinition::ShaderResourceUsage retVal;
+
+	retVal.numInputs						= getNumTypeInstances(shader, glu::STORAGE_IN, dummyConstantTypeFilter<true>);
+	retVal.numInputVectors					= getNumVectors(shader, glu::STORAGE_IN);
+	retVal.numInputComponents				= getNumComponents(shader, glu::STORAGE_IN);
+
+	retVal.numOutputs						= getNumTypeInstances(shader, glu::STORAGE_OUT, dummyConstantTypeFilter<true>);
+	retVal.numOutputVectors					= getNumVectors(shader, glu::STORAGE_OUT);
+	retVal.numOutputComponents				= getNumComponents(shader, glu::STORAGE_OUT);
+
+	retVal.numDefaultBlockUniformComponents	= getNumDefaultBlockComponents(shader, glu::STORAGE_UNIFORM);
+	retVal.numCombinedUniformComponents		= getNumComponents(shader, glu::STORAGE_UNIFORM);
+	retVal.numUniformVectors				= getNumVectors(shader, glu::STORAGE_UNIFORM);
+
+	retVal.numSamplers						= getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeSampler);
+	retVal.numImages						= getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeImage);
+
+	retVal.numAtomicCounterBuffers			= getNumAtomicCounterBuffers(shader);
+	retVal.numAtomicCounters				= getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeAtomicCounter);
+
+	retVal.numUniformBlocks					= getNumShaderBlocks(shader, glu::STORAGE_UNIFORM);
+	retVal.numShaderStorageBlocks			= getNumShaderBlocks(shader, glu::STORAGE_BUFFER);
+
+	return retVal;
+}
+
+ProgramInterfaceDefinition::ProgramResourceUsage getCombinedProgramResourceUsage (const ProgramInterfaceDefinition::Program* program)
+{
+	ProgramInterfaceDefinition::ProgramResourceUsage retVal;
+
+	retVal.uniformBufferMaxBinding				= 0;
+	retVal.uniformBufferMaxSize					= 0;
+	retVal.numUniformBlocks						= 0;
+	retVal.numCombinedVertexUniformComponents	= 0;
+	retVal.numCombinedFragmentUniformComponents	= 0;
+	retVal.shaderStorageBufferMaxBinding		= 0;
+	retVal.shaderStorageBufferMaxSize			= 0;
+	retVal.numShaderStorageBlocks				= 0;
+	retVal.numVaryingComponents					= 0;
+	retVal.numVaryingVectors					= 0;
+	retVal.numCombinedSamplers					= 0;
+	retVal.atomicCounterBufferMaxBinding		= 0;
+	retVal.atomicCounterBufferMaxSize			= 0;
+	retVal.numAtomicCounterBuffers				= 0;
+	retVal.numAtomicCounters					= 0;
+	retVal.maxImageBinding						= 0;
+	retVal.numCombinedImages					= 0;
+	retVal.numCombinedOutputResources			= 0;
+	retVal.numXFBInterleavedComponents			= 0;
+	retVal.numXFBSeparateAttribs				= 0;
+	retVal.numXFBSeparateComponents				= 0;
+
+	for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
+	{
+		const ProgramInterfaceDefinition::Shader* const shader = program->getShaders()[shaderNdx];
+
+		retVal.uniformBufferMaxBinding		= de::max(retVal.uniformBufferMaxBinding, getMaxBufferBinding(shader, glu::STORAGE_UNIFORM));
+		retVal.uniformBufferMaxSize			= de::max(retVal.uniformBufferMaxSize, getBufferMaxSize(shader, glu::STORAGE_UNIFORM));
+		retVal.numUniformBlocks				+= getNumShaderBlocks(shader, glu::STORAGE_UNIFORM);
+
+		if (shader->getType() == glu::SHADERTYPE_VERTEX)
+			retVal.numCombinedVertexUniformComponents += getNumComponents(shader, glu::STORAGE_UNIFORM);
+
+		if (shader->getType() == glu::SHADERTYPE_FRAGMENT)
+			retVal.numCombinedFragmentUniformComponents += getNumComponents(shader, glu::STORAGE_UNIFORM);
+
+		retVal.shaderStorageBufferMaxBinding	= de::max(retVal.shaderStorageBufferMaxBinding, getMaxBufferBinding(shader, glu::STORAGE_BUFFER));
+		retVal.shaderStorageBufferMaxSize		= de::max(retVal.shaderStorageBufferMaxSize, getBufferMaxSize(shader, glu::STORAGE_BUFFER));
+		retVal.numShaderStorageBlocks			+= getNumShaderBlocks(shader, glu::STORAGE_BUFFER);
+
+		if (shader->getType() == glu::SHADERTYPE_VERTEX)
+		{
+			retVal.numVaryingComponents += getNumComponents(shader, glu::STORAGE_OUT);
+			retVal.numVaryingVectors	+= getNumVectors(shader, glu::STORAGE_OUT);
+		}
+
+		retVal.numCombinedSamplers	+= getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeSampler);
+
+		retVal.atomicCounterBufferMaxBinding	= de::max(retVal.atomicCounterBufferMaxBinding, getAtomicCounterMaxBinding(shader));
+		retVal.atomicCounterBufferMaxSize		= de::max(retVal.atomicCounterBufferMaxSize, getAtomicCounterMaxBufferSize(shader));
+		retVal.numAtomicCounterBuffers			+= getNumAtomicCounterBuffers(shader);
+		retVal.numAtomicCounters				+= getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeAtomicCounter);
+		retVal.maxImageBinding					= de::max(retVal.maxImageBinding, getUniformMaxBinding(shader, glu::isDataTypeImage));
+		retVal.numCombinedImages				+= getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeImage);
+
+		retVal.numCombinedOutputResources		+= getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeImage);
+		retVal.numCombinedOutputResources		+= getNumShaderBlocks(shader, glu::STORAGE_BUFFER);
+		if (shader->getType() == glu::SHADERTYPE_FRAGMENT)
+			retVal.numCombinedOutputResources += getNumVectors(shader, glu::STORAGE_OUT);
+	}
+
+	if (program->getTransformFeedbackMode() == GL_INTERLEAVED_ATTRIBS)
+		retVal.numXFBInterleavedComponents = getNumXFBComponents(program);
+	else if (program->getTransformFeedbackMode() == GL_SEPARATE_ATTRIBS)
+	{
+		retVal.numXFBSeparateAttribs	= (int)program->getTransformFeedbackVaryings().size();
+		retVal.numXFBSeparateComponents	= getNumMaxXFBOutputComponents(program);
+	}
+
+	return retVal;
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fProgramInterfaceDefinitionUtil.hpp b/modules/gles31/functional/es31fProgramInterfaceDefinitionUtil.hpp
new file mode 100644
index 0000000..9e6cea19
--- /dev/null
+++ b/modules/gles31/functional/es31fProgramInterfaceDefinitionUtil.hpp
@@ -0,0 +1,166 @@
+#ifndef _ES31FPROGRAMINTERFACEDEFINITIONUTIL_HPP
+#define _ES31FPROGRAMINTERFACEDEFINITIONUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Program interface utilities
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+#include "gluShaderProgram.hpp"
+#include "es31fProgramInterfaceDefinition.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace ProgramInterfaceDefinition
+{
+
+class Program;
+
+class VariablePathComponent
+{
+public:
+									VariablePathComponent	(void)									:m_type(TYPE_LAST)				{								}
+									VariablePathComponent	(const glu::VarType* type)				:m_type(TYPE_TYPE)				{ m_data.type = type;			}
+									VariablePathComponent	(const glu::InterfaceBlock* block)		:m_type(TYPE_INTERFACEBLOCK)	{ m_data.block = block;			}
+									VariablePathComponent	(const glu::VariableDeclaration* decl)	:m_type(TYPE_DECLARATION)		{ m_data.declaration = decl;	}
+
+									VariablePathComponent	(const VariablePathComponent& other) : m_data(other.m_data), m_type(other.m_type) { }
+	VariablePathComponent&			operator=				(const VariablePathComponent& other) { m_type = other.m_type; m_data = other.m_data; return *this; }
+
+	bool							isVariableType			(void) const { return m_type == TYPE_TYPE;								}
+	bool							isInterfaceBlock		(void) const { return m_type == TYPE_INTERFACEBLOCK;					}
+	bool							isDeclaration			(void) const { return m_type == TYPE_DECLARATION;						}
+
+	const glu::VarType*				getVariableType			(void) const { DE_ASSERT(isVariableType()); return m_data.type;			}
+	const glu::InterfaceBlock*		getInterfaceBlock		(void) const { DE_ASSERT(isInterfaceBlock()); return m_data.block;		}
+	const glu::VariableDeclaration*	getDeclaration			(void) const { DE_ASSERT(isDeclaration()); return m_data.declaration;	}
+
+private:
+	enum Type
+	{
+		TYPE_TYPE,
+		TYPE_INTERFACEBLOCK,
+		TYPE_DECLARATION,
+
+		TYPE_LAST
+	};
+
+	union Data
+	{
+		const glu::VarType*				type;
+		const glu::InterfaceBlock*		block;
+		const glu::VariableDeclaration*	declaration;
+
+		Data (void) : type(DE_NULL) { }
+	} m_data;
+
+	Type m_type;
+};
+
+struct VariableSearchFilter
+{
+								VariableSearchFilter	(glu::ShaderType shaderType, glu::Storage storage) : m_shaderType(shaderType), m_storage(storage), m_null(false) { }
+
+	static VariableSearchFilter	intersection			(const VariableSearchFilter& a, const VariableSearchFilter& b);
+
+	bool						matchesFilter			(const ProgramInterfaceDefinition::Shader* shader) const	{ return !m_null && (m_shaderType == glu::SHADERTYPE_LAST || shader->getType() == m_shaderType);	}
+	bool						matchesFilter			(const glu::VariableDeclaration& variable) const			{ return !m_null && (m_storage == glu::STORAGE_LAST || variable.storage == m_storage);				}
+	bool						matchesFilter			(const glu::InterfaceBlock& block) const					{ return !m_null && (m_storage == glu::STORAGE_LAST || block.storage == m_storage);				}
+
+	glu::ShaderType				getShaderTypeFilter		(void) const												{ return m_shaderType;	}
+	glu::Storage				getStorageFilter		(void) const												{ return m_storage;		}
+
+private:
+								VariableSearchFilter	(glu::ShaderType shaderType, glu::Storage storage, bool empty) : m_shaderType(shaderType), m_storage(storage), m_null(empty) { }
+
+	const glu::ShaderType		m_shaderType;
+	const glu::Storage			m_storage;
+	const bool					m_null;					// !< Null filter does not match any variable
+};
+
+struct ShaderResourceUsage
+{
+	int numInputs;
+	int numInputVectors;
+	int numInputComponents;
+	int numOutputs;
+	int numOutputVectors;
+	int numOutputComponents;
+
+	int numDefaultBlockUniformComponents;
+	int numCombinedUniformComponents;
+	int numUniformVectors;
+
+	int numSamplers;
+	int numImages;
+
+	int numAtomicCounterBuffers;
+	int numAtomicCounters;
+
+	int numUniformBlocks;
+	int numShaderStorageBlocks;
+};
+
+struct ProgramResourceUsage
+{
+	int uniformBufferMaxBinding;
+	int uniformBufferMaxSize;
+	int numUniformBlocks;
+	int numCombinedVertexUniformComponents;
+	int numCombinedFragmentUniformComponents;
+	int shaderStorageBufferMaxBinding;
+	int shaderStorageBufferMaxSize;
+	int numShaderStorageBlocks;
+	int numVaryingComponents;
+	int numVaryingVectors;
+	int numCombinedSamplers;
+	int atomicCounterBufferMaxBinding;
+	int atomicCounterBufferMaxSize;
+	int numAtomicCounterBuffers;
+	int numAtomicCounters;
+	int maxImageBinding;
+	int numCombinedImages;
+	int numCombinedOutputResources;
+	int numXFBInterleavedComponents;
+	int numXFBSeparateAttribs;
+	int numXFBSeparateComponents;
+};
+
+} // ProgramInterfaceDefinition
+
+std::vector<std::string>							getProgramInterfaceResourceList				(const ProgramInterfaceDefinition::Program* program, ProgramInterface interface);
+std::vector<std::string>							getProgramInterfaceBlockMemberResourceList	(const glu::InterfaceBlock& interfaceBlock);
+glu::ProgramSources									generateProgramInterfaceProgramSources		(const ProgramInterfaceDefinition::Program* program);
+bool												findProgramVariablePathByPathName			(std::vector<ProgramInterfaceDefinition::VariablePathComponent>& typePath, const ProgramInterfaceDefinition::Program* program, const std::string& pathName, const ProgramInterfaceDefinition::VariableSearchFilter& filter);
+void												generateVariableTypeResourceNames			(std::vector<std::string>& resources, const std::string& name, const glu::VarType& type, bool isTopLevelBufferVariable);
+ProgramInterfaceDefinition::ShaderResourceUsage		getShaderResourceUsage						(const ProgramInterfaceDefinition::Shader* shader);
+ProgramInterfaceDefinition::ProgramResourceUsage	getCombinedProgramResourceUsage				(const ProgramInterfaceDefinition::Program* program);
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FPROGRAMINTERFACEDEFINITIONUTIL_HPP
diff --git a/modules/gles31/functional/es31fProgramInterfaceQueryTestCase.cpp b/modules/gles31/functional/es31fProgramInterfaceQueryTestCase.cpp
new file mode 100644
index 0000000..382328e
--- /dev/null
+++ b/modules/gles31/functional/es31fProgramInterfaceQueryTestCase.cpp
@@ -0,0 +1,1968 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Program interface query test case
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fProgramInterfaceQueryTestCase.hpp"
+#include "es31fProgramInterfaceDefinitionUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "gluVarTypeUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "gluShaderProgram.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deString.h"
+#include "deStringUtil.hpp"
+#include "deSTLUtil.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+using ProgramInterfaceDefinition::VariablePathComponent;
+using ProgramInterfaceDefinition::VariableSearchFilter;
+
+static bool stringEndsWith (const std::string& str, const std::string& suffix)
+{
+	if (suffix.length() > str.length())
+		return false;
+	else
+		return str.substr(str.length() - suffix.length()) == suffix;
+}
+
+static glw::GLenum getProgramDefaultBlockInterfaceFromStorage (glu::Storage storage)
+{
+	switch (storage)
+	{
+		case glu::STORAGE_IN:		return GL_PROGRAM_INPUT;
+		case glu::STORAGE_OUT:		return GL_PROGRAM_OUTPUT;
+		case glu::STORAGE_UNIFORM:	return GL_UNIFORM;
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static bool isBufferBackedInterfaceBlockStorage (glu::Storage storage)
+{
+	return storage == glu::STORAGE_BUFFER || storage == glu::STORAGE_UNIFORM;
+}
+
+static int getTypeSize (glu::DataType type)
+{
+	if (type == glu::TYPE_FLOAT)
+		return 4;
+	else if (type == glu::TYPE_INT || type == glu::TYPE_UINT)
+		return 4;
+	else if (type == glu::TYPE_BOOL)
+		return 4; // uint
+
+	DE_ASSERT(false);
+	return 0;
+}
+
+static int getVarTypeSize (const glu::VarType& type)
+{
+	if (type.isBasicType())
+	{
+		// return in basic machine units
+		return glu::getDataTypeScalarSize(type.getBasicType()) * getTypeSize(glu::getDataTypeScalarType(type.getBasicType()));
+	}
+	else if (type.isStructType())
+	{
+		int size = 0;
+		for (int ndx = 0; ndx < type.getStructPtr()->getNumMembers(); ++ndx)
+			size += getVarTypeSize(type.getStructPtr()->getMember(ndx).getType());
+		return size;
+	}
+	else if (type.isArrayType())
+	{
+		// unsized arrays are handled as if they had only one element
+		if (type.getArraySize() == glu::VarType::UNSIZED_ARRAY)
+			return getVarTypeSize(type.getElementType());
+		else
+			return type.getArraySize() * getVarTypeSize(type.getElementType());
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return 0;
+	}
+}
+
+static glu::MatrixOrder getMatrixOrderFromPath (const std::vector<VariablePathComponent>& path)
+{
+	glu::MatrixOrder order = glu::MATRIXORDER_LAST;
+
+	// inherit majority
+	for (int pathNdx = 0; pathNdx < (int)path.size(); ++pathNdx)
+	{
+		glu::MatrixOrder matOrder;
+
+		if (path[pathNdx].isInterfaceBlock())
+			matOrder = path[pathNdx].getInterfaceBlock()->layout.matrixOrder;
+		else if (path[pathNdx].isDeclaration())
+			matOrder = path[pathNdx].getDeclaration()->layout.matrixOrder;
+		else if (path[pathNdx].isVariableType())
+			matOrder = glu::MATRIXORDER_LAST;
+		else
+		{
+			DE_ASSERT(false);
+			return glu::MATRIXORDER_LAST;
+		}
+
+		if (matOrder != glu::MATRIXORDER_LAST)
+			order = matOrder;
+	}
+
+	return order;
+}
+
+class PropValidator
+{
+public:
+									PropValidator					(Context& context, ProgramResourcePropFlags validationProp, const char* requiredExtension = "");
+
+	virtual std::string				getHumanReadablePropertyString	(glw::GLint propVal) const;
+	virtual void					validate						(const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const = 0;
+
+	bool							isSupported						(void) const;
+	bool							isSelected						(deUint32 caseFlags) const;
+
+protected:
+	void							setError						(const std::string& err) const;
+
+	tcu::TestContext&				m_testCtx;
+	const glu::RenderContext&		m_renderContext;
+
+private:
+	const glu::ContextInfo&			m_contextInfo;
+	const std::string				m_extension;
+	const ProgramResourcePropFlags	m_validationProp;
+};
+
+PropValidator::PropValidator (Context& context, ProgramResourcePropFlags validationProp, const char* requiredExtension)
+	: m_testCtx			(context.getTestContext())
+	, m_renderContext	(context.getRenderContext())
+	, m_contextInfo		(context.getContextInfo())
+	, m_extension		(requiredExtension)
+	, m_validationProp	(validationProp)
+{
+}
+
+std::string PropValidator::getHumanReadablePropertyString (glw::GLint propVal) const
+{
+	return de::toString(propVal);
+}
+
+bool PropValidator::isSupported (void) const
+{
+	return m_extension.empty() || m_contextInfo.isExtensionSupported(m_extension.c_str());
+}
+
+bool PropValidator::isSelected (deUint32 caseFlags) const
+{
+	return (caseFlags & (deUint32)m_validationProp) != 0;
+}
+
+void PropValidator::setError (const std::string& err) const
+{
+	// don't overwrite earlier errors
+	if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, err.c_str());
+}
+
+class SingleVariableValidator : public PropValidator
+{
+public:
+					SingleVariableValidator	(Context& context, ProgramResourcePropFlags validationProp, glw::GLuint programID, const VariableSearchFilter& filter, const char* requiredExtension = "");
+
+	void			validate				(const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const;
+	virtual void	validateSingleVariable	(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const = 0;
+	virtual void	validateBuiltinVariable	(const std::string& resource, glw::GLint propValue) const;
+
+protected:
+	const VariableSearchFilter	m_filter;
+	const glw::GLuint			m_programID;
+};
+
+SingleVariableValidator::SingleVariableValidator (Context& context, ProgramResourcePropFlags validationProp, glw::GLuint programID, const VariableSearchFilter& filter, const char* requiredExtension)
+	: PropValidator	(context, validationProp, requiredExtension)
+	, m_filter		(filter)
+	, m_programID	(programID)
+{
+}
+
+void SingleVariableValidator::validate (const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const
+{
+	std::vector<VariablePathComponent> path;
+
+	if (findProgramVariablePathByPathName(path, program, resource, m_filter))
+	{
+		const glu::VarType* variable = (path.back().isVariableType()) ? (path.back().getVariableType()) : (DE_NULL);
+
+		if (!variable || !variable->isBasicType())
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, resource name \"" << resource << "\" refers to a non-basic type." << tcu::TestLog::EndMessage;
+			setError("resource not basic type");
+		}
+		else
+			validateSingleVariable(path, resource, propValue);
+
+		// finding matching variable in any shader is sufficient
+		return;
+	}
+	else if (deStringBeginsWith(resource.c_str(), "gl_"))
+	{
+		// special case for builtins
+		validateBuiltinVariable(resource, propValue);
+		return;
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Error, could not find resource \"" << resource << "\" in the program" << tcu::TestLog::EndMessage;
+	setError("could not find resource");
+}
+
+void SingleVariableValidator::validateBuiltinVariable (const std::string& resource, glw::GLint propValue) const
+{
+	DE_UNREF(propValue);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Error, could not find builtin resource \"" << resource << "\" in the program" << tcu::TestLog::EndMessage;
+	setError("could not find builtin resource");
+}
+
+class SingleBlockValidator : public PropValidator
+{
+public:
+								SingleBlockValidator	(Context& context, ProgramResourcePropFlags validationProp, glw::GLuint programID, const VariableSearchFilter& filter, const char* requiredExtension = "");
+
+	void						validate				(const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const;
+	virtual void				validateSingleBlock		(const glu::InterfaceBlock& block, const std::vector<int>& instanceIndex, const std::string& resource, glw::GLint propValue) const = 0;
+
+protected:
+	const VariableSearchFilter	m_filter;
+	const glw::GLuint			m_programID;
+};
+
+SingleBlockValidator::SingleBlockValidator (Context& context, ProgramResourcePropFlags validationProp, glw::GLuint programID, const VariableSearchFilter& filter, const char* requiredExtension)
+	: PropValidator	(context, validationProp, requiredExtension)
+	, m_filter		(filter)
+	, m_programID	(programID)
+{
+}
+
+void SingleBlockValidator::validate (const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const
+{
+	glu::VarTokenizer	tokenizer		(resource.c_str());
+	const std::string	blockName		= tokenizer.getIdentifier();
+	std::vector<int>	instanceIndex;
+
+	tokenizer.advance();
+
+	// array index
+	while (tokenizer.getToken() == glu::VarTokenizer::TOKEN_LEFT_BRACKET)
+	{
+		tokenizer.advance();
+		DE_ASSERT(tokenizer.getToken() == glu::VarTokenizer::TOKEN_NUMBER);
+
+		instanceIndex.push_back(tokenizer.getNumber());
+
+		tokenizer.advance();
+		DE_ASSERT(tokenizer.getToken() == glu::VarTokenizer::TOKEN_RIGHT_BRACKET);
+
+		tokenizer.advance();
+	}
+
+	// no trailing garbage
+	DE_ASSERT(tokenizer.getToken() == glu::VarTokenizer::TOKEN_END);
+
+	for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
+	{
+		const ProgramInterfaceDefinition::Shader* const shader = program->getShaders()[shaderNdx];
+		if (!m_filter.matchesFilter(shader))
+			continue;
+
+		for (int blockNdx = 0; blockNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++blockNdx)
+		{
+			const glu::InterfaceBlock& block = shader->getDefaultBlock().interfaceBlocks[blockNdx];
+
+			if (m_filter.matchesFilter(block) && block.interfaceName == blockName)
+			{
+				// dimensions match
+				DE_ASSERT(instanceIndex.size() == block.dimensions.size());
+
+				validateSingleBlock(block, instanceIndex, resource, propValue);
+				return;
+			}
+		}
+	}
+	m_testCtx.getLog() << tcu::TestLog::Message << "Error, could not find resource \"" << resource << "\" in the program" << tcu::TestLog::EndMessage;
+	setError("could not find resource");
+}
+
+class TypeValidator : public SingleVariableValidator
+{
+public:
+				TypeValidator					(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+
+	std::string	getHumanReadablePropertyString	(glw::GLint propVal) const;
+	void		validateSingleVariable			(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+	void		validateBuiltinVariable			(const std::string& resource, glw::GLint propValue) const;
+};
+
+TypeValidator::TypeValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_TYPE, programID, filter)
+{
+}
+
+std::string TypeValidator::getHumanReadablePropertyString (glw::GLint propVal) const
+{
+	return de::toString(glu::getShaderVarTypeStr(propVal));
+}
+
+void TypeValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	const glu::VarType* variable = path.back().getVariableType();
+
+	DE_UNREF(resource);
+
+	if (variable->getBasicType() != glu::getDataTypeFromGLType(propValue))
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << glu::getDataTypeFromGLType(propValue) << tcu::TestLog::EndMessage;
+		setError("resource type invalid");
+	}
+}
+
+void TypeValidator::validateBuiltinVariable (const std::string& resource, glw::GLint propValue) const
+{
+	if (resource == "gl_Position")
+	{
+		if (glu::getDataTypeFromGLType(propValue) != glu::TYPE_FLOAT_VEC4)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << glu::getDataTypeFromGLType(propValue) << tcu::TestLog::EndMessage;
+			setError("resource type invalid");
+		}
+	}
+	else
+		DE_ASSERT(false);
+}
+
+class ArraySizeValidator : public SingleVariableValidator
+{
+public:
+				ArraySizeValidator				(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+	void		validateSingleVariable			(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+};
+
+ArraySizeValidator::ArraySizeValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_ARRAY_SIZE, programID, filter)
+{
+}
+
+void ArraySizeValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	const VariablePathComponent		nullComponent;
+	const VariablePathComponent&	enclosingcomponent	= (path.size() > 1) ? (path[path.size()-2]) : (nullComponent);
+
+	const bool						isArray				= enclosingcomponent.isVariableType() && enclosingcomponent.getVariableType()->isArrayType();
+	const bool						inUnsizedArray		= isArray && (enclosingcomponent.getVariableType()->getArraySize() == glu::VarType::UNSIZED_ARRAY);
+	const int						arraySize			= (!isArray) ? (1) : (inUnsizedArray) ? (0) : (enclosingcomponent.getVariableType()->getArraySize());
+
+	DE_UNREF(resource);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying array size, expecting " << arraySize << tcu::TestLog::EndMessage;
+
+	if (arraySize != propValue)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+		setError("resource array size invalid");
+	}
+}
+
+class ArrayStrideValidator : public SingleVariableValidator
+{
+public:
+				ArrayStrideValidator			(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+	void		validateSingleVariable			(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+};
+
+ArrayStrideValidator::ArrayStrideValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_ARRAY_STRIDE, programID, filter)
+{
+}
+
+void ArrayStrideValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	const VariablePathComponent		nullComponent;
+	const VariablePathComponent&	component			= path.back();
+	const VariablePathComponent&	enclosingcomponent	= (path.size() > 1) ? (path[path.size()-2]) : (nullComponent);
+	const VariablePathComponent&	firstComponent		= path.front();
+
+	const bool						isBufferBlock		= firstComponent.isInterfaceBlock() && isBufferBackedInterfaceBlockStorage(firstComponent.getInterfaceBlock()->storage);
+	const bool						isArray				= enclosingcomponent.isVariableType() && enclosingcomponent.getVariableType()->isArrayType();
+	const bool						isAtomicCounter		= glu::isDataTypeAtomicCounter(component.getVariableType()->getBasicType()); // atomic counters are buffer backed with a stride of 4 basic machine units
+
+	DE_UNREF(resource);
+
+	// Layout tests will verify layouts of buffer backed arrays properly. Here we just check values are greater or equal to the element size
+	if (isBufferBlock && isArray)
+	{
+		const int elementSize = glu::getDataTypeScalarSize(component.getVariableType()->getBasicType()) * getTypeSize(glu::getDataTypeScalarType(component.getVariableType()->getBasicType()));
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying array stride, expecting greater or equal to " << elementSize << tcu::TestLog::EndMessage;
+
+		if (propValue < elementSize)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+			setError("resource array stride invalid");
+		}
+	}
+	else
+	{
+		// Atomics are buffer backed with stride of 4 even though they are not in an interface block
+		const int arrayStride = (isAtomicCounter && isArray) ? (4) : (!isBufferBlock && !isAtomicCounter) ? (-1) : (0);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying array stride, expecting " << arrayStride << tcu::TestLog::EndMessage;
+
+		if (arrayStride != propValue)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+			setError("resource array stride invalid");
+		}
+	}
+}
+
+class BlockIndexValidator : public SingleVariableValidator
+{
+public:
+				BlockIndexValidator				(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+	void		validateSingleVariable			(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+};
+
+BlockIndexValidator::BlockIndexValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_BLOCK_INDEX, programID, filter)
+{
+}
+
+void BlockIndexValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	const VariablePathComponent& firstComponent = path.front();
+
+	DE_UNREF(resource);
+
+	if (!firstComponent.isInterfaceBlock())
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying block index, expecting -1" << tcu::TestLog::EndMessage;
+
+		if (propValue != -1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+			setError("resource block index invalid");
+		}
+	}
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying block index, expecting a valid block index" << tcu::TestLog::EndMessage;
+
+		if (propValue == -1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+			setError("resource block index invalid");
+		}
+		else
+		{
+			const glw::Functions&	gl			= m_renderContext.getFunctions();
+			const glw::GLenum		interface	= (firstComponent.getInterfaceBlock()->storage == glu::STORAGE_UNIFORM) ? (GL_UNIFORM_BLOCK) :
+												  (firstComponent.getInterfaceBlock()->storage == glu::STORAGE_BUFFER) ? (GL_SHADER_STORAGE_BLOCK) :
+												  (0);
+			glw::GLint				written		= 0;
+			std::vector<char>		nameBuffer	(firstComponent.getInterfaceBlock()->interfaceName.size() + 3 * firstComponent.getInterfaceBlock()->dimensions.size() + 2, '\0'); // +3 for appended "[N]", +1 for '\0' and +1 just for safety
+
+			gl.getProgramResourceName(m_programID, interface, propValue, (int)nameBuffer.size() - 1, &written, &nameBuffer[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query block name");
+			TCU_CHECK(written < (int)nameBuffer.size());
+			TCU_CHECK(nameBuffer.back() == '\0');
+
+			{
+				const std::string	blockName		(&nameBuffer[0], written);
+				std::ostringstream	expectedName;
+
+				expectedName << firstComponent.getInterfaceBlock()->interfaceName;
+				for (int dimensionNdx = 0; dimensionNdx < (int)firstComponent.getInterfaceBlock()->dimensions.size(); ++dimensionNdx)
+					expectedName << "[0]";
+
+				m_testCtx.getLog() << tcu::TestLog::Message << "Block name with index " << propValue << " is \"" << blockName << "\"" << tcu::TestLog::EndMessage;
+				if (blockName != expectedName.str())
+				{
+					m_testCtx.getLog() << tcu::TestLog::Message << "\tError, expected " << expectedName.str() << tcu::TestLog::EndMessage;
+					setError("resource block index invalid");
+				}
+			}
+		}
+	}
+}
+
+class IsRowMajorValidator : public SingleVariableValidator
+{
+public:
+				IsRowMajorValidator				(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+
+	std::string getHumanReadablePropertyString	(glw::GLint propVal) const;
+	void		validateSingleVariable			(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+};
+
+IsRowMajorValidator::IsRowMajorValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR, programID, filter)
+{
+}
+
+std::string IsRowMajorValidator::getHumanReadablePropertyString	(glw::GLint propVal) const
+{
+	return de::toString(glu::getBooleanStr(propVal));
+}
+
+void IsRowMajorValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	const VariablePathComponent&	component			= path.back();
+	const VariablePathComponent&	firstComponent		= path.front();
+
+	const bool						isBufferBlock		= firstComponent.isInterfaceBlock() && isBufferBackedInterfaceBlockStorage(firstComponent.getInterfaceBlock()->storage);
+	const bool						isMatrix			= glu::isDataTypeMatrix(component.getVariableType()->getBasicType());
+	const int						expected			= (isBufferBlock && isMatrix && getMatrixOrderFromPath(path) == glu::MATRIXORDER_ROW_MAJOR) ? (1) : (0);
+
+	DE_UNREF(resource);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying matrix order, expecting IS_ROW_MAJOR = " << expected << tcu::TestLog::EndMessage;
+
+	if (propValue != expected)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+		setError("resource matrix order invalid");
+	}
+}
+
+class MatrixStrideValidator : public SingleVariableValidator
+{
+public:
+				MatrixStrideValidator			(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+	void		validateSingleVariable			(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+};
+
+MatrixStrideValidator::MatrixStrideValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_MATRIX_STRIDE, programID, filter)
+{
+}
+
+void MatrixStrideValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	const VariablePathComponent&	component			= path.back();
+	const VariablePathComponent&	firstComponent		= path.front();
+
+	const bool						isBufferBlock		= firstComponent.isInterfaceBlock() && isBufferBackedInterfaceBlockStorage(firstComponent.getInterfaceBlock()->storage);
+	const bool						isMatrix			= glu::isDataTypeMatrix(component.getVariableType()->getBasicType());
+
+	DE_UNREF(resource);
+
+	// Layout tests will verify layouts of buffer backed arrays properly. Here we just check the stride is is greater or equal to the row/column size
+	if (isBufferBlock && isMatrix)
+	{
+		const bool	columnMajor			= getMatrixOrderFromPath(path) != glu::MATRIXORDER_ROW_MAJOR;
+		const int	numMajorElements	= (columnMajor) ? (glu::getDataTypeMatrixNumRows(component.getVariableType()->getBasicType())) : (glu::getDataTypeMatrixNumColumns(component.getVariableType()->getBasicType()));
+		const int	majorSize			= numMajorElements * getTypeSize(glu::getDataTypeScalarType(component.getVariableType()->getBasicType()));
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying matrix stride, expecting greater or equal to " << majorSize << tcu::TestLog::EndMessage;
+
+		if (propValue < majorSize)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+			setError("resource matrix stride invalid");
+		}
+	}
+	else
+	{
+		const int matrixStride = (!isBufferBlock && !glu::isDataTypeAtomicCounter(component.getVariableType()->getBasicType())) ? (-1) : (0);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying matrix stride, expecting " << matrixStride << tcu::TestLog::EndMessage;
+
+		if (matrixStride != propValue)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+			setError("resource matrix stride invalid");
+		}
+	}
+}
+
+class AtomicCounterBufferIndexVerifier : public SingleVariableValidator
+{
+public:
+				AtomicCounterBufferIndexVerifier	(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+	void		validateSingleVariable				(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+};
+
+AtomicCounterBufferIndexVerifier::AtomicCounterBufferIndexVerifier (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_ATOMIC_COUNTER_BUFFER_INDEX, programID, filter)
+{
+}
+
+void AtomicCounterBufferIndexVerifier::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	DE_UNREF(resource);
+
+	if (!glu::isDataTypeAtomicCounter(path.back().getVariableType()->getBasicType()))
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying atomic counter buffer index, expecting -1" << tcu::TestLog::EndMessage;
+
+		if (propValue != -1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+			setError("resource atomic counter buffer index invalid");
+		}
+	}
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying atomic counter buffer index, expecting a valid index" << tcu::TestLog::EndMessage;
+
+		if (propValue == -1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+			setError("resource atomic counter buffer index invalid");
+		}
+		else
+		{
+			const glw::Functions&	gl					= m_renderContext.getFunctions();
+			glw::GLint				numActiveResources	= 0;
+
+			gl.getProgramInterfaceiv(m_programID, GL_ATOMIC_COUNTER_BUFFER, GL_ACTIVE_RESOURCES, &numActiveResources);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "getProgramInterfaceiv(..., GL_ATOMIC_COUNTER_BUFFER, GL_ACTIVE_RESOURCES, ...)");
+
+			if (propValue >= numActiveResources)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << ", GL_ACTIVE_RESOURCES = " << numActiveResources << tcu::TestLog::EndMessage;
+				setError("resource atomic counter buffer index invalid");
+			}
+		}
+	}
+}
+
+class LocationValidator : public SingleVariableValidator
+{
+public:
+				LocationValidator		(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+	void		validateSingleVariable	(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+};
+
+LocationValidator::LocationValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_LOCATION, programID, filter)
+{
+}
+
+static int getVariableLocationLength (const glu::VarType& type)
+{
+	if (type.isBasicType())
+	{
+		if (glu::isDataTypeMatrix(type.getBasicType()))
+			return glu::getDataTypeMatrixNumColumns(type.getBasicType());
+		else
+			return 1;
+	}
+	else if (type.isStructType())
+	{
+		int size = 0;
+		for (int ndx = 0; ndx < type.getStructPtr()->getNumMembers(); ++ndx)
+			size += getVariableLocationLength(type.getStructPtr()->getMember(ndx).getType());
+		return size;
+	}
+	else if (type.isArrayType())
+		return type.getArraySize() * getVariableLocationLength(type.getElementType());
+	else
+	{
+		DE_ASSERT(false);
+		return 0;
+	}
+}
+
+static int getIOSubVariableLocation (const std::vector<VariablePathComponent>& path, int startNdx, int currentLocation)
+{
+	if (currentLocation == -1)
+		return -1;
+
+	if (path[startNdx].getVariableType()->isBasicType())
+		return currentLocation;
+	else if (path[startNdx].getVariableType()->isArrayType())
+		return getIOSubVariableLocation(path, startNdx+1, currentLocation);
+	else if (path[startNdx].getVariableType()->isStructType())
+	{
+		for (int ndx = 0; ndx < path[startNdx].getVariableType()->getStructPtr()->getNumMembers(); ++ndx)
+		{
+			if (&path[startNdx].getVariableType()->getStructPtr()->getMember(ndx).getType() == path[startNdx + 1].getVariableType())
+				return getIOSubVariableLocation(path, startNdx + 1, currentLocation);
+
+			if (currentLocation != -1)
+				currentLocation += getVariableLocationLength(path[startNdx].getVariableType()->getStructPtr()->getMember(ndx).getType());
+		}
+
+		// could not find member, never happens
+		DE_ASSERT(false);
+		return -1;
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return -1;
+	}
+}
+
+static int getIOBlockVariableLocation (const std::vector<VariablePathComponent>& path)
+{
+	const glu::InterfaceBlock*	block			= path.front().getInterfaceBlock();
+	int							currentLocation	= block->layout.location;
+
+	// Find the block member
+	for (int memberNdx = 0; memberNdx < (int)block->variables.size(); ++memberNdx)
+	{
+		if (&block->variables[memberNdx] == path[1].getDeclaration())
+			break;
+
+		if (block->variables[memberNdx].layout.location != -1)
+			currentLocation = block->variables[memberNdx].layout.location;
+
+		currentLocation += getVariableLocationLength(block->variables[memberNdx].varType);
+	}
+
+	// Find subtype location in the complex type
+	return getIOSubVariableLocation(path, 2, currentLocation);
+}
+
+static int getExplicitLocationFromPath (const std::vector<VariablePathComponent>& path)
+{
+	const glu::VariableDeclaration* varDecl = (path[0].isInterfaceBlock()) ? (path[1].getDeclaration()) : (path[0].getDeclaration());
+
+	if (path.front().isInterfaceBlock() && path.front().getInterfaceBlock()->storage == glu::STORAGE_UNIFORM)
+	{
+		// inside uniform block
+		return -1;
+	}
+	else if (path.front().isInterfaceBlock() && (path.front().getInterfaceBlock()->storage == glu::STORAGE_IN || path.front().getInterfaceBlock()->storage == glu::STORAGE_OUT))
+	{
+		// inside ioblock
+		return getIOBlockVariableLocation(path);
+	}
+	else if (varDecl->storage == glu::STORAGE_UNIFORM)
+	{
+		// default block uniform
+		return varDecl->layout.location;
+	}
+	else if (varDecl->storage == glu::STORAGE_IN || varDecl->storage == glu::STORAGE_OUT)
+	{
+		// default block input/output
+		return getIOSubVariableLocation(path, 1, varDecl->layout.location);
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return -1;
+	}
+}
+
+void LocationValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	const bool	isAtomicCounterUniform	= glu::isDataTypeAtomicCounter(path.back().getVariableType()->getBasicType());
+	const bool	isUniformBlockVariable	= path.front().isInterfaceBlock() && path.front().getInterfaceBlock()->storage == glu::STORAGE_UNIFORM;
+	const bool	isVertexShader			= m_filter.getShaderTypeFilter() == glu::SHADERTYPE_VERTEX;
+	const bool	isFragmentShader		= m_filter.getShaderTypeFilter() == glu::SHADERTYPE_FRAGMENT;
+	const bool	isInputVariable			= (path.front().isInterfaceBlock()) ? (path.front().getInterfaceBlock()->storage == glu::STORAGE_IN) : (path.front().getDeclaration()->storage == glu::STORAGE_IN);
+	const bool	isOutputVariable		= (path.front().isInterfaceBlock()) ? (path.front().getInterfaceBlock()->storage == glu::STORAGE_OUT) : (path.front().getDeclaration()->storage == glu::STORAGE_OUT);
+	const int	explicitLayoutLocation	= getExplicitLocationFromPath(path);
+
+	bool		expectLocation;
+	std::string	reasonStr;
+
+	if (isAtomicCounterUniform)
+	{
+		expectLocation = false;
+		reasonStr = "Atomic counter uniforms have effective location of -1";
+	}
+	else if (isUniformBlockVariable)
+	{
+		expectLocation = false;
+		reasonStr = "Uniform block variables have effective location of -1";
+	}
+	else if (isInputVariable && !isVertexShader && explicitLayoutLocation == -1)
+	{
+		expectLocation = false;
+		reasonStr = "Inputs (except for vertex shader inputs) not declared with a location layout qualifier have effective location of -1";
+	}
+	else if (isOutputVariable && !isFragmentShader && explicitLayoutLocation == -1)
+	{
+		expectLocation = false;
+		reasonStr = "Outputs (except for fragment shader outputs) not declared with a location layout qualifier have effective location of -1";
+	}
+	else
+	{
+		expectLocation = true;
+	}
+
+	if (!expectLocation)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying uniform location, expecting -1. (" << reasonStr << ")" << tcu::TestLog::EndMessage;
+
+		if (propValue != -1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+			setError("resource location invalid");
+		}
+	}
+	else
+	{
+		bool locationOk;
+
+		if (explicitLayoutLocation == -1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Verifying location, expecting a valid location" << tcu::TestLog::EndMessage;
+			locationOk = (propValue != -1);
+		}
+		else
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Verifying location, expecting " << explicitLayoutLocation << tcu::TestLog::EndMessage;
+			locationOk = (propValue == explicitLayoutLocation);
+		}
+
+		if (!locationOk)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+			setError("resource location invalid");
+		}
+		else
+		{
+			const VariablePathComponent		nullComponent;
+			const VariablePathComponent&	enclosingcomponent	= (path.size() > 1) ? (path[path.size()-2]) : (nullComponent);
+			const bool						isArray				= enclosingcomponent.isVariableType() && enclosingcomponent.getVariableType()->isArrayType();
+
+			const glw::Functions&			gl					= m_renderContext.getFunctions();
+			const glu::Storage				storage				= (path.front().isInterfaceBlock()) ? (path.front().getInterfaceBlock()->storage) : (path.front().getDeclaration()->storage);
+			const glw::GLenum				interface			= getProgramDefaultBlockInterfaceFromStorage(storage);
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Comparing location to the values returned by GetProgramResourceLocation" << tcu::TestLog::EndMessage;
+
+			// Test all bottom-level array elements
+			if (isArray)
+			{
+				const std::string arrayResourceName = (resource.size() > 3) ? (resource.substr(0, resource.size() - 3)) : (""); // chop "[0]"
+
+				for (int arrayElementNdx = 0; arrayElementNdx < enclosingcomponent.getVariableType()->getArraySize(); ++arrayElementNdx)
+				{
+					const std::string	elementResourceName	= arrayResourceName + "[" + de::toString(arrayElementNdx) + "]";
+					const glw::GLint	location			= gl.getProgramResourceLocation(m_programID, interface, elementResourceName.c_str());
+
+					if (location != propValue+arrayElementNdx)
+					{
+						m_testCtx.getLog()
+							<< tcu::TestLog::Message
+							<< "\tError, getProgramResourceLocation (resource=\"" << elementResourceName << "\") returned location " << location
+							<< ", expected " << (propValue+arrayElementNdx)
+							<< tcu::TestLog::EndMessage;
+						setError("resource location invalid");
+					}
+					else
+						m_testCtx.getLog() << tcu::TestLog::Message << "\tLocation of \"" << elementResourceName << "\":\t" << location << tcu::TestLog::EndMessage;
+				}
+			}
+			else
+			{
+				const glw::GLint location = gl.getProgramResourceLocation(m_programID, interface, resource.c_str());
+
+				if (location != propValue)
+				{
+					m_testCtx.getLog() << tcu::TestLog::Message << "\tError, getProgramResourceLocation returned location " << location << ", expected " << propValue << tcu::TestLog::EndMessage;
+					setError("resource location invalid");
+				}
+			}
+
+		}
+	}
+}
+
+class VariableNameLengthValidator : public SingleVariableValidator
+{
+public:
+				VariableNameLengthValidator	(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+	void		validateSingleVariable		(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+	void		validateBuiltinVariable		(const std::string& resource, glw::GLint propValue) const;
+	void		validateNameLength			(const std::string& resource, glw::GLint propValue) const;
+};
+
+VariableNameLengthValidator::VariableNameLengthValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_NAME_LENGTH, programID, filter)
+{
+}
+
+void VariableNameLengthValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	DE_UNREF(path);
+	validateNameLength(resource, propValue);
+}
+
+void VariableNameLengthValidator::validateBuiltinVariable (const std::string& resource, glw::GLint propValue) const
+{
+	validateNameLength(resource, propValue);
+}
+
+void VariableNameLengthValidator::validateNameLength (const std::string& resource, glw::GLint propValue) const
+{
+	const int expected = (int)resource.length() + 1; // includes null byte
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying name length, expecting " << expected << " (" << (int)resource.length() << " for \"" << resource << "\" + 1 byte for terminating null character)" << tcu::TestLog::EndMessage;
+
+	if (propValue != expected)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid name length, got " << propValue << tcu::TestLog::EndMessage;
+		setError("name length invalid");
+	}
+}
+
+class OffsetValidator : public SingleVariableValidator
+{
+public:
+				OffsetValidator			(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+	void		validateSingleVariable	(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+};
+
+OffsetValidator::OffsetValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_OFFSET, programID, filter)
+{
+}
+
+void OffsetValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	const bool isAtomicCounterUniform		= glu::isDataTypeAtomicCounter(path.back().getVariableType()->getBasicType());
+	const bool isBufferBackedBlockStorage	= path.front().isInterfaceBlock() && isBufferBackedInterfaceBlockStorage(path.front().getInterfaceBlock()->storage);
+
+	DE_UNREF(resource);
+
+	if (!isAtomicCounterUniform && !isBufferBackedBlockStorage)
+	{
+		// Not buffer backed
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying offset, expecting -1" << tcu::TestLog::EndMessage;
+
+		if (propValue != -1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid offset, got " << propValue << tcu::TestLog::EndMessage;
+			setError("offset invalid");
+		}
+	}
+	else
+	{
+		// Expect a valid offset
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying offset, expecting a valid offset" << tcu::TestLog::EndMessage;
+
+		if (propValue < 0)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid offset, got " << propValue << tcu::TestLog::EndMessage;
+			setError("offset invalid");
+		}
+	}
+}
+
+class VariableReferencedByShaderValidator : public PropValidator
+{
+public:
+								VariableReferencedByShaderValidator	(Context& context, const VariableSearchFilter& searchFilter);
+
+	std::string					getHumanReadablePropertyString		(glw::GLint propVal) const;
+	void						validate							(const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const;
+
+private:
+	const VariableSearchFilter	m_filter;
+};
+
+VariableReferencedByShaderValidator::VariableReferencedByShaderValidator (Context& context, const VariableSearchFilter& searchFilter)
+	: PropValidator	(context, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER)
+	, m_filter		(searchFilter)
+{
+}
+
+std::string VariableReferencedByShaderValidator::getHumanReadablePropertyString	(glw::GLint propVal) const
+{
+	return de::toString(glu::getBooleanStr(propVal));
+}
+
+void VariableReferencedByShaderValidator::validate (const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const
+{
+	std::vector<VariablePathComponent>	dummyPath;
+	const bool							referencedByShader = findProgramVariablePathByPathName(dummyPath, program, resource, m_filter);
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying referenced by " << glu::getShaderTypeName(m_filter.getShaderTypeFilter()) << " shader, expecting "
+		<< ((referencedByShader) ? ("GL_TRUE") : ("GL_FALSE"))
+		<< tcu::TestLog::EndMessage;
+
+	if (propValue != ((referencedByShader) ? (1) : (0)))
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid referenced_by_" << glu::getShaderTypeName(m_filter.getShaderTypeFilter()) << ", got " << propValue << tcu::TestLog::EndMessage;
+		setError("referenced_by_" + std::string(glu::getShaderTypeName(m_filter.getShaderTypeFilter())) + " invalid");
+	}
+}
+
+class BlockNameLengthValidator : public SingleBlockValidator
+{
+public:
+			BlockNameLengthValidator	(Context& context, const glw::GLuint programID, const VariableSearchFilter& filter);
+	void	validateSingleBlock			(const glu::InterfaceBlock& block, const std::vector<int>& instanceIndex, const std::string& resource, glw::GLint propValue) const;
+};
+
+BlockNameLengthValidator::BlockNameLengthValidator (Context& context, const glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleBlockValidator(context, PROGRAMRESOURCEPROP_NAME_LENGTH, programID, filter)
+{
+}
+
+void BlockNameLengthValidator::validateSingleBlock (const glu::InterfaceBlock& block, const std::vector<int>& instanceIndex, const std::string& resource, glw::GLint propValue) const
+{
+	DE_UNREF(instanceIndex);
+	DE_UNREF(block);
+
+	const int expected = (int)resource.length() + 1; // includes null byte
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying name length, expecting " << expected << " (" << (int)resource.length() << " for \"" << resource << "\" + 1 byte for terminating null character)" << tcu::TestLog::EndMessage;
+
+	if (propValue != expected)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid name length, got " << propValue << tcu::TestLog::EndMessage;
+		setError("name length invalid");
+	}
+}
+
+class BufferBindingValidator : public SingleBlockValidator
+{
+public:
+			BufferBindingValidator	(Context& context, const glw::GLuint programID, const VariableSearchFilter& filter);
+	void	validateSingleBlock		(const glu::InterfaceBlock& block, const std::vector<int>& instanceIndex, const std::string& resource, glw::GLint propValue) const;
+};
+
+BufferBindingValidator::BufferBindingValidator (Context& context, const glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleBlockValidator(context, PROGRAMRESOURCEPROP_BUFFER_BINDING, programID, filter)
+{
+}
+
+void BufferBindingValidator::validateSingleBlock (const glu::InterfaceBlock& block, const std::vector<int>& instanceIndex, const std::string& resource, glw::GLint propValue) const
+{
+	DE_UNREF(resource);
+
+	if (block.layout.binding != -1)
+	{
+		int flatIndex		= 0;
+		int dimensionSize	= 1;
+
+		for (int dimensionNdx = (int)(block.dimensions.size()) - 1; dimensionNdx >= 0; --dimensionNdx)
+		{
+			flatIndex += dimensionSize * instanceIndex[dimensionNdx];
+			dimensionSize *= block.dimensions[dimensionNdx];
+		}
+
+		const int expected = (block.dimensions.empty()) ? (block.layout.binding) : (block.layout.binding + flatIndex);
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying block binding, expecting " << expected << tcu::TestLog::EndMessage;
+
+		if (propValue != expected)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid buffer binding, got " << propValue << tcu::TestLog::EndMessage;
+			setError("buffer binding invalid");
+		}
+	}
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying buffer binding, expecting a valid binding" << tcu::TestLog::EndMessage;
+
+		if (propValue < 0)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid buffer binding, got " << propValue << tcu::TestLog::EndMessage;
+			setError("buffer binding invalid");
+		}
+	}
+}
+
+class BlockReferencedByShaderValidator : public PropValidator
+{
+public:
+								BlockReferencedByShaderValidator	(Context& context, const VariableSearchFilter& searchFilter);
+
+	std::string					getHumanReadablePropertyString		(glw::GLint propVal) const;
+	void						validate							(const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const;
+
+private:
+	const VariableSearchFilter	m_filter;
+};
+
+BlockReferencedByShaderValidator::BlockReferencedByShaderValidator (Context& context, const VariableSearchFilter& searchFilter)
+	: PropValidator	(context, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER)
+	, m_filter		(searchFilter)
+{
+}
+
+std::string BlockReferencedByShaderValidator::getHumanReadablePropertyString	(glw::GLint propVal) const
+{
+	return de::toString(glu::getBooleanStr(propVal));
+}
+
+void BlockReferencedByShaderValidator::validate (const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const
+{
+	const std::string	blockName			= glu::parseVariableName(resource.c_str());
+	bool				referencedByShader	= false;
+
+	DE_UNREF(resource);
+
+	for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
+	{
+		const ProgramInterfaceDefinition::Shader* const shader = program->getShaders()[shaderNdx];
+		if (!m_filter.matchesFilter(shader))
+			continue;
+
+		for (int blockNdx = 0; blockNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++blockNdx)
+		{
+			const glu::InterfaceBlock& block = shader->getDefaultBlock().interfaceBlocks[blockNdx];
+
+			if (m_filter.matchesFilter(block) && block.interfaceName == blockName)
+				referencedByShader = true;
+		}
+	}
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying referenced by " << glu::getShaderTypeName(m_filter.getShaderTypeFilter()) << " shader, expecting "
+		<< ((referencedByShader) ? ("GL_TRUE") : ("GL_FALSE"))
+		<< tcu::TestLog::EndMessage;
+
+	if (propValue != ((referencedByShader) ? (1) : (0)))
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid referenced_by_" << glu::getShaderTypeName(m_filter.getShaderTypeFilter()) << ", got " << propValue << tcu::TestLog::EndMessage;
+		setError("referenced_by_" + std::string(glu::getShaderTypeName(m_filter.getShaderTypeFilter())) + " invalid");
+	}
+}
+
+class TopLevelArraySizeValidator : public SingleVariableValidator
+{
+public:
+				TopLevelArraySizeValidator	(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+	void		validateSingleVariable		(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+};
+
+TopLevelArraySizeValidator::TopLevelArraySizeValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_SIZE, programID, filter)
+{
+}
+
+void TopLevelArraySizeValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	int			expected;
+	std::string	reason;
+
+	DE_ASSERT(path.front().isInterfaceBlock() && path.front().getInterfaceBlock()->storage == glu::STORAGE_BUFFER);
+	DE_UNREF(resource);
+
+	if (!path[1].getDeclaration()->varType.isArrayType())
+	{
+		expected = 1;
+		reason = "Top-level block member is not an array";
+	}
+	else if (path[1].getDeclaration()->varType.getElementType().isBasicType())
+	{
+		expected = 1;
+		reason = "Top-level block member is not an array of an aggregate type";
+	}
+	else if (path[1].getDeclaration()->varType.getArraySize() == glu::VarType::UNSIZED_ARRAY)
+	{
+		expected = 0;
+		reason = "Top-level block member is an unsized top-level array";
+	}
+	else
+	{
+		expected = path[1].getDeclaration()->varType.getArraySize();
+		reason = "Top-level block member is a sized top-level array";
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying top level array size, expecting " << expected << ". (" << reason << ")." << tcu::TestLog::EndMessage;
+
+	if (propValue != expected)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid top level array size, got " << propValue << tcu::TestLog::EndMessage;
+		setError("top level array size invalid");
+	}
+}
+
+class TopLevelArrayStrideValidator : public SingleVariableValidator
+{
+public:
+				TopLevelArrayStrideValidator	(Context& context, glw::GLuint programID, const VariableSearchFilter& filter);
+	void		validateSingleVariable			(const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const;
+};
+
+TopLevelArrayStrideValidator::TopLevelArrayStrideValidator (Context& context, glw::GLuint programID, const VariableSearchFilter& filter)
+	: SingleVariableValidator(context, PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_STRIDE, programID, filter)
+{
+}
+
+void TopLevelArrayStrideValidator::validateSingleVariable (const std::vector<VariablePathComponent>& path, const std::string& resource, glw::GLint propValue) const
+{
+	DE_ASSERT(path.front().isInterfaceBlock() && path.front().getInterfaceBlock()->storage == glu::STORAGE_BUFFER);
+	DE_UNREF(resource);
+
+	if (!path[1].getDeclaration()->varType.isArrayType())
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying top level array stride, expecting 0. (Top-level block member is not an array)." << tcu::TestLog::EndMessage;
+
+		if (propValue != 0)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, top level array stride, got " << propValue << tcu::TestLog::EndMessage;
+			setError("top level array stride invalid");
+		}
+	}
+	else if (path[1].getDeclaration()->varType.getElementType().isBasicType())
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying top level array stride, expecting 0. (Top-level block member is not an array of an aggregate type)." << tcu::TestLog::EndMessage;
+
+		if (propValue != 0)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, top level array stride, got " << propValue << tcu::TestLog::EndMessage;
+			setError("top level array stride invalid");
+		}
+	}
+	else
+	{
+		const int minimumStride = getVarTypeSize(path[1].getDeclaration()->varType.getElementType());
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying top level array stride, expecting greater or equal to " << minimumStride << "." << tcu::TestLog::EndMessage;
+
+		if (propValue < minimumStride)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid top level array stride, got " << propValue << tcu::TestLog::EndMessage;
+			setError("top level array stride invalid");
+		}
+	}
+}
+
+class TransformFeedbackArraySizeValidator : public PropValidator
+{
+public:
+				TransformFeedbackArraySizeValidator	(Context& context);
+	void		validate							(const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const;
+};
+
+TransformFeedbackArraySizeValidator::TransformFeedbackArraySizeValidator (Context& context)
+	: PropValidator(context, PROGRAMRESOURCEPROP_ARRAY_SIZE)
+{
+}
+
+void TransformFeedbackArraySizeValidator::validate (const ProgramInterfaceDefinition::Program* program, const std::string& resource, glw::GLint propValue) const
+{
+	int arraySize = 0;
+
+	if (deStringBeginsWith(resource.c_str(), "gl_"))
+	{
+		if (resource == "gl_Position")
+			arraySize = 1;
+		else
+			DE_ASSERT(false);
+	}
+	else if (!stringEndsWith(resource, "[0]"))
+	{
+		// user-defined non-array. Just check it exists
+		std::vector<VariablePathComponent> path;
+		if (!findProgramVariablePathByPathName(path, program, resource, VariableSearchFilter(glu::SHADERTYPE_VERTEX, glu::STORAGE_OUT)))
+		{
+			DE_ASSERT(false);
+			return;
+		}
+
+		arraySize = 1;
+	}
+	else
+	{
+		bool generatorFound = false;
+
+		// user-defined, array or array element. Find the generating varying declaration
+		for (int varyingNdx = 0; varyingNdx < (int)program->getTransformFeedbackVaryings().size(); ++varyingNdx)
+		{
+			const std::string					varyingName	= program->getTransformFeedbackVaryings()[varyingNdx];
+			std::vector<VariablePathComponent>	path;
+			std::vector<std::string>			resources;
+
+			if (!findProgramVariablePathByPathName(path, program, varyingName, VariableSearchFilter(glu::SHADERTYPE_VERTEX, glu::STORAGE_OUT)))
+			{
+				// program does not contain feedback varying, not valid program
+				DE_ASSERT(false);
+				return;
+			}
+
+			generateVariableTypeResourceNames(resources, varyingName, *path.back().getVariableType(), false);
+
+			if (de::contains(resources.begin(), resources.end(), resource))
+			{
+				arraySize = (path.back().getVariableType()->isArrayType()) ? (path.back().getVariableType()->getArraySize()) : (1);
+				generatorFound = true;
+				break;
+			}
+		}
+
+		if (!generatorFound)
+		{
+			DE_ASSERT(false);
+			return;
+		}
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying array size, expecting " << arraySize << tcu::TestLog::EndMessage;
+	if (arraySize != propValue)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
+		setError("resource array size invalid");
+	}
+}
+
+} // anonymous
+
+ProgramResourceQueryTestTarget::ProgramResourceQueryTestTarget (ProgramInterface interface_, deUint32 propFlags_)
+	: interface(interface_)
+	, propFlags(propFlags_)
+{
+	switch (interface)
+	{
+		case PROGRAMINTERFACE_UNIFORM:						DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_UNIFORM_INTERFACE_MASK)			== propFlags);	break;
+		case PROGRAMINTERFACE_UNIFORM_BLOCK:				DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_UNIFORM_BLOCK_INTERFACE_MASK)	== propFlags);	break;
+		case PROGRAMINTERFACE_SHADER_STORAGE_BLOCK:			DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_SHADER_STORAGE_BLOCK_MASK)		== propFlags);	break;
+		case PROGRAMINTERFACE_PROGRAM_INPUT:				DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_PROGRAM_INPUT_MASK)				== propFlags);	break;
+		case PROGRAMINTERFACE_PROGRAM_OUTPUT:				DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_PROGRAM_OUTPUT_MASK)				== propFlags);	break;
+		case PROGRAMINTERFACE_BUFFER_VARIABLE:				DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_BUFFER_VARIABLE_MASK)			== propFlags);	break;
+		case PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING:	DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_TRANSFORM_FEEDBACK_VARYING_MASK)	== propFlags);	break;
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+ProgramInterfaceQueryTestCase::ProgramInterfaceQueryTestCase (Context& context, const char* name, const char* description, ProgramResourceQueryTestTarget queryTarget)
+	: TestCase		(context, name, description)
+	, m_queryTarget	(queryTarget)
+{
+}
+
+ProgramInterfaceQueryTestCase::~ProgramInterfaceQueryTestCase (void)
+{
+}
+
+ProgramInterface ProgramInterfaceQueryTestCase::getTargetInterface (void) const
+{
+	return m_queryTarget.interface;
+}
+
+static glw::GLenum getGLInterfaceEnumValue (ProgramInterface interface)
+{
+	switch (interface)
+	{
+		case PROGRAMINTERFACE_UNIFORM:						return GL_UNIFORM;
+		case PROGRAMINTERFACE_UNIFORM_BLOCK:				return GL_UNIFORM_BLOCK;
+		case PROGRAMINTERFACE_ATOMIC_COUNTER_BUFFER:		return GL_ATOMIC_COUNTER_BUFFER;
+		case PROGRAMINTERFACE_PROGRAM_INPUT:				return GL_PROGRAM_INPUT;
+		case PROGRAMINTERFACE_PROGRAM_OUTPUT:				return GL_PROGRAM_OUTPUT;
+		case PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING:	return GL_TRANSFORM_FEEDBACK_VARYING;
+		case PROGRAMINTERFACE_BUFFER_VARIABLE:				return GL_BUFFER_VARIABLE;
+		case PROGRAMINTERFACE_SHADER_STORAGE_BLOCK:			return GL_SHADER_STORAGE_BLOCK;
+		default:
+			DE_ASSERT(false);
+			return 0;
+	};
+}
+
+static void queryAndValidateProps (tcu::TestContext& testCtx, const glw::Functions& gl, glw::GLuint programID, ProgramInterface interface, const char* targetResourceName, const ProgramInterfaceDefinition::Program* programDefinition, const std::vector<glw::GLenum>& props, const std::vector<const PropValidator*>& validators)
+{
+	const glw::GLenum			glInterface		= getGLInterfaceEnumValue(interface);
+	glw::GLuint					resourceNdx;
+	glw::GLint					written			= -1;
+	std::vector<glw::GLint>		propValues		(props.size() + 1, -2);	// prefill result buffer with an invalid value. -1 might be valid sometimes, avoid it. Make buffer one larger to allow detection of too many return values
+
+	DE_ASSERT(props.size() == validators.size());
+
+	// query
+
+	resourceNdx = gl.getProgramResourceIndex(programID, glInterface, targetResourceName);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "get resource index");
+
+	if (resourceNdx == GL_INVALID_INDEX)
+	{
+		testCtx.getLog() << tcu::TestLog::Message << "getProgramResourceIndex returned GL_INVALID_INDEX for \"" << targetResourceName << "\"" << tcu::TestLog::EndMessage;
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "could not find target resource");
+
+		// try to recover but keep the test result as failure
+		{
+			const std::string	resourceName			= std::string(targetResourceName);
+			std::string			simplifiedResourceName;
+
+			if (stringEndsWith(resourceName, "[0]"))
+				simplifiedResourceName = resourceName.substr(0, resourceName.length() - 3);
+			else
+			{
+				const size_t lastMember = resourceName.find_last_of('.');
+				if (lastMember != std::string::npos)
+					simplifiedResourceName = resourceName.substr(0, lastMember);
+			}
+
+			if (simplifiedResourceName.empty())
+				return;
+
+			resourceNdx = gl.getProgramResourceIndex(programID, glInterface, simplifiedResourceName.c_str());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "get resource index");
+
+			if (resourceNdx == GL_INVALID_INDEX)
+				return;
+
+			testCtx.getLog() << tcu::TestLog::Message << "\tResource not found, continuing anyway using index obtained for resource \"" << simplifiedResourceName << "\"" << tcu::TestLog::EndMessage;
+		}
+	}
+
+	gl.getProgramResourceiv(programID, glInterface, resourceNdx, (int)props.size(), &props[0], (int)propValues.size(), &written, &propValues[0]);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "get props");
+
+	if (written != (int)props.size())
+	{
+		testCtx.getLog() << tcu::TestLog::Message << "getProgramResourceiv returned unexpected number of values, expected " << (int)props.size() << ", got " << written << tcu::TestLog::EndMessage;
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "getProgramResourceiv returned unexpected number of values");
+		return;
+	}
+
+	if (propValues.back() != -2)
+	{
+		testCtx.getLog() << tcu::TestLog::Message << "getProgramResourceiv post write buffer guard value was modified, too many return values" << tcu::TestLog::EndMessage;
+		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "getProgramResourceiv returned unexpected number of values");
+		return;
+	}
+	propValues.pop_back();
+	DE_ASSERT(validators.size() == propValues.size());
+
+	// log
+
+	{
+		tcu::MessageBuilder message(&testCtx.getLog());
+		message << "For resource index " << resourceNdx << " (\"" << targetResourceName << "\") got following properties:\n";
+
+		for (int propNdx = 0; propNdx < (int)propValues.size(); ++propNdx)
+			message << "\t" << glu::getProgramResourcePropertyName(props[propNdx]) << ":\t" << validators[propNdx]->getHumanReadablePropertyString(propValues[propNdx]) << "\n";
+
+		message << tcu::TestLog::EndMessage;
+	}
+
+	// validate
+
+	for (int propNdx = 0; propNdx < (int)propValues.size(); ++propNdx)
+		validators[propNdx]->validate(programDefinition, targetResourceName, propValues[propNdx]);
+}
+
+ProgramInterfaceQueryTestCase::IterateResult ProgramInterfaceQueryTestCase::iterate (void)
+{
+	struct TestProperty
+	{
+		glw::GLenum				prop;
+		const PropValidator*	validator;
+	};
+
+	const ProgramInterfaceDefinition::Program*	programDefinition	= getProgramDefinition();
+	const std::vector<std::string>				targetResources		= getQueryTargetResources();
+	glu::ShaderProgram							program				(m_context.getRenderContext(), generateProgramInterfaceProgramSources(programDefinition));
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	DE_ASSERT(programDefinition->isValid());
+
+	// Log program
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program", "Program");
+
+		// Feedback varyings
+		if (!programDefinition->getTransformFeedbackVaryings().empty())
+		{
+			tcu::MessageBuilder builder(&m_testCtx.getLog());
+			builder << "Transform feedback varyings: {";
+			for (int ndx = 0; ndx < (int)programDefinition->getTransformFeedbackVaryings().size(); ++ndx)
+			{
+				if (ndx)
+					builder << ", ";
+				builder << "\"" << programDefinition->getTransformFeedbackVaryings()[ndx] << "\"";
+			}
+			builder << "}" << tcu::TestLog::EndMessage;
+		}
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Program build failed, checking if program exceeded implementation limits" << tcu::TestLog::EndMessage;
+			checkProgramResourceUsage(programDefinition, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+			// within limits
+			throw tcu::TestError("could not build program");
+		}
+	}
+
+	// Check interface props
+
+	switch (m_queryTarget.interface)
+	{
+		case PROGRAMINTERFACE_UNIFORM:
+		{
+			const VariableSearchFilter					uniformFilter						(glu::SHADERTYPE_LAST, glu::STORAGE_UNIFORM);
+
+			const TypeValidator							typeValidator						(m_context, program.getProgram(), uniformFilter);
+			const ArraySizeValidator					arraySizeValidator					(m_context, program.getProgram(), uniformFilter);
+			const ArrayStrideValidator					arrayStrideValidator				(m_context, program.getProgram(), uniformFilter);
+			const BlockIndexValidator					blockIndexValidator					(m_context, program.getProgram(), uniformFilter);
+			const IsRowMajorValidator					isRowMajorValidator					(m_context, program.getProgram(), uniformFilter);
+			const MatrixStrideValidator					matrixStrideValidator				(m_context, program.getProgram(), uniformFilter);
+			const AtomicCounterBufferIndexVerifier		atomicCounterBufferIndexVerifier	(m_context, program.getProgram(), uniformFilter);
+			const LocationValidator						locationValidator					(m_context, program.getProgram(), uniformFilter);
+			const VariableNameLengthValidator			nameLengthValidator					(m_context, program.getProgram(), uniformFilter);
+			const OffsetValidator						offsetVerifier						(m_context, program.getProgram(), uniformFilter);
+			const VariableReferencedByShaderValidator	referencedByVertexVerifier			(m_context, VariableSearchFilter(glu::SHADERTYPE_VERTEX,	glu::STORAGE_UNIFORM));
+			const VariableReferencedByShaderValidator	referencedByFragmentVerifier		(m_context, VariableSearchFilter(glu::SHADERTYPE_FRAGMENT,	glu::STORAGE_UNIFORM));
+			const VariableReferencedByShaderValidator	referencedByComputeVerifier			(m_context, VariableSearchFilter(glu::SHADERTYPE_COMPUTE,	glu::STORAGE_UNIFORM));
+
+			const TestProperty allProperties[] =
+			{
+				{ GL_ARRAY_SIZE,					&arraySizeValidator					},
+				{ GL_ARRAY_STRIDE,					&arrayStrideValidator				},
+				{ GL_ATOMIC_COUNTER_BUFFER_INDEX,	&atomicCounterBufferIndexVerifier	},
+				{ GL_BLOCK_INDEX,					&blockIndexValidator				},
+				{ GL_IS_ROW_MAJOR,					&isRowMajorValidator				},
+				{ GL_LOCATION,						&locationValidator					},
+				{ GL_MATRIX_STRIDE,					&matrixStrideValidator				},
+				{ GL_NAME_LENGTH,					&nameLengthValidator				},
+				{ GL_OFFSET,						&offsetVerifier						},
+				{ GL_REFERENCED_BY_VERTEX_SHADER,	&referencedByVertexVerifier			},
+				{ GL_REFERENCED_BY_FRAGMENT_SHADER,	&referencedByFragmentVerifier		},
+				{ GL_REFERENCED_BY_COMPUTE_SHADER,	&referencedByComputeVerifier		},
+				{ GL_TYPE,							&typeValidator						},
+			};
+
+			for (int targetResourceNdx = 0; targetResourceNdx < (int)targetResources.size(); ++targetResourceNdx)
+			{
+				const tcu::ScopedLogSection			section			(m_testCtx.getLog(), "UniformResource", "Uniform resource \"" +  targetResources[targetResourceNdx] + "\"");
+				const glw::Functions&				gl				= m_context.getRenderContext().getFunctions();
+				std::vector<glw::GLenum>			props;
+				std::vector<const PropValidator*>	validators;
+
+				for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(allProperties); ++propNdx)
+				{
+					if (allProperties[propNdx].validator->isSelected(m_queryTarget.propFlags) &&
+						allProperties[propNdx].validator->isSupported())
+					{
+						props.push_back(allProperties[propNdx].prop);
+						validators.push_back(allProperties[propNdx].validator);
+					}
+				}
+
+				DE_ASSERT(!props.empty());
+
+				queryAndValidateProps(m_testCtx, gl, program.getProgram(), m_queryTarget.interface, targetResources[targetResourceNdx].c_str(), programDefinition, props, validators);
+			}
+
+			break;
+		}
+
+		case PROGRAMINTERFACE_UNIFORM_BLOCK:
+		case PROGRAMINTERFACE_SHADER_STORAGE_BLOCK:
+		{
+			const glu::Storage						storage								= (m_queryTarget.interface == PROGRAMINTERFACE_UNIFORM_BLOCK) ? (glu::STORAGE_UNIFORM) : (glu::STORAGE_BUFFER);
+			const VariableSearchFilter				blockFilter							(glu::SHADERTYPE_LAST, storage);
+
+			const BlockNameLengthValidator			nameLengthValidator					(m_context, program.getProgram(), blockFilter);
+			const BlockReferencedByShaderValidator	referencedByVertexVerifier			(m_context, VariableSearchFilter(glu::SHADERTYPE_VERTEX,	storage));
+			const BlockReferencedByShaderValidator	referencedByFragmentVerifier		(m_context, VariableSearchFilter(glu::SHADERTYPE_FRAGMENT,	storage));
+			const BlockReferencedByShaderValidator	referencedByComputeVerifier			(m_context, VariableSearchFilter(glu::SHADERTYPE_COMPUTE,	storage));
+			const BufferBindingValidator			bufferBindingValidator				(m_context, program.getProgram(), blockFilter);
+
+			const TestProperty allProperties[] =
+			{
+				{ GL_NAME_LENGTH,					&nameLengthValidator				},
+				{ GL_REFERENCED_BY_VERTEX_SHADER,	&referencedByVertexVerifier			},
+				{ GL_REFERENCED_BY_FRAGMENT_SHADER,	&referencedByFragmentVerifier		},
+				{ GL_REFERENCED_BY_COMPUTE_SHADER,	&referencedByComputeVerifier		},
+				{ GL_BUFFER_BINDING,				&bufferBindingValidator				},
+			};
+
+			for (int targetResourceNdx = 0; targetResourceNdx < (int)targetResources.size(); ++targetResourceNdx)
+			{
+				const tcu::ScopedLogSection			section			(m_testCtx.getLog(), "BlockResource", "Interface block \"" +  targetResources[targetResourceNdx] + "\"");
+				const glw::Functions&				gl				= m_context.getRenderContext().getFunctions();
+				std::vector<glw::GLenum>			props;
+				std::vector<const PropValidator*>	validators;
+
+				for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(allProperties); ++propNdx)
+				{
+					if (allProperties[propNdx].validator->isSelected(m_queryTarget.propFlags) &&
+						allProperties[propNdx].validator->isSupported())
+					{
+						props.push_back(allProperties[propNdx].prop);
+						validators.push_back(allProperties[propNdx].validator);
+					}
+				}
+
+				DE_ASSERT(!props.empty());
+
+				queryAndValidateProps(m_testCtx, gl, program.getProgram(), m_queryTarget.interface, targetResources[targetResourceNdx].c_str(), programDefinition, props, validators);
+			}
+
+			break;
+		}
+
+		case PROGRAMINTERFACE_PROGRAM_INPUT:
+		case PROGRAMINTERFACE_PROGRAM_OUTPUT:
+		{
+			const glu::Storage							storage							= (m_queryTarget.interface == PROGRAMINTERFACE_PROGRAM_INPUT) ? (glu::STORAGE_IN) : (glu::STORAGE_OUT);
+			const glu::ShaderType						shaderType						= (m_queryTarget.interface == PROGRAMINTERFACE_PROGRAM_INPUT) ? (programDefinition->getFirstStage()) : (programDefinition->getLastStage());
+			const VariableSearchFilter					variableFilter					(shaderType, storage);
+
+			const TypeValidator							typeValidator					(m_context, program.getProgram(), variableFilter);
+			const ArraySizeValidator					arraySizeValidator				(m_context, program.getProgram(), variableFilter);
+			const LocationValidator						locationValidator				(m_context, program.getProgram(), variableFilter);
+			const VariableNameLengthValidator			nameLengthValidator				(m_context, program.getProgram(), variableFilter);
+			const VariableReferencedByShaderValidator	referencedByVertexVerifier		(m_context, VariableSearchFilter::intersection(VariableSearchFilter(glu::SHADERTYPE_VERTEX,		storage), variableFilter));
+			const VariableReferencedByShaderValidator	referencedByFragmentVerifier	(m_context, VariableSearchFilter::intersection(VariableSearchFilter(glu::SHADERTYPE_FRAGMENT,	storage), variableFilter));
+			const VariableReferencedByShaderValidator	referencedByComputeVerifier		(m_context, VariableSearchFilter::intersection(VariableSearchFilter(glu::SHADERTYPE_COMPUTE,	storage), variableFilter));
+
+			const TestProperty allProperties[] =
+			{
+				{ GL_ARRAY_SIZE,					&arraySizeValidator				},
+				{ GL_LOCATION,						&locationValidator				},
+				{ GL_NAME_LENGTH,					&nameLengthValidator			},
+				{ GL_REFERENCED_BY_VERTEX_SHADER,	&referencedByVertexVerifier		},
+				{ GL_REFERENCED_BY_FRAGMENT_SHADER,	&referencedByFragmentVerifier	},
+				{ GL_REFERENCED_BY_COMPUTE_SHADER,	&referencedByComputeVerifier	},
+				{ GL_TYPE,							&typeValidator					},
+			};
+
+			for (int targetResourceNdx = 0; targetResourceNdx < (int)targetResources.size(); ++targetResourceNdx)
+			{
+				const std::string					resourceInterfaceName	= (m_queryTarget.interface == PROGRAMINTERFACE_PROGRAM_INPUT) ? ("Input") : ("Output");
+				const tcu::ScopedLogSection			section					(m_testCtx.getLog(), "BlockResource", resourceInterfaceName + " resource \"" +  targetResources[targetResourceNdx] + "\"");
+				const glw::Functions&				gl						= m_context.getRenderContext().getFunctions();
+				std::vector<glw::GLenum>			props;
+				std::vector<const PropValidator*>	validators;
+
+				for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(allProperties); ++propNdx)
+				{
+					if (allProperties[propNdx].validator->isSelected(m_queryTarget.propFlags) &&
+						allProperties[propNdx].validator->isSupported())
+					{
+						props.push_back(allProperties[propNdx].prop);
+						validators.push_back(allProperties[propNdx].validator);
+					}
+				}
+
+				DE_ASSERT(!props.empty());
+
+				queryAndValidateProps(m_testCtx, gl, program.getProgram(), m_queryTarget.interface, targetResources[targetResourceNdx].c_str(), programDefinition, props, validators);
+			}
+
+			break;
+		}
+
+		case PROGRAMINTERFACE_BUFFER_VARIABLE:
+		{
+			const VariableSearchFilter					variableFilter					(glu::SHADERTYPE_LAST, glu::STORAGE_BUFFER);
+
+			const TypeValidator							typeValidator					(m_context, program.getProgram(), variableFilter);
+			const ArraySizeValidator					arraySizeValidator				(m_context, program.getProgram(), variableFilter);
+			const ArrayStrideValidator					arrayStrideValidator			(m_context, program.getProgram(), variableFilter);
+			const BlockIndexValidator					blockIndexValidator				(m_context, program.getProgram(), variableFilter);
+			const IsRowMajorValidator					isRowMajorValidator				(m_context, program.getProgram(), variableFilter);
+			const MatrixStrideValidator					matrixStrideValidator			(m_context, program.getProgram(), variableFilter);
+			const OffsetValidator						offsetValidator					(m_context, program.getProgram(), variableFilter);
+			const VariableNameLengthValidator			nameLengthValidator				(m_context, program.getProgram(), variableFilter);
+			const VariableReferencedByShaderValidator	referencedByVertexVerifier		(m_context, VariableSearchFilter(glu::SHADERTYPE_VERTEX,	glu::STORAGE_BUFFER));
+			const VariableReferencedByShaderValidator	referencedByFragmentVerifier	(m_context, VariableSearchFilter(glu::SHADERTYPE_FRAGMENT,	glu::STORAGE_BUFFER));
+			const VariableReferencedByShaderValidator	referencedByComputeVerifier		(m_context, VariableSearchFilter(glu::SHADERTYPE_COMPUTE,	glu::STORAGE_BUFFER));
+			const TopLevelArraySizeValidator			topLevelArraySizeValidator		(m_context, program.getProgram(), variableFilter);
+			const TopLevelArrayStrideValidator			topLevelArrayStrideValidator	(m_context, program.getProgram(), variableFilter);
+
+			const TestProperty allProperties[] =
+			{
+				{ GL_ARRAY_SIZE,					&arraySizeValidator				},
+				{ GL_ARRAY_STRIDE,					&arrayStrideValidator			},
+				{ GL_BLOCK_INDEX,					&blockIndexValidator			},
+				{ GL_IS_ROW_MAJOR,					&isRowMajorValidator			},
+				{ GL_MATRIX_STRIDE,					&matrixStrideValidator			},
+				{ GL_NAME_LENGTH,					&nameLengthValidator			},
+				{ GL_OFFSET,						&offsetValidator				},
+				{ GL_REFERENCED_BY_VERTEX_SHADER,	&referencedByVertexVerifier		},
+				{ GL_REFERENCED_BY_FRAGMENT_SHADER,	&referencedByFragmentVerifier	},
+				{ GL_REFERENCED_BY_COMPUTE_SHADER,	&referencedByComputeVerifier	},
+				{ GL_TOP_LEVEL_ARRAY_SIZE,			&topLevelArraySizeValidator		},
+				{ GL_TOP_LEVEL_ARRAY_STRIDE,		&topLevelArrayStrideValidator	},
+				{ GL_TYPE,							&typeValidator					},
+			};
+
+			for (int targetResourceNdx = 0; targetResourceNdx < (int)targetResources.size(); ++targetResourceNdx)
+			{
+				const tcu::ScopedLogSection			section			(m_testCtx.getLog(), "BufferVariableResource", "Buffer variable \"" +  targetResources[targetResourceNdx] + "\"");
+				const glw::Functions&				gl				= m_context.getRenderContext().getFunctions();
+				std::vector<glw::GLenum>			props;
+				std::vector<const PropValidator*>	validators;
+
+				for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(allProperties); ++propNdx)
+				{
+					if (allProperties[propNdx].validator->isSelected(m_queryTarget.propFlags) &&
+						allProperties[propNdx].validator->isSupported())
+					{
+						props.push_back(allProperties[propNdx].prop);
+						validators.push_back(allProperties[propNdx].validator);
+					}
+				}
+
+				DE_ASSERT(!props.empty());
+
+				queryAndValidateProps(m_testCtx, gl, program.getProgram(), m_queryTarget.interface, targetResources[targetResourceNdx].c_str(), programDefinition, props, validators);
+			}
+
+			break;
+		}
+
+		case PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING:
+		{
+			const TypeValidator							typeValidator			(m_context, program.getProgram(), VariableSearchFilter(glu::SHADERTYPE_VERTEX, glu::STORAGE_OUT));
+			const TransformFeedbackArraySizeValidator	arraySizeValidator		(m_context);
+			const VariableNameLengthValidator			nameLengthValidator		(m_context, program.getProgram(), VariableSearchFilter(glu::SHADERTYPE_VERTEX, glu::STORAGE_OUT));
+
+			const TestProperty allProperties[] =
+			{
+				{ GL_ARRAY_SIZE,					&arraySizeValidator				},
+				{ GL_NAME_LENGTH,					&nameLengthValidator			},
+				{ GL_TYPE,							&typeValidator					},
+			};
+
+			for (int targetResourceNdx = 0; targetResourceNdx < (int)targetResources.size(); ++targetResourceNdx)
+			{
+				const tcu::ScopedLogSection			section			(m_testCtx.getLog(), "XFBVariableResource", "Transform feedback varying \"" +  targetResources[targetResourceNdx] + "\"");
+				const glw::Functions&				gl				= m_context.getRenderContext().getFunctions();
+				std::vector<glw::GLenum>			props;
+				std::vector<const PropValidator*>	validators;
+
+				for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(allProperties); ++propNdx)
+				{
+					if (allProperties[propNdx].validator->isSelected(m_queryTarget.propFlags) &&
+						allProperties[propNdx].validator->isSupported())
+					{
+						props.push_back(allProperties[propNdx].prop);
+						validators.push_back(allProperties[propNdx].validator);
+					}
+				}
+
+				DE_ASSERT(!props.empty());
+
+				queryAndValidateProps(m_testCtx, gl, program.getProgram(), m_queryTarget.interface, targetResources[targetResourceNdx].c_str(), programDefinition, props, validators);
+			}
+
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	return STOP;
+}
+
+static bool checkLimit (glw::GLenum pname, int usage, const glw::Functions& gl, tcu::TestLog& log)
+{
+	if (usage > 0)
+	{
+		glw::GLint limit = 0;
+		gl.getIntegerv(pname, &limit);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query limits");
+
+		log << tcu::TestLog::Message << "\t" << glu::getGettableStateStr(pname) << " = " << limit << ", test requires " << usage << tcu::TestLog::EndMessage;
+
+		if (limit < usage)
+		{
+			log << tcu::TestLog::Message << "\t\tLimit exceeded" << tcu::TestLog::EndMessage;
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static bool checkShaderResourceUsage (const ProgramInterfaceDefinition::Shader* shader, const glw::Functions& gl, tcu::TestLog& log)
+{
+	const ProgramInterfaceDefinition::ShaderResourceUsage usage = getShaderResourceUsage(shader);
+
+	switch (shader->getType())
+	{
+		case glu::SHADERTYPE_VERTEX:
+		{
+			const struct
+			{
+				glw::GLenum	pname;
+				int			usage;
+			} restrictions[] =
+			{
+				{ GL_MAX_VERTEX_ATTRIBS,						usage.numInputVectors					},
+				{ GL_MAX_VERTEX_UNIFORM_COMPONENTS,				usage.numDefaultBlockUniformComponents	},
+				{ GL_MAX_VERTEX_UNIFORM_VECTORS,				usage.numUniformVectors					},
+				{ GL_MAX_VERTEX_UNIFORM_BLOCKS,					usage.numUniformBlocks					},
+				{ GL_MAX_VERTEX_OUTPUT_COMPONENTS,				usage.numOutputComponents				},
+				{ GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,			usage.numSamplers						},
+				{ GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS,			usage.numAtomicCounterBuffers			},
+				{ GL_MAX_VERTEX_ATOMIC_COUNTERS,				usage.numAtomicCounters					},
+				{ GL_MAX_VERTEX_IMAGE_UNIFORMS,					usage.numImages							},
+				{ GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS,	usage.numCombinedUniformComponents		},
+				{ GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS,			usage.numShaderStorageBlocks			},
+			};
+
+			bool allOk = true;
+
+			log << tcu::TestLog::Message << "Vertex shader:" << tcu::TestLog::EndMessage;
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
+				allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);
+
+			return allOk;
+		}
+
+		case glu::SHADERTYPE_FRAGMENT:
+		{
+			const struct
+			{
+				glw::GLenum	pname;
+				int			usage;
+			} restrictions[] =
+			{
+				{ GL_MAX_FRAGMENT_UNIFORM_COMPONENTS,			usage.numDefaultBlockUniformComponents		},
+				{ GL_MAX_FRAGMENT_UNIFORM_VECTORS,				usage.numUniformVectors						},
+				{ GL_MAX_FRAGMENT_UNIFORM_BLOCKS,				usage.numUniformBlocks						},
+				{ GL_MAX_FRAGMENT_INPUT_COMPONENTS,				usage.numInputComponents					},
+				{ GL_MAX_TEXTURE_IMAGE_UNITS,					usage.numSamplers							},
+				{ GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS,		usage.numAtomicCounterBuffers				},
+				{ GL_MAX_FRAGMENT_ATOMIC_COUNTERS,				usage.numAtomicCounters						},
+				{ GL_MAX_FRAGMENT_IMAGE_UNIFORMS,				usage.numImages								},
+				{ GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS,	usage.numCombinedUniformComponents			},
+				{ GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS,		usage.numShaderStorageBlocks				},
+			};
+
+			bool allOk = true;
+
+			log << tcu::TestLog::Message << "Fragment shader:" << tcu::TestLog::EndMessage;
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
+				allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);
+
+			return allOk;
+		}
+
+		case glu::SHADERTYPE_COMPUTE:
+		{
+			const struct
+			{
+				glw::GLenum	pname;
+				int			usage;
+			} restrictions[] =
+			{
+				{ GL_MAX_COMPUTE_UNIFORM_BLOCKS,				usage.numUniformBlocks					},
+				{ GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS,			usage.numSamplers						},
+				{ GL_MAX_COMPUTE_UNIFORM_COMPONENTS,			usage.numDefaultBlockUniformComponents	},
+				{ GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS,		usage.numAtomicCounterBuffers			},
+				{ GL_MAX_COMPUTE_ATOMIC_COUNTERS,				usage.numAtomicCounters					},
+				{ GL_MAX_COMPUTE_IMAGE_UNIFORMS,				usage.numImages							},
+				{ GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS,	usage.numCombinedUniformComponents		},
+				{ GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS,			usage.numShaderStorageBlocks			},
+			};
+
+			bool allOk = true;
+
+			log << tcu::TestLog::Message << "Compute shader:" << tcu::TestLog::EndMessage;
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
+				allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);
+
+			return allOk;
+		}
+
+		default:
+			DE_ASSERT(false);
+			return false;
+	}
+}
+
+static bool checkProgramCombinedResourceUsage (const ProgramInterfaceDefinition::Program* program, const glw::Functions& gl, tcu::TestLog& log)
+{
+	const ProgramInterfaceDefinition::ProgramResourceUsage usage = getCombinedProgramResourceUsage(program);
+
+	const struct
+	{
+		glw::GLenum	pname;
+		int			usage;
+	} restrictions[] =
+	{
+		{ GL_MAX_UNIFORM_BUFFER_BINDINGS,						usage.uniformBufferMaxBinding+1				},
+		{ GL_MAX_UNIFORM_BLOCK_SIZE,							usage.uniformBufferMaxSize					},
+		{ GL_MAX_COMBINED_UNIFORM_BLOCKS,						usage.numUniformBlocks						},
+		{ GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS,			usage.numCombinedVertexUniformComponents	},
+		{ GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS,			usage.numCombinedFragmentUniformComponents	},
+		{ GL_MAX_VARYING_COMPONENTS,							usage.numVaryingComponents					},
+		{ GL_MAX_VARYING_VECTORS,								usage.numVaryingVectors						},
+		{ GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,					usage.numCombinedSamplers					},
+		{ GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES,				usage.numCombinedOutputResources			},
+		{ GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS,				usage.atomicCounterBufferMaxBinding+1		},
+		{ GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE,					usage.atomicCounterBufferMaxSize			},
+		{ GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS,				usage.numAtomicCounterBuffers				},
+		{ GL_MAX_COMBINED_ATOMIC_COUNTERS,						usage.numAtomicCounters						},
+		{ GL_MAX_IMAGE_UNITS,									usage.maxImageBinding						},
+		{ GL_MAX_COMBINED_IMAGE_UNIFORMS,						usage.numCombinedImages						},
+		{ GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS,				usage.shaderStorageBufferMaxBinding+1		},
+		{ GL_MAX_SHADER_STORAGE_BLOCK_SIZE,						usage.shaderStorageBufferMaxSize			},
+		{ GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS,				usage.numShaderStorageBlocks				},
+		{ GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,		usage.numXFBInterleavedComponents			},
+		{ GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,			usage.numXFBSeparateAttribs					},
+		{ GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS,		usage.numXFBSeparateComponents				},
+	};
+
+	bool allOk = true;
+
+	log << tcu::TestLog::Message << "Program combined:" << tcu::TestLog::EndMessage;
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
+		allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);
+
+	return allOk;
+}
+
+void checkProgramResourceUsage (const ProgramInterfaceDefinition::Program* program, const glw::Functions& gl, tcu::TestLog& log)
+{
+	bool limitExceeded = false;
+
+	for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
+		limitExceeded |= !checkShaderResourceUsage(program->getShaders()[shaderNdx], gl, log);
+
+	limitExceeded |= !checkProgramCombinedResourceUsage(program, gl, log);
+
+	if (limitExceeded)
+	{
+		log << tcu::TestLog::Message << "One or more resource limits exceeded" << tcu::TestLog::EndMessage;
+		throw tcu::NotSupportedError("one or more resource limits exceeded");
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fProgramInterfaceQueryTestCase.hpp b/modules/gles31/functional/es31fProgramInterfaceQueryTestCase.hpp
new file mode 100644
index 0000000..be2f5f6
--- /dev/null
+++ b/modules/gles31/functional/es31fProgramInterfaceQueryTestCase.hpp
@@ -0,0 +1,140 @@
+#ifndef _ES31FPROGRAMINTERFACEQUERYTESTCASE_HPP
+#define _ES31FPROGRAMINTERFACEQUERYTESTCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Program interface query test case
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+#include "es31fProgramInterfaceDefinition.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+struct ProgramResourceQueryTestTarget
+{
+						ProgramResourceQueryTestTarget (ProgramInterface interface_, deUint32 propFlags_);
+
+	ProgramInterface	interface;
+	deUint32			propFlags;
+};
+
+enum ProgramResourcePropFlags
+{
+	PROGRAMRESOURCEPROP_ARRAY_SIZE						= (1 <<  1),
+	PROGRAMRESOURCEPROP_ARRAY_STRIDE					= (1 <<  2),
+	PROGRAMRESOURCEPROP_ATOMIC_COUNTER_BUFFER_INDEX		= (1 <<  3),
+	PROGRAMRESOURCEPROP_BLOCK_INDEX						= (1 <<  4),
+	PROGRAMRESOURCEPROP_LOCATION						= (1 <<  5),
+	PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR				= (1 <<  6),
+	PROGRAMRESOURCEPROP_MATRIX_STRIDE					= (1 <<  7),
+	PROGRAMRESOURCEPROP_NAME_LENGTH						= (1 <<  8),
+	PROGRAMRESOURCEPROP_OFFSET							= (1 <<  9),
+	PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER			= (1 << 10),
+	PROGRAMRESOURCEPROP_TYPE							= (1 << 11),
+	PROGRAMRESOURCEPROP_BUFFER_BINDING					= (1 << 12),
+	PROGRAMRESOURCEPROP_BUFFER_DATA_SIZE				= (1 << 13),
+	PROGRAMRESOURCEPROP_ACTIVE_VARIABLES				= (1 << 14),
+	PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_SIZE			= (1 << 15),
+	PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_STRIDE			= (1 << 16),
+
+	PROGRAMRESOURCEPROP_UNIFORM_INTERFACE_MASK			= PROGRAMRESOURCEPROP_ARRAY_SIZE					|
+														  PROGRAMRESOURCEPROP_ARRAY_STRIDE					|
+														  PROGRAMRESOURCEPROP_ATOMIC_COUNTER_BUFFER_INDEX	|
+														  PROGRAMRESOURCEPROP_BLOCK_INDEX					|
+														  PROGRAMRESOURCEPROP_LOCATION						|
+														  PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR				|
+														  PROGRAMRESOURCEPROP_MATRIX_STRIDE					|
+														  PROGRAMRESOURCEPROP_NAME_LENGTH					|
+														  PROGRAMRESOURCEPROP_OFFSET						|
+														  PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER			|
+														  PROGRAMRESOURCEPROP_TYPE,
+
+	PROGRAMRESOURCEPROP_UNIFORM_BLOCK_INTERFACE_MASK	= PROGRAMRESOURCEPROP_NAME_LENGTH					|
+														  PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER			|
+														  PROGRAMRESOURCEPROP_BUFFER_BINDING				|
+														  PROGRAMRESOURCEPROP_BUFFER_DATA_SIZE				|
+														  PROGRAMRESOURCEPROP_ACTIVE_VARIABLES,
+
+	PROGRAMRESOURCEPROP_SHADER_STORAGE_BLOCK_MASK		= PROGRAMRESOURCEPROP_NAME_LENGTH					|
+														  PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER			|
+														  PROGRAMRESOURCEPROP_BUFFER_BINDING				|
+														  PROGRAMRESOURCEPROP_BUFFER_DATA_SIZE				|
+														  PROGRAMRESOURCEPROP_ACTIVE_VARIABLES,
+
+	PROGRAMRESOURCEPROP_PROGRAM_INPUT_MASK				= PROGRAMRESOURCEPROP_ARRAY_SIZE					|
+														  PROGRAMRESOURCEPROP_LOCATION						|
+														  PROGRAMRESOURCEPROP_NAME_LENGTH					|
+														  PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER			|
+														  PROGRAMRESOURCEPROP_TYPE,
+
+	PROGRAMRESOURCEPROP_PROGRAM_OUTPUT_MASK				= PROGRAMRESOURCEPROP_ARRAY_SIZE					|
+														  PROGRAMRESOURCEPROP_LOCATION						|
+														  PROGRAMRESOURCEPROP_NAME_LENGTH					|
+														  PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER			|
+														  PROGRAMRESOURCEPROP_TYPE,
+
+	PROGRAMRESOURCEPROP_BUFFER_VARIABLE_MASK			= PROGRAMRESOURCEPROP_ARRAY_SIZE					|
+														  PROGRAMRESOURCEPROP_ARRAY_STRIDE					|
+														  PROGRAMRESOURCEPROP_BLOCK_INDEX					|
+														  PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR				|
+														  PROGRAMRESOURCEPROP_MATRIX_STRIDE					|
+														  PROGRAMRESOURCEPROP_NAME_LENGTH					|
+														  PROGRAMRESOURCEPROP_OFFSET						|
+														  PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER			|
+														  PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_SIZE			|
+														  PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_STRIDE		|
+														  PROGRAMRESOURCEPROP_TYPE,
+
+	PROGRAMRESOURCEPROP_TRANSFORM_FEEDBACK_VARYING_MASK	= PROGRAMRESOURCEPROP_ARRAY_SIZE					|
+														  PROGRAMRESOURCEPROP_NAME_LENGTH					|
+														  PROGRAMRESOURCEPROP_TYPE,
+};
+
+class ProgramInterfaceQueryTestCase : public TestCase
+{
+public:
+													ProgramInterfaceQueryTestCase	(Context& context, const char* name, const char* description, ProgramResourceQueryTestTarget queryTarget);
+													~ProgramInterfaceQueryTestCase	(void);
+
+protected:
+	ProgramInterface								getTargetInterface				(void) const;
+
+private:
+	IterateResult									iterate							(void);
+	virtual ProgramInterfaceDefinition::Program*	getProgramDefinition			(void) = 0;
+	virtual std::vector<std::string>				getQueryTargetResources			(void) = 0;
+
+	const ProgramResourceQueryTestTarget			m_queryTarget;
+};
+
+void checkProgramResourceUsage (const ProgramInterfaceDefinition::Program* program, const glw::Functions& gl, tcu::TestLog& log);
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FPROGRAMINTERFACEQUERYTESTCASE_HPP
diff --git a/modules/gles31/functional/es31fProgramInterfaceQueryTests.cpp b/modules/gles31/functional/es31fProgramInterfaceQueryTests.cpp
new file mode 100644
index 0000000..27c14f9
--- /dev/null
+++ b/modules/gles31/functional/es31fProgramInterfaceQueryTests.cpp
@@ -0,0 +1,6025 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Program interface query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fProgramInterfaceQueryTests.hpp"
+#include "es31fProgramInterfaceQueryTestCase.hpp"
+#include "es31fProgramInterfaceDefinition.hpp"
+#include "es31fProgramInterfaceDefinitionUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluVarTypeUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+#include "deStringUtil.hpp"
+#include "deSharedPtr.hpp"
+#include "deUniquePtr.hpp"
+#include "deSTLUtil.hpp"
+
+#include <set>
+#include <map>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+static bool stringEndsWith (const std::string& str, const std::string& suffix)
+{
+	if (suffix.length() > str.length())
+		return false;
+	else
+		return str.substr(str.length() - suffix.length()) == suffix;
+}
+
+static int getTypeSize (glu::DataType type)
+{
+	if (type == glu::TYPE_FLOAT)
+		return 4;
+	else if (type == glu::TYPE_INT || type == glu::TYPE_UINT)
+		return 4;
+	else if (type == glu::TYPE_BOOL)
+		return 4; // uint
+
+	DE_ASSERT(false);
+	return 0;
+}
+
+static int getVarTypeSize (const glu::VarType& type)
+{
+	if (type.isBasicType())
+		return glu::getDataTypeScalarSize(type.getBasicType()) * getTypeSize(glu::getDataTypeScalarType(type.getBasicType()));
+	else if (type.isStructType())
+	{
+		int size = 0;
+		for (int ndx = 0; ndx < type.getStructPtr()->getNumMembers(); ++ndx)
+			size += getVarTypeSize(type.getStructPtr()->getMember(ndx).getType());
+		return size;
+	}
+	else if (type.isArrayType())
+	{
+		if (type.getArraySize() == glu::VarType::UNSIZED_ARRAY)
+			return getVarTypeSize(type.getElementType());
+		else
+			return type.getArraySize() * getVarTypeSize(type.getElementType());
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return 0;
+	}
+}
+
+static std::string convertGLTypeNameToTestName (const char* glName)
+{
+	// vectors and matrices are fine as is
+	{
+		if (deStringBeginsWith(glName, "vec")  == DE_TRUE ||
+			deStringBeginsWith(glName, "ivec") == DE_TRUE ||
+			deStringBeginsWith(glName, "uvec") == DE_TRUE ||
+			deStringBeginsWith(glName, "bvec") == DE_TRUE ||
+			deStringBeginsWith(glName, "mat")  == DE_TRUE)
+			return std::string(glName);
+	}
+
+	// convert camel case to use underscore
+	{
+		std::ostringstream	buf;
+		std::istringstream	name					(glName);
+		bool				mergeNextToken			= false;
+		bool				previousTokenWasDigit	= false;
+
+		while (!name.eof())
+		{
+			std::ostringstream token;
+
+			while (name.peek() != EOF)
+			{
+				if ((de::isDigit((char)name.peek()) || de::isUpper((char)name.peek())) && token.tellp())
+					break;
+
+				token << de::toLower((char)name.get());
+			}
+
+			if (buf.str().empty() || mergeNextToken)
+				buf << token.str();
+			else
+				buf << '_' << token.str();
+
+			// Single char causes next char to be merged (don't split initialisms or acronyms) unless it is 'D' after a number (split to ..._2d_acronym_aa
+			mergeNextToken = false;
+			if (token.tellp() == (std::streamoff)1)
+			{
+				if (!previousTokenWasDigit || token.str()[0] != 'd')
+					mergeNextToken = true;
+
+				previousTokenWasDigit = de::isDigit(token.str()[0]);
+			}
+			else
+				previousTokenWasDigit = false;
+		}
+
+		return buf.str();
+	}
+}
+
+static glw::GLenum getProgramInterfaceGLEnum (ProgramInterface interface)
+{
+	static const glw::GLenum s_enums[] =
+	{
+		GL_UNIFORM,						// PROGRAMINTERFACE_UNIFORM
+		GL_UNIFORM_BLOCK,				// PROGRAMINTERFACE_UNIFORM_BLOCK
+		GL_ATOMIC_COUNTER_BUFFER,		// PROGRAMINTERFACE_ATOMIC_COUNTER_BUFFER
+		GL_PROGRAM_INPUT,				// PROGRAMINTERFACE_PROGRAM_INPUT
+		GL_PROGRAM_OUTPUT,				// PROGRAMINTERFACE_PROGRAM_OUTPUT
+		GL_TRANSFORM_FEEDBACK_VARYING,	// PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING
+		GL_BUFFER_VARIABLE,				// PROGRAMINTERFACE_BUFFER_VARIABLE
+		GL_SHADER_STORAGE_BLOCK,		// PROGRAMINTERFACE_SHADER_STORAGE_BLOCK
+	};
+
+	return de::getSizedArrayElement<PROGRAMINTERFACE_LAST>(s_enums, interface);
+
+}
+
+namespace ResourceDefinition
+{
+
+class Node
+{
+public:
+	enum NodeType
+	{
+		TYPE_PROGRAM = 0,
+		TYPE_SHADER,
+		TYPE_DEFAULT_BLOCK,
+		TYPE_VARIABLE,
+		TYPE_INTERFACE_BLOCK,
+		TYPE_ARRAY_ELEMENT,
+		TYPE_STRUCT_MEMBER,
+		TYPE_STORAGE_QUALIFIER,
+		TYPE_LAYOUT_QUALIFIER,
+		TYPE_SHADER_SET,
+		TYPE_INTERPOLATION_QUALIFIER,
+		TYPE_TRANSFORM_FEEDBACK_TARGET,
+
+		TYPE_LAST
+	};
+
+	typedef de::SharedPtr<const Node, de::DefaultDeleter<const Node>, false> SharedPtr;
+
+							Node				(NodeType type, const SharedPtr& enclosingNode) : m_type(type), m_enclosingNode(enclosingNode) { DE_ASSERT(type < TYPE_LAST); }
+	virtual					~Node				(void) { }
+
+	inline const Node*		getEnclosingNode	(void) const					{ return m_enclosingNode.get();	}
+	inline NodeType			getType				(void) const					{ return m_type;				}
+
+private:
+	const NodeType			m_type;
+	const SharedPtr			m_enclosingNode;
+};
+
+class Program : public Node
+{
+public:
+	Program (bool separable = false)
+		: Node			(TYPE_PROGRAM, SharedPtr())
+		, m_separable	(separable)
+	{
+	}
+
+	const bool m_separable;
+};
+
+class Shader : public Node
+{
+public:
+	Shader (const SharedPtr& enclosingNode, glu::ShaderType type, glu::GLSLVersion version)
+		: Node		(TYPE_SHADER, enclosingNode)
+		, m_type	(type)
+		, m_version	(version)
+	{
+		DE_ASSERT(enclosingNode->getType() == TYPE_PROGRAM);
+	}
+
+	const glu::ShaderType	m_type;
+	const glu::GLSLVersion	m_version;
+};
+
+class DefaultBlock : public Node
+{
+public:
+	DefaultBlock (const SharedPtr& enclosing)
+		: Node(TYPE_DEFAULT_BLOCK, enclosing)
+	{
+		// enclosed by the shader
+		DE_ASSERT(enclosing->getType() == TYPE_SHADER		||
+				  enclosing->getType() == TYPE_SHADER_SET);
+	}
+};
+
+class StorageQualifier : public Node
+{
+public:
+	StorageQualifier (const SharedPtr& enclosing, glu::Storage storage)
+		: Node		(TYPE_STORAGE_QUALIFIER, enclosing)
+		, m_storage	(storage)
+	{
+		// not a part of any block
+		DE_ASSERT(enclosing->getType() == TYPE_DEFAULT_BLOCK);
+	}
+
+	const glu::Storage	m_storage;
+};
+
+class Variable : public Node
+{
+public:
+	Variable (const SharedPtr& enclosing, glu::DataType dataType)
+		: Node			(TYPE_VARIABLE, enclosing)
+		, m_dataType	(dataType)
+	{
+		DE_ASSERT(enclosing->getType() == TYPE_STORAGE_QUALIFIER		||
+				  enclosing->getType() == TYPE_LAYOUT_QUALIFIER			||
+				  enclosing->getType() == TYPE_INTERPOLATION_QUALIFIER	||
+				  enclosing->getType() == TYPE_INTERFACE_BLOCK			||
+				  enclosing->getType() == TYPE_ARRAY_ELEMENT			||
+				  enclosing->getType() == TYPE_STRUCT_MEMBER			||
+				  enclosing->getType() == TYPE_TRANSFORM_FEEDBACK_TARGET);
+	}
+
+	const glu::DataType	m_dataType;
+};
+
+class InterfaceBlock : public Node
+{
+public:
+	InterfaceBlock (const SharedPtr& enclosing, bool named)
+		: Node		(TYPE_INTERFACE_BLOCK, enclosing)
+		, m_named	(named)
+	{
+		// Must be qualified
+		const Node* storageNode = enclosing.get();
+		while (storageNode->getType() == TYPE_ARRAY_ELEMENT || storageNode->getType() == TYPE_LAYOUT_QUALIFIER)
+			storageNode = storageNode->getEnclosingNode();
+
+		DE_ASSERT(storageNode->getType() == TYPE_STORAGE_QUALIFIER);
+		DE_UNREF(storageNode);
+	}
+
+	const bool	m_named;
+};
+
+class ArrayElement : public Node
+{
+public:
+	ArrayElement (const SharedPtr& enclosing, int arraySize = DEFAULT_SIZE)
+		: Node			(TYPE_ARRAY_ELEMENT, enclosing)
+		, m_arraySize	(arraySize)
+	{
+		DE_ASSERT(enclosing->getType() == TYPE_STORAGE_QUALIFIER		||
+				  enclosing->getType() == TYPE_LAYOUT_QUALIFIER			||
+				  enclosing->getType() == TYPE_INTERPOLATION_QUALIFIER	||
+				  enclosing->getType() == TYPE_INTERFACE_BLOCK			||
+				  enclosing->getType() == TYPE_ARRAY_ELEMENT			||
+				  enclosing->getType() == TYPE_STRUCT_MEMBER			||
+				  enclosing->getType() == TYPE_TRANSFORM_FEEDBACK_TARGET);
+	}
+
+	const int m_arraySize;
+
+	enum
+	{
+		DEFAULT_SIZE	= -1,
+		UNSIZED_ARRAY	= -2,
+	};
+};
+
+class StructMember : public Node
+{
+public:
+	StructMember (const SharedPtr& enclosing)
+		: Node(TYPE_STRUCT_MEMBER, enclosing)
+	{
+		DE_ASSERT(enclosing->getType() == TYPE_STORAGE_QUALIFIER		||
+				  enclosing->getType() == TYPE_LAYOUT_QUALIFIER			||
+				  enclosing->getType() == TYPE_INTERPOLATION_QUALIFIER	||
+				  enclosing->getType() == TYPE_INTERFACE_BLOCK			||
+				  enclosing->getType() == TYPE_ARRAY_ELEMENT			||
+				  enclosing->getType() == TYPE_STRUCT_MEMBER			||
+				  enclosing->getType() == TYPE_TRANSFORM_FEEDBACK_TARGET);
+	}
+};
+
+class LayoutQualifier : public Node
+{
+public:
+	LayoutQualifier (const SharedPtr& enclosing, const glu::Layout& layout)
+		: Node		(TYPE_LAYOUT_QUALIFIER, enclosing)
+		, m_layout	(layout)
+	{
+		DE_ASSERT(enclosing->getType() == TYPE_STORAGE_QUALIFIER		||
+				  enclosing->getType() == TYPE_LAYOUT_QUALIFIER			||
+				  enclosing->getType() == TYPE_INTERPOLATION_QUALIFIER	||
+				  enclosing->getType() == TYPE_DEFAULT_BLOCK			||
+				  enclosing->getType() == TYPE_INTERFACE_BLOCK);
+	}
+
+	const glu::Layout m_layout;
+};
+
+class InterpolationQualifier : public Node
+{
+public:
+	InterpolationQualifier (const SharedPtr& enclosing, const glu::Interpolation& interpolation)
+		: Node				(TYPE_INTERPOLATION_QUALIFIER, enclosing)
+		, m_interpolation	(interpolation)
+	{
+		DE_ASSERT(enclosing->getType() == TYPE_STORAGE_QUALIFIER		||
+				  enclosing->getType() == TYPE_LAYOUT_QUALIFIER			||
+				  enclosing->getType() == TYPE_INTERPOLATION_QUALIFIER	||
+				  enclosing->getType() == TYPE_DEFAULT_BLOCK			||
+				  enclosing->getType() == TYPE_INTERFACE_BLOCK);
+	}
+
+	const glu::Interpolation m_interpolation;
+};
+
+class ShaderSet : public Node
+{
+public:
+			ShaderSet			(const SharedPtr& enclosing, glu::GLSLVersion version);
+
+	void	setStage			(glu::ShaderType type, bool referencing);
+	bool	isStagePresent		(glu::ShaderType stage) const;
+	bool	isStageReferencing	(glu::ShaderType stage) const;
+
+	const glu::GLSLVersion	m_version;
+private:
+	bool					m_stagePresent[glu::SHADERTYPE_LAST];
+	bool					m_stageReferencing[glu::SHADERTYPE_LAST];
+};
+
+ShaderSet::ShaderSet (const SharedPtr& enclosing, glu::GLSLVersion version)
+	: Node		(TYPE_SHADER_SET, enclosing)
+	, m_version	(version)
+{
+	DE_ASSERT(enclosing->getType() == TYPE_PROGRAM);
+
+	deMemset(m_stagePresent, 0, sizeof(m_stagePresent));
+	deMemset(m_stageReferencing, 0, sizeof(m_stageReferencing));
+}
+
+void ShaderSet::setStage (glu::ShaderType type, bool referencing)
+{
+	m_stagePresent[type] = true;
+	m_stageReferencing[type] = referencing;
+}
+
+bool ShaderSet::isStagePresent (glu::ShaderType stage) const
+{
+	DE_ASSERT(stage < glu::SHADERTYPE_LAST);
+	return m_stagePresent[stage];
+}
+
+bool ShaderSet::isStageReferencing (glu::ShaderType stage) const
+{
+	DE_ASSERT(stage < glu::SHADERTYPE_LAST);
+	return m_stageReferencing[stage];
+}
+
+class TransformFeedbackTarget : public Node
+{
+public:
+	TransformFeedbackTarget (const SharedPtr& enclosing, const char* builtinVarName = DE_NULL)
+		: Node				(TYPE_TRANSFORM_FEEDBACK_TARGET, enclosing)
+		, m_builtinVarName	(builtinVarName)
+	{
+	}
+
+	const char* const m_builtinVarName;
+};
+
+} // ResourceDefinition
+
+static glu::Precision getDataTypeDefaultPrecision (const glu::DataType& type)
+{
+	if (glu::isDataTypeBoolOrBVec(type))
+		return glu::PRECISION_LAST;
+	else if (glu::isDataTypeScalarOrVector(type) || glu::isDataTypeMatrix(type))
+		return glu::PRECISION_HIGHP;
+	else if (glu::isDataTypeSampler(type))
+		return glu::PRECISION_HIGHP;
+	else if (glu::isDataTypeImage(type))
+		return glu::PRECISION_LAST;
+	else if (type == glu::TYPE_UINT_ATOMIC_COUNTER)
+		return glu::PRECISION_HIGHP;
+
+	DE_ASSERT(false);
+	return glu::PRECISION_LAST;
+}
+
+static de::MovePtr<ProgramInterfaceDefinition::Program>	generateProgramDefinitionFromResource (const ResourceDefinition::Node* resource)
+{
+	de::MovePtr<ProgramInterfaceDefinition::Program>	program	(new ProgramInterfaceDefinition::Program());
+	const ResourceDefinition::Node*						head	= resource;
+
+	if (head->getType() == ResourceDefinition::Node::TYPE_VARIABLE)
+	{
+		DE_ASSERT(dynamic_cast<const ResourceDefinition::Variable*>(resource));
+
+		enum BindingType
+		{
+			BINDING_VARIABLE,
+			BINDING_INTERFACE_BLOCK,
+			BINDING_DEFAULT_BLOCK
+		};
+
+		int											structNdx				= 0;
+		int											autoAssignArraySize		= 0;
+		const glu::DataType							basicType				= static_cast<const ResourceDefinition::Variable*>(resource)->m_dataType;
+		BindingType									boundObject				= BINDING_VARIABLE;
+		glu::VariableDeclaration					variable				(glu::VarType(basicType, getDataTypeDefaultPrecision(basicType)), "target");
+		glu::InterfaceBlock							interfaceBlock;
+		ProgramInterfaceDefinition::DefaultBlock	defaultBlock;
+		std::vector<std::string>					feedbackTargetVaryingPath;
+		bool										feedbackTargetSet		= false;
+
+		// image specific
+		if (glu::isDataTypeImage(basicType))
+		{
+			variable.memoryAccessQualifierBits |= glu::MEMORYACCESSQUALIFIER_READONLY_BIT;
+			variable.layout.binding = 1;
+
+			if (basicType >= glu::TYPE_IMAGE_2D && basicType <= glu::TYPE_IMAGE_3D)
+				variable.layout.format = glu::FORMATLAYOUT_RGBA8;
+			else if (basicType >= glu::TYPE_INT_IMAGE_2D && basicType <= glu::TYPE_INT_IMAGE_3D)
+				variable.layout.format = glu::FORMATLAYOUT_RGBA8I;
+			else if (basicType >= glu::TYPE_UINT_IMAGE_2D && basicType <= glu::TYPE_UINT_IMAGE_3D)
+				variable.layout.format = glu::FORMATLAYOUT_RGBA8UI;
+			else
+				DE_ASSERT(false);
+		}
+
+		// atomic counter specific
+		if (basicType == glu::TYPE_UINT_ATOMIC_COUNTER)
+			variable.layout.binding = 1;
+
+		for (head = head->getEnclosingNode(); head; head = head->getEnclosingNode())
+		{
+			if (head->getType() == ResourceDefinition::Node::TYPE_STORAGE_QUALIFIER)
+			{
+				const ResourceDefinition::StorageQualifier* qualifier = static_cast<const ResourceDefinition::StorageQualifier*>(head);
+
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::StorageQualifier*>(head));
+
+				if (boundObject == BINDING_VARIABLE)
+				{
+					DE_ASSERT(variable.storage == glu::STORAGE_LAST);
+					variable.storage = qualifier->m_storage;
+				}
+				else if (boundObject == BINDING_INTERFACE_BLOCK)
+				{
+					DE_ASSERT(interfaceBlock.storage == glu::STORAGE_LAST);
+					interfaceBlock.storage = qualifier->m_storage;
+				}
+				else
+					DE_ASSERT(false);
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_LAYOUT_QUALIFIER)
+			{
+				const ResourceDefinition::LayoutQualifier*	qualifier		= static_cast<const ResourceDefinition::LayoutQualifier*>(head);
+				glu::Layout*								targetLayout	= DE_NULL;
+
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::LayoutQualifier*>(head));
+
+				if (boundObject == BINDING_VARIABLE)
+					targetLayout = &variable.layout;
+				else if (boundObject == BINDING_INTERFACE_BLOCK)
+					targetLayout = &interfaceBlock.layout;
+				else
+					DE_ASSERT(false);
+
+				if (qualifier->m_layout.location != -1)
+					targetLayout->location = qualifier->m_layout.location;
+
+				if (qualifier->m_layout.binding != -1)
+					targetLayout->binding = qualifier->m_layout.binding;
+
+				if (qualifier->m_layout.offset != -1)
+					targetLayout->offset = qualifier->m_layout.offset;
+
+				if (qualifier->m_layout.format != glu::FORMATLAYOUT_LAST)
+					targetLayout->format = qualifier->m_layout.format;
+
+				if (qualifier->m_layout.matrixOrder != glu::MATRIXORDER_LAST)
+					targetLayout->matrixOrder = qualifier->m_layout.matrixOrder;
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_INTERPOLATION_QUALIFIER)
+			{
+				const ResourceDefinition::InterpolationQualifier* qualifier = static_cast<const ResourceDefinition::InterpolationQualifier*>(head);
+
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::InterpolationQualifier*>(head));
+
+				if (boundObject == BINDING_VARIABLE)
+					variable.interpolation = qualifier->m_interpolation;
+				else
+					DE_ASSERT(false);
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_ARRAY_ELEMENT)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::ArrayElement*>(head));
+
+				const ResourceDefinition::ArrayElement*	arrayElement = static_cast<const ResourceDefinition::ArrayElement*>(head);
+				int										arraySize;
+
+				// Vary array size per level
+				if (arrayElement->m_arraySize == ResourceDefinition::ArrayElement::DEFAULT_SIZE)
+				{
+					if (--autoAssignArraySize <= 1)
+						autoAssignArraySize = 3;
+
+					arraySize = autoAssignArraySize;
+				}
+				else if (arrayElement->m_arraySize == ResourceDefinition::ArrayElement::UNSIZED_ARRAY)
+					arraySize = glu::VarType::UNSIZED_ARRAY;
+				else
+					arraySize = arrayElement->m_arraySize;
+
+				if (boundObject == BINDING_VARIABLE)
+					variable.varType = glu::VarType(variable.varType, arraySize);
+				else if (boundObject == BINDING_INTERFACE_BLOCK)
+					interfaceBlock.dimensions.push_back(arraySize);
+				else
+					DE_ASSERT(false);
+
+				if (feedbackTargetSet)
+					feedbackTargetVaryingPath.back().append("[0]");
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_STRUCT_MEMBER)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::StructMember*>(head));
+				DE_ASSERT(boundObject == BINDING_VARIABLE);
+
+				// Struct members cannot contain any qualifiers except precision
+				DE_ASSERT(variable.interpolation == glu::INTERPOLATION_LAST);
+				DE_ASSERT(variable.layout == glu::Layout());
+				DE_ASSERT(variable.memoryAccessQualifierBits == 0);
+				DE_ASSERT(variable.storage == glu::STORAGE_LAST);
+
+				{
+					glu::StructType* structPtr = new glu::StructType(("StructType" + de::toString(structNdx++)).c_str());
+					structPtr->addMember(variable.name.c_str(), variable.varType);
+
+					variable = glu::VariableDeclaration(glu::VarType(structPtr), "target");
+				}
+
+				if (feedbackTargetSet)
+					feedbackTargetVaryingPath.push_back("target");
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::InterfaceBlock*>(head));
+				DE_ASSERT(boundObject == BINDING_VARIABLE);
+
+				const bool named = static_cast<const ResourceDefinition::InterfaceBlock*>(head)->m_named;
+
+				boundObject = BINDING_INTERFACE_BLOCK;
+
+				interfaceBlock.interfaceName = "TargetInterface";
+				interfaceBlock.instanceName = (named) ? ("targetInstance") : ("");
+				interfaceBlock.variables.push_back(variable);
+
+				if (feedbackTargetSet && !interfaceBlock.instanceName.empty())
+					feedbackTargetVaryingPath.push_back(interfaceBlock.interfaceName);
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::DefaultBlock*>(head));
+				DE_ASSERT(boundObject == BINDING_VARIABLE || boundObject == BINDING_INTERFACE_BLOCK);
+
+				if (boundObject == BINDING_VARIABLE)
+					defaultBlock.variables.push_back(variable);
+				else if (boundObject == BINDING_INTERFACE_BLOCK)
+					defaultBlock.interfaceBlocks.push_back(interfaceBlock);
+				else
+					DE_ASSERT(false);
+
+				boundObject = BINDING_DEFAULT_BLOCK;
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_SHADER)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::Shader*>(head));
+
+				const ResourceDefinition::Shader*	shaderDef	= static_cast<const ResourceDefinition::Shader*>(head);
+				ProgramInterfaceDefinition::Shader* shader		= program->addShader(shaderDef->m_type, shaderDef->m_version);
+
+				shader->getDefaultBlock() = defaultBlock;
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_SHADER_SET)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::ShaderSet*>(head));
+
+				const ResourceDefinition::ShaderSet* shaderDef = static_cast<const ResourceDefinition::ShaderSet*>(head);
+
+				for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; ++shaderType)
+				{
+					if (shaderDef->isStagePresent((glu::ShaderType)shaderType))
+					{
+						ProgramInterfaceDefinition::Shader* shader = program->addShader((glu::ShaderType)shaderType, shaderDef->m_version);
+
+						if (shaderDef->isStageReferencing((glu::ShaderType)shaderType))
+							shader->getDefaultBlock() = defaultBlock;
+					}
+				}
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_PROGRAM)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::Program*>(head));
+
+				const ResourceDefinition::Program* programDef = static_cast<const ResourceDefinition::Program*>(head);
+
+				program->setSeparable(programDef->m_separable);
+
+				DE_ASSERT(feedbackTargetSet == !feedbackTargetVaryingPath.empty());
+				if (!feedbackTargetVaryingPath.empty())
+				{
+					std::ostringstream buf;
+
+					for (std::vector<std::string>::reverse_iterator it = feedbackTargetVaryingPath.rbegin(); it != feedbackTargetVaryingPath.rend(); ++it)
+					{
+						if (it != feedbackTargetVaryingPath.rbegin())
+							buf << ".";
+						buf << *it;
+					}
+
+					program->addTransformFeedbackVarying(buf.str());
+					program->setTransformFeedbackMode(GL_INTERLEAVED_ATTRIBS);
+				}
+				break;
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_TRANSFORM_FEEDBACK_TARGET)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::TransformFeedbackTarget*>(head));
+
+				const ResourceDefinition::TransformFeedbackTarget* feedbackTarget = static_cast<const ResourceDefinition::TransformFeedbackTarget*>(head);
+
+				DE_ASSERT(feedbackTarget->m_builtinVarName == DE_NULL);
+				DE_UNREF(feedbackTarget);
+
+				feedbackTargetSet = true;
+				feedbackTargetVaryingPath.push_back(variable.name);
+			}
+			else
+			{
+				DE_ASSERT(DE_FALSE);
+				break;
+			}
+		}
+	}
+	else if (head->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK ||
+			 head->getType() == ResourceDefinition::Node::TYPE_TRANSFORM_FEEDBACK_TARGET)
+	{
+		const char* feedbackTargetVaryingName = DE_NULL;
+
+		// empty default block
+
+		for (; head; head = head->getEnclosingNode())
+		{
+			if (head->getType() == ResourceDefinition::Node::TYPE_SHADER)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::Shader*>(head));
+
+				const ResourceDefinition::Shader* shaderDef = static_cast<const ResourceDefinition::Shader*>(head);
+
+				program->addShader(shaderDef->m_type, shaderDef->m_version);
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_SHADER_SET)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::ShaderSet*>(head));
+
+				const ResourceDefinition::ShaderSet* shaderDef = static_cast<const ResourceDefinition::ShaderSet*>(head);
+
+				for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; ++shaderType)
+					if (shaderDef->isStagePresent((glu::ShaderType)shaderType))
+						program->addShader((glu::ShaderType)shaderType, shaderDef->m_version);
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_PROGRAM)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::Program*>(head));
+
+				const ResourceDefinition::Program* programDef = static_cast<const ResourceDefinition::Program*>(head);
+
+				program->setSeparable(programDef->m_separable);
+				if (feedbackTargetVaryingName)
+				{
+					program->addTransformFeedbackVarying(std::string(feedbackTargetVaryingName));
+					program->setTransformFeedbackMode(GL_INTERLEAVED_ATTRIBS);
+				}
+				break;
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_TRANSFORM_FEEDBACK_TARGET)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::TransformFeedbackTarget*>(head));
+
+				const ResourceDefinition::TransformFeedbackTarget* feedbackTarget = static_cast<const ResourceDefinition::TransformFeedbackTarget*>(head);
+
+				DE_ASSERT(feedbackTarget->m_builtinVarName != DE_NULL);
+
+				feedbackTargetVaryingName = feedbackTarget->m_builtinVarName;
+			}
+			else if (head->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK)
+			{
+			}
+			else
+			{
+				DE_ASSERT(DE_FALSE);
+				break;
+			}
+		}
+	}
+
+	return program;
+}
+
+static void checkAndLogProgram (const glu::ShaderProgram& program, const ProgramInterfaceDefinition::Program* programDefinition, const glw::Functions& gl, tcu::TestLog& log)
+{
+	const tcu::ScopedLogSection section(log, "Program", "Program");
+
+	log << program;
+	if (!program.isOk())
+	{
+		log << tcu::TestLog::Message << "Program build failed, checking if program exceeded implementation limits" << tcu::TestLog::EndMessage;
+		checkProgramResourceUsage(programDefinition, gl, log);
+
+		// within limits
+		throw tcu::TestError("could not build program");
+	}
+}
+
+// Resource list query case
+
+class ResourceListTestCase : public TestCase
+{
+public:
+												ResourceListTestCase		(Context& context, const ResourceDefinition::Node::SharedPtr& targetResource, const ProgramInterface& interface, const char* name = DE_NULL);
+												~ResourceListTestCase		(void);
+
+protected:
+	void										deinit						(void);
+	IterateResult								iterate						(void);
+
+	void										queryResourceList			(std::vector<std::string>& dst, glw::GLuint program);
+	bool										verifyResourceList			(const std::vector<std::string>& resourceList, const std::vector<std::string>& expectedResources);
+	bool										verifyResourceIndexQuery	(const std::vector<std::string>& resourceList, const std::vector<std::string>& referenceResources, glw::GLuint program);
+	bool										verifyMaxNameLength			(const std::vector<std::string>& referenceResourceList, glw::GLuint program);
+
+	static std::string							genTestCaseName				(const ResourceDefinition::Node*);
+
+	const ProgramInterface						m_programInterface;
+	ResourceDefinition::Node::SharedPtr			m_targetResource;
+};
+
+ResourceListTestCase::ResourceListTestCase (Context& context, const ResourceDefinition::Node::SharedPtr& targetResource, const ProgramInterface& interface, const char* name)
+	: TestCase				(context, (name == DE_NULL) ? (genTestCaseName(targetResource.get()).c_str()) : (name), "")
+	, m_programInterface	(interface)
+	, m_targetResource		(targetResource)
+{
+	// GL_ATOMIC_COUNTER_BUFFER: no resource names
+	DE_ASSERT(m_programInterface != PROGRAMINTERFACE_ATOMIC_COUNTER_BUFFER);
+}
+
+ResourceListTestCase::~ResourceListTestCase (void)
+{
+	deinit();
+}
+
+void ResourceListTestCase::deinit (void)
+{
+	m_targetResource.clear();
+}
+
+ResourceListTestCase::IterateResult ResourceListTestCase::iterate (void)
+{
+	const de::UniquePtr<ProgramInterfaceDefinition::Program>	programDefinition	(generateProgramDefinitionFromResource(m_targetResource.get()));
+	const glu::ShaderProgram									program				(m_context.getRenderContext(), generateProgramInterfaceProgramSources(programDefinition.get()));
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	checkAndLogProgram(program, programDefinition.get(), m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	// Check resource list
+	{
+		const tcu::ScopedLogSection	section				(m_testCtx.getLog(), "ResourceList", "Resource list");
+		std::vector<std::string>	resourceList;
+		std::vector<std::string>	expectedResources;
+
+		queryResourceList(resourceList, program.getProgram());
+		expectedResources = getProgramInterfaceResourceList(programDefinition.get(), m_programInterface);
+
+		// verify the list and the expected list match
+
+		if (!verifyResourceList(resourceList, expectedResources))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid resource list");
+
+		// verify GetProgramResourceIndex() matches the indices of the list
+
+		if (!verifyResourceIndexQuery(resourceList, expectedResources, program.getProgram()))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "GetProgramResourceIndex returned unexpected values");
+
+		// Verify MAX_NAME_LENGTH
+		if (!verifyMaxNameLength(resourceList, program.getProgram()))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "MAX_NAME_LENGTH invalid");
+	}
+
+	return STOP;
+}
+
+void ResourceListTestCase::queryResourceList (std::vector<std::string>& dst, glw::GLuint program)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const glw::GLenum		programInterface	= getProgramInterfaceGLEnum(m_programInterface);
+	glw::GLint				numActiveResources	= 0;
+	glw::GLint				maxNameLength		= 0;
+	std::vector<char>		buffer;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Querying " << glu::getProgramInterfaceName(programInterface) << " interface:" << tcu::TestLog::EndMessage;
+
+	gl.getProgramInterfaceiv(program, programInterface, GL_ACTIVE_RESOURCES, &numActiveResources);
+	gl.getProgramInterfaceiv(program, programInterface, GL_MAX_NAME_LENGTH, &maxNameLength);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "query interface");
+
+	m_testCtx.getLog()	<< tcu::TestLog::Message
+						<< "\tGL_ACTIVE_RESOURCES = " << numActiveResources << "\n"
+						<< "\tGL_MAX_NAME_LENGTH = " << maxNameLength
+						<< tcu::TestLog::EndMessage;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Querying all active resources" << tcu::TestLog::EndMessage;
+
+	buffer.resize(maxNameLength+1, '\0');
+
+	for (int resourceNdx = 0; resourceNdx < numActiveResources; ++resourceNdx)
+	{
+		glw::GLint written = 0;
+
+		gl.getProgramResourceName(program, programInterface, resourceNdx, maxNameLength, &written, &buffer[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query resource name");
+
+		dst.push_back(std::string(&buffer[0], written));
+	}
+}
+
+bool ResourceListTestCase::verifyResourceList (const std::vector<std::string>& resourceList, const std::vector<std::string>& expectedResources)
+{
+	bool error = false;
+
+	// Log and compare resource lists
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "GL returned resources:" << tcu::TestLog::EndMessage;
+
+	for (int ndx = 0; ndx < (int)resourceList.size(); ++ndx)
+		m_testCtx.getLog() << tcu::TestLog::Message << "\t" << ndx << ": " << resourceList[ndx] << tcu::TestLog::EndMessage;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Expected list of resources:" << tcu::TestLog::EndMessage;
+
+	for (int ndx = 0; ndx < (int)expectedResources.size(); ++ndx)
+		m_testCtx.getLog() << tcu::TestLog::Message << "\t" << ndx << ": " << expectedResources[ndx] << tcu::TestLog::EndMessage;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying resource list contents." << tcu::TestLog::EndMessage;
+
+	for (int ndx = 0; ndx < (int)expectedResources.size(); ++ndx)
+	{
+		if (!de::contains(resourceList.begin(), resourceList.end(), expectedResources[ndx]))
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, resource list did not contain active resource " << expectedResources[ndx] << tcu::TestLog::EndMessage;
+			error = true;
+		}
+	}
+
+	for (int ndx = 0; ndx < (int)resourceList.size(); ++ndx)
+	{
+		// Ignore all builtin variables
+		if (deStringBeginsWith(resourceList[ndx].c_str(), "gl_") == DE_FALSE && !de::contains(expectedResources.begin(), expectedResources.end(), resourceList[ndx]))
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, resource list contains unexpected resource name " << resourceList[ndx] << tcu::TestLog::EndMessage;
+			error = true;
+		}
+	}
+
+	return !error;
+}
+
+bool ResourceListTestCase::verifyResourceIndexQuery (const std::vector<std::string>& resourceList, const std::vector<std::string>& referenceResources, glw::GLuint program)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const glw::GLenum		programInterface	= getProgramInterfaceGLEnum(m_programInterface);
+	bool					error				= false;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying GetProgramResourceIndex returns correct indices for resource names." << tcu::TestLog::EndMessage;
+
+	for (int ndx = 0; ndx < (int)referenceResources.size(); ++ndx)
+	{
+		const glw::GLuint index = gl.getProgramResourceIndex(program, programInterface, referenceResources[ndx].c_str());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");
+
+		if (index == GL_INVALID_INDEX)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, for active resource " << referenceResources[ndx] << " got index GL_INVALID_INDEX." << tcu::TestLog::EndMessage;
+			error = true;
+		}
+		else if ((int)index >= (int)resourceList.size())
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, for active resource " << referenceResources[ndx] << " got index " << index << " (larger or equal to GL_ACTIVE_RESOURCES)." << tcu::TestLog::EndMessage;
+			error = true;
+		}
+		else if (resourceList[index] != referenceResources[ndx])
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, for active resource " << referenceResources[ndx] << " got index (index = " << index << ") of another resource (" << resourceList[index] << ")." << tcu::TestLog::EndMessage;
+			error = true;
+		}
+	}
+
+	// Query for "name" should match "name[0]"
+
+	for (int ndx = 0; ndx < (int)referenceResources.size(); ++ndx)
+	{
+		if (stringEndsWith(referenceResources[ndx], "[0]"))
+		{
+			const std::string	queryString	= referenceResources[ndx].substr(0, referenceResources[ndx].length()-3);
+			const glw::GLuint	index		= gl.getProgramResourceIndex(program, programInterface, queryString.c_str());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");
+
+			if (index == GL_INVALID_INDEX)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for " << queryString << " resulted in index GL_INVALID_INDEX." << tcu::TestLog::EndMessage;
+				error = true;
+			}
+			else if ((int)index >= (int)resourceList.size())
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for " << queryString << " resulted in index " << index << " (larger or equal to GL_ACTIVE_RESOURCES)." << tcu::TestLog::EndMessage;
+				error = true;
+			}
+			else if (resourceList[index] != queryString + "[0]")
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for " << queryString << " got index (index = " << index << ") of another resource (" << resourceList[index] << ")." << tcu::TestLog::EndMessage;
+				error = true;
+			}
+		}
+	}
+
+	return !error;
+}
+
+bool ResourceListTestCase::verifyMaxNameLength (const std::vector<std::string>& resourceList, glw::GLuint program)
+{
+	const glw::Functions&	gl						= m_context.getRenderContext().getFunctions();
+	const glw::GLenum		programInterface		= getProgramInterfaceGLEnum(m_programInterface);
+	glw::GLint				maxNameLength			= 0;
+	glw::GLint				expectedMaxNameLength	= 0;
+
+	gl.getProgramInterfaceiv(program, programInterface, GL_MAX_NAME_LENGTH, &maxNameLength);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "query interface");
+
+	for (int ndx = 0; ndx < (int)resourceList.size(); ++ndx)
+		expectedMaxNameLength = de::max(expectedMaxNameLength, (int)resourceList[ndx].size() + 1);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying MAX_NAME_LENGTH, expecting " << expectedMaxNameLength << tcu::TestLog::EndMessage;
+
+	if (expectedMaxNameLength != maxNameLength)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Error, got " << maxNameLength << tcu::TestLog::EndMessage;
+		return false;
+	}
+
+	return true;
+}
+
+std::string ResourceListTestCase::genTestCaseName (const ResourceDefinition::Node* root)
+{
+	std::ostringstream buf;
+	buf << "var";
+
+	for (const ResourceDefinition::Node* node = root; node; node = node->getEnclosingNode())
+	{
+		if (node->getType() == ResourceDefinition::Node::TYPE_STRUCT_MEMBER)
+			buf << "_struct";
+		if (node->getType() == ResourceDefinition::Node::TYPE_ARRAY_ELEMENT)
+			buf << "_array";
+	}
+
+	return buf.str();
+}
+
+// Resouce property query case
+
+class ResourceTestCase : public ProgramInterfaceQueryTestCase
+{
+public:
+															ResourceTestCase			(Context& context, const ResourceDefinition::Node::SharedPtr& targetResource, const ProgramResourceQueryTestTarget& queryTarget, const char* name = DE_NULL);
+															~ResourceTestCase			(void);
+
+private:
+	void													init						(void);
+	void													deinit						(void);
+	ProgramInterfaceDefinition::Program*					getProgramDefinition		(void);
+	std::vector<std::string>								getQueryTargetResources		(void);
+
+	static std::string										genTestCaseName				(const ResourceDefinition::Node*);
+	static std::string										genMultilineDescription		(const ResourceDefinition::Node*);
+
+	ResourceDefinition::Node::SharedPtr						m_targetResource;
+	ProgramInterfaceDefinition::Program*					m_program;
+	std::vector<std::string>								m_targetResources;
+};
+
+ResourceTestCase::ResourceTestCase (Context& context, const ResourceDefinition::Node::SharedPtr& targetResource, const ProgramResourceQueryTestTarget& queryTarget, const char* name)
+	: ProgramInterfaceQueryTestCase	(context, (name == DE_NULL) ? (genTestCaseName(targetResource.get()).c_str()) : (name), "", queryTarget)
+	, m_targetResource				(targetResource)
+	, m_program						(DE_NULL)
+{
+}
+
+ResourceTestCase::~ResourceTestCase (void)
+{
+	deinit();
+}
+
+void ResourceTestCase::init (void)
+{
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< genMultilineDescription(m_targetResource.get())
+		<< tcu::TestLog::EndMessage;
+
+	// Program
+	{
+		// Generate interface with target resource
+		m_program = generateProgramDefinitionFromResource(m_targetResource.get()).release();
+		m_targetResources = getProgramInterfaceResourceList(m_program, getTargetInterface());
+	}
+}
+
+void ResourceTestCase::deinit (void)
+{
+	m_targetResource.clear();
+
+	delete m_program;
+	m_program = DE_NULL;
+
+	m_targetResources = std::vector<std::string>();
+}
+
+ProgramInterfaceDefinition::Program* ResourceTestCase::getProgramDefinition (void)
+{
+	return m_program;
+}
+
+std::vector<std::string> ResourceTestCase::getQueryTargetResources (void)
+{
+	return m_targetResources;
+}
+
+std::string ResourceTestCase::genTestCaseName (const ResourceDefinition::Node* resource)
+{
+	if (resource->getType() == ResourceDefinition::Node::TYPE_VARIABLE)
+	{
+		DE_ASSERT(dynamic_cast<const ResourceDefinition::Variable*>(resource));
+
+		const ResourceDefinition::Variable* variable = static_cast<const ResourceDefinition::Variable*>(resource);
+
+		return convertGLTypeNameToTestName(glu::getDataTypeName(variable->m_dataType));
+	}
+
+	DE_ASSERT(false);
+	return "";
+}
+
+std::string ResourceTestCase::genMultilineDescription (const ResourceDefinition::Node* resource)
+{
+	if (resource->getType() == ResourceDefinition::Node::TYPE_VARIABLE)
+	{
+		DE_ASSERT(dynamic_cast<const ResourceDefinition::Variable*>(resource));
+
+		const ResourceDefinition::Variable*	varDef				= static_cast<const ResourceDefinition::Variable*>(resource);
+		std::ostringstream					buf;
+		std::ostringstream					structureDescriptor;
+		std::string							uniformType;
+
+		for (const ResourceDefinition::Node* node = resource; node; node = node->getEnclosingNode())
+		{
+			if (node->getType() == ResourceDefinition::Node::TYPE_STORAGE_QUALIFIER)
+			{
+				DE_ASSERT(dynamic_cast<const ResourceDefinition::StorageQualifier*>(node));
+
+				const ResourceDefinition::StorageQualifier*	storageDef = static_cast<const ResourceDefinition::StorageQualifier*>(node);
+
+				uniformType = std::string(" ") + glu::getStorageName(storageDef->m_storage);
+				structureDescriptor << "\n\tdeclared as " << glu::getStorageName(storageDef->m_storage);
+			}
+
+			if (node->getType() == ResourceDefinition::Node::TYPE_ARRAY_ELEMENT)
+				structureDescriptor << "\n\tarray";
+
+			if (node->getType() == ResourceDefinition::Node::TYPE_STRUCT_MEMBER)
+				structureDescriptor << "\n\tin a struct";
+
+			if (node->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK)
+				structureDescriptor << "\n\tin the default block";
+
+			if (node->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK)
+				structureDescriptor << "\n\tin an interface block";
+		}
+
+		buf	<< "Querying properties of " << glu::getDataTypeName(varDef->m_dataType) << uniformType << " variable.\n"
+			<< "Variable is:\n"
+			<< "\t" << glu::getDataTypeName(varDef->m_dataType)
+			<< structureDescriptor.str();
+
+		return buf.str();
+	}
+	else if (resource->getType() == ResourceDefinition::Node::TYPE_TRANSFORM_FEEDBACK_TARGET)
+	{
+		DE_ASSERT(dynamic_cast<const ResourceDefinition::TransformFeedbackTarget*>(resource));
+
+		const ResourceDefinition::TransformFeedbackTarget* xfbDef = static_cast<const ResourceDefinition::TransformFeedbackTarget*>(resource);
+
+		DE_ASSERT(xfbDef->m_builtinVarName);
+
+		return std::string("Querying properties of a builtin variable ") + xfbDef->m_builtinVarName;
+	}
+
+	DE_ASSERT(false);
+	return DE_NULL;
+}
+
+class ResourceNameBufferLimitCase : public TestCase
+{
+public:
+					ResourceNameBufferLimitCase		(Context& context, const char* name, const char* description);
+					~ResourceNameBufferLimitCase	(void);
+
+private:
+	IterateResult	iterate							(void);
+};
+
+ResourceNameBufferLimitCase::ResourceNameBufferLimitCase (Context& context, const char* name, const char* description)
+	: TestCase(context, name, description)
+{
+}
+
+ResourceNameBufferLimitCase::~ResourceNameBufferLimitCase (void)
+{
+}
+
+ResourceNameBufferLimitCase::IterateResult ResourceNameBufferLimitCase::iterate (void)
+{
+	static const char* const computeSource =	"#version 310 es\n"
+												"layout(local_size_x = 1) in;\n"
+												"uniform highp int u_uniformWithALongName;\n"
+												"writeonly buffer OutputBufferBlock { highp int b_output_int; };\n"
+												"void main ()\n"
+												"{\n"
+												"	b_output_int = u_uniformWithALongName;\n"
+												"}\n";
+
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	const glu::ShaderProgram	program			(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(computeSource));
+	glw::GLuint					uniformIndex;
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Log program
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program", "Program");
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			throw tcu::TestError("could not build program");
+	}
+
+	uniformIndex = gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_uniformWithALongName");
+	GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");
+
+	if (uniformIndex == GL_INVALID_INDEX)
+		throw tcu::TestError("Uniform u_uniformWithALongName resource index was GL_INVALID_INDEX");
+
+	// Query with different sized buffers, len("u_uniformWithALongName") == 22
+
+	{
+		static const struct
+		{
+			const char*	description;
+			int			querySize;
+			bool		returnLength;
+		} querySizes[] =
+		{
+			{ "Query to larger buffer",										24,		true	},
+			{ "Query to buffer the same size",								23,		true	},
+			{ "Query to one byte too small buffer",							22,		true	},
+			{ "Query to one byte buffer",									1,		true	},
+			{ "Query to zero sized buffer",									0,		true	},
+			{ "Query to one byte too small buffer, null length argument",	22,		false	},
+			{ "Query to one byte buffer, null length argument",				1,		false	},
+			{ "Query to zero sized buffer, null length argument",			0,		false	},
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(querySizes); ++ndx)
+		{
+			const tcu::ScopedLogSection			section				(m_testCtx.getLog(), "Query", querySizes[ndx].description);
+			const int							uniformNameLen		= 22;
+			const int							expectedWriteLen	= (querySizes[ndx].querySize != 0) ? (de::min(uniformNameLen, (querySizes[ndx].querySize - 1))) : (0);
+			char								buffer				[26];
+			glw::GLsizei						written				= -1;
+
+			// One byte for guard
+			DE_ASSERT((int)sizeof(buffer) > querySizes[ndx].querySize);
+
+			deMemset(buffer, 'x', sizeof(buffer));
+
+			if (querySizes[ndx].querySize)
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Querying uniform name to a buffer of size " << querySizes[ndx].querySize
+					<< ", expecting query to write " << expectedWriteLen << " bytes followed by a null terminator"
+					<< tcu::TestLog::EndMessage;
+			else
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Querying uniform name to a buffer of size " << querySizes[ndx].querySize
+					<< ", expecting query to write 0 bytes"
+					<< tcu::TestLog::EndMessage;
+
+			gl.getProgramResourceName(program.getProgram(), GL_UNIFORM, uniformIndex, querySizes[ndx].querySize, (querySizes[ndx].returnLength) ? (&written) : (DE_NULL), buffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query resource name");
+
+			if (querySizes[ndx].returnLength && written != expectedWriteLen)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected write length of " << expectedWriteLen << ", got " << written << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected write lenght");
+			}
+			else if (querySizes[ndx].querySize != 0 && buffer[expectedWriteLen] != 0)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected null terminator at " << expectedWriteLen << ", got dec=" << (int)buffer[expectedWriteLen] << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Missing null terminator");
+			}
+			else if (querySizes[ndx].querySize != 0 && buffer[expectedWriteLen+1] != 'x')
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, guard at index " << (expectedWriteLen+1) << " was modified, got dec=" << (int)buffer[expectedWriteLen+1] << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Wrote over buffer size");
+			}
+			else if (querySizes[ndx].querySize == 0 && buffer[0] != 'x')
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, buffer size was 0 but buffer contents were modified. At index 0 got dec=" << (int)buffer[0] << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents were modified");
+			}
+		}
+	}
+
+	return STOP;
+}
+
+class ResourceQueryBufferLimitCase : public TestCase
+{
+public:
+					ResourceQueryBufferLimitCase	(Context& context, const char* name, const char* description);
+					~ResourceQueryBufferLimitCase	(void);
+
+private:
+	IterateResult	iterate							(void);
+};
+
+ResourceQueryBufferLimitCase::ResourceQueryBufferLimitCase (Context& context, const char* name, const char* description)
+	: TestCase(context, name, description)
+{
+}
+
+ResourceQueryBufferLimitCase::~ResourceQueryBufferLimitCase (void)
+{
+}
+
+ResourceQueryBufferLimitCase::IterateResult ResourceQueryBufferLimitCase::iterate (void)
+{
+	static const char* const computeSource =	"#version 310 es\n"
+												"layout(local_size_x = 1) in;\n"
+												"uniform highp int u_uniform;\n"
+												"writeonly buffer OutputBufferBlock { highp int b_output_int; };\n"
+												"void main ()\n"
+												"{\n"
+												"	b_output_int = u_uniform;\n"
+												"}\n";
+
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	const glu::ShaderProgram	program			(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(computeSource));
+	glw::GLuint					uniformIndex;
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Log program
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program", "Program");
+
+		m_testCtx.getLog() << program;
+		if (!program.isOk())
+			throw tcu::TestError("could not build program");
+	}
+
+	uniformIndex = gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_uniform");
+	GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");
+
+	if (uniformIndex == GL_INVALID_INDEX)
+		throw tcu::TestError("Uniform u_uniform resource index was GL_INVALID_INDEX");
+
+	// Query uniform properties
+
+	{
+		static const struct
+		{
+			const char*	description;
+			int			numProps;
+			int			bufferSize;
+			bool		returnLength;
+		} querySizes[] =
+		{
+			{ "Query to a larger buffer",							2, 3, true	},
+			{ "Query to too small a buffer",						3, 2, true	},
+			{ "Query to zero sized buffer",							3, 0, true	},
+			{ "Query to a larger buffer, null length argument",		2, 3, false	},
+			{ "Query to too small a buffer, null length argument",	3, 2, false	},
+			{ "Query to zero sized buffer, null length argument",	3, 0, false	},
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(querySizes); ++ndx)
+		{
+			const tcu::ScopedLogSection		section				(m_testCtx.getLog(), "QueryToLarger", querySizes[ndx].description);
+			const glw::GLenum				props[]				= { GL_LOCATION, GL_LOCATION, GL_LOCATION };
+			const int						expectedWriteLen	= de::min(querySizes[ndx].bufferSize, querySizes[ndx].numProps);
+			int								params[]			= { 255, 255, 255, 255 };
+			glw::GLsizei					written				= -1;
+
+			DE_ASSERT(querySizes[ndx].numProps <= DE_LENGTH_OF_ARRAY(props));
+			DE_ASSERT(querySizes[ndx].bufferSize < DE_LENGTH_OF_ARRAY(params)); // leave at least one element for overflow detection
+
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "Querying " << querySizes[ndx].numProps << " uniform prop(s) to a buffer with size " << querySizes[ndx].bufferSize << ". Expecting query to return " << expectedWriteLen << " prop(s)"
+				<< tcu::TestLog::EndMessage;
+
+			gl.getProgramResourceiv(program.getProgram(), GL_UNIFORM, uniformIndex, querySizes[ndx].numProps, props, querySizes[ndx].bufferSize, (querySizes[ndx].returnLength) ? (&written) : (DE_NULL), params);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query program resources");
+
+			if (querySizes[ndx].returnLength && written != expectedWriteLen)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected write length of " << expectedWriteLen << ", got " << written << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected write lenght");
+			}
+			else if (params[expectedWriteLen] != 255)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, guard at index " << (expectedWriteLen) << " was modified. Was 255 before call, got dec=" << params[expectedWriteLen] << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Wrote over buffer size");
+			}
+		}
+	}
+
+	return STOP;
+}
+
+class InterfaceBlockBaseCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASE_NAMED_BLOCK = 0,
+		CASE_UNNAMED_BLOCK,
+		CASE_BLOCK_ARRAY,
+
+		CASE_LAST
+	};
+
+											InterfaceBlockBaseCase		(Context& context, const char* name, const char* description, glu::Storage storage, CaseType caseType);
+											~InterfaceBlockBaseCase		(void);
+
+private:
+	void									init						(void);
+	void									deinit						(void);
+
+protected:
+	const glu::Storage						m_storage;
+	const CaseType							m_caseType;
+	ProgramInterfaceDefinition::Program*	m_program;
+};
+
+InterfaceBlockBaseCase::InterfaceBlockBaseCase (Context& context, const char* name, const char* description, glu::Storage storage, CaseType caseType)
+	: TestCase		(context, name, description)
+	, m_storage		(storage)
+	, m_caseType	(caseType)
+	, m_program		(DE_NULL)
+{
+	DE_ASSERT(storage == glu::STORAGE_UNIFORM || storage == glu::STORAGE_BUFFER);
+}
+
+InterfaceBlockBaseCase::~InterfaceBlockBaseCase (void)
+{
+	deinit();
+}
+
+void InterfaceBlockBaseCase::init (void)
+{
+	ProgramInterfaceDefinition::Shader* shader;
+
+	m_program = new ProgramInterfaceDefinition::Program();
+	shader = m_program->addShader(glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES);
+
+	// PrecedingInterface
+	{
+		glu::InterfaceBlock precedingInterfaceBlock;
+
+		precedingInterfaceBlock.interfaceName	= "PrecedingInterface";
+		precedingInterfaceBlock.layout.binding	= 0;
+		precedingInterfaceBlock.storage			= m_storage;
+		precedingInterfaceBlock.instanceName	= "precedingInstance";
+
+		precedingInterfaceBlock.variables.push_back(glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "precedingMember"));
+
+		// Unsized array type
+		if (m_storage == glu::STORAGE_BUFFER)
+			precedingInterfaceBlock.variables.push_back(glu::VariableDeclaration(glu::VarType(glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), glu::VarType::UNSIZED_ARRAY), "precedingMemberUnsizedArray"));
+		else
+			precedingInterfaceBlock.variables.push_back(glu::VariableDeclaration(glu::VarType(glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), 2), "precedingMemberArray"));
+
+		shader->getDefaultBlock().interfaceBlocks.push_back(precedingInterfaceBlock);
+	}
+
+	// TargetInterface
+	{
+		glu::InterfaceBlock targetInterfaceBlock;
+
+		targetInterfaceBlock.interfaceName	= "TargetInterface";
+		targetInterfaceBlock.layout.binding	= 1;
+		targetInterfaceBlock.storage		= m_storage;
+
+		if (m_caseType == CASE_UNNAMED_BLOCK)
+			targetInterfaceBlock.instanceName = "";
+		else
+			targetInterfaceBlock.instanceName = "targetInstance";
+
+		if (m_caseType == CASE_BLOCK_ARRAY)
+			targetInterfaceBlock.dimensions.push_back(2);
+
+		// Basic type
+		{
+			targetInterfaceBlock.variables.push_back(glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "blockMemberBasic"));
+		}
+
+		// Array type
+		{
+			targetInterfaceBlock.variables.push_back(glu::VariableDeclaration(glu::VarType(glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), 3), "blockMemberArray"));
+		}
+
+		// Struct type
+		{
+			glu::StructType* structPtr = new glu::StructType("StructType");
+			structPtr->addMember("structMemberBasic", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP));
+			structPtr->addMember("structMemberArray", glu::VarType(glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), 2));
+
+			targetInterfaceBlock.variables.push_back(glu::VariableDeclaration(glu::VarType(glu::VarType(structPtr), 2), "blockMemberStruct"));
+		}
+
+		// Unsized array type
+		if (m_storage == glu::STORAGE_BUFFER)
+			targetInterfaceBlock.variables.push_back(glu::VariableDeclaration(glu::VarType(glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), glu::VarType::UNSIZED_ARRAY), "blockMemberUnsizedArray"));
+
+		shader->getDefaultBlock().interfaceBlocks.push_back(targetInterfaceBlock);
+	}
+
+	// TrailingInterface
+	{
+		glu::InterfaceBlock trailingInterfaceBlock;
+
+		trailingInterfaceBlock.interfaceName	= "TrailingInterface";
+		trailingInterfaceBlock.layout.binding	= 3;
+		trailingInterfaceBlock.storage			= m_storage;
+		trailingInterfaceBlock.instanceName		= "trailingInstance";
+		trailingInterfaceBlock.variables.push_back(glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "trailingMember"));
+
+		shader->getDefaultBlock().interfaceBlocks.push_back(trailingInterfaceBlock);
+	}
+
+	DE_ASSERT(m_program->isValid());
+}
+
+void InterfaceBlockBaseCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+class InterfaceBlockActiveVariablesTestCase : public InterfaceBlockBaseCase
+{
+public:
+											InterfaceBlockActiveVariablesTestCase	(Context& context, const char* name, const char* description, glu::Storage storage, CaseType caseType);
+
+private:
+	IterateResult							iterate									(void);
+};
+
+InterfaceBlockActiveVariablesTestCase::InterfaceBlockActiveVariablesTestCase (Context& context, const char* name, const char* description, glu::Storage storage, CaseType caseType)
+	: InterfaceBlockBaseCase(context, name, description, storage, caseType)
+{
+}
+
+InterfaceBlockActiveVariablesTestCase::IterateResult InterfaceBlockActiveVariablesTestCase::iterate (void)
+{
+	const ProgramInterface			programInterface				= (m_storage == glu::STORAGE_UNIFORM) ? (PROGRAMINTERFACE_UNIFORM_BLOCK) :
+																	  (m_storage == glu::STORAGE_BUFFER) ? (PROGRAMINTERFACE_SHADER_STORAGE_BLOCK) :
+																	  (PROGRAMINTERFACE_LAST);
+	const glw::GLenum				programGLInterfaceValue			= getProgramInterfaceGLEnum(programInterface);
+	const glw::GLenum				programMemberInterfaceValue		= (m_storage == glu::STORAGE_UNIFORM) ? (GL_UNIFORM) :
+																	  (m_storage == glu::STORAGE_BUFFER) ? (GL_BUFFER_VARIABLE) :
+																	  (0);
+	const std::vector<std::string>	blockNames						= getProgramInterfaceResourceList(m_program, programInterface);
+	glu::ShaderProgram				program							(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
+	int								expectedMaxNumActiveVariables	= 0;
+
+	DE_ASSERT(programInterface != PROGRAMINTERFACE_LAST);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	// Verify all blocks
+
+	for (int blockNdx = 0; blockNdx < (int)blockNames.size(); ++blockNdx)
+	{
+		const tcu::ScopedLogSection section				(m_testCtx.getLog(), "Block", "Block \"" + blockNames[blockNdx] + "\"");
+		const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
+		const glw::GLuint			resourceNdx			= gl.getProgramResourceIndex(program.getProgram(), programGLInterfaceValue, blockNames[blockNdx].c_str());
+		glw::GLint					numActiveResources;
+		std::vector<std::string>	activeResourceNames;
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");
+
+		if (resourceNdx == GL_INVALID_INDEX)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, getProgramResourceIndex returned GL_INVALID_INDEX for \"" << blockNames[blockNdx] << "\"" << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Resource not found");
+			continue;
+		}
+
+		// query block information
+
+		{
+			const glw::GLenum	props[]			= { GL_NUM_ACTIVE_VARIABLES };
+			glw::GLint			retBuffer[2]	= { -1, -1 };
+			glw::GLint			written			= -1;
+
+			gl.getProgramResourceiv(program.getProgram(), programGLInterfaceValue, resourceNdx, DE_LENGTH_OF_ARRAY(props), props, 1, &written, retBuffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query GL_NUM_ACTIVE_VARIABLES");
+
+			numActiveResources = retBuffer[0];
+			expectedMaxNumActiveVariables = de::max(expectedMaxNumActiveVariables, numActiveResources);
+			m_testCtx.getLog() << tcu::TestLog::Message << "NUM_ACTIVE_VARIABLES = " << numActiveResources << tcu::TestLog::EndMessage;
+
+			if (written == -1 || retBuffer[0] == -1)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, Query for NUM_ACTIVE_VARIABLES did not return a value" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query for NUM_ACTIVE_VARIABLES failed");
+				continue;
+			}
+			else if (retBuffer[1] != -1)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, Query for NUM_ACTIVE_VARIABLES returned too many values" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query for NUM_ACTIVE_VARIABLES returned too many values");
+				continue;
+			}
+			else if (retBuffer[0] < 0)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, NUM_ACTIVE_VARIABLES < 0" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "NUM_ACTIVE_VARIABLES < 0");
+				continue;
+			}
+		}
+
+		// query block variable information
+
+		{
+			const glw::GLenum			props[]					= { GL_ACTIVE_VARIABLES };
+			std::vector<glw::GLint>		activeVariableIndices	(numActiveResources + 1, -1);	// Allocate one extra trailing to detect wrong write lengths
+			glw::GLint					written					= -1;
+
+			gl.getProgramResourceiv(program.getProgram(), programGLInterfaceValue, resourceNdx, DE_LENGTH_OF_ARRAY(props), props, (glw::GLsizei)activeVariableIndices.size(), &written, &activeVariableIndices[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query GL_ACTIVE_VARIABLES");
+
+			if (written == -1)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, Query for GL_ACTIVE_VARIABLES did not return any values" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query for GL_ACTIVE_VARIABLES failed");
+				continue;
+			}
+			else if (written != numActiveResources)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, Query for GL_ACTIVE_VARIABLES did not return NUM_ACTIVE_VARIABLES values" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query for GL_ACTIVE_VARIABLES returned invalid number of values");
+				continue;
+			}
+			else if (activeVariableIndices.back() != -1)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, GL_ACTIVE_VARIABLES query return buffer trailing guard value was modified, getProgramResourceiv returned more than NUM_ACTIVE_VARIABLES values" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query for GL_ACTIVE_VARIABLES returned too many values");
+				continue;
+			}
+
+			// log indices
+			{
+				tcu::MessageBuilder builder(&m_testCtx.getLog());
+
+				builder << "Active variable indices: {";
+				for (int varNdx = 0; varNdx < numActiveResources; ++varNdx)
+				{
+					if (varNdx)
+						builder << ", ";
+					builder << activeVariableIndices[varNdx];
+				}
+				builder << "}" << tcu::TestLog::EndMessage;
+			}
+
+			// collect names
+
+			activeResourceNames.resize(numActiveResources);
+
+			for (int varNdx = 0; varNdx < numActiveResources; ++varNdx)
+			{
+				const glw::GLenum	nameProp	= GL_NAME_LENGTH;
+				glw::GLint			nameLength	= -1;
+				std::vector<char>	nameBuffer;
+
+				written = -1;
+				gl.getProgramResourceiv(program.getProgram(), programMemberInterfaceValue, activeVariableIndices[varNdx], 1, &nameProp, 1, &written, &nameLength);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "query GL_NAME_LENGTH");
+
+				if (nameLength <= 0 || written <= 0)
+				{
+					m_testCtx.getLog() << tcu::TestLog::Message << "Error, GL_NAME_LENGTH query failed" << tcu::TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "GL_NAME_LENGTH query failed");
+					continue;
+				}
+
+				nameBuffer.resize(nameLength + 2, 'X'); // allocate more than required
+				written = -1;
+				gl.getProgramResourceName(program.getProgram(), programMemberInterfaceValue, activeVariableIndices[varNdx], nameLength+1, &written, &nameBuffer[0]);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "getProgramResourceName");
+
+				if (written <= 0)
+				{
+					m_testCtx.getLog() << tcu::TestLog::Message << "Error, name query failed, no data written" << tcu::TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "name query failed");
+					continue;
+				}
+				else if (written > nameLength)
+				{
+					m_testCtx.getLog() << tcu::TestLog::Message << "Error, name query failed, query returned too much data" << tcu::TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "name query failed");
+					continue;
+				}
+
+				activeResourceNames[varNdx] = std::string(&nameBuffer[0], written);
+			}
+
+			// log collected names
+			{
+				tcu::MessageBuilder builder(&m_testCtx.getLog());
+
+				builder << "Active variables:\n";
+				for (int varNdx = 0; varNdx < numActiveResources; ++varNdx)
+					builder << "\t" << activeResourceNames[varNdx] << "\n";
+				builder << tcu::TestLog::EndMessage;
+			}
+		}
+
+		// verify names
+		{
+			glu::InterfaceBlock*		block		= DE_NULL;
+			const std::string			blockName	= glu::parseVariableName(blockNames[blockNdx].c_str());
+			std::vector<std::string>	referenceList;
+
+			for (int interfaceNdx = 0; interfaceNdx < (int)m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
+			{
+				if (m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks[interfaceNdx].interfaceName == blockName)
+				{
+					block = &m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks[interfaceNdx];
+					break;
+				}
+			}
+
+			if (!block)
+				throw tcu::InternalError("could not find block referenced in the reference resource list");
+
+			// generate reference list
+
+			referenceList = getProgramInterfaceBlockMemberResourceList(*block);
+			{
+				tcu::MessageBuilder builder(&m_testCtx.getLog());
+
+				builder << "Expected variable names:\n";
+				for (int varNdx = 0; varNdx < (int)referenceList.size(); ++varNdx)
+					builder << "\t" << referenceList[varNdx] << "\n";
+				builder << tcu::TestLog::EndMessage;
+			}
+
+			// compare lists
+			{
+				bool listsIdentical = true;
+
+				for (int ndx = 0; ndx < (int)referenceList.size(); ++ndx)
+				{
+					if (!de::contains(activeResourceNames.begin(), activeResourceNames.end(), referenceList[ndx]))
+					{
+						m_testCtx.getLog() << tcu::TestLog::Message << "Error, variable name list did not contain active variable " << referenceList[ndx] << tcu::TestLog::EndMessage;
+						listsIdentical = false;
+					}
+				}
+
+				for (int ndx = 0; ndx < (int)activeResourceNames.size(); ++ndx)
+				{
+					if (!de::contains(referenceList.begin(), referenceList.end(), activeResourceNames[ndx]))
+					{
+						m_testCtx.getLog() << tcu::TestLog::Message << "Error, variable name list contains unexpected resource \"" << activeResourceNames[ndx] << "\"" << tcu::TestLog::EndMessage;
+						listsIdentical = false;
+					}
+				}
+
+				if (listsIdentical)
+					m_testCtx.getLog() << tcu::TestLog::Message << "Lists identical" << tcu::TestLog::EndMessage;
+				else
+				{
+					m_testCtx.getLog() << tcu::TestLog::Message << "Error, invalid active variable list" << tcu::TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid active variable list");
+					continue;
+				}
+			}
+		}
+	}
+
+	// Max num active variables
+	{
+		const tcu::ScopedLogSection	section					(m_testCtx.getLog(), "MaxNumActiveVariables", "MAX_NUM_ACTIVE_VARIABLES");
+		const glw::Functions&		gl						= m_context.getRenderContext().getFunctions();
+		glw::GLint					maxNumActiveVariables	= -1;
+
+		gl.getProgramInterfaceiv(program.getProgram(), programGLInterfaceValue, GL_MAX_NUM_ACTIVE_VARIABLES, &maxNumActiveVariables);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query MAX_NUM_ACTIVE_VARIABLES");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "MAX_NUM_ACTIVE_VARIABLES = " << maxNumActiveVariables << tcu::TestLog::EndMessage;
+
+		if (expectedMaxNumActiveVariables != maxNumActiveVariables)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected MAX_NUM_ACTIVE_VARIABLES" << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "unexpected MAX_NUM_ACTIVE_VARIABLES");
+		}
+		else
+			m_testCtx.getLog() << tcu::TestLog::Message << "MAX_NUM_ACTIVE_VARIABLES valid" << tcu::TestLog::EndMessage;
+	}
+
+	return STOP;
+}
+
+class InterfaceBlockDataSizeTestCase : public InterfaceBlockBaseCase
+{
+public:
+											InterfaceBlockDataSizeTestCase	(Context& context, const char* name, const char* description, glu::Storage storage, CaseType caseType);
+
+private:
+	IterateResult							iterate							(void);
+	int										getBlockMinDataSize				(const std::string& blockName) const;
+	int										getBlockMinDataSize				(const glu::InterfaceBlock& block) const;
+};
+
+InterfaceBlockDataSizeTestCase::InterfaceBlockDataSizeTestCase (Context& context, const char* name, const char* description, glu::Storage storage, CaseType caseType)
+	: InterfaceBlockBaseCase(context, name, description, storage, caseType)
+{
+}
+
+InterfaceBlockDataSizeTestCase::IterateResult InterfaceBlockDataSizeTestCase::iterate (void)
+{
+	const ProgramInterface			programInterface		= (m_storage == glu::STORAGE_UNIFORM) ? (PROGRAMINTERFACE_UNIFORM_BLOCK) :
+															  (m_storage == glu::STORAGE_BUFFER) ? (PROGRAMINTERFACE_SHADER_STORAGE_BLOCK) :
+															  (PROGRAMINTERFACE_LAST);
+	const glw::GLenum				programGLInterfaceValue	= getProgramInterfaceGLEnum(programInterface);
+	const std::vector<std::string>	blockNames				= getProgramInterfaceResourceList(m_program, programInterface);
+	glu::ShaderProgram				program					(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
+
+	DE_ASSERT(programInterface != PROGRAMINTERFACE_LAST);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	// Verify all blocks
+	for (int blockNdx = 0; blockNdx < (int)blockNames.size(); ++blockNdx)
+	{
+		const tcu::ScopedLogSection section				(m_testCtx.getLog(), "Block", "Block \"" + blockNames[blockNdx] + "\"");
+		const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
+		const glw::GLuint			resourceNdx			= gl.getProgramResourceIndex(program.getProgram(), programGLInterfaceValue, blockNames[blockNdx].c_str());
+		const int					expectedMinDataSize	= getBlockMinDataSize(blockNames[blockNdx]);
+		glw::GLint					queryDataSize		= -1;
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");
+
+		if (resourceNdx == GL_INVALID_INDEX)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, getProgramResourceIndex returned GL_INVALID_INDEX for \"" << blockNames[blockNdx] << "\"" << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Resource not found");
+			continue;
+		}
+
+		// query
+		{
+			const glw::GLenum prop = GL_BUFFER_DATA_SIZE;
+
+			gl.getProgramResourceiv(program.getProgram(), programGLInterfaceValue, resourceNdx, 1, &prop, 1, DE_NULL, &queryDataSize);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query resource BUFFER_DATA_SIZE");
+		}
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "BUFFER_DATA_SIZE = " << queryDataSize << "\n"
+			<< "Buffer data size with tight packing: " << expectedMinDataSize
+			<< tcu::TestLog::EndMessage;
+
+		if (queryDataSize < expectedMinDataSize)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, buffer size was less than minimum buffer data size" << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer data size invalid");
+			continue;
+		}
+		else
+			m_testCtx.getLog() << tcu::TestLog::Message << "Buffer size valid" << tcu::TestLog::EndMessage;
+	}
+
+	return STOP;
+}
+
+int InterfaceBlockDataSizeTestCase::getBlockMinDataSize (const std::string& blockFullName) const
+{
+	const std::string blockName = glu::parseVariableName(blockFullName.c_str());
+
+	for (int interfaceNdx = 0; interfaceNdx < (int)m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
+	{
+		if (m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks[interfaceNdx].interfaceName == blockName &&
+			m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks[interfaceNdx].storage == m_storage)
+			return getBlockMinDataSize(m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks[interfaceNdx]);
+	}
+
+	DE_ASSERT(false);
+	return -1;
+}
+
+class AtomicCounterCase : public TestCase
+{
+public:
+											AtomicCounterCase			(Context& context, const char* name, const char* description);
+											~AtomicCounterCase			(void);
+
+private:
+	void									init						(void);
+	void									deinit						(void);
+
+protected:
+	int										getNumAtomicCounterBuffers	(void) const;
+	int										getMaxNumActiveVariables	(void) const;
+	int										getBufferVariableCount		(int binding) const;
+	int										getBufferMinimumDataSize	(int binding) const;
+
+	ProgramInterfaceDefinition::Program*	m_program;
+};
+
+AtomicCounterCase::AtomicCounterCase (Context& context, const char* name, const char* description)
+	: TestCase	(context, name, description)
+	, m_program	(DE_NULL)
+{
+}
+
+AtomicCounterCase::~AtomicCounterCase (void)
+{
+	deinit();
+}
+
+void AtomicCounterCase::init (void)
+{
+	ProgramInterfaceDefinition::Shader* shader;
+
+	m_program = new ProgramInterfaceDefinition::Program();
+	shader = m_program->addShader(glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES);
+
+	{
+		glu::VariableDeclaration decl(glu::VarType(glu::TYPE_UINT_ATOMIC_COUNTER, glu::PRECISION_LAST), "binding1_counter1", glu::STORAGE_UNIFORM);
+		decl.layout.binding = 1;
+		shader->getDefaultBlock().variables.push_back(decl);
+	}
+	{
+		glu::VariableDeclaration decl(glu::VarType(glu::TYPE_UINT_ATOMIC_COUNTER, glu::PRECISION_LAST), "binding1_counter2", glu::STORAGE_UNIFORM);
+		decl.layout.binding = 1;
+		decl.layout.offset = 8;
+
+		shader->getDefaultBlock().variables.push_back(decl);
+	}
+	{
+		glu::VariableDeclaration decl(glu::VarType(glu::TYPE_UINT_ATOMIC_COUNTER, glu::PRECISION_LAST), "binding2_counter1", glu::STORAGE_UNIFORM);
+		decl.layout.binding = 2;
+		shader->getDefaultBlock().variables.push_back(decl);
+	}
+
+	DE_ASSERT(m_program->isValid());
+}
+
+void AtomicCounterCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+int AtomicCounterCase::getNumAtomicCounterBuffers (void) const
+{
+	std::set<int> buffers;
+
+	for (int ndx = 0; ndx < (int)m_program->getShaders()[0]->getDefaultBlock().variables.size(); ++ndx)
+	{
+		if (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.isBasicType() &&
+			glu::isDataTypeAtomicCounter(m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.getBasicType()))
+		{
+			buffers.insert(m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.binding);
+		}
+	}
+
+	return (int)buffers.size();
+}
+
+int AtomicCounterCase::getMaxNumActiveVariables (void) const
+{
+	int					maxVars			= 0;
+	std::map<int,int>	numBufferVars;
+
+	for (int ndx = 0; ndx < (int)m_program->getShaders()[0]->getDefaultBlock().variables.size(); ++ndx)
+	{
+		if (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.isBasicType() &&
+			glu::isDataTypeAtomicCounter(m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.getBasicType()))
+		{
+			const int binding = m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.binding;
+
+			if (numBufferVars.find(binding) == numBufferVars.end())
+				numBufferVars[binding] = 1;
+			else
+				++numBufferVars[binding];
+		}
+	}
+
+	for (std::map<int,int>::const_iterator it = numBufferVars.begin(); it != numBufferVars.end(); ++it)
+		maxVars = de::max(maxVars, it->second);
+
+	return maxVars;
+}
+
+int AtomicCounterCase::getBufferVariableCount (int binding) const
+{
+	int numVars = 0;
+
+	for (int ndx = 0; ndx < (int)m_program->getShaders()[0]->getDefaultBlock().variables.size(); ++ndx)
+	{
+		if (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.isBasicType() &&
+			glu::isDataTypeAtomicCounter(m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.getBasicType()) &&
+			m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.binding == binding)
+			++numVars;
+	}
+
+	return numVars;
+}
+
+int AtomicCounterCase::getBufferMinimumDataSize (int binding) const
+{
+	int minSize			= -1;
+	int currentOffset	= 0;
+
+	for (int ndx = 0; ndx < (int)m_program->getShaders()[0]->getDefaultBlock().variables.size(); ++ndx)
+	{
+		if (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.isBasicType() &&
+			glu::isDataTypeAtomicCounter(m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.getBasicType()) &&
+			m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.binding == binding)
+		{
+			const int thisOffset = (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.offset != -1) ? (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.offset) : (currentOffset);
+			currentOffset = thisOffset + 4;
+
+			minSize = de::max(minSize, thisOffset + 4);
+		}
+	}
+
+	return minSize;
+}
+
+class AtomicCounterResourceListCase : public AtomicCounterCase
+{
+public:
+						AtomicCounterResourceListCase	(Context& context, const char* name, const char* description);
+
+private:
+	IterateResult		iterate							(void);
+};
+
+AtomicCounterResourceListCase::AtomicCounterResourceListCase (Context& context, const char* name, const char* description)
+	: AtomicCounterCase(context, name, description)
+{
+}
+
+AtomicCounterResourceListCase::IterateResult AtomicCounterResourceListCase::iterate (void)
+{
+	const glu::ShaderProgram program(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	{
+		const tcu::ScopedLogSection		section						(m_testCtx.getLog(), "ActiveResources", "ACTIVE_RESOURCES");
+		const glw::Functions&			gl							= m_context.getRenderContext().getFunctions();
+		glw::GLint						numActiveResources			= -1;
+		const int						numExpectedActiveResources	= 2; // 2 buffer bindings
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying ACTIVE_RESOURCES, expecting " << numExpectedActiveResources << tcu::TestLog::EndMessage;
+
+		gl.getProgramInterfaceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, GL_ACTIVE_RESOURCES, &numActiveResources);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query GL_ACTIVE_RESOURCES");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "ACTIVE_RESOURCES = " << numActiveResources << tcu::TestLog::EndMessage;
+
+		if (numActiveResources != numExpectedActiveResources)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected ACTIVE_RESOURCES" << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected ACTIVE_RESOURCES");
+		}
+		else
+			m_testCtx.getLog() << tcu::TestLog::Message << "ACTIVE_RESOURCES valid" << tcu::TestLog::EndMessage;
+	}
+
+	return STOP;
+}
+
+class AtomicCounterActiveVariablesCase : public AtomicCounterCase
+{
+public:
+					AtomicCounterActiveVariablesCase	(Context& context, const char* name, const char* description);
+
+private:
+	IterateResult	iterate								(void);
+};
+
+AtomicCounterActiveVariablesCase::AtomicCounterActiveVariablesCase (Context& context, const char* name, const char* description)
+	: AtomicCounterCase(context, name, description)
+{
+}
+
+AtomicCounterActiveVariablesCase::IterateResult AtomicCounterActiveVariablesCase::iterate (void)
+{
+	const glw::Functions&		gl								= m_context.getRenderContext().getFunctions();
+	const glu::ShaderProgram	program							(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
+	const int					numAtomicBuffers				= getNumAtomicCounterBuffers();
+	const int					expectedMaxNumActiveVariables	= getMaxNumActiveVariables();
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	// check active variables
+	{
+		const tcu::ScopedLogSection	section						(m_testCtx.getLog(), "Interface", "ATOMIC_COUNTER_BUFFER interface");
+		glw::GLint					queryActiveResources		= -1;
+		glw::GLint					queryMaxNumActiveVariables	= -1;
+
+		gl.getProgramInterfaceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, GL_ACTIVE_RESOURCES, &queryActiveResources);
+		gl.getProgramInterfaceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, GL_MAX_NUM_ACTIVE_VARIABLES, &queryMaxNumActiveVariables);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query interface");
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "GL_ACTIVE_RESOURCES = " << queryActiveResources << "\n"
+			<< "GL_MAX_NUM_ACTIVE_VARIABLES = " << queryMaxNumActiveVariables << "\n"
+			<< tcu::TestLog::EndMessage;
+
+		if (queryActiveResources != numAtomicBuffers)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected GL_ACTIVE_RESOURCES, expected " << numAtomicBuffers << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected GL_ACTIVE_RESOURCES");
+		}
+
+		if (queryMaxNumActiveVariables != expectedMaxNumActiveVariables)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected GL_MAX_NUM_ACTIVE_VARIABLES, expected " << expectedMaxNumActiveVariables << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected GL_MAX_NUM_ACTIVE_VARIABLES");
+		}
+	}
+
+	// Check each buffer
+	for (int bufferNdx = 0; bufferNdx < numAtomicBuffers; ++bufferNdx)
+	{
+		const tcu::ScopedLogSection	section				(m_testCtx.getLog(), "Resource", "Resource index " + de::toString(bufferNdx));
+		std::vector<glw::GLint>		activeVariables;
+		std::vector<std::string>	memberNames;
+
+		// Find active variables
+		{
+			const glw::GLenum	numActiveVariablesProp	= GL_NUM_ACTIVE_VARIABLES;
+			const glw::GLenum	activeVariablesProp		= GL_ACTIVE_VARIABLES;
+			glw::GLint			numActiveVariables		= -2;
+			glw::GLint			written					= -1;
+
+			gl.getProgramResourceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferNdx, 1, &numActiveVariablesProp, 1, &written, &numActiveVariables);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query num active variables");
+
+			if (numActiveVariables <= 0)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected NUM_ACTIVE_VARIABLES: " << numActiveVariables  << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected NUM_ACTIVE_VARIABLES");
+				continue;
+			}
+
+			if (written <= 0)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for NUM_ACTIVE_VARIABLES returned no values" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "NUM_ACTIVE_VARIABLES query failed");
+				continue;
+			}
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "GL_NUM_ACTIVE_VARIABLES = " << numActiveVariables << tcu::TestLog::EndMessage;
+
+			written = -1;
+			activeVariables.resize(numActiveVariables + 1, -2);
+
+			gl.getProgramResourceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferNdx, 1, &activeVariablesProp, numActiveVariables, &written, &activeVariables[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query active variables");
+
+			if (written != numActiveVariables)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, unexpected number of ACTIVE_VARIABLES, NUM_ACTIVE_VARIABLES = " << numActiveVariables << ", query returned " << written << " values" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected ACTIVE_VARIABLES");
+				continue;
+			}
+
+			if (activeVariables.back() != -2)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for ACTIVE_VARIABLES wrote over target buffer bounds" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "ACTIVE_VARIABLES query failed");
+				continue;
+			}
+
+			activeVariables.pop_back();
+		}
+
+		// log indices
+		{
+			tcu::MessageBuilder builder(&m_testCtx.getLog());
+
+			builder << "Active variable indices: {";
+			for (int varNdx = 0; varNdx < (int)activeVariables.size(); ++varNdx)
+			{
+				if (varNdx)
+					builder << ", ";
+				builder << activeVariables[varNdx];
+			}
+			builder << "}" << tcu::TestLog::EndMessage;
+		}
+
+		// collect member names
+		for (int ndx = 0; ndx < (int)activeVariables.size(); ++ndx)
+		{
+			const glw::GLenum	nameLengthProp	= GL_NAME_LENGTH;
+			glw::GLint			nameLength		= -1;
+			glw::GLint			written			= -1;
+			std::vector<char>	nameBuf;
+
+			gl.getProgramResourceiv(program.getProgram(), GL_UNIFORM, activeVariables[ndx], 1, &nameLengthProp, 1, &written, &nameLength);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer variable name length");
+
+			if (written <= 0 || nameLength == -1)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for GL_NAME_LENGTH returned no values" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "GL_NAME_LENGTH query failed");
+				continue;
+			}
+
+			nameBuf.resize(nameLength + 2, 'X'); // +2 to tolerate potential off-by-ones in some implementations, name queries will check these cases better
+			written = -1;
+
+			gl.getProgramResourceName(program.getProgram(), GL_UNIFORM, activeVariables[ndx], (int)nameBuf.size(), &written, &nameBuf[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer variable name");
+
+			if (written <= 0)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for resource name returned no name" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Name query failed");
+				continue;
+			}
+
+			memberNames.push_back(std::string(&nameBuf[0], written));
+		}
+
+		// log names
+		{
+			tcu::MessageBuilder builder(&m_testCtx.getLog());
+
+			builder << "Active variables:\n";
+			for (int varNdx = 0; varNdx < (int)memberNames.size(); ++varNdx)
+			{
+				builder << "\t" << memberNames[varNdx] << "\n";
+			}
+			builder << tcu::TestLog::EndMessage;
+		}
+
+		// check names are all in the same buffer
+		{
+			bool bindingsValid = true;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Verifying names" << tcu::TestLog::EndMessage;
+
+			for (int nameNdx = 0; nameNdx < (int)memberNames.size(); ++nameNdx)
+			{
+				int prevBinding = -1;
+
+				for (int varNdx = 0; varNdx < (int)m_program->getShaders()[0]->getDefaultBlock().variables.size(); ++varNdx)
+				{
+					if (m_program->getShaders()[0]->getDefaultBlock().variables[varNdx].name == memberNames[nameNdx])
+					{
+						const int varBinding = m_program->getShaders()[0]->getDefaultBlock().variables[varNdx].layout.binding;
+
+						if (prevBinding == -1 || prevBinding == varBinding)
+							prevBinding = varBinding;
+						else
+							bindingsValid = false;
+					}
+				}
+
+				if (prevBinding == -1)
+				{
+					m_testCtx.getLog() << tcu::TestLog::Message << "Error, could not find variable with name \"" << memberNames[nameNdx] << "\"" << tcu::TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Variable name invalid");
+				}
+				else if (getBufferVariableCount(prevBinding) != (int)memberNames.size())
+				{
+					m_testCtx.getLog()
+						<< tcu::TestLog::Message
+						<< "Error, unexpected variable count for binding " << prevBinding
+						<< ". Expected " << getBufferVariableCount(prevBinding) << ", got " << (int)memberNames.size()
+						<< tcu::TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Variable names invalid");
+				}
+			}
+
+			if (!bindingsValid)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Error, all resource do not share the same buffer" << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Active variables invalid");
+				continue;
+			}
+		}
+	}
+
+	return STOP;
+}
+
+class AtomicCounterBufferBindingCase : public AtomicCounterCase
+{
+public:
+					AtomicCounterBufferBindingCase		(Context& context, const char* name, const char* description);
+
+private:
+	IterateResult	iterate								(void);
+};
+
+AtomicCounterBufferBindingCase::AtomicCounterBufferBindingCase (Context& context, const char* name, const char* description)
+	: AtomicCounterCase(context, name, description)
+{
+}
+
+AtomicCounterBufferBindingCase::IterateResult AtomicCounterBufferBindingCase::iterate (void)
+{
+	const glw::Functions&		gl								= m_context.getRenderContext().getFunctions();
+	const glu::ShaderProgram	program							(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
+	const int					numAtomicBuffers				= getNumAtomicCounterBuffers();
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	// check every buffer
+	for (int bufferNdx = 0; bufferNdx < numAtomicBuffers; ++bufferNdx)
+	{
+		const tcu::ScopedLogSection	section				(m_testCtx.getLog(), "Resource", "Resource index " + de::toString(bufferNdx));
+		const glw::GLenum			bufferBindingProp	= GL_BUFFER_BINDING;
+		glw::GLint					bufferBinding		= -1;
+		glw::GLint					written				= -1;
+
+		gl.getProgramResourceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferNdx, 1, &bufferBindingProp, 1, &written, &bufferBinding);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer binding");
+
+		if (written <= 0)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for BUFFER_BINDING returned no values." << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "BUFFER_BINDING query failed");
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_BUFFER_BINDING = " << bufferBinding << tcu::TestLog::EndMessage;
+
+		// no such buffer binding?
+		if (getBufferVariableCount(bufferBinding) == 0)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, got buffer with BUFFER_BINDING = " << bufferBinding << ", but such buffer does not exist." << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected BUFFER_BINDING");
+		}
+	}
+
+	return STOP;
+}
+
+class AtomicCounterBufferDataSizeCase : public AtomicCounterCase
+{
+public:
+					AtomicCounterBufferDataSizeCase		(Context& context, const char* name, const char* description);
+
+private:
+	IterateResult	iterate								(void);
+};
+
+AtomicCounterBufferDataSizeCase::AtomicCounterBufferDataSizeCase (Context& context, const char* name, const char* description)
+	: AtomicCounterCase(context, name, description)
+{
+}
+
+AtomicCounterBufferDataSizeCase::IterateResult AtomicCounterBufferDataSizeCase::iterate (void)
+{
+	const glw::Functions&		gl								= m_context.getRenderContext().getFunctions();
+	const glu::ShaderProgram	program							(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
+	const int					numAtomicBuffers				= getNumAtomicCounterBuffers();
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	// check every buffer
+	for (int bufferNdx = 0; bufferNdx < numAtomicBuffers; ++bufferNdx)
+	{
+		const tcu::ScopedLogSection	section				(m_testCtx.getLog(), "Resource", "Resource index " + de::toString(bufferNdx));
+		const glw::GLenum			props[]				= { GL_BUFFER_BINDING, GL_BUFFER_DATA_SIZE };
+		glw::GLint					values[]			= { -1, -1 };
+		glw::GLint					written				= -1;
+		int							bufferMinDataSize;
+
+		gl.getProgramResourceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferNdx, DE_LENGTH_OF_ARRAY(props), props, DE_LENGTH_OF_ARRAY(values), &written, values);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer binding");
+
+		if (written != 2)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for (BUFFER_BINDING, BUFFER_DATA_SIZE) returned " << written << " value(s)." << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "property query failed");
+			continue;
+		}
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "GL_BUFFER_BINDING = " << values[0] << "\n"
+			<< "GL_BUFFER_DATA_SIZE = " << values[1]
+			<< tcu::TestLog::EndMessage;
+
+		bufferMinDataSize = getBufferMinimumDataSize(values[0]);
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying data size, expected greater than or equal to " << bufferMinDataSize << tcu::TestLog::EndMessage;
+
+		// no such buffer binding?
+		if (bufferMinDataSize == -1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, got buffer with BUFFER_BINDING = " << values[0] << ", but such buffer does not exist." << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected BUFFER_BINDING");
+		}
+		else if (values[1] < bufferMinDataSize)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, got buffer with BUFFER_DATA_SIZE = " << values[1] << ", expected greater than or equal to " << bufferMinDataSize << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected BUFFER_BINDING");
+		}
+		else
+			m_testCtx.getLog() << tcu::TestLog::Message << "Data size valid" << tcu::TestLog::EndMessage;
+	}
+
+	return STOP;
+}
+
+class AtomicCounterReferencedByCase : public TestCase
+{
+public:
+											AtomicCounterReferencedByCase	(Context& context, const char* name, const char* description, deUint32 presentStagesMask, deUint32 activeStagesMask);
+											~AtomicCounterReferencedByCase	(void);
+
+private:
+	void									init							(void);
+	void									deinit							(void);
+	IterateResult							iterate							(void);
+
+	const deUint32							m_presentStagesMask;
+	const deUint32							m_activeStagesMask;
+	ProgramInterfaceDefinition::Program*	m_program;
+};
+
+AtomicCounterReferencedByCase::AtomicCounterReferencedByCase (Context& context, const char* name, const char* description, deUint32 presentStagesMask, deUint32 activeStagesMask)
+	: TestCase				(context, name, description)
+	, m_presentStagesMask	(presentStagesMask)
+	, m_activeStagesMask	(activeStagesMask)
+	, m_program				(DE_NULL)
+{
+	DE_ASSERT((activeStagesMask & presentStagesMask) == activeStagesMask);
+}
+
+AtomicCounterReferencedByCase::~AtomicCounterReferencedByCase (void)
+{
+	deinit();
+}
+
+void AtomicCounterReferencedByCase::init (void)
+{
+	glu::VariableDeclaration atomicVar(glu::VarType(glu::TYPE_UINT_ATOMIC_COUNTER, glu::PRECISION_LAST), "targetCounter", glu::STORAGE_UNIFORM);
+
+	atomicVar.layout.binding = 1;
+
+	m_program = new ProgramInterfaceDefinition::Program();
+	m_program->setSeparable(((m_presentStagesMask & (1 << glu::SHADERTYPE_VERTEX)) != 0) != ((m_presentStagesMask & (1 << glu::SHADERTYPE_FRAGMENT)) != 0));
+
+	for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; ++shaderType)
+	{
+		if (m_activeStagesMask & (1 << shaderType))
+			m_program->addShader((glu::ShaderType)shaderType, glu::GLSL_VERSION_310_ES)->getDefaultBlock().variables.push_back(atomicVar);
+		else if (m_presentStagesMask & (1 << shaderType))
+			m_program->addShader((glu::ShaderType)shaderType, glu::GLSL_VERSION_310_ES);
+	}
+
+	DE_ASSERT(m_program->isValid());
+}
+
+void AtomicCounterReferencedByCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+AtomicCounterReferencedByCase::IterateResult AtomicCounterReferencedByCase::iterate (void)
+{
+	static const struct
+	{
+		glw::GLenum		propName;
+		glu::ShaderType	shaderType;
+	} targetProps[] =
+	{
+		{ GL_REFERENCED_BY_VERTEX_SHADER,	glu::SHADERTYPE_VERTEX		},
+		{ GL_REFERENCED_BY_FRAGMENT_SHADER,	glu::SHADERTYPE_FRAGMENT	},
+		{ GL_REFERENCED_BY_COMPUTE_SHADER,	glu::SHADERTYPE_COMPUTE		},
+	};
+
+	const glw::Functions&		gl								= m_context.getRenderContext().getFunctions();
+	const glu::ShaderProgram	program							(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	// check props
+	for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(targetProps); ++propNdx)
+	{
+		const glw::GLenum	prop		= targetProps[propNdx].propName;
+		const glw::GLint	expected	= ((m_activeStagesMask & (1 << targetProps[propNdx].shaderType)) != 0) ? (GL_TRUE) : (GL_FALSE);
+		glw::GLint			value		= -1;
+		glw::GLint			written		= -1;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying " << glu::getProgramResourcePropertyName(prop) << ", expecting " << glu::getBooleanName(expected) << tcu::TestLog::EndMessage;
+
+		gl.getProgramResourceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, 0, 1, &prop, 1, &written, &value);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer binding");
+
+		if (written != 1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for referenced_by_* returned invalid number of values." << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "property query failed");
+			continue;
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << glu::getProgramResourcePropertyName(prop) << " = " << glu::getBooleanStr(value) << tcu::TestLog::EndMessage;
+
+		if (value != expected)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected value" << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "unexpected property value");
+			continue;
+		}
+	}
+
+	return STOP;
+}
+
+class ProgramInputOutputReferencedByCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASE_VERTEX_FRAGMENT = 0,
+		CASE_SEPARABLE_VERTEX,
+		CASE_SEPARABLE_FRAGMENT,
+
+		CASE_LAST
+	};
+											ProgramInputOutputReferencedByCase	(Context& context, const char* name, const char* description, glu::Storage targetStorage, CaseType caseType);
+											~ProgramInputOutputReferencedByCase	(void);
+
+private:
+	void									init								(void);
+	void									deinit								(void);
+	IterateResult							iterate								(void);
+
+	const CaseType							m_caseType;
+	const glu::Storage						m_targetStorage;
+	ProgramInterfaceDefinition::Program*	m_program;
+};
+
+ProgramInputOutputReferencedByCase::ProgramInputOutputReferencedByCase (Context& context, const char* name, const char* description, glu::Storage targetStorage, CaseType caseType)
+	: TestCase				(context, name, description)
+	, m_caseType			(caseType)
+	, m_targetStorage		(targetStorage)
+	, m_program				(DE_NULL)
+{
+	DE_ASSERT(caseType < CASE_LAST);
+}
+
+ProgramInputOutputReferencedByCase::~ProgramInputOutputReferencedByCase (void)
+{
+	deinit();
+}
+
+void ProgramInputOutputReferencedByCase::init (void)
+{
+	m_program = new ProgramInterfaceDefinition::Program();
+
+	if (m_caseType == CASE_SEPARABLE_VERTEX || m_caseType == CASE_SEPARABLE_FRAGMENT)
+	{
+		const std::string				varName		= (m_targetStorage == glu::STORAGE_IN) ? ("shaderInput") : ("shaderOutput");
+		const glu::VariableDeclaration	targetDecl	(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), varName, m_targetStorage);
+
+		m_program->setSeparable(true);
+		m_program->addShader((m_caseType == CASE_SEPARABLE_VERTEX) ? (glu::SHADERTYPE_VERTEX) : (glu::SHADERTYPE_FRAGMENT), glu::GLSL_VERSION_310_ES)->getDefaultBlock().variables.push_back(targetDecl);
+	}
+	else if (m_caseType == CASE_VERTEX_FRAGMENT)
+	{
+		ProgramInterfaceDefinition::Shader*	vertex		= m_program->addShader(glu::SHADERTYPE_VERTEX, glu::GLSL_VERSION_310_ES);
+		ProgramInterfaceDefinition::Shader*	fragment	= m_program->addShader(glu::SHADERTYPE_FRAGMENT, glu::GLSL_VERSION_310_ES);
+
+		m_program->setSeparable(false);
+
+		vertex->getDefaultBlock().variables.push_back(glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "shaderInput", glu::STORAGE_IN));
+		vertex->getDefaultBlock().variables.push_back(glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "shaderOutput", glu::STORAGE_OUT, glu::INTERPOLATION_LAST, glu::Layout(1)));
+
+		fragment->getDefaultBlock().variables.push_back(glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "shaderInput", glu::STORAGE_IN, glu::INTERPOLATION_LAST, glu::Layout(1)));
+		fragment->getDefaultBlock().variables.push_back(glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "shaderOutput", glu::STORAGE_OUT, glu::INTERPOLATION_LAST, glu::Layout(0)));
+	}
+	else
+		DE_ASSERT(false);
+
+	DE_ASSERT(m_program->isValid());
+}
+
+void ProgramInputOutputReferencedByCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+ProgramInputOutputReferencedByCase::IterateResult ProgramInputOutputReferencedByCase::iterate (void)
+{
+	static const struct
+	{
+		glw::GLenum		propName;
+		glu::ShaderType	shaderType;
+	} targetProps[] =
+	{
+		{ GL_REFERENCED_BY_VERTEX_SHADER,	glu::SHADERTYPE_VERTEX		},
+		{ GL_REFERENCED_BY_FRAGMENT_SHADER,	glu::SHADERTYPE_FRAGMENT	},
+		{ GL_REFERENCED_BY_COMPUTE_SHADER,	glu::SHADERTYPE_COMPUTE		},
+	};
+
+	const glw::Functions&		gl								= m_context.getRenderContext().getFunctions();
+	const glu::ShaderProgram	program							(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
+	const std::string			targetResourceName				= (m_targetStorage == glu::STORAGE_IN) ? ("shaderInput") : ("shaderOutput");
+	const glw::GLenum			programGLInterface				= (m_targetStorage == glu::STORAGE_IN) ? (GL_PROGRAM_INPUT) : (GL_PROGRAM_OUTPUT);
+	glw::GLuint					resourceIndex;
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	// find target resource index
+
+	resourceIndex = gl.getProgramResourceIndex(program.getProgram(), programGLInterface, targetResourceName.c_str());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");
+
+	if (resourceIndex == GL_INVALID_INDEX)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for resource \"" << targetResourceName << "\" index returned invalid index." << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "could not find target resource");
+		return STOP;
+	}
+
+	// check props
+	for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(targetProps); ++propNdx)
+	{
+		const glw::GLenum	prop			= targetProps[propNdx].propName;
+		const bool			vertexPresent	= (m_caseType == CASE_VERTEX_FRAGMENT) || (m_caseType == CASE_SEPARABLE_VERTEX);
+		const bool			fragmentPresent	= (m_caseType == CASE_VERTEX_FRAGMENT) || (m_caseType == CASE_SEPARABLE_FRAGMENT);
+		const bool			expected		= (m_targetStorage == glu::STORAGE_IN) ?
+												((vertexPresent) ? (targetProps[propNdx].shaderType == glu::SHADERTYPE_VERTEX) : (targetProps[propNdx].shaderType == glu::SHADERTYPE_FRAGMENT)) :
+												((fragmentPresent) ? (targetProps[propNdx].shaderType == glu::SHADERTYPE_FRAGMENT) : (targetProps[propNdx].shaderType == glu::SHADERTYPE_VERTEX));
+		glw::GLint			value			= -1;
+		glw::GLint			written			= -1;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying " << glu::getProgramResourcePropertyName(prop) << ", expecting " << ((expected) ? ("TRUE") : ("FALSE")) << tcu::TestLog::EndMessage;
+
+		gl.getProgramResourceiv(program.getProgram(), programGLInterface, resourceIndex, 1, &prop, 1, &written, &value);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer binding");
+
+		if (written != 1)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for referenced_by_* returned invalid number of values." << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "property query failed");
+			continue;
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << glu::getProgramResourcePropertyName(prop) << " = " << glu::getBooleanStr(value) << tcu::TestLog::EndMessage;
+
+		if (value != ((expected) ? (GL_TRUE) : (GL_FALSE)))
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected value" << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "unexpected property value");
+			continue;
+		}
+	}
+
+	return STOP;
+}
+
+class FeedbackResourceListTestCase : public ResourceListTestCase
+{
+public:
+											FeedbackResourceListTestCase	(Context& context, const ResourceDefinition::Node::SharedPtr& resource, const char* name);
+											~FeedbackResourceListTestCase	(void);
+
+private:
+	IterateResult							iterate							(void);
+};
+
+FeedbackResourceListTestCase::FeedbackResourceListTestCase (Context& context, const ResourceDefinition::Node::SharedPtr& resource, const char* name)
+	: ResourceListTestCase(context, resource, PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, name)
+{
+}
+
+FeedbackResourceListTestCase::~FeedbackResourceListTestCase (void)
+{
+	deinit();
+}
+
+FeedbackResourceListTestCase::IterateResult FeedbackResourceListTestCase::iterate (void)
+{
+	const de::UniquePtr<ProgramInterfaceDefinition::Program>	programDefinition	(generateProgramDefinitionFromResource(m_targetResource.get()));
+	const glu::ShaderProgram									program				(m_context.getRenderContext(), generateProgramInterfaceProgramSources(programDefinition.get()));
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Feedback varyings
+	{
+		tcu::MessageBuilder builder(&m_testCtx.getLog());
+		builder << "Transform feedback varyings: {";
+		for (int ndx = 0; ndx < (int)programDefinition->getTransformFeedbackVaryings().size(); ++ndx)
+		{
+			if (ndx)
+				builder << ", ";
+			builder << "\"" << programDefinition->getTransformFeedbackVaryings()[ndx] << "\"";
+		}
+		builder << "}" << tcu::TestLog::EndMessage;
+	}
+
+	checkAndLogProgram(program, programDefinition.get(), m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+
+	// Check resource list
+	{
+		const tcu::ScopedLogSection	section				(m_testCtx.getLog(), "ResourceList", "Resource list");
+		std::vector<std::string>	resourceList;
+		std::vector<std::string>	expectedResources;
+
+		queryResourceList(resourceList, program.getProgram());
+		expectedResources = getProgramInterfaceResourceList(programDefinition.get(), m_programInterface);
+
+		// verify the list and the expected list match
+
+		if (!verifyResourceList(resourceList, expectedResources))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid resource list");
+
+		// verify GetProgramResourceIndex() matches the indices of the list
+
+		if (!verifyResourceIndexQuery(resourceList, expectedResources, program.getProgram()))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "GetProgramResourceIndex returned unexpected values");
+
+		// Verify MAX_NAME_LENGTH
+		if (!verifyMaxNameLength(resourceList, program.getProgram()))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "MAX_NAME_LENGTH invalid");
+	}
+
+	return STOP;
+}
+
+int InterfaceBlockDataSizeTestCase::getBlockMinDataSize (const glu::InterfaceBlock& block) const
+{
+	int dataSize = 0;
+
+	for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
+		dataSize += getVarTypeSize(block.variables[ndx].varType);
+
+	return dataSize;
+}
+
+static bool isDataTypeLayoutQualified (glu::DataType type)
+{
+	return glu::isDataTypeImage(type) || glu::isDataTypeAtomicCounter(type);
+}
+
+static void generateVariableCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const ProgramResourceQueryTestTarget& queryTarget, int expandLevel = 3, bool createTestGroup = true)
+{
+	static const struct
+	{
+		int				level;
+		glu::DataType	dataType;
+	} variableTypes[] =
+	{
+		{ 0,	glu::TYPE_FLOAT			},
+		{ 1,	glu::TYPE_INT			},
+		{ 1,	glu::TYPE_UINT			},
+		{ 1,	glu::TYPE_BOOL			},
+
+		{ 3,	glu::TYPE_FLOAT_VEC2	},
+		{ 1,	glu::TYPE_FLOAT_VEC3	},
+		{ 1,	glu::TYPE_FLOAT_VEC4	},
+
+		{ 3,	glu::TYPE_INT_VEC2		},
+		{ 2,	glu::TYPE_INT_VEC3		},
+		{ 3,	glu::TYPE_INT_VEC4		},
+
+		{ 3,	glu::TYPE_UINT_VEC2		},
+		{ 2,	glu::TYPE_UINT_VEC3		},
+		{ 3,	glu::TYPE_UINT_VEC4		},
+
+		{ 3,	glu::TYPE_BOOL_VEC2		},
+		{ 2,	glu::TYPE_BOOL_VEC3		},
+		{ 3,	glu::TYPE_BOOL_VEC4		},
+
+		{ 2,	glu::TYPE_FLOAT_MAT2	},
+		{ 3,	glu::TYPE_FLOAT_MAT2X3	},
+		{ 3,	glu::TYPE_FLOAT_MAT2X4	},
+		{ 2,	glu::TYPE_FLOAT_MAT3X2	},
+		{ 2,	glu::TYPE_FLOAT_MAT3	},
+		{ 3,	glu::TYPE_FLOAT_MAT3X4	},
+		{ 2,	glu::TYPE_FLOAT_MAT4X2	},
+		{ 3,	glu::TYPE_FLOAT_MAT4X3	},
+		{ 2,	glu::TYPE_FLOAT_MAT4	},
+	};
+
+	tcu::TestCaseGroup* group;
+
+	if (createTestGroup)
+	{
+		group = new tcu::TestCaseGroup(context.getTestContext(), "basic_type", "Basic variable");
+		targetGroup->addChild(group);
+	}
+	else
+		group = targetGroup;
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
+	{
+		if (variableTypes[ndx].level <= expandLevel)
+		{
+			const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, variableTypes[ndx].dataType));
+			group->addChild(new ResourceTestCase(context, variable, queryTarget));
+		}
+	}
+}
+
+static void generateOpaqueTypeCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const ProgramResourceQueryTestTarget& queryTarget, int expandLevel = 3, bool createTestGroup = true)
+{
+	static const struct
+	{
+		int				level;
+		glu::DataType	dataType;
+	} variableTypes[] =
+	{
+		{ 0,	glu::TYPE_SAMPLER_2D					},
+		{ 2,	glu::TYPE_SAMPLER_CUBE					},
+		{ 1,	glu::TYPE_SAMPLER_2D_ARRAY				},
+		{ 1,	glu::TYPE_SAMPLER_3D					},
+		{ 2,	glu::TYPE_SAMPLER_2D_SHADOW				},
+		{ 3,	glu::TYPE_SAMPLER_CUBE_SHADOW			},
+		{ 3,	glu::TYPE_SAMPLER_2D_ARRAY_SHADOW		},
+		{ 1,	glu::TYPE_INT_SAMPLER_2D				},
+		{ 3,	glu::TYPE_INT_SAMPLER_CUBE				},
+		{ 3,	glu::TYPE_INT_SAMPLER_2D_ARRAY			},
+		{ 3,	glu::TYPE_INT_SAMPLER_3D				},
+		{ 2,	glu::TYPE_UINT_SAMPLER_2D				},
+		{ 3,	glu::TYPE_UINT_SAMPLER_CUBE				},
+		{ 3,	glu::TYPE_UINT_SAMPLER_2D_ARRAY			},
+		{ 3,	glu::TYPE_UINT_SAMPLER_3D				},
+		{ 2,	glu::TYPE_SAMPLER_2D_MULTISAMPLE		},
+		{ 2,	glu::TYPE_INT_SAMPLER_2D_MULTISAMPLE	},
+		{ 3,	glu::TYPE_UINT_SAMPLER_2D_MULTISAMPLE	},
+		{ 1,	glu::TYPE_IMAGE_2D						},
+		{ 3,	glu::TYPE_IMAGE_CUBE					},
+		{ 3,	glu::TYPE_IMAGE_2D_ARRAY				},
+		{ 3,	glu::TYPE_IMAGE_3D						},
+		{ 3,	glu::TYPE_INT_IMAGE_2D					},
+		{ 3,	glu::TYPE_INT_IMAGE_CUBE				},
+		{ 1,	glu::TYPE_INT_IMAGE_2D_ARRAY			},
+		{ 3,	glu::TYPE_INT_IMAGE_3D					},
+		{ 2,	glu::TYPE_UINT_IMAGE_2D					},
+		{ 3,	glu::TYPE_UINT_IMAGE_CUBE				},
+		{ 3,	glu::TYPE_UINT_IMAGE_2D_ARRAY			},
+		{ 3,	glu::TYPE_UINT_IMAGE_3D					},
+		{ 1,	glu::TYPE_UINT_ATOMIC_COUNTER			},
+	};
+
+	bool isStructMember = false;
+
+	// Requirements
+	for (const ResourceDefinition::Node* node = parentStructure.get(); node; node = node->getEnclosingNode())
+	{
+		// Don't insert inside a interface block
+		if (node->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK)
+			return;
+
+		isStructMember |= (node->getType() == ResourceDefinition::Node::TYPE_STRUCT_MEMBER);
+	}
+
+	// Add cases
+	{
+		tcu::TestCaseGroup* group;
+
+		if (createTestGroup)
+		{
+			group = new tcu::TestCaseGroup(context.getTestContext(), "opaque_type", "Opaque types");
+			targetGroup->addChild(group);
+		}
+		else
+			group = targetGroup;
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
+		{
+			if (variableTypes[ndx].level > expandLevel)
+				continue;
+
+			// Layout qualifiers are not allowed on struct members
+			if (isDataTypeLayoutQualified(variableTypes[ndx].dataType) && isStructMember)
+				continue;
+
+			{
+				const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, variableTypes[ndx].dataType));
+				group->addChild(new ResourceTestCase(context, variable, queryTarget));
+			}
+		}
+	}
+}
+
+static void generateCompoundVariableCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const ProgramResourceQueryTestTarget& queryTarget, int expandLevel = 3);
+
+static void generateVariableArrayCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const ProgramResourceQueryTestTarget& queryTarget, int expandLevel = 3)
+{
+	if (expandLevel > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(parentStructure));
+		tcu::TestCaseGroup* const					blockGroup		= new tcu::TestCaseGroup(context.getTestContext(), "array", "Arrays");
+
+		targetGroup->addChild(blockGroup);
+
+		// Arrays of basic variables
+		generateVariableCases(context, arrayElement, blockGroup, queryTarget, expandLevel, expandLevel != 1);
+
+		// Arrays of opaque types
+		generateOpaqueTypeCases(context, arrayElement, blockGroup, queryTarget, expandLevel, expandLevel != 1);
+
+		// Arrays of arrays
+		generateVariableArrayCases(context, arrayElement, blockGroup, queryTarget, expandLevel-1);
+
+		// Arrays of structs
+		generateCompoundVariableCases(context, arrayElement, blockGroup, queryTarget, expandLevel-1);
+	}
+}
+
+static void generateCompoundVariableCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const ProgramResourceQueryTestTarget& queryTarget, int expandLevel)
+{
+	if (expandLevel > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(parentStructure));
+		tcu::TestCaseGroup* const					blockGroup		= new tcu::TestCaseGroup(context.getTestContext(), "struct", "Structs");
+
+		targetGroup->addChild(blockGroup);
+
+		// Struct containing basic variable
+		generateVariableCases(context, structMember, blockGroup, queryTarget, expandLevel, expandLevel != 1);
+
+		// Struct containing opaque types
+		generateOpaqueTypeCases(context, structMember, blockGroup, queryTarget, expandLevel, expandLevel != 1);
+
+		// Struct containing arrays
+		generateVariableArrayCases(context, structMember, blockGroup, queryTarget, expandLevel-1);
+
+		// Struct containing struct
+		generateCompoundVariableCases(context, structMember, blockGroup, queryTarget, expandLevel-1);
+	}
+}
+
+// Resource list cases
+
+enum BlockFlags
+{
+	BLOCKFLAG_DEFAULT	= 0x01,
+	BLOCKFLAG_NAMED		= 0x02,
+	BLOCKFLAG_UNNAMED	= 0x04,
+	BLOCKFLAG_ARRAY		= 0x08,
+
+	BLOCKFLAG_ALL		= 0x0F
+};
+
+static void generateUniformCaseBlocks (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, deUint32 blockFlags, void (*blockContentGenerator)(Context&, const ResourceDefinition::Node::SharedPtr&, tcu::TestCaseGroup* const))
+{
+	const ResourceDefinition::Node::SharedPtr defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr uniform		(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_UNIFORM));
+
+	// .default_block
+	if (blockFlags & BLOCKFLAG_DEFAULT)
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "default_block", "Default block");
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, uniform, blockGroup);
+	}
+
+	// .named_block
+	if (blockFlags & BLOCKFLAG_NAMED)
+	{
+		const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(uniform, true));
+
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "named_block", "Named uniform block");
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, block, blockGroup);
+	}
+
+	// .unnamed_block
+	if (blockFlags & BLOCKFLAG_UNNAMED)
+	{
+		const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(uniform, false));
+
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "unnamed_block", "Unnamed uniform block");
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, block, blockGroup);
+	}
+
+	// .block_array
+	if (blockFlags & BLOCKFLAG_ARRAY)
+	{
+		const ResourceDefinition::Node::SharedPtr arrayElement	(new ResourceDefinition::ArrayElement(uniform));
+		const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(arrayElement, true));
+
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "block_array", "Uniform block array");
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, block, blockGroup);
+	}
+}
+
+static void generateBufferBackedResourceListBlockContentCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, ProgramInterface interface, int depth)
+{
+	// variable
+	{
+		const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new ResourceListTestCase(context, variable, interface));
+	}
+
+	// struct
+	if (depth > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
+		generateBufferBackedResourceListBlockContentCases(context, structMember, targetGroup, interface, depth - 1);
+	}
+
+	// array
+	if (depth > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure));
+		generateBufferBackedResourceListBlockContentCases(context, arrayElement, targetGroup, interface, depth - 1);
+	}
+}
+
+static void generateBufferBackedVariableAggregateTypeCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, ProgramInterface interface, ProgramResourcePropFlags targetProp, glu::DataType dataType, const std::string& nameSuffix, int depth)
+{
+	// variable
+	{
+		const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, dataType));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(interface, targetProp), ("var" + nameSuffix).c_str()));
+	}
+
+	// struct
+	if (depth > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
+		generateBufferBackedVariableAggregateTypeCases(context, structMember, targetGroup, interface, targetProp, dataType, "_struct" + nameSuffix, depth - 1);
+	}
+
+	// array
+	if (depth > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure));
+		generateBufferBackedVariableAggregateTypeCases(context, arrayElement, targetGroup, interface, targetProp, dataType, "_array" + nameSuffix, depth - 1);
+	}
+}
+
+static void generateUniformResourceListBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup)
+{
+	generateBufferBackedResourceListBlockContentCases(context, parentStructure, targetGroup, PROGRAMINTERFACE_UNIFORM, 4);
+}
+
+static void generateUniformBlockArraySizeContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup)
+{
+	const ProgramResourceQueryTestTarget	queryTarget			(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_ARRAY_SIZE);
+	const bool								isInterfaceBlock	= (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
+	const bool								namedNonArrayBlock	= isInterfaceBlock																					&&
+																  static_cast<const ResourceDefinition::InterfaceBlock*>(parentStructure.get())->m_named			&&
+																  parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;
+
+	if (!isInterfaceBlock || namedNonArrayBlock)
+	{
+		// .types
+		{
+			tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
+			targetGroup->addChild(blockGroup);
+
+			generateVariableCases(context, parentStructure, blockGroup, queryTarget, 2, false);
+			generateOpaqueTypeCases(context, parentStructure, blockGroup, queryTarget, 2, false);
+		}
+
+		// aggregates
+		{
+			tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "Aggregate types");
+			targetGroup->addChild(blockGroup);
+
+			generateBufferBackedVariableAggregateTypeCases(context, parentStructure, blockGroup, queryTarget.interface, PROGRAMRESOURCEPROP_ARRAY_SIZE, glu::TYPE_FLOAT, "", 3);
+		}
+	}
+	else
+	{
+		// aggregates
+		generateBufferBackedVariableAggregateTypeCases(context, parentStructure, targetGroup, queryTarget.interface, PROGRAMRESOURCEPROP_ARRAY_SIZE, glu::TYPE_FLOAT, "", 2);
+	}
+}
+
+static void generateBufferBackedArrayStrideTypeAggregateSubCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const std::string& namePrefix, ProgramInterface interface, glu::DataType type, int expandLevel)
+{
+	// case
+	{
+		const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, type));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(interface, PROGRAMRESOURCEPROP_ARRAY_STRIDE), namePrefix.c_str()));
+	}
+
+	if (expandLevel > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(parentStructure));
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(parentStructure));
+
+		// _struct
+		generateBufferBackedArrayStrideTypeAggregateSubCases(context, structMember, targetGroup, namePrefix + "_struct", interface, type, expandLevel - 1);
+
+		// _array
+		generateBufferBackedArrayStrideTypeAggregateSubCases(context, arrayElement, targetGroup, namePrefix + "_array", interface, type, expandLevel - 1);
+	}
+}
+
+static void generateBufferBackedArrayStrideTypeAggregateCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, ProgramInterface interface, glu::DataType type, int expandLevel, bool includeBaseCase)
+{
+	const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(parentStructure));
+	const std::string							namePrefix		= glu::getDataTypeName(type);
+
+	if (expandLevel == 0 || includeBaseCase)
+	{
+		const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, type));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(interface, PROGRAMRESOURCEPROP_ARRAY_STRIDE), namePrefix.c_str()));
+	}
+	if (expandLevel >= 1)
+	{
+		// _struct
+		if (!glu::isDataTypeAtomicCounter(type))
+			generateBufferBackedArrayStrideTypeAggregateSubCases(context, structMember, targetGroup, namePrefix + "_struct", interface, type, expandLevel - 1);
+
+		// _array
+		generateBufferBackedArrayStrideTypeAggregateSubCases(context, arrayElement, targetGroup, namePrefix + "_array", interface, type, expandLevel - 1);
+	}
+}
+
+static void generateUniformBlockArrayStrideContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup)
+{
+	const ProgramResourceQueryTestTarget	queryTarget			(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_ARRAY_STRIDE);
+	const bool								isInterfaceBlock	= (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
+	const bool								namedNonArrayBlock	= isInterfaceBlock																					&&
+																  static_cast<const ResourceDefinition::InterfaceBlock*>(parentStructure.get())->m_named			&&
+																  parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;
+
+	if (!isInterfaceBlock || namedNonArrayBlock)
+	{
+		// .types
+		{
+			tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
+			targetGroup->addChild(blockGroup);
+
+			generateVariableCases(context, parentStructure, blockGroup, queryTarget, 2, false);
+			generateOpaqueTypeCases(context, parentStructure, blockGroup, queryTarget, 2, false);
+		}
+
+		// .aggregates
+		{
+			tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "Aggregate types");
+			targetGroup->addChild(blockGroup);
+
+			// .sampler_2d_*
+			if (!isInterfaceBlock)
+				generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup, queryTarget.interface, glu::TYPE_SAMPLER_2D, 1, false);
+
+			// .atomic_counter_*
+			if (!isInterfaceBlock)
+			{
+				const ResourceDefinition::Node::SharedPtr layout(new ResourceDefinition::LayoutQualifier(parentStructure, glu::Layout(-1, 0)));
+				generateBufferBackedArrayStrideTypeAggregateCases(context, layout, blockGroup, queryTarget.interface, glu::TYPE_UINT_ATOMIC_COUNTER, 1, false);
+			}
+
+			// .float_*
+			generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup, queryTarget.interface, glu::TYPE_FLOAT, 2, false);
+
+			// .bool_*
+			generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup, queryTarget.interface, glu::TYPE_BOOL, 1, false);
+
+			// .bvec3_*
+			generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup, queryTarget.interface, glu::TYPE_BOOL_VEC3, 2, false);
+
+			// .vec3_*
+			generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup, queryTarget.interface, glu::TYPE_FLOAT_VEC3, 2, false);
+
+			// .ivec2_*
+			generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup, queryTarget.interface, glu::TYPE_INT_VEC3, 2, false);
+		}
+	}
+	else
+	{
+		generateVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
+		generateVariableArrayCases(context, parentStructure, targetGroup, queryTarget, 1);
+		generateCompoundVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
+	}
+}
+
+static void generateUniformBlockLocationContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup)
+{
+	const ProgramResourceQueryTestTarget	queryTarget			(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_LOCATION);
+	const bool								isInterfaceBlock	= (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
+
+	if (!isInterfaceBlock)
+	{
+		generateVariableCases(context, parentStructure, targetGroup, queryTarget, 3);
+		generateOpaqueTypeCases(context, parentStructure, targetGroup, queryTarget, 3);
+		generateVariableArrayCases(context, parentStructure, targetGroup, queryTarget, 2);
+		generateCompoundVariableCases(context, parentStructure, targetGroup, queryTarget, 2);
+	}
+	else
+		generateVariableCases(context, parentStructure, targetGroup, queryTarget, 1, false);
+}
+
+static void generateUniformBlockBlockIndexContents (Context& context, tcu::TestCaseGroup* const targetGroup)
+{
+	const ResourceDefinition::Node::SharedPtr	program			(new ResourceDefinition::Program());
+	const ResourceDefinition::Node::SharedPtr	shader			(new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES));
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(shader));
+	const ResourceDefinition::Node::SharedPtr	uniform			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_UNIFORM));
+	const ResourceDefinition::Node::SharedPtr	binding			(new ResourceDefinition::LayoutQualifier(uniform, glu::Layout(-1, 0)));
+
+	// .default_block
+	{
+		const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(uniform, glu::TYPE_FLOAT_VEC4));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_BLOCK_INDEX), "default_block"));
+	}
+
+	// .named_block
+	{
+		const ResourceDefinition::Node::SharedPtr	buffer		(new ResourceDefinition::InterfaceBlock(binding, true));
+		const ResourceDefinition::Node::SharedPtr	variable	(new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_BLOCK_INDEX), "named_block"));
+	}
+
+	// .unnamed_block
+	{
+		const ResourceDefinition::Node::SharedPtr	buffer		(new ResourceDefinition::InterfaceBlock(binding, false));
+		const ResourceDefinition::Node::SharedPtr	variable	(new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_BLOCK_INDEX), "unnamed_block"));
+	}
+
+	// .block_array
+	{
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(binding));
+		const ResourceDefinition::Node::SharedPtr	buffer			(new ResourceDefinition::InterfaceBlock(arrayElement, true));
+		const ResourceDefinition::Node::SharedPtr	variable		(new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_BLOCK_INDEX), "block_array"));
+	}
+}
+
+static void generateUniformBlockAtomicCounterBufferIndexContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup)
+{
+	const ProgramResourceQueryTestTarget	queryTarget			(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_ATOMIC_COUNTER_BUFFER_INDEX);
+	const bool								isInterfaceBlock	= (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
+
+	if (!isInterfaceBlock)
+	{
+		generateVariableCases(context, parentStructure, targetGroup, queryTarget, 3);
+		generateOpaqueTypeCases(context, parentStructure, targetGroup, queryTarget, 3);
+
+		// .array
+		{
+			const ResourceDefinition::Node::SharedPtr	arrayElement		(new ResourceDefinition::ArrayElement(parentStructure));
+			const ResourceDefinition::Node::SharedPtr	arrayArrayElement	(new ResourceDefinition::ArrayElement(arrayElement));
+			const ResourceDefinition::Node::SharedPtr	variable			(new ResourceDefinition::Variable(arrayElement, glu::TYPE_UINT_ATOMIC_COUNTER));
+			const ResourceDefinition::Node::SharedPtr	elementvariable		(new ResourceDefinition::Variable(arrayArrayElement, glu::TYPE_UINT_ATOMIC_COUNTER));
+			tcu::TestCaseGroup* const					blockGroup			= new tcu::TestCaseGroup(context.getTestContext(), "array", "Arrays");
+
+			targetGroup->addChild(blockGroup);
+
+			blockGroup->addChild(new ResourceTestCase(context, variable, queryTarget, "var_array"));
+			blockGroup->addChild(new ResourceTestCase(context, elementvariable, queryTarget, "var_array_array"));
+		}
+	}
+	else
+		generateVariableCases(context, parentStructure, targetGroup, queryTarget, 1, false);
+}
+
+static void generateUniformBlockNameLengthContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup)
+{
+	const bool	isInterfaceBlock	= (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
+	const bool	namedNonArrayBlock	= isInterfaceBlock																					&&
+									  static_cast<const ResourceDefinition::InterfaceBlock*>(parentStructure.get())->m_named			&&
+									  parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;
+
+	if (!isInterfaceBlock || namedNonArrayBlock)
+		generateBufferBackedVariableAggregateTypeCases(context, parentStructure, targetGroup, PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_NAME_LENGTH, glu::TYPE_FLOAT, "", 2);
+	else
+		generateBufferBackedVariableAggregateTypeCases(context, parentStructure, targetGroup, PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_NAME_LENGTH, glu::TYPE_FLOAT, "", 1);
+}
+
+static void generateUniformBlockTypeContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup)
+{
+	const ProgramResourceQueryTestTarget	queryTarget			(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_TYPE);
+	const bool								isInterfaceBlock	= (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
+	const bool								namedNonArrayBlock	= isInterfaceBlock																					&&
+																  static_cast<const ResourceDefinition::InterfaceBlock*>(parentStructure.get())->m_named			&&
+																  parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;
+
+	if (!isInterfaceBlock || namedNonArrayBlock)
+	{
+		// .types
+		{
+			tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
+			targetGroup->addChild(blockGroup);
+
+			generateVariableCases(context, parentStructure, blockGroup, queryTarget, 3, false);
+			generateOpaqueTypeCases(context, parentStructure, blockGroup, queryTarget, 3, false);
+		}
+
+		generateVariableArrayCases(context, parentStructure, targetGroup, queryTarget, 1);
+		generateCompoundVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
+
+	}
+	else
+	{
+		generateVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
+		generateVariableArrayCases(context, parentStructure, targetGroup, queryTarget, 1);
+		generateCompoundVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
+	}
+}
+
+static void generateUniformBlockOffsetContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup)
+{
+	const ProgramResourceQueryTestTarget	queryTarget			(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_OFFSET);
+	const bool								isInterfaceBlock	= (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
+	const bool								namedNonArrayBlock	= isInterfaceBlock																					&&
+																  static_cast<const ResourceDefinition::InterfaceBlock*>(parentStructure.get())->m_named			&&
+																  parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;
+
+	if (!isInterfaceBlock)
+	{
+		// .types
+		{
+			tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
+			targetGroup->addChild(blockGroup);
+
+			generateVariableCases(context, parentStructure, blockGroup, queryTarget, 3, false);
+			generateOpaqueTypeCases(context, parentStructure, blockGroup, queryTarget, 3, false);
+		}
+
+		// .aggregates
+		{
+			tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "Aggregate types");
+			targetGroup->addChild(blockGroup);
+
+			// .atomic_uint_struct
+			// .atomic_uint_array
+			{
+				const ResourceDefinition::Node::SharedPtr offset			(new ResourceDefinition::LayoutQualifier(parentStructure, glu::Layout(-1, -1, 4)));
+				const ResourceDefinition::Node::SharedPtr arrayElement		(new ResourceDefinition::ArrayElement(offset));
+				const ResourceDefinition::Node::SharedPtr elementVariable	(new ResourceDefinition::Variable(arrayElement, glu::TYPE_UINT_ATOMIC_COUNTER));
+
+				blockGroup->addChild(new ResourceTestCase(context, elementVariable, queryTarget, "atomic_uint_array"));
+			}
+
+			// .float_array
+			// .float_struct
+			{
+				const ResourceDefinition::Node::SharedPtr structMember		(new ResourceDefinition::StructMember(parentStructure));
+				const ResourceDefinition::Node::SharedPtr arrayElement		(new ResourceDefinition::ArrayElement(parentStructure));
+				const ResourceDefinition::Node::SharedPtr memberVariable	(new ResourceDefinition::Variable(structMember, glu::TYPE_FLOAT));
+				const ResourceDefinition::Node::SharedPtr elementVariable	(new ResourceDefinition::Variable(arrayElement, glu::TYPE_FLOAT));
+
+				blockGroup->addChild(new ResourceTestCase(context, memberVariable, queryTarget, "float_struct"));
+				blockGroup->addChild(new ResourceTestCase(context, elementVariable, queryTarget, "float_array"));
+			}
+		}
+	}
+	else if (namedNonArrayBlock)
+	{
+		// .types
+		{
+			tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
+			targetGroup->addChild(blockGroup);
+
+			generateVariableCases(context, parentStructure, blockGroup, queryTarget, 3, false);
+			generateOpaqueTypeCases(context, parentStructure, blockGroup, queryTarget, 3, false);
+		}
+
+		// .aggregates
+		{
+			tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "Aggregate types");
+			targetGroup->addChild(blockGroup);
+
+			// .float_array
+			// .float_struct
+			{
+				const ResourceDefinition::Node::SharedPtr structMember		(new ResourceDefinition::StructMember(parentStructure));
+				const ResourceDefinition::Node::SharedPtr arrayElement		(new ResourceDefinition::StructMember(parentStructure));
+				const ResourceDefinition::Node::SharedPtr memberVariable	(new ResourceDefinition::Variable(structMember, glu::TYPE_FLOAT));
+				const ResourceDefinition::Node::SharedPtr elementVariable	(new ResourceDefinition::Variable(arrayElement, glu::TYPE_FLOAT));
+
+				blockGroup->addChild(new ResourceTestCase(context, memberVariable, queryTarget, "float_struct"));
+				blockGroup->addChild(new ResourceTestCase(context, elementVariable, queryTarget, "float_array"));
+			}
+		}
+	}
+	else
+	{
+		generateVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
+		generateVariableArrayCases(context, parentStructure, targetGroup, queryTarget, 1);
+		generateCompoundVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
+	}
+}
+
+static void generateMatrixVariableCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const ProgramResourceQueryTestTarget& queryTarget, bool createTestGroup = true, int expandLevel = 2)
+{
+	static const struct
+	{
+		int				priority;
+		glu::DataType	type;
+	} variableTypes[] =
+	{
+		{ 0,	glu::TYPE_FLOAT_MAT2	},
+		{ 1,	glu::TYPE_FLOAT_MAT2X3	},
+		{ 2,	glu::TYPE_FLOAT_MAT2X4	},
+		{ 2,	glu::TYPE_FLOAT_MAT3X2	},
+		{ 1,	glu::TYPE_FLOAT_MAT3	},
+		{ 0,	glu::TYPE_FLOAT_MAT3X4	},
+		{ 2,	glu::TYPE_FLOAT_MAT4X2	},
+		{ 1,	glu::TYPE_FLOAT_MAT4X3	},
+		{ 0,	glu::TYPE_FLOAT_MAT4	},
+	};
+
+	tcu::TestCaseGroup* group;
+
+	if (createTestGroup)
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "matrix", "Basic matrix type");
+		targetGroup->addChild(blockGroup);
+		group = blockGroup;
+	}
+	else
+		group = targetGroup;
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
+	{
+		if (variableTypes[ndx].priority < expandLevel)
+		{
+			const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, variableTypes[ndx].type));
+			group->addChild(new ResourceTestCase(context, variable, queryTarget));
+		}
+	}
+}
+
+static void generateMatrixStructCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const ProgramResourceQueryTestTarget& queryTarget, int expandLevel);
+
+static void generateMatrixArrayCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const ProgramResourceQueryTestTarget& queryTarget, int expandLevel)
+{
+	if (expandLevel > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(parentStructure));
+		tcu::TestCaseGroup* const					blockGroup		= new tcu::TestCaseGroup(context.getTestContext(), "array", "Arrays");
+
+		targetGroup->addChild(blockGroup);
+
+		// Arrays of basic variables
+		generateMatrixVariableCases(context, arrayElement, blockGroup, queryTarget, expandLevel != 1, expandLevel);
+
+		// Arrays of arrays
+		generateMatrixArrayCases(context, arrayElement, blockGroup, queryTarget, expandLevel-1);
+
+		// Arrays of structs
+		generateMatrixStructCases(context, arrayElement, blockGroup, queryTarget, expandLevel-1);
+	}
+}
+
+static void generateMatrixStructCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const ProgramResourceQueryTestTarget& queryTarget, int expandLevel)
+{
+	if (expandLevel > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(parentStructure));
+		tcu::TestCaseGroup* const					blockGroup		= new tcu::TestCaseGroup(context.getTestContext(), "struct", "Structs");
+
+		targetGroup->addChild(blockGroup);
+
+		// Struct containing basic variable
+		generateMatrixVariableCases(context, structMember, blockGroup, queryTarget, expandLevel != 1, expandLevel);
+
+		// Struct containing arrays
+		generateMatrixArrayCases(context, structMember, blockGroup, queryTarget, expandLevel-1);
+
+		// Struct containing struct
+		generateMatrixStructCases(context, structMember, blockGroup, queryTarget, expandLevel-1);
+	}
+}
+
+static void generateUniformMatrixOrderCaseBlockContentCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, bool extendedBasicTypeCases, bool opaqueCases)
+{
+	static const struct
+	{
+		const char*			name;
+		glu::MatrixOrder	order;
+	} qualifiers[] =
+	{
+		{ "no_qualifier",	glu::MATRIXORDER_LAST			},
+		{ "row_major",		glu::MATRIXORDER_ROW_MAJOR		},
+		{ "column_major",	glu::MATRIXORDER_COLUMN_MAJOR	},
+	};
+
+	const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR);
+
+	for (int qualifierNdx = 0; qualifierNdx < DE_LENGTH_OF_ARRAY(qualifiers); ++qualifierNdx)
+	{
+		// Add layout qualifiers only for block members
+		if (qualifiers[qualifierNdx].order == glu::MATRIXORDER_LAST || parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK)
+		{
+			ResourceDefinition::Node::SharedPtr	subStructure	= parentStructure;
+			tcu::TestCaseGroup* const			qualifierGroup	= new tcu::TestCaseGroup(context.getTestContext(), qualifiers[qualifierNdx].name, "");
+
+			targetGroup->addChild(qualifierGroup);
+
+			if (qualifiers[qualifierNdx].order != glu::MATRIXORDER_LAST)
+			{
+				glu::Layout layout;
+				layout.matrixOrder = qualifiers[qualifierNdx].order;
+				subStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(subStructure, layout));
+			}
+
+			if (extendedBasicTypeCases && qualifiers[qualifierNdx].order == glu::MATRIXORDER_LAST)
+			{
+				// .types
+				{
+					tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "");
+					qualifierGroup->addChild(blockGroup);
+
+					generateVariableCases(context, subStructure, blockGroup, queryTarget, 1, false);
+					generateMatrixVariableCases(context, subStructure, blockGroup, queryTarget, false);
+					if (opaqueCases)
+						generateOpaqueTypeCases(context, subStructure, blockGroup, queryTarget, 2, false);
+				}
+
+				// .aggregates
+				{
+					tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "");
+					qualifierGroup->addChild(blockGroup);
+
+					generateBufferBackedVariableAggregateTypeCases(context, subStructure, blockGroup, queryTarget.interface, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR, glu::TYPE_FLOAT_MAT3X2, "", 1);
+				}
+			}
+			else
+			{
+				generateBufferBackedVariableAggregateTypeCases(context, subStructure, qualifierGroup, queryTarget.interface, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR, glu::TYPE_FLOAT_MAT3X2, "", 1);
+			}
+		}
+	}
+}
+
+static void generateUniformMatrixStrideCaseBlockContentCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, bool extendedBasicTypeCases, bool opaqueCases)
+{
+	static const struct
+	{
+		const char*			name;
+		glu::MatrixOrder	order;
+	} qualifiers[] =
+	{
+		{ "no_qualifier",	glu::MATRIXORDER_LAST			},
+		{ "row_major",		glu::MATRIXORDER_ROW_MAJOR		},
+		{ "column_major",	glu::MATRIXORDER_COLUMN_MAJOR	},
+	};
+
+	const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_MATRIX_STRIDE);
+
+	for (int qualifierNdx = 0; qualifierNdx < DE_LENGTH_OF_ARRAY(qualifiers); ++qualifierNdx)
+	{
+		// Add layout qualifiers only for block members
+		if (qualifiers[qualifierNdx].order == glu::MATRIXORDER_LAST || parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK)
+		{
+			ResourceDefinition::Node::SharedPtr	subStructure	= parentStructure;
+			tcu::TestCaseGroup* const			qualifierGroup	= new tcu::TestCaseGroup(context.getTestContext(), qualifiers[qualifierNdx].name, "");
+
+			targetGroup->addChild(qualifierGroup);
+
+			if (qualifiers[qualifierNdx].order != glu::MATRIXORDER_LAST)
+			{
+				glu::Layout layout;
+				layout.matrixOrder = qualifiers[qualifierNdx].order;
+				subStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(subStructure, layout));
+			}
+
+			if (extendedBasicTypeCases)
+			{
+				// .types
+				// .matrix
+				if (qualifiers[qualifierNdx].order == glu::MATRIXORDER_LAST)
+				{
+					tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "");
+					qualifierGroup->addChild(blockGroup);
+
+					generateVariableCases(context, subStructure, blockGroup, queryTarget, 1, false);
+					generateMatrixVariableCases(context, subStructure, blockGroup, queryTarget, false);
+					if (opaqueCases)
+						generateOpaqueTypeCases(context, subStructure, blockGroup, queryTarget, 2, false);
+				}
+				else
+					generateMatrixVariableCases(context, subStructure, qualifierGroup, queryTarget);
+
+				// .aggregates
+				{
+					tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "");
+					qualifierGroup->addChild(blockGroup);
+
+					generateBufferBackedVariableAggregateTypeCases(context, subStructure, blockGroup, queryTarget.interface, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR, glu::TYPE_FLOAT_MAT3X2, "", 1);
+				}
+			}
+			else
+				generateBufferBackedVariableAggregateTypeCases(context, subStructure, qualifierGroup, queryTarget.interface, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR, glu::TYPE_FLOAT_MAT3X2, "", 1);
+		}
+	}
+}
+
+static void generateUniformMatrixCaseBlocks (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, void (*blockContentGenerator)(Context&, const ResourceDefinition::Node::SharedPtr&, tcu::TestCaseGroup* const, bool, bool))
+{
+	static const struct
+	{
+		const char*			name;
+		const char*			description;
+		bool				block;
+		bool				namedBlock;
+		bool				extendedBasicTypeCases;
+		glu::MatrixOrder	order;
+	} children[] =
+	{
+		{ "default_block",				"Default block",			false,	true,	true,	glu::MATRIXORDER_LAST			},
+		{ "named_block",				"Named uniform block",		true,	true,	true,	glu::MATRIXORDER_LAST			},
+		{ "named_block_row_major",		"Named uniform block",		true,	true,	false,	glu::MATRIXORDER_ROW_MAJOR		},
+		{ "named_block_col_major",		"Named uniform block",		true,	true,	false,	glu::MATRIXORDER_COLUMN_MAJOR	},
+		{ "unnamed_block",				"Unnamed uniform block",	true,	false,	false,	glu::MATRIXORDER_LAST			},
+		{ "unnamed_block_row_major",	"Unnamed uniform block",	true,	false,	false,	glu::MATRIXORDER_ROW_MAJOR		},
+		{ "unnamed_block_col_major",	"Unnamed uniform block",	true,	false,	false,	glu::MATRIXORDER_COLUMN_MAJOR	},
+	};
+
+	const ResourceDefinition::Node::SharedPtr defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr uniform		(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_UNIFORM));
+
+	for (int childNdx = 0; childNdx < (int)DE_LENGTH_OF_ARRAY(children); ++childNdx)
+	{
+		ResourceDefinition::Node::SharedPtr	subStructure	= uniform;
+		tcu::TestCaseGroup* const			blockGroup		= new tcu::TestCaseGroup(context.getTestContext(), children[childNdx].name, children[childNdx].description);
+		const bool							addOpaqueCases	= children[childNdx].extendedBasicTypeCases && !children[childNdx].block;
+
+		targetGroup->addChild(blockGroup);
+
+		if (children[childNdx].order != glu::MATRIXORDER_LAST)
+		{
+			glu::Layout layout;
+			layout.matrixOrder = children[childNdx].order;
+			subStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(subStructure, layout));
+		}
+
+		if (children[childNdx].block)
+			subStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::InterfaceBlock(subStructure, children[childNdx].namedBlock));
+
+		blockContentGenerator(context, subStructure, blockGroup, children[childNdx].extendedBasicTypeCases, addOpaqueCases);
+	}
+}
+
+static void generateBufferReferencedByShaderInterfaceBlockCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, const ProgramResourceQueryTestTarget& queryTarget, bool extendedCases)
+{
+	const bool isDefaultBlock = (parentStructure->getType() != ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
+
+	// .float
+	// .float_array
+	// .float_struct
+	{
+		const ResourceDefinition::Node::SharedPtr	variable		(new ResourceDefinition::Variable(parentStructure, glu::TYPE_FLOAT));
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(parentStructure));
+		const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(parentStructure));
+		const ResourceDefinition::Node::SharedPtr	variableArray	(new ResourceDefinition::Variable(arrayElement, glu::TYPE_FLOAT));
+		const ResourceDefinition::Node::SharedPtr	variableStruct	(new ResourceDefinition::Variable(structMember, glu::TYPE_FLOAT));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, queryTarget, "float"));
+		targetGroup->addChild(new ResourceTestCase(context, variableArray, queryTarget, "float_array"));
+		targetGroup->addChild(new ResourceTestCase(context, variableStruct, queryTarget, "float_struct"));
+	}
+
+	// .sampler
+	// .sampler_array
+	// .sampler_struct
+	if (isDefaultBlock)
+	{
+		const ResourceDefinition::Node::SharedPtr	layout			(new ResourceDefinition::LayoutQualifier(parentStructure, glu::Layout(-1, 0)));
+		const ResourceDefinition::Node::SharedPtr	variable		(new ResourceDefinition::Variable(layout, glu::TYPE_SAMPLER_2D));
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(layout));
+		const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(parentStructure));
+		const ResourceDefinition::Node::SharedPtr	variableArray	(new ResourceDefinition::Variable(arrayElement, glu::TYPE_SAMPLER_2D));
+		const ResourceDefinition::Node::SharedPtr	variableStruct	(new ResourceDefinition::Variable(structMember, glu::TYPE_SAMPLER_2D));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, queryTarget, "sampler"));
+		targetGroup->addChild(new ResourceTestCase(context, variableArray, queryTarget, "sampler_array"));
+		targetGroup->addChild(new ResourceTestCase(context, variableStruct, queryTarget, "sampler_struct"));
+	}
+
+	// .atomic_uint
+	// .atomic_uint_array
+	if (isDefaultBlock)
+	{
+		const ResourceDefinition::Node::SharedPtr	layout			(new ResourceDefinition::LayoutQualifier(parentStructure, glu::Layout(-1, 0)));
+		const ResourceDefinition::Node::SharedPtr	variable		(new ResourceDefinition::Variable(layout, glu::TYPE_UINT_ATOMIC_COUNTER));
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(layout));
+		const ResourceDefinition::Node::SharedPtr	variableArray	(new ResourceDefinition::Variable(arrayElement, glu::TYPE_UINT_ATOMIC_COUNTER));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, queryTarget, "atomic_uint"));
+		targetGroup->addChild(new ResourceTestCase(context, variableArray, queryTarget, "atomic_uint_array"));
+	}
+
+	if (extendedCases)
+	{
+		// .float_array_struct
+		{
+			const ResourceDefinition::Node::SharedPtr	structMember		(new ResourceDefinition::StructMember(parentStructure));
+			const ResourceDefinition::Node::SharedPtr	arrayElement		(new ResourceDefinition::ArrayElement(structMember));
+			const ResourceDefinition::Node::SharedPtr	variableArrayStruct	(new ResourceDefinition::Variable(arrayElement, glu::TYPE_FLOAT));
+
+			targetGroup->addChild(new ResourceTestCase(context, variableArrayStruct, queryTarget, "float_array_struct"));
+		}
+
+		// .float_struct_array
+		{
+			const ResourceDefinition::Node::SharedPtr	arrayElement		(new ResourceDefinition::ArrayElement(parentStructure));
+			const ResourceDefinition::Node::SharedPtr	arrayStructMember	(new ResourceDefinition::StructMember(arrayElement));
+			const ResourceDefinition::Node::SharedPtr	variableArrayStruct	(new ResourceDefinition::Variable(arrayStructMember, glu::TYPE_FLOAT));
+
+			targetGroup->addChild(new ResourceTestCase(context, variableArrayStruct, queryTarget, "float_struct_array"));
+		}
+
+		// .float_array_array
+		{
+			const ResourceDefinition::Node::SharedPtr	arrayElement		(new ResourceDefinition::ArrayElement(parentStructure));
+			const ResourceDefinition::Node::SharedPtr	subArrayElement		(new ResourceDefinition::ArrayElement(arrayElement));
+			const ResourceDefinition::Node::SharedPtr	variableArrayStruct	(new ResourceDefinition::Variable(subArrayElement, glu::TYPE_FLOAT));
+
+			targetGroup->addChild(new ResourceTestCase(context, variableArrayStruct, queryTarget, "float_array_array"));
+		}
+
+		// .float_struct_struct
+		{
+			const ResourceDefinition::Node::SharedPtr	structMember		(new ResourceDefinition::StructMember(parentStructure));
+			const ResourceDefinition::Node::SharedPtr	subStructMember		(new ResourceDefinition::StructMember(structMember));
+			const ResourceDefinition::Node::SharedPtr	variableArrayStruct	(new ResourceDefinition::Variable(subStructMember, glu::TYPE_FLOAT));
+
+			targetGroup->addChild(new ResourceTestCase(context, variableArrayStruct, queryTarget, "float_struct_struct"));
+		}
+
+		if (queryTarget.interface == PROGRAMINTERFACE_BUFFER_VARIABLE)
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
+
+			// .float_unsized_array
+			{
+				const ResourceDefinition::Node::SharedPtr	variableArray	(new ResourceDefinition::Variable(arrayElement, glu::TYPE_FLOAT));
+
+				targetGroup->addChild(new ResourceTestCase(context, variableArray, queryTarget, "float_unsized_array"));
+			}
+
+			// .float_unsized_struct_array
+			{
+				const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(arrayElement));
+				const ResourceDefinition::Node::SharedPtr	variableArray	(new ResourceDefinition::Variable(structMember, glu::TYPE_FLOAT));
+
+				targetGroup->addChild(new ResourceTestCase(context, variableArray, queryTarget, "float_unsized_struct_array"));
+			}
+		}
+	}
+}
+
+static void generateUniformReferencedByShaderSingleBlockContentCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, int expandLevel)
+{
+	DE_UNREF(expandLevel);
+
+	const ResourceDefinition::Node::SharedPtr	defaultBlock		(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	uniform				(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_UNIFORM));
+	const ProgramResourceQueryTestTarget		queryTarget			(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER);
+	const bool									singleShaderCase	= parentStructure->getType() == ResourceDefinition::Node::TYPE_SHADER;
+
+	// .default_block
+	{
+		TestCaseGroup* const blockGroup = new TestCaseGroup(context, "default_block", "");
+		targetGroup->addChild(blockGroup);
+
+		generateBufferReferencedByShaderInterfaceBlockCases(context, uniform, blockGroup, queryTarget, singleShaderCase);
+	}
+
+	// .named_block
+	{
+		const ResourceDefinition::Node::SharedPtr	block		(new ResourceDefinition::InterfaceBlock(uniform, true));
+		TestCaseGroup* const						blockGroup	= new TestCaseGroup(context, "uniform_block", "");
+
+		targetGroup->addChild(blockGroup);
+
+		generateBufferReferencedByShaderInterfaceBlockCases(context, block, blockGroup, queryTarget, singleShaderCase);
+	}
+
+	// .unnamed_block
+	{
+		const ResourceDefinition::Node::SharedPtr	block		(new ResourceDefinition::InterfaceBlock(uniform, false));
+		TestCaseGroup* const						blockGroup	= new TestCaseGroup(context, "unnamed_block", "");
+
+		targetGroup->addChild(blockGroup);
+
+		generateBufferReferencedByShaderInterfaceBlockCases(context, block, blockGroup, queryTarget, false);
+	}
+
+	// .block_array
+	{
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(uniform));
+		const ResourceDefinition::Node::SharedPtr	block			(new ResourceDefinition::InterfaceBlock(arrayElement, true));
+		TestCaseGroup* const						blockGroup		= new TestCaseGroup(context, "block_array", "");
+
+		targetGroup->addChild(blockGroup);
+
+		generateBufferReferencedByShaderInterfaceBlockCases(context, block, blockGroup, queryTarget, false);
+	}
+}
+
+static void generateReferencedByShaderCaseBlocks (Context& context, tcu::TestCaseGroup* const targetGroup, void (*generateBlockContent)(Context&, const ResourceDefinition::Node::SharedPtr&, tcu::TestCaseGroup*, int expandLevel))
+{
+	static const struct
+	{
+		const char*		name;
+		glu::ShaderType	stage;
+		int				expandLevel;
+	} singleStageCases[] =
+	{
+		{ "compute",			glu::SHADERTYPE_COMPUTE,	3	},
+		{ "separable_vertex",	glu::SHADERTYPE_VERTEX,		2	},
+		{ "separable_fragment",	glu::SHADERTYPE_FRAGMENT,	2	},
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		stagesPresent;
+		deUint32		stagesReferencing;
+		int				expandLevel;
+	} multiStageCases[] =
+	{
+		{ "vertex_fragment",				(1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT),	(1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT),	3	},
+		{ "vertex_fragment_only_fragment",	(1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT),									(1 << glu::SHADERTYPE_FRAGMENT),	2	},
+		{ "vertex_fragment_only_vertex",	(1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT),	(1 << glu::SHADERTYPE_VERTEX),										2	},
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(singleStageCases); ++ndx)
+	{
+		TestCaseGroup* const						blockGroup			= new TestCaseGroup(context, singleStageCases[ndx].name, "");
+		const bool									programSeparable	= (singleStageCases[ndx].stage != glu::SHADERTYPE_COMPUTE);
+		const ResourceDefinition::Node::SharedPtr	program				(new ResourceDefinition::Program(programSeparable));
+		const ResourceDefinition::Node::SharedPtr	stage				(new ResourceDefinition::Shader(program, singleStageCases[ndx].stage, glu::GLSL_VERSION_310_ES));
+
+		targetGroup->addChild(blockGroup);
+
+		generateBlockContent(context, stage, blockGroup, singleStageCases[ndx].expandLevel);
+	}
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(multiStageCases); ++ndx)
+	{
+		TestCaseGroup* const						blockGroup			= new TestCaseGroup(context, multiStageCases[ndx].name, "");
+		const ResourceDefinition::Node::SharedPtr	program				(new ResourceDefinition::Program());
+		ResourceDefinition::ShaderSet*				shaderSet			= new ResourceDefinition::ShaderSet(program, glu::GLSL_VERSION_310_ES);
+
+		targetGroup->addChild(blockGroup);
+
+		for (int shaderBit = 0; shaderBit < glu::SHADERTYPE_LAST; ++shaderBit)
+		{
+			const int	stageMask			= (1 << shaderBit);
+			const bool	stagePresent		= (multiStageCases[ndx].stagesPresent & stageMask) != 0;
+			const bool	stageReferencing	= (multiStageCases[ndx].stagesReferencing & stageMask) != 0;
+
+			DE_ASSERT(stagePresent || !stageReferencing);
+
+			if (stagePresent)
+				shaderSet->setStage((glu::ShaderType)shaderBit, stageReferencing);
+		}
+
+		{
+			const ResourceDefinition::Node::SharedPtr shaders(shaderSet);
+
+			generateBlockContent(context, shaders, blockGroup, multiStageCases[ndx].expandLevel);
+		}
+	}
+}
+
+static glu::DataType generateRandomDataType (de::Random& rnd, bool excludeOpaqueTypes)
+{
+	static const glu::DataType s_types[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_INT,
+		glu::TYPE_UINT,
+		glu::TYPE_BOOL,
+		glu::TYPE_FLOAT_VEC2,
+		glu::TYPE_FLOAT_VEC3,
+		glu::TYPE_FLOAT_VEC4,
+		glu::TYPE_INT_VEC2,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_INT_VEC4,
+		glu::TYPE_UINT_VEC2,
+		glu::TYPE_UINT_VEC3,
+		glu::TYPE_UINT_VEC4,
+		glu::TYPE_BOOL_VEC2,
+		glu::TYPE_BOOL_VEC3,
+		glu::TYPE_BOOL_VEC4,
+		glu::TYPE_FLOAT_MAT2,
+		glu::TYPE_FLOAT_MAT2X3,
+		glu::TYPE_FLOAT_MAT2X4,
+		glu::TYPE_FLOAT_MAT3X2,
+		glu::TYPE_FLOAT_MAT3,
+		glu::TYPE_FLOAT_MAT3X4,
+		glu::TYPE_FLOAT_MAT4X2,
+		glu::TYPE_FLOAT_MAT4X3,
+		glu::TYPE_FLOAT_MAT4,
+
+		glu::TYPE_SAMPLER_2D,
+		glu::TYPE_SAMPLER_CUBE,
+		glu::TYPE_SAMPLER_2D_ARRAY,
+		glu::TYPE_SAMPLER_3D,
+		glu::TYPE_SAMPLER_2D_SHADOW,
+		glu::TYPE_SAMPLER_CUBE_SHADOW,
+		glu::TYPE_SAMPLER_2D_ARRAY_SHADOW,
+		glu::TYPE_INT_SAMPLER_2D,
+		glu::TYPE_INT_SAMPLER_CUBE,
+		glu::TYPE_INT_SAMPLER_2D_ARRAY,
+		glu::TYPE_INT_SAMPLER_3D,
+		glu::TYPE_UINT_SAMPLER_2D,
+		glu::TYPE_UINT_SAMPLER_CUBE,
+		glu::TYPE_UINT_SAMPLER_2D_ARRAY,
+		glu::TYPE_UINT_SAMPLER_3D,
+		glu::TYPE_SAMPLER_2D_MULTISAMPLE,
+		glu::TYPE_INT_SAMPLER_2D_MULTISAMPLE,
+		glu::TYPE_UINT_SAMPLER_2D_MULTISAMPLE,
+		glu::TYPE_IMAGE_2D,
+		glu::TYPE_IMAGE_CUBE,
+		glu::TYPE_IMAGE_2D_ARRAY,
+		glu::TYPE_IMAGE_3D,
+		glu::TYPE_INT_IMAGE_2D,
+		glu::TYPE_INT_IMAGE_CUBE,
+		glu::TYPE_INT_IMAGE_2D_ARRAY,
+		glu::TYPE_INT_IMAGE_3D,
+		glu::TYPE_UINT_IMAGE_2D,
+		glu::TYPE_UINT_IMAGE_CUBE,
+		glu::TYPE_UINT_IMAGE_2D_ARRAY,
+		glu::TYPE_UINT_IMAGE_3D,
+		glu::TYPE_UINT_ATOMIC_COUNTER
+	};
+
+	for (;;)
+	{
+		const glu::DataType type = s_types[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_types)-1)];
+
+		if (!excludeOpaqueTypes					||
+			glu::isDataTypeScalarOrVector(type)	||
+			glu::isDataTypeMatrix(type))
+			return type;
+	}
+}
+
+static ResourceDefinition::Node::SharedPtr generateRandomVariableDefinition (de::Random& rnd, const ResourceDefinition::Node::SharedPtr& parentStructure, glu::DataType baseType, const glu::Layout& layout, bool allowUnsized)
+{
+	const int							maxNesting			= 4;
+	ResourceDefinition::Node::SharedPtr	currentStructure	= parentStructure;
+	const bool							canBeInsideAStruct	= layout.binding == -1 && !isDataTypeLayoutQualified(baseType);
+
+	for (int nestNdx = 0; nestNdx < maxNesting; ++nestNdx)
+	{
+		if (allowUnsized && nestNdx == 0 && rnd.getFloat() < 0.2)
+			currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::ArrayElement(currentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
+		else if (rnd.getFloat() < 0.3 && canBeInsideAStruct)
+			currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::StructMember(currentStructure));
+		else if (rnd.getFloat() < 0.3)
+			currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::ArrayElement(currentStructure));
+		else
+			break;
+	}
+
+	return ResourceDefinition::Node::SharedPtr(new ResourceDefinition::Variable(currentStructure, baseType));
+}
+
+static ResourceDefinition::Node::SharedPtr generateRandomShaderSet (de::Random& rnd)
+{
+	if (rnd.getFloat() < 0.5f)
+	{
+		// compute only
+		const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
+		return ResourceDefinition::Node::SharedPtr(new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES));
+	}
+	else if (rnd.getFloat() < 0.5f)
+	{
+		// vertex and fragment
+		const ResourceDefinition::Node::SharedPtr	program		(new ResourceDefinition::Program());
+		ResourceDefinition::ShaderSet*				shaderSet	= new ResourceDefinition::ShaderSet(program, glu::GLSL_VERSION_310_ES);
+
+		if (rnd.getBool())
+		{
+			shaderSet->setStage(glu::SHADERTYPE_VERTEX, true);
+			shaderSet->setStage(glu::SHADERTYPE_FRAGMENT, rnd.getBool());
+		}
+		else
+		{
+			shaderSet->setStage(glu::SHADERTYPE_VERTEX, rnd.getBool());
+			shaderSet->setStage(glu::SHADERTYPE_FRAGMENT, true);
+		}
+
+		return ResourceDefinition::Node::SharedPtr(shaderSet);
+	}
+	else
+	{
+		// separate vertex or fragment
+		const ResourceDefinition::Node::SharedPtr	program		(new ResourceDefinition::Program(true));
+		const glu::ShaderType						shaderType	= (rnd.getBool()) ? (glu::SHADERTYPE_VERTEX) : (glu::SHADERTYPE_FRAGMENT);
+
+		return ResourceDefinition::Node::SharedPtr(new ResourceDefinition::Shader(program, shaderType, glu::GLSL_VERSION_310_ES));
+	}
+}
+
+static glu::Layout generateRandomUniformBlockLayout (de::Random& rnd)
+{
+	glu::Layout layout;
+
+	if (rnd.getBool())
+		layout.binding = rnd.getInt(0, 5);
+
+	if (rnd.getBool())
+		layout.matrixOrder = (rnd.getBool()) ? (glu::MATRIXORDER_COLUMN_MAJOR) : (glu::MATRIXORDER_ROW_MAJOR);
+
+	return layout;
+}
+
+static glu::Layout generateRandomBufferBlockLayout (de::Random& rnd)
+{
+	return generateRandomUniformBlockLayout(rnd);
+}
+
+static glu::Layout generateRandomVariableLayout (de::Random& rnd, glu::DataType type, bool interfaceBlockMember)
+{
+	glu::Layout layout;
+
+	if ((glu::isDataTypeAtomicCounter(type) || glu::isDataTypeImage(type) || glu::isDataTypeSampler(type)) && rnd.getBool())
+		layout.binding = rnd.getInt(0, 5);
+
+	if (glu::isDataTypeAtomicCounter(type) && rnd.getBool())
+		layout.offset = rnd.getInt(0, 3) * 4;
+
+	if (glu::isDataTypeMatrix(type) && interfaceBlockMember && rnd.getBool())
+		layout.matrixOrder = (rnd.getBool()) ? (glu::MATRIXORDER_COLUMN_MAJOR) : (glu::MATRIXORDER_ROW_MAJOR);
+
+	return layout;
+}
+
+static void generateUniformRandomCase (Context& context, tcu::TestCaseGroup* const targetGroup, int index)
+{
+	de::Random									rnd					(index * 0x12345);
+	const ResourceDefinition::Node::SharedPtr	shader				= generateRandomShaderSet(rnd);
+	const bool									interfaceBlock		= rnd.getBool();
+	const glu::DataType							type				= generateRandomDataType(rnd, interfaceBlock);
+	const glu::Layout							layout				= generateRandomVariableLayout(rnd, type, interfaceBlock);
+	const ResourceDefinition::Node::SharedPtr	defaultBlock		(new ResourceDefinition::DefaultBlock(shader));
+	const ResourceDefinition::Node::SharedPtr	uniform				(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_UNIFORM));
+	ResourceDefinition::Node::SharedPtr			currentStructure	= uniform;
+
+	if (interfaceBlock)
+	{
+		const bool namedBlock = rnd.getBool();
+
+		currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(currentStructure, generateRandomUniformBlockLayout(rnd)));
+
+		if (namedBlock && rnd.getBool())
+			currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::ArrayElement(currentStructure));
+
+		currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::InterfaceBlock(currentStructure, namedBlock));
+	}
+
+	currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(currentStructure, layout));
+	currentStructure = generateRandomVariableDefinition(rnd, currentStructure, type, layout, false);
+
+	targetGroup->addChild(new ResourceTestCase(context, currentStructure, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_UNIFORM_INTERFACE_MASK), de::toString(index).c_str()));
+}
+
+static void generateUniformCaseRandomCases (Context& context, tcu::TestCaseGroup* const targetGroup)
+{
+	const int numCases = 40;
+
+	for (int ndx = 0; ndx < numCases; ++ndx)
+		generateUniformRandomCase(context, targetGroup, ndx);
+}
+
+class UniformInterfaceTestGroup : public TestCaseGroup
+{
+public:
+			UniformInterfaceTestGroup	(Context& context);
+	void	init						(void);
+};
+
+UniformInterfaceTestGroup::UniformInterfaceTestGroup (Context& context)
+	: TestCaseGroup(context, "uniform", "Uniform interace")
+{
+}
+
+void UniformInterfaceTestGroup::init (void)
+{
+	const ResourceDefinition::Node::SharedPtr	program			(new ResourceDefinition::Program());
+	const ResourceDefinition::Node::SharedPtr	computeShader	(new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES));
+
+	// .resource_list
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "resource_list", "Resource list");
+		addChild(blockGroup);
+		generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL, generateUniformResourceListBlockContents);
+	}
+
+	// .array_size
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "array_size", "Query array size");
+		addChild(blockGroup);
+		generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL, generateUniformBlockArraySizeContents);
+	}
+
+	// .array_stride
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "array_stride", "Query array stride");
+		addChild(blockGroup);
+		generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL, generateUniformBlockArrayStrideContents);
+	}
+
+	// .atomic_counter_buffer_index
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "atomic_counter_buffer_index", "Query atomic counter buffer index");
+		addChild(blockGroup);
+		generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_DEFAULT | BLOCKFLAG_NAMED, generateUniformBlockAtomicCounterBufferIndexContents);
+	}
+
+	// .block_index
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "block_index", "Query block index");
+		addChild(blockGroup);
+		generateUniformBlockBlockIndexContents(m_context, blockGroup);
+	}
+
+	// .location
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "location", "Query location");
+		addChild(blockGroup);
+		generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_DEFAULT | BLOCKFLAG_NAMED | BLOCKFLAG_UNNAMED, generateUniformBlockLocationContents);
+	}
+
+	// .matrix_row_major
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "matrix_row_major", "Query matrix row_major");
+		addChild(blockGroup);
+		generateUniformMatrixCaseBlocks(m_context, computeShader, blockGroup, generateUniformMatrixOrderCaseBlockContentCases);
+	}
+
+	// .matrix_stride
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "matrix_stride", "Query matrix stride");
+		addChild(blockGroup);
+		generateUniformMatrixCaseBlocks(m_context, computeShader, blockGroup, generateUniformMatrixStrideCaseBlockContentCases);
+	}
+
+	// .name_length
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "name_length", "Query name length");
+		addChild(blockGroup);
+		generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL, generateUniformBlockNameLengthContents);
+	}
+
+	// .offset
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "offset", "Query offset");
+		addChild(blockGroup);
+		generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL, generateUniformBlockOffsetContents);
+	}
+
+	// .referenced_by_shader
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "referenced_by_shader", "Query referenced by shader");
+		addChild(blockGroup);
+		generateReferencedByShaderCaseBlocks(m_context, blockGroup, generateUniformReferencedByShaderSingleBlockContentCases);
+	}
+
+	// .type
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "type", "Query type");
+		addChild(blockGroup);
+		generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL, generateUniformBlockTypeContents);
+	}
+
+	// .random
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Random");
+		addChild(blockGroup);
+		generateUniformCaseRandomCases(m_context, blockGroup);
+	}
+}
+
+static void generateBufferBackedInterfaceResourceListCase (Context& context, const ResourceDefinition::Node::SharedPtr& targetResource, tcu::TestCaseGroup* const targetGroup, ProgramInterface interface, const char* blockName)
+{
+	targetGroup->addChild(new ResourceListTestCase(context, targetResource, interface, blockName));
+}
+
+static void generateBufferBackedInterfaceNameLengthCase (Context& context, const ResourceDefinition::Node::SharedPtr& targetResource, tcu::TestCaseGroup* const targetGroup, ProgramInterface interface, const char* blockName)
+{
+	targetGroup->addChild(new ResourceTestCase(context, targetResource, ProgramResourceQueryTestTarget(interface, PROGRAMRESOURCEPROP_NAME_LENGTH), blockName));
+}
+
+static void generateBufferBackedInterfaceResourceBasicBlockTypes (Context& context, tcu::TestCaseGroup* targetGroup, glu::Storage storage, void (*blockContentGenerator)(Context&, const ResourceDefinition::Node::SharedPtr&, tcu::TestCaseGroup* const, ProgramInterface interface, const char* blockName))
+{
+	const ResourceDefinition::Node::SharedPtr	program				(new ResourceDefinition::Program());
+	const ResourceDefinition::Node::SharedPtr	shader				(new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES));
+	const ResourceDefinition::Node::SharedPtr	defaultBlock		(new ResourceDefinition::DefaultBlock(shader));
+	const ResourceDefinition::Node::SharedPtr	storageQualifier	(new ResourceDefinition::StorageQualifier(defaultBlock, storage));
+	const ResourceDefinition::Node::SharedPtr	binding				(new ResourceDefinition::LayoutQualifier(storageQualifier, glu::Layout(-1, 1)));
+	const ProgramInterface						programInterface	= (storage == glu::STORAGE_UNIFORM) ? (PROGRAMINTERFACE_UNIFORM_BLOCK) : (PROGRAMINTERFACE_SHADER_STORAGE_BLOCK);
+
+	// .named_block
+	{
+		const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(binding, true));
+		const ResourceDefinition::Node::SharedPtr dummyVariable	(new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));
+
+		blockContentGenerator(context, dummyVariable, targetGroup, programInterface, "named_block");
+	}
+
+	// .unnamed_block
+	{
+		const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(binding, false));
+		const ResourceDefinition::Node::SharedPtr dummyVariable	(new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));
+
+		blockContentGenerator(context, dummyVariable, targetGroup, programInterface, "unnamed_block");
+	}
+
+	// .block_array
+	{
+		const ResourceDefinition::Node::SharedPtr arrayElement	(new ResourceDefinition::ArrayElement(binding, 3));
+		const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(arrayElement, true));
+		const ResourceDefinition::Node::SharedPtr dummyVariable	(new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));
+
+		blockContentGenerator(context, dummyVariable, targetGroup, programInterface, "block_array");
+	}
+
+	// .block_array_single_element
+	{
+		const ResourceDefinition::Node::SharedPtr arrayElement	(new ResourceDefinition::ArrayElement(binding, 1));
+		const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(arrayElement, true));
+		const ResourceDefinition::Node::SharedPtr dummyVariable	(new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));
+
+		blockContentGenerator(context, dummyVariable, targetGroup, programInterface, "block_array_single_element");
+	}
+}
+
+static void generateBufferBackedInterfaceResourceBufferBindingCases (Context& context, tcu::TestCaseGroup* targetGroup, glu::Storage storage)
+{
+	const ResourceDefinition::Node::SharedPtr	program				(new ResourceDefinition::Program());
+	const ResourceDefinition::Node::SharedPtr	shader				(new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES));
+	const ResourceDefinition::Node::SharedPtr	defaultBlock		(new ResourceDefinition::DefaultBlock(shader));
+	const ResourceDefinition::Node::SharedPtr	storageQualifier	(new ResourceDefinition::StorageQualifier(defaultBlock, storage));
+
+	for (int ndx = 0; ndx < 2; ++ndx)
+	{
+		const bool									explicitBinding		= (ndx == 1);
+		const int									bindingNdx			= (explicitBinding) ? (1) : (-1);
+		const std::string							nameSuffix			= (explicitBinding) ? ("_explicit_binding") : ("");
+		const ResourceDefinition::Node::SharedPtr	binding				(new ResourceDefinition::LayoutQualifier(storageQualifier, glu::Layout(-1, bindingNdx)));
+		const ProgramInterface						programInterface	= (storage == glu::STORAGE_UNIFORM) ? (PROGRAMINTERFACE_UNIFORM_BLOCK) : (PROGRAMINTERFACE_SHADER_STORAGE_BLOCK);
+
+		// .named_block*
+		{
+			const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(binding, true));
+			const ResourceDefinition::Node::SharedPtr dummyVariable	(new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));
+
+			targetGroup->addChild(new ResourceTestCase(context, dummyVariable, ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_BUFFER_BINDING), ("named_block" + nameSuffix).c_str()));
+		}
+
+		// .unnamed_block*
+		{
+			const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(binding, false));
+			const ResourceDefinition::Node::SharedPtr dummyVariable	(new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));
+
+			targetGroup->addChild(new ResourceTestCase(context, dummyVariable, ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_BUFFER_BINDING), ("unnamed_block" + nameSuffix).c_str()));
+		}
+
+		// .block_array*
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElement	(new ResourceDefinition::ArrayElement(binding, 3));
+			const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(arrayElement, true));
+			const ResourceDefinition::Node::SharedPtr dummyVariable	(new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));
+
+			targetGroup->addChild(new ResourceTestCase(context, dummyVariable, ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_BUFFER_BINDING), ("block_array" + nameSuffix).c_str()));
+		}
+	}
+}
+
+template <glu::Storage Storage>
+static void generateBufferBlockReferencedByShaderSingleBlockContentCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, int expandLevel)
+{
+	const ProgramInterface						programInterface	= (Storage == glu::STORAGE_UNIFORM) ? (PROGRAMINTERFACE_UNIFORM_BLOCK) :
+																      (Storage == glu::STORAGE_BUFFER) ? (PROGRAMINTERFACE_SHADER_STORAGE_BLOCK) :
+																      (PROGRAMINTERFACE_LAST);
+	const ResourceDefinition::Node::SharedPtr	defaultBlock		(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	storage				(new ResourceDefinition::StorageQualifier(defaultBlock, Storage));
+
+	DE_UNREF(expandLevel);
+
+	DE_ASSERT(programInterface != PROGRAMINTERFACE_LAST);
+
+	// .named_block
+	{
+		const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(storage, true));
+		const ResourceDefinition::Node::SharedPtr dummyVariable	(new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));
+
+		targetGroup->addChild(new ResourceTestCase(context, dummyVariable, ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER), "named_block"));
+	}
+
+	// .unnamed_block
+	{
+		const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(storage, false));
+		const ResourceDefinition::Node::SharedPtr dummyVariable	(new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));
+
+		targetGroup->addChild(new ResourceTestCase(context, dummyVariable, ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER), "unnamed_block"));
+	}
+
+	// .block_array
+	{
+		const ResourceDefinition::Node::SharedPtr arrayElement	(new ResourceDefinition::ArrayElement(storage, 3));
+		const ResourceDefinition::Node::SharedPtr block			(new ResourceDefinition::InterfaceBlock(arrayElement, true));
+		const ResourceDefinition::Node::SharedPtr dummyVariable	(new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));
+
+		targetGroup->addChild(new ResourceTestCase(context, dummyVariable, ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER), "block_array"));
+	}
+}
+
+static void generateBufferBackedInterfaceResourceActiveVariablesCase (Context& context, tcu::TestCaseGroup* targetGroup, glu::Storage storage)
+{
+	targetGroup->addChild(new InterfaceBlockActiveVariablesTestCase(context, "named_block",		"Named block",		storage,	InterfaceBlockActiveVariablesTestCase::CASE_NAMED_BLOCK));
+	targetGroup->addChild(new InterfaceBlockActiveVariablesTestCase(context, "unnamed_block",	"Unnamed block",	storage,	InterfaceBlockActiveVariablesTestCase::CASE_UNNAMED_BLOCK));
+	targetGroup->addChild(new InterfaceBlockActiveVariablesTestCase(context, "block_array",		"Block array",		storage,	InterfaceBlockActiveVariablesTestCase::CASE_BLOCK_ARRAY));
+}
+
+static void generateBufferBackedInterfaceResourceBufferDataSizeCases (Context& context, tcu::TestCaseGroup* targetGroup, glu::Storage storage)
+{
+	targetGroup->addChild(new InterfaceBlockDataSizeTestCase(context, "named_block",	"Named block",		storage,	InterfaceBlockDataSizeTestCase::CASE_NAMED_BLOCK));
+	targetGroup->addChild(new InterfaceBlockDataSizeTestCase(context, "unnamed_block",	"Unnamed block",	storage,	InterfaceBlockDataSizeTestCase::CASE_UNNAMED_BLOCK));
+	targetGroup->addChild(new InterfaceBlockDataSizeTestCase(context, "block_array",	"Block array",		storage,	InterfaceBlockDataSizeTestCase::CASE_BLOCK_ARRAY));
+}
+
+class BufferBackedBlockInterfaceTestGroup : public TestCaseGroup
+{
+public:
+						BufferBackedBlockInterfaceTestGroup	(Context& context, glu::Storage interfaceBlockStorage);
+	void				init								(void);
+
+private:
+	static const char*	getGroupName						(glu::Storage storage);
+	static const char*	getGroupDescription					(glu::Storage storage);
+
+	const glu::Storage	m_storage;
+};
+
+BufferBackedBlockInterfaceTestGroup::BufferBackedBlockInterfaceTestGroup(Context& context, glu::Storage storage)
+	: TestCaseGroup	(context, getGroupName(storage), getGroupDescription(storage))
+	, m_storage		(storage)
+{
+	DE_ASSERT(storage == glu::STORAGE_BUFFER || storage == glu::STORAGE_UNIFORM);
+}
+
+void BufferBackedBlockInterfaceTestGroup::init (void)
+{
+	// .resource_list
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "resource_list", "Resource list");
+		addChild(blockGroup);
+		generateBufferBackedInterfaceResourceBasicBlockTypes(m_context, blockGroup, m_storage, generateBufferBackedInterfaceResourceListCase);
+	}
+
+	// .active_variables
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "active_variables", "Active variables");
+		addChild(blockGroup);
+		generateBufferBackedInterfaceResourceActiveVariablesCase(m_context, blockGroup, m_storage);
+	}
+
+	// .buffer_binding
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "buffer_binding", "Buffer binding");
+		addChild(blockGroup);
+		generateBufferBackedInterfaceResourceBufferBindingCases(m_context, blockGroup, m_storage);
+	}
+
+	// .buffer_data_size
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "buffer_data_size", "Buffer data size");
+		addChild(blockGroup);
+		generateBufferBackedInterfaceResourceBufferDataSizeCases(m_context, blockGroup, m_storage);
+	}
+
+	// .name_length
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "name_length", "Name length");
+		addChild(blockGroup);
+		generateBufferBackedInterfaceResourceBasicBlockTypes(m_context, blockGroup, m_storage, generateBufferBackedInterfaceNameLengthCase);
+	}
+
+	// .referenced_by
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "referenced_by", "Referenced by shader");
+		addChild(blockGroup);
+
+		if (m_storage == glu::STORAGE_UNIFORM)
+			generateReferencedByShaderCaseBlocks(m_context, blockGroup, generateBufferBlockReferencedByShaderSingleBlockContentCases<glu::STORAGE_UNIFORM>);
+		else if (m_storage == glu::STORAGE_BUFFER)
+			generateReferencedByShaderCaseBlocks(m_context, blockGroup, generateBufferBlockReferencedByShaderSingleBlockContentCases<glu::STORAGE_BUFFER>);
+		else
+			DE_ASSERT(false);
+	}
+}
+
+const char* BufferBackedBlockInterfaceTestGroup::getGroupName (glu::Storage storage)
+{
+	switch (storage)
+	{
+		case glu::STORAGE_UNIFORM:	return "uniform_block";
+		case glu::STORAGE_BUFFER:	return "shader_storage_block";
+		default:
+			DE_ASSERT("false");
+			return DE_NULL;
+	}
+}
+
+const char* BufferBackedBlockInterfaceTestGroup::getGroupDescription (glu::Storage storage)
+{
+	switch (storage)
+	{
+		case glu::STORAGE_UNIFORM:	return "Uniform block interface";
+		case glu::STORAGE_BUFFER:	return "Shader storage block interface";
+		default:
+			DE_ASSERT("false");
+			return DE_NULL;
+	}
+}
+
+class AtomicCounterTestGroup : public TestCaseGroup
+{
+public:
+			AtomicCounterTestGroup	(Context& context);
+	void	init					(void);
+};
+
+AtomicCounterTestGroup::AtomicCounterTestGroup (Context& context)
+	: TestCaseGroup(context, "atomic_counter_buffer", "Atomic counter buffer")
+{
+}
+
+void AtomicCounterTestGroup::init (void)
+{
+	// .resource_list
+	addChild(new AtomicCounterResourceListCase(m_context, "resource_list", "Resource list"));
+
+	// .active_variables
+	addChild(new AtomicCounterActiveVariablesCase(m_context, "active_variables", "Active variables"));
+
+	// .buffer_binding
+	addChild(new AtomicCounterBufferBindingCase(m_context, "buffer_binding", "Buffer binding"));
+
+	// .buffer_data_size
+	addChild(new AtomicCounterBufferDataSizeCase(m_context, "buffer_data_size", "Buffer binding"));
+
+	// .referenced_by
+	addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_compute",							"",	(1 << glu::SHADERTYPE_COMPUTE),										(1 << glu::SHADERTYPE_COMPUTE)));
+	addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_separable_vertex",					"",	(1 << glu::SHADERTYPE_VERTEX),										(1 << glu::SHADERTYPE_VERTEX)));
+	addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_separable_fragment",				"",	(1 << glu::SHADERTYPE_FRAGMENT),									(1 << glu::SHADERTYPE_FRAGMENT)));
+	addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_vertex_fragment",					"",	(1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT),	(1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT)));
+	addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_vertex_fragment_only_fragment",	"",	(1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT),									(1 << glu::SHADERTYPE_FRAGMENT)));
+	addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_vertex_fragment_only_vertex",		"",	(1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT),	(1 << glu::SHADERTYPE_VERTEX)));
+}
+
+static void generateProgramInputOutputShaderCaseBlocks (Context& context, tcu::TestCaseGroup* targetGroup, bool withCompute, bool inputCase, void (*blockContentGenerator)(Context&, const ResourceDefinition::Node::SharedPtr&, tcu::TestCaseGroup*, deUint32))
+{
+	// .vertex_fragment
+	{
+		tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "vertex_fragment", "Vertex and fragment");
+		const ResourceDefinition::Node::SharedPtr	program			(new ResourceDefinition::Program(false));
+		ResourceDefinition::ShaderSet*				shaderSetPtr	= new ResourceDefinition::ShaderSet(program, glu::GLSL_VERSION_310_ES);
+		const ResourceDefinition::Node::SharedPtr	shaderSet		(shaderSetPtr);
+
+		shaderSetPtr->setStage(glu::SHADERTYPE_VERTEX, inputCase);
+		shaderSetPtr->setStage(glu::SHADERTYPE_FRAGMENT, !inputCase);
+
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, shaderSet, blockGroup, (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT));
+	}
+
+	// .separable_vertex
+	{
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "separable_vertex", "Separable vertex");
+		const ResourceDefinition::Node::SharedPtr	program		(new ResourceDefinition::Program(true));
+		const ResourceDefinition::Node::SharedPtr	shader		(new ResourceDefinition::Shader(program, glu::SHADERTYPE_VERTEX, glu::GLSL_VERSION_310_ES));
+
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, shader, blockGroup, (1 << glu::SHADERTYPE_VERTEX));
+	}
+
+	// .separable_fragment
+	{
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "separable_fragment", "Separable fragment");
+		const ResourceDefinition::Node::SharedPtr	program		(new ResourceDefinition::Program(true));
+		const ResourceDefinition::Node::SharedPtr	shader		(new ResourceDefinition::Shader(program, glu::SHADERTYPE_FRAGMENT, glu::GLSL_VERSION_310_ES));
+
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, shader, blockGroup, (1 << glu::SHADERTYPE_FRAGMENT));
+	}
+
+	// .compute
+	if (withCompute)
+	{
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "compute", "Compute");
+		const ResourceDefinition::Node::SharedPtr	program		(new ResourceDefinition::Program(true));
+		const ResourceDefinition::Node::SharedPtr	shader		(new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES));
+
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, shader, blockGroup, (1 << glu::SHADERTYPE_COMPUTE));
+	}
+}
+
+static void generateProgramInputResourceListBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, deUint32 presentShadersMask)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	input			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_IN));
+
+	// .empty
+	targetGroup->addChild(new ResourceListTestCase(context, defaultBlock, PROGRAMINTERFACE_PROGRAM_INPUT, "empty"));
+
+	// vertex first stage
+	if (presentShadersMask & (1 << glu::SHADERTYPE_VERTEX))
+	{
+		// .var
+		const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(input, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new ResourceListTestCase(context, variable, PROGRAMINTERFACE_PROGRAM_INPUT));
+	}
+
+	// fragment first stage
+	if (presentShadersMask == (1 << glu::SHADERTYPE_FRAGMENT))
+	{
+		// .var
+		{
+			const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(input, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceListTestCase(context, variable, PROGRAMINTERFACE_PROGRAM_INPUT));
+		}
+		// .var_struct
+		{
+			const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(input));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceListTestCase(context, variable, PROGRAMINTERFACE_PROGRAM_INPUT));
+		}
+		// .var_array
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(input));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceListTestCase(context, variable, PROGRAMINTERFACE_PROGRAM_INPUT));
+		}
+	}
+}
+
+static void generateProgramOutputResourceListBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, deUint32 presentShadersMask)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	output			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_OUT));
+
+	// .empty
+	targetGroup->addChild(new ResourceListTestCase(context, defaultBlock, PROGRAMINTERFACE_PROGRAM_OUTPUT, "empty"));
+
+	// vertex last stage
+	if (presentShadersMask == (1 << glu::SHADERTYPE_VERTEX))
+	{
+		// .var
+		{
+			const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(output, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceListTestCase(context, variable, PROGRAMINTERFACE_PROGRAM_OUTPUT));
+		}
+		// .var_struct
+		{
+			const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(output));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceListTestCase(context, variable, PROGRAMINTERFACE_PROGRAM_OUTPUT));
+		}
+		// .var_array
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(output));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceListTestCase(context, variable, PROGRAMINTERFACE_PROGRAM_OUTPUT));
+		}
+	}
+
+	// fragment last stage
+	if (presentShadersMask & (1 << glu::SHADERTYPE_FRAGMENT))
+	{
+		// .var
+		{
+			const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(output, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceListTestCase(context, variable, PROGRAMINTERFACE_PROGRAM_OUTPUT));
+		}
+		// .var_array
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(output));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceListTestCase(context, variable, PROGRAMINTERFACE_PROGRAM_OUTPUT));
+		}
+	}
+}
+
+template <ProgramResourcePropFlags TargetProp>
+static void generateProgramInputBasicBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, deUint32 presentShadersMask)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	input			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_IN));
+
+	// vertex first stage
+	if (presentShadersMask & (1 << glu::SHADERTYPE_VERTEX))
+	{
+		// .var
+		const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(input, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, TargetProp), "var"));
+	}
+
+	// fragment first stage
+	if (presentShadersMask == (1 << glu::SHADERTYPE_FRAGMENT))
+	{
+		// .var
+		{
+			const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(input, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, TargetProp), "var"));
+		}
+		// .var_struct
+		{
+			const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(input));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, TargetProp), "var_struct"));
+		}
+		// .var_array
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(input));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, TargetProp), "var_array"));
+		}
+	}
+}
+
+template <ProgramResourcePropFlags TargetProp>
+static void generateProgramOutputBasicBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, deUint32 presentShadersMask)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	output			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_OUT));
+
+	// vertex last stage
+	if (presentShadersMask == (1 << glu::SHADERTYPE_VERTEX))
+	{
+		// .var
+		{
+			const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(output, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, TargetProp), "var"));
+		}
+		// .var_struct
+		{
+			const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(output));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, TargetProp), "var_struct"));
+		}
+		// .var_array
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(output));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, TargetProp), "var_array"));
+		}
+	}
+
+	// fragment last stage
+	if (presentShadersMask & (1 << glu::SHADERTYPE_FRAGMENT))
+	{
+		// .var
+		{
+			const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(output, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, TargetProp), "var"));
+		}
+		// .var_array
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(output));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, TargetProp), "var_array"));
+		}
+	}
+}
+
+static void generateProgramInputLocationBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, deUint32 presentShadersMask)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	input			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_IN));
+
+	// vertex first stage
+	if (presentShadersMask & (1 << glu::SHADERTYPE_VERTEX))
+	{
+		// .var
+		{
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(input, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
+		}
+		// .var_explicit_location
+		{
+			const ResourceDefinition::Node::SharedPtr layout	(new ResourceDefinition::LayoutQualifier(input, glu::Layout(2)));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(layout, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var_explicit_location"));
+		}
+	}
+
+	// fragment first stage
+	if (presentShadersMask == (1 << glu::SHADERTYPE_FRAGMENT))
+	{
+		// .var
+		{
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(input, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
+		}
+		// .var_explicit_location
+		{
+			const ResourceDefinition::Node::SharedPtr layout	(new ResourceDefinition::LayoutQualifier(input, glu::Layout(2)));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(layout, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var_explicit_location"));
+		}
+		// .var_struct
+		{
+			const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(input));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var_struct"));
+		}
+		// .var_struct_explicit_location
+		{
+			const ResourceDefinition::Node::SharedPtr layout	(new ResourceDefinition::LayoutQualifier(input, glu::Layout(2)));
+			const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(layout));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var_struct_explicit_location"));
+		}
+		// .var_array
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(input));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var_array"));
+		}
+		// .var_array_explicit_location
+		{
+			const ResourceDefinition::Node::SharedPtr layout	(new ResourceDefinition::LayoutQualifier(input, glu::Layout(2)));
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(layout));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var_array_explicit_location"));
+		}
+	}
+}
+
+static void generateProgramOutputLocationBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, deUint32 presentShadersMask)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	output			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_OUT));
+
+	// vertex last stage
+	if (presentShadersMask == (1 << glu::SHADERTYPE_VERTEX))
+	{
+		// .var
+		{
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(output, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
+		}
+		// .var_explicit_location
+		{
+			const ResourceDefinition::Node::SharedPtr layout	(new ResourceDefinition::LayoutQualifier(output, glu::Layout(2)));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(layout, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var_explicit_location"));
+		}
+		// .var_struct
+		{
+			const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(output));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var_struct"));
+		}
+		// .var_struct_explicit_location
+		{
+			const ResourceDefinition::Node::SharedPtr layout	(new ResourceDefinition::LayoutQualifier(output, glu::Layout(2)));
+			const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(layout));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var_struct_explicit_location"));
+		}
+		// .var_array
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(output));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var_array"));
+		}
+		// .var_array_explicit_location
+		{
+			const ResourceDefinition::Node::SharedPtr layout	(new ResourceDefinition::LayoutQualifier(output, glu::Layout(2)));
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(layout));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var_array_explicit_location"));
+		}
+	}
+
+	// fragment last stage
+	if (presentShadersMask & (1 << glu::SHADERTYPE_FRAGMENT))
+	{
+		// .var
+		{
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(output, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
+		}
+		// .var_explicit_location
+		{
+			const ResourceDefinition::Node::SharedPtr layout	(new ResourceDefinition::LayoutQualifier(output, glu::Layout(2)));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(layout, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var_explicit_location"));
+		}
+		// .var_array
+		{
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(output));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var_array"));
+		}
+		// .var_array_explicit_location
+		{
+			const ResourceDefinition::Node::SharedPtr layout	(new ResourceDefinition::LayoutQualifier(output, glu::Layout(2)));
+			const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(layout));
+			const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var_array_explicit_location"));
+		}
+	}
+}
+
+static void generateProgramInputOutputReferencedByCases (Context& context, tcu::TestCaseGroup* targetGroup, glu::Storage storage)
+{
+	targetGroup->addChild(new ProgramInputOutputReferencedByCase(context, "referenced_by_vertex_fragment",		"",	storage,	ProgramInputOutputReferencedByCase::CASE_VERTEX_FRAGMENT));
+	targetGroup->addChild(new ProgramInputOutputReferencedByCase(context, "referenced_by_separable_vertex",		"",	storage,	ProgramInputOutputReferencedByCase::CASE_SEPARABLE_VERTEX));
+	targetGroup->addChild(new ProgramInputOutputReferencedByCase(context, "referenced_by_separable_fragment",	"",	storage,	ProgramInputOutputReferencedByCase::CASE_SEPARABLE_FRAGMENT));
+}
+
+static void generateProgramInputTypeBasicTypeCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup)
+{
+	static const glu::DataType variableTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_INT,
+		glu::TYPE_UINT,
+
+		glu::TYPE_FLOAT_VEC2,
+		glu::TYPE_FLOAT_VEC3,
+		glu::TYPE_FLOAT_VEC4,
+
+		glu::TYPE_INT_VEC2,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_INT_VEC4,
+
+		glu::TYPE_UINT_VEC2,
+		glu::TYPE_UINT_VEC3,
+		glu::TYPE_UINT_VEC4,
+
+		glu::TYPE_FLOAT_MAT2,
+		glu::TYPE_FLOAT_MAT2X3,
+		glu::TYPE_FLOAT_MAT2X4,
+		glu::TYPE_FLOAT_MAT3X2,
+		glu::TYPE_FLOAT_MAT3,
+		glu::TYPE_FLOAT_MAT3X4,
+		glu::TYPE_FLOAT_MAT4X2,
+		glu::TYPE_FLOAT_MAT4X3,
+		glu::TYPE_FLOAT_MAT4,
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
+	{
+		const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, variableTypes[ndx]));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_TYPE)));
+	}
+}
+
+static void generateProgramInputTypeBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, deUint32 presentShadersMask)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	input			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_IN));
+
+	// vertex first stage
+	if (presentShadersMask & (1 << glu::SHADERTYPE_VERTEX))
+	{
+		// Only basic types (and no booleans)
+		generateProgramInputTypeBasicTypeCases(context, input, targetGroup);
+	}
+
+	// fragment first stage
+	if (presentShadersMask == (1 << glu::SHADERTYPE_FRAGMENT))
+	{
+		const ResourceDefinition::Node::SharedPtr flatShading(new ResourceDefinition::InterpolationQualifier(input, glu::INTERPOLATION_FLAT));
+
+		// Only basic types, arrays of basic types, struct of basic types (and no booleans)
+		{
+			tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "basic_type", "Basic types");
+			targetGroup->addChild(blockGroup);
+			generateProgramInputTypeBasicTypeCases(context, flatShading, blockGroup);
+		}
+		{
+			const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(flatShading));
+			tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "array", "Array types");
+
+			targetGroup->addChild(blockGroup);
+			generateProgramInputTypeBasicTypeCases(context, arrayElement, blockGroup);
+		}
+		{
+			const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(flatShading));
+			tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "struct", "Struct types");
+
+			targetGroup->addChild(blockGroup);
+			generateProgramInputTypeBasicTypeCases(context, structMember, blockGroup);
+		}
+	}
+}
+
+static void generateProgramOutputTypeBasicTypeCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, bool addMatrixCases)
+{
+	static const glu::DataType variableTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_INT,
+		glu::TYPE_UINT,
+
+		glu::TYPE_FLOAT_VEC2,
+		glu::TYPE_FLOAT_VEC3,
+		glu::TYPE_FLOAT_VEC4,
+
+		glu::TYPE_INT_VEC2,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_INT_VEC4,
+
+		glu::TYPE_UINT_VEC2,
+		glu::TYPE_UINT_VEC3,
+		glu::TYPE_UINT_VEC4,
+
+		glu::TYPE_FLOAT_MAT2,
+		glu::TYPE_FLOAT_MAT2X3,
+		glu::TYPE_FLOAT_MAT2X4,
+		glu::TYPE_FLOAT_MAT3X2,
+		glu::TYPE_FLOAT_MAT3,
+		glu::TYPE_FLOAT_MAT3X4,
+		glu::TYPE_FLOAT_MAT4X2,
+		glu::TYPE_FLOAT_MAT4X3,
+		glu::TYPE_FLOAT_MAT4,
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
+	{
+		if (addMatrixCases || !glu::isDataTypeMatrix(variableTypes[ndx]))
+		{
+			const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, variableTypes[ndx]));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_TYPE)));
+		}
+	}
+}
+
+static void generateProgramOutputTypeBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, deUint32 presentShadersMask)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	output			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_OUT));
+
+	// vertex last stage
+	if (presentShadersMask == (1 << glu::SHADERTYPE_VERTEX))
+	{
+		const ResourceDefinition::Node::SharedPtr flatShading(new ResourceDefinition::InterpolationQualifier(output, glu::INTERPOLATION_FLAT));
+
+		// Only basic types, arrays of basic types, struct of basic types (and no booleans)
+		{
+			tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "basic_type", "Basic types");
+			targetGroup->addChild(blockGroup);
+			generateProgramOutputTypeBasicTypeCases(context, flatShading, blockGroup, true);
+		}
+		{
+			const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(flatShading));
+			tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "array", "Array types");
+
+			targetGroup->addChild(blockGroup);
+			generateProgramOutputTypeBasicTypeCases(context, arrayElement, blockGroup, true);
+		}
+		{
+			const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(flatShading));
+			tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "struct", "Struct types");
+
+			targetGroup->addChild(blockGroup);
+			generateProgramOutputTypeBasicTypeCases(context, structMember, blockGroup, true);
+		}
+	}
+
+	// fragment last stage
+	if (presentShadersMask & (1 << glu::SHADERTYPE_FRAGMENT))
+	{
+		// only basic type and basic type array (and no booleans or matrices)
+		{
+			tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "basic_type", "Basic types");
+			targetGroup->addChild(blockGroup);
+			generateProgramOutputTypeBasicTypeCases(context, output, blockGroup, false);
+		}
+		{
+			const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(output));
+			tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "array", "Array types");
+
+			targetGroup->addChild(blockGroup);
+			generateProgramOutputTypeBasicTypeCases(context, arrayElement, blockGroup, false);
+		}
+	}
+}
+
+class ProgramInputTestGroup : public TestCaseGroup
+{
+public:
+			ProgramInputTestGroup	(Context& context);
+	void	init					(void);
+};
+
+ProgramInputTestGroup::ProgramInputTestGroup (Context& context)
+	: TestCaseGroup(context, "program_input", "Program input")
+{
+}
+
+void ProgramInputTestGroup::init (void)
+{
+	// .resource_list
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "resource_list", "Resource list");
+		addChild(blockGroup);
+		generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, true, true, generateProgramInputResourceListBlockContents);
+	}
+
+	// .array_size
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "array_size", "Array size");
+		addChild(blockGroup);
+		generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, false, true, generateProgramInputBasicBlockContents<PROGRAMRESOURCEPROP_ARRAY_SIZE>);
+	}
+
+	// .location
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "location", "Location");
+		addChild(blockGroup);
+		generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, false, true, generateProgramInputLocationBlockContents);
+	}
+
+	// .name_length
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "name_length", "Name length");
+		addChild(blockGroup);
+		generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, false, true, generateProgramInputBasicBlockContents<PROGRAMRESOURCEPROP_NAME_LENGTH>);
+	}
+
+	// .referenced_by
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "referenced_by", "Reference by shader");
+		addChild(blockGroup);
+		generateProgramInputOutputReferencedByCases(m_context, blockGroup, glu::STORAGE_IN);
+	}
+
+	// .type
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "type", "Type");
+		addChild(blockGroup);
+		generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, true, true, generateProgramInputTypeBlockContents);
+	}
+}
+
+class ProgramOutputTestGroup : public TestCaseGroup
+{
+public:
+			ProgramOutputTestGroup	(Context& context);
+	void	init					(void);
+};
+
+ProgramOutputTestGroup::ProgramOutputTestGroup (Context& context)
+	: TestCaseGroup(context, "program_output", "Program output")
+{
+}
+
+void ProgramOutputTestGroup::init (void)
+{
+	// .resource_list
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "resource_list", "Resource list");
+		addChild(blockGroup);
+		generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, true, false, generateProgramOutputResourceListBlockContents);
+	}
+
+	// .array_size
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "array_size", "Array size");
+		addChild(blockGroup);
+		generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, false, false, generateProgramOutputBasicBlockContents<PROGRAMRESOURCEPROP_ARRAY_SIZE>);
+	}
+
+	// .location
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "location", "Location");
+		addChild(blockGroup);
+		generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, false, false, generateProgramOutputLocationBlockContents);
+	}
+
+	// .name_length
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "name_length", "Name length");
+		addChild(blockGroup);
+		generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, false, false, generateProgramOutputBasicBlockContents<PROGRAMRESOURCEPROP_NAME_LENGTH>);
+	}
+
+	// .referenced_by
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "referenced_by", "Reference by shader");
+		addChild(blockGroup);
+		generateProgramInputOutputReferencedByCases(m_context, blockGroup, glu::STORAGE_OUT);
+	}
+
+	// .type
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(m_testCtx, "type", "Type");
+		addChild(blockGroup);
+		generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, true, false, generateProgramOutputTypeBlockContents);
+	}
+}
+
+static void generateTransformFeedbackShaderCaseBlocks (Context& context, tcu::TestCaseGroup* targetGroup, void (*blockContentGenerator)(Context&, const ResourceDefinition::Node::SharedPtr&, tcu::TestCaseGroup*))
+{
+	// .vertex_fragment
+	{
+		tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "vertex_fragment", "Vertex and fragment");
+		const ResourceDefinition::Node::SharedPtr	program			(new ResourceDefinition::Program(false));
+		ResourceDefinition::ShaderSet*				shaderSetPtr	= new ResourceDefinition::ShaderSet(program, glu::GLSL_VERSION_310_ES);
+		const ResourceDefinition::Node::SharedPtr	shaderSet		(shaderSetPtr);
+
+		shaderSetPtr->setStage(glu::SHADERTYPE_VERTEX, true);
+		shaderSetPtr->setStage(glu::SHADERTYPE_FRAGMENT, false);
+
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, shaderSet, blockGroup);
+	}
+
+	// .separable_vertex
+	{
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "separable_vertex", "Separable vertex");
+		const ResourceDefinition::Node::SharedPtr	program		(new ResourceDefinition::Program(true));
+		const ResourceDefinition::Node::SharedPtr	shader		(new ResourceDefinition::Shader(program, glu::SHADERTYPE_VERTEX, glu::GLSL_VERSION_310_ES));
+
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, shader, blockGroup);
+	}
+}
+
+static void generateTransformFeedbackResourceListBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	output			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_OUT));
+
+	// .builtin_gl_position
+	{
+		const ResourceDefinition::Node::SharedPtr xfbTarget(new ResourceDefinition::TransformFeedbackTarget(defaultBlock, "gl_Position"));
+		targetGroup->addChild(new FeedbackResourceListTestCase(context, xfbTarget, "builtin_gl_position"));
+	}
+	// .default_block_basic_type
+	{
+		const ResourceDefinition::Node::SharedPtr xfbTarget	(new ResourceDefinition::TransformFeedbackTarget(output));
+		const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new FeedbackResourceListTestCase(context, variable, "default_block_basic_type"));
+	}
+	// .default_block_struct
+	{
+		const ResourceDefinition::Node::SharedPtr xfbTarget	(new ResourceDefinition::TransformFeedbackTarget(output));
+		const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(xfbTarget));
+		const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new FeedbackResourceListTestCase(context, variable, "default_block_struct"));
+	}
+	// .default_block_struct_member
+	{
+		const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(output));
+		const ResourceDefinition::Node::SharedPtr xfbTarget	(new ResourceDefinition::TransformFeedbackTarget(structMbr));
+		const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new FeedbackResourceListTestCase(context, variable, "default_block_struct_member"));
+	}
+	// .default_block_array
+	{
+		const ResourceDefinition::Node::SharedPtr xfbTarget	(new ResourceDefinition::TransformFeedbackTarget(output));
+		const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(xfbTarget));
+		const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new FeedbackResourceListTestCase(context, variable, "default_block_array"));
+	}
+	// .default_block_array_element
+	{
+		const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(output));
+		const ResourceDefinition::Node::SharedPtr xfbTarget	(new ResourceDefinition::TransformFeedbackTarget(arrayElem));
+		const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new FeedbackResourceListTestCase(context, variable, "default_block_array_element"));
+	}
+}
+
+template <ProgramResourcePropFlags TargetProp>
+static void generateTransformFeedbackVariableBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	output			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_OUT));
+
+	// .builtin_gl_position
+	{
+		const ResourceDefinition::Node::SharedPtr xfbTarget(new ResourceDefinition::TransformFeedbackTarget(defaultBlock, "gl_Position"));
+		targetGroup->addChild(new ResourceTestCase(context, xfbTarget, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp), "builtin_gl_position"));
+	}
+	// .default_block_basic_type
+	{
+		const ResourceDefinition::Node::SharedPtr xfbTarget	(new ResourceDefinition::TransformFeedbackTarget(output));
+		const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp), "default_block_basic_type"));
+	}
+	// .default_block_struct
+	{
+		const ResourceDefinition::Node::SharedPtr xfbTarget	(new ResourceDefinition::TransformFeedbackTarget(output));
+		const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(xfbTarget));
+		const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp), "default_block_struct"));
+	}
+	// .default_block_struct_member
+	{
+		const ResourceDefinition::Node::SharedPtr structMbr	(new ResourceDefinition::StructMember(output));
+		const ResourceDefinition::Node::SharedPtr xfbTarget	(new ResourceDefinition::TransformFeedbackTarget(structMbr));
+		const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp), "default_block_struct_member"));
+	}
+	// .default_block_array
+	{
+		const ResourceDefinition::Node::SharedPtr xfbTarget	(new ResourceDefinition::TransformFeedbackTarget(output));
+		const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(xfbTarget));
+		const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp), "default_block_array"));
+	}
+	// .default_block_array_element
+	{
+		const ResourceDefinition::Node::SharedPtr arrayElem	(new ResourceDefinition::ArrayElement(output));
+		const ResourceDefinition::Node::SharedPtr xfbTarget	(new ResourceDefinition::TransformFeedbackTarget(arrayElem));
+		const ResourceDefinition::Node::SharedPtr variable	(new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp), "default_block_array_element"));
+	}
+}
+
+static void generateTransformFeedbackVariableBasicTypeCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup)
+{
+	static const glu::DataType variableTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_INT,
+		glu::TYPE_UINT,
+
+		glu::TYPE_FLOAT_VEC2,
+		glu::TYPE_FLOAT_VEC3,
+		glu::TYPE_FLOAT_VEC4,
+
+		glu::TYPE_INT_VEC2,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_INT_VEC4,
+
+		glu::TYPE_UINT_VEC2,
+		glu::TYPE_UINT_VEC3,
+		glu::TYPE_UINT_VEC4,
+
+		glu::TYPE_FLOAT_MAT2,
+		glu::TYPE_FLOAT_MAT2X3,
+		glu::TYPE_FLOAT_MAT2X4,
+		glu::TYPE_FLOAT_MAT3X2,
+		glu::TYPE_FLOAT_MAT3,
+		glu::TYPE_FLOAT_MAT3X4,
+		glu::TYPE_FLOAT_MAT4X2,
+		glu::TYPE_FLOAT_MAT4X3,
+		glu::TYPE_FLOAT_MAT4,
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
+	{
+		const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, variableTypes[ndx]));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, PROGRAMRESOURCEPROP_TYPE)));
+	}
+}
+
+static void generateTransformFeedbackVariableTypeBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup)
+{
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	output			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_OUT));
+	const ResourceDefinition::Node::SharedPtr	flatShading		(new ResourceDefinition::InterpolationQualifier(output, glu::INTERPOLATION_FLAT));
+
+	// Only basic types, arrays of basic types, struct of basic types (and no booleans)
+	{
+		const ResourceDefinition::Node::SharedPtr	xfbTarget		(new ResourceDefinition::TransformFeedbackTarget(flatShading));
+		tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "basic_type", "Basic types");
+
+		targetGroup->addChild(blockGroup);
+		generateTransformFeedbackVariableBasicTypeCases(context, xfbTarget, blockGroup);
+	}
+	{
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(flatShading));
+		const ResourceDefinition::Node::SharedPtr	xfbTarget		(new ResourceDefinition::TransformFeedbackTarget(arrayElement));
+		tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "array", "Array types");
+
+		targetGroup->addChild(blockGroup);
+		generateTransformFeedbackVariableBasicTypeCases(context, xfbTarget, blockGroup);
+	}
+	{
+		const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(flatShading));
+		const ResourceDefinition::Node::SharedPtr	xfbTarget		(new ResourceDefinition::TransformFeedbackTarget(structMember));
+		tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "struct", "Struct types");
+
+		targetGroup->addChild(blockGroup);
+		generateTransformFeedbackVariableBasicTypeCases(context, xfbTarget, blockGroup);
+	}
+}
+
+class TransformFeedbackVaryingTestGroup : public TestCaseGroup
+{
+public:
+			TransformFeedbackVaryingTestGroup	(Context& context);
+	void	init								(void);
+};
+
+TransformFeedbackVaryingTestGroup::TransformFeedbackVaryingTestGroup (Context& context)
+	: TestCaseGroup(context, "transform_feedback_varying", "Transform feedback varyings")
+{
+}
+
+void TransformFeedbackVaryingTestGroup::init (void)
+{
+	// .resource_list
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "resource_list", "Resource list");
+		addChild(blockGroup);
+		generateTransformFeedbackShaderCaseBlocks(m_context, blockGroup, generateTransformFeedbackResourceListBlockContents);
+	}
+
+	// .array_size
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "array_size", "Array size");
+		addChild(blockGroup);
+		generateTransformFeedbackShaderCaseBlocks(m_context, blockGroup, generateTransformFeedbackVariableBlockContents<PROGRAMRESOURCEPROP_ARRAY_SIZE>);
+	}
+
+	// .name_length
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "name_length", "Name length");
+		addChild(blockGroup);
+		generateTransformFeedbackShaderCaseBlocks(m_context, blockGroup, generateTransformFeedbackVariableBlockContents<PROGRAMRESOURCEPROP_NAME_LENGTH>);
+	}
+
+	// .type
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "type", "Type");
+		addChild(blockGroup);
+		generateTransformFeedbackShaderCaseBlocks(m_context, blockGroup, generateTransformFeedbackVariableTypeBlockContents);
+	}
+}
+
+static void generateBufferVariableBufferCaseBlocks (Context& context, tcu::TestCaseGroup* targetGroup, void (*blockContentGenerator)(Context&, const ResourceDefinition::Node::SharedPtr&, tcu::TestCaseGroup*))
+{
+	const ResourceDefinition::Node::SharedPtr	program			(new ResourceDefinition::Program());
+	const ResourceDefinition::Node::SharedPtr	shader			(new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES));
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(shader));
+	const ResourceDefinition::Node::SharedPtr	bufferStorage	(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
+	const ResourceDefinition::Node::SharedPtr	binding			(new ResourceDefinition::LayoutQualifier(bufferStorage, glu::Layout(-1, 0)));
+
+	// .named_block
+	{
+		const ResourceDefinition::Node::SharedPtr	buffer		(new ResourceDefinition::InterfaceBlock(binding, true));
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "named_block", "Named block");
+
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, buffer, blockGroup);
+	}
+
+	// .unnamed_block
+	{
+		const ResourceDefinition::Node::SharedPtr	buffer		(new ResourceDefinition::InterfaceBlock(binding, false));
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "unnamed_block", "Unnamed block");
+
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, buffer, blockGroup);
+	}
+
+	// .block_array
+	{
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(binding));
+		const ResourceDefinition::Node::SharedPtr	buffer			(new ResourceDefinition::InterfaceBlock(arrayElement, true));
+		tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "block_array", "Block array");
+
+		targetGroup->addChild(blockGroup);
+
+		blockContentGenerator(context, buffer, blockGroup);
+	}
+}
+
+static void generateBufferVariableResourceListBlockContentsProxy (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup)
+{
+	generateBufferBackedResourceListBlockContentCases(context, parentStructure, targetGroup, PROGRAMINTERFACE_BUFFER_VARIABLE, 4);
+}
+
+static void generateBufferVariableArraySizeSubCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup, ProgramResourcePropFlags targetProp, bool sizedArray, bool extendedCases)
+{
+	const ProgramResourceQueryTestTarget	queryTarget		(PROGRAMINTERFACE_BUFFER_VARIABLE, targetProp);
+	tcu::TestCaseGroup*						aggregateGroup;
+
+	// .types
+	if (extendedCases)
+	{
+		tcu::TestCaseGroup* const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
+		targetGroup->addChild(blockGroup);
+
+		generateVariableCases(context, parentStructure, blockGroup, queryTarget, (sizedArray) ? (2) : (1), false);
+	}
+
+	// .aggregates
+	if (extendedCases)
+	{
+		aggregateGroup = new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "Aggregate types");
+		targetGroup->addChild(aggregateGroup);
+	}
+	else
+		aggregateGroup = targetGroup;
+
+	// .float_*
+	generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, aggregateGroup, queryTarget.interface, glu::TYPE_FLOAT, (extendedCases && sizedArray) ? (2) : (1), !extendedCases);
+
+	// .bool_*
+	generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, aggregateGroup, queryTarget.interface, glu::TYPE_BOOL, (extendedCases && sizedArray) ? (1) : (0), !extendedCases);
+
+	// .bvec3_*
+	generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, aggregateGroup, queryTarget.interface, glu::TYPE_BOOL_VEC3, (extendedCases && sizedArray) ? (2) : (1), !extendedCases);
+
+	// .vec4_*
+	generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, aggregateGroup, queryTarget.interface, glu::TYPE_FLOAT_VEC4, (extendedCases && sizedArray) ? (2) : (1), !extendedCases);
+
+	// .ivec2_*
+	generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, aggregateGroup, queryTarget.interface, glu::TYPE_INT_VEC2, (extendedCases && sizedArray) ? (2) : (1), !extendedCases);
+}
+
+template <ProgramResourcePropFlags TargetProp>
+static void generateBufferVariableArrayCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* const targetGroup)
+{
+	const ProgramResourceQueryTestTarget	queryTarget			(PROGRAMINTERFACE_BUFFER_VARIABLE, TargetProp);
+	const bool								namedNonArrayBlock	= static_cast<const ResourceDefinition::InterfaceBlock*>(parentStructure.get())->m_named && parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;
+
+	// .non_array
+	if (namedNonArrayBlock)
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "non_array", "Non-array target");
+		targetGroup->addChild(blockGroup);
+
+		generateVariableCases(context, parentStructure, blockGroup, queryTarget, 1, false);
+	}
+
+	// .sized
+	{
+		const ResourceDefinition::Node::SharedPtr	sized		(new ResourceDefinition::ArrayElement(parentStructure));
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "sized", "Sized target");
+		targetGroup->addChild(blockGroup);
+
+		generateBufferVariableArraySizeSubCases(context, sized, blockGroup, TargetProp, true, namedNonArrayBlock);
+	}
+
+	// .unsized
+	{
+		const ResourceDefinition::Node::SharedPtr	unsized		(new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "unsized", "Unsized target");
+		targetGroup->addChild(blockGroup);
+
+		generateBufferVariableArraySizeSubCases(context, unsized, blockGroup, TargetProp, false, namedNonArrayBlock);
+	}
+}
+
+static void generateBufferVariableBlockIndexCases (Context& context, tcu::TestCaseGroup* const targetGroup)
+{
+	const ResourceDefinition::Node::SharedPtr	program			(new ResourceDefinition::Program());
+	const ResourceDefinition::Node::SharedPtr	shader			(new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES));
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(shader));
+	const ResourceDefinition::Node::SharedPtr	bufferStorage	(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
+	const ResourceDefinition::Node::SharedPtr	binding			(new ResourceDefinition::LayoutQualifier(bufferStorage, glu::Layout(-1, 0)));
+
+	// .named_block
+	{
+		const ResourceDefinition::Node::SharedPtr	buffer		(new ResourceDefinition::InterfaceBlock(binding, true));
+		const ResourceDefinition::Node::SharedPtr	variable	(new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_BLOCK_INDEX), "named_block"));
+	}
+
+	// .unnamed_block
+	{
+		const ResourceDefinition::Node::SharedPtr	buffer		(new ResourceDefinition::InterfaceBlock(binding, false));
+		const ResourceDefinition::Node::SharedPtr	variable	(new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_BLOCK_INDEX), "unnamed_block"));
+	}
+
+	// .block_array
+	{
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(binding));
+		const ResourceDefinition::Node::SharedPtr	buffer			(new ResourceDefinition::InterfaceBlock(arrayElement, true));
+		const ResourceDefinition::Node::SharedPtr	variable		(new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_BLOCK_INDEX), "block_array"));
+	}
+}
+
+static void generateBufferVariableMatrixCaseBlocks (Context& context, tcu::TestCaseGroup* const targetGroup, void (*blockContentGenerator)(Context&, const ResourceDefinition::Node::SharedPtr&, tcu::TestCaseGroup*, bool))
+{
+	static const struct
+	{
+		const char*			name;
+		const char*			description;
+		bool				namedBlock;
+		bool				extendedBasicTypeCases;
+		glu::MatrixOrder	order;
+	} children[] =
+	{
+		{ "named_block",				"Named uniform block",		true,	true,	glu::MATRIXORDER_LAST			},
+		{ "named_block_row_major",		"Named uniform block",		true,	false,	glu::MATRIXORDER_ROW_MAJOR		},
+		{ "named_block_col_major",		"Named uniform block",		true,	false,	glu::MATRIXORDER_COLUMN_MAJOR	},
+		{ "unnamed_block",				"Unnamed uniform block",	false,	false,	glu::MATRIXORDER_LAST			},
+		{ "unnamed_block_row_major",	"Unnamed uniform block",	false,	false,	glu::MATRIXORDER_ROW_MAJOR		},
+		{ "unnamed_block_col_major",	"Unnamed uniform block",	false,	false,	glu::MATRIXORDER_COLUMN_MAJOR	},
+	};
+
+	const ResourceDefinition::Node::SharedPtr	program			(new ResourceDefinition::Program());
+	const ResourceDefinition::Node::SharedPtr	shader			(new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES));
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(shader));
+	const ResourceDefinition::Node::SharedPtr	buffer			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
+
+	for (int childNdx = 0; childNdx < (int)DE_LENGTH_OF_ARRAY(children); ++childNdx)
+	{
+		ResourceDefinition::Node::SharedPtr	parentStructure	= buffer;
+		tcu::TestCaseGroup* const			blockGroup		= new TestCaseGroup(context, children[childNdx].name, children[childNdx].description);
+
+		targetGroup->addChild(blockGroup);
+
+		if (children[childNdx].order != glu::MATRIXORDER_LAST)
+		{
+			glu::Layout layout;
+			layout.matrixOrder = children[childNdx].order;
+			parentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(parentStructure, layout));
+		}
+
+		parentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::InterfaceBlock(parentStructure, children[childNdx].namedBlock));
+
+		blockContentGenerator(context, parentStructure, blockGroup, children[childNdx].extendedBasicTypeCases);
+	}
+}
+
+static void generateBufferVariableMatrixVariableBasicTypeCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, ProgramResourcePropFlags targetProp)
+{
+	// all matrix types and some non-matrix
+
+	static const glu::DataType variableTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_FLOAT_MAT2,
+		glu::TYPE_FLOAT_MAT2X3,
+		glu::TYPE_FLOAT_MAT2X4,
+		glu::TYPE_FLOAT_MAT3X2,
+		glu::TYPE_FLOAT_MAT3,
+		glu::TYPE_FLOAT_MAT3X4,
+		glu::TYPE_FLOAT_MAT4X2,
+		glu::TYPE_FLOAT_MAT4X3,
+		glu::TYPE_FLOAT_MAT4,
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
+	{
+		const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, variableTypes[ndx]));
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, targetProp)));
+	}
+}
+
+static void generateBufferVariableMatrixVariableCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, ProgramResourcePropFlags targetProp)
+{
+	// Basic aggregates
+	generateBufferBackedVariableAggregateTypeCases(context, parentStructure, targetGroup, PROGRAMINTERFACE_BUFFER_VARIABLE, targetProp, glu::TYPE_FLOAT_MAT3X2, "", 2);
+
+	// Unsized array
+	{
+		const ResourceDefinition::Node::SharedPtr	unsized		(new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
+		const ResourceDefinition::Node::SharedPtr	variable	(new ResourceDefinition::Variable(unsized, glu::TYPE_FLOAT_MAT3X2));
+
+		targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, targetProp), "var_unsized_array"));
+	}
+}
+
+template <ProgramResourcePropFlags TargetProp>
+static void generateBufferVariableMatrixCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, bool extendedTypeCases)
+{
+	// .types
+	if (extendedTypeCases)
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "types", "Types");
+		targetGroup->addChild(blockGroup);
+		generateBufferVariableMatrixVariableBasicTypeCases(context, parentStructure, blockGroup, TargetProp);
+	}
+
+	// .no_qualifier
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "no_qualifier", "No qualifier");
+		targetGroup->addChild(blockGroup);
+		generateBufferVariableMatrixVariableCases(context, parentStructure, blockGroup, TargetProp);
+	}
+
+	// .column_major
+	{
+		const ResourceDefinition::Node::SharedPtr matrixOrder(new ResourceDefinition::LayoutQualifier(parentStructure, glu::Layout(-1, -1, -1, glu::FORMATLAYOUT_LAST, glu::MATRIXORDER_COLUMN_MAJOR)));
+
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "column_major", "Column major qualifier");
+		targetGroup->addChild(blockGroup);
+		generateBufferVariableMatrixVariableCases(context, matrixOrder, blockGroup, TargetProp);
+	}
+
+	// .row_major
+	{
+		const ResourceDefinition::Node::SharedPtr matrixOrder(new ResourceDefinition::LayoutQualifier(parentStructure, glu::Layout(-1, -1, -1, glu::FORMATLAYOUT_LAST, glu::MATRIXORDER_ROW_MAJOR)));
+
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "row_major", "Row major qualifier");
+		targetGroup->addChild(blockGroup);
+		generateBufferVariableMatrixVariableCases(context, matrixOrder, blockGroup, TargetProp);
+	}
+}
+
+static void generateBufferVariableNameLengthCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup)
+{
+	// .sized
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "sized", "Sized target");
+		targetGroup->addChild(blockGroup);
+
+		generateBufferBackedVariableAggregateTypeCases(context, parentStructure, blockGroup, PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_NAME_LENGTH, glu::TYPE_FLOAT, "", 3);
+	}
+
+	// .unsized
+	{
+		const ResourceDefinition::Node::SharedPtr	unsized		(new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "unsized", "Unsized target");
+		targetGroup->addChild(blockGroup);
+
+		generateBufferBackedVariableAggregateTypeCases(context, unsized, blockGroup, PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_NAME_LENGTH, glu::TYPE_FLOAT, "", 2);
+	}
+}
+
+static void generateBufferVariableOffsetCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup)
+{
+	// .sized
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "sized", "Sized target");
+		targetGroup->addChild(blockGroup);
+
+		generateBufferBackedVariableAggregateTypeCases(context, parentStructure, blockGroup, PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_OFFSET, glu::TYPE_FLOAT, "", 3);
+	}
+
+	// .unsized
+	{
+		const ResourceDefinition::Node::SharedPtr	unsized		(new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "unsized", "Unsized target");
+		targetGroup->addChild(blockGroup);
+
+		generateBufferBackedVariableAggregateTypeCases(context, unsized, blockGroup, PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_OFFSET, glu::TYPE_FLOAT, "", 2);
+	}
+}
+
+static void generateBufferVariableReferencedByBlockContents (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, int expandLevel)
+{
+	DE_UNREF(expandLevel);
+
+	const ProgramResourceQueryTestTarget		queryTarget		(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER);
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(parentStructure));
+	const ResourceDefinition::Node::SharedPtr	storage			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
+	const bool									singleShaderCase	= parentStructure->getType() == ResourceDefinition::Node::TYPE_SHADER;
+
+	// .named_block
+	{
+		const ResourceDefinition::Node::SharedPtr	buffer		(new ResourceDefinition::InterfaceBlock(storage, true));
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "named_block", "Named block");
+
+		targetGroup->addChild(blockGroup);
+
+		generateBufferReferencedByShaderInterfaceBlockCases(context, buffer, blockGroup, queryTarget, singleShaderCase);
+	}
+
+	// .unnamed_block
+	{
+		const ResourceDefinition::Node::SharedPtr	buffer		(new ResourceDefinition::InterfaceBlock(storage, false));
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "unnamed_block", "Unnamed block");
+
+		targetGroup->addChild(blockGroup);
+
+		generateBufferReferencedByShaderInterfaceBlockCases(context, buffer, blockGroup, queryTarget, false);
+	}
+
+	// .block_array
+	{
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(storage));
+		const ResourceDefinition::Node::SharedPtr	buffer			(new ResourceDefinition::InterfaceBlock(arrayElement, true));
+		tcu::TestCaseGroup* const					blockGroup	= new TestCaseGroup(context, "block_array", "Block array");
+
+		targetGroup->addChild(blockGroup);
+
+		generateBufferReferencedByShaderInterfaceBlockCases(context, buffer, blockGroup, queryTarget, false);
+	}
+}
+
+template <ProgramResourcePropFlags TargetProp>
+static void generateBufferVariableTopLevelCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup)
+{
+	// basic and aggregate types
+	generateBufferBackedVariableAggregateTypeCases(context, parentStructure, targetGroup, PROGRAMINTERFACE_BUFFER_VARIABLE, TargetProp, glu::TYPE_FLOAT_VEC4, "", 3);
+
+	// basic and aggregate types in an unsized array
+	{
+		const ResourceDefinition::Node::SharedPtr unsized(new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
+
+		generateBufferBackedVariableAggregateTypeCases(context, unsized, targetGroup, PROGRAMINTERFACE_BUFFER_VARIABLE, TargetProp, glu::TYPE_FLOAT_VEC4, "_unsized_array", 2);
+	}
+}
+
+static void generateBufferVariableTypeBasicTypeCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, int expandLevel)
+{
+	static const struct
+	{
+		int				level;
+		glu::DataType	dataType;
+	} variableTypes[] =
+	{
+		{ 0,	glu::TYPE_FLOAT			},
+		{ 1,	glu::TYPE_INT			},
+		{ 1,	glu::TYPE_UINT			},
+		{ 1,	glu::TYPE_BOOL			},
+
+		{ 3,	glu::TYPE_FLOAT_VEC2	},
+		{ 1,	glu::TYPE_FLOAT_VEC3	},
+		{ 1,	glu::TYPE_FLOAT_VEC4	},
+
+		{ 3,	glu::TYPE_INT_VEC2		},
+		{ 2,	glu::TYPE_INT_VEC3		},
+		{ 3,	glu::TYPE_INT_VEC4		},
+
+		{ 3,	glu::TYPE_UINT_VEC2		},
+		{ 2,	glu::TYPE_UINT_VEC3		},
+		{ 3,	glu::TYPE_UINT_VEC4		},
+
+		{ 3,	glu::TYPE_BOOL_VEC2		},
+		{ 2,	glu::TYPE_BOOL_VEC3		},
+		{ 3,	glu::TYPE_BOOL_VEC4		},
+
+		{ 2,	glu::TYPE_FLOAT_MAT2	},
+		{ 3,	glu::TYPE_FLOAT_MAT2X3	},
+		{ 3,	glu::TYPE_FLOAT_MAT2X4	},
+		{ 2,	glu::TYPE_FLOAT_MAT3X2	},
+		{ 2,	glu::TYPE_FLOAT_MAT3	},
+		{ 3,	glu::TYPE_FLOAT_MAT3X4	},
+		{ 2,	glu::TYPE_FLOAT_MAT4X2	},
+		{ 3,	glu::TYPE_FLOAT_MAT4X3	},
+		{ 2,	glu::TYPE_FLOAT_MAT4	},
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
+	{
+		if (variableTypes[ndx].level <= expandLevel)
+		{
+			const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, variableTypes[ndx].dataType));
+			targetGroup->addChild(new ResourceTestCase(context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_TYPE)));
+		}
+	}
+}
+
+static void generateBufferVariableTypeCases (Context& context, const ResourceDefinition::Node::SharedPtr& parentStructure, tcu::TestCaseGroup* targetGroup, int depth = 3)
+{
+	// .basic_type
+	if (depth > 0)
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(context, "basic_type", "Basic type");
+		targetGroup->addChild(blockGroup);
+		generateBufferVariableTypeBasicTypeCases(context, parentStructure, blockGroup, depth);
+	}
+	else
+	{
+		// flatten bottom-level
+		generateBufferVariableTypeBasicTypeCases(context, parentStructure, targetGroup, depth);
+	}
+
+	// .array
+	if (depth > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr	arrayElement	(new ResourceDefinition::ArrayElement(parentStructure));
+		tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "array", "Arrays");
+
+		targetGroup->addChild(blockGroup);
+		generateBufferVariableTypeCases(context, arrayElement, blockGroup, depth-1);
+	}
+
+	// .struct
+	if (depth > 0)
+	{
+		const ResourceDefinition::Node::SharedPtr	structMember	(new ResourceDefinition::StructMember(parentStructure));
+		tcu::TestCaseGroup* const					blockGroup		= new TestCaseGroup(context, "struct", "Structs");
+
+		targetGroup->addChild(blockGroup);
+		generateBufferVariableTypeCases(context, structMember, blockGroup, depth-1);
+	}
+}
+
+static void generateBufferVariableTypeBlock (Context& context, tcu::TestCaseGroup* targetGroup)
+{
+	const ResourceDefinition::Node::SharedPtr	program			(new ResourceDefinition::Program());
+	const ResourceDefinition::Node::SharedPtr	shader			(new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glu::GLSL_VERSION_310_ES));
+	const ResourceDefinition::Node::SharedPtr	defaultBlock	(new ResourceDefinition::DefaultBlock(shader));
+	const ResourceDefinition::Node::SharedPtr	buffer			(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
+	const ResourceDefinition::Node::SharedPtr	block			(new ResourceDefinition::InterfaceBlock(buffer, true));
+
+	generateBufferVariableTypeCases(context, block, targetGroup);
+}
+
+static void generateBufferVariableRandomCase (Context& context, tcu::TestCaseGroup* const targetGroup, int index)
+{
+	de::Random									rnd					(index * 0x12345);
+	const ResourceDefinition::Node::SharedPtr	shader				= generateRandomShaderSet(rnd);
+	const glu::DataType							type				= generateRandomDataType(rnd, true);
+	const glu::Layout							layout				= generateRandomVariableLayout(rnd, type, true);
+	const bool									namedBlock			= rnd.getBool();
+	const ResourceDefinition::Node::SharedPtr	defaultBlock		(new ResourceDefinition::DefaultBlock(shader));
+	const ResourceDefinition::Node::SharedPtr	buffer				(new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
+	ResourceDefinition::Node::SharedPtr			currentStructure	(new ResourceDefinition::LayoutQualifier(buffer, generateRandomBufferBlockLayout(rnd)));
+
+	if (namedBlock && rnd.getBool())
+		currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::ArrayElement(currentStructure));
+	currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::InterfaceBlock(currentStructure, namedBlock));
+
+	currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(currentStructure, layout));
+	currentStructure = generateRandomVariableDefinition(rnd, currentStructure, type, layout, true);
+
+	targetGroup->addChild(new ResourceTestCase(context, currentStructure, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_BUFFER_VARIABLE_MASK), de::toString(index).c_str()));
+}
+
+static void generateBufferVariableRandomCases (Context& context, tcu::TestCaseGroup* const targetGroup)
+{
+	const int numCases = 40;
+
+	for (int ndx = 0; ndx < numCases; ++ndx)
+		generateBufferVariableRandomCase(context, targetGroup, ndx);
+}
+
+class BufferVariableTestGroup : public TestCaseGroup
+{
+public:
+			BufferVariableTestGroup	(Context& context);
+	void	init								(void);
+};
+
+BufferVariableTestGroup::BufferVariableTestGroup (Context& context)
+	: TestCaseGroup(context, "buffer_variable", "Buffer variable")
+{
+}
+
+void BufferVariableTestGroup::init (void)
+{
+	// .resource_list
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "resource_list", "Resource list");
+		addChild(blockGroup);
+		generateBufferVariableBufferCaseBlocks(m_context, blockGroup, generateBufferVariableResourceListBlockContentsProxy);
+	}
+
+	// .array_size
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "array_size", "Array size");
+		addChild(blockGroup);
+		generateBufferVariableBufferCaseBlocks(m_context, blockGroup, generateBufferVariableArrayCases<PROGRAMRESOURCEPROP_ARRAY_SIZE>);
+	}
+
+	// .array_stride
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "array_stride", "Array stride");
+		addChild(blockGroup);
+		generateBufferVariableBufferCaseBlocks(m_context, blockGroup, generateBufferVariableArrayCases<PROGRAMRESOURCEPROP_ARRAY_STRIDE>);
+	}
+
+	// .block_index
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "block_index", "Block index");
+		addChild(blockGroup);
+		generateBufferVariableBlockIndexCases(m_context, blockGroup);
+	}
+
+	// .is_row_major
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "is_row_major", "Is row major");
+		addChild(blockGroup);
+		generateBufferVariableMatrixCaseBlocks(m_context, blockGroup, generateBufferVariableMatrixCases<PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR>);
+	}
+
+	// .matrix_stride
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "matrix_stride", "Matrix stride");
+		addChild(blockGroup);
+		generateBufferVariableMatrixCaseBlocks(m_context, blockGroup, generateBufferVariableMatrixCases<PROGRAMRESOURCEPROP_MATRIX_STRIDE>);
+	}
+
+	// .name_length
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "name_length", "Name length");
+		addChild(blockGroup);
+		generateBufferVariableBufferCaseBlocks(m_context, blockGroup, generateBufferVariableNameLengthCases);
+	}
+
+	// .offset
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "offset", "Offset");
+		addChild(blockGroup);
+		generateBufferVariableBufferCaseBlocks(m_context, blockGroup, generateBufferVariableOffsetCases);
+	}
+
+	// .referenced_by
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "referenced_by", "Referenced by");
+		addChild(blockGroup);
+		generateReferencedByShaderCaseBlocks(m_context, blockGroup, generateBufferVariableReferencedByBlockContents);
+	}
+
+	// .top_level_array_size
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "top_level_array_size", "Top-level array size");
+		addChild(blockGroup);
+		generateBufferVariableBufferCaseBlocks(m_context, blockGroup, generateBufferVariableTopLevelCases<PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_SIZE>);
+	}
+
+	// .top_level_array_stride
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "top_level_array_stride", "Top-level array stride");
+		addChild(blockGroup);
+		generateBufferVariableBufferCaseBlocks(m_context, blockGroup, generateBufferVariableTopLevelCases<PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_STRIDE>);
+	}
+
+	// .type
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "type", "Type");
+		addChild(blockGroup);
+		generateBufferVariableTypeBlock(m_context, blockGroup);
+	}
+
+	// .random
+	{
+		tcu::TestCaseGroup* const blockGroup = new TestCaseGroup(m_context, "random", "Random");
+		addChild(blockGroup);
+		generateBufferVariableRandomCases(m_context, blockGroup);
+	}
+}
+
+} // anonymous
+
+ProgramInterfaceQueryTests::ProgramInterfaceQueryTests (Context& context)
+	: TestCaseGroup(context, "program_interface_query", "Program interface query tests")
+{
+}
+
+ProgramInterfaceQueryTests::~ProgramInterfaceQueryTests (void)
+{
+}
+
+void ProgramInterfaceQueryTests::init (void)
+{
+	// Misc queries
+
+	// .buffer_limited_query
+	{
+		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "buffer_limited_query", "Queries limited by the buffer size");
+
+		addChild(group);
+
+		group->addChild(new ResourceNameBufferLimitCase(m_context, "resource_name_query", "Test GetProgramResourceName with too small a buffer"));
+		group->addChild(new ResourceQueryBufferLimitCase(m_context, "resource_query", "Test GetProgramResourceiv with too small a buffer"));
+	}
+
+	// Interfaces
+
+	// .uniform
+	addChild(new UniformInterfaceTestGroup(m_context));
+
+	// .uniform_block
+	addChild(new BufferBackedBlockInterfaceTestGroup(m_context, glu::STORAGE_UNIFORM));
+
+	// .atomic_counter_buffer
+	addChild(new AtomicCounterTestGroup(m_context));
+
+	// .program_input
+	addChild(new ProgramInputTestGroup(m_context));
+
+	// .program_output
+	addChild(new ProgramOutputTestGroup(m_context));
+
+	// .transform_feedback_varying
+	addChild(new TransformFeedbackVaryingTestGroup(m_context));
+
+	// .buffer_variable
+	addChild(new BufferVariableTestGroup(m_context));
+
+	// .shader_storage_block
+	addChild(new BufferBackedBlockInterfaceTestGroup(m_context, glu::STORAGE_BUFFER));
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fProgramInterfaceQueryTests.hpp b/modules/gles31/functional/es31fProgramInterfaceQueryTests.hpp
new file mode 100644
index 0000000..29621ed
--- /dev/null
+++ b/modules/gles31/functional/es31fProgramInterfaceQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FPROGRAMINTERFACEQUERYTESTS_HPP
+#define _ES31FPROGRAMINTERFACEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Program interface query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ProgramInterfaceQueryTests : public TestCaseGroup
+{
+public:
+								ProgramInterfaceQueryTests	(Context& context);
+								~ProgramInterfaceQueryTests	(void);
+
+	void						init						(void);
+
+private:
+								ProgramInterfaceQueryTests	(const ProgramInterfaceQueryTests& other);
+	ProgramInterfaceQueryTests&	operator=					(const ProgramInterfaceQueryTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FPROGRAMINTERFACEQUERYTESTS_HPP
diff --git a/modules/gles31/functional/es31fProgramUniformTests.cpp b/modules/gles31/functional/es31fProgramUniformTests.cpp
new file mode 100644
index 0000000..02a4db3
--- /dev/null
+++ b/modules/gles31/functional/es31fProgramUniformTests.cpp
@@ -0,0 +1,2091 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief glProgramUniform*() tests.
+ *
+ * \todo [2013-02-26 nuutti] Much duplication between ES2&3 uniform api
+ *							 tests and this. Utilities to glshared?
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fProgramUniformTests.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluVarType.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluDrawUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuCommandLine.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+#include "deSharedPtr.hpp"
+#include "deMemory.h"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include <set>
+#include <cstring>
+
+using namespace glw;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using tcu::ScopedLogSection;
+using glu::ShaderProgram;
+using glu::StructType;
+using de::Random;
+using de::SharedPtr;
+
+typedef bool (* dataTypePredicate)(glu::DataType);
+
+enum
+{
+	MAX_RENDER_WIDTH			= 32,
+	MAX_RENDER_HEIGHT			= 32,
+	MAX_NUM_SAMPLER_UNIFORMS	= 16
+};
+
+static const glu::DataType s_testDataTypes[] =
+{
+	glu::TYPE_FLOAT,
+	glu::TYPE_FLOAT_VEC2,
+	glu::TYPE_FLOAT_VEC3,
+	glu::TYPE_FLOAT_VEC4,
+	glu::TYPE_FLOAT_MAT2,
+	glu::TYPE_FLOAT_MAT2X3,
+	glu::TYPE_FLOAT_MAT2X4,
+	glu::TYPE_FLOAT_MAT3X2,
+	glu::TYPE_FLOAT_MAT3,
+	glu::TYPE_FLOAT_MAT3X4,
+	glu::TYPE_FLOAT_MAT4X2,
+	glu::TYPE_FLOAT_MAT4X3,
+	glu::TYPE_FLOAT_MAT4,
+
+	glu::TYPE_INT,
+	glu::TYPE_INT_VEC2,
+	glu::TYPE_INT_VEC3,
+	glu::TYPE_INT_VEC4,
+
+	glu::TYPE_UINT,
+	glu::TYPE_UINT_VEC2,
+	glu::TYPE_UINT_VEC3,
+	glu::TYPE_UINT_VEC4,
+
+	glu::TYPE_BOOL,
+	glu::TYPE_BOOL_VEC2,
+	glu::TYPE_BOOL_VEC3,
+	glu::TYPE_BOOL_VEC4,
+
+	glu::TYPE_SAMPLER_2D,
+	glu::TYPE_SAMPLER_CUBE
+	// \note We don't test all sampler types here.
+};
+
+static inline int getGLInt (const glw::Functions& funcs, const deUint32 name)
+{
+	int val = -1;
+	funcs.getIntegerv(name, &val);
+	return val;
+}
+
+static inline tcu::Vec4 vec4FromPtr (const float* const ptr)
+{
+	tcu::Vec4 result;
+	for (int i = 0; i < 4; i++)
+		result[i] = ptr[i];
+	return result;
+}
+
+static inline string beforeLast (const string& str, const char c)
+{
+	return str.substr(0, str.find_last_of(c));
+}
+
+static inline void fillWithColor (const tcu::PixelBufferAccess& access, const tcu::Vec4& color)
+{
+	for (int z = 0; z < access.getDepth(); z++)
+	for (int y = 0; y < access.getHeight(); y++)
+	for (int x = 0; x < access.getWidth(); x++)
+		access.setPixel(color, x, y, z);
+}
+
+static inline int getSamplerNumLookupDimensions (const glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_SAMPLER_2D:
+		case glu::TYPE_INT_SAMPLER_2D:
+		case glu::TYPE_UINT_SAMPLER_2D:
+			return 2;
+
+		case glu::TYPE_SAMPLER_3D:
+		case glu::TYPE_INT_SAMPLER_3D:
+		case glu::TYPE_UINT_SAMPLER_3D:
+		case glu::TYPE_SAMPLER_2D_SHADOW:
+		case glu::TYPE_SAMPLER_2D_ARRAY:
+		case glu::TYPE_INT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_SAMPLER_CUBE:
+		case glu::TYPE_INT_SAMPLER_CUBE:
+		case glu::TYPE_UINT_SAMPLER_CUBE:
+			return 3;
+
+		case glu::TYPE_SAMPLER_CUBE_SHADOW:
+		case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
+			return 4;
+
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static inline glu::DataType getSamplerLookupReturnType (const glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_SAMPLER_2D:
+		case glu::TYPE_SAMPLER_CUBE:
+		case glu::TYPE_SAMPLER_2D_ARRAY:
+		case glu::TYPE_SAMPLER_3D:
+			return glu::TYPE_FLOAT_VEC4;
+
+		case glu::TYPE_UINT_SAMPLER_2D:
+		case glu::TYPE_UINT_SAMPLER_CUBE:
+		case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_UINT_SAMPLER_3D:
+			return glu::TYPE_UINT_VEC4;
+
+		case glu::TYPE_INT_SAMPLER_2D:
+		case glu::TYPE_INT_SAMPLER_CUBE:
+		case glu::TYPE_INT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_INT_SAMPLER_3D:
+			return glu::TYPE_INT_VEC4;
+
+		case glu::TYPE_SAMPLER_2D_SHADOW:
+		case glu::TYPE_SAMPLER_CUBE_SHADOW:
+		case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
+			return glu::TYPE_FLOAT;
+
+		default:
+			DE_ASSERT(false);
+			return glu::TYPE_LAST;
+	}
+}
+
+template<glu::DataType T>
+static bool dataTypeEquals (const glu::DataType t)
+{
+	return t == T;
+}
+
+template<int N>
+static bool dataTypeIsMatrixWithNRows (const glu::DataType t)
+{
+	return glu::isDataTypeMatrix(t) && glu::getDataTypeMatrixNumRows(t) == N;
+}
+
+static bool typeContainsMatchingBasicType (const glu::VarType& type, const dataTypePredicate predicate)
+{
+	if (type.isBasicType())
+		return predicate(type.getBasicType());
+	else if (type.isArrayType())
+		return typeContainsMatchingBasicType(type.getElementType(), predicate);
+	else
+	{
+		DE_ASSERT(type.isStructType());
+		const StructType& structType = *type.getStructPtr();
+		for (int i = 0; i < structType.getNumMembers(); i++)
+			if (typeContainsMatchingBasicType(structType.getMember(i).getType(), predicate))
+				return true;
+		return false;
+	}
+}
+
+static void getDistinctSamplerTypes (vector<glu::DataType>& dst, const glu::VarType& type)
+{
+	if (type.isBasicType())
+	{
+		const glu::DataType basicType = type.getBasicType();
+		if (glu::isDataTypeSampler(basicType) && std::find(dst.begin(), dst.end(), basicType) == dst.end())
+			dst.push_back(basicType);
+	}
+	else if (type.isArrayType())
+		getDistinctSamplerTypes(dst, type.getElementType());
+	else
+	{
+		DE_ASSERT(type.isStructType());
+		const StructType& structType = *type.getStructPtr();
+		for (int i = 0; i < structType.getNumMembers(); i++)
+			getDistinctSamplerTypes(dst, structType.getMember(i).getType());
+	}
+}
+
+static int getNumSamplersInType (const glu::VarType& type)
+{
+	if (type.isBasicType())
+		return glu::isDataTypeSampler(type.getBasicType()) ? 1 : 0;
+	else if (type.isArrayType())
+		return getNumSamplersInType(type.getElementType()) * type.getArraySize();
+	else
+	{
+		DE_ASSERT(type.isStructType());
+		const StructType& structType = *type.getStructPtr();
+		int sum = 0;
+		for (int i = 0; i < structType.getNumMembers(); i++)
+			sum += getNumSamplersInType(structType.getMember(i).getType());
+		return sum;
+	}
+}
+
+namespace
+{
+
+struct VarValue
+{
+	glu::DataType type;
+
+	union
+	{
+		float		floatV[4*4]; // At most mat4. \note Matrices here are column-major.
+		deInt32		intV[4];
+		deUint32	uintV[4];
+		bool		boolV[4];
+		struct
+		{
+			int		unit;
+			union
+			{
+				float		floatV[4];
+				deInt32		intV[4];
+				deUint32	uintV[4];
+			} fillColor;
+		} samplerV;
+	} val;
+};
+
+enum CaseShaderType
+{
+	CASESHADERTYPE_VERTEX = 0,
+	CASESHADERTYPE_FRAGMENT,
+	CASESHADERTYPE_BOTH,
+
+	CASESHADERTYPE_LAST
+};
+
+struct Uniform
+{
+	string			name;
+	glu::VarType	type;
+
+	Uniform (const char* const name_, const glu::VarType& type_) : name(name_), type(type_) {}
+};
+
+// A set of uniforms, along with related struct types.
+class UniformCollection
+{
+public:
+	int					getNumUniforms		(void) const					{ return (int)m_uniforms.size();	}
+	int					getNumStructTypes	(void) const					{ return (int)m_structTypes.size();	}
+	Uniform&			getUniform			(const int ndx)					{ return m_uniforms[ndx];			}
+	const Uniform&		getUniform			(const int ndx) const			{ return m_uniforms[ndx];			}
+	const StructType*	getStructType		(const int ndx) const			{ return m_structTypes[ndx];		}
+	void				addUniform			(const Uniform& uniform)		{ m_uniforms.push_back(uniform);	}
+	void				addStructType		(const StructType* const type)	{ m_structTypes.push_back(type);	}
+
+	UniformCollection	(void) {}
+	~UniformCollection	(void)
+	{
+		for (int i = 0; i < (int)m_structTypes.size(); i++)
+			delete m_structTypes[i];
+	}
+
+	// Add the contents of m_uniforms and m_structTypes to receiver, and remove them from this one.
+	// \note receiver takes ownership of the struct types.
+	void moveContents (UniformCollection& receiver)
+	{
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			receiver.addUniform(m_uniforms[i]);
+		m_uniforms.clear();
+
+		for (int i = 0; i < (int)m_structTypes.size(); i++)
+			receiver.addStructType(m_structTypes[i]);
+		m_structTypes.clear();
+	}
+
+	bool containsMatchingBasicType (const dataTypePredicate predicate) const
+	{
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			if (typeContainsMatchingBasicType(m_uniforms[i].type, predicate))
+				return true;
+		return false;
+	}
+
+	vector<glu::DataType> getSamplerTypes (void) const
+	{
+		vector<glu::DataType> samplerTypes;
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			getDistinctSamplerTypes(samplerTypes, m_uniforms[i].type);
+		return samplerTypes;
+	}
+
+	bool containsSeveralSamplerTypes (void) const
+	{
+		return getSamplerTypes().size() > 1;
+	}
+
+	int getNumSamplers (void) const
+	{
+		int sum = 0;
+		for (int i = 0; i < (int)m_uniforms.size(); i++)
+			sum += getNumSamplersInType(m_uniforms[i].type);
+		return sum;
+	}
+
+	static UniformCollection* basic (const glu::DataType type, const char* const nameSuffix = "")
+	{
+		UniformCollection* const	res		= new UniformCollection;
+		const glu::Precision		prec	= glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		res->m_uniforms.push_back(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(type, prec)));
+		return res;
+	}
+
+	static UniformCollection* basicArray (const glu::DataType type, const char* const nameSuffix = "")
+	{
+		UniformCollection* const	res		= new UniformCollection;
+		const glu::Precision		prec	= glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		res->m_uniforms.push_back(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(glu::VarType(type, prec), 3)));
+		return res;
+	}
+
+	static UniformCollection* basicStruct (const glu::DataType type0, const glu::DataType type1, const bool containsArrays, const char* const nameSuffix = "")
+	{
+		UniformCollection* const	res		= new UniformCollection;
+		const glu::Precision		prec0	= glu::isDataTypeBoolOrBVec(type0) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		const glu::Precision		prec1	= glu::isDataTypeBoolOrBVec(type1) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+
+		StructType* const structType = new StructType((string("structType") + nameSuffix).c_str());
+		structType->addMember("m0", glu::VarType(type0, prec0));
+		structType->addMember("m1", glu::VarType(type1, prec1));
+		if (containsArrays)
+		{
+			structType->addMember("m2", glu::VarType(glu::VarType(type0, prec0), 3));
+			structType->addMember("m3", glu::VarType(glu::VarType(type1, prec1), 3));
+		}
+
+		res->addStructType(structType);
+		res->addUniform(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(structType)));
+
+		return res;
+	}
+
+	static UniformCollection* structInArray (const glu::DataType type0, const glu::DataType type1, const bool containsArrays, const char* const nameSuffix = "")
+	{
+		UniformCollection* const res = basicStruct(type0, type1, containsArrays, nameSuffix);
+		res->getUniform(0).type = glu::VarType(res->getUniform(0).type, 3);
+		return res;
+	}
+
+	static UniformCollection* nestedArraysStructs (const glu::DataType type0, const glu::DataType type1, const char* const nameSuffix = "")
+	{
+		UniformCollection* const res		= new UniformCollection;
+		const glu::Precision prec0			= glu::isDataTypeBoolOrBVec(type0) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		const glu::Precision prec1			= glu::isDataTypeBoolOrBVec(type1) ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP;
+		StructType* const structType		= new StructType((string("structType") + nameSuffix).c_str());
+		StructType* const subStructType		= new StructType((string("subStructType") + nameSuffix).c_str());
+		StructType* const subSubStructType	= new StructType((string("subSubStructType") + nameSuffix).c_str());
+
+		subSubStructType->addMember("mss0", glu::VarType(type0, prec0));
+		subSubStructType->addMember("mss1", glu::VarType(type1, prec1));
+
+		subStructType->addMember("ms0", glu::VarType(type1, prec1));
+		subStructType->addMember("ms1", glu::VarType(glu::VarType(type0, prec0), 2));
+		subStructType->addMember("ms2", glu::VarType(glu::VarType(subSubStructType), 2));
+
+		structType->addMember("m0", glu::VarType(type0, prec0));
+		structType->addMember("m1", glu::VarType(subStructType));
+		structType->addMember("m2", glu::VarType(type1, prec1));
+
+		res->addStructType(subSubStructType);
+		res->addStructType(subStructType);
+		res->addStructType(structType);
+
+		res->addUniform(Uniform((string("u_var") + nameSuffix).c_str(), glu::VarType(structType)));
+
+		return res;
+	}
+
+	static UniformCollection* multipleBasic (const char* const nameSuffix = "")
+	{
+		static const glu::DataType	types[]	= { glu::TYPE_FLOAT, glu::TYPE_INT_VEC3, glu::TYPE_UINT_VEC4, glu::TYPE_FLOAT_MAT3, glu::TYPE_BOOL_VEC2 };
+		UniformCollection* const	res		= new UniformCollection;
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(types); i++)
+		{
+			UniformCollection* const sub = basic(types[i], ("_" + de::toString(i) + nameSuffix).c_str());
+			sub->moveContents(*res);
+			delete sub;
+		}
+
+		return res;
+	}
+
+	static UniformCollection* multipleBasicArray (const char* const nameSuffix = "")
+	{
+		static const glu::DataType	types[]	= { glu::TYPE_FLOAT, glu::TYPE_INT_VEC3, glu::TYPE_BOOL_VEC2 };
+		UniformCollection* const	res		= new UniformCollection;
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(types); i++)
+		{
+			UniformCollection* const sub = basicArray(types[i], ("_" + de::toString(i) + nameSuffix).c_str());
+			sub->moveContents(*res);
+			delete sub;
+		}
+
+		return res;
+	}
+
+	static UniformCollection* multipleNestedArraysStructs (const char* const nameSuffix = "")
+	{
+		static const glu::DataType	types0[]	= { glu::TYPE_FLOAT,		glu::TYPE_INT,		glu::TYPE_BOOL_VEC4 };
+		static const glu::DataType	types1[]	= { glu::TYPE_FLOAT_VEC4,	glu::TYPE_INT_VEC4,	glu::TYPE_BOOL };
+		UniformCollection* const	res			= new UniformCollection;
+
+		DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types0) == DE_LENGTH_OF_ARRAY(types1));
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(types0); i++)
+		{
+			UniformCollection* const sub = nestedArraysStructs(types0[i], types1[i], ("_" + de::toString(i) + nameSuffix).c_str());
+			sub->moveContents(*res);
+			delete sub;
+		}
+
+		return res;
+	}
+
+private:
+	// \note Copying these would be cumbersome, since deep-copying both m_uniforms and m_structTypes
+	// would mean that we'd need to update pointers from uniforms to point to the new structTypes.
+	// When the same UniformCollection is needed in several places, a SharedPtr is used instead.
+								UniformCollection	(const UniformCollection&); // Not allowed.
+	UniformCollection&			operator=			(const UniformCollection&); // Not allowed.
+
+	vector<Uniform>				m_uniforms;
+	vector<const StructType*>	m_structTypes;
+};
+
+}; // anonymous
+
+static VarValue getSamplerFillValue (const VarValue& sampler)
+{
+	DE_ASSERT(glu::isDataTypeSampler(sampler.type));
+
+	VarValue result;
+	result.type = getSamplerLookupReturnType(sampler.type);
+
+	switch (result.type)
+	{
+		case glu::TYPE_FLOAT_VEC4:
+			for (int i = 0; i < 4; i++)
+				result.val.floatV[i] = sampler.val.samplerV.fillColor.floatV[i];
+			break;
+		case glu::TYPE_UINT_VEC4:
+			for (int i = 0; i < 4; i++)
+				result.val.uintV[i] = sampler.val.samplerV.fillColor.uintV[i];
+			break;
+		case glu::TYPE_INT_VEC4:
+			for (int i = 0; i < 4; i++)
+				result.val.intV[i] = sampler.val.samplerV.fillColor.intV[i];
+			break;
+		case glu::TYPE_FLOAT:
+			result.val.floatV[0] = sampler.val.samplerV.fillColor.floatV[0];
+			break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	return result;
+}
+
+static VarValue getSamplerUnitValue (const VarValue& sampler)
+{
+	DE_ASSERT(glu::isDataTypeSampler(sampler.type));
+
+	VarValue result;
+	result.type = glu::TYPE_INT;
+	result.val.intV[0] = sampler.val.samplerV.unit;
+
+	return result;
+}
+
+static glu::DataType getDataTypeTransposedMatrix (const glu::DataType original)
+{
+	return glu::getDataTypeMatrix(glu::getDataTypeMatrixNumRows(original), glu::getDataTypeMatrixNumColumns(original));
+}
+
+static VarValue getTransposeMatrix (const VarValue& original)
+{
+	DE_ASSERT(glu::isDataTypeMatrix(original.type));
+
+	const int	rows = glu::getDataTypeMatrixNumRows(original.type);
+	const int	cols = glu::getDataTypeMatrixNumColumns(original.type);
+	VarValue	result;
+	result.type = getDataTypeTransposedMatrix(original.type);
+
+	for (int i = 0; i < rows; i++)
+	for (int j = 0; j < cols; j++)
+		result.val.floatV[i*cols + j] = original.val.floatV[j*rows + i];
+
+	return result;
+}
+
+static string shaderVarValueStr (const VarValue& value)
+{
+	const int			numElems = glu::getDataTypeScalarSize(value.type);
+	std::ostringstream	result;
+
+	if (numElems > 1)
+		result << glu::getDataTypeName(value.type) << "(";
+
+	for (int i = 0; i < numElems; i++)
+	{
+		if (i > 0)
+			result << ", ";
+
+		if (glu::isDataTypeFloatOrVec(value.type) || glu::isDataTypeMatrix(value.type))
+			result << de::floatToString(value.val.floatV[i], 2);
+		else if (glu::isDataTypeIntOrIVec((value.type)))
+			result << de::toString(value.val.intV[i]);
+		else if (glu::isDataTypeUintOrUVec((value.type)))
+			result << de::toString(value.val.uintV[i]) << "u";
+		else if (glu::isDataTypeBoolOrBVec((value.type)))
+			result << (value.val.boolV[i] ? "true" : "false");
+		else if (glu::isDataTypeSampler((value.type)))
+			result << shaderVarValueStr(getSamplerFillValue(value));
+		else
+			DE_ASSERT(false);
+	}
+
+	if (numElems > 1)
+		result << ")";
+
+	return result.str();
+}
+
+static string apiVarValueStr (const VarValue& value)
+{
+	const int			numElems = glu::getDataTypeScalarSize(value.type);
+	std::ostringstream	result;
+
+	if (numElems > 1)
+		result << "(";
+
+	for (int i = 0; i < numElems; i++)
+	{
+		if (i > 0)
+			result << ", ";
+
+		if (glu::isDataTypeFloatOrVec(value.type) || glu::isDataTypeMatrix(value.type))
+			result << de::floatToString(value.val.floatV[i], 2);
+		else if (glu::isDataTypeIntOrIVec((value.type)))
+			result << de::toString(value.val.intV[i]);
+		else if (glu::isDataTypeUintOrUVec((value.type)))
+			result << de::toString(value.val.uintV[i]);
+		else if (glu::isDataTypeBoolOrBVec((value.type)))
+			result << (value.val.boolV[i] ? "true" : "false");
+		else if (glu::isDataTypeSampler((value.type)))
+			result << value.val.samplerV.unit;
+		else
+			DE_ASSERT(false);
+	}
+
+	if (numElems > 1)
+		result << ")";
+
+	return result.str();
+}
+
+static VarValue generateRandomVarValue (const glu::DataType type, Random& rnd, int samplerUnit = -1 /* Used if type is a sampler type. \note Samplers' unit numbers are not randomized. */)
+{
+	const int	numElems = glu::getDataTypeScalarSize(type);
+	VarValue	result;
+	result.type = type;
+
+	DE_ASSERT((samplerUnit >= 0) == (glu::isDataTypeSampler(type)));
+
+	if (glu::isDataTypeFloatOrVec(type) || glu::isDataTypeMatrix(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.floatV[i] = rnd.getFloat(-10.0f, 10.0f);
+	}
+	else if (glu::isDataTypeIntOrIVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.intV[i] = rnd.getInt(-10, 10);
+	}
+	else if (glu::isDataTypeUintOrUVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.uintV[i] = (deUint32)rnd.getInt(0, 10);
+	}
+	else if (glu::isDataTypeBoolOrBVec(type))
+	{
+		for (int i = 0; i < numElems; i++)
+			result.val.boolV[i] = rnd.getBool();
+	}
+	else if (glu::isDataTypeSampler(type))
+	{
+		const glu::DataType		texResultType		= getSamplerLookupReturnType(type);
+		const glu::DataType		texResultScalarType	= glu::getDataTypeScalarType(texResultType);
+		const int				texResultNumDims	= glu::getDataTypeScalarSize(texResultType);
+
+		result.val.samplerV.unit = samplerUnit;
+
+		for (int i = 0; i < texResultNumDims; i++)
+		{
+			switch (texResultScalarType)
+			{
+				case glu::TYPE_FLOAT:	result.val.samplerV.fillColor.floatV[i]		= rnd.getFloat(0.0f, 1.0f);		break;
+				case glu::TYPE_INT:		result.val.samplerV.fillColor.intV[i]		= rnd.getInt(-10, 10);			break;
+				case glu::TYPE_UINT:	result.val.samplerV.fillColor.uintV[i]		= (deUint32)rnd.getInt(0, 10);	break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+	return result;
+}
+
+static bool apiVarValueEquals (const VarValue& a, const VarValue& b)
+{
+	const int		size			= glu::getDataTypeScalarSize(a.type);
+	const float		floatThreshold	= 0.05f;
+
+	DE_ASSERT(a.type == b.type);
+
+	if (glu::isDataTypeFloatOrVec(a.type) || glu::isDataTypeMatrix(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (de::abs(a.val.floatV[i] - b.val.floatV[i]) >= floatThreshold)
+				return false;
+	}
+	else if (glu::isDataTypeIntOrIVec(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (a.val.intV[i] != b.val.intV[i])
+				return false;
+	}
+	else if (glu::isDataTypeUintOrUVec(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (a.val.uintV[i] != b.val.uintV[i])
+				return false;
+	}
+	else if (glu::isDataTypeBoolOrBVec(a.type))
+	{
+		for (int i = 0; i < size; i++)
+			if (a.val.boolV[i] != b.val.boolV[i])
+				return false;
+	}
+	else if (glu::isDataTypeSampler(a.type))
+	{
+		if (a.val.samplerV.unit != b.val.samplerV.unit)
+			return false;
+	}
+	else
+		DE_ASSERT(false);
+
+	return true;
+}
+
+static VarValue getRandomBoolRepresentation (const VarValue& boolValue, const glu::DataType targetScalarType, Random& rnd)
+{
+	DE_ASSERT(glu::isDataTypeBoolOrBVec(boolValue.type));
+
+	const int				size		= glu::getDataTypeScalarSize(boolValue.type);
+	const glu::DataType		targetType	= size == 1 ? targetScalarType : glu::getDataTypeVector(targetScalarType, size);
+	VarValue				result;
+	result.type = targetType;
+
+	switch (targetScalarType)
+	{
+		case glu::TYPE_INT:
+			for (int i = 0; i < size; i++)
+			{
+				if (boolValue.val.boolV[i])
+				{
+					result.val.intV[i] = rnd.getInt(-10, 10);
+					if (result.val.intV[i] == 0)
+						result.val.intV[i] = 1;
+				}
+				else
+					result.val.intV[i] = 0;
+			}
+			break;
+
+		case glu::TYPE_UINT:
+			for (int i = 0; i < size; i++)
+			{
+				if (boolValue.val.boolV[i])
+					result.val.uintV[i] = rnd.getInt(1, 10);
+				else
+					result.val.uintV[i] = 0;
+			}
+			break;
+
+		case glu::TYPE_FLOAT:
+			for (int i = 0; i < size; i++)
+			{
+				if (boolValue.val.boolV[i])
+				{
+					result.val.floatV[i] = rnd.getFloat(-10.0f, 10.0f);
+					if (result.val.floatV[i] == 0.0f)
+						result.val.floatV[i] = 1.0f;
+				}
+				else
+					result.val.floatV[i] = 0;
+			}
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	return result;
+}
+
+static const char* getCaseShaderTypeName (const CaseShaderType type)
+{
+	switch (type)
+	{
+		case CASESHADERTYPE_VERTEX:		return "vertex";
+		case CASESHADERTYPE_FRAGMENT:	return "fragment";
+		case CASESHADERTYPE_BOTH:		return "both";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+class UniformCase : public TestCase, protected glu::CallLogWrapper
+{
+public:
+	enum Feature
+	{
+		// ARRAYUSAGE_ONLY_MIDDLE_INDEX: only middle index of each array is used in shader. If not given, use all indices.
+		FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX	= 1<<0,
+
+		// UNIFORMFUNC_VALUE: use pass-by-value versions of uniform assignment funcs, e.g. glProgramUniform1f(), where possible. If not given, use pass-by-pointer versions.
+		FEATURE_UNIFORMFUNC_VALUE				= 1<<1,
+
+		// MATRIXMODE_ROWMAJOR: pass matrices to GL in row major form. If not given, use column major.
+		FEATURE_MATRIXMODE_ROWMAJOR				= 1<<2,
+
+		// ARRAYASSIGN: how basic-type arrays are assigned with glProgramUniform*(). If none given, assign each element of an array separately.
+		FEATURE_ARRAYASSIGN_FULL				= 1<<3, //!< Assign all elements of an array with one glProgramUniform*().
+		FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO		= 1<<4, //!< Assign two elements per one glProgramUniform*().
+
+		// UNIFORMUSAGE_EVERY_OTHER: use about half of the uniforms. If not given, use all uniforms (except that some array indices may be omitted according to ARRAYUSAGE).
+		FEATURE_UNIFORMUSAGE_EVERY_OTHER		= 1<<5,
+
+		// BOOLEANAPITYPE: type used to pass booleans to and from GL api. If none given, use float.
+		FEATURE_BOOLEANAPITYPE_INT				= 1<<6,
+		FEATURE_BOOLEANAPITYPE_UINT				= 1<<7,
+
+		// ARRAY_FIRST_ELEM_NAME_NO_INDEX: in certain API functions, when referring to the first element of an array, use just the array name without [0] at the end.
+		FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX	= 1<<8
+	};
+
+								UniformCase		(Context& context, const char* name, const char* description, CaseShaderType caseType, const SharedPtr<const UniformCollection>& uniformCollection);
+								UniformCase		(Context& context, const char* name, const char* description, CaseShaderType caseType, const SharedPtr<const UniformCollection>& uniformCollection, deUint32 features);
+	virtual						~UniformCase	(void);
+
+	virtual void				init			(void);
+	virtual void				deinit			(void);
+
+	IterateResult				iterate			(void);
+
+protected:
+	// A basic uniform is a uniform (possibly struct or array member) whose type is a basic type (e.g. float, ivec4, sampler2d).
+	struct BasicUniform
+	{
+		string			name;
+		glu::DataType	type;
+		bool			isUsedInShader;
+		VarValue		finalValue;	//!< The value we ultimately want to set for this uniform.
+
+		string			rootName;	//!< If this is a member of a basic-typed array, rootName is the name of that array with "[0]" appended. Otherwise it equals name.
+		int				elemNdx;	//!< If this is a member of a basic-typed array, elemNdx is the index in that array. Otherwise -1.
+		int				rootSize;	//!< If this is a member of a basic-typed array, rootSize is the size of that array. Otherwise 1.
+
+		BasicUniform (const char* const		name_,
+					  const glu::DataType	type_,
+					  const bool			isUsedInShader_,
+					  const VarValue&		finalValue_,
+					  const char* const		rootName_	= DE_NULL,
+					  const int				elemNdx_	= -1,
+					  const int				rootSize_	= 1)
+					  : name			(name_)
+					  , type			(type_)
+					  , isUsedInShader	(isUsedInShader_)
+					  , finalValue		(finalValue_)
+					  , rootName		(rootName_ == DE_NULL ? name_ : rootName_)
+					  , elemNdx			(elemNdx_)
+					  , rootSize		(rootSize_)
+					 {
+					 }
+
+		static vector<BasicUniform>::const_iterator findWithName (const vector<BasicUniform>& vec, const char* const name)
+		{
+			for (vector<BasicUniform>::const_iterator it = vec.begin(); it != vec.end(); it++)
+			{
+				if (it->name == name)
+					return it;
+			}
+			return vec.end();
+		}
+	};
+
+	// Reference values for info that is expected to be reported by glGetActiveUniform() or glGetActiveUniformsiv().
+	struct BasicUniformReportRef
+	{
+		string			name;
+		// \note minSize and maxSize are for arrays and can be distinct since implementations are allowed, but not required, to trim the inactive end indices of arrays.
+		int				minSize;
+		int				maxSize;
+		glu::DataType	type;
+		bool			isUsedInShader;
+
+		BasicUniformReportRef (const char* const name_, const int minS, const int maxS, const glu::DataType type_, const bool used)
+			: name(name_), minSize(minS), maxSize(maxS), type(type_), isUsedInShader(used) { DE_ASSERT(minSize <= maxSize); }
+		BasicUniformReportRef (const char* const name_, const glu::DataType type_, const bool used)
+			: name(name_), minSize(1), maxSize(1), type(type_), isUsedInShader(used) {}
+	};
+
+	// Get uniform values with glGetUniform*() and put to valuesDst. Uniforms that get -1 from glGetUniformLocation() get glu::TYPE_INVALID.
+	bool						getUniforms								(vector<VarValue>& valuesDst, const vector<BasicUniform>& basicUniforms, deUint32 programGL);
+	// Assign the basicUniforms[].finalValue values for uniforms. \note rnd parameter is for booleans (true can be any nonzero value).
+	void						assignUniforms							(const vector<BasicUniform>& basicUniforms, deUint32 programGL, Random& rnd);
+	// Compare the uniform values given in values (obtained with glGetUniform*()) with the basicUniform.finalValue values.
+	bool						compareUniformValues					(const vector<VarValue>& values, const vector<BasicUniform>& basicUniforms);
+	// Render and check that all pixels are green (i.e. all uniform comparisons passed).
+	bool						renderTest								(const vector<BasicUniform>& basicUniforms, const ShaderProgram& program, Random& rnd);
+
+	virtual bool				test									(const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd) = 0;
+
+	const deUint32								m_features;
+	const SharedPtr<const UniformCollection>	m_uniformCollection;
+
+private:
+	// Generates the basic uniforms, based on the uniform with name varName and type varType, in the same manner as are expected
+	// to be returned by glGetActiveUniform(), e.g. generates a name like var[0] for arrays, and recursively generates struct member names.
+	void						generateBasicUniforms					(vector<BasicUniform>&				basicUniformsDst,
+																		 vector<BasicUniformReportRef>&		basicUniformReportsDst,
+																		 const glu::VarType&				varType,
+																		 const char*						varName,
+																		 bool								isParentActive,
+																		 int&								samplerUnitCounter,
+																		 Random&							rnd) const;
+
+	void						writeUniformDefinitions					(std::ostringstream& dst) const;
+	void						writeUniformCompareExpr					(std::ostringstream& dst, const BasicUniform& uniform) const;
+	void						writeUniformComparisons					(std::ostringstream& dst, const vector<BasicUniform>& basicUniforms, const char* variableName) const;
+
+	string						generateVertexSource					(const vector<BasicUniform>& basicUniforms) const;
+	string						generateFragmentSource					(const vector<BasicUniform>& basicUniforms) const;
+
+	void						setupTexture							(const VarValue& value);
+
+	const CaseShaderType						m_caseShaderType;
+
+	vector<glu::Texture2D*>						m_textures2d;
+	vector<glu::TextureCube*>					m_texturesCube;
+	vector<deUint32>							m_filledTextureUnits;
+};
+
+UniformCase::UniformCase (Context& context, const char* const name, const char* const description, const CaseShaderType caseShaderType, const SharedPtr<const UniformCollection>& uniformCollection, const deUint32 features)
+	: TestCase				(context, name, description)
+	, CallLogWrapper		(context.getRenderContext().getFunctions(), m_testCtx.getLog())
+	, m_features			(features)
+	, m_uniformCollection	(uniformCollection)
+	, m_caseShaderType		(caseShaderType)
+{
+}
+
+UniformCase::UniformCase (Context& context, const char* const name, const char* const description, const CaseShaderType caseShaderType, const SharedPtr<const UniformCollection>& uniformCollection)
+	: TestCase				(context, name, description)
+	, CallLogWrapper		(context.getRenderContext().getFunctions(), m_testCtx.getLog())
+	, m_features			(0)
+	, m_uniformCollection	(uniformCollection)
+	, m_caseShaderType		(caseShaderType)
+{
+}
+
+void UniformCase::init (void)
+{
+	{
+		const glw::Functions&	funcs						= m_context.getRenderContext().getFunctions();
+		const int				numSamplerUniforms			= m_uniformCollection->getNumSamplers();
+		const int				vertexTexUnitsRequired		= m_caseShaderType != CASESHADERTYPE_FRAGMENT ? numSamplerUniforms : 0;
+		const int				fragmentTexUnitsRequired	= m_caseShaderType != CASESHADERTYPE_VERTEX ? numSamplerUniforms : 0;
+		const int				combinedTexUnitsRequired	= vertexTexUnitsRequired + fragmentTexUnitsRequired;
+		const int				vertexTexUnitsSupported		= getGLInt(funcs, GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS);
+		const int				fragmentTexUnitsSupported	= getGLInt(funcs, GL_MAX_TEXTURE_IMAGE_UNITS);
+		const int				combinedTexUnitsSupported	= getGLInt(funcs, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+
+		DE_ASSERT(numSamplerUniforms <= MAX_NUM_SAMPLER_UNIFORMS);
+
+		if (vertexTexUnitsRequired > vertexTexUnitsSupported)
+			throw tcu::NotSupportedError(de::toString(vertexTexUnitsRequired) + " vertex texture units required, " + de::toString(vertexTexUnitsSupported) + " supported");
+		if (fragmentTexUnitsRequired > fragmentTexUnitsSupported)
+			throw tcu::NotSupportedError(de::toString(fragmentTexUnitsRequired) + " fragment texture units required, " + de::toString(fragmentTexUnitsSupported) + " supported");
+		if (combinedTexUnitsRequired > combinedTexUnitsSupported)
+			throw tcu::NotSupportedError(de::toString(combinedTexUnitsRequired) + " combined texture units required, " + de::toString(combinedTexUnitsSupported) + " supported");
+	}
+
+	enableLogging(true);
+}
+
+void UniformCase::deinit (void)
+{
+	for (int i = 0; i < (int)m_textures2d.size(); i++)
+		delete m_textures2d[i];
+	m_textures2d.clear();
+
+	for (int i = 0; i < (int)m_texturesCube.size(); i++)
+		delete m_texturesCube[i];
+	m_texturesCube.clear();
+
+	m_filledTextureUnits.clear();
+}
+
+UniformCase::~UniformCase (void)
+{
+	UniformCase::deinit();
+}
+
+void UniformCase::generateBasicUniforms (vector<BasicUniform>& basicUniformsDst, vector<BasicUniformReportRef>& basicUniformReportsDst, const glu::VarType& varType, const char* const varName, const bool isParentActive, int& samplerUnitCounter, Random& rnd) const
+{
+	if (varType.isBasicType())
+	{
+		const bool				isActive	= isParentActive && (m_features & FEATURE_UNIFORMUSAGE_EVERY_OTHER ? basicUniformsDst.size() % 2 == 0 : true);
+		const glu::DataType		type		= varType.getBasicType();
+		const VarValue			value		= glu::isDataTypeSampler(type) ? generateRandomVarValue(type, rnd, samplerUnitCounter++)
+																		   : generateRandomVarValue(varType.getBasicType(), rnd);
+
+		basicUniformsDst.push_back(BasicUniform(varName, varType.getBasicType(), isActive, value));
+		basicUniformReportsDst.push_back(BasicUniformReportRef(varName, varType.getBasicType(), isActive));
+	}
+	else if (varType.isArrayType())
+	{
+		const int		size			= varType.getArraySize();
+		const string	arrayRootName	= string("") + varName + "[0]";
+		vector<bool>	isElemActive;
+
+		for (int elemNdx = 0; elemNdx < varType.getArraySize(); elemNdx++)
+		{
+			const string	indexedName		= string("") + varName + "[" + de::toString(elemNdx) + "]";
+			const bool		isCurElemActive	= isParentActive																						&&
+											  (m_features & FEATURE_UNIFORMUSAGE_EVERY_OTHER			? basicUniformsDst.size() % 2 == 0	: true)	&&
+											  (m_features & FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX		? elemNdx == size/2					: true);
+
+			isElemActive.push_back(isCurElemActive);
+
+			if (varType.getElementType().isBasicType())
+			{
+				// \note We don't want separate entries in basicUniformReportsDst for elements of basic-type arrays.
+				const glu::DataType	elemBasicType	= varType.getElementType().getBasicType();
+				const VarValue		value			= glu::isDataTypeSampler(elemBasicType) ? generateRandomVarValue(elemBasicType, rnd, samplerUnitCounter++)
+																							: generateRandomVarValue(elemBasicType, rnd);
+
+				basicUniformsDst.push_back(BasicUniform(indexedName.c_str(), elemBasicType, isCurElemActive, value, arrayRootName.c_str(), elemNdx, size));
+			}
+			else
+				generateBasicUniforms(basicUniformsDst, basicUniformReportsDst, varType.getElementType(), indexedName.c_str(), isCurElemActive, samplerUnitCounter, rnd);
+		}
+
+		if (varType.getElementType().isBasicType())
+		{
+			int minSize;
+			for (minSize = varType.getArraySize(); minSize > 0 && !isElemActive[minSize-1]; minSize--);
+
+			basicUniformReportsDst.push_back(BasicUniformReportRef(arrayRootName.c_str(), minSize, size, varType.getElementType().getBasicType(), isParentActive && minSize > 0));
+		}
+	}
+	else
+	{
+		DE_ASSERT(varType.isStructType());
+
+		const StructType& structType = *varType.getStructPtr();
+
+		for (int i = 0; i < structType.getNumMembers(); i++)
+		{
+			const glu::StructMember&	member			= structType.getMember(i);
+			const string				memberFullName	= string("") + varName + "." + member.getName();
+
+			generateBasicUniforms(basicUniformsDst, basicUniformReportsDst, member.getType(), memberFullName.c_str(), isParentActive, samplerUnitCounter, rnd);
+		}
+	}
+}
+
+void UniformCase::writeUniformDefinitions (std::ostringstream& dst) const
+{
+	for (int i = 0; i < (int)m_uniformCollection->getNumStructTypes(); i++)
+		dst << glu::declare(m_uniformCollection->getStructType(i)) << ";\n";
+
+	for (int i = 0; i < (int)m_uniformCollection->getNumUniforms(); i++)
+		dst << "uniform " << glu::declare(m_uniformCollection->getUniform(i).type, m_uniformCollection->getUniform(i).name.c_str()) << ";\n";
+
+	dst << "\n";
+
+	{
+		static const struct
+		{
+			dataTypePredicate	requiringTypes[2];
+			const char*			definition;
+		} compareFuncs[] =
+		{
+			{ { glu::isDataTypeFloatOrVec,				glu::isDataTypeMatrix				}, "mediump float compare_float    (mediump float a, mediump float b)  { return abs(a - b) < 0.05 ? 1.0 : 0.0; }"																		},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_VEC2>,	dataTypeIsMatrixWithNRows<2>		}, "mediump float compare_vec2     (mediump vec2 a, mediump vec2 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y); }"														},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_VEC3>,	dataTypeIsMatrixWithNRows<3>		}, "mediump float compare_vec3     (mediump vec3 a, mediump vec3 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y)*compare_float(a.z, b.z); }"								},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_VEC4>,	dataTypeIsMatrixWithNRows<4>		}, "mediump float compare_vec4     (mediump vec4 a, mediump vec4 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y)*compare_float(a.z, b.z)*compare_float(a.w, b.w); }"		},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat2     (mediump mat2 a, mediump mat2 b)    { return compare_vec2(a[0], b[0])*compare_vec2(a[1], b[1]); }"													},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT2X3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat2x3   (mediump mat2x3 a, mediump mat2x3 b){ return compare_vec3(a[0], b[0])*compare_vec3(a[1], b[1]); }"													},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT2X4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat2x4   (mediump mat2x4 a, mediump mat2x4 b){ return compare_vec4(a[0], b[0])*compare_vec4(a[1], b[1]); }"													},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT3X2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat3x2   (mediump mat3x2 a, mediump mat3x2 b){ return compare_vec2(a[0], b[0])*compare_vec2(a[1], b[1])*compare_vec2(a[2], b[2]); }"							},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat3     (mediump mat3 a, mediump mat3 b)    { return compare_vec3(a[0], b[0])*compare_vec3(a[1], b[1])*compare_vec3(a[2], b[2]); }"							},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT3X4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat3x4   (mediump mat3x4 a, mediump mat3x4 b){ return compare_vec4(a[0], b[0])*compare_vec4(a[1], b[1])*compare_vec4(a[2], b[2]); }"							},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT4X2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat4x2   (mediump mat4x2 a, mediump mat4x2 b){ return compare_vec2(a[0], b[0])*compare_vec2(a[1], b[1])*compare_vec2(a[2], b[2])*compare_vec2(a[3], b[3]); }"	},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT4X3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat4x3   (mediump mat4x3 a, mediump mat4x3 b){ return compare_vec3(a[0], b[0])*compare_vec3(a[1], b[1])*compare_vec3(a[2], b[2])*compare_vec3(a[3], b[3]); }"	},
+			{ { dataTypeEquals<glu::TYPE_FLOAT_MAT4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_mat4     (mediump mat4 a, mediump mat4 b)    { return compare_vec4(a[0], b[0])*compare_vec4(a[1], b[1])*compare_vec4(a[2], b[2])*compare_vec4(a[3], b[3]); }"	},
+			{ { dataTypeEquals<glu::TYPE_INT>,			dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_int      (mediump int a, mediump int b)      { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_INT_VEC2>,		dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_ivec2    (mediump ivec2 a, mediump ivec2 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_INT_VEC3>,		dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_ivec3    (mediump ivec3 a, mediump ivec3 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_INT_VEC4>,		dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_ivec4    (mediump ivec4 a, mediump ivec4 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_UINT>,			dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_uint     (mediump uint a, mediump uint b)    { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_UINT_VEC2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_uvec2    (mediump uvec2 a, mediump uvec2 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_UINT_VEC3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_uvec3    (mediump uvec3 a, mediump uvec3 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_UINT_VEC4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_uvec4    (mediump uvec4 a, mediump uvec4 b)  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL>,			dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bool     (bool a, bool b)                    { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL_VEC2>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bvec2    (bvec2 a, bvec2 b)                  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL_VEC3>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bvec3    (bvec3 a, bvec3 b)                  { return a == b ? 1.0 : 0.0; }"																					},
+			{ { dataTypeEquals<glu::TYPE_BOOL_VEC4>,	dataTypeEquals<glu::TYPE_INVALID>	}, "mediump float compare_bvec4    (bvec4 a, bvec4 b)                  { return a == b ? 1.0 : 0.0; }"																					}
+		};
+
+		const vector<glu::DataType> samplerTypes = m_uniformCollection->getSamplerTypes();
+
+		for (int compFuncNdx = 0; compFuncNdx < DE_LENGTH_OF_ARRAY(compareFuncs); compFuncNdx++)
+		{
+			const dataTypePredicate		(&typeReq)[2]			= compareFuncs[compFuncNdx].requiringTypes;
+			bool						containsTypeSampler		= false;
+
+			for (int i = 0; i < (int)samplerTypes.size(); i++)
+			{
+				if (glu::isDataTypeSampler(samplerTypes[i]))
+				{
+					const glu::DataType retType = getSamplerLookupReturnType(samplerTypes[i]);
+					if (typeReq[0](retType) || typeReq[1](retType))
+					{
+						containsTypeSampler = true;
+						break;
+					}
+				}
+			}
+
+			if (containsTypeSampler || m_uniformCollection->containsMatchingBasicType(typeReq[0]) || m_uniformCollection->containsMatchingBasicType(typeReq[1]))
+				dst << compareFuncs[compFuncNdx].definition << "\n";
+		}
+	}
+}
+
+void UniformCase::writeUniformCompareExpr (std::ostringstream& dst, const BasicUniform& uniform) const
+{
+	if (glu::isDataTypeSampler(uniform.type))
+		dst << "compare_" << glu::getDataTypeName(getSamplerLookupReturnType(uniform.type)) << "(texture(" << uniform.name << ", vec" << getSamplerNumLookupDimensions(uniform.type) << "(0.0))";
+	else
+		dst << "compare_" << glu::getDataTypeName(uniform.type) << "(" << uniform.name;
+
+	dst << ", " << shaderVarValueStr(uniform.finalValue) << ")";
+}
+
+void UniformCase::writeUniformComparisons (std::ostringstream& dst, const vector<BasicUniform>& basicUniforms, const char* const variableName) const
+{
+	for (int i = 0; i < (int)basicUniforms.size(); i++)
+	{
+		const BasicUniform& unif = basicUniforms[i];
+
+		if (unif.isUsedInShader)
+		{
+			dst << "\t" << variableName << " *= ";
+			writeUniformCompareExpr(dst, basicUniforms[i]);
+			dst << ";\n";
+		}
+		else
+			dst << "\t// UNUSED: " << basicUniforms[i].name << "\n";
+	}
+}
+
+string UniformCase::generateVertexSource (const vector<BasicUniform>& basicUniforms) const
+{
+	const bool			isVertexCase = m_caseShaderType == CASESHADERTYPE_VERTEX || m_caseShaderType == CASESHADERTYPE_BOTH;
+	std::ostringstream	result;
+
+	result << "#version 310 es\n"
+			  "in highp vec4 a_position;\n"
+			  "out mediump float v_vtxOut;\n"
+			  "\n";
+
+	if (isVertexCase)
+		writeUniformDefinitions(result);
+
+	result << "\n"
+			  "void main (void)\n"
+			  "{\n"
+			  "	gl_Position = a_position;\n"
+			  "	v_vtxOut = 1.0;\n";
+
+	if (isVertexCase)
+		writeUniformComparisons(result, basicUniforms, "v_vtxOut");
+
+	result << "}\n";
+
+	return result.str();
+}
+
+string UniformCase::generateFragmentSource (const vector<BasicUniform>& basicUniforms) const
+{
+	const bool			isFragmentCase = m_caseShaderType == CASESHADERTYPE_FRAGMENT || m_caseShaderType == CASESHADERTYPE_BOTH;
+	std::ostringstream	result;
+
+	result << "#version 310 es\n"
+			  "in mediump float v_vtxOut;\n"
+			  "\n";
+
+	if (isFragmentCase)
+		writeUniformDefinitions(result);
+
+	result << "\n"
+			  "layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+			  "\n"
+			  "void main (void)\n"
+			  "{\n"
+			  "	mediump float result = v_vtxOut;\n";
+
+	if (isFragmentCase)
+		writeUniformComparisons(result, basicUniforms, "result");
+
+	result << "	dEQP_FragColor = vec4(1.0-result, result, 0.0, 1.0);\n"
+			  "}\n";
+
+	return result.str();
+}
+
+void UniformCase::setupTexture (const VarValue& value)
+{
+	// \note No handling for samplers other than 2D or cube.
+
+	enableLogging(false);
+
+	DE_ASSERT(getSamplerLookupReturnType(value.type) == glu::TYPE_FLOAT_VEC4);
+
+	const int						width			= 32;
+	const int						height			= 32;
+	const tcu::Vec4					color			= vec4FromPtr(&value.val.samplerV.fillColor.floatV[0]);
+
+	if (value.type == glu::TYPE_SAMPLER_2D)
+	{
+		glu::Texture2D* texture		= new glu::Texture2D(m_context.getRenderContext(), GL_RGBA, GL_UNSIGNED_BYTE, width, height);
+		tcu::Texture2D& refTexture	= texture->getRefTexture();
+		m_textures2d.push_back(texture);
+
+		refTexture.allocLevel(0);
+		fillWithColor(refTexture.getLevel(0), color);
+
+		GLU_CHECK_CALL(glActiveTexture(GL_TEXTURE0 + value.val.samplerV.unit));
+		m_filledTextureUnits.push_back(value.val.samplerV.unit);
+		texture->upload();
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+	}
+	else if (value.type == glu::TYPE_SAMPLER_CUBE)
+	{
+		DE_STATIC_ASSERT(width == height);
+		glu::TextureCube* texture		= new glu::TextureCube(m_context.getRenderContext(), GL_RGBA, GL_UNSIGNED_BYTE, width);
+		tcu::TextureCube& refTexture	= texture->getRefTexture();
+		m_texturesCube.push_back(texture);
+
+		for (int face = 0; face < (int)tcu::CUBEFACE_LAST; face++)
+		{
+			refTexture.allocLevel((tcu::CubeFace)face, 0);
+			fillWithColor(refTexture.getLevelFace(0, (tcu::CubeFace)face), color);
+		}
+
+		GLU_CHECK_CALL(glActiveTexture(GL_TEXTURE0 + value.val.samplerV.unit));
+		m_filledTextureUnits.push_back(value.val.samplerV.unit);
+		texture->upload();
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+		GLU_CHECK_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+
+	}
+	else
+		DE_ASSERT(false);
+
+	enableLogging(true);
+}
+
+bool UniformCase::getUniforms (vector<VarValue>& valuesDst, const vector<BasicUniform>& basicUniforms, const deUint32 programGL)
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		success		= true;
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform		= basicUniforms[unifNdx];
+		const string			queryName	= m_features & FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX && uniform.elemNdx == 0 ? beforeLast(uniform.name, '[') : uniform.name;
+		const int				location	= glGetUniformLocation(programGL, queryName.c_str());
+		const int				size		= glu::getDataTypeScalarSize(uniform.type);
+		VarValue				value;
+
+		deMemset(&value, 0xcd, sizeof(value)); // Initialize to known garbage.
+
+		if (location == -1)
+		{
+			value.type = glu::TYPE_INVALID;
+			valuesDst.push_back(value);
+			if (uniform.isUsedInShader)
+			{
+				log << TestLog::Message << "// FAILURE: " << uniform.name << " was used in shader, but has location -1" << TestLog::EndMessage;
+				success = false;
+			}
+			continue;
+		}
+
+		value.type = uniform.type;
+
+		DE_STATIC_ASSERT(sizeof(GLint) == sizeof(value.val.intV[0]));
+		DE_STATIC_ASSERT(sizeof(GLuint) == sizeof(value.val.uintV[0]));
+		DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(value.val.floatV[0]));
+
+		if (glu::isDataTypeFloatOrVec(uniform.type) || glu::isDataTypeMatrix(uniform.type))
+			GLU_CHECK_CALL(glGetUniformfv(programGL, location, &value.val.floatV[0]));
+		else if (glu::isDataTypeIntOrIVec(uniform.type))
+			GLU_CHECK_CALL(glGetUniformiv(programGL, location, &value.val.intV[0]));
+		else if (glu::isDataTypeUintOrUVec(uniform.type))
+			GLU_CHECK_CALL(glGetUniformuiv(programGL, location, &value.val.uintV[0]));
+		else if (glu::isDataTypeBoolOrBVec(uniform.type))
+		{
+			if (m_features & FEATURE_BOOLEANAPITYPE_INT)
+			{
+				GLU_CHECK_CALL(glGetUniformiv(programGL, location, &value.val.intV[0]));
+				for (int i = 0; i < size; i++)
+					value.val.boolV[i] = value.val.intV[i] != 0;
+			}
+			else if (m_features & FEATURE_BOOLEANAPITYPE_UINT)
+			{
+				GLU_CHECK_CALL(glGetUniformuiv(programGL, location, &value.val.uintV[0]));
+				for (int i = 0; i < size; i++)
+					value.val.boolV[i] = value.val.uintV[i] != 0;
+			}
+			else // Default: use float.
+			{
+				GLU_CHECK_CALL(glGetUniformfv(programGL, location, &value.val.floatV[0]));
+				for (int i = 0; i < size; i++)
+					value.val.boolV[i] = value.val.floatV[i] != 0.0f;
+			}
+		}
+		else if (glu::isDataTypeSampler(uniform.type))
+		{
+			GLint unit = -1;
+			GLU_CHECK_CALL(glGetUniformiv(programGL, location, &unit));
+			value.val.samplerV.unit = unit;
+		}
+		else
+			DE_ASSERT(false);
+
+		valuesDst.push_back(value);
+
+		log << TestLog::Message << "// Got " << uniform.name << " value " << apiVarValueStr(value) << TestLog::EndMessage;
+	}
+
+	return success;
+}
+
+void UniformCase::assignUniforms (const vector<BasicUniform>& basicUniforms, deUint32 programGL, Random& rnd)
+{
+	TestLog&				log				= m_testCtx.getLog();
+	const bool				transpose		= (m_features & FEATURE_MATRIXMODE_ROWMAJOR) != 0;
+	const GLboolean			transposeGL		= transpose ? GL_TRUE : GL_FALSE;
+	const glu::DataType		boolApiType		= m_features & FEATURE_BOOLEANAPITYPE_INT	? glu::TYPE_INT
+											: m_features & FEATURE_BOOLEANAPITYPE_UINT	? glu::TYPE_UINT
+											:											  glu::TYPE_FLOAT;
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform				= basicUniforms[unifNdx];
+		const bool				isArrayMember		= uniform.elemNdx >= 0;
+		const string			queryName			= m_features & FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX && uniform.elemNdx == 0 ? beforeLast(uniform.name, '[') : uniform.name;
+		const int				numValuesToAssign	= !isArrayMember									? 1
+													: m_features & FEATURE_ARRAYASSIGN_FULL				? (uniform.elemNdx == 0			? uniform.rootSize	: 0)
+													: m_features & FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO	? (uniform.elemNdx % 2 == 0		? 2					: 0)
+													: /* Default: assign array elements separately */	  1;
+
+		DE_ASSERT(numValuesToAssign >= 0);
+		DE_ASSERT(numValuesToAssign == 1 || isArrayMember);
+
+		if (numValuesToAssign == 0)
+		{
+			log << TestLog::Message << "// Uniform " << uniform.name << " is covered by another glProgramUniform*v() call to the same array" << TestLog::EndMessage;
+			continue;
+		}
+
+		const int			location			= glGetUniformLocation(programGL, queryName.c_str());
+		const int			typeSize			= glu::getDataTypeScalarSize(uniform.type);
+		const bool			assignByValue		= m_features & FEATURE_UNIFORMFUNC_VALUE && !glu::isDataTypeMatrix(uniform.type) && numValuesToAssign == 1;
+		vector<VarValue>	valuesToAssign;
+
+		for (int i = 0; i < numValuesToAssign; i++)
+		{
+			const string	curName = isArrayMember ? beforeLast(uniform.rootName, '[') + "[" + de::toString(uniform.elemNdx+i) + "]" : uniform.name;
+			VarValue		unifValue;
+
+			if (isArrayMember)
+			{
+				const vector<BasicUniform>::const_iterator elemUnif = BasicUniform::findWithName(basicUniforms, curName.c_str());
+				if (elemUnif == basicUniforms.end())
+					continue;
+				unifValue = elemUnif->finalValue;
+			}
+			else
+				unifValue = uniform.finalValue;
+
+			const VarValue apiValue = glu::isDataTypeBoolOrBVec(unifValue.type)	? getRandomBoolRepresentation(unifValue, boolApiType, rnd)
+									: glu::isDataTypeSampler(unifValue.type)	? getSamplerUnitValue(unifValue)
+									: unifValue;
+
+			valuesToAssign.push_back(glu::isDataTypeMatrix(apiValue.type) && transpose ? getTransposeMatrix(apiValue) : apiValue);
+
+			if (glu::isDataTypeBoolOrBVec(uniform.type))
+				log << TestLog::Message << "// Using type " << glu::getDataTypeName(boolApiType) << " to set boolean value " << apiVarValueStr(unifValue) << " for " << curName << TestLog::EndMessage;
+			else if (glu::isDataTypeSampler(uniform.type))
+				log << TestLog::Message << "// Texture for the sampler uniform " << curName << " will be filled with color " << apiVarValueStr(getSamplerFillValue(uniform.finalValue)) << TestLog::EndMessage;
+		}
+
+		DE_ASSERT(!valuesToAssign.empty());
+
+		if (glu::isDataTypeFloatOrVec(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+			{
+				const float* const ptr = &valuesToAssign[0].val.floatV[0];
+
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glProgramUniform1f(programGL, location, ptr[0]));							break;
+					case 2: GLU_CHECK_CALL(glProgramUniform2f(programGL, location, ptr[0], ptr[1]));					break;
+					case 3: GLU_CHECK_CALL(glProgramUniform3f(programGL, location, ptr[0], ptr[1], ptr[2]));			break;
+					case 4: GLU_CHECK_CALL(glProgramUniform4f(programGL, location, ptr[0], ptr[1], ptr[2], ptr[3]));	break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+			else
+			{
+				vector<float> buffer(valuesToAssign.size() * typeSize);
+				for (int i = 0; i < (int)buffer.size(); i++)
+					buffer[i] = valuesToAssign[i / typeSize].val.floatV[i % typeSize];
+
+				DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(buffer[0]));
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glProgramUniform1fv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 2: GLU_CHECK_CALL(glProgramUniform2fv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 3: GLU_CHECK_CALL(glProgramUniform3fv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 4: GLU_CHECK_CALL(glProgramUniform4fv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+		else if (glu::isDataTypeMatrix(valuesToAssign[0].type))
+		{
+			DE_ASSERT(!assignByValue);
+
+			vector<float> buffer(valuesToAssign.size() * typeSize);
+			for (int i = 0; i < (int)buffer.size(); i++)
+				buffer[i] = valuesToAssign[i / typeSize].val.floatV[i % typeSize];
+
+			DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(buffer[0]));
+			switch (uniform.type)
+			{
+				case glu::TYPE_FLOAT_MAT2:		GLU_CHECK_CALL(glProgramUniformMatrix2fv	(programGL, location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT3:		GLU_CHECK_CALL(glProgramUniformMatrix3fv	(programGL, location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT4:		GLU_CHECK_CALL(glProgramUniformMatrix4fv	(programGL, location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT2X3:	GLU_CHECK_CALL(glProgramUniformMatrix2x3fv	(programGL, location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT2X4:	GLU_CHECK_CALL(glProgramUniformMatrix2x4fv	(programGL, location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT3X2:	GLU_CHECK_CALL(glProgramUniformMatrix3x2fv	(programGL, location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT3X4:	GLU_CHECK_CALL(glProgramUniformMatrix3x4fv	(programGL, location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT4X2:	GLU_CHECK_CALL(glProgramUniformMatrix4x2fv	(programGL, location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				case glu::TYPE_FLOAT_MAT4X3:	GLU_CHECK_CALL(glProgramUniformMatrix4x3fv	(programGL, location, (GLsizei)valuesToAssign.size(), transposeGL, &buffer[0])); break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+		else if (glu::isDataTypeIntOrIVec(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+			{
+				const deInt32* const ptr = &valuesToAssign[0].val.intV[0];
+
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glProgramUniform1i(programGL, location, ptr[0]));							break;
+					case 2: GLU_CHECK_CALL(glProgramUniform2i(programGL, location, ptr[0], ptr[1]));					break;
+					case 3: GLU_CHECK_CALL(glProgramUniform3i(programGL, location, ptr[0], ptr[1], ptr[2]));			break;
+					case 4: GLU_CHECK_CALL(glProgramUniform4i(programGL, location, ptr[0], ptr[1], ptr[2], ptr[3]));	break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+			else
+			{
+				vector<deInt32> buffer(valuesToAssign.size() * typeSize);
+				for (int i = 0; i < (int)buffer.size(); i++)
+					buffer[i] = valuesToAssign[i / typeSize].val.intV[i % typeSize];
+
+				DE_STATIC_ASSERT(sizeof(GLint) == sizeof(buffer[0]));
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glProgramUniform1iv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 2: GLU_CHECK_CALL(glProgramUniform2iv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 3: GLU_CHECK_CALL(glProgramUniform3iv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 4: GLU_CHECK_CALL(glProgramUniform4iv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+		else if (glu::isDataTypeUintOrUVec(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+			{
+				const deUint32* const ptr = &valuesToAssign[0].val.uintV[0];
+
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glProgramUniform1ui(programGL, location, ptr[0]));							break;
+					case 2: GLU_CHECK_CALL(glProgramUniform2ui(programGL, location, ptr[0], ptr[1]));					break;
+					case 3: GLU_CHECK_CALL(glProgramUniform3ui(programGL, location, ptr[0], ptr[1], ptr[2]));			break;
+					case 4: GLU_CHECK_CALL(glProgramUniform4ui(programGL, location, ptr[0], ptr[1], ptr[2], ptr[3]));	break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+			else
+			{
+				vector<deUint32> buffer(valuesToAssign.size() * typeSize);
+				for (int i = 0; i < (int)buffer.size(); i++)
+					buffer[i] = valuesToAssign[i / typeSize].val.intV[i % typeSize];
+
+				DE_STATIC_ASSERT(sizeof(GLuint) == sizeof(buffer[0]));
+				switch (typeSize)
+				{
+					case 1: GLU_CHECK_CALL(glProgramUniform1uiv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 2: GLU_CHECK_CALL(glProgramUniform2uiv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 3: GLU_CHECK_CALL(glProgramUniform3uiv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					case 4: GLU_CHECK_CALL(glProgramUniform4uiv(programGL, location, (GLsizei)valuesToAssign.size(), &buffer[0])); break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+		else if (glu::isDataTypeSampler(valuesToAssign[0].type))
+		{
+			if (assignByValue)
+				GLU_CHECK_CALL(glProgramUniform1i(programGL, location, uniform.finalValue.val.samplerV.unit));
+			else
+			{
+				const GLint unit = uniform.finalValue.val.samplerV.unit;
+				GLU_CHECK_CALL(glProgramUniform1iv(programGL, location, (GLsizei)valuesToAssign.size(), &unit));
+			}
+		}
+		else
+			DE_ASSERT(false);
+	}
+}
+
+bool UniformCase::compareUniformValues (const vector<VarValue>& values, const vector<BasicUniform>& basicUniforms)
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		success		= true;
+
+	for (int unifNdx = 0; unifNdx < (int)basicUniforms.size(); unifNdx++)
+	{
+		const BasicUniform&		uniform		= basicUniforms[unifNdx];
+		const VarValue&			unifValue	= values[unifNdx];
+
+		log << TestLog::Message << "// Checking uniform " << uniform.name << TestLog::EndMessage;
+
+		if (unifValue.type == glu::TYPE_INVALID) // This happens when glGetUniformLocation() returned -1.
+			continue;
+
+		if (!apiVarValueEquals(unifValue, uniform.finalValue))
+		{
+			log << TestLog::Message << "// FAILURE: value obtained with glGetUniform*() for uniform " << uniform.name << " differs from value set with glProgramUniform*()" << TestLog::EndMessage;
+			success = false;
+		}
+	}
+
+	return success;
+}
+
+bool UniformCase::renderTest (const vector<BasicUniform>& basicUniforms, const ShaderProgram& program, Random& rnd)
+{
+	TestLog&					log				= m_testCtx.getLog();
+	const tcu::RenderTarget&	renderTarget	= m_context.getRenderTarget();
+	const int					viewportW		= de::min<int>(renderTarget.getWidth(),		MAX_RENDER_WIDTH);
+	const int					viewportH		= de::min<int>(renderTarget.getHeight(),	MAX_RENDER_HEIGHT);
+	const int					viewportX		= rnd.getInt(0, renderTarget.getWidth()		- viewportW);
+	const int					viewportY		= rnd.getInt(0, renderTarget.getHeight()	- viewportH);
+	tcu::Surface				renderedImg		(viewportW, viewportH);
+
+	// Assert that no two samplers of different types have the same texture unit - this is an error in GL.
+	for (int i = 0; i < (int)basicUniforms.size(); i++)
+	{
+		if (glu::isDataTypeSampler(basicUniforms[i].type))
+		{
+			for (int j = 0; j < i; j++)
+			{
+				if (glu::isDataTypeSampler(basicUniforms[j].type) && basicUniforms[i].type != basicUniforms[j].type)
+					DE_ASSERT(basicUniforms[i].finalValue.val.samplerV.unit != basicUniforms[j].finalValue.val.samplerV.unit);
+			}
+		}
+	}
+
+	for (int i = 0; i < (int)basicUniforms.size(); i++)
+	{
+		if (glu::isDataTypeSampler(basicUniforms[i].type) && std::find(m_filledTextureUnits.begin(), m_filledTextureUnits.end(), basicUniforms[i].finalValue.val.samplerV.unit) == m_filledTextureUnits.end())
+		{
+			log << TestLog::Message << "// Filling texture at unit " << apiVarValueStr(basicUniforms[i].finalValue) << " with color " << shaderVarValueStr(basicUniforms[i].finalValue) << TestLog::EndMessage;
+			setupTexture(basicUniforms[i].finalValue);
+		}
+	}
+
+	GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportW, viewportH));
+	GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));
+	GLU_CHECK_CALL(glUseProgram(program.getProgram()));
+
+	{
+		static const float position[] =
+		{
+			-1.0f, -1.0f, 0.0f, 1.0f,
+			-1.0f, +1.0f, 0.0f, 1.0f,
+			+1.0f, -1.0f, 0.0f, 1.0f,
+			+1.0f, +1.0f, 0.0f, 1.0f
+		};
+		static const deUint16			indices[]	= { 0, 1, 2, 2, 1, 3 };
+		const glu::VertexArrayBinding	binding		= glu::va::Float("a_position", 4, 4, 0, &position[0]);
+
+		glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &binding, glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+		glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, renderedImg.getAccess());
+	}
+
+	int numFailedPixels = 0;
+	for (int y = 0; y < renderedImg.getHeight(); y++)
+	{
+		for (int x = 0; x < renderedImg.getWidth(); x++)
+		{
+			if (renderedImg.getPixel(x, y) != tcu::RGBA::green)
+				numFailedPixels += 1;
+		}
+	}
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Image("RenderedImage", "Rendered image", renderedImg);
+		log << TestLog::Message << "FAILURE: image comparison failed, got " << numFailedPixels << " non-green pixels" << TestLog::EndMessage;
+		return false;
+	}
+	else
+	{
+		log << TestLog::Message << "Success: got all-green pixels (all uniforms have correct values)" << TestLog::EndMessage;
+		return true;
+	}
+}
+
+UniformCase::IterateResult UniformCase::iterate (void)
+{
+	Random							rnd				(deStringHash(getName()) ^ (deUint32)m_context.getTestContext().getCommandLine().getBaseSeed());
+	TestLog&						log				= m_testCtx.getLog();
+	vector<BasicUniform>			basicUniforms;
+	vector<BasicUniformReportRef>	basicUniformReportsRef;
+
+	{
+		int samplerUnitCounter = 0;
+		for (int i = 0; i < (int)m_uniformCollection->getNumUniforms(); i++)
+			generateBasicUniforms(basicUniforms, basicUniformReportsRef, m_uniformCollection->getUniform(i).type, m_uniformCollection->getUniform(i).name.c_str(), true, samplerUnitCounter, rnd);
+	}
+
+	const string					vertexSource	= generateVertexSource(basicUniforms);
+	const string					fragmentSource	= generateFragmentSource(basicUniforms);
+	const ShaderProgram				program			(m_context.getRenderContext(), glu::makeVtxFragSources(vertexSource, fragmentSource));
+
+	// A dummy program that we'll give to glUseProgram before we actually need
+	// the real program above, to see if an implementation tries to use the
+	// currently active program for something inappropriate (instead of the
+	// program given as argument to, say, glProgramUniform*).
+	const ShaderProgram				dummyProgram	(m_context.getRenderContext(), glu::makeVtxFragSources("#version 310 es\n"
+																										   "void main (void) { gl_Position = vec4(1.0); }\n",
+
+																										   "#version 310 es\n"
+																										   "layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+																										   "void main (void) { dEQP_FragColor = vec4(0.0, 0.0, 1.0, 1.0); }\n"));
+
+	log << program;
+
+	if (!program.isOk())
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		return STOP;
+	}
+
+	if (!dummyProgram.isOk())
+	{
+		log << dummyProgram;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compilation of dummy program failed");
+		return STOP;
+	}
+
+	log << TestLog::Message << "// Note: calling glUseProgram with a dummy program (will only use the real program once it's needed for rendering)" << TestLog::EndMessage;
+	glUseProgram(dummyProgram.getProgram());
+
+	const bool success = test(basicUniforms, basicUniformReportsRef, program, rnd);
+	m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							success ? "Passed"				: "Failed");
+
+	return STOP;
+}
+
+class UniformAssignCase : public UniformCase
+{
+public:
+	enum CheckMethod
+	{
+		CHECKMETHOD_GET_UNIFORM = 0,	//!< Check values with glGetUniform*().
+		CHECKMETHOD_RENDER,				//!< Check values by rendering with the value-checking shader.
+
+		CHECKMETHOD_LAST
+	};
+	enum AssignMethod
+	{
+		ASSIGNMETHOD_POINTER = 0,
+		ASSIGNMETHOD_VALUE,
+
+		ASSIGNMETHOD_LAST
+	};
+
+						UniformAssignCase			(Context&									context,
+													 const char*								name,
+													 const char*								description,
+													 CaseShaderType								shaderType,
+													 const SharedPtr<const UniformCollection>&	uniformCollection,
+													 CheckMethod								checkMethod,
+													 AssignMethod								assignMethod,
+													 deUint32									additionalFeatures = 0);
+
+	bool				test						(const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd);
+
+	static const char*	getCheckMethodName			(CheckMethod checkMethod);
+	static const char*	getCheckMethodDescription	(CheckMethod checkMethod);
+	static const char*	getAssignMethodName			(AssignMethod checkMethod);
+	static const char*	getAssignMethodDescription	(AssignMethod checkMethod);
+
+private:
+	const CheckMethod m_checkMethod;
+};
+
+const char* UniformAssignCase::getCheckMethodName (const CheckMethod checkMethod)
+{
+	switch (checkMethod)
+	{
+		case CHECKMETHOD_GET_UNIFORM:	return "get_uniform";
+		case CHECKMETHOD_RENDER:		return "render";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+const char* UniformAssignCase::getCheckMethodDescription (const CheckMethod checkMethod)
+{
+	switch (checkMethod)
+	{
+		case CHECKMETHOD_GET_UNIFORM:	return "Verify values with glGetUniform*()";
+		case CHECKMETHOD_RENDER:		return "Verify values by rendering";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+const char* UniformAssignCase::getAssignMethodName (const AssignMethod assignMethod)
+{
+	switch (assignMethod)
+	{
+		case ASSIGNMETHOD_POINTER:		return "by_pointer";
+		case ASSIGNMETHOD_VALUE:		return "by_value";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+const char* UniformAssignCase::getAssignMethodDescription (const AssignMethod assignMethod)
+{
+	switch (assignMethod)
+	{
+		case ASSIGNMETHOD_POINTER:		return "Assign values by-pointer";
+		case ASSIGNMETHOD_VALUE:		return "Assign values by-value";
+		default: DE_ASSERT(false);		return DE_NULL;
+	}
+}
+
+UniformAssignCase::UniformAssignCase (Context&									context,
+									  const char* const							name,
+									  const char* const							description,
+									  const CaseShaderType						shaderType,
+									  const SharedPtr<const UniformCollection>&	uniformCollection,
+									  const CheckMethod							checkMethod,
+									  const AssignMethod						assignMethod,
+									  const deUint32							additionalFeatures)
+	: UniformCase		(context, name, description, shaderType, uniformCollection,
+						 (assignMethod == ASSIGNMETHOD_VALUE ? FEATURE_UNIFORMFUNC_VALUE : 0) | additionalFeatures)
+	, m_checkMethod		(checkMethod)
+{
+	DE_ASSERT(assignMethod != ASSIGNMETHOD_LAST);
+}
+
+bool UniformAssignCase::test (const vector<BasicUniform>& basicUniforms, const vector<BasicUniformReportRef>& basicUniformReportsRef, const ShaderProgram& program, Random& rnd)
+{
+	DE_UNREF(basicUniformReportsRef);
+
+	const deUint32	programGL	= program.getProgram();
+	TestLog&		log			= m_testCtx.getLog();
+
+	{
+		const ScopedLogSection section(log, "UniformAssign", "Uniform value assignments");
+		assignUniforms(basicUniforms, programGL, rnd);
+	}
+
+	if (m_checkMethod == CHECKMETHOD_GET_UNIFORM)
+	{
+		vector<VarValue> values;
+
+		{
+			const ScopedLogSection section(log, "GetUniforms", "Uniform value query");
+			const bool success = getUniforms(values, basicUniforms, program.getProgram());
+
+			if (!success)
+				return false;
+		}
+
+		{
+			const ScopedLogSection section(log, "ValueCheck", "Verify that the reported values match the assigned values");
+			const bool success = compareUniformValues(values, basicUniforms);
+
+			if (!success)
+				return false;
+		}
+	}
+	else
+	{
+		DE_ASSERT(m_checkMethod == CHECKMETHOD_RENDER);
+
+		const ScopedLogSection section(log, "RenderTest", "Render test");
+		const bool success = renderTest(basicUniforms, program, rnd);
+
+		if (!success)
+			return false;
+	}
+
+	return true;
+}
+
+ProgramUniformTests::ProgramUniformTests (Context& context)
+	: TestCaseGroup(context, "program_uniform", "glProgramUniform*() tests")
+{
+}
+
+ProgramUniformTests::~ProgramUniformTests (void)
+{
+}
+
+namespace
+{
+
+// \note Although this is only used in ProgramUniformTests::init, it needs to be defined here as it's used as a template argument.
+struct UniformCollectionCase
+{
+	string								namePrefix;
+	SharedPtr<const UniformCollection>	uniformCollection;
+
+	UniformCollectionCase (const char* const name, const UniformCollection* uniformCollection_)
+		: namePrefix			(name ? name + string("_") : "")
+		, uniformCollection		(uniformCollection_)
+	{
+	}
+};
+
+} // anonymous
+
+void ProgramUniformTests::init (void)
+{
+	// Generate sets of UniformCollections that are used by several cases.
+
+	enum
+	{
+		UNIFORMCOLLECTIONS_BASIC = 0,
+		UNIFORMCOLLECTIONS_BASIC_ARRAY,
+		UNIFORMCOLLECTIONS_BASIC_STRUCT,
+		UNIFORMCOLLECTIONS_STRUCT_IN_ARRAY,
+		UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT,
+		UNIFORMCOLLECTIONS_NESTED_STRUCTS_ARRAYS,
+		UNIFORMCOLLECTIONS_MULTIPLE_BASIC,
+		UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY,
+		UNIFORMCOLLECTIONS_MULTIPLE_NESTED_STRUCTS_ARRAYS,
+
+		UNIFORMCOLLECTIONS_LAST
+	};
+
+	struct UniformCollectionGroup
+	{
+		string							name;
+		vector<UniformCollectionCase>	cases;
+	} defaultUniformCollections[UNIFORMCOLLECTIONS_LAST];
+
+	defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC].name							= "basic";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_ARRAY].name						= "basic_array";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_STRUCT].name						= "basic_struct";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_STRUCT_IN_ARRAY].name					= "struct_in_array";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT].name					= "array_in_struct";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_NESTED_STRUCTS_ARRAYS].name			= "nested_structs_arrays";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC].name					= "multiple_basic";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY].name				= "multiple_basic_array";
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_NESTED_STRUCTS_ARRAYS].name	= "multiple_nested_structs_arrays";
+
+	for (int dataTypeNdx = 0; dataTypeNdx < DE_LENGTH_OF_ARRAY(s_testDataTypes); dataTypeNdx++)
+	{
+		const glu::DataType		dataType	= s_testDataTypes[dataTypeNdx];
+		const char* const		typeName	= glu::getDataTypeName(dataType);
+
+		defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC].cases.push_back(UniformCollectionCase(typeName, UniformCollection::basic(dataType)));
+
+		if (glu::isDataTypeScalar(dataType)													||
+			(glu::isDataTypeVector(dataType) && glu::getDataTypeScalarSize(dataType) == 4)	||
+			dataType == glu::TYPE_FLOAT_MAT4												||
+			dataType == glu::TYPE_SAMPLER_2D)
+			defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_ARRAY].cases.push_back(UniformCollectionCase(typeName, UniformCollection::basicArray(dataType)));
+
+		if (glu::isDataTypeScalar(dataType)		||
+			dataType == glu::TYPE_FLOAT_MAT4	||
+			dataType == glu::TYPE_SAMPLER_2D)
+		{
+			const glu::DataType		secondDataType	= glu::isDataTypeScalar(dataType)	? glu::getDataTypeVector(dataType, 4)
+													: dataType == glu::TYPE_FLOAT_MAT4	? glu::TYPE_FLOAT_MAT2
+													: dataType == glu::TYPE_SAMPLER_2D	? glu::TYPE_SAMPLER_CUBE
+													: glu::TYPE_LAST;
+			DE_ASSERT(secondDataType != glu::TYPE_LAST);
+			const char* const		secondTypeName	= glu::getDataTypeName(secondDataType);
+			const string			name			= string("") + typeName + "_" + secondTypeName;
+
+			defaultUniformCollections[UNIFORMCOLLECTIONS_BASIC_STRUCT].cases.push_back			(UniformCollectionCase(name.c_str(), UniformCollection::basicStruct(dataType, secondDataType, false)));
+			defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT].cases.push_back		(UniformCollectionCase(name.c_str(), UniformCollection::basicStruct(dataType, secondDataType, true)));
+			defaultUniformCollections[UNIFORMCOLLECTIONS_STRUCT_IN_ARRAY].cases.push_back		(UniformCollectionCase(name.c_str(), UniformCollection::structInArray(dataType, secondDataType, false)));
+			defaultUniformCollections[UNIFORMCOLLECTIONS_NESTED_STRUCTS_ARRAYS].cases.push_back	(UniformCollectionCase(name.c_str(), UniformCollection::nestedArraysStructs(dataType, secondDataType)));
+		}
+	}
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC].cases.push_back					(UniformCollectionCase(DE_NULL, UniformCollection::multipleBasic()));
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY].cases.push_back				(UniformCollectionCase(DE_NULL, UniformCollection::multipleBasicArray()));
+	defaultUniformCollections[UNIFORMCOLLECTIONS_MULTIPLE_NESTED_STRUCTS_ARRAYS].cases.push_back	(UniformCollectionCase(DE_NULL, UniformCollection::multipleNestedArraysStructs()));
+
+	// Basic by-pointer or by-value uniform assignment cases.
+
+	for (int assignMethodI = 0; assignMethodI < (int)UniformAssignCase::ASSIGNMETHOD_LAST; assignMethodI++)
+	{
+		const UniformAssignCase::AssignMethod	assignMethod		= (UniformAssignCase::AssignMethod)assignMethodI;
+		TestCaseGroup* const					assignMethodGroup	= new TestCaseGroup(m_context, UniformAssignCase::getAssignMethodName(assignMethod), UniformAssignCase::getAssignMethodDescription(assignMethod));
+		addChild(assignMethodGroup);
+
+		for (int checkMethodI = 0; checkMethodI < (int)UniformAssignCase::CHECKMETHOD_LAST; checkMethodI++)
+		{
+			const UniformAssignCase::CheckMethod	checkMethod			= (UniformAssignCase::CheckMethod)checkMethodI;
+			TestCaseGroup* const					checkMethodGroup	= new TestCaseGroup(m_context, UniformAssignCase::getCheckMethodName(checkMethod), UniformAssignCase::getCheckMethodDescription(checkMethod));
+			assignMethodGroup->addChild(checkMethodGroup);
+
+			for (int collectionGroupNdx = 0; collectionGroupNdx < (int)UNIFORMCOLLECTIONS_LAST; collectionGroupNdx++)
+			{
+				const int numArrayFirstElemNameCases = checkMethod == UniformAssignCase::CHECKMETHOD_GET_UNIFORM && collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY ? 2 : 1;
+
+				for (int referToFirstArrayElemWithoutIndexI = 0; referToFirstArrayElemWithoutIndexI < numArrayFirstElemNameCases; referToFirstArrayElemWithoutIndexI++)
+				{
+					const UniformCollectionGroup&	collectionGroup			= defaultUniformCollections[collectionGroupNdx];
+					const string					collectionGroupName		= collectionGroup.name + (referToFirstArrayElemWithoutIndexI == 0 ? "" : "_first_elem_without_brackets");
+					TestCaseGroup* const			collectionTestGroup		= new TestCaseGroup(m_context, collectionGroupName.c_str(), "");
+					checkMethodGroup->addChild(collectionTestGroup);
+
+					for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+					{
+						const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+						const string								collName			= collectionCase.namePrefix;
+						const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+						const bool									containsBooleans	= uniformCollection->containsMatchingBasicType(glu::isDataTypeBoolOrBVec);
+						const bool									varyBoolApiType		= checkMethod == UniformAssignCase::CHECKMETHOD_GET_UNIFORM && containsBooleans &&
+																							(collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC || collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY);
+						const int									numBoolVariations	= varyBoolApiType ? 3 : 1;
+						const bool									containsMatrices	= uniformCollection->containsMatchingBasicType(glu::isDataTypeMatrix);
+						const bool									varyMatrixMode		= containsMatrices &&
+																							(collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC || collectionGroupNdx == UNIFORMCOLLECTIONS_BASIC_ARRAY);
+						const int									numMatVariations	= varyMatrixMode ? 2 : 1;
+
+						if (containsMatrices && assignMethod != UniformAssignCase::ASSIGNMETHOD_POINTER)
+							continue;
+
+						for (int booleanTypeI = 0; booleanTypeI < numBoolVariations; booleanTypeI++)
+						{
+							const deUint32		booleanTypeFeat		= booleanTypeI == 1 ? UniformCase::FEATURE_BOOLEANAPITYPE_INT
+																	: booleanTypeI == 2 ? UniformCase::FEATURE_BOOLEANAPITYPE_UINT
+																	: 0;
+							const char* const	booleanTypeName		= booleanTypeI == 1 ? "int"
+																	: booleanTypeI == 2 ? "uint"
+																	: "float";
+							const string		nameWithBoolType	= varyBoolApiType ? collName + "api_" + booleanTypeName + "_" : collName;
+
+							for (int matrixTypeI = 0; matrixTypeI < numMatVariations; matrixTypeI++)
+							{
+								const string nameWithMatrixType = nameWithBoolType + (matrixTypeI == 1 ? "row_major_" : "");
+
+								for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+								{
+									const string	name							= nameWithMatrixType + getCaseShaderTypeName((CaseShaderType)shaderType);
+									const deUint32	arrayFirstElemNameNoIndexFeat	= referToFirstArrayElemWithoutIndexI == 0 ? 0 : UniformCase::FEATURE_ARRAY_FIRST_ELEM_NAME_NO_INDEX;
+
+									collectionTestGroup->addChild(new UniformAssignCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																						checkMethod, assignMethod,
+																						booleanTypeFeat | arrayFirstElemNameNoIndexFeat | (matrixTypeI == 1 ? UniformCase::FEATURE_MATRIXMODE_ROWMAJOR : 0)));
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// Cases that assign multiple basic-array elements with one glProgramUniform*v() (i.e. the count parameter is bigger than 1).
+
+	{
+		static const struct
+		{
+			UniformCase::Feature	arrayAssignMode;
+			const char*				name;
+			const char*				description;
+		} arrayAssignGroups[] =
+		{
+			{ UniformCase::FEATURE_ARRAYASSIGN_FULL,			"basic_array_assign_full",		"Assign entire basic-type arrays per glProgramUniform*v() call"				},
+			{ UniformCase::FEATURE_ARRAYASSIGN_BLOCKS_OF_TWO,	"basic_array_assign_partial",	"Assign two elements of a basic-type array per glProgramUniform*v() call"	}
+		};
+
+		for (int arrayAssignGroupNdx = 0; arrayAssignGroupNdx < DE_LENGTH_OF_ARRAY(arrayAssignGroups); arrayAssignGroupNdx++)
+		{
+			UniformCase::Feature	arrayAssignMode		= arrayAssignGroups[arrayAssignGroupNdx].arrayAssignMode;
+			const char* const		groupName			= arrayAssignGroups[arrayAssignGroupNdx].name;
+			const char* const		groupDesc			= arrayAssignGroups[arrayAssignGroupNdx].description;
+
+			TestCaseGroup* const curArrayAssignGroup = new TestCaseGroup(m_context, groupName, groupDesc);
+			addChild(curArrayAssignGroup);
+
+			static const int basicArrayCollectionGroups[] = { UNIFORMCOLLECTIONS_BASIC_ARRAY, UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT, UNIFORMCOLLECTIONS_MULTIPLE_BASIC_ARRAY };
+
+			for (int collectionGroupNdx = 0; collectionGroupNdx < DE_LENGTH_OF_ARRAY(basicArrayCollectionGroups); collectionGroupNdx++)
+			{
+				const UniformCollectionGroup&	collectionGroup		= defaultUniformCollections[basicArrayCollectionGroups[collectionGroupNdx]];
+				TestCaseGroup* const			collectionTestGroup	= new TestCaseGroup(m_context, collectionGroup.name.c_str(), "");
+				curArrayAssignGroup->addChild(collectionTestGroup);
+
+				for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+				{
+					const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+					const string								collName			= collectionCase.namePrefix;
+					const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+
+					for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+					{
+						const string name = collName + getCaseShaderTypeName((CaseShaderType)shaderType);
+						collectionTestGroup->addChild(new UniformAssignCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																			UniformAssignCase::CHECKMETHOD_GET_UNIFORM, UniformAssignCase::ASSIGNMETHOD_POINTER,
+																			arrayAssignMode));
+					}
+				}
+			}
+		}
+	}
+
+	// Cases with unused uniforms.
+
+	{
+		TestCaseGroup* const unusedUniformsGroup = new TestCaseGroup(m_context, "unused_uniforms", "Test with unused uniforms");
+		addChild(unusedUniformsGroup);
+
+		const UniformCollectionGroup& collectionGroup = defaultUniformCollections[UNIFORMCOLLECTIONS_ARRAY_IN_STRUCT];
+
+		for (int collectionNdx = 0; collectionNdx < (int)collectionGroup.cases.size(); collectionNdx++)
+		{
+			const UniformCollectionCase&				collectionCase		= collectionGroup.cases[collectionNdx];
+			const string								collName			= collectionCase.namePrefix;
+			const SharedPtr<const UniformCollection>&	uniformCollection	= collectionCase.uniformCollection;
+
+			for (int shaderType = 0; shaderType < (int)CASESHADERTYPE_LAST; shaderType++)
+			{
+				const string name = collName + getCaseShaderTypeName((CaseShaderType)shaderType);
+				unusedUniformsGroup->addChild(new UniformAssignCase(m_context, name.c_str(), "", (CaseShaderType)shaderType, uniformCollection,
+																	UniformAssignCase::CHECKMETHOD_GET_UNIFORM, UniformAssignCase::ASSIGNMETHOD_POINTER,
+																	UniformCase::FEATURE_ARRAYUSAGE_ONLY_MIDDLE_INDEX | UniformCase::FEATURE_UNIFORMUSAGE_EVERY_OTHER));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fProgramUniformTests.hpp b/modules/gles31/functional/es31fProgramUniformTests.hpp
new file mode 100644
index 0000000..ebcf5ff
--- /dev/null
+++ b/modules/gles31/functional/es31fProgramUniformTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FPROGRAMUNIFORMTESTS_HPP
+#define _ES31FPROGRAMUNIFORMTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief glProgramUniform*() tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ProgramUniformTests : public TestCaseGroup
+{
+public:
+							ProgramUniformTests		(Context& context);
+							~ProgramUniformTests	(void);
+
+	void					init					(void);
+
+private:
+							ProgramUniformTests		(const ProgramUniformTests& other);
+	ProgramUniformTests&	operator=				(const ProgramUniformTests& other);
+};
+
+} // Functional
+} // gles3
+} // deqp
+
+#endif // _ES31FPROGRAMUNIFORMTESTS_HPP
diff --git a/modules/gles31/functional/es31fSSBOArrayLengthTests.cpp b/modules/gles31/functional/es31fSSBOArrayLengthTests.cpp
new file mode 100644
index 0000000..d1831b6
--- /dev/null
+++ b/modules/gles31/functional/es31fSSBOArrayLengthTests.cpp
@@ -0,0 +1,312 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief SSBO array length tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fSSBOArrayLengthTests.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "tcuTestLog.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+
+#include <sstream>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+class SSBOArrayLengthCase : public TestCase
+{
+public:
+	enum ArrayAccess
+	{
+		ACCESS_DEFAULT = 0,
+		ACCESS_WRITEONLY,
+		ACCESS_READONLY,
+
+		ACCESS_LAST
+	};
+
+						SSBOArrayLengthCase		(Context& context, const char* name, const char* desc, ArrayAccess access, bool sized);
+						~SSBOArrayLengthCase	(void);
+
+	void				init					(void);
+	void				deinit					(void);
+	IterateResult		iterate					(void);
+
+private:
+	std::string			genComputeSource		(void) const;
+
+	const ArrayAccess	m_access;
+	const bool			m_sized;
+
+	glu::ShaderProgram*	m_shader;
+	deUint32			m_targetBufferID;
+	deUint32			m_outputBufferID;
+
+	static const int	s_fixedBufferSize = 16;
+};
+
+SSBOArrayLengthCase::SSBOArrayLengthCase (Context& context, const char* name, const char* desc, ArrayAccess access, bool sized)
+	: TestCase			(context, name, desc)
+	, m_access			(access)
+	, m_sized			(sized)
+	, m_shader			(DE_NULL)
+	, m_targetBufferID	(0)
+	, m_outputBufferID	(0)
+{
+}
+
+SSBOArrayLengthCase::~SSBOArrayLengthCase (void)
+{
+	deinit();
+}
+
+void SSBOArrayLengthCase::init (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const deUint32			invalidValue	= 0xFFFFFFFFUL;
+
+	// program
+	m_shader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genComputeSource()));
+	m_testCtx.getLog() << *m_shader;
+
+	if (!m_shader->isOk())
+		throw tcu::TestError("Failed to build shader");
+
+	// gen and attach buffers
+	gl.genBuffers(1, &m_outputBufferID);
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_outputBufferID);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, 2 * (int)sizeof(deUint32), &invalidValue, GL_DYNAMIC_COPY);
+
+	gl.genBuffers(1, &m_targetBufferID);
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_targetBufferID);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "create buffers");
+
+	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_outputBufferID);
+	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_targetBufferID);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind buffers");
+
+	// check the ssbo has expected layout
+	{
+		const deUint32		index	= gl.getProgramResourceIndex(m_shader->getProgram(), GL_BUFFER_VARIABLE, "Out.outLength");
+		const glw::GLenum	prop	= GL_OFFSET;
+		glw::GLint			result	= 0;
+
+		if (index == GL_INVALID_INDEX)
+			throw tcu::TestError("Failed to find outLength variable");
+
+		gl.getProgramResourceiv(m_shader->getProgram(), GL_BUFFER_VARIABLE, index, 1, &prop, 1, DE_NULL, &result);
+
+		if (result != 0)
+			throw tcu::TestError("Unexpected outLength location");
+	}
+	{
+		const deUint32		index	= gl.getProgramResourceIndex(m_shader->getProgram(), GL_BUFFER_VARIABLE, "Out.unused");
+		const glw::GLenum	prop	= GL_OFFSET;
+		glw::GLint			result	= 0;
+
+		if (index == GL_INVALID_INDEX)
+			throw tcu::TestError("Failed to find unused variable");
+
+		gl.getProgramResourceiv(m_shader->getProgram(), GL_BUFFER_VARIABLE, index, 1, &prop, 1, DE_NULL, &result);
+
+		if (result != 4)
+			throw tcu::TestError("Unexpected unused location");
+	}
+	{
+		const deUint32		index	= gl.getProgramResourceIndex(m_shader->getProgram(), GL_BUFFER_VARIABLE, "Target.array");
+		const glw::GLenum	prop	= GL_ARRAY_STRIDE;
+		glw::GLint			result	= 0;
+
+		if (index == GL_INVALID_INDEX)
+			throw tcu::TestError("Failed to find array variable");
+
+		gl.getProgramResourceiv(m_shader->getProgram(), GL_BUFFER_VARIABLE, index, 1, &prop, 1, DE_NULL, &result);
+
+		if (result != 4)
+			throw tcu::TestError("Unexpected array stride");
+	}
+}
+
+void SSBOArrayLengthCase::deinit (void)
+{
+	if (m_shader)
+	{
+		delete m_shader;
+		m_shader = DE_NULL;
+	}
+
+	if (m_targetBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_targetBufferID);
+		m_targetBufferID = 0;
+	}
+
+	if (m_outputBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_outputBufferID);
+		m_outputBufferID = 0;
+	}
+}
+
+SSBOArrayLengthCase::IterateResult SSBOArrayLengthCase::iterate (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	bool					error	= false;
+
+	// Update buffer size
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Allocating float memory buffer with " << static_cast<int>(s_fixedBufferSize) << " elements." << tcu::TestLog::EndMessage;
+
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_targetBufferID);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, s_fixedBufferSize * (int)sizeof(float), DE_NULL, GL_DYNAMIC_COPY);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "update buffer");
+
+	// Run compute
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Running compute shader." << tcu::TestLog::EndMessage;
+
+	gl.useProgram(m_shader->getProgram());
+	gl.dispatchCompute(1, 1, 1);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch");
+
+	// Verify
+	{
+		const void* ptr;
+
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_outputBufferID);
+		ptr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, (int)sizeof(deUint32), GL_MAP_READ_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "map");
+
+		if (!ptr)
+			throw tcu::TestError("mapBufferRange returned NULL");
+
+		if (*(const deUint32*)ptr != (deUint32)s_fixedBufferSize)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Length returned was " << *(const deUint32*)ptr << ", expected " << static_cast<int>(s_fixedBufferSize) << tcu::TestLog::EndMessage;
+			error = true;
+		}
+		else
+			m_testCtx.getLog() << tcu::TestLog::Message << "Length returned was correct." << tcu::TestLog::EndMessage;
+
+		if (gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER) == GL_FALSE)
+			throw tcu::TestError("unmapBuffer returned false");
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "unmap");
+	}
+
+	if (!error)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	return STOP;
+}
+
+std::string SSBOArrayLengthCase::genComputeSource (void) const
+{
+	const std::string qualifierStr	= (m_access == ACCESS_READONLY) ? ("readonly ") : (m_access == ACCESS_WRITEONLY) ? ("writeonly ") : ("");
+	const std::string sizeStr		= (m_sized) ? (de::toString(static_cast<int>(s_fixedBufferSize))) : ("");
+
+	std::ostringstream buf;
+	buf << "#version 310 es\n"
+		<< "layout(local_size_x = 1, local_size_y = 1) in;\n"
+		<< "layout(std430) buffer;\n"
+		<< "\n"
+		<< "layout(binding = 0) buffer Out\n"
+		<< "{\n"
+		<< "    int outLength;\n"
+		<< "    uint unused;\n"
+		<< "} sb_out;\n"
+		<< "layout(binding = 1) " << qualifierStr << "buffer Target\n"
+		<< "{\n"
+		<< "    float array[" << sizeStr << "];\n"
+		<< "} sb_target;\n\n"
+		<< "void main (void)\n"
+		<< "{\n";
+
+	// read
+	if (m_access == ACCESS_READONLY || m_access == ACCESS_DEFAULT)
+		buf << "    sb_out.unused = uint(sb_target.array[1]);\n";
+
+	// write
+	if (m_access == ACCESS_WRITEONLY || m_access == ACCESS_DEFAULT)
+		buf << "    sb_target.array[2] = float(sb_out.unused);\n";
+
+	// actual test
+	buf << "\n"
+		<< "    sb_out.outLength = sb_target.array.length();\n"
+		<< "}\n";
+
+	return buf.str();
+}
+
+} // anonymous
+
+SSBOArrayLengthTests::SSBOArrayLengthTests (Context& context)
+	: TestCaseGroup(context, "array_length", "Test array.length()")
+{
+}
+
+SSBOArrayLengthTests::~SSBOArrayLengthTests (void)
+{
+}
+
+void SSBOArrayLengthTests::init (void)
+{
+	static const struct Qualifier
+	{
+		SSBOArrayLengthCase::ArrayAccess	access;
+		const char*							name;
+		const char*							desc;
+	}  qualifiers[] =
+	{
+		{ SSBOArrayLengthCase::ACCESS_DEFAULT,		"",				""			},
+		{ SSBOArrayLengthCase::ACCESS_WRITEONLY,	"writeonly_",	"writeonly"	},
+		{ SSBOArrayLengthCase::ACCESS_READONLY,		"readonly_",	"readonly"	},
+	};
+
+	static const bool arraysSized[]	= { true, false };
+
+	for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(arraysSized); ++sizeNdx)
+	for (int qualifierNdx = 0; qualifierNdx < DE_LENGTH_OF_ARRAY(qualifiers); ++qualifierNdx)
+	{
+		const std::string name = std::string() + ((arraysSized[sizeNdx]) ? ("sized_") : ("unsized_")) + qualifiers[qualifierNdx].name + "array";
+		const std::string desc = std::string("Test length() of ") + ((arraysSized[sizeNdx]) ? ("sized ") : ("unsized ")) + qualifiers[qualifierNdx].name + " array";
+
+		this->addChild(new SSBOArrayLengthCase(m_context, name.c_str(), desc.c_str(), qualifiers[qualifierNdx].access, arraysSized[sizeNdx]));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fSSBOArrayLengthTests.hpp b/modules/gles31/functional/es31fSSBOArrayLengthTests.hpp
new file mode 100644
index 0000000..5be1574
--- /dev/null
+++ b/modules/gles31/functional/es31fSSBOArrayLengthTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSSBOARRAYLENGTHTESTS_HPP
+#define _ES31FSSBOARRAYLENGTHTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief SSBO array length tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class SSBOArrayLengthTests : public TestCaseGroup
+{
+public:
+							SSBOArrayLengthTests	(Context& context);
+							~SSBOArrayLengthTests	(void);
+
+	void					init					(void);
+
+private:
+							SSBOArrayLengthTests	(const SSBOArrayLengthTests& other);
+	SSBOArrayLengthTests&	operator=				(const SSBOArrayLengthTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSSBOARRAYLENGTHTESTS_HPP
diff --git a/modules/gles31/functional/es31fSSBOLayoutCase.cpp b/modules/gles31/functional/es31fSSBOLayoutCase.cpp
new file mode 100644
index 0000000..23d36e2
--- /dev/null
+++ b/modules/gles31/functional/es31fSSBOLayoutCase.cpp
@@ -0,0 +1,2667 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief SSBO layout case.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fSSBOLayoutCase.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "gluProgramInterfaceQuery.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluVarTypeUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMemory.h"
+#include "deString.h"
+#include "deMath.h"
+
+#include <algorithm>
+#include <map>
+
+using tcu::TestLog;
+using std::string;
+using std::vector;
+using std::map;
+
+namespace deqp
+{
+namespace gles31
+{
+
+using glu::VarType;
+using glu::StructType;
+using glu::StructMember;
+
+namespace bb
+{
+
+struct LayoutFlagsFmt
+{
+	deUint32 flags;
+	LayoutFlagsFmt (deUint32 flags_) : flags(flags_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const LayoutFlagsFmt& fmt)
+{
+	static const struct
+	{
+		deUint32	bit;
+		const char*	token;
+	} bitDesc[] =
+	{
+		{ LAYOUT_SHARED,		"shared"		},
+		{ LAYOUT_PACKED,		"packed"		},
+		{ LAYOUT_STD140,		"std140"		},
+		{ LAYOUT_STD430,		"std430"		},
+		{ LAYOUT_ROW_MAJOR,		"row_major"		},
+		{ LAYOUT_COLUMN_MAJOR,	"column_major"	}
+	};
+
+	deUint32 remBits = fmt.flags;
+	for (int descNdx = 0; descNdx < DE_LENGTH_OF_ARRAY(bitDesc); descNdx++)
+	{
+		if (remBits & bitDesc[descNdx].bit)
+		{
+			if (remBits != fmt.flags)
+				str << ", ";
+			str << bitDesc[descNdx].token;
+			remBits &= ~bitDesc[descNdx].bit;
+		}
+	}
+	DE_ASSERT(remBits == 0);
+	return str;
+}
+
+// BufferVar implementation.
+
+BufferVar::BufferVar (const char* name, const VarType& type, deUint32 flags)
+	: m_name	(name)
+	, m_type	(type)
+	, m_flags	(flags)
+{
+}
+
+// BufferBlock implementation.
+
+BufferBlock::BufferBlock (const char* blockName)
+	: m_blockName	(blockName)
+	, m_arraySize	(-1)
+	, m_flags		(0)
+{
+	setArraySize(0);
+}
+
+void BufferBlock::setArraySize (int arraySize)
+{
+	DE_ASSERT(arraySize >= 0);
+	m_lastUnsizedArraySizes.resize(arraySize == 0 ? 1 : arraySize, 0);
+	m_arraySize = arraySize;
+}
+
+struct BlockLayoutEntry
+{
+	BlockLayoutEntry (void)
+		: size(0)
+	{
+	}
+
+	std::string			name;
+	int					size;
+	std::vector<int>	activeVarIndices;
+};
+
+std::ostream& operator<< (std::ostream& stream, const BlockLayoutEntry& entry)
+{
+	stream << entry.name << " { name = " << entry.name
+		   << ", size = " << entry.size
+		   << ", activeVarIndices = [";
+
+	for (vector<int>::const_iterator i = entry.activeVarIndices.begin(); i != entry.activeVarIndices.end(); i++)
+	{
+		if (i != entry.activeVarIndices.begin())
+			stream << ", ";
+		stream << *i;
+	}
+
+	stream << "] }";
+	return stream;
+}
+
+struct BufferVarLayoutEntry
+{
+	BufferVarLayoutEntry (void)
+		: type					(glu::TYPE_LAST)
+		, blockNdx				(-1)
+		, offset				(-1)
+		, arraySize				(-1)
+		, arrayStride			(-1)
+		, matrixStride			(-1)
+		, topLevelArraySize		(-1)
+		, topLevelArrayStride	(-1)
+		, isRowMajor			(false)
+	{
+	}
+
+	std::string			name;
+	glu::DataType		type;
+	int					blockNdx;
+	int					offset;
+	int					arraySize;
+	int					arrayStride;
+	int					matrixStride;
+	int					topLevelArraySize;
+	int					topLevelArrayStride;
+	bool				isRowMajor;
+};
+
+// \note For arrays of basic types (not aggregate types), TOP_LEVEL_ARRAY_SIZE = 1,
+//		 but for practical reasons these functions report ARRAY_SIZE as top level
+//		 array size for such arrays (and 1 as array size).
+//		 See Khronos bug 11753
+
+static bool isBasicTypeArray (const BufferVarLayoutEntry& entry)
+{
+	// \note Fails to differentiate cases like a[1][1] where implementation assigns
+	//		 0 as both top-level and bottom-level array stride (which is valid for
+	//		 impl-dependent layouts) but this doesn't matter in practice.
+	return entry.topLevelArraySize == 1 && entry.topLevelArrayStride == 0 && (entry.arraySize != 1 || entry.arrayStride != 0);
+}
+
+static int getTopLevelArraySize (const BufferVarLayoutEntry& entry)
+{
+	return isBasicTypeArray(entry) ? entry.arraySize : entry.topLevelArraySize;
+}
+
+static int getTopLevelArrayStride (const BufferVarLayoutEntry& entry)
+{
+	return isBasicTypeArray(entry) ? entry.arrayStride : entry.topLevelArrayStride;
+}
+
+static int getArraySize (const BufferVarLayoutEntry& entry)
+{
+	return isBasicTypeArray(entry) ? 1 : entry.arraySize;
+}
+
+static int getArrayStride (const BufferVarLayoutEntry& entry)
+{
+	return isBasicTypeArray(entry) ? 0 : entry.arrayStride;
+}
+
+static bool isUnsizedTopLevelArray (const BufferVarLayoutEntry& entry)
+{
+	return getTopLevelArraySize(entry) == 0;
+}
+
+std::ostream& operator<< (std::ostream& stream, const BufferVarLayoutEntry& entry)
+{
+	stream << entry.name << " { type = " << glu::getDataTypeName(entry.type)
+		   << ", blockNdx = " << entry.blockNdx
+		   << ", offset = " << entry.offset
+		   << ", arraySize = " << entry.arraySize
+		   << ", arrayStride = " << entry.arrayStride
+		   << ", matrixStride = " << entry.matrixStride
+		   << ", topLevelArraySize = " << entry.topLevelArraySize
+		   << ", topLevelArrayStride = " << entry.topLevelArrayStride
+		   << ", isRowMajor = " << (entry.isRowMajor ? "true" : "false")
+		   << " }";
+	return stream;
+}
+
+class BufferLayout
+{
+public:
+	std::vector<BlockLayoutEntry>		blocks;
+	std::vector<BufferVarLayoutEntry>	bufferVars;
+
+	int									getVariableIndex		(const string& name) const;
+	int									getBlockIndex			(const string& name) const;
+};
+
+// \todo [2012-01-24 pyry] Speed up lookups using hash.
+
+int BufferLayout::getVariableIndex (const string& name) const
+{
+	for (int ndx = 0; ndx < (int)bufferVars.size(); ndx++)
+	{
+		if (bufferVars[ndx].name == name)
+			return ndx;
+	}
+	return -1;
+}
+
+int BufferLayout::getBlockIndex (const string& name) const
+{
+	for (int ndx = 0; ndx < (int)blocks.size(); ndx++)
+	{
+		if (blocks[ndx].name == name)
+			return ndx;
+	}
+	return -1;
+}
+
+// ShaderInterface implementation.
+
+ShaderInterface::ShaderInterface (void)
+{
+}
+
+ShaderInterface::~ShaderInterface (void)
+{
+	for (std::vector<StructType*>::iterator i = m_structs.begin(); i != m_structs.end(); i++)
+		delete *i;
+
+	for (std::vector<BufferBlock*>::iterator i = m_bufferBlocks.begin(); i != m_bufferBlocks.end(); i++)
+		delete *i;
+}
+
+StructType& ShaderInterface::allocStruct (const char* name)
+{
+	m_structs.reserve(m_structs.size()+1);
+	m_structs.push_back(new StructType(name));
+	return *m_structs.back();
+}
+
+struct StructNameEquals
+{
+	std::string name;
+
+	StructNameEquals (const char* name_) : name(name_) {}
+
+	bool operator() (const StructType* type) const
+	{
+		return type->getTypeName() && name == type->getTypeName();
+	}
+};
+
+const StructType* ShaderInterface::findStruct (const char* name) const
+{
+	std::vector<StructType*>::const_iterator pos = std::find_if(m_structs.begin(), m_structs.end(), StructNameEquals(name));
+	return pos != m_structs.end() ? *pos : DE_NULL;
+}
+
+void ShaderInterface::getNamedStructs (std::vector<const StructType*>& structs) const
+{
+	for (std::vector<StructType*>::const_iterator i = m_structs.begin(); i != m_structs.end(); i++)
+	{
+		if ((*i)->getTypeName() != DE_NULL)
+			structs.push_back(*i);
+	}
+}
+
+BufferBlock& ShaderInterface::allocBlock (const char* name)
+{
+	m_bufferBlocks.reserve(m_bufferBlocks.size()+1);
+	m_bufferBlocks.push_back(new BufferBlock(name));
+	return *m_bufferBlocks.back();
+}
+
+// BlockDataPtr
+
+struct BlockDataPtr
+{
+	void*		ptr;
+	int			size;						//!< Redundant, for debugging purposes.
+	int			lastUnsizedArraySize;
+
+	BlockDataPtr (void* ptr_, int size_, int lastUnsizedArraySize_)
+		: ptr					(ptr_)
+		, size					(size_)
+		, lastUnsizedArraySize	(lastUnsizedArraySize_)
+	{
+	}
+
+	BlockDataPtr (void)
+		: ptr					(DE_NULL)
+		, size					(0)
+		, lastUnsizedArraySize	(0)
+	{
+	}
+};
+
+namespace // Utilities
+{
+
+int findBlockIndex (const BufferLayout& layout, const string& name)
+{
+	for (int ndx = 0; ndx < (int)layout.blocks.size(); ndx++)
+	{
+		if (layout.blocks[ndx].name == name)
+			return ndx;
+	}
+	return -1;
+}
+
+// Layout computation.
+
+int getDataTypeByteSize (glu::DataType type)
+{
+	return glu::getDataTypeScalarSize(type)*sizeof(deUint32);
+}
+
+int getDataTypeByteAlignment (glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_FLOAT:
+		case glu::TYPE_INT:
+		case glu::TYPE_UINT:
+		case glu::TYPE_BOOL:		return 1*sizeof(deUint32);
+
+		case glu::TYPE_FLOAT_VEC2:
+		case glu::TYPE_INT_VEC2:
+		case glu::TYPE_UINT_VEC2:
+		case glu::TYPE_BOOL_VEC2:	return 2*sizeof(deUint32);
+
+		case glu::TYPE_FLOAT_VEC3:
+		case glu::TYPE_INT_VEC3:
+		case glu::TYPE_UINT_VEC3:
+		case glu::TYPE_BOOL_VEC3:	// Fall-through to vec4
+
+		case glu::TYPE_FLOAT_VEC4:
+		case glu::TYPE_INT_VEC4:
+		case glu::TYPE_UINT_VEC4:
+		case glu::TYPE_BOOL_VEC4:	return 4*sizeof(deUint32);
+
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static inline int deRoundUp32 (int a, int b)
+{
+	int d = a/b;
+	return d*b == a ? a : (d+1)*b;
+}
+
+int computeStd140BaseAlignment (const VarType& type, deUint32 layoutFlags)
+{
+	const int vec4Alignment = sizeof(deUint32)*4;
+
+	if (type.isBasicType())
+	{
+		glu::DataType basicType = type.getBasicType();
+
+		if (glu::isDataTypeMatrix(basicType))
+		{
+			const bool	isRowMajor	= !!(layoutFlags & LAYOUT_ROW_MAJOR);
+			const int	vecSize		= isRowMajor ? glu::getDataTypeMatrixNumColumns(basicType)
+												 : glu::getDataTypeMatrixNumRows(basicType);
+			const int	vecAlign	= deAlign32(getDataTypeByteAlignment(glu::getDataTypeFloatVec(vecSize)), vec4Alignment);
+
+			return vecAlign;
+		}
+		else
+			return getDataTypeByteAlignment(basicType);
+	}
+	else if (type.isArrayType())
+	{
+		int elemAlignment = computeStd140BaseAlignment(type.getElementType(), layoutFlags);
+
+		// Round up to alignment of vec4
+		return deAlign32(elemAlignment, vec4Alignment);
+	}
+	else
+	{
+		DE_ASSERT(type.isStructType());
+
+		int maxBaseAlignment = 0;
+
+		for (StructType::ConstIterator memberIter = type.getStructPtr()->begin(); memberIter != type.getStructPtr()->end(); memberIter++)
+			maxBaseAlignment = de::max(maxBaseAlignment, computeStd140BaseAlignment(memberIter->getType(), layoutFlags));
+
+		return deAlign32(maxBaseAlignment, vec4Alignment);
+	}
+}
+
+int computeStd430BaseAlignment (const VarType& type, deUint32 layoutFlags)
+{
+	// Otherwise identical to std140 except that alignment of structures and arrays
+	// are not rounded up to alignment of vec4.
+
+	if (type.isBasicType())
+	{
+		glu::DataType basicType = type.getBasicType();
+
+		if (glu::isDataTypeMatrix(basicType))
+		{
+			const bool	isRowMajor	= !!(layoutFlags & LAYOUT_ROW_MAJOR);
+			const int	vecSize		= isRowMajor ? glu::getDataTypeMatrixNumColumns(basicType)
+												 : glu::getDataTypeMatrixNumRows(basicType);
+			const int	vecAlign	= getDataTypeByteAlignment(glu::getDataTypeFloatVec(vecSize));
+
+			return vecAlign;
+		}
+		else
+			return getDataTypeByteAlignment(basicType);
+	}
+	else if (type.isArrayType())
+	{
+		return computeStd430BaseAlignment(type.getElementType(), layoutFlags);
+	}
+	else
+	{
+		DE_ASSERT(type.isStructType());
+
+		int maxBaseAlignment = 0;
+
+		for (StructType::ConstIterator memberIter = type.getStructPtr()->begin(); memberIter != type.getStructPtr()->end(); memberIter++)
+			maxBaseAlignment = de::max(maxBaseAlignment, computeStd430BaseAlignment(memberIter->getType(), layoutFlags));
+
+		return maxBaseAlignment;
+	}
+}
+
+inline deUint32 mergeLayoutFlags (deUint32 prevFlags, deUint32 newFlags)
+{
+	const deUint32	packingMask		= LAYOUT_PACKED|LAYOUT_SHARED|LAYOUT_STD140|LAYOUT_STD430;
+	const deUint32	matrixMask		= LAYOUT_ROW_MAJOR|LAYOUT_COLUMN_MAJOR;
+
+	deUint32 mergedFlags = 0;
+
+	mergedFlags |= ((newFlags & packingMask)	? newFlags : prevFlags) & packingMask;
+	mergedFlags |= ((newFlags & matrixMask)		? newFlags : prevFlags) & matrixMask;
+
+	return mergedFlags;
+}
+
+//! Appends all child elements to layout, returns value that should be appended to offset.
+int computeReferenceLayout (
+	BufferLayout&		layout,
+	int					curBlockNdx,
+	int					baseOffset,
+	const std::string&	curPrefix,
+	const VarType&		type,
+	deUint32			layoutFlags)
+{
+	// Reference layout uses std430 rules by default. std140 rules are
+	// choosen only for blocks that have std140 layout.
+	const bool	isStd140			= (layoutFlags & LAYOUT_STD140) != 0;
+	const int	baseAlignment		= isStd140 ? computeStd140BaseAlignment(type, layoutFlags)
+											   : computeStd430BaseAlignment(type, layoutFlags);
+	int			curOffset			= deAlign32(baseOffset, baseAlignment);
+	const int	topLevelArraySize	= 1; // Default values
+	const int	topLevelArrayStride	= 0;
+
+	if (type.isBasicType())
+	{
+		const glu::DataType		basicType	= type.getBasicType();
+		BufferVarLayoutEntry	entry;
+
+		entry.name					= curPrefix;
+		entry.type					= basicType;
+		entry.arraySize				= 1;
+		entry.arrayStride			= 0;
+		entry.matrixStride			= 0;
+		entry.topLevelArraySize		= topLevelArraySize;
+		entry.topLevelArrayStride	= topLevelArrayStride;
+		entry.blockNdx				= curBlockNdx;
+
+		if (glu::isDataTypeMatrix(basicType))
+		{
+			// Array of vectors as specified in rules 5 & 7.
+			const bool	isRowMajor			= !!(layoutFlags & LAYOUT_ROW_MAJOR);
+			const int	numVecs				= isRowMajor ? glu::getDataTypeMatrixNumRows(basicType)
+														 : glu::getDataTypeMatrixNumColumns(basicType);
+
+			entry.offset		= curOffset;
+			entry.matrixStride	= baseAlignment;
+			entry.isRowMajor	= isRowMajor;
+
+			curOffset += numVecs*baseAlignment;
+		}
+		else
+		{
+			// Scalar or vector.
+			entry.offset = curOffset;
+
+			curOffset += getDataTypeByteSize(basicType);
+		}
+
+		layout.bufferVars.push_back(entry);
+	}
+	else if (type.isArrayType())
+	{
+		const VarType&	elemType	= type.getElementType();
+
+		if (elemType.isBasicType() && !glu::isDataTypeMatrix(elemType.getBasicType()))
+		{
+			// Array of scalars or vectors.
+			const glu::DataType		elemBasicType	= elemType.getBasicType();
+			const int				stride			= baseAlignment;
+			BufferVarLayoutEntry	entry;
+
+			entry.name					= curPrefix + "[0]"; // Array variables are always postfixed with [0]
+			entry.type					= elemBasicType;
+			entry.blockNdx				= curBlockNdx;
+			entry.offset				= curOffset;
+			entry.arraySize				= type.getArraySize();
+			entry.arrayStride			= stride;
+			entry.matrixStride			= 0;
+			entry.topLevelArraySize		= topLevelArraySize;
+			entry.topLevelArrayStride	= topLevelArrayStride;
+
+			curOffset += stride*type.getArraySize();
+
+			layout.bufferVars.push_back(entry);
+		}
+		else if (elemType.isBasicType() && glu::isDataTypeMatrix(elemType.getBasicType()))
+		{
+			// Array of matrices.
+			const glu::DataType			elemBasicType	= elemType.getBasicType();
+			const bool					isRowMajor		= !!(layoutFlags & LAYOUT_ROW_MAJOR);
+			const int					numVecs			= isRowMajor ? glu::getDataTypeMatrixNumRows(elemBasicType)
+																	 : glu::getDataTypeMatrixNumColumns(elemBasicType);
+			const int					vecStride		= baseAlignment;
+			BufferVarLayoutEntry		entry;
+
+			entry.name					= curPrefix + "[0]"; // Array variables are always postfixed with [0]
+			entry.type					= elemBasicType;
+			entry.blockNdx				= curBlockNdx;
+			entry.offset				= curOffset;
+			entry.arraySize				= type.getArraySize();
+			entry.arrayStride			= vecStride*numVecs;
+			entry.matrixStride			= vecStride;
+			entry.isRowMajor			= isRowMajor;
+			entry.topLevelArraySize		= topLevelArraySize;
+			entry.topLevelArrayStride	= topLevelArrayStride;
+
+			curOffset += numVecs*vecStride*type.getArraySize();
+
+			layout.bufferVars.push_back(entry);
+		}
+		else
+		{
+			DE_ASSERT(elemType.isStructType() || elemType.isArrayType());
+
+			for (int elemNdx = 0; elemNdx < type.getArraySize(); elemNdx++)
+				curOffset += computeReferenceLayout(layout, curBlockNdx, curOffset, curPrefix + "[" + de::toString(elemNdx) + "]", type.getElementType(), layoutFlags);
+		}
+	}
+	else
+	{
+		DE_ASSERT(type.isStructType());
+
+		for (StructType::ConstIterator memberIter = type.getStructPtr()->begin(); memberIter != type.getStructPtr()->end(); memberIter++)
+			curOffset += computeReferenceLayout(layout, curBlockNdx, curOffset, curPrefix + "." + memberIter->getName(), memberIter->getType(), layoutFlags);
+
+		curOffset = deAlign32(curOffset, baseAlignment);
+	}
+
+	return curOffset-baseOffset;
+}
+
+//! Appends all child elements to layout, returns offset increment.
+int computeReferenceLayout (BufferLayout& layout, int curBlockNdx, const std::string& blockPrefix, int baseOffset, const BufferVar& bufVar, deUint32 blockLayoutFlags)
+{
+	const VarType&	varType			= bufVar.getType();
+	const deUint32	combinedFlags	= mergeLayoutFlags(blockLayoutFlags, bufVar.getFlags());
+
+	if (varType.isArrayType())
+	{
+		// Top-level arrays need special care.
+		const int		topLevelArraySize	= varType.getArraySize() == VarType::UNSIZED_ARRAY ? 0 : varType.getArraySize();
+		const string	prefix				= blockPrefix + bufVar.getName() + "[0]";
+		const bool		isStd140			= (blockLayoutFlags & LAYOUT_STD140) != 0;
+		const int		vec4Align			= sizeof(deUint32)*4;
+		const int		baseAlignment		= isStd140 ? computeStd140BaseAlignment(varType, combinedFlags)
+													   : computeStd430BaseAlignment(varType, combinedFlags);
+		int				curOffset			= deAlign32(baseOffset, baseAlignment);
+		const VarType&	elemType			= varType.getElementType();
+
+		if (elemType.isBasicType() && !glu::isDataTypeMatrix(elemType.getBasicType()))
+		{
+			// Array of scalars or vectors.
+			const glu::DataType		elemBasicType	= elemType.getBasicType();
+			const int				elemBaseAlign	= getDataTypeByteAlignment(elemBasicType);
+			const int				stride			= isStd140 ? deAlign32(elemBaseAlign, vec4Align) : elemBaseAlign;
+			BufferVarLayoutEntry	entry;
+
+			entry.name					= prefix;
+			entry.topLevelArraySize		= 1;
+			entry.topLevelArrayStride	= 0;
+			entry.type					= elemBasicType;
+			entry.blockNdx				= curBlockNdx;
+			entry.offset				= curOffset;
+			entry.arraySize				= topLevelArraySize;
+			entry.arrayStride			= stride;
+			entry.matrixStride			= 0;
+
+			layout.bufferVars.push_back(entry);
+
+			curOffset += stride*topLevelArraySize;
+		}
+		else if (elemType.isBasicType() && glu::isDataTypeMatrix(elemType.getBasicType()))
+		{
+			// Array of matrices.
+			const glu::DataType		elemBasicType	= elemType.getBasicType();
+			const bool				isRowMajor		= !!(combinedFlags & LAYOUT_ROW_MAJOR);
+			const int				vecSize			= isRowMajor ? glu::getDataTypeMatrixNumColumns(elemBasicType)
+																 : glu::getDataTypeMatrixNumRows(elemBasicType);
+			const int				numVecs			= isRowMajor ? glu::getDataTypeMatrixNumRows(elemBasicType)
+																 : glu::getDataTypeMatrixNumColumns(elemBasicType);
+			const glu::DataType		vecType			= glu::getDataTypeFloatVec(vecSize);
+			const int				vecBaseAlign	= getDataTypeByteAlignment(vecType);
+			const int				stride			= isStd140 ? deAlign32(vecBaseAlign, vec4Align) : vecBaseAlign;
+			BufferVarLayoutEntry	entry;
+
+			entry.name					= prefix;
+			entry.topLevelArraySize		= 1;
+			entry.topLevelArrayStride	= 0;
+			entry.type					= elemBasicType;
+			entry.blockNdx				= curBlockNdx;
+			entry.offset				= curOffset;
+			entry.arraySize				= topLevelArraySize;
+			entry.arrayStride			= stride*numVecs;
+			entry.matrixStride			= stride;
+			entry.isRowMajor			= isRowMajor;
+
+			layout.bufferVars.push_back(entry);
+
+			curOffset += stride*numVecs*topLevelArraySize;
+		}
+		else
+		{
+			DE_ASSERT(elemType.isStructType() || elemType.isArrayType());
+
+			// Struct base alignment is not added multiple times as curOffset supplied to computeReferenceLayout
+			// was already aligned correctly. Thus computeReferenceLayout should not add any extra padding
+			// before struct. Padding after struct will be added as it should.
+			//
+			// Stride could be computed prior to creating child elements, but it would essentially require running
+			// the layout computation twice. Instead we fix stride to child elements afterwards.
+
+			const int	firstChildNdx	= (int)layout.bufferVars.size();
+			const int	stride			= computeReferenceLayout(layout, curBlockNdx, curOffset, prefix, varType.getElementType(), combinedFlags);
+
+			for (int childNdx = firstChildNdx; childNdx < (int)layout.bufferVars.size(); childNdx++)
+			{
+				layout.bufferVars[childNdx].topLevelArraySize	= topLevelArraySize;
+				layout.bufferVars[childNdx].topLevelArrayStride	= stride;
+			}
+
+			curOffset += stride*topLevelArraySize;
+		}
+
+		return curOffset-baseOffset;
+	}
+	else
+		return computeReferenceLayout(layout, curBlockNdx, baseOffset, blockPrefix + bufVar.getName(), varType, combinedFlags);
+}
+
+void computeReferenceLayout (BufferLayout& layout, const ShaderInterface& interface)
+{
+	int numBlocks = interface.getNumBlocks();
+
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		const BufferBlock&	block			= interface.getBlock(blockNdx);
+		bool				hasInstanceName	= block.getInstanceName() != DE_NULL;
+		std::string			blockPrefix		= hasInstanceName ? (std::string(block.getBlockName()) + ".") : std::string("");
+		int					curOffset		= 0;
+		int					activeBlockNdx	= (int)layout.blocks.size();
+		int					firstVarNdx		= (int)layout.bufferVars.size();
+
+		for (BufferBlock::const_iterator varIter = block.begin(); varIter != block.end(); varIter++)
+		{
+			const BufferVar& bufVar = *varIter;
+			curOffset += computeReferenceLayout(layout, activeBlockNdx,  blockPrefix, curOffset, bufVar, block.getFlags());
+		}
+
+		int	varIndicesEnd	= (int)layout.bufferVars.size();
+		int	blockSize		= curOffset;
+		int	numInstances	= block.isArray() ? block.getArraySize() : 1;
+
+		// Create block layout entries for each instance.
+		for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		{
+			// Allocate entry for instance.
+			layout.blocks.push_back(BlockLayoutEntry());
+			BlockLayoutEntry& blockEntry = layout.blocks.back();
+
+			blockEntry.name = block.getBlockName();
+			blockEntry.size = blockSize;
+
+			// Compute active variable set for block.
+			for (int varNdx = firstVarNdx; varNdx < varIndicesEnd; varNdx++)
+				blockEntry.activeVarIndices.push_back(varNdx);
+
+			if (block.isArray())
+				blockEntry.name += "[" + de::toString(instanceNdx) + "]";
+		}
+	}
+}
+
+// Value generator.
+
+void generateValue (const BufferVarLayoutEntry& entry, int topLevelArraySize, void* basePtr, de::Random& rnd)
+{
+	const glu::DataType	scalarType		= glu::getDataTypeScalarType(entry.type);
+	const int			scalarSize		= glu::getDataTypeScalarSize(entry.type);
+	const int			arraySize		= getArraySize(entry);
+	const int			topLevelStride	= getTopLevelArrayStride(entry);
+	const int			arrayStride		= getArrayStride(entry);
+	const bool			isMatrix		= glu::isDataTypeMatrix(entry.type);
+	const int			numVecs			= isMatrix ? (entry.isRowMajor ? glu::getDataTypeMatrixNumRows(entry.type) : glu::getDataTypeMatrixNumColumns(entry.type)) : 1;
+	const int			vecSize			= scalarSize / numVecs;
+	const int			compSize		= sizeof(deUint32);
+
+	DE_ASSERT(scalarSize%numVecs == 0);
+	DE_ASSERT(topLevelArraySize >= 0);
+	DE_ASSERT(arraySize > 0);
+
+	for (int topElemNdx = 0; topElemNdx < topLevelArraySize; topElemNdx++)
+	{
+		deUint8* const topElemPtr = (deUint8*)basePtr + entry.offset + topElemNdx*topLevelStride;
+
+		for (int elemNdx = 0; elemNdx < arraySize; elemNdx++)
+		{
+			deUint8* const elemPtr = topElemPtr + elemNdx*arrayStride;
+
+			for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
+			{
+				deUint8* const vecPtr = elemPtr + (isMatrix ? vecNdx*entry.matrixStride : 0);
+
+				for (int compNdx = 0; compNdx < vecSize; compNdx++)
+				{
+					deUint8* const compPtr = vecPtr + compSize*compNdx;
+
+					switch (scalarType)
+					{
+						case glu::TYPE_FLOAT:	*((float*)compPtr)		= (float)rnd.getInt(-9, 9);						break;
+						case glu::TYPE_INT:		*((int*)compPtr)		= rnd.getInt(-9, 9);							break;
+						case glu::TYPE_UINT:	*((deUint32*)compPtr)	= (deUint32)rnd.getInt(0, 9);					break;
+						// \note Random bit pattern is used for true values. Spec states that all non-zero values are
+						//       interpreted as true but some implementations fail this.
+						case glu::TYPE_BOOL:	*((deUint32*)compPtr)	= rnd.getBool() ? rnd.getUint32()|1u : 0u;		break;
+						default:
+							DE_ASSERT(false);
+					}
+				}
+			}
+		}
+	}
+}
+
+void generateValues (const BufferLayout& layout, const vector<BlockDataPtr>& blockPointers, deUint32 seed)
+{
+	de::Random	rnd			(seed);
+	const int	numBlocks	= (int)layout.blocks.size();
+
+	DE_ASSERT(numBlocks == (int)blockPointers.size());
+
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		const BlockLayoutEntry&	blockLayout	= layout.blocks[blockNdx];
+		const BlockDataPtr&		blockPtr	= blockPointers[blockNdx];
+		const int				numEntries	= (int)layout.blocks[blockNdx].activeVarIndices.size();
+
+		for (int entryNdx = 0; entryNdx < numEntries; entryNdx++)
+		{
+			const int					varNdx				= blockLayout.activeVarIndices[entryNdx];
+			const BufferVarLayoutEntry&	varEntry			= layout.bufferVars[varNdx];
+			const bool					unsizedArray		= (entryNdx+1 == numEntries) && isUnsizedTopLevelArray(varEntry);
+			const int					topLevelArraySize	= unsizedArray ? blockPtr.lastUnsizedArraySize : getTopLevelArraySize(varEntry);
+
+			generateValue(varEntry, topLevelArraySize, blockPtr.ptr, rnd);
+		}
+	}
+}
+
+// Shader generator.
+
+const char* getCompareFuncForType (glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_FLOAT:			return "bool compare_float    (highp float a, highp float b)  { return abs(a - b) < 0.05; }\n";
+		case glu::TYPE_FLOAT_VEC2:		return "bool compare_vec2     (highp vec2 a, highp vec2 b)    { return compare_float(a.x, b.x)&&compare_float(a.y, b.y); }\n";
+		case glu::TYPE_FLOAT_VEC3:		return "bool compare_vec3     (highp vec3 a, highp vec3 b)    { return compare_float(a.x, b.x)&&compare_float(a.y, b.y)&&compare_float(a.z, b.z); }\n";
+		case glu::TYPE_FLOAT_VEC4:		return "bool compare_vec4     (highp vec4 a, highp vec4 b)    { return compare_float(a.x, b.x)&&compare_float(a.y, b.y)&&compare_float(a.z, b.z)&&compare_float(a.w, b.w); }\n";
+		case glu::TYPE_FLOAT_MAT2:		return "bool compare_mat2     (highp mat2 a, highp mat2 b)    { return compare_vec2(a[0], b[0])&&compare_vec2(a[1], b[1]); }\n";
+		case glu::TYPE_FLOAT_MAT2X3:	return "bool compare_mat2x3   (highp mat2x3 a, highp mat2x3 b){ return compare_vec3(a[0], b[0])&&compare_vec3(a[1], b[1]); }\n";
+		case glu::TYPE_FLOAT_MAT2X4:	return "bool compare_mat2x4   (highp mat2x4 a, highp mat2x4 b){ return compare_vec4(a[0], b[0])&&compare_vec4(a[1], b[1]); }\n";
+		case glu::TYPE_FLOAT_MAT3X2:	return "bool compare_mat3x2   (highp mat3x2 a, highp mat3x2 b){ return compare_vec2(a[0], b[0])&&compare_vec2(a[1], b[1])&&compare_vec2(a[2], b[2]); }\n";
+		case glu::TYPE_FLOAT_MAT3:		return "bool compare_mat3     (highp mat3 a, highp mat3 b)    { return compare_vec3(a[0], b[0])&&compare_vec3(a[1], b[1])&&compare_vec3(a[2], b[2]); }\n";
+		case glu::TYPE_FLOAT_MAT3X4:	return "bool compare_mat3x4   (highp mat3x4 a, highp mat3x4 b){ return compare_vec4(a[0], b[0])&&compare_vec4(a[1], b[1])&&compare_vec4(a[2], b[2]); }\n";
+		case glu::TYPE_FLOAT_MAT4X2:	return "bool compare_mat4x2   (highp mat4x2 a, highp mat4x2 b){ return compare_vec2(a[0], b[0])&&compare_vec2(a[1], b[1])&&compare_vec2(a[2], b[2])&&compare_vec2(a[3], b[3]); }\n";
+		case glu::TYPE_FLOAT_MAT4X3:	return "bool compare_mat4x3   (highp mat4x3 a, highp mat4x3 b){ return compare_vec3(a[0], b[0])&&compare_vec3(a[1], b[1])&&compare_vec3(a[2], b[2])&&compare_vec3(a[3], b[3]); }\n";
+		case glu::TYPE_FLOAT_MAT4:		return "bool compare_mat4     (highp mat4 a, highp mat4 b)    { return compare_vec4(a[0], b[0])&&compare_vec4(a[1], b[1])&&compare_vec4(a[2], b[2])&&compare_vec4(a[3], b[3]); }\n";
+		case glu::TYPE_INT:				return "bool compare_int      (highp int a, highp int b)      { return a == b; }\n";
+		case glu::TYPE_INT_VEC2:		return "bool compare_ivec2    (highp ivec2 a, highp ivec2 b)  { return a == b; }\n";
+		case glu::TYPE_INT_VEC3:		return "bool compare_ivec3    (highp ivec3 a, highp ivec3 b)  { return a == b; }\n";
+		case glu::TYPE_INT_VEC4:		return "bool compare_ivec4    (highp ivec4 a, highp ivec4 b)  { return a == b; }\n";
+		case glu::TYPE_UINT:			return "bool compare_uint     (highp uint a, highp uint b)    { return a == b; }\n";
+		case glu::TYPE_UINT_VEC2:		return "bool compare_uvec2    (highp uvec2 a, highp uvec2 b)  { return a == b; }\n";
+		case glu::TYPE_UINT_VEC3:		return "bool compare_uvec3    (highp uvec3 a, highp uvec3 b)  { return a == b; }\n";
+		case glu::TYPE_UINT_VEC4:		return "bool compare_uvec4    (highp uvec4 a, highp uvec4 b)  { return a == b; }\n";
+		case glu::TYPE_BOOL:			return "bool compare_bool     (bool a, bool b)                { return a == b; }\n";
+		case glu::TYPE_BOOL_VEC2:		return "bool compare_bvec2    (bvec2 a, bvec2 b)              { return a == b; }\n";
+		case glu::TYPE_BOOL_VEC3:		return "bool compare_bvec3    (bvec3 a, bvec3 b)              { return a == b; }\n";
+		case glu::TYPE_BOOL_VEC4:		return "bool compare_bvec4    (bvec4 a, bvec4 b)              { return a == b; }\n";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+void getCompareDependencies (std::set<glu::DataType>& compareFuncs, glu::DataType basicType)
+{
+	switch (basicType)
+	{
+		case glu::TYPE_FLOAT_VEC2:
+		case glu::TYPE_FLOAT_VEC3:
+		case glu::TYPE_FLOAT_VEC4:
+			compareFuncs.insert(glu::TYPE_FLOAT);
+			compareFuncs.insert(basicType);
+			break;
+
+		case glu::TYPE_FLOAT_MAT2:
+		case glu::TYPE_FLOAT_MAT2X3:
+		case glu::TYPE_FLOAT_MAT2X4:
+		case glu::TYPE_FLOAT_MAT3X2:
+		case glu::TYPE_FLOAT_MAT3:
+		case glu::TYPE_FLOAT_MAT3X4:
+		case glu::TYPE_FLOAT_MAT4X2:
+		case glu::TYPE_FLOAT_MAT4X3:
+		case glu::TYPE_FLOAT_MAT4:
+			compareFuncs.insert(glu::TYPE_FLOAT);
+			compareFuncs.insert(glu::getDataTypeFloatVec(glu::getDataTypeMatrixNumRows(basicType)));
+			compareFuncs.insert(basicType);
+			break;
+
+		default:
+			compareFuncs.insert(basicType);
+			break;
+	}
+}
+
+void collectUniqueBasicTypes (std::set<glu::DataType>& basicTypes, const VarType& type)
+{
+	if (type.isStructType())
+	{
+		for (StructType::ConstIterator iter = type.getStructPtr()->begin(); iter != type.getStructPtr()->end(); ++iter)
+			collectUniqueBasicTypes(basicTypes, iter->getType());
+	}
+	else if (type.isArrayType())
+		collectUniqueBasicTypes(basicTypes, type.getElementType());
+	else
+	{
+		DE_ASSERT(type.isBasicType());
+		basicTypes.insert(type.getBasicType());
+	}
+}
+
+void collectUniqueBasicTypes (std::set<glu::DataType>& basicTypes, const BufferBlock& bufferBlock)
+{
+	for (BufferBlock::const_iterator iter = bufferBlock.begin(); iter != bufferBlock.end(); ++iter)
+		collectUniqueBasicTypes(basicTypes, iter->getType());
+}
+
+void collectUniqueBasicTypes (std::set<glu::DataType>& basicTypes, const ShaderInterface& interface)
+{
+	for (int ndx = 0; ndx < interface.getNumBlocks(); ++ndx)
+		collectUniqueBasicTypes(basicTypes, interface.getBlock(ndx));
+}
+
+void generateCompareFuncs (std::ostream& str, const ShaderInterface& interface)
+{
+	std::set<glu::DataType> types;
+	std::set<glu::DataType> compareFuncs;
+
+	// Collect unique basic types
+	collectUniqueBasicTypes(types, interface);
+
+	// Set of compare functions required
+	for (std::set<glu::DataType>::const_iterator iter = types.begin(); iter != types.end(); ++iter)
+	{
+		getCompareDependencies(compareFuncs, *iter);
+	}
+
+	for (int type = 0; type < glu::TYPE_LAST; ++type)
+	{
+		if (compareFuncs.find(glu::DataType(type)) != compareFuncs.end())
+			str << getCompareFuncForType(glu::DataType(type));
+	}
+}
+
+struct Indent
+{
+	int level;
+	Indent (int level_) : level(level_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const Indent& indent)
+{
+	for (int i = 0; i < indent.level; i++)
+		str << "\t";
+	return str;
+}
+
+void generateDeclaration (std::ostream& src, const BufferVar& bufferVar, int indentLevel)
+{
+	// \todo [pyry] Qualifiers
+
+	if ((bufferVar.getFlags() & LAYOUT_MASK) != 0)
+		src << "layout(" << LayoutFlagsFmt(bufferVar.getFlags() & LAYOUT_MASK) << ") ";
+
+	src << glu::declare(bufferVar.getType(), bufferVar.getName(), indentLevel);
+}
+
+void generateDeclaration (std::ostream& src, const BufferBlock& block, int bindingPoint)
+{
+	src << "layout(";
+
+	if ((block.getFlags() & LAYOUT_MASK) != 0)
+		src << LayoutFlagsFmt(block.getFlags() & LAYOUT_MASK) << ", ";
+
+	src << "binding = " << bindingPoint;
+
+	src << ") ";
+
+	src << "buffer " << block.getBlockName();
+	src << "\n{\n";
+
+	for (BufferBlock::const_iterator varIter = block.begin(); varIter != block.end(); varIter++)
+	{
+		src << Indent(1);
+		generateDeclaration(src, *varIter, 1 /* indent level */);
+		src << ";\n";
+	}
+
+	src << "}";
+
+	if (block.getInstanceName() != DE_NULL)
+	{
+		src << " " << block.getInstanceName();
+		if (block.isArray())
+			src << "[" << block.getArraySize() << "]";
+	}
+	else
+		DE_ASSERT(!block.isArray());
+
+	src << ";\n";
+}
+
+void generateImmMatrixSrc (std::ostream& src, glu::DataType basicType, int matrixStride, bool isRowMajor, const void* valuePtr)
+{
+	DE_ASSERT(glu::isDataTypeMatrix(basicType));
+
+	const int		compSize		= sizeof(deUint32);
+	const int		numRows			= glu::getDataTypeMatrixNumRows(basicType);
+	const int		numCols			= glu::getDataTypeMatrixNumColumns(basicType);
+
+	src << glu::getDataTypeName(basicType) << "(";
+
+	// Constructed in column-wise order.
+	for (int colNdx = 0; colNdx < numCols; colNdx++)
+	{
+		for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
+		{
+			const deUint8*	compPtr	= (const deUint8*)valuePtr + (isRowMajor ? rowNdx*matrixStride + colNdx*compSize
+																				: colNdx*matrixStride + rowNdx*compSize);
+
+			if (colNdx > 0 || rowNdx > 0)
+				src << ", ";
+
+			src << de::floatToString(*((const float*)compPtr), 1);
+		}
+	}
+
+	src << ")";
+}
+
+void generateImmScalarVectorSrc (std::ostream& src, glu::DataType basicType, const void* valuePtr)
+{
+	DE_ASSERT(glu::isDataTypeFloatOrVec(basicType)	||
+			  glu::isDataTypeIntOrIVec(basicType)	||
+			  glu::isDataTypeUintOrUVec(basicType)	||
+			  glu::isDataTypeBoolOrBVec(basicType));
+
+	const glu::DataType		scalarType		= glu::getDataTypeScalarType(basicType);
+	const int				scalarSize		= glu::getDataTypeScalarSize(basicType);
+	const int				compSize		= sizeof(deUint32);
+
+	if (scalarSize > 1)
+		src << glu::getDataTypeName(basicType) << "(";
+
+	for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
+	{
+		const deUint8* compPtr = (const deUint8*)valuePtr + scalarNdx*compSize;
+
+		if (scalarNdx > 0)
+			src << ", ";
+
+		switch (scalarType)
+		{
+			case glu::TYPE_FLOAT:	src << de::floatToString(*((const float*)compPtr), 1);			break;
+			case glu::TYPE_INT:		src << *((const int*)compPtr);									break;
+			case glu::TYPE_UINT:	src << *((const deUint32*)compPtr) << "u";						break;
+			case glu::TYPE_BOOL:	src << (*((const deUint32*)compPtr) != 0u ? "true" : "false");	break;
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	if (scalarSize > 1)
+		src << ")";
+}
+
+string getAPIName (const BufferBlock& block, const BufferVar& var, const glu::TypeComponentVector& accessPath)
+{
+	std::ostringstream name;
+
+	if (block.getInstanceName())
+		name << block.getBlockName() << ".";
+
+	name << var.getName();
+
+	for (glu::TypeComponentVector::const_iterator pathComp = accessPath.begin(); pathComp != accessPath.end(); pathComp++)
+	{
+		if (pathComp->type == glu::VarTypeComponent::STRUCT_MEMBER)
+		{
+			const VarType		curType		= glu::getVarType(var.getType(), accessPath.begin(), pathComp);
+			const StructType*	structPtr	= curType.getStructPtr();
+
+			name << "." << structPtr->getMember(pathComp->index).getName();
+		}
+		else if (pathComp->type == glu::VarTypeComponent::ARRAY_ELEMENT)
+		{
+			if (pathComp == accessPath.begin() || (pathComp+1) == accessPath.end())
+				name << "[0]"; // Top- / bottom-level array
+			else
+				name << "[" << pathComp->index << "]";
+		}
+		else
+			DE_ASSERT(false);
+	}
+
+	return name.str();
+}
+
+string getShaderName (const BufferBlock& block, int instanceNdx, const BufferVar& var, const glu::TypeComponentVector& accessPath)
+{
+	std::ostringstream name;
+
+	if (block.getInstanceName())
+	{
+		name << block.getInstanceName();
+
+		if (block.isArray())
+			name << "[" << instanceNdx << "]";
+
+		name << ".";
+	}
+	else
+		DE_ASSERT(instanceNdx == 0);
+
+	name << var.getName();
+
+	for (glu::TypeComponentVector::const_iterator pathComp = accessPath.begin(); pathComp != accessPath.end(); pathComp++)
+	{
+		if (pathComp->type == glu::VarTypeComponent::STRUCT_MEMBER)
+		{
+			const VarType		curType		= glu::getVarType(var.getType(), accessPath.begin(), pathComp);
+			const StructType*	structPtr	= curType.getStructPtr();
+
+			name << "." << structPtr->getMember(pathComp->index).getName();
+		}
+		else if (pathComp->type == glu::VarTypeComponent::ARRAY_ELEMENT)
+			name << "[" << pathComp->index << "]";
+		else
+			DE_ASSERT(false);
+	}
+
+	return name.str();
+}
+
+int computeOffset (const BufferVarLayoutEntry& varLayout, const glu::TypeComponentVector& accessPath)
+{
+	const int	topLevelNdx		= (!accessPath.empty() && accessPath.front().type == glu::VarTypeComponent::ARRAY_ELEMENT) ? accessPath.front().index : 0;
+	const int	bottomLevelNdx	= (accessPath.size() > 1 && accessPath.back().type == glu::VarTypeComponent::ARRAY_ELEMENT) ? accessPath.back().index : 0;
+
+	return varLayout.offset + getTopLevelArrayStride(varLayout)*topLevelNdx + getArrayStride(varLayout)*bottomLevelNdx;
+}
+
+void generateCompareSrc (
+	std::ostream&				src,
+	const char*					resultVar,
+	const BufferLayout&			bufferLayout,
+	const BufferBlock&			block,
+	int							instanceNdx,
+	const BlockDataPtr&			blockPtr,
+	const BufferVar&			bufVar,
+	const glu::SubTypeAccess&	accessPath)
+{
+	const VarType curType = accessPath.getType();
+
+	if (curType.isArrayType())
+	{
+		const int arraySize = curType.getArraySize() == VarType::UNSIZED_ARRAY ? block.getLastUnsizedArraySize(instanceNdx) : curType.getArraySize();
+
+		for (int elemNdx = 0; elemNdx < arraySize; elemNdx++)
+			generateCompareSrc(src, resultVar, bufferLayout, block, instanceNdx, blockPtr, bufVar, accessPath.element(elemNdx));
+	}
+	else if (curType.isStructType())
+	{
+		const int numMembers = curType.getStructPtr()->getNumMembers();
+
+		for (int memberNdx = 0; memberNdx < numMembers; memberNdx++)
+			generateCompareSrc(src, resultVar, bufferLayout, block, instanceNdx, blockPtr, bufVar, accessPath.member(memberNdx));
+	}
+	else
+	{
+		DE_ASSERT(curType.isBasicType());
+
+		const string	apiName	= getAPIName(block, bufVar, accessPath.getPath());
+		const int		varNdx	= bufferLayout.getVariableIndex(apiName);
+
+		DE_ASSERT(varNdx >= 0);
+		{
+			const BufferVarLayoutEntry&	varLayout		= bufferLayout.bufferVars[varNdx];
+			const string				shaderName		= getShaderName(block, instanceNdx, bufVar, accessPath.getPath());
+			const glu::DataType			basicType		= curType.getBasicType();
+			const bool					isMatrix		= glu::isDataTypeMatrix(basicType);
+			const char*					typeName		= glu::getDataTypeName(basicType);
+			const void*					valuePtr		= (const deUint8*)blockPtr.ptr + computeOffset(varLayout, accessPath.getPath());
+
+			src << "\t" << resultVar << " = " << resultVar << " && compare_" << typeName << "(" << shaderName << ", ";
+
+			if (isMatrix)
+				generateImmMatrixSrc(src, basicType, varLayout.matrixStride, varLayout.isRowMajor, valuePtr);
+			else
+				generateImmScalarVectorSrc(src, basicType, valuePtr);
+
+			src << ");\n";
+		}
+	}
+}
+
+void generateCompareSrc (std::ostream& src, const char* resultVar, const ShaderInterface& interface, const BufferLayout& layout, const vector<BlockDataPtr>& blockPointers)
+{
+	for (int declNdx = 0; declNdx < interface.getNumBlocks(); declNdx++)
+	{
+		const BufferBlock&	block			= interface.getBlock(declNdx);
+		const bool			isArray			= block.isArray();
+		const int			numInstances	= isArray ? block.getArraySize() : 1;
+
+		DE_ASSERT(!isArray || block.getInstanceName());
+
+		for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		{
+			const string		instanceName	= block.getBlockName() + (isArray ? "[" + de::toString(instanceNdx) + "]" : string(""));
+			const int			blockNdx		= layout.getBlockIndex(instanceName);
+			const BlockDataPtr&	blockPtr		= blockPointers[blockNdx];
+
+			for (BufferBlock::const_iterator varIter = block.begin(); varIter != block.end(); varIter++)
+			{
+				const BufferVar& bufVar = *varIter;
+
+				if ((bufVar.getFlags() & ACCESS_READ) == 0)
+					continue; // Don't read from that variable.
+
+				generateCompareSrc(src, resultVar, layout, block, instanceNdx, blockPtr, bufVar, glu::SubTypeAccess(bufVar.getType()));
+			}
+		}
+	}
+}
+
+// \todo [2013-10-14 pyry] Almost identical to generateCompareSrc - unify?
+
+void generateWriteSrc (
+	std::ostream&				src,
+	const BufferLayout&			bufferLayout,
+	const BufferBlock&			block,
+	int							instanceNdx,
+	const BlockDataPtr&			blockPtr,
+	const BufferVar&			bufVar,
+	const glu::SubTypeAccess&	accessPath)
+{
+	const VarType curType = accessPath.getType();
+
+	if (curType.isArrayType())
+	{
+		const int arraySize = curType.getArraySize() == VarType::UNSIZED_ARRAY ? block.getLastUnsizedArraySize(instanceNdx) : curType.getArraySize();
+
+		for (int elemNdx = 0; elemNdx < arraySize; elemNdx++)
+			generateWriteSrc(src, bufferLayout, block, instanceNdx, blockPtr, bufVar, accessPath.element(elemNdx));
+	}
+	else if (curType.isStructType())
+	{
+		const int numMembers = curType.getStructPtr()->getNumMembers();
+
+		for (int memberNdx = 0; memberNdx < numMembers; memberNdx++)
+			generateWriteSrc(src, bufferLayout, block, instanceNdx, blockPtr, bufVar, accessPath.member(memberNdx));
+	}
+	else
+	{
+		DE_ASSERT(curType.isBasicType());
+
+		const string	apiName	= getAPIName(block, bufVar, accessPath.getPath());
+		const int		varNdx	= bufferLayout.getVariableIndex(apiName);
+
+		DE_ASSERT(varNdx >= 0);
+		{
+			const BufferVarLayoutEntry&	varLayout		= bufferLayout.bufferVars[varNdx];
+			const string				shaderName		= getShaderName(block, instanceNdx, bufVar, accessPath.getPath());
+			const glu::DataType			basicType		= curType.getBasicType();
+			const bool					isMatrix		= glu::isDataTypeMatrix(basicType);
+			const void*					valuePtr		= (const deUint8*)blockPtr.ptr + computeOffset(varLayout, accessPath.getPath());
+
+			src << "\t" << shaderName << " = ";
+
+			if (isMatrix)
+				generateImmMatrixSrc(src, basicType, varLayout.matrixStride, varLayout.isRowMajor, valuePtr);
+			else
+				generateImmScalarVectorSrc(src, basicType, valuePtr);
+
+			src << ";\n";
+		}
+	}
+}
+
+void generateWriteSrc (std::ostream& src, const ShaderInterface& interface, const BufferLayout& layout, const vector<BlockDataPtr>& blockPointers)
+{
+	for (int declNdx = 0; declNdx < interface.getNumBlocks(); declNdx++)
+	{
+		const BufferBlock&	block			= interface.getBlock(declNdx);
+		const bool			isArray			= block.isArray();
+		const int			numInstances	= isArray ? block.getArraySize() : 1;
+
+		DE_ASSERT(!isArray || block.getInstanceName());
+
+		for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		{
+			const string		instanceName	= block.getBlockName() + (isArray ? "[" + de::toString(instanceNdx) + "]" : string(""));
+			const int			blockNdx		= layout.getBlockIndex(instanceName);
+			const BlockDataPtr&	blockPtr		= blockPointers[blockNdx];
+
+			for (BufferBlock::const_iterator varIter = block.begin(); varIter != block.end(); varIter++)
+			{
+				const BufferVar& bufVar = *varIter;
+
+				if ((bufVar.getFlags() & ACCESS_WRITE) == 0)
+					continue; // Don't write to that variable.
+
+				generateWriteSrc(src, layout, block, instanceNdx, blockPtr, bufVar, glu::SubTypeAccess(bufVar.getType()));
+			}
+		}
+	}
+}
+
+string generateComputeShader (glu::GLSLVersion glslVersion, const ShaderInterface& interface, const BufferLayout& layout, const vector<BlockDataPtr>& comparePtrs, const vector<BlockDataPtr>& writePtrs)
+{
+	std::ostringstream src;
+
+	DE_ASSERT(glslVersion == glu::GLSL_VERSION_310_ES || glslVersion == glu::GLSL_VERSION_430);
+
+	src << glu::getGLSLVersionDeclaration(glslVersion) << "\n";
+	src << "layout(local_size_x = 1) in;\n";
+	src << "\n";
+
+	std::vector<const StructType*> namedStructs;
+	interface.getNamedStructs(namedStructs);
+	for (std::vector<const StructType*>::const_iterator structIter = namedStructs.begin(); structIter != namedStructs.end(); structIter++)
+		src << glu::declare(*structIter) << ";\n";
+
+	{
+		int bindingPoint = 0;
+
+		for (int blockNdx = 0; blockNdx < interface.getNumBlocks(); blockNdx++)
+		{
+			const BufferBlock& block = interface.getBlock(blockNdx);
+			generateDeclaration(src, block, bindingPoint);
+
+			bindingPoint += block.isArray() ? block.getArraySize() : 1;
+		}
+	}
+
+	// Atomic counter for counting passed invocations.
+	src << "\nlayout(binding = 0) uniform atomic_uint ac_numPassed;\n";
+
+	// Comparison utilities.
+	src << "\n";
+	generateCompareFuncs(src, interface);
+
+	src << "\n"
+		   "void main (void)\n"
+		   "{\n"
+		   "	bool allOk = true;\n";
+
+	// Value compare.
+	generateCompareSrc(src, "allOk", interface, layout, comparePtrs);
+
+	src << "	if (allOk)\n"
+		<< "		atomicCounterIncrement(ac_numPassed);\n"
+		<< "\n";
+
+	// Value write.
+	generateWriteSrc(src, interface, layout, writePtrs);
+
+	src << "}\n";
+
+	return src.str();
+}
+
+void getGLBufferLayout (const glw::Functions& gl, BufferLayout& layout, deUint32 program)
+{
+	int		numActiveBufferVars	= 0;
+	int		numActiveBlocks		= 0;
+
+	gl.getProgramInterfaceiv(program, GL_BUFFER_VARIABLE,		GL_ACTIVE_RESOURCES,	&numActiveBufferVars);
+	gl.getProgramInterfaceiv(program, GL_SHADER_STORAGE_BLOCK,	GL_ACTIVE_RESOURCES,	&numActiveBlocks);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get number of buffer variables and buffer blocks");
+
+	// Block entries.
+	layout.blocks.resize(numActiveBlocks);
+	for (int blockNdx = 0; blockNdx < numActiveBlocks; blockNdx++)
+	{
+		BlockLayoutEntry&	entry				= layout.blocks[blockNdx];
+		const deUint32		queryParams[]		= { GL_BUFFER_DATA_SIZE, GL_NUM_ACTIVE_VARIABLES, GL_NAME_LENGTH };
+		int					returnValues[]		= { 0, 0, 0 };
+
+		DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(queryParams) == DE_LENGTH_OF_ARRAY(returnValues));
+
+		{
+			int returnLength = 0;
+			gl.getProgramResourceiv(program, GL_SHADER_STORAGE_BLOCK, (deUint32)blockNdx, DE_LENGTH_OF_ARRAY(queryParams), &queryParams[0], DE_LENGTH_OF_ARRAY(returnValues), &returnLength, &returnValues[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramResourceiv(GL_SHADER_STORAGE_BLOCK) failed");
+
+			if (returnLength != DE_LENGTH_OF_ARRAY(returnValues))
+				throw tcu::TestError("glGetProgramResourceiv(GL_SHADER_STORAGE_BLOCK) returned wrong number of values");
+		}
+
+		entry.size = returnValues[0];
+
+		// Query active variables
+		if (returnValues[1] > 0)
+		{
+			const int		numBlockVars	= returnValues[1];
+			const deUint32	queryArg		= GL_ACTIVE_VARIABLES;
+			int				retLength		= 0;
+
+			entry.activeVarIndices.resize(numBlockVars);
+			gl.getProgramResourceiv(program, GL_SHADER_STORAGE_BLOCK, (deUint32)blockNdx, 1, &queryArg, numBlockVars, &retLength, &entry.activeVarIndices[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramResourceiv(GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_VARIABLES) failed");
+
+			if (retLength != numBlockVars)
+				throw tcu::TestError("glGetProgramResourceiv(GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_VARIABLES) returned wrong number of values");
+		}
+
+		// Query name
+		if (returnValues[2] > 0)
+		{
+			const int		nameLen		= returnValues[2];
+			int				retLen		= 0;
+			vector<char>	name		(nameLen);
+
+			gl.getProgramResourceName(program, GL_SHADER_STORAGE_BLOCK, (deUint32)blockNdx, (glw::GLsizei)name.size(), &retLen, &name[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramResourceName(GL_SHADER_STORAGE_BLOCK) failed");
+
+			if (retLen+1 != nameLen)
+				throw tcu::TestError("glGetProgramResourceName(GL_SHADER_STORAGE_BLOCK) returned invalid name. Number of characters written is inconsistent with NAME_LENGTH property.");
+			if (name[nameLen-1] != 0)
+				throw tcu::TestError("glGetProgramResourceName(GL_SHADER_STORAGE_BLOCK) returned invalid name. Expected null terminator at index " + de::toString(nameLen-1));
+
+			entry.name = &name[0];
+		}
+		else
+			throw tcu::TestError("glGetProgramResourceiv() returned invalid GL_NAME_LENGTH");
+	}
+
+	layout.bufferVars.resize(numActiveBufferVars);
+	for (int bufVarNdx = 0; bufVarNdx < numActiveBufferVars; bufVarNdx++)
+	{
+		BufferVarLayoutEntry&	entry				= layout.bufferVars[bufVarNdx];
+		const deUint32			queryParams[] =
+		{
+			GL_BLOCK_INDEX,					// 0
+			GL_TYPE,						// 1
+			GL_OFFSET,						// 2
+			GL_ARRAY_SIZE,					// 3
+			GL_ARRAY_STRIDE,				// 4
+			GL_MATRIX_STRIDE,				// 5
+			GL_TOP_LEVEL_ARRAY_SIZE,		// 6
+			GL_TOP_LEVEL_ARRAY_STRIDE,		// 7
+			GL_IS_ROW_MAJOR,				// 8
+			GL_NAME_LENGTH					// 9
+		};
+		int returnValues[DE_LENGTH_OF_ARRAY(queryParams)];
+
+		DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(queryParams) == DE_LENGTH_OF_ARRAY(returnValues));
+
+		{
+			int returnLength = 0;
+			gl.getProgramResourceiv(program, GL_BUFFER_VARIABLE, (deUint32)bufVarNdx, DE_LENGTH_OF_ARRAY(queryParams), &queryParams[0], DE_LENGTH_OF_ARRAY(returnValues), &returnLength, &returnValues[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramResourceiv(GL_BUFFER_VARIABLE) failed");
+
+			if (returnLength != DE_LENGTH_OF_ARRAY(returnValues))
+				throw tcu::TestError("glGetProgramResourceiv(GL_BUFFER_VARIABLE) returned wrong number of values");
+		}
+
+		// Map values
+		entry.blockNdx				= returnValues[0];
+		entry.type					= glu::getDataTypeFromGLType(returnValues[1]);
+		entry.offset				= returnValues[2];
+		entry.arraySize				= returnValues[3];
+		entry.arrayStride			= returnValues[4];
+		entry.matrixStride			= returnValues[5];
+		entry.topLevelArraySize		= returnValues[6];
+		entry.topLevelArrayStride	= returnValues[7];
+		entry.isRowMajor			= returnValues[8] != 0;
+
+		// Query name
+		DE_ASSERT(queryParams[9] == GL_NAME_LENGTH);
+		if (returnValues[9] > 0)
+		{
+			const int		nameLen		= returnValues[9];
+			int				retLen		= 0;
+			vector<char>	name		(nameLen);
+
+			gl.getProgramResourceName(program, GL_BUFFER_VARIABLE, (deUint32)bufVarNdx, (glw::GLsizei)name.size(), &retLen, &name[0]);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramResourceName(GL_BUFFER_VARIABLE) failed");
+
+			if (retLen+1 != nameLen)
+				throw tcu::TestError("glGetProgramResourceName(GL_BUFFER_VARIABLE) returned invalid name. Number of characters written is inconsistent with NAME_LENGTH property.");
+			if (name[nameLen-1] != 0)
+				throw tcu::TestError("glGetProgramResourceName(GL_BUFFER_VARIABLE) returned invalid name. Expected null terminator at index " + de::toString(nameLen-1));
+
+			entry.name = &name[0];
+		}
+		else
+			throw tcu::TestError("glGetProgramResourceiv() returned invalid GL_NAME_LENGTH");
+	}
+}
+
+void copyBufferVarData (const BufferVarLayoutEntry& dstEntry, const BlockDataPtr& dstBlockPtr, const BufferVarLayoutEntry& srcEntry, const BlockDataPtr& srcBlockPtr)
+{
+	DE_ASSERT(dstEntry.arraySize <= srcEntry.arraySize);
+	DE_ASSERT(dstEntry.topLevelArraySize <= srcEntry.topLevelArraySize);
+	DE_ASSERT(dstBlockPtr.lastUnsizedArraySize <= srcBlockPtr.lastUnsizedArraySize);
+	DE_ASSERT(dstEntry.type == srcEntry.type);
+
+	deUint8* const			dstBasePtr			= (deUint8*)dstBlockPtr.ptr + dstEntry.offset;
+	const deUint8* const	srcBasePtr			= (const deUint8*)srcBlockPtr.ptr + srcEntry.offset;
+	const int				scalarSize			= glu::getDataTypeScalarSize(dstEntry.type);
+	const bool				isMatrix			= glu::isDataTypeMatrix(dstEntry.type);
+	const int				compSize			= sizeof(deUint32);
+	const int				dstArraySize		= getArraySize(dstEntry);
+	const int				dstArrayStride		= getArrayStride(dstEntry);
+	const int				dstTopLevelSize		= isUnsizedTopLevelArray(dstEntry) ? dstBlockPtr.lastUnsizedArraySize : getTopLevelArraySize(dstEntry);
+	const int				dstTopLevelStride	= getTopLevelArrayStride(dstEntry);
+	const int				srcArraySize		= getArraySize(srcEntry);
+	const int				srcArrayStride		= getArrayStride(srcEntry);
+	const int				srcTopLevelSize		= isUnsizedTopLevelArray(srcEntry) ? srcBlockPtr.lastUnsizedArraySize : getTopLevelArraySize(srcEntry);
+	const int				srcTopLevelStride	= getTopLevelArrayStride(srcEntry);
+
+	DE_ASSERT(dstArraySize <= srcArraySize && dstTopLevelSize <= srcTopLevelSize);
+	DE_UNREF(srcArraySize && srcTopLevelSize);
+
+	for (int topElemNdx = 0; topElemNdx < dstTopLevelSize; topElemNdx++)
+	{
+		deUint8* const			dstTopPtr	= dstBasePtr + topElemNdx*dstTopLevelStride;
+		const deUint8* const	srcTopPtr	= srcBasePtr + topElemNdx*srcTopLevelStride;
+
+		for (int elementNdx = 0; elementNdx < dstArraySize; elementNdx++)
+		{
+			deUint8* const			dstElemPtr	= dstTopPtr + elementNdx*dstArrayStride;
+			const deUint8* const	srcElemPtr	= srcTopPtr + elementNdx*srcArrayStride;
+
+			if (isMatrix)
+			{
+				const int	numRows	= glu::getDataTypeMatrixNumRows(dstEntry.type);
+				const int	numCols	= glu::getDataTypeMatrixNumColumns(dstEntry.type);
+
+				for (int colNdx = 0; colNdx < numCols; colNdx++)
+				{
+					for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
+					{
+						deUint8*		dstCompPtr	= dstElemPtr + (dstEntry.isRowMajor ? rowNdx*dstEntry.matrixStride + colNdx*compSize
+																						: colNdx*dstEntry.matrixStride + rowNdx*compSize);
+						const deUint8*	srcCompPtr	= srcElemPtr + (srcEntry.isRowMajor ? rowNdx*srcEntry.matrixStride + colNdx*compSize
+																						: colNdx*srcEntry.matrixStride + rowNdx*compSize);
+
+						DE_ASSERT((deIntptr)(srcCompPtr + compSize) - (deIntptr)srcBlockPtr.ptr <= (deIntptr)srcBlockPtr.size);
+						DE_ASSERT((deIntptr)(dstCompPtr + compSize) - (deIntptr)dstBlockPtr.ptr <= (deIntptr)dstBlockPtr.size);
+						deMemcpy(dstCompPtr, srcCompPtr, compSize);
+					}
+				}
+			}
+			else
+			{
+				DE_ASSERT((deIntptr)(srcElemPtr + scalarSize*compSize) - (deIntptr)srcBlockPtr.ptr <= (deIntptr)srcBlockPtr.size);
+				DE_ASSERT((deIntptr)(dstElemPtr + scalarSize*compSize) - (deIntptr)dstBlockPtr.ptr <= (deIntptr)dstBlockPtr.size);
+				deMemcpy(dstElemPtr, srcElemPtr, scalarSize*compSize);
+			}
+		}
+	}
+}
+
+void copyData (const BufferLayout& dstLayout, const vector<BlockDataPtr>& dstBlockPointers, const BufferLayout& srcLayout, const vector<BlockDataPtr>& srcBlockPointers)
+{
+	// \note Src layout is used as reference in case of activeVarIndices happens to be incorrect in dstLayout blocks.
+	int numBlocks = (int)srcLayout.blocks.size();
+
+	for (int srcBlockNdx = 0; srcBlockNdx < numBlocks; srcBlockNdx++)
+	{
+		const BlockLayoutEntry&		srcBlock	= srcLayout.blocks[srcBlockNdx];
+		const BlockDataPtr&			srcBlockPtr	= srcBlockPointers[srcBlockNdx];
+		int							dstBlockNdx	= dstLayout.getBlockIndex(srcBlock.name.c_str());
+
+		if (dstBlockNdx >= 0)
+		{
+			DE_ASSERT(de::inBounds(dstBlockNdx, 0, (int)dstBlockPointers.size()));
+
+			const BlockDataPtr& dstBlockPtr = dstBlockPointers[dstBlockNdx];
+
+			for (vector<int>::const_iterator srcVarNdxIter = srcBlock.activeVarIndices.begin(); srcVarNdxIter != srcBlock.activeVarIndices.end(); srcVarNdxIter++)
+			{
+				const BufferVarLayoutEntry&	srcEntry	= srcLayout.bufferVars[*srcVarNdxIter];
+				int							dstVarNdx	= dstLayout.getVariableIndex(srcEntry.name.c_str());
+
+				if (dstVarNdx >= 0)
+					copyBufferVarData(dstLayout.bufferVars[dstVarNdx], dstBlockPtr, srcEntry, srcBlockPtr);
+			}
+		}
+	}
+}
+
+void copyNonWrittenData (
+	const BufferLayout&			layout,
+	const BufferBlock&			block,
+	int							instanceNdx,
+	const BlockDataPtr&			srcBlockPtr,
+	const BlockDataPtr&			dstBlockPtr,
+	const BufferVar&			bufVar,
+	const glu::SubTypeAccess&	accessPath)
+{
+	const VarType curType = accessPath.getType();
+
+	if (curType.isArrayType())
+	{
+		const int arraySize = curType.getArraySize() == VarType::UNSIZED_ARRAY ? block.getLastUnsizedArraySize(instanceNdx) : curType.getArraySize();
+
+		for (int elemNdx = 0; elemNdx < arraySize; elemNdx++)
+			copyNonWrittenData(layout, block, instanceNdx, srcBlockPtr, dstBlockPtr, bufVar, accessPath.element(elemNdx));
+	}
+	else if (curType.isStructType())
+	{
+		const int numMembers = curType.getStructPtr()->getNumMembers();
+
+		for (int memberNdx = 0; memberNdx < numMembers; memberNdx++)
+			copyNonWrittenData(layout, block, instanceNdx, srcBlockPtr, dstBlockPtr, bufVar, accessPath.member(memberNdx));
+	}
+	else
+	{
+		DE_ASSERT(curType.isBasicType());
+
+		const string	apiName	= getAPIName(block, bufVar, accessPath.getPath());
+		const int		varNdx	= layout.getVariableIndex(apiName);
+
+		DE_ASSERT(varNdx >= 0);
+		{
+			const BufferVarLayoutEntry& varLayout = layout.bufferVars[varNdx];
+			copyBufferVarData(varLayout, dstBlockPtr, varLayout, srcBlockPtr);
+		}
+	}
+}
+
+void copyNonWrittenData (const ShaderInterface& interface, const BufferLayout& layout, const vector<BlockDataPtr>& srcPtrs, const vector<BlockDataPtr>& dstPtrs)
+{
+	for (int declNdx = 0; declNdx < interface.getNumBlocks(); declNdx++)
+	{
+		const BufferBlock&	block			= interface.getBlock(declNdx);
+		const bool			isArray			= block.isArray();
+		const int			numInstances	= isArray ? block.getArraySize() : 1;
+
+		DE_ASSERT(!isArray || block.getInstanceName());
+
+		for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		{
+			const string		instanceName	= block.getBlockName() + (isArray ? "[" + de::toString(instanceNdx) + "]" : string(""));
+			const int			blockNdx		= layout.getBlockIndex(instanceName);
+			const BlockDataPtr&	srcBlockPtr		= srcPtrs[blockNdx];
+			const BlockDataPtr&	dstBlockPtr		= dstPtrs[blockNdx];
+
+			for (BufferBlock::const_iterator varIter = block.begin(); varIter != block.end(); varIter++)
+			{
+				const BufferVar& bufVar = *varIter;
+
+				if (bufVar.getFlags() & ACCESS_WRITE)
+					continue;
+
+				copyNonWrittenData(layout, block, instanceNdx, srcBlockPtr, dstBlockPtr, bufVar, glu::SubTypeAccess(bufVar.getType()));
+			}
+		}
+	}
+}
+
+bool compareComponents (glu::DataType scalarType, const void* ref, const void* res, int numComps)
+{
+	if (scalarType == glu::TYPE_FLOAT)
+	{
+		const float threshold = 0.05f; // Same as used in shaders - should be fine for values being used.
+
+		for (int ndx = 0; ndx < numComps; ndx++)
+		{
+			const float		refVal		= *((const float*)ref + ndx);
+			const float		resVal		= *((const float*)res + ndx);
+
+			if (deFloatAbs(resVal - refVal) >= threshold)
+				return false;
+		}
+	}
+	else if (scalarType == glu::TYPE_BOOL)
+	{
+		for (int ndx = 0; ndx < numComps; ndx++)
+		{
+			const deUint32	refVal		= *((const deUint32*)ref + ndx);
+			const deUint32	resVal		= *((const deUint32*)res + ndx);
+
+			if ((refVal != 0) != (resVal != 0))
+				return false;
+		}
+	}
+	else
+	{
+		DE_ASSERT(scalarType == glu::TYPE_INT || scalarType == glu::TYPE_UINT);
+
+		for (int ndx = 0; ndx < numComps; ndx++)
+		{
+			const deUint32	refVal		= *((const deUint32*)ref + ndx);
+			const deUint32	resVal		= *((const deUint32*)res + ndx);
+
+			if (refVal != resVal)
+				return false;
+		}
+	}
+
+	return true;
+}
+
+bool compareBufferVarData (tcu::TestLog& log, const BufferVarLayoutEntry& refEntry, const BlockDataPtr& refBlockPtr, const BufferVarLayoutEntry& resEntry, const BlockDataPtr& resBlockPtr)
+{
+	DE_ASSERT(resEntry.arraySize <= refEntry.arraySize);
+	DE_ASSERT(resEntry.topLevelArraySize <= refEntry.topLevelArraySize);
+	DE_ASSERT(resBlockPtr.lastUnsizedArraySize <= refBlockPtr.lastUnsizedArraySize);
+	DE_ASSERT(resEntry.type == refEntry.type);
+
+	deUint8* const			resBasePtr			= (deUint8*)resBlockPtr.ptr + resEntry.offset;
+	const deUint8* const	refBasePtr			= (const deUint8*)refBlockPtr.ptr + refEntry.offset;
+	const glu::DataType		scalarType			= glu::getDataTypeScalarType(refEntry.type);
+	const int				scalarSize			= glu::getDataTypeScalarSize(resEntry.type);
+	const bool				isMatrix			= glu::isDataTypeMatrix(resEntry.type);
+	const int				compSize			= sizeof(deUint32);
+	const int				maxPrints			= 3;
+	int						numFailed			= 0;
+
+	const int				resArraySize		= getArraySize(resEntry);
+	const int				resArrayStride		= getArrayStride(resEntry);
+	const int				resTopLevelSize		= isUnsizedTopLevelArray(resEntry) ? resBlockPtr.lastUnsizedArraySize : getTopLevelArraySize(resEntry);
+	const int				resTopLevelStride	= getTopLevelArrayStride(resEntry);
+	const int				refArraySize		= getArraySize(refEntry);
+	const int				refArrayStride		= getArrayStride(refEntry);
+	const int				refTopLevelSize		= isUnsizedTopLevelArray(refEntry) ? refBlockPtr.lastUnsizedArraySize : getTopLevelArraySize(refEntry);
+	const int				refTopLevelStride	= getTopLevelArrayStride(refEntry);
+
+	DE_ASSERT(resArraySize <= refArraySize && resTopLevelSize <= refTopLevelSize);
+	DE_UNREF(refArraySize && refTopLevelSize);
+
+	for (int topElemNdx = 0; topElemNdx < resTopLevelSize; topElemNdx++)
+	{
+		deUint8* const			resTopPtr	= resBasePtr + topElemNdx*resTopLevelStride;
+		const deUint8* const	refTopPtr	= refBasePtr + topElemNdx*refTopLevelStride;
+
+		for (int elementNdx = 0; elementNdx < resArraySize; elementNdx++)
+		{
+			deUint8* const			resElemPtr	= resTopPtr + elementNdx*resArrayStride;
+			const deUint8* const	refElemPtr	= refTopPtr + elementNdx*refArrayStride;
+
+			if (isMatrix)
+			{
+				const int	numRows	= glu::getDataTypeMatrixNumRows(resEntry.type);
+				const int	numCols	= glu::getDataTypeMatrixNumColumns(resEntry.type);
+				bool		isOk	= true;
+
+				for (int colNdx = 0; colNdx < numCols; colNdx++)
+				{
+					for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
+					{
+						deUint8*		resCompPtr	= resElemPtr + (resEntry.isRowMajor ? rowNdx*resEntry.matrixStride + colNdx*compSize
+																						: colNdx*resEntry.matrixStride + rowNdx*compSize);
+						const deUint8*	refCompPtr	= refElemPtr + (refEntry.isRowMajor ? rowNdx*refEntry.matrixStride + colNdx*compSize
+																						: colNdx*refEntry.matrixStride + rowNdx*compSize);
+
+						DE_ASSERT((deIntptr)(refCompPtr + compSize) - (deIntptr)refBlockPtr.ptr <= (deIntptr)refBlockPtr.size);
+						DE_ASSERT((deIntptr)(resCompPtr + compSize) - (deIntptr)resBlockPtr.ptr <= (deIntptr)resBlockPtr.size);
+
+						isOk = isOk && compareComponents(scalarType, resCompPtr, refCompPtr, 1);
+					}
+				}
+
+				if (!isOk)
+				{
+					numFailed += 1;
+					if (numFailed < maxPrints)
+					{
+						std::ostringstream expected, got;
+						generateImmMatrixSrc(expected, refEntry.type, refEntry.matrixStride, refEntry.isRowMajor, refElemPtr);
+						generateImmMatrixSrc(got, resEntry.type, resEntry.matrixStride, resEntry.isRowMajor, resElemPtr);
+						log << TestLog::Message << "ERROR: mismatch in " << refEntry.name << ", top-level ndx " << topElemNdx << ", bottom-level ndx " << elementNdx << ":\n"
+												<< "  expected " << expected.str() << "\n"
+												<< "  got " << got.str()
+							<< TestLog::EndMessage;
+					}
+				}
+			}
+			else
+			{
+				DE_ASSERT((deIntptr)(refElemPtr + scalarSize*compSize) - (deIntptr)refBlockPtr.ptr <= (deIntptr)refBlockPtr.size);
+				DE_ASSERT((deIntptr)(resElemPtr + scalarSize*compSize) - (deIntptr)resBlockPtr.ptr <= (deIntptr)resBlockPtr.size);
+
+				const bool isOk = compareComponents(scalarType, resElemPtr, refElemPtr, scalarSize);
+
+				if (!isOk)
+				{
+					numFailed += 1;
+					if (numFailed < maxPrints)
+					{
+						std::ostringstream expected, got;
+						generateImmScalarVectorSrc(expected, refEntry.type, refElemPtr);
+						generateImmScalarVectorSrc(got, resEntry.type, resElemPtr);
+						log << TestLog::Message << "ERROR: mismatch in " << refEntry.name << ", top-level ndx " << topElemNdx << ", bottom-level ndx " << elementNdx << ":\n"
+												<< "  expected " << expected.str() << "\n"
+												<< "  got " << got.str()
+							<< TestLog::EndMessage;
+					}
+				}
+			}
+		}
+	}
+
+	if (numFailed >= maxPrints)
+		log << TestLog::Message << "... (" << numFailed << " failures for " << refEntry.name << " in total)" << TestLog::EndMessage;
+
+	return numFailed == 0;
+}
+
+bool compareData (tcu::TestLog& log, const BufferLayout& refLayout, const vector<BlockDataPtr>& refBlockPointers, const BufferLayout& resLayout, const vector<BlockDataPtr>& resBlockPointers)
+{
+	const int	numBlocks	= (int)refLayout.blocks.size();
+	bool		allOk		= true;
+
+	for (int refBlockNdx = 0; refBlockNdx < numBlocks; refBlockNdx++)
+	{
+		const BlockLayoutEntry&		refBlock	= refLayout.blocks[refBlockNdx];
+		const BlockDataPtr&			refBlockPtr	= refBlockPointers[refBlockNdx];
+		int							resBlockNdx	= resLayout.getBlockIndex(refBlock.name.c_str());
+
+		if (resBlockNdx >= 0)
+		{
+			DE_ASSERT(de::inBounds(resBlockNdx, 0, (int)resBlockPointers.size()));
+
+			const BlockDataPtr& resBlockPtr = resBlockPointers[resBlockNdx];
+
+			for (vector<int>::const_iterator refVarNdxIter = refBlock.activeVarIndices.begin(); refVarNdxIter != refBlock.activeVarIndices.end(); refVarNdxIter++)
+			{
+				const BufferVarLayoutEntry&	refEntry	= refLayout.bufferVars[*refVarNdxIter];
+				int							resVarNdx	= resLayout.getVariableIndex(refEntry.name.c_str());
+
+				if (resVarNdx >= 0)
+				{
+					const BufferVarLayoutEntry& resEntry = resLayout.bufferVars[resVarNdx];
+					allOk = compareBufferVarData(log, refEntry, refBlockPtr, resEntry, resBlockPtr) && allOk;
+				}
+			}
+		}
+	}
+
+	return allOk;
+}
+
+string getBlockAPIName (const BufferBlock& block, int instanceNdx)
+{
+	DE_ASSERT(block.isArray() || instanceNdx == 0);
+	return block.getBlockName() + (block.isArray() ? ("[" + de::toString(instanceNdx) + "]") : string());
+}
+
+vector<int> computeBufferSizes (const ShaderInterface& interface, const BufferLayout& layout)
+{
+	vector<int> sizes(layout.blocks.size());
+
+	for (int declNdx = 0; declNdx < interface.getNumBlocks(); declNdx++)
+	{
+		const BufferBlock&	block			= interface.getBlock(declNdx);
+		const bool			isArray			= block.isArray();
+		const int			numInstances	= isArray ? block.getArraySize() : 1;
+
+		for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		{
+			const string	apiName		= getBlockAPIName(block, instanceNdx);
+			const int		blockNdx	= layout.getBlockIndex(apiName);
+
+			if (blockNdx >= 0)
+			{
+				const BlockLayoutEntry&		blockLayout		= layout.blocks[blockNdx];
+				const int					baseSize		= blockLayout.size;
+				const int					lastVarNdx		= !layout.bufferVars.empty() ? blockLayout.activeVarIndices.back() : -1;
+				const BufferVarLayoutEntry*	lastEntry		= lastVarNdx >= 0 ? &layout.bufferVars[lastVarNdx] : DE_NULL;
+				const bool					isLastUnsized	= lastEntry && isUnsizedTopLevelArray(*lastEntry);
+				const int					lastArraySize	= isLastUnsized ? block.getLastUnsizedArraySize(instanceNdx) : 0;
+				const int					stride			= isLastUnsized ? getTopLevelArrayStride(*lastEntry) : 0;
+
+				sizes[blockNdx] = baseSize + lastArraySize*stride;
+			}
+		}
+	}
+
+	return sizes;
+}
+
+BlockDataPtr getBlockDataPtr (const BufferLayout& layout, const BlockLayoutEntry& blockLayout, void* ptr, int bufferSize)
+{
+	const int							lastVarNdx		= !blockLayout.activeVarIndices.empty() ? blockLayout.activeVarIndices.back() : -1;
+	const BufferVarLayoutEntry* const	lastEntry		= lastVarNdx >= 0 ? &layout.bufferVars[lastVarNdx] : DE_NULL;
+	const bool							isLastUnsized	= lastEntry && isUnsizedTopLevelArray(*lastEntry);
+	const int							baseSize		= blockLayout.size;
+
+	if (isLastUnsized)
+	{
+		const int		lastArrayStride	= getTopLevelArrayStride(*lastEntry);
+		const int		lastArraySize	= (bufferSize-baseSize) / (lastArrayStride ? lastArrayStride : 1);
+
+		DE_ASSERT(baseSize + lastArraySize*lastArrayStride == bufferSize);
+
+		return BlockDataPtr(ptr, bufferSize, lastArraySize);
+	}
+	else
+		return BlockDataPtr(ptr, bufferSize, 0);
+}
+
+struct RefDataStorage
+{
+	vector<deUint8>			data;
+	vector<BlockDataPtr>	pointers;
+};
+
+struct Buffer
+{
+	deUint32				buffer;
+	int						size;
+
+	Buffer (deUint32 buffer_, int size_) : buffer(buffer_), size(size_) {}
+	Buffer (void) : buffer(0), size(0) {}
+};
+
+struct BlockLocation
+{
+	int						index;
+	int						offset;
+	int						size;
+
+	BlockLocation (int index_, int offset_, int size_) : index(index_), offset(offset_), size(size_) {}
+	BlockLocation (void) : index(0), offset(0), size(0) {}
+};
+
+void initRefDataStorage (const ShaderInterface& interface, const BufferLayout& layout, RefDataStorage& storage)
+{
+	DE_ASSERT(storage.data.empty() && storage.pointers.empty());
+
+	const vector<int>	bufferSizes = computeBufferSizes(interface, layout);
+	int					totalSize	= 0;
+
+	for (vector<int>::const_iterator sizeIter = bufferSizes.begin(); sizeIter != bufferSizes.end(); ++sizeIter)
+		totalSize += *sizeIter;
+
+	storage.data.resize(totalSize);
+
+	// Pointers for each block.
+	{
+		deUint8*	basePtr		= storage.data.empty() ? DE_NULL : &storage.data[0];
+		int			curOffset	= 0;
+
+		DE_ASSERT(bufferSizes.size() == layout.blocks.size());
+		DE_ASSERT(totalSize == 0 || basePtr);
+
+		storage.pointers.resize(layout.blocks.size());
+
+		for (int blockNdx = 0; blockNdx < (int)layout.blocks.size(); blockNdx++)
+		{
+			const BlockLayoutEntry&	blockLayout		= layout.blocks[blockNdx];
+			const int				bufferSize		= bufferSizes[blockNdx];
+
+			storage.pointers[blockNdx] = getBlockDataPtr(layout, blockLayout, basePtr + curOffset, bufferSize);
+
+			curOffset += bufferSize;
+		}
+	}
+}
+
+vector<BlockDataPtr> blockLocationsToPtrs (const BufferLayout& layout, const vector<BlockLocation>& blockLocations, const vector<void*>& bufPtrs)
+{
+	vector<BlockDataPtr> blockPtrs(blockLocations.size());
+
+	DE_ASSERT(layout.blocks.size() == blockLocations.size());
+
+	for (int blockNdx = 0; blockNdx < (int)layout.blocks.size(); blockNdx++)
+	{
+		const BlockLayoutEntry&	blockLayout		= layout.blocks[blockNdx];
+		const BlockLocation&	location		= blockLocations[blockNdx];
+
+		blockPtrs[blockNdx] = getBlockDataPtr(layout, blockLayout, (deUint8*)bufPtrs[location.index] + location.offset, location.size);
+	}
+
+	return blockPtrs;
+}
+
+vector<void*> mapBuffers (const glw::Functions& gl, const vector<Buffer>& buffers, deUint32 access)
+{
+	vector<void*> mapPtrs(buffers.size(), DE_NULL);
+
+	try
+	{
+		for (int ndx = 0; ndx < (int)buffers.size(); ndx++)
+		{
+			if (buffers[ndx].size > 0)
+			{
+				gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[ndx].buffer);
+				mapPtrs[ndx] = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffers[ndx].size, access);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to map buffer");
+				TCU_CHECK(mapPtrs[ndx]);
+			}
+			else
+				mapPtrs[ndx] = DE_NULL;
+		}
+
+		return mapPtrs;
+	}
+	catch (...)
+	{
+		for (int ndx = 0; ndx < (int)buffers.size(); ndx++)
+		{
+			if (mapPtrs[ndx])
+			{
+				gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[ndx].buffer);
+				gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+			}
+		}
+
+		throw;
+	}
+}
+
+void unmapBuffers (const glw::Functions& gl, const vector<Buffer>& buffers)
+{
+	for (int ndx = 0; ndx < (int)buffers.size(); ndx++)
+	{
+		if (buffers[ndx].size > 0)
+		{
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[ndx].buffer);
+			gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+		}
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to unmap buffer");
+}
+
+} // anonymous (utilities)
+
+class BufferManager
+{
+public:
+								BufferManager	(const glu::RenderContext& renderCtx);
+								~BufferManager	(void);
+
+	deUint32					allocBuffer		(void);
+
+private:
+								BufferManager	(const BufferManager& other);
+	BufferManager&				operator=		(const BufferManager& other);
+
+	const glu::RenderContext&	m_renderCtx;
+	std::vector<deUint32>		m_buffers;
+};
+
+BufferManager::BufferManager (const glu::RenderContext& renderCtx)
+	: m_renderCtx(renderCtx)
+{
+}
+
+BufferManager::~BufferManager (void)
+{
+	if (!m_buffers.empty())
+		m_renderCtx.getFunctions().deleteBuffers((glw::GLsizei)m_buffers.size(), &m_buffers[0]);
+}
+
+deUint32 BufferManager::allocBuffer (void)
+{
+	deUint32 buf = 0;
+
+	m_buffers.reserve(m_buffers.size()+1);
+	m_renderCtx.getFunctions().genBuffers(1, &buf);
+	GLU_EXPECT_NO_ERROR(m_renderCtx.getFunctions().getError(), "Failed to allocate buffer");
+	m_buffers.push_back(buf);
+
+	return buf;
+}
+
+} // bb
+
+using namespace bb;
+
+// SSBOLayoutCase.
+
+SSBOLayoutCase::SSBOLayoutCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, glu::GLSLVersion glslVersion, BufferMode bufferMode)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_glslVersion	(glslVersion)
+	, m_bufferMode	(bufferMode)
+{
+	DE_ASSERT(glslVersion == glu::GLSL_VERSION_310_ES || glslVersion == glu::GLSL_VERSION_430);
+}
+
+SSBOLayoutCase::~SSBOLayoutCase (void)
+{
+}
+
+SSBOLayoutCase::IterateResult SSBOLayoutCase::iterate (void)
+{
+	TestLog&					log				= m_testCtx.getLog();
+	const glw::Functions&		gl				= m_renderCtx.getFunctions();
+
+	BufferLayout				refLayout;		// std140 / std430 layout.
+	BufferLayout				glLayout;		// Layout reported by GL.
+	RefDataStorage				initialData;	// Initial data stored in buffer.
+	RefDataStorage				writeData;		// Data written by compute shader.
+
+	BufferManager				bufferManager	(m_renderCtx);
+	vector<Buffer>				buffers;		// Buffers allocated for storage
+	vector<BlockLocation>		blockLocations;	// Block locations in storage (index, offset)
+
+	// Initialize result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	computeReferenceLayout	(refLayout, m_interface);
+	initRefDataStorage		(m_interface, refLayout, initialData);
+	initRefDataStorage		(m_interface, refLayout, writeData);
+	generateValues			(refLayout, initialData.pointers, deStringHash(getName()) ^ 0xad2f7214);
+	generateValues			(refLayout, writeData.pointers, deStringHash(getName()) ^ 0x25ca4e7);
+	copyNonWrittenData		(m_interface, refLayout, initialData.pointers, writeData.pointers);
+
+	const glu::ShaderProgram program(m_renderCtx, glu::ProgramSources() << glu::ComputeSource(generateComputeShader(m_glslVersion, m_interface, refLayout, initialData.pointers, writeData.pointers)));
+	log << program;
+
+	if (!program.isOk())
+	{
+		// Compile failed.
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		return STOP;
+	}
+
+	// Query layout from GL.
+	getGLBufferLayout(gl, glLayout, program.getProgram());
+
+	// Print layout to log.
+	{
+		tcu::ScopedLogSection section(log, "ActiveBufferBlocks", "Active Buffer Blocks");
+		for (int blockNdx = 0; blockNdx < (int)glLayout.blocks.size(); blockNdx++)
+			log << TestLog::Message << blockNdx << ": " << glLayout.blocks[blockNdx] << TestLog::EndMessage;
+	}
+
+	{
+		tcu::ScopedLogSection section(log, "ActiveBufferVars", "Active Buffer Variables");
+		for (int varNdx = 0; varNdx < (int)glLayout.bufferVars.size(); varNdx++)
+			log << TestLog::Message << varNdx << ": " << glLayout.bufferVars[varNdx] << TestLog::EndMessage;
+	}
+
+	// Verify layouts.
+	{
+		if (!checkLayoutIndices(glLayout) || !checkLayoutBounds(glLayout) || !compareTypes(refLayout, glLayout))
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid layout");
+			return STOP; // It is not safe to use the given layout.
+		}
+
+		if (!compareStdBlocks(refLayout, glLayout))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid std140 or std430 layout");
+
+		if (!compareSharedBlocks(refLayout, glLayout))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid shared layout");
+
+		if (!checkIndexQueries(program.getProgram(), glLayout))
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Inconsintent block index query results");
+	}
+
+	// Allocate GL buffers & compute placement.
+	{
+		const int			numBlocks		= (int)glLayout.blocks.size();
+		const vector<int>	bufferSizes		= computeBufferSizes(m_interface, glLayout);
+
+		DE_ASSERT(bufferSizes.size() == glLayout.blocks.size());
+
+		blockLocations.resize(numBlocks);
+
+		if (m_bufferMode == BUFFERMODE_PER_BLOCK)
+		{
+			buffers.resize(numBlocks);
+
+			for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+			{
+				const int bufferSize = bufferSizes[blockNdx];
+
+				buffers[blockNdx].size = bufferSize;
+				blockLocations[blockNdx] = BlockLocation(blockNdx, 0, bufferSize);
+			}
+		}
+		else
+		{
+			DE_ASSERT(m_bufferMode == BUFFERMODE_SINGLE);
+
+			int		bindingAlignment	= 0;
+			int		totalSize			= 0;
+
+			gl.getIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &bindingAlignment);
+
+			{
+				int curOffset = 0;
+				DE_ASSERT(bufferSizes.size() == glLayout.blocks.size());
+				for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+				{
+					const int bufferSize = bufferSizes[blockNdx];
+
+					if (bindingAlignment > 0)
+						curOffset = deRoundUp32(curOffset, bindingAlignment);
+
+					blockLocations[blockNdx] = BlockLocation(0, curOffset, bufferSize);
+					curOffset += bufferSize;
+				}
+				totalSize = curOffset;
+			}
+
+			buffers.resize(1);
+			buffers[0].size = totalSize;
+		}
+
+		for (int bufNdx = 0; bufNdx < (int)buffers.size(); bufNdx++)
+		{
+			const int		bufferSize	= buffers[bufNdx].size;
+			const deUint32	buffer		= bufferManager.allocBuffer();
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_STATIC_DRAW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to allocate buffer");
+
+			buffers[bufNdx].buffer = buffer;
+		}
+	}
+
+	{
+		const vector<void*>			mapPtrs			= mapBuffers(gl, buffers, GL_MAP_WRITE_BIT);
+		const vector<BlockDataPtr>	mappedBlockPtrs	= blockLocationsToPtrs(glLayout, blockLocations, mapPtrs);
+
+		copyData(glLayout, mappedBlockPtrs, refLayout, initialData.pointers);
+
+		unmapBuffers(gl, buffers);
+	}
+
+	{
+		int bindingPoint = 0;
+
+		for (int blockDeclNdx = 0; blockDeclNdx < m_interface.getNumBlocks(); blockDeclNdx++)
+		{
+			const BufferBlock&	block		= m_interface.getBlock(blockDeclNdx);
+			const int			numInst		= block.isArray() ? block.getArraySize() : 1;
+
+			for (int instNdx = 0; instNdx < numInst; instNdx++)
+			{
+				const string	instName	= getBlockAPIName(block, instNdx);
+				const int		layoutNdx	= findBlockIndex(glLayout, instName);
+
+				if (layoutNdx >= 0)
+				{
+					const BlockLocation& blockLoc = blockLocations[layoutNdx];
+					gl.bindBufferRange(GL_SHADER_STORAGE_BUFFER, bindingPoint, buffers[blockLoc.index].buffer, blockLoc.offset, blockLoc.size);
+				}
+
+				bindingPoint += 1;
+			}
+		}
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to bind buffers");
+
+	{
+		const bool execOk = execute(program.getProgram());
+
+		if (execOk)
+		{
+			const vector<void*>			mapPtrs			= mapBuffers(gl, buffers, GL_MAP_READ_BIT);
+			const vector<BlockDataPtr>	mappedBlockPtrs	= blockLocationsToPtrs(glLayout, blockLocations, mapPtrs);
+
+			const bool					compareOk		= compareData(m_testCtx.getLog(), refLayout, writeData.pointers, glLayout, mappedBlockPtrs);
+
+			unmapBuffers(gl, buffers);
+
+			if (!compareOk)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result comparison failed");
+		}
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader execution failed");
+	}
+
+	return STOP;
+}
+
+bool SSBOLayoutCase::compareStdBlocks (const BufferLayout& refLayout, const BufferLayout& cmpLayout) const
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		isOk		= true;
+	int			numBlocks	= m_interface.getNumBlocks();
+
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		const BufferBlock&		block			= m_interface.getBlock(blockNdx);
+		bool					isArray			= block.isArray();
+		std::string				instanceName	= string(block.getBlockName()) + (isArray ? "[0]" : "");
+		int						refBlockNdx		= refLayout.getBlockIndex(instanceName.c_str());
+		int						cmpBlockNdx		= cmpLayout.getBlockIndex(instanceName.c_str());
+
+		if ((block.getFlags() & (LAYOUT_STD140|LAYOUT_STD430)) == 0)
+			continue; // Not std* layout.
+
+		DE_ASSERT(refBlockNdx >= 0);
+
+		if (cmpBlockNdx < 0)
+		{
+			// Not found.
+			log << TestLog::Message << "Error: Buffer block '" << instanceName << "' not found" << TestLog::EndMessage;
+			isOk = false;
+			continue;
+		}
+
+		const BlockLayoutEntry&		refBlockLayout	= refLayout.blocks[refBlockNdx];
+		const BlockLayoutEntry&		cmpBlockLayout	= cmpLayout.blocks[cmpBlockNdx];
+
+		// \todo [2012-01-24 pyry] Verify that activeVarIndices is correct.
+		// \todo [2012-01-24 pyry] Verify all instances.
+		if (refBlockLayout.activeVarIndices.size() != cmpBlockLayout.activeVarIndices.size())
+		{
+			log << TestLog::Message << "Error: Number of active variables differ in block '" << instanceName
+				<< "' (expected " << refBlockLayout.activeVarIndices.size()
+				<< ", got " << cmpBlockLayout.activeVarIndices.size()
+				<< ")" << TestLog::EndMessage;
+			isOk = false;
+		}
+
+		for (vector<int>::const_iterator ndxIter = refBlockLayout.activeVarIndices.begin(); ndxIter != refBlockLayout.activeVarIndices.end(); ndxIter++)
+		{
+			const BufferVarLayoutEntry&	refEntry	= refLayout.bufferVars[*ndxIter];
+			int							cmpEntryNdx	= cmpLayout.getVariableIndex(refEntry.name.c_str());
+
+			if (cmpEntryNdx < 0)
+			{
+				log << TestLog::Message << "Error: Buffer variable '" << refEntry.name << "' not found" << TestLog::EndMessage;
+				isOk = false;
+				continue;
+			}
+
+			const BufferVarLayoutEntry&	cmpEntry	= cmpLayout.bufferVars[cmpEntryNdx];
+
+			if (refEntry.type					!= cmpEntry.type				||
+				refEntry.arraySize				!= cmpEntry.arraySize			||
+				refEntry.offset					!= cmpEntry.offset				||
+				refEntry.arrayStride			!= cmpEntry.arrayStride			||
+				refEntry.matrixStride			!= cmpEntry.matrixStride		||
+				refEntry.topLevelArraySize		!= cmpEntry.topLevelArraySize	||
+				refEntry.topLevelArrayStride	!= cmpEntry.topLevelArrayStride	||
+				refEntry.isRowMajor				!= cmpEntry.isRowMajor)
+			{
+				log << TestLog::Message << "Error: Layout mismatch in '" << refEntry.name << "':\n"
+					<< "  expected: " << refEntry << "\n"
+					<< "  got: " << cmpEntry
+					<< TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+	}
+
+	return isOk;
+}
+
+bool SSBOLayoutCase::compareSharedBlocks (const BufferLayout& refLayout, const BufferLayout& cmpLayout) const
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		isOk		= true;
+	int			numBlocks	= m_interface.getNumBlocks();
+
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		const BufferBlock&		block			= m_interface.getBlock(blockNdx);
+		bool					isArray			= block.isArray();
+		std::string				instanceName	= string(block.getBlockName()) + (isArray ? "[0]" : "");
+		int						refBlockNdx		= refLayout.getBlockIndex(instanceName.c_str());
+		int						cmpBlockNdx		= cmpLayout.getBlockIndex(instanceName.c_str());
+
+		if ((block.getFlags() & LAYOUT_SHARED) == 0)
+			continue; // Not shared layout.
+
+		DE_ASSERT(refBlockNdx >= 0);
+
+		if (cmpBlockNdx < 0)
+		{
+			// Not found, should it?
+			log << TestLog::Message << "Error: Buffer block '" << instanceName << "' not found" << TestLog::EndMessage;
+			isOk = false;
+			continue;
+		}
+
+		const BlockLayoutEntry&		refBlockLayout	= refLayout.blocks[refBlockNdx];
+		const BlockLayoutEntry&		cmpBlockLayout	= cmpLayout.blocks[cmpBlockNdx];
+
+		if (refBlockLayout.activeVarIndices.size() != cmpBlockLayout.activeVarIndices.size())
+		{
+			log << TestLog::Message << "Error: Number of active variables differ in block '" << instanceName
+				<< "' (expected " << refBlockLayout.activeVarIndices.size()
+				<< ", got " << cmpBlockLayout.activeVarIndices.size()
+				<< ")" << TestLog::EndMessage;
+			isOk = false;
+		}
+
+		for (vector<int>::const_iterator ndxIter = refBlockLayout.activeVarIndices.begin(); ndxIter != refBlockLayout.activeVarIndices.end(); ndxIter++)
+		{
+			const BufferVarLayoutEntry&	refEntry	= refLayout.bufferVars[*ndxIter];
+			int							cmpEntryNdx	= cmpLayout.getVariableIndex(refEntry.name.c_str());
+
+			if (cmpEntryNdx < 0)
+			{
+				log << TestLog::Message << "Error: Buffer variable '" << refEntry.name << "' not found" << TestLog::EndMessage;
+				isOk = false;
+				continue;
+			}
+
+			const BufferVarLayoutEntry&	cmpEntry	= cmpLayout.bufferVars[cmpEntryNdx];
+
+			if (refEntry.type				!= cmpEntry.type				||
+				refEntry.arraySize			!= cmpEntry.arraySize			||
+				refEntry.topLevelArraySize	!= cmpEntry.topLevelArraySize	||
+				refEntry.isRowMajor	!= cmpEntry.isRowMajor)
+			{
+				log << TestLog::Message << "Error: Type / array size mismatch in '" << refEntry.name << "':\n"
+					<< "  expected: " << refEntry << "\n"
+					<< "  got: " << cmpEntry
+					<< TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+	}
+
+	return isOk;
+}
+
+bool SSBOLayoutCase::compareTypes (const BufferLayout& refLayout, const BufferLayout& cmpLayout) const
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		isOk		= true;
+	int			numBlocks	= m_interface.getNumBlocks();
+
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		const BufferBlock&		block			= m_interface.getBlock(blockNdx);
+		bool					isArray			= block.isArray();
+		int						numInstances	= isArray ? block.getArraySize() : 1;
+
+		for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		{
+			std::ostringstream instanceName;
+
+			instanceName << block.getBlockName();
+			if (isArray)
+				instanceName << "[" << instanceNdx << "]";
+
+			int cmpBlockNdx = cmpLayout.getBlockIndex(instanceName.str().c_str());
+
+			if (cmpBlockNdx < 0)
+				continue;
+
+			const BlockLayoutEntry& cmpBlockLayout = cmpLayout.blocks[cmpBlockNdx];
+
+			for (vector<int>::const_iterator ndxIter = cmpBlockLayout.activeVarIndices.begin(); ndxIter != cmpBlockLayout.activeVarIndices.end(); ndxIter++)
+			{
+				const BufferVarLayoutEntry&	cmpEntry	= cmpLayout.bufferVars[*ndxIter];
+				int							refEntryNdx	= refLayout.getVariableIndex(cmpEntry.name.c_str());
+
+				if (refEntryNdx < 0)
+				{
+					log << TestLog::Message << "Error: Buffer variable '" << cmpEntry.name << "' not found in reference layout" << TestLog::EndMessage;
+					isOk = false;
+					continue;
+				}
+
+				const BufferVarLayoutEntry&	refEntry	= refLayout.bufferVars[refEntryNdx];
+
+				if (refEntry.type != cmpEntry.type)
+				{
+					log << TestLog::Message << "Error: Buffer variable type mismatch in '" << refEntry.name << "':\n"
+						<< "  expected: " << glu::getDataTypeName(refEntry.type) << "\n"
+						<< "  got: " << glu::getDataTypeName(cmpEntry.type)
+						<< TestLog::EndMessage;
+					isOk = false;
+				}
+
+				if (refEntry.arraySize < cmpEntry.arraySize)
+				{
+					log << TestLog::Message << "Error: Invalid array size in '" << refEntry.name << "': expected <= " << refEntry.arraySize << TestLog::EndMessage;
+					isOk = false;
+				}
+
+				if (refEntry.topLevelArraySize < cmpEntry.topLevelArraySize)
+				{
+					log << TestLog::Message << "Error: Invalid top-level array size in '" << refEntry.name << "': expected <= " << refEntry.topLevelArraySize << TestLog::EndMessage;
+					isOk = false;
+				}
+			}
+		}
+	}
+
+	return isOk;
+}
+
+bool SSBOLayoutCase::checkLayoutIndices (const BufferLayout& layout) const
+{
+	TestLog&	log			= m_testCtx.getLog();
+	int			numVars		= (int)layout.bufferVars.size();
+	int			numBlocks	= (int)layout.blocks.size();
+	bool		isOk		= true;
+
+	// Check variable block indices.
+	for (int varNdx = 0; varNdx < numVars; varNdx++)
+	{
+		const BufferVarLayoutEntry& bufVar = layout.bufferVars[varNdx];
+
+		if (bufVar.blockNdx < 0 || !deInBounds32(bufVar.blockNdx, 0, numBlocks))
+		{
+			log << TestLog::Message << "Error: Invalid block index in buffer variable '" << bufVar.name << "'" << TestLog::EndMessage;
+			isOk = false;
+		}
+	}
+
+	// Check active variables.
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		const BlockLayoutEntry& block = layout.blocks[blockNdx];
+
+		for (vector<int>::const_iterator varNdxIter = block.activeVarIndices.begin(); varNdxIter != block.activeVarIndices.end(); varNdxIter++)
+		{
+			if (!deInBounds32(*varNdxIter, 0, numVars))
+			{
+				log << TestLog::Message << "Error: Invalid active variable index " << *varNdxIter << " in block '" << block.name << "'" << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+	}
+
+	return isOk;
+}
+
+bool SSBOLayoutCase::checkLayoutBounds (const BufferLayout& layout) const
+{
+	TestLog&	log			= m_testCtx.getLog();
+	const int	numVars		= (int)layout.bufferVars.size();
+	bool		isOk		= true;
+
+	for (int varNdx = 0; varNdx < numVars; varNdx++)
+	{
+		const BufferVarLayoutEntry& var = layout.bufferVars[varNdx];
+
+		if (var.blockNdx < 0 || isUnsizedTopLevelArray(var))
+			continue;
+
+		const BlockLayoutEntry&		block			= layout.blocks[var.blockNdx];
+		const bool					isMatrix		= glu::isDataTypeMatrix(var.type);
+		const int					numVecs			= isMatrix ? (var.isRowMajor ? glu::getDataTypeMatrixNumRows(var.type) : glu::getDataTypeMatrixNumColumns(var.type)) : 1;
+		const int					numComps		= isMatrix ? (var.isRowMajor ? glu::getDataTypeMatrixNumColumns(var.type) : glu::getDataTypeMatrixNumRows(var.type)) : glu::getDataTypeScalarSize(var.type);
+		const int					numElements		= getArraySize(var);
+		const int					topLevelSize	= getTopLevelArraySize(var);
+		const int					arrayStride		= getArrayStride(var);
+		const int					topLevelStride	= getTopLevelArrayStride(var);
+		const int					compSize		= sizeof(deUint32);
+		const int					vecSize			= numComps*compSize;
+
+		int							minOffset		= 0;
+		int							maxOffset		= 0;
+
+		// For negative strides.
+		minOffset	= de::min(minOffset, (numVecs-1)*var.matrixStride);
+		minOffset	= de::min(minOffset, (numElements-1)*arrayStride);
+		minOffset	= de::min(minOffset, (topLevelSize-1)*topLevelStride + (numElements-1)*arrayStride + (numVecs-1)*var.matrixStride);
+
+		maxOffset	= de::max(maxOffset, vecSize);
+		maxOffset	= de::max(maxOffset, (numVecs-1)*var.matrixStride + vecSize);
+		maxOffset	= de::max(maxOffset, (numElements-1)*arrayStride + vecSize);
+		maxOffset	= de::max(maxOffset, (topLevelSize-1)*topLevelStride + (numElements-1)*arrayStride + vecSize);
+		maxOffset	= de::max(maxOffset, (topLevelSize-1)*topLevelStride + (numElements-1)*arrayStride + (numVecs-1)*var.matrixStride + vecSize);
+
+		if (var.offset+minOffset < 0 || var.offset+maxOffset > block.size)
+		{
+			log << TestLog::Message << "Error: Variable '" << var.name << "' out of block bounds" << TestLog::EndMessage;
+			isOk = false;
+		}
+	}
+
+	return isOk;
+}
+
+bool SSBOLayoutCase::checkIndexQueries (deUint32 program, const BufferLayout& layout) const
+{
+	tcu::TestLog&				log			= m_testCtx.getLog();
+	const glw::Functions&		gl			= m_renderCtx.getFunctions();
+	bool						allOk		= true;
+
+	// \note Spec mandates that buffer blocks are assigned consecutive locations from 0.
+	//		 BlockLayoutEntries are stored in that order in UniformLayout.
+	for (int blockNdx = 0; blockNdx < (int)layout.blocks.size(); blockNdx++)
+	{
+		const BlockLayoutEntry&		block		= layout.blocks[blockNdx];
+		const int					queriedNdx	= gl.getProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, block.name.c_str());
+
+		if (queriedNdx != blockNdx)
+		{
+			log << TestLog::Message << "ERROR: glGetProgramResourceIndex(" << block.name << ") returned " << queriedNdx << ", expected " << blockNdx << "!" << TestLog::EndMessage;
+			allOk = false;
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformBlockIndex()");
+	}
+
+	return allOk;
+}
+
+bool SSBOLayoutCase::execute (deUint32 program)
+{
+	const glw::Functions&				gl				= m_renderCtx.getFunctions();
+	const deUint32						numPassedLoc	= gl.getProgramResourceIndex(program, GL_UNIFORM, "ac_numPassed");
+	const glu::InterfaceVariableInfo	acVarInfo		= numPassedLoc != GL_INVALID_INDEX ? glu::getProgramInterfaceVariableInfo(gl, program, GL_UNIFORM, numPassedLoc)
+																						   : glu::InterfaceVariableInfo();
+	const glu::InterfaceBlockInfo		acBufferInfo	= acVarInfo.atomicCounterBufferIndex != GL_INVALID_INDEX ? glu::getProgramInterfaceBlockInfo(gl, program, GL_ATOMIC_COUNTER_BUFFER, acVarInfo.atomicCounterBufferIndex)
+																												 : glu::InterfaceBlockInfo();
+	const glu::Buffer					acBuffer		(m_renderCtx);
+	bool								isOk			= true;
+
+	if (numPassedLoc == GL_INVALID_INDEX)
+		throw tcu::TestError("No location for ac_numPassed found");
+
+	if (acBufferInfo.index == GL_INVALID_INDEX)
+		throw tcu::TestError("ac_numPassed buffer index is GL_INVALID_INDEX");
+
+	if (acBufferInfo.dataSize == 0)
+		throw tcu::TestError("ac_numPassed buffer size = 0");
+
+	// Initialize atomic counter buffer.
+	{
+		vector<deUint8> emptyData(acBufferInfo.dataSize, 0);
+
+		gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, *acBuffer);
+		gl.bufferData(GL_ATOMIC_COUNTER_BUFFER, (glw::GLsizeiptr)emptyData.size(), &emptyData[0], GL_STATIC_READ);
+		gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, acBufferInfo.index, *acBuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Setting up buffer for ac_numPassed failed");
+	}
+
+	gl.useProgram(program);
+	gl.dispatchCompute(1, 1, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute() failed");
+
+	// Read back ac_numPassed data.
+	{
+		const void*	mapPtr		= gl.mapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, acBufferInfo.dataSize, GL_MAP_READ_BIT);
+		const int	refCount	= 1;
+		int			resCount	= 0;
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER) failed");
+		TCU_CHECK(mapPtr);
+
+		resCount = *(const int*)((const deUint8*)mapPtr + acVarInfo.offset);
+
+		gl.unmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER) failed");
+
+		if (refCount != resCount)
+		{
+			m_testCtx.getLog() << TestLog::Message << "ERROR: ac_numPassed = " << resCount << ", expected " << refCount << TestLog::EndMessage;
+			isOk = false;
+		}
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Shader execution failed");
+
+	return isOk;
+}
+
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fSSBOLayoutCase.hpp b/modules/gles31/functional/es31fSSBOLayoutCase.hpp
new file mode 100644
index 0000000..0ca3537
--- /dev/null
+++ b/modules/gles31/functional/es31fSSBOLayoutCase.hpp
@@ -0,0 +1,183 @@
+#ifndef _ES31FSSBOLAYOUTCASE_HPP
+#define _ES31FSSBOLAYOUTCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief SSBO layout tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluVarType.hpp"
+
+namespace glu
+{
+class RenderContext;
+}
+
+namespace deqp
+{
+namespace gles31
+{
+
+// Buffer block details.
+namespace bb
+{
+
+enum BufferVarFlags
+{
+	LAYOUT_SHARED		= (1<<0),
+	LAYOUT_PACKED		= (1<<1),
+	LAYOUT_STD140		= (1<<2),
+	LAYOUT_STD430		= (1<<3),
+	LAYOUT_ROW_MAJOR	= (1<<4),
+	LAYOUT_COLUMN_MAJOR	= (1<<5),	//!< \note Lack of both flags means column-major matrix.
+	LAYOUT_MASK			= LAYOUT_SHARED|LAYOUT_PACKED|LAYOUT_STD140|LAYOUT_STD430|LAYOUT_ROW_MAJOR|LAYOUT_COLUMN_MAJOR,
+
+	// \todo [2013-10-14 pyry] Investigate adding these.
+/*	QUALIFIER_COHERENT	= (1<<6),
+	QUALIFIER_VOLATILE	= (1<<7),
+	QUALIFIER_RESTRICT	= (1<<8),
+	QUALIFIER_READONLY	= (1<<9),
+	QUALIFIER_WRITEONLY	= (1<<10),*/
+
+	ACCESS_READ			= (1<<11),	//!< Buffer variable is read in the shader.
+	ACCESS_WRITE		= (1<<12),	//!< Buffer variable is written in the shader.
+};
+
+class BufferVar
+{
+public:
+						BufferVar		(const char* name, const glu::VarType& type, deUint32 flags);
+
+	const char*			getName			(void) const { return m_name.c_str();	}
+	const glu::VarType&	getType			(void) const { return m_type;			}
+	deUint32			getFlags		(void) const { return m_flags;			}
+
+private:
+	std::string			m_name;
+	glu::VarType		m_type;
+	deUint32			m_flags;
+};
+
+class BufferBlock
+{
+public:
+	typedef std::vector<BufferVar>::iterator		iterator;
+	typedef std::vector<BufferVar>::const_iterator	const_iterator;
+
+							BufferBlock				(const char* blockName);
+
+	const char*				getBlockName			(void) const { return m_blockName.c_str();		}
+	const char*				getInstanceName			(void) const { return m_instanceName.empty() ? DE_NULL : m_instanceName.c_str();	}
+	bool					isArray					(void) const { return m_arraySize > 0;			}
+	int						getArraySize			(void) const { return m_arraySize;				}
+	deUint32				getFlags				(void) const { return m_flags;					}
+
+	void					setInstanceName			(const char* name)			{ m_instanceName = name;			}
+	void					setFlags				(deUint32 flags)			{ m_flags = flags;					}
+	void					addMember				(const BufferVar& var)		{ m_variables.push_back(var);		}
+	void					setArraySize			(int arraySize);
+
+	int						getLastUnsizedArraySize	(int instanceNdx) const		{ return m_lastUnsizedArraySizes[instanceNdx];	}
+	void					setLastUnsizedArraySize	(int instanceNdx, int size)	{ m_lastUnsizedArraySizes[instanceNdx] = size;	}
+
+	inline iterator			begin					(void)			{ return m_variables.begin();	}
+	inline const_iterator	begin					(void) const	{ return m_variables.begin();	}
+	inline iterator			end						(void)			{ return m_variables.end();		}
+	inline const_iterator	end						(void) const	{ return m_variables.end();		}
+
+private:
+	std::string				m_blockName;
+	std::string				m_instanceName;
+	std::vector<BufferVar>	m_variables;
+	int						m_arraySize;				//!< Array size or 0 if not interface block array.
+	std::vector<int>		m_lastUnsizedArraySizes;	//!< Sizes of last unsized array element, can be different per instance.
+	deUint32				m_flags;
+};
+
+class ShaderInterface
+{
+public:
+									ShaderInterface			(void);
+									~ShaderInterface		(void);
+
+	glu::StructType&				allocStruct				(const char* name);
+	const glu::StructType*			findStruct				(const char* name) const;
+	void							getNamedStructs			(std::vector<const glu::StructType*>& structs) const;
+
+	BufferBlock&					allocBlock				(const char* name);
+
+	int								getNumBlocks			(void) const	{ return (int)m_bufferBlocks.size();	}
+	const BufferBlock&				getBlock				(int ndx) const	{ return *m_bufferBlocks[ndx];			}
+
+private:
+									ShaderInterface			(const ShaderInterface&);
+	ShaderInterface&				operator=				(const ShaderInterface&);
+
+	std::vector<glu::StructType*>	m_structs;
+	std::vector<BufferBlock*>		m_bufferBlocks;
+};
+
+class BufferLayout;
+
+} // bb
+
+class SSBOLayoutCase : public tcu::TestCase
+{
+public:
+	enum BufferMode
+	{
+		BUFFERMODE_SINGLE = 0,	//!< Single buffer shared between uniform blocks.
+		BUFFERMODE_PER_BLOCK,	//!< Per-block buffers
+
+		BUFFERMODE_LAST
+	};
+
+								SSBOLayoutCase				(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, glu::GLSLVersion glslVersion, BufferMode bufferMode);
+								~SSBOLayoutCase				(void);
+
+	IterateResult				iterate						(void);
+
+protected:
+	bool						compareStdBlocks			(const bb::BufferLayout& refLayout, const bb::BufferLayout& cmpLayout) const;
+	bool						compareSharedBlocks			(const bb::BufferLayout& refLayout, const bb::BufferLayout& cmpLayout) const;
+	bool						compareTypes				(const bb::BufferLayout& refLayout, const bb::BufferLayout& cmpLayout) const;
+	bool						checkLayoutIndices			(const bb::BufferLayout& layout) const;
+	bool						checkLayoutBounds			(const bb::BufferLayout& layout) const;
+	bool						checkIndexQueries			(deUint32 program, const bb::BufferLayout& layout) const;
+
+	bool						execute						(deUint32 program);
+
+	glu::RenderContext&			m_renderCtx;
+	glu::GLSLVersion			m_glslVersion;
+	BufferMode					m_bufferMode;
+	bb::ShaderInterface			m_interface;
+
+private:
+								SSBOLayoutCase				(const SSBOLayoutCase&);
+	SSBOLayoutCase&				operator=					(const SSBOLayoutCase&);
+};
+
+} // gles31
+} // deqp
+
+#endif // _ES31FSSBOLAYOUTCASE_HPP
diff --git a/modules/gles31/functional/es31fSSBOLayoutTests.cpp b/modules/gles31/functional/es31fSSBOLayoutTests.cpp
new file mode 100644
index 0000000..289be6a
--- /dev/null
+++ b/modules/gles31/functional/es31fSSBOLayoutTests.cpp
@@ -0,0 +1,1326 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief SSBO layout tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fSSBOLayoutTests.hpp"
+#include "es31fSSBOLayoutCase.hpp"
+#include "tcuCommandLine.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using namespace bb;
+using glu::VarType;
+using glu::StructType;
+
+namespace
+{
+
+enum FeatureBits
+{
+	FEATURE_VECTORS				= (1<<0),
+	FEATURE_MATRICES			= (1<<1),
+	FEATURE_ARRAYS				= (1<<2),
+	FEATURE_STRUCTS				= (1<<3),
+	FEATURE_NESTED_STRUCTS		= (1<<4),
+	FEATURE_INSTANCE_ARRAYS		= (1<<5),
+	FEATURE_UNUSED_VARS			= (1<<6),
+	FEATURE_UNUSED_MEMBERS		= (1<<7),
+	FEATURE_PACKED_LAYOUT		= (1<<8),
+	FEATURE_SHARED_LAYOUT		= (1<<9),
+	FEATURE_STD140_LAYOUT		= (1<<10),
+	FEATURE_STD430_LAYOUT		= (1<<11),
+	FEATURE_MATRIX_LAYOUT		= (1<<12),	//!< Matrix layout flags.
+	FEATURE_UNSIZED_ARRAYS		= (1<<13),
+	FEATURE_ARRAYS_OF_ARRAYS	= (1<<14)
+};
+
+class RandomSSBOLayoutCase : public SSBOLayoutCase
+{
+public:
+
+							RandomSSBOLayoutCase		(Context& context, const char* name, const char* description, BufferMode bufferMode, deUint32 features, deUint32 seed);
+
+	void					init						(void);
+
+private:
+	void					generateBlock				(de::Random& rnd, deUint32 layoutFlags);
+	void					generateBufferVar			(de::Random& rnd, BufferBlock& block, bool isLastMember);
+	glu::VarType			generateType				(de::Random& rnd, int typeDepth, bool arrayOk, bool unusedArrayOk);
+
+	deUint32				m_features;
+	int						m_maxBlocks;
+	int						m_maxInstances;
+	int						m_maxArrayLength;
+	int						m_maxStructDepth;
+	int						m_maxBlockMembers;
+	int						m_maxStructMembers;
+	deUint32				m_seed;
+
+	int						m_blockNdx;
+	int						m_bufferVarNdx;
+	int						m_structNdx;
+};
+
+RandomSSBOLayoutCase::RandomSSBOLayoutCase (Context& context, const char* name, const char* description, BufferMode bufferMode, deUint32 features, deUint32 seed)
+	: SSBOLayoutCase		(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+	, m_features			(features)
+	, m_maxBlocks			(4)
+	, m_maxInstances		((features & FEATURE_INSTANCE_ARRAYS)	? 3 : 0)
+	, m_maxArrayLength		((features & FEATURE_ARRAYS)			? 8 : 0)
+	, m_maxStructDepth		((features & FEATURE_STRUCTS)			? 2 : 0)
+	, m_maxBlockMembers		(5)
+	, m_maxStructMembers	(4)
+	, m_seed				(seed)
+	, m_blockNdx			(1)
+	, m_bufferVarNdx		(1)
+	, m_structNdx			(1)
+{
+}
+
+void RandomSSBOLayoutCase::init (void)
+{
+	de::Random rnd(m_seed);
+
+	const int numBlocks = rnd.getInt(1, m_maxBlocks);
+
+	for (int ndx = 0; ndx < numBlocks; ndx++)
+		generateBlock(rnd, 0);
+}
+
+void RandomSSBOLayoutCase::generateBlock (de::Random& rnd, deUint32 layoutFlags)
+{
+	DE_ASSERT(m_blockNdx <= 'z' - 'a');
+
+	const float		instanceArrayWeight	= 0.3f;
+	BufferBlock&	block				= m_interface.allocBlock((string("Block") + (char)('A' + m_blockNdx)).c_str());
+	int				numInstances		= (m_maxInstances > 0 && rnd.getFloat() < instanceArrayWeight) ? rnd.getInt(0, m_maxInstances) : 0;
+	int				numVars				= rnd.getInt(1, m_maxBlockMembers);
+
+	if (numInstances > 0)
+		block.setArraySize(numInstances);
+
+	if (numInstances > 0 || rnd.getBool())
+		block.setInstanceName((string("block") + (char)('A' + m_blockNdx)).c_str());
+
+	// Layout flag candidates.
+	vector<deUint32> layoutFlagCandidates;
+	layoutFlagCandidates.push_back(0);
+	if (m_features & FEATURE_PACKED_LAYOUT)
+		layoutFlagCandidates.push_back(LAYOUT_PACKED);
+	if ((m_features & FEATURE_SHARED_LAYOUT))
+		layoutFlagCandidates.push_back(LAYOUT_SHARED);
+	if (m_features & FEATURE_STD140_LAYOUT)
+		layoutFlagCandidates.push_back(LAYOUT_STD140);
+
+	layoutFlags |= rnd.choose<deUint32>(layoutFlagCandidates.begin(), layoutFlagCandidates.end());
+
+	if (m_features & FEATURE_MATRIX_LAYOUT)
+	{
+		static const deUint32 matrixCandidates[] = { 0, LAYOUT_ROW_MAJOR, LAYOUT_COLUMN_MAJOR };
+		layoutFlags |= rnd.choose<deUint32>(&matrixCandidates[0], &matrixCandidates[DE_LENGTH_OF_ARRAY(matrixCandidates)]);
+	}
+
+	block.setFlags(layoutFlags);
+
+	for (int ndx = 0; ndx < numVars; ndx++)
+		generateBufferVar(rnd, block, (ndx+1 == numVars));
+
+	if (numVars > 0)
+	{
+		const BufferVar&	lastVar			= *(block.end()-1);
+		const glu::VarType&	lastType		= lastVar.getType();
+		const bool			isUnsizedArr	= lastType.isArrayType() && (lastType.getArraySize() == glu::VarType::UNSIZED_ARRAY);
+
+		if (isUnsizedArr)
+		{
+			for (int instanceNdx = 0; instanceNdx < (numInstances ? numInstances : 1); instanceNdx++)
+			{
+				const int arrSize = rnd.getInt(0, m_maxArrayLength);
+				block.setLastUnsizedArraySize(instanceNdx, arrSize);
+			}
+		}
+	}
+
+	m_blockNdx += 1;
+}
+
+static std::string genName (char first, char last, int ndx)
+{
+	std::string	str			= "";
+	int			alphabetLen	= last - first + 1;
+
+	while (ndx > alphabetLen)
+	{
+		str.insert(str.begin(), (char)(first + ((ndx-1)%alphabetLen)));
+		ndx = ((ndx-1) / alphabetLen);
+	}
+
+	str.insert(str.begin(), (char)(first + (ndx%(alphabetLen+1)) - 1));
+
+	return str;
+}
+
+void RandomSSBOLayoutCase::generateBufferVar (de::Random& rnd, BufferBlock& block, bool isLastMember)
+{
+	const float			readWeight			= 0.7f;
+	const float			writeWeight			= 0.7f;
+	const float			accessWeight		= 0.85f;
+	const bool			unusedOk			= (m_features & FEATURE_UNUSED_VARS) != 0;
+	const std::string	name				= genName('a', 'z', m_bufferVarNdx);
+	const glu::VarType	type				= generateType(rnd, 0, true, isLastMember && (m_features & FEATURE_UNSIZED_ARRAYS));
+	const bool			access				= !unusedOk || (rnd.getFloat() < accessWeight);
+	const bool			read				= access ? (rnd.getFloat() < readWeight) : false;
+	const bool			write				= access ? (!read || (rnd.getFloat() < writeWeight)) : false;
+	const deUint32		flags				= (read ? ACCESS_READ : 0) | (write ? ACCESS_WRITE : 0);
+
+	block.addMember(BufferVar(name.c_str(), type, flags));
+
+	m_bufferVarNdx += 1;
+}
+
+glu::VarType RandomSSBOLayoutCase::generateType (de::Random& rnd, int typeDepth, bool arrayOk, bool unsizedArrayOk)
+{
+	const float structWeight		= 0.1f;
+	const float arrayWeight			= 0.1f;
+	const float	unsizedArrayWeight	= 0.8f;
+
+	DE_ASSERT(arrayOk || !unsizedArrayOk);
+
+	if (unsizedArrayOk && (rnd.getFloat() < unsizedArrayWeight))
+	{
+		const bool			childArrayOk	= (m_features & FEATURE_ARRAYS_OF_ARRAYS) != 0;
+		const glu::VarType	elementType		= generateType(rnd, typeDepth, childArrayOk, false);
+		return glu::VarType(elementType, glu::VarType::UNSIZED_ARRAY);
+	}
+	else if (typeDepth < m_maxStructDepth && rnd.getFloat() < structWeight)
+	{
+		// \todo [2013-10-14 pyry] Implement unused flags for members!
+//		bool					unusedOk			= (m_features & FEATURE_UNUSED_MEMBERS) != 0;
+		vector<glu::VarType>	memberTypes;
+		int						numMembers = rnd.getInt(1, m_maxStructMembers);
+
+		// Generate members first so nested struct declarations are in correct order.
+		for (int ndx = 0; ndx < numMembers; ndx++)
+			memberTypes.push_back(generateType(rnd, typeDepth+1, true, false));
+
+		glu::StructType& structType = m_interface.allocStruct((string("s") + genName('A', 'Z', m_structNdx)).c_str());
+		m_structNdx += 1;
+
+		DE_ASSERT(numMembers <= 'Z' - 'A');
+		for (int ndx = 0; ndx < numMembers; ndx++)
+		{
+			structType.addMember((string("m") + (char)('A' + ndx)).c_str(), memberTypes[ndx]);
+		}
+
+		return glu::VarType(&structType);
+	}
+	else if (m_maxArrayLength > 0 && arrayOk && rnd.getFloat() < arrayWeight)
+	{
+		const int			arrayLength		= rnd.getInt(1, m_maxArrayLength);
+		const bool			childArrayOk	= (m_features & FEATURE_ARRAYS_OF_ARRAYS) != 0;
+		const glu::VarType	elementType		= generateType(rnd, typeDepth, childArrayOk, false);
+
+		return glu::VarType(elementType, arrayLength);
+	}
+	else
+	{
+		vector<glu::DataType> typeCandidates;
+
+		typeCandidates.push_back(glu::TYPE_FLOAT);
+		typeCandidates.push_back(glu::TYPE_INT);
+		typeCandidates.push_back(glu::TYPE_UINT);
+		typeCandidates.push_back(glu::TYPE_BOOL);
+
+		if (m_features & FEATURE_VECTORS)
+		{
+			typeCandidates.push_back(glu::TYPE_FLOAT_VEC2);
+			typeCandidates.push_back(glu::TYPE_FLOAT_VEC3);
+			typeCandidates.push_back(glu::TYPE_FLOAT_VEC4);
+			typeCandidates.push_back(glu::TYPE_INT_VEC2);
+			typeCandidates.push_back(glu::TYPE_INT_VEC3);
+			typeCandidates.push_back(glu::TYPE_INT_VEC4);
+			typeCandidates.push_back(glu::TYPE_UINT_VEC2);
+			typeCandidates.push_back(glu::TYPE_UINT_VEC3);
+			typeCandidates.push_back(glu::TYPE_UINT_VEC4);
+			typeCandidates.push_back(glu::TYPE_BOOL_VEC2);
+			typeCandidates.push_back(glu::TYPE_BOOL_VEC3);
+			typeCandidates.push_back(glu::TYPE_BOOL_VEC4);
+		}
+
+		if (m_features & FEATURE_MATRICES)
+		{
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT2);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT2X3);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT3X2);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT3);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT3X4);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT4X2);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT4X3);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT4);
+		}
+
+		glu::DataType	type		= rnd.choose<glu::DataType>(typeCandidates.begin(), typeCandidates.end());
+		glu::Precision	precision;
+
+		if (!glu::isDataTypeBoolOrBVec(type))
+		{
+			// Precision.
+			static const glu::Precision precisionCandidates[] = { glu::PRECISION_LOWP, glu::PRECISION_MEDIUMP, glu::PRECISION_HIGHP };
+			precision = rnd.choose<glu::Precision>(&precisionCandidates[0], &precisionCandidates[DE_LENGTH_OF_ARRAY(precisionCandidates)]);
+		}
+		else
+			precision = glu::PRECISION_LAST;
+
+		return glu::VarType(type, precision);
+	}
+}
+
+class BlockBasicTypeCase : public SSBOLayoutCase
+{
+public:
+	BlockBasicTypeCase (Context& context, const char* name, const char* description, const VarType& type, deUint32 layoutFlags, int numInstances)
+		: SSBOLayoutCase(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, BUFFERMODE_PER_BLOCK)
+	{
+		BufferBlock& block = m_interface.allocBlock("Block");
+		block.addMember(BufferVar("var", type, ACCESS_READ|ACCESS_WRITE));
+		block.setFlags(layoutFlags);
+
+		if (numInstances > 0)
+		{
+			block.setArraySize(numInstances);
+			block.setInstanceName("block");
+		}
+	}
+};
+
+class BlockBasicUnsizedArrayCase : public SSBOLayoutCase
+{
+public:
+	BlockBasicUnsizedArrayCase (Context& context, const char* name, const char* description, const VarType& elementType, int arraySize, deUint32 layoutFlags)
+		: SSBOLayoutCase(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, BUFFERMODE_PER_BLOCK)
+	{
+		BufferBlock& block = m_interface.allocBlock("Block");
+		block.addMember(BufferVar("var", VarType(elementType, VarType::UNSIZED_ARRAY), ACCESS_READ|ACCESS_WRITE));
+		block.setFlags(layoutFlags);
+
+		block.setLastUnsizedArraySize(0, arraySize);
+	}
+};
+
+static void createRandomCaseGroup (tcu::TestCaseGroup* parentGroup, Context& context, const char* groupName, const char* description, SSBOLayoutCase::BufferMode bufferMode, deUint32 features, int numCases, deUint32 baseSeed)
+{
+	tcu::TestCaseGroup* group = new tcu::TestCaseGroup(context.getTestContext(), groupName, description);
+	parentGroup->addChild(group);
+
+	baseSeed += (deUint32)context.getTestContext().getCommandLine().getBaseSeed();
+
+	for (int ndx = 0; ndx < numCases; ndx++)
+		group->addChild(new RandomSSBOLayoutCase(context, de::toString(ndx).c_str(), "", bufferMode, features, (deUint32)ndx+baseSeed));
+}
+
+class BlockSingleStructCase : public SSBOLayoutCase
+{
+public:
+	BlockSingleStructCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: SSBOLayoutCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_INT_VEC3, glu::PRECISION_HIGHP)); // \todo [pyry] First member is unused.
+		typeS.addMember("b", VarType(VarType(glu::TYPE_FLOAT_MAT3, glu::PRECISION_MEDIUMP), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP));
+
+		BufferBlock& block = m_interface.allocBlock("Block");
+		block.addMember(BufferVar("s", VarType(&typeS), ACCESS_READ|ACCESS_WRITE));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class BlockSingleStructArrayCase : public SSBOLayoutCase
+{
+public:
+	BlockSingleStructArrayCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: SSBOLayoutCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_INT_VEC3, glu::PRECISION_HIGHP)); // \todo [pyry] UNUSED
+		typeS.addMember("b", VarType(VarType(glu::TYPE_FLOAT_MAT3, glu::PRECISION_MEDIUMP), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP));
+
+		BufferBlock& block = m_interface.allocBlock("Block");
+		block.addMember(BufferVar("u", VarType(glu::TYPE_UINT, glu::PRECISION_LOWP), 0 /* no access */));
+		block.addMember(BufferVar("s", VarType(VarType(&typeS), 3), ACCESS_READ|ACCESS_WRITE));
+		block.addMember(BufferVar("v", VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_MEDIUMP), ACCESS_WRITE));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class BlockSingleNestedStructCase : public SSBOLayoutCase
+{
+public:
+	BlockSingleNestedStructCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: SSBOLayoutCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_INT_VEC3, glu::PRECISION_HIGHP));
+		typeS.addMember("b", VarType(VarType(glu::TYPE_FLOAT_MAT3, glu::PRECISION_MEDIUMP), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP)); // \todo [pyry] UNUSED
+
+		StructType& typeT = m_interface.allocStruct("T");
+		typeT.addMember("a", VarType(glu::TYPE_FLOAT_MAT3, glu::PRECISION_MEDIUMP));
+		typeT.addMember("b", VarType(&typeS));
+
+		BufferBlock& block = m_interface.allocBlock("Block");
+		block.addMember(BufferVar("s", VarType(&typeS), ACCESS_READ));
+		block.addMember(BufferVar("v", VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_LOWP), 0 /* no access */));
+		block.addMember(BufferVar("t", VarType(&typeT), ACCESS_READ|ACCESS_WRITE));
+		block.addMember(BufferVar("u", VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP), ACCESS_WRITE));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class BlockSingleNestedStructArrayCase : public SSBOLayoutCase
+{
+public:
+	BlockSingleNestedStructArrayCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: SSBOLayoutCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_INT_VEC3, glu::PRECISION_HIGHP));
+		typeS.addMember("b", VarType(VarType(glu::TYPE_INT_VEC2, glu::PRECISION_MEDIUMP), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP)); // \todo [pyry] UNUSED
+
+		StructType& typeT = m_interface.allocStruct("T");
+		typeT.addMember("a", VarType(glu::TYPE_FLOAT_MAT3, glu::PRECISION_MEDIUMP));
+		typeT.addMember("b", VarType(VarType(&typeS), 3));
+
+		BufferBlock& block = m_interface.allocBlock("Block");
+		block.addMember(BufferVar("s", VarType(&typeS), ACCESS_WRITE));
+		block.addMember(BufferVar("v", VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_LOWP), 0 /* no access */));
+		block.addMember(BufferVar("t", VarType(VarType(&typeT), 2), ACCESS_READ));
+		block.addMember(BufferVar("u", VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP), ACCESS_READ|ACCESS_WRITE));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class BlockUnsizedStructArrayCase : public SSBOLayoutCase
+{
+public:
+	BlockUnsizedStructArrayCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: SSBOLayoutCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_UINT_VEC2, glu::PRECISION_HIGHP)); // \todo [pyry] UNUSED
+		typeS.addMember("b", VarType(VarType(glu::TYPE_FLOAT_MAT2X4, glu::PRECISION_MEDIUMP), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC3, glu::PRECISION_HIGHP));
+
+		BufferBlock& block = m_interface.allocBlock("Block");
+		block.addMember(BufferVar("u", VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_LOWP), 0 /* no access */));
+		block.addMember(BufferVar("v", VarType(glu::TYPE_UINT, glu::PRECISION_MEDIUMP), ACCESS_WRITE));
+		block.addMember(BufferVar("s", VarType(VarType(&typeS), VarType::UNSIZED_ARRAY), ACCESS_READ|ACCESS_WRITE));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+
+		{
+			de::Random rnd(246);
+			for (int ndx = 0; ndx < (m_numInstances ? m_numInstances : 1); ndx++)
+			{
+				const int lastArrayLen = rnd.getInt(1, 5);
+				block.setLastUnsizedArraySize(ndx, lastArrayLen);
+			}
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class Block2LevelUnsizedStructArrayCase : public SSBOLayoutCase
+{
+public:
+	Block2LevelUnsizedStructArrayCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: SSBOLayoutCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_INT_VEC3, glu::PRECISION_HIGHP));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP));
+
+		BufferBlock& block = m_interface.allocBlock("Block");
+		block.addMember(BufferVar("u", VarType(glu::TYPE_UINT, glu::PRECISION_LOWP), 0 /* no access */));
+		block.addMember(BufferVar("v", VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_MEDIUMP), ACCESS_WRITE));
+		block.addMember(BufferVar("s", VarType(VarType(VarType(&typeS), 2), VarType::UNSIZED_ARRAY), ACCESS_READ|ACCESS_WRITE));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+
+		{
+			de::Random rnd(2344);
+			for (int ndx = 0; ndx < (m_numInstances ? m_numInstances : 1); ndx++)
+			{
+				const int lastArrayLen = rnd.getInt(1, 5);
+				block.setLastUnsizedArraySize(ndx, lastArrayLen);
+			}
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class BlockUnsizedNestedStructArrayCase : public SSBOLayoutCase
+{
+public:
+	BlockUnsizedNestedStructArrayCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: SSBOLayoutCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_UINT_VEC3, glu::PRECISION_HIGHP));
+		typeS.addMember("b", VarType(VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_MEDIUMP), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP)); // \todo [pyry] UNUSED
+
+		StructType& typeT = m_interface.allocStruct("T");
+		typeT.addMember("a", VarType(glu::TYPE_FLOAT_MAT4X3, glu::PRECISION_MEDIUMP));
+		typeT.addMember("b", VarType(VarType(&typeS), 3));
+		typeT.addMember("c", VarType(glu::TYPE_INT, glu::PRECISION_HIGHP));
+
+		BufferBlock& block = m_interface.allocBlock("Block");
+		block.addMember(BufferVar("s", VarType(&typeS), ACCESS_WRITE));
+		block.addMember(BufferVar("v", VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_LOWP), 0 /* no access */));
+		block.addMember(BufferVar("u", VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP), ACCESS_READ|ACCESS_WRITE));
+		block.addMember(BufferVar("t", VarType(VarType(&typeT), VarType::UNSIZED_ARRAY), ACCESS_READ));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+
+		{
+			de::Random rnd(7921);
+			for (int ndx = 0; ndx < (m_numInstances ? m_numInstances : 1); ndx++)
+			{
+				const int lastArrayLen = rnd.getInt(1, 5);
+				block.setLastUnsizedArraySize(ndx, lastArrayLen);
+			}
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+class BlockMultiBasicTypesCase : public SSBOLayoutCase
+{
+public:
+	BlockMultiBasicTypesCase (Context& context, const char* name, const char* description, deUint32 flagsA, deUint32 flagsB, BufferMode bufferMode, int numInstances)
+		: SSBOLayoutCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+		, m_flagsA			(flagsA)
+		, m_flagsB			(flagsB)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		BufferBlock& blockA = m_interface.allocBlock("BlockA");
+		blockA.addMember(BufferVar("a", VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), ACCESS_READ|ACCESS_WRITE));
+		blockA.addMember(BufferVar("b", VarType(glu::TYPE_UINT_VEC3, glu::PRECISION_LOWP), 0 /* no access */));
+		blockA.addMember(BufferVar("c", VarType(glu::TYPE_FLOAT_MAT2, glu::PRECISION_MEDIUMP), ACCESS_READ));
+		blockA.setInstanceName("blockA");
+		blockA.setFlags(m_flagsA);
+
+		BufferBlock& blockB = m_interface.allocBlock("BlockB");
+		blockB.addMember(BufferVar("a", VarType(glu::TYPE_FLOAT_MAT3, glu::PRECISION_MEDIUMP), ACCESS_WRITE));
+		blockB.addMember(BufferVar("b", VarType(glu::TYPE_INT_VEC2, glu::PRECISION_LOWP), ACCESS_READ));
+		blockB.addMember(BufferVar("c", VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), 0 /* no access */));
+		blockB.addMember(BufferVar("d", VarType(glu::TYPE_BOOL, glu::PRECISION_LAST), ACCESS_READ|ACCESS_WRITE));
+		blockB.setInstanceName("blockB");
+		blockB.setFlags(m_flagsB);
+
+		if (m_numInstances > 0)
+		{
+			blockA.setArraySize(m_numInstances);
+			blockB.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_flagsA;
+	deUint32	m_flagsB;
+	int			m_numInstances;
+};
+
+class BlockMultiNestedStructCase : public SSBOLayoutCase
+{
+public:
+	BlockMultiNestedStructCase (Context& context, const char* name, const char* description, deUint32 flagsA, deUint32 flagsB, BufferMode bufferMode, int numInstances)
+		: SSBOLayoutCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+		, m_flagsA			(flagsA)
+		, m_flagsB			(flagsB)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_FLOAT_MAT3, glu::PRECISION_LOWP));
+		typeS.addMember("b", VarType(VarType(glu::TYPE_INT_VEC2, glu::PRECISION_MEDIUMP), 4));
+		typeS.addMember("c", VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP));
+
+		StructType& typeT = m_interface.allocStruct("T");
+		typeT.addMember("a", VarType(glu::TYPE_UINT, glu::PRECISION_MEDIUMP)); // \todo [pyry] UNUSED
+		typeT.addMember("b", VarType(&typeS));
+		typeT.addMember("c", VarType(glu::TYPE_BOOL_VEC4, glu::PRECISION_LAST));
+
+		BufferBlock& blockA = m_interface.allocBlock("BlockA");
+		blockA.addMember(BufferVar("a", VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), ACCESS_READ|ACCESS_WRITE));
+		blockA.addMember(BufferVar("b", VarType(&typeS), ACCESS_WRITE));
+		blockA.addMember(BufferVar("c", VarType(glu::TYPE_UINT_VEC3, glu::PRECISION_LOWP), 0 /* no access */));
+		blockA.setInstanceName("blockA");
+		blockA.setFlags(m_flagsA);
+
+		BufferBlock& blockB = m_interface.allocBlock("BlockB");
+		blockB.addMember(BufferVar("a", VarType(glu::TYPE_FLOAT_MAT2, glu::PRECISION_MEDIUMP), ACCESS_WRITE));
+		blockB.addMember(BufferVar("b", VarType(&typeT), ACCESS_READ|ACCESS_WRITE));
+		blockB.addMember(BufferVar("c", VarType(glu::TYPE_BOOL_VEC4, glu::PRECISION_LAST), 0 /* no access */));
+		blockB.addMember(BufferVar("d", VarType(glu::TYPE_BOOL, glu::PRECISION_LAST), ACCESS_READ|ACCESS_WRITE));
+		blockB.setInstanceName("blockB");
+		blockB.setFlags(m_flagsB);
+
+		if (m_numInstances > 0)
+		{
+			blockA.setArraySize(m_numInstances);
+			blockB.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_flagsA;
+	deUint32	m_flagsB;
+	int			m_numInstances;
+};
+
+} // anonymous
+
+SSBOLayoutTests::SSBOLayoutTests (Context& context)
+	: TestCaseGroup(context, "layout", "SSBO Layout Tests")
+{
+}
+
+SSBOLayoutTests::~SSBOLayoutTests (void)
+{
+}
+
+void SSBOLayoutTests::init (void)
+{
+	static const glu::DataType basicTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_FLOAT_VEC2,
+		glu::TYPE_FLOAT_VEC3,
+		glu::TYPE_FLOAT_VEC4,
+		glu::TYPE_INT,
+		glu::TYPE_INT_VEC2,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_INT_VEC4,
+		glu::TYPE_UINT,
+		glu::TYPE_UINT_VEC2,
+		glu::TYPE_UINT_VEC3,
+		glu::TYPE_UINT_VEC4,
+		glu::TYPE_BOOL,
+		glu::TYPE_BOOL_VEC2,
+		glu::TYPE_BOOL_VEC3,
+		glu::TYPE_BOOL_VEC4,
+		glu::TYPE_FLOAT_MAT2,
+		glu::TYPE_FLOAT_MAT3,
+		glu::TYPE_FLOAT_MAT4,
+		glu::TYPE_FLOAT_MAT2X3,
+		glu::TYPE_FLOAT_MAT2X4,
+		glu::TYPE_FLOAT_MAT3X2,
+		glu::TYPE_FLOAT_MAT3X4,
+		glu::TYPE_FLOAT_MAT4X2,
+		glu::TYPE_FLOAT_MAT4X3
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		flags;
+	} layoutFlags[] =
+	{
+		{ "shared",		LAYOUT_SHARED	},
+		{ "packed",		LAYOUT_PACKED	},
+		{ "std140",		LAYOUT_STD140	},
+		{ "std430",		LAYOUT_STD430	}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		flags;
+	} matrixFlags[] =
+	{
+		{ "row_major",		LAYOUT_ROW_MAJOR	},
+		{ "column_major",	LAYOUT_COLUMN_MAJOR }
+	};
+
+	static const struct
+	{
+		const char*							name;
+		SSBOLayoutCase::BufferMode		mode;
+	} bufferModes[] =
+	{
+		{ "per_block_buffer",	SSBOLayoutCase::BUFFERMODE_PER_BLOCK },
+		{ "single_buffer",		SSBOLayoutCase::BUFFERMODE_SINGLE	}
+	};
+
+	// ubo.single_basic_type
+	{
+		tcu::TestCaseGroup* singleBasicTypeGroup = new tcu::TestCaseGroup(m_testCtx, "single_basic_type", "Single basic variable in single buffer");
+		addChild(singleBasicTypeGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			singleBasicTypeGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				glu::DataType	type		= basicTypes[basicTypeNdx];
+				const char*		typeName	= glu::getDataTypeName(type);
+
+				if (glu::isDataTypeBoolOrBVec(type))
+					layoutGroup->addChild(new BlockBasicTypeCase(m_context, typeName, "", VarType(type, glu::PRECISION_LAST), layoutFlags[layoutFlagNdx].flags, 0));
+				else
+				{
+					for (int precNdx = 0; precNdx < glu::PRECISION_LAST; precNdx++)
+					{
+						const glu::Precision	precision	= glu::Precision(precNdx);
+						const string			caseName	= string(glu::getPrecisionName(precision)) + "_" + typeName;
+
+						layoutGroup->addChild(new BlockBasicTypeCase(m_context, caseName.c_str(), "", VarType(type, precision), layoutFlags[layoutFlagNdx].flags, 0));
+					}
+				}
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+					{
+						for (int precNdx = 0; precNdx < glu::PRECISION_LAST; precNdx++)
+						{
+							const glu::Precision	precision	= glu::Precision(precNdx);
+							const string			caseName	= string(matrixFlags[matFlagNdx].name) + "_" + string(glu::getPrecisionName(precision)) + "_" + typeName;
+
+							layoutGroup->addChild(new BlockBasicTypeCase(m_context, caseName.c_str(), "", glu::VarType(type, precision), layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags, 0));
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// ubo.single_basic_array
+	{
+		tcu::TestCaseGroup* singleBasicArrayGroup = new tcu::TestCaseGroup(m_testCtx, "single_basic_array", "Single basic array variable in single buffer");
+		addChild(singleBasicArrayGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			singleBasicArrayGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				glu::DataType	type		= basicTypes[basicTypeNdx];
+				const char*		typeName	= glu::getDataTypeName(type);
+				const int		arraySize	= 3;
+
+				layoutGroup->addChild(new BlockBasicTypeCase(m_context, typeName, "",
+															 VarType(VarType(type, glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_HIGHP), arraySize),
+															 layoutFlags[layoutFlagNdx].flags, 0));
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+						layoutGroup->addChild(new BlockBasicTypeCase(m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(), "",
+																	 VarType(VarType(type, glu::PRECISION_HIGHP), arraySize),
+																	 layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags, 0));
+				}
+			}
+		}
+	}
+
+	// ubo.basic_unsized_array
+	{
+		tcu::TestCaseGroup* basicUnsizedArray = new tcu::TestCaseGroup(m_testCtx, "basic_unsized_array", "Basic unsized array tests");
+		addChild(basicUnsizedArray);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			basicUnsizedArray->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				glu::DataType	type		= basicTypes[basicTypeNdx];
+				const char*		typeName	= glu::getDataTypeName(type);
+				const int		arraySize	= 19;
+
+				layoutGroup->addChild(new BlockBasicUnsizedArrayCase(m_context, typeName, "",
+																	 VarType(type, glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_HIGHP),
+																	 arraySize, layoutFlags[layoutFlagNdx].flags));
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+						layoutGroup->addChild(new BlockBasicUnsizedArrayCase(m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(), "",
+																			 VarType(type, glu::PRECISION_HIGHP), arraySize,
+																			 layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags));
+				}
+			}
+		}
+	}
+
+	// ubo.2_level_array
+	{
+		tcu::TestCaseGroup* nestedArrayGroup = new tcu::TestCaseGroup(m_testCtx, "2_level_array", "2-level nested array");
+		addChild(nestedArrayGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			nestedArrayGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				glu::DataType	type		= basicTypes[basicTypeNdx];
+				const char*		typeName	= glu::getDataTypeName(type);
+				const int		childSize	= 3;
+				const int		parentSize	= 4;
+				const VarType	childType	(VarType(type, glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_HIGHP), childSize);
+				const VarType	fullType	(childType, parentSize);
+
+				layoutGroup->addChild(new BlockBasicTypeCase(m_context, typeName, "", fullType, layoutFlags[layoutFlagNdx].flags, 0));
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+						layoutGroup->addChild(new BlockBasicTypeCase(m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(), "",
+																	 fullType,
+																	 layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags, 0));
+				}
+			}
+		}
+	}
+
+	// ubo.3_level_array
+	{
+		tcu::TestCaseGroup* nestedArrayGroup = new tcu::TestCaseGroup(m_testCtx, "3_level_array", "3-level nested array");
+		addChild(nestedArrayGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			nestedArrayGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				glu::DataType	type		= basicTypes[basicTypeNdx];
+				const char*		typeName	= glu::getDataTypeName(type);
+				const int		childSize0	= 3;
+				const int		childSize1	= 2;
+				const int		parentSize	= 4;
+				const VarType	childType0	(VarType(type, glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_HIGHP), childSize0);
+				const VarType	childType1	(childType0, childSize1);
+				const VarType	fullType	(childType1, parentSize);
+
+				layoutGroup->addChild(new BlockBasicTypeCase(m_context, typeName, "", fullType, layoutFlags[layoutFlagNdx].flags, 0));
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+						layoutGroup->addChild(new BlockBasicTypeCase(m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(), "",
+																	 fullType,
+																	 layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags, 0));
+				}
+			}
+		}
+	}
+
+	// ubo.3_level_unsized_array
+	{
+		tcu::TestCaseGroup* nestedArrayGroup = new tcu::TestCaseGroup(m_testCtx, "3_level_unsized_array", "3-level nested array, top-level array unsized");
+		addChild(nestedArrayGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			nestedArrayGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				glu::DataType	type		= basicTypes[basicTypeNdx];
+				const char*		typeName	= glu::getDataTypeName(type);
+				const int		childSize0	= 2;
+				const int		childSize1	= 4;
+				const int		parentSize	= 3;
+				const VarType	childType0	(VarType(type, glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_HIGHP), childSize0);
+				const VarType	childType1	(childType0, childSize1);
+
+				layoutGroup->addChild(new BlockBasicUnsizedArrayCase(m_context, typeName, "", childType1, parentSize, layoutFlags[layoutFlagNdx].flags));
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+						layoutGroup->addChild(new BlockBasicUnsizedArrayCase(m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(), "",
+																			 childType1, parentSize,
+																			 layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags));
+				}
+			}
+		}
+	}
+
+	// ubo.single_struct
+	{
+		tcu::TestCaseGroup* singleStructGroup = new tcu::TestCaseGroup(m_testCtx, "single_struct", "Single struct in uniform block");
+		addChild(singleStructGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			singleStructGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					const deUint32	caseFlags	= layoutFlags[layoutFlagNdx].flags;
+					string			caseName	= layoutFlags[layoutFlagNdx].name;
+
+					if (bufferModes[modeNdx].mode == SSBOLayoutCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						caseName += "_instance_array";
+
+					modeGroup->addChild(new BlockSingleStructCase(m_context, caseName.c_str(), "", caseFlags, bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.single_struct_array
+	{
+		tcu::TestCaseGroup* singleStructArrayGroup = new tcu::TestCaseGroup(m_testCtx, "single_struct_array", "Struct array in one uniform block");
+		addChild(singleStructArrayGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			singleStructArrayGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == SSBOLayoutCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockSingleStructArrayCase(m_context, baseName.c_str(),	"", baseFlags, bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.single_nested_struct
+	{
+		tcu::TestCaseGroup* singleNestedStructGroup = new tcu::TestCaseGroup(m_testCtx, "single_nested_struct", "Nested struct in one uniform block");
+		addChild(singleNestedStructGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			singleNestedStructGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == SSBOLayoutCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockSingleNestedStructCase(m_context, baseName.c_str(), "", baseFlags, bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.single_nested_struct_array
+	{
+		tcu::TestCaseGroup* singleNestedStructArrayGroup = new tcu::TestCaseGroup(m_testCtx, "single_nested_struct_array", "Nested struct array in one uniform block");
+		addChild(singleNestedStructArrayGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			singleNestedStructArrayGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == SSBOLayoutCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockSingleNestedStructArrayCase(m_context, baseName.c_str(), "", baseFlags, bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.unsized_struct_array
+	{
+		tcu::TestCaseGroup* singleStructArrayGroup = new tcu::TestCaseGroup(m_testCtx, "unsized_struct_array", "Unsized struct array in one uniform block");
+		addChild(singleStructArrayGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			singleStructArrayGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == SSBOLayoutCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockUnsizedStructArrayCase(m_context, baseName.c_str(),	"", baseFlags, bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.2_level_unsized_struct_array
+	{
+		tcu::TestCaseGroup* structArrayGroup = new tcu::TestCaseGroup(m_testCtx, "2_level_unsized_struct_array", "Unsized 2-level struct array in one uniform block");
+		addChild(structArrayGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			structArrayGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == SSBOLayoutCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new Block2LevelUnsizedStructArrayCase(m_context, baseName.c_str(),	"", baseFlags, bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.unsized_nested_struct_array
+	{
+		tcu::TestCaseGroup* singleNestedStructArrayGroup = new tcu::TestCaseGroup(m_testCtx, "unsized_nested_struct_array", "Unsized, nested struct array in one uniform block");
+		addChild(singleNestedStructArrayGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			singleNestedStructArrayGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == SSBOLayoutCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockUnsizedNestedStructArrayCase(m_context, baseName.c_str(), "", baseFlags, bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.instance_array_basic_type
+	{
+		tcu::TestCaseGroup* instanceArrayBasicTypeGroup = new tcu::TestCaseGroup(m_testCtx, "instance_array_basic_type", "Single basic variable in instance array");
+		addChild(instanceArrayBasicTypeGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			instanceArrayBasicTypeGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				glu::DataType	type			= basicTypes[basicTypeNdx];
+				const char*		typeName		= glu::getDataTypeName(type);
+				const int		numInstances	= 3;
+
+				layoutGroup->addChild(new BlockBasicTypeCase(m_context, typeName, "",
+															 VarType(type, glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_HIGHP),
+															 layoutFlags[layoutFlagNdx].flags, numInstances));
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+						layoutGroup->addChild(new BlockBasicTypeCase(m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(), "",
+																	 VarType(type, glu::PRECISION_HIGHP), layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags,
+																	 numInstances));
+				}
+			}
+		}
+	}
+
+	// ubo.multi_basic_types
+	{
+		tcu::TestCaseGroup* multiBasicTypesGroup = new tcu::TestCaseGroup(m_testCtx, "multi_basic_types", "Multiple buffers with basic types");
+		addChild(multiBasicTypesGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			multiBasicTypesGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockMultiBasicTypesCase(m_context, baseName.c_str(), "", baseFlags, baseFlags, bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.multi_nested_struct
+	{
+		tcu::TestCaseGroup* multiNestedStructGroup = new tcu::TestCaseGroup(m_testCtx, "multi_nested_struct", "Multiple buffers with nested structs");
+		addChild(multiNestedStructGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			multiNestedStructGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new BlockMultiNestedStructCase(m_context, baseName.c_str(), "", baseFlags, baseFlags, bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.random
+	{
+		const deUint32	allLayouts		= FEATURE_PACKED_LAYOUT|FEATURE_SHARED_LAYOUT|FEATURE_STD140_LAYOUT;
+		const deUint32	allBasicTypes	= FEATURE_VECTORS|FEATURE_MATRICES;
+		const deUint32	unused			= FEATURE_UNUSED_MEMBERS|FEATURE_UNUSED_VARS;
+		const deUint32	unsized			= FEATURE_UNSIZED_ARRAYS;
+		const deUint32	matFlags		= FEATURE_MATRIX_LAYOUT;
+
+		tcu::TestCaseGroup* randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Random Uniform Block cases");
+		addChild(randomGroup);
+
+		// Basic types.
+		createRandomCaseGroup(randomGroup, m_context, "scalar_types",		"Scalar types only, per-block buffers",				SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused,																			25, 0);
+		createRandomCaseGroup(randomGroup, m_context, "vector_types",		"Scalar and vector types only, per-block buffers",	SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused|FEATURE_VECTORS,															25, 25);
+		createRandomCaseGroup(randomGroup, m_context, "basic_types",		"All basic types, per-block buffers",				SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused|allBasicTypes|matFlags,													25, 50);
+		createRandomCaseGroup(randomGroup, m_context, "basic_arrays",		"Arrays, per-block buffers",						SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused|allBasicTypes|matFlags|FEATURE_ARRAYS,									25, 50);
+		createRandomCaseGroup(randomGroup, m_context, "unsized_arrays",		"Unsized arrays, per-block buffers",				SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused|allBasicTypes|matFlags|unsized|FEATURE_ARRAYS,							25, 50);
+		createRandomCaseGroup(randomGroup, m_context, "arrays_of_arrays",	"Arrays of arrays, per-block buffers",				SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused|allBasicTypes|matFlags|unsized|FEATURE_ARRAYS|FEATURE_ARRAYS_OF_ARRAYS,	25, 950);
+
+		createRandomCaseGroup(randomGroup, m_context, "basic_instance_arrays",					"Basic instance arrays, per-block buffers",				SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused|allBasicTypes|matFlags|unsized|FEATURE_INSTANCE_ARRAYS,															25, 75);
+		createRandomCaseGroup(randomGroup, m_context, "nested_structs",							"Nested structs, per-block buffers",					SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused|allBasicTypes|matFlags|unsized|FEATURE_STRUCTS,																	25, 100);
+		createRandomCaseGroup(randomGroup, m_context, "nested_structs_arrays",					"Nested structs, arrays, per-block buffers",			SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused|allBasicTypes|matFlags|unsized|FEATURE_STRUCTS|FEATURE_ARRAYS|FEATURE_ARRAYS_OF_ARRAYS,							25, 150);
+		createRandomCaseGroup(randomGroup, m_context, "nested_structs_instance_arrays",			"Nested structs, instance arrays, per-block buffers",	SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused|allBasicTypes|matFlags|unsized|FEATURE_STRUCTS|FEATURE_INSTANCE_ARRAYS,											25, 125);
+		createRandomCaseGroup(randomGroup, m_context, "nested_structs_arrays_instance_arrays",	"Nested structs, instance arrays, per-block buffers",	SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	allLayouts|unused|allBasicTypes|matFlags|unsized|FEATURE_STRUCTS|FEATURE_ARRAYS|FEATURE_ARRAYS_OF_ARRAYS|FEATURE_INSTANCE_ARRAYS,	25, 175);
+
+		createRandomCaseGroup(randomGroup, m_context, "all_per_block_buffers",	"All random features, per-block buffers",	SSBOLayoutCase::BUFFERMODE_PER_BLOCK,	~0u,	50, 200);
+		createRandomCaseGroup(randomGroup, m_context, "all_shared_buffer",		"All random features, shared buffer",		SSBOLayoutCase::BUFFERMODE_SINGLE,		~0u,	50, 250);
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fSSBOLayoutTests.hpp b/modules/gles31/functional/es31fSSBOLayoutTests.hpp
new file mode 100644
index 0000000..39648f8
--- /dev/null
+++ b/modules/gles31/functional/es31fSSBOLayoutTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSSBOLAYOUTTESTS_HPP
+#define _ES31FSSBOLAYOUTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief SSBO layout tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class SSBOLayoutTests : public TestCaseGroup
+{
+public:
+							SSBOLayoutTests		(Context& context);
+							~SSBOLayoutTests	(void);
+
+	void					init				(void);
+
+private:
+							SSBOLayoutTests		(const SSBOLayoutTests& other);
+	SSBOLayoutTests&		operator=			(const SSBOLayoutTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSSBOLAYOUTTESTS_HPP
diff --git a/modules/gles31/functional/es31fSampleShadingTests.cpp b/modules/gles31/functional/es31fSampleShadingTests.cpp
new file mode 100644
index 0000000..6daa5b9
--- /dev/null
+++ b/modules/gles31/functional/es31fSampleShadingTests.cpp
@@ -0,0 +1,711 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sample shading tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fSampleShadingTests.hpp"
+#include "es31fMultisampleShaderRenderCase.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluContextInfo.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "gluPixelTransfer.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+
+#include <map>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+class SampleShadingStateCase : public TestCase
+{
+public:
+	enum VerifierType
+	{
+		TYPE_IS_ENABLED = 0,
+		TYPE_GET_BOOLEAN,
+		TYPE_GET_INTEGER,
+		TYPE_GET_FLOAT,
+		TYPE_GET_INTEGER64,
+		TYPE_LAST
+	};
+
+						SampleShadingStateCase	(Context& ctx, const char* name, const char* desc, VerifierType);
+
+	void				init					(void);
+	IterateResult		iterate					(void);
+
+private:
+	bool				verify					(bool v);
+
+	const VerifierType	m_verifier;
+};
+
+SampleShadingStateCase::SampleShadingStateCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+	: TestCase		(ctx, name, desc)
+	, m_verifier	(type)
+{
+	DE_ASSERT(m_verifier < TYPE_LAST);
+}
+
+void SampleShadingStateCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_sample_shading"))
+		throw tcu::NotSupportedError("Test requires GL_OES_sample_shading extension");
+}
+
+SampleShadingStateCase::IterateResult SampleShadingStateCase::iterate (void)
+{
+	bool				allOk	= true;
+	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	gl.enableLogging(true);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// initial
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying initial value" << tcu::TestLog::EndMessage;
+		allOk &= verify(false);
+	}
+
+	// true and false too
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying random values" << tcu::TestLog::EndMessage;
+
+		gl.glEnable(GL_SAMPLE_SHADING);
+		allOk &= verify(true);
+
+		gl.glDisable(GL_SAMPLE_SHADING);
+		allOk &= verify(false);
+	}
+
+	if (!allOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected value");
+
+	return STOP;
+}
+
+bool SampleShadingStateCase::verify (bool v)
+{
+	glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	gl.enableLogging(true);
+
+	switch (m_verifier)
+	{
+		case TYPE_IS_ENABLED:
+		{
+			const glw::GLboolean retVal = gl.glIsEnabled(GL_SAMPLE_SHADING);
+
+			if ((v && retVal==GL_TRUE) || (!v && retVal==GL_FALSE))
+				return true;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << ((v) ? ("GL_TRUE") : ("GL_FALSE")) << ", got " << ((retVal == GL_TRUE) ? ("GL_TRUE") : (retVal == GL_FALSE) ? ("GL_FALSE") : ("not-a-boolean")) << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		case TYPE_GET_BOOLEAN:
+		{
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLboolean> state;
+			gl.glGetBooleanv(GL_SAMPLE_SHADING, &state);
+
+			if (!state.verifyValidity(m_testCtx))
+				return false;
+
+			if ((v && state==GL_TRUE) || (!v && state==GL_FALSE))
+				return true;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << ((v) ? ("GL_TRUE") : ("GL_FALSE")) << ", got " << ((state == GL_TRUE) ? ("GL_TRUE") : (state == GL_FALSE) ? ("GL_FALSE") : ("not-a-boolean")) << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		case TYPE_GET_INTEGER:
+		{
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint> state;
+			gl.glGetIntegerv(GL_SAMPLE_SHADING, &state);
+
+			if (!state.verifyValidity(m_testCtx))
+				return false;
+
+			if ((v && state==1) || (!v && state==0))
+				return true;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << ((v) ? ("1") : ("0")) << ", got " << state << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		case TYPE_GET_FLOAT:
+		{
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLfloat> state;
+			gl.glGetFloatv(GL_SAMPLE_SHADING, &state);
+
+			if (!state.verifyValidity(m_testCtx))
+				return false;
+
+			if ((v && state==1.0f) || (!v && state==0.0f))
+				return true;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << ((v) ? ("1.0") : ("0.0")) << ", got " << state << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		case TYPE_GET_INTEGER64:
+		{
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint64> state;
+			gl.glGetInteger64v(GL_SAMPLE_SHADING, &state);
+
+			if (!state.verifyValidity(m_testCtx))
+				return false;
+
+			if ((v && state==1) || (!v && state==0))
+				return true;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << ((v) ? ("1") : ("0")) << ", got " << state << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		default:
+		{
+			DE_ASSERT(false);
+			return false;
+		}
+	}
+}
+
+class MinSampleShadingValueCase : public TestCase
+{
+public:
+	enum VerifierType
+	{
+		TYPE_GET_BOOLEAN = 0,
+		TYPE_GET_INTEGER,
+		TYPE_GET_FLOAT,
+		TYPE_GET_INTEGER64,
+		TYPE_LAST
+	};
+
+						MinSampleShadingValueCase	(Context& ctx, const char* name, const char* desc, VerifierType);
+
+	void				init						(void);
+	IterateResult		iterate						(void);
+
+private:
+	bool				verify						(float v);
+
+	const VerifierType	m_verifier;
+};
+
+MinSampleShadingValueCase::MinSampleShadingValueCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+	: TestCase		(ctx, name, desc)
+	, m_verifier	(type)
+{
+	DE_ASSERT(m_verifier < TYPE_LAST);
+}
+
+void MinSampleShadingValueCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_sample_shading"))
+		throw tcu::NotSupportedError("Test requires GL_OES_sample_shading extension");
+}
+
+MinSampleShadingValueCase::IterateResult MinSampleShadingValueCase::iterate (void)
+{
+	bool				allOk	= true;
+	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	gl.enableLogging(true);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// initial
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying initial value" << tcu::TestLog::EndMessage;
+		allOk &= verify(0.0);
+	}
+
+	// special values
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying special values" << tcu::TestLog::EndMessage;
+
+		gl.glMinSampleShading(0.0f);
+		allOk &= verify(0.0);
+
+		gl.glMinSampleShading(1.0f);
+		allOk &= verify(1.0);
+
+		gl.glMinSampleShading(0.5f);
+		allOk &= verify(0.5);
+	}
+
+	// random values
+	{
+		const int	numRandomTests	= 10;
+		de::Random	rnd				(0xde123);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying random values" << tcu::TestLog::EndMessage;
+
+		for (int randNdx = 0; randNdx < numRandomTests; ++randNdx)
+		{
+			const float value = rnd.getFloat();
+
+			gl.glMinSampleShading(value);
+			allOk &= verify(value);
+		}
+	}
+
+	if (!allOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected value");
+
+	return STOP;
+}
+
+bool MinSampleShadingValueCase::verify (float v)
+{
+	glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	gl.enableLogging(true);
+
+	switch (m_verifier)
+	{
+		case TYPE_GET_BOOLEAN:
+		{
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLboolean> state;
+			gl.glGetBooleanv(GL_MIN_SAMPLE_SHADING_VALUE, &state);
+
+			if (!state.verifyValidity(m_testCtx))
+				return false;
+
+			if ((v!=0.0f && state==GL_TRUE) || (v==0.0f && state==GL_FALSE))
+				return true;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << ((v!=0.0f) ? ("GL_TRUE") : ("GL_FALSE")) << ", got " << ((state == GL_TRUE) ? ("GL_TRUE") : (state == GL_FALSE) ? ("GL_FALSE") : ("not-a-boolean")) << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		case TYPE_GET_INTEGER:
+		{
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint> state;
+			gl.glGetIntegerv(GL_MIN_SAMPLE_SHADING_VALUE, &state);
+
+			if (!state.verifyValidity(m_testCtx))
+				return false;
+
+			if ((v>=0.5f && state==1) || (v<=0.5f && state==0))
+				return true;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << ((v==0.5) ? ("0 or 1") : (v<0.5) ? ("0") : ("1")) << ", got " << state << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		case TYPE_GET_FLOAT:
+		{
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLfloat> state;
+			gl.glGetFloatv(GL_MIN_SAMPLE_SHADING_VALUE, &state);
+
+			if (!state.verifyValidity(m_testCtx))
+				return false;
+
+			if (v == state)
+				return true;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << v << ", got " << state << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		case TYPE_GET_INTEGER64:
+		{
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint64> state;
+			gl.glGetInteger64v(GL_MIN_SAMPLE_SHADING_VALUE, &state);
+
+			if (!state.verifyValidity(m_testCtx))
+				return false;
+
+			if ((v>=0.5f && state==1) || (v<=0.5f && state==0))
+				return true;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << ((v==0.5) ? ("0 or 1") : (v<0.5) ? ("0") : ("1")) << ", got " << state << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		default:
+		{
+			DE_ASSERT(false);
+			return false;
+		}
+	}
+}
+
+class MinSampleShadingValueClampingCase : public TestCase
+{
+public:
+						MinSampleShadingValueClampingCase	(Context& ctx, const char* name, const char* desc);
+
+	void				init								(void);
+	IterateResult		iterate								(void);
+
+private:
+	bool				verify								(float v);
+};
+
+MinSampleShadingValueClampingCase::MinSampleShadingValueClampingCase (Context& ctx, const char* name, const char* desc)
+	: TestCase(ctx, name, desc)
+{
+}
+
+void MinSampleShadingValueClampingCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_sample_shading"))
+		throw tcu::NotSupportedError("Test requires GL_OES_sample_shading extension");
+}
+
+MinSampleShadingValueClampingCase::IterateResult MinSampleShadingValueClampingCase::iterate (void)
+{
+	bool				allOk	= true;
+	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	gl.enableLogging(true);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// special values
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying clamped values. Value is clamped when specified." << tcu::TestLog::EndMessage;
+
+		gl.glMinSampleShading(-0.5f);
+		allOk &= verify(0.0);
+
+		gl.glMinSampleShading(-1.0f);
+		allOk &= verify(0.0);
+
+		gl.glMinSampleShading(-1.5f);
+		allOk &= verify(0.0);
+
+		gl.glMinSampleShading(1.5f);
+		allOk &= verify(1.0);
+
+		gl.glMinSampleShading(2.0f);
+		allOk &= verify(1.0);
+
+		gl.glMinSampleShading(2.5f);
+		allOk &= verify(1.0);
+	}
+
+	if (!allOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected value");
+
+	return STOP;
+}
+
+bool MinSampleShadingValueClampingCase::verify (float v)
+{
+	glu::CallLogWrapper												gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLfloat>	state;
+
+	gl.enableLogging(true);
+
+	gl.glGetFloatv(GL_MIN_SAMPLE_SHADING_VALUE, &state);
+
+	if (!state.verifyValidity(m_testCtx))
+		return false;
+
+	if (v == state)
+		return true;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << v << ", got " << state << tcu::TestLog::EndMessage;
+	return false;
+}
+
+class SampleShadingRenderingCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
+{
+public:
+	enum TestType
+	{
+		TEST_DISCARD = 0,
+		TEST_COLOR,
+
+		TEST_LAST
+	};
+						SampleShadingRenderingCase	(Context& ctx, const char* name, const char* desc, RenderTarget target, int numSamples, TestType type);
+						~SampleShadingRenderingCase	(void);
+
+	void				init						(void);
+private:
+	void				setShadingValue				(int sampleCount);
+
+	void				preDraw						(void);
+	void				postDraw					(void);
+	std::string			getIterationDescription		(int iteration) const;
+
+	bool				verifyImage					(const tcu::Surface& resultImage);
+
+	std::string			genFragmentSource			(int numSamples) const;
+
+	enum
+	{
+		RENDER_SIZE = 128
+	};
+
+	const TestType		m_type;
+};
+
+SampleShadingRenderingCase::SampleShadingRenderingCase (Context& ctx, const char* name, const char* desc, RenderTarget target, int numSamples, TestType type)
+	: MultisampleShaderRenderUtil::MultisampleRenderCase	(ctx, name, desc, numSamples, target, RENDER_SIZE)
+	, m_type												(type)
+{
+	DE_ASSERT(type < TEST_LAST);
+}
+
+SampleShadingRenderingCase::~SampleShadingRenderingCase (void)
+{
+	deinit();
+}
+
+void SampleShadingRenderingCase::init (void)
+{
+	// requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_sample_shading"))
+		throw tcu::NotSupportedError("Test requires GL_OES_sample_shading extension");
+	if (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() <= 1)
+		throw tcu::NotSupportedError("Multisampled default framebuffer required");
+
+	// test purpose and expectations
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying that a varying is given at least N different values for different samples within a single pixel.\n"
+		<< "	Render high-frequency function, map result to black/white. Modify N with glMinSampleShading().\n"
+		<< "	=> Resulting image should contain N+1 shades of gray.\n"
+		<< tcu::TestLog::EndMessage;
+
+	// setup resources
+
+	MultisampleShaderRenderUtil::MultisampleRenderCase::init();
+
+	// set iterations
+
+	m_numIterations = m_numTargetSamples + 1;
+}
+
+void SampleShadingRenderingCase::setShadingValue (int sampleCount)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (sampleCount == 0)
+	{
+		gl.disable(GL_SAMPLE_SHADING);
+		gl.minSampleShading(1.0f);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set ratio");
+	}
+	else
+	{
+		// Minimum number of samples is max(ceil(<mss> * <samples>),1). Decrease mss with epsilon to prevent
+		// ceiling to a too large sample count.
+		const float epsilon	= 0.25f / (float)m_numTargetSamples;
+		const float ratio	= (sampleCount / (float)m_numTargetSamples) - epsilon;
+
+		gl.enable(GL_SAMPLE_SHADING);
+		gl.minSampleShading(ratio);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set ratio");
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Setting MIN_SAMPLE_SHADING_VALUE = " << ratio << "\n"
+			<< "Requested sample count: shadingValue * numSamples = " << ratio << " * " << m_numTargetSamples << " = " << (ratio * m_numTargetSamples) << "\n"
+			<< "Minimum sample count: ceil(shadingValue * numSamples) = ceil(" << (ratio * m_numTargetSamples) << ") = " << sampleCount
+			<< tcu::TestLog::EndMessage;
+
+		// can't fail with reasonable values of numSamples
+		DE_ASSERT(deFloatCeil(ratio * m_numTargetSamples) == float(sampleCount));
+	}
+}
+
+void SampleShadingRenderingCase::preDraw (void)
+{
+	setShadingValue(m_iteration);
+}
+
+void SampleShadingRenderingCase::postDraw (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	gl.disable(GL_SAMPLE_SHADING);
+	gl.minSampleShading(1.0f);
+}
+
+std::string	SampleShadingRenderingCase::getIterationDescription (int iteration) const
+{
+	if (iteration == 0)
+		return "Disabled SAMPLE_SHADING";
+	else
+		return "Samples per pixel: " + de::toString(iteration);
+}
+
+bool SampleShadingRenderingCase::verifyImage (const tcu::Surface& resultImage)
+{
+	const int				numShadesRequired	= (m_iteration == 0) ? (2) : (m_iteration + 1);
+	const int				rareThreshold		= 100;
+	int						rareCount			= 0;
+	std::map<deUint32, int>	shadeFrequency;
+
+	// we should now have n+1 different shades of white, n = num samples
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Image("ResultImage", "Result Image", resultImage.getAccess())
+		<< tcu::TestLog::Message
+		<< "Verifying image has (at least) " << numShadesRequired << " different shades.\n"
+		<< "Excluding pixels with no full coverage (pixels on the shared edge of the triangle pair)."
+		<< tcu::TestLog::EndMessage;
+
+	for (int y = 0; y < RENDER_SIZE; ++y)
+	for (int x = 0; x < RENDER_SIZE; ++x)
+	{
+		const tcu::RGBA	color	= resultImage.getPixel(x, y);
+		const deUint32	packed	= ((deUint32)color.getRed()) + ((deUint32)color.getGreen() << 8) + ((deUint32)color.getGreen() << 16);
+
+		// on the triangle edge, skip
+		if (x == y)
+			continue;
+
+		if (shadeFrequency.find(packed) == shadeFrequency.end())
+			shadeFrequency[packed] = 1;
+		else
+			shadeFrequency[packed] = shadeFrequency[packed] + 1;
+	}
+
+	for (std::map<deUint32, int>::const_iterator it = shadeFrequency.begin(); it != shadeFrequency.end(); ++it)
+		if (it->second < rareThreshold)
+			rareCount++;
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Found " << (int)shadeFrequency.size() << " different shades.\n"
+		<< "\tRare (less than " << rareThreshold << " pixels): " << rareCount << "\n"
+		<< "\tCommon: " << (int)shadeFrequency.size() - rareCount << "\n"
+		<< tcu::TestLog::EndMessage;
+
+	if ((int)shadeFrequency.size() < numShadesRequired)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage;
+		return false;
+	}
+	return true;
+}
+
+std::string SampleShadingRenderingCase::genFragmentSource (int numSamples) const
+{
+	DE_UNREF(numSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"in highp vec4 v_position;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	highp float field = dot(v_position.xy, v_position.xy) + dot(21.0 * v_position.xx, sin(3.1 * v_position.xy));\n"
+			"	fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+			"\n"
+			"	if (fract(field) > 0.5)\n";
+
+	if (m_type == TEST_DISCARD)
+		buf <<	"		discard;\n";
+	else if (m_type == TEST_COLOR)
+		buf <<	"		fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"}";
+
+	return buf.str();
+}
+
+} // anonymous
+
+SampleShadingTests::SampleShadingTests (Context& context)
+	: TestCaseGroup(context, "sample_shading", "Test sample shading")
+{
+}
+
+SampleShadingTests::~SampleShadingTests (void)
+{
+}
+
+void SampleShadingTests::init (void)
+{
+	tcu::TestCaseGroup* const stateQueryGroup = new tcu::TestCaseGroup(m_testCtx, "state_query", "State query tests.");
+	tcu::TestCaseGroup* const minSamplesGroup = new tcu::TestCaseGroup(m_testCtx, "min_sample_shading", "Min sample shading tests.");
+
+	addChild(stateQueryGroup);
+	addChild(minSamplesGroup);
+
+	// .state query
+	{
+		stateQueryGroup->addChild(new SampleShadingStateCase			(m_context, "sample_shading_is_enabled",				"test SAMPLE_SHADING",						SampleShadingStateCase::TYPE_IS_ENABLED));
+		stateQueryGroup->addChild(new SampleShadingStateCase			(m_context, "sample_shading_get_boolean",				"test SAMPLE_SHADING",						SampleShadingStateCase::TYPE_GET_BOOLEAN));
+		stateQueryGroup->addChild(new SampleShadingStateCase			(m_context, "sample_shading_get_integer",				"test SAMPLE_SHADING",						SampleShadingStateCase::TYPE_GET_INTEGER));
+		stateQueryGroup->addChild(new SampleShadingStateCase			(m_context, "sample_shading_get_float",					"test SAMPLE_SHADING",						SampleShadingStateCase::TYPE_GET_FLOAT));
+		stateQueryGroup->addChild(new SampleShadingStateCase			(m_context, "sample_shading_get_integer64",				"test SAMPLE_SHADING",						SampleShadingStateCase::TYPE_GET_INTEGER64));
+		stateQueryGroup->addChild(new MinSampleShadingValueCase			(m_context, "min_sample_shading_value_get_boolean",		"test MIN_SAMPLE_SHADING_VALUE",			MinSampleShadingValueCase::TYPE_GET_BOOLEAN));
+		stateQueryGroup->addChild(new MinSampleShadingValueCase			(m_context, "min_sample_shading_value_get_integer",		"test MIN_SAMPLE_SHADING_VALUE",			MinSampleShadingValueCase::TYPE_GET_INTEGER));
+		stateQueryGroup->addChild(new MinSampleShadingValueCase			(m_context, "min_sample_shading_value_get_float",		"test MIN_SAMPLE_SHADING_VALUE",			MinSampleShadingValueCase::TYPE_GET_FLOAT));
+		stateQueryGroup->addChild(new MinSampleShadingValueCase			(m_context, "min_sample_shading_value_get_integer64",	"test MIN_SAMPLE_SHADING_VALUE",			MinSampleShadingValueCase::TYPE_GET_INTEGER64));
+		stateQueryGroup->addChild(new MinSampleShadingValueClampingCase	(m_context, "min_sample_shading_value_clamping",		"test MIN_SAMPLE_SHADING_VALUE clamping"));
+	}
+
+	// .min_sample_count
+	{
+		static const struct Target
+		{
+			SampleShadingRenderingCase::RenderTarget	target;
+			int											numSamples;
+			const char*									name;
+		} targets[] =
+		{
+			{ SampleShadingRenderingCase::TARGET_DEFAULT,			0,	"default_framebuffer"					},
+			{ SampleShadingRenderingCase::TARGET_TEXTURE,			2,	"multisample_texture_samples_2"			},
+			{ SampleShadingRenderingCase::TARGET_TEXTURE,			4,	"multisample_texture_samples_4"			},
+			{ SampleShadingRenderingCase::TARGET_TEXTURE,			8,	"multisample_texture_samples_8"			},
+			{ SampleShadingRenderingCase::TARGET_TEXTURE,			16,	"multisample_texture_samples_16"		},
+			{ SampleShadingRenderingCase::TARGET_RENDERBUFFER,		2,	"multisample_renderbuffer_samples_2"	},
+			{ SampleShadingRenderingCase::TARGET_RENDERBUFFER,		4,	"multisample_renderbuffer_samples_4"	},
+			{ SampleShadingRenderingCase::TARGET_RENDERBUFFER,		8,	"multisample_renderbuffer_samples_8"	},
+			{ SampleShadingRenderingCase::TARGET_RENDERBUFFER,		16,	"multisample_renderbuffer_samples_16"	},
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(targets); ++ndx)
+		{
+			minSamplesGroup->addChild(new SampleShadingRenderingCase(m_context, (std::string(targets[ndx].name) + "_color").c_str(),	"Test multiple samples per pixel with color",	targets[ndx].target, targets[ndx].numSamples, SampleShadingRenderingCase::TEST_COLOR));
+			minSamplesGroup->addChild(new SampleShadingRenderingCase(m_context, (std::string(targets[ndx].name) + "_discard").c_str(),	"Test multiple samples per pixel with",			targets[ndx].target, targets[ndx].numSamples, SampleShadingRenderingCase::TEST_DISCARD));
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fSampleShadingTests.hpp b/modules/gles31/functional/es31fSampleShadingTests.hpp
new file mode 100644
index 0000000..831be6a
--- /dev/null
+++ b/modules/gles31/functional/es31fSampleShadingTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSAMPLESHADINGTESTS_HPP
+#define _ES31FSAMPLESHADINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sample shading tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class SampleShadingTests : public TestCaseGroup
+{
+public:
+							SampleShadingTests		(Context& context);
+							~SampleShadingTests		(void);
+
+	void					init					(void);
+
+private:
+							SampleShadingTests		(const SampleShadingTests& other);
+	SampleShadingTests&		operator=				(const SampleShadingTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSAMPLESHADINGTESTS_HPP
diff --git a/modules/gles31/functional/es31fSampleVariableTests.cpp b/modules/gles31/functional/es31fSampleVariableTests.cpp
new file mode 100644
index 0000000..4699047
--- /dev/null
+++ b/modules/gles31/functional/es31fSampleVariableTests.cpp
@@ -0,0 +1,2329 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sample variable tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fSampleVariableTests.hpp"
+#include "es31fMultisampleShaderRenderCase.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuFormatUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+class Verifier
+{
+public:
+	virtual bool	verify	(const tcu::RGBA& testColor, const tcu::IVec2& position) const = 0;
+	virtual void	logInfo	(tcu::TestLog& log) const = 0;
+};
+
+class ColorVerifier : public Verifier
+{
+public:
+	ColorVerifier (const tcu::Vec3& _color, int _threshold = 8)
+		: m_color		(tcu::Vec4(_color.x(), _color.y(), _color.z(), 1.0f))
+		, m_threshold	(tcu::IVec3(_threshold))
+	{
+	}
+
+	ColorVerifier (const tcu::Vec3& _color, tcu::IVec3 _threshold)
+		: m_color		(tcu::Vec4(_color.x(), _color.y(), _color.z(), 1.0f))
+		, m_threshold	(_threshold)
+	{
+	}
+
+	bool verify (const tcu::RGBA& testColor, const tcu::IVec2& position) const
+	{
+		DE_UNREF(position);
+		return !tcu::boolAny(tcu::greaterThan(tcu::abs(m_color.toIVec().swizzle(0, 1, 2) - testColor.toIVec().swizzle(0, 1, 2)), tcu::IVec3(m_threshold)));
+	}
+
+	void logInfo (tcu::TestLog& log) const
+	{
+		// full threshold? print * for clarity
+		log	<< tcu::TestLog::Message
+			<< "Expecting unicolored image, color = RGB("
+			<< ((m_threshold[0] >= 255) ? ("*") : (de::toString(m_color.getRed()))) << ", "
+			<< ((m_threshold[1] >= 255) ? ("*") : (de::toString(m_color.getGreen()))) << ", "
+			<< ((m_threshold[2] >= 255) ? ("*") : (de::toString(m_color.getBlue()))) << ")"
+			<< tcu::TestLog::EndMessage;
+	}
+
+	const tcu::RGBA		m_color;
+	const tcu::IVec3	m_threshold;
+};
+
+class FullBlueSomeGreenVerifier : public Verifier
+{
+public:
+	FullBlueSomeGreenVerifier (void)
+	{
+	}
+
+	bool verify (const tcu::RGBA& testColor, const tcu::IVec2& position) const
+	{
+		DE_UNREF(position);
+
+		// Values from 0.0 and 1.0 are accurate
+
+		if (testColor.getRed() != 0)
+			return false;
+		if (testColor.getGreen() == 0)
+			return false;
+		if (testColor.getBlue() != 255)
+			return false;
+		return true;
+	}
+
+	void logInfo (tcu::TestLog& log) const
+	{
+		log << tcu::TestLog::Message << "Expecting color c = (0.0, x, 1.0), x > 0.0" << tcu::TestLog::EndMessage;
+	}
+};
+
+class NoRedVerifier : public Verifier
+{
+public:
+	NoRedVerifier (void)
+	{
+	}
+
+	bool verify (const tcu::RGBA& testColor, const tcu::IVec2& position) const
+	{
+		DE_UNREF(position);
+		return testColor.getRed() == 0;
+	}
+
+	void logInfo (tcu::TestLog& log) const
+	{
+		log << tcu::TestLog::Message << "Expecting zero-valued red channel." << tcu::TestLog::EndMessage;
+	}
+};
+
+class SampleAverageVerifier : public Verifier
+{
+public:
+				SampleAverageVerifier	(int _numSamples);
+
+	bool		verify					(const tcu::RGBA& testColor, const tcu::IVec2& position) const;
+	void		logInfo					(tcu::TestLog& log) const;
+
+	const int	m_numSamples;
+	const bool	m_isStatisticallySignificant;
+	float		m_distanceThreshold;
+};
+
+SampleAverageVerifier::SampleAverageVerifier (int _numSamples)
+	: m_numSamples					(_numSamples)
+	, m_isStatisticallySignificant	(_numSamples >= 4)
+	, m_distanceThreshold			(0.0f)
+{
+	// approximate Bates distribution as normal
+	const float variance			= (1.0f / (12.0f * m_numSamples));
+	const float standardDeviation	= deFloatSqrt(variance);
+
+	// 95% of means of sample positions are within 2 standard deviations if
+	// they were randomly assigned. Sample patterns are expected to be more
+	// uniform than a random pattern.
+	m_distanceThreshold = 2 * standardDeviation;
+}
+
+bool SampleAverageVerifier::verify (const tcu::RGBA& testColor, const tcu::IVec2& position) const
+{
+	DE_UNREF(position);
+	DE_ASSERT(m_isStatisticallySignificant);
+
+	const tcu::Vec2	avgPosition				(testColor.getGreen() / 255.0f, testColor.getBlue() / 255.0f);
+	const tcu::Vec2	distanceFromCenter		= tcu::abs(avgPosition - tcu::Vec2(0.5f, 0.5f));
+
+	return distanceFromCenter.x() < m_distanceThreshold && distanceFromCenter.y() < m_distanceThreshold;
+}
+
+void SampleAverageVerifier::logInfo (tcu::TestLog& log) const
+{
+	log << tcu::TestLog::Message << "Expecting average sample position to be near the pixel center. Maximum per-axis distance " << m_distanceThreshold << tcu::TestLog::EndMessage;
+}
+
+class PartialDiscardVerifier : public Verifier
+{
+public:
+	PartialDiscardVerifier (void)
+	{
+	}
+
+	bool verify (const tcu::RGBA& testColor, const tcu::IVec2& position) const
+	{
+		DE_UNREF(position);
+
+		return (testColor.getGreen() != 0) && (testColor.getGreen() != 255);
+	}
+
+	void logInfo (tcu::TestLog& log) const
+	{
+		log << tcu::TestLog::Message << "Expecting color non-zero and non-saturated green channel" << tcu::TestLog::EndMessage;
+	}
+};
+
+static bool verifyImageWithVerifier (const tcu::Surface& resultImage, tcu::TestLog& log, const Verifier& verifier, bool logOnSuccess = true)
+{
+	tcu::Surface	errorMask	(resultImage.getWidth(), resultImage.getHeight());
+	bool			error		= false;
+
+	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+	if (logOnSuccess)
+	{
+		log << tcu::TestLog::Message << "Verifying image." << tcu::TestLog::EndMessage;
+		verifier.logInfo(log);
+	}
+
+	for (int y = 0; y < resultImage.getHeight(); ++y)
+	for (int x = 0; x < resultImage.getWidth(); ++x)
+	{
+		const tcu::RGBA color		= resultImage.getPixel(x, y);
+
+		// verify color value is valid for this pixel position
+		if (!verifier.verify(color, tcu::IVec2(x,y)))
+		{
+			error = true;
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+		}
+	}
+
+	if (error)
+	{
+		// describe the verification logic if we haven't already
+		if (!logOnSuccess)
+			verifier.logInfo(log);
+
+		log	<< tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
+			<< tcu::TestLog::ImageSet("Verification", "Image Verification")
+			<< tcu::TestLog::Image("Result", "Result image", resultImage.getAccess())
+			<< tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask.getAccess())
+			<< tcu::TestLog::EndImageSet;
+	}
+	else if (logOnSuccess)
+	{
+		log << tcu::TestLog::Message << "Image verification passed." << tcu::TestLog::EndMessage
+			<< tcu::TestLog::ImageSet("Verification", "Image Verification")
+			<< tcu::TestLog::Image("Result", "Result image", resultImage.getAccess())
+			<< tcu::TestLog::EndImageSet;
+	}
+
+	return !error;
+}
+
+class MultisampleRenderCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
+{
+public:
+						MultisampleRenderCase		(Context& context, const char* name, const char* desc, int numSamples, RenderTarget target, int renderSize, int flags = 0);
+	virtual				~MultisampleRenderCase		(void);
+
+	virtual void		init						(void);
+
+};
+
+MultisampleRenderCase::MultisampleRenderCase (Context& context, const char* name, const char* desc, int numSamples, RenderTarget target, int renderSize, int flags)
+	: MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, desc, numSamples, target, renderSize, flags)
+{
+	DE_ASSERT(target < TARGET_LAST);
+}
+
+MultisampleRenderCase::~MultisampleRenderCase (void)
+{
+	MultisampleRenderCase::deinit();
+}
+
+void MultisampleRenderCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_sample_variables"))
+		throw tcu::NotSupportedError("Test requires GL_OES_sample_variables extension");
+
+	MultisampleShaderRenderUtil::MultisampleRenderCase::init();
+}
+
+class NumSamplesCase : public MultisampleRenderCase
+{
+public:
+					NumSamplesCase				(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target);
+					~NumSamplesCase				(void);
+
+	std::string		genFragmentSource			(int numTargetSamples) const;
+	bool			verifyImage					(const tcu::Surface& resultImage);
+
+private:
+	enum
+	{
+		RENDER_SIZE = 64
+	};
+};
+
+NumSamplesCase::NumSamplesCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target)
+	: MultisampleRenderCase(context, name, desc, sampleCount, target, RENDER_SIZE)
+{
+}
+
+NumSamplesCase::~NumSamplesCase (void)
+{
+}
+
+std::string NumSamplesCase::genFragmentSource (int numTargetSamples) const
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_sample_variables : require\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+			"	if (gl_NumSamples == " << numTargetSamples << ")\n"
+			"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+bool NumSamplesCase::verifyImage (const tcu::Surface& resultImage)
+{
+	return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), NoRedVerifier());
+}
+
+class MaxSamplesCase : public MultisampleRenderCase
+{
+public:
+					MaxSamplesCase				(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target);
+					~MaxSamplesCase				(void);
+
+private:
+	void			preDraw						(void);
+	std::string		genFragmentSource			(int numTargetSamples) const;
+	bool			verifyImage					(const tcu::Surface& resultImage);
+
+	enum
+	{
+		RENDER_SIZE = 64
+	};
+};
+
+MaxSamplesCase::MaxSamplesCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target)
+	: MultisampleRenderCase(context, name, desc, sampleCount, target, RENDER_SIZE)
+{
+}
+
+MaxSamplesCase::~MaxSamplesCase (void)
+{
+}
+
+void MaxSamplesCase::preDraw (void)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	deInt32					maxSamples	= -1;
+
+	// query samples
+	{
+		gl.getIntegerv(GL_MAX_SAMPLES, &maxSamples);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "query GL_MAX_SAMPLES");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_SAMPLES = " << maxSamples << tcu::TestLog::EndMessage;
+	}
+
+	// set samples
+	{
+		const int maxSampleLoc = gl.getUniformLocation(m_program->getProgram(), "u_maxSamples");
+		if (maxSampleLoc == -1)
+			throw tcu::TestError("Location of u_maxSamples was -1");
+
+		gl.uniform1i(maxSampleLoc, maxSamples);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set u_maxSamples uniform");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Set u_maxSamples = " << maxSamples << tcu::TestLog::EndMessage;
+	}
+}
+
+std::string MaxSamplesCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_sample_variables : require\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"uniform mediump int u_maxSamples;\n"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+			"	if (gl_MaxSamples != u_maxSamples)\n"
+			"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+bool MaxSamplesCase::verifyImage (const tcu::Surface& resultImage)
+{
+	return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), NoRedVerifier());
+}
+
+class SampleIDCase : public MultisampleRenderCase
+{
+public:
+					SampleIDCase				(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target);
+					~SampleIDCase				(void);
+
+	void			init						(void);
+
+private:
+	std::string		genFragmentSource			(int numTargetSamples) const;
+	bool			verifyImage					(const tcu::Surface& resultImage);
+	bool			verifySampleBuffers			(const std::vector<tcu::Surface>& resultBuffers);
+
+	enum
+	{
+		RENDER_SIZE = 64
+	};
+	enum VerificationMode
+	{
+		VERIFY_USING_SAMPLES,
+		VERIFY_USING_SELECTION,
+	};
+
+	const VerificationMode	m_vericationMode;
+};
+
+SampleIDCase::SampleIDCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target)
+	: MultisampleRenderCase	(context, name, desc, sampleCount, target, RENDER_SIZE, MultisampleShaderRenderUtil::MultisampleRenderCase::FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS)
+	, m_vericationMode		((target == TARGET_TEXTURE) ? (VERIFY_USING_SAMPLES) : (VERIFY_USING_SELECTION))
+{
+}
+
+SampleIDCase::~SampleIDCase (void)
+{
+}
+
+void SampleIDCase::init (void)
+{
+	// log the test method and expectations
+	if (m_vericationMode == VERIFY_USING_SAMPLES)
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Writing gl_SampleID to the green channel of the texture and verifying texture values, expecting:\n"
+			<< "	1) 0 with non-multisample targets.\n"
+			<< "	2) value N at sample index N of a multisample texture\n"
+			<< tcu::TestLog::EndMessage;
+	else if (m_vericationMode == VERIFY_USING_SELECTION)
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Selecting a single sample id for each pixel and writing color only if gl_SampleID == selected.\n"
+			<< "Expecting all output pixels to be partially (multisample) or fully (singlesample) colored.\n"
+			<< tcu::TestLog::EndMessage;
+	else
+		DE_ASSERT(false);
+
+	MultisampleRenderCase::init();
+}
+
+std::string SampleIDCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_ASSERT(numTargetSamples != 0);
+
+	std::ostringstream buf;
+
+	if (m_vericationMode == VERIFY_USING_SAMPLES)
+	{
+		// encode the id to the output, and then verify it during sampling
+		buf <<	"#version 310 es\n"
+				"#extension GL_OES_sample_variables : require\n"
+				"layout(location = 0) out mediump vec4 fragColor;\n"
+				"void main (void)\n"
+				"{\n"
+				"	highp float normalizedSample = float(gl_SampleID) / float(" << numTargetSamples << ");\n"
+				"	fragColor = vec4(0.0, normalizedSample, 1.0, 1.0);\n"
+				"}\n";
+	}
+	else if (m_vericationMode == VERIFY_USING_SELECTION)
+	{
+		if (numTargetSamples == 1)
+		{
+			// single sample, just verify value is 0
+			buf <<	"#version 310 es\n"
+					"#extension GL_OES_sample_variables : require\n"
+					"layout(location = 0) out mediump vec4 fragColor;\n"
+					"void main (void)\n"
+					"{\n"
+					"	if (gl_SampleID == 0)\n"
+					"		fragColor = vec4(0.0, 1.0, 1.0, 1.0);\n"
+					"	else\n"
+					"		fragColor = vec4(0.0, 0.0, 1.0, 1.0);\n"
+					"}\n";
+		}
+		else
+		{
+			// select only one sample per PIXEL
+			buf <<	"#version 310 es\n"
+					"#extension GL_OES_sample_variables : require\n"
+					"in highp vec4 v_position;\n"
+					"layout(location = 0) out mediump vec4 fragColor;\n"
+					"void main (void)\n"
+					"{\n"
+					"	highp vec2 relPosition = (v_position.xy + vec2(1.0, 1.0)) / 2.0;\n"
+					"	highp ivec2 pixelPos = ivec2(floor(relPosition * " << RENDER_SIZE << ".0));\n"
+					"	highp int selectedID = abs(pixelPos.x + 17 * pixelPos.y) % " << numTargetSamples << ";\n"
+					"\n"
+					"	if (gl_SampleID == selectedID)\n"
+					"		fragColor = vec4(0.0, 1.0, 1.0, 1.0);\n"
+					"	else\n"
+					"		fragColor = vec4(0.0, 0.0, 1.0, 1.0);\n"
+					"}\n";
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+	return buf.str();
+}
+
+bool SampleIDCase::verifyImage (const tcu::Surface& resultImage)
+{
+	if (m_vericationMode == VERIFY_USING_SAMPLES)
+	{
+		// never happens
+		DE_ASSERT(false);
+		return false;
+	}
+	else if (m_vericationMode == VERIFY_USING_SELECTION)
+	{
+		// should result in full blue and some green everywhere
+		return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), FullBlueSomeGreenVerifier());
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return false;
+	}
+}
+
+bool SampleIDCase::verifySampleBuffers (const std::vector<tcu::Surface>& resultBuffers)
+{
+	// Verify all sample buffers
+	bool allOk = true;
+
+	// Log layers
+	{
+		m_testCtx.getLog() << tcu::TestLog::ImageSet("SampleBuffers", "Image sample buffers");
+		for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
+			m_testCtx.getLog() << tcu::TestLog::Image("Buffer" + de::toString(sampleNdx), "Sample " + de::toString(sampleNdx), resultBuffers[sampleNdx].getAccess());
+		m_testCtx.getLog() << tcu::TestLog::EndImageSet;
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample buffers" << tcu::TestLog::EndMessage;
+	for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
+	{
+		// sample id should be sample index
+		const int threshold = 255 / 4 / m_numTargetSamples + 1;
+		const float sampleIdColor = sampleNdx / (float)m_numTargetSamples;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample " << (sampleNdx+1) << "/" << (int)resultBuffers.size() << tcu::TestLog::EndMessage;
+		allOk &= verifyImageWithVerifier(resultBuffers[sampleNdx], m_testCtx.getLog(), ColorVerifier(tcu::Vec3(0.0f, sampleIdColor, 1.0f), tcu::IVec3(1, threshold, 1)), false);
+	}
+
+	if (!allOk)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Sample buffer verification failed" << tcu::TestLog::EndMessage;
+
+	return allOk;
+}
+
+class SamplePosDistributionCase : public MultisampleRenderCase
+{
+public:
+					SamplePosDistributionCase	(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target);
+					~SamplePosDistributionCase	(void);
+
+	void			init						(void);
+private:
+	enum
+	{
+		RENDER_SIZE = 64
+	};
+
+	std::string		genFragmentSource			(int numTargetSamples) const;
+	bool			verifyImage					(const tcu::Surface& resultImage);
+	bool			verifySampleBuffers			(const std::vector<tcu::Surface>& resultBuffers);
+};
+
+SamplePosDistributionCase::SamplePosDistributionCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target)
+	: MultisampleRenderCase(context, name, desc, sampleCount, target, RENDER_SIZE, MultisampleShaderRenderUtil::MultisampleRenderCase::FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS)
+{
+}
+
+SamplePosDistributionCase::~SamplePosDistributionCase (void)
+{
+}
+
+void SamplePosDistributionCase::init (void)
+{
+	// log the test method and expectations
+	if (m_renderTarget == TARGET_TEXTURE)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Verifying gl_SamplePosition value:\n"
+			<< "	1) With non-multisample targets: Expect the center of the pixel.\n"
+			<< "	2) With multisample targets:\n"
+			<< "		a) Expect legal sample position.\n"
+			<< "		b) Sample position is unique within the set of all sample positions of a pixel.\n"
+			<< "		c) Sample position distribution is uniform or almost uniform.\n"
+			<< tcu::TestLog::EndMessage;
+	}
+	else
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Verifying gl_SamplePosition value:\n"
+			<< "	1) With non-multisample targets: Expect the center of the pixel.\n"
+			<< "	2) With multisample targets:\n"
+			<< "		a) Expect legal sample position.\n"
+			<< "		b) Sample position distribution is uniform or almost uniform.\n"
+			<< tcu::TestLog::EndMessage;
+	}
+
+	MultisampleRenderCase::init();
+}
+
+std::string SamplePosDistributionCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_ASSERT(numTargetSamples != 0);
+	DE_UNREF(numTargetSamples);
+
+	const bool			multisampleTarget = (m_numRequestedSamples > 0) || (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() > 1);
+	std::ostringstream	buf;
+
+	if (multisampleTarget)
+	{
+		// encode the position to the output, use red channel as error channel
+		buf <<	"#version 310 es\n"
+				"#extension GL_OES_sample_variables : require\n"
+				"layout(location = 0) out mediump vec4 fragColor;\n"
+				"void main (void)\n"
+				"{\n"
+				"	if (gl_SamplePosition.x < 0.0 || gl_SamplePosition.x > 1.0 || gl_SamplePosition.y < 0.0 || gl_SamplePosition.y > 1.0)\n"
+				"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	else\n"
+				"		fragColor = vec4(0.0, gl_SamplePosition.x, gl_SamplePosition.y, 1.0);\n"
+				"}\n";
+	}
+	else
+	{
+		// verify value is ok
+		buf <<	"#version 310 es\n"
+				"#extension GL_OES_sample_variables : require\n"
+				"layout(location = 0) out mediump vec4 fragColor;\n"
+				"void main (void)\n"
+				"{\n"
+				"	if (gl_SamplePosition.x != 0.5 || gl_SamplePosition.y != 0.5)\n"
+				"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	else\n"
+				"		fragColor = vec4(0.0, gl_SamplePosition.x, gl_SamplePosition.y, 1.0);\n"
+				"}\n";
+	}
+
+	return buf.str();
+}
+
+bool SamplePosDistributionCase::verifyImage (const tcu::Surface& resultImage)
+{
+	const int				sampleCount	= (m_renderTarget == TARGET_DEFAULT) ? (m_context.getRenderTarget().getNumSamples()) : (m_numRequestedSamples);
+	SampleAverageVerifier	verifier	(sampleCount);
+
+	// check there is nothing in the error channel
+	if (!verifyImageWithVerifier(resultImage, m_testCtx.getLog(), NoRedVerifier()))
+		return false;
+
+	// position average should be around 0.5, 0.5
+	if (verifier.m_isStatisticallySignificant && !verifyImageWithVerifier(resultImage, m_testCtx.getLog(), verifier))
+		throw MultisampleShaderRenderUtil::QualityWarning("Bias detected, sample positions are not uniformly distributed within the pixel");
+
+	return true;
+}
+
+bool SamplePosDistributionCase::verifySampleBuffers (const std::vector<tcu::Surface>& resultBuffers)
+{
+	const int	width				= resultBuffers[0].getWidth();
+	const int	height				= resultBuffers[0].getHeight();
+	bool		allOk				= true;
+	bool		distibutionError	= false;
+
+	// Check sample range, uniqueness, and distribution, log layers
+	{
+		m_testCtx.getLog() << tcu::TestLog::ImageSet("SampleBuffers", "Image sample buffers");
+		for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
+			m_testCtx.getLog() << tcu::TestLog::Image("Buffer" + de::toString(sampleNdx), "Sample " + de::toString(sampleNdx), resultBuffers[sampleNdx].getAccess());
+		m_testCtx.getLog() << tcu::TestLog::EndImageSet;
+	}
+
+	// verify range
+	{
+		bool rangeOk = true;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample position range" << tcu::TestLog::EndMessage;
+		for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
+		{
+			// shader does the check, just check the shader error output (red)
+			m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample " << (sampleNdx+1) << "/" << (int)resultBuffers.size() << tcu::TestLog::EndMessage;
+			rangeOk &= verifyImageWithVerifier(resultBuffers[sampleNdx], m_testCtx.getLog(), NoRedVerifier(), false);
+		}
+
+		if (!rangeOk)
+		{
+			allOk = false;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Sample position verification failed." << tcu::TestLog::EndMessage;
+		}
+	}
+
+	// Verify uniqueness
+	{
+		bool					uniquenessOk	= true;
+		tcu::Surface			errorMask		(width, height);
+		std::vector<tcu::Vec2>	samplePositions	(resultBuffers.size());
+		int						printCount		= 0;
+		const int				printFloodLimit	= 5;
+
+		tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample position uniqueness." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < height; ++y)
+		for (int x = 0; x < width; ++x)
+		{
+			bool samplePosNotUnique = false;
+
+			for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
+			{
+				const tcu::RGBA color = resultBuffers[sampleNdx].getPixel(x, y);
+				samplePositions[sampleNdx] = tcu::Vec2(color.getGreen() / 255.0f, color.getBlue() / 255.0f);
+			}
+
+			// Just check there are no two samples with same positions
+			for (int sampleNdxA = 0;            sampleNdxA < (int)resultBuffers.size() && (!samplePosNotUnique || printCount < printFloodLimit); ++sampleNdxA)
+			for (int sampleNdxB = sampleNdxA+1; sampleNdxB < (int)resultBuffers.size() && (!samplePosNotUnique || printCount < printFloodLimit); ++sampleNdxB)
+			{
+				if (samplePositions[sampleNdxA] == samplePositions[sampleNdxB])
+				{
+					if (++printCount <= printFloodLimit)
+					{
+						m_testCtx.getLog()
+							<< tcu::TestLog::Message
+							<< "Pixel (" << x << ", " << y << "): Samples " << sampleNdxA << " and " << sampleNdxB << " have the same position."
+							<< tcu::TestLog::EndMessage;
+					}
+
+					samplePosNotUnique = true;
+					uniquenessOk = false;
+					errorMask.setPixel(x, y, tcu::RGBA::red);
+				}
+			}
+		}
+
+		// end result
+		if (!uniquenessOk)
+		{
+			if (printCount > printFloodLimit)
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "...\n"
+					<< "Omitted " << (printCount-printFloodLimit) << " error descriptions."
+					<< tcu::TestLog::EndMessage;
+
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
+				<< tcu::TestLog::ImageSet("Verification", "Image Verification")
+				<< tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask.getAccess())
+				<< tcu::TestLog::EndImageSet;
+
+			allOk = false;
+		}
+	}
+
+	// check distribution
+	{
+		const SampleAverageVerifier verifier		(m_numTargetSamples);
+		tcu::Surface				errorMask		(width, height);
+		int							printCount		= 0;
+		const int					printFloodLimit	= 5;
+
+		tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+		// don't bother with small sample counts
+		if (verifier.m_isStatisticallySignificant)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample position distribution is (nearly) unbiased." << tcu::TestLog::EndMessage;
+			verifier.logInfo(m_testCtx.getLog());
+
+			for (int y = 0; y < height; ++y)
+			for (int x = 0; x < width; ++x)
+			{
+				tcu::IVec3 colorSum(0, 0, 0);
+
+				// color average
+
+				for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
+				{
+					const tcu::RGBA color = resultBuffers[sampleNdx].getPixel(x, y);
+					colorSum.x() += color.getRed();
+					colorSum.y() += color.getBlue();
+					colorSum.z() += color.getGreen();
+				}
+
+				colorSum.x() /= m_numTargetSamples;
+				colorSum.y() /= m_numTargetSamples;
+				colorSum.z() /= m_numTargetSamples;
+
+				// verify average sample position
+
+				if (!verifier.verify(tcu::RGBA(colorSum.x(), colorSum.y(), colorSum.z(), 0), tcu::IVec2(x, y)))
+				{
+					if (++printCount <= printFloodLimit)
+					{
+						m_testCtx.getLog()
+							<< tcu::TestLog::Message
+							<< "Pixel (" << x << ", " << y << "): Sample distribution is biased."
+							<< tcu::TestLog::EndMessage;
+					}
+
+					distibutionError = true;
+					errorMask.setPixel(x, y, tcu::RGBA::red);
+				}
+			}
+
+			// sub-verification result
+			if (distibutionError)
+			{
+				if (printCount > printFloodLimit)
+					m_testCtx.getLog()
+						<< tcu::TestLog::Message
+						<< "...\n"
+						<< "Omitted " << (printCount-printFloodLimit) << " error descriptions."
+						<< tcu::TestLog::EndMessage;
+
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
+					<< tcu::TestLog::ImageSet("Verification", "Image Verification")
+					<< tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask.getAccess())
+					<< tcu::TestLog::EndImageSet;
+			}
+		}
+	}
+
+	// results
+	if (!allOk)
+		return false;
+	else if (distibutionError)
+		throw MultisampleShaderRenderUtil::QualityWarning("Bias detected, sample positions are not uniformly distributed within the pixel");
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verification ok." << tcu::TestLog::EndMessage;
+		return true;
+	}
+}
+
+class SamplePosCorrectnessCase : public MultisampleRenderCase
+{
+public:
+					SamplePosCorrectnessCase	(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target);
+					~SamplePosCorrectnessCase	(void);
+
+	void			init						(void);
+private:
+	enum
+	{
+		RENDER_SIZE = 32
+	};
+
+	void			preDraw						(void);
+	void			postDraw					(void);
+
+	std::string		genVertexSource				(int numTargetSamples) const;
+	std::string		genFragmentSource			(int numTargetSamples) const;
+	bool			verifyImage					(const tcu::Surface& resultImage);
+
+	bool			m_useSampleQualifier;
+};
+
+SamplePosCorrectnessCase::SamplePosCorrectnessCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target)
+	: MultisampleRenderCase	(context, name, desc, sampleCount, target, RENDER_SIZE)
+	, m_useSampleQualifier	(false)
+{
+}
+
+SamplePosCorrectnessCase::~SamplePosCorrectnessCase (void)
+{
+}
+
+void SamplePosCorrectnessCase::init (void)
+{
+	// requirements: per-invocation interpolation required
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation") &&
+		!m_context.getContextInfo().isExtensionSupported("GL_OES_sample_shading"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation or GL_OES_sample_shading extension");
+
+	// prefer to use the sample qualifier path
+	m_useSampleQualifier = m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation");
+
+	// log the test method and expectations
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying gl_SamplePosition correctness:\n"
+		<< "	1) Varying values should be sampled at the sample position.\n"
+		<< "		=> fract(screenSpacePosition) == gl_SamplePosition\n"
+		<< tcu::TestLog::EndMessage;
+
+	MultisampleRenderCase::init();
+}
+
+void SamplePosCorrectnessCase::preDraw (void)
+{
+	if (!m_useSampleQualifier)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		// use GL_OES_sample_shading to set per fragment sample invocation interpolation
+		gl.enable(GL_SAMPLE_SHADING);
+		gl.minSampleShading(1.0f);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set ratio");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Enabling per-sample interpolation with GL_SAMPLE_SHADING." << tcu::TestLog::EndMessage;
+	}
+}
+
+void SamplePosCorrectnessCase::postDraw (void)
+{
+	if (!m_useSampleQualifier)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.disable(GL_SAMPLE_SHADING);
+		gl.minSampleShading(1.0f);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set ratio");
+	}
+}
+
+std::string SamplePosCorrectnessCase::genVertexSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"in highp vec4 a_position;\n"
+		<<	((m_useSampleQualifier) ? ("#extension GL_OES_shader_multisample_interpolation : require\n") : (""))
+		<<	((m_useSampleQualifier) ? ("sample ") : ("")) << "out highp vec4 v_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_position = a_position;\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string SamplePosCorrectnessCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	// encode the position to the output, use red channel as error channel
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_sample_variables : require\n"
+		<<	((m_useSampleQualifier) ? ("#extension GL_OES_shader_multisample_interpolation : require\n") : (""))
+		<<	((m_useSampleQualifier) ? ("sample ") : ("")) << "in highp vec4 v_position;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	const highp float maxDistance = 0.15625; // 4 subpixel bits. Assume 3 accurate bits + 0.03125 for other errors\n" // 0.03125 = mediump epsilon when value = 32 (RENDER_SIZE)
+			"\n"
+			"	highp vec2 screenSpacePosition = (v_position.xy + vec2(1.0, 1.0)) / 2.0 * " << RENDER_SIZE << ".0;\n"
+			"	highp ivec2 nearbyPixel = ivec2(floor(screenSpacePosition));\n"
+			"	bool allOk = false;\n"
+			"\n"
+			"	// sample at edge + inaccuaries may cause us to round to any neighboring pixel\n"
+			"	// check all neighbors for any match\n"
+			"	for (highp int dy = -1; dy <= 1; ++dy)\n"
+			"	for (highp int dx = -1; dx <= 1; ++dx)\n"
+			"	{\n"
+			"		highp ivec2 currentPixel = nearbyPixel + ivec2(dx, dy);\n"
+			"		highp vec2 candidateSamplingPos = vec2(currentPixel) + gl_SamplePosition.xy;\n"
+			"		highp vec2 positionDiff = abs(candidateSamplingPos - screenSpacePosition);\n"
+			"		if (positionDiff.x < maxDistance && positionDiff.y < maxDistance)\n"
+			"			allOk = true;\n"
+			"	}\n"
+			"\n"
+			"	if (allOk)\n"
+			"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+			"	else\n"
+			"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+bool SamplePosCorrectnessCase::verifyImage (const tcu::Surface& resultImage)
+{
+	return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), NoRedVerifier());
+}
+
+class SampleMaskBaseCase : public MultisampleRenderCase
+{
+public:
+	enum ShaderRunMode
+	{
+		RUN_PER_PIXEL = 0,
+		RUN_PER_SAMPLE,
+		RUN_PER_TWO_SAMPLES,
+
+		RUN_LAST
+	};
+
+						SampleMaskBaseCase			(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target, int renderSize, ShaderRunMode runMode, int flags = 0);
+	virtual				~SampleMaskBaseCase			(void);
+
+protected:
+	virtual void		init						(void);
+	virtual void		preDraw						(void);
+	virtual void		postDraw					(void);
+	virtual bool		verifyImage					(const tcu::Surface& resultImage);
+
+	const ShaderRunMode	m_runMode;
+};
+
+SampleMaskBaseCase::SampleMaskBaseCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target, int renderSize, ShaderRunMode runMode, int flags)
+	: MultisampleRenderCase	(context, name, desc, sampleCount, target, renderSize, flags)
+	, m_runMode				(runMode)
+{
+	DE_ASSERT(runMode < RUN_LAST);
+}
+
+SampleMaskBaseCase::~SampleMaskBaseCase (void)
+{
+}
+
+void SampleMaskBaseCase::init (void)
+{
+	// required extra extension
+	if (m_runMode == RUN_PER_TWO_SAMPLES && !m_context.getContextInfo().isExtensionSupported("GL_OES_sample_shading"))
+		throw tcu::NotSupportedError("Test requires GL_OES_sample_shading extension");
+
+	MultisampleRenderCase::init();
+}
+
+void SampleMaskBaseCase::preDraw (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_runMode == RUN_PER_TWO_SAMPLES)
+	{
+		gl.enable(GL_SAMPLE_SHADING);
+		gl.minSampleShading(0.5f);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "enable sample shading");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Enabled GL_SAMPLE_SHADING, value = 0.5" << tcu::TestLog::EndMessage;
+	}
+}
+
+void SampleMaskBaseCase::postDraw (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_runMode == RUN_PER_TWO_SAMPLES)
+	{
+		gl.disable(GL_SAMPLE_SHADING);
+		gl.minSampleShading(1.0f);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "disable sample shading");
+	}
+}
+
+bool SampleMaskBaseCase::verifyImage (const tcu::Surface& resultImage)
+{
+	// shader does the verification
+	return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), NoRedVerifier());
+}
+
+class SampleMaskCase : public SampleMaskBaseCase
+{
+public:
+						SampleMaskCase				(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target);
+						~SampleMaskCase				(void);
+
+	void				init						(void);
+	void				preDraw						(void);
+	void				postDraw					(void);
+
+private:
+	enum
+	{
+		RENDER_SIZE = 64
+	};
+
+	std::string			genFragmentSource			(int numTargetSamples) const;
+};
+
+SampleMaskCase::SampleMaskCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target)
+	: SampleMaskBaseCase(context, name, desc, sampleCount, target, RENDER_SIZE, RUN_PER_PIXEL)
+{
+}
+
+SampleMaskCase::~SampleMaskCase (void)
+{
+}
+
+void SampleMaskCase::init (void)
+{
+	// log the test method and expectations
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying gl_SampleMaskIn value with SAMPLE_MASK state. gl_SampleMaskIn does not contain any bits set that are have been killed by SAMPLE_MASK state. Expecting:\n"
+		<< "	1) With multisample targets: gl_SampleMaskIn AND ~(SAMPLE_MASK) should be zero.\n"
+		<< "	2) With non-multisample targets: SAMPLE_MASK state is only ANDed as a multisample operation. gl_SampleMaskIn should only have its last bit set regardless of SAMPLE_MASK state.\n"
+		<< tcu::TestLog::EndMessage;
+
+	SampleMaskBaseCase::init();
+}
+
+void SampleMaskCase::preDraw (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const bool				multisampleTarget	= (m_numRequestedSamples > 0) || (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() > 1);
+	const deUint32			fullMask			= (deUint32)0xAAAAAAAAUL;
+	const deUint32			maskMask			= (1U << m_numTargetSamples) - 1;
+	const deUint32			effectiveMask		=  fullMask & maskMask;
+
+	// set test mask
+	gl.enable(GL_SAMPLE_MASK);
+	gl.sampleMaski(0, effectiveMask);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set mask");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Setting sample mask " << tcu::Format::Hex<4>(effectiveMask) << tcu::TestLog::EndMessage;
+
+	// set multisample case uniforms
+	if (multisampleTarget)
+	{
+		const int maskLoc = gl.getUniformLocation(m_program->getProgram(), "u_sampleMask");
+		if (maskLoc == -1)
+			throw tcu::TestError("Location of u_mask was -1");
+
+		gl.uniform1ui(maskLoc, effectiveMask);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set mask uniform");
+	}
+
+	// base class logic
+	SampleMaskBaseCase::preDraw();
+}
+
+void SampleMaskCase::postDraw (void)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const deUint32			fullMask	= (1U << m_numTargetSamples) - 1;
+
+	gl.disable(GL_SAMPLE_MASK);
+	gl.sampleMaski(0, fullMask);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set mask");
+
+	// base class logic
+	SampleMaskBaseCase::postDraw();
+}
+
+std::string SampleMaskCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_ASSERT(numTargetSamples != 0);
+
+	const bool			multisampleTarget = (m_numRequestedSamples > 0) || (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() > 1);
+	std::ostringstream	buf;
+
+	// test supports only one sample mask word
+	if (numTargetSamples > 32)
+		throw tcu::NotSupportedError("Sample count larger than 32 is not supported.");
+
+	if (multisampleTarget)
+	{
+		buf <<	"#version 310 es\n"
+				"#extension GL_OES_sample_variables : require\n"
+				"layout(location = 0) out mediump vec4 fragColor;\n"
+				"uniform highp uint u_sampleMask;\n"
+				"void main (void)\n"
+				"{\n"
+				"	if ((uint(gl_SampleMaskIn[0]) & (~u_sampleMask)) != 0u)\n"
+				"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	else\n"
+				"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"}\n";
+	}
+	else
+	{
+		// non-multisample targets don't get multisample operations like ANDing with mask
+
+		buf <<	"#version 310 es\n"
+				"#extension GL_OES_sample_variables : require\n"
+				"layout(location = 0) out mediump vec4 fragColor;\n"
+				"uniform highp uint u_sampleMask;\n"
+				"void main (void)\n"
+				"{\n"
+				"	if (gl_SampleMaskIn[0] != 1)\n"
+				"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	else\n"
+				"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"}\n";
+	}
+
+	return buf.str();
+}
+
+class SampleMaskCountCase : public SampleMaskBaseCase
+{
+public:
+						SampleMaskCountCase			(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target, ShaderRunMode runMode);
+						~SampleMaskCountCase		(void);
+
+	void				init						(void);
+	void				preDraw						(void);
+	void				postDraw					(void);
+
+private:
+	enum
+	{
+		RENDER_SIZE = 64
+	};
+
+	std::string			genFragmentSource			(int numTargetSamples) const;
+};
+
+SampleMaskCountCase::SampleMaskCountCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target, ShaderRunMode runMode)
+	: SampleMaskBaseCase(context, name, desc, sampleCount, target, RENDER_SIZE, runMode)
+{
+	DE_ASSERT(runMode < RUN_LAST);
+}
+
+SampleMaskCountCase::~SampleMaskCountCase (void)
+{
+}
+
+void SampleMaskCountCase::init (void)
+{
+	// log the test method and expectations
+	if (m_runMode == RUN_PER_PIXEL)
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Verifying gl_SampleMaskIn.\n"
+			<< "	Fragment shader may be invoked [1, numSamples] times.\n"
+			<< "	=> gl_SampleMaskIn should have the number of bits set in range [1, numSamples]\n"
+			<< tcu::TestLog::EndMessage;
+	else if (m_runMode == RUN_PER_SAMPLE)
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Verifying gl_SampleMaskIn.\n"
+			<< "	Fragment will be invoked numSamples times.\n"
+			<< "	=> gl_SampleMaskIn should have only one bit set.\n"
+			<< tcu::TestLog::EndMessage;
+	else if (m_runMode == RUN_PER_TWO_SAMPLES)
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Verifying gl_SampleMaskIn.\n"
+			<< "	Fragment shader may be invoked [ceil(numSamples/2), numSamples] times.\n"
+			<< "	=> gl_SampleMaskIn should have the number of bits set in range [1, numSamples - ceil(numSamples/2) + 1]:\n"
+			<< tcu::TestLog::EndMessage;
+	else
+		DE_ASSERT(false);
+
+	SampleMaskBaseCase::init();
+}
+
+void SampleMaskCountCase::preDraw (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_runMode == RUN_PER_PIXEL)
+	{
+		const int maxLoc = gl.getUniformLocation(m_program->getProgram(), "u_maxBitCount");
+		const int minLoc = gl.getUniformLocation(m_program->getProgram(), "u_minBitCount");
+		const int minBitCount = 1;
+		const int maxBitCount = m_numTargetSamples;
+
+		if (maxLoc == -1)
+			throw tcu::TestError("Location of u_maxBitCount was -1");
+		if (minLoc == -1)
+			throw tcu::TestError("Location of u_minBitCount was -1");
+
+		gl.uniform1i(minLoc, minBitCount);
+		gl.uniform1i(maxLoc, maxBitCount);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set limits");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting minBitCount = " << minBitCount << ", maxBitCount = " << maxBitCount << tcu::TestLog::EndMessage;
+	}
+	else if (m_runMode == RUN_PER_TWO_SAMPLES)
+	{
+		const int maxLoc = gl.getUniformLocation(m_program->getProgram(), "u_maxBitCount");
+		const int minLoc = gl.getUniformLocation(m_program->getProgram(), "u_minBitCount");
+
+		// Worst case: all but one shader invocations get one sample, one shader invocation the rest of the samples
+		const int minInvocationCount = ((m_numTargetSamples + 1) / 2);
+		const int minBitCount = 1;
+		const int maxBitCount = (m_numTargetSamples <= 2) ? (1) : (m_numTargetSamples - ((minInvocationCount-1) * minBitCount));
+
+		if (maxLoc == -1)
+			throw tcu::TestError("Location of u_maxBitCount was -1");
+		if (minLoc == -1)
+			throw tcu::TestError("Location of u_minBitCount was -1");
+
+		gl.uniform1i(minLoc, minBitCount);
+		gl.uniform1i(maxLoc, maxBitCount);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set limits");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting minBitCount = " << minBitCount << ", maxBitCount = " << maxBitCount << tcu::TestLog::EndMessage;
+	}
+
+	SampleMaskBaseCase::preDraw();
+}
+
+void SampleMaskCountCase::postDraw (void)
+{
+	SampleMaskBaseCase::postDraw();
+}
+
+std::string SampleMaskCountCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_ASSERT(numTargetSamples != 0);
+
+	std::ostringstream buf;
+
+	// test supports only one sample mask word
+	if (numTargetSamples > 32)
+		throw tcu::NotSupportedError("Sample count larger than 32 is not supported.");
+
+	// count the number of the bits in gl_SampleMask
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_sample_variables : require\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n";
+
+	if (m_runMode != RUN_PER_SAMPLE)
+		buf <<	"uniform highp int u_minBitCount;\n"
+				"uniform highp int u_maxBitCount;\n";
+
+	buf <<	"void main (void)\n"
+			"{\n"
+			"	mediump int maskBitCount = 0;\n"
+			"	for (int i = 0; i < 32; ++i)\n"
+			"		if (((gl_SampleMaskIn[0] >> i) & 0x01) == 0x01)\n"
+			"			++maskBitCount;\n"
+			"\n";
+
+	if (m_runMode == RUN_PER_SAMPLE)
+	{
+		// check the validity here
+		buf <<	"	// force per-sample shading\n"
+				"	highp float blue = float(gl_SampleID);\n"
+				"\n"
+				"	if (maskBitCount != 1)\n"
+				"		fragColor = vec4(1.0, 0.0, blue, 1.0);\n"
+				"	else\n"
+				"		fragColor = vec4(0.0, 1.0, blue, 1.0);\n"
+				"}\n";
+	}
+	else
+	{
+		// check the validity here
+		buf <<	"	if (maskBitCount < u_minBitCount || maskBitCount > u_maxBitCount)\n"
+				"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	else\n"
+				"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"}\n";
+	}
+
+	return buf.str();
+}
+
+class SampleMaskUniqueCase : public SampleMaskBaseCase
+{
+public:
+						SampleMaskUniqueCase		(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target, ShaderRunMode runMode);
+						~SampleMaskUniqueCase		(void);
+
+	void				init						(void);
+
+private:
+	enum
+	{
+		RENDER_SIZE = 64
+	};
+
+	std::string			genFragmentSource			(int numTargetSamples) const;
+	bool				verifySampleBuffers			(const std::vector<tcu::Surface>& resultBuffers);
+};
+
+SampleMaskUniqueCase::SampleMaskUniqueCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target, ShaderRunMode runMode)
+	: SampleMaskBaseCase(context, name, desc, sampleCount, target, RENDER_SIZE, runMode, MultisampleShaderRenderUtil::MultisampleRenderCase::FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS)
+{
+	DE_ASSERT(runMode == RUN_PER_SAMPLE);
+	DE_ASSERT(target == TARGET_TEXTURE);
+}
+
+SampleMaskUniqueCase::~SampleMaskUniqueCase (void)
+{
+}
+
+void SampleMaskUniqueCase::init (void)
+{
+	// log the test method and expectations
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying gl_SampleMaskIn.\n"
+		<< "	Fragment will be invoked numSamples times.\n"
+		<< "	=> gl_SampleMaskIn should have only one bit set\n"
+		<< "	=> and that bit index should be unique within other fragment shader invocations of that pixel.\n"
+		<< "	Writing sampleMask bit index to green channel in render shader. Verifying uniqueness in sampler shader.\n"
+		<< tcu::TestLog::EndMessage;
+
+	SampleMaskBaseCase::init();
+}
+
+std::string SampleMaskUniqueCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_ASSERT(numTargetSamples != 0);
+
+	std::ostringstream buf;
+
+	// test supports only one sample mask word
+	if (numTargetSamples > 32)
+		throw tcu::NotSupportedError("Sample count larger than 32 is not supported.");
+
+	// find our sampleID by searching for unique bit.
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_sample_variables : require\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	mediump int firstIndex = -1;\n"
+			"	for (int i = 0; i < 32; ++i)\n"
+			"	{\n"
+			"		if (((gl_SampleMaskIn[0] >> i) & 0x01) == 0x01)\n"
+			"		{\n"
+			"			firstIndex = i;\n"
+			"			break;\n"
+			"		}\n"
+			"	}\n"
+			"\n"
+			"	bool notUniqueError = false;\n"
+			"	for (int i = firstIndex + 1; i < 32; ++i)\n"
+			"		if (((gl_SampleMaskIn[0] >> i) & 0x01) == 0x01)\n"
+			"			notUniqueError = true;\n"
+			"\n"
+			"	highp float encodedSampleId = float(firstIndex) / " << numTargetSamples <<".0;\n"
+			"\n"
+			"	// force per-sample shading\n"
+			"	highp float blue = float(gl_SampleID);\n"
+			"\n"
+			"	if (notUniqueError)\n"
+			"		fragColor = vec4(1.0, 0.0, blue, 1.0);\n"
+			"	else\n"
+			"		fragColor = vec4(0.0, encodedSampleId, blue, 1.0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+bool SampleMaskUniqueCase::verifySampleBuffers (const std::vector<tcu::Surface>& resultBuffers)
+{
+	const int	width				= resultBuffers[0].getWidth();
+	const int	height				= resultBuffers[0].getHeight();
+	bool		allOk				= true;
+
+	// Log samples
+	{
+		m_testCtx.getLog() << tcu::TestLog::ImageSet("SampleBuffers", "Image sample buffers");
+		for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
+			m_testCtx.getLog() << tcu::TestLog::Image("Buffer" + de::toString(sampleNdx), "Sample " + de::toString(sampleNdx), resultBuffers[sampleNdx].getAccess());
+		m_testCtx.getLog() << tcu::TestLog::EndImageSet;
+	}
+
+	// check for earlier errors (in fragment shader)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying fragment shader invocation found only one set sample mask bit." << tcu::TestLog::EndMessage;
+
+		for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
+		{
+			// shader does the check, just check the shader error output (red)
+			m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample " << (sampleNdx+1) << "/" << (int)resultBuffers.size() << tcu::TestLog::EndMessage;
+			allOk &= verifyImageWithVerifier(resultBuffers[sampleNdx], m_testCtx.getLog(), NoRedVerifier(), false);
+		}
+
+		if (!allOk)
+		{
+			// can't check the uniqueness if the masks don't work at all
+			m_testCtx.getLog() << tcu::TestLog::Message << "Could not get mask information from the rendered image, cannot continue verification." << tcu::TestLog::EndMessage;
+			return false;
+		}
+	}
+
+	// verify index / index ranges
+
+	if (m_numRequestedSamples == 0)
+	{
+		// single sample target, expect index=0
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample mask bit index is 0." << tcu::TestLog::EndMessage;
+
+		// only check the mask index
+		allOk &= verifyImageWithVerifier(resultBuffers[0], m_testCtx.getLog(), ColorVerifier(tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::IVec3(255, 8, 255)), false);
+	}
+	else
+	{
+		// check uniqueness
+
+		tcu::Surface		errorMask		(width, height);
+		bool				uniquenessOk	= true;
+		int					printCount		= 0;
+		const int			printFloodLimit	= 5;
+		std::vector<int>	maskBitIndices	(resultBuffers.size());
+
+		tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying per-invocation sample mask bit is unique." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < height; ++y)
+		for (int x = 0; x < width; ++x)
+		{
+			bool maskNdxNotUnique = false;
+
+			// decode index
+			for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
+			{
+				const tcu::RGBA color = resultBuffers[sampleNdx].getPixel(x, y);
+				maskBitIndices[sampleNdx] = (int)deFloatRound(color.getGreen() / 255.0f * m_numTargetSamples);
+			}
+
+			// just check there are no two invocations with the same bit index
+			for (int sampleNdxA = 0;            sampleNdxA < (int)resultBuffers.size() && (!maskNdxNotUnique || printCount < printFloodLimit); ++sampleNdxA)
+			for (int sampleNdxB = sampleNdxA+1; sampleNdxB < (int)resultBuffers.size() && (!maskNdxNotUnique || printCount < printFloodLimit); ++sampleNdxB)
+			{
+				if (maskBitIndices[sampleNdxA] == maskBitIndices[sampleNdxB])
+				{
+					if (++printCount <= printFloodLimit)
+					{
+						m_testCtx.getLog()
+							<< tcu::TestLog::Message
+							<< "Pixel (" << x << ", " << y << "): Samples " << sampleNdxA << " and " << sampleNdxB << " have the same sample mask. (Single bit at index " << maskBitIndices[sampleNdxA] << ")"
+							<< tcu::TestLog::EndMessage;
+					}
+
+					maskNdxNotUnique = true;
+					uniquenessOk = false;
+					errorMask.setPixel(x, y, tcu::RGBA::red);
+				}
+			}
+		}
+
+		// end result
+		if (!uniquenessOk)
+		{
+			if (printCount > printFloodLimit)
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "...\n"
+					<< "Omitted " << (printCount-printFloodLimit) << " error descriptions."
+					<< tcu::TestLog::EndMessage;
+
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
+				<< tcu::TestLog::ImageSet("Verification", "Image Verification")
+				<< tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask.getAccess())
+				<< tcu::TestLog::EndImageSet;
+
+			allOk = false;
+		}
+	}
+
+	return allOk;
+}
+
+class SampleMaskUniqueSetCase : public SampleMaskBaseCase
+{
+public:
+									SampleMaskUniqueSetCase		(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target, ShaderRunMode runMode);
+									~SampleMaskUniqueSetCase	(void);
+
+	void							init						(void);
+	void							deinit						(void);
+
+private:
+	enum
+	{
+		RENDER_SIZE = 64
+	};
+
+	void							preDraw						(void);
+	void							postDraw					(void);
+	std::string						genFragmentSource			(int numTargetSamples) const;
+	bool							verifySampleBuffers			(const std::vector<tcu::Surface>& resultBuffers);
+	std::string						getIterationDescription		(int iteration) const;
+
+	void							preTest						(void);
+	void							postTest					(void);
+
+	std::vector<tcu::Surface>		m_iterationSampleBuffers;
+};
+
+SampleMaskUniqueSetCase::SampleMaskUniqueSetCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target, ShaderRunMode runMode)
+	: SampleMaskBaseCase(context, name, desc, sampleCount, target, RENDER_SIZE, runMode, MultisampleShaderRenderUtil::MultisampleRenderCase::FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS)
+{
+	DE_ASSERT(runMode == RUN_PER_TWO_SAMPLES);
+	DE_ASSERT(target == TARGET_TEXTURE);
+
+	// high and low bits
+	m_numIterations = 2;
+}
+
+SampleMaskUniqueSetCase::~SampleMaskUniqueSetCase (void)
+{
+}
+
+void SampleMaskUniqueSetCase::init (void)
+{
+	// log the test method and expectations
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying gl_SampleMaskIn.\n"
+		<< "	Fragment shader may be invoked [ceil(numSamples/2), numSamples] times.\n"
+		<< "	=> Each invocation should have unique bit set\n"
+		<< "	Writing highest and lowest bit index to color channels in render shader. Verifying:\n"
+		<< "		1) no other invocation contains these bits in sampler shader.\n"
+		<< "		2) number of invocations is at least ceil(numSamples/2).\n"
+		<< tcu::TestLog::EndMessage;
+
+	SampleMaskBaseCase::init();
+}
+
+void SampleMaskUniqueSetCase::deinit (void)
+{
+	m_iterationSampleBuffers.clear();
+}
+
+void SampleMaskUniqueSetCase::preDraw (void)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	const int				selectorLoc	= gl.getUniformLocation(m_program->getProgram(), "u_bitSelector");
+
+	gl.uniform1ui(selectorLoc, (deUint32)m_iteration);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set u_bitSelector");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Setting u_bitSelector = " << m_iteration << tcu::TestLog::EndMessage;
+
+	SampleMaskBaseCase::preDraw();
+}
+
+void SampleMaskUniqueSetCase::postDraw (void)
+{
+	SampleMaskBaseCase::postDraw();
+}
+
+std::string SampleMaskUniqueSetCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_ASSERT(numTargetSamples != 0);
+
+	std::ostringstream buf;
+
+	// test supports only one sample mask word
+	if (numTargetSamples > 32)
+		throw tcu::NotSupportedError("Sample count larger than 32 is not supported.");
+
+	// output min and max sample id
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_sample_variables : require\n"
+			"uniform highp uint u_bitSelector;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	highp int selectedBits;\n"
+			"	if (u_bitSelector == 0u)\n"
+			"		selectedBits = (gl_SampleMaskIn[0] & 0xFFFF);\n"
+			"	else\n"
+			"		selectedBits = ((gl_SampleMaskIn[0] >> 16) & 0xFFFF);\n"
+			"\n"
+			"	// encode bits to color\n"
+			"	highp int redBits = selectedBits & 31;\n"
+			"	highp int greenBits = (selectedBits >> 5) & 63;\n"
+			"	highp int blueBits = (selectedBits >> 11) & 31;\n"
+			"\n"
+			"	fragColor = vec4(float(redBits) / float(31), float(greenBits) / float(63), float(blueBits) / float(31), 1.0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+bool SampleMaskUniqueSetCase::verifySampleBuffers (const std::vector<tcu::Surface>& resultBuffers)
+{
+	// we need results from all passes to do verification. Store results and verify later (at postTest).
+
+	DE_ASSERT(m_numTargetSamples == (int)resultBuffers.size());
+	for (int ndx = 0; ndx < m_numTargetSamples; ++ndx)
+		m_iterationSampleBuffers[m_iteration * m_numTargetSamples + ndx] = resultBuffers[ndx];
+
+	return true;
+}
+
+std::string SampleMaskUniqueSetCase::getIterationDescription (int iteration) const
+{
+	if (iteration == 0)
+		return "Reading low bits";
+	else if (iteration == 1)
+		return "Reading high bits";
+	else
+		DE_ASSERT(false);
+	return "";
+}
+
+void SampleMaskUniqueSetCase::preTest (void)
+{
+	m_iterationSampleBuffers.resize(m_numTargetSamples * 2);
+}
+
+void SampleMaskUniqueSetCase::postTest (void)
+{
+	DE_ASSERT((m_iterationSampleBuffers.size() % 2) == 0);
+	DE_ASSERT((int)m_iterationSampleBuffers.size() / 2 == m_numTargetSamples);
+
+	const int						width			= m_iterationSampleBuffers[0].getWidth();
+	const int						height			= m_iterationSampleBuffers[0].getHeight();
+	bool							allOk			= true;
+	std::vector<tcu::TextureLevel>	sampleCoverage	(m_numTargetSamples);
+	const tcu::ScopedLogSection		section			(m_testCtx.getLog(), "Verify", "Verify masks");
+
+	// convert color layers to 32 bit coverage masks, 2 passes per coverage
+
+	for (int sampleNdx = 0; sampleNdx < (int)sampleCoverage.size(); ++sampleNdx)
+	{
+		sampleCoverage[sampleNdx].setStorage(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT32), width, height);
+
+		for (int y = 0; y < height; ++y)
+		for (int x = 0; x < width; ++x)
+		{
+			const tcu::RGBA		lowColor	= m_iterationSampleBuffers[sampleNdx].getPixel(x, y);
+			const tcu::RGBA		highColor	= m_iterationSampleBuffers[sampleNdx + (int)sampleCoverage.size()].getPixel(x, y);
+			deUint16			low;
+			deUint16			high;
+
+			{
+				int redBits		= (int)deFloatRound(lowColor.getRed() / 255.0f * 31);
+				int greenBits	= (int)deFloatRound(lowColor.getGreen() / 255.0f * 63);
+				int blueBits	= (int)deFloatRound(lowColor.getBlue() / 255.0f * 31);
+
+				low = (deUint16)(redBits | (greenBits << 5) | (blueBits << 11));
+			}
+			{
+				int redBits		= (int)deFloatRound(highColor.getRed() / 255.0f * 31);
+				int greenBits	= (int)deFloatRound(highColor.getGreen() / 255.0f * 63);
+				int blueBits	= (int)deFloatRound(highColor.getBlue() / 255.0f * 31);
+
+				high = (deUint16)(redBits | (greenBits << 5) | (blueBits << 11));
+			}
+
+			sampleCoverage[sampleNdx].getAccess().setPixel(tcu::UVec4((((deUint32)high) << 16) | low, 0, 0, 0), x, y);
+		}
+	}
+
+	// verify masks
+
+	if (m_numRequestedSamples == 0)
+	{
+		// single sample target, expect mask = 0x01
+		const int	printFloodLimit	= 5;
+		int			printCount		= 0;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample mask is 0x00000001." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < height; ++y)
+		for (int x = 0; x < width; ++x)
+		{
+			deUint32 mask = sampleCoverage[0].getAccess().getPixelUint(x, y).x();
+			if (mask != 0x01)
+			{
+				allOk = false;
+
+				if (++printCount <= printFloodLimit)
+				{
+					m_testCtx.getLog()
+						<< tcu::TestLog::Message
+						<< "Pixel (" << x << ", " << y << "): Invalid mask, got " << tcu::Format::Hex<8>(mask) << ", expected " << tcu::Format::Hex<8>(0x01) << "\n"
+						<< tcu::TestLog::EndMessage;
+				}
+			}
+		}
+
+		if (!allOk && printCount > printFloodLimit)
+		{
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "...\n"
+				<< "Omitted " << (printCount-printFloodLimit) << " error descriptions."
+				<< tcu::TestLog::EndMessage;
+		}
+	}
+	else
+	{
+		// check uniqueness
+		{
+			bool		uniquenessOk	= true;
+			int			printCount		= 0;
+			const int	printFloodLimit	= 5;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Verifying invocation sample masks do not share bits." << tcu::TestLog::EndMessage;
+
+			for (int y = 0; y < height; ++y)
+			for (int x = 0; x < width; ++x)
+			{
+				bool maskBitsNotUnique = false;
+
+				for (int sampleNdxA = 0;            sampleNdxA < m_numTargetSamples && (!maskBitsNotUnique || printCount < printFloodLimit); ++sampleNdxA)
+				for (int sampleNdxB = sampleNdxA+1; sampleNdxB < m_numTargetSamples && (!maskBitsNotUnique || printCount < printFloodLimit); ++sampleNdxB)
+				{
+					const deUint32 maskA = sampleCoverage[sampleNdxA].getAccess().getPixelUint(x, y).x();
+					const deUint32 maskB = sampleCoverage[sampleNdxB].getAccess().getPixelUint(x, y).x();
+
+					// equal mask == emitted by the same invocation
+					if (maskA != maskB)
+					{
+						// shares samples?
+						if (maskA & maskB)
+						{
+							maskBitsNotUnique = true;
+							uniquenessOk = false;
+
+							if (++printCount <= printFloodLimit)
+							{
+								m_testCtx.getLog()
+									<< tcu::TestLog::Message
+									<< "Pixel (" << x << ", " << y << "):\n"
+									<< "\tSamples " << sampleNdxA << " and " << sampleNdxB << " share mask bits\n"
+									<< "\tMask" << sampleNdxA << " = " << tcu::Format::Hex<8>(maskA) << "\n"
+									<< "\tMask" << sampleNdxB << " = " << tcu::Format::Hex<8>(maskB) << "\n"
+									<< tcu::TestLog::EndMessage;
+							}
+						}
+					}
+				}
+			}
+
+			if (!uniquenessOk)
+			{
+				allOk = false;
+
+				if (printCount > printFloodLimit)
+					m_testCtx.getLog()
+						<< tcu::TestLog::Message
+						<< "...\n"
+						<< "Omitted " << (printCount-printFloodLimit) << " error descriptions."
+						<< tcu::TestLog::EndMessage;
+			}
+		}
+
+		// check number of sample mask bit groups is valid ( == number of invocations )
+		{
+			const deUint32			minNumInvocations	= (deUint32)de::max(1, (m_numTargetSamples+1)/2);
+			bool					countOk				= true;
+			int						printCount			= 0;
+			const int				printFloodLimit		= 5;
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Verifying cardinality of separate sample mask bit sets. Expecting equal to the number of invocations, (greater or equal to " << minNumInvocations << ")" << tcu::TestLog::EndMessage;
+
+			for (int y = 0; y < height; ++y)
+			for (int x = 0; x < width; ++x)
+			{
+				std::set<deUint32> masks;
+
+				for (int maskNdx = 0; maskNdx < m_numTargetSamples; ++maskNdx)
+				{
+					const deUint32 mask = sampleCoverage[maskNdx].getAccess().getPixelUint(x, y).x();
+					masks.insert(mask);
+				}
+
+				if ((int)masks.size() < (int)minNumInvocations)
+				{
+					if (++printCount <= printFloodLimit)
+					{
+						m_testCtx.getLog()
+							<< tcu::TestLog::Message
+							<< "Pixel (" << x << ", " << y << "): Pixel invocations had only " << (int)masks.size() << " separate mask sets. Expected " << minNumInvocations << " or more. Found masks:"
+							<< tcu::TestLog::EndMessage;
+
+						for (std::set<deUint32>::iterator it = masks.begin(); it != masks.end(); ++it)
+							m_testCtx.getLog()
+							<< tcu::TestLog::Message
+							<< "\tMask: " << tcu::Format::Hex<8>(*it) << "\n"
+							<< tcu::TestLog::EndMessage;
+					}
+
+					countOk = false;
+				}
+			}
+
+			if (!countOk)
+			{
+				allOk = false;
+
+				if (printCount > printFloodLimit)
+					m_testCtx.getLog()
+						<< tcu::TestLog::Message
+						<< "...\n"
+						<< "Omitted " << (printCount-printFloodLimit) << " error descriptions."
+						<< tcu::TestLog::EndMessage;
+			}
+		}
+	}
+
+	if (!allOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+}
+
+class SampleMaskWriteCase : public SampleMaskBaseCase
+{
+public:
+	enum TestMode
+	{
+		TEST_DISCARD = 0,
+		TEST_INVERSE,
+
+		TEST_LAST
+	};
+						SampleMaskWriteCase			(Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target, ShaderRunMode runMode, TestMode testMode);
+						~SampleMaskWriteCase		(void);
+
+	void				init						(void);
+	void				preDraw						(void);
+	void				postDraw					(void);
+
+private:
+	enum
+	{
+		RENDER_SIZE = 64
+	};
+
+	std::string			genFragmentSource			(int numTargetSamples) const;
+	bool				verifyImage					(const tcu::Surface& resultImage);
+
+	const TestMode		m_testMode;
+};
+
+SampleMaskWriteCase::SampleMaskWriteCase (Context& context, const char* name, const char* desc, int sampleCount, RenderTarget target, ShaderRunMode runMode, TestMode testMode)
+	: SampleMaskBaseCase	(context, name, desc, sampleCount, target, RENDER_SIZE, runMode)
+	, m_testMode			(testMode)
+{
+	DE_ASSERT(testMode < TEST_LAST);
+}
+
+SampleMaskWriteCase::~SampleMaskWriteCase (void)
+{
+}
+
+void SampleMaskWriteCase::init (void)
+{
+	// log the test method and expectations
+	if (m_testMode == TEST_DISCARD)
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Discarding half of the samples using gl_SampleMask, expecting:\n"
+			<< "	1) half intensity on multisample targets (numSamples > 1)\n"
+			<< "	2) full discard on multisample targets (numSamples == 1)\n"
+			<< "	3) full intensity (no discard) on singlesample targets. (Mask is only applied as a multisample operation.)\n"
+			<< tcu::TestLog::EndMessage;
+	else if (m_testMode == TEST_INVERSE)
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Discarding half of the samples using GL_SAMPLE_MASK, setting inverse mask in fragment shader using gl_SampleMask, expecting:\n"
+			<< "	1) full discard on multisample targets (mask & modifiedCoverge == 0)\n"
+			<< "	2) full intensity (no discard) on singlesample targets. (Mask and coverage is only applied as a multisample operation.)\n"
+			<< tcu::TestLog::EndMessage;
+	else
+		DE_ASSERT(false);
+
+	SampleMaskBaseCase::init();
+}
+
+void SampleMaskWriteCase::preDraw (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_testMode == TEST_INVERSE)
+	{
+		// set mask to 0xAAAA.., set inverse mask bit coverage in shader
+
+		const int		maskLoc	= gl.getUniformLocation(m_program->getProgram(), "u_mask");
+		const deUint32	mask	= (deUint32)0xAAAAAAAAUL;
+
+		if (maskLoc == -1)
+			throw tcu::TestError("Location of u_mask was -1");
+
+		gl.enable(GL_SAMPLE_MASK);
+		gl.sampleMaski(0, mask);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set mask");
+
+		gl.uniform1ui(maskLoc, mask);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set mask uniform");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting sample mask " << tcu::Format::Hex<4>(mask) << tcu::TestLog::EndMessage;
+	}
+
+	SampleMaskBaseCase::preDraw();
+}
+
+void SampleMaskWriteCase::postDraw (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_testMode == TEST_INVERSE)
+	{
+		const deUint32 fullMask	= (1U << m_numTargetSamples) - 1;
+
+		gl.disable(GL_SAMPLE_MASK);
+		gl.sampleMaski(0, fullMask);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set mask");
+	}
+
+	SampleMaskBaseCase::postDraw();
+}
+
+std::string SampleMaskWriteCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_ASSERT(numTargetSamples != 0);
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream	buf;
+
+	if (m_testMode == TEST_DISCARD)
+	{
+		// mask out every other coverage bit
+
+		buf <<	"#version 310 es\n"
+				"#extension GL_OES_sample_variables : require\n"
+				"layout(location = 0) out mediump vec4 fragColor;\n"
+				"void main (void)\n"
+				"{\n"
+				"	for (int i = 0; i < gl_SampleMask.length(); ++i)\n"
+				"		gl_SampleMask[i] = int(0xAAAAAAAA);\n"
+				"\n";
+
+		if (m_runMode == RUN_PER_SAMPLE)
+			buf <<	"	// force per-sample shading\n"
+					"	highp float blue = float(gl_SampleID);\n"
+					"\n"
+					"	fragColor = vec4(0.0, 1.0, blue, 1.0);\n"
+					"}\n";
+		else
+			buf <<	"	fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+					"}\n";
+	}
+	else if (m_testMode == TEST_INVERSE)
+	{
+		// inverse every coverage bit
+
+		buf <<	"#version 310 es\n"
+				"#extension GL_OES_sample_variables : require\n"
+				"layout(location = 0) out mediump vec4 fragColor;\n"
+				"uniform highp uint u_mask;\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_SampleMask[0] = int(~u_mask);\n"
+				"\n";
+
+		if (m_runMode == RUN_PER_SAMPLE)
+			buf <<	"	// force per-sample shading\n"
+					"	highp float blue = float(gl_SampleID);\n"
+					"\n"
+					"	fragColor = vec4(0.0, 1.0, blue, 1.0);\n"
+					"}\n";
+		else
+			buf <<	"	fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+					"}\n";
+	}
+	else
+		DE_ASSERT(false);
+
+	return buf.str();
+}
+
+bool SampleMaskWriteCase::verifyImage (const tcu::Surface& resultImage)
+{
+	const bool singleSampleTarget = m_numRequestedSamples == 0 && !(m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() > 1);
+
+	if (m_testMode == TEST_DISCARD)
+	{
+		if (singleSampleTarget)
+		{
+			// single sample case => multisample operations are not effective => don't discard anything
+			// expect green
+			return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), ColorVerifier(tcu::Vec3(0.0f, 1.0f, 0.0f)));
+		}
+		else if (m_numTargetSamples == 1)
+		{
+			// total discard, expect black
+			return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), ColorVerifier(tcu::Vec3(0.0f, 0.0f, 0.0f)));
+		}
+		else
+		{
+			// partial discard, expect something between black and green
+			return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), PartialDiscardVerifier());
+		}
+	}
+	else if (m_testMode == TEST_INVERSE)
+	{
+		if (singleSampleTarget)
+		{
+			// single sample case => multisample operations are not effective => don't discard anything
+			// expect green
+			return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), ColorVerifier(tcu::Vec3(0.0f, 1.0f, 0.0f)));
+		}
+		else
+		{
+			// total discard, expect black
+			return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), ColorVerifier(tcu::Vec3(0.0f, 0.0f, 0.0f)));
+		}
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return false;
+	}
+}
+
+} // anonymous
+
+SampleVariableTests::SampleVariableTests (Context& context)
+	: TestCaseGroup(context, "sample_variables", "Test sample variables")
+{
+}
+
+SampleVariableTests::~SampleVariableTests (void)
+{
+}
+
+void SampleVariableTests::init (void)
+{
+	tcu::TestCaseGroup* const numSampleGroup	= new tcu::TestCaseGroup(m_testCtx,	"num_samples",		"Test NumSamples");
+	tcu::TestCaseGroup* const maxSampleGroup	= new tcu::TestCaseGroup(m_testCtx,	"max_samples",		"Test MaxSamples");
+	tcu::TestCaseGroup* const sampleIDGroup		= new tcu::TestCaseGroup(m_testCtx,	"sample_id",		"Test SampleID");
+	tcu::TestCaseGroup* const samplePosGroup	= new tcu::TestCaseGroup(m_testCtx,	"sample_pos",		"Test SamplePosition");
+	tcu::TestCaseGroup* const sampleMaskInGroup	= new tcu::TestCaseGroup(m_testCtx,	"sample_mask_in",	"Test SampleMaskIn");
+	tcu::TestCaseGroup* const sampleMaskGroup	= new tcu::TestCaseGroup(m_testCtx,	"sample_mask",		"Test SampleMask");
+
+	addChild(numSampleGroup);
+	addChild(maxSampleGroup);
+	addChild(sampleIDGroup);
+	addChild(samplePosGroup);
+	addChild(sampleMaskInGroup);
+	addChild(sampleMaskGroup);
+
+	static const struct RenderTarget
+	{
+		const char*							name;
+		const char*							desc;
+		int									numSamples;
+		MultisampleRenderCase::RenderTarget	target;
+	} targets[] =
+	{
+		{ "default_framebuffer",		"Test with default framebuffer",	0,	MultisampleRenderCase::TARGET_DEFAULT		},
+		{ "singlesample_texture",		"Test with singlesample texture",	0,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "multisample_texture_1",		"Test with multisample texture",	1,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "multisample_texture_2",		"Test with multisample texture",	2,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "multisample_texture_4",		"Test with multisample texture",	4,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "multisample_texture_8",		"Test with multisample texture",	8,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "multisample_texture_16",		"Test with multisample texture",	16,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "singlesample_rbo",			"Test with singlesample rbo",		0,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+		{ "multisample_rbo_1",			"Test with multisample rbo",		1,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+		{ "multisample_rbo_2",			"Test with multisample rbo",		2,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+		{ "multisample_rbo_4",			"Test with multisample rbo",		4,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+		{ "multisample_rbo_8",			"Test with multisample rbo",		8,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+		{ "multisample_rbo_16",			"Test with multisample rbo",		16,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+	};
+
+	// .num_samples
+	{
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+			numSampleGroup->addChild(new NumSamplesCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target));
+	}
+
+	// .max_samples
+	{
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+			maxSampleGroup->addChild(new MaxSamplesCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target));
+	}
+
+	// .sample_ID
+	{
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+			sampleIDGroup->addChild(new SampleIDCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target));
+	}
+
+	// .sample_pos
+	{
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"correctness", "Test SamplePos correctness");
+			samplePosGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SamplePosCorrectnessCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target));
+		}
+
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"distribution", "Test SamplePos distribution");
+			samplePosGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SamplePosDistributionCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target));
+		}
+	}
+
+	// .sample_mask_in
+	{
+		// .sample_mask
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"sample_mask", "Test with GL_SAMPLE_MASK");
+			sampleMaskInGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SampleMaskCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target));
+		}
+		// .bit_count_per_pixel
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"bit_count_per_pixel", "Test number of coverage bits");
+			sampleMaskInGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SampleMaskCountCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskCountCase::RUN_PER_PIXEL));
+		}
+		// .bit_count_per_sample
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"bit_count_per_sample", "Test number of coverage bits");
+			sampleMaskInGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SampleMaskCountCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskCountCase::RUN_PER_SAMPLE));
+		}
+		// .bit_count_per_two_samples
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"bit_count_per_two_samples", "Test number of coverage bits");
+			sampleMaskInGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SampleMaskCountCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskCountCase::RUN_PER_TWO_SAMPLES));
+		}
+		// .bits_unique_per_sample
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"bits_unique_per_sample", "Test coverage bits");
+			sampleMaskInGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				if (targets[targetNdx].target == MultisampleRenderCase::TARGET_TEXTURE)
+					group->addChild(new SampleMaskUniqueCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskUniqueCase::RUN_PER_SAMPLE));
+		}
+		// .bits_unique_per_two_samples
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"bits_unique_per_two_samples", "Test coverage bits");
+			sampleMaskInGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				if (targets[targetNdx].target == MultisampleRenderCase::TARGET_TEXTURE)
+					group->addChild(new SampleMaskUniqueSetCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskUniqueCase::RUN_PER_TWO_SAMPLES));
+		}
+	}
+
+	// .sample_mask
+	{
+		// .discard_half_per_pixel
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"discard_half_per_pixel", "Test coverage bits");
+			sampleMaskGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SampleMaskWriteCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskWriteCase::RUN_PER_PIXEL, SampleMaskWriteCase::TEST_DISCARD));
+		}
+		// .discard_half_per_sample
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"discard_half_per_sample", "Test coverage bits");
+			sampleMaskGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SampleMaskWriteCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskWriteCase::RUN_PER_SAMPLE, SampleMaskWriteCase::TEST_DISCARD));
+		}
+		// .discard_half_per_two_samples
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"discard_half_per_two_samples", "Test coverage bits");
+			sampleMaskGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SampleMaskWriteCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskWriteCase::RUN_PER_TWO_SAMPLES, SampleMaskWriteCase::TEST_DISCARD));
+		}
+
+		// .discard_half_per_two_samples
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"inverse_per_pixel", "Test coverage bits");
+			sampleMaskGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SampleMaskWriteCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskWriteCase::RUN_PER_PIXEL, SampleMaskWriteCase::TEST_INVERSE));
+		}
+		// .inverse_per_sample
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"inverse_per_sample", "Test coverage bits");
+			sampleMaskGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SampleMaskWriteCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskWriteCase::RUN_PER_SAMPLE, SampleMaskWriteCase::TEST_INVERSE));
+		}
+		// .inverse_per_two_samples
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx,	"inverse_per_two_samples", "Test coverage bits");
+			sampleMaskGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new SampleMaskWriteCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SampleMaskWriteCase::RUN_PER_TWO_SAMPLES, SampleMaskWriteCase::TEST_INVERSE));
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fSampleVariableTests.hpp b/modules/gles31/functional/es31fSampleVariableTests.hpp
new file mode 100644
index 0000000..5b60fc1
--- /dev/null
+++ b/modules/gles31/functional/es31fSampleVariableTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSAMPLEVARIABLETESTS_HPP
+#define _ES31FSAMPLEVARIABLETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sample variable tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class SampleVariableTests : public TestCaseGroup
+{
+public:
+							SampleVariableTests		(Context& context);
+							~SampleVariableTests	(void);
+
+	void					init					(void);
+
+private:
+							SampleVariableTests		(const SampleVariableTests& other);
+	SampleVariableTests&	operator=				(const SampleVariableTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSAMPLEVARIABLETESTS_HPP
diff --git a/modules/gles31/functional/es31fSeparateShaderTests.cpp b/modules/gles31/functional/es31fSeparateShaderTests.cpp
new file mode 100644
index 0000000..83a3d4b
--- /dev/null
+++ b/modules/gles31/functional/es31fSeparateShaderTests.cpp
@@ -0,0 +1,1706 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tests for separate shader objects
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fSeparateShaderTests.hpp"
+
+#include "deInt32.h"
+#include "deString.h"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+#include "deRandom.hpp"
+#include "deSTLUtil.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuSurface.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluVarType.hpp"
+#include "glsShaderLibrary.hpp"
+#include "glwFunctions.hpp"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+#include <cstdarg>
+#include <algorithm>
+#include <map>
+#include <sstream>
+#include <string>
+#include <set>
+#include <vector>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+using std::map;
+using std::set;
+using std::ostringstream;
+using std::string;
+using std::vector;
+using de::MovePtr;
+using de::Random;
+using de::UniquePtr;
+using tcu::MessageBuilder;
+using tcu::RenderTarget;
+using tcu::StringTemplate;
+using tcu::Surface;
+using tcu::TestLog;
+using tcu::ResultCollector;
+using glu::CallLogWrapper;
+using glu::DataType;
+using glu::VariableDeclaration;
+using glu::Interpolation;
+using glu::Precision;
+using glu::Program;
+using glu::ProgramPipeline;
+using glu::ProgramSources;
+using glu::RenderContext;
+using glu::ShaderProgram;
+using glu::ShaderType;
+using glu::Storage;
+using glu::VarType;
+using glu::VertexSource;
+using glu::FragmentSource;
+using glu::ProgramSeparable;
+
+using namespace glw;
+
+#define LOG_CALL(CALL) do	\
+{							\
+	enableLogging(true);	\
+	CALL;					\
+	enableLogging(false);	\
+} while (deGetFalse())
+
+enum
+{
+	VIEWPORT_SIZE = 128
+};
+
+DataType randomType (Random& rnd)
+{
+	using namespace glu;
+
+	if (rnd.getInt(0, 7) == 0)
+	{
+		const int numCols = rnd.getInt(2, 4), numRows = rnd.getInt(2, 4);
+
+		return getDataTypeMatrix(numCols, numRows);
+	}
+	else
+	{
+		static const DataType	s_types[]	= { TYPE_FLOAT,	TYPE_INT,	TYPE_UINT	};
+		static const float		s_weights[] = { 3.0,		1.0,		1.0			};
+		const int				size		= rnd.getInt(1, 4);
+		const DataType			scalarType	= rnd.chooseWeighted<DataType>(
+			DE_ARRAY_BEGIN(s_types), DE_ARRAY_END(s_types), DE_ARRAY_BEGIN(s_weights));
+		return getDataTypeVector(scalarType, size);
+	}
+
+	DE_ASSERT(!"Impossible");
+	return TYPE_INVALID;
+}
+
+Interpolation randomInterpolation (Random& rnd)
+{
+	return Interpolation(rnd.getInt(0, glu::INTERPOLATION_LAST - 1));
+}
+
+enum BindingKind
+{
+	BINDING_NAME,
+	BINDING_LOCATION,
+	BINDING_LAST
+};
+
+BindingKind randomBinding (Random& rnd)
+{
+	return rnd.getBool() ? BINDING_LOCATION : BINDING_NAME;
+}
+
+void printInputColor (ostringstream& oss, const VariableDeclaration& input)
+{
+	using namespace glu;
+
+	const DataType	basicType	= input.varType.getBasicType();
+	string			exp			= input.name;
+
+	switch (getDataTypeScalarType(basicType))
+	{
+		case TYPE_FLOAT:
+			break;
+
+		case TYPE_INT:
+		case TYPE_UINT:
+		{
+			DataType floatType = getDataTypeFloatScalars(basicType);
+			exp = string() + "(" + getDataTypeName(floatType) + "(" + exp + ") / 255.0" + ")";
+			break;
+		}
+
+		default:
+			DE_ASSERT(!"Impossible");
+	}
+
+	if (isDataTypeScalarOrVector(basicType))
+	{
+		switch (getDataTypeScalarSize(basicType))
+		{
+			case 1:
+				oss << "hsv(vec3(" << exp << ", 1.0, 1.0))";
+				break;
+			case 2:
+				oss << "hsv(vec3(" << exp << ", 1.0))";
+				break;
+			case 3:
+				oss << "vec4(" << exp << ", 1.0)";
+				break;
+			case 4:
+				oss << exp;
+				break;
+			default:
+				DE_ASSERT(!"Impossible");
+		}
+	}
+	else if (isDataTypeMatrix(basicType))
+	{
+		int	rows	= getDataTypeMatrixNumRows(basicType);
+		int	columns	= getDataTypeMatrixNumColumns(basicType);
+
+		if (rows == columns)
+			oss << "hsv(vec3(determinant(" << exp << ")))";
+		else
+		{
+			if (rows != 3 && columns >= 3)
+			{
+				exp = "transpose(" + exp + ")";
+				std::swap(rows, columns);
+			}
+			exp = exp + "[0]";
+			if (rows > 3)
+				exp = exp + ".xyz";
+			oss << "hsv(" << exp << ")";
+		}
+	}
+	else
+		DE_ASSERT(!"Impossible");
+}
+
+// Representation for the varyings between vertex and fragment shaders
+
+struct VaryingParams
+{
+	VaryingParams 			(void)
+		: count				(0)
+		, type				(glu::TYPE_LAST)
+		, binding			(BINDING_LAST)
+		, vtxInterp			(glu::INTERPOLATION_LAST)
+		, frgInterp			(glu::INTERPOLATION_LAST) {}
+
+	int						count;
+	DataType				type;
+	BindingKind				binding;
+	Interpolation			vtxInterp;
+	Interpolation			frgInterp;
+};
+
+struct VaryingInterface
+{
+	vector<VariableDeclaration>	vtxOutputs;
+	vector<VariableDeclaration>	frgInputs;
+};
+
+// Generate corresponding input and output variable declarations that may vary
+// in compatible ways.
+
+Interpolation chooseInterpolation (Interpolation param, DataType type, Random& rnd)
+{
+	if (glu::getDataTypeScalarType(type) != glu::TYPE_FLOAT)
+		return glu::INTERPOLATION_FLAT;
+
+	if (param == glu::INTERPOLATION_LAST)
+		return randomInterpolation(rnd);
+
+	return param;
+}
+
+VaryingInterface genVaryingInterface (const VaryingParams&		params,
+									  Random&					rnd)
+{
+	using namespace	glu;
+
+	VaryingInterface	ret;
+	int					offset = 0;
+
+	for (int varNdx = 0; varNdx < params.count; ++varNdx)
+	{
+		const BindingKind	binding			= ((params.binding == BINDING_LAST)
+											   ? randomBinding(rnd) : params.binding);
+		const DataType		type			= ((params.type == TYPE_LAST)
+											   ? randomType(rnd) : params.type);
+		const Interpolation	vtxInterp		= chooseInterpolation(params.vtxInterp, type, rnd);
+		const Interpolation	frgInterp		= chooseInterpolation(params.frgInterp, type, rnd);
+		const int			loc				= ((binding == BINDING_LOCATION) ? offset : -1);
+		const string		ndxStr			= de::toString(varNdx);
+		const string		vtxName			= ((binding == BINDING_NAME)
+											   ? "var" + ndxStr : "vtxVar" + ndxStr);
+		const string		frgName			= ((binding == BINDING_NAME)
+											   ? "var" + ndxStr : "frgVar" + ndxStr);
+		const VarType		varType			(type, PRECISION_HIGHP);
+
+		offset += getDataTypeNumLocations(type);
+
+		// Over 16 locations aren't necessarily supported, so halt here.
+		if (offset > 16)
+			break;
+
+		ret.vtxOutputs.push_back(
+			VariableDeclaration(varType, vtxName, STORAGE_OUT, vtxInterp, loc));
+		ret.frgInputs.push_back(
+			VariableDeclaration(varType, frgName, STORAGE_IN, frgInterp, loc));
+	}
+
+	return ret;
+}
+
+// Create vertex output variable declarations that are maximally compatible
+// with the fragment input variables.
+
+vector<VariableDeclaration> varyingCompatVtxOutputs (const VaryingInterface& varyings)
+{
+	vector<VariableDeclaration> outputs = varyings.vtxOutputs;
+
+	for (size_t i = 0; i < outputs.size(); ++i)
+	{
+		outputs[i].interpolation = varyings.frgInputs[i].interpolation;
+		outputs[i].name = varyings.frgInputs[i].name;
+	}
+
+	return outputs;
+}
+
+// Shader source generation
+
+void printFloat (ostringstream& oss, double d)
+{
+	oss.setf(oss.fixed | oss.internal);
+	oss.precision(4);
+	oss.width(7);
+	oss << d;
+}
+
+void printFloatDeclaration (ostringstream&	oss,
+							const string&	varName,
+							bool			uniform,
+							GLfloat			value		= 0.0)
+{
+	using namespace glu;
+
+	const VarType	varType	(TYPE_FLOAT, PRECISION_HIGHP);
+
+	if (uniform)
+		oss << VariableDeclaration(varType, varName, STORAGE_UNIFORM) << ";\n";
+	else
+		oss << VariableDeclaration(varType, varName, STORAGE_CONST)
+			<< " = " << de::floatToString(value, 6) << ";\n";
+}
+
+void printRandomInitializer (ostringstream& oss, DataType type, Random& rnd)
+{
+	using namespace glu;
+	const int		size	= getDataTypeScalarSize(type);
+
+	if (size > 0)
+		oss << getDataTypeName(type) << "(";
+
+	for (int i = 0; i < size; ++i)
+	{
+		oss << (i == 0 ? "" : ", ");
+		switch (getDataTypeScalarType(type))
+		{
+			case TYPE_FLOAT:
+				printFloat(oss, rnd.getInt(0, 16) / 16.0);
+				break;
+
+			case TYPE_INT:
+			case TYPE_UINT:
+				oss << rnd.getInt(0, 255);
+				break;
+
+			case TYPE_BOOL:
+				oss << (rnd.getBool() ? "true" : "false");
+				break;
+
+			default:
+				DE_ASSERT(!"Impossible");
+		}
+	}
+
+	if (size > 0)
+		oss << ")";
+}
+
+string genVtxShaderSrc (deUint32							seed,
+						const vector<VariableDeclaration>&	outputs,
+						const string&						varName,
+						bool								uniform,
+						float								value = 0.0)
+{
+	ostringstream		oss;
+	Random				rnd								(seed);
+	enum { 				NUM_COMPONENTS 					= 2 };
+	static const int	s_quadrants[][NUM_COMPONENTS]	= { {1, 1}, {-1, 1}, {1, -1} };
+
+	oss << "#version 310 es\n";
+
+	oss << "const vec2 triangle[3] = vec2[3](\n";
+
+	for (int vertexNdx = 0; vertexNdx < DE_LENGTH_OF_ARRAY(s_quadrants); ++vertexNdx)
+	{
+		oss << "\tvec2(";
+
+		for (int componentNdx = 0; componentNdx < NUM_COMPONENTS; ++componentNdx)
+		{
+			printFloat(oss, s_quadrants[vertexNdx][componentNdx] * rnd.getInt(4,16) / 16.0);
+			oss << (componentNdx < 1 ? ", " : "");
+		}
+
+		oss << ")" << (vertexNdx < 2 ? "," : "") << "\n";
+	}
+	oss << ");\n";
+
+
+	for (vector<VariableDeclaration>::const_iterator it = outputs.begin();
+		 it != outputs.end(); ++it)
+	{
+		const DataType	type		= it->varType.getBasicType();
+		const string	typeName	= glu::getDataTypeName(type);
+
+		oss << "const " << typeName << " " << it->name << "Inits[3] = "
+			<< typeName << "[3](\n";
+		for (int i = 0; i < 3; ++i)
+		{
+			oss << (i == 0 ? "\t" : ",\n\t");
+			printRandomInitializer(oss, type, rnd);
+		}
+		oss << ");\n"
+			<< *it << ";\n";
+	}
+
+	printFloatDeclaration(oss, varName, uniform, value);
+
+	oss << "void main (void)\n"
+		<< "{\n"
+		<< "\tgl_Position = vec4(" << varName << " * triangle[gl_VertexID], 0.0, 1.0);\n";
+
+	for (vector<VariableDeclaration>::const_iterator it = outputs.begin();
+		 it != outputs.end(); ++it)
+		oss << "\t" << it->name << " = " << it->name << "Inits[gl_VertexID];\n";
+
+	oss << "}\n";
+
+	return oss.str();
+}
+
+string genFrgShaderSrc (deUint32							seed,
+						const vector<VariableDeclaration>&	inputs,
+						const string&						varName,
+						bool								uniform,
+						float								value = 0.0)
+{
+	Random				rnd		(seed);
+	ostringstream		oss;
+
+	oss.precision(4);
+	oss.width(7);
+	oss << "#version 310 es\n";
+
+	oss << "precision highp float;\n";;
+
+	oss << "out vec4 fragColor;\n";;
+
+	printFloatDeclaration(oss, varName, uniform, value);
+
+	for (vector<VariableDeclaration>::const_iterator it = inputs.begin();
+		 it != inputs.end(); ++it)
+		oss << *it << ";\n";
+
+	// glsl % isn't defined for negative numbers
+	oss << "int imod (int n, int d)" << "\n"
+		<< "{" << "\n"
+		<< "\t" << "return (n < 0 ? d - 1 - (-1 - n) % d : n % d);" << "\n"
+		<< "}" << "\n";
+
+	oss << "vec4 hsv (vec3 hsv)"
+		<< "{" << "\n"
+		<< "\tfloat h = hsv.x * 3.0;\n"
+		<< "\tfloat r = max(0.0, 1.0 - h) + max(0.0, h - 2.0);\n"
+		<< "\tfloat g = max(0.0, 1.0 - abs(h - 1.0));\n"
+		<< "\tfloat b = max(0.0, 1.0 - abs(h - 2.0));\n"
+		<< "\tvec3 hs = mix(vec3(1.0), vec3(r, g, b), hsv.y);\n"
+		<< "\treturn vec4(hsv.z * hs, 1.0);\n"
+		<< "}\n";
+
+	oss << "void main (void)\n"
+		<< "{\n";
+
+	oss << "\t" << "fragColor = vec4(vec3(" << varName << "), 1.0);" << "\n";
+
+	if (inputs.size() > 0)
+	{
+		oss << "\t" << "switch (imod(int(0.5 * (gl_FragCoord.x - gl_FragCoord.y)), "
+			<< inputs.size() << "))" << "\n"
+			<< "\t" << "{" << "\n";
+
+		for (size_t i = 0; i < inputs.size(); ++i)
+		{
+			oss << "\t\t" << "case " << i << ":" << "\n"
+				<< "\t\t\t" << "fragColor *= ";
+
+			printInputColor(oss, inputs[i]);
+
+			oss << ";" << "\n"
+				<< "\t\t\t" << "break;" << "\n";
+		}
+
+		oss << "\t\t" << "case " << inputs.size() << ":\n"
+			<< "\t\t\t" << "fragColor = vec4(1.0, 0.0, 1.0, 1.0);" << "\n";
+		oss << "\t\t\t" << "break;" << "\n";
+
+		oss << "\t\t" << "case -1:\n"
+			<< "\t\t\t" << "fragColor = vec4(1.0, 1.0, 0.0, 1.0);" << "\n";
+		oss << "\t\t\t" << "break;" << "\n";
+
+		oss << "\t\t" << "default:" << "\n"
+			<< "\t\t\t" << "fragColor = vec4(1.0, 1.0, 0.0, 1.0);" << "\n";
+
+		oss << "\t" << "}\n";
+
+	}
+
+	oss << "}\n";
+
+	return oss.str();
+}
+
+// ProgramWrapper
+
+class ProgramWrapper
+{
+public:
+	virtual 		~ProgramWrapper			(void) {}
+
+	virtual GLuint	getProgramName			(void) = 0;
+	virtual void	writeToLog				(TestLog& log) = 0;
+};
+
+class ShaderProgramWrapper : public ProgramWrapper
+{
+public:
+					ShaderProgramWrapper	(const RenderContext&	renderCtx,
+											 const ProgramSources&	sources)
+						: m_shaderProgram	(renderCtx, sources) {}
+					~ShaderProgramWrapper	(void) {}
+
+	GLuint			getProgramName			(void) { return m_shaderProgram.getProgram(); }
+	ShaderProgram&	getShaderProgram		(void) { return m_shaderProgram; }
+	void			writeToLog				(TestLog& log) { log << m_shaderProgram; }
+
+private:
+	ShaderProgram	m_shaderProgram;
+};
+
+class RawProgramWrapper : public ProgramWrapper
+{
+public:
+					RawProgramWrapper		(const RenderContext&	renderCtx,
+											 GLuint					programName,
+											 ShaderType				shaderType,
+											 const string&			source)
+						: m_program			(renderCtx, programName)
+						, m_shaderType		(shaderType)
+						, m_source			(source) {}
+					~RawProgramWrapper		(void) {}
+
+	GLuint			getProgramName			(void) { return m_program.getProgram(); }
+	Program&		getProgram				(void) { return m_program; }
+	void			writeToLog				(TestLog& log);
+
+private:
+	Program			m_program;
+	ShaderType		m_shaderType;
+	const string	m_source;
+};
+
+void RawProgramWrapper::writeToLog (TestLog& log)
+{
+	const string	info	= m_program.getInfoLog();
+	qpShaderType	qpType	= glu::getLogShaderType(m_shaderType);
+
+	log << TestLog::ShaderProgram(true, info)
+		<< TestLog::Shader(qpType, m_source,
+						   true, "[Shader created by glCreateShaderProgramv()]")
+		<< TestLog::EndShaderProgram;
+}
+
+// ProgramParams
+
+struct ProgramParams
+{
+	ProgramParams (deUint32 vtxSeed_, GLfloat vtxScale_, deUint32 frgSeed_, GLfloat frgScale_)
+		: vtxSeed	(vtxSeed_)
+		, vtxScale	(vtxScale_)
+		, frgSeed	(frgSeed_)
+		, frgScale	(frgScale_) {}
+	deUint32	vtxSeed;
+	GLfloat		vtxScale;
+	deUint32	frgSeed;
+	GLfloat		frgScale;
+};
+
+ProgramParams genProgramParams (Random& rnd)
+{
+	const deUint32	vtxSeed		= rnd.getUint32();
+	const GLfloat	vtxScale	= rnd.getInt(8, 16) / 16.0f;
+	const deUint32	frgSeed		= rnd.getUint32();
+	const GLfloat	frgScale	= rnd.getInt(0, 16) / 16.0f;
+
+	return ProgramParams(vtxSeed, vtxScale, frgSeed, frgScale);
+}
+
+// TestParams
+
+struct TestParams
+{
+	bool					initSingle;
+	bool					switchVtx;
+	bool					switchFrg;
+	bool					useUniform;
+	bool					useSameName;
+	bool					useCreateHelper;
+	bool					useProgramUniform;
+	VaryingParams			varyings;
+};
+
+deUint32 paramsSeed (const TestParams& params)
+{
+	deUint32 paramCode 	= (params.initSingle	 		<< 0 |
+						   params.switchVtx 			<< 1 |
+						   params.switchFrg				<< 2 |
+						   params.useUniform			<< 3 |
+						   params.useSameName			<< 4 |
+						   params.useCreateHelper		<< 5 |
+						   params.useProgramUniform		<< 6);
+
+	paramCode = deUint32Hash(paramCode) + params.varyings.count;
+	paramCode = deUint32Hash(paramCode) + params.varyings.type;
+	paramCode = deUint32Hash(paramCode) + params.varyings.binding;
+	paramCode = deUint32Hash(paramCode) + params.varyings.vtxInterp;
+	paramCode = deUint32Hash(paramCode) + params.varyings.frgInterp;
+
+	return deUint32Hash(paramCode);
+}
+
+string paramsCode (const TestParams& params)
+{
+	using namespace glu;
+
+	ostringstream oss;
+
+	oss << (params.initSingle ? "1" : "2")
+		<< (params.switchVtx ? "v" : "")
+		<< (params.switchFrg ? "f" : "")
+		<< (params.useProgramUniform ? "p" : "")
+		<< (params.useUniform ? "u" : "")
+		<< (params.useSameName ? "s" : "")
+		<< (params.useCreateHelper ? "c" : "")
+		 << de::toString(params.varyings.count)
+		 << (params.varyings.binding == BINDING_NAME ? "n" :
+			 params.varyings.binding == BINDING_LOCATION ? "l" :
+			 params.varyings.binding == BINDING_LAST ? "r" :
+			"")
+		 << (params.varyings.vtxInterp == INTERPOLATION_SMOOTH ? "m" :
+			 params.varyings.vtxInterp == INTERPOLATION_CENTROID ? "e" :
+			 params.varyings.vtxInterp == INTERPOLATION_FLAT ? "a" :
+			 params.varyings.vtxInterp == INTERPOLATION_LAST ? "r" :
+			"o")
+		 << (params.varyings.frgInterp == INTERPOLATION_SMOOTH ? "m" :
+			 params.varyings.frgInterp == INTERPOLATION_CENTROID ? "e" :
+			 params.varyings.frgInterp == INTERPOLATION_FLAT ? "a" :
+			 params.varyings.frgInterp == INTERPOLATION_LAST ? "r" :
+			"o");
+	return oss.str();
+}
+
+bool paramsValid (const TestParams& params)
+{
+	using namespace glu;
+
+	// Final pipeline has a single program?
+	if (params.initSingle)
+	{
+		// Cannot have conflicting names for uniforms or constants
+		if (params.useSameName)
+			return false;
+
+		// CreateShaderProgram would never get called
+		if (!params.switchVtx && !params.switchFrg && params.useCreateHelper)
+			return false;
+	}
+
+	// ProgramUniform would never get called
+	if (params.useProgramUniform && !params.useUniform)
+		return false;
+
+	// Interpolation is meaningless if we don't use an in/out variable.
+	if (params.varyings.count == 0 &&
+		!(params.varyings.vtxInterp == INTERPOLATION_LAST &&
+		  params.varyings.frgInterp == INTERPOLATION_LAST))
+		return false;
+
+	return true;
+}
+
+void logParams (TestLog& log, const TestParams& params)
+{
+	// We don't log operational details here since those are shown
+	// in the log messages during execution.
+	MessageBuilder msg = log.message();
+
+	msg << "Pipeline configuration:\n";
+
+	msg << "Vertex and fragment shaders have "
+		<< (params.useUniform ? "uniform" : "constant") << "s with "
+		<< (params.useSameName ? "the same name" : "different names") << ".\n";
+
+	if (params.varyings.count == 0)
+		msg << "There are no varyings.\n";
+	else
+	{
+		if (params.varyings.count == 1)
+			msg << "There is one varying.\n";
+		else
+			msg << "There are " << params.varyings.count << " varyings.\n";
+
+		if (params.varyings.type == glu::TYPE_LAST)
+			msg << "Varyings are of random types.\n";
+		else
+			msg << "Varyings are of type '"
+				<< glu::getDataTypeName(params.varyings.type) << "'.\n";
+
+		msg << "Varying outputs and inputs correspond ";
+		switch (params.varyings.binding)
+		{
+			case BINDING_NAME:
+				msg << "by name.\n";
+				break;
+			case BINDING_LOCATION:
+				msg << "by location.\n";
+				break;
+			case BINDING_LAST:
+				msg << "randomly either by name or by location.\n";
+				break;
+			default:
+				DE_ASSERT(!"Impossible");
+		}
+
+		msg << "In the vertex shader the varyings are qualified ";
+		if (params.varyings.vtxInterp == glu::INTERPOLATION_LAST)
+			msg << "with a random interpolation qualifier.\n";
+		else
+			msg << "'" << glu::getInterpolationName(params.varyings.vtxInterp) << "'.\n";
+
+		msg << "In the fragment shader the varyings are qualified ";
+		if (params.varyings.frgInterp == glu::INTERPOLATION_LAST)
+			msg << "with a random interpolation qualifier.\n";
+		else
+			msg << "'" << glu::getInterpolationName(params.varyings.frgInterp) << "'.\n";
+	}
+
+	msg << TestLog::EndMessage;
+
+	log.writeMessage("");
+}
+
+TestParams genParams (deUint32 seed)
+{
+	Random		rnd		(seed);
+	TestParams	params;
+
+	do
+	{
+		params.initSingle			= rnd.getBool();
+		params.switchVtx			= rnd.getBool();
+		params.switchFrg			= rnd.getBool();
+		params.useUniform			= rnd.getBool();
+		params.useProgramUniform	= params.useUniform && rnd.getBool();
+		params.useCreateHelper		= rnd.getBool();
+		params.useSameName			= rnd.getBool();
+		{
+			int i = rnd.getInt(-1, 3);
+			params.varyings.count = (i == -1 ? 0 : 1 << i);
+		}
+		if (params.varyings.count > 0)
+		{
+			params.varyings.type		= glu::TYPE_LAST;
+			params.varyings.binding		= BINDING_LAST;
+			params.varyings.vtxInterp	= glu::INTERPOLATION_LAST;
+			params.varyings.frgInterp	= glu::INTERPOLATION_LAST;
+		}
+		else
+		{
+			params.varyings.type		= glu::TYPE_INVALID;
+			params.varyings.binding		= BINDING_LAST;
+			params.varyings.vtxInterp	= glu::INTERPOLATION_LAST;
+			params.varyings.frgInterp	= glu::INTERPOLATION_LAST;
+		}
+	} while (!paramsValid(params));
+
+	return params;
+}
+
+// Program pipeline wrapper that retains references to component programs.
+
+struct Pipeline
+{
+								Pipeline			(MovePtr<ProgramPipeline>	pipeline_,
+													 MovePtr<ProgramWrapper>	fullProg_,
+													 MovePtr<ProgramWrapper>	vtxProg_,
+													 MovePtr<ProgramWrapper>	frgProg_)
+									: pipeline	(pipeline_)
+									, fullProg	(fullProg_)
+									, vtxProg	(vtxProg_)
+									, frgProg	(frgProg_) {}
+
+	ProgramWrapper&				getVertexProgram	(void) const
+	{
+		return vtxProg ? *vtxProg : *fullProg;
+	}
+
+	ProgramWrapper&				getFragmentProgram	(void) const
+	{
+		return frgProg ? *frgProg : *fullProg;
+	}
+
+	UniquePtr<ProgramPipeline>	pipeline;
+	UniquePtr<ProgramWrapper>	fullProg;
+	UniquePtr<ProgramWrapper>	vtxProg;
+	UniquePtr<ProgramWrapper>	frgProg;
+};
+
+void logPipeline(TestLog& log, const Pipeline& pipeline)
+{
+	ProgramWrapper&	vtxProg	= pipeline.getVertexProgram();
+	ProgramWrapper&	frgProg	= pipeline.getFragmentProgram();
+
+	log.writeMessage("// Failed program pipeline:");
+	if (&vtxProg == &frgProg)
+	{
+		log.writeMessage("// Common program for both vertex and fragment stages:");
+		vtxProg.writeToLog(log);
+	}
+	else
+	{
+		log.writeMessage("// Vertex stage program:");
+		vtxProg.writeToLog(log);
+		log.writeMessage("// Fragment stage program:");
+		frgProg.writeToLog(log);
+	}
+}
+
+// Rectangle
+
+struct Rectangle
+{
+			Rectangle	(int x_, int y_, int width_, int height_)
+				: x			(x_)
+				, y			(y_)
+				, width		(width_)
+				, height	(height_) {}
+	int	x;
+	int	y;
+	int	width;
+	int	height;
+};
+
+void setViewport (const RenderContext& renderCtx, const Rectangle& rect)
+{
+	renderCtx.getFunctions().viewport(rect.x, rect.y, rect.width, rect.height);
+}
+
+void readRectangle (const RenderContext& renderCtx, const Rectangle& rect, Surface& dst)
+{
+	dst.setSize(rect.width, rect.height);
+	glu::readPixels(renderCtx, rect.x, rect.y, dst.getAccess());
+}
+
+Rectangle randomViewport (const RenderContext& ctx, Random& rnd,
+						  GLint maxWidth, GLint maxHeight)
+{
+	const RenderTarget&	target	= ctx.getRenderTarget();
+	GLint				width	= de::min(target.getWidth(), maxWidth);
+	GLint				xOff	= rnd.getInt(0, target.getWidth() - width);
+	GLint				height	= de::min(target.getHeight(), maxHeight);
+	GLint				yOff	= rnd.getInt(0, target.getHeight() - height);
+
+	return Rectangle(xOff, yOff, width, height);
+}
+
+// SeparateShaderTest
+
+class SeparateShaderTest : public TestCase, private CallLogWrapper
+{
+public:
+	typedef	void			(SeparateShaderTest::*TestFunc)
+														(MovePtr<Pipeline>&		pipeOut);
+
+							SeparateShaderTest			(Context&				ctx,
+														 const string&			name,
+														 const string&			description,
+														 int					iterations,
+														 const TestParams&		params,
+														 TestFunc				testFunc);
+
+	IterateResult			iterate						(void);
+
+	void					testPipelineRendering		(MovePtr<Pipeline>&		pipeOut);
+	void					testCurrentProgPriority		(MovePtr<Pipeline>&		pipeOut);
+	void					testActiveProgramUniform	(MovePtr<Pipeline>&		pipeOut);
+	void					testPipelineQueryActive		(MovePtr<Pipeline>&		pipeOut);
+	void					testPipelineQueryPrograms	(MovePtr<Pipeline>&		pipeOut);
+
+private:
+	TestLog&				log							(void);
+	const RenderContext&	getRenderContext			(void);
+
+	void					setUniform					(ProgramWrapper&		program,
+														 const string&			uniformName,
+														 GLfloat				value,
+														 bool					useProgramUni);
+
+	void					drawSurface					(Surface&				dst,
+														 deUint32				seed = 0);
+
+	MovePtr<ProgramWrapper>	createShaderProgram			(const string*			vtxSource,
+														 const string*			frgSource,
+														 bool					separable);
+
+	MovePtr<ProgramWrapper>	createSingleShaderProgram	(ShaderType			shaderType,
+														 const string&			src);
+
+	MovePtr<Pipeline>		createPipeline				(const ProgramParams&	pp);
+
+	MovePtr<ProgramWrapper>	createReferenceProgram		(const ProgramParams&	pp);
+
+	int						m_iterations;
+	int						m_currentIteration;
+	TestParams				m_params;
+	TestFunc				m_testFunc;
+	Random					m_rnd;
+	ResultCollector			m_status;
+	VaryingInterface		m_varyings;
+
+	// Per-iteration state required for logging on exception
+	MovePtr<ProgramWrapper>	m_fullProg;
+	MovePtr<ProgramWrapper>	m_vtxProg;
+	MovePtr<ProgramWrapper>	m_frgProg;
+
+};
+
+const RenderContext& SeparateShaderTest::getRenderContext (void)
+{
+	return m_context.getRenderContext();
+}
+
+TestLog& SeparateShaderTest::log (void)
+{
+	return m_testCtx.getLog();
+}
+
+SeparateShaderTest::SeparateShaderTest (Context&			ctx,
+										const string&		name,
+										const string&		description,
+										int					iterations,
+										const TestParams&	params,
+										TestFunc			testFunc)
+	: TestCase			(ctx, name.c_str(), description.c_str())
+	, CallLogWrapper	(ctx.getRenderContext().getFunctions(), log())
+	, m_iterations		(iterations)
+	, m_currentIteration(0)
+	, m_params			(params)
+	, m_testFunc		(testFunc)
+	, m_rnd				(paramsSeed(params))
+	, m_status			(log(), "// ")
+	, m_varyings		(genVaryingInterface(params.varyings, m_rnd))
+{
+}
+
+MovePtr<ProgramWrapper> SeparateShaderTest::createShaderProgram (const string*	vtxSource,
+																 const string*	frgSource,
+																 bool			separable)
+{
+	ProgramSources sources;
+
+	if (vtxSource != DE_NULL)
+		sources << VertexSource(*vtxSource);
+	if (frgSource != DE_NULL)
+		sources << FragmentSource(*frgSource);
+	sources << ProgramSeparable(separable);
+
+	MovePtr<ShaderProgramWrapper> wrapper (new ShaderProgramWrapper(getRenderContext(),
+																	sources));
+	if (!wrapper->getShaderProgram().isOk())
+	{
+		log().writeMessage("Couldn't create shader program");
+		wrapper->writeToLog(log());
+		TCU_FAIL("Couldn't create shader program");
+	}
+
+	return MovePtr<ProgramWrapper>(wrapper.release());
+}
+
+MovePtr<ProgramWrapper> SeparateShaderTest::createSingleShaderProgram (ShaderType shaderType,
+																	   const string& src)
+{
+	const RenderContext&	renderCtx	= getRenderContext();
+
+	if (m_params.useCreateHelper)
+	{
+		const char*	const	srcStr		= src.c_str();
+		const GLenum		glType		= glu::getGLShaderType(shaderType);
+		const GLuint		programName	= glCreateShaderProgramv(glType, 1, &srcStr);
+
+		if (glGetError() != GL_NO_ERROR || programName == 0)
+		{
+			qpShaderType qpType = glu::getLogShaderType(shaderType);
+
+			log() << TestLog::Message << "glCreateShaderProgramv() failed"
+				  << TestLog::EndMessage
+				  << TestLog::ShaderProgram(false, "[glCreateShaderProgramv() failed]")
+				  << TestLog::Shader(qpType, src,
+									 false, "[glCreateShaderProgramv() failed]")
+				  << TestLog::EndShaderProgram;
+			TCU_FAIL("glCreateShaderProgramv() failed");
+		}
+
+		RawProgramWrapper* const	wrapper	= new RawProgramWrapper(renderCtx, programName,
+																	shaderType, src);
+		MovePtr<ProgramWrapper>		wrapperPtr(wrapper);
+		Program& 					program = wrapper->getProgram();
+
+		if (!program.getLinkStatus())
+		{
+			log().writeMessage("glCreateShaderProgramv() failed at linking");
+			wrapper->writeToLog(log());
+			TCU_FAIL("glCreateShaderProgram() failed at linking");
+		}
+		return wrapperPtr;
+	}
+	else
+	{
+		switch (shaderType)
+		{
+			case glu::SHADERTYPE_VERTEX:
+				return createShaderProgram(&src, DE_NULL, true);
+			case glu::SHADERTYPE_FRAGMENT:
+				return createShaderProgram(DE_NULL, &src, true);
+			default:
+				DE_ASSERT(!"Impossible case");
+		}
+	}
+	return MovePtr<ProgramWrapper>(); // Shut up compiler warnings.
+}
+
+void SeparateShaderTest::setUniform (ProgramWrapper&	program,
+									 const string&		uniformName,
+									 GLfloat			value,
+									 bool				useProgramUniform)
+{
+	const GLuint		progName	= program.getProgramName();
+	const GLint			location	= glGetUniformLocation(progName, uniformName.c_str());
+	MessageBuilder		msg			= log().message();
+
+	msg << "// Set program " << progName << "'s uniform '" << uniformName << "' to " << value;
+	if (useProgramUniform)
+	{
+		msg << " using glProgramUniform1f";
+		glProgramUniform1f(progName, location, value);
+	}
+	else
+	{
+		msg << " using glUseProgram and glUniform1f";
+		glUseProgram(progName);
+		glUniform1f(location, value);
+		glUseProgram(0);
+	}
+	msg << TestLog::EndMessage;
+}
+
+MovePtr<Pipeline> SeparateShaderTest::createPipeline(const ProgramParams& pp)
+{
+	const bool		useUniform	= m_params.useUniform;
+	const string	vtxName		= m_params.useSameName ? "scale" : "vtxScale";
+	const deUint32	initVtxSeed	= m_params.switchVtx ? m_rnd.getUint32() : pp.vtxSeed;
+
+	const string	frgName		= m_params.useSameName ? "scale" : "frgScale";
+	const deUint32	initFrgSeed	= m_params.switchFrg ? m_rnd.getUint32() : pp.frgSeed;
+	const string	frgSource	= genFrgShaderSrc(initFrgSeed, m_varyings.frgInputs,
+												  frgName, useUniform, pp.frgScale);
+
+	const RenderContext&		renderCtx	= getRenderContext();
+	MovePtr<ProgramPipeline>	pipeline	(new ProgramPipeline(renderCtx));
+	MovePtr<ProgramWrapper>		fullProg;
+	MovePtr<ProgramWrapper>		vtxProg;
+	MovePtr<ProgramWrapper>		frgProg;
+
+	// We cannot allow a situation where we have a single program with a
+	// single uniform, because then the vertex and fragment shader uniforms
+	// would not be distinct in the final pipeline, and we are going to test
+	// that altering one uniform will not affect the other.
+	DE_ASSERT(!(m_params.initSingle	&& m_params.useSameName &&
+				!m_params.switchVtx && !m_params.switchFrg));
+
+	if (m_params.initSingle)
+	{
+		string vtxSource = genVtxShaderSrc(initVtxSeed,
+										   varyingCompatVtxOutputs(m_varyings),
+										   vtxName, useUniform, pp.vtxScale);
+		fullProg = createShaderProgram(&vtxSource, &frgSource, true);
+		pipeline->useProgramStages(GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
+								   fullProg->getProgramName());
+		log() << TestLog::Message
+			  << "// Created pipeline " << pipeline->getPipeline()
+			  << " with two-shader program " << fullProg->getProgramName()
+			  << TestLog::EndMessage;
+	}
+	else
+	{
+		string vtxSource = genVtxShaderSrc(initVtxSeed, m_varyings.vtxOutputs,
+										   vtxName, useUniform, pp.vtxScale);
+		vtxProg = createSingleShaderProgram(glu::SHADERTYPE_VERTEX, vtxSource);
+		pipeline->useProgramStages(GL_VERTEX_SHADER_BIT, vtxProg->getProgramName());
+
+		frgProg = createSingleShaderProgram(glu::SHADERTYPE_FRAGMENT, frgSource);
+		pipeline->useProgramStages(GL_FRAGMENT_SHADER_BIT, frgProg->getProgramName());
+
+		log() << TestLog::Message
+			  << "// Created pipeline " << pipeline->getPipeline()
+			  << " with vertex program " << vtxProg->getProgramName()
+			  << " and fragment program " << frgProg->getProgramName()
+			  << TestLog::EndMessage;
+	}
+
+	m_status.check(pipeline->isValid(),
+				   "Pipeline is invalid after initialization");
+
+	if (m_params.switchVtx)
+	{
+		string newSource = genVtxShaderSrc(pp.vtxSeed, m_varyings.vtxOutputs,
+										   vtxName, useUniform, pp.vtxScale);
+		vtxProg = createSingleShaderProgram(glu::SHADERTYPE_VERTEX, newSource);
+		pipeline->useProgramStages(GL_VERTEX_SHADER_BIT, vtxProg->getProgramName());
+		log() << TestLog::Message
+			  << "// Switched pipeline " << pipeline->getPipeline()
+			  << "'s vertex stage to single-shader program " << vtxProg->getProgramName()
+			  << TestLog::EndMessage;
+	}
+	if (m_params.switchFrg)
+	{
+		string newSource = genFrgShaderSrc(pp.frgSeed, m_varyings.frgInputs,
+										   frgName, useUniform, pp.frgScale);
+		frgProg = createSingleShaderProgram(glu::SHADERTYPE_FRAGMENT, newSource);
+		pipeline->useProgramStages(GL_FRAGMENT_SHADER_BIT, frgProg->getProgramName());
+		log() << TestLog::Message
+			  << "// Switched pipeline " << pipeline->getPipeline()
+			  << "'s fragment stage to single-shader program " << frgProg->getProgramName()
+			  << TestLog::EndMessage;
+	}
+
+	if (m_params.switchVtx || m_params.switchFrg)
+		m_status.check(pipeline->isValid(),
+					   "Pipeline became invalid after changing a stage's program");
+
+	if (m_params.useUniform)
+	{
+		ProgramWrapper& vtxStage = *(vtxProg ? vtxProg : fullProg);
+		ProgramWrapper& frgStage = *(frgProg ? frgProg : fullProg);
+
+		setUniform(vtxStage, vtxName, pp.vtxScale, m_params.useProgramUniform);
+		setUniform(frgStage, frgName, pp.frgScale, m_params.useProgramUniform);
+	}
+	else
+		log().writeMessage("// Programs use constants instead of uniforms");
+
+	return MovePtr<Pipeline>(new Pipeline(pipeline, fullProg, vtxProg, frgProg));
+}
+
+MovePtr<ProgramWrapper> SeparateShaderTest::createReferenceProgram(const ProgramParams& pp)
+{
+	bool					useUniform	= m_params.useUniform;
+	const string			vtxSrc		= genVtxShaderSrc(pp.vtxSeed,
+														  varyingCompatVtxOutputs(m_varyings),
+														  "vtxScale", useUniform, pp.vtxScale);
+	const string			frgSrc		= genFrgShaderSrc(pp.frgSeed, m_varyings.frgInputs,
+														  "frgScale", useUniform, pp.frgScale);
+	MovePtr<ProgramWrapper>	program		= createShaderProgram(&vtxSrc, &frgSrc, false);
+	GLuint					progName	= program->getProgramName();
+
+	log() << TestLog::Message
+		  << "// Created monolithic shader program " << progName
+		  << TestLog::EndMessage;
+
+	if (useUniform)
+	{
+		setUniform(*program, "vtxScale", pp.vtxScale, false);
+		setUniform(*program, "frgScale", pp.frgScale, false);
+	}
+
+	return program;
+}
+
+void SeparateShaderTest::drawSurface (Surface& dst, deUint32 seed)
+{
+	const RenderContext&	renderCtx	= getRenderContext();
+	Random					rnd			(seed > 0 ? seed : m_rnd.getUint32());
+	Rectangle				viewport 	= randomViewport(renderCtx, rnd,
+														 VIEWPORT_SIZE, VIEWPORT_SIZE);
+	glClearColor(0.125f, 0.25f, 0.5f, 1.f);
+	setViewport(renderCtx, viewport);
+	glClear(GL_COLOR_BUFFER_BIT);
+	GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 3));
+	readRectangle(renderCtx, viewport, dst);
+	log().writeMessage("// Drew a triangle");
+}
+
+void SeparateShaderTest::testPipelineRendering (MovePtr<Pipeline>& pipeOut)
+{
+	ProgramParams				pp			= genProgramParams(m_rnd);
+	Pipeline&					pipeline	= *(pipeOut = createPipeline(pp));
+	GLuint						pipeName	= pipeline.pipeline->getPipeline();
+	UniquePtr<ProgramWrapper>	refProgram	(createReferenceProgram(pp));
+	GLuint						refProgName	= refProgram->getProgramName();
+	Surface						refSurface;
+	Surface						pipelineSurface;
+	GLuint						drawSeed	= m_rnd.getUint32();
+
+	glUseProgram(refProgName);
+	log() << TestLog::Message << "// Use program " << refProgName << TestLog::EndMessage;
+	drawSurface(refSurface, drawSeed);
+	glUseProgram(0);
+
+	glBindProgramPipeline(pipeName);
+	log() << TestLog::Message << "// Bind pipeline " << pipeName << TestLog::EndMessage;
+	drawSurface(pipelineSurface, drawSeed);
+	glBindProgramPipeline(0);
+
+	{
+		const bool result = tcu::fuzzyCompare(
+			m_testCtx.getLog(), "Program pipeline result",
+			"Result of comparing a program pipeline with a monolithic program",
+			refSurface, pipelineSurface, 0.05f, tcu::COMPARE_LOG_RESULT);
+
+		m_status.check(result, "Pipeline rendering differs from equivalent monolithic program");
+	}
+}
+
+void SeparateShaderTest::testCurrentProgPriority (MovePtr<Pipeline>& pipeOut)
+{
+	ProgramParams				pipePp		= genProgramParams(m_rnd);
+	ProgramParams				programPp	= genProgramParams(m_rnd);
+	Pipeline&					pipeline	= *(pipeOut = createPipeline(pipePp));
+	GLuint						pipeName	= pipeline.pipeline->getPipeline();
+	UniquePtr<ProgramWrapper>	program		(createReferenceProgram(programPp));
+	Surface						pipelineSurface;
+	Surface						refSurface;
+	Surface						resultSurface;
+	deUint32					drawSeed	= m_rnd.getUint32();
+
+	LOG_CALL(glBindProgramPipeline(pipeName));
+	drawSurface(pipelineSurface, drawSeed);
+	LOG_CALL(glBindProgramPipeline(0));
+
+	LOG_CALL(glUseProgram(program->getProgramName()));
+	drawSurface(refSurface, drawSeed);
+	LOG_CALL(glUseProgram(0));
+
+	LOG_CALL(glUseProgram(program->getProgramName()));
+	LOG_CALL(glBindProgramPipeline(pipeName));
+	drawSurface(resultSurface, drawSeed);
+	LOG_CALL(glBindProgramPipeline(0));
+	LOG_CALL(glUseProgram(0));
+
+	bool result = tcu::pixelThresholdCompare(
+		m_testCtx.getLog(), "Active program rendering result",
+		"Active program rendering result",
+		refSurface, resultSurface, tcu::RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);
+
+	m_status.check(result, "glBindProgramPipeline() affects glUseProgram()");
+	if (!result)
+		log() << TestLog::Image("Pipeline image", "Image produced by pipeline",
+								pipelineSurface);
+}
+
+void SeparateShaderTest::testActiveProgramUniform (MovePtr<Pipeline>& pipeOut)
+{
+	ProgramParams				refPp			= genProgramParams(m_rnd);
+	Surface						refSurface;
+	Surface						resultSurface;
+	deUint32					drawSeed		= m_rnd.getUint32();
+
+	DE_UNREF(pipeOut);
+	{
+		UniquePtr<ProgramWrapper>	refProg		(createReferenceProgram(refPp));
+		GLuint						refProgName	= refProg->getProgramName();
+
+		glUseProgram(refProgName);
+		log() << TestLog::Message << "// Use reference program " << refProgName
+			  << TestLog::EndMessage;
+		drawSurface(refSurface, drawSeed);
+		glUseProgram(0);
+	}
+
+	{
+		ProgramParams				changePp	= genProgramParams(m_rnd);
+		changePp.vtxSeed 						= refPp.vtxSeed;
+		changePp.frgSeed 						= refPp.frgSeed;
+		UniquePtr<ProgramWrapper>	changeProg	(createReferenceProgram(changePp));
+		GLuint						changeName	= changeProg->getProgramName();
+		ProgramPipeline				pipeline	(getRenderContext());
+		GLint						vtxLoc		= glGetUniformLocation(changeName, "vtxScale");
+		GLint						frgLoc		= glGetUniformLocation(changeName, "frgScale");
+
+		LOG_CALL(glBindProgramPipeline(pipeline.getPipeline()));
+
+		pipeline.activeShaderProgram(changeName);
+		log() << TestLog::Message << "// Set active shader program to " << changeName
+			  << TestLog::EndMessage;
+
+		glUniform1f(vtxLoc, refPp.vtxScale);
+		log() << TestLog::Message
+			  << "// Set uniform 'vtxScale' to " << refPp.vtxScale << " using glUniform1f"
+			  << TestLog::EndMessage;
+		glUniform1f(frgLoc, refPp.frgScale);
+		log() << TestLog::Message
+			  << "// Set uniform 'frgScale' to " << refPp.frgScale << " using glUniform1f"
+			  << TestLog::EndMessage;
+
+		pipeline.activeShaderProgram(0);
+		LOG_CALL(glBindProgramPipeline(0));
+
+		LOG_CALL(glUseProgram(changeName));
+		drawSurface(resultSurface, drawSeed);
+		LOG_CALL(glUseProgram(0));
+	}
+
+	bool result = tcu::fuzzyCompare(
+		m_testCtx.getLog(), "Active program uniform result",
+		"Active program uniform result",
+		refSurface, resultSurface, 0.05f, tcu::COMPARE_LOG_RESULT);
+
+	m_status.check(result,
+				   "glUniform() did not correctly modify "
+				   "the active program of the bound pipeline");
+}
+
+void SeparateShaderTest::testPipelineQueryPrograms (MovePtr<Pipeline>& pipeOut)
+{
+	ProgramParams				pipePp		= genProgramParams(m_rnd);
+	Pipeline&					pipeline	= *(pipeOut = createPipeline(pipePp));
+	GLuint						pipeName	= pipeline.pipeline->getPipeline();
+	GLint						queryVtx	= 0;
+	GLint						queryFrg	= 0;
+
+	LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_VERTEX_SHADER, &queryVtx)));
+	m_status.check(GLuint(queryVtx) == pipeline.getVertexProgram().getProgramName(),
+				   "Program pipeline query reported wrong vertex shader program");
+
+	LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_FRAGMENT_SHADER, &queryFrg)));
+	m_status.check(GLuint(queryFrg) == pipeline.getFragmentProgram().getProgramName(),
+				   "Program pipeline query reported wrong fragment shader program");
+}
+
+void SeparateShaderTest::testPipelineQueryActive (MovePtr<Pipeline>& pipeOut)
+{
+	ProgramParams				pipePp		= genProgramParams(m_rnd);
+	Pipeline&					pipeline	= *(pipeOut = createPipeline(pipePp));
+	GLuint						pipeName	= pipeline.pipeline->getPipeline();
+	GLuint						newActive	= pipeline.getVertexProgram().getProgramName();
+	GLint						queryActive	= 0;
+
+	LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_ACTIVE_PROGRAM, &queryActive)));
+	m_status.check(queryActive == 0,
+				   "Program pipeline query reported non-zero initial active program");
+
+	pipeline.pipeline->activeShaderProgram(newActive);
+	log() << TestLog::Message
+		  << "Set pipeline " << pipeName << "'s active shader program to " << newActive
+		  << TestLog::EndMessage;
+
+	LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_ACTIVE_PROGRAM, &queryActive)));
+	m_status.check(GLuint(queryActive) == newActive,
+				   "Program pipeline query reported incorrect active program");
+
+	pipeline.pipeline->activeShaderProgram(0);
+}
+
+TestCase::IterateResult SeparateShaderTest::iterate (void)
+{
+	MovePtr<Pipeline> pipeline;
+
+	DE_ASSERT(m_iterations > 0);
+
+	if (m_currentIteration == 0)
+		logParams(log(), m_params);
+
+	++m_currentIteration;
+
+	try
+	{
+		(this->*m_testFunc)(pipeline);
+		log().writeMessage("");
+	}
+	catch (const tcu::Exception&)
+	{
+		if (pipeline)
+			logPipeline(log(), *pipeline);
+		throw;
+	}
+
+	if (m_status.getResult() != QP_TEST_RESULT_PASS)
+	{
+		if (pipeline)
+			logPipeline(log(), *pipeline);
+	}
+	else if (m_currentIteration < m_iterations)
+		return CONTINUE;
+
+	m_status.setTestContextResult(m_testCtx);
+	return STOP;
+}
+
+// Group construction utilities
+
+enum ParamFlags
+{
+	PARAMFLAGS_SWITCH_FRAGMENT	= 1 << 0,
+	PARAMFLAGS_SWITCH_VERTEX	= 1 << 1,
+	PARAMFLAGS_INIT_SINGLE		= 1 << 2,
+	PARAMFLAGS_LAST				= 1 << 3,
+	PARAMFLAGS_MASK				= PARAMFLAGS_LAST - 1
+};
+
+
+bool addRenderTest (TestCaseGroup& group, const string& namePrefix, const string& descPrefix,
+					int numIterations, ParamFlags flags, TestParams params)
+{
+	ostringstream	name;
+	ostringstream	desc;
+
+	name << namePrefix;
+	desc << descPrefix;
+
+	params.initSingle	= (flags & PARAMFLAGS_INIT_SINGLE) != 0;
+	params.switchVtx	= (flags & PARAMFLAGS_SWITCH_VERTEX) != 0;
+	params.switchFrg	= (flags & PARAMFLAGS_SWITCH_FRAGMENT) != 0;
+
+	name << (flags & PARAMFLAGS_INIT_SINGLE ? "single_program" : "separate_programs");
+	desc << (flags & PARAMFLAGS_INIT_SINGLE
+			 ? "Single program with two shaders"
+			 : "Separate programs for each shader");
+
+	switch (flags & (PARAMFLAGS_SWITCH_FRAGMENT | PARAMFLAGS_SWITCH_VERTEX))
+	{
+		case 0:
+			break;
+		case PARAMFLAGS_SWITCH_FRAGMENT:
+			name << "_add_fragment";
+			desc << ", then add a fragment program";
+			break;
+		case PARAMFLAGS_SWITCH_VERTEX:
+			name << "_add_vertex";
+			desc << ", then add a vertex program";
+			break;
+		case PARAMFLAGS_SWITCH_FRAGMENT | PARAMFLAGS_SWITCH_VERTEX:
+			name << "_add_both";
+			desc << ", then add both vertex and shader programs";
+			break;
+	}
+
+	if (!paramsValid(params))
+		return false;
+
+	group.addChild(new SeparateShaderTest(group.getContext(), name.str(), desc.str(),
+										  numIterations, params,
+										  &SeparateShaderTest::testPipelineRendering));
+
+	return true;
+}
+
+void describeInterpolation(const string& stage, Interpolation qual,
+						   ostringstream& name, ostringstream& desc)
+{
+	if (qual == glu::INTERPOLATION_LAST)
+	{
+		desc << ", unqualified in " << stage << " shader";
+		return;
+	}
+	else
+	{
+		const string qualName = glu::getInterpolationName(qual);
+
+		name << "_" << stage << "_" << qualName;
+		desc << ", qualified '" << qualName << "' in " << stage << " shader";
+	}
+}
+
+
+} // anonymous namespace
+
+TestCaseGroup* createSeparateShaderTests (Context& ctx)
+{
+	TestParams		defaultParams;
+	int				numIterations	= 4;
+	TestCaseGroup*	group			=
+		new TestCaseGroup(ctx, "separate_shader", "Separate shader tests");
+
+	defaultParams.useUniform			= false;
+	defaultParams.initSingle			= false;
+	defaultParams.switchVtx				= false;
+	defaultParams.switchFrg				= false;
+	defaultParams.useCreateHelper		= false;
+	defaultParams.useProgramUniform		= false;
+	defaultParams.useSameName			= false;
+	defaultParams.varyings.count		= 0;
+	defaultParams.varyings.type			= glu::TYPE_INVALID;
+	defaultParams.varyings.binding		= BINDING_NAME;
+	defaultParams.varyings.vtxInterp	= glu::INTERPOLATION_LAST;
+	defaultParams.varyings.frgInterp	= glu::INTERPOLATION_LAST;
+
+	TestCaseGroup* stagesGroup =
+		new TestCaseGroup(ctx, "pipeline", "Pipeline configuration tests");
+	group->addChild(stagesGroup);
+
+	for (deUint32 flags = 0; flags < PARAMFLAGS_LAST << 2; ++flags)
+	{
+		TestParams		params			= defaultParams;
+		ostringstream	name;
+		ostringstream	desc;
+
+		if (flags & (PARAMFLAGS_LAST << 1))
+		{
+			params.useSameName = true;
+			name << "same_";
+			desc << "Identically named ";
+		}
+		else
+		{
+			name << "different_";
+			desc << "Differently named ";
+		}
+
+		if (flags & PARAMFLAGS_LAST)
+		{
+			params.useUniform = true;
+			name << "uniform_";
+			desc << "uniforms, ";
+		}
+		else
+		{
+			name << "constant_";
+			desc << "constants, ";
+		}
+
+		addRenderTest(*stagesGroup, name.str(), desc.str(), numIterations,
+					  ParamFlags(flags & PARAMFLAGS_MASK), params);
+	}
+
+	TestCaseGroup* programUniformGroup =
+		new TestCaseGroup(ctx, "program_uniform", "ProgramUniform tests");
+	group->addChild(programUniformGroup);
+
+	for (deUint32 flags = 0; flags < PARAMFLAGS_LAST; ++flags)
+	{
+		TestParams		params			= defaultParams;
+
+		params.useUniform = true;
+		params.useProgramUniform = true;
+
+		addRenderTest(*programUniformGroup, "", "", numIterations, ParamFlags(flags), params);
+	}
+
+	TestCaseGroup* createShaderProgramGroup =
+		new TestCaseGroup(ctx, "create_shader_program", "CreateShaderProgram tests");
+	group->addChild(createShaderProgramGroup);
+
+	for (deUint32 flags = 0; flags < PARAMFLAGS_LAST; ++flags)
+	{
+		TestParams		params			= defaultParams;
+
+		params.useCreateHelper = true;
+
+		addRenderTest(*createShaderProgramGroup, "", "", numIterations,
+					  ParamFlags(flags), params);
+	}
+
+	TestCaseGroup* interfaceGroup =
+		new TestCaseGroup(ctx, "interface", "Shader interface compatibility tests");
+	group->addChild(interfaceGroup);
+
+	enum
+	{
+		NUM_INTERPOLATIONS	= glu::INTERPOLATION_LAST + 1, // INTERPOLATION_LAST is valid
+		INTERFACEFLAGS_LAST = BINDING_LAST * NUM_INTERPOLATIONS * NUM_INTERPOLATIONS
+	};
+
+	for (deUint32 flags = 0; flags < INTERFACEFLAGS_LAST; ++flags)
+	{
+		deUint32		tmpFlags	= flags;
+		Interpolation	frgInterp	= Interpolation(tmpFlags % NUM_INTERPOLATIONS);
+		Interpolation	vtxInterp	= Interpolation((tmpFlags /= NUM_INTERPOLATIONS)
+													% NUM_INTERPOLATIONS);
+		BindingKind		binding		= BindingKind((tmpFlags /= NUM_INTERPOLATIONS)
+												  % BINDING_LAST);
+		TestParams		params		= defaultParams;
+		ostringstream	name;
+		ostringstream	desc;
+
+		params.varyings.count		= 1;
+		params.varyings.type		= glu::TYPE_FLOAT;
+		params.varyings.binding		= binding;
+		params.varyings.vtxInterp	= vtxInterp;
+		params.varyings.frgInterp	= frgInterp;
+
+		switch (binding)
+		{
+			case BINDING_LOCATION:
+				name << "same_location";
+				desc << "Varyings have same location, ";
+				break;
+			case BINDING_NAME:
+				name << "same_name";
+				desc << "Varyings have same name, ";
+				break;
+			default:
+				DE_ASSERT(!"Impossible");
+		}
+
+		describeInterpolation("vertex", vtxInterp, name, desc);
+		describeInterpolation("fragment", frgInterp, name, desc);
+
+		if (!paramsValid(params))
+			continue;
+
+		interfaceGroup->addChild(
+			new SeparateShaderTest(ctx, name.str(), desc.str(), numIterations, params,
+								   &SeparateShaderTest::testPipelineRendering));
+	}
+
+	deUint32		baseSeed	= ctx.getTestContext().getCommandLine().getBaseSeed();
+	Random			rnd			(deStringHash("separate_shader.random") + baseSeed);
+	set<string>		seen;
+	TestCaseGroup*	randomGroup	= new TestCaseGroup(
+		ctx, "random", "Random pipeline configuration tests");
+	group->addChild(randomGroup);
+
+	for (deUint32 i = 0; i < 128; ++i)
+	{
+		TestParams		params;
+		string			code;
+		deUint32		genIterations	= 4096;
+
+		do
+		{
+			params	= genParams(rnd.getUint32());
+			code	= paramsCode(params);
+		} while (de::contains(seen, code) && --genIterations > 0);
+
+		seen.insert(code);
+
+		string name = de::toString(i); // Would be code but baseSeed can change
+
+		randomGroup->addChild(new SeparateShaderTest(
+								  ctx, name, name, numIterations, params,
+								  &SeparateShaderTest::testPipelineRendering));
+	}
+
+	TestCaseGroup* apiGroup =
+		new TestCaseGroup(ctx, "api", "Program pipeline API tests");
+	group->addChild(apiGroup);
+
+	{
+		// More or less random parameters. These shouldn't have much effect, so just
+		// do a single sample.
+		TestParams params = defaultParams;
+		params.useUniform = true;
+		apiGroup->addChild(new SeparateShaderTest(
+								  ctx,
+								  "current_program_priority",
+								  "Test priority between current program and pipeline binding",
+								  1, params, &SeparateShaderTest::testCurrentProgPriority));
+		apiGroup->addChild(new SeparateShaderTest(
+								  ctx,
+								  "active_program_uniform",
+								  "Test that glUniform() affects a pipeline's active program",
+								  1, params, &SeparateShaderTest::testActiveProgramUniform));
+
+		apiGroup->addChild(new SeparateShaderTest(
+								 ctx,
+								 "pipeline_programs",
+								 "Test queries for programs in program pipeline stages",
+								 1, params, &SeparateShaderTest::testPipelineQueryPrograms));
+
+		apiGroup->addChild(new SeparateShaderTest(
+								 ctx,
+								 "pipeline_active",
+								 "Test query for active programs in a program pipeline",
+								 1, params, &SeparateShaderTest::testPipelineQueryActive));
+	}
+
+	TestCaseGroup* interfaceMismatchGroup =
+		new TestCaseGroup(ctx, "validation", "Negative program pipeline interface matching");
+	group->addChild(interfaceMismatchGroup);
+
+	{
+		gls::ShaderLibrary					shaderLibrary	(ctx.getTestContext(), ctx.getRenderContext(), ctx.getContextInfo());
+		const std::vector<tcu::TestNode*>	children		= shaderLibrary.loadShaderFile("shaders/separate_shader_validation.test");
+
+		for (int i = 0; i < (int)children.size(); i++)
+			interfaceMismatchGroup->addChild(children[i]);
+	}
+
+	return group;
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fSeparateShaderTests.hpp b/modules/gles31/functional/es31fSeparateShaderTests.hpp
new file mode 100644
index 0000000..4dde4cc
--- /dev/null
+++ b/modules/gles31/functional/es31fSeparateShaderTests.hpp
@@ -0,0 +1,41 @@
+#ifndef _ES31FSEPARATESHADERTESTS_HPP
+#define _ES31FSEPARATESHADERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Separate shader tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+TestCaseGroup*		createSeparateShaderTests	(Context& context);
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSEPARATESHADERTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderAtomicOpTests.cpp b/modules/gles31/functional/es31fShaderAtomicOpTests.cpp
new file mode 100644
index 0000000..0cdd461
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderAtomicOpTests.cpp
@@ -0,0 +1,1040 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader atomic operation tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderAtomicOpTests.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluProgramInterfaceQuery.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuFormatUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <algorithm>
+#include <set>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+using tcu::UVec3;
+using std::set;
+using namespace glu;
+
+template<typename T, int Size>
+static inline T product (const tcu::Vector<T, Size>& v)
+{
+	T res = v[0];
+	for (int ndx = 1; ndx < Size; ndx++)
+		res *= v[ndx];
+	return res;
+}
+
+class ShaderAtomicOpCase : public TestCase
+{
+public:
+							ShaderAtomicOpCase	(Context& context, const char* name, const char* funcName, AtomicOperandType operandType, DataType type, Precision precision, const UVec3& workGroupSize);
+							~ShaderAtomicOpCase	(void);
+
+	void					init				(void);
+	void					deinit				(void);
+	IterateResult			iterate				(void);
+
+protected:
+	virtual void			getInputs			(int numValues, int stride, void* inputs) const = 0;
+	virtual bool			verify				(int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const = 0;
+
+	const string			m_funcName;
+	const AtomicOperandType	m_operandType;
+	const DataType			m_type;
+	const Precision			m_precision;
+
+	const UVec3				m_workGroupSize;
+	const UVec3				m_numWorkGroups;
+
+	deUint32				m_initialValue;
+
+private:
+							ShaderAtomicOpCase	(const ShaderAtomicOpCase& other);
+	ShaderAtomicOpCase&		operator=			(const ShaderAtomicOpCase& other);
+
+	ShaderProgram*			m_program;
+};
+
+ShaderAtomicOpCase::ShaderAtomicOpCase (Context& context, const char* name, const char* funcName, AtomicOperandType operandType, DataType type, Precision precision, const UVec3& workGroupSize)
+	: TestCase			(context, name, funcName)
+	, m_funcName		(funcName)
+	, m_operandType		(operandType)
+	, m_type			(type)
+	, m_precision		(precision)
+	, m_workGroupSize	(workGroupSize)
+	, m_numWorkGroups	(4,4,4)
+	, m_initialValue	(0)
+	, m_program			(DE_NULL)
+{
+}
+
+ShaderAtomicOpCase::~ShaderAtomicOpCase (void)
+{
+	ShaderAtomicOpCase::deinit();
+}
+
+void ShaderAtomicOpCase::init (void)
+{
+	const bool			isSSBO		= m_operandType == ATOMIC_OPERAND_BUFFER_VARIABLE;
+	const char*			precName	= getPrecisionName(m_precision);
+	const char*			typeName	= getDataTypeName(m_type);
+	const deUint32		numValues	= product(m_workGroupSize)*product(m_numWorkGroups);
+	std::ostringstream	src;
+
+	src << "#version 310 es\n"
+		<< "layout(local_size_x = " << m_workGroupSize.x()
+		<< ", local_size_y = " << m_workGroupSize.y()
+		<< ", local_size_z = " << m_workGroupSize.z() << ") in;\n"
+		<< "layout(binding = 0) buffer InOut\n"
+		<< "{\n"
+		<< "	" << precName << " " << typeName << " inputValues[" << numValues << "];\n"
+		<< "	" << precName << " " << typeName << " outputValues[" << numValues << "];\n"
+		<< "	" << (isSSBO ? "coherent " : "") << precName << " " << typeName << " groupValues[" << product(m_numWorkGroups) << "];\n"
+		<< "} sb_inout;\n";
+
+	if (!isSSBO)
+		src << "shared " << precName << " " << typeName << " s_var;\n";
+
+	src << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	uint localSize  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
+		<< "	uint globalNdx  = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+		<< "	uint globalOffs = localSize*globalNdx;\n"
+		<< "	uint offset     = globalOffs + gl_LocalInvocationIndex;\n"
+		<< "\n";
+
+	if (isSSBO)
+	{
+		src << "	sb_inout.outputValues[offset] = " << m_funcName << "(sb_inout.groupValues[globalNdx], sb_inout.inputValues[offset]);\n";
+	}
+	else
+	{
+		src << "	if (gl_LocalInvocationIndex == 0u)\n"
+			<< "		s_var = " << typeName << "(" << tcu::toHex(m_initialValue) << "u);\n"
+			<< "	memoryBarrierShared();\n"
+			<< "	sb_inout.outputValues[offset] = " << m_funcName << "(s_var, sb_inout.inputValues[offset]);\n"
+			<< "	memoryBarrierShared();\n"
+			<< "	if (gl_LocalInvocationIndex == 0u)\n"
+			<< "		sb_inout.groupValues[globalNdx] = s_var;\n";
+	}
+
+	src << "}\n";
+
+	DE_ASSERT(!m_program);
+	m_program = new ShaderProgram(m_context.getRenderContext(), ProgramSources() << ComputeSource(src.str()));
+
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+	{
+		delete m_program;
+		m_program = DE_NULL;
+		throw tcu::TestError("Compile failed");
+	}
+}
+
+void ShaderAtomicOpCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+ShaderAtomicOpCase::IterateResult ShaderAtomicOpCase::iterate (void)
+{
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	const deUint32				program			= m_program->getProgram();
+	const Buffer				inoutBuffer		(m_context.getRenderContext());
+	const deUint32				blockNdx		= gl.getProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, "InOut");
+	const InterfaceBlockInfo	blockInfo		= getProgramInterfaceBlockInfo(gl, program, GL_SHADER_STORAGE_BLOCK, blockNdx);
+	const deUint32				inVarNdx		= gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.inputValues[0]");
+	const InterfaceVariableInfo	inVarInfo		= getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, inVarNdx);
+	const deUint32				outVarNdx		= gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.outputValues[0]");
+	const InterfaceVariableInfo	outVarInfo		= getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, outVarNdx);
+	const deUint32				groupVarNdx		= gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.groupValues[0]");
+	const InterfaceVariableInfo	groupVarInfo	= getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, groupVarNdx);
+	const deUint32				numValues		= product(m_workGroupSize)*product(m_numWorkGroups);
+
+	TCU_CHECK(inVarInfo.arraySize == numValues &&
+			  outVarInfo.arraySize == numValues &&
+			  groupVarInfo.arraySize == product(m_numWorkGroups));
+
+	gl.useProgram(program);
+
+	// Setup buffer.
+	{
+		vector<deUint8> bufData(blockInfo.dataSize);
+		std::fill(bufData.begin(), bufData.end(), 0);
+
+		getInputs((int)numValues, (int)inVarInfo.arrayStride, &bufData[0] + inVarInfo.offset);
+
+		if (m_operandType == ATOMIC_OPERAND_BUFFER_VARIABLE)
+		{
+			for (deUint32 valNdx = 0; valNdx < product(m_numWorkGroups); valNdx++)
+				*(deUint32*)(&bufData[0] + groupVarInfo.offset + groupVarInfo.arrayStride*valNdx) = m_initialValue;
+		}
+
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *inoutBuffer);
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockInfo.dataSize, &bufData[0], GL_STATIC_READ);
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *inoutBuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+	}
+
+	gl.dispatchCompute(m_numWorkGroups.x(), m_numWorkGroups.y(), m_numWorkGroups.z());
+
+	// Read back and compare
+	{
+		const void*		resPtr		= gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, blockInfo.dataSize, GL_MAP_READ_BIT);
+		bool			isOk		= true;
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+		TCU_CHECK(resPtr);
+
+		isOk = verify((int)numValues,
+					  (int)inVarInfo.arrayStride, (const deUint8*)resPtr + inVarInfo.offset,
+					  (int)outVarInfo.arrayStride, (const deUint8*)resPtr + outVarInfo.offset,
+					  (int)groupVarInfo.arrayStride, (const deUint8*)resPtr + groupVarInfo.offset);
+
+		gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Comparison failed");
+	}
+
+	return STOP;
+}
+
+class ShaderAtomicAddCase : public ShaderAtomicOpCase
+{
+public:
+	ShaderAtomicAddCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
+		: ShaderAtomicOpCase(context, name, "atomicAdd", operandType, type, precision, UVec3(3,2,1))
+	{
+		m_initialValue = 1;
+	}
+
+protected:
+	void getInputs (int numValues, int stride, void* inputs) const
+	{
+		de::Random	rnd			(deStringHash(getName()));
+		const int	maxVal		= m_precision == PRECISION_LOWP ? 2 : 32;
+		const int	minVal		= 1;
+
+		// \todo [2013-09-04 pyry] Negative values!
+
+		for (int valNdx = 0; valNdx < numValues; valNdx++)
+			*(int*)((deUint8*)inputs + stride*valNdx) = rnd.getInt(minVal, maxVal);
+	}
+
+	bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
+	{
+		const int	workGroupSize	= (int)product(m_workGroupSize);
+		const int	numWorkGroups	= numValues/workGroupSize;
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int	groupOffset		= groupNdx*workGroupSize;
+			const int	groupOutput		= *(const int*)((const deUint8*)groupOutputs + groupNdx*groupStride);
+			set<int>	outValues;
+			bool		maxFound		= false;
+			int			valueSum		= (int)m_initialValue;
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const int inputValue = *(const int*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
+				valueSum += inputValue;
+			}
+
+			if (groupOutput != valueSum)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected sum " << valueSum << ", got " << groupOutput << TestLog::EndMessage;
+				return false;
+			}
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const int	inputValue		= *(const int*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
+				const int	outputValue		= *(const int*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
+
+				if (!de::inRange(outputValue, (int)m_initialValue, valueSum-inputValue))
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
+														   << ": expected value in range [" << m_initialValue << ", " << (valueSum-inputValue)
+														   << "], got " << outputValue
+									   << TestLog::EndMessage;
+					return false;
+				}
+
+				if (outValues.find(outputValue) != outValues.end())
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
+														   << ": found duplicate value " << outputValue
+									   << TestLog::EndMessage;
+					return false;
+				}
+
+				outValues.insert(outputValue);
+				if (outputValue == valueSum-inputValue)
+					maxFound = true;
+			}
+
+			if (!maxFound)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: could not find maximum expected value from group " << groupNdx << TestLog::EndMessage;
+				return false;
+			}
+
+			if (outValues.find((int)m_initialValue) == outValues.end())
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: could not find initial value from group " << groupNdx << TestLog::EndMessage;
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class ShaderAtomicMinCase : public ShaderAtomicOpCase
+{
+public:
+	ShaderAtomicMinCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
+		: ShaderAtomicOpCase(context, name, "atomicMin", operandType, type, precision, UVec3(3,2,1))
+	{
+		m_initialValue = m_precision == PRECISION_LOWP ? 100 : 1000;
+	}
+
+protected:
+	void getInputs (int numValues, int stride, void* inputs) const
+	{
+		de::Random	rnd			(deStringHash(getName()));
+		const bool	isSigned	= m_type == TYPE_INT;
+		const int	maxVal		= m_precision == PRECISION_LOWP ? 100 : 1000;
+		const int	minVal		= isSigned ? -maxVal : 0;
+
+		for (int valNdx = 0; valNdx < numValues; valNdx++)
+			*(int*)((deUint8*)inputs + stride*valNdx) = rnd.getInt(minVal, maxVal);
+	}
+
+	bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
+	{
+		const int	workGroupSize	= (int)product(m_workGroupSize);
+		const int	numWorkGroups	= numValues/workGroupSize;
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int	groupOffset		= groupNdx*workGroupSize;
+			const int	groupOutput		= *(const int*)((const deUint8*)groupOutputs + groupNdx*groupStride);
+			set<int>	inValues;
+			set<int>	outValues;
+			int			minValue		= (int)m_initialValue;
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const int inputValue = *(const int*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
+				inValues.insert(inputValue);
+				minValue = de::min(inputValue, minValue);
+			}
+
+			if (minValue != groupOutput)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected minimum " << minValue << ", got " << groupOutput << TestLog::EndMessage;
+				return false;
+			}
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const int outputValue = *(const int*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
+
+				if (inValues.find(outputValue) == inValues.end() && outputValue != (int)m_initialValue)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
+														   << ": found unexpected value " << outputValue
+									   << TestLog::EndMessage;
+					return false;
+				}
+
+				outValues.insert(outputValue);
+			}
+
+			if (outValues.find((int)m_initialValue) == outValues.end())
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: could not find initial value from group " << groupNdx << TestLog::EndMessage;
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class ShaderAtomicMaxCase : public ShaderAtomicOpCase
+{
+public:
+	ShaderAtomicMaxCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
+		: ShaderAtomicOpCase(context, name, "atomicMax", operandType, type, precision, UVec3(3,2,1))
+	{
+		const bool isSigned = m_type == TYPE_INT;
+		m_initialValue = isSigned ? (m_precision == PRECISION_LOWP ? -100 : -1000) : 0;
+	}
+
+protected:
+	void getInputs (int numValues, int stride, void* inputs) const
+	{
+		de::Random	rnd			(deStringHash(getName()));
+		const bool	isSigned	= m_type == TYPE_INT;
+		const int	maxVal		= m_precision == PRECISION_LOWP ? 100 : 1000;
+		const int	minVal		= isSigned ? -maxVal : 0;
+
+		for (int valNdx = 0; valNdx < numValues; valNdx++)
+			*(int*)((deUint8*)inputs + stride*valNdx) = rnd.getInt(minVal, maxVal);
+	}
+
+	bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
+	{
+		const int	workGroupSize	= (int)product(m_workGroupSize);
+		const int	numWorkGroups	= numValues/workGroupSize;
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int	groupOffset		= groupNdx*workGroupSize;
+			const int	groupOutput		= *(const int*)((const deUint8*)groupOutputs + groupNdx*groupStride);
+			set<int>	inValues;
+			set<int>	outValues;
+			int			maxValue		= (int)m_initialValue;
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const int inputValue = *(const int*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
+				inValues.insert(inputValue);
+				maxValue = de::max(maxValue, inputValue);
+			}
+
+			if (maxValue != groupOutput)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected maximum " << maxValue << ", got " << groupOutput << TestLog::EndMessage;
+				return false;
+			}
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const int outputValue = *(const int*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
+
+				if (inValues.find(outputValue) == inValues.end() && outputValue != (int)m_initialValue)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
+														   << ": found unexpected value " << outputValue
+									   << TestLog::EndMessage;
+					return false;
+				}
+
+				outValues.insert(outputValue);
+			}
+
+			if (outValues.find((int)m_initialValue) == outValues.end())
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: could not find initial value from group " << groupNdx << TestLog::EndMessage;
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class ShaderAtomicAndCase : public ShaderAtomicOpCase
+{
+public:
+	ShaderAtomicAndCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
+		: ShaderAtomicOpCase(context, name, "atomicAnd", operandType, type, precision, UVec3(3,2,1))
+	{
+		const int		numBits		= m_precision == PRECISION_HIGHP ? 32 :
+									  m_precision == PRECISION_MEDIUMP ? 16 : 8;
+		const deUint32	valueMask	= numBits == 32 ? ~0u : (1u<<numBits)-1u;
+		m_initialValue = ~((1u<<(numBits-1u)) | 1u) & valueMask; // All bits except lowest and highest set.
+	}
+
+protected:
+	void getInputs (int numValues, int stride, void* inputs) const
+	{
+		de::Random		rnd				(deStringHash(getName()));
+		const int		workGroupSize	= (int)product(m_workGroupSize);
+		const int		numWorkGroups	= numValues/workGroupSize;
+		const int		numBits			= m_precision == PRECISION_HIGHP ? 32 :
+										  m_precision == PRECISION_MEDIUMP ? 16 : 8;
+		const deUint32	valueMask		= numBits == 32 ? ~0u : (1u<<numBits)-1u;
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int		groupOffset		= groupNdx*workGroupSize;
+			const deUint32	groupMask		= 1<<rnd.getInt(0, numBits-2); // One bit is always set.
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+				*(deUint32*)((deUint8*)inputs + stride*(groupOffset+localNdx)) = (rnd.getUint32() & valueMask) | groupMask;
+		}
+	}
+
+	bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
+	{
+		const int	workGroupSize	= (int)product(m_workGroupSize);
+		const int	numWorkGroups	= numValues/workGroupSize;
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int		groupOffset		= groupNdx*workGroupSize;
+			const deUint32	groupOutput		= *(const deUint32*)((const deUint8*)groupOutputs + groupNdx*groupStride);
+			deUint32		expectedValue	= m_initialValue;
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const deUint32 inputValue = *(const deUint32*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
+				expectedValue &= inputValue;
+			}
+
+			if (expectedValue != groupOutput)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected " << tcu::toHex(expectedValue) << ", got " << tcu::toHex(groupOutput) << TestLog::EndMessage;
+				return false;
+			}
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const deUint32 outputValue = *(const deUint32*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
+
+				if ((outputValue & ~m_initialValue) != 0)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
+														   << ": found unexpected value " << tcu::toHex(outputValue)
+									   << TestLog::EndMessage;
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class ShaderAtomicOrCase : public ShaderAtomicOpCase
+{
+public:
+	ShaderAtomicOrCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
+		: ShaderAtomicOpCase(context, name, "atomicOr", operandType, type, precision, UVec3(3,2,1))
+	{
+		m_initialValue = 1u; // Lowest bit set.
+	}
+
+protected:
+	void getInputs (int numValues, int stride, void* inputs) const
+	{
+		de::Random		rnd				(deStringHash(getName()));
+		const int		workGroupSize	= (int)product(m_workGroupSize);
+		const int		numWorkGroups	= numValues/workGroupSize;
+		const int		numBits			= m_precision == PRECISION_HIGHP ? 32 :
+										  m_precision == PRECISION_MEDIUMP ? 16 : 8;
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int groupOffset = groupNdx*workGroupSize;
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+				*(deUint32*)((deUint8*)inputs + stride*(groupOffset+localNdx)) = 1u<<rnd.getInt(0, numBits-1);
+		}
+	}
+
+	bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
+	{
+		const int	workGroupSize	= (int)product(m_workGroupSize);
+		const int	numWorkGroups	= numValues/workGroupSize;
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int		groupOffset		= groupNdx*workGroupSize;
+			const deUint32	groupOutput		= *(const deUint32*)((const deUint8*)groupOutputs + groupNdx*groupStride);
+			deUint32		expectedValue	= m_initialValue;
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const deUint32 inputValue = *(const deUint32*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
+				expectedValue |= inputValue;
+			}
+
+			if (expectedValue != groupOutput)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected " << tcu::toHex(expectedValue) << ", got " << tcu::toHex(groupOutput) << TestLog::EndMessage;
+				return false;
+			}
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const deUint32 outputValue = *(const deUint32*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
+
+				if ((outputValue & m_initialValue) == 0)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
+														   << ": found unexpected value " << tcu::toHex(outputValue)
+									   << TestLog::EndMessage;
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class ShaderAtomicXorCase : public ShaderAtomicOpCase
+{
+public:
+	ShaderAtomicXorCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
+		: ShaderAtomicOpCase(context, name, "atomicXor", operandType, type, precision, UVec3(3,2,1))
+	{
+		m_initialValue = 0;
+	}
+
+protected:
+	void getInputs (int numValues, int stride, void* inputs) const
+	{
+		de::Random		rnd				(deStringHash(getName()));
+		const int		workGroupSize	= (int)product(m_workGroupSize);
+		const int		numWorkGroups	= numValues/workGroupSize;
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int groupOffset = groupNdx*workGroupSize;
+
+			// First uses random bit-pattern.
+			*(deUint32*)((deUint8*)inputs + stride*(groupOffset)) = rnd.getUint32();
+
+			// Rest have either all or no bits set.
+			for (int localNdx = 1; localNdx < workGroupSize; localNdx++)
+				*(deUint32*)((deUint8*)inputs + stride*(groupOffset+localNdx)) = rnd.getBool() ? ~0u : 0u;
+		}
+	}
+
+	bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
+	{
+		const int		workGroupSize	= (int)product(m_workGroupSize);
+		const int		numWorkGroups	= numValues/workGroupSize;
+		const int		numBits			= m_precision == PRECISION_HIGHP ? 32 :
+										  m_precision == PRECISION_MEDIUMP ? 16 : 8;
+		const deUint32	compareMask		= numBits == 32 ? ~0u : (1u<<numBits)-1u;
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int		groupOffset		= groupNdx*workGroupSize;
+			const deUint32	groupOutput		= *(const deUint32*)((const deUint8*)groupOutputs + groupNdx*groupStride);
+			const deUint32	randomValue		= *(const int*)((const deUint8*)inputs + inputStride*groupOffset);
+			const deUint32	expected0		= randomValue ^ 0u;
+			const deUint32	expected1		= randomValue ^ ~0u;
+			int				numXorZeros		= (m_initialValue == 0) ? 1 : 0;
+
+			for (int localNdx = 1; localNdx < workGroupSize; localNdx++)
+			{
+				const deUint32 inputValue = *(const deUint32*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
+				if (inputValue == 0)
+					numXorZeros += 1;
+			}
+
+			const deUint32 expected = (numXorZeros%2 == 0) ? expected0 : expected1;
+
+			if ((groupOutput & compareMask) != (expected & compareMask))
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected " << tcu::toHex(expected0)
+													   << " or " << tcu::toHex(expected1) << " (compare mask " << tcu::toHex(compareMask)
+													   << "), got " << tcu::toHex(groupOutput) << TestLog::EndMessage;
+				return false;
+			}
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const deUint32 outputValue = *(const deUint32*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
+
+				if ((outputValue&compareMask) != 0 &&
+					(outputValue&compareMask) != compareMask &&
+					(outputValue&compareMask) != (expected0&compareMask) &&
+					(outputValue&compareMask) != (expected1&compareMask))
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
+														   << ": found unexpected value " << tcu::toHex(outputValue)
+									   << TestLog::EndMessage;
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class ShaderAtomicExchangeCase : public ShaderAtomicOpCase
+{
+public:
+	ShaderAtomicExchangeCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
+		: ShaderAtomicOpCase(context, name, "atomicExchange", operandType, type, precision, UVec3(3,2,1))
+	{
+		m_initialValue = 0;
+	}
+
+protected:
+	void getInputs (int numValues, int stride, void* inputs) const
+	{
+		const int	workGroupSize	= (int)product(m_workGroupSize);
+		const int	numWorkGroups	= numValues/workGroupSize;
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int groupOffset = groupNdx*workGroupSize;
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+				*(int*)((deUint8*)inputs + stride*(groupOffset+localNdx)) = localNdx+1;
+		}
+	}
+
+	bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
+	{
+		const int	workGroupSize	= (int)product(m_workGroupSize);
+		const int	numWorkGroups	= numValues/workGroupSize;
+
+		DE_UNREF(inputStride && inputs);
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int	groupOffset		= groupNdx*workGroupSize;
+			const int	groupOutput		= *(const int*)((const deUint8*)groupOutputs + groupNdx*groupStride);
+			set<int>	usedValues;
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const int outputValue = *(const int*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
+
+				if (!de::inRange(outputValue, 0, workGroupSize) || usedValues.find(outputValue) != usedValues.end())
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
+														   << ": found unexpected value " << outputValue
+									   << TestLog::EndMessage;
+					return false;
+				}
+				usedValues.insert(outputValue);
+			}
+
+			if (!de::inRange(groupOutput, 0, workGroupSize) || usedValues.find(groupOutput) != usedValues.end())
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": unexpected final value" << groupOutput << TestLog::EndMessage;
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class ShaderAtomicCompSwapCase : public TestCase
+{
+public:
+									ShaderAtomicCompSwapCase	(Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision);
+									~ShaderAtomicCompSwapCase	(void);
+
+	void							init						(void);
+	void							deinit						(void);
+	IterateResult					iterate						(void);
+
+protected:
+
+private:
+									ShaderAtomicCompSwapCase	(const ShaderAtomicCompSwapCase& other);
+	ShaderAtomicCompSwapCase&		operator=					(const ShaderAtomicCompSwapCase& other);
+
+	const AtomicOperandType			m_operandType;
+	const DataType					m_type;
+	const Precision					m_precision;
+
+	const UVec3						m_workGroupSize;
+	const UVec3						m_numWorkGroups;
+
+	ShaderProgram*					m_program;
+};
+
+ShaderAtomicCompSwapCase::ShaderAtomicCompSwapCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
+	: TestCase			(context, name, "atomicCompSwap() Test")
+	, m_operandType		(operandType)
+	, m_type			(type)
+	, m_precision		(precision)
+	, m_workGroupSize	(3,2,1)
+	, m_numWorkGroups	(4,4,4)
+	, m_program			(DE_NULL)
+{
+}
+
+ShaderAtomicCompSwapCase::~ShaderAtomicCompSwapCase (void)
+{
+	ShaderAtomicCompSwapCase::deinit();
+}
+
+void ShaderAtomicCompSwapCase::init (void)
+{
+	const bool			isSSBO		= m_operandType == ATOMIC_OPERAND_BUFFER_VARIABLE;
+	const char*			precName	= getPrecisionName(m_precision);
+	const char*			typeName	= getDataTypeName(m_type);
+	const deUint32		numValues	= product(m_workGroupSize)*product(m_numWorkGroups);
+	std::ostringstream	src;
+
+	src << "#version 310 es\n"
+		<< "layout(local_size_x = " << m_workGroupSize.x()
+		<< ", local_size_y = " << m_workGroupSize.y()
+		<< ", local_size_z = " << m_workGroupSize.z() << ") in;\n"
+		<< "layout(binding = 0) buffer InOut\n"
+		<< "{\n"
+		<< "	" << precName << " " << typeName << " compareValues[" << numValues << "];\n"
+		<< "	" << precName << " " << typeName << " exchangeValues[" << numValues << "];\n"
+		<< "	" << precName << " " << typeName << " outputValues[" << numValues << "];\n"
+		<< "	" << (isSSBO ? "coherent " : "") << precName << " " << typeName << " groupValues[" << product(m_numWorkGroups) << "];\n"
+		<< "} sb_inout;\n";
+
+	if (!isSSBO)
+		src << "shared " << precName << " " << typeName << " s_var;\n";
+
+	src << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	uint localSize  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
+		<< "	uint globalNdx  = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+		<< "	uint globalOffs = localSize*globalNdx;\n"
+		<< "	uint offset     = globalOffs + gl_LocalInvocationIndex;\n"
+		<< "\n";
+
+	if (!isSSBO)
+	{
+		src << "	if (gl_LocalInvocationIndex == 0u)\n"
+			<< "		s_var = " << typeName << "(" << 0 << ");\n"
+			<< "	memoryBarrierShared();\n"
+			<< "\n";
+	}
+
+	src << "	" << precName << " " << typeName << " compare = sb_inout.compareValues[offset];\n"
+		<< "	" << precName << " " << typeName << " exchange = sb_inout.exchangeValues[offset];\n"
+		<< "	" << precName << " " << typeName << " result;\n"
+		<< "\n"
+		<< "	for (uint ndx = 0u; ndx < localSize; ndx++)\n"
+		<< "	{\n"
+		<< "		result = atomicCompSwap(" << (isSSBO ? "sb_inout.groupValues[globalNdx]" : "s_var") << ", compare, exchange);\n"
+		<< "		if (result == compare)\n"
+		<< "			break;\n"
+		<< "	}\n"
+		<< "\n"
+		<< "	sb_inout.outputValues[offset] = result;\n";
+
+	if (!isSSBO)
+	{
+		src << "	memoryBarrierShared();\n"
+			<< "	if (gl_LocalInvocationIndex == 0u)\n"
+			<< "		sb_inout.groupValues[globalNdx] = s_var;\n";
+	}
+
+	src << "}\n";
+
+	DE_ASSERT(!m_program);
+	m_program = new ShaderProgram(m_context.getRenderContext(), ProgramSources() << ComputeSource(src.str()));
+
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+	{
+		delete m_program;
+		m_program = DE_NULL;
+		throw tcu::TestError("Compile failed");
+	}
+}
+
+void ShaderAtomicCompSwapCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+ShaderAtomicOpCase::IterateResult ShaderAtomicCompSwapCase::iterate (void)
+{
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	const deUint32				program			= m_program->getProgram();
+	const Buffer				inoutBuffer		(m_context.getRenderContext());
+	const deUint32				blockNdx		= gl.getProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, "InOut");
+	const InterfaceBlockInfo	blockInfo		= getProgramInterfaceBlockInfo(gl, program, GL_SHADER_STORAGE_BLOCK, blockNdx);
+	const deUint32				cmpVarNdx		= gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.compareValues[0]");
+	const InterfaceVariableInfo	cmpVarInfo		= getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, cmpVarNdx);
+	const deUint32				exhVarNdx		= gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.exchangeValues[0]");
+	const InterfaceVariableInfo	exhVarInfo		= getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, exhVarNdx);
+	const deUint32				outVarNdx		= gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.outputValues[0]");
+	const InterfaceVariableInfo	outVarInfo		= getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, outVarNdx);
+	const deUint32				groupVarNdx		= gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.groupValues[0]");
+	const InterfaceVariableInfo	groupVarInfo	= getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, groupVarNdx);
+	const deUint32				numValues		= product(m_workGroupSize)*product(m_numWorkGroups);
+
+	TCU_CHECK(cmpVarInfo.arraySize == numValues &&
+			  exhVarInfo.arraySize == numValues &&
+			  outVarInfo.arraySize == numValues &&
+			  groupVarInfo.arraySize == product(m_numWorkGroups));
+
+	gl.useProgram(program);
+
+	// \todo [2013-09-05 pyry] Use randomized input values!
+
+	// Setup buffer.
+	{
+		const deUint32	workGroupSize	= product(m_workGroupSize);
+		vector<deUint8>	bufData			(blockInfo.dataSize);
+
+		std::fill(bufData.begin(), bufData.end(), 0);
+
+		for (deUint32 ndx = 0; ndx < numValues; ndx++)
+			*(deUint32*)(&bufData[0] + cmpVarInfo.offset + cmpVarInfo.arrayStride*ndx) = ndx%workGroupSize;
+
+		for (deUint32 ndx = 0; ndx < numValues; ndx++)
+			*(deUint32*)(&bufData[0] + exhVarInfo.offset + exhVarInfo.arrayStride*ndx) = (ndx%workGroupSize)+1;
+
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *inoutBuffer);
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockInfo.dataSize, &bufData[0], GL_STATIC_READ);
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *inoutBuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+	}
+
+	gl.dispatchCompute(m_numWorkGroups.x(), m_numWorkGroups.y(), m_numWorkGroups.z());
+
+	// Read back and compare
+	{
+		const void*		resPtr			= gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, blockInfo.dataSize, GL_MAP_READ_BIT);
+		const int		numWorkGroups	= (int)product(m_numWorkGroups);
+		const int		workGroupSize	= (int)product(m_workGroupSize);
+		bool			isOk			= true;
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+		TCU_CHECK(resPtr);
+
+		for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
+		{
+			const int	groupOffset		= groupNdx*workGroupSize;
+			const int	groupOutput		= *(const int*)((const deUint8*)resPtr + groupVarInfo.offset + groupNdx*groupVarInfo.arrayStride);
+
+			for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
+			{
+				const int	refValue		= localNdx;
+				const int	outputValue		= *(const int*)((const deUint8*)resPtr + outVarInfo.offset + outVarInfo.arrayStride*(groupOffset+localNdx));
+
+				if (outputValue != refValue)
+				{
+					m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
+														   << ": expected " << refValue << ", got " << outputValue
+									   << TestLog::EndMessage;
+					isOk = false;
+					break;
+				}
+			}
+
+			if (groupOutput != workGroupSize)
+			{
+				m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected" << workGroupSize << ", got " << groupOutput << TestLog::EndMessage;
+				isOk = false;
+				break;
+			}
+		}
+
+		gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Comparison failed");
+	}
+
+	return STOP;
+}
+
+ShaderAtomicOpTests::ShaderAtomicOpTests (Context& context, const char* name, AtomicOperandType operandType)
+	: TestCaseGroup	(context, name, "Atomic Operation Tests")
+	, m_operandType	(operandType)
+{
+}
+
+ShaderAtomicOpTests::~ShaderAtomicOpTests (void)
+{
+}
+
+template<typename T>
+static tcu::TestCaseGroup* createAtomicOpGroup (Context& context, AtomicOperandType operandType, const char* groupName)
+{
+	tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(context.getTestContext(), groupName, (string("Atomic ") + groupName).c_str());
+	try
+	{
+		for (int precNdx = 0; precNdx < PRECISION_LAST; precNdx++)
+		{
+			for (int typeNdx = 0; typeNdx < 2; typeNdx++)
+			{
+				const Precision		precision		= Precision(precNdx);
+				const DataType		type			= typeNdx > 0 ? TYPE_INT : TYPE_UINT;
+				const string		caseName		= string(getPrecisionName(precision)) + "_" + getDataTypeName(type);
+
+				group->addChild(new T(context, caseName.c_str(), operandType, type, precision));
+			}
+		}
+
+		return group;
+	}
+	catch (...)
+	{
+		delete group;
+		throw;
+	}
+}
+
+void ShaderAtomicOpTests::init (void)
+{
+	addChild(createAtomicOpGroup<ShaderAtomicAddCase>		(m_context, m_operandType, "add"));
+	addChild(createAtomicOpGroup<ShaderAtomicMinCase>		(m_context, m_operandType, "min"));
+	addChild(createAtomicOpGroup<ShaderAtomicMaxCase>		(m_context, m_operandType, "max"));
+	addChild(createAtomicOpGroup<ShaderAtomicAndCase>		(m_context, m_operandType, "and"));
+	addChild(createAtomicOpGroup<ShaderAtomicOrCase>		(m_context, m_operandType, "or"));
+	addChild(createAtomicOpGroup<ShaderAtomicXorCase>		(m_context, m_operandType, "xor"));
+	addChild(createAtomicOpGroup<ShaderAtomicExchangeCase>	(m_context, m_operandType, "exchange"));
+	addChild(createAtomicOpGroup<ShaderAtomicCompSwapCase>	(m_context, m_operandType, "compswap"));
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderAtomicOpTests.hpp b/modules/gles31/functional/es31fShaderAtomicOpTests.hpp
new file mode 100644
index 0000000..39b348a
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderAtomicOpTests.hpp
@@ -0,0 +1,63 @@
+#ifndef _ES31FSHADERATOMICOPTESTS_HPP
+#define _ES31FSHADERATOMICOPTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader atomic operation tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+enum AtomicOperandType
+{
+	ATOMIC_OPERAND_BUFFER_VARIABLE = 0,		//!< Variable in SSBO
+	ATOMIC_OPERAND_SHARED_VARIABLE,			//!< Shared variable.
+
+	ATOMIC_OPERAND_TYPE_LAST
+};
+
+class ShaderAtomicOpTests : public TestCaseGroup
+{
+public:
+							ShaderAtomicOpTests		(Context& context, const char* name, AtomicOperandType operandType);
+							~ShaderAtomicOpTests	(void);
+
+	void					init					(void);
+
+private:
+							ShaderAtomicOpTests		(const ShaderAtomicOpTests& other);
+	ShaderAtomicOpTests&	operator=				(const ShaderAtomicOpTests& other);
+
+	const AtomicOperandType	m_operandType;
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERATOMICOPTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderBuiltinConstantTests.cpp b/modules/gles31/functional/es31fShaderBuiltinConstantTests.cpp
new file mode 100644
index 0000000..42c07d5
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderBuiltinConstantTests.cpp
@@ -0,0 +1,413 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader built-in constant tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderBuiltinConstantTests.hpp"
+#include "glsShaderExecUtil.hpp"
+#include "deUniquePtr.hpp"
+#include "deStringUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "gluStrUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+static int getInteger (const glw::Functions& gl, deUint32 pname)
+{
+	int value = -1;
+	gl.getIntegerv(pname, &value);
+	GLU_EXPECT_NO_ERROR(gl.getError(), ("glGetIntegerv(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
+	return value;
+}
+
+template<deUint32 Pname>
+static int getInteger (const glw::Functions& gl)
+{
+	return getInteger(gl, Pname);
+}
+
+static int getVectorsFromComps (const glw::Functions& gl, deUint32 pname)
+{
+	int value = -1;
+	gl.getIntegerv(pname, &value);
+	GLU_EXPECT_NO_ERROR(gl.getError(), ("glGetIntegerv(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
+	TCU_CHECK_MSG(value%4 == 0, ("Expected " + glu::getGettableStateStr((int)pname).toString() + " to be divisible by 4").c_str());
+	return value/4;
+}
+
+template<deUint32 Pname>
+static int getVectorsFromComps (const glw::Functions& gl)
+{
+	return getVectorsFromComps(gl, Pname);
+}
+
+static tcu::IVec3 getIVec3 (const glw::Functions& gl, deUint32 pname)
+{
+	tcu::IVec3 value(-1);
+	for (int ndx = 0; ndx < 3; ndx++)
+		gl.getIntegeri_v(pname, (glw::GLuint)ndx, &value[ndx]);
+	GLU_EXPECT_NO_ERROR(gl.getError(), ("glGetIntegeri_v(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
+	return value;
+}
+
+template<deUint32 Pname>
+static tcu::IVec3 getIVec3 (const glw::Functions& gl)
+{
+	return getIVec3(gl, Pname);
+}
+
+static std::string makeCaseName (const std::string& varName)
+{
+	DE_ASSERT(varName.length() > 3);
+	DE_ASSERT(varName.substr(0,3) == "gl_");
+
+	std::ostringstream name;
+	name << de::toLower(char(varName[3]));
+
+	for (size_t ndx = 4; ndx < varName.length(); ndx++)
+	{
+		const char c = char(varName[ndx]);
+		if (de::isUpper(c))
+			name << '_' << de::toLower(c);
+		else
+			name << c;
+	}
+
+	return name.str();
+}
+
+enum
+{
+	VS = (1<<glu::SHADERTYPE_VERTEX),
+	TC = (1<<glu::SHADERTYPE_TESSELLATION_CONTROL),
+	TE = (1<<glu::SHADERTYPE_TESSELLATION_EVALUATION),
+	GS = (1<<glu::SHADERTYPE_GEOMETRY),
+	FS = (1<<glu::SHADERTYPE_FRAGMENT),
+	CS = (1<<glu::SHADERTYPE_COMPUTE),
+
+	SHADER_TYPES = VS|TC|TE|GS|FS|CS
+};
+
+template<typename DataType>
+class ShaderBuiltinConstantCase : public TestCase
+{
+public:
+	typedef DataType (*GetConstantValueFunc) (const glw::Functions& gl);
+
+								ShaderBuiltinConstantCase	(Context& context, const char* varName, GetConstantValueFunc getValue, const char* requiredExt);
+								~ShaderBuiltinConstantCase	(void);
+
+	void						init						(void);
+	IterateResult				iterate						(void);
+
+private:
+	bool						verifyInShaderType			(glu::ShaderType shaderType, DataType reference);
+
+	const std::string			m_varName;
+	const GetConstantValueFunc	m_getValue;
+	const std::string			m_requiredExt;
+};
+
+template<typename DataType>
+ShaderBuiltinConstantCase<DataType>::ShaderBuiltinConstantCase (Context& context, const char* varName, GetConstantValueFunc getValue, const char* requiredExt)
+	: TestCase		(context, makeCaseName(varName).c_str(), varName)
+	, m_varName		(varName)
+	, m_getValue	(getValue)
+	, m_requiredExt	(requiredExt ? requiredExt : "")
+{
+	DE_ASSERT(!requiredExt == m_requiredExt.empty());
+}
+
+template<typename DataType>
+ShaderBuiltinConstantCase<DataType>::~ShaderBuiltinConstantCase (void)
+{
+}
+
+template<typename DataType>
+void ShaderBuiltinConstantCase<DataType>::init (void)
+{
+	if (!m_requiredExt.empty() && !m_context.getContextInfo().isExtensionSupported(m_requiredExt.c_str()))
+		throw tcu::NotSupportedError(m_requiredExt + " not supported");
+}
+
+static gls::ShaderExecUtil::ShaderExecutor* createGetConstantExecutor (const glu::RenderContext&	renderCtx,
+																	   glu::ShaderType				shaderType,
+																	   glu::DataType				dataType,
+																	   const std::string&			varName,
+																	   const std::string&			extName)
+{
+	using namespace gls::ShaderExecUtil;
+
+	ShaderSpec	shaderSpec;
+
+	shaderSpec.version	= glu::GLSL_VERSION_310_ES;
+	shaderSpec.source	= string("result = ") + varName + ";\n";
+
+	shaderSpec.outputs.push_back(Symbol("result", glu::VarType(dataType, glu::PRECISION_HIGHP)));
+
+	if (!extName.empty())
+		shaderSpec.globalDeclarations = "#extension " + extName + " : require\n";
+
+	return createExecutor(renderCtx, shaderType, shaderSpec);
+}
+
+template<typename DataType>
+static void logVarValue (tcu::TestLog& log, const std::string& varName, DataType value)
+{
+	log << TestLog::Message << varName << " = " << value << TestLog::EndMessage;
+}
+
+template<>
+void logVarValue<int> (tcu::TestLog& log, const std::string& varName, int value)
+{
+	log << TestLog::Integer(varName, varName, "", QP_KEY_TAG_NONE, value);
+}
+
+template<typename DataType>
+bool ShaderBuiltinConstantCase<DataType>::verifyInShaderType (glu::ShaderType shaderType, DataType reference)
+{
+	using namespace gls::ShaderExecUtil;
+
+	const de::UniquePtr<ShaderExecutor>	shaderExecutor	(createGetConstantExecutor(m_context.getRenderContext(), shaderType, glu::dataTypeOf<DataType>(), m_varName, m_requiredExt));
+	DataType							result			= DataType(-1);
+	void* const							outputs			= &result;
+
+	if (!shaderExecutor->isOk())
+	{
+		shaderExecutor->log(m_testCtx.getLog());
+		TCU_FAIL("Compile failed");
+	}
+
+	shaderExecutor->useProgram();
+	shaderExecutor->execute(1, DE_NULL, &outputs);
+
+	logVarValue(m_testCtx.getLog(), m_varName, result);
+
+	if (result != reference)
+	{
+		m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << m_varName << " = " << reference << TestLog::EndMessage
+						   << TestLog::Message << "Test shader:" << TestLog::EndMessage;
+		shaderExecutor->log(m_testCtx.getLog());
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid builtin constant value");
+		return false;
+	}
+	else
+		return true;
+}
+
+template<typename DataType>
+TestCase::IterateResult ShaderBuiltinConstantCase<DataType>::iterate (void)
+{
+	const DataType	reference	= m_getValue(m_context.getRenderContext().getFunctions());
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; shaderType++)
+	{
+		if ((SHADER_TYPES & (1<<shaderType)) != 0)
+		{
+			const char* const			shaderTypeName	= glu::getShaderTypeName(glu::ShaderType(shaderType));
+			const tcu::ScopedLogSection	section			(m_testCtx.getLog(), shaderTypeName, shaderTypeName);
+
+			try
+			{
+				const bool isOk = verifyInShaderType(glu::ShaderType(shaderType), reference);
+				DE_ASSERT(isOk || m_testCtx.getTestResult() != QP_TEST_RESULT_PASS);
+				DE_UNREF(isOk);
+			}
+			catch (const tcu::NotSupportedError& e)
+			{
+				m_testCtx.getLog() << TestLog::Message << "Not checking " << shaderTypeName << ": " << e.what() << TestLog::EndMessage;
+			}
+			catch (const tcu::TestError& e)
+			{
+				m_testCtx.getLog() << TestLog::Message << e.what() << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, e.getMessage());
+			}
+		}
+	}
+
+	return STOP;
+}
+
+} // anonymous
+
+ShaderBuiltinConstantTests::ShaderBuiltinConstantTests (Context& context)
+	: TestCaseGroup(context, "builtin_constants", "Built-in Constant Tests")
+{
+}
+
+ShaderBuiltinConstantTests::~ShaderBuiltinConstantTests (void)
+{
+}
+
+void ShaderBuiltinConstantTests::init (void)
+{
+	// Core builtin constants
+	{
+		static const struct
+		{
+			const char*												varName;
+			ShaderBuiltinConstantCase<int>::GetConstantValueFunc	getValue;
+		} intConstants[] =
+		{
+			{ "gl_MaxVertexAttribs",					getInteger<GL_MAX_VERTEX_ATTRIBS>						},
+			{ "gl_MaxVertexUniformVectors",				getInteger<GL_MAX_VERTEX_UNIFORM_VECTORS>				},
+			{ "gl_MaxVertexOutputVectors",				getVectorsFromComps<GL_MAX_VERTEX_OUTPUT_COMPONENTS>	},
+			{ "gl_MaxFragmentInputVectors",				getVectorsFromComps<GL_MAX_FRAGMENT_INPUT_COMPONENTS>	},
+			{ "gl_MaxFragmentUniformVectors",			getInteger<GL_MAX_FRAGMENT_UNIFORM_VECTORS>				},
+			{ "gl_MaxDrawBuffers",						getInteger<GL_MAX_DRAW_BUFFERS>							},
+
+			{ "gl_MaxVertexTextureImageUnits",			getInteger<GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS>			},
+			{ "gl_MaxCombinedTextureImageUnits",		getInteger<GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS>			},
+			{ "gl_MaxTextureImageUnits",				getInteger<GL_MAX_TEXTURE_IMAGE_UNITS>					},
+
+			{ "gl_MinProgramTexelOffset",				getInteger<GL_MIN_PROGRAM_TEXEL_OFFSET>					},
+			{ "gl_MaxProgramTexelOffset",				getInteger<GL_MAX_PROGRAM_TEXEL_OFFSET>					},
+
+			{ "gl_MaxImageUnits",						getInteger<GL_MAX_IMAGE_UNITS>							},
+			{ "gl_MaxVertexImageUniforms",				getInteger<GL_MAX_VERTEX_IMAGE_UNIFORMS>				},
+			{ "gl_MaxFragmentImageUniforms",			getInteger<GL_MAX_FRAGMENT_IMAGE_UNIFORMS>				},
+			{ "gl_MaxComputeImageUniforms",				getInteger<GL_MAX_COMPUTE_IMAGE_UNIFORMS>				},
+			{ "gl_MaxCombinedImageUniforms",			getInteger<GL_MAX_COMBINED_IMAGE_UNIFORMS>				},
+
+			{ "gl_MaxCombinedShaderOutputResources",	getInteger<GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES>		},
+
+			{ "gl_MaxComputeUniformComponents",			getInteger<GL_MAX_COMPUTE_UNIFORM_COMPONENTS>			},
+			{ "gl_MaxComputeTextureImageUnits",			getInteger<GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS>			},
+
+			{ "gl_MaxComputeAtomicCounters",			getInteger<GL_MAX_COMPUTE_ATOMIC_COUNTERS>				},
+			{ "gl_MaxComputeAtomicCounterBuffers",		getInteger<GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS>		},
+
+			{ "gl_MaxVertexAtomicCounters",				getInteger<GL_MAX_VERTEX_ATOMIC_COUNTERS>				},
+			{ "gl_MaxFragmentAtomicCounters",			getInteger<GL_MAX_FRAGMENT_ATOMIC_COUNTERS>				},
+			{ "gl_MaxCombinedAtomicCounters",			getInteger<GL_MAX_COMBINED_ATOMIC_COUNTERS>				},
+			{ "gl_MaxAtomicCounterBindings",			getInteger<GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS>		},
+
+			{ "gl_MaxVertexAtomicCounterBuffers",		getInteger<GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS>		},
+			{ "gl_MaxFragmentAtomicCounterBuffers",		getInteger<GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS>		},
+			{ "gl_MaxCombinedAtomicCounterBuffers",		getInteger<GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS>		},
+			{ "gl_MaxAtomicCounterBufferSize",			getInteger<GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE>			},
+		};
+
+		static const struct
+		{
+			const char*													varName;
+			ShaderBuiltinConstantCase<tcu::IVec3>::GetConstantValueFunc	getValue;
+		} ivec3Constants[] =
+		{
+			{ "gl_MaxComputeWorkGroupCount",			getIVec3<GL_MAX_COMPUTE_WORK_GROUP_COUNT>				},
+			{ "gl_MaxComputeWorkGroupSize",				getIVec3<GL_MAX_COMPUTE_WORK_GROUP_SIZE>				},
+		};
+
+		tcu::TestCaseGroup* const coreGroup = new tcu::TestCaseGroup(m_testCtx, "core", "Core Specification");
+		addChild(coreGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intConstants); ndx++)
+			coreGroup->addChild(new ShaderBuiltinConstantCase<int>(m_context, intConstants[ndx].varName, intConstants[ndx].getValue, DE_NULL));
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(ivec3Constants); ndx++)
+			coreGroup->addChild(new ShaderBuiltinConstantCase<tcu::IVec3>(m_context, ivec3Constants[ndx].varName, ivec3Constants[ndx].getValue, DE_NULL));
+	}
+
+	// OES_sample_variables
+	{
+		tcu::TestCaseGroup* const sampleVarGroup = new tcu::TestCaseGroup(m_testCtx, "sample_variables", "GL_OES_sample_variables");
+		addChild(sampleVarGroup);
+		sampleVarGroup->addChild(new ShaderBuiltinConstantCase<int>(m_context, "gl_MaxSamples", getInteger<GL_MAX_SAMPLES>, "GL_OES_sample_variables"));
+	}
+
+	// EXT_geometry_shader
+	{
+		static const struct
+		{
+			const char*												varName;
+			ShaderBuiltinConstantCase<int>::GetConstantValueFunc	getValue;
+		} intConstants[] =
+		{
+			{ "gl_MaxGeometryInputComponents",			getInteger<GL_MAX_GEOMETRY_INPUT_COMPONENTS>			},
+			{ "gl_MaxGeometryOutputComponents",			getInteger<GL_MAX_GEOMETRY_OUTPUT_COMPONENTS>			},
+			{ "gl_MaxGeometryImageUniforms",			getInteger<GL_MAX_GEOMETRY_IMAGE_UNIFORMS>				},
+			{ "gl_MaxGeometryTextureImageUnits",		getInteger<GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS>			},
+			{ "gl_MaxGeometryOutputVertices",			getInteger<GL_MAX_GEOMETRY_OUTPUT_VERTICES>				},
+			{ "gl_MaxGeometryTotalOutputComponents",	getInteger<GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS>		},
+			{ "gl_MaxGeometryUniformComponents",		getInteger<GL_MAX_GEOMETRY_UNIFORM_COMPONENTS>			},
+			{ "gl_MaxGeometryAtomicCounters",			getInteger<GL_MAX_GEOMETRY_ATOMIC_COUNTERS>				},
+			{ "gl_MaxGeometryAtomicCounterBuffers",		getInteger<GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS>		},
+		};
+
+		tcu::TestCaseGroup* const geomGroup = new tcu::TestCaseGroup(m_testCtx, "geometry_shader", "GL_EXT_geometry_shader");
+		addChild(geomGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intConstants); ndx++)
+			geomGroup->addChild(new ShaderBuiltinConstantCase<int>(m_context, intConstants[ndx].varName, intConstants[ndx].getValue, "GL_EXT_geometry_shader"));
+	}
+
+	// EXT_tessellation_shader
+	{
+		static const struct
+		{
+			const char*												varName;
+			ShaderBuiltinConstantCase<int>::GetConstantValueFunc	getValue;
+		} intConstants[] =
+		{
+			{ "gl_MaxTessControlInputComponents",			getInteger<GL_MAX_TESS_CONTROL_INPUT_COMPONENTS>		},
+			{ "gl_MaxTessControlOutputComponents",			getInteger<GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS>		},
+			{ "gl_MaxTessControlTextureImageUnits",			getInteger<GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS>		},
+			{ "gl_MaxTessControlUniformComponents",			getInteger<GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS>		},
+			{ "gl_MaxTessControlTotalOutputComponents",		getInteger<GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS>	},
+
+			{ "gl_MaxTessEvaluationInputComponents",		getInteger<GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS>		},
+			{ "gl_MaxTessEvaluationOutputComponents",		getInteger<GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS>	},
+			{ "gl_MaxTessEvaluationTextureImageUnits",		getInteger<GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS>	},
+			{ "gl_MaxTessEvaluationUniformComponents",		getInteger<GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS>	},
+
+			{ "gl_MaxTessPatchComponents",					getInteger<GL_MAX_TESS_PATCH_COMPONENTS>				},
+
+			{ "gl_MaxPatchVertices",						getInteger<GL_MAX_PATCH_VERTICES>						},
+			{ "gl_MaxTessGenLevel",							getInteger<GL_MAX_TESS_GEN_LEVEL>						},
+		};
+
+		tcu::TestCaseGroup* const tessGroup = new tcu::TestCaseGroup(m_testCtx, "tessellation_shader", "GL_EXT_tessellation_shader");
+		addChild(tessGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intConstants); ndx++)
+			tessGroup->addChild(new ShaderBuiltinConstantCase<int>(m_context, intConstants[ndx].varName, intConstants[ndx].getValue, "GL_EXT_tessellation_shader"));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderBuiltinConstantTests.hpp b/modules/gles31/functional/es31fShaderBuiltinConstantTests.hpp
new file mode 100644
index 0000000..82653be
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderBuiltinConstantTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES31FSHADERBUILTINCONSTANTTESTS_HPP
+#define _ES31FSHADERBUILTINCONSTANTTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader built-in constant tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderBuiltinConstantTests : public TestCaseGroup
+{
+public:
+								ShaderBuiltinConstantTests	(Context& context);
+	virtual						~ShaderBuiltinConstantTests	(void);
+
+	virtual void				init						(void);
+
+private:
+								ShaderBuiltinConstantTests	(const ShaderBuiltinConstantTests&);
+	ShaderBuiltinConstantTests&	operator=					(const ShaderBuiltinConstantTests&);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERBUILTINCONSTANTTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderCommonFunctionTests.cpp b/modules/gles31/functional/es31fShaderCommonFunctionTests.cpp
new file mode 100644
index 0000000..e444dfa
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderCommonFunctionTests.cpp
@@ -0,0 +1,2097 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Common built-in function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderCommonFunctionTests.hpp"
+#include "gluContextInfo.hpp"
+#include "glsShaderExecUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuFormatUtil.hpp"
+#include "tcuFloat.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using namespace gls::ShaderExecUtil;
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+
+// Utilities
+
+template<typename T, int Size>
+struct VecArrayAccess
+{
+public:
+									VecArrayAccess	(const void* ptr) : m_array((tcu::Vector<T, Size>*)ptr) {}
+									~VecArrayAccess	(void) {}
+
+	const tcu::Vector<T, Size>&		operator[]		(size_t offset) const	{ return m_array[offset];	}
+	tcu::Vector<T, Size>&			operator[]		(size_t offset)			{ return m_array[offset];	}
+
+private:
+	tcu::Vector<T, Size>*			m_array;
+};
+
+template<typename T>	T			randomScalar	(de::Random& rnd, T minValue, T maxValue);
+template<> inline		float		randomScalar	(de::Random& rnd, float minValue, float maxValue)		{ return rnd.getFloat(minValue, maxValue);	}
+template<> inline		deInt32		randomScalar	(de::Random& rnd, deInt32 minValue, deInt32 maxValue)	{ return rnd.getInt(minValue, maxValue);	}
+template<> inline		deUint32	randomScalar	(de::Random& rnd, deUint32 minValue, deUint32 maxValue)	{ return minValue + rnd.getUint32() % (maxValue - minValue + 1); }
+
+template<typename T, int Size>
+inline tcu::Vector<T, Size> randomVector (de::Random& rnd, const tcu::Vector<T, Size>& minValue, const tcu::Vector<T, Size>& maxValue)
+{
+	tcu::Vector<T, Size> res;
+	for (int ndx = 0; ndx < Size; ndx++)
+		res[ndx] = randomScalar<T>(rnd, minValue[ndx], maxValue[ndx]);
+	return res;
+}
+
+template<typename T, int Size>
+static void fillRandomVectors (de::Random& rnd, const tcu::Vector<T, Size>& minValue, const tcu::Vector<T, Size>& maxValue, void* dst, int numValues, int offset = 0)
+{
+	VecArrayAccess<T, Size> access(dst);
+	for (int ndx = 0; ndx < numValues; ndx++)
+		access[offset + ndx] = randomVector<T, Size>(rnd, minValue, maxValue);
+}
+
+template<typename T>
+static void fillRandomScalars (de::Random& rnd, T minValue, T maxValue, void* dst, int numValues, int offset = 0)
+{
+	T* typedPtr = (T*)dst;
+	for (int ndx = 0; ndx < numValues; ndx++)
+		typedPtr[offset + ndx] = randomScalar<T>(rnd, minValue, maxValue);
+}
+
+inline int numBitsLostInOp (float input, float output)
+{
+	const int	inExp		= tcu::Float32(input).exponent();
+	const int	outExp		= tcu::Float32(output).exponent();
+
+	return de::max(0, inExp-outExp); // Lost due to mantissa shift.
+}
+
+inline deUint32 getUlpDiff (float a, float b)
+{
+	const deUint32	aBits	= tcu::Float32(a).bits();
+	const deUint32	bBits	= tcu::Float32(b).bits();
+	return aBits > bBits ? aBits - bBits : bBits - aBits;
+}
+
+inline deUint32 getUlpDiffIgnoreZeroSign (float a, float b)
+{
+	if (tcu::Float32(a).isZero())
+		return getUlpDiff(tcu::Float32::construct(tcu::Float32(b).sign(), 0, 0).asFloat(), b);
+	else if (tcu::Float32(b).isZero())
+		return getUlpDiff(a, tcu::Float32::construct(tcu::Float32(a).sign(), 0, 0).asFloat());
+	else
+		return getUlpDiff(a, b);
+}
+
+inline bool supportsSignedZero (glu::Precision precision)
+{
+	// \note GLSL ES 3.1 doesn't really require support for -0, but we require it for highp
+	//		 as it is very widely supported.
+	return precision == glu::PRECISION_HIGHP;
+}
+
+inline float getEpsFromMaxUlpDiff (float value, deUint32 ulpDiff)
+{
+	const int exp = tcu::Float32(value).exponent();
+	return tcu::Float32::construct(+1, exp, (1u<<23) | ulpDiff).asFloat() - tcu::Float32::construct(+1, exp, 1u<<23).asFloat();
+}
+
+inline deUint32 getMaxUlpDiffFromBits (int numAccurateBits)
+{
+	const int		numGarbageBits	= 23-numAccurateBits;
+	const deUint32	mask			= (1u<<numGarbageBits)-1u;
+
+	return mask;
+}
+
+inline float getEpsFromBits (float value, int numAccurateBits)
+{
+	return getEpsFromMaxUlpDiff(value, getMaxUlpDiffFromBits(numAccurateBits));
+}
+
+static int getMinMantissaBits (glu::Precision precision)
+{
+	const int bits[] =
+	{
+		7,		// lowp
+		10,		// mediump
+		23		// highp
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(bits) == glu::PRECISION_LAST);
+	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(bits)));
+	return bits[precision];
+}
+
+// CommonFunctionCase
+
+class CommonFunctionCase : public TestCase
+{
+public:
+							CommonFunctionCase		(Context& context, const char* name, const char* description, glu::ShaderType shaderType);
+							~CommonFunctionCase		(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+protected:
+							CommonFunctionCase		(const CommonFunctionCase& other);
+	CommonFunctionCase&		operator=				(const CommonFunctionCase& other);
+
+	virtual void			getInputValues			(int numValues, void* const* values) const = 0;
+	virtual bool			compare					(const void* const* inputs, const void* const* outputs) = 0;
+
+	glu::ShaderType			m_shaderType;
+	ShaderSpec				m_spec;
+	int						m_numValues;
+
+	std::ostringstream		m_failMsg;				//!< Comparison failure help message.
+
+private:
+	ShaderExecutor*			m_executor;
+};
+
+CommonFunctionCase::CommonFunctionCase (Context& context, const char* name, const char* description, glu::ShaderType shaderType)
+	: TestCase		(context, name, description)
+	, m_shaderType	(shaderType)
+	, m_numValues	(100)
+	, m_executor	(DE_NULL)
+{
+	m_spec.version = glu::GLSL_VERSION_310_ES;
+}
+
+CommonFunctionCase::~CommonFunctionCase (void)
+{
+	CommonFunctionCase::deinit();
+}
+
+void CommonFunctionCase::init (void)
+{
+	DE_ASSERT(!m_executor);
+
+	m_executor = createExecutor(m_context.getRenderContext(), m_shaderType, m_spec);
+	m_testCtx.getLog() << m_executor;
+
+	if (!m_executor->isOk())
+		throw tcu::TestError("Compile failed");
+}
+
+void CommonFunctionCase::deinit (void)
+{
+	delete m_executor;
+	m_executor = DE_NULL;
+}
+
+static vector<int> getScalarSizes (const vector<Symbol>& symbols)
+{
+	vector<int> sizes(symbols.size());
+	for (int ndx = 0; ndx < (int)symbols.size(); ++ndx)
+		sizes[ndx] = symbols[ndx].varType.getScalarSize();
+	return sizes;
+}
+
+static int computeTotalScalarSize (const vector<Symbol>& symbols)
+{
+	int totalSize = 0;
+	for (vector<Symbol>::const_iterator sym = symbols.begin(); sym != symbols.end(); ++sym)
+		totalSize += sym->varType.getScalarSize();
+	return totalSize;
+}
+
+static vector<void*> getInputOutputPointers (const vector<Symbol>& symbols, vector<deUint32>& data, const int numValues)
+{
+	vector<void*>	pointers		(symbols.size());
+	int				curScalarOffset	= 0;
+
+	for (int varNdx = 0; varNdx < (int)symbols.size(); ++varNdx)
+	{
+		const Symbol&	var				= symbols[varNdx];
+		const int		scalarSize		= var.varType.getScalarSize();
+
+		// Uses planar layout as input/output specs do not support strides.
+		pointers[varNdx] = &data[curScalarOffset];
+		curScalarOffset += scalarSize*numValues;
+	}
+
+	DE_ASSERT(curScalarOffset == (int)data.size());
+
+	return pointers;
+}
+
+// \todo [2013-08-08 pyry] Make generic utility and move to glu?
+
+struct HexFloat
+{
+	const float value;
+	HexFloat (const float value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const HexFloat& v)
+{
+	return str << v.value << " / " << tcu::toHex(tcu::Float32(v.value).bits());
+}
+
+struct HexBool
+{
+	const deUint32 value;
+	HexBool (const deUint32 value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const HexBool& v)
+{
+	return str << (v.value ? "true" : "false") << " / " << tcu::toHex(v.value);
+}
+
+struct VarValue
+{
+	const glu::VarType&	type;
+	const void*			value;
+
+	VarValue (const glu::VarType& type_, const void* value_) : type(type_), value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const VarValue& varValue)
+{
+	DE_ASSERT(varValue.type.isBasicType());
+
+	const glu::DataType		basicType		= varValue.type.getBasicType();
+	const glu::DataType		scalarType		= glu::getDataTypeScalarType(basicType);
+	const int				numComponents	= glu::getDataTypeScalarSize(basicType);
+
+	if (numComponents > 1)
+		str << glu::getDataTypeName(basicType) << "(";
+
+	for (int compNdx = 0; compNdx < numComponents; compNdx++)
+	{
+		if (compNdx != 0)
+			str << ", ";
+
+		switch (scalarType)
+		{
+			case glu::TYPE_FLOAT:	str << HexFloat(((const float*)varValue.value)[compNdx]);			break;
+			case glu::TYPE_INT:		str << ((const deInt32*)varValue.value)[compNdx];					break;
+			case glu::TYPE_UINT:	str << tcu::toHex(((const deUint32*)varValue.value)[compNdx]);		break;
+			case glu::TYPE_BOOL:	str << HexBool(((const deUint32*)varValue.value)[compNdx]);			break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	if (numComponents > 1)
+		str << ")";
+
+	return str;
+}
+
+CommonFunctionCase::IterateResult CommonFunctionCase::iterate (void)
+{
+	const int				numInputScalars			= computeTotalScalarSize(m_spec.inputs);
+	const int				numOutputScalars		= computeTotalScalarSize(m_spec.outputs);
+	vector<deUint32>		inputData				(numInputScalars * m_numValues);
+	vector<deUint32>		outputData				(numOutputScalars * m_numValues);
+	const vector<void*>		inputPointers			= getInputOutputPointers(m_spec.inputs, inputData, m_numValues);
+	const vector<void*>		outputPointers			= getInputOutputPointers(m_spec.outputs, outputData, m_numValues);
+
+	// Initialize input data.
+	getInputValues(m_numValues, &inputPointers[0]);
+
+	// Execute shader.
+	m_executor->useProgram();
+	m_executor->execute(m_numValues, &inputPointers[0], &outputPointers[0]);
+
+	// Compare results.
+	{
+		const vector<int>		inScalarSizes		= getScalarSizes(m_spec.inputs);
+		const vector<int>		outScalarSizes		= getScalarSizes(m_spec.outputs);
+		vector<void*>			curInputPtr			(inputPointers.size());
+		vector<void*>			curOutputPtr		(outputPointers.size());
+		int						numFailed			= 0;
+
+		for (int valNdx = 0; valNdx < m_numValues; valNdx++)
+		{
+			// Set up pointers for comparison.
+			for (int inNdx = 0; inNdx < (int)curInputPtr.size(); ++inNdx)
+				curInputPtr[inNdx] = (deUint32*)inputPointers[inNdx] + inScalarSizes[inNdx]*valNdx;
+
+			for (int outNdx = 0; outNdx < (int)curOutputPtr.size(); ++outNdx)
+				curOutputPtr[outNdx] = (deUint32*)outputPointers[outNdx] + outScalarSizes[outNdx]*valNdx;
+
+			if (!compare(&curInputPtr[0], &curOutputPtr[0]))
+			{
+				// \todo [2013-08-08 pyry] We probably want to log reference value as well?
+
+				m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed for value " << valNdx << ":\n  " << m_failMsg.str() << TestLog::EndMessage;
+
+				m_testCtx.getLog() << TestLog::Message << "  inputs:" << TestLog::EndMessage;
+				for (int inNdx = 0; inNdx < (int)curInputPtr.size(); inNdx++)
+					m_testCtx.getLog() << TestLog::Message << "    " << m_spec.inputs[inNdx].name << " = "
+														   << VarValue(m_spec.inputs[inNdx].varType, curInputPtr[inNdx])
+									   << TestLog::EndMessage;
+
+				m_testCtx.getLog() << TestLog::Message << "  outputs:" << TestLog::EndMessage;
+				for (int outNdx = 0; outNdx < (int)curOutputPtr.size(); outNdx++)
+					m_testCtx.getLog() << TestLog::Message << "    " << m_spec.outputs[outNdx].name << " = "
+														   << VarValue(m_spec.outputs[outNdx].varType, curOutputPtr[outNdx])
+									   << TestLog::EndMessage;
+
+				m_failMsg.str("");
+				m_failMsg.clear();
+				numFailed += 1;
+			}
+		}
+
+		m_testCtx.getLog() << TestLog::Message << (m_numValues - numFailed) << " / " << m_numValues << " values passed" << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								numFailed == 0 ? "Pass"					: "Result comparison failed");
+	}
+
+	return STOP;
+}
+
+static const char* getPrecisionPostfix (glu::Precision precision)
+{
+	static const char* s_postfix[] =
+	{
+		"_lowp",
+		"_mediump",
+		"_highp"
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_postfix) == glu::PRECISION_LAST);
+	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
+	return s_postfix[precision];
+}
+
+static const char* getShaderTypePostfix (glu::ShaderType shaderType)
+{
+	static const char* s_postfix[] =
+	{
+		"_vertex",
+		"_fragment",
+		"_geometry",
+		"_tess_control",
+		"_tess_eval",
+		"_compute"
+	};
+	DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
+	return s_postfix[shaderType];
+}
+
+static std::string getCommonFuncCaseName (glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+{
+	return string(glu::getDataTypeName(baseType)) + getPrecisionPostfix(precision) + getShaderTypePostfix(shaderType);
+}
+
+class AbsCase : public CommonFunctionCase
+{
+public:
+	AbsCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "abs", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = abs(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 floatRanges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+		const IVec2 intRanges[] =
+		{
+			IVec2(-(1<<7)+1,	(1<<7)-1),
+			IVec2(-(1<<15)+1,	(1<<15)-1),
+			IVec2(0x80000001,	0x7fffffff)
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0x235facu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		if (glu::isDataTypeFloatOrVec(type))
+			fillRandomScalars(rnd, floatRanges[precision].x(), floatRanges[precision].y(), values[0], numValues*scalarSize);
+		else
+			fillRandomScalars(rnd, intRanges[precision].x(), intRanges[precision].y(), values[0], numValues*scalarSize);
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (glu::isDataTypeFloatOrVec(type))
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= (1u<<(23-mantissaBits))-1u;
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref0		= de::abs(in0);
+				const deUint32	ulpDiff0	= getUlpDiff(out0, ref0);
+
+				if (ulpDiff0 > maxUlpDiff)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref0) << " with ULP threshold " << maxUlpDiff << ", got ULP diff " << ulpDiff0;
+					return false;
+				}
+			}
+		}
+		else
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const int	in0		= ((const int*)inputs[0])[compNdx];
+				const int	out0	= ((const int*)outputs[0])[compNdx];
+				const int	ref0	= de::abs(in0);
+
+				if (out0 != ref0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << ref0;
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class SignCase : public CommonFunctionCase
+{
+public:
+	SignCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "sign", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = sign(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 floatRanges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e4f,		1e4f),	// mediump	- note: may end up as inf
+			Vec2(-1e8f,		1e8f)	// highp	- note: may end up as inf
+		};
+		const IVec2 intRanges[] =
+		{
+			IVec2(-(1<<7),		(1<<7)-1),
+			IVec2(-(1<<15),		(1<<15)-1),
+			IVec2(0x80000000,	0x7fffffff)
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0x324u);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		if (glu::isDataTypeFloatOrVec(type))
+		{
+			// Special cases.
+			std::fill((float*)values[0], (float*)values[0] + scalarSize, +1.0f);
+			std::fill((float*)values[0], (float*)values[0] + scalarSize, -1.0f);
+			std::fill((float*)values[0], (float*)values[0] + scalarSize,  0.0f);
+			fillRandomScalars(rnd, floatRanges[precision].x(), floatRanges[precision].y(), (float*)values[0] + scalarSize*3, (numValues-3)*scalarSize);
+		}
+		else
+		{
+			std::fill((int*)values[0], (int*)values[0] + scalarSize, +1);
+			std::fill((int*)values[0], (int*)values[0] + scalarSize, -1);
+			std::fill((int*)values[0], (int*)values[0] + scalarSize,  0);
+			fillRandomScalars(rnd, intRanges[precision].x(), intRanges[precision].y(), (int*)values[0] + scalarSize*3, (numValues-3)*scalarSize);
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (glu::isDataTypeFloatOrVec(type))
+		{
+			// Both highp and mediump should be able to represent -1, 0, and +1 exactly
+			const deUint32 maxUlpDiff = precision == glu::PRECISION_LOWP ? getMaxUlpDiffFromBits(getMinMantissaBits(precision)) : 0;
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref0		= in0 < 0.0f ? -1.0f :
+											  in0 > 0.0f ? +1.0f : 0.0f;
+				const deUint32	ulpDiff0	= getUlpDiff(out0, ref0);
+
+				if (ulpDiff0 > maxUlpDiff)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref0) << " with ULP threshold " << maxUlpDiff << ", got ULP diff " << ulpDiff0;
+					return false;
+				}
+			}
+		}
+		else
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const int	in0		= ((const int*)inputs[0])[compNdx];
+				const int	out0	= ((const int*)outputs[0])[compNdx];
+				const int	ref0	= in0 < 0 ? -1 :
+									  in0 > 0 ? +1 : 0;
+
+				if (out0 != ref0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << ref0;
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+static float roundEven (float v)
+{
+	const float		q			= deFloatFrac(v);
+	const int		truncated	= int(v-q);
+	const int		rounded		= (q > 0.5f)							? (truncated + 1) :	// Rounded up
+									(q == 0.5f && (truncated % 2 != 0))	? (truncated + 1) :	// Round to nearest even at 0.5
+									truncated;												// Rounded down
+
+	return float(rounded);
+}
+
+class RoundEvenCase : public CommonFunctionCase
+{
+public:
+	RoundEvenCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "roundEven", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = roundEven(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd				(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		int						numSpecialCases	= 0;
+
+		// Special cases.
+		if (precision != glu::PRECISION_LOWP)
+		{
+			DE_ASSERT(numValues >= 20);
+			for (int ndx = 0; ndx < 20; ndx++)
+			{
+				const float v = de::clamp(float(ndx) - 10.5f, ranges[precision].x(), ranges[precision].y());
+				std::fill((float*)values[0], (float*)values[0] + scalarSize, v);
+				numSpecialCases += 1;
+			}
+		}
+
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + numSpecialCases*scalarSize, (numValues-numSpecialCases)*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasSignedZero	= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			// Require exact rounding result.
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref			= roundEven(in0);
+
+				const deUint32	ulpDiff		= hasSignedZero ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref);
+
+				if (ulpDiff > 0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);	// ULP diff for rounded integer value.
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const int		minRes		= int(roundEven(in0-eps));
+				const int		maxRes		= int(roundEven(in0+eps));
+				bool			anyOk		= false;
+
+				for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++)
+				{
+					const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal));
+
+					if (ulpDiff <= maxUlpDiff)
+					{
+						anyOk = true;
+						break;
+					}
+				}
+
+				if (!anyOk)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff);
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class ModfCase : public CommonFunctionCase
+{
+public:
+	ModfCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "modf", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out1", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = modf(in0, out1);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), values[0], numValues*scalarSize);
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasZeroSign		= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		const int				mantissaBits	= getMinMantissaBits(precision);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const float		in0			= ((const float*)inputs[0])[compNdx];
+			const float		out0		= ((const float*)outputs[0])[compNdx];
+			const float		out1		= ((const float*)outputs[1])[compNdx];
+
+			const float		refOut1		= float(int(in0));
+			const float		refOut0		= in0 - refOut1;
+
+			const int		bitsLost	= precision != glu::PRECISION_HIGHP ? numBitsLostInOp(in0, refOut0) : 0;
+			const deUint32	maxUlpDiff	= getMaxUlpDiffFromBits(de::max(mantissaBits - bitsLost, 0));
+
+			const float		resSum		= out0 + out1;
+
+			const deUint32	ulpDiff		= hasZeroSign ? getUlpDiff(resSum, in0) : getUlpDiffIgnoreZeroSign(resSum, in0);
+
+			if (ulpDiff > maxUlpDiff)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = (" << HexFloat(refOut0) << ") + (" << HexFloat(refOut1) << ") = " << HexFloat(in0) << " with ULP threshold "
+							<< tcu::toHex(maxUlpDiff) << ", got ULP diff " << tcu::toHex(ulpDiff);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class IsnanCase : public CommonFunctionCase
+{
+public:
+	IsnanCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "isnan", shaderType)
+	{
+		DE_ASSERT(glu::isDataTypeFloatOrVec(baseType));
+
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	boolType	= vecSize > 1 ? glu::getDataTypeBoolVec(vecSize) : glu::TYPE_BOOL;
+
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(boolType, glu::PRECISION_LAST)));
+		m_spec.source = "out0 = isnan(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd				(deStringHash(getName()) ^ 0xc2a39fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const int				mantissaBits	= getMinMantissaBits(precision);
+		const deUint32			mantissaMask	= ~getMaxUlpDiffFromBits(mantissaBits) & ((1u<<23)-1u);
+
+		for (int valNdx = 0; valNdx < numValues*scalarSize; valNdx++)
+		{
+			const bool		isNan		= rnd.getFloat() > 0.3f;
+			const bool		isInf		= !isNan && rnd.getFloat() > 0.4f;
+			const deUint32	mantissa	= !isInf ? ((1u<<22) | (rnd.getUint32() & mantissaMask)) : 0;
+			const deUint32	exp			= !isNan && !isInf ? (rnd.getUint32() & 0x7fu) : 0xffu;
+			const deUint32	sign		= rnd.getUint32() & 0x1u;
+			const deUint32	value		= (sign << 31) | (exp << 23) | mantissa;
+
+			DE_ASSERT(tcu::Float32(value).isInf() == isInf && tcu::Float32(value).isNaN() == isNan);
+
+			((deUint32*)values[0])[valNdx] = value;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP)
+		{
+			// Only highp is required to support inf/nan
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0		= ((const float*)inputs[0])[compNdx];
+				const deUint32	out0	= ((const deUint32*)outputs[0])[compNdx];
+				const deUint32	ref		= tcu::Float32(in0).isNaN() ? 1u : 0u;
+
+				if (out0 != ref)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexBool(ref);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			// Value can be either 0 or 1
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const int out0 = ((const int*)outputs[0])[compNdx];
+
+				if (out0 != 0 && out0 != 1)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = 0 / 1";
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class IsinfCase : public CommonFunctionCase
+{
+public:
+	IsinfCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "isinf", shaderType)
+	{
+		DE_ASSERT(glu::isDataTypeFloatOrVec(baseType));
+
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	boolType	= vecSize > 1 ? glu::getDataTypeBoolVec(vecSize) : glu::TYPE_BOOL;
+
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(boolType, glu::PRECISION_LAST)));
+		m_spec.source = "out0 = isinf(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd				(deStringHash(getName()) ^ 0xc2a39fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const int				mantissaBits	= getMinMantissaBits(precision);
+		const deUint32			mantissaMask	= ~getMaxUlpDiffFromBits(mantissaBits) & ((1u<<23)-1u);
+
+		for (int valNdx = 0; valNdx < numValues*scalarSize; valNdx++)
+		{
+			const bool		isInf		= rnd.getFloat() > 0.3f;
+			const bool		isNan		= !isInf && rnd.getFloat() > 0.4f;
+			const deUint32	mantissa	= !isInf ? ((1u<<22) | (rnd.getUint32() & mantissaMask)) : 0;
+			const deUint32	exp			= !isNan && !isInf ? (rnd.getUint32() & 0x7fu) : 0xffu;
+			const deUint32	sign		= rnd.getUint32() & 0x1u;
+			const deUint32	value		= (sign << 31) | (exp << 23) | mantissa;
+
+			DE_ASSERT(tcu::Float32(value).isInf() == isInf && tcu::Float32(value).isNaN() == isNan);
+
+			((deUint32*)values[0])[valNdx] = value;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP)
+		{
+			// Only highp is required to support inf/nan
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0		= ((const float*)inputs[0])[compNdx];
+				const deUint32	out0	= ((const deUint32*)outputs[0])[compNdx];
+				const deUint32	ref		= tcu::Float32(in0).isInf() ? 1u : 0u;
+
+				if (out0 != ref)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexBool(ref);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			// Value can be either 0 or 1
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const int out0 = ((const int*)outputs[0])[compNdx];
+
+				if (out0 != 0 && out0 != 1)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = 0 / 1";
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class FloatBitsToUintIntCase : public CommonFunctionCase
+{
+public:
+	FloatBitsToUintIntCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType, bool outIsSigned)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), outIsSigned ? "floatBitsToInt" : "floatBitsToUint", shaderType)
+	{
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	intType		= outIsSigned ? (vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT)
+													  : (vecSize > 1 ? glu::getDataTypeUintVec(vecSize) : glu::TYPE_UINT);
+
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(intType, glu::PRECISION_HIGHP)));
+		m_spec.source = outIsSigned ? "out0 = floatBitsToInt(in0);" : "out0 = floatBitsToUint(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0x2790au);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), values[0], numValues*scalarSize);
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		const int				mantissaBits	= getMinMantissaBits(precision);
+		const int				maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const float		in0			= ((const float*)inputs[0])[compNdx];
+			const deUint32	out0		= ((const deUint32*)outputs[0])[compNdx];
+			const deUint32	refOut0		= tcu::Float32(in0).bits();
+			const int		ulpDiff		= de::abs((int)out0 - (int)refOut0);
+
+			if (ulpDiff > maxUlpDiff)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(refOut0) << " with threshold "
+							<< tcu::toHex(maxUlpDiff) << ", got diff " << tcu::toHex(ulpDiff);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class FloatBitsToIntCase : public FloatBitsToUintIntCase
+{
+public:
+	FloatBitsToIntCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: FloatBitsToUintIntCase(context, baseType, precision, shaderType, true)
+	{
+	}
+};
+
+class FloatBitsToUintCase : public FloatBitsToUintIntCase
+{
+public:
+	FloatBitsToUintCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: FloatBitsToUintIntCase(context, baseType, precision, shaderType, false)
+	{
+	}
+};
+
+class BitsToFloatCase : public CommonFunctionCase
+{
+public:
+	BitsToFloatCase (Context& context, glu::DataType baseType, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, glu::PRECISION_HIGHP, shaderType).c_str(), glu::isDataTypeIntOrIVec(baseType) ? "intBitsToFloat" : "uintBitsToFloat", shaderType)
+	{
+		const bool			inIsSigned	= glu::isDataTypeIntOrIVec(baseType);
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	floatType	= vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT;
+
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(floatType, glu::PRECISION_HIGHP)));
+		m_spec.source = inIsSigned ? "out0 = intBitsToFloat(in0);" : "out0 = uintBitsToFloat(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0xbbb225u);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		const Vec2				range		(-1e8f, +1e8f);
+
+		// \note Filled as floats.
+		fillRandomScalars(rnd, range.x(), range.y(), values[0], numValues*scalarSize);
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const int				maxUlpDiff		= 0;
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const float		in0			= ((const float*)inputs[0])[compNdx];
+			const float		out0		= ((const float*)outputs[0])[compNdx];
+			const int		ulpDiff		= de::abs((int)in0 - (int)out0);
+
+			if (ulpDiff > maxUlpDiff)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(in0) << " with ULP threshold "
+							<< tcu::toHex(maxUlpDiff) << ", got ULP diff " << tcu::toHex(ulpDiff);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class FloorCase : public CommonFunctionCase
+{
+public:
+	FloorCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "floor", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = floor(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0], numValues*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			// Require exact result.
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref			= deFloatFloor(in0);
+
+				const deUint32	ulpDiff		= getUlpDiff(out0, ref);
+
+				if (ulpDiff > 0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);	// ULP diff for rounded integer value.
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const int		minRes		= int(deFloatFloor(in0-eps));
+				const int		maxRes		= int(deFloatFloor(in0+eps));
+				bool			anyOk		= false;
+
+				for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++)
+				{
+					const deUint32 ulpDiff = getUlpDiff(out0, float(roundedVal));
+
+					if (ulpDiff <= maxUlpDiff)
+					{
+						anyOk = true;
+						break;
+					}
+				}
+
+				if (!anyOk)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff);
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class TruncCase : public CommonFunctionCase
+{
+public:
+	TruncCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "trunc", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = trunc(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd				(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const float				specialCases[]	= { 0.0f, -0.0f, -0.9f, 0.9f, 1.0f, -1.0f };
+		const int				numSpecialCases	= DE_LENGTH_OF_ARRAY(specialCases);
+
+		// Special cases
+		for (int caseNdx = 0; caseNdx < numSpecialCases; caseNdx++)
+		{
+			for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
+				((float*)values[0])[caseNdx*scalarSize + scalarNdx] = specialCases[caseNdx];
+		}
+
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + scalarSize*numSpecialCases, (numValues-numSpecialCases)*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasSignedZero	= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			// Require exact result.
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const bool		isNeg		= tcu::Float32(in0).sign() < 0;
+				const float		ref			= isNeg ? (-float(int(-in0))) : float(int(in0));
+
+				const deUint32	ulpDiff		= hasSignedZero ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref);
+
+				if (ulpDiff > 0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);	// ULP diff for rounded integer value.
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const int		minRes		= int(in0-eps);
+				const int		maxRes		= int(in0+eps);
+				bool			anyOk		= false;
+
+				for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++)
+				{
+					const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal));
+
+					if (ulpDiff <= maxUlpDiff)
+					{
+						anyOk = true;
+						break;
+					}
+				}
+
+				if (!anyOk)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff);
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class RoundCase : public CommonFunctionCase
+{
+public:
+	RoundCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "round", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = round(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd				(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		int						numSpecialCases	= 0;
+
+		// Special cases.
+		if (precision != glu::PRECISION_LOWP)
+		{
+			DE_ASSERT(numValues >= 10);
+			for (int ndx = 0; ndx < 10; ndx++)
+			{
+				const float v = de::clamp(float(ndx) - 5.5f, ranges[precision].x(), ranges[precision].y());
+				std::fill((float*)values[0], (float*)values[0] + scalarSize, v);
+				numSpecialCases += 1;
+			}
+		}
+
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + numSpecialCases*scalarSize, (numValues-numSpecialCases)*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasZeroSign		= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+
+				if (deFloatFrac(in0) == 0.5f)
+				{
+					// Allow both ceil(in) and floor(in)
+					const float		ref0		= deFloatFloor(in0);
+					const float		ref1		= deFloatCeil(in0);
+					const deUint32	ulpDiff0	= hasZeroSign ? getUlpDiff(out0, ref0) : getUlpDiffIgnoreZeroSign(out0, ref0);
+					const deUint32	ulpDiff1	= hasZeroSign ? getUlpDiff(out0, ref1) : getUlpDiffIgnoreZeroSign(out0, ref1);
+
+					if (ulpDiff0 > 0 && ulpDiff1 > 0)
+					{
+						m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref0) << " or " << HexFloat(ref1) << ", got ULP diff " << tcu::toHex(de::min(ulpDiff0, ulpDiff1));
+						return false;
+					}
+				}
+				else
+				{
+					// Require exact result
+					const float		ref		= roundEven(in0);
+					const deUint32	ulpDiff	= hasZeroSign ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref);
+
+					if (ulpDiff > 0)
+					{
+						m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+						return false;
+					}
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);	// ULP diff for rounded integer value.
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const int		minRes		= int(roundEven(in0-eps));
+				const int		maxRes		= int(roundEven(in0+eps));
+				bool			anyOk		= false;
+
+				for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++)
+				{
+					const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal));
+
+					if (ulpDiff <= maxUlpDiff)
+					{
+						anyOk = true;
+						break;
+					}
+				}
+
+				if (!anyOk)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff);
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class CeilCase : public CommonFunctionCase
+{
+public:
+	CeilCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "ceil", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = ceil(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0], numValues*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasZeroSign		= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			// Require exact result.
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref			= deFloatCeil(in0);
+
+				const deUint32	ulpDiff		= hasZeroSign ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref);
+
+				if (ulpDiff > 0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const deUint32	maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);	// ULP diff for rounded integer value.
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const int		minRes		= int(deFloatCeil(in0-eps));
+				const int		maxRes		= int(deFloatCeil(in0+eps));
+				bool			anyOk		= false;
+
+				for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++)
+				{
+					const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal));
+
+					if (ulpDiff <= maxUlpDiff)
+					{
+						anyOk = true;
+						break;
+					}
+				}
+
+				if (!anyOk && de::inRange(0, minRes, maxRes))
+				{
+					// Allow -0 as well.
+					const int ulpDiff = de::abs((int)tcu::Float32(out0).bits() - (int)0x80000000u);
+					anyOk = ((deUint32)ulpDiff <= maxUlpDiff);
+				}
+
+				if (!anyOk)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff);
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+class FractCase : public CommonFunctionCase
+{
+public:
+	FractCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "fract", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
+		m_spec.source = "out0 = fract(in0);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd				(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		int						numSpecialCases	= 0;
+
+		// Special cases.
+		if (precision != glu::PRECISION_LOWP)
+		{
+			DE_ASSERT(numValues >= 10);
+			for (int ndx = 0; ndx < 10; ndx++)
+			{
+				const float v = de::clamp(float(ndx) - 5.5f, ranges[precision].x(), ranges[precision].y());
+				std::fill((float*)values[0], (float*)values[0] + scalarSize, v);
+				numSpecialCases += 1;
+			}
+		}
+
+		// Random cases.
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + numSpecialCases*scalarSize, (numValues-numSpecialCases)*scalarSize);
+
+		// If precision is mediump, make sure values can be represented in fp16 exactly
+		if (precision == glu::PRECISION_MEDIUMP)
+		{
+			for (int ndx = 0; ndx < numValues*scalarSize; ndx++)
+				((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat();
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				hasZeroSign		= supportsSignedZero(precision);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP)
+		{
+			// Require exact result.
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+				const float		ref			= deFloatFrac(in0);
+
+				const deUint32	ulpDiff		= hasZeroSign ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref);
+
+				if (ulpDiff > 0)
+				{
+					m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff);
+					return false;
+				}
+			}
+		}
+		else
+		{
+			const int		mantissaBits	= getMinMantissaBits(precision);
+			const float		eps				= getEpsFromBits(1.0f, mantissaBits);	// epsilon for rounding bounds
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const float		in0			= ((const float*)inputs[0])[compNdx];
+				const float		out0		= ((const float*)outputs[0])[compNdx];
+
+				if (int(deFloatFloor(in0-eps)) == int(deFloatFloor(in0+eps)))
+				{
+					const float		ref			= deFloatFrac(in0);
+					const int		bitsLost	= numBitsLostInOp(in0, ref);
+					const deUint32	maxUlpDiff	= getMaxUlpDiffFromBits(de::max(0, mantissaBits-bitsLost));	// ULP diff for rounded integer value.
+					const deUint32	ulpDiff		= getUlpDiffIgnoreZeroSign(out0, ref);
+
+					if (ulpDiff > maxUlpDiff)
+					{
+						m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << " with ULP threshold " << tcu::toHex(maxUlpDiff) << ", got diff " << tcu::toHex(ulpDiff);
+						return false;
+					}
+				}
+				else
+				{
+					if (out0 >= 1.0f)
+					{
+						m_failMsg << "Expected [" << compNdx << "] < 1.0";
+						return false;
+					}
+				}
+			}
+		}
+
+		return true;
+	}
+};
+
+static inline void frexp (float in, float* significand, int* exponent)
+{
+	const tcu::Float32 fpValue(in);
+
+	if (!fpValue.isZero())
+	{
+		// Construct float that has exactly the mantissa, and exponent of -1.
+		*significand	= tcu::Float32::construct(fpValue.sign(), -1, fpValue.mantissa()).asFloat();
+		*exponent		= fpValue.exponent()+1;
+	}
+	else
+	{
+		*significand	= fpValue.sign() < 0 ? -0.0f : 0.0f;
+		*exponent		= 0;
+	}
+}
+
+static inline float ldexp (float significand, int exponent)
+{
+	const tcu::Float32 mant(significand);
+
+	if (exponent == 0 && mant.isZero())
+	{
+		return mant.sign() < 0 ? -0.0f : 0.0f;
+	}
+	else
+	{
+		return tcu::Float32::construct(mant.sign(), exponent+mant.exponent(), mant.mantissa()).asFloat();
+	}
+}
+
+class FrexpCase : public CommonFunctionCase
+{
+public:
+	FrexpCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "frexp", shaderType)
+	{
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	intType		= vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
+
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out1", glu::VarType(intType, glu::PRECISION_HIGHP)));
+		m_spec.source = "out0 = frexp(in0, out1);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd			(deStringHash(getName()) ^ 0x2790au);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+
+		// Special cases
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			((float*)values[0])[scalarSize*0 + compNdx] = 0.0f;
+			((float*)values[0])[scalarSize*1 + compNdx] = -0.0f;
+			((float*)values[0])[scalarSize*2 + compNdx] = 0.5f;
+			((float*)values[0])[scalarSize*3 + compNdx] = -0.5f;
+			((float*)values[0])[scalarSize*4 + compNdx] = 1.0f;
+			((float*)values[0])[scalarSize*5 + compNdx] = -1.0f;
+			((float*)values[0])[scalarSize*6 + compNdx] = 2.0f;
+			((float*)values[0])[scalarSize*7 + compNdx] = -2.0f;
+		}
+
+		fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + 8*scalarSize, (numValues-8)*scalarSize);
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const bool				signedZero		= supportsSignedZero(precision);
+
+		const int				mantissaBits	= getMinMantissaBits(precision);
+		const deUint32			maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const float		in0			= ((const float*)inputs[0])[compNdx];
+			const float		out0		= ((const float*)outputs[0])[compNdx];
+			const int		out1		= ((const int*)outputs[1])[compNdx];
+
+			float			refOut0;
+			int				refOut1;
+
+			frexp(in0, &refOut0, &refOut1);
+
+			const deUint32	ulpDiff0	= signedZero ? getUlpDiff(out0, refOut0) : getUlpDiffIgnoreZeroSign(out0, refOut0);
+
+			if (ulpDiff0 > maxUlpDiff || out1 != refOut1)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(refOut0) << ", " << refOut1 << " with ULP threshold "
+						  << tcu::toHex(maxUlpDiff) << ", got ULP diff " << tcu::toHex(ulpDiff0);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class LdexpCase : public CommonFunctionCase
+{
+public:
+	LdexpCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "ldexp", shaderType)
+	{
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	intType		= vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
+
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
+		m_spec.inputs.push_back(Symbol("in1", glu::VarType(intType, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, glu::PRECISION_HIGHP)));
+		m_spec.source = "out0 = ldexp(in0, in1);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd					(deStringHash(getName()) ^ 0x2790au);
+		const glu::DataType		type				= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision			= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize			= glu::getDataTypeScalarSize(type);
+		int						valueNdx			= 0;
+
+		{
+			const float easySpecialCases[] = { 0.0f, -0.0f, 0.5f, -0.5f, 1.0f, -1.0f, 2.0f, -2.0f };
+
+			DE_ASSERT(valueNdx + DE_LENGTH_OF_ARRAY(easySpecialCases) <= numValues);
+			for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(easySpecialCases); caseNdx++)
+			{
+				float	in0;
+				int		in1;
+
+				frexp(easySpecialCases[caseNdx], &in0, &in1);
+
+				for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+				{
+					((float*)values[0])[valueNdx*scalarSize + compNdx] = in0;
+					((int*)values[1])[valueNdx*scalarSize + compNdx] = in1;
+				}
+
+				valueNdx += 1;
+			}
+		}
+
+		{
+			// \note lowp and mediump can not necessarily fit the values in hard cases, so we'll use only easy ones.
+			const int numEasyRandomCases = precision == glu::PRECISION_HIGHP ? 50 : (numValues-valueNdx);
+
+			DE_ASSERT(valueNdx + numEasyRandomCases <= numValues);
+			for (int caseNdx = 0; caseNdx < numEasyRandomCases; caseNdx++)
+			{
+				for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+				{
+					const float	in	= rnd.getFloat(ranges[precision].x(), ranges[precision].y());
+					float		in0;
+					int			in1;
+
+					frexp(in, &in0, &in1);
+
+					((float*)values[0])[valueNdx*scalarSize + compNdx] = in0;
+					((int*)values[1])[valueNdx*scalarSize + compNdx] = in1;
+				}
+
+				valueNdx += 1;
+			}
+		}
+
+		{
+			const int numHardRandomCases = numValues-valueNdx;
+			DE_ASSERT(numHardRandomCases >= 0 && valueNdx + numHardRandomCases <= numValues);
+
+			for (int caseNdx = 0; caseNdx < numHardRandomCases; caseNdx++)
+			{
+				for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+				{
+					const int		fpExp		= rnd.getInt(-126, 127);
+					const int		sign		= rnd.getBool() ? -1 : +1;
+					const deUint32	mantissa	= (1u<<23) | (rnd.getUint32() & ((1u<<23)-1));
+					const int		in1			= rnd.getInt(de::max(-126, -126-fpExp), de::min(127, 127-fpExp));
+					const float		in0			= tcu::Float32::construct(sign, fpExp, mantissa).asFloat();
+
+					DE_ASSERT(de::inRange(in1, -126, 127)); // See Khronos bug 11180
+					DE_ASSERT(de::inRange(in1+fpExp, -126, 127));
+
+					const float		out			= ldexp(in0, in1);
+
+					DE_ASSERT(!tcu::Float32(out).isInf() && !tcu::Float32(out).isDenorm());
+					DE_UNREF(out);
+
+					((float*)values[0])[valueNdx*scalarSize + compNdx] = in0;
+					((int*)values[1])[valueNdx*scalarSize + compNdx] = in1;
+				}
+
+				valueNdx += 1;
+			}
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const bool				signedZero		= supportsSignedZero(precision);
+
+		const int				mantissaBits	= getMinMantissaBits(precision);
+		const deUint32			maxUlpDiff		= getMaxUlpDiffFromBits(mantissaBits);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const float		in0			= ((const float*)inputs[0])[compNdx];
+			const int		in1			= ((const int*)inputs[1])[compNdx];
+			const float		out0		= ((const float*)outputs[0])[compNdx];
+			const float		refOut0		= ldexp(in0, in1);
+			const deUint32	ulpDiff		= signedZero ? getUlpDiff(out0, refOut0) : getUlpDiffIgnoreZeroSign(out0, refOut0);
+
+			const int		inExp		= tcu::Float32(in0).exponent();
+
+			if (ulpDiff > maxUlpDiff)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(refOut0) << ", (exp = " << inExp << ") with ULP threshold "
+						  << tcu::toHex(maxUlpDiff) << ", got ULP diff " << tcu::toHex(ulpDiff);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class FmaCase : public CommonFunctionCase
+{
+public:
+	FmaCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "fma", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("a", glu::VarType(baseType, precision)));
+		m_spec.inputs.push_back(Symbol("b", glu::VarType(baseType, precision)));
+		m_spec.inputs.push_back(Symbol("c", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("res", glu::VarType(baseType, precision)));
+		m_spec.source = "res = fma(a, b, c);";
+		m_spec.globalDeclarations = "#extension GL_EXT_gpu_shader5 : require\n";
+	}
+
+	void init (void)
+	{
+		if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"))
+			throw tcu::NotSupportedError("GL_EXT_gpu_shader5 not supported");
+
+		CommonFunctionCase::init();
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		const Vec2 ranges[] =
+		{
+			Vec2(-2.0f,		2.0f),	// lowp
+			Vec2(-1e3f,		1e3f),	// mediump
+			Vec2(-1e7f,		1e7f)	// highp
+		};
+
+		de::Random				rnd				(deStringHash(getName()) ^ 0xac23fu);
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const float				specialCases[][3] =
+		{
+			// a		b		c
+			{ 0.0f,		0.0f,	0.0f },
+			{ 0.0f,		1.0f,	0.0f },
+			{ 0.0f,		0.0f,	-1.0f },
+			{ 1.0f,		1.0f,	0.0f },
+			{ 1.0f,		1.0f,	1.0f },
+			{ -1.0f,	1.0f,	0.0f },
+			{ 1.0f,		-1.0f,	0.0f },
+			{ -1.0f,	-1.0f,	0.0f },
+			{ -0.0f,	1.0f,	0.0f },
+			{ 1.0f,		-0.0f,	0.0f }
+		};
+		const int				numSpecialCases	= DE_LENGTH_OF_ARRAY(specialCases);
+
+		// Special cases
+		for (int caseNdx = 0; caseNdx < numSpecialCases; caseNdx++)
+		{
+			for (int inputNdx = 0; inputNdx < 3; inputNdx++)
+			{
+				for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
+					((float*)values[inputNdx])[caseNdx*scalarSize + scalarNdx] = specialCases[caseNdx][inputNdx];
+			}
+		}
+
+		// Random cases.
+		{
+			const int	numScalars	= (numValues-numSpecialCases)*scalarSize;
+			const int	offs		= scalarSize*numSpecialCases;
+
+			for (int inputNdx = 0; inputNdx < 3; inputNdx++)
+				fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[inputNdx] + offs, numScalars);
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const bool				signedZero		= supportsSignedZero(precision);
+
+		const int				mantissaBits	= getMinMantissaBits(precision);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const float		a			= ((const float*)inputs[0])[compNdx];
+			const float		b			= ((const float*)inputs[1])[compNdx];
+			const float		c			= ((const float*)inputs[2])[compNdx];
+			const float		res			= ((const float*)outputs[0])[compNdx];
+			const float		ref			= a*b + c;
+
+			const int		numBitsLost	= precision != glu::PRECISION_HIGHP
+										? de::max(de::max(numBitsLostInOp(a, res), numBitsLostInOp(b, res)), numBitsLostInOp(c, res))+1
+										: 1;
+			const deUint32	maxUlpDiff	= getMaxUlpDiffFromBits(de::max(0, mantissaBits-numBitsLost));
+
+			const deUint32	ulpDiff		= signedZero ? getUlpDiff(res, ref) : getUlpDiffIgnoreZeroSign(res, ref);
+
+			if (ulpDiff > maxUlpDiff)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << " with ULP threshold "
+						  << tcu::toHex(maxUlpDiff) << ", got ULP diff " << tcu::toHex(ulpDiff);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+ShaderCommonFunctionTests::ShaderCommonFunctionTests (Context& context)
+	: TestCaseGroup(context, "common", "Common function tests")
+{
+}
+
+ShaderCommonFunctionTests::~ShaderCommonFunctionTests (void)
+{
+}
+
+template<class TestClass>
+static void addFunctionCases (TestCaseGroup* parent, const char* functionName, bool floatTypes, bool intTypes, bool uintTypes, deUint32 shaderBits)
+{
+	tcu::TestCaseGroup* group = new tcu::TestCaseGroup(parent->getTestContext(), functionName, functionName);
+	parent->addChild(group);
+
+	const glu::DataType scalarTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_INT,
+		glu::TYPE_UINT
+	};
+
+	for (int scalarTypeNdx = 0; scalarTypeNdx < DE_LENGTH_OF_ARRAY(scalarTypes); scalarTypeNdx++)
+	{
+		const glu::DataType scalarType = scalarTypes[scalarTypeNdx];
+
+		if ((!floatTypes && scalarType == glu::TYPE_FLOAT)	||
+			(!intTypes && scalarType == glu::TYPE_INT)		||
+			(!uintTypes && scalarType == glu::TYPE_UINT))
+			continue;
+
+		for (int vecSize = 1; vecSize <= 4; vecSize++)
+		{
+			for (int prec = glu::PRECISION_LOWP; prec <= glu::PRECISION_HIGHP; prec++)
+			{
+				for (int shaderTypeNdx = 0; shaderTypeNdx < glu::SHADERTYPE_LAST; shaderTypeNdx++)
+				{
+					if (shaderBits & (1<<shaderTypeNdx))
+						group->addChild(new TestClass(parent->getContext(), glu::DataType(scalarType + vecSize - 1), glu::Precision(prec), glu::ShaderType(shaderTypeNdx)));
+				}
+			}
+		}
+	}
+}
+
+void ShaderCommonFunctionTests::init (void)
+{
+	enum
+	{
+		VS = (1<<glu::SHADERTYPE_VERTEX),
+		TC = (1<<glu::SHADERTYPE_TESSELLATION_CONTROL),
+		TE = (1<<glu::SHADERTYPE_TESSELLATION_EVALUATION),
+		GS = (1<<glu::SHADERTYPE_GEOMETRY),
+		FS = (1<<glu::SHADERTYPE_FRAGMENT),
+		CS = (1<<glu::SHADERTYPE_COMPUTE),
+
+		ALL_SHADERS = VS|TC|TE|GS|FS|CS,
+		NEW_SHADERS = TC|TE|GS|CS,
+	};
+
+	//																	Float?	Int?	Uint?	Shaders
+	addFunctionCases<AbsCase>				(this,	"abs",				true,	true,	false,	NEW_SHADERS);
+	addFunctionCases<SignCase>				(this,	"sign",				true,	true,	false,	NEW_SHADERS);
+	addFunctionCases<FloorCase>				(this,	"floor",			true,	false,	false,	NEW_SHADERS);
+	addFunctionCases<TruncCase>				(this,	"trunc",			true,	false,	false,	NEW_SHADERS);
+	addFunctionCases<RoundCase>				(this,	"round",			true,	false,	false,	NEW_SHADERS);
+	addFunctionCases<RoundEvenCase>			(this,	"roundeven",		true,	false,	false,	NEW_SHADERS);
+	addFunctionCases<CeilCase>				(this,	"ceil",				true,	false,	false,	NEW_SHADERS);
+	addFunctionCases<FractCase>				(this,	"fract",			true,	false,	false,	NEW_SHADERS);
+	// mod
+	addFunctionCases<ModfCase>				(this,	"modf",				true,	false,	false,	NEW_SHADERS);
+	// min
+	// max
+	// clamp
+	// mix
+	// step
+	// smoothstep
+	addFunctionCases<IsnanCase>				(this,	"isnan",			true,	false,	false,	NEW_SHADERS);
+	addFunctionCases<IsinfCase>				(this,	"isinf",			true,	false,	false,	NEW_SHADERS);
+	addFunctionCases<FloatBitsToIntCase>	(this,	"floatbitstoint",	true,	false,	false,	NEW_SHADERS);
+	addFunctionCases<FloatBitsToUintCase>	(this,	"floatbitstouint",	true,	false,	false,	NEW_SHADERS);
+
+	addFunctionCases<FrexpCase>				(this,	"frexp",			true,	false,	false,	ALL_SHADERS);
+	addFunctionCases<LdexpCase>				(this,	"ldexp",			true,	false,	false,	ALL_SHADERS);
+	addFunctionCases<FmaCase>				(this,	"fma",				true,	false,	false,	ALL_SHADERS);
+
+	// (u)intBitsToFloat()
+	{
+		const deUint32		shaderBits	= NEW_SHADERS;
+		tcu::TestCaseGroup* intGroup	= new tcu::TestCaseGroup(m_testCtx, "intbitstofloat",	"intBitsToFloat() Tests");
+		tcu::TestCaseGroup* uintGroup	= new tcu::TestCaseGroup(m_testCtx, "uintbitstofloat",	"uintBitsToFloat() Tests");
+
+		addChild(intGroup);
+		addChild(uintGroup);
+
+		for (int vecSize = 1; vecSize < 4; vecSize++)
+		{
+			const glu::DataType		intType		= vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
+			const glu::DataType		uintType	= vecSize > 1 ? glu::getDataTypeUintVec(vecSize) : glu::TYPE_UINT;
+
+			for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; shaderType++)
+			{
+				if (shaderBits & (1<<shaderType))
+				{
+					intGroup->addChild(new BitsToFloatCase(m_context, intType, glu::ShaderType(shaderType)));
+					uintGroup->addChild(new BitsToFloatCase(m_context, uintType, glu::ShaderType(shaderType)));
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderCommonFunctionTests.hpp b/modules/gles31/functional/es31fShaderCommonFunctionTests.hpp
new file mode 100644
index 0000000..9e15482
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderCommonFunctionTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES31FSHADERCOMMONFUNCTIONTESTS_HPP
+#define _ES31FSHADERCOMMONFUNCTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Common built-in function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderCommonFunctionTests : public TestCaseGroup
+{
+public:
+									ShaderCommonFunctionTests		(Context& context);
+	virtual							~ShaderCommonFunctionTests		(void);
+
+	virtual void					init							(void);
+
+private:
+									ShaderCommonFunctionTests		(const ShaderCommonFunctionTests&);		// not allowed!
+	ShaderCommonFunctionTests&		operator=						(const ShaderCommonFunctionTests&);		// not allowed!
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERCOMMONFUNCTIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderHelperInvocationTests.cpp b/modules/gles31/functional/es31fShaderHelperInvocationTests.cpp
new file mode 100644
index 0000000..928f3dd
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderHelperInvocationTests.cpp
@@ -0,0 +1,673 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief gl_HelperInvocation tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderHelperInvocationTests.hpp"
+
+#include "gluObjectWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluPixelTransfer.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "tcuSurface.hpp"
+
+#include "deUniquePtr.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+using glu::ShaderProgram;
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::IVec2;
+using de::MovePtr;
+using std::string;
+using std::vector;
+
+enum PrimitiveType
+{
+	PRIMITIVETYPE_TRIANGLE = 0,
+	PRIMITIVETYPE_LINE,
+	PRIMITIVETYPE_WIDE_LINE,
+	PRIMITIVETYPE_POINT,
+	PRIMITIVETYPE_WIDE_POINT,
+
+	PRIMITIVETYPE_LAST
+};
+
+static int getNumVerticesPerPrimitive (PrimitiveType primType)
+{
+	switch (primType)
+	{
+		case PRIMITIVETYPE_TRIANGLE:	return 3;
+		case PRIMITIVETYPE_LINE:		return 2;
+		case PRIMITIVETYPE_WIDE_LINE:	return 2;
+		case PRIMITIVETYPE_POINT:		return 1;
+		case PRIMITIVETYPE_WIDE_POINT:	return 1;
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+static glu::PrimitiveType getGluPrimitiveType (PrimitiveType primType)
+{
+	switch (primType)
+	{
+		case PRIMITIVETYPE_TRIANGLE:	return glu::PRIMITIVETYPE_TRIANGLES;
+		case PRIMITIVETYPE_LINE:		return glu::PRIMITIVETYPE_LINES;
+		case PRIMITIVETYPE_WIDE_LINE:	return glu::PRIMITIVETYPE_LINES;
+		case PRIMITIVETYPE_POINT:		return glu::PRIMITIVETYPE_POINTS;
+		case PRIMITIVETYPE_WIDE_POINT:	return glu::PRIMITIVETYPE_POINTS;
+		default:
+			DE_ASSERT(false);
+			return glu::PRIMITIVETYPE_LAST;
+	}
+}
+
+static void genVertices (PrimitiveType primType, int numPrimitives, de::Random* rnd, vector<Vec2>* dst)
+{
+	const bool		isTri		= primType == PRIMITIVETYPE_TRIANGLE;
+	const float		minCoord	= isTri ? -1.5f : -1.0f;
+	const float		maxCoord	= isTri ? +1.5f : +1.0f;
+	const int		numVert		= getNumVerticesPerPrimitive(primType)*numPrimitives;
+
+	dst->resize(numVert);
+
+	for (size_t ndx = 0; ndx < dst->size(); ndx++)
+	{
+		(*dst)[ndx][0] = rnd->getFloat(minCoord, maxCoord);
+		(*dst)[ndx][1] = rnd->getFloat(minCoord, maxCoord);
+	}
+}
+
+static int getInteger (const glw::Functions& gl, deUint32 pname)
+{
+	int v = 0;
+	gl.getIntegerv(pname, &v);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+	return v;
+}
+
+static Vec2 getRange (const glw::Functions& gl, deUint32 pname)
+{
+	Vec2 v(0.0f);
+	gl.getFloatv(pname, v.getPtr());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetFloatv()");
+	return v;
+}
+
+static void drawRandomPrimitives (const glu::RenderContext& renderCtx, deUint32 program, PrimitiveType primType, int numPrimitives, de::Random* rnd)
+{
+	const glw::Functions&			gl				= renderCtx.getFunctions();
+	const float						minPointSize	= 16.0f;
+	const float						maxPointSize	= 32.0f;
+	const float						minLineWidth	= 16.0f;
+	const float						maxLineWidth	= 32.0f;
+	vector<Vec2>					vertices;
+	vector<glu::VertexArrayBinding>	vertexArrays;
+
+	genVertices(primType, numPrimitives, rnd, &vertices);
+
+	vertexArrays.push_back(glu::va::Float("a_position", 2, (int)vertices.size(), 0, (const float*)&vertices[0]));
+
+	gl.useProgram(program);
+
+	// Special state for certain primitives
+	if (primType == PRIMITIVETYPE_POINT || primType == PRIMITIVETYPE_WIDE_POINT)
+	{
+		const Vec2		range			= getRange(gl, GL_ALIASED_POINT_SIZE_RANGE);
+		const bool		isWidePoint		= primType == PRIMITIVETYPE_WIDE_POINT;
+		const float		pointSize		= isWidePoint ? de::min(rnd->getFloat(minPointSize, maxPointSize), range.y()) : 1.0f;
+		const int		pointSizeLoc	= gl.getUniformLocation(program, "u_pointSize");
+
+		gl.uniform1f(pointSizeLoc, pointSize);
+	}
+	else if (primType == PRIMITIVETYPE_WIDE_LINE)
+	{
+		const Vec2		range			= getRange(gl, GL_ALIASED_LINE_WIDTH_RANGE);
+		const float		lineWidth		= de::min(rnd->getFloat(minLineWidth, maxLineWidth), range.y());
+
+		gl.lineWidth(lineWidth);
+	}
+
+	glu::draw(renderCtx, program, (int)vertexArrays.size(), &vertexArrays[0],
+			  glu::PrimitiveList(getGluPrimitiveType(primType), (int)vertices.size()));
+}
+
+class FboHelper
+{
+public:
+								FboHelper			(const glu::RenderContext& renderCtx, int width, int height, deUint32 format, int numSamples);
+								~FboHelper			(void);
+
+	void						bindForRendering	(void);
+	void						readPixels			(int x, int y, const tcu::PixelBufferAccess& dst);
+
+private:
+	const glu::RenderContext&	m_renderCtx;
+	const int					m_numSamples;
+
+	glu::Renderbuffer			m_colorbuffer;
+	glu::Framebuffer			m_framebuffer;
+	glu::Renderbuffer			m_resolveColorbuffer;
+	glu::Framebuffer			m_resolveFramebuffer;
+};
+
+FboHelper::FboHelper (const glu::RenderContext& renderCtx, int width, int height, deUint32 format, int numSamples)
+	: m_renderCtx			(renderCtx)
+	, m_numSamples			(numSamples)
+	, m_colorbuffer			(renderCtx)
+	, m_framebuffer			(renderCtx)
+	, m_resolveColorbuffer	(renderCtx)
+	, m_resolveFramebuffer	(renderCtx)
+{
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	const int				maxSamples	= getInteger(gl, GL_MAX_SAMPLES);
+
+	gl.bindRenderbuffer(GL_RENDERBUFFER, *m_colorbuffer);
+	gl.renderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples, format, width, height);
+	gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer);
+	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_colorbuffer);
+
+	if (m_numSamples > maxSamples && gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_UNSUPPORTED)
+		throw tcu::NotSupportedError("Sample count exceeds GL_MAX_SAMPLES");
+
+	TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+	if (m_numSamples != 0)
+	{
+		gl.bindRenderbuffer(GL_RENDERBUFFER, *m_resolveColorbuffer);
+		gl.renderbufferStorage(GL_RENDERBUFFER, format, width, height);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *m_resolveFramebuffer);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_resolveColorbuffer);
+		TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create framebuffer");
+}
+
+FboHelper::~FboHelper (void)
+{
+}
+
+void FboHelper::bindForRendering (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()");
+}
+
+void FboHelper::readPixels (int x, int y, const tcu::PixelBufferAccess& dst)
+{
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+	const int				width	= dst.getWidth();
+	const int				height	= dst.getHeight();
+
+	if (m_numSamples != 0)
+	{
+		gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, *m_resolveFramebuffer);
+		gl.blitFramebuffer(x, y, width, height, x, y, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, *m_resolveFramebuffer);
+	}
+
+	glu::readPixels(m_renderCtx, x, y, dst);
+}
+
+enum
+{
+	FRAMEBUFFER_WIDTH	= 256,
+	FRAMEBUFFER_HEIGHT	= 256,
+	FRAMEBUFFER_FORMAT	= GL_RGBA8,
+	NUM_SAMPLES_MAX		= -1
+};
+
+//! Verifies that gl_HelperInvocation is false in all rendered pixels.
+class HelperInvocationValueCase : public TestCase
+{
+public:
+							HelperInvocationValueCase	(Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples);
+							~HelperInvocationValueCase	(void);
+
+	void					init						(void);
+	void					deinit						(void);
+	IterateResult			iterate						(void);
+
+private:
+	const PrimitiveType		m_primitiveType;
+	const int				m_numSamples;
+
+	const int				m_numIters;
+	const int				m_numPrimitivesPerIter;
+
+	MovePtr<ShaderProgram>	m_program;
+	MovePtr<FboHelper>		m_fbo;
+	int						m_iterNdx;
+};
+
+HelperInvocationValueCase::HelperInvocationValueCase (Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples)
+	: TestCase					(context, name, description)
+	, m_primitiveType			(primType)
+	, m_numSamples				(numSamples)
+	, m_numIters				(5)
+	, m_numPrimitivesPerIter	(10)
+	, m_iterNdx					(0)
+{
+}
+
+HelperInvocationValueCase::~HelperInvocationValueCase (void)
+{
+	deinit();
+}
+
+void HelperInvocationValueCase::init (void)
+{
+	const glu::RenderContext&	renderCtx		= m_context.getRenderContext();
+	const glw::Functions&		gl				= renderCtx.getFunctions();
+	const int					maxSamples		= getInteger(gl, GL_MAX_SAMPLES);
+	const int					actualSamples	= m_numSamples == NUM_SAMPLES_MAX ? maxSamples : m_numSamples;
+
+	m_program = MovePtr<ShaderProgram>(new ShaderProgram(m_context.getRenderContext(),
+		glu::ProgramSources()
+			<< glu::VertexSource(
+				"#version 310 es\n"
+				"in highp vec2 a_position;\n"
+				"uniform highp float u_pointSize;\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+				"	gl_PointSize = u_pointSize;\n"
+				"}\n")
+			<< glu::FragmentSource(
+				"#version 310 es\n"
+				"out mediump vec4 o_color;\n"
+				"void main (void)\n"
+				"{\n"
+				"	if (gl_HelperInvocation)\n"
+				"		o_color = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	else\n"
+				"		o_color = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"}\n")));
+
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+	{
+		m_program.clear();
+		TCU_FAIL("Compile failed");
+	}
+
+	m_testCtx.getLog() << TestLog::Message << "Using GL_RGBA8 framebuffer with "
+					   << actualSamples << " samples" << TestLog::EndMessage;
+
+	m_fbo = MovePtr<FboHelper>(new FboHelper(renderCtx, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT,
+											 FRAMEBUFFER_FORMAT, actualSamples));
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void HelperInvocationValueCase::deinit (void)
+{
+	m_program.clear();
+	m_fbo.clear();
+}
+
+static bool verifyHelperInvocationValue (TestLog& log, const tcu::Surface& result, bool isMultiSample)
+{
+	const tcu::RGBA		bgRef				(0, 0, 0, 255);
+	const tcu::RGBA		fgRef				(0, 255, 0, 255);
+	const tcu::RGBA		threshold			(1, isMultiSample ? 254 : 1, 1, 1);
+	int					numInvalidPixels	= 0;
+
+	for (int y = 0; y < result.getHeight(); ++y)
+	{
+		for (int x = 0; x < result.getWidth(); ++x)
+		{
+			const tcu::RGBA	resPix	= result.getPixel(x, y);
+
+			if (!tcu::compareThreshold(resPix, bgRef, threshold) &&
+				!tcu::compareThreshold(resPix, fgRef, threshold))
+				numInvalidPixels += 1;
+		}
+	}
+
+	if (numInvalidPixels > 0)
+	{
+		log << TestLog::Image("Result", "Result image", result);
+		log << TestLog::Message << "ERROR: Found " << numInvalidPixels << " invalid result pixels!" << TestLog::EndMessage;
+	}
+	else
+		log << TestLog::Message << "All result pixels are valid" << TestLog::EndMessage;
+
+	return numInvalidPixels == 0;
+}
+
+HelperInvocationValueCase::IterateResult HelperInvocationValueCase::iterate (void)
+{
+	const glu::RenderContext&		renderCtx	= m_context.getRenderContext();
+	const glw::Functions&			gl			= renderCtx.getFunctions();
+	const string					sectionName	= string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(m_numIters);
+	const tcu::ScopedLogSection		section		(m_testCtx.getLog(), (string("Iter") + de::toString(m_iterNdx)), sectionName);
+	de::Random						rnd			(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
+	tcu::Surface					result		(FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);
+
+	m_fbo->bindForRendering();
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	drawRandomPrimitives(renderCtx, m_program->getProgram(), m_primitiveType, m_numPrimitivesPerIter, &rnd);
+
+	m_fbo->readPixels(0, 0, result.getAccess());
+
+	if (!verifyHelperInvocationValue(m_testCtx.getLog(), result, m_numSamples != 0))
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixels found");
+
+	m_iterNdx += 1;
+	return (m_iterNdx < m_numIters) ? CONTINUE : STOP;
+}
+
+//! Checks derivates when value depends on gl_HelperInvocation.
+class HelperInvocationDerivateCase : public TestCase
+{
+public:
+							HelperInvocationDerivateCase	(Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples, const char* derivateFunc);
+							~HelperInvocationDerivateCase	(void);
+
+	void					init							(void);
+	void					deinit							(void);
+	IterateResult			iterate							(void);
+
+private:
+	const PrimitiveType		m_primitiveType;
+	const int				m_numSamples;
+	const std::string		m_derivateFunc;
+
+	const int				m_numIters;
+
+	MovePtr<ShaderProgram>	m_program;
+	MovePtr<FboHelper>		m_fbo;
+	int						m_iterNdx;
+};
+
+HelperInvocationDerivateCase::HelperInvocationDerivateCase (Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples, const char* derivateFunc)
+	: TestCase					(context, name, description)
+	, m_primitiveType			(primType)
+	, m_numSamples				(numSamples)
+	, m_derivateFunc			(derivateFunc)
+	, m_numIters				(16)
+	, m_iterNdx					(0)
+{
+}
+
+HelperInvocationDerivateCase::~HelperInvocationDerivateCase (void)
+{
+	deinit();
+}
+
+void HelperInvocationDerivateCase::init (void)
+{
+	const glu::RenderContext&	renderCtx		= m_context.getRenderContext();
+	const glw::Functions&		gl				= renderCtx.getFunctions();
+	const int					maxSamples		= getInteger(gl, GL_MAX_SAMPLES);
+	const int					actualSamples	= m_numSamples == NUM_SAMPLES_MAX ? maxSamples : m_numSamples;
+
+	m_program = MovePtr<ShaderProgram>(new ShaderProgram(m_context.getRenderContext(),
+		glu::ProgramSources()
+			<< glu::VertexSource(
+				"#version 310 es\n"
+				"in highp vec2 a_position;\n"
+				"uniform highp float u_pointSize;\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+				"	gl_PointSize = u_pointSize;\n"
+				"}\n")
+			<< glu::FragmentSource(string(
+				"#version 310 es\n"
+				"out mediump vec4 o_color;\n"
+				"void main (void)\n"
+				"{\n"
+				"	highp float value		= gl_HelperInvocation ? 1.0 : 0.0;\n"
+				"	highp float derivate	= ") + m_derivateFunc + "(value);\n"
+				"	if (gl_HelperInvocation)\n"
+				"		o_color = vec4(1.0, 0.0, derivate, 1.0);\n"
+				"	else\n"
+				"		o_color = vec4(0.0, 1.0, derivate, 1.0);\n"
+				"}\n")));
+
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+	{
+		m_program.clear();
+		TCU_FAIL("Compile failed");
+	}
+
+	m_testCtx.getLog() << TestLog::Message << "Using GL_RGBA8 framebuffer with "
+					   << actualSamples << " samples" << TestLog::EndMessage;
+
+	m_fbo = MovePtr<FboHelper>(new FboHelper(renderCtx, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT,
+											 FRAMEBUFFER_FORMAT, actualSamples));
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void HelperInvocationDerivateCase::deinit (void)
+{
+	m_program.clear();
+	m_fbo.clear();
+}
+
+static bool hasNeighborWithColor (const tcu::Surface& surface, int x, int y, tcu::RGBA color, tcu::RGBA threshold)
+{
+	static const IVec2 s_neighbors[] =
+	{
+		IVec2(-1, -1),
+		IVec2( 0, -1),
+		IVec2(+1, -1),
+		IVec2(-1,  0),
+		IVec2(+1,  0),
+		IVec2(-1, +1),
+		IVec2( 0, +1),
+		IVec2(+1, +1)
+	};
+
+	const int	w	= surface.getWidth();
+	const int	h	= surface.getHeight();
+
+	for (int sample = 0; sample < DE_LENGTH_OF_ARRAY(s_neighbors); sample++)
+	{
+		const IVec2	pos	= IVec2(x, y) + s_neighbors[sample];
+
+		if (de::inBounds(pos.x(), 0, w) && de::inBounds(pos.y(), 0, h))
+		{
+			const tcu::RGBA neighborColor = surface.getPixel(pos.x(), pos.y());
+
+			if (tcu::compareThreshold(color, neighborColor, threshold))
+				return true;
+		}
+		else
+			return true; // Can't know for certain
+	}
+
+	return false;
+}
+
+static bool verifyHelperInvocationDerivate (TestLog& log, const tcu::Surface& result, bool isMultiSample)
+{
+	const tcu::RGBA		bgRef				(0, 0, 0, 255);
+	const tcu::RGBA		fgRef				(0, 255, 0, 255);
+	const tcu::RGBA		isBgThreshold		(1, isMultiSample ? 254 : 1, 0, 1);
+	const tcu::RGBA		isFgThreshold		(1, isMultiSample ? 254 : 1, 255, 1);
+	int					numInvalidPixels	= 0;
+	int					numNonZeroDeriv		= 0;
+
+	for (int y = 0; y < result.getHeight(); ++y)
+	{
+		for (int x = 0; x < result.getWidth(); ++x)
+		{
+			const tcu::RGBA	resPix			= result.getPixel(x, y);
+			const bool		isBg			= tcu::compareThreshold(resPix, bgRef, isBgThreshold);
+			const bool		isFg			= tcu::compareThreshold(resPix, fgRef, isFgThreshold);
+			const bool		nonZeroDeriv	= resPix.getBlue() > 0;
+			const bool		neighborBg		= nonZeroDeriv ? hasNeighborWithColor(result, x, y, bgRef, isBgThreshold) : false;
+
+			if (nonZeroDeriv)
+				numNonZeroDeriv	+= 1;
+
+			if ((!isBg && !isFg) ||				// Neither of valid colors (ignoring blue channel that has derivate)
+				(nonZeroDeriv && !neighborBg))	// Has non-zero derivate, but sample not at primitive edge
+				numInvalidPixels += 1;
+		}
+	}
+
+	if (numInvalidPixels > 0)
+	{
+		log << TestLog::Image("Result", "Result image", result);
+		log << TestLog::Message << "ERROR: Found " << numInvalidPixels << " invalid result pixels!" << TestLog::EndMessage;
+	}
+	else
+		log << TestLog::Message << "All result pixels are valid" << TestLog::EndMessage;
+
+	log << TestLog::Message << "Found " << numNonZeroDeriv << " pixels with non-zero derivate (neighbor sample has gl_HelperInvocation = true)" << TestLog::EndMessage;
+
+	return numInvalidPixels == 0;
+}
+
+HelperInvocationDerivateCase::IterateResult HelperInvocationDerivateCase::iterate (void)
+{
+	const glu::RenderContext&		renderCtx	= m_context.getRenderContext();
+	const glw::Functions&			gl			= renderCtx.getFunctions();
+	const string					sectionName	= string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(m_numIters);
+	const tcu::ScopedLogSection		section		(m_testCtx.getLog(), (string("Iter") + de::toString(m_iterNdx)), sectionName);
+	de::Random						rnd			(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
+	tcu::Surface					result		(FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);
+
+	m_fbo->bindForRendering();
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	drawRandomPrimitives(renderCtx, m_program->getProgram(), m_primitiveType, 1, &rnd);
+
+	m_fbo->readPixels(0, 0, result.getAccess());
+
+	if (!verifyHelperInvocationDerivate(m_testCtx.getLog(), result, m_numSamples != 0))
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixels found");
+
+	m_iterNdx += 1;
+	return (m_iterNdx < m_numIters) ? CONTINUE : STOP;
+}
+
+} // anonymous
+
+ShaderHelperInvocationTests::ShaderHelperInvocationTests (Context& context)
+	: TestCaseGroup(context, "helper_invocation", "gl_HelperInvocation tests")
+{
+}
+
+ShaderHelperInvocationTests::~ShaderHelperInvocationTests (void)
+{
+}
+
+void ShaderHelperInvocationTests::init (void)
+{
+	static const struct
+	{
+		const char*		caseName;
+		PrimitiveType	primType;
+	} s_primTypes[] =
+	{
+		{ "triangles",		PRIMITIVETYPE_TRIANGLE		},
+		{ "lines",			PRIMITIVETYPE_LINE			},
+		{ "wide_lines",		PRIMITIVETYPE_WIDE_LINE		},
+		{ "points",			PRIMITIVETYPE_POINT			},
+		{ "wide_points",	PRIMITIVETYPE_WIDE_POINT	}
+	};
+
+	static const struct
+	{
+		const char*		suffix;
+		int				numSamples;
+	} s_sampleCounts[] =
+	{
+		{ "",					0				},
+		{ "_4_samples",			4				},
+		{ "_8_samples",			8				},
+		{ "_max_samples",		NUM_SAMPLES_MAX	}
+	};
+
+	// value
+	{
+		tcu::TestCaseGroup* const valueGroup = new tcu::TestCaseGroup(m_testCtx, "value", "gl_HelperInvocation value in rendered pixels");
+		addChild(valueGroup);
+
+		for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(s_sampleCounts); sampleCountNdx++)
+		{
+			for (int primTypeNdx = 0; primTypeNdx < DE_LENGTH_OF_ARRAY(s_primTypes); primTypeNdx++)
+			{
+				const string		name		= string(s_primTypes[primTypeNdx].caseName) + s_sampleCounts[sampleCountNdx].suffix;
+				const PrimitiveType	primType	= s_primTypes[primTypeNdx].primType;
+				const int			numSamples	= s_sampleCounts[sampleCountNdx].numSamples;
+
+				valueGroup->addChild(new HelperInvocationValueCase(m_context, name.c_str(), "", primType, numSamples));
+			}
+		}
+	}
+
+	// derivate
+	{
+		tcu::TestCaseGroup* const derivateGroup = new tcu::TestCaseGroup(m_testCtx, "derivate", "Derivate of gl_HelperInvocation-dependent value");
+		addChild(derivateGroup);
+
+		for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(s_sampleCounts); sampleCountNdx++)
+		{
+			for (int primTypeNdx = 0; primTypeNdx < DE_LENGTH_OF_ARRAY(s_primTypes); primTypeNdx++)
+			{
+				const string		name		= string(s_primTypes[primTypeNdx].caseName) + s_sampleCounts[sampleCountNdx].suffix;
+				const PrimitiveType	primType	= s_primTypes[primTypeNdx].primType;
+				const int			numSamples	= s_sampleCounts[sampleCountNdx].numSamples;
+
+				derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_dfdx").c_str(),	"", primType, numSamples, "dFdx"));
+				derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_dfdy").c_str(),	"", primType, numSamples, "dFdy"));
+				derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_fwidth").c_str(),	"", primType, numSamples, "fwidth"));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderHelperInvocationTests.hpp b/modules/gles31/functional/es31fShaderHelperInvocationTests.hpp
new file mode 100644
index 0000000..82c1b7f
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderHelperInvocationTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES31FSHADERHELPERINVOCATIONTESTS_HPP
+#define _ES31FSHADERHELPERINVOCATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief gl_HelperInvocation tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderHelperInvocationTests : public TestCaseGroup
+{
+public:
+									ShaderHelperInvocationTests		(Context& context);
+	virtual							~ShaderHelperInvocationTests	(void);
+
+	virtual void					init							(void);
+
+private:
+									ShaderHelperInvocationTests		(const ShaderHelperInvocationTests&);
+	ShaderHelperInvocationTests&	operator=						(const ShaderHelperInvocationTests&);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERHELPERINVOCATIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderImageLoadStoreTests.cpp b/modules/gles31/functional/es31fShaderImageLoadStoreTests.cpp
new file mode 100644
index 0000000..76ae9c1
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderImageLoadStoreTests.cpp
@@ -0,0 +1,3273 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader Image Load & Store Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderImageLoadStoreTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluProgramInterfaceQuery.hpp"
+#include "gluDrawUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuFloat.hpp"
+#include "tcuVectorUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deSharedPtr.hpp"
+#include "deUniquePtr.hpp"
+#include "deRandom.hpp"
+#include "deMemory.h"
+#include "glwFunctions.hpp"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <map>
+
+using glu::RenderContext;
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::UVec2;
+using tcu::UVec3;
+using tcu::UVec4;
+using tcu::TextureFormat;
+using tcu::ConstPixelBufferAccess;
+using tcu::PixelBufferAccess;
+using de::toString;
+using de::SharedPtr;
+using de::UniquePtr;
+
+using std::vector;
+using std::string;
+
+namespace deqp
+{
+
+using namespace gls::TextureTestUtil;
+
+namespace gles31
+{
+namespace Functional
+{
+
+//! Default image sizes used in most test cases.
+static inline IVec3 defaultImageSize (TextureType type)
+{
+	switch (type)
+	{
+		case TEXTURETYPE_BUFFER:	return IVec3(64,	1,		1);
+		case TEXTURETYPE_2D:		return IVec3(64,	64,		1);
+		case TEXTURETYPE_CUBE:		return IVec3(64,	64,		1);
+		case TEXTURETYPE_3D:		return IVec3(64,	64,		8);
+		case TEXTURETYPE_2D_ARRAY:	return IVec3(64,	64,		8);
+		default:
+			DE_ASSERT(false);
+			return IVec3(-1);
+	}
+}
+
+template <typename T, int Size>
+static string arrayStr (const T (&arr)[Size])
+{
+	string result = "{ ";
+	for (int i = 0; i < Size; i++)
+		result += (i > 0 ? ", " : "") + toString(arr[i]);
+	result += " }";
+	return result;
+}
+
+static const char* getTextureTypeName (TextureType type)
+{
+	switch (type)
+	{
+		case TEXTURETYPE_BUFFER:	return "buffer";
+		case TEXTURETYPE_2D:		return "2d";
+		case TEXTURETYPE_CUBE:		return "cube";
+		case TEXTURETYPE_3D:		return "3d";
+		case TEXTURETYPE_2D_ARRAY:	return "2d_array";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+static inline bool isFormatTypeUnsignedInteger (TextureFormat::ChannelType type)
+{
+	return type == TextureFormat::UNSIGNED_INT8		||
+		   type == TextureFormat::UNSIGNED_INT16	||
+		   type == TextureFormat::UNSIGNED_INT32;
+}
+
+static inline bool isFormatTypeSignedInteger (TextureFormat::ChannelType type)
+{
+	return type == TextureFormat::SIGNED_INT8	||
+		   type == TextureFormat::SIGNED_INT16	||
+		   type == TextureFormat::SIGNED_INT32;
+}
+
+static inline bool isFormatTypeInteger (TextureFormat::ChannelType type)
+{
+	return isFormatTypeUnsignedInteger(type) || isFormatTypeSignedInteger(type);
+}
+
+static inline bool isFormatTypeUnorm (TextureFormat::ChannelType type)
+{
+	return type == TextureFormat::UNORM_INT8	||
+		   type == TextureFormat::UNORM_INT16	||
+		   type == TextureFormat::UNORM_INT32;
+}
+
+static inline bool isFormatTypeSnorm (TextureFormat::ChannelType type)
+{
+	return type == TextureFormat::SNORM_INT8	||
+		   type == TextureFormat::SNORM_INT16	||
+		   type == TextureFormat::SNORM_INT32;
+}
+
+static inline bool isFormatSupportedForTextureBuffer (const TextureFormat& format)
+{
+	switch (format.order)
+	{
+		case TextureFormat::RGB:
+			return format.type == TextureFormat::FLOAT				||
+				   format.type == TextureFormat::SIGNED_INT32		||
+				   format.type == TextureFormat::UNSIGNED_INT32;
+
+		// \note Fallthroughs.
+		case TextureFormat::R:
+		case TextureFormat::RG:
+		case TextureFormat::RGBA:
+			return format.type == TextureFormat::UNORM_INT8			||
+				   format.type == TextureFormat::HALF_FLOAT			||
+				   format.type == TextureFormat::FLOAT				||
+				   format.type == TextureFormat::SIGNED_INT8		||
+				   format.type == TextureFormat::SIGNED_INT16		||
+				   format.type == TextureFormat::SIGNED_INT32		||
+				   format.type == TextureFormat::UNSIGNED_INT8		||
+				   format.type == TextureFormat::UNSIGNED_INT16		||
+				   format.type == TextureFormat::UNSIGNED_INT32;
+
+		default:
+			return false;
+	}
+}
+
+static inline string getShaderImageFormatQualifier (const TextureFormat& format)
+{
+	const char* orderPart;
+	const char* typePart;
+
+	switch (format.order)
+	{
+		case TextureFormat::R:		orderPart = "r";		break;
+		case TextureFormat::RGBA:	orderPart = "rgba";		break;
+		default:
+			DE_ASSERT(false);
+			orderPart = DE_NULL;
+	}
+
+	switch (format.type)
+	{
+		case TextureFormat::FLOAT:				typePart = "32f";			break;
+		case TextureFormat::HALF_FLOAT:			typePart = "16f";			break;
+
+		case TextureFormat::UNSIGNED_INT32:		typePart = "32ui";			break;
+		case TextureFormat::UNSIGNED_INT16:		typePart = "16ui";			break;
+		case TextureFormat::UNSIGNED_INT8:		typePart = "8ui";			break;
+
+		case TextureFormat::SIGNED_INT32:		typePart = "32i";			break;
+		case TextureFormat::SIGNED_INT16:		typePart = "16i";			break;
+		case TextureFormat::SIGNED_INT8:		typePart = "8i";			break;
+
+		case TextureFormat::UNORM_INT16:		typePart = "16";			break;
+		case TextureFormat::UNORM_INT8:			typePart = "8";				break;
+
+		case TextureFormat::SNORM_INT16:		typePart = "16_snorm";		break;
+		case TextureFormat::SNORM_INT8:			typePart = "8_snorm";		break;
+
+		default:
+			DE_ASSERT(false);
+			typePart = DE_NULL;
+	}
+
+	return string() + orderPart + typePart;
+}
+
+static inline string getShaderSamplerOrImageType (TextureFormat::ChannelType formatType, TextureType textureType, bool isSampler)
+{
+	const char* const formatPart		= isFormatTypeUnsignedInteger(formatType)	? "u"
+										: isFormatTypeSignedInteger(formatType)		? "i"
+										: "";
+
+	const char* const imageTypePart		= textureType == TEXTURETYPE_BUFFER		? "Buffer"
+										: textureType == TEXTURETYPE_2D			? "2D"
+										: textureType == TEXTURETYPE_3D			? "3D"
+										: textureType == TEXTURETYPE_CUBE		? "Cube"
+										: textureType == TEXTURETYPE_2D_ARRAY	? "2DArray"
+										: DE_NULL;
+
+	return string() + formatPart + (isSampler ? "sampler" : "image") + imageTypePart;
+}
+
+static inline string getShaderImageType (TextureFormat::ChannelType formatType, TextureType imageType)
+{
+	return getShaderSamplerOrImageType(formatType, imageType, false);
+}
+
+static inline string getShaderSamplerType (TextureFormat::ChannelType formatType, TextureType imageType)
+{
+	return getShaderSamplerOrImageType(formatType, imageType, true);
+}
+
+static inline deUint32 getGLTextureTarget (TextureType texType)
+{
+	switch (texType)
+	{
+		case TEXTURETYPE_BUFFER:	return GL_TEXTURE_BUFFER;
+		case TEXTURETYPE_2D:		return GL_TEXTURE_2D;
+		case TEXTURETYPE_3D:		return GL_TEXTURE_3D;
+		case TEXTURETYPE_CUBE:		return GL_TEXTURE_CUBE_MAP;
+		case TEXTURETYPE_2D_ARRAY:	return GL_TEXTURE_2D_ARRAY;
+		default:
+			DE_ASSERT(false);
+			return (deUint32)-1;
+	}
+}
+
+static deUint32 cubeFaceToGLFace (tcu::CubeFace face)
+{
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
+		case tcu::CUBEFACE_POSITIVE_X: return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
+		case tcu::CUBEFACE_NEGATIVE_Y: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
+		case tcu::CUBEFACE_POSITIVE_Y: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
+		case tcu::CUBEFACE_NEGATIVE_Z: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
+		case tcu::CUBEFACE_POSITIVE_Z: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
+		default:
+			DE_ASSERT(false);
+			return GL_NONE;
+	}
+}
+
+static inline tcu::Texture1D* newOneLevelTexture1D (const tcu::TextureFormat& format, int w)
+{
+	tcu::Texture1D* const res = new tcu::Texture1D(format, w);
+	res->allocLevel(0);
+	return res;
+}
+
+static inline tcu::Texture2D* newOneLevelTexture2D (const tcu::TextureFormat& format, int w, int h)
+{
+	tcu::Texture2D* const res = new tcu::Texture2D(format, w, h);
+	res->allocLevel(0);
+	return res;
+}
+
+static inline tcu::TextureCube* newOneLevelTextureCube (const tcu::TextureFormat& format, int size)
+{
+	tcu::TextureCube* const res = new tcu::TextureCube(format, size);
+	for (int i = 0; i < tcu::CUBEFACE_LAST; i++)
+		res->allocLevel((tcu::CubeFace)i, 0);
+	return res;
+}
+
+static inline tcu::Texture3D* newOneLevelTexture3D (const tcu::TextureFormat& format, int w, int h, int d)
+{
+	tcu::Texture3D* const res = new tcu::Texture3D(format, w, h, d);
+	res->allocLevel(0);
+	return res;
+}
+
+static inline tcu::Texture2DArray* newOneLevelTexture2DArray (const tcu::TextureFormat& format, int w, int h, int d)
+{
+	tcu::Texture2DArray* const res = new tcu::Texture2DArray(format, w, h, d);
+	res->allocLevel(0);
+	return res;
+}
+
+static inline TextureType textureLayerType (TextureType entireTextureType)
+{
+	switch (entireTextureType)
+	{
+		// Single-layer types.
+		// \note Fallthrough.
+		case TEXTURETYPE_BUFFER:
+		case TEXTURETYPE_2D:
+			return entireTextureType;
+
+		// Multi-layer types with 2d layers.
+		case TEXTURETYPE_3D:
+		case TEXTURETYPE_CUBE:
+		case TEXTURETYPE_2D_ARRAY:
+			return TEXTURETYPE_2D;
+
+		default:
+			DE_ASSERT(false);
+			return TEXTURETYPE_LAST;
+	}
+}
+
+static const char* const s_texBufExtString = "GL_EXT_texture_buffer";
+
+static inline void checkTextureTypeExtensions (const glu::ContextInfo& contextInfo, TextureType type)
+{
+	if (type == TEXTURETYPE_BUFFER && !contextInfo.isExtensionSupported(s_texBufExtString))
+		throw tcu::NotSupportedError("Test requires " + string(s_texBufExtString) + " extension");
+}
+
+static inline string textureTypeExtensionShaderRequires (TextureType type)
+{
+	if (type == TEXTURETYPE_BUFFER)
+		return "#extension " + string(s_texBufExtString) + " : require\n";
+	else
+		return "";
+}
+
+namespace
+{
+
+enum AtomicOperation
+{
+	ATOMIC_OPERATION_ADD = 0,
+	ATOMIC_OPERATION_MIN,
+	ATOMIC_OPERATION_MAX,
+	ATOMIC_OPERATION_AND,
+	ATOMIC_OPERATION_OR,
+	ATOMIC_OPERATION_XOR,
+	ATOMIC_OPERATION_EXCHANGE,
+	ATOMIC_OPERATION_COMP_SWAP,
+
+	ATOMIC_OPERATION_LAST
+};
+
+//! An order-independent operation is one for which the end result doesn't depend on the order in which the operations are carried (i.e. is both commutative and associative).
+static bool isOrderIndependentAtomicOperation (AtomicOperation op)
+{
+	return op == ATOMIC_OPERATION_ADD	||
+		   op == ATOMIC_OPERATION_MIN	||
+		   op == ATOMIC_OPERATION_MAX	||
+		   op == ATOMIC_OPERATION_AND	||
+		   op == ATOMIC_OPERATION_OR	||
+		   op == ATOMIC_OPERATION_XOR;
+}
+
+//! Computes the result of an atomic operation where "a" is the data operated on and "b" is the parameter to the atomic function.
+int computeBinaryAtomicOperationResult (AtomicOperation op, int a, int b)
+{
+	switch (op)
+	{
+		case ATOMIC_OPERATION_ADD:			return a + b;
+		case ATOMIC_OPERATION_MIN:			return de::min(a, b);
+		case ATOMIC_OPERATION_MAX:			return de::max(a, b);
+		case ATOMIC_OPERATION_AND:			return a & b;
+		case ATOMIC_OPERATION_OR:			return a | b;
+		case ATOMIC_OPERATION_XOR:			return a ^ b;
+		case ATOMIC_OPERATION_EXCHANGE:		return b;
+		default:
+			DE_ASSERT(false);
+			return -1;
+	}
+}
+
+//! \note For floats, only the exchange operation is supported.
+float computeBinaryAtomicOperationResult (AtomicOperation op, float /*a*/, float b)
+{
+	switch (op)
+	{
+		case ATOMIC_OPERATION_EXCHANGE: return b;
+		default:
+			DE_ASSERT(false);
+			return -1;
+	}
+}
+
+static const char* getAtomicOperationCaseName (AtomicOperation op)
+{
+	switch (op)
+	{
+		case ATOMIC_OPERATION_ADD:			return "add";
+		case ATOMIC_OPERATION_MIN:			return "min";
+		case ATOMIC_OPERATION_MAX:			return "max";
+		case ATOMIC_OPERATION_AND:			return "and";
+		case ATOMIC_OPERATION_OR:			return "or";
+		case ATOMIC_OPERATION_XOR:			return "xor";
+		case ATOMIC_OPERATION_EXCHANGE:		return "exchange";
+		case ATOMIC_OPERATION_COMP_SWAP:	return "comp_swap";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+static const char* getAtomicOperationShaderFuncName (AtomicOperation op)
+{
+	switch (op)
+	{
+		case ATOMIC_OPERATION_ADD:			return "imageAtomicAdd";
+		case ATOMIC_OPERATION_MIN:			return "imageAtomicMin";
+		case ATOMIC_OPERATION_MAX:			return "imageAtomicMax";
+		case ATOMIC_OPERATION_AND:			return "imageAtomicAnd";
+		case ATOMIC_OPERATION_OR:			return "imageAtomicOr";
+		case ATOMIC_OPERATION_XOR:			return "imageAtomicXor";
+		case ATOMIC_OPERATION_EXCHANGE:		return "imageAtomicExchange";
+		case ATOMIC_OPERATION_COMP_SWAP:	return "imageAtomicCompSwap";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+//! In GLSL, when accessing cube images, the z coordinate is mapped to a cube face.
+//! \note This is _not_ the same as casting the z to a tcu::CubeFace.
+static inline tcu::CubeFace glslImageFuncZToCubeFace (int z)
+{
+	static const tcu::CubeFace faces[6] =
+	{
+		tcu::CUBEFACE_POSITIVE_X,
+		tcu::CUBEFACE_NEGATIVE_X,
+		tcu::CUBEFACE_POSITIVE_Y,
+		tcu::CUBEFACE_NEGATIVE_Y,
+		tcu::CUBEFACE_POSITIVE_Z,
+		tcu::CUBEFACE_NEGATIVE_Z
+	};
+
+	DE_ASSERT(de::inBounds(z, 0, DE_LENGTH_OF_ARRAY(faces)));
+	return faces[z];
+}
+
+class BufferMemMap
+{
+public:
+	BufferMemMap (const glw::Functions& gl, deUint32 target, int offset, int size, deUint32 access)
+		: m_gl		(gl)
+		, m_target	(target)
+		, m_ptr		(DE_NULL)
+	{
+		m_ptr = gl.mapBufferRange(target, offset, size, access);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+		TCU_CHECK(m_ptr);
+	}
+
+	~BufferMemMap (void)
+	{
+		m_gl.unmapBuffer(m_target);
+	}
+
+	void*	getPtr		(void) const { return m_ptr; }
+	void*	operator*	(void) const { return m_ptr; }
+
+private:
+							BufferMemMap			(const BufferMemMap& other);
+	BufferMemMap&			operator=				(const BufferMemMap& other);
+
+	const glw::Functions&	m_gl;
+	const deUint32			m_target;
+	void*					m_ptr;
+};
+
+//! Utility for more readable uniform assignment logging; logs the name of the uniform when assigning. Handles the locations, querying them the first time they're assigned
+//  \note Assumes that the appropriate program is in use when assigning uniforms.
+class UniformAccessLogger
+{
+public:
+	UniformAccessLogger (const glw::Functions& gl, TestLog& log, deUint32 programGL)
+		: m_gl			(gl)
+		, m_log			(log)
+		, m_programGL	(programGL)
+	{
+	}
+
+	void						assign1i (const string& name, int x);
+	void						assign3f (const string& name, float x, float y, float z);
+
+private:
+	int							getLocation (const string& name);
+
+	const glw::Functions&		m_gl;
+	TestLog&					m_log;
+	const deUint32				m_programGL;
+
+	std::map<string, int>		m_uniformLocations;
+};
+
+int UniformAccessLogger::getLocation (const string& name)
+{
+	if (m_uniformLocations.find(name) == m_uniformLocations.end())
+	{
+		const int loc = m_gl.getUniformLocation(m_programGL, name.c_str());
+		TCU_CHECK(loc != -1);
+		m_uniformLocations[name] = loc;
+	}
+	return m_uniformLocations[name];
+}
+
+void UniformAccessLogger::assign1i (const string& name, int x)
+{
+	const int loc = getLocation(name);
+	m_log << TestLog::Message << "// Assigning to uniform " << name << ": " << x << TestLog::EndMessage;
+	m_gl.uniform1i(loc, x);
+}
+
+void UniformAccessLogger::assign3f (const string& name, float x, float y, float z)
+{
+	const int loc = getLocation(name);
+	m_log << TestLog::Message << "// Assigning to uniform " << name << ": " << Vec3(x, y, z) << TestLog::EndMessage;
+	m_gl.uniform3f(loc, x, y, z);
+}
+
+//! Class containing a (single-level) texture of a given type. Supports accessing pixels with coordinate convention similar to that in imageStore() and imageLoad() in shaders; useful especially for cube maps.
+class LayeredImage
+{
+public:
+												LayeredImage				(TextureType type, const TextureFormat& format, int w, int h, int d);
+
+	TextureType									getImageType				(void) const { return m_type; }
+	const IVec3&								getSize						(void) const { return m_size; }
+	const TextureFormat&						getFormat					(void) const { return m_format; }
+
+	// \note For cube maps, set/getPixel's z parameter specifies the cube face in the same manner as in imageStore/imageLoad in GL shaders (see glslImageFuncZToCubeFace), instead of directly as a tcu::CubeFace.
+
+	template <typename ColorT>
+	void										setPixel					(int x, int y, int z, const ColorT& color) const;
+
+	Vec4										getPixel					(int x, int y, int z) const;
+	IVec4										getPixelInt					(int x, int y, int z) const;
+	UVec4										getPixelUint				(int x, int y, int z) const { return getPixelInt(x, y, z).asUint(); }
+
+	PixelBufferAccess							getAccess					(void)							{ return getAccessInternal();				}
+	PixelBufferAccess							getSliceAccess				(int slice)						{ return getSliceAccessInternal(slice);		}
+	PixelBufferAccess							getCubeFaceAccess			(tcu::CubeFace face)			{ return getCubeFaceAccessInternal(face);	}
+
+	ConstPixelBufferAccess						getAccess					(void)					const	{ return getAccessInternal();				}
+	ConstPixelBufferAccess						getSliceAccess				(int slice)				const	{ return getSliceAccessInternal(slice);		}
+	ConstPixelBufferAccess						getCubeFaceAccess			(tcu::CubeFace face)	const	{ return getCubeFaceAccessInternal(face);	}
+
+private:
+												LayeredImage				(const LayeredImage&);
+	LayeredImage&								operator=					(const LayeredImage&);
+
+	// Some helpers to reduce code duplication between const/non-const versions of getAccess and others.
+	PixelBufferAccess							getAccessInternal			(void) const;
+	PixelBufferAccess							getSliceAccessInternal		(int slice) const;
+	PixelBufferAccess							getCubeFaceAccessInternal	(tcu::CubeFace face) const;
+
+	const TextureType							m_type;
+	const IVec3									m_size;
+	const TextureFormat							m_format;
+
+	// \note Depending on m_type, exactly one of the following will contain non-null.
+	const SharedPtr<tcu::Texture1D>				m_texBuffer;
+	const SharedPtr<tcu::Texture2D>				m_tex2D;
+	const SharedPtr<tcu::TextureCube>			m_texCube;
+	const SharedPtr<tcu::Texture3D>				m_tex3D;
+	const SharedPtr<tcu::Texture2DArray>		m_tex2DArray;
+};
+
+LayeredImage::LayeredImage (TextureType type, const TextureFormat& format, int w, int h, int d)
+	: m_type		(type)
+	, m_size		(w, h, d)
+	, m_format		(format)
+	, m_texBuffer	(type == TEXTURETYPE_BUFFER		? SharedPtr<tcu::Texture1D>			(newOneLevelTexture1D		(format, w))		: SharedPtr<tcu::Texture1D>())
+	, m_tex2D		(type == TEXTURETYPE_2D			? SharedPtr<tcu::Texture2D>			(newOneLevelTexture2D		(format, w, h))		: SharedPtr<tcu::Texture2D>())
+	, m_texCube		(type == TEXTURETYPE_CUBE		? SharedPtr<tcu::TextureCube>		(newOneLevelTextureCube		(format, w))		: SharedPtr<tcu::TextureCube>())
+	, m_tex3D		(type == TEXTURETYPE_3D			? SharedPtr<tcu::Texture3D>			(newOneLevelTexture3D		(format, w, h, d))	: SharedPtr<tcu::Texture3D>())
+	, m_tex2DArray	(type == TEXTURETYPE_2D_ARRAY	? SharedPtr<tcu::Texture2DArray>	(newOneLevelTexture2DArray	(format, w, h, d))	: SharedPtr<tcu::Texture2DArray>())
+{
+	DE_ASSERT(m_size.z() == 1					||
+			  m_type == TEXTURETYPE_3D			||
+			  m_type == TEXTURETYPE_2D_ARRAY);
+
+	DE_ASSERT(m_size.y() == 1					||
+			  m_type == TEXTURETYPE_2D			||
+			  m_type == TEXTURETYPE_CUBE		||
+			  m_type == TEXTURETYPE_3D			||
+			  m_type == TEXTURETYPE_2D_ARRAY);
+
+	DE_ASSERT(w == h || type != TEXTURETYPE_CUBE);
+
+	DE_ASSERT(m_texBuffer	!= DE_NULL ||
+			  m_tex2D		!= DE_NULL ||
+			  m_texCube		!= DE_NULL ||
+			  m_tex3D		!= DE_NULL ||
+			  m_tex2DArray	!= DE_NULL);
+}
+
+template <typename ColorT>
+void LayeredImage::setPixel (int x, int y, int z, const ColorT& color) const
+{
+	const PixelBufferAccess access = m_type == TEXTURETYPE_BUFFER		? m_texBuffer->getLevel(0)
+								   : m_type == TEXTURETYPE_2D			? m_tex2D->getLevel(0)
+								   : m_type == TEXTURETYPE_CUBE			? m_texCube->getLevelFace(0, glslImageFuncZToCubeFace(z))
+								   : m_type == TEXTURETYPE_3D			? m_tex3D->getLevel(0)
+								   : m_type == TEXTURETYPE_2D_ARRAY		? m_tex2DArray->getLevel(0)
+								   : PixelBufferAccess(TextureFormat(), -1, -1, -1, DE_NULL);
+
+	access.setPixel(color, x, y, m_type == TEXTURETYPE_CUBE ? 0 : z);
+}
+
+Vec4 LayeredImage::getPixel (int x, int y, int z) const
+{
+	const ConstPixelBufferAccess access = m_type == TEXTURETYPE_CUBE ? getCubeFaceAccess(glslImageFuncZToCubeFace(z)) : getAccess();
+	return access.getPixel(x, y, m_type == TEXTURETYPE_CUBE ? 0 : z);
+}
+
+IVec4 LayeredImage::getPixelInt (int x, int y, int z) const
+{
+	const ConstPixelBufferAccess access = m_type == TEXTURETYPE_CUBE ? getCubeFaceAccess(glslImageFuncZToCubeFace(z)) : getAccess();
+	return access.getPixelInt(x, y, m_type == TEXTURETYPE_CUBE ? 0 : z);
+}
+
+PixelBufferAccess LayeredImage::getAccessInternal (void) const
+{
+	DE_ASSERT(m_type == TEXTURETYPE_BUFFER || m_type == TEXTURETYPE_2D || m_type == TEXTURETYPE_3D || m_type == TEXTURETYPE_2D_ARRAY);
+
+	return m_type == TEXTURETYPE_BUFFER		? m_texBuffer->getLevel(0)
+		 : m_type == TEXTURETYPE_2D			? m_tex2D->getLevel(0)
+		 : m_type == TEXTURETYPE_3D			? m_tex3D->getLevel(0)
+		 : m_type == TEXTURETYPE_2D_ARRAY	? m_tex2DArray->getLevel(0)
+		 : PixelBufferAccess(TextureFormat(), -1, -1, -1, DE_NULL);
+}
+
+PixelBufferAccess LayeredImage::getSliceAccessInternal (int slice) const
+{
+	const PixelBufferAccess srcAccess = getAccessInternal();
+	return tcu::getSubregion(srcAccess, 0, 0, slice, srcAccess.getWidth(), srcAccess.getHeight(), 1);
+}
+
+PixelBufferAccess LayeredImage::getCubeFaceAccessInternal (tcu::CubeFace face) const
+{
+	DE_ASSERT(m_type == TEXTURETYPE_CUBE);
+	return m_texCube->getLevelFace(0, face);
+}
+
+//! Set texture storage or, if using buffer texture, setup buffer and attach to texture.
+static void setTextureStorage (glu::CallLogWrapper& glLog, TextureType imageType, deUint32 internalFormat, const IVec3& imageSize, deUint32 textureBufGL)
+{
+	const deUint32 textureTarget = getGLTextureTarget(imageType);
+
+	switch (imageType)
+	{
+		case TEXTURETYPE_BUFFER:
+		{
+			const TextureFormat		format		= glu::mapGLInternalFormat(internalFormat);
+			const int				numBytes	= format.getPixelSize() * imageSize.x();
+			DE_ASSERT(isFormatSupportedForTextureBuffer(format));
+			glLog.glBindBuffer(GL_TEXTURE_BUFFER, textureBufGL);
+			glLog.glBufferData(GL_TEXTURE_BUFFER, numBytes, DE_NULL, GL_STATIC_DRAW);
+			glLog.glTexBuffer(GL_TEXTURE_BUFFER, internalFormat, textureBufGL);
+			DE_ASSERT(imageSize.y() == 1 && imageSize.z() == 1);
+			break;
+		}
+
+		// \note Fall-throughs.
+
+		case TEXTURETYPE_2D:
+		case TEXTURETYPE_CUBE:
+			glLog.glTexStorage2D(textureTarget, 1, internalFormat, imageSize.x(), imageSize.y());
+			DE_ASSERT(imageSize.z() == 1);
+			break;
+
+		case TEXTURETYPE_3D:
+		case TEXTURETYPE_2D_ARRAY:
+			glLog.glTexStorage3D(textureTarget, 1, internalFormat, imageSize.x(), imageSize.y(), imageSize.z());
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+static void uploadTexture (glu::CallLogWrapper& glLog, const LayeredImage& src, deUint32 textureBufGL)
+{
+	const deUint32				internalFormat	= glu::getInternalFormat(src.getFormat());
+	const glu::TransferFormat	transferFormat	= glu::getTransferFormat(src.getFormat());
+	const IVec3&				imageSize		= src.getSize();
+
+	setTextureStorage(glLog, src.getImageType(), internalFormat, imageSize, textureBufGL);
+
+	{
+		const int	pixelSize = src.getFormat().getPixelSize();
+		int			unpackAlignment;
+
+		if (deIsPowerOfTwo32(pixelSize))
+			unpackAlignment = 8;
+		else
+			unpackAlignment = 1;
+
+		glLog.glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlignment);
+	}
+
+	if (src.getImageType() == TEXTURETYPE_BUFFER)
+	{
+		glLog.glBindBuffer(GL_TEXTURE_BUFFER, textureBufGL);
+		glLog.glBufferData(GL_TEXTURE_BUFFER, src.getFormat().getPixelSize() * imageSize.x(), src.getAccess().getDataPtr(), GL_STATIC_DRAW);
+	}
+	else if (src.getImageType() == TEXTURETYPE_2D)
+		glLog.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, imageSize.x(), imageSize.y(), transferFormat.format, transferFormat.dataType, src.getAccess().getDataPtr());
+	else if (src.getImageType() == TEXTURETYPE_CUBE)
+	{
+		for (int faceI = 0; faceI < tcu::CUBEFACE_LAST; faceI++)
+		{
+			const tcu::CubeFace face = (tcu::CubeFace)faceI;
+			glLog.glTexSubImage2D(cubeFaceToGLFace(face), 0, 0, 0, imageSize.x(), imageSize.y(), transferFormat.format, transferFormat.dataType, src.getCubeFaceAccess(face).getDataPtr());
+		}
+	}
+	else
+	{
+		DE_ASSERT(src.getImageType() == TEXTURETYPE_3D || src.getImageType() == TEXTURETYPE_2D_ARRAY);
+		const deUint32 textureTarget = getGLTextureTarget(src.getImageType());
+		glLog.glTexSubImage3D(textureTarget, 0, 0, 0, 0, imageSize.x(), imageSize.y(), imageSize.z(), transferFormat.format, transferFormat.dataType, src.getAccess().getDataPtr());
+	}
+}
+
+static void readPixelsRGBAInteger32 (const PixelBufferAccess& dst, int originX, int originY, glu::CallLogWrapper& glLog)
+{
+	DE_ASSERT(dst.getDepth() == 1);
+
+	if (isFormatTypeUnsignedInteger(dst.getFormat().type))
+	{
+		vector<UVec4> data(dst.getWidth()*dst.getHeight());
+
+		glLog.glReadPixels(originX, originY, dst.getWidth(), dst.getHeight(), GL_RGBA_INTEGER, GL_UNSIGNED_INT, &data[0]);
+
+		for (int y = 0; y < dst.getHeight(); y++)
+		for (int x = 0; x < dst.getWidth(); x++)
+			dst.setPixel(data[y*dst.getWidth() + x], x, y);
+	}
+	else if (isFormatTypeSignedInteger(dst.getFormat().type))
+	{
+		vector<IVec4> data(dst.getWidth()*dst.getHeight());
+
+		glLog.glReadPixels(originX, originY, dst.getWidth(), dst.getHeight(), GL_RGBA_INTEGER, GL_INT, &data[0]);
+
+		for (int y = 0; y < dst.getHeight(); y++)
+		for (int x = 0; x < dst.getWidth(); x++)
+			dst.setPixel(data[y*dst.getWidth() + x], x, y);
+	}
+	else
+		DE_ASSERT(false);
+}
+
+//! Base for a functor for verifying and logging a 2d texture layer (2d image, cube face, 3d slice, 2d layer).
+class ImageLayerVerifier
+{
+public:
+	virtual bool	operator()				(TestLog&, const ConstPixelBufferAccess&, int sliceOrFaceNdx) const = 0;
+	virtual			~ImageLayerVerifier		(void) {}
+};
+
+static void setTexParameteri (glu::CallLogWrapper& glLog, deUint32 target)
+{
+	if (target != GL_TEXTURE_BUFFER)
+	{
+		glLog.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		glLog.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	}
+}
+
+//! Binds texture (one layer at a time) to color attachment of FBO and does glReadPixels(). Calls the verifier for each layer.
+//! \note Not for buffer textures.
+static bool readIntegerTextureViaFBOAndVerify (const RenderContext&			renderCtx,
+											   glu::CallLogWrapper&			glLog,
+											   deUint32						textureGL,
+											   TextureType					textureType,
+											   const TextureFormat&			textureFormat,
+											   const IVec3&					textureSize,
+											   const ImageLayerVerifier&	verifyLayer)
+{
+	DE_ASSERT(isFormatTypeInteger(textureFormat.type));
+	DE_ASSERT(textureType != TEXTURETYPE_BUFFER);
+
+	TestLog& log = glLog.getLog();
+
+	const tcu::ScopedLogSection section(log, "Verification", "Result verification (bind texture layer-by-layer to FBO, read with glReadPixels())");
+
+	const int			numSlicesOrFaces	= textureType == TEXTURETYPE_CUBE ? 6 : textureSize.z();
+	const deUint32		textureTargetGL		= getGLTextureTarget(textureType);
+	glu::Framebuffer	fbo					(renderCtx);
+	tcu::TextureLevel	resultSlice			(textureFormat, textureSize.x(), textureSize.y());
+
+	glLog.glBindFramebuffer(GL_FRAMEBUFFER, *fbo);
+	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "Bind FBO");
+
+	glLog.glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
+	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glMemoryBarrier");
+
+	glLog.glActiveTexture(GL_TEXTURE0);
+	glLog.glBindTexture(textureTargetGL, textureGL);
+	setTexParameteri(glLog, textureTargetGL);
+
+	for (int sliceOrFaceNdx = 0; sliceOrFaceNdx < numSlicesOrFaces; sliceOrFaceNdx++)
+	{
+		if (textureType == TEXTURETYPE_CUBE)
+			glLog.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx)), textureGL, 0);
+		else if (textureType == TEXTURETYPE_2D)
+			glLog.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureGL, 0);
+		else if (textureType == TEXTURETYPE_3D || textureType == TEXTURETYPE_2D_ARRAY)
+			glLog.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureGL, 0, sliceOrFaceNdx);
+		else
+			DE_ASSERT(false);
+
+		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "Bind texture to framebuffer color attachment 0");
+
+		TCU_CHECK(glLog.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+		readPixelsRGBAInteger32(resultSlice.getAccess(), 0, 0, glLog);
+		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glReadPixels");
+
+		if (!verifyLayer(log, resultSlice, sliceOrFaceNdx))
+			return false;
+	}
+
+	return true;
+}
+
+//! Reads texture with texture() in compute shader, one layer at a time, putting values into a SSBO and reading with a mapping. Calls the verifier for each layer.
+//! \note Not for buffer textures.
+static bool readFloatOrNormTextureWithLookupsAndVerify (const RenderContext&		renderCtx,
+														glu::CallLogWrapper&		glLog,
+														deUint32					textureGL,
+														TextureType					textureType,
+														const TextureFormat&		textureFormat,
+														const IVec3&				textureSize,
+														const ImageLayerVerifier&	verifyLayer)
+{
+	DE_ASSERT(!isFormatTypeInteger(textureFormat.type));
+	DE_ASSERT(textureType != TEXTURETYPE_BUFFER);
+
+	TestLog& log = glLog.getLog();
+
+	const tcu::ScopedLogSection section(log, "Verification", "Result verification (read texture layer-by-layer in compute shader with texture() into SSBO)");
+
+	const glu::ShaderProgram program(renderCtx,
+		glu::ProgramSources() << glu::ComputeSource("#version 310 es\n"
+													"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+													"layout (binding = 0) buffer Output\n"
+													"{\n"
+													"	vec4 color[" + toString(textureSize.x()*textureSize.y()) + "];\n"
+													"} sb_out;\n"
+													"\n"
+													"precision highp " + getShaderSamplerType(textureFormat.type, textureType) + ";\n"
+													"\n"
+													"uniform highp " + getShaderSamplerType(textureFormat.type, textureType) + " u_texture;\n"
+													"uniform highp vec3 u_texCoordLD;\n"
+													"uniform highp vec3 u_texCoordRD;\n"
+													"uniform highp vec3 u_texCoordLU;\n"
+													"uniform highp vec3 u_texCoordRU;\n"
+													"\n"
+													"void main (void)\n"
+													"{\n"
+													"	int gx = int(gl_GlobalInvocationID.x);\n"
+													"	int gy = int(gl_GlobalInvocationID.y);\n"
+													"	highp float s = (float(gx) + 0.5) / float(" + toString(textureSize.x()) + ");\n"
+													"	highp float t = (float(gy) + 0.5) / float(" + toString(textureType == TEXTURETYPE_CUBE ? textureSize.x() : textureSize.y()) + ");\n"
+													"	highp vec3 texCoord = u_texCoordLD*(1.0-s)*(1.0-t)\n"
+													"	                    + u_texCoordRD*(    s)*(1.0-t)\n"
+													"	                    + u_texCoordLU*(1.0-s)*(    t)\n"
+													"	                    + u_texCoordRU*(    s)*(    t);\n"
+													"	int ndx = gy*" + toString(textureSize.x()) + " + gx;\n"
+													"	sb_out.color[ndx] = texture(u_texture, texCoord" + (textureType == TEXTURETYPE_2D ? ".xy" : "") + ");\n"
+													"}\n"));
+
+	glLog.glUseProgram(program.getProgram());
+
+	log << program;
+
+	if (!program.isOk())
+	{
+		log << TestLog::Message << "// Failure: failed to compile program" << TestLog::EndMessage;
+		TCU_FAIL("Program compilation failed");
+	}
+
+	{
+		const deUint32			textureTargetGL		= getGLTextureTarget(textureType);
+		const glu::Buffer		outputBuffer		(renderCtx);
+		UniformAccessLogger		uniforms			(renderCtx.getFunctions(), log, program.getProgram());
+
+		// Setup texture.
+
+		glLog.glActiveTexture(GL_TEXTURE0);
+		glLog.glBindTexture(textureTargetGL, textureGL);
+		setTexParameteri(glLog, textureTargetGL);
+
+		uniforms.assign1i("u_texture", 0);
+
+		// Setup output buffer.
+		{
+			const deUint32		blockIndex		= glLog.glGetProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int			blockSize		= glu::getProgramResourceInt(renderCtx.getFunctions(), program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+
+			log << TestLog::Message << "// Got buffer data size = " << blockSize << TestLog::EndMessage;
+			TCU_CHECK(blockSize > 0);
+
+			glLog.glBindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+			glLog.glBufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
+			glLog.glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "SSB setup failed");
+		}
+
+		// Dispatch one layer at a time, read back and verify.
+		{
+			const int							numSlicesOrFaces	= textureType == TEXTURETYPE_CUBE ? 6 : textureSize.z();
+			tcu::TextureLevel					resultSlice			(textureFormat, textureSize.x(), textureSize.y());
+			const PixelBufferAccess				resultSliceAccess	= resultSlice.getAccess();
+			const deUint32						blockIndex			= glLog.glGetProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
+			const int							blockSize			= glu::getProgramResourceInt(renderCtx.getFunctions(), program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
+			const deUint32						valueIndex			= glLog.glGetProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.color");
+			const glu::InterfaceVariableInfo	valueInfo			= glu::getProgramInterfaceVariableInfo(renderCtx.getFunctions(), program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
+
+			TCU_CHECK(valueInfo.arraySize == (deUint32)(textureSize.x()*textureSize.y()));
+
+			glLog.glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
+
+			for (int sliceOrFaceNdx = 0; sliceOrFaceNdx < numSlicesOrFaces; sliceOrFaceNdx++)
+			{
+				if (textureType == TEXTURETYPE_CUBE)
+				{
+					vector<float> coords;
+					computeQuadTexCoordCube(coords, glslImageFuncZToCubeFace(sliceOrFaceNdx));
+					uniforms.assign3f("u_texCoordLD", coords[3*0 + 0], coords[3*0 + 1], coords[3*0 + 2]);
+					uniforms.assign3f("u_texCoordRD", coords[3*2 + 0], coords[3*2 + 1], coords[3*2 + 2]);
+					uniforms.assign3f("u_texCoordLU", coords[3*1 + 0], coords[3*1 + 1], coords[3*1 + 2]);
+					uniforms.assign3f("u_texCoordRU", coords[3*3 + 0], coords[3*3 + 1], coords[3*3 + 2]);
+				}
+				else
+				{
+					const float z = textureType == TEXTURETYPE_3D ?
+										((float)sliceOrFaceNdx + 0.5f) / (float)numSlicesOrFaces :
+										(float)sliceOrFaceNdx;
+					uniforms.assign3f("u_texCoordLD", 0.0f, 0.0f, z);
+					uniforms.assign3f("u_texCoordRD", 1.0f, 0.0f, z);
+					uniforms.assign3f("u_texCoordLU", 0.0f, 1.0f, z);
+					uniforms.assign3f("u_texCoordRU", 1.0f, 1.0f, z);
+				}
+
+				glLog.glDispatchCompute(textureSize.x(), textureSize.y(), 1);
+
+				{
+					log << TestLog::Message << "// Note: mapping buffer and reading color values written" << TestLog::EndMessage;
+
+					const BufferMemMap bufMap(renderCtx.getFunctions(), GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
+
+					for (int y = 0; y < textureSize.y(); y++)
+					for (int x = 0; x < textureSize.x(); x++)
+					{
+						const int				ndx			= y*textureSize.x() + x;
+						const float* const		clrData		= (const float*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx);
+
+						switch (textureFormat.order)
+						{
+							case TextureFormat::R:		resultSliceAccess.setPixel(Vec4(clrData[0]),											x, y); break;
+							case TextureFormat::RGBA:	resultSliceAccess.setPixel(Vec4(clrData[0], clrData[1], clrData[2], clrData[3]),		x, y); break;
+							default:
+								DE_ASSERT(false);
+						}
+					}
+				}
+
+				if (!verifyLayer(log, resultSliceAccess, sliceOrFaceNdx))
+					return false;
+			}
+		}
+
+		return true;
+	}
+}
+
+//! Read buffer texture by reading the corresponding buffer with a mapping.
+static bool readBufferTextureWithMappingAndVerify (const RenderContext&			renderCtx,
+												   glu::CallLogWrapper&			glLog,
+												   deUint32						bufferGL,
+												   const TextureFormat&			textureFormat,
+												   int							imageSize,
+												   const ImageLayerVerifier&	verifyLayer)
+{
+	tcu::TextureLevel			result			(textureFormat, imageSize, 1);
+	const PixelBufferAccess		resultAccess	= result.getAccess();
+	DE_ASSERT(resultAccess.getDataSize() == imageSize * textureFormat.getPixelSize());
+
+	const tcu::ScopedLogSection section(glLog.getLog(), "Verification", "Result verification (read texture's buffer with a mapping)");
+	glLog.glBindBuffer(GL_TEXTURE_BUFFER, bufferGL);
+
+	{
+		const BufferMemMap bufMap(renderCtx.getFunctions(), GL_TEXTURE_BUFFER, 0, resultAccess.getDataSize(), GL_MAP_READ_BIT);
+		deMemcpy(resultAccess.getDataPtr(), bufMap.getPtr(), resultAccess.getDataSize());
+	}
+
+	return verifyLayer(glLog.getLog(), resultAccess, 0);
+}
+
+//! Calls the appropriate texture verification function depending on texture format or type.
+static bool readTextureAndVerify (const RenderContext&			renderCtx,
+								  glu::CallLogWrapper&			glLog,
+								  deUint32						textureGL,
+								  deUint32						bufferGL,
+								  TextureType					textureType,
+								  const TextureFormat&			textureFormat,
+								  const IVec3&					imageSize,
+								  const ImageLayerVerifier&		verifyLayer)
+{
+	if (textureType == TEXTURETYPE_BUFFER)
+		return readBufferTextureWithMappingAndVerify(renderCtx, glLog, bufferGL, textureFormat, imageSize.x(), verifyLayer);
+	else
+		return isFormatTypeInteger(textureFormat.type) ? readIntegerTextureViaFBOAndVerify				(renderCtx, glLog, textureGL, textureType, textureFormat, imageSize, verifyLayer)
+													   : readFloatOrNormTextureWithLookupsAndVerify		(renderCtx, glLog, textureGL, textureType, textureFormat, imageSize, verifyLayer);
+}
+
+//! An ImageLayerVerifier that simply compares the result slice to a slice in a reference image.
+//! \note Holds the reference image as a reference (no pun intended) instead of a copy; caller must be aware of lifetime issues.
+class ImageLayerComparer : public ImageLayerVerifier
+{
+public:
+	ImageLayerComparer (const LayeredImage& reference,
+						const IVec2& relevantRegion = IVec2(0) /* If given, only check this region of each slice. */)
+		: m_reference		(reference)
+		, m_relevantRegion	(relevantRegion.x() > 0 && relevantRegion.y() > 0 ? relevantRegion : reference.getSize().swizzle(0, 1))
+	{
+	}
+
+	bool operator() (TestLog& log, const tcu::ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const
+	{
+		const bool						isCube				= m_reference.getImageType() == TEXTURETYPE_CUBE;
+		const ConstPixelBufferAccess	referenceSlice		= tcu::getSubregion(isCube ? m_reference.getCubeFaceAccess(glslImageFuncZToCubeFace(sliceOrFaceNdx))
+																					   : m_reference.getSliceAccess(sliceOrFaceNdx),
+																				0, 0, m_relevantRegion.x(), m_relevantRegion.y());
+
+		const string comparisonName = "Comparison" + toString(sliceOrFaceNdx);
+		const string comparisonDesc = "Image Comparison, "
+									+ (isCube ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx))))
+											  : "slice " + toString(sliceOrFaceNdx));
+
+		if (isFormatTypeInteger(m_reference.getFormat().type))
+			return tcu::intThresholdCompare(log, comparisonName.c_str(), comparisonDesc.c_str(), referenceSlice, resultSlice, UVec4(0), tcu::COMPARE_LOG_RESULT);
+		else
+			return tcu::floatThresholdCompare(log, comparisonName.c_str(), comparisonDesc.c_str(), referenceSlice, resultSlice, Vec4(0.01f), tcu::COMPARE_LOG_RESULT);
+	}
+
+private:
+	const LayeredImage&		m_reference;
+	const IVec2				m_relevantRegion;
+};
+
+//! Case that just stores some computation results into an image.
+class ImageStoreCase : public TestCase
+{
+public:
+	enum CaseFlag
+	{
+		CASEFLAG_SINGLE_LAYER_BIND = 1 << 0 //!< If given, glBindImageTexture() is called with GL_FALSE <layered> argument, and for each layer the compute shader is separately dispatched.
+	};
+
+	ImageStoreCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType textureType, deUint32 caseFlags = 0)
+		: TestCase				(context, name, description)
+		, m_format				(format)
+		, m_textureType			(textureType)
+		, m_singleLayerBind		((caseFlags & CASEFLAG_SINGLE_LAYER_BIND) != 0)
+	{
+	}
+
+	void			init		(void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_textureType); }
+	IterateResult	iterate		(void);
+
+private:
+	const TextureFormat		m_format;
+	const TextureType		m_textureType;
+	const bool				m_singleLayerBind;
+};
+
+ImageStoreCase::IterateResult ImageStoreCase::iterate (void)
+{
+	const RenderContext&		renderCtx				= m_context.getRenderContext();
+	TestLog&					log						(m_testCtx.getLog());
+	glu::CallLogWrapper			glLog					(renderCtx.getFunctions(), log);
+	const deUint32				internalFormatGL		= glu::getInternalFormat(m_format);
+	const deUint32				textureTargetGL			= getGLTextureTarget(m_textureType);
+	const IVec3&				imageSize				= defaultImageSize(m_textureType);
+	const int					numSlicesOrFaces		= m_textureType == TEXTURETYPE_CUBE ? 6 : imageSize.z();
+	const int					maxImageDimension		= de::max(imageSize.x(), de::max(imageSize.y(), imageSize.z()));
+	const float					storeColorScale			= isFormatTypeUnorm(m_format.type) ? 1.0f / (float)(maxImageDimension - 1)
+														: isFormatTypeSnorm(m_format.type) ? 2.0f / (float)(maxImageDimension - 1)
+														: 1.0f;
+	const float					storeColorBias			= isFormatTypeSnorm(m_format.type) ? -1.0f : 0.0f;
+	const glu::Buffer			textureBuf				(renderCtx); // \note Only really used if using buffer texture.
+	const glu::Texture			texture					(renderCtx);
+
+	glLog.enableLogging(true);
+
+	// Setup texture.
+
+	log << TestLog::Message << "// Created a texture (name " << *texture << ")" << TestLog::EndMessage;
+	if (m_textureType == TEXTURETYPE_BUFFER)
+		log << TestLog::Message << "// Created a buffer for the texture (name " << *textureBuf << ")" << TestLog::EndMessage;
+
+	glLog.glActiveTexture(GL_TEXTURE0);
+	glLog.glBindTexture(textureTargetGL, *texture);
+	setTexParameteri(glLog, textureTargetGL);
+	setTextureStorage(glLog, m_textureType, internalFormatGL, imageSize, *textureBuf);
+
+	// Perform image stores in compute shader.
+
+	{
+		// Generate compute shader.
+
+		const string		shaderImageFormatStr	= getShaderImageFormatQualifier(m_format);
+		const TextureType	shaderImageType			= m_singleLayerBind ? textureLayerType(m_textureType) : m_textureType;
+		const string		shaderImageTypeStr		= getShaderImageType(m_format.type, shaderImageType);
+		const bool			isUintFormat			= isFormatTypeUnsignedInteger(m_format.type);
+		const bool			isIntFormat				= isFormatTypeSignedInteger(m_format.type);
+		const string		colorBaseExpr			= string(isUintFormat ? "u" : isIntFormat ? "i" : "") + "vec4(gx^gy^gz, "
+																												 "(" + toString(imageSize.x()-1) + "-gx)^gy^gz, "
+																												 "gx^(" + toString(imageSize.y()-1) + "-gy)^gz, "
+																												 "(" + toString(imageSize.x()-1) + "-gx)^(" + toString(imageSize.y()-1) + "-gy)^gz)";
+		const string		colorExpr				= colorBaseExpr + (storeColorScale == 1.0f ? "" : "*" + toString(storeColorScale))
+																	+ (storeColorBias == 0.0f ? "" : " + float(" + toString(storeColorBias) + ")");
+
+		const glu::ShaderProgram program(renderCtx,
+			glu::ProgramSources() << glu::ComputeSource("#version 310 es\n"
+														+ textureTypeExtensionShaderRequires(shaderImageType) +
+														"\n"
+														"precision highp " + shaderImageTypeStr + ";\n"
+														"\n"
+														"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+														"layout (" + shaderImageFormatStr + ", binding=0) writeonly uniform " + shaderImageTypeStr + " u_image;\n"
+														+ (m_singleLayerBind ? "uniform int u_layerNdx;\n" : "") +
+														"\n"
+														"void main (void)\n"
+														"{\n"
+														"	int gx = int(gl_GlobalInvocationID.x);\n"
+														"	int gy = int(gl_GlobalInvocationID.y);\n"
+														"	int gz = " + (m_singleLayerBind ? "u_layerNdx" : "int(gl_GlobalInvocationID.z)") + ";\n"
+														+ (shaderImageType == TEXTURETYPE_BUFFER ?
+															"	imageStore(u_image, gx, " + colorExpr + ");\n"
+														 : shaderImageType == TEXTURETYPE_2D ?
+															"	imageStore(u_image, ivec2(gx, gy), " + colorExpr + ");\n"
+														 : shaderImageType == TEXTURETYPE_3D || shaderImageType == TEXTURETYPE_CUBE || shaderImageType == TEXTURETYPE_2D_ARRAY ?
+															"	imageStore(u_image, ivec3(gx, gy, gz), " + colorExpr + ");\n"
+														 : DE_NULL) +
+														"}\n"));
+
+		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());
+
+		log << program;
+
+		if (!program.isOk())
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
+			return STOP;
+		}
+
+		// Setup and dispatch.
+
+		glLog.glUseProgram(program.getProgram());
+
+		if (m_singleLayerBind)
+		{
+			for (int layerNdx = 0; layerNdx < numSlicesOrFaces; layerNdx++)
+			{
+				if (layerNdx > 0)
+					glLog.glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+
+				uniforms.assign1i("u_layerNdx", layerNdx);
+
+				glLog.glBindImageTexture(0, *texture, 0, GL_FALSE, layerNdx, GL_WRITE_ONLY, internalFormatGL);
+				GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+				glLog.glDispatchCompute(imageSize.x(), imageSize.y(), 1);
+				GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
+			}
+		}
+		else
+		{
+			glLog.glBindImageTexture(0, *texture, 0, GL_TRUE, 0, GL_WRITE_ONLY, internalFormatGL);
+			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+			glLog.glDispatchCompute(imageSize.x(), imageSize.y(), numSlicesOrFaces);
+			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
+		}
+	}
+
+	// Create reference, read texture and compare to reference.
+	{
+		const int		isIntegerFormat		= isFormatTypeInteger(m_format.type);
+		LayeredImage	reference			(m_textureType, m_format, imageSize.x(), imageSize.y(), imageSize.z());
+
+		DE_ASSERT(!isIntegerFormat || (storeColorScale == 1.0f && storeColorBias == 0.0f));
+
+		for (int z = 0; z < numSlicesOrFaces; z++)
+		for (int y = 0; y < imageSize.y(); y++)
+		for (int x = 0; x < imageSize.x(); x++)
+		{
+			const IVec4 color(x^y^z, (imageSize.x()-1-x)^y^z, x^(imageSize.y()-1-y)^z, (imageSize.x()-1-x)^(imageSize.y()-1-y)^z);
+
+			if (isIntegerFormat)
+				reference.setPixel(x, y, z, color);
+			else
+				reference.setPixel(x, y, z, color.asFloat()*storeColorScale + storeColorBias);
+		}
+
+		const bool compareOk = readTextureAndVerify(renderCtx, glLog, *texture, *textureBuf, m_textureType, m_format, imageSize, ImageLayerComparer(reference));
+
+		m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, compareOk ? "Pass" : "Image comparison failed");
+		return STOP;
+	}
+}
+
+//! Case that copies an image to another, using imageLoad() and imageStore(). Texture formats don't necessarily match image formats.
+class ImageLoadAndStoreCase : public TestCase
+{
+public:
+	enum CaseFlag
+	{
+		CASEFLAG_SINGLE_LAYER_BIND	= 1 << 0,	//!< If given, glBindImageTexture() is called with GL_FALSE <layered> argument, and for each layer the compute shader is separately dispatched.
+		CASEFLAG_RESTRICT_IMAGES	= 1 << 1	//!< If given, images in shader will be qualified with "restrict".
+	};
+
+	ImageLoadAndStoreCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType textureType, deUint32 caseFlags = 0)
+		: TestCase				(context, name, description)
+		, m_textureFormat		(format)
+		, m_imageFormat			(format)
+		, m_textureType			(textureType)
+		, m_restrictImages		((caseFlags & CASEFLAG_RESTRICT_IMAGES)		!= 0)
+		, m_singleLayerBind		((caseFlags & CASEFLAG_SINGLE_LAYER_BIND)	!= 0)
+	{
+	}
+
+	ImageLoadAndStoreCase (Context& context, const char* name, const char* description, const TextureFormat& textureFormat, const TextureFormat& imageFormat, TextureType textureType, deUint32 caseFlags = 0)
+		: TestCase				(context, name, description)
+		, m_textureFormat		(textureFormat)
+		, m_imageFormat			(imageFormat)
+		, m_textureType			(textureType)
+		, m_restrictImages		((caseFlags & CASEFLAG_RESTRICT_IMAGES)		!= 0)
+		, m_singleLayerBind		((caseFlags & CASEFLAG_SINGLE_LAYER_BIND)	!= 0)
+	{
+		DE_ASSERT(textureFormat.getPixelSize() == imageFormat.getPixelSize());
+	}
+
+	void			init		(void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_textureType); }
+	IterateResult	iterate		(void);
+
+private:
+	template <TextureFormat::ChannelType ImageFormatType, typename TcuFloatType, typename TcuFloatStorageType>
+	static void					replaceBadFloatReinterpretValues (LayeredImage& image, const TextureFormat& imageFormat);
+
+	const TextureFormat			m_textureFormat;
+	const TextureFormat			m_imageFormat;
+	const TextureType			m_textureType;
+	const bool					m_restrictImages;
+	const bool					m_singleLayerBind;
+};
+
+template <TextureFormat::ChannelType ImageFormatType, typename TcuFloatType, typename TcuFloatTypeStorageType>
+void ImageLoadAndStoreCase::replaceBadFloatReinterpretValues (LayeredImage& image, const TextureFormat& imageFormat)
+{
+	// Find potential bad values, such as nan or inf, and replace with something else.
+	const int		pixelSize			= imageFormat.getPixelSize();
+	const int		imageNumChannels	= imageFormat.order == tcu::TextureFormat::R	? 1
+										: imageFormat.order == tcu::TextureFormat::RGBA	? 4
+										: 0;
+	const IVec3		imageSize			= image.getSize();
+	const int		numSlicesOrFaces	= image.getImageType() == TEXTURETYPE_CUBE ? 6 : imageSize.z();
+
+	DE_ASSERT(pixelSize % imageNumChannels == 0);
+
+	for (int z = 0; z < numSlicesOrFaces; z++)
+	{
+		const PixelBufferAccess		sliceAccess		= image.getImageType() == TEXTURETYPE_CUBE ? image.getCubeFaceAccess((tcu::CubeFace)z) : image.getSliceAccess(z);
+		const int					rowPitch		= sliceAccess.getRowPitch();
+		void *const					data			= sliceAccess.getDataPtr();
+
+		for (int y = 0; y < imageSize.y(); y++)
+		for (int x = 0; x < imageSize.x(); x++)
+		{
+			void *const pixelData = (deUint8*)data + y*rowPitch + x*pixelSize;
+
+			for (int c = 0; c < imageNumChannels; c++)
+			{
+				void *const			channelData		= (deUint8*)pixelData + c*pixelSize/imageNumChannels;
+				const TcuFloatType	f				(*(TcuFloatTypeStorageType*)channelData);
+
+				if (f.isDenorm() || f.isInf() || f.isNaN())
+					*(TcuFloatTypeStorageType*)channelData = TcuFloatType(0.0f).bits();
+			}
+		}
+	}
+}
+
+ImageLoadAndStoreCase::IterateResult ImageLoadAndStoreCase::iterate (void)
+{
+	const RenderContext&		renderCtx					= m_context.getRenderContext();
+	TestLog&					log							(m_testCtx.getLog());
+	glu::CallLogWrapper			glLog						(renderCtx.getFunctions(), log);
+	const deUint32				textureInternalFormatGL		= glu::getInternalFormat(m_textureFormat);
+	const deUint32				imageInternalFormatGL		= glu::getInternalFormat(m_imageFormat);
+	const deUint32				textureTargetGL				= getGLTextureTarget(m_textureType);
+	const IVec3&				imageSize					= defaultImageSize(m_textureType);
+	const int					maxImageDimension			= de::max(imageSize.x(), de::max(imageSize.y(), imageSize.z()));
+	const float					storeColorScale				= isFormatTypeUnorm(m_textureFormat.type) ? 1.0f / (float)(maxImageDimension - 1)
+															: isFormatTypeSnorm(m_textureFormat.type) ? 2.0f / (float)(maxImageDimension - 1)
+															: 1.0f;
+	const float					storeColorBias				= isFormatTypeSnorm(m_textureFormat.type) ? -1.0f : 0.0f;
+	const int					numSlicesOrFaces			= m_textureType == TEXTURETYPE_CUBE ? 6 : imageSize.z();
+	const bool					isIntegerTextureFormat		= isFormatTypeInteger(m_textureFormat.type);
+	const glu::Buffer			texture0Buf					(renderCtx);
+	const glu::Buffer			texture1Buf					(renderCtx);
+	const glu::Texture			texture0					(renderCtx);
+	const glu::Texture			texture1					(renderCtx);
+	LayeredImage				reference					(m_textureType, m_textureFormat, imageSize.x(), imageSize.y(), imageSize.z());
+
+	glLog.enableLogging(true);
+
+	// Setup textures.
+
+	log << TestLog::Message << "// Created 2 textures (names " << *texture0 << " and " << *texture1 << ")" << TestLog::EndMessage;
+	if (m_textureType == TEXTURETYPE_BUFFER)
+		log << TestLog::Message << "// Created buffers for the textures (names " << *texture0Buf << " and " << *texture1Buf << ")" << TestLog::EndMessage;
+
+	// First, fill reference with (a fairly arbitrary) initial pattern. This will be used as texture upload source data as well as for actual reference computation later on.
+
+	DE_ASSERT(!isIntegerTextureFormat || (storeColorScale == 1.0f && storeColorBias == 0.0f));
+
+	for (int z = 0; z < numSlicesOrFaces; z++)
+	for (int y = 0; y < imageSize.y(); y++)
+	for (int x = 0; x < imageSize.x(); x++)
+	{
+		const IVec4 color(x^y^z, (imageSize.x()-1-x)^y^z, x^(imageSize.y()-1-y)^z, (imageSize.x()-1-x)^(imageSize.y()-1-y)^z);
+
+		if (isIntegerTextureFormat)
+			reference.setPixel(x, y, z, color);
+		else
+			reference.setPixel(x, y, z, color.asFloat()*storeColorScale + storeColorBias);
+	}
+
+	// If re-interpreting the texture contents as floating point values, need to get rid of inf, nan etc.
+	if (m_imageFormat.type == TextureFormat::HALF_FLOAT && m_textureFormat.type != TextureFormat::HALF_FLOAT)
+		replaceBadFloatReinterpretValues<TextureFormat::HALF_FLOAT, tcu::Float16, deUint16>(reference, m_imageFormat);
+	else if (m_imageFormat.type == TextureFormat::FLOAT && m_textureFormat.type != TextureFormat::FLOAT)
+		replaceBadFloatReinterpretValues<TextureFormat::FLOAT, tcu::Float32, deUint32>(reference, m_imageFormat);
+
+	// Upload initial pattern to texture 0.
+
+	glLog.glActiveTexture(GL_TEXTURE0);
+	glLog.glBindTexture(textureTargetGL, *texture0);
+	setTexParameteri(glLog, textureTargetGL);
+
+	log << TestLog::Message << "// Filling texture " << *texture0 << " with xor pattern" << TestLog::EndMessage;
+
+	uploadTexture(glLog, reference, *texture0Buf);
+
+	// Set storage for texture 1.
+
+	glLog.glActiveTexture(GL_TEXTURE1);
+	glLog.glBindTexture(textureTargetGL, *texture1);
+	setTexParameteri(glLog, textureTargetGL);
+	setTextureStorage(glLog, m_textureType, textureInternalFormatGL, imageSize, *texture1Buf);
+
+	// Perform image loads and stores in compute shader and finalize reference computation.
+
+	{
+		// Generate compute shader.
+
+		const char* const		maybeRestrict			= m_restrictImages ? "restrict" : "";
+		const string			shaderImageFormatStr	= getShaderImageFormatQualifier(m_imageFormat);
+		const TextureType		shaderImageType			= m_singleLayerBind ? textureLayerType(m_textureType) : m_textureType;
+		const string			shaderImageTypeStr		= getShaderImageType(m_imageFormat.type, shaderImageType);
+
+		const glu::ShaderProgram program(renderCtx,
+			glu::ProgramSources() << glu::ComputeSource("#version 310 es\n"
+														+ textureTypeExtensionShaderRequires(shaderImageType) +
+														"\n"
+														"precision highp " + shaderImageTypeStr + ";\n"
+														"\n"
+														"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+														"layout (" + shaderImageFormatStr + ", binding=0) " + maybeRestrict + " readonly uniform " + shaderImageTypeStr + " u_image0;\n"
+														"layout (" + shaderImageFormatStr + ", binding=1) " + maybeRestrict + " writeonly uniform " + shaderImageTypeStr + " u_image1;\n"
+														"\n"
+														"void main (void)\n"
+														"{\n"
+														+ (shaderImageType == TEXTURETYPE_BUFFER ?
+															"	int pos = int(gl_GlobalInvocationID.x);\n"
+															"	imageStore(u_image1, pos, imageLoad(u_image0, " + toString(imageSize.x()-1) + "-pos.x));\n"
+														 : shaderImageType == TEXTURETYPE_2D ?
+															"	ivec2 pos = ivec2(gl_GlobalInvocationID.xy);\n"
+															"	imageStore(u_image1, pos, imageLoad(u_image0, ivec2(" + toString(imageSize.x()-1) + "-pos.x, pos.y)));\n"
+														 : shaderImageType == TEXTURETYPE_3D || shaderImageType == TEXTURETYPE_CUBE || shaderImageType == TEXTURETYPE_2D_ARRAY ?
+															"	ivec3 pos = ivec3(gl_GlobalInvocationID);\n"
+															"	imageStore(u_image1, pos, imageLoad(u_image0, ivec3(" + toString(imageSize.x()-1) + "-pos.x, pos.y, pos.z)));\n"
+														 : DE_NULL) +
+														"}\n"));
+
+		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());
+
+		log << program;
+
+		if (!program.isOk())
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
+			return STOP;
+		}
+
+		// Setup and dispatch.
+
+		glLog.glUseProgram(program.getProgram());
+
+		if (m_singleLayerBind)
+		{
+			for (int layerNdx = 0; layerNdx < numSlicesOrFaces; layerNdx++)
+			{
+				if (layerNdx > 0)
+					glLog.glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+
+				glLog.glBindImageTexture(0, *texture0, 0, GL_FALSE, layerNdx, GL_READ_ONLY, imageInternalFormatGL);
+				GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+				glLog.glBindImageTexture(1, *texture1, 0, GL_FALSE, layerNdx, GL_WRITE_ONLY, imageInternalFormatGL);
+				GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+				glLog.glDispatchCompute(imageSize.x(), imageSize.y(), 1);
+				GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
+			}
+		}
+		else
+		{
+			glLog.glBindImageTexture(0, *texture0, 0, GL_TRUE, 0, GL_READ_ONLY, imageInternalFormatGL);
+			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+			glLog.glBindImageTexture(1, *texture1, 0, GL_TRUE, 0, GL_WRITE_ONLY, imageInternalFormatGL);
+			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+			glLog.glDispatchCompute(imageSize.x(), imageSize.y(), numSlicesOrFaces);
+			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
+		}
+
+		// Finalize reference.
+
+		if (m_textureFormat != m_imageFormat)
+		{
+			// Format re-interpretation case. Read data with image format and write back, with the same image format.
+			// We do this because the data may change a little during lookups (e.g. unorm8 -> float; not all unorms can be exactly represented as floats).
+
+			const int					pixelSize		= m_imageFormat.getPixelSize();
+			tcu::TextureLevel			scratch			(m_imageFormat, 1, 1);
+			const PixelBufferAccess		scratchAccess	= scratch.getAccess();
+
+			for (int z = 0; z < numSlicesOrFaces; z++)
+			{
+				const PixelBufferAccess		sliceAccess		= m_textureType == TEXTURETYPE_CUBE ? reference.getCubeFaceAccess((tcu::CubeFace)z) : reference.getSliceAccess(z);
+				const int					rowPitch		= sliceAccess.getRowPitch();
+				void *const					data			= sliceAccess.getDataPtr();
+
+				for (int y = 0; y < imageSize.y(); y++)
+				for (int x = 0; x < imageSize.x(); x++)
+				{
+					void *const pixelData = (deUint8*)data + y*rowPitch + x*pixelSize;
+
+					deMemcpy(scratchAccess.getDataPtr(), pixelData, pixelSize);
+
+					if (isFormatTypeInteger(m_imageFormat.type))
+						scratchAccess.setPixel(scratchAccess.getPixelUint(0, 0), 0, 0);
+					else
+						scratchAccess.setPixel(scratchAccess.getPixel(0, 0), 0, 0);
+
+					deMemcpy(pixelData, scratchAccess.getDataPtr(), pixelSize);
+				}
+			}
+		}
+
+		for (int z = 0; z < numSlicesOrFaces; z++)
+		for (int y = 0; y < imageSize.y(); y++)
+		for (int x = 0; x < imageSize.x()/2; x++)
+		{
+			if (isIntegerTextureFormat)
+			{
+				const UVec4 temp = reference.getPixelUint(imageSize.x()-1-x, y, z);
+				reference.setPixel(imageSize.x()-1-x, y, z, reference.getPixelUint(x, y, z));
+				reference.setPixel(x, y, z, temp);
+			}
+			else
+			{
+				const Vec4 temp = reference.getPixel(imageSize.x()-1-x, y, z);
+				reference.setPixel(imageSize.x()-1-x, y, z, reference.getPixel(x, y, z));
+				reference.setPixel(x, y, z, temp);
+			}
+		}
+	}
+
+	// Read texture 1 and compare to reference.
+
+	const bool compareOk = readTextureAndVerify(renderCtx, glLog, *texture1, *texture1Buf, m_textureType, m_textureFormat, imageSize, ImageLayerComparer(reference));
+
+	m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, compareOk ? "Pass" : "Image comparison failed");
+	return STOP;
+}
+
+enum AtomicOperationCaseType
+{
+	ATOMIC_OPERATION_CASE_TYPE_END_RESULT = 0,	//!< Atomic case checks the end result of the operations, and not the return values.
+	ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES,	//!< Atomic case checks the return values of the atomic function, and not the end result.
+
+	ATOMIC_OPERATION_CASE_TYPE_LAST
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Binary atomic operation case.
+ *
+ * Case that performs binary atomic operations (i.e. any but compSwap) and
+ * verifies according to the given AtomicOperationCaseType.
+ *
+ * For the "end result" case type, a single texture (and image) is created,
+ * upon which the atomic operations operate. A compute shader is dispatched
+ * with dimensions equal to the image size, except with a bigger X size
+ * so that every pixel is operated on by multiple invocations. The end
+ * results are verified in BinaryAtomicOperationCase::EndResultVerifier.
+ * The return values of the atomic function calls are ignored.
+ *
+ * For the "return value" case type, the case does much the same operations
+ * as in the "end result" case, but also creates an additional texture,
+ * of size equal to the dispatch size, into which the return values of the
+ * atomic functions are stored (with imageStore()). The return values are
+ * verified in BinaryAtomicOperationCase::ReturnValueVerifier.
+ * The end result values are not checked.
+ *
+ * The compute shader invocations contributing to a pixel (X, Y, Z) in the
+ * end result image are the invocations with global IDs
+ * (X, Y, Z), (X+W, Y, Z), (X+2*W, Y, Z), ..., (X+(N-1)*W, Y, W), where W
+ * is the width of the end result image and N is
+ * BinaryAtomicOperationCase::NUM_INVOCATIONS_PER_PIXEL.
+ *//*--------------------------------------------------------------------*/
+class BinaryAtomicOperationCase : public TestCase
+{
+public:
+									BinaryAtomicOperationCase		(Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, AtomicOperation operation, AtomicOperationCaseType caseType)
+		: TestCase		(context, name, description)
+		, m_format		(format)
+		, m_imageType	(imageType)
+		, m_operation	(operation)
+		, m_caseType	(caseType)
+	{
+		DE_ASSERT(m_format == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32)	||
+				  m_format == TextureFormat(TextureFormat::R, TextureFormat::SIGNED_INT32)		||
+				  (m_format == TextureFormat(TextureFormat::R, TextureFormat::FLOAT) && m_operation == ATOMIC_OPERATION_EXCHANGE));
+
+		DE_ASSERT(m_operation != ATOMIC_OPERATION_COMP_SWAP);
+	}
+
+	void							init							(void);
+	IterateResult					iterate							(void);
+
+private:
+	class EndResultVerifier;
+	class ReturnValueVerifier;
+
+	static int						getOperationInitialValue		(AtomicOperation op); //!< Appropriate value with which to initialize the texture.
+	//! Compute the argument given to the atomic function at the given invocation ID, when the entire dispatch has the given width and height.
+	static int						getAtomicFuncArgument			(AtomicOperation op, const IVec3& invocationID, const IVec2& dispatchSizeXY);
+	//! Generate the shader expression for the argument given to the atomic function. x, y and z are the identifiers for the invocation ID components.
+	static string					getAtomicFuncArgumentShaderStr	(AtomicOperation op, const string& x, const string& y, const string& z, const IVec2& dispatchSizeXY);
+
+	static const int				NUM_INVOCATIONS_PER_PIXEL = 5;
+
+	const TextureFormat				m_format;
+	const TextureType				m_imageType;
+	const AtomicOperation			m_operation;
+	const AtomicOperationCaseType	m_caseType;
+};
+
+int BinaryAtomicOperationCase::getOperationInitialValue (AtomicOperation op)
+{
+	switch (op)
+	{
+		// \note 18 is just an arbitrary small nonzero value.
+		case ATOMIC_OPERATION_ADD:			return 18;
+		case ATOMIC_OPERATION_MIN:			return (1<<15) - 1;
+		case ATOMIC_OPERATION_MAX:			return 18;
+		case ATOMIC_OPERATION_AND:			return (1<<15) - 1;
+		case ATOMIC_OPERATION_OR:			return 18;
+		case ATOMIC_OPERATION_XOR:			return 18;
+		case ATOMIC_OPERATION_EXCHANGE:		return 18;
+		default:
+			DE_ASSERT(false);
+			return -1;
+	}
+}
+
+int BinaryAtomicOperationCase::getAtomicFuncArgument (AtomicOperation op, const IVec3& invocationID, const IVec2& dispatchSizeXY)
+{
+	const int x		= invocationID.x();
+	const int y		= invocationID.y();
+	const int z		= invocationID.z();
+	const int wid	= dispatchSizeXY.x();
+	const int hei	= dispatchSizeXY.y();
+
+	switch (op)
+	{
+		// \note Fall-throughs.
+		case ATOMIC_OPERATION_ADD:
+		case ATOMIC_OPERATION_MIN:
+		case ATOMIC_OPERATION_MAX:
+		case ATOMIC_OPERATION_AND:
+		case ATOMIC_OPERATION_OR:
+		case ATOMIC_OPERATION_XOR:
+			return x*x + y*y + z*z;
+
+		case ATOMIC_OPERATION_EXCHANGE:
+			return (z*wid + x)*hei + y;
+
+		default:
+			DE_ASSERT(false);
+			return -1;
+	}
+}
+
+string BinaryAtomicOperationCase::getAtomicFuncArgumentShaderStr (AtomicOperation op, const string& x, const string& y, const string& z, const IVec2& dispatchSizeXY)
+{
+	switch (op)
+	{
+		// \note Fall-throughs.
+		case ATOMIC_OPERATION_ADD:
+		case ATOMIC_OPERATION_MIN:
+		case ATOMIC_OPERATION_MAX:
+		case ATOMIC_OPERATION_AND:
+		case ATOMIC_OPERATION_OR:
+		case ATOMIC_OPERATION_XOR:
+			return "("+ x+"*"+x +" + "+ y+"*"+y +" + "+ z+"*"+z +")";
+
+		case ATOMIC_OPERATION_EXCHANGE:
+			return "((" + z + "*" + toString(dispatchSizeXY.x()) + " + " + x + ")*" + toString(dispatchSizeXY.y()) + " + " + y + ")";
+
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+class BinaryAtomicOperationCase::EndResultVerifier : public ImageLayerVerifier
+{
+public:
+	EndResultVerifier (AtomicOperation operation, TextureType imageType) : m_operation(operation), m_imageType(imageType) {}
+
+	bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const
+	{
+		const bool		isIntegerFormat		= isFormatTypeInteger(resultSlice.getFormat().type);
+		const IVec2		dispatchSizeXY		(NUM_INVOCATIONS_PER_PIXEL*resultSlice.getWidth(), resultSlice.getHeight());
+
+		log << TestLog::Image("EndResults" + toString(sliceOrFaceNdx),
+							  "Result Values, " + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx))))
+																				   : "slice " + toString(sliceOrFaceNdx)),
+							  resultSlice);
+
+		for (int y = 0; y < resultSlice.getHeight(); y++)
+		for (int x = 0; x < resultSlice.getWidth(); x++)
+		{
+			union
+			{
+				int		i;
+				float	f;
+			} result;
+
+			if (isIntegerFormat)
+				result.i = resultSlice.getPixelInt(x, y).x();
+			else
+				result.f = resultSlice.getPixel(x, y).x();
+
+			// Compute the arguments that were given to the atomic function in the invocations that contribute to this pixel.
+
+			IVec3	invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL];
+			int		atomicArgs[NUM_INVOCATIONS_PER_PIXEL];
+
+			for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
+			{
+				const IVec3 gid(x + i*resultSlice.getWidth(), y, sliceOrFaceNdx);
+
+				invocationGlobalIDs[i]	= gid;
+				atomicArgs[i]			= getAtomicFuncArgument(m_operation, gid, dispatchSizeXY);
+			}
+
+			if (isOrderIndependentAtomicOperation(m_operation))
+			{
+				// Just accumulate the atomic args (and the initial value) according to the operation, and compare.
+
+				DE_ASSERT(isIntegerFormat);
+
+				int reference = getOperationInitialValue(m_operation);
+
+				for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
+					reference = computeBinaryAtomicOperationResult(m_operation, reference, atomicArgs[i]);
+
+				if (result.i != reference)
+				{
+					log << TestLog::Message << "// Failure: end result at pixel " << IVec2(x, y) << " of current layer is " << result.i << TestLog::EndMessage
+						<< TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage
+						<< TestLog::Message << "// Note: data expression values for the IDs are " << arrayStr(atomicArgs) << TestLog::EndMessage
+						<< TestLog::Message << "// Note: reference value is " << reference << TestLog::EndMessage;
+					return false;
+				}
+			}
+			else if (m_operation == ATOMIC_OPERATION_EXCHANGE)
+			{
+				// Check that the end result equals one of the atomic args.
+
+				bool matchFound = false;
+
+				for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL && !matchFound; i++)
+					matchFound = isIntegerFormat ? result.i == atomicArgs[i]
+												 : de::abs(result.f - (float)atomicArgs[i]) <= 0.01f;
+
+				if (!matchFound)
+				{
+					log << TestLog::Message << "// Failure: invalid value at pixel " << IVec2(x, y) << ": got " << (isIntegerFormat ? toString(result.i) : toString(result.f)) << TestLog::EndMessage
+											<< TestLog::Message << "// Note: expected one of " << arrayStr(atomicArgs) << TestLog::EndMessage;
+
+					return false;
+				}
+			}
+			else
+				DE_ASSERT(false);
+		}
+
+		return true;
+	}
+
+private:
+	const AtomicOperation	m_operation;
+	const TextureType		m_imageType;
+};
+
+class BinaryAtomicOperationCase::ReturnValueVerifier : public ImageLayerVerifier
+{
+public:
+	//! \note endResultImageLayerSize is (width, height) of the image operated on by the atomic ops, and not the size of the image where the return values are stored.
+	ReturnValueVerifier (AtomicOperation operation, TextureType imageType, const IVec2& endResultImageLayerSize) : m_operation(operation), m_imageType(imageType), m_endResultImageLayerSize(endResultImageLayerSize) {}
+
+	bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const
+	{
+		const bool		isIntegerFormat		(isFormatTypeInteger(resultSlice.getFormat().type));
+		const IVec2		dispatchSizeXY	(resultSlice.getWidth(), resultSlice.getHeight());
+
+		DE_ASSERT(resultSlice.getWidth()	== NUM_INVOCATIONS_PER_PIXEL*m_endResultImageLayerSize.x()	&&
+				  resultSlice.getHeight()	== m_endResultImageLayerSize.y()							&&
+				  resultSlice.getDepth()	== 1);
+
+		log << TestLog::Image("ReturnValues" + toString(sliceOrFaceNdx),
+							  "Per-Invocation Return Values, "
+								   + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx))))
+																	  : "slice " + toString(sliceOrFaceNdx)),
+							  resultSlice);
+
+		for (int y = 0; y < m_endResultImageLayerSize.y(); y++)
+		for (int x = 0; x < m_endResultImageLayerSize.x(); x++)
+		{
+			union IntFloatArr
+			{
+				int		i[NUM_INVOCATIONS_PER_PIXEL];
+				float	f[NUM_INVOCATIONS_PER_PIXEL];
+			};
+
+			// Get the atomic function args and return values for all the invocations that contribute to the pixel (x, y) in the current end result slice.
+
+			IntFloatArr		returnValues;
+			IntFloatArr		atomicArgs;
+			IVec3			invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL];
+			IVec2			pixelCoords[NUM_INVOCATIONS_PER_PIXEL];
+
+			for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
+			{
+				const IVec2 pixCoord	(x + i*m_endResultImageLayerSize.x(), y);
+				const IVec3 gid			(pixCoord.x(), pixCoord.y(), sliceOrFaceNdx);
+
+				invocationGlobalIDs[i]	= gid;
+				pixelCoords[i]			= pixCoord;
+
+				if (isIntegerFormat)
+				{
+					returnValues.i[i]	= resultSlice.getPixelInt(gid.x(), y).x();
+					atomicArgs.i[i]		= getAtomicFuncArgument(m_operation, gid, dispatchSizeXY);
+				}
+				else
+				{
+					returnValues.f[i]	= resultSlice.getPixel(gid.x(), y).x();
+					atomicArgs.f[i]		= (float)getAtomicFuncArgument(m_operation, gid, dispatchSizeXY);
+				}
+			}
+
+			// Verify that the return values form a valid sequence.
+
+			{
+				const bool success = isIntegerFormat ? verifyOperationAccumulationIntermediateValues(m_operation,
+																									 getOperationInitialValue(m_operation),
+																									 atomicArgs.i,
+																									 returnValues.i)
+
+													 : verifyOperationAccumulationIntermediateValues(m_operation,
+																									 (float)getOperationInitialValue(m_operation),
+																									 atomicArgs.f,
+																									 returnValues.f);
+
+				if (!success)
+				{
+					log << TestLog::Message << "// Failure: intermediate return values at pixels " << arrayStr(pixelCoords) << " of current layer are "
+											<< (isIntegerFormat ? arrayStr(returnValues.i) : arrayStr(returnValues.f)) << TestLog::EndMessage
+						<< TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage
+						<< TestLog::Message << "// Note: data expression values for the IDs are "
+											<< (isIntegerFormat ? arrayStr(atomicArgs.i) : arrayStr(atomicArgs.f))
+											<< "; return values are not a valid result for any order of operations" << TestLog::EndMessage;
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+
+private:
+	const AtomicOperation	m_operation;
+	const TextureType		m_imageType;
+	const IVec2				m_endResultImageLayerSize;
+
+	//! Check whether there exists an ordering of args such that { init*A", init*A*B, ..., init*A*B*...*LAST } is the "returnValues" sequence, where { A, B, ..., LAST } is args, and * denotes the operation.
+	//	That is, whether "returnValues" is a valid sequence of intermediate return values when "operation" has been accumulated on "args" (and "init") in some arbitrary order.
+	template <typename T>
+	static bool verifyOperationAccumulationIntermediateValues (AtomicOperation operation, T init, const T (&args)[NUM_INVOCATIONS_PER_PIXEL], const T (&returnValues)[NUM_INVOCATIONS_PER_PIXEL])
+	{
+		bool argsUsed[NUM_INVOCATIONS_PER_PIXEL] = { false };
+
+		return verifyRecursive(operation, 0, init, argsUsed, args, returnValues);
+	}
+
+	static bool compare (int a, int b)		{ return a == b; }
+	static bool compare (float a, float b)	{ return de::abs(a - b) <= 0.01f; }
+
+	//! Depth-first search for verifying the return value sequence.
+	template <typename T>
+	static bool verifyRecursive (AtomicOperation operation, int index, T valueSoFar, bool (&argsUsed)[NUM_INVOCATIONS_PER_PIXEL], const T (&args)[NUM_INVOCATIONS_PER_PIXEL], const T (&returnValues)[NUM_INVOCATIONS_PER_PIXEL])
+	{
+		if (index < NUM_INVOCATIONS_PER_PIXEL)
+		{
+			for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
+			{
+				if (!argsUsed[i] && compare(returnValues[i], valueSoFar))
+				{
+					argsUsed[i] = true;
+					if (verifyRecursive(operation, index+1, computeBinaryAtomicOperationResult(operation, valueSoFar, args[i]), argsUsed, args, returnValues))
+						return true;
+					argsUsed[i] = false;
+				}
+			}
+
+			return false;
+		}
+		else
+			return true;
+	}
+};
+
+void BinaryAtomicOperationCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
+		throw tcu::NotSupportedError("Test requires OES_shader_image_atomic extension");
+
+	checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType);
+}
+
+BinaryAtomicOperationCase::IterateResult BinaryAtomicOperationCase::iterate (void)
+{
+	const RenderContext&		renderCtx				= m_context.getRenderContext();
+	TestLog&					log						(m_testCtx.getLog());
+	glu::CallLogWrapper			glLog					(renderCtx.getFunctions(), log);
+	const deUint32				internalFormatGL		= glu::getInternalFormat(m_format);
+	const deUint32				textureTargetGL			= getGLTextureTarget(m_imageType);
+	const IVec3&				imageSize				= defaultImageSize(m_imageType);
+	const int					numSlicesOrFaces		= m_imageType == TEXTURETYPE_CUBE ? 6 : imageSize.z();
+	const bool					isUintFormat			= isFormatTypeUnsignedInteger(m_format.type);
+	const bool					isIntFormat				= isFormatTypeSignedInteger(m_format.type);
+	const glu::Buffer			endResultTextureBuf		(renderCtx);
+	const glu::Buffer			returnValueTextureBuf	(renderCtx);
+	const glu::Texture			endResultTexture		(renderCtx); //!< Texture for the final result; i.e. the texture on which the atomic operations are done. Size imageSize.
+	const glu::Texture			returnValueTexture		(renderCtx); //!< Texture into which the return values are stored if m_caseType == CASETYPE_RETURN_VALUES.
+																	 //	  Size imageSize*IVec3(N, 1, 1) or, for cube maps, imageSize*IVec3(N, N, 1) where N is NUM_INVOCATIONS_PER_PIXEL.
+
+	glLog.enableLogging(true);
+
+	// Setup textures.
+
+	log << TestLog::Message << "// Created a texture (name " << *endResultTexture << ") to act as the target of atomic operations" << TestLog::EndMessage;
+	if (m_imageType == TEXTURETYPE_BUFFER)
+		log << TestLog::Message << "// Created a buffer for the texture (name " << *endResultTextureBuf << ")" << TestLog::EndMessage;
+
+	if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES)
+	{
+		log << TestLog::Message << "// Created a texture (name " << *returnValueTexture << ") to which the intermediate return values of the atomic operation are stored" << TestLog::EndMessage;
+		if (m_imageType == TEXTURETYPE_BUFFER)
+			log << TestLog::Message << "// Created a buffer for the texture (name " << *returnValueTextureBuf << ")" << TestLog::EndMessage;
+	}
+
+	// Fill endResultTexture with initial pattern.
+
+	{
+		const LayeredImage imageData(m_imageType, m_format, imageSize.x(), imageSize.y(), imageSize.z());
+
+		{
+			const IVec4 initial(getOperationInitialValue(m_operation));
+
+			for (int z = 0; z < numSlicesOrFaces; z++)
+			for (int y = 0; y < imageSize.y(); y++)
+			for (int x = 0; x < imageSize.x(); x++)
+				imageData.setPixel(x, y, z, initial);
+		}
+
+		// Upload initial pattern to endResultTexture and bind to image.
+
+		glLog.glActiveTexture(GL_TEXTURE0);
+		glLog.glBindTexture(textureTargetGL, *endResultTexture);
+		setTexParameteri(glLog, textureTargetGL);
+
+		log << TestLog::Message << "// Filling end-result texture with initial pattern (initial value " << getOperationInitialValue(m_operation) << ")" << TestLog::EndMessage;
+
+		uploadTexture(glLog, imageData, *endResultTextureBuf);
+	}
+
+	glLog.glBindImageTexture(0, *endResultTexture, 0, GL_TRUE, 0, GL_READ_WRITE, internalFormatGL);
+	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+	if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES)
+	{
+		// Set storage for returnValueTexture and bind to image.
+
+		glLog.glActiveTexture(GL_TEXTURE1);
+		glLog.glBindTexture(textureTargetGL, *returnValueTexture);
+		setTexParameteri(glLog, textureTargetGL);
+
+		log << TestLog::Message << "// Setting storage of return-value texture" << TestLog::EndMessage;
+		setTextureStorage(glLog, m_imageType, internalFormatGL, imageSize * (m_imageType == TEXTURETYPE_CUBE ? IVec3(NUM_INVOCATIONS_PER_PIXEL, NUM_INVOCATIONS_PER_PIXEL,	1)
+																											 : IVec3(NUM_INVOCATIONS_PER_PIXEL, 1,							1)),
+						  *returnValueTextureBuf);
+
+		glLog.glBindImageTexture(1, *returnValueTexture, 0, GL_TRUE, 0, GL_WRITE_ONLY, internalFormatGL);
+		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+	}
+
+	// Perform image stores in compute shader and finalize reference computation.
+
+	{
+		// Generate compute shader.
+
+		const string colorVecTypeName		= string(isUintFormat ? "u" : isIntFormat ? "i" : "") + "vec4";
+		const string atomicCoord			= m_imageType == TEXTURETYPE_BUFFER		? "gx % " + toString(imageSize.x())
+											: m_imageType == TEXTURETYPE_2D			? "ivec2(gx % " + toString(imageSize.x()) + ", gy)"
+											: "ivec3(gx % " + toString(imageSize.x()) + ", gy, gz)";
+		const string invocationCoord		= m_imageType == TEXTURETYPE_BUFFER		? "gx"
+											: m_imageType == TEXTURETYPE_2D			? "ivec2(gx, gy)"
+											: "ivec3(gx, gy, gz)";
+		const string atomicArgExpr			= (isUintFormat		? "uint"
+											 : isIntFormat		? ""
+											 : "float")
+												+ getAtomicFuncArgumentShaderStr(m_operation, "gx", "gy", "gz", IVec2(NUM_INVOCATIONS_PER_PIXEL*imageSize.x(), imageSize.y()));
+		const string atomicInvocation		= string() + getAtomicOperationShaderFuncName(m_operation) + "(u_results, " + atomicCoord + ", " + atomicArgExpr + ")";
+		const string shaderImageFormatStr	= getShaderImageFormatQualifier(m_format);
+		const string shaderImageTypeStr		= getShaderImageType(m_format.type, m_imageType);
+
+		const glu::ShaderProgram program(renderCtx,
+			glu::ProgramSources() << glu::ComputeSource("#version 310 es\n"
+														"#extension GL_OES_shader_image_atomic : require\n"
+														+ textureTypeExtensionShaderRequires(m_imageType) +
+														"\n"
+														"precision highp " + shaderImageTypeStr + ";\n"
+														"\n"
+														"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+														"layout (" + shaderImageFormatStr + ", binding=0) coherent uniform " + shaderImageTypeStr + " u_results;\n"
+														+ (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ?
+															  "layout (" + shaderImageFormatStr + ", binding=1) writeonly uniform " + shaderImageTypeStr + " u_returnValues;\n"
+															: "") +
+														"\n"
+														"void main (void)\n"
+														"{\n"
+														"	int gx = int(gl_GlobalInvocationID.x);\n"
+														"	int gy = int(gl_GlobalInvocationID.y);\n"
+														"	int gz = int(gl_GlobalInvocationID.z);\n"
+														+ (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ?
+															"	imageStore(u_returnValues, " + invocationCoord + ", " + colorVecTypeName + "(" + atomicInvocation + "));\n"
+														 : m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ?
+															"	" + atomicInvocation + ";\n"
+														 : DE_NULL) +
+														"}\n"));
+
+		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());
+
+		log << program;
+
+		if (!program.isOk())
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
+			return STOP;
+		}
+
+		// Setup and dispatch.
+
+		glLog.glUseProgram(program.getProgram());
+
+		glLog.glDispatchCompute(NUM_INVOCATIONS_PER_PIXEL*imageSize.x(), imageSize.y(), numSlicesOrFaces);
+		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
+	}
+
+	// Read texture and check.
+
+	{
+		const deUint32								textureToCheckGL	= m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? *endResultTexture
+																		: m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES	? *returnValueTexture
+																		: (deUint32)-1;
+		const deUint32								textureToCheckBufGL	= m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? *endResultTextureBuf
+																		: m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES	? *returnValueTextureBuf
+																		: (deUint32)-1;
+
+		const IVec3									textureToCheckSize	= imageSize * IVec3(m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? 1 : NUM_INVOCATIONS_PER_PIXEL, 1, 1);
+		const UniquePtr<const ImageLayerVerifier>	verifier			(m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? new EndResultVerifier(m_operation, m_imageType)
+																	   : m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES		? new ReturnValueVerifier(m_operation, m_imageType, imageSize.swizzle(0, 1))
+																	   : (ImageLayerVerifier*)DE_NULL);
+
+		if (readTextureAndVerify(renderCtx, glLog, textureToCheckGL, textureToCheckBufGL, m_imageType, m_format, textureToCheckSize, *verifier))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+
+		return STOP;
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Atomic compSwap operation case.
+ *
+ * Similar in principle to BinaryAtomicOperationCase, but separated for
+ * convenience, since the atomic function is somewhat different. Like
+ * BinaryAtomicOperationCase, this has separate cases for checking end
+ * result and return values.
+ *//*--------------------------------------------------------------------*/
+class AtomicCompSwapCase : public TestCase
+{
+public:
+									AtomicCompSwapCase		(Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, AtomicOperationCaseType caseType)
+		: TestCase		(context, name, description)
+		, m_format		(format)
+		, m_imageType	(imageType)
+		, m_caseType	(caseType)
+	{
+		DE_ASSERT(m_format == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32)	||
+				  m_format == TextureFormat(TextureFormat::R, TextureFormat::SIGNED_INT32));
+	}
+
+	void							init					(void);
+	IterateResult					iterate					(void);
+
+private:
+	class EndResultVerifier;
+	class ReturnValueVerifier;
+
+	static int						getCompareArg			(const IVec3& invocationID, int imageWidth);
+	static int						getAssignArg			(const IVec3& invocationID, int imageWidth);
+	static string					getCompareArgShaderStr	(const string& x, const string& y, const string& z, int imageWidth);
+	static string					getAssignArgShaderStr	(const string& x, const string& y, const string& z, int imageWidth);
+
+	static const int				NUM_INVOCATIONS_PER_PIXEL = 5;
+
+	const TextureFormat				m_format;
+	const TextureType				m_imageType;
+	const AtomicOperationCaseType	m_caseType;
+};
+
+int AtomicCompSwapCase::getCompareArg (const IVec3& invocationID, int imageWidth)
+{
+	const int x							= invocationID.x();
+	const int y							= invocationID.y();
+	const int z							= invocationID.z();
+	const int wrapX						= x % imageWidth;
+	const int curPixelInvocationNdx		= x / imageWidth;
+
+	return wrapX*wrapX + y*y + z*z + curPixelInvocationNdx*42;
+}
+
+int AtomicCompSwapCase::getAssignArg (const IVec3& invocationID, int imageWidth)
+{
+	return getCompareArg(IVec3(invocationID.x() + imageWidth, invocationID.y(), invocationID.z()), imageWidth);
+}
+
+string AtomicCompSwapCase::getCompareArgShaderStr (const string& x, const string& y, const string& z, int imageWidth)
+{
+	const string wrapX					= "(" + x + "%" + toString(imageWidth) + ")";
+	const string curPixelInvocationNdx	= "(" + x + "/" + toString(imageWidth) + ")";
+
+	return "(" +wrapX+"*"+wrapX+ " + " +y+"*"+y+ " + " +z+"*"+z+ " + " + curPixelInvocationNdx + "*42)";
+}
+
+string AtomicCompSwapCase::getAssignArgShaderStr (const string& x, const string& y, const string& z, int imageWidth)
+{
+	const string wrapX					= "(" + x + "%" + toString(imageWidth) + ")";
+	const string curPixelInvocationNdx	= "(" + x + "/" + toString(imageWidth) + " + 1)";
+
+	return "(" +wrapX+"*"+wrapX+ " + " +y+"*"+y+ " + " +z+"*"+z+ " + " + curPixelInvocationNdx + "*42)";
+}
+
+void AtomicCompSwapCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
+		throw tcu::NotSupportedError("Test requires OES_shader_image_atomic extension");
+
+	checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType);
+}
+
+class AtomicCompSwapCase::EndResultVerifier : public ImageLayerVerifier
+{
+public:
+	EndResultVerifier (TextureType imageType, int imageWidth) : m_imageType(imageType), m_imageWidth(imageWidth) {}
+
+	bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const
+	{
+		DE_ASSERT(isFormatTypeInteger(resultSlice.getFormat().type));
+		DE_ASSERT(resultSlice.getWidth() == m_imageWidth);
+
+		log << TestLog::Image("EndResults" + toString(sliceOrFaceNdx),
+							  "Result Values, " + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx))))
+																				   : "slice " + toString(sliceOrFaceNdx)),
+							  resultSlice);
+
+		for (int y = 0; y < resultSlice.getHeight(); y++)
+		for (int x = 0; x < resultSlice.getWidth(); x++)
+		{
+			// Compute the value-to-assign arguments that were given to the atomic function in the invocations that contribute to this pixel.
+			// One of those should be the result.
+
+			const int	result = resultSlice.getPixelInt(x, y).x();
+			IVec3		invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL];
+			int			assignArgs[NUM_INVOCATIONS_PER_PIXEL];
+
+			for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
+			{
+				const IVec3 gid(x + i*resultSlice.getWidth(), y, sliceOrFaceNdx);
+
+				invocationGlobalIDs[i]	= gid;
+				assignArgs[i]			= getAssignArg(gid, m_imageWidth);
+			}
+
+			{
+				bool matchFound = false;
+				for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL && !matchFound; i++)
+					matchFound = result == assignArgs[i];
+
+				if (!matchFound)
+				{
+					log << TestLog::Message << "// Failure: invalid value at pixel " << IVec2(x, y) << ": got " << result << TestLog::EndMessage
+						<< TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage
+						<< TestLog::Message << "// Note: expected one of " << arrayStr(assignArgs)
+											<< " (those are the values given as the 'data' argument in the invocations that contribute to this pixel)"
+											<< TestLog::EndMessage;
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+
+private:
+	const TextureType	m_imageType;
+	const int			m_imageWidth;
+};
+
+class AtomicCompSwapCase::ReturnValueVerifier : public ImageLayerVerifier
+{
+public:
+	//! \note endResultImageLayerSize is (width, height) of the image operated on by the atomic ops, and not the size of the image where the return values are stored.
+	ReturnValueVerifier (TextureType imageType, int endResultImageWidth) : m_imageType(imageType), m_endResultImageWidth(endResultImageWidth) {}
+
+	bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const
+	{
+		DE_ASSERT(isFormatTypeInteger(resultSlice.getFormat().type));
+		DE_ASSERT(resultSlice.getWidth() == NUM_INVOCATIONS_PER_PIXEL*m_endResultImageWidth);
+
+		log << TestLog::Image("ReturnValues" + toString(sliceOrFaceNdx),
+							  "Per-Invocation Return Values, "
+								   + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx))))
+																	  : "slice " + toString(sliceOrFaceNdx)),
+							  resultSlice);
+
+		for (int y = 0; y < resultSlice.getHeight(); y++)
+		for (int x = 0; x < m_endResultImageWidth; x++)
+		{
+			// Get the atomic function args and return values for all the invocations that contribute to the pixel (x, y) in the current end result slice.
+
+			int		returnValues[NUM_INVOCATIONS_PER_PIXEL];
+			int		compareArgs[NUM_INVOCATIONS_PER_PIXEL];
+			IVec3	invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL];
+			IVec2	pixelCoords[NUM_INVOCATIONS_PER_PIXEL];
+
+			for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
+			{
+				const IVec2 pixCoord	(x + i*m_endResultImageWidth, y);
+				const IVec3 gid			(pixCoord.x(), pixCoord.y(), sliceOrFaceNdx);
+
+				pixelCoords[i]			= pixCoord;
+				invocationGlobalIDs[i]	= gid;
+				returnValues[i]			= resultSlice.getPixelInt(gid.x(), y).x();
+				compareArgs[i]			= getCompareArg(gid, m_endResultImageWidth);
+			}
+
+			// Verify that the return values form a valid sequence.
+			// Due to the way the compare and assign arguments to the atomic calls are organized
+			// among the different invocations contributing to the same pixel -- i.e. one invocation
+			// compares to A and assigns B, another compares to B and assigns C, and so on, where
+			// A<B<C etc -- the return value sequence must be (assuming 4 invocations operating
+			// on one result image pixel, for example) A A A A, A B B B, A B C C or A B C D
+			// depending on the execution order.
+
+			{
+				bool success = true;
+
+				{
+					bool flatPartStarted = false;
+
+					for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL && success; i++)
+					{
+						if (returnValues[i] == compareArgs[i])
+							success = !flatPartStarted;
+						else
+						{
+							success = i > 0 && returnValues[i] == returnValues[i-1];
+							flatPartStarted = true;
+						}
+					}
+				}
+
+				if (!success)
+				{
+					log << TestLog::Message << "// Failure: intermediate return values at pixels " << arrayStr(pixelCoords) << " of current layer are "
+											<< arrayStr(returnValues) << TestLog::EndMessage
+						<< TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage
+						<< TestLog::Message << "// Note: 'compare' argument values for the IDs are " << arrayStr(compareArgs) << TestLog::EndMessage;
+
+					{
+						std::ostringstream validReturnValueSequences;
+						for (int flatPartStartNdx = 0; flatPartStartNdx < NUM_INVOCATIONS_PER_PIXEL; flatPartStartNdx++)
+						{
+							int curSeq[NUM_INVOCATIONS_PER_PIXEL];
+							for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
+								curSeq[i] = compareArgs[de::min(i, flatPartStartNdx)];
+							validReturnValueSequences << "// " << arrayStr(curSeq) << "\n";
+						}
+
+						log << TestLog::Message << "// Note: expected one of the following return value sequences:\n" << validReturnValueSequences.str() << TestLog::EndMessage;
+					}
+
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+
+private:
+	const TextureType	m_imageType;
+	const int			m_endResultImageWidth;
+};
+
+AtomicCompSwapCase::IterateResult AtomicCompSwapCase::iterate (void)
+{
+	const RenderContext&		renderCtx				= m_context.getRenderContext();
+	TestLog&					log						(m_testCtx.getLog());
+	glu::CallLogWrapper			glLog					(renderCtx.getFunctions(), log);
+	const deUint32				internalFormatGL		= glu::getInternalFormat(m_format);
+	const deUint32				textureTargetGL			= getGLTextureTarget(m_imageType);
+	const IVec3&				imageSize				= defaultImageSize(m_imageType);
+	const int					numSlicesOrFaces		= m_imageType == TEXTURETYPE_CUBE ? 6 : imageSize.z();
+	const bool					isUintFormat			= isFormatTypeUnsignedInteger(m_format.type);
+	const bool					isIntFormat				= isFormatTypeSignedInteger(m_format.type);
+	const glu::Buffer			endResultTextureBuf		(renderCtx);
+	const glu::Buffer			returnValueTextureBuf	(renderCtx);
+	const glu::Texture			endResultTexture		(renderCtx); //!< Texture for the final result; i.e. the texture on which the atomic operations are done. Size imageSize.
+	const glu::Texture			returnValueTexture		(renderCtx); //!< Texture into which the return values are stored if m_caseType == CASETYPE_RETURN_VALUES.
+																	 //	  Size imageSize*IVec3(N, 1, 1) or, for cube maps, imageSize*IVec3(N, N, 1) where N is NUM_INVOCATIONS_PER_PIXEL.
+
+	DE_ASSERT(isUintFormat || isIntFormat);
+
+	glLog.enableLogging(true);
+
+	// Setup textures.
+
+	log << TestLog::Message << "// Created a texture (name " << *endResultTexture << ") to act as the target of atomic operations" << TestLog::EndMessage;
+	if (m_imageType == TEXTURETYPE_BUFFER)
+		log << TestLog::Message << "// Created a buffer for the texture (name " << *endResultTextureBuf << ")" << TestLog::EndMessage;
+
+	if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES)
+	{
+		log << TestLog::Message << "// Created a texture (name " << *returnValueTexture << ") to which the intermediate return values of the atomic operation are stored" << TestLog::EndMessage;
+		if (m_imageType == TEXTURETYPE_BUFFER)
+			log << TestLog::Message << "// Created a buffer for the texture (name " << *returnValueTextureBuf << ")" << TestLog::EndMessage;
+	}
+
+	// Fill endResultTexture with initial pattern.
+
+	{
+		const LayeredImage imageData(m_imageType, m_format, imageSize.x(), imageSize.y(), imageSize.z());
+
+		{
+			for (int z = 0; z < numSlicesOrFaces; z++)
+			for (int y = 0; y < imageSize.y(); y++)
+			for (int x = 0; x < imageSize.x(); x++)
+				imageData.setPixel(x, y, z, IVec4(getCompareArg(IVec3(x, y, z), imageSize.x())));
+		}
+
+		// Upload initial pattern to endResultTexture and bind to image.
+
+		glLog.glActiveTexture(GL_TEXTURE0);
+		glLog.glBindTexture(textureTargetGL, *endResultTexture);
+		setTexParameteri(glLog, textureTargetGL);
+
+		log << TestLog::Message << "// Filling end-result texture with initial pattern" << TestLog::EndMessage;
+
+		uploadTexture(glLog, imageData, *endResultTextureBuf);
+	}
+
+	glLog.glBindImageTexture(0, *endResultTexture, 0, GL_TRUE, 0, GL_READ_WRITE, internalFormatGL);
+	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+	if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES)
+	{
+		// Set storage for returnValueTexture and bind to image.
+
+		glLog.glActiveTexture(GL_TEXTURE1);
+		glLog.glBindTexture(textureTargetGL, *returnValueTexture);
+		setTexParameteri(glLog, textureTargetGL);
+
+		log << TestLog::Message << "// Setting storage of return-value texture" << TestLog::EndMessage;
+		setTextureStorage(glLog, m_imageType, internalFormatGL, imageSize * (m_imageType == TEXTURETYPE_CUBE ? IVec3(NUM_INVOCATIONS_PER_PIXEL, NUM_INVOCATIONS_PER_PIXEL,	1)
+																											 : IVec3(NUM_INVOCATIONS_PER_PIXEL, 1,							1)),
+						  *returnValueTextureBuf);
+
+		glLog.glBindImageTexture(1, *returnValueTexture, 0, GL_TRUE, 0, GL_WRITE_ONLY, internalFormatGL);
+		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+	}
+
+	// Perform atomics in compute shader.
+
+	{
+		// Generate compute shader.
+
+		const string colorScalarTypeName	= isUintFormat ? "uint" : isIntFormat ? "int" : DE_NULL;
+		const string colorVecTypeName		= string(isUintFormat ? "u" : isIntFormat ? "i" : DE_NULL) + "vec4";
+		const string atomicCoord			= m_imageType == TEXTURETYPE_BUFFER		? "gx % " + toString(imageSize.x())
+											: m_imageType == TEXTURETYPE_2D			? "ivec2(gx % " + toString(imageSize.x()) + ", gy)"
+											: "ivec3(gx % " + toString(imageSize.x()) + ", gy, gz)";
+		const string invocationCoord		= m_imageType == TEXTURETYPE_BUFFER		? "gx"
+											: m_imageType == TEXTURETYPE_2D			? "ivec2(gx, gy)"
+											: "ivec3(gx, gy, gz)";
+		const string shaderImageFormatStr	= getShaderImageFormatQualifier(m_format);
+		const string shaderImageTypeStr		= getShaderImageType(m_format.type, m_imageType);
+
+		const glu::ShaderProgram program(renderCtx,
+			glu::ProgramSources() << glu::ComputeSource("#version 310 es\n"
+														"#extension GL_OES_shader_image_atomic : require\n"
+														+ textureTypeExtensionShaderRequires(m_imageType) +
+														"\n"
+														"precision highp " + shaderImageTypeStr + ";\n"
+														"\n"
+														"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+														"layout (" + shaderImageFormatStr + ", binding=0) coherent uniform " + shaderImageTypeStr + " u_results;\n"
+														+ (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ?
+															  "layout (" + shaderImageFormatStr + ", binding=1) writeonly uniform " + shaderImageTypeStr + " u_returnValues;\n"
+															: "") +
+														"\n"
+														"void main (void)\n"
+														"{\n"
+														"	int gx = int(gl_GlobalInvocationID.x);\n"
+														"	int gy = int(gl_GlobalInvocationID.y);\n"
+														"	int gz = int(gl_GlobalInvocationID.z);\n"
+														"	" + colorScalarTypeName + " compare = " + colorScalarTypeName + getCompareArgShaderStr("gx", "gy", "gz", imageSize.x()) + ";\n"
+														"	" + colorScalarTypeName + " data    = " + colorScalarTypeName + getAssignArgShaderStr("gx", "gy", "gz", imageSize.x()) + ";\n"
+														"	" + colorScalarTypeName + " status  = " + colorScalarTypeName + "(-1);\n"
+														"	status = imageAtomicCompSwap(u_results, " + atomicCoord + ", compare, data);\n"
+														+ (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ?
+															"	imageStore(u_returnValues, " + invocationCoord + ", " + colorVecTypeName + "(status));\n" :
+															"") +
+														"}\n"));
+
+		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());
+
+		log << program;
+
+		if (!program.isOk())
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
+			return STOP;
+		}
+
+		// Setup and dispatch.
+
+		glLog.glUseProgram(program.getProgram());
+
+		glLog.glDispatchCompute(NUM_INVOCATIONS_PER_PIXEL*imageSize.x(), imageSize.y(), numSlicesOrFaces);
+		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
+	}
+
+	// Create reference, read texture and compare.
+
+	{
+		const deUint32								textureToCheckGL	= m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? *endResultTexture
+																		: m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES	? *returnValueTexture
+																		: (deUint32)-1;
+
+		const deUint32								textureToCheckBufGL	= m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? *endResultTextureBuf
+																		: m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES	? *returnValueTextureBuf
+																		: (deUint32)-1;
+
+		// Actual size of the texture being checked.
+		const IVec3									textureToCheckSize	= imageSize * (m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT	? IVec3(1,							1,							1)
+																					 : m_imageType == TEXTURETYPE_CUBE						? IVec3(NUM_INVOCATIONS_PER_PIXEL,	NUM_INVOCATIONS_PER_PIXEL,	1)
+																					 :														  IVec3(NUM_INVOCATIONS_PER_PIXEL,	1,							1));
+
+		// The relevant region of the texture being checked (potentially
+		// different from actual texture size for cube maps, because cube maps
+		// may have unused pixels due to square size restriction).
+		const IVec3									relevantRegion		= imageSize * (m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT	? IVec3(1,							1,							1)
+																					 :														  IVec3(NUM_INVOCATIONS_PER_PIXEL,	1,							1));
+
+		const UniquePtr<const ImageLayerVerifier>	verifier			(m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? new EndResultVerifier(m_imageType, imageSize.x())
+																	   : m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES		? new ReturnValueVerifier(m_imageType, imageSize.x())
+																	   : (ImageLayerVerifier*)DE_NULL);
+
+		if (readTextureAndVerify(renderCtx, glLog, textureToCheckGL, textureToCheckBufGL, m_imageType, m_format, relevantRegion, *verifier))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+
+		return STOP;
+	}
+}
+
+//! Case testing the "coherent" or "volatile" qualifier, along with memoryBarrier() and barrier().
+class CoherenceCase : public TestCase
+{
+public:
+	enum Qualifier
+	{
+		QUALIFIER_COHERENT = 0,
+		QUALIFIER_VOLATILE,
+
+		QUALIFIER_LAST
+	};
+
+	CoherenceCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, Qualifier qualifier)
+		: TestCase		(context, name, description)
+		, m_format		(format)
+		, m_imageType	(imageType)
+		, m_qualifier	(qualifier)
+	{
+		DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_Y) == DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_X) &&
+						 DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_Z) == DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_X));
+
+		DE_ASSERT(qualifier == QUALIFIER_COHERENT || qualifier == QUALIFIER_VOLATILE);
+
+		DE_ASSERT(m_format == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32)	||
+				  m_format == TextureFormat(TextureFormat::R, TextureFormat::SIGNED_INT32)		||
+				  m_format == TextureFormat(TextureFormat::R, TextureFormat::FLOAT));
+	}
+
+	void			init		(void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType); }
+	IterateResult	iterate		(void);
+
+private:
+	static const int			SHADER_READ_OFFSETS_X[4];
+	static const int			SHADER_READ_OFFSETS_Y[4];
+	static const int			SHADER_READ_OFFSETS_Z[4];
+	static const char* const	SHADER_READ_OFFSETS_X_STR;
+	static const char* const	SHADER_READ_OFFSETS_Y_STR;
+	static const char* const	SHADER_READ_OFFSETS_Z_STR;
+
+	const TextureFormat		m_format;
+	const TextureType		m_imageType;
+	const Qualifier			m_qualifier;
+};
+
+const int			CoherenceCase::SHADER_READ_OFFSETS_X[4]		=		{ 1, 4, 7, 10 };
+const int			CoherenceCase::SHADER_READ_OFFSETS_Y[4]		=		{ 2, 5, 8, 11 };
+const int			CoherenceCase::SHADER_READ_OFFSETS_Z[4]		=		{ 3, 6, 9, 12 };
+const char* const	CoherenceCase::SHADER_READ_OFFSETS_X_STR	= "int[]( 1, 4, 7, 10 )";
+const char* const	CoherenceCase::SHADER_READ_OFFSETS_Y_STR	= "int[]( 2, 5, 8, 11 )";
+const char* const	CoherenceCase::SHADER_READ_OFFSETS_Z_STR	= "int[]( 3, 6, 9, 12 )";
+
+CoherenceCase::IterateResult CoherenceCase::iterate (void)
+{
+	const RenderContext&		renderCtx					= m_context.getRenderContext();
+	TestLog&					log							(m_testCtx.getLog());
+	glu::CallLogWrapper			glLog						(renderCtx.getFunctions(), log);
+	const deUint32				internalFormatGL			= glu::getInternalFormat(m_format);
+	const deUint32				textureTargetGL				= getGLTextureTarget(m_imageType);
+	const IVec3&				imageSize					= defaultImageSize(m_imageType);
+	const int					numSlicesOrFaces			= m_imageType == TEXTURETYPE_CUBE ? 6 : imageSize.z();
+	const bool					isUintFormat				= isFormatTypeUnsignedInteger(m_format.type);
+	const bool					isIntFormat					= isFormatTypeSignedInteger(m_format.type);
+	const char* const			qualifierName				= m_qualifier == QUALIFIER_COHERENT ? "coherent"
+															: m_qualifier == QUALIFIER_VOLATILE ? "volatile"
+															: DE_NULL;
+	const glu::Buffer			textureBuf					(renderCtx);
+	const glu::Texture			texture						(renderCtx);
+	const IVec3					numGroups					= IVec3(16, de::min(16, imageSize.y()), de::min(2, numSlicesOrFaces));
+	const IVec3					workItemSize				= IVec3(imageSize.x(), imageSize.y(), numSlicesOrFaces);
+	const IVec3					localSize					= workItemSize / numGroups;
+	const IVec3					minReqMaxLocalSize			= IVec3(128, 128, 64);
+	const int					minReqMaxLocalInvocations	= 128;
+
+	DE_ASSERT(workItemSize == localSize*numGroups);
+	DE_ASSERT(tcu::boolAll(tcu::lessThanEqual(localSize, minReqMaxLocalSize)));
+	DE_ASSERT(localSize.x()*localSize.y()*localSize.z() <= minReqMaxLocalInvocations);
+	DE_UNREF(minReqMaxLocalSize);
+	DE_UNREF(minReqMaxLocalInvocations);
+
+	glLog.enableLogging(true);
+
+	// Setup texture.
+
+	log << TestLog::Message << "// Created a texture (name " << *texture << ")" << TestLog::EndMessage;
+	if (m_imageType == TEXTURETYPE_BUFFER)
+		log << TestLog::Message << "// Created a buffer for the texture (name " << *textureBuf << ")" << TestLog::EndMessage;
+
+	glLog.glActiveTexture(GL_TEXTURE0);
+	glLog.glBindTexture(textureTargetGL, *texture);
+	setTexParameteri(glLog, textureTargetGL);
+	setTextureStorage(glLog, m_imageType, internalFormatGL, imageSize, *textureBuf);
+	glLog.glBindImageTexture(0, *texture, 0, GL_TRUE, 0, GL_READ_WRITE, internalFormatGL);
+	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+	// Perform computations in compute shader.
+
+	{
+		// Generate compute shader.
+
+		const string		colorVecTypeName		= string(isUintFormat ? "u" : isIntFormat ? "i" : "") + "vec4";
+		const char* const	colorScalarTypeName		= isUintFormat ? "uint" : isIntFormat ? "int" : "float";
+		const string		invocationCoord			= m_imageType == TEXTURETYPE_BUFFER		? "gx"
+													: m_imageType == TEXTURETYPE_2D			? "ivec2(gx, gy)"
+													: "ivec3(gx, gy, gz)";
+		const string		shaderImageFormatStr	= getShaderImageFormatQualifier(m_format);
+		const string		shaderImageTypeStr		= getShaderImageType(m_format.type, m_imageType);
+		const string		localSizeX				= de::toString(localSize.x());
+		const string		localSizeY				= de::toString(localSize.y());
+		const string		localSizeZ				= de::toString(localSize.z());
+
+		const glu::ShaderProgram program(renderCtx,
+			glu::ProgramSources() << glu::ComputeSource("#version 310 es\n"
+														+ textureTypeExtensionShaderRequires(m_imageType) +
+														"\n"
+														"precision highp " + shaderImageTypeStr + ";\n"
+														"\n"
+														"layout (local_size_x = " + localSizeX
+															+ ", local_size_y = " + localSizeY
+															+ ", local_size_z = " + localSizeZ
+															+ ") in;\n"
+														"layout (" + shaderImageFormatStr + ", binding=0) " + qualifierName + " uniform " + shaderImageTypeStr + " u_image;\n"
+														"void main (void)\n"
+														"{\n"
+														"	int gx = int(gl_GlobalInvocationID.x);\n"
+														"	int gy = int(gl_GlobalInvocationID.y);\n"
+														"	int gz = int(gl_GlobalInvocationID.z);\n"
+														"	imageStore(u_image, " + invocationCoord + ", " + colorVecTypeName + "(gx^gy^gz));\n"
+														"\n"
+														"	memoryBarrier();\n"
+														"	barrier();\n"
+														"\n"
+														"	" + colorScalarTypeName + " sum = " + colorScalarTypeName + "(0);\n"
+														"	int groupBaseX = gx/" + localSizeX + "*" + localSizeX + ";\n"
+														"	int groupBaseY = gy/" + localSizeY + "*" + localSizeY + ";\n"
+														"	int groupBaseZ = gz/" + localSizeZ + "*" + localSizeZ + ";\n"
+														"	int xOffsets[] = " + SHADER_READ_OFFSETS_X_STR + ";\n"
+														"	int yOffsets[] = " + SHADER_READ_OFFSETS_Y_STR + ";\n"
+														"	int zOffsets[] = " + SHADER_READ_OFFSETS_Z_STR + ";\n"
+														"	for (int i = 0; i < " + toString(DE_LENGTH_OF_ARRAY(SHADER_READ_OFFSETS_X)) + "; i++)\n"
+														"	{\n"
+														"		int readX = groupBaseX + (gx + xOffsets[i]) % " + localSizeX + ";\n"
+														"		int readY = groupBaseY + (gy + yOffsets[i]) % " + localSizeY + ";\n"
+														"		int readZ = groupBaseZ + (gz + zOffsets[i]) % " + localSizeZ + ";\n"
+														"		sum += imageLoad(u_image, " + (m_imageType == TEXTURETYPE_BUFFER	? "readX"
+																							 : m_imageType == TEXTURETYPE_2D		? "ivec2(readX, readY)"
+																							 : "ivec3(readX, readY, readZ)") + ").x;\n"
+														"	}\n"
+														"\n"
+														"	memoryBarrier();\n"
+														"	barrier();\n"
+														"\n"
+														"	imageStore(u_image, " + invocationCoord + ", " + colorVecTypeName + "(sum));\n"
+														"}\n"));
+
+		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());
+
+		log << program;
+
+		if (!program.isOk())
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
+			return STOP;
+		}
+
+		// Setup and dispatch.
+
+		glLog.glUseProgram(program.getProgram());
+
+		glLog.glDispatchCompute(numGroups.x(), numGroups.y(), numGroups.z());
+		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
+	}
+
+	// Create reference, read texture and compare.
+
+	{
+		LayeredImage reference(m_imageType, m_format, imageSize.x(), imageSize.y(), imageSize.z());
+
+		{
+			LayeredImage base(m_imageType, m_format, imageSize.x(), imageSize.y(), imageSize.z());
+			for (int z = 0; z < numSlicesOrFaces; z++)
+			for (int y = 0; y < imageSize.y(); y++)
+			for (int x = 0; x < imageSize.x(); x++)
+				base.setPixel(x, y, z, IVec4(x^y^z));
+
+			for (int z = 0; z < numSlicesOrFaces; z++)
+			for (int y = 0; y < imageSize.y(); y++)
+			for (int x = 0; x < imageSize.x(); x++)
+			{
+				const int	groupBaseX	= x / localSize.x() * localSize.x();
+				const int	groupBaseY	= y / localSize.y() * localSize.y();
+				const int	groupBaseZ	= z / localSize.z() * localSize.z();
+				int			sum			= 0;
+				for (int i = 0; i < DE_LENGTH_OF_ARRAY(SHADER_READ_OFFSETS_X); i++)
+					sum += base.getPixelInt(groupBaseX + (x + SHADER_READ_OFFSETS_X[i]) % localSize.x(),
+											groupBaseY + (y + SHADER_READ_OFFSETS_Y[i]) % localSize.y(),
+											groupBaseZ + (z + SHADER_READ_OFFSETS_Z[i]) % localSize.z()).x();
+
+				reference.setPixel(x, y, z, IVec4(sum));
+			}
+		}
+
+		if (readTextureAndVerify(renderCtx, glLog, *texture, *textureBuf, m_imageType, m_format, imageSize, ImageLayerComparer(reference)))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+		return STOP;
+	}
+}
+
+class R32UIImageSingleValueVerifier : public ImageLayerVerifier
+{
+public:
+	R32UIImageSingleValueVerifier (const deUint32 value)					: m_min(value),	m_max(value)	{}
+	R32UIImageSingleValueVerifier (const deUint32 min, const deUint32 max)	: m_min(min),	m_max(max)		{}
+
+	bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int) const
+	{
+		DE_ASSERT(resultSlice.getWidth() == 1 && resultSlice.getHeight() == 1 && resultSlice.getDepth() == 1);
+		DE_ASSERT(resultSlice.getFormat() == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32));
+
+		log << TestLog::Message << "// Note: expecting to get value " << (m_min == m_max ? toString(m_min) : "in range [" + toString(m_min) + ", " + toString(m_max) + "]") << TestLog::EndMessage;
+
+		const deUint32 resultValue = resultSlice.getPixelUint(0, 0).x();
+		if (!de::inRange(resultValue, m_min, m_max))
+		{
+			log << TestLog::Message << "// Failure: got value " << resultValue << TestLog::EndMessage;
+			return false;
+		}
+		else
+		{
+			log << TestLog::Message << "// Success: got value " << resultValue << TestLog::EndMessage;
+			return true;
+		}
+	}
+
+private:
+	const deUint32 m_min;
+	const deUint32 m_max;
+};
+
+//! Tests the imageSize() GLSL function. Stores result in a 1x1 R32UI image. The image with which imageSize() is called isn't read or written, and
+//  can thus be qualifier readonly, writeonly, or both.
+class ImageSizeCase : public TestCase
+{
+public:
+	enum ImageAccess
+	{
+		IMAGEACCESS_READ_ONLY = 0,
+		IMAGEACCESS_WRITE_ONLY,
+		IMAGEACCESS_READ_ONLY_WRITE_ONLY,
+
+		IMAGEACCESS_LAST
+	};
+
+	ImageSizeCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, const IVec3& size, ImageAccess imageAccess)
+		: TestCase			(context, name, description)
+		, m_format			(format)
+		, m_imageType		(imageType)
+		, m_imageSize		(size)
+		, m_imageAccess		(imageAccess)
+	{
+	}
+
+	void			init		(void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType); }
+	IterateResult	iterate		(void);
+
+private:
+	const TextureFormat		m_format;
+	const TextureType		m_imageType;
+	const IVec3				m_imageSize;
+	const ImageAccess		m_imageAccess;
+};
+
+ImageSizeCase::IterateResult ImageSizeCase::iterate (void)
+{
+	const RenderContext&		renderCtx				= m_context.getRenderContext();
+	TestLog&					log						(m_testCtx.getLog());
+	glu::CallLogWrapper			glLog					(renderCtx.getFunctions(), log);
+	const deUint32				internalFormatGL		= glu::getInternalFormat(m_format);
+	const deUint32				textureTargetGL			= getGLTextureTarget(m_imageType);
+	const glu::Buffer			mainTextureBuf			(renderCtx);
+	const glu::Texture			mainTexture				(renderCtx);
+	const glu::Texture			shaderOutResultTexture	(renderCtx);
+
+	glLog.enableLogging(true);
+
+	// Setup textures.
+
+	log << TestLog::Message << "// Created a texture (name " << *mainTexture << ")" << TestLog::EndMessage;
+	if (m_imageType == TEXTURETYPE_BUFFER)
+		log << TestLog::Message << "// Created a buffer for the texture (name " << *mainTextureBuf << ")" << TestLog::EndMessage;
+	log << TestLog::Message << "// Created a texture (name " << *shaderOutResultTexture << ") for storing the shader output" << TestLog::EndMessage;
+
+	glLog.glActiveTexture(GL_TEXTURE0);
+	glLog.glBindTexture(textureTargetGL, *mainTexture);
+	setTexParameteri(glLog, textureTargetGL);
+	setTextureStorage(glLog, m_imageType, internalFormatGL, m_imageSize, *mainTextureBuf);
+	glLog.glBindImageTexture(0, *mainTexture, 0, GL_TRUE, 0, GL_READ_WRITE, internalFormatGL);
+	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+	glLog.glActiveTexture(GL_TEXTURE1);
+	glLog.glBindTexture(GL_TEXTURE_2D, *shaderOutResultTexture);
+	setTexParameteri(glLog, GL_TEXTURE_2D);
+	setTextureStorage(glLog, TEXTURETYPE_2D, GL_R32UI, IVec3(1, 1, 1), 0 /* always 2d texture, no buffer needed */);
+	glLog.glBindImageTexture(1, *shaderOutResultTexture, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_R32UI);
+	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+	// Read texture size in compute shader.
+
+	{
+		// Generate compute shader.
+
+		const char* const	shaderImageAccessStr	= m_imageAccess == IMAGEACCESS_READ_ONLY			? "readonly"
+													: m_imageAccess == IMAGEACCESS_WRITE_ONLY			? "writeonly"
+													: m_imageAccess == IMAGEACCESS_READ_ONLY_WRITE_ONLY	? "readonly writeonly"
+													: DE_NULL;
+		const string		shaderImageFormatStr	= getShaderImageFormatQualifier(m_format);
+		const string		shaderImageTypeStr		= getShaderImageType(m_format.type, m_imageType);
+
+		const glu::ShaderProgram program(renderCtx,
+			glu::ProgramSources() << glu::ComputeSource("#version 310 es\n"
+														+ textureTypeExtensionShaderRequires(m_imageType) +
+														"\n"
+														"precision highp " + shaderImageTypeStr + ";\n"
+														"precision highp uimage2D;\n"
+														"\n"
+														"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+														"layout (" + shaderImageFormatStr + ", binding=0) " + shaderImageAccessStr + " uniform " + shaderImageTypeStr + " u_image;\n"
+														"layout (r32ui, binding=1) writeonly uniform uimage2D u_result;\n"
+														"void main (void)\n"
+														"{\n"
+														+ (m_imageType == TEXTURETYPE_BUFFER ?
+															"	int result = imageSize(u_image);\n"
+														 : m_imageType == TEXTURETYPE_2D || m_imageType == TEXTURETYPE_CUBE ?
+															"	ivec2 size = imageSize(u_image);\n"
+															"	int result = size.y*1000 + size.x;\n"
+														 : m_imageType == TEXTURETYPE_3D || m_imageType == TEXTURETYPE_2D_ARRAY ?
+															"	ivec3 size = imageSize(u_image);\n"
+															"	int result = size.z*1000000 + size.y*1000 + size.x;\n"
+														 : DE_NULL) +
+														"	imageStore(u_result, ivec2(0, 0), uvec4(result));\n"
+														"}\n"));
+
+		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());
+
+		log << program;
+
+		if (!program.isOk())
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
+			return STOP;
+		}
+
+		// Setup and dispatch.
+
+		glLog.glUseProgram(program.getProgram());
+
+		glLog.glDispatchCompute(1, 1, 1);
+		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
+	}
+
+	// Read texture and compare to reference.
+
+	{
+		const deUint32	referenceOutput		= m_imageType == TEXTURETYPE_BUFFER										? (deUint32)(												  m_imageSize.x())
+											: m_imageType == TEXTURETYPE_2D || m_imageType == TEXTURETYPE_CUBE		? (deUint32)(						   m_imageSize.y()*1000 + m_imageSize.x())
+											: m_imageType == TEXTURETYPE_3D || m_imageType == TEXTURETYPE_2D_ARRAY	? (deUint32)(m_imageSize.z()*1000000 + m_imageSize.y()*1000 + m_imageSize.x())
+											: (deUint32)-1;
+
+		if (readIntegerTextureViaFBOAndVerify(renderCtx, glLog, *shaderOutResultTexture, TEXTURETYPE_2D, TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32),
+											  IVec3(1, 1, 1), R32UIImageSingleValueVerifier(referenceOutput)))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong value");
+
+		return STOP;
+	}
+}
+
+//! Case testing the control over early/late fragment tests.
+class EarlyFragmentTestsCase : public TestCase
+{
+public:
+	enum TestType
+	{
+		TESTTYPE_DEPTH = 0,
+		TESTTYPE_STENCIL,
+
+		TESTTYPE_LAST
+	};
+
+	EarlyFragmentTestsCase (Context& context, const char* name, const char* description, TestType type, bool useEarlyTests)
+		: TestCase			(context, name, description)
+		, m_type			(type)
+		, m_useEarlyTests	(useEarlyTests)
+	{
+	}
+
+	void init (void)
+	{
+		if (m_context.getContextInfo().getInt(GL_MAX_FRAGMENT_IMAGE_UNIFORMS) == 0)
+			throw tcu::NotSupportedError("GL_MAX_FRAGMENT_IMAGE_UNIFORMS is zero");
+
+		if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
+			throw tcu::NotSupportedError("Test requires OES_shader_image_atomic extension");
+
+		if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE)
+			throw tcu::NotSupportedError("Render target must have at least " + toString(RENDER_SIZE) + " width and height");
+	}
+
+	IterateResult iterate (void);
+
+private:
+	static const int RENDER_SIZE;
+
+	const TestType	m_type;
+	const bool		m_useEarlyTests;
+};
+
+const int EarlyFragmentTestsCase::RENDER_SIZE = 32;
+
+EarlyFragmentTestsCase::IterateResult EarlyFragmentTestsCase::iterate (void)
+{
+	const RenderContext&		renderCtx		= m_context.getRenderContext();
+	TestLog&					log				(m_testCtx.getLog());
+	glu::CallLogWrapper			glLog			(renderCtx.getFunctions(), log);
+	de::Random					rnd				(deStringHash(getName()));
+	const int					viewportWidth	= RENDER_SIZE;
+	const int					viewportHeight	= RENDER_SIZE;
+	const int					viewportX		= rnd.getInt(0, renderCtx.getRenderTarget().getWidth() - viewportWidth);
+	const int					viewportY		= rnd.getInt(0, renderCtx.getRenderTarget().getHeight() - viewportHeight);
+	const IVec3					imageSize		= defaultImageSize(TEXTURETYPE_2D);
+	const glu::Texture			texture			(renderCtx);
+
+	glLog.enableLogging(true);
+
+	// Setup texture.
+
+	log << TestLog::Message << "// Created a texture (name " << *texture << ")" << TestLog::EndMessage;
+
+	glLog.glActiveTexture(GL_TEXTURE0);
+	glLog.glBindTexture(GL_TEXTURE_2D, *texture);
+	setTexParameteri(glLog, GL_TEXTURE_2D);
+	{
+		LayeredImage src(TEXTURETYPE_2D, TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32), 1, 1, 1);
+		src.setPixel(0, 0, 0, IVec4(0));
+		uploadTexture(glLog, src, 0 /* always 2d texture, no buffer needed */);
+	}
+	glLog.glBindImageTexture(0, *texture, 0, GL_TRUE, 0, GL_READ_WRITE, GL_R32UI);
+	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
+
+	// Set up appropriate conditions for the test.
+
+	glLog.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	glLog.glClear(GL_COLOR_BUFFER_BIT);
+
+	if (m_type == TESTTYPE_DEPTH)
+	{
+		glLog.glClearDepthf(0.5f);
+		glLog.glClear(GL_DEPTH_BUFFER_BIT);
+		glLog.glEnable(GL_DEPTH_TEST);
+	}
+	else if (m_type == TESTTYPE_STENCIL)
+	{
+		glLog.glClearStencil(0);
+		glLog.glClear(GL_STENCIL_BUFFER_BIT);
+		glLog.glScissor(viewportX, viewportY, viewportWidth/2, viewportHeight);
+		glLog.glEnable(GL_SCISSOR_TEST);
+		glLog.glClearStencil(1);
+		glLog.glClear(GL_STENCIL_BUFFER_BIT);
+		glLog.glDisable(GL_SCISSOR_TEST);
+		glLog.glStencilFunc(GL_EQUAL, 1, 1);
+		glLog.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+		glLog.glEnable(GL_STENCIL_TEST);
+	}
+	else
+		DE_ASSERT(false);
+
+	// Perform image stores in fragment shader.
+
+	{
+		// Generate fragment shader.
+
+		const glu::ShaderProgram program(renderCtx,
+			glu::ProgramSources() << glu::VertexSource(		"#version 310 es\n"
+															"\n"
+															"highp in vec3 a_position;\n"
+															"\n"
+															"void main (void)\n"
+															"{\n"
+															"	gl_Position = vec4(a_position, 1.0);\n"
+															"}\n")
+
+								  << glu::FragmentSource(	"#version 310 es\n"
+															"#extension GL_OES_shader_image_atomic : require\n"
+															"\n"
+															+ string(m_useEarlyTests ? "layout (early_fragment_tests) in;\n\n" : "") +
+															"layout (location = 0) out highp vec4 o_color;\n"
+															"\n"
+															"precision highp uimage2D;\n"
+															"\n"
+															"layout (r32ui, binding=0) coherent uniform uimage2D u_image;\n"
+															"\n"
+															"void main (void)\n"
+															"{\n"
+															"	imageAtomicAdd(u_image, ivec2(0, 0), uint(1));\n"
+															"	o_color = vec4(1.0);\n"
+															"}\n"));
+
+		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());
+
+		log << program;
+
+		if (!program.isOk())
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
+			return STOP;
+		}
+
+		// Setup and draw full-viewport quad.
+
+		glLog.glUseProgram(program.getProgram());
+
+		{
+			static const float vertexPositions[4*3] =
+			{
+				-1.0, -1.0, -1.0f,
+				 1.0, -1.0,  0.0f,
+				-1.0,  1.0,  0.0f,
+				 1.0,  1.0,  1.0f,
+			};
+
+			static const deUint16 indices[6] = { 0, 1, 2, 2, 1, 3 };
+
+			const glu::VertexArrayBinding attrBindings[] =
+			{
+				glu::va::Float("a_position", 3, 4, 0, &vertexPositions[0])
+			};
+
+			glLog.glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
+
+			glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
+				glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "Draw failed");
+		}
+	}
+
+	// Log rendered result for convenience.
+	{
+		tcu::Surface rendered(viewportWidth, viewportHeight);
+		glu::readPixels(renderCtx, viewportX, viewportY, rendered.getAccess());
+		log << TestLog::Image("Rendered", "Rendered image", rendered);
+	}
+
+	// Read counter value and check.
+	{
+		const int numSamples		= de::max(1, renderCtx.getRenderTarget().getNumSamples());
+		const int expectedCounter	= m_useEarlyTests ? viewportWidth*viewportHeight/2				: viewportWidth*viewportHeight;
+		const int tolerance			= m_useEarlyTests ? de::max(viewportWidth, viewportHeight)*3	: 0;
+		const int expectedMin		= de::max(0, expectedCounter - tolerance);
+		const int expectedMax		= (expectedCounter + tolerance) * numSamples;
+
+		if (readIntegerTextureViaFBOAndVerify(renderCtx, glLog, *texture, TEXTURETYPE_2D, TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32),
+											  IVec3(1, 1, 1), R32UIImageSingleValueVerifier(expectedMin, expectedMax)))
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong value");
+
+		return STOP;
+	}
+}
+
+} // anonymous
+
+ShaderImageLoadStoreTests::ShaderImageLoadStoreTests (Context& context)
+	: TestCaseGroup(context, "image_load_store", "Shader Image Load & Store Tests")
+{
+}
+
+ShaderImageLoadStoreTests::~ShaderImageLoadStoreTests (void)
+{
+}
+
+void ShaderImageLoadStoreTests::init (void)
+{
+	// Per-image-type tests.
+
+	{
+		static const TextureType imageTypes[] =
+		{
+			TEXTURETYPE_2D,
+			TEXTURETYPE_CUBE,
+			TEXTURETYPE_3D,
+			TEXTURETYPE_2D_ARRAY,
+			TEXTURETYPE_BUFFER
+		};
+
+		static const TextureFormat formats[] =
+		{
+			TextureFormat(TextureFormat::RGBA,	TextureFormat::FLOAT),
+			TextureFormat(TextureFormat::RGBA,	TextureFormat::HALF_FLOAT),
+			TextureFormat(TextureFormat::R,		TextureFormat::FLOAT),
+
+			TextureFormat(TextureFormat::RGBA,	TextureFormat::UNSIGNED_INT32),
+			TextureFormat(TextureFormat::RGBA,	TextureFormat::UNSIGNED_INT16),
+			TextureFormat(TextureFormat::RGBA,	TextureFormat::UNSIGNED_INT8),
+			TextureFormat(TextureFormat::R,		TextureFormat::UNSIGNED_INT32),
+
+			TextureFormat(TextureFormat::RGBA,	TextureFormat::SIGNED_INT32),
+			TextureFormat(TextureFormat::RGBA,	TextureFormat::SIGNED_INT16),
+			TextureFormat(TextureFormat::RGBA,	TextureFormat::SIGNED_INT8),
+			TextureFormat(TextureFormat::R,		TextureFormat::SIGNED_INT32),
+
+			TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8),
+
+			TextureFormat(TextureFormat::RGBA,	TextureFormat::SNORM_INT8)
+		};
+
+		for (int imageTypeNdx = 0; imageTypeNdx < DE_LENGTH_OF_ARRAY(imageTypes); imageTypeNdx++)
+		{
+			const TextureType		imageType			= imageTypes[imageTypeNdx];
+			TestCaseGroup* const	imageTypeGroup		= new TestCaseGroup(m_context, getTextureTypeName(imageType), "");
+			addChild(imageTypeGroup);
+
+			TestCaseGroup* const	storeGroup			= new TestCaseGroup(m_context, "store",					"Plain imageStore() cases");
+			TestCaseGroup* const	loadStoreGroup		= new TestCaseGroup(m_context, "load_store",			"Cases with imageLoad() followed by imageStore()");
+			TestCaseGroup* const	atomicGroup			= new TestCaseGroup(m_context, "atomic",				"Atomic image operation cases");
+			TestCaseGroup* const	qualifierGroup		= new TestCaseGroup(m_context, "qualifiers",			"Coherent, volatile and restrict");
+			TestCaseGroup* const	reinterpretGroup	= new TestCaseGroup(m_context, "format_reinterpret",	"Cases with differing texture and image formats");
+			TestCaseGroup* const	imageSizeGroup		= new TestCaseGroup(m_context, "image_size",			"imageSize() cases");
+			imageTypeGroup->addChild(storeGroup);
+			imageTypeGroup->addChild(loadStoreGroup);
+			imageTypeGroup->addChild(atomicGroup);
+			imageTypeGroup->addChild(qualifierGroup);
+			imageTypeGroup->addChild(reinterpretGroup);
+			imageTypeGroup->addChild(imageSizeGroup);
+
+			for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
+			{
+				const TextureFormat&	format		= formats[formatNdx];
+				const string			formatName	= getShaderImageFormatQualifier(formats[formatNdx]);
+
+				if (imageType == TEXTURETYPE_BUFFER && !isFormatSupportedForTextureBuffer(format))
+					continue;
+
+				// Store cases.
+
+				storeGroup->addChild(new ImageStoreCase(m_context, formatName.c_str(), "", format, imageType));
+				if (textureLayerType(imageType) != imageType)
+					storeGroup->addChild(new ImageStoreCase(m_context, (formatName + "_single_layer").c_str(), "", format, imageType, ImageStoreCase::CASEFLAG_SINGLE_LAYER_BIND));
+
+				// Load & store.
+
+				loadStoreGroup->addChild(new ImageLoadAndStoreCase(m_context, formatName.c_str(), "", format, imageType));
+				if (textureLayerType(imageType) != imageType)
+					loadStoreGroup->addChild(new ImageLoadAndStoreCase(m_context, (formatName + "_single_layer").c_str(), "", format, imageType, ImageLoadAndStoreCase::CASEFLAG_SINGLE_LAYER_BIND));
+
+				if (format.order == TextureFormat::R)
+				{
+					// Atomic operations.
+
+					for (int operationI = 0; operationI < ATOMIC_OPERATION_LAST; operationI++)
+					{
+						for (int atomicCaseTypeI = 0; atomicCaseTypeI < ATOMIC_OPERATION_CASE_TYPE_LAST; atomicCaseTypeI++)
+						{
+							const AtomicOperation operation = (AtomicOperation)operationI;
+
+							if (format.type == TextureFormat::FLOAT && operation != ATOMIC_OPERATION_EXCHANGE)
+								continue;
+
+							const AtomicOperationCaseType	caseType		= (AtomicOperationCaseType)atomicCaseTypeI;
+							const string					caseTypeName	= caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? "result"
+																			: caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES	? "return_value"
+																			: DE_NULL;
+							const string					caseName		= string() + getAtomicOperationCaseName(operation) + "_" + formatName + "_" + caseTypeName;
+
+							if (operation == ATOMIC_OPERATION_COMP_SWAP)
+								atomicGroup->addChild(new AtomicCompSwapCase(m_context, caseName.c_str(), "", format, imageType, caseType));
+							else
+								atomicGroup->addChild(new BinaryAtomicOperationCase(m_context, caseName.c_str(), "", format, imageType, operation, caseType));
+						}
+					}
+
+					// Coherence.
+
+					for (int coherenceQualifierI = 0; coherenceQualifierI < CoherenceCase::QUALIFIER_LAST; coherenceQualifierI++)
+					{
+						const CoherenceCase::Qualifier	coherenceQualifier		= (CoherenceCase::Qualifier)coherenceQualifierI;
+						const char* const				coherenceQualifierName	= coherenceQualifier == CoherenceCase::QUALIFIER_COHERENT ? "coherent"
+																				: coherenceQualifier == CoherenceCase::QUALIFIER_VOLATILE ? "volatile"
+																				: DE_NULL;
+						const string					caseName				= string() + coherenceQualifierName + "_" + formatName;
+
+						qualifierGroup->addChild(new CoherenceCase(m_context, caseName.c_str(), "", format, imageType, coherenceQualifier));
+					}
+				}
+			}
+
+			// Restrict.
+			qualifierGroup->addChild(new ImageLoadAndStoreCase(m_context, "restrict", "", TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), imageType, ImageLoadAndStoreCase::CASEFLAG_RESTRICT_IMAGES));
+
+			// Format re-interpretation.
+
+			for (int texFmtNdx = 0; texFmtNdx < DE_LENGTH_OF_ARRAY(formats); texFmtNdx++)
+			for (int imgFmtNdx = 0; imgFmtNdx < DE_LENGTH_OF_ARRAY(formats); imgFmtNdx++)
+			{
+				const TextureFormat& texFmt = formats[texFmtNdx];
+				const TextureFormat& imgFmt = formats[imgFmtNdx];
+
+				if (imageType == TEXTURETYPE_BUFFER && !isFormatSupportedForTextureBuffer(texFmt))
+					continue;
+
+				if (texFmt != imgFmt && texFmt.getPixelSize() == imgFmt.getPixelSize())
+					reinterpretGroup->addChild(new ImageLoadAndStoreCase(m_context,
+																		 (getShaderImageFormatQualifier(texFmt) + "_" + getShaderImageFormatQualifier(imgFmt)).c_str(), "",
+																		 texFmt, imgFmt, imageType));
+			}
+
+			// imageSize().
+
+			{
+				static const IVec3 baseImageSizes[] =
+				{
+					IVec3(32, 32, 32),
+					IVec3(12, 34, 56),
+					IVec3(1,   1,  1),
+					IVec3(7,   1,  1)
+				};
+
+				for (int imageAccessI = 0; imageAccessI < ImageSizeCase::IMAGEACCESS_LAST; imageAccessI++)
+				{
+					const ImageSizeCase::ImageAccess	imageAccess		= (ImageSizeCase::ImageAccess)imageAccessI;
+					const char* const					imageAccessStr	= imageAccess == ImageSizeCase::IMAGEACCESS_READ_ONLY				? "readonly"
+																		: imageAccess == ImageSizeCase::IMAGEACCESS_WRITE_ONLY				? "writeonly"
+																		: imageAccess == ImageSizeCase::IMAGEACCESS_READ_ONLY_WRITE_ONLY	? "readonly_writeonly"
+																		: DE_NULL;
+
+					for (int imageSizeNdx = 0; imageSizeNdx < DE_LENGTH_OF_ARRAY(baseImageSizes); imageSizeNdx++)
+					{
+						const IVec3&	baseSize	= baseImageSizes[imageSizeNdx];
+						const IVec3		imageSize	= imageType == TEXTURETYPE_BUFFER		? IVec3(baseSize.x(), 1, 1)
+													: imageType == TEXTURETYPE_2D			? IVec3(baseSize.x(), baseSize.y(), 1)
+													: imageType == TEXTURETYPE_CUBE			? IVec3(baseSize.x(), baseSize.x(), 1)
+													: imageType == TEXTURETYPE_3D			? baseSize
+													: imageType == TEXTURETYPE_2D_ARRAY		? baseSize
+													: IVec3(-1, -1, -1);
+
+						const string	sizeStr		= imageType == TEXTURETYPE_BUFFER		? toString(imageSize.x())
+													: imageType == TEXTURETYPE_2D			? toString(imageSize.x()) + "x" + toString(imageSize.y())
+													: imageType == TEXTURETYPE_CUBE			? toString(imageSize.x()) + "x" + toString(imageSize.y())
+													: imageType == TEXTURETYPE_3D			? toString(imageSize.x()) + "x" + toString(imageSize.y()) + "x" + toString(imageSize.z())
+													: imageType == TEXTURETYPE_2D_ARRAY		? toString(imageSize.x()) + "x" + toString(imageSize.y()) + "x" + toString(imageSize.z())
+													: DE_NULL;
+
+						const string	caseName	= string() + imageAccessStr + "_" + sizeStr;
+
+						imageSizeGroup->addChild(new ImageSizeCase(m_context, caseName.c_str(), "", TextureFormat(TextureFormat::RGBA, TextureFormat::FLOAT), imageType, imageSize, imageAccess));
+					}
+				}
+			}
+		}
+	}
+
+	// early_fragment_tests cases.
+
+	{
+		TestCaseGroup* const earlyTestsGroup = new TestCaseGroup(m_context, "early_fragment_tests", "");
+		addChild(earlyTestsGroup);
+
+		for (int useEarlyTestsI = 0; useEarlyTestsI <= 1; useEarlyTestsI++)
+		{
+			const bool useEarlyTests = useEarlyTestsI != 0;
+
+			for (int testTypeI = 0; testTypeI < EarlyFragmentTestsCase::TESTTYPE_LAST; testTypeI++)
+			{
+				const EarlyFragmentTestsCase::TestType		testType		= (EarlyFragmentTestsCase::TestType)testTypeI;
+
+				const string								testTypeName	= testType == EarlyFragmentTestsCase::TESTTYPE_DEPTH		? "depth"
+																			  : testType == EarlyFragmentTestsCase::TESTTYPE_STENCIL	? "stencil"
+																			  : DE_NULL;
+
+				const string								caseName		= string(useEarlyTests ? "" : "no_") + "early_fragment_tests_" + testTypeName;
+
+				const string								caseDesc		= string(useEarlyTests ? "Specify" : "Don't specify")
+																			+ " early_fragment_tests, target the " + testTypeName + " test";
+
+				earlyTestsGroup->addChild(new EarlyFragmentTestsCase(m_context, caseName.c_str(), caseDesc.c_str(), testType, useEarlyTests));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderImageLoadStoreTests.hpp b/modules/gles31/functional/es31fShaderImageLoadStoreTests.hpp
new file mode 100644
index 0000000..944f830
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderImageLoadStoreTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSHADERIMAGELOADSTORETESTS_HPP
+#define _ES31FSHADERIMAGELOADSTORETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader Image Load & Store Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderImageLoadStoreTests : public TestCaseGroup
+{
+public:
+								ShaderImageLoadStoreTests	(Context& context);
+								~ShaderImageLoadStoreTests	(void);
+
+	void						init						(void);
+
+private:
+								ShaderImageLoadStoreTests	(const ShaderImageLoadStoreTests& other);
+	ShaderImageLoadStoreTests&	operator=					(const ShaderImageLoadStoreTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERIMAGELOADSTORETESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderIntegerFunctionTests.cpp b/modules/gles31/functional/es31fShaderIntegerFunctionTests.cpp
new file mode 100644
index 0000000..c72c482
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderIntegerFunctionTests.cpp
@@ -0,0 +1,1159 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Integer built-in function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderIntegerFunctionTests.hpp"
+#include "glsShaderExecUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuFormatUtil.hpp"
+#include "tcuFloat.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+#include "deInt32.h"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using namespace gls::ShaderExecUtil;
+
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::UVec2;
+using tcu::UVec3;
+using tcu::UVec4;
+
+// Utilities
+
+namespace
+{
+
+struct HexFloat
+{
+	const float value;
+	HexFloat (const float value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const HexFloat& v)
+{
+	return str << v.value << " / " << tcu::toHex(tcu::Float32(v.value).bits());
+}
+
+struct VarValue
+{
+	const glu::VarType&	type;
+	const void*			value;
+
+	VarValue (const glu::VarType& type_, const void* value_) : type(type_), value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const VarValue& varValue)
+{
+	DE_ASSERT(varValue.type.isBasicType());
+
+	const glu::DataType		basicType		= varValue.type.getBasicType();
+	const glu::DataType		scalarType		= glu::getDataTypeScalarType(basicType);
+	const int				numComponents	= glu::getDataTypeScalarSize(basicType);
+
+	if (numComponents > 1)
+		str << glu::getDataTypeName(basicType) << "(";
+
+	for (int compNdx = 0; compNdx < numComponents; compNdx++)
+	{
+		if (compNdx != 0)
+			str << ", ";
+
+		switch (scalarType)
+		{
+			case glu::TYPE_FLOAT:	str << HexFloat(((const float*)varValue.value)[compNdx]);						break;
+			case glu::TYPE_INT:		str << ((const deInt32*)varValue.value)[compNdx];								break;
+			case glu::TYPE_UINT:	str << tcu::toHex(((const deUint32*)varValue.value)[compNdx]);					break;
+			case glu::TYPE_BOOL:	str << (((const deUint32*)varValue.value)[compNdx] != 0 ? "true" : "false");	break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	if (numComponents > 1)
+		str << ")";
+
+	return str;
+}
+
+inline int getShaderUintBitCount (glu::ShaderType shaderType, glu::Precision precision)
+{
+	// \todo [2013-10-31 pyry] Query from GL for vertex and fragment shaders.
+	DE_UNREF(shaderType);
+	const int bitCounts[] = { 8, 16, 32 };
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(bitCounts) == glu::PRECISION_LAST);
+	return bitCounts[precision];
+}
+
+} // anonymous
+
+// IntegerFunctionCase
+
+class IntegerFunctionCase : public TestCase
+{
+public:
+							IntegerFunctionCase		(Context& context, const char* name, const char* description, glu::ShaderType shaderType);
+							~IntegerFunctionCase	(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+protected:
+							IntegerFunctionCase		(const IntegerFunctionCase& other);
+	IntegerFunctionCase&	operator=				(const IntegerFunctionCase& other);
+
+	virtual void			getInputValues			(int numValues, void* const* values) const = 0;
+	virtual bool			compare					(const void* const* inputs, const void* const* outputs) = 0;
+
+	glu::ShaderType			m_shaderType;
+	ShaderSpec				m_spec;
+	int						m_numValues;
+
+	std::ostringstream		m_failMsg;				//!< Comparison failure help message.
+
+private:
+	ShaderExecutor*			m_executor;
+};
+
+IntegerFunctionCase::IntegerFunctionCase (Context& context, const char* name, const char* description, glu::ShaderType shaderType)
+	: TestCase		(context, name, description)
+	, m_shaderType	(shaderType)
+	, m_numValues	(100)
+	, m_executor	(DE_NULL)
+{
+	m_spec.version = glu::GLSL_VERSION_310_ES;
+}
+
+IntegerFunctionCase::~IntegerFunctionCase (void)
+{
+	IntegerFunctionCase::deinit();
+}
+
+void IntegerFunctionCase::init (void)
+{
+	DE_ASSERT(!m_executor);
+
+	m_executor = createExecutor(m_context.getRenderContext(), m_shaderType, m_spec);
+	m_testCtx.getLog() << m_executor;
+
+	if (!m_executor->isOk())
+		throw tcu::TestError("Compile failed");
+}
+
+void IntegerFunctionCase::deinit (void)
+{
+	delete m_executor;
+	m_executor = DE_NULL;
+}
+
+static vector<int> getScalarSizes (const vector<Symbol>& symbols)
+{
+	vector<int> sizes(symbols.size());
+	for (int ndx = 0; ndx < (int)symbols.size(); ++ndx)
+		sizes[ndx] = symbols[ndx].varType.getScalarSize();
+	return sizes;
+}
+
+static int computeTotalScalarSize (const vector<Symbol>& symbols)
+{
+	int totalSize = 0;
+	for (vector<Symbol>::const_iterator sym = symbols.begin(); sym != symbols.end(); ++sym)
+		totalSize += sym->varType.getScalarSize();
+	return totalSize;
+}
+
+static vector<void*> getInputOutputPointers (const vector<Symbol>& symbols, vector<deUint32>& data, const int numValues)
+{
+	vector<void*>	pointers		(symbols.size());
+	int				curScalarOffset	= 0;
+
+	for (int varNdx = 0; varNdx < (int)symbols.size(); ++varNdx)
+	{
+		const Symbol&	var				= symbols[varNdx];
+		const int		scalarSize		= var.varType.getScalarSize();
+
+		// Uses planar layout as input/output specs do not support strides.
+		pointers[varNdx] = &data[curScalarOffset];
+		curScalarOffset += scalarSize*numValues;
+	}
+
+	DE_ASSERT(curScalarOffset == (int)data.size());
+
+	return pointers;
+}
+
+IntegerFunctionCase::IterateResult IntegerFunctionCase::iterate (void)
+{
+	const int				numInputScalars			= computeTotalScalarSize(m_spec.inputs);
+	const int				numOutputScalars		= computeTotalScalarSize(m_spec.outputs);
+	vector<deUint32>		inputData				(numInputScalars * m_numValues);
+	vector<deUint32>		outputData				(numOutputScalars * m_numValues);
+	const vector<void*>		inputPointers			= getInputOutputPointers(m_spec.inputs, inputData, m_numValues);
+	const vector<void*>		outputPointers			= getInputOutputPointers(m_spec.outputs, outputData, m_numValues);
+
+	// Initialize input data.
+	getInputValues(m_numValues, &inputPointers[0]);
+
+	// Execute shader.
+	m_executor->useProgram();
+	m_executor->execute(m_numValues, &inputPointers[0], &outputPointers[0]);
+
+	// Compare results.
+	{
+		const vector<int>		inScalarSizes		= getScalarSizes(m_spec.inputs);
+		const vector<int>		outScalarSizes		= getScalarSizes(m_spec.outputs);
+		vector<void*>			curInputPtr			(inputPointers.size());
+		vector<void*>			curOutputPtr		(outputPointers.size());
+		int						numFailed			= 0;
+
+		for (int valNdx = 0; valNdx < m_numValues; valNdx++)
+		{
+			// Set up pointers for comparison.
+			for (int inNdx = 0; inNdx < (int)curInputPtr.size(); ++inNdx)
+				curInputPtr[inNdx] = (deUint32*)inputPointers[inNdx] + inScalarSizes[inNdx]*valNdx;
+
+			for (int outNdx = 0; outNdx < (int)curOutputPtr.size(); ++outNdx)
+				curOutputPtr[outNdx] = (deUint32*)outputPointers[outNdx] + outScalarSizes[outNdx]*valNdx;
+
+			if (!compare(&curInputPtr[0], &curOutputPtr[0]))
+			{
+				// \todo [2013-08-08 pyry] We probably want to log reference value as well?
+
+				m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed for value " << valNdx << ":\n  " << m_failMsg.str() << TestLog::EndMessage;
+
+				m_testCtx.getLog() << TestLog::Message << "  inputs:" << TestLog::EndMessage;
+				for (int inNdx = 0; inNdx < (int)curInputPtr.size(); inNdx++)
+					m_testCtx.getLog() << TestLog::Message << "    " << m_spec.inputs[inNdx].name << " = "
+														   << VarValue(m_spec.inputs[inNdx].varType, curInputPtr[inNdx])
+									   << TestLog::EndMessage;
+
+				m_testCtx.getLog() << TestLog::Message << "  outputs:" << TestLog::EndMessage;
+				for (int outNdx = 0; outNdx < (int)curOutputPtr.size(); outNdx++)
+					m_testCtx.getLog() << TestLog::Message << "    " << m_spec.outputs[outNdx].name << " = "
+														   << VarValue(m_spec.outputs[outNdx].varType, curOutputPtr[outNdx])
+									   << TestLog::EndMessage;
+
+				m_failMsg.str("");
+				m_failMsg.clear();
+				numFailed += 1;
+			}
+		}
+
+		m_testCtx.getLog() << TestLog::Message << (m_numValues - numFailed) << " / " << m_numValues << " values passed" << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								numFailed == 0 ? "Pass"					: "Result comparison failed");
+	}
+
+	return STOP;
+}
+
+static const char* getPrecisionPostfix (glu::Precision precision)
+{
+	static const char* s_postfix[] =
+	{
+		"_lowp",
+		"_mediump",
+		"_highp"
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_postfix) == glu::PRECISION_LAST);
+	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
+	return s_postfix[precision];
+}
+
+static const char* getShaderTypePostfix (glu::ShaderType shaderType)
+{
+	static const char* s_postfix[] =
+	{
+		"_vertex",
+		"_fragment",
+		"_geometry",
+		"_tess_control",
+		"_tess_eval",
+		"_compute"
+	};
+	DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
+	return s_postfix[shaderType];
+}
+
+static std::string getIntegerFuncCaseName (glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+{
+	return string(glu::getDataTypeName(baseType)) + getPrecisionPostfix(precision) + getShaderTypePostfix(shaderType);
+}
+
+class UaddCarryCase : public IntegerFunctionCase
+{
+public:
+	UaddCarryCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: IntegerFunctionCase(context, getIntegerFuncCaseName(baseType, precision, shaderType).c_str(), "uaddCarry", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("x", glu::VarType(baseType, precision)));
+		m_spec.inputs.push_back(Symbol("y", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("sum", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("carry", glu::VarType(baseType, glu::PRECISION_LOWP)));
+		m_spec.source = "sum = uaddCarry(x, y, carry);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0x235facu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+//		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		deUint32*				in0			= (deUint32*)values[0];
+		deUint32*				in1			= (deUint32*)values[1];
+		int						valueNdx	= 0;
+
+		const struct
+		{
+			deUint32	x;
+			deUint32	y;
+		} easyCases[] =
+		{
+			{ 0x00000000u,	0x00000000u },
+			{ 0xfffffffeu,	0x00000001u },
+			{ 0x00000001u,	0xfffffffeu },
+			{ 0xffffffffu,	0x00000001u },
+			{ 0x00000001u,	0xffffffffu },
+			{ 0xfffffffeu,	0x00000002u },
+			{ 0x00000002u,	0xfffffffeu },
+			{ 0xffffffffu,	0xffffffffu }
+		};
+
+		for (int easyCaseNdx = 0; easyCaseNdx < DE_LENGTH_OF_ARRAY(easyCases); easyCaseNdx++)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				in0[valueNdx*scalarSize + compNdx] = easyCases[easyCaseNdx].x;
+				in1[valueNdx*scalarSize + compNdx] = easyCases[easyCaseNdx].y;
+			}
+
+			valueNdx += 1;
+		}
+
+		while (valueNdx < numValues)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				in0[valueNdx*scalarSize + compNdx] = rnd.getUint32();
+				in1[valueNdx*scalarSize + compNdx] = rnd.getUint32();
+			}
+
+			valueNdx += 1;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const deUint32			cmpMasks0[]		= { 0xffu, 0xffffu, 0xffffffffu };
+		const deUint32			cmpMasks1[]		= { 0xfffffff0u, 0xfffffff0u, 0xffffffffu };
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const deUint32			mask0			= cmpMasks0[precision];
+		const deUint32			mask1			= cmpMasks1[precision];
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const deUint32	in0		= ((const deUint32*)inputs[0])[compNdx];
+			const deUint32	in1		= ((const deUint32*)inputs[1])[compNdx];
+			const deUint32	out0	= ((const deUint32*)outputs[0])[compNdx];
+			const deUint32	out1	= ((const deUint32*)outputs[1])[compNdx];
+			const deUint32	ref0	= in0+in1;
+			const deUint32	ref1	= (deUint64(in0)+deUint64(in1)) > 0xffffffffu ? 1u : 0u;
+
+			if (((out0&mask0) != (ref0&mask0)) || ((out1&mask1) != (ref1&mask1)))
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(ref0) << ", " << tcu::toHex(ref1);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class UsubBorrowCase : public IntegerFunctionCase
+{
+public:
+	UsubBorrowCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: IntegerFunctionCase(context, getIntegerFuncCaseName(baseType, precision, shaderType).c_str(), "usubBorrow", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("x", glu::VarType(baseType, precision)));
+		m_spec.inputs.push_back(Symbol("y", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("diff", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("carry", glu::VarType(baseType, glu::PRECISION_LOWP)));
+		m_spec.source = "diff = usubBorrow(x, y, carry);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0x235facu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+//		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		deUint32*				in0			= (deUint32*)values[0];
+		deUint32*				in1			= (deUint32*)values[1];
+		int						valueNdx	= 0;
+
+		const struct
+		{
+			deUint32	x;
+			deUint32	y;
+		} easyCases[] =
+		{
+			{ 0x00000000u,	0x00000000u },
+			{ 0x00000001u,	0x00000001u },
+			{ 0x00000001u,	0x00000002u },
+			{ 0x00000001u,	0xffffffffu },
+			{ 0xfffffffeu,	0xffffffffu },
+			{ 0xffffffffu,	0xffffffffu },
+		};
+
+		for (int easyCaseNdx = 0; easyCaseNdx < DE_LENGTH_OF_ARRAY(easyCases); easyCaseNdx++)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				in0[valueNdx*scalarSize + compNdx] = easyCases[easyCaseNdx].x;
+				in1[valueNdx*scalarSize + compNdx] = easyCases[easyCaseNdx].y;
+			}
+
+			valueNdx += 1;
+		}
+
+		while (valueNdx < numValues)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				in0[valueNdx*scalarSize + compNdx] = rnd.getUint32();
+				in1[valueNdx*scalarSize + compNdx] = rnd.getUint32();
+			}
+
+			valueNdx += 1;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const deUint32			cmpMasks0[]		= { 0xffu, 0xffffu, 0xffffffffu };
+		const deUint32			cmpMasks1[]		= { 0xfffffff0u, 0xfffffff0u, 0xffffffffu };
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const deUint32			mask0			= cmpMasks0[precision];
+		const deUint32			mask1			= cmpMasks1[precision];
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const deUint32	in0		= ((const deUint32*)inputs[0])[compNdx];
+			const deUint32	in1		= ((const deUint32*)inputs[1])[compNdx];
+			const deUint32	out0	= ((const deUint32*)outputs[0])[compNdx];
+			const deUint32	out1	= ((const deUint32*)outputs[1])[compNdx];
+			const deUint32	ref0	= in0-in1;
+			const deUint32	ref1	= in0 >= in1 ? 0u : 1u;
+
+			if (((out0&mask0) != (ref0&mask0)) || ((out1&mask1) != (ref1&mask1)))
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(ref0) << ", " << tcu::toHex(ref1);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class UmulExtendedCase : public IntegerFunctionCase
+{
+public:
+	UmulExtendedCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: IntegerFunctionCase(context, getIntegerFuncCaseName(baseType, precision, shaderType).c_str(), "umulExtended", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("x", glu::VarType(baseType, precision)));
+		m_spec.inputs.push_back(Symbol("y", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("msb", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("lsb", glu::VarType(baseType, precision)));
+		m_spec.source = "umulExtended(x, y, msb, lsb);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0x235facu);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+//		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		deUint32*				in0			= (deUint32*)values[0];
+		deUint32*				in1			= (deUint32*)values[1];
+		int						valueNdx	= 0;
+
+		const struct
+		{
+			deUint32	x;
+			deUint32	y;
+		} easyCases[] =
+		{
+			{ 0x00000000u,	0x00000000u },
+			{ 0xffffffffu,	0x00000001u },
+			{ 0xffffffffu,	0x00000002u },
+			{ 0x00000001u,	0xffffffffu },
+			{ 0x00000002u,	0xffffffffu },
+			{ 0xffffffffu,	0xffffffffu },
+		};
+
+		for (int easyCaseNdx = 0; easyCaseNdx < DE_LENGTH_OF_ARRAY(easyCases); easyCaseNdx++)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				in0[valueNdx*scalarSize + compNdx] = easyCases[easyCaseNdx].x;
+				in1[valueNdx*scalarSize + compNdx] = easyCases[easyCaseNdx].y;
+			}
+
+			valueNdx += 1;
+		}
+
+		while (valueNdx < numValues)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const deUint32	base0	= rnd.getUint32();
+				const deUint32	base1	= rnd.getUint32();
+				const int		adj0	= rnd.getInt(0, 20);
+				const int		adj1	= rnd.getInt(0, 20);
+				in0[valueNdx*scalarSize + compNdx] = base0 >> adj0;
+				in1[valueNdx*scalarSize + compNdx] = base1 >> adj1;
+			}
+
+			valueNdx += 1;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const deUint32	in0		= ((const deUint32*)inputs[0])[compNdx];
+			const deUint32	in1		= ((const deUint32*)inputs[1])[compNdx];
+			const deUint32	out0	= ((const deUint32*)outputs[0])[compNdx];
+			const deUint32	out1	= ((const deUint32*)outputs[1])[compNdx];
+			const deUint64	mul64	= deUint64(in0)*deUint64(in1);
+			const deUint32	ref0	= deUint32(mul64 >> 32);
+			const deUint32	ref1	= deUint32(mul64 & 0xffffffffu);
+
+			if (out0 != ref0 || out1 != ref1)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(ref0) << ", " << tcu::toHex(ref1);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class ImulExtendedCase : public IntegerFunctionCase
+{
+public:
+	ImulExtendedCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: IntegerFunctionCase(context, getIntegerFuncCaseName(baseType, precision, shaderType).c_str(), "imulExtended", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("x", glu::VarType(baseType, precision)));
+		m_spec.inputs.push_back(Symbol("y", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("msb", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("lsb", glu::VarType(baseType, precision)));
+		m_spec.source = "imulExtended(x, y, msb, lsb);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0x224fa1u);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+//		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		deUint32*				in0			= (deUint32*)values[0];
+		deUint32*				in1			= (deUint32*)values[1];
+		int						valueNdx	= 0;
+
+		const struct
+		{
+			deUint32	x;
+			deUint32	y;
+		} easyCases[] =
+		{
+			{ 0x00000000u,	0x00000000u },
+			{ 0xffffffffu,	0x00000002u },
+			{ 0x7fffffffu,	0x00000001u },
+			{ 0x7fffffffu,	0x00000002u },
+			{ 0x7fffffffu,	0x7fffffffu },
+			{ 0xffffffffu,	0xffffffffu },
+			{ 0x7fffffffu,	0xfffffffeu },
+		};
+
+		for (int easyCaseNdx = 0; easyCaseNdx < DE_LENGTH_OF_ARRAY(easyCases); easyCaseNdx++)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				in0[valueNdx*scalarSize + compNdx] = (deInt32)easyCases[easyCaseNdx].x;
+				in1[valueNdx*scalarSize + compNdx] = (deInt32)easyCases[easyCaseNdx].y;
+			}
+
+			valueNdx += 1;
+		}
+
+		while (valueNdx < numValues)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				const deInt32	base0	= (deInt32)rnd.getUint32();
+				const deInt32	base1	= (deInt32)rnd.getUint32();
+				const int		adj0	= rnd.getInt(0, 20);
+				const int		adj1	= rnd.getInt(0, 20);
+				in0[valueNdx*scalarSize + compNdx] = base0 >> adj0;
+				in1[valueNdx*scalarSize + compNdx] = base1 >> adj1;
+			}
+
+			valueNdx += 1;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const deInt32	in0		= ((const deInt32*)inputs[0])[compNdx];
+			const deInt32	in1		= ((const deInt32*)inputs[1])[compNdx];
+			const deInt32	out0	= ((const deInt32*)outputs[0])[compNdx];
+			const deInt32	out1	= ((const deInt32*)outputs[1])[compNdx];
+			const deInt64	mul64	= deInt64(in0)*deInt64(in1);
+			const deInt32	ref0	= deInt32(mul64 >> 32);
+			const deInt32	ref1	= deInt32(mul64 & 0xffffffffu);
+
+			if (out0 != ref0 || out1 != ref1)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(ref0) << ", " << tcu::toHex(ref1);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class BitfieldExtractCase : public IntegerFunctionCase
+{
+public:
+	BitfieldExtractCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: IntegerFunctionCase(context, getIntegerFuncCaseName(baseType, precision, shaderType).c_str(), "bitfieldExtract", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("value", glu::VarType(baseType, precision)));
+		m_spec.inputs.push_back(Symbol("offset", glu::VarType(glu::TYPE_INT, precision)));
+		m_spec.inputs.push_back(Symbol("bits", glu::VarType(glu::TYPE_INT, precision)));
+		m_spec.outputs.push_back(Symbol("extracted", glu::VarType(baseType, precision)));
+		m_spec.source = "extracted = bitfieldExtract(value, offset, bits);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0xa113fca2u);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		const bool				ignoreSign	= precision != glu::PRECISION_HIGHP && glu::isDataTypeIntOrIVec(type);
+		const int				numBits		= getShaderUintBitCount(m_shaderType, precision) - (ignoreSign ? 1 : 0);
+		deUint32*				inValue		= (deUint32*)values[0];
+		int*					inOffset	= (int*)values[1];
+		int*					inBits		= (int*)values[2];
+		int						valueNdx	= 0;
+
+		while (valueNdx < numValues)
+		{
+			const int		bits	= rnd.getInt(0, numBits);
+			const int		offset	= rnd.getInt(0, numBits-bits);
+
+			inOffset[valueNdx]	= offset;
+			inBits[valueNdx]	= bits;
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+				inValue[valueNdx*scalarSize + compNdx] = rnd.getUint32();
+
+			valueNdx += 1;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const bool				isSigned		= glu::isDataTypeIntOrIVec(type);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const int				offset			= *((const int*)inputs[1]);
+		const int				bits			= *((const int*)inputs[2]);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const deUint32	value	= ((const deUint32*)inputs[0])[compNdx];
+			const deUint32	out		= ((const deUint32*)outputs[0])[compNdx];
+			const deUint32	valMask	= (bits == 32 ? ~0u : ((1u<<bits)-1u));
+			const deUint32	baseVal	= (value >> offset) & valMask;
+			const deUint32	ref		= baseVal | ((isSigned && (baseVal & (1<<(bits-1)))) ? ~valMask : 0u);
+
+			if (out != ref)
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(ref);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class BitfieldInsertCase : public IntegerFunctionCase
+{
+public:
+	BitfieldInsertCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: IntegerFunctionCase(context, getIntegerFuncCaseName(baseType, precision, shaderType).c_str(), "bitfieldInsert", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("base", glu::VarType(baseType, precision)));
+		m_spec.inputs.push_back(Symbol("insert", glu::VarType(baseType, precision)));
+		m_spec.inputs.push_back(Symbol("offset", glu::VarType(glu::TYPE_INT, precision)));
+		m_spec.inputs.push_back(Symbol("bits", glu::VarType(glu::TYPE_INT, precision)));
+		m_spec.outputs.push_back(Symbol("result", glu::VarType(baseType, precision)));
+		m_spec.source = "result = bitfieldInsert(base, insert, offset, bits);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0x12c2acff);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision	= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		const int				numBits		= getShaderUintBitCount(m_shaderType, precision);
+		deUint32*				inBase		= (deUint32*)values[0];
+		deUint32*				inInsert	= (deUint32*)values[1];
+		int*					inOffset	= (int*)values[2];
+		int*					inBits		= (int*)values[3];
+		int						valueNdx	= 0;
+
+		while (valueNdx < numValues)
+		{
+			const int		bits	= rnd.getInt(0, numBits);
+			const int		offset	= rnd.getInt(0, numBits-bits);
+
+			inOffset[valueNdx]	= offset;
+			inBits[valueNdx]	= bits;
+
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+			{
+				inBase[valueNdx*scalarSize + compNdx]	= rnd.getUint32();
+				inInsert[valueNdx*scalarSize + compNdx]	= rnd.getUint32();
+			}
+
+			valueNdx += 1;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const deUint32			cmpMasks[]		= { 0xffu, 0xffffu, 0xffffffffu };
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const deUint32			cmpMask			= cmpMasks[precision];
+		const int				offset			= *((const int*)inputs[2]);
+		const int				bits			= *((const int*)inputs[3]);
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const deUint32	base	= ((const deUint32*)inputs[0])[compNdx];
+			const deUint32	insert	= ((const deUint32*)inputs[1])[compNdx];
+			const deInt32	out		= ((const deUint32*)outputs[0])[compNdx];
+
+			const deUint32	mask	= bits == 32 ? ~0u : (1u<<bits)-1;
+			const deUint32	ref		= (base & ~(mask<<offset)) | ((insert & mask)<<offset);
+
+			if ((out&cmpMask) != (ref&cmpMask))
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(ref);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+static inline deUint32 reverseBits (deUint32 v)
+{
+	v = (((v & 0xaaaaaaaa) >> 1) | ((v & 0x55555555) << 1));
+	v = (((v & 0xcccccccc) >> 2) | ((v & 0x33333333) << 2));
+	v = (((v & 0xf0f0f0f0) >> 4) | ((v & 0x0f0f0f0f) << 4));
+	v = (((v & 0xff00ff00) >> 8) | ((v & 0x00ff00ff) << 8));
+	return((v >> 16) | (v << 16));
+}
+
+class BitfieldReverseCase : public IntegerFunctionCase
+{
+public:
+	BitfieldReverseCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: IntegerFunctionCase(context, getIntegerFuncCaseName(baseType, precision, shaderType).c_str(), "bitfieldReverse", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("value", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("result", glu::VarType(baseType, glu::PRECISION_HIGHP)));
+		m_spec.source = "result = bitfieldReverse(value);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0xff23a4);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		deUint32*				inValue		= (deUint32*)values[0];
+		int						valueNdx	= 0;
+
+		while (valueNdx < numValues)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+				inValue[valueNdx*scalarSize + compNdx] = rnd.getUint32();
+
+			valueNdx += 1;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const deUint32			cmpMasks[]		= { 0xff000000u, 0xffff0000u, 0xffffffffu };
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const deUint32			cmpMask			= cmpMasks[precision];
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const deUint32	value	= ((const deUint32*)inputs[0])[compNdx];
+			const deInt32	out		= ((const deUint32*)outputs[0])[compNdx];
+			const deUint32	ref		= reverseBits(value);
+
+			if ((out&cmpMask) != (ref&cmpMask))
+			{
+				m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(ref);
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+class BitCountCase : public IntegerFunctionCase
+{
+public:
+	BitCountCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: IntegerFunctionCase(context, getIntegerFuncCaseName(baseType, precision, shaderType).c_str(), "bitCount", shaderType)
+	{
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	intType		= vecSize == 1 ? glu::TYPE_INT : glu::getDataTypeIntVec(vecSize);
+
+		m_spec.inputs.push_back(Symbol("value", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("count", glu::VarType(intType, glu::PRECISION_LOWP)));
+		m_spec.source = "count = bitCount(value);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0xab2cca4);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		deUint32*				inValue		= (deUint32*)values[0];
+		int						valueNdx	= 0;
+
+		while (valueNdx < numValues)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+				inValue[valueNdx*scalarSize + compNdx] = rnd.getUint32();
+
+			valueNdx += 1;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const deUint32			countMasks[]	= { 0xffu, 0xffffu, 0xffffffffu };
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const deUint32			countMask		= countMasks[precision];
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const deUint32	value	= ((const deUint32*)inputs[0])[compNdx];
+			const int		out		= ((const int*)outputs[0])[compNdx];
+			const int		minRef	= dePop32(value&countMask);
+			const int		maxRef	= dePop32(value);
+
+			if (!de::inRange(out, minRef, maxRef))
+			{
+				m_failMsg << "Expected [" << compNdx << "] in range [" << minRef << ", " << maxRef << "]";
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+static int findLSB (deUint32 value)
+{
+	for (int i = 0; i < 32; i++)
+	{
+		if (value & (1u<<i))
+			return i;
+	}
+	return -1;
+}
+
+class FindLSBCase : public IntegerFunctionCase
+{
+public:
+	FindLSBCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: IntegerFunctionCase(context, getIntegerFuncCaseName(baseType, precision, shaderType).c_str(), "findLSB", shaderType)
+	{
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	intType		= vecSize == 1 ? glu::TYPE_INT : glu::getDataTypeIntVec(vecSize);
+
+		m_spec.inputs.push_back(Symbol("value", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("lsb", glu::VarType(intType, glu::PRECISION_LOWP)));
+		m_spec.source = "lsb = findLSB(value);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0x9923c2af);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		deUint32*				inValue		= (deUint32*)values[0];
+		int						valueNdx	= 0;
+
+		while (valueNdx < numValues)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+				inValue[valueNdx*scalarSize + compNdx] = rnd.getUint32();
+
+			valueNdx += 1;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const deUint32			masks[]			= { 0xffu, 0xffffu, 0xffffffffu };
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const deUint32			mask			= masks[precision];
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const deUint32	value	= ((const deUint32*)inputs[0])[compNdx];
+			const int		out		= ((const int*)outputs[0])[compNdx];
+			const int		minRef	= findLSB(value&mask);
+			const int		maxRef	= findLSB(value);
+
+			if (!de::inRange(out, minRef, maxRef))
+			{
+				m_failMsg << "Expected [" << compNdx << "] in range [" << minRef << ", " << maxRef << "]";
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+static int findMSB (int value)
+{
+	if (value > 0)
+		return 31 - deClz32((deUint32)value);
+	else if (value < 0)
+		return 31 - deClz32(~(deUint32)value);
+	else
+		return -1;
+}
+
+static int findMSB (deUint32 value)
+{
+	if (value > 0)
+		return 31 - deClz32(value);
+	else
+		return -1;
+}
+
+class FindMSBCase : public IntegerFunctionCase
+{
+public:
+	FindMSBCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType)
+		: IntegerFunctionCase(context, getIntegerFuncCaseName(baseType, precision, shaderType).c_str(), "findMSB", shaderType)
+	{
+		const int			vecSize		= glu::getDataTypeScalarSize(baseType);
+		const glu::DataType	intType		= vecSize == 1 ? glu::TYPE_INT : glu::getDataTypeIntVec(vecSize);
+
+		m_spec.inputs.push_back(Symbol("value", glu::VarType(baseType, precision)));
+		m_spec.outputs.push_back(Symbol("msb", glu::VarType(intType, glu::PRECISION_LOWP)));
+		m_spec.source = "msb = findMSB(value);";
+	}
+
+	void getInputValues (int numValues, void* const* values) const
+	{
+		de::Random				rnd			(deStringHash(getName()) ^ 0x742ac4e);
+		const glu::DataType		type		= m_spec.inputs[0].varType.getBasicType();
+		const int				scalarSize	= glu::getDataTypeScalarSize(type);
+		deUint32*				inValue		= (deUint32*)values[0];
+		int						valueNdx	= 0;
+
+		while (valueNdx < numValues)
+		{
+			for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+				inValue[valueNdx*scalarSize + compNdx] = rnd.getUint32();
+
+			valueNdx += 1;
+		}
+	}
+
+	bool compare (const void* const* inputs, const void* const* outputs)
+	{
+		const deUint32			masks[]			= { 0xffu, 0xffffu, 0xffffffffu };
+		const glu::DataType		type			= m_spec.inputs[0].varType.getBasicType();
+		const glu::Precision	precision		= m_spec.inputs[0].varType.getPrecision();
+		const bool				isSigned		= glu::isDataTypeIntOrIVec(type);
+		const int				scalarSize		= glu::getDataTypeScalarSize(type);
+		const deUint32			mask			= masks[precision];
+
+		for (int compNdx = 0; compNdx < scalarSize; compNdx++)
+		{
+			const deUint32	value	= ((const deUint32*)inputs[0])[compNdx];
+			const int		out		= ((const int*)outputs[0])[compNdx];
+			const int		minRef	= isSigned ? findMSB(int(value&mask))	: findMSB(value&mask);
+			const int		maxRef	= isSigned ? findMSB(int(value))		: findMSB(value);
+
+			if (!de::inRange(out, minRef, maxRef))
+			{
+				m_failMsg << "Expected [" << compNdx << "] in range [" << minRef << ", " << maxRef << "]";
+				return false;
+			}
+		}
+
+		return true;
+	}
+};
+
+ShaderIntegerFunctionTests::ShaderIntegerFunctionTests (Context& context)
+	: TestCaseGroup(context, "integer", "Integer function tests")
+{
+}
+
+ShaderIntegerFunctionTests::~ShaderIntegerFunctionTests (void)
+{
+}
+
+template<class TestClass>
+static void addFunctionCases (TestCaseGroup* parent, const char* functionName, bool intTypes, bool uintTypes, bool allPrec, deUint32 shaderBits)
+{
+	tcu::TestCaseGroup* group = new tcu::TestCaseGroup(parent->getTestContext(), functionName, functionName);
+	parent->addChild(group);
+
+	const glu::DataType scalarTypes[] =
+	{
+		glu::TYPE_INT,
+		glu::TYPE_UINT
+	};
+
+	for (int scalarTypeNdx = 0; scalarTypeNdx < DE_LENGTH_OF_ARRAY(scalarTypes); scalarTypeNdx++)
+	{
+		const glu::DataType scalarType = scalarTypes[scalarTypeNdx];
+
+		if ((!intTypes && scalarType == glu::TYPE_INT) || (!uintTypes && scalarType == glu::TYPE_UINT))
+			continue;
+
+		for (int vecSize = 1; vecSize <= 4; vecSize++)
+		{
+			for (int prec = glu::PRECISION_LOWP; prec <= glu::PRECISION_HIGHP; prec++)
+			{
+				if (prec != glu::PRECISION_HIGHP && !allPrec)
+					continue;
+
+				for (int shaderTypeNdx = 0; shaderTypeNdx < glu::SHADERTYPE_LAST; shaderTypeNdx++)
+				{
+					if (shaderBits & (1<<shaderTypeNdx))
+						group->addChild(new TestClass(parent->getContext(), glu::DataType(scalarType + vecSize - 1), glu::Precision(prec), glu::ShaderType(shaderTypeNdx)));
+				}
+			}
+		}
+	}
+}
+
+void ShaderIntegerFunctionTests::init (void)
+{
+	enum
+	{
+		VS = (1<<glu::SHADERTYPE_VERTEX),
+		FS = (1<<glu::SHADERTYPE_FRAGMENT),
+		CS = (1<<glu::SHADERTYPE_COMPUTE),
+		GS = (1<<glu::SHADERTYPE_GEOMETRY),
+		TC = (1<<glu::SHADERTYPE_TESSELLATION_CONTROL),
+		TE = (1<<glu::SHADERTYPE_TESSELLATION_EVALUATION),
+
+		ALL_SHADERS = VS|TC|TE|GS|FS|CS
+	};
+
+	//																		Int?	Uint?	AllPrec?	Shaders
+	addFunctionCases<UaddCarryCase>				(this,	"uaddcarry",		false,	true,	true,		ALL_SHADERS);
+	addFunctionCases<UsubBorrowCase>			(this,	"usubborrow",		false,	true,	true,		ALL_SHADERS);
+	addFunctionCases<UmulExtendedCase>			(this,	"umulextended",		false,	true,	false,		ALL_SHADERS);
+	addFunctionCases<ImulExtendedCase>			(this,	"imulextended",		true,	false,	false,		ALL_SHADERS);
+	addFunctionCases<BitfieldExtractCase>		(this,	"bitfieldextract",	true,	true,	true,		ALL_SHADERS);
+	addFunctionCases<BitfieldInsertCase>		(this,	"bitfieldinsert",	true,	true,	true,		ALL_SHADERS);
+	addFunctionCases<BitfieldReverseCase>		(this,	"bitfieldreverse",	true,	true,	true,		ALL_SHADERS);
+	addFunctionCases<BitCountCase>				(this,	"bitcount",			true,	true,	true,		ALL_SHADERS);
+	addFunctionCases<FindLSBCase>				(this,	"findlsb",			true,	true,	true,		ALL_SHADERS);
+	addFunctionCases<FindMSBCase>				(this,	"findmsb",			true,	true,	true,		ALL_SHADERS);
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderIntegerFunctionTests.hpp b/modules/gles31/functional/es31fShaderIntegerFunctionTests.hpp
new file mode 100644
index 0000000..839b4fe
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderIntegerFunctionTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES31FSHADERINTEGERFUNCTIONTESTS_HPP
+#define _ES31FSHADERINTEGERFUNCTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Integer built-in function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderIntegerFunctionTests : public TestCaseGroup
+{
+public:
+									ShaderIntegerFunctionTests		(Context& context);
+	virtual							~ShaderIntegerFunctionTests		(void);
+
+	virtual void					init							(void);
+
+private:
+									ShaderIntegerFunctionTests		(const ShaderIntegerFunctionTests&);		// not allowed!
+	ShaderIntegerFunctionTests&		operator=						(const ShaderIntegerFunctionTests&);		// not allowed!
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERINTEGERFUNCTIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderMultisampleInterpolationStateQueryTests.cpp b/modules/gles31/functional/es31fShaderMultisampleInterpolationStateQueryTests.cpp
new file mode 100644
index 0000000..2d19aa6
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderMultisampleInterpolationStateQueryTests.cpp
@@ -0,0 +1,391 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample interpolation state query tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderMultisampleInterpolationStateQueryTests.hpp"
+#include "tcuTestLog.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+enum VerifierType
+{
+	VERIFIER_GET_BOOLEAN = 0,
+	VERIFIER_GET_INTEGER,
+	VERIFIER_GET_FLOAT,
+	VERIFIER_GET_INTEGER64,
+};
+
+static void verifyGreaterOrEqual (VerifierType verifier, glw::GLenum target, float minValue, Context& context)
+{
+	glu::CallLogWrapper gl(context.getRenderContext().getFunctions(), context.getTestContext().getLog());
+
+	gl.enableLogging(true);
+
+	switch (verifier)
+	{
+		case VERIFIER_GET_BOOLEAN:
+		{
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLboolean>	value;
+
+			gl.glGetBooleanv(target, &value);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "getBoolean");
+
+			if (!value.verifyValidity(context.getTestContext()))
+				return;
+			if (value != GL_TRUE && value != GL_FALSE)
+			{
+				context.getTestContext().getLog() << tcu::TestLog::Message << "Returned value is not a boolean"<< tcu::TestLog::EndMessage;
+				context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean");
+				return;
+			}
+
+			if (value > 0.0f && value == GL_FALSE)
+			{
+				context.getTestContext().getLog() << tcu::TestLog::Message << "Expected GL_TRUE, got GL_FALSE" << tcu::TestLog::EndMessage;
+				context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+			}
+			break;
+		}
+
+		case VERIFIER_GET_INTEGER:
+		{
+			const glw::GLint											refValue = (glw::GLint)deFloatFloor(minValue);
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint>	value;
+
+			gl.glGetIntegerv(target, &value);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "getInteger");
+
+			if (!value.verifyValidity(context.getTestContext()))
+				return;
+
+			context.getTestContext().getLog() << tcu::TestLog::Message << "Expecting greater or equal to " << refValue << ", got " << value << tcu::TestLog::EndMessage;
+			if (value < refValue)
+			{
+				context.getTestContext().getLog() << tcu::TestLog::Message << "Value not in valid range" << tcu::TestLog::EndMessage;
+				context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+			}
+			break;
+		}
+
+		case VERIFIER_GET_FLOAT:
+		{
+			const float														refValue = minValue;
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLfloat>	value;
+
+			gl.glGetFloatv(target, &value);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "getFloat");
+
+			if (!value.verifyValidity(context.getTestContext()))
+				return;
+
+			context.getTestContext().getLog() << tcu::TestLog::Message << "Expecting greater or equal to " << refValue << ", got " << value << tcu::TestLog::EndMessage;
+			if (value < refValue)
+			{
+				context.getTestContext().getLog() << tcu::TestLog::Message << "Value not in valid range" << tcu::TestLog::EndMessage;
+				context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+			}
+			break;
+		}
+
+		case VERIFIER_GET_INTEGER64:
+		{
+			const glw::GLint64												refValue = (glw::GLint64)deFloatFloor(minValue);
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint64>	value;
+
+			gl.glGetInteger64v(target, &value);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "getInteger64");
+
+			if (!value.verifyValidity(context.getTestContext()))
+				return;
+
+			context.getTestContext().getLog() << tcu::TestLog::Message << "Expecting greater or equal to " << refValue << ", got " << value << tcu::TestLog::EndMessage;
+			if (value < refValue)
+			{
+				context.getTestContext().getLog() << tcu::TestLog::Message << "Value not in valid range" << tcu::TestLog::EndMessage;
+				context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+			}
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+static void verifyLessOrEqual (VerifierType verifier, glw::GLenum target, float minValue, Context& context)
+{
+	glu::CallLogWrapper gl(context.getRenderContext().getFunctions(), context.getTestContext().getLog());
+
+	gl.enableLogging(true);
+
+	switch (verifier)
+	{
+		case VERIFIER_GET_BOOLEAN:
+		{
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLboolean>	value;
+
+			gl.glGetBooleanv(target, &value);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "getBoolean");
+
+			if (!value.verifyValidity(context.getTestContext()))
+				return;
+			if (value != GL_TRUE && value != GL_FALSE)
+			{
+				context.getTestContext().getLog() << tcu::TestLog::Message << "Returned value is not a boolean"<< tcu::TestLog::EndMessage;
+				context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean");
+				return;
+			}
+
+			if (value < 0.0f && value == GL_FALSE)
+			{
+				context.getTestContext().getLog() << tcu::TestLog::Message << "Expected GL_TRUE, got GL_FALSE" << tcu::TestLog::EndMessage;
+				context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+			}
+			break;
+		}
+
+		case VERIFIER_GET_INTEGER:
+		{
+			const glw::GLint											refValue = (glw::GLint)deFloatCeil(minValue);
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint>	value;
+
+			gl.glGetIntegerv(target, &value);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "getInteger");
+
+			if (!value.verifyValidity(context.getTestContext()))
+				return;
+
+			context.getTestContext().getLog() << tcu::TestLog::Message << "Expecting less or equal to " << refValue << ", got " << value << tcu::TestLog::EndMessage;
+			if (value > refValue)
+			{
+				context.getTestContext().getLog() << tcu::TestLog::Message << "Value not in valid range" << tcu::TestLog::EndMessage;
+				context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+			}
+			break;
+		}
+
+		case VERIFIER_GET_FLOAT:
+		{
+			const float														refValue = minValue;
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLfloat>	value;
+
+			gl.glGetFloatv(target, &value);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "getFloat");
+
+			if (!value.verifyValidity(context.getTestContext()))
+				return;
+
+			context.getTestContext().getLog() << tcu::TestLog::Message << "Expecting greater or equal to " << refValue << ", got " << value << tcu::TestLog::EndMessage;
+			if (value > refValue)
+			{
+				context.getTestContext().getLog() << tcu::TestLog::Message << "Value not in valid range" << tcu::TestLog::EndMessage;
+				context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+			}
+			break;
+		}
+
+		case VERIFIER_GET_INTEGER64:
+		{
+			const glw::GLint64												refValue = (glw::GLint64)deFloatCeil(minValue);
+			gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint64>	value;
+
+			gl.glGetInteger64v(target, &value);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "getInteger64");
+
+			if (!value.verifyValidity(context.getTestContext()))
+				return;
+
+			context.getTestContext().getLog() << tcu::TestLog::Message << "Expecting greater or equal to " << refValue << ", got " << value << tcu::TestLog::EndMessage;
+			if (value > refValue)
+			{
+				context.getTestContext().getLog() << tcu::TestLog::Message << "Value not in valid range" << tcu::TestLog::EndMessage;
+				context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+			}
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+class InterpolationOffsetCase : public TestCase
+{
+public:
+	enum TestType
+	{
+		TEST_MIN_OFFSET = 0,
+		TEST_MAX_OFFSET,
+
+		TEST_LAST
+	};
+
+						InterpolationOffsetCase		(Context& context, const char* name, const char* desc, VerifierType verifier, TestType testType);
+						~InterpolationOffsetCase	(void);
+
+	void				init						(void);
+	IterateResult		iterate						(void);
+
+private:
+	const VerifierType	m_verifier;
+	const TestType		m_testType;
+};
+
+InterpolationOffsetCase::InterpolationOffsetCase (Context& context, const char* name, const char* desc, VerifierType verifier, TestType testType)
+	: TestCase		(context, name, desc)
+	, m_verifier	(verifier)
+	, m_testType	(testType)
+{
+	DE_ASSERT(m_testType < TEST_LAST);
+}
+
+InterpolationOffsetCase::~InterpolationOffsetCase (void)
+{
+}
+
+void InterpolationOffsetCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
+}
+
+InterpolationOffsetCase::IterateResult InterpolationOffsetCase::iterate (void)
+{
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	if (m_testType == TEST_MAX_OFFSET)
+		verifyGreaterOrEqual(m_verifier, GL_MAX_FRAGMENT_INTERPOLATION_OFFSET, 0.5, m_context);
+	else if (m_testType == TEST_MIN_OFFSET)
+		verifyLessOrEqual(m_verifier, GL_MIN_FRAGMENT_INTERPOLATION_OFFSET, -0.5, m_context);
+	else
+		DE_ASSERT(false);
+
+	return STOP;
+}
+
+class FragmentInterpolationOffsetBitsCase : public TestCase
+{
+public:
+						FragmentInterpolationOffsetBitsCase		(Context& context, const char* name, const char* desc, VerifierType verifier);
+						~FragmentInterpolationOffsetBitsCase	(void);
+
+	void				init									(void);
+	IterateResult		iterate									(void);
+
+private:
+	const VerifierType	m_verifier;
+};
+
+FragmentInterpolationOffsetBitsCase::FragmentInterpolationOffsetBitsCase (Context& context, const char* name, const char* desc, VerifierType verifier)
+	: TestCase		(context, name, desc)
+	, m_verifier	(verifier)
+{
+}
+
+FragmentInterpolationOffsetBitsCase::~FragmentInterpolationOffsetBitsCase (void)
+{
+}
+
+void FragmentInterpolationOffsetBitsCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
+}
+
+FragmentInterpolationOffsetBitsCase::IterateResult FragmentInterpolationOffsetBitsCase::iterate (void)
+{
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	verifyGreaterOrEqual(m_verifier, GL_FRAGMENT_INTERPOLATION_OFFSET_BITS, 4.0, m_context);
+	return STOP;
+}
+
+} // anonymous
+
+ShaderMultisampleInterpolationStateQueryTests::ShaderMultisampleInterpolationStateQueryTests (Context& context)
+	: TestCaseGroup(context, "multisample_interpolation", "Test multisample interpolation states")
+{
+}
+
+ShaderMultisampleInterpolationStateQueryTests::~ShaderMultisampleInterpolationStateQueryTests (void)
+{
+}
+
+void ShaderMultisampleInterpolationStateQueryTests::init (void)
+{
+	static const struct Verifier
+	{
+		VerifierType	verifier;
+		const char*		name;
+		const char*		desc;
+	} verifiers[] =
+	{
+		{ VERIFIER_GET_BOOLEAN,		"get_boolean",		"Test using getBoolean"		},
+		{ VERIFIER_GET_INTEGER,		"get_integer",		"Test using getInteger"		},
+		{ VERIFIER_GET_FLOAT,		"get_float",		"Test using getFloat"		},
+		{ VERIFIER_GET_INTEGER64,	"get_integer64",	"Test using getInteger64"	},
+	};
+
+	// .min_fragment_interpolation_offset
+	{
+		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "min_fragment_interpolation_offset", "Test MIN_FRAGMENT_INTERPOLATION_OFFSET");
+		addChild(group);
+
+		for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)
+			group->addChild(new InterpolationOffsetCase(m_context, verifiers[verifierNdx].name, verifiers[verifierNdx].desc, verifiers[verifierNdx].verifier, InterpolationOffsetCase::TEST_MIN_OFFSET));
+	}
+
+	// .max_fragment_interpolation_offset
+	{
+		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "max_fragment_interpolation_offset", "Test MAX_FRAGMENT_INTERPOLATION_OFFSET");
+		addChild(group);
+
+		for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)
+			group->addChild(new InterpolationOffsetCase(m_context, verifiers[verifierNdx].name, verifiers[verifierNdx].desc, verifiers[verifierNdx].verifier, InterpolationOffsetCase::TEST_MAX_OFFSET));
+	}
+
+	// .fragment_interpolation_offset_bits
+	{
+		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "fragment_interpolation_offset_bits", "Test FRAGMENT_INTERPOLATION_OFFSET_BITS");
+		addChild(group);
+
+		for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)
+			group->addChild(new FragmentInterpolationOffsetBitsCase(m_context, verifiers[verifierNdx].name, verifiers[verifierNdx].desc, verifiers[verifierNdx].verifier));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderMultisampleInterpolationStateQueryTests.hpp b/modules/gles31/functional/es31fShaderMultisampleInterpolationStateQueryTests.hpp
new file mode 100644
index 0000000..a7b2b8b
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderMultisampleInterpolationStateQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSHADERMULTISAMPLEINTERPOLATIONSTATEQUERYTESTS_HPP
+#define _ES31FSHADERMULTISAMPLEINTERPOLATIONSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample interpolation state query tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderMultisampleInterpolationStateQueryTests : public TestCaseGroup
+{
+public:
+													ShaderMultisampleInterpolationStateQueryTests	(Context& context);
+													~ShaderMultisampleInterpolationStateQueryTests	(void);
+
+	void											init											(void);
+
+private:
+													ShaderMultisampleInterpolationStateQueryTests	(ShaderMultisampleInterpolationStateQueryTests& other);
+	ShaderMultisampleInterpolationStateQueryTests&	operator=										(const ShaderMultisampleInterpolationStateQueryTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERMULTISAMPLEINTERPOLATIONSTATEQUERYTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderMultisampleInterpolationTests.cpp b/modules/gles31/functional/es31fShaderMultisampleInterpolationTests.cpp
new file mode 100644
index 0000000..7b8d53a
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderMultisampleInterpolationTests.cpp
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample interpolation tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderMultisampleInterpolationTests.hpp"
+#include "es31fMultisampleShaderRenderCase.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluContextInfo.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+
+#include <map>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+
+static bool verifyGreenImage (const tcu::Surface& image, tcu::TestLog& log)
+{
+	bool error = false;
+
+	log << tcu::TestLog::Message << "Verifying result image, expecting green." << tcu::TestLog::EndMessage;
+
+	// all pixels must be green
+
+	for (int y = 0; y < image.getHeight(); ++y)
+	for (int x = 0; x < image.getWidth(); ++x)
+	{
+		const tcu::RGBA color			= image.getPixel(x, y);
+		const int		greenThreshold	= 8;
+
+		if (color.getRed() > 0 || color.getGreen() < 255-greenThreshold || color.getBlue() > 0)
+			error = true;
+	}
+
+	if (error)
+		log	<< tcu::TestLog::Image("ResultImage", "Result Image", image.getAccess())
+			<< tcu::TestLog::Message
+			<< "Image verification failed."
+			<< tcu::TestLog::EndMessage;
+	else
+		log	<< tcu::TestLog::Image("ResultImage", "Result Image", image.getAccess())
+			<< tcu::TestLog::Message
+			<< "Image verification passed."
+			<< tcu::TestLog::EndMessage;
+
+	return !error;
+}
+
+class MultisampleShadeCountRenderCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
+{
+public:
+						MultisampleShadeCountRenderCase		(Context& context, const char* name, const char* description, int numSamples, RenderTarget target);
+	virtual				~MultisampleShadeCountRenderCase	(void);
+
+	void				init								(void);
+
+private:
+	enum
+	{
+		RENDER_SIZE = 128
+	};
+
+	virtual std::string	getIterationDescription				(int iteration) const;
+	bool				verifyImage							(const tcu::Surface& resultImage);
+};
+
+MultisampleShadeCountRenderCase::MultisampleShadeCountRenderCase (Context& context, const char* name, const char* description, int numSamples, RenderTarget target)
+	: MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, description, numSamples, target, RENDER_SIZE, MultisampleShaderRenderUtil::MultisampleRenderCase::FLAG_PER_ITERATION_SHADER)
+{
+	m_numIterations = -1; // must be set by deriving class
+}
+
+MultisampleShadeCountRenderCase::~MultisampleShadeCountRenderCase (void)
+{
+}
+
+void MultisampleShadeCountRenderCase::init (void)
+{
+	// requirements
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
+
+	MultisampleShaderRenderUtil::MultisampleRenderCase::init();
+}
+
+std::string	MultisampleShadeCountRenderCase::getIterationDescription (int iteration) const
+{
+	// must be overriden
+	DE_UNREF(iteration);
+	DE_ASSERT(false);
+	return "";
+}
+
+bool MultisampleShadeCountRenderCase::verifyImage (const tcu::Surface& resultImage)
+{
+	const bool				isSingleSampleTarget	= (m_renderTarget != TARGET_DEFAULT && m_numRequestedSamples == 0) || (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() <= 1);
+	const int				numShadesRequired		= (isSingleSampleTarget) ? (2) : (m_numTargetSamples + 1);
+	const int				rareThreshold			= 100;
+	int						rareCount				= 0;
+	std::map<deUint32, int>	shadeFrequency;
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Image("ResultImage", "Result Image", resultImage.getAccess())
+		<< tcu::TestLog::Message
+		<< "Verifying image has (at least) " << numShadesRequired << " different shades.\n"
+		<< "Excluding pixels with no full coverage (pixels on the shared edge of the triangle pair)."
+		<< tcu::TestLog::EndMessage;
+
+	for (int y = 0; y < RENDER_SIZE; ++y)
+	for (int x = 0; x < RENDER_SIZE; ++x)
+	{
+		const tcu::RGBA	color	= resultImage.getPixel(x, y);
+		const deUint32	packed	= ((deUint32)color.getRed()) + ((deUint32)color.getGreen() << 8) + ((deUint32)color.getGreen() << 16);
+
+		// on the triangle edge, skip
+		if (x == y)
+			continue;
+
+		if (shadeFrequency.find(packed) == shadeFrequency.end())
+			shadeFrequency[packed] = 1;
+		else
+			shadeFrequency[packed] = shadeFrequency[packed] + 1;
+	}
+
+	for (std::map<deUint32, int>::const_iterator it = shadeFrequency.begin(); it != shadeFrequency.end(); ++it)
+		if (it->second < rareThreshold)
+			rareCount++;
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Found " << (int)shadeFrequency.size() << " different shades.\n"
+		<< "\tRare (less than " << rareThreshold << " pixels): " << rareCount << "\n"
+		<< "\tCommon: " << (int)shadeFrequency.size() - rareCount << "\n"
+		<< tcu::TestLog::EndMessage;
+
+	if ((int)shadeFrequency.size() < numShadesRequired)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage;
+		return false;
+	}
+	return true;
+}
+
+class SampleQualifierRenderCase : public MultisampleShadeCountRenderCase
+{
+public:
+				SampleQualifierRenderCase	(Context& context, const char* name, const char* description, int numSamples, RenderTarget target);
+				~SampleQualifierRenderCase	(void);
+
+	void		init						(void);
+
+private:
+	std::string	genVertexSource				(int numTargetSamples) const;
+	std::string	genFragmentSource			(int numTargetSamples) const;
+	std::string	getIterationDescription		(int iteration) const;
+};
+
+SampleQualifierRenderCase::SampleQualifierRenderCase (Context& context, const char* name, const char* description, int numSamples, RenderTarget target)
+	: MultisampleShadeCountRenderCase(context, name, description, numSamples, target)
+{
+	m_numIterations = 6; // float, vec2, .3, .4, array, struct
+}
+
+SampleQualifierRenderCase::~SampleQualifierRenderCase (void)
+{
+}
+
+void SampleQualifierRenderCase::init (void)
+{
+	const bool isSingleSampleTarget = (m_renderTarget != TARGET_DEFAULT && m_numRequestedSamples == 0) || (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() <= 1);
+
+	// test purpose and expectations
+	if (isSingleSampleTarget)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Verifying that a sample-qualified varying is given different values for different samples.\n"
+			<< "	Render high-frequency function, map result to black/white.\n"
+			<< "	=> Resulting image image should contain both black and white pixels.\n"
+			<< tcu::TestLog::EndMessage;
+	}
+	else
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Verifying that a sample-qualified varying is given different values for different samples.\n"
+			<< "	Render high-frequency function, map result to black/white.\n"
+			<< "	=> Resulting image should contain n+1 shades of gray, n = sample count.\n"
+			<< tcu::TestLog::EndMessage;
+	}
+
+	MultisampleShadeCountRenderCase::init();
+}
+
+std::string	SampleQualifierRenderCase::genVertexSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_shader_multisample_interpolation : require\n"
+			"in highp vec4 a_position;\n";
+
+	if (m_iteration == 0)
+		buf << "sample out highp float v_input;\n";
+	else if (m_iteration == 1)
+		buf << "sample out highp vec2 v_input;\n";
+	else if (m_iteration == 2)
+		buf << "sample out highp vec3 v_input;\n";
+	else if (m_iteration == 3)
+		buf << "sample out highp vec4 v_input;\n";
+	else if (m_iteration == 4)
+		buf << "sample out highp float[2] v_input;\n";
+	else if (m_iteration == 5)
+		buf << "struct VaryingStruct { highp float a; highp float b; };\n"
+			   "sample out VaryingStruct v_input;\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n";
+
+	if (m_iteration == 0)
+		buf << "	v_input = a_position.x + exp(a_position.y) + step(0.9, a_position.x)*step(a_position.y, -0.9)*8.0;\n";
+	else if (m_iteration == 1)
+		buf << "	v_input = a_position.xy;\n";
+	else if (m_iteration == 2)
+		buf << "	v_input = vec3(a_position.xy, a_position.x * 2.0 - a_position.y);\n";
+	else if (m_iteration == 3)
+		buf << "	v_input = vec4(a_position.xy, a_position.x * 2.0 - a_position.y, a_position.x*a_position.y);\n";
+	else if (m_iteration == 4)
+		buf << "	v_input[0] = a_position.x;\n"
+			   "	v_input[1] = a_position.y;\n";
+	else if (m_iteration == 5)
+		buf << "	v_input.a = a_position.x;\n"
+			   "	v_input.b = a_position.y;\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"}";
+
+	return buf.str();
+}
+
+std::string	SampleQualifierRenderCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_shader_multisample_interpolation : require\n";
+
+	if (m_iteration == 0)
+		buf << "sample in highp float v_input;\n";
+	else if (m_iteration == 1)
+		buf << "sample in highp vec2 v_input;\n";
+	else if (m_iteration == 2)
+		buf << "sample in highp vec3 v_input;\n";
+	else if (m_iteration == 3)
+		buf << "sample in highp vec4 v_input;\n";
+	else if (m_iteration == 4)
+		buf << "sample in highp float[2] v_input;\n";
+	else if (m_iteration == 5)
+		buf << "struct VaryingStruct { highp float a; highp float b; };\n"
+			   "sample in VaryingStruct v_input;\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n";
+
+	if (m_iteration == 0)
+		buf << "	highp float field = exp(v_input) + v_input*v_input;\n";
+	else if (m_iteration == 1)
+		buf << "	highp float field = dot(v_input.xy, v_input.xy) + dot(21.0 * v_input.xx, sin(3.1 * v_input.xy));\n";
+	else if (m_iteration == 2)
+		buf << "	highp float field = dot(v_input.xy, v_input.xy) + dot(21.0 * v_input.zx, sin(3.1 * v_input.zy));\n";
+	else if (m_iteration == 3)
+		buf << "	highp float field = dot(v_input.xy, v_input.zw) + dot(21.0 * v_input.zy, sin(3.1 * v_input.zw));\n";
+	else if (m_iteration == 4)
+		buf << "	highp float field = dot(vec2(v_input[0], v_input[1]), vec2(v_input[0], v_input[1])) + dot(21.0 * vec2(v_input[0]), sin(3.1 * vec2(v_input[0], v_input[1])));\n";
+	else if (m_iteration == 5)
+		buf << "	highp float field = dot(vec2(v_input.a, v_input.b), vec2(v_input.a, v_input.b)) + dot(21.0 * vec2(v_input.a), sin(3.1 * vec2(v_input.a, v_input.b)));\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"	fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+			"\n"
+			"	if (fract(field) > 0.5)\n"
+			"		fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n"
+			"}";
+
+	return buf.str();
+}
+
+std::string	SampleQualifierRenderCase::getIterationDescription (int iteration) const
+{
+	if (iteration == 0)
+		return "Test with float varying";
+	else if (iteration == 1)
+		return "Test with vec2 varying";
+	else if (iteration == 2)
+		return "Test with vec3 varying";
+	else if (iteration == 3)
+		return "Test with vec4 varying";
+	else if (iteration == 4)
+		return "Test with array varying";
+	else if (iteration == 5)
+		return "Test with struct varying";
+
+	DE_ASSERT(false);
+	return "";
+}
+
+class InterpolateAtSampleRenderCase : public MultisampleShadeCountRenderCase
+{
+public:
+	enum IndexingMode
+	{
+		INDEXING_STATIC,
+		INDEXING_DYNAMIC,
+
+		INDEXING_LAST
+	};
+						InterpolateAtSampleRenderCase	(Context& context, const char* name, const char* description, int numSamples, RenderTarget target, IndexingMode mode);
+						~InterpolateAtSampleRenderCase	(void);
+
+	void				init							(void);
+	void				preDraw							(void);
+
+private:
+	std::string			genVertexSource					(int numTargetSamples) const;
+	std::string			genFragmentSource				(int numTargetSamples) const;
+	std::string			getIterationDescription			(int iteration) const;
+
+	const IndexingMode	m_indexMode;
+};
+
+InterpolateAtSampleRenderCase::InterpolateAtSampleRenderCase (Context& context, const char* name, const char* description, int numSamples, RenderTarget target, IndexingMode mode)
+	: MultisampleShadeCountRenderCase	(context, name, description, numSamples, target)
+	, m_indexMode						(mode)
+{
+	DE_ASSERT(mode < INDEXING_LAST);
+
+	m_numIterations = 5; // float, vec2, .3, .4, array
+}
+
+InterpolateAtSampleRenderCase::~InterpolateAtSampleRenderCase (void)
+{
+}
+
+void InterpolateAtSampleRenderCase::init (void)
+{
+	const bool isSingleSampleTarget = (m_renderTarget != TARGET_DEFAULT && m_numRequestedSamples == 0) || (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() <= 1);
+
+	// test purpose and expectations
+	if (isSingleSampleTarget)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Verifying that a interpolateAtSample returns different values for different samples.\n"
+			<< "	Render high-frequency function, map result to black/white.\n"
+			<< "	=> Resulting image image should contain both black and white pixels.\n"
+			<< tcu::TestLog::EndMessage;
+	}
+	else
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Verifying that a interpolateAtSample returns different values for different samples.\n"
+			<< "	Render high-frequency function, map result to black/white.\n"
+			<< "	=> Resulting image should contain n+1 shades of gray, n = sample count.\n"
+			<< tcu::TestLog::EndMessage;
+	}
+
+	MultisampleShadeCountRenderCase::init();
+}
+
+void InterpolateAtSampleRenderCase::preDraw (void)
+{
+	if (m_indexMode == INDEXING_DYNAMIC)
+	{
+		const deInt32			range		= m_numTargetSamples;
+		const deInt32			offset		= 1;
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+		const deInt32			offsetLoc	= gl.getUniformLocation(m_program->getProgram(), "u_offset");
+		const deInt32			rangeLoc	= gl.getUniformLocation(m_program->getProgram(), "u_range");
+
+		if (offsetLoc == -1)
+			throw tcu::TestError("Location of u_offset was -1");
+		if (rangeLoc == -1)
+			throw tcu::TestError("Location of u_range was -1");
+
+		gl.uniform1i(offsetLoc, 0);
+		gl.uniform1i(rangeLoc, m_numTargetSamples);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set uniforms");
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Set u_offset = " << offset << "\n"
+			<< "Set u_range = " << range
+			<< tcu::TestLog::EndMessage;
+	}
+}
+
+std::string InterpolateAtSampleRenderCase::genVertexSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"in highp vec4 a_position;\n";
+
+	if (m_iteration == 0)
+		buf << "out highp float v_input;\n";
+	else if (m_iteration == 1)
+		buf << "out highp vec2 v_input;\n";
+	else if (m_iteration == 2)
+		buf << "out highp vec3 v_input;\n";
+	else if (m_iteration == 3)
+		buf << "out highp vec4 v_input;\n";
+	else if (m_iteration == 4)
+		buf << "out highp vec2[2] v_input;\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n";
+
+	if (m_iteration == 0)
+		buf << "	v_input = a_position.x + exp(a_position.y) + step(0.9, a_position.x)*step(a_position.y, -0.9)*8.0;\n";
+	else if (m_iteration == 1)
+		buf << "	v_input = a_position.xy;\n";
+	else if (m_iteration == 2)
+		buf << "	v_input = vec3(a_position.xy, a_position.x * 2.0 - a_position.y);\n";
+	else if (m_iteration == 3)
+		buf << "	v_input = vec4(a_position.xy, a_position.x * 2.0 - a_position.y, a_position.x*a_position.y);\n";
+	else if (m_iteration == 4)
+		buf << "	v_input[0] = a_position.yx + vec2(0.5, 0.5);\n"
+			   "	v_input[1] = a_position.xy;\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"}";
+
+	return buf.str();
+}
+
+std::string InterpolateAtSampleRenderCase::genFragmentSource (int numTargetSamples) const
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_shader_multisample_interpolation : require\n";
+
+	if (m_iteration == 0)
+		buf << "in highp float v_input;\n";
+	else if (m_iteration == 1)
+		buf << "in highp vec2 v_input;\n";
+	else if (m_iteration == 2)
+		buf << "in highp vec3 v_input;\n";
+	else if (m_iteration == 3)
+		buf << "in highp vec4 v_input;\n";
+	else if (m_iteration == 4)
+		buf << "in highp vec2[2] v_input;\n";
+	else
+		DE_ASSERT(false);
+
+	buf << "layout(location = 0) out mediump vec4 fragColor;\n";
+
+	if (m_indexMode == INDEXING_DYNAMIC)
+		buf <<	"uniform highp int u_offset;\n"
+				"uniform highp int u_range;\n";
+
+	buf <<	"void main (void)\n"
+			"{\n"
+			"	mediump int coverage = 0;\n"
+			"\n";
+
+	if (m_indexMode == INDEXING_STATIC)
+	{
+		for (int ndx = 0; ndx < numTargetSamples; ++ndx)
+		{
+			if (m_iteration == 0)
+				buf <<	"	highp float sampleInput" << ndx << " = interpolateAtSample(v_input, " << ndx << ");\n";
+			else if (m_iteration == 1)
+				buf <<	"	highp vec2 sampleInput" << ndx << " = interpolateAtSample(v_input, " << ndx << ");\n";
+			else if (m_iteration == 2)
+				buf <<	"	highp vec3 sampleInput" << ndx << " = interpolateAtSample(v_input, " << ndx << ");\n";
+			else if (m_iteration == 3)
+				buf <<	"	highp vec4 sampleInput" << ndx << " = interpolateAtSample(v_input, " << ndx << ");\n";
+			else if (m_iteration == 4)
+				buf <<	"	highp vec2 sampleInput" << ndx << " = interpolateAtSample(v_input[1], " << ndx << ");\n";
+			else
+				DE_ASSERT(false);
+		}
+		buf <<	"\n";
+
+		for (int ndx = 0; ndx < numTargetSamples; ++ndx)
+		{
+			if (m_iteration == 0)
+				buf << "	highp float field" << ndx << " = exp(sampleInput" << ndx << ") + sampleInput" << ndx << "*sampleInput" << ndx << ";\n";
+			else if (m_iteration == 1 || m_iteration == 4)
+				buf << "	highp float field" << ndx << " = dot(sampleInput" << ndx << ", sampleInput" << ndx << ") + dot(21.0 * sampleInput" << ndx << ".xx, sin(3.1 * sampleInput" << ndx << "));\n";
+			else if (m_iteration == 2)
+				buf << "	highp float field" << ndx << " = dot(sampleInput" << ndx << ".xy, sampleInput" << ndx << ".xy) + dot(21.0 * sampleInput" << ndx << ".zx, sin(3.1 * sampleInput" << ndx << ".zy));\n";
+			else if (m_iteration == 3)
+				buf << "	highp float field" << ndx << " = dot(sampleInput" << ndx << ".xy, sampleInput" << ndx << ".zw) + dot(21.0 * sampleInput" << ndx << ".zy, sin(3.1 * sampleInput" << ndx << ".zw));\n";
+			else
+				DE_ASSERT(false);
+		}
+		buf <<	"\n";
+
+		for (int ndx = 0; ndx < numTargetSamples; ++ndx)
+			buf <<	"	if (fract(field" << ndx << ") <= 0.5)\n"
+					"		++coverage;\n";
+	}
+	else if (m_indexMode == INDEXING_DYNAMIC)
+	{
+		buf <<	"	for (int ndx = 0; ndx < " << numTargetSamples << "; ++ndx)\n"
+				"	{\n";
+
+		if (m_iteration == 0)
+			buf <<	"		highp float sampleInput = interpolateAtSample(v_input, (u_offset + ndx) % u_range);\n";
+		else if (m_iteration == 1)
+			buf <<	"		highp vec2 sampleInput = interpolateAtSample(v_input, (u_offset + ndx) % u_range);\n";
+		else if (m_iteration == 2)
+			buf <<	"		highp vec3 sampleInput = interpolateAtSample(v_input, (u_offset + ndx) % u_range);\n";
+		else if (m_iteration == 3)
+			buf <<	"		highp vec4 sampleInput = interpolateAtSample(v_input, (u_offset + ndx) % u_range);\n";
+		else if (m_iteration == 4)
+			buf <<	"		highp vec2 sampleInput = interpolateAtSample(v_input[1], (u_offset + ndx) % u_range);\n";
+		else
+			DE_ASSERT(false);
+
+		if (m_iteration == 0)
+			buf << "		highp float field = exp(sampleInput) + sampleInput*sampleInput;\n";
+		else if (m_iteration == 1 || m_iteration == 4)
+			buf << "		highp float field = dot(sampleInput, sampleInput) + dot(21.0 * sampleInput.xx, sin(3.1 * sampleInput));\n";
+		else if (m_iteration == 2)
+			buf << "		highp float field = dot(sampleInput.xy, sampleInput.xy) + dot(21.0 * sampleInput.zx, sin(3.1 * sampleInput.zy));\n";
+		else if (m_iteration == 3)
+			buf << "		highp float field = dot(sampleInput.xy, sampleInput.zw) + dot(21.0 * sampleInput.zy, sin(3.1 * sampleInput.zw));\n";
+		else
+			DE_ASSERT(false);
+
+		buf <<	"		if (fract(field) <= 0.5)\n"
+				"			++coverage;\n"
+				"	}\n";
+	}
+
+	buf <<	"	fragColor = vec4(vec3(float(coverage) / float(" << numTargetSamples << ")), 1.0);\n"
+			"}";
+
+	return buf.str();
+}
+
+std::string InterpolateAtSampleRenderCase::getIterationDescription (int iteration) const
+{
+	if (iteration == 0)
+		return "Test with float varying";
+	else if (iteration < 4)
+		return "Test with vec" + de::toString(iteration+1) + " varying";
+	else if (iteration == 4)
+		return "Test with array varying";
+
+	DE_ASSERT(false);
+	return "";
+}
+
+class SingleSampleInterpolateAtSampleCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
+{
+public:
+	enum SampleCase
+	{
+		SAMPLE_0 = 0,
+		SAMPLE_N,
+
+		SAMPLE_LAST
+	};
+
+						SingleSampleInterpolateAtSampleCase		(Context& context, const char* name, const char* description, int numSamples, RenderTarget target, SampleCase sampleCase);
+	virtual				~SingleSampleInterpolateAtSampleCase	(void);
+
+	void				init									(void);
+
+private:
+	enum
+	{
+		RENDER_SIZE = 32
+	};
+
+	std::string			genVertexSource							(int numTargetSamples) const;
+	std::string			genFragmentSource						(int numTargetSamples) const;
+	bool				verifyImage								(const tcu::Surface& resultImage);
+
+	const SampleCase	m_sampleCase;
+};
+
+SingleSampleInterpolateAtSampleCase::SingleSampleInterpolateAtSampleCase (Context& context, const char* name, const char* description, int numSamples, RenderTarget target, SampleCase sampleCase)
+	: MultisampleShaderRenderUtil::MultisampleRenderCase	(context, name, description, numSamples, target, RENDER_SIZE)
+	, m_sampleCase											(sampleCase)
+{
+	DE_ASSERT(numSamples == 0);
+	DE_ASSERT(sampleCase < SAMPLE_LAST);
+}
+
+SingleSampleInterpolateAtSampleCase::~SingleSampleInterpolateAtSampleCase (void)
+{
+}
+
+void SingleSampleInterpolateAtSampleCase::init (void)
+{
+	// requirements
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
+	if (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() > 1)
+		throw tcu::NotSupportedError("Non-multisample framebuffer required");
+
+	// test purpose and expectations
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying that using interpolateAtSample with multisample buffers not available returns sample evaluated at the center of the pixel.\n"
+		<< "	Interpolate varying containing screen space location.\n"
+		<< "	=> fract(screen space location) should be (about) (0.5, 0.5)\n"
+		<< tcu::TestLog::EndMessage;
+
+	MultisampleShaderRenderUtil::MultisampleRenderCase::init();
+}
+
+std::string SingleSampleInterpolateAtSampleCase::genVertexSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"in highp vec4 a_position;\n"
+			"out highp vec2 v_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_position = (a_position.xy + vec2(1.0, 1.0)) / 2.0 * vec2(" << RENDER_SIZE << ".0, " << RENDER_SIZE << ".0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string SingleSampleInterpolateAtSampleCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_shader_multisample_interpolation : require\n"
+			"in highp vec2 v_position;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	const highp float threshold = 0.15625; // 4 subpixel bits. Assume 3 accurate bits + 0.03125 for other errors\n"; // 0.03125 = mediump epsilon when value = 32 (RENDER_SIZE)
+
+	if (m_sampleCase == SAMPLE_0)
+	{
+		buf <<	"	highp vec2 samplePosition = interpolateAtSample(v_position, 0);\n"
+				"	highp vec2 positionInsideAPixel = fract(samplePosition);\n"
+				"\n"
+				"	if (abs(positionInsideAPixel.x - 0.5) <= threshold && abs(positionInsideAPixel.y - 0.5) <= threshold)\n"
+				"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"	else\n"
+				"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"}\n";
+	}
+	else if (m_sampleCase == SAMPLE_N)
+	{
+		buf <<	"	bool allOk = true;\n"
+				"	for (int sampleNdx = 159; sampleNdx < 163; ++sampleNdx)\n"
+				"	{\n"
+				"		highp vec2 samplePosition = interpolateAtSample(v_position, sampleNdx);\n"
+				"		highp vec2 positionInsideAPixel = fract(samplePosition);\n"
+				"		if (abs(positionInsideAPixel.x - 0.5) > threshold || abs(positionInsideAPixel.y - 0.5) > threshold)\n"
+				"			allOk = false;\n"
+				"	}\n"
+				"\n"
+				"	if (allOk)\n"
+				"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"	else\n"
+				"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"}\n";
+	}
+	else
+		DE_ASSERT(false);
+
+	return buf.str();
+}
+
+bool SingleSampleInterpolateAtSampleCase::verifyImage (const tcu::Surface& resultImage)
+{
+	return verifyGreenImage(resultImage, m_testCtx.getLog());
+}
+
+class CentroidRenderCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
+{
+public:
+									CentroidRenderCase	(Context& context, const char* name, const char* description, int numSamples, RenderTarget target, int renderSize);
+	virtual							~CentroidRenderCase	(void);
+
+	void							init				(void);
+
+private:
+	void							setupRenderData		(void);
+};
+
+CentroidRenderCase::CentroidRenderCase (Context& context, const char* name, const char* description, int numSamples, RenderTarget target, int renderSize)
+	: MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, description, numSamples, target, renderSize)
+{
+}
+
+CentroidRenderCase::~CentroidRenderCase (void)
+{
+}
+
+void CentroidRenderCase::init (void)
+{
+	// requirements
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
+
+	MultisampleShaderRenderUtil::MultisampleRenderCase::init();
+}
+
+void CentroidRenderCase::setupRenderData (void)
+{
+	const int				numTriangles	= 200;
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	std::vector<tcu::Vec4>	data			(numTriangles * 3 * 3);
+
+	m_renderMode = GL_TRIANGLES;
+	m_renderCount = numTriangles * 3;
+	m_renderSceneDescription = "triangle fan of narrow triangles";
+
+	m_renderAttribs["a_position"].offset = 0;
+	m_renderAttribs["a_position"].stride = sizeof(float[4]) * 3;
+	m_renderAttribs["a_barycentricsA"].offset = sizeof(float[4]);
+	m_renderAttribs["a_barycentricsA"].stride = sizeof(float[4]) * 3;
+	m_renderAttribs["a_barycentricsB"].offset = sizeof(float[4]) * 2;
+	m_renderAttribs["a_barycentricsB"].stride = sizeof(float[4]) * 3;
+
+	for (int triangleNdx = 0; triangleNdx < numTriangles; ++triangleNdx)
+	{
+		const float angle		= ((float)triangleNdx) / numTriangles * 2.0f * DE_PI;
+		const float nextAngle	= ((float)triangleNdx + 1.0f) / numTriangles * 2.0f * DE_PI;
+
+		data[(triangleNdx * 3 + 0) * 3 + 0] = tcu::Vec4(0.2f, -0.3f, 0.0f, 1.0f);
+		data[(triangleNdx * 3 + 0) * 3 + 1] = tcu::Vec4(1.0f,  0.0f, 0.0f, 0.0f);
+		data[(triangleNdx * 3 + 0) * 3 + 2] = tcu::Vec4(1.0f,  0.0f, 0.0f, 0.0f);
+
+		data[(triangleNdx * 3 + 1) * 3 + 0] = tcu::Vec4(2.0f * cos(angle), 2.0f * sin(angle), 0.0f, 1.0f);
+		data[(triangleNdx * 3 + 1) * 3 + 1] = tcu::Vec4(0.0f,  1.0f, 0.0f, 0.0f);
+		data[(triangleNdx * 3 + 1) * 3 + 2] = tcu::Vec4(0.0f,  1.0f, 0.0f, 0.0f);
+
+		data[(triangleNdx * 3 + 2) * 3 + 0] = tcu::Vec4(2.0f * cos(nextAngle), 2.0f * sin(nextAngle), 0.0f, 1.0f);
+		data[(triangleNdx * 3 + 2) * 3 + 1] = tcu::Vec4(0.0f,  0.0f, 1.0f, 0.0f);
+		data[(triangleNdx * 3 + 2) * 3 + 2] = tcu::Vec4(0.0f,  0.0f, 1.0f, 0.0f);
+	}
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);
+	gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(data.size() * sizeof(tcu::Vec4)), data[0].getPtr(), GL_STATIC_DRAW);
+}
+
+class CentroidQualifierAtSampleCase : public CentroidRenderCase
+{
+public:
+									CentroidQualifierAtSampleCase	(Context& context, const char* name, const char* description, int numSamples, RenderTarget target);
+	virtual							~CentroidQualifierAtSampleCase	(void);
+
+	void							init						(void);
+
+private:
+	enum
+	{
+		RENDER_SIZE = 128
+	};
+
+	std::string						genVertexSource				(int numTargetSamples) const;
+	std::string						genFragmentSource			(int numTargetSamples) const;
+	bool							verifyImage					(const tcu::Surface& resultImage);
+};
+
+CentroidQualifierAtSampleCase::CentroidQualifierAtSampleCase (Context& context, const char* name, const char* description, int numSamples, RenderTarget target)
+	: CentroidRenderCase(context, name, description, numSamples, target, RENDER_SIZE)
+{
+}
+
+CentroidQualifierAtSampleCase::~CentroidQualifierAtSampleCase (void)
+{
+}
+
+void CentroidQualifierAtSampleCase::init (void)
+{
+	// test purpose and expectations
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying that interpolateAtSample ignores the centroid-qualifier.\n"
+		<< "	Draw a fan of narrow triangles (large number of pixels on the edges).\n"
+		<< "	Set varyings 'barycentricsA' and 'barycentricsB' to contain barycentric coordinates.\n"
+		<< "	Add centroid-qualifier for barycentricsB.\n"
+		<< "	=> interpolateAtSample(barycentricsB, N) ~= interpolateAtSample(barycentricsA, N)\n"
+		<< tcu::TestLog::EndMessage;
+
+	CentroidRenderCase::init();
+}
+
+std::string CentroidQualifierAtSampleCase::genVertexSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	return	"#version 310 es\n"
+			"in highp vec4 a_position;\n"
+			"in highp vec4 a_barycentricsA;\n"
+			"in highp vec4 a_barycentricsB;\n"
+			"out highp vec3 v_barycentricsA;\n"
+			"centroid out highp vec3 v_barycentricsB;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_barycentricsA = a_barycentricsA.xyz;\n"
+			"	v_barycentricsB = a_barycentricsB.xyz;\n"
+			"}\n";
+}
+
+std::string CentroidQualifierAtSampleCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_shader_multisample_interpolation : require\n"
+			"in highp vec3 v_barycentricsA;\n"
+			"centroid in highp vec3 v_barycentricsB;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	const highp float threshold = 0.0005;\n"
+			"	bool allOk = true;\n"
+			"\n"
+			"	for (int sampleNdx = 0; sampleNdx < " << numTargetSamples << "; ++sampleNdx)\n"
+			"	{\n"
+			"		highp vec3 sampleA = interpolateAtSample(v_barycentricsA, sampleNdx);\n"
+			"		highp vec3 sampleB = interpolateAtSample(v_barycentricsB, sampleNdx);\n"
+			"		bool valuesEqual = all(lessThan(abs(sampleA - sampleB), vec3(threshold)));\n"
+			"		if (!valuesEqual)\n"
+			"			allOk = false;\n"
+			"	}\n"
+			"\n"
+			"	if (allOk)\n"
+			"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+			"	else\n"
+			"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+bool CentroidQualifierAtSampleCase::verifyImage (const tcu::Surface& resultImage)
+{
+	return verifyGreenImage(resultImage, m_testCtx.getLog());
+}
+
+class InterpolateAtSampleIDCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
+{
+public:
+						InterpolateAtSampleIDCase	(Context& context, const char* name, const char* description, int numSamples, RenderTarget target);
+	virtual				~InterpolateAtSampleIDCase	(void);
+
+	void				init						(void);
+private:
+	enum
+	{
+		RENDER_SIZE = 32
+	};
+
+	std::string			genVertexSource				(int numTargetSamples) const;
+	std::string			genFragmentSource			(int numTargetSamples) const;
+	bool				verifyImage					(const tcu::Surface& resultImage);
+};
+
+InterpolateAtSampleIDCase::InterpolateAtSampleIDCase (Context& context, const char* name, const char* description, int numSamples, RenderTarget target)
+	: MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, description, numSamples, target, RENDER_SIZE)
+{
+}
+
+InterpolateAtSampleIDCase::~InterpolateAtSampleIDCase (void)
+{
+}
+
+void InterpolateAtSampleIDCase::init (void)
+{
+	// requirements
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_sample_variables"))
+		throw tcu::NotSupportedError("Test requires GL_OES_sample_variables extension");
+
+	// test purpose and expectations
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying that interpolateAtSample with the sample set to the current sampleID returns consistent values.\n"
+		<< "	Interpolate varying containing screen space location.\n"
+		<< "	=> interpolateAtSample(varying, sampleID) = varying"
+		<< tcu::TestLog::EndMessage;
+
+	MultisampleShaderRenderUtil::MultisampleRenderCase::init();
+}
+
+std::string InterpolateAtSampleIDCase::genVertexSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_shader_multisample_interpolation : require\n"
+			"in highp vec4 a_position;\n"
+			"sample out highp vec2 v_screenPosition;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_screenPosition = (a_position.xy + vec2(1.0, 1.0)) / 2.0 * vec2(" << RENDER_SIZE << ".0, " << RENDER_SIZE << ".0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string InterpolateAtSampleIDCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	return	"#version 310 es\n"
+			"#extension GL_OES_shader_multisample_interpolation : require\n"
+			"sample in highp vec2 v_screenPosition;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	const highp float threshold = 0.15625; // 4 subpixel bits. Assume 3 accurate bits + 0.03125 for other errors\n" // 0.03125 = mediump epsilon when value = 32 (RENDER_SIZE)
+			"\n"
+			"	highp vec2 offsetValue = interpolateAtSample(v_screenPosition, gl_SampleID);\n"
+			"	highp vec2 refValue = v_screenPosition;\n"
+			"\n"
+			"	bool valuesEqual = all(lessThan(abs(offsetValue - refValue), vec2(threshold)));\n"
+			"	if (valuesEqual)\n"
+			"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+			"	else\n"
+			"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+			"}\n";
+}
+
+bool InterpolateAtSampleIDCase::verifyImage (const tcu::Surface& resultImage)
+{
+	return verifyGreenImage(resultImage, m_testCtx.getLog());
+}
+
+class InterpolateAtCentroidCase : public CentroidRenderCase
+{
+public:
+	enum TestType
+	{
+		TEST_CONSISTENCY = 0,
+		TEST_ARRAY_ELEMENT,
+
+		TEST_LAST
+	};
+
+									InterpolateAtCentroidCase	(Context& context, const char* name, const char* description, int numSamples, RenderTarget target, TestType type);
+	virtual							~InterpolateAtCentroidCase	(void);
+
+	void							init						(void);
+
+private:
+	enum
+	{
+		RENDER_SIZE = 128
+	};
+
+	std::string						genVertexSource				(int numTargetSamples) const;
+	std::string						genFragmentSource			(int numTargetSamples) const;
+	bool							verifyImage					(const tcu::Surface& resultImage);
+
+	const TestType					m_type;
+};
+
+InterpolateAtCentroidCase::InterpolateAtCentroidCase (Context& context, const char* name, const char* description, int numSamples, RenderTarget target, TestType type)
+	: CentroidRenderCase	(context, name, description, numSamples, target, RENDER_SIZE)
+	, m_type				(type)
+{
+}
+
+InterpolateAtCentroidCase::~InterpolateAtCentroidCase (void)
+{
+}
+
+void InterpolateAtCentroidCase::init (void)
+{
+	// test purpose and expectations
+	if (m_type == TEST_CONSISTENCY)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Verifying that interpolateAtCentroid does not return different values than a corresponding centroid-qualified varying.\n"
+			<< "	Draw a fan of narrow triangles (large number of pixels on the edges).\n"
+			<< "	Set varyings 'barycentricsA' and 'barycentricsB' to contain barycentric coordinates.\n"
+			<< "	Add centroid-qualifier for barycentricsB.\n"
+			<< "	=> interpolateAtCentroid(barycentricsA) ~= barycentricsB\n"
+			<< tcu::TestLog::EndMessage;
+	}
+	else if (m_type == TEST_ARRAY_ELEMENT)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Testing interpolateAtCentroid with element of array as an argument."
+			<< tcu::TestLog::EndMessage;
+	}
+	else
+		DE_ASSERT(false);
+
+	CentroidRenderCase::init();
+}
+
+std::string InterpolateAtCentroidCase::genVertexSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	if (m_type == TEST_CONSISTENCY)
+		return	"#version 310 es\n"
+				"in highp vec4 a_position;\n"
+				"in highp vec4 a_barycentricsA;\n"
+				"in highp vec4 a_barycentricsB;\n"
+				"out highp vec3 v_barycentricsA;\n"
+				"centroid out highp vec3 v_barycentricsB;\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = a_position;\n"
+				"	v_barycentricsA = a_barycentricsA.xyz;\n"
+				"	v_barycentricsB = a_barycentricsB.xyz;\n"
+				"}\n";
+	else if (m_type == TEST_ARRAY_ELEMENT)
+		return	"#version 310 es\n"
+				"in highp vec4 a_position;\n"
+				"in highp vec4 a_barycentricsA;\n"
+				"in highp vec4 a_barycentricsB;\n"
+				"out highp vec3[2] v_barycentrics;\n"
+				"void main (void)\n"
+				"{\n"
+				"	gl_Position = a_position;\n"
+				"	v_barycentrics[0] = a_barycentricsA.xyz;\n"
+				"	v_barycentrics[1] = a_barycentricsB.xyz;\n"
+				"}\n";
+
+	DE_ASSERT(false);
+	return "";
+}
+
+std::string InterpolateAtCentroidCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	if (m_type == TEST_CONSISTENCY)
+		return	"#version 310 es\n"
+				"#extension GL_OES_shader_multisample_interpolation : require\n"
+				"in highp vec3 v_barycentricsA;\n"
+				"centroid in highp vec3 v_barycentricsB;\n"
+				"layout(location = 0) out highp vec4 fragColor;\n"
+				"void main (void)\n"
+				"{\n"
+				"	const highp float threshold = 0.0005;\n"
+				"\n"
+				"	highp vec3 centroidASampled = interpolateAtCentroid(v_barycentricsA);\n"
+				"	bool valuesEqual = all(lessThan(abs(centroidASampled - v_barycentricsB), vec3(threshold)));\n"
+				"	bool centroidAIsInvalid = any(greaterThan(centroidASampled, vec3(1.0))) ||\n"
+				"	                          any(lessThan(centroidASampled, vec3(0.0)));\n"
+				"	bool centroidBIsInvalid = any(greaterThan(v_barycentricsB, vec3(1.0))) ||\n"
+				"	                          any(lessThan(v_barycentricsB, vec3(0.0)));\n"
+				"\n"
+				"	if (valuesEqual && !centroidAIsInvalid && !centroidBIsInvalid)\n"
+				"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"	else if (centroidAIsInvalid || centroidBIsInvalid)\n"
+				"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"	else\n"
+				"		fragColor = vec4(1.0, 1.0, 0.0, 1.0);\n"
+				"}\n";
+	else if (m_type == TEST_ARRAY_ELEMENT)
+		return	"#version 310 es\n"
+				"#extension GL_OES_shader_multisample_interpolation : require\n"
+				"in highp vec3[2] v_barycentrics;\n"
+				"layout(location = 0) out mediump vec4 fragColor;\n"
+				"void main (void)\n"
+				"{\n"
+				"	const highp float threshold = 0.0005;\n"
+				"\n"
+				"	highp vec3 centroidInterpolated = interpolateAtCentroid(v_barycentrics[1]);\n"
+				"	bool centroidIsInvalid = any(greaterThan(centroidInterpolated, vec3(1.0))) ||\n"
+				"	                         any(lessThan(centroidInterpolated, vec3(0.0)));\n"
+				"\n"
+				"	if (!centroidIsInvalid)\n"
+				"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"	else\n"
+				"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+				"}\n";
+
+	DE_ASSERT(false);
+	return "";
+}
+
+bool InterpolateAtCentroidCase::verifyImage (const tcu::Surface& resultImage)
+{
+	return verifyGreenImage(resultImage, m_testCtx.getLog());
+}
+
+class InterpolateAtOffsetCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
+{
+public:
+	enum TestType
+	{
+		TEST_QUALIFIER_NONE = 0,
+		TEST_QUALIFIER_CENTROID,
+		TEST_QUALIFIER_SAMPLE,
+		TEST_ARRAY_ELEMENT,
+
+		TEST_LAST
+	};
+						InterpolateAtOffsetCase		(Context& context, const char* name, const char* description, int numSamples, RenderTarget target, TestType testType);
+	virtual				~InterpolateAtOffsetCase	(void);
+
+	void				init						(void);
+private:
+	enum
+	{
+		RENDER_SIZE = 32
+	};
+
+	std::string			genVertexSource				(int numTargetSamples) const;
+	std::string			genFragmentSource			(int numTargetSamples) const;
+	bool				verifyImage					(const tcu::Surface& resultImage);
+
+	const TestType		m_testType;
+};
+
+InterpolateAtOffsetCase::InterpolateAtOffsetCase (Context& context, const char* name, const char* description, int numSamples, RenderTarget target, TestType testType)
+	: MultisampleShaderRenderUtil::MultisampleRenderCase	(context, name, description, numSamples, target, RENDER_SIZE)
+	, m_testType											(testType)
+{
+	DE_ASSERT(testType < TEST_LAST);
+}
+
+InterpolateAtOffsetCase::~InterpolateAtOffsetCase (void)
+{
+}
+
+void InterpolateAtOffsetCase::init (void)
+{
+	// requirements
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
+
+	// test purpose and expectations
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying that interpolateAtOffset returns correct values.\n"
+		<< "	Interpolate varying containing screen space location.\n"
+		<< "	=> interpolateAtOffset(varying, offset) should be \"varying value at the pixel center\" + offset"
+		<< tcu::TestLog::EndMessage;
+
+	MultisampleShaderRenderUtil::MultisampleRenderCase::init();
+}
+
+std::string InterpolateAtOffsetCase::genVertexSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+	buf << "#version 310 es\n"
+		<< "#extension GL_OES_shader_multisample_interpolation : require\n"
+		<< "in highp vec4 a_position;\n";
+
+	if (m_testType == TEST_QUALIFIER_NONE || m_testType == TEST_QUALIFIER_CENTROID || m_testType == TEST_QUALIFIER_SAMPLE)
+	{
+		const char* const qualifier = (m_testType == TEST_QUALIFIER_CENTROID) ? ("centroid ") : (m_testType == TEST_QUALIFIER_SAMPLE) ? ("sample ") : ("");
+		buf << qualifier << "out highp vec2 v_screenPosition;\n"
+			<< qualifier << "out highp vec2 v_offset;\n";
+	}
+	else if (m_testType == TEST_ARRAY_ELEMENT)
+	{
+		buf << "out highp vec2[2] v_screenPosition;\n"
+			<< "out highp vec2 v_offset;\n";
+	}
+	else
+		DE_ASSERT(false);
+
+	buf	<< "void main (void)\n"
+		<< "{\n"
+		<< "	gl_Position = a_position;\n";
+
+	if (m_testType != TEST_ARRAY_ELEMENT)
+		buf	<< "	v_screenPosition = (a_position.xy + vec2(1.0, 1.0)) / 2.0 * vec2(" << RENDER_SIZE << ".0, " << RENDER_SIZE << ".0);\n";
+	else
+		buf	<< "	v_screenPosition[0] = a_position.xy; // not used\n"
+				"	v_screenPosition[1] = (a_position.xy + vec2(1.0, 1.0)) / 2.0 * vec2(" << RENDER_SIZE << ".0, " << RENDER_SIZE << ".0);\n";
+
+	buf	<< "	v_offset = a_position.xy * 0.5f;\n"
+		<< "}\n";
+
+	return buf.str();
+}
+
+std::string InterpolateAtOffsetCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	const char* const	arrayIndexing = (m_testType == TEST_ARRAY_ELEMENT) ? ("[1]") : ("");
+	std::ostringstream	buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_shader_multisample_interpolation : require\n";
+
+	if (m_testType == TEST_QUALIFIER_NONE || m_testType == TEST_QUALIFIER_CENTROID || m_testType == TEST_QUALIFIER_SAMPLE)
+	{
+		const char* const qualifier = (m_testType == TEST_QUALIFIER_CENTROID) ? ("centroid ") : (m_testType == TEST_QUALIFIER_SAMPLE) ? ("sample ") : ("");
+		buf	<< qualifier << "in highp vec2 v_screenPosition;\n"
+			<< qualifier << "in highp vec2 v_offset;\n";
+	}
+	else if (m_testType == TEST_ARRAY_ELEMENT)
+	{
+		buf << "in highp vec2[2] v_screenPosition;\n"
+			<< "in highp vec2 v_offset;\n";
+	}
+	else
+		DE_ASSERT(false);
+
+	buf	<<	"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	const highp float threshold = 0.15625; // 4 subpixel bits. Assume 3 accurate bits + 0.03125 for other errors\n" // 0.03125 = mediump epsilon when value = 32 (RENDER_SIZE)
+			"\n"
+			"	highp vec2 pixelCenter = floor(v_screenPosition" << arrayIndexing << ") + vec2(0.5, 0.5);\n"
+			"	highp vec2 offsetValue = interpolateAtOffset(v_screenPosition" << arrayIndexing << ", v_offset);\n"
+			"	highp vec2 refValue = pixelCenter + v_offset;\n"
+			"\n"
+			"	bool valuesEqual = all(lessThan(abs(offsetValue - refValue), vec2(threshold)));\n"
+			"	if (valuesEqual)\n"
+			"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+			"	else\n"
+			"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+bool InterpolateAtOffsetCase::verifyImage (const tcu::Surface& resultImage)
+{
+	return verifyGreenImage(resultImage, m_testCtx.getLog());
+}
+
+class InterpolateAtSamplePositionCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
+{
+public:
+						InterpolateAtSamplePositionCase		(Context& context, const char* name, const char* description, int numSamples, RenderTarget target);
+	virtual				~InterpolateAtSamplePositionCase	(void);
+
+	void				init								(void);
+private:
+	enum
+	{
+		RENDER_SIZE = 32
+	};
+
+	std::string			genVertexSource						(int numTargetSamples) const;
+	std::string			genFragmentSource					(int numTargetSamples) const;
+	bool				verifyImage							(const tcu::Surface& resultImage);
+};
+
+InterpolateAtSamplePositionCase::InterpolateAtSamplePositionCase (Context& context, const char* name, const char* description, int numSamples, RenderTarget target)
+	: MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, description, numSamples, target, RENDER_SIZE)
+{
+}
+
+InterpolateAtSamplePositionCase::~InterpolateAtSamplePositionCase (void)
+{
+}
+
+void InterpolateAtSamplePositionCase::init (void)
+{
+	// requirements
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_sample_variables"))
+		throw tcu::NotSupportedError("Test requires GL_OES_sample_variables extension");
+
+	// test purpose and expectations
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Verifying that interpolateAtOffset with the offset of current sample position returns consistent values.\n"
+		<< "	Interpolate varying containing screen space location.\n"
+		<< "	=> interpolateAtOffset(varying, currentOffset) = varying"
+		<< tcu::TestLog::EndMessage;
+
+	MultisampleShaderRenderUtil::MultisampleRenderCase::init();
+}
+
+std::string InterpolateAtSamplePositionCase::genVertexSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_OES_shader_multisample_interpolation : require\n"
+			"in highp vec4 a_position;\n"
+			"sample out highp vec2 v_screenPosition;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_screenPosition = (a_position.xy + vec2(1.0, 1.0)) / 2.0 * vec2(" << RENDER_SIZE << ".0, " << RENDER_SIZE << ".0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string InterpolateAtSamplePositionCase::genFragmentSource (int numTargetSamples) const
+{
+	DE_UNREF(numTargetSamples);
+
+	return	"#version 310 es\n"
+			"#extension GL_OES_shader_multisample_interpolation : require\n"
+			"sample in highp vec2 v_screenPosition;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	const highp float threshold = 0.15625; // 4 subpixel bits. Assume 3 accurate bits + 0.03125 for other errors\n" // 0.03125 = mediump epsilon when value = 32 (RENDER_SIZE)
+			"\n"
+			"	highp vec2 offset = gl_SamplePosition - vec2(0.5, 0.5);\n"
+			"	highp vec2 offsetValue = interpolateAtOffset(v_screenPosition, offset);\n"
+			"	highp vec2 refValue = v_screenPosition;\n"
+			"\n"
+			"	bool valuesEqual = all(lessThan(abs(offsetValue - refValue), vec2(threshold)));\n"
+			"	if (valuesEqual)\n"
+			"		fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+			"	else\n"
+			"		fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+			"}\n";
+}
+
+bool InterpolateAtSamplePositionCase::verifyImage (const tcu::Surface& resultImage)
+{
+	return verifyGreenImage(resultImage, m_testCtx.getLog());
+}
+
+} // anonymous
+
+ShaderMultisampleInterpolationTests::ShaderMultisampleInterpolationTests (Context& context)
+	: TestCaseGroup(context, "multisample_interpolation", "Test multisample interpolation")
+{
+}
+
+ShaderMultisampleInterpolationTests::~ShaderMultisampleInterpolationTests (void)
+{
+}
+
+void ShaderMultisampleInterpolationTests::init (void)
+{
+	using namespace MultisampleShaderRenderUtil;
+
+	static const struct RenderTarget
+	{
+		const char*							name;
+		const char*							desc;
+		int									numSamples;
+		MultisampleRenderCase::RenderTarget	target;
+	} targets[] =
+	{
+		{ "default_framebuffer",		"Test with default framebuffer",	0,	MultisampleRenderCase::TARGET_DEFAULT		},
+		{ "singlesample_texture",		"Test with singlesample texture",	0,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "multisample_texture_1",		"Test with multisample texture",	1,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "multisample_texture_2",		"Test with multisample texture",	2,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "multisample_texture_4",		"Test with multisample texture",	4,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "multisample_texture_8",		"Test with multisample texture",	8,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "multisample_texture_16",		"Test with multisample texture",	16,	MultisampleRenderCase::TARGET_TEXTURE		},
+		{ "singlesample_rbo",			"Test with singlesample rbo",		0,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+		{ "multisample_rbo_1",			"Test with multisample rbo",		1,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+		{ "multisample_rbo_2",			"Test with multisample rbo",		2,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+		{ "multisample_rbo_4",			"Test with multisample rbo",		4,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+		{ "multisample_rbo_8",			"Test with multisample rbo",		8,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+		{ "multisample_rbo_16",			"Test with multisample rbo",		16,	MultisampleRenderCase::TARGET_RENDERBUFFER	},
+	};
+
+	// .sample_qualifier
+	{
+		tcu::TestCaseGroup* const sampleQualifierGroup = new tcu::TestCaseGroup(m_testCtx, "sample_qualifier", "Test sample qualifier");
+		addChild(sampleQualifierGroup);
+
+		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+			sampleQualifierGroup->addChild(new SampleQualifierRenderCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target));
+	}
+
+	// .interpolate_at_sample
+	{
+		tcu::TestCaseGroup* const interpolateAtSampleGroup = new tcu::TestCaseGroup(m_testCtx, "interpolate_at_sample", "Test interpolateAtSample");
+		addChild(interpolateAtSampleGroup);
+
+		// .static_sample_number
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "static_sample_number", "Test interpolateAtSample sample number");
+			interpolateAtSampleGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new InterpolateAtSampleRenderCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, InterpolateAtSampleRenderCase::INDEXING_STATIC));
+		}
+
+		// .dynamic_sample_number
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "dynamic_sample_number", "Test interpolateAtSample sample number");
+			interpolateAtSampleGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new InterpolateAtSampleRenderCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, InterpolateAtSampleRenderCase::INDEXING_DYNAMIC));
+		}
+
+		// .non_multisample_buffer
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "non_multisample_buffer", "Test interpolateAtSample with non-multisample buffers");
+			interpolateAtSampleGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				if (targets[targetNdx].numSamples == 0)
+					group->addChild(new SingleSampleInterpolateAtSampleCase(m_context, std::string("sample_0_").append(targets[targetNdx].name).c_str(), targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SingleSampleInterpolateAtSampleCase::SAMPLE_0));
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				if (targets[targetNdx].numSamples == 0)
+					group->addChild(new SingleSampleInterpolateAtSampleCase(m_context, std::string("sample_n_").append(targets[targetNdx].name).c_str(), targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, SingleSampleInterpolateAtSampleCase::SAMPLE_N));
+		}
+
+		// .centroid_qualifier
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "centroid_qualified", "Test interpolateAtSample with centroid qualified varying");
+			interpolateAtSampleGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new CentroidQualifierAtSampleCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target));
+		}
+
+		// .at_sample_id
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "at_sample_id", "Test interpolateAtOffset at current sample id");
+			interpolateAtSampleGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new InterpolateAtSampleIDCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target));
+		}
+	}
+
+	// .interpolate_at_centroid
+	{
+		tcu::TestCaseGroup* const methodGroup = new tcu::TestCaseGroup(m_testCtx, "interpolate_at_centroid", "Test interpolateAtCentroid");
+		addChild(methodGroup);
+
+		// .consistency
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "consistency", "Test interpolateAtCentroid return value is consistent to centroid qualified value");
+			methodGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new InterpolateAtCentroidCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, InterpolateAtCentroidCase::TEST_CONSISTENCY));
+		}
+
+		// .array_element
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "array_element", "Test interpolateAtCentroid with array element");
+			methodGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new InterpolateAtCentroidCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, InterpolateAtCentroidCase::TEST_ARRAY_ELEMENT));
+		}
+	}
+
+	// .interpolate_at_offset
+	{
+		static const struct TestConfig
+		{
+			const char*							name;
+			InterpolateAtOffsetCase::TestType	type;
+		} configs[] =
+		{
+			{ "no_qualifiers",		InterpolateAtOffsetCase::TEST_QUALIFIER_NONE		},
+			{ "centroid_qualifier",	InterpolateAtOffsetCase::TEST_QUALIFIER_CENTROID	},
+			{ "sample_qualifier",	InterpolateAtOffsetCase::TEST_QUALIFIER_SAMPLE		},
+		};
+
+		tcu::TestCaseGroup* const methodGroup = new tcu::TestCaseGroup(m_testCtx, "interpolate_at_offset", "Test interpolateAtOffset");
+		addChild(methodGroup);
+
+		// .no_qualifiers
+		// .centroid_qualifier
+		// .sample_qualifier
+		for (int configNdx = 0; configNdx < DE_LENGTH_OF_ARRAY(configs); ++configNdx)
+		{
+			tcu::TestCaseGroup* const qualifierGroup = new tcu::TestCaseGroup(m_testCtx, configs[configNdx].name, "Test interpolateAtOffset with qualified/non-qualified varying");
+			methodGroup->addChild(qualifierGroup);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				qualifierGroup->addChild(new InterpolateAtOffsetCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, configs[configNdx].type));
+		}
+
+		// .at_sample_position
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "at_sample_position", "Test interpolateAtOffset at sample position");
+			methodGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new InterpolateAtSamplePositionCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target));
+		}
+
+		// .array_element
+		{
+			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "array_element", "Test interpolateAtOffset with array element");
+			methodGroup->addChild(group);
+
+			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
+				group->addChild(new InterpolateAtOffsetCase(m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target, InterpolateAtOffsetCase::TEST_ARRAY_ELEMENT));
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderMultisampleInterpolationTests.hpp b/modules/gles31/functional/es31fShaderMultisampleInterpolationTests.hpp
new file mode 100644
index 0000000..8232d50
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderMultisampleInterpolationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSHADERMULTISAMPLEINTERPOLATIONTESTS_HPP
+#define _ES31FSHADERMULTISAMPLEINTERPOLATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample interpolation tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderMultisampleInterpolationTests : public TestCaseGroup
+{
+public:
+											ShaderMultisampleInterpolationTests		(Context& context);
+											~ShaderMultisampleInterpolationTests	(void);
+
+	void									init									(void);
+
+private:
+											ShaderMultisampleInterpolationTests		(ShaderMultisampleInterpolationTests& other);
+	ShaderMultisampleInterpolationTests&	operator=								(const ShaderMultisampleInterpolationTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERMULTISAMPLEINTERPOLATIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderPackingFunctionTests.cpp b/modules/gles31/functional/es31fShaderPackingFunctionTests.cpp
new file mode 100644
index 0000000..c8129e5
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderPackingFunctionTests.cpp
@@ -0,0 +1,1252 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Floating-point packing and unpacking function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderPackingFunctionTests.hpp"
+#include "glsShaderExecUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuFormatUtil.hpp"
+#include "tcuFloat.hpp"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deString.h"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using std::string;
+using tcu::TestLog;
+using namespace gls::ShaderExecUtil;
+
+namespace
+{
+
+inline deUint32 getUlpDiff (float a, float b)
+{
+	const deUint32	aBits	= tcu::Float32(a).bits();
+	const deUint32	bBits	= tcu::Float32(b).bits();
+	return aBits > bBits ? aBits - bBits : bBits - aBits;
+}
+
+struct HexFloat
+{
+	const float value;
+	HexFloat (const float value_) : value(value_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const HexFloat& v)
+{
+	return str << v.value << " / " << tcu::toHex(tcu::Float32(v.value).bits());
+}
+
+} // anonymous
+
+// ShaderPackingFunctionCase
+
+class ShaderPackingFunctionCase : public TestCase
+{
+public:
+								ShaderPackingFunctionCase	(Context& context, const char* name, const char* description, glu::ShaderType shaderType);
+								~ShaderPackingFunctionCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+
+protected:
+	glu::ShaderType				m_shaderType;
+	ShaderSpec					m_spec;
+	ShaderExecutor*				m_executor;
+
+private:
+								ShaderPackingFunctionCase	(const ShaderPackingFunctionCase& other);
+	ShaderPackingFunctionCase&	operator=					(const ShaderPackingFunctionCase& other);
+};
+
+ShaderPackingFunctionCase::ShaderPackingFunctionCase (Context& context, const char* name, const char* description, glu::ShaderType shaderType)
+	: TestCase		(context, name, description)
+	, m_shaderType	(shaderType)
+	, m_executor	(DE_NULL)
+{
+	m_spec.version = glu::GLSL_VERSION_310_ES;
+}
+
+ShaderPackingFunctionCase::~ShaderPackingFunctionCase (void)
+{
+	ShaderPackingFunctionCase::deinit();
+}
+
+void ShaderPackingFunctionCase::init (void)
+{
+	DE_ASSERT(!m_executor);
+
+	m_executor = createExecutor(m_context.getRenderContext(), m_shaderType, m_spec);
+	m_testCtx.getLog() << m_executor;
+
+	if (!m_executor->isOk())
+		throw tcu::TestError("Compile failed");
+}
+
+void ShaderPackingFunctionCase::deinit (void)
+{
+	delete m_executor;
+	m_executor = DE_NULL;
+}
+
+// Test cases
+
+static const char* getPrecisionPostfix (glu::Precision precision)
+{
+	static const char* s_postfix[] =
+	{
+		"_lowp",
+		"_mediump",
+		"_highp"
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_postfix) == glu::PRECISION_LAST);
+	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
+	return s_postfix[precision];
+}
+
+static const char* getShaderTypePostfix (glu::ShaderType shaderType)
+{
+	static const char* s_postfix[] =
+	{
+		"_vertex",
+		"_fragment",
+		"_geometry",
+		"_tess_control",
+		"_tess_eval",
+		"_compute"
+	};
+	DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
+	return s_postfix[shaderType];
+}
+
+class PackSnorm2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	PackSnorm2x16Case (Context& context, glu::ShaderType shaderType, glu::Precision precision)
+		: ShaderPackingFunctionCase	(context, (string("packsnorm2x16") + getPrecisionPostfix(precision) + getShaderTypePostfix(shaderType)).c_str(), "packSnorm2x16", shaderType)
+		, m_precision				(precision)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_FLOAT_VEC2, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = packSnorm2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<tcu::Vec2>		inputs;
+		std::vector<deUint32>		outputs;
+		const int					maxDiff		= m_precision == glu::PRECISION_HIGHP	? 1		:		// Rounding only.
+												  m_precision == glu::PRECISION_MEDIUMP	? 33	:		// (2^-10) * (2^15) + 1
+												  m_precision == glu::PRECISION_LOWP	? 129	: 0;	// (2^-8) * (2^15) + 1
+
+		// Special values to check.
+		inputs.push_back(tcu::Vec2(0.0f, 0.0f));
+		inputs.push_back(tcu::Vec2(-1.0f, 1.0f));
+		inputs.push_back(tcu::Vec2(0.5f, -0.5f));
+		inputs.push_back(tcu::Vec2(-1.5f, 1.5f));
+		inputs.push_back(tcu::Vec2(0.25f, -0.75f));
+
+		// Random values, mostly in range.
+		for (int ndx = 0; ndx < 15; ndx++)
+		{
+			const float x = rnd.getFloat()*2.5f - 1.25f;
+			const float y = rnd.getFloat()*2.5f - 1.25f;
+			inputs.push_back(tcu::Vec2(x, y));
+		}
+
+		// Large random values.
+		for (int ndx = 0; ndx < 80; ndx++)
+		{
+			const float x = rnd.getFloat()*1e6f - 0.5e6f;
+			const float y = rnd.getFloat()*1e6f - 0.5e6f;
+			inputs.push_back(tcu::Vec2(x, y));
+		}
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < numValues; valNdx++)
+			{
+				const deUint16	ref0	= (deUint16)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].x(), -1.0f, 1.0f) * 32767.0f), -(1<<15), (1<<15)-1);
+				const deUint16	ref1	= (deUint16)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].y(), -1.0f, 1.0f) * 32767.0f), -(1<<15), (1<<15)-1);
+				const deUint32	ref		= (ref1 << 16) | ref0;
+				const deUint32	res		= outputs[valNdx];
+				const deUint16	res0	= (deUint16)(res & 0xffff);
+				const deUint16	res1	= (deUint16)(res >> 16);
+				const int		diff0	= de::abs((int)ref0 - (int)res0);
+				const int		diff1	= de::abs((int)ref1 - (int)res1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx
+															   << ", expected packSnorm2x16(" << inputs[valNdx] << ") = " << tcu::toHex(ref)
+															   << ", got " << tcu::toHex(res)
+															   << "\n  diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	glu::Precision m_precision;
+};
+
+class UnpackSnorm2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	UnpackSnorm2x16Case (Context& context, glu::ShaderType shaderType)
+		: ShaderPackingFunctionCase(context, (string("unpacksnorm2x16") + getShaderTypePostfix(shaderType)).c_str(), "unpackSnorm2x16", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = unpackSnorm2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32				maxDiff		= 1; // Rounding error.
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<deUint32>		inputs;
+		std::vector<tcu::Vec2>		outputs;
+
+		inputs.push_back(0x00000000u);
+		inputs.push_back(0x7fff8000u);
+		inputs.push_back(0x80007fffu);
+		inputs.push_back(0xffffffffu);
+		inputs.push_back(0x0001fffeu);
+
+		// Random values.
+		for (int ndx = 0; ndx < 95; ndx++)
+			inputs.push_back(rnd.getUint32());
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deInt16	in0			= (deInt16)(deUint16)(inputs[valNdx] & 0xffff);
+				const deInt16	in1			= (deInt16)(deUint16)(inputs[valNdx] >> 16);
+				const float		ref0		= de::clamp(float(in0) / 32767.f, -1.0f, 1.0f);
+				const float		ref1		= de::clamp(float(in1) / 32767.f, -1.0f, 1.0f);
+				const float		res0		= outputs[valNdx].x();
+				const float		res1		= outputs[valNdx].y();
+
+				const deUint32	diff0	= getUlpDiff(ref0, res0);
+				const deUint32	diff1	= getUlpDiff(ref1, res1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx << ",\n"
+															   << "  expected unpackSnorm2x16(" << tcu::toHex(inputs[valNdx]) << ") = "
+															   << "vec2(" << HexFloat(ref0) << ", " << HexFloat(ref1) << ")"
+															   << ", got vec2(" << HexFloat(res0) << ", " << HexFloat(res1) << ")"
+															   << "\n  ULP diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class PackUnorm2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	PackUnorm2x16Case (Context& context, glu::ShaderType shaderType, glu::Precision precision)
+		: ShaderPackingFunctionCase	(context, (string("packunorm2x16") + getPrecisionPostfix(precision) + getShaderTypePostfix(shaderType)).c_str(), "packUnorm2x16", shaderType)
+		, m_precision				(precision)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_FLOAT_VEC2, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = packUnorm2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<tcu::Vec2>		inputs;
+		std::vector<deUint32>		outputs;
+		const int					maxDiff		= m_precision == glu::PRECISION_HIGHP	? 1		:		// Rounding only.
+												  m_precision == glu::PRECISION_MEDIUMP	? 65	:		// (2^-10) * (2^16) + 1
+												  m_precision == glu::PRECISION_LOWP	? 257	: 0;	// (2^-8) * (2^16) + 1
+
+		// Special values to check.
+		inputs.push_back(tcu::Vec2(0.0f, 0.0f));
+		inputs.push_back(tcu::Vec2(0.5f, 1.0f));
+		inputs.push_back(tcu::Vec2(1.0f, 0.5f));
+		inputs.push_back(tcu::Vec2(-0.5f, 1.5f));
+		inputs.push_back(tcu::Vec2(0.25f, 0.75f));
+
+		// Random values, mostly in range.
+		for (int ndx = 0; ndx < 15; ndx++)
+		{
+			const float x = rnd.getFloat()*1.25f;
+			const float y = rnd.getFloat()*1.25f;
+			inputs.push_back(tcu::Vec2(x, y));
+		}
+
+		// Large random values.
+		for (int ndx = 0; ndx < 80; ndx++)
+		{
+			const float x = rnd.getFloat()*1e6f - 1e5f;
+			const float y = rnd.getFloat()*1e6f - 1e5f;
+			inputs.push_back(tcu::Vec2(x, y));
+		}
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deUint16	ref0	= (deUint16)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].x(), 0.0f, 1.0f) * 65535.0f), 0, (1<<16)-1);
+				const deUint16	ref1	= (deUint16)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].y(), 0.0f, 1.0f) * 65535.0f), 0, (1<<16)-1);
+				const deUint32	ref		= (ref1 << 16) | ref0;
+				const deUint32	res		= outputs[valNdx];
+				const deUint16	res0	= (deUint16)(res & 0xffff);
+				const deUint16	res1	= (deUint16)(res >> 16);
+				const int		diff0	= de::abs((int)ref0 - (int)res0);
+				const int		diff1	= de::abs((int)ref1 - (int)res1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx
+															   << ", expected packUnorm2x16(" << inputs[valNdx] << ") = " << tcu::toHex(ref)
+															   << ", got " << tcu::toHex(res)
+															   << "\n  diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	glu::Precision m_precision;
+};
+
+class UnpackUnorm2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	UnpackUnorm2x16Case (Context& context, glu::ShaderType shaderType)
+		: ShaderPackingFunctionCase(context, (string("unpackunorm2x16") + getShaderTypePostfix(shaderType)).c_str(), "unpackUnorm2x16", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = unpackUnorm2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32				maxDiff		= 1; // Rounding error.
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<deUint32>		inputs;
+		std::vector<tcu::Vec2>		outputs;
+
+		inputs.push_back(0x00000000u);
+		inputs.push_back(0x7fff8000u);
+		inputs.push_back(0x80007fffu);
+		inputs.push_back(0xffffffffu);
+		inputs.push_back(0x0001fffeu);
+
+		// Random values.
+		for (int ndx = 0; ndx < 95; ndx++)
+			inputs.push_back(rnd.getUint32());
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deUint16	in0			= (deUint16)(inputs[valNdx] & 0xffff);
+				const deUint16	in1			= (deUint16)(inputs[valNdx] >> 16);
+				const float		ref0		= float(in0) / 65535.0f;
+				const float		ref1		= float(in1) / 65535.0f;
+				const float		res0		= outputs[valNdx].x();
+				const float		res1		= outputs[valNdx].y();
+
+				const deUint32	diff0		= getUlpDiff(ref0, res0);
+				const deUint32	diff1		= getUlpDiff(ref1, res1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx << ",\n"
+															   << "  expected unpackUnorm2x16(" << tcu::toHex(inputs[valNdx]) << ") = "
+															   << "vec2(" << HexFloat(ref0) << ", " << HexFloat(ref1) << ")"
+															   << ", got vec2(" << HexFloat(res0) << ", " << HexFloat(res1) << ")"
+															   << "\n  ULP diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class PackHalf2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	PackHalf2x16Case (Context& context, glu::ShaderType shaderType)
+		: ShaderPackingFunctionCase(context, (string("packhalf2x16") + getShaderTypePostfix(shaderType)).c_str(), "packHalf2x16", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = packHalf2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		const int					maxDiff		= 0; // Values can be represented exactly in mediump.
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<tcu::Vec2>		inputs;
+		std::vector<deUint32>		outputs;
+
+		// Special values to check.
+		inputs.push_back(tcu::Vec2(0.0f, 0.0f));
+		inputs.push_back(tcu::Vec2(0.5f, 1.0f));
+		inputs.push_back(tcu::Vec2(1.0f, 0.5f));
+		inputs.push_back(tcu::Vec2(-0.5f, 1.5f));
+		inputs.push_back(tcu::Vec2(0.25f, 0.75f));
+
+		// Random values.
+		{
+			const int	minExp	= -14;
+			const int	maxExp	= 15;
+
+			for (int ndx = 0; ndx < 95; ndx++)
+			{
+				tcu::Vec2 v;
+				for (int c = 0; c < 2; c++)
+				{
+					const int		s			= rnd.getBool() ? 1 : -1;
+					const int		exp			= rnd.getInt(minExp, maxExp);
+					const deUint32	mantissa	= rnd.getUint32() & ((1<<23)-1);
+
+					v[c] = tcu::Float32::construct(s, exp ? exp : 1 /* avoid denormals */, (1u<<23) | mantissa).asFloat();
+				}
+				inputs.push_back(v);
+			}
+		}
+
+		// Convert input values to fp16 and back to make sure they can be represented exactly in mediump.
+		for (std::vector<tcu::Vec2>::iterator inVal = inputs.begin(); inVal != inputs.end(); ++inVal)
+			*inVal = tcu::Vec2(tcu::Float16(inVal->x()).asFloat(), tcu::Float16(inVal->y()).asFloat());
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deUint16	ref0	= (deUint16)tcu::Float16(inputs[valNdx].x()).bits();
+				const deUint16	ref1	= (deUint16)tcu::Float16(inputs[valNdx].y()).bits();
+				const deUint32	ref		= (ref1 << 16) | ref0;
+				const deUint32	res		= outputs[valNdx];
+				const deUint16	res0	= (deUint16)(res & 0xffff);
+				const deUint16	res1	= (deUint16)(res >> 16);
+				const int		diff0	= de::abs((int)ref0 - (int)res0);
+				const int		diff1	= de::abs((int)ref1 - (int)res1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx
+															   << ", expected packHalf2x16(" << inputs[valNdx] << ") = " << tcu::toHex(ref)
+															   << ", got " << tcu::toHex(res)
+															   << "\n  diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class UnpackHalf2x16Case : public ShaderPackingFunctionCase
+{
+public:
+	UnpackHalf2x16Case (Context& context, glu::ShaderType shaderType)
+		: ShaderPackingFunctionCase(context, (string("unpackhalf2x16") + getShaderTypePostfix(shaderType)).c_str(), "unpackHalf2x16", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_FLOAT_VEC2, glu::PRECISION_MEDIUMP)));
+
+		m_spec.source = "out0 = unpackHalf2x16(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		const int					maxDiff		= 0; // All bits must be accurate.
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<deUint32>		inputs;
+		std::vector<tcu::Vec2>		outputs;
+
+		// Special values.
+		inputs.push_back((tcu::Float16( 0.0f).bits() << 16) | tcu::Float16( 1.0f).bits());
+		inputs.push_back((tcu::Float16( 1.0f).bits() << 16) | tcu::Float16( 0.0f).bits());
+		inputs.push_back((tcu::Float16(-1.0f).bits() << 16) | tcu::Float16( 0.5f).bits());
+		inputs.push_back((tcu::Float16( 0.5f).bits() << 16) | tcu::Float16(-0.5f).bits());
+
+		// Construct random values.
+		{
+			const int	minExp		= -14;
+			const int	maxExp		= 15;
+			const int	mantBits	= 10;
+
+			for (int ndx = 0; ndx < 96; ndx++)
+			{
+				deUint32 inVal = 0;
+				for (int c = 0; c < 2; c++)
+				{
+					const int		s			= rnd.getBool() ? 1 : -1;
+					const int		exp			= rnd.getInt(minExp, maxExp);
+					const deUint32	mantissa	= rnd.getUint32() & ((1<<mantBits)-1);
+					const deUint16	value		= tcu::Float16::construct(s, exp ? exp : 1 /* avoid denorm */, (1u<<10) | mantissa).bits();
+
+					inVal |= value << (16*c);
+				}
+				inputs.push_back(inVal);
+			}
+		}
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deUint16	in0			= (deUint16)(inputs[valNdx] & 0xffff);
+				const deUint16	in1			= (deUint16)(inputs[valNdx] >> 16);
+				const float		ref0		= tcu::Float16(in0).asFloat();
+				const float		ref1		= tcu::Float16(in1).asFloat();
+				const float		res0		= outputs[valNdx].x();
+				const float		res1		= outputs[valNdx].y();
+
+				const deUint32	refBits0	= tcu::Float32(ref0).bits();
+				const deUint32	refBits1	= tcu::Float32(ref1).bits();
+				const deUint32	resBits0	= tcu::Float32(res0).bits();
+				const deUint32	resBits1	= tcu::Float32(res1).bits();
+
+				const int		diff0	= de::abs((int)refBits0 - (int)resBits0);
+				const int		diff1	= de::abs((int)refBits1 - (int)resBits1);
+
+				if (diff0 > maxDiff || diff1 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx << ",\n"
+															   << "  expected unpackHalf2x16(" << tcu::toHex(inputs[valNdx]) << ") = "
+															   << "vec2(" << ref0 << " / " << tcu::toHex(refBits0) << ", " << ref1 << " / " << tcu::toHex(refBits1) << ")"
+															   << ", got vec2(" << res0 << " / " << tcu::toHex(resBits0) << ", " << res1 << " / " << tcu::toHex(resBits1) << ")"
+															   << "\n  ULP diffs = (" << diff0 << ", " << diff1 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class PackSnorm4x8Case : public ShaderPackingFunctionCase
+{
+public:
+	PackSnorm4x8Case (Context& context, glu::ShaderType shaderType, glu::Precision precision)
+		: ShaderPackingFunctionCase	(context, (string("packsnorm4x8") + getPrecisionPostfix(precision) + getShaderTypePostfix(shaderType)).c_str(), "packSnorm4x8", shaderType)
+		, m_precision				(precision)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_FLOAT_VEC4, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = packSnorm4x8(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		de::Random					rnd			(deStringHash(getName()) ^ 0x42f2c0);
+		std::vector<tcu::Vec4>		inputs;
+		std::vector<deUint32>		outputs;
+		const int					maxDiff		= m_precision == glu::PRECISION_HIGHP	? 1	:		// Rounding only.
+												  m_precision == glu::PRECISION_MEDIUMP	? 1	:		// (2^-10) * (2^7) + 1
+												  m_precision == glu::PRECISION_LOWP	? 2	: 0;	// (2^-8) * (2^7) + 1
+
+		// Special values to check.
+		inputs.push_back(tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f));
+		inputs.push_back(tcu::Vec4(-1.0f, 1.0f, -1.0f, 1.0f));
+		inputs.push_back(tcu::Vec4(0.5f, -0.5f, -0.5f, 0.5f));
+		inputs.push_back(tcu::Vec4(-1.5f, 1.5f, -1.5f, 1.5f));
+		inputs.push_back(tcu::Vec4(0.25f, -0.75f, -0.25f, 0.75f));
+
+		// Random values, mostly in range.
+		for (int ndx = 0; ndx < 15; ndx++)
+		{
+			const float x = rnd.getFloat()*2.5f - 1.25f;
+			const float y = rnd.getFloat()*2.5f - 1.25f;
+			const float z = rnd.getFloat()*2.5f - 1.25f;
+			const float w = rnd.getFloat()*2.5f - 1.25f;
+			inputs.push_back(tcu::Vec4(x, y, z, w));
+		}
+
+		// Large random values.
+		for (int ndx = 0; ndx < 80; ndx++)
+		{
+			const float x = rnd.getFloat()*1e6f - 0.5e6f;
+			const float y = rnd.getFloat()*1e6f - 0.5e6f;
+			const float z = rnd.getFloat()*1e6f - 0.5e6f;
+			const float w = rnd.getFloat()*1e6f - 0.5e6f;
+			inputs.push_back(tcu::Vec4(x, y, z, w));
+		}
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < numValues; valNdx++)
+			{
+				const deUint16	ref0	= (deUint8)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].x(), -1.0f, 1.0f) * 127.0f), -(1<<7), (1<<7)-1);
+				const deUint16	ref1	= (deUint8)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].y(), -1.0f, 1.0f) * 127.0f), -(1<<7), (1<<7)-1);
+				const deUint16	ref2	= (deUint8)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].z(), -1.0f, 1.0f) * 127.0f), -(1<<7), (1<<7)-1);
+				const deUint16	ref3	= (deUint8)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].w(), -1.0f, 1.0f) * 127.0f), -(1<<7), (1<<7)-1);
+				const deUint32	ref		= (deUint32(ref3) << 24) | (deUint32(ref2) << 16) | (deUint32(ref1) << 8) | deUint32(ref0);
+				const deUint32	res		= outputs[valNdx];
+				const deUint16	res0	= (deUint8)(res & 0xff);
+				const deUint16	res1	= (deUint8)((res >> 8) & 0xff);
+				const deUint16	res2	= (deUint8)((res >> 16) & 0xff);
+				const deUint16	res3	= (deUint8)((res >> 24) & 0xff);
+				const int		diff0	= de::abs((int)ref0 - (int)res0);
+				const int		diff1	= de::abs((int)ref1 - (int)res1);
+				const int		diff2	= de::abs((int)ref2 - (int)res2);
+				const int		diff3	= de::abs((int)ref3 - (int)res3);
+
+				if (diff0 > maxDiff || diff1 > maxDiff || diff2 > maxDiff || diff3 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx
+															   << ", expected packSnorm4x8(" << inputs[valNdx] << ") = " << tcu::toHex(ref)
+															   << ", got " << tcu::toHex(res)
+															   << "\n  diffs = " << tcu::IVec4(diff0, diff1, diff2, diff3) << ", max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	glu::Precision m_precision;
+};
+
+class UnpackSnorm4x8Case : public ShaderPackingFunctionCase
+{
+public:
+	UnpackSnorm4x8Case (Context& context, glu::ShaderType shaderType)
+		: ShaderPackingFunctionCase(context, (string("unpacksnorm4x8") + getShaderTypePostfix(shaderType)).c_str(), "unpackSnorm4x8", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = unpackSnorm4x8(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32				maxDiff		= 1; // Rounding error.
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<deUint32>		inputs;
+		std::vector<tcu::Vec4>		outputs;
+
+		inputs.push_back(0x00000000u);
+		inputs.push_back(0x7fff8000u);
+		inputs.push_back(0x80007fffu);
+		inputs.push_back(0xffffffffu);
+		inputs.push_back(0x0001fffeu);
+
+		// Random values.
+		for (int ndx = 0; ndx < 95; ndx++)
+			inputs.push_back(rnd.getUint32());
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deInt8	in0		= (deInt8)(deUint8)(inputs[valNdx] & 0xff);
+				const deInt8	in1		= (deInt8)(deUint8)((inputs[valNdx] >> 8) & 0xff);
+				const deInt8	in2		= (deInt8)(deUint8)((inputs[valNdx] >> 16) & 0xff);
+				const deInt8	in3		= (deInt8)(deUint8)(inputs[valNdx] >> 24);
+				const float		ref0	= de::clamp(float(in0) / 127.f, -1.0f, 1.0f);
+				const float		ref1	= de::clamp(float(in1) / 127.f, -1.0f, 1.0f);
+				const float		ref2	= de::clamp(float(in2) / 127.f, -1.0f, 1.0f);
+				const float		ref3	= de::clamp(float(in3) / 127.f, -1.0f, 1.0f);
+				const float		res0	= outputs[valNdx].x();
+				const float		res1	= outputs[valNdx].y();
+				const float		res2	= outputs[valNdx].z();
+				const float		res3	= outputs[valNdx].w();
+
+				const deUint32	diff0	= getUlpDiff(ref0, res0);
+				const deUint32	diff1	= getUlpDiff(ref1, res1);
+				const deUint32	diff2	= getUlpDiff(ref2, res2);
+				const deUint32	diff3	= getUlpDiff(ref3, res3);
+
+				if (diff0 > maxDiff || diff1 > maxDiff || diff2 > maxDiff || diff3 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx << ",\n"
+															   << "  expected unpackSnorm4x8(" << tcu::toHex(inputs[valNdx]) << ") = "
+															   << "vec4(" << HexFloat(ref0) << ", " << HexFloat(ref1) << ", " << HexFloat(ref2) << ", " << HexFloat(ref3) << ")"
+															   << ", got vec4(" << HexFloat(res0) << ", " << HexFloat(res1) << ", " << HexFloat(res2) << ", " << HexFloat(res3) << ")"
+															   << "\n  ULP diffs = (" << diff0 << ", " << diff1 << ", " << diff2 << ", " << diff3 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+class PackUnorm4x8Case : public ShaderPackingFunctionCase
+{
+public:
+	PackUnorm4x8Case (Context& context, glu::ShaderType shaderType, glu::Precision precision)
+		: ShaderPackingFunctionCase	(context, (string("packunorm4x8") + getPrecisionPostfix(precision) + getShaderTypePostfix(shaderType)).c_str(), "packUnorm4x8", shaderType)
+		, m_precision				(precision)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_FLOAT_VEC4, precision)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = packUnorm4x8(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<tcu::Vec4>		inputs;
+		std::vector<deUint32>		outputs;
+		const int					maxDiff		= m_precision == glu::PRECISION_HIGHP	? 1	:		// Rounding only.
+												  m_precision == glu::PRECISION_MEDIUMP	? 1	:		// (2^-10) * (2^8) + 1
+												  m_precision == glu::PRECISION_LOWP	? 2	: 0;	// (2^-8) * (2^8) + 1
+
+		// Special values to check.
+		inputs.push_back(tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f));
+		inputs.push_back(tcu::Vec4(-1.0f, 1.0f, -1.0f, 1.0f));
+		inputs.push_back(tcu::Vec4(0.5f, -0.5f, -0.5f, 0.5f));
+		inputs.push_back(tcu::Vec4(-1.5f, 1.5f, -1.5f, 1.5f));
+		inputs.push_back(tcu::Vec4(0.25f, -0.75f, -0.25f, 0.75f));
+
+		// Random values, mostly in range.
+		for (int ndx = 0; ndx < 15; ndx++)
+		{
+			const float x = rnd.getFloat()*1.25f - 0.125f;
+			const float y = rnd.getFloat()*1.25f - 0.125f;
+			const float z = rnd.getFloat()*1.25f - 0.125f;
+			const float w = rnd.getFloat()*1.25f - 0.125f;
+			inputs.push_back(tcu::Vec4(x, y, z, w));
+		}
+
+		// Large random values.
+		for (int ndx = 0; ndx < 80; ndx++)
+		{
+			const float x = rnd.getFloat()*1e6f - 1e5f;
+			const float y = rnd.getFloat()*1e6f - 1e5f;
+			const float z = rnd.getFloat()*1e6f - 1e5f;
+			const float w = rnd.getFloat()*1e6f - 1e5f;
+			inputs.push_back(tcu::Vec4(x, y, z, w));
+		}
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deUint16	ref0	= (deUint8)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].x(), 0.0f, 1.0f) * 255.0f), 0, (1<<8)-1);
+				const deUint16	ref1	= (deUint8)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].y(), 0.0f, 1.0f) * 255.0f), 0, (1<<8)-1);
+				const deUint16	ref2	= (deUint8)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].z(), 0.0f, 1.0f) * 255.0f), 0, (1<<8)-1);
+				const deUint16	ref3	= (deUint8)de::clamp(deRoundFloatToInt32(de::clamp(inputs[valNdx].w(), 0.0f, 1.0f) * 255.0f), 0, (1<<8)-1);
+				const deUint32	ref		= (deUint32(ref3) << 24) | (deUint32(ref2) << 16) | (deUint32(ref1) << 8) | deUint32(ref0);
+				const deUint32	res		= outputs[valNdx];
+				const deUint16	res0	= (deUint8)(res & 0xff);
+				const deUint16	res1	= (deUint8)((res >> 8) & 0xff);
+				const deUint16	res2	= (deUint8)((res >> 16) & 0xff);
+				const deUint16	res3	= (deUint8)((res >> 24) & 0xff);
+				const int		diff0	= de::abs((int)ref0 - (int)res0);
+				const int		diff1	= de::abs((int)ref1 - (int)res1);
+				const int		diff2	= de::abs((int)ref2 - (int)res2);
+				const int		diff3	= de::abs((int)ref3 - (int)res3);
+
+				if (diff0 > maxDiff || diff1 > maxDiff || diff2 > maxDiff || diff3 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx
+															   << ", expected packUnorm4x8(" << inputs[valNdx] << ") = " << tcu::toHex(ref)
+															   << ", got " << tcu::toHex(res)
+															   << "\n  diffs = " << tcu::IVec4(diff0, diff1, diff2, diff3) << ", max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	glu::Precision m_precision;
+};
+
+class UnpackUnorm4x8Case : public ShaderPackingFunctionCase
+{
+public:
+	UnpackUnorm4x8Case (Context& context, glu::ShaderType shaderType)
+		: ShaderPackingFunctionCase(context, (string("unpackunorm4x8") + getShaderTypePostfix(shaderType)).c_str(), "unpackUnorm4x8", shaderType)
+	{
+		m_spec.inputs.push_back(Symbol("in0", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
+		m_spec.outputs.push_back(Symbol("out0", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP)));
+
+		m_spec.source = "out0 = unpackUnorm4x8(in0);";
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint32				maxDiff		= 1; // Rounding error.
+		de::Random					rnd			(deStringHash(getName()) ^ 0x776002);
+		std::vector<deUint32>		inputs;
+		std::vector<tcu::Vec4>		outputs;
+
+		inputs.push_back(0x00000000u);
+		inputs.push_back(0x7fff8000u);
+		inputs.push_back(0x80007fffu);
+		inputs.push_back(0xffffffffu);
+		inputs.push_back(0x0001fffeu);
+
+		// Random values.
+		for (int ndx = 0; ndx < 95; ndx++)
+			inputs.push_back(rnd.getUint32());
+
+		outputs.resize(inputs.size());
+
+		m_testCtx.getLog() << TestLog::Message << "Executing shader for " << inputs.size() << " input values" << tcu::TestLog::EndMessage;
+
+		{
+			const void*	in	= &inputs[0];
+			void*		out	= &outputs[0];
+
+			m_executor->useProgram();
+			m_executor->execute((int)inputs.size(), &in, &out);
+		}
+
+		// Verify
+		{
+			const int	numValues	= (int)inputs.size();
+			const int	maxPrints	= 10;
+			int			numFailed	= 0;
+
+			for (int valNdx = 0; valNdx < (int)inputs.size(); valNdx++)
+			{
+				const deUint8	in0		= (deUint8)(inputs[valNdx] & 0xff);
+				const deUint8	in1		= (deUint8)((inputs[valNdx] >> 8) & 0xff);
+				const deUint8	in2		= (deUint8)((inputs[valNdx] >> 16) & 0xff);
+				const deUint8	in3		= (deUint8)(inputs[valNdx] >> 24);
+				const float		ref0	= de::clamp(float(in0) / 255.f, 0.0f, 1.0f);
+				const float		ref1	= de::clamp(float(in1) / 255.f, 0.0f, 1.0f);
+				const float		ref2	= de::clamp(float(in2) / 255.f, 0.0f, 1.0f);
+				const float		ref3	= de::clamp(float(in3) / 255.f, 0.0f, 1.0f);
+				const float		res0	= outputs[valNdx].x();
+				const float		res1	= outputs[valNdx].y();
+				const float		res2	= outputs[valNdx].z();
+				const float		res3	= outputs[valNdx].w();
+
+				const deUint32	diff0	= getUlpDiff(ref0, res0);
+				const deUint32	diff1	= getUlpDiff(ref1, res1);
+				const deUint32	diff2	= getUlpDiff(ref2, res2);
+				const deUint32	diff3	= getUlpDiff(ref3, res3);
+
+				if (diff0 > maxDiff || diff1 > maxDiff || diff2 > maxDiff || diff3 > maxDiff)
+				{
+					if (numFailed < maxPrints)
+					{
+						m_testCtx.getLog() << TestLog::Message << "ERROR: Mismatch in value " << valNdx << ",\n"
+															   << "  expected unpackUnorm4x8(" << tcu::toHex(inputs[valNdx]) << ") = "
+															   << "vec4(" << HexFloat(ref0) << ", " << HexFloat(ref1) << ", " << HexFloat(ref2) << ", " << HexFloat(ref3) << ")"
+															   << ", got vec4(" << HexFloat(res0) << ", " << HexFloat(res1) << ", " << HexFloat(res2) << ", " << HexFloat(res3) << ")"
+															   << "\n  ULP diffs = (" << diff0 << ", " << diff1 << ", " << diff2 << ", " << diff3 << "), max diff = " << maxDiff
+										   << TestLog::EndMessage;
+					}
+					else if (numFailed == maxPrints)
+						m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+					numFailed += 1;
+				}
+			}
+
+			m_testCtx.getLog() << TestLog::Message << (numValues - numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+			m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									numFailed == 0 ? "Pass"					: "Result comparison failed");
+		}
+
+		return STOP;
+	}
+};
+
+ShaderPackingFunctionTests::ShaderPackingFunctionTests (Context& context)
+	: TestCaseGroup(context, "pack_unpack", "Floating-point pack and unpack function tests")
+{
+}
+
+ShaderPackingFunctionTests::~ShaderPackingFunctionTests (void)
+{
+}
+
+void ShaderPackingFunctionTests::init (void)
+{
+	// New built-in functions in GLES 3.1
+	{
+		const glu::ShaderType allShaderTypes[] =
+		{
+			glu::SHADERTYPE_VERTEX,
+			glu::SHADERTYPE_TESSELLATION_CONTROL,
+			glu::SHADERTYPE_TESSELLATION_EVALUATION,
+			glu::SHADERTYPE_GEOMETRY,
+			glu::SHADERTYPE_FRAGMENT,
+			glu::SHADERTYPE_COMPUTE
+		};
+
+		// packSnorm4x8
+		for (int prec = 0; prec < glu::PRECISION_LAST; prec++)
+		{
+			for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(allShaderTypes); shaderTypeNdx++)
+				addChild(new PackSnorm4x8Case(m_context, allShaderTypes[shaderTypeNdx], glu::Precision(prec)));
+		}
+
+		// unpackSnorm4x8
+		for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(allShaderTypes); shaderTypeNdx++)
+			addChild(new UnpackSnorm4x8Case(m_context, allShaderTypes[shaderTypeNdx]));
+
+		// packUnorm4x8
+		for (int prec = 0; prec < glu::PRECISION_LAST; prec++)
+		{
+			for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(allShaderTypes); shaderTypeNdx++)
+				addChild(new PackUnorm4x8Case(m_context, allShaderTypes[shaderTypeNdx], glu::Precision(prec)));
+		}
+
+		// unpackUnorm4x8
+		for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(allShaderTypes); shaderTypeNdx++)
+			addChild(new UnpackUnorm4x8Case(m_context, allShaderTypes[shaderTypeNdx]));
+	}
+
+	// GLES 3 functions in new shader types.
+	{
+		const glu::ShaderType newShaderTypes[] =
+		{
+			glu::SHADERTYPE_GEOMETRY,
+			glu::SHADERTYPE_COMPUTE
+		};
+
+		// packSnorm2x16
+		for (int prec = 0; prec < glu::PRECISION_LAST; prec++)
+		{
+			for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(newShaderTypes); shaderTypeNdx++)
+				addChild(new PackSnorm2x16Case(m_context, newShaderTypes[shaderTypeNdx], glu::Precision(prec)));
+		}
+
+		// unpackSnorm2x16
+		for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(newShaderTypes); shaderTypeNdx++)
+			addChild(new UnpackSnorm2x16Case(m_context, newShaderTypes[shaderTypeNdx]));
+
+		// packUnorm2x16
+		for (int prec = 0; prec < glu::PRECISION_LAST; prec++)
+		{
+			for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(newShaderTypes); shaderTypeNdx++)
+				addChild(new PackUnorm2x16Case(m_context, newShaderTypes[shaderTypeNdx], glu::Precision(prec)));
+		}
+
+		// unpackUnorm2x16
+		for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(newShaderTypes); shaderTypeNdx++)
+			addChild(new UnpackUnorm2x16Case(m_context, newShaderTypes[shaderTypeNdx]));
+
+		// packHalf2x16
+		for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(newShaderTypes); shaderTypeNdx++)
+			addChild(new PackHalf2x16Case(m_context, newShaderTypes[shaderTypeNdx]));
+
+		// unpackHalf2x16
+		for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(newShaderTypes); shaderTypeNdx++)
+			addChild(new UnpackHalf2x16Case(m_context, newShaderTypes[shaderTypeNdx]));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderPackingFunctionTests.hpp b/modules/gles31/functional/es31fShaderPackingFunctionTests.hpp
new file mode 100644
index 0000000..a4823ce
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderPackingFunctionTests.hpp
@@ -0,0 +1,52 @@
+#ifndef _ES31FSHADERPACKINGFUNCTIONTESTS_HPP
+#define _ES31FSHADERPACKINGFUNCTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Floating-point packing and unpacking function tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderPackingFunctionTests : public TestCaseGroup
+{
+public:
+									ShaderPackingFunctionTests		(Context& context);
+	virtual							~ShaderPackingFunctionTests		(void);
+
+	virtual void					init							(void);
+
+private:
+									ShaderPackingFunctionTests		(const ShaderPackingFunctionTests&);		// not allowed!
+	ShaderPackingFunctionTests&		operator=						(const ShaderPackingFunctionTests&);		// not allowed!
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERPACKINGFUNCTIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderSharedVarTests.cpp b/modules/gles31/functional/es31fShaderSharedVarTests.cpp
new file mode 100644
index 0000000..dc134d4
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderSharedVarTests.cpp
@@ -0,0 +1,421 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLSL Shared variable tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderSharedVarTests.hpp"
+#include "es31fShaderAtomicOpTests.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluProgramInterfaceQuery.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuFormatUtil.hpp"
+#include "deRandom.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <algorithm>
+#include <set>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+using tcu::UVec3;
+using std::set;
+using namespace glu;
+
+enum
+{
+	MAX_VALUE_ARRAY_LENGTH	= 16	// * 2 * sizeof(mat4) = 512
+};
+
+template<typename T, int Size>
+static inline T product (const tcu::Vector<T, Size>& v)
+{
+	T res = v[0];
+	for (int ndx = 1; ndx < Size; ndx++)
+		res *= v[ndx];
+	return res;
+}
+
+class SharedBasicVarCase : public TestCase
+{
+public:
+							SharedBasicVarCase		(Context& context, const char* name, DataType basicType, Precision precision, const tcu::UVec3& workGroupSize);
+							~SharedBasicVarCase		(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+							SharedBasicVarCase		(const SharedBasicVarCase& other);
+	SharedBasicVarCase&		operator=				(const SharedBasicVarCase& other);
+
+	const DataType			m_basicType;
+	const Precision			m_precision;
+	const tcu::UVec3		m_workGroupSize;
+
+	ShaderProgram*			m_program;
+};
+
+static std::string getBasicCaseDescription (DataType basicType, Precision precision, const tcu::UVec3& workGroupSize)
+{
+	std::ostringstream str;
+	if (precision != PRECISION_LAST)
+		str << getPrecisionName(precision) << " ";
+	str << getDataTypeName(basicType) << ", work group size = " << workGroupSize;
+	return str.str();
+}
+
+SharedBasicVarCase::SharedBasicVarCase (Context& context, const char* name, DataType basicType, Precision precision, const tcu::UVec3& workGroupSize)
+	: TestCase			(context, name, getBasicCaseDescription(basicType, precision, workGroupSize).c_str())
+	, m_basicType		(basicType)
+	, m_precision		(precision)
+	, m_workGroupSize	(workGroupSize)
+	, m_program			(DE_NULL)
+{
+}
+
+SharedBasicVarCase::~SharedBasicVarCase (void)
+{
+	SharedBasicVarCase::deinit();
+}
+
+void SharedBasicVarCase::init (void)
+{
+	const int			valArrayLength	= de::min<int>(MAX_VALUE_ARRAY_LENGTH, product(m_workGroupSize));
+	const char*			precName		= m_precision != glu::PRECISION_LAST ? getPrecisionName(m_precision) : "";
+	const char*			typeName		= getDataTypeName(m_basicType);
+	std::ostringstream	src;
+
+	src << "#version 310 es\n"
+		<< "layout (local_size_x = " << m_workGroupSize[0]
+		<< ", local_size_y = " << m_workGroupSize[1]
+		<< ", local_size_z = " << m_workGroupSize[2]
+		<< ") in;\n"
+		<< "const uint LOCAL_SIZE = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
+		<< "shared " << precName << " " << typeName << " s_var;\n"
+		<< "uniform " << precName << " " << typeName << " u_val[" << valArrayLength << "];\n"
+		<< "uniform " << precName << " " << typeName << " u_ref[" << valArrayLength << "];\n"
+		<< "uniform uint u_numIters;\n"
+		<< "layout(binding = 0) buffer Result\n"
+		<< "{\n"
+		<< "	bool isOk[LOCAL_SIZE];\n"
+		<< "};\n"
+		<< "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	bool allOk = true;\n"
+		<< "	for (uint ndx = 0u; ndx < u_numIters; ndx++)\n"
+		<< "	{\n"
+		<< "		if (ndx == gl_LocalInvocationIndex)\n"
+		<< "			s_var = u_val[ndx%uint(u_val.length())];\n"
+		<< "\n"
+		<< "		memoryBarrierShared();\n"
+		<< "\n"
+		<< "		if (s_var != u_ref[ndx%uint(u_ref.length())])\n"
+		<< "			allOk = false;\n"
+		<< "	}\n"
+		<< "\n"
+		<< "	isOk[gl_LocalInvocationIndex] = allOk;\n"
+		<< "}\n";
+
+	DE_ASSERT(!m_program);
+	m_program = new ShaderProgram(m_context.getRenderContext(), ProgramSources() << ComputeSource(src.str()));
+
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+	{
+		delete m_program;
+		m_program = DE_NULL;
+		throw tcu::TestError("Compile failed");
+	}
+}
+
+void SharedBasicVarCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+SharedBasicVarCase::IterateResult SharedBasicVarCase::iterate (void)
+{
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	const deUint32				program			= m_program->getProgram();
+	Buffer						outputBuffer	(m_context.getRenderContext());
+	const deUint32				outBlockNdx		= gl.getProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, "Result");
+	const InterfaceBlockInfo	outBlockInfo	= getProgramInterfaceBlockInfo(gl, program, GL_SHADER_STORAGE_BLOCK, outBlockNdx);
+
+	gl.useProgram(program);
+
+	// Setup input values.
+	{
+		const int		numValues		= (int)product(m_workGroupSize);
+		const int		valLoc			= gl.getUniformLocation(program, "u_val[0]");
+		const int		refLoc			= gl.getUniformLocation(program, "u_ref[0]");
+		const int		iterCountLoc	= gl.getUniformLocation(program, "u_numIters");
+		const int		scalarSize		= getDataTypeScalarSize(m_basicType);
+
+		if (isDataTypeFloatOrVec(m_basicType))
+		{
+			const int		maxInt			= m_precision == glu::PRECISION_LOWP ? 2 : 1024;
+			const int		minInt			= -de::min(numValues/2, maxInt);
+			vector<float>	values			(numValues*scalarSize);
+
+			for (int ndx = 0; ndx < (int)values.size(); ndx++)
+				values[ndx] = float(minInt + (ndx % (maxInt-minInt+1)));
+
+			for (int uNdx = 0; uNdx < 2; uNdx++)
+			{
+				const int location = uNdx == 1 ? refLoc : valLoc;
+
+				if (scalarSize == 1)		gl.uniform1fv(location, numValues, &values[0]);
+				else if (scalarSize == 2)	gl.uniform2fv(location, numValues, &values[0]);
+				else if (scalarSize == 3)	gl.uniform3fv(location, numValues, &values[0]);
+				else if (scalarSize == 4)	gl.uniform4fv(location, numValues, &values[0]);
+			}
+		}
+		else if (isDataTypeIntOrIVec(m_basicType))
+		{
+			const int		maxInt			= m_precision == glu::PRECISION_LOWP ? 64 : 1024;
+			const int		minInt			= -de::min(numValues/2, maxInt);
+			vector<int>		values			(numValues*scalarSize);
+
+			for (int ndx = 0; ndx < (int)values.size(); ndx++)
+				values[ndx] = minInt + (ndx % (maxInt-minInt+1));
+
+			for (int uNdx = 0; uNdx < 2; uNdx++)
+			{
+				const int location = uNdx == 1 ? refLoc : valLoc;
+
+				if (scalarSize == 1)		gl.uniform1iv(location, numValues, &values[0]);
+				else if (scalarSize == 2)	gl.uniform2iv(location, numValues, &values[0]);
+				else if (scalarSize == 3)	gl.uniform3iv(location, numValues, &values[0]);
+				else if (scalarSize == 4)	gl.uniform4iv(location, numValues, &values[0]);
+			}
+		}
+		else if (isDataTypeUintOrUVec(m_basicType))
+		{
+			const deUint32		maxInt		= m_precision == glu::PRECISION_LOWP ? 128 : 1024;
+			vector<deUint32>	values		(numValues*scalarSize);
+
+			for (int ndx = 0; ndx < (int)values.size(); ndx++)
+				values[ndx] = ndx % (maxInt+1);
+
+			for (int uNdx = 0; uNdx < 2; uNdx++)
+			{
+				const int location = uNdx == 1 ? refLoc : valLoc;
+
+				if (scalarSize == 1)		gl.uniform1uiv(location, numValues, &values[0]);
+				else if (scalarSize == 2)	gl.uniform2uiv(location, numValues, &values[0]);
+				else if (scalarSize == 3)	gl.uniform3uiv(location, numValues, &values[0]);
+				else if (scalarSize == 4)	gl.uniform4uiv(location, numValues, &values[0]);
+			}
+		}
+		else if (isDataTypeBoolOrBVec(m_basicType))
+		{
+			de::Random		rnd				(0x324f);
+			vector<int>		values			(numValues*scalarSize);
+
+			for (int ndx = 0; ndx < (int)values.size(); ndx++)
+				values[ndx] = rnd.getBool() ? 1 : 0;
+
+			for (int uNdx = 0; uNdx < 2; uNdx++)
+			{
+				const int location = uNdx == 1 ? refLoc : valLoc;
+
+				if (scalarSize == 1)		gl.uniform1iv(location, numValues, &values[0]);
+				else if (scalarSize == 2)	gl.uniform2iv(location, numValues, &values[0]);
+				else if (scalarSize == 3)	gl.uniform3iv(location, numValues, &values[0]);
+				else if (scalarSize == 4)	gl.uniform4iv(location, numValues, &values[0]);
+			}
+		}
+		else if (isDataTypeMatrix(m_basicType))
+		{
+			const int		maxInt			= m_precision == glu::PRECISION_LOWP ? 2 : 1024;
+			const int		minInt			= -de::min(numValues/2, maxInt);
+			vector<float>	values			(numValues*scalarSize);
+
+			for (int ndx = 0; ndx < (int)values.size(); ndx++)
+				values[ndx] = float(minInt + (ndx % (maxInt-minInt+1)));
+
+			for (int uNdx = 0; uNdx < 2; uNdx++)
+			{
+				const int location = uNdx == 1 ? refLoc : valLoc;
+
+				switch (m_basicType)
+				{
+					case TYPE_FLOAT_MAT2:	gl.uniformMatrix2fv  (location, numValues, DE_FALSE, &values[0]);	break;
+					case TYPE_FLOAT_MAT2X3:	gl.uniformMatrix2x3fv(location, numValues, DE_FALSE, &values[0]);	break;
+					case TYPE_FLOAT_MAT2X4:	gl.uniformMatrix2x4fv(location, numValues, DE_FALSE, &values[0]);	break;
+					case TYPE_FLOAT_MAT3X2:	gl.uniformMatrix3x2fv(location, numValues, DE_FALSE, &values[0]);	break;
+					case TYPE_FLOAT_MAT3:	gl.uniformMatrix3fv  (location, numValues, DE_FALSE, &values[0]);	break;
+					case TYPE_FLOAT_MAT3X4:	gl.uniformMatrix3x4fv(location, numValues, DE_FALSE, &values[0]);	break;
+					case TYPE_FLOAT_MAT4X2:	gl.uniformMatrix4x2fv(location, numValues, DE_FALSE, &values[0]);	break;
+					case TYPE_FLOAT_MAT4X3:	gl.uniformMatrix4x3fv(location, numValues, DE_FALSE, &values[0]);	break;
+					case TYPE_FLOAT_MAT4:	gl.uniformMatrix4fv  (location, numValues, DE_FALSE, &values[0]);	break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+
+		gl.uniform1ui(iterCountLoc, product(m_workGroupSize));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Input value setup failed");
+	}
+
+	// Setup output buffer.
+	{
+		vector<deUint8> emptyData(outBlockInfo.dataSize);
+		std::fill(emptyData.begin(), emptyData.end(), 0);
+
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, outBlockInfo.dataSize, &emptyData[0], GL_STATIC_READ);
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
+	}
+
+	gl.dispatchCompute(1, 1, 1);
+
+	// Read back and compare
+	{
+		const deUint32				numValues	= product(m_workGroupSize);
+		const InterfaceVariableInfo	outVarInfo	= getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, outBlockInfo.activeVariables[0]);
+		const void*					resPtr		= gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, outBlockInfo.dataSize, GL_MAP_READ_BIT);
+		const int					maxErrMsg	= 10;
+		int							numFailed	= 0;
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+		TCU_CHECK(resPtr);
+
+		for (deUint32 ndx = 0; ndx < numValues; ndx++)
+		{
+			const int resVal = *((const int*)((const deUint8*)resPtr + outVarInfo.offset + outVarInfo.arrayStride*ndx));
+
+			if (resVal == 0)
+			{
+				if (numFailed < maxErrMsg)
+					m_testCtx.getLog() << TestLog::Message << "ERROR: isOk[" << ndx << "] = " << resVal << " != true" << TestLog::EndMessage;
+				else if (numFailed == maxErrMsg)
+					m_testCtx.getLog() << TestLog::Message << "..." << TestLog::EndMessage;
+
+				numFailed += 1;
+			}
+		}
+
+		gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
+
+		m_testCtx.getLog() << TestLog::Message << (numValues-numFailed) << " / " << numValues << " values passed" << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								numFailed == 0 ? "Pass"					: "Comparison failed");
+	}
+
+	return STOP;
+}
+
+ShaderSharedVarTests::ShaderSharedVarTests (Context& context)
+	: TestCaseGroup(context, "shared_var", "Shared Variable Tests")
+{
+}
+
+ShaderSharedVarTests::~ShaderSharedVarTests (void)
+{
+}
+
+void ShaderSharedVarTests::init (void)
+{
+	// .basic_type
+	{
+		tcu::TestCaseGroup *const basicTypeGroup = new tcu::TestCaseGroup(m_testCtx, "basic_type", "Basic Types");
+		addChild(basicTypeGroup);
+
+		for (int basicType = TYPE_FLOAT; basicType <= TYPE_BOOL_VEC4; basicType++)
+		{
+			if (glu::isDataTypeBoolOrBVec(DataType(basicType)))
+			{
+				const tcu::UVec3	workGroupSize	(2,1,3);
+				basicTypeGroup->addChild(new SharedBasicVarCase(m_context, getDataTypeName(DataType(basicType)), DataType(basicType), PRECISION_LAST, workGroupSize));
+			}
+			else
+			{
+				for (int precision = 0; precision < PRECISION_LAST; precision++)
+				{
+					const tcu::UVec3	workGroupSize	(2,1,3);
+					const string		name			= string(getDataTypeName(DataType(basicType))) + "_" + getPrecisionName(Precision(precision));
+
+					basicTypeGroup->addChild(new SharedBasicVarCase(m_context, name.c_str(), DataType(basicType), Precision(precision), workGroupSize));
+				}
+			}
+		}
+	}
+
+	// .work_group_size
+	{
+		tcu::TestCaseGroup *const workGroupSizeGroup = new tcu::TestCaseGroup(m_testCtx, "work_group_size", "Shared Variables with Various Work Group Sizes");
+		addChild(workGroupSizeGroup);
+
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "float_1_1_1",		TYPE_FLOAT,			PRECISION_HIGHP,	tcu::UVec3(1,1,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "float_64_1_1",		TYPE_FLOAT,			PRECISION_HIGHP,	tcu::UVec3(64,1,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "float_1_64_1",		TYPE_FLOAT,			PRECISION_HIGHP,	tcu::UVec3(1,64,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "float_1_1_64",		TYPE_FLOAT,			PRECISION_HIGHP,	tcu::UVec3(1,1,64)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "float_256_1_1",		TYPE_FLOAT,			PRECISION_HIGHP,	tcu::UVec3(256,1,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "float_1_256_1",		TYPE_FLOAT,			PRECISION_HIGHP,	tcu::UVec3(1,256,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "float_17_5_9",		TYPE_FLOAT,			PRECISION_HIGHP,	tcu::UVec3(17,5,9)));
+
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "vec4_1_1_1",		TYPE_FLOAT_VEC4,	PRECISION_HIGHP,	tcu::UVec3(1,1,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "vec4_64_1_1",		TYPE_FLOAT_VEC4,	PRECISION_HIGHP,	tcu::UVec3(64,1,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "vec4_1_64_1",		TYPE_FLOAT_VEC4,	PRECISION_HIGHP,	tcu::UVec3(1,64,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "vec4_1_1_64",		TYPE_FLOAT_VEC4,	PRECISION_HIGHP,	tcu::UVec3(1,1,64)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "vec4_256_1_1",		TYPE_FLOAT_VEC4,	PRECISION_HIGHP,	tcu::UVec3(256,1,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "vec4_1_256_1",		TYPE_FLOAT_VEC4,	PRECISION_HIGHP,	tcu::UVec3(1,256,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "vec4_17_5_9",		TYPE_FLOAT_VEC4,	PRECISION_HIGHP,	tcu::UVec3(17,5,9)));
+
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "mat4_1_1_1",		TYPE_FLOAT_MAT4,	PRECISION_HIGHP,	tcu::UVec3(1,1,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "mat4_64_1_1",		TYPE_FLOAT_MAT4,	PRECISION_HIGHP,	tcu::UVec3(64,1,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "mat4_1_64_1",		TYPE_FLOAT_MAT4,	PRECISION_HIGHP,	tcu::UVec3(1,64,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "mat4_1_1_64",		TYPE_FLOAT_MAT4,	PRECISION_HIGHP,	tcu::UVec3(1,1,64)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "mat4_256_1_1",		TYPE_FLOAT_MAT4,	PRECISION_HIGHP,	tcu::UVec3(256,1,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "mat4_1_256_1",		TYPE_FLOAT_MAT4,	PRECISION_HIGHP,	tcu::UVec3(1,256,1)));
+		workGroupSizeGroup->addChild(new SharedBasicVarCase(m_context, "mat4_17_5_9",		TYPE_FLOAT_MAT4,	PRECISION_HIGHP,	tcu::UVec3(17,5,9)));
+	}
+
+	// .atomic
+	addChild(new ShaderAtomicOpTests(m_context, "atomic", ATOMIC_OPERAND_SHARED_VARIABLE));
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderSharedVarTests.hpp b/modules/gles31/functional/es31fShaderSharedVarTests.hpp
new file mode 100644
index 0000000..d2bacb0
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderSharedVarTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSHADERSHAREDVARTESTS_HPP
+#define _ES31FSHADERSHAREDVARTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLSL Shared variable tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderSharedVarTests : public TestCaseGroup
+{
+public:
+							ShaderSharedVarTests	(Context& context);
+							~ShaderSharedVarTests	(void);
+
+	void					init					(void);
+
+private:
+							ShaderSharedVarTests	(const ShaderSharedVarTests& other);
+	ShaderSharedVarTests&	operator=				(const ShaderSharedVarTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERSHAREDVARTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderStateQueryTests.cpp b/modules/gles31/functional/es31fShaderStateQueryTests.cpp
new file mode 100644
index 0000000..36a7b98
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderStateQueryTests.cpp
@@ -0,0 +1,165 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader state query tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderStateQueryTests.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+class SamplerTypeCase : public TestCase
+{
+public:
+					SamplerTypeCase	(Context& ctx, const char* name, const char* desc);
+
+private:
+	IterateResult	iterate			(void);
+};
+
+SamplerTypeCase::SamplerTypeCase (Context& ctx, const char* name, const char* desc)
+	: TestCase(ctx, name, desc)
+{
+}
+
+SamplerTypeCase::IterateResult SamplerTypeCase::iterate (void)
+{
+	static const struct SamplerType
+	{
+		glw::GLenum glType;
+		const char* typeStr;
+		const char* samplePosStr;
+	} samplerTypes[] =
+	{
+		{ GL_SAMPLER_2D_MULTISAMPLE,						"sampler2DMS",			"ivec2(gl_FragCoord.xy)"	},
+		{ GL_INT_SAMPLER_2D_MULTISAMPLE,					"isampler2DMS",			"ivec2(gl_FragCoord.xy)"	},
+		{ GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE,			"usampler2DMS",			"ivec2(gl_FragCoord.xy)"	},
+		{ GL_SAMPLER_2D_MULTISAMPLE_ARRAY,					"sampler2DMSArray",		"ivec3(gl_FragCoord.xyz)"	},
+		{ GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY,				"isampler2DMSArray",	"ivec3(gl_FragCoord.xyz)"	},
+		{ GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY,		"usampler2DMSArray",	"ivec3(gl_FragCoord.xyz)"	},
+	};
+
+	static const char* const vertexSource			=	"#version 310 es\n"
+														"in highp vec4 a_position;\n"
+														"void main(void)\n"
+														"{\n"
+														"	gl_Position = a_position;\n"
+														"}\n";
+	static const char* const fragmentSourceTemplate	=	"#version 310 es\n"
+														"uniform highp ${SAMPLERTYPE} u_sampler;\n"
+														"layout(location = 0) out highp vec4 dEQP_FragColor;\n"
+														"void main(void)\n"
+														"{\n"
+														"	dEQP_FragColor = vec4(texelFetch(u_sampler, ${POSITION}, 0));\n"
+														"}\n";
+
+	bool error = false;
+
+	for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(samplerTypes); ++typeNdx)
+	{
+		const tcu::ScopedLogSection			section		(m_testCtx.getLog(), std::string(samplerTypes[typeNdx].typeStr), std::string() + "Sampler type " + samplerTypes[typeNdx].typeStr);
+		std::map<std::string, std::string>	shaderArgs;
+
+		shaderArgs["SAMPLERTYPE"]	= samplerTypes[typeNdx].typeStr;
+		shaderArgs["POSITION"]		= samplerTypes[typeNdx].samplePosStr;
+
+		{
+			const std::string			fragmentSource	= tcu::StringTemplate(fragmentSourceTemplate).specialize(shaderArgs);
+			const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+			glu::ShaderProgram			program			(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexSource) << glu::FragmentSource(fragmentSource));
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Building program with uniform sampler of type " << samplerTypes[typeNdx].typeStr << tcu::TestLog::EndMessage;
+
+			if (!program.isOk())
+			{
+				m_testCtx.getLog() << program;
+				throw tcu::TestError("could not build shader");
+			}
+
+			// only one uniform -- uniform at index 0
+			{
+				int uniforms = 0;
+				gl.getProgramiv(program.getProgram(), GL_ACTIVE_UNIFORMS, &uniforms);
+
+				if (uniforms != 1)
+					throw tcu::TestError("Unexpected GL_ACTIVE_UNIFORMS, expected 1");
+			}
+
+			m_testCtx.getLog() << tcu::TestLog::Message << "Verifying uniform type." << tcu::TestLog::EndMessage;
+
+			// check type
+			{
+				const glw::GLuint	uniformIndex	= 0;
+				glw::GLint			type			= 0;
+
+				gl.getActiveUniformsiv(program.getProgram(), 1, &uniformIndex, GL_UNIFORM_TYPE, &type);
+
+				if (type != (glw::GLint)samplerTypes[typeNdx].glType)
+				{
+					m_testCtx.getLog() << tcu::TestLog::Message << "Invalid type, expected " << samplerTypes[typeNdx].glType << ", got " << type << tcu::TestLog::EndMessage;
+					error = true;
+				}
+			}
+
+			GLU_EXPECT_NO_ERROR(gl.getError(), "");
+		}
+	}
+
+	if (!error)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid uniform type");
+
+	return STOP;
+}
+
+} // anonymous
+
+ShaderStateQueryTests::ShaderStateQueryTests (Context& context)
+	: TestCaseGroup(context, "shader", "Shader state query tests")
+{
+}
+
+ShaderStateQueryTests::~ShaderStateQueryTests (void)
+{
+}
+
+void ShaderStateQueryTests::init (void)
+{
+	// sampler type query
+	addChild(new SamplerTypeCase(m_context, "sampler_type", "Sampler type cases"));
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderStateQueryTests.hpp b/modules/gles31/functional/es31fShaderStateQueryTests.hpp
new file mode 100644
index 0000000..52bf0c4
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderStateQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSHADERSTATEQUERYTESTS_HPP
+#define _ES31FSHADERSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader state query tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderStateQueryTests : public TestCaseGroup
+{
+public:
+							ShaderStateQueryTests	(Context& context);
+							~ShaderStateQueryTests	(void);
+
+	void					init					(void);
+
+private:
+							ShaderStateQueryTests	(const ShaderStateQueryTests& other);
+	ShaderStateQueryTests&	operator=				(const ShaderStateQueryTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERSTATEQUERYTESTS_HPP
diff --git a/modules/gles31/functional/es31fShaderTextureSizeTests.cpp b/modules/gles31/functional/es31fShaderTextureSizeTests.cpp
new file mode 100644
index 0000000..6d68791
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderTextureSizeTests.cpp
@@ -0,0 +1,521 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample texture size tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fShaderTextureSizeTests.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluContextInfo.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deStringUtil.hpp"
+
+using namespace glw;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+static const char* const s_positionVertexShaderSource =	"#version 310 es\n"
+														"in highp vec4 a_position;\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = a_position;\n"
+														"}\n";
+
+class TextureSizeCase : public TestCase
+{
+public:
+	enum TextureType
+	{
+		TEXTURE_FLOAT_2D = 0,
+		TEXTURE_FLOAT_2D_ARRAY,
+		TEXTURE_INT_2D,
+		TEXTURE_INT_2D_ARRAY,
+		TEXTURE_UINT_2D,
+		TEXTURE_UINT_2D_ARRAY,
+
+		TEXTURE_LAST
+	};
+
+							TextureSizeCase		(Context& context, const char* name, const char* desc, TextureType type, int samples);
+							~TextureSizeCase	(void);
+
+private:
+	void					init				(void);
+	void					deinit				(void);
+	IterateResult			iterate				(void);
+
+	std::string				genFragmentSource	(void);
+	glw::GLenum				getTextureGLTarget	(void);
+	glw::GLenum				getTextureGLInternalFormat (void);
+
+	void					createTexture		(const tcu::IVec3& size);
+	void					deleteTexture		(void);
+	void					runShader			(tcu::Surface& dst, const tcu::IVec3& size);
+	bool					verifyImage			(const tcu::Surface& dst);
+
+	const TextureType		m_type;
+	const int				m_numSamples;
+	const bool				m_isArrayType;
+
+	glw::GLuint				m_texture;
+	glw::GLuint				m_vbo;
+	glu::ShaderProgram*		m_shader;
+	std::vector<tcu::IVec3>	m_iterations;
+	int						m_iteration;
+
+	bool					m_allIterationsPassed;
+	bool					m_allCasesSkipped;
+};
+
+TextureSizeCase::TextureSizeCase (Context& context, const char* name, const char* desc, TextureType type, int samples)
+	: TestCase				(context, name, desc)
+	, m_type				(type)
+	, m_numSamples			(samples)
+	, m_isArrayType			(m_type == TEXTURE_FLOAT_2D_ARRAY || m_type == TEXTURE_INT_2D_ARRAY || m_type == TEXTURE_UINT_2D_ARRAY)
+	, m_texture				(0)
+	, m_vbo					(0)
+	, m_shader				(DE_NULL)
+	, m_iteration			(0)
+	, m_allIterationsPassed	(true)
+	, m_allCasesSkipped		(true)
+{
+	DE_ASSERT(type < TEXTURE_LAST);
+}
+
+TextureSizeCase::~TextureSizeCase (void)
+{
+	deinit();
+}
+
+void TextureSizeCase::init (void)
+{
+	static const tcu::IVec2 testSizes2D[] =
+	{
+		tcu::IVec2(1,	1),
+		tcu::IVec2(1,	4),
+		tcu::IVec2(4,	8),
+		tcu::IVec2(21,	11),
+		tcu::IVec2(107,	254),
+		tcu::IVec2(-1,	-1),
+	};
+	static const tcu::IVec3 testSizes3D[] =
+	{
+		tcu::IVec3(1,	1,		1),
+		tcu::IVec3(1,	4,		7),
+		tcu::IVec3(4,	8,		12),
+		tcu::IVec3(21,	11,		9),
+		tcu::IVec3(107,	254,	2),
+		tcu::IVec3(-1,	-1,		3),
+		tcu::IVec3(4,	4,		-1),
+		tcu::IVec3(-1,	-1,		-1),
+	};
+	static const tcu::Vec4 fullscreenQuad[] =
+	{
+		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)
+	};
+
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	glw::GLint				maxTextureSize		= 0;
+	glw::GLint				maxTextureLayers	= 0;
+	glw::GLint				maxSamples			= 0;
+
+	gl.getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+	gl.getIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxTextureLayers);
+	gl.getInternalformativ(getTextureGLTarget(), getTextureGLInternalFormat(), GL_SAMPLES, 1, &maxSamples);
+
+	// requirements
+	if (m_isArrayType && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
+		throw tcu::NotSupportedError("Test requires OES_texture_storage_multisample_2d_array extension");
+	if (m_context.getRenderTarget().getWidth() < 1 || m_context.getRenderTarget().getHeight() < 1)
+		throw tcu::NotSupportedError("rendertarget size must be at least 1x1");
+	if (m_numSamples > maxSamples)
+		throw tcu::NotSupportedError("sample count is not supported");
+
+	// gen shade
+
+	m_shader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_positionVertexShaderSource) << glu::FragmentSource(genFragmentSource()));
+	m_testCtx.getLog() << *m_shader;
+	if (!m_shader->isOk())
+		throw tcu::TestError("shader build failed");
+
+	// gen buffer
+
+	gl.genBuffers(1, &m_vbo);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_vbo);
+	gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);
+
+	// gen iterations
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "GL_MAX_TEXTURE_SIZE = " << maxTextureSize << "\n"
+		<< "GL_MAX_ARRAY_TEXTURE_LAYERS = " << maxTextureLayers
+		<< tcu::TestLog::EndMessage;
+
+	if (!m_isArrayType)
+	{
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(testSizes2D); ++ndx)
+		{
+			if (testSizes2D[ndx].x() <= maxTextureSize && testSizes2D[ndx].y() <= maxTextureSize)
+			{
+				const int w = (testSizes2D[ndx].x() < 0) ? (maxTextureSize) : (testSizes2D[ndx].x());
+				const int h = (testSizes2D[ndx].y() < 0) ? (maxTextureSize) : (testSizes2D[ndx].y());
+
+				m_iterations.push_back(tcu::IVec3(w, h, 0));
+			}
+		}
+	}
+	else
+	{
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(testSizes3D); ++ndx)
+		{
+			if (testSizes3D[ndx].x() <= maxTextureSize && testSizes3D[ndx].y() <= maxTextureSize && testSizes3D[ndx].z() <= maxTextureLayers)
+			{
+				const int w = (testSizes3D[ndx].x() < 0) ? (maxTextureSize)		: (testSizes3D[ndx].x());
+				const int h = (testSizes3D[ndx].y() < 0) ? (maxTextureSize)		: (testSizes3D[ndx].y());
+				const int d = (testSizes3D[ndx].z() < 0) ? (maxTextureLayers)	: (testSizes3D[ndx].z());
+
+				m_iterations.push_back(tcu::IVec3(w, h, d));
+			}
+		}
+	}
+}
+
+void TextureSizeCase::deinit (void)
+{
+	if (m_texture)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texture);
+		m_texture = 0;
+	}
+
+	if (m_vbo)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_vbo);
+		m_vbo = 0;
+	}
+
+	if (m_shader)
+	{
+		delete m_shader;
+		m_shader = DE_NULL;
+	}
+}
+
+TextureSizeCase::IterateResult TextureSizeCase::iterate (void)
+{
+	tcu::Surface	result		(1, 1);
+	bool			skipTest	= false;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "\nIteration " << (m_iteration+1) << " / " << (int)m_iterations.size() << tcu::TestLog::EndMessage;
+
+	try
+	{
+		// set texture size
+
+		createTexture(m_iterations[m_iteration]);
+
+		// query texture size
+
+		runShader(result, m_iterations[m_iteration]);
+	}
+	catch (glu::OutOfMemoryError&)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Got GL_OUT_OF_MEMORY, skipping this size" << tcu::TestLog::EndMessage;
+
+		skipTest = true;
+	}
+
+	// free resources
+
+	deleteTexture();
+
+	// queried value was correct?
+
+	if (!skipTest)
+	{
+		m_allCasesSkipped = false;
+
+		if (!verifyImage(result))
+			m_allIterationsPassed = false;
+	}
+
+	// final result
+
+	if (++m_iteration < (int)m_iterations.size())
+		return CONTINUE;
+
+	if (!m_allIterationsPassed)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "One or more test sizes failed." << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid texture size");
+	}
+	else if (m_allCasesSkipped)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Could not test any texture size, texture creation failed." << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "All test texture creations failed");
+	}
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "All texture sizes passed." << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+
+	return STOP;
+}
+
+std::string TextureSizeCase::genFragmentSource (void)
+{
+	static const char* const templateSource =	"#version 310 es\n"
+												"${EXTENSION_STATEMENT}"
+												"layout(location = 0) out highp vec4 fragColor;\n"
+												"uniform highp ${SAMPLERTYPE} u_sampler;\n"
+												"uniform highp ${SIZETYPE} u_size;\n"
+												"void main (void)\n"
+												"{\n"
+												"	const highp vec4 okColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+												"	const highp vec4 failColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+												"	fragColor = (textureSize(u_sampler) == u_size) ? (okColor) : (failColor);\n"
+												"}\n";
+
+	std::map<std::string, std::string> args;
+
+	switch (m_type)
+	{
+		case TEXTURE_FLOAT_2D:			args["SAMPLERTYPE"] = "sampler2DMS";		break;
+		case TEXTURE_FLOAT_2D_ARRAY:	args["SAMPLERTYPE"] = "sampler2DMSArray";	break;
+		case TEXTURE_INT_2D:			args["SAMPLERTYPE"] = "isampler2DMS";		break;
+		case TEXTURE_INT_2D_ARRAY:		args["SAMPLERTYPE"] = "isampler2DMSArray";	break;
+		case TEXTURE_UINT_2D:			args["SAMPLERTYPE"] = "usampler2DMS";		break;
+		case TEXTURE_UINT_2D_ARRAY:		args["SAMPLERTYPE"] = "usampler2DMSArray";	break;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	if (!m_isArrayType)
+		args["SIZETYPE"] = "ivec2";
+	else
+		args["SIZETYPE"] = "ivec3";
+
+	if (m_isArrayType)
+		args["EXTENSION_STATEMENT"] = "#extension GL_OES_texture_storage_multisample_2d_array : require\n";
+	else
+		args["EXTENSION_STATEMENT"] = "";
+
+	return tcu::StringTemplate(templateSource).specialize(args);
+}
+
+glw::GLenum TextureSizeCase::getTextureGLTarget (void)
+{
+	switch (m_type)
+	{
+		case TEXTURE_FLOAT_2D:			return GL_TEXTURE_2D_MULTISAMPLE;
+		case TEXTURE_FLOAT_2D_ARRAY:	return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
+		case TEXTURE_INT_2D:			return GL_TEXTURE_2D_MULTISAMPLE;
+		case TEXTURE_INT_2D_ARRAY:		return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
+		case TEXTURE_UINT_2D:			return GL_TEXTURE_2D_MULTISAMPLE;
+		case TEXTURE_UINT_2D_ARRAY:		return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+glw::GLenum TextureSizeCase::getTextureGLInternalFormat (void)
+{
+	switch (m_type)
+	{
+		case TEXTURE_FLOAT_2D:			return GL_RGBA8;
+		case TEXTURE_FLOAT_2D_ARRAY:	return GL_RGBA8;
+		case TEXTURE_INT_2D:			return GL_R8I;
+		case TEXTURE_INT_2D_ARRAY:		return GL_R8I;
+		case TEXTURE_UINT_2D:			return GL_R8UI;
+		case TEXTURE_UINT_2D_ARRAY:		return GL_R8UI;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+void TextureSizeCase::createTexture (const tcu::IVec3& size)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (!m_isArrayType)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Creating texture with size " << size.x() << "x" << size.y() << tcu::TestLog::EndMessage;
+	else
+		m_testCtx.getLog() << tcu::TestLog::Message << "Creating texture with size " << size.x() << "x" << size.y() << "x" << size.z() << tcu::TestLog::EndMessage;
+
+	gl.genTextures(1, &m_texture);
+	gl.bindTexture(getTextureGLTarget(), m_texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "texture gen");
+
+	if (!m_isArrayType)
+		gl.texStorage2DMultisample(getTextureGLTarget(), m_numSamples, getTextureGLInternalFormat(), size.x(), size.y(), GL_FALSE);
+	else
+		gl.texStorage3DMultisample(getTextureGLTarget(), m_numSamples, getTextureGLInternalFormat(), size.x(), size.y(), size.z(), GL_FALSE);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "texStorage");
+}
+
+void TextureSizeCase::deleteTexture (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_texture)
+	{
+		gl.deleteTextures(1, &m_texture);
+		m_texture = 0;
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "texture delete");
+	}
+}
+
+void TextureSizeCase::runShader (tcu::Surface& dst, const tcu::IVec3& size)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const int				positionLoc			= gl.getAttribLocation(m_shader->getProgram(), "a_position");
+	const int				shaderSamplerLoc	= gl.getUniformLocation(m_shader->getProgram(), "u_sampler");
+	const int				shaderSizeLoc		= gl.getUniformLocation(m_shader->getProgram(), "u_size");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Running the verification shader." << tcu::TestLog::EndMessage;
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "preclear");
+	gl.viewport(0, 0, 1, 1);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_vbo);
+	gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(positionLoc);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "vertexAttrib");
+
+	gl.useProgram(m_shader->getProgram());
+	gl.uniform1i(shaderSamplerLoc, 0);
+	if (m_isArrayType)
+		gl.uniform3iv(shaderSizeLoc, 1, size.getPtr());
+	else
+		gl.uniform2iv(shaderSizeLoc, 1, size.getPtr());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "setup program");
+
+	gl.bindTexture(getTextureGLTarget(), m_texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bindtex");
+
+	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");
+
+	gl.disableVertexAttribArray(positionLoc);
+	gl.useProgram(0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "cleanup");
+
+	gl.finish();
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "readPixels");
+}
+
+bool TextureSizeCase::verifyImage (const tcu::Surface& dst)
+{
+	DE_ASSERT(dst.getWidth() == 1 && dst.getHeight() == 1);
+
+	const int		colorThresholdRed	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().redBits);
+	const int		colorThresholdGreen	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().greenBits);
+	const int		colorThresholdBlue	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().blueBits);
+	const tcu::RGBA	color				= dst.getPixel(0,0);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying image." << tcu::TestLog::EndMessage;
+
+	// green
+	if (color.getRed() < colorThresholdRed && color.getGreen() > 255 - colorThresholdGreen && color.getBlue() < colorThresholdBlue)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Result ok." << tcu::TestLog::EndMessage;
+		return true;
+	}
+	// red
+	else if (color.getRed() > 255 - colorThresholdRed && color.getGreen() < colorThresholdGreen && color.getBlue() < colorThresholdBlue)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Image size incorrect." << tcu::TestLog::EndMessage;
+		return false;
+	}
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Expected either green or red pixel, got " << color << tcu::TestLog::EndMessage;
+	return false;
+}
+
+} // anonymous
+
+ShaderTextureSizeTests::ShaderTextureSizeTests (Context& context)
+	: TestCaseGroup(context, "texture_size", "Texture size tests")
+{
+}
+
+ShaderTextureSizeTests::~ShaderTextureSizeTests (void)
+{
+}
+
+void ShaderTextureSizeTests::init (void)
+{
+	static const struct SamplerType
+	{
+		TextureSizeCase::TextureType	type;
+		const char*						name;
+	} samplerTypes[] =
+	{
+		{ TextureSizeCase::TEXTURE_FLOAT_2D,		"texture_2d"			},
+		{ TextureSizeCase::TEXTURE_FLOAT_2D_ARRAY,	"texture_2d_array"		},
+		{ TextureSizeCase::TEXTURE_INT_2D,			"texture_int_2d"		},
+		{ TextureSizeCase::TEXTURE_INT_2D_ARRAY,	"texture_int_2d_array"	},
+		{ TextureSizeCase::TEXTURE_UINT_2D,			"texture_uint_2d"		},
+		{ TextureSizeCase::TEXTURE_UINT_2D_ARRAY,	"texture_uint_2d_array"	},
+	};
+
+	static const int sampleCounts[] = { 1, 4 };
+
+	for (int samplerTypeNdx = 0; samplerTypeNdx < DE_LENGTH_OF_ARRAY(samplerTypes); ++samplerTypeNdx)
+	{
+		for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(sampleCounts); ++sampleCountNdx)
+		{
+			const std::string name = std::string() + "samples_" + de::toString(sampleCounts[sampleCountNdx]) + "_" + samplerTypes[samplerTypeNdx].name;
+			const std::string desc = std::string() + "samples count = " + de::toString(sampleCounts[sampleCountNdx]) + ", type = " + samplerTypes[samplerTypeNdx].name;
+
+			addChild(new TextureSizeCase(m_context, name.c_str(), desc.c_str(), samplerTypes[samplerTypeNdx].type, sampleCounts[sampleCountNdx]));
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fShaderTextureSizeTests.hpp b/modules/gles31/functional/es31fShaderTextureSizeTests.hpp
new file mode 100644
index 0000000..664e0ce
--- /dev/null
+++ b/modules/gles31/functional/es31fShaderTextureSizeTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSHADERTEXTURESIZETESTS_HPP
+#define _ES31FSHADERTEXTURESIZETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample texture size tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class ShaderTextureSizeTests : public TestCaseGroup
+{
+public:
+									ShaderTextureSizeTests	(Context& context);
+									~ShaderTextureSizeTests	(void);
+
+	void							init					(void);
+
+private:
+									ShaderTextureSizeTests	(const ShaderTextureSizeTests& other);
+	ShaderTextureSizeTests&			operator=				(const ShaderTextureSizeTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSHADERTEXTURESIZETESTS_HPP
diff --git a/modules/gles31/functional/es31fStencilTexturingTests.cpp b/modules/gles31/functional/es31fStencilTexturingTests.cpp
new file mode 100644
index 0000000..83b2326
--- /dev/null
+++ b/modules/gles31/functional/es31fStencilTexturingTests.cpp
@@ -0,0 +1,1047 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stencil texturing tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fStencilTexturingTests.hpp"
+
+#include "gluStrUtil.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluContextInfo.hpp"
+
+#include "glsTextureTestUtil.hpp"
+
+#include "tcuVector.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTexLookupVerifier.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "deStringUtil.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using std::vector;
+using std::string;
+using tcu::IVec4;
+using tcu::Vec2;
+using tcu::Vec4;
+using tcu::TestLog;
+using tcu::TextureLevel;
+using tcu::TextureFormat;
+
+namespace
+{
+
+static void genTestRects (vector<IVec4>& rects, int width, int height)
+{
+	int curWidth	= width;
+	int curHeight	= height;
+	int ndx			= 0;
+
+	for (;;)
+	{
+		rects.push_back(IVec4(width-curWidth, height-curHeight, curWidth, curHeight));
+
+		DE_ASSERT(curWidth >= 1 && curHeight >= 1);
+		if (curWidth == 1 && curHeight == 1)
+			break;
+		else if (curHeight > 1 && ((ndx%2) == 0 || curWidth == 1))
+			curHeight -= 1;
+		else
+			curWidth -= 1;
+
+		ndx += 1;
+	}
+}
+
+static void rectsToTriangles (const vector<IVec4>& rects, int width, int height, vector<Vec2>& positions, vector<deUint16>& indices)
+{
+	const float		w		= float(width);
+	const float		h		= float(height);
+
+	positions.resize(rects.size()*4);
+	indices.resize(rects.size()*6);
+
+	for (int rectNdx = 0; rectNdx < (int)rects.size(); rectNdx++)
+	{
+		const int		rx		= rects[rectNdx].x();
+		const int		ry		= rects[rectNdx].y();
+		const int		rw		= rects[rectNdx].z();
+		const int		rh		= rects[rectNdx].w();
+
+		const float		x0		= float(rx*2)/w - 1.0f;
+		const float		x1		= float((rx+rw)*2)/w - 1.0f;
+		const float		y0		= float(ry*2)/h - 1.0f;
+		const float		y1		= float((ry+rh)*2)/h - 1.0f;
+
+		positions[rectNdx*4 + 0] = Vec2(x0, y0);
+		positions[rectNdx*4 + 1] = Vec2(x1, y0);
+		positions[rectNdx*4 + 2] = Vec2(x0, y1);
+		positions[rectNdx*4 + 3] = Vec2(x1, y1);
+
+		indices[rectNdx*6 + 0] = rectNdx*4 + 0;
+		indices[rectNdx*6 + 1] = rectNdx*4 + 1;
+		indices[rectNdx*6 + 2] = rectNdx*4 + 2;
+		indices[rectNdx*6 + 3] = rectNdx*4 + 2;
+		indices[rectNdx*6 + 4] = rectNdx*4 + 1;
+		indices[rectNdx*6 + 5] = rectNdx*4 + 3;
+	}
+}
+
+static void drawTestPattern (const glu::RenderContext& renderCtx, int width, int height)
+{
+	const glu::ShaderProgram program(renderCtx, glu::ProgramSources()
+		<< glu::VertexSource(
+			"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n")
+		<< glu::FragmentSource(
+			"#version 300 es\n"
+			"void main (void) {}\n"));
+
+	const glw::Functions&	gl		= renderCtx.getFunctions();
+	vector<IVec4>			rects;
+	vector<Vec2>			positions;
+	vector<deUint16>		indices;
+
+	if (!program.isOk())
+		throw tcu::TestError("Compile failed");
+
+	gl.useProgram	(program.getProgram());
+	gl.viewport		(0, 0, width, height);
+	gl.clear		(GL_STENCIL_BUFFER_BIT);
+	gl.enable		(GL_STENCIL_TEST);
+	gl.stencilOp	(GL_KEEP, GL_KEEP, GL_INCR_WRAP);
+	gl.stencilFunc	(GL_ALWAYS, 0, ~0u);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "State setup failed");
+
+	genTestRects	(rects, width, height);
+	rectsToTriangles(rects, width, height, positions, indices);
+
+	{
+		const glu::VertexArrayBinding posBinding = glu::va::Float("a_position", 2, (int)positions.size(), 0, positions[0].getPtr());
+		glu::draw(renderCtx, program.getProgram(), 1, &posBinding, glu::pr::Triangles((int)indices.size(), &indices[0]));
+	}
+
+	gl.disable(GL_STENCIL_TEST);
+}
+
+static void renderTestPatternReference (const tcu::PixelBufferAccess& dst)
+{
+	const int		stencilBits		= tcu::getTextureFormatBitDepth(dst.getFormat()).w();
+	const deUint32	stencilMask		= (1u<<stencilBits)-1u;
+	vector<IVec4>	rects;
+
+	DE_ASSERT(dst.getFormat().order == TextureFormat::S || dst.getFormat().order == TextureFormat::DS);
+
+	tcu::clear(dst, IVec4(0));
+
+	genTestRects(rects, dst.getWidth(), dst.getHeight());
+
+	for (vector<IVec4>::const_iterator rectIter = rects.begin(); rectIter != rects.end(); ++rectIter)
+	{
+		const int	x0		= rectIter->x();
+		const int	y0		= rectIter->y();
+		const int	x1		= x0+rectIter->z();
+		const int	y1		= y0+rectIter->w();
+
+		for (int y = y0; y < y1; y++)
+		{
+			for (int x = x0; x < x1; x++)
+			{
+				const int oldVal	= dst.getPixStencil(x, y);
+				const int newVal	= (oldVal+1)&stencilMask;
+
+				dst.setPixStencil(newVal, x, y);
+			}
+		}
+	}
+}
+
+static void blitStencilToColor2D (const glu::RenderContext& renderCtx, deUint32 srcTex, int width, int height)
+{
+	const glu::ShaderProgram program(renderCtx, glu::ProgramSources()
+		<< glu::VertexSource(
+			"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"in highp vec2 a_texCoord;\n"
+			"out highp vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_texCoord = a_texCoord;\n"
+			"}\n")
+		<< glu::FragmentSource(
+			"#version 300 es\n"
+			"uniform highp usampler2D u_sampler;\n"
+			"in highp vec2 v_texCoord;\n"
+			"layout(location = 0) out highp uint o_stencil;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_stencil = texture(u_sampler, v_texCoord).x;\n"
+			"}\n"));
+
+	const float positions[] =
+	{
+		-1.0f, -1.0f,
+		+1.0f, -1.0f,
+		-1.0f, +1.0f,
+		+1.0f, +1.0f
+	};
+	const float texCoord[] =
+	{
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		0.0f, 1.0f,
+		1.0f, 1.0f
+	};
+	const glu::VertexArrayBinding vertexArrays[] =
+	{
+		glu::va::Float("a_position", 2, 4, 0, &positions[0]),
+		glu::va::Float("a_texCoord", 2, 4, 0, &texCoord[0])
+	};
+	const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+	const glw::Functions& gl = renderCtx.getFunctions();
+
+	if (!program.isOk())
+		throw tcu::TestError("Compile failed");
+
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D, srcTex);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Texture state setup failed");
+
+	gl.useProgram(program.getProgram());
+	gl.uniform1i(gl.getUniformLocation(program.getProgram(), "u_sampler"), 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Program setup failed");
+
+	gl.viewport(0, 0, width, height);
+	glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
+			  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+}
+
+static void blitStencilToColor2DArray (const glu::RenderContext& renderCtx, deUint32 srcTex, int width, int height, int level)
+{
+	const glu::ShaderProgram program(renderCtx, glu::ProgramSources()
+		<< glu::VertexSource(
+			"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"in highp vec3 a_texCoord;\n"
+			"out highp vec3 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_texCoord = a_texCoord;\n"
+			"}\n")
+		<< glu::FragmentSource(
+			"#version 300 es\n"
+			"uniform highp usampler2DArray u_sampler;\n"
+			"in highp vec3 v_texCoord;\n"
+			"layout(location = 0) out highp uint o_stencil;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_stencil = texture(u_sampler, v_texCoord).x;\n"
+			"}\n"));
+
+	const float positions[] =
+	{
+		-1.0f, -1.0f,
+		+1.0f, -1.0f,
+		-1.0f, +1.0f,
+		+1.0f, +1.0f
+	};
+	const float texCoord[] =
+	{
+		0.0f, 0.0f, float(level),
+		1.0f, 0.0f, float(level),
+		0.0f, 1.0f, float(level),
+		1.0f, 1.0f, float(level)
+	};
+	const glu::VertexArrayBinding vertexArrays[] =
+	{
+		glu::va::Float("a_position", 2, 4, 0, &positions[0]),
+		glu::va::Float("a_texCoord", 3, 4, 0, &texCoord[0])
+	};
+	const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+	const glw::Functions& gl = renderCtx.getFunctions();
+
+	if (!program.isOk())
+		throw tcu::TestError("Compile failed");
+
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_2D_ARRAY, srcTex);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Texture state setup failed");
+
+	gl.useProgram(program.getProgram());
+	gl.uniform1i(gl.getUniformLocation(program.getProgram(), "u_sampler"), 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Program setup failed");
+
+	gl.viewport(0, 0, width, height);
+	glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
+			  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+}
+
+static void blitStencilToColorCube (const glu::RenderContext& renderCtx, deUint32 srcTex, const float* texCoord, int width, int height)
+{
+	const glu::ShaderProgram program(renderCtx, glu::ProgramSources()
+		<< glu::VertexSource(
+			"#version 300 es\n"
+			"in highp vec4 a_position;\n"
+			"in highp vec3 a_texCoord;\n"
+			"out highp vec3 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_texCoord = a_texCoord;\n"
+			"}\n")
+		<< glu::FragmentSource(
+			"#version 300 es\n"
+			"uniform highp usamplerCube u_sampler;\n"
+			"in highp vec3 v_texCoord;\n"
+			"layout(location = 0) out highp vec4 o_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	o_color.x = float(texture(u_sampler, v_texCoord).x) / 255.0;\n"
+			"	o_color.yzw = vec3(0.0, 0.0, 1.0);\n"
+			"}\n"));
+
+	const float positions[] =
+	{
+		-1.0f, -1.0f,
+		-1.0f, +1.0f,
+		+1.0f, -1.0f,
+		+1.0f, +1.0f
+	};
+	const glu::VertexArrayBinding vertexArrays[] =
+	{
+		glu::va::Float("a_position", 2, 4, 0, &positions[0]),
+		glu::va::Float("a_texCoord", 3, 4, 0, texCoord)
+	};
+	const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+	const glw::Functions& gl = renderCtx.getFunctions();
+
+	if (!program.isOk())
+		throw tcu::TestError("Compile failed");
+
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, srcTex);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Texture state setup failed");
+
+	gl.useProgram(program.getProgram());
+	gl.uniform1i(gl.getUniformLocation(program.getProgram(), "u_sampler"), 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Program setup failed");
+
+	gl.viewport(0, 0, width, height);
+	glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
+			  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+}
+
+static inline tcu::ConstPixelBufferAccess stencilToRedAccess (const tcu::ConstPixelBufferAccess& access)
+{
+	DE_ASSERT(access.getFormat() == TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8));
+	return tcu::ConstPixelBufferAccess(TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT8),
+									   access.getWidth(), access.getHeight(), access.getDepth(), access.getRowPitch(), access.getSlicePitch(), access.getDataPtr());
+}
+
+static bool compareStencilToRed (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& stencilRef, const tcu::ConstPixelBufferAccess& result)
+{
+	const int		maxPrints		= 10;
+	int				numFailed		= 0;
+
+	DE_ASSERT(stencilRef.getFormat().order == TextureFormat::S);
+	DE_ASSERT(stencilRef.getWidth() == result.getWidth() && stencilRef.getHeight() == result.getHeight());
+
+	for (int y = 0; y < stencilRef.getHeight(); y++)
+	{
+		for (int x = 0; x < stencilRef.getWidth(); x++)
+		{
+			const int		ref		= stencilRef.getPixStencil(x, y);
+			const int		res		= result.getPixelInt(x, y).x();
+
+			if (ref != res)
+			{
+				if (numFailed < maxPrints)
+					log << TestLog::Message << "ERROR: Expected " << ref << ", got " << res << " at (" << x << ", " << y << ")" << TestLog::EndMessage;
+				else if (numFailed == maxPrints)
+					log << TestLog::Message << "..." << TestLog::EndMessage;
+
+				numFailed += 1;
+			}
+		}
+	}
+
+	log << TestLog::Message << "Found " << numFailed << " faulty pixels, comparison " << (numFailed == 0 ? "passed." : "FAILED!") << TestLog::EndMessage;
+
+	log << TestLog::ImageSet("ComparisonResult", "Image comparison result")
+		<< TestLog::Image("Result", "Result stencil buffer", result);
+
+	if (numFailed > 0)
+		log << TestLog::Image("Reference", "Reference stencil buffer", stencilToRedAccess(stencilRef));
+
+	log << TestLog::EndImageSet;
+
+	return numFailed == 0;
+}
+
+static bool compareRedChannel (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& result, int reference)
+{
+	const int		maxPrints		= 10;
+	int				numFailed		= 0;
+
+	for (int y = 0; y < result.getHeight(); y++)
+	{
+		for (int x = 0; x < result.getWidth(); x++)
+		{
+			const int res = result.getPixelInt(x, y).x();
+
+			if (reference != res)
+			{
+				if (numFailed < maxPrints)
+					log << TestLog::Message << "ERROR: Expected " << reference << ", got " << res << " at (" << x << ", " << y << ")" << TestLog::EndMessage;
+				else if (numFailed == maxPrints)
+					log << TestLog::Message << "..." << TestLog::EndMessage;
+
+				numFailed += 1;
+			}
+		}
+	}
+
+	log << TestLog::Message << "Found " << numFailed << " faulty pixels, comparison " << (numFailed == 0 ? "passed." : "FAILED!") << TestLog::EndMessage;
+
+	log << TestLog::ImageSet("ComparisonResult", "Image comparison result")
+		<< TestLog::Image("Result", "Result stencil buffer", result);
+
+	log << TestLog::EndImageSet;
+
+	return numFailed == 0;
+}
+
+static void stencilToUnorm8 (const tcu::TextureCube& src, tcu::TextureCube& dst)
+{
+	for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
+	{
+		for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+		{
+			const tcu::CubeFace face = tcu::CubeFace(faceNdx);
+
+			if (!src.isLevelEmpty(face, levelNdx))
+			{
+				dst.allocLevel(face, levelNdx);
+
+				const tcu::ConstPixelBufferAccess	srcLevel	= src.getLevelFace(levelNdx, face);
+				const tcu::PixelBufferAccess		dstLevel	= dst.getLevelFace(levelNdx, face);
+
+				for (int y = 0; y < src.getSize(); y++)
+				for (int x = 0; x < src.getSize(); x++)
+					dstLevel.setPixel(Vec4(float(srcLevel.getPixStencil(x, y)) / 255.f, 0.f, 0.f, 1.f), x, y);
+			}
+		}
+	}
+}
+
+static void checkDepthStencilFormatSupport (const glu::ContextInfo& ctxInfo, deUint32 format)
+{
+	if (format == GL_STENCIL_INDEX8)
+	{
+		const char* reqExt = "GL_OES_texture_stencil8";
+		if (!ctxInfo.isExtensionSupported(reqExt))
+			throw tcu::NotSupportedError(glu::getPixelFormatStr(format).toString() + " requires " + reqExt);
+	}
+	else
+	{
+		DE_ASSERT(format == GL_DEPTH32F_STENCIL8 || format == GL_DEPTH24_STENCIL8);
+	}
+}
+
+static void checkFramebufferStatus (const glw::Functions& gl)
+{
+	const deUint32 status = gl.checkFramebufferStatus(GL_FRAMEBUFFER);
+
+	if (status == GL_FRAMEBUFFER_UNSUPPORTED)
+		throw tcu::NotSupportedError("Unsupported framebuffer configuration");
+	else if (status != GL_FRAMEBUFFER_COMPLETE)
+		throw tcu::TestError("Incomplete framebuffer: " + glu::getFramebufferStatusStr(status).toString());
+}
+
+class UploadTex2DCase : public TestCase
+{
+public:
+	UploadTex2DCase (Context& context, const char* name, deUint32 format)
+		: TestCase	(context, name, glu::getPixelFormatName(format))
+		, m_format	(format)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glu::RenderContext&	renderCtx			= m_context.getRenderContext();
+		const glw::Functions&		gl					= renderCtx.getFunctions();
+		const int					width				= 129;
+		const int					height				= 113;
+		glu::Framebuffer			fbo					(renderCtx);
+		glu::Renderbuffer			colorBuf			(renderCtx);
+		glu::Texture				depthStencilTex		(renderCtx);
+		TextureLevel				uploadLevel			(glu::mapGLInternalFormat(m_format), width, height);
+		TextureLevel				readLevel			(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
+		TextureLevel				stencilOnlyLevel	(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);
+
+		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);
+
+		renderTestPatternReference(uploadLevel);
+		renderTestPatternReference(stencilOnlyLevel);
+
+		gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
+		gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);
+		glu::texSubImage2D(renderCtx, GL_TEXTURE_2D, 0, 0, 0, uploadLevel);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
+		checkFramebufferStatus(gl);
+
+		blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
+		glu::readPixels(renderCtx, 0, 0, readLevel);
+
+		{
+			const bool compareOk = compareStencilToRed(m_testCtx.getLog(), stencilOnlyLevel, readLevel);
+			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
+									compareOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	const deUint32 m_format;
+};
+
+class UploadTex2DArrayCase : public TestCase
+{
+public:
+	UploadTex2DArrayCase (Context& context, const char* name, deUint32 format)
+		: TestCase	(context, name, glu::getPixelFormatName(format))
+		, m_format	(format)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glu::RenderContext&	renderCtx			= m_context.getRenderContext();
+		const glw::Functions&		gl					= renderCtx.getFunctions();
+		const int					width				= 41;
+		const int					height				= 13;
+		const int					levels				= 7;
+		const int					ptrnLevel			= 3;
+		glu::Framebuffer			fbo					(renderCtx);
+		glu::Renderbuffer			colorBuf			(renderCtx);
+		glu::Texture				depthStencilTex		(renderCtx);
+		TextureLevel				uploadLevel			(glu::mapGLInternalFormat(m_format), width, height, levels);
+
+		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);
+
+		for (int levelNdx = 0; levelNdx < levels; levelNdx++)
+		{
+			const tcu::PixelBufferAccess levelAccess = tcu::getSubregion(uploadLevel.getAccess(), 0, 0, levelNdx, width, height, 1);
+
+			if (levelNdx == ptrnLevel)
+				renderTestPatternReference(levelAccess);
+			else
+				tcu::clear(levelAccess, IVec4(0, 0, 0, levelNdx));
+		}
+
+		gl.bindTexture(GL_TEXTURE_2D_ARRAY, *depthStencilTex);
+		gl.texStorage3D(GL_TEXTURE_2D_ARRAY, 1, m_format, width, height, levels);
+		glu::texSubImage3D(renderCtx, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, uploadLevel);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
+		checkFramebufferStatus(gl);
+
+		{
+			TextureLevel	readLevel		(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
+			bool			allLevelsOk		= true;
+
+			for (int levelNdx = 0; levelNdx < levels; levelNdx++)
+			{
+				tcu::ScopedLogSection section(m_testCtx.getLog(), "Level" + de::toString(levelNdx), "Level " + de::toString(levelNdx));
+				bool levelOk;
+
+				blitStencilToColor2DArray(renderCtx, *depthStencilTex, width, height, levelNdx);
+				glu::readPixels(renderCtx, 0, 0, readLevel);
+
+				if (levelNdx == ptrnLevel)
+				{
+					TextureLevel reference(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);
+					renderTestPatternReference(reference);
+
+					levelOk = compareStencilToRed(m_testCtx.getLog(), reference, readLevel);
+				}
+				else
+					levelOk = compareRedChannel(m_testCtx.getLog(), readLevel, levelNdx);
+
+				if (!levelOk)
+				{
+					allLevelsOk = false;
+					break;
+				}
+			}
+
+			m_testCtx.setTestResult(allLevelsOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									allLevelsOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	const deUint32 m_format;
+};
+
+class UploadTexCubeCase : public TestCase
+{
+public:
+	UploadTexCubeCase (Context& context, const char* name, deUint32 format)
+		: TestCase	(context, name, glu::getPixelFormatName(format))
+		, m_format	(format)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glu::RenderContext&	renderCtx			= m_context.getRenderContext();
+		const glw::Functions&		gl					= renderCtx.getFunctions();
+		const int					size				= 64;
+		const int					renderWidth			= 128;
+		const int					renderHeight		= 128;
+		vector<float>				texCoord;
+		glu::Framebuffer			fbo					(renderCtx);
+		glu::Renderbuffer			colorBuf			(renderCtx);
+		glu::Texture				depthStencilTex		(renderCtx);
+		tcu::TextureCube			texData				(glu::mapGLInternalFormat(m_format), size);
+		tcu::TextureLevel			result				(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), renderWidth, renderHeight);
+
+		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);
+
+		for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+		{
+			const tcu::CubeFace		face		= tcu::CubeFace(faceNdx);
+			const int				stencilVal	= 42*faceNdx;
+
+			texData.allocLevel(face, 0);
+			tcu::clear(texData.getLevelFace(0, face), IVec4(0, 0, 0, stencilVal));
+		}
+
+		gls::TextureTestUtil::computeQuadTexCoordCube(texCoord, tcu::CUBEFACE_NEGATIVE_X, Vec2(-1.5f, -1.3f), Vec2(1.3f, 1.4f));
+
+		gl.bindTexture(GL_TEXTURE_CUBE_MAP, *depthStencilTex);
+		gl.texStorage2D(GL_TEXTURE_CUBE_MAP, 1, m_format, size, size);
+
+		for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+			glu::texSubImage2D(renderCtx, glu::getGLCubeFace(tcu::CubeFace(faceNdx)), 0, 0, 0, texData.getLevelFace(0, tcu::CubeFace(faceNdx)));
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, renderWidth, renderHeight);
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
+		checkFramebufferStatus(gl);
+
+		blitStencilToColorCube(renderCtx, *depthStencilTex, &texCoord[0], renderWidth, renderHeight);
+		glu::readPixels(renderCtx, 0, 0, result);
+
+		{
+			using namespace gls::TextureTestUtil;
+
+			tcu::TextureCube		redTex			(TextureFormat(TextureFormat::R, TextureFormat::UNORM_INT8), size);
+			const ReferenceParams	sampleParams	(TEXTURETYPE_CUBE, tcu::Sampler(tcu::Sampler::CLAMP_TO_EDGE,
+																					tcu::Sampler::CLAMP_TO_EDGE,
+																					tcu::Sampler::CLAMP_TO_EDGE,
+																					tcu::Sampler::NEAREST,
+																					tcu::Sampler::NEAREST));
+			tcu::LookupPrecision	lookupPrec;
+			tcu::LodPrecision		lodPrec;
+			bool					compareOk;
+
+			lookupPrec.colorMask		= tcu::BVec4(true, true, true, true);
+			lookupPrec.colorThreshold	= tcu::computeFixedPointThreshold(IVec4(8, 8, 8, 8));
+			lookupPrec.coordBits		= tcu::IVec3(22, 22, 22);
+			lookupPrec.uvwBits			= tcu::IVec3(5, 5, 0);
+			lodPrec.lodBits				= 7;
+			lodPrec.derivateBits		= 16;
+
+			stencilToUnorm8(texData, redTex);
+
+			compareOk = verifyTextureResult(m_testCtx, result, redTex, &texCoord[0], sampleParams, lookupPrec, lodPrec, tcu::PixelFormat(8, 8, 8, 8));
+
+			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
+									compareOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	const deUint32 m_format;
+};
+
+class RenderTex2DCase : public TestCase
+{
+public:
+	RenderTex2DCase (Context& context, const char* name, deUint32 format)
+		: TestCase	(context, name, glu::getPixelFormatName(format))
+		, m_format	(format)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glu::RenderContext&	renderCtx		= m_context.getRenderContext();
+		const glw::Functions&		gl				= renderCtx.getFunctions();
+		const int					width			= 117;
+		const int					height			= 193;
+		glu::Framebuffer			fbo				(renderCtx);
+		glu::Renderbuffer			colorBuf		(renderCtx);
+		glu::Texture				depthStencilTex	(renderCtx);
+		TextureLevel				result			(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
+		TextureLevel				reference		(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);
+
+		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);
+
+		gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
+		gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
+		gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, *depthStencilTex, 0);
+		checkFramebufferStatus(gl);
+
+		drawTestPattern(renderCtx, width, height);
+
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
+		checkFramebufferStatus(gl);
+
+		blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
+		glu::readPixels(renderCtx, 0, 0, result.getAccess());
+
+		renderTestPatternReference(reference);
+
+		{
+			const bool compareOk = compareStencilToRed(m_testCtx.getLog(), reference, result);
+			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
+									compareOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	const deUint32 m_format;
+};
+
+class ClearTex2DCase : public TestCase
+{
+public:
+	ClearTex2DCase (Context& context, const char* name, deUint32 format)
+		: TestCase	(context, name, glu::getPixelFormatName(format))
+		, m_format	(format)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glu::RenderContext&	renderCtx		= m_context.getRenderContext();
+		const glw::Functions&		gl				= renderCtx.getFunctions();
+		const int					width			= 125;
+		const int					height			= 117;
+		const int					cellSize		= 8;
+		glu::Framebuffer			fbo				(renderCtx);
+		glu::Renderbuffer			colorBuf		(renderCtx);
+		glu::Texture				depthStencilTex	(renderCtx);
+		TextureLevel				result			(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
+		TextureLevel				reference		(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);
+
+		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);
+
+		gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
+		gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
+		gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, *depthStencilTex, 0);
+		checkFramebufferStatus(gl);
+
+		gl.enable(GL_SCISSOR_TEST);
+
+		for (int y = 0; y < height; y += cellSize)
+		{
+			for (int x = 0; x < width; x += cellSize)
+			{
+				const int		clearW		= de::min(cellSize, width-x);
+				const int		clearH		= de::min(cellSize, height-y);
+				const int		stencil		= int((deInt32Hash(x) ^ deInt32Hash(y)) & 0xff);
+
+				gl.clearStencil(stencil);
+				gl.scissor(x, y, clearW, clearH);
+				gl.clear(GL_STENCIL_BUFFER_BIT);
+
+				tcu::clear(tcu::getSubregion(reference.getAccess(), x, y, clearW, clearH), IVec4(0, 0, 0, stencil));
+			}
+		}
+
+		gl.disable(GL_SCISSOR_TEST);
+
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
+		checkFramebufferStatus(gl);
+
+		blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
+		glu::readPixels(renderCtx, 0, 0, result.getAccess());
+
+		{
+			const bool compareOk = compareStencilToRed(m_testCtx.getLog(), reference, result);
+			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
+									compareOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	const deUint32 m_format;
+};
+
+class CompareModeCase : public TestCase
+{
+public:
+	CompareModeCase (Context& context, const char* name, deUint32 format)
+		: TestCase	(context, name, glu::getPixelFormatName(format))
+		, m_format	(format)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glu::RenderContext&	renderCtx			= m_context.getRenderContext();
+		const glw::Functions&		gl					= renderCtx.getFunctions();
+		const int					width				= 64;
+		const int					height				= 64;
+		glu::Framebuffer			fbo					(renderCtx);
+		glu::Renderbuffer			colorBuf			(renderCtx);
+		glu::Texture				depthStencilTex		(renderCtx);
+		TextureLevel				uploadLevel			(glu::mapGLInternalFormat(m_format), width, height);
+		TextureLevel				readLevel			(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
+		TextureLevel				stencilOnlyLevel	(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);
+
+		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);
+
+		m_testCtx.getLog() << TestLog::Message << "NOTE: Texture compare mode has no effect when reading stencil values." << TestLog::EndMessage;
+
+		renderTestPatternReference(uploadLevel);
+		renderTestPatternReference(stencilOnlyLevel);
+
+		gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
+		gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS);
+		glu::texSubImage2D(renderCtx, GL_TEXTURE_2D, 0, 0, 0, uploadLevel);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
+		checkFramebufferStatus(gl);
+
+		blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
+		glu::readPixels(renderCtx, 0, 0, readLevel);
+
+		{
+			const bool compareOk = compareStencilToRed(m_testCtx.getLog(), stencilOnlyLevel, readLevel);
+			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
+									compareOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	const deUint32 m_format;
+};
+
+class BaseLevelCase : public TestCase
+{
+public:
+	BaseLevelCase (Context& context, const char* name, deUint32 format)
+		: TestCase	(context, name, glu::getPixelFormatName(format))
+		, m_format	(format)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glu::RenderContext&	renderCtx			= m_context.getRenderContext();
+		const glw::Functions&		gl					= renderCtx.getFunctions();
+		const int					width				= 128;
+		const int					height				= 128;
+		const int					levelNdx			= 2;
+		const int					levelWidth			= width>>levelNdx;
+		const int					levelHeight			= height>>levelNdx;
+		glu::Framebuffer			fbo					(renderCtx);
+		glu::Renderbuffer			colorBuf			(renderCtx);
+		glu::Texture				depthStencilTex		(renderCtx);
+		TextureLevel				uploadLevel			(glu::mapGLInternalFormat(m_format), levelWidth, levelHeight);
+		TextureLevel				readLevel			(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), levelWidth, levelHeight);
+		TextureLevel				stencilOnlyLevel	(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), levelWidth, levelHeight);
+
+		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);
+
+		m_testCtx.getLog() << TestLog::Message << "GL_TEXTURE_BASE_LEVEL = " << levelNdx << TestLog::EndMessage;
+
+		renderTestPatternReference(uploadLevel);
+		renderTestPatternReference(stencilOnlyLevel);
+
+		gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
+		gl.texStorage2D(GL_TEXTURE_2D, deLog2Floor32(de::max(width, height))+1, m_format, width, height);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, levelNdx);
+		glu::texSubImage2D(renderCtx, GL_TEXTURE_2D, levelNdx, 0, 0, uploadLevel);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, levelWidth, levelHeight);
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
+		checkFramebufferStatus(gl);
+
+		blitStencilToColor2D(renderCtx, *depthStencilTex, levelWidth, levelHeight);
+		glu::readPixels(renderCtx, 0, 0, readLevel);
+
+		{
+			const bool compareOk = compareStencilToRed(m_testCtx.getLog(), stencilOnlyLevel, readLevel);
+			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
+									compareOk ? "Pass"				: "Image comparison failed");
+		}
+
+		return STOP;
+	}
+
+private:
+	const deUint32 m_format;
+};
+
+} // anonymous
+
+StencilTexturingTests::StencilTexturingTests (Context& context)
+	: TestCaseGroup(context, "stencil_texturing", "Stencil texturing tests")
+{
+}
+
+StencilTexturingTests::~StencilTexturingTests (void)
+{
+}
+
+void StencilTexturingTests::init (void)
+{
+	// .format
+	{
+		tcu::TestCaseGroup* const formatGroup = new tcu::TestCaseGroup(m_testCtx, "format", "Formats");
+		addChild(formatGroup);
+
+		formatGroup->addChild(new UploadTex2DCase		(m_context, "depth32f_stencil8_2d",			GL_DEPTH32F_STENCIL8));
+		formatGroup->addChild(new UploadTex2DArrayCase	(m_context, "depth32f_stencil8_2d_array",	GL_DEPTH32F_STENCIL8));
+		formatGroup->addChild(new UploadTexCubeCase		(m_context, "depth32f_stencil8_cube",		GL_DEPTH32F_STENCIL8));
+		formatGroup->addChild(new UploadTex2DCase		(m_context, "depth24_stencil8_2d",			GL_DEPTH24_STENCIL8));
+		formatGroup->addChild(new UploadTex2DArrayCase	(m_context, "depth24_stencil8_2d_array",	GL_DEPTH24_STENCIL8));
+		formatGroup->addChild(new UploadTexCubeCase		(m_context, "depth24_stencil8_cube",		GL_DEPTH24_STENCIL8));
+
+		// OES_texture_stencil8
+		formatGroup->addChild(new UploadTex2DCase		(m_context, "stencil_index8_2d",			GL_STENCIL_INDEX8));
+		formatGroup->addChild(new UploadTex2DArrayCase	(m_context, "stencil_index8_2d_array",		GL_STENCIL_INDEX8));
+		formatGroup->addChild(new UploadTexCubeCase		(m_context, "stencil_index8_cube",			GL_STENCIL_INDEX8));
+	}
+
+	// .render
+	{
+		tcu::TestCaseGroup* const readRenderGroup = new tcu::TestCaseGroup(m_testCtx, "render", "Read rendered stencil values");
+		addChild(readRenderGroup);
+
+		readRenderGroup->addChild(new ClearTex2DCase	(m_context, "depth32f_stencil8_clear",	GL_DEPTH32F_STENCIL8));
+		readRenderGroup->addChild(new RenderTex2DCase	(m_context, "depth32f_stencil8_draw",	GL_DEPTH32F_STENCIL8));
+		readRenderGroup->addChild(new ClearTex2DCase	(m_context, "depth24_stencil8_clear",	GL_DEPTH24_STENCIL8));
+		readRenderGroup->addChild(new RenderTex2DCase	(m_context, "depth24_stencil8_draw",	GL_DEPTH24_STENCIL8));
+	}
+
+	// .misc
+	{
+		tcu::TestCaseGroup* const miscGroup = new tcu::TestCaseGroup(m_testCtx, "misc", "Misc cases");
+		addChild(miscGroup);
+
+		miscGroup->addChild(new CompareModeCase	(m_context, "compare_mode_effect",	GL_DEPTH24_STENCIL8));
+		miscGroup->addChild(new BaseLevelCase	(m_context, "base_level",			GL_DEPTH24_STENCIL8));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fStencilTexturingTests.hpp b/modules/gles31/functional/es31fStencilTexturingTests.hpp
new file mode 100644
index 0000000..0a17c12
--- /dev/null
+++ b/modules/gles31/functional/es31fStencilTexturingTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSTENCILTEXTURINGTESTS_HPP
+#define _ES31FSTENCILTEXTURINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stencil texturing tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class StencilTexturingTests : public TestCaseGroup
+{
+public:
+							StencilTexturingTests	(Context& context);
+							~StencilTexturingTests	(void);
+
+	void					init					(void);
+
+private:
+							StencilTexturingTests	(const StencilTexturingTests& other);
+	StencilTexturingTests&	operator=				(const StencilTexturingTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSTENCILTEXTURINGTESTS_HPP
diff --git a/modules/gles31/functional/es31fSynchronizationTests.cpp b/modules/gles31/functional/es31fSynchronizationTests.cpp
new file mode 100644
index 0000000..3e67c1b
--- /dev/null
+++ b/modules/gles31/functional/es31fSynchronizationTests.cpp
@@ -0,0 +1,3331 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Synchronization Tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fSynchronizationTests.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluContextInfo.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+#include "deSharedPtr.hpp"
+#include "deMemory.h"
+#include "deRandom.hpp"
+
+#include <map>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+
+static bool validateSortedAtomicRampAdditionValueChain (const std::vector<deUint32>& valueChain, deUint32 sumValue, int& invalidOperationNdx, deUint32& errorDelta, deUint32& errorExpected)
+{
+	std::vector<deUint32> chainDelta(valueChain.size());
+
+	for (int callNdx = 0; callNdx < (int)valueChain.size(); ++callNdx)
+		chainDelta[callNdx] = ((callNdx + 1 == (int)valueChain.size()) ? (sumValue) : (valueChain[callNdx+1])) - valueChain[callNdx];
+
+	// chainDelta contains now the actual additions applied to the value
+	// check there exists an addition ramp form 1 to ...
+	std::sort(chainDelta.begin(), chainDelta.end());
+
+	for (int callNdx = 0; callNdx < (int)valueChain.size(); ++callNdx)
+	{
+		if ((int)chainDelta[callNdx] != callNdx+1)
+		{
+			invalidOperationNdx = callNdx;
+			errorDelta = chainDelta[callNdx];
+			errorExpected = callNdx+1;
+
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static void readBuffer (const glw::Functions& gl, deUint32 target, int numElements, std::vector<deUint32>& result)
+{
+	const void* ptr = gl.mapBufferRange(target, 0, (int)(sizeof(deUint32) * numElements), GL_MAP_READ_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "map");
+
+	if (!ptr)
+		throw tcu::TestError("mapBufferRange returned NULL");
+
+	result.resize(numElements);
+	memcpy(&result[0], ptr, sizeof(deUint32) * numElements);
+
+	if (gl.unmapBuffer(target) == GL_FALSE)
+		throw tcu::TestError("unmapBuffer returned false");
+}
+
+static deUint32 readBufferUint32 (const glw::Functions& gl, deUint32 target)
+{
+	std::vector<deUint32> vec;
+
+	readBuffer(gl, target, 1, vec);
+
+	return vec[0];
+}
+
+//! Generate a ramp of values from 1 to numElements, and shuffle it
+void generateShuffledRamp (int numElements, std::vector<int>& ramp)
+{
+	de::Random rng(0xabcd);
+
+	// some positive (non-zero) unique values
+	ramp.resize(numElements);
+	for (int callNdx = 0; callNdx < numElements; ++callNdx)
+		ramp[callNdx] = callNdx + 1;
+
+	rng.shuffle(ramp.begin(), ramp.end());
+}
+
+class InterInvocationTestCase : public TestCase
+{
+public:
+	enum StorageType
+	{
+		STORAGE_BUFFER = 0,
+		STORAGE_IMAGE,
+
+		STORAGE_LAST
+	};
+	enum CaseFlags
+	{
+		FLAG_ATOMIC				= 0x1,
+		FLAG_ALIASING_STORAGES	= 0x2,
+		FLAG_IN_GROUP			= 0x4,
+	};
+
+						InterInvocationTestCase		(Context& context, const char* name, const char* desc, StorageType storage, int flags = 0);
+						~InterInvocationTestCase	(void);
+
+private:
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+	void				runCompute					(void);
+	bool				verifyResults				(void);
+	virtual std::string	genShaderSource				(void) const = 0;
+
+protected:
+	std::string			genBarrierSource			(void) const;
+
+	const StorageType	m_storage;
+	const bool			m_useAtomic;
+	const bool			m_aliasingStorages;
+	const bool			m_syncWithGroup;
+	const int			m_workWidth;				// !< total work width
+	const int			m_workHeight;				// !<     ...    height
+	const int			m_localWidth;				// !< group width
+	const int			m_localHeight;				// !< group height
+	const int			m_elementsPerInvocation;	// !< elements accessed by a single invocation
+
+private:
+	glw::GLuint			m_storageBuf;
+	glw::GLuint			m_storageTex;
+	glw::GLuint			m_resultBuf;
+	glu::ShaderProgram*	m_program;
+};
+
+InterInvocationTestCase::InterInvocationTestCase (Context& context, const char* name, const char* desc, StorageType storage, int flags)
+	: TestCase					(context, name, desc)
+	, m_storage					(storage)
+	, m_useAtomic				((flags & FLAG_ATOMIC) != 0)
+	, m_aliasingStorages		((flags & FLAG_ALIASING_STORAGES) != 0)
+	, m_syncWithGroup			((flags & FLAG_IN_GROUP) != 0)
+	, m_workWidth				(256)
+	, m_workHeight				(256)
+	, m_localWidth				(16)
+	, m_localHeight				(8)
+	, m_elementsPerInvocation	(8)
+	, m_storageBuf				(0)
+	, m_storageTex				(0)
+	, m_resultBuf				(0)
+	, m_program					(DE_NULL)
+{
+	DE_ASSERT(m_storage < STORAGE_LAST);
+	DE_ASSERT(m_localWidth*m_localHeight <= 128); // minimum MAX_COMPUTE_WORK_GROUP_INVOCATIONS value
+}
+
+InterInvocationTestCase::~InterInvocationTestCase (void)
+{
+	deinit();
+}
+
+void InterInvocationTestCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// requirements
+
+	if (m_useAtomic && m_storage == STORAGE_IMAGE && !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_image_atomic extension");
+
+	// program
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genShaderSource()));
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build program");
+
+	// source
+
+	if (m_storage == STORAGE_BUFFER)
+	{
+		const int				bufferElements	= m_workWidth * m_workHeight * m_elementsPerInvocation;
+		const int				bufferSize		= bufferElements * sizeof(deUint32);
+		std::vector<deUint32>	zeroBuffer		(bufferElements, 0);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating zero-filled buffer for storage, size " << bufferElements << " elements, " << bufferSize << " bytes." << tcu::TestLog::EndMessage;
+
+		gl.genBuffers(1, &m_storageBuf);
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_storageBuf);
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &zeroBuffer[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen storage buf");
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		const int				bufferElements	= m_workWidth * m_workHeight * m_elementsPerInvocation;
+		const int				bufferSize		= bufferElements * sizeof(deUint32);
+		std::vector<deUint32>	zeroBuffer		(bufferElements, 0);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating zero-filled image for storage, size " << m_workWidth << "x" << m_workHeight * m_elementsPerInvocation << ", " << bufferSize << " bytes." << tcu::TestLog::EndMessage;
+
+		gl.genTextures(1, &m_storageTex);
+		gl.bindTexture(GL_TEXTURE_2D, m_storageTex);
+		gl.texImage2D(GL_TEXTURE_2D, 0, GL_R32I, m_workWidth, m_workHeight * m_elementsPerInvocation, 0, GL_RED_INTEGER, GL_INT, &zeroBuffer[0]);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen storage image");
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	// destination
+
+	{
+		const int				bufferElements	= m_workWidth * m_workHeight;
+		const int				bufferSize		= bufferElements * sizeof(deUint32);
+		std::vector<deInt32>	negativeBuffer	(bufferElements, -1);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating -1 filled buffer for results, size " << bufferElements << " elements, " << bufferSize << " bytes." << tcu::TestLog::EndMessage;
+
+		gl.genBuffers(1, &m_resultBuf);
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_resultBuf);
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &negativeBuffer[0], GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen storage buf");
+	}
+}
+
+void InterInvocationTestCase::deinit (void)
+{
+	if (m_storageBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_storageBuf);
+		m_storageBuf = DE_NULL;
+	}
+
+	if (m_storageTex)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_storageTex);
+		m_storageTex = DE_NULL;
+	}
+
+	if (m_resultBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_resultBuf);
+		m_resultBuf = DE_NULL;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+InterInvocationTestCase::IterateResult InterInvocationTestCase::iterate (void)
+{
+	// Dispatch
+	runCompute();
+
+	// Verify buffer contents
+	if (verifyResults())
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, (std::string((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) + " content verification failed").c_str());
+
+	return STOP;
+}
+
+void InterInvocationTestCase::runCompute (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	const int				groupsX	= m_workWidth / m_localWidth;
+	const int				groupsY	= m_workHeight / m_localHeight;
+
+	DE_ASSERT((m_workWidth % m_localWidth) == 0);
+	DE_ASSERT((m_workHeight % m_localHeight) == 0);
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Dispatching compute.\n"
+		<< "	group size: " << m_localWidth << "x" << m_localHeight << "\n"
+		<< "	dispatch size: " << groupsX << "x" << groupsY << "\n"
+		<< "	total work size: " << m_workWidth << "x" << m_workHeight << "\n"
+		<< tcu::TestLog::EndMessage;
+
+	gl.useProgram(m_program->getProgram());
+
+	// source
+	if (m_storage == STORAGE_BUFFER && !m_aliasingStorages)
+	{
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageBuf);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buf");
+	}
+	else if (m_storage == STORAGE_BUFFER && m_aliasingStorages)
+	{
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageBuf);
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_storageBuf);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buf");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Binding same buffer object to buffer storages." << tcu::TestLog::EndMessage;
+	}
+	else if (m_storage == STORAGE_IMAGE && !m_aliasingStorages)
+	{
+		gl.bindImageTexture(1, m_storageTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buf");
+	}
+	else if (m_storage == STORAGE_IMAGE && m_aliasingStorages)
+	{
+		gl.bindImageTexture(1, m_storageTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);
+		gl.bindImageTexture(2, m_storageTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buf");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Binding same texture level to image storages." << tcu::TestLog::EndMessage;
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	// destination
+	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_resultBuf);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buf");
+
+	// dispatch
+	gl.dispatchCompute(groupsX, groupsY, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatchCompute");
+}
+
+bool InterInvocationTestCase::verifyResults (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const int				errorFloodThreshold	= 5;
+	int						numErrorsLogged		= 0;
+	const void*				mapped				= DE_NULL;
+	std::vector<deInt32>	results				(m_workWidth * m_workHeight);
+	bool					error				= false;
+
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_resultBuf);
+	mapped = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, m_workWidth * m_workHeight * sizeof(deInt32), GL_MAP_READ_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "map buffer");
+
+	// copy to properly aligned array
+	deMemcpy(&results[0], mapped, m_workWidth * m_workHeight * sizeof(deUint32));
+
+	if (gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER) != GL_TRUE)
+		throw tcu::TestError("memory map store corrupted");
+
+	// check the results
+	for (int ndx = 0; ndx < (int)results.size(); ++ndx)
+	{
+		if (results[ndx] != 1)
+		{
+			error = true;
+
+			if (numErrorsLogged == 0)
+				m_testCtx.getLog() << tcu::TestLog::Message << "Result buffer failed, got unexpected values.\n" << tcu::TestLog::EndMessage;
+			if (numErrorsLogged++ < errorFloodThreshold)
+				m_testCtx.getLog() << tcu::TestLog::Message << "	Error at index " << ndx << ": expected 1, got " << results[ndx] << ".\n" << tcu::TestLog::EndMessage;
+			else
+			{
+				// after N errors, no point continuing verification
+				m_testCtx.getLog() << tcu::TestLog::Message << "	-- too many errors, skipping verification --\n" << tcu::TestLog::EndMessage;
+				break;
+			}
+		}
+	}
+
+	if (!error)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Result buffer ok." << tcu::TestLog::EndMessage;
+	return !error;
+}
+
+std::string InterInvocationTestCase::genBarrierSource (void) const
+{
+	std::ostringstream buf;
+
+	if (m_syncWithGroup)
+	{
+		// Wait until all invocations in this work group have their texture/buffer read/write operations complete
+		// \note We could also use memoryBarrierBuffer() or memoryBarrierImage() in place of groupMemoryBarrier() but
+		//       we only require intra-workgroup synchronization.
+		buf << "\n"
+			<< "	groupMemoryBarrier();\n"
+			<< "	barrier();\n"
+			<< "\n";
+	}
+	else if (m_storage == STORAGE_BUFFER)
+	{
+		DE_ASSERT(!m_syncWithGroup);
+
+		// Waiting only for data written by this invocation. Since all buffer reads and writes are
+		// processed in order (within a single invocation), we don't have to do anything.
+		buf << "\n";
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		DE_ASSERT(!m_syncWithGroup);
+
+		// Waiting only for data written by this invocation. But since operations complete in undefined
+		// order, we have to wait for them to complete.
+		buf << "\n"
+			<< "	memoryBarrierImage();\n"
+			<< "\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	return buf.str();
+}
+
+class InvocationBasicCase : public InterInvocationTestCase
+{
+public:
+							InvocationBasicCase		(Context& context, const char* name, const char* desc, StorageType storage, int flags);
+private:
+	std::string				genShaderSource			(void) const;
+	virtual std::string		genShaderMainBlock		(void) const = 0;
+};
+
+InvocationBasicCase::InvocationBasicCase (Context& context, const char* name, const char* desc, StorageType storage, int flags)
+	: InterInvocationTestCase(context, name, desc, storage, flags)
+{
+}
+
+std::string InvocationBasicCase::genShaderSource (void) const
+{
+	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
+	std::ostringstream	buf;
+
+	buf << "#version 310 es\n"
+		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
+		<< "layout (local_size_x=" << m_localWidth << ", local_size_y=" << m_localHeight << ") in;\n"
+		<< "layout(binding=0, std430) buffer Output\n"
+		<< "{\n"
+		<< "	highp int values[];\n"
+		<< "} sb_result;\n";
+
+	if (m_storage == STORAGE_BUFFER)
+		buf << "layout(binding=1, std430) coherent buffer Storage\n"
+			<< "{\n"
+			<< "	highp int values[];\n"
+			<< "} sb_store;\n"
+			<< "\n"
+			<< "highp int getIndex (in highp uvec2 localID, in highp int element)\n"
+			<< "{\n"
+			<< "	highp uint groupNdx = gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+			<< "	return int((localID.y * gl_NumWorkGroups.x * gl_NumWorkGroups.y * gl_WorkGroupSize.x) + (groupNdx * gl_WorkGroupSize.x) + localID.x) * " << m_elementsPerInvocation << " + element;\n"
+			<< "}\n";
+	else if (m_storage == STORAGE_IMAGE)
+		buf << "layout(r32i, binding=1) coherent uniform highp iimage2D u_image;\n"
+			<< "\n"
+			<< "highp ivec2 getCoord (in highp uvec2 localID, in highp int element)\n"
+			<< "{\n"
+			<< "	return ivec2(int(gl_WorkGroupID.x * gl_WorkGroupSize.x + localID.x), int(gl_WorkGroupID.y * gl_WorkGroupSize.y + localID.y) + element * " << m_workHeight << ");\n"
+			<< "}\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	int resultNdx   = int(gl_GlobalInvocationID.y * gl_NumWorkGroups.x * gl_WorkGroupSize.x + gl_GlobalInvocationID.x);\n"
+		<< "	int groupNdx    = int(gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x);\n"
+		<< "	bool allOk      = true;\n"
+		<< "\n"
+		<< genShaderMainBlock()
+		<< "\n"
+		<< "	sb_result.values[resultNdx] = (allOk) ? (1) : (0);\n"
+		<< "}\n";
+
+	return buf.str();
+}
+
+class InvocationWriteReadCase : public InvocationBasicCase
+{
+public:
+					InvocationWriteReadCase		(Context& context, const char* name, const char* desc, StorageType storage, int flags);
+private:
+	std::string		genShaderMainBlock			(void) const;
+};
+
+InvocationWriteReadCase::InvocationWriteReadCase (Context& context, const char* name, const char* desc, StorageType storage, int flags)
+	: InvocationBasicCase(context, name, desc, storage, flags)
+{
+}
+
+std::string InvocationWriteReadCase::genShaderMainBlock (void) const
+{
+	std::ostringstream buf;
+
+	// write
+
+	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
+	{
+		if (m_storage == STORAGE_BUFFER && m_useAtomic)
+			buf << "\tatomicAdd(sb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], groupNdx);\n";
+		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
+			buf << "\tsb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = groupNdx;\n";
+		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
+			buf << "\timageAtomicAdd(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), int(groupNdx));\n";
+		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
+			buf << "\timageStore(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), ivec4(int(groupNdx), 0, 0, 0));\n";
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// barrier
+
+	buf << genBarrierSource();
+
+	// read
+
+	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
+	{
+		const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx+1) + ", " + de::toString(2*ndx) + ")) % gl_WorkGroupSize.xy") : ("gl_LocalInvocationID.xy");
+
+		if (m_storage == STORAGE_BUFFER && m_useAtomic)
+			buf << "\tallOk = allOk && (atomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx << ")], 0) == groupNdx);\n";
+		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
+			buf << "\tallOk = allOk && (sb_store.values[getIndex(" << localID << ", " << ndx << ")] == groupNdx);\n";
+		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
+			buf << "\tallOk = allOk && (imageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx << "), 0) == groupNdx);\n";
+		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
+			buf << "\tallOk = allOk && (imageLoad(u_image, getCoord(" << localID << ", " << ndx << ")).x == groupNdx);\n";
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return buf.str();
+}
+
+class InvocationReadWriteCase : public InvocationBasicCase
+{
+public:
+					InvocationReadWriteCase		(Context& context, const char* name, const char* desc, StorageType storage, int flags);
+private:
+	std::string		genShaderMainBlock			(void) const;
+};
+
+InvocationReadWriteCase::InvocationReadWriteCase (Context& context, const char* name, const char* desc, StorageType storage, int flags)
+	: InvocationBasicCase(context, name, desc, storage, flags)
+{
+}
+
+std::string InvocationReadWriteCase::genShaderMainBlock (void) const
+{
+	std::ostringstream buf;
+
+	// read
+
+	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
+	{
+		const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx+1) + ", " + de::toString(2*ndx) + ")) % gl_WorkGroupSize.xy") : ("gl_LocalInvocationID.xy");
+
+		if (m_storage == STORAGE_BUFFER && m_useAtomic)
+			buf << "\tallOk = allOk && (atomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx << ")], 123) == 0);\n";
+		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
+			buf << "\tallOk = allOk && (sb_store.values[getIndex(" << localID << ", " << ndx << ")] == 0);\n";
+		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
+			buf << "\tallOk = allOk && (imageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx << "), 123) == 0);\n";
+		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
+			buf << "\tallOk = allOk && (imageLoad(u_image, getCoord(" << localID << ", " << ndx << ")).x == 0);\n";
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// barrier
+
+	buf << genBarrierSource();
+
+	// write
+
+	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
+	{
+		if (m_storage == STORAGE_BUFFER && m_useAtomic)
+			buf << "\tatomicAdd(sb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], groupNdx);\n";
+		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
+			buf << "\tsb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = groupNdx;\n";
+		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
+			buf << "\timageAtomicAdd(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), int(groupNdx));\n";
+		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
+			buf << "\timageStore(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), ivec4(int(groupNdx), 0, 0, 0));\n";
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return buf.str();
+}
+
+class InvocationOverWriteCase : public InvocationBasicCase
+{
+public:
+					InvocationOverWriteCase		(Context& context, const char* name, const char* desc, StorageType storage, int flags);
+private:
+	std::string		genShaderMainBlock			(void) const;
+};
+
+InvocationOverWriteCase::InvocationOverWriteCase (Context& context, const char* name, const char* desc, StorageType storage, int flags)
+	: InvocationBasicCase(context, name, desc, storage, flags)
+{
+}
+
+std::string InvocationOverWriteCase::genShaderMainBlock (void) const
+{
+	std::ostringstream buf;
+
+	// write
+
+	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
+	{
+		if (m_storage == STORAGE_BUFFER && m_useAtomic)
+			buf << "\tatomicAdd(sb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], 456);\n";
+		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
+			buf << "\tsb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = 456;\n";
+		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
+			buf << "\timageAtomicAdd(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), 456);\n";
+		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
+			buf << "\timageStore(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), ivec4(456, 0, 0, 0));\n";
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// barrier
+
+	buf << genBarrierSource();
+
+	// write over
+
+	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
+	{
+		// write another invocation's value or our own value depending on test type
+		const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx+4) + ", " + de::toString(3*ndx) + ")) % gl_WorkGroupSize.xy") : ("gl_LocalInvocationID.xy");
+
+		if (m_storage == STORAGE_BUFFER && m_useAtomic)
+			buf << "\tatomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx << ")], groupNdx);\n";
+		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
+			buf << "\tsb_store.values[getIndex(" << localID << ", " << ndx << ")] = groupNdx;\n";
+		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
+			buf << "\timageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx << "), groupNdx);\n";
+		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
+			buf << "\timageStore(u_image, getCoord(" << localID << ", " << ndx << "), ivec4(groupNdx, 0, 0, 0));\n";
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// barrier
+
+	buf << genBarrierSource();
+
+	// read
+
+	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
+	{
+		// check another invocation's value or our own value depending on test type
+		const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx+1) + ", " + de::toString(2*ndx) + ")) % gl_WorkGroupSize.xy") : ("gl_LocalInvocationID.xy");
+
+		if (m_storage == STORAGE_BUFFER && m_useAtomic)
+			buf << "\tallOk = allOk && (atomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx << ")], 123) == groupNdx);\n";
+		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
+			buf << "\tallOk = allOk && (sb_store.values[getIndex(" << localID << ", " << ndx << ")] == groupNdx);\n";
+		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
+			buf << "\tallOk = allOk && (imageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx << "), 123) == groupNdx);\n";
+		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
+			buf << "\tallOk = allOk && (imageLoad(u_image, getCoord(" << localID << ", " << ndx << ")).x == groupNdx);\n";
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return buf.str();
+}
+
+class InvocationAliasWriteCase : public InterInvocationTestCase
+{
+public:
+	enum TestType
+	{
+		TYPE_WRITE = 0,
+		TYPE_OVERWRITE,
+
+		TYPE_LAST
+	};
+
+					InvocationAliasWriteCase	(Context& context, const char* name, const char* desc, TestType type, StorageType storage, int flags);
+private:
+	std::string		genShaderSource				(void) const;
+
+	const TestType	m_type;
+};
+
+InvocationAliasWriteCase::InvocationAliasWriteCase (Context& context, const char* name, const char* desc, TestType type, StorageType storage, int flags)
+	: InterInvocationTestCase	(context, name, desc, storage, flags | FLAG_ALIASING_STORAGES)
+	, m_type					(type)
+{
+	DE_ASSERT(type < TYPE_LAST);
+}
+
+std::string InvocationAliasWriteCase::genShaderSource (void) const
+{
+	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
+	std::ostringstream	buf;
+
+	buf << "#version 310 es\n"
+		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
+		<< "layout (local_size_x=" << m_localWidth << ", local_size_y=" << m_localHeight << ") in;\n"
+		<< "layout(binding=0, std430) buffer Output\n"
+		<< "{\n"
+		<< "	highp int values[];\n"
+		<< "} sb_result;\n";
+
+	if (m_storage == STORAGE_BUFFER)
+		buf << "layout(binding=1, std430) coherent buffer Storage0\n"
+			<< "{\n"
+			<< "	highp int values[];\n"
+			<< "} sb_store0;\n"
+			<< "layout(binding=2, std430) coherent buffer Storage1\n"
+			<< "{\n"
+			<< "	highp int values[];\n"
+			<< "} sb_store1;\n"
+			<< "\n"
+			<< "highp int getIndex (in highp uvec2 localID, in highp int element)\n"
+			<< "{\n"
+			<< "	highp uint groupNdx = gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
+			<< "	return int((localID.y * gl_NumWorkGroups.x * gl_NumWorkGroups.y * gl_WorkGroupSize.x) + (groupNdx * gl_WorkGroupSize.x) + localID.x) * " << m_elementsPerInvocation << " + element;\n"
+			<< "}\n";
+	else if (m_storage == STORAGE_IMAGE)
+		buf << "layout(r32i, binding=1) coherent uniform highp iimage2D u_image0;\n"
+			<< "layout(r32i, binding=2) coherent uniform highp iimage2D u_image1;\n"
+			<< "\n"
+			<< "highp ivec2 getCoord (in highp uvec2 localID, in highp int element)\n"
+			<< "{\n"
+			<< "	return ivec2(int(gl_WorkGroupID.x * gl_WorkGroupSize.x + localID.x), int(gl_WorkGroupID.y * gl_WorkGroupSize.y + localID.y) + element * " << m_workHeight << ");\n"
+			<< "}\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	int resultNdx   = int(gl_GlobalInvocationID.y * gl_NumWorkGroups.x * gl_WorkGroupSize.x + gl_GlobalInvocationID.x);\n"
+		<< "	int groupNdx    = int(gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x);\n"
+		<< "	bool allOk      = true;\n"
+		<< "\n";
+
+	if (m_type == TYPE_OVERWRITE)
+	{
+		// write
+
+		for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
+		{
+			if (m_storage == STORAGE_BUFFER && m_useAtomic)
+				buf << "\tatomicAdd(sb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], 456);\n";
+			else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
+				buf << "\tsb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = 456;\n";
+			else if (m_storage == STORAGE_IMAGE && m_useAtomic)
+				buf << "\timageAtomicAdd(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx << "), 456);\n";
+			else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
+				buf << "\timageStore(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx << "), ivec4(456, 0, 0, 0));\n";
+			else
+				DE_ASSERT(DE_FALSE);
+		}
+
+		// barrier
+
+		buf << genBarrierSource();
+	}
+	else
+		DE_ASSERT(m_type == TYPE_WRITE);
+
+	// write (again)
+
+	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
+	{
+		const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx+2) + ", " + de::toString(2*ndx) + ")) % gl_WorkGroupSize.xy") : ("gl_LocalInvocationID.xy");
+
+		if (m_storage == STORAGE_BUFFER && m_useAtomic)
+			buf << "\tatomicExchange(sb_store1.values[getIndex(" << localID << ", " << ndx << ")], groupNdx);\n";
+		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
+			buf << "\tsb_store1.values[getIndex(" << localID << ", " << ndx << ")] = groupNdx;\n";
+		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
+			buf << "\timageAtomicExchange(u_image1, getCoord(" << localID << ", " << ndx << "), groupNdx);\n";
+		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
+			buf << "\timageStore(u_image1, getCoord(" << localID << ", " << ndx << "), ivec4(groupNdx, 0, 0, 0));\n";
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// barrier
+
+	buf << genBarrierSource();
+
+	// read
+
+	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
+	{
+		if (m_storage == STORAGE_BUFFER && m_useAtomic)
+			buf << "\tallOk = allOk && (atomicExchange(sb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], 123) == groupNdx);\n";
+		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
+			buf << "\tallOk = allOk && (sb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] == groupNdx);\n";
+		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
+			buf << "\tallOk = allOk && (imageAtomicExchange(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx << "), 123) == groupNdx);\n";
+		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
+			buf << "\tallOk = allOk && (imageLoad(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx << ")).x == groupNdx);\n";
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// return result
+
+	buf << "\n"
+		<< "	sb_result.values[resultNdx] = (allOk) ? (1) : (0);\n"
+		<< "}\n";
+
+	return buf.str();
+}
+
+namespace op
+{
+
+struct WriteData
+{
+	int targetHandle;
+	int seed;
+
+	static WriteData Generate(int targetHandle, int seed)
+	{
+		WriteData retVal;
+
+		retVal.targetHandle = targetHandle;
+		retVal.seed = seed;
+
+		return retVal;
+	}
+};
+
+struct ReadData
+{
+	int targetHandle;
+	int seed;
+
+	static ReadData Generate(int targetHandle, int seed)
+	{
+		ReadData retVal;
+
+		retVal.targetHandle = targetHandle;
+		retVal.seed = seed;
+
+		return retVal;
+	}
+};
+
+struct Barrier
+{
+};
+
+struct WriteDataInterleaved
+{
+	int		targetHandle;
+	int		seed;
+	bool	evenOdd;
+
+	static WriteDataInterleaved Generate(int targetHandle, int seed, bool evenOdd)
+	{
+		WriteDataInterleaved retVal;
+
+		retVal.targetHandle = targetHandle;
+		retVal.seed = seed;
+		retVal.evenOdd = evenOdd;
+
+		return retVal;
+	}
+};
+
+struct ReadDataInterleaved
+{
+	int targetHandle;
+	int seed0;
+	int seed1;
+
+	static ReadDataInterleaved Generate(int targetHandle, int seed0, int seed1)
+	{
+		ReadDataInterleaved retVal;
+
+		retVal.targetHandle = targetHandle;
+		retVal.seed0 = seed0;
+		retVal.seed1 = seed1;
+
+		return retVal;
+	}
+};
+
+struct ReadMultipleData
+{
+	int targetHandle0;
+	int seed0;
+	int targetHandle1;
+	int seed1;
+
+	static ReadMultipleData Generate(int targetHandle0, int seed0, int targetHandle1, int seed1)
+	{
+		ReadMultipleData retVal;
+
+		retVal.targetHandle0 = targetHandle0;
+		retVal.seed0 = seed0;
+		retVal.targetHandle1 = targetHandle1;
+		retVal.seed1 = seed1;
+
+		return retVal;
+	}
+};
+
+struct ReadZeroData
+{
+	int targetHandle;
+
+	static ReadZeroData Generate(int targetHandle)
+	{
+		ReadZeroData retVal;
+
+		retVal.targetHandle = targetHandle;
+
+		return retVal;
+	}
+};
+
+} // namespace op
+
+class InterCallTestCase;
+
+class InterCallOperations
+{
+public:
+	InterCallOperations& operator<< (const op::WriteData&);
+	InterCallOperations& operator<< (const op::ReadData&);
+	InterCallOperations& operator<< (const op::Barrier&);
+	InterCallOperations& operator<< (const op::ReadMultipleData&);
+	InterCallOperations& operator<< (const op::WriteDataInterleaved&);
+	InterCallOperations& operator<< (const op::ReadDataInterleaved&);
+	InterCallOperations& operator<< (const op::ReadZeroData&);
+
+private:
+	struct Command
+	{
+		enum CommandType
+		{
+			TYPE_WRITE = 0,
+			TYPE_READ,
+			TYPE_BARRIER,
+			TYPE_READ_MULTIPLE,
+			TYPE_WRITE_INTERLEAVE,
+			TYPE_READ_INTERLEAVE,
+			TYPE_READ_ZERO,
+
+			TYPE_LAST
+		};
+
+		CommandType type;
+
+		union CommandUnion
+		{
+			op::WriteData				write;
+			op::ReadData				read;
+			op::Barrier					barrier;
+			op::ReadMultipleData		readMulti;
+			op::WriteDataInterleaved	writeInterleave;
+			op::ReadDataInterleaved		readInterleave;
+			op::ReadZeroData			readZero;
+		} u_cmd;
+	};
+
+	friend class InterCallTestCase;
+
+	std::vector<Command> m_cmds;
+};
+
+InterCallOperations& InterCallOperations::operator<< (const op::WriteData& cmd)
+{
+	m_cmds.push_back(Command());
+	m_cmds.back().type = Command::TYPE_WRITE;
+	m_cmds.back().u_cmd.write = cmd;
+
+	return *this;
+}
+
+InterCallOperations& InterCallOperations::operator<< (const op::ReadData& cmd)
+{
+	m_cmds.push_back(Command());
+	m_cmds.back().type = Command::TYPE_READ;
+	m_cmds.back().u_cmd.read = cmd;
+
+	return *this;
+}
+
+InterCallOperations& InterCallOperations::operator<< (const op::Barrier& cmd)
+{
+	m_cmds.push_back(Command());
+	m_cmds.back().type = Command::TYPE_BARRIER;
+	m_cmds.back().u_cmd.barrier = cmd;
+
+	return *this;
+}
+
+InterCallOperations& InterCallOperations::operator<< (const op::ReadMultipleData& cmd)
+{
+	m_cmds.push_back(Command());
+	m_cmds.back().type = Command::TYPE_READ_MULTIPLE;
+	m_cmds.back().u_cmd.readMulti = cmd;
+
+	return *this;
+}
+
+InterCallOperations& InterCallOperations::operator<< (const op::WriteDataInterleaved& cmd)
+{
+	m_cmds.push_back(Command());
+	m_cmds.back().type = Command::TYPE_WRITE_INTERLEAVE;
+	m_cmds.back().u_cmd.writeInterleave = cmd;
+
+	return *this;
+}
+
+InterCallOperations& InterCallOperations::operator<< (const op::ReadDataInterleaved& cmd)
+{
+	m_cmds.push_back(Command());
+	m_cmds.back().type = Command::TYPE_READ_INTERLEAVE;
+	m_cmds.back().u_cmd.readInterleave = cmd;
+
+	return *this;
+}
+
+InterCallOperations& InterCallOperations::operator<< (const op::ReadZeroData& cmd)
+{
+	m_cmds.push_back(Command());
+	m_cmds.back().type = Command::TYPE_READ_ZERO;
+	m_cmds.back().u_cmd.readZero = cmd;
+
+	return *this;
+}
+
+class InterCallTestCase : public TestCase
+{
+public:
+	enum StorageType
+	{
+		STORAGE_BUFFER = 0,
+		STORAGE_IMAGE,
+
+		STORAGE_LAST
+	};
+	enum Flags
+	{
+		FLAG_USE_ATOMIC	= 1,
+		FLAG_USE_INT	= 2,
+	};
+													InterCallTestCase			(Context& context, const char* name, const char* desc, StorageType storage, int flags, const InterCallOperations& ops);
+													~InterCallTestCase			(void);
+
+private:
+	void											init						(void);
+	void											deinit						(void);
+	IterateResult									iterate						(void);
+	bool											verifyResults				(void);
+
+	void											runCommand					(const op::WriteData& cmd, int stepNdx, int& programFriendlyName);
+	void											runCommand					(const op::ReadData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName);
+	void											runCommand					(const op::Barrier&);
+	void											runCommand					(const op::ReadMultipleData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName);
+	void											runCommand					(const op::WriteDataInterleaved& cmd, int stepNdx, int& programFriendlyName);
+	void											runCommand					(const op::ReadDataInterleaved& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName);
+	void											runCommand					(const op::ReadZeroData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName);
+	void											runSingleRead				(int targetHandle, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName);
+
+	glw::GLuint										genStorage					(int friendlyName);
+	glw::GLuint										genResultStorage			(void);
+	glu::ShaderProgram*								genWriteProgram				(int seed);
+	glu::ShaderProgram*								genReadProgram				(int seed);
+	glu::ShaderProgram*								genReadMultipleProgram		(int seed0, int seed1);
+	glu::ShaderProgram*								genWriteInterleavedProgram	(int seed, bool evenOdd);
+	glu::ShaderProgram*								genReadInterleavedProgram	(int seed0, int seed1);
+	glu::ShaderProgram*								genReadZeroProgram			(void);
+
+	const StorageType								m_storage;
+	const int										m_invocationGridSize;	// !< width and height of the two dimensional work dispatch
+	const int										m_perInvocationSize;	// !< number of elements accessed in single invocation
+	const std::vector<InterCallOperations::Command>	m_cmds;
+	const bool										m_useAtomic;
+	const bool										m_formatInteger;
+
+	std::vector<glu::ShaderProgram*>				m_operationPrograms;
+	std::vector<glw::GLuint>						m_operationResultStorages;
+	std::map<int, glw::GLuint>						m_storageIDs;
+};
+
+InterCallTestCase::InterCallTestCase (Context& context, const char* name, const char* desc, StorageType storage, int flags, const InterCallOperations& ops)
+	: TestCase					(context, name, desc)
+	, m_storage					(storage)
+	, m_invocationGridSize		(512)
+	, m_perInvocationSize		(2)
+	, m_cmds					(ops.m_cmds)
+	, m_useAtomic				((flags & FLAG_USE_ATOMIC) != 0)
+	, m_formatInteger			((flags & FLAG_USE_INT) != 0)
+{
+}
+
+InterCallTestCase::~InterCallTestCase (void)
+{
+	deinit();
+}
+
+void InterCallTestCase::init (void)
+{
+	int programFriendlyName = 0;
+
+	// requirements
+
+	if (m_useAtomic && m_storage == STORAGE_IMAGE && !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_image_atomic extension");
+
+	// generate resources and validate command list
+
+	m_operationPrograms.resize(m_cmds.size(), DE_NULL);
+	m_operationResultStorages.resize(m_cmds.size(), 0);
+
+	for (int step = 0; step < (int)m_cmds.size(); ++step)
+	{
+		switch (m_cmds[step].type)
+		{
+			case InterCallOperations::Command::TYPE_WRITE:
+			{
+				const op::WriteData& cmd = m_cmds[step].u_cmd.write;
+
+				// new storage handle?
+				if (m_storageIDs.find(cmd.targetHandle) == m_storageIDs.end())
+					m_storageIDs[cmd.targetHandle] = genStorage(cmd.targetHandle);
+
+				// program
+				{
+					glu::ShaderProgram* program = genWriteProgram(cmd.seed);
+
+					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
+					m_testCtx.getLog() << *program;
+
+					if (!program->isOk())
+						throw tcu::TestError("could not build program");
+
+					m_operationPrograms[step] = program;
+				}
+				break;
+			}
+
+			case InterCallOperations::Command::TYPE_READ:
+			{
+				const op::ReadData& cmd = m_cmds[step].u_cmd.read;
+				DE_ASSERT(m_storageIDs.find(cmd.targetHandle) != m_storageIDs.end());
+
+				// program and result storage
+				{
+					glu::ShaderProgram* program = genReadProgram(cmd.seed);
+
+					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
+					m_testCtx.getLog() << *program;
+
+					if (!program->isOk())
+						throw tcu::TestError("could not build program");
+
+					m_operationPrograms[step] = program;
+					m_operationResultStorages[step] = genResultStorage();
+				}
+				break;
+			}
+
+			case InterCallOperations::Command::TYPE_BARRIER:
+			{
+				break;
+			}
+
+			case InterCallOperations::Command::TYPE_READ_MULTIPLE:
+			{
+				const op::ReadMultipleData& cmd = m_cmds[step].u_cmd.readMulti;
+				DE_ASSERT(m_storageIDs.find(cmd.targetHandle0) != m_storageIDs.end());
+				DE_ASSERT(m_storageIDs.find(cmd.targetHandle1) != m_storageIDs.end());
+
+				// program
+				{
+					glu::ShaderProgram* program = genReadMultipleProgram(cmd.seed0, cmd.seed1);
+
+					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
+					m_testCtx.getLog() << *program;
+
+					if (!program->isOk())
+						throw tcu::TestError("could not build program");
+
+					m_operationPrograms[step] = program;
+					m_operationResultStorages[step] = genResultStorage();
+				}
+				break;
+			}
+
+			case InterCallOperations::Command::TYPE_WRITE_INTERLEAVE:
+			{
+				const op::WriteDataInterleaved& cmd = m_cmds[step].u_cmd.writeInterleave;
+
+				// new storage handle?
+				if (m_storageIDs.find(cmd.targetHandle) == m_storageIDs.end())
+					m_storageIDs[cmd.targetHandle] = genStorage(cmd.targetHandle);
+
+				// program
+				{
+					glu::ShaderProgram* program = genWriteInterleavedProgram(cmd.seed, cmd.evenOdd);
+
+					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
+					m_testCtx.getLog() << *program;
+
+					if (!program->isOk())
+						throw tcu::TestError("could not build program");
+
+					m_operationPrograms[step] = program;
+				}
+				break;
+			}
+
+			case InterCallOperations::Command::TYPE_READ_INTERLEAVE:
+			{
+				const op::ReadDataInterleaved& cmd = m_cmds[step].u_cmd.readInterleave;
+				DE_ASSERT(m_storageIDs.find(cmd.targetHandle) != m_storageIDs.end());
+
+				// program
+				{
+					glu::ShaderProgram* program = genReadInterleavedProgram(cmd.seed0, cmd.seed1);
+
+					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
+					m_testCtx.getLog() << *program;
+
+					if (!program->isOk())
+						throw tcu::TestError("could not build program");
+
+					m_operationPrograms[step] = program;
+					m_operationResultStorages[step] = genResultStorage();
+				}
+				break;
+			}
+
+			case InterCallOperations::Command::TYPE_READ_ZERO:
+			{
+				const op::ReadZeroData& cmd = m_cmds[step].u_cmd.readZero;
+
+				// new storage handle?
+				if (m_storageIDs.find(cmd.targetHandle) == m_storageIDs.end())
+					m_storageIDs[cmd.targetHandle] = genStorage(cmd.targetHandle);
+
+				// program
+				{
+					glu::ShaderProgram* program = genReadZeroProgram();
+
+					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
+					m_testCtx.getLog() << *program;
+
+					if (!program->isOk())
+						throw tcu::TestError("could not build program");
+
+					m_operationPrograms[step] = program;
+					m_operationResultStorages[step] = genResultStorage();
+				}
+				break;
+			}
+
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+void InterCallTestCase::deinit (void)
+{
+	// programs
+	for (int ndx = 0; ndx < (int)m_operationPrograms.size(); ++ndx)
+		delete m_operationPrograms[ndx];
+	m_operationPrograms.clear();
+
+	// result storages
+	for (int ndx = 0; ndx < (int)m_operationResultStorages.size(); ++ndx)
+	{
+		if (m_operationResultStorages[ndx])
+			m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_operationResultStorages[ndx]);
+	}
+	m_operationResultStorages.clear();
+
+	// storage
+	for (std::map<int, glw::GLuint>::const_iterator it = m_storageIDs.begin(); it != m_storageIDs.end(); ++it)
+	{
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		if (m_storage == STORAGE_BUFFER)
+			gl.deleteBuffers(1, &it->second);
+		else if (m_storage == STORAGE_IMAGE)
+			gl.deleteTextures(1, &it->second);
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+	m_storageIDs.clear();
+}
+
+InterCallTestCase::IterateResult InterCallTestCase::iterate (void)
+{
+	int programFriendlyName			= 0;
+	int resultStorageFriendlyName	= 0;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Running operations:" << tcu::TestLog::EndMessage;
+
+	// run steps
+
+	for (int step = 0; step < (int)m_cmds.size(); ++step)
+	{
+		switch (m_cmds[step].type)
+		{
+			case InterCallOperations::Command::TYPE_WRITE:				runCommand(m_cmds[step].u_cmd.write,			step,	programFriendlyName);								break;
+			case InterCallOperations::Command::TYPE_READ:				runCommand(m_cmds[step].u_cmd.read,				step,	programFriendlyName, resultStorageFriendlyName);	break;
+			case InterCallOperations::Command::TYPE_BARRIER:			runCommand(m_cmds[step].u_cmd.barrier);																		break;
+			case InterCallOperations::Command::TYPE_READ_MULTIPLE:		runCommand(m_cmds[step].u_cmd.readMulti,		step,	programFriendlyName, resultStorageFriendlyName);	break;
+			case InterCallOperations::Command::TYPE_WRITE_INTERLEAVE:	runCommand(m_cmds[step].u_cmd.writeInterleave,	step,	programFriendlyName);								break;
+			case InterCallOperations::Command::TYPE_READ_INTERLEAVE:	runCommand(m_cmds[step].u_cmd.readInterleave,	step,	programFriendlyName, resultStorageFriendlyName);	break;
+			case InterCallOperations::Command::TYPE_READ_ZERO:			runCommand(m_cmds[step].u_cmd.readZero,			step,	programFriendlyName, resultStorageFriendlyName);	break;
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+
+	// read results from result buffers
+	if (verifyResults())
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, (std::string((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) + " content verification failed").c_str());
+
+	return STOP;
+}
+
+bool InterCallTestCase::verifyResults (void)
+{
+	int		resultBufferFriendlyName	= 0;
+	bool	allResultsOk				= true;
+	bool	anyResult					= false;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Reading verifier program results" << tcu::TestLog::EndMessage;
+
+	for (int step = 0; step < (int)m_cmds.size(); ++step)
+	{
+		const int	errorFloodThreshold	= 5;
+		int			numErrorsLogged		= 0;
+
+		if (m_operationResultStorages[step])
+		{
+			const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+			const void*				mapped	= DE_NULL;
+			std::vector<deInt32>	results	(m_invocationGridSize * m_invocationGridSize);
+			bool					error	= false;
+
+			anyResult = true;
+
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_operationResultStorages[step]);
+			mapped = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, m_invocationGridSize * m_invocationGridSize * sizeof(deUint32), GL_MAP_READ_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "map buffer");
+
+			// copy to properly aligned array
+			deMemcpy(&results[0], mapped, m_invocationGridSize * m_invocationGridSize * sizeof(deUint32));
+
+			if (gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER) != GL_TRUE)
+				throw tcu::TestError("memory map store corrupted");
+
+			// check the results
+			for (int ndx = 0; ndx < (int)results.size(); ++ndx)
+			{
+				if (results[ndx] != 1)
+				{
+					error = true;
+
+					if (numErrorsLogged == 0)
+						m_testCtx.getLog() << tcu::TestLog::Message << "Result storage #" << ++resultBufferFriendlyName << " failed, got unexpected values.\n" << tcu::TestLog::EndMessage;
+					if (numErrorsLogged++ < errorFloodThreshold)
+						m_testCtx.getLog() << tcu::TestLog::Message << "	Error at index " << ndx << ": expected 1, got " << results[ndx] << ".\n" << tcu::TestLog::EndMessage;
+					else
+					{
+						// after N errors, no point continuing verification
+						m_testCtx.getLog() << tcu::TestLog::Message << "	-- too many errors, skipping verification --\n" << tcu::TestLog::EndMessage;
+						break;
+					}
+				}
+			}
+
+			if (error)
+			{
+				allResultsOk = false;
+			}
+			else
+				m_testCtx.getLog() << tcu::TestLog::Message << "Result storage #" << ++resultBufferFriendlyName << " ok." << tcu::TestLog::EndMessage;
+		}
+	}
+
+	DE_ASSERT(anyResult);
+	DE_UNREF(anyResult);
+
+	return allResultsOk;
+}
+
+void InterCallTestCase::runCommand (const op::WriteData& cmd, int stepNdx, int& programFriendlyName)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Running program #" << ++programFriendlyName << " to write " << ((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) << " #" << cmd.targetHandle << ".\n"
+		<< "	Dispatch size: " << m_invocationGridSize << "x" << m_invocationGridSize << "."
+		<< tcu::TestLog::EndMessage;
+
+	gl.useProgram(m_operationPrograms[stepNdx]->getProgram());
+
+	// set destination
+	if (m_storage == STORAGE_BUFFER)
+	{
+		DE_ASSERT(m_storageIDs[cmd.targetHandle]);
+
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_storageIDs[cmd.targetHandle]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination buffer");
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		DE_ASSERT(m_storageIDs[cmd.targetHandle]);
+
+		gl.bindImageTexture(0, m_storageIDs[cmd.targetHandle], 0, GL_FALSE, 0, (m_useAtomic) ? (GL_READ_WRITE) : (GL_WRITE_ONLY), (m_formatInteger) ? (GL_R32I) : (GL_R32F));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination image");
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	// calc
+	gl.dispatchCompute(m_invocationGridSize, m_invocationGridSize, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch write");
+}
+
+void InterCallTestCase::runCommand (const op::ReadData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName)
+{
+	runSingleRead(cmd.targetHandle, stepNdx, programFriendlyName, resultStorageFriendlyName);
+}
+
+void InterCallTestCase::runCommand (const op::Barrier& cmd)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	DE_UNREF(cmd);
+
+	if (m_storage == STORAGE_BUFFER)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Memory Barrier\n\tbits = GL_SHADER_STORAGE_BARRIER_BIT" << tcu::TestLog::EndMessage;
+		gl.memoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Memory Barrier\n\tbits = GL_SHADER_IMAGE_ACCESS_BARRIER_BIT" << tcu::TestLog::EndMessage;
+		gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+}
+
+void InterCallTestCase::runCommand (const op::ReadMultipleData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Running program #" << ++programFriendlyName << " to verify " << ((m_storage == STORAGE_BUFFER) ? ("buffers") : ("images")) << " #" << cmd.targetHandle0 << " and #" << cmd.targetHandle1 << ".\n"
+		<< "	Writing results to result storage #" << ++resultStorageFriendlyName << ".\n"
+		<< "	Dispatch size: " << m_invocationGridSize << "x" << m_invocationGridSize << "."
+		<< tcu::TestLog::EndMessage;
+
+	gl.useProgram(m_operationPrograms[stepNdx]->getProgram());
+
+	// set sources
+	if (m_storage == STORAGE_BUFFER)
+	{
+		DE_ASSERT(m_storageIDs[cmd.targetHandle0]);
+		DE_ASSERT(m_storageIDs[cmd.targetHandle1]);
+
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageIDs[cmd.targetHandle0]);
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_storageIDs[cmd.targetHandle1]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buffers");
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		DE_ASSERT(m_storageIDs[cmd.targetHandle0]);
+		DE_ASSERT(m_storageIDs[cmd.targetHandle1]);
+
+		gl.bindImageTexture(1, m_storageIDs[cmd.targetHandle0], 0, GL_FALSE, 0, (m_useAtomic) ? (GL_READ_WRITE) : (GL_READ_ONLY), (m_formatInteger) ? (GL_R32I) : (GL_R32F));
+		gl.bindImageTexture(2, m_storageIDs[cmd.targetHandle1], 0, GL_FALSE, 0, (m_useAtomic) ? (GL_READ_WRITE) : (GL_READ_ONLY), (m_formatInteger) ? (GL_R32I) : (GL_R32F));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source images");
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	// set destination
+	DE_ASSERT(m_operationResultStorages[stepNdx]);
+	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_operationResultStorages[stepNdx]);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buffer");
+
+	// calc
+	gl.dispatchCompute(m_invocationGridSize, m_invocationGridSize, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch read multi");
+}
+
+void InterCallTestCase::runCommand (const op::WriteDataInterleaved& cmd, int stepNdx, int& programFriendlyName)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Running program #" << ++programFriendlyName << " to write " << ((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) << " #" << cmd.targetHandle << ".\n"
+		<< "	Writing to every " << ((cmd.evenOdd) ? ("even") : ("odd")) << " " << ((m_storage == STORAGE_BUFFER) ? ("element") : ("column")) << ".\n"
+		<< "	Dispatch size: " << m_invocationGridSize / 2 << "x" << m_invocationGridSize << "."
+		<< tcu::TestLog::EndMessage;
+
+	gl.useProgram(m_operationPrograms[stepNdx]->getProgram());
+
+	// set destination
+	if (m_storage == STORAGE_BUFFER)
+	{
+		DE_ASSERT(m_storageIDs[cmd.targetHandle]);
+
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_storageIDs[cmd.targetHandle]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination buffer");
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		DE_ASSERT(m_storageIDs[cmd.targetHandle]);
+
+		gl.bindImageTexture(0, m_storageIDs[cmd.targetHandle], 0, GL_FALSE, 0, (m_useAtomic) ? (GL_READ_WRITE) : (GL_WRITE_ONLY), (m_formatInteger) ? (GL_R32I) : (GL_R32F));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination image");
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	// calc
+	gl.dispatchCompute(m_invocationGridSize / 2, m_invocationGridSize, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch write");
+}
+
+void InterCallTestCase::runCommand (const op::ReadDataInterleaved& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName)
+{
+	runSingleRead(cmd.targetHandle, stepNdx, programFriendlyName, resultStorageFriendlyName);
+}
+
+void InterCallTestCase::runCommand (const op::ReadZeroData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName)
+{
+	runSingleRead(cmd.targetHandle, stepNdx, programFriendlyName, resultStorageFriendlyName);
+}
+
+void InterCallTestCase::runSingleRead (int targetHandle, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Running program #" << ++programFriendlyName << " to verify " << ((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) << " #" << targetHandle << ".\n"
+		<< "	Writing results to result storage #" << ++resultStorageFriendlyName << ".\n"
+		<< "	Dispatch size: " << m_invocationGridSize << "x" << m_invocationGridSize << "."
+		<< tcu::TestLog::EndMessage;
+
+	gl.useProgram(m_operationPrograms[stepNdx]->getProgram());
+
+	// set source
+	if (m_storage == STORAGE_BUFFER)
+	{
+		DE_ASSERT(m_storageIDs[targetHandle]);
+
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageIDs[targetHandle]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buffer");
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		DE_ASSERT(m_storageIDs[targetHandle]);
+
+		gl.bindImageTexture(1, m_storageIDs[targetHandle], 0, GL_FALSE, 0, (m_useAtomic) ? (GL_READ_WRITE) : (GL_READ_ONLY), (m_formatInteger) ? (GL_R32I) : (GL_R32F));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source image");
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	// set destination
+	DE_ASSERT(m_operationResultStorages[stepNdx]);
+	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_operationResultStorages[stepNdx]);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buffer");
+
+	// calc
+	gl.dispatchCompute(m_invocationGridSize, m_invocationGridSize, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch read");
+}
+
+glw::GLuint InterCallTestCase::genStorage (int friendlyName)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	if (m_storage == STORAGE_BUFFER)
+	{
+		const int		numElements		= m_invocationGridSize * m_invocationGridSize * m_perInvocationSize;
+		const int		bufferSize		= numElements * ((m_formatInteger) ? (sizeof(deInt32)) : (sizeof(glw::GLfloat)));
+		glw::GLuint		retVal			= 0;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Creating buffer #" << friendlyName << ", size " << bufferSize << " bytes." << tcu::TestLog::EndMessage;
+
+		gl.genBuffers(1, &retVal);
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, retVal);
+
+		if (m_formatInteger)
+		{
+			const std::vector<deUint32> zeroBuffer(numElements, 0);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &zeroBuffer[0], GL_STATIC_DRAW);
+		}
+		else
+		{
+			const std::vector<float> zeroBuffer(numElements, 0.0f);
+			gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &zeroBuffer[0], GL_STATIC_DRAW);
+		}
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
+
+		return retVal;
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		const int	imageWidth	= m_invocationGridSize;
+		const int	imageHeight	= m_invocationGridSize * m_perInvocationSize;
+		glw::GLuint	retVal		= 0;
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Creating image #" << friendlyName << ", size " << imageWidth << "x" << imageHeight
+			<< ", internalformat = " << ((m_formatInteger) ? ("r32i") : ("r32f"))
+			<< ", size = " << (imageWidth*imageHeight*sizeof(deUint32)) << " bytes."
+			<< tcu::TestLog::EndMessage;
+
+		gl.genTextures(1, &retVal);
+		gl.bindTexture(GL_TEXTURE_2D, retVal);
+
+		if (m_formatInteger)
+		{
+			const std::vector<deUint32> zeroBuffer(imageWidth * imageHeight, 0);
+			gl.texImage2D(GL_TEXTURE_2D, 0, GL_R32I, imageWidth, imageHeight, 0, GL_RED_INTEGER, GL_INT, &zeroBuffer[0]);
+		}
+		else
+		{
+			const std::vector<float> zeroBuffer(imageWidth * imageHeight, 0.0f);
+			gl.texImage2D(GL_TEXTURE_2D, 0, GL_R32F, imageWidth, imageHeight, 0, GL_RED, GL_FLOAT, &zeroBuffer[0]);
+		}
+
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen image");
+
+		return retVal;
+	}
+	else
+	{
+		DE_ASSERT(DE_FALSE);
+		return 0;
+	}
+}
+
+glw::GLuint InterCallTestCase::genResultStorage (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	glw::GLuint				retVal	= 0;
+
+	gl.genBuffers(1, &retVal);
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, retVal);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, m_invocationGridSize * m_invocationGridSize * sizeof(deUint32), DE_NULL, GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
+
+	return retVal;
+}
+
+glu::ShaderProgram* InterCallTestCase::genWriteProgram (int seed)
+{
+	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
+	std::ostringstream	buf;
+
+	buf << "#version 310 es\n"
+		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
+		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";
+
+	if (m_storage == STORAGE_BUFFER)
+		buf << "layout(binding=0, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
+			<< "{\n"
+			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
+			<< "} sb_out;\n";
+	else if (m_storage == STORAGE_IMAGE)
+		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=0) " << ((m_useAtomic) ? ("coherent ") : ("writeonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageOut;\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	uvec3 size    = gl_NumWorkGroups * gl_WorkGroupSize;\n"
+		<< "	int groupNdx  = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
+		<< "\n";
+
+	// Write to buffer/image m_perInvocationSize elements
+	if (m_storage == STORAGE_BUFFER)
+	{
+		for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
+		{
+			if (m_useAtomic)
+				buf << "	atomicExchange(";
+			else
+				buf << "	";
+
+			buf << "sb_out.values[(groupNdx + " << seed + writeNdx*m_invocationGridSize*m_invocationGridSize << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize << "]";
+
+			if (m_useAtomic)
+				buf << ", " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
+			else
+				buf << " = " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx);\n";
+		}
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
+		{
+			if (m_useAtomic)
+				buf << "	imageAtomicExchange";
+			else
+				buf << "	imageStore";
+
+			buf << "(u_imageOut, ivec2((int(gl_GlobalInvocationID.x) + " << (seed + writeNdx*100) << ") % " << m_invocationGridSize << ", int(gl_GlobalInvocationID.y) + " << writeNdx*m_invocationGridSize << "), ";
+
+			if (m_useAtomic)
+				buf << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
+			else
+				buf << ((m_formatInteger) ? ("ivec4(int(groupNdx), 0, 0, 0)") : ("vec4(float(groupNdx), 0.0, 0.0, 0.0)")) << ");\n";
+		}
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
+}
+
+glu::ShaderProgram* InterCallTestCase::genReadProgram (int seed)
+{
+	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
+	std::ostringstream	buf;
+
+	buf << "#version 310 es\n"
+		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
+		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";
+
+	if (m_storage == STORAGE_BUFFER)
+		buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
+			<< "{\n"
+			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
+			<< "} sb_in;\n";
+	else if (m_storage == STORAGE_IMAGE)
+		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) " << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn;\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "layout(binding=0, std430) buffer ResultBuffer\n"
+		<< "{\n"
+		<< "	highp int resultOk[];\n"
+		<< "} sb_result;\n"
+		<< "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
+		<< "	int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
+		<< "	" << ((m_formatInteger) ? ("int") : ("float")) << " zero = " << ((m_formatInteger) ? ("0") : ("0.0")) << ";\n"
+		<< "	bool allOk = true;\n"
+		<< "\n";
+
+	// Verify data
+
+	if (m_storage == STORAGE_BUFFER)
+	{
+		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
+			buf << "	allOk = allOk && ("
+				<< ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in.values[(groupNdx + " << seed + readNdx*m_invocationGridSize*m_invocationGridSize << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize << "]"
+				<< ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
+			buf << "	allOk = allOk && ("
+			<< ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad")) << "(u_imageIn, ivec2((gl_GlobalInvocationID.x + " << (seed + readNdx*100) << "u) % " << m_invocationGridSize << "u, gl_GlobalInvocationID.y + " << readNdx*m_invocationGridSize << "u)"
+			<< ((m_useAtomic) ? (", zero") : ("")) << ").x == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "	sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
+		<< "}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
+}
+
+glu::ShaderProgram* InterCallTestCase::genReadMultipleProgram (int seed0, int seed1)
+{
+	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
+	std::ostringstream	buf;
+
+	buf << "#version 310 es\n"
+		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
+		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";
+
+	if (m_storage == STORAGE_BUFFER)
+		buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer0\n"
+			<< "{\n"
+			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
+			<< "} sb_in0;\n"
+			<< "layout(binding=2, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer1\n"
+			<< "{\n"
+			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
+			<< "} sb_in1;\n";
+	else if (m_storage == STORAGE_IMAGE)
+		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) " << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn0;\n"
+			<< "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=2) " << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn1;\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "layout(binding=0, std430) buffer ResultBuffer\n"
+		<< "{\n"
+		<< "	highp int resultOk[];\n"
+		<< "} sb_result;\n"
+		<< "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
+		<< "	int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
+		<< "	" << ((m_formatInteger) ? ("int") : ("float")) << " zero = " << ((m_formatInteger) ? ("0") : ("0.0")) << ";\n"
+		<< "	bool allOk = true;\n"
+		<< "\n";
+
+	// Verify data
+
+	if (m_storage == STORAGE_BUFFER)
+	{
+		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
+			buf << "	allOk = allOk && (" << ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in0.values[(groupNdx + " << seed0 + readNdx*m_invocationGridSize*m_invocationGridSize << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize << "]" << ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n"
+				<< "	allOk = allOk && (" << ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in1.values[(groupNdx + " << seed1 + readNdx*m_invocationGridSize*m_invocationGridSize << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize << "]" << ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
+			buf << "	allOk = allOk && (" << ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad")) << "(u_imageIn0, ivec2((gl_GlobalInvocationID.x + " << (seed0 + readNdx*100) << "u) % " << m_invocationGridSize << "u, gl_GlobalInvocationID.y + " << readNdx*m_invocationGridSize << "u)" << ((m_useAtomic) ? (", zero") : ("")) << ").x == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n"
+				<< "	allOk = allOk && (" << ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad")) << "(u_imageIn1, ivec2((gl_GlobalInvocationID.x + " << (seed1 + readNdx*100) << "u) % " << m_invocationGridSize << "u, gl_GlobalInvocationID.y + " << readNdx*m_invocationGridSize << "u)" << ((m_useAtomic) ? (", zero") : ("")) << ").x == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "	sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
+		<< "}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
+}
+
+glu::ShaderProgram* InterCallTestCase::genWriteInterleavedProgram (int seed, bool evenOdd)
+{
+	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
+	std::ostringstream	buf;
+
+	buf << "#version 310 es\n"
+		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
+		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";
+
+	if (m_storage == STORAGE_BUFFER)
+		buf << "layout(binding=0, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
+			<< "{\n"
+			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
+			<< "} sb_out;\n";
+	else if (m_storage == STORAGE_IMAGE)
+		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=0) " << ((m_useAtomic) ? ("coherent ") : ("writeonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageOut;\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	uvec3 size    = gl_NumWorkGroups * gl_WorkGroupSize;\n"
+		<< "	int groupNdx  = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
+		<< "\n";
+
+	// Write to buffer/image m_perInvocationSize elements
+	if (m_storage == STORAGE_BUFFER)
+	{
+		for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
+		{
+			if (m_useAtomic)
+				buf << "	atomicExchange(";
+			else
+				buf << "	";
+
+			buf << "sb_out.values[((groupNdx + " << seed + writeNdx*m_invocationGridSize*m_invocationGridSize / 2 << ") % " << m_invocationGridSize*m_invocationGridSize / 2 * m_perInvocationSize  << ") * 2 + " << ((evenOdd) ? (0) : (1)) << "]";
+
+			if (m_useAtomic)
+				buf << ", " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
+			else
+				buf << "= " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx);\n";
+		}
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
+		{
+			if (m_useAtomic)
+				buf << "	imageAtomicExchange";
+			else
+				buf << "	imageStore";
+
+			buf << "(u_imageOut, ivec2(((int(gl_GlobalInvocationID.x) + " << (seed + writeNdx*100) << ") % " << m_invocationGridSize / 2 << ") * 2 + " << ((evenOdd) ? (0) : (1)) << ", int(gl_GlobalInvocationID.y) + " << writeNdx*m_invocationGridSize << "), ";
+
+			if (m_useAtomic)
+				buf << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
+			else
+				buf << ((m_formatInteger) ? ("ivec4(int(groupNdx), 0, 0, 0)") : ("vec4(float(groupNdx), 0.0, 0.0, 0.0)")) << ");\n";
+		}
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
+}
+
+glu::ShaderProgram* InterCallTestCase::genReadInterleavedProgram (int seed0, int seed1)
+{
+	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
+	std::ostringstream	buf;
+
+	buf << "#version 310 es\n"
+		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
+		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";
+
+	if (m_storage == STORAGE_BUFFER)
+		buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
+			<< "{\n"
+			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
+			<< "} sb_in;\n";
+	else if (m_storage == STORAGE_IMAGE)
+		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) " << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn;\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "layout(binding=0, std430) buffer ResultBuffer\n"
+		<< "{\n"
+		<< "	highp int resultOk[];\n"
+		<< "} sb_result;\n"
+		<< "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
+		<< "	int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
+		<< "	int interleavedGroupNdx = int((size.x >> 1U) * size.y * gl_GlobalInvocationID.z + (size.x >> 1U) * gl_GlobalInvocationID.y + (gl_GlobalInvocationID.x >> 1U));\n"
+		<< "	" << ((m_formatInteger) ? ("int") : ("float")) << " zero = " << ((m_formatInteger) ? ("0") : ("0.0")) << ";\n"
+		<< "	bool allOk = true;\n"
+		<< "\n";
+
+	// Verify data
+
+	if (m_storage == STORAGE_BUFFER)
+	{
+		buf << "	if (groupNdx % 2 == 0)\n"
+			<< "	{\n";
+		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
+			buf << "		allOk = allOk && ("
+				<< ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in.values[((interleavedGroupNdx + " << seed0 + readNdx*m_invocationGridSize*m_invocationGridSize / 2 << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize / 2 << ") * 2 + 0]"
+				<< ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(interleavedGroupNdx));\n";
+		buf << "	}\n"
+			<< "	else\n"
+			<< "	{\n";
+		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
+			buf << "		allOk = allOk && ("
+				<< ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in.values[((interleavedGroupNdx + " << seed1 + readNdx*m_invocationGridSize*m_invocationGridSize / 2 << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize / 2 << ") * 2 + 1]"
+				<< ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(interleavedGroupNdx));\n";
+		buf << "	}\n";
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		buf << "	if (groupNdx % 2 == 0)\n"
+			<< "	{\n";
+		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
+			buf << "		allOk = allOk && ("
+				<< ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad"))
+				<< "(u_imageIn, ivec2(((int(gl_GlobalInvocationID.x >> 1U) + " << (seed0 + readNdx*100) << ") % " << m_invocationGridSize / 2 << ") * 2 + 0, int(gl_GlobalInvocationID.y) + " << readNdx*m_invocationGridSize << ")"
+				<< ((m_useAtomic) ? (", zero") : ("")) << ").x == " << ((m_formatInteger) ? ("int") : ("float")) << "(interleavedGroupNdx));\n";
+		buf << "	}\n"
+			<< "	else\n"
+			<< "	{\n";
+		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
+			buf << "		allOk = allOk && ("
+				<< ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad"))
+				<< "(u_imageIn, ivec2(((int(gl_GlobalInvocationID.x >> 1U) + " << (seed1 + readNdx*100) << ") % " << m_invocationGridSize / 2 << ") * 2 + 1, int(gl_GlobalInvocationID.y) + " << readNdx*m_invocationGridSize << ")"
+				<< ((m_useAtomic) ? (", zero") : ("")) << ").x == " << ((m_formatInteger) ? ("int") : ("float")) << "(interleavedGroupNdx));\n";
+		buf << "	}\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "	sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
+		<< "}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
+}
+
+glu::ShaderProgram*	InterCallTestCase::genReadZeroProgram (void)
+{
+	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
+	std::ostringstream	buf;
+
+	buf << "#version 310 es\n"
+		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
+		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";
+
+	if (m_storage == STORAGE_BUFFER)
+		buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
+			<< "{\n"
+			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
+			<< "} sb_in;\n";
+	else if (m_storage == STORAGE_IMAGE)
+		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) " << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn;\n";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "layout(binding=0, std430) buffer ResultBuffer\n"
+		<< "{\n"
+		<< "	highp int resultOk[];\n"
+		<< "} sb_result;\n"
+		<< "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
+		<< "	int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
+		<< "	" << ((m_formatInteger) ? ("int") : ("float")) << " anything = " << ((m_formatInteger) ? ("5") : ("5.0")) << ";\n"
+		<< "	bool allOk = true;\n"
+		<< "\n";
+
+	// Verify data
+
+	if (m_storage == STORAGE_BUFFER)
+	{
+		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
+			buf << "	allOk = allOk && ("
+				<< ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in.values[groupNdx * " << m_perInvocationSize << " + " << readNdx << "]"
+				<< ((m_useAtomic) ? (", anything)") : ("")) << " == " << ((m_formatInteger) ? ("0") : ("0.0")) << ");\n";
+	}
+	else if (m_storage == STORAGE_IMAGE)
+	{
+		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
+			buf << "	allOk = allOk && ("
+			<< ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad")) << "(u_imageIn, ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y + " << (readNdx*m_invocationGridSize) << "u)"
+			<< ((m_useAtomic) ? (", anything") : ("")) << ").x == " << ((m_formatInteger) ? ("0") : ("0.0")) << ");\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	buf << "	sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
+		<< "}\n";
+
+	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
+}
+
+class SSBOConcurrentAtomicCase : public TestCase
+{
+public:
+
+							SSBOConcurrentAtomicCase	(Context& context, const char* name, const char* description, int numCalls, int workSize);
+							~SSBOConcurrentAtomicCase	(void);
+
+	void					init						(void);
+	void					deinit						(void);
+	IterateResult			iterate						(void);
+
+private:
+	std::string				genComputeSource			(void) const;
+
+	const int				m_numCalls;
+	const int				m_workSize;
+	glu::ShaderProgram*		m_program;
+	deUint32				m_bufferID;
+	std::vector<deUint32>	m_intermediateResultBuffers;
+};
+
+SSBOConcurrentAtomicCase::SSBOConcurrentAtomicCase (Context& context, const char* name, const char* description, int numCalls, int workSize)
+	: TestCase		(context, name, description)
+	, m_numCalls	(numCalls)
+	, m_workSize	(workSize)
+	, m_program		(DE_NULL)
+	, m_bufferID	(DE_NULL)
+{
+}
+
+SSBOConcurrentAtomicCase::~SSBOConcurrentAtomicCase (void)
+{
+	deinit();
+}
+
+void SSBOConcurrentAtomicCase::init (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	std::vector<deUint32>	zeroData			(m_workSize, 0);
+
+	// gen buffers
+
+	gl.genBuffers(1, &m_bufferID);
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_bufferID);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(deUint32) * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);
+
+	for (int ndx = 0; ndx < m_numCalls; ++ndx)
+	{
+		deUint32 buffer = 0;
+
+		gl.genBuffers(1, &buffer);
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(deUint32) * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);
+
+		m_intermediateResultBuffers.push_back(buffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");
+	}
+
+	// gen program
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genComputeSource()));
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build program");
+}
+
+void SSBOConcurrentAtomicCase::deinit (void)
+{
+	if (m_bufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_bufferID);
+		m_bufferID = 0;
+	}
+
+	for (int ndx = 0; ndx < (int)m_intermediateResultBuffers.size(); ++ndx)
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_intermediateResultBuffers[ndx]);
+	m_intermediateResultBuffers.clear();
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+TestCase::IterateResult SSBOConcurrentAtomicCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const deUint32			sumValue		= (deUint32)(m_numCalls * (m_numCalls + 1) / 2);
+	std::vector<int>		deltas;
+
+	// generate unique deltas
+	generateShuffledRamp(m_numCalls, deltas);
+
+	// invoke program N times, each with a different delta
+	{
+		const int deltaLocation = gl.getUniformLocation(m_program->getProgram(), "u_atomicDelta");
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Running shader " << m_numCalls << " times.\n"
+			<< "Num groups = (" << m_workSize << ", 1, 1)\n"
+			<< "Setting u_atomicDelta to a unique value for each call.\n"
+			<< tcu::TestLog::EndMessage;
+
+		if (deltaLocation == -1)
+			throw tcu::TestError("u_atomicDelta location was -1");
+
+		gl.useProgram(m_program->getProgram());
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_bufferID);
+
+		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
+		{
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "Call " << callNdx << ": u_atomicDelta = " << deltas[callNdx]
+				<< tcu::TestLog::EndMessage;
+
+			gl.uniform1ui(deltaLocation, deltas[callNdx]);
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_intermediateResultBuffers[callNdx]);
+			gl.dispatchCompute(m_workSize, 1, 1);
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
+	}
+
+	// Verify result
+	{
+		std::vector<deUint32> result;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work buffer, it should be filled with value " << sumValue << tcu::TestLog::EndMessage;
+
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_bufferID);
+		readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_workSize, result);
+
+		for (int ndx = 0; ndx < m_workSize; ++ndx)
+		{
+			if (result[ndx] != sumValue)
+			{
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Work buffer error, at index " << ndx << " expected value " << (sumValue) << ", got " << result[ndx] << "\n"
+					<< "Work buffer contains invalid values."
+					<< tcu::TestLog::EndMessage;
+
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
+				return STOP;
+			}
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Work buffer contents are valid." << tcu::TestLog::EndMessage;
+	}
+
+	// verify steps
+	{
+		std::vector<std::vector<deUint32> >	intermediateResults	(m_numCalls);
+		std::vector<deUint32>				valueChain			(m_numCalls);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying intermediate results. " << tcu::TestLog::EndMessage;
+
+		// collect results
+
+		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
+		{
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffers[callNdx]);
+			readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_workSize, intermediateResults[callNdx]);
+		}
+
+		// verify values
+
+		for (int valueNdx = 0; valueNdx < m_workSize; ++valueNdx)
+		{
+			int			invalidOperationNdx;
+			deUint32	errorDelta;
+			deUint32	errorExpected;
+
+			// collect result chain for each element
+			for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
+				valueChain[callNdx] = intermediateResults[callNdx][valueNdx];
+
+			// check there exists a path from 0 to sumValue using each addition once
+			// decompose cumulative results to addition operations (all additions positive => this works)
+
+			std::sort(valueChain.begin(), valueChain.end());
+
+			// validate chain
+			if (!validateSortedAtomicRampAdditionValueChain(valueChain, sumValue, invalidOperationNdx, errorDelta, errorExpected))
+			{
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Intermediate buffer error, at value index " << valueNdx << ", applied operation index " << invalidOperationNdx << ", value was increased by " << errorDelta << ", but expected " << errorExpected << ".\n"
+					<< "Intermediate buffer contains invalid values. Values at index " << valueNdx << "\n"
+					<< tcu::TestLog::EndMessage;
+
+				for (int logCallNdx = 0; logCallNdx < m_numCalls; ++logCallNdx)
+					m_testCtx.getLog() << tcu::TestLog::Message << "Value[" << logCallNdx << "] = " << intermediateResults[logCallNdx][valueNdx] << tcu::TestLog::EndMessage;
+				m_testCtx.getLog() << tcu::TestLog::Message << "Result = " << sumValue << tcu::TestLog::EndMessage;
+
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
+				return STOP;
+			}
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffers are valid." << tcu::TestLog::EndMessage;
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+std::string SSBOConcurrentAtomicCase::genComputeSource (void) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+		<< "layout (binding = 1, std430) writeonly buffer IntermediateResults\n"
+		<< "{\n"
+		<< "	highp uint values[" << m_workSize << "];\n"
+		<< "} sb_ires;\n"
+		<< "\n"
+		<< "layout (binding = 2, std430) volatile buffer WorkBuffer\n"
+		<< "{\n"
+		<< "	highp uint values[" << m_workSize << "];\n"
+		<< "} sb_work;\n"
+		<< "uniform highp uint u_atomicDelta;\n"
+		<< "\n"
+		<< "void main ()\n"
+		<< "{\n"
+		<< "	highp uint invocationIndex = gl_GlobalInvocationID.x;\n"
+		<< "	sb_ires.values[invocationIndex] = atomicAdd(sb_work.values[invocationIndex], u_atomicDelta);\n"
+		<< "}";
+
+	return buf.str();
+}
+
+class ConcurrentAtomicCounterCase : public TestCase
+{
+public:
+
+							ConcurrentAtomicCounterCase		(Context& context, const char* name, const char* description, int numCalls, int workSize);
+							~ConcurrentAtomicCounterCase	(void);
+
+	void					init							(void);
+	void					deinit							(void);
+	IterateResult			iterate							(void);
+
+private:
+	std::string				genComputeSource				(bool evenOdd) const;
+
+	const int				m_numCalls;
+	const int				m_workSize;
+	glu::ShaderProgram*		m_evenProgram;
+	glu::ShaderProgram*		m_oddProgram;
+	deUint32				m_counterBuffer;
+	deUint32				m_intermediateResultBuffer;
+};
+
+ConcurrentAtomicCounterCase::ConcurrentAtomicCounterCase (Context& context, const char* name, const char* description, int numCalls, int workSize)
+	: TestCase					(context, name, description)
+	, m_numCalls				(numCalls)
+	, m_workSize				(workSize)
+	, m_evenProgram				(DE_NULL)
+	, m_oddProgram				(DE_NULL)
+	, m_counterBuffer			(DE_NULL)
+	, m_intermediateResultBuffer(DE_NULL)
+{
+}
+
+ConcurrentAtomicCounterCase::~ConcurrentAtomicCounterCase (void)
+{
+	deinit();
+}
+
+void ConcurrentAtomicCounterCase::init (void)
+{
+	const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+	const std::vector<deUint32>	zeroData	(m_numCalls * m_workSize, 0);
+
+	// gen buffer
+
+	gl.genBuffers(1, &m_counterBuffer);
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_counterBuffer);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(deUint32), &zeroData[0], GL_DYNAMIC_COPY);
+
+	gl.genBuffers(1, &m_intermediateResultBuffer);
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffer);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(deUint32) * m_numCalls * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");
+
+	// gen programs
+
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "EvenProgram", "Even program");
+
+		m_evenProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genComputeSource(true)));
+		m_testCtx.getLog() << *m_evenProgram;
+		if (!m_evenProgram->isOk())
+			throw tcu::TestError("could not build program");
+	}
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "OddProgram", "Odd program");
+
+		m_oddProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genComputeSource(false)));
+		m_testCtx.getLog() << *m_oddProgram;
+		if (!m_oddProgram->isOk())
+			throw tcu::TestError("could not build program");
+	}
+}
+
+void ConcurrentAtomicCounterCase::deinit (void)
+{
+	if (m_counterBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_counterBuffer);
+		m_counterBuffer = 0;
+	}
+	if (m_intermediateResultBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_intermediateResultBuffer);
+		m_intermediateResultBuffer = 0;
+	}
+
+	delete m_evenProgram;
+	m_evenProgram = DE_NULL;
+
+	delete m_oddProgram;
+	m_oddProgram = DE_NULL;
+}
+
+TestCase::IterateResult ConcurrentAtomicCounterCase::iterate (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// invoke program N times, each with a different delta
+	{
+		const int evenCallNdxLocation	= gl.getUniformLocation(m_evenProgram->getProgram(), "u_callNdx");
+		const int oddCallNdxLocation	= gl.getUniformLocation(m_oddProgram->getProgram(), "u_callNdx");
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Running shader pair (even & odd) " << m_numCalls << " times.\n"
+			<< "Num groups = (" << m_workSize << ", 1, 1)\n"
+			<< tcu::TestLog::EndMessage;
+
+		if (evenCallNdxLocation == -1)
+			throw tcu::TestError("u_callNdx location was -1");
+		if (oddCallNdxLocation == -1)
+			throw tcu::TestError("u_callNdx location was -1");
+
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_intermediateResultBuffer);
+		gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 2, m_counterBuffer);
+
+		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
+		{
+			gl.useProgram(m_evenProgram->getProgram());
+			gl.uniform1ui(evenCallNdxLocation, (deUint32)callNdx);
+			gl.dispatchCompute(m_workSize, 1, 1);
+
+			gl.useProgram(m_oddProgram->getProgram());
+			gl.uniform1ui(oddCallNdxLocation, (deUint32)callNdx);
+			gl.dispatchCompute(m_workSize, 1, 1);
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
+	}
+
+	// Verify result
+	{
+		deUint32 result;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work buffer, it should be " << m_numCalls*m_workSize << tcu::TestLog::EndMessage;
+
+		gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, m_counterBuffer);
+		result = readBufferUint32(gl, GL_ATOMIC_COUNTER_BUFFER);
+
+		if ((int)result != m_numCalls*m_workSize)
+		{
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "Counter buffer error, expected value " << (m_numCalls*m_workSize) << ", got " << result << "\n"
+				<< tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
+			return STOP;
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Counter buffer is valid." << tcu::TestLog::EndMessage;
+	}
+
+	// verify steps
+	{
+		std::vector<deUint32> intermediateResults;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying intermediate results. " << tcu::TestLog::EndMessage;
+
+		// collect results
+
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffer);
+		readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_numCalls * m_workSize, intermediateResults);
+
+		// verify values
+
+		std::sort(intermediateResults.begin(), intermediateResults.end());
+
+		for (int valueNdx = 0; valueNdx < m_workSize * m_numCalls; ++valueNdx)
+		{
+			if ((int)intermediateResults[valueNdx] != valueNdx)
+			{
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Intermediate buffer error, at value index " << valueNdx << ", expected " << valueNdx << ", got " << intermediateResults[valueNdx] << ".\n"
+					<< "Intermediate buffer contains invalid values. Intermediate results:\n"
+					<< tcu::TestLog::EndMessage;
+
+				for (int logCallNdx = 0; logCallNdx < m_workSize * m_numCalls; ++logCallNdx)
+					m_testCtx.getLog() << tcu::TestLog::Message << "Value[" << logCallNdx << "] = " << intermediateResults[logCallNdx] << tcu::TestLog::EndMessage;
+
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
+				return STOP;
+			}
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffers are valid." << tcu::TestLog::EndMessage;
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+std::string ConcurrentAtomicCounterCase::genComputeSource (bool evenOdd) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+		<< "layout (binding = 1, std430) writeonly buffer IntermediateResults\n"
+		<< "{\n"
+		<< "	highp uint values[" << m_workSize * m_numCalls << "];\n"
+		<< "} sb_ires;\n"
+		<< "\n"
+		<< "layout (binding = 2, offset = 0) uniform atomic_uint u_counter;\n"
+		<< "uniform highp uint u_callNdx;\n"
+		<< "\n"
+		<< "void main ()\n"
+		<< "{\n"
+		<< "	highp uint dataNdx = u_callNdx * " << m_workSize << "u + gl_GlobalInvocationID.x;\n"
+		<< "	if ((dataNdx % 2u) == " << ((evenOdd) ? (0) : (1)) << "u)\n"
+		<< "		sb_ires.values[dataNdx] = atomicCounterIncrement(u_counter);\n"
+		<< "}";
+
+	return buf.str();
+}
+
+class ConcurrentImageAtomicCase : public TestCase
+{
+public:
+
+							ConcurrentImageAtomicCase	(Context& context, const char* name, const char* description, int numCalls, int workSize);
+							~ConcurrentImageAtomicCase	(void);
+
+	void					init						(void);
+	void					deinit						(void);
+	IterateResult			iterate						(void);
+
+private:
+	void					readWorkImage				(std::vector<deUint32>& result);
+
+	std::string				genComputeSource			(void) const;
+	std::string				genImageReadSource			(void) const;
+	std::string				genImageClearSource			(void) const;
+
+	const int				m_numCalls;
+	const int				m_workSize;
+	glu::ShaderProgram*		m_program;
+	glu::ShaderProgram*		m_imageReadProgram;
+	glu::ShaderProgram*		m_imageClearProgram;
+	deUint32				m_imageID;
+	std::vector<deUint32>	m_intermediateResultBuffers;
+};
+
+ConcurrentImageAtomicCase::ConcurrentImageAtomicCase (Context& context, const char* name, const char* description, int numCalls, int workSize)
+	: TestCase				(context, name, description)
+	, m_numCalls			(numCalls)
+	, m_workSize			(workSize)
+	, m_program				(DE_NULL)
+	, m_imageReadProgram	(DE_NULL)
+	, m_imageClearProgram	(DE_NULL)
+	, m_imageID				(DE_NULL)
+{
+}
+
+ConcurrentImageAtomicCase::~ConcurrentImageAtomicCase (void)
+{
+	deinit();
+}
+
+void ConcurrentImageAtomicCase::init (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	std::vector<deUint32>	zeroData			(m_workSize * m_workSize, 0);
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
+		throw tcu::NotSupportedError("Test requires GL_OES_shader_image_atomic");
+
+	// gen image
+
+	gl.genTextures(1, &m_imageID);
+	gl.bindTexture(GL_TEXTURE_2D, m_imageID);
+	gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_workSize, m_workSize);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");
+
+	// gen buffers
+
+	for (int ndx = 0; ndx < m_numCalls; ++ndx)
+	{
+		deUint32 buffer = 0;
+
+		gl.genBuffers(1, &buffer);
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(deUint32) * m_workSize * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);
+
+		m_intermediateResultBuffers.push_back(buffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");
+	}
+
+	// gen programs
+
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genComputeSource()));
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build program");
+
+	m_imageReadProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genImageReadSource()));
+	if (!m_imageReadProgram->isOk())
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "ImageReadProgram", "Image read program");
+
+		m_testCtx.getLog() << *m_imageReadProgram;
+		throw tcu::TestError("could not build program");
+	}
+
+	m_imageClearProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genImageClearSource()));
+	if (!m_imageClearProgram->isOk())
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "ImageClearProgram", "Image read program");
+
+		m_testCtx.getLog() << *m_imageClearProgram;
+		throw tcu::TestError("could not build program");
+	}
+}
+
+void ConcurrentImageAtomicCase::deinit (void)
+{
+	if (m_imageID)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_imageID);
+		m_imageID = 0;
+	}
+
+	for (int ndx = 0; ndx < (int)m_intermediateResultBuffers.size(); ++ndx)
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_intermediateResultBuffers[ndx]);
+	m_intermediateResultBuffers.clear();
+
+	delete m_program;
+	m_program = DE_NULL;
+
+	delete m_imageReadProgram;
+	m_imageReadProgram = DE_NULL;
+
+	delete m_imageClearProgram;
+	m_imageClearProgram = DE_NULL;
+}
+
+TestCase::IterateResult ConcurrentImageAtomicCase::iterate (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const deUint32			sumValue		= (deUint32)(m_numCalls * (m_numCalls + 1) / 2);
+	std::vector<int>		deltas;
+
+	// generate unique deltas
+	generateShuffledRamp(m_numCalls, deltas);
+
+	// clear image
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Clearing image contents" << tcu::TestLog::EndMessage;
+
+		gl.useProgram(m_imageClearProgram->getProgram());
+		gl.bindImageTexture(2, m_imageID, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
+		gl.dispatchCompute(m_workSize, m_workSize, 1);
+		gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+	}
+
+	// invoke program N times, each with a different delta
+	{
+		const int deltaLocation = gl.getUniformLocation(m_program->getProgram(), "u_atomicDelta");
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Running shader " << m_numCalls << " times.\n"
+			<< "Num groups = (" << m_workSize << ", " << m_workSize << ", 1)\n"
+			<< "Setting u_atomicDelta to a unique value for each call.\n"
+			<< tcu::TestLog::EndMessage;
+
+		if (deltaLocation == -1)
+			throw tcu::TestError("u_atomicDelta location was -1");
+
+		gl.useProgram(m_program->getProgram());
+		gl.bindImageTexture(2, m_imageID, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);
+
+		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
+		{
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "Call " << callNdx << ": u_atomicDelta = " << deltas[callNdx]
+				<< tcu::TestLog::EndMessage;
+
+			gl.uniform1ui(deltaLocation, deltas[callNdx]);
+			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_intermediateResultBuffers[callNdx]);
+			gl.dispatchCompute(m_workSize, m_workSize, 1);
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
+	}
+
+	// Verify result
+	{
+		std::vector<deUint32> result;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work image, it should be filled with value " << sumValue << tcu::TestLog::EndMessage;
+
+		readWorkImage(result);
+
+		for (int ndx = 0; ndx < m_workSize * m_workSize; ++ndx)
+		{
+			if (result[ndx] != sumValue)
+			{
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Work image error, at index (" << ndx % m_workSize << ", " << ndx / m_workSize << ") expected value " << (sumValue) << ", got " << result[ndx] << "\n"
+					<< "Work image contains invalid values."
+					<< tcu::TestLog::EndMessage;
+
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image contents invalid");
+				return STOP;
+			}
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Work image contents are valid." << tcu::TestLog::EndMessage;
+	}
+
+	// verify steps
+	{
+		std::vector<std::vector<deUint32> >	intermediateResults	(m_numCalls);
+		std::vector<deUint32>				valueChain			(m_numCalls);
+		std::vector<deUint32>				chainDelta			(m_numCalls);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying intermediate results. " << tcu::TestLog::EndMessage;
+
+		// collect results
+
+		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
+		{
+			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffers[callNdx]);
+			readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_workSize * m_workSize, intermediateResults[callNdx]);
+		}
+
+		// verify values
+
+		for (int valueNdx = 0; valueNdx < m_workSize; ++valueNdx)
+		{
+			int			invalidOperationNdx;
+			deUint32	errorDelta;
+			deUint32	errorExpected;
+
+			// collect result chain for each element
+			for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
+				valueChain[callNdx] = intermediateResults[callNdx][valueNdx];
+
+			// check there exists a path from 0 to sumValue using each addition once
+			// decompose cumulative results to addition operations (all additions positive => this works)
+
+			std::sort(valueChain.begin(), valueChain.end());
+
+			for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
+				chainDelta[callNdx] = ((callNdx + 1 == m_numCalls) ? (sumValue) : (valueChain[callNdx+1])) - valueChain[callNdx];
+
+			// chainDelta contains now the actual additions applied to the value
+			std::sort(chainDelta.begin(), chainDelta.end());
+
+			// validate chain
+			if (!validateSortedAtomicRampAdditionValueChain(valueChain, sumValue, invalidOperationNdx, errorDelta, errorExpected))
+			{
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Intermediate buffer error, at index (" << valueNdx % m_workSize << ", " << valueNdx / m_workSize << "), applied operation index "
+					<< invalidOperationNdx << ", value was increased by " << errorDelta << ", but expected " << errorExpected << ".\n"
+					<< "Intermediate buffer contains invalid values. Values at index (" << valueNdx % m_workSize << ", " << valueNdx / m_workSize << ")\n"
+					<< tcu::TestLog::EndMessage;
+
+				for (int logCallNdx = 0; logCallNdx < m_numCalls; ++logCallNdx)
+					m_testCtx.getLog() << tcu::TestLog::Message << "Value[" << logCallNdx << "] = " << intermediateResults[logCallNdx][valueNdx] << tcu::TestLog::EndMessage;
+				m_testCtx.getLog() << tcu::TestLog::Message << "Result = " << sumValue << tcu::TestLog::EndMessage;
+
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
+				return STOP;
+			}
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffers are valid." << tcu::TestLog::EndMessage;
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+void ConcurrentImageAtomicCase::readWorkImage (std::vector<deUint32>& result)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	glu::Buffer				resultBuffer	(m_context.getRenderContext());
+
+	// Read image to an ssbo
+
+	{
+		const std::vector<deUint32> zeroData(m_workSize*m_workSize, 0);
+
+		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *resultBuffer);
+		gl.bufferData(GL_SHADER_STORAGE_BUFFER, (int)(sizeof(deUint32) * m_workSize * m_workSize), &zeroData[0], GL_DYNAMIC_COPY);
+
+		gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+		gl.useProgram(m_imageReadProgram->getProgram());
+
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *resultBuffer);
+		gl.bindImageTexture(2, m_imageID, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
+		gl.dispatchCompute(m_workSize, m_workSize, 1);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "read");
+	}
+
+	// Read ssbo
+	{
+		const void* ptr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, (int)(sizeof(deUint32) * m_workSize * m_workSize), GL_MAP_READ_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "map");
+
+		if (!ptr)
+			throw tcu::TestError("mapBufferRange returned NULL");
+
+		result.resize(m_workSize * m_workSize);
+		memcpy(&result[0], ptr, sizeof(deUint32) * m_workSize * m_workSize);
+
+		if (gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER) == GL_FALSE)
+			throw tcu::TestError("unmapBuffer returned false");
+	}
+}
+
+std::string ConcurrentImageAtomicCase::genComputeSource (void) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "#extension GL_OES_shader_image_atomic : require\n"
+		<< "\n"
+		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+		<< "layout (binding = 1, std430) writeonly buffer IntermediateResults\n"
+		<< "{\n"
+		<< "	highp uint values[" << m_workSize * m_workSize << "];\n"
+		<< "} sb_ires;\n"
+		<< "\n"
+		<< "layout (binding = 2, r32ui) volatile uniform uimage2D u_workImage;\n"
+		<< "uniform highp uint u_atomicDelta;\n"
+		<< "\n"
+		<< "void main ()\n"
+		<< "{\n"
+		<< "	highp uint invocationIndex = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * uint(" << m_workSize <<");\n"
+		<< "	sb_ires.values[invocationIndex] = imageAtomicAdd(u_workImage, ivec2(gl_GlobalInvocationID.xy), u_atomicDelta);\n"
+		<< "}";
+
+	return buf.str();
+}
+
+std::string ConcurrentImageAtomicCase::genImageReadSource (void) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "\n"
+		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+		<< "layout (binding = 1, std430) writeonly buffer ImageValues\n"
+		<< "{\n"
+		<< "	highp uint values[" << m_workSize * m_workSize << "];\n"
+		<< "} sb_res;\n"
+		<< "\n"
+		<< "layout (binding = 2, r32ui) readonly uniform uimage2D u_workImage;\n"
+		<< "\n"
+		<< "void main ()\n"
+		<< "{\n"
+		<< "	highp uint invocationIndex = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * uint(" << m_workSize <<");\n"
+		<< "	sb_res.values[invocationIndex] = imageLoad(u_workImage, ivec2(gl_GlobalInvocationID.xy)).x;\n"
+		<< "}";
+
+	return buf.str();
+}
+
+std::string ConcurrentImageAtomicCase::genImageClearSource (void) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "\n"
+		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+		<< "layout (binding = 2, r32ui) writeonly uniform uimage2D u_workImage;\n"
+		<< "\n"
+		<< "void main ()\n"
+		<< "{\n"
+		<< "	imageStore(u_workImage, ivec2(gl_GlobalInvocationID.xy), uvec4(0, 0, 0, 0));\n"
+		<< "}";
+
+	return buf.str();
+}
+
+class ConcurrentSSBOAtomicCounterMixedCase : public TestCase
+{
+public:
+							ConcurrentSSBOAtomicCounterMixedCase	(Context& context, const char* name, const char* description, int numCalls, int workSize);
+							~ConcurrentSSBOAtomicCounterMixedCase	(void);
+
+	void					init									(void);
+	void					deinit									(void);
+	IterateResult			iterate									(void);
+
+private:
+	std::string				genSSBOComputeSource					(void) const;
+	std::string				genAtomicCounterComputeSource			(void) const;
+
+	const int				m_numCalls;
+	const int				m_workSize;
+	deUint32				m_bufferID;
+	glu::ShaderProgram*		m_ssboAtomicProgram;
+	glu::ShaderProgram*		m_atomicCounterProgram;
+};
+
+ConcurrentSSBOAtomicCounterMixedCase::ConcurrentSSBOAtomicCounterMixedCase (Context& context, const char* name, const char* description, int numCalls, int workSize)
+	: TestCase					(context, name, description)
+	, m_numCalls				(numCalls)
+	, m_workSize				(workSize)
+	, m_bufferID				(DE_NULL)
+	, m_ssboAtomicProgram		(DE_NULL)
+	, m_atomicCounterProgram	(DE_NULL)
+{
+	// SSBO atomic XORs cancel out
+	DE_ASSERT((workSize * numCalls) % (16 * 2) == 0);
+}
+
+ConcurrentSSBOAtomicCounterMixedCase::~ConcurrentSSBOAtomicCounterMixedCase (void)
+{
+	deinit();
+}
+
+void ConcurrentSSBOAtomicCounterMixedCase::init (void)
+{
+	const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
+	const deUint32				zeroBuf[2]	= { 0, 0 };
+
+	// gen buffer
+
+	gl.genBuffers(1, &m_bufferID);
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_bufferID);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, (int)(sizeof(deUint32) * 2), zeroBuf, GL_DYNAMIC_COPY);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");
+
+	// gen programs
+
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "SSBOProgram", "SSBO atomic program");
+
+		m_ssboAtomicProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genSSBOComputeSource()));
+		m_testCtx.getLog() << *m_ssboAtomicProgram;
+		if (!m_ssboAtomicProgram->isOk())
+			throw tcu::TestError("could not build program");
+	}
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "AtomicCounterProgram", "Atomic counter program");
+
+		m_atomicCounterProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genAtomicCounterComputeSource()));
+		m_testCtx.getLog() << *m_atomicCounterProgram;
+		if (!m_atomicCounterProgram->isOk())
+			throw tcu::TestError("could not build program");
+	}
+}
+
+void ConcurrentSSBOAtomicCounterMixedCase::deinit (void)
+{
+	if (m_bufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_bufferID);
+		m_bufferID = 0;
+	}
+
+	delete m_ssboAtomicProgram;
+	m_ssboAtomicProgram = DE_NULL;
+
+	delete m_atomicCounterProgram;
+	m_atomicCounterProgram = DE_NULL;
+}
+
+TestCase::IterateResult ConcurrentSSBOAtomicCounterMixedCase::iterate (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Testing atomic counters and SSBO atomic operations with both backed by the same buffer." << tcu::TestLog::EndMessage;
+
+	// invoke programs N times
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Running SSBO atomic program and atomic counter program " << m_numCalls << " times. (interleaved)\n"
+			<< "Num groups = (" << m_workSize << ", 1, 1)\n"
+			<< tcu::TestLog::EndMessage;
+
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_bufferID);
+		gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 2, m_bufferID);
+
+		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
+		{
+			gl.useProgram(m_atomicCounterProgram->getProgram());
+			gl.dispatchCompute(m_workSize, 1, 1);
+
+			gl.useProgram(m_ssboAtomicProgram->getProgram());
+			gl.dispatchCompute(m_workSize, 1, 1);
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
+	}
+
+	// Verify result
+	{
+		deUint32 result;
+
+		// XORs cancel out, only addition is left
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work buffer, it should be " << m_numCalls*m_workSize << tcu::TestLog::EndMessage;
+
+		gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, m_bufferID);
+		result = readBufferUint32(gl, GL_ATOMIC_COUNTER_BUFFER);
+
+		if ((int)result != m_numCalls*m_workSize)
+		{
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "Buffer value error, expected value " << (m_numCalls*m_workSize) << ", got " << result << "\n"
+				<< tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
+			return STOP;
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Buffer is valid." << tcu::TestLog::EndMessage;
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+std::string ConcurrentSSBOAtomicCounterMixedCase::genSSBOComputeSource (void) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+		<< "layout (binding = 1, std430) volatile buffer WorkBuffer\n"
+		<< "{\n"
+		<< "	highp uint targetValue;\n"
+		<< "	highp uint dummy;\n"
+		<< "} sb_work;\n"
+		<< "\n"
+		<< "void main ()\n"
+		<< "{\n"
+		<< "	// flip high bits\n"
+		<< "	highp uint mask = uint(1) << (16u + (gl_GlobalInvocationID.x % 16u));\n"
+		<< "	sb_work.dummy = atomicXor(sb_work.targetValue, mask);\n"
+		<< "}";
+
+	return buf.str();
+}
+
+std::string ConcurrentSSBOAtomicCounterMixedCase::genAtomicCounterComputeSource (void) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
+		<< "\n"
+		<< "layout (binding = 2, offset = 0) uniform atomic_uint u_counter;\n"
+		<< "\n"
+		<< "void main ()\n"
+		<< "{\n"
+		<< "	atomicCounterIncrement(u_counter);\n"
+		<< "}";
+
+	return buf.str();
+}
+
+} // anonymous
+
+SynchronizationTests::SynchronizationTests (Context& context)
+	: TestCaseGroup(context, "synchronization", "Synchronization tests")
+{
+}
+
+SynchronizationTests::~SynchronizationTests (void)
+{
+}
+
+void SynchronizationTests::init (void)
+{
+	tcu::TestCaseGroup* const inInvocationGroup		= new tcu::TestCaseGroup(m_testCtx, "in_invocation",	"Test intra-invocation synchronization");
+	tcu::TestCaseGroup* const interInvocationGroup	= new tcu::TestCaseGroup(m_testCtx, "inter_invocation", "Test inter-invocation synchronization");
+	tcu::TestCaseGroup* const interCallGroup		= new tcu::TestCaseGroup(m_testCtx, "inter_call",       "Test inter-call synchronization");
+
+	addChild(inInvocationGroup);
+	addChild(interInvocationGroup);
+	addChild(interCallGroup);
+
+	// .in_invocation & .inter_invocation
+	{
+		static const struct CaseConfig
+		{
+			const char*									namePrefix;
+			const InterInvocationTestCase::StorageType	storage;
+			const int									flags;
+		} configs[] =
+		{
+			{ "image",			InterInvocationTestCase::STORAGE_IMAGE,		0										},
+			{ "image_atomic",	InterInvocationTestCase::STORAGE_IMAGE,		InterInvocationTestCase::FLAG_ATOMIC	},
+			{ "ssbo",			InterInvocationTestCase::STORAGE_BUFFER,	0										},
+			{ "ssbo_atomic",	InterInvocationTestCase::STORAGE_BUFFER,	InterInvocationTestCase::FLAG_ATOMIC	},
+		};
+
+		for (int groupNdx = 0; groupNdx < 2; ++groupNdx)
+		{
+			tcu::TestCaseGroup* const	targetGroup	= (groupNdx == 0) ? (inInvocationGroup) : (interInvocationGroup);
+			const int					extraFlags	= (groupNdx == 0) ? (0) : (InterInvocationTestCase::FLAG_IN_GROUP);
+
+			for (int configNdx = 0; configNdx < DE_LENGTH_OF_ARRAY(configs); ++configNdx)
+			{
+				const char* const target = (configs[configNdx].storage == InterInvocationTestCase::STORAGE_BUFFER) ? ("buffer") : ("image");
+
+				targetGroup->addChild(new InvocationWriteReadCase(m_context,
+																  (std::string(configs[configNdx].namePrefix) + "_write_read").c_str(),
+																  (std::string("Write to ") + target + " and read it").c_str(),
+																  configs[configNdx].storage,
+																  configs[configNdx].flags | extraFlags));
+
+				targetGroup->addChild(new InvocationReadWriteCase(m_context,
+																  (std::string(configs[configNdx].namePrefix) + "_read_write").c_str(),
+																  (std::string("Read form ") + target + " and then write to it").c_str(),
+																  configs[configNdx].storage,
+																  configs[configNdx].flags | extraFlags));
+
+				targetGroup->addChild(new InvocationOverWriteCase(m_context,
+																  (std::string(configs[configNdx].namePrefix) + "_overwrite").c_str(),
+																  (std::string("Write to ") + target + " twice and read it").c_str(),
+																  configs[configNdx].storage,
+																  configs[configNdx].flags | extraFlags));
+
+				targetGroup->addChild(new InvocationAliasWriteCase(m_context,
+																   (std::string(configs[configNdx].namePrefix) + "_alias_write").c_str(),
+																   (std::string("Write to aliasing ") + target + " and read it").c_str(),
+																   InvocationAliasWriteCase::TYPE_WRITE,
+																   configs[configNdx].storage,
+																   configs[configNdx].flags | extraFlags));
+
+				targetGroup->addChild(new InvocationAliasWriteCase(m_context,
+																   (std::string(configs[configNdx].namePrefix) + "_alias_overwrite").c_str(),
+																   (std::string("Write to aliasing ") + target + "s and read it").c_str(),
+																   InvocationAliasWriteCase::TYPE_OVERWRITE,
+																   configs[configNdx].storage,
+																   configs[configNdx].flags | extraFlags));
+			}
+		}
+	}
+
+	// .inter_call
+	{
+		tcu::TestCaseGroup* const withBarrierGroup		= new tcu::TestCaseGroup(m_testCtx, "with_memory_barrier", "Synchronize with memory barrier");
+		tcu::TestCaseGroup* const withoutBarrierGroup	= new tcu::TestCaseGroup(m_testCtx, "without_memory_barrier", "Synchronize without memory barrier");
+
+		interCallGroup->addChild(withBarrierGroup);
+		interCallGroup->addChild(withoutBarrierGroup);
+
+		// .with_memory_barrier
+		{
+			static const struct CaseConfig
+			{
+				const char*								namePrefix;
+				const InterCallTestCase::StorageType	storage;
+				const int								flags;
+			} configs[] =
+			{
+				{ "image",			InterCallTestCase::STORAGE_IMAGE,	0																		},
+				{ "image_atomic",	InterCallTestCase::STORAGE_IMAGE,	InterCallTestCase::FLAG_USE_ATOMIC | InterCallTestCase::FLAG_USE_INT	},
+				{ "ssbo",			InterCallTestCase::STORAGE_BUFFER,	0																		},
+				{ "ssbo_atomic",	InterCallTestCase::STORAGE_BUFFER,	InterCallTestCase::FLAG_USE_ATOMIC | InterCallTestCase::FLAG_USE_INT	},
+			};
+
+			const int seed0 = 123;
+			const int seed1 = 457;
+
+			for (int configNdx = 0; configNdx < DE_LENGTH_OF_ARRAY(configs); ++configNdx)
+			{
+				const char* const target = (configs[configNdx].storage == InterCallTestCase::STORAGE_BUFFER) ? ("buffer") : ("image");
+
+				withBarrierGroup->addChild(new InterCallTestCase(m_context,
+																 (std::string(configs[configNdx].namePrefix) + "_write_read").c_str(),
+																 (std::string("Write to ") + target + " and read it").c_str(),
+																 configs[configNdx].storage,
+																 configs[configNdx].flags,
+																 InterCallOperations()
+																	<< op::WriteData::Generate(1, seed0)
+																	<< op::Barrier()
+																	<< op::ReadData::Generate(1, seed0)));
+
+				withBarrierGroup->addChild(new InterCallTestCase(m_context,
+																 (std::string(configs[configNdx].namePrefix) + "_read_write").c_str(),
+																 (std::string("Read from ") + target + " and then write to it").c_str(),
+																 configs[configNdx].storage,
+																 configs[configNdx].flags,
+																 InterCallOperations()
+																	<< op::ReadZeroData::Generate(1)
+																	<< op::Barrier()
+																	<< op::WriteData::Generate(1, seed0)));
+
+				withBarrierGroup->addChild(new InterCallTestCase(m_context,
+																 (std::string(configs[configNdx].namePrefix) + "_overwrite").c_str(),
+																 (std::string("Write to ") + target + " twice and read it").c_str(),
+																 configs[configNdx].storage,
+																 configs[configNdx].flags,
+																 InterCallOperations()
+																	<< op::WriteData::Generate(1, seed0)
+																	<< op::Barrier()
+																	<< op::WriteData::Generate(1, seed1)
+																	<< op::Barrier()
+																	<< op::ReadData::Generate(1, seed1)));
+
+				withBarrierGroup->addChild(new InterCallTestCase(m_context,
+																 (std::string(configs[configNdx].namePrefix) + "_multiple_write_read").c_str(),
+																 (std::string("Write to multiple ") + target + "s and read them").c_str(),
+																 configs[configNdx].storage,
+																 configs[configNdx].flags,
+																 InterCallOperations()
+																	<< op::WriteData::Generate(1, seed0)
+																	<< op::WriteData::Generate(2, seed1)
+																	<< op::Barrier()
+																	<< op::ReadMultipleData::Generate(1, seed0, 2, seed1)));
+
+				withBarrierGroup->addChild(new InterCallTestCase(m_context,
+																 (std::string(configs[configNdx].namePrefix) + "_multiple_interleaved_write_read").c_str(),
+																 (std::string("Write to same ") + target + " in multiple calls and read it").c_str(),
+																 configs[configNdx].storage,
+																 configs[configNdx].flags,
+																 InterCallOperations()
+																	<< op::WriteDataInterleaved::Generate(1, seed0, true)
+																	<< op::WriteDataInterleaved::Generate(1, seed1, false)
+																	<< op::Barrier()
+																	<< op::ReadDataInterleaved::Generate(1, seed0, seed1)));
+
+				withBarrierGroup->addChild(new InterCallTestCase(m_context,
+																 (std::string(configs[configNdx].namePrefix) + "_multiple_unrelated_write_read_ordered").c_str(),
+																 (std::string("Two unrelated ") + target + " write-reads").c_str(),
+																 configs[configNdx].storage,
+																 configs[configNdx].flags,
+																 InterCallOperations()
+																	<< op::WriteData::Generate(1, seed0)
+																	<< op::WriteData::Generate(2, seed1)
+																	<< op::Barrier()
+																	<< op::ReadData::Generate(1, seed0)
+																	<< op::ReadData::Generate(2, seed1)));
+
+				withBarrierGroup->addChild(new InterCallTestCase(m_context,
+																 (std::string(configs[configNdx].namePrefix) + "_multiple_unrelated_write_read_non_ordered").c_str(),
+																 (std::string("Two unrelated ") + target + " write-reads").c_str(),
+																 configs[configNdx].storage,
+																 configs[configNdx].flags,
+																 InterCallOperations()
+																	<< op::WriteData::Generate(1, seed0)
+																	<< op::WriteData::Generate(2, seed1)
+																	<< op::Barrier()
+																	<< op::ReadData::Generate(2, seed1)
+																	<< op::ReadData::Generate(1, seed0)));
+			}
+
+			// .without_memory_barrier
+			{
+				struct InvocationConfig
+				{
+					const char*	name;
+					int			count;
+				};
+
+				static const InvocationConfig ssboInvocations[] =
+				{
+					{ "1k",		1024	},
+					{ "4k",		4096	},
+					{ "32k",	32768	},
+				};
+				static const InvocationConfig imageInvocations[] =
+				{
+					{ "8x8",		8	},
+					{ "32x32",		32	},
+					{ "128x128",	128	},
+				};
+				static const InvocationConfig counterInvocations[] =
+				{
+					{ "32",		32		},
+					{ "128",	128		},
+					{ "1k",		1024	},
+				};
+				static const int callCounts[] = { 2, 5, 100 };
+
+				for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(ssboInvocations); ++invocationNdx)
+					for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
+						withoutBarrierGroup->addChild(new SSBOConcurrentAtomicCase(m_context, (std::string("ssbo_atomic_dispatch_") + de::toString(callCounts[callCountNdx]) + "_calls_" + ssboInvocations[invocationNdx].name + "_invocations").c_str(),	"", callCounts[callCountNdx], ssboInvocations[invocationNdx].count));
+
+				for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(imageInvocations); ++invocationNdx)
+					for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
+						withoutBarrierGroup->addChild(new ConcurrentImageAtomicCase(m_context, (std::string("image_atomic_dispatch_") + de::toString(callCounts[callCountNdx]) + "_calls_" + imageInvocations[invocationNdx].name + "_invocations").c_str(),	"", callCounts[callCountNdx], imageInvocations[invocationNdx].count));
+
+				for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(counterInvocations); ++invocationNdx)
+					for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
+						withoutBarrierGroup->addChild(new ConcurrentAtomicCounterCase(m_context, (std::string("atomic_counter_dispatch_") + de::toString(callCounts[callCountNdx]) + "_calls_" + counterInvocations[invocationNdx].name + "_invocations").c_str(),	"", callCounts[callCountNdx], counterInvocations[invocationNdx].count));
+
+				for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(counterInvocations); ++invocationNdx)
+					for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
+						withoutBarrierGroup->addChild(new ConcurrentSSBOAtomicCounterMixedCase(m_context, (std::string("ssbo_atomic_counter_mixed_dispatch_") + de::toString(callCounts[callCountNdx]) + "_calls_" + counterInvocations[invocationNdx].name + "_invocations").c_str(),	"", callCounts[callCountNdx], counterInvocations[invocationNdx].count));
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fSynchronizationTests.hpp b/modules/gles31/functional/es31fSynchronizationTests.hpp
new file mode 100644
index 0000000..b6b0496
--- /dev/null
+++ b/modules/gles31/functional/es31fSynchronizationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FSYNCHRONIZATIONTESTS_HPP
+#define _ES31FSYNCHRONIZATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Synchronization Tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class SynchronizationTests : public TestCaseGroup
+{
+public:
+								SynchronizationTests	(Context& context);
+								~SynchronizationTests	(void);
+
+	void						init					(void);
+
+private:
+								SynchronizationTests	(const SynchronizationTests& other);
+	SynchronizationTests&		operator=				(const SynchronizationTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FSYNCHRONIZATIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fTessellationGeometryInteractionTests.cpp b/modules/gles31/functional/es31fTessellationGeometryInteractionTests.cpp
new file mode 100644
index 0000000..7e5355a
--- /dev/null
+++ b/modules/gles31/functional/es31fTessellationGeometryInteractionTests.cpp
@@ -0,0 +1,3211 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tessellation and geometry shader interaction tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fTessellationGeometryInteractionTests.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluStrUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+
+#include <sstream>
+#include <algorithm>
+#include <iterator>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+static const char* const s_positionVertexShader =		"#version 310 es\n"
+														"in highp vec4 a_position;\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = a_position;\n"
+														"}\n";
+static const char* const s_whiteOutputFragmentShader =	"#version 310 es\n"
+														"layout(location = 0) out mediump vec4 fragColor;\n"
+														"void main (void)\n"
+														"{\n"
+														"	fragColor = vec4(1.0);\n"
+														"}\n";
+
+static bool isBlack (const tcu::RGBA& c)
+{
+	return c.getRed() == 0 && c.getGreen() == 0 && c.getBlue() == 0;
+}
+
+class IdentityShaderCase : public TestCase
+{
+public:
+					IdentityShaderCase	(Context& context, const char* name, const char* description);
+
+protected:
+	const char*		getVertexSource		(void) const;
+	const char*		getFragmentSource	(void) const;
+};
+
+IdentityShaderCase::IdentityShaderCase (Context& context, const char* name, const char* description)
+	: TestCase(context, name, description)
+{
+}
+
+const char* IdentityShaderCase::getVertexSource (void) const
+{
+	return	"#version 310 es\n"
+			"in highp vec4 a_position;\n"
+			"out highp vec4 v_vertex_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_vertex_color = vec4(a_position.x * 0.5 + 0.5, a_position.y * 0.5 + 0.5, 1.0, 0.4);\n"
+			"}\n";
+}
+
+const char* IdentityShaderCase::getFragmentSource (void) const
+{
+	return	"#version 310 es\n"
+			"in mediump vec4 v_fragment_color;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = v_fragment_color;\n"
+			"}\n";
+}
+
+class IdentityGeometryShaderCase : public IdentityShaderCase
+{
+public:
+	enum CaseType
+	{
+		CASE_TRIANGLES = 0,
+		CASE_QUADS,
+		CASE_ISOLINES,
+	};
+
+					IdentityGeometryShaderCase			(Context& context, const char* name, const char* description, CaseType caseType);
+					~IdentityGeometryShaderCase			(void);
+
+private:
+	void			init								(void);
+	void			deinit								(void);
+	IterateResult	iterate								(void);
+
+	std::string		getTessellationControlSource		(void) const;
+	std::string		getTessellationEvaluationSource		(bool geometryActive) const;
+	std::string		getGeometrySource					(void) const;
+
+	enum
+	{
+		RENDER_SIZE = 128,
+	};
+
+	const CaseType	m_case;
+	deUint32		m_patchBuffer;
+};
+
+IdentityGeometryShaderCase::IdentityGeometryShaderCase (Context& context, const char* name, const char* description, CaseType caseType)
+	: IdentityShaderCase	(context, name, description)
+	, m_case				(caseType)
+	, m_patchBuffer			(0)
+{
+}
+
+IdentityGeometryShaderCase::~IdentityGeometryShaderCase (void)
+{
+	deinit();
+}
+
+void IdentityGeometryShaderCase::init (void)
+{
+	// Requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
+		!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
+
+	if (m_context.getRenderTarget().getWidth() < RENDER_SIZE ||
+		m_context.getRenderTarget().getHeight() < RENDER_SIZE)
+		throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE) + " or larger render target.");
+
+	// Log
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing tessellating shader program output does not change when a passthrough geometry shader is attached.\n"
+		<< "Rendering two images, first with and second without a geometry shader. Expecting similar results.\n"
+		<< "Using additive blending to detect overlap.\n"
+		<< tcu::TestLog::EndMessage;
+
+	// Resources
+
+	{
+		static const tcu::Vec4 patchBufferData[4] =
+		{
+			tcu::Vec4( -0.9f, -0.9f, 0.0f, 1.0f ),
+			tcu::Vec4( -0.9f,  0.9f, 0.0f, 1.0f ),
+			tcu::Vec4(  0.9f, -0.9f, 0.0f, 1.0f ),
+			tcu::Vec4(  0.9f,  0.9f, 0.0f, 1.0f ),
+		};
+
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		gl.genBuffers(1, &m_patchBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(patchBufferData), patchBufferData, GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
+	}
+}
+
+void IdentityGeometryShaderCase::deinit (void)
+{
+	if (m_patchBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_patchBuffer);
+		m_patchBuffer = 0;
+	}
+}
+
+IdentityGeometryShaderCase::IterateResult IdentityGeometryShaderCase::iterate (void)
+{
+	const float				innerTessellationLevel	= 14.0f;
+	const float				outerTessellationLevel	= 14.0f;
+	const glw::Functions&	gl						= m_context.getRenderContext().getFunctions();
+	tcu::Surface			resultWithGeometry		(RENDER_SIZE, RENDER_SIZE);
+	tcu::Surface			resultWithoutGeometry	(RENDER_SIZE, RENDER_SIZE);
+
+	const struct
+	{
+		const char*				name;
+		const char*				description;
+		bool					containsGeometryShader;
+		tcu::PixelBufferAccess	surfaceAccess;
+	} renderTargets[] =
+	{
+		{ "RenderWithGeometryShader",		"Render with geometry shader",		true,	resultWithGeometry.getAccess()		},
+		{ "RenderWithoutGeometryShader",	"Render without geometry shader",	false,	resultWithoutGeometry.getAccess()	},
+	};
+
+	gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set viewport");
+
+	gl.enable(GL_BLEND);
+	gl.blendFunc(GL_SRC_ALPHA, GL_ONE);
+	gl.blendEquation(GL_FUNC_ADD);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set blend");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Tessellation level: inner " << innerTessellationLevel << ", outer " << outerTessellationLevel << tcu::TestLog::EndMessage;
+
+	// render with and without geometry shader
+	for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renderTargets); ++renderNdx)
+	{
+		const tcu::ScopedLogSection	section	(m_testCtx.getLog(), renderTargets[renderNdx].name, renderTargets[renderNdx].description);
+		glu::ProgramSources			sources;
+
+		sources	<< glu::VertexSource(getVertexSource())
+				<< glu::FragmentSource(getFragmentSource())
+				<< glu::TessellationControlSource(getTessellationControlSource())
+				<< glu::TessellationEvaluationSource(getTessellationEvaluationSource(renderTargets[renderNdx].containsGeometryShader));
+
+		if (renderTargets[renderNdx].containsGeometryShader)
+			sources << glu::GeometrySource(getGeometrySource());
+
+		{
+			const glu::ShaderProgram	program					(m_context.getRenderContext(), sources);
+			const glu::VertexArray		vao						(m_context.getRenderContext());
+			const int					posLocation				= gl.getAttribLocation(program.getProgram(), "a_position");
+			const int					innerTessellationLoc	= gl.getUniformLocation(program.getProgram(), "u_innerTessellationLevel");
+			const int					outerTessellationLoc	= gl.getUniformLocation(program.getProgram(), "u_outerTessellationLevel");
+
+			m_testCtx.getLog() << program;
+
+			if (!program.isOk())
+				throw tcu::TestError("could not build program");
+			if (posLocation == -1)
+				throw tcu::TestError("a_position location was -1");
+			if (outerTessellationLoc == -1)
+				throw tcu::TestError("u_outerTessellationLevel location was -1");
+
+			gl.bindVertexArray(*vao);
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
+			gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+			gl.enableVertexAttribArray(posLocation);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");
+
+			gl.useProgram(program.getProgram());
+			gl.uniform1f(outerTessellationLoc, outerTessellationLevel);
+
+			if (innerTessellationLoc == -1)
+				gl.uniform1f(innerTessellationLoc, innerTessellationLevel);
+
+			GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
+
+			gl.patchParameteri(GL_PATCH_VERTICES, (m_case == CASE_TRIANGLES) ? (3): (4));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
+
+			gl.clear(GL_COLOR_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+			gl.drawArrays(GL_PATCHES, 0, 4);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
+
+			glu::readPixels(m_context.getRenderContext(), 0, 0, renderTargets[renderNdx].surfaceAccess);
+		}
+	}
+
+	if (tcu::intThresholdPositionDeviationCompare(m_testCtx.getLog(),
+												  "ImageCompare",
+												  "Image comparison",
+												  resultWithoutGeometry.getAccess(),
+												  resultWithGeometry.getAccess(),
+												  tcu::UVec4(8, 8, 8, 255),
+												  tcu::IVec3(1, 1, 0),
+												  true,
+												  tcu::COMPARE_LOG_RESULT))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+	return STOP;
+}
+
+std::string IdentityGeometryShaderCase::getTessellationControlSource (void) const
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout(vertices = 4) out;\n"
+			"\n"
+			"uniform highp float u_innerTessellationLevel;\n"
+			"uniform highp float u_outerTessellationLevel;\n"
+			"in highp vec4 v_vertex_color[];\n"
+			"out highp vec4 v_patch_color[];\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
+			"	v_patch_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n"
+			"\n";
+
+	if (m_case == CASE_TRIANGLES)
+		buf <<	"	gl_TessLevelOuter[0] = u_outerTessellationLevel;\n"
+				"	gl_TessLevelOuter[1] = u_outerTessellationLevel;\n"
+				"	gl_TessLevelOuter[2] = u_outerTessellationLevel;\n"
+				"	gl_TessLevelInner[0] = u_innerTessellationLevel;\n";
+	else if (m_case == CASE_QUADS)
+		buf <<	"	gl_TessLevelOuter[0] = u_outerTessellationLevel;\n"
+				"	gl_TessLevelOuter[1] = u_outerTessellationLevel;\n"
+				"	gl_TessLevelOuter[2] = u_outerTessellationLevel;\n"
+				"	gl_TessLevelOuter[3] = u_outerTessellationLevel;\n"
+				"	gl_TessLevelInner[0] = u_innerTessellationLevel;\n"
+				"	gl_TessLevelInner[1] = u_innerTessellationLevel;\n";
+	else if (m_case == CASE_ISOLINES)
+		buf <<	"	gl_TessLevelOuter[0] = u_outerTessellationLevel;\n"
+				"	gl_TessLevelOuter[1] = u_outerTessellationLevel;\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+std::string IdentityGeometryShaderCase::getTessellationEvaluationSource (bool geometryActive) const
+{
+	const char* const	colorOutputName = ((geometryActive) ? ("v_evaluated_color") : ("v_fragment_color"));
+	std::ostringstream	buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout("
+				<< ((m_case == CASE_TRIANGLES) ? ("triangles") : (m_case == CASE_QUADS) ? ("quads") : ("isolines"))
+				<< ") in;\n"
+			"\n"
+			"in highp vec4 v_patch_color[];\n"
+			"out highp vec4 " << colorOutputName << ";\n"
+			"\n"
+			"void main (void)\n"
+			"{\n";
+
+	if (m_case == CASE_TRIANGLES)
+		buf <<	"	vec3 weights = vec3(pow(gl_TessCoord.x, 1.3), pow(gl_TessCoord.y, 1.3), pow(gl_TessCoord.z, 1.3));\n"
+				"	vec3 cweights = gl_TessCoord;\n"
+				"	gl_Position = vec4(weights.x * gl_in[0].gl_Position.xyz + weights.y * gl_in[1].gl_Position.xyz + weights.z * gl_in[2].gl_Position.xyz, 1.0);\n"
+				"	" << colorOutputName << " = cweights.x * v_patch_color[0] + cweights.y * v_patch_color[1] + cweights.z * v_patch_color[2];\n";
+	else if (m_case == CASE_QUADS || m_case == CASE_ISOLINES)
+		buf <<	"	vec2 normalizedCoord = (gl_TessCoord.xy * 2.0 - vec2(1.0));\n"
+				"	vec2 normalizedWeights = normalizedCoord * (vec2(1.0) - 0.3 * cos(normalizedCoord.yx * 1.57));\n"
+				"	vec2 weights = normalizedWeights * 0.5 + vec2(0.5);\n"
+				"	vec2 cweights = gl_TessCoord.xy;\n"
+				"	gl_Position = mix(mix(gl_in[0].gl_Position, gl_in[1].gl_Position, weights.y), mix(gl_in[2].gl_Position, gl_in[3].gl_Position, weights.y), weights.x);\n"
+				"	" << colorOutputName << " = mix(mix(v_patch_color[0], v_patch_color[1], cweights.y), mix(v_patch_color[2], v_patch_color[3], cweights.y), cweights.x);\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+std::string IdentityGeometryShaderCase::getGeometrySource (void) const
+{
+	const char* const	geometryInputPrimitive			= (m_case == CASE_ISOLINES) ? ("lines") : ("triangles");
+	const char* const	geometryOutputPrimitive			= (m_case == CASE_ISOLINES) ? ("line_strip") : ("triangle_strip");
+	const int			numEmitVertices					= (m_case == CASE_ISOLINES) ? (2) : (3);
+	std::ostringstream	buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n"
+			"layout(" << geometryInputPrimitive << ") in;\n"
+			"layout(" << geometryOutputPrimitive << ", max_vertices=" << numEmitVertices <<") out;\n"
+			"\n"
+			"in highp vec4 v_evaluated_color[];\n"
+			"out highp vec4 v_fragment_color;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	for (int ndx = 0; ndx < gl_in.length(); ++ndx)\n"
+			"	{\n"
+			"		gl_Position = gl_in[ndx].gl_Position;\n"
+			"		v_fragment_color = v_evaluated_color[ndx];\n"
+			"		EmitVertex();\n"
+			"	}\n"
+			"}\n";
+
+	return buf.str();
+}
+
+class IdentityTessellationShaderCase : public IdentityShaderCase
+{
+public:
+	enum CaseType
+	{
+		CASE_TRIANGLES = 0,
+		CASE_ISOLINES,
+	};
+
+					IdentityTessellationShaderCase		(Context& context, const char* name, const char* description, CaseType caseType);
+					~IdentityTessellationShaderCase		(void);
+
+private:
+	void			init								(void);
+	void			deinit								(void);
+	IterateResult	iterate								(void);
+
+	std::string		getTessellationControlSource		(void) const;
+	std::string		getTessellationEvaluationSource		(void) const;
+	std::string		getGeometrySource					(bool tessellationActive) const;
+
+	enum
+	{
+		RENDER_SIZE = 256,
+	};
+
+	const CaseType	m_case;
+	deUint32		m_dataBuffer;
+};
+
+IdentityTessellationShaderCase::IdentityTessellationShaderCase (Context& context, const char* name, const char* description, CaseType caseType)
+	: IdentityShaderCase	(context, name, description)
+	, m_case				(caseType)
+	, m_dataBuffer			(0)
+{
+}
+
+IdentityTessellationShaderCase::~IdentityTessellationShaderCase (void)
+{
+	deinit();
+}
+
+void IdentityTessellationShaderCase::init (void)
+{
+	// Requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
+		!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
+
+	if (m_context.getRenderTarget().getWidth() < RENDER_SIZE ||
+		m_context.getRenderTarget().getHeight() < RENDER_SIZE)
+		throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE) + " or larger render target.");
+
+	// Log
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing geometry shading shader program output does not change when a passthrough tessellation shader is attached.\n"
+		<< "Rendering two images, first with and second without a tessellation shader. Expecting similar results.\n"
+		<< "Using additive blending to detect overlap.\n"
+		<< tcu::TestLog::EndMessage;
+
+	// Resources
+
+	{
+		static const tcu::Vec4	pointData[]	=
+		{
+			tcu::Vec4( -0.4f,  0.4f, 0.0f, 1.0f ),
+			tcu::Vec4(  0.0f, -0.5f, 0.0f, 1.0f ),
+			tcu::Vec4(  0.4f,  0.4f, 0.0f, 1.0f ),
+		};
+		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+
+		gl.genBuffers(1, &m_dataBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(pointData), pointData, GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
+	}
+}
+
+void IdentityTessellationShaderCase::deinit (void)
+{
+	if (m_dataBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_dataBuffer);
+		m_dataBuffer = 0;
+	}
+}
+
+IdentityTessellationShaderCase::IterateResult IdentityTessellationShaderCase::iterate (void)
+{
+	const glw::Functions&	gl							= m_context.getRenderContext().getFunctions();
+	tcu::Surface			resultWithTessellation		(RENDER_SIZE, RENDER_SIZE);
+	tcu::Surface			resultWithoutTessellation	(RENDER_SIZE, RENDER_SIZE);
+	const int				numPrimitiveVertices		= (m_case == CASE_TRIANGLES) ? (3) : (2);
+
+	const struct
+	{
+		const char*				name;
+		const char*				description;
+		bool					containsTessellationShaders;
+		tcu::PixelBufferAccess	surfaceAccess;
+	} renderTargets[] =
+	{
+		{ "RenderWithTessellationShader",		"Render with tessellation shader",		true,	resultWithTessellation.getAccess()		},
+		{ "RenderWithoutTessellationShader",	"Render without tessellation shader",	false,	resultWithoutTessellation.getAccess()	},
+	};
+
+	gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set viewport");
+
+	gl.enable(GL_BLEND);
+	gl.blendFunc(GL_SRC_ALPHA, GL_ONE);
+	gl.blendEquation(GL_FUNC_ADD);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set blend");
+
+	// render with and without tessellation shader
+	for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renderTargets); ++renderNdx)
+	{
+		const tcu::ScopedLogSection	section	(m_testCtx.getLog(), renderTargets[renderNdx].name, renderTargets[renderNdx].description);
+		glu::ProgramSources			sources;
+
+		sources	<< glu::VertexSource(getVertexSource())
+				<< glu::FragmentSource(getFragmentSource())
+				<< glu::GeometrySource(getGeometrySource(renderTargets[renderNdx].containsTessellationShaders));
+
+		if (renderTargets[renderNdx].containsTessellationShaders)
+			sources	<< glu::TessellationControlSource(getTessellationControlSource())
+					<< glu::TessellationEvaluationSource(getTessellationEvaluationSource());
+
+		{
+			const glu::ShaderProgram	program					(m_context.getRenderContext(), sources);
+			const glu::VertexArray		vao						(m_context.getRenderContext());
+			const int					posLocation				= gl.getAttribLocation(program.getProgram(), "a_position");
+
+			m_testCtx.getLog() << program;
+
+			if (!program.isOk())
+				throw tcu::TestError("could not build program");
+			if (posLocation == -1)
+				throw tcu::TestError("a_position location was -1");
+
+			gl.bindVertexArray(*vao);
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBuffer);
+			gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+			gl.enableVertexAttribArray(posLocation);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");
+
+			gl.useProgram(program.getProgram());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
+
+			gl.clear(GL_COLOR_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+			if (renderTargets[renderNdx].containsTessellationShaders)
+			{
+				gl.patchParameteri(GL_PATCH_VERTICES, numPrimitiveVertices);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
+
+				gl.drawArrays(GL_PATCHES, 0, numPrimitiveVertices);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
+			}
+			else
+			{
+				gl.drawArrays((m_case == CASE_TRIANGLES) ? (GL_TRIANGLES) : (GL_LINES), 0, numPrimitiveVertices);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "draw primitives");
+			}
+
+			glu::readPixels(m_context.getRenderContext(), 0, 0, renderTargets[renderNdx].surfaceAccess);
+		}
+	}
+
+	// compare
+	{
+		bool imageOk;
+
+		if (m_context.getRenderTarget().getNumSamples() > 1)
+			imageOk = tcu::fuzzyCompare(m_testCtx.getLog(),
+										"ImageCompare",
+										"Image comparison",
+										resultWithoutTessellation.getAccess(),
+										resultWithTessellation.getAccess(),
+										0.03f,
+										tcu::COMPARE_LOG_RESULT);
+		else
+			imageOk = tcu::intThresholdPositionDeviationCompare(m_testCtx.getLog(),
+																"ImageCompare",
+																"Image comparison",
+																resultWithoutTessellation.getAccess(),
+																resultWithTessellation.getAccess(),
+																tcu::UVec4(8, 8, 8, 255),				//!< threshold
+																tcu::IVec3(1, 1, 0),					//!< 3x3 search kernel
+																true,									//!< fragments may end up over the viewport, just ignore them
+																tcu::COMPARE_LOG_RESULT);
+
+		if (imageOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+	}
+
+	return STOP;
+}
+
+std::string IdentityTessellationShaderCase::getTessellationControlSource (void) const
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout(vertices = " << ((m_case == CASE_TRIANGLES) ? (3) : (2)) << ") out;\n"
+			"\n"
+			"in highp vec4 v_vertex_color[];\n"
+			"out highp vec4 v_control_color[];\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
+			"	v_control_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n"
+			"\n";
+
+	if (m_case == CASE_TRIANGLES)
+		buf <<	"	gl_TessLevelOuter[0] = 1.0;\n"
+				"	gl_TessLevelOuter[1] = 1.0;\n"
+				"	gl_TessLevelOuter[2] = 1.0;\n"
+				"	gl_TessLevelInner[0] = 1.0;\n";
+	else if (m_case == CASE_ISOLINES)
+		buf <<	"	gl_TessLevelOuter[0] = 1.0;\n"
+				"	gl_TessLevelOuter[1] = 1.0;\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+std::string IdentityTessellationShaderCase::getTessellationEvaluationSource (void) const
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout("
+				<< ((m_case == CASE_TRIANGLES) ? ("triangles") : ("isolines"))
+				<< ") in;\n"
+			"\n"
+			"in highp vec4 v_control_color[];\n"
+			"out highp vec4 v_evaluated_color;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n";
+
+	if (m_case == CASE_TRIANGLES)
+		buf <<	"	gl_Position = 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"
+				"	v_evaluated_color = gl_TessCoord.x * v_control_color[0] + gl_TessCoord.y * v_control_color[1] + gl_TessCoord.z * v_control_color[2];\n";
+	else if (m_case == CASE_ISOLINES)
+		buf <<	"	gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);\n"
+				"	v_evaluated_color = mix(v_control_color[0], v_control_color[1], gl_TessCoord.x);\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+std::string IdentityTessellationShaderCase::getGeometrySource (bool tessellationActive) const
+{
+	const char* const	colorSourceName			= (tessellationActive) ? ("v_evaluated_color") : ("v_vertex_color");
+	const char* const	geometryInputPrimitive	= (m_case == CASE_ISOLINES) ? ("lines") : ("triangles");
+	const char* const	geometryOutputPrimitive	= (m_case == CASE_ISOLINES) ? ("line_strip") : ("triangle_strip");
+	const int			numEmitVertices			= (m_case == CASE_ISOLINES) ? (11) : (8);
+	std::ostringstream	buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n"
+			"layout(" << geometryInputPrimitive << ") in;\n"
+			"layout(" << geometryOutputPrimitive << ", max_vertices=" << numEmitVertices <<") out;\n"
+			"\n"
+			"in highp vec4 " << colorSourceName << "[];\n"
+			"out highp vec4 v_fragment_color;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n";
+
+	if (m_case == CASE_TRIANGLES)
+	{
+		buf <<	"	vec4 centerPos = (gl_in[0].gl_Position + gl_in[1].gl_Position + gl_in[2].gl_Position) / 3.0f;\n"
+				"\n"
+				"	for (int ndx = 0; ndx < 4; ++ndx)\n"
+				"	{\n"
+				"		gl_Position = centerPos + (centerPos - gl_in[ndx % 3].gl_Position);\n"
+				"		v_fragment_color = " << colorSourceName << "[ndx % 3];\n"
+				"		EmitVertex();\n"
+				"\n"
+				"		gl_Position = centerPos + 0.7 * (centerPos - gl_in[ndx % 3].gl_Position);\n"
+				"		v_fragment_color = " << colorSourceName << "[ndx % 3];\n"
+				"		EmitVertex();\n"
+				"	}\n";
+
+	}
+	else if (m_case == CASE_ISOLINES)
+	{
+		buf <<	"	vec4 mdir = vec4(gl_in[0].gl_Position.y - gl_in[1].gl_Position.y, gl_in[1].gl_Position.x - gl_in[0].gl_Position.x, 0.0, 0.0);\n"
+				"	for (int i = 0; i <= 10; ++i)\n"
+				"	{\n"
+				"		float xweight = cos(float(i) / 10.0 * 6.28) * 0.5 + 0.5;\n"
+				"		float mweight = sin(float(i) / 10.0 * 6.28) * 0.1 + 0.1;\n"
+				"		gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, xweight) + mweight * mdir;\n"
+				"		v_fragment_color = mix(" << colorSourceName << "[0], " << colorSourceName << "[1], xweight);\n"
+				"		EmitVertex();\n"
+				"	}\n";
+	}
+	else
+		DE_ASSERT(false);
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+class FeedbackPrimitiveTypeCase : public TestCase
+{
+public:
+	enum TessellationOutputType
+	{
+		TESSELLATION_OUT_TRIANGLES = 0,
+		TESSELLATION_OUT_QUADS,
+		TESSELLATION_OUT_ISOLINES,
+
+		TESSELLATION_OUT_LAST
+	};
+	enum TessellationPointMode
+	{
+		TESSELLATION_POINTMODE_OFF = 0,
+		TESSELLATION_POINTMODE_ON,
+
+		TESSELLATION_POINTMODE_LAST
+	};
+	enum GeometryOutputType
+	{
+		GEOMETRY_OUTPUT_POINTS = 0,
+		GEOMETRY_OUTPUT_LINES,
+		GEOMETRY_OUTPUT_TRIANGLES,
+
+		GEOMETRY_OUTPUT_LAST
+	};
+
+									FeedbackPrimitiveTypeCase				(Context& context,
+																			 const char* name,
+																			 const char* description,
+																			 TessellationOutputType tessellationOutput,
+																			 TessellationPointMode tessellationPointMode,
+																			 GeometryOutputType geometryOutputType);
+									~FeedbackPrimitiveTypeCase				(void);
+
+private:
+	void							init									(void);
+	void							deinit									(void);
+	IterateResult					iterate									(void);
+
+	void							renderWithFeedback						(tcu::Surface& dst);
+	void							renderWithoutFeedback					(tcu::Surface& dst);
+	void							verifyFeedbackResults					(const std::vector<tcu::Vec4>& feedbackResult);
+	void							verifyRenderedImage						(const tcu::Surface& image, const std::vector<tcu::Vec4>& vertices);
+
+	void							genTransformFeedback					(void);
+	int								getNumGeneratedElementsPerPrimitive		(void) const;
+	int								getNumGeneratedPrimitives				(void) const;
+	int								getNumTessellatedPrimitives				(void) const;
+	int								getGeometryAmplification				(void) const;
+
+	const char*						getVertexSource							(void) const;
+	const char*						getFragmentSource						(void) const;
+	std::string						getTessellationControlSource			(void) const;
+	std::string						getTessellationEvaluationSource			(void) const;
+	std::string						getGeometrySource						(void) const;
+
+	static const char*				getTessellationOutputDescription		(TessellationOutputType tessellationOutput,
+																			 TessellationPointMode tessellationPointMode);
+	static const char*				getGeometryInputDescription				(TessellationOutputType tessellationOutput,
+																			 TessellationPointMode tessellationPointMode);
+	static const char*				getGeometryOutputDescription			(GeometryOutputType geometryOutput);
+	glw::GLenum						getOutputPrimitiveGLType				(void) const;
+
+	enum
+	{
+		RENDER_SIZE = 128,
+	};
+
+	const TessellationOutputType	m_tessellationOutput;
+	const TessellationPointMode		m_tessellationPointMode;
+	const GeometryOutputType		m_geometryOutputType;
+
+	glu::ShaderProgram*				m_feedbackProgram;
+	glu::ShaderProgram*				m_nonFeedbackProgram;
+	deUint32						m_patchBuffer;
+	deUint32						m_feedbackID;
+	deUint32						m_feedbackBuffer;
+};
+
+FeedbackPrimitiveTypeCase::FeedbackPrimitiveTypeCase (Context& context,
+									  const char* name,
+									  const char* description,
+									  TessellationOutputType tessellationOutput,
+									  TessellationPointMode tessellationPointMode,
+									  GeometryOutputType geometryOutputType)
+	: TestCase					(context, name, description)
+	, m_tessellationOutput		(tessellationOutput)
+	, m_tessellationPointMode	(tessellationPointMode)
+	, m_geometryOutputType		(geometryOutputType)
+	, m_feedbackProgram			(DE_NULL)
+	, m_nonFeedbackProgram		(DE_NULL)
+	, m_patchBuffer				(0)
+	, m_feedbackID				(0)
+	, m_feedbackBuffer			(0)
+{
+	DE_ASSERT(tessellationOutput < TESSELLATION_OUT_LAST);
+	DE_ASSERT(tessellationPointMode < TESSELLATION_POINTMODE_LAST);
+	DE_ASSERT(geometryOutputType < GEOMETRY_OUTPUT_LAST);
+}
+
+FeedbackPrimitiveTypeCase::~FeedbackPrimitiveTypeCase (void)
+{
+	deinit();
+}
+
+void FeedbackPrimitiveTypeCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
+		!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
+
+	if (m_context.getRenderTarget().getWidth() < RENDER_SIZE ||
+		m_context.getRenderTarget().getHeight() < RENDER_SIZE)
+		throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE) + " or larger render target.");
+
+	// Log
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing "
+			<< getTessellationOutputDescription(m_tessellationOutput, m_tessellationPointMode)
+			<< "->"
+			<< getGeometryInputDescription(m_tessellationOutput, m_tessellationPointMode)
+			<< " primitive conversion with and without transform feedback.\n"
+		<< "Sending a patch of 4 vertices (2x2 uniform grid) to tessellation control shader.\n"
+		<< "Control shader emits a patch of 9 vertices (3x3 uniform grid).\n"
+		<< "Setting outer tessellation level = 3, inner = 3.\n"
+		<< "Primitive generator emits " << getTessellationOutputDescription(m_tessellationOutput, m_tessellationPointMode) << "\n"
+		<< "Geometry shader transforms emitted primitives to " << getGeometryOutputDescription(m_geometryOutputType) << "\n"
+		<< "Reading back vertex positions of generated primitives using transform feedback.\n"
+		<< "Verifying rendered image and feedback vertices are consistent.\n"
+		<< "Rendering scene again with identical shader program, but without setting feedback varying. Expecting similar output image."
+		<< tcu::TestLog::EndMessage;
+
+	// Resources
+
+	{
+		static const tcu::Vec4 patchBufferData[4] =
+		{
+			tcu::Vec4( -0.9f, -0.9f, 0.0f, 1.0f ),
+			tcu::Vec4( -0.9f,  0.9f, 0.0f, 1.0f ),
+			tcu::Vec4(  0.9f, -0.9f, 0.0f, 1.0f ),
+			tcu::Vec4(  0.9f,  0.9f, 0.0f, 1.0f ),
+		};
+
+		gl.genBuffers(1, &m_patchBuffer);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, sizeof(patchBufferData), patchBufferData, GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
+	}
+
+	m_feedbackProgram = new glu::ShaderProgram(m_context.getRenderContext(),
+											   glu::ProgramSources()
+												<< glu::VertexSource(getVertexSource())
+												<< glu::FragmentSource(getFragmentSource())
+												<< glu::TessellationControlSource(getTessellationControlSource())
+												<< glu::TessellationEvaluationSource(getTessellationEvaluationSource())
+												<< glu::GeometrySource(getGeometrySource())
+												<< glu::TransformFeedbackVarying("tf_someVertexPosition")
+												<< glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS));
+	m_testCtx.getLog() << *m_feedbackProgram;
+	if (!m_feedbackProgram->isOk())
+		throw tcu::TestError("failed to build program");
+
+	m_nonFeedbackProgram = new glu::ShaderProgram(m_context.getRenderContext(),
+												  glu::ProgramSources()
+													<< glu::VertexSource(getVertexSource())
+													<< glu::FragmentSource(getFragmentSource())
+													<< glu::TessellationControlSource(getTessellationControlSource())
+													<< glu::TessellationEvaluationSource(getTessellationEvaluationSource())
+													<< glu::GeometrySource(getGeometrySource()));
+	if (!m_nonFeedbackProgram->isOk())
+	{
+		m_testCtx.getLog() << *m_nonFeedbackProgram;
+		throw tcu::TestError("failed to build program");
+	}
+
+	genTransformFeedback();
+}
+
+void FeedbackPrimitiveTypeCase::deinit (void)
+{
+	if (m_patchBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_patchBuffer);
+		m_patchBuffer = 0;
+	}
+
+	if (m_feedbackBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_feedbackBuffer);
+		m_feedbackBuffer = 0;
+	}
+
+	if (m_feedbackID)
+	{
+		m_context.getRenderContext().getFunctions().deleteTransformFeedbacks(1, &m_feedbackID);
+		m_feedbackID = 0;
+	}
+
+	if (m_feedbackProgram)
+	{
+		delete m_feedbackProgram;
+		m_feedbackProgram = DE_NULL;
+	}
+
+	if (m_nonFeedbackProgram)
+	{
+		delete m_nonFeedbackProgram;
+		m_nonFeedbackProgram = DE_NULL;
+	}
+}
+
+FeedbackPrimitiveTypeCase::IterateResult FeedbackPrimitiveTypeCase::iterate (void)
+{
+	tcu::Surface feedbackResult		(RENDER_SIZE, RENDER_SIZE);
+	tcu::Surface nonFeedbackResult	(RENDER_SIZE, RENDER_SIZE);
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// render with and without XFB
+	renderWithFeedback(feedbackResult);
+	renderWithoutFeedback(nonFeedbackResult);
+
+	// compare
+	{
+		bool imageOk;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Comparing the image rendered with no transform feedback against the image rendered with enabled transform feedback." << tcu::TestLog::EndMessage;
+
+		if (m_context.getRenderTarget().getNumSamples() > 1)
+			imageOk = tcu::fuzzyCompare(m_testCtx.getLog(),
+										"ImageCompare",
+										"Image comparison",
+										feedbackResult.getAccess(),
+										nonFeedbackResult.getAccess(),
+										0.03f,
+										tcu::COMPARE_LOG_RESULT);
+		else
+			imageOk = tcu::intThresholdPositionDeviationCompare(m_testCtx.getLog(),
+																"ImageCompare",
+																"Image comparison",
+																feedbackResult.getAccess(),
+																nonFeedbackResult.getAccess(),
+																tcu::UVec4(8, 8, 8, 255),						//!< threshold
+																tcu::IVec3(1, 1, 0),							//!< 3x3 search kernel
+																true,											//!< fragments may end up over the viewport, just ignore them
+																tcu::COMPARE_LOG_RESULT);
+
+		if (!imageOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+	}
+
+	return STOP;
+}
+
+void FeedbackPrimitiveTypeCase::renderWithFeedback(tcu::Surface& dst)
+{
+	const glw::Functions&			gl							= m_context.getRenderContext().getFunctions();
+	const glu::VertexArray			vao							(m_context.getRenderContext());
+	const glu::Query				primitivesGeneratedQuery	(m_context.getRenderContext());
+	const int						posLocation					= gl.getAttribLocation(m_feedbackProgram->getProgram(), "a_position");
+	const glw::GLenum				feedbackPrimitiveMode		= getOutputPrimitiveGLType();
+
+	if (posLocation == -1)
+		throw tcu::TestError("a_position was -1");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering with transform feedback" << tcu::TestLog::EndMessage;
+
+	gl.viewport(0, 0, dst.getWidth(), dst.getHeight());
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	gl.bindVertexArray(*vao);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
+	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(posLocation);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");
+
+	gl.useProgram(m_feedbackProgram->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
+
+	gl.patchParameteri(GL_PATCH_VERTICES, 4);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
+
+	gl.beginQuery(GL_PRIMITIVES_GENERATED, *primitivesGeneratedQuery);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "begin GL_PRIMITIVES_GENERATED query");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Begin transform feedback with mode " << glu::getPrimitiveTypeStr(feedbackPrimitiveMode) << tcu::TestLog::EndMessage;
+
+	gl.beginTransformFeedback(feedbackPrimitiveMode);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "begin xfb");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Calling drawArrays with mode GL_PATCHES" << tcu::TestLog::EndMessage;
+
+	gl.drawArrays(GL_PATCHES, 0, 4);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
+
+	gl.endTransformFeedback();
+	GLU_EXPECT_NO_ERROR(gl.getError(), "end xfb");
+
+	gl.endQuery(GL_PRIMITIVES_GENERATED);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "end GL_PRIMITIVES_GENERATED query");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "readPixels");
+
+	// verify GL_PRIMITIVES_GENERATED
+	{
+		glw::GLuint primitivesGeneratedResult = 0;
+		gl.getQueryObjectuiv(*primitivesGeneratedQuery, GL_QUERY_RESULT, &primitivesGeneratedResult);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_PRIMITIVES_GENERATED value");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying GL_PRIMITIVES_GENERATED, expecting " << getNumGeneratedPrimitives() << tcu::TestLog::EndMessage;
+
+		if ((int)primitivesGeneratedResult != getNumGeneratedPrimitives())
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Error, GL_PRIMITIVES_GENERATED was " << primitivesGeneratedResult << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected GL_PRIMITIVES_GENERATED");
+		}
+		else
+			m_testCtx.getLog() << tcu::TestLog::Message << "GL_PRIMITIVES_GENERATED valid." << tcu::TestLog::EndMessage;
+	}
+
+	// feedback
+	{
+		std::vector<tcu::Vec4>	feedbackResults		(getNumGeneratedElementsPerPrimitive() * getNumGeneratedPrimitives());
+		const void*				mappedPtr			= gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, (glw::GLsizeiptr)(feedbackResults.size() * sizeof(tcu::Vec4)), GL_MAP_READ_BIT);
+		glw::GLboolean			unmapResult;
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "mapBufferRange");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Reading transform feedback buffer." << tcu::TestLog::EndMessage;
+		if (!mappedPtr)
+			throw tcu::TestError("mapBufferRange returned null");
+
+		deMemcpy(feedbackResults[0].getPtr(), mappedPtr, (int)(feedbackResults.size() * sizeof(tcu::Vec4)));
+
+		unmapResult = gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "unmapBuffer");
+
+		if (unmapResult != GL_TRUE)
+			throw tcu::TestError("unmapBuffer failed, did not return true");
+
+		// verify transform results
+		verifyFeedbackResults(feedbackResults);
+
+		// verify feedback results are consistent with rendered image
+		verifyRenderedImage(dst, feedbackResults);
+	}
+}
+
+void FeedbackPrimitiveTypeCase::renderWithoutFeedback (tcu::Surface& dst)
+{
+	const glw::Functions&			gl							= m_context.getRenderContext().getFunctions();
+	const glu::VertexArray			vao							(m_context.getRenderContext());
+	const int						posLocation					= gl.getAttribLocation(m_nonFeedbackProgram->getProgram(), "a_position");
+
+	if (posLocation == -1)
+		throw tcu::TestError("a_position was -1");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering without transform feedback" << tcu::TestLog::EndMessage;
+
+	gl.viewport(0, 0, dst.getWidth(), dst.getHeight());
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	gl.bindVertexArray(*vao);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
+	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(posLocation);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");
+
+	gl.useProgram(m_nonFeedbackProgram->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
+
+	gl.patchParameteri(GL_PATCH_VERTICES, 4);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Calling drawArrays with mode GL_PATCHES" << tcu::TestLog::EndMessage;
+
+	gl.drawArrays(GL_PATCHES, 0, 4);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "readPixels");
+}
+
+void FeedbackPrimitiveTypeCase::verifyFeedbackResults (const std::vector<tcu::Vec4>& feedbackResult)
+{
+	const int	geometryAmplification	= getGeometryAmplification();
+	const int	elementsPerPrimitive	= getNumGeneratedElementsPerPrimitive();
+	const int	errorFloodThreshold		= 8;
+	int			readNdx					= 0;
+	int			numErrors				= 0;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying feedback results." << tcu::TestLog::EndMessage;
+
+	for (int tessellatedPrimitiveNdx = 0; tessellatedPrimitiveNdx < getNumTessellatedPrimitives(); ++tessellatedPrimitiveNdx)
+	{
+		const tcu::Vec4	primitiveVertex = feedbackResult[readNdx];
+
+		// check the generated vertices are in the proper range (range: -0.4 <-> 0.4)
+		{
+			const float	equalThreshold	=	1.0e-6f;
+			const bool	centroidOk		=	(primitiveVertex.x() >= -0.4f - equalThreshold) &&
+											(primitiveVertex.x() <=  0.4f + equalThreshold) &&
+											(primitiveVertex.y() >= -0.4f - equalThreshold) &&
+											(primitiveVertex.y() <=  0.4f + equalThreshold) &&
+											(de::abs(primitiveVertex.z()) < equalThreshold) &&
+											(de::abs(primitiveVertex.w() - 1.0f) < equalThreshold);
+
+			if (!centroidOk && numErrors++ < errorFloodThreshold)
+			{
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Element at index " << (readNdx) << " (tessellation invocation " << tessellatedPrimitiveNdx << ")\n"
+					<< "\texpected vertex in range: ( [-0.4, 0.4], [-0.4, 0.4], 0.0, 1.0 )\n"
+					<< "\tgot: " << primitiveVertex
+					<< tcu::TestLog::EndMessage;
+
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid feedback output");
+
+				++readNdx;
+				continue;
+			}
+		}
+
+		// check all other primitives generated from this tessellated primitive have the same feedback value
+		for (int generatedPrimitiveNdx = 0; generatedPrimitiveNdx < geometryAmplification; ++generatedPrimitiveNdx)
+		for (int primitiveVertexNdx = 0; primitiveVertexNdx < elementsPerPrimitive; ++primitiveVertexNdx)
+		{
+			const tcu::Vec4 generatedElementVertex	= feedbackResult[readNdx];
+			const tcu::Vec4 equalThreshold			(1.0e-6f);
+
+			if (tcu::boolAny(tcu::greaterThan(tcu::abs(primitiveVertex - generatedElementVertex), equalThreshold)))
+			{
+				if (numErrors++ < errorFloodThreshold)
+				{
+					m_testCtx.getLog()
+						<< tcu::TestLog::Message
+						<< "Element at index " << (readNdx) << " (tessellation invocation " << tessellatedPrimitiveNdx << ", geometry primitive " << generatedPrimitiveNdx << ", emitted vertex " << primitiveVertexNdx << "):\n"
+						<< "\tfeedback result was not contant over whole primitive.\n"
+						<< "\tfirst emitted value: " << primitiveVertex << "\n"
+						<< "\tcurrent emitted value:" << generatedElementVertex << "\n"
+						<< tcu::TestLog::EndMessage;
+				}
+
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got multiple different feedback values for a single primitive");
+			}
+
+			readNdx++;
+		}
+	}
+
+	if (numErrors > errorFloodThreshold)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Omitted " << (numErrors - errorFloodThreshold) << " error(s)." << tcu::TestLog::EndMessage;
+}
+
+static bool feedbackResultCompare (const tcu::Vec4& a, const tcu::Vec4& b)
+{
+	if (a.x() < b.x())
+		return true;
+	if (a.x() > b.x())
+		return false;
+
+	return a.y() < b.y();
+}
+
+void FeedbackPrimitiveTypeCase::verifyRenderedImage (const tcu::Surface& image, const std::vector<tcu::Vec4>& tfVertices)
+{
+	std::vector<tcu::Vec4> vertices;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Comparing result image against feedback results." << tcu::TestLog::EndMessage;
+
+	// Check only unique vertices
+	std::unique_copy(tfVertices.begin(), tfVertices.end(), std::back_insert_iterator<std::vector<tcu::Vec4> >(vertices));
+	std::sort(vertices.begin(), vertices.end(), feedbackResultCompare);
+	vertices.erase(std::unique(vertices.begin(), vertices.end()), vertices.end());
+
+	// Verifying vertices recorded with feedback actually ended up on the result image
+	for (int ndx = 0; ndx < (int)vertices.size(); ++ndx)
+	{
+		// Rasterization (of lines) may deviate by one pixel. In addition to that, allow minimal errors in rasterized position vs. feedback result.
+		// This minimal error could result in a difference in rounding => allow one additional pixel in deviation
+
+		const int			rasterDeviation	= 2;
+		const tcu::IVec2	rasterPos		((int)deFloatRound((vertices[ndx].x() * 0.5f + 0.5f) * image.getWidth()), (int)deFloatRound((vertices[ndx].y() * 0.5f + 0.5f) * image.getHeight()));
+
+		// Find produced rasterization results
+		bool				found			= false;
+
+		for (int dy = -rasterDeviation; dy <= rasterDeviation && !found; ++dy)
+		for (int dx = -rasterDeviation; dx <= rasterDeviation && !found; ++dx)
+		{
+			// Raster result could end up outside the viewport
+			if (rasterPos.x() + dx < 0 || rasterPos.x() + dx >= image.getWidth() ||
+				rasterPos.y() + dy < 0 || rasterPos.y() + dy >= image.getHeight())
+				found = true;
+			else
+			{
+				const tcu::RGBA result = image.getPixel(rasterPos.x() + dx, rasterPos.y() + dy);
+
+				if(!isBlack(result))
+					found = true;
+			}
+		}
+
+		if (!found)
+		{
+			m_testCtx.getLog()
+				<< tcu::TestLog::Message
+				<< "Vertex " << vertices[ndx] << "\n"
+				<< "\tCould not find rasterization output for vertex.\n"
+				<< "\tExpected non-black pixels near " << rasterPos
+				<< tcu::TestLog::EndMessage;
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid result image");
+		}
+	}
+}
+
+void FeedbackPrimitiveTypeCase::genTransformFeedback (void)
+{
+	const glw::Functions&			gl						= m_context.getRenderContext().getFunctions();
+	const int						elementsPerPrimitive	= getNumGeneratedElementsPerPrimitive();
+	const int						feedbackPrimitives		= getNumGeneratedPrimitives();
+	const int						feedbackElements		= elementsPerPrimitive * feedbackPrimitives;
+	const std::vector<tcu::Vec4>	initialBuffer			(feedbackElements, tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f));
+
+	gl.genTransformFeedbacks(1, &m_feedbackID);
+	gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_feedbackID);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "gen transform feedback");
+
+	gl.genBuffers(1, &m_feedbackBuffer);
+	gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_feedbackBuffer);
+	gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(tcu::Vec4) * initialBuffer.size(), initialBuffer[0].getPtr(), GL_STATIC_COPY);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "gen feedback buffer");
+
+	gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_feedbackBuffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind feedback buffer");
+}
+
+static int getTriangleNumOutputPrimitives (int tessellationLevel)
+{
+	if (tessellationLevel == 1)
+		return 1;
+	else if (tessellationLevel == 2)
+		return 6;
+	else
+		return 3 * (2 + 2 * (tessellationLevel - 2)) + getTriangleNumOutputPrimitives(tessellationLevel - 2);
+}
+
+static int getTriangleNumOutputPrimitivesPoints (int tessellationLevel)
+{
+	if (tessellationLevel == 0)
+		return 1;
+	else if (tessellationLevel == 1)
+		return 3;
+	else
+		return 3 + 3 * (tessellationLevel - 1) + getTriangleNumOutputPrimitivesPoints(tessellationLevel - 2);
+}
+
+int FeedbackPrimitiveTypeCase::getNumGeneratedElementsPerPrimitive (void) const
+{
+	if (m_geometryOutputType == GEOMETRY_OUTPUT_TRIANGLES)
+		return 3;
+	else if (m_geometryOutputType == GEOMETRY_OUTPUT_LINES)
+		return 2;
+	else if (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS)
+		return 1;
+	else
+	{
+		DE_ASSERT(false);
+		return -1;
+	}
+}
+
+int FeedbackPrimitiveTypeCase::getNumGeneratedPrimitives (void) const
+{
+	return getNumTessellatedPrimitives() * getGeometryAmplification();
+}
+
+int FeedbackPrimitiveTypeCase::getNumTessellatedPrimitives (void) const
+{
+	const int tessellationLevel = 3;
+
+	if (m_tessellationPointMode == TESSELLATION_POINTMODE_OFF)
+	{
+		if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
+			return getTriangleNumOutputPrimitives(tessellationLevel);
+		else if (m_tessellationOutput == TESSELLATION_OUT_QUADS)
+			return tessellationLevel * tessellationLevel * 2; // tessellated as triangles
+		else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
+			return tessellationLevel * tessellationLevel;
+	}
+	else if (m_tessellationPointMode == TESSELLATION_POINTMODE_ON)
+	{
+		if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
+			return getTriangleNumOutputPrimitivesPoints(tessellationLevel);
+		else if (m_tessellationOutput == TESSELLATION_OUT_QUADS)
+			return (tessellationLevel + 1) * (tessellationLevel + 1);
+		else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
+			return tessellationLevel * (tessellationLevel + 1);
+	}
+
+	DE_ASSERT(false);
+	return -1;
+}
+
+int FeedbackPrimitiveTypeCase::getGeometryAmplification (void) const
+{
+	const int outputAmplification	= (m_geometryOutputType == GEOMETRY_OUTPUT_LINES) ? (2) : (1);
+	const int numInputVertices		= (m_tessellationPointMode) ? (1) : (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? (2) : (3);
+
+	return outputAmplification * numInputVertices;
+}
+
+glw::GLenum FeedbackPrimitiveTypeCase::getOutputPrimitiveGLType (void) const
+{
+	if (m_geometryOutputType == GEOMETRY_OUTPUT_TRIANGLES)
+		return GL_TRIANGLES;
+	else if (m_geometryOutputType == GEOMETRY_OUTPUT_LINES)
+		return GL_LINES;
+	else if (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS)
+		return GL_POINTS;
+	else
+	{
+		DE_ASSERT(false);
+		return -1;
+	}
+}
+
+const char* FeedbackPrimitiveTypeCase::getVertexSource (void) const
+{
+	return s_positionVertexShader;
+}
+
+const char* FeedbackPrimitiveTypeCase::getFragmentSource (void) const
+{
+	return s_whiteOutputFragmentShader;
+}
+
+std::string FeedbackPrimitiveTypeCase::getTessellationControlSource (void) const
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout(vertices = 9) out;\n"
+			"\n"
+			"uniform highp float u_innerTessellationLevel;\n"
+			"uniform highp float u_outerTessellationLevel;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	if (gl_PatchVerticesIn != 4)\n"
+			"		return;\n"
+			"\n"
+			"	// Convert input 2x2 grid to 3x3 grid\n"
+			"	float xweight = float(gl_InvocationID % 3) / 2.0f;\n"
+			"	float yweight = float(gl_InvocationID / 3) / 2.0f;\n"
+			"\n"
+			"	vec4 y0 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, yweight);\n"
+			"	vec4 y1 = mix(gl_in[2].gl_Position, gl_in[3].gl_Position, yweight);\n"
+			"\n"
+			"	gl_out[gl_InvocationID].gl_Position = mix(y0, y1, xweight);\n"
+			"\n";
+
+	if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
+		buf <<	"	gl_TessLevelOuter[0] = 3.0;\n"
+				"	gl_TessLevelOuter[1] = 3.0;\n"
+				"	gl_TessLevelOuter[2] = 3.0;\n"
+				"	gl_TessLevelInner[0] = 3.0;\n";
+	else if (m_tessellationOutput == TESSELLATION_OUT_QUADS)
+		buf <<	"	gl_TessLevelOuter[0] = 3.0;\n"
+				"	gl_TessLevelOuter[1] = 3.0;\n"
+				"	gl_TessLevelOuter[2] = 3.0;\n"
+				"	gl_TessLevelOuter[3] = 3.0;\n"
+				"	gl_TessLevelInner[0] = 3.0;\n"
+				"	gl_TessLevelInner[1] = 3.0;\n";
+	else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
+		buf <<	"	gl_TessLevelOuter[0] = 3.0;\n"
+				"	gl_TessLevelOuter[1] = 3.0;\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+std::string FeedbackPrimitiveTypeCase::getTessellationEvaluationSource (void) const
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout("
+				<< ((m_tessellationOutput == TESSELLATION_OUT_TRIANGLES) ? ("triangles") : (m_tessellationOutput == TESSELLATION_OUT_QUADS) ? ("quads") : ("isolines"))
+				<< ((m_tessellationPointMode) ? (", point_mode") : (""))
+				<< ") in;\n"
+			"\n"
+			"out highp vec4 v_tessellationCoords;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	if (gl_PatchVerticesIn != 9)\n"
+			"		return;\n"
+			"\n"
+			"	vec4 patchCentroid = vec4(0.0);\n"
+			"	for (int ndx = 0; ndx < gl_PatchVerticesIn; ++ndx)\n"
+			"		patchCentroid += gl_in[ndx].gl_Position;\n"
+			"	patchCentroid /= patchCentroid.w;\n"
+			"\n";
+
+	if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
+		buf <<	"	// map barycentric coords to 2d coords\n"
+				"	const vec3 tessDirX = vec3( 0.4,  0.4, 0.0);\n"
+				"	const vec3 tessDirY = vec3( 0.0, -0.4, 0.0);\n"
+				"	const vec3 tessDirZ = vec3(-0.4,  0.4, 0.0);\n"
+				"	gl_Position = patchCentroid + vec4(gl_TessCoord.x * tessDirX + gl_TessCoord.y * tessDirY + gl_TessCoord.z * tessDirZ, 0.0);\n";
+	else if (m_tessellationOutput == TESSELLATION_OUT_QUADS || m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
+		buf <<	"	gl_Position = patchCentroid + vec4(gl_TessCoord.x * 0.8 - 0.4, gl_TessCoord.y * 0.8 - 0.4, 0.0, 0.0);\n";
+	else
+		DE_ASSERT(false);
+
+	buf <<	"	v_tessellationCoords = vec4(gl_TessCoord, 0.0);\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string FeedbackPrimitiveTypeCase::getGeometrySource (void) const
+{
+	const char* const	geometryInputPrimitive			= (m_tessellationPointMode) ? ("points") : (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? ("lines") : ("triangles");
+	const char* const	geometryOutputPrimitive			= (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS) ? ("points") : (m_geometryOutputType == GEOMETRY_OUTPUT_LINES) ? ("line_strip") : ("triangle_strip");
+	const int			numInputVertices				= (m_tessellationPointMode) ? (1) : (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? (2) : (3);
+	const int			numSingleVertexOutputVertices	= (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS) ? (1) : (m_geometryOutputType == GEOMETRY_OUTPUT_LINES) ? (4) : (3);
+	const int			numEmitVertices					= numInputVertices * numSingleVertexOutputVertices;
+	std::ostringstream	buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n"
+			"layout(" << geometryInputPrimitive << ") in;\n"
+			"layout(" << geometryOutputPrimitive << ", max_vertices=" << numEmitVertices <<") out;\n"
+			"\n"
+			"in highp vec4 v_tessellationCoords[];\n"
+			"out highp vec4 tf_someVertexPosition;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	// Emit primitive\n"
+			"	for (int ndx = 0; ndx < gl_in.length(); ++ndx)\n"
+			"	{\n";
+
+	switch (m_geometryOutputType)
+	{
+		case GEOMETRY_OUTPUT_POINTS:
+			buf <<	"		// Draw point on vertex\n"
+					"		gl_Position = gl_in[ndx].gl_Position;\n"
+					"		tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
+					"		EmitVertex();\n";
+			break;
+
+		case GEOMETRY_OUTPUT_LINES:
+			buf <<	"		// Draw cross on vertex\n"
+					"		gl_Position = gl_in[ndx].gl_Position + vec4(-0.02, -0.02, 0.0, 0.0);\n"
+					"		tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
+					"		EmitVertex();\n"
+					"		gl_Position = gl_in[ndx].gl_Position + vec4( 0.02,  0.02, 0.0, 0.0);\n"
+					"		tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
+					"		EmitVertex();\n"
+					"		EndPrimitive();\n"
+					"		gl_Position = gl_in[ndx].gl_Position + vec4( 0.02, -0.02, 0.0, 0.0);\n"
+					"		tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
+					"		EmitVertex();\n"
+					"		gl_Position = gl_in[ndx].gl_Position + vec4(-0.02,  0.02, 0.0, 0.0);\n"
+					"		tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
+					"		EmitVertex();\n"
+					"		EndPrimitive();\n";
+			break;
+
+		case GEOMETRY_OUTPUT_TRIANGLES:
+			buf <<	"		// Draw triangle on vertex\n"
+					"		gl_Position = gl_in[ndx].gl_Position + vec4(  0.00, -0.02, 0.0, 0.0);\n"
+					"		tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
+					"		EmitVertex();\n"
+					"		gl_Position = gl_in[ndx].gl_Position + vec4(  0.02,  0.00, 0.0, 0.0);\n"
+					"		tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
+					"		EmitVertex();\n"
+					"		gl_Position = gl_in[ndx].gl_Position + vec4( -0.02,  0.00, 0.0, 0.0);\n"
+					"		tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
+					"		EmitVertex();\n"
+					"		EndPrimitive();\n";
+			break;
+
+		default:
+			DE_ASSERT(false);
+			return "";
+	}
+
+	buf <<	"	}\n"
+			"}\n";
+
+	return buf.str();
+}
+
+const char* FeedbackPrimitiveTypeCase::getTessellationOutputDescription (TessellationOutputType tessellationOutput, TessellationPointMode pointMode)
+{
+	switch (tessellationOutput)
+	{
+		case TESSELLATION_OUT_TRIANGLES:	return (pointMode) ? ("points (triangles in point mode)") : ("triangles");
+		case TESSELLATION_OUT_QUADS:		return (pointMode) ? ("points (quads in point mode)")     : ("quads");
+		case TESSELLATION_OUT_ISOLINES:		return (pointMode) ? ("points (isolines in point mode)")  : ("isolines");
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+const char* FeedbackPrimitiveTypeCase::getGeometryInputDescription (TessellationOutputType tessellationOutput, TessellationPointMode pointMode)
+{
+	switch (tessellationOutput)
+	{
+		case TESSELLATION_OUT_TRIANGLES:	return (pointMode) ? ("points") : ("triangles");
+		case TESSELLATION_OUT_QUADS:		return (pointMode) ? ("points") : ("triangles");
+		case TESSELLATION_OUT_ISOLINES:		return (pointMode) ? ("points") : ("lines");
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+const char* FeedbackPrimitiveTypeCase::getGeometryOutputDescription (GeometryOutputType geometryOutput)
+{
+	switch (geometryOutput)
+	{
+		case GEOMETRY_OUTPUT_POINTS:		return "points";
+		case GEOMETRY_OUTPUT_LINES:			return "lines";
+		case GEOMETRY_OUTPUT_TRIANGLES:		return "triangles";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+class PointSizeCase : public TestCase
+{
+public:
+	enum Flags
+	{
+		FLAG_VERTEX_SET						= 0x01,		// !< set gl_PointSize in vertex shader
+		FLAG_TESSELLATION_CONTROL_SET		= 0x02,		// !< set gl_PointSize in tessellation evaluation shader
+		FLAG_TESSELLATION_EVALUATION_SET	= 0x04,		// !< set gl_PointSize in tessellation control shader
+		FLAG_TESSELLATION_ADD				= 0x08,		// !< read and add to gl_PointSize in tessellation shader pair
+		FLAG_TESSELLATION_DONT_SET			= 0x10,		// !< don't set gl_PointSize in tessellation shader
+		FLAG_GEOMETRY_SET					= 0x20,		// !< set gl_PointSize in geometry shader
+		FLAG_GEOMETRY_ADD					= 0x40,		// !< read and add to gl_PointSize in geometry shader
+		FLAG_GEOMETRY_DONT_SET				= 0x80,		// !< don't set gl_PointSize in geometry shader
+	};
+
+						PointSizeCase					(Context& context, const char* name, const char* description, int flags);
+						~PointSizeCase					(void);
+
+	static std::string	genTestCaseName					(int flags);
+	static std::string	genTestCaseDescription			(int flags);
+
+private:
+	void				init							(void);
+	void				deinit							(void);
+	IterateResult		iterate							(void);
+
+	void				checkExtensions					(void) const;
+	void				checkPointSizeRequirements		(void) const;
+
+	void				renderTo						(tcu::Surface& dst);
+	bool				verifyImage						(const tcu::Surface& src);
+	int					getExpectedPointSize			(void) const;
+
+	std::string			genVertexSource					(void) const;
+	const char*			genFragmentSource				(void) const;
+	std::string			genTessellationControlSource	(void) const;
+	std::string			genTessellationEvaluationSource	(void) const;
+	std::string			genGeometrySource				(void) const;
+
+	enum
+	{
+		RENDER_SIZE = 32,
+	};
+
+	const int			m_flags;
+	glu::ShaderProgram*	m_program;
+};
+
+PointSizeCase::PointSizeCase (Context& context, const char* name, const char* description, int flags)
+	: TestCase	(context, name, description)
+	, m_flags	(flags)
+	, m_program	(DE_NULL)
+{
+}
+
+PointSizeCase::~PointSizeCase (void)
+{
+	deinit();
+}
+
+std::string PointSizeCase::genTestCaseName (int flags)
+{
+	std::ostringstream buf;
+
+	// join per-bit descriptions into a single string with '_' separator
+	if (flags & FLAG_VERTEX_SET)					buf																		<< "vertex_set";
+	if (flags & FLAG_TESSELLATION_CONTROL_SET)		buf << ((flags & (FLAG_TESSELLATION_CONTROL_SET-1))		? ("_") : (""))	<< "control_set";
+	if (flags & FLAG_TESSELLATION_EVALUATION_SET)	buf << ((flags & (FLAG_TESSELLATION_EVALUATION_SET-1))	? ("_") : (""))	<< "evaluation_set";
+	if (flags & FLAG_TESSELLATION_ADD)				buf << ((flags & (FLAG_TESSELLATION_ADD-1))				? ("_") : (""))	<< "control_pass_eval_add";
+	if (flags & FLAG_TESSELLATION_DONT_SET)			buf << ((flags & (FLAG_TESSELLATION_DONT_SET-1))		? ("_") : (""))	<< "eval_default";
+	if (flags & FLAG_GEOMETRY_SET)					buf << ((flags & (FLAG_GEOMETRY_SET-1))					? ("_") : (""))	<< "geometry_set";
+	if (flags & FLAG_GEOMETRY_ADD)					buf << ((flags & (FLAG_GEOMETRY_ADD-1))					? ("_") : (""))	<< "geometry_add";
+	if (flags & FLAG_GEOMETRY_DONT_SET)				buf << ((flags & (FLAG_GEOMETRY_DONT_SET-1))			? ("_") : (""))	<< "geometry_default";
+
+	return buf.str();
+}
+
+std::string PointSizeCase::genTestCaseDescription (int flags)
+{
+	std::ostringstream buf;
+
+	// join per-bit descriptions into a single string with ", " separator
+	if (flags & FLAG_VERTEX_SET)					buf																			<< "set point size in vertex shader";
+	if (flags & FLAG_TESSELLATION_CONTROL_SET)		buf << ((flags & (FLAG_TESSELLATION_CONTROL_SET-1))		? (", ") : (""))	<< "set point size in tessellation control shader";
+	if (flags & FLAG_TESSELLATION_EVALUATION_SET)	buf << ((flags & (FLAG_TESSELLATION_EVALUATION_SET-1))	? (", ") : (""))	<< "set point size in tessellation evaluation shader";
+	if (flags & FLAG_TESSELLATION_ADD)				buf << ((flags & (FLAG_TESSELLATION_ADD-1))				? (", ") : (""))	<< "add to point size in tessellation shader";
+	if (flags & FLAG_TESSELLATION_DONT_SET)			buf << ((flags & (FLAG_TESSELLATION_DONT_SET-1))		? (", ") : (""))	<< "don't set point size in tessellation evaluation shader";
+	if (flags & FLAG_GEOMETRY_SET)					buf << ((flags & (FLAG_GEOMETRY_SET-1))					? (", ") : (""))	<< "set point size in geometry shader";
+	if (flags & FLAG_GEOMETRY_ADD)					buf << ((flags & (FLAG_GEOMETRY_ADD-1))					? (", ") : (""))	<< "add to point size in geometry shader";
+	if (flags & FLAG_GEOMETRY_DONT_SET)				buf << ((flags & (FLAG_GEOMETRY_DONT_SET-1))			? (", ") : (""))	<< "don't set point size in geometry shader";
+
+	return buf.str();
+}
+
+void PointSizeCase::init (void)
+{
+	checkExtensions();
+	checkPointSizeRequirements();
+
+	// log
+
+	if (m_flags & FLAG_VERTEX_SET)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in vertex shader to 2.0." << tcu::TestLog::EndMessage;
+	if (m_flags & FLAG_TESSELLATION_CONTROL_SET)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in tessellation control shader to 4.0. (And ignoring it in evaluation)." << tcu::TestLog::EndMessage;
+	if (m_flags & FLAG_TESSELLATION_EVALUATION_SET)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in tessellation evaluation shader to 4.0." << tcu::TestLog::EndMessage;
+	if (m_flags & FLAG_TESSELLATION_ADD)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Reading point size in tessellation control shader and adding 2.0 to it in evaluation." << tcu::TestLog::EndMessage;
+	if (m_flags & FLAG_TESSELLATION_DONT_SET)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Not setting point size in tessellation evaluation shader (resulting in the default point size)." << tcu::TestLog::EndMessage;
+	if (m_flags & FLAG_GEOMETRY_SET)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in geometry shader to 6.0." << tcu::TestLog::EndMessage;
+	if (m_flags & FLAG_GEOMETRY_ADD)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Reading point size in geometry shader and adding 2.0." << tcu::TestLog::EndMessage;
+	if (m_flags & FLAG_GEOMETRY_DONT_SET)
+		m_testCtx.getLog() << tcu::TestLog::Message << "Not setting point size in geometry shader (resulting in the default point size)." << tcu::TestLog::EndMessage;
+
+	// program
+
+	{
+		glu::ProgramSources sources;
+		sources	<< glu::VertexSource(genVertexSource())
+				<< glu::FragmentSource(genFragmentSource());
+
+		if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD | FLAG_TESSELLATION_DONT_SET))
+			sources << glu::TessellationControlSource(genTessellationControlSource())
+					<< glu::TessellationEvaluationSource(genTessellationEvaluationSource());
+
+		if (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD | FLAG_GEOMETRY_DONT_SET))
+			sources << glu::GeometrySource(genGeometrySource());
+
+		m_program = new glu::ShaderProgram(m_context.getRenderContext(), sources);
+
+		m_testCtx.getLog() << *m_program;
+		if (!m_program->isOk())
+			throw tcu::TestError("failed to build program");
+	}
+}
+
+void PointSizeCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+PointSizeCase::IterateResult PointSizeCase::iterate (void)
+{
+	tcu::Surface resultImage(RENDER_SIZE, RENDER_SIZE);
+
+	renderTo(resultImage);
+
+	if (verifyImage(resultImage))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+
+	return STOP;
+}
+
+void PointSizeCase::checkExtensions (void) const
+{
+	std::vector<std::string>	requiredExtensions;
+	bool						allOk				= true;
+
+	if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD | FLAG_TESSELLATION_DONT_SET))
+		requiredExtensions.push_back("GL_EXT_tessellation_shader");
+
+	if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD))
+		requiredExtensions.push_back("GL_EXT_tessellation_point_size");
+
+	if (m_flags & (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD | FLAG_GEOMETRY_DONT_SET)))
+		requiredExtensions.push_back("GL_EXT_geometry_shader");
+
+	if (m_flags & (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD)))
+		requiredExtensions.push_back("GL_EXT_geometry_point_size");
+
+	for (int ndx = 0; ndx < (int)requiredExtensions.size(); ++ndx)
+		if (!m_context.getContextInfo().isExtensionSupported(requiredExtensions[ndx].c_str()))
+			allOk = false;
+
+	if (!allOk)
+	{
+		std::ostringstream extensionList;
+
+		for (int ndx = 0; ndx < (int)requiredExtensions.size(); ++ndx)
+		{
+			if (ndx != 0)
+				extensionList << ", ";
+			extensionList << requiredExtensions[ndx];
+		}
+
+		throw tcu::NotSupportedError("Test requires {" + extensionList.str() + "} extension(s)");
+	}
+}
+
+void PointSizeCase::checkPointSizeRequirements (void) const
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	float					aliasedSizeRange[2]	= { 0.0f, 0.0f };
+	const int				requiredSize		= getExpectedPointSize();
+
+	gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, aliasedSizeRange);
+
+	if (float(requiredSize) > aliasedSizeRange[1])
+		throw tcu::NotSupportedError("Test requires point size " + de::toString(requiredSize));
+}
+
+void PointSizeCase::renderTo (tcu::Surface& dst)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const bool				tessellationActive	= (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD | FLAG_TESSELLATION_DONT_SET)) != 0;
+	const int				positionLocation	= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	const glu::VertexArray	vao					(m_context.getRenderContext());
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering single point." << tcu::TestLog::EndMessage;
+
+	if (positionLocation == -1)
+		throw tcu::TestError("Attribute a_position location was -1");
+
+	gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	gl.bindVertexArray(*vao);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind vao");
+
+	gl.useProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
+
+	gl.vertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f);
+
+	if (tessellationActive)
+	{
+		gl.patchParameteri(GL_PATCH_VERTICES, 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
+
+		gl.drawArrays(GL_PATCHES, 0, 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
+	}
+	else
+	{
+		gl.drawArrays(GL_POINTS, 0, 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "draw points");
+	}
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+}
+
+bool PointSizeCase::verifyImage (const tcu::Surface& src)
+{
+	const bool MSAATarget	= (m_context.getRenderTarget().getNumSamples() > 1);
+	const int expectedSize	= getExpectedPointSize();
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying rendered point size. Expecting " << expectedSize << " pixels." << tcu::TestLog::EndMessage;
+	m_testCtx.getLog() << tcu::TestLog::Image("RenderImage", "Rendered image", src.getAccess());
+
+	{
+		bool		resultAreaFound	= false;
+		tcu::IVec4	resultArea;
+
+		// Find rasterization output area
+
+		for (int y = 0; y < src.getHeight(); ++y)
+		for (int x = 0; x < src.getWidth();  ++x)
+		{
+			if (!isBlack(src.getPixel(x, y)))
+			{
+				if (!resultAreaFound)
+				{
+					// first fragment
+					resultArea = tcu::IVec4(x, y, x + 1, y + 1);
+					resultAreaFound = true;
+				}
+				else
+				{
+					// union area
+					resultArea.x() = de::min(resultArea.x(), x);
+					resultArea.y() = de::min(resultArea.y(), y);
+					resultArea.z() = de::max(resultArea.z(), x+1);
+					resultArea.w() = de::max(resultArea.w(), y+1);
+				}
+			}
+		}
+
+		if (!resultAreaFound)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Verification failed, could not find any point fragments." << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		// verify area size
+		if (MSAATarget)
+		{
+			const tcu::IVec2 pointSize = resultArea.swizzle(2,3) - resultArea.swizzle(0, 1);
+
+			// MSAA: edges may be a little fuzzy
+			if (de::abs(pointSize.x() - pointSize.y()) > 1)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Rasterized point is not a square. Detected point size was " << pointSize << tcu::TestLog::EndMessage;
+				return false;
+			}
+
+			// MSAA may produce larger areas, allow one pixel larger
+			if (expectedSize != de::max(pointSize.x(), pointSize.y()) && (expectedSize+1) != de::max(pointSize.x(), pointSize.y()))
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Point size invalid, expected " << expectedSize << ", got " << de::max(pointSize.x(), pointSize.y()) << tcu::TestLog::EndMessage;
+				return false;
+			}
+		}
+		else
+		{
+			const tcu::IVec2 pointSize = resultArea.swizzle(2,3) - resultArea.swizzle(0, 1);
+
+			if (pointSize.x() != pointSize.y())
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Rasterized point is not a square. Point size was " << pointSize << tcu::TestLog::EndMessage;
+				return false;
+			}
+
+			if (pointSize.x() != expectedSize)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Point size invalid, expected " << expectedSize << ", got " << pointSize.x() << tcu::TestLog::EndMessage;
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+int PointSizeCase::getExpectedPointSize (void) const
+{
+	int addition = 0;
+
+	// geometry
+	if (m_flags & FLAG_GEOMETRY_DONT_SET)
+		return 1;
+	else if (m_flags & FLAG_GEOMETRY_SET)
+		return 6;
+	else if (m_flags & FLAG_GEOMETRY_ADD)
+		addition += 2;
+
+	// tessellation
+	if (m_flags & FLAG_TESSELLATION_EVALUATION_SET)
+		return 4 + addition;
+	else if (m_flags & FLAG_TESSELLATION_ADD)
+		addition += 2;
+	else if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_DONT_SET))
+	{
+		DE_ASSERT((m_flags & FLAG_GEOMETRY_ADD) == 0); // reading pointSize undefined
+		return 1;
+	}
+
+	// vertex
+	if (m_flags & FLAG_VERTEX_SET)
+		return 2 + addition;
+
+	// undefined
+	DE_ASSERT(false);
+	return -1;
+}
+
+std::string PointSizeCase::genVertexSource (void) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "in highp vec4 a_position;\n"
+		<< "void main ()\n"
+		<< "{\n"
+		<< "	gl_Position = a_position;\n";
+
+	if (m_flags & FLAG_VERTEX_SET)
+		buf << "	gl_PointSize = 2.0;\n";
+
+	buf	<< "}\n";
+
+	return buf.str();
+}
+
+const char* PointSizeCase::genFragmentSource (void) const
+{
+	return s_whiteOutputFragmentShader;
+}
+
+std::string PointSizeCase::genTessellationControlSource (void) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "#extension GL_EXT_tessellation_shader : require\n"
+		<< ((m_flags & FLAG_TESSELLATION_DONT_SET) ? ("") : ("#extension GL_EXT_tessellation_point_size : require\n"))
+		<< "layout(vertices = 1) out;\n"
+		<< "void main ()\n"
+		<< "{\n"
+		<< "	gl_TessLevelOuter[0] = 3.0;\n"
+		<< "	gl_TessLevelOuter[1] = 3.0;\n"
+		<< "	gl_TessLevelOuter[2] = 3.0;\n"
+		<< "	gl_TessLevelInner[0] = 3.0;\n"
+		<< "	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n";
+
+	if (m_flags & FLAG_TESSELLATION_ADD)
+		buf << "	// pass as is to eval\n"
+			<< "	gl_out[gl_InvocationID].gl_PointSize = gl_in[gl_InvocationID].gl_PointSize;\n";
+	else if (m_flags & FLAG_TESSELLATION_CONTROL_SET)
+		buf << "	// thrown away\n"
+			<< "	gl_out[gl_InvocationID].gl_PointSize = 4.0;\n";
+
+	buf	<< "}\n";
+
+	return buf.str();
+}
+
+std::string PointSizeCase::genTessellationEvaluationSource (void) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "#extension GL_EXT_tessellation_shader : require\n"
+		<< ((m_flags & FLAG_TESSELLATION_DONT_SET) ? ("") : ("#extension GL_EXT_tessellation_point_size : require\n"))
+		<< "layout(triangles, point_mode) in;\n"
+		<< "void main ()\n"
+		<< "{\n"
+		<< "	// hide all but one vertex\n"
+		<< "	if (gl_TessCoord.x < 0.99)\n"
+		<< "		gl_Position = vec4(-2.0, 0.0, 0.0, 1.0);\n"
+		<< "	else\n"
+		<< "		gl_Position = gl_in[0].gl_Position;\n";
+
+	if (m_flags & FLAG_TESSELLATION_ADD)
+		buf << "\n"
+			<< "	// add to point size\n"
+			<< "	gl_PointSize = gl_in[0].gl_PointSize + 2.0;\n";
+	else if (m_flags & FLAG_TESSELLATION_EVALUATION_SET)
+		buf << "\n"
+			<< "	// set point size\n"
+			<< "	gl_PointSize = 4.0;\n";
+
+	buf	<< "}\n";
+
+	return buf.str();
+}
+
+std::string PointSizeCase::genGeometrySource (void) const
+{
+	std::ostringstream buf;
+
+	buf	<< "#version 310 es\n"
+		<< "#extension GL_EXT_geometry_shader : require\n"
+		<< ((m_flags & FLAG_GEOMETRY_DONT_SET) ? ("") : ("#extension GL_EXT_geometry_point_size : require\n"))
+		<< "layout (points) in;\n"
+		<< "layout (points, max_vertices=1) out;\n"
+		<< "\n"
+		<< "void main ()\n"
+		<< "{\n";
+
+	if (m_flags & FLAG_GEOMETRY_SET)
+		buf	<< "	gl_Position = gl_in[0].gl_Position;\n"
+			<< "	gl_PointSize = 6.0;\n";
+	else if (m_flags & FLAG_GEOMETRY_ADD)
+		buf	<< "	gl_Position = gl_in[0].gl_Position;\n"
+			<< "	gl_PointSize = gl_in[0].gl_PointSize + 2.0;\n";
+	else if (m_flags & FLAG_GEOMETRY_DONT_SET)
+		buf	<< "	gl_Position = gl_in[0].gl_Position;\n";
+
+	buf	<< "	EmitVertex();\n"
+		<< "}\n";
+
+	return buf.str();
+}
+
+class AllowedRenderFailureException : public std::runtime_error
+{
+public:
+	AllowedRenderFailureException (const char* message) : std::runtime_error(message) { }
+};
+
+class GridRenderCase : public TestCase
+{
+public:
+	enum Flags
+	{
+		FLAG_TESSELLATION_MAX_SPEC						= 0x0001,
+		FLAG_TESSELLATION_MAX_IMPLEMENTATION			= 0x0002,
+		FLAG_GEOMETRY_MAX_SPEC							= 0x0004,
+		FLAG_GEOMETRY_MAX_IMPLEMENTATION				= 0x0008,
+		FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC				= 0x0010,
+		FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION	= 0x0020,
+
+		FLAG_GEOMETRY_SCATTER_INSTANCES					= 0x0040,
+		FLAG_GEOMETRY_SCATTER_PRIMITIVES				= 0x0080,
+		FLAG_GEOMETRY_SEPARATE_PRIMITIVES				= 0x0100, //!< if set, geometry shader outputs separate grid cells and not continuous slices
+		FLAG_GEOMETRY_SCATTER_LAYERS					= 0x0200,
+
+		FLAG_ALLOW_OUT_OF_MEMORY						= 0x0400, //!< allow draw command to set GL_OUT_OF_MEMORY
+	};
+
+						GridRenderCase					(Context& context, const char* name, const char* description, int flags);
+						~GridRenderCase					(void);
+
+private:
+	void				init							(void);
+	void				deinit							(void);
+	IterateResult		iterate							(void);
+
+	void				renderTo						(std::vector<tcu::Surface>& dst);
+	bool				verifyResultLayer				(int layerNdx, const tcu::Surface& dst);
+
+	const char*			getVertexSource					(void);
+	const char*			getFragmentSource				(void);
+	std::string			getTessellationControlSource	(int tessLevel);
+	std::string			getTessellationEvaluationSource	(int tessLevel);
+	std::string			getGeometryShaderSource			(int numPrimitives, int numInstances, int tessLevel);
+
+	enum
+	{
+		RENDER_SIZE = 256
+	};
+
+	const int			m_flags;
+
+	glu::ShaderProgram*	m_program;
+	deUint32			m_texture;
+	int					m_numLayers;
+};
+
+GridRenderCase::GridRenderCase (Context& context, const char* name, const char* description, int flags)
+	: TestCase		(context, name, description)
+	, m_flags		(flags)
+	, m_program		(DE_NULL)
+	, m_texture		(0)
+	, m_numLayers	(1)
+{
+	DE_ASSERT(((m_flags & FLAG_TESSELLATION_MAX_SPEC) == 0)			|| ((m_flags & FLAG_TESSELLATION_MAX_IMPLEMENTATION) == 0));
+	DE_ASSERT(((m_flags & FLAG_GEOMETRY_MAX_SPEC) == 0)				|| ((m_flags & FLAG_GEOMETRY_MAX_IMPLEMENTATION) == 0));
+	DE_ASSERT(((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC) == 0)	|| ((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION) == 0));
+	DE_ASSERT(((m_flags & (FLAG_GEOMETRY_SCATTER_PRIMITIVES | FLAG_GEOMETRY_SCATTER_LAYERS)) != 0) == ((m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0));
+}
+
+GridRenderCase::~GridRenderCase (void)
+{
+	deinit();
+}
+
+void GridRenderCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
+		!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
+
+	if ((m_flags & FLAG_GEOMETRY_SCATTER_LAYERS) == 0)
+	{
+		if (m_context.getRenderTarget().getWidth() < RENDER_SIZE ||
+			m_context.getRenderTarget().getHeight() < RENDER_SIZE)
+			throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE) + " or larger render target.");
+	}
+
+	// Log
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing tessellation and geometry shaders that output a large number of primitives.\n"
+		<< getDescription()
+		<< tcu::TestLog::EndMessage;
+
+	// Render target
+	if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
+	{
+		// set limits
+		m_numLayers = 8;
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Rendering to 2d texture array, numLayers = " << m_numLayers << tcu::TestLog::EndMessage;
+
+		gl.genTextures(1, &m_texture);
+		gl.bindTexture(GL_TEXTURE_2D_ARRAY, m_texture);
+		gl.texStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, RENDER_SIZE, RENDER_SIZE, m_numLayers);
+
+		gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen texture");
+	}
+
+	// Gen program
+	{
+		glu::ProgramSources	sources;
+		int					tessGenLevel = -1;
+
+		sources	<< glu::VertexSource(getVertexSource())
+				<< glu::FragmentSource(getFragmentSource());
+
+		// Tessellation limits
+		{
+			if (m_flags & FLAG_TESSELLATION_MAX_IMPLEMENTATION)
+			{
+				gl.getIntegerv(GL_MAX_TESS_GEN_LEVEL, &tessGenLevel);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "query tessellation limits");
+			}
+			else if (m_flags & FLAG_TESSELLATION_MAX_SPEC)
+			{
+				tessGenLevel = 64;
+			}
+			else
+			{
+				tessGenLevel = 5;
+			}
+
+			m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Tessellation level: " << tessGenLevel << ", mode = quad.\n"
+					<< "\tEach input patch produces " << (tessGenLevel*tessGenLevel) << " (" << (tessGenLevel*tessGenLevel*2) << " triangles)\n"
+					<< tcu::TestLog::EndMessage;
+
+			sources << glu::TessellationControlSource(getTessellationControlSource(tessGenLevel))
+					<< glu::TessellationEvaluationSource(getTessellationEvaluationSource(tessGenLevel));
+		}
+
+		// Geometry limits
+		{
+			int		geometryOutputComponents		= -1;
+			int		geometryOutputVertices			= -1;
+			int		geometryTotalOutputComponents	= -1;
+			int		geometryShaderInvocations		= -1;
+			bool	logGeometryLimits				= false;
+			bool	logInvocationLimits				= false;
+
+			if (m_flags & FLAG_GEOMETRY_MAX_IMPLEMENTATION)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Using implementation maximum geometry shader output limits." << tcu::TestLog::EndMessage;
+
+				gl.getIntegerv(GL_MAX_GEOMETRY_OUTPUT_COMPONENTS, &geometryOutputComponents);
+				gl.getIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &geometryOutputVertices);
+				gl.getIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, &geometryTotalOutputComponents);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "query geometry limits");
+
+				logGeometryLimits = true;
+			}
+			else if (m_flags & FLAG_GEOMETRY_MAX_SPEC)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Using geometry shader extension minimum maximum output limits." << tcu::TestLog::EndMessage;
+
+				geometryOutputComponents = 128;
+				geometryOutputVertices = 256;
+				geometryTotalOutputComponents = 1024;
+				logGeometryLimits = true;
+			}
+			else
+			{
+				geometryOutputComponents = 128;
+				geometryOutputVertices = 16;
+				geometryTotalOutputComponents = 1024;
+			}
+
+			if (m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION)
+			{
+				gl.getIntegerv(GL_MAX_GEOMETRY_SHADER_INVOCATIONS, &geometryShaderInvocations);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "query geometry invocation limits");
+
+				logInvocationLimits = true;
+			}
+			else if (m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC)
+			{
+				geometryShaderInvocations = 32;
+				logInvocationLimits = true;
+			}
+			else
+			{
+				geometryShaderInvocations = 4;
+			}
+
+			if (logGeometryLimits || logInvocationLimits)
+			{
+				tcu::MessageBuilder msg(&m_testCtx.getLog());
+
+				msg << "Geometry shader, targeting following limits:\n";
+
+				if (logGeometryLimits)
+					msg	<< "\tGL_MAX_GEOMETRY_OUTPUT_COMPONENTS = " << geometryOutputComponents << "\n"
+						<< "\tGL_MAX_GEOMETRY_OUTPUT_VERTICES = " << geometryOutputVertices << "\n"
+						<< "\tGL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = " << geometryTotalOutputComponents << "\n";
+
+				if (logInvocationLimits)
+					msg << "\tGL_MAX_GEOMETRY_SHADER_INVOCATIONS = " << geometryShaderInvocations;
+
+				msg << tcu::TestLog::EndMessage;
+			}
+
+			{
+				const bool	separatePrimitives			= (m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0;
+				const int	numComponentsPerVertex		= 8; // vec4 pos, vec4 color
+				int			numVerticesPerInvocation;
+				int			numPrimitivesPerInvocation;
+				int			geometryVerticesPerPrimitive;
+				int			geometryPrimitivesOutPerPrimitive;
+
+				if (separatePrimitives)
+				{
+					const int	numComponentLimit	= geometryTotalOutputComponents / (4 * numComponentsPerVertex);
+					const int	numOutputLimit		= geometryOutputVertices / 4;
+
+					numPrimitivesPerInvocation		= de::min(numComponentLimit, numOutputLimit);
+					numVerticesPerInvocation		= numPrimitivesPerInvocation * 4;
+				}
+				else
+				{
+					// If FLAG_GEOMETRY_SEPARATE_PRIMITIVES is not set, geometry shader fills a rectangle area in slices.
+					// Each slice is a triangle strip and is generated by a single shader invocation.
+					// One slice with 4 segment ends (nodes) and 3 segments:
+					//    .__.__.__.
+					//    |\ |\ |\ |
+					//    |_\|_\|_\|
+
+					const int	numSliceNodesComponentLimit	= geometryTotalOutputComponents / (2 * numComponentsPerVertex);			// each node 2 vertices
+					const int	numSliceNodesOutputLimit	= geometryOutputVertices / 2;											// each node 2 vertices
+					const int	numSliceNodes				= de::min(numSliceNodesComponentLimit, numSliceNodesOutputLimit);
+
+					numVerticesPerInvocation				= numSliceNodes * 2;
+					numPrimitivesPerInvocation				= (numSliceNodes - 1) * 2;
+				}
+
+				geometryVerticesPerPrimitive = numVerticesPerInvocation * geometryShaderInvocations;
+				geometryPrimitivesOutPerPrimitive = numPrimitivesPerInvocation * geometryShaderInvocations;
+
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Geometry shader:\n"
+					<< "\tTotal output vertex count per invocation: " << (numVerticesPerInvocation) << "\n"
+					<< "\tTotal output primitive count per invocation: " << (numPrimitivesPerInvocation) << "\n"
+					<< "\tNumber of invocations per primitive: " << geometryShaderInvocations << "\n"
+					<< "\tTotal output vertex count per input primitive: " << (geometryVerticesPerPrimitive) << "\n"
+					<< "\tTotal output primitive count per input primitive: " << (geometryPrimitivesOutPerPrimitive) << "\n"
+					<< tcu::TestLog::EndMessage;
+
+				sources	<< glu::GeometrySource(getGeometryShaderSource(numPrimitivesPerInvocation, geometryShaderInvocations, tessGenLevel));
+
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Program:\n"
+					<< "\tTotal program output vertices count per input patch: " << (tessGenLevel*tessGenLevel*2 * geometryVerticesPerPrimitive) << "\n"
+					<< "\tTotal program output primitive count per input patch: " << (tessGenLevel*tessGenLevel*2 * geometryPrimitivesOutPerPrimitive) << "\n"
+					<< tcu::TestLog::EndMessage;
+			}
+		}
+
+		m_program = new glu::ShaderProgram(m_context.getRenderContext(), sources);
+		m_testCtx.getLog() << *m_program;
+		if (!m_program->isOk())
+			throw tcu::TestError("failed to build program");
+	}
+}
+
+void GridRenderCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+
+	if (m_texture)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texture);
+		m_texture = 0;
+	}
+}
+
+GridRenderCase::IterateResult GridRenderCase::iterate (void)
+{
+	std::vector<tcu::Surface>	renderedLayers	(m_numLayers);
+	bool						allLayersOk		= true;
+
+	for (int ndx = 0; ndx < m_numLayers; ++ndx)
+		renderedLayers[ndx].setSize(RENDER_SIZE, RENDER_SIZE);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering single point at the origin. Expecting yellow and green colored grid-like image. (High-frequency grid may appear unicolored)." << tcu::TestLog::EndMessage;
+
+	try
+	{
+		renderTo(renderedLayers);
+	}
+	catch (const AllowedRenderFailureException& ex)
+	{
+		// Got accepted failure
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Could not render, reason: " << ex.what() << "\n"
+			<< "Failure is allowed."
+			<< tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+	for (int ndx = 0; ndx < m_numLayers; ++ndx)
+		allLayersOk &= verifyResultLayer(ndx, renderedLayers[ndx]);
+
+	if (allLayersOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+	return STOP;
+}
+
+void GridRenderCase::renderTo (std::vector<tcu::Surface>& dst)
+{
+	const glw::Functions&			gl					= m_context.getRenderContext().getFunctions();
+	const int						positionLocation	= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	const glu::VertexArray			vao					(m_context.getRenderContext());
+	de::MovePtr<glu::Framebuffer>	fbo;
+
+	if (positionLocation == -1)
+		throw tcu::TestError("Attribute a_position location was -1");
+
+	gl.viewport(0, 0, dst.front().getWidth(), dst.front().getHeight());
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "viewport");
+
+	gl.bindVertexArray(*vao);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind vao");
+
+	gl.useProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
+
+	gl.patchParameteri(GL_PATCH_VERTICES, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
+
+	gl.vertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f);
+
+	if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
+	{
+		// clear texture contents
+		{
+			glu::Framebuffer clearFbo(m_context.getRenderContext());
+			gl.bindFramebuffer(GL_FRAMEBUFFER, *clearFbo);
+
+			for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx)
+			{
+				gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0, layerNdx);
+				gl.clear(GL_COLOR_BUFFER_BIT);
+			}
+
+			GLU_EXPECT_NO_ERROR(gl.getError(), "clear tex contents");
+		}
+
+		// create and bind layered fbo
+
+		fbo = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext()));
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, **fbo);
+		gl.framebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");
+	}
+	else
+	{
+		// clear viewport
+		gl.clear(GL_COLOR_BUFFER_BIT);
+	}
+
+	// draw
+	{
+		glw::GLenum glerror;
+
+		gl.drawArrays(GL_PATCHES, 0, 1);
+
+		glerror = gl.getError();
+		if (glerror == GL_OUT_OF_MEMORY && (m_flags & FLAG_ALLOW_OUT_OF_MEMORY))
+			throw AllowedRenderFailureException("got GL_OUT_OF_MEMORY while drawing");
+
+		GLU_EXPECT_NO_ERROR(glerror, "draw patches");
+	}
+
+	// Read layers
+
+	if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
+	{
+		glu::Framebuffer readFbo(m_context.getRenderContext());
+		gl.bindFramebuffer(GL_FRAMEBUFFER, *readFbo);
+
+		for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx)
+		{
+			gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0, layerNdx);
+			glu::readPixels(m_context.getRenderContext(), 0, 0, dst[layerNdx].getAccess());
+			GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
+		}
+	}
+	else
+	{
+		glu::readPixels(m_context.getRenderContext(), 0, 0, dst.front().getAccess());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
+	}
+}
+
+bool GridRenderCase::verifyResultLayer (int layerNdx, const tcu::Surface& image)
+{
+	tcu::Surface	errorMask	(image.getWidth(), image.getHeight());
+	bool			foundError	= false;
+
+	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output layer " << layerNdx  << tcu::TestLog::EndMessage;
+
+	for (int y = 0; y < image.getHeight(); ++y)
+	for (int x = 0; x < image.getWidth(); ++x)
+	{
+		const int		threshold	= 8;
+		const tcu::RGBA	color		= image.getPixel(x, y);
+
+		// Color must be a linear combination of green and yellow
+		if (color.getGreen() < 255 - threshold || color.getBlue() > threshold)
+		{
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+			foundError = true;
+		}
+	}
+
+	if (!foundError)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message << "Image valid." << tcu::TestLog::EndMessage
+			<< tcu::TestLog::ImageSet("ImageVerification", "Image verification")
+			<< tcu::TestLog::Image("Result", "Rendered result", image.getAccess())
+			<< tcu::TestLog::EndImageSet;
+		return true;
+	}
+	else
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message << "Image verification failed, found invalid pixels." << tcu::TestLog::EndMessage
+			<< tcu::TestLog::ImageSet("ImageVerification", "Image verification")
+			<< tcu::TestLog::Image("Result", "Rendered result", image.getAccess())
+			<< tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
+			<< tcu::TestLog::EndImageSet;
+		return false;
+	}
+}
+
+const char* GridRenderCase::getVertexSource (void)
+{
+	return s_positionVertexShader;
+}
+
+const char* GridRenderCase::getFragmentSource (void)
+{
+	return	"#version 310 es\n"
+			"flat in mediump vec4 v_color;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = v_color;\n"
+			"}\n";
+}
+
+std::string GridRenderCase::getTessellationControlSource (int tessLevel)
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout(vertices=1) out;\n"
+			"\n"
+			"void main()\n"
+			"{\n"
+			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
+			"	gl_TessLevelOuter[0] = " << tessLevel << ".0;\n"
+			"	gl_TessLevelOuter[1] = " << tessLevel << ".0;\n"
+			"	gl_TessLevelOuter[2] = " << tessLevel << ".0;\n"
+			"	gl_TessLevelOuter[3] = " << tessLevel << ".0;\n"
+			"	gl_TessLevelInner[0] = " << tessLevel << ".0;\n"
+			"	gl_TessLevelInner[1] = " << tessLevel << ".0;\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string GridRenderCase::getTessellationEvaluationSource (int tessLevel)
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout(quads) in;\n"
+			"\n"
+			"out mediump ivec2 v_tessellationGridPosition;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n";
+
+	if (m_flags & (FLAG_GEOMETRY_SCATTER_INSTANCES | FLAG_GEOMETRY_SCATTER_PRIMITIVES | FLAG_GEOMETRY_SCATTER_LAYERS))
+		buf <<	"	// Cover only a small area in a corner. The area will be expanded in geometry shader to cover whole viewport\n"
+				"	gl_Position = vec4(gl_TessCoord.x * 0.3 - 1.0, gl_TessCoord.y * 0.3 - 1.0, 0.0, 1.0);\n";
+	else
+		buf <<	"	// Fill the whole viewport\n"
+				"	gl_Position = vec4(gl_TessCoord.x * 2.0 - 1.0, gl_TessCoord.y * 2.0 - 1.0, 0.0, 1.0);\n";
+
+	buf <<	"	// Calculate position in tessellation grid\n"
+			"	v_tessellationGridPosition = ivec2(round(gl_TessCoord.xy * float(" << tessLevel << ")));\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string GridRenderCase::getGeometryShaderSource (int numPrimitives, int numInstances, int tessLevel)
+{
+	std::ostringstream buf;
+
+	buf	<<	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n"
+			"layout(triangles, invocations=" << numInstances << ") in;\n"
+			"layout(triangle_strip, max_vertices=" << ((m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) ? (4 * numPrimitives) : (numPrimitives + 2)) << ") out;\n"
+			"\n"
+			"in mediump ivec2 v_tessellationGridPosition[];\n"
+			"flat out highp vec4 v_color;\n"
+			"\n"
+			"void main ()\n"
+			"{\n"
+			"	const float equalThreshold = 0.001;\n"
+			"	const float gapOffset = 0.0001; // subdivision performed by the geometry shader might produce gaps. Fill potential gaps by enlarging the output slice a little.\n"
+			"\n"
+			"	// Input triangle is generated from an axis-aligned rectangle by splitting it in half\n"
+			"	// Original rectangle can be found by finding the bounding AABB of the triangle\n"
+			"	vec4 aabb = vec4(min(gl_in[0].gl_Position.x, min(gl_in[1].gl_Position.x, gl_in[2].gl_Position.x)),\n"
+			"	                 min(gl_in[0].gl_Position.y, min(gl_in[1].gl_Position.y, gl_in[2].gl_Position.y)),\n"
+			"	                 max(gl_in[0].gl_Position.x, max(gl_in[1].gl_Position.x, gl_in[2].gl_Position.x)),\n"
+			"	                 max(gl_in[0].gl_Position.y, max(gl_in[1].gl_Position.y, gl_in[2].gl_Position.y)));\n"
+			"\n"
+			"	// Location in tessellation grid\n"
+			"	ivec2 gridPosition = ivec2(min(v_tessellationGridPosition[0], min(v_tessellationGridPosition[1], v_tessellationGridPosition[2])));\n"
+			"\n"
+			"	// Which triangle of the two that split the grid cell\n"
+			"	int numVerticesOnBottomEdge = 0;\n"
+			"	for (int ndx = 0; ndx < 3; ++ndx)\n"
+			"		if (abs(gl_in[ndx].gl_Position.y - aabb.w) < equalThreshold)\n"
+			"			++numVerticesOnBottomEdge;\n"
+			"	bool isBottomTriangle = numVerticesOnBottomEdge == 2;\n"
+			"\n";
+
+	if (m_flags & FLAG_GEOMETRY_SCATTER_PRIMITIVES)
+	{
+		// scatter primitives
+		buf <<	"	// Draw grid cells\n"
+				"	int inputTriangleNdx = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n"
+				"	for (int ndx = 0; ndx < " << numPrimitives << "; ++ndx)\n"
+				"	{\n"
+				"		ivec2 dstGridSize = ivec2(" << tessLevel << " * " << numPrimitives << ", 2 * " << tessLevel << " * " << numInstances << ");\n"
+				"		ivec2 dstGridNdx = ivec2(" << tessLevel << " * ndx + gridPosition.x, " << tessLevel << " * inputTriangleNdx + 2 * gridPosition.y + ndx * 127) % dstGridSize;\n"
+				"		vec4 dstArea;\n"
+				"		dstArea.x = float(dstGridNdx.x) / float(dstGridSize.x) * 2.0 - 1.0 - gapOffset;\n"
+				"		dstArea.y = float(dstGridNdx.y) / float(dstGridSize.y) * 2.0 - 1.0 - gapOffset;\n"
+				"		dstArea.z = float(dstGridNdx.x+1) / float(dstGridSize.x) * 2.0 - 1.0 + gapOffset;\n"
+				"		dstArea.w = float(dstGridNdx.y+1) / float(dstGridSize.y) * 2.0 - 1.0 + gapOffset;\n"
+				"\n"
+				"		vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"		vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
+				"		vec4 outputColor = (((dstGridNdx.y + dstGridNdx.x) % 2) == 0) ? (green) : (yellow);\n"
+				"\n"
+				"		gl_Position = vec4(dstArea.x, dstArea.y, 0.0, 1.0);\n"
+				"		v_color = outputColor;\n"
+				"		EmitVertex();\n"
+				"\n"
+				"		gl_Position = vec4(dstArea.x, dstArea.w, 0.0, 1.0);\n"
+				"		v_color = outputColor;\n"
+				"		EmitVertex();\n"
+				"\n"
+				"		gl_Position = vec4(dstArea.z, dstArea.y, 0.0, 1.0);\n"
+				"		v_color = outputColor;\n"
+				"		EmitVertex();\n"
+				"\n"
+				"		gl_Position = vec4(dstArea.z, dstArea.w, 0.0, 1.0);\n"
+				"		v_color = outputColor;\n"
+				"		EmitVertex();\n"
+				"		EndPrimitive();\n"
+				"	}\n";
+	}
+	else if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
+	{
+		// Number of subrectangle instances = num layers
+		DE_ASSERT(m_numLayers == numInstances * 2);
+
+		buf <<	"	// Draw grid cells, send each primitive to a separate layer\n"
+				"	int baseLayer = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n"
+				"	for (int ndx = 0; ndx < " << numPrimitives << "; ++ndx)\n"
+				"	{\n"
+				"		ivec2 dstGridSize = ivec2(" << tessLevel << " * " << numPrimitives << ", " << tessLevel << ");\n"
+				"		ivec2 dstGridNdx = ivec2((gridPosition.x * " << numPrimitives << " * 7 + ndx)*13, (gridPosition.y * 127 + ndx) * 19) % dstGridSize;\n"
+				"		vec4 dstArea;\n"
+				"		dstArea.x = float(dstGridNdx.x) / float(dstGridSize.x) * 2.0 - 1.0 - gapOffset;\n"
+				"		dstArea.y = float(dstGridNdx.y) / float(dstGridSize.y) * 2.0 - 1.0 - gapOffset;\n"
+				"		dstArea.z = float(dstGridNdx.x+1) / float(dstGridSize.x) * 2.0 - 1.0 + gapOffset;\n"
+				"		dstArea.w = float(dstGridNdx.y+1) / float(dstGridSize.y) * 2.0 - 1.0 + gapOffset;\n"
+				"\n"
+				"		vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"		vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
+				"		vec4 outputColor = (((dstGridNdx.y + dstGridNdx.x) % 2) == 0) ? (green) : (yellow);\n"
+				"\n"
+				"		gl_Position = vec4(dstArea.x, dstArea.y, 0.0, 1.0);\n"
+				"		v_color = outputColor;\n"
+				"		gl_Layer = ((baseLayer + ndx) * 11) % " << m_numLayers << ";\n"
+				"		EmitVertex();\n"
+				"\n"
+				"		gl_Position = vec4(dstArea.x, dstArea.w, 0.0, 1.0);\n"
+				"		v_color = outputColor;\n"
+				"		gl_Layer = ((baseLayer + ndx) * 11) % " << m_numLayers << ";\n"
+				"		EmitVertex();\n"
+				"\n"
+				"		gl_Position = vec4(dstArea.z, dstArea.y, 0.0, 1.0);\n"
+				"		v_color = outputColor;\n"
+				"		gl_Layer = ((baseLayer + ndx) * 11) % " << m_numLayers << ";\n"
+				"		EmitVertex();\n"
+				"\n"
+				"		gl_Position = vec4(dstArea.z, dstArea.w, 0.0, 1.0);\n"
+				"		v_color = outputColor;\n"
+				"		gl_Layer = ((baseLayer + ndx) * 11) % " << m_numLayers << ";\n"
+				"		EmitVertex();\n"
+				"		EndPrimitive();\n"
+				"	}\n";
+	}
+	else
+	{
+		if (m_flags & FLAG_GEOMETRY_SCATTER_INSTANCES)
+		{
+			buf <<	"	// Scatter slices\n"
+					"	int inputTriangleNdx = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n"
+					"	ivec2 srcSliceNdx = ivec2(gridPosition.x, gridPosition.y * " << (numInstances*2) << " + inputTriangleNdx);\n"
+					"	ivec2 dstSliceNdx = ivec2(7 * srcSliceNdx.x, 127 * srcSliceNdx.y) % ivec2(" << tessLevel << ", " << tessLevel << " * " << (numInstances*2) << ");\n"
+					"\n"
+					"	// Draw slice to the dstSlice slot\n"
+					"	vec4 outputSliceArea;\n"
+					"	outputSliceArea.x = float(dstSliceNdx.x) / float(" << tessLevel << ") * 2.0 - 1.0 - gapOffset;\n"
+					"	outputSliceArea.y = float(dstSliceNdx.y) / float(" << (tessLevel * numInstances * 2) << ") * 2.0 - 1.0 - gapOffset;\n"
+					"	outputSliceArea.z = float(dstSliceNdx.x+1) / float(" << tessLevel << ") * 2.0 - 1.0 + gapOffset;\n"
+					"	outputSliceArea.w = float(dstSliceNdx.y+1) / float(" << (tessLevel * numInstances * 2) << ") * 2.0 - 1.0 + gapOffset;\n";
+		}
+		else
+		{
+			buf <<	"	// Fill the input area with slices\n"
+					"	// Upper triangle produces slices only to the upper half of the quad and vice-versa\n"
+					"	float triangleOffset = (isBottomTriangle) ? ((aabb.w + aabb.y) / 2.0) : (aabb.y);\n"
+					"	// Each slice is a invocation\n"
+					"	float sliceHeight = (aabb.w - aabb.y) / float(2 * " << numInstances << ");\n"
+					"	float invocationOffset = float(gl_InvocationID) * sliceHeight;\n"
+					"\n"
+					"	vec4 outputSliceArea;\n"
+					"	outputSliceArea.x = aabb.x - gapOffset;\n"
+					"	outputSliceArea.y = triangleOffset + invocationOffset - gapOffset;\n"
+					"	outputSliceArea.z = aabb.z + gapOffset;\n"
+					"	outputSliceArea.w = triangleOffset + invocationOffset + sliceHeight + gapOffset;\n";
+		}
+
+		buf <<	"\n"
+				"	// Draw slice\n"
+				"	for (int ndx = 0; ndx < " << ((numPrimitives+2)/2) << "; ++ndx)\n"
+				"	{\n"
+				"		vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
+				"		vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
+				"		vec4 outputColor = (((gl_InvocationID + ndx) % 2) == 0) ? (green) : (yellow);\n"
+				"		float xpos = mix(outputSliceArea.x, outputSliceArea.z, float(ndx) / float(" << (numPrimitives/2) << "));\n"
+				"\n"
+				"		gl_Position = vec4(xpos, outputSliceArea.y, 0.0, 1.0);\n"
+				"		v_color = outputColor;\n"
+				"		EmitVertex();\n"
+				"\n"
+				"		gl_Position = vec4(xpos, outputSliceArea.w, 0.0, 1.0);\n"
+				"		v_color = outputColor;\n"
+				"		EmitVertex();\n"
+				"	}\n";
+	}
+
+	buf <<	"}\n";
+
+	return buf.str();
+}
+
+class FeedbackRecordVariableSelectionCase : public TestCase
+{
+public:
+						FeedbackRecordVariableSelectionCase		(Context& context, const char* name, const char* description);
+						~FeedbackRecordVariableSelectionCase	(void);
+
+private:
+	void				init									(void);
+	void				deinit									(void);
+	IterateResult		iterate									(void);
+
+	const char*			getVertexSource							(void);
+	const char*			getFragmentSource						(void);
+	const char*			getTessellationControlSource			(void);
+	const char*			getTessellationEvaluationSource			(void);
+	const char*			getGeometrySource						(void);
+
+	glu::ShaderProgram*	m_program;
+	deUint32			m_xfbBuf;
+};
+
+FeedbackRecordVariableSelectionCase::FeedbackRecordVariableSelectionCase (Context& context, const char* name, const char* description)
+	: TestCase	(context, name, description)
+	, m_program	(DE_NULL)
+	, m_xfbBuf	(0)
+{
+}
+
+FeedbackRecordVariableSelectionCase::~FeedbackRecordVariableSelectionCase (void)
+{
+	deinit();
+}
+
+void FeedbackRecordVariableSelectionCase::init (void)
+{
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
+		!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Declaring multiple output variables with the same name in multiple shader stages. Capturing the value of the varying using transform feedback." << tcu::TestLog::EndMessage;
+
+	// gen feedback buffer fit for 1 triangle (4 components)
+	{
+		static const tcu::Vec4 initialData[3] =
+		{
+			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, -1.0f),
+		};
+
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Creating buffer for transform feedback. Allocating storage for one triangle. Filling with -1.0" << tcu::TestLog::EndMessage;
+
+		gl.genBuffers(1, &m_xfbBuf);
+		gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_xfbBuf);
+		gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, (int)(sizeof(tcu::Vec4[3])), initialData, GL_DYNAMIC_READ);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "gen xfb buf");
+	}
+
+	// gen shader
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+																	 << glu::VertexSource(getVertexSource())
+																	 << glu::FragmentSource(getFragmentSource())
+																	 << glu::TessellationControlSource(getTessellationControlSource())
+																	 << glu::TessellationEvaluationSource(getTessellationEvaluationSource())
+																	 << glu::GeometrySource(getGeometrySource())
+																	 << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)
+																	 << glu::TransformFeedbackVarying("tf_feedback"));
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build program");
+}
+
+void FeedbackRecordVariableSelectionCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+
+	if (m_xfbBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_xfbBuf);
+		m_xfbBuf = 0;
+	}
+}
+
+FeedbackRecordVariableSelectionCase::IterateResult FeedbackRecordVariableSelectionCase::iterate (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	const int				posLoc	= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	const glu::VertexArray	vao		(m_context.getRenderContext());
+
+	if (posLoc == -1)
+		throw tcu::TestError("a_position attribute location was -1");
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering a patch of size 1." << tcu::TestLog::EndMessage;
+
+	// Render and feed back
+
+	gl.viewport(0, 0, 1, 1);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
+
+	gl.bindVertexArray(*vao);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bindVertexArray");
+
+	gl.vertexAttrib4f(posLoc, 0.0f, 0.0f, 0.0f, 1.0f);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "vertexAttrib4f");
+
+	gl.useProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
+
+	gl.patchParameteri(GL_PATCH_VERTICES, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
+
+	gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_xfbBuf);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind xfb buf");
+
+	gl.beginTransformFeedback(GL_TRIANGLES);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "beginTransformFeedback");
+
+	gl.drawArrays(GL_PATCHES, 0, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");
+
+	gl.endTransformFeedback();
+	GLU_EXPECT_NO_ERROR(gl.getError(), "beginTransformFeedback");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying the value of tf_feedback using transform feedback, expecting (3.0, 3.0, 3.0, 3.0)." << tcu::TestLog::EndMessage;
+
+	// Read back result (one triangle)
+	{
+		tcu::Vec4	feedbackValues[3];
+		const void* mapPtr				= gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, (int)sizeof(feedbackValues), GL_MAP_READ_BIT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "mapBufferRange");
+
+		if (mapPtr == DE_NULL)
+			throw tcu::TestError("mapBufferRange returned null");
+
+		deMemcpy(feedbackValues, mapPtr, sizeof(feedbackValues));
+
+		if (gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER) != GL_TRUE)
+			throw tcu::TestError("unmapBuffer did not return TRUE");
+
+		for (int ndx = 0; ndx < 3; ++ndx)
+		{
+			if (!tcu::boolAll(tcu::lessThan(tcu::abs(feedbackValues[ndx] - tcu::Vec4(3.0f)), tcu::Vec4(0.001f))))
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Feedback vertex " << ndx << ": expected (3.0, 3.0, 3.0, 3.0), got " << feedbackValues[ndx] << tcu::TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected feedback results");
+			}
+		}
+	}
+
+	return STOP;
+}
+
+const char* FeedbackRecordVariableSelectionCase::getVertexSource (void)
+{
+	return	"#version 310 es\n"
+			"in highp vec4 a_position;\n"
+			"out highp vec4 tf_feedback;\n"
+			"void main()\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	tf_feedback = vec4(1.0, 1.0, 1.0, 1.0);\n"
+			"}\n";
+}
+
+const char* FeedbackRecordVariableSelectionCase::getFragmentSource (void)
+{
+	return s_whiteOutputFragmentShader;
+}
+
+const char* FeedbackRecordVariableSelectionCase::getTessellationControlSource (void)
+{
+	return	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout(vertices=3) out;\n"
+			"void main()\n"
+			"{\n"
+			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
+			"	gl_TessLevelOuter[0] = 1.0;\n"
+			"	gl_TessLevelOuter[1] = 1.0;\n"
+			"	gl_TessLevelOuter[2] = 1.0;\n"
+			"	gl_TessLevelInner[0] = 1.0;\n"
+			"}\n";
+}
+
+const char* FeedbackRecordVariableSelectionCase::getTessellationEvaluationSource (void)
+{
+	return	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout(triangles) in;\n"
+			"out highp vec4 tf_feedback;\n"
+			"void main()\n"
+			"{\n"
+			"	gl_Position = gl_in[0].gl_Position * gl_TessCoord.x + gl_in[1].gl_Position * gl_TessCoord.y + gl_in[2].gl_Position * gl_TessCoord.z;\n"
+			"	tf_feedback = vec4(2.0, 2.0, 2.0, 2.0);\n"
+			"}\n";
+}
+
+const char* FeedbackRecordVariableSelectionCase::getGeometrySource (void)
+{
+	return	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n"
+			"layout (triangles) in;\n"
+			"layout (triangle_strip, max_vertices=3) out;\n"
+			"out highp vec4 tf_feedback;\n"
+			"void main()\n"
+			"{\n"
+			"	for (int ndx = 0; ndx < 3; ++ndx)\n"
+			"	{\n"
+			"		gl_Position = gl_in[ndx].gl_Position + vec4(float(ndx), float(ndx)*float(ndx), 0.0, 0.0);\n"
+			"		tf_feedback = vec4(3.0, 3.0, 3.0, 3.0);\n"
+			"		EmitVertex();\n"
+			"	}\n"
+			"	EndPrimitive();\n"
+			"}\n";
+}
+
+} // anonymous
+
+TessellationGeometryInteractionTests::TessellationGeometryInteractionTests (Context& context)
+	: TestCaseGroup(context, "tessellation_geometry_interaction", "Tessellation and geometry shader interaction tests")
+{
+}
+
+TessellationGeometryInteractionTests::~TessellationGeometryInteractionTests (void)
+{
+}
+
+void TessellationGeometryInteractionTests::init (void)
+{
+	tcu::TestCaseGroup* const renderGroup		= new tcu::TestCaseGroup(m_testCtx, "render",		"Various render tests");
+	tcu::TestCaseGroup* const feedbackGroup		= new tcu::TestCaseGroup(m_testCtx, "feedback",		"Test transform feedback");
+	tcu::TestCaseGroup* const pointSizeGroup	= new tcu::TestCaseGroup(m_testCtx, "point_size",	"Test point size");
+
+	addChild(renderGroup);
+	addChild(feedbackGroup);
+	addChild(pointSizeGroup);
+
+	// .render
+	{
+		tcu::TestCaseGroup* const passthroughGroup	= new tcu::TestCaseGroup(m_testCtx, "passthrough",	"Render various types with either passthrough geometry or tessellation shader");
+		tcu::TestCaseGroup* const limitGroup		= new tcu::TestCaseGroup(m_testCtx, "limits",		"Render with properties near their limits");
+		tcu::TestCaseGroup* const scatterGroup		= new tcu::TestCaseGroup(m_testCtx, "scatter",		"Scatter output primitives");
+
+		renderGroup->addChild(passthroughGroup);
+		renderGroup->addChild(limitGroup);
+		renderGroup->addChild(scatterGroup);
+
+		// .passthrough
+		{
+			// tessellate_tris_passthrough_geometry_no_change
+			// tessellate_quads_passthrough_geometry_no_change
+			// tessellate_isolines_passthrough_geometry_no_change
+			passthroughGroup->addChild(new IdentityGeometryShaderCase(m_context, "tessellate_tris_passthrough_geometry_no_change",		"Passthrough geometry shader has no effect", IdentityGeometryShaderCase::CASE_TRIANGLES));
+			passthroughGroup->addChild(new IdentityGeometryShaderCase(m_context, "tessellate_quads_passthrough_geometry_no_change",		"Passthrough geometry shader has no effect", IdentityGeometryShaderCase::CASE_QUADS));
+			passthroughGroup->addChild(new IdentityGeometryShaderCase(m_context, "tessellate_isolines_passthrough_geometry_no_change",	"Passthrough geometry shader has no effect", IdentityGeometryShaderCase::CASE_ISOLINES));
+
+			// passthrough_tessellation_geometry_shade_triangles_no_change
+			// passthrough_tessellation_geometry_shade_lines_no_change
+			passthroughGroup->addChild(new IdentityTessellationShaderCase(m_context, "passthrough_tessellation_geometry_shade_triangles_no_change",	"Passthrough tessellation shader has no effect", IdentityTessellationShaderCase::CASE_TRIANGLES));
+			passthroughGroup->addChild(new IdentityTessellationShaderCase(m_context, "passthrough_tessellation_geometry_shade_lines_no_change",		"Passthrough tessellation shader has no effect", IdentityTessellationShaderCase::CASE_ISOLINES));
+		}
+
+		// .limits
+		{
+			static const struct LimitCaseDef
+			{
+				const char*	name;
+				const char*	desc;
+				int			flags;
+			} cases[] =
+			{
+				// Test single limit
+				{
+					"output_required_max_tessellation",
+					"Minimum maximum tessellation level",
+					GridRenderCase::FLAG_TESSELLATION_MAX_SPEC
+				},
+				{
+					"output_implementation_max_tessellation",
+					"Maximum tessellation level supported by the implementation",
+					GridRenderCase::FLAG_TESSELLATION_MAX_IMPLEMENTATION
+				},
+				{
+					"output_required_max_geometry",
+					"Output minimum maximum number of vertices the geometry shader",
+					GridRenderCase::FLAG_GEOMETRY_MAX_SPEC
+				},
+				{
+					"output_implementation_max_geometry",
+					"Output maximum number of vertices in the geometry shader supported by the implementation",
+					GridRenderCase::FLAG_GEOMETRY_MAX_IMPLEMENTATION
+				},
+				{
+					"output_required_max_invocations",
+					"Minimum maximum number of geometry shader invocations",
+					GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC
+				},
+				{
+					"output_implementation_max_invocations",
+					"Maximum number of geometry shader invocations supported by the implementation",
+					GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION
+				},
+			};
+
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ++ndx)
+				limitGroup->addChild(new GridRenderCase(m_context, cases[ndx].name, cases[ndx].desc, cases[ndx].flags));
+		}
+
+		// .scatter
+		{
+			scatterGroup->addChild(new GridRenderCase(m_context,
+													  "geometry_scatter_instances",
+													  "Each geometry shader instance outputs its primitives far from other instances of the same execution",
+													  GridRenderCase::FLAG_GEOMETRY_SCATTER_INSTANCES));
+			scatterGroup->addChild(new GridRenderCase(m_context,
+													  "geometry_scatter_primitives",
+													  "Each geometry shader instance outputs its primitives far from other primitives of the same instance",
+													  GridRenderCase::FLAG_GEOMETRY_SCATTER_PRIMITIVES | GridRenderCase::FLAG_GEOMETRY_SEPARATE_PRIMITIVES));
+			scatterGroup->addChild(new GridRenderCase(m_context,
+													  "geometry_scatter_layers",
+													  "Each geometry shader instance outputs its primitives to multiple layers and far from other primitives of the same instance",
+													  GridRenderCase::FLAG_GEOMETRY_SCATTER_LAYERS | GridRenderCase::FLAG_GEOMETRY_SEPARATE_PRIMITIVES));
+		}
+	}
+
+	// .feedback
+	{
+		static const struct PrimitiveCaseConfig
+		{
+			const char*											name;
+			const char*											description;
+			FeedbackPrimitiveTypeCase::TessellationOutputType	tessellationOutput;
+			FeedbackPrimitiveTypeCase::TessellationPointMode	tessellationPointMode;
+			FeedbackPrimitiveTypeCase::GeometryOutputType		geometryOutputType;
+		} caseConfigs[] =
+		{
+			// tess output triangles -> geo input triangles, output points
+			{
+				"tessellation_output_triangles_geometry_output_points",
+				"Tessellation outputs triangles, geometry outputs points",
+				FeedbackPrimitiveTypeCase::TESSELLATION_OUT_TRIANGLES,
+				FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_OFF,
+				FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_POINTS
+			},
+
+			// tess output quads <-> geo input triangles, output points
+			{
+				"tessellation_output_quads_geometry_output_points",
+				"Tessellation outputs quads, geometry outputs points",
+				FeedbackPrimitiveTypeCase::TESSELLATION_OUT_QUADS,
+				FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_OFF,
+				FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_POINTS
+			},
+
+			// tess output isolines <-> geo input lines, output points
+			{
+				"tessellation_output_isolines_geometry_output_points",
+				"Tessellation outputs isolines, geometry outputs points",
+				FeedbackPrimitiveTypeCase::TESSELLATION_OUT_ISOLINES,
+				FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_OFF,
+				FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_POINTS
+			},
+
+			// tess output triangles, point_mode <-> geo input points, output lines
+			{
+				"tessellation_output_triangles_point_mode_geometry_output_lines",
+				"Tessellation outputs triangles in point mode, geometry outputs lines",
+				FeedbackPrimitiveTypeCase::TESSELLATION_OUT_TRIANGLES,
+				FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_ON,
+				FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_LINES
+			},
+
+			// tess output quads, point_mode <-> geo input points, output lines
+			{
+				"tessellation_output_quads_point_mode_geometry_output_lines",
+				"Tessellation outputs quads in point mode, geometry outputs lines",
+				FeedbackPrimitiveTypeCase::TESSELLATION_OUT_QUADS,
+				FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_ON,
+				FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_LINES
+			},
+
+			// tess output isolines, point_mode <-> geo input points, output triangles
+			{
+				"tessellation_output_isolines_point_mode_geometry_output_triangles",
+				"Tessellation outputs isolines in point mode, geometry outputs triangles",
+				FeedbackPrimitiveTypeCase::TESSELLATION_OUT_ISOLINES,
+				FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_ON,
+				FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_TRIANGLES
+			},
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(caseConfigs); ++ndx)
+		{
+			feedbackGroup->addChild(new FeedbackPrimitiveTypeCase(m_context,
+																  caseConfigs[ndx].name,
+																  caseConfigs[ndx].description,
+																  caseConfigs[ndx].tessellationOutput,
+																  caseConfigs[ndx].tessellationPointMode,
+																  caseConfigs[ndx].geometryOutputType));
+		}
+
+		feedbackGroup->addChild(new FeedbackRecordVariableSelectionCase(m_context, "record_variable_selection", "Record a variable that has been declared as an output variable in multiple shader stages"));
+	}
+
+	// .point_size
+	{
+		static const int caseFlags[] =
+		{
+			PointSizeCase::FLAG_VERTEX_SET,
+												PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET,
+																										PointSizeCase::FLAG_GEOMETRY_SET,
+			PointSizeCase::FLAG_VERTEX_SET	|	PointSizeCase::FLAG_TESSELLATION_CONTROL_SET,
+			PointSizeCase::FLAG_VERTEX_SET	|	PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET,
+			PointSizeCase::FLAG_VERTEX_SET	|	PointSizeCase::FLAG_TESSELLATION_DONT_SET,
+			PointSizeCase::FLAG_VERTEX_SET 	|															PointSizeCase::FLAG_GEOMETRY_SET,
+			PointSizeCase::FLAG_VERTEX_SET	|	PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET		|	PointSizeCase::FLAG_GEOMETRY_SET,
+			PointSizeCase::FLAG_VERTEX_SET	|	PointSizeCase::FLAG_TESSELLATION_ADD				|	PointSizeCase::FLAG_GEOMETRY_ADD,
+			PointSizeCase::FLAG_VERTEX_SET	|	PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET		|	PointSizeCase::FLAG_GEOMETRY_DONT_SET,
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(caseFlags); ++ndx)
+		{
+			const std::string name = PointSizeCase::genTestCaseName(caseFlags[ndx]);
+			const std::string desc = PointSizeCase::genTestCaseDescription(caseFlags[ndx]);
+
+			pointSizeGroup->addChild(new PointSizeCase(m_context, name.c_str(), desc.c_str(), caseFlags[ndx]));
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fTessellationGeometryInteractionTests.hpp b/modules/gles31/functional/es31fTessellationGeometryInteractionTests.hpp
new file mode 100644
index 0000000..e45fb53
--- /dev/null
+++ b/modules/gles31/functional/es31fTessellationGeometryInteractionTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FTESSELLATIONGEOMETRYINTERACTIONTESTS_HPP
+#define _ES31FTESSELLATIONGEOMETRYINTERACTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tessellation and geometry shader interaction tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class TessellationGeometryInteractionTests : public TestCaseGroup
+{
+public:
+											TessellationGeometryInteractionTests	(Context& context);
+											~TessellationGeometryInteractionTests	(void);
+
+	void									init									(void);
+
+private:
+											TessellationGeometryInteractionTests	(const TessellationGeometryInteractionTests& other);
+	TessellationGeometryInteractionTests&	operator=								(const TessellationGeometryInteractionTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FTESSELLATIONGEOMETRYINTERACTIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fTessellationTests.cpp b/modules/gles31/functional/es31fTessellationTests.cpp
new file mode 100644
index 0000000..518336d
--- /dev/null
+++ b/modules/gles31/functional/es31fTessellationTests.cpp
@@ -0,0 +1,6594 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tessellation Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fTessellationTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluRenderContext.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluStrUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "gluVarType.hpp"
+#include "gluVarTypeUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuImageIO.hpp"
+#include "tcuResource.hpp"
+#include "tcuImageCompare.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deSharedPtr.hpp"
+#include "deString.h"
+#include "deMath.h"
+
+#include "glwEnums.hpp"
+#include "glwDefs.hpp"
+#include "glwFunctions.hpp"
+
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <functional>
+#include <set>
+#include <limits>
+
+using glu::ShaderProgram;
+using glu::RenderContext;
+using tcu::RenderTarget;
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using de::Random;
+using de::SharedPtr;
+
+using std::vector;
+using std::string;
+
+using namespace glw; // For GL types.
+
+namespace deqp
+{
+
+using gls::TextureTestUtil::RandomViewport;
+
+namespace gles31
+{
+namespace Functional
+{
+
+enum
+{
+	MINIMUM_MAX_TESS_GEN_LEVEL = 64 //!< GL-defined minimum for GL_MAX_TESS_GEN_LEVEL.
+};
+
+static inline bool vec3XLessThan (const Vec3& a, const Vec3& b) { return a.x() < b.x(); }
+
+template <typename IterT>
+static string elemsStr (const IterT& begin, const IterT& end, int wrapLengthParam = 0, int numIndentationSpaces = 0)
+{
+	const string	baseIndentation	= string(numIndentationSpaces, ' ');
+	const string	deepIndentation	= baseIndentation + string(4, ' ');
+	const int		wrapLength		= wrapLengthParam > 0 ? wrapLengthParam : std::numeric_limits<int>::max();
+	const int		length			= (int)std::distance(begin, end);
+	string			result;
+
+	if (length > wrapLength)
+		result += "(amount: " + de::toString(length) + ") ";
+	result += string() + "{" + (length > wrapLength ? "\n"+deepIndentation : " ");
+
+	{
+		int index = 0;
+		for (IterT it = begin; it != end; ++it)
+		{
+			if (it != begin)
+				result += string() + ", " + (index % wrapLength == 0 ? "\n"+deepIndentation : "");
+			result += de::toString(*it);
+			index++;
+		}
+
+		result += length > wrapLength ? "\n"+baseIndentation : " ";
+	}
+
+	result += "}";
+	return result;
+}
+
+template <typename ContainerT>
+static string containerStr (const ContainerT& c, int wrapLengthParam = 0, int numIndentationSpaces = 0)
+{
+	return elemsStr(c.begin(), c.end(), wrapLengthParam, numIndentationSpaces);
+}
+
+template <typename T, int N>
+static string arrayStr (const T (&arr)[N], int wrapLengthParam = 0, int numIndentationSpaces = 0)
+{
+	return elemsStr(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr), wrapLengthParam, numIndentationSpaces);
+}
+
+template <typename T, int N>
+static T arrayMax (const T (&arr)[N])
+{
+	return *std::max_element(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr));
+}
+
+template <typename T, typename MembT>
+static vector<MembT> members (const vector<T>& objs, MembT T::* membP)
+{
+	vector<MembT> result(objs.size());
+	for (int i = 0; i < (int)objs.size(); i++)
+		result[i] = objs[i].*membP;
+	return result;
+}
+
+template <typename T, int N>
+static vector<T> arrayToVector (const T (&arr)[N])
+{
+	return vector<T>(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr));
+}
+
+template <typename ContainerT, typename T>
+static inline bool contains (const ContainerT& c, const T& key)
+{
+	return c.find(key) != c.end();
+}
+
+template <int Size>
+static inline tcu::Vector<bool, Size> singleTrueMask (int index)
+{
+	DE_ASSERT(de::inBounds(index, 0, Size));
+	tcu::Vector<bool, Size> result;
+	result[index] = true;
+	return result;
+}
+
+static int intPow (int base, int exp)
+{
+	DE_ASSERT(exp >= 0);
+	if (exp == 0)
+		return 1;
+	else
+	{
+		const int sub = intPow(base, exp/2);
+		if (exp % 2 == 0)
+			return sub*sub;
+		else
+			return sub*sub*base;
+	}
+}
+
+tcu::Surface getPixels (const glu::RenderContext& rCtx, int x, int y, int width, int height)
+{
+	tcu::Surface result(width, height);
+	glu::readPixels(rCtx, x, y, result.getAccess());
+	return result;
+}
+
+tcu::Surface getPixels (const glu::RenderContext& rCtx, const RandomViewport& vp)
+{
+	return getPixels(rCtx, vp.x, vp.y, vp.width, vp.height);
+}
+
+static inline void checkRenderTargetSize (const RenderTarget& renderTarget, int minSize)
+{
+	if (renderTarget.getWidth() < minSize || renderTarget.getHeight() < minSize)
+		throw tcu::NotSupportedError("Render target width and height must be at least " + de::toString(minSize));
+}
+
+tcu::TextureLevel getPNG (const tcu::Archive& archive, const string& filename)
+{
+	tcu::TextureLevel result;
+	tcu::ImageIO::loadPNG(result, archive, filename.c_str());
+	return result;
+}
+
+static int numBasicSubobjects (const glu::VarType& type)
+{
+	if (type.isBasicType())
+		return 1;
+	else if (type.isArrayType())
+		return type.getArraySize()*numBasicSubobjects(type.getElementType());
+	else if (type.isStructType())
+	{
+		const glu::StructType&	structType	= *type.getStructPtr();
+		int						result		= 0;
+		for (int i = 0; i < structType.getNumMembers(); i++)
+			result += numBasicSubobjects(structType.getMember(i).getType());
+		return result;
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return -1;
+	}
+}
+
+static inline int numVerticesPerPrimitive (deUint32 primitiveTypeGL)
+{
+	switch (primitiveTypeGL)
+	{
+		case GL_POINTS:		return 1;
+		case GL_TRIANGLES:	return 3;
+		case GL_LINES:		return 2;
+		default:
+			DE_ASSERT(false);
+			return -1;
+	}
+}
+
+static inline void setViewport (const glw::Functions& gl, const RandomViewport& vp)
+{
+	gl.viewport(vp.x, vp.y, vp.width, vp.height);
+}
+
+static inline deUint32 getQueryResult (const glw::Functions& gl, deUint32 queryObject)
+{
+	deUint32 result = (deUint32)-1;
+	gl.getQueryObjectuiv(queryObject, GL_QUERY_RESULT, &result);
+	TCU_CHECK(result != (deUint32)-1);
+	return result;
+}
+
+template <typename T>
+static void readDataMapped (const glw::Functions& gl, deUint32 bufferTarget, int numElems, T* dst)
+{
+	const int							numBytes	= numElems*(int)sizeof(T);
+	const T* const						mappedData	= (const T*)gl.mapBufferRange(bufferTarget, 0, numBytes, GL_MAP_READ_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), (string() + "glMapBufferRange(" + glu::getBufferTargetName((int)bufferTarget) + ", 0, " + de::toString(numBytes) + ", GL_MAP_READ_BIT)").c_str());
+	TCU_CHECK(mappedData != DE_NULL);
+
+	for (int i = 0; i < numElems; i++)
+		dst[i] = mappedData[i];
+
+	gl.unmapBuffer(bufferTarget);
+}
+
+template <typename T>
+static vector<T> readDataMapped (const glw::Functions& gl, deUint32 bufferTarget, int numElems)
+{
+	vector<T> result(numElems);
+	readDataMapped(gl, bufferTarget, numElems, &result[0]);
+	return result;
+}
+
+namespace
+{
+
+template <typename ArgT, bool res>
+struct ConstantUnaryPredicate
+{
+	bool operator() (const ArgT&) const { return res; }
+};
+
+//! Helper for handling simple, one-varying transform feedbacks.
+template <typename VaryingT>
+class TransformFeedbackHandler
+{
+public:
+	struct Result
+	{
+		int					numPrimitives;
+		vector<VaryingT>	varying;
+
+		Result (void)								: numPrimitives(-1) {}
+		Result (int n, const vector<VaryingT>& v)	: numPrimitives(n), varying(v) {}
+	};
+
+									TransformFeedbackHandler	(const glu::RenderContext& renderCtx, int maxNumVertices);
+
+	Result							renderAndGetPrimitives		(deUint32 programGL, deUint32 tfPrimTypeGL, int numBindings, const glu::VertexArrayBinding* bindings, int numVertices) const;
+
+private:
+	const glu::RenderContext&		m_renderCtx;
+	const glu::TransformFeedback	m_tf;
+	const glu::Buffer				m_tfBuffer;
+	const glu::Query				m_tfPrimQuery;
+};
+
+template <typename AttribType>
+TransformFeedbackHandler<AttribType>::TransformFeedbackHandler (const glu::RenderContext& renderCtx, int maxNumVertices)
+	: m_renderCtx		(renderCtx)
+	, m_tf				(renderCtx)
+	, m_tfBuffer		(renderCtx)
+	, m_tfPrimQuery		(renderCtx)
+{
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	// \note Room for 1 extra triangle, to detect if GL returns too many primitives.
+	const int				bufferSize	= (maxNumVertices + 3) * (int)sizeof(AttribType);
+
+	gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, *m_tfBuffer);
+	gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_READ);
+}
+
+template <typename AttribType>
+typename TransformFeedbackHandler<AttribType>::Result TransformFeedbackHandler<AttribType>::renderAndGetPrimitives (deUint32 programGL, deUint32 tfPrimTypeGL, int numBindings, const glu::VertexArrayBinding* bindings, int numVertices) const
+{
+	DE_ASSERT(tfPrimTypeGL == GL_POINTS || tfPrimTypeGL == GL_LINES || tfPrimTypeGL == GL_TRIANGLES);
+
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, *m_tf);
+	gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, *m_tfBuffer);
+	gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, *m_tfBuffer);
+
+	gl.beginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, *m_tfPrimQuery);
+	gl.beginTransformFeedback(tfPrimTypeGL);
+
+	glu::draw(m_renderCtx, programGL, numBindings, bindings, glu::pr::Patches(numVertices));
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
+
+	gl.endTransformFeedback();
+	gl.endQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
+
+	{
+		const int numPrimsWritten = (int)getQueryResult(gl, *m_tfPrimQuery);
+		return Result(numPrimsWritten, readDataMapped<AttribType>(gl, GL_TRANSFORM_FEEDBACK_BUFFER, numPrimsWritten * numVerticesPerPrimitive(tfPrimTypeGL)));
+	}
+}
+
+template <typename T>
+class SizeLessThan
+{
+public:
+	bool operator() (const T& a, const T& b) const { return a.size() < b.size(); }
+};
+
+//! Predicate functor for comparing structs by their members.
+template <typename Pred, typename T, typename MembT>
+class MemberPred
+{
+public:
+				MemberPred	(MembT T::* membP) : m_membP(membP), m_pred(Pred()) {}
+	bool		operator()	(const T& a, const T& b) const { return m_pred(a.*m_membP, b.*m_membP); }
+
+private:
+	MembT T::*	m_membP;
+	Pred		m_pred;
+};
+
+//! Convenience wrapper for MemberPred, because class template arguments aren't deduced based on constructor arguments.
+template <template <typename> class Pred, typename T, typename MembT>
+static MemberPred<Pred<MembT>, T, MembT> memberPred (MembT T::* membP) { return MemberPred<Pred<MembT>, T, MembT>(membP); }
+
+template <typename SeqT, int Size, typename Pred>
+class LexCompare
+{
+public:
+	LexCompare (void) : m_pred(Pred()) {}
+
+	bool operator() (const SeqT& a, const SeqT& b) const
+	{
+		for (int i = 0; i < Size; i++)
+		{
+			if (m_pred(a[i], b[i]))
+				return true;
+			if (m_pred(b[i], a[i]))
+				return false;
+		}
+		return false;
+	}
+
+private:
+	Pred m_pred;
+};
+
+template <int Size>
+class VecLexLessThan : public LexCompare<tcu::Vector<float, Size>, Size, std::less<float> >
+{
+};
+
+enum TessPrimitiveType
+{
+	TESSPRIMITIVETYPE_TRIANGLES = 0,
+	TESSPRIMITIVETYPE_QUADS,
+	TESSPRIMITIVETYPE_ISOLINES,
+
+	TESSPRIMITIVETYPE_LAST
+};
+
+enum SpacingMode
+{
+	SPACINGMODE_EQUAL,
+	SPACINGMODE_FRACTIONAL_ODD,
+	SPACINGMODE_FRACTIONAL_EVEN,
+
+	SPACINGMODE_LAST
+};
+
+enum Winding
+{
+	WINDING_CCW = 0,
+	WINDING_CW,
+
+	WINDING_LAST
+};
+
+static inline const char* getTessPrimitiveTypeShaderName (TessPrimitiveType type)
+{
+	switch (type)
+	{
+		case TESSPRIMITIVETYPE_TRIANGLES:	return "triangles";
+		case TESSPRIMITIVETYPE_QUADS:		return "quads";
+		case TESSPRIMITIVETYPE_ISOLINES:	return "isolines";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+static inline const char* getSpacingModeShaderName (SpacingMode mode)
+{
+	switch (mode)
+	{
+		case SPACINGMODE_EQUAL:				return "equal_spacing";
+		case SPACINGMODE_FRACTIONAL_ODD:	return "fractional_odd_spacing";
+		case SPACINGMODE_FRACTIONAL_EVEN:	return "fractional_even_spacing";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+static inline const char* getWindingShaderName (Winding winding)
+{
+	switch (winding)
+	{
+		case WINDING_CCW:	return "ccw";
+		case WINDING_CW:	return "cw";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode=false)
+{
+	return string() + "layout (" + getTessPrimitiveTypeShaderName(primType)
+								 + ", " + getSpacingModeShaderName(spacing)
+								 + ", " + getWindingShaderName(winding)
+								 + (usePointMode ? ", point_mode" : "")
+								 + ") in;\n";
+}
+
+static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, SpacingMode spacing, bool usePointMode=false)
+{
+	return string() + "layout (" + getTessPrimitiveTypeShaderName(primType)
+								 + ", " + getSpacingModeShaderName(spacing)
+								 + (usePointMode ? ", point_mode" : "")
+								 + ") in;\n";
+}
+
+static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, Winding winding, bool usePointMode=false)
+{
+	return string() + "layout (" + getTessPrimitiveTypeShaderName(primType)
+								 + ", " + getWindingShaderName(winding)
+								 + (usePointMode ? ", point_mode" : "")
+								 + ") in;\n";
+}
+
+static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, bool usePointMode=false)
+{
+	return string() + "layout (" + getTessPrimitiveTypeShaderName(primType)
+								 + (usePointMode ? ", point_mode" : "")
+								 + ") in;\n";
+}
+
+static inline deUint32 outputPrimitiveTypeGL (TessPrimitiveType tessPrimType, bool usePointMode)
+{
+	if (usePointMode)
+		return GL_POINTS;
+	else
+	{
+		switch (tessPrimType)
+		{
+			case TESSPRIMITIVETYPE_TRIANGLES:	return GL_TRIANGLES;
+			case TESSPRIMITIVETYPE_QUADS:		return GL_TRIANGLES;
+			case TESSPRIMITIVETYPE_ISOLINES:	return GL_LINES;
+			default:
+				DE_ASSERT(false);
+				return (deUint32)-1;
+		}
+	}
+}
+
+static inline int numInnerTessellationLevels (TessPrimitiveType primType)
+{
+	switch (primType)
+	{
+		case TESSPRIMITIVETYPE_TRIANGLES:	return 1;
+		case TESSPRIMITIVETYPE_QUADS:		return 2;
+		case TESSPRIMITIVETYPE_ISOLINES:	return 0;
+		default: DE_ASSERT(false); return -1;
+	}
+}
+
+static inline int numOuterTessellationLevels (TessPrimitiveType primType)
+{
+	switch (primType)
+	{
+		case TESSPRIMITIVETYPE_TRIANGLES:	return 3;
+		case TESSPRIMITIVETYPE_QUADS:		return 4;
+		case TESSPRIMITIVETYPE_ISOLINES:	return 2;
+		default: DE_ASSERT(false); return -1;
+	}
+}
+
+static string tessellationLevelsString (const float* inner, int numInner, const float* outer, int numOuter)
+{
+	DE_ASSERT(numInner >= 0 && numOuter >= 0);
+	return "inner: " + elemsStr(inner, inner+numInner) + ", outer: " + elemsStr(outer, outer+numOuter);
+}
+
+static string tessellationLevelsString (const float* inner, const float* outer, TessPrimitiveType primType)
+{
+	return tessellationLevelsString(inner, numInnerTessellationLevels(primType), outer, numOuterTessellationLevels(primType));
+}
+
+static string tessellationLevelsString (const float* inner, const float* outer)
+{
+	return tessellationLevelsString(inner, 2, outer, 4);
+}
+
+static inline float getClampedTessLevel (SpacingMode mode, float tessLevel)
+{
+	switch (mode)
+	{
+		case SPACINGMODE_EQUAL:				return de::max(1.0f, tessLevel);
+		case SPACINGMODE_FRACTIONAL_ODD:	return de::max(1.0f, tessLevel);
+		case SPACINGMODE_FRACTIONAL_EVEN:	return de::max(2.0f, tessLevel);
+		default:
+			DE_ASSERT(false);
+			return -1.0f;
+	}
+}
+
+static inline int getRoundedTessLevel (SpacingMode mode, float clampedTessLevel)
+{
+	int result = (int)deFloatCeil(clampedTessLevel);
+
+	switch (mode)
+	{
+		case SPACINGMODE_EQUAL:											break;
+		case SPACINGMODE_FRACTIONAL_ODD:	result += 1 - result % 2;	break;
+		case SPACINGMODE_FRACTIONAL_EVEN:	result += result % 2;		break;
+		default:
+			DE_ASSERT(false);
+	}
+	DE_ASSERT(de::inRange<int>(result, 1, MINIMUM_MAX_TESS_GEN_LEVEL));
+
+	return result;
+}
+
+static int getClampedRoundedTessLevel (SpacingMode mode, float tessLevel)
+{
+	return getRoundedTessLevel(mode, getClampedTessLevel(mode, tessLevel));
+}
+
+//! A description of an outer edge of a triangle, quad or isolines.
+//! An outer edge can be described by the index of a u/v/w coordinate
+//! and the coordinate's value along that edge.
+struct OuterEdgeDescription
+{
+	int		constantCoordinateIndex;
+	float	constantCoordinateValueChoices[2];
+	int		numConstantCoordinateValueChoices;
+
+	OuterEdgeDescription (int i, float c0)			: constantCoordinateIndex(i), numConstantCoordinateValueChoices(1) { constantCoordinateValueChoices[0] = c0; }
+	OuterEdgeDescription (int i, float c0, float c1)	: constantCoordinateIndex(i), numConstantCoordinateValueChoices(2) { constantCoordinateValueChoices[0] = c0; constantCoordinateValueChoices[1] = c1; }
+
+	string description (void) const
+	{
+		static const char* const	coordinateNames[] = { "u", "v", "w" };
+		string						result;
+		for (int i = 0; i < numConstantCoordinateValueChoices; i++)
+			result += string() + (i > 0 ? " or " : "") + coordinateNames[constantCoordinateIndex] + "=" + de::toString(constantCoordinateValueChoices[i]);
+		return result;
+	}
+
+	bool contains (const Vec3& v) const
+	{
+		for (int i = 0; i < numConstantCoordinateValueChoices; i++)
+			if (v[constantCoordinateIndex] == constantCoordinateValueChoices[i])
+				return true;
+		return false;
+	}
+};
+
+static vector<OuterEdgeDescription> outerEdgeDescriptions (TessPrimitiveType primType)
+{
+	static const OuterEdgeDescription triangleOuterEdgeDescriptions[3] =
+	{
+		OuterEdgeDescription(0, 0.0f),
+		OuterEdgeDescription(1, 0.0f),
+		OuterEdgeDescription(2, 0.0f)
+	};
+
+	static const OuterEdgeDescription quadOuterEdgeDescriptions[4] =
+	{
+		OuterEdgeDescription(0, 0.0f),
+		OuterEdgeDescription(1, 0.0f),
+		OuterEdgeDescription(0, 1.0f),
+		OuterEdgeDescription(1, 1.0f)
+	};
+
+	static const OuterEdgeDescription isolinesOuterEdgeDescriptions[1] =
+	{
+		OuterEdgeDescription(0, 0.0f, 1.0f),
+	};
+
+	switch (primType)
+	{
+		case TESSPRIMITIVETYPE_TRIANGLES:	return arrayToVector(triangleOuterEdgeDescriptions);
+		case TESSPRIMITIVETYPE_QUADS:		return arrayToVector(quadOuterEdgeDescriptions);
+		case TESSPRIMITIVETYPE_ISOLINES:	return arrayToVector(isolinesOuterEdgeDescriptions);
+		default: DE_ASSERT(false); return vector<OuterEdgeDescription>();
+	}
+}
+
+// \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that u+v+w == 1.0f, or [uvw] + (1.0f-[uvw]) == 1.0f).
+static vector<Vec3> generateReferenceTriangleTessCoords (SpacingMode spacingMode, int inner, int outer0, int outer1, int outer2)
+{
+	vector<Vec3> tessCoords;
+
+	if (inner == 1)
+	{
+		if (outer0 == 1 && outer1 == 1 && outer2 == 1)
+		{
+			tessCoords.push_back(Vec3(1.0f, 0.0f, 0.0f));
+			tessCoords.push_back(Vec3(0.0f, 1.0f, 0.0f));
+			tessCoords.push_back(Vec3(0.0f, 0.0f, 1.0f));
+			return tessCoords;
+		}
+		else
+			return generateReferenceTriangleTessCoords(spacingMode, spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
+																	outer0, outer1, outer2);
+	}
+	else
+	{
+		for (int i = 0; i < outer0; i++) { const float v = (float)i / (float)outer0; tessCoords.push_back(Vec3(	   0.0f,		   v,	1.0f - v)); }
+		for (int i = 0; i < outer1; i++) { const float v = (float)i / (float)outer1; tessCoords.push_back(Vec3(1.0f - v,		0.0f,		   v)); }
+		for (int i = 0; i < outer2; i++) { const float v = (float)i / (float)outer2; tessCoords.push_back(Vec3(		  v,	1.0f - v,		0.0f)); }
+
+		const int numInnerTriangles = inner/2;
+		for (int innerTriangleNdx = 0; innerTriangleNdx < numInnerTriangles; innerTriangleNdx++)
+		{
+			const int curInnerTriangleLevel = inner - 2*(innerTriangleNdx+1);
+
+			if (curInnerTriangleLevel == 0)
+				tessCoords.push_back(Vec3(1.0f/3.0f));
+			else
+			{
+				const float		minUVW		= (float)(2 * (innerTriangleNdx + 1)) / (float)(3 * inner);
+				const float		maxUVW		= 1.0f - 2.0f*minUVW;
+				const Vec3		corners[3]	=
+				{
+					Vec3(maxUVW, minUVW, minUVW),
+					Vec3(minUVW, maxUVW, minUVW),
+					Vec3(minUVW, minUVW, maxUVW)
+				};
+
+				for (int i = 0; i < curInnerTriangleLevel; i++)
+				{
+					const float f = (float)i / (float)curInnerTriangleLevel;
+					for (int j = 0; j < 3; j++)
+						tessCoords.push_back((1.0f - f)*corners[j] + f*corners[(j+1)%3]);
+				}
+			}
+		}
+
+		return tessCoords;
+	}
+}
+
+static int referenceTriangleNonPointModePrimitiveCount (SpacingMode spacingMode, int inner, int outer0, int outer1, int outer2)
+{
+	if (inner == 1)
+	{
+		if (outer0 == 1 && outer1 == 1 && outer2 == 1)
+			return 1;
+		else
+			return referenceTriangleNonPointModePrimitiveCount(spacingMode, spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
+																			outer0, outer1, outer2);
+	}
+	else
+	{
+		int result = outer0 + outer1 + outer2;
+
+		const int numInnerTriangles = inner/2;
+		for (int innerTriangleNdx = 0; innerTriangleNdx < numInnerTriangles; innerTriangleNdx++)
+		{
+			const int curInnerTriangleLevel = inner - 2*(innerTriangleNdx+1);
+
+			if (curInnerTriangleLevel == 1)
+				result += 4;
+			else
+				result += 2*3*curInnerTriangleLevel;
+		}
+
+		return result;
+	}
+}
+
+// \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that [uv] + (1.0f-[uv]) == 1.0f).
+static vector<Vec3> generateReferenceQuadTessCoords (SpacingMode spacingMode, int inner0, int inner1, int outer0, int outer1, int outer2, int outer3)
+{
+	vector<Vec3> tessCoords;
+
+	if (inner0 == 1 || inner1 == 1)
+	{
+		if (inner0 == 1 && inner1 == 1 && outer0 == 1 && outer1 == 1 && outer2 == 1 && outer3 == 1)
+		{
+			tessCoords.push_back(Vec3(0.0f, 0.0f, 0.0f));
+			tessCoords.push_back(Vec3(1.0f, 0.0f, 0.0f));
+			tessCoords.push_back(Vec3(0.0f, 1.0f, 0.0f));
+			tessCoords.push_back(Vec3(1.0f, 1.0f, 0.0f));
+			return tessCoords;
+		}
+		else
+			return generateReferenceQuadTessCoords(spacingMode, inner0 > 1 ? inner0 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
+																inner1 > 1 ? inner1 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
+																outer0, outer1, outer2, outer3);
+	}
+	else
+	{
+		for (int i = 0; i < outer0; i++) { const float v = (float)i / (float)outer0; tessCoords.push_back(Vec3(0.0f,	v,			0.0f)); }
+		for (int i = 0; i < outer1; i++) { const float v = (float)i / (float)outer1; tessCoords.push_back(Vec3(1.0f-v,	0.0f,		0.0f)); }
+		for (int i = 0; i < outer2; i++) { const float v = (float)i / (float)outer2; tessCoords.push_back(Vec3(1.0f,	1.0f-v,		0.0f)); }
+		for (int i = 0; i < outer3; i++) { const float v = (float)i / (float)outer3; tessCoords.push_back(Vec3(v,		1.0f,		0.0f)); }
+
+		for (int innerVtxY = 0; innerVtxY < inner1-1; innerVtxY++)
+		for (int innerVtxX = 0; innerVtxX < inner0-1; innerVtxX++)
+			tessCoords.push_back(Vec3((float)(innerVtxX + 1) / (float)inner0,
+									  (float)(innerVtxY + 1) / (float)inner1,
+									  0.0f));
+
+		return tessCoords;
+	}
+}
+
+static int referenceQuadNonPointModePrimitiveCount (SpacingMode spacingMode, int inner0, int inner1, int outer0, int outer1, int outer2, int outer3)
+{
+	vector<Vec3> tessCoords;
+
+	if (inner0 == 1 || inner1 == 1)
+	{
+		if (inner0 == 1 && inner1 == 1 && outer0 == 1 && outer1 == 1 && outer2 == 1 && outer3 == 1)
+			return 2;
+		else
+			return referenceQuadNonPointModePrimitiveCount(spacingMode, inner0 > 1 ? inner0 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
+																		inner1 > 1 ? inner1 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
+																		outer0, outer1, outer2, outer3);
+	}
+	else
+		return 2*(inner0-2)*(inner1-2) + 2*(inner0-2) + 2*(inner1-2) + outer0+outer1+outer2+outer3;
+}
+
+// \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that [uv] + (1.0f-[uv]) == 1.0f).
+static vector<Vec3> generateReferenceIsolineTessCoords (int outer0, int outer1)
+{
+	vector<Vec3> tessCoords;
+
+	for (int y = 0; y < outer0;		y++)
+	for (int x = 0; x < outer1+1;	x++)
+		tessCoords.push_back(Vec3((float)x / (float)outer1,
+												  (float)y / (float)outer0,
+												  0.0f));
+
+	return tessCoords;
+}
+
+static int referenceIsolineNonPointModePrimitiveCount (int outer0, int outer1)
+{
+	return outer0*outer1;
+}
+
+static void getClampedRoundedTriangleTessLevels (SpacingMode spacingMode, const float* innerSrc, const float* outerSrc, int* innerDst, int *outerDst)
+{
+	innerDst[0] = getClampedRoundedTessLevel(spacingMode, innerSrc[0]);
+	for (int i = 0; i < 3; i++)
+		outerDst[i] = getClampedRoundedTessLevel(spacingMode, outerSrc[i]);
+}
+
+static void getClampedRoundedQuadTessLevels (SpacingMode spacingMode, const float* innerSrc, const float* outerSrc, int* innerDst, int *outerDst)
+{
+	for (int i = 0; i < 2; i++)
+		innerDst[i] = getClampedRoundedTessLevel(spacingMode, innerSrc[i]);
+	for (int i = 0; i < 4; i++)
+		outerDst[i] = getClampedRoundedTessLevel(spacingMode, outerSrc[i]);
+}
+
+static void getClampedRoundedIsolineTessLevels (SpacingMode spacingMode, const float* outerSrc, int* outerDst)
+{
+	outerDst[0] = getClampedRoundedTessLevel(SPACINGMODE_EQUAL,	outerSrc[0]);
+	outerDst[1] = getClampedRoundedTessLevel(spacingMode,		outerSrc[1]);
+}
+
+static inline bool isPatchDiscarded (TessPrimitiveType primitiveType, const float* outerLevels)
+{
+	const int numOuterLevels = numOuterTessellationLevels(primitiveType);
+	for (int i = 0; i < numOuterLevels; i++)
+		if (outerLevels[i] <= 0.0f)
+			return true;
+	return false;
+}
+
+static vector<Vec3> generateReferenceTessCoords (TessPrimitiveType primitiveType, SpacingMode spacingMode, const float* innerLevels, const float* outerLevels)
+{
+	if (isPatchDiscarded(primitiveType, outerLevels))
+		return vector<Vec3>();
+
+	switch (primitiveType)
+	{
+		case TESSPRIMITIVETYPE_TRIANGLES:
+		{
+			int inner;
+			int outer[3];
+			getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]);
+
+			if (spacingMode != SPACINGMODE_EQUAL)
+			{
+				// \note For fractional spacing modes, exact results are implementation-defined except in special cases.
+				DE_ASSERT(de::abs(innerLevels[0] - (float)inner) < 0.001f);
+				for (int i = 0; i < 3; i++)
+					DE_ASSERT(de::abs(outerLevels[i] - (float)outer[i]) < 0.001f);
+				DE_ASSERT(inner > 1 || (outer[0] == 1 && outer[1] == 1 && outer[2] == 1));
+			}
+
+			return generateReferenceTriangleTessCoords(spacingMode, inner, outer[0], outer[1], outer[2]);
+		}
+
+		case TESSPRIMITIVETYPE_QUADS:
+		{
+			int inner[2];
+			int outer[4];
+			getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]);
+
+			if (spacingMode != SPACINGMODE_EQUAL)
+			{
+				// \note For fractional spacing modes, exact results are implementation-defined except in special cases.
+				for (int i = 0; i < 2; i++)
+					DE_ASSERT(de::abs(innerLevels[i] - (float)inner[i]) < 0.001f);
+				for (int i = 0; i < 4; i++)
+					DE_ASSERT(de::abs(outerLevels[i] - (float)outer[i]) < 0.001f);
+
+				DE_ASSERT((inner[0] > 1 && inner[1] > 1) || (inner[0] == 1 && inner[1] == 1 && outer[0] == 1 && outer[1] == 1 && outer[2] == 1 && outer[3] == 1));
+			}
+
+			return generateReferenceQuadTessCoords(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]);
+		}
+
+		case TESSPRIMITIVETYPE_ISOLINES:
+		{
+			int outer[2];
+			getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]);
+
+			if (spacingMode != SPACINGMODE_EQUAL)
+			{
+				// \note For fractional spacing modes, exact results are implementation-defined except in special cases.
+				DE_ASSERT(de::abs(outerLevels[1] - (float)outer[1]) < 0.001f);
+			}
+
+			return generateReferenceIsolineTessCoords(outer[0], outer[1]);
+		}
+
+		default:
+			DE_ASSERT(false);
+			return vector<Vec3>();
+	}
+}
+
+static int referencePointModePrimitiveCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, const float* innerLevels, const float* outerLevels)
+{
+	if (isPatchDiscarded(primitiveType, outerLevels))
+		return 0;
+
+	switch (primitiveType)
+	{
+		case TESSPRIMITIVETYPE_TRIANGLES:
+		{
+			int inner;
+			int outer[3];
+			getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]);
+			return (int)generateReferenceTriangleTessCoords(spacingMode, inner, outer[0], outer[1], outer[2]).size();
+		}
+
+		case TESSPRIMITIVETYPE_QUADS:
+		{
+			int inner[2];
+			int outer[4];
+			getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]);
+			return (int)generateReferenceQuadTessCoords(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]).size();
+		}
+
+		case TESSPRIMITIVETYPE_ISOLINES:
+		{
+			int outer[2];
+			getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]);
+			return (int)generateReferenceIsolineTessCoords(outer[0], outer[1]).size();
+		}
+
+		default:
+			DE_ASSERT(false);
+			return -1;
+	}
+}
+
+static int referenceNonPointModePrimitiveCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, const float* innerLevels, const float* outerLevels)
+{
+	if (isPatchDiscarded(primitiveType, outerLevels))
+		return 0;
+
+	switch (primitiveType)
+	{
+		case TESSPRIMITIVETYPE_TRIANGLES:
+		{
+			int inner;
+			int outer[3];
+			getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]);
+			return referenceTriangleNonPointModePrimitiveCount(spacingMode, inner, outer[0], outer[1], outer[2]);
+		}
+
+		case TESSPRIMITIVETYPE_QUADS:
+		{
+			int inner[2];
+			int outer[4];
+			getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]);
+			return referenceQuadNonPointModePrimitiveCount(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]);
+		}
+
+		case TESSPRIMITIVETYPE_ISOLINES:
+		{
+			int outer[2];
+			getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]);
+			return referenceIsolineNonPointModePrimitiveCount(outer[0], outer[1]);
+		}
+
+		default:
+			DE_ASSERT(false);
+			return -1;
+	}
+}
+
+static int referencePrimitiveCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode, const float* innerLevels, const float* outerLevels)
+{
+	return usePointMode ? referencePointModePrimitiveCount		(primitiveType, spacingMode, innerLevels, outerLevels)
+						: referenceNonPointModePrimitiveCount	(primitiveType, spacingMode, innerLevels, outerLevels);
+}
+
+static int referenceVertexCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode, const float* innerLevels, const float* outerLevels)
+{
+	return referencePrimitiveCount(primitiveType, spacingMode, usePointMode, innerLevels, outerLevels)
+		   * numVerticesPerPrimitive(outputPrimitiveTypeGL(primitiveType, usePointMode));
+}
+
+//! Helper for calling referenceVertexCount multiple times with different tessellation levels.
+//! \note Levels contains inner and outer levels, per patch, in order IIOOOO. The full 6 levels must always be present, irrespective of primitiveType.
+static int multiplePatchReferenceVertexCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode, const float* levels, int numPatches)
+{
+	int result = 0;
+	for (int patchNdx = 0; patchNdx < numPatches; patchNdx++)
+		result += referenceVertexCount(primitiveType, spacingMode, usePointMode, &levels[6*patchNdx + 0], &levels[6*patchNdx + 2]);
+	return result;
+}
+
+vector<float> generateRandomPatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel, de::Random& rnd)
+{
+	vector<float> tessLevels(numPatches*6);
+
+	for (int patchNdx = 0; patchNdx < numPatches; patchNdx++)
+	{
+		float* const inner = &tessLevels[patchNdx*6 + 0];
+		float* const outer = &tessLevels[patchNdx*6 + 2];
+
+		for (int j = 0; j < 2; j++)
+			inner[j] = rnd.getFloat(1.0f, 62.0f);
+		for (int j = 0; j < 4; j++)
+			outer[j] = j == constantOuterLevelIndex ? constantOuterLevel : rnd.getFloat(1.0f, 62.0f);
+	}
+
+	return tessLevels;
+}
+
+static inline void drawPoint (tcu::Surface& dst, int centerX, int centerY, const tcu::RGBA& color, int size)
+{
+	const int width		= dst.getWidth();
+	const int height	= dst.getHeight();
+	DE_ASSERT(de::inBounds(centerX, 0, width) && de::inBounds(centerY, 0, height));
+	DE_ASSERT(size > 0);
+
+	for (int yOff = -((size-1)/2); yOff <= size/2; yOff++)
+	for (int xOff = -((size-1)/2); xOff <= size/2; xOff++)
+	{
+		const int pixX = centerX + xOff;
+		const int pixY = centerY + yOff;
+		if (de::inBounds(pixX, 0, width) && de::inBounds(pixY, 0, height))
+			dst.setPixel(pixX, pixY, color);
+	}
+}
+
+static void drawTessCoordPoint (tcu::Surface& dst, TessPrimitiveType primitiveType, const Vec3& pt, const tcu::RGBA& color, int size)
+{
+	// \note These coordinates should match the description in the log message in TessCoordCase::iterate.
+
+	static const Vec2 triangleCorners[3] =
+	{
+		Vec2(0.95f, 0.95f),
+		Vec2(0.5f,  0.95f - 0.9f*deFloatSqrt(3.0f/4.0f)),
+		Vec2(0.05f, 0.95f)
+	};
+
+	static const float quadIsolineLDRU[4] =
+	{
+		0.1f, 0.9f, 0.9f, 0.1f
+	};
+
+	const Vec2 dstPos = primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? pt.x()*triangleCorners[0]
+																	 + pt.y()*triangleCorners[1]
+																	 + pt.z()*triangleCorners[2]
+
+					  : primitiveType == TESSPRIMITIVETYPE_QUADS ||
+						primitiveType == TESSPRIMITIVETYPE_ISOLINES ? Vec2((1.0f - pt.x())*quadIsolineLDRU[0] + pt.x()*quadIsolineLDRU[2],
+																		   (1.0f - pt.y())*quadIsolineLDRU[1] + pt.y()*quadIsolineLDRU[3])
+
+					  : Vec2(-1.0f);
+
+	drawPoint(dst, (int)(dstPos.x()*dst.getWidth()), (int)(dstPos.y()*dst.getHeight()), color, size);
+}
+
+static void drawTessCoordVisualization (tcu::Surface& dst, TessPrimitiveType primitiveType, const vector<Vec3>& coords)
+{
+	const int		imageWidth		= 256;
+	const int		imageHeight		= 256;
+	const Vec2		imageSizeFloat	((float)imageWidth, (float)imageHeight);
+	dst.setSize(imageWidth, imageHeight);
+
+	tcu::clear(dst.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	for (int i = 0; i < (int)coords.size(); i++)
+		drawTessCoordPoint(dst, primitiveType, coords[i], tcu::RGBA::white, 2);
+}
+
+static int binarySearchFirstVec3WithXAtLeast (const vector<Vec3>& sorted, float x)
+{
+	const Vec3 ref(x, 0.0f, 0.0f);
+	const vector<Vec3>::const_iterator first = std::lower_bound(sorted.begin(), sorted.end(), ref, vec3XLessThan);
+	if (first == sorted.end())
+		return -1;
+	return (int)std::distance(sorted.begin(), first);
+}
+
+template <typename T, typename P>
+static vector<T> sorted (const vector<T>& unsorted, P pred)
+{
+	vector<T> result = unsorted;
+	std::sort(result.begin(), result.end(), pred);
+	return result;
+}
+
+template <typename T>
+static vector<T> sorted (const vector<T>& unsorted)
+{
+	vector<T> result = unsorted;
+	std::sort(result.begin(), result.end());
+	return result;
+}
+
+// Check that all points in subset are (approximately) present also in superset.
+static bool oneWayComparePointSets (TestLog&				log,
+									tcu::Surface&			errorDst,
+									TessPrimitiveType		primitiveType,
+									const vector<Vec3>&		subset,
+									const vector<Vec3>&		superset,
+									const char*				subsetName,
+									const char*				supersetName,
+									const tcu::RGBA&		errorColor)
+{
+	const vector<Vec3>	supersetSorted			= sorted(superset, vec3XLessThan);
+	const float			epsilon					= 0.01f;
+	const int			maxNumFailurePrints		= 5;
+	int					numFailuresDetected		= 0;
+
+	for (int subNdx = 0; subNdx < (int)subset.size(); subNdx++)
+	{
+		const Vec3& subPt = subset[subNdx];
+
+		bool matchFound = false;
+
+		{
+			// Binary search the index of the first point in supersetSorted with x in the [subPt.x() - epsilon, subPt.x() + epsilon] range.
+			const Vec3	matchMin			= subPt - epsilon;
+			const Vec3	matchMax			= subPt + epsilon;
+			int			firstCandidateNdx	= binarySearchFirstVec3WithXAtLeast(supersetSorted, matchMin.x());
+
+			if (firstCandidateNdx >= 0)
+			{
+				// Compare subPt to all points in supersetSorted with x in the [subPt.x() - epsilon, subPt.x() + epsilon] range.
+				for (int superNdx = firstCandidateNdx; superNdx < (int)supersetSorted.size() && supersetSorted[superNdx].x() <= matchMax.x(); superNdx++)
+				{
+					const Vec3& superPt = supersetSorted[superNdx];
+
+					if (tcu::boolAll(tcu::greaterThanEqual	(superPt, matchMin)) &&
+						tcu::boolAll(tcu::lessThanEqual		(superPt, matchMax)))
+					{
+						matchFound = true;
+						break;
+					}
+				}
+			}
+		}
+
+		if (!matchFound)
+		{
+			numFailuresDetected++;
+			if (numFailuresDetected < maxNumFailurePrints)
+				log << TestLog::Message << "Failure: no matching " << supersetName << " point found for " << subsetName << " point " << subPt << TestLog::EndMessage;
+			else if (numFailuresDetected == maxNumFailurePrints)
+				log << TestLog::Message << "Note: More errors follow" << TestLog::EndMessage;
+
+			drawTessCoordPoint(errorDst, primitiveType, subPt, errorColor, 4);
+		}
+	}
+
+	return numFailuresDetected == 0;
+}
+
+static bool compareTessCoords (TestLog& log, TessPrimitiveType primitiveType, const vector<Vec3>& refCoords, const vector<Vec3>& resCoords)
+{
+	tcu::Surface	refVisual;
+	tcu::Surface	resVisual;
+	bool			success = true;
+
+	drawTessCoordVisualization(refVisual, primitiveType, refCoords);
+	drawTessCoordVisualization(resVisual, primitiveType, resCoords);
+
+	// Check that all points in reference also exist in result.
+	success = oneWayComparePointSets(log, refVisual, primitiveType, refCoords, resCoords, "reference", "result", tcu::RGBA::blue) && success;
+	// Check that all points in result also exist in reference.
+	success = oneWayComparePointSets(log, resVisual, primitiveType, resCoords, refCoords, "result", "reference", tcu::RGBA::red) && success;
+
+	if (!success)
+	{
+		log << TestLog::Message << "Note: in the following reference visualization, points that are missing in result point set are blue (if any)" << TestLog::EndMessage
+			<< TestLog::Image("RefTessCoordVisualization", "Reference tessCoord visualization", refVisual)
+			<< TestLog::Message << "Note: in the following result visualization, points that are missing in reference point set are red (if any)" << TestLog::EndMessage;
+	}
+
+	log << TestLog::Image("ResTessCoordVisualization", "Result tessCoord visualization", resVisual);
+
+	return success;
+}
+
+namespace VerifyFractionalSpacingSingleInternal
+{
+
+struct Segment
+{
+	int		index; //!< Index of left coordinate in sortedXCoords.
+	float	length;
+	Segment (void)						: index(-1),		length(-1.0f)	{}
+	Segment (int index_, float length_)	: index(index_),	length(length_)	{}
+
+	static vector<float> lengths (const vector<Segment>& segments) { return members(segments, &Segment::length); }
+};
+
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Verify fractional spacing conditions for a single line
+ *
+ * Verify that the splitting of an edge (resulting from e.g. an isoline
+ * with outer levels { 1.0, tessLevel }) with a given fractional spacing
+ * mode fulfills certain conditions given in the spec.
+ *
+ * Note that some conditions can't be checked from just one line
+ * (specifically, that the additional segment decreases monotonically
+ * length and the requirement that the additional segments be placed
+ * identically for identical values of clamped level).
+ *
+ * Therefore, the function stores some values to additionalSegmentLengthDst
+ * and additionalSegmentLocationDst that can later be given to
+ * verifyFractionalSpacingMultiple(). A negative value in length means that
+ * no additional segments are present, i.e. there's just one segment.
+ * A negative value in location means that the value wasn't determinable,
+ * i.e. all segments had same length.
+ * The values are not stored if false is returned.
+ *//*--------------------------------------------------------------------*/
+static bool verifyFractionalSpacingSingle (TestLog& log, SpacingMode spacingMode, float tessLevel, const vector<float>& coords, float& additionalSegmentLengthDst, int& additionalSegmentLocationDst)
+{
+	using namespace VerifyFractionalSpacingSingleInternal;
+
+	DE_ASSERT(spacingMode == SPACINGMODE_FRACTIONAL_ODD || spacingMode == SPACINGMODE_FRACTIONAL_EVEN);
+
+	const float				clampedLevel	= getClampedTessLevel(spacingMode, tessLevel);
+	const int				finalLevel		= getRoundedTessLevel(spacingMode, clampedLevel);
+	const vector<float>		sortedCoords	= sorted(coords);
+	string					failNote		= "Note: tessellation level is " + de::toString(tessLevel) + "\nNote: sorted coordinates are:\n    " + containerStr(sortedCoords);
+
+	if ((int)coords.size() != finalLevel + 1)
+	{
+		log << TestLog::Message << "Failure: number of vertices is " << coords.size() << "; expected " << finalLevel + 1
+			<< " (clamped tessellation level is " << clampedLevel << ")"
+			<< "; final level (clamped level rounded up to " << (spacingMode == SPACINGMODE_FRACTIONAL_EVEN ? "even" : "odd") << ") is " << finalLevel
+			<< " and should equal the number of segments, i.e. number of vertices minus 1" << TestLog::EndMessage
+			<< TestLog::Message << failNote << TestLog::EndMessage;
+		return false;
+	}
+
+	if (sortedCoords[0] != 0.0f || sortedCoords.back() != 1.0f)
+	{
+		log << TestLog::Message << "Failure: smallest coordinate should be 0.0 and biggest should be 1.0" << TestLog::EndMessage
+			<< TestLog::Message << failNote << TestLog::EndMessage;
+		return false;
+	}
+
+	{
+		vector<Segment> segments(finalLevel);
+		for (int i = 0; i < finalLevel; i++)
+			segments[i] = Segment(i, sortedCoords[i+1] - sortedCoords[i]);
+
+		failNote += "\nNote: segment lengths are, from left to right:\n    " + containerStr(Segment::lengths(segments));
+
+		{
+			// Divide segments to two different groups based on length.
+
+			vector<Segment> segmentsA;
+			vector<Segment> segmentsB;
+			segmentsA.push_back(segments[0]);
+
+			for (int segNdx = 1; segNdx < (int)segments.size(); segNdx++)
+			{
+				const float		epsilon		= 0.001f;
+				const Segment&	seg			= segments[segNdx];
+
+				if (de::abs(seg.length - segmentsA[0].length) < epsilon)
+					segmentsA.push_back(seg);
+				else if (segmentsB.empty() || de::abs(seg.length - segmentsB[0].length) < epsilon)
+					segmentsB.push_back(seg);
+				else
+				{
+					log << TestLog::Message << "Failure: couldn't divide segments to 2 groups by length; "
+											<< "e.g. segment of length " << seg.length << " isn't approximately equal to either "
+											<< segmentsA[0].length << " or " << segmentsB[0].length << TestLog::EndMessage
+											<< TestLog::Message << failNote << TestLog::EndMessage;
+					return false;
+				}
+			}
+
+			if (clampedLevel == (float)finalLevel)
+			{
+				// All segments should be of equal length.
+				if (!segmentsA.empty() && !segmentsB.empty())
+				{
+					log << TestLog::Message << "Failure: clamped and final tessellation level are equal, but not all segments are of equal length." << TestLog::EndMessage
+						<< TestLog::Message << failNote << TestLog::EndMessage;
+					return false;
+				}
+			}
+
+			if (segmentsA.empty() || segmentsB.empty()) // All segments have same length. This is ok.
+			{
+				additionalSegmentLengthDst		= segments.size() == 1 ? -1.0f : segments[0].length;
+				additionalSegmentLocationDst	= -1;
+				return true;
+			}
+
+			if (segmentsA.size() != 2 && segmentsB.size() != 2)
+			{
+				log << TestLog::Message << "Failure: when dividing the segments to 2 groups by length, neither of the two groups has exactly 2 or 0 segments in it" << TestLog::EndMessage
+					<< TestLog::Message << failNote << TestLog::EndMessage;
+				return false;
+			}
+
+			// For convenience, arrange so that the 2-segment group is segmentsB.
+			if (segmentsB.size() != 2)
+				std::swap(segmentsA, segmentsB);
+
+			// \note For 4-segment lines both segmentsA and segmentsB have 2 segments each.
+			//		 Thus, we can't be sure which ones were meant as the additional segments.
+			//		 We give the benefit of the doubt by assuming that they're the shorter
+			//		 ones (as they should).
+
+			if (segmentsA.size() != 2)
+			{
+				if (segmentsB[0].length > segmentsA[0].length + 0.001f)
+				{
+					log << TestLog::Message << "Failure: the two additional segments are longer than the other segments" << TestLog::EndMessage
+						<< TestLog::Message << failNote << TestLog::EndMessage;
+					return false;
+				}
+			}
+
+			// Check that the additional segments are placed symmetrically.
+			if (segmentsB[0].index + segmentsB[1].index + 1 != (int)segments.size())
+			{
+				log << TestLog::Message << "Failure: the two additional segments aren't placed symmetrically; "
+										<< "one is at index " << segmentsB[0].index << " and other is at index " << segmentsB[1].index
+										<< " (note: the two indexes should sum to " << (int)segments.size()-1 << ", i.e. numberOfSegments-1)" << TestLog::EndMessage
+					<< TestLog::Message << failNote << TestLog::EndMessage;
+				return false;
+			}
+
+			additionalSegmentLengthDst = segmentsB[0].length;
+			if (segmentsA.size() != 2)
+				additionalSegmentLocationDst = de::min(segmentsB[0].index, segmentsB[1].index);
+			else
+				additionalSegmentLocationDst = segmentsB[0].length < segmentsA[0].length - 0.001f ? de::min(segmentsB[0].index, segmentsB[1].index)
+											 : segmentsA[0].length < segmentsB[0].length - 0.001f ? de::min(segmentsA[0].index, segmentsA[1].index)
+											 : -1; // \note -1 when can't reliably decide which ones are the additional segments, a or b.
+			return true;
+		}
+	}
+}
+
+namespace VerifyFractionalSpacingMultipleInternal
+{
+
+struct LineData
+{
+	float	tessLevel;
+	float	additionalSegmentLength;
+	int		additionalSegmentLocation;
+	LineData (float lev, float len, int loc) : tessLevel(lev), additionalSegmentLength(len), additionalSegmentLocation(loc) {}
+};
+
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Verify fractional spacing conditions between multiple lines
+ *
+ * Verify the fractional spacing conditions that are not checked in
+ * verifyFractionalSpacingSingle(). Uses values given by said function
+ * as parameters, in addition to the spacing mode and tessellation level.
+ *//*--------------------------------------------------------------------*/
+static bool verifyFractionalSpacingMultiple (TestLog& log, SpacingMode spacingMode, const vector<float>& tessLevels, const vector<float>& additionalSegmentLengths, const vector<int>& additionalSegmentLocations)
+{
+	using namespace VerifyFractionalSpacingMultipleInternal;
+
+	DE_ASSERT(spacingMode == SPACINGMODE_FRACTIONAL_ODD || spacingMode == SPACINGMODE_FRACTIONAL_EVEN);
+	DE_ASSERT(tessLevels.size() == additionalSegmentLengths.size() &&
+			  tessLevels.size() == additionalSegmentLocations.size());
+
+	vector<LineData> lineDatas;
+
+	for (int i = 0; i < (int)tessLevels.size(); i++)
+		lineDatas.push_back(LineData(tessLevels[i], additionalSegmentLengths[i], additionalSegmentLocations[i]));
+
+	{
+		const vector<LineData> lineDatasSortedByLevel = sorted(lineDatas, memberPred<std::less>(&LineData::tessLevel));
+
+		// Check that lines with identical clamped tessellation levels have identical additionalSegmentLocation.
+
+		for (int lineNdx = 1; lineNdx < (int)lineDatasSortedByLevel.size(); lineNdx++)
+		{
+			const LineData& curData		= lineDatasSortedByLevel[lineNdx];
+			const LineData& prevData	= lineDatasSortedByLevel[lineNdx-1];
+
+			if (curData.additionalSegmentLocation < 0 || prevData.additionalSegmentLocation < 0)
+				continue; // Unknown locations, skip.
+
+			if (getClampedTessLevel(spacingMode, curData.tessLevel) == getClampedTessLevel(spacingMode, prevData.tessLevel) &&
+				curData.additionalSegmentLocation != prevData.additionalSegmentLocation)
+			{
+				log << TestLog::Message << "Failure: additional segments not located identically for two edges with identical clamped tessellation levels" << TestLog::EndMessage
+					<< TestLog::Message << "Note: tessellation levels are " << curData.tessLevel << " and " << prevData.tessLevel
+										<< " (clamped level " << getClampedTessLevel(spacingMode, curData.tessLevel) << ")"
+										<< "; but first additional segments located at indices "
+										<< curData.additionalSegmentLocation << " and " << prevData.additionalSegmentLocation << ", respectively" << TestLog::EndMessage;
+				return false;
+			}
+		}
+
+		// Check that, among lines with same clamped rounded tessellation level, additionalSegmentLength is monotonically decreasing with "clampedRoundedTessLevel - clampedTessLevel" (the "fraction").
+
+		for (int lineNdx = 1; lineNdx < (int)lineDatasSortedByLevel.size(); lineNdx++)
+		{
+			const LineData&		curData				= lineDatasSortedByLevel[lineNdx];
+			const LineData&		prevData			= lineDatasSortedByLevel[lineNdx-1];
+
+			if (curData.additionalSegmentLength < 0.0f || prevData.additionalSegmentLength < 0.0f)
+				continue; // Unknown segment lengths, skip.
+
+			const float			curClampedLevel		= getClampedTessLevel(spacingMode, curData.tessLevel);
+			const float			prevClampedLevel	= getClampedTessLevel(spacingMode, prevData.tessLevel);
+			const int			curFinalLevel		= getRoundedTessLevel(spacingMode, curClampedLevel);
+			const int			prevFinalLevel		= getRoundedTessLevel(spacingMode, prevClampedLevel);
+
+			if (curFinalLevel != prevFinalLevel)
+				continue;
+
+			const float			curFraction		= curFinalLevel - curClampedLevel;
+			const float			prevFraction	= prevFinalLevel - prevClampedLevel;
+
+			if (curData.additionalSegmentLength < prevData.additionalSegmentLength ||
+				(curClampedLevel == prevClampedLevel && curData.additionalSegmentLength != prevData.additionalSegmentLength))
+			{
+				log << TestLog::Message << "Failure: additional segment length isn't monotonically decreasing with the fraction <n> - <f>, among edges with same final tessellation level" << TestLog::EndMessage
+					<< TestLog::Message << "Note: <f> stands for the clamped tessellation level and <n> for the final (rounded and clamped) tessellation level" << TestLog::EndMessage
+					<< TestLog::Message << "Note: two edges have tessellation levels " << prevData.tessLevel << " and " << curData.tessLevel << " respectively"
+										<< ", clamped " << prevClampedLevel << " and " << curClampedLevel << ", final " << prevFinalLevel << " and " << curFinalLevel
+										<< "; fractions are " << prevFraction << " and " << curFraction
+										<< ", but resulted in segment lengths " << prevData.additionalSegmentLength << " and " << curData.additionalSegmentLength << TestLog::EndMessage;
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+//! Compare triangle sets, ignoring triangle order and vertex order within triangle, and possibly exclude some triangles too.
+template <typename IsTriangleRelevantT>
+static bool compareTriangleSets (const vector<Vec3>&			coordsA,
+								 const vector<Vec3>&			coordsB,
+								 TestLog&						log,
+								 const IsTriangleRelevantT&		isTriangleRelevant,
+								 const char*					ignoredTriangleDescription = DE_NULL)
+{
+	typedef tcu::Vector<Vec3, 3>							Triangle;
+	typedef LexCompare<Triangle, 3, VecLexLessThan<3> >		TriangleLexLessThan;
+	typedef std::set<Triangle, TriangleLexLessThan>			TriangleSet;
+
+	DE_ASSERT(coordsA.size() % 3 == 0 && coordsB.size() % 3 == 0);
+
+	const int		numTrianglesA = (int)coordsA.size()/3;
+	const int		numTrianglesB = (int)coordsB.size()/3;
+	TriangleSet		trianglesA;
+	TriangleSet		trianglesB;
+
+	for (int aOrB = 0; aOrB < 2; aOrB++)
+	{
+		const vector<Vec3>&		coords			= aOrB == 0 ? coordsA			: coordsB;
+		const int				numTriangles	= aOrB == 0 ? numTrianglesA		: numTrianglesB;
+		TriangleSet&			triangles		= aOrB == 0 ? trianglesA		: trianglesB;
+
+		for (int triNdx = 0; triNdx < numTriangles; triNdx++)
+		{
+			Triangle triangle(coords[3*triNdx + 0],
+							  coords[3*triNdx + 1],
+							  coords[3*triNdx + 2]);
+
+			if (isTriangleRelevant(triangle.getPtr()))
+			{
+				std::sort(triangle.getPtr(), triangle.getPtr()+3, VecLexLessThan<3>());
+				triangles.insert(triangle);
+			}
+		}
+	}
+
+	{
+		TriangleSet::const_iterator aIt = trianglesA.begin();
+		TriangleSet::const_iterator bIt = trianglesB.begin();
+
+		while (aIt != trianglesA.end() || bIt != trianglesB.end())
+		{
+			const bool aEnd = aIt == trianglesA.end();
+			const bool bEnd = bIt == trianglesB.end();
+
+			if (aEnd || bEnd || *aIt != *bIt)
+			{
+				log << TestLog::Message << "Failure: triangle sets in two cases are not equal (when ignoring triangle and vertex order"
+					<< (ignoredTriangleDescription == DE_NULL ? "" : string() + ", and " + ignoredTriangleDescription) << ")" << TestLog::EndMessage;
+
+				if (!aEnd && (bEnd || TriangleLexLessThan()(*aIt, *bIt)))
+					log << TestLog::Message << "Note: e.g. triangle " << *aIt << " exists for first case but not for second" << TestLog::EndMessage;
+				else
+					log << TestLog::Message << "Note: e.g. triangle " << *bIt << " exists for second case but not for first" << TestLog::EndMessage;
+
+				return false;
+			}
+
+			++aIt;
+			++bIt;
+		}
+
+		return true;
+	}
+}
+
+static bool compareTriangleSets (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, TestLog& log)
+{
+	return compareTriangleSets(coordsA, coordsB, log, ConstantUnaryPredicate<const Vec3*, true>());
+}
+
+static void checkExtensionSupport (Context& context, const char* extName)
+{
+	if (!context.getContextInfo().isExtensionSupported(extName))
+		throw tcu::NotSupportedError(string(extName) + " not supported");
+}
+
+static void checkTessellationSupport (Context& context)
+{
+	checkExtensionSupport(context, "GL_EXT_tessellation_shader");
+}
+
+// Draw primitives with shared edges and check that no cracks are visible at the shared edges.
+class CommonEdgeCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_BASIC = 0,		//!< Order patch vertices such that when two patches share a vertex, it's at the same index for both.
+		CASETYPE_PRECISE,		//!< Vertex indices don't match like for CASETYPE_BASIC, but other measures are taken, using the 'precise' qualifier.
+
+		CASETYPE_LAST
+	};
+
+	CommonEdgeCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing, CaseType caseType)
+		: TestCase			(context, name, description)
+		, m_primitiveType	(primitiveType)
+		, m_spacing			(spacing)
+		, m_caseType		(caseType)
+	{
+		DE_ASSERT(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES || m_primitiveType == TESSPRIMITIVETYPE_QUADS);
+	}
+
+	void							init		(void);
+	void							deinit		(void);
+	IterateResult					iterate		(void);
+
+private:
+	static const int				RENDER_SIZE = 256;
+
+	const TessPrimitiveType			m_primitiveType;
+	const SpacingMode				m_spacing;
+	const CaseType					m_caseType;
+
+	SharedPtr<const ShaderProgram>	m_program;
+};
+
+void CommonEdgeCase::init (void)
+{
+	checkTessellationSupport(m_context);
+
+	if (m_caseType == CASETYPE_PRECISE)
+		checkExtensionSupport(m_context, "GL_EXT_gpu_shader5");
+
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+			<< glu::VertexSource					("#version 310 es\n"
+													 "\n"
+													 "in highp vec2 in_v_position;\n"
+													 "in highp float in_v_tessParam;\n"
+													 "\n"
+													 "out highp vec2 in_tc_position;\n"
+													 "out highp float in_tc_tessParam;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	in_tc_position = in_v_position;\n"
+													 "	in_tc_tessParam = in_v_tessParam;\n"
+													 "}\n")
+
+			<< glu::TessellationControlSource		("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 + string(m_caseType == CASETYPE_PRECISE ? "#extension GL_EXT_gpu_shader5 : require\n" : "") +
+													 "\n"
+													 "layout (vertices = " + string(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "3" : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? "4" : DE_NULL) + ") out;\n"
+													 "\n"
+													 "in highp vec2 in_tc_position[];\n"
+													 "in highp float in_tc_tessParam[];\n"
+													 "\n"
+													 "out highp vec2 in_te_position[];\n"
+													 "\n"
+													 + (m_caseType == CASETYPE_PRECISE ? "precise gl_TessLevelOuter;\n\n" : "") +
+													 "void main (void)\n"
+													 "{\n"
+													 "	in_te_position[gl_InvocationID] = in_tc_position[gl_InvocationID];\n"
+													 "\n"
+													 "	gl_TessLevelInner[0] = 5.0;\n"
+													 "	gl_TessLevelInner[1] = 5.0;\n"
+													 "\n"
+													 + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
+														"	gl_TessLevelOuter[0] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[1] + in_tc_tessParam[2]);\n"
+														"	gl_TessLevelOuter[1] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[2] + in_tc_tessParam[0]);\n"
+														"	gl_TessLevelOuter[2] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[0] + in_tc_tessParam[1]);\n"
+													  : m_primitiveType == TESSPRIMITIVETYPE_QUADS ?
+														"	gl_TessLevelOuter[0] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[0] + in_tc_tessParam[2]);\n"
+														"	gl_TessLevelOuter[1] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[1] + in_tc_tessParam[0]);\n"
+														"	gl_TessLevelOuter[2] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[3] + in_tc_tessParam[1]);\n"
+														"	gl_TessLevelOuter[3] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[2] + in_tc_tessParam[3]);\n"
+													  : DE_NULL) +
+													 "}\n")
+
+			<< glu::TessellationEvaluationSource	("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 + string(m_caseType == CASETYPE_PRECISE ? "#extension GL_EXT_gpu_shader5 : require\n" : "") +
+													 "\n"
+													 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing) +
+													 "\n"
+													 "in highp vec2 in_te_position[];\n"
+													 "\n"
+													 "out mediump vec4 in_f_color;\n"
+													 "\n"
+													 + (m_caseType == CASETYPE_PRECISE ? "precise gl_Position;\n\n" : "") +
+													 "void main (void)\n"
+													 "{\n"
+													 + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
+														string(m_caseType == CASETYPE_PRECISE
+															? "\t// Note: when this is an edge vertex, at most two of the following terms are non-zero (so order doesn't matter)\n"
+															: "") +
+														"	highp vec2 pos = gl_TessCoord.x*in_te_position[0] + gl_TessCoord.y*in_te_position[1] + gl_TessCoord.z*in_te_position[2];\n"
+														"\n"
+														"	highp float f = sqrt(3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z))) * 0.5 + 0.5;\n"
+														"	in_f_color = vec4(gl_TessCoord*f, 1.0);\n"
+													  : m_primitiveType == TESSPRIMITIVETYPE_QUADS ?
+													    string()
+														+ (m_caseType == CASETYPE_BASIC ?
+															"	highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[0]\n"
+															"	               + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[1]\n"
+															"	               + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[2]\n"
+															"	               + (    gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[3];\n"
+														 : m_caseType == CASETYPE_PRECISE ?
+															"	highp vec2 a = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[0];\n"
+															"	highp vec2 b = (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[1];\n"
+															"	highp vec2 c = (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[2];\n"
+															"	highp vec2 d = (    gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[3];\n"
+															"	// Note: when this is an edge vertex, at most two of the following terms are non-zero (so order doesn't matter)\n"
+															"	highp vec2 pos = a+b+c+d;\n"
+														 : DE_NULL) +
+														"\n"
+														"	highp float f = sqrt(1.0 - 2.0 * max(abs(gl_TessCoord.x - 0.5), abs(gl_TessCoord.y - 0.5)))*0.5 + 0.5;\n"
+														"	in_f_color = vec4(0.1, gl_TessCoord.xy*f, 1.0);\n"
+													  : DE_NULL) +
+													 "\n"
+													 "	// Offset the position slightly, based on the parity of the bits in the float representation.\n"
+													 "	// This is done to detect possible small differences in edge vertex positions between patches.\n"
+													 "	uvec2 bits = floatBitsToUint(pos);\n"
+													 "	uint numBits = 0;\n"
+													 "	for (int i = 0; i < 32; i++)\n"
+													 "		numBits += ((bits[0] >> i) & 1) + ((bits[1] >> i) & 1);\n"
+													 "	pos += float(numBits&1)*0.04;\n"
+													 "\n"
+													 "	gl_Position = vec4(pos, 0.0, 1.0);\n"
+													 "}\n")
+
+			<< glu::FragmentSource					("#version 310 es\n"
+													 "\n"
+													 "layout (location = 0) out mediump vec4 o_color;\n"
+													 "\n"
+													 "in mediump vec4 in_f_color;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	o_color = in_f_color;\n"
+													 "}\n")));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void CommonEdgeCase::deinit (void)
+{
+	m_program.clear();
+}
+
+CommonEdgeCase::IterateResult CommonEdgeCase::iterate (void)
+{
+	TestLog&					log						= m_testCtx.getLog();
+	const RenderContext&		renderCtx				= m_context.getRenderContext();
+	const RandomViewport		viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const deUint32				programGL				= m_program->getProgram();
+	const glw::Functions&		gl						= renderCtx.getFunctions();
+
+	const int					gridWidth				= 4;
+	const int					gridHeight				= 4;
+	const int					numVertices				= (gridWidth+1)*(gridHeight+1);
+	const int					numIndices				= gridWidth*gridHeight * (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3*2 : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? 4 : -1);
+	const int					numPosCompsPerVertex	= 2;
+	const int					totalNumPosComps		= numPosCompsPerVertex*numVertices;
+	vector<float>				gridPosComps;
+	vector<float>				gridTessParams;
+	vector<deUint16>			gridIndices;
+
+	gridPosComps.reserve(totalNumPosComps);
+	gridTessParams.reserve(numVertices);
+	gridIndices.reserve(numIndices);
+
+	{
+		Random constantSeedRnd(42);
+
+		for (int i = 0; i < gridHeight+1; i++)
+		for (int j = 0; j < gridWidth+1; j++)
+		{
+			gridPosComps.push_back(-1.0f + 2.0f * ((float)j + 0.5f) / (float)(gridWidth+1));
+			gridPosComps.push_back(-1.0f + 2.0f * ((float)i + 0.5f) / (float)(gridHeight+1));
+			gridTessParams.push_back((float)(i*(gridWidth+1) + j) / (float)(numVertices-1));
+		}
+	}
+
+	// Generate patch vertex indices.
+	// \note If CASETYPE_BASIC, the vertices are ordered such that when multiple
+	//		 triangles/quads share a vertex, it's at the same index for everyone.
+
+	if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+	{
+		for (int i = 0; i < gridHeight; i++)
+		for (int j = 0; j < gridWidth; j++)
+		{
+			const int corners[4] =
+			{
+				(i+0)*(gridWidth+1) + j+0,
+				(i+0)*(gridWidth+1) + j+1,
+				(i+1)*(gridWidth+1) + j+0,
+				(i+1)*(gridWidth+1) + j+1
+			};
+
+			const int secondTriangleVertexIndexOffset = m_caseType == CASETYPE_BASIC	? 0
+													  : m_caseType == CASETYPE_PRECISE	? 1
+													  : -1;
+			DE_ASSERT(secondTriangleVertexIndexOffset != -1);
+
+			for (int k = 0; k < 3; k++)
+				gridIndices.push_back(corners[(k+0 + i + (2-j%3)) % 3]);
+			for (int k = 0; k < 3; k++)
+				gridIndices.push_back(corners[(k+2 + i + (2-j%3) + secondTriangleVertexIndexOffset) % 3 + 1]);
+		}
+	}
+	else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
+	{
+		for (int i = 0; i < gridHeight; i++)
+		for (int j = 0; j < gridWidth; j++)
+		{
+			// \note The vertices are ordered such that when multiple quads
+			//		 share a vertices, it's at the same index for everyone.
+			for (int m = 0; m < 2; m++)
+			for (int n = 0; n < 2; n++)
+				gridIndices.push_back((i+(i+m)%2)*(gridWidth+1) + j+(j+n)%2);
+
+			if(m_caseType == CASETYPE_PRECISE && (i+j) % 2 == 0)
+				std::reverse(gridIndices.begin() + (gridIndices.size() - 4),
+							 gridIndices.begin() + gridIndices.size());
+		}
+	}
+	else
+		DE_ASSERT(false);
+
+	DE_ASSERT((int)gridPosComps.size() == totalNumPosComps);
+	DE_ASSERT((int)gridTessParams.size() == numVertices);
+	DE_ASSERT((int)gridIndices.size() == numIndices);
+
+	setViewport(gl, viewport);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.useProgram(programGL);
+
+	{
+		gl.patchParameteri(GL_PATCH_VERTICES, m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? 4 : -1);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+
+		const glu::VertexArrayBinding attrBindings[] =
+		{
+			glu::va::Float("in_v_position", numPosCompsPerVertex, numVertices, 0, &gridPosComps[0]),
+			glu::va::Float("in_v_tessParam", 1, numVertices, 0, &gridTessParams[0])
+		};
+
+		glu::draw(renderCtx, programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
+			glu::pr::Patches((int)gridIndices.size(), &gridIndices[0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
+	}
+
+	{
+		const tcu::Surface rendered = getPixels(renderCtx, viewport);
+
+		log << TestLog::Image("RenderedImage", "Rendered Image", rendered)
+			<< TestLog::Message << "Note: coloring is done to clarify the positioning and orientation of the "
+								<< (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "triangles" : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? "quads" : DE_NULL)
+								<< "; the color of a vertex corresponds to the index of that vertex in the patch"
+								<< TestLog::EndMessage;
+
+		if (m_caseType == CASETYPE_BASIC)
+			log << TestLog::Message << "Note: each shared vertex has the same index among the primitives it belongs to" << TestLog::EndMessage;
+		else if (m_caseType == CASETYPE_PRECISE)
+			log << TestLog::Message << "Note: the 'precise' qualifier is used to avoid cracks between primitives" << TestLog::EndMessage;
+		else
+			DE_ASSERT(false);
+
+		// Ad-hoc result verification - check that a certain rectangle in the image contains no black pixels.
+
+		const int startX	= (int)(0.15f * (float)rendered.getWidth());
+		const int endX		= (int)(0.85f * (float)rendered.getWidth());
+		const int startY	= (int)(0.15f * (float)rendered.getHeight());
+		const int endY		= (int)(0.85f * (float)rendered.getHeight());
+
+		for (int y = startY; y < endY; y++)
+		for (int x = startX; x < endX; x++)
+		{
+			const tcu::RGBA pixel = rendered.getPixel(x, y);
+
+			if (pixel.getRed() == 0 && pixel.getGreen() == 0 && pixel.getBlue() == 0)
+			{
+				log << TestLog::Message << "Failure: there seem to be cracks in the rendered result" << TestLog::EndMessage
+					<< TestLog::Message << "Note: pixel with zero r, g and b channels found at " << tcu::IVec2(x, y) << TestLog::EndMessage;
+
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+				return STOP;
+			}
+		}
+
+		log << TestLog::Message << "Success: there seem to be no cracks in the rendered result" << TestLog::EndMessage;
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+// Check tessellation coordinates (read with transform feedback).
+class TessCoordCase : public TestCase
+{
+public:
+					TessCoordCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing)
+						: TestCase			(context, name, description)
+						, m_primitiveType	(primitiveType)
+						, m_spacing			(spacing)
+					{
+					}
+
+	void			init		(void);
+	void			deinit		(void);
+	IterateResult	iterate		(void);
+
+private:
+	struct TessLevels
+	{
+		float inner[2];
+		float outer[4];
+	};
+
+	static const int				RENDER_SIZE = 16;
+
+	vector<TessLevels>				genTessLevelCases (void) const;
+
+	const TessPrimitiveType			m_primitiveType;
+	const SpacingMode				m_spacing;
+
+	SharedPtr<const ShaderProgram>	m_program;
+};
+
+void TessCoordCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+			<< glu::VertexSource					("#version 310 es\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "}\n")
+
+			<< glu::TessellationControlSource		("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 "layout (vertices = 1) out;\n"
+													 "\n"
+													 "uniform mediump float u_tessLevelInner0;\n"
+													 "uniform mediump float u_tessLevelInner1;\n"
+													 "\n"
+													 "uniform mediump float u_tessLevelOuter0;\n"
+													 "uniform mediump float u_tessLevelOuter1;\n"
+													 "uniform mediump float u_tessLevelOuter2;\n"
+													 "uniform mediump float u_tessLevelOuter3;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	gl_TessLevelInner[0] = u_tessLevelInner0;\n"
+													 "	gl_TessLevelInner[1] = u_tessLevelInner1;\n"
+													 "\n"
+													 "	gl_TessLevelOuter[0] = u_tessLevelOuter0;\n"
+													 "	gl_TessLevelOuter[1] = u_tessLevelOuter1;\n"
+													 "	gl_TessLevelOuter[2] = u_tessLevelOuter2;\n"
+													 "	gl_TessLevelOuter[3] = u_tessLevelOuter3;\n"
+													 "}\n")
+
+			<< glu::TessellationEvaluationSource	("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, true) +
+													 "\n"
+													 "out highp vec3 out_te_tessCoord;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	out_te_tessCoord = gl_TessCoord;\n"
+													 "	gl_Position = vec4(gl_TessCoord.xy*1.6 - 0.8, 0.0, 1.0);\n"
+													 "}\n")
+
+			<< glu::FragmentSource					("#version 310 es\n"
+													 "\n"
+													 "layout (location = 0) out mediump vec4 o_color;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	o_color = vec4(1.0);\n"
+													 "}\n")
+
+			<< glu::TransformFeedbackVarying		("out_te_tessCoord")
+			<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void TessCoordCase::deinit (void)
+{
+	m_program.clear();
+}
+
+vector<TessCoordCase::TessLevels> TessCoordCase::genTessLevelCases (void) const
+{
+	static const TessLevels rawTessLevelCases[] =
+	{
+		{ { 1.0f,	1.0f	},	{ 1.0f,		1.0f,	1.0f,	1.0f	} },
+		{ { 63.0f,	24.0f	},	{ 15.0f,	42.0f,	10.0f,	12.0f	} },
+		{ { 3.0f,	2.0f	},	{ 6.0f,		8.0f,	7.0f,	9.0f	} },
+		{ { 4.0f,	6.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
+		{ { 2.0f,	2.0f	},	{ 6.0f,		8.0f,	7.0f,	9.0f	} },
+		{ { 5.0f,	6.0f	},	{ 1.0f,		1.0f,	1.0f,	1.0f	} },
+		{ { 1.0f,	6.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
+		{ { 5.0f,	1.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
+		{ { 5.2f,	1.6f	},	{ 2.9f,		3.4f,	1.5f,	4.1f	} }
+	};
+
+	if (m_spacing == SPACINGMODE_EQUAL)
+		return vector<TessLevels>(DE_ARRAY_BEGIN(rawTessLevelCases), DE_ARRAY_END(rawTessLevelCases));
+	else
+	{
+		vector<TessLevels> result;
+		result.reserve(DE_LENGTH_OF_ARRAY(rawTessLevelCases));
+
+		for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < DE_LENGTH_OF_ARRAY(rawTessLevelCases); tessLevelCaseNdx++)
+		{
+			TessLevels curTessLevelCase = rawTessLevelCases[tessLevelCaseNdx];
+
+			float* const inner = &curTessLevelCase.inner[0];
+			float* const outer = &curTessLevelCase.outer[0];
+
+			for (int j = 0; j < 2; j++) inner[j] = (float)getClampedRoundedTessLevel(m_spacing, inner[j]);
+			for (int j = 0; j < 4; j++) outer[j] = (float)getClampedRoundedTessLevel(m_spacing, outer[j]);
+
+			if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+			{
+				if (outer[0] > 1.0f || outer[1] > 1.0f || outer[2] > 1.0f)
+				{
+					if (inner[0] == 1.0f)
+						inner[0] = (float)getClampedRoundedTessLevel(m_spacing, inner[0] + 0.1f);
+				}
+			}
+			else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
+			{
+				if (outer[0] > 1.0f || outer[1] > 1.0f || outer[2] > 1.0f || outer[3] > 1.0f)
+				{
+					if (inner[0] == 1.0f) inner[0] = (float)getClampedRoundedTessLevel(m_spacing, inner[0] + 0.1f);
+					if (inner[1] == 1.0f) inner[1] = (float)getClampedRoundedTessLevel(m_spacing, inner[1] + 0.1f);
+				}
+			}
+
+			result.push_back(curTessLevelCase);
+		}
+
+		DE_ASSERT((int)result.size() == DE_LENGTH_OF_ARRAY(rawTessLevelCases));
+		return result;
+	}
+}
+
+TessCoordCase::IterateResult TessCoordCase::iterate (void)
+{
+	typedef TransformFeedbackHandler<Vec3> TFHandler;
+
+	TestLog&						log							= m_testCtx.getLog();
+	const RenderContext&			renderCtx					= m_context.getRenderContext();
+	const RandomViewport			viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const deUint32					programGL					= m_program->getProgram();
+	const glw::Functions&			gl							= renderCtx.getFunctions();
+
+	const int						tessLevelInner0Loc			= gl.getUniformLocation(programGL, "u_tessLevelInner0");
+	const int						tessLevelInner1Loc			= gl.getUniformLocation(programGL, "u_tessLevelInner1");
+	const int						tessLevelOuter0Loc			= gl.getUniformLocation(programGL, "u_tessLevelOuter0");
+	const int						tessLevelOuter1Loc			= gl.getUniformLocation(programGL, "u_tessLevelOuter1");
+	const int						tessLevelOuter2Loc			= gl.getUniformLocation(programGL, "u_tessLevelOuter2");
+	const int						tessLevelOuter3Loc			= gl.getUniformLocation(programGL, "u_tessLevelOuter3");
+
+	const vector<TessLevels>		tessLevelCases				= genTessLevelCases();
+	vector<vector<Vec3> >			caseReferences				(tessLevelCases.size());
+
+	for (int i = 0; i < (int)tessLevelCases.size(); i++)
+		caseReferences[i] = generateReferenceTessCoords(m_primitiveType, m_spacing, &tessLevelCases[i].inner[0], &tessLevelCases[i].outer[0]);
+
+	const int						maxNumVertices				= (int)std::max_element(caseReferences.begin(), caseReferences.end(), SizeLessThan<vector<Vec3> >())->size();
+	const TFHandler					tfHandler					(m_context.getRenderContext(), maxNumVertices);
+
+	bool							success						= true;
+
+	setViewport(gl, viewport);
+	gl.useProgram(programGL);
+
+	gl.patchParameteri(GL_PATCH_VERTICES, 1);
+
+	for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++)
+	{
+		const float* const innerLevels = &tessLevelCases[tessLevelCaseNdx].inner[0];
+		const float* const outerLevels = &tessLevelCases[tessLevelCaseNdx].outer[0];
+
+		log << TestLog::Message << "Tessellation levels: " << tessellationLevelsString(innerLevels, outerLevels, m_primitiveType) << TestLog::EndMessage;
+
+		gl.uniform1f(tessLevelInner0Loc, innerLevels[0]);
+		gl.uniform1f(tessLevelInner1Loc, innerLevels[1]);
+		gl.uniform1f(tessLevelOuter0Loc, outerLevels[0]);
+		gl.uniform1f(tessLevelOuter1Loc, outerLevels[1]);
+		gl.uniform1f(tessLevelOuter2Loc, outerLevels[2]);
+		gl.uniform1f(tessLevelOuter3Loc, outerLevels[3]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");
+
+		{
+			const vector<Vec3>&			tessCoordsRef	= caseReferences[tessLevelCaseNdx];
+			const TFHandler::Result		tfResult		= tfHandler.renderAndGetPrimitives(programGL, GL_POINTS, 0, DE_NULL, 1);
+
+			if (tfResult.numPrimitives != (int)tessCoordsRef.size())
+			{
+				log << TestLog::Message << "Failure: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be "
+										<< tfResult.numPrimitives << ", reference value is " << tessCoordsRef.size()
+										<< " (logging further info anyway)" << TestLog::EndMessage;
+				success = false;
+			}
+			else
+				log << TestLog::Message << "Note: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be " << tfResult.numPrimitives << TestLog::EndMessage;
+
+			if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+				log << TestLog::Message << "Note: in the following visualization(s), the u=1, v=1, w=1 corners are at the right, top, and left corners, respectively" << TestLog::EndMessage;
+			else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS || m_primitiveType == TESSPRIMITIVETYPE_ISOLINES)
+				log << TestLog::Message << "Note: in the following visualization(s), u and v coordinate go left-to-right and bottom-to-top, respectively" << TestLog::EndMessage;
+			else
+				DE_ASSERT(false);
+
+			success = compareTessCoords(log, m_primitiveType, tessCoordsRef, tfResult.varying) && success;
+		}
+
+		if (!success)
+			break;
+		else
+			log << TestLog::Message << "All OK" << TestLog::EndMessage;
+	}
+
+	m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Invalid tessellation coordinates");
+	return STOP;
+}
+
+// Check validity of fractional spacing modes. Draws a single isoline, reads tesscoords with transform feedback.
+class FractionalSpacingModeCase : public TestCase
+{
+public:
+					FractionalSpacingModeCase (Context& context, const char* name, const char* description, SpacingMode spacing)
+						: TestCase	(context, name, description)
+						, m_spacing	(spacing)
+					{
+						DE_ASSERT(m_spacing == SPACINGMODE_FRACTIONAL_EVEN || m_spacing == SPACINGMODE_FRACTIONAL_ODD);
+					}
+
+	void			init		(void);
+	void			deinit		(void);
+	IterateResult	iterate		(void);
+
+private:
+	static const int				RENDER_SIZE = 16;
+
+	static vector<float>			genTessLevelCases (void);
+
+	const SpacingMode				m_spacing;
+
+	SharedPtr<const ShaderProgram>	m_program;
+};
+
+void FractionalSpacingModeCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+			<< glu::VertexSource					("#version 310 es\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "}\n")
+
+			<< glu::TessellationControlSource		("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 "layout (vertices = 1) out;\n"
+													 "\n"
+													 "uniform mediump float u_tessLevelOuter1;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	gl_TessLevelOuter[0] = 1.0;\n"
+													 "	gl_TessLevelOuter[1] = u_tessLevelOuter1;\n"
+													 "}\n")
+
+			<< glu::TessellationEvaluationSource	("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_ISOLINES, m_spacing, true) +
+													 "\n"
+													 "out highp float out_te_tessCoord;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	out_te_tessCoord = gl_TessCoord.x;\n"
+													 "	gl_Position = vec4(gl_TessCoord.xy*1.6 - 0.8, 0.0, 1.0);\n"
+													 "}\n")
+
+			<< glu::FragmentSource					("#version 310 es\n"
+													 "\n"
+													 "layout (location = 0) out mediump vec4 o_color;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	o_color = vec4(1.0);\n"
+													 "}\n")
+
+			<< glu::TransformFeedbackVarying		("out_te_tessCoord")
+			<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void FractionalSpacingModeCase::deinit (void)
+{
+	m_program.clear();
+}
+
+vector<float> FractionalSpacingModeCase::genTessLevelCases (void)
+{
+	vector<float> result;
+
+	// Ranges [7.0 .. 8.0), [8.0 .. 9.0) and [9.0 .. 10.0)
+	{
+		static const float	rangeStarts[]		= { 7.0f, 8.0f, 9.0f };
+		const int			numSamplesPerRange	= 10;
+
+		for (int rangeNdx = 0; rangeNdx < DE_LENGTH_OF_ARRAY(rangeStarts); rangeNdx++)
+			for (int i = 0; i < numSamplesPerRange; i++)
+				result.push_back(rangeStarts[rangeNdx] + (float)i/(float)numSamplesPerRange);
+	}
+
+	// 0.3, 1.3, 2.3,  ... , 62.3
+	for (int i = 0; i <= 62; i++)
+		result.push_back((float)i + 0.3f);
+
+	return result;
+}
+
+FractionalSpacingModeCase::IterateResult FractionalSpacingModeCase::iterate (void)
+{
+	typedef TransformFeedbackHandler<float> TFHandler;
+
+	TestLog&						log							= m_testCtx.getLog();
+	const RenderContext&			renderCtx					= m_context.getRenderContext();
+	const RandomViewport			viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const deUint32					programGL					= m_program->getProgram();
+	const glw::Functions&			gl							= renderCtx.getFunctions();
+
+	const int						tessLevelOuter1Loc			= gl.getUniformLocation(programGL, "u_tessLevelOuter1");
+
+	// Second outer tessellation levels.
+	const vector<float>				tessLevelCases				= genTessLevelCases();
+	const int						maxNumVertices				= 1 + getClampedRoundedTessLevel(m_spacing, *std::max_element(tessLevelCases.begin(), tessLevelCases.end()));
+	vector<float>					additionalSegmentLengths;
+	vector<int>						additionalSegmentLocations;
+
+	const TFHandler					tfHandler					(m_context.getRenderContext(), maxNumVertices);
+
+	bool							success						= true;
+
+	setViewport(gl, viewport);
+	gl.useProgram(programGL);
+
+	gl.patchParameteri(GL_PATCH_VERTICES, 1);
+
+	for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++)
+	{
+		const float outerLevel1 = tessLevelCases[tessLevelCaseNdx];
+
+		gl.uniform1f(tessLevelOuter1Loc, outerLevel1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");
+
+		{
+			const TFHandler::Result		tfResult = tfHandler.renderAndGetPrimitives(programGL, GL_POINTS, 0, DE_NULL, 1);
+			float						additionalSegmentLength;
+			int							additionalSegmentLocation;
+
+			success = verifyFractionalSpacingSingle(log, m_spacing, outerLevel1, tfResult.varying,
+													additionalSegmentLength, additionalSegmentLocation);
+
+			if (!success)
+				break;
+
+			additionalSegmentLengths.push_back(additionalSegmentLength);
+			additionalSegmentLocations.push_back(additionalSegmentLocation);
+		}
+	}
+
+	if (success)
+		success = verifyFractionalSpacingMultiple(log, m_spacing, tessLevelCases, additionalSegmentLengths, additionalSegmentLocations);
+
+	m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Invalid tessellation coordinates");
+	return STOP;
+}
+
+// Base class for a case with one input attribute (in_v_position) and optionally a TCS; tests with a couple of different sets of tessellation levels.
+class BasicVariousTessLevelsPosAttrCase : public TestCase
+{
+public:
+					BasicVariousTessLevelsPosAttrCase (Context&				context,
+													   const char*			name,
+													   const char*			description,
+													   TessPrimitiveType	primitiveType,
+													   SpacingMode			spacing,
+													   const char*			referenceImagePathPrefix)
+						: TestCase						(context, name, description)
+						, m_primitiveType				(primitiveType)
+						, m_spacing						(spacing)
+						, m_referenceImagePathPrefix	(referenceImagePathPrefix)
+					{
+					}
+
+	void			init			(void);
+	void			deinit			(void);
+	IterateResult	iterate			(void);
+
+protected:
+	virtual const glu::ProgramSources	makeSources (TessPrimitiveType, SpacingMode, const char* vtxOutPosAttrName) const = DE_NULL;
+
+private:
+	static const int					RENDER_SIZE = 256;
+
+	const TessPrimitiveType				m_primitiveType;
+	const SpacingMode					m_spacing;
+	const string						m_referenceImagePathPrefix;
+
+	SharedPtr<const ShaderProgram>		m_program;
+};
+
+void BasicVariousTessLevelsPosAttrCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	{
+		glu::ProgramSources sources = makeSources(m_primitiveType, m_spacing, "in_tc_position");
+		DE_ASSERT(sources.sources[glu::SHADERTYPE_TESSELLATION_CONTROL].empty());
+
+		sources << glu::TessellationControlSource(	"#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													"\n"
+													"layout (vertices = " + string(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "3" : "4") + ") out;\n"
+													"\n"
+													"in highp vec2 in_tc_position[];\n"
+													"\n"
+													"out highp vec2 in_te_position[];\n"
+													"\n"
+													"uniform mediump float u_tessLevelInner0;\n"
+													"uniform mediump float u_tessLevelInner1;\n"
+													"uniform mediump float u_tessLevelOuter0;\n"
+													"uniform mediump float u_tessLevelOuter1;\n"
+													"uniform mediump float u_tessLevelOuter2;\n"
+													"uniform mediump float u_tessLevelOuter3;\n"
+													"\n"
+													"void main (void)\n"
+													"{\n"
+													"	in_te_position[gl_InvocationID] = in_tc_position[gl_InvocationID];\n"
+													"\n"
+													"	gl_TessLevelInner[0] = u_tessLevelInner0;\n"
+													"	gl_TessLevelInner[1] = u_tessLevelInner1;\n"
+													"\n"
+													"	gl_TessLevelOuter[0] = u_tessLevelOuter0;\n"
+													"	gl_TessLevelOuter[1] = u_tessLevelOuter1;\n"
+													"	gl_TessLevelOuter[2] = u_tessLevelOuter2;\n"
+													"	gl_TessLevelOuter[3] = u_tessLevelOuter3;\n"
+													"}\n");
+
+		m_program = SharedPtr<const ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(), sources));
+	}
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void BasicVariousTessLevelsPosAttrCase::deinit (void)
+{
+	m_program.clear();
+}
+
+BasicVariousTessLevelsPosAttrCase::IterateResult BasicVariousTessLevelsPosAttrCase::iterate (void)
+{
+	static const struct
+	{
+		float inner[2];
+		float outer[4];
+	} tessLevelCases[] =
+	{
+		{ { 9.0f,	9.0f	},	{ 9.0f,		9.0f,	9.0f,	9.0f	} },
+		{ { 8.0f,	11.0f	},	{ 13.0f,	15.0f,	18.0f,	21.0f	} },
+		{ { 17.0f,	14.0f	},	{ 3.0f,		6.0f,	9.0f,	12.0f	} }
+	};
+
+	TestLog&				log					= m_testCtx.getLog();
+	const RenderContext&	renderCtx			= m_context.getRenderContext();
+	const RandomViewport	viewport			(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const deUint32			programGL			= m_program->getProgram();
+	const glw::Functions&	gl					= renderCtx.getFunctions();
+	const int				patchSize			= m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES	? 3
+												: m_primitiveType == TESSPRIMITIVETYPE_QUADS		? 4
+												: m_primitiveType == TESSPRIMITIVETYPE_ISOLINES		? 4
+												: -1;
+
+	setViewport(gl, viewport);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.useProgram(programGL);
+
+	gl.patchParameteri(GL_PATCH_VERTICES, patchSize);
+
+	for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < DE_LENGTH_OF_ARRAY(tessLevelCases); tessLevelCaseNdx++)
+	{
+		float innerLevels[2];
+		float outerLevels[4];
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(innerLevels); i++)
+			innerLevels[i] = (float)getClampedRoundedTessLevel(m_spacing, tessLevelCases[tessLevelCaseNdx].inner[i]);
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(outerLevels); i++)
+			outerLevels[i] = (float)getClampedRoundedTessLevel(m_spacing, tessLevelCases[tessLevelCaseNdx].outer[i]);
+
+		log << TestLog::Message << "Tessellation levels: " << tessellationLevelsString(&innerLevels[0], &outerLevels[0], m_primitiveType) << TestLog::EndMessage;
+
+		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelInner0"), innerLevels[0]);
+		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelInner1"), innerLevels[1]);
+		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter0"), outerLevels[0]);
+		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter1"), outerLevels[1]);
+		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter2"), outerLevels[2]);
+		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter3"), outerLevels[3]);
+
+		gl.clear(GL_COLOR_BUFFER_BIT);
+
+		{
+			vector<Vec2> positions;
+			positions.reserve(4);
+
+			if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+			{
+				positions.push_back(Vec2( 0.8f,    0.6f));
+				positions.push_back(Vec2( 0.0f, -0.786f));
+				positions.push_back(Vec2(-0.8f,    0.6f));
+			}
+			else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS || m_primitiveType == TESSPRIMITIVETYPE_ISOLINES)
+			{
+				positions.push_back(Vec2(-0.8f, -0.8f));
+				positions.push_back(Vec2( 0.8f, -0.8f));
+				positions.push_back(Vec2(-0.8f,  0.8f));
+				positions.push_back(Vec2( 0.8f,  0.8f));
+			}
+			else
+				DE_ASSERT(false);
+
+			DE_ASSERT((int)positions.size() == patchSize);
+
+			const glu::VertexArrayBinding attrBindings[] =
+			{
+				glu::va::Float("in_v_position", 2, (int)positions.size(), 0, &positions[0].x())
+			};
+
+			glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
+				glu::pr::Patches(patchSize));
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
+		}
+
+		{
+			const tcu::Surface			rendered	= getPixels(renderCtx, viewport);
+			const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePathPrefix + "_" + de::toString(tessLevelCaseNdx) + ".png");
+			const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), rendered.getAccess(), 0.002f, tcu::COMPARE_LOG_RESULT);
+
+			if (!success)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+				return STOP;
+			}
+		}
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+// Test that there are no obvious gaps in the triangulation of a tessellated triangle or quad.
+class BasicTriangleFillCoverCase : public BasicVariousTessLevelsPosAttrCase
+{
+public:
+	BasicTriangleFillCoverCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing, const char* referenceImagePathPrefix)
+		: BasicVariousTessLevelsPosAttrCase (context, name, description, primitiveType, spacing, referenceImagePathPrefix)
+	{
+		DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
+	}
+
+protected:
+	const glu::ProgramSources makeSources (TessPrimitiveType primitiveType, SpacingMode spacing, const char* vtxOutPosAttrName) const
+	{
+		return glu::ProgramSources()
+			<< glu::VertexSource					("#version 310 es\n"
+													 "\n"
+													 "in highp vec2 in_v_position;\n"
+													 "\n"
+													 "out highp vec2 " + string(vtxOutPosAttrName) + ";\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	" + vtxOutPosAttrName + " = in_v_position;\n"
+													 "}\n")
+
+			<< glu::TessellationEvaluationSource	("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 + getTessellationEvaluationInLayoutString(primitiveType, spacing) +
+													 "\n"
+													 "in highp vec2 in_te_position[];\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 + (primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
+														"\n"
+														"	highp float d = 3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z));\n"
+														"	highp vec2 corner0 = in_te_position[0];\n"
+														"	highp vec2 corner1 = in_te_position[1];\n"
+														"	highp vec2 corner2 = in_te_position[2];\n"
+														"	highp vec2 pos =  corner0*gl_TessCoord.x + corner1*gl_TessCoord.y + corner2*gl_TessCoord.z;\n"
+														"	highp vec2 fromCenter = pos - (corner0 + corner1 + corner2) / 3.0;\n"
+														"	highp float f = (1.0 - length(fromCenter)) * (1.5 - d);\n"
+														"	pos += 0.75 * f * fromCenter / (length(fromCenter) + 0.3);\n"
+														"	gl_Position = vec4(pos, 0.0, 1.0);\n"
+													  : primitiveType == TESSPRIMITIVETYPE_QUADS ?
+														"	highp vec2 corner0 = in_te_position[0];\n"
+														"	highp vec2 corner1 = in_te_position[1];\n"
+														"	highp vec2 corner2 = in_te_position[2];\n"
+														"	highp vec2 corner3 = in_te_position[3];\n"
+														"	highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n"
+														"	               + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n"
+														"	               + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*corner2\n"
+														"	               + (    gl_TessCoord.x)*(    gl_TessCoord.y)*corner3;\n"
+														"	highp float d = 2.0 * min(abs(gl_TessCoord.x-0.5), abs(gl_TessCoord.y-0.5));\n"
+														"	highp vec2 fromCenter = pos - (corner0 + corner1 + corner2 + corner3) / 4.0;\n"
+														"	highp float f = (1.0 - length(fromCenter)) * sqrt(1.7 - d);\n"
+														"	pos += 0.75 * f * fromCenter / (length(fromCenter) + 0.3);\n"
+														"	gl_Position = vec4(pos, 0.0, 1.0);\n"
+													  : DE_NULL) +
+													 "}\n")
+
+			<< glu::FragmentSource					("#version 310 es\n"
+													 "\n"
+													 "layout (location = 0) out mediump vec4 o_color;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	o_color = vec4(1.0);\n"
+													 "}\n");
+	}
+};
+
+// Check that there are no obvious overlaps in the triangulation of a tessellated triangle or quad.
+class BasicTriangleFillNonOverlapCase : public BasicVariousTessLevelsPosAttrCase
+{
+public:
+	BasicTriangleFillNonOverlapCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing, const char* referenceImagePathPrefix)
+		: BasicVariousTessLevelsPosAttrCase (context, name, description, primitiveType, spacing, referenceImagePathPrefix)
+	{
+		DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
+	}
+
+protected:
+	const glu::ProgramSources makeSources (TessPrimitiveType primitiveType, SpacingMode spacing, const char* vtxOutPosAttrName) const
+	{
+		return glu::ProgramSources()
+			<< glu::VertexSource					("#version 310 es\n"
+													 "\n"
+													 "in highp vec2 in_v_position;\n"
+													 "\n"
+													 "out highp vec2 " + string(vtxOutPosAttrName) + ";\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	" + vtxOutPosAttrName + " = in_v_position;\n"
+													 "}\n")
+
+			<< glu::TessellationEvaluationSource	("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 + getTessellationEvaluationInLayoutString(primitiveType, spacing) +
+													 "\n"
+													 "in highp vec2 in_te_position[];\n"
+													 "\n"
+													 "out mediump vec4 in_f_color;\n"
+													 "\n"
+													 "uniform mediump float u_tessLevelInner0;\n"
+													 "uniform mediump float u_tessLevelInner1;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 + (primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
+														"\n"
+														"	highp vec2 corner0 = in_te_position[0];\n"
+														"	highp vec2 corner1 = in_te_position[1];\n"
+														"	highp vec2 corner2 = in_te_position[2];\n"
+														"	highp vec2 pos =  corner0*gl_TessCoord.x + corner1*gl_TessCoord.y + corner2*gl_TessCoord.z;\n"
+														"	gl_Position = vec4(pos, 0.0, 1.0);\n"
+														"	highp int numConcentricTriangles = int(round(u_tessLevelInner0)) / 2 + 1;\n"
+														"	highp float d = 3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z));\n"
+														"	highp int phase = int(d*float(numConcentricTriangles)) % 3;\n"
+														"	in_f_color = phase == 0 ? vec4(1.0, 0.0, 0.0, 1.0)\n"
+														"	           : phase == 1 ? vec4(0.0, 1.0, 0.0, 1.0)\n"
+														"	           :              vec4(0.0, 0.0, 1.0, 1.0);\n"
+													  : primitiveType == TESSPRIMITIVETYPE_QUADS ?
+														"	highp vec2 corner0 = in_te_position[0];\n"
+														"	highp vec2 corner1 = in_te_position[1];\n"
+														"	highp vec2 corner2 = in_te_position[2];\n"
+														"	highp vec2 corner3 = in_te_position[3];\n"
+														"	highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n"
+														"	               + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n"
+														"	               + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*corner2\n"
+														"	               + (    gl_TessCoord.x)*(    gl_TessCoord.y)*corner3;\n"
+														"	gl_Position = vec4(pos, 0.0, 1.0);\n"
+														"	highp int phaseX = int(round((0.5 - abs(gl_TessCoord.x-0.5)) * u_tessLevelInner0));\n"
+														"	highp int phaseY = int(round((0.5 - abs(gl_TessCoord.y-0.5)) * u_tessLevelInner1));\n"
+														"	highp int phase = min(phaseX, phaseY) % 3;\n"
+														"	in_f_color = phase == 0 ? vec4(1.0, 0.0, 0.0, 1.0)\n"
+														"	           : phase == 1 ? vec4(0.0, 1.0, 0.0, 1.0)\n"
+														"	           :              vec4(0.0, 0.0, 1.0, 1.0);\n"
+													  : DE_NULL) +
+													 "}\n")
+
+			<< glu::FragmentSource					("#version 310 es\n"
+													 "\n"
+													 "layout (location = 0) out mediump vec4 o_color;\n"
+													 "\n"
+													 "in mediump vec4 in_f_color;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	o_color = in_f_color;\n"
+													 "}\n");
+	}
+};
+
+// Basic isolines rendering case.
+class IsolinesRenderCase : public BasicVariousTessLevelsPosAttrCase
+{
+public:
+	IsolinesRenderCase (Context& context, const char* name, const char* description, SpacingMode spacing, const char* referenceImagePathPrefix)
+		: BasicVariousTessLevelsPosAttrCase (context, name, description, TESSPRIMITIVETYPE_ISOLINES, spacing, referenceImagePathPrefix)
+	{
+	}
+
+protected:
+	const glu::ProgramSources makeSources (TessPrimitiveType primitiveType, SpacingMode spacing, const char* vtxOutPosAttrName) const
+	{
+		DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_ISOLINES);
+		DE_UNREF(primitiveType);
+
+		return glu::ProgramSources()
+			<< glu::VertexSource					("#version 310 es\n"
+													 "\n"
+													 "in highp vec2 in_v_position;\n"
+													 "\n"
+													 "out highp vec2 " + string(vtxOutPosAttrName) + ";\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	" + vtxOutPosAttrName + " = in_v_position;\n"
+													 "}\n")
+
+			<< glu::TessellationEvaluationSource	("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_ISOLINES, spacing) +
+													 "\n"
+													 "in highp vec2 in_te_position[];\n"
+													 "\n"
+													 "out mediump vec4 in_f_color;\n"
+													 "\n"
+													 "uniform mediump float u_tessLevelOuter0;\n"
+													 "uniform mediump float u_tessLevelOuter1;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	highp vec2 corner0 = in_te_position[0];\n"
+													 "	highp vec2 corner1 = in_te_position[1];\n"
+													 "	highp vec2 corner2 = in_te_position[2];\n"
+													 "	highp vec2 corner3 = in_te_position[3];\n"
+													 "	highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n"
+													 "	               + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n"
+													 "	               + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*corner2\n"
+													 "	               + (    gl_TessCoord.x)*(    gl_TessCoord.y)*corner3;\n"
+													 "	pos.y += 0.15*sin(gl_TessCoord.x*10.0);\n"
+													 "	gl_Position = vec4(pos, 0.0, 1.0);\n"
+													 "	highp int phaseX = int(round(gl_TessCoord.x*u_tessLevelOuter1));\n"
+													 "	highp int phaseY = int(round(gl_TessCoord.y*u_tessLevelOuter0));\n"
+													 "	highp int phase = (phaseX + phaseY) % 3;\n"
+													 "	in_f_color = phase == 0 ? vec4(1.0, 0.0, 0.0, 1.0)\n"
+													 "	           : phase == 1 ? vec4(0.0, 1.0, 0.0, 1.0)\n"
+													 "	           :              vec4(0.0, 0.0, 1.0, 1.0);\n"
+													 "}\n")
+
+			<< glu::FragmentSource					("#version 310 es\n"
+													 "\n"
+													 "layout (location = 0) out mediump vec4 o_color;\n"
+													 "\n"
+													 "in mediump vec4 in_f_color;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	o_color = in_f_color;\n"
+													 "}\n");
+	}
+};
+
+// Test the "cw" and "ccw" TES input layout qualifiers.
+class WindingCase : public TestCase
+{
+public:
+					WindingCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, Winding winding)
+						: TestCase			(context, name, description)
+						, m_primitiveType	(primitiveType)
+						, m_winding			(winding)
+					{
+						DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
+					}
+
+	void			init		(void);
+	void			deinit		(void);
+	IterateResult	iterate		(void);
+
+private:
+	static const int				RENDER_SIZE = 64;
+
+	const TessPrimitiveType			m_primitiveType;
+	const Winding					m_winding;
+
+	SharedPtr<const ShaderProgram>	m_program;
+};
+
+void WindingCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+			<< glu::VertexSource					("#version 310 es\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "}\n")
+
+			<< glu::TessellationControlSource		("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 "layout (vertices = 1) out;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	gl_TessLevelInner[0] = 5.0;\n"
+													 "	gl_TessLevelInner[1] = 5.0;\n"
+													 "\n"
+													 "	gl_TessLevelOuter[0] = 5.0;\n"
+													 "	gl_TessLevelOuter[1] = 5.0;\n"
+													 "	gl_TessLevelOuter[2] = 5.0;\n"
+													 "	gl_TessLevelOuter[3] = 5.0;\n"
+													 "}\n")
+
+			<< glu::TessellationEvaluationSource	("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 + getTessellationEvaluationInLayoutString(m_primitiveType, m_winding) +
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	gl_Position = vec4(gl_TessCoord.xy*2.0 - 1.0, 0.0, 1.0);\n"
+													 "}\n")
+
+			<< glu::FragmentSource					("#version 310 es\n"
+													 "\n"
+													 "layout (location = 0) out mediump vec4 o_color;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	o_color = vec4(1.0);\n"
+													 "}\n")));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void WindingCase::deinit (void)
+{
+	m_program.clear();
+}
+
+WindingCase::IterateResult WindingCase::iterate (void)
+{
+	TestLog&						log							= m_testCtx.getLog();
+	const RenderContext&			renderCtx					= m_context.getRenderContext();
+	const RandomViewport			viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const deUint32					programGL					= m_program->getProgram();
+	const glw::Functions&			gl							= renderCtx.getFunctions();
+	const glu::VertexArray			vao							(renderCtx);
+
+	bool							success						= true;
+
+	setViewport(gl, viewport);
+	gl.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
+	gl.useProgram(programGL);
+
+	gl.patchParameteri(GL_PATCH_VERTICES, 1);
+
+	gl.enable(GL_CULL_FACE);
+
+	gl.bindVertexArray(*vao);
+
+	log << TestLog::Message << "Face culling enabled" << TestLog::EndMessage;
+
+	for (int frontFaceWinding = 0; frontFaceWinding < WINDING_LAST; frontFaceWinding++)
+	{
+		log << TestLog::Message << "Setting glFrontFace(" << (frontFaceWinding == WINDING_CW ? "GL_CW" : "GL_CCW") << ")" << TestLog::EndMessage;
+
+		gl.frontFace(frontFaceWinding == WINDING_CW ? GL_CW : GL_CCW);
+
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.drawArrays(GL_PATCHES, 0, 1);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
+
+		{
+			const tcu::Surface rendered = getPixels(renderCtx, viewport);
+			log << TestLog::Image("RenderedImage", "Rendered Image", rendered);
+
+			{
+				const int totalNumPixels		= rendered.getWidth()*rendered.getHeight();
+				const int badPixelTolerance		= m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 5*de::max(rendered.getWidth(), rendered.getHeight()) : 0;
+
+				int numWhitePixels	= 0;
+				int numRedPixels	= 0;
+				for (int y = 0; y < rendered.getHeight();	y++)
+				for (int x = 0; x < rendered.getWidth();	x++)
+				{
+					numWhitePixels	+= rendered.getPixel(x, y) == tcu::RGBA::white	? 1 : 0;
+					numRedPixels	+= rendered.getPixel(x, y) == tcu::RGBA::red	? 1 : 0;
+				}
+
+				DE_ASSERT(numWhitePixels + numRedPixels <= totalNumPixels);
+
+				log << TestLog::Message << "Note: got " << numWhitePixels << " white and " << numRedPixels << " red pixels" << TestLog::EndMessage;
+
+				if (totalNumPixels - numWhitePixels - numRedPixels > badPixelTolerance)
+				{
+					log << TestLog::Message << "Failure: Got " << totalNumPixels - numWhitePixels - numRedPixels << " other than white or red pixels (maximum tolerance " << badPixelTolerance << ")" << TestLog::EndMessage;
+					success = false;
+					break;
+				}
+
+				if ((Winding)frontFaceWinding == m_winding)
+				{
+					if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+					{
+						if (de::abs(numWhitePixels - totalNumPixels/2) > badPixelTolerance)
+						{
+							log << TestLog::Message << "Failure: wrong number of white pixels; expected approximately " << totalNumPixels/2 << TestLog::EndMessage;
+							success = false;
+							break;
+						}
+					}
+					else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
+					{
+						if (numWhitePixels != totalNumPixels)
+						{
+							log << TestLog::Message << "Failure: expected only white pixels (full-viewport quad)" << TestLog::EndMessage;
+							success = false;
+							break;
+						}
+					}
+					else
+						DE_ASSERT(false);
+				}
+				else
+				{
+					if (numWhitePixels != 0)
+					{
+						log << TestLog::Message << "Failure: expected only red pixels (everything culled)" << TestLog::EndMessage;
+						success = false;
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Image verification failed");
+	return STOP;
+}
+
+// Test potentially differing input and output patch sizes.
+class PatchVertexCountCase : public TestCase
+{
+public:
+	PatchVertexCountCase (Context& context, const char* name, const char* description, int inputPatchSize, int outputPatchSize, const char* referenceImagePath)
+		: TestCase				(context, name, description)
+		, m_inputPatchSize		(inputPatchSize)
+		, m_outputPatchSize		(outputPatchSize)
+		, m_referenceImagePath	(referenceImagePath)
+	{
+	}
+
+	void							init				(void);
+	void							deinit				(void);
+	IterateResult					iterate				(void);
+
+private:
+	static const int				RENDER_SIZE = 256;
+
+	const int						m_inputPatchSize;
+	const int						m_outputPatchSize;
+
+	const string					m_referenceImagePath;
+
+	SharedPtr<const ShaderProgram>	m_program;
+};
+
+void PatchVertexCountCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	const string inSizeStr		= de::toString(m_inputPatchSize);
+	const string outSizeStr		= de::toString(m_outputPatchSize);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+			<< glu::VertexSource					("#version 310 es\n"
+													 "\n"
+													 "in highp float in_v_attr;\n"
+													 "\n"
+													 "out highp float in_tc_attr;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	in_tc_attr = in_v_attr;\n"
+													 "}\n")
+
+			<< glu::TessellationControlSource		("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 "layout (vertices = " + outSizeStr + ") out;\n"
+													 "\n"
+													 "in highp float in_tc_attr[];\n"
+													 "\n"
+													 "out highp float in_te_attr[];\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID*" + inSizeStr + "/" + outSizeStr + "];\n"
+													 "\n"
+													 "	gl_TessLevelInner[0] = 5.0;\n"
+													 "	gl_TessLevelInner[1] = 5.0;\n"
+													 "\n"
+													"	gl_TessLevelOuter[0] = 5.0;\n"
+													"	gl_TessLevelOuter[1] = 5.0;\n"
+													"	gl_TessLevelOuter[2] = 5.0;\n"
+													"	gl_TessLevelOuter[3] = 5.0;\n"
+													 "}\n")
+
+			<< glu::TessellationEvaluationSource	("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) +
+													 "\n"
+													 "in highp float in_te_attr[];\n"
+													 "\n"
+													 "out mediump vec4 in_f_color;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	highp float x = gl_TessCoord.x*2.0 - 1.0;\n"
+													 "	highp float y = gl_TessCoord.y - in_te_attr[int(round(gl_TessCoord.x*float(" + outSizeStr + "-1)))];\n"
+													 "	gl_Position = vec4(x, y, 0.0, 1.0);\n"
+													 "	in_f_color = vec4(1.0);\n"
+													 "}\n")
+
+			<< glu::FragmentSource					("#version 310 es\n"
+													 "\n"
+													 "layout (location = 0) out mediump vec4 o_color;\n"
+													 "\n"
+													 "in mediump vec4 in_f_color;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	o_color = in_f_color;\n"
+													 "}\n")));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void PatchVertexCountCase::deinit (void)
+{
+	m_program.clear();
+}
+
+PatchVertexCountCase::IterateResult PatchVertexCountCase::iterate (void)
+{
+	TestLog&					log						= m_testCtx.getLog();
+	const RenderContext&		renderCtx				= m_context.getRenderContext();
+	const RandomViewport		viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const deUint32				programGL				= m_program->getProgram();
+	const glw::Functions&		gl						= renderCtx.getFunctions();
+
+	setViewport(gl, viewport);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.useProgram(programGL);
+
+	log << TestLog::Message << "Note: input patch size is " << m_inputPatchSize << ", output patch size is " << m_outputPatchSize << TestLog::EndMessage;
+
+	{
+		vector<float> attributeData;
+		attributeData.reserve(m_inputPatchSize);
+
+		for (int i = 0; i < m_inputPatchSize; i++)
+		{
+			const float f = (float)i / (float)(m_inputPatchSize-1);
+			attributeData.push_back(f*f);
+		}
+
+		gl.patchParameteri(GL_PATCH_VERTICES, m_inputPatchSize);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+
+		const glu::VertexArrayBinding attrBindings[] =
+		{
+			glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])
+		};
+
+		glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
+			glu::pr::Patches(m_inputPatchSize));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
+	}
+
+	{
+		const tcu::Surface			rendered	= getPixels(renderCtx, viewport);
+		const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePath);
+		const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);
+
+		m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Image comparison failed");
+		return STOP;
+	}
+}
+
+// Test per-patch inputs/outputs.
+class PerPatchDataCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_PRIMITIVE_ID_TCS = 0,
+		CASETYPE_PRIMITIVE_ID_TES,
+		CASETYPE_PATCH_VERTICES_IN_TCS,
+		CASETYPE_PATCH_VERTICES_IN_TES,
+		CASETYPE_TESS_LEVEL_INNER0_TES,
+		CASETYPE_TESS_LEVEL_INNER1_TES,
+		CASETYPE_TESS_LEVEL_OUTER0_TES,
+		CASETYPE_TESS_LEVEL_OUTER1_TES,
+		CASETYPE_TESS_LEVEL_OUTER2_TES,
+		CASETYPE_TESS_LEVEL_OUTER3_TES,
+
+		CASETYPE_LAST
+	};
+
+	PerPatchDataCase (Context& context, const char* name, const char* description, CaseType caseType, const char* referenceImagePath)
+		: TestCase				(context, name, description)
+		, m_caseType			(caseType)
+		, m_referenceImagePath	(caseTypeUsesRefImageFromFile(caseType) ? referenceImagePath : "")
+	{
+		DE_ASSERT(caseTypeUsesRefImageFromFile(caseType) == (referenceImagePath != DE_NULL));
+	}
+
+	void							init							(void);
+	void							deinit							(void);
+	IterateResult					iterate							(void);
+
+	static const char*				getCaseTypeName					(CaseType);
+	static const char*				getCaseTypeDescription			(CaseType);
+	static bool						caseTypeUsesRefImageFromFile	(CaseType);
+
+private:
+	static const int				RENDER_SIZE = 256;
+	static const int				INPUT_PATCH_SIZE;
+	static const int				OUTPUT_PATCH_SIZE;
+
+	const CaseType					m_caseType;
+	const string					m_referenceImagePath;
+
+	SharedPtr<const ShaderProgram>	m_program;
+};
+
+const int PerPatchDataCase::INPUT_PATCH_SIZE	= 10;
+const int PerPatchDataCase::OUTPUT_PATCH_SIZE	= 5;
+
+const char* PerPatchDataCase::getCaseTypeName (CaseType type)
+{
+	switch (type)
+	{
+		case CASETYPE_PRIMITIVE_ID_TCS:			return "primitive_id_tcs";
+		case CASETYPE_PRIMITIVE_ID_TES:			return "primitive_id_tes";
+		case CASETYPE_PATCH_VERTICES_IN_TCS:	return "patch_vertices_in_tcs";
+		case CASETYPE_PATCH_VERTICES_IN_TES:	return "patch_vertices_in_tes";
+		case CASETYPE_TESS_LEVEL_INNER0_TES:	return "tess_level_inner_0_tes";
+		case CASETYPE_TESS_LEVEL_INNER1_TES:	return "tess_level_inner_1_tes";
+		case CASETYPE_TESS_LEVEL_OUTER0_TES:	return "tess_level_outer_0_tes";
+		case CASETYPE_TESS_LEVEL_OUTER1_TES:	return "tess_level_outer_1_tes";
+		case CASETYPE_TESS_LEVEL_OUTER2_TES:	return "tess_level_outer_2_tes";
+		case CASETYPE_TESS_LEVEL_OUTER3_TES:	return "tess_level_outer_3_tes";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+const char* PerPatchDataCase::getCaseTypeDescription (CaseType type)
+{
+	switch (type)
+	{
+		case CASETYPE_PRIMITIVE_ID_TCS:			return "Read gl_PrimitiveID in TCS and pass it as patch output to TES";
+		case CASETYPE_PRIMITIVE_ID_TES:			return "Read gl_PrimitiveID in TES";
+		case CASETYPE_PATCH_VERTICES_IN_TCS:	return "Read gl_PatchVerticesIn in TCS and pass it as patch output to TES";
+		case CASETYPE_PATCH_VERTICES_IN_TES:	return "Read gl_PatchVerticesIn in TES";
+		case CASETYPE_TESS_LEVEL_INNER0_TES:	return "Read gl_TessLevelInner[0] in TES";
+		case CASETYPE_TESS_LEVEL_INNER1_TES:	return "Read gl_TessLevelInner[1] in TES";
+		case CASETYPE_TESS_LEVEL_OUTER0_TES:	return "Read gl_TessLevelOuter[0] in TES";
+		case CASETYPE_TESS_LEVEL_OUTER1_TES:	return "Read gl_TessLevelOuter[1] in TES";
+		case CASETYPE_TESS_LEVEL_OUTER2_TES:	return "Read gl_TessLevelOuter[2] in TES";
+		case CASETYPE_TESS_LEVEL_OUTER3_TES:	return "Read gl_TessLevelOuter[3] in TES";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+bool PerPatchDataCase::caseTypeUsesRefImageFromFile (CaseType type)
+{
+	switch (type)
+	{
+		case CASETYPE_PRIMITIVE_ID_TCS:
+		case CASETYPE_PRIMITIVE_ID_TES:
+			return true;
+
+		default:
+			return false;
+	}
+}
+
+void PerPatchDataCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	DE_ASSERT(OUTPUT_PATCH_SIZE < INPUT_PATCH_SIZE);
+
+	const string inSizeStr		= de::toString(INPUT_PATCH_SIZE);
+	const string outSizeStr		= de::toString(OUTPUT_PATCH_SIZE);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+			<< glu::VertexSource					("#version 310 es\n"
+													 "\n"
+													 "in highp float in_v_attr;\n"
+													 "\n"
+													 "out highp float in_tc_attr;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	in_tc_attr = in_v_attr;\n"
+													 "}\n")
+
+			<< glu::TessellationControlSource		("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 "layout (vertices = " + outSizeStr + ") out;\n"
+													 "\n"
+													 "in highp float in_tc_attr[];\n"
+													 "\n"
+													 "out highp float in_te_attr[];\n"
+													 + (m_caseType == CASETYPE_PRIMITIVE_ID_TCS			? "patch out mediump int in_te_primitiveIDFromTCS;\n"
+													  : m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS	? "patch out mediump int in_te_patchVerticesInFromTCS;\n"
+													  : "") +
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID];\n"
+													 + (m_caseType == CASETYPE_PRIMITIVE_ID_TCS			? "\tin_te_primitiveIDFromTCS = gl_PrimitiveID;\n"
+													  : m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS	? "\tin_te_patchVerticesInFromTCS = gl_PatchVerticesIn;\n"
+													  : "") +
+													 "\n"
+													 "	gl_TessLevelInner[0] = 9.0;\n"
+													 "	gl_TessLevelInner[1] = 8.0;\n"
+													 "\n"
+													"	gl_TessLevelOuter[0] = 7.0;\n"
+													"	gl_TessLevelOuter[1] = 6.0;\n"
+													"	gl_TessLevelOuter[2] = 5.0;\n"
+													"	gl_TessLevelOuter[3] = 4.0;\n"
+													 "}\n")
+
+			<< glu::TessellationEvaluationSource	("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) +
+													 "\n"
+													 "in highp float in_te_attr[];\n"
+													 + (m_caseType == CASETYPE_PRIMITIVE_ID_TCS			? "patch in mediump int in_te_primitiveIDFromTCS;\n"
+													  : m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS	? "patch in mediump int in_te_patchVerticesInFromTCS;\n"
+													  : string()) +
+													 "\n"
+													 "out mediump vec4 in_f_color;\n"
+													 "\n"
+													 "uniform highp float u_xScale;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	highp float x = (gl_TessCoord.x*u_xScale + in_te_attr[0]) * 2.0 - 1.0;\n"
+													 "	highp float y = gl_TessCoord.y*2.0 - 1.0;\n"
+													 "	gl_Position = vec4(x, y, 0.0, 1.0);\n"
+													 + (m_caseType == CASETYPE_PRIMITIVE_ID_TCS			? "\tbool ok = in_te_primitiveIDFromTCS == 3;\n"
+													  : m_caseType == CASETYPE_PRIMITIVE_ID_TES			? "\tbool ok = gl_PrimitiveID == 3;\n"
+													  : m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS	? "\tbool ok = in_te_patchVerticesInFromTCS == " + inSizeStr + ";\n"
+													  : m_caseType == CASETYPE_PATCH_VERTICES_IN_TES	? "\tbool ok = gl_PatchVerticesIn == " + outSizeStr + ";\n"
+													  : m_caseType == CASETYPE_TESS_LEVEL_INNER0_TES	? "\tbool ok = abs(gl_TessLevelInner[0] - 9.0) < 0.1f;\n"
+													  : m_caseType == CASETYPE_TESS_LEVEL_INNER1_TES	? "\tbool ok = abs(gl_TessLevelInner[1] - 8.0) < 0.1f;\n"
+													  : m_caseType == CASETYPE_TESS_LEVEL_OUTER0_TES	? "\tbool ok = abs(gl_TessLevelOuter[0] - 7.0) < 0.1f;\n"
+													  : m_caseType == CASETYPE_TESS_LEVEL_OUTER1_TES	? "\tbool ok = abs(gl_TessLevelOuter[1] - 6.0) < 0.1f;\n"
+													  : m_caseType == CASETYPE_TESS_LEVEL_OUTER2_TES	? "\tbool ok = abs(gl_TessLevelOuter[2] - 5.0) < 0.1f;\n"
+													  : m_caseType == CASETYPE_TESS_LEVEL_OUTER3_TES	? "\tbool ok = abs(gl_TessLevelOuter[3] - 4.0) < 0.1f;\n"
+													  : DE_NULL) +
+													  "	in_f_color = ok ? vec4(1.0) : vec4(vec3(0.0), 1.0);\n"
+													 "}\n")
+
+			<< glu::FragmentSource					("#version 310 es\n"
+													 "\n"
+													 "layout (location = 0) out mediump vec4 o_color;\n"
+													 "\n"
+													 "in mediump vec4 in_f_color;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	o_color = in_f_color;\n"
+													 "}\n")));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void PerPatchDataCase::deinit (void)
+{
+	m_program.clear();
+}
+
+PerPatchDataCase::IterateResult PerPatchDataCase::iterate (void)
+{
+	TestLog&					log						= m_testCtx.getLog();
+	const RenderContext&		renderCtx				= m_context.getRenderContext();
+	const RandomViewport		viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const deUint32				programGL				= m_program->getProgram();
+	const glw::Functions&		gl						= renderCtx.getFunctions();
+
+	setViewport(gl, viewport);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.useProgram(programGL);
+
+	log << TestLog::Message << "Note: input patch size is " << INPUT_PATCH_SIZE << ", output patch size is " << OUTPUT_PATCH_SIZE << TestLog::EndMessage;
+
+	{
+		const int numPrimitives = m_caseType == CASETYPE_PRIMITIVE_ID_TCS || m_caseType == CASETYPE_PRIMITIVE_ID_TES ? 8 : 1;
+
+		vector<float> attributeData;
+		attributeData.reserve(numPrimitives*INPUT_PATCH_SIZE);
+
+		for (int i = 0; i < numPrimitives; i++)
+		{
+			attributeData.push_back((float)i / (float)numPrimitives);
+			for (int j = 0; j < INPUT_PATCH_SIZE-1; j++)
+				attributeData.push_back(0.0f);
+		}
+
+		gl.patchParameteri(GL_PATCH_VERTICES, INPUT_PATCH_SIZE);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+
+		gl.uniform1f(gl.getUniformLocation(programGL, "u_xScale"), 1.0f / (float)numPrimitives);
+
+		const glu::VertexArrayBinding attrBindings[] =
+		{
+			glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])
+		};
+
+		glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
+			glu::pr::Patches(numPrimitives*INPUT_PATCH_SIZE));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
+	}
+
+	{
+		const tcu::Surface rendered = getPixels(renderCtx, viewport);
+
+		if (m_caseType == CASETYPE_PRIMITIVE_ID_TCS || m_caseType == CASETYPE_PRIMITIVE_ID_TES)
+		{
+			DE_ASSERT(caseTypeUsesRefImageFromFile(m_caseType));
+
+			const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePath);
+			const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);
+
+			m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Image comparison failed");
+			return STOP;
+		}
+		else
+		{
+			DE_ASSERT(!caseTypeUsesRefImageFromFile(m_caseType));
+
+			log << TestLog::Image("RenderedImage", "Rendered Image", rendered);
+
+			for (int y = 0; y < rendered.getHeight();	y++)
+			for (int x = 0; x < rendered.getWidth();	x++)
+			{
+				if (rendered.getPixel(x, y) != tcu::RGBA::white)
+				{
+					log << TestLog::Message << "Failure: expected all white pixels" << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+					return STOP;
+				}
+			}
+
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+			return STOP;
+		}
+	}
+}
+
+// Basic barrier() usage in TCS.
+class BarrierCase : public TestCase
+{
+public:
+	BarrierCase (Context& context, const char* name, const char* description, const char* referenceImagePath)
+		: TestCase				(context, name, description)
+		, m_referenceImagePath	(referenceImagePath)
+	{
+	}
+
+	void							init		(void);
+	void							deinit		(void);
+	IterateResult					iterate		(void);
+
+private:
+	static const int				RENDER_SIZE = 256;
+	static const int				NUM_VERTICES;
+
+	const string					m_referenceImagePath;
+
+	SharedPtr<const ShaderProgram>	m_program;
+};
+
+const int BarrierCase::NUM_VERTICES = 32;
+
+void BarrierCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	const string numVertsStr = de::toString(NUM_VERTICES);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+			<< glu::VertexSource					("#version 310 es\n"
+													 "\n"
+													 "in highp float in_v_attr;\n"
+													 "\n"
+													 "out highp float in_tc_attr;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	in_tc_attr = in_v_attr;\n"
+													 "}\n")
+
+			<< glu::TessellationControlSource		("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 "layout (vertices = " + numVertsStr + ") out;\n"
+													 "\n"
+													 "in highp float in_tc_attr[];\n"
+													 "\n"
+													 "out highp float in_te_attr[];\n"
+													 "patch out highp float in_te_patchAttr;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID];\n"
+													 "	in_te_patchAttr = 0.0f;\n"
+													 "	barrier();\n"
+													 "	if (gl_InvocationID == 5)\n"
+													 "		in_te_patchAttr = float(gl_InvocationID)*0.1;\n"
+													 "	barrier();\n"
+													 "	highp float temp = in_te_patchAttr + in_te_attr[gl_InvocationID];\n"
+													 "	barrier();\n"
+													 "	if (gl_InvocationID == " + numVertsStr + "-1)\n"
+													 "		in_te_patchAttr = float(gl_InvocationID);\n"
+													 "	barrier();\n"
+													 "	in_te_attr[gl_InvocationID] = temp;\n"
+													 "	barrier();\n"
+													 "	temp = temp + in_te_attr[(gl_InvocationID+1) % " + numVertsStr + "];\n"
+													 "	barrier();\n"
+													 "	in_te_attr[gl_InvocationID] = 0.25*temp;\n"
+													 "\n"
+													 "	gl_TessLevelInner[0] = 32.0;\n"
+													 "	gl_TessLevelInner[1] = 32.0;\n"
+													 "\n"
+													 "	gl_TessLevelOuter[0] = 32.0;\n"
+													 "	gl_TessLevelOuter[1] = 32.0;\n"
+													 "	gl_TessLevelOuter[2] = 32.0;\n"
+													 "	gl_TessLevelOuter[3] = 32.0;\n"
+													 "}\n")
+
+			<< glu::TessellationEvaluationSource	("#version 310 es\n"
+													 "#extension GL_EXT_tessellation_shader : require\n"
+													 "\n"
+													 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) +
+													 "\n"
+													 "in highp float in_te_attr[];\n"
+													 "patch in highp float in_te_patchAttr;\n"
+													 "\n"
+													 "out highp float in_f_blue;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	highp float x = gl_TessCoord.x*2.0 - 1.0;\n"
+													 "	highp float y = gl_TessCoord.y - in_te_attr[int(round(gl_TessCoord.x*float(" + numVertsStr + "-1)))];\n"
+													 "	gl_Position = vec4(x, y, 0.0, 1.0);\n"
+													 "	in_f_blue = abs(in_te_patchAttr - (" + numVertsStr + "-1));\n"
+													 "}\n")
+
+			<< glu::FragmentSource					("#version 310 es\n"
+													 "\n"
+													 "layout (location = 0) out mediump vec4 o_color;\n"
+													 "\n"
+													 "in highp float in_f_blue;\n"
+													 "\n"
+													 "void main (void)\n"
+													 "{\n"
+													 "	o_color = vec4(1.0, 0.0, in_f_blue, 1.0);\n"
+													 "}\n")));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void BarrierCase::deinit (void)
+{
+	m_program.clear();
+}
+
+BarrierCase::IterateResult BarrierCase::iterate (void)
+{
+	TestLog&					log						= m_testCtx.getLog();
+	const RenderContext&		renderCtx				= m_context.getRenderContext();
+	const RandomViewport		viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const deUint32				programGL				= m_program->getProgram();
+	const glw::Functions&		gl						= renderCtx.getFunctions();
+
+	setViewport(gl, viewport);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.useProgram(programGL);
+
+	{
+		vector<float> attributeData(NUM_VERTICES);
+
+		for (int i = 0; i < NUM_VERTICES; i++)
+			attributeData[i] = (float)i / (float)(NUM_VERTICES-1);
+
+		gl.patchParameteri(GL_PATCH_VERTICES, NUM_VERTICES);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+
+		const glu::VertexArrayBinding attrBindings[] =
+		{
+			glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])
+		};
+
+		glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
+			glu::pr::Patches(NUM_VERTICES));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
+	}
+
+	{
+		const tcu::Surface			rendered	= getPixels(renderCtx, viewport);
+		const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePath);
+		const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);
+
+		m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Image comparison failed");
+		return STOP;
+	}
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Base class for testing invariance of entire primitive set
+ *
+ * Draws two patches with identical tessellation levels and compares the
+ * results. Repeats the same with other programs that are only different
+ * in irrelevant ways; compares the results between these two programs.
+ * Also potentially compares to results produced by different tessellation
+ * levels (see e.g. invariance rule #6).
+ * Furthermore, repeats the above with multiple different tessellation
+ * value sets.
+ *
+ * The manner of primitive set comparison is defined by subclass. E.g.
+ * case for invariance rule #1 tests that same vertices come out, in same
+ * order; rule #5 only requires that the same triangles are output, but
+ * not necessarily in the same order.
+ *//*--------------------------------------------------------------------*/
+class PrimitiveSetInvarianceCase : public TestCase
+{
+public:
+	enum WindingUsage
+	{
+		WINDINGUSAGE_CCW = 0,
+		WINDINGUSAGE_CW,
+		WINDINGUSAGE_VARY,
+
+		WINDINGUSAGE_LAST
+	};
+
+	PrimitiveSetInvarianceCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, bool usePointMode, WindingUsage windingUsage)
+		: TestCase			(context, name, description)
+		, m_primitiveType	(primType)
+		, m_spacing			(spacing)
+		, m_usePointMode	(usePointMode)
+		, m_windingUsage	(windingUsage)
+	{
+	}
+
+	void									init				(void);
+	void									deinit				(void);
+	IterateResult							iterate				(void);
+
+protected:
+	struct TessLevels
+	{
+		float inner[2];
+		float outer[4];
+		string description (void) const { return tessellationLevelsString(&inner[0], &outer[0]); }
+	};
+	struct LevelCase
+	{
+		vector<TessLevels>	levels;
+		int					mem; //!< Subclass-defined arbitrary piece of data, for type of the levelcase, if needed. Passed to compare().
+		LevelCase (const TessLevels& lev) : levels(vector<TessLevels>(1, lev)), mem(0) {}
+		LevelCase (void) : mem(0) {}
+	};
+
+	virtual vector<LevelCase>	genTessLevelCases	(void) const;
+	virtual bool				compare				(const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, int levelCaseMem) const = 0;
+
+	const TessPrimitiveType		m_primitiveType;
+
+private:
+	struct Program
+	{
+		Winding							winding;
+		SharedPtr<const ShaderProgram>	program;
+
+				Program			(Winding w, const SharedPtr<const ShaderProgram>& prog) : winding(w), program(prog) {}
+
+		string	description		(void) const { return string() + "winding mode " + getWindingShaderName(winding); };
+	};
+
+	static const int			RENDER_SIZE = 16;
+
+	const SpacingMode			m_spacing;
+	const bool					m_usePointMode;
+	const WindingUsage			m_windingUsage;
+
+	vector<Program>				m_programs;
+};
+
+vector<PrimitiveSetInvarianceCase::LevelCase> PrimitiveSetInvarianceCase::genTessLevelCases (void) const
+{
+	static const TessLevels basicTessLevelCases[] =
+	{
+		{ { 1.0f,	1.0f	},	{ 1.0f,		1.0f,	1.0f,	1.0f	} },
+		{ { 63.0f,	24.0f	},	{ 15.0f,	42.0f,	10.0f,	12.0f	} },
+		{ { 3.0f,	2.0f	},	{ 6.0f,		8.0f,	7.0f,	9.0f	} },
+		{ { 4.0f,	6.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
+		{ { 2.0f,	2.0f	},	{ 6.0f,		8.0f,	7.0f,	9.0f	} },
+		{ { 5.0f,	6.0f	},	{ 1.0f,		1.0f,	1.0f,	1.0f	} },
+		{ { 1.0f,	6.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
+		{ { 5.0f,	1.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
+		{ { 5.2f,	1.6f	},	{ 2.9f,		3.4f,	1.5f,	4.1f	} }
+	};
+
+	vector<LevelCase> result;
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(basicTessLevelCases); i++)
+		result.push_back(LevelCase(basicTessLevelCases[i]));
+
+	{
+		de::Random rnd(123);
+		for (int i = 0; i < 10; i++)
+		{
+			TessLevels levels;
+			for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.inner); j++)
+				levels.inner[j] = rnd.getFloat(1.0f, 16.0f);
+			for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.outer); j++)
+				levels.outer[j] = rnd.getFloat(1.0f, 16.0f);
+			result.push_back(LevelCase(levels));
+		}
+	}
+
+	return result;
+}
+
+void PrimitiveSetInvarianceCase::init (void)
+{
+	const int			numDifferentConstantExprCases = 2;
+	vector<Winding>		windings;
+	switch (m_windingUsage)
+	{
+		case WINDINGUSAGE_CCW:		windings.push_back(WINDING_CCW); break;
+		case WINDINGUSAGE_CW:		windings.push_back(WINDING_CW); break;
+		case WINDINGUSAGE_VARY:		windings.push_back(WINDING_CCW);
+									windings.push_back(WINDING_CW); break;
+		default: DE_ASSERT(false);
+	}
+
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	for (int constantExprCaseNdx = 0; constantExprCaseNdx < numDifferentConstantExprCases; constantExprCaseNdx++)
+	{
+		for (int windingCaseNdx = 0; windingCaseNdx < (int)windings.size(); windingCaseNdx++)
+		{
+			const string	floatLit01 = de::floatToString(10.0f / (float)(constantExprCaseNdx + 10), 2);
+			const int		programNdx = (int)m_programs.size();
+
+			m_programs.push_back(Program(windings[windingCaseNdx],
+										 SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+					<< glu::VertexSource					("#version 310 es\n"
+															 "\n"
+															 "in highp float in_v_attr;\n"
+															 "out highp float in_tc_attr;\n"
+															 "\n"
+															 "void main (void)\n"
+															 "{\n"
+															 "	in_tc_attr = in_v_attr;\n"
+															 "}\n")
+
+					<< glu::TessellationControlSource		("#version 310 es\n"
+															 "#extension GL_EXT_tessellation_shader : require\n"
+															 "\n"
+															 "layout (vertices = " + de::toString(constantExprCaseNdx+1) + ") out;\n"
+															 "\n"
+															 "in highp float in_tc_attr[];\n"
+															 "\n"
+															 "patch out highp float in_te_positionOffset;\n"
+															 "\n"
+															 "void main (void)\n"
+															 "{\n"
+															 "	in_te_positionOffset = in_tc_attr[6];\n"
+															 "\n"
+															 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
+															 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
+															 "\n"
+															 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
+															 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
+															 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
+															 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
+															 "}\n")
+
+					<< glu::TessellationEvaluationSource	("#version 310 es\n"
+															 "#extension GL_EXT_tessellation_shader : require\n"
+															 "\n"
+															 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, windings[windingCaseNdx], m_usePointMode) +
+															 "\n"
+															 "patch in highp float in_te_positionOffset;\n"
+															 "\n"
+															 "out highp vec4 in_f_color;\n"
+															 "invariant out highp vec3 out_te_tessCoord;\n"
+															 "\n"
+															 "void main (void)\n"
+															 "{\n"
+															 "	gl_Position = vec4(gl_TessCoord.xy*" + floatLit01 + " - in_te_positionOffset + float(gl_PrimitiveID)*0.1, 0.0, 1.0);\n"
+															 "	in_f_color = vec4(" + floatLit01 + ");\n"
+															 "	out_te_tessCoord = gl_TessCoord;\n"
+															 "}\n")
+
+					<< glu::FragmentSource					("#version 310 es\n"
+															 "\n"
+															 "layout (location = 0) out mediump vec4 o_color;\n"
+															 "\n"
+															 "in highp vec4 in_f_color;\n"
+															 "\n"
+															 "void main (void)\n"
+															 "{\n"
+															 "	o_color = in_f_color;\n"
+															 "}\n")
+
+					<< glu::TransformFeedbackVarying		("out_te_tessCoord")
+					<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)))));
+
+			{
+				const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program" + de::toString(programNdx), "Program " + de::toString(programNdx));
+
+				if (programNdx == 0 || !m_programs.back().program->isOk())
+					m_testCtx.getLog() << *m_programs.back().program;
+
+				if (!m_programs.back().program->isOk())
+					TCU_FAIL("Program compilation failed");
+
+				if (programNdx > 0)
+					m_testCtx.getLog() << TestLog::Message << "Note: program " << programNdx << " is similar to above, except some constants are different, and: " << m_programs.back().description() << TestLog::EndMessage;
+			}
+		}
+	}
+}
+
+void PrimitiveSetInvarianceCase::deinit (void)
+{
+	m_programs.clear();
+}
+
+PrimitiveSetInvarianceCase::IterateResult PrimitiveSetInvarianceCase::iterate (void)
+{
+	typedef TransformFeedbackHandler<Vec3> TFHandler;
+
+	TestLog&					log					= m_testCtx.getLog();
+	const RenderContext&		renderCtx			= m_context.getRenderContext();
+	const RandomViewport		viewport			(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const glw::Functions&		gl					= renderCtx.getFunctions();
+	const vector<LevelCase>		tessLevelCases		= genTessLevelCases();
+	vector<vector<int> >		primitiveCounts;
+	int							maxNumPrimitives	= -1;
+
+	for (int caseNdx = 0; caseNdx < (int)tessLevelCases.size(); caseNdx++)
+	{
+		primitiveCounts.push_back(vector<int>());
+		for (int i = 0; i < (int)tessLevelCases[caseNdx].levels.size(); i++)
+		{
+			const int primCount = referencePrimitiveCount(m_primitiveType, m_spacing, m_usePointMode,
+														  &tessLevelCases[caseNdx].levels[i].inner[0], &tessLevelCases[caseNdx].levels[i].outer[0]);
+			primitiveCounts.back().push_back(primCount);
+			maxNumPrimitives = de::max(maxNumPrimitives, primCount);
+		}
+	}
+
+	const deUint32				primitiveTypeGL		= outputPrimitiveTypeGL(m_primitiveType, m_usePointMode);
+	const TFHandler				transformFeedback	(m_context.getRenderContext(), 2*maxNumPrimitives*numVerticesPerPrimitive(primitiveTypeGL));
+
+	setViewport(gl, viewport);
+	gl.patchParameteri(GL_PATCH_VERTICES, 7);
+
+	for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++)
+	{
+		const LevelCase&	levelCase = tessLevelCases[tessLevelCaseNdx];
+		vector<Vec3>		firstPrimVertices;
+
+		{
+			string tessLevelsStr;
+			for (int i = 0; i < (int)levelCase.levels.size(); i++)
+				tessLevelsStr += (levelCase.levels.size() > 1 ? "\n" : "") + levelCase.levels[i].description();
+			log << TestLog::Message << "Tessellation level sets: " << tessLevelsStr << TestLog::EndMessage;
+		}
+
+		for (int subTessLevelCaseNdx = 0; subTessLevelCaseNdx < (int)levelCase.levels.size(); subTessLevelCaseNdx++)
+		{
+			const TessLevels&				tessLevels		= levelCase.levels[subTessLevelCaseNdx];
+			const float						(&inner)[2]		= tessLevels.inner;
+			const float						(&outer)[4]		= tessLevels.outer;
+			const float						attribute[2*7]	= { inner[0], inner[1], outer[0], outer[1], outer[2], outer[3], 0.0f,
+																inner[0], inner[1], outer[0], outer[1], outer[2], outer[3], 0.5f };
+			const glu::VertexArrayBinding	bindings[]		= { glu::va::Float("in_v_attr", 1, DE_LENGTH_OF_ARRAY(attribute), 0, &attribute[0]) };
+
+			for (int programNdx = 0; programNdx < (int)m_programs.size(); programNdx++)
+			{
+				const deUint32				programGL	= m_programs[programNdx].program->getProgram();
+				gl.useProgram(programGL);
+				const TFHandler::Result		tfResult	= transformFeedback.renderAndGetPrimitives(programGL, primitiveTypeGL, DE_LENGTH_OF_ARRAY(bindings), &bindings[0], DE_LENGTH_OF_ARRAY(attribute));
+
+				if (tfResult.numPrimitives != 2*primitiveCounts[tessLevelCaseNdx][subTessLevelCaseNdx])
+				{
+					log << TestLog::Message << "Failure: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be "
+											<< tfResult.numPrimitives << ", reference value is " << 2*primitiveCounts[tessLevelCaseNdx][subTessLevelCaseNdx]
+											<< TestLog::EndMessage;
+
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives");
+					return STOP;
+				}
+
+				{
+					const int			half			= (int)tfResult.varying.size()/2;
+					const vector<Vec3>	prim0Vertices	= vector<Vec3>(tfResult.varying.begin(), tfResult.varying.begin() + half);
+					const Vec3* const	prim1Vertices	= &tfResult.varying[half];
+
+					for (int vtxNdx = 0; vtxNdx < (int)prim0Vertices.size(); vtxNdx++)
+					{
+						if (prim0Vertices[vtxNdx] != prim1Vertices[vtxNdx])
+						{
+							log << TestLog::Message << "Failure: tessellation coordinate at index " << vtxNdx << " differs between two primitives drawn in one draw call" << TestLog::EndMessage
+								<< TestLog::Message << "Note: the coordinate is " << prim0Vertices[vtxNdx] << " for the first primitive and " << prim1Vertices[vtxNdx] << " for the second" << TestLog::EndMessage
+								<< TestLog::Message << "Note: tessellation levels for both primitives were: " << tessellationLevelsString(&inner[0], &outer[0]) << TestLog::EndMessage;
+							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives");
+							return STOP;
+						}
+					}
+
+					if (programNdx == 0 && subTessLevelCaseNdx == 0)
+						firstPrimVertices = prim0Vertices;
+					else
+					{
+						const bool compareOk = compare(firstPrimVertices, prim0Vertices, levelCase.mem);
+						if (!compareOk)
+						{
+							log << TestLog::Message << "Note: comparison of tessellation coordinates failed; comparison was made between following cases:\n"
+													<< "  - case A: program 0, tessellation levels: " << tessLevelCases[tessLevelCaseNdx].levels[0].description() << "\n"
+													<< "  - case B: program " << programNdx << ", tessellation levels: " << tessLevels.description() << TestLog::EndMessage;
+							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives");
+							return STOP;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test invariance rule #1
+ *
+ * Test that the sequence of primitives input to the TES only depends on
+ * the tessellation levels, tessellation mode, spacing mode, winding, and
+ * point mode.
+ *//*--------------------------------------------------------------------*/
+class InvariantPrimitiveSetCase : public PrimitiveSetInvarianceCase
+{
+public:
+	InvariantPrimitiveSetCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
+		: PrimitiveSetInvarianceCase(context, name, description, primType, spacing, usePointMode, winding == WINDING_CCW	? WINDINGUSAGE_CCW
+																								: winding == WINDING_CW		? WINDINGUSAGE_CW
+																								: WINDINGUSAGE_LAST)
+	{
+	}
+
+protected:
+	virtual bool compare (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, int) const
+	{
+		for (int vtxNdx = 0; vtxNdx < (int)coordsA.size(); vtxNdx++)
+		{
+			if (coordsA[vtxNdx] != coordsB[vtxNdx])
+			{
+				m_testCtx.getLog() << TestLog::Message << "Failure: tessellation coordinate at index " << vtxNdx << " differs between two programs" << TestLog::EndMessage
+								   << TestLog::Message << "Note: the coordinate is " << coordsA[vtxNdx] << " for the first program and " << coordsB[vtxNdx] << " for the other" << TestLog::EndMessage;
+				return false;
+			}
+		}
+		return true;
+	}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test invariance rule #2
+ *
+ * Test that the set of vertices along an outer edge of a quad or triangle
+ * only depends on that edge's tessellation level, and spacing.
+ *
+ * For each (outer) edge in the quad or triangle, draw multiple patches
+ * with identical tessellation levels for that outer edge but with
+ * different values for the other outer edges; compare, among the
+ * primitives, the vertices generated for that outer edge. Repeat with
+ * different programs, using different winding etc. settings. Compare
+ * the edge's vertices between different programs.
+ *//*--------------------------------------------------------------------*/
+class InvariantOuterEdgeCase : public TestCase
+{
+public:
+	InvariantOuterEdgeCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing)
+		: TestCase			(context, name, description)
+		, m_primitiveType	(primType)
+		, m_spacing			(spacing)
+	{
+		DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
+	}
+
+	void						init		(void);
+	void						deinit		(void);
+	IterateResult				iterate		(void);
+
+private:
+	struct Program
+	{
+		Winding							winding;
+		bool							usePointMode;
+		SharedPtr<const ShaderProgram>	program;
+
+				Program			(Winding w, bool point, const SharedPtr<const ShaderProgram>& prog) : winding(w), usePointMode(point), program(prog) {}
+
+		string	description		(void) const { return string() + "winding mode " + getWindingShaderName(winding) + ", " + (usePointMode ? "" : "don't ") + "use point mode"; };
+	};
+
+	static vector<float>		generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel);
+
+	static const int			RENDER_SIZE = 16;
+
+	const TessPrimitiveType		m_primitiveType;
+	const SpacingMode			m_spacing;
+
+	vector<Program>				m_programs;
+};
+
+vector<float> InvariantOuterEdgeCase::generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel)
+{
+	de::Random rnd(123);
+	return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd);
+}
+
+void InvariantOuterEdgeCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	for (int windingI = 0; windingI < WINDING_LAST; windingI++)
+	{
+		const Winding winding = (Winding)windingI;
+
+		for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
+		{
+			const bool		usePointMode	= usePointModeI != 0;
+			const int		programNdx		= (int)m_programs.size();
+			const string	floatLit01		= de::floatToString(10.0f / (float)(programNdx + 10), 2);
+
+			m_programs.push_back(Program(winding, usePointMode,
+										 SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+				<< glu::VertexSource					("#version 310 es\n"
+														 "\n"
+														 "in highp float in_v_attr;\n"
+														 "out highp float in_tc_attr;\n"
+														 "\n"
+														 "void main (void)\n"
+														 "{\n"
+														 "	in_tc_attr = in_v_attr;\n"
+														 "}\n")
+
+				<< glu::TessellationControlSource		("#version 310 es\n"
+														 "#extension GL_EXT_tessellation_shader : require\n"
+														 "\n"
+														 "layout (vertices = " + de::toString(programNdx+1) + ") out;\n"
+														 "\n"
+														 "in highp float in_tc_attr[];\n"
+														 "\n"
+														 "void main (void)\n"
+														 "{\n"
+														 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
+														 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
+														 "\n"
+														 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
+														 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
+														 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
+														 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
+														 "}\n")
+
+				<< glu::TessellationEvaluationSource	("#version 310 es\n"
+														 "#extension GL_EXT_tessellation_shader : require\n"
+														 "\n"
+														 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, winding, usePointMode) +
+														 "\n"
+														 "out highp vec4 in_f_color;\n"
+														 "invariant out highp vec3 out_te_tessCoord;\n"
+														 "\n"
+														 "void main (void)\n"
+														 "{\n"
+														 "	gl_Position = vec4(gl_TessCoord.xy*" + floatLit01 + " - float(gl_PrimitiveID)*0.05, 0.0, 1.0);\n"
+														 "	in_f_color = vec4(" + floatLit01 + ");\n"
+														 "	out_te_tessCoord = gl_TessCoord;\n"
+														 "}\n")
+
+				<< glu::FragmentSource					("#version 310 es\n"
+														 "\n"
+														 "layout (location = 0) out mediump vec4 o_color;\n"
+														 "\n"
+														 "in highp vec4 in_f_color;\n"
+														 "\n"
+														 "void main (void)\n"
+														 "{\n"
+														 "	o_color = in_f_color;\n"
+														 "}\n")
+
+				<< glu::TransformFeedbackVarying		("out_te_tessCoord")
+				<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)))));
+
+			{
+				const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program" + de::toString(programNdx), "Program " + de::toString(programNdx));
+
+				if (programNdx == 0 || !m_programs.back().program->isOk())
+					m_testCtx.getLog() << *m_programs.back().program;
+
+				if (!m_programs.back().program->isOk())
+					TCU_FAIL("Program compilation failed");
+
+				if (programNdx > 0)
+					m_testCtx.getLog() << TestLog::Message << "Note: program " << programNdx << " is similar to above, except some constants are different, and: " << m_programs.back().description() << TestLog::EndMessage;
+			}
+		}
+	}
+}
+
+void InvariantOuterEdgeCase::deinit (void)
+{
+	m_programs.clear();
+}
+
+InvariantOuterEdgeCase::IterateResult InvariantOuterEdgeCase::iterate (void)
+{
+	typedef TransformFeedbackHandler<Vec3> TFHandler;
+
+	TestLog&							log							= m_testCtx.getLog();
+	const RenderContext&				renderCtx					= m_context.getRenderContext();
+	const RandomViewport				viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const glw::Functions&				gl							= renderCtx.getFunctions();
+
+	static const float					singleOuterEdgeLevels[]		= { 1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f, 3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f };
+	const int							numPatchesPerDrawCall		= 10;
+	const vector<OuterEdgeDescription>	edgeDescriptions			= outerEdgeDescriptions(m_primitiveType);
+
+	{
+		// Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
+		int maxNumVerticesInDrawCall = 0;
+		{
+			const vector<float> patchTessLevels = generatePatchTessLevels(numPatchesPerDrawCall, 0 /* outer-edge index doesn't affect vertex count */, arrayMax(singleOuterEdgeLevels));
+
+			for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
+				maxNumVerticesInDrawCall = de::max(maxNumVerticesInDrawCall,
+												   multiplePatchReferenceVertexCount(m_primitiveType, m_spacing, usePointModeI != 0, &patchTessLevels[0], numPatchesPerDrawCall));
+		}
+
+		{
+			const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);
+
+			setViewport(gl, viewport);
+			gl.patchParameteri(GL_PATCH_VERTICES, 6);
+
+			for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++)
+			{
+				const OuterEdgeDescription& edgeDesc = edgeDescriptions[outerEdgeIndex];
+
+				for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels); outerEdgeLevelCaseNdx++)
+				{
+					typedef std::set<Vec3, VecLexLessThan<3> > Vec3Set;
+
+					const vector<float>				patchTessLevels		= generatePatchTessLevels(numPatchesPerDrawCall, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
+					const glu::VertexArrayBinding	bindings[]			= { glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0]) };
+					Vec3Set							firstOuterEdgeVertices; // Vertices of the outer edge of the first patch of the first program's draw call; used for comparison with other patches.
+
+					log << TestLog::Message << "Testing with outer tessellation level " << singleOuterEdgeLevels[outerEdgeLevelCaseNdx]
+											<< " for the " << edgeDesc.description() << " edge, and with various levels for other edges, and with all programs" << TestLog::EndMessage;
+
+					for (int programNdx = 0; programNdx < (int)m_programs.size(); programNdx++)
+					{
+						const Program& program		= m_programs[programNdx];
+						const deUint32 programGL	= program.program->getProgram();
+
+						gl.useProgram(programGL);
+
+						{
+							const TFHandler::Result		tfResult			= tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, program.usePointMode),
+																											   DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size());
+							const int					refNumVertices		= multiplePatchReferenceVertexCount(m_primitiveType, m_spacing, program.usePointMode, &patchTessLevels[0], numPatchesPerDrawCall);
+							int							numVerticesRead		= 0;
+
+							if ((int)tfResult.varying.size() != refNumVertices)
+							{
+								log << TestLog::Message << "Failure: the number of vertices returned by transform feedback is "
+														<< tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage
+									<< TestLog::Message << "Note: rendered " << numPatchesPerDrawCall
+														<< " patches in one draw call; tessellation levels for each patch are (in order [inner0, inner1, outer0, outer1, outer2, outer3]):\n"
+														<< containerStr(patchTessLevels, 6) << TestLog::EndMessage;
+
+								m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
+								return STOP;
+							}
+
+							// Check the vertices of each patch.
+
+							for (int patchNdx = 0; patchNdx < numPatchesPerDrawCall; patchNdx++)
+							{
+								const float* const	innerLevels			= &patchTessLevels[6*patchNdx + 0];
+								const float* const	outerLevels			= &patchTessLevels[6*patchNdx + 2];
+								const int			patchNumVertices	= referenceVertexCount(m_primitiveType, m_spacing, program.usePointMode, innerLevels, outerLevels);
+								Vec3Set				outerEdgeVertices;
+
+								// We're interested in just the vertices on the current outer edge.
+								for(int vtxNdx = numVerticesRead; vtxNdx < numVerticesRead + patchNumVertices; vtxNdx++)
+								{
+									const Vec3& vtx = tfResult.varying[vtxNdx];
+									if (edgeDesc.contains(vtx))
+										outerEdgeVertices.insert(tfResult.varying[vtxNdx]);
+								}
+
+								// Check that the outer edge contains an appropriate number of vertices.
+								{
+									const int refNumVerticesOnOuterEdge = 1 + getClampedRoundedTessLevel(m_spacing, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
+
+									if ((int)outerEdgeVertices.size() != refNumVerticesOnOuterEdge)
+									{
+										log << TestLog::Message << "Failure: the number of vertices on outer edge is " << outerEdgeVertices.size()
+																<< ", expected " << refNumVerticesOnOuterEdge << TestLog::EndMessage
+											<< TestLog::Message << "Note: vertices on the outer edge are:\n" << containerStr(outerEdgeVertices, 5, 0) << TestLog::EndMessage
+											<< TestLog::Message << "Note: the following parameters were used: " << program.description()
+																<< ", tessellation levels: " << tessellationLevelsString(innerLevels, outerLevels) << TestLog::EndMessage;
+										m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
+										return STOP;
+									}
+								}
+
+								// Compare the vertices to those of the first patch (unless this is the first patch).
+
+								if (programNdx == 0 && patchNdx == 0)
+									firstOuterEdgeVertices = outerEdgeVertices;
+								else
+								{
+									if (firstOuterEdgeVertices != outerEdgeVertices)
+									{
+										log << TestLog::Message << "Failure: vertices generated for the edge differ between the following cases:\n"
+																<< "  - case A: " << m_programs[0].description() << ", tessellation levels: "
+																				  << tessellationLevelsString(&patchTessLevels[0], &patchTessLevels[2]) << "\n"
+																<< "  - case B: " << program.description() << ", tessellation levels: "
+																				  << tessellationLevelsString(innerLevels, outerLevels) << TestLog::EndMessage;
+
+										log << TestLog::Message << "Note: resulting vertices for the edge for the cases were:\n"
+																<< "  - case A: " << containerStr(firstOuterEdgeVertices, 5, 14) << "\n"
+																<< "  - case B: " << containerStr(outerEdgeVertices, 5, 14) << TestLog::EndMessage;
+
+										m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
+										return STOP;
+									}
+								}
+
+								numVerticesRead += patchNumVertices;
+							}
+
+							DE_ASSERT(numVerticesRead == (int)tfResult.varying.size());
+						}
+					}
+				}
+			}
+		}
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test invariance rule #3
+ *
+ * Test that the vertices along an outer edge are placed symmetrically.
+ *
+ * Draw multiple patches with different tessellation levels and different
+ * point_mode, winding etc. Before outputting tesscoords with TF, mirror
+ * the vertices in the TES such that every vertex on an outer edge -
+ * except the possible middle vertex - should be duplicated in the output.
+ * Check that appropriate duplicates exist.
+ *//*--------------------------------------------------------------------*/
+class SymmetricOuterEdgeCase : public TestCase
+{
+public:
+	SymmetricOuterEdgeCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
+		: TestCase			(context, name, description)
+		, m_primitiveType	(primType)
+		, m_spacing			(spacing)
+		, m_winding			(winding)
+		, m_usePointMode	(usePointMode)
+	{
+	}
+
+	void									init		(void);
+	void									deinit		(void);
+	IterateResult							iterate		(void);
+
+private:
+	static vector<float>					generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel);
+
+	static const int						RENDER_SIZE = 16;
+
+	const TessPrimitiveType					m_primitiveType;
+	const SpacingMode						m_spacing;
+	const Winding							m_winding;
+	const bool								m_usePointMode;
+
+	SharedPtr<const glu::ShaderProgram>		m_program;
+};
+
+vector<float> SymmetricOuterEdgeCase::generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel)
+{
+	de::Random rnd(123);
+	return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd);
+}
+
+void SymmetricOuterEdgeCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+		<< glu::VertexSource					("#version 310 es\n"
+												 "\n"
+												 "in highp float in_v_attr;\n"
+												 "out highp float in_tc_attr;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	in_tc_attr = in_v_attr;\n"
+												 "}\n")
+
+		<< glu::TessellationControlSource		("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 "layout (vertices = 1) out;\n"
+												 "\n"
+												 "in highp float in_tc_attr[];\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
+												 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
+												 "\n"
+												 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
+												 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
+												 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
+												 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
+												 "}\n")
+
+		<< glu::TessellationEvaluationSource	("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
+												 "\n"
+												 "out highp vec4 in_f_color;\n"
+												 "out highp vec4 out_te_tessCoord_isMirrored;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
+													"	float x = gl_TessCoord.x;\n"
+													"	float y = gl_TessCoord.y;\n"
+													"	float z = gl_TessCoord.z;\n"
+													"	// Mirror one half of each outer edge onto the other half, except the endpoints (because they belong to two edges)\n"
+													"	out_te_tessCoord_isMirrored = z == 0.0 && x > 0.5 && x != 1.0 ? vec4(1.0-x,  1.0-y,    0.0, 1.0)\n"
+													"	                            : y == 0.0 && z > 0.5 && z != 1.0 ? vec4(1.0-x,    0.0,  1.0-z, 1.0)\n"
+													"	                            : x == 0.0 && y > 0.5 && y != 1.0 ? vec4(  0.0,  1.0-y,  1.0-z, 1.0)\n"
+													"	                            : vec4(x, y, z, 0.0);\n"
+												  : m_primitiveType == TESSPRIMITIVETYPE_QUADS ?
+													"	float x = gl_TessCoord.x;\n"
+													"	float y = gl_TessCoord.y;\n"
+													"	// Mirror one half of each outer edge onto the other half, except the endpoints (because they belong to two edges)\n"
+													"	out_te_tessCoord_isMirrored = (x == 0.0 || x == 1.0) && y > 0.5 && y != 1.0 ? vec4(    x, 1.0-y, 0.0, 1.0)\n"
+													"	                            : (y == 0.0 || y == 1.0) && x > 0.5 && x != 1.0 ? vec4(1.0-x,     y, 0.0, 1.0)\n"
+													"	                            : vec4(x, y, 0.0, 0.0);\n"
+												  : m_primitiveType == TESSPRIMITIVETYPE_ISOLINES ?
+													"	float x = gl_TessCoord.x;\n"
+													"	float y = gl_TessCoord.y;\n"
+													"	// Mirror one half of each outer edge onto the other half\n"
+													"	out_te_tessCoord_isMirrored = (x == 0.0 || x == 1.0) && y > 0.5 ? vec4(x, 1.0-y, 0.0, 1.0)\n"
+													"	                            : vec4(x, y, 0.0, 0.0f);\n"
+												  : DE_NULL) +
+												 "\n"
+												 "	gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n"
+												 "	in_f_color = vec4(1.0);\n"
+												 "}\n")
+
+		<< glu::FragmentSource					("#version 310 es\n"
+												 "\n"
+												 "layout (location = 0) out mediump vec4 o_color;\n"
+												 "\n"
+												 "in highp vec4 in_f_color;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	o_color = in_f_color;\n"
+												 "}\n")
+
+		<< glu::TransformFeedbackVarying		("out_te_tessCoord_isMirrored")
+		<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void SymmetricOuterEdgeCase::deinit (void)
+{
+	m_program.clear();
+}
+
+SymmetricOuterEdgeCase::IterateResult SymmetricOuterEdgeCase::iterate (void)
+{
+	typedef TransformFeedbackHandler<Vec4> TFHandler;
+
+	TestLog&							log							= m_testCtx.getLog();
+	const RenderContext&				renderCtx					= m_context.getRenderContext();
+	const RandomViewport				viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const glw::Functions&				gl							= renderCtx.getFunctions();
+
+	static const float					singleOuterEdgeLevels[]		= { 1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f, 3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f };
+	const vector<OuterEdgeDescription>	edgeDescriptions			= outerEdgeDescriptions(m_primitiveType);
+
+	{
+		// Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
+		int maxNumVerticesInDrawCall;
+		{
+			const vector<float> patchTessLevels = generatePatchTessLevels(1, 0 /* outer-edge index doesn't affect vertex count */, arrayMax(singleOuterEdgeLevels));
+			maxNumVerticesInDrawCall = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &patchTessLevels[0], &patchTessLevels[2]);
+		}
+
+		{
+			const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);
+
+			setViewport(gl, viewport);
+			gl.patchParameteri(GL_PATCH_VERTICES, 6);
+
+			for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++)
+			{
+				const OuterEdgeDescription& edgeDesc = edgeDescriptions[outerEdgeIndex];
+
+				for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels); outerEdgeLevelCaseNdx++)
+				{
+					typedef std::set<Vec3, VecLexLessThan<3> > Vec3Set;
+
+					const vector<float>				patchTessLevels		= generatePatchTessLevels(1, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
+					const glu::VertexArrayBinding	bindings[]			= { glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0]) };
+
+					log << TestLog::Message << "Testing with outer tessellation level " << singleOuterEdgeLevels[outerEdgeLevelCaseNdx]
+											<< " for the " << edgeDesc.description() << " edge, and with various levels for other edges" << TestLog::EndMessage;
+
+					{
+						const deUint32 programGL = m_program->getProgram();
+
+						gl.useProgram(programGL);
+
+						{
+							const TFHandler::Result		tfResult		= tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
+																										   DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size());
+							const int					refNumVertices	= referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &patchTessLevels[0], &patchTessLevels[2]);
+
+							if ((int)tfResult.varying.size() != refNumVertices)
+							{
+								log << TestLog::Message << "Failure: the number of vertices returned by transform feedback is "
+														<< tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage
+									<< TestLog::Message << "Note: rendered 1 patch, tessellation levels are (in order [inner0, inner1, outer0, outer1, outer2, outer3]):\n"
+														<< containerStr(patchTessLevels, 6) << TestLog::EndMessage;
+
+								m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
+								return STOP;
+							}
+
+							// Check the vertices.
+
+							{
+								Vec3Set nonMirroredEdgeVertices;
+								Vec3Set mirroredEdgeVertices;
+
+								// We're interested in just the vertices on the current outer edge.
+								for(int vtxNdx = 0; vtxNdx < refNumVertices; vtxNdx++)
+								{
+									const Vec3& vtx = tfResult.varying[vtxNdx].swizzle(0,1,2);
+									if (edgeDesc.contains(vtx))
+									{
+										// Ignore the middle vertex of the outer edge, as it's exactly at the mirroring point;
+										// for isolines, also ignore (0, 0) and (1, 0) because there's no mirrored counterpart for them.
+										if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES && vtx == tcu::select(Vec3(0.0f), Vec3(0.5f), singleTrueMask<3>(edgeDesc.constantCoordinateIndex)))
+											continue;
+										if (m_primitiveType == TESSPRIMITIVETYPE_QUADS && vtx.swizzle(0,1) == tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]),
+																															   Vec2(0.5f),
+																															   singleTrueMask<2>(edgeDesc.constantCoordinateIndex)))
+											continue;
+										if (m_primitiveType == TESSPRIMITIVETYPE_ISOLINES && (vtx == Vec3(0.0f, 0.5f, 0.0f) || vtx == Vec3(1.0f, 0.5f, 0.0f) ||
+																							  vtx == Vec3(0.0f, 0.0f, 0.0f) || vtx == Vec3(1.0f, 0.0f, 0.0f)))
+											continue;
+
+										const bool isMirrored = tfResult.varying[vtxNdx].w() > 0.5f;
+										if (isMirrored)
+											mirroredEdgeVertices.insert(vtx);
+										else
+											nonMirroredEdgeVertices.insert(vtx);
+									}
+								}
+
+								if (m_primitiveType != TESSPRIMITIVETYPE_ISOLINES)
+								{
+									// Check that both endpoints are present. Note that endpoints aren't mirrored by the shader, since they belong to more than one edge.
+
+									Vec3 endpointA;
+									Vec3 endpointB;
+
+									if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+									{
+										endpointA = tcu::select(Vec3(1.0f), Vec3(0.0f), singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 1) % 3));
+										endpointB = tcu::select(Vec3(1.0f), Vec3(0.0f), singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 2) % 3));
+									}
+									else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
+									{
+										endpointA.xy() = tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]), Vec2(0.0f), singleTrueMask<2>(edgeDesc.constantCoordinateIndex));
+										endpointB.xy() = tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]), Vec2(1.0f), singleTrueMask<2>(edgeDesc.constantCoordinateIndex));
+									}
+									else
+										DE_ASSERT(false);
+
+									if (!contains(nonMirroredEdgeVertices, endpointA) ||
+										!contains(nonMirroredEdgeVertices, endpointB))
+									{
+										log << TestLog::Message << "Failure: edge doesn't contain both endpoints, " << endpointA << " and " << endpointB << TestLog::EndMessage
+											<< TestLog::Message << "Note: non-mirrored vertices:\n" << containerStr(nonMirroredEdgeVertices, 5)
+																<< "\nmirrored vertices:\n" << containerStr(mirroredEdgeVertices, 5) << TestLog::EndMessage;
+										m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
+										return STOP;
+									}
+									nonMirroredEdgeVertices.erase(endpointA);
+									nonMirroredEdgeVertices.erase(endpointB);
+								}
+
+								if (nonMirroredEdgeVertices != mirroredEdgeVertices)
+								{
+									log << TestLog::Message << "Failure: the set of mirrored edges isn't equal to the set of non-mirrored edges (ignoring endpoints and possible middle)" << TestLog::EndMessage
+										<< TestLog::Message << "Note: non-mirrored vertices:\n" << containerStr(nonMirroredEdgeVertices, 5)
+																<< "\nmirrored vertices:\n" << containerStr(mirroredEdgeVertices, 5) << TestLog::EndMessage;
+									m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
+									return STOP;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test invariance rule #4
+ *
+ * Test that the vertices on an outer edge don't depend on which of the
+ * edges it is, other than with respect to component order.
+ *//*--------------------------------------------------------------------*/
+class OuterEdgeVertexSetIndexIndependenceCase : public TestCase
+{
+public:
+	OuterEdgeVertexSetIndexIndependenceCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
+		: TestCase			(context, name, description)
+		, m_primitiveType	(primType)
+		, m_spacing			(spacing)
+		, m_winding			(winding)
+		, m_usePointMode	(usePointMode)
+	{
+		DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
+	}
+
+	void									init		(void);
+	void									deinit		(void);
+	IterateResult							iterate		(void);
+
+private:
+	static vector<float>					generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel);
+
+	static const int						RENDER_SIZE = 16;
+
+	const TessPrimitiveType					m_primitiveType;
+	const SpacingMode						m_spacing;
+	const Winding							m_winding;
+	const bool								m_usePointMode;
+
+	SharedPtr<const glu::ShaderProgram>		m_program;
+};
+
+vector<float> OuterEdgeVertexSetIndexIndependenceCase::generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel)
+{
+	de::Random rnd(123);
+	return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd);
+}
+
+void OuterEdgeVertexSetIndexIndependenceCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+		<< glu::VertexSource					("#version 310 es\n"
+												 "\n"
+												 "in highp float in_v_attr;\n"
+												 "out highp float in_tc_attr;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	in_tc_attr = in_v_attr;\n"
+												 "}\n")
+
+		<< glu::TessellationControlSource		("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 "layout (vertices = 1) out;\n"
+												 "\n"
+												 "in highp float in_tc_attr[];\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
+												 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
+												 "\n"
+												 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
+												 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
+												 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
+												 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
+												 "}\n")
+
+		<< glu::TessellationEvaluationSource	("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
+												 "\n"
+												 "out highp vec4 in_f_color;\n"
+												 "out highp vec3 out_te_tessCoord;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	out_te_tessCoord = gl_TessCoord;"
+												 "	gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n"
+												 "	in_f_color = vec4(1.0);\n"
+												 "}\n")
+
+		<< glu::FragmentSource					("#version 310 es\n"
+												 "\n"
+												 "layout (location = 0) out mediump vec4 o_color;\n"
+												 "\n"
+												 "in highp vec4 in_f_color;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	o_color = in_f_color;\n"
+												 "}\n")
+
+		<< glu::TransformFeedbackVarying		("out_te_tessCoord")
+		<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void OuterEdgeVertexSetIndexIndependenceCase::deinit (void)
+{
+	m_program.clear();
+}
+
+OuterEdgeVertexSetIndexIndependenceCase::IterateResult OuterEdgeVertexSetIndexIndependenceCase::iterate (void)
+{
+	typedef TransformFeedbackHandler<Vec3> TFHandler;
+
+	TestLog&							log							= m_testCtx.getLog();
+	const RenderContext&				renderCtx					= m_context.getRenderContext();
+	const RandomViewport				viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const glw::Functions&				gl							= renderCtx.getFunctions();
+	const deUint32						programGL					= m_program->getProgram();
+
+	static const float					singleOuterEdgeLevels[]		= { 1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f, 3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f };
+	const vector<OuterEdgeDescription>	edgeDescriptions			= outerEdgeDescriptions(m_primitiveType);
+
+	gl.useProgram(programGL);
+	setViewport(gl, viewport);
+	gl.patchParameteri(GL_PATCH_VERTICES, 6);
+
+	{
+		// Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
+		int maxNumVerticesInDrawCall = 0;
+		{
+			const vector<float> patchTessLevels = generatePatchTessLevels(1, 0 /* outer-edge index doesn't affect vertex count */, arrayMax(singleOuterEdgeLevels));
+			maxNumVerticesInDrawCall = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &patchTessLevels[0], &patchTessLevels[2]);
+		}
+
+		{
+			const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);
+
+			for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels); outerEdgeLevelCaseNdx++)
+			{
+				typedef std::set<Vec3, VecLexLessThan<3> > Vec3Set;
+
+				Vec3Set firstEdgeVertices;
+
+				for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++)
+				{
+					const OuterEdgeDescription&		edgeDesc			= edgeDescriptions[outerEdgeIndex];
+					const vector<float>				patchTessLevels		= generatePatchTessLevels(1, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
+					const glu::VertexArrayBinding	bindings[]			= { glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0]) };
+
+					log << TestLog::Message << "Testing with outer tessellation level " << singleOuterEdgeLevels[outerEdgeLevelCaseNdx]
+											<< " for the " << edgeDesc.description() << " edge, and with various levels for other edges" << TestLog::EndMessage;
+
+					{
+						const TFHandler::Result		tfResult		= tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
+																										DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size());
+						const int					refNumVertices	= referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &patchTessLevels[0], &patchTessLevels[2]);
+
+						if ((int)tfResult.varying.size() != refNumVertices)
+						{
+							log << TestLog::Message << "Failure: the number of vertices returned by transform feedback is "
+													<< tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage
+								<< TestLog::Message << "Note: rendered 1 patch, tessellation levels are (in order [inner0, inner1, outer0, outer1, outer2, outer3]):\n"
+													<< containerStr(patchTessLevels, 6) << TestLog::EndMessage;
+
+							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
+							return STOP;
+						}
+
+						{
+							Vec3Set currentEdgeVertices;
+
+							// Get the vertices on the current outer edge.
+							for(int vtxNdx = 0; vtxNdx < refNumVertices; vtxNdx++)
+							{
+								const Vec3& vtx = tfResult.varying[vtxNdx];
+								if (edgeDesc.contains(vtx))
+								{
+									// Swizzle components to match the order of the first edge.
+									if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+									{
+										currentEdgeVertices.insert(outerEdgeIndex == 0 ? vtx
+																	: outerEdgeIndex == 1 ? vtx.swizzle(1, 0, 2)
+																	: outerEdgeIndex == 2 ? vtx.swizzle(2, 1, 0)
+																	: Vec3(-1.0f));
+									}
+									else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
+									{
+										currentEdgeVertices.insert(Vec3(outerEdgeIndex == 0 ? vtx.y()
+																		: outerEdgeIndex == 1 ? vtx.x()
+																		: outerEdgeIndex == 2 ? vtx.y()
+																		: outerEdgeIndex == 3 ? vtx.x()
+																		: -1.0f,
+																		0.0f, 0.0f));
+									}
+									else
+										DE_ASSERT(false);
+								}
+							}
+
+							if (outerEdgeIndex == 0)
+								firstEdgeVertices = currentEdgeVertices;
+							else
+							{
+								// Compare vertices of this edge to those of the first edge.
+
+								if (currentEdgeVertices != firstEdgeVertices)
+								{
+									const char* const swizzleDesc = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? (outerEdgeIndex == 1 ? "(y, x, z)"
+																													: outerEdgeIndex == 2 ? "(z, y, x)"
+																													: DE_NULL)
+																	: m_primitiveType == TESSPRIMITIVETYPE_QUADS ? (outerEdgeIndex == 1 ? "(x, 0)"
+																												: outerEdgeIndex == 2 ? "(y, 0)"
+																												: outerEdgeIndex == 3 ? "(x, 0)"
+																												: DE_NULL)
+																	: DE_NULL;
+
+									log << TestLog::Message << "Failure: the set of vertices on the " << edgeDesc.description() << " edge"
+															<< " doesn't match the set of vertices on the " << edgeDescriptions[0].description() << " edge" << TestLog::EndMessage
+										<< TestLog::Message << "Note: set of vertices on " << edgeDesc.description() << " edge, components swizzled like " << swizzleDesc
+															<< " to match component order on first edge:\n" << containerStr(currentEdgeVertices, 5)
+															<< "\non " << edgeDescriptions[0].description() << " edge:\n" << containerStr(firstEdgeVertices, 5) << TestLog::EndMessage;
+									m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
+									return STOP;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test invariance rule #5
+ *
+ * Test that the set of triangles input to the TES only depends on the
+ * tessellation levels, tessellation mode and spacing mode. Specifically,
+ * winding doesn't change the set of triangles, though it can change the
+ * order in which they are input to TES, and can (and will) change the
+ * vertex order within a triangle.
+ *//*--------------------------------------------------------------------*/
+class InvariantTriangleSetCase : public PrimitiveSetInvarianceCase
+{
+public:
+	InvariantTriangleSetCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing)
+		: PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY)
+	{
+		DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
+	}
+
+protected:
+	virtual bool compare (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, int) const
+	{
+		return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog());
+	}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test invariance rule #6
+ *
+ * Test that the set of inner triangles input to the TES only depends on
+ * the inner tessellation levels, tessellation mode and spacing mode.
+ *//*--------------------------------------------------------------------*/
+class InvariantInnerTriangleSetCase : public PrimitiveSetInvarianceCase
+{
+public:
+	InvariantInnerTriangleSetCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing)
+		: PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY)
+	{
+		DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
+	}
+
+protected:
+	virtual vector<LevelCase> genTessLevelCases (void) const
+	{
+		const int					numSubCases		= 4;
+		const vector<LevelCase>		baseResults		= PrimitiveSetInvarianceCase::genTessLevelCases();
+		vector<LevelCase>			result;
+		de::Random					rnd				(123);
+
+		// Generate variants with different values for irrelevant levels.
+		for (int baseNdx = 0; baseNdx < (int)baseResults.size(); baseNdx++)
+		{
+			const TessLevels&	base	= baseResults[baseNdx].levels[0];
+			TessLevels			levels	= base;
+			LevelCase			levelCase;
+
+			for (int subNdx = 0; subNdx < numSubCases; subNdx++)
+			{
+				levelCase.levels.push_back(levels);
+
+				for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); i++)
+					levels.outer[i] = rnd.getFloat(2.0f, 16.0f);
+				if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+					levels.inner[1] = rnd.getFloat(2.0f, 16.0f);
+			}
+
+			result.push_back(levelCase);
+		}
+
+		return result;
+	}
+
+	struct IsInnerTriangleTriangle
+	{
+		bool operator() (const Vec3* vertices) const
+		{
+			for (int v = 0; v < 3; v++)
+				for (int c = 0; c < 3; c++)
+					if (vertices[v][c] == 0.0f)
+						return false;
+			return true;
+		}
+	};
+
+	struct IsInnerQuadTriangle
+	{
+		bool operator() (const Vec3* vertices) const
+		{
+			for (int v = 0; v < 3; v++)
+				for (int c = 0; c < 2; c++)
+					if (vertices[v][c] == 0.0f || vertices[v][c] == 1.0f)
+						return false;
+			return true;
+		}
+	};
+
+	virtual bool compare (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, int) const
+	{
+		if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+			return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), IsInnerTriangleTriangle(), "outer triangles");
+		else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
+			return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), IsInnerQuadTriangle(), "outer triangles");
+		else
+		{
+			DE_ASSERT(false);
+			return false;
+		}
+	}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test invariance rule #7
+ *
+ * Test that the set of outer triangles input to the TES only depends on
+ * tessellation mode, spacing mode and the inner and outer tessellation
+ * levels corresponding to the inner and outer edges relevant to that
+ * triangle.
+ *//*--------------------------------------------------------------------*/
+class InvariantOuterTriangleSetCase : public PrimitiveSetInvarianceCase
+{
+public:
+	InvariantOuterTriangleSetCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing)
+		: PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY)
+	{
+		DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
+	}
+
+protected:
+	virtual vector<LevelCase> genTessLevelCases (void) const
+	{
+		const int					numSubCasesPerEdge	= 4;
+		const int					numEdges			= m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES	? 3
+														: m_primitiveType == TESSPRIMITIVETYPE_QUADS		? 4
+														: -1;
+		const vector<LevelCase>		baseResult			= PrimitiveSetInvarianceCase::genTessLevelCases();
+		vector<LevelCase>			result;
+		de::Random					rnd					(123);
+
+		// Generate variants with different values for irrelevant levels.
+		for (int baseNdx = 0; baseNdx < (int)baseResult.size(); baseNdx++)
+		{
+			const TessLevels& base = baseResult[baseNdx].levels[0];
+			if (base.inner[0] == 1.0f || (m_primitiveType == TESSPRIMITIVETYPE_QUADS && base.inner[1] == 1.0f))
+				continue;
+
+			for (int edgeNdx = 0; edgeNdx < numEdges; edgeNdx++)
+			{
+				TessLevels	levels = base;
+				LevelCase	levelCase;
+				levelCase.mem = edgeNdx;
+
+				for (int subCaseNdx = 0; subCaseNdx < numSubCasesPerEdge; subCaseNdx++)
+				{
+					levelCase.levels.push_back(levels);
+
+					for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); i++)
+					{
+						if (i != edgeNdx)
+							levels.outer[i] = rnd.getFloat(2.0f, 16.0f);
+					}
+
+					if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+						levels.inner[1] = rnd.getFloat(2.0f, 16.0f);
+				}
+
+				result.push_back(levelCase);
+			}
+		}
+
+		return result;
+	}
+
+	class IsTriangleTriangleOnOuterEdge
+	{
+	public:
+		IsTriangleTriangleOnOuterEdge (int edgeNdx) : m_edgeNdx(edgeNdx) {}
+		bool operator() (const Vec3* vertices) const
+		{
+			bool touchesAppropriateEdge = false;
+			for (int v = 0; v < 3; v++)
+				if (vertices[v][m_edgeNdx] == 0.0f)
+					touchesAppropriateEdge = true;
+
+			if (touchesAppropriateEdge)
+			{
+				const Vec3 avg = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
+				return avg[m_edgeNdx] < avg[(m_edgeNdx+1)%3] &&
+					   avg[m_edgeNdx] < avg[(m_edgeNdx+2)%3];
+			}
+			return false;
+		}
+
+	private:
+		int m_edgeNdx;
+	};
+
+	class IsQuadTriangleOnOuterEdge
+	{
+	public:
+		IsQuadTriangleOnOuterEdge (int edgeNdx) : m_edgeNdx(edgeNdx) {}
+
+		bool onEdge (const Vec3& v) const
+		{
+			return v[m_edgeNdx%2] == (m_edgeNdx <= 1 ? 0.0f : 1.0f);
+		}
+
+		static inline bool onAnyEdge (const Vec3& v)
+		{
+			return v[0] == 0.0f || v[0] == 1.0f || v[1] == 0.0f || v[1] == 1.0f;
+		}
+
+		bool operator() (const Vec3* vertices) const
+		{
+			for (int v = 0; v < 3; v++)
+			{
+				const Vec3& a = vertices[v];
+				const Vec3& b = vertices[(v+1)%3];
+				const Vec3& c = vertices[(v+2)%3];
+				if (onEdge(a) && onEdge(b))
+					return true;
+				if (onEdge(c) && !onAnyEdge(a) && !onAnyEdge(b) && a[m_edgeNdx%2] == b[m_edgeNdx%2])
+					return true;
+			}
+
+			return false;
+		}
+
+	private:
+		int m_edgeNdx;
+	};
+
+	virtual bool compare (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, int outerEdgeNdx) const
+	{
+		if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
+		{
+			return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(),
+									   IsTriangleTriangleOnOuterEdge(outerEdgeNdx),
+									   ("inner triangles, and outer triangles corresponding to other edge than edge "
+										+ outerEdgeDescriptions(m_primitiveType)[outerEdgeNdx].description()).c_str());
+		}
+		else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
+		{
+			return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(),
+									   IsQuadTriangleOnOuterEdge(outerEdgeNdx),
+									   ("inner triangles, and outer triangles corresponding to other edge than edge "
+										+ outerEdgeDescriptions(m_primitiveType)[outerEdgeNdx].description()).c_str());
+		}
+		else
+			DE_ASSERT(false);
+
+		return true;
+	}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Base class for testing individual components of tess coords
+ *
+ * Useful for testing parts of invariance rule #8.
+ *//*--------------------------------------------------------------------*/
+class TessCoordComponentInvarianceCase : public TestCase
+{
+public:
+	TessCoordComponentInvarianceCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
+		: TestCase			(context, name, description)
+		, m_primitiveType	(primType)
+		, m_spacing			(spacing)
+		, m_winding			(winding)
+		, m_usePointMode	(usePointMode)
+	{
+	}
+
+	void									init		(void);
+	void									deinit		(void);
+	IterateResult							iterate		(void);
+
+protected:
+	virtual string							tessEvalOutputComponentStatements	(const char* tessCoordComponentName, const char* outputComponentName) const = 0;
+	virtual bool							checkTessCoordComponent				(float component) const = 0;
+
+private:
+	static vector<float>					genTessLevelCases (int numCases);
+
+	static const int						RENDER_SIZE = 16;
+
+	const TessPrimitiveType					m_primitiveType;
+	const SpacingMode						m_spacing;
+	const Winding							m_winding;
+	const bool								m_usePointMode;
+
+	SharedPtr<const glu::ShaderProgram>		m_program;
+};
+
+void TessCoordComponentInvarianceCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+		<< glu::VertexSource					("#version 310 es\n"
+												 "\n"
+												 "in highp float in_v_attr;\n"
+												 "out highp float in_tc_attr;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	in_tc_attr = in_v_attr;\n"
+												 "}\n")
+
+		<< glu::TessellationControlSource		("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 "layout (vertices = 1) out;\n"
+												 "\n"
+												 "in highp float in_tc_attr[];\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
+												 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
+												 "\n"
+												 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
+												 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
+												 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
+												 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
+												 "}\n")
+
+		<< glu::TessellationEvaluationSource	("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
+												 "\n"
+												 "out highp vec4 in_f_color;\n"
+												 "out highp vec3 out_te_output;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 + tessEvalOutputComponentStatements("gl_TessCoord.x", "out_te_output.x")
+												 + tessEvalOutputComponentStatements("gl_TessCoord.y", "out_te_output.y")
+
+												 + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
+													tessEvalOutputComponentStatements("gl_TessCoord.z", "out_te_output.z") :
+													"	out_te_output.z = 0.0f;\n") +
+												 "	gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n"
+												 "	in_f_color = vec4(1.0);\n"
+												 "}\n")
+
+		<< glu::FragmentSource					("#version 310 es\n"
+												 "\n"
+												 "layout (location = 0) out mediump vec4 o_color;\n"
+												 "\n"
+												 "in highp vec4 in_f_color;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	o_color = in_f_color;\n"
+												 "}\n")
+
+		<< glu::TransformFeedbackVarying		("out_te_output")
+		<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void TessCoordComponentInvarianceCase::deinit (void)
+{
+	m_program.clear();
+}
+
+vector<float> TessCoordComponentInvarianceCase::genTessLevelCases (int numCases)
+{
+	de::Random		rnd(123);
+	vector<float>	result;
+
+	for (int i = 0; i < numCases; i++)
+		for (int j = 0; j < 6; j++)
+			result.push_back(rnd.getFloat(1.0f, 63.0f));
+
+	return result;
+}
+
+TessCoordComponentInvarianceCase::IterateResult TessCoordComponentInvarianceCase::iterate (void)
+{
+	typedef TransformFeedbackHandler<Vec3> TFHandler;
+
+	TestLog&				log					= m_testCtx.getLog();
+	const RenderContext&	renderCtx			= m_context.getRenderContext();
+	const RandomViewport	viewport			(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const glw::Functions&	gl					= renderCtx.getFunctions();
+	const int				numTessLevelCases	= 32;
+	const vector<float>		tessLevels			= genTessLevelCases(numTessLevelCases);
+	const deUint32			programGL			= m_program->getProgram();
+
+	gl.useProgram(programGL);
+	setViewport(gl, viewport);
+	gl.patchParameteri(GL_PATCH_VERTICES, 6);
+
+	{
+		// Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
+		int maxNumVerticesInDrawCall = 0;
+		for (int i = 0; i < numTessLevelCases; i++)
+			maxNumVerticesInDrawCall = de::max(maxNumVerticesInDrawCall, referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &tessLevels[6*i+0], &tessLevels[6*i+2]));
+
+		{
+			const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);
+
+			for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < numTessLevelCases; tessLevelCaseNdx++)
+			{
+				log << TestLog::Message << "Testing with tessellation levels: " << tessellationLevelsString(&tessLevels[6*tessLevelCaseNdx+0], &tessLevels[6*tessLevelCaseNdx+2]) << TestLog::EndMessage;
+
+				const glu::VertexArrayBinding bindings[] = { glu::va::Float("in_v_attr", 1, (int)6, 0, &tessLevels[6*tessLevelCaseNdx]) };
+				const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
+																					DE_LENGTH_OF_ARRAY(bindings), &bindings[0], 6);
+
+				for (int vtxNdx = 0; vtxNdx < (int)tfResult.varying.size(); vtxNdx++)
+				{
+					const Vec3&		vec			= tfResult.varying[vtxNdx];
+					const int		numComps	= m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : 2;
+
+					for (int compNdx = 0; compNdx < numComps; compNdx++)
+					{
+						if (!checkTessCoordComponent(vec[compNdx]))
+						{
+							log << TestLog::Message << "Note: output value at index " << vtxNdx << " is "
+													<< (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? de::toString(vec) : de::toString(vec.swizzle(0,1)))
+													<< TestLog::EndMessage;
+							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid tessellation coordinate component");
+							return STOP;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test first part of invariance rule #8
+ *
+ * Test that all (relevant) components of tess coord are in [0,1].
+ *//*--------------------------------------------------------------------*/
+class TessCoordComponentRangeCase : public TessCoordComponentInvarianceCase
+{
+public:
+	TessCoordComponentRangeCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
+		: TessCoordComponentInvarianceCase(context, name, description, primType, spacing, winding, usePointMode)
+	{
+	}
+
+protected:
+	virtual string tessEvalOutputComponentStatements (const char* tessCoordComponentName, const char* outputComponentName) const
+	{
+		return string() + "\t" + outputComponentName + " = " + tessCoordComponentName + ";\n";
+	}
+
+	virtual bool checkTessCoordComponent (float component) const
+	{
+		if (!de::inRange(component, 0.0f, 1.0f))
+		{
+			m_testCtx.getLog() << TestLog::Message << "Failure: tess coord component isn't in range [0,1]" << TestLog::EndMessage;
+			return false;
+		}
+		return true;
+	}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test second part of invariance rule #8
+ *
+ * Test that all (relevant) components of tess coord are in [0,1] and
+ * 1.0-c is exact for every such component c.
+ *//*--------------------------------------------------------------------*/
+class OneMinusTessCoordComponentCase : public TessCoordComponentInvarianceCase
+{
+public:
+	OneMinusTessCoordComponentCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
+		: TessCoordComponentInvarianceCase(context, name, description, primType, spacing, winding, usePointMode)
+	{
+	}
+
+protected:
+	virtual string tessEvalOutputComponentStatements (const char* tessCoordComponentName, const char* outputComponentName) const
+	{
+		return string() + "	{\n"
+						  "		float oneMinusComp = 1.0 - " + tessCoordComponentName + ";\n"
+						  "		" + outputComponentName + " = " + tessCoordComponentName + " + oneMinusComp;\n"
+						  "	}\n";
+	}
+
+	virtual bool checkTessCoordComponent (float component) const
+	{
+		if (component != 1.0f)
+		{
+			m_testCtx.getLog() << TestLog::Message << "Failure: comp + (1.0-comp) doesn't equal 1.0 for some component of tessellation coordinate" << TestLog::EndMessage;
+			return false;
+		}
+		return true;
+	}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test that patch is discarded if relevant outer level <= 0.0
+ *
+ * Draws patches with different combinations of tessellation levels,
+ * varying which levels are negative. Verifies by checking that colored
+ * pixels exist inside the area of valid primitives, and only black pixels
+ * exist inside the area of discarded primitives. An additional sanity
+ * test is done, checking that the number of primitives written by TF is
+ * correct.
+ *//*--------------------------------------------------------------------*/
+class PrimitiveDiscardCase : public TestCase
+{
+public:
+	PrimitiveDiscardCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
+		: TestCase			(context, name, description)
+		, m_primitiveType	(primType)
+		, m_spacing			(spacing)
+		, m_winding			(winding)
+		, m_usePointMode	(usePointMode)
+	{
+	}
+
+	void									init		(void);
+	void									deinit		(void);
+	IterateResult							iterate		(void);
+
+private:
+	static vector<float>					genAttributes (void);
+
+	static const int						RENDER_SIZE = 256;
+
+	const TessPrimitiveType					m_primitiveType;
+	const SpacingMode						m_spacing;
+	const Winding							m_winding;
+	const bool								m_usePointMode;
+
+	SharedPtr<const glu::ShaderProgram>		m_program;
+};
+
+void PrimitiveDiscardCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+		<< glu::VertexSource					("#version 310 es\n"
+												 "\n"
+												 "in highp float in_v_attr;\n"
+												 "out highp float in_tc_attr;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	in_tc_attr = in_v_attr;\n"
+												 "}\n")
+
+		<< glu::TessellationControlSource		("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 "layout (vertices = 1) out;\n"
+												 "\n"
+												 "in highp float in_tc_attr[];\n"
+												 "\n"
+												 "patch out highp vec2 in_te_positionScale;\n"
+												 "patch out highp vec2 in_te_positionOffset;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	in_te_positionScale  = vec2(in_tc_attr[6], in_tc_attr[7]);\n"
+												 "	in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n"
+												 "\n"
+												 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
+												 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
+												 "\n"
+												 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
+												 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
+												 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
+												 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
+												 "}\n")
+
+		<< glu::TessellationEvaluationSource	("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
+												 "\n"
+												 "patch in highp vec2 in_te_positionScale;\n"
+												 "patch in highp vec2 in_te_positionOffset;\n"
+												 "\n"
+												 "out highp vec3 out_te_tessCoord;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	out_te_tessCoord = gl_TessCoord;\n"
+												 "	gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n"
+												 "}\n")
+
+		<< glu::FragmentSource					("#version 310 es\n"
+												 "\n"
+												 "layout (location = 0) out mediump vec4 o_color;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	o_color = vec4(1.0);\n"
+												 "}\n")
+
+		<< glu::TransformFeedbackVarying		("out_te_tessCoord")
+		<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void PrimitiveDiscardCase::deinit (void)
+{
+	m_program.clear();
+}
+
+vector<float> PrimitiveDiscardCase::genAttributes (void)
+{
+	// Generate input attributes (tessellation levels, and position scale and
+	// offset) for a number of primitives. Each primitive has a different
+	// combination of tessellatio levels; each level is either a valid
+	// value or an "invalid" value (negative or zero, chosen from
+	// invalidTessLevelChoices).
+
+	// \note The attributes are generated in such an order that all of the
+	//		 valid attribute tuples come before the first invalid one both
+	//		 in the result vector, and when scanning the resulting 2d grid
+	//		 of primitives is scanned in y-major order. This makes
+	//		 verification somewhat simpler.
+
+	static const float	baseTessLevels[6]			= { 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f };
+	static const float	invalidTessLevelChoices[]	= { -0.42f, 0.0f };
+	const int			numChoices					= 1 + DE_LENGTH_OF_ARRAY(invalidTessLevelChoices);
+	float				choices[6][numChoices];
+	vector<float>		result;
+
+	for (int levelNdx = 0; levelNdx < 6; levelNdx++)
+		for (int choiceNdx = 0; choiceNdx < numChoices; choiceNdx++)
+			choices[levelNdx][choiceNdx] = choiceNdx == 0 ? baseTessLevels[levelNdx] : invalidTessLevelChoices[choiceNdx-1];
+
+	{
+		const int	numCols			= intPow(numChoices, 6/2); // sqrt(numChoices**6) == sqrt(number of primitives)
+		const int	numRows			= numCols;
+		int			index			= 0;
+		int			i[6];
+		// We could do this with some generic combination-generation function, but meh, it's not that bad.
+		for (i[2] = 0; i[2] < numChoices; i[2]++) // First  outer
+		for (i[3] = 0; i[3] < numChoices; i[3]++) // Second outer
+		for (i[4] = 0; i[4] < numChoices; i[4]++) // Third  outer
+		for (i[5] = 0; i[5] < numChoices; i[5]++) // Fourth outer
+		for (i[0] = 0; i[0] < numChoices; i[0]++) // First  inner
+		for (i[1] = 0; i[1] < numChoices; i[1]++) // Second inner
+		{
+			for (int j = 0; j < 6; j++)
+				result.push_back(choices[j][i[j]]);
+
+			{
+				const int col = index % numCols;
+				const int row = index / numCols;
+				// Position scale.
+				result.push_back((float)2.0f / (float)numCols);
+				result.push_back((float)2.0f / (float)numRows);
+				// Position offset.
+				result.push_back((float)col / (float)numCols * 2.0f - 1.0f);
+				result.push_back((float)row / (float)numRows * 2.0f - 1.0f);
+			}
+
+			index++;
+		}
+	}
+
+	return result;
+}
+
+PrimitiveDiscardCase::IterateResult PrimitiveDiscardCase::iterate (void)
+{
+	typedef TransformFeedbackHandler<Vec3> TFHandler;
+
+	TestLog&				log						= m_testCtx.getLog();
+	const RenderContext&	renderCtx				= m_context.getRenderContext();
+	const RandomViewport	viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const glw::Functions&	gl						= renderCtx.getFunctions();
+	const vector<float>		attributes				= genAttributes();
+	const int				numAttribsPerPrimitive	= 6+2+2; // Tess levels, scale, offset.
+	const int				numPrimitives			= (int)attributes.size() / numAttribsPerPrimitive;
+	const deUint32			programGL				= m_program->getProgram();
+
+	gl.useProgram(programGL);
+	setViewport(gl, viewport);
+	gl.patchParameteri(GL_PATCH_VERTICES, numAttribsPerPrimitive);
+
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	// Check the convenience assertion that all discarded patches come after the last non-discarded patch.
+	{
+		bool discardedPatchEncountered = false;
+		for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++)
+		{
+			const bool discard = isPatchDiscarded(m_primitiveType, &attributes[numAttribsPerPrimitive*patchNdx + 2]);
+			DE_ASSERT(discard || !discardedPatchEncountered);
+			discardedPatchEncountered = discard;
+		}
+		DE_UNREF(discardedPatchEncountered);
+	}
+
+	{
+		int numVerticesInDrawCall = 0;
+		for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++)
+			numVerticesInDrawCall += referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &attributes[numAttribsPerPrimitive*patchNdx+0], &attributes[numAttribsPerPrimitive*patchNdx+2]);
+
+		log << TestLog::Message << "Note: rendering " << numPrimitives << " patches; first patches have valid relevant outer levels, "
+								<< "but later patches have one or more invalid (i.e. less than or equal to 0.0) relevant outer levels" << TestLog::EndMessage;
+
+		{
+			const TFHandler					tfHandler	(m_context.getRenderContext(), numVerticesInDrawCall);
+			const glu::VertexArrayBinding	bindings[]	= { glu::va::Float("in_v_attr", 1, (int)attributes.size(), 0, &attributes[0]) };
+			const TFHandler::Result			tfResult	= tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
+																						   DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)attributes.size());
+			const tcu::Surface				pixels		= getPixels(renderCtx, viewport);
+
+			log << TestLog::Image("RenderedImage", "Rendered image", pixels);
+
+			if ((int)tfResult.varying.size() != numVerticesInDrawCall)
+			{
+				log << TestLog::Message << "Failure: expected " << numVerticesInDrawCall << " vertices from transform feedback, got " << tfResult.varying.size() << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Wrong number of tessellation coordinates");
+				return STOP;
+			}
+
+			// Check that white pixels are found around every non-discarded
+			// patch, and that only black pixels are found after the last
+			// non-discarded patch.
+			{
+				int lastWhitePixelRow									= 0;
+				int secondToLastWhitePixelRow							= 0;
+				int	lastWhitePixelColumnOnSecondToLastWhitePixelRow		= 0;
+
+				for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++)
+				{
+					const float* const	attr			= &attributes[numAttribsPerPrimitive*patchNdx];
+					const bool			validLevels		= !isPatchDiscarded(m_primitiveType, &attr[2]);
+
+					if (validLevels)
+					{
+						// Not a discarded patch; check that at least one white pixel is found in its area.
+
+						const float* const	scale		= &attr[6];
+						const float* const	offset		= &attr[8];
+						const int			x0			= (int)((			offset[0] + 1.0f)*0.5f*(float)pixels.getWidth()) - 1;
+						const int			x1			= (int)((scale[0] + offset[0] + 1.0f)*0.5f*(float)pixels.getWidth()) + 1;
+						const int			y0			= (int)((			offset[1] + 1.0f)*0.5f*(float)pixels.getHeight()) - 1;
+						const int			y1			= (int)((scale[1] + offset[1] + 1.0f)*0.5f*(float)pixels.getHeight()) + 1;
+						const bool			isMSAA		= renderCtx.getRenderTarget().getNumSamples() > 1;
+						bool				pixelOk		= false;
+
+						if (y1 > lastWhitePixelRow)
+						{
+							secondToLastWhitePixelRow	= lastWhitePixelRow;
+							lastWhitePixelRow			= y1;
+						}
+						lastWhitePixelColumnOnSecondToLastWhitePixelRow = x1;
+
+						for (int y = y0; y <= y1 && !pixelOk; y++)
+						for (int x = x0; x <= x1 && !pixelOk; x++)
+						{
+							if (!de::inBounds(x, 0, pixels.getWidth()) || !de::inBounds(y, 0, pixels.getHeight()))
+								continue;
+
+							if (isMSAA)
+							{
+								if (pixels.getPixel(x, y) != tcu::RGBA::black)
+									pixelOk = true;
+							}
+							else
+							{
+								if (pixels.getPixel(x, y) == tcu::RGBA::white)
+									pixelOk = true;
+							}
+						}
+
+						if (!pixelOk)
+						{
+							log << TestLog::Message << "Failure: expected at least one " << (isMSAA ? "non-black" : "white") << " pixel in the rectangle "
+													<< "[x0=" << x0 << ", y0=" << y0 << ", x1=" << x1 << ", y1=" << y1 << "]" << TestLog::EndMessage
+								<< TestLog::Message << "Note: the rectangle approximately corresponds to the patch with these tessellation levels: "
+													<< tessellationLevelsString(&attr[0], &attr[1]) << TestLog::EndMessage;
+							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+							return STOP;
+						}
+					}
+					else
+					{
+						// First discarded primitive patch; the remaining are guaranteed to be discarded ones as well.
+
+						for (int y = 0; y < pixels.getHeight(); y++)
+						for (int x = 0; x < pixels.getWidth(); x++)
+						{
+							if (y > lastWhitePixelRow || (y > secondToLastWhitePixelRow && x > lastWhitePixelColumnOnSecondToLastWhitePixelRow))
+							{
+								if (pixels.getPixel(x, y) != tcu::RGBA::black)
+								{
+									log << TestLog::Message << "Failure: expected all pixels to be black in the area "
+															<< (lastWhitePixelColumnOnSecondToLastWhitePixelRow < pixels.getWidth()-1
+																	? string() + "y > " + de::toString(lastWhitePixelRow) + " || (y > " + de::toString(secondToLastWhitePixelRow)
+																			   + " && x > " + de::toString(lastWhitePixelColumnOnSecondToLastWhitePixelRow) + ")"
+																	: string() + "y > " + de::toString(lastWhitePixelRow))
+															<< " (they all correspond to patches that should be discarded)" << TestLog::EndMessage
+										<< TestLog::Message << "Note: pixel " << tcu::IVec2(x, y) << " isn't black" << TestLog::EndMessage;
+									m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+									return STOP;
+								}
+							}
+						}
+
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Case testing user-defined IO between TCS and TES
+ *
+ * TCS outputs various values to TES, including aggregates. The outputs
+ * can be per-patch or per-vertex, and if per-vertex, they can also be in
+ * an IO block. Per-vertex input array size can be left implicit (i.e.
+ * inputArray[]) or explicit either by gl_MaxPatchVertices or an integer
+ * literal whose value is queried from GL.
+ *
+ * The values output are generated in TCS and verified in TES against
+ * similarly generated values. In case a verification of a value fails, the
+ * index of the invalid value is output with TF.
+ * As a sanity check, also the rendering result is verified (against pre-
+ * rendered reference).
+ *//*--------------------------------------------------------------------*/
+class UserDefinedIOCase : public TestCase
+{
+public:
+	enum IOType
+	{
+		IO_TYPE_PER_PATCH = 0,
+		IO_TYPE_PER_PATCH_ARRAY,
+		IO_TYPE_PER_VERTEX,
+		IO_TYPE_PER_VERTEX_BLOCK,
+
+		IO_TYPE_LAST
+	};
+
+	enum VertexIOArraySize
+	{
+		VERTEX_IO_ARRAY_SIZE_IMPLICIT = 0,
+		VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN,		//!< Use gl_MaxPatchVertices as size for per-vertex input array.
+		VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY,				//!< Query GL_MAX_PATCH_VERTICES, and use that as size for per-vertex input array.
+
+		VERTEX_IO_ARRAY_SIZE_LAST
+	};
+
+	UserDefinedIOCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, IOType ioType, VertexIOArraySize vertexIOArraySize, const char* referenceImagePath)
+		: TestCase				(context, name, description)
+		, m_primitiveType		(primType)
+		, m_ioType				(ioType)
+		, m_vertexIOArraySize	(vertexIOArraySize)
+		, m_referenceImagePath	(referenceImagePath)
+	{
+	}
+
+	void									init		(void);
+	void									deinit		(void);
+	IterateResult							iterate		(void);
+
+private:
+	typedef string (*BasicTypeVisitFunc)(const string& name, glu::DataType type, int indentationDepth); //!< See glslTraverseBasicTypes below.
+
+	class TopLevelObject
+	{
+	public:
+		virtual			~TopLevelObject					(void) {}
+
+		virtual string	name							(void) const = 0;
+		virtual string	declare							(const string& arraySizeExpr) const = 0;
+		virtual string	glslTraverseBasicTypes			(int numArrayElements, //!< If negative, traverse just array[gl_InvocationID], not all indices.
+														 int indentationDepth,
+														 BasicTypeVisitFunc) const = 0;
+		virtual int		numBasicSubobjectsInElementType	(void) const = 0;
+		virtual string	basicSubobjectAtIndex			(int index, int arraySize) const = 0;
+	};
+
+	class Variable : public TopLevelObject
+	{
+	public:
+		Variable (const string& name_, const glu::VarType& type, bool isArray)
+			: m_name		(name_)
+			, m_type		(type)
+			, m_isArray		(isArray)
+		{
+			DE_ASSERT(!type.isArrayType());
+		}
+
+		string	name								(void) const { return m_name; }
+		string	declare								(const string& arraySizeExpr) const;
+		string	glslTraverseBasicTypes				(int numArrayElements, int indentationDepth, BasicTypeVisitFunc) const;
+		int		numBasicSubobjectsInElementType		(void) const;
+		string	basicSubobjectAtIndex				(int index, int arraySize) const;
+
+	private:
+		string			m_name;
+		glu::VarType	m_type; //!< If this Variable is an array element, m_type is the element type; otherwise just the variable type.
+		const bool		m_isArray;
+	};
+
+	class IOBlock : public TopLevelObject
+	{
+	public:
+		struct Member
+		{
+			string			name;
+			glu::VarType	type;
+			Member (const string& n, const glu::VarType& t) : name(n), type(t) {}
+		};
+
+		IOBlock (const string& blockName, const string& interfaceName, const vector<Member>& members)
+			: m_blockName		(blockName)
+			, m_interfaceName	(interfaceName)
+			, m_members			(members)
+		{
+		}
+
+		string	name								(void) const { return m_interfaceName; }
+		string	declare								(const string& arraySizeExpr) const;
+		string	glslTraverseBasicTypes				(int numArrayElements, int indentationDepth, BasicTypeVisitFunc) const;
+		int		numBasicSubobjectsInElementType		(void) const;
+		string	basicSubobjectAtIndex				(int index, int arraySize) const;
+
+	private:
+		string			m_blockName;
+		string			m_interfaceName;
+		vector<Member>	m_members;
+	};
+
+	static string							glslTraverseBasicTypes				(const string&			rootName,
+																				 const glu::VarType&	rootType,
+																				 int					arrayNestingDepth,
+																				 int					indentationDepth,
+																				 BasicTypeVisitFunc		visit);
+
+	static string							glslAssignBasicTypeObject			(const string& name, glu::DataType, int indentationDepth);
+	static string							glslCheckBasicTypeObject			(const string& name, glu::DataType, int indentationDepth);
+	static int								numBasicSubobjectsInElementType		(const vector<SharedPtr<TopLevelObject> >&);
+	static string							basicSubobjectAtIndex				(int index, const vector<SharedPtr<TopLevelObject> >&, int topLevelArraySizes);
+
+	static const int						RENDER_SIZE = 256;
+	static const int						NUM_OUTPUT_VERTICES;
+	static const int						NUM_PER_PATCH_ARRAY_ELEMS;
+
+	const TessPrimitiveType					m_primitiveType;
+	const IOType							m_ioType;
+	const VertexIOArraySize					m_vertexIOArraySize;
+	const string							m_referenceImagePath;
+
+	vector<glu::StructType>					m_structTypes;
+	vector<SharedPtr<TopLevelObject> >		m_tcsOutputs;
+	vector<SharedPtr<TopLevelObject> >		m_tesInputs;
+
+	SharedPtr<const glu::ShaderProgram>		m_program;
+};
+
+const int UserDefinedIOCase::NUM_OUTPUT_VERTICES			= 5;
+const int UserDefinedIOCase::NUM_PER_PATCH_ARRAY_ELEMS		= 3;
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Generate GLSL code to traverse (possibly aggregate) object
+ *
+ * Generates a string that represents GLSL code that traverses the
+ * basic-type subobjects in a rootType-typed object named rootName. Arrays
+ * are traversed with loops and struct members are each traversed
+ * separately. The code for each basic-type subobject is generated with
+ * the function given as the 'visit' argument.
+ *//*--------------------------------------------------------------------*/
+string UserDefinedIOCase::glslTraverseBasicTypes (const string&			rootName,
+												  const glu::VarType&	rootType,
+												  int					arrayNestingDepth,
+												  int					indentationDepth,
+												  BasicTypeVisitFunc	visit)
+{
+	if (rootType.isBasicType())
+		return visit(rootName, rootType.getBasicType(), indentationDepth);
+	else if (rootType.isArrayType())
+	{
+		const string indentation	= string(indentationDepth, '\t');
+		const string loopIndexName	= "i" + de::toString(arrayNestingDepth);
+		const string arrayLength	= de::toString(rootType.getArraySize());
+		return indentation + "for (int " + loopIndexName + " = 0; " + loopIndexName + " < " + de::toString(rootType.getArraySize()) + "; " + loopIndexName + "++)\n" +
+			   indentation + "{\n" +
+			   glslTraverseBasicTypes(rootName + "[" + loopIndexName + "]", rootType.getElementType(), arrayNestingDepth+1, indentationDepth+1, visit) +
+			   indentation + "}\n";
+	}
+	else if (rootType.isStructType())
+	{
+		const glu::StructType&	structType = *rootType.getStructPtr();
+		const int				numMembers = structType.getNumMembers();
+		string					result;
+
+		for (int membNdx = 0; membNdx < numMembers; membNdx++)
+		{
+			const glu::StructMember& member = structType.getMember(membNdx);
+			result += glslTraverseBasicTypes(rootName + "." + member.getName(), member.getType(), arrayNestingDepth, indentationDepth, visit);
+		}
+
+		return result;
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return DE_NULL;
+	}
+}
+
+string UserDefinedIOCase::Variable::declare (const string& sizeExpr) const
+{
+	return de::toString(glu::declare(m_type, m_name)) + (m_isArray ? "[" + sizeExpr + "]" : "") + ";\n";
+}
+
+string UserDefinedIOCase::IOBlock::declare (const string& sizeExpr) const
+{
+	string result = m_blockName + "\n" +
+					"{\n";
+	for (int i = 0; i < (int)m_members.size(); i++)
+		result += "\t" + de::toString(glu::declare(m_members[i].type, m_members[i].name)) + ";\n";
+	result += "} " + m_interfaceName + "[" + sizeExpr + "]" + ";\n";
+	return result;
+}
+
+string UserDefinedIOCase::Variable::glslTraverseBasicTypes (int numArrayElements, int indentationDepth, BasicTypeVisitFunc visit) const
+{
+	const bool				traverseAsArray		= m_isArray && numArrayElements >= 0;
+	const string			traversedName		= m_name + (m_isArray && !traverseAsArray ? "[gl_InvocationID]" : "");
+	const glu::VarType		type				= traverseAsArray ? glu::VarType(m_type, numArrayElements) : m_type;
+
+	return UserDefinedIOCase::glslTraverseBasicTypes(traversedName, type, 0, indentationDepth, visit);
+}
+
+string UserDefinedIOCase::IOBlock::glslTraverseBasicTypes (int numArrayElements, int indentationDepth, BasicTypeVisitFunc visit) const
+{
+	if (numArrayElements >= 0)
+	{
+		const string	indentation			= string(indentationDepth, '\t');
+		string			result				= indentation + "for (int i0 = 0; i0 < " + de::toString(numArrayElements) + "; i0++)\n" +
+											  indentation + "{\n";
+		for (int i = 0; i < (int)m_members.size(); i++)
+			result += UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "[i0]." + m_members[i].name, m_members[i].type, 1, indentationDepth+1, visit);
+		result += indentation + "}\n";
+		return result;
+	}
+	else
+	{
+		string result;
+		for (int i = 0; i < (int)m_members.size(); i++)
+			result += UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "[gl_InvocationID]." + m_members[i].name, m_members[i].type, 0, indentationDepth, visit);
+		return result;
+	}
+}
+
+int UserDefinedIOCase::Variable::numBasicSubobjectsInElementType (void) const
+{
+	return numBasicSubobjects(m_type);
+}
+
+int UserDefinedIOCase::IOBlock::numBasicSubobjectsInElementType (void) const
+{
+	int result = 0;
+	for (int i = 0; i < (int)m_members.size(); i++)
+		result += numBasicSubobjects(m_members[i].type);
+	return result;
+}
+
+string UserDefinedIOCase::Variable::basicSubobjectAtIndex (int subobjectIndex, int arraySize) const
+{
+	const glu::VarType	type			= m_isArray ? glu::VarType(m_type, arraySize) : m_type;
+	int					currentIndex	= 0;
+
+	for (glu::BasicTypeIterator basicIt = glu::BasicTypeIterator::begin(&type);
+		 basicIt != glu::BasicTypeIterator::end(&type);
+		 ++basicIt)
+	{
+		if (currentIndex == subobjectIndex)
+			return m_name + de::toString(glu::TypeAccessFormat(type, basicIt.getPath()));
+		currentIndex++;
+	}
+	DE_ASSERT(false);
+	return DE_NULL;
+}
+
+string UserDefinedIOCase::IOBlock::basicSubobjectAtIndex (int subobjectIndex, int arraySize) const
+{
+	int currentIndex = 0;
+	for (int arrayNdx = 0; arrayNdx < arraySize; arrayNdx++)
+	{
+		for (int memberNdx = 0; memberNdx < (int)m_members.size(); memberNdx++)
+		{
+			const glu::VarType& membType = m_members[memberNdx].type;
+			for (glu::BasicTypeIterator basicIt = glu::BasicTypeIterator::begin(&membType);
+				 basicIt != glu::BasicTypeIterator::end(&membType);
+				 ++basicIt)
+			{
+				if (currentIndex == subobjectIndex)
+					return m_interfaceName + "[" + de::toString(arrayNdx) + "]." + m_members[memberNdx].name + de::toString(glu::TypeAccessFormat(membType, basicIt.getPath()));
+				currentIndex++;
+			}
+		}
+	}
+	DE_ASSERT(false);
+	return DE_NULL;
+}
+
+// Used as the 'visit' argument for glslTraverseBasicTypes.
+string UserDefinedIOCase::glslAssignBasicTypeObject (const string& name, glu::DataType type, int indentationDepth)
+{
+	const int		scalarSize		= glu::getDataTypeScalarSize(type);
+	const string	indentation		= string(indentationDepth, '\t');
+	string			result;
+
+	result += indentation + name + " = ";
+
+	if (type != glu::TYPE_FLOAT)
+		result += string() + glu::getDataTypeName(type) + "(";
+	for (int i = 0; i < scalarSize; i++)
+		result += (i > 0 ? ", v+" + de::floatToString(0.8f*(float)i, 1)
+						 : "v");
+	if (type != glu::TYPE_FLOAT)
+		result += ")";
+	result += ";\n" +
+			  indentation + "v += 0.4;\n";
+	return result;
+}
+
+// Used as the 'visit' argument for glslTraverseBasicTypes.
+string UserDefinedIOCase::glslCheckBasicTypeObject (const string& name, glu::DataType type, int indentationDepth)
+{
+	const int		scalarSize		= glu::getDataTypeScalarSize(type);
+	const string	indentation		= string(indentationDepth, '\t');
+	string			result;
+
+	result += indentation + "allOk = allOk && compare_" + glu::getDataTypeName(type) + "(" + name + ", ";
+
+	if (type != glu::TYPE_FLOAT)
+		result += string() + glu::getDataTypeName(type) + "(";
+	for (int i = 0; i < scalarSize; i++)
+		result += (i > 0 ? ", v+" + de::floatToString(0.8f*(float)i, 1)
+						 : "v");
+	if (type != glu::TYPE_FLOAT)
+		result += ")";
+	result += ");\n" +
+			  indentation + "v += 0.4;\n" +
+			  indentation + "if (allOk) firstFailedInputIndex++;\n";
+
+	return result;
+}
+
+int UserDefinedIOCase::numBasicSubobjectsInElementType (const vector<SharedPtr<TopLevelObject> >& objects)
+{
+	int result = 0;
+	for (int i = 0; i < (int)objects.size(); i++)
+		result += objects[i]->numBasicSubobjectsInElementType();
+	return result;
+}
+
+string UserDefinedIOCase::basicSubobjectAtIndex (int subobjectIndex, const vector<SharedPtr<TopLevelObject> >& objects, int topLevelArraySize)
+{
+	int currentIndex	= 0;
+	int objectIndex		= 0;
+	for (; currentIndex < subobjectIndex; objectIndex++)
+		currentIndex += objects[objectIndex]->numBasicSubobjectsInElementType() * topLevelArraySize;
+	if (currentIndex > subobjectIndex)
+	{
+		objectIndex--;
+		currentIndex -= objects[objectIndex]->numBasicSubobjectsInElementType() * topLevelArraySize;
+	}
+
+	return objects[objectIndex]->basicSubobjectAtIndex(subobjectIndex - currentIndex, topLevelArraySize);
+}
+
+void UserDefinedIOCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	const bool			isPerPatchIO				= m_ioType == IO_TYPE_PER_PATCH || m_ioType == IO_TYPE_PER_PATCH_ARRAY;
+	const bool			isExplicitVertexArraySize	= m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN ||
+													  m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY;
+
+	const string		vertexAttrArrayInputSize	= m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_IMPLICIT					? ""
+													: m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN	? "gl_MaxPatchVertices"
+													: m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY			? de::toString(m_context.getContextInfo().getInt(GL_MAX_PATCH_VERTICES))
+													: DE_NULL;
+
+	const char* const	maybePatch					= isPerPatchIO ? "patch " : "";
+	const string		outMaybePatch				= string() + maybePatch + "out ";
+	const string		inMaybePatch				= string() + maybePatch + "in ";
+	const bool			useBlock					= m_ioType == IO_TYPE_PER_VERTEX_BLOCK;
+
+	string				tcsDeclarations;
+	string				tcsStatements;
+
+	string				tesDeclarations;
+	string				tesStatements;
+
+	{
+		m_structTypes.push_back(glu::StructType("S"));
+
+		const glu::VarType	highpFloat		(glu::TYPE_FLOAT, glu::PRECISION_HIGHP);
+		glu::StructType&	structType		= m_structTypes.back();
+		const glu::VarType	structVarType	(&structType);
+
+		structType.addMember("x", glu::VarType(glu::TYPE_INT, glu::PRECISION_HIGHP));
+		structType.addMember("y", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP));
+		structType.addMember("z", glu::VarType(highpFloat, 2));
+
+		if (useBlock)
+		{
+			vector<IOBlock::Member> blockMembers;
+			blockMembers.push_back(IOBlock::Member("blockS",	structVarType));
+			blockMembers.push_back(IOBlock::Member("blockFa",	glu::VarType(highpFloat, 3)));
+			blockMembers.push_back(IOBlock::Member("blockSa",	glu::VarType(structVarType, 2)));
+			blockMembers.push_back(IOBlock::Member("blockF",	highpFloat));
+
+			m_tcsOutputs.push_back	(SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "tcBlock", blockMembers)));
+			m_tesInputs.push_back	(SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "teBlock", blockMembers)));
+		}
+		else
+		{
+			const Variable var0("in_te_s", structVarType,	m_ioType != IO_TYPE_PER_PATCH);
+			const Variable var1("in_te_f", highpFloat,		m_ioType != IO_TYPE_PER_PATCH);
+
+			m_tcsOutputs.push_back	(SharedPtr<TopLevelObject>(new Variable(var0)));
+			m_tesInputs.push_back	(SharedPtr<TopLevelObject>(new Variable(var0)));
+			m_tcsOutputs.push_back	(SharedPtr<TopLevelObject>(new Variable(var1)));
+			m_tesInputs.push_back	(SharedPtr<TopLevelObject>(new Variable(var1)));
+		}
+
+		tcsDeclarations += "in " + Variable("in_tc_attr", highpFloat, true).declare(vertexAttrArrayInputSize);
+		tcsDeclarations += de::toString(glu::declare(structType)) + ";\n";
+		tcsStatements += "\t{\n"
+						 "\t\thighp float v = 1.3;\n";
+
+		for (int tcsOutputNdx = 0; tcsOutputNdx < (int)m_tcsOutputs.size(); tcsOutputNdx++)
+		{
+			const TopLevelObject& output = *m_tcsOutputs[tcsOutputNdx];
+
+			tcsDeclarations += outMaybePatch + output.declare(m_ioType == IO_TYPE_PER_PATCH_ARRAY	? de::toString(NUM_PER_PATCH_ARRAY_ELEMS)
+															  : isExplicitVertexArraySize			? de::toString(NUM_OUTPUT_VERTICES)
+															  : "");
+			if (!isPerPatchIO)
+				tcsStatements += "\t\tv += float(gl_InvocationID)*" + de::floatToString(0.4f*output.numBasicSubobjectsInElementType(), 1) + ";\n";
+
+			tcsStatements += "\n\t\t// Assign values to output " + output.name() + "\n" +
+							 output.glslTraverseBasicTypes(isPerPatchIO ? NUM_PER_PATCH_ARRAY_ELEMS : -1, 2, glslAssignBasicTypeObject);
+
+			if (!isPerPatchIO)
+				tcsStatements += "\t\tv += float(" + de::toString(NUM_OUTPUT_VERTICES) + "-gl_InvocationID-1)*" + de::floatToString(0.4f*output.numBasicSubobjectsInElementType(), 1) + ";\n";
+		}
+		tcsStatements += "\t}\n";
+
+		tesDeclarations += de::toString(glu::declare(structType)) + ";\n";
+		tesStatements += "\tbool allOk = true;\n"
+						 "\thighp uint firstFailedInputIndex = 0;\n"
+						 "\t{\n"
+						 "\t\thighp float v = 1.3;\n";
+		for (int tesInputNdx = 0; tesInputNdx < (int)m_tesInputs.size(); tesInputNdx++)
+		{
+			const TopLevelObject& input = *m_tesInputs[tesInputNdx];
+			tesDeclarations += inMaybePatch + input.declare(m_ioType == IO_TYPE_PER_PATCH_ARRAY	? de::toString(NUM_PER_PATCH_ARRAY_ELEMS)
+														  : isExplicitVertexArraySize			? de::toString(vertexAttrArrayInputSize)
+														  : "");
+			tesStatements += "\n\t\t// Check values in input " + input.name() + "\n" +
+							 input.glslTraverseBasicTypes(isPerPatchIO ? NUM_PER_PATCH_ARRAY_ELEMS : NUM_OUTPUT_VERTICES, 2, glslCheckBasicTypeObject);
+		}
+		tesStatements += "\t}\n";
+	}
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+		<< glu::VertexSource					("#version 310 es\n"
+												 "\n"
+												 "in highp float in_v_attr;\n"
+												 "out highp float in_tc_attr;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	in_tc_attr = in_v_attr;\n"
+												 "}\n")
+
+		<< glu::TessellationControlSource		("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 "layout (vertices = " + de::toString(NUM_OUTPUT_VERTICES) + ") out;\n"
+												 "\n"
+												 + tcsDeclarations +
+												 "\n"
+												 "patch out highp vec2 in_te_positionScale;\n"
+												 "patch out highp vec2 in_te_positionOffset;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 + tcsStatements +
+												 "\n"
+												 "	in_te_positionScale  = vec2(in_tc_attr[6], in_tc_attr[7]);\n"
+												 "	in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n"
+												 "\n"
+												 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
+												 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
+												 "\n"
+												 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
+												 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
+												 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
+												 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
+												 "}\n")
+
+		<< glu::TessellationEvaluationSource	("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 + getTessellationEvaluationInLayoutString(m_primitiveType) +
+												 "\n"
+												 + tesDeclarations +
+												 "\n"
+												 "patch in highp vec2 in_te_positionScale;\n"
+												 "patch in highp vec2 in_te_positionOffset;\n"
+												 "\n"
+												 "out highp vec4 in_f_color;\n"
+												 "// Will contain the index of the first incorrect input,\n"
+												 "// or the number of inputs if all are correct\n"
+												 "flat out highp uint out_te_firstFailedInputIndex;\n"
+												 "\n"
+												 "bool compare_int   (int   a, int   b) { return a == b; }\n"
+												 "bool compare_float (float a, float b) { return abs(a - b) < 0.01f; }\n"
+												 "bool compare_vec4  (vec4  a, vec4  b) { return all(lessThan(abs(a - b), vec4(0.01f))); }\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 + tesStatements +
+												 "\n"
+												 "	gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n"
+												 "	in_f_color = allOk ? vec4(0.0, 1.0, 0.0, 1.0)\n"
+												 "	                   : vec4(1.0, 0.0, 0.0, 1.0);\n"
+												 "	out_te_firstFailedInputIndex = firstFailedInputIndex;\n"
+												 "}\n")
+
+		<< glu::FragmentSource					("#version 310 es\n"
+												 "\n"
+												 "layout (location = 0) out mediump vec4 o_color;\n"
+												 "\n"
+												 "in highp vec4 in_f_color;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	o_color = in_f_color;\n"
+												 "}\n")
+
+		<< glu::TransformFeedbackVarying		("out_te_firstFailedInputIndex")
+		<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void UserDefinedIOCase::deinit (void)
+{
+	m_program.clear();
+}
+
+UserDefinedIOCase::IterateResult UserDefinedIOCase::iterate (void)
+{
+	typedef TransformFeedbackHandler<deUint32> TFHandler;
+
+	TestLog&				log						= m_testCtx.getLog();
+	const RenderContext&	renderCtx				= m_context.getRenderContext();
+	const RandomViewport	viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const glw::Functions&	gl						= renderCtx.getFunctions();
+	static const float		attributes[6+2+2]		= { /* inner */ 3.0f, 4.0f, /* outer */ 5.0f, 6.0f, 7.0f, 8.0f, /* pos. scale */ 1.2f, 1.3f, /* pos. offset */ -0.3f, -0.4f };
+	const deUint32			programGL				= m_program->getProgram();
+	const int				numVertices				= referenceVertexCount(m_primitiveType, SPACINGMODE_EQUAL, false, &attributes[0], &attributes[2]);
+	const TFHandler			tfHandler				(renderCtx, numVertices);
+
+	gl.useProgram(programGL);
+	setViewport(gl, viewport);
+	gl.patchParameteri(GL_PATCH_VERTICES, DE_LENGTH_OF_ARRAY(attributes));
+
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	{
+		const glu::VertexArrayBinding	bindings[]	= { glu::va::Float("in_v_attr", 1, DE_LENGTH_OF_ARRAY(attributes), 0, &attributes[0]) };
+		const TFHandler::Result			tfResult	= tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, false),
+																					   DE_LENGTH_OF_ARRAY(bindings), &bindings[0], DE_LENGTH_OF_ARRAY(attributes));
+
+		{
+			const tcu::Surface			pixels		= getPixels(renderCtx, viewport);
+			const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePath.c_str());
+			const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), pixels.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);
+
+			if (!success)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+				return STOP;
+			}
+		}
+
+		if ((int)tfResult.varying.size() != numVertices)
+		{
+			log << TestLog::Message << "Failure: transform feedback returned " << tfResult.varying.size() << " vertices; expected " << numVertices << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Wrong number of vertices");
+			return STOP;
+		}
+
+		{
+			const int topLevelArraySize		= (m_ioType == IO_TYPE_PER_PATCH		? 1
+											 : m_ioType == IO_TYPE_PER_PATCH_ARRAY	? NUM_PER_PATCH_ARRAY_ELEMS
+											 : NUM_OUTPUT_VERTICES);
+			const int numTEInputs			= numBasicSubobjectsInElementType(m_tesInputs) * topLevelArraySize;
+
+			for (int vertexNdx = 0; vertexNdx < (int)numVertices; vertexNdx++)
+			{
+				if (tfResult.varying[vertexNdx] > (deUint32)numTEInputs)
+				{
+					log << TestLog::Message << "Failure: out_te_firstFailedInputIndex has value " << tfResult.varying[vertexNdx]
+											<< ", should be in range [0, " << numTEInputs << "]" << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid transform feedback output");
+					return STOP;
+				}
+				else if (tfResult.varying[vertexNdx] != (deUint32)numTEInputs)
+				{
+					log << TestLog::Message << "Failure: in tessellation evaluation shader, check for input "
+											<< basicSubobjectAtIndex(tfResult.varying[vertexNdx], m_tesInputs, topLevelArraySize) << " failed" << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid input value in tessellation evaluation shader");
+					return STOP;
+				}
+			}
+		}
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Pass gl_Position between VS and TCS, or between TCS and TES.
+ *
+ * In TCS gl_Position is in the gl_out[] block and in TES in the gl_in[]
+ * block, and has no special semantics in those. Arbitrary vec4 data can
+ * thus be passed there.
+ *//*--------------------------------------------------------------------*/
+class GLPositionCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_VS_TO_TCS = 0,
+		CASETYPE_TCS_TO_TES,
+		CASETYPE_VS_TO_TCS_TO_TES,
+
+		CASETYPE_LAST
+	};
+
+	GLPositionCase (Context& context, const char* name, const char* description, CaseType caseType, const char* referenceImagePath)
+		: TestCase				(context, name, description)
+		, m_caseType			(caseType)
+		, m_referenceImagePath	(referenceImagePath)
+	{
+	}
+
+	void									init				(void);
+	void									deinit				(void);
+	IterateResult							iterate				(void);
+
+	static const char*						getCaseTypeName		(CaseType type);
+
+private:
+	static const int						RENDER_SIZE = 256;
+
+	const CaseType							m_caseType;
+	const string							m_referenceImagePath;
+
+	SharedPtr<const glu::ShaderProgram>		m_program;
+};
+
+const char* GLPositionCase::getCaseTypeName (CaseType type)
+{
+	switch (type)
+	{
+		case CASETYPE_VS_TO_TCS:			return "gl_position_vs_to_tcs";
+		case CASETYPE_TCS_TO_TES:			return "gl_position_tcs_to_tes";
+		case CASETYPE_VS_TO_TCS_TO_TES:		return "gl_position_vs_to_tcs_to_tes";
+		default:
+			DE_ASSERT(false); return DE_NULL;
+	}
+}
+
+void GLPositionCase::init (void)
+{
+	checkTessellationSupport(m_context);
+	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);
+
+	const bool		vsToTCS		= m_caseType == CASETYPE_VS_TO_TCS		|| m_caseType == CASETYPE_VS_TO_TCS_TO_TES;
+	const bool		tcsToTES	= m_caseType == CASETYPE_TCS_TO_TES		|| m_caseType == CASETYPE_VS_TO_TCS_TO_TES;
+
+	const string	tesIn0		= tcsToTES ? "gl_in[0].gl_Position" : "in_te_attr[0]";
+	const string	tesIn1		= tcsToTES ? "gl_in[1].gl_Position" : "in_te_attr[1]";
+	const string	tesIn2		= tcsToTES ? "gl_in[2].gl_Position" : "in_te_attr[2]";
+
+	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
+		<< glu::VertexSource					("#version 310 es\n"
+												 "\n"
+												 "in highp vec4 in_v_attr;\n"
+												 + string(!vsToTCS ? "out highp vec4 in_tc_attr;\n" : "") +
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	" + (vsToTCS ? "gl_Position" : "in_tc_attr") + " = in_v_attr;\n"
+												 "}\n")
+
+		<< glu::TessellationControlSource		("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 "layout (vertices = 3) out;\n"
+												 "\n"
+												 + string(!vsToTCS ? "in highp vec4 in_tc_attr[];\n" : "") +
+												 "\n"
+												 + (!tcsToTES ? "out highp vec4 in_te_attr[];\n" : "") +
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	" + (tcsToTES ? "gl_out[gl_InvocationID].gl_Position" : "in_te_attr[gl_InvocationID]") + " = "
+													  + (vsToTCS ? "gl_in[gl_InvocationID].gl_Position" : "in_tc_attr[gl_InvocationID]") + ";\n"
+												 "\n"
+												 "	gl_TessLevelInner[0] = 2.0;\n"
+												 "	gl_TessLevelInner[1] = 3.0;\n"
+												 "\n"
+												 "	gl_TessLevelOuter[0] = 4.0;\n"
+												 "	gl_TessLevelOuter[1] = 5.0;\n"
+												 "	gl_TessLevelOuter[2] = 6.0;\n"
+												 "	gl_TessLevelOuter[3] = 7.0;\n"
+												 "}\n")
+
+		<< glu::TessellationEvaluationSource	("#version 310 es\n"
+												 "#extension GL_EXT_tessellation_shader : require\n"
+												 "\n"
+												 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_TRIANGLES) +
+												 "\n"
+												 + (!tcsToTES ? "in highp vec4 in_te_attr[];\n" : "") +
+												 "\n"
+												 "out highp vec4 in_f_color;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	highp vec2 xy = gl_TessCoord.x * " + tesIn0 + ".xy\n"
+												 "	              + gl_TessCoord.y * " + tesIn1 + ".xy\n"
+												 "	              + gl_TessCoord.z * " + tesIn2 + ".xy;\n"
+												 "	gl_Position = vec4(xy, 0.0, 1.0);\n"
+												 "	in_f_color = vec4(" + tesIn0 + ".z + " + tesIn1 + ".w,\n"
+												 "	                  " + tesIn2 + ".z + " + tesIn0 + ".w,\n"
+												 "	                  " + tesIn1 + ".z + " + tesIn2 + ".w,\n"
+												 "	                  1.0);\n"
+												 "}\n")
+
+		<< glu::FragmentSource					("#version 310 es\n"
+												 "\n"
+												 "layout (location = 0) out mediump vec4 o_color;\n"
+												 "\n"
+												 "in highp vec4 in_f_color;\n"
+												 "\n"
+												 "void main (void)\n"
+												 "{\n"
+												 "	o_color = in_f_color;\n"
+												 "}\n")));
+
+	m_testCtx.getLog() << *m_program;
+	if (!m_program->isOk())
+		TCU_FAIL("Program compilation failed");
+}
+
+void GLPositionCase::deinit (void)
+{
+	m_program.clear();
+}
+
+GLPositionCase::IterateResult GLPositionCase::iterate (void)
+{
+	TestLog&				log						= m_testCtx.getLog();
+	const RenderContext&	renderCtx				= m_context.getRenderContext();
+	const RandomViewport	viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
+	const glw::Functions&	gl						= renderCtx.getFunctions();
+	const deUint32			programGL				= m_program->getProgram();
+
+	static const float attributes[3*4] =
+	{
+		-0.8f, -0.7f, 0.1f, 0.7f,
+		-0.5f,  0.4f, 0.2f, 0.5f,
+		 0.3f,  0.2f, 0.3f, 0.45f
+	};
+
+	gl.useProgram(programGL);
+	setViewport(gl, viewport);
+	gl.patchParameteri(GL_PATCH_VERTICES, 3);
+
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	log << TestLog::Message << "Note: input data for in_v_attr:\n" << arrayStr(attributes, 4) << TestLog::EndMessage;
+
+	{
+		const glu::VertexArrayBinding bindings[] = { glu::va::Float("in_v_attr", 4, 3, 0, &attributes[0]) };
+		glu::draw(renderCtx, programGL, DE_LENGTH_OF_ARRAY(bindings), &bindings[0], glu::pr::Patches(3));
+
+		{
+			const tcu::Surface			pixels		= getPixels(renderCtx, viewport);
+			const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePath.c_str());
+			const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), pixels.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);
+
+			if (!success)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+				return STOP;
+			}
+		}
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+} // anonymous
+
+TessellationTests::TessellationTests (Context& context)
+	: TestCaseGroup(context, "tessellation", "Tessellation Tests")
+{
+}
+
+TessellationTests::~TessellationTests (void)
+{
+}
+
+void TessellationTests::init (void)
+{
+	{
+		TestCaseGroup* const tessCoordGroup = new TestCaseGroup(m_context, "tesscoord", "Get tessellation coordinates with transform feedback and validate them");
+		addChild(tessCoordGroup);
+
+		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
+		{
+			const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
+
+			for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
+				tessCoordGroup->addChild(new TessCoordCase(m_context,
+														   (string() + getTessPrimitiveTypeShaderName(primitiveType) + "_" + getSpacingModeShaderName((SpacingMode)spacingI)).c_str(), "",
+														   primitiveType, (SpacingMode)spacingI));
+		}
+	}
+
+	{
+		TestCaseGroup* const windingGroup = new TestCaseGroup(m_context, "winding", "Test the cw and ccw input layout qualifiers");
+		addChild(windingGroup);
+
+		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
+		{
+			const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
+			if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
+				continue;
+
+			for (int windingI = 0; windingI < WINDING_LAST; windingI++)
+			{
+				const Winding winding = (Winding)windingI;
+				windingGroup->addChild(new WindingCase(m_context, (string() + getTessPrimitiveTypeShaderName(primitiveType) + "_" + getWindingShaderName(winding)).c_str(), "", primitiveType, winding));
+			}
+		}
+	}
+
+	{
+		TestCaseGroup* const shaderInputOutputGroup = new TestCaseGroup(m_context, "shader_input_output", "Test tessellation control and evaluation shader inputs and outputs");
+		addChild(shaderInputOutputGroup);
+
+		{
+			static const struct
+			{
+				int inPatchSize;
+				int outPatchSize;
+			} patchVertexCountCases[] =
+			{
+				{  5, 10 },
+				{ 10,  5 }
+			};
+
+			for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(patchVertexCountCases); caseNdx++)
+			{
+				const int inSize	= patchVertexCountCases[caseNdx].inPatchSize;
+				const int outSize	= patchVertexCountCases[caseNdx].outPatchSize;
+
+				const string caseName = "patch_vertices_" + de::toString(inSize) + "_in_" + de::toString(outSize) + "_out";
+
+				shaderInputOutputGroup->addChild(new PatchVertexCountCase(m_context, caseName.c_str(), "Test input and output patch vertex counts", inSize, outSize,
+																		  ("data/tessellation/" + caseName + "_ref.png").c_str()));
+			}
+		}
+
+		for (int caseTypeI = 0; caseTypeI < PerPatchDataCase::CASETYPE_LAST; caseTypeI++)
+		{
+			const PerPatchDataCase::CaseType	caseType	= (PerPatchDataCase::CaseType)caseTypeI;
+			const char* const					caseName	= PerPatchDataCase::getCaseTypeName(caseType);
+
+			shaderInputOutputGroup->addChild(new PerPatchDataCase(m_context, caseName, PerPatchDataCase::getCaseTypeDescription(caseType), caseType,
+																  PerPatchDataCase::caseTypeUsesRefImageFromFile(caseType) ? (string() + "data/tessellation/" + caseName + "_ref.png").c_str() : DE_NULL));
+		}
+
+		for (int caseTypeI = 0; caseTypeI < GLPositionCase::CASETYPE_LAST; caseTypeI++)
+		{
+			const GLPositionCase::CaseType	caseType	= (GLPositionCase::CaseType)caseTypeI;
+			const char* const				caseName	= GLPositionCase::getCaseTypeName(caseType);
+
+			shaderInputOutputGroup->addChild(new GLPositionCase(m_context, caseName, "", caseType, "data/tessellation/gl_position_ref.png"));
+		}
+
+		shaderInputOutputGroup->addChild(new BarrierCase(m_context, "barrier", "Basic barrier usage", "data/tessellation/barrier_ref.png"));
+	}
+
+	{
+		TestCaseGroup* const miscDrawGroup = new TestCaseGroup(m_context, "misc_draw", "Miscellaneous draw-result-verifying cases");
+		addChild(miscDrawGroup);
+
+		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
+		{
+			const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
+			if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
+				continue;
+
+			const char* const primTypeName = getTessPrimitiveTypeShaderName(primitiveType);
+
+			for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
+			{
+				const string caseName = string() + "fill_cover_" + primTypeName + "_" + getSpacingModeShaderName((SpacingMode)spacingI);
+
+				miscDrawGroup->addChild(new BasicTriangleFillCoverCase(m_context,
+																	   caseName.c_str(), "Check that there are no obvious gaps in the triangle-filled area of a tessellated shape",
+																	   primitiveType, (SpacingMode)spacingI,
+																	   ("data/tessellation/" + caseName + "_ref").c_str()));
+			}
+		}
+
+		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
+		{
+			const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
+			if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
+				continue;
+
+			const char* const primTypeName = getTessPrimitiveTypeShaderName(primitiveType);
+
+			for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
+			{
+				const string caseName = string() + "fill_overlap_" + primTypeName + "_" + getSpacingModeShaderName((SpacingMode)spacingI);
+
+				miscDrawGroup->addChild(new BasicTriangleFillNonOverlapCase(m_context,
+																			caseName.c_str(), "Check that there are no obvious triangle overlaps in the triangle-filled area of a tessellated shape",
+																			primitiveType, (SpacingMode)spacingI,
+																			("data/tessellation/" + caseName + "_ref").c_str()));
+			}
+		}
+
+		for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
+		{
+			const string caseName = string() + "isolines_" + getSpacingModeShaderName((SpacingMode)spacingI);
+
+			miscDrawGroup->addChild(new IsolinesRenderCase(m_context,
+														   caseName.c_str(), "Basic isolines render test",
+														   (SpacingMode)spacingI,
+														   ("data/tessellation/" + caseName + "_ref").c_str()));
+		}
+	}
+
+	{
+		TestCaseGroup* const commonEdgeGroup = new TestCaseGroup(m_context, "common_edge", "Draw multiple adjacent shapes and check that no cracks appear between them");
+		addChild(commonEdgeGroup);
+
+		for (int caseTypeI = 0; caseTypeI < CommonEdgeCase::CASETYPE_LAST; caseTypeI++)
+		{
+			for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
+			{
+				const CommonEdgeCase::CaseType	caseType		= (CommonEdgeCase::CaseType)caseTypeI;
+				const TessPrimitiveType			primitiveType	= (TessPrimitiveType)primitiveTypeI;
+				if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
+						continue;
+
+				for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
+				{
+					const SpacingMode	spacing		= (SpacingMode)spacingI;
+					const string		caseName	= (string() + getTessPrimitiveTypeShaderName(primitiveType)
+																+ "_" + getSpacingModeShaderName(spacing)
+																+ (caseType == CommonEdgeCase::CASETYPE_BASIC		? ""
+																 : caseType == CommonEdgeCase::CASETYPE_PRECISE		? "_precise"
+																 : DE_NULL));
+
+					commonEdgeGroup->addChild(new CommonEdgeCase(m_context, caseName.c_str(), "", primitiveType, spacing, caseType));
+				}
+			}
+		}
+	}
+
+	{
+		TestCaseGroup* const fractionalSpacingModeGroup = new TestCaseGroup(m_context, "fractional_spacing", "Test fractional spacing modes");
+		addChild(fractionalSpacingModeGroup);
+
+		fractionalSpacingModeGroup->addChild(new FractionalSpacingModeCase(m_context, "odd",	"", SPACINGMODE_FRACTIONAL_ODD));
+		fractionalSpacingModeGroup->addChild(new FractionalSpacingModeCase(m_context, "even",	"", SPACINGMODE_FRACTIONAL_EVEN));
+	}
+
+	{
+		TestCaseGroup* const primitiveDiscardGroup = new TestCaseGroup(m_context, "primitive_discard", "Test primitive discard with relevant outer tessellation level <= 0.0");
+		addChild(primitiveDiscardGroup);
+
+		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
+		{
+			for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
+			{
+				for (int windingI = 0; windingI < WINDING_LAST; windingI++)
+				{
+					for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
+					{
+						const TessPrimitiveType		primitiveType	= (TessPrimitiveType)primitiveTypeI;
+						const SpacingMode			spacing			= (SpacingMode)spacingI;
+						const Winding				winding			= (Winding)windingI;
+						const bool					usePointMode	= usePointModeI != 0;
+
+						primitiveDiscardGroup->addChild(new PrimitiveDiscardCase(m_context, (string() + getTessPrimitiveTypeShaderName(primitiveType)
+																									  + "_" + getSpacingModeShaderName(spacing)
+																									  + "_" + getWindingShaderName(winding)
+																									  + (usePointMode ? "_point_mode" : "")).c_str(), "",
+																				 primitiveType, spacing, winding, usePointMode));
+					}
+				}
+			}
+		}
+	}
+
+	{
+		TestCaseGroup* const invarianceGroup							= new TestCaseGroup(m_context, "invariance",						"Test tessellation invariance rules");
+
+		TestCaseGroup* const invariantPrimitiveSetGroup					= new TestCaseGroup(m_context, "primitive_set",						"Test invariance rule #1");
+		TestCaseGroup* const invariantOuterEdgeGroup					= new TestCaseGroup(m_context, "outer_edge_division",				"Test invariance rule #2");
+		TestCaseGroup* const symmetricOuterEdgeGroup					= new TestCaseGroup(m_context, "outer_edge_symmetry",				"Test invariance rule #3");
+		TestCaseGroup* const outerEdgeVertexSetIndexIndependenceGroup	= new TestCaseGroup(m_context, "outer_edge_index_independence",		"Test invariance rule #4");
+		TestCaseGroup* const invariantTriangleSetGroup					= new TestCaseGroup(m_context, "triangle_set",						"Test invariance rule #5");
+		TestCaseGroup* const invariantInnerTriangleSetGroup				= new TestCaseGroup(m_context, "inner_triangle_set",				"Test invariance rule #6");
+		TestCaseGroup* const invariantOuterTriangleSetGroup				= new TestCaseGroup(m_context, "outer_triangle_set",				"Test invariance rule #7");
+		TestCaseGroup* const tessCoordComponentRangeGroup				= new TestCaseGroup(m_context, "tess_coord_component_range",		"Test invariance rule #8, first part");
+		TestCaseGroup* const oneMinusTessCoordComponentGroup			= new TestCaseGroup(m_context, "one_minus_tess_coord_component",	"Test invariance rule #8, second part");
+
+		addChild(invarianceGroup);
+		invarianceGroup->addChild(invariantPrimitiveSetGroup);
+		invarianceGroup->addChild(invariantOuterEdgeGroup);
+		invarianceGroup->addChild(symmetricOuterEdgeGroup);
+		invarianceGroup->addChild(outerEdgeVertexSetIndexIndependenceGroup);
+		invarianceGroup->addChild(invariantTriangleSetGroup);
+		invarianceGroup->addChild(invariantInnerTriangleSetGroup);
+		invarianceGroup->addChild(invariantOuterTriangleSetGroup);
+		invarianceGroup->addChild(tessCoordComponentRangeGroup);
+		invarianceGroup->addChild(oneMinusTessCoordComponentGroup);
+
+		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
+		{
+			const TessPrimitiveType		primitiveType	= (TessPrimitiveType)primitiveTypeI;
+			const string				primName		= getTessPrimitiveTypeShaderName(primitiveType);
+			const bool					triOrQuad		= primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS;
+
+			for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
+			{
+				const SpacingMode	spacing			= (SpacingMode)spacingI;
+				const string		primSpacName	= primName + "_" + getSpacingModeShaderName(spacing);
+
+				if (triOrQuad)
+				{
+					invariantOuterEdgeGroup->addChild		(new InvariantOuterEdgeCase			(m_context, primSpacName.c_str(), "", primitiveType, spacing));
+					invariantTriangleSetGroup->addChild		(new InvariantTriangleSetCase		(m_context, primSpacName.c_str(), "", primitiveType, spacing));
+					invariantInnerTriangleSetGroup->addChild(new InvariantInnerTriangleSetCase	(m_context, primSpacName.c_str(), "", primitiveType, spacing));
+					invariantOuterTriangleSetGroup->addChild(new InvariantOuterTriangleSetCase	(m_context, primSpacName.c_str(), "", primitiveType, spacing));
+				}
+
+				for (int windingI = 0; windingI < WINDING_LAST; windingI++)
+				{
+					const Winding	winding				= (Winding)windingI;
+					const string	primSpacWindName	= primSpacName + "_" + getWindingShaderName(winding);
+
+					for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
+					{
+						const bool		usePointMode			= usePointModeI != 0;
+						const string	primSpacWindPointName	= primSpacWindName + (usePointMode ? "_point_mode" : "");
+
+						invariantPrimitiveSetGroup->addChild		(new InvariantPrimitiveSetCase			(m_context, primSpacWindPointName.c_str(), "", primitiveType, spacing, winding, usePointMode));
+						symmetricOuterEdgeGroup->addChild			(new SymmetricOuterEdgeCase				(m_context, primSpacWindPointName.c_str(), "", primitiveType, spacing, winding, usePointMode));
+						tessCoordComponentRangeGroup->addChild		(new TessCoordComponentRangeCase		(m_context, primSpacWindPointName.c_str(), "", primitiveType, spacing, winding, usePointMode));
+						oneMinusTessCoordComponentGroup->addChild	(new OneMinusTessCoordComponentCase		(m_context, primSpacWindPointName.c_str(), "", primitiveType, spacing, winding, usePointMode));
+
+						if (triOrQuad)
+							outerEdgeVertexSetIndexIndependenceGroup->addChild(new OuterEdgeVertexSetIndexIndependenceCase(m_context, primSpacWindPointName.c_str(), "",
+																														   primitiveType, spacing, winding, usePointMode));
+					}
+				}
+			}
+		}
+	}
+
+	{
+		TestCaseGroup* const userDefinedIOGroup = new TestCaseGroup(m_context, "user_defined_io", "Test non-built-in per-patch and per-vertex inputs and outputs");
+		addChild(userDefinedIOGroup);
+
+		for (int ioTypeI = 0; ioTypeI < UserDefinedIOCase::IO_TYPE_LAST; ioTypeI++)
+		{
+			const UserDefinedIOCase::IOType		ioType			= (UserDefinedIOCase::IOType)ioTypeI;
+			TestCaseGroup* const				ioTypeGroup		= new TestCaseGroup(m_context,
+																					ioType == UserDefinedIOCase::IO_TYPE_PER_PATCH			? "per_patch"
+																				  : ioType == UserDefinedIOCase::IO_TYPE_PER_PATCH_ARRAY	? "per_patch_array"
+																				  : ioType == UserDefinedIOCase::IO_TYPE_PER_VERTEX			? "per_vertex"
+																				  : ioType == UserDefinedIOCase::IO_TYPE_PER_VERTEX_BLOCK	? "per_vertex_block"
+																				  : DE_NULL,
+																					ioType == UserDefinedIOCase::IO_TYPE_PER_PATCH			? "Per-patch TCS outputs"
+																				  : ioType == UserDefinedIOCase::IO_TYPE_PER_PATCH_ARRAY	? "Per-patch array TCS outputs"
+																				  : ioType == UserDefinedIOCase::IO_TYPE_PER_VERTEX			? "Per-vertex TCS outputs"
+																				  : ioType == UserDefinedIOCase::IO_TYPE_PER_VERTEX_BLOCK	? "Per-vertex TCS outputs in IO block"
+																				  : DE_NULL);
+			userDefinedIOGroup->addChild(ioTypeGroup);
+
+			for (int vertexArraySizeI = 0; vertexArraySizeI < UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_LAST; vertexArraySizeI++)
+			{
+				const UserDefinedIOCase::VertexIOArraySize	vertexArraySize			= (UserDefinedIOCase::VertexIOArraySize)vertexArraySizeI;
+				TestCaseGroup* const						vertexArraySizeGroup	= new TestCaseGroup(m_context,
+																										vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_IMPLICIT
+																											? "vertex_io_array_size_implicit"
+																									  : vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN
+																											? "vertex_io_array_size_shader_builtin"
+																									  : vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY
+																											? "vertex_io_array_size_query"
+																									  : DE_NULL,
+																									    "");
+				ioTypeGroup->addChild(vertexArraySizeGroup);
+
+				for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
+				{
+					const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
+					vertexArraySizeGroup->addChild(new UserDefinedIOCase(m_context, getTessPrimitiveTypeShaderName(primitiveType), "", primitiveType, ioType, vertexArraySize,
+																		 (string() + "data/tessellation/user_defined_io_" + getTessPrimitiveTypeShaderName(primitiveType) + "_ref.png").c_str()));
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fTessellationTests.hpp b/modules/gles31/functional/es31fTessellationTests.hpp
new file mode 100644
index 0000000..b54a86f
--- /dev/null
+++ b/modules/gles31/functional/es31fTessellationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FTESSELLATIONTESTS_HPP
+#define _ES31FTESSELLATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tessellation Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class TessellationTests : public TestCaseGroup
+{
+public:
+						TessellationTests	(Context& context);
+						~TessellationTests	(void);
+
+	void				init				(void);
+
+private:
+						TessellationTests	(const TessellationTests& other);
+	TessellationTests&	operator=			(const TessellationTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FTESSELLATIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fTextureBufferTests.cpp b/modules/gles31/functional/es31fTextureBufferTests.cpp
new file mode 100644
index 0000000..133e0b8
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureBufferTests.cpp
@@ -0,0 +1,305 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture buffer tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fTextureBufferTests.hpp"
+
+#include "glsTextureBufferCase.hpp"
+
+#include "glwEnums.hpp"
+
+#include "deStringUtil.hpp"
+
+#include <string>
+
+using std::string;
+using namespace deqp::gls::TextureBufferCaseUtil;
+using deqp::gls::TextureBufferCase;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+string toTestName (RenderBits renderBits)
+{
+	struct
+	{
+		RenderBits	bit;
+		const char*	str;
+	} bitInfos[] =
+	{
+		{ RENDERBITS_AS_VERTEX_ARRAY,		"as_vertex_array"		},
+		{ RENDERBITS_AS_INDEX_ARRAY,		"as_index_array"		},
+		{ RENDERBITS_AS_VERTEX_TEXTURE,		"as_vertex_texture"		},
+		{ RENDERBITS_AS_FRAGMENT_TEXTURE,	"as_fragment_texture"	}
+	};
+
+	std::ostringstream	stream;
+	bool				first	= true;
+
+	DE_ASSERT(renderBits != 0);
+
+	for (int infoNdx = 0; infoNdx < DE_LENGTH_OF_ARRAY(bitInfos); infoNdx++)
+	{
+		if (renderBits & bitInfos[infoNdx].bit)
+		{
+			stream << (first ? "" : "_") << bitInfos[infoNdx].str;
+			first = false;
+		}
+	}
+
+	return stream.str();
+}
+
+string toTestName (ModifyBits modifyBits)
+{
+	struct
+	{
+		ModifyBits	bit;
+		const char*	str;
+	} bitInfos[] =
+	{
+		{ MODIFYBITS_BUFFERDATA,			"bufferdata"			},
+		{ MODIFYBITS_BUFFERSUBDATA,			"buffersubdata"			},
+		{ MODIFYBITS_MAPBUFFER_WRITE,		"mapbuffer_write"		},
+		{ MODIFYBITS_MAPBUFFER_READWRITE,	"mapbuffer_readwrite"	}
+	};
+
+	std::ostringstream	stream;
+	bool				first	= true;
+
+	DE_ASSERT(modifyBits != 0);
+
+	for (int infoNdx = 0; infoNdx < DE_LENGTH_OF_ARRAY(bitInfos); infoNdx++)
+	{
+		if (modifyBits & bitInfos[infoNdx].bit)
+		{
+			stream << (first ? "" : "_") << bitInfos[infoNdx].str;
+			first = false;
+		}
+	}
+
+	return stream.str();
+}
+
+RenderBits operator| (RenderBits a, RenderBits b)
+{
+	return (RenderBits)(deUint32(a) | deUint32(b));
+}
+
+} // anonymous
+
+TestCaseGroup* createTextureBufferTests (Context& context)
+{
+	TestCaseGroup* const root = new TestCaseGroup(context, "texture_buffer", "Texture buffer syncronization tests");
+
+	const size_t bufferSizes[] =
+	{
+		512,
+		513,
+		65536,
+		65537,
+		131071
+	};
+
+	const size_t rangeSizes[] =
+	{
+		512,
+		513,
+		65537,
+		98304,
+	};
+
+	const size_t offsets[] =
+	{
+		1,
+		7
+	};
+
+	const RenderBits renderTypeCombinations[] =
+	{
+		RENDERBITS_AS_VERTEX_ARRAY,
+									  RENDERBITS_AS_INDEX_ARRAY,
+		RENDERBITS_AS_VERTEX_ARRAY	| RENDERBITS_AS_INDEX_ARRAY,
+
+																  RENDERBITS_AS_VERTEX_TEXTURE,
+		RENDERBITS_AS_VERTEX_ARRAY	|							  RENDERBITS_AS_VERTEX_TEXTURE,
+									  RENDERBITS_AS_INDEX_ARRAY	| RENDERBITS_AS_VERTEX_TEXTURE,
+		RENDERBITS_AS_VERTEX_ARRAY	| RENDERBITS_AS_INDEX_ARRAY	| RENDERBITS_AS_VERTEX_TEXTURE,
+
+																								  RENDERBITS_AS_FRAGMENT_TEXTURE,
+		RENDERBITS_AS_VERTEX_ARRAY	|															  RENDERBITS_AS_FRAGMENT_TEXTURE,
+									  RENDERBITS_AS_INDEX_ARRAY |								  RENDERBITS_AS_FRAGMENT_TEXTURE,
+		RENDERBITS_AS_VERTEX_ARRAY	| RENDERBITS_AS_INDEX_ARRAY |								  RENDERBITS_AS_FRAGMENT_TEXTURE,
+																  RENDERBITS_AS_VERTEX_TEXTURE	| RENDERBITS_AS_FRAGMENT_TEXTURE,
+		RENDERBITS_AS_VERTEX_ARRAY	|							  RENDERBITS_AS_VERTEX_TEXTURE	| RENDERBITS_AS_FRAGMENT_TEXTURE,
+									  RENDERBITS_AS_INDEX_ARRAY	| RENDERBITS_AS_VERTEX_TEXTURE	| RENDERBITS_AS_FRAGMENT_TEXTURE,
+		RENDERBITS_AS_VERTEX_ARRAY	| RENDERBITS_AS_INDEX_ARRAY	| RENDERBITS_AS_VERTEX_TEXTURE	| RENDERBITS_AS_FRAGMENT_TEXTURE
+	};
+
+	const ModifyBits modifyTypes[] =
+	{
+		MODIFYBITS_BUFFERDATA,
+		MODIFYBITS_BUFFERSUBDATA,
+		MODIFYBITS_MAPBUFFER_WRITE,
+		MODIFYBITS_MAPBUFFER_READWRITE
+	};
+
+	// Rendering test
+	{
+		TestCaseGroup* const renderGroup = new TestCaseGroup(context, "render", "Setup texture buffer with glBufferData and render data in different ways");
+		root->addChild(renderGroup);
+
+		for (int renderTypeNdx = 0; renderTypeNdx < DE_LENGTH_OF_ARRAY(renderTypeCombinations); renderTypeNdx++)
+		{
+			const RenderBits		renderType		= renderTypeCombinations[renderTypeNdx];
+			TestCaseGroup* const	renderTypeGroup	= new TestCaseGroup(context, toTestName(renderType).c_str(), toTestName(renderType).c_str());
+
+			renderGroup->addChild(renderTypeGroup);
+
+			for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(bufferSizes); sizeNdx++)
+			{
+				const size_t size	= bufferSizes[sizeNdx];
+				const string name	("buffer_size_" + de::toString(size));
+
+				renderTypeGroup->addChild(new TextureBufferCase(context.getTestContext(), context.getRenderContext(), GL_RGBA8, size, 0, 0, RENDERBITS_NONE, MODIFYBITS_NONE, renderType, name.c_str(), name.c_str()));
+			}
+
+			for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(rangeSizes); sizeNdx++)
+			{
+				const size_t size		= rangeSizes[sizeNdx];
+				const string name		("range_size_" + de::toString(size));
+				const size_t bufferSize	= 131072;
+
+				renderTypeGroup->addChild(new TextureBufferCase(context.getTestContext(), context.getRenderContext(), GL_RGBA8, bufferSize, 0, size, RENDERBITS_NONE, MODIFYBITS_NONE, renderType, name.c_str(), name.c_str()));
+			}
+
+			for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(offsets); offsetNdx++)
+			{
+				const size_t offset		= offsets[offsetNdx];
+				const size_t bufferSize	= 131072;
+				const size_t size		= 65537;
+				const string name		("offset_" + de::toString(offset) + "_alignments");
+
+				renderTypeGroup->addChild(new TextureBufferCase(context.getTestContext(), context.getRenderContext(), GL_RGBA8, bufferSize, offset, size, RENDERBITS_NONE, MODIFYBITS_NONE, renderType, name.c_str(), name.c_str()));
+			}
+		}
+	}
+
+	// Modify tests
+	{
+		TestCaseGroup* const modifyGroup = new TestCaseGroup(context, "modify", "Modify texture buffer content in multiple ways");
+		root->addChild(modifyGroup);
+
+		for (int modifyNdx = 0; modifyNdx < DE_LENGTH_OF_ARRAY(modifyTypes); modifyNdx++)
+		{
+			const ModifyBits		modifyType		= modifyTypes[modifyNdx];
+			TestCaseGroup* const	modifyTypeGroup	= new TestCaseGroup(context, toTestName(modifyType).c_str(), toTestName(modifyType).c_str());
+
+			modifyGroup->addChild(modifyTypeGroup);
+
+			for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(bufferSizes); sizeNdx++)
+			{
+				const size_t	size	= bufferSizes[sizeNdx];
+				const string	name	("buffer_size_" + de::toString(size));
+
+				modifyTypeGroup->addChild(new TextureBufferCase(context.getTestContext(), context.getRenderContext(), GL_RGBA8, size, 0, 0, RENDERBITS_NONE, modifyType, RENDERBITS_AS_FRAGMENT_TEXTURE, name.c_str(), name.c_str()));
+			}
+
+			for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(rangeSizes); sizeNdx++)
+			{
+				const size_t size		= rangeSizes[sizeNdx];
+				const string name		("range_size_" + de::toString(size));
+				const size_t bufferSize	= 131072;
+
+				modifyTypeGroup->addChild(new TextureBufferCase(context.getTestContext(), context.getRenderContext(), GL_RGBA8, bufferSize, 0, size, RENDERBITS_NONE, modifyType, RENDERBITS_AS_FRAGMENT_TEXTURE, name.c_str(), name.c_str()));
+			}
+
+			for (int offsetNdx = 0; offsetNdx < DE_LENGTH_OF_ARRAY(offsets); offsetNdx++)
+			{
+				const size_t offset		= offsets[offsetNdx];
+				const size_t bufferSize	= 131072;
+				const size_t size		= 65537;
+				const string name		("offset_" + de::toString(offset) + "_alignments");
+
+				modifyTypeGroup->addChild(new TextureBufferCase(context.getTestContext(), context.getRenderContext(), GL_RGBA8, bufferSize, offset, size, RENDERBITS_NONE, modifyType, RENDERBITS_AS_FRAGMENT_TEXTURE, name.c_str(), name.c_str()));
+			}
+		}
+	}
+
+	// Modify-Render tests
+	{
+		TestCaseGroup* const modifyRenderGroup = new TestCaseGroup(context, "modify_render", "Modify texture buffer content in multiple ways and render in different ways");
+		root->addChild(modifyRenderGroup);
+
+		for (int modifyNdx = 0; modifyNdx < DE_LENGTH_OF_ARRAY(modifyTypes); modifyNdx++)
+		{
+			const ModifyBits		modifyType		= modifyTypes[modifyNdx];
+			TestCaseGroup* const	modifyTypeGroup	= new TestCaseGroup(context, toTestName(modifyType).c_str(), toTestName(modifyType).c_str());
+
+			modifyRenderGroup->addChild(modifyTypeGroup);
+
+			for (int renderTypeNdx = 0; renderTypeNdx < DE_LENGTH_OF_ARRAY(renderTypeCombinations); renderTypeNdx++)
+			{
+				const RenderBits	renderType	= renderTypeCombinations[renderTypeNdx];
+				const size_t		size		= 16*1024;
+				const string		name		(toTestName(renderType));
+
+				modifyTypeGroup->addChild(new TextureBufferCase(context.getTestContext(), context.getRenderContext(), GL_RGBA8, size, 0, 0, RENDERBITS_NONE, modifyType, renderType, name.c_str(), name.c_str()));
+			}
+		}
+	}
+
+	// Render-Modify tests
+	{
+		TestCaseGroup* const renderModifyGroup = new TestCaseGroup(context, "render_modify", "Render texture buffer and modify.");
+		root->addChild(renderModifyGroup);
+
+		for (int renderTypeNdx = 0; renderTypeNdx < DE_LENGTH_OF_ARRAY(renderTypeCombinations); renderTypeNdx++)
+		{
+			const RenderBits		renderType		= renderTypeCombinations[renderTypeNdx];
+			TestCaseGroup* const	renderTypeGroup	= new TestCaseGroup(context, toTestName(renderType).c_str(), toTestName(renderType).c_str());
+
+			renderModifyGroup->addChild(renderTypeGroup);
+
+			for (int modifyNdx = 0; modifyNdx < DE_LENGTH_OF_ARRAY(modifyTypes); modifyNdx++)
+			{
+				const ModifyBits	modifyType	= modifyTypes[modifyNdx];
+				const size_t		size		= 16*1024;
+				const string		name		(toTestName(modifyType));
+
+				renderTypeGroup->addChild(new TextureBufferCase(context.getTestContext(), context.getRenderContext(), GL_RGBA8, size, 0, 0, renderType, modifyType, RENDERBITS_AS_FRAGMENT_TEXTURE, name.c_str(), name.c_str()));
+			}
+		}
+	}
+
+	return root;
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fTextureBufferTests.hpp b/modules/gles31/functional/es31fTextureBufferTests.hpp
new file mode 100644
index 0000000..895a1d1
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureBufferTests.hpp
@@ -0,0 +1,42 @@
+#ifndef _ES31FTEXTUREBUFFERTESTS_HPP
+#define _ES31FTEXTUREBUFFERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture buffer tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+TestCaseGroup* createTextureBufferTests (Context& context);
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FTEXTUREBUFFERTESTS_HPP
diff --git a/modules/gles31/functional/es31fTextureFormatTests.cpp b/modules/gles31/functional/es31fTextureFormatTests.cpp
new file mode 100644
index 0000000..91adc29
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureFormatTests.cpp
@@ -0,0 +1,561 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fTextureFormatTests.hpp"
+#include "gluContextInfo.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using namespace deqp::gls;
+using namespace deqp::gls::TextureTestUtil;
+using tcu::Sampler;
+
+static tcu::CubeFace getCubeFaceFromNdx (int ndx)
+{
+	switch (ndx)
+	{
+		case 0:	return tcu::CUBEFACE_POSITIVE_X;
+		case 1:	return tcu::CUBEFACE_NEGATIVE_X;
+		case 2:	return tcu::CUBEFACE_POSITIVE_Y;
+		case 3:	return tcu::CUBEFACE_NEGATIVE_Y;
+		case 4:	return tcu::CUBEFACE_POSITIVE_Z;
+		case 5:	return tcu::CUBEFACE_NEGATIVE_Z;
+		default:
+			DE_ASSERT(false);
+			return tcu::CUBEFACE_LAST;
+	}
+}
+
+// TextureCubeArrayFormatCase
+
+class TextureCubeArrayFormatCase : public tcu::TestCase
+{
+public:
+										TextureCubeArrayFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, deUint32 format, deUint32 dataType, int size, int depth);
+										TextureCubeArrayFormatCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, deUint32 internalFormat, int size, int depth);
+										~TextureCubeArrayFormatCase	(void);
+
+	void								init						(void);
+	void								deinit						(void);
+	IterateResult						iterate						(void);
+
+private:
+										TextureCubeArrayFormatCase	(const TextureCubeArrayFormatCase& other);
+	TextureCubeArrayFormatCase&			operator=					(const TextureCubeArrayFormatCase& other);
+
+	bool								checkSupport				(void);
+	bool								testLayerFace				(int layerNdx);
+
+	glu::RenderContext&					m_renderCtx;
+	const glu::ContextInfo&				m_renderCtxInfo;
+
+	const deUint32						m_format;
+	const deUint32						m_dataType;
+	const int							m_size;
+	const int							m_depth;
+
+	glu::TextureCubeArray*				m_texture;
+	TextureTestUtil::TextureRenderer	m_renderer;
+
+	int									m_curLayerFace;
+};
+
+TextureCubeArrayFormatCase::TextureCubeArrayFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, deUint32 format, deUint32 dataType, int size, int depth)
+	: TestCase			(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_format			(format)
+	, m_dataType		(dataType)
+	, m_size			(size)
+	, m_depth			(depth)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_310_ES, glu::PRECISION_HIGHP)
+	, m_curLayerFace	(0)
+{
+}
+
+TextureCubeArrayFormatCase::TextureCubeArrayFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& renderCtxInfo, const char* name, const char* description, deUint32 internalFormat, int size, int depth)
+	: TestCase			(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_renderCtxInfo	(renderCtxInfo)
+	, m_format			(internalFormat)
+	, m_dataType		(GL_NONE)
+	, m_size			(size)
+	, m_depth			(depth)
+	, m_texture			(DE_NULL)
+	, m_renderer		(renderCtx, testCtx, glu::GLSL_VERSION_310_ES, glu::PRECISION_HIGHP)
+	, m_curLayerFace	(0)
+{
+}
+
+TextureCubeArrayFormatCase::~TextureCubeArrayFormatCase (void)
+{
+	deinit();
+}
+
+void TextureCubeArrayFormatCase::init (void)
+{
+	if (checkSupport())
+	{
+		m_texture = m_dataType != GL_NONE
+				  ? new glu::TextureCubeArray(m_renderCtx, m_format, m_dataType, m_size, m_depth)	// Implicit internal format.
+				  : new glu::TextureCubeArray(m_renderCtx, m_format, m_size, m_depth);				// Explicit internal format.
+
+		tcu::TextureFormatInfo spec = tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+		// Fill level 0.
+		m_texture->getRefTexture().allocLevel(0);
+		tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), spec.valueMin, spec.valueMax);
+
+		// Initialize state.
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		m_curLayerFace = 0;
+	}
+	else
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Cube map arrays not supported");
+	}
+}
+
+void TextureCubeArrayFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+bool TextureCubeArrayFormatCase::checkSupport (void)
+{
+	return m_renderCtxInfo.isExtensionSupported("GL_EXT_texture_cube_map_array");
+}
+
+bool TextureCubeArrayFormatCase::testLayerFace (int layerFaceNdx)
+{
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	TestLog&				log				= m_testCtx.getLog();
+	RandomViewport			viewport		(m_renderCtx.getRenderTarget(), m_size, m_size, deStringHash(getName()));
+	tcu::Surface			renderedFrame	(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame	(viewport.width, viewport.height);
+	tcu::RGBA				threshold		= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	ReferenceParams			renderParams	(TEXTURETYPE_CUBE_ARRAY);
+	tcu::TextureFormatInfo	spec			= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+	const int				layerNdx		= layerFaceNdx / 6;
+	const tcu::CubeFace		face			= getCubeFaceFromNdx(layerFaceNdx % 6);
+
+	renderParams.samplerType				= getSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.sampler					= Sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST, Sampler::NEAREST);
+	renderParams.sampler.seamlessCubeMap	= true;
+	renderParams.colorScale					= spec.lookupScale;
+	renderParams.colorBias					= spec.lookupBias;
+
+	// Layer here specifies the cube slice
+	computeQuadTexCoordCubeArray(texCoord, layerNdx, face, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, m_texture->getGLTexture());
+
+	// Setup nearest neighbor filtering and clamp-to-edge.
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	// Compute reference.
+	sampleTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], renderParams);
+
+	// Compare and log.
+	return compareImages(log, (string("LayerFace" + de::toString(layerFaceNdx))).c_str(), (string("Layer-face " + de::toString(layerFaceNdx))).c_str(), referenceFrame, renderedFrame, threshold);
+}
+
+TextureCubeArrayFormatCase::IterateResult TextureCubeArrayFormatCase::iterate (void)
+{
+	if (m_testCtx.getTestResult() == QP_TEST_RESULT_NOT_SUPPORTED)
+		return STOP;
+
+	// Execute test for all layers.
+	bool isOk = testLayerFace(m_curLayerFace);
+
+	if (!isOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+
+	m_curLayerFace += 1;
+
+	return m_curLayerFace < m_texture->getRefTexture().getNumLayers() ? CONTINUE : STOP;
+}
+
+// TextureBufferFormatCase
+
+class TextureBufferFormatCase : public tcu::TestCase
+{
+public:
+								TextureBufferFormatCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 internalFormat, int width);
+								~TextureBufferFormatCase	(void);
+
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+private:
+								TextureBufferFormatCase		(const TextureBufferFormatCase& other);
+	TextureBufferFormatCase&	operator=					(const TextureBufferFormatCase& other);
+
+	glu::RenderContext&			m_renderCtx;
+
+	deUint32					m_format;
+	int							m_width;
+
+	glu::TextureBuffer*			m_texture;
+	TextureRenderer				m_renderer;
+};
+
+TextureBufferFormatCase::TextureBufferFormatCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, deUint32 internalFormat, int width)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_format		(internalFormat)
+	, m_width		(width)
+	, m_texture		(DE_NULL)
+	, m_renderer	(renderCtx, testCtx, glu::GLSL_VERSION_310_ES, glu::PRECISION_HIGHP)
+{
+}
+
+TextureBufferFormatCase::~TextureBufferFormatCase (void)
+{
+	deinit();
+}
+
+void TextureBufferFormatCase::init (void)
+{
+	TestLog&				log		= m_testCtx.getLog();
+	tcu::TextureFormat		fmt		= glu::mapGLInternalFormat(m_format);
+	tcu::TextureFormatInfo	spec	= tcu::getTextureFormatInfo(fmt);
+	tcu::Vec4				colorA	(spec.valueMin.x(), spec.valueMax.y(), spec.valueMin.z(), spec.valueMax.w());
+	tcu::Vec4				colorB	(spec.valueMax.x(), spec.valueMin.y(), spec.valueMax.z(), spec.valueMin.w());
+	std::ostringstream		fmtName;
+
+	fmtName << glu::getPixelFormatStr(m_format);
+
+	log << TestLog::Message << "Buffer texture, " << fmtName.str() << ", " << m_width
+							<< ",\n  fill with " << formatGradient(&colorA, &colorB) << " gradient"
+		<< TestLog::EndMessage;
+
+	m_texture = new glu::TextureBuffer(m_renderCtx, m_format, m_width * fmt.getPixelSize());
+
+	// Fill level 0.
+	tcu::fillWithComponentGradients(m_texture->getRefTexture(), colorA, colorB);
+}
+
+void TextureBufferFormatCase::deinit (void)
+{
+	delete m_texture;
+	m_texture = DE_NULL;
+
+	m_renderer.clear();
+}
+
+TextureBufferFormatCase::IterateResult TextureBufferFormatCase::iterate (void)
+{
+	TestLog&				log					= m_testCtx.getLog();
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	RandomViewport			viewport			(m_renderCtx.getRenderTarget(), m_width, 1, deStringHash(getName()));
+	tcu::Surface			renderedFrame		(viewport.width, viewport.height);
+	tcu::Surface			referenceFrame		(viewport.width, viewport.height);
+	tcu::RGBA				threshold			= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
+	vector<float>			texCoord;
+	RenderParams			renderParams		(TEXTURETYPE_BUFFER);
+	tcu::TextureFormatInfo	spec				= tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());
+
+	renderParams.flags			|= RenderParams::LOG_ALL;
+	renderParams.samplerType	= getFetchSamplerType(m_texture->getRefTexture().getFormat());
+	renderParams.colorScale		= spec.lookupScale;
+	renderParams.colorBias		= spec.lookupBias;
+
+	computeQuadTexCoord1D(texCoord, 0.0f, (float)(m_texture->getRefTexture().getWidth()));
+
+	gl.clearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	// Setup base viewport.
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	// Upload texture data to GL.
+	m_texture->upload();
+
+	// Bind to unit 0.
+	gl.activeTexture(GL_TEXTURE0);
+	gl.bindTexture(GL_TEXTURE_BUFFER, m_texture->getGLTexture());
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");
+
+	// Draw.
+	m_renderer.renderQuad(0, &texCoord[0], renderParams);
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, renderedFrame.getAccess());
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
+
+	// Compute reference.
+	fetchTexture(SurfaceAccess(referenceFrame, m_renderCtx.getRenderTarget().getPixelFormat()), m_texture->getRefTexture(), &texCoord[0], spec.lookupScale, spec.lookupBias);
+
+	// Compare and log.
+	bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);
+
+	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							isOk ? "Pass"				: "Image comparison failed");
+
+	return STOP;
+}
+
+// TextureFormatTests
+
+TextureFormatTests::TextureFormatTests (Context& context)
+	: TestCaseGroup(context, "format", "Texture Format Tests")
+{
+}
+
+TextureFormatTests::~TextureFormatTests (void)
+{
+}
+
+vector<string> toStringVector (const char* const* str, int numStr)
+{
+	vector<string> v;
+	v.resize(numStr);
+	for (int i = 0; i < numStr; i++)
+		v[i] = str[i];
+	return v;
+}
+
+void TextureFormatTests::init (void)
+{
+	tcu::TestCaseGroup* unsizedGroup	= DE_NULL;
+	tcu::TestCaseGroup*	sizedGroup		= DE_NULL;
+	tcu::TestCaseGroup*	sizedBufferGroup = DE_NULL;
+	addChild((unsizedGroup		= new tcu::TestCaseGroup(m_testCtx,	"unsized",	"Unsized formats")));
+	addChild((sizedGroup		= new tcu::TestCaseGroup(m_testCtx,	"sized",	"Sized formats")));
+	addChild((sizedBufferGroup	= new tcu::TestCaseGroup(m_testCtx,	"buffer",	"Sized formats (Buffer)")));
+
+	tcu::TestCaseGroup*	sizedCubeArrayGroup	= DE_NULL;
+	sizedGroup->addChild((sizedCubeArrayGroup = new tcu::TestCaseGroup(m_testCtx, "cube_array", "Sized formats (2D Array)")));
+
+	struct
+	{
+		const char*	name;
+		deUint32	format;
+		deUint32	dataType;
+	} texFormats[] =
+	{
+		{ "alpha",							GL_ALPHA,			GL_UNSIGNED_BYTE },
+		{ "luminance",						GL_LUMINANCE,		GL_UNSIGNED_BYTE },
+		{ "luminance_alpha",				GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
+		{ "rgb_unsigned_short_5_6_5",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
+		{ "rgb_unsigned_byte",				GL_RGB,				GL_UNSIGNED_BYTE },
+		{ "rgba_unsigned_short_4_4_4_4",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
+		{ "rgba_unsigned_short_5_5_5_1",	GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
+		{ "rgba_unsigned_byte",				GL_RGBA,			GL_UNSIGNED_BYTE }
+	};
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
+	{
+		deUint32	format		= texFormats[formatNdx].format;
+		deUint32	dataType	= texFormats[formatNdx].dataType;
+		string	nameBase		= texFormats[formatNdx].name;
+		string	descriptionBase	= string(glu::getPixelFormatName(format)) + ", " + glu::getTypeName(dataType);
+
+		unsizedGroup->addChild(new TextureCubeArrayFormatCase (m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), (nameBase + "_cube_array_pot").c_str(),		(descriptionBase + ", GL_TEXTURE_CUBE_MAP_ARRAY").c_str(), format, dataType, 64, 12));
+		unsizedGroup->addChild(new TextureCubeArrayFormatCase (m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), (nameBase + "_cube_array_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_CUBE_MAP_ARRAY").c_str(), format, dataType, 64, 12));
+	}
+
+	struct
+	{
+		const char*	name;
+		deUint32	internalFormat;
+	} sizedColorFormats[] =
+	{
+		{ "rgba32f",			GL_RGBA32F,			},
+		{ "rgba32i",			GL_RGBA32I,			},
+		{ "rgba32ui",			GL_RGBA32UI,		},
+		{ "rgba16f",			GL_RGBA16F,			},
+		{ "rgba16i",			GL_RGBA16I,			},
+		{ "rgba16ui",			GL_RGBA16UI,		},
+		{ "rgba8",				GL_RGBA8,			},
+		{ "rgba8i",				GL_RGBA8I,			},
+		{ "rgba8ui",			GL_RGBA8UI,			},
+		{ "srgb8_alpha8",		GL_SRGB8_ALPHA8,	},
+		{ "rgb10_a2",			GL_RGB10_A2,		},
+		{ "rgb10_a2ui",			GL_RGB10_A2UI,		},
+		{ "rgba4",				GL_RGBA4,			},
+		{ "rgb5_a1",			GL_RGB5_A1,			},
+		{ "rgba8_snorm",		GL_RGBA8_SNORM,		},
+		{ "rgb8",				GL_RGB8,			},
+		{ "rgb565",				GL_RGB565,			},
+		{ "r11f_g11f_b10f",		GL_R11F_G11F_B10F,	},
+		{ "rgb32f",				GL_RGB32F,			},
+		{ "rgb32i",				GL_RGB32I,			},
+		{ "rgb32ui",			GL_RGB32UI,			},
+		{ "rgb16f",				GL_RGB16F,			},
+		{ "rgb16i",				GL_RGB16I,			},
+		{ "rgb16ui",			GL_RGB16UI,			},
+		{ "rgb8_snorm",			GL_RGB8_SNORM,		},
+		{ "rgb8i",				GL_RGB8I,			},
+		{ "rgb8ui",				GL_RGB8UI,			},
+		{ "srgb8",				GL_SRGB8,			},
+		{ "rgb9_e5",			GL_RGB9_E5,			},
+		{ "rg32f",				GL_RG32F,			},
+		{ "rg32i",				GL_RG32I,			},
+		{ "rg32ui",				GL_RG32UI,			},
+		{ "rg16f",				GL_RG16F,			},
+		{ "rg16i",				GL_RG16I,			},
+		{ "rg16ui",				GL_RG16UI,			},
+		{ "rg8",				GL_RG8,				},
+		{ "rg8i",				GL_RG8I,			},
+		{ "rg8ui",				GL_RG8UI,			},
+		{ "rg8_snorm",			GL_RG8_SNORM,		},
+		{ "r32f",				GL_R32F,			},
+		{ "r32i",				GL_R32I,			},
+		{ "r32ui",				GL_R32UI,			},
+		{ "r16f",				GL_R16F,			},
+		{ "r16i",				GL_R16I,			},
+		{ "r16ui",				GL_R16UI,			},
+		{ "r8",					GL_R8,				},
+		{ "r8i",				GL_R8I,				},
+		{ "r8ui",				GL_R8UI,			},
+		{ "r8_snorm",			GL_R8_SNORM,		}
+	};
+
+	struct
+	{
+		const char*	name;
+		deUint32	internalFormat;
+	} sizedDepthStencilFormats[] =
+	{
+		// Depth and stencil formats
+		{ "depth_component32f",	GL_DEPTH_COMPONENT32F	},
+		{ "depth_component24",	GL_DEPTH_COMPONENT24	},
+		{ "depth_component16",	GL_DEPTH_COMPONENT16	},
+		{ "depth32f_stencil8",	GL_DEPTH32F_STENCIL8	},
+		{ "depth24_stencil8",	GL_DEPTH24_STENCIL8		}
+	};
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(sizedColorFormats); formatNdx++)
+	{
+		deUint32	internalFormat	= sizedColorFormats[formatNdx].internalFormat;
+		string		nameBase		= sizedColorFormats[formatNdx].name;
+		string		descriptionBase	= glu::getPixelFormatName(internalFormat);
+
+		sizedCubeArrayGroup->addChild(new TextureCubeArrayFormatCase (m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), (nameBase + "_pot").c_str(),		(descriptionBase + ", GL_TEXTURE_CUBE_MAP_ARRAY").c_str(), internalFormat, 64, 12));
+		sizedCubeArrayGroup->addChild(new TextureCubeArrayFormatCase (m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), (nameBase + "_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_CUBE_MAP_ARRAY").c_str(), internalFormat, 64, 12));
+	}
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(sizedDepthStencilFormats); formatNdx++)
+	{
+		deUint32	internalFormat	= sizedDepthStencilFormats[formatNdx].internalFormat;
+		string		nameBase		= sizedDepthStencilFormats[formatNdx].name;
+		string		descriptionBase	= glu::getPixelFormatName(internalFormat);
+
+		sizedCubeArrayGroup->addChild(new TextureCubeArrayFormatCase (m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), (nameBase + "_pot").c_str(),		(descriptionBase + ", GL_TEXTURE_CUBE_MAP_ARRAY").c_str(), internalFormat, 64, 12));
+		sizedCubeArrayGroup->addChild(new TextureCubeArrayFormatCase (m_testCtx, m_context.getRenderContext(), m_context.getContextInfo(), (nameBase + "_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_CUBE_MAP_ARRAY").c_str(), internalFormat, 64, 12));
+	}
+
+	// \todo Check
+	struct
+	{
+		const char*	name;
+		deUint32	internalFormat;
+	} bufferColorFormats[] =
+	{
+		{ "r8",					GL_R8,				},
+		{ "r16f",				GL_R16F,			},
+		{ "r32f",				GL_R32F,			},
+		{ "r8i",				GL_R8I,				},
+		{ "r16i",				GL_R16I,			},
+		{ "r32i",				GL_R32I,			},
+		{ "r8ui",				GL_R8UI,			},
+		{ "r16ui",				GL_R16UI,			},
+		{ "r32ui",				GL_R32UI,			},
+		{ "rg8",				GL_RG8,				},
+		{ "rg16f",				GL_RG16F,			},
+		{ "rg32f",				GL_RG32F,			},
+		{ "rg8i",				GL_RG8I,			},
+		{ "rg16i",				GL_RG16I,			},
+		{ "rg32i",				GL_RG32I,			},
+		{ "rg8ui",				GL_RG8UI,			},
+		{ "rg16ui",				GL_RG16UI,			},
+		{ "rg32ui",				GL_RG32UI,			},
+		{ "rgba8",				GL_RGBA8,			},
+		{ "rgba16f",			GL_RGBA16F,			},
+		{ "rgba32f",			GL_RGBA32F,			},
+		{ "rgba8i",				GL_RGBA8I,			},
+		{ "rgba16i",			GL_RGBA16I,			},
+		{ "rgba32i",			GL_RGBA32I,			},
+		{ "rgba8ui",			GL_RGBA8UI,			},
+		{ "rgba16ui",			GL_RGBA16UI,		},
+		{ "rgba32ui",			GL_RGBA32UI,		}
+	};
+
+	for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(bufferColorFormats); formatNdx++)
+	{
+		deUint32	internalFormat	= bufferColorFormats[formatNdx].internalFormat;
+		string		nameBase		= bufferColorFormats[formatNdx].name;
+		string		descriptionBase	= glu::getPixelFormatName(internalFormat);
+
+		sizedBufferGroup->addChild	(new TextureBufferFormatCase	(m_testCtx, m_context.getRenderContext(),	(nameBase + "_pot").c_str(),	(descriptionBase + ", GL_TEXTURE_BUFFER").c_str(),	internalFormat, 64));
+		sizedBufferGroup->addChild	(new TextureBufferFormatCase	(m_testCtx, m_context.getRenderContext(),	(nameBase + "_npot").c_str(),	(descriptionBase + ", GL_TEXTURE_BUFFER").c_str(),	internalFormat, 112));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fTextureFormatTests.hpp b/modules/gles31/functional/es31fTextureFormatTests.hpp
new file mode 100644
index 0000000..f5243f0
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureFormatTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FTEXTUREFORMATTESTS_HPP
+#define _ES31FTEXTUREFORMATTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture format tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class TextureFormatTests : public TestCaseGroup
+{
+public:
+							TextureFormatTests		(Context& context);
+							~TextureFormatTests		(void);
+
+	void					init					(void);
+
+private:
+							TextureFormatTests		(const TextureFormatTests& other);
+	TextureFormatTests&		operator=				(const TextureFormatTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FTEXTUREFORMATTESTS_HPP
diff --git a/modules/gles31/functional/es31fTextureGatherTests.cpp b/modules/gles31/functional/es31fTextureGatherTests.cpp
new file mode 100644
index 0000000..710cadd
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureGatherTests.cpp
@@ -0,0 +1,2152 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLSL textureGather[Offset[s]] tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fTextureGatherTests.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluTexture.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "gluObjectWrapper.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTexLookupVerifier.hpp"
+#include "tcuTexCompareVerifier.hpp"
+#include "tcuCommandLine.hpp"
+#include "deUniquePtr.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+#include "deString.h"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+using glu::ShaderProgram;
+using tcu::ConstPixelBufferAccess;
+using tcu::PixelBufferAccess;
+using tcu::TestLog;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::UVec4;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using de::MovePtr;
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+
+using gls::TextureTestUtil::TextureType;
+using gls::TextureTestUtil::TEXTURETYPE_2D;
+using gls::TextureTestUtil::TEXTURETYPE_2D_ARRAY;
+using gls::TextureTestUtil::TEXTURETYPE_CUBE;
+
+namespace gles31
+{
+namespace Functional
+{
+
+namespace
+{
+
+// Round-to-zero int division, because pre-c++11 it's somewhat implementation-defined for negative values.
+static inline int divRoundToZero (int a, int b)
+{
+	return de::abs(a) / de::abs(b) * deSign32(a) * deSign32(b);
+}
+
+static void fillWithRandomColorTiles (const PixelBufferAccess& dst, const Vec4& minVal, const Vec4& maxVal, deUint32 seed)
+{
+	const int	numCols		= 7;
+	const int	numRows		= 5;
+	de::Random	rnd			(seed);
+
+	for (int slice = 0; slice < dst.getDepth(); slice++)
+	for (int row = 0; row < numRows; row++)
+	for (int col = 0; col < numCols; col++)
+	{
+		const int	yBegin	= (row+0)*dst.getHeight()/numRows;
+		const int	yEnd	= (row+1)*dst.getHeight()/numRows;
+		const int	xBegin	= (col+0)*dst.getWidth()/numCols;
+		const int	xEnd	= (col+1)*dst.getWidth()/numCols;
+		Vec4		color;
+		for (int i = 0; i < 4; i++)
+			color[i] = rnd.getFloat(minVal[i], maxVal[i]);
+		tcu::clear(tcu::getSubregion(dst, xBegin, yBegin, slice, xEnd-xBegin, yEnd-yBegin, 1), color);
+	}
+}
+
+static tcu::TextureLevel getPixels (const glu::RenderContext& renderCtx, const IVec2& size, const tcu::TextureFormat& colorBufferFormat)
+{
+	tcu::TextureLevel result(colorBufferFormat, size.x(), size.y());
+	glu::readPixels(renderCtx, 0, 0, result.getAccess());
+	return result;
+}
+
+static inline bool isDepthFormat (const tcu::TextureFormat& fmt)
+{
+	return fmt.order == tcu::TextureFormat::D || fmt.order == tcu::TextureFormat::DS;
+}
+
+static inline bool isUnormFormatType (tcu::TextureFormat::ChannelType type)
+{
+	return type == tcu::TextureFormat::UNORM_INT8	||
+		   type == tcu::TextureFormat::UNORM_INT16	||
+		   type == tcu::TextureFormat::UNORM_INT32;
+}
+
+static inline bool isSIntFormatType (tcu::TextureFormat::ChannelType type)
+{
+	return type == tcu::TextureFormat::SIGNED_INT8	||
+		   type == tcu::TextureFormat::SIGNED_INT16	||
+		   type == tcu::TextureFormat::SIGNED_INT32;
+}
+
+static inline bool isUIntFormatType (tcu::TextureFormat::ChannelType type)
+{
+	return type == tcu::TextureFormat::UNSIGNED_INT8	||
+		   type == tcu::TextureFormat::UNSIGNED_INT16	||
+		   type == tcu::TextureFormat::UNSIGNED_INT32;
+}
+
+enum TextureSwizzleComponent
+{
+	TEXTURESWIZZLECOMPONENT_R = 0,
+	TEXTURESWIZZLECOMPONENT_G,
+	TEXTURESWIZZLECOMPONENT_B,
+	TEXTURESWIZZLECOMPONENT_A,
+	TEXTURESWIZZLECOMPONENT_ZERO,
+	TEXTURESWIZZLECOMPONENT_ONE,
+
+	TEXTURESWIZZLECOMPONENT_LAST
+};
+
+static std::ostream& operator<< (std::ostream& stream, TextureSwizzleComponent comp)
+{
+	switch (comp)
+	{
+		case TEXTURESWIZZLECOMPONENT_R:		return stream << "RED";
+		case TEXTURESWIZZLECOMPONENT_G:		return stream << "GREEN";
+		case TEXTURESWIZZLECOMPONENT_B:		return stream << "BLUE";
+		case TEXTURESWIZZLECOMPONENT_A:		return stream << "ALPHA";
+		case TEXTURESWIZZLECOMPONENT_ZERO:	return stream << "ZERO";
+		case TEXTURESWIZZLECOMPONENT_ONE:	return stream << "ONE";
+		default: DE_ASSERT(false); return stream;
+	}
+}
+
+typedef tcu::Vector<TextureSwizzleComponent, 4> TextureSwizzle;
+
+static const TextureSwizzle s_identityTextureSwizzle(TEXTURESWIZZLECOMPONENT_R,
+													 TEXTURESWIZZLECOMPONENT_G,
+													 TEXTURESWIZZLECOMPONENT_B,
+													 TEXTURESWIZZLECOMPONENT_A);
+
+static deUint32 getGLTextureSwizzleComponent (TextureSwizzleComponent c)
+{
+	switch (c)
+	{
+		case TEXTURESWIZZLECOMPONENT_R:		return GL_RED;
+		case TEXTURESWIZZLECOMPONENT_G:		return GL_GREEN;
+		case TEXTURESWIZZLECOMPONENT_B:		return GL_BLUE;
+		case TEXTURESWIZZLECOMPONENT_A:		return GL_ALPHA;
+		case TEXTURESWIZZLECOMPONENT_ZERO:	return GL_ZERO;
+		case TEXTURESWIZZLECOMPONENT_ONE:	return GL_ONE;
+		default: DE_ASSERT(false); return (deUint32)-1;
+	}
+}
+
+template <typename T>
+static inline T swizzleColorChannel (const tcu::Vector<T, 4>& src, TextureSwizzleComponent swizzle)
+{
+	switch (swizzle)
+	{
+		case TEXTURESWIZZLECOMPONENT_R:		return src[0];
+		case TEXTURESWIZZLECOMPONENT_G:		return src[1];
+		case TEXTURESWIZZLECOMPONENT_B:		return src[2];
+		case TEXTURESWIZZLECOMPONENT_A:		return src[3];
+		case TEXTURESWIZZLECOMPONENT_ZERO:	return (T)0;
+		case TEXTURESWIZZLECOMPONENT_ONE:	return (T)1;
+		default: DE_ASSERT(false); return (T)-1;
+	}
+}
+
+template <typename T>
+static inline tcu::Vector<T, 4> swizzleColor (const tcu::Vector<T, 4>& src, const TextureSwizzle& swizzle)
+{
+	tcu::Vector<T, 4> result;
+	for (int i = 0; i < 4; i++)
+		result[i] = swizzleColorChannel(src, swizzle[i]);
+	return result;
+}
+
+template <typename T>
+static void swizzlePixels (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src, const TextureSwizzle& swizzle)
+{
+	DE_ASSERT(dst.getWidth()  == src.getWidth()  &&
+			  dst.getHeight() == src.getHeight() &&
+			  dst.getDepth()  == src.getDepth());
+	for (int z = 0; z < src.getDepth(); z++)
+	for (int y = 0; y < src.getHeight(); y++)
+	for (int x = 0; x < src.getWidth(); x++)
+		dst.setPixel(swizzleColor(src.getPixelT<T>(x, y, z), swizzle), x, y, z);
+}
+
+static void swizzlePixels (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src, const TextureSwizzle& swizzle)
+{
+	if (isDepthFormat(dst.getFormat()))
+	{
+		DE_ASSERT(swizzle == s_identityTextureSwizzle);
+		tcu::copy(dst, src);
+	}
+	else if (isUnormFormatType(dst.getFormat().type))
+		swizzlePixels<float>(dst, src, swizzle);
+	else if (isUIntFormatType(dst.getFormat().type))
+		swizzlePixels<deUint32>(dst, src, swizzle);
+	else if (isSIntFormatType(dst.getFormat().type))
+		swizzlePixels<deInt32>(dst, src, swizzle);
+	else
+		DE_ASSERT(false);
+}
+
+static void swizzleTexture (tcu::Texture2D& dst, const tcu::Texture2D& src, const TextureSwizzle& swizzle)
+{
+	dst = tcu::Texture2D(src.getFormat(), src.getWidth(), src.getHeight());
+	for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
+	{
+		if (src.isLevelEmpty(levelNdx))
+			continue;
+		dst.allocLevel(levelNdx);
+		swizzlePixels(dst.getLevel(levelNdx), src.getLevel(levelNdx), swizzle);
+	}
+}
+
+static void swizzleTexture (tcu::Texture2DArray& dst, const tcu::Texture2DArray& src, const TextureSwizzle& swizzle)
+{
+	dst = tcu::Texture2DArray(src.getFormat(), src.getWidth(), src.getHeight(), src.getNumLayers());
+	for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
+	{
+		if (src.isLevelEmpty(levelNdx))
+			continue;
+		dst.allocLevel(levelNdx);
+		swizzlePixels(dst.getLevel(levelNdx), src.getLevel(levelNdx), swizzle);
+	}
+}
+
+static void swizzleTexture (tcu::TextureCube& dst, const tcu::TextureCube& src, const TextureSwizzle& swizzle)
+{
+	dst = tcu::TextureCube(src.getFormat(), src.getSize());
+	for (int faceI = 0; faceI < tcu::CUBEFACE_LAST; faceI++)
+	{
+		const tcu::CubeFace face = (tcu::CubeFace)faceI;
+		for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
+		{
+			if (src.isLevelEmpty(face, levelNdx))
+				continue;
+			dst.allocLevel(face, levelNdx);
+			swizzlePixels(dst.getLevelFace(levelNdx, face), src.getLevelFace(levelNdx, face), swizzle);
+		}
+	}
+}
+
+static tcu::Texture2DView getOneLevelSubView (const tcu::Texture2DView& view, int level)
+{
+	return tcu::Texture2DView(1, view.getLevels() + level);
+}
+
+static tcu::Texture2DArrayView getOneLevelSubView (const tcu::Texture2DArrayView& view, int level)
+{
+	return tcu::Texture2DArrayView(1, view.getLevels() + level);
+}
+
+static tcu::TextureCubeView getOneLevelSubView (const tcu::TextureCubeView& view, int level)
+{
+	const tcu::ConstPixelBufferAccess* levels[tcu::CUBEFACE_LAST];
+
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		levels[face] = view.getFaceLevels((tcu::CubeFace)face) + level;
+
+	return tcu::TextureCubeView(1, levels);
+}
+
+class PixelOffsets
+{
+public:
+	virtual void operator() (const IVec2& pixCoord, IVec2 (&dst)[4]) const = 0;
+	virtual ~PixelOffsets (void) {}
+};
+
+class MultiplePixelOffsets : public PixelOffsets
+{
+public:
+	MultiplePixelOffsets (const IVec2& a,
+						  const IVec2& b,
+						  const IVec2& c,
+						  const IVec2& d)
+	{
+		m_offsets[0] = a;
+		m_offsets[1] = b;
+		m_offsets[2] = c;
+		m_offsets[3] = d;
+	}
+
+	void operator() (const IVec2& /* pixCoord */, IVec2 (&dst)[4]) const
+	{
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(dst); i++)
+			dst[i] = m_offsets[i];
+	}
+
+private:
+	IVec2 m_offsets[4];
+};
+
+class SinglePixelOffsets : public MultiplePixelOffsets
+{
+public:
+	SinglePixelOffsets (const IVec2& offset)
+		: MultiplePixelOffsets(offset + IVec2(0, 1),
+							   offset + IVec2(1, 1),
+							   offset + IVec2(1, 0),
+							   offset + IVec2(0, 0))
+	{
+	}
+};
+
+class DynamicSinglePixelOffsets : public PixelOffsets
+{
+public:
+	DynamicSinglePixelOffsets (const IVec2& offsetRange) : m_offsetRange(offsetRange) {}
+
+	void operator() (const IVec2& pixCoord, IVec2 (&dst)[4]) const
+	{
+		const int offsetRangeSize = m_offsetRange.y() - m_offsetRange.x() + 1;
+		SinglePixelOffsets(tcu::mod(pixCoord.swizzle(1,0), IVec2(offsetRangeSize)) + m_offsetRange.x())(IVec2(), dst);
+	}
+
+private:
+	IVec2 m_offsetRange;
+};
+
+template <typename T>
+static inline T triQuadInterpolate (const T (&values)[4], float xFactor, float yFactor)
+{
+	if (xFactor + yFactor < 1.0f)
+		return values[0] + (values[2]-values[0])*xFactor		+ (values[1]-values[0])*yFactor;
+	else
+		return values[3] + (values[1]-values[3])*(1.0f-xFactor)	+ (values[2]-values[3])*(1.0f-yFactor);
+}
+
+template <int N>
+static inline void computeTexCoordVecs (const vector<float>& texCoords, tcu::Vector<float, N> (&dst)[4])
+{
+	DE_ASSERT((int)texCoords.size() == 4*N);
+	for (int i = 0; i < 4; i++)
+	for (int j = 0; j < N; j++)
+		dst[i][j] = texCoords[i*N + j];
+}
+
+#if defined(DE_DEBUG)
+// Whether offsets correspond to the sample offsets used with plain textureGather().
+static inline bool isZeroOffsetOffsets (const IVec2 (&offsets)[4])
+{
+	IVec2 ref[4];
+	SinglePixelOffsets(IVec2(0))(IVec2(), ref);
+	return std::equal(DE_ARRAY_BEGIN(offsets),
+					  DE_ARRAY_END(offsets),
+					  DE_ARRAY_BEGIN(ref));
+}
+#endif
+
+template <typename ColorScalarType>
+static tcu::Vector<ColorScalarType, 4> gatherOffsets (const tcu::Texture2DView& texture, const tcu::Sampler& sampler, const Vec2& coord, int componentNdx, const IVec2 (&offsets)[4])
+{
+	return texture.gatherOffsets(sampler, coord.x(), coord.y(), componentNdx, offsets).cast<ColorScalarType>();
+}
+
+template <typename ColorScalarType>
+static tcu::Vector<ColorScalarType, 4> gatherOffsets (const tcu::Texture2DArrayView& texture, const tcu::Sampler& sampler, const Vec3& coord, int componentNdx, const IVec2 (&offsets)[4])
+{
+	return texture.gatherOffsets(sampler, coord.x(), coord.y(), coord.z(), componentNdx, offsets).cast<ColorScalarType>();
+}
+
+template <typename ColorScalarType>
+static tcu::Vector<ColorScalarType, 4> gatherOffsets (const tcu::TextureCubeView& texture, const tcu::Sampler& sampler, const Vec3& coord, int componentNdx, const IVec2 (&offsets)[4])
+{
+	DE_ASSERT(isZeroOffsetOffsets(offsets));
+	DE_UNREF(offsets);
+	return texture.gather(sampler, coord.x(), coord.y(), coord.z(), componentNdx).cast<ColorScalarType>();
+}
+
+static Vec4 gatherOffsetsCompare (const tcu::Texture2DView& texture, const tcu::Sampler& sampler, float refZ, const Vec2& coord, const IVec2 (&offsets)[4])
+{
+	return texture.gatherOffsetsCompare(sampler, refZ, coord.x(), coord.y(), offsets);
+}
+
+static Vec4 gatherOffsetsCompare (const tcu::Texture2DArrayView& texture, const tcu::Sampler& sampler, float refZ, const Vec3& coord, const IVec2 (&offsets)[4])
+{
+	return texture.gatherOffsetsCompare(sampler, refZ, coord.x(), coord.y(), coord.z(), offsets);
+}
+
+static Vec4 gatherOffsetsCompare (const tcu::TextureCubeView& texture, const tcu::Sampler& sampler, float refZ, const Vec3& coord, const IVec2 (&offsets)[4])
+{
+	DE_ASSERT(isZeroOffsetOffsets(offsets));
+	DE_UNREF(offsets);
+	return texture.gatherCompare(sampler, refZ, coord.x(), coord.y(), coord.z());
+}
+
+template <typename PrecType, typename ColorScalarT>
+static bool isGatherOffsetsResultValid (const tcu::TextureCubeView&				texture,
+										const tcu::Sampler&						sampler,
+										const PrecType&							prec,
+										const Vec3&								coord,
+										int										componentNdx,
+										const IVec2								(&offsets)[4],
+										const tcu::Vector<ColorScalarT, 4>&		result)
+{
+	DE_ASSERT(isZeroOffsetOffsets(offsets));
+	DE_UNREF(offsets);
+	return tcu::isGatherResultValid(texture, sampler, prec, coord, componentNdx, result);
+}
+
+static bool isGatherOffsetsCompareResultValid (const tcu::TextureCubeView&		texture,
+											   const tcu::Sampler&				sampler,
+											   const tcu::TexComparePrecision&	prec,
+											   const Vec3&						coord,
+											   const IVec2						(&offsets)[4],
+											   float							cmpReference,
+											   const Vec4&						result)
+{
+	DE_ASSERT(isZeroOffsetOffsets(offsets));
+	DE_UNREF(offsets);
+	return tcu::isGatherCompareResultValid(texture, sampler, prec, coord, cmpReference, result);
+}
+
+template <typename ColorScalarType, typename PrecType, typename TexViewT, typename TexCoordT>
+static bool verifyGatherOffsets (TestLog&						log,
+								 const ConstPixelBufferAccess&	result,
+								 const TexViewT&				texture,
+								 const TexCoordT				(&texCoords)[4],
+								 const tcu::Sampler&			sampler,
+								 const PrecType&				lookupPrec,
+								 int							componentNdx,
+								 const PixelOffsets&			getPixelOffsets)
+{
+	typedef tcu::Vector<ColorScalarType, 4> ColorVec;
+
+	const int					width			= result.getWidth();
+	const int					height			= result.getWidth();
+	tcu::TextureLevel			ideal			(result.getFormat(), width, height);
+	const PixelBufferAccess		idealAccess		= ideal.getAccess();
+	tcu::Surface				errorMask		(width, height);
+	bool						success			= true;
+
+	tcu::clear(errorMask.getAccess(), tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < height; py++)
+	for (int px = 0; px < width; px++)
+	{
+		IVec2		offsets[4];
+		getPixelOffsets(IVec2(px, py), offsets);
+
+		const Vec2			viewportCoord	= (Vec2((float)px, (float)py) + 0.5f) / Vec2((float)width, (float)height);
+		const TexCoordT		texCoord		= triQuadInterpolate(texCoords, viewportCoord.x(), viewportCoord.y());
+		const ColorVec		resultPix		= result.getPixelT<ColorScalarType>(px, py);
+		const ColorVec		idealPix		= gatherOffsets<ColorScalarType>(texture, sampler, texCoord, componentNdx, offsets);
+
+		idealAccess.setPixel(idealPix, px, py);
+
+		if (tcu::boolAny(tcu::logicalAnd(lookupPrec.colorMask,
+										 tcu::greaterThan(tcu::absDiff(resultPix, idealPix),
+														  lookupPrec.colorThreshold.template cast<ColorScalarType>()))))
+		{
+			if (!isGatherOffsetsResultValid(texture, sampler, lookupPrec, texCoord, componentNdx, offsets, resultPix))
+			{
+				errorMask.setPixel(px, py, tcu::RGBA::red);
+				success = false;
+			}
+		}
+	}
+
+	log << TestLog::ImageSet("VerifyResult", "Verification result")
+		<< TestLog::Image("Rendered", "Rendered image", result);
+
+	if (!success)
+	{
+		log << TestLog::Image("Reference", "Ideal reference image", ideal)
+			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+	}
+
+	log << TestLog::EndImageSet;
+
+	return success;
+}
+
+class PixelCompareRefZ
+{
+public:
+	virtual float operator() (const IVec2& pixCoord) const = 0;
+};
+
+class PixelCompareRefZDefault : public PixelCompareRefZ
+{
+public:
+	PixelCompareRefZDefault (const IVec2& renderSize) : m_renderSize(renderSize) {}
+
+	float operator() (const IVec2& pixCoord) const
+	{
+		return (float)(pixCoord.x() + 0.5f) / (float)m_renderSize.x();
+	}
+
+private:
+	IVec2 m_renderSize;
+};
+
+template <typename TexViewT, typename TexCoordT>
+static bool verifyGatherOffsetsCompare (TestLog&							log,
+										const ConstPixelBufferAccess&		result,
+										const TexViewT&						texture,
+										const TexCoordT						(&texCoords)[4],
+										const tcu::Sampler&					sampler,
+										const tcu::TexComparePrecision&		compPrec,
+										const PixelCompareRefZ&				getPixelRefZ,
+										const PixelOffsets&					getPixelOffsets)
+{
+	const int					width			= result.getWidth();
+	const int					height			= result.getWidth();
+	tcu::Surface				ideal			(width, height);
+	const PixelBufferAccess		idealAccess		= ideal.getAccess();
+	tcu::Surface				errorMask		(width, height);
+	bool						success			= true;
+
+	tcu::clear(errorMask.getAccess(), tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < height; py++)
+	for (int px = 0; px < width; px++)
+	{
+		IVec2		offsets[4];
+		getPixelOffsets(IVec2(px, py), offsets);
+
+		const Vec2			viewportCoord	= (Vec2((float)px, (float)py) + 0.5f) / Vec2((float)width, (float)height);
+		const TexCoordT		texCoord		= triQuadInterpolate(texCoords, viewportCoord.x(), viewportCoord.y());
+		const float			refZ			= getPixelRefZ(IVec2(px, py));
+		const Vec4			resultPix		= result.getPixel(px, py);
+		const Vec4			idealPix		= gatherOffsetsCompare(texture, sampler, refZ, texCoord, offsets);
+
+		idealAccess.setPixel(idealPix, px, py);
+
+		if (!tcu::boolAll(tcu::equal(resultPix, idealPix)))
+		{
+			if (!isGatherOffsetsCompareResultValid(texture, sampler, compPrec, texCoord, offsets, refZ, resultPix))
+			{
+				errorMask.setPixel(px, py, tcu::RGBA::red);
+				success = false;
+			}
+		}
+	}
+
+	log << TestLog::ImageSet("VerifyResult", "Verification result")
+		<< TestLog::Image("Rendered", "Rendered image", result);
+
+	if (!success)
+	{
+		log << TestLog::Image("Reference", "Ideal reference image", ideal)
+			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+	}
+
+	log << TestLog::EndImageSet;
+
+	return success;
+}
+
+static bool verifySingleColored (TestLog& log, const ConstPixelBufferAccess& result, const Vec4& refColor)
+{
+	const int					width			= result.getWidth();
+	const int					height			= result.getWidth();
+	tcu::Surface				ideal			(width, height);
+	const PixelBufferAccess		idealAccess		= ideal.getAccess();
+	tcu::Surface				errorMask		(width, height);
+	bool						success			= true;
+
+	tcu::clear(errorMask.getAccess(), tcu::RGBA::green.toVec());
+	tcu::clear(idealAccess, refColor);
+
+	for (int py = 0; py < height; py++)
+	for (int px = 0; px < width; px++)
+	{
+		if (result.getPixel(px, py) != refColor)
+		{
+			errorMask.setPixel(px, py, tcu::RGBA::red);
+			success = false;
+		}
+	}
+
+	log << TestLog::ImageSet("VerifyResult", "Verification result")
+		<< TestLog::Image("Rendered", "Rendered image", result);
+
+	if (!success)
+	{
+		log << TestLog::Image("Reference", "Ideal reference image", ideal)
+			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+	}
+
+	log << TestLog::EndImageSet;
+
+	return success;
+}
+
+enum GatherType
+{
+	GATHERTYPE_BASIC = 0,
+	GATHERTYPE_OFFSET,
+	GATHERTYPE_OFFSET_DYNAMIC,
+	GATHERTYPE_OFFSETS,
+
+	GATHERTYPE_LAST
+};
+
+static inline const char* gatherTypeName (GatherType type)
+{
+	switch (type)
+	{
+		case GATHERTYPE_BASIC:				return "basic";
+		case GATHERTYPE_OFFSET:				return "offset";
+		case GATHERTYPE_OFFSET_DYNAMIC:		return "offset_dynamic";
+		case GATHERTYPE_OFFSETS:			return "offsets";
+		default: DE_ASSERT(false); return DE_NULL;
+	}
+}
+
+static inline const char* gatherTypeDescription (GatherType type)
+{
+	switch (type)
+	{
+		case GATHERTYPE_BASIC:				return "textureGather";
+		case GATHERTYPE_OFFSET:				return "textureGatherOffset";
+		case GATHERTYPE_OFFSET_DYNAMIC:		return "textureGatherOffset with dynamic offsets";
+		case GATHERTYPE_OFFSETS:			return "textureGatherOffsets";
+		default: DE_ASSERT(false); return DE_NULL;
+	}
+}
+
+static inline bool requireGpuShader5 (GatherType gatherType)
+{
+	return gatherType == GATHERTYPE_OFFSET_DYNAMIC || gatherType == GATHERTYPE_OFFSETS;
+}
+
+struct GatherArgs
+{
+	int		componentNdx;	// If negative, implicit component index 0 is used (i.e. the parameter is not given).
+	IVec2	offsets[4];		// \note Unless GATHERTYPE_OFFSETS is used, only offsets[0] is relevant; also, for GATHERTYPE_OFFSET_DYNAMIC, none are relevant.
+
+	GatherArgs (void)
+		: componentNdx(-1)
+	{
+		std::fill(DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), IVec2());
+	}
+
+	GatherArgs (int comp,
+				const IVec2& off0 = IVec2(),
+				const IVec2& off1 = IVec2(),
+				const IVec2& off2 = IVec2(),
+				const IVec2& off3 = IVec2())
+		: componentNdx(comp)
+	{
+		offsets[0] = off0;
+		offsets[1] = off1;
+		offsets[2] = off2;
+		offsets[3] = off3;
+	}
+};
+
+static MovePtr<PixelOffsets> makePixelOffsetsFunctor (GatherType gatherType, const GatherArgs& gatherArgs, const IVec2& offsetRange)
+{
+	if (gatherType == GATHERTYPE_BASIC || gatherType == GATHERTYPE_OFFSET)
+	{
+		const IVec2 offset = gatherType == GATHERTYPE_BASIC ? IVec2(0) : gatherArgs.offsets[0];
+		return MovePtr<PixelOffsets>(new SinglePixelOffsets(offset));
+	}
+	else if (gatherType == GATHERTYPE_OFFSET_DYNAMIC)
+	{
+		return MovePtr<PixelOffsets>(new DynamicSinglePixelOffsets(offsetRange));
+	}
+	else if (gatherType == GATHERTYPE_OFFSETS)
+		return MovePtr<PixelOffsets>(new MultiplePixelOffsets(gatherArgs.offsets[0],
+															  gatherArgs.offsets[1],
+															  gatherArgs.offsets[2],
+															  gatherArgs.offsets[3]));
+	else
+	{
+		DE_ASSERT(false);
+		return MovePtr<PixelOffsets>(DE_NULL);
+	}
+}
+
+static inline glu::DataType getSamplerType (TextureType textureType, const tcu::TextureFormat& format)
+{
+	if (isDepthFormat(format))
+	{
+		switch (textureType)
+		{
+			case TEXTURETYPE_2D:		return glu::TYPE_SAMPLER_2D_SHADOW;
+			case TEXTURETYPE_2D_ARRAY:	return glu::TYPE_SAMPLER_2D_ARRAY_SHADOW;
+			case TEXTURETYPE_CUBE:		return glu::TYPE_SAMPLER_CUBE_SHADOW;
+			default: DE_ASSERT(false); return glu::TYPE_LAST;
+		}
+	}
+	else
+	{
+		switch (textureType)
+		{
+			case TEXTURETYPE_2D:		return glu::getSampler2DType(format);
+			case TEXTURETYPE_2D_ARRAY:	return glu::getSampler2DArrayType(format);
+			case TEXTURETYPE_CUBE:		return glu::getSamplerCubeType(format);
+			default: DE_ASSERT(false); return glu::TYPE_LAST;
+		}
+	}
+}
+
+static inline glu::DataType getSamplerGatherResultType (glu::DataType samplerType)
+{
+	switch (samplerType)
+	{
+		case glu::TYPE_SAMPLER_2D_SHADOW:
+		case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
+		case glu::TYPE_SAMPLER_CUBE_SHADOW:
+		case glu::TYPE_SAMPLER_2D:
+		case glu::TYPE_SAMPLER_2D_ARRAY:
+		case glu::TYPE_SAMPLER_CUBE:
+			return glu::TYPE_FLOAT_VEC4;
+
+		case glu::TYPE_INT_SAMPLER_2D:
+		case glu::TYPE_INT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_INT_SAMPLER_CUBE:
+			return glu::TYPE_INT_VEC4;
+
+		case glu::TYPE_UINT_SAMPLER_2D:
+		case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
+		case glu::TYPE_UINT_SAMPLER_CUBE:
+			return glu::TYPE_UINT_VEC4;
+
+		default:
+			DE_ASSERT(false);
+			return glu::TYPE_LAST;
+	}
+}
+
+static inline int getNumTextureSamplingDimensions (TextureType type)
+{
+	switch (type)
+	{
+		case TEXTURETYPE_2D:		return 2;
+		case TEXTURETYPE_2D_ARRAY:	return 3;
+		case TEXTURETYPE_CUBE:		return 3;
+		default: DE_ASSERT(false); return -1;
+	}
+}
+
+static deUint32 getGLTextureType (TextureType type)
+{
+	switch (type)
+	{
+		case TEXTURETYPE_2D:		return GL_TEXTURE_2D;
+		case TEXTURETYPE_2D_ARRAY:	return GL_TEXTURE_2D_ARRAY;
+		case TEXTURETYPE_CUBE:		return GL_TEXTURE_CUBE_MAP;
+		default: DE_ASSERT(false); return (deUint32)-1;
+	}
+}
+
+enum OffsetSize
+{
+	OFFSETSIZE_NONE = 0,
+	OFFSETSIZE_MINIMUM_REQUIRED,
+	OFFSETSIZE_IMPLEMENTATION_MAXIMUM,
+
+	OFFSETSIZE_LAST
+};
+
+static inline bool isMipmapFilter (tcu::Sampler::FilterMode filter)
+{
+	switch (filter)
+	{
+		case tcu::Sampler::NEAREST:
+		case tcu::Sampler::LINEAR:
+			return false;
+
+		case tcu::Sampler::NEAREST_MIPMAP_NEAREST:
+		case tcu::Sampler::NEAREST_MIPMAP_LINEAR:
+		case tcu::Sampler::LINEAR_MIPMAP_NEAREST:
+		case tcu::Sampler::LINEAR_MIPMAP_LINEAR:
+			return true;
+
+		default:
+			DE_ASSERT(false);
+			return false;
+	}
+}
+
+class TextureGatherCase : public TestCase
+{
+public:
+										TextureGatherCase		(Context&					context,
+																 const char*				name,
+																 const char*				description,
+																 TextureType				textureType,
+																 GatherType					gatherType,
+																 OffsetSize					offsetSize,
+																 tcu::TextureFormat			textureFormat,
+																 tcu::Sampler::CompareMode	shadowCompareMode, //!< Should be COMPAREMODE_NONE iff textureFormat is a depth format.
+																 tcu::Sampler::WrapMode		wrapS,
+																 tcu::Sampler::WrapMode		wrapT,
+																 const TextureSwizzle&		texSwizzle,
+																 // \note Filter modes have no effect on gather (except when it comes to
+																 //		  texture completeness); these are supposed to test just that.
+																 tcu::Sampler::FilterMode	minFilter,
+																 tcu::Sampler::FilterMode	magFilter,
+																 int						baseLevel,
+																 bool						mipmapIncomplete);
+
+	void								init					(void);
+	void								deinit					(void);
+	IterateResult						iterate					(void);
+
+protected:
+	IVec2								getOffsetRange			(void) const;
+
+	template <typename TexViewT, typename TexCoordT>
+	bool								verify					(const ConstPixelBufferAccess&	rendered,
+																 const TexViewT&				texture,
+																 const TexCoordT				(&bottomLeft)[4],
+																 const GatherArgs&				gatherArgs) const;
+
+	virtual void						generateIterations		(void) = 0;
+	virtual void						createAndUploadTexture	(void) = 0;
+	virtual int							getNumIterations		(void) const = 0;
+	virtual GatherArgs					getGatherArgs			(int iterationNdx) const = 0;
+	virtual vector<float>				computeQuadTexCoord		(int iterationNdx) const = 0;
+	virtual bool						verify					(int iterationNdx, const ConstPixelBufferAccess& rendered) const = 0;
+
+	const GatherType					m_gatherType;
+	const OffsetSize					m_offsetSize;
+	const tcu::TextureFormat			m_textureFormat;
+	const tcu::Sampler::CompareMode		m_shadowCompareMode;
+	const tcu::Sampler::WrapMode		m_wrapS;
+	const tcu::Sampler::WrapMode		m_wrapT;
+	const TextureSwizzle				m_textureSwizzle;
+	const tcu::Sampler::FilterMode		m_minFilter;
+	const tcu::Sampler::FilterMode		m_magFilter;
+	const int							m_baseLevel;
+	const bool							m_mipmapIncomplete;
+
+private:
+	enum
+	{
+		SPEC_MAX_MIN_OFFSET = -8,
+		SPEC_MIN_MAX_OFFSET = 7
+	};
+
+	static const IVec2					RENDER_SIZE;
+
+	static glu::VertexSource			genVertexShaderSource		(bool requireGpuShader5, int numTexCoordComponents, bool useNormalizedCoordInput);
+	static glu::FragmentSource			genFragmentShaderSource		(bool requireGpuShader5, int numTexCoordComponents, glu::DataType samplerType, const string& funcCall, bool useNormalizedCoordInput, bool usePixCoord);
+	static string						genGatherFuncCall			(GatherType, const tcu::TextureFormat&, const GatherArgs&, const string& refZExpr, const IVec2& offsetRange, int indentationDepth);
+	static glu::ProgramSources			genProgramSources			(GatherType, TextureType, const tcu::TextureFormat&, const GatherArgs&, const string& refZExpr, const IVec2& offsetRange);
+
+	const TextureType					m_textureType;
+
+	const tcu::TextureFormat			m_colorBufferFormat;
+	MovePtr<glu::Renderbuffer>			m_colorBuffer;
+	MovePtr<glu::Framebuffer>			m_fbo;
+
+	int									m_currentIteration;
+	MovePtr<ShaderProgram>				m_program;
+};
+
+const IVec2 TextureGatherCase::RENDER_SIZE = IVec2(64, 64);
+
+TextureGatherCase::TextureGatherCase (Context&						context,
+									  const char*					name,
+									  const char*					description,
+									  TextureType					textureType,
+									  GatherType					gatherType,
+									  OffsetSize					offsetSize,
+									  tcu::TextureFormat			textureFormat,
+									  tcu::Sampler::CompareMode		shadowCompareMode, //!< Should be COMPAREMODE_NONE iff textureType == TEXTURETYPE_NORMAL.
+									  tcu::Sampler::WrapMode		wrapS,
+									  tcu::Sampler::WrapMode		wrapT,
+									  const TextureSwizzle&			textureSwizzle,
+									  tcu::Sampler::FilterMode		minFilter,
+									  tcu::Sampler::FilterMode		magFilter,
+									  int							baseLevel,
+									  bool							mipmapIncomplete)
+	: TestCase				(context, name, description)
+	, m_gatherType			(gatherType)
+	, m_offsetSize			(offsetSize)
+	, m_textureFormat		(textureFormat)
+	, m_shadowCompareMode	(shadowCompareMode)
+	, m_wrapS				(wrapS)
+	, m_wrapT				(wrapT)
+	, m_textureSwizzle		(textureSwizzle)
+	, m_minFilter			(minFilter)
+	, m_magFilter			(magFilter)
+	, m_baseLevel			(baseLevel)
+	, m_mipmapIncomplete	(mipmapIncomplete)
+	, m_textureType			(textureType)
+	, m_colorBufferFormat	(tcu::TextureFormat(tcu::TextureFormat::RGBA,
+												isDepthFormat(textureFormat) ? tcu::TextureFormat::UNORM_INT8 : textureFormat.type))
+	, m_currentIteration	(0)
+{
+	DE_ASSERT((m_gatherType == GATHERTYPE_BASIC) == (m_offsetSize == OFFSETSIZE_NONE));
+	DE_ASSERT((m_shadowCompareMode != tcu::Sampler::COMPAREMODE_NONE) == isDepthFormat(m_textureFormat));
+	DE_ASSERT(isUnormFormatType(m_colorBufferFormat.type)						||
+			  m_colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT8		||
+			  m_colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT16	||
+			  m_colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT8		||
+			  m_colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT16);
+	DE_ASSERT(glu::isGLInternalColorFormatFilterable(glu::getInternalFormat(m_colorBufferFormat)) ||
+			  (m_magFilter == tcu::Sampler::NEAREST && (m_minFilter == tcu::Sampler::NEAREST || m_minFilter == tcu::Sampler::NEAREST_MIPMAP_NEAREST)));
+	DE_ASSERT(isMipmapFilter(m_minFilter) || !m_mipmapIncomplete);
+	DE_ASSERT(!(m_mipmapIncomplete && isDepthFormat(m_textureFormat))); // It's not clear what shadow textures should return when incomplete.
+}
+
+IVec2 TextureGatherCase::getOffsetRange (void) const
+{
+	switch (m_offsetSize)
+	{
+		case OFFSETSIZE_NONE:
+			return IVec2(0);
+			break;
+
+		case OFFSETSIZE_MINIMUM_REQUIRED:
+			// \note Defined by spec.
+			return IVec2(SPEC_MAX_MIN_OFFSET,
+						 SPEC_MIN_MAX_OFFSET);
+			break;
+
+		case OFFSETSIZE_IMPLEMENTATION_MAXIMUM:
+			return IVec2(m_context.getContextInfo().getInt(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET),
+						 m_context.getContextInfo().getInt(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET));
+			break;
+
+		default:
+			DE_ASSERT(false);
+			return IVec2(-1);
+	}
+}
+
+glu::VertexSource TextureGatherCase::genVertexShaderSource (bool requireGpuShader5, int numTexCoordComponents, bool useNormalizedCoordInput)
+{
+	DE_ASSERT(numTexCoordComponents == 2 || numTexCoordComponents == 3);
+	const string texCoordType = "vec" + de::toString(numTexCoordComponents);
+	return glu::VertexSource("#version 310 es\n"
+							 + string(requireGpuShader5 ? "#extension GL_EXT_gpu_shader5 : require\n" : "") +
+							 "\n"
+							 "in highp vec2 a_position;\n"
+							 "in highp " + texCoordType + " a_texCoord;\n"
+							 + (useNormalizedCoordInput ? "in highp vec2 a_normalizedCoord; // (0,0) to (1,1)\n" : "") +
+							 "\n"
+							 "out highp " + texCoordType + " v_texCoord;\n"
+							 + (useNormalizedCoordInput ? "out highp vec2 v_normalizedCoord;\n" : "") +
+							 "\n"
+							 "void main (void)\n"
+							 "{\n"
+							 "	gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);\n"
+							 "	v_texCoord = a_texCoord;\n"
+							 + (useNormalizedCoordInput ? "\tv_normalizedCoord = a_normalizedCoord;\n" : "") +
+							 "}\n");
+}
+
+glu::FragmentSource TextureGatherCase::genFragmentShaderSource (bool			requireGpuShader5,
+																int				numTexCoordComponents,
+																glu::DataType	samplerType,
+																const string&	funcCall,
+																bool			useNormalizedCoordInput,
+																bool			usePixCoord)
+{
+	DE_ASSERT(glu::isDataTypeSampler(samplerType));
+	DE_ASSERT(de::inRange(numTexCoordComponents, 2, 3));
+	DE_ASSERT(!usePixCoord || useNormalizedCoordInput);
+
+	const string texCoordType = "vec" + de::toString(numTexCoordComponents);
+
+	return glu::FragmentSource("#version 310 es\n"
+							   + string(requireGpuShader5 ? "#extension GL_EXT_gpu_shader5 : require\n" : "") +
+							   "\n"
+							   "layout (location = 0) out mediump " + glu::getDataTypeName(getSamplerGatherResultType(samplerType)) + " o_color;\n"
+							   "\n"
+							   "in highp " + texCoordType + " v_texCoord;\n"
+							   + (useNormalizedCoordInput ? "in highp vec2 v_normalizedCoord;\n" : "") +
+							   "\n"
+							   "uniform highp " + string(glu::getDataTypeName(samplerType)) + " u_sampler;\n"
+							   + (useNormalizedCoordInput ? "uniform highp vec2 u_viewportSize;\n" : "") +
+							   "\n"
+							   "void main(void)\n"
+							   "{\n"
+							   + (usePixCoord ? "\tivec2 pixCoord = ivec2(v_normalizedCoord*u_viewportSize);\n" : "") +
+							   "	o_color = " + funcCall + ";\n"
+							   "}\n");
+}
+
+string TextureGatherCase::genGatherFuncCall (GatherType gatherType, const tcu::TextureFormat& textureFormat, const GatherArgs& gatherArgs, const string& refZExpr, const IVec2& offsetRange, int indentationDepth)
+{
+	string result;
+
+	switch (gatherType)
+	{
+		case GATHERTYPE_BASIC:
+			result += "textureGather";
+			break;
+		case GATHERTYPE_OFFSET: // \note Fallthrough.
+		case GATHERTYPE_OFFSET_DYNAMIC:
+			result += "textureGatherOffset";
+			break;
+		case GATHERTYPE_OFFSETS:
+			result += "textureGatherOffsets";
+			break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	result += "(u_sampler, v_texCoord";
+
+	if (isDepthFormat(textureFormat))
+	{
+		DE_ASSERT(gatherArgs.componentNdx < 0);
+		result += ", " + refZExpr;
+	}
+
+	if (gatherType == GATHERTYPE_OFFSET ||
+		gatherType == GATHERTYPE_OFFSET_DYNAMIC ||
+		gatherType == GATHERTYPE_OFFSETS)
+	{
+		result += ", ";
+		switch (gatherType)
+		{
+			case GATHERTYPE_OFFSET:
+				result += "ivec2" + de::toString(gatherArgs.offsets[0]);
+				break;
+
+			case GATHERTYPE_OFFSET_DYNAMIC:
+				result += "pixCoord.yx % ivec2(" + de::toString(offsetRange.y() - offsetRange.x() + 1) + ") + " + de::toString(offsetRange.x());
+				break;
+
+			case GATHERTYPE_OFFSETS:
+				result += "ivec2[4](\n"
+						  + string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[0]) + ",\n"
+						  + string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[1]) + ",\n"
+						  + string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[2]) + ",\n"
+						  + string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[3]) + ")\n"
+						  + string(indentationDepth, '\t') + "\t";
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	if (gatherArgs.componentNdx >= 0)
+	{
+		DE_ASSERT(gatherArgs.componentNdx < 4);
+		result += ", " + de::toString(gatherArgs.componentNdx);
+	}
+
+	result += ")";
+
+	return result;
+}
+
+// \note If componentNdx for genProgramSources() is -1, component index is not specified.
+glu::ProgramSources TextureGatherCase::genProgramSources (GatherType					gatherType,
+														  TextureType					textureType,
+														  const tcu::TextureFormat&		textureFormat,
+														  const GatherArgs&				gatherArgs,
+														  const string&					refZExpr,
+														  const IVec2&					offsetRange)
+{
+	const bool				usePixCoord			= gatherType == GATHERTYPE_OFFSET_DYNAMIC;
+	const bool				useNormalizedCoord	= usePixCoord || isDepthFormat(textureFormat);
+	const bool				isDynamicOffset		= gatherType == GATHERTYPE_OFFSET_DYNAMIC;
+	const bool				isShadow			= isDepthFormat(textureFormat);
+	const glu::DataType		samplerType			= getSamplerType(textureType, textureFormat);
+	const int				numDims				= getNumTextureSamplingDimensions(textureType);
+	const string			funcCall			= genGatherFuncCall(gatherType, textureFormat, gatherArgs, refZExpr, offsetRange, 1);
+
+	return glu::ProgramSources() << genVertexShaderSource(requireGpuShader5(gatherType), numDims, isDynamicOffset || isShadow)
+								 << genFragmentShaderSource(requireGpuShader5(gatherType), numDims, samplerType, funcCall, useNormalizedCoord, usePixCoord);
+}
+
+void TextureGatherCase::init (void)
+{
+	TestLog&					log			= m_testCtx.getLog();
+	const glu::RenderContext&	renderCtx	= m_context.getRenderContext();
+	const glw::Functions&		gl			= renderCtx.getFunctions();
+	const deUint32				texTypeGL	= getGLTextureType(m_textureType);
+
+	// Check prerequisites.
+	if (requireGpuShader5(m_gatherType) && !m_context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"))
+		throw tcu::NotSupportedError("GL_EXT_gpu_shader5 required");
+
+	// Log and check implementation offset limits, if appropriate.
+	if (m_offsetSize == OFFSETSIZE_IMPLEMENTATION_MAXIMUM)
+	{
+		const IVec2 offsetRange = getOffsetRange();
+		log << TestLog::Integer("ImplementationMinTextureGatherOffset", "Implementation's value for GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET", "", QP_KEY_TAG_NONE, offsetRange[0])
+			<< TestLog::Integer("ImplementationMaxTextureGatherOffset", "Implementation's value for GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET", "", QP_KEY_TAG_NONE, offsetRange[1]);
+		TCU_CHECK_MSG(offsetRange[0] <= SPEC_MAX_MIN_OFFSET, ("GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET must be at most " + de::toString((int)SPEC_MAX_MIN_OFFSET)).c_str());
+		TCU_CHECK_MSG(offsetRange[1] >= SPEC_MIN_MAX_OFFSET, ("GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET must be at least " + de::toString((int)SPEC_MIN_MAX_OFFSET)).c_str());
+	}
+
+	// Create rbo and fbo.
+
+	m_colorBuffer = MovePtr<glu::Renderbuffer>(new glu::Renderbuffer(renderCtx));
+	gl.bindRenderbuffer(GL_RENDERBUFFER, **m_colorBuffer);
+	gl.renderbufferStorage(GL_RENDERBUFFER, glu::getInternalFormat(m_colorBufferFormat), RENDER_SIZE.x(), RENDER_SIZE.y());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Create and setup renderbuffer object");
+
+	m_fbo = MovePtr<glu::Framebuffer>(new glu::Framebuffer(renderCtx));
+	gl.bindFramebuffer(GL_FRAMEBUFFER, **m_fbo);
+	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, **m_colorBuffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Create and setup framebuffer object");
+
+	log << TestLog::Message << "Using a framebuffer object with renderbuffer with format "
+							<< glu::getPixelFormatName(glu::getInternalFormat(m_colorBufferFormat))
+							<< " and size " << RENDER_SIZE << TestLog::EndMessage;
+
+	// Generate subclass-specific iterations.
+
+	generateIterations();
+	m_currentIteration = 0;
+
+	// Initialize texture.
+
+	createAndUploadTexture();
+	gl.texParameteri(texTypeGL, GL_TEXTURE_WRAP_S,		glu::getGLWrapMode(m_wrapS));
+	gl.texParameteri(texTypeGL, GL_TEXTURE_WRAP_T,		glu::getGLWrapMode(m_wrapT));
+	gl.texParameteri(texTypeGL, GL_TEXTURE_MIN_FILTER,	glu::getGLFilterMode(m_minFilter));
+	gl.texParameteri(texTypeGL, GL_TEXTURE_MAG_FILTER,	glu::getGLFilterMode(m_magFilter));
+
+	if (m_baseLevel != 0)
+		gl.texParameteri(texTypeGL, GL_TEXTURE_BASE_LEVEL, m_baseLevel);
+
+	if (isDepthFormat(m_textureFormat))
+	{
+		gl.texParameteri(texTypeGL, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+		gl.texParameteri(texTypeGL, GL_TEXTURE_COMPARE_FUNC, glu::getGLCompareFunc(m_shadowCompareMode));
+	}
+
+	{
+		const deUint32 swizzleNamesGL[4] =
+		{
+			GL_TEXTURE_SWIZZLE_R,
+			GL_TEXTURE_SWIZZLE_G,
+			GL_TEXTURE_SWIZZLE_B,
+			GL_TEXTURE_SWIZZLE_A
+		};
+		const deUint32 initialGLTexSwizzles[4]	= { GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA };
+
+		for (int i = 0; i < 4; i++)
+		{
+			const deUint32 curGLSwizzle = getGLTextureSwizzleComponent(m_textureSwizzle[i]);
+			if (curGLSwizzle != initialGLTexSwizzles[i])
+				gl.texParameteri(texTypeGL, swizzleNamesGL[i], curGLSwizzle);
+		}
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set texture parameters");
+
+	log << TestLog::Message << "Texture base level is " << m_baseLevel << TestLog::EndMessage
+		<< TestLog::Message << "s and t wrap modes are "
+							<< glu::getTextureWrapModeName(glu::getGLWrapMode(m_wrapS)) << " and "
+							<< glu::getTextureWrapModeName(glu::getGLWrapMode(m_wrapT)) << ", respectively" << TestLog::EndMessage
+		<< TestLog::Message << "Minification and magnification filter modes are "
+							<< glu::getTextureFilterName(glu::getGLFilterMode(m_minFilter)) << " and "
+							<< glu::getTextureFilterName(glu::getGLFilterMode(m_magFilter)) << ", respectively "
+							<< (m_mipmapIncomplete ?
+								"(note that they cause the texture to be incomplete)" :
+								"(note that they should have no effect on gather result)")
+							<< TestLog::EndMessage
+		<< TestLog::Message << "Using texture swizzle " << m_textureSwizzle << TestLog::EndMessage;
+
+	if (m_shadowCompareMode != tcu::Sampler::COMPAREMODE_NONE)
+		log << TestLog::Message << "Using texture compare func " << glu::getCompareFuncName(glu::getGLCompareFunc(m_shadowCompareMode)) << TestLog::EndMessage;
+}
+
+void TextureGatherCase::deinit (void)
+{
+	m_program		= MovePtr<ShaderProgram>(DE_NULL);
+	m_fbo			= MovePtr<glu::Framebuffer>(DE_NULL);
+	m_colorBuffer	= MovePtr<glu::Renderbuffer>(DE_NULL);
+}
+
+TextureGatherCase::IterateResult TextureGatherCase::iterate (void)
+{
+	TestLog&						log								= m_testCtx.getLog();
+	const tcu::ScopedLogSection		iterationSection				(log, "Iteration" + de::toString(m_currentIteration), "Iteration " + de::toString(m_currentIteration));
+	const glu::RenderContext&		renderCtx						= m_context.getRenderContext();
+	const tcu::IVec2				renderSize						= RENDER_SIZE;
+	const glw::Functions&			gl								= renderCtx.getFunctions();
+	const GatherArgs&				gatherArgs						= getGatherArgs(m_currentIteration);
+	const string					refZExpr						= "v_normalizedCoord.x";
+	const bool						needPixelCoordInShader			= m_gatherType == GATHERTYPE_OFFSET_DYNAMIC;
+	const bool						needNormalizedCoordInShader		= needPixelCoordInShader || isDepthFormat(m_textureFormat);
+
+	// Generate a program appropriate for this iteration.
+
+	m_program = MovePtr<ShaderProgram>(new ShaderProgram(renderCtx, genProgramSources(m_gatherType, m_textureType, m_textureFormat, gatherArgs, refZExpr, getOffsetRange())));
+	if (m_currentIteration == 0)
+		m_testCtx.getLog() << *m_program;
+	else
+		m_testCtx.getLog() << TestLog::Message << "Using a program similar to the previous one, except with a gather function call as follows:\n"
+											   << genGatherFuncCall(m_gatherType, m_textureFormat, gatherArgs, refZExpr, getOffsetRange(), 0)
+											   << TestLog::EndMessage;
+	if (!m_program->isOk())
+	{
+		if (m_currentIteration != 0)
+			m_testCtx.getLog() << *m_program;
+		TCU_FAIL("Failed to build program");
+	}
+
+	// Render.
+
+	gl.viewport(0, 0, RENDER_SIZE.x(), RENDER_SIZE.y());
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	{
+		const float position[4*2] =
+		{
+			-1.0f, -1.0f,
+			-1.0f, +1.0f,
+			+1.0f, -1.0f,
+			+1.0f, +1.0f,
+		};
+
+		const float normalizedCoord[4*2] =
+		{
+			0.0f, 0.0f,
+			0.0f, 1.0f,
+			1.0f, 0.0f,
+			1.0f, 1.0f,
+		};
+
+		const vector<float> texCoord = computeQuadTexCoord(m_currentIteration);
+
+		vector<glu::VertexArrayBinding> attrBindings;
+		attrBindings.push_back(glu::va::Float("a_position", 2, 4, 0, &position[0]));
+		attrBindings.push_back(glu::va::Float("a_texCoord", (int)texCoord.size()/4, 4, 0, &texCoord[0]));
+		if (needNormalizedCoordInShader)
+			attrBindings.push_back(glu::va::Float("a_normalizedCoord", 2, 4, 0, &normalizedCoord[0]));
+
+		const deUint16 indices[6] = { 0, 1, 2, 2, 1, 3 };
+
+		gl.useProgram(m_program->getProgram());
+
+		{
+			const int samplerUniformLocation = gl.getUniformLocation(m_program->getProgram(), "u_sampler");
+			TCU_CHECK(samplerUniformLocation >= 0);
+			gl.uniform1i(samplerUniformLocation, 0);
+		}
+
+		if (needPixelCoordInShader)
+		{
+			const int viewportSizeUniformLocation = gl.getUniformLocation(m_program->getProgram(), "u_viewportSize");
+			TCU_CHECK(viewportSizeUniformLocation >= 0);
+			gl.uniform2f(viewportSizeUniformLocation, (float)RENDER_SIZE.x(), (float)RENDER_SIZE.y());
+		}
+
+		if (texCoord.size() == 2*4)
+		{
+			Vec2 texCoordVec[4];
+			computeTexCoordVecs(texCoord, texCoordVec);
+			log << TestLog::Message << "Texture coordinates run from " << texCoordVec[0] << " to " << texCoordVec[3] << TestLog::EndMessage;
+		}
+		else if (texCoord.size() == 3*4)
+		{
+			Vec3 texCoordVec[4];
+			computeTexCoordVecs(texCoord, texCoordVec);
+			log << TestLog::Message << "Texture coordinates run from " << texCoordVec[0] << " to " << texCoordVec[3] << TestLog::EndMessage;
+		}
+		else
+			DE_ASSERT(false);
+
+		glu::draw(renderCtx, m_program->getProgram(), (int)attrBindings.size(), &attrBindings[0],
+			glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+	}
+
+	// Verify result.
+
+	{
+		const tcu::TextureLevel rendered = getPixels(renderCtx, RENDER_SIZE, m_colorBufferFormat);
+
+		if (!verify(m_currentIteration, rendered.getAccess()))
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result verification failed");
+			return STOP;
+		}
+	}
+
+	m_currentIteration++;
+	if (m_currentIteration == (int)getNumIterations())
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+template <typename TexViewT, typename TexCoordT>
+bool TextureGatherCase::verify (const ConstPixelBufferAccess&	rendered,
+								const TexViewT&					texture,
+								const TexCoordT					(&texCoords)[4],
+								const GatherArgs&				gatherArgs) const
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (m_mipmapIncomplete)
+	{
+		const int	componentNdx		= de::max(0, gatherArgs.componentNdx);
+		const Vec4	incompleteColor		(0.0f, 0.0f, 0.0f, 1.0f);
+		const Vec4	refColor			(incompleteColor[componentNdx]);
+		const bool	isOk				= verifySingleColored(log, rendered, refColor);
+
+		if (!isOk)
+			log << TestLog::Message << "Note: expected color " << refColor << " for all pixels; "
+									<< incompleteColor[componentNdx] << " is component at index " << componentNdx
+									<< " in the color " << incompleteColor << ", which is used for incomplete textures" << TestLog::EndMessage;
+
+		return isOk;
+	}
+	else
+	{
+		DE_ASSERT(m_colorBufferFormat.order == tcu::TextureFormat::RGBA);
+		DE_ASSERT(m_colorBufferFormat.type == tcu::TextureFormat::UNORM_INT8		||
+				  m_colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT8		||
+				  m_colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT8);
+
+		const MovePtr<PixelOffsets>		pixelOffsets	= makePixelOffsetsFunctor(m_gatherType, gatherArgs, getOffsetRange());
+		const tcu::PixelFormat			pixelFormat		= tcu::PixelFormat(8,8,8,8);
+		const IVec4						colorBits		= tcu::max(gls::TextureTestUtil::getBitsVec(pixelFormat) - 1, tcu::IVec4(0));
+		const IVec3						coordBits		= m_textureType == TEXTURETYPE_2D			? IVec3(20,20,0)
+														: m_textureType == TEXTURETYPE_CUBE			? IVec3(10,10,10)
+														: m_textureType == TEXTURETYPE_2D_ARRAY		? IVec3(20,20,20)
+														: IVec3(-1);
+		const IVec3						uvwBits			= m_textureType == TEXTURETYPE_2D			? IVec3(7,7,0)
+														: m_textureType == TEXTURETYPE_CUBE			? IVec3(6,6,0)
+														: m_textureType == TEXTURETYPE_2D_ARRAY		? IVec3(7,7,7)
+														: IVec3(-1);
+		tcu::Sampler					sampler;
+		sampler.wrapS		= m_wrapS;
+		sampler.wrapT		= m_wrapT;
+		sampler.compare		= m_shadowCompareMode;
+
+		if (isDepthFormat(m_textureFormat))
+		{
+			tcu::TexComparePrecision comparePrec;
+			comparePrec.coordBits		= coordBits;
+			comparePrec.uvwBits			= uvwBits;
+			comparePrec.referenceBits	= 16;
+			comparePrec.resultBits		= pixelFormat.redBits-1;
+
+			return verifyGatherOffsetsCompare(log, rendered, texture, texCoords, sampler, comparePrec, PixelCompareRefZDefault(RENDER_SIZE), *pixelOffsets);
+		}
+		else
+		{
+			const int componentNdx = de::max(0, gatherArgs.componentNdx);
+
+			if (isUnormFormatType(m_textureFormat.type))
+			{
+				tcu::LookupPrecision lookupPrec;
+				lookupPrec.colorThreshold	= tcu::computeFixedPointThreshold(colorBits);
+				lookupPrec.coordBits		= coordBits;
+				lookupPrec.uvwBits			= uvwBits;
+				lookupPrec.colorMask		= gls::TextureTestUtil::getCompareMask(pixelFormat);
+				return verifyGatherOffsets<float>(log, rendered, texture, texCoords, sampler, lookupPrec, componentNdx, *pixelOffsets);
+			}
+			else if (isUIntFormatType(m_textureFormat.type) || isSIntFormatType(m_textureFormat.type))
+			{
+				tcu::IntLookupPrecision		lookupPrec;
+				lookupPrec.colorThreshold	= UVec4(0);
+				lookupPrec.coordBits		= coordBits;
+				lookupPrec.uvwBits			= uvwBits;
+				lookupPrec.colorMask		= gls::TextureTestUtil::getCompareMask(pixelFormat);
+
+				if (isUIntFormatType(m_textureFormat.type))
+					return verifyGatherOffsets<deUint32>(log, rendered, texture, texCoords, sampler, lookupPrec, componentNdx, *pixelOffsets);
+				else if (isSIntFormatType(m_textureFormat.type))
+					return verifyGatherOffsets<deInt32>(log, rendered, texture, texCoords, sampler, lookupPrec, componentNdx, *pixelOffsets);
+				else
+				{
+					DE_ASSERT(false);
+					return false;
+				}
+			}
+			else
+			{
+				DE_ASSERT(false);
+				return false;
+			}
+		}
+	}
+}
+
+vector<GatherArgs> generateBasic2DCaseIterations (GatherType gatherType, const tcu::TextureFormat& textureFormat, const IVec2& offsetRange)
+{
+	const int			numComponentCases	= isDepthFormat(textureFormat) ? 1 : 4+1; // \note For non-depth textures, test explicit components 0 to 3 and implicit component 0.
+	vector<GatherArgs>	result;
+
+	for (int componentCaseNdx = 0; componentCaseNdx < numComponentCases; componentCaseNdx++)
+	{
+		const int componentNdx = componentCaseNdx - 1;
+
+		switch (gatherType)
+		{
+			case GATHERTYPE_BASIC:
+				result.push_back(GatherArgs(componentNdx));
+				break;
+
+			case GATHERTYPE_OFFSET:
+			{
+				const int min	= offsetRange.x();
+				const int max	= offsetRange.y();
+				const int hmin	= divRoundToZero(min, 2);
+				const int hmax	= divRoundToZero(max, 2);
+
+				result.push_back(GatherArgs(componentNdx, IVec2(min, max)));
+
+				if (componentCaseNdx == 0) // Don't test all offsets variants for all color components (they should be pretty orthogonal).
+				{
+					result.push_back(GatherArgs(componentNdx, IVec2(min,	min)));
+					result.push_back(GatherArgs(componentNdx, IVec2(max,	min)));
+					result.push_back(GatherArgs(componentNdx, IVec2(max,	max)));
+
+					result.push_back(GatherArgs(componentNdx, IVec2(0,		hmax)));
+					result.push_back(GatherArgs(componentNdx, IVec2(hmin,	0)));
+					result.push_back(GatherArgs(componentNdx, IVec2(0,		0)));
+				}
+
+				break;
+			}
+
+			case GATHERTYPE_OFFSET_DYNAMIC:
+				result.push_back(GatherArgs(componentNdx));
+				break;
+
+			case GATHERTYPE_OFFSETS:
+			{
+				const int min	= offsetRange.x();
+				const int max	= offsetRange.y();
+				const int hmin	= divRoundToZero(min, 2);
+				const int hmax	= divRoundToZero(max, 2);
+
+				result.push_back(GatherArgs(componentNdx,
+											IVec2(min,	min),
+											IVec2(min,	max),
+											IVec2(max,	min),
+											IVec2(max,	max)));
+
+				if (componentCaseNdx == 0) // Don't test all offsets variants for all color components (they should be pretty orthogonal).
+					result.push_back(GatherArgs(componentNdx,
+												IVec2(min,	hmax),
+												IVec2(hmin,	max),
+												IVec2(0,	hmax),
+												IVec2(hmax,	0)));
+				break;
+			}
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	return result;
+}
+
+class TextureGather2DCase : public TextureGatherCase
+{
+public:
+	TextureGather2DCase (Context&					context,
+						 const char*				name,
+						 const char*				description,
+						 GatherType					gatherType,
+						 OffsetSize					offsetSize,
+						 tcu::TextureFormat			textureFormat,
+						 tcu::Sampler::CompareMode	shadowCompareMode,
+						 tcu::Sampler::WrapMode		wrapS,
+						 tcu::Sampler::WrapMode		wrapT,
+						 const TextureSwizzle&		texSwizzle,
+						 tcu::Sampler::FilterMode	minFilter,
+						 tcu::Sampler::FilterMode	magFilter,
+						 int						baseLevel,
+						 bool						mipmapIncomplete,
+						 const IVec2&				textureSize)
+		: TextureGatherCase		(context, name, description, TEXTURETYPE_2D, gatherType, offsetSize, textureFormat, shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, mipmapIncomplete)
+		, m_textureSize			(textureSize)
+		, m_swizzledTexture		(tcu::TextureFormat(), 1, 1)
+	{
+	}
+
+protected:
+	void						generateIterations		(void);
+	void						createAndUploadTexture	(void);
+	int							getNumIterations		(void) const { DE_ASSERT(!m_iterations.empty()); return (int)m_iterations.size(); }
+	GatherArgs					getGatherArgs			(int iterationNdx) const { return m_iterations[iterationNdx]; }
+	vector<float>				computeQuadTexCoord		(int iterationNdx) const;
+	bool						verify					(int iterationNdx, const ConstPixelBufferAccess& rendered) const;
+
+private:
+	const IVec2					m_textureSize;
+
+	MovePtr<glu::Texture2D>		m_texture;
+	tcu::Texture2D				m_swizzledTexture;
+	vector<GatherArgs>			m_iterations;
+};
+
+vector<float> TextureGather2DCase::computeQuadTexCoord (int /* iterationNdx */) const
+{
+	vector<float> res;
+	gls::TextureTestUtil::computeQuadTexCoord2D(res, Vec2(-0.3f, -0.4f), Vec2(1.5f, 1.6f));
+	return res;
+}
+
+void TextureGather2DCase::generateIterations (void)
+{
+	DE_ASSERT(m_iterations.empty());
+	m_iterations = generateBasic2DCaseIterations(m_gatherType, m_textureFormat, getOffsetRange());
+}
+
+void TextureGather2DCase::createAndUploadTexture (void)
+{
+	const glu::RenderContext&		renderCtx	= m_context.getRenderContext();
+	const glw::Functions&			gl			= renderCtx.getFunctions();
+	const tcu::TextureFormatInfo	texFmtInfo	= tcu::getTextureFormatInfo(m_textureFormat);
+
+	m_texture = MovePtr<glu::Texture2D>(new glu::Texture2D(renderCtx, glu::getInternalFormat(m_textureFormat), m_textureSize.x(), m_textureSize.y()));
+
+	{
+		tcu::Texture2D&		refTexture	= m_texture->getRefTexture();
+		const int			levelBegin	= m_baseLevel;
+		const int			levelEnd	= isMipmapFilter(m_minFilter) && !m_mipmapIncomplete ? refTexture.getNumLevels() : m_baseLevel+1;
+		DE_ASSERT(m_baseLevel < refTexture.getNumLevels());
+
+		for (int levelNdx = levelBegin; levelNdx < levelEnd; levelNdx++)
+		{
+			refTexture.allocLevel(levelNdx);
+			const PixelBufferAccess& level = refTexture.getLevel(levelNdx);
+			fillWithRandomColorTiles(level, texFmtInfo.valueMin, texFmtInfo.valueMax, (deUint32)m_testCtx.getCommandLine().getBaseSeed());
+			m_testCtx.getLog() << TestLog::Image("InputTextureLevel" + de::toString(levelNdx), "Input texture, level " + de::toString(levelNdx), level)
+							   << TestLog::Message << "Note: texture level's size is " << IVec2(level.getWidth(), level.getHeight()) << TestLog::EndMessage;
+		}
+
+		swizzleTexture(m_swizzledTexture, refTexture, m_textureSwizzle);
+	}
+
+	gl.activeTexture(GL_TEXTURE0);
+	m_texture->upload();
+}
+
+bool TextureGather2DCase::verify (int iterationNdx, const ConstPixelBufferAccess& rendered) const
+{
+	Vec2 texCoords[4];
+	computeTexCoordVecs(computeQuadTexCoord(iterationNdx), texCoords);
+	return TextureGatherCase::verify(rendered, getOneLevelSubView(tcu::Texture2DView(m_swizzledTexture), m_baseLevel), texCoords, m_iterations[iterationNdx]);
+}
+
+class TextureGather2DArrayCase : public TextureGatherCase
+{
+public:
+	TextureGather2DArrayCase (Context&						context,
+							  const char*					name,
+							  const char*					description,
+							  GatherType					gatherType,
+							  OffsetSize					offsetSize,
+							  tcu::TextureFormat			textureFormat,
+							  tcu::Sampler::CompareMode		shadowCompareMode,
+							  tcu::Sampler::WrapMode		wrapS,
+							  tcu::Sampler::WrapMode		wrapT,
+							  const TextureSwizzle&			texSwizzle,
+							  tcu::Sampler::FilterMode		minFilter,
+							  tcu::Sampler::FilterMode		magFilter,
+							  int							baseLevel,
+							  bool							mipmapIncomplete,
+							  const IVec3&					textureSize)
+		: TextureGatherCase		(context, name, description, TEXTURETYPE_2D_ARRAY, gatherType, offsetSize, textureFormat, shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, mipmapIncomplete)
+		, m_textureSize			(textureSize)
+		, m_swizzledTexture		(tcu::TextureFormat(), 1, 1, 1)
+	{
+	}
+
+protected:
+	void							generateIterations		(void);
+	void							createAndUploadTexture	(void);
+	int								getNumIterations		(void) const { DE_ASSERT(!m_iterations.empty()); return (int)m_iterations.size(); }
+	GatherArgs						getGatherArgs			(int iterationNdx) const { return m_iterations[iterationNdx].gatherArgs; }
+	vector<float>					computeQuadTexCoord		(int iterationNdx) const;
+	bool							verify					(int iterationNdx, const ConstPixelBufferAccess& rendered) const;
+
+private:
+	struct Iteration
+	{
+		GatherArgs	gatherArgs;
+		int			layerNdx;
+	};
+
+	const IVec3						m_textureSize;
+
+	MovePtr<glu::Texture2DArray>	m_texture;
+	tcu::Texture2DArray				m_swizzledTexture;
+	vector<Iteration>				m_iterations;
+};
+
+vector<float> TextureGather2DArrayCase::computeQuadTexCoord (int iterationNdx) const
+{
+	vector<float> res;
+	gls::TextureTestUtil::computeQuadTexCoord2DArray(res, m_iterations[iterationNdx].layerNdx, Vec2(-0.3f, -0.4f), Vec2(1.5f, 1.6f));
+	return res;
+}
+
+void TextureGather2DArrayCase::generateIterations (void)
+{
+	DE_ASSERT(m_iterations.empty());
+
+	const vector<GatherArgs> basicIterations = generateBasic2DCaseIterations(m_gatherType, m_textureFormat, getOffsetRange());
+
+	// \note Out-of-bounds layer indices are tested too.
+	for (int layerNdx = -1; layerNdx < m_textureSize.z()+1; layerNdx++)
+	{
+		// Don't duplicate all cases for all layers.
+		if (layerNdx == 0)
+		{
+			for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
+			{
+				m_iterations.push_back(Iteration());
+				m_iterations.back().gatherArgs = basicIterations[basicNdx];
+				m_iterations.back().layerNdx = layerNdx;
+			}
+		}
+		else
+		{
+			// For other layers than 0, only test one component and one set of offsets per layer.
+			for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
+			{
+				if (isDepthFormat(m_textureFormat) || basicIterations[basicNdx].componentNdx == (layerNdx + 2) % 4)
+				{
+					m_iterations.push_back(Iteration());
+					m_iterations.back().gatherArgs = basicIterations[basicNdx];
+					m_iterations.back().layerNdx = layerNdx;
+					break;
+				}
+			}
+		}
+	}
+}
+
+void TextureGather2DArrayCase::createAndUploadTexture (void)
+{
+	TestLog&						log			= m_testCtx.getLog();
+	const glu::RenderContext&		renderCtx	= m_context.getRenderContext();
+	const glw::Functions&			gl			= renderCtx.getFunctions();
+	const tcu::TextureFormatInfo	texFmtInfo	= tcu::getTextureFormatInfo(m_textureFormat);
+
+	m_texture = MovePtr<glu::Texture2DArray>(new glu::Texture2DArray(renderCtx, glu::getInternalFormat(m_textureFormat), m_textureSize.x(), m_textureSize.y(), m_textureSize.z()));
+
+	{
+		tcu::Texture2DArray&	refTexture	= m_texture->getRefTexture();
+		const int				levelBegin	= m_baseLevel;
+		const int				levelEnd	= isMipmapFilter(m_minFilter) && !m_mipmapIncomplete ? refTexture.getNumLevels() : m_baseLevel+1;
+		DE_ASSERT(m_baseLevel < refTexture.getNumLevels());
+
+		for (int levelNdx = levelBegin; levelNdx < levelEnd; levelNdx++)
+		{
+			refTexture.allocLevel(levelNdx);
+			const PixelBufferAccess& level = refTexture.getLevel(levelNdx);
+			fillWithRandomColorTiles(level, texFmtInfo.valueMin, texFmtInfo.valueMax, (deUint32)m_testCtx.getCommandLine().getBaseSeed());
+
+			log << TestLog::ImageSet("InputTextureLevel", "Input texture, level " + de::toString(levelNdx));
+			for (int layerNdx = 0; layerNdx < m_textureSize.z(); layerNdx++)
+				log << TestLog::Image("InputTextureLevel" + de::toString(layerNdx) + "Layer" + de::toString(layerNdx),
+									  "Layer " + de::toString(layerNdx),
+									  tcu::getSubregion(level, 0, 0, layerNdx, level.getWidth(), level.getHeight(), 1));
+			log << TestLog::EndImageSet
+				<< TestLog::Message << "Note: texture level's size is " << IVec3(level.getWidth(), level.getHeight(), level.getDepth()) << TestLog::EndMessage;
+		}
+
+		swizzleTexture(m_swizzledTexture, refTexture, m_textureSwizzle);
+	}
+
+	gl.activeTexture(GL_TEXTURE0);
+	m_texture->upload();
+}
+
+bool TextureGather2DArrayCase::verify (int iterationNdx, const ConstPixelBufferAccess& rendered) const
+{
+	Vec3 texCoords[4];
+	computeTexCoordVecs(computeQuadTexCoord(iterationNdx), texCoords);
+	return TextureGatherCase::verify(rendered, getOneLevelSubView(tcu::Texture2DArrayView(m_swizzledTexture), m_baseLevel), texCoords, m_iterations[iterationNdx].gatherArgs);
+}
+
+// \note Cube case always uses just basic textureGather(); offset versions are not defined for cube maps.
+class TextureGatherCubeCase : public TextureGatherCase
+{
+public:
+	TextureGatherCubeCase (Context&						context,
+						   const char*					name,
+						   const char*					description,
+						   tcu::TextureFormat			textureFormat,
+						   tcu::Sampler::CompareMode	shadowCompareMode,
+						   tcu::Sampler::WrapMode		wrapS,
+						   tcu::Sampler::WrapMode		wrapT,
+						   const TextureSwizzle&		texSwizzle,
+						   tcu::Sampler::FilterMode		minFilter,
+						   tcu::Sampler::FilterMode		magFilter,
+						   int							baseLevel,
+						   bool							mipmapIncomplete,
+						   int							textureSize)
+		: TextureGatherCase		(context, name, description, TEXTURETYPE_CUBE, GATHERTYPE_BASIC, OFFSETSIZE_NONE, textureFormat, shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, mipmapIncomplete)
+		, m_textureSize			(textureSize)
+		, m_swizzledTexture		(tcu::TextureFormat(), 1)
+	{
+	}
+
+protected:
+	void						generateIterations		(void);
+	void						createAndUploadTexture	(void);
+	int							getNumIterations		(void) const { DE_ASSERT(!m_iterations.empty()); return (int)m_iterations.size(); }
+	GatherArgs					getGatherArgs			(int iterationNdx) const { return m_iterations[iterationNdx].gatherArgs; }
+	vector<float>				computeQuadTexCoord		(int iterationNdx) const;
+	bool						verify					(int iterationNdx, const ConstPixelBufferAccess& rendered) const;
+
+private:
+	struct Iteration
+	{
+		GatherArgs		gatherArgs;
+		tcu::CubeFace	face;
+	};
+
+	const int					m_textureSize;
+
+	MovePtr<glu::TextureCube>	m_texture;
+	tcu::TextureCube			m_swizzledTexture;
+	vector<Iteration>			m_iterations;
+};
+
+vector<float> TextureGatherCubeCase::computeQuadTexCoord (int iterationNdx) const
+{
+	vector<float> res;
+	gls::TextureTestUtil::computeQuadTexCoordCube(res, m_iterations[iterationNdx].face, Vec2(-1.2f), Vec2(1.2f));
+	return res;
+}
+
+void TextureGatherCubeCase::generateIterations (void)
+{
+	DE_ASSERT(m_iterations.empty());
+
+	const vector<GatherArgs> basicIterations = generateBasic2DCaseIterations(m_gatherType, m_textureFormat, getOffsetRange());
+
+	for (int cubeFaceI = 0; cubeFaceI < tcu::CUBEFACE_LAST; cubeFaceI++)
+	{
+		const tcu::CubeFace cubeFace = (tcu::CubeFace)cubeFaceI;
+
+		// Don't duplicate all cases for all faces.
+		if (cubeFaceI == 0)
+		{
+			for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
+			{
+				m_iterations.push_back(Iteration());
+				m_iterations.back().gatherArgs = basicIterations[basicNdx];
+				m_iterations.back().face = cubeFace;
+			}
+		}
+		else
+		{
+			// For other faces than first, only test one component per face.
+			for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
+			{
+				if (isDepthFormat(m_textureFormat) || basicIterations[basicNdx].componentNdx == cubeFaceI % 4)
+				{
+					m_iterations.push_back(Iteration());
+					m_iterations.back().gatherArgs = basicIterations[basicNdx];
+					m_iterations.back().face = cubeFace;
+					break;
+				}
+			}
+		}
+	}
+}
+
+void TextureGatherCubeCase::createAndUploadTexture (void)
+{
+	TestLog&						log			= m_testCtx.getLog();
+	const glu::RenderContext&		renderCtx	= m_context.getRenderContext();
+	const glw::Functions&			gl			= renderCtx.getFunctions();
+	const tcu::TextureFormatInfo	texFmtInfo	= tcu::getTextureFormatInfo(m_textureFormat);
+
+	m_texture = MovePtr<glu::TextureCube>(new glu::TextureCube(renderCtx, glu::getInternalFormat(m_textureFormat), m_textureSize));
+
+	{
+		tcu::TextureCube&	refTexture	= m_texture->getRefTexture();
+		const int			levelBegin	= m_baseLevel;
+		const int			levelEnd	= isMipmapFilter(m_minFilter) && !m_mipmapIncomplete ? refTexture.getNumLevels() : m_baseLevel+1;
+		DE_ASSERT(m_baseLevel < refTexture.getNumLevels());
+
+		for (int levelNdx = levelBegin; levelNdx < levelEnd; levelNdx++)
+		{
+			log << TestLog::ImageSet("InputTextureLevel" + de::toString(levelNdx), "Input texture, level " + de::toString(levelNdx));
+
+			for (int cubeFaceI = 0; cubeFaceI < tcu::CUBEFACE_LAST; cubeFaceI++)
+			{
+				const tcu::CubeFace			cubeFace	= (tcu::CubeFace)cubeFaceI;
+				refTexture.allocLevel(cubeFace, levelNdx);
+				const PixelBufferAccess&	levelFace	= refTexture.getLevelFace(levelNdx, cubeFace);
+				fillWithRandomColorTiles(levelFace, texFmtInfo.valueMin, texFmtInfo.valueMax, (deUint32)m_testCtx.getCommandLine().getBaseSeed() ^ (deUint32)cubeFaceI);
+
+				m_testCtx.getLog() << TestLog::Image("InputTextureLevel" + de::toString(levelNdx) + "Face" + de::toString((int)cubeFace),
+													 de::toString(cubeFace),
+													 levelFace);
+			}
+
+			log << TestLog::EndImageSet
+				<< TestLog::Message << "Note: texture level's size is " << refTexture.getLevelFace(levelNdx, tcu::CUBEFACE_NEGATIVE_X).getWidth() << TestLog::EndMessage;
+		}
+
+		swizzleTexture(m_swizzledTexture, refTexture, m_textureSwizzle);
+	}
+
+	gl.activeTexture(GL_TEXTURE0);
+	m_texture->upload();
+}
+
+bool TextureGatherCubeCase::verify (int iterationNdx, const ConstPixelBufferAccess& rendered) const
+{
+	Vec3 texCoords[4];
+	computeTexCoordVecs(computeQuadTexCoord(iterationNdx), texCoords);
+	return TextureGatherCase::verify(rendered, getOneLevelSubView(tcu::TextureCubeView(m_swizzledTexture), m_baseLevel), texCoords, m_iterations[iterationNdx].gatherArgs);
+}
+
+static inline TextureGatherCase* makeTextureGatherCase (TextureType					textureType,
+														Context&					context,
+														const char*					name,
+														const char*					description,
+														GatherType					gatherType,
+														OffsetSize					offsetSize,
+														tcu::TextureFormat			textureFormat,
+														tcu::Sampler::CompareMode	shadowCompareMode,
+														tcu::Sampler::WrapMode		wrapS,
+														tcu::Sampler::WrapMode		wrapT,
+														const TextureSwizzle&		texSwizzle,
+														tcu::Sampler::FilterMode	minFilter,
+														tcu::Sampler::FilterMode	magFilter,
+														int							baseLevel,
+														const IVec3&				textureSize,
+														bool						mipmapIncomplete = false)
+{
+	switch (textureType)
+	{
+		case TEXTURETYPE_2D:
+			return new TextureGather2DCase(context, name, description, gatherType, offsetSize, textureFormat, shadowCompareMode,
+										   wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, mipmapIncomplete, textureSize.swizzle(0, 1));
+
+		case TEXTURETYPE_2D_ARRAY:
+			return new TextureGather2DArrayCase(context, name, description, gatherType, offsetSize, textureFormat, shadowCompareMode,
+												wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, mipmapIncomplete, textureSize);
+
+		case TEXTURETYPE_CUBE:
+			DE_ASSERT(gatherType == GATHERTYPE_BASIC);
+			DE_ASSERT(offsetSize == OFFSETSIZE_NONE);
+			return new TextureGatherCubeCase(context, name, description, textureFormat, shadowCompareMode,
+											 wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, mipmapIncomplete, textureSize.x());
+
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+} // anonymous
+
+TextureGatherTests::TextureGatherTests (Context& context)
+	: TestCaseGroup(context, "gather", "textureGather* tests")
+{
+}
+
+static inline const char* compareModeName (tcu::Sampler::CompareMode mode)
+{
+	switch (mode)
+	{
+		case tcu::Sampler::COMPAREMODE_LESS:				return "less";
+		case tcu::Sampler::COMPAREMODE_LESS_OR_EQUAL:		return "less_or_equal";
+		case tcu::Sampler::COMPAREMODE_GREATER:				return "greater";
+		case tcu::Sampler::COMPAREMODE_GREATER_OR_EQUAL:	return "greater_or_equal";
+		case tcu::Sampler::COMPAREMODE_EQUAL:				return "equal";
+		case tcu::Sampler::COMPAREMODE_NOT_EQUAL:			return "not_equal";
+		case tcu::Sampler::COMPAREMODE_ALWAYS:				return "always";
+		case tcu::Sampler::COMPAREMODE_NEVER:				return "never";
+		default: DE_ASSERT(false); return DE_NULL;
+	}
+}
+
+void TextureGatherTests::init (void)
+{
+	const struct
+	{
+		const char* name;
+		TextureType type;
+	} textureTypes[] =
+	{
+		{ "2d",			TEXTURETYPE_2D			},
+		{ "2d_array",	TEXTURETYPE_2D_ARRAY	},
+		{ "cube",		TEXTURETYPE_CUBE		}
+	};
+
+	const struct
+	{
+		const char*			name;
+		tcu::TextureFormat	format;
+	} formats[] =
+	{
+		{ "rgba8",		tcu::TextureFormat(tcu::TextureFormat::RGBA,	tcu::TextureFormat::UNORM_INT8)		},
+		{ "rgba8ui",	tcu::TextureFormat(tcu::TextureFormat::RGBA,	tcu::TextureFormat::UNSIGNED_INT8)	},
+		{ "rgba8i",		tcu::TextureFormat(tcu::TextureFormat::RGBA,	tcu::TextureFormat::SIGNED_INT8)	},
+		{ "depth32f",	tcu::TextureFormat(tcu::TextureFormat::D,		tcu::TextureFormat::FLOAT)			}
+	};
+
+	const struct
+	{
+		const char*		name;
+		IVec3			size;
+	} textureSizes[] =
+	{
+		{ "size_pot",	IVec3(64, 64, 3) },
+		{ "size_npot",	IVec3(17, 23, 3) }
+	};
+
+	const struct
+	{
+		const char*				name;
+		tcu::Sampler::WrapMode	mode;
+	} wrapModes[] =
+	{
+		{ "clamp_to_edge",		tcu::Sampler::CLAMP_TO_EDGE			},
+		{ "repeat",				tcu::Sampler::REPEAT_GL				},
+		{ "mirrored_repeat",	tcu::Sampler::MIRRORED_REPEAT_GL	}
+	};
+
+	for (int gatherTypeI = 0; gatherTypeI < GATHERTYPE_LAST; gatherTypeI++)
+	{
+		const GatherType		gatherType			= (GatherType)gatherTypeI;
+		TestCaseGroup* const	gatherTypeGroup		= new TestCaseGroup(m_context, gatherTypeName(gatherType), gatherTypeDescription(gatherType));
+		addChild(gatherTypeGroup);
+
+		for (int offsetSizeI = 0; offsetSizeI < OFFSETSIZE_LAST; offsetSizeI++)
+		{
+			const OffsetSize offsetSize = (OffsetSize)offsetSizeI;
+			if ((gatherType == GATHERTYPE_BASIC) != (offsetSize == OFFSETSIZE_NONE))
+				continue;
+
+			TestCaseGroup* const offsetSizeGroup = offsetSize == OFFSETSIZE_NONE ?
+													gatherTypeGroup :
+													new TestCaseGroup(m_context,
+																	  offsetSize == OFFSETSIZE_MINIMUM_REQUIRED				? "min_required_offset"
+																	  : offsetSize == OFFSETSIZE_IMPLEMENTATION_MAXIMUM		? "implementation_offset"
+																	  : DE_NULL,
+																	  offsetSize == OFFSETSIZE_MINIMUM_REQUIRED				? "Use offsets within GL minimum required range"
+																	  : offsetSize == OFFSETSIZE_IMPLEMENTATION_MAXIMUM		? "Use offsets within the implementation range"
+																	  : DE_NULL);
+			if (offsetSizeGroup != gatherTypeGroup)
+				gatherTypeGroup->addChild(offsetSizeGroup);
+
+			for (int textureTypeNdx = 0; textureTypeNdx < DE_LENGTH_OF_ARRAY(textureTypes); textureTypeNdx++)
+			{
+				const TextureType textureType = textureTypes[textureTypeNdx].type;
+
+				if (textureType == TEXTURETYPE_CUBE && gatherType != GATHERTYPE_BASIC)
+					continue;
+
+				TestCaseGroup* const textureTypeGroup = new TestCaseGroup(m_context, textureTypes[textureTypeNdx].name, "");
+				offsetSizeGroup->addChild(textureTypeGroup);
+
+				for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
+				{
+					const tcu::TextureFormat&	format			= formats[formatNdx].format;
+					TestCaseGroup* const		formatGroup		= new TestCaseGroup(m_context, formats[formatNdx].name, "");
+					textureTypeGroup->addChild(formatGroup);
+
+					for (int textureSizeNdx = 0; textureSizeNdx < DE_LENGTH_OF_ARRAY(textureSizes); textureSizeNdx++)
+					{
+						const IVec3&			textureSize			= textureSizes[textureSizeNdx].size;
+						TestCaseGroup* const	textureSizeGroup	= new TestCaseGroup(m_context, textureSizes[textureSizeNdx].name, "");
+						formatGroup->addChild(textureSizeGroup);
+
+						for (int compareModeI = 0; compareModeI < tcu::Sampler::COMPAREMODE_LAST; compareModeI++)
+						{
+							const tcu::Sampler::CompareMode compareMode = (tcu::Sampler::CompareMode)compareModeI;
+
+							if ((compareMode != tcu::Sampler::COMPAREMODE_NONE) != isDepthFormat(format))
+								continue;
+
+							if (compareMode != tcu::Sampler::COMPAREMODE_NONE &&
+								compareMode != tcu::Sampler::COMPAREMODE_LESS &&
+								compareMode != tcu::Sampler::COMPAREMODE_GREATER)
+								continue;
+
+							TestCaseGroup* const compareModeGroup = compareMode == tcu::Sampler::COMPAREMODE_NONE ?
+																		textureSizeGroup :
+																		new TestCaseGroup(m_context,
+																						  (string() + "compare_" + compareModeName(compareMode)).c_str(),
+																						  "");
+							if (compareModeGroup != textureSizeGroup)
+								textureSizeGroup->addChild(compareModeGroup);
+
+							for (int wrapCaseNdx = 0; wrapCaseNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapCaseNdx++)
+							{
+								const int						wrapSNdx	= wrapCaseNdx;
+								const int						wrapTNdx	= (wrapCaseNdx + 1) % DE_LENGTH_OF_ARRAY(wrapModes);
+								const tcu::Sampler::WrapMode	wrapS		= wrapModes[wrapSNdx].mode;
+								const tcu::Sampler::WrapMode	wrapT		= wrapModes[wrapTNdx].mode;
+
+								const string caseName = string() + wrapModes[wrapSNdx].name + "_" + wrapModes[wrapTNdx].name;
+
+								compareModeGroup->addChild(makeTextureGatherCase(textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format, compareMode, wrapS, wrapT,
+																				 s_identityTextureSwizzle, tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, 0, textureSize));
+							}
+						}
+					}
+
+					if (offsetSize != OFFSETSIZE_MINIMUM_REQUIRED) // Don't test all features for both offset size types, as they should be rather orthogonal.
+					{
+						if (!isDepthFormat(format))
+						{
+							TestCaseGroup* const swizzleGroup = new TestCaseGroup(m_context, "texture_swizzle", "");
+							formatGroup->addChild(swizzleGroup);
+
+							DE_STATIC_ASSERT(TEXTURESWIZZLECOMPONENT_R == 0);
+							for (int swizzleCaseNdx = 0; swizzleCaseNdx < TEXTURESWIZZLECOMPONENT_LAST; swizzleCaseNdx++)
+							{
+								TextureSwizzle	swizzle;
+								string			caseName;
+
+								for (int i = 0; i < 4; i++)
+								{
+									swizzle[i] = (TextureSwizzleComponent)((swizzleCaseNdx + i) % (int)TEXTURESWIZZLECOMPONENT_LAST);
+									caseName += (i > 0 ? "_" : "") + de::toLower(de::toString(swizzle[i]));
+								}
+
+								if (swizzle == s_identityTextureSwizzle)
+									continue; // Already tested in above cases.
+
+								swizzleGroup->addChild(makeTextureGatherCase(textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format,
+																			 tcu::Sampler::COMPAREMODE_NONE, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+																			 swizzle, tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, 0, IVec3(64, 64, 3)));
+							}
+						}
+
+						{
+							TestCaseGroup* const filterModeGroup = new TestCaseGroup(m_context, "filter_mode", "Test that filter modes have no effect");
+							formatGroup->addChild(filterModeGroup);
+
+							const struct
+							{
+								const char*					name;
+								tcu::Sampler::FilterMode	filter;
+							} magFilters[] =
+							{
+								{ "linear",		tcu::Sampler::LINEAR	},
+								{ "nearest",	tcu::Sampler::NEAREST	}
+							};
+
+							const struct
+							{
+								const char*					name;
+								tcu::Sampler::FilterMode	filter;
+							} minFilters[] =
+							{
+								// \note Don't test NEAREST here, as it's covered by other cases.
+								{ "linear",						tcu::Sampler::LINEAR					},
+								{ "nearest_mipmap_nearest",		tcu::Sampler::NEAREST_MIPMAP_NEAREST	},
+								{ "nearest_mipmap_linear",		tcu::Sampler::NEAREST_MIPMAP_LINEAR		},
+								{ "linear_mipmap_nearest",		tcu::Sampler::LINEAR_MIPMAP_NEAREST		},
+								{ "linear_mipmap_linear",		tcu::Sampler::LINEAR_MIPMAP_LINEAR		},
+							};
+
+							for (int minFilterNdx = 0; minFilterNdx < DE_LENGTH_OF_ARRAY(minFilters); minFilterNdx++)
+							for (int magFilterNdx = 0; magFilterNdx < DE_LENGTH_OF_ARRAY(magFilters); magFilterNdx++)
+							{
+								const tcu::Sampler::FilterMode		minFilter		= minFilters[minFilterNdx].filter;
+								const tcu::Sampler::FilterMode		magFilter		= magFilters[magFilterNdx].filter;
+								const tcu::Sampler::CompareMode		compareMode		= isDepthFormat(format) ? tcu::Sampler::COMPAREMODE_LESS : tcu::Sampler::COMPAREMODE_NONE;
+
+								if ((isUnormFormatType(format.type) || isDepthFormat(format)) && magFilter == tcu::Sampler::NEAREST)
+									continue; // Covered by other cases.
+								if ((isUIntFormatType(format.type) || isSIntFormatType(format.type)) &&
+									(magFilter != tcu::Sampler::NEAREST || minFilter != tcu::Sampler::NEAREST_MIPMAP_NEAREST))
+									continue;
+
+								const string caseName = string() + "min_" + minFilters[minFilterNdx].name + "_mag_" + magFilters[magFilterNdx].name;
+
+								filterModeGroup->addChild(makeTextureGatherCase(textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format, compareMode,
+																				tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, s_identityTextureSwizzle, minFilter, magFilter, 0, IVec3(64, 64, 3)));
+							}
+						}
+
+						{
+							TestCaseGroup* const baseLevelGroup = new TestCaseGroup(m_context, "base_level", "");
+							formatGroup->addChild(baseLevelGroup);
+
+							for (int baseLevel = 1; baseLevel <= 2; baseLevel++)
+							{
+								const string						caseName		= "level_" + de::toString(baseLevel);
+								const tcu::Sampler::CompareMode		compareMode		= isDepthFormat(format) ? tcu::Sampler::COMPAREMODE_LESS : tcu::Sampler::COMPAREMODE_NONE;
+								baseLevelGroup->addChild(makeTextureGatherCase(textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format,
+																			   compareMode, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+																			   s_identityTextureSwizzle, tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, baseLevel, IVec3(64, 64, 3)));
+							}
+						}
+
+						if (!isDepthFormat(format)) // What shadow textures should return for incomplete textures is unclear.
+						{
+							TestCaseGroup* const incompleteGroup = new TestCaseGroup(m_context, "incomplete", "Test that textureGather* takes components from (0,0,0,1) for incomplete textures");
+							formatGroup->addChild(incompleteGroup);
+
+							const tcu::Sampler::CompareMode compareMode = isDepthFormat(format) ? tcu::Sampler::COMPAREMODE_LESS : tcu::Sampler::COMPAREMODE_NONE;
+							incompleteGroup->addChild(makeTextureGatherCase(textureType, m_context, "mipmap_incomplete", "", gatherType, offsetSize, format,
+																			compareMode, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
+																			s_identityTextureSwizzle, tcu::Sampler::NEAREST_MIPMAP_NEAREST, tcu::Sampler::NEAREST, 0, IVec3(64, 64, 3),
+																			true /* Mipmap-incomplete */));
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fTextureGatherTests.hpp b/modules/gles31/functional/es31fTextureGatherTests.hpp
new file mode 100644
index 0000000..ba64566
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureGatherTests.hpp
@@ -0,0 +1,51 @@
+#ifndef _ES31FTEXTUREGATHERTESTS_HPP
+#define _ES31FTEXTUREGATHERTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLSL textureGather[Offset[s]] tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class TextureGatherTests : public TestCaseGroup
+{
+public:
+							TextureGatherTests	(Context& context);
+	void					init				(void);
+
+private:
+							TextureGatherTests	(const TextureGatherTests& other);
+	TextureGatherTests&		operator=			(const TextureGatherTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FTEXTUREGATHERTESTS_HPP
diff --git a/modules/gles31/functional/es31fTextureLevelStateQueryTests.cpp b/modules/gles31/functional/es31fTextureLevelStateQueryTests.cpp
new file mode 100644
index 0000000..bbd5119
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureLevelStateQueryTests.cpp
@@ -0,0 +1,1442 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture level state query tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fTextureLevelStateQueryTests.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "gluRenderContext.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+#include "tcuTextureUtil.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+enum VerifierType
+{
+	VERIFIER_INT = 0,
+	VERIFIER_FLOAT
+};
+
+struct TextureGenerationSpec
+{
+	struct TextureLevelSpec
+	{
+		int			width;
+		int			height;
+		int			depth;
+		int			level;
+		glw::GLenum internalFormat;
+		bool		compressed;
+	};
+
+	glw::GLenum						bindTarget;
+	glw::GLenum						queryTarget;
+	bool							immutable;
+	bool							fixedSamplePos;	// !< fixed sample pos argument for multisample textures
+	int								sampleCount;
+	std::vector<TextureLevelSpec>	levels;
+	std::string						description;
+};
+
+static bool textureTypeHasDepth (glw::GLenum textureBindTarget)
+{
+	switch (textureBindTarget)
+	{
+		case GL_TEXTURE_2D:						return false;
+		case GL_TEXTURE_3D:						return true;
+		case GL_TEXTURE_2D_ARRAY:				return true;
+		case GL_TEXTURE_CUBE_MAP:				return false;
+		case GL_TEXTURE_2D_MULTISAMPLE:			return false;
+		case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:	return true;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return false;
+	}
+}
+
+struct IntegerPrinter
+{
+	static std::string	getIntegerName	(int v)		{ return de::toString(v); }
+	static std::string	getFloatName	(float v)	{ return de::toString(v); }
+};
+
+struct PixelFormatPrinter
+{
+	static std::string	getIntegerName	(int v)		{ return de::toString(glu::getPixelFormatStr(v));		}
+	static std::string	getFloatName	(float v)	{ return de::toString(glu::getPixelFormatStr((int)v));	}
+};
+
+template <typename Printer>
+static bool verifyTextureLevelParameterEqualWithPrinter (glu::CallLogWrapper& gl, glw::GLenum target, int level, glw::GLenum pname, int refValue, VerifierType type)
+{
+	gl.getLog() << tcu::TestLog::Message << "Verifying " << glu::getTextureLevelParameterStr(pname) << ", expecting " << Printer::getIntegerName(refValue) << tcu::TestLog::EndMessage;
+
+	if (type == VERIFIER_INT)
+	{
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<int> result;
+
+		gl.glGetTexLevelParameteriv(target, level, pname, &result);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetTexLevelParameteriv");
+
+		if (result.isUndefined())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: Get* did not write a value." << tcu::TestLog::EndMessage;
+			return false;
+		}
+		else if (result.isMemoryContaminated())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: detected illegal memory write." << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		if (result == refValue)
+			return true;
+
+		gl.getLog() << tcu::TestLog::Message << "Error: Expected " << Printer::getIntegerName(refValue) << ", got " << Printer::getIntegerName(result) << tcu::TestLog::EndMessage;
+		return false;
+	}
+	else if (type == VERIFIER_FLOAT)
+	{
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<float> result;
+
+		gl.glGetTexLevelParameterfv(target, level, pname, &result);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetTexLevelParameterfv");
+
+		if (result.isUndefined())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: Get* did not write a value." << tcu::TestLog::EndMessage;
+			return false;
+		}
+		else if (result.isMemoryContaminated())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: detected illegal memory write." << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		if (result == (float)refValue)
+			return true;
+
+		gl.getLog() << tcu::TestLog::Message << "Error: Expected " << Printer::getIntegerName(refValue) << ", got " << Printer::getFloatName(result) << tcu::TestLog::EndMessage;
+		return false;
+	}
+
+	DE_ASSERT(DE_FALSE);
+	return false;
+}
+
+static bool verifyTextureLevelParameterEqual (glu::CallLogWrapper& gl, glw::GLenum target, int level, glw::GLenum pname, int refValue, VerifierType type)
+{
+	return verifyTextureLevelParameterEqualWithPrinter<IntegerPrinter>(gl, target, level, pname, refValue, type);
+}
+
+static bool verifyTextureLevelParameterInternalFormatEqual (glu::CallLogWrapper& gl, glw::GLenum target, int level, glw::GLenum pname, int refValue, VerifierType type)
+{
+	return verifyTextureLevelParameterEqualWithPrinter<PixelFormatPrinter>(gl, target, level, pname, refValue, type);
+}
+
+static bool verifyTextureLevelParameterGreaterOrEqual (glu::CallLogWrapper& gl, glw::GLenum target, int level, glw::GLenum pname, int refValue, VerifierType type)
+{
+	gl.getLog() << tcu::TestLog::Message << "Verifying " << glu::getTextureLevelParameterStr(pname) << ", expecting " << refValue << " or greater" << tcu::TestLog::EndMessage;
+
+	if (type == VERIFIER_INT)
+	{
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<int> result;
+
+		gl.glGetTexLevelParameteriv(target, level, pname, &result);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetTexLevelParameteriv");
+
+		if (result.isUndefined())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: Get* did not write a value." << tcu::TestLog::EndMessage;
+			return false;
+		}
+		else if (result.isMemoryContaminated())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: detected illegal memory write." << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		if (result >= refValue)
+			return true;
+
+		gl.getLog() << tcu::TestLog::Message << "Error: Expected " << refValue << " or larger, got " << result << tcu::TestLog::EndMessage;
+		return false;
+	}
+	else if (type == VERIFIER_FLOAT)
+	{
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<float> result;
+
+		gl.glGetTexLevelParameterfv(target, level, pname, &result);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetTexLevelParameterfv");
+
+		if (result.isUndefined())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: Get* did not write a value." << tcu::TestLog::EndMessage;
+			return false;
+		}
+		else if (result.isMemoryContaminated())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: detected illegal memory write." << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		if (result >= (float)refValue)
+			return true;
+
+		gl.getLog() << tcu::TestLog::Message << "Error: Expected " << refValue << " or larger, got " << result << tcu::TestLog::EndMessage;
+		return false;
+	}
+
+	DE_ASSERT(DE_FALSE);
+	return false;
+}
+
+static bool verifyTextureLevelParameterInternalFormatAnyOf (glu::CallLogWrapper& gl, glw::GLenum target, int level, glw::GLenum pname, const int* refValues, int numRefValues, VerifierType type)
+{
+	// Log what we try to do
+	{
+		tcu::MessageBuilder msg(&gl.getLog());
+
+		msg << "Verifying " << glu::getTextureLevelParameterStr(pname) << ", expecting any of {";
+		for (int ndx = 0; ndx < numRefValues; ++ndx)
+		{
+			if (ndx != 0)
+				msg << ", ";
+			msg << glu::getPixelFormatStr(refValues[ndx]);
+		}
+		msg << "}";
+		msg << tcu::TestLog::EndMessage;
+	}
+
+	// verify
+	if (type == VERIFIER_INT)
+	{
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<int> result;
+
+		gl.glGetTexLevelParameteriv(target, level, pname, &result);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetTexLevelParameteriv");
+
+		if (result.isUndefined())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: Get* did not write a value." << tcu::TestLog::EndMessage;
+			return false;
+		}
+		else if (result.isMemoryContaminated())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: detected illegal memory write." << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		for (int ndx = 0; ndx < numRefValues; ++ndx)
+			if (result == refValues[ndx])
+				return true;
+
+		gl.getLog() << tcu::TestLog::Message << "Error: got " << result << ", (" << glu::getPixelFormatStr(result) << ")" << tcu::TestLog::EndMessage;
+		return false;
+	}
+	else if (type == VERIFIER_FLOAT)
+	{
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<float> result;
+
+		gl.glGetTexLevelParameterfv(target, level, pname, &result);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetTexLevelParameterfv");
+
+		if (result.isUndefined())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: Get* did not write a value." << tcu::TestLog::EndMessage;
+			return false;
+		}
+		else if (result.isMemoryContaminated())
+		{
+			gl.getLog() << tcu::TestLog::Message << "Error: detected illegal memory write." << tcu::TestLog::EndMessage;
+			return false;
+		}
+
+		for (int ndx = 0; ndx < numRefValues; ++ndx)
+			if (result == (float)refValues[ndx])
+				return true;
+
+		gl.getLog() << tcu::TestLog::Message << "Error: got " << result << ", (" << glu::getPixelFormatStr((int)result) << ")" << tcu::TestLog::EndMessage;
+		return false;
+	}
+
+	DE_ASSERT(DE_FALSE);
+	return false;
+
+}
+
+static void generateColorTextureGenerationGroup (std::vector<TextureGenerationSpec>& group, int max2DSamples, int max2DArraySamples, glw::GLenum internalFormat)
+{
+	// initial setups
+	static const struct InitialSetup
+	{
+		glw::GLenum	bindTarget;
+		glw::GLenum	queryTarget;
+		bool		immutable;
+		const char*	description;
+	} initialSetups[] =
+	{
+		{ GL_TEXTURE_2D,					GL_TEXTURE_2D,					true,	"GL_TEXTURE_2D, initial values"						},
+		{ GL_TEXTURE_3D,					GL_TEXTURE_3D,					true,	"GL_TEXTURE_3D, initial values"						},
+		{ GL_TEXTURE_2D_ARRAY,				GL_TEXTURE_2D_ARRAY,			true,	"GL_TEXTURE_2D_ARRAY, initial values"				},
+		{ GL_TEXTURE_CUBE_MAP,				GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,	true,	"GL_TEXTURE_CUBE_MAP, initial values"				},
+		{ GL_TEXTURE_2D_MULTISAMPLE,		GL_TEXTURE_2D_MULTISAMPLE,		true,	"GL_TEXTURE_2D_MULTISAMPLE, initial values"			},
+		{ GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	GL_TEXTURE_2D_MULTISAMPLE_ARRAY,true,	"GL_TEXTURE_2D_MULTISAMPLE_ARRAY, initial values"	},
+	};
+
+	// multisample setups
+	static const struct MultisampleSetup
+	{
+		glw::GLenum	bindTarget;
+		int			sampleCount;
+		const char*	description;
+	} msSetups[] =
+	{
+		{ GL_TEXTURE_2D_MULTISAMPLE,		1,					"immutable GL_TEXTURE_2D_MULTISAMPLE, low sample count"			},
+		{ GL_TEXTURE_2D_MULTISAMPLE,		max2DSamples,		"immutable GL_TEXTURE_2D_MULTISAMPLE, max sample count"			},
+		{ GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	1,					"immutable GL_TEXTURE_2D_MULTISAMPLE_ARRAY, low sample count"	},
+		{ GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	max2DArraySamples,	"immutable GL_TEXTURE_2D_MULTISAMPLE_ARRAY, max sample count"	},
+	};
+
+	// normal setups
+	static const struct NormalSetup
+	{
+		glw::GLenum	bindTarget;
+		glw::GLenum	queryTarget;
+		bool		immutable;
+		int			level;
+		const char*	description;
+	} normalSetups[] =
+	{
+		{ GL_TEXTURE_2D,					GL_TEXTURE_2D,					true,	0,	"immutable GL_TEXTURE_2D"					},
+		{ GL_TEXTURE_3D,					GL_TEXTURE_3D,					true,	0,	"immutable GL_TEXTURE_3D"					},
+		{ GL_TEXTURE_2D_ARRAY,				GL_TEXTURE_2D_ARRAY,			true,	0,	"immutable GL_TEXTURE_2D_ARRAY"				},
+		{ GL_TEXTURE_CUBE_MAP,				GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,	true,	0,	"immutable GL_TEXTURE_CUBE_MAP"				},
+		{ GL_TEXTURE_2D,					GL_TEXTURE_2D,					false,	0,	"mutable GL_TEXTURE_2D"						},
+		{ GL_TEXTURE_3D,					GL_TEXTURE_3D,					false,	0,	"mutable GL_TEXTURE_3D"						},
+		{ GL_TEXTURE_2D_ARRAY,				GL_TEXTURE_2D_ARRAY,			false,	0,	"mutable GL_TEXTURE_2D_ARRAY"				},
+		{ GL_TEXTURE_CUBE_MAP,				GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,	false,	0,	"mutable GL_TEXTURE_CUBE_MAP"				},
+		{ GL_TEXTURE_2D,					GL_TEXTURE_2D,					false,	3,	"mutable GL_TEXTURE_2D, mip level 3"		},
+		{ GL_TEXTURE_3D,					GL_TEXTURE_3D,					false,	3,	"mutable GL_TEXTURE_3D, mip level 3"		},
+		{ GL_TEXTURE_2D_ARRAY,				GL_TEXTURE_2D_ARRAY,			false,	3,	"mutable GL_TEXTURE_2D_ARRAY, mip level 3"	},
+		{ GL_TEXTURE_CUBE_MAP,				GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,	false,	3,	"mutable GL_TEXTURE_CUBE_MAP, mip level 3"	},
+	};
+
+	for (int initialSetupNdx = 0; initialSetupNdx < DE_LENGTH_OF_ARRAY(initialSetups); ++initialSetupNdx)
+	{
+		TextureGenerationSpec texGen;
+		texGen.bindTarget		= initialSetups[initialSetupNdx].bindTarget;
+		texGen.queryTarget		= initialSetups[initialSetupNdx].queryTarget;
+		texGen.immutable		= initialSetups[initialSetupNdx].immutable;
+		texGen.sampleCount		= 0;
+		texGen.description		= initialSetups[initialSetupNdx].description;
+
+		group.push_back(texGen);
+	}
+
+	for (int msSetupNdx = 0; msSetupNdx < DE_LENGTH_OF_ARRAY(msSetups); ++msSetupNdx)
+	{
+		TextureGenerationSpec					texGen;
+		TextureGenerationSpec::TextureLevelSpec	level;
+
+		texGen.bindTarget		= msSetups[msSetupNdx].bindTarget;
+		texGen.queryTarget		= msSetups[msSetupNdx].bindTarget;
+		texGen.immutable		= true;
+		texGen.sampleCount		= msSetups[msSetupNdx].sampleCount;
+		texGen.fixedSamplePos	= false;
+		texGen.description		= msSetups[msSetupNdx].description;
+
+		level.width				= 32;
+		level.height			= 32;
+		level.depth				= (textureTypeHasDepth(texGen.bindTarget)) ? (8) : (0);
+		level.level				= 0;
+		level.internalFormat	= internalFormat;
+		level.compressed		= false;
+
+		texGen.levels.push_back(level);
+		group.push_back(texGen);
+	}
+
+	for (int normalSetupNdx = 0; normalSetupNdx < DE_LENGTH_OF_ARRAY(normalSetups); ++normalSetupNdx)
+	{
+		TextureGenerationSpec					texGen;
+		TextureGenerationSpec::TextureLevelSpec	level;
+
+		texGen.bindTarget		= normalSetups[normalSetupNdx].bindTarget;
+		texGen.queryTarget		= normalSetups[normalSetupNdx].queryTarget;
+		texGen.immutable		= normalSetups[normalSetupNdx].immutable;
+		texGen.sampleCount		= 0;
+		texGen.description		= normalSetups[normalSetupNdx].description;
+
+		level.width				= 32;
+		level.height			= 32;
+		level.depth				= (textureTypeHasDepth(texGen.bindTarget)) ? (8) : (0);
+		level.level				= normalSetups[normalSetupNdx].level;
+		level.internalFormat	= internalFormat;
+		level.compressed		= false;
+
+		texGen.levels.push_back(level);
+		group.push_back(texGen);
+	}
+}
+
+static void generateColorMultisampleTextureGenerationGroup (std::vector<TextureGenerationSpec>& group, int max2DSamples, int max2DArraySamples, glw::GLenum internalFormat)
+{
+	// multisample setups
+	static const struct MultisampleSetup
+	{
+		glw::GLenum	bindTarget;
+		bool		initialized;
+		int			sampleCount;
+		bool		fixedSamples;
+		const char*	description;
+	} msSetups[] =
+	{
+		{ GL_TEXTURE_2D_MULTISAMPLE,		false,	0,					false,	"GL_TEXTURE_2D_MULTISAMPLE, initial values"					},
+		{ GL_TEXTURE_2D_MULTISAMPLE,		true,	1,					false,	"GL_TEXTURE_2D_MULTISAMPLE, low sample count"				},
+		{ GL_TEXTURE_2D_MULTISAMPLE,		true,	max2DSamples,		false,	"GL_TEXTURE_2D_MULTISAMPLE, max sample count"				},
+		{ GL_TEXTURE_2D_MULTISAMPLE,		true,	max2DSamples,		true,	"GL_TEXTURE_2D_MULTISAMPLE, fixed sample positions"			},
+		{ GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	false,	0,					false,	"GL_TEXTURE_2D_MULTISAMPLE_ARRAY, initial values"			},
+		{ GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	true,	1,					false,	"GL_TEXTURE_2D_MULTISAMPLE_ARRAY, low sample count"			},
+		{ GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	true,	max2DArraySamples,	false,	"GL_TEXTURE_2D_MULTISAMPLE_ARRAY, max sample count"			},
+		{ GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	true,	max2DArraySamples,	true,	"GL_TEXTURE_2D_MULTISAMPLE_ARRAY, fixed sample positions"	},
+	};
+
+	for (int msSetupNdx = 0; msSetupNdx < DE_LENGTH_OF_ARRAY(msSetups); ++msSetupNdx)
+	{
+		TextureGenerationSpec texGen;
+
+		texGen.bindTarget		= msSetups[msSetupNdx].bindTarget;
+		texGen.queryTarget		= msSetups[msSetupNdx].bindTarget;
+		texGen.immutable		= true;
+		texGen.sampleCount		= msSetups[msSetupNdx].sampleCount;
+		texGen.fixedSamplePos	= msSetups[msSetupNdx].fixedSamples;
+		texGen.description		= msSetups[msSetupNdx].description;
+
+		if (msSetups[msSetupNdx].initialized)
+		{
+			TextureGenerationSpec::TextureLevelSpec	level;
+			level.width				= 32;
+			level.height			= 32;
+			level.depth				= (textureTypeHasDepth(texGen.bindTarget)) ? (8) : (0);
+			level.level				= 0;
+			level.internalFormat	= internalFormat;
+			level.compressed		= false;
+
+			texGen.levels.push_back(level);
+		}
+
+		group.push_back(texGen);
+	}
+}
+
+static void generateInternalFormatTextureGenerationGroup (std::vector<TextureGenerationSpec>& group)
+{
+	// initial setups
+	static const struct InitialSetup
+	{
+		glw::GLenum	bindTarget;
+		glw::GLenum	queryTarget;
+		bool		immutable;
+		const char*	description;
+	} initialSetups[] =
+	{
+		{ GL_TEXTURE_2D,					GL_TEXTURE_2D,					true,	"GL_TEXTURE_2D, initial values"						},
+		{ GL_TEXTURE_3D,					GL_TEXTURE_3D,					true,	"GL_TEXTURE_3D, initial values"						},
+		{ GL_TEXTURE_2D_ARRAY,				GL_TEXTURE_2D_ARRAY,			true,	"GL_TEXTURE_2D_ARRAY, initial values"				},
+		{ GL_TEXTURE_CUBE_MAP,				GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,	true,	"GL_TEXTURE_CUBE_MAP, initial values"				},
+		{ GL_TEXTURE_2D_MULTISAMPLE,		GL_TEXTURE_2D_MULTISAMPLE,		true,	"GL_TEXTURE_2D_MULTISAMPLE, initial values"			},
+		{ GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	GL_TEXTURE_2D_MULTISAMPLE_ARRAY,true,	"GL_TEXTURE_2D_MULTISAMPLE_ARRAY, initial values"	},
+	};
+
+	// Renderable internal formats (subset)
+	static const glw::GLenum renderableInternalFormats[] =
+	{
+		GL_R8, GL_RGB565, GL_RGB5_A1, GL_RGB10_A2UI, GL_SRGB8_ALPHA8, GL_RG32I,
+		GL_RGBA16UI, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24,
+		GL_DEPTH_COMPONENT32F, GL_DEPTH24_STENCIL8, GL_DEPTH32F_STENCIL8
+	};
+
+	// Internal formats
+	static const glw::GLenum internalFormats[] =
+	{
+		GL_R8, GL_R8_SNORM, GL_RG8, GL_RG8_SNORM, GL_RGB8, GL_RGB8_SNORM, GL_RGB565, GL_RGBA4, GL_RGB5_A1,
+		GL_RGBA8, GL_RGBA8_SNORM, GL_RGB10_A2, GL_RGB10_A2UI, GL_SRGB8, GL_SRGB8_ALPHA8, GL_R16F, GL_RG16F,
+		GL_RGB16F, GL_RGBA16F, GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F, GL_R11F_G11F_B10F, GL_RGB9_E5, GL_R8I,
+		GL_R8UI, GL_R16I, GL_R16UI, GL_R32I, GL_R32UI, GL_RG8I, GL_RG8UI, GL_RG16I, GL_RG16UI, GL_RG32I, GL_RG32UI,
+		GL_RGB8I, GL_RGB8UI, GL_RGB16I, GL_RGB16UI, GL_RGB32I, GL_RGB32UI, GL_RGBA8I, GL_RGBA8UI, GL_RGBA16I,
+		GL_RGBA16UI, GL_RGBA32I, GL_RGBA32UI,
+
+		GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT16,
+		GL_DEPTH32F_STENCIL8, GL_DEPTH24_STENCIL8
+	};
+
+	for (int initialSetupNdx = 0; initialSetupNdx < DE_LENGTH_OF_ARRAY(initialSetups); ++initialSetupNdx)
+	{
+		TextureGenerationSpec texGen;
+		texGen.bindTarget		= initialSetups[initialSetupNdx].bindTarget;
+		texGen.queryTarget		= initialSetups[initialSetupNdx].queryTarget;
+		texGen.immutable		= initialSetups[initialSetupNdx].immutable;
+		texGen.sampleCount		= 0;
+		texGen.description		= initialSetups[initialSetupNdx].description;
+
+		group.push_back(texGen);
+	}
+
+	// test some color/stencil/depth renderable with multisample texture2d
+	for (int internalFormatNdx = 0; internalFormatNdx < DE_LENGTH_OF_ARRAY(renderableInternalFormats); ++internalFormatNdx)
+	{
+		TextureGenerationSpec					texGen;
+		TextureGenerationSpec::TextureLevelSpec	level;
+
+		texGen.bindTarget		= GL_TEXTURE_2D_MULTISAMPLE;
+		texGen.queryTarget		= GL_TEXTURE_2D_MULTISAMPLE;
+		texGen.immutable		= true;
+		texGen.sampleCount		= 1;
+		texGen.fixedSamplePos	= false;
+		texGen.description		= std::string() + "GL_TEXTURE_2D_MULTISAMPLE, internal format " + glu::getPixelFormatName(renderableInternalFormats[internalFormatNdx]);
+
+		level.width				= 32;
+		level.height			= 32;
+		level.depth				= 0;
+		level.level				= 0;
+		level.internalFormat	= renderableInternalFormats[internalFormatNdx];
+		level.compressed		= false;
+
+		texGen.levels.push_back(level);
+		group.push_back(texGen);
+	}
+
+	// test all with texture2d
+	for (int internalFormatNdx = 0; internalFormatNdx < DE_LENGTH_OF_ARRAY(internalFormats); ++internalFormatNdx)
+	{
+		TextureGenerationSpec					texGen;
+		TextureGenerationSpec::TextureLevelSpec	level;
+
+		texGen.bindTarget		= GL_TEXTURE_2D;
+		texGen.queryTarget		= GL_TEXTURE_2D;
+		texGen.immutable		= true;
+		texGen.sampleCount		= 0;
+		texGen.description		= std::string() + "GL_TEXTURE_2D, internal format " + glu::getPixelFormatName(internalFormats[internalFormatNdx]);
+
+		level.width				= 32;
+		level.height			= 32;
+		level.depth				= 0;
+		level.level				= 0;
+		level.internalFormat	= internalFormats[internalFormatNdx];
+		level.compressed		= false;
+
+		texGen.levels.push_back(level);
+		group.push_back(texGen);
+	}
+
+	// test rgba8 with mip level 3
+	{
+		TextureGenerationSpec					texGen;
+		TextureGenerationSpec::TextureLevelSpec	level;
+
+		texGen.bindTarget		= GL_TEXTURE_2D;
+		texGen.queryTarget		= GL_TEXTURE_2D;
+		texGen.immutable		= false;
+		texGen.sampleCount		= 0;
+		texGen.description		= std::string() + "GL_TEXTURE_2D, internal format GL_RGBA8";
+
+		level.width				= 32;
+		level.height			= 32;
+		level.depth				= 0;
+		level.level				= 3;
+		level.internalFormat	= GL_RGBA8;
+		level.compressed		= false;
+
+		texGen.levels.push_back(level);
+		group.push_back(texGen);
+	}
+}
+
+static void generateCompressedTextureGenerationGroup (std::vector<TextureGenerationSpec>& group)
+{
+	// initial ms
+	{
+		TextureGenerationSpec texGen;
+		texGen.bindTarget	= GL_TEXTURE_2D_MULTISAMPLE;
+		texGen.queryTarget	= GL_TEXTURE_2D_MULTISAMPLE;
+		texGen.immutable	= true;
+		texGen.sampleCount	= 0;
+		texGen.description	= "GL_TEXTURE_2D_MULTISAMPLE, initial values";
+
+		group.push_back(texGen);
+	}
+
+	// initial non-ms
+	{
+		TextureGenerationSpec texGen;
+		texGen.bindTarget	= GL_TEXTURE_2D;
+		texGen.queryTarget	= GL_TEXTURE_2D;
+		texGen.immutable	= true;
+		texGen.sampleCount	= 0;
+		texGen.description	= "GL_TEXTURE_2D, initial values";
+
+		group.push_back(texGen);
+	}
+
+	// compressed
+	{
+		TextureGenerationSpec					texGen;
+		TextureGenerationSpec::TextureLevelSpec	level;
+
+		texGen.bindTarget		= GL_TEXTURE_2D;
+		texGen.queryTarget		= GL_TEXTURE_2D;
+		texGen.immutable		= false;
+		texGen.sampleCount		= 0;
+		texGen.description		= "GL_TEXTURE_2D, compressed";
+
+		level.width				= 32;
+		level.height			= 32;
+		level.depth				= 0;
+		level.level				= 0;
+		level.internalFormat	= GL_COMPRESSED_RGB8_ETC2;
+		level.compressed		= true;
+
+		texGen.levels.push_back(level);
+		group.push_back(texGen);
+	}
+}
+
+void applyTextureGenergationSpec (glu::CallLogWrapper& gl, const TextureGenerationSpec& spec)
+{
+	DE_ASSERT(!(spec.immutable && spec.levels.size() > 1));		// !< immutable textures have only one level
+
+	for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
+	{
+		const glu::TransferFormat transferFormat = (spec.levels[levelNdx].compressed) ? (glu::TransferFormat()) : (glu::getTransferFormat(glu::mapGLInternalFormat(spec.levels[levelNdx].internalFormat)));
+
+		if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D)
+			gl.glTexStorage2D(spec.bindTarget, 1, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height);
+		else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_3D)
+			gl.glTexStorage3D(spec.bindTarget, 1, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height, spec.levels[levelNdx].depth);
+		else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D_ARRAY)
+			gl.glTexStorage3D(spec.bindTarget, 1, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height, spec.levels[levelNdx].depth);
+		else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_CUBE_MAP)
+			gl.glTexStorage2D(spec.bindTarget, 1, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height);
+		else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D_MULTISAMPLE)
+			gl.glTexStorage2DMultisample(spec.bindTarget, spec.sampleCount, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height, (spec.fixedSamplePos) ? (GL_TRUE) : (GL_FALSE));
+		else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY)
+			gl.glTexStorage3DMultisample(spec.bindTarget, spec.sampleCount, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height, spec.levels[levelNdx].depth, (spec.fixedSamplePos) ? (GL_TRUE) : (GL_FALSE));
+		else if (!spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D)
+			gl.glTexImage2D(spec.bindTarget, spec.levels[levelNdx].level, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height, 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+		else if (!spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_3D)
+			gl.glTexImage3D(spec.bindTarget, spec.levels[levelNdx].level, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height, spec.levels[levelNdx].depth, 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+		else if (!spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D_ARRAY)
+			gl.glTexImage3D(spec.bindTarget, spec.levels[levelNdx].level, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height, spec.levels[levelNdx].depth, 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+		else if (!spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_CUBE_MAP)
+			gl.glTexImage2D(spec.queryTarget, spec.levels[levelNdx].level, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height, 0, transferFormat.format, transferFormat.dataType, DE_NULL);
+		else if (!spec.immutable && spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D)
+		{
+			DE_ASSERT(spec.levels[levelNdx].width == 32);
+			DE_ASSERT(spec.levels[levelNdx].height == 32);
+			DE_ASSERT(spec.levels[levelNdx].internalFormat == GL_COMPRESSED_RGB8_ETC2);
+
+			static const deUint8 buffer[64 * 8] = { 0 };
+			gl.glCompressedTexImage2D(spec.bindTarget, spec.levels[levelNdx].level, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width, spec.levels[levelNdx].height, 0, sizeof(buffer), buffer);
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "set level");
+	}
+}
+
+class TextureLevelCase : public TestCase
+{
+public:
+										TextureLevelCase		(Context& ctx, const char* name, const char* desc, VerifierType type);
+										~TextureLevelCase		(void);
+
+	void								init					(void);
+	void								deinit					(void);
+	IterateResult						iterate					(void);
+
+protected:
+	void								getFormatSamples		(glw::GLenum target, std::vector<int>& samples);
+	bool								testConfig				(const TextureGenerationSpec& spec);
+	virtual bool						checkTextureState		(glu::CallLogWrapper& gl, const TextureGenerationSpec& spec) = 0;
+	virtual void						generateTestIterations	(std::vector<TextureGenerationSpec>& iterations) = 0;
+
+	const VerifierType					m_type;
+	const glw::GLenum					m_internalFormat;
+	glw::GLuint							m_texture;
+
+private:
+	int									m_iteration;
+	std::vector<TextureGenerationSpec>	m_iterations;
+	bool								m_allIterationsOk;
+	std::vector<int>					m_failedIterations;
+};
+
+TextureLevelCase::TextureLevelCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+	: TestCase			(ctx, name, desc)
+	, m_type			(type)
+	, m_internalFormat	(GL_RGBA8)
+	, m_texture			(0)
+	, m_iteration		(0)
+	, m_allIterationsOk	(true)
+{
+}
+
+TextureLevelCase::~TextureLevelCase (void)
+{
+	deinit();
+}
+
+void TextureLevelCase::init (void)
+{
+	generateTestIterations(m_iterations);
+}
+
+void TextureLevelCase::deinit (void)
+{
+	if (m_texture)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texture);
+		m_texture = 0;
+	}
+}
+
+void TextureLevelCase::getFormatSamples (glw::GLenum target, std::vector<int>& samples)
+{
+	const glw::Functions	gl			= m_context.getRenderContext().getFunctions();
+	int						sampleCount	= -1;
+
+	// fake values for unsupported queries to simplify code. The extension will be checked later for each config anyway (in testConfig())
+	if (target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
+	{
+		samples.resize(1);
+		samples[0] = 0;
+		return;
+	}
+
+	gl.getInternalformativ(target, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &sampleCount);
+
+	if (sampleCount < 0)
+		throw tcu::TestError("internal format query failed");
+
+	samples.resize(sampleCount);
+
+	if (sampleCount > 0)
+	{
+		gl.getInternalformativ(target, m_internalFormat, GL_SAMPLES, sampleCount, &samples[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "get max samples");
+	}
+}
+
+TextureLevelCase::IterateResult TextureLevelCase::iterate (void)
+{
+	const bool result = testConfig(m_iterations[m_iteration]);
+
+	if (!result)
+	{
+		m_failedIterations.push_back(m_iteration);
+		m_allIterationsOk = false;
+	}
+
+	if (++m_iteration < (int)m_iterations.size())
+		return CONTINUE;
+
+	if (m_allIterationsOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+	{
+		tcu::MessageBuilder msg(&m_testCtx.getLog());
+
+		msg << "Following iteration(s) failed: ";
+		for (int ndx = 0; ndx < (int)m_failedIterations.size(); ++ndx)
+		{
+			if (ndx)
+				msg << ", ";
+			msg << (m_failedIterations[ndx] + 1);
+		}
+		msg << tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "One or more iterations failed");
+	}
+	return STOP;
+}
+
+bool TextureLevelCase::testConfig (const TextureGenerationSpec& spec)
+{
+	const tcu::ScopedLogSection section(m_testCtx.getLog(), "Iteration", std::string() + "Iteration " + de::toString(m_iteration+1) + "/" + de::toString((int)m_iterations.size()) + " - " + spec.description);
+	glu::CallLogWrapper			gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	bool						result;
+
+	// skip unsupported targets
+
+	if (spec.bindTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Target binding requires GL_OES_texture_storage_multisample_2d_array extension, skipping." << tcu::TestLog::EndMessage;
+		return true;
+	}
+
+	// test supported targets
+
+	gl.enableLogging(true);
+
+	gl.glGenTextures(1, &m_texture);
+	gl.glBindTexture(spec.bindTarget, m_texture);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");
+
+	// Set the state
+	applyTextureGenergationSpec(gl, spec);
+
+	// Verify the state
+	result = checkTextureState(gl, spec);
+
+	gl.glDeleteTextures(1, &m_texture);
+	m_texture = 0;
+
+	return result;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test all texture targets
+ *//*--------------------------------------------------------------------*/
+class TextureLevelCommonCase : public TextureLevelCase
+{
+public:
+					TextureLevelCommonCase	(Context& ctx, const char* name, const char* desc, VerifierType type);
+
+protected:
+	virtual void	generateTestIterations	(std::vector<TextureGenerationSpec>& iterations);
+};
+
+TextureLevelCommonCase::TextureLevelCommonCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+	: TextureLevelCase(ctx, name, desc, type)
+{
+}
+
+void TextureLevelCommonCase::generateTestIterations (std::vector<TextureGenerationSpec>& iterations)
+{
+	std::vector<int> texture2DSamples;
+	std::vector<int> texture2DArraySamples;
+
+	getFormatSamples(GL_TEXTURE_2D_MULTISAMPLE, texture2DSamples);
+	getFormatSamples(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, texture2DArraySamples);
+
+	TCU_CHECK(!texture2DSamples.empty());
+	TCU_CHECK(!texture2DArraySamples.empty());
+
+	// gen iterations
+
+	generateColorTextureGenerationGroup(iterations, texture2DSamples[0], texture2DArraySamples[0], m_internalFormat);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Test all multisample texture targets
+ *//*--------------------------------------------------------------------*/
+class TextureLevelMultisampleCase : public TextureLevelCase
+{
+public:
+					TextureLevelMultisampleCase	(Context& ctx, const char* name, const char* desc, VerifierType type);
+
+protected:
+	virtual void	generateTestIterations		(std::vector<TextureGenerationSpec>& iterations);
+};
+
+TextureLevelMultisampleCase::TextureLevelMultisampleCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+	: TextureLevelCase(ctx, name, desc, type)
+{
+}
+
+void TextureLevelMultisampleCase::generateTestIterations (std::vector<TextureGenerationSpec>& iterations)
+{
+	std::vector<int> texture2DSamples;
+	std::vector<int> texture2DArraySamples;
+
+	getFormatSamples(GL_TEXTURE_2D_MULTISAMPLE, texture2DSamples);
+	getFormatSamples(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, texture2DArraySamples);
+
+	TCU_CHECK(!texture2DSamples.empty());
+	TCU_CHECK(!texture2DArraySamples.empty());
+
+	// gen iterations
+
+	generateColorMultisampleTextureGenerationGroup(iterations, texture2DSamples[0], texture2DArraySamples[0], m_internalFormat);
+}
+
+class TextureLevelSampleCase : public TextureLevelMultisampleCase
+{
+public:
+	TextureLevelSampleCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+		: TextureLevelMultisampleCase(ctx, name, desc, type)
+	{
+	}
+
+private:
+	bool checkTextureState (glu::CallLogWrapper& gl, const TextureGenerationSpec& spec)
+	{
+		const int queryLevel	= (spec.levels.empty()) ? (0) : (spec.levels[0].level);
+		const int refValue		= (spec.levels.empty()) ? (0) : (spec.sampleCount);
+
+		return verifyTextureLevelParameterGreaterOrEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_SAMPLES, refValue, m_type);
+	}
+};
+
+class TextureLevelFixedSamplesCase : public TextureLevelMultisampleCase
+{
+public:
+	TextureLevelFixedSamplesCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+		: TextureLevelMultisampleCase(ctx, name, desc, type)
+	{
+	}
+
+private:
+	bool checkTextureState (glu::CallLogWrapper& gl, const TextureGenerationSpec& spec)
+	{
+		const int queryLevel	= 0;
+		const int refValue		= (spec.levels.empty()) ? (1) : ((spec.fixedSamplePos) ? (1) : (0));
+
+		return verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_FIXED_SAMPLE_LOCATIONS, refValue, m_type);
+	}
+};
+
+class TextureLevelWidthCase : public TextureLevelCommonCase
+{
+public:
+	TextureLevelWidthCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+		: TextureLevelCommonCase(ctx, name, desc, type)
+	{
+	}
+
+private:
+	bool checkTextureState (glu::CallLogWrapper& gl, const TextureGenerationSpec& spec)
+	{
+		const int	initialValue	= 0;
+		bool		allOk			= true;
+
+		if (spec.levels.empty())
+		{
+			const int queryLevel	= 0;
+			const int refValue		= initialValue;
+
+			allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_WIDTH, refValue, m_type);
+		}
+		else
+		{
+			for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
+			{
+				const int queryLevel	= spec.levels[levelNdx].level;
+				const int refValue		= spec.levels[levelNdx].width;
+
+				allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_WIDTH, refValue, m_type);
+			}
+		}
+
+		return allOk;
+	}
+};
+
+class TextureLevelHeightCase : public TextureLevelCommonCase
+{
+public:
+	TextureLevelHeightCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+		: TextureLevelCommonCase(ctx, name, desc, type)
+	{
+	}
+
+private:
+	bool checkTextureState (glu::CallLogWrapper& gl, const TextureGenerationSpec& spec)
+	{
+		const int	initialValue	= 0;
+		bool		allOk			= true;
+
+		if (spec.levels.empty())
+		{
+			const int queryLevel	= 0;
+			const int refValue		= initialValue;
+
+			allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_HEIGHT, refValue, m_type);
+		}
+		else
+		{
+			for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
+			{
+				const int queryLevel	= spec.levels[levelNdx].level;
+				const int refValue		= spec.levels[levelNdx].height;
+
+				allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_HEIGHT, refValue, m_type);
+			}
+		}
+
+		return allOk;
+	}
+};
+
+class TextureLevelDepthCase : public TextureLevelCommonCase
+{
+public:
+	TextureLevelDepthCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+		: TextureLevelCommonCase(ctx, name, desc, type)
+	{
+	}
+
+private:
+
+	void generateTestIterations (std::vector<TextureGenerationSpec>& iterations)
+	{
+		std::vector<TextureGenerationSpec> allIterations;
+		this->TextureLevelCommonCase::generateTestIterations(allIterations);
+
+		// test only cases with depth
+		for (int ndx = 0; ndx < (int)allIterations.size(); ++ndx)
+			if (textureTypeHasDepth(allIterations[ndx].bindTarget))
+				iterations.push_back(allIterations[ndx]);
+	}
+
+	bool checkTextureState (glu::CallLogWrapper& gl, const TextureGenerationSpec& spec)
+	{
+		const int	initialValue	= 0;
+		bool		allOk			= true;
+
+		if (spec.levels.empty())
+		{
+			const int queryLevel	= 0;
+			const int refValue		= initialValue;
+
+			allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_DEPTH, refValue, m_type);
+		}
+		else
+		{
+			for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
+			{
+				const int queryLevel	= spec.levels[levelNdx].level;
+				const int refValue		= spec.levels[levelNdx].depth;
+
+				allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_DEPTH, refValue, m_type);
+			}
+		}
+
+		return allOk;
+	}
+};
+
+class TextureLevelInternalFormatCase : public TextureLevelCase
+{
+public:
+	TextureLevelInternalFormatCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+		: TextureLevelCase(ctx, name, desc, type)
+	{
+	}
+
+private:
+	void generateTestIterations (std::vector<TextureGenerationSpec>& iterations)
+	{
+		generateInternalFormatTextureGenerationGroup(iterations);
+	}
+
+	bool checkTextureState (glu::CallLogWrapper& gl, const TextureGenerationSpec& spec)
+	{
+		bool allOk = true;
+
+		if (spec.levels.empty())
+		{
+			const int queryLevel		= 0;
+			const int initialValues[2]	= { GL_RGBA, GL_R8 };
+
+			allOk &= verifyTextureLevelParameterInternalFormatAnyOf(gl, spec.queryTarget, queryLevel, GL_TEXTURE_INTERNAL_FORMAT, initialValues, DE_LENGTH_OF_ARRAY(initialValues), m_type);
+		}
+		else
+		{
+			for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
+			{
+				const int queryLevel	= spec.levels[levelNdx].level;
+				const int refValue		= spec.levels[levelNdx].internalFormat;
+
+				allOk &= verifyTextureLevelParameterInternalFormatEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_INTERNAL_FORMAT, refValue, m_type);
+			}
+		}
+
+		return allOk;
+	}
+};
+
+class TextureLevelSizeCase : public TextureLevelCase
+{
+public:
+						TextureLevelSizeCase			(Context& ctx, const char* name, const char* desc, VerifierType type, glw::GLenum pname);
+
+private:
+	void				generateTestIterations			(std::vector<TextureGenerationSpec>& iterations);
+	bool				checkTextureState				(glu::CallLogWrapper& gl, const TextureGenerationSpec& spec);
+	int					getMinimumComponentResolution	(glw::GLenum internalFormat);
+
+	const glw::GLenum	m_pname;
+};
+
+TextureLevelSizeCase::TextureLevelSizeCase (Context& ctx, const char* name, const char* desc, VerifierType type, glw::GLenum pname)
+	: TextureLevelCase	(ctx, name, desc, type)
+	, m_pname			(pname)
+{
+}
+
+void TextureLevelSizeCase::generateTestIterations (std::vector<TextureGenerationSpec>& iterations)
+{
+	generateInternalFormatTextureGenerationGroup(iterations);
+}
+
+bool TextureLevelSizeCase::checkTextureState (glu::CallLogWrapper& gl, const TextureGenerationSpec& spec)
+{
+	bool allOk = true;
+
+	if (spec.levels.empty())
+	{
+		allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, m_pname, 0, m_type);
+	}
+	else
+	{
+		for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
+		{
+			const int queryLevel	= spec.levels[levelNdx].level;
+			const int refValue		= getMinimumComponentResolution(spec.levels[levelNdx].internalFormat);
+
+			allOk &= verifyTextureLevelParameterGreaterOrEqual(gl, spec.queryTarget, queryLevel, m_pname, refValue, m_type);
+		}
+	}
+
+	return allOk;
+}
+
+int TextureLevelSizeCase::getMinimumComponentResolution (glw::GLenum internalFormat)
+{
+	const tcu::TextureFormat	format			= glu::mapGLInternalFormat(internalFormat);
+	const tcu::IVec4			channelBitDepth	= tcu::getTextureFormatBitDepth(format);
+
+	switch (m_pname)
+	{
+		case GL_TEXTURE_RED_SIZE:
+			if (format.order == tcu::TextureFormat::R		||
+				format.order == tcu::TextureFormat::RG		||
+				format.order == tcu::TextureFormat::RGB		||
+				format.order == tcu::TextureFormat::RGBA	||
+				format.order == tcu::TextureFormat::BGRA	||
+				format.order == tcu::TextureFormat::ARGB	||
+				format.order == tcu::TextureFormat::sRGB	||
+				format.order == tcu::TextureFormat::sRGBA)
+				return channelBitDepth[0];
+			else
+				return 0;
+
+		case GL_TEXTURE_GREEN_SIZE:
+			if (format.order == tcu::TextureFormat::RG		||
+				format.order == tcu::TextureFormat::RGB		||
+				format.order == tcu::TextureFormat::RGBA	||
+				format.order == tcu::TextureFormat::BGRA	||
+				format.order == tcu::TextureFormat::ARGB	||
+				format.order == tcu::TextureFormat::sRGB	||
+				format.order == tcu::TextureFormat::sRGBA)
+				return channelBitDepth[1];
+			else
+				return 0;
+
+		case GL_TEXTURE_BLUE_SIZE:
+			if (format.order == tcu::TextureFormat::RGB		||
+				format.order == tcu::TextureFormat::RGBA	||
+				format.order == tcu::TextureFormat::BGRA	||
+				format.order == tcu::TextureFormat::ARGB	||
+				format.order == tcu::TextureFormat::sRGB	||
+				format.order == tcu::TextureFormat::sRGBA)
+				return channelBitDepth[2];
+			else
+				return 0;
+
+		case GL_TEXTURE_ALPHA_SIZE:
+			if (format.order == tcu::TextureFormat::RGBA	||
+				format.order == tcu::TextureFormat::BGRA	||
+				format.order == tcu::TextureFormat::ARGB	||
+				format.order == tcu::TextureFormat::sRGBA)
+				return channelBitDepth[3];
+			else
+				return 0;
+
+		case GL_TEXTURE_DEPTH_SIZE:
+			if (format.order == tcu::TextureFormat::D	||
+				format.order == tcu::TextureFormat::DS)
+				return channelBitDepth[0];
+			else
+				return 0;
+
+		case GL_TEXTURE_STENCIL_SIZE:
+			if (format.order == tcu::TextureFormat::DS)
+				return channelBitDepth[3];
+			else
+				return 0;
+
+		case GL_TEXTURE_SHARED_SIZE:
+			if (internalFormat == GL_RGB9_E5)
+				return 5;
+			else
+				return 0;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+class TextureLevelTypeCase : public TextureLevelCase
+{
+public:
+						TextureLevelTypeCase			(Context& ctx, const char* name, const char* desc, VerifierType type, glw::GLenum pname);
+
+private:
+	void				generateTestIterations			(std::vector<TextureGenerationSpec>& iterations);
+	bool				checkTextureState				(glu::CallLogWrapper& gl, const TextureGenerationSpec& spec);
+	int					getComponentType				(glw::GLenum internalFormat);
+
+	const glw::GLenum	m_pname;
+};
+
+TextureLevelTypeCase::TextureLevelTypeCase (Context& ctx, const char* name, const char* desc, VerifierType type, glw::GLenum pname)
+	: TextureLevelCase	(ctx, name, desc, type)
+	, m_pname			(pname)
+{
+}
+
+void TextureLevelTypeCase::generateTestIterations (std::vector<TextureGenerationSpec>& iterations)
+{
+	generateInternalFormatTextureGenerationGroup(iterations);
+}
+
+bool TextureLevelTypeCase::checkTextureState (glu::CallLogWrapper& gl, const TextureGenerationSpec& spec)
+{
+	bool allOk = true;
+
+	if (spec.levels.empty())
+	{
+		allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, m_pname, GL_NONE, m_type);
+	}
+	else
+	{
+		for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
+		{
+			const int queryLevel	= spec.levels[levelNdx].level;
+			const int refValue		= getComponentType(spec.levels[levelNdx].internalFormat);
+
+			allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, m_pname, refValue, m_type);
+		}
+	}
+
+	return allOk;
+}
+
+int TextureLevelTypeCase::getComponentType (glw::GLenum internalFormat)
+{
+	const tcu::TextureFormat		format			= glu::mapGLInternalFormat(internalFormat);
+	const tcu::TextureChannelClass	channelClass	= tcu::getTextureChannelClass(format.type);
+	glw::GLenum						channelType		= GL_NONE;
+
+	// depth-stencil special cases
+	if (format.type == tcu::TextureFormat::UNSIGNED_INT_24_8)
+	{
+		if (m_pname == GL_TEXTURE_DEPTH_TYPE)
+			return GL_UNSIGNED_NORMALIZED;
+		else
+			return GL_NONE;
+	}
+	else if (format.type == tcu::TextureFormat::FLOAT_UNSIGNED_INT_24_8_REV)
+	{
+		if (m_pname == GL_TEXTURE_DEPTH_TYPE)
+			return GL_FLOAT;
+		else
+			return GL_NONE;
+	}
+	else
+	{
+		switch (channelClass)
+		{
+			case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:		channelType = GL_SIGNED_NORMALIZED;		break;
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:		channelType = GL_UNSIGNED_NORMALIZED;	break;
+			case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:			channelType = GL_INT;					break;
+			case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:			channelType = GL_UNSIGNED_INT;			break;
+			case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:			channelType = GL_FLOAT;					break;
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+
+	switch (m_pname)
+	{
+		case GL_TEXTURE_RED_TYPE:
+			if (format.order == tcu::TextureFormat::R		||
+				format.order == tcu::TextureFormat::RG		||
+				format.order == tcu::TextureFormat::RGB		||
+				format.order == tcu::TextureFormat::RGBA	||
+				format.order == tcu::TextureFormat::BGRA	||
+				format.order == tcu::TextureFormat::ARGB	||
+				format.order == tcu::TextureFormat::sRGB	||
+				format.order == tcu::TextureFormat::sRGBA)
+				return channelType;
+			else
+				return GL_NONE;
+
+		case GL_TEXTURE_GREEN_TYPE:
+			if (format.order == tcu::TextureFormat::RG		||
+				format.order == tcu::TextureFormat::RGB		||
+				format.order == tcu::TextureFormat::RGBA	||
+				format.order == tcu::TextureFormat::BGRA	||
+				format.order == tcu::TextureFormat::ARGB	||
+				format.order == tcu::TextureFormat::sRGB	||
+				format.order == tcu::TextureFormat::sRGBA)
+				return channelType;
+			else
+				return GL_NONE;
+
+		case GL_TEXTURE_BLUE_TYPE:
+			if (format.order == tcu::TextureFormat::RGB		||
+				format.order == tcu::TextureFormat::RGBA	||
+				format.order == tcu::TextureFormat::BGRA	||
+				format.order == tcu::TextureFormat::ARGB	||
+				format.order == tcu::TextureFormat::sRGB	||
+				format.order == tcu::TextureFormat::sRGBA)
+				return channelType;
+			else
+				return GL_NONE;
+
+		case GL_TEXTURE_ALPHA_TYPE:
+			if (format.order == tcu::TextureFormat::RGBA	||
+				format.order == tcu::TextureFormat::BGRA	||
+				format.order == tcu::TextureFormat::ARGB	||
+				format.order == tcu::TextureFormat::sRGBA)
+				return channelType;
+			else
+				return GL_NONE;
+
+		case GL_TEXTURE_DEPTH_TYPE:
+			if (format.order == tcu::TextureFormat::D	||
+				format.order == tcu::TextureFormat::DS)
+				return channelType;
+			else
+				return GL_NONE;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+class TextureLevelCompressedCase : public TextureLevelCase
+{
+public:
+	TextureLevelCompressedCase (Context& ctx, const char* name, const char* desc, VerifierType type)
+		: TextureLevelCase(ctx, name, desc, type)
+	{
+	}
+
+private:
+	void generateTestIterations (std::vector<TextureGenerationSpec>& iterations)
+	{
+		generateCompressedTextureGenerationGroup(iterations);
+	}
+
+	bool checkTextureState (glu::CallLogWrapper& gl, const TextureGenerationSpec& spec)
+	{
+		bool allOk = true;
+
+		if (spec.levels.empty())
+		{
+			allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, GL_TEXTURE_COMPRESSED, 0, m_type);
+		}
+		else
+		{
+			for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
+			{
+				const int queryLevel	= spec.levels[levelNdx].level;
+				const int refValue		= (spec.levels[levelNdx].compressed) ? (1) : (0);
+
+				allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_COMPRESSED, refValue, m_type);
+			}
+		}
+
+		return allOk;
+	}
+};
+
+} // anonymous
+
+TextureLevelStateQueryTests::TextureLevelStateQueryTests (Context& context)
+	: TestCaseGroup(context, "texture_level", "GetTexLevelParameter tests")
+{
+}
+
+TextureLevelStateQueryTests::~TextureLevelStateQueryTests (void)
+{
+}
+
+void TextureLevelStateQueryTests::init (void)
+{
+	tcu::TestCaseGroup* const integerGroup = new tcu::TestCaseGroup(m_testCtx, "integer", "use GetTexLevelParameteriv");
+	tcu::TestCaseGroup* const floatGroup = new tcu::TestCaseGroup(m_testCtx, "float", "use GetTexLevelParameterfv");
+
+	addChild(integerGroup);
+	addChild(floatGroup);
+
+	for (int groupNdx = 0; groupNdx < 2; ++groupNdx)
+	{
+		tcu::TestCaseGroup* const	group		= (groupNdx == 0) ? (integerGroup) : (floatGroup);
+		const VerifierType			verifier	= (groupNdx == 0) ? (VERIFIER_INT) : (VERIFIER_FLOAT);
+
+
+		group->addChild(new TextureLevelSampleCase			(m_context, "texture_samples",					"Verify TEXTURE_SAMPLES",					verifier));
+		group->addChild(new TextureLevelFixedSamplesCase	(m_context, "texture_fixed_sample_locations",	"Verify TEXTURE_FIXED_SAMPLE_LOCATIONS",	verifier));
+		group->addChild(new TextureLevelWidthCase			(m_context, "texture_width",					"Verify TEXTURE_WIDTH",						verifier));
+		group->addChild(new TextureLevelHeightCase			(m_context, "texture_height",					"Verify TEXTURE_HEIGHT",					verifier));
+		group->addChild(new TextureLevelDepthCase			(m_context, "texture_depth",					"Verify TEXTURE_DEPTH",						verifier));
+		group->addChild(new TextureLevelInternalFormatCase	(m_context, "texture_internal_format",			"Verify TEXTURE_INTERNAL_FORMAT",			verifier));
+		group->addChild(new TextureLevelSizeCase			(m_context, "texture_red_size",					"Verify TEXTURE_RED_SIZE",					verifier,	GL_TEXTURE_RED_SIZE));
+		group->addChild(new TextureLevelSizeCase			(m_context, "texture_green_size",				"Verify TEXTURE_GREEN_SIZE",				verifier,	GL_TEXTURE_GREEN_SIZE));
+		group->addChild(new TextureLevelSizeCase			(m_context, "texture_blue_size",				"Verify TEXTURE_BLUE_SIZE",					verifier,	GL_TEXTURE_BLUE_SIZE));
+		group->addChild(new TextureLevelSizeCase			(m_context, "texture_alpha_size",				"Verify TEXTURE_ALPHA_SIZE",				verifier,	GL_TEXTURE_ALPHA_SIZE));
+		group->addChild(new TextureLevelSizeCase			(m_context, "texture_depth_size",				"Verify TEXTURE_DEPTH_SIZE",				verifier,	GL_TEXTURE_DEPTH_SIZE));
+		group->addChild(new TextureLevelSizeCase			(m_context, "texture_stencil_size",				"Verify TEXTURE_STENCIL_SIZE",				verifier,	GL_TEXTURE_STENCIL_SIZE));
+		group->addChild(new TextureLevelSizeCase			(m_context, "texture_shared_size",				"Verify TEXTURE_SHARED_SIZE",				verifier,	GL_TEXTURE_SHARED_SIZE));
+		group->addChild(new TextureLevelTypeCase			(m_context, "texture_red_type",					"Verify TEXTURE_RED_TYPE",					verifier,	GL_TEXTURE_RED_TYPE));
+		group->addChild(new TextureLevelTypeCase			(m_context, "texture_green_type",				"Verify TEXTURE_GREEN_TYPE",				verifier,	GL_TEXTURE_GREEN_TYPE));
+		group->addChild(new TextureLevelTypeCase			(m_context, "texture_blue_type",				"Verify TEXTURE_BLUE_TYPE",					verifier,	GL_TEXTURE_BLUE_TYPE));
+		group->addChild(new TextureLevelTypeCase			(m_context, "texture_alpha_type",				"Verify TEXTURE_ALPHA_TYPE",				verifier,	GL_TEXTURE_ALPHA_TYPE));
+		group->addChild(new TextureLevelTypeCase			(m_context, "texture_depth_type",				"Verify TEXTURE_DEPTH_TYPE",				verifier,	GL_TEXTURE_DEPTH_TYPE));
+		group->addChild(new TextureLevelCompressedCase		(m_context, "texture_compressed",				"Verify TEXTURE_COMPRESSED",				verifier));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fTextureLevelStateQueryTests.hpp b/modules/gles31/functional/es31fTextureLevelStateQueryTests.hpp
new file mode 100644
index 0000000..296b9fa
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureLevelStateQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FTEXTURELEVELSTATEQUERYTESTS_HPP
+#define _ES31FTEXTURELEVELSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture level state query tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class TextureLevelStateQueryTests : public TestCaseGroup
+{
+public:
+									TextureLevelStateQueryTests	(Context& context);
+									~TextureLevelStateQueryTests(void);
+
+	void							init						(void);
+
+private:
+									TextureLevelStateQueryTests	(const TextureLevelStateQueryTests& other);
+	TextureLevelStateQueryTests&	operator=					(const TextureLevelStateQueryTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FTEXTURELEVELSTATEQUERYTESTS_HPP
diff --git a/modules/gles31/functional/es31fTextureMultisampleTests.cpp b/modules/gles31/functional/es31fTextureMultisampleTests.cpp
new file mode 100644
index 0000000..7f8a786
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureMultisampleTests.cpp
@@ -0,0 +1,2106 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample texture test
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fTextureMultisampleTests.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuTextureUtil.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "glsRasterizationTestUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluStrUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deStringUtil.hpp"
+#include "deRandom.hpp"
+
+using namespace glw;
+using deqp::gls::RasterizationTestUtil::RasterizationArguments;
+using deqp::gls::RasterizationTestUtil::TriangleSceneSpec;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+static std::string sampleMaskToString (const std::vector<deUint32>& bitfield, int numBits)
+{
+	std::string result(numBits, '0');
+
+	// move from back to front and set chars to 1
+	for (int wordNdx = 0; wordNdx < (int)bitfield.size(); ++wordNdx)
+	{
+		for (int bit = 0; bit < 32; ++bit)
+		{
+			const int targetCharNdx = numBits - (wordNdx*32+bit) - 1;
+
+			// beginning of the string reached
+			if (targetCharNdx < 0)
+				return result;
+
+			if ((bitfield[wordNdx] >> bit) & 0x01)
+				result[targetCharNdx] = '1';
+		}
+	}
+
+	return result;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Returns the number of words needed to represent mask of given length
+ *//*--------------------------------------------------------------------*/
+static int getEffectiveSampleMaskWordCount (int highestBitNdx)
+{
+	const int wordSize	= 32;
+	const int maskLen	= highestBitNdx + 1;
+
+	return ((maskLen - 1) / wordSize) + 1; // round_up(mask_len /  wordSize)
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Creates sample mask with all less significant bits than nthBit set
+ *//*--------------------------------------------------------------------*/
+static std::vector<deUint32> genAllSetToNthBitSampleMask (int nthBit)
+{
+	const int				wordSize	= 32;
+	const int				numWords	= getEffectiveSampleMaskWordCount(nthBit - 1);
+	const deUint32			topWordBits	= (deUint32)(nthBit - (numWords - 1) * wordSize);
+	std::vector<deUint32>	mask		(numWords);
+
+	for (int ndx = 0; ndx < numWords - 1; ++ndx)
+		mask[ndx] = 0xFFFFFFFF;
+
+	mask[numWords - 1] = (deUint32)((1ULL << topWordBits) - (deUint32)1);
+	return mask;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Creates sample mask with nthBit set
+ *//*--------------------------------------------------------------------*/
+static std::vector<deUint32> genSetNthBitSampleMask (int nthBit)
+{
+	const int				wordSize	= 32;
+	const int				numWords	= getEffectiveSampleMaskWordCount(nthBit);
+	const deUint32			topWordBits	= (deUint32)(nthBit - (numWords - 1) * wordSize);
+	std::vector<deUint32>	mask		(numWords);
+
+	for (int ndx = 0; ndx < numWords - 1; ++ndx)
+		mask[ndx] = 0;
+
+	mask[numWords - 1] = (deUint32)(1ULL << topWordBits);
+	return mask;
+}
+
+class SamplePosRasterizationTest : public TestCase
+{
+public:
+								SamplePosRasterizationTest	(Context& context, const char* name, const char* desc, int samples);
+								~SamplePosRasterizationTest	(void);
+
+private:
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+	void						genMultisampleTexture		(void);
+	void						genSamplerProgram			(void);
+	bool						testMultisampleTexture		(int sampleNdx);
+	void						drawSample					(tcu::Surface& dst, int sampleNdx);
+	void						convertToSceneSpec			(TriangleSceneSpec& scene, const tcu::Vec2& samplePos) const;
+
+	struct Triangle
+	{
+		tcu::Vec4 p1;
+		tcu::Vec4 p2;
+		tcu::Vec4 p3;
+	};
+
+	const int					m_samples;
+	const int					m_canvasSize;
+	std::vector<Triangle>		m_testTriangles;
+
+	int							m_iteration;
+	bool						m_allIterationsOk;
+
+	GLuint						m_texID;
+	GLuint						m_vaoID;
+	GLuint						m_vboID;
+	std::vector<tcu::Vec2>		m_samplePositions;
+	int							m_subpixelBits;
+
+	const glu::ShaderProgram*	m_samplerProgram;
+	GLint						m_samplerProgramPosLoc;
+	GLint						m_samplerProgramSamplerLoc;
+	GLint						m_samplerProgramSampleNdxLoc;
+};
+
+SamplePosRasterizationTest::SamplePosRasterizationTest (Context& context, const char* name, const char* desc, int samples)
+	: TestCase						(context, name, desc)
+	, m_samples						(samples)
+	, m_canvasSize					(256)
+	, m_iteration					(0)
+	, m_allIterationsOk				(true)
+	, m_texID						(0)
+	, m_vaoID						(0)
+	, m_vboID						(0)
+	, m_subpixelBits				(0)
+	, m_samplerProgram				(DE_NULL)
+	, m_samplerProgramPosLoc		(-1)
+	, m_samplerProgramSamplerLoc	(-1)
+	, m_samplerProgramSampleNdxLoc	(-1)
+{
+}
+
+SamplePosRasterizationTest::~SamplePosRasterizationTest (void)
+{
+	deinit();
+}
+
+void SamplePosRasterizationTest::init (void)
+{
+	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
+	GLint					maxSamples	= 0;
+
+	// requirements
+
+	if (m_context.getRenderTarget().getWidth() < m_canvasSize || m_context.getRenderTarget().getHeight() < m_canvasSize)
+		throw tcu::NotSupportedError("render target size must be at least " + de::toString(m_canvasSize) + "x" + de::toString(m_canvasSize));
+
+	gl.getIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &maxSamples);
+	if (m_samples > maxSamples)
+		throw tcu::NotSupportedError("Requested sample count is greater than GL_MAX_COLOR_TEXTURE_SAMPLES");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_COLOR_TEXTURE_SAMPLES = " << maxSamples << tcu::TestLog::EndMessage;
+
+	gl.getIntegerv(GL_SUBPIXEL_BITS, &m_subpixelBits);
+	m_testCtx.getLog() << tcu::TestLog::Message << "GL_SUBPIXEL_BITS = " << m_subpixelBits << tcu::TestLog::EndMessage;
+
+	// generate textures & other gl stuff
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Creating multisample texture" << tcu::TestLog::EndMessage;
+
+	gl.genTextures				(1, &m_texID);
+	gl.bindTexture				(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
+	gl.texStorage2DMultisample	(GL_TEXTURE_2D_MULTISAMPLE, m_samples, GL_RGBA8, m_canvasSize, m_canvasSize, GL_TRUE);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "texStorage2DMultisample");
+
+	gl.genVertexArrays		(1, &m_vaoID);
+	gl.bindVertexArray		(m_vaoID);
+	GLU_EXPECT_NO_ERROR		(gl.getError(), "bindVertexArray");
+
+	gl.genBuffers			(1, &m_vboID);
+	gl.bindBuffer			(GL_ARRAY_BUFFER, m_vboID);
+	GLU_EXPECT_NO_ERROR		(gl.getError(), "bindBuffer");
+
+	// generate test scene
+	for (int i = 0; i < 20; ++i)
+	{
+		// vertical spikes
+		Triangle tri;
+		tri.p1 = tcu::Vec4(((float)i        + 1.0f / (float)(i + 1)) / 20.0f,	 0.0f,	0.0f,	1.0f);
+		tri.p2 = tcu::Vec4(((float)i + 0.3f + 1.0f / (float)(i + 1)) / 20.0f,	 0.0f,	0.0f,	1.0f);
+		tri.p3 = tcu::Vec4(((float)i        + 1.0f / (float)(i + 1)) / 20.0f,	-1.0f,	0.0f,	1.0f);
+		m_testTriangles.push_back(tri);
+	}
+	for (int i = 0; i < 20; ++i)
+	{
+		// horisontal spikes
+		Triangle tri;
+		tri.p1 = tcu::Vec4(-1.0f,	((float)i        + 1.0f / (float)(i + 1)) / 20.0f,	0.0f,	1.0f);
+		tri.p2 = tcu::Vec4(-1.0f,	((float)i + 0.3f + 1.0f / (float)(i + 1)) / 20.0f,	0.0f,	1.0f);
+		tri.p3 = tcu::Vec4( 0.0f,	((float)i        + 1.0f / (float)(i + 1)) / 20.0f,	0.0f,	1.0f);
+		m_testTriangles.push_back(tri);
+	}
+
+	for (int i = 0; i < 20; ++i)
+	{
+		// fan
+		const tcu::Vec2 p = tcu::Vec2(deFloatCos(((float)i)/20.0f*DE_PI*2) * 0.5f + 0.5f, deFloatSin(((float)i)/20.0f*DE_PI*2) * 0.5f + 0.5f);
+		const tcu::Vec2 d = tcu::Vec2(0.1f, 0.02f);
+
+		Triangle tri;
+		tri.p1 = tcu::Vec4(0.4f,			0.4f,			0.0f,	1.0f);
+		tri.p2 = tcu::Vec4(p.x(),			p.y(),			0.0f,	1.0f);
+		tri.p3 = tcu::Vec4(p.x() + d.x(),	p.y() + d.y(),	0.0f,	1.0f);
+		m_testTriangles.push_back(tri);
+	}
+	{
+		Triangle tri;
+		tri.p1 = tcu::Vec4(-0.202f, -0.202f, 0.0f, 1.0f);
+		tri.p2 = tcu::Vec4(-0.802f, -0.202f, 0.0f, 1.0f);
+		tri.p3 = tcu::Vec4(-0.802f, -0.802f, 0.0f, 1.0f);
+		m_testTriangles.push_back(tri);
+	}
+
+	// generate multisample texture (and query the sample positions in it)
+	genMultisampleTexture();
+
+	// verify queried samples are in a valid range
+	for (int sampleNdx = 0; sampleNdx < m_samples; ++sampleNdx)
+	{
+		if (m_samplePositions[sampleNdx].x() < 0.0f || m_samplePositions[sampleNdx].x() > 1.0f ||
+			m_samplePositions[sampleNdx].y() < 0.0f || m_samplePositions[sampleNdx].y() > 1.0f)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Sample position of sample " << sampleNdx << " should be in range ([0, 1], [0, 1]). Got " << m_samplePositions[sampleNdx] << tcu::TestLog::EndMessage;
+			throw tcu::TestError("invalid sample position");
+		}
+	}
+
+	// generate sampler program
+	genSamplerProgram();
+}
+
+void SamplePosRasterizationTest::deinit (void)
+{
+	if (m_vboID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_vboID);
+		m_vboID = 0;
+	}
+
+	if (m_vaoID)
+	{
+		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vaoID);
+		m_vaoID = 0;
+	}
+
+	if (m_texID)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texID);
+		m_texID = 0;
+	}
+
+	if (m_samplerProgram)
+	{
+		delete m_samplerProgram;
+		m_samplerProgram = DE_NULL;
+	}
+}
+
+SamplePosRasterizationTest::IterateResult SamplePosRasterizationTest::iterate (void)
+{
+	m_allIterationsOk &= testMultisampleTexture(m_iteration);
+	m_iteration++;
+
+	if (m_iteration < m_samples)
+		return CONTINUE;
+
+	// End result
+	if (m_allIterationsOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Pixel comparison failed");
+
+	return STOP;
+}
+
+void SamplePosRasterizationTest::genMultisampleTexture (void)
+{
+	const char* const vertexShaderSource	=	"#version 310 es\n"
+												"in highp vec4 a_position;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"}\n";
+	const char* const fragmentShaderSource	=	"#version 310 es\n"
+												"layout(location = 0) out highp vec4 fragColor;\n"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+												"}\n";
+
+	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
+	const glu::ShaderProgram	program			(m_context.getRenderContext(), glu::ProgramSources()
+																			<< glu::VertexSource(vertexShaderSource)
+																			<< glu::FragmentSource(fragmentShaderSource));
+	const GLuint				posLoc			= gl.getAttribLocation(program.getProgram(), "a_position");
+	GLuint						fboID			= 0;
+
+	if (!program.isOk())
+	{
+		m_testCtx.getLog() << program;
+		throw tcu::TestError("Failed to build shader.");
+	}
+
+	gl.bindTexture			(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
+	gl.bindVertexArray		(m_vaoID);
+	gl.bindBuffer			(GL_ARRAY_BUFFER, m_vboID);
+
+	// Setup fbo for drawing and for sample position query
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Attaching texture to FBO" << tcu::TestLog::EndMessage;
+
+	gl.genFramebuffers		(1, &fboID);
+	gl.bindFramebuffer		(GL_FRAMEBUFFER, fboID);
+	gl.framebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, m_texID, 0);
+	GLU_EXPECT_NO_ERROR		(gl.getError(), "framebufferTexture2D");
+
+	// Query sample positions of the multisample texture by querying the sample positions
+	// from an fbo which has the multisample texture as attachment.
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Sample locations:" << tcu::TestLog::EndMessage;
+
+	for (int sampleNdx = 0; sampleNdx < m_samples; ++sampleNdx)
+	{
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<float[2]> position;
+
+		gl.getMultisamplefv(GL_SAMPLE_POSITION, (deUint32)sampleNdx, position);
+		if (!position.verifyValidity(m_testCtx))
+			throw tcu::TestError("Error while querying sample positions");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "\t" << sampleNdx << ": (" << position[0] << ", " << position[1] << ")" << tcu::TestLog::EndMessage;
+		m_samplePositions.push_back(tcu::Vec2(position[0], position[1]));
+	}
+
+	// Draw test pattern to texture
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing test pattern to the texture" << tcu::TestLog::EndMessage;
+
+	gl.bufferData				(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(m_testTriangles.size() * sizeof(Triangle)), &m_testTriangles[0], GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "bufferData");
+
+	gl.viewport					(0, 0, m_canvasSize, m_canvasSize);
+	gl.clearColor				(0, 0, 0, 1);
+	gl.clear					(GL_COLOR_BUFFER_BIT);
+	gl.vertexAttribPointer		(posLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray	(posLoc);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "vertexAttribPointer");
+
+	gl.useProgram				(program.getProgram());
+	gl.drawArrays				(GL_TRIANGLES, 0, (glw::GLsizei)(m_testTriangles.size() * 3));
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "drawArrays");
+
+	gl.disableVertexAttribArray	(posLoc);
+	gl.useProgram				(0);
+	gl.deleteFramebuffers		(1, &fboID);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "cleanup");
+}
+
+void SamplePosRasterizationTest::genSamplerProgram (void)
+{
+	const char* const	vertexShaderSource	=	"#version 310 es\n"
+												"in highp vec4 a_position;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"}\n";
+	const char* const	fragShaderSource	=	"#version 310 es\n"
+												"layout(location = 0) out highp vec4 fragColor;\n"
+												"uniform highp sampler2DMS u_sampler;\n"
+												"uniform highp int u_sample;\n"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = texelFetch(u_sampler, ivec2(int(floor(gl_FragCoord.x)), int(floor(gl_FragCoord.y))), u_sample);\n"
+												"}\n";
+	const tcu::ScopedLogSection	section			(m_testCtx.getLog(), "Generate sampler shader", "Generate sampler shader");
+	const glw::Functions&		gl			=	m_context.getRenderContext().getFunctions();
+
+	m_samplerProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(fragShaderSource));
+	m_testCtx.getLog() << *m_samplerProgram;
+
+	if (!m_samplerProgram->isOk())
+		throw tcu::TestError("Could not create sampler program.");
+
+	m_samplerProgramPosLoc			= gl.getAttribLocation(m_samplerProgram->getProgram(), "a_position");
+	m_samplerProgramSamplerLoc		= gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sampler");
+	m_samplerProgramSampleNdxLoc	= gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sample");
+}
+
+bool SamplePosRasterizationTest::testMultisampleTexture (int sampleNdx)
+{
+	tcu::Surface		glSurface(m_canvasSize, m_canvasSize);
+	TriangleSceneSpec	scene;
+
+	// Draw sample
+	drawSample(glSurface, sampleNdx);
+
+	// Draw reference(s)
+	convertToSceneSpec(scene, m_samplePositions[sampleNdx]);
+
+	// Compare
+	{
+		RasterizationArguments args;
+		args.redBits		= m_context.getRenderTarget().getPixelFormat().redBits;
+		args.greenBits		= m_context.getRenderTarget().getPixelFormat().greenBits;
+		args.blueBits		= m_context.getRenderTarget().getPixelFormat().blueBits;
+		args.numSamples		= 0;
+		args.subpixelBits	= m_subpixelBits;
+
+		return gls::RasterizationTestUtil::verifyTriangleGroupRasterization(glSurface, scene, args, m_testCtx.getLog(), deqp::gls::RasterizationTestUtil::VERIFICATIONMODE_STRICT);
+	}
+}
+
+void SamplePosRasterizationTest::drawSample (tcu::Surface& dst, int sampleNdx)
+{
+	// Downsample using only one sample
+	static const tcu::Vec4 fullscreenQuad[] =
+	{
+		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)
+	};
+
+	const tcu::ScopedLogSection section	(m_testCtx.getLog(), "Test sample position " + de::toString(sampleNdx+1) + "/" + de::toString(m_samples), "Test sample position " + de::toString(sampleNdx+1) + "/" + de::toString(m_samples));
+	const glw::Functions&		gl		= m_context.getRenderContext().getFunctions();
+
+	gl.bindTexture				(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
+	gl.bindVertexArray			(m_vaoID);
+	gl.bindBuffer				(GL_ARRAY_BUFFER, m_vboID);
+
+	gl.bufferData				(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), &fullscreenQuad[0], GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "bufferData");
+
+	gl.viewport					(0, 0, m_canvasSize, m_canvasSize);
+	gl.clearColor				(0, 0, 0, 1);
+	gl.clear					(GL_COLOR_BUFFER_BIT);
+	gl.vertexAttribPointer		(m_samplerProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray	(m_samplerProgramPosLoc);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "vertexAttribPointer");
+
+	gl.useProgram				(m_samplerProgram->getProgram());
+	gl.uniform1i				(m_samplerProgramSamplerLoc, 0);
+	gl.uniform1i				(m_samplerProgramSampleNdxLoc, (deInt32)sampleNdx);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "useprogram");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Reading from texture with sample index " << sampleNdx << tcu::TestLog::EndMessage;
+
+	gl.drawArrays				(GL_TRIANGLE_STRIP, 0, 4);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "drawArrays");
+
+	gl.disableVertexAttribArray	(m_samplerProgramPosLoc);
+	gl.useProgram				(0);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "cleanup");
+
+	gl.finish					();
+	glu::readPixels				(m_context.getRenderContext(), 0, 0, dst.getAccess());
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "readPixels");
+}
+
+void SamplePosRasterizationTest::convertToSceneSpec (TriangleSceneSpec& scene, const tcu::Vec2& samplePos) const
+{
+	// Triangles are offset from the pixel center by "offset". Move the triangles back to take this into account.
+	const tcu::Vec4 offset = tcu::Vec4(samplePos.x() - 0.5f, samplePos.y() - 0.5f, 0.0f, 0.0f) / tcu::Vec4((float)m_canvasSize, (float)m_canvasSize, 1.0f, 1.0f) * 2.0f;
+
+	for (int triangleNdx = 0; triangleNdx < (int)m_testTriangles.size(); ++triangleNdx)
+	{
+		TriangleSceneSpec::SceneTriangle triangle;
+
+		triangle.positions[0] = m_testTriangles[triangleNdx].p1 - offset;
+		triangle.positions[1] = m_testTriangles[triangleNdx].p2 - offset;
+		triangle.positions[2] = m_testTriangles[triangleNdx].p3 - offset;
+
+		triangle.sharedEdge[0] = false;
+		triangle.sharedEdge[1] = false;
+		triangle.sharedEdge[2] = false;
+
+		scene.triangles.push_back(triangle);
+	}
+}
+
+class SampleMaskCase : public TestCase
+{
+public:
+	enum CaseFlags
+	{
+		FLAGS_NONE					= 0,
+		FLAGS_ALPHA_TO_COVERAGE		= (1ULL << 0),
+		FLAGS_SAMPLE_COVERAGE		= (1ULL << 1),
+		FLAGS_HIGH_BITS				= (1ULL << 2),
+	};
+
+								SampleMaskCase				(Context& context, const char* name, const char* desc, int samples, int flags);
+								~SampleMaskCase				(void);
+
+private:
+	void						init						(void);
+	void						deinit						(void);
+	IterateResult				iterate						(void);
+
+	void						genSamplerProgram			(void);
+	void						genAlphaProgram				(void);
+	void						updateTexture				(int sample);
+	bool						verifyTexture				(int sample);
+	void						drawSample					(tcu::Surface& dst, int sample);
+
+	const int					m_samples;
+	const int					m_canvasSize;
+	const int					m_gridsize;
+	const int					m_effectiveSampleMaskWordCount;
+
+	int							m_flags;
+	int							m_currentSample;
+	int							m_allIterationsOk;
+
+	glw::GLuint					m_texID;
+	glw::GLuint					m_vaoID;
+	glw::GLuint					m_vboID;
+	glw::GLuint					m_fboID;
+
+	const glu::ShaderProgram*	m_samplerProgram;
+	glw::GLint					m_samplerProgramPosLoc;
+	glw::GLint					m_samplerProgramSamplerLoc;
+	glw::GLint					m_samplerProgramSampleNdxLoc;
+
+	const glu::ShaderProgram*	m_alphaProgram;
+	glw::GLint					m_alphaProgramPosLoc;
+};
+
+SampleMaskCase::SampleMaskCase (Context& context, const char* name, const char* desc, int samples, int flags)
+	: TestCase						(context, name, desc)
+	, m_samples						(samples)
+	, m_canvasSize					(256)
+	, m_gridsize					(16)
+	, m_effectiveSampleMaskWordCount(getEffectiveSampleMaskWordCount(samples - 1))
+	, m_flags						(flags)
+	, m_currentSample				(-1)
+	, m_allIterationsOk				(true)
+	, m_texID						(0)
+	, m_vaoID						(0)
+	, m_vboID						(0)
+	, m_fboID						(0)
+	, m_samplerProgram				(DE_NULL)
+	, m_samplerProgramPosLoc		(-1)
+	, m_samplerProgramSamplerLoc	(-1)
+	, m_samplerProgramSampleNdxLoc	(-1)
+	, m_alphaProgram				(DE_NULL)
+	, m_alphaProgramPosLoc			(-1)
+{
+}
+
+SampleMaskCase::~SampleMaskCase (void)
+{
+	deinit();
+}
+
+void SampleMaskCase::init (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	glw::GLint				maxSamples			= 0;
+	glw::GLint				maxSampleMaskWords	= 0;
+
+	// requirements
+
+	if (m_context.getRenderTarget().getWidth() < m_canvasSize || m_context.getRenderTarget().getHeight() < m_canvasSize)
+		throw tcu::NotSupportedError("render target size must be at least " + de::toString(m_canvasSize) + "x" + de::toString(m_canvasSize));
+
+	gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
+	if (m_effectiveSampleMaskWordCount > maxSampleMaskWords)
+		throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");
+
+	gl.getIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &maxSamples);
+	if (m_samples > maxSamples)
+		throw tcu::NotSupportedError("Requested sample count is greater than GL_MAX_COLOR_TEXTURE_SAMPLES");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_COLOR_TEXTURE_SAMPLES = " << maxSamples << tcu::TestLog::EndMessage;
+
+	// Don't even try to test high bits if there are none
+
+	if ((m_flags & FLAGS_HIGH_BITS) && (m_samples % 32 == 0))
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Sample count is multiple of word size. No unused high bits in sample mask.\nSkipping." << tcu::TestLog::EndMessage;
+		throw tcu::NotSupportedError("Test requires unused high bits (sample count not multiple of 32)");
+	}
+
+	// generate textures
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Creating multisample texture with sample count " << m_samples << tcu::TestLog::EndMessage;
+
+	gl.genTextures				(1, &m_texID);
+	gl.bindTexture				(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
+	gl.texStorage2DMultisample	(GL_TEXTURE_2D_MULTISAMPLE, m_samples, GL_RGBA8, m_canvasSize, m_canvasSize, GL_FALSE);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "texStorage2DMultisample");
+
+	// attach texture to fbo
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Attaching texture to FBO" << tcu::TestLog::EndMessage;
+
+	gl.genFramebuffers		(1, &m_fboID);
+	gl.bindFramebuffer		(GL_FRAMEBUFFER, m_fboID);
+	gl.framebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, m_texID, 0);
+	GLU_EXPECT_NO_ERROR		(gl.getError(), "framebufferTexture2D");
+
+	// buffers
+
+	gl.genVertexArrays		(1, &m_vaoID);
+	GLU_EXPECT_NO_ERROR		(gl.getError(), "genVertexArrays");
+
+	gl.genBuffers			(1, &m_vboID);
+	gl.bindBuffer			(GL_ARRAY_BUFFER, m_vboID);
+	GLU_EXPECT_NO_ERROR		(gl.getError(), "genBuffers");
+
+	// generate grid pattern
+	{
+		std::vector<tcu::Vec4> gridData(m_gridsize*m_gridsize*6);
+
+		for (int y = 0; y < m_gridsize; ++y)
+		for (int x = 0; x < m_gridsize; ++x)
+		{
+			gridData[(y * m_gridsize + x)*6 + 0] = tcu::Vec4(((float)(x+0) / m_gridsize) * 2.0f - 1.0f, ((float)(y+0) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
+			gridData[(y * m_gridsize + x)*6 + 1] = tcu::Vec4(((float)(x+0) / m_gridsize) * 2.0f - 1.0f, ((float)(y+1) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
+			gridData[(y * m_gridsize + x)*6 + 2] = tcu::Vec4(((float)(x+1) / m_gridsize) * 2.0f - 1.0f, ((float)(y+1) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
+			gridData[(y * m_gridsize + x)*6 + 3] = tcu::Vec4(((float)(x+0) / m_gridsize) * 2.0f - 1.0f, ((float)(y+0) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
+			gridData[(y * m_gridsize + x)*6 + 4] = tcu::Vec4(((float)(x+1) / m_gridsize) * 2.0f - 1.0f, ((float)(y+1) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
+			gridData[(y * m_gridsize + x)*6 + 5] = tcu::Vec4(((float)(x+1) / m_gridsize) * 2.0f - 1.0f, ((float)(y+0) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
+		}
+
+		gl.bufferData			(GL_ARRAY_BUFFER, (int)(gridData.size() * sizeof(tcu::Vec4)), gridData[0].getPtr(), GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR		(gl.getError(), "bufferData");
+	}
+
+	// generate programs
+
+	genSamplerProgram();
+	genAlphaProgram();
+}
+
+void SampleMaskCase::deinit (void)
+{
+	if (m_texID)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texID);
+		m_texID = 0;
+	}
+	if (m_vaoID)
+	{
+		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vaoID);
+		m_vaoID = 0;
+	}
+	if (m_vboID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_vboID);
+		m_vboID = 0;
+	}
+	if (m_fboID)
+	{
+		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_fboID);
+		m_fboID = 0;
+	}
+
+	if (m_samplerProgram)
+	{
+		delete m_samplerProgram;
+		m_samplerProgram = DE_NULL;
+	}
+	if (m_alphaProgram)
+	{
+		delete m_alphaProgram;
+		m_alphaProgram = DE_NULL;
+	}
+}
+
+SampleMaskCase::IterateResult SampleMaskCase::iterate (void)
+{
+	const tcu::ScopedLogSection	section(m_testCtx.getLog(), "Iteration", (m_currentSample == -1) ? ("Verifying with zero mask") : (std::string() + "Verifying sample " + de::toString(m_currentSample + 1) + "/" + de::toString(m_samples)));
+
+	bool iterationOk;
+
+	// Mask only one sample, clear rest
+
+	updateTexture(m_currentSample);
+
+	// Verify only one sample set is in the texture
+
+	iterationOk = verifyTexture(m_currentSample);
+	if (!iterationOk)
+		m_allIterationsOk = false;
+
+	m_currentSample++;
+	if (m_currentSample < m_samples)
+		return CONTINUE;
+
+	// End result
+
+	if (m_allIterationsOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else if (m_flags & FLAGS_HIGH_BITS)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unused mask bits have effect");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Sample test failed");
+
+	return STOP;
+}
+
+void SampleMaskCase::genSamplerProgram (void)
+{
+	const char* const	vertexShaderSource	=	"#version 310 es\n"
+												"in highp vec4 a_position;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"}\n";
+	const char* const	fragShaderSource	=	"#version 310 es\n"
+												"layout(location = 0) out highp vec4 fragColor;\n"
+												"uniform highp sampler2DMS u_sampler;\n"
+												"uniform highp int u_sample;\n"
+												"void main (void)\n"
+												"{\n"
+												"	highp float correctCoverage = 0.0;\n"
+												"	highp float incorrectCoverage = 0.0;\n"
+												"	highp ivec2 texelPos = ivec2(int(floor(gl_FragCoord.x)), int(floor(gl_FragCoord.y)));\n"
+												"\n"
+												"	for (int sampleNdx = 0; sampleNdx < ${NUMSAMPLES}; ++sampleNdx)\n"
+												"	{\n"
+												"		highp float sampleColor = texelFetch(u_sampler, texelPos, sampleNdx).r;\n"
+												"		if (sampleNdx == u_sample)\n"
+												"			correctCoverage += sampleColor;\n"
+												"		else\n"
+												"			incorrectCoverage += sampleColor;\n"
+												"	}\n"
+												"	fragColor = vec4(correctCoverage, incorrectCoverage, 0.0, 1.0);\n"
+												"}\n";
+	const tcu::ScopedLogSection			section	(m_testCtx.getLog(), "GenerateSamplerShader", "Generate sampler shader");
+	const glw::Functions&				gl		=	m_context.getRenderContext().getFunctions();
+	std::map<std::string, std::string>	args;
+
+	args["NUMSAMPLES"] = de::toString(m_samples);
+
+	m_samplerProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(tcu::StringTemplate(fragShaderSource).specialize(args)));
+	m_testCtx.getLog() << *m_samplerProgram;
+
+	if (!m_samplerProgram->isOk())
+		throw tcu::TestError("Could not create sampler program.");
+
+	m_samplerProgramPosLoc			= gl.getAttribLocation(m_samplerProgram->getProgram(), "a_position");
+	m_samplerProgramSamplerLoc		= gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sampler");
+	m_samplerProgramSampleNdxLoc	= gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sample");
+}
+
+void SampleMaskCase::genAlphaProgram (void)
+{
+	const char* const	vertexShaderSource	=	"#version 310 es\n"
+												"in highp vec4 a_position;\n"
+												"out highp float v_alpha;\n"
+												"void main (void)\n"
+												"{\n"
+												"	gl_Position = a_position;\n"
+												"	v_alpha = (a_position.x * 0.5 + 0.5)*(a_position.y * 0.5 + 0.5);\n"
+												"}\n";
+	const char* const	fragShaderSource	=	"#version 310 es\n"
+												"layout(location = 0) out highp vec4 fragColor;\n"
+												"in mediump float v_alpha;\n"
+												"void main (void)\n"
+												"{\n"
+												"	fragColor = vec4(1.0, 1.0, 1.0, v_alpha);\n"
+												"}\n";
+	const glw::Functions&		gl			=	m_context.getRenderContext().getFunctions();
+
+	m_alphaProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(fragShaderSource));
+
+	if (!m_alphaProgram->isOk())
+	{
+		m_testCtx.getLog() << *m_alphaProgram;
+		throw tcu::TestError("Could not create aplha program.");
+	}
+
+	m_alphaProgramPosLoc = gl.getAttribLocation(m_alphaProgram->getProgram(), "a_position");
+}
+
+void SampleMaskCase::updateTexture (int sample)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// prepare draw
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
+	gl.viewport(0, 0, m_canvasSize, m_canvasSize);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+
+	// clear all samples
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Clearing image" << tcu::TestLog::EndMessage;
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	// set mask state
+
+	if (m_flags & FLAGS_HIGH_BITS)
+	{
+		const std::vector<deUint32> bitmask			= genSetNthBitSampleMask(sample);
+		const std::vector<deUint32>	effectiveMask	= genAllSetToNthBitSampleMask(m_samples);
+		std::vector<deUint32>		totalBitmask	(effectiveMask.size());
+
+		DE_ASSERT((int)totalBitmask.size() == m_effectiveSampleMaskWordCount);
+
+		// set some arbitrary high bits to non-effective bits
+		for (int wordNdx = 0; wordNdx < (int)effectiveMask.size(); ++wordNdx)
+		{
+			const deUint32 randomMask	= (deUint32)deUint32Hash(wordNdx << 2 ^ sample);
+			const deUint32 sampleMask	= (wordNdx < (int)bitmask.size()) ? (bitmask[wordNdx]) : (0);
+			const deUint32 maskMask		= effectiveMask[wordNdx];
+
+			totalBitmask[wordNdx] = (sampleMask & maskMask) | (randomMask & ~maskMask);
+		}
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting sample mask to 0b" << sampleMaskToString(totalBitmask, (int)totalBitmask.size() * 32) << tcu::TestLog::EndMessage;
+
+		gl.enable(GL_SAMPLE_MASK);
+		for (int wordNdx = 0; wordNdx < m_effectiveSampleMaskWordCount; ++wordNdx)
+		{
+			const GLbitfield wordmask = (wordNdx < (int)totalBitmask.size()) ? ((GLbitfield)(totalBitmask[wordNdx])) : (0);
+			gl.sampleMaski((deUint32)wordNdx, wordmask);
+		}
+	}
+	else
+	{
+		const std::vector<deUint32> bitmask = genSetNthBitSampleMask(sample);
+		DE_ASSERT((int)bitmask.size() <= m_effectiveSampleMaskWordCount);
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Setting sample mask to 0b" << sampleMaskToString(bitmask, m_samples) << tcu::TestLog::EndMessage;
+
+		gl.enable(GL_SAMPLE_MASK);
+		for (int wordNdx = 0; wordNdx < m_effectiveSampleMaskWordCount; ++wordNdx)
+		{
+			const GLbitfield wordmask = (wordNdx < (int)bitmask.size()) ? ((GLbitfield)(bitmask[wordNdx])) : (0);
+			gl.sampleMaski((deUint32)wordNdx, wordmask);
+		}
+	}
+	if (m_flags & FLAGS_ALPHA_TO_COVERAGE)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Enabling alpha to coverage." << tcu::TestLog::EndMessage;
+		gl.enable(GL_SAMPLE_ALPHA_TO_COVERAGE);
+	}
+	if (m_flags & FLAGS_SAMPLE_COVERAGE)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Enabling sample coverage. Varying sample coverage for grid cells." << tcu::TestLog::EndMessage;
+		gl.enable(GL_SAMPLE_COVERAGE);
+	}
+
+	// draw test pattern
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing test grid" << tcu::TestLog::EndMessage;
+
+	gl.bindVertexArray			(m_vaoID);
+	gl.bindBuffer				(GL_ARRAY_BUFFER, m_vboID);
+	gl.vertexAttribPointer		(m_alphaProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray	(m_alphaProgramPosLoc);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "vertexAttribPointer");
+
+	gl.useProgram				(m_alphaProgram->getProgram());
+
+	for (int y = 0; y < m_gridsize; ++y)
+	for (int x = 0; x < m_gridsize; ++x)
+	{
+		if (m_flags & FLAGS_SAMPLE_COVERAGE)
+			gl.sampleCoverage((y*m_gridsize + x) / float(m_gridsize*m_gridsize), GL_FALSE);
+
+		gl.drawArrays				(GL_TRIANGLES, (y*m_gridsize + x) * 6, 6);
+		GLU_EXPECT_NO_ERROR			(gl.getError(), "drawArrays");
+	}
+
+	// clean state
+
+	gl.disableVertexAttribArray	(m_alphaProgramPosLoc);
+	gl.useProgram				(0);
+	gl.bindFramebuffer			(GL_FRAMEBUFFER, 0);
+	gl.disable					(GL_SAMPLE_MASK);
+	gl.disable					(GL_SAMPLE_ALPHA_TO_COVERAGE);
+	gl.disable					(GL_SAMPLE_COVERAGE);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "clean");
+}
+
+bool SampleMaskCase::verifyTexture (int sample)
+{
+	tcu::Surface	result		(m_canvasSize, m_canvasSize);
+	tcu::Surface	errorMask	(m_canvasSize, m_canvasSize);
+	bool			error		= false;
+
+	tcu::clear(errorMask.getAccess(), tcu::RGBA::green.toVec());
+
+	// Draw sample:
+	//	Sample sampleNdx is set to red channel
+	//	Other samples are set to green channel
+	drawSample(result, sample);
+
+	// Check surface contains only sampleNdx
+	for (int y = 0; y < m_canvasSize; ++y)
+	for (int x = 0; x < m_canvasSize; ++x)
+	{
+		const tcu::RGBA color					= result.getPixel(x, y);
+
+		// Allow missing coverage with FLAGS_ALPHA_TO_COVERAGE and FLAGS_SAMPLE_COVERAGE, or if there are no samples enabled
+		const bool		allowMissingCoverage	= ((m_flags & (FLAGS_ALPHA_TO_COVERAGE | FLAGS_SAMPLE_COVERAGE)) != 0) || (sample == -1);
+
+		// disabled sample was written to
+		if (color.getGreen() != 0)
+		{
+			error = true;
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+		}
+		// enabled sample was not written to
+		else if (color.getRed() != 255 && !allowMissingCoverage)
+		{
+			error = true;
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+		}
+	}
+
+	if (error)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message << "Image verification failed, disabled samples found." << tcu::TestLog::EndMessage
+			<< tcu::TestLog::ImageSet("VerificationResult", "Result of rendering")
+			<< tcu::TestLog::Image("Result",	"Result",		result)
+			<< tcu::TestLog::Image("ErrorMask",	"Error Mask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+		return false;
+	}
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Image verification ok, no disabled samples found." << tcu::TestLog::EndMessage;
+		return true;
+	}
+}
+
+void SampleMaskCase::drawSample (tcu::Surface& dst, int sample)
+{
+	// Downsample using only one sample
+	static const tcu::Vec4 fullscreenQuad[] =
+	{
+		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)
+	};
+
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	glu::Buffer				vertexBuffer	(m_context.getRenderContext());
+
+	gl.bindTexture				(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
+	gl.bindVertexArray			(m_vaoID);
+
+	gl.bindBuffer				(GL_ARRAY_BUFFER, *vertexBuffer);
+	gl.bufferData				(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), &fullscreenQuad[0], GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "bufferData");
+
+	gl.viewport					(0, 0, m_canvasSize, m_canvasSize);
+	gl.clearColor				(0, 0, 0, 1);
+	gl.clear					(GL_COLOR_BUFFER_BIT);
+	gl.vertexAttribPointer		(m_samplerProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray	(m_samplerProgramPosLoc);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "vertexAttribPointer");
+
+	gl.useProgram				(m_samplerProgram->getProgram());
+	gl.uniform1i				(m_samplerProgramSamplerLoc, 0);
+	gl.uniform1i				(m_samplerProgramSampleNdxLoc, (deInt32)sample);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "useprogram");
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Reading from texture with sampler shader, u_sample = " << sample << tcu::TestLog::EndMessage;
+
+	gl.drawArrays				(GL_TRIANGLE_STRIP, 0, 4);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "drawArrays");
+
+	gl.disableVertexAttribArray	(m_samplerProgramPosLoc);
+	gl.useProgram				(0);
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "cleanup");
+
+	gl.finish					();
+	glu::readPixels				(m_context.getRenderContext(), 0, 0, dst.getAccess());
+	GLU_EXPECT_NO_ERROR			(gl.getError(), "readPixels");
+}
+
+class MultisampleTextureUsageCase : public TestCase
+{
+public:
+
+	enum TextureType
+	{
+		TEXTURE_COLOR_2D = 0,
+		TEXTURE_COLOR_2D_ARRAY,
+		TEXTURE_INT_2D,
+		TEXTURE_INT_2D_ARRAY,
+		TEXTURE_UINT_2D,
+		TEXTURE_UINT_2D_ARRAY,
+		TEXTURE_DEPTH_2D,
+		TEXTURE_DEPTH_2D_ARRAY,
+
+		TEXTURE_LAST
+	};
+
+						MultisampleTextureUsageCase		(Context& ctx, const char* name, const char* desc, int samples, TextureType type);
+						~MultisampleTextureUsageCase	(void);
+
+private:
+	void				init							(void);
+	void				deinit							(void);
+	IterateResult		iterate							(void);
+
+	void				genDrawShader					(void);
+	void				genSamplerShader				(void);
+
+	void				renderToTexture					(float value);
+	void				sampleTexture					(tcu::Surface& dst, float value);
+	bool				verifyImage						(const tcu::Surface& dst);
+
+	static const int	s_textureSize					= 256;
+	static const int	s_textureArraySize				= 8;
+	static const int	s_textureLayer					= 3;
+
+	const TextureType	m_type;
+	const int			m_numSamples;
+
+	glw::GLuint			m_fboID;
+	glw::GLuint			m_textureID;
+
+	glu::ShaderProgram*	m_drawShader;
+	glu::ShaderProgram*	m_samplerShader;
+
+	const bool			m_isColorFormat;
+	const bool			m_isSignedFormat;
+	const bool			m_isUnsignedFormat;
+	const bool			m_isDepthFormat;
+	const bool			m_isArrayType;
+};
+
+MultisampleTextureUsageCase::MultisampleTextureUsageCase (Context& ctx, const char* name, const char* desc, int samples, TextureType type)
+	: TestCase			(ctx, name, desc)
+	, m_type			(type)
+	, m_numSamples		(samples)
+	, m_fboID			(0)
+	, m_textureID		(0)
+	, m_drawShader		(DE_NULL)
+	, m_samplerShader	(DE_NULL)
+	, m_isColorFormat	(m_type == TEXTURE_COLOR_2D	|| m_type == TEXTURE_COLOR_2D_ARRAY)
+	, m_isSignedFormat	(m_type == TEXTURE_INT_2D	|| m_type == TEXTURE_INT_2D_ARRAY)
+	, m_isUnsignedFormat(m_type == TEXTURE_UINT_2D	|| m_type == TEXTURE_UINT_2D_ARRAY)
+	, m_isDepthFormat	(m_type == TEXTURE_DEPTH_2D	|| m_type == TEXTURE_DEPTH_2D_ARRAY)
+	, m_isArrayType		(m_type == TEXTURE_COLOR_2D_ARRAY || m_type == TEXTURE_INT_2D_ARRAY || m_type == TEXTURE_UINT_2D_ARRAY || m_type == TEXTURE_DEPTH_2D_ARRAY)
+{
+	DE_ASSERT(m_type < TEXTURE_LAST);
+}
+
+MultisampleTextureUsageCase::~MultisampleTextureUsageCase (void)
+{
+	deinit();
+}
+
+void MultisampleTextureUsageCase::init (void)
+{
+	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
+	const glw::GLenum		internalFormat	= (m_isColorFormat) ? (GL_R8) : (m_isSignedFormat) ? (GL_R8I) : (m_isUnsignedFormat) ? (GL_R8UI) : (m_isDepthFormat) ? (GL_DEPTH_COMPONENT32F) : (0);
+	const glw::GLenum		textureTarget	= (m_isArrayType) ? (GL_TEXTURE_2D_MULTISAMPLE_ARRAY) : (GL_TEXTURE_2D_MULTISAMPLE);
+	const glw::GLenum		fboAttachment	= (m_isDepthFormat) ? (GL_DEPTH_ATTACHMENT) : (GL_COLOR_ATTACHMENT0);
+
+	DE_ASSERT(internalFormat);
+
+	// requirements
+
+	if (m_isArrayType && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
+		throw tcu::NotSupportedError("Test requires OES_texture_storage_multisample_2d_array extension");
+	if (m_context.getRenderTarget().getWidth() < s_textureSize || m_context.getRenderTarget().getHeight() < s_textureSize)
+		throw tcu::NotSupportedError("render target size must be at least " + de::toString(static_cast<int>(s_textureSize)) + "x" + de::toString(static_cast<int>(s_textureSize)));
+
+	{
+		glw::GLint maxSamples = 0;
+		gl.getInternalformativ(textureTarget, internalFormat, GL_SAMPLES, 1, &maxSamples);
+
+		if (m_numSamples > maxSamples)
+			throw tcu::NotSupportedError("Requested sample count is greater than supported");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << "Max sample count for " << glu::getPixelFormatStr(internalFormat) << ": " << maxSamples << tcu::TestLog::EndMessage;
+	}
+
+	{
+		GLint maxTextureSize = 0;
+		gl.getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+
+		if (s_textureSize > maxTextureSize)
+			throw tcu::NotSupportedError("Larger GL_MAX_TEXTURE_SIZE is required");
+	}
+
+	if (m_isArrayType)
+	{
+		GLint maxTextureLayers = 0;
+		gl.getIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxTextureLayers);
+
+		if (s_textureArraySize > maxTextureLayers)
+			throw tcu::NotSupportedError("Larger GL_MAX_ARRAY_TEXTURE_LAYERS is required");
+	}
+
+	// create texture
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Creating multisample " << ((m_isDepthFormat) ? ("depth") : ("")) << " texture" << ((m_isArrayType) ? (" array") : ("")) << tcu::TestLog::EndMessage;
+
+	gl.genTextures(1, &m_textureID);
+	gl.bindTexture(textureTarget, m_textureID);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind texture");
+
+	if (m_isArrayType)
+		gl.texStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, m_numSamples, internalFormat, s_textureSize, s_textureSize, s_textureArraySize, GL_FALSE);
+	else
+		gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_numSamples, internalFormat, s_textureSize, s_textureSize, GL_FALSE);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "texstorage");
+
+	// create fbo for drawing
+
+	gl.genFramebuffers(1, &m_fboID);
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
+
+	if (m_isArrayType)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Attaching multisample texture array layer " << static_cast<int>(s_textureLayer) << " to fbo" << tcu::TestLog::EndMessage;
+		gl.framebufferTextureLayer(GL_FRAMEBUFFER, fboAttachment, m_textureID, 0, s_textureLayer);
+	}
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Attaching multisample texture to fbo" << tcu::TestLog::EndMessage;
+		gl.framebufferTexture2D(GL_FRAMEBUFFER, fboAttachment, textureTarget, m_textureID, 0);
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");
+
+	// create shader for rendering to fbo
+	genDrawShader();
+
+	// create shader for sampling the texture rendered to
+	genSamplerShader();
+}
+
+void MultisampleTextureUsageCase::deinit (void)
+{
+	if (m_textureID)
+	{
+		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_textureID);
+		m_textureID = 0;
+	}
+
+	if (m_fboID)
+	{
+		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_fboID);
+		m_fboID = 0;
+	}
+
+	if (m_drawShader)
+	{
+		delete m_drawShader;
+		m_drawShader = DE_NULL;
+	}
+
+	if (m_samplerShader)
+	{
+		delete m_samplerShader;
+		m_samplerShader = DE_NULL;
+	}
+}
+
+MultisampleTextureUsageCase::IterateResult MultisampleTextureUsageCase::iterate (void)
+{
+	const tcu::ScopedLogSection	section			(m_testCtx.getLog(), "Sample", "Render to texture and sample texture");
+	tcu::Surface				result			(s_textureSize, s_textureSize);
+	const float					minValue		= (m_isColorFormat || m_isDepthFormat) ? (0.0f) : (m_isSignedFormat) ? (-100.0f) : (m_isUnsignedFormat) ? (0.0f)	: ( 1.0f);
+	const float					maxValue		= (m_isColorFormat || m_isDepthFormat) ? (1.0f) : (m_isSignedFormat) ? ( 100.0f) : (m_isUnsignedFormat) ? (200.0f)	: (-1.0f);
+	de::Random					rnd				(deUint32Hash((deUint32)m_type));
+	const float					rawValue		= rnd.getFloat(minValue, maxValue);
+	const float					preparedValue	= (m_isSignedFormat || m_isUnsignedFormat) ? (deFloatFloor(rawValue)) : (rawValue);
+
+	// draw to fbo with a random value
+
+	renderToTexture(preparedValue);
+
+	// draw from texture to front buffer
+
+	sampleTexture(result, preparedValue);
+
+	// result is ok?
+
+	if (verifyImage(result))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+
+	return STOP;
+}
+
+void MultisampleTextureUsageCase::genDrawShader (void)
+{
+	const tcu::ScopedLogSection section(m_testCtx.getLog(), "RenderShader", "Generate render-to-texture shader");
+
+	static const char* const	vertexShaderSource =		"#version 310 es\n"
+															"in highp vec4 a_position;\n"
+															"void main (void)\n"
+															"{\n"
+															"	gl_Position = a_position;\n"
+															"}\n";
+	static const char* const	fragmentShaderSourceColor =	"#version 310 es\n"
+															"layout(location = 0) out highp ${OUTTYPE} fragColor;\n"
+															"uniform highp float u_writeValue;\n"
+															"void main (void)\n"
+															"{\n"
+															"	fragColor = ${OUTTYPE}(vec4(u_writeValue, 1.0, 1.0, 1.0));\n"
+															"}\n";
+	static const char* const	fragmentShaderSourceDepth =	"#version 310 es\n"
+															"layout(location = 0) out highp vec4 fragColor;\n"
+															"uniform highp float u_writeValue;\n"
+															"void main (void)\n"
+															"{\n"
+															"	fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n"
+															"	gl_FragDepth = u_writeValue;\n"
+															"}\n";
+	const char* const			fragmentSource =			(m_isDepthFormat) ? (fragmentShaderSourceDepth) : (fragmentShaderSourceColor);
+
+	std::map<std::string, std::string> fragmentArguments;
+
+	if (m_isColorFormat || m_isDepthFormat)
+		fragmentArguments["OUTTYPE"] = "vec4";
+	else if (m_isSignedFormat)
+		fragmentArguments["OUTTYPE"] = "ivec4";
+	else if (m_isUnsignedFormat)
+		fragmentArguments["OUTTYPE"] = "uvec4";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	m_drawShader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(tcu::StringTemplate(fragmentSource).specialize(fragmentArguments)));
+	m_testCtx.getLog() << *m_drawShader;
+
+	if (!m_drawShader->isOk())
+		throw tcu::TestError("could not build shader");
+}
+
+void MultisampleTextureUsageCase::genSamplerShader (void)
+{
+	const tcu::ScopedLogSection section(m_testCtx.getLog(), "SamplerShader", "Generate texture sampler shader");
+
+	static const char* const vertexShaderSource =	"#version 310 es\n"
+													"in highp vec4 a_position;\n"
+													"out highp float v_gradient;\n"
+													"void main (void)\n"
+													"{\n"
+													"	gl_Position = a_position;\n"
+													"	v_gradient = a_position.x * 0.5 + 0.5;\n"
+													"}\n";
+	static const char* const fragmentShaderSource =	"#version 310 es\n"
+													"${EXTENSION_STATEMENT}"
+													"layout(location = 0) out highp vec4 fragColor;\n"
+													"uniform highp ${SAMPLERTYPE} u_sampler;\n"
+													"uniform highp int u_maxSamples;\n"
+													"uniform highp int u_layer;\n"
+													"uniform highp float u_cmpValue;\n"
+													"in highp float v_gradient;\n"
+													"void main (void)\n"
+													"{\n"
+													"	const highp vec4 okColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+													"	const highp vec4 failColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+													"	const highp float epsilon = ${EPSILON};\n"
+													"\n"
+													"	highp int sampleNdx = clamp(int(floor(v_gradient * float(u_maxSamples))), 0, u_maxSamples-1);\n"
+													"	highp float value = float(texelFetch(u_sampler, ${FETCHPOS}, sampleNdx).r);\n"
+													"	fragColor = (abs(u_cmpValue - value) < epsilon) ? (okColor) : (failColor);\n"
+													"}\n";
+
+	std::map<std::string, std::string> fragmentArguments;
+
+	if (m_isArrayType)
+		fragmentArguments["FETCHPOS"] = "ivec3(floor(gl_FragCoord.xy), u_layer)";
+	else
+		fragmentArguments["FETCHPOS"] = "ivec2(floor(gl_FragCoord.xy))";
+
+	if (m_isColorFormat || m_isDepthFormat)
+		fragmentArguments["EPSILON"] = "0.1";
+	else
+		fragmentArguments["EPSILON"] = "1.0";
+
+	if (m_isArrayType)
+		fragmentArguments["EXTENSION_STATEMENT"] = "#extension GL_OES_texture_storage_multisample_2d_array : require\n";
+	else
+		fragmentArguments["EXTENSION_STATEMENT"] = "";
+
+	switch (m_type)
+	{
+		case TEXTURE_COLOR_2D:			fragmentArguments["SAMPLERTYPE"] = "sampler2DMS";		break;
+		case TEXTURE_COLOR_2D_ARRAY:	fragmentArguments["SAMPLERTYPE"] = "sampler2DMSArray";	break;
+		case TEXTURE_INT_2D:			fragmentArguments["SAMPLERTYPE"] = "isampler2DMS";		break;
+		case TEXTURE_INT_2D_ARRAY:		fragmentArguments["SAMPLERTYPE"] = "isampler2DMSArray";	break;
+		case TEXTURE_UINT_2D:			fragmentArguments["SAMPLERTYPE"] = "usampler2DMS";		break;
+		case TEXTURE_UINT_2D_ARRAY:		fragmentArguments["SAMPLERTYPE"] = "usampler2DMSArray";	break;
+		case TEXTURE_DEPTH_2D:			fragmentArguments["SAMPLERTYPE"] = "sampler2DMS";		break;
+		case TEXTURE_DEPTH_2D_ARRAY:	fragmentArguments["SAMPLERTYPE"] = "sampler2DMSArray";	break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	m_samplerShader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(tcu::StringTemplate(fragmentShaderSource).specialize(fragmentArguments)));
+	m_testCtx.getLog() << *m_samplerShader;
+
+	if (!m_samplerShader->isOk())
+		throw tcu::TestError("could not build shader");
+}
+
+void MultisampleTextureUsageCase::renderToTexture (float value)
+{
+	static const tcu::Vec4 fullscreenQuad[] =
+	{
+		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),
+	};
+
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const int				posLocation			= gl.getAttribLocation(m_drawShader->getProgram(), "a_position");
+	const int				valueLocation		= gl.getUniformLocation(m_drawShader->getProgram(), "u_writeValue");
+	glu::Buffer				vertexAttibBuffer	(m_context.getRenderContext());
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Filling multisample texture with value " << value  << tcu::TestLog::EndMessage;
+
+	// upload data
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, *vertexAttibBuffer);
+	gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad[0].getPtr(), GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bufferdata");
+
+	// clear buffer
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
+	gl.viewport(0, 0, s_textureSize, s_textureSize);
+
+	if (m_isColorFormat)
+	{
+		const float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+		gl.clearBufferfv(GL_COLOR, 0, clearColor);
+	}
+	else if (m_isSignedFormat)
+	{
+		const deInt32 clearColor[4] = { 0, 0, 0, 0 };
+		gl.clearBufferiv(GL_COLOR, 0, clearColor);
+	}
+	else if (m_isUnsignedFormat)
+	{
+		const deUint32 clearColor[4] = { 0, 0, 0, 0 };
+		gl.clearBufferuiv(GL_COLOR, 0, clearColor);
+	}
+	else if (m_isDepthFormat)
+	{
+		const float clearDepth = 0.5f;
+		gl.clearBufferfv(GL_DEPTH, 0, &clearDepth);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clear buffer");
+
+	// setup shader and draw
+
+	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(posLocation);
+
+	gl.useProgram(m_drawShader->getProgram());
+	gl.uniform1f(valueLocation, value);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "setup draw");
+
+	if (m_isDepthFormat)
+	{
+		gl.enable(GL_DEPTH_TEST);
+		gl.depthFunc(GL_ALWAYS);
+	}
+
+	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
+
+	// clean state
+
+	if (m_isDepthFormat)
+		gl.disable(GL_DEPTH_TEST);
+
+	gl.disableVertexAttribArray(posLocation);
+	gl.useProgram(0);
+	gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clean");
+}
+
+void MultisampleTextureUsageCase::sampleTexture (tcu::Surface& dst, float value)
+{
+	static const tcu::Vec4 fullscreenQuad[] =
+	{
+		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),
+	};
+
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const int				posLocation			= gl.getAttribLocation(m_samplerShader->getProgram(), "a_position");
+	const int				samplerLocation		= gl.getUniformLocation(m_samplerShader->getProgram(), "u_sampler");
+	const int				maxSamplesLocation	= gl.getUniformLocation(m_samplerShader->getProgram(), "u_maxSamples");
+	const int				layerLocation		= gl.getUniformLocation(m_samplerShader->getProgram(), "u_layer");
+	const int				valueLocation		= gl.getUniformLocation(m_samplerShader->getProgram(), "u_cmpValue");
+	const glw::GLenum		textureTarget		= (m_isArrayType) ? (GL_TEXTURE_2D_MULTISAMPLE_ARRAY) : (GL_TEXTURE_2D_MULTISAMPLE);
+	glu::Buffer				vertexAttibBuffer	(m_context.getRenderContext());
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Sampling from texture." << tcu::TestLog::EndMessage;
+
+	// upload data
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, *vertexAttibBuffer);
+	gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad[0].getPtr(), GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bufferdata");
+
+	// clear
+
+	gl.viewport(0, 0, s_textureSize, s_textureSize);
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	// setup shader and draw
+
+	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+	gl.enableVertexAttribArray(posLocation);
+
+	gl.useProgram(m_samplerShader->getProgram());
+	gl.uniform1i(samplerLocation, 0);
+	gl.uniform1i(maxSamplesLocation, m_numSamples);
+	if (m_isArrayType)
+		gl.uniform1i(layerLocation, s_textureLayer);
+	gl.uniform1f(valueLocation, value);
+	gl.bindTexture(textureTarget, m_textureID);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "setup draw");
+
+	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
+
+	// clean state
+
+	gl.disableVertexAttribArray(posLocation);
+	gl.useProgram(0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "clean");
+
+	// read results
+	gl.finish();
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+}
+
+bool MultisampleTextureUsageCase::verifyImage (const tcu::Surface& dst)
+{
+	bool error = false;
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying image." << tcu::TestLog::EndMessage;
+
+	for (int y = 0; y < dst.getHeight(); ++y)
+	for (int x = 0; x < dst.getWidth(); ++x)
+	{
+		const tcu::RGBA color				= dst.getPixel(x, y);
+		const int		colorThresholdRed	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().redBits);
+		const int		colorThresholdGreen	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().greenBits);
+		const int		colorThresholdBlue	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().blueBits);
+
+		// only green is accepted
+		if (color.getRed() > colorThresholdRed || color.getGreen() < 255 - colorThresholdGreen || color.getBlue() > colorThresholdBlue)
+			error = true;
+	}
+
+	if (error)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Invalid pixels found." << tcu::TestLog::EndMessage;
+		m_testCtx.getLog()
+			<< tcu::TestLog::ImageSet("Verification result", "Result of rendering")
+			<< tcu::TestLog::Image("Result", "Result", dst)
+			<< tcu::TestLog::EndImageSet;
+
+		return false;
+	}
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
+		return true;
+	}
+}
+
+class NegativeFramebufferCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASE_DIFFERENT_N_SAMPLES_TEX = 0,
+		CASE_DIFFERENT_N_SAMPLES_RBO,
+		CASE_DIFFERENT_FIXED_TEX,
+		CASE_DIFFERENT_FIXED_RBO,
+		CASE_NON_ZERO_LEVEL,
+
+		CASE_LAST
+	};
+
+						NegativeFramebufferCase		(Context& context, const char* name, const char* desc, CaseType caseType);
+						~NegativeFramebufferCase	(void);
+
+private:
+	void				init						(void);
+	void				deinit						(void);
+	IterateResult		iterate						(void);
+
+	void				getFormatSamples			(glw::GLenum target, std::vector<int>& samples);
+
+	const CaseType		m_caseType;
+	const int			m_fboSize;
+	const glw::GLenum	m_internalFormat;
+
+	int					m_numSamples0;	// !< samples for attachment 0
+	int					m_numSamples1;	// !< samples for attachment 1
+};
+
+NegativeFramebufferCase::NegativeFramebufferCase (Context& context, const char* name, const char* desc, CaseType caseType)
+	: TestCase			(context, name, desc)
+	, m_caseType		(caseType)
+	, m_fboSize			(64)
+	, m_internalFormat	(GL_RGBA8)
+	, m_numSamples0		(-1)
+	, m_numSamples1		(-1)
+{
+}
+
+NegativeFramebufferCase::~NegativeFramebufferCase (void)
+{
+	deinit();
+}
+
+void NegativeFramebufferCase::init (void)
+{
+	const bool				colorAttachmentTexture	= (m_caseType == CASE_DIFFERENT_N_SAMPLES_TEX) || (m_caseType == CASE_DIFFERENT_FIXED_TEX);
+	const bool				colorAttachmentRbo		= (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO) || (m_caseType == CASE_DIFFERENT_FIXED_RBO);
+	const bool				useDifferentSampleCounts= (m_caseType == CASE_DIFFERENT_N_SAMPLES_TEX) || (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO);
+	std::vector<int>		textureSamples;
+	std::vector<int>		rboSamples;
+
+	getFormatSamples(GL_TEXTURE_2D_MULTISAMPLE, textureSamples);
+	getFormatSamples(GL_RENDERBUFFER, rboSamples);
+
+	TCU_CHECK(!textureSamples.empty());
+	TCU_CHECK(!rboSamples.empty());
+
+	// select sample counts
+
+	if (useDifferentSampleCounts)
+	{
+		if (colorAttachmentTexture)
+		{
+			m_numSamples0 = textureSamples[0];
+
+			if (textureSamples.size() >= 2)
+				m_numSamples1 = textureSamples[1];
+			else
+				throw tcu::NotSupportedError("Test requires multiple supported sample counts");
+		}
+		else if (colorAttachmentRbo)
+		{
+			for (int texNdx = 0; texNdx < (int)textureSamples.size(); ++texNdx)
+			for (int rboNdx = 0; rboNdx < (int)rboSamples.size(); ++rboNdx)
+			{
+				if (textureSamples[texNdx] != rboSamples[rboNdx])
+				{
+					m_numSamples0 = textureSamples[texNdx];
+					m_numSamples1 = rboSamples[rboNdx];
+					return;
+				}
+			}
+
+			throw tcu::NotSupportedError("Test requires multiple supported sample counts");
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+	else
+	{
+		if (colorAttachmentTexture)
+		{
+			m_numSamples0 = textureSamples[0];
+			m_numSamples1 = textureSamples[0];
+		}
+		else if (colorAttachmentRbo)
+		{
+			for (int texNdx = 0; texNdx < (int)textureSamples.size(); ++texNdx)
+			for (int rboNdx = 0; rboNdx < (int)rboSamples.size(); ++rboNdx)
+			{
+				if (textureSamples[texNdx] == rboSamples[rboNdx])
+				{
+					m_numSamples0 = textureSamples[texNdx];
+					m_numSamples1 = rboSamples[rboNdx];
+					return;
+				}
+			}
+
+			throw tcu::NotSupportedError("Test requires a sample count supported in both rbo and texture");
+		}
+		else
+		{
+			m_numSamples0 = textureSamples[0];
+		}
+	}
+}
+
+void NegativeFramebufferCase::deinit (void)
+{
+}
+
+NegativeFramebufferCase::IterateResult NegativeFramebufferCase::iterate (void)
+{
+	const bool				colorAttachmentTexture	= (m_caseType == CASE_DIFFERENT_N_SAMPLES_TEX) || (m_caseType == CASE_DIFFERENT_FIXED_TEX);
+	const bool				colorAttachmentRbo		= (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO) || (m_caseType == CASE_DIFFERENT_FIXED_RBO);
+	const glw::GLenum		fixedSampleLocations0	= (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO) ? (GL_TRUE) : (GL_FALSE);
+	const glw::GLenum		fixedSampleLocations1	= ((m_caseType == CASE_DIFFERENT_FIXED_TEX) || (m_caseType == CASE_DIFFERENT_FIXED_RBO)) ? (GL_TRUE) : (GL_FALSE);
+	glu::CallLogWrapper		gl						(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glw::GLuint				fboId					= 0;
+	glw::GLuint				rboId					= 0;
+	glw::GLuint				tex0Id					= 0;
+	glw::GLuint				tex1Id					= 0;
+
+	bool					testFailed				= false;
+
+	gl.enableLogging(true);
+
+	try
+	{
+		gl.glGenFramebuffers(1, &fboId);
+		gl.glBindFramebuffer(GL_FRAMEBUFFER, fboId);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen fbo");
+
+		gl.glGenTextures(1, &tex0Id);
+		gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex0Id);
+		gl.glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_numSamples0, m_internalFormat, m_fboSize, m_fboSize, fixedSampleLocations0);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen texture 0");
+
+		if (m_caseType == CASE_NON_ZERO_LEVEL)
+		{
+			glw::GLenum error;
+
+			// attaching non-zero level generates invalid value
+			gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex0Id, 5);
+			error = gl.glGetError();
+
+			if (error != GL_INVALID_VALUE)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_INVALID_VALUE, got " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+				testFailed = true;
+			}
+		}
+		else
+		{
+			gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex0Id, 0);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "attach to c0");
+
+			if (colorAttachmentTexture)
+			{
+				gl.glGenTextures(1, &tex1Id);
+				gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex1Id);
+				gl.glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_numSamples1, m_internalFormat, m_fboSize, m_fboSize, fixedSampleLocations1);
+				GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen texture 1");
+
+				gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D_MULTISAMPLE, tex1Id, 0);
+				GLU_EXPECT_NO_ERROR(gl.glGetError(), "attach to c1");
+			}
+			else if (colorAttachmentRbo)
+			{
+				gl.glGenRenderbuffers(1, &rboId);
+				gl.glBindRenderbuffer(GL_RENDERBUFFER, rboId);
+				gl.glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples1, m_internalFormat, m_fboSize, m_fboSize);
+				GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen rb");
+
+				gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, rboId);
+				GLU_EXPECT_NO_ERROR(gl.glGetError(), "attach to c1");
+			}
+			else
+				DE_ASSERT(DE_FALSE);
+
+			// should not be complete
+			{
+				glw::GLenum status = gl.glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+				if (status != GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
+				{
+					m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, got " << glu::getFramebufferStatusName(status) << tcu::TestLog::EndMessage;
+					testFailed = true;
+				}
+			}
+		}
+	}
+	catch (...)
+	{
+		gl.glDeleteFramebuffers(1, &fboId);
+		gl.glDeleteRenderbuffers(1, &rboId);
+		gl.glDeleteTextures(1, &tex0Id);
+		gl.glDeleteTextures(1, &tex1Id);
+		throw;
+	}
+
+	gl.glDeleteFramebuffers(1, &fboId);
+	gl.glDeleteRenderbuffers(1, &rboId);
+	gl.glDeleteTextures(1, &tex0Id);
+	gl.glDeleteTextures(1, &tex1Id);
+
+	if (testFailed)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong error code");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+void NegativeFramebufferCase::getFormatSamples (glw::GLenum target, std::vector<int>& samples)
+{
+	const glw::Functions	gl			= m_context.getRenderContext().getFunctions();
+	int						sampleCount	= 0;
+
+	gl.getInternalformativ(target, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &sampleCount);
+	samples.resize(sampleCount);
+
+	if (sampleCount > 0)
+	{
+		gl.getInternalformativ(target, m_internalFormat, GL_SAMPLES, sampleCount, &samples[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "get max samples");
+	}
+}
+
+class NegativeTexParameterCase : public TestCase
+{
+public:
+	enum TexParam
+	{
+		TEXTURE_MIN_FILTER = 0,
+		TEXTURE_MAG_FILTER,
+		TEXTURE_WRAP_S,
+		TEXTURE_WRAP_T,
+		TEXTURE_WRAP_R,
+		TEXTURE_MIN_LOD,
+		TEXTURE_MAX_LOD,
+		TEXTURE_COMPARE_MODE,
+		TEXTURE_COMPARE_FUNC,
+		TEXTURE_BASE_LEVEL,
+
+		TEXTURE_LAST
+	};
+
+					NegativeTexParameterCase	(Context& context, const char* name, const char* desc, TexParam param);
+					~NegativeTexParameterCase	(void);
+
+private:
+	void			init						(void);
+	void			deinit						(void);
+	IterateResult	iterate						(void);
+
+	glw::GLenum		getParamGLEnum				(void) const;
+	glw::GLint		getParamValue				(void) const;
+	glw::GLenum		getExpectedError			(void) const;
+
+	const TexParam	m_texParam;
+	int				m_iteration;
+};
+
+NegativeTexParameterCase::NegativeTexParameterCase (Context& context, const char* name, const char* desc, TexParam param)
+	: TestCase		(context, name, desc)
+	, m_texParam	(param)
+	, m_iteration	(0)
+{
+	DE_ASSERT(param < TEXTURE_LAST);
+}
+
+NegativeTexParameterCase::~NegativeTexParameterCase	(void)
+{
+	deinit();
+}
+
+void NegativeTexParameterCase::init (void)
+{
+	// default value
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void NegativeTexParameterCase::deinit (void)
+{
+}
+
+NegativeTexParameterCase::IterateResult NegativeTexParameterCase::iterate (void)
+{
+	static const struct TextureType
+	{
+		const char*	name;
+		glw::GLenum	target;
+		glw::GLenum	internalFormat;
+		bool		isArrayType;
+	} types[] =
+	{
+		{ "color",					GL_TEXTURE_2D_MULTISAMPLE,			GL_RGBA8,	false	},
+		{ "color array",			GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	GL_RGBA8,	true	},
+		{ "signed integer",			GL_TEXTURE_2D_MULTISAMPLE,			GL_R8I,		false	},
+		{ "signed integer array",	GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	GL_R8I,		true	},
+		{ "unsigned integer",		GL_TEXTURE_2D_MULTISAMPLE,			GL_R8UI,	false	},
+		{ "unsigned integer array",	GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	GL_R8UI,	true	},
+	};
+
+	const tcu::ScopedLogSection scope(m_testCtx.getLog(), "Iteration", std::string() + "Testing parameter with " + types[m_iteration].name + " texture");
+
+	if (types[m_iteration].isArrayType && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
+		m_testCtx.getLog() << tcu::TestLog::Message << "GL_OES_texture_storage_multisample_2d_array not supported, skipping target" << tcu::TestLog::EndMessage;
+	else
+	{
+		glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+		glu::Texture			texture	(m_context.getRenderContext());
+		glw::GLenum				error;
+
+		gl.enableLogging(true);
+
+		// gen texture
+
+		gl.glBindTexture(types[m_iteration].target, *texture);
+
+		if (types[m_iteration].isArrayType)
+			gl.glTexStorage3DMultisample(types[m_iteration].target, 1, types[m_iteration].internalFormat, 16, 16, 16, GL_FALSE);
+		else
+			gl.glTexStorage2DMultisample(types[m_iteration].target, 1, types[m_iteration].internalFormat, 16, 16, GL_FALSE);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup texture");
+
+		// set param
+
+		gl.glTexParameteri(types[m_iteration].target, getParamGLEnum(), getParamValue());
+		error = gl.glGetError();
+
+		// expect failure
+
+		if (error != getExpectedError())
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "Got unexpected error: " << glu::getErrorStr(error) << ", expected: " << glu::getErrorStr(getExpectedError()) << tcu::TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong error code");
+		}
+	}
+
+	if (++m_iteration < DE_LENGTH_OF_ARRAY(types))
+		return CONTINUE;
+	return STOP;
+}
+
+glw::GLenum NegativeTexParameterCase::getParamGLEnum (void) const
+{
+	switch (m_texParam)
+	{
+		case TEXTURE_MIN_FILTER:	return GL_TEXTURE_MIN_FILTER;
+		case TEXTURE_MAG_FILTER:	return GL_TEXTURE_MAG_FILTER;
+		case TEXTURE_WRAP_S:		return GL_TEXTURE_WRAP_S;
+		case TEXTURE_WRAP_T:		return GL_TEXTURE_WRAP_T;
+		case TEXTURE_WRAP_R:		return GL_TEXTURE_WRAP_R;
+		case TEXTURE_MIN_LOD:		return GL_TEXTURE_MIN_LOD;
+		case TEXTURE_MAX_LOD:		return GL_TEXTURE_MAX_LOD;
+		case TEXTURE_COMPARE_MODE:	return GL_TEXTURE_COMPARE_MODE;
+		case TEXTURE_COMPARE_FUNC:	return GL_TEXTURE_COMPARE_FUNC;
+		case TEXTURE_BASE_LEVEL:	return GL_TEXTURE_BASE_LEVEL;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+glw::GLint NegativeTexParameterCase::getParamValue (void) const
+{
+	switch (m_texParam)
+	{
+		case TEXTURE_MIN_FILTER:	return GL_LINEAR;
+		case TEXTURE_MAG_FILTER:	return GL_LINEAR;
+		case TEXTURE_WRAP_S:		return GL_CLAMP_TO_EDGE;
+		case TEXTURE_WRAP_T:		return GL_CLAMP_TO_EDGE;
+		case TEXTURE_WRAP_R:		return GL_CLAMP_TO_EDGE;
+		case TEXTURE_MIN_LOD:		return 1;
+		case TEXTURE_MAX_LOD:		return 5;
+		case TEXTURE_COMPARE_MODE:	return GL_NONE;
+		case TEXTURE_COMPARE_FUNC:	return GL_NOTEQUAL;
+		case TEXTURE_BASE_LEVEL:	return 2;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+glw::GLenum NegativeTexParameterCase::getExpectedError (void) const
+{
+	switch (m_texParam)
+	{
+		case TEXTURE_MIN_FILTER:	return GL_INVALID_ENUM;
+		case TEXTURE_MAG_FILTER:	return GL_INVALID_ENUM;
+		case TEXTURE_WRAP_S:		return GL_INVALID_ENUM;
+		case TEXTURE_WRAP_T:		return GL_INVALID_ENUM;
+		case TEXTURE_WRAP_R:		return GL_INVALID_ENUM;
+		case TEXTURE_MIN_LOD:		return GL_INVALID_ENUM;
+		case TEXTURE_MAX_LOD:		return GL_INVALID_ENUM;
+		case TEXTURE_COMPARE_MODE:	return GL_INVALID_ENUM;
+		case TEXTURE_COMPARE_FUNC:	return GL_INVALID_ENUM;
+		case TEXTURE_BASE_LEVEL:	return GL_INVALID_OPERATION;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return 0;
+	}
+}
+
+class NegativeTexureSampleCase : public TestCase
+{
+public:
+	enum SampleCountParam
+	{
+		SAMPLECOUNT_HIGH = 0,
+		SAMPLECOUNT_ZERO,
+
+		SAMPLECOUNT_LAST
+	};
+
+							NegativeTexureSampleCase	(Context& context, const char* name, const char* desc, SampleCountParam param);
+private:
+	IterateResult			iterate						(void);
+
+	const SampleCountParam	m_sampleParam;
+};
+
+NegativeTexureSampleCase::NegativeTexureSampleCase (Context& context, const char* name, const char* desc, SampleCountParam param)
+	: TestCase		(context, name, desc)
+	, m_sampleParam	(param)
+{
+	DE_ASSERT(param < SAMPLECOUNT_LAST);
+}
+
+NegativeTexureSampleCase::IterateResult NegativeTexureSampleCase::iterate (void)
+{
+	const glw::GLenum		expectedError	= (m_sampleParam == SAMPLECOUNT_HIGH) ? (GL_INVALID_OPERATION) : (GL_INVALID_VALUE);
+	glu::CallLogWrapper		gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::Texture			texture			(m_context.getRenderContext());
+	glw::GLenum				error;
+	int						samples			= -1;
+
+	gl.enableLogging(true);
+
+	// calc samples
+
+	if (m_sampleParam == SAMPLECOUNT_HIGH)
+	{
+		int maxSamples = 0;
+
+		gl.glGetInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_SAMPLES, 1, &maxSamples);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetInternalformativ");
+
+		samples = maxSamples + 1;
+	}
+	else if (m_sampleParam == SAMPLECOUNT_ZERO)
+		samples = 0;
+	else
+		DE_ASSERT(DE_FALSE);
+
+	// create texture with bad values
+
+	gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, *texture);
+	gl.glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA8, 64, 64, GL_FALSE);
+	error = gl.glGetError();
+
+	// expect failure
+
+	if (error == expectedError)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "Got unexpected error: " << glu::getErrorStr(error) << ", expected: " << glu::getErrorStr(expectedError) << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong error code");
+	}
+
+	return STOP;
+}
+
+
+} // anonymous
+
+TextureMultisampleTests::TextureMultisampleTests (Context& context)
+	: TestCaseGroup(context, "multisample", "Multisample texture tests")
+{
+}
+
+TextureMultisampleTests::~TextureMultisampleTests (void)
+{
+}
+
+void TextureMultisampleTests::init (void)
+{
+	static const int sampleCounts[] = { 1, 2, 3, 4, 8, 10, 12, 13, 16, 64 };
+
+	static const struct TextureType
+	{
+		const char*									name;
+		MultisampleTextureUsageCase::TextureType	type;
+	} textureTypes[] =
+	{
+		{ "texture_color_2d",		MultisampleTextureUsageCase::TEXTURE_COLOR_2D		},
+		{ "texture_color_2d_array",	MultisampleTextureUsageCase::TEXTURE_COLOR_2D_ARRAY	},
+		{ "texture_int_2d",			MultisampleTextureUsageCase::TEXTURE_INT_2D			},
+		{ "texture_int_2d_array",	MultisampleTextureUsageCase::TEXTURE_INT_2D_ARRAY	},
+		{ "texture_uint_2d",		MultisampleTextureUsageCase::TEXTURE_UINT_2D		},
+		{ "texture_uint_2d_array",	MultisampleTextureUsageCase::TEXTURE_UINT_2D_ARRAY	},
+		{ "texture_depth_2d",		MultisampleTextureUsageCase::TEXTURE_DEPTH_2D		},
+		{ "texture_depth_2d_array",	MultisampleTextureUsageCase::TEXTURE_DEPTH_2D_ARRAY	},
+	};
+
+	// .samples_x
+	for (int sampleNdx = 0; sampleNdx < DE_LENGTH_OF_ARRAY(sampleCounts); ++sampleNdx)
+	{
+		tcu::TestCaseGroup* const sampleGroup = new tcu::TestCaseGroup(m_testCtx, (std::string("samples_") + de::toString(sampleCounts[sampleNdx])).c_str(), "Test with N samples");
+		addChild(sampleGroup);
+
+		// position query works
+		sampleGroup->addChild(new SamplePosRasterizationTest(m_context, "sample_position", "test SAMPLE_POSITION", sampleCounts[sampleNdx]));
+
+		// sample mask is ANDed properly
+		sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_only",											"Test with SampleMask only",									sampleCounts[sampleNdx],	SampleMaskCase::FLAGS_NONE));
+		sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_and_alpha_to_coverage",						"Test with SampleMask and alpha to coverage",					sampleCounts[sampleNdx],	SampleMaskCase::FLAGS_ALPHA_TO_COVERAGE));
+		sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_and_sample_coverage",							"Test with SampleMask and sample coverage",						sampleCounts[sampleNdx],	SampleMaskCase::FLAGS_SAMPLE_COVERAGE));
+		sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_and_sample_coverage_and_alpha_to_coverage",	"Test with SampleMask, sample coverage, and alpha to coverage",	sampleCounts[sampleNdx],	SampleMaskCase::FLAGS_ALPHA_TO_COVERAGE | SampleMaskCase::FLAGS_SAMPLE_COVERAGE));
+
+		// high bits cause no unexpected behavior
+		sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_non_effective_bits",							"Test with SampleMask, set higher bits than sample count",		sampleCounts[sampleNdx],	SampleMaskCase::FLAGS_HIGH_BITS));
+
+		// usage
+		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(textureTypes); ++typeNdx)
+			sampleGroup->addChild(new MultisampleTextureUsageCase(m_context, (std::string("use_") + textureTypes[typeNdx].name).c_str(), textureTypes[typeNdx].name, sampleCounts[sampleNdx], textureTypes[typeNdx].type));
+	}
+
+	// .negative
+	{
+		tcu::TestCaseGroup* const negativeGroup = new tcu::TestCaseGroup(m_testCtx, "negative", "Negative tests");
+		addChild(negativeGroup);
+
+		negativeGroup->addChild(new NegativeFramebufferCase	(m_context, "fbo_attach_different_sample_count_tex_tex",	"Attach different sample counts",	NegativeFramebufferCase::CASE_DIFFERENT_N_SAMPLES_TEX));
+		negativeGroup->addChild(new NegativeFramebufferCase	(m_context, "fbo_attach_different_sample_count_tex_rbo",	"Attach different sample counts",	NegativeFramebufferCase::CASE_DIFFERENT_N_SAMPLES_RBO));
+		negativeGroup->addChild(new NegativeFramebufferCase	(m_context, "fbo_attach_different_fixed_state_tex_tex",		"Attach fixed and non fixed",		NegativeFramebufferCase::CASE_DIFFERENT_FIXED_TEX));
+		negativeGroup->addChild(new NegativeFramebufferCase	(m_context, "fbo_attach_different_fixed_state_tex_rbo",		"Attach fixed and non fixed",		NegativeFramebufferCase::CASE_DIFFERENT_FIXED_RBO));
+		negativeGroup->addChild(new NegativeFramebufferCase	(m_context, "fbo_attach_non_zero_level",					"Attach non-zero level",			NegativeFramebufferCase::CASE_NON_ZERO_LEVEL));
+		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_min_filter",							"set TEXTURE_MIN_FILTER",			NegativeTexParameterCase::TEXTURE_MIN_FILTER));
+		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_mag_filter",							"set TEXTURE_MAG_FILTER",			NegativeTexParameterCase::TEXTURE_MAG_FILTER));
+		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_wrap_s",								"set TEXTURE_WRAP_S",				NegativeTexParameterCase::TEXTURE_WRAP_S));
+		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_wrap_t",								"set TEXTURE_WRAP_T",				NegativeTexParameterCase::TEXTURE_WRAP_T));
+		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_wrap_r",								"set TEXTURE_WRAP_R",				NegativeTexParameterCase::TEXTURE_WRAP_R));
+		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_min_lod",								"set TEXTURE_MIN_LOD",				NegativeTexParameterCase::TEXTURE_MIN_LOD));
+		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_max_lod",								"set TEXTURE_MAX_LOD",				NegativeTexParameterCase::TEXTURE_MAX_LOD));
+		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_compare_mode",							"set TEXTURE_COMPARE_MODE",			NegativeTexParameterCase::TEXTURE_COMPARE_MODE));
+		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_compare_func",							"set TEXTURE_COMPARE_FUNC",			NegativeTexParameterCase::TEXTURE_COMPARE_FUNC));
+		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_base_level",							"set TEXTURE_BASE_LEVEL",			NegativeTexParameterCase::TEXTURE_BASE_LEVEL));
+		negativeGroup->addChild(new NegativeTexureSampleCase(m_context, "texture_high_sample_count",					"TexStorage with high numSamples",	NegativeTexureSampleCase::SAMPLECOUNT_HIGH));
+		negativeGroup->addChild(new NegativeTexureSampleCase(m_context, "texture_zero_sample_count",					"TexStorage with zero numSamples",	NegativeTexureSampleCase::SAMPLECOUNT_ZERO));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fTextureMultisampleTests.hpp b/modules/gles31/functional/es31fTextureMultisampleTests.hpp
new file mode 100644
index 0000000..de3e1b3
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureMultisampleTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FTEXTUREMULTISAMPLETESTS_HPP
+#define _ES31FTEXTUREMULTISAMPLETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Multisample texture tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class TextureMultisampleTests : public TestCaseGroup
+{
+public:
+								TextureMultisampleTests		(Context& context);
+								~TextureMultisampleTests	(void);
+
+	void						init						(void);
+
+private:
+								TextureMultisampleTests		(const TextureMultisampleTests& other);
+	TextureMultisampleTests&	operator=					(const TextureMultisampleTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FTEXTUREMULTISAMPLETESTS_HPP
diff --git a/modules/gles31/functional/es31fTextureSpecificationTests.cpp b/modules/gles31/functional/es31fTextureSpecificationTests.cpp
new file mode 100644
index 0000000..302d1b4
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureSpecificationTests.cpp
@@ -0,0 +1,1103 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture specification tests.
+ *
+ * \todo [pyry] Following tests are missing:
+ *  - Specify mipmap incomplete texture, use without mipmaps, re-specify
+ *    as complete and render.
+ *  - Randomly re-specify levels to eventually reach mipmap-complete texture.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fTextureSpecificationTests.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVectorUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "sglrContextUtil.hpp"
+#include "sglrContextWrapper.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+// \todo [2012-04-29 pyry] Should be named SglrUtil
+#include "es31fFboTestUtil.hpp"
+
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+using std::string;
+using std::vector;
+using std::pair;
+using tcu::TestLog;
+using tcu::Vec4;
+using tcu::IVec4;
+using tcu::UVec4;
+using namespace FboTestUtil;
+
+enum
+{
+	VIEWPORT_WIDTH	= 256,
+	VIEWPORT_HEIGHT	= 256
+};
+
+static inline int maxLevelCount (int size)
+{
+	return (int)deLog2Floor32(size)+1;
+}
+
+template <int Size>
+static tcu::Vector<float, Size> randomVector (de::Random& rnd, const tcu::Vector<float, Size>& minVal = tcu::Vector<float, Size>(0.0f), const tcu::Vector<float, Size>& maxVal = tcu::Vector<float, Size>(1.0f))
+{
+	tcu::Vector<float, Size> res;
+	for (int ndx = 0; ndx < Size; ndx++)
+		res[ndx] = rnd.getFloat(minVal[ndx], maxVal[ndx]);
+	return res;
+}
+
+static tcu::CubeFace getCubeFaceFromNdx (int ndx)
+{
+	switch (ndx)
+	{
+		case 0:	return tcu::CUBEFACE_POSITIVE_X;
+		case 1:	return tcu::CUBEFACE_NEGATIVE_X;
+		case 2:	return tcu::CUBEFACE_POSITIVE_Y;
+		case 3:	return tcu::CUBEFACE_NEGATIVE_Y;
+		case 4:	return tcu::CUBEFACE_POSITIVE_Z;
+		case 5:	return tcu::CUBEFACE_NEGATIVE_Z;
+		default:
+			DE_ASSERT(false);
+			return tcu::CUBEFACE_LAST;
+	}
+}
+
+class TextureSpecCase : public TestCase, public sglr::ContextWrapper
+{
+public:
+						TextureSpecCase			(Context& context, const char* name, const char* desc);
+						~TextureSpecCase		(void);
+
+	IterateResult		iterate					(void);
+
+protected:
+	virtual bool		checkExtensionSupport	(void)	{ return true; }
+
+	virtual void		createTexture			(void)																= DE_NULL;
+	virtual void		verifyTexture			(sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext)	= DE_NULL;
+
+	// Utilities.
+	void				renderTex				(tcu::Surface& dst, deUint32 program, int width, int height);
+	void				readPixels				(tcu::Surface& dst, int x, int y, int width, int height);
+
+private:
+						TextureSpecCase			(const TextureSpecCase& other);
+	TextureSpecCase&	operator=				(const TextureSpecCase& other);
+};
+
+TextureSpecCase::TextureSpecCase (Context& context, const char* name, const char* desc)
+	: TestCase(context, name, desc)
+{
+}
+
+TextureSpecCase::~TextureSpecCase (void)
+{
+}
+
+TextureSpecCase::IterateResult TextureSpecCase::iterate (void)
+{
+	glu::RenderContext&			renderCtx				= TestCase::m_context.getRenderContext();
+	const tcu::RenderTarget&	renderTarget			= renderCtx.getRenderTarget();
+	tcu::TestLog&				log						= m_testCtx.getLog();
+
+	if (renderTarget.getWidth() < VIEWPORT_WIDTH || renderTarget.getHeight() < VIEWPORT_HEIGHT)
+		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);
+
+	if (!checkExtensionSupport())
+		throw tcu::NotSupportedError("Extension not supported", "", __FILE__, __LINE__);
+
+	// Context size, and viewport for GLES3.1
+	de::Random	rnd			(deStringHash(getName()));
+	int			width		= deMin32(renderTarget.getWidth(),	VIEWPORT_WIDTH);
+	int			height		= deMin32(renderTarget.getHeight(),	VIEWPORT_HEIGHT);
+	int			x			= rnd.getInt(0, renderTarget.getWidth()		- width);
+	int			y			= rnd.getInt(0, renderTarget.getHeight()	- height);
+
+	// Contexts.
+	sglr::GLContext					gles31Context	(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(x, y, width, height));
+	sglr::ReferenceContextBuffers	refBuffers		(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), 0 /* depth */, 0 /* stencil */, width, height);
+	sglr::ReferenceContext			refContext		(sglr::ReferenceContextLimits(renderCtx), refBuffers.getColorbuffer(), refBuffers.getDepthbuffer(), refBuffers.getStencilbuffer());
+
+	// Clear color buffer.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles31Context);
+		glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
+		glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+	}
+
+	// Construct texture using both GLES3.1 and reference contexts.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles31Context);
+		createTexture();
+		TCU_CHECK(glGetError() == GL_NO_ERROR);
+	}
+
+	// Initialize case result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Disable logging.
+	gles31Context.enableLogging(0);
+
+	// Verify results.
+	verifyTexture(gles31Context, refContext);
+
+	return STOP;
+}
+
+void TextureSpecCase::renderTex (tcu::Surface& dst, deUint32 program, int width, int height)
+{
+	int		targetW		= getWidth();
+	int		targetH		= getHeight();
+
+	float	w			= (float)width	/ (float)targetW;
+	float	h			= (float)height	/ (float)targetH;
+
+	sglr::drawQuad(*getCurrentContext(), program, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(-1.0f + w*2.0f, -1.0f + h*2.0f, 0.0f));
+
+	// Read pixels back.
+	readPixels(dst, 0, 0, width, height);
+}
+
+void TextureSpecCase::readPixels (tcu::Surface& dst, int x, int y, int width, int height)
+{
+	dst.setSize(width, height);
+	glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr());
+}
+
+class TextureCubeArraySpecCase : public TextureSpecCase
+{
+public:
+							TextureCubeArraySpecCase	(Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int size, int depth, int numLevels);
+							~TextureCubeArraySpecCase	(void);
+
+protected:
+	virtual bool			checkExtensionSupport		(void);
+	virtual void			verifyTexture				(sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext);
+
+	tcu::TextureFormat		m_texFormat;
+	tcu::TextureFormatInfo	m_texFormatInfo;
+	int						m_size;
+	int						m_depth;
+	int						m_numLevels;
+};
+
+TextureCubeArraySpecCase::TextureCubeArraySpecCase (Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int size, int depth, int numLevels)
+	: TextureSpecCase		(context, name, desc)
+	, m_texFormat			(format)
+	, m_texFormatInfo		(tcu::getTextureFormatInfo(format))
+	, m_size				(size)
+	, m_depth				(depth)
+	, m_numLevels			(numLevels)
+{
+}
+
+TextureCubeArraySpecCase::~TextureCubeArraySpecCase (void)
+{
+}
+
+bool TextureCubeArraySpecCase::checkExtensionSupport (void)
+{
+	return m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_cube_map_array");
+}
+
+void TextureCubeArraySpecCase::verifyTexture (sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext)
+{
+	TextureCubeArrayShader	shader			(glu::getSamplerCubeArrayType(m_texFormat), glu::TYPE_FLOAT_VEC4);
+	deUint32				shaderIDgles	= gles3Context.createProgram(&shader);
+	deUint32				shaderIDRef		= refContext.createProgram(&shader);
+
+	shader.setTexScaleBias(m_texFormatInfo.lookupScale, m_texFormatInfo.lookupBias);
+
+	// Set state.
+	for (int ndx = 0; ndx < 2; ndx++)
+	{
+		sglr::Context* ctx = ndx ? static_cast<sglr::Context*>(&refContext) : static_cast<sglr::Context*>(&gles3Context);
+
+		setContext(ctx);
+
+		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R,		GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAX_LEVEL,	m_numLevels-1);
+	}
+
+	for (int layerFaceNdx = 0; layerFaceNdx < m_depth; layerFaceNdx++)
+	{
+		const int			layerNdx	= layerFaceNdx / 6;
+		const tcu::CubeFace	face		= getCubeFaceFromNdx(layerFaceNdx % 6);
+		bool				layerOk		= true;
+
+		shader.setLayer(layerNdx);
+		shader.setFace(face);
+
+		for (int levelNdx = 0; levelNdx < m_numLevels; levelNdx++)
+		{
+			int				levelSize	= de::max(1, m_size	>> levelNdx);
+			tcu::Surface	reference;
+			tcu::Surface	result;
+
+			if (levelSize <= 2)
+				continue; // Fuzzy compare doesn't work for images this small.
+
+			for (int ndx = 0; ndx < 2; ndx++)
+			{
+				tcu::Surface&	dst			= ndx ? reference									: result;
+				sglr::Context*	ctx			= ndx ? static_cast<sglr::Context*>(&refContext)	: static_cast<sglr::Context*>(&gles3Context);
+				deUint32		shaderID	= ndx ? shaderIDRef									: shaderIDgles;
+
+				setContext(ctx);
+				shader.setUniforms(*ctx, shaderID);
+				renderTex(dst, shaderID, levelSize, levelSize);
+			}
+
+			const float		threshold		= 0.02f;
+			string			levelStr		= de::toString(levelNdx);
+			string			layerFaceStr	= de::toString(layerFaceNdx);
+			string			name			= string("LayerFace") + layerFaceStr + "Level" + levelStr;
+			string			desc			= string("Layer-face ") + layerFaceStr + ", Level " + levelStr;
+			bool			isFaceOk		= tcu::fuzzyCompare(m_testCtx.getLog(), name.c_str(), desc.c_str(), reference, result, threshold,
+																(levelNdx == 0 && layerFaceNdx == 0) == 0 ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);
+
+			if (!isFaceOk)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+				layerOk = false;
+				break;
+			}
+		}
+
+		if (!layerOk)
+			break;
+	}
+}
+
+// Basic TexImage3D() with cube map array texture usage
+class BasicTexImageCubeArrayCase : public TextureCubeArraySpecCase
+{
+public:
+	BasicTexImageCubeArrayCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size, int numLayers)
+		: TextureCubeArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, numLayers, maxLevelCount(size))
+		, m_internalFormat			(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32				tex			= 0;
+		tcu::TextureLevel		levelData	(m_texFormat);
+		de::Random				rnd			(deStringHash(getName()));
+		glu::TransferFormat		transferFmt	= glu::getTransferFormat(m_texFormat);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_size	>> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			levelData.setSize(levelW, levelW, m_depth);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, ndx, m_internalFormat, levelW, levelW, m_depth, 0, transferFmt.format, transferFmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Basic glTexStorage3D() with cube map array texture usage
+class BasicTexStorageCubeArrayCase : public TextureCubeArraySpecCase
+{
+public:
+	BasicTexStorageCubeArrayCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size, int numLayers, int numLevels)
+		: TextureCubeArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, numLayers, numLevels)
+		, m_internalFormat			(internalFormat)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		deUint32				tex			= 0;
+		tcu::TextureLevel		levelData	(m_texFormat);
+		de::Random				rnd			(deStringHash(getName()));
+		glu::TransferFormat		transferFmt	= glu::getTransferFormat(m_texFormat);
+
+		glGenTextures	(1, &tex);
+		glBindTexture	(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
+		glTexStorage3D	(GL_TEXTURE_CUBE_MAP_ARRAY, m_numLevels, m_internalFormat, m_size, m_size, m_depth);
+
+		glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			int		levelW		= de::max(1, m_size	>> ndx);
+			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+
+			levelData.setSize(levelW, levelW, m_depth);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, ndx, 0, 0, 0, levelW, levelW, m_depth, transferFmt.format, transferFmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	deUint32 m_internalFormat;
+};
+
+// Pixel buffer object cases.
+
+// TexImage3D() cube map array from pixel buffer object.
+class TexImageCubeArrayBufferCase : public TextureCubeArraySpecCase
+{
+public:
+	TexImageCubeArrayBufferCase (Context&		context,
+							   const char*	name,
+							   const char*	desc,
+							   deUint32		internalFormat,
+							   int			size,
+							   int			depth,
+							   int			imageHeight,
+							   int			rowLength,
+							   int			skipImages,
+							   int			skipRows,
+							   int			skipPixels,
+							   int			alignment,
+							   int			offset)
+		: TextureCubeArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, depth, 1)
+		, m_internalFormat			(internalFormat)
+		, m_imageHeight				(imageHeight)
+		, m_rowLength				(rowLength)
+		, m_skipImages				(skipImages)
+		, m_skipRows				(skipRows)
+		, m_skipPixels				(skipPixels)
+		, m_alignment				(alignment)
+		, m_offset					(offset)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		int						rowLength		= m_rowLength > 0 ? m_rowLength : m_size;
+		int						rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
+		int						imageHeight		= m_imageHeight > 0 ? m_imageHeight : m_size;
+		int						slicePitch		= imageHeight*rowPitch;
+		deUint32				tex				= 0;
+		deUint32				buf				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		// Fill data with grid.
+		data.resize(slicePitch*(m_depth+m_skipImages) + m_offset);
+		{
+			Vec4	cScale		= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias		= m_texFormatInfo.valueMin;
+			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_size, m_size, m_depth, rowPitch, slicePitch, &data[0] + m_skipImages*slicePitch + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
+		}
+
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER, (int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	m_imageHeight);
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	m_skipImages);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
+		glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, m_internalFormat, m_size, m_size, m_depth, 0, transferFmt.format, transferFmt.dataType, (const void*)(deUintptr)m_offset);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_imageHeight;
+	int			m_rowLength;
+	int			m_skipImages;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+	int			m_offset;
+};
+
+// TexSubImage3D() cube map array PBO case.
+class TexSubImageCubeArrayBufferCase : public TextureCubeArraySpecCase
+{
+public:
+	TexSubImageCubeArrayBufferCase (Context&		context,
+								 const char*	name,
+								 const char*	desc,
+								 deUint32		internalFormat,
+								 int			size,
+								 int			depth,
+								 int			subX,
+								 int			subY,
+								 int			subZ,
+								 int			subW,
+								 int			subH,
+								 int			subD,
+								 int			imageHeight,
+								 int			rowLength,
+								 int			skipImages,
+								 int			skipRows,
+								 int			skipPixels,
+								 int			alignment,
+								 int			offset)
+		: TextureCubeArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, depth, 1)
+		, m_internalFormat			(internalFormat)
+		, m_subX					(subX)
+		, m_subY					(subY)
+		, m_subZ					(subZ)
+		, m_subW					(subW)
+		, m_subH					(subH)
+		, m_subD					(subD)
+		, m_imageHeight				(imageHeight)
+		, m_rowLength				(rowLength)
+		, m_skipImages				(skipImages)
+		, m_skipRows				(skipRows)
+		, m_skipPixels				(skipPixels)
+		, m_alignment				(alignment)
+		, m_offset					(offset)
+	{
+	}
+
+protected:
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		deUint32				tex				= 0;
+		deUint32				buf				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
+
+		// Fill with gradient.
+		{
+			int		rowPitch		= deAlign32(pixelSize*m_size,  4);
+			int		slicePitch		= rowPitch*m_size;
+
+			data.resize(slicePitch*m_depth);
+			tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_size, m_size, m_depth, rowPitch, slicePitch, &data[0]), m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
+		}
+
+		glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, m_internalFormat, m_size, m_size, m_depth, 0, transferFmt.format, transferFmt.dataType, &data[0]);
+
+		// Fill data with grid.
+		{
+			int		rowLength		= m_rowLength > 0 ? m_rowLength : m_subW;
+			int		rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
+			int		imageHeight		= m_imageHeight > 0 ? m_imageHeight : m_subH;
+			int		slicePitch		= imageHeight*rowPitch;
+			Vec4	cScale			= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
+			Vec4	cBias			= m_texFormatInfo.valueMin;
+			Vec4	colorA			= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
+			Vec4	colorB			= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;
+
+			data.resize(slicePitch*(m_depth+m_skipImages) + m_offset);
+			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_subW, m_subH, m_subD, rowPitch, slicePitch, &data[0] + m_skipImages*slicePitch + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
+		}
+
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER,	buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER,	(int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	m_imageHeight);
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	m_skipImages);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
+		glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, m_subX, m_subY, m_subZ, m_subW, m_subH, m_subD, transferFmt.format, transferFmt.dataType, (const void*)(deIntptr)m_offset);
+	}
+
+	deUint32	m_internalFormat;
+	int			m_subX;
+	int			m_subY;
+	int			m_subZ;
+	int			m_subW;
+	int			m_subH;
+	int			m_subD;
+	int			m_imageHeight;
+	int			m_rowLength;
+	int			m_skipImages;
+	int			m_skipRows;
+	int			m_skipPixels;
+	int			m_alignment;
+	int			m_offset;
+};
+
+// TexImage3D() depth case.
+class TexImageCubeArrayDepthCase : public TextureCubeArraySpecCase
+{
+public:
+	TexImageCubeArrayDepthCase (Context&	context,
+							  const char*	name,
+							  const char*	desc,
+							  deUint32		internalFormat,
+							  int			imageSize,
+							  int			numLayers)
+		: TextureCubeArraySpecCase(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageSize, numLayers, maxLevelCount(imageSize))
+		, m_internalFormat		(internalFormat)
+	{
+		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
+		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
+		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
+	}
+
+	void createTexture (void)
+	{
+		glu::TransferFormat	fmt			= glu::getTransferFormat(m_texFormat);
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(m_texFormat);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+		GLU_CHECK();
+
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			const int   levelW		= de::max(1, m_size >> ndx);
+			const Vec4  gMin		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const Vec4  gMax		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+
+			levelData.setSize(levelW, levelW, m_depth);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, ndx, m_internalFormat, levelW, levelW, m_depth, 0, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	const deUint32 m_internalFormat;
+};
+
+// TexSubImage3D() depth case.
+class TexSubImageCubeArrayDepthCase : public TextureCubeArraySpecCase
+{
+public:
+	TexSubImageCubeArrayDepthCase (Context&		context,
+								 const char*	name,
+								 const char*	desc,
+								 deUint32		internalFormat,
+								 int			imageSize,
+								 int			numLayers)
+		: TextureCubeArraySpecCase(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageSize, numLayers, maxLevelCount(imageSize))
+		, m_internalFormat		(internalFormat)
+	{
+		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
+		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
+		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
+	}
+
+	void createTexture (void)
+	{
+		glu::TransferFormat	fmt			= glu::getTransferFormat(m_texFormat);
+		de::Random			rnd			(deStringHash(getName()));
+		deUint32			tex			= 0;
+		tcu::TextureLevel	levelData	(m_texFormat);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+		GLU_CHECK();
+
+		// First specify full texture.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			const int   levelW		= de::max(1, m_size >> ndx);
+			const Vec4  gMin		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const Vec4  gMax		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+
+			levelData.setSize(levelW, levelW, m_depth);
+			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);
+
+			glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, ndx, m_internalFormat, levelW, levelW, m_depth, 0, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
+		}
+
+		// Re-specify parts of each level.
+		for (int ndx = 0; ndx < m_numLevels; ndx++)
+		{
+			const int	levelW		= de::max(1, m_size >> ndx);
+
+			const int	w			= rnd.getInt(1, levelW);
+			const int	h			= rnd.getInt(1, levelW);
+			const int	d			= rnd.getInt(1, m_depth);
+			const int	x			= rnd.getInt(0, levelW-w);
+			const int	y			= rnd.getInt(0, levelW-h);
+			const int	z			= rnd.getInt(0, m_depth-d);
+
+			const Vec4	colorA		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+			const Vec4	colorB		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const int	cellSize	= rnd.getInt(2, 16);
+
+			levelData.setSize(w, h, d);
+			tcu::fillWithGrid(levelData.getAccess(), cellSize, colorA, colorB);
+
+			glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, ndx, x, y, z, w, h, d, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
+		}
+	}
+
+	const deUint32 m_internalFormat;
+};
+
+// TexImage3D() depth case with pbo.
+class TexImageCubeArrayDepthBufferCase : public TextureCubeArraySpecCase
+{
+public:
+	TexImageCubeArrayDepthBufferCase (Context&	context,
+									const char*	name,
+									const char*	desc,
+									deUint32	internalFormat,
+									int			imageSize,
+									int			numLayers)
+		: TextureCubeArraySpecCase(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageSize, numLayers, 1)
+		, m_internalFormat		(internalFormat)
+	{
+		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
+		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
+		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
+	}
+
+	void createTexture (void)
+	{
+		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
+		int						pixelSize		= m_texFormat.getPixelSize();
+		int						rowLength		= m_size;
+		int						alignment		= 4;
+		int						rowPitch		= deAlign32(rowLength*pixelSize, alignment);
+		int						imageHeight		= m_size;
+		int						slicePitch		= imageHeight*rowPitch;
+		deUint32				tex				= 0;
+		deUint32				buf				= 0;
+		vector<deUint8>			data;
+
+		DE_ASSERT(m_numLevels == 1);
+
+		// Fill data with grid.
+		data.resize(slicePitch*m_depth);
+		{
+			const Vec4 gMin = Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
+			const Vec4 gMax = Vec4(2.0f, 1.5f, -1.0f, 2.0f);
+
+			tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_size, m_size, m_depth, rowPitch, slicePitch, &data[0]), gMin, gMax);
+		}
+
+		glGenBuffers(1, &buf);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
+		glBufferData(GL_PIXEL_UNPACK_BUFFER, (int)data.size(), &data[0], GL_STATIC_DRAW);
+
+		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	imageHeight);
+		glPixelStorei(GL_UNPACK_ROW_LENGTH,		rowLength);
+		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	0);
+		glPixelStorei(GL_UNPACK_SKIP_ROWS,		0);
+		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	0);
+		glPixelStorei(GL_UNPACK_ALIGNMENT,		alignment);
+
+		glGenTextures(1, &tex);
+		glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
+		glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, m_internalFormat, m_size, m_size, m_depth, 0, transferFmt.format, transferFmt.dataType, DE_NULL);
+		glDeleteBuffers(1, &buf);
+	}
+
+	const deUint32 m_internalFormat;
+};
+
+TextureSpecificationTests::TextureSpecificationTests (Context& context)
+	: TestCaseGroup(context, "specification", "Texture Specification Tests")
+{
+}
+
+TextureSpecificationTests::~TextureSpecificationTests (void)
+{
+}
+
+void TextureSpecificationTests::init (void)
+{
+	struct
+	{
+		const char*	name;
+		deUint32	internalFormat;
+	} colorFormats[] =
+	{
+		{ "rgba32f",			GL_RGBA32F,			},
+		{ "rgba32i",			GL_RGBA32I,			},
+		{ "rgba32ui",			GL_RGBA32UI,		},
+		{ "rgba16f",			GL_RGBA16F,			},
+		{ "rgba16i",			GL_RGBA16I,			},
+		{ "rgba16ui",			GL_RGBA16UI,		},
+		{ "rgba8",				GL_RGBA8,			},
+		{ "rgba8i",				GL_RGBA8I,			},
+		{ "rgba8ui",			GL_RGBA8UI,			},
+		{ "srgb8_alpha8",		GL_SRGB8_ALPHA8,	},
+		{ "rgb10_a2",			GL_RGB10_A2,		},
+		{ "rgb10_a2ui",			GL_RGB10_A2UI,		},
+		{ "rgba4",				GL_RGBA4,			},
+		{ "rgb5_a1",			GL_RGB5_A1,			},
+		{ "rgba8_snorm",		GL_RGBA8_SNORM,		},
+		{ "rgb8",				GL_RGB8,			},
+		{ "rgb565",				GL_RGB565,			},
+		{ "r11f_g11f_b10f",		GL_R11F_G11F_B10F,	},
+		{ "rgb32f",				GL_RGB32F,			},
+		{ "rgb32i",				GL_RGB32I,			},
+		{ "rgb32ui",			GL_RGB32UI,			},
+		{ "rgb16f",				GL_RGB16F,			},
+		{ "rgb16i",				GL_RGB16I,			},
+		{ "rgb16ui",			GL_RGB16UI,			},
+		{ "rgb8_snorm",			GL_RGB8_SNORM,		},
+		{ "rgb8i",				GL_RGB8I,			},
+		{ "rgb8ui",				GL_RGB8UI,			},
+		{ "srgb8",				GL_SRGB8,			},
+		{ "rgb9_e5",			GL_RGB9_E5,			},
+		{ "rg32f",				GL_RG32F,			},
+		{ "rg32i",				GL_RG32I,			},
+		{ "rg32ui",				GL_RG32UI,			},
+		{ "rg16f",				GL_RG16F,			},
+		{ "rg16i",				GL_RG16I,			},
+		{ "rg16ui",				GL_RG16UI,			},
+		{ "rg8",				GL_RG8,				},
+		{ "rg8i",				GL_RG8I,			},
+		{ "rg8ui",				GL_RG8UI,			},
+		{ "rg8_snorm",			GL_RG8_SNORM,		},
+		{ "r32f",				GL_R32F,			},
+		{ "r32i",				GL_R32I,			},
+		{ "r32ui",				GL_R32UI,			},
+		{ "r16f",				GL_R16F,			},
+		{ "r16i",				GL_R16I,			},
+		{ "r16ui",				GL_R16UI,			},
+		{ "r8",					GL_R8,				},
+		{ "r8i",				GL_R8I,				},
+		{ "r8ui",				GL_R8UI,			},
+		{ "r8_snorm",			GL_R8_SNORM,		}
+	};
+
+	static const struct
+	{
+		const char*	name;
+		deUint32	internalFormat;
+	} depthStencilFormats[] =
+	{
+		// Depth and stencil formats
+		{ "depth_component32f",	GL_DEPTH_COMPONENT32F	},
+		{ "depth_component24",	GL_DEPTH_COMPONENT24	},
+		{ "depth_component16",	GL_DEPTH_COMPONENT16	},
+		{ "depth32f_stencil8",	GL_DEPTH32F_STENCIL8	},
+		{ "depth24_stencil8",	GL_DEPTH24_STENCIL8		}
+	};
+
+	// Basic TexImage3D usage.
+	{
+		tcu::TestCaseGroup* basicTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_teximage3d", "Basic glTexImage3D() usage");
+		addChild(basicTexImageGroup);
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const char*	fmtName				= colorFormats[formatNdx].name;
+			deUint32	format				= colorFormats[formatNdx].internalFormat;
+			const int	texCubeArraySize	= 64;
+			const int	texCubeArrayLayers	= 6;
+
+			basicTexImageGroup->addChild(new BasicTexImageCubeArrayCase	(m_context,	(string(fmtName) + "_cube_array").c_str(),	"",	format, texCubeArraySize, texCubeArrayLayers));
+		}
+	}
+
+	// glTexImage3D() pbo cases.
+	{
+		tcu::TestCaseGroup* pboGroup = new tcu::TestCaseGroup(m_testCtx, "teximage3d_pbo", "glTexImage3D() from PBO");
+		addChild(pboGroup);
+
+		// Parameter cases
+		static const struct
+		{
+			const char*	name;
+			deUint32	format;
+			int			size;
+			int			depth;
+			int			imageHeight;
+			int			rowLength;
+			int			skipImages;
+			int			skipRows;
+			int			skipPixels;
+			int			alignment;
+			int			offset;
+		} parameterCases[] =
+		{
+			{ "rgb8_offset",		GL_RGB8,	23,	6,	0,	0,	0,	0,	0,	1,	67 },
+			{ "rgb8_alignment",		GL_RGB8,	23,	6,	0,	0,	0,	0,	0,	2,	0 },
+			{ "rgb8_image_height",	GL_RGB8,	23,	6,	26,	0,	0,	0,	0,	4,	0 },
+			{ "rgb8_row_length",	GL_RGB8,	23,	6,	0,	27,	0,	0,	0,	4,	0 },
+			{ "rgb8_skip_images",	GL_RGB8,	23,	6,	0,	0,	3,	0,	0,	4,	0 },
+			{ "rgb8_skip_rows",		GL_RGB8,	23,	6,	26,	0,	0,	3,	0,	4,	0 },
+			{ "rgb8_skip_pixels",	GL_RGB8,	23,	6,	0,	25,	0,	0,	2,	4,	0 }
+		};
+
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const string	fmtName				= colorFormats[formatNdx].name;
+			const deUint32	format				= colorFormats[formatNdx].internalFormat;
+			const int		texCubeArraySize	= 20;
+			const int		texCubeDepth		= 6;
+
+			pboGroup->addChild(new TexImageCubeArrayBufferCase	(m_context, (fmtName + "_cube_array").c_str(),	"", format, texCubeArraySize, texCubeDepth, 0, 0, 0, 0, 0, 4, 0));
+		}
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(parameterCases); ndx++)
+		{
+			pboGroup->addChild(new TexImageCubeArrayBufferCase(m_context, (string(parameterCases[ndx].name) + "_cube_array").c_str(), "",
+														parameterCases[ndx].format,
+														parameterCases[ndx].size,
+														parameterCases[ndx].depth,
+														parameterCases[ndx].imageHeight,
+														parameterCases[ndx].rowLength,
+														parameterCases[ndx].skipImages,
+														parameterCases[ndx].skipRows,
+														parameterCases[ndx].skipPixels,
+														parameterCases[ndx].alignment,
+														parameterCases[ndx].offset));
+		}
+	}
+
+	// glTexImage3D() depth cases.
+	{
+		tcu::TestCaseGroup* shadow3dGroup = new tcu::TestCaseGroup(m_testCtx, "teximage3d_depth", "glTexImage3D() with depth or depth/stencil format");
+		addChild(shadow3dGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
+		{
+			const int	texCubeArraySize	= 64;
+			const int	texCubeArrayDepth	= 6;
+
+			shadow3dGroup->addChild(new TexImageCubeArrayDepthCase(m_context, (std::string(depthStencilFormats[ndx].name) + "_cube_array").c_str(), "", depthStencilFormats[ndx].internalFormat, texCubeArraySize, texCubeArrayDepth));
+		}
+	}
+
+	// glTexImage3D() depth cases with pbo.
+	{
+		tcu::TestCaseGroup* shadow3dGroup = new tcu::TestCaseGroup(m_testCtx, "teximage3d_depth_pbo", "glTexImage3D() with depth or depth/stencil format with pbo");
+		addChild(shadow3dGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
+		{
+			const int	texCubeArraySize	= 64;
+			const int	texCubeArrayDepth	= 6;
+
+			shadow3dGroup->addChild(new TexImageCubeArrayDepthBufferCase(m_context, (std::string(depthStencilFormats[ndx].name) + "_cube_array").c_str(), "", depthStencilFormats[ndx].internalFormat, texCubeArraySize, texCubeArrayDepth));
+		}
+	}
+
+	// glTexSubImage3D() PBO cases.
+	{
+		tcu::TestCaseGroup* pboGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage3d_pbo", "glTexSubImage3D() pixel buffer object tests");
+		addChild(pboGroup);
+
+		static const struct
+		{
+			const char*	name;
+			deUint32	format;
+			int			size;
+			int			depth;
+			int			subX;
+			int			subY;
+			int			subZ;
+			int			subW;
+			int			subH;
+			int			subD;
+			int			imageHeight;
+			int			rowLength;
+			int			skipImages;
+			int			skipRows;
+			int			skipPixels;
+			int			alignment;
+			int			offset;
+		} paramCases[] =
+		{
+			{ "rgb8_offset",		GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	0,	0,	0,	0,	0,	4,	67 },
+			{ "rgb8_image_height",	GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	26,	0,	0,	0,	0,	4,	0 },
+			{ "rgb8_row_length",	GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	0,	27,	0,	0,	0,	4,	0 },
+			{ "rgb8_skip_images",	GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	0,	0,	3,	0,	0,	4,	0 },
+			{ "rgb8_skip_rows",		GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	22,	0,	0,	3,	0,	4,	0 },
+			{ "rgb8_skip_pixels",	GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	0,	25,	0,	0,	2,	4,	0 }
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorFormats); ndx++)
+		{
+			pboGroup->addChild(new TexSubImageCubeArrayBufferCase(m_context, (std::string(colorFormats[ndx].name) + "_cube_array").c_str(), "",
+														   colorFormats[ndx].internalFormat,
+														   26,	// Size
+														   12,	// Depth
+														   1,	// Sub X
+														   2,	// Sub Y
+														   0,	// Sub Z
+														   23,	// Sub W
+														   19,	// Sub H
+														   8,	// Sub D
+														   0,	// Image height
+														   0,	// Row length
+														   0,	// Skip images
+														   0,	// Skip rows
+														   0,	// Skip pixels
+														   4,	// Alignment
+														   0	/* offset */));
+		}
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(paramCases); ndx++)
+		{
+			pboGroup->addChild(new TexSubImageCubeArrayBufferCase(m_context, (std::string(paramCases[ndx].name) + "_cube_array").c_str(), "",
+														   paramCases[ndx].format,
+														   paramCases[ndx].size,
+														   paramCases[ndx].depth,
+														   paramCases[ndx].subX,
+														   paramCases[ndx].subY,
+														   paramCases[ndx].subZ,
+														   paramCases[ndx].subW,
+														   paramCases[ndx].subH,
+														   paramCases[ndx].subD,
+														   paramCases[ndx].imageHeight,
+														   paramCases[ndx].rowLength,
+														   paramCases[ndx].skipImages,
+														   paramCases[ndx].skipRows,
+														   paramCases[ndx].skipPixels,
+														   paramCases[ndx].alignment,
+														   paramCases[ndx].offset));
+		}
+	}
+
+	// glTexSubImage3D() depth cases.
+	{
+		tcu::TestCaseGroup* shadow3dGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage3d_depth", "glTexSubImage3D() with depth or depth/stencil format");
+		addChild(shadow3dGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
+		{
+			const int	texCubeArraySize	= 57;
+			const int	texCubeArrayLayers	= 6;
+
+			shadow3dGroup->addChild(new TexSubImageCubeArrayDepthCase(m_context, (std::string(depthStencilFormats[ndx].name) + "_cube_array").c_str(), "", depthStencilFormats[ndx].internalFormat, texCubeArraySize, texCubeArrayLayers));
+		}
+	}
+
+	// glTexStorage3D() cases.
+	{
+		tcu::TestCaseGroup* texStorageGroup = new tcu::TestCaseGroup(m_testCtx, "texstorage3d", "Basic glTexStorage3D() usage");
+		addChild(texStorageGroup);
+
+		// All formats.
+		tcu::TestCaseGroup* formatGroup = new tcu::TestCaseGroup(m_testCtx, "format", "glTexStorage3D() with all formats");
+		texStorageGroup->addChild(formatGroup);
+
+		// Color formats.
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
+		{
+			const char*	fmtName				= colorFormats[formatNdx].name;
+			deUint32	internalFormat		= colorFormats[formatNdx].internalFormat;
+			const int	texCubeArraySize	= 57;
+			const int	texCubeArrayLayers	= 6;
+			int			texCubeArrayLevels	= maxLevelCount(texCubeArraySize);
+
+			formatGroup->addChild(new BasicTexStorageCubeArrayCase	(m_context, (string(fmtName) + "_cube_array").c_str(),	"", internalFormat, texCubeArraySize, texCubeArrayLayers, texCubeArrayLevels));
+		}
+
+		// Depth/stencil formats (only 2D texture array is supported).
+		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); formatNdx++)
+		{
+			const char*	fmtName				= depthStencilFormats[formatNdx].name;
+			deUint32	internalFormat		= depthStencilFormats[formatNdx].internalFormat;
+			const int	texCubeArraySize	= 57;
+			const int	texCubeArrayLayers	= 6;
+			int			texCubeArrayLevels	= maxLevelCount(texCubeArraySize);
+
+			formatGroup->addChild(new BasicTexStorageCubeArrayCase	(m_context, (string(fmtName) + "_cube_array").c_str(),	"", internalFormat, texCubeArraySize, texCubeArrayLayers, texCubeArrayLevels));
+		}
+
+		// Sizes.
+		static const struct
+		{
+			int				size;
+			int				layers;
+			int				levels;
+		} texCubeArraySizes[] =
+		{
+			//	Sz	La	Le
+			{	1,	6,	1 },
+			{	2,	6,	2 },
+			{	32,	6,	3 },
+			{	64,	6,	4 },
+			{	57,	12,	1 },
+			{	57,	12,	2 },
+			{	57,	12,	6 }
+		};
+
+		tcu::TestCaseGroup* sizeGroup = new tcu::TestCaseGroup(m_testCtx, "size", "glTexStorage3D() with various sizes");
+		texStorageGroup->addChild(sizeGroup);
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(texCubeArraySizes); ndx++)
+		{
+			const deUint32		format		= GL_RGBA8;
+			int					size		= texCubeArraySizes[ndx].size;
+			int					layers		= texCubeArraySizes[ndx].layers;
+			int					levels		= texCubeArraySizes[ndx].levels;
+			string				name		= string("cube_array_") + de::toString(size) + "x" + de::toString(size) + "x" + de::toString(layers) + "_" + de::toString(levels) + "_levels";
+
+			sizeGroup->addChild(new BasicTexStorageCubeArrayCase(m_context, name.c_str(), "", format, size, layers, levels));
+		}
+	}
+}
+
+} // Functional
+} // gles3
+} // deqp
diff --git a/modules/gles31/functional/es31fTextureSpecificationTests.hpp b/modules/gles31/functional/es31fTextureSpecificationTests.hpp
new file mode 100644
index 0000000..eef4265
--- /dev/null
+++ b/modules/gles31/functional/es31fTextureSpecificationTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FTEXTURESPECIFICATIONTESTS_HPP
+#define _ES31FTEXTURESPECIFICATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture specification tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class TextureSpecificationTests : public TestCaseGroup
+{
+public:
+									TextureSpecificationTests		(Context& context);
+									~TextureSpecificationTests		(void);
+
+	void							init							(void);
+
+private:
+									TextureSpecificationTests		(const TextureSpecificationTests& other);
+	TextureSpecificationTests&		operator=						(const TextureSpecificationTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FTEXTURESPECIFICATIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fUniformBlockTests.cpp b/modules/gles31/functional/es31fUniformBlockTests.cpp
new file mode 100644
index 0000000..2096486
--- /dev/null
+++ b/modules/gles31/functional/es31fUniformBlockTests.cpp
@@ -0,0 +1,312 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Uniform block tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fUniformBlockTests.hpp"
+#include "glsUniformBlockCase.hpp"
+#include "glsRandomUniformBlockCase.hpp"
+#include "tcuCommandLine.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+using gls::UniformBlockCase;
+using gls::RandomUniformBlockCase;
+using namespace gls::ub;
+
+void createRandomCaseGroup (tcu::TestCaseGroup* parentGroup, Context& context, const char* groupName, const char* description, UniformBlockCase::BufferMode bufferMode, deUint32 features, int numCases, deUint32 baseSeed)
+{
+	tcu::TestCaseGroup* group = new tcu::TestCaseGroup(context.getTestContext(), groupName, description);
+	parentGroup->addChild(group);
+
+	baseSeed += (deUint32)context.getTestContext().getCommandLine().getBaseSeed();
+
+	for (int ndx = 0; ndx < numCases; ndx++)
+		group->addChild(new RandomUniformBlockCase(context.getTestContext(), context.getRenderContext(), glu::GLSL_VERSION_310_ES,
+												   de::toString(ndx).c_str(), "", bufferMode, features, (deUint32)ndx+baseSeed));
+}
+
+class BlockBasicTypeCase : public UniformBlockCase
+{
+public:
+	BlockBasicTypeCase (Context& context, const char* name, const char* description, const VarType& type, deUint32 layoutFlags, int numInstances)
+		: UniformBlockCase(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, BUFFERMODE_PER_BLOCK)
+	{
+		UniformBlock& block = m_interface.allocBlock("Block");
+		block.addUniform(Uniform("var", type, 0));
+		block.setFlags(layoutFlags);
+
+		if (numInstances > 0)
+		{
+			block.setArraySize(numInstances);
+			block.setInstanceName("block");
+		}
+	}
+};
+
+static void createBlockBasicTypeCases (tcu::TestCaseGroup* group, Context& context, const char* name, const VarType& type, deUint32 layoutFlags, int numInstances = 0)
+{
+	group->addChild(new BlockBasicTypeCase(context, (string(name) + "_vertex").c_str(),		"", type, layoutFlags|DECLARE_VERTEX,					numInstances));
+	group->addChild(new BlockBasicTypeCase(context, (string(name) + "_fragment").c_str(),	"", type, layoutFlags|DECLARE_FRAGMENT,					numInstances));
+
+	if (!(layoutFlags & LAYOUT_PACKED))
+		group->addChild(new BlockBasicTypeCase(context, (string(name) + "_both").c_str(),	"", type, layoutFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	numInstances));
+}
+
+class Block2LevelStructArrayCase : public UniformBlockCase
+{
+public:
+	Block2LevelStructArrayCase (Context& context, const char* name, const char* description, deUint32 layoutFlags, BufferMode bufferMode, int numInstances)
+		: UniformBlockCase	(context.getTestContext(), context.getRenderContext(), name, description, glu::GLSL_VERSION_310_ES, bufferMode)
+		, m_layoutFlags		(layoutFlags)
+		, m_numInstances	(numInstances)
+	{
+	}
+
+	void init (void)
+	{
+		StructType& typeS = m_interface.allocStruct("S");
+		typeS.addMember("a", VarType(glu::TYPE_UINT_VEC3, PRECISION_HIGH), UNUSED_BOTH);
+		typeS.addMember("b", VarType(VarType(glu::TYPE_FLOAT_MAT2, PRECISION_MEDIUM), 4));
+		typeS.addMember("c", VarType(glu::TYPE_UINT, PRECISION_LOW));
+
+		UniformBlock& block = m_interface.allocBlock("Block");
+		block.addUniform(Uniform("u", VarType(glu::TYPE_INT, PRECISION_MEDIUM)));
+		block.addUniform(Uniform("s", VarType(VarType(VarType(&typeS), 3), 2)));
+		block.addUniform(Uniform("v", VarType(glu::TYPE_FLOAT_VEC2, PRECISION_MEDIUM)));
+		block.setFlags(m_layoutFlags);
+
+		if (m_numInstances > 0)
+		{
+			block.setInstanceName("block");
+			block.setArraySize(m_numInstances);
+		}
+	}
+
+private:
+	deUint32	m_layoutFlags;
+	int			m_numInstances;
+};
+
+} // anonymous
+
+UniformBlockTests::UniformBlockTests (Context& context)
+	: TestCaseGroup(context, "ubo", "Uniform Block tests")
+{
+}
+
+UniformBlockTests::~UniformBlockTests (void)
+{
+}
+
+void UniformBlockTests::init (void)
+{
+	static const glu::DataType basicTypes[] =
+	{
+		glu::TYPE_FLOAT,
+		glu::TYPE_FLOAT_VEC2,
+		glu::TYPE_FLOAT_VEC3,
+		glu::TYPE_FLOAT_VEC4,
+		glu::TYPE_INT,
+		glu::TYPE_INT_VEC2,
+		glu::TYPE_INT_VEC3,
+		glu::TYPE_INT_VEC4,
+		glu::TYPE_UINT,
+		glu::TYPE_UINT_VEC2,
+		glu::TYPE_UINT_VEC3,
+		glu::TYPE_UINT_VEC4,
+		glu::TYPE_BOOL,
+		glu::TYPE_BOOL_VEC2,
+		glu::TYPE_BOOL_VEC3,
+		glu::TYPE_BOOL_VEC4,
+		glu::TYPE_FLOAT_MAT2,
+		glu::TYPE_FLOAT_MAT3,
+		glu::TYPE_FLOAT_MAT4,
+		glu::TYPE_FLOAT_MAT2X3,
+		glu::TYPE_FLOAT_MAT2X4,
+		glu::TYPE_FLOAT_MAT3X2,
+		glu::TYPE_FLOAT_MAT3X4,
+		glu::TYPE_FLOAT_MAT4X2,
+		glu::TYPE_FLOAT_MAT4X3
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		flags;
+	} layoutFlags[] =
+	{
+		{ "shared",		LAYOUT_SHARED	},
+		{ "packed",		LAYOUT_PACKED	},
+		{ "std140",		LAYOUT_STD140	}
+	};
+
+	static const struct
+	{
+		const char*		name;
+		deUint32		flags;
+	} matrixFlags[] =
+	{
+		{ "row_major",		LAYOUT_ROW_MAJOR	},
+		{ "column_major",	LAYOUT_COLUMN_MAJOR }
+	};
+
+	static const struct
+	{
+		const char*							name;
+		UniformBlockCase::BufferMode		mode;
+	} bufferModes[] =
+	{
+		{ "per_block_buffer",	UniformBlockCase::BUFFERMODE_PER_BLOCK },
+		{ "single_buffer",		UniformBlockCase::BUFFERMODE_SINGLE	}
+	};
+
+	// ubo.2_level_array
+	{
+		tcu::TestCaseGroup* nestedArrayGroup = new tcu::TestCaseGroup(m_testCtx, "2_level_array", "2-level basic array variable in single buffer");
+		addChild(nestedArrayGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			nestedArrayGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				const glu::DataType	type		= basicTypes[basicTypeNdx];
+				const char*			typeName	= glu::getDataTypeName(type);
+				const int			childSize	= 4;
+				const int			parentSize	= 3;
+				const VarType		childType	(VarType(type, glu::isDataTypeBoolOrBVec(type) ? 0 : PRECISION_HIGH), childSize);
+				const VarType		parentType	(childType, parentSize);
+
+				createBlockBasicTypeCases(layoutGroup, m_context, typeName, parentType, layoutFlags[layoutFlagNdx].flags);
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+						createBlockBasicTypeCases(layoutGroup, m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(),
+												  parentType, layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags);
+				}
+			}
+		}
+	}
+
+	// ubo.3_level_array
+	{
+		tcu::TestCaseGroup* nestedArrayGroup = new tcu::TestCaseGroup(m_testCtx, "3_level_array", "3-level basic array variable in single buffer");
+		addChild(nestedArrayGroup);
+
+		for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+		{
+			tcu::TestCaseGroup* layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
+			nestedArrayGroup->addChild(layoutGroup);
+
+			for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
+			{
+				const glu::DataType	type		= basicTypes[basicTypeNdx];
+				const char*			typeName	= glu::getDataTypeName(type);
+				const int			childSize0	= 2;
+				const int			childSize1	= 4;
+				const int			parentSize	= 3;
+				const VarType		childType0	(VarType(type, glu::isDataTypeBoolOrBVec(type) ? 0 : PRECISION_HIGH), childSize0);
+				const VarType		childType1	(childType0, childSize1);
+				const VarType		parentType	(childType1, parentSize);
+
+				createBlockBasicTypeCases(layoutGroup, m_context, typeName, parentType, layoutFlags[layoutFlagNdx].flags);
+
+				if (glu::isDataTypeMatrix(type))
+				{
+					for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
+						createBlockBasicTypeCases(layoutGroup, m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(),
+												  parentType, layoutFlags[layoutFlagNdx].flags|matrixFlags[matFlagNdx].flags);
+				}
+			}
+		}
+	}
+
+	// ubo.2_level_struct_array
+	{
+		tcu::TestCaseGroup* structArrayArrayGroup = new tcu::TestCaseGroup(m_testCtx, "2_level_struct_array", "Struct array in one uniform block");
+		addChild(structArrayArrayGroup);
+
+		for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
+		{
+			tcu::TestCaseGroup* modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
+			structArrayArrayGroup->addChild(modeGroup);
+
+			for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
+			{
+				for (int isArray = 0; isArray < 2; isArray++)
+				{
+					std::string	baseName	= layoutFlags[layoutFlagNdx].name;
+					deUint32	baseFlags	= layoutFlags[layoutFlagNdx].flags;
+
+					if (bufferModes[modeNdx].mode == UniformBlockCase::BUFFERMODE_SINGLE && isArray == 0)
+						continue; // Doesn't make sense to add this variant.
+
+					if (isArray)
+						baseName += "_instance_array";
+
+					modeGroup->addChild(new Block2LevelStructArrayCase(m_context, (baseName + "_vertex").c_str(),	"", baseFlags|DECLARE_VERTEX,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+					modeGroup->addChild(new Block2LevelStructArrayCase(m_context, (baseName + "_fragment").c_str(),	"", baseFlags|DECLARE_FRAGMENT,					bufferModes[modeNdx].mode, isArray ? 3 : 0));
+
+					if (!(baseFlags & LAYOUT_PACKED))
+						modeGroup->addChild(new Block2LevelStructArrayCase(m_context, (baseName + "_both").c_str(),	"", baseFlags|DECLARE_VERTEX|DECLARE_FRAGMENT,	bufferModes[modeNdx].mode, isArray ? 3 : 0));
+				}
+			}
+		}
+	}
+
+	// ubo.random
+	{
+		const deUint32	allShaders		= FEATURE_VERTEX_BLOCKS|FEATURE_FRAGMENT_BLOCKS|FEATURE_SHARED_BLOCKS;
+		const deUint32	allLayouts		= FEATURE_PACKED_LAYOUT|FEATURE_SHARED_LAYOUT|FEATURE_STD140_LAYOUT;
+		const deUint32	allBasicTypes	= FEATURE_VECTORS|FEATURE_MATRICES;
+		const deUint32	unused			= FEATURE_UNUSED_MEMBERS|FEATURE_UNUSED_UNIFORMS;
+		const deUint32	matFlags		= FEATURE_MATRIX_LAYOUT;
+		const deUint32	basicTypeArrays	= allShaders|allLayouts|unused|allBasicTypes|matFlags|FEATURE_ARRAYS|FEATURE_ARRAYS_OF_ARRAYS;
+		const deUint32	allFeatures		= ~0u;
+
+		tcu::TestCaseGroup* randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Random Uniform Block cases");
+		addChild(randomGroup);
+
+		createRandomCaseGroup(randomGroup, m_context, "basic_type_arrays",		"Arrays, per-block buffers",				UniformBlockCase::BUFFERMODE_PER_BLOCK,	basicTypeArrays,	25, 1150);
+		createRandomCaseGroup(randomGroup, m_context, "all_per_block_buffers",	"All random features, per-block buffers",	UniformBlockCase::BUFFERMODE_PER_BLOCK,	allFeatures,		50, 11200);
+		createRandomCaseGroup(randomGroup, m_context, "all_shared_buffer",		"All random features, shared buffer",		UniformBlockCase::BUFFERMODE_SINGLE,	allFeatures,		50, 11250);
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fUniformBlockTests.hpp b/modules/gles31/functional/es31fUniformBlockTests.hpp
new file mode 100644
index 0000000..59491d7
--- /dev/null
+++ b/modules/gles31/functional/es31fUniformBlockTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FUNIFORMBLOCKTESTS_HPP
+#define _ES31FUNIFORMBLOCKTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Uniform block tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class UniformBlockTests : public TestCaseGroup
+{
+public:
+							UniformBlockTests		(Context& context);
+							~UniformBlockTests		(void);
+
+	void					init					(void);
+
+private:
+							UniformBlockTests		(const UniformBlockTests& other);
+	UniformBlockTests&		operator=				(const UniformBlockTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FUNIFORMBLOCKTESTS_HPP
diff --git a/modules/gles31/functional/es31fUniformLocationTests.cpp b/modules/gles31/functional/es31fUniformLocationTests.cpp
new file mode 100644
index 0000000..fd6ff83
--- /dev/null
+++ b/modules/gles31/functional/es31fUniformLocationTests.cpp
@@ -0,0 +1,1064 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Explicit uniform location tests
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fUniformLocationTests.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuCommandLine.hpp"
+
+#include "glsShaderLibrary.hpp"
+#include "glsTextureTestUtil.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluTexture.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluVarType.hpp"
+#include "gluVarTypeUtil.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "sglrContextUtil.hpp"
+
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+#include "deString.h"
+#include "deRandom.hpp"
+#include "deInt32.h"
+
+#include <set>
+#include <map>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+using std::string;
+using std::vector;
+using std::map;
+using de::UniquePtr;
+using glu::VarType;
+
+struct UniformInfo
+{
+	enum ShaderStage
+	{
+		SHADERSTAGE_NONE	= 0,
+		SHADERSTAGE_VERTEX	= (1<<0),
+		SHADERSTAGE_FRAGMENT= (1<<1),
+		SHADERSTAGE_BOTH	= (SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT),
+	};
+
+	VarType			type;
+	ShaderStage		declareLocation; // support declarations with/without layout qualifiers, needed for linkage testing
+	ShaderStage		layoutLocation;
+	ShaderStage		checkLocation;
+	int				location; // -1 for unset
+
+	UniformInfo (VarType type_, ShaderStage declareLocation_, ShaderStage layoutLocation_, ShaderStage checkLocation_, int location_ = -1)
+		: type				(type_)
+		, declareLocation	(declareLocation_)
+		, layoutLocation	(layoutLocation_)
+		, checkLocation		(checkLocation_)
+		, location			(location_)
+	{
+	}
+};
+
+class UniformLocationCase : public tcu::TestCase
+{
+public:
+								UniformLocationCase		(tcu::TestContext&			context,
+														 glu::RenderContext&		renderContext,
+														 const char*				name,
+														 const char*				desc,
+														 const vector<UniformInfo>&	uniformInfo);
+	virtual 					~UniformLocationCase	(void) {}
+
+	virtual IterateResult		iterate					(void);
+
+protected:
+	IterateResult				run						(const vector<UniformInfo>& uniformList);
+	static glu::ProgramSources	genShaderSources		(const vector<UniformInfo>& uniformList);
+	bool						verifyLocations			(const glu::ShaderProgram& program, const vector<UniformInfo>& uniformList);
+	void						render					(const glu::ShaderProgram& program, const vector<UniformInfo>& uniformList);
+	static bool					verifyResult			(const tcu::ConstPixelBufferAccess& access);
+
+	static float				getExpectedValue		(glu::DataType type, int id, const char* name);
+
+	de::MovePtr<glu::Texture2D>	createTexture			(glu::DataType samplerType, float redChannelValue, int binding);
+
+	glu::RenderContext&			m_renderCtx;
+
+	const vector<UniformInfo>	m_uniformInfo;
+
+	enum
+	{
+		RENDER_SIZE = 16
+	};
+};
+
+string getUniformName (int ndx, const glu::VarType& type, const glu::TypeComponentVector& path)
+{
+	std::ostringstream buff;
+	buff << "uni" << ndx << glu::TypeAccessFormat(type, path);
+
+	return buff.str();
+}
+
+string getFirstComponentName (const glu::VarType& type)
+{
+	std::ostringstream buff;
+	if (glu::isDataTypeVector(type.getBasicType()))
+		buff << glu::TypeAccessFormat(type, glu::SubTypeAccess(type).component(0).getPath());
+	else if (glu::isDataTypeMatrix(type.getBasicType()))
+		buff << glu::TypeAccessFormat(type, glu::SubTypeAccess(type).column(0).component(0).getPath());
+
+	return buff.str();
+}
+
+UniformLocationCase::UniformLocationCase (tcu::TestContext&				context,
+										  glu::RenderContext&			renderContext,
+										  const char*					name,
+										  const char*					desc,
+										  const vector<UniformInfo>&	uniformInfo)
+	: TestCase			(context, name, desc)
+	, m_renderCtx		(renderContext)
+	, m_uniformInfo		(uniformInfo)
+{
+}
+
+// [from, to]
+std::vector<int> shuffledRange (int from, int to, int seed)
+{
+	const int	count	= to - from;
+
+	vector<int> retval	(count);
+	de::Random	rng		(seed);
+
+	DE_ASSERT(count > 0);
+
+	for (int ndx = 0; ndx < count; ndx++)
+		retval[ndx] = ndx + from;
+
+	rng.shuffle(retval.begin(), retval.end());
+	return retval;
+}
+
+glu::DataType getDataTypeSamplerSampleType (glu::DataType type)
+{
+	using namespace glu;
+
+	if (type >= TYPE_SAMPLER_1D && type <= TYPE_SAMPLER_3D)
+		return TYPE_FLOAT_VEC4;
+	else if (type >= TYPE_INT_SAMPLER_1D && type <= TYPE_INT_SAMPLER_3D)
+		return TYPE_INT_VEC4;
+	else if (type >= TYPE_UINT_SAMPLER_1D && type <= TYPE_UINT_SAMPLER_3D)
+		return TYPE_UINT_VEC4;
+	else if (type >= TYPE_SAMPLER_1D_SHADOW && type <=	TYPE_SAMPLER_2D_ARRAY_SHADOW)
+		return TYPE_FLOAT;
+	else
+		DE_ASSERT(!"Unknown sampler type");
+
+	return TYPE_INVALID;
+}
+
+// A (hopefully) unique value for a uniform. For multi-component types creates only one value. Values are in the range [0,1] for floats, [-128, 127] for ints, [0,255] for uints and 0/1 for booleans. Samplers are treated according to the types they return.
+float UniformLocationCase::getExpectedValue (glu::DataType type, int id, const char* name)
+{
+	const deUint32	hash			= deStringHash(name) + deInt32Hash(id);
+
+	glu::DataType	adjustedType	= type;
+
+	if (glu::isDataTypeSampler(type))
+		adjustedType = getDataTypeSamplerSampleType(type);
+
+	if (glu::isDataTypeIntOrIVec(adjustedType))
+		return float(hash%128);
+	else if (glu::isDataTypeUintOrUVec(adjustedType))
+		return float(hash%255);
+	else if (glu::isDataTypeFloatOrVec(adjustedType))
+		return (hash%255)/255.0f;
+	else if (glu::isDataTypeBoolOrBVec(adjustedType))
+		return float(hash%2);
+	else
+		DE_ASSERT(!"Unkown primitive type");
+
+	return glu::TYPE_INVALID;
+}
+
+UniformLocationCase::IterateResult UniformLocationCase::iterate (void)
+{
+	return run(m_uniformInfo);
+}
+
+UniformLocationCase::IterateResult UniformLocationCase::run (const vector<UniformInfo>& uniformList)
+{
+	using gls::TextureTestUtil::RandomViewport;
+
+	const glu::ProgramSources	sources		= genShaderSources(uniformList);
+	const glu::ShaderProgram	program		(m_renderCtx, sources);
+	const int					baseSeed	= m_testCtx.getCommandLine().getBaseSeed();
+	const glw::Functions&		gl			= m_renderCtx.getFunctions();
+	const RandomViewport		viewport	(m_renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()) + baseSeed);
+
+	tcu::Surface				rendered	(RENDER_SIZE, RENDER_SIZE);
+
+	if (!verifyLocations(program, uniformList))
+		return STOP;
+
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+	gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+	render(program, uniformList);
+
+	glu::readPixels(m_renderCtx, viewport.x, viewport.y, rendered.getAccess());
+
+	if (!verifyResult(rendered.getAccess()))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader produced incorrect result");
+		return STOP;
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+glu::ProgramSources UniformLocationCase::genShaderSources (const vector<UniformInfo>& uniformList)
+{
+	std::ostringstream	vertDecl, vertMain, fragDecl, fragMain;
+
+	vertDecl << "#version 310 es\n"
+			 << "precision highp float;\n"
+			 << "precision highp int;\n"
+			 << "float verify(float val, float ref) { return float(abs(val-ref) < 0.05); }\n\n"
+			 << "in highp vec4 a_position;\n"
+			 << "out highp vec4 v_color;\n";
+	fragDecl << "#version 310 es\n\n"
+			 << "precision highp float;\n"
+			 << "precision highp int;\n"
+			 << "float verify(float val, float ref) { return float(abs(val-ref) < 0.05); }\n\n"
+			 << "in highp vec4 v_color;\n"
+			 << "layout(location = 0) out mediump vec4 o_color;\n\n";
+
+	vertMain << "void main()\n{\n"
+			 << "	gl_Position = a_position;\n"
+			 << "	v_color = vec4(1.0);\n";
+
+	fragMain << "void main()\n{\n"
+			 << "	o_color = v_color;\n";
+
+	std::set<const glu::StructType*> declaredStructs;
+
+	// Declare uniforms
+	for (int uniformNdx = 0; uniformNdx < int(uniformList.size()); uniformNdx++)
+	{
+		const UniformInfo&	uniformInfo = uniformList[uniformNdx];
+
+		const bool			declareInVert	= (uniformInfo.declareLocation & UniformInfo::SHADERSTAGE_VERTEX)   != 0;
+		const bool			declareInFrag	= (uniformInfo.declareLocation & UniformInfo::SHADERSTAGE_FRAGMENT) != 0;
+		const bool			layoutInVert    = (uniformInfo.layoutLocation  & UniformInfo::SHADERSTAGE_VERTEX)   != 0;
+		const bool			layoutInFrag    = (uniformInfo.layoutLocation  & UniformInfo::SHADERSTAGE_FRAGMENT) != 0;
+		const bool			checkInVert		= (uniformInfo.checkLocation   & UniformInfo::SHADERSTAGE_VERTEX)   != 0;
+		const bool			checkInFrag		= (uniformInfo.checkLocation   & UniformInfo::SHADERSTAGE_FRAGMENT) != 0;
+
+		const string		layout			= uniformInfo.location >= 0 ? "layout(location = " + de::toString(uniformInfo.location) + ") " : "";
+		const string		uniName			= "uni" + de::toString(uniformNdx);
+
+		int					location		= uniformInfo.location;
+		int					subTypeIndex	= 0;
+
+		DE_ASSERT((declareInVert && layoutInVert) || !layoutInVert); // Cannot have layout without declaration
+		DE_ASSERT((declareInFrag && layoutInFrag) || !layoutInFrag);
+		DE_ASSERT(location<0 || (layoutInVert || layoutInFrag)); // Cannot have location without layout
+
+		// struct definitions
+		if (uniformInfo.type.isStructType())
+		{
+			const glu::StructType* const structType = uniformInfo.type.getStructPtr();
+			if (!declaredStructs.count(structType))
+			{
+				if (declareInVert)
+					vertDecl << glu::declare(structType, 0) << ";\n";
+
+				if (declareInFrag)
+					fragDecl << glu::declare(structType, 0) << ";\n";
+
+				declaredStructs.insert(structType);
+			}
+		}
+
+		if (declareInVert)
+			vertDecl << "uniform " << (layoutInVert ? layout : "") << glu::declare(uniformInfo.type, uniName) << ";\n";
+
+		if (declareInFrag)
+			fragDecl << "uniform " << (layoutInFrag ? layout : "") << glu::declare(uniformInfo.type, uniName) << ";\n";
+
+		// Anything that needs to be done for each enclosed primitive type
+		for (glu::BasicTypeIterator subTypeIter = glu::BasicTypeIterator::begin(&uniformInfo.type); subTypeIter != glu::BasicTypeIterator::end(&uniformInfo.type); subTypeIter++, subTypeIndex++)
+		{
+			const glu::VarType	subType		= glu::getVarType(uniformInfo.type, subTypeIter.getPath());
+			const glu::DataType	scalarType	= glu::getDataTypeScalarType(subType.getBasicType());
+			const char* const	typeName	= glu::getDataTypeName(scalarType);
+			const string		expectValue	= de::floatToString(getExpectedValue(scalarType, location >= 0 ? location+subTypeIndex : -1, typeName), 3);
+
+			if (glu::isDataTypeSampler(scalarType))
+			{
+				if (checkInVert)
+					vertMain << "	v_color.rgb *= verify(float( texture(" << uniName
+							 << glu::TypeAccessFormat(uniformInfo.type, subTypeIter.getPath())
+							 << ", vec2(0.5)).r), " << expectValue << ");\n";
+				if (checkInFrag)
+					fragMain << "	o_color.rgb *= verify(float( texture(" << uniName
+							 << glu::TypeAccessFormat(uniformInfo.type, subTypeIter.getPath())
+							 << ", vec2(0.5)).r), " << expectValue << ");\n";
+			}
+			else
+			{
+				if (checkInVert)
+					vertMain << "	v_color.rgb *= verify(float(" << uniName
+							 << glu::TypeAccessFormat(uniformInfo.type, subTypeIter.getPath())
+							 << getFirstComponentName(subType) << "), " << expectValue << ");\n";
+				if (checkInFrag)
+					fragMain << "	o_color.rgb *= verify(float(" << uniName
+							 << glu::TypeAccessFormat(uniformInfo.type, subTypeIter.getPath())
+							 << getFirstComponentName(subType) << "), " << expectValue << ");\n";
+			}
+		}
+	}
+
+	vertMain << "}\n";
+	fragMain << "}\n";
+
+	return glu::makeVtxFragSources(vertDecl.str() + vertMain.str(), fragDecl.str() + fragMain.str());
+}
+
+bool UniformLocationCase::verifyLocations (const glu::ShaderProgram& program, const vector<UniformInfo>& uniformList)
+{
+	using tcu::TestLog;
+
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	const bool				vertexOk	= program.getShaderInfo(glu::SHADERTYPE_VERTEX).compileOk;
+	const bool				fragmentOk	= program.getShaderInfo(glu::SHADERTYPE_FRAGMENT).compileOk;
+	const bool				linkOk		= program.getProgramInfo().linkOk;
+	const deUint32			programID	= program.getProgram();
+
+	TestLog&				log			= m_testCtx.getLog();
+	std::set<int>			usedLocations;
+
+	log << program;
+
+	if (!vertexOk || !fragmentOk || !linkOk)
+	{
+		log << TestLog::Message << "ERROR: shader failed to compile/link" << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader failed to compile/link");
+		return false;
+	}
+
+	for (int uniformNdx = 0; uniformNdx < int(uniformList.size()); uniformNdx++)
+	{
+		const UniformInfo&	uniformInfo		= uniformList[uniformNdx];
+		int					subTypeIndex	= 0;
+
+		for (glu::BasicTypeIterator subTypeIter = glu::BasicTypeIterator::begin(&uniformInfo.type); subTypeIter != glu::BasicTypeIterator::end(&uniformInfo.type); subTypeIter++, subTypeIndex++)
+		{
+			const glu::VarType	type		= glu::getVarType(uniformInfo.type, subTypeIter.getPath());
+			const string		name		= getUniformName(uniformNdx, uniformInfo.type, subTypeIter.getPath());
+			const int			gotLoc		= gl.getUniformLocation(programID, name.c_str());
+			const int			expectLoc	= uniformInfo.location >= 0 ? uniformInfo.location+subTypeIndex : -1;
+
+			if (expectLoc >= 0)
+			{
+				if (uniformInfo.checkLocation == 0 && gotLoc == -1)
+					continue;
+
+				if (gotLoc != expectLoc)
+				{
+					log << TestLog::Message << "ERROR: found uniform " << name << " in location " << gotLoc << " when it should have been in " << expectLoc << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect uniform location");
+					return false;
+				}
+
+				if (usedLocations.find(expectLoc) != usedLocations.end())
+				{
+					log << TestLog::Message << "ERROR: expected uniform " << name << " in location " << gotLoc << " but it has already been used" << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Overlapping uniform location");
+					return false;
+				}
+
+				usedLocations.insert(expectLoc);
+			}
+			else if (gotLoc >= 0)
+			{
+				if (usedLocations.count(gotLoc))
+				{
+					log << TestLog::Message << "ERROR: found uniform " << name << " in location " << gotLoc << " which has already been used" << TestLog::EndMessage;
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Overlapping uniform location");
+					return false;
+				}
+
+				usedLocations.insert(gotLoc);
+			}
+		}
+	}
+
+	return true;
+}
+
+// Check that shader output is white (or very close to it)
+bool UniformLocationCase::verifyResult (const tcu::ConstPixelBufferAccess& access)
+{
+	using tcu::Vec4;
+
+	const Vec4 threshold (0.1f, 0.1f, 0.1f, 0.1f);
+	const Vec4 reference (1.0f, 1.0f, 1.0f, 1.0f);
+
+	for (int y = 0; y < access.getHeight(); y++)
+	{
+		for (int x = 0; x < access.getWidth(); x++)
+		{
+			const Vec4 diff = abs(access.getPixel(x, y) - reference);
+
+			if (!boolAll(lessThanEqual(diff, threshold)))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+// get a 4 channel 8 bits each texture format that is usable by the given sampler type
+deUint32 getTextureFormat (glu::DataType samplerType)
+{
+	using namespace glu;
+
+	switch (samplerType)
+	{
+		case TYPE_SAMPLER_1D:
+		case TYPE_SAMPLER_2D:
+		case TYPE_SAMPLER_CUBE:
+		case TYPE_SAMPLER_2D_ARRAY:
+		case TYPE_SAMPLER_3D:
+			return GL_RGBA8;
+
+		case TYPE_INT_SAMPLER_1D:
+		case TYPE_INT_SAMPLER_2D:
+		case TYPE_INT_SAMPLER_CUBE:
+		case TYPE_INT_SAMPLER_2D_ARRAY:
+		case TYPE_INT_SAMPLER_3D:
+			return GL_RGBA8I;
+
+		case TYPE_UINT_SAMPLER_1D:
+		case TYPE_UINT_SAMPLER_2D:
+		case TYPE_UINT_SAMPLER_CUBE:
+		case TYPE_UINT_SAMPLER_2D_ARRAY:
+		case TYPE_UINT_SAMPLER_3D:
+			return GL_RGBA8UI;
+
+		default:
+			DE_ASSERT(!"Unsupported (sampler) type");
+			return 0;
+	}
+}
+
+// create a texture suitable for sampling by the given sampler type and bind it
+de::MovePtr<glu::Texture2D> UniformLocationCase::createTexture (glu::DataType samplerType, float redChannelValue, int binding)
+{
+	using namespace glu;
+
+	const glw::Functions&	gl		 = m_renderCtx.getFunctions();
+
+	const deUint32			format	 = getTextureFormat(samplerType);
+	de::MovePtr<Texture2D>	tex;
+
+	tex = de::MovePtr<Texture2D>(new Texture2D(m_renderCtx, format, 16, 16));
+
+	tex->getRefTexture().allocLevel(0);
+
+	if (format == GL_RGBA8I || format == GL_RGBA8UI)
+		tcu::clear(tex->getRefTexture().getLevel(0), tcu::IVec4(int(redChannelValue), 0, 0, 0));
+	else
+		tcu::clear(tex->getRefTexture().getLevel(0), tcu::Vec4(redChannelValue, 0.0f, 0.0f, 1.0f));
+
+	gl.activeTexture(GL_TEXTURE0 + binding);
+	tex->upload();
+
+	gl.bindTexture(GL_TEXTURE_2D, tex->getGLTexture());
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "UniformLocationCase: texture upload");
+
+	return tex;
+}
+
+void UniformLocationCase::render (const glu::ShaderProgram& program, const vector<UniformInfo>& uniformList)
+{
+	using glu::Texture2D;
+	using de::MovePtr;
+	typedef vector<Texture2D*> TextureList;
+
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	const deUint32			programID		= program.getProgram();
+	const deInt32			posLoc			= gl.getAttribLocation(programID, "a_position");
+
+	// Vertex data.
+	const float position[] =
+	{
+		-1.0f, -1.0f, 0.1f,	1.0f,
+		-1.0f,  1.0f, 0.1f,	1.0f,
+		 1.0f, -1.0f, 0.1f,	1.0f,
+		 1.0f,  1.0f, 0.1f,	1.0f
+	};
+	const deUint16			indices[]		= { 0, 1, 2, 2, 1, 3 };
+
+	// some buffers to feed to the GPU, only the first element is relevant since the others are never verified
+	float					floatBuf[16]	= {0.0f};
+	deInt32					intBuf[4]		= {0};
+	deUint32				uintBuf[4]		= {0};
+
+	TextureList				texList;
+
+	TCU_CHECK(posLoc >= 0);
+	gl.useProgram(programID);
+
+	try
+	{
+
+		// Set uniforms
+		for (unsigned int uniformNdx = 0; uniformNdx < uniformList.size(); uniformNdx++)
+		{
+			const UniformInfo&	uniformInfo			= uniformList[uniformNdx];
+			int					expectedLocation	= uniformInfo.location;
+
+			for (glu::BasicTypeIterator subTypeIter = glu::BasicTypeIterator::begin(&uniformInfo.type); subTypeIter != glu::BasicTypeIterator::end(&uniformInfo.type); subTypeIter++)
+			{
+				const glu::VarType	type			= glu::getVarType(uniformInfo.type, subTypeIter.getPath());
+				const string		name			= getUniformName(uniformNdx, uniformInfo.type, subTypeIter.getPath());
+				const int			gotLoc			= gl.getUniformLocation(programID, name.c_str());
+				const glu::DataType	scalarType		= glu::getDataTypeScalarType(type.getBasicType());
+				const char*	const	typeName		= glu::getDataTypeName(scalarType);
+				const float			expectedValue	= getExpectedValue(scalarType, expectedLocation, typeName);
+
+				if (glu::isDataTypeSampler(scalarType))
+				{
+					const int binding = (int)texList.size();
+
+					texList.push_back(createTexture(scalarType, expectedValue, binding).release());
+					gl.uniform1i(gotLoc, binding);
+				}
+				else if(gotLoc >= 0)
+				{
+					floatBuf[0] = expectedValue;
+					intBuf[0]   = int(expectedValue);
+					uintBuf[0]  = deUint32(expectedValue);
+
+					m_testCtx.getLog() << tcu::TestLog::Message << "Set uniform " << name << " in location " << gotLoc << " to " << expectedValue << tcu::TestLog::EndMessage;
+
+					switch (type.getBasicType())
+					{
+						case glu::TYPE_FLOAT:			gl.uniform1fv(gotLoc, 1, floatBuf);					break;
+						case glu::TYPE_FLOAT_VEC2:		gl.uniform2fv(gotLoc, 1, floatBuf);					break;
+						case glu::TYPE_FLOAT_VEC3:		gl.uniform3fv(gotLoc, 1, floatBuf);					break;
+						case glu::TYPE_FLOAT_VEC4:		gl.uniform4fv(gotLoc, 1, floatBuf);					break;
+
+						case glu::TYPE_INT:				gl.uniform1iv(gotLoc, 1, intBuf);					break;
+						case glu::TYPE_INT_VEC2:		gl.uniform2iv(gotLoc, 1, intBuf);					break;
+						case glu::TYPE_INT_VEC3:		gl.uniform3iv(gotLoc, 1, intBuf);					break;
+						case glu::TYPE_INT_VEC4:		gl.uniform4iv(gotLoc, 1, intBuf);					break;
+
+						case glu::TYPE_UINT:			gl.uniform1uiv(gotLoc, 1, uintBuf);					break;
+						case glu::TYPE_UINT_VEC2:		gl.uniform2uiv(gotLoc, 1, uintBuf);					break;
+						case glu::TYPE_UINT_VEC3:		gl.uniform3uiv(gotLoc, 1, uintBuf);					break;
+						case glu::TYPE_UINT_VEC4:		gl.uniform4uiv(gotLoc, 1, uintBuf);					break;
+
+						case glu::TYPE_BOOL:			gl.uniform1iv(gotLoc, 1, intBuf);					break;
+						case glu::TYPE_BOOL_VEC2:		gl.uniform2iv(gotLoc, 1, intBuf);					break;
+						case glu::TYPE_BOOL_VEC3:		gl.uniform3iv(gotLoc, 1, intBuf);					break;
+						case glu::TYPE_BOOL_VEC4:		gl.uniform4iv(gotLoc, 1, intBuf);					break;
+
+						case glu::TYPE_FLOAT_MAT2:		gl.uniformMatrix2fv(gotLoc, 1, false, floatBuf);	break;
+						case glu::TYPE_FLOAT_MAT2X3:	gl.uniformMatrix2x3fv(gotLoc, 1, false, floatBuf);	break;
+						case glu::TYPE_FLOAT_MAT2X4:	gl.uniformMatrix2x4fv(gotLoc, 1, false, floatBuf);	break;
+
+						case glu::TYPE_FLOAT_MAT3X2:	gl.uniformMatrix3x2fv(gotLoc, 1, false, floatBuf);	break;
+						case glu::TYPE_FLOAT_MAT3:		gl.uniformMatrix3fv(gotLoc, 1, false, floatBuf);	break;
+						case glu::TYPE_FLOAT_MAT3X4:	gl.uniformMatrix3x4fv(gotLoc, 1, false, floatBuf);	break;
+
+						case glu::TYPE_FLOAT_MAT4X2:	gl.uniformMatrix4x2fv(gotLoc, 1, false, floatBuf);	break;
+						case glu::TYPE_FLOAT_MAT4X3:	gl.uniformMatrix4x3fv(gotLoc, 1, false, floatBuf);	break;
+						case glu::TYPE_FLOAT_MAT4:		gl.uniformMatrix4fv(gotLoc, 1, false, floatBuf);	break;
+						default:
+							DE_ASSERT(false);
+					}
+				}
+
+				expectedLocation += expectedLocation>=0;
+			}
+		}
+
+		gl.enableVertexAttribArray(posLoc);
+		gl.vertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &position[0]);
+
+		gl.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_SHORT, &indices[0]);
+
+		gl.disableVertexAttribArray(posLoc);
+	}
+	catch(...)
+	{
+		for (int i = 0; i < int(texList.size()); i++)
+			delete texList[i];
+
+		throw;
+	}
+
+	for (int i = 0; i < int(texList.size()); i++)
+		delete texList[i];
+}
+
+class MaxUniformLocationCase : public UniformLocationCase
+{
+public:
+								MaxUniformLocationCase		(tcu::TestContext&			context,
+															 glu::RenderContext&		renderContext,
+															 const char*				name,
+															 const char*				desc,
+															 const vector<UniformInfo>&	uniformInfo);
+	virtual 					~MaxUniformLocationCase		(void) {}
+	virtual IterateResult		iterate						(void);
+};
+
+MaxUniformLocationCase::MaxUniformLocationCase (tcu::TestContext&			context,
+												glu::RenderContext&			renderContext,
+												const char*					name,
+												const char*					desc,
+												const vector<UniformInfo>&	uniformInfo)
+	: UniformLocationCase(context, renderContext, name, desc, uniformInfo)
+{
+	DE_ASSERT(!uniformInfo.empty());
+}
+
+UniformLocationCase::IterateResult MaxUniformLocationCase::iterate (void)
+{
+	int					maxLocation = 1024;
+	vector<UniformInfo>	uniformInfo = m_uniformInfo;
+
+	m_renderCtx.getFunctions().getIntegerv(GL_MAX_UNIFORM_LOCATIONS, &maxLocation);
+
+	uniformInfo[0].location = maxLocation-1;
+
+	return UniformLocationCase::run(uniformInfo);
+}
+
+} // Anonymous
+
+UniformLocationTests::UniformLocationTests (Context& context)
+	: TestCaseGroup(context, "uniform_location", "Explicit uniform locations")
+{
+}
+
+UniformLocationTests::~UniformLocationTests (void)
+{
+	for (int i = 0; i < int(structTypes.size()); i++)
+		delete structTypes[i];
+}
+
+glu::VarType createVarType (glu::DataType type)
+{
+	return glu::VarType(type, glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_HIGHP);
+}
+
+void UniformLocationTests::init (void)
+{
+	using namespace glu;
+
+	const UniformInfo::ShaderStage checkStages[]	= { UniformInfo::SHADERSTAGE_VERTEX, UniformInfo::SHADERSTAGE_FRAGMENT };
+	const char*						stageNames[]	= {"vertex", "fragment"};
+	const int						maxLocations	= 1024;
+	const int						baseSeed		= m_context.getTestContext().getCommandLine().getBaseSeed();
+
+	const DataType					primitiveTypes[] =
+	{
+		TYPE_FLOAT,
+		TYPE_FLOAT_VEC2,
+		TYPE_FLOAT_VEC3,
+		TYPE_FLOAT_VEC4,
+
+		TYPE_INT,
+		TYPE_INT_VEC2,
+		TYPE_INT_VEC3,
+		TYPE_INT_VEC4,
+
+		TYPE_UINT,
+		TYPE_UINT_VEC2,
+		TYPE_UINT_VEC3,
+		TYPE_UINT_VEC4,
+
+		TYPE_BOOL,
+		TYPE_BOOL_VEC2,
+		TYPE_BOOL_VEC3,
+		TYPE_BOOL_VEC4,
+
+		TYPE_FLOAT_MAT2,
+		TYPE_FLOAT_MAT2X3,
+		TYPE_FLOAT_MAT2X4,
+		TYPE_FLOAT_MAT3X2,
+		TYPE_FLOAT_MAT3,
+		TYPE_FLOAT_MAT3X4,
+		TYPE_FLOAT_MAT4X2,
+		TYPE_FLOAT_MAT4X3,
+		TYPE_FLOAT_MAT4,
+
+		TYPE_SAMPLER_2D,
+		TYPE_INT_SAMPLER_2D,
+		TYPE_UINT_SAMPLER_2D,
+	};
+
+	const int maxPrimitiveTypeNdx = DE_LENGTH_OF_ARRAY(primitiveTypes) - 4;
+	DE_ASSERT(primitiveTypes[maxPrimitiveTypeNdx] == TYPE_FLOAT_MAT4);
+
+	// Primitive type cases with trivial linkage
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(m_testCtx, "basic", "Location specified with use, single shader stage");
+		de::Random					rng		(baseSeed + 0x1001);
+		addChild(group);
+
+		for (int primitiveNdx = 0; primitiveNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveNdx++)
+		{
+			const DataType		type	= primitiveTypes[primitiveNdx];
+
+			for (int stageNdx = 0; stageNdx < DE_LENGTH_OF_ARRAY(checkStages); stageNdx++)
+			{
+				const string		name		= string(getDataTypeName(type)) + "_" + stageNames[stageNdx];
+
+				vector<UniformInfo> config;
+
+				UniformInfo			uniform	(createVarType(type),
+											 checkStages[stageNdx],
+											 checkStages[stageNdx],
+											 checkStages[stageNdx],
+											 rng.getInt(0, maxLocations-1));
+
+				config.push_back(uniform);
+				group->addChild(new UniformLocationCase (m_testCtx, m_context.getRenderContext(), name.c_str(), name.c_str(), config));
+			}
+		}
+	}
+
+	// Arrays
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(m_testCtx, "array", "Array location specified with use, single shader stage");
+		de::Random					rng		(baseSeed + 0x2001);
+		addChild(group);
+
+		for (int primitiveNdx = 0; primitiveNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveNdx++)
+		{
+			const DataType		type	= primitiveTypes[primitiveNdx];
+
+			for (int stageNdx = 0; stageNdx < DE_LENGTH_OF_ARRAY(checkStages); stageNdx++)
+			{
+
+				const string		name	= string(getDataTypeName(type)) + "_" + stageNames[stageNdx];
+
+				vector<UniformInfo> config;
+
+				UniformInfo			uniform	(VarType(createVarType(type), 8),
+												checkStages[stageNdx],
+												checkStages[stageNdx],
+												checkStages[stageNdx],
+												rng.getInt(0, maxLocations-1-8));
+
+				config.push_back(uniform);
+				group->addChild(new UniformLocationCase (m_testCtx, m_context.getRenderContext(), name.c_str(), name.c_str(), config));
+			}
+		}
+	}
+
+	// Nested Arrays
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(m_testCtx, "nested_array", "Array location specified with use, single shader stage");
+		de::Random					rng		(baseSeed + 0x3001);
+		addChild(group);
+
+		for (int primitiveNdx = 0; primitiveNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveNdx++)
+		{
+			const DataType		type	= primitiveTypes[primitiveNdx];
+
+			for (int stageNdx = 0; stageNdx < DE_LENGTH_OF_ARRAY(checkStages); stageNdx++)
+			{
+				const string		name		= string(getDataTypeName(type)) + "_" + stageNames[stageNdx];
+				// stay comfortably within minimum max uniform component count (896 in fragment) and sampler count with all types
+				const int			arraySize	= (getDataTypeScalarSize(type) > 4 || isDataTypeSampler(type)) ? 3 : 7;
+
+				vector<UniformInfo> config;
+
+				UniformInfo			uniform	(VarType(VarType(createVarType(type), arraySize), arraySize),
+											 checkStages[stageNdx],
+											 checkStages[stageNdx],
+											 checkStages[stageNdx],
+											 rng.getInt(0, maxLocations-1-arraySize*arraySize));
+
+				config.push_back(uniform);
+				group->addChild(new UniformLocationCase (m_testCtx, m_context.getRenderContext(), name.c_str(), name.c_str(), config));
+			}
+		}
+	}
+
+	// Structs
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(m_testCtx, "struct", "Struct location, random contents & declaration location");
+		de::Random					rng		(baseSeed + 0x4001);
+		addChild(group);
+
+		for (int caseNdx = 0; caseNdx < 16; caseNdx++)
+		{
+			typedef UniformInfo::ShaderStage Stage;
+
+			const string	name		= "case_" + de::toString(caseNdx);
+
+			const Stage		layoutLoc	= Stage(rng.getUint32()&0x3);
+			const Stage		declareLoc	= Stage((rng.getUint32()&0x3) | layoutLoc);
+			const Stage		verifyLoc	= Stage((rng.getUint32()&0x3) & declareLoc);
+			const int		location	= layoutLoc ? rng.getInt(0, maxLocations-1-5) : -1;
+
+			StructType*		structProto = new StructType("S");
+
+			structTypes.push_back(structProto);
+
+			structProto->addMember("a", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			structProto->addMember("b", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			structProto->addMember("c", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			structProto->addMember("d", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			structProto->addMember("e", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+
+			{
+				vector<UniformInfo> config;
+
+				config.push_back(UniformInfo(VarType(structProto),
+											 declareLoc,
+											 layoutLoc,
+											 verifyLoc,
+											 location));
+				group->addChild(new UniformLocationCase (m_testCtx, m_context.getRenderContext(), name.c_str(), name.c_str(), config));
+			}
+		}
+	}
+
+	// Nested Structs
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(m_testCtx, "nested_struct", "Struct location specified with use, single shader stage");
+		de::Random					rng		(baseSeed + 0x5001);
+
+		addChild(group);
+
+		for (int caseNdx = 0; caseNdx < 16; caseNdx++)
+		{
+			typedef UniformInfo::ShaderStage Stage;
+
+			const string	name		= "case_" + de::toString(caseNdx);
+			const int		baseLoc		= rng.getInt(0, maxLocations-1-60);
+
+			// Structs need to be added in the order of their declaration
+			const Stage		layoutLocs[]=
+			{
+				Stage(rng.getUint32()&0x3),
+				Stage(rng.getUint32()&0x3),
+				Stage(rng.getUint32()&0x3),
+				Stage(rng.getUint32()&0x3),
+			};
+
+			const deUint32	tempDecl[] =
+			{
+				(rng.getUint32()&0x3) | layoutLocs[0],
+				(rng.getUint32()&0x3) | layoutLocs[1],
+				(rng.getUint32()&0x3) | layoutLocs[2],
+				(rng.getUint32()&0x3) | layoutLocs[3],
+			};
+
+			// Component structs need to be declared if anything using them is declared
+			const Stage		declareLocs[] =
+			{
+				Stage(tempDecl[0] | tempDecl[1] | tempDecl[2] | tempDecl[3]),
+				Stage(tempDecl[1] | tempDecl[2] | tempDecl[3]),
+				Stage(tempDecl[2] | tempDecl[3]),
+				Stage(tempDecl[3]),
+			};
+
+			const Stage		verifyLocs[] =
+			{
+				Stage(rng.getUint32()&0x3 & declareLocs[0]),
+				Stage(rng.getUint32()&0x3 & declareLocs[1]),
+				Stage(rng.getUint32()&0x3 & declareLocs[2]),
+				Stage(rng.getUint32()&0x3 & declareLocs[3]),
+			};
+
+			StructType*		testTypes[]	=
+			{
+				new StructType("Type0"),
+				new StructType("Type1"),
+				new StructType("Type2"),
+				new StructType("Type3"),
+			};
+
+			structTypes.push_back(testTypes[0]);
+			structTypes.push_back(testTypes[1]);
+			structTypes.push_back(testTypes[2]);
+			structTypes.push_back(testTypes[3]);
+
+			testTypes[0]->addMember("a", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			testTypes[0]->addMember("b", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			testTypes[0]->addMember("c", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			testTypes[0]->addMember("d", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			testTypes[0]->addMember("e", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+
+			testTypes[1]->addMember("a", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			testTypes[1]->addMember("b", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			testTypes[1]->addMember("c", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			testTypes[1]->addMember("d", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+			testTypes[1]->addMember("e", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+
+			testTypes[2]->addMember("a", VarType(testTypes[0]));
+			testTypes[2]->addMember("b", VarType(testTypes[1]));
+			testTypes[2]->addMember("c", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
+
+			testTypes[3]->addMember("a", VarType(testTypes[2]));
+
+			{
+				vector<UniformInfo> config;
+
+				config.push_back(UniformInfo(VarType(testTypes[0]),
+											 declareLocs[0],
+											 layoutLocs[0],
+											 verifyLocs[0],
+											 layoutLocs[0] ? baseLoc : -1));
+
+				config.push_back(UniformInfo(VarType(testTypes[1]),
+											 declareLocs[1],
+											 layoutLocs[1],
+											 verifyLocs[1],
+											 layoutLocs[1] ? baseLoc+5 : -1));
+
+				config.push_back(UniformInfo(VarType(testTypes[2]),
+											 declareLocs[2],
+											 layoutLocs[2],
+											 verifyLocs[2],
+											 layoutLocs[2] ? baseLoc+16 : -1));
+
+				config.push_back(UniformInfo(VarType(testTypes[3]),
+											 declareLocs[3],
+											 layoutLocs[3],
+											 verifyLocs[3],
+											 layoutLocs[3] ? baseLoc+27 : -1));
+
+				group->addChild(new UniformLocationCase (m_testCtx, m_context.getRenderContext(), name.c_str(), name.c_str(), config));
+			}
+		}
+	}
+
+	// Min/Max location
+	{
+		tcu::TestCaseGroup* const	group		= new tcu::TestCaseGroup(m_testCtx, "min_max", "Maximum & minimum location");
+		de::Random					rng			(baseSeed + 0x1f01);
+
+		addChild(group);
+
+		for (int primitiveNdx = 0; primitiveNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveNdx++)
+		{
+			const DataType		type	= primitiveTypes[primitiveNdx];
+
+			for (int stageNdx = 0; stageNdx < DE_LENGTH_OF_ARRAY(checkStages); stageNdx++)
+			{
+				const string		name		= string(getDataTypeName(type)) + "_" + stageNames[stageNdx];
+				vector<UniformInfo> config;
+
+				config.push_back(UniformInfo(createVarType(type),
+											 checkStages[stageNdx],
+											 checkStages[stageNdx],
+											 checkStages[stageNdx],
+											 0));
+
+				group->addChild(new UniformLocationCase (m_testCtx, m_context.getRenderContext(), (name+"_min").c_str(), (name+"_min").c_str(), config));
+
+				group->addChild(new MaxUniformLocationCase (m_testCtx, m_context.getRenderContext(), (name+"_max").c_str(), (name+"_max").c_str(), config));
+			}
+		}
+	}
+
+	// Link
+	{
+		tcu::TestCaseGroup* const	group	= new tcu::TestCaseGroup(m_testCtx, "link", "Location specified independently from use");
+		de::Random					rng		(baseSeed + 0x82e1);
+
+		addChild(group);
+
+		for (int caseNdx = 0; caseNdx < 10; caseNdx++)
+		{
+			const string		name		= "case_" + de::toString(caseNdx);
+			vector<UniformInfo> config;
+
+			vector<int>			locations	= shuffledRange(0, maxLocations, 0x1234 + caseNdx*100);
+
+			for (int count = 0; count < 32; count++)
+			{
+				typedef UniformInfo::ShaderStage Stage;
+
+				const Stage			layoutLoc	= Stage(rng.getUint32()&0x3);
+				const Stage			declareLoc	= Stage((rng.getUint32()&0x3) | layoutLoc);
+				const Stage			verifyLoc	= Stage((rng.getUint32()&0x3) & declareLoc);
+
+				const UniformInfo	uniform		(createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]),
+												 declareLoc,
+												 layoutLoc,
+												 verifyLoc,
+												 (layoutLoc!=0) ? locations.back() : -1);
+
+				config.push_back(uniform);
+				locations.pop_back();
+			}
+			group->addChild(new UniformLocationCase (m_testCtx, m_context.getRenderContext(), name.c_str(), name.c_str(), config));
+		}
+	}
+
+	// Negative
+	{
+		gls::ShaderLibrary			shaderLibrary    (m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
+		const vector<TestNode*>     negativeCases    = shaderLibrary.loadShaderFile("shaders/uniform_location.test");
+		tcu::TestCaseGroup* const	group			 = new tcu::TestCaseGroup(m_testCtx, "negative", "Negative tests");
+
+		addChild(group);
+
+		for (int ndx = 0; ndx < int(negativeCases.size()); ndx++)
+			group->addChild(negativeCases[ndx]);
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fUniformLocationTests.hpp b/modules/gles31/functional/es31fUniformLocationTests.hpp
new file mode 100644
index 0000000..6a4f847
--- /dev/null
+++ b/modules/gles31/functional/es31fUniformLocationTests.hpp
@@ -0,0 +1,62 @@
+#ifndef _ES31FUNIFORMLOCATIONTESTS_HPP
+#define _ES31FUNIFORMLOCATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Explicit uniform location tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace glu
+{
+	class StructType;
+}
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+typedef std::vector<glu::StructType*> StructList;
+
+class UniformLocationTests : public TestCaseGroup
+{
+public:
+							UniformLocationTests	(Context& context);
+	virtual					~UniformLocationTests	(void);
+
+	void					init					(void);
+
+private:
+							UniformLocationTests	(const UniformLocationTests& other);
+	UniformLocationTests&	operator=				(const UniformLocationTests& other);
+
+	StructList				structTypes;
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FUNIFORMLOCATIONTESTS_HPP
diff --git a/modules/gles31/functional/es31fVertexAttributeBindingStateQueryTests.cpp b/modules/gles31/functional/es31fVertexAttributeBindingStateQueryTests.cpp
new file mode 100644
index 0000000..80d3305
--- /dev/null
+++ b/modules/gles31/functional/es31fVertexAttributeBindingStateQueryTests.cpp
@@ -0,0 +1,812 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex attribute binding state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fVertexAttributeBindingStateQueryTests.hpp"
+#include "tcuTestLog.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluRenderContext.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluStrUtil.hpp"
+#include "glsStateQueryUtil.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+class AttributeBindingCase : public TestCase
+{
+public:
+					AttributeBindingCase	(Context& context, const char* name, const char* desc);
+	IterateResult	iterate					(void);
+};
+
+AttributeBindingCase::AttributeBindingCase (Context& context, const char* name, const char* desc)
+	: TestCase(context, name, desc)
+{
+}
+
+AttributeBindingCase::IterateResult AttributeBindingCase::iterate (void)
+{
+	glu::CallLogWrapper gl			(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::VertexArray	vao			(m_context.getRenderContext());
+	glw::GLenum			error		= 0;
+	glw::GLint			maxAttrs	= -1;
+	bool				allOk		= true;
+
+	gl.enableLogging(true);
+
+	gl.glBindVertexArray(*vao);
+	gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttrs);
+
+	// initial
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial values");
+
+		for (int attr = 0; attr < de::max(16, maxAttrs); ++attr)
+		{
+			glw::GLint bindingState = -1;
+
+			gl.glGetVertexAttribiv(attr, GL_VERTEX_ATTRIB_BINDING, &bindingState);
+			error = gl.glGetError();
+
+			if (error != GL_NO_ERROR)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Got error " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+				allOk = false;
+			}
+			else if (bindingState != attr)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << attr << ", got " << bindingState << tcu::TestLog::EndMessage;
+				allOk = false;
+			}
+		}
+	}
+
+	// is part of vao
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "vao", "VAO state");
+		glu::VertexArray			otherVao		(m_context.getRenderContext());
+		glw::GLint					bindingState	= -1;
+
+		// set to value A in vao1
+		gl.glVertexAttribBinding(1, 4);
+
+		// set to value B in vao2
+		gl.glBindVertexArray(*otherVao);
+		gl.glVertexAttribBinding(1, 7);
+
+		// check value is still ok in original vao
+		gl.glBindVertexArray(*vao);
+		gl.glGetVertexAttribiv(1, GL_VERTEX_ATTRIB_BINDING, &bindingState);
+		error = gl.glGetError();
+
+		if (error != GL_NO_ERROR)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Got error " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+			allOk = false;
+		}
+		else if (bindingState != 4)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected 4, got " << bindingState << tcu::TestLog::EndMessage;
+			allOk = false;
+		}
+	}
+
+	// random values
+	{
+		const tcu::ScopedLogSection	section			(m_testCtx.getLog(), "random", "Random values");
+		de::Random					rnd				(0xabc);
+		const int					numRandomTests	= 10;
+
+		for (int randomTestNdx = 0; randomTestNdx < numRandomTests; ++randomTestNdx)
+		{
+			// switch random va to random binding
+			const int	va				= rnd.getInt(0, de::max(16, maxAttrs)-1);
+			const int	binding			= rnd.getInt(0, 16);
+			glw::GLint	bindingState	= -1;
+
+			gl.glVertexAttribBinding(va, binding);
+			gl.glGetVertexAttribiv(va, GL_VERTEX_ATTRIB_BINDING, &bindingState);
+			error = gl.glGetError();
+
+			if (error != GL_NO_ERROR)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Got error " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+				allOk = false;
+			}
+			else if (bindingState != binding)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << binding << ", got " << bindingState << tcu::TestLog::EndMessage;
+				allOk = false;
+			}
+		}
+	}
+
+	if (allOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	return STOP;
+}
+
+class AttributeRelativeOffsetCase : public TestCase
+{
+public:
+					AttributeRelativeOffsetCase	(Context& context, const char* name, const char* desc);
+	IterateResult	iterate						(void);
+};
+
+AttributeRelativeOffsetCase::AttributeRelativeOffsetCase (Context& context, const char* name, const char* desc)
+	: TestCase(context, name, desc)
+{
+}
+
+AttributeRelativeOffsetCase::IterateResult AttributeRelativeOffsetCase::iterate (void)
+{
+	glu::CallLogWrapper gl			(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::VertexArray	vao			(m_context.getRenderContext());
+	glw::GLenum			error		= 0;
+	glw::GLint			maxAttrs	= -1;
+	bool				allOk		= true;
+
+	gl.enableLogging(true);
+
+	gl.glBindVertexArray(*vao);
+	gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttrs);
+
+	// initial
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial values");
+
+		for (int attr = 0; attr < de::max(16, maxAttrs); ++attr)
+		{
+			glw::GLint relOffsetState = -1;
+
+			gl.glGetVertexAttribiv(attr, GL_VERTEX_ATTRIB_RELATIVE_OFFSET, &relOffsetState);
+			error = gl.glGetError();
+
+			if (error != GL_NO_ERROR)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Got error " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+				allOk = false;
+			}
+			else if (relOffsetState != 0)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected 0, got " << relOffsetState << tcu::TestLog::EndMessage;
+				allOk = false;
+			}
+		}
+	}
+
+	// is part of vao
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "vao", "VAO state");
+		glu::VertexArray			otherVao		(m_context.getRenderContext());
+		glw::GLint					relOffsetState	= -1;
+
+		// set to value A in vao1
+		gl.glVertexAttribFormat(1, 4, GL_FLOAT, GL_FALSE, 9);
+
+		// set to value B in vao2
+		gl.glBindVertexArray(*otherVao);
+		gl.glVertexAttribFormat(1, 4, GL_FLOAT, GL_FALSE, 21);
+
+		// check value is still ok in original vao
+		gl.glBindVertexArray(*vao);
+		gl.glGetVertexAttribiv(1, GL_VERTEX_ATTRIB_RELATIVE_OFFSET, &relOffsetState);
+		error = gl.glGetError();
+
+		if (error != GL_NO_ERROR)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Got error " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+			allOk = false;
+		}
+		else if (relOffsetState != 9)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected 9, got " << relOffsetState << tcu::TestLog::EndMessage;
+			allOk = false;
+		}
+	}
+
+	// random values
+	{
+		const tcu::ScopedLogSection	section			(m_testCtx.getLog(), "random", "Random values");
+		de::Random					rnd				(0xabc);
+		const int					numRandomTests	= 10;
+
+		for (int randomTestNdx = 0; randomTestNdx < numRandomTests; ++randomTestNdx)
+		{
+			const int	va				= rnd.getInt(0, de::max(16, maxAttrs)-1);
+			const int	offset			= rnd.getInt(0, 2047);
+			glw::GLint	relOffsetState	= -1;
+
+			gl.glVertexAttribFormat(va, 4, GL_FLOAT, GL_FALSE, offset);
+			gl.glGetVertexAttribiv(va, GL_VERTEX_ATTRIB_RELATIVE_OFFSET, &relOffsetState);
+			error = gl.glGetError();
+
+			if (error != GL_NO_ERROR)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Got error " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+				allOk = false;
+			}
+			else if (relOffsetState != offset)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << offset << ", got " << relOffsetState << tcu::TestLog::EndMessage;
+				allOk = false;
+			}
+		}
+	}
+
+	if (allOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid value");
+	return STOP;
+}
+
+class IndexedCase : public TestCase
+{
+public:
+	enum VerifierType
+	{
+		VERIFIER_INT,
+		VERIFIER_INT64,
+
+		VERIFIER_LAST
+	};
+
+						IndexedCase			(Context& context, const char* name, const char* desc, VerifierType verifier);
+
+	IterateResult		iterate				(void);
+	void				verifyValue			(glu::CallLogWrapper& gl, glw::GLenum name, int index, int expected);
+
+	virtual void		test				(void) = 0;
+private:
+	const VerifierType	m_verifier;
+};
+
+IndexedCase::IndexedCase (Context& context, const char* name, const char* desc, VerifierType verifier)
+	: TestCase		(context, name, desc)
+	, m_verifier	(verifier)
+{
+	DE_ASSERT(verifier < VERIFIER_LAST);
+}
+
+IndexedCase::IterateResult IndexedCase::iterate (void)
+{
+	// default value
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	test();
+
+	return STOP;
+}
+
+void IndexedCase::verifyValue (glu::CallLogWrapper& gl, glw::GLenum name, int index, int expected)
+{
+	if (m_verifier == VERIFIER_INT)
+	{
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint>	value;
+		glw::GLenum													error = 0;
+
+		gl.glGetIntegeri_v(name, index, &value);
+		error = gl.glGetError();
+
+		if (error != GL_NO_ERROR)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Got unexpected error: " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+			if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected error");
+		}
+		else if (!value.verifyValidity(m_testCtx))
+		{
+			// verifyValidity sets error
+		}
+		else
+		{
+			if (value != expected)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << expected << ", got " << value << tcu::TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected value");
+			}
+		}
+	}
+	else if (m_verifier == VERIFIER_INT64)
+	{
+		gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint64>	value;
+		glw::GLenum														error = 0;
+
+		gl.glGetInteger64i_v(name, index, &value);
+		error = gl.glGetError();
+
+		if (error != GL_NO_ERROR)
+		{
+			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Got unexpected error: " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+			if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected error");
+		}
+		else if (!value.verifyValidity(m_testCtx))
+		{
+			// verifyValidity sets error
+		}
+		else
+		{
+			if (value != expected)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Expected " << expected << ", got " << value << tcu::TestLog::EndMessage;
+				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected value");
+			}
+		}
+	}
+	else
+		DE_ASSERT(false);
+}
+
+class VertexBindingDivisorCase : public IndexedCase
+{
+public:
+			VertexBindingDivisorCase	(Context& context, const char* name, const char* desc, VerifierType verifier);
+	void	test						(void);
+};
+
+VertexBindingDivisorCase::VertexBindingDivisorCase (Context& context, const char* name, const char* desc, VerifierType verifier)
+	: IndexedCase(context, name, desc, verifier)
+{
+}
+
+void VertexBindingDivisorCase::test (void)
+{
+	glu::CallLogWrapper gl					(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::VertexArray	vao					(m_context.getRenderContext());
+	glw::GLint			reportedMaxBindings	= -1;
+	glw::GLint			maxBindings;
+
+	gl.enableLogging(true);
+
+	gl.glBindVertexArray(*vao);
+	gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &reportedMaxBindings);
+	maxBindings = de::max(16, reportedMaxBindings);
+
+	// initial
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial values");
+
+		for (int binding = 0; binding < maxBindings; ++binding)
+			verifyValue(gl, GL_VERTEX_BINDING_DIVISOR, binding, 0);
+	}
+
+	// is part of vao
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "vao", "VAO state");
+		glu::VertexArray			otherVao		(m_context.getRenderContext());
+
+		// set to value A in vao1
+		gl.glVertexBindingDivisor(1, 4);
+
+		// set to value B in vao2
+		gl.glBindVertexArray(*otherVao);
+		gl.glVertexBindingDivisor(1, 9);
+
+		// check value is still ok in original vao
+		gl.glBindVertexArray(*vao);
+		verifyValue(gl, GL_VERTEX_BINDING_DIVISOR, 1, 4);
+	}
+
+	// random values
+	{
+		const tcu::ScopedLogSection	section			(m_testCtx.getLog(), "random", "Random values");
+		de::Random					rnd				(0xabc);
+		const int					numRandomTests	= 10;
+
+		for (int randomTestNdx = 0; randomTestNdx < numRandomTests; ++randomTestNdx)
+		{
+			const int	binding			= rnd.getInt(0, maxBindings-1);
+			const int	divisor			= rnd.getInt(0, 2047);
+
+			gl.glVertexBindingDivisor(binding, divisor);
+			verifyValue(gl, GL_VERTEX_BINDING_DIVISOR, binding, divisor);
+		}
+	}
+}
+
+class VertexBindingOffsetCase : public IndexedCase
+{
+public:
+			VertexBindingOffsetCase		(Context& context, const char* name, const char* desc, VerifierType verifier);
+	void	test						(void);
+};
+
+VertexBindingOffsetCase::VertexBindingOffsetCase (Context& context, const char* name, const char* desc, VerifierType verifier)
+	: IndexedCase(context, name, desc, verifier)
+{
+}
+
+void VertexBindingOffsetCase::test (void)
+{
+	glu::CallLogWrapper gl					(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::VertexArray	vao					(m_context.getRenderContext());
+	glu::Buffer			buffer				(m_context.getRenderContext());
+	glw::GLint			reportedMaxBindings	= -1;
+	glw::GLint			maxBindings;
+
+	gl.enableLogging(true);
+
+	gl.glBindVertexArray(*vao);
+	gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &reportedMaxBindings);
+	maxBindings = de::max(16, reportedMaxBindings);
+
+	// initial
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial values");
+
+		for (int binding = 0; binding < maxBindings; ++binding)
+			verifyValue(gl, GL_VERTEX_BINDING_OFFSET, binding, 0);
+	}
+
+	// is part of vao
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "vao", "VAO state");
+		glu::VertexArray			otherVao		(m_context.getRenderContext());
+
+		// set to value A in vao1
+		gl.glBindVertexBuffer(1, *buffer, 4, 32);
+
+		// set to value B in vao2
+		gl.glBindVertexArray(*otherVao);
+		gl.glBindVertexBuffer(1, *buffer, 13, 32);
+
+		// check value is still ok in original vao
+		gl.glBindVertexArray(*vao);
+		verifyValue(gl, GL_VERTEX_BINDING_OFFSET, 1, 4);
+	}
+
+	// random values
+	{
+		const tcu::ScopedLogSection	section			(m_testCtx.getLog(), "random", "Random values");
+		de::Random					rnd				(0xabc);
+		const int					numRandomTests	= 10;
+
+		for (int randomTestNdx = 0; randomTestNdx < numRandomTests; ++randomTestNdx)
+		{
+			const int	binding			= rnd.getInt(0, maxBindings-1);
+			const int	offset			= rnd.getInt(0, 4000);
+
+			gl.glBindVertexBuffer(binding, *buffer, offset, 32);
+			verifyValue(gl, GL_VERTEX_BINDING_OFFSET, binding, offset);
+		}
+	}
+}
+
+class VertexBindingStrideCase : public IndexedCase
+{
+public:
+			VertexBindingStrideCase		(Context& context, const char* name, const char* desc, VerifierType verifier);
+	void	test						(void);
+};
+
+VertexBindingStrideCase::VertexBindingStrideCase (Context& context, const char* name, const char* desc, VerifierType verifier)
+	: IndexedCase(context, name, desc, verifier)
+{
+}
+
+void VertexBindingStrideCase::test (void)
+{
+	glu::CallLogWrapper gl					(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::VertexArray	vao					(m_context.getRenderContext());
+	glu::Buffer			buffer				(m_context.getRenderContext());
+	glw::GLint			reportedMaxBindings	= -1;
+	glw::GLint			maxBindings;
+
+	gl.enableLogging(true);
+
+	gl.glBindVertexArray(*vao);
+	gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &reportedMaxBindings);
+	maxBindings = de::max(16, reportedMaxBindings);
+
+	// initial
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial values");
+
+		for (int binding = 0; binding < maxBindings; ++binding)
+			verifyValue(gl, GL_VERTEX_BINDING_STRIDE, binding, 16);
+	}
+
+	// is part of vao
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "vao", "VAO state");
+		glu::VertexArray			otherVao		(m_context.getRenderContext());
+
+		// set to value A in vao1
+		gl.glBindVertexBuffer(1, *buffer, 0, 32);
+
+		// set to value B in vao2
+		gl.glBindVertexArray(*otherVao);
+		gl.glBindVertexBuffer(1, *buffer, 0, 64);
+
+		// check value is still ok in original vao
+		gl.glBindVertexArray(*vao);
+		verifyValue(gl, GL_VERTEX_BINDING_STRIDE, 1, 32);
+	}
+
+	// random values
+	{
+		const tcu::ScopedLogSection	section			(m_testCtx.getLog(), "random", "Random values");
+		de::Random					rnd				(0xabc);
+		const int					numRandomTests	= 10;
+
+		for (int randomTestNdx = 0; randomTestNdx < numRandomTests; ++randomTestNdx)
+		{
+			const int	binding			= rnd.getInt(0, maxBindings-1);
+			const int	stride			= rnd.getInt(0, 2048);
+
+			gl.glBindVertexBuffer(binding, *buffer, 0, stride);
+			verifyValue(gl, GL_VERTEX_BINDING_STRIDE, binding, stride);
+		}
+	}
+}
+
+class VertexBindingBufferCase : public IndexedCase
+{
+public:
+			VertexBindingBufferCase		(Context& context, const char* name, const char* desc, VerifierType verifier);
+	void	test						(void);
+};
+
+VertexBindingBufferCase::VertexBindingBufferCase (Context& context, const char* name, const char* desc, VerifierType verifier)
+	: IndexedCase(context, name, desc, verifier)
+{
+}
+
+void VertexBindingBufferCase::test (void)
+{
+	glu::CallLogWrapper gl					(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::VertexArray	vao					(m_context.getRenderContext());
+	glu::Buffer			buffer				(m_context.getRenderContext());
+	glw::GLint			reportedMaxBindings	= -1;
+	glw::GLint			maxBindings;
+
+	gl.enableLogging(true);
+
+	gl.glBindVertexArray(*vao);
+	gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &reportedMaxBindings);
+	maxBindings = de::max(16, reportedMaxBindings);
+
+	// initial
+	{
+		const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial values");
+
+		for (int binding = 0; binding < maxBindings; ++binding)
+			verifyValue(gl, GL_VERTEX_BINDING_BUFFER, binding, 0);
+	}
+
+	// is part of vao
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "vao", "VAO state");
+		glu::VertexArray			otherVao		(m_context.getRenderContext());
+		glu::Buffer					otherBuffer		(m_context.getRenderContext());
+
+		// set to value A in vao1
+		gl.glBindVertexBuffer(1, *buffer, 0, 32);
+
+		// set to value B in vao2
+		gl.glBindVertexArray(*otherVao);
+		gl.glBindVertexBuffer(1, *otherBuffer, 0, 32);
+
+		// check value is still ok in original vao
+		gl.glBindVertexArray(*vao);
+		verifyValue(gl, GL_VERTEX_BINDING_BUFFER, 1, *buffer);
+	}
+
+	// Is detached in delete from active vao and not from deactive
+	{
+		const tcu::ScopedLogSection section			(m_testCtx.getLog(), "autoUnbind", "Unbind on delete");
+		glu::VertexArray			otherVao		(m_context.getRenderContext());
+		glw::GLuint					otherBuffer		= -1;
+
+		gl.glGenBuffers(1, &otherBuffer);
+
+		// set in vao1 and vao2
+		gl.glBindVertexBuffer(1, otherBuffer, 0, 32);
+		gl.glBindVertexArray(*otherVao);
+		gl.glBindVertexBuffer(1, otherBuffer, 0, 32);
+
+		// delete buffer. This unbinds it from active (vao2) but not from unactive
+		gl.glDeleteBuffers(1, &otherBuffer);
+		verifyValue(gl, GL_VERTEX_BINDING_BUFFER, 1, 0);
+		gl.glBindVertexArray(*vao);
+		verifyValue(gl, GL_VERTEX_BINDING_BUFFER, 1, otherBuffer);
+	}
+}
+
+class MixedVertexBindingDivisorCase : public IndexedCase
+{
+public:
+			MixedVertexBindingDivisorCase	(Context& context, const char* name, const char* desc);
+	void	test							(void);
+};
+
+MixedVertexBindingDivisorCase::MixedVertexBindingDivisorCase (Context& context, const char* name, const char* desc)
+	: IndexedCase(context, name, desc, VERIFIER_INT)
+{
+}
+
+void MixedVertexBindingDivisorCase::test (void)
+{
+	glu::CallLogWrapper gl					(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::VertexArray	vao					(m_context.getRenderContext());
+
+	gl.enableLogging(true);
+
+	gl.glVertexAttribDivisor(1, 4);
+	verifyValue(gl, GL_VERTEX_BINDING_DIVISOR, 1, 4);
+}
+
+class MixedVertexBindingOffsetCase : public IndexedCase
+{
+public:
+			MixedVertexBindingOffsetCase	(Context& context, const char* name, const char* desc);
+	void	test							(void);
+};
+
+MixedVertexBindingOffsetCase::MixedVertexBindingOffsetCase (Context& context, const char* name, const char* desc)
+	: IndexedCase(context, name, desc, VERIFIER_INT)
+{
+}
+
+void MixedVertexBindingOffsetCase::test (void)
+{
+	glu::CallLogWrapper gl					(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::VertexArray	vao					(m_context.getRenderContext());
+	glu::Buffer			buffer				(m_context.getRenderContext());
+
+	gl.enableLogging(true);
+
+	gl.glBindBuffer(GL_ARRAY_BUFFER, *buffer);
+	gl.glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (const deUint8*)DE_NULL + 12);
+
+	verifyValue(gl, GL_VERTEX_BINDING_OFFSET, 1, 12);
+}
+
+class MixedVertexBindingStrideCase : public IndexedCase
+{
+public:
+			MixedVertexBindingStrideCase	(Context& context, const char* name, const char* desc);
+	void	test							(void);
+};
+
+MixedVertexBindingStrideCase::MixedVertexBindingStrideCase (Context& context, const char* name, const char* desc)
+	: IndexedCase(context, name, desc, VERIFIER_INT)
+{
+}
+
+void MixedVertexBindingStrideCase::test (void)
+{
+	glu::CallLogWrapper gl					(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::VertexArray	vao					(m_context.getRenderContext());
+	glu::Buffer			buffer				(m_context.getRenderContext());
+
+	gl.enableLogging(true);
+
+	gl.glBindBuffer(GL_ARRAY_BUFFER, *buffer);
+	gl.glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 12, 0);
+	verifyValue(gl, GL_VERTEX_BINDING_STRIDE, 1, 12);
+
+	// test effectiveStride
+	gl.glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, 0);
+	verifyValue(gl, GL_VERTEX_BINDING_STRIDE, 1, 16);
+}
+
+class MixedVertexBindingBufferCase : public IndexedCase
+{
+public:
+			MixedVertexBindingBufferCase	(Context& context, const char* name, const char* desc);
+	void	test							(void);
+};
+
+MixedVertexBindingBufferCase::MixedVertexBindingBufferCase (Context& context, const char* name, const char* desc)
+	: IndexedCase(context, name, desc, VERIFIER_INT)
+{
+}
+
+void MixedVertexBindingBufferCase::test (void)
+{
+	glu::CallLogWrapper gl					(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::VertexArray	vao					(m_context.getRenderContext());
+	glu::Buffer			buffer				(m_context.getRenderContext());
+
+	gl.enableLogging(true);
+
+	gl.glBindBuffer(GL_ARRAY_BUFFER, *buffer);
+	gl.glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, 0);
+	verifyValue(gl, GL_VERTEX_BINDING_BUFFER, 1, *buffer);
+}
+
+} // anonymous
+
+VertexAttributeBindingStateQueryTests::VertexAttributeBindingStateQueryTests (Context& context)
+	: TestCaseGroup(context, "vertex_attribute_binding", "Query vertex attribute binding state.")
+{
+}
+
+VertexAttributeBindingStateQueryTests::~VertexAttributeBindingStateQueryTests (void)
+{
+}
+
+void VertexAttributeBindingStateQueryTests::init (void)
+{
+	tcu::TestCaseGroup* const attributeGroup	= new TestCaseGroup(m_context, "vertex_attrib", "Vertex attribute state");
+	tcu::TestCaseGroup* const indexedGroup		= new TestCaseGroup(m_context, "indexed", "Indexed state");
+
+	addChild(attributeGroup);
+	addChild(indexedGroup);
+
+	// .vertex_attrib
+	{
+		attributeGroup->addChild(new AttributeBindingCase		(m_context,	"vertex_attrib_binding",			"Test VERTEX_ATTRIB_BINDING"));
+		attributeGroup->addChild(new AttributeRelativeOffsetCase(m_context,	"vertex_attrib_relative_offset",	"Test VERTEX_ATTRIB_RELATIVE_OFFSET"));
+	}
+
+	// .indexed (and 64)
+	{
+		static const struct Verifier
+		{
+			const char*					name;
+			IndexedCase::VerifierType	type;
+		} verifiers[] =
+		{
+			{ "getintegeri",	IndexedCase::VERIFIER_INT	},
+			{ "getintegeri64",	IndexedCase::VERIFIER_INT64	},
+		};
+
+		// states
+
+		for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)
+		{
+			indexedGroup->addChild(new VertexBindingDivisorCase	(m_context, (std::string("vertex_binding_divisor_") + verifiers[verifierNdx].name).c_str(),	"Test VERTEX_BINDING_DIVISOR",	verifiers[verifierNdx].type));
+			indexedGroup->addChild(new VertexBindingOffsetCase	(m_context, (std::string("vertex_binding_offset_") + verifiers[verifierNdx].name).c_str(),	"Test VERTEX_BINDING_OFFSET",	verifiers[verifierNdx].type));
+			indexedGroup->addChild(new VertexBindingStrideCase	(m_context, (std::string("vertex_binding_stride_") + verifiers[verifierNdx].name).c_str(),	"Test VERTEX_BINDING_STRIDE",	verifiers[verifierNdx].type));
+			indexedGroup->addChild(new VertexBindingBufferCase	(m_context, (std::string("vertex_binding_buffer_") + verifiers[verifierNdx].name).c_str(),	"Test VERTEX_BINDING_BUFFER",	verifiers[verifierNdx].type));
+		}
+
+		// mixed apis
+
+		indexedGroup->addChild(new MixedVertexBindingDivisorCase(m_context, "vertex_binding_divisor_mixed",	"Test VERTEX_BINDING_DIVISOR"));
+		indexedGroup->addChild(new MixedVertexBindingOffsetCase	(m_context, "vertex_binding_offset_mixed",	"Test VERTEX_BINDING_OFFSET"));
+		indexedGroup->addChild(new MixedVertexBindingStrideCase	(m_context, "vertex_binding_stride_mixed",	"Test VERTEX_BINDING_STRIDE"));
+		indexedGroup->addChild(new MixedVertexBindingBufferCase	(m_context, "vertex_binding_buffer_mixed",	"Test VERTEX_BINDING_BUFFER"));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fVertexAttributeBindingStateQueryTests.hpp b/modules/gles31/functional/es31fVertexAttributeBindingStateQueryTests.hpp
new file mode 100644
index 0000000..14e2f76
--- /dev/null
+++ b/modules/gles31/functional/es31fVertexAttributeBindingStateQueryTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FVERTEXATTRIBUTEBINDINGSTATEQUERYTESTS_HPP
+#define _ES31FVERTEXATTRIBUTEBINDINGSTATEQUERYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex attribute binding state query tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class VertexAttributeBindingStateQueryTests : public TestCaseGroup
+{
+public:
+											VertexAttributeBindingStateQueryTests	(Context& context);
+											~VertexAttributeBindingStateQueryTests	(void);
+
+	void									init									(void);
+
+private:
+											VertexAttributeBindingStateQueryTests	(const VertexAttributeBindingStateQueryTests& other);
+	VertexAttributeBindingStateQueryTests&	operator=								(const VertexAttributeBindingStateQueryTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FVERTEXATTRIBUTEBINDINGSTATEQUERYTESTS_HPP
diff --git a/modules/gles31/functional/es31fVertexAttributeBindingTests.cpp b/modules/gles31/functional/es31fVertexAttributeBindingTests.cpp
new file mode 100644
index 0000000..292b342
--- /dev/null
+++ b/modules/gles31/functional/es31fVertexAttributeBindingTests.cpp
@@ -0,0 +1,1639 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex attribute binding tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31fVertexAttributeBindingTests.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluRenderContext.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluStrUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+#include "deInt32.h"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+namespace
+{
+
+static const char* const s_colorFragmentShader =		"#version 310 es\n"
+														"in mediump vec4 v_color;\n"
+														"layout(location = 0) out mediump vec4 fragColor;\n"
+														"void main (void)\n"
+														"{\n"
+														"	fragColor = v_color;\n"
+														"}\n";
+
+static const char* const s_positionColorShader =		"#version 310 es\n"
+														"in highp vec4 a_position;\n"
+														"in highp vec4 a_color;\n"
+														"out highp vec4 v_color;\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = a_position;\n"
+														"	v_color = a_color;\n"
+														"}\n";
+
+static const char* const s_positionColorOffsetShader =	"#version 310 es\n"
+														"in highp vec4 a_position;\n"
+														"in highp vec4 a_offset;\n"
+														"in highp vec4 a_color;\n"
+														"out highp vec4 v_color;\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = a_position + a_offset;\n"
+														"	v_color = a_color;\n"
+														"}\n";
+
+// Verifies image contains only yellow or greeen, or a linear combination
+// of these colors.
+static bool verifyImageYellowGreen (const tcu::Surface& image, tcu::TestLog& log, bool logImageOnSuccess)
+{
+	using tcu::TestLog;
+
+	const tcu::RGBA green		(0, 255, 0, 255);
+	const tcu::RGBA yellow		(255, 255, 0, 255);
+	const int colorThreshold	= 20;
+
+	tcu::Surface error			(image.getWidth(), image.getHeight());
+	bool isOk					= true;
+
+	log << TestLog::Message << "Verifying image contents." << TestLog::EndMessage;
+
+	for (int y = 0; y < image.getHeight(); y++)
+	for (int x = 0; x < image.getWidth(); x++)
+	{
+		const tcu::RGBA pixel = image.getPixel(x, y);
+		bool pixelOk = true;
+
+		// Any pixel with !(G ~= 255) is faulty (not a linear combinations of green and yellow)
+		if (de::abs(pixel.getGreen() - 255) > colorThreshold)
+			pixelOk = false;
+
+		// Any pixel with !(B ~= 0) is faulty (not a linear combinations of green and yellow)
+		if (de::abs(pixel.getBlue() - 0) > colorThreshold)
+			pixelOk = false;
+
+		error.setPixel(x, y, (pixelOk) ? (tcu::RGBA(0, 255, 0, 255)) : (tcu::RGBA(255, 0, 0, 255)));
+		isOk = isOk && pixelOk;
+	}
+
+	if (!isOk)
+	{
+		log << TestLog::Message << "Image verification failed." << TestLog::EndMessage;
+		log << TestLog::ImageSet("Verfication result", "Result of rendering")
+			<< TestLog::Image("Result",		"Result",		image)
+			<< TestLog::Image("ErrorMask",	"Error mask",	error)
+			<< TestLog::EndImageSet;
+	}
+	else
+	{
+		log << TestLog::Message << "Image verification passed." << TestLog::EndMessage;
+
+		if (logImageOnSuccess)
+			log << TestLog::ImageSet("Verfication result", "Result of rendering")
+				<< TestLog::Image("Result", "Result", image)
+				<< TestLog::EndImageSet;
+	}
+
+	return isOk;
+}
+
+class BindingRenderCase : public TestCase
+{
+public:
+	enum
+	{
+		TEST_RENDER_SIZE = 64
+	};
+
+						BindingRenderCase	(Context& ctx, const char* name, const char* desc, bool unalignedData);
+	virtual				~BindingRenderCase	(void);
+
+	virtual void		init				(void);
+	virtual void		deinit				(void);
+	IterateResult		iterate				(void);
+
+private:
+	virtual void		renderTo			(tcu::Surface& dst) = 0;
+	virtual void		createBuffers		(void) = 0;
+	virtual void		createShader		(void) = 0;
+
+protected:
+	const bool			m_unalignedData;
+	glw::GLuint			m_vao;
+	glu::ShaderProgram*	m_program;
+};
+
+BindingRenderCase::BindingRenderCase (Context& ctx, const char* name, const char* desc, bool unalignedData)
+	: TestCase			(ctx, name, desc)
+	, m_unalignedData	(unalignedData)
+	, m_vao				(0)
+	, m_program			(DE_NULL)
+{
+}
+
+BindingRenderCase::~BindingRenderCase (void)
+{
+	deinit();
+}
+
+void BindingRenderCase::init (void)
+{
+	// check requirements
+	if (m_context.getRenderTarget().getWidth() < TEST_RENDER_SIZE || m_context.getRenderTarget().getHeight() < TEST_RENDER_SIZE)
+		throw tcu::NotSupportedError("Test requires at least " + de::toString<int>(TEST_RENDER_SIZE) + "x" + de::toString<int>(TEST_RENDER_SIZE) + " render target");
+
+	// resources
+	m_context.getRenderContext().getFunctions().genVertexArrays(1, &m_vao);
+	if (m_context.getRenderContext().getFunctions().getError() != GL_NO_ERROR)
+		throw tcu::TestError("could not gen vao");
+
+	createBuffers();
+	createShader();
+}
+
+void BindingRenderCase::deinit (void)
+{
+	if (m_vao)
+	{
+		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vao);
+		m_vao = 0;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+BindingRenderCase::IterateResult BindingRenderCase::iterate (void)
+{
+	tcu::Surface surface(TEST_RENDER_SIZE, TEST_RENDER_SIZE);
+
+	// draw pattern
+
+	renderTo(surface);
+
+	// verify results
+
+	if (verifyImageYellowGreen(surface, m_testCtx.getLog(), false))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else if (m_unalignedData)
+		m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned data");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+
+	return STOP;
+}
+
+class SingleBindingCase : public BindingRenderCase
+{
+public:
+
+	enum CaseFlag
+	{
+		FLAG_ATTRIB_UNALIGNED			= (1<<0),		// !< unalign attributes with relativeOffset
+		FLAG_ATTRIB_ALIGNED				= (1<<1),		// !< align attributes with relativeOffset to the buffer begin (and not buffer offset)
+		FLAG_ATTRIBS_MULTIPLE_ELEMS		= (1<<2),		// !< use multiple attribute elements
+		FLAG_ATTRIBS_SHARED_ELEMS		= (1<<3),		// !< use multiple shared attribute elements. xyzw & rgba stored as (x, y, zr, wg, b, a)
+
+		FLAG_BUF_ALIGNED_OFFSET			= (1<<4),		// !< use aligned offset to the buffer object
+		FLAG_BUF_UNALIGNED_OFFSET		= (1<<5),		// !< use unaligned offset to the buffer object
+		FLAG_BUF_UNALIGNED_STRIDE		= (1<<6),		// !< unalign buffer elements
+	};
+						SingleBindingCase	(Context& ctx, const char* name, int flags);
+						~SingleBindingCase	(void);
+
+	void				init				(void);
+	void				deinit				(void);
+
+private:
+	struct TestSpec
+	{
+		int		bufferOffset;
+		int		bufferStride;
+		int		positionAttrOffset;
+		int		colorAttrOffset;
+		bool	hasColorAttr;
+	};
+
+	enum
+	{
+		GRID_SIZE = 20
+	};
+
+	void				renderTo			(tcu::Surface& dst);
+
+	static TestSpec		genTestSpec			(int flags);
+	static std::string	genTestDescription	(int flags);
+	static bool			isDataUnaligned		(int flags);
+
+	void				createBuffers		(void);
+	void				createShader		(void);
+	std::string			genVertexSource		(void);
+
+	const TestSpec		m_spec;
+	glw::GLuint			m_buf;
+};
+
+SingleBindingCase::SingleBindingCase (Context& ctx, const char* name, int flags)
+	: BindingRenderCase	(ctx, name, genTestDescription(flags).c_str(), isDataUnaligned(flags))
+	, m_spec			(genTestSpec(flags))
+	, m_buf				(0)
+{
+	DE_ASSERT(!((flags & FLAG_ATTRIB_UNALIGNED) && (flags & FLAG_ATTRIB_ALIGNED)));
+	DE_ASSERT(!((flags & FLAG_ATTRIB_ALIGNED) && (flags & FLAG_BUF_UNALIGNED_STRIDE)));
+
+	DE_ASSERT(!isDataUnaligned(flags));
+}
+
+SingleBindingCase::~SingleBindingCase (void)
+{
+	deinit();
+}
+
+void SingleBindingCase::init (void)
+{
+	// log what we are trying to do
+
+	m_testCtx.getLog()	<< tcu::TestLog::Message
+						<< "Rendering " << (int)GRID_SIZE << "x" << (int)GRID_SIZE << " grid.\n"
+						<< "Buffer format:\n"
+						<< "	bufferOffset: " << m_spec.bufferOffset << "\n"
+						<< "	bufferStride: " << m_spec.bufferStride << "\n"
+						<< "Vertex position format:\n"
+						<< "	type: float4\n"
+						<< "	offset: " << m_spec.positionAttrOffset << "\n"
+						<< "	total offset: " << m_spec.bufferOffset + m_spec.positionAttrOffset << "\n"
+						<< tcu::TestLog::EndMessage;
+
+	if (m_spec.hasColorAttr)
+		m_testCtx.getLog()	<< tcu::TestLog::Message
+							<< "Color:\n"
+							<< "	type: float4\n"
+							<< "	offset: " << m_spec.colorAttrOffset << "\n"
+							<< "	total offset: " << m_spec.bufferOffset + m_spec.colorAttrOffset << "\n"
+							<< tcu::TestLog::EndMessage;
+	// init
+
+	BindingRenderCase::init();
+}
+
+void SingleBindingCase::deinit (void)
+{
+	if (m_buf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buf);
+		m_buf = 0;
+	}
+
+	BindingRenderCase::deinit();
+}
+
+void SingleBindingCase::renderTo (tcu::Surface& dst)
+{
+	glu::CallLogWrapper gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	const int			positionLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
+	const int			colorLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_color");
+	const int			colorUniformLoc	= gl.glGetUniformLocation(m_program->getProgram(), "u_color");
+
+	gl.enableLogging(true);
+
+	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	gl.glClear(GL_COLOR_BUFFER_BIT);
+	gl.glViewport(0, 0, dst.getWidth(), dst.getHeight());
+	gl.glBindVertexArray(m_vao);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set vao");
+
+	gl.glUseProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");
+
+	if (m_spec.hasColorAttr)
+	{
+		gl.glBindVertexBuffer(3, m_buf, m_spec.bufferOffset, m_spec.bufferStride);
+
+		gl.glVertexAttribBinding(positionLoc, 3);
+		gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, m_spec.positionAttrOffset);
+		gl.glEnableVertexAttribArray(positionLoc);
+
+		gl.glVertexAttribBinding(colorLoc, 3);
+		gl.glVertexAttribFormat(colorLoc, 4, GL_FLOAT, GL_FALSE, m_spec.colorAttrOffset);
+		gl.glEnableVertexAttribArray(colorLoc);
+
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");
+
+		gl.glDrawArrays(GL_TRIANGLES, 0, GRID_SIZE*GRID_SIZE*6);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw");
+	}
+	else
+	{
+		gl.glBindVertexBuffer(3, m_buf, m_spec.bufferOffset, m_spec.bufferStride);
+		gl.glVertexAttribBinding(positionLoc, 3);
+		gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, m_spec.positionAttrOffset);
+		gl.glEnableVertexAttribArray(positionLoc);
+
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");
+		gl.glUniform4f(colorUniformLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+
+		gl.glDrawArrays(GL_TRIANGLES, 0, GRID_SIZE*GRID_SIZE*6);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw");
+	}
+
+	gl.glFinish();
+	gl.glBindVertexArray(0);
+	gl.glUseProgram(0);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "clean");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+}
+
+SingleBindingCase::TestSpec SingleBindingCase::genTestSpec (int flags)
+{
+	const int	datumSize				= 4;
+	const int	bufferOffset			= (flags & FLAG_BUF_ALIGNED_OFFSET) ? (32) : (flags & FLAG_BUF_UNALIGNED_OFFSET) ? (19) : (0);
+	const int	attrBufAlignment		= ((bufferOffset % datumSize) == 0) ? (0) : (datumSize - (bufferOffset % datumSize));
+	const int	positionAttrOffset		= (flags & FLAG_ATTRIB_UNALIGNED) ? (3) : (flags & FLAG_ATTRIB_ALIGNED) ? (attrBufAlignment) : (0);
+	const bool	hasColorAttr			= (flags & FLAG_ATTRIBS_SHARED_ELEMS) || (flags & FLAG_ATTRIBS_MULTIPLE_ELEMS);
+	const int	colorAttrOffset			= (flags & FLAG_ATTRIBS_SHARED_ELEMS) ? (2 * datumSize) : (flags & FLAG_ATTRIBS_MULTIPLE_ELEMS) ? (4 * datumSize) : (-1);
+
+	const int	bufferStrideBase		= de::max(positionAttrOffset + 4 * datumSize, colorAttrOffset + 4 * datumSize);
+	const int	bufferStrideAlignment	= ((bufferStrideBase % datumSize) == 0) ? (0) : (datumSize - (bufferStrideBase % datumSize));
+	const int	bufferStridePadding		= ((flags & FLAG_BUF_UNALIGNED_STRIDE) && deIsAligned32(bufferStrideBase, datumSize)) ? (13) : (!(flags & FLAG_BUF_UNALIGNED_STRIDE) && !deIsAligned32(bufferStrideBase, datumSize)) ? (bufferStrideAlignment) : (0);
+
+	TestSpec spec;
+
+	spec.bufferOffset			= bufferOffset;
+	spec.bufferStride			= bufferStrideBase + bufferStridePadding;
+	spec.positionAttrOffset		= positionAttrOffset;
+	spec.colorAttrOffset		= colorAttrOffset;
+	spec.hasColorAttr			= hasColorAttr;
+
+	if (flags & FLAG_ATTRIB_UNALIGNED)
+		DE_ASSERT(!deIsAligned32(spec.bufferOffset + spec.positionAttrOffset, datumSize));
+	else if (flags & FLAG_ATTRIB_ALIGNED)
+		DE_ASSERT(deIsAligned32(spec.bufferOffset + spec.positionAttrOffset, datumSize));
+
+	if (flags & FLAG_BUF_UNALIGNED_STRIDE)
+		DE_ASSERT(!deIsAligned32(spec.bufferStride, datumSize));
+	else
+		DE_ASSERT(deIsAligned32(spec.bufferStride, datumSize));
+
+	return spec;
+}
+
+std::string SingleBindingCase::genTestDescription (int flags)
+{
+	std::ostringstream buf;
+	buf << "draw test pattern";
+
+	if (flags & FLAG_ATTRIB_UNALIGNED)
+		buf << ", attribute offset (unaligned)";
+	if (flags & FLAG_ATTRIB_ALIGNED)
+		buf << ", attribute offset (aligned)";
+
+	if (flags & FLAG_ATTRIBS_MULTIPLE_ELEMS)
+		buf << ", 2 attributes";
+	if (flags & FLAG_ATTRIBS_SHARED_ELEMS)
+		buf << ", 2 attributes (some components shared)";
+
+	if (flags & FLAG_BUF_ALIGNED_OFFSET)
+		buf << ", buffer offset aligned";
+	if (flags & FLAG_BUF_UNALIGNED_OFFSET)
+		buf << ", buffer offset unaligned";
+	if (flags & FLAG_BUF_UNALIGNED_STRIDE)
+		buf << ", buffer stride unaligned";
+
+	return buf.str();
+}
+
+bool SingleBindingCase::isDataUnaligned (int flags)
+{
+	if (flags & FLAG_ATTRIB_UNALIGNED)
+		return true;
+	if (flags & FLAG_ATTRIB_ALIGNED)
+		return false;
+
+	return (flags & FLAG_BUF_UNALIGNED_OFFSET) || (flags & FLAG_BUF_UNALIGNED_STRIDE);
+}
+
+void SingleBindingCase::createBuffers (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	std::vector<deUint8>	dataBuf	(m_spec.bufferOffset + m_spec.bufferStride * GRID_SIZE * GRID_SIZE * 6);
+
+	// In interleaved mode color rg and position zw are the same. Select "good" values for r and g
+	const tcu::Vec4			colorA	(0.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4			colorB	(0.5f, 1.0f, 0.0f, 1.0f);
+
+	for (int y = 0; y < GRID_SIZE; ++y)
+	for (int x = 0; x < GRID_SIZE; ++x)
+	{
+		const tcu::Vec4&	color = ((x + y) % 2 == 0) ? (colorA) : (colorB);
+		const tcu::Vec4		positions[6] =
+		{
+			tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+			tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+			tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+			tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+			tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+			tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+		};
+
+		// copy cell vertices to the buffer.
+		for (int v = 0; v < 6; ++v)
+			memcpy(&dataBuf[m_spec.bufferOffset + m_spec.positionAttrOffset + m_spec.bufferStride * ((y * GRID_SIZE + x) * 6 + v)], positions[v].getPtr(), sizeof(positions[v]));
+
+		// copy color to buffer
+		if (m_spec.hasColorAttr)
+			for (int v = 0; v < 6; ++v)
+				memcpy(&dataBuf[m_spec.bufferOffset + m_spec.colorAttrOffset + m_spec.bufferStride * ((y * GRID_SIZE + x) * 6 + v)], color.getPtr(), sizeof(color));
+	}
+
+	gl.genBuffers(1, &m_buf);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_buf);
+	gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)dataBuf.size(), &dataBuf[0], GL_STATIC_DRAW);
+	gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+
+	if (gl.getError() != GL_NO_ERROR)
+		throw tcu::TestError("could not init buffer");
+}
+
+void SingleBindingCase::createShader (void)
+{
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource()) << glu::FragmentSource(s_colorFragmentShader));
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build shader");
+}
+
+std::string SingleBindingCase::genVertexSource (void)
+{
+	const bool			useUniformColor = !m_spec.hasColorAttr;
+	std::ostringstream	buf;
+
+	buf <<	"#version 310 es\n"
+			"in highp vec4 a_position;\n";
+
+	if (!useUniformColor)
+		buf << "in highp vec4 a_color;\n";
+	else
+		buf << "uniform highp vec4 u_color;\n";
+
+	buf <<	"out highp vec4 v_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_color = " << ((useUniformColor) ? ("u_color") : ("a_color")) << ";\n"
+			"}\n";
+
+	return buf.str();
+}
+
+class MultipleBindingCase : public BindingRenderCase
+{
+public:
+
+	enum CaseFlag
+	{
+		FLAG_ZERO_STRIDE		= (1<<0),	// !< set a buffer stride to zero
+		FLAG_INSTANCED			= (1<<1),	// !< set a buffer instance divisor to non-zero
+		FLAG_ALIASING_BUFFERS	= (1<<2),	// !< bind buffer to multiple binding points
+	};
+
+						MultipleBindingCase		(Context& ctx, const char* name, int flags);
+						~MultipleBindingCase	(void);
+
+	void				init					(void);
+	void				deinit					(void);
+
+private:
+	struct TestSpec
+	{
+		bool zeroStride;
+		bool instanced;
+		bool aliasingBuffers;
+	};
+
+	enum
+	{
+		GRID_SIZE = 20
+	};
+
+	void				renderTo				(tcu::Surface& dst);
+
+	TestSpec			genTestSpec				(int flags) const;
+	std::string			genTestDescription		(int flags) const;
+	void				createBuffers			(void);
+	void				createShader			(void);
+
+	const TestSpec		m_spec;
+	glw::GLuint			m_primitiveBuf;
+	glw::GLuint			m_colorOffsetBuf;
+};
+
+MultipleBindingCase::MultipleBindingCase (Context& ctx, const char* name, int flags)
+	: BindingRenderCase	(ctx, name, genTestDescription(flags).c_str(), false)
+	, m_spec			(genTestSpec(flags))
+	, m_primitiveBuf	(0)
+	, m_colorOffsetBuf	(0)
+{
+	DE_ASSERT(!(m_spec.instanced && m_spec.zeroStride));
+}
+
+MultipleBindingCase::~MultipleBindingCase (void)
+{
+	deinit();
+}
+
+void MultipleBindingCase::init (void)
+{
+	BindingRenderCase::init();
+
+	// log what we are trying to do
+
+	m_testCtx.getLog()	<< tcu::TestLog::Message
+						<< "Rendering " << (int)GRID_SIZE << "x" << (int)GRID_SIZE << " grid.\n"
+						<< "Vertex positions:\n"
+						<< "	binding point: 1\n"
+						<< "Vertex offsets:\n"
+						<< "	binding point: 2\n"
+						<< "Vertex colors:\n"
+						<< "	binding point: 2\n"
+						<< "Binding point 1:\n"
+						<< "	buffer object: " << m_primitiveBuf << "\n"
+						<< "Binding point 2:\n"
+						<< "	buffer object: " << ((m_spec.aliasingBuffers) ? (m_primitiveBuf) : (m_colorOffsetBuf)) << "\n"
+						<< "	instance divisor: " << ((m_spec.instanced) ? (1) : (0)) << "\n"
+						<< "	stride: " << ((m_spec.zeroStride) ? (0) : (4*4*2)) << "\n"
+						<< tcu::TestLog::EndMessage;
+}
+
+void MultipleBindingCase::deinit (void)
+{
+	if (m_primitiveBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_primitiveBuf);
+		m_primitiveBuf = DE_NULL;
+	}
+
+	if (m_colorOffsetBuf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_colorOffsetBuf);
+		m_colorOffsetBuf = DE_NULL;
+	}
+
+	BindingRenderCase::deinit();
+}
+
+void MultipleBindingCase::renderTo (tcu::Surface& dst)
+{
+	glu::CallLogWrapper gl					(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	const int			positionLoc			= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
+	const int			colorLoc			= gl.glGetAttribLocation(m_program->getProgram(), "a_color");
+	const int			offsetLoc			= gl.glGetAttribLocation(m_program->getProgram(), "a_offset");
+
+	const int			positionBinding		= 1;
+	const int			colorOffsetBinding	= 2;
+
+	gl.enableLogging(true);
+
+	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	gl.glClear(GL_COLOR_BUFFER_BIT);
+	gl.glViewport(0, 0, dst.getWidth(), dst.getHeight());
+	gl.glBindVertexArray(m_vao);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set vao");
+
+	gl.glUseProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");
+
+	// Setup format & binding
+
+	gl.glEnableVertexAttribArray(positionLoc);
+	gl.glEnableVertexAttribArray(colorLoc);
+	gl.glEnableVertexAttribArray(offsetLoc);
+
+	gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, 0);
+	gl.glVertexAttribFormat(colorLoc, 4, GL_FLOAT, GL_FALSE, 0);
+	gl.glVertexAttribFormat(offsetLoc, 4, GL_FLOAT, GL_FALSE, sizeof(tcu::Vec4));
+
+	gl.glVertexAttribBinding(positionLoc, positionBinding);
+	gl.glVertexAttribBinding(colorLoc, colorOffsetBinding);
+	gl.glVertexAttribBinding(offsetLoc, colorOffsetBinding);
+
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup attribs");
+
+	// setup binding points
+
+	gl.glVertexBindingDivisor(positionBinding, 0);
+	gl.glBindVertexBuffer(positionBinding, m_primitiveBuf, 0, sizeof(tcu::Vec4));
+
+	{
+		const int			stride	= (m_spec.zeroStride) ? (0) : (2 * (int)sizeof(tcu::Vec4));
+		const int			offset	= (!m_spec.aliasingBuffers) ? (0) : (m_spec.instanced) ? (6 * (int)sizeof(tcu::Vec4)) : (6 * GRID_SIZE * GRID_SIZE * (int)sizeof(tcu::Vec4));
+		const glw::GLuint	buffer	= (m_spec.aliasingBuffers) ? (m_primitiveBuf) : (m_colorOffsetBuf);
+		const int			divisor	= (m_spec.instanced) ? (1) : (0);
+
+		gl.glVertexBindingDivisor(colorOffsetBinding, divisor);
+		gl.glBindVertexBuffer(colorOffsetBinding, buffer, offset, (glw::GLsizei)stride);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set binding points");
+
+	if (m_spec.instanced)
+		gl.glDrawArraysInstanced(GL_TRIANGLES, 0, 6, GRID_SIZE*GRID_SIZE);
+	else
+		gl.glDrawArrays(GL_TRIANGLES, 0, GRID_SIZE*GRID_SIZE*6);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw");
+
+	gl.glFinish();
+	gl.glBindVertexArray(0);
+	gl.glUseProgram(0);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "clean");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+}
+
+MultipleBindingCase::TestSpec MultipleBindingCase::genTestSpec (int flags) const
+{
+	MultipleBindingCase::TestSpec spec;
+
+	spec.zeroStride			= !!(flags & FLAG_ZERO_STRIDE);
+	spec.instanced			= !!(flags & FLAG_INSTANCED);
+	spec.aliasingBuffers	= !!(flags & FLAG_ALIASING_BUFFERS);
+
+	return spec;
+}
+
+std::string MultipleBindingCase::genTestDescription (int flags) const
+{
+	std::ostringstream buf;
+	buf << "draw test pattern";
+
+	if (flags & FLAG_ZERO_STRIDE)
+		buf << ", zero stride";
+	if (flags & FLAG_INSTANCED)
+		buf << ", instanced binding point";
+	if (flags & FLAG_ALIASING_BUFFERS)
+		buf << ", binding points share buffer object";
+
+	return buf.str();
+}
+
+void MultipleBindingCase::createBuffers (void)
+{
+	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
+	const tcu::Vec4			green				= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4			yellow				= tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
+
+	const int				vertexDataSize		= (m_spec.instanced) ? (6) : (6 * GRID_SIZE * GRID_SIZE);
+	const int				offsetColorSize		= (m_spec.zeroStride) ? (2) : (m_spec.instanced) ? (2 * GRID_SIZE * GRID_SIZE) : (2 * 6 * GRID_SIZE * GRID_SIZE);
+	const int				primitiveBufSize	= (m_spec.aliasingBuffers) ? (vertexDataSize + offsetColorSize) : (vertexDataSize);
+	const int				colorOffsetBufSize	= (m_spec.aliasingBuffers) ? (0) : (offsetColorSize);
+
+	std::vector<tcu::Vec4>	primitiveData		(primitiveBufSize);
+	std::vector<tcu::Vec4>	colorOffsetData		(colorOffsetBufSize);
+	tcu::Vec4*				colorOffsetWritePtr = DE_NULL;
+
+	if (m_spec.aliasingBuffers)
+	{
+		if (m_spec.instanced)
+			colorOffsetWritePtr = &primitiveData[6];
+		else
+			colorOffsetWritePtr = &primitiveData[GRID_SIZE*GRID_SIZE*6];
+	}
+	else
+		colorOffsetWritePtr = &colorOffsetData[0];
+
+	// write vertex position
+
+	if (m_spec.instanced)
+	{
+		// store single basic primitive
+		primitiveData[0] = tcu::Vec4(0.0f,				0.0f,				0.0f, 1.0f);
+		primitiveData[1] = tcu::Vec4(0.0f,				2.0f / GRID_SIZE,	0.0f, 1.0f);
+		primitiveData[2] = tcu::Vec4(2.0f / GRID_SIZE,	2.0f / GRID_SIZE,	0.0f, 1.0f);
+		primitiveData[3] = tcu::Vec4(0.0f,				0.0f,				0.0f, 1.0f);
+		primitiveData[4] = tcu::Vec4(2.0f / GRID_SIZE,	2.0f / GRID_SIZE,	0.0f, 1.0f);
+		primitiveData[5] = tcu::Vec4(2.0f / GRID_SIZE,	0.0f,				0.0f, 1.0f);
+	}
+	else
+	{
+		// store whole grid
+		for (int y = 0; y < GRID_SIZE; ++y)
+		for (int x = 0; x < GRID_SIZE; ++x)
+		{
+			primitiveData[(y * GRID_SIZE + x) * 6 + 0] = tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+			primitiveData[(y * GRID_SIZE + x) * 6 + 1] = tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+			primitiveData[(y * GRID_SIZE + x) * 6 + 2] = tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+			primitiveData[(y * GRID_SIZE + x) * 6 + 3] = tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+			primitiveData[(y * GRID_SIZE + x) * 6 + 4] = tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+			primitiveData[(y * GRID_SIZE + x) * 6 + 5] = tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+		}
+	}
+
+	// store color&offset
+
+	if (m_spec.zeroStride)
+	{
+		colorOffsetWritePtr[0] = green;
+		colorOffsetWritePtr[1] = tcu::Vec4(0.0f);
+	}
+	else if (m_spec.instanced)
+	{
+		for (int y = 0; y < GRID_SIZE; ++y)
+		for (int x = 0; x < GRID_SIZE; ++x)
+		{
+			const tcu::Vec4& color = ((x + y) % 2 == 0) ? (green) : (yellow);
+
+			colorOffsetWritePtr[(y * GRID_SIZE + x) * 2 + 0] = color;
+			colorOffsetWritePtr[(y * GRID_SIZE + x) * 2 + 1] = tcu::Vec4(x / float(GRID_SIZE) * 2.0f - 1.0f, y / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 0.0f);
+		}
+	}
+	else
+	{
+		for (int y = 0; y < GRID_SIZE; ++y)
+		for (int x = 0; x < GRID_SIZE; ++x)
+		for (int v = 0; v < 6; ++v)
+		{
+			const tcu::Vec4& color = ((x + y) % 2 == 0) ? (green) : (yellow);
+
+			colorOffsetWritePtr[((y * GRID_SIZE + x) * 6 + v) * 2 + 0] = color;
+			colorOffsetWritePtr[((y * GRID_SIZE + x) * 6 + v) * 2 + 1] = tcu::Vec4(0.0f);
+		}
+	}
+
+	// upload vertex data
+
+	gl.genBuffers(1, &m_primitiveBuf);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_primitiveBuf);
+	gl.bufferData(GL_ARRAY_BUFFER, (int)(primitiveData.size() * sizeof(tcu::Vec4)), primitiveData[0].getPtr(), GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "upload data");
+
+	if (!m_spec.aliasingBuffers)
+	{
+		// upload color & offset data
+
+		gl.genBuffers(1, &m_colorOffsetBuf);
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_colorOffsetBuf);
+		gl.bufferData(GL_ARRAY_BUFFER, (int)(colorOffsetData.size() * sizeof(tcu::Vec4)), colorOffsetData[0].getPtr(), GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "upload colordata");
+	}
+}
+
+void MultipleBindingCase::createShader (void)
+{
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_positionColorOffsetShader) << glu::FragmentSource(s_colorFragmentShader));
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build shader");
+}
+
+class MixedBindingCase : public BindingRenderCase
+{
+public:
+
+	enum CaseType
+	{
+		CASE_BASIC = 0,
+		CASE_INSTANCED_BINDING,
+		CASE_INSTANCED_ATTRIB,
+
+		CASE_LAST
+	};
+
+						MixedBindingCase		(Context& ctx, const char* name, const char* desc, CaseType caseType);
+						~MixedBindingCase		(void);
+
+	void				init					(void);
+	void				deinit					(void);
+
+private:
+	enum
+	{
+		GRID_SIZE = 20
+	};
+
+	void				renderTo				(tcu::Surface& dst);
+	void				createBuffers			(void);
+	void				createShader			(void);
+
+	const CaseType		m_case;
+	glw::GLuint			m_posBuffer;
+	glw::GLuint			m_colorOffsetBuffer;
+};
+
+MixedBindingCase::MixedBindingCase (Context& ctx, const char* name, const char* desc, CaseType caseType)
+	: BindingRenderCase		(ctx, name, desc, false)
+	, m_case				(caseType)
+	, m_posBuffer			(0)
+	, m_colorOffsetBuffer	(0)
+{
+	DE_ASSERT(caseType < CASE_LAST);
+}
+
+MixedBindingCase::~MixedBindingCase (void)
+{
+	deinit();
+}
+
+void MixedBindingCase::init (void)
+{
+	BindingRenderCase::init();
+}
+
+void MixedBindingCase::deinit (void)
+{
+	if (m_posBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_posBuffer);
+		m_posBuffer = DE_NULL;
+	}
+
+	if (m_colorOffsetBuffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_colorOffsetBuffer);
+		m_colorOffsetBuffer = DE_NULL;
+	}
+
+	BindingRenderCase::deinit();
+}
+
+void MixedBindingCase::renderTo (tcu::Surface& dst)
+{
+	glu::CallLogWrapper gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	const int			positionLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
+	const int			colorLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_color");
+	const int			offsetLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_offset");
+
+	gl.enableLogging(true);
+
+	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	gl.glClear(GL_COLOR_BUFFER_BIT);
+	gl.glViewport(0, 0, dst.getWidth(), dst.getHeight());
+	gl.glBindVertexArray(m_vao);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set vao");
+
+	gl.glUseProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");
+
+	switch (m_case)
+	{
+		case CASE_BASIC:
+		{
+			// bind position using vertex_attrib_binding api
+
+			gl.glBindVertexBuffer(positionLoc, m_posBuffer, 0, (glw::GLsizei)sizeof(tcu::Vec4));
+			gl.glVertexAttribBinding(positionLoc, positionLoc);
+			gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, 0);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set binding");
+
+			// bind color using old api
+
+			gl.glBindBuffer(GL_ARRAY_BUFFER, m_colorOffsetBuffer);
+			gl.glVertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, glw::GLsizei(2 * sizeof(tcu::Vec4)), DE_NULL);
+			gl.glVertexAttribPointer(offsetLoc, 4, GL_FLOAT, GL_FALSE, glw::GLsizei(2 * sizeof(tcu::Vec4)), (deUint8*)DE_NULL + sizeof(tcu::Vec4));
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");
+
+			// draw
+			gl.glEnableVertexAttribArray(positionLoc);
+			gl.glEnableVertexAttribArray(colorLoc);
+			gl.glEnableVertexAttribArray(offsetLoc);
+			gl.glDrawArrays(GL_TRIANGLES, 0, 6*GRID_SIZE*GRID_SIZE);
+			break;
+		}
+
+		case CASE_INSTANCED_BINDING:
+		{
+			// bind position using old api
+			gl.glBindBuffer(GL_ARRAY_BUFFER, m_posBuffer);
+			gl.glVertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");
+
+			// bind color using vertex_attrib_binding api
+			gl.glBindVertexBuffer(colorLoc, m_colorOffsetBuffer, 0, (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
+			gl.glVertexBindingDivisor(colorLoc, 1);
+
+			gl.glVertexAttribBinding(colorLoc, colorLoc);
+			gl.glVertexAttribBinding(offsetLoc, colorLoc);
+
+			gl.glVertexAttribFormat(colorLoc, 4, GL_FLOAT, GL_FALSE, 0);
+			gl.glVertexAttribFormat(offsetLoc, 4, GL_FLOAT, GL_FALSE, sizeof(tcu::Vec4));
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set binding");
+
+			// draw
+			gl.glEnableVertexAttribArray(positionLoc);
+			gl.glEnableVertexAttribArray(colorLoc);
+			gl.glEnableVertexAttribArray(offsetLoc);
+			gl.glDrawArraysInstanced(GL_TRIANGLES, 0, 6, GRID_SIZE*GRID_SIZE);
+			break;
+		}
+
+		case CASE_INSTANCED_ATTRIB:
+		{
+			// bind position using vertex_attrib_binding api
+			gl.glBindVertexBuffer(positionLoc, m_posBuffer, 0, (glw::GLsizei)sizeof(tcu::Vec4));
+			gl.glVertexAttribBinding(positionLoc, positionLoc);
+			gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, 0);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set binding");
+
+			// bind color using old api
+			gl.glBindBuffer(GL_ARRAY_BUFFER, m_colorOffsetBuffer);
+			gl.glVertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, glw::GLsizei(2 * sizeof(tcu::Vec4)), DE_NULL);
+			gl.glVertexAttribPointer(offsetLoc, 4, GL_FLOAT, GL_FALSE, glw::GLsizei(2 * sizeof(tcu::Vec4)), (deUint8*)DE_NULL + sizeof(tcu::Vec4));
+			gl.glVertexAttribDivisor(colorLoc, 1);
+			gl.glVertexAttribDivisor(offsetLoc, 1);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");
+
+			// draw
+			gl.glEnableVertexAttribArray(positionLoc);
+			gl.glEnableVertexAttribArray(colorLoc);
+			gl.glEnableVertexAttribArray(offsetLoc);
+			gl.glDrawArraysInstanced(GL_TRIANGLES, 0, 6, GRID_SIZE*GRID_SIZE);
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	gl.glFinish();
+	gl.glBindVertexArray(0);
+	gl.glUseProgram(0);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "clean");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+}
+
+void MixedBindingCase::createBuffers (void)
+{
+	const glw::Functions&	gl								= m_context.getRenderContext().getFunctions();
+	const tcu::Vec4			green							= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4			yellow							= tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
+
+	// draw grid. In instanced mode, each cell is an instance
+	const bool				instanced						= (m_case == CASE_INSTANCED_BINDING) || (m_case == CASE_INSTANCED_ATTRIB);
+	const int				numCells						= GRID_SIZE*GRID_SIZE;
+	const int				numPositionCells				= (instanced) ? (1) : (numCells);
+	const int				numPositionElements				= 6 * numPositionCells;
+	const int				numInstanceElementsPerCell		= (instanced) ? (1) : (6);
+	const int				numColorOffsetElements			= numInstanceElementsPerCell * numCells;
+
+	std::vector<tcu::Vec4>	positionData					(numPositionElements);
+	std::vector<tcu::Vec4>	colorOffsetData					(2 * numColorOffsetElements);
+
+	// positions
+
+	for (int primNdx = 0; primNdx < numPositionCells; ++primNdx)
+	{
+		positionData[primNdx*6 + 0] =  tcu::Vec4(0.0f,				0.0f,				0.0f, 1.0f);
+		positionData[primNdx*6 + 1] =  tcu::Vec4(0.0f,				2.0f / GRID_SIZE,	0.0f, 1.0f);
+		positionData[primNdx*6 + 2] =  tcu::Vec4(2.0f / GRID_SIZE,	2.0f / GRID_SIZE,	0.0f, 1.0f);
+		positionData[primNdx*6 + 3] =  tcu::Vec4(0.0f,				0.0f,				0.0f, 1.0f);
+		positionData[primNdx*6 + 4] =  tcu::Vec4(2.0f / GRID_SIZE,	2.0f / GRID_SIZE,	0.0f, 1.0f);
+		positionData[primNdx*6 + 5] =  tcu::Vec4(2.0f / GRID_SIZE,	0.0f,				0.0f, 1.0f);
+	}
+
+	// color & offset
+
+	for (int y = 0; y < GRID_SIZE; ++y)
+	for (int x = 0; x < GRID_SIZE; ++x)
+	{
+		for (int v = 0; v < numInstanceElementsPerCell; ++v)
+		{
+			const tcu::Vec4& color = ((x + y) % 2 == 0) ? (green) : (yellow);
+
+			colorOffsetData[((y * GRID_SIZE + x) * numInstanceElementsPerCell + v) * 2 + 0] = color;
+			colorOffsetData[((y * GRID_SIZE + x) * numInstanceElementsPerCell + v) * 2 + 1] = tcu::Vec4(x / float(GRID_SIZE) * 2.0f - 1.0f, y / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 0.0f);
+		}
+	}
+
+	// upload vertex data
+
+	gl.genBuffers(1, &m_posBuffer);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_posBuffer);
+	gl.bufferData(GL_ARRAY_BUFFER, (int)(positionData.size() * sizeof(tcu::Vec4)), positionData[0].getPtr(), GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "upload position data");
+
+	gl.genBuffers(1, &m_colorOffsetBuffer);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_colorOffsetBuffer);
+	gl.bufferData(GL_ARRAY_BUFFER, (int)(colorOffsetData.size() * sizeof(tcu::Vec4)), colorOffsetData[0].getPtr(), GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "upload position data");
+}
+
+void MixedBindingCase::createShader (void)
+{
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_positionColorOffsetShader) << glu::FragmentSource(s_colorFragmentShader));
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build shader");
+}
+
+class MixedApiCase : public BindingRenderCase
+{
+public:
+
+	enum CaseType
+	{
+		CASE_CHANGE_BUFFER = 0,
+		CASE_CHANGE_BUFFER_OFFSET,
+		CASE_CHANGE_BUFFER_STRIDE,
+		CASE_CHANGE_BINDING_POINT,
+
+		CASE_LAST
+	};
+
+						MixedApiCase			(Context& ctx, const char* name, const char* desc, CaseType caseType);
+						~MixedApiCase			(void);
+
+	void				init					(void);
+	void				deinit					(void);
+
+private:
+	enum
+	{
+		GRID_SIZE = 20
+	};
+
+	void				renderTo				(tcu::Surface& dst);
+	void				createBuffers			(void);
+	void				createShader			(void);
+
+	const CaseType		m_case;
+	glw::GLuint			m_buffer;
+};
+
+
+MixedApiCase::MixedApiCase (Context& ctx, const char* name, const char* desc, CaseType caseType)
+	: BindingRenderCase		(ctx, name, desc, false)
+	, m_case				(caseType)
+	, m_buffer				(0)
+{
+	DE_ASSERT(caseType < CASE_LAST);
+}
+
+MixedApiCase::~MixedApiCase (void)
+{
+	deinit();
+}
+
+void MixedApiCase::init (void)
+{
+	BindingRenderCase::init();
+}
+
+void MixedApiCase::deinit (void)
+{
+	if (m_buffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buffer);
+		m_buffer = DE_NULL;
+	}
+
+	BindingRenderCase::deinit();
+}
+
+void MixedApiCase::renderTo (tcu::Surface& dst)
+{
+	glu::CallLogWrapper		gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	const int				positionLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
+	const int				colorLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_color");
+	glu::Buffer				dummyBuffer		(m_context.getRenderContext());
+
+	gl.enableLogging(true);
+
+	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	gl.glClear(GL_COLOR_BUFFER_BIT);
+	gl.glViewport(0, 0, dst.getWidth(), dst.getHeight());
+	gl.glBindVertexArray(m_vao);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set vao");
+
+	gl.glUseProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");
+
+	switch (m_case)
+	{
+		case CASE_CHANGE_BUFFER:
+		{
+			// bind data using old api
+
+			gl.glBindBuffer(GL_ARRAY_BUFFER, *dummyBuffer);
+			gl.glVertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), (const deUint8*)DE_NULL);
+			gl.glVertexAttribPointer(colorLoc,    4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), (const deUint8*)DE_NULL + sizeof(tcu::Vec4));
+
+			// change buffer with vertex_attrib_binding
+
+			gl.glBindVertexBuffer(positionLoc, m_buffer, 0,                 (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
+			gl.glBindVertexBuffer(colorLoc,    m_buffer, sizeof(tcu::Vec4), (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
+			break;
+		}
+
+		case CASE_CHANGE_BUFFER_OFFSET:
+		{
+			// bind data using old api
+
+			gl.glBindBuffer(GL_ARRAY_BUFFER, m_buffer);
+			gl.glVertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), (const deUint8*)DE_NULL);
+			gl.glVertexAttribPointer(colorLoc,    4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), (const deUint8*)DE_NULL);
+
+			// change buffer offset with vertex_attrib_binding
+
+			gl.glBindVertexBuffer(positionLoc, m_buffer, 0,                 (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
+			gl.glBindVertexBuffer(colorLoc,    m_buffer, sizeof(tcu::Vec4), (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
+			break;
+		}
+
+		case CASE_CHANGE_BUFFER_STRIDE:
+		{
+			// bind data using old api
+
+			gl.glBindBuffer(GL_ARRAY_BUFFER, m_buffer);
+			gl.glVertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 8, (const deUint8*)DE_NULL);
+			gl.glVertexAttribPointer(colorLoc,    4, GL_FLOAT, GL_FALSE, 4, (const deUint8*)DE_NULL);
+
+			// change buffer stride with vertex_attrib_binding
+
+			gl.glBindVertexBuffer(positionLoc, m_buffer, 0,                 (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
+			gl.glBindVertexBuffer(colorLoc,    m_buffer, sizeof(tcu::Vec4), (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
+			break;
+		}
+
+		case CASE_CHANGE_BINDING_POINT:
+		{
+			const int maxUsedLocation	= de::max(positionLoc, colorLoc);
+			const int bindingPoint1		= maxUsedLocation + 1;
+			const int bindingPoint2		= maxUsedLocation + 2;
+
+			// bind data using old api
+
+			gl.glBindBuffer(GL_ARRAY_BUFFER, m_buffer);
+			gl.glVertexAttribPointer(bindingPoint1, 4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), (const deUint8*)DE_NULL);
+			gl.glVertexAttribPointer(bindingPoint2, 4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), (const deUint8*)DE_NULL + sizeof(tcu::Vec4));
+
+			// change buffer binding point with vertex_attrib_binding
+
+			gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, 0);
+			gl.glVertexAttribFormat(colorLoc, 4, GL_FLOAT, GL_FALSE, 0);
+
+			gl.glVertexAttribBinding(positionLoc, bindingPoint1);
+			gl.glVertexAttribBinding(colorLoc, bindingPoint2);
+
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	// draw
+	gl.glEnableVertexAttribArray(positionLoc);
+	gl.glEnableVertexAttribArray(colorLoc);
+	gl.glDrawArrays(GL_TRIANGLES, 0, 6*GRID_SIZE*GRID_SIZE);
+
+	gl.glFinish();
+	gl.glBindVertexArray(0);
+	gl.glUseProgram(0);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "clean");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+}
+
+void MixedApiCase::createBuffers (void)
+{
+	const tcu::Vec4			green							= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4			yellow							= tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
+
+	const glw::Functions&	gl								= m_context.getRenderContext().getFunctions();
+	std::vector<tcu::Vec4>	vertexData						(12 * GRID_SIZE * GRID_SIZE);
+
+	for (int y = 0; y < GRID_SIZE; ++y)
+	for (int x = 0; x < GRID_SIZE; ++x)
+	{
+		const tcu::Vec4& color = ((x + y) % 2 == 0) ? (green) : (yellow);
+
+		vertexData[(y * GRID_SIZE + x) * 12 +  0] = tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+		vertexData[(y * GRID_SIZE + x) * 12 +  1] = color;
+		vertexData[(y * GRID_SIZE + x) * 12 +  2] = tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+		vertexData[(y * GRID_SIZE + x) * 12 +  3] = color;
+		vertexData[(y * GRID_SIZE + x) * 12 +  4] = tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+		vertexData[(y * GRID_SIZE + x) * 12 +  5] = color;
+		vertexData[(y * GRID_SIZE + x) * 12 +  6] = tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+		vertexData[(y * GRID_SIZE + x) * 12 +  7] = color;
+		vertexData[(y * GRID_SIZE + x) * 12 +  8] = tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+		vertexData[(y * GRID_SIZE + x) * 12 +  9] = color;
+		vertexData[(y * GRID_SIZE + x) * 12 + 10] = tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
+		vertexData[(y * GRID_SIZE + x) * 12 + 11] = color;
+	}
+
+	// upload vertex data
+
+	gl.genBuffers(1, &m_buffer);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);
+	gl.bufferData(GL_ARRAY_BUFFER, (int)(vertexData.size() * sizeof(tcu::Vec4)), vertexData[0].getPtr(), GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "upload data");
+}
+
+void MixedApiCase::createShader (void)
+{
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_positionColorShader) << glu::FragmentSource(s_colorFragmentShader));
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build shader");
+}
+
+class DefaultVAOCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASE_BIND_VERTEX_BUFFER,
+		CASE_VERTEX_ATTRIB_FORMAT,
+		CASE_VERTEX_ATTRIB_I_FORMAT,
+		CASE_VERTEX_ATTRIB_BINDING,
+		CASE_VERTEX_BINDING_DIVISOR,
+
+		CASE_LAST
+	};
+
+					DefaultVAOCase		(Context& ctx, const char* name, const char* desc, CaseType caseType);
+					~DefaultVAOCase		(void);
+
+	IterateResult	iterate				(void);
+
+private:
+	const CaseType	m_caseType;
+};
+
+DefaultVAOCase::DefaultVAOCase (Context& ctx, const char* name, const char* desc, CaseType caseType)
+	: TestCase		(ctx, name, desc)
+	, m_caseType	(caseType)
+{
+	DE_ASSERT(caseType < CASE_LAST);
+}
+
+DefaultVAOCase::~DefaultVAOCase (void)
+{
+}
+
+DefaultVAOCase::IterateResult DefaultVAOCase::iterate (void)
+{
+	glw::GLenum			error	= 0;
+	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+
+	gl.enableLogging(true);
+
+	switch (m_caseType)
+	{
+		case CASE_BIND_VERTEX_BUFFER:
+		{
+			glu::Buffer buffer(m_context.getRenderContext());
+			gl.glBindVertexBuffer(0, *buffer, 0, 0);
+			break;
+		}
+
+		case CASE_VERTEX_ATTRIB_FORMAT:
+			gl.glVertexAttribFormat(0, 4, GL_FLOAT, GL_FALSE, 0);
+			break;
+
+		case CASE_VERTEX_ATTRIB_I_FORMAT:
+			gl.glVertexAttribIFormat(0, 4, GL_INT, 0);
+			break;
+
+		case CASE_VERTEX_ATTRIB_BINDING:
+			gl.glVertexAttribBinding(0, 0);
+			break;
+
+		case CASE_VERTEX_BINDING_DIVISOR:
+			gl.glVertexBindingDivisor(0, 1);
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	error = gl.glGetError();
+
+	if (error != GL_INVALID_OPERATION)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_INVALID_OPERATION, got " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid error");
+	}
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	return STOP;
+}
+
+class BindToCreateCase : public TestCase
+{
+public:
+					BindToCreateCase	(Context& ctx, const char* name, const char* desc);
+					~BindToCreateCase	(void);
+
+	IterateResult	iterate				(void);
+};
+
+BindToCreateCase::BindToCreateCase (Context& ctx, const char* name, const char* desc)
+	: TestCase(ctx, name, desc)
+{
+}
+
+BindToCreateCase::~BindToCreateCase (void)
+{
+}
+
+BindToCreateCase::IterateResult BindToCreateCase::iterate (void)
+{
+	glw::GLuint			buffer	= 0;
+	glw::GLenum			error;
+	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	glu::VertexArray	vao		(m_context.getRenderContext());
+
+	gl.enableLogging(true);
+
+	gl.glGenBuffers(1, &buffer);
+	gl.glDeleteBuffers(1, &buffer);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
+
+	gl.glBindVertexArray(*vao);
+	gl.glBindVertexBuffer(0, buffer, 0, 0);
+
+	error = gl.glGetError();
+
+	if (error != GL_INVALID_OPERATION)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_INVALID_OPERATION, got " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid error");
+	}
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	return STOP;
+}
+
+class NegativeApiCase : public TestCase
+{
+public:
+	enum CaseType
+	{
+		CASE_LARGE_OFFSET,
+		CASE_LARGE_STRIDE,
+		CASE_NEGATIVE_STRIDE,
+		CASE_NEGATIVE_OFFSET,
+		CASE_INVALID_ATTR,
+		CASE_INVALID_BINDING,
+
+		CASE_LAST
+	};
+					NegativeApiCase		(Context& ctx, const char* name, const char* desc, CaseType caseType);
+					~NegativeApiCase	(void);
+
+	IterateResult	iterate				(void);
+
+private:
+	const CaseType	m_caseType;
+};
+
+NegativeApiCase::NegativeApiCase (Context& ctx, const char* name, const char* desc, CaseType caseType)
+	: TestCase		(ctx, name, desc)
+	, m_caseType	(caseType)
+{
+}
+
+NegativeApiCase::~NegativeApiCase (void)
+{
+}
+
+NegativeApiCase::IterateResult NegativeApiCase::iterate (void)
+{
+	glw::GLenum			error;
+	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
+	glu::VertexArray	vao		(m_context.getRenderContext());
+
+	gl.enableLogging(true);
+	gl.glBindVertexArray(*vao);
+
+	switch (m_caseType)
+	{
+		case CASE_LARGE_OFFSET:
+		{
+			glw::GLint	maxOffset	= -1;
+			glw::GLint	largeOffset;
+
+			gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET, &maxOffset);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
+
+			largeOffset = maxOffset + 1;
+
+			// skip if maximum unsigned or signed values
+			if (maxOffset == -1 || maxOffset == 0x7FFFFFFF)
+				throw tcu::NotSupportedError("Implementation supports all offsets");
+
+			gl.glVertexAttribFormat(0, 4, GL_FLOAT, GL_FALSE, largeOffset);
+			break;
+		}
+
+		case CASE_LARGE_STRIDE:
+		{
+			glu::Buffer buffer		(m_context.getRenderContext());
+			glw::GLint	maxStride	= -1;
+			glw::GLint	largeStride;
+
+			gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_STRIDE, &maxStride);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
+
+			largeStride = maxStride + 1;
+
+			// skip if maximum unsigned or signed values
+			if (maxStride == -1 || maxStride == 0x7FFFFFFF)
+				throw tcu::NotSupportedError("Implementation supports all strides");
+
+			gl.glBindVertexBuffer(0, *buffer, 0, largeStride);
+			break;
+		}
+
+		case CASE_NEGATIVE_STRIDE:
+		{
+			glu::Buffer buffer(m_context.getRenderContext());
+			gl.glBindVertexBuffer(0, *buffer, 0, -20);
+			break;
+		}
+
+		case CASE_NEGATIVE_OFFSET:
+		{
+			glu::Buffer buffer(m_context.getRenderContext());
+			gl.glBindVertexBuffer(0, *buffer, -20, 0);
+			break;
+		}
+
+		case CASE_INVALID_ATTR:
+		{
+			glw::GLint maxIndex = -1;
+			glw::GLint largeIndex;
+
+			gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxIndex);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
+
+			largeIndex = maxIndex + 1;
+
+			// skip if maximum unsigned or signed values
+			if (maxIndex == -1 || maxIndex == 0x7FFFFFFF)
+				throw tcu::NotSupportedError("Implementation supports any attribute index");
+
+			gl.glVertexAttribBinding(largeIndex, 0);
+			break;
+		}
+
+		case CASE_INVALID_BINDING:
+		{
+			glw::GLint maxBindings = -1;
+			glw::GLint largeBinding;
+
+			gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &maxBindings);
+			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
+
+			largeBinding = maxBindings + 1;
+
+			// skip if maximum unsigned or signed values
+			if (maxBindings == -1 || maxBindings == 0x7FFFFFFF)
+				throw tcu::NotSupportedError("Implementation supports any binding");
+
+			gl.glVertexAttribBinding(0, largeBinding);
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	error = gl.glGetError();
+
+	if (error != GL_INVALID_VALUE)
+	{
+		m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_INVALID_VALUE, got " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid error");
+	}
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	return STOP;
+}
+
+} // anonymous
+
+VertexAttributeBindingTests::VertexAttributeBindingTests (Context& context)
+	: TestCaseGroup(context, "vertex_attribute_binding", "Test vertex attribute binding")
+{
+}
+
+VertexAttributeBindingTests::~VertexAttributeBindingTests (void)
+{
+}
+
+void VertexAttributeBindingTests::init (void)
+{
+	tcu::TestCaseGroup* const usageGroup	= new tcu::TestCaseGroup(m_testCtx, "usage", "Test using binding points");
+	tcu::TestCaseGroup* const negativeGroup	= new tcu::TestCaseGroup(m_testCtx, "negative", "Negative test");
+
+	addChild(usageGroup);
+	addChild(negativeGroup);
+
+	// .usage
+	{
+		tcu::TestCaseGroup* const singleGroup	= new tcu::TestCaseGroup(m_testCtx, "single_binding", "Test using single binding point");
+		tcu::TestCaseGroup* const multipleGroup	= new tcu::TestCaseGroup(m_testCtx, "multiple_bindings", "Test using multiple binding points");
+		tcu::TestCaseGroup* const mixedGroup	= new tcu::TestCaseGroup(m_testCtx, "mixed_usage", "Test using binding point and non binding point api variants");
+
+		usageGroup->addChild(singleGroup);
+		usageGroup->addChild(multipleGroup);
+		usageGroup->addChild(mixedGroup);
+
+		// single binding
+
+		singleGroup->addChild(new SingleBindingCase(m_context, "elements_1",																					  0));
+		singleGroup->addChild(new SingleBindingCase(m_context, "elements_2",																					  SingleBindingCase::FLAG_ATTRIBS_MULTIPLE_ELEMS));
+		singleGroup->addChild(new SingleBindingCase(m_context, "elements_2_share_elements",																		  SingleBindingCase::FLAG_ATTRIBS_SHARED_ELEMS));
+		singleGroup->addChild(new SingleBindingCase(m_context, "offset_elements_1",								SingleBindingCase::FLAG_BUF_ALIGNED_OFFSET		| 0));
+		singleGroup->addChild(new SingleBindingCase(m_context, "offset_elements_2",								SingleBindingCase::FLAG_BUF_ALIGNED_OFFSET		| SingleBindingCase::FLAG_ATTRIBS_MULTIPLE_ELEMS));
+		singleGroup->addChild(new SingleBindingCase(m_context, "offset_elements_2_share_elements",				SingleBindingCase::FLAG_BUF_ALIGNED_OFFSET		| SingleBindingCase::FLAG_ATTRIBS_SHARED_ELEMS));
+		singleGroup->addChild(new SingleBindingCase(m_context, "unaligned_offset_elements_1_aligned_elements",	SingleBindingCase::FLAG_BUF_UNALIGNED_OFFSET	| SingleBindingCase::FLAG_ATTRIB_ALIGNED));			// !< total offset is aligned
+
+		// multiple bindings
+
+		multipleGroup->addChild(new MultipleBindingCase(m_context, "basic",									0));
+		multipleGroup->addChild(new MultipleBindingCase(m_context, "zero_stride",							MultipleBindingCase::FLAG_ZERO_STRIDE));
+		multipleGroup->addChild(new MultipleBindingCase(m_context, "instanced",								MultipleBindingCase::FLAG_INSTANCED));
+		multipleGroup->addChild(new MultipleBindingCase(m_context, "aliasing_buffer_zero_stride",			MultipleBindingCase::FLAG_ALIASING_BUFFERS	| MultipleBindingCase::FLAG_ZERO_STRIDE));
+		multipleGroup->addChild(new MultipleBindingCase(m_context, "aliasing_buffer_instanced",				MultipleBindingCase::FLAG_ALIASING_BUFFERS	| MultipleBindingCase::FLAG_INSTANCED));
+
+		// mixed cases
+		mixedGroup->addChild(new MixedBindingCase(m_context,		"mixed_attribs_basic",					"Use different api for different attributes",			MixedBindingCase::CASE_BASIC));
+		mixedGroup->addChild(new MixedBindingCase(m_context,		"mixed_attribs_instanced_binding",		"Use different api for different attributes",			MixedBindingCase::CASE_INSTANCED_BINDING));
+		mixedGroup->addChild(new MixedBindingCase(m_context,		"mixed_attribs_instanced_attrib",		"Use different api for different attributes",			MixedBindingCase::CASE_INSTANCED_ATTRIB));
+
+		mixedGroup->addChild(new MixedApiCase(m_context,			"mixed_api_change_buffer",				"change buffer with vertex_attrib_binding api",			MixedApiCase::CASE_CHANGE_BUFFER));
+		mixedGroup->addChild(new MixedApiCase(m_context,			"mixed_api_change_buffer_offset",		"change buffer offset with vertex_attrib_binding api",	MixedApiCase::CASE_CHANGE_BUFFER_OFFSET));
+		mixedGroup->addChild(new MixedApiCase(m_context,			"mixed_api_change_buffer_stride",		"change buffer stride with vertex_attrib_binding api",	MixedApiCase::CASE_CHANGE_BUFFER_STRIDE));
+		mixedGroup->addChild(new MixedApiCase(m_context,			"mixed_api_change_binding_point",		"change binding point with vertex_attrib_binding api",	MixedApiCase::CASE_CHANGE_BINDING_POINT));
+	}
+
+	// negative
+	{
+		negativeGroup->addChild(new DefaultVAOCase(m_context,	"default_vao_bind_vertex_buffer",			"use with default vao",	DefaultVAOCase::CASE_BIND_VERTEX_BUFFER));
+		negativeGroup->addChild(new DefaultVAOCase(m_context,	"default_vao_vertex_attrib_format",			"use with default vao",	DefaultVAOCase::CASE_VERTEX_ATTRIB_FORMAT));
+		negativeGroup->addChild(new DefaultVAOCase(m_context,	"default_vao_vertex_attrib_i_format",		"use with default vao",	DefaultVAOCase::CASE_VERTEX_ATTRIB_I_FORMAT));
+		negativeGroup->addChild(new DefaultVAOCase(m_context,	"default_vao_vertex_attrib_binding",		"use with default vao",	DefaultVAOCase::CASE_VERTEX_ATTRIB_BINDING));
+		negativeGroup->addChild(new DefaultVAOCase(m_context,	"default_vao_vertex_binding_divisor",		"use with default vao",	DefaultVAOCase::CASE_VERTEX_BINDING_DIVISOR));
+
+		negativeGroup->addChild(new BindToCreateCase(m_context,	"bind_create_new_buffer",					"bind not existing buffer"));
+
+		negativeGroup->addChild(new NegativeApiCase(m_context, "vertex_attrib_format_large_offset",			"large relative offset",	NegativeApiCase::CASE_LARGE_OFFSET));
+		negativeGroup->addChild(new NegativeApiCase(m_context, "bind_vertex_buffer_large_stride",			"large stride",				NegativeApiCase::CASE_LARGE_STRIDE));
+		negativeGroup->addChild(new NegativeApiCase(m_context, "bind_vertex_buffer_negative_stride",		"negative stride",			NegativeApiCase::CASE_NEGATIVE_STRIDE));
+		negativeGroup->addChild(new NegativeApiCase(m_context, "bind_vertex_buffer_negative_offset",		"negative offset",			NegativeApiCase::CASE_NEGATIVE_OFFSET));
+		negativeGroup->addChild(new NegativeApiCase(m_context, "vertex_attrib_binding_invalid_attr",		"bind invalid attr",		NegativeApiCase::CASE_INVALID_ATTR));
+		negativeGroup->addChild(new NegativeApiCase(m_context, "vertex_attrib_binding_invalid_binding",		"bind invalid binding",		NegativeApiCase::CASE_INVALID_BINDING));
+	}
+}
+
+} // Functional
+} // gles31
+} // deqp
diff --git a/modules/gles31/functional/es31fVertexAttributeBindingTests.hpp b/modules/gles31/functional/es31fVertexAttributeBindingTests.hpp
new file mode 100644
index 0000000..5fb0453
--- /dev/null
+++ b/modules/gles31/functional/es31fVertexAttributeBindingTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31FVERTEXATTRIBUTEBINDINGTESTS_HPP
+#define _ES31FVERTEXATTRIBUTEBINDINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex attribute binding tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Functional
+{
+
+class VertexAttributeBindingTests : public TestCaseGroup
+{
+public:
+									VertexAttributeBindingTests		(Context& context);
+									~VertexAttributeBindingTests	(void);
+
+	void							init							(void);
+
+private:
+									VertexAttributeBindingTests		(const VertexAttributeBindingTests& other);
+	VertexAttributeBindingTests&	operator=						(const VertexAttributeBindingTests& other);
+};
+
+} // Functional
+} // gles31
+} // deqp
+
+#endif // _ES31FVERTEXATTRIBUTEBINDINGTESTS_HPP
diff --git a/modules/gles31/gles31.cmake b/modules/gles31/gles31.cmake
new file mode 100644
index 0000000..7d54ff8
--- /dev/null
+++ b/modules/gles31/gles31.cmake
@@ -0,0 +1,3 @@
+if (DEQP_SUPPORT_GLES3)
+	add_subdirectory(gles31)
+endif ()
diff --git a/modules/gles31/scripts/gen-implicit-conversions.py b/modules/gles31/scripts/gen-implicit-conversions.py
new file mode 100644
index 0000000..188f3b5
--- /dev/null
+++ b/modules/gles31/scripts/gen-implicit-conversions.py
@@ -0,0 +1,957 @@
+import sys
+import itertools
+import operator
+
+import genutil
+
+from genutil import Scalar, Vec2, Vec3, Vec4, Uint, UVec2, UVec3, UVec4, CaseGroup
+
+
+# Templates
+
+ARTIHMETIC_CASE_TEMPLATE = """
+case ${{NAME}}
+	version 310 es
+	require extension { "GL_EXT_shader_implicit_conversions" } in { vertex, fragment }
+	values
+	{
+		${{VALUES}}
+	}
+
+	both ""
+		#version 310 es
+		precision highp float;
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			out0 = ${{EXPR}};
+			${OUTPUT}
+		}
+	""
+end
+""".strip()
+
+FUNCTIONS_CASE_TEMPLATE = """
+case ${{NAME}}
+	version 310 es
+	require extension { "GL_EXT_shader_implicit_conversions" } in { vertex, fragment }
+	values
+	{
+		${{VALUES}}
+	}
+
+	both ""
+		#version 310 es
+		precision highp float;
+		${DECLARATIONS}
+
+		${{OUTTYPE}} func (${{OUTTYPE}} a)
+		{
+			return a * ${{OUTTYPE}}(2);
+		}
+
+		void main()
+		{
+			${SETUP}
+			out0 = func(in0);
+			${OUTPUT}
+		}
+	""
+end
+""".strip()
+
+ARRAY_CASE_TEMPLATE = """
+case ${{NAME}}
+	version 310 es
+	require extension { "GL_EXT_shader_implicit_conversions" } in { vertex, fragment }
+	values
+	{
+		${{VALUES}}
+	}
+
+	both ""
+		#version 310 es
+		precision highp float;
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			${{ARRAYTYPE}}[] x = ${{ARRAYTYPE}}[] (${{ARRAYVALUES}});
+			out0 = ${{EXPR}};
+			${OUTPUT}
+		}
+	""
+end
+""".strip()
+
+STRUCT_CASE_TEMPLATE = """
+case ${{NAME}}
+	version 310 es
+	require extension { "GL_EXT_shader_implicit_conversions" } in { vertex, fragment }
+	values
+	{
+		${{VALUES}}
+	}
+
+	both ""
+		#version 310 es
+		precision highp float;
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			struct {
+				${{OUTTYPE}} val;
+			} x;
+
+			x.val = ${{STRUCTVALUE}};
+
+			out0 = ${{EXPR}};
+			${OUTPUT}
+		}
+	""
+end
+""".strip()
+
+INVALID_CASE_TEMPLATE = """
+case ${{NAME}}
+	expect compile_fail
+	version 310 es
+	require extension { "GL_EXT_shader_implicit_conversions" } in { vertex, fragment }
+	values
+	{
+		${{VALUES}}
+	}
+
+	both ""
+		#version 310 es
+		precision highp float;
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			out0 = in0 + ${{OPERAND}};
+			${OUTPUT}
+		}
+	""
+end
+""".strip()
+
+INVALID_ARRAY_CASE_TEMPLATE = """
+case ${{NAME}}
+	expect compile_fail
+	version 310 es
+	require extension { "GL_EXT_shader_implicit_conversions" } in { vertex, fragment }
+	values {}
+
+	both ""
+		#version 310 es
+		precision highp float;
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			${{EXPR}}
+			${OUTPUT}
+		}
+	""
+end
+""".strip()
+
+INVALID_STRUCT_CASE_TEMPLATE = """
+case ${{NAME}}
+	expect compile_fail
+	version 310 es
+	require extension { "GL_EXT_shader_implicit_conversions" } in { vertex, fragment }
+	values {}
+
+	both ""
+		#version 310 es
+		precision highp float;
+		${DECLARATIONS}
+
+		void main()
+		{
+			${SETUP}
+			struct { ${{INTYPE}} value; } a;
+			struct { ${{OUTTYPE}} value; } b;
+			a = ${{INVALUE}};
+			b = a;
+			${OUTPUT}
+		}
+	""
+end
+""".strip()
+
+
+# Input values
+
+IN_ISCALAR = [  2,  1,  1,  3,  5 ]
+IN_USCALAR = [  1,  3,  4,  7, 11 ]
+
+IN_IVECTOR = [
+	( 1,  2,  3,  4),
+	( 2,  1,  2,  6),
+	( 3,  7,  2,  5),
+]
+
+IN_UVECTOR = [
+	( 2,  3,  5,  8),
+	( 4,  6,  2,  9),
+	( 1, 13,  7,  4),
+]
+
+IN_VALUES = {
+	"int":		[Scalar(x)								for x in IN_ISCALAR],
+	"uint":		[Scalar(x)								for x in IN_USCALAR],
+	"ivec2":	[Vec2(x[0], x[1])						for x in IN_IVECTOR],
+	"uvec2":	[Vec2(x[0], x[1])						for x in IN_UVECTOR],
+	"ivec3":	[Vec3(x[0], x[1], x[2])					for x in IN_IVECTOR],
+	"uvec3":	[Vec3(x[0], x[1], x[2])					for x in IN_UVECTOR],
+	"ivec4":	[Vec4(x[0], x[1], x[2], x[3])			for x in IN_IVECTOR],
+	"uvec4":	[Vec4(x[0], x[1], x[2], x[3])			for x in IN_UVECTOR],
+	"float":	[Scalar(x).toFloat()					for x in IN_ISCALAR],
+	"vec2":		[Vec2(x[0], x[1]).toFloat()				for x in IN_IVECTOR],
+	"vec3":		[Vec3(x[0], x[1], x[2]).toFloat()		for x in IN_IVECTOR],
+	"vec4":		[Vec4(x[0], x[1], x[2], x[3]).toFloat()	for x in IN_IVECTOR],
+}
+
+VALID_CONVERSIONS = {
+	"int":		["float", "uint"],
+	"uint":		["float"],
+	"ivec2":	["uvec2", "vec2"],
+	"uvec2":	["vec2"],
+	"ivec3":	["uvec3", "vec3"],
+	"uvec3":	["vec3"],
+	"ivec4":	["uvec4", "vec4"],
+	"uvec4":	["vec4"]
+}
+
+SCALAR_TO_VECTOR_CONVERSIONS = {
+	"int":		["vec2", "vec3", "vec4", "uvec2", "uvec3", "uvec4"],
+	"uint":		["vec2", "vec3", "vec4"]
+}
+
+VALID_ASSIGNMENTS = {
+	"int":		["ivec2", "ivec3", "ivec4"],
+	"uint":		["uvec2", "uvec3", "uvec4"],
+	"ivec2":	["int", "float"],
+	"ivec3":	["int", "float"],
+	"ivec4":	["int", "float"],
+	"uvec2":	["uint", "float"],
+	"uvec3":	["uint", "float"],
+	"uvec4":	["uint", "float"],
+	"float":	["vec2", "vec3", "vec4"],
+	"vec2":		["float"],
+	"vec3":		["float"],
+	"vec4":		["float"]
+}
+
+IN_TYPE_ORDER = [
+	"int", 	 "uint",
+	"ivec2", "uvec2", "ivec3",
+	"uvec3", "ivec4", "uvec4",
+
+	"float",
+	"vec2",  "vec3",  "vec4"
+]
+
+def isScalarTypeName (name):
+	return name in ["float", "int", "uint"]
+
+def isVec2TypeName (name):
+	return name in ["vec2", "ivec2", "uvec2"]
+
+def isVec3TypeName (name):
+	return name in ["vec3", "ivec3", "uvec3"]
+
+def isVec4TypeName (name):
+	return name in ["vec4", "ivec4", "uvec4"]
+
+# Utilities
+
+def scalarToVector(a, b):
+	if isinstance(a, Scalar) and isinstance(b, Vec2):
+		a = a.toVec2()
+	elif isinstance(a, Scalar) and isinstance(b, Vec3):
+		a = a.toVec3()
+	elif isinstance(a, Scalar) and isinstance(b, Vec4):
+		a = a.toVec4()
+	return a
+
+def isUintTypeName (type_name):
+	return type_name in ["uint", "uvec2", "uvec3", "uvec4"]
+
+def convLiteral (type, value):
+	if isUintTypeName(type):
+		return int(value)
+	else:
+		return value
+
+def valueToStr(value_type, value):
+	if isinstance(value, Scalar):
+		return str(value)
+	else:
+		assert isinstance(value, genutil.Vec)
+		out = value_type + "("
+		out += ", ".join([str(convLiteral(value_type, x)) for x in value.getScalars()])
+		out += ")"
+		return out
+
+
+def valuesToStr(prefix, value_type, values):
+	def gen_value_strs(value_list, value_type):
+		for value in value_list:
+			yield valueToStr(value_type, value)
+	return "%s = [ %s ];" % (prefix, " | ".join(gen_value_strs(values, value_type)))
+
+
+# Test cases
+
+class ArithmeticCase(genutil.ShaderCase):
+	def __init__(self, name, op, in_type, out_type, reverse=False):
+		self.op_func = {
+			"+":	operator.add,
+			"-":	operator.sub,
+			"*":	operator.mul,
+			"/":	operator.div,
+		}
+		self.name		= name
+		self.op			= op
+		self.in_type	= in_type
+		self.out_type	= out_type
+		self.reverse	= reverse
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"EXPR":		self.get_expr(),
+			"VALUES":	self.gen_values(),
+		}
+		return genutil.fillTemplate(ARTIHMETIC_CASE_TEMPLATE, params)
+
+	def apply(self, a, b):
+		assert(self.op in self.op_func)
+		a = scalarToVector(a, b)
+
+		if self.reverse:
+			b, a = a, b
+
+		return self.op_func[self.op](a, b)
+
+	def get_expr(self):
+		expr = ["in0", self.op, str(self.get_operand())]
+
+		if self.reverse:
+			expr.reverse()
+
+		return " ".join(expr)
+
+	def get_operand(self):
+		operands = {
+			"float":	Scalar(2.0),
+			"vec2":		Vec2(1.0, 2.0),
+			"vec3":		Vec3(1.0, 2.0, 3.0),
+			"vec4":		Vec4(1.0, 2.0, 3.0, 4.0),
+			"uint":		Uint(2),
+			"uvec2":	UVec2(1, 2),
+			"uvec3":	UVec3(1, 2, 3),
+			"uvec4":	UVec4(1, 2, 3, 4),
+		}
+		assert self.out_type in operands
+		return operands[self.out_type]
+
+	def gen_values(self):
+		in_values	= IN_VALUES[self.in_type]
+
+		y			= self.get_operand()
+		out_values	= [self.apply(x, y) for x in in_values]
+
+		out = []
+		out.append(valuesToStr("input %s in0" % (self.in_type), self.in_type, in_values))
+		out.append(valuesToStr("output %s out0" % (self.out_type), self.out_type, out_values))
+
+		return "\n".join(out)
+
+
+class ComparisonsCase(ArithmeticCase):
+	def __init__(self, name, op, in_type, out_type, reverse=False):
+		super(ComparisonsCase, self).__init__(name, op, in_type, out_type, reverse)
+
+		self.op_func = {
+			"==":	operator.eq,
+			"!=":	operator.ne,
+			"<":	operator.lt,
+			">":	operator.gt,
+			"<=":	operator.le,
+			">=":	operator.ge,
+		}
+
+	def apply(self, a, b):
+		assert(self.op in self.op_func)
+
+		if isinstance(a, Scalar) and isinstance(b, Scalar):
+			a, b = float(a), float(b)
+
+		if self.reverse:
+			b, a = a, b
+
+		return Scalar(self.op_func[self.op](a, b))
+
+	def gen_values(self):
+		in_values	= IN_VALUES[self.in_type]
+
+		y			= self.get_operand()
+		out_values	= [self.apply(x, y) for x in in_values]
+
+		out = []
+		out.append(valuesToStr("input %s in0" % (self.in_type), self.in_type, in_values))
+		out.append(valuesToStr("output bool out0", "bool", out_values))
+
+		return "\n".join(out)
+
+
+class ParenthesizedCase(genutil.ShaderCase):
+	def __init__(self, name, in_type, out_type, reverse=False, input_in_parens=False):
+		self.name				= name
+		self.in_type			= in_type
+		self.out_type			= out_type
+		self.reverse			= reverse
+		self.input_in_parens	= input_in_parens
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"EXPR":		self.get_expr(),
+			"VALUES":	self.gen_values(),
+		}
+		return genutil.fillTemplate(ARTIHMETIC_CASE_TEMPLATE, params)
+
+	def apply(self, a):
+		b, c	= self.get_operand(0), self.get_operand(1)
+		a		= scalarToVector(a, b)
+
+		if self.input_in_parens:
+			return b*(a+c)
+		else:
+			return a*(b+c)
+
+	def get_expr(self):
+		def make_paren_expr():
+			out = [
+				"in0" if self.input_in_parens else self.get_operand(0),
+				"+",
+				self.get_operand(1)
+			]
+			return "(%s)" % (" ".join([str(x) for x in out]))
+
+		expr = [
+			"in0" if not self.input_in_parens else self.get_operand(0),
+			"*",
+			make_paren_expr()
+		]
+
+		if self.reverse:
+			expr.reverse()
+
+		return " ".join([str(x) for x in expr])
+
+	def get_operand(self, ndx=0):
+		return IN_VALUES[self.out_type][ndx]
+
+	def gen_values(self):
+		in_values	= IN_VALUES[self.in_type]
+
+		out_values	= [self.apply(x) for x in in_values]
+
+		out = []
+		out.append(valuesToStr("input %s in0" % (self.in_type), self.in_type, in_values))
+		out.append(valuesToStr("output %s out0" % (self.out_type), self.out_type, out_values))
+
+		return "\n".join(out)
+
+
+class FunctionsCase(genutil.ShaderCase):
+	def __init__(self, name, in_type, out_type):
+		self.name		= name
+		self.in_type	= in_type
+		self.out_type	= out_type
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"OUTTYPE":	self.out_type,
+			"VALUES":	self.gen_values(),
+		}
+		return genutil.fillTemplate(FUNCTIONS_CASE_TEMPLATE, params)
+
+	def apply(self, a):
+		if isUintTypeName(self.out_type):
+			return a.toUint() * Uint(2)
+		else:
+			return a.toFloat() * Scalar(2.0)
+
+	def gen_values(self):
+		in_values	= IN_VALUES[self.in_type]
+		out_values	= [self.apply(x) for x in in_values]
+
+		out = []
+		out.append(valuesToStr("input %s in0" % (self.in_type), self.in_type, in_values))
+		out.append(valuesToStr("output %s out0" % (self.out_type), self.out_type, out_values))
+
+		return "\n".join(out)
+
+
+class ArrayCase(genutil.ShaderCase):
+	def __init__(self, name, in_type, out_type, reverse=False):
+		self.name		= name
+		self.in_type	= in_type
+		self.out_type	= out_type
+		self.reverse	= reverse
+
+	def __str__(self):
+		params = {
+			"NAME":			self.name,
+			"VALUES":		self.gen_values(),
+			"ARRAYTYPE":	self.out_type,
+			"ARRAYVALUES":	self.gen_array_values(),
+			"EXPR":			self.get_expr(),
+		}
+		return genutil.fillTemplate(ARRAY_CASE_TEMPLATE, params)
+
+	def apply(self, a):
+		b = IN_VALUES[self.out_type][1]
+		a = scalarToVector(a, b)
+
+		return a + b
+
+	def get_expr(self):
+		if not self.reverse:
+			return "in0 + x[1]"
+		else:
+			return "x[1] + in0"
+
+	def gen_values(self):
+		in_values	= IN_VALUES[self.in_type]
+		out_values	= [self.apply(x) for x in in_values]
+
+		out = []
+		out.append(valuesToStr("input %s in0" % (self.in_type), self.in_type, in_values))
+		out.append(valuesToStr("output %s out0" % (self.out_type), self.out_type, out_values))
+
+		return "\n".join(out)
+
+	def gen_array_values(self):
+		out = [valueToStr(self.out_type, x) for x in IN_VALUES[self.out_type]]
+		return ", ".join(out)
+
+
+class ArrayUnpackCase(genutil.ShaderCase):
+	def __init__(self, name, in_type, out_type):
+		self.name		= name
+		self.in_type	= in_type
+		self.out_type	= out_type
+
+	def __str__(self):
+		params = {
+			"NAME":			self.name,
+			"VALUES":		self.gen_values(),
+			"ARRAYTYPE":	"float",
+			"ARRAYVALUES":	self.gen_array_values(),
+			"EXPR":			self.get_expr(),
+		}
+		return genutil.fillTemplate(ARRAY_CASE_TEMPLATE, params)
+
+	def apply(self, a):
+		if isinstance(a, Scalar) and isVec2TypeName(self.out_type):
+			a = a.toVec2()
+		elif isinstance(a, Scalar) and isVec3TypeName(self.out_type):
+			a = a.toVec3()
+		elif isinstance(a, Scalar) and isVec4TypeName(self.out_type):
+			a = a.toVec4()
+
+		b = IN_VALUES["float"]
+
+		out = [Scalar(x)+y for x, y in zip(a.getScalars(), b)]
+
+		if self.out_type == "float":
+			return out[0].toFloat()
+		elif self.out_type == "uint":
+			return out[0].toUint()
+		elif self.out_type == "vec2":
+			return Vec2(out[0], out[1]).toFloat()
+		elif self.out_type == "uvec2":
+			return Vec2(out[0], out[1]).toUint()
+		elif self.out_type == "vec3":
+			return Vec3(out[0], out[1], out[2]).toFloat()
+		elif self.out_type == "uvec3":
+			return Vec3(out[0], out[1], out[2]).toUint()
+		elif self.out_type == "vec4":
+			return Vec4(out[0], out[1], out[2], out[3]).toFloat()
+		elif self.out_type == "uvec4":
+			return Vec4(out[0], out[1], out[2], out[3]).toUint()
+
+	def get_expr(self):
+		def num_scalars(typename):
+			return IN_VALUES[typename][0].getNumScalars()
+
+		def gen_sums():
+			in_scalars	= num_scalars(self.in_type)
+			out_scalars	= num_scalars(self.out_type)
+
+			for ndx in range(out_scalars):
+				if in_scalars > 1:
+					yield "in0[%i] + x[%i]" % (ndx, ndx)
+				else:
+					yield "in0 + x[%i]" % (ndx)
+
+		return "%s(%s)" % (self.out_type, ", ".join(gen_sums()))
+
+	def gen_values(self):
+		in_values	= IN_VALUES[self.in_type]
+		out_values	= [self.apply(x) for x in in_values]
+
+		out = []
+		out.append(valuesToStr("input %s in0" % (self.in_type), self.in_type, in_values))
+		out.append(valuesToStr("output %s out0" % (self.out_type), self.out_type, out_values))
+
+		return "\n".join(out)
+
+	def gen_array_values(self):
+		out = [valueToStr(self.out_type, x) for x in IN_VALUES["float"]]
+		return ", ".join(out)
+
+
+class StructCase(genutil.ShaderCase):
+	def __init__(self, name, in_type, out_type, reverse=False):
+		self.name		= name
+		self.in_type	= in_type
+		self.out_type	= out_type
+		self.reverse	= reverse
+
+	def __str__(self):
+		params = {
+			"NAME":			self.name,
+			"VALUES":		self.gen_values(),
+			"OUTTYPE":		self.out_type,
+			"STRUCTVALUE":	self.get_struct_value(),
+			"EXPR":			self.get_expr(),
+		}
+		return genutil.fillTemplate(STRUCT_CASE_TEMPLATE, params)
+
+	def apply(self, a):
+		if isinstance(a, Scalar) and isVec2TypeName(self.out_type):
+			a = a.toVec2()
+		elif isinstance(a, Scalar) and isVec3TypeName(self.out_type):
+			a = a.toVec3()
+		elif isinstance(a, Scalar) and isVec4TypeName(self.out_type):
+			a = a.toVec4()
+
+		b = IN_VALUES[self.out_type][0]
+
+		return a + b
+
+	def get_expr(self):
+		if not self.reverse:
+			return "in0 + x.val"
+		else:
+			return "x.val + in0"
+
+	def gen_values(self):
+		in_values	= IN_VALUES[self.in_type]
+		out_values	= [self.apply(x) for x in in_values]
+
+		out = []
+		out.append(valuesToStr("input %s in0" % (self.in_type), self.in_type, in_values))
+		out.append(valuesToStr("output %s out0" % (self.out_type), self.out_type, out_values))
+
+		return "\n".join(out)
+
+	def get_struct_value(self):
+		return valueToStr(self.out_type, IN_VALUES[self.out_type][0])
+
+
+class InvalidCase(genutil.ShaderCase):
+	def __init__(self, name, in_type, out_type):
+		self.name		= name
+		self.in_type	= in_type
+		self.out_type	= out_type
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"OPERAND":	str(self.get_operand()),
+			"VALUES":	self.gen_values(),
+		}
+		return genutil.fillTemplate(INVALID_CASE_TEMPLATE, params)
+
+	def apply(self, a, b):
+		return b
+
+	def get_operand(self):
+		return IN_VALUES[self.out_type][0]
+
+	def gen_values(self):
+		in_values	= IN_VALUES[self.in_type]
+
+		y			= self.get_operand()
+		out_values	= [self.apply(x, y) for x in in_values]
+
+		out = []
+		out.append(valuesToStr("input %s in0" % (self.in_type), self.in_type, in_values))
+		out.append(valuesToStr("output %s out0" % (self.out_type), self.out_type, out_values))
+
+		return "\n".join(out)
+
+
+class InvalidArrayCase(genutil.ShaderCase):
+	def __init__(self, name, in_type, out_type):
+		self.name		= name
+		self.in_type	= in_type
+		self.out_type	= out_type
+
+	def __str__(self):
+		params = {
+			"NAME":	self.name,
+			"EXPR":	self.gen_expr(),
+		}
+		return genutil.fillTemplate(INVALID_ARRAY_CASE_TEMPLATE, params)
+
+	def gen_expr(self):
+		in_values = [valueToStr(self.out_type, x) for x in IN_VALUES[self.in_type]]
+
+		out = "%s a[] = %s[] (%s);" % (self.out_type, self.in_type, ", ".join(in_values))
+
+		return out
+
+
+class InvalidStructCase(genutil.ShaderCase):
+	def __init__(self, name, in_type, out_type):
+		self.name		= name
+		self.in_type	= in_type
+		self.out_type	= out_type
+
+	def __str__(self):
+		params = {
+			"NAME":		self.name,
+			"INTYPE":	self.in_type,
+			"OUTTYPE":	self.out_type,
+			"INVALUE":	self.get_value(),
+		}
+		return genutil.fillTemplate(INVALID_STRUCT_CASE_TEMPLATE, params)
+
+	def get_value(self):
+		return valueToStr(self.in_type, IN_VALUES[self.in_type][0])
+
+
+# Case file generation
+
+def genConversionPairs(order=IN_TYPE_ORDER, scalar_to_vector=True, additional={}):
+	def gen_order(conversions):
+		key_set = set(conversions.iterkeys())
+		for typename in order:
+			if typename in key_set:
+				yield typename
+	conversions = {}
+
+	for in_type in VALID_CONVERSIONS:
+		conversions[in_type] = [] + VALID_CONVERSIONS[in_type]
+		if in_type in SCALAR_TO_VECTOR_CONVERSIONS and scalar_to_vector:
+			conversions[in_type] += SCALAR_TO_VECTOR_CONVERSIONS[in_type]
+
+	for key in additional.iterkeys():
+			value = conversions.get(key, [])
+			conversions[key] = value + additional[key]
+
+	for in_type in gen_order(conversions):
+		for out_type in conversions[in_type]:
+			yield (in_type, out_type)
+
+
+def genInvalidConversions():
+	types = IN_TYPE_ORDER
+	valid_pairs = set(genConversionPairs(additional=VALID_ASSIGNMENTS))
+
+	for pair in itertools.permutations(types, 2):
+		if pair not in valid_pairs:
+			yield pair
+
+
+def genArithmeticCases(reverse=False):
+	op_names = [
+		("add", "Addition",			"+"),
+		("sub", "Subtraction",		"-"),
+		("mul", "Multiplication",	"*"),
+		("div", "Division",			"/")
+	]
+
+	for name, desc, op in op_names:
+		casegroup = CaseGroup(name, desc, [])
+		for in_type, out_type in genConversionPairs():
+			if op == "-" and isUintTypeName(out_type):
+				continue # Can't handle at the moment
+			name = in_type + "_to_" + out_type
+			casegroup.children.append(ArithmeticCase(name, op, in_type, out_type, reverse))
+		yield casegroup
+
+
+def genComparisonCases(reverse=False):
+	op_names = [
+		("equal",				"Equal",					"=="),
+		("not_equal",			"Not equal",				"!="),
+		("less",				"Less than",				"<"),
+		("greater",				"Greater than",				">"),
+		("less_or_equal",		"Less than or equal",		"<="),
+		("greater_or_equal",	"Greater than or equal",	">="),
+	]
+
+	for name, desc, op in op_names:
+		casegroup	= CaseGroup(name, desc, [])
+		type_order	= IN_TYPE_ORDER if name in ["equal", "not_equal"] else ["int", "uint"]
+
+		for in_type, out_type in genConversionPairs(order=type_order, scalar_to_vector=False):
+			name = in_type + "_to_" + out_type
+			casegroup.children.append(ComparisonsCase(name, op, in_type, out_type, reverse))
+		yield casegroup
+
+
+def genParenthesizedCases():
+	for reverse in [True, False]:
+		if reverse:
+			name = "paren_expr_before_literal"
+			desc = "Parenthesized expression before literal"
+		else:
+			name = "literal_before_paren_expr"
+			desc = "Literal before parenthesized expression"
+		reversegroup = CaseGroup(name, desc, [])
+
+		for input_in_parens in [True, False]:
+			if input_in_parens:
+				name = "input_in_parens"
+				desc = "Input variable in parenthesized expression"
+			else:
+				name = "input_outside_parens"
+				desc = "Input variable outside parenthesized expression"
+			casegroup = CaseGroup(name, desc, [])
+
+			for in_type, out_type in genConversionPairs():
+				name = in_type + "_to_" + out_type
+				casegroup.children.append(
+					ParenthesizedCase(name, in_type, out_type, reverse, input_in_parens)
+				)
+			reversegroup.children.append(casegroup)
+		yield reversegroup
+
+
+def genArrayCases(reverse=False):
+	for in_type, out_type in genConversionPairs():
+		name = in_type + "_to_" + out_type
+		yield ArrayCase(name, in_type, out_type, reverse)
+
+
+def genArrayUnpackCases(reverse=False):
+	for in_type, out_type in genConversionPairs():
+		name = in_type + "_to_" + out_type
+		yield ArrayUnpackCase(name, in_type, out_type)
+
+
+def genFunctionsCases():
+	for in_type, out_type in genConversionPairs(scalar_to_vector=False):
+		name = in_type + "_to_" + out_type
+		yield FunctionsCase(name, in_type, out_type)
+
+
+def genStructCases(reverse=False):
+	for in_type, out_type in genConversionPairs():
+		name = in_type + "_to_" + out_type
+		yield StructCase(name, in_type, out_type, reverse)
+
+
+def genInvalidCases(reverse=False):
+	for in_type, out_type in genInvalidConversions():
+		name = in_type + "_to_" + out_type
+		yield InvalidCase(name, in_type, out_type)
+
+
+def genInvalidArrayCases():
+	for in_type, out_type in genConversionPairs(scalar_to_vector=False):
+		name = in_type + "_to_" + out_type
+		yield InvalidArrayCase(name, in_type, out_type)
+
+
+def genInvalidStructCases():
+	for in_type, out_type in genConversionPairs(scalar_to_vector=False):
+		name = in_type + "_to_" + out_type
+		yield InvalidStructCase(name, in_type, out_type)
+
+
+def genAllCases():
+	yield CaseGroup(
+		"arithmetic", "Arithmetic operations",
+		[
+			CaseGroup("input_before_literal", "Input before literal",
+					  genArithmeticCases(reverse=False)),
+			CaseGroup("literal_before_input", "Literal before input",
+					  genArithmeticCases(reverse=True)),
+		]
+	)
+
+	yield CaseGroup(
+		"comparisons", "Comparisons",
+		[
+			CaseGroup("input_before_literal", "Input before literal",
+					  genComparisonCases(reverse=False)),
+			CaseGroup("literal_before_input", "Literal before input",
+					  genComparisonCases(reverse=True)),
+		]
+	)
+
+	yield CaseGroup(
+		"array_subscripts", "Array subscripts",
+		[
+			CaseGroup("input_before_subscript", "Input before subscript",
+					  genArrayCases(reverse=False)),
+			CaseGroup("subscript_before_input", "Subscript before input",
+					  genArrayCases(reverse=True)),
+		#	CaseGroup("unpack", "Unpack array and repack as value",
+		#			  genArrayUnpackCases()),
+		]
+	)
+
+	yield CaseGroup("functions", "Function calls",
+					genFunctionsCases())
+
+	yield CaseGroup("struct_fields", "Struct field selectors",
+		[
+			CaseGroup("input_before_field", "Input before field",
+					  genStructCases(reverse=False)),
+			CaseGroup("field_before_input", "Field before input",
+					  genStructCases(reverse=True)),
+		]
+	)
+
+	yield CaseGroup("parenthesized_expressions", "Parenthesized expressions",
+					genParenthesizedCases())
+
+	yield CaseGroup(
+		"invalid", "Invalid conversions",
+		[
+			CaseGroup("variables", "Single variables",
+					  genInvalidCases()),
+			CaseGroup("arrays", "Arrays",
+					  genInvalidArrayCases()),
+			CaseGroup("structs", "Structs",
+					  genInvalidStructCases()),
+		]
+	)
+
+
+if __name__ == "__main__":
+	print "Generating shader case files."
+	genutil.writeAllCases("implicit_conversions.test", genAllCases())
diff --git a/modules/gles31/scripts/genutil.py b/modules/gles31/scripts/genutil.py
new file mode 100644
index 0000000..f7ccc1a
--- /dev/null
+++ b/modules/gles31/scripts/genutil.py
@@ -0,0 +1,793 @@
+import re
+import math
+import random
+
+PREAMBLE = """
+# WARNING: This file is auto-generated. Do NOT modify it manually, but rather
+# modify the generating script file. Otherwise changes will be lost!
+"""[1:]
+
+class CaseGroup(object):
+	def __init__(self, name, description, children):
+		self.name			= name
+		self.description	= description
+		self.children		= children
+
+class ShaderCase(object):
+	def __init__(self):
+		pass
+
+g_processedCases = {}
+
+def indentTextBlock(text, indent):
+	indentStr = indent * "\t"
+	lines = text.split("\n")
+	lines = [indentStr + line for line in lines]
+	lines = [ ["", line][line.strip() != ""] for line in lines]
+	return "\n".join(lines)
+
+def writeCase(f, case, indent, prefix):
+	print "    %s" % (prefix + case.name)
+	if isinstance(case, CaseGroup):
+		f.write(indentTextBlock('group %s "%s"\n\n' % (case.name, case.description), indent))
+		for child in case.children:
+			writeCase(f, child, indent + 1, prefix + case.name + ".")
+		f.write(indentTextBlock("\nend # %s\n" % case.name, indent))
+	else:
+		# \todo [petri] Fix hack.
+		fullPath = prefix + case.name
+		assert (fullPath not in g_processedCases)
+		g_processedCases[fullPath] = None
+		f.write(indentTextBlock(str(case) + "\n", indent))
+
+def writeAllCases(fileName, caseList):
+	# Write all cases to file.
+	print "  %s.." % fileName
+	f = file(fileName, "wb")
+	f.write(PREAMBLE + "\n")
+	for case in caseList:
+		writeCase(f, case, 0, "")
+	f.close()
+
+	print "done! (%d cases written)" % len(g_processedCases)
+
+# Template operations.
+
+def genValues(inputs, outputs):
+	res = []
+	for (name, values) in inputs:
+		res.append("input %s = [ %s ];" % (name, " | ".join([str(v) for v in values]).lower()))
+	for (name, values) in outputs:
+		res.append("output %s = [ %s ];" % (name, " | ".join([str(v) for v in values]).lower()))
+	return ("\n".join(res))
+
+def fillTemplate(template, params):
+	s = template
+
+	for (key, value) in params.items():
+		m = re.search(r"^(\s*)\$\{\{%s\}\}$" % key, s, re.M)
+		if m is not None:
+			start = m.start(0)
+			end = m.end(0)
+			ws = m.group(1)
+			if value is not None:
+				repl = "\n".join(["%s%s" % (ws, line) for line in value.split("\n")])
+				s = s[:start] + repl + s[end:]
+			else:
+				s = s[:start] + s[end+1:] # drop the whole line
+		else:
+			s = s.replace("${{%s}}" % key, value)
+	return s
+
+# Return shuffled version of list
+def shuffled(lst):
+	tmp = lst[:]
+	random.shuffle(tmp)
+	return tmp
+
+def repeatToLength(lst, toLength):
+	return (toLength / len(lst)) * lst + lst[: toLength % len(lst)]
+
+# Helpers to convert a list of Scalar/Vec values into another type.
+
+def toFloat(lst):	return [Scalar(float(v.x)) for v in lst]
+def toInt(lst):		return [Scalar(int(v.x)) for v in lst]
+def toUint(lst):	return [Uint(int(v.x)) for v in lst]
+def toBool(lst):	return [Scalar(bool(v.x)) for v in lst]
+def toVec4(lst):	return [v.toFloat().toVec4() for v in lst]
+def toVec3(lst):	return [v.toFloat().toVec3() for v in lst]
+def toVec2(lst):	return [v.toFloat().toVec2() for v in lst]
+def toIVec4(lst):	return [v.toInt().toVec4() for v in lst]
+def toIVec3(lst):	return [v.toInt().toVec3() for v in lst]
+def toIVec2(lst):	return [v.toInt().toVec2() for v in lst]
+def toBVec4(lst):	return [v.toBool().toVec4() for v in lst]
+def toBVec3(lst):	return [v.toBool().toVec3() for v in lst]
+def toBVec2(lst):	return [v.toBool().toVec2() for v in lst]
+def toUVec4(lst):	return [v.toUint().toUVec4() for v in lst]
+def toUVec3(lst):	return [v.toUint().toUVec3() for v in lst]
+def toUVec2(lst):	return [v.toUint().toUVec2() for v in lst]
+def toMat2(lst):	return [v.toMat2() for v in lst]
+def toMat2x3(lst):	return [v.toMat2x3() for v in lst]
+def toMat2x4(lst):	return [v.toMat2x4() for v in lst]
+def toMat3x2(lst):	return [v.toMat3x2() for v in lst]
+def toMat3(lst):	return [v.toMat3() for v in lst]
+def toMat3x4(lst):	return [v.toMat3x4() for v in lst]
+def toMat4x2(lst):	return [v.toMat4x2() for v in lst]
+def toMat4x3(lst):	return [v.toMat4x3() for v in lst]
+def toMat4(lst):	return [v.toMat4() for v in lst]
+
+# Random value generation.
+
+class GenRandom(object):
+	def __init__(self):
+		pass
+
+	def uniformVec4(self, count, mn, mx):
+		ret = [Vec4(random.uniform(mn, mx), random.uniform(mn, mx), random.uniform(mn, mx), random.uniform(mn, mx)) for x in xrange(count)]
+		ret[0].x = mn
+		ret[1].x = mx
+		ret[2].x = (mn + mx) * 0.5
+		return ret
+
+	def uniformBVec4(self, count):
+		ret = [Vec4(random.random() >= 0.5, random.random() >= 0.5, random.random() >= 0.5, random.random() >= 0.5) for x in xrange(count)]
+		ret[0].x = True
+		ret[1].x = False
+		return ret
+
+#	def uniform(self,
+
+# Math operating on Scalar/Vector types.
+
+def glslSign(a):			return 0.0 if (a == 0) else +1.0 if (a > 0.0) else -1.0
+def glslMod(x, y):			return x - y*math.floor(x/y)
+def glslClamp(x, mn, mx):	return mn if (x < mn) else mx if (x > mx) else x
+
+class GenMath(object):
+	@staticmethod
+	def unary(func):	return lambda val: val.applyUnary(func)
+
+	@staticmethod
+	def binary(func):	return lambda a, b: (b.expandVec(a)).applyBinary(func, a.expandVec(b))
+
+	@staticmethod
+	def frac(val):		return val.applyUnary(lambda x: x - math.floor(x))
+
+	@staticmethod
+	def exp2(val):		return val.applyUnary(lambda x: math.pow(2.0, x))
+
+	@staticmethod
+	def log2(val):		return val.applyUnary(lambda x: math.log(x, 2.0))
+
+	@staticmethod
+	def rsq(val):		return val.applyUnary(lambda x: 1.0 / math.sqrt(x))
+
+	@staticmethod
+	def sign(val):		return val.applyUnary(glslSign)
+
+	@staticmethod
+	def isEqual(a, b):	return Scalar(a.isEqual(b))
+
+	@staticmethod
+	def isNotEqual(a, b):	return Scalar(not a.isEqual(b))
+
+	@staticmethod
+	def step(a, b):		return (b.expandVec(a)).applyBinary(lambda edge, x: [1.0, 0.0][x < edge], a.expandVec(b))
+
+	@staticmethod
+	def length(a):		return a.length()
+
+	@staticmethod
+	def distance(a, b):	return a.distance(b)
+
+	@staticmethod
+	def dot(a, b):		return a.dot(b)
+
+	@staticmethod
+	def cross(a, b):	return a.cross(b)
+
+	@staticmethod
+	def normalize(a):	return a.normalize()
+
+	@staticmethod
+	def boolAny(a):		return a.boolAny()
+
+	@staticmethod
+	def boolAll(a):		return a.boolAll()
+
+	@staticmethod
+	def boolNot(a):		return a.boolNot()
+
+	@staticmethod
+	def abs(a):			return a.abs()
+
+# ..
+
+class Scalar(object):
+	def __init__(self, x):
+		self.x = x
+
+	def applyUnary(self, func):			return Scalar(func(self.x))
+	def applyBinary(self, func, other):	return Scalar(func(self.x, other.x))
+
+	def isEqual(self, other):	assert isinstance(other, Scalar); return (self.x == other.x)
+
+	def expandVec(self, val):	return val
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.x)
+	def toVec3(self):			return Vec3(self.x, self.x, self.x)
+	def toVec4(self):			return Vec4(self.x, self.x, self.x, self.x)
+	def toUVec2(self):			return UVec2(self.x, self.x)
+	def toUVec3(self):			return UVec3(self.x, self.x, self.x)
+	def toUVec4(self):			return UVec4(self.x, self.x, self.x, self.x)
+	def toMat2(self):			return Mat.fromScalar(2, 2, float(self.x))
+	def toMat2x3(self):			return Mat.fromScalar(2, 3, float(self.x))
+	def toMat2x4(self):			return Mat.fromScalar(2, 4, float(self.x))
+	def toMat3x2(self):			return Mat.fromScalar(3, 2, float(self.x))
+	def toMat3(self):			return Mat.fromScalar(3, 3, float(self.x))
+	def toMat3x4(self):			return Mat.fromScalar(3, 4, float(self.x))
+	def toMat4x2(self):			return Mat.fromScalar(4, 2, float(self.x))
+	def toMat4x3(self):			return Mat.fromScalar(4, 3, float(self.x))
+	def toMat4(self):			return Mat.fromScalar(4, 4, float(self.x))
+
+	def toFloat(self):			return Scalar(float(self.x))
+	def toInt(self):			return Scalar(int(self.x))
+	def toUint(self):			return Uint(int(self.x))
+	def toBool(self):			return Scalar(bool(self.x))
+
+	def getNumScalars(self):	return 1
+	def getScalars(self):		return [self.x]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bool"
+		elif isinstance(self.x, int):
+			return "int"
+		elif isinstance(self.x, float):
+			return "float"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ""
+
+	def __str__(self):
+		return str(self.x).lower()
+
+	def __float__(self):
+		return float(self.x)
+
+	def length(self):
+		return Scalar(abs(self.x))
+
+	def distance(self, v):
+		assert isinstance(v, Scalar)
+		return Scalar(abs(self.x - v.x))
+
+	def dot(self, v):
+		assert isinstance(v, Scalar)
+		return Scalar(self.x * v.x)
+
+	def normalize(self):
+		return Scalar(glslSign(self.x))
+
+	def abs(self):
+		if isinstance(self.x, bool):
+			return Scalar(self.x)
+		else:
+			return Scalar(abs(self.x))
+
+	def __neg__(self):
+		return Scalar(-self.x)
+
+	def __add__(self, val):
+		if not isinstance(val, Scalar):
+			print val
+		assert isinstance(val, Scalar)
+		return Scalar(self.x + val.x)
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			return Scalar(self.x * val.x)
+		elif isinstance(val, Vec2):
+			return Vec2(self.x * val.x, self.x * val.y)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x * val.x, self.x * val.y, self.x * val.z)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x * val.x, self.x * val.y, self.x * val.z, self.x * val.w)
+		else:
+			assert False
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Scalar(self.x / val.x)
+		elif isinstance(val, Vec2):
+			return Vec2(self.x / val.x, self.x / val.y)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x / val.x, self.x / val.y, self.x / val.z)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x / val.x, self.x / val.y, self.x / val.z, self.x / val.w)
+		else:
+			assert False
+
+class Uint(Scalar):
+	def __init__(self, x):
+		assert x >= 0
+		self.x = x
+
+	def typeString(self):
+		return "uint"
+
+	def abs(self):
+		return Scalar.abs(self).toUint()
+
+	def __neg__(self):
+		return Scalar.__neg__(self).toUint()
+
+	def __add__(self, val):
+		return Scalar.__add__(self, val).toUint()
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		return Scalar.__mul__(self, val).toUint()
+
+	def __div__(self, val):
+		return Scalar.__div__(self, val).toUint()
+
+class Vec(object):
+	@staticmethod
+	def fromScalarList(lst):
+		assert (len(lst) >= 1 and len(lst) <= 4)
+		if (len(lst) == 1):		return Scalar(lst[0])
+		elif (len(lst) == 2):	return Vec2(lst[0], lst[1])
+		elif (len(lst) == 3):	return Vec3(lst[0], lst[1], lst[2])
+		else:					return Vec4(lst[0], lst[1], lst[2], lst[3])
+
+	def isEqual(self, other):
+		assert isinstance(other, Vec);
+		return (self.getScalars() == other.getScalars())
+
+	def length(self):
+		return Scalar(math.sqrt(self.dot(self).x))
+
+	def normalize(self):
+		return self * Scalar(1.0 / self.length().x)
+
+	def swizzle(self, indexList):
+		inScalars = self.getScalars()
+		outScalars = map(lambda ndx: inScalars[ndx], indexList)
+		return Vec.fromScalarList(outScalars)
+
+	def __init__(self):
+		pass
+
+class Vec2(Vec):
+	def __init__(self, x, y):
+		assert(x.__class__ == y.__class__)
+		self.x = x
+		self.y = y
+
+	def applyUnary(self, func):			return Vec2(func(self.x), func(self.y))
+	def applyBinary(self, func, other):	return Vec2(func(self.x, other.x), func(self.y, other.y))
+
+	def expandVec(self, val):	return val.toVec2()
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.y)
+	def toVec3(self):			return Vec3(self.x, self.y, 0.0)
+	def toVec4(self):			return Vec4(self.x, self.y, 0.0, 0.0)
+	def toUVec2(self):			return UVec2(self.x, self.y)
+	def toUVec3(self):			return UVec3(self.x, self.y, 0.0)
+	def toUVec4(self):			return UVec4(self.x, self.y, 0.0, 0.0)
+	def toMat2(self):			return Mat2(float(self.x), 0.0, 0.0, float(self.y));
+
+	def toFloat(self):			return Vec2(float(self.x), float(self.y))
+	def toInt(self):			return Vec2(int(self.x), int(self.y))
+	def toUint(self):			return UVec2(int(self.x), int(self.y))
+	def toBool(self):			return Vec2(bool(self.x), bool(self.y))
+
+	def getNumScalars(self):	return 2
+	def getScalars(self):		return [self.x, self.y]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bvec2"
+		elif isinstance(self.x, int):
+			return "ivec2"
+		elif isinstance(self.x, float):
+			return "vec2"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ".xyxy"
+
+	def __str__(self):
+		if isinstance(self.x, bool):
+			return "bvec2(%s, %s)" % (str(self.x).lower(), str(self.y).lower())
+		elif isinstance(self.x, int):
+			return "ivec2(%i, %i)" % (self.x, self.y)
+		elif isinstance(self.x, float):
+			return "vec2(%s, %s)" % (self.x, self.y)
+		else:
+			assert False
+
+	def distance(self, v):
+		assert isinstance(v, Vec2)
+		return (self - v).length()
+
+	def dot(self, v):
+		assert isinstance(v, Vec2)
+		return Scalar(self.x*v.x + self.y*v.y)
+
+	def abs(self):
+		if isinstance(self.x, bool):
+			return Vec2(self.x, self.y)
+		else:
+			return Vec2(abs(self.x), abs(self.y))
+
+	def __neg__(self):
+		return Vec2(-self.x, -self.y)
+
+	def __add__(self, val):
+		if isinstance(val, Scalar):
+			return Vec2(self.x + val, self.y + val)
+		elif isinstance(val, Vec2):
+			return Vec2(self.x + val.x, self.y + val.y)
+		else:
+			assert False
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			val = val.toVec2()
+		assert isinstance(val, Vec2)
+		return Vec2(self.x * val.x, self.y * val.y)
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Vec2(self.x / val.x, self.y / val.x)
+		else:
+			assert isinstance(val, Vec2)
+			return Vec2(self.x / val.x, self.y / val.y)
+
+	def boolAny(self):	return Scalar(self.x or self.y)
+	def boolAll(self):	return Scalar(self.x and self.y)
+	def boolNot(self):	return Vec2(not self.x, not self.y)
+
+class UVec2(Vec2):
+	def __init__(self, x, y):
+		assert isinstance(x, int) and isinstance(y, int)
+		assert x >= 0 and y >= 0
+		Vec2.__init__(self, x, y)
+
+	def typeString(self):
+		return "uvec2"
+
+	def __str__(self):
+		return "uvec2(%i, %i)" % (self.x, self.y)
+
+	def abs(self):
+		return Vec2.abs(self).toUint()
+
+class Vec3(Vec):
+	def __init__(self, x, y, z):
+		assert((x.__class__ == y.__class__) and (x.__class__ == z.__class__))
+		self.x = x
+		self.y = y
+		self.z = z
+
+	def applyUnary(self, func):			return Vec3(func(self.x), func(self.y), func(self.z))
+	def applyBinary(self, func, other):	return Vec3(func(self.x, other.x), func(self.y, other.y), func(self.z, other.z))
+
+	def expandVec(self, val):	return val.toVec3()
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.y)
+	def toVec3(self):			return Vec3(self.x, self.y, self.z)
+	def toVec4(self):			return Vec4(self.x, self.y, self.z, 0.0)
+	def toUVec2(self):			return UVec2(self.x, self.y)
+	def toUVec3(self):			return UVec3(self.x, self.y, self.z)
+	def toUVec4(self):			return UVec4(self.x, self.y, self.z, 0.0)
+	def toMat3(self):			return Mat3(float(self.x), 0.0, 0.0,  0.0, float(self.y), 0.0,  0.0, 0.0, float(self.z));
+
+	def toFloat(self):			return Vec3(float(self.x), float(self.y), float(self.z))
+	def toInt(self):			return Vec3(int(self.x), int(self.y), int(self.z))
+	def toUint(self):			return UVec3(int(self.x), int(self.y), int(self.z))
+	def toBool(self):			return Vec3(bool(self.x), bool(self.y), bool(self.z))
+
+	def getNumScalars(self):	return 3
+	def getScalars(self):		return [self.x, self.y, self.z]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bvec3"
+		elif isinstance(self.x, int):
+			return "ivec3"
+		elif isinstance(self.x, float):
+			return "vec3"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ".xyzx"
+
+	def __str__(self):
+		if isinstance(self.x, bool):
+			return "bvec3(%s, %s, %s)" % (str(self.x).lower(), str(self.y).lower(), str(self.z).lower())
+		elif isinstance(self.x, int):
+			return "ivec3(%i, %i, %i)" % (self.x, self.y, self.z)
+		elif isinstance(self.x, float):
+			return "vec3(%s, %s, %s)" % (self.x, self.y, self.z)
+		else:
+			assert False
+
+	def distance(self, v):
+		assert isinstance(v, Vec3)
+		return (self - v).length()
+
+	def dot(self, v):
+		assert isinstance(v, Vec3)
+		return Scalar(self.x*v.x + self.y*v.y + self.z*v.z)
+
+	def cross(self, v):
+		assert isinstance(v, Vec3)
+		return Vec3(self.y*v.z - v.y*self.z,
+					self.z*v.x - v.z*self.x,
+					self.x*v.y - v.x*self.y)
+
+	def abs(self):
+		if isinstance(self.x, bool):
+			return Vec3(self.x, self.y, self.z)
+		else:
+			return Vec3(abs(self.x), abs(self.y), abs(self.z))
+
+	def __neg__(self):
+		return Vec3(-self.x, -self.y, -self.z)
+
+	def __add__(self, val):
+		if isinstance(val, Scalar):
+			return Vec3(self.x + val, self.y + val)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x + val.x, self.y + val.y, self.z + val.z)
+		else:
+			assert False
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			val = val.toVec3()
+		assert isinstance(val, Vec3)
+		return Vec3(self.x * val.x, self.y * val.y, self.z * val.z)
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Vec3(self.x / val.x, self.y / val.x, self.z / val.x)
+		elif isinstance(val, Vec3):
+			return Vec3(self.x / val.x, self.y / val.y, self.z / val.z)
+		else:
+			assert False
+
+	def boolAny(self):	return Scalar(self.x or self.y or self.z)
+	def boolAll(self):	return Scalar(self.x and self.y and self.z)
+	def boolNot(self):	return Vec3(not self.x, not self.y, not self.z)
+
+class UVec3(Vec3):
+	def __init__(self, x, y, z):
+		assert isinstance(x, int) and isinstance(y, int) and isinstance(z, int)
+		assert x >= 0 and y >= 0 and z >= 0
+		Vec3.__init__(self, x, y, z)
+
+	def typeString(self):
+		return "uvec3"
+
+	def __str__(self):
+		return "uvec3(%i, %i, %i)" % (self.x, self.y, self.z)
+
+	def abs(self):
+		return Vec3.abs(self).toUint()
+
+class Vec4(Vec):
+	def __init__(self, x, y, z, w):
+		assert((x.__class__ == y.__class__) and (x.__class__ == z.__class__) and (x.__class__ == w.__class__))
+		self.x = x
+		self.y = y
+		self.z = z
+		self.w = w
+
+	def applyUnary(self, func):			return Vec4(func(self.x), func(self.y), func(self.z), func(self.w))
+	def applyBinary(self, func, other):	return Vec4(func(self.x, other.x), func(self.y, other.y), func(self.z, other.z), func(self.w, other.w))
+
+	def expandVec(self, val):	return val.toVec4()
+	def toScalar(self):			return Scalar(self.x)
+	def toVec2(self):			return Vec2(self.x, self.y)
+	def toVec3(self):			return Vec3(self.x, self.y, self.z)
+	def toVec4(self):			return Vec4(self.x, self.y, self.z, self.w)
+	def toUVec2(self):			return UVec2(self.x, self.y)
+	def toUVec3(self):			return UVec3(self.x, self.y, self.z)
+	def toUVec4(self):			return UVec4(self.x, self.y, self.z, self.w)
+	def toMat2(self):			return Mat2(float(self.x), float(self.y), float(self.z), float(self.w))
+	def toMat4(self):			return Mat4(float(self.x), 0.0, 0.0, 0.0,  0.0, float(self.y), 0.0, 0.0,  0.0, 0.0, float(self.z), 0.0,  0.0, 0.0, 0.0, float(self.w));
+
+	def toFloat(self):			return Vec4(float(self.x), float(self.y), float(self.z), float(self.w))
+	def toInt(self):			return Vec4(int(self.x), int(self.y), int(self.z), int(self.w))
+	def toUint(self):			return UVec4(int(self.x), int(self.y), int(self.z), int(self.w))
+	def toBool(self):			return Vec4(bool(self.x), bool(self.y), bool(self.z), bool(self.w))
+
+	def getNumScalars(self):	return 4
+	def getScalars(self):		return [self.x, self.y, self.z, self.w]
+
+	def typeString(self):
+		if isinstance(self.x, bool):
+			return "bvec4"
+		elif isinstance(self.x, int):
+			return "ivec4"
+		elif isinstance(self.x, float):
+			return "vec4"
+		else:
+			assert False
+
+	def vec4Swizzle(self):
+		return ""
+
+	def __str__(self):
+		if isinstance(self.x, bool):
+			return "bvec4(%s, %s, %s, %s)" % (str(self.x).lower(), str(self.y).lower(), str(self.z).lower(), str(self.w).lower())
+		elif isinstance(self.x, int):
+			return "ivec4(%i, %i, %i, %i)" % (self.x, self.y, self.z, self.w)
+		elif isinstance(self.x, float):
+			return "vec4(%s, %s, %s, %s)" % (self.x, self.y, self.z, self.w)
+		else:
+			assert False
+
+	def distance(self, v):
+		assert isinstance(v, Vec4)
+		return (self - v).length()
+
+	def dot(self, v):
+		assert isinstance(v, Vec4)
+		return Scalar(self.x*v.x + self.y*v.y + self.z*v.z + self.w*v.w)
+
+	def abs(self):
+		if isinstance(self.x, bool):
+			return Vec4(self.x, self.y, self.z, self.w)
+		else:
+			return Vec4(abs(self.x), abs(self.y), abs(self.z), abs(self.w))
+
+	def __neg__(self):
+		return Vec4(-self.x, -self.y, -self.z, -self.w)
+
+	def __add__(self, val):
+		if isinstance(val, Scalar):
+			return Vec3(self.x + val, self.y + val)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x + val.x, self.y + val.y, self.z + val.z, self.w + val.w)
+		else:
+			assert False
+
+	def __sub__(self, val):
+		return self + (-val)
+
+	def __mul__(self, val):
+		if isinstance(val, Scalar):
+			val = val.toVec4()
+		assert isinstance(val, Vec4)
+		return Vec4(self.x * val.x, self.y * val.y, self.z * val.z, self.w * val.w)
+
+	def __div__(self, val):
+		if isinstance(val, Scalar):
+			return Vec4(self.x / val.x, self.y / val.x, self.z / val.x, self.w / val.x)
+		elif isinstance(val, Vec4):
+			return Vec4(self.x / val.x, self.y / val.y, self.z / val.z, self.w / val.w)
+		else:
+			assert False
+
+	def boolAny(self):	return Scalar(self.x or self.y or self.z or self.w)
+	def boolAll(self):	return Scalar(self.x and self.y and self.z and self.w)
+	def boolNot(self):	return Vec4(not self.x, not self.y, not self.z, not self.w)
+
+class UVec4(Vec4):
+	def __init__(self, x, y, z, w):
+		assert isinstance(x, int) and isinstance(y, int) and isinstance(z, int) and isinstance(w, int)
+		assert x >= 0 and y >= 0 and z >= 0 and w >= 0
+		Vec4.__init__(self, x, y, z, w)
+
+	def typeString(self):
+		return "uvec4"
+
+	def __str__(self):
+		return "uvec4(%i, %i, %i, %i)" % (self.x, self.y, self.z, self.w)
+
+	def abs(self):
+		return Vec4.abs(self).toUint()
+
+# \note Column-major storage.
+class Mat(object):
+	def __init__ (self, numCols, numRows, scalars):
+		assert len(scalars) == numRows*numCols
+		self.numCols	= numCols
+		self.numRows	= numRows
+		self.scalars	= scalars
+
+	@staticmethod
+	def fromScalar (numCols, numRows, scalar):
+		scalars = []
+		for col in range(0, numCols):
+			for row in range(0, numRows):
+				scalars.append(scalar if col == row else 0.0)
+		return Mat(numCols, numRows, scalars)
+
+	@staticmethod
+	def identity (numCols, numRows):
+		return Mat.fromScalar(numCols, numRows, 1.0)
+
+	def get (self, colNdx, rowNdx):
+		assert 0 <= colNdx and colNdx < self.numCols
+		assert 0 <= rowNdx and rowNdx < self.numRows
+		return self.scalars[colNdx*self.numRows + rowNdx]
+
+	def set (self, colNdx, rowNdx, scalar):
+		assert 0 <= colNdx and colNdx < self.numCols
+		assert 0 <= rowNdx and rowNdx < self.numRows
+		self.scalars[colNdx*self.numRows + rowNdx] = scalar
+
+	def toMatrix (self, numCols, numRows):
+		res = Mat.identity(numCols, numRows)
+		for col in range(0, min(self.numCols, numCols)):
+			for row in range(0, min(self.numRows, numRows)):
+				res.set(col, row, self.get(col, row))
+		return res
+
+	def toMat2 (self):		return self.toMatrix(2, 2)
+	def toMat2x3 (self):	return self.toMatrix(2, 3)
+	def toMat2x4 (self):	return self.toMatrix(2, 4)
+	def toMat3x2 (self):	return self.toMatrix(3, 2)
+	def toMat3 (self):		return self.toMatrix(3, 3)
+	def toMat3x4 (self):	return self.toMatrix(3, 4)
+	def toMat4x2 (self):	return self.toMatrix(4, 2)
+	def toMat4x3 (self):	return self.toMatrix(4, 3)
+	def toMat4 (self):		return self.toMatrix(4, 4)
+
+	def typeString(self):
+		if self.numRows == self.numCols:
+			return "mat%d" % self.numRows
+		else:
+			return "mat%dx%d" % (self.numCols, self.numRows)
+
+	def __str__(self):
+		return "%s(%s)" % (self.typeString(), ", ".join(["%s" % s for s in self.scalars]))
+
+	def isTypeEqual (self, other):
+		return isinstance(other, Mat) and self.numRows == other.numRows and self.numCols == other.numCols
+
+	def isEqual(self, other):
+		assert self.isTypeEqual(other)
+		return (self.scalars == other.scalars)
+
+	def compMul(self, val):
+		assert self.isTypeEqual(val)
+		return Mat(self.numRows, self.numCols, [self.scalars(i) * val.scalars(i) for i in range(self.numRows*self.numCols)])
+
+class Mat2(Mat):
+	def __init__(self, m00, m01, m10, m11):
+		Mat.__init__(self, 2, 2, [m00, m10, m01, m11])
+
+class Mat3(Mat):
+	def __init__(self, m00, m01, m02, m10, m11, m12, m20, m21, m22):
+		Mat.__init__(self, 3, 3, [m00, m10, m20,
+								  m01, m11, m21,
+								  m02, m12, m22])
+
+class Mat4(Mat):
+	def __init__(self, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33):
+		Mat.__init__(self, 4, 4, [m00, m10, m20, m30,
+								  m01, m11, m21, m31,
+								  m02, m12, m22, m32,
+								  m03, m13, m23, m33])
diff --git a/modules/gles31/stress/CMakeLists.txt b/modules/gles31/stress/CMakeLists.txt
new file mode 100644
index 0000000..3bed4e3
--- /dev/null
+++ b/modules/gles31/stress/CMakeLists.txt
@@ -0,0 +1,15 @@
+# dEQP-GLES31.stress
+
+set(DEQP_GLES31_STRESS_SRCS
+	es31sDrawTests.cpp
+	es31sDrawTests.hpp
+	es31sStressTests.cpp
+	es31sStressTests.hpp
+	es31sVertexAttributeBindingTests.cpp
+	es31sVertexAttributeBindingTests.hpp
+	es31sTessellationGeometryInteractionTests.cpp
+	es31sTessellationGeometryInteractionTests.hpp
+	)
+
+add_library(deqp-gles31-stress STATIC ${DEQP_GLES31_STRESS_SRCS})
+target_link_libraries(deqp-gles31-stress deqp-gl-shared glutil tcutil ${DEQP_GLES31_LIBRARIES})
diff --git a/modules/gles31/stress/es31sDrawTests.cpp b/modules/gles31/stress/es31sDrawTests.cpp
new file mode 100644
index 0000000..7af5c2a
--- /dev/null
+++ b/modules/gles31/stress/es31sDrawTests.cpp
@@ -0,0 +1,598 @@
+
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Drawing stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31sDrawTests.hpp"
+#include "glsDrawTest.hpp"
+#include "gluRenderContext.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+
+#include <set>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Stress
+{
+namespace
+{
+
+static const char* s_colorVertexShaderSource =		"#version 310 es\n"
+													"in highp vec4 a_position;\n"
+													"in highp vec4 a_color;\n"
+													"out highp vec4 v_color;\n"
+													"void main (void)\n"
+													"{\n"
+													"	gl_Position = a_position;\n"
+													"	v_color = a_color;\n"
+													"}\n";
+static const char* s_colorFragmentShaderSource	=	"#version 310 es\n"
+													"layout(location = 0) out highp vec4 fragColor;\n"
+													"in highp vec4 v_color;\n"
+													"void main (void)\n"
+													"{\n"
+													"	fragColor = v_color;\n"
+													"}\n";
+struct DrawElementsCommand
+{
+	deUint32 count;
+	deUint32 primCount;
+	deUint32 firstIndex;
+	deInt32  baseVertex;
+	deUint32 reservedMustBeZero;
+};
+DE_STATIC_ASSERT(5 * sizeof(deUint32) == sizeof(DrawElementsCommand)); // tight packing
+
+struct DrawArraysCommand
+{
+	deUint32 count;
+	deUint32 primCount;
+	deUint32 first;
+	deUint32 reservedMustBeZero;
+};
+DE_STATIC_ASSERT(4 * sizeof(deUint32) == sizeof(DrawArraysCommand)); // tight packing
+
+class InvalidDrawCase : public TestCase
+{
+public:
+	enum DrawType
+	{
+		DRAW_ARRAYS,
+		DRAW_ELEMENTS,
+
+		DRAW_LAST
+	};
+	enum InvalidOperation
+	{
+		INVALID_DATA_COUNT = 0,
+		INVALID_DATA_FIRST,
+		INVALID_DATA_INSTANCED,
+		INVALID_INDEX_COUNT,
+		INVALID_INDEX_FIRST,
+		INVALID_RESERVED,
+		INVALID_INDEX,
+
+		INVALID_LAST
+	};
+
+							InvalidDrawCase		(Context& context, const char* name, const char* desc, DrawType type, InvalidOperation op);
+							~InvalidDrawCase	(void);
+
+	void					init				(void);
+	void					deinit				(void);
+	IterateResult			iterate				(void);
+
+private:
+	const DrawType			m_drawType;
+	const InvalidOperation	m_op;
+	glw::GLuint				m_dataBufferID;
+	glw::GLuint				m_indexBufferID;
+	glw::GLuint				m_cmdBufferID;
+	glw::GLuint				m_colorBufferID;
+	glw::GLuint				m_vao;
+};
+
+InvalidDrawCase::InvalidDrawCase (Context& context, const char* name, const char* desc, DrawType type, InvalidOperation op)
+	: TestCase			(context, name, desc)
+	, m_drawType		(type)
+	, m_op				(op)
+	, m_dataBufferID	(0)
+	, m_indexBufferID	(0)
+	, m_cmdBufferID		(0)
+	, m_colorBufferID	(0)
+	, m_vao				(0)
+{
+	DE_ASSERT(type < DRAW_LAST);
+	DE_ASSERT(op < INVALID_LAST);
+}
+
+InvalidDrawCase::~InvalidDrawCase (void)
+{
+	deinit();
+}
+
+void InvalidDrawCase::init (void)
+{
+}
+
+void InvalidDrawCase::deinit (void)
+{
+	if (m_dataBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_dataBufferID);
+		m_dataBufferID = 0;
+	}
+	if (m_indexBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_indexBufferID);
+		m_indexBufferID = 0;
+	}
+	if (m_cmdBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_cmdBufferID);
+		m_cmdBufferID = 0;
+	}
+	if (m_colorBufferID)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_colorBufferID);
+		m_colorBufferID = 0;
+	}
+	if (m_vao)
+	{
+		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vao);
+		m_vao = 0;
+	}
+}
+
+InvalidDrawCase::IterateResult InvalidDrawCase::iterate (void)
+{
+	const int drawCount				= 10;		//!< number of elements safe to draw (all buffers have this)
+	const int overBoundDrawCount	= 10000;	//!< number of elements in all other buffers than our target buffer
+	const int drawInstances			= 1;
+	const int overBoundInstances	= 1000;
+
+	glu::CallLogWrapper gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	glu::ShaderProgram	program			(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_colorVertexShaderSource) << glu::FragmentSource(s_colorFragmentShaderSource));
+	const deUint32		programID		= program.getProgram();
+	const deInt32		posLocation		= gl.glGetAttribLocation(programID, "a_position");
+	const deInt32		colorLocation	= gl.glGetAttribLocation(programID, "a_color");
+
+	gl.enableLogging(true);
+
+	gl.glGenVertexArrays(1, &m_vao);
+	gl.glBindVertexArray(m_vao);
+	glu::checkError(gl.glGetError(), "gen vao", __FILE__, __LINE__);
+
+	// indices
+	if (m_drawType == DRAW_ELEMENTS)
+	{
+		const int				indexBufferSize = (m_op == INVALID_INDEX_COUNT) ? (drawCount) : (overBoundDrawCount);
+		std::vector<deUint16>	indices			(indexBufferSize);
+
+		for (int ndx = 0; ndx < (int)indices.size(); ++ndx)
+			indices[ndx] = (m_op == INVALID_INDEX) ? (overBoundDrawCount + ndx) : (ndx);
+
+		gl.glGenBuffers(1, &m_indexBufferID);
+		gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferID);
+		gl.glBufferData(GL_ELEMENT_ARRAY_BUFFER, (int)(indices.size() * sizeof(deUint16)), &indices[0], GL_STATIC_DRAW);
+		glu::checkError(gl.glGetError(), "", __FILE__, __LINE__);
+	}
+
+	// data
+	{
+		const int dataSize = (m_op == INVALID_DATA_COUNT) ? (drawCount) : (overBoundDrawCount);
+
+		// any data is ok
+		gl.glGenBuffers(1, &m_dataBufferID);
+		gl.glBindBuffer(GL_ARRAY_BUFFER, m_dataBufferID);
+		gl.glBufferData(GL_ARRAY_BUFFER, dataSize * sizeof(float[4]), DE_NULL, GL_STATIC_DRAW);
+		gl.glVertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		gl.glEnableVertexAttribArray(posLocation);
+		glu::checkError(gl.glGetError(), "", __FILE__, __LINE__);
+	}
+
+	// potentially instanced data
+	{
+		const int dataSize = drawInstances;
+
+		gl.glGenBuffers(1, &m_colorBufferID);
+		gl.glBindBuffer(GL_ARRAY_BUFFER, m_colorBufferID);
+		gl.glBufferData(GL_ARRAY_BUFFER, dataSize * sizeof(float[4]), DE_NULL, GL_STATIC_DRAW);
+		gl.glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		gl.glEnableVertexAttribArray(colorLocation);
+		gl.glVertexAttribDivisor(colorLocation, 1);
+		glu::checkError(gl.glGetError(), "", __FILE__, __LINE__);
+	}
+
+	// command
+	if (m_drawType == DRAW_ARRAYS)
+	{
+		DrawArraysCommand drawCommand;
+		drawCommand.count				= overBoundDrawCount;
+		drawCommand.primCount			= (m_op == INVALID_DATA_INSTANCED)	? (overBoundInstances)	: (drawInstances);
+		drawCommand.first				= (m_op == INVALID_DATA_FIRST)		? (overBoundDrawCount)	: (0);
+		drawCommand.reservedMustBeZero	= (m_op == INVALID_RESERVED)		? (5)					: (0);
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "drawCommand:"
+			<< "\n\tcount:\t" << drawCommand.count
+			<< "\n\tprimCount\t" << drawCommand.primCount
+			<< "\n\tfirst\t" << drawCommand.first
+			<< "\n\treservedMustBeZero\t" << drawCommand.reservedMustBeZero
+			<< tcu::TestLog::EndMessage;
+
+		gl.glGenBuffers(1, &m_cmdBufferID);
+		gl.glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_cmdBufferID);
+		gl.glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(drawCommand), &drawCommand, GL_STATIC_DRAW);
+		glu::checkError(gl.glGetError(), "", __FILE__, __LINE__);
+	}
+	else if (m_drawType == DRAW_ELEMENTS)
+	{
+		DrawElementsCommand drawCommand;
+		drawCommand.count				= overBoundDrawCount;
+		drawCommand.primCount			= (m_op == INVALID_DATA_INSTANCED)	? (overBoundInstances)	: (drawInstances);
+		drawCommand.firstIndex			= (m_op == INVALID_INDEX_FIRST)		? (overBoundDrawCount)	: (0);
+		drawCommand.baseVertex			= (m_op == INVALID_DATA_FIRST)		? (overBoundDrawCount)	: (0);
+		drawCommand.reservedMustBeZero	= (m_op == INVALID_RESERVED)		? (5)					: (0);
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "drawCommand:"
+			<< "\n\tcount:\t" << drawCommand.count
+			<< "\n\tprimCount\t" << drawCommand.primCount
+			<< "\n\tfirstIndex\t" << drawCommand.firstIndex
+			<< "\n\tbaseVertex\t" << drawCommand.baseVertex
+			<< "\n\treservedMustBeZero\t" << drawCommand.reservedMustBeZero
+			<< tcu::TestLog::EndMessage;
+
+		gl.glGenBuffers(1, &m_cmdBufferID);
+		gl.glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_cmdBufferID);
+		gl.glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(drawCommand), &drawCommand, GL_STATIC_DRAW);
+		glu::checkError(gl.glGetError(), "", __FILE__, __LINE__);
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	gl.glViewport(0, 0, 1, 1);
+	gl.glUseProgram(programID);
+
+	if (m_drawType == DRAW_ELEMENTS)
+		gl.glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, DE_NULL);
+	else if (m_drawType == DRAW_ARRAYS)
+		gl.glDrawArraysIndirect(GL_TRIANGLES, DE_NULL);
+	else
+		DE_ASSERT(DE_FALSE);
+
+	gl.glUseProgram(0);
+	gl.glFinish();
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+class RandomGroup : public TestCaseGroup
+{
+public:
+			RandomGroup		(Context& context, const char* name, const char* descr);
+			~RandomGroup	(void);
+
+	void	init			(void);
+};
+
+template <int SIZE>
+struct UniformWeightArray
+{
+	float weights[SIZE];
+
+	UniformWeightArray (void)
+	{
+		for (int i=0; i<SIZE; ++i)
+			weights[i] = 1.0f;
+	}
+};
+
+RandomGroup::RandomGroup (Context& context, const char* name, const char* descr)
+	: TestCaseGroup	(context, name, descr)
+{
+}
+
+RandomGroup::~RandomGroup (void)
+{
+}
+
+void RandomGroup::init (void)
+{
+	const int	numAttempts				= 100;
+
+	const int	attribCounts[]			= { 1,   2,   5 };
+	const float	attribWeights[]			= { 30, 10,   1 };
+	const int	primitiveCounts[]		= { 1,   5,  64 };
+	const float	primitiveCountWeights[]	= { 20, 10,   1 };
+	const int	indexOffsets[]			= { 0,   7,  13 };
+	const float	indexOffsetWeights[]	= { 20, 20,   1 };
+	const int	firsts[]				= { 0,   7,  13 };
+	const float	firstWeights[]			= { 20, 20,   1 };
+
+	const int	instanceCounts[]		= { 1,   2,  16,  17 };
+	const float	instanceWeights[]		= { 20, 10,   5,   1 };
+	const int	indexMins[]				= { 0,   1,   3,   8 };
+	const int	indexMaxs[]				= { 4,   8, 128, 257 };
+	const float	indexWeights[]			= { 50, 50,  50,  50 };
+	const int	offsets[]				= { 0,   1,   5,  12 };
+	const float	offsetWeights[]			= { 50, 10,  10,  10 };
+	const int	strides[]				= { 0,   7,  16,  17 };
+	const float	strideWeights[]			= { 50, 10,  10,  10 };
+	const int	instanceDivisors[]		= { 0,   1,   3, 129 };
+	const float	instanceDivisorWeights[]= { 70, 30,  10,  10 };
+
+	const int	indirectOffsets[]		= { 0,   1,   2 };
+	const float indirectOffsetWeigths[]	= { 2,   1,   1 };
+	const int	baseVertices[]			= { 0,   1,  -2,   4,  3 };
+	const float baseVertexWeigths[]		= { 4,   1,   1,   1,  1 };
+
+	gls::DrawTestSpec::Primitive primitives[] =
+	{
+		gls::DrawTestSpec::PRIMITIVE_POINTS,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN,
+		gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINES,
+		gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
+		gls::DrawTestSpec::PRIMITIVE_LINE_LOOP
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(primitives)> primitiveWeights;
+
+	gls::DrawTestSpec::DrawMethod drawMethods[] =
+	{
+		gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT,
+		gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(drawMethods)> drawMethodWeights;
+
+	gls::DrawTestSpec::IndexType indexTypes[] =
+	{
+		gls::DrawTestSpec::INDEXTYPE_BYTE,
+		gls::DrawTestSpec::INDEXTYPE_SHORT,
+		gls::DrawTestSpec::INDEXTYPE_INT,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(indexTypes)> indexTypeWeights;
+
+	gls::DrawTestSpec::InputType inputTypes[] =
+	{
+		gls::DrawTestSpec::INPUTTYPE_FLOAT,
+		gls::DrawTestSpec::INPUTTYPE_FIXED,
+		gls::DrawTestSpec::INPUTTYPE_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_SHORT,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT,
+		gls::DrawTestSpec::INPUTTYPE_INT,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT,
+		gls::DrawTestSpec::INPUTTYPE_HALF,
+		gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		gls::DrawTestSpec::INPUTTYPE_INT_2_10_10_10,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(inputTypes)> inputTypeWeights;
+
+	gls::DrawTestSpec::OutputType outputTypes[] =
+	{
+		gls::DrawTestSpec::OUTPUTTYPE_FLOAT,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_VEC4,
+		gls::DrawTestSpec::OUTPUTTYPE_INT,
+		gls::DrawTestSpec::OUTPUTTYPE_UINT,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_IVEC4,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC2,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC3,
+		gls::DrawTestSpec::OUTPUTTYPE_UVEC4,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(outputTypes)> outputTypeWeights;
+
+	gls::DrawTestSpec::Usage usages[] =
+	{
+		gls::DrawTestSpec::USAGE_DYNAMIC_DRAW,
+		gls::DrawTestSpec::USAGE_STATIC_DRAW,
+		gls::DrawTestSpec::USAGE_STREAM_DRAW,
+		gls::DrawTestSpec::USAGE_STREAM_READ,
+		gls::DrawTestSpec::USAGE_STREAM_COPY,
+		gls::DrawTestSpec::USAGE_STATIC_READ,
+		gls::DrawTestSpec::USAGE_STATIC_COPY,
+		gls::DrawTestSpec::USAGE_DYNAMIC_READ,
+		gls::DrawTestSpec::USAGE_DYNAMIC_COPY,
+	};
+	const UniformWeightArray<DE_LENGTH_OF_ARRAY(usages)> usageWeights;
+
+	std::set<deUint32>	insertedHashes;
+	size_t				insertedCount = 0;
+
+	for (int ndx = 0; ndx < numAttempts; ++ndx)
+	{
+		de::Random random(0xc551393 + ndx); // random does not depend on previous cases
+
+		int					attributeCount = random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(attribCounts), DE_ARRAY_END(attribCounts), attribWeights);
+		int					drawCommandSize;
+		gls::DrawTestSpec	spec;
+
+		spec.apiType				= glu::ApiType::es(3,1);
+		spec.primitive				= random.chooseWeighted<gls::DrawTestSpec::Primitive>	(DE_ARRAY_BEGIN(primitives),		DE_ARRAY_END(primitives),		primitiveWeights.weights);
+		spec.primitiveCount			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(primitiveCounts),	DE_ARRAY_END(primitiveCounts),	primitiveCountWeights);
+		spec.drawMethod				= random.chooseWeighted<gls::DrawTestSpec::DrawMethod>	(DE_ARRAY_BEGIN(drawMethods),		DE_ARRAY_END(drawMethods),		drawMethodWeights.weights);
+
+		if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT)
+			drawCommandSize = sizeof(deUint32[4]);
+		else if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT)
+			drawCommandSize = sizeof(deUint32[5]);
+		else
+		{
+			DE_ASSERT(DE_FALSE);
+			return;
+		}
+
+		spec.indexType				= random.chooseWeighted<gls::DrawTestSpec::IndexType>	(DE_ARRAY_BEGIN(indexTypes),		DE_ARRAY_END(indexTypes),		indexTypeWeights.weights);
+		spec.indexPointerOffset		= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexOffsets),		DE_ARRAY_END(indexOffsets),		indexOffsetWeights);
+		spec.indexStorage			= gls::DrawTestSpec::STORAGE_BUFFER;
+		spec.first					= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(firsts),			DE_ARRAY_END(firsts),			firstWeights);
+		spec.indexMin				= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexMins),			DE_ARRAY_END(indexMins),		indexWeights);
+		spec.indexMax				= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indexMaxs),			DE_ARRAY_END(indexMaxs),		indexWeights);
+		spec.instanceCount			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(instanceCounts),	DE_ARRAY_END(instanceCounts),	instanceWeights);
+		spec.indirectOffset			= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(indirectOffsets),	DE_ARRAY_END(indirectOffsets),	indirectOffsetWeigths) * drawCommandSize;
+		spec.baseVertex				= random.chooseWeighted<int, const int*, const float*>	(DE_ARRAY_BEGIN(baseVertices),		DE_ARRAY_END(baseVertices),		baseVertexWeigths);
+
+		// check spec is legal
+		if (!spec.valid())
+			continue;
+
+		for (int attrNdx = 0; attrNdx < attributeCount;)
+		{
+			bool valid;
+			gls::DrawTestSpec::AttributeSpec attribSpec;
+
+			attribSpec.inputType			= random.chooseWeighted<gls::DrawTestSpec::InputType>	(DE_ARRAY_BEGIN(inputTypes),		DE_ARRAY_END(inputTypes),		inputTypeWeights.weights);
+			attribSpec.outputType			= random.chooseWeighted<gls::DrawTestSpec::OutputType>	(DE_ARRAY_BEGIN(outputTypes),		DE_ARRAY_END(outputTypes),		outputTypeWeights.weights);
+			attribSpec.storage				= gls::DrawTestSpec::STORAGE_BUFFER;
+			attribSpec.usage				= random.chooseWeighted<gls::DrawTestSpec::Usage>		(DE_ARRAY_BEGIN(usages),			DE_ARRAY_END(usages),			usageWeights.weights);
+			attribSpec.componentCount		= random.getInt(1, 4);
+			attribSpec.offset				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), offsetWeights);
+			attribSpec.stride				= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(strides), DE_ARRAY_END(strides), strideWeights);
+			attribSpec.normalize			= random.getBool();
+			attribSpec.instanceDivisor		= random.chooseWeighted<int, const int*, const float*>(DE_ARRAY_BEGIN(instanceDivisors), DE_ARRAY_END(instanceDivisors), instanceDivisorWeights);
+			attribSpec.useDefaultAttribute	= random.getBool();
+
+			// check spec is legal
+			valid = attribSpec.valid(spec.apiType);
+
+			// we do not want interleaved elements. (Might result in some weird floating point values)
+			if (attribSpec.stride && attribSpec.componentCount * gls::DrawTestSpec::inputTypeSize(attribSpec.inputType) > attribSpec.stride)
+				valid = false;
+
+			// try again if not valid
+			if (valid)
+			{
+				spec.attribs.push_back(attribSpec);
+				++attrNdx;
+			}
+		}
+
+		// Do not collapse all vertex positions to a single positions
+		if (spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+			spec.attribs[0].instanceDivisor = 0;
+
+		// Is render result meaningful?
+		{
+			// Only one vertex
+			if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && spec.indexMin == spec.indexMax && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+			if (spec.attribs[0].useDefaultAttribute && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
+				continue;
+
+			// Triangle only on one axis
+			if (spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLES || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN || spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP)
+			{
+				if (spec.attribs[0].componentCount == 1)
+					continue;
+				if (spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_FLOAT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_INT || spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_UINT)
+					continue;
+				if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED && (spec.indexMax - spec.indexMin) < 2)
+					continue;
+			}
+		}
+
+		// Add case
+		{
+			deUint32 hash = spec.hash();
+			for (int attrNdx = 0; attrNdx < attributeCount; ++attrNdx)
+				hash = (hash << 2) ^ (deUint32)spec.attribs[attrNdx].hash();
+
+			if (insertedHashes.find(hash) == insertedHashes.end())
+			{
+				// Only unaligned cases
+				if (spec.isCompatibilityTest() == gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET ||
+					spec.isCompatibilityTest() == gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+					this->addChild(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), spec, de::toString(insertedCount).c_str(), spec.getDesc().c_str()));
+				insertedHashes.insert(hash);
+
+				++insertedCount;
+			}
+		}
+	}
+}
+
+} // anonymous
+
+DrawTests::DrawTests (Context& context)
+	: TestCaseGroup(context, "draw_indirect", "Indirect drawing tests")
+{
+}
+
+DrawTests::~DrawTests (void)
+{
+}
+
+void DrawTests::init (void)
+{
+	tcu::TestCaseGroup* const unalignedGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned_data", "Test with unaligned data");
+	tcu::TestCaseGroup* const drawArraysGroup	= new tcu::TestCaseGroup(m_testCtx, "drawarrays", "draw arrays");
+	tcu::TestCaseGroup* const drawElementsGroup	= new tcu::TestCaseGroup(m_testCtx, "drawelements", "draw elements");
+
+	addChild(unalignedGroup);
+	addChild(drawArraysGroup);
+	addChild(drawElementsGroup);
+
+	// .unaligned_data
+	{
+		unalignedGroup->addChild(new RandomGroup(m_context, "random", "random draw commands."));
+	}
+
+	// .drawarrays
+	{
+		drawArraysGroup->addChild(new InvalidDrawCase(m_context, "data_over_bounds_with_count",			"Draw arrays vertex elements beyond the array end are accessed",	InvalidDrawCase::DRAW_ARRAYS,	InvalidDrawCase::INVALID_DATA_COUNT));
+		drawArraysGroup->addChild(new InvalidDrawCase(m_context, "data_over_bounds_with_first",			"Draw arrays vertex elements beyond the array end are accessed",	InvalidDrawCase::DRAW_ARRAYS,	InvalidDrawCase::INVALID_DATA_FIRST));
+		drawArraysGroup->addChild(new InvalidDrawCase(m_context, "data_over_bounds_with_primcount",		"Draw arrays vertex elements beyond the array end are accessed",	InvalidDrawCase::DRAW_ARRAYS,	InvalidDrawCase::INVALID_DATA_INSTANCED));
+		drawArraysGroup->addChild(new InvalidDrawCase(m_context, "reserved_non_zero",					"reservedMustBeZero is set to non-zero value",						InvalidDrawCase::DRAW_ARRAYS,	InvalidDrawCase::INVALID_RESERVED));
+	}
+
+	// .drawelements
+	{
+		drawElementsGroup->addChild(new InvalidDrawCase(m_context, "data_over_bounds_with_count",		"Draw elements vertex elements beyond the array end are accessed",	InvalidDrawCase::DRAW_ELEMENTS, InvalidDrawCase::INVALID_DATA_COUNT));
+		drawElementsGroup->addChild(new InvalidDrawCase(m_context, "data_over_bounds_with_basevertex",	"Draw elements vertex elements beyond the array end are accessed",	InvalidDrawCase::DRAW_ELEMENTS,	InvalidDrawCase::INVALID_DATA_FIRST));
+		drawElementsGroup->addChild(new InvalidDrawCase(m_context, "data_over_bounds_with_indices",		"Draw elements vertex elements beyond the array end are accessed",	InvalidDrawCase::DRAW_ELEMENTS,	InvalidDrawCase::INVALID_INDEX));
+		drawElementsGroup->addChild(new InvalidDrawCase(m_context, "data_over_bounds_with_primcount",	"Draw elements vertex elements beyond the array end are accessed",	InvalidDrawCase::DRAW_ELEMENTS,	InvalidDrawCase::INVALID_DATA_INSTANCED));
+		drawElementsGroup->addChild(new InvalidDrawCase(m_context, "index_over_bounds_with_count",		"Draw elements index elements beyond the array end are accessed",	InvalidDrawCase::DRAW_ELEMENTS,	InvalidDrawCase::INVALID_INDEX_COUNT));
+		drawElementsGroup->addChild(new InvalidDrawCase(m_context, "index_over_bounds_with_firstindex",	"Draw elements index elements beyond the array end are accessed",	InvalidDrawCase::DRAW_ELEMENTS,	InvalidDrawCase::INVALID_INDEX_FIRST));
+		drawElementsGroup->addChild(new InvalidDrawCase(m_context, "reserved_non_zero",					"reservedMustBeZero is set to non-zero value",						InvalidDrawCase::DRAW_ELEMENTS,	InvalidDrawCase::INVALID_RESERVED));
+	}
+}
+
+} // Stress
+} // gles31
+} // deqp
\ No newline at end of file
diff --git a/modules/gles31/stress/es31sDrawTests.hpp b/modules/gles31/stress/es31sDrawTests.hpp
new file mode 100644
index 0000000..09efff8
--- /dev/null
+++ b/modules/gles31/stress/es31sDrawTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31SDRAWTESTS_HPP
+#define _ES31SDRAWTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Drawing stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Stress
+{
+
+class DrawTests : public TestCaseGroup
+{
+public:
+						DrawTests		(Context& context);
+						~DrawTests		(void);
+
+	void				init			(void);
+
+private:
+						DrawTests		(const DrawTests& other);
+	DrawTests&			operator=		(const DrawTests& other);
+};
+
+} // Stress
+} // gles31
+} // deqp
+
+#endif // _ES31SDRAWTESTS_HPP
diff --git a/modules/gles31/stress/es31sStressTests.cpp b/modules/gles31/stress/es31sStressTests.cpp
new file mode 100644
index 0000000..5c80135
--- /dev/null
+++ b/modules/gles31/stress/es31sStressTests.cpp
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31sStressTests.hpp"
+
+#include "es31sDrawTests.hpp"
+#include "es31sVertexAttributeBindingTests.hpp"
+#include "es31sTessellationGeometryInteractionTests.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Stress
+{
+
+StressTests::StressTests (Context& context)
+	: TestCaseGroup(context, "stress", "Stress tests")
+{
+}
+
+StressTests::~StressTests (void)
+{
+}
+
+void StressTests::init (void)
+{
+	addChild(new DrawTests								(m_context));
+	addChild(new VertexAttributeBindingTests			(m_context));
+	addChild(new TessellationGeometryInteractionTests	(m_context));
+}
+
+} // Stress
+} // gles31
+} // deqp
diff --git a/modules/gles31/stress/es31sStressTests.hpp b/modules/gles31/stress/es31sStressTests.hpp
new file mode 100644
index 0000000..53b3385
--- /dev/null
+++ b/modules/gles31/stress/es31sStressTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31SSTRESSTESTS_HPP
+#define _ES31SSTRESSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Stress
+{
+
+class StressTests : public TestCaseGroup
+{
+public:
+					StressTests		(Context& context);
+					~StressTests	(void);
+
+	void			init			(void);
+
+private:
+					StressTests		(const StressTests& other);
+	StressTests&	operator=		(const StressTests& other);
+};
+
+} // Stress
+} // gles31
+} // deqp
+
+#endif // _ES31SSTRESSTESTS_HPP
diff --git a/modules/gles31/stress/es31sTessellationGeometryInteractionTests.cpp b/modules/gles31/stress/es31sTessellationGeometryInteractionTests.cpp
new file mode 100644
index 0000000..6314c46
--- /dev/null
+++ b/modules/gles31/stress/es31sTessellationGeometryInteractionTests.cpp
@@ -0,0 +1,633 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tessellation and geometry shader interaction stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31sTessellationGeometryInteractionTests.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluContextInfo.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+
+#include <sstream>
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Stress
+{
+namespace
+{
+
+class AllowedRenderFailureException : public std::runtime_error
+{
+public:
+	AllowedRenderFailureException (const char* message) : std::runtime_error(message) { }
+};
+
+class GridRenderCase : public TestCase
+{
+public:
+	enum Flags
+	{
+		FLAG_TESSELLATION_MAX_SPEC						= 0x0001,
+		FLAG_TESSELLATION_MAX_IMPLEMENTATION			= 0x0002,
+		FLAG_GEOMETRY_MAX_SPEC							= 0x0004,
+		FLAG_GEOMETRY_MAX_IMPLEMENTATION				= 0x0008,
+		FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC				= 0x0010,
+		FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION	= 0x0020,
+	};
+
+						GridRenderCase					(Context& context, const char* name, const char* description, int flags);
+						~GridRenderCase					(void);
+
+private:
+	void				init							(void);
+	void				deinit							(void);
+	IterateResult		iterate							(void);
+
+	void				renderTo						(std::vector<tcu::Surface>& dst);
+	bool				verifyResultLayer				(int layerNdx, const tcu::Surface& dst);
+
+	const char*			getVertexSource					(void);
+	const char*			getFragmentSource				(void);
+	std::string			getTessellationControlSource	(int tessLevel);
+	std::string			getTessellationEvaluationSource	(int tessLevel);
+	std::string			getGeometryShaderSource			(int numPrimitives, int numInstances);
+
+	enum
+	{
+		RENDER_SIZE = 256
+	};
+
+	const int			m_flags;
+
+	glu::ShaderProgram*	m_program;
+	int					m_numLayers;
+};
+
+GridRenderCase::GridRenderCase (Context& context, const char* name, const char* description, int flags)
+	: TestCase		(context, name, description)
+	, m_flags		(flags)
+	, m_program		(DE_NULL)
+	, m_numLayers	(1)
+{
+	DE_ASSERT(((m_flags & FLAG_TESSELLATION_MAX_SPEC) == 0)			|| ((m_flags & FLAG_TESSELLATION_MAX_IMPLEMENTATION) == 0));
+	DE_ASSERT(((m_flags & FLAG_GEOMETRY_MAX_SPEC) == 0)				|| ((m_flags & FLAG_GEOMETRY_MAX_IMPLEMENTATION) == 0));
+	DE_ASSERT(((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC) == 0)	|| ((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION) == 0));
+}
+
+GridRenderCase::~GridRenderCase (void)
+{
+	deinit();
+}
+
+void GridRenderCase::init (void)
+{
+	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+	// Requirements
+
+	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
+		!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
+		throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
+
+	if (m_context.getRenderTarget().getWidth() < RENDER_SIZE ||
+		m_context.getRenderTarget().getHeight() < RENDER_SIZE)
+		throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE) + " or larger render target.");
+
+	// Log
+
+	m_testCtx.getLog()
+		<< tcu::TestLog::Message
+		<< "Testing tessellation and geometry shaders that output a large number of primitives.\n"
+		<< getDescription()
+		<< tcu::TestLog::EndMessage;
+
+	// Gen program
+	{
+		glu::ProgramSources	sources;
+		int					tessGenLevel = -1;
+
+		sources	<< glu::VertexSource(getVertexSource())
+				<< glu::FragmentSource(getFragmentSource());
+
+		// Tessellation limits
+		{
+			if (m_flags & FLAG_TESSELLATION_MAX_IMPLEMENTATION)
+			{
+				gl.getIntegerv(GL_MAX_TESS_GEN_LEVEL, &tessGenLevel);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "query tessellation limits");
+			}
+			else if (m_flags & FLAG_TESSELLATION_MAX_SPEC)
+			{
+				tessGenLevel = 64;
+			}
+			else
+			{
+				tessGenLevel = 5;
+			}
+
+			m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Tessellation level: " << tessGenLevel << ", mode = quad.\n"
+					<< "\tEach input patch produces " << (tessGenLevel*tessGenLevel) << " (" << (tessGenLevel*tessGenLevel*2) << " triangles)\n"
+					<< tcu::TestLog::EndMessage;
+
+			sources << glu::TessellationControlSource(getTessellationControlSource(tessGenLevel))
+					<< glu::TessellationEvaluationSource(getTessellationEvaluationSource(tessGenLevel));
+		}
+
+		// Geometry limits
+		{
+			int		geometryOutputComponents		= -1;
+			int		geometryOutputVertices			= -1;
+			int		geometryTotalOutputComponents	= -1;
+			int		geometryShaderInvocations		= -1;
+			bool	logGeometryLimits				= false;
+			bool	logInvocationLimits				= false;
+
+			if (m_flags & FLAG_GEOMETRY_MAX_IMPLEMENTATION)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Using implementation maximum geometry shader output limits." << tcu::TestLog::EndMessage;
+
+				gl.getIntegerv(GL_MAX_GEOMETRY_OUTPUT_COMPONENTS, &geometryOutputComponents);
+				gl.getIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &geometryOutputVertices);
+				gl.getIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, &geometryTotalOutputComponents);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "query geometry limits");
+
+				logGeometryLimits = true;
+			}
+			else if (m_flags & FLAG_GEOMETRY_MAX_SPEC)
+			{
+				m_testCtx.getLog() << tcu::TestLog::Message << "Using geometry shader extension minimum maximum output limits." << tcu::TestLog::EndMessage;
+
+				geometryOutputComponents = 128;
+				geometryOutputVertices = 256;
+				geometryTotalOutputComponents = 1024;
+				logGeometryLimits = true;
+			}
+			else
+			{
+				geometryOutputComponents = 128;
+				geometryOutputVertices = 16;
+				geometryTotalOutputComponents = 1024;
+			}
+
+			if (m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION)
+			{
+				gl.getIntegerv(GL_MAX_GEOMETRY_SHADER_INVOCATIONS, &geometryShaderInvocations);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "query geometry invocation limits");
+
+				logInvocationLimits = true;
+			}
+			else if (m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC)
+			{
+				geometryShaderInvocations = 32;
+				logInvocationLimits = true;
+			}
+			else
+			{
+				geometryShaderInvocations = 4;
+			}
+
+			if (logGeometryLimits || logInvocationLimits)
+			{
+				tcu::MessageBuilder msg(&m_testCtx.getLog());
+
+				msg << "Geometry shader, targeting following limits:\n";
+
+				if (logGeometryLimits)
+					msg	<< "\tGL_MAX_GEOMETRY_OUTPUT_COMPONENTS = " << geometryOutputComponents << "\n"
+						<< "\tGL_MAX_GEOMETRY_OUTPUT_VERTICES = " << geometryOutputVertices << "\n"
+						<< "\tGL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = " << geometryTotalOutputComponents << "\n";
+
+				if (logInvocationLimits)
+					msg << "\tGL_MAX_GEOMETRY_SHADER_INVOCATIONS = " << geometryShaderInvocations;
+
+				msg << tcu::TestLog::EndMessage;
+			}
+
+
+			{
+				const int	numComponentsPerVertex		= 8; // vec4 pos, vec4 color
+
+				// If FLAG_GEOMETRY_SEPARATE_PRIMITIVES is not set, geometry shader fills a rectangle area in slices.
+				// Each slice is a triangle strip and is generated by a single shader invocation.
+				// One slice with 4 segment ends (nodes) and 3 segments:
+				//    .__.__.__.
+				//    |\ |\ |\ |
+				//    |_\|_\|_\|
+
+				const int	numSliceNodesComponentLimit			= geometryTotalOutputComponents / (2 * numComponentsPerVertex);			// each node 2 vertices
+				const int	numSliceNodesOutputLimit			= geometryOutputVertices / 2;											// each node 2 vertices
+				const int	numSliceNodes						= de::min(numSliceNodesComponentLimit, numSliceNodesOutputLimit);
+
+				const int	numVerticesPerInvocation			= numSliceNodes * 2;
+				const int	numPrimitivesPerInvocation			= (numSliceNodes - 1) * 2;
+
+				const int	geometryVerticesPerPrimitive		= numVerticesPerInvocation * geometryShaderInvocations;
+				const int	geometryPrimitivesOutPerPrimitive	= numPrimitivesPerInvocation * geometryShaderInvocations;
+
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Geometry shader:\n"
+					<< "\tTotal output vertex count per invocation: " << (numVerticesPerInvocation) << "\n"
+					<< "\tTotal output primitive count per invocation: " << (numPrimitivesPerInvocation) << "\n"
+					<< "\tNumber of invocations per primitive: " << geometryShaderInvocations << "\n"
+					<< "\tTotal output vertex count per input primitive: " << (geometryVerticesPerPrimitive) << "\n"
+					<< "\tTotal output primitive count per input primitive: " << (geometryPrimitivesOutPerPrimitive) << "\n"
+					<< tcu::TestLog::EndMessage;
+
+				sources	<< glu::GeometrySource(getGeometryShaderSource(numPrimitivesPerInvocation, geometryShaderInvocations));
+
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "Program:\n"
+					<< "\tTotal program output vertices count per input patch: " << (tessGenLevel*tessGenLevel*2 * geometryVerticesPerPrimitive) << "\n"
+					<< "\tTotal program output primitive count per input patch: " << (tessGenLevel*tessGenLevel*2 * geometryPrimitivesOutPerPrimitive) << "\n"
+					<< tcu::TestLog::EndMessage;
+			}
+		}
+
+		m_program = new glu::ShaderProgram(m_context.getRenderContext(), sources);
+		m_testCtx.getLog() << *m_program;
+		if (!m_program->isOk())
+			throw tcu::TestError("failed to build program");
+	}
+}
+
+void GridRenderCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+GridRenderCase::IterateResult GridRenderCase::iterate (void)
+{
+	std::vector<tcu::Surface>	renderedLayers	(m_numLayers);
+	bool						allLayersOk		= true;
+
+	for (int ndx = 0; ndx < m_numLayers; ++ndx)
+		renderedLayers[ndx].setSize(RENDER_SIZE, RENDER_SIZE);
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering single point at the origin. Expecting yellow and green colored grid-like image. (High-frequency grid may appear unicolored)." << tcu::TestLog::EndMessage;
+
+	try
+	{
+		renderTo(renderedLayers);
+	}
+	catch (const AllowedRenderFailureException& ex)
+	{
+		// Got accepted failure
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message
+			<< "Could not render, reason: " << ex.what() << "\n"
+			<< "Failure is allowed."
+			<< tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+	for (int ndx = 0; ndx < m_numLayers; ++ndx)
+		allLayersOk &= verifyResultLayer(ndx, renderedLayers[ndx]);
+
+	if (allLayersOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+	return STOP;
+}
+
+void GridRenderCase::renderTo (std::vector<tcu::Surface>& dst)
+{
+	const glw::Functions&			gl					= m_context.getRenderContext().getFunctions();
+	const int						positionLocation	= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	const glu::VertexArray			vao					(m_context.getRenderContext());
+
+	if (positionLocation == -1)
+		throw tcu::TestError("Attribute a_position location was -1");
+
+	gl.viewport(0, 0, dst.front().getWidth(), dst.front().getHeight());
+	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "viewport");
+
+	gl.bindVertexArray(*vao);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "bind vao");
+
+	gl.useProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
+
+	gl.patchParameteri(GL_PATCH_VERTICES, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
+
+	gl.vertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f);
+
+	// clear viewport
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	// draw
+	{
+		glw::GLenum glerror;
+
+		gl.drawArrays(GL_PATCHES, 0, 1);
+
+		// allow always OOM
+		glerror = gl.getError();
+		if (glerror == GL_OUT_OF_MEMORY)
+			throw AllowedRenderFailureException("got GL_OUT_OF_MEMORY while drawing");
+
+		GLU_EXPECT_NO_ERROR(glerror, "draw patches");
+	}
+
+	// Read layers
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.front().getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
+}
+
+bool GridRenderCase::verifyResultLayer (int layerNdx, const tcu::Surface& image)
+{
+	tcu::Surface	errorMask	(image.getWidth(), image.getHeight());
+	bool			foundError	= false;
+
+	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+
+	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output layer " << layerNdx  << tcu::TestLog::EndMessage;
+
+	for (int y = 0; y < image.getHeight(); ++y)
+	for (int x = 0; x < image.getWidth(); ++x)
+	{
+		const int		threshold	= 8;
+		const tcu::RGBA	color		= image.getPixel(x, y);
+
+		// Color must be a linear combination of green and yellow
+		if (color.getGreen() < 255 - threshold || color.getBlue() > threshold)
+		{
+			errorMask.setPixel(x, y, tcu::RGBA::red);
+			foundError = true;
+		}
+	}
+
+	if (!foundError)
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message << "Image valid." << tcu::TestLog::EndMessage
+			<< tcu::TestLog::ImageSet("ImageVerification", "Image verification")
+			<< tcu::TestLog::Image("Result", "Rendered result", image.getAccess())
+			<< tcu::TestLog::EndImageSet;
+		return true;
+	}
+	else
+	{
+		m_testCtx.getLog()
+			<< tcu::TestLog::Message << "Image verification failed, found invalid pixels." << tcu::TestLog::EndMessage
+			<< tcu::TestLog::ImageSet("ImageVerification", "Image verification")
+			<< tcu::TestLog::Image("Result", "Rendered result", image.getAccess())
+			<< tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
+			<< tcu::TestLog::EndImageSet;
+		return false;
+	}
+}
+
+const char* GridRenderCase::getVertexSource (void)
+{
+	return	"#version 310 es\n"
+			"in highp vec4 a_position;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"}\n";
+}
+
+const char* GridRenderCase::getFragmentSource (void)
+{
+	return	"#version 310 es\n"
+			"flat in mediump vec4 v_color;\n"
+			"layout(location = 0) out mediump vec4 fragColor;\n"
+			"void main (void)\n"
+			"{\n"
+			"	fragColor = v_color;\n"
+			"}\n";
+}
+
+std::string GridRenderCase::getTessellationControlSource (int tessLevel)
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout(vertices=1) out;\n"
+			"\n"
+			"void main()\n"
+			"{\n"
+			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
+			"	gl_TessLevelOuter[0] = " << tessLevel << ".0;\n"
+			"	gl_TessLevelOuter[1] = " << tessLevel << ".0;\n"
+			"	gl_TessLevelOuter[2] = " << tessLevel << ".0;\n"
+			"	gl_TessLevelOuter[3] = " << tessLevel << ".0;\n"
+			"	gl_TessLevelInner[0] = " << tessLevel << ".0;\n"
+			"	gl_TessLevelInner[1] = " << tessLevel << ".0;\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string GridRenderCase::getTessellationEvaluationSource (int tessLevel)
+{
+	std::ostringstream buf;
+
+	buf <<	"#version 310 es\n"
+			"#extension GL_EXT_tessellation_shader : require\n"
+			"layout(quads) in;\n"
+			"\n"
+			"out mediump ivec2 v_tessellationGridPosition;\n"
+			"\n"
+			"void main (void)\n"
+			"{\n"
+			"	// Fill the whole viewport\n"
+			"	gl_Position = vec4(gl_TessCoord.x * 2.0 - 1.0, gl_TessCoord.y * 2.0 - 1.0, 0.0, 1.0);\n"
+			"	// Calculate position in tessellation grid\n"
+			"	v_tessellationGridPosition = ivec2(round(gl_TessCoord.xy * float(" << tessLevel << ")));\n"
+			"}\n";
+
+	return buf.str();
+}
+
+std::string GridRenderCase::getGeometryShaderSource (int numPrimitives, int numInstances)
+{
+	std::ostringstream buf;
+
+	buf	<<	"#version 310 es\n"
+			"#extension GL_EXT_geometry_shader : require\n"
+			"layout(triangles, invocations=" << numInstances << ") in;\n"
+			"layout(triangle_strip, max_vertices=" << (numPrimitives + 2) << ") out;\n"
+			"\n"
+			"in mediump ivec2 v_tessellationGridPosition[];\n"
+			"flat out highp vec4 v_color;\n"
+			"\n"
+			"void main ()\n"
+			"{\n"
+			"	const float equalThreshold = 0.001;\n"
+			"	const float gapOffset = 0.0001; // subdivision performed by the geometry shader might produce gaps. Fill potential gaps by enlarging the output slice a little.\n"
+			"\n"
+			"	// Input triangle is generated from an axis-aligned rectangle by splitting it in half\n"
+			"	// Original rectangle can be found by finding the bounding AABB of the triangle\n"
+			"	vec4 aabb = vec4(min(gl_in[0].gl_Position.x, min(gl_in[1].gl_Position.x, gl_in[2].gl_Position.x)),\n"
+			"	                 min(gl_in[0].gl_Position.y, min(gl_in[1].gl_Position.y, gl_in[2].gl_Position.y)),\n"
+			"	                 max(gl_in[0].gl_Position.x, max(gl_in[1].gl_Position.x, gl_in[2].gl_Position.x)),\n"
+			"	                 max(gl_in[0].gl_Position.y, max(gl_in[1].gl_Position.y, gl_in[2].gl_Position.y)));\n"
+			"\n"
+			"	// Location in tessellation grid\n"
+			"	ivec2 gridPosition = ivec2(min(v_tessellationGridPosition[0], min(v_tessellationGridPosition[1], v_tessellationGridPosition[2])));\n"
+			"\n"
+			"	// Which triangle of the two that split the grid cell\n"
+			"	int numVerticesOnBottomEdge = 0;\n"
+			"	for (int ndx = 0; ndx < 3; ++ndx)\n"
+			"		if (abs(gl_in[ndx].gl_Position.y - aabb.w) < equalThreshold)\n"
+			"			++numVerticesOnBottomEdge;\n"
+			"	bool isBottomTriangle = numVerticesOnBottomEdge == 2;\n"
+			"\n"
+			"	// Fill the input area with slices\n"
+			"	// Upper triangle produces slices only to the upper half of the quad and vice-versa\n"
+			"	float triangleOffset = (isBottomTriangle) ? ((aabb.w + aabb.y) / 2.0) : (aabb.y);\n"
+			"	// Each slice is a invocation\n"
+			"	float sliceHeight = (aabb.w - aabb.y) / float(2 * " << numInstances << ");\n"
+			"	float invocationOffset = float(gl_InvocationID) * sliceHeight;\n"
+			"\n"
+			"	vec4 outputSliceArea;\n"
+			"	outputSliceArea.x = aabb.x - gapOffset;\n"
+			"	outputSliceArea.y = triangleOffset + invocationOffset - gapOffset;\n"
+			"	outputSliceArea.z = aabb.z + gapOffset;\n"
+			"	outputSliceArea.w = triangleOffset + invocationOffset + sliceHeight + gapOffset;\n""\n"
+			"	// Draw slice\n"
+			"	for (int ndx = 0; ndx < " << ((numPrimitives+2)/2) << "; ++ndx)\n"
+			"	{\n"
+			"		vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
+			"		vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
+			"		vec4 outputColor = (((gl_InvocationID + ndx) % 2) == 0) ? (green) : (yellow);\n"
+			"		float xpos = mix(outputSliceArea.x, outputSliceArea.z, float(ndx) / float(" << (numPrimitives/2) << "));\n"
+			"\n"
+			"		gl_Position = vec4(xpos, outputSliceArea.y, 0.0, 1.0);\n"
+			"		v_color = outputColor;\n"
+			"		EmitVertex();\n"
+			"\n"
+			"		gl_Position = vec4(xpos, outputSliceArea.w, 0.0, 1.0);\n"
+			"		v_color = outputColor;\n"
+			"		EmitVertex();\n"
+			"	}\n"
+			"}\n";
+
+	return buf.str();
+}
+
+} // anonymous
+
+TessellationGeometryInteractionTests::TessellationGeometryInteractionTests (Context& context)
+	: TestCaseGroup(context, "tessellation_geometry_interaction", "Tessellation and geometry shader interaction stress tests")
+{
+}
+
+TessellationGeometryInteractionTests::~TessellationGeometryInteractionTests (void)
+{
+}
+
+void TessellationGeometryInteractionTests::init (void)
+{
+	tcu::TestCaseGroup* const multilimitGroup = new tcu::TestCaseGroup(m_testCtx, "render_multiple_limits", "Various render tests");
+
+	addChild(multilimitGroup);
+
+	// .render_multiple_limits
+	{
+		static const struct LimitCaseDef
+		{
+			const char*	name;
+			const char*	desc;
+			int			flags;
+		} cases[] =
+		{
+			// Test multiple limits at the same time
+
+			{
+				"output_required_max_tessellation_max_geometry",
+				"Minimum maximum tessellation level and geometry shader output vertices",
+				GridRenderCase::FLAG_TESSELLATION_MAX_SPEC | GridRenderCase::FLAG_GEOMETRY_MAX_SPEC
+			},
+			{
+				"output_implementation_max_tessellation_max_geometry",
+				"Maximum tessellation level and geometry shader output vertices supported by the implementation",
+				GridRenderCase::FLAG_TESSELLATION_MAX_IMPLEMENTATION | GridRenderCase::FLAG_GEOMETRY_MAX_IMPLEMENTATION
+			},
+			{
+				"output_required_max_tessellation_max_invocations",
+				"Minimum maximum tessellation level and geometry shader invocations",
+				GridRenderCase::FLAG_TESSELLATION_MAX_SPEC | GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC
+			},
+			{
+				"output_implementation_max_tessellation_max_invocations",
+				"Maximum tessellation level and geometry shader invocations supported by the implementation",
+				GridRenderCase::FLAG_TESSELLATION_MAX_IMPLEMENTATION | GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION
+			},
+			{
+				"output_required_max_geometry_max_invocations",
+				"Minimum maximum geometry shader output vertices and invocations",
+				GridRenderCase::FLAG_GEOMETRY_MAX_SPEC | GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC
+			},
+			{
+				"output_implementation_max_geometry_max_invocations",
+				"Maximum geometry shader output vertices and invocations invocations supported by the implementation",
+				GridRenderCase::FLAG_GEOMETRY_MAX_IMPLEMENTATION | GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION
+			},
+
+			// Test all limits simultaneously
+			{
+				"output_max_required",
+				"Output minimum maximum number of vertices",
+				GridRenderCase::FLAG_TESSELLATION_MAX_SPEC | GridRenderCase::FLAG_GEOMETRY_MAX_SPEC | GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC
+			},
+			{
+				"output_max_implementation",
+				"Output maximum number of vertices supported by the implementation",
+				GridRenderCase::FLAG_TESSELLATION_MAX_IMPLEMENTATION | GridRenderCase::FLAG_GEOMETRY_MAX_IMPLEMENTATION | GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION
+			},
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ++ndx)
+			multilimitGroup->addChild(new GridRenderCase(m_context, cases[ndx].name, cases[ndx].desc, cases[ndx].flags));
+	}
+}
+
+} // Stress
+} // gles31
+} // deqp
diff --git a/modules/gles31/stress/es31sTessellationGeometryInteractionTests.hpp b/modules/gles31/stress/es31sTessellationGeometryInteractionTests.hpp
new file mode 100644
index 0000000..aca6e82
--- /dev/null
+++ b/modules/gles31/stress/es31sTessellationGeometryInteractionTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31STESSELLATIONGEOMETRYINTERACTIONTESTS_HPP
+#define _ES31STESSELLATIONGEOMETRYINTERACTIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tessellation and geometry shader interaction stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Stress
+{
+
+class TessellationGeometryInteractionTests : public TestCaseGroup
+{
+public:
+											TessellationGeometryInteractionTests	(Context& context);
+											~TessellationGeometryInteractionTests	(void);
+
+	void									init									(void);
+
+private:
+											TessellationGeometryInteractionTests	(const TessellationGeometryInteractionTests& other);
+	TessellationGeometryInteractionTests&	operator=								(const TessellationGeometryInteractionTests& other);
+};
+
+} // Stress
+} // gles31
+} // deqp
+
+#endif // _ES31STESSELLATIONGEOMETRYINTERACTIONTESTS_HPP
diff --git a/modules/gles31/stress/es31sVertexAttributeBindingTests.cpp b/modules/gles31/stress/es31sVertexAttributeBindingTests.cpp
new file mode 100644
index 0000000..87cf343
--- /dev/null
+++ b/modules/gles31/stress/es31sVertexAttributeBindingTests.cpp
@@ -0,0 +1,662 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex attribute binding stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "es31sVertexAttributeBindingTests.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuSurface.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluStrUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deStringUtil.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Stress
+{
+namespace
+{
+
+static const char* const s_vertexSource =				"#version 310 es\n"
+														"in highp vec4 a_position;\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = a_position;\n"
+														"}\n";
+
+static const char* const s_fragmentSource =				"#version 310 es\n"
+														"layout(location = 0) out mediump vec4 fragColor;\n"
+														"void main (void)\n"
+														"{\n"
+														"	fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+														"}\n";
+
+static const char* const s_colorFragmentShader =		"#version 310 es\n"
+														"in mediump vec4 v_color;\n"
+														"layout(location = 0) out mediump vec4 fragColor;\n"
+														"void main (void)\n"
+														"{\n"
+														"	fragColor = v_color;\n"
+														"}\n";
+
+// Verifies image contains only yellow or greeen, or a linear combination
+// of these colors.
+static bool verifyImageYellowGreen (const tcu::Surface& image, tcu::TestLog& log, bool logImageOnSuccess)
+{
+	using tcu::TestLog;
+
+	const tcu::RGBA green		(0, 255, 0, 255);
+	const tcu::RGBA yellow		(255, 255, 0, 255);
+	const int colorThreshold	= 20;
+
+	tcu::Surface error			(image.getWidth(), image.getHeight());
+	bool isOk					= true;
+
+	log << TestLog::Message << "Verifying image contents." << TestLog::EndMessage;
+
+	for (int y = 0; y < image.getHeight(); y++)
+	for (int x = 0; x < image.getWidth(); x++)
+	{
+		const tcu::RGBA pixel = image.getPixel(x, y);
+		bool pixelOk = true;
+
+		// Any pixel with !(G ~= 255) is faulty (not a linear combinations of green and yellow)
+		if (de::abs(pixel.getGreen() - 255) > colorThreshold)
+			pixelOk = false;
+
+		// Any pixel with !(B ~= 0) is faulty (not a linear combinations of green and yellow)
+		if (de::abs(pixel.getBlue() - 0) > colorThreshold)
+			pixelOk = false;
+
+		error.setPixel(x, y, (pixelOk) ? (tcu::RGBA(0, 255, 0, 255)) : (tcu::RGBA(255, 0, 0, 255)));
+		isOk = isOk && pixelOk;
+	}
+
+	if (!isOk)
+	{
+		log << TestLog::Message << "Image verification failed." << TestLog::EndMessage;
+		log << TestLog::ImageSet("Verfication result", "Result of rendering")
+			<< TestLog::Image("Result",		"Result",		image)
+			<< TestLog::Image("ErrorMask",	"Error mask",	error)
+			<< TestLog::EndImageSet;
+	}
+	else
+	{
+		log << TestLog::Message << "Image verification passed." << TestLog::EndMessage;
+
+		if (logImageOnSuccess)
+			log << TestLog::ImageSet("Verfication result", "Result of rendering")
+				<< TestLog::Image("Result", "Result", image)
+				<< TestLog::EndImageSet;
+	}
+
+	return isOk;
+}
+
+class BindingRenderCase : public TestCase
+{
+public:
+	enum
+	{
+		TEST_RENDER_SIZE = 64
+	};
+
+						BindingRenderCase	(Context& ctx, const char* name, const char* desc, bool unalignedData);
+	virtual				~BindingRenderCase	(void);
+
+	virtual void		init				(void);
+	virtual void		deinit				(void);
+	IterateResult		iterate				(void);
+
+private:
+	virtual void		renderTo			(tcu::Surface& dst) = 0;
+	virtual void		createBuffers		(void) = 0;
+	virtual void		createShader		(void) = 0;
+
+protected:
+	const bool			m_unalignedData;
+	glw::GLuint			m_vao;
+	glu::ShaderProgram*	m_program;
+};
+
+BindingRenderCase::BindingRenderCase (Context& ctx, const char* name, const char* desc, bool unalignedData)
+	: TestCase			(ctx, name, desc)
+	, m_unalignedData	(unalignedData)
+	, m_vao				(0)
+	, m_program			(DE_NULL)
+{
+}
+
+BindingRenderCase::~BindingRenderCase (void)
+{
+	deinit();
+}
+
+void BindingRenderCase::init (void)
+{
+	// check requirements
+	if (m_context.getRenderTarget().getWidth() < TEST_RENDER_SIZE || m_context.getRenderTarget().getHeight() < TEST_RENDER_SIZE)
+		throw tcu::NotSupportedError("Test requires at least " + de::toString<int>(TEST_RENDER_SIZE) + "x" + de::toString<int>(TEST_RENDER_SIZE) + " render target");
+
+	// resources
+	m_context.getRenderContext().getFunctions().genVertexArrays(1, &m_vao);
+	if (m_context.getRenderContext().getFunctions().getError() != GL_NO_ERROR)
+		throw tcu::TestError("could not gen vao");
+
+	createBuffers();
+	createShader();
+}
+
+void BindingRenderCase::deinit (void)
+{
+	if (m_vao)
+	{
+		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vao);
+		m_vao = 0;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+BindingRenderCase::IterateResult BindingRenderCase::iterate (void)
+{
+	tcu::Surface surface(TEST_RENDER_SIZE, TEST_RENDER_SIZE);
+
+	// draw pattern
+
+	renderTo(surface);
+
+	// verify results
+
+	if (verifyImageYellowGreen(surface, m_testCtx.getLog(), false))
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	else if (m_unalignedData)
+		m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned data");
+	else
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
+
+	return STOP;
+}
+
+class SingleBindingCase : public BindingRenderCase
+{
+public:
+
+	enum CaseFlag
+	{
+		FLAG_ATTRIB_UNALIGNED			= (1<<0),		// !< unalign attributes with relativeOffset
+		FLAG_ATTRIB_ALIGNED				= (1<<1),		// !< align attributes with relativeOffset to the buffer begin (and not buffer offset)
+		FLAG_ATTRIBS_MULTIPLE_ELEMS		= (1<<2),		// !< use multiple attribute elements
+		FLAG_ATTRIBS_SHARED_ELEMS		= (1<<3),		// !< use multiple shared attribute elements. xyzw & rgba stored as (x, y, zr, wg, b, a)
+
+		FLAG_BUF_ALIGNED_OFFSET			= (1<<4),		// !< use aligned offset to the buffer object
+		FLAG_BUF_UNALIGNED_OFFSET		= (1<<5),		// !< use unaligned offset to the buffer object
+		FLAG_BUF_UNALIGNED_STRIDE		= (1<<6),		// !< unalign buffer elements
+	};
+						SingleBindingCase	(Context& ctx, const char* name, int flags);
+						~SingleBindingCase	(void);
+
+	void				init				(void);
+	void				deinit				(void);
+
+private:
+	struct TestSpec
+	{
+		int		bufferOffset;
+		int		bufferStride;
+		int		positionAttrOffset;
+		int		colorAttrOffset;
+		bool	hasColorAttr;
+	};
+
+	enum
+	{
+		GRID_SIZE = 20
+	};
+
+	void				renderTo			(tcu::Surface& dst);
+
+	static TestSpec		genTestSpec			(int flags);
+	static std::string	genTestDescription	(int flags);
+	static bool			isDataUnaligned		(int flags);
+
+	void				createBuffers		(void);
+	void				createShader		(void);
+	std::string			genVertexSource		(void);
+
+	const TestSpec		m_spec;
+	glw::GLuint			m_buf;
+};
+
+SingleBindingCase::SingleBindingCase (Context& ctx, const char* name, int flags)
+	: BindingRenderCase	(ctx, name, genTestDescription(flags).c_str(), isDataUnaligned(flags))
+	, m_spec			(genTestSpec(flags))
+	, m_buf				(0)
+{
+	DE_ASSERT(!((flags & FLAG_ATTRIB_UNALIGNED) && (flags & FLAG_ATTRIB_ALIGNED)));
+	DE_ASSERT(!((flags & FLAG_ATTRIB_ALIGNED) && (flags & FLAG_BUF_UNALIGNED_STRIDE)));
+
+	DE_ASSERT(isDataUnaligned(flags));
+}
+
+SingleBindingCase::~SingleBindingCase (void)
+{
+	deinit();
+}
+
+void SingleBindingCase::init (void)
+{
+	// log what we are trying to do
+
+	m_testCtx.getLog()	<< tcu::TestLog::Message
+						<< "Rendering " << (int)GRID_SIZE << "x" << (int)GRID_SIZE << " grid.\n"
+						<< "Buffer format:\n"
+						<< "	bufferOffset: " << m_spec.bufferOffset << "\n"
+						<< "	bufferStride: " << m_spec.bufferStride << "\n"
+						<< "Vertex position format:\n"
+						<< "	type: float4\n"
+						<< "	offset: " << m_spec.positionAttrOffset << "\n"
+						<< "	total offset: " << m_spec.bufferOffset + m_spec.positionAttrOffset << "\n"
+						<< tcu::TestLog::EndMessage;
+
+	if (m_spec.hasColorAttr)
+		m_testCtx.getLog()	<< tcu::TestLog::Message
+							<< "Color:\n"
+							<< "	type: float4\n"
+							<< "	offset: " << m_spec.colorAttrOffset << "\n"
+							<< "	total offset: " << m_spec.bufferOffset + m_spec.colorAttrOffset << "\n"
+							<< tcu::TestLog::EndMessage;
+	// init
+
+	BindingRenderCase::init();
+}
+
+void SingleBindingCase::deinit (void)
+{
+	if (m_buf)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buf);
+		m_buf = 0;
+	}
+
+	BindingRenderCase::deinit();
+}
+
+void SingleBindingCase::renderTo (tcu::Surface& dst)
+{
+	glu::CallLogWrapper gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	const int			positionLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
+	const int			colorLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_color");
+	const int			colorUniformLoc	= gl.glGetUniformLocation(m_program->getProgram(), "u_color");
+
+	gl.enableLogging(true);
+
+	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	gl.glClear(GL_COLOR_BUFFER_BIT);
+	gl.glViewport(0, 0, dst.getWidth(), dst.getHeight());
+	gl.glBindVertexArray(m_vao);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set vao");
+
+	gl.glUseProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");
+
+	if (m_spec.hasColorAttr)
+	{
+		gl.glBindVertexBuffer(3, m_buf, m_spec.bufferOffset, m_spec.bufferStride);
+
+		gl.glVertexAttribBinding(positionLoc, 3);
+		gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, m_spec.positionAttrOffset);
+		gl.glEnableVertexAttribArray(positionLoc);
+
+		gl.glVertexAttribBinding(colorLoc, 3);
+		gl.glVertexAttribFormat(colorLoc, 4, GL_FLOAT, GL_FALSE, m_spec.colorAttrOffset);
+		gl.glEnableVertexAttribArray(colorLoc);
+
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");
+
+		gl.glDrawArrays(GL_TRIANGLES, 0, GRID_SIZE*GRID_SIZE*6);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw");
+	}
+	else
+	{
+		gl.glBindVertexBuffer(3, m_buf, m_spec.bufferOffset, m_spec.bufferStride);
+		gl.glVertexAttribBinding(positionLoc, 3);
+		gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, m_spec.positionAttrOffset);
+		gl.glEnableVertexAttribArray(positionLoc);
+
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");
+		gl.glUniform4f(colorUniformLoc, 0.0f, 1.0f, 0.0f, 1.0f);
+
+		gl.glDrawArrays(GL_TRIANGLES, 0, GRID_SIZE*GRID_SIZE*6);
+		GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw");
+	}
+
+	gl.glFinish();
+	gl.glBindVertexArray(0);
+	gl.glUseProgram(0);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "clean");
+
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+}
+
+SingleBindingCase::TestSpec SingleBindingCase::genTestSpec (int flags)
+{
+	const int	datumSize				= 4;
+	const int	bufferOffset			= (flags & FLAG_BUF_ALIGNED_OFFSET) ? (32) : (flags & FLAG_BUF_UNALIGNED_OFFSET) ? (19) : (0);
+	const int	attrBufAlignment		= ((bufferOffset % datumSize) == 0) ? (0) : (datumSize - (bufferOffset % datumSize));
+	const int	positionAttrOffset		= (flags & FLAG_ATTRIB_UNALIGNED) ? (3) : (flags & FLAG_ATTRIB_ALIGNED) ? (attrBufAlignment) : (0);
+	const bool	hasColorAttr			= (flags & FLAG_ATTRIBS_SHARED_ELEMS) || (flags & FLAG_ATTRIBS_MULTIPLE_ELEMS);
+	const int	colorAttrOffset			= (flags & FLAG_ATTRIBS_SHARED_ELEMS) ? (2 * datumSize) : (flags & FLAG_ATTRIBS_MULTIPLE_ELEMS) ? (4 * datumSize) : (-1);
+
+	const int	bufferStrideBase		= de::max(positionAttrOffset + 4 * datumSize, colorAttrOffset + 4 * datumSize);
+	const int	bufferStrideAlignment	= ((bufferStrideBase % datumSize) == 0) ? (0) : (datumSize - (bufferStrideBase % datumSize));
+	const int	bufferStridePadding		= ((flags & FLAG_BUF_UNALIGNED_STRIDE) && deIsAligned32(bufferStrideBase, datumSize)) ? (13) : (!(flags & FLAG_BUF_UNALIGNED_STRIDE) && !deIsAligned32(bufferStrideBase, datumSize)) ? (bufferStrideAlignment) : (0);
+
+	TestSpec spec;
+
+	spec.bufferOffset			= bufferOffset;
+	spec.bufferStride			= bufferStrideBase + bufferStridePadding;
+	spec.positionAttrOffset		= positionAttrOffset;
+	spec.colorAttrOffset		= colorAttrOffset;
+	spec.hasColorAttr			= hasColorAttr;
+
+	if (flags & FLAG_ATTRIB_UNALIGNED)
+		DE_ASSERT(!deIsAligned32(spec.bufferOffset + spec.positionAttrOffset, datumSize));
+	else if (flags & FLAG_ATTRIB_ALIGNED)
+		DE_ASSERT(deIsAligned32(spec.bufferOffset + spec.positionAttrOffset, datumSize));
+
+	if (flags & FLAG_BUF_UNALIGNED_STRIDE)
+		DE_ASSERT(!deIsAligned32(spec.bufferStride, datumSize));
+	else
+		DE_ASSERT(deIsAligned32(spec.bufferStride, datumSize));
+
+	return spec;
+}
+
+std::string SingleBindingCase::genTestDescription (int flags)
+{
+	std::ostringstream buf;
+	buf << "draw test pattern";
+
+	if (flags & FLAG_ATTRIB_UNALIGNED)
+		buf << ", attribute offset (unaligned)";
+	if (flags & FLAG_ATTRIB_ALIGNED)
+		buf << ", attribute offset (aligned)";
+
+	if (flags & FLAG_ATTRIBS_MULTIPLE_ELEMS)
+		buf << ", 2 attributes";
+	if (flags & FLAG_ATTRIBS_SHARED_ELEMS)
+		buf << ", 2 attributes (some components shared)";
+
+	if (flags & FLAG_BUF_ALIGNED_OFFSET)
+		buf << ", buffer offset aligned";
+	if (flags & FLAG_BUF_UNALIGNED_OFFSET)
+		buf << ", buffer offset unaligned";
+	if (flags & FLAG_BUF_UNALIGNED_STRIDE)
+		buf << ", buffer stride unaligned";
+
+	return buf.str();
+}
+
+bool SingleBindingCase::isDataUnaligned (int flags)
+{
+	if (flags & FLAG_ATTRIB_UNALIGNED)
+		return true;
+	if (flags & FLAG_ATTRIB_ALIGNED)
+		return false;
+
+	return (flags & FLAG_BUF_UNALIGNED_OFFSET) || (flags & FLAG_BUF_UNALIGNED_STRIDE);
+}
+
+void SingleBindingCase::createBuffers (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	std::vector<deUint8>	dataBuf	(m_spec.bufferOffset + m_spec.bufferStride * GRID_SIZE * GRID_SIZE * 6);
+
+	// In interleaved mode color rg and position zw are the same. Select "good" values for r and g
+	const tcu::Vec4			colorA	(0.0f, 1.0f, 0.0f, 1.0f);
+	const tcu::Vec4			colorB	(0.5f, 1.0f, 0.0f, 1.0f);
+
+	for (int y = 0; y < GRID_SIZE; ++y)
+	for (int x = 0; x < GRID_SIZE; ++x)
+	{
+		const tcu::Vec4&	color = ((x + y) % 2 == 0) ? (colorA) : (colorB);
+		const tcu::Vec4		positions[6] =
+		{
+			tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+			tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+			tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+			tcu::Vec4((x+0) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+			tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+			tcu::Vec4((x+1) / float(GRID_SIZE) * 2.0f - 1.0f, (y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
+		};
+
+		// copy cell vertices to the buffer.
+		for (int v = 0; v < 6; ++v)
+			memcpy(&dataBuf[m_spec.bufferOffset + m_spec.positionAttrOffset + m_spec.bufferStride * ((y * GRID_SIZE + x) * 6 + v)], positions[v].getPtr(), sizeof(positions[v]));
+
+		// copy color to buffer
+		if (m_spec.hasColorAttr)
+			for (int v = 0; v < 6; ++v)
+				memcpy(&dataBuf[m_spec.bufferOffset + m_spec.colorAttrOffset + m_spec.bufferStride * ((y * GRID_SIZE + x) * 6 + v)], color.getPtr(), sizeof(color));
+	}
+
+	gl.genBuffers(1, &m_buf);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_buf);
+	gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)dataBuf.size(), &dataBuf[0], GL_STATIC_DRAW);
+	gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+
+	if (gl.getError() != GL_NO_ERROR)
+		throw tcu::TestError("could not init buffer");
+}
+
+void SingleBindingCase::createShader (void)
+{
+	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource()) << glu::FragmentSource(s_colorFragmentShader));
+	m_testCtx.getLog() << *m_program;
+
+	if (!m_program->isOk())
+		throw tcu::TestError("could not build shader");
+}
+
+std::string SingleBindingCase::genVertexSource (void)
+{
+	const bool			useUniformColor = !m_spec.hasColorAttr;
+	std::ostringstream	buf;
+
+	buf <<	"#version 310 es\n"
+			"in highp vec4 a_position;\n";
+
+	if (!useUniformColor)
+		buf << "in highp vec4 a_color;\n";
+	else
+		buf << "uniform highp vec4 u_color;\n";
+
+	buf <<	"out highp vec4 v_color;\n"
+			"void main (void)\n"
+			"{\n"
+			"	gl_Position = a_position;\n"
+			"	v_color = " << ((useUniformColor) ? ("u_color") : ("a_color")) << ";\n"
+			"}\n";
+
+	return buf.str();
+}
+
+class BindVertexBufferCase : public TestCase
+{
+public:
+						BindVertexBufferCase	(Context& ctx, const char* name, const char* desc, int offset, int drawCount);
+						~BindVertexBufferCase	(void);
+
+	void				init					(void);
+	void				deinit					(void);
+	IterateResult		iterate					(void);
+
+private:
+	const int			m_offset;
+	const int			m_drawCount;
+	deUint32			m_buffer;
+	glu::ShaderProgram*	m_program;
+};
+
+BindVertexBufferCase::BindVertexBufferCase (Context& ctx, const char* name, const char* desc, int offset, int drawCount)
+	: TestCase		(ctx, name, desc)
+	, m_offset		(offset)
+	, m_drawCount	(drawCount)
+	, m_buffer		(0)
+	, m_program		(DE_NULL)
+{
+}
+
+BindVertexBufferCase::~BindVertexBufferCase (void)
+{
+	deinit();
+}
+
+void BindVertexBufferCase::init (void)
+{
+	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+	std::vector<tcu::Vec4>	data	(m_drawCount); // !< some junk data to make sure buffer is really allocated
+
+	gl.genBuffers(1, &m_buffer);
+	gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);
+	gl.bufferData(GL_ARRAY_BUFFER, int(m_drawCount * sizeof(tcu::Vec4)), &data[0], GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "buffer gen");
+
+	m_program = 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("could not build program");
+	}
+}
+
+void BindVertexBufferCase::deinit (void)
+{
+	if (m_buffer)
+	{
+		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buffer);
+		m_buffer = 0;
+	}
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+BindVertexBufferCase::IterateResult BindVertexBufferCase::iterate (void)
+{
+	glu::CallLogWrapper		gl			(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
+	const deInt32			positionLoc = gl.glGetAttribLocation(m_program->getProgram(), "a_position");
+	tcu::Surface			dst			(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
+	glu::VertexArray		vao			(m_context.getRenderContext());
+
+	gl.enableLogging(true);
+
+	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	gl.glClear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup");
+
+	gl.glUseProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");
+
+	gl.glBindVertexArray(*vao);
+	gl.glEnableVertexAttribArray(positionLoc);
+	gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, 0);
+	gl.glVertexAttribBinding(positionLoc, 0);
+	gl.glBindVertexBuffer(0, m_buffer, m_offset, int(sizeof(tcu::Vec4)));
+	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set buffer");
+
+	gl.glDrawArrays(GL_POINTS, 0, m_drawCount);
+
+	// allow errors after attempted out-of-bounds memory access
+	{
+		const deUint32 error = gl.glGetError();
+
+		if (error != GL_NO_ERROR)
+			m_testCtx.getLog() << tcu::TestLog::Message << "Got error: " << glu::getErrorStr(error) << ", ignoring..." << tcu::TestLog::EndMessage;
+	}
+
+	// read pixels to wait for rendering
+	gl.glFinish();
+	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+} // anonymous
+
+VertexAttributeBindingTests::VertexAttributeBindingTests (Context& context)
+	: TestCaseGroup(context, "vertex_attribute_binding", "Test vertex attribute binding stress tests")
+{
+}
+
+VertexAttributeBindingTests::~VertexAttributeBindingTests (void)
+{
+}
+
+void VertexAttributeBindingTests::init (void)
+{
+	tcu::TestCaseGroup* const unalignedGroup	= new tcu::TestCaseGroup(m_testCtx, "unaligned",		"Unaligned access");
+	tcu::TestCaseGroup* const bufferRangeGroup	= new tcu::TestCaseGroup(m_testCtx, "buffer_bounds",	"Source data over buffer bounds");
+
+	addChild(unalignedGroup);
+	addChild(bufferRangeGroup);
+
+	// .unaligned
+	{
+		unalignedGroup->addChild(new SingleBindingCase(m_context, "elements_1_unaligned",																		  SingleBindingCase::FLAG_ATTRIB_UNALIGNED));
+		unalignedGroup->addChild(new SingleBindingCase(m_context, "offset_elements_1_unaligned",				SingleBindingCase::FLAG_BUF_ALIGNED_OFFSET		| SingleBindingCase::FLAG_ATTRIB_UNALIGNED));
+
+		unalignedGroup->addChild(new SingleBindingCase(m_context, "unaligned_offset_elements_1",				SingleBindingCase::FLAG_BUF_UNALIGNED_OFFSET	| 0));
+		unalignedGroup->addChild(new SingleBindingCase(m_context, "unaligned_offset_elements_1_unaligned",		SingleBindingCase::FLAG_BUF_UNALIGNED_OFFSET	| SingleBindingCase::FLAG_ATTRIB_UNALIGNED));
+		unalignedGroup->addChild(new SingleBindingCase(m_context, "unaligned_offset_elements_2",				SingleBindingCase::FLAG_BUF_UNALIGNED_OFFSET	| SingleBindingCase::FLAG_ATTRIBS_MULTIPLE_ELEMS));
+		unalignedGroup->addChild(new SingleBindingCase(m_context, "unaligned_offset_elements_2_share_elements",	SingleBindingCase::FLAG_BUF_UNALIGNED_OFFSET	| SingleBindingCase::FLAG_ATTRIBS_SHARED_ELEMS));
+
+		unalignedGroup->addChild(new SingleBindingCase(m_context, "unaligned_stride_elements_1",				SingleBindingCase::FLAG_BUF_UNALIGNED_STRIDE	| 0));
+		unalignedGroup->addChild(new SingleBindingCase(m_context, "unaligned_stride_elements_2",				SingleBindingCase::FLAG_BUF_UNALIGNED_STRIDE	| SingleBindingCase::FLAG_ATTRIBS_MULTIPLE_ELEMS));
+		unalignedGroup->addChild(new SingleBindingCase(m_context, "unaligned_stride_elements_2_share_elements",	SingleBindingCase::FLAG_BUF_UNALIGNED_STRIDE	| SingleBindingCase::FLAG_ATTRIBS_SHARED_ELEMS));
+	}
+
+	// .buffer_bounds
+	{
+		// bind buffer offset cases
+		bufferRangeGroup->addChild(new BindVertexBufferCase(m_context, "bind_vertex_buffer_offset_over_bounds_10",		"Offset over buffer bounds",				0x00210000, 10));
+		bufferRangeGroup->addChild(new BindVertexBufferCase(m_context, "bind_vertex_buffer_offset_over_bounds_1000",	"Offset over buffer bounds",				0x00210000, 1000));
+		bufferRangeGroup->addChild(new BindVertexBufferCase(m_context, "bind_vertex_buffer_offset_near_wrap_10",		"Offset over buffer bounds, near wrapping",	0x7FFFFFF0, 10));
+		bufferRangeGroup->addChild(new BindVertexBufferCase(m_context, "bind_vertex_buffer_offset_near_wrap_1000",		"Offset over buffer bounds, near wrapping",	0x7FFFFFF0, 1000));
+	}
+}
+
+} // Stress
+} // gles31
+} // deqp
diff --git a/modules/gles31/stress/es31sVertexAttributeBindingTests.hpp b/modules/gles31/stress/es31sVertexAttributeBindingTests.hpp
new file mode 100644
index 0000000..6841635
--- /dev/null
+++ b/modules/gles31/stress/es31sVertexAttributeBindingTests.hpp
@@ -0,0 +1,53 @@
+#ifndef _ES31SVERTEXATTRIBUTEBINDINGTESTS_HPP
+#define _ES31SVERTEXATTRIBUTEBINDINGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex attribute binding stress tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+namespace Stress
+{
+
+class VertexAttributeBindingTests : public TestCaseGroup
+{
+public:
+										VertexAttributeBindingTests		(Context& context);
+										~VertexAttributeBindingTests	(void);
+
+	void								init							(void);
+
+private:
+										VertexAttributeBindingTests		(const VertexAttributeBindingTests& other);
+	VertexAttributeBindingTests&		operator=						(const VertexAttributeBindingTests& other);
+};
+
+} // Stress
+} // gles31
+} // deqp
+
+#endif // _ES31SVERTEXATTRIBUTEBINDINGTESTS_HPP
diff --git a/modules/gles31/tes31Context.cpp b/modules/gles31/tes31Context.cpp
new file mode 100644
index 0000000..19e8cec
--- /dev/null
+++ b/modules/gles31/tes31Context.cpp
@@ -0,0 +1,85 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.1 test context.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31Context.hpp"
+#include "gluRenderContext.hpp"
+#include "gluRenderConfig.hpp"
+#include "gluFboRenderContext.hpp"
+#include "gluES3PlusWrapperContext.hpp"
+#include "gluContextInfo.hpp"
+#include "gluDummyRenderContext.hpp"
+#include "tcuCommandLine.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+
+Context::Context (tcu::TestContext& testCtx)
+	: m_testCtx		(testCtx)
+	, m_renderCtx	(DE_NULL)
+	, m_contextInfo	(DE_NULL)
+{
+	if (m_testCtx.getCommandLine().getRunMode() == tcu::RUNMODE_EXECUTE)
+		createRenderContext();
+	else
+		m_renderCtx = new glu::DummyRenderContext();
+}
+
+Context::~Context (void)
+{
+	destroyRenderContext();
+}
+
+void Context::createRenderContext (void)
+{
+	DE_ASSERT(!m_renderCtx && !m_contextInfo);
+
+	try
+	{
+		m_renderCtx		= glu::createDefaultRenderContext(m_testCtx.getPlatform(), m_testCtx.getCommandLine(), glu::ApiType::es(3,1));
+		m_contextInfo	= glu::ContextInfo::create(*m_renderCtx);
+	}
+	catch (...)
+	{
+		destroyRenderContext();
+		throw;
+	}
+}
+
+void Context::destroyRenderContext (void)
+{
+	delete m_contextInfo;
+	delete m_renderCtx;
+
+	m_contextInfo	= DE_NULL;
+	m_renderCtx		= DE_NULL;
+}
+
+const tcu::RenderTarget& Context::getRenderTarget (void) const
+{
+	return m_renderCtx->getRenderTarget();
+}
+
+} // gles31
+} // deqp
diff --git a/modules/gles31/tes31Context.hpp b/modules/gles31/tes31Context.hpp
new file mode 100644
index 0000000..e09f9c5
--- /dev/null
+++ b/modules/gles31/tes31Context.hpp
@@ -0,0 +1,71 @@
+#ifndef _TES31CONTEXT_HPP
+#define _TES31CONTEXT_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.1 test context.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestContext.hpp"
+
+namespace glu
+{
+class RenderContext;
+class ContextInfo;
+}
+
+namespace tcu
+{
+class RenderTarget;
+}
+
+namespace deqp
+{
+namespace gles31
+{
+
+class Context
+{
+public:
+									Context					(tcu::TestContext& testCtx);
+									~Context				(void);
+
+	tcu::TestContext&				getTestContext			(void)			{ return m_testCtx;			}
+	glu::RenderContext&				getRenderContext		(void)			{ return *m_renderCtx;		}
+	const glu::ContextInfo&			getContextInfo			(void) const	{ return *m_contextInfo;	}
+	const tcu::RenderTarget&		getRenderTarget			(void) const;
+
+private:
+									Context					(const Context& other);
+	Context&						operator=				(const Context& other);
+
+	void							createRenderContext		(void);
+	void							destroyRenderContext	(void);
+
+	tcu::TestContext&				m_testCtx;
+	glu::RenderContext*				m_renderCtx;
+	glu::ContextInfo*				m_contextInfo;
+};
+
+} // gles31
+} // deqp
+
+#endif // _TES31CONTEXT_HPP
diff --git a/modules/gles31/tes31InfoTests.cpp b/modules/gles31/tes31InfoTests.cpp
new file mode 100644
index 0000000..e902c34
--- /dev/null
+++ b/modules/gles31/tes31InfoTests.cpp
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Platform Information and Capability Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31InfoTests.hpp"
+#include "tcuTestLog.hpp"
+#include "gluDefs.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <string>
+#include <vector>
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace gles31
+{
+
+class QueryStringCase : public TestCase
+{
+public:
+	QueryStringCase (Context& context, const char* name, const char* description, deUint32 query)
+		: TestCase	(context, name, description)
+		, m_query	(query)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
+		const char*				result	= (const char*)gl.getString(m_query);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetString() failed");
+
+		m_testCtx.getLog() << tcu::TestLog::Message << result << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		return STOP;
+	}
+
+private:
+	deUint32 m_query;
+};
+
+class QueryExtensionsCase : public TestCase
+{
+public:
+	QueryExtensionsCase (Context& context)
+		: TestCase	(context, "extensions", "Supported Extensions")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const vector<string> extensions = m_context.getContextInfo().getExtensions();
+
+		for (vector<string>::const_iterator i = extensions.begin(); i != extensions.end(); i++)
+			m_testCtx.getLog() << tcu::TestLog::Message << *i << tcu::TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		return STOP;
+	}
+};
+
+class RenderTargetInfoCase : public TestCase
+{
+public:
+	RenderTargetInfoCase (Context& context)
+		: TestCase	(context, "render_target", "Render Target Info")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const tcu::RenderTarget&	rt		= m_context.getRenderTarget();
+		const tcu::PixelFormat&		pf		= rt.getPixelFormat();
+
+		m_testCtx.getLog()
+			<< tcu::TestLog::Integer("RedBits",		"Red channel bits",		"", QP_KEY_TAG_NONE,	pf.redBits)
+			<< tcu::TestLog::Integer("GreenBits",	"Green channel bits",	"",	QP_KEY_TAG_NONE,	pf.greenBits)
+			<< tcu::TestLog::Integer("BlueBits",	"Blue channel bits",	"",	QP_KEY_TAG_NONE,	pf.blueBits)
+			<< tcu::TestLog::Integer("AlphaBits",	"Alpha channel bits",	"",	QP_KEY_TAG_NONE,	pf.alphaBits)
+			<< tcu::TestLog::Integer("DepthBits",	"Depth bits",			"",	QP_KEY_TAG_NONE,	rt.getDepthBits())
+			<< tcu::TestLog::Integer("StencilBits",	"Stencil bits",			"",	QP_KEY_TAG_NONE,	rt.getStencilBits())
+			<< tcu::TestLog::Integer("NumSamples",	"Number of samples",	"",	QP_KEY_TAG_NONE,	rt.getNumSamples())
+			<< tcu::TestLog::Integer("Width",		"Width",				"",	QP_KEY_TAG_NONE,	rt.getWidth())
+			<< tcu::TestLog::Integer("Height",		"Height",				"",	QP_KEY_TAG_NONE,	rt.getHeight());
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+InfoTests::InfoTests (Context& context)
+	: TestCaseGroup(context, "info", "Platform information queries")
+{
+}
+
+InfoTests::~InfoTests (void)
+{
+}
+
+void InfoTests::init (void)
+{
+	addChild(new QueryStringCase(m_context, "vendor",					"Vendor String",					GL_VENDOR));
+	addChild(new QueryStringCase(m_context, "renderer",					"Renderer String",					GL_RENDERER));
+	addChild(new QueryStringCase(m_context, "version",					"Version String",					GL_VERSION));
+	addChild(new QueryStringCase(m_context, "shading_language_version",	"Shading Language Version String",	GL_SHADING_LANGUAGE_VERSION));
+	addChild(new QueryExtensionsCase(m_context));
+	addChild(new RenderTargetInfoCase(m_context));
+}
+
+} // gles31
+} // deqp
diff --git a/modules/gles31/tes31InfoTests.hpp b/modules/gles31/tes31InfoTests.hpp
new file mode 100644
index 0000000..c923c17
--- /dev/null
+++ b/modules/gles31/tes31InfoTests.hpp
@@ -0,0 +1,46 @@
+#ifndef _TES31INFOTESTS_HPP
+#define _TES31INFOTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Platform Information and Capability Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tes31TestCase.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+
+class InfoTests : public TestCaseGroup
+{
+public:
+				InfoTests		(Context& context);
+				~InfoTests		(void);
+
+	void		init			(void);
+};
+
+} // gles31
+} // deqp
+
+#endif // _TES31INFOTESTS_HPP
diff --git a/modules/gles31/tes31TestCase.cpp b/modules/gles31/tes31TestCase.cpp
new file mode 100644
index 0000000..53af1d4
--- /dev/null
+++ b/modules/gles31/tes31TestCase.cpp
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3plus test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestCase.hpp"
diff --git a/modules/gles31/tes31TestCase.hpp b/modules/gles31/tes31TestCase.hpp
new file mode 100644
index 0000000..56f96b2
--- /dev/null
+++ b/modules/gles31/tes31TestCase.hpp
@@ -0,0 +1,86 @@
+#ifndef _TES31TESTCASE_HPP
+#define _TES31TESTCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.1 test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "tes31Context.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+
+class TestCaseGroup : public tcu::TestCaseGroup
+{
+public:
+						TestCaseGroup		(Context& context, const char* name, const char* description);
+						TestCaseGroup		(Context& context, const char* name, const char* description, const std::vector<TestNode*>& children);
+	virtual				~TestCaseGroup		(void) {}
+
+	Context&			getContext			(void) { return m_context; }
+
+protected:
+	Context&			m_context;
+};
+
+class TestCase : public tcu::TestCase
+{
+public:
+						TestCase			(Context& context, const char* name, const char* description);
+						TestCase			(Context& context, tcu::TestNodeType nodeType, const char* name, const char* description);
+	virtual				~TestCase			(void) {}
+
+protected:
+	Context&			m_context;
+};
+
+inline TestCaseGroup::TestCaseGroup (Context& context, const char* name, const char* description)
+	: tcu::TestCaseGroup	(context.getTestContext(), name, description)
+	, m_context				(context)
+{
+}
+
+inline TestCaseGroup::TestCaseGroup (Context& context, const char* name, const char* description, const std::vector<TestNode*>& children)
+	: tcu::TestCaseGroup	(context.getTestContext(), name, description, children)
+	, m_context				(context)
+{
+}
+
+inline TestCase::TestCase (Context& context, const char* name, const char* description)
+	: tcu::TestCase			(context.getTestContext(), name, description)
+	, m_context				(context)
+{
+}
+
+inline TestCase::TestCase (Context& context, tcu::TestNodeType nodeType, const char* name, const char* description)
+	: tcu::TestCase			(context.getTestContext(), nodeType, name, description)
+	, m_context				(context)
+{
+}
+
+} // gles31
+} // deqp
+
+#endif // _TES31TESTCASE_HPP
diff --git a/modules/gles31/tes31TestCaseWrapper.cpp b/modules/gles31/tes31TestCaseWrapper.cpp
new file mode 100644
index 0000000..05b89e3
--- /dev/null
+++ b/modules/gles31/tes31TestCaseWrapper.cpp
@@ -0,0 +1,100 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.1 Test Case Wrapper.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestCaseWrapper.hpp"
+#include "gluStateReset.hpp"
+#include "tcuTestLog.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+
+using tcu::TestLog;
+
+TestCaseWrapper::TestCaseWrapper (tcu::TestContext& testCtx, glu::RenderContext& renderCtx)
+	: tcu::TestCaseWrapper	(testCtx)
+	, m_renderCtx			(renderCtx)
+{
+//	TCU_CHECK(renderCtx.getType() == glu::CONTEXTTYPE_GL43_CORE);
+}
+
+TestCaseWrapper::~TestCaseWrapper (void)
+{
+}
+
+bool TestCaseWrapper::initTestCase (tcu::TestCase* testCase)
+{
+	return tcu::TestCaseWrapper::initTestCase(testCase);
+}
+
+bool TestCaseWrapper::deinitTestCase (tcu::TestCase* testCase)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (!tcu::TestCaseWrapper::deinitTestCase(testCase))
+		return false;
+
+	try
+	{
+		// Reset state
+		glu::resetState(m_renderCtx);
+	}
+	catch (const std::exception& e)
+	{
+		log << e;
+		log << TestLog::Message << "Error in state reset, test program will terminate." << TestLog::EndMessage;
+		return false;
+	}
+
+	return true;
+}
+
+tcu::TestNode::IterateResult TestCaseWrapper::iterateTestCase (tcu::TestCase* testCase)
+{
+	tcu::TestCase::IterateResult result = tcu::TestCaseWrapper::iterateTestCase(testCase);
+
+	// Call implementation specific post-iterate routine (usually handles native events and swaps buffers)
+	try
+	{
+		m_renderCtx.postIterate();
+		return result;
+	}
+	catch (const tcu::ResourceError& e)
+	{
+		m_testCtx.getLog() << e;
+		m_testCtx.setTestResult(QP_TEST_RESULT_RESOURCE_ERROR, "Resource error in context post-iteration routine");
+		return tcu::TestNode::STOP;
+	}
+	catch (const std::exception& e)
+	{
+		m_testCtx.getLog() << e;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Error in context post-iteration routine");
+		return tcu::TestNode::STOP;
+	}
+}
+
+} // gles31
+} // deqp
diff --git a/modules/gles31/tes31TestCaseWrapper.hpp b/modules/gles31/tes31TestCaseWrapper.hpp
new file mode 100644
index 0000000..941e7f7
--- /dev/null
+++ b/modules/gles31/tes31TestCaseWrapper.hpp
@@ -0,0 +1,54 @@
+#ifndef _TES31TESTCASEWRAPPER_HPP
+#define _TES31TESTCASEWRAPPER_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.1 Test Case Wrapper.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCaseWrapper.hpp"
+#include "gluRenderContext.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+
+class TestCaseWrapper : public tcu::TestCaseWrapper
+{
+public:
+											TestCaseWrapper			(tcu::TestContext& testCtx, glu::RenderContext& renderCtx);
+	virtual									~TestCaseWrapper		(void);
+
+	virtual bool							initTestCase			(tcu::TestCase* testCase);
+
+	// If deinit returns false, test execution will be aborted.
+	virtual bool							deinitTestCase			(tcu::TestCase* testCase);
+	virtual tcu::TestNode::IterateResult	iterateTestCase			(tcu::TestCase* testCase);
+
+private:
+	glu::RenderContext&						m_renderCtx;
+};
+
+} // gles31
+} // deqp
+
+#endif // _TES31TESTCASEWRAPPER_HPP
diff --git a/modules/gles31/tes31TestPackage.cpp b/modules/gles31/tes31TestPackage.cpp
new file mode 100644
index 0000000..54ce5aa
--- /dev/null
+++ b/modules/gles31/tes31TestPackage.cpp
@@ -0,0 +1,101 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.1 Test Package
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestPackage.hpp"
+#include "tes31InfoTests.hpp"
+#include "es31fFunctionalTests.hpp"
+#include "es31sStressTests.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+
+PackageContext::PackageContext (tcu::TestContext& testCtx)
+	: m_context		(DE_NULL)
+	, m_caseWrapper	(DE_NULL)
+{
+	try
+	{
+		m_context		= new Context(testCtx);
+		m_caseWrapper	= new TestCaseWrapper(testCtx, m_context->getRenderContext());
+	}
+	catch (...)
+	{
+		delete m_caseWrapper;
+		delete m_context;
+
+		throw;
+	}
+}
+
+PackageContext::~PackageContext (void)
+{
+	delete m_caseWrapper;
+	delete m_context;
+}
+
+TestPackage::TestPackage (tcu::TestContext& testCtx)
+	: tcu::TestPackage	(testCtx, "dEQP-GLES31", "dEQP OpenGL ES 3.1 Tests")
+	, m_packageCtx		(DE_NULL)
+	, m_archive			(testCtx.getRootArchive(), "gles31/")
+{
+}
+
+TestPackage::~TestPackage (void)
+{
+	// Destroy children first since destructors may access context.
+	TestNode::deinit();
+	delete m_packageCtx;
+}
+
+void TestPackage::init (void)
+{
+	try
+	{
+		// Create context
+		m_packageCtx = new PackageContext(m_testCtx);
+
+		// Add main test groups
+		addChild(new InfoTests						(m_packageCtx->getContext()));
+		addChild(new Functional::FunctionalTests	(m_packageCtx->getContext()));
+		addChild(new Stress::StressTests			(m_packageCtx->getContext()));
+	}
+	catch (...)
+	{
+		delete m_packageCtx;
+		m_packageCtx = DE_NULL;
+
+		throw;
+	}
+}
+
+void TestPackage::deinit (void)
+{
+	TestNode::deinit();
+	delete m_packageCtx;
+	m_packageCtx = DE_NULL;
+}
+
+} // gles31
+} // deqp
diff --git a/modules/gles31/tes31TestPackage.hpp b/modules/gles31/tes31TestPackage.hpp
new file mode 100644
index 0000000..cfe339b
--- /dev/null
+++ b/modules/gles31/tes31TestPackage.hpp
@@ -0,0 +1,71 @@
+#ifndef _TES31TESTPACKAGE_HPP
+#define _TES31TESTPACKAGE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.1 Test Package
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestPackage.hpp"
+#include "tes31TestCaseWrapper.hpp"
+#include "tes31Context.hpp"
+#include "tcuResource.hpp"
+
+namespace deqp
+{
+namespace gles31
+{
+
+class PackageContext
+{
+public:
+									PackageContext			(tcu::TestContext& testCtx);
+									~PackageContext			(void);
+
+	Context&						getContext				(void) { return *m_context;		}
+	tcu::TestCaseWrapper&			getTestCaseWrapper		(void) { return *m_caseWrapper; }
+
+private:
+	Context*						m_context;
+	TestCaseWrapper*				m_caseWrapper;
+};
+
+class TestPackage : public tcu::TestPackage
+{
+public:
+									TestPackage				(tcu::TestContext& testCtx);
+	virtual							~TestPackage			(void);
+
+	virtual void					init					(void);
+	virtual void					deinit					(void);
+
+	tcu::TestCaseWrapper&			getTestCaseWrapper		(void) { return m_packageCtx->getTestCaseWrapper(); }
+	tcu::Archive&					getArchive				(void) { return m_archive; }
+
+private:
+	PackageContext*					m_packageCtx;
+	tcu::ResourcePrefix				m_archive;
+};
+
+} // gles31
+} // deqp
+
+#endif // _TES31TESTPACKAGE_HPP
diff --git a/modules/gles31/tes31TestPackageEntry.cpp b/modules/gles31/tes31TestPackageEntry.cpp
new file mode 100644
index 0000000..6f6fd8a
--- /dev/null
+++ b/modules/gles31/tes31TestPackageEntry.cpp
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.1 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief OpenGL ES 3.1 Test Package Entry Point.
+ *//*--------------------------------------------------------------------*/
+
+#include "tes31TestPackage.hpp"
+
+// Register package to test executor.
+
+static tcu::TestPackage* createTestPackage (tcu::TestContext& testCtx)
+{
+	return new deqp::gles31::TestPackage(testCtx);
+}
+
+tcu::TestPackageDescriptor g_gles31PackageDescriptor("dEQP-GLES31", createTestPackage);
diff --git a/modules/glshared/CMakeLists.txt b/modules/glshared/CMakeLists.txt
new file mode 100644
index 0000000..4a2b9aa
--- /dev/null
+++ b/modules/glshared/CMakeLists.txt
@@ -0,0 +1,72 @@
+# Shared GL(ES) test code
+
+set(DEQP_GL_SHARED_SRCS
+	glsBuiltinPrecisionTests.cpp
+	glsBuiltinPrecisionTests.hpp
+	glsCalibration.cpp
+	glsCalibration.hpp
+	glsFboCompletenessTests.cpp
+	glsFboCompletenessTests.hpp
+	glsFboUtil.cpp
+	glsFboUtil.hpp
+	glsFragmentOpUtil.cpp
+	glsFragmentOpUtil.hpp
+	glsLifetimeTests.cpp
+	glsLifetimeTests.hpp
+	glsLongStressCase.cpp
+	glsLongStressCase.hpp
+	glsLongStressTestUtil.cpp
+	glsLongStressTestUtil.hpp
+	glsMemoryStressCase.cpp
+	glsMemoryStressCase.hpp
+	glsRandomShaderCase.cpp
+	glsRandomShaderCase.hpp
+	glsShaderConstExprTests.cpp
+	glsShaderConstExprTests.hpp
+	glsShaderLibrary.cpp
+	glsShaderLibrary.hpp
+	glsShaderLibraryCase.cpp
+	glsShaderLibraryCase.hpp
+	glsShaderPerformanceCase.cpp
+	glsShaderPerformanceCase.hpp
+	glsShaderRenderCase.cpp
+	glsShaderRenderCase.hpp
+	glsStateQueryUtil.hpp
+	glsScissorTests.cpp
+	glsScissorTests.hpp
+	glsTextureTestUtil.cpp
+	glsTextureTestUtil.hpp
+	glsUniformBlockCase.cpp
+	glsUniformBlockCase.hpp
+	glsVertexArrayTests.cpp
+	glsVertexArrayTests.hpp
+	glsDrawTest.cpp
+	glsDrawTest.hpp
+	glsShaderExecUtil.cpp
+	glsShaderExecUtil.hpp
+	glsSamplerObjectTest.hpp
+	glsSamplerObjectTest.cpp
+	glsShaderPerformanceMeasurer.cpp
+	glsShaderPerformanceMeasurer.hpp
+	glsRasterizationTestUtil.cpp
+	glsRasterizationTestUtil.hpp
+	glsInteractionTestUtil.cpp
+	glsInteractionTestUtil.hpp
+	glsRandomShaderProgram.cpp
+	glsRandomShaderProgram.hpp
+	glsFragOpInteractionCase.cpp
+	glsFragOpInteractionCase.hpp
+	glsStateChangePerfTestCases.cpp
+	glsStateChangePerfTestCases.hpp
+	glsBufferTestUtil.cpp
+	glsBufferTestUtil.hpp
+	glsAttributeLocationTests.hpp
+	glsAttributeLocationTests.cpp
+	glsRandomUniformBlockCase.cpp
+	glsRandomUniformBlockCase.hpp
+	glsTextureBufferCase.hpp
+	glsTextureBufferCase.cpp
+	)
+
+add_library(deqp-gl-shared STATIC ${DEQP_GL_SHARED_SRCS})
+target_link_libraries(deqp-gl-shared glutil glwrapper randomshaders tcutil glutil-sglr)
diff --git a/modules/glshared/glsAttributeLocationTests.cpp b/modules/glshared/glsAttributeLocationTests.cpp
new file mode 100644
index 0000000..2840f31
--- /dev/null
+++ b/modules/glshared/glsAttributeLocationTests.cpp
@@ -0,0 +1,1537 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Attribute location tests
+ *//*--------------------------------------------------------------------*/
+
+#include "glsAttributeLocationTests.hpp"
+
+#include "tcuStringTemplate.hpp"
+#include "tcuTestLog.hpp"
+
+#include "gluDefs.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluStrUtil.hpp"
+
+#include "glwFunctions.hpp"
+
+#include "deStringUtil.hpp"
+
+#include <map>
+#include <set>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <cstring>
+
+#include "glw.h"
+
+using tcu::TestLog;
+
+using std::string;
+using std::vector;
+using std::set;
+using std::map;
+using std::pair;
+
+using namespace deqp::gls::AttributeLocationTestUtil;
+
+namespace deqp
+{
+namespace gls
+{
+namespace
+{
+
+deInt32 getBoundLocation (const map<string, deUint32>& bindings, const string& attrib)
+{
+	std::map<string, deUint32>::const_iterator iter = bindings.find(attrib);
+
+	return (iter == bindings.end() ? (deInt32)Attribute::LOC_UNDEF : iter->second);
+}
+
+bool hasAttributeAliasing (const vector<Attribute>& attributes, const map<string, deUint32>& bindings)
+{
+	vector<bool> reservedSpaces;
+
+	for (int attribNdx = 0; attribNdx < (int)attributes.size(); attribNdx++)
+	{
+		const deInt32	location	= getBoundLocation(bindings, attributes[attribNdx].getName());
+		const deUint32	size		= attributes[attribNdx].getType().getLocationSize();
+
+		if (location != Attribute::LOC_UNDEF)
+		{
+			if (reservedSpaces.size() < location + size)
+				reservedSpaces.resize(location + size, false);
+
+			for (int i = 0; i < (int)size; i++)
+			{
+				if (reservedSpaces[location + i])
+					return true;
+
+				reservedSpaces[location + i] = true;
+			}
+		}
+	}
+
+	return false;
+}
+
+deInt32 getMaxAttributeLocations (glu::RenderContext& renderCtx)
+{
+	const glw::Functions& gl = renderCtx.getFunctions();
+	deInt32 maxAttribs;
+
+	gl.getIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttribs);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
+
+	return maxAttribs;
+}
+
+string generateAttributeDefinitions (const vector<Attribute>& attributes)
+{
+	std::ostringstream src;
+
+	for (vector<Attribute>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
+	{
+		if (iter->getLayoutLocation() != Attribute::LOC_UNDEF)
+			src << "layout(location = " << iter->getLayoutLocation() << ") ";
+
+		src << "${VTX_INPUT} mediump "
+			<< iter->getType().getName() << " "
+			<< iter->getName()
+			<<  (iter->getArraySize() != Attribute::NOT_ARRAY ? "[" + de::toString(iter->getArraySize()) + "]" : "") << ";\n";
+	}
+
+	return src.str();
+}
+
+string generateConditionUniformDefinitions (const vector<Attribute>& attributes)
+{
+	std::ostringstream src;
+	set<string> conditions;
+
+	for (vector<Attribute>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
+	{
+		if (iter->getCondition() != Cond::COND_NEVER && iter->getCondition() != Cond::COND_ALWAYS)
+			conditions.insert(iter->getCondition().getName());
+	}
+
+	for (set<string>::const_iterator iter = conditions.begin(); iter != conditions.end(); ++iter)
+			src << "uniform mediump float u_" << (*iter) << ";\n";
+
+	return src.str();
+}
+
+string generateToVec4Expression (const Attribute& attrib, int id=-1)
+{
+	const string		variableName(attrib.getName() + (attrib.getArraySize() != Attribute::NOT_ARRAY ? "[" + de::toString(id) + "]" : ""));
+	std::ostringstream	src;
+
+	switch (attrib.getType().getGLTypeEnum())
+	{
+		case GL_INT_VEC2:
+		case GL_UNSIGNED_INT_VEC2:
+		case GL_FLOAT_VEC2:
+			src << "vec4(" << variableName << ".xy, " << variableName << ".yx)";
+			break;
+
+		case GL_INT_VEC3:
+		case GL_UNSIGNED_INT_VEC3:
+		case GL_FLOAT_VEC3:
+			src << "vec4(" << variableName << ".xyz, " << variableName << ".x)";
+			break;
+
+		default:
+			src << "vec4(" << variableName << ")";
+			break;
+	}
+
+	return src.str();
+}
+
+string generateOutputCode (const vector<Attribute>& attributes)
+{
+	std::ostringstream src;
+
+	for (vector<Attribute>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
+	{
+		if (iter->getCondition() == Cond::COND_NEVER)
+		{
+			src <<
+			"\tif (0 != 0)\n"
+			"\t{\n";
+
+			if (iter->getArraySize() == Attribute::NOT_ARRAY)
+				src << "\t\tcolor += " << generateToVec4Expression(*iter) << ";\n";
+			else
+			{
+				for (int i = 0; i < iter->getArraySize(); i++)
+					src << "\t\tcolor += " << generateToVec4Expression(*iter, i) << ";\n";
+			}
+
+			src << "\t}\n";
+		}
+		else if (iter->getCondition() == Cond::COND_ALWAYS)
+		{
+			if (iter->getArraySize() == Attribute::NOT_ARRAY)
+				src << "\tcolor += " << generateToVec4Expression(*iter) << ";\n";
+			else
+			{
+				for (int i = 0; i < iter->getArraySize(); i++)
+					src << "\tcolor += " << generateToVec4Expression(*iter, i) << ";\n";
+			}
+		}
+		else
+		{
+			src <<
+			"\tif (u_" << iter->getCondition().getName() << (iter->getCondition().getNegate() ? " != " : " == ") << "0.0)\n"
+			"\t{\n";
+
+			if (iter->getArraySize() == Attribute::NOT_ARRAY)
+				src << "\t\tcolor += " << generateToVec4Expression(*iter) << ";\n";
+			else
+			{
+				for (int i = 0; i < iter->getArraySize(); i++)
+					src << "\t\tcolor += " << generateToVec4Expression(*iter, i) << ";\n";
+			}
+
+			src <<
+			"\t}\n";
+		}
+	}
+
+	return src.str();
+}
+
+string generateVertexShaderTemplate (const vector<Attribute>& attributes)
+{
+	std::ostringstream src;
+
+	src <<	"${VERSION}\n"
+			"${VTX_OUTPUT} mediump vec4 v_color;\n";
+
+	src << generateAttributeDefinitions(attributes)
+		<< "\n"
+		<< generateConditionUniformDefinitions(attributes)
+		<< "\n";
+
+	src <<	"void main (void)\n"
+			"{\n"
+			"\tmediump vec4 color = vec4(0.0);\n"
+			"\n";
+
+	src << generateOutputCode(attributes);
+
+	src <<	"\n"
+			"\tv_color = color;\n"
+			"\tgl_Position = color;\n"
+			"}\n";
+
+	return src.str();
+}
+
+string createVertexShaderSource (glu::RenderContext& renderCtx, const vector<Attribute>& attributes, bool attributeAliasing)
+{
+	// \note On GLES only GLSL #version 100 supports aliasing
+	const glu::GLSLVersion		contextGLSLVersion		= glu::getContextTypeGLSLVersion(renderCtx.getType());
+	const glu::GLSLVersion		glslVersion				= (attributeAliasing && glu::glslVersionIsES(contextGLSLVersion) ? glu::GLSL_VERSION_100_ES : contextGLSLVersion);
+	const bool					usesInOutQualifiers		= glu::glslVersionUsesInOutQualifiers(glslVersion);
+	const tcu::StringTemplate	vertexShaderTemplate(generateVertexShaderTemplate(attributes));
+
+	map<string, string> parameters;
+
+	parameters["VERSION"]					= glu::getGLSLVersionDeclaration(glslVersion);
+	parameters["VTX_OUTPUT"]				= (usesInOutQualifiers ? "out"				: "varying");
+	parameters["VTX_INPUT"]					= (usesInOutQualifiers ? "in"				: "attribute");
+	parameters["FRAG_INPUT"]				= (usesInOutQualifiers ? "in"				: "varying");
+	parameters["FRAG_OUTPUT_VAR"]			= (usesInOutQualifiers ? "dEQP_FragColor"	: "gl_FragColor");
+	parameters["FRAG_OUTPUT_DECLARATION"]	= (usesInOutQualifiers
+													? "layout(location=0) out mediump vec4 dEQP_FragColor;"
+													: "");
+
+	return vertexShaderTemplate.specialize(parameters);
+}
+
+string createFragmentShaderSource (glu::RenderContext& renderCtx, bool attributeAliasing)
+{
+	const char* const fragmentShaderSource =
+		"${VERSION}\n"
+		"${FRAG_OUTPUT_DECLARATION}\n"
+		"${FRAG_INPUT} mediump vec4 v_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"\t${FRAG_OUTPUT_VAR} = v_color;\n"
+		"}\n";
+
+	// \note On GLES only GLSL #version 100 supports aliasing
+	const glu::GLSLVersion		contextGLSLVersion		= glu::getContextTypeGLSLVersion(renderCtx.getType());
+	const glu::GLSLVersion		glslVersion				= (attributeAliasing && glu::glslVersionIsES(contextGLSLVersion) ? glu::GLSL_VERSION_100_ES : contextGLSLVersion);
+	const tcu::StringTemplate	fragmentShaderTemplate(fragmentShaderSource);
+	const bool					usesInOutQualifiers		= glu::glslVersionUsesInOutQualifiers(glslVersion);
+
+	map<string, string> parameters;
+
+	parameters["VERSION"]					= glu::getGLSLVersionDeclaration(glslVersion);
+	parameters["VTX_OUTPUT"]				= (usesInOutQualifiers ? "out"				: "varying");
+	parameters["VTX_INPUT"]					= (usesInOutQualifiers ? "in"				: "attribute");
+	parameters["FRAG_INPUT"]				= (usesInOutQualifiers ? "in"				: "varying");
+	parameters["FRAG_OUTPUT_VAR"]			= (usesInOutQualifiers ? "dEQP_FragColor"	: "gl_FragColor");
+	parameters["FRAG_OUTPUT_DECLARATION"]	= (usesInOutQualifiers
+													? "layout(location=0) out mediump vec4 dEQP_FragColor;"
+													: "");
+
+	return fragmentShaderTemplate.specialize(parameters);
+}
+
+string getShaderInfoLog (const glw::Functions& gl, deUint32 shader)
+{
+	deInt32	length = 0;
+	string	infoLog;
+
+	gl.getShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv()");
+
+	infoLog.resize(length, '\0');
+
+	gl.getShaderInfoLog(shader, (glw::GLsizei)infoLog.length(), DE_NULL, &(infoLog[0]));
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderInfoLog()");
+
+	return infoLog;
+}
+
+bool getShaderCompileStatus (const glw::Functions& gl, deUint32 shader)
+{
+	deInt32 status;
+
+	gl.getShaderiv(shader, GL_COMPILE_STATUS, &status);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv()");
+
+	return status == GL_TRUE;
+}
+
+string getProgramInfoLog (const glw::Functions& gl, deUint32 program)
+{
+	deInt32	length = 0;
+	string	infoLog;
+
+	gl.getProgramiv(program, GL_INFO_LOG_LENGTH, &length);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramiv()");
+
+	infoLog.resize(length, '\0');
+
+	gl.getProgramInfoLog(program, (glw::GLsizei)infoLog.length(), DE_NULL, &(infoLog[0]));
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramInfoLog()");
+
+	return infoLog;
+}
+
+bool getProgramLinkStatus (const glw::Functions& gl, deUint32 program)
+{
+	deInt32 status;
+
+	gl.getProgramiv(program, GL_LINK_STATUS, &status);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramiv()");
+
+	return status == GL_TRUE;
+}
+
+void logProgram (TestLog& log, const glw::Functions& gl, deUint32 program)
+{
+	const bool				programLinkOk	= getProgramLinkStatus(gl, program);
+	const string			programInfoLog	= getProgramInfoLog(gl, program);
+	tcu::ScopedLogSection	linkInfo		(log, "Program Link Info", "Program Link Info");
+
+	{
+		tcu::ScopedLogSection infoLogSection(log, "Info Log", "Info Log");
+
+		log << TestLog::Message << programInfoLog << TestLog::EndMessage;
+	}
+
+	log << TestLog::Message << "Link result: " << (programLinkOk ? "Ok" : "Fail") << TestLog::EndMessage;
+}
+
+void logShaders (TestLog&		log,
+				const string&	vertexShaderSource,
+				const string&	vertexShaderInfoLog,
+				bool			vertexCompileOk,
+				const string&	fragmentShaderSource,
+				const string&	fragmentShaderInfoLog,
+				bool			fragmentCompileOk)
+{
+	// \todo [mika] Log as real shader elements. Currently not supported by TestLog.
+	{
+		tcu::ScopedLogSection shaderSection(log, "Vertex Shader Info", "Vertex Shader Info");
+
+		log << TestLog::KernelSource(vertexShaderSource);
+
+		{
+			tcu::ScopedLogSection infoLogSection(log, "Info Log", "Info Log");
+
+			log << TestLog::Message << vertexShaderInfoLog << TestLog::EndMessage;
+		}
+
+		log << TestLog::Message << "Compilation result: " << (vertexCompileOk ? "Ok" : "Failed") << TestLog::EndMessage;
+	}
+
+	{
+		tcu::ScopedLogSection shaderSection(log, "Fragment Shader Info", "Fragment Shader Info");
+
+		log << TestLog::KernelSource(fragmentShaderSource);
+
+		{
+			tcu::ScopedLogSection infoLogSection(log, "Info Log", "Info Log");
+
+			log << TestLog::Message << fragmentShaderInfoLog << TestLog::EndMessage;
+		}
+
+		log << TestLog::Message << "Compilation result: " << (fragmentCompileOk ? "Ok" : "Failed") << TestLog::EndMessage;
+	}
+}
+
+pair<deUint32, deUint32> createAndAttachShaders (TestLog& log, glu::RenderContext& renderCtx, deUint32 program, const vector<Attribute>& attributes, bool attributeAliasing)
+{
+	const glw::Functions&	gl						= renderCtx.getFunctions();
+	const string			vertexShaderSource		= createVertexShaderSource(renderCtx, attributes, attributeAliasing);
+	const string			fragmentShaderSource	= createFragmentShaderSource(renderCtx, attributeAliasing);
+
+	const deUint32			vertexShader			= gl.createShader(GL_VERTEX_SHADER);
+	const deUint32			fragmentShader			= gl.createShader(GL_FRAGMENT_SHADER);
+
+	try
+	{
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glCreateShader()");
+
+		{
+			const char* const vertexShaderString	= vertexShaderSource.c_str();
+			const char* const fragmentShaderString	= fragmentShaderSource.c_str();
+
+			gl.shaderSource(vertexShader, 1, &vertexShaderString, DE_NULL);
+			gl.shaderSource(fragmentShader, 1, &fragmentShaderString, DE_NULL);
+
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glShaderSource()");
+		}
+
+		gl.compileShader(vertexShader);
+		gl.compileShader(fragmentShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glCompileShader()");
+
+		gl.attachShader(program, vertexShader);
+		gl.attachShader(program, fragmentShader);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glAttachShader()");
+
+		{
+			const bool		vertexCompileOk			= getShaderCompileStatus(gl, vertexShader);
+			const bool		fragmentCompileOk		= getShaderCompileStatus(gl, fragmentShader);
+
+			const string	vertexShaderInfoLog		= getShaderInfoLog(gl, vertexShader);
+			const string	fragmentShaderInfoLog	= getShaderInfoLog(gl, fragmentShader);
+
+			logShaders(log, vertexShaderSource, vertexShaderInfoLog, vertexCompileOk, fragmentShaderSource, fragmentShaderInfoLog, fragmentCompileOk);
+
+			TCU_CHECK_MSG(vertexCompileOk, "Vertex shader compilation failed");
+			TCU_CHECK_MSG(fragmentCompileOk, "Fragment shader compilation failed");
+		}
+
+		gl.deleteShader(vertexShader);
+		gl.deleteShader(fragmentShader);
+
+		return pair<deUint32, deUint32>(vertexShader, fragmentShader);
+	}
+	catch (...)
+	{
+		if (vertexShader != 0)
+			gl.deleteShader(vertexShader);
+
+		if (fragmentShader != 0)
+			gl.deleteShader(fragmentShader);
+
+		throw;
+	}
+}
+
+void bindAttributes (TestLog& log, const glw::Functions& gl, deUint32 program, const vector<Bind>& binds)
+{
+	for (vector<Bind>::const_iterator iter = binds.begin(); iter != binds.end(); ++iter)
+	{
+		log << TestLog::Message << "Bind attribute: '" << iter->getAttributeName() << "' to " << iter->getLocation() << TestLog::EndMessage;
+		gl.bindAttribLocation(program, iter->getLocation(), iter->getAttributeName().c_str());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindAttribLocation()");
+	}
+}
+
+void logAttributes (TestLog& log, const vector<Attribute>& attributes)
+{
+	for (int attribNdx = 0; attribNdx < (int)attributes.size(); attribNdx++)
+	{
+		const Attribute& attrib = attributes[attribNdx];
+
+		log << TestLog::Message
+			<< "Type: " << attrib.getType().getName()
+			<< ", Name: " << attrib.getName()
+			<< (attrib.getLayoutLocation()	!= Attribute::LOC_UNDEF ? ", Layout location "	+ de::toString(attrib.getLayoutLocation()) : "")
+			<< TestLog::EndMessage;
+	}
+}
+
+bool checkActiveAttribQuery (TestLog& log, const glw::Functions& gl, deUint32 program, const vector<Attribute>& attributes)
+{
+	deInt32					activeAttribCount = 0;
+	set<string>				activeAttributes;
+	bool					isOk = true;
+
+	gl.getProgramiv(program, GL_ACTIVE_ATTRIBUTES, &activeAttribCount);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &activeAttribCount)");
+
+	for (int activeAttribNdx = 0; activeAttribNdx < activeAttribCount; activeAttribNdx++)
+	{
+		char			name[128];
+		const size_t	maxNameSize = DE_LENGTH_OF_ARRAY(name) - 1;
+		deInt32			length = 0;
+		deInt32			size = 0;
+		deUint32		type = 0;
+
+		std::memset(name, 0, sizeof(name));
+
+		gl.getActiveAttrib(program, activeAttribNdx, maxNameSize, &length, &size, &type, name);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetActiveAttrib()");
+
+		log << TestLog::Message
+			<< "glGetActiveAttrib(program"
+			<< ", index=" << activeAttribNdx
+			<< ", bufSize=" << maxNameSize
+			<< ", length=" << length
+			<< ", size=" << size
+			<< ", type=" << glu::getShaderVarTypeStr(type)
+			<< ", name='" << name << "')" << TestLog::EndMessage;
+
+		{
+			bool found = false;
+
+			for (int attribNdx = 0; attribNdx < (int)attributes.size(); attribNdx++)
+			{
+				const Attribute& attrib = attributes[attribNdx];
+
+				if (attrib.getName() == name)
+				{
+					if (type != attrib.getType().getGLTypeEnum())
+					{
+						log << TestLog::Message
+							<< "Error: Wrong type " << glu::getShaderVarTypeStr(type)
+							<< " expected " << glu::getShaderVarTypeStr(attrib.getType().getGLTypeEnum())
+							<< TestLog::EndMessage;
+
+						isOk = false;
+					}
+
+					if (attrib.getArraySize() == Attribute::NOT_ARRAY)
+					{
+						if (size != 1)
+						{
+							log << TestLog::Message << "Error: Wrong size " << size << " expected " << 1 << TestLog::EndMessage;
+							isOk = false;
+						}
+					}
+					else
+					{
+						if (size != attrib.getArraySize())
+						{
+							log << TestLog::Message << "Error: Wrong size " << size << " expected " << attrib.getArraySize() << TestLog::EndMessage;
+							isOk = false;
+						}
+					}
+
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				log << TestLog::Message << "Error: Unknown attribute '" << name << "' returned by glGetActiveAttrib()." << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+
+		activeAttributes.insert(name);
+	}
+
+	for (int attribNdx = 0; attribNdx < (int)attributes.size(); attribNdx++)
+	{
+		const Attribute&	attrib		= attributes[attribNdx];
+		const bool			isActive	= attrib.getCondition() != Cond::COND_NEVER;
+
+		if (isActive)
+		{
+			if (activeAttributes.find(attrib.getName()) == activeAttributes.end())
+			{
+				log << TestLog::Message << "Error: Active attribute " << attrib.getName() << " wasn't returned by glGetActiveAttrib()." << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+		else
+		{
+			if (activeAttributes.find(attrib.getName()) != activeAttributes.end())
+				log << TestLog::Message << "Note: Inactive attribute " << attrib.getName() << " was returned by glGetActiveAttrib()." << TestLog::EndMessage;
+		}
+	}
+
+	return isOk;
+}
+
+bool checkAttribLocationQuery (TestLog& log, const glw::Functions& gl, deUint32 program, const vector<Attribute>& attributes, const map<string, deUint32>& bindings)
+{
+	bool isOk = true;
+
+	for (int attribNdx = 0; attribNdx < (int)attributes.size(); attribNdx++)
+	{
+		const Attribute&	attrib				= attributes[attribNdx];
+		const deInt32		expectedLocation	= (attrib.getLayoutLocation() != Attribute::LOC_UNDEF ? attrib.getLayoutLocation() : getBoundLocation(bindings, attrib.getName()));
+		const deInt32		location			= gl.getAttribLocation(program, attrib.getName().c_str());
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetAttribLocation()");
+
+		log << TestLog::Message
+			<< location << " = glGetAttribLocation(program, \"" << attrib.getName() << "\")"
+			<< (attrib.getCondition() != Cond::COND_NEVER && expectedLocation != Attribute::LOC_UNDEF ? ", expected " + de::toString(expectedLocation) : "")
+			<< "." << TestLog::EndMessage;
+
+		if (attrib.getCondition() == Cond::COND_NEVER && location != -1)
+			log << TestLog::Message << "\tNote: Inactive attribute with location." << TestLog::EndMessage;
+
+		if (attrib.getCondition() != Cond::COND_NEVER && expectedLocation != Attribute::LOC_UNDEF && expectedLocation != location)
+			log << TestLog::Message << "\tError: Invalid attribute location." << TestLog::EndMessage;
+
+		isOk &= (attrib.getCondition() == Cond::COND_NEVER || expectedLocation == Attribute::LOC_UNDEF || expectedLocation == location);
+	}
+
+	return isOk;
+}
+
+bool checkQuery (TestLog& log, const glw::Functions& gl, deUint32 program, const vector<Attribute>& attributes, const map<string, deUint32>& bindings)
+{
+	bool isOk = checkActiveAttribQuery(log, gl, program, attributes);
+
+	if (!checkAttribLocationQuery(log, gl, program, attributes, bindings))
+		isOk = false;
+
+	return isOk;
+}
+
+string generateTestName (const AttribType& type, int arraySize)
+{
+	return type.getName() + (arraySize != Attribute::NOT_ARRAY ? "_array_" + de::toString(arraySize) : "");
+}
+
+} // anonymous
+
+namespace AttributeLocationTestUtil
+{
+
+AttribType::AttribType (const string& name, deUint32 localSize, deUint32 typeEnum)
+	: m_name			(name)
+	, m_locationSize	(localSize)
+	, m_glTypeEnum		(typeEnum)
+{
+}
+
+Cond::Cond (const string& name, bool negate)
+	: m_negate	(negate)
+	, m_name	(name)
+{
+}
+
+Cond::Cond (ConstCond cond)
+	: m_negate	(cond != COND_NEVER)
+	, m_name	("__always__")
+{
+	DE_ASSERT(cond == COND_ALWAYS || cond == COND_NEVER);
+}
+
+Attribute::Attribute (const AttribType& type, const string& name, deInt32 layoutLocation, const Cond& cond, int arraySize)
+	: m_type			(type)
+	, m_name			(name)
+	, m_layoutLocation	(layoutLocation)
+	, m_cond			(cond)
+	, m_arraySize		(arraySize)
+{
+}
+
+Bind::Bind (const std::string& attribute, deUint32 location)
+	: m_attribute	(attribute)
+	, m_location	(location)
+{
+}
+
+void runTest (tcu::TestContext&			testCtx,
+			glu::RenderContext&			renderCtx,
+			const vector<Attribute>&	attributes,
+			const vector<Bind>&			preAttachBind,
+			const vector<Bind>&			preLinkBind,
+			const vector<Bind>&			postLinkBind,
+			bool						relink,
+			bool						reattach = false,
+			const vector<Attribute>&	reattachAttributes = vector<Attribute>())
+{
+	TestLog&					log			= testCtx.getLog();
+	const glw::Functions&		gl			= renderCtx.getFunctions();
+	deUint32					program 	= 0;
+	pair<deUint32, deUint32>	shaders;
+
+	try
+	{
+		bool					isOk			= true;
+		map<string, deUint32>	activeBindings;
+
+		for (int bindNdx = 0; bindNdx < (int)preAttachBind.size(); bindNdx++)
+			activeBindings[preAttachBind[bindNdx].getAttributeName()] = preAttachBind[bindNdx].getLocation();
+
+		for (int bindNdx = 0; bindNdx < (int)preLinkBind.size(); bindNdx++)
+			activeBindings[preLinkBind[bindNdx].getAttributeName()] = preLinkBind[bindNdx].getLocation();
+
+		{
+			tcu::ScopedLogSection section(log, "Attributes", "Attribute information");
+			logAttributes(testCtx.getLog(), attributes);
+		}
+
+		log << TestLog::Message << "Create program." << TestLog::EndMessage;
+		program = gl.createProgram();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glCreateProgram()");
+
+		if (!preAttachBind.empty())
+			bindAttributes(log, gl, program, preAttachBind);
+
+		log << TestLog::Message << "Create and attach shaders to program." << TestLog::EndMessage;
+		shaders = createAndAttachShaders(log, renderCtx, program, attributes, hasAttributeAliasing(attributes, activeBindings));
+
+		if (!preLinkBind.empty())
+			bindAttributes(log, gl, program, preLinkBind);
+
+		log << TestLog::Message << "Link program." << TestLog::EndMessage;
+
+		gl.linkProgram(program);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glLinkProgram()");
+
+		logProgram(log, gl, program);
+		TCU_CHECK_MSG(getProgramLinkStatus(gl, program), "Program link failed");
+
+		if (!checkQuery(log, gl, program, attributes, activeBindings))
+			isOk = false;
+
+		if (!postLinkBind.empty())
+		{
+			bindAttributes(log, gl, program, postLinkBind);
+
+			if (!checkQuery(log, gl, program, attributes, activeBindings))
+				isOk = false;
+		}
+
+		if (relink)
+		{
+			log << TestLog::Message << "Relink program." << TestLog::EndMessage;
+			gl.linkProgram(program);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glLinkProgram()");
+
+			logProgram(log, gl, program);
+			TCU_CHECK_MSG(getProgramLinkStatus(gl, program), "Program link failed");
+
+			for (int bindNdx = 0; bindNdx < (int)postLinkBind.size(); bindNdx++)
+				activeBindings[postLinkBind[bindNdx].getAttributeName()] = postLinkBind[bindNdx].getLocation();
+
+			if (!checkQuery(log, gl, program, attributes, activeBindings))
+				isOk = false;
+		}
+
+		if (reattach)
+		{
+			gl.detachShader(program, shaders.first);
+			gl.detachShader(program, shaders.second);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glDetachShader()");
+
+			log << TestLog::Message << "Create and attach shaders to program." << TestLog::EndMessage;
+			createAndAttachShaders(log, renderCtx, program, reattachAttributes, hasAttributeAliasing(reattachAttributes, activeBindings));
+
+			log << TestLog::Message << "Relink program." << TestLog::EndMessage;
+			gl.linkProgram(program);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glLinkProgram()");
+
+			logProgram(log, gl, program);
+			TCU_CHECK_MSG(getProgramLinkStatus(gl, program), "Program link failed");
+
+			if (!checkQuery(log, gl, program, reattachAttributes, activeBindings))
+				isOk = false;
+		}
+
+		gl.deleteProgram(program);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteProgram()");
+
+		if (isOk)
+			testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	}
+	catch (...)
+	{
+		if (program)
+			gl.deleteProgram(program);
+
+		throw;
+	}
+}
+
+} // AttributeLocationTestUtil
+
+BindAttributeTest::BindAttributeTest (tcu::TestContext&		testCtx,
+									  glu::RenderContext&	renderCtx,
+									  const AttribType&		type,
+									  int					arraySize)
+	: TestCase		(testCtx, generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult BindAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+
+	attributes.push_back(Attribute(m_type, "a_0", Attribute::LOC_UNDEF, Cond::COND_ALWAYS, m_arraySize));
+	bindings.push_back(Bind("a_0", 3));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+BindMaxAttributesTest::BindMaxAttributesTest (tcu::TestContext&		testCtx,
+											  glu::RenderContext&	renderCtx,
+											  const AttribType&		type,
+											  int					arraySize)
+	: TestCase		(testCtx,  generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult BindMaxAttributesTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const deInt32		maxAttributes		= getMaxAttributeLocations(m_renderCtx);
+	const int			arrayElementCount	= (m_arraySize != Attribute::NOT_ARRAY ? m_arraySize : 1);
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+	int					ndx = 0;
+
+	m_testCtx.getLog() << TestLog::Message << "GL_MAX_VERTEX_ATTRIBS: " << maxAttributes << TestLog::EndMessage;
+
+	for (int loc = maxAttributes - (arrayElementCount * m_type.getLocationSize()); loc >= 0; loc -= (arrayElementCount * m_type.getLocationSize()))
+	{
+		attributes.push_back(Attribute(m_type, "a_" + de::toString(ndx), Attribute::LOC_UNDEF, Cond::COND_ALWAYS, m_arraySize));
+		bindings.push_back(Bind("a_" + de::toString(ndx), loc));
+		ndx++;
+	}
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+BindAliasingAttributeTest::BindAliasingAttributeTest (tcu::TestContext&		testCtx,
+													  glu::RenderContext&	renderCtx,
+													  const AttribType&		type,
+													  int					offset,
+													  int					arraySize)
+	: TestCase		(testCtx,	("cond_" + generateTestName(type, arraySize) + (offset != 0 ? "_offset_" + de::toString(offset) : "")).c_str(),
+								("cond_" + generateTestName(type, arraySize) + (offset != 0 ? "_offset_" + de::toString(offset) : "")).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_offset		(offset)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult BindAliasingAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+
+	attributes.push_back(Attribute(m_type, "a_0", Attribute::LOC_UNDEF, Cond("A", true), m_arraySize));
+	attributes.push_back(Attribute(AttribType("vec4", 1, GL_FLOAT_VEC4), "a_1", Attribute::LOC_UNDEF, Cond("A", false)));
+	bindings.push_back(Bind("a_0", 1));
+	bindings.push_back(Bind("a_1", 1 + m_offset));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+BindMaxAliasingAttributeTest::BindMaxAliasingAttributeTest (tcu::TestContext&	testCtx,
+															glu::RenderContext&	renderCtx,
+															const AttribType&	type,
+															int					arraySize)
+	: TestCase		(testCtx, ("max_cond_" + generateTestName(type, arraySize)).c_str(), ("max_cond_" + generateTestName(type, arraySize)).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult BindMaxAliasingAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const deInt32		maxAttributes		= getMaxAttributeLocations(m_renderCtx);
+	const int			arrayElementCount	= (m_arraySize != Attribute::NOT_ARRAY ? m_arraySize : 1);
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+	int					ndx = 0;
+
+	m_testCtx.getLog() << TestLog::Message << "GL_MAX_VERTEX_ATTRIBS: " << maxAttributes << TestLog::EndMessage;
+
+	for (int loc = maxAttributes - arrayElementCount * m_type.getLocationSize(); loc >= 0; loc -= m_type.getLocationSize() * arrayElementCount)
+	{
+		attributes.push_back(Attribute(m_type, "a_" + de::toString(ndx), Attribute::LOC_UNDEF, Cond("A", true)));
+		bindings.push_back(Bind("a_" + de::toString(ndx), loc));
+
+		attributes.push_back(Attribute(m_type, "a_" + de::toString(ndx + maxAttributes), Attribute::LOC_UNDEF, Cond("A", false)));
+		bindings.push_back(Bind("a_" + de::toString(ndx + maxAttributes), loc));
+		ndx++;
+	}
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+BindHoleAttributeTest::BindHoleAttributeTest (tcu::TestContext&		testCtx,
+											  glu::RenderContext&	renderCtx,
+											  const AttribType&		type,
+											  int					arraySize)
+	: TestCase		(testCtx, generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult BindHoleAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const deInt32		maxAttributes = getMaxAttributeLocations(m_renderCtx);
+	const AttribType	vec4("vec4", 1, GL_FLOAT_VEC4);
+	const int			arrayElementCount	= (m_arraySize != Attribute::NOT_ARRAY ? m_arraySize : 1);
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+	int					ndx;
+
+	attributes.push_back(Attribute(vec4, "a_0"));
+	bindings.push_back(Bind("a_0", 0));
+
+	attributes.push_back(Attribute(m_type, "a_1", Attribute::LOC_UNDEF, Cond::COND_ALWAYS, m_arraySize));
+
+	ndx = 2;
+	for (int loc = 1 + m_type.getLocationSize() * arrayElementCount; loc < maxAttributes; loc++)
+	{
+		attributes.push_back(Attribute(vec4, "a_" + de::toString(ndx)));
+		bindings.push_back(Bind("a_" + de::toString(ndx), loc));
+
+		ndx++;
+	}
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+BindInactiveAliasingAttributeTest::BindInactiveAliasingAttributeTest (tcu::TestContext&		testCtx,
+																	  glu::RenderContext&	renderCtx,
+																	  const AttribType&		type,
+																	  int					arraySize)
+	: TestCase		(testCtx,	("max_inactive_" + generateTestName(type, arraySize)).c_str(),
+								("max_inactive_" + generateTestName(type, arraySize)).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult BindInactiveAliasingAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const deInt32		maxAttributes		= getMaxAttributeLocations(m_renderCtx);
+	const int			arrayElementCount	= (m_arraySize != Attribute::NOT_ARRAY ? m_arraySize : 1);
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+	int					ndx = 0;
+
+	m_testCtx.getLog() << TestLog::Message << "GL_MAX_VERTEX_ATTRIBS: " << maxAttributes << TestLog::EndMessage;
+
+	for (int loc = maxAttributes - arrayElementCount * m_type.getLocationSize(); loc >= 0; loc -= m_type.getLocationSize() * arrayElementCount)
+	{
+		attributes.push_back(Attribute(m_type, "a_" + de::toString(ndx), Attribute::LOC_UNDEF, Cond("A")));
+		bindings.push_back(Bind("a_" + de::toString(ndx), loc));
+
+		attributes.push_back(Attribute(m_type, "a_" + de::toString(ndx + maxAttributes), Attribute::LOC_UNDEF, Cond::COND_NEVER));
+		bindings.push_back(Bind("a_" + de::toString(ndx + maxAttributes), loc));
+		ndx++;
+	}
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+PreAttachBindAttributeTest::PreAttachBindAttributeTest (tcu::TestContext&	testCtx,
+														glu::RenderContext&	renderCtx)
+	: TestCase		(testCtx, "pre_attach", "pre_attach")
+	, m_renderCtx	(renderCtx)
+{
+}
+
+tcu::TestCase::IterateResult PreAttachBindAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+
+	attributes.push_back(Attribute(AttribType("vec4", 1, GL_FLOAT_VEC4), "a_0"));
+	bindings.push_back(Bind("a_0", 3));
+
+	runTest(m_testCtx, m_renderCtx, attributes, bindings, noBindings, noBindings, false);
+	return STOP;
+}
+
+PreLinkBindAttributeTest::PreLinkBindAttributeTest (tcu::TestContext&	testCtx,
+													glu::RenderContext&	renderCtx)
+	: TestCase		(testCtx, "pre_link", "pre_link")
+	, m_renderCtx	(renderCtx)
+{
+}
+
+tcu::TestCase::IterateResult PreLinkBindAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+
+	attributes.push_back(Attribute(AttribType("vec4", 1, GL_FLOAT_VEC4), "a_0"));
+	bindings.push_back(Bind("a_0", 3));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+PostLinkBindAttributeTest::PostLinkBindAttributeTest (tcu::TestContext&		testCtx,
+													  glu::RenderContext&	renderCtx)
+	: TestCase		(testCtx, "post_link", "post_link")
+	, m_renderCtx	(renderCtx)
+{
+}
+
+tcu::TestCase::IterateResult PostLinkBindAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+
+	attributes.push_back(Attribute(AttribType("vec4", 1, GL_FLOAT_VEC4), "a_0"));
+	bindings.push_back(Bind("a_0", 3));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, noBindings, bindings, false);
+	return STOP;
+}
+
+LocationAttributeTest::LocationAttributeTest (tcu::TestContext&		testCtx,
+											  glu::RenderContext&	renderCtx,
+											  const AttribType&		type,
+											  int					arraySize)
+	: TestCase		(testCtx, generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult LocationAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+
+	vector<Attribute>	attributes;
+
+	attributes.push_back(Attribute(m_type, "a_0", 3, Cond::COND_ALWAYS, m_arraySize));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, noBindings, noBindings, false);
+	return STOP;
+}
+
+LocationMaxAttributesTest::LocationMaxAttributesTest (tcu::TestContext&		testCtx,
+													  glu::RenderContext&	renderCtx,
+													  const AttribType&		type,
+													  int					arraySize)
+	: TestCase		(testCtx, generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult LocationMaxAttributesTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const deInt32		maxAttributes		= getMaxAttributeLocations(m_renderCtx);
+	const int			arrayElementCount	= (m_arraySize != Attribute::NOT_ARRAY ? m_arraySize : 1);
+
+	vector<Attribute>	attributes;
+	int					ndx = 0;
+
+	m_testCtx.getLog() << TestLog::Message << "GL_MAX_VERTEX_ATTRIBS: " << maxAttributes << TestLog::EndMessage;
+
+	for (int loc = maxAttributes - (arrayElementCount * m_type.getLocationSize()); loc >= 0; loc -= (arrayElementCount * m_type.getLocationSize()))
+	{
+		attributes.push_back(Attribute(m_type, "a_" + de::toString(ndx), loc, Cond::COND_ALWAYS, m_arraySize));
+		ndx++;
+	}
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, noBindings, noBindings, false);
+	return STOP;
+}
+
+LocationHoleAttributeTest::LocationHoleAttributeTest (tcu::TestContext&		testCtx,
+													  glu::RenderContext&	renderCtx,
+													  const AttribType&		type,
+													  int					arraySize)
+	: TestCase		(testCtx, generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult LocationHoleAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const deInt32		maxAttributes = getMaxAttributeLocations(m_renderCtx);
+	const AttribType	vec4("vec4", 1, GL_FLOAT_VEC4);
+	const int			arrayElementCount	= (m_arraySize != Attribute::NOT_ARRAY ? m_arraySize : 1);
+
+	vector<Attribute>	attributes;
+	int					ndx;
+
+	attributes.push_back(Attribute(vec4, "a_0", 0));
+
+	attributes.push_back(Attribute(m_type, "a_1", Attribute::LOC_UNDEF, Cond::COND_ALWAYS, m_arraySize));
+
+	ndx = 2;
+	for (int loc = 1 + m_type.getLocationSize() * arrayElementCount; loc < maxAttributes; loc++)
+	{
+		attributes.push_back(Attribute(vec4, "a_" + de::toString(ndx), loc));
+		ndx++;
+	}
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, noBindings, noBindings, false);
+	return STOP;
+}
+
+MixedAttributeTest::MixedAttributeTest (tcu::TestContext&	testCtx,
+										glu::RenderContext&	renderCtx,
+										const AttribType&	type,
+										int					arraySize)
+	: TestCase		(testCtx, generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult MixedAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+
+	vector<Bind>		bindings;
+	vector<Attribute>	attributes;
+
+	attributes.push_back(Attribute(m_type, "a_0", 3, Cond::COND_ALWAYS, m_arraySize));
+	bindings.push_back(Bind("a_0", 4));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+MixedMaxAttributesTest::MixedMaxAttributesTest (tcu::TestContext&	testCtx,
+												glu::RenderContext&	renderCtx,
+												const AttribType&	type,
+												int					arraySize)
+	: TestCase		(testCtx, generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult MixedMaxAttributesTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const deInt32		maxAttributes		= getMaxAttributeLocations(m_renderCtx);
+	const int			arrayElementCount	= (m_arraySize != Attribute::NOT_ARRAY ? m_arraySize : 1);
+
+	vector<Bind>		bindings;
+	vector<Attribute>	attributes;
+	int					ndx = 0;
+
+	m_testCtx.getLog() << TestLog::Message << "GL_MAX_VERTEX_ATTRIBS: " << maxAttributes << TestLog::EndMessage;
+
+	for (int loc = maxAttributes - (arrayElementCount * m_type.getLocationSize()); loc >= 0; loc -= (arrayElementCount * m_type.getLocationSize()))
+	{
+		if ((ndx % 2) != 0)
+			attributes.push_back(Attribute(m_type, "a_" + de::toString(ndx), loc, Cond::COND_ALWAYS, m_arraySize));
+		else
+		{
+			attributes.push_back(Attribute(m_type, "a_" + de::toString(ndx), Attribute::LOC_UNDEF, Cond::COND_ALWAYS, m_arraySize));
+			bindings.push_back(Bind("a_" + de::toString(ndx), loc));
+
+		}
+		ndx++;
+	}
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+MixedHoleAttributeTest::MixedHoleAttributeTest (tcu::TestContext&		testCtx,
+												glu::RenderContext&		renderCtx,
+												const AttribType&		type,
+												int						arraySize)
+	: TestCase		(testCtx, generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult MixedHoleAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const deInt32		maxAttributes = getMaxAttributeLocations(m_renderCtx);
+	const AttribType	vec4("vec4", 1, GL_FLOAT_VEC4);
+	const int			arrayElementCount	= (m_arraySize != Attribute::NOT_ARRAY ? m_arraySize : 1);
+
+	vector<Bind>		bindings;
+	vector<Attribute>	attributes;
+	int					ndx;
+
+	attributes.push_back(Attribute(vec4, "a_0"));
+	bindings.push_back(Bind("a_0", 0));
+
+	attributes.push_back(Attribute(m_type, "a_1", Attribute::LOC_UNDEF, Cond::COND_ALWAYS, m_arraySize));
+
+	ndx = 2;
+	for (int loc = 1 + m_type.getLocationSize() * arrayElementCount; loc < maxAttributes; loc++)
+	{
+		if ((ndx % 2) != 0)
+			attributes.push_back(Attribute(vec4, "a_" + de::toString(ndx), loc));
+		else
+		{
+			attributes.push_back(Attribute(vec4, "a_" + de::toString(ndx), loc));
+			bindings.push_back(Bind("a_" + de::toString(ndx), loc));
+
+		}
+		ndx++;
+	}
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+BindRelinkAttributeTest::BindRelinkAttributeTest (tcu::TestContext&		testCtx,
+												  glu::RenderContext&	renderCtx)
+	: TestCase		(testCtx, "relink", "relink")
+	, m_renderCtx	(renderCtx)
+{
+}
+
+tcu::TestCase::IterateResult BindRelinkAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const AttribType	vec4("vec4", 1, GL_FLOAT_VEC4);
+
+	vector<Attribute>	attributes;
+	vector<Bind>		preLinkBindings;
+	vector<Bind>		postLinkBindings;
+
+	attributes.push_back(Attribute(vec4, "a_0"));
+	attributes.push_back(Attribute(vec4, "a_1"));
+
+	preLinkBindings.push_back(Bind("a_0", 3));
+	preLinkBindings.push_back(Bind("a_0", 5));
+
+	postLinkBindings.push_back(Bind("a_0", 6));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, preLinkBindings, postLinkBindings, true);
+	return STOP;
+}
+
+BindRelinkHoleAttributeTest::BindRelinkHoleAttributeTest (tcu::TestContext&		testCtx,
+														  glu::RenderContext&	renderCtx,
+														  const AttribType&		type,
+														  int					arraySize)
+	: TestCase		(testCtx, generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult BindRelinkHoleAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const deInt32		maxAttributes		= getMaxAttributeLocations(m_renderCtx);
+	const AttribType	vec4				("vec4", 1, GL_FLOAT_VEC4);
+	const int			arrayElementCount	= (m_arraySize != Attribute::NOT_ARRAY ? m_arraySize : 1);
+
+	vector<Attribute>	attributes;
+	vector<Bind>		preLinkBindings;
+	vector<Bind>		postLinkBindings;
+	int					ndx;
+
+	attributes.push_back(Attribute(vec4, "a_0"));
+	preLinkBindings.push_back(Bind("a_0", 0));
+
+	attributes.push_back(Attribute(m_type, "a_1", Attribute::LOC_UNDEF, Cond::COND_ALWAYS, m_arraySize));
+
+	ndx = 2;
+	for (int loc = 1 + m_type.getLocationSize() * arrayElementCount; loc < maxAttributes; loc++)
+	{
+		attributes.push_back(Attribute(vec4, "a_" + de::toString(ndx)));
+		preLinkBindings.push_back(Bind("a_" + de::toString(ndx), loc));
+
+		ndx++;
+	}
+
+	postLinkBindings.push_back(Bind("a_2", 1));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, preLinkBindings, postLinkBindings, true);
+	return STOP;
+}
+
+MixedRelinkHoleAttributeTest::MixedRelinkHoleAttributeTest (tcu::TestContext&		testCtx,
+															glu::RenderContext&		renderCtx,
+															const AttribType&		type,
+															int						arraySize)
+	: TestCase		(testCtx, generateTestName(type, arraySize).c_str(), generateTestName(type, arraySize).c_str())
+	, m_renderCtx	(renderCtx)
+	, m_type		(type)
+	, m_arraySize	(arraySize)
+{
+}
+
+tcu::TestCase::IterateResult MixedRelinkHoleAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const deInt32		maxAttributes		= getMaxAttributeLocations(m_renderCtx);
+	const AttribType	vec4				("vec4", 1, GL_FLOAT_VEC4);
+	const int			arrayElementCount	= (m_arraySize != Attribute::NOT_ARRAY ? m_arraySize : 1);
+
+	vector<Bind>		preLinkBindings;
+	vector<Bind>		postLinkBindings;
+	vector<Attribute>	attributes;
+	int					ndx;
+
+	attributes.push_back(Attribute(vec4, "a_0"));
+	preLinkBindings.push_back(Bind("a_0", 0));
+
+	attributes.push_back(Attribute(m_type, "a_1", Attribute::LOC_UNDEF, Cond::COND_ALWAYS, m_arraySize));
+
+	ndx = 2;
+	for (int loc = 1 + m_type.getLocationSize() * arrayElementCount; loc < maxAttributes; loc++)
+	{
+		if ((ndx % 2) != 0)
+			attributes.push_back(Attribute(vec4, "a_" + de::toString(ndx), loc));
+		else
+		{
+			attributes.push_back(Attribute(vec4, "a_" + de::toString(ndx)));
+			preLinkBindings.push_back(Bind("a_" + de::toString(ndx), loc));
+
+		}
+		ndx++;
+	}
+
+	postLinkBindings.push_back(Bind("a_2", 1));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, preLinkBindings, postLinkBindings, true);
+	return STOP;
+}
+
+BindReattachAttributeTest::BindReattachAttributeTest (tcu::TestContext&		testCtx,
+													  glu::RenderContext&	renderCtx)
+	: TestCase		(testCtx, "reattach", "reattach")
+	, m_renderCtx	(renderCtx)
+{
+}
+
+tcu::TestCase::IterateResult BindReattachAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const AttribType	vec4("vec4", 1, GL_FLOAT_VEC4);
+	const AttribType	vec2("vec2", 1, GL_FLOAT_VEC2);
+
+	vector<Bind>		bindings;
+	vector<Attribute>	attributes;
+	vector<Attribute>	reattachAttributes;
+
+	attributes.push_back(Attribute(vec4, "a_0"));
+	bindings.push_back(Bind("a_0", 1));
+	bindings.push_back(Bind("a_1", 1));
+
+	reattachAttributes.push_back(Attribute(vec2, "a_1"));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false, true, reattachAttributes);
+	return STOP;
+}
+
+PreAttachMixedAttributeTest::PreAttachMixedAttributeTest (tcu::TestContext&	testCtx,
+														glu::RenderContext&	renderCtx)
+	: TestCase		(testCtx, "pre_attach", "pre_attach")
+	, m_renderCtx	(renderCtx)
+{
+}
+
+tcu::TestCase::IterateResult PreAttachMixedAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+
+	attributes.push_back(Attribute(AttribType("vec4", 1, GL_FLOAT_VEC4), "a_0", 1));
+	bindings.push_back(Bind("a_0", 3));
+
+	runTest(m_testCtx, m_renderCtx, attributes, bindings, noBindings, noBindings, false);
+	return STOP;
+}
+
+PreLinkMixedAttributeTest::PreLinkMixedAttributeTest (tcu::TestContext&	testCtx,
+													glu::RenderContext&	renderCtx)
+	: TestCase		(testCtx, "pre_link", "pre_link")
+	, m_renderCtx	(renderCtx)
+{
+}
+
+tcu::TestCase::IterateResult PreLinkMixedAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+
+	attributes.push_back(Attribute(AttribType("vec4", 1, GL_FLOAT_VEC4), "a_0", 1));
+	bindings.push_back(Bind("a_0", 3));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false);
+	return STOP;
+}
+
+PostLinkMixedAttributeTest::PostLinkMixedAttributeTest (tcu::TestContext&	testCtx,
+														glu::RenderContext&	renderCtx)
+	: TestCase		(testCtx, "post_link", "post_link")
+	, m_renderCtx	(renderCtx)
+{
+}
+
+tcu::TestCase::IterateResult PostLinkMixedAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+
+	vector<Attribute>	attributes;
+	vector<Bind>		bindings;
+
+	attributes.push_back(Attribute(AttribType("vec4", 1, GL_FLOAT_VEC4), "a_0", 1));
+	bindings.push_back(Bind("a_0", 3));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, noBindings, bindings, false);
+	return STOP;
+}
+
+MixedReattachAttributeTest::MixedReattachAttributeTest (tcu::TestContext&	testCtx,
+														glu::RenderContext&	renderCtx)
+	: TestCase		(testCtx, "reattach", "reattach")
+	, m_renderCtx	(renderCtx)
+{
+}
+
+tcu::TestCase::IterateResult MixedReattachAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const AttribType	vec4("vec4", 1, GL_FLOAT_VEC4);
+	const AttribType	vec2("vec2", 1, GL_FLOAT_VEC2);
+
+	vector<Bind>		bindings;
+	vector<Attribute>	attributes;
+	vector<Attribute>	reattachAttributes;
+
+	attributes.push_back(Attribute(vec4, "a_0", 2));
+	bindings.push_back(Bind("a_0", 1));
+	bindings.push_back(Bind("a_1", 1));
+
+	reattachAttributes.push_back(Attribute(vec2, "a_1"));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, bindings, noBindings, false, true, reattachAttributes);
+	return STOP;
+}
+
+MixedRelinkAttributeTest::MixedRelinkAttributeTest (tcu::TestContext&	testCtx,
+													glu::RenderContext&	renderCtx)
+	: TestCase		(testCtx, "relink", "relink")
+	, m_renderCtx	(renderCtx)
+{
+}
+
+tcu::TestCase::IterateResult MixedRelinkAttributeTest::iterate (void)
+{
+	const vector<Bind>	noBindings;
+	const AttribType	vec4("vec4", 1, GL_FLOAT_VEC4);
+
+	vector<Attribute>	attributes;
+	vector<Bind>		preLinkBindings;
+	vector<Bind>		postLinkBindings;
+
+	attributes.push_back(Attribute(vec4, "a_0", 1));
+	attributes.push_back(Attribute(vec4, "a_1"));
+
+	preLinkBindings.push_back(Bind("a_0", 3));
+	preLinkBindings.push_back(Bind("a_0", 5));
+
+	postLinkBindings.push_back(Bind("a_0", 6));
+
+	runTest(m_testCtx, m_renderCtx, attributes, noBindings, preLinkBindings, postLinkBindings, true);
+	return STOP;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsAttributeLocationTests.hpp b/modules/glshared/glsAttributeLocationTests.hpp
new file mode 100644
index 0000000..e25b487
--- /dev/null
+++ b/modules/glshared/glsAttributeLocationTests.hpp
@@ -0,0 +1,528 @@
+#ifndef _GLSATTRIBUTELOCATIONTESTS_HPP
+#define _GLSATTRIBUTELOCATIONTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Attribute location tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+
+#include <string>
+#include <vector>
+
+namespace glu
+{
+class ShaderProgram;
+class RenderContext;
+} // glu
+
+namespace deqp
+{
+namespace gls
+{
+namespace AttributeLocationTestUtil
+{
+
+class AttribType
+{
+public:
+						AttribType		(const std::string& name, deUint32 locationSize, deUint32 typeEnum);
+
+	const std::string&	getName			(void) const { return m_name;			}
+	deUint32 			getLocationSize	(void) const { return m_locationSize;	}
+	deUint32			getGLTypeEnum	(void) const { return m_glTypeEnum;		}
+
+private:
+	std::string			m_name;
+	deUint32			m_locationSize;
+	deUint32			m_glTypeEnum;
+};
+
+class Cond
+{
+public:
+	enum ConstCond
+	{
+		COND_ALWAYS,
+		COND_NEVER
+	};
+
+						Cond		(ConstCond cond);
+	explicit			Cond		(const std::string& name, bool negate = true);
+	bool				operator==	(const Cond& other)	const	{ return m_negate == other.m_negate && m_name == other.m_name;	}
+	bool				operator!=	(const Cond& other)	const	{ return !(*this == other);										}
+	const std::string	getName		(void) const				{ return m_name;												}
+	bool				getNegate	(void) const				{ return m_negate;												}
+
+private:
+	bool				m_negate;
+	std::string			m_name;
+};
+
+class Attribute
+{
+public:
+	enum
+	{
+		// Location is not defined
+		LOC_UNDEF = -1
+	};
+
+	enum
+	{
+		// Not an array
+		NOT_ARRAY = -1
+	};
+
+						Attribute			(const AttribType&	type,
+											 const std::string&	name,
+											 deInt32			layoutLocation 	= LOC_UNDEF,
+											 const Cond&		cond			= Cond::COND_ALWAYS,
+											 int				arraySize		= NOT_ARRAY);
+
+	const AttribType	getType				(void) const { return m_type;			}
+	const std::string&	getName				(void) const { return m_name;			}
+	deInt32				getLayoutLocation	(void) const { return m_layoutLocation;	}
+	const Cond&			getCondition		(void) const { return m_cond;			}
+	int					getArraySize		(void) const { return m_arraySize;		}
+
+private:
+	AttribType			m_type;
+	std::string			m_name;
+	deInt32				m_layoutLocation;
+	Cond				m_cond;
+	int					m_arraySize;
+};
+
+class Bind
+{
+public:
+						Bind				(const std::string& attribute, deUint32 location);
+
+	const std::string&	getAttributeName	(void) const { return m_attribute;	}
+	deUint32			getLocation			(void) const { return m_location;	}
+
+private:
+	std::string			m_attribute;
+	deUint32			m_location;
+};
+
+} // AttributeLocationTestUtil
+
+// Simple bind attribute test
+class BindAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							BindAttributeTest	(tcu::TestContext&		testCtx,
+												 glu::RenderContext&	renderCtx,
+												 const AttribType&		type,
+												 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate				(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+// Bind maximum number of attributes
+class BindMaxAttributesTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							BindMaxAttributesTest	(tcu::TestContext&		testCtx,
+													 glu::RenderContext&	renderCtx,
+													 const AttribType&		type,
+													 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate					(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class BindAliasingAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							BindAliasingAttributeTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx,
+														 const AttribType&		type,
+														 int					offset = 0,
+														 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_offset;
+	const int				m_arraySize;
+};
+
+class BindMaxAliasingAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							BindMaxAliasingAttributeTest	(tcu::TestContext&	testCtx,
+															glu::RenderContext&	renderCtx,
+															const AttribType&	type,
+															int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate							(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class BindInactiveAliasingAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							BindInactiveAliasingAttributeTest	(tcu::TestContext&		testCtx,
+																 glu::RenderContext&	renderCtx,
+																 const AttribType&		type,
+																 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate								(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class BindHoleAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							BindHoleAttributeTest		(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx,
+														 const AttribType&		type,
+														 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class PreAttachBindAttributeTest : public tcu::TestCase
+{
+public:
+							PreAttachBindAttributeTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+};
+
+class PreLinkBindAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							PreLinkBindAttributeTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+};
+
+class PostLinkBindAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							PostLinkBindAttributeTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+};
+
+class BindReattachAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							BindReattachAttributeTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+};
+
+class LocationAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							LocationAttributeTest	(tcu::TestContext&		testCtx,
+													 glu::RenderContext&	renderCtx,
+													 const AttribType&		type,
+													 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate					(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class LocationMaxAttributesTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							LocationMaxAttributesTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx,
+														 const AttribType&		type,
+														 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class LocationHoleAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							LocationHoleAttributeTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx,
+														 const AttribType&		type,
+														 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class MixedAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							MixedAttributeTest	(tcu::TestContext&		testCtx,
+												 glu::RenderContext&	renderCtx,
+												 const AttribType&		type,
+												 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate				(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class MixedMaxAttributesTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							MixedMaxAttributesTest	(tcu::TestContext&		testCtx,
+													 glu::RenderContext&	renderCtx,
+													 const AttribType&		type,
+													 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate					(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class MixedHoleAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							MixedHoleAttributeTest	(tcu::TestContext&		testCtx,
+													 glu::RenderContext&	renderCtx,
+													 const AttribType&		type,
+													 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate					(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class BindRelinkAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							BindRelinkAttributeTest	(tcu::TestContext&		testCtx,
+													 glu::RenderContext&	renderCtx);
+
+	virtual IterateResult	iterate					(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+};
+
+class BindRelinkHoleAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							BindRelinkHoleAttributeTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx,
+														 const AttribType&		type,
+														 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class MixedRelinkHoleAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							MixedRelinkHoleAttributeTest	(tcu::TestContext&		testCtx,
+															 glu::RenderContext&	renderCtx,
+															 const AttribType&		type,
+															 int					arraySize = AttributeLocationTestUtil::Attribute::NOT_ARRAY);
+
+	virtual IterateResult	iterate							(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+	const AttribType		m_type;
+	const int				m_arraySize;
+};
+
+class PreAttachMixedAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							PreAttachMixedAttributeTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+};
+
+class PreLinkMixedAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							PreLinkMixedAttributeTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+};
+
+class PostLinkMixedAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							PostLinkMixedAttributeTest	(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx);
+
+	virtual IterateResult	iterate						(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+};
+
+class MixedReattachAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							MixedReattachAttributeTest		(tcu::TestContext&		testCtx,
+															 glu::RenderContext&	renderCtx);
+
+	virtual IterateResult	iterate							(void);
+
+private:
+	glu::RenderContext&		m_renderCtx;
+};
+
+class MixedRelinkAttributeTest : public tcu::TestCase
+{
+public:
+	typedef AttributeLocationTestUtil::AttribType AttribType;
+
+							MixedRelinkAttributeTest(tcu::TestContext&		testCtx,
+													 glu::RenderContext&	renderCtx);
+
+	virtual IterateResult	iterate					(void);
+
+private:
+	glu::RenderContext&								m_renderCtx;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSATTRIBUTELOCATIONTESTS_HPP
diff --git a/modules/glshared/glsBufferTestUtil.cpp b/modules/glshared/glsBufferTestUtil.cpp
new file mode 100644
index 0000000..5ea1bd2
--- /dev/null
+++ b/modules/glshared/glsBufferTestUtil.cpp
@@ -0,0 +1,877 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsBufferTestUtil.hpp"
+#include "tcuRandomValueIterator.hpp"
+#include "tcuSurface.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVector.hpp"
+#include "tcuFormatUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestLog.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluRenderContext.hpp"
+#include "gluStrUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "deMemory.h"
+#include "deStringUtil.hpp"
+
+#include <algorithm>
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+namespace BufferTestUtil
+{
+
+enum
+{
+	VERIFY_QUAD_SIZE					= 8,		//!< Quad size in VertexArrayVerifier
+	MAX_LINES_PER_INDEX_ARRAY_DRAW		= 128,		//!< Maximum number of lines per one draw in IndexArrayVerifier
+	INDEX_ARRAY_DRAW_VIEWPORT_WIDTH		= 128,
+	INDEX_ARRAY_DRAW_VIEWPORT_HEIGHT	= 128
+};
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using std::set;
+
+// Helper functions.
+
+void fillWithRandomBytes (deUint8* ptr, int numBytes, deUint32 seed)
+{
+	std::copy(tcu::RandomValueIterator<deUint8>::begin(seed, numBytes), tcu::RandomValueIterator<deUint8>::end(), ptr);
+}
+
+bool compareByteArrays (tcu::TestLog& log, const deUint8* resPtr, const deUint8* refPtr, int numBytes)
+{
+	bool			isOk			= true;
+	const int		maxSpanLen		= 8;
+	const int		maxDiffSpans	= 4;
+	int				numDiffSpans	= 0;
+	int				diffSpanStart	= -1;
+	int				ndx				= 0;
+
+	log << TestLog::Section("Verify", "Verification result");
+
+	for (;ndx < numBytes; ndx++)
+	{
+		if (resPtr[ndx] != refPtr[ndx])
+		{
+			if (diffSpanStart < 0)
+				diffSpanStart = ndx;
+
+			isOk = false;
+		}
+		else if (diffSpanStart >= 0)
+		{
+			if (numDiffSpans < maxDiffSpans)
+			{
+				int len			= ndx-diffSpanStart;
+				int	printLen	= de::min(len, maxSpanLen);
+
+				log << TestLog::Message << len << " byte difference at offset " << diffSpanStart << "\n"
+										<< "  expected "	<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart+printLen)) << "\n"
+										<< "  got "			<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart+printLen))
+					<< TestLog::EndMessage;
+			}
+			else
+				log << TestLog::Message << "(output too long, truncated)" << TestLog::EndMessage;
+
+			numDiffSpans	+= 1;
+			diffSpanStart	 = -1;
+		}
+	}
+
+	if (diffSpanStart >= 0)
+	{
+		if (numDiffSpans < maxDiffSpans)
+		{
+				int len			= ndx-diffSpanStart;
+				int	printLen	= de::min(len, maxSpanLen);
+
+				log << TestLog::Message << len << " byte difference at offset " << diffSpanStart << "\n"
+										<< "  expected "	<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart+printLen)) << "\n"
+										<< "  got "			<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart+printLen))
+					<< TestLog::EndMessage;
+		}
+		else
+			log << TestLog::Message << "(output too long, truncated)" << TestLog::EndMessage;
+	}
+
+	log << TestLog::Message << (isOk ? "Verification passed." : "Verification FAILED!") << TestLog::EndMessage;
+	log << TestLog::EndSection;
+
+	return isOk;
+}
+
+const char* getBufferTargetName (deUint32 target)
+{
+	switch (target)
+	{
+		case GL_ARRAY_BUFFER:				return "array";
+		case GL_COPY_READ_BUFFER:			return "copy_read";
+		case GL_COPY_WRITE_BUFFER:			return "copy_write";
+		case GL_ELEMENT_ARRAY_BUFFER:		return "element_array";
+		case GL_PIXEL_PACK_BUFFER:			return "pixel_pack";
+		case GL_PIXEL_UNPACK_BUFFER:		return "pixel_unpack";
+		case GL_TEXTURE_BUFFER:				return "texture";
+		case GL_TRANSFORM_FEEDBACK_BUFFER:	return "transform_feedback";
+		case GL_UNIFORM_BUFFER:				return "uniform";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+const char* getUsageHintName (deUint32 hint)
+{
+	switch (hint)
+	{
+		case GL_STREAM_DRAW:	return "stream_draw";
+		case GL_STREAM_READ:	return "stream_read";
+		case GL_STREAM_COPY:	return "stream_copy";
+		case GL_STATIC_DRAW:	return "static_draw";
+		case GL_STATIC_READ:	return "static_read";
+		case GL_STATIC_COPY:	return "static_copy";
+		case GL_DYNAMIC_DRAW:	return "dynamic_draw";
+		case GL_DYNAMIC_READ:	return "dynamic_read";
+		case GL_DYNAMIC_COPY:	return "dynamic_copy";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+// BufferCase
+
+BufferCase::BufferCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description)
+	: TestCase			(testCtx, name, description)
+	, CallLogWrapper	(renderCtx.getFunctions(), testCtx.getLog())
+	, m_renderCtx		(renderCtx)
+{
+}
+
+BufferCase::~BufferCase (void)
+{
+	enableLogging(false);
+	BufferCase::deinit();
+}
+
+void BufferCase::init (void)
+{
+	enableLogging(true);
+}
+
+void BufferCase::deinit (void)
+{
+	for (set<deUint32>::const_iterator bufIter = m_allocatedBuffers.begin(); bufIter != m_allocatedBuffers.end(); bufIter++)
+		glDeleteBuffers(1, &(*bufIter));
+}
+
+deUint32 BufferCase::genBuffer (void)
+{
+	deUint32 buf = 0;
+	glGenBuffers(1, &buf);
+	if (buf != 0)
+	{
+		try
+		{
+			m_allocatedBuffers.insert(buf);
+		}
+		catch (const std::exception&)
+		{
+			glDeleteBuffers(1, &buf);
+			throw;
+		}
+	}
+	return buf;
+}
+
+void BufferCase::deleteBuffer (deUint32 buffer)
+{
+	glDeleteBuffers(1, &buffer);
+	m_allocatedBuffers.erase(buffer);
+}
+
+void BufferCase::checkError (void)
+{
+	glw::GLenum err = glGetError();
+	if (err != GL_NO_ERROR)
+		throw tcu::TestError(string("Got ") + glu::getErrorStr(err).toString());
+}
+
+// ReferenceBuffer
+
+void ReferenceBuffer::setSize (int numBytes)
+{
+	m_data.resize(numBytes);
+}
+
+void ReferenceBuffer::setData (int numBytes, const deUint8* bytes)
+{
+	m_data.resize(numBytes);
+	std::copy(bytes, bytes+numBytes, m_data.begin());
+}
+
+void ReferenceBuffer::setSubData (int offset, int numBytes, const deUint8* bytes)
+{
+	DE_ASSERT(de::inBounds(offset, 0, (int)m_data.size()) && de::inRange(offset+numBytes, offset, (int)m_data.size()));
+	std::copy(bytes, bytes+numBytes, m_data.begin()+offset);
+}
+
+// BufferWriterBase
+
+BufferWriterBase::BufferWriterBase (glu::RenderContext& renderCtx, tcu::TestLog& log)
+	: CallLogWrapper	(renderCtx.getFunctions(), log)
+	, m_renderCtx		(renderCtx)
+{
+	enableLogging(true);
+}
+
+void BufferWriterBase::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 targetHint)
+{
+	DE_UNREF(targetHint);
+	write(buffer, offset, numBytes, bytes);
+}
+
+// BufferWriter
+
+BufferWriter::BufferWriter (glu::RenderContext& renderCtx, tcu::TestLog& log, WriteType writeType)
+	: m_writer(DE_NULL)
+{
+	switch (writeType)
+	{
+		case WRITE_BUFFER_SUB_DATA:		m_writer = new BufferSubDataWriter	(renderCtx, log);	break;
+		case WRITE_BUFFER_WRITE_MAP:	m_writer = new BufferWriteMapWriter	(renderCtx, log);	break;
+		default:
+			TCU_FAIL("Unsupported writer");
+	}
+}
+
+BufferWriter::~BufferWriter (void)
+{
+	delete m_writer;
+}
+
+void BufferWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes)
+{
+	DE_ASSERT(numBytes >= getMinSize());
+	DE_ASSERT(offset%getAlignment() == 0);
+	DE_ASSERT((offset+numBytes)%getAlignment() == 0);
+	return m_writer->write(buffer, offset, numBytes, bytes);
+}
+
+void BufferWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 targetHint)
+{
+	DE_ASSERT(numBytes >= getMinSize());
+	DE_ASSERT(offset%getAlignment() == 0);
+	DE_ASSERT((offset+numBytes)%getAlignment() == 0);
+	return m_writer->write(buffer, offset, numBytes, bytes, targetHint);
+}
+
+// BufferSubDataWriter
+
+void BufferSubDataWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes)
+{
+	write(buffer, offset, numBytes, bytes, GL_ARRAY_BUFFER);
+}
+
+void BufferSubDataWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 target)
+{
+	glBindBuffer(target, buffer);
+	glBufferSubData(target, offset, numBytes, bytes);
+	glBindBuffer(target, 0);
+	GLU_CHECK();
+}
+
+// BufferWriteMapWriter
+
+void BufferWriteMapWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes)
+{
+	write(buffer, offset, numBytes, bytes, GL_ARRAY_BUFFER);
+}
+
+void BufferWriteMapWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 target)
+{
+	glBindBuffer(target, buffer);
+
+	void* ptr = glMapBufferRange(target, offset, numBytes, GL_MAP_WRITE_BIT);
+	GLU_CHECK_MSG("glMapBufferRange");
+
+	deMemcpy(ptr, bytes, numBytes);
+
+	glUnmapBuffer(target);
+	glBindBuffer(target, 0);
+	GLU_CHECK();
+}
+
+// BufferVerifierBase
+
+BufferVerifierBase::BufferVerifierBase (glu::RenderContext& renderCtx, tcu::TestLog& log)
+	: CallLogWrapper	(renderCtx.getFunctions(), log)
+	, m_renderCtx		(renderCtx)
+	, m_log				(log)
+{
+	enableLogging(true);
+}
+
+bool BufferVerifierBase::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes, deUint32 targetHint)
+{
+	DE_UNREF(targetHint);
+	return verify(buffer, reference, offset, numBytes);
+}
+
+// BufferVerifier
+
+BufferVerifier::BufferVerifier (glu::RenderContext& renderCtx, tcu::TestLog& log, VerifyType verifyType)
+	: m_verifier(DE_NULL)
+{
+	switch (verifyType)
+	{
+		case VERIFY_AS_VERTEX_ARRAY:	m_verifier = new VertexArrayVerifier(renderCtx, log);	break;
+		case VERIFY_AS_INDEX_ARRAY:		m_verifier = new IndexArrayVerifier	(renderCtx, log);	break;
+		case VERIFY_BUFFER_READ_MAP:	m_verifier = new BufferMapVerifier	(renderCtx, log);	break;
+		default:
+			TCU_FAIL("Unsupported verifier");
+	}
+}
+
+BufferVerifier::~BufferVerifier (void)
+{
+	delete m_verifier;
+}
+
+bool BufferVerifier::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes)
+{
+	DE_ASSERT(numBytes >= getMinSize());
+	DE_ASSERT(offset%getAlignment() == 0);
+	DE_ASSERT((offset+numBytes)%getAlignment() == 0);
+	return m_verifier->verify(buffer, reference, offset, numBytes);
+}
+
+bool BufferVerifier::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes, deUint32 targetHint)
+{
+	DE_ASSERT(numBytes >= getMinSize());
+	DE_ASSERT(offset%getAlignment() == 0);
+	DE_ASSERT((offset+numBytes)%getAlignment() == 0);
+	return m_verifier->verify(buffer, reference, offset, numBytes, targetHint);
+}
+
+// BufferMapVerifier
+
+bool BufferMapVerifier::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes)
+{
+	return verify(buffer, reference, offset, numBytes, GL_ARRAY_BUFFER);
+}
+
+bool BufferMapVerifier::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes, deUint32 target)
+{
+	const deUint8*	mapPtr		= DE_NULL;
+	bool			isOk		= false;
+
+	glBindBuffer(target, buffer);
+	mapPtr = (const deUint8*)glMapBufferRange(target, offset, numBytes, GL_MAP_READ_BIT);
+	GLU_CHECK_MSG("glMapBufferRange");
+	TCU_CHECK(mapPtr);
+
+	isOk = compareByteArrays(m_log, mapPtr, reference+offset, numBytes);
+
+	glUnmapBuffer(target);
+	GLU_CHECK_MSG("glUnmapBuffer");
+
+	glBindBuffer(target, 0);
+
+	return isOk;
+}
+
+// VertexArrayVerifier
+
+VertexArrayVerifier::VertexArrayVerifier (glu::RenderContext& renderCtx, tcu::TestLog& log)
+	: BufferVerifierBase	(renderCtx, log)
+	, m_program				(DE_NULL)
+	, m_posLoc				(0)
+	, m_byteVecLoc			(0)
+	, m_vao					(0)
+{
+	const glu::ContextType	ctxType		= renderCtx.getType();
+	const glu::GLSLVersion	glslVersion	= glu::isContextTypeES(ctxType) ? glu::GLSL_VERSION_300_ES : glu::GLSL_VERSION_330;
+
+	DE_ASSERT(glu::isGLSLVersionSupported(ctxType, glslVersion));
+
+	m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(
+		string(glu::getGLSLVersionDeclaration(glslVersion)) + "\n"
+		"in highp vec2 a_position;\n"
+		"in mediump vec3 a_byteVec;\n"
+		"out mediump vec3 v_byteVec;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		"	v_byteVec = a_byteVec;\n"
+		"}\n",
+
+		string(glu::getGLSLVersionDeclaration(glslVersion)) + "\n"
+		"in mediump vec3 v_byteVec;\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"	o_color = vec4(v_byteVec, 1.0);\n"
+		"}\n"));
+
+	if (!m_program->isOk())
+	{
+		m_log << *m_program;
+		delete m_program;
+		TCU_FAIL("Compile failed");
+	}
+
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	m_posLoc		= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	m_byteVecLoc	= gl.getAttribLocation(m_program->getProgram(), "a_byteVec");
+
+	gl.genVertexArrays(1, &m_vao);
+	gl.genBuffers(1, &m_positionBuf);
+	gl.genBuffers(1, &m_indexBuf);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Initialization failed");
+}
+
+VertexArrayVerifier::~VertexArrayVerifier (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_vao)			gl.deleteVertexArrays(1, &m_vao);
+	if (m_positionBuf)	gl.deleteBuffers(1, &m_positionBuf);
+	if (m_indexBuf)		gl.deleteBuffers(1, &m_indexBuf);
+
+	delete m_program;
+}
+
+static void computePositions (vector<tcu::Vec2>& positions, int gridSizeX, int gridSizeY)
+{
+	positions.resize(gridSizeX*gridSizeY*4);
+
+	for (int y = 0; y < gridSizeY; y++)
+	for (int x = 0; x < gridSizeX; x++)
+	{
+		float	sx0			= (x+0) / (float)gridSizeX;
+		float	sy0			= (y+0) / (float)gridSizeY;
+		float	sx1			= (x+1) / (float)gridSizeX;
+		float	sy1			= (y+1) / (float)gridSizeY;
+		float	fx0			= 2.0f * sx0 - 1.0f;
+		float	fy0			= 2.0f * sy0 - 1.0f;
+		float	fx1			= 2.0f * sx1 - 1.0f;
+		float	fy1			= 2.0f * sy1 - 1.0f;
+		int		baseNdx		= (y * gridSizeX + x)*4;
+
+		positions[baseNdx+0] = tcu::Vec2(fx0, fy0);
+		positions[baseNdx+1] = tcu::Vec2(fx0, fy1);
+		positions[baseNdx+2] = tcu::Vec2(fx1, fy0);
+		positions[baseNdx+3] = tcu::Vec2(fx1, fy1);
+	}
+}
+
+static void computeIndices (vector<deUint16>& indices, int gridSizeX, int gridSizeY)
+{
+	indices.resize(3 * 2 * gridSizeX * gridSizeY);
+
+	for (int quadNdx = 0; quadNdx < gridSizeX*gridSizeY; quadNdx++)
+	{
+		int v00 = quadNdx*4 + 0;
+		int v01 = quadNdx*4 + 1;
+		int v10 = quadNdx*4 + 2;
+		int v11 = quadNdx*4 + 3;
+
+		DE_ASSERT(v11 < (1<<16));
+
+		indices[quadNdx*6 + 0] = (deUint16)v10;
+		indices[quadNdx*6 + 1] = (deUint16)v00;
+		indices[quadNdx*6 + 2] = (deUint16)v01;
+
+		indices[quadNdx*6 + 3] = (deUint16)v10;
+		indices[quadNdx*6 + 4] = (deUint16)v01;
+		indices[quadNdx*6 + 5] = (deUint16)v11;
+	}
+}
+
+static inline tcu::Vec4 fetchVtxColor (const deUint8* ptr, int vtxNdx)
+{
+	return tcu::RGBA(*(ptr + vtxNdx*3 + 0),
+					 *(ptr + vtxNdx*3 + 1),
+					 *(ptr + vtxNdx*3 + 2),
+					 255).toVec();
+}
+
+static void renderQuadGridReference (tcu::Surface& dst, int numQuads, int rowLength, const deUint8* inPtr)
+{
+	using tcu::Vec4;
+
+	dst.setSize(rowLength*VERIFY_QUAD_SIZE, (numQuads/rowLength + (numQuads%rowLength != 0 ? 1 : 0))*VERIFY_QUAD_SIZE);
+
+	tcu::PixelBufferAccess dstAccess = dst.getAccess();
+	tcu::clear(dstAccess, tcu::IVec4(0, 0, 0, 0xff));
+
+	for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
+	{
+		int		x0		= (quadNdx%rowLength)*VERIFY_QUAD_SIZE;
+		int		y0		= (quadNdx/rowLength)*VERIFY_QUAD_SIZE;
+		Vec4	v00		= fetchVtxColor(inPtr, quadNdx*4 + 0);
+		Vec4	v10		= fetchVtxColor(inPtr, quadNdx*4 + 1);
+		Vec4	v01		= fetchVtxColor(inPtr, quadNdx*4 + 2);
+		Vec4	v11		= fetchVtxColor(inPtr, quadNdx*4 + 3);
+
+		for (int y = 0; y < VERIFY_QUAD_SIZE; y++)
+		for (int x = 0; x < VERIFY_QUAD_SIZE; x++)
+		{
+			float		fx		= (float)(x+0.5f) / (float)VERIFY_QUAD_SIZE;
+			float		fy		= (float)(y+0.5f) / (float)VERIFY_QUAD_SIZE;
+
+			bool		tri		= fx + fy <= 1.0f;
+			float		tx		= tri ? fx : (1.0f-fx);
+			float		ty		= tri ? fy : (1.0f-fy);
+			const Vec4&	t0		= tri ? v00 : v11;
+			const Vec4&	t1		= tri ? v01 : v10;
+			const Vec4&	t2		= tri ? v10 : v01;
+			Vec4		color	= t0 + (t1-t0)*tx + (t2-t0)*ty;
+
+			dstAccess.setPixel(color, x0+x, y0+y);
+		}
+	}
+}
+
+bool VertexArrayVerifier::verify (deUint32 buffer, const deUint8* refPtr, int offset, int numBytes)
+{
+	const tcu::RenderTarget&	renderTarget		= m_renderCtx.getRenderTarget();
+	const int					numBytesInVtx		= 3;
+	const int					numBytesInQuad		= numBytesInVtx*4;
+	int							maxQuadsX			= de::min(128, renderTarget.getWidth()	/ VERIFY_QUAD_SIZE);
+	int							maxQuadsY			= de::min(128, renderTarget.getHeight()	/ VERIFY_QUAD_SIZE);
+	int							maxQuadsPerBatch	= maxQuadsX*maxQuadsY;
+	int							numVerified			= 0;
+	deUint32					program				= m_program->getProgram();
+	tcu::RGBA					threshold			= renderTarget.getPixelFormat().getColorThreshold() + tcu::RGBA(3,3,3,3);
+	bool						isOk				= true;
+
+	vector<tcu::Vec2>			positions;
+	vector<deUint16>			indices;
+
+	tcu::Surface				rendered;
+	tcu::Surface				reference;
+
+	DE_ASSERT(numBytes >= numBytesInQuad); // Can't render full quad with smaller buffers.
+
+	computePositions(positions, maxQuadsX, maxQuadsY);
+	computeIndices(indices, maxQuadsX, maxQuadsY);
+
+	// Reset buffer bindings.
+	glBindBuffer				(GL_PIXEL_PACK_BUFFER, 0);
+
+	// Setup rendering state.
+	glViewport					(0, 0, maxQuadsX*VERIFY_QUAD_SIZE, maxQuadsY*VERIFY_QUAD_SIZE);
+	glClearColor				(0.0f, 0.0f, 0.0f, 1.0f);
+	glUseProgram				(program);
+	glBindVertexArray			(m_vao);
+
+	// Upload positions
+	glBindBuffer				(GL_ARRAY_BUFFER, m_positionBuf);
+	glBufferData				(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(positions.size()*sizeof(positions[0])), &positions[0], GL_STATIC_DRAW);
+	glEnableVertexAttribArray	(m_posLoc);
+	glVertexAttribPointer		(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+
+	// Upload indices
+	glBindBuffer				(GL_ELEMENT_ARRAY_BUFFER, m_indexBuf);
+	glBufferData				(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indices.size()*sizeof(indices[0])), &indices[0], GL_STATIC_DRAW);
+
+	glEnableVertexAttribArray	(m_byteVecLoc);
+	glBindBuffer				(GL_ARRAY_BUFFER, buffer);
+
+	while (numVerified < numBytes)
+	{
+		int		numRemaining		= numBytes-numVerified;
+		bool	isLeftoverBatch		= numRemaining < numBytesInQuad;
+		int		numBytesToVerify	= isLeftoverBatch ? numBytesInQuad				: de::min(maxQuadsPerBatch*numBytesInQuad, numRemaining - numRemaining%numBytesInQuad);
+		int		curOffset			= isLeftoverBatch ? (numBytes-numBytesInQuad)	: numVerified;
+		int		numQuads			= numBytesToVerify/numBytesInQuad;
+		int		numCols				= de::min(maxQuadsX, numQuads);
+		int		numRows				= numQuads/maxQuadsX + (numQuads%maxQuadsX != 0 ? 1 : 0);
+		string	imageSetDesc		= string("Bytes ") + de::toString(offset+curOffset) + " to " + de::toString(offset+curOffset+numBytesToVerify-1);
+
+		DE_ASSERT(numBytesToVerify > 0 && numBytesToVerify%numBytesInQuad == 0);
+		DE_ASSERT(de::inBounds(curOffset, 0, numBytes));
+		DE_ASSERT(de::inRange(curOffset+numBytesToVerify, curOffset, numBytes));
+
+		// Render batch.
+		glClear					(GL_COLOR_BUFFER_BIT);
+		glVertexAttribPointer	(m_byteVecLoc, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0, (const glw::GLvoid*)(deUintptr)(offset + curOffset));
+		glDrawElements			(GL_TRIANGLES, numQuads*6, GL_UNSIGNED_SHORT, DE_NULL);
+
+		renderQuadGridReference(reference,  numQuads, numCols, refPtr + offset + curOffset);
+
+		rendered.setSize(numCols*VERIFY_QUAD_SIZE, numRows*VERIFY_QUAD_SIZE);
+		glu::readPixels(m_renderCtx, 0, 0, rendered.getAccess());
+
+		if (!tcu::pixelThresholdCompare(m_log, "RenderResult", imageSetDesc.c_str(), reference, rendered, threshold, tcu::COMPARE_LOG_RESULT))
+		{
+			isOk = false;
+			break;
+		}
+
+		numVerified += isLeftoverBatch ? numRemaining : numBytesToVerify;
+	}
+
+	glBindVertexArray(0);
+
+	return isOk;
+}
+
+// IndexArrayVerifier
+
+IndexArrayVerifier::IndexArrayVerifier (glu::RenderContext& renderCtx, tcu::TestLog& log)
+	: BufferVerifierBase	(renderCtx, log)
+	, m_program				(DE_NULL)
+	, m_posLoc				(0)
+	, m_colorLoc			(0)
+{
+
+	const glu::ContextType	ctxType		= renderCtx.getType();
+	const glu::GLSLVersion	glslVersion	= glu::isContextTypeES(ctxType) ? glu::GLSL_VERSION_300_ES : glu::GLSL_VERSION_330;
+
+	DE_ASSERT(glu::isGLSLVersionSupported(ctxType, glslVersion));
+
+	m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(
+		string(glu::getGLSLVersionDeclaration(glslVersion)) + "\n"
+		"in highp vec2 a_position;\n"
+		"in mediump vec3 a_color;\n"
+		"out mediump vec3 v_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
+		"	v_color = a_color;\n"
+		"}\n",
+
+		string(glu::getGLSLVersionDeclaration(glslVersion)) + "\n"
+		"in mediump vec3 v_color;\n"
+		"layout(location = 0) out mediump vec4 o_color;\n"
+		"void main (void)\n"
+		"{\n"
+		"	o_color = vec4(v_color, 1.0);\n"
+		"}\n"));
+
+	if (!m_program->isOk())
+	{
+		m_log << *m_program;
+		delete m_program;
+		TCU_FAIL("Compile failed");
+	}
+
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	m_posLoc	= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	m_colorLoc	= gl.getAttribLocation(m_program->getProgram(), "a_color");
+
+	gl.genVertexArrays(1, &m_vao);
+	gl.genBuffers(1, &m_positionBuf);
+	gl.genBuffers(1, &m_colorBuf);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Initialization failed");
+}
+
+IndexArrayVerifier::~IndexArrayVerifier (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_vao)			gl.deleteVertexArrays(1, &m_vao);
+	if (m_positionBuf)	gl.deleteBuffers(1, &m_positionBuf);
+	if (m_colorBuf)		gl.deleteBuffers(1, &m_colorBuf);
+
+	delete m_program;
+}
+
+static void computeIndexVerifierPositions (std::vector<tcu::Vec2>& dst)
+{
+	const int	numPosX		= 16;
+	const int	numPosY		= 16;
+
+	dst.resize(numPosX*numPosY);
+
+	for (int y = 0; y < numPosY; y++)
+	{
+		for (int x = 0; x < numPosX; x++)
+		{
+			float	xf	= float(x) / float(numPosX-1);
+			float	yf	= float(y) / float(numPosY-1);
+
+			dst[y*numPosX + x] = tcu::Vec2(2.0f*xf - 1.0f, 2.0f*yf - 1.0f);
+		}
+	}
+}
+
+static void computeIndexVerifierColors (std::vector<tcu::Vec3>& dst)
+{
+	const int	numColors	= 256;
+	const float	minVal		= 0.1f;
+	const float maxVal		= 0.5f;
+	de::Random	rnd			(0xabc231);
+
+	dst.resize(numColors);
+
+	for (std::vector<tcu::Vec3>::iterator i = dst.begin(); i != dst.end(); ++i)
+	{
+		i->x()	= rnd.getFloat(minVal, maxVal);
+		i->y()	= rnd.getFloat(minVal, maxVal);
+		i->z()	= rnd.getFloat(minVal, maxVal);
+	}
+}
+
+template<typename T>
+static void execVertexFetch (T* dst, const T* src, const deUint8* indices, int numIndices)
+{
+	for (int i = 0; i < numIndices; ++i)
+		dst[i] = src[indices[i]];
+}
+
+bool IndexArrayVerifier::verify (deUint32 buffer, const deUint8* refPtr, int offset, int numBytes)
+{
+	const tcu::RenderTarget&	renderTarget		= m_renderCtx.getRenderTarget();
+	const int					viewportW			= de::min<int>(INDEX_ARRAY_DRAW_VIEWPORT_WIDTH, renderTarget.getWidth());
+	const int					viewportH			= de::min<int>(INDEX_ARRAY_DRAW_VIEWPORT_HEIGHT, renderTarget.getHeight());
+	const int					minBytesPerBatch	= 2;
+	const tcu::RGBA				threshold			(0,0,0,0);
+
+	std::vector<tcu::Vec2>		positions;
+	std::vector<tcu::Vec3>		colors;
+
+	std::vector<tcu::Vec2>		fetchedPos			(MAX_LINES_PER_INDEX_ARRAY_DRAW+1);
+	std::vector<tcu::Vec3>		fetchedColor		(MAX_LINES_PER_INDEX_ARRAY_DRAW+1);
+
+	tcu::Surface				indexBufferImg		(viewportW, viewportH);
+	tcu::Surface				referenceImg		(viewportW, viewportH);
+
+	int							numVerified			= 0;
+	bool						isOk				= true;
+
+	DE_STATIC_ASSERT(sizeof(tcu::Vec2) == sizeof(float)*2);
+	DE_STATIC_ASSERT(sizeof(tcu::Vec3) == sizeof(float)*3);
+
+	computeIndexVerifierPositions(positions);
+	computeIndexVerifierColors(colors);
+
+	// Reset buffer bindings.
+	glBindVertexArray			(m_vao);
+	glBindBuffer				(GL_PIXEL_PACK_BUFFER,		0);
+	glBindBuffer				(GL_ELEMENT_ARRAY_BUFFER,	buffer);
+
+	// Setup rendering state.
+	glViewport					(0, 0, viewportW, viewportH);
+	glClearColor				(0.0f, 0.0f, 0.0f, 1.0f);
+	glUseProgram				(m_program->getProgram());
+	glEnableVertexAttribArray	(m_posLoc);
+	glEnableVertexAttribArray	(m_colorLoc);
+	glEnable					(GL_BLEND);
+	glBlendFunc					(GL_ONE, GL_ONE);
+	glBlendEquation				(GL_FUNC_ADD);
+
+	while (numVerified < numBytes)
+	{
+		int		numRemaining		= numBytes-numVerified;
+		bool	isLeftoverBatch		= numRemaining < minBytesPerBatch;
+		int		numBytesToVerify	= isLeftoverBatch ? minBytesPerBatch			: de::min(MAX_LINES_PER_INDEX_ARRAY_DRAW+1, numRemaining);
+		int		curOffset			= isLeftoverBatch ? (numBytes-minBytesPerBatch)	: numVerified;
+		string	imageSetDesc		= string("Bytes ") + de::toString(offset+curOffset) + " to " + de::toString(offset+curOffset+numBytesToVerify-1);
+
+		// Step 1: Render using index buffer.
+		glClear					(GL_COLOR_BUFFER_BIT);
+
+		glBindBuffer			(GL_ARRAY_BUFFER, m_positionBuf);
+		glBufferData			(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(positions.size()*sizeof(positions[0])), &positions[0], GL_STREAM_DRAW);
+		glVertexAttribPointer	(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+
+		glBindBuffer			(GL_ARRAY_BUFFER, m_colorBuf);
+		glBufferData			(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(colors.size()*sizeof(colors[0])), &colors[0], GL_STREAM_DRAW);
+		glVertexAttribPointer	(m_colorLoc, 3, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+
+		glDrawElements			(GL_LINE_STRIP, numBytesToVerify, GL_UNSIGNED_BYTE, (void*)(deUintptr)(offset+curOffset));
+		glu::readPixels			(m_renderCtx, 0, 0, indexBufferImg.getAccess());
+
+		// Step 2: Do manual fetch and render without index buffer.
+		execVertexFetch(&fetchedPos[0], &positions[0], refPtr+offset+curOffset, numBytesToVerify);
+		execVertexFetch(&fetchedColor[0], &colors[0], refPtr+offset+curOffset, numBytesToVerify);
+
+		glClear					(GL_COLOR_BUFFER_BIT);
+
+		glBindBuffer			(GL_ARRAY_BUFFER, m_positionBuf);
+		glBufferData			(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(fetchedPos.size()*sizeof(fetchedPos[0])), &fetchedPos[0], GL_STREAM_DRAW);
+		glVertexAttribPointer	(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+
+		glBindBuffer			(GL_ARRAY_BUFFER, m_colorBuf);
+		glBufferData			(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(fetchedColor.size()*sizeof(fetchedColor[0])), &fetchedColor[0], GL_STREAM_DRAW);
+		glVertexAttribPointer	(m_colorLoc, 3, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+
+		glDrawArrays			(GL_LINE_STRIP, 0, numBytesToVerify);
+		glu::readPixels			(m_renderCtx, 0, 0, referenceImg.getAccess());
+
+		if (!tcu::pixelThresholdCompare(m_log, "RenderResult", imageSetDesc.c_str(), referenceImg, indexBufferImg, threshold, tcu::COMPARE_LOG_RESULT))
+		{
+			isOk = false;
+			break;
+		}
+
+		numVerified += isLeftoverBatch ? numRemaining : numBytesToVerify;
+	}
+
+	glBindVertexArray(0);
+
+	return isOk;
+}
+
+const char* getWriteTypeDescription (WriteType write)
+{
+	static const char* s_desc[] =
+	{
+		"glBufferSubData()",
+		"glMapBufferRange()",
+		"transform feedback",
+		"glReadPixels() into PBO binding"
+	};
+	return de::getSizedArrayElement<WRITE_LAST>(s_desc, write);
+}
+
+const char* getVerifyTypeDescription (VerifyType verify)
+{
+	static const char* s_desc[] =
+	{
+		"rendering as vertex data",
+		"rendering as index data",
+		"reading in shader as uniform buffer data",
+		"using as PBO and uploading to texture",
+		"reading back using glMapBufferRange()"
+	};
+	return de::getSizedArrayElement<VERIFY_LAST>(s_desc, verify);
+}
+
+} // BufferTestUtil
+} // gls
+} // deqp
diff --git a/modules/glshared/glsBufferTestUtil.hpp b/modules/glshared/glsBufferTestUtil.hpp
new file mode 100644
index 0000000..379b048
--- /dev/null
+++ b/modules/glshared/glsBufferTestUtil.hpp
@@ -0,0 +1,287 @@
+#ifndef _GLSBUFFERTESTUTIL_HPP
+#define _GLSBUFFERTESTUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Buffer test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluObjectWrapper.hpp"
+
+#include <vector>
+#include <set>
+
+namespace tcu
+{
+class TestLog;
+}
+
+namespace glu
+{
+class RenderContext;
+class ShaderProgram;
+}
+
+namespace deqp
+{
+namespace gls
+{
+namespace BufferTestUtil
+{
+
+// Helper functions.
+
+void			fillWithRandomBytes		(deUint8* ptr, int numBytes, deUint32 seed);
+bool			compareByteArrays		(tcu::TestLog& log, const deUint8* resPtr, const deUint8* refPtr, int numBytes);
+const char*		getBufferTargetName		(deUint32 target);
+const char*		getUsageHintName		(deUint32 hint);
+
+// Base class for buffer cases.
+
+class BufferCase : public tcu::TestCase, public glu::CallLogWrapper
+{
+public:
+							BufferCase							(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description);
+	virtual					~BufferCase							(void);
+
+	void					init								(void);
+	void					deinit								(void);
+
+	deUint32				genBuffer							(void);
+	void					deleteBuffer						(deUint32 buffer);
+	void					checkError							(void);
+
+protected:
+	glu::RenderContext&		m_renderCtx;
+
+private:
+	// Resource handles for cleanup in case of unexpected iterate() termination.
+	std::set<deUint32>		m_allocatedBuffers;
+};
+
+// Reference buffer.
+
+class ReferenceBuffer
+{
+public:
+							ReferenceBuffer			(void) {}
+							~ReferenceBuffer		(void) {}
+
+	void					setSize					(int numBytes);
+	void					setData					(int numBytes, const deUint8* bytes);
+	void					setSubData				(int offset, int numBytes, const deUint8* bytes);
+
+	deUint8*				getPtr					(int offset = 0)		{ return &m_data[offset]; }
+	const deUint8*			getPtr					(int offset = 0) const	{ return &m_data[offset]; }
+
+private:
+	std::vector<deUint8>	m_data;
+};
+
+// Buffer writer system.
+
+enum WriteType
+{
+	WRITE_BUFFER_SUB_DATA = 0,
+	WRITE_BUFFER_WRITE_MAP,
+	WRITE_TRANSFORM_FEEDBACK,
+	WRITE_PIXEL_PACK,
+
+	WRITE_LAST
+};
+
+const char*	getWriteTypeDescription	(WriteType type);
+
+class BufferWriterBase : protected glu::CallLogWrapper
+{
+public:
+							BufferWriterBase		(glu::RenderContext& renderCtx, tcu::TestLog& log);
+	virtual					~BufferWriterBase		(void) {}
+
+	virtual int				getMinSize				(void) const = DE_NULL;
+	virtual int				getAlignment			(void) const = DE_NULL;
+	virtual void			write					(deUint32 buffer, int offset, int numBytes, const deUint8* bytes) = DE_NULL;
+	virtual void			write					(deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 targetHint);
+
+protected:
+	glu::RenderContext&		m_renderCtx;
+
+private:
+							BufferWriterBase		(const BufferWriterBase& other);
+	BufferWriterBase&		operator=				(const BufferWriterBase& other);
+};
+
+class BufferWriter
+{
+public:
+							BufferWriter			(glu::RenderContext& renderCtx, tcu::TestLog& log, WriteType writeType);
+							~BufferWriter			(void);
+
+	int						getMinSize				(void) const { return m_writer->getMinSize();	}
+	int						getAlignment			(void) const { return m_writer->getAlignment();	}
+	void					write					(deUint32 buffer, int offset, int numBytes, const deUint8* bytes);
+	void					write					(deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 targetHint);
+
+private:
+							BufferWriter			(const BufferWriter& other);
+	BufferWriter&			operator=				(const BufferWriter& other);
+
+	BufferWriterBase*		m_writer;
+};
+
+class BufferSubDataWriter : public BufferWriterBase
+{
+public:
+						BufferSubDataWriter			(glu::RenderContext& renderCtx, tcu::TestLog& log) : BufferWriterBase(renderCtx, log) {}
+						~BufferSubDataWriter		(void) {}
+
+	int					getMinSize					(void) const { return 1; }
+	int					getAlignment				(void) const { return 1; }
+	virtual void		write						(deUint32 buffer, int offset, int numBytes, const deUint8* bytes);
+	virtual void		write						(deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 target);
+};
+
+class BufferWriteMapWriter : public BufferWriterBase
+{
+public:
+						BufferWriteMapWriter		(glu::RenderContext& renderCtx, tcu::TestLog& log) : BufferWriterBase(renderCtx, log) {}
+						~BufferWriteMapWriter		(void) {}
+
+	int					getMinSize					(void) const { return 1; }
+	int					getAlignment				(void) const { return 1; }
+	virtual void		write						(deUint32 buffer, int offset, int numBytes, const deUint8* bytes);
+	virtual void		write						(deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 target);
+};
+
+// Buffer verifier system.
+
+enum VerifyType
+{
+	VERIFY_AS_VERTEX_ARRAY	= 0,
+	VERIFY_AS_INDEX_ARRAY,
+	VERIFY_AS_UNIFORM_BUFFER,
+	VERIFY_AS_PIXEL_UNPACK_BUFFER,
+	VERIFY_BUFFER_READ_MAP,
+
+	VERIFY_LAST
+};
+
+const char* getVerifyTypeDescription (VerifyType type);
+
+class BufferVerifierBase : public glu::CallLogWrapper
+{
+public:
+							BufferVerifierBase		(glu::RenderContext& renderCtx, tcu::TestLog& log);
+	virtual					~BufferVerifierBase		(void) {}
+
+	virtual int				getMinSize				(void) const = DE_NULL;
+	virtual int				getAlignment			(void) const = DE_NULL;
+	virtual bool			verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes) = DE_NULL;
+	virtual bool			verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes, deUint32 targetHint);
+
+protected:
+	glu::RenderContext&		m_renderCtx;
+	tcu::TestLog&			m_log;
+
+private:
+							BufferVerifierBase		(const BufferVerifierBase& other);
+	BufferVerifierBase&		operator=				(const BufferVerifierBase& other);
+};
+
+class BufferVerifier
+{
+public:
+							BufferVerifier			(glu::RenderContext& renderCtx, tcu::TestLog& log, VerifyType verifyType);
+							~BufferVerifier			(void);
+
+	int						getMinSize				(void) const { return m_verifier->getMinSize();		}
+	int						getAlignment			(void) const { return m_verifier->getAlignment();	}
+
+	// \note Offset is applied to reference pointer as well.
+	bool					verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes);
+	bool					verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes, deUint32 targetHint);
+
+private:
+							BufferVerifier			(const BufferVerifier& other);
+	BufferVerifier&			operator=				(const BufferVerifier& other);
+
+	BufferVerifierBase*		m_verifier;
+};
+
+class BufferMapVerifier : public BufferVerifierBase
+{
+public:
+						BufferMapVerifier		(glu::RenderContext& renderCtx, tcu::TestLog& log) : BufferVerifierBase(renderCtx, log) {}
+						~BufferMapVerifier		(void) {}
+
+	int					getMinSize				(void) const { return 1; }
+	int					getAlignment			(void) const { return 1; }
+	bool				verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes);
+	bool				verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes, deUint32 target);
+};
+
+class VertexArrayVerifier : public BufferVerifierBase
+{
+public:
+						VertexArrayVerifier		(glu::RenderContext& renderCtx, tcu::TestLog& log);
+						~VertexArrayVerifier	(void);
+
+	int					getMinSize				(void) const { return 3*4; }
+	int					getAlignment			(void) const { return 1; }
+	bool				verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes);
+
+private:
+	glu::ShaderProgram*	m_program;
+	deUint32			m_posLoc;
+	deUint32			m_byteVecLoc;
+
+	deUint32			m_vao;
+	deUint32			m_positionBuf;
+	deUint32			m_indexBuf;
+};
+
+class IndexArrayVerifier : public BufferVerifierBase
+{
+public:
+						IndexArrayVerifier		(glu::RenderContext& renderCtx, tcu::TestLog& log);
+						~IndexArrayVerifier		(void);
+
+	int					getMinSize				(void) const { return 2; }
+	int					getAlignment			(void) const { return 1; }
+	bool				verify					(deUint32 buffer, const deUint8* reference, int offset, int numBytes);
+
+private:
+	glu::ShaderProgram*	m_program;
+	deUint32			m_posLoc;
+	deUint32			m_colorLoc;
+
+	deUint32			m_vao;
+	deUint32			m_positionBuf;
+	deUint32			m_colorBuf;
+};
+
+} // BufferTestUtil
+} // gls
+} // deqp
+
+#endif // _GLSBUFFERTESTUTIL_HPP
diff --git a/modules/glshared/glsBuiltinPrecisionTests.cpp b/modules/glshared/glsBuiltinPrecisionTests.cpp
new file mode 100644
index 0000000..e2b6f4f
--- /dev/null
+++ b/modules/glshared/glsBuiltinPrecisionTests.cpp
@@ -0,0 +1,5209 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Precision and range tests for GLSL builtins and types.
+ *
+ *//*--------------------------------------------------------------------*/
+
+#include "glsBuiltinPrecisionTests.hpp"
+
+#include "deMath.h"
+#include "deMemory.h"
+#include "deDefs.hpp"
+#include "deRandom.hpp"
+#include "deSTLUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+#include "deSharedPtr.hpp"
+
+#include "tcuCommandLine.hpp"
+#include "tcuFloatFormat.hpp"
+#include "tcuInterval.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "tcuMatrix.hpp"
+
+#include "gluContextInfo.hpp"
+#include "gluVarType.hpp"
+#include "gluRenderContext.hpp"
+#include "glwDefs.hpp"
+
+#include "glsShaderExecUtil.hpp"
+
+#include <cmath>
+#include <string>
+#include <sstream>
+#include <iostream>
+#include <map>
+#include <utility>
+
+// Uncomment this to get evaluation trace dumps to std::cerr
+// #define GLS_ENABLE_TRACE
+
+// set this to true to dump even passing results
+#define GLS_LOG_ALL_RESULTS false
+
+namespace deqp
+{
+namespace gls
+{
+namespace BuiltinPrecisionTests
+{
+
+using std::string;
+using std::map;
+using std::ostream;
+using std::ostringstream;
+using std::pair;
+using std::vector;
+using std::set;
+
+using de::MovePtr;
+using de::Random;
+using de::SharedPtr;
+using de::UniquePtr;
+using tcu::Interval;
+using tcu::FloatFormat;
+using tcu::MessageBuilder;
+using tcu::TestCase;
+using tcu::TestLog;
+using tcu::Vector;
+using tcu::Matrix;
+namespace matrix = tcu::matrix;
+using glu::Precision;
+using glu::RenderContext;
+using glu::VarType;
+using glu::DataType;
+using glu::ShaderType;
+using glu::ContextInfo;
+using gls::ShaderExecUtil::Symbol;
+
+typedef TestCase::IterateResult IterateResult;
+
+using namespace glw;
+using namespace tcu;
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Generic singleton creator.
+ *
+ * instance<T>() returns a reference to a unique default-constructed instance
+ * of T. This is mainly used for our GLSL function implementations: each
+ * function is implemented by an object, and each of the objects has a
+ * distinct class. It would be extremely toilsome to maintain a separate
+ * context object that contained individual instances of the function classes,
+ * so we have to resort to global singleton instances.
+ *
+ *//*--------------------------------------------------------------------*/
+template <typename T>
+const T& instance (void)
+{
+	static const T s_instance = T();
+	return s_instance;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Dummy placeholder type for unused template parameters.
+ *
+ * In the precision tests we are dealing with functions of different arities.
+ * To minimize code duplication, we only define templates with the maximum
+ * number of arguments, currently four. If a function's arity is less than the
+ * maximum, Void us used as the type for unused arguments.
+ *
+ * Although Voids are not used at run-time, they still must be compilable, so
+ * they must support all operations that other types do.
+ *
+ *//*--------------------------------------------------------------------*/
+struct Void
+{
+	typedef	Void		Element;
+	enum
+	{
+		SIZE = 0,
+	};
+
+	template <typename T>
+	explicit			Void			(const T&)		{}
+						Void			(void)			{}
+						operator double	(void)	const	{ return TCU_NAN; }
+
+	// These are used to make Voids usable as containers in container-generic code.
+	Void&				operator[]		(int)			{ return *this; }
+	const Void&			operator[]		(int)	const	{ return *this; }
+};
+
+ostream& operator<< (ostream& os, Void) { return os << "()"; }
+
+//! Returns true for all other types except Void
+template <typename T>	bool isTypeValid		(void)	{ return true;	}
+template <>				bool isTypeValid<Void>	(void)	{ return false;	}
+
+//! Utility function for getting the name of a data type.
+//! This is used in vector and matrix constructors.
+template <typename T>
+const char* dataTypeNameOf (void)
+{
+	return glu::getDataTypeName(glu::dataTypeOf<T>());
+}
+
+template <>
+const char* dataTypeNameOf<Void> (void)
+{
+	DE_ASSERT(!"Impossible");
+	return DE_NULL;
+}
+
+//! A hack to get Void support for VarType.
+template <typename T>
+VarType getVarTypeOf (Precision prec = glu::PRECISION_LAST)
+{
+	return glu::varTypeOf<T>(prec);
+}
+
+template <>
+VarType getVarTypeOf<Void> (Precision)
+{
+	DE_ASSERT(!"Impossible");
+	return VarType();
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Type traits for generalized interval types.
+ *
+ * We are trying to compute sets of acceptable values not only for
+ * float-valued expressions but also for compound values: vectors and
+ * matrices. We approximate a set of vectors as a vector of intervals and
+ * likewise for matrices.
+ *
+ * We now need generalized operations for each type and its interval
+ * approximation. These are given in the type Traits<T>.
+ *
+ * The type Traits<T>::IVal is the approximation of T: it is `Interval` for
+ * scalar types, and a vector or matrix of intervals for container types.
+ *
+ * To allow template inference to take place, there are function wrappers for
+ * the actual operations in Traits<T>. Hence we can just use:
+ *
+ * makeIVal(someFloat)
+ *
+ * instead of:
+ *
+ * Traits<float>::doMakeIVal(value)
+ *
+ *//*--------------------------------------------------------------------*/
+
+template <typename T> struct Traits;
+
+//! Create container from elementwise singleton values.
+template <typename T>
+typename Traits<T>::IVal makeIVal (const T& value)
+{
+	return Traits<T>::doMakeIVal(value);
+}
+
+//! Elementwise union of intervals.
+template <typename T>
+typename Traits<T>::IVal unionIVal (const typename Traits<T>::IVal& a,
+									const typename Traits<T>::IVal& b)
+{
+	return Traits<T>::doUnion(a, b);
+}
+
+//! Returns true iff every element of `ival` contains the corresponding element of `value`.
+template <typename T>
+bool contains (const typename Traits<T>::IVal& ival, const T& value)
+{
+	return Traits<T>::doContains(ival, value);
+}
+
+//! Print out an interval with the precision of `fmt`.
+template <typename T>
+void printIVal (const FloatFormat& fmt, const typename Traits<T>::IVal& ival, ostream& os)
+{
+	Traits<T>::doPrintIVal(fmt, ival, os);
+}
+
+template <typename T>
+string intervalToString (const FloatFormat& fmt, const typename Traits<T>::IVal& ival)
+{
+	ostringstream oss;
+	printIVal<T>(fmt, ival, oss);
+	return oss.str();
+}
+
+//! Print out a value with the precision of `fmt`.
+template <typename T>
+void printValue (const FloatFormat& fmt, const T& value, ostream& os)
+{
+	Traits<T>::doPrintValue(fmt, value, os);
+}
+
+template <typename T>
+string valueToString (const FloatFormat& fmt, const T& val)
+{
+	ostringstream oss;
+	printValue(fmt, val, oss);
+	return oss.str();
+}
+
+//! Approximate `value` elementwise to the float precision defined in `fmt`.
+//! The resulting interval might not be a singleton if rounding in both
+//! directions is allowed.
+template <typename T>
+typename Traits<T>::IVal round (const FloatFormat& fmt, const T& value)
+{
+	return Traits<T>::doRound(fmt, value);
+}
+
+template <typename T>
+typename Traits<T>::IVal convert (const FloatFormat&				fmt,
+								  const typename Traits<T>::IVal&	value)
+{
+	return Traits<T>::doConvert(fmt, value);
+}
+
+//! Common traits for scalar types.
+template <typename T>
+struct ScalarTraits
+{
+	typedef 			Interval		IVal;
+
+	static Interval		doMakeIVal		(const T& value)
+	{
+		// Thankfully all scalar types have a well-defined conversion to `double`,
+		// hence Interval can represent their ranges without problems.
+		return Interval(double(value));
+	}
+
+	static Interval		doUnion			(const Interval& a, const Interval& b)
+	{
+		return a | b;
+	}
+
+	static bool			doContains		(const Interval& a, T value)
+	{
+		return a.contains(double(value));
+	}
+
+	static Interval		doConvert		(const FloatFormat& fmt, const IVal& ival)
+	{
+		return fmt.convert(ival);
+	}
+
+	static Interval		doRound			(const FloatFormat& fmt, T value)
+	{
+		return fmt.roundOut(double(value));
+	}
+};
+
+template<>
+struct Traits<float> : ScalarTraits<float>
+{
+	static void			doPrintIVal		(const FloatFormat&	fmt,
+										 const Interval&	ival,
+										 ostream&			os)
+	{
+		os << fmt.intervalToHex(ival);
+	}
+
+	static void			doPrintValue	(const FloatFormat&	fmt,
+										 const float&		value,
+										 ostream&			os)
+	{
+		os << fmt.floatToHex(value);
+	}
+};
+
+template<>
+struct Traits<bool> : ScalarTraits<bool>
+{
+	static void			doPrintValue	(const FloatFormat&,
+										 const float&		value,
+										 ostream&			os)
+	{
+		os << (value ? "true" : "false");
+	}
+
+	static void			doPrintIVal		(const FloatFormat&,
+										 const Interval&	ival,
+										 ostream&			os)
+	{
+		os << "{";
+		if (ival.contains(false))
+			os << "false";
+		if (ival.contains(false) && ival.contains(true))
+			os << ", ";
+		if (ival.contains(true))
+			os << "true";
+		os << "}";
+	}
+};
+
+template<>
+struct Traits<int> : ScalarTraits<int>
+{
+	static void			doPrintValue 	(const FloatFormat&,
+										 const int&			value,
+										 ostream&			os)
+	{
+		os << value;
+	}
+
+	static void			doPrintIVal		(const FloatFormat&,
+										 const Interval&	ival,
+										 ostream&			os)
+	{
+		os << "[" << int(ival.lo()) << ", " << int(ival.hi()) << "]";
+	}
+};
+
+//! Common traits for containers, i.e. vectors and matrices.
+//! T is the container type itself, I is the same type with interval elements.
+template <typename T, typename I>
+struct ContainerTraits
+{
+	typedef typename	T::Element		Element;
+	typedef				I				IVal;
+
+	static IVal			doMakeIVal		(const T& value)
+	{
+		IVal ret;
+
+		for (int ndx = 0; ndx < T::SIZE; ++ndx)
+			ret[ndx] = makeIVal(value[ndx]);
+
+		return ret;
+	}
+
+	static IVal			doUnion			(const IVal& a, const IVal& b)
+	{
+		IVal ret;
+
+		for (int ndx = 0; ndx < T::SIZE; ++ndx)
+			ret[ndx] = unionIVal<Element>(a[ndx], b[ndx]);
+
+		return ret;
+	}
+
+	static bool			doContains		(const IVal& ival, const T& value)
+	{
+		for (int ndx = 0; ndx < T::SIZE; ++ndx)
+			if (!contains(ival[ndx], value[ndx]))
+				return false;
+
+		return true;
+	}
+
+	static void			doPrintIVal		(const FloatFormat& fmt, const IVal ival, ostream& os)
+	{
+		os << "(";
+
+		for (int ndx = 0; ndx < T::SIZE; ++ndx)
+		{
+			if (ndx > 0)
+				os << ", ";
+
+			printIVal<Element>(fmt, ival[ndx], os);
+		}
+
+		os << ")";
+	}
+
+	static void			doPrintValue	(const FloatFormat& fmt, const T& value, ostream& os)
+	{
+		os << dataTypeNameOf<T>() << "(";
+
+		for (int ndx = 0; ndx < T::SIZE; ++ndx)
+		{
+			if (ndx > 0)
+				os << ", ";
+
+			printValue<Element>(fmt, value[ndx], os);
+		}
+
+		os << ")";
+	}
+
+	static IVal			doConvert		(const FloatFormat& fmt, const IVal& value)
+	{
+		IVal ret;
+
+		for (int ndx = 0; ndx < T::SIZE; ++ndx)
+			ret[ndx] = convert<Element>(fmt, value[ndx]);
+
+		return ret;
+	}
+
+	static IVal			doRound			(const FloatFormat& fmt, T value)
+	{
+		IVal ret;
+
+		for (int ndx = 0; ndx < T::SIZE; ++ndx)
+			ret[ndx] = round(fmt, value[ndx]);
+
+		return ret;
+	}
+};
+
+template <typename T, int Size>
+struct Traits<Vector<T, Size> > :
+	ContainerTraits<Vector<T, Size>, Vector<typename Traits<T>::IVal, Size> >
+{
+};
+
+template <typename T, int Rows, int Cols>
+struct Traits<Matrix<T, Rows, Cols> > :
+	ContainerTraits<Matrix<T, Rows, Cols>, Matrix<typename Traits<T>::IVal, Rows, Cols> >
+{
+};
+
+//! Void traits. These are just dummies, but technically valid: a Void is a
+//! unit type with a single possible value.
+template<>
+struct Traits<Void>
+{
+	typedef		Void			IVal;
+
+	static Void	doMakeIVal		(const Void& value) 					{ return value; }
+	static Void	doUnion			(const Void&, const Void&)				{ return Void(); }
+	static bool	doContains		(const Void&, Void) 					{ return true; }
+	static Void	doRound			(const FloatFormat&, const Void& value)	{ return value; }
+	static Void	doConvert		(const FloatFormat&, const Void& value)	{ return value; }
+
+	static void	doPrintValue 	(const FloatFormat&, const Void&, ostream& os)
+	{
+		os << "()";
+	}
+
+	static void	doPrintIVal		(const FloatFormat&, const Void&, ostream& os)
+	{
+		os << "()";
+	}
+};
+
+//! This is needed for container-generic operations.
+//! We want a scalar type T to be its own "one-element vector".
+template <typename T, int Size> struct ContainerOf	{ typedef Vector<T, Size>	Container; };
+
+template <typename T>			struct ContainerOf<T, 1>		{ typedef T		Container; };
+template <int Size> 			struct ContainerOf<Void, Size>	{ typedef Void	Container; };
+
+// This is a kludge that is only needed to get the ExprP::operator[] syntactic sugar to work.
+template <typename T>	struct ElementOf		{ typedef	typename T::Element	Element; };
+template <>				struct ElementOf<float>	{ typedef	void				Element; };
+template <>				struct ElementOf<bool>	{ typedef	void				Element; };
+template <>				struct ElementOf<int>	{ typedef	void				Element; };
+
+/*--------------------------------------------------------------------*//*!
+ *
+ * \name Abstract syntax for expressions and statements.
+ *
+ * We represent GLSL programs as syntax objects: an Expr<T> represents an
+ * expression whose GLSL type corresponds to the C++ type T, and a Statement
+ * represents a statement.
+ *
+ * To ease memory management, we use shared pointers to refer to expressions
+ * and statements. ExprP<T> is a shared pointer to an Expr<T>, and StatementP
+ * is a shared pointer to a Statement.
+ *
+ * \{
+ *
+ *//*--------------------------------------------------------------------*/
+
+class ExprBase;
+class ExpandContext;
+class Statement;
+class StatementP;
+class FuncBase;
+template <typename T> class ExprP;
+template <typename T> class Variable;
+template <typename T> class VariableP;
+template <typename T> class DefaultSampling;
+
+typedef set<const FuncBase*> FuncSet;
+
+template <typename T>
+VariableP<T>	variable			(const string& name);
+StatementP		compoundStatement	(const vector<StatementP>& statements);
+
+/*--------------------------------------------------------------------*//*!
+ * \brief A variable environment.
+ *
+ * An Environment object maintains the mapping between variables of the
+ * abstract syntax tree and their values.
+ *
+ * \todo [2014-03-28 lauri] At least run-time type safety.
+ *
+ *//*--------------------------------------------------------------------*/
+class Environment
+{
+public:
+	template<typename T>
+	void						bind	(const Variable<T>&					variable,
+										 const typename Traits<T>::IVal&	value)
+	{
+		deUint8* const data = new deUint8[sizeof(value)];
+
+		deMemcpy(data, &value, sizeof(value));
+		de::insert(m_map, variable.getName(), SharedPtr<deUint8>(data));
+	}
+
+	template<typename T>
+	typename Traits<T>::IVal&	lookup	(const Variable<T>& variable) const
+	{
+		deUint8* const data = de::lookup(m_map, variable.getName()).get();
+
+		return *reinterpret_cast<typename Traits<T>::IVal*>(data);
+	}
+
+private:
+	map<string, SharedPtr<deUint8, de::ArrayDeleter<deUint8> > >	m_map;
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Evaluation context.
+ *
+ * The evaluation context contains everything that separates one execution of
+ * an expression from the next. Currently this means the desired floating
+ * point precision and the current variable environment.
+ *
+ *//*--------------------------------------------------------------------*/
+struct EvalContext
+{
+	EvalContext (const FloatFormat&	format_,
+				 Precision			floatPrecision_,
+				 Environment&		env_,
+				 int				callDepth_ = 0)
+		: format			(format_)
+		, floatPrecision	(floatPrecision_)
+		, env				(env_)
+		, callDepth			(callDepth_) {}
+
+	FloatFormat		format;
+	Precision		floatPrecision;
+	Environment&	env;
+	int				callDepth;
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Simple incremental counter.
+ *
+ * This is used to make sure that different ExpandContexts will not produce
+ * overlapping temporary names.
+ *
+ *//*--------------------------------------------------------------------*/
+class Counter
+{
+public:
+			Counter		(int count = 0) : m_count(count) {}
+	int		operator()	(void) { return m_count++; }
+
+private:
+	int		m_count;
+};
+
+class ExpandContext
+{
+public:
+						ExpandContext	(Counter& symCounter) : m_symCounter(symCounter) {}
+						ExpandContext	(const ExpandContext& parent)
+							: m_symCounter(parent.m_symCounter) {}
+
+	template<typename T>
+	VariableP<T>		genSym			(const string& baseName)
+	{
+		return variable<T>(baseName + de::toString(m_symCounter()));
+	}
+
+	void				addStatement	(const StatementP& stmt)
+	{
+		m_statements.push_back(stmt);
+	}
+
+	vector<StatementP>	getStatements	(void) const
+	{
+		return m_statements;
+	}
+private:
+	Counter&			m_symCounter;
+	vector<StatementP>	m_statements;
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief A statement or declaration.
+ *
+ * Statements have no values. Instead, they are executed for their side
+ * effects only: the execute() method should modify at least one variable in
+ * the environment.
+ *
+ * As a bit of a kludge, a Statement object can also represent a declaration:
+ * when it is evaluated, it can add a variable binding to the environment
+ * instead of modifying a current one.
+ *
+ *//*--------------------------------------------------------------------*/
+class Statement
+{
+public:
+	virtual	~Statement		(void) 							{								 }
+	//! Execute the statement, modifying the environment of `ctx`
+	void	execute			(EvalContext&	ctx)	const	{ this->doExecute(ctx);			 }
+	void	print			(ostream&		os)		const	{ this->doPrint(os);			 }
+	//! Add the functions used in this statement to `dst`.
+	void	getUsedFuncs	(FuncSet& dst)			const	{ this->doGetUsedFuncs(dst);	 }
+
+protected:
+	virtual void	doPrint			(ostream& os)			const	= 0;
+	virtual void	doExecute		(EvalContext& ctx)		const	= 0;
+	virtual void	doGetUsedFuncs	(FuncSet& dst)			const	= 0;
+};
+
+ostream& operator<<(ostream& os, const Statement& stmt)
+{
+	stmt.print(os);
+	return os;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Smart pointer for statements (and declarations)
+ *
+ *//*--------------------------------------------------------------------*/
+class StatementP : public SharedPtr<const Statement>
+{
+public:
+	typedef		SharedPtr<const Statement>	Super;
+
+				StatementP			(void) {}
+	explicit	StatementP			(const Statement* ptr)	: Super(ptr) {}
+				StatementP			(const Super& ptr)		: Super(ptr) {}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief
+ *
+ * A statement that modifies a variable or a declaration that binds a variable.
+ *
+ *//*--------------------------------------------------------------------*/
+template <typename T>
+class VariableStatement : public Statement
+{
+public:
+					VariableStatement	(const VariableP<T>& variable, const ExprP<T>& value,
+										 bool isDeclaration)
+						: m_variable		(variable)
+						, m_value			(value)
+						, m_isDeclaration	(isDeclaration) {}
+
+protected:
+	void			doPrint				(ostream& os)							const
+	{
+		if (m_isDeclaration)
+			os << glu::declare(getVarTypeOf<T>(), m_variable->getName());
+		else
+			os << m_variable->getName();
+
+		os << " = " << *m_value << ";\n";
+	}
+
+	void			doExecute			(EvalContext& ctx)						const
+	{
+		if (m_isDeclaration)
+			ctx.env.bind(*m_variable, m_value->evaluate(ctx));
+		else
+			ctx.env.lookup(*m_variable) = m_value->evaluate(ctx);
+	}
+
+	void			doGetUsedFuncs		(FuncSet& dst)							const
+	{
+		m_value->getUsedFuncs(dst);
+	}
+
+	VariableP<T>	m_variable;
+	ExprP<T>		m_value;
+	bool			m_isDeclaration;
+};
+
+template <typename T>
+StatementP variableStatement (const VariableP<T>&	variable,
+							  const ExprP<T>&		value,
+							  bool					isDeclaration)
+{
+	return StatementP(new VariableStatement<T>(variable, value, isDeclaration));
+}
+
+template <typename T>
+StatementP variableDeclaration (const VariableP<T>& variable, const ExprP<T>& definiens)
+{
+	return variableStatement(variable, definiens, true);
+}
+
+template <typename T>
+StatementP variableAssignment (const VariableP<T>& variable, const ExprP<T>& value)
+{
+	return variableStatement(variable, value, false);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief A compound statement, i.e. a block.
+ *
+ * A compound statement is executed by executing its constituent statements in
+ * sequence.
+ *
+ *//*--------------------------------------------------------------------*/
+class CompoundStatement : public Statement
+{
+public:
+						CompoundStatement	(const vector<StatementP>& statements)
+							: m_statements	(statements) {}
+
+protected:
+	void				doPrint				(ostream&		os)						const
+	{
+		os << "{\n";
+
+		for (size_t ndx = 0; ndx < m_statements.size(); ++ndx)
+			os << *m_statements[ndx];
+
+		os << "}\n";
+	}
+
+	void				doExecute			(EvalContext&	ctx)					const
+	{
+		for (size_t ndx = 0; ndx < m_statements.size(); ++ndx)
+			m_statements[ndx]->execute(ctx);
+	}
+
+	void				doGetUsedFuncs		(FuncSet& dst)							const
+	{
+		for (size_t ndx = 0; ndx < m_statements.size(); ++ndx)
+			m_statements[ndx]->getUsedFuncs(dst);
+	}
+
+	vector<StatementP>	m_statements;
+};
+
+StatementP compoundStatement(const vector<StatementP>& statements)
+{
+	return StatementP(new CompoundStatement(statements));
+}
+
+//! Common base class for all expressions regardless of their type.
+class ExprBase
+{
+public:
+	virtual				~ExprBase		(void)									{}
+	void				printExpr		(ostream& os) const { this->doPrintExpr(os); }
+
+	//! Output the functions that this expression refers to
+	void				getUsedFuncs	(FuncSet& dst) const
+	{
+		this->doGetUsedFuncs(dst);
+	}
+
+protected:
+	virtual void		doPrintExpr		(ostream&)	const	{}
+	virtual void		doGetUsedFuncs	(FuncSet&)	const	{}
+};
+
+//! Type-specific operations for an expression representing type T.
+template <typename T>
+class Expr : public ExprBase
+{
+public:
+	typedef 			T				Val;
+	typedef typename 	Traits<T>::IVal	IVal;
+
+	IVal				evaluate		(const EvalContext&	ctx) const;
+
+protected:
+	virtual IVal		doEvaluate		(const EvalContext&	ctx) const = 0;
+};
+
+//! Evaluate an expression with the given context, optionally tracing the calls to stderr.
+template <typename T>
+typename Traits<T>::IVal Expr<T>::evaluate (const EvalContext& ctx) const
+{
+#ifdef GLS_ENABLE_TRACE
+	EvalContext			newCtx	(ctx.format, ctx.floatPrecision, ctx.env, ctx.callDepth + 1);
+	const IVal			ret		= this->doEvaluate(newCtx);
+
+	if (isTypeValid<T>())
+	{
+		std::cerr << string(ctx.callDepth, ' ');
+		this->printExpr(std::cerr);
+		std::cerr << " -> " << intervalToString<T>(ctx.format, ret) << std::endl;
+	}
+	return ret;
+#else
+	return this->doEvaluate(ctx);
+#endif
+}
+
+template <typename T>
+class ExprPBase : public SharedPtr<const Expr<T> >
+{
+public:
+};
+
+ostream& operator<< (ostream& os, const ExprBase& expr)
+{
+	expr.printExpr(os);
+	return os;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Shared pointer to an expression of a container type.
+ *
+ * Container types (i.e. vectors and matrices) support the subscription
+ * operator. This class provides a bit of syntactic sugar to allow us to use
+ * the C++ subscription operator to create a subscription expression.
+ *//*--------------------------------------------------------------------*/
+template <typename T>
+class ContainerExprPBase : public ExprPBase<T>
+{
+public:
+	ExprP<typename T::Element>	operator[]	(int i) const;
+};
+
+template <typename T>
+class ExprP : public ExprPBase<T> {};
+
+// We treat Voids as containers since the dummy parameters in generalized
+// vector functions are represented as Voids.
+template <>
+class ExprP<Void> : public ContainerExprPBase<Void> {};
+
+template <typename T, int Size>
+class ExprP<Vector<T, Size> > : public ContainerExprPBase<Vector<T, Size> > {};
+
+template <typename T, int Rows, int Cols>
+class ExprP<Matrix<T, Rows, Cols> > : public ContainerExprPBase<Matrix<T, Rows, Cols> > {};
+
+template <typename T> ExprP<T> exprP (void)
+{
+	return ExprP<T>();
+}
+
+template <typename T>
+ExprP<T> exprP (const SharedPtr<const Expr<T> >& ptr)
+{
+	ExprP<T> ret;
+	static_cast<SharedPtr<const Expr<T> >&>(ret) = ptr;
+	return ret;
+}
+
+template <typename T>
+ExprP<T> exprP (const Expr<T>* ptr)
+{
+	return exprP(SharedPtr<const Expr<T> >(ptr));
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief A shared pointer to a variable expression.
+ *
+ * This is just a narrowing of ExprP for the operations that require a variable
+ * instead of an arbitrary expression.
+ *
+ *//*--------------------------------------------------------------------*/
+template <typename T>
+class VariableP : public SharedPtr<const Variable<T> >
+{
+public:
+	typedef		SharedPtr<const Variable<T> >	Super;
+	explicit	VariableP	(const Variable<T>* ptr) : Super(ptr) {}
+				VariableP	(void) {}
+				VariableP	(const Super& ptr) : Super(ptr) {}
+
+	operator	ExprP<T>	(void) const { return exprP(SharedPtr<const Expr<T> >(*this)); }
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \name Syntactic sugar operators for expressions.
+ *
+ * @{
+ *
+ * These operators allow the use of C++ syntax to construct GLSL expressions
+ * containing operators: e.g. "a+b" creates an addition expression with
+ * operands a and b, and so on.
+ *
+ *//*--------------------------------------------------------------------*/
+ExprP<float>						operator-(const ExprP<float>&						arg0);
+ExprP<float>						operator+(const ExprP<float>&						arg0,
+											  const ExprP<float>&						arg1);
+ExprP<float>						operator-(const ExprP<float>&						arg0,
+											  const ExprP<float>&						arg1);
+ExprP<float> 						operator*(const ExprP<float>&						arg0,
+											  const ExprP<float>&						arg1);
+ExprP<float> 						operator/(const ExprP<float>&						arg0,
+											  const ExprP<float>&						arg1);
+template<int Size>
+ExprP<Vector<float, Size> >			operator-(const ExprP<Vector<float, Size> >&		arg0);
+template<int Size>
+ExprP<Vector<float, Size> >			operator*(const ExprP<Vector<float, Size> >&		arg0,
+											  const ExprP<float>&						arg1);
+template<int Size>
+ExprP<Vector<float, Size> >			operator*(const ExprP<Vector<float, Size> >&		arg0,
+											  const ExprP<Vector<float, Size> >&		arg1);
+template<int Size>
+ExprP<Vector<float, Size> >			operator-(const ExprP<Vector<float, Size> >&		arg0,
+											  const ExprP<Vector<float, Size> >&		arg1);
+template<int Left, int Mid, int Right>
+ExprP<Matrix<float, Left, Right> >	operator* (const ExprP<Matrix<float, Left, Mid> >&	left,
+											   const ExprP<Matrix<float, Mid, Right> >&	right);
+template<int Rows, int Cols>
+ExprP<Vector<float, Rows> >			operator* (const ExprP<Vector<float, Cols> >&		left,
+											   const ExprP<Matrix<float, Rows, Cols> >&	right);
+template<int Rows, int Cols>
+ExprP<Vector<float, Cols> >			operator* (const ExprP<Matrix<float, Rows, Cols> >&	left,
+											   const ExprP<Vector<float, Rows> >&		right);
+template<int Rows, int Cols>
+ExprP<Matrix<float, Rows, Cols> >	operator* (const ExprP<Matrix<float, Rows, Cols> >&	left,
+											   const ExprP<float>&						right);
+template<int Rows, int Cols>
+ExprP<Matrix<float, Rows, Cols> > 	operator+ (const ExprP<Matrix<float, Rows, Cols> >&	left,
+											   const ExprP<Matrix<float, Rows, Cols> >&	right);
+template<int Rows, int Cols>
+ExprP<Matrix<float, Rows, Cols> > 	operator- (const ExprP<Matrix<float, Rows, Cols> >&	mat);
+
+//! @}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Variable expression.
+ *
+ * A variable is evaluated by looking up its range of possible values from an
+ * environment.
+ *//*--------------------------------------------------------------------*/
+template <typename T>
+class Variable : public Expr<T>
+{
+public:
+	typedef typename Expr<T>::IVal IVal;
+
+					Variable	(const string& name) : m_name (name) {}
+	string			getName		(void)							const { return m_name; }
+
+protected:
+	void			doPrintExpr	(ostream& os)					const { os << m_name; }
+	IVal			doEvaluate	(const EvalContext& ctx)		const
+	{
+		return ctx.env.lookup<T>(*this);
+	}
+
+private:
+	string	m_name;
+};
+
+template <typename T>
+VariableP<T> variable (const string& name)
+{
+	return VariableP<T>(new Variable<T>(name));
+}
+
+template <typename T>
+VariableP<T> bindExpression (const string& name, ExpandContext& ctx, const ExprP<T>& expr)
+{
+	VariableP<T> var = ctx.genSym<T>(name);
+	ctx.addStatement(variableDeclaration(var, expr));
+	return var;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Constant expression.
+ *
+ * A constant is evaluated by rounding it to a set of possible values allowed
+ * by the current floating point precision.
+ *//*--------------------------------------------------------------------*/
+template <typename T>
+class Constant : public Expr<T>
+{
+public:
+	typedef typename Expr<T>::IVal IVal;
+
+			Constant		(const T& value) : m_value(value) {}
+
+protected:
+	void	doPrintExpr		(ostream& os) const 		{ os << m_value; }
+	IVal	doEvaluate		(const EvalContext&) const	{ return makeIVal(m_value); }
+
+private:
+	T		m_value;
+};
+
+template <typename T>
+ExprP<T> constant (const T& value)
+{
+	return exprP(new Constant<T>(value));
+}
+
+//! Return a reference to a singleton void constant.
+const ExprP<Void>& voidP (void)
+{
+	static const ExprP<Void> singleton = constant(Void());
+
+	return singleton;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Four-element tuple.
+ *
+ * This is used for various things where we need one thing for each possible
+ * function parameter. Currently the maximum supported number of parameters is
+ * four.
+ *//*--------------------------------------------------------------------*/
+template <typename T0 = Void, typename T1 = Void, typename T2 = Void, typename T3 = Void>
+struct Tuple4
+{
+	explicit Tuple4 (const T0& e0 = T0(),
+					 const T1& e1 = T1(),
+					 const T2& e2 = T2(),
+					 const T3& e3 = T3())
+		: a	(e0)
+		, b	(e1)
+		, c	(e2)
+		, d	(e3)
+	{
+	}
+
+	T0 a;
+	T1 b;
+	T2 c;
+	T3 d;
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Function signature.
+ *
+ * This is a purely compile-time structure used to bundle all types in a
+ * function signature together. This makes passing the signature around in
+ * templates easier, since we only need to take and pass a single Sig instead
+ * of a bunch of parameter types and a return type.
+ *
+ *//*--------------------------------------------------------------------*/
+template <typename R,
+		  typename P0 = Void, typename P1 = Void,
+		  typename P2 = Void, typename P3 = Void>
+struct Signature
+{
+	typedef R							Ret;
+	typedef P0							Arg0;
+	typedef P1							Arg1;
+	typedef P2							Arg2;
+	typedef P3							Arg3;
+	typedef typename Traits<Ret>::IVal	IRet;
+	typedef typename Traits<Arg0>::IVal	IArg0;
+	typedef typename Traits<Arg1>::IVal	IArg1;
+	typedef typename Traits<Arg2>::IVal	IArg2;
+	typedef typename Traits<Arg3>::IVal	IArg3;
+
+	typedef Tuple4<	const Arg0&,	const Arg1&,	const Arg2&,	const Arg3&>	Args;
+	typedef Tuple4<	const IArg0&,	const IArg1&,	const IArg2&,	const IArg3&> 	IArgs;
+	typedef Tuple4<	ExprP<Arg0>,	ExprP<Arg1>,	ExprP<Arg2>,	ExprP<Arg3> >	ArgExprs;
+};
+
+typedef vector<const ExprBase*> BaseArgExprs;
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Type-independent operations for function objects.
+ *
+ *//*--------------------------------------------------------------------*/
+class FuncBase
+{
+public:
+	virtual			~FuncBase				(void)					{}
+	virtual string	getName					(void) 					const = 0;
+	//! Name of extension that this function requires, or empty.
+	virtual string	getRequiredExtension	(void) 					const { return ""; }
+	virtual void	print					(ostream&,
+											 const BaseArgExprs&)	const = 0;
+	//! Index of output parameter, or -1 if none of the parameters is output.
+	virtual int		getOutParamIndex		(void)					const { return -1; }
+
+	void			printDefinition			(ostream& os)			const
+	{
+		doPrintDefinition(os);
+	}
+
+	void				getUsedFuncs		(FuncSet& dst) const
+	{
+		this->doGetUsedFuncs(dst);
+	}
+
+protected:
+	virtual void	doPrintDefinition		(ostream& os)			const = 0;
+	virtual void	doGetUsedFuncs			(FuncSet& dst)			const = 0;
+};
+
+typedef Tuple4<string, string, string, string> ParamNames;
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Function objects.
+ *
+ * Each Func object represents a GLSL function. It can be applied to interval
+ * arguments, and it returns the an interval that is a conservative
+ * approximation of the image of the GLSL function over the argument
+ * intervals. That is, it is given a set of possible arguments and it returns
+ * the set of possible values.
+ *
+ *//*--------------------------------------------------------------------*/
+template <typename Sig_>
+class Func : public FuncBase
+{
+public:
+	typedef Sig_										Sig;
+	typedef typename Sig::Ret							Ret;
+	typedef typename Sig::Arg0							Arg0;
+	typedef typename Sig::Arg1							Arg1;
+	typedef typename Sig::Arg2							Arg2;
+	typedef typename Sig::Arg3							Arg3;
+	typedef typename Sig::IRet							IRet;
+	typedef typename Sig::IArg0							IArg0;
+	typedef typename Sig::IArg1							IArg1;
+	typedef typename Sig::IArg2							IArg2;
+	typedef typename Sig::IArg3							IArg3;
+	typedef typename Sig::Args							Args;
+	typedef typename Sig::IArgs							IArgs;
+	typedef typename Sig::ArgExprs						ArgExprs;
+
+	void				print			(ostream&			os,
+										 const BaseArgExprs& args)				const
+	{
+		this->doPrint(os, args);
+	}
+
+	IRet				apply			(const EvalContext&	ctx,
+										 const IArg0&		arg0 = IArg0(),
+										 const IArg1&		arg1 = IArg1(),
+										 const IArg2&		arg2 = IArg2(),
+										 const IArg3&		arg3 = IArg3())		const
+	{
+		return this->applyArgs(ctx, IArgs(arg0, arg1, arg2, arg3));
+	}
+	IRet				applyArgs		(const EvalContext&	ctx,
+										 const IArgs&		args)				const
+	{
+		return this->doApply(ctx, args);
+	}
+	ExprP<Ret>			operator()		(const ExprP<Arg0>&		arg0 = voidP(),
+										 const ExprP<Arg1>&		arg1 = voidP(),
+										 const ExprP<Arg2>&		arg2 = voidP(),
+										 const ExprP<Arg3>&		arg3 = voidP())		const;
+
+	const ParamNames&	getParamNames	(void)									const
+	{
+		return this->doGetParamNames();
+	}
+
+protected:
+	virtual IRet		doApply			(const EvalContext&,
+										 const IArgs&)							const = 0;
+	virtual void		doPrint			(ostream& os, const BaseArgExprs& args)	const
+	{
+		os << getName() << "(";
+
+		if (isTypeValid<Arg0>())
+			os << *args[0];
+
+		if (isTypeValid<Arg1>())
+			os << ", " << *args[1];
+
+		if (isTypeValid<Arg2>())
+			os << ", " << *args[2];
+
+		if (isTypeValid<Arg3>())
+			os << ", " << *args[3];
+
+		os << ")";
+	}
+
+	virtual const ParamNames&	doGetParamNames	(void)							const
+	{
+		static ParamNames	names	("a", "b", "c", "d");
+		return names;
+	}
+};
+
+template <typename Sig>
+class Apply : public Expr<typename Sig::Ret>
+{
+public:
+	typedef typename Sig::Ret				Ret;
+	typedef typename Sig::Arg0				Arg0;
+	typedef typename Sig::Arg1				Arg1;
+	typedef typename Sig::Arg2				Arg2;
+	typedef typename Sig::Arg3				Arg3;
+	typedef typename Expr<Ret>::Val			Val;
+	typedef typename Expr<Ret>::IVal		IVal;
+	typedef Func<Sig>						ApplyFunc;
+	typedef typename ApplyFunc::ArgExprs	ArgExprs;
+
+						Apply	(const ApplyFunc&		func,
+								 const ExprP<Arg0>&		arg0 = voidP(),
+								 const ExprP<Arg1>&		arg1 = voidP(),
+								 const ExprP<Arg2>&		arg2 = voidP(),
+								 const ExprP<Arg3>&		arg3 = voidP())
+							: m_func	(func),
+							  m_args	(arg0, arg1, arg2, arg3) {}
+
+						Apply	(const ApplyFunc&	func,
+								 const ArgExprs&	args)
+							: m_func	(func),
+							  m_args	(args) {}
+protected:
+	void				doPrintExpr			(ostream& os) const
+	{
+		BaseArgExprs	args;
+		args.push_back(m_args.a.get());
+		args.push_back(m_args.b.get());
+		args.push_back(m_args.c.get());
+		args.push_back(m_args.d.get());
+		m_func.print(os, args);
+	}
+
+	IVal				doEvaluate		(const EvalContext& ctx) const
+	{
+		return m_func.apply(ctx,
+							m_args.a->evaluate(ctx), m_args.b->evaluate(ctx),
+							m_args.c->evaluate(ctx), m_args.d->evaluate(ctx));
+	}
+
+	void				doGetUsedFuncs	(FuncSet& dst) const
+	{
+		m_func.getUsedFuncs(dst);
+		m_args.a->getUsedFuncs(dst);
+		m_args.b->getUsedFuncs(dst);
+		m_args.c->getUsedFuncs(dst);
+		m_args.d->getUsedFuncs(dst);
+	}
+
+	const ApplyFunc&	m_func;
+	ArgExprs			m_args;
+};
+
+template <typename Sig>
+ExprP<typename Sig::Ret> createApply (const Func<Sig>&						func,
+									  const typename Func<Sig>::ArgExprs&	args)
+{
+	return exprP(new Apply<Sig>(func, args));
+}
+
+template <typename Sig>
+ExprP<typename Sig::Ret> createApply (
+	const Func<Sig>&			func,
+	const ExprP<typename Sig::Arg0>&	arg0 = voidP(),
+	const ExprP<typename Sig::Arg1>&	arg1 = voidP(),
+	const ExprP<typename Sig::Arg2>&	arg2 = voidP(),
+	const ExprP<typename Sig::Arg3>&	arg3 = voidP())
+{
+	return exprP(new Apply<Sig>(func, arg0, arg1, arg2, arg3));
+}
+
+template <typename Sig>
+ExprP<typename Sig::Ret> Func<Sig>::operator() (const ExprP<typename Sig::Arg0>& arg0,
+												const ExprP<typename Sig::Arg1>& arg1,
+												const ExprP<typename Sig::Arg2>& arg2,
+												const ExprP<typename Sig::Arg3>& arg3) const
+{
+	return createApply(*this, arg0, arg1, arg2, arg3);
+}
+
+template <typename F>
+ExprP<typename F::Ret> app (const ExprP<typename F::Arg0>& arg0 = voidP(),
+							const ExprP<typename F::Arg1>& arg1 = voidP(),
+							const ExprP<typename F::Arg2>& arg2 = voidP(),
+							const ExprP<typename F::Arg3>& arg3 = voidP())
+{
+	return createApply(instance<F>(), arg0, arg1, arg2, arg3);
+}
+
+template <typename F>
+typename F::IRet call (const EvalContext&			ctx,
+					   const typename F::IArg0&		arg0 = Void(),
+					   const typename F::IArg1&		arg1 = Void(),
+					   const typename F::IArg2&		arg2 = Void(),
+					   const typename F::IArg3&		arg3 = Void())
+{
+	return instance<F>().apply(ctx, arg0, arg1, arg2, arg3);
+}
+
+template <typename Sig>
+class ApplyVar : public Apply<Sig>
+{
+public:
+	typedef typename Sig::Ret				Ret;
+	typedef typename Sig::Arg0				Arg0;
+	typedef typename Sig::Arg1				Arg1;
+	typedef typename Sig::Arg2				Arg2;
+	typedef typename Sig::Arg3				Arg3;
+	typedef typename Expr<Ret>::Val			Val;
+	typedef typename Expr<Ret>::IVal		IVal;
+	typedef Func<Sig>						ApplyFunc;
+	typedef typename ApplyFunc::ArgExprs	ArgExprs;
+
+						ApplyVar	(const ApplyFunc&			func,
+									 const VariableP<Arg0>&		arg0,
+									 const VariableP<Arg1>&		arg1,
+									 const VariableP<Arg2>&		arg2,
+									 const VariableP<Arg3>&		arg3)
+							: Apply<Sig> (func, arg0, arg1, arg2, arg3) {}
+protected:
+	IVal				doEvaluate		(const EvalContext& ctx) const
+	{
+		const Variable<Arg0>&	var0 = static_cast<const Variable<Arg0>&>(*this->m_args.a);
+		const Variable<Arg1>&	var1 = static_cast<const Variable<Arg1>&>(*this->m_args.b);
+		const Variable<Arg2>&	var2 = static_cast<const Variable<Arg2>&>(*this->m_args.c);
+		const Variable<Arg3>&	var3 = static_cast<const Variable<Arg3>&>(*this->m_args.d);
+		return this->m_func.apply(ctx,
+								  ctx.env.lookup(var0), ctx.env.lookup(var1),
+								  ctx.env.lookup(var2), ctx.env.lookup(var3));
+	}
+};
+
+template <typename Sig>
+ExprP<typename Sig::Ret> applyVar (const Func<Sig>&						func,
+								   const VariableP<typename Sig::Arg0>&	arg0,
+								   const VariableP<typename Sig::Arg1>&	arg1,
+								   const VariableP<typename Sig::Arg2>&	arg2,
+								   const VariableP<typename Sig::Arg3>&	arg3)
+{
+	return exprP(new ApplyVar<Sig>(func, arg0, arg1, arg2, arg3));
+}
+
+template <typename Sig_>
+class DerivedFunc : public Func<Sig_>
+{
+public:
+	typedef typename DerivedFunc::ArgExprs		ArgExprs;
+	typedef typename DerivedFunc::IRet			IRet;
+	typedef typename DerivedFunc::IArgs			IArgs;
+	typedef typename DerivedFunc::Ret			Ret;
+	typedef typename DerivedFunc::Arg0			Arg0;
+	typedef typename DerivedFunc::Arg1			Arg1;
+	typedef typename DerivedFunc::Arg2			Arg2;
+	typedef typename DerivedFunc::Arg3			Arg3;
+	typedef typename DerivedFunc::IArg0			IArg0;
+	typedef typename DerivedFunc::IArg1			IArg1;
+	typedef typename DerivedFunc::IArg2			IArg2;
+	typedef typename DerivedFunc::IArg3			IArg3;
+
+protected:
+	void						doPrintDefinition	(ostream& os) const
+	{
+		const ParamNames&	paramNames	= this->getParamNames();
+
+		initialize();
+
+		os << dataTypeNameOf<Ret>() << " " << this->getName()
+			<< "(";
+		if (isTypeValid<Arg0>())
+			os << dataTypeNameOf<Arg0>() << " " << paramNames.a;
+		if (isTypeValid<Arg1>())
+			os << ", " << dataTypeNameOf<Arg1>() << " " << paramNames.b;
+		if (isTypeValid<Arg2>())
+			os << ", " << dataTypeNameOf<Arg2>() << " " << paramNames.c;
+		if (isTypeValid<Arg3>())
+			os << ", " << dataTypeNameOf<Arg3>() << " " << paramNames.d;
+		os << ")\n{\n";
+
+		for (size_t ndx = 0; ndx < m_body.size(); ++ndx)
+			os << *m_body[ndx];
+		os << "return " << *m_ret << ";\n";
+		os << "}\n";
+	}
+
+	IRet						doApply			(const EvalContext&	ctx,
+												 const IArgs&		args) const
+	{
+		Environment	funEnv;
+		IArgs&		mutArgs		= const_cast<IArgs&>(args);
+		IRet		ret;
+
+		initialize();
+
+		funEnv.bind(*m_var0, args.a);
+		funEnv.bind(*m_var1, args.b);
+		funEnv.bind(*m_var2, args.c);
+		funEnv.bind(*m_var3, args.d);
+
+		{
+			EvalContext	funCtx(ctx.format, ctx.floatPrecision, funEnv, ctx.callDepth);
+
+			for (size_t ndx = 0; ndx < m_body.size(); ++ndx)
+				m_body[ndx]->execute(funCtx);
+
+			ret = m_ret->evaluate(funCtx);
+		}
+
+		// \todo [lauri] Store references instead of values in environment
+		const_cast<IArg0&>(mutArgs.a) = funEnv.lookup(*m_var0);
+		const_cast<IArg1&>(mutArgs.b) = funEnv.lookup(*m_var1);
+		const_cast<IArg2&>(mutArgs.c) = funEnv.lookup(*m_var2);
+		const_cast<IArg3&>(mutArgs.d) = funEnv.lookup(*m_var3);
+
+		return ret;
+	}
+
+	void						doGetUsedFuncs	(FuncSet& dst) const
+	{
+		initialize();
+		if (dst.insert(this).second)
+		{
+			for (size_t ndx = 0; ndx < m_body.size(); ++ndx)
+				m_body[ndx]->getUsedFuncs(dst);
+			m_ret->getUsedFuncs(dst);
+		}
+	}
+
+	virtual ExprP<Ret>			doExpand		(ExpandContext& ctx, const ArgExprs& args_) const = 0;
+
+	// These are transparently initialized when first needed. They cannot be
+	// initialized in the constructor because they depend on the doExpand
+	// method of the subclass.
+
+	mutable VariableP<Arg0>		m_var0;
+	mutable VariableP<Arg1>		m_var1;
+	mutable VariableP<Arg2>		m_var2;
+	mutable VariableP<Arg3>		m_var3;
+	mutable vector<StatementP>	m_body;
+	mutable ExprP<Ret>			m_ret;
+
+private:
+
+	void				initialize		(void)	const
+	{
+		if (!m_ret)
+		{
+			const ParamNames&	paramNames	= this->getParamNames();
+			Counter				symCounter;
+			ExpandContext		ctx			(symCounter);
+			ArgExprs			args;
+
+			args.a	= m_var0 = variable<Arg0>(paramNames.a);
+			args.b	= m_var1 = variable<Arg1>(paramNames.b);
+			args.c	= m_var2 = variable<Arg2>(paramNames.c);
+			args.d	= m_var3 = variable<Arg3>(paramNames.d);
+
+			m_ret	= this->doExpand(ctx, args);
+			m_body	= ctx.getStatements();
+		}
+	}
+};
+
+template <typename Sig>
+class PrimitiveFunc : public Func<Sig>
+{
+public:
+	typedef typename PrimitiveFunc::Ret			Ret;
+	typedef typename PrimitiveFunc::ArgExprs	ArgExprs;
+
+protected:
+	void	doPrintDefinition	(ostream&) const	{}
+	void	doGetUsedFuncs		(FuncSet&) const	{}
+};
+
+template <typename T>
+class Cond : public PrimitiveFunc<Signature<T, bool, T, T> >
+{
+public:
+	typedef typename Cond::IArgs	IArgs;
+	typedef typename Cond::IRet		IRet;
+
+	string	getName	(void) const
+	{
+		return "_cond";
+	}
+
+protected:
+
+	void	doPrint	(ostream& os, const BaseArgExprs& args) const
+	{
+		os << "(" << *args[0] << " ? " << *args[1] << " : " << *args[2] << ")";
+	}
+
+	IRet	doApply	(const EvalContext&, const IArgs& iargs)const
+	{
+		IRet	ret;
+
+		if (iargs.a.contains(true))
+			ret = unionIVal<T>(ret, iargs.b);
+
+		if (iargs.a.contains(false))
+			ret = unionIVal<T>(ret, iargs.c);
+
+		return ret;
+	}
+};
+
+template <typename T>
+class CompareOperator : public PrimitiveFunc<Signature<bool, T, T> >
+{
+public:
+	typedef typename CompareOperator::IArgs	IArgs;
+	typedef typename CompareOperator::IArg0	IArg0;
+	typedef typename CompareOperator::IArg1	IArg1;
+	typedef typename CompareOperator::IRet	IRet;
+
+protected:
+	void			doPrint	(ostream& os, const BaseArgExprs& args) const
+	{
+		os << "(" << *args[0] << getSymbol() << *args[1] << ")";
+	}
+
+	Interval		doApply	(const EvalContext&, const IArgs& iargs) const
+	{
+		const IArg0&	arg0 = iargs.a;
+		const IArg1&	arg1 = iargs.b;
+		IRet	ret;
+
+		if (canSucceed(arg0, arg1))
+			ret |= true;
+		if (canFail(arg0, arg1))
+			ret |= false;
+
+		return ret;
+	}
+
+	virtual string	getSymbol	(void) const = 0;
+	virtual bool	canSucceed	(const IArg0&, const IArg1&) const = 0;
+	virtual bool	canFail		(const IArg0&, const IArg1&) const = 0;
+};
+
+template <typename T>
+class LessThan : public CompareOperator<T>
+{
+public:
+	string	getName		(void) const									{ return "lessThan"; }
+
+protected:
+	string	getSymbol	(void) const									{ return "<";		}
+
+	bool	canSucceed	(const Interval& a, const Interval& b) const
+	{
+		return (a.lo() < b.hi());
+	}
+
+	bool	canFail		(const Interval& a, const Interval& b) const
+	{
+		return !(a.hi() < b.lo());
+	}
+};
+
+template <typename T>
+ExprP<bool> operator< (const ExprP<T>& a, const ExprP<T>& b)
+{
+	return app<LessThan<T> >(a, b);
+}
+
+template <typename T>
+ExprP<T> cond (const ExprP<bool>&	test,
+			   const ExprP<T>&		consequent,
+			   const ExprP<T>&		alternative)
+{
+	return app<Cond<T> >(test, consequent, alternative);
+}
+
+/*--------------------------------------------------------------------*//*!
+ *
+ * @}
+ *
+ *//*--------------------------------------------------------------------*/
+
+class FloatFunc1 : public PrimitiveFunc<Signature<float, float> >
+{
+protected:
+	Interval			doApply			(const EvalContext& ctx, const IArgs& iargs) const
+	{
+		return this->applyMonotone(ctx, iargs.a);
+	}
+
+	Interval			applyMonotone	(const EvalContext& ctx, const Interval& iarg0) const
+	{
+		Interval ret;
+
+		TCU_INTERVAL_APPLY_MONOTONE1(ret, arg0, iarg0, val,
+									 TCU_SET_INTERVAL(val, point,
+													  point = this->applyPoint(ctx, arg0)));
+
+		ret |= innerExtrema(ctx, iarg0);
+		ret &= (this->getCodomain() | TCU_NAN);
+
+		return ctx.format.convert(ret);
+	}
+
+	virtual Interval	innerExtrema	(const EvalContext&, const Interval&) const
+	{
+		return Interval(); // empty interval, i.e. no extrema
+	}
+
+	virtual Interval	applyPoint		(const EvalContext& ctx, double arg0) const
+	{
+		const double	exact	= this->applyExact(arg0);
+		const double	prec	= this->precision(ctx, exact, arg0);
+
+		return exact + Interval(-prec, prec);
+	}
+
+	virtual double		applyExact		(double) const
+	{
+		TCU_THROW(InternalError, "Cannot apply");
+	}
+
+	virtual Interval	getCodomain		(void) const
+	{
+		return Interval::unbounded(true);
+	}
+
+	virtual double		precision		(const EvalContext& ctx, double, double) const = 0;
+};
+
+class CFloatFunc1 : public FloatFunc1
+{
+public:
+			CFloatFunc1	(const string& name, DoubleFunc1& func)
+				: m_name(name), m_func(func) {}
+
+	string			getName		(void) const		{ return m_name; }
+
+protected:
+	double			applyExact	(double x) const	{ return m_func(x); }
+
+	const string	m_name;
+	DoubleFunc1&	m_func;
+};
+
+class FloatFunc2 : public PrimitiveFunc<Signature<float, float, float> >
+{
+protected:
+	Interval			doApply			(const EvalContext&	ctx, const IArgs& iargs) const
+	{
+		return this->applyMonotone(ctx, iargs.a, iargs.b);
+	}
+
+	Interval			applyMonotone	(const EvalContext&	ctx,
+										 const Interval&	xi,
+										 const Interval&	yi) const
+	{
+		Interval reti;
+
+		TCU_INTERVAL_APPLY_MONOTONE2(reti, x, xi, y, yi, ret,
+									 TCU_SET_INTERVAL(ret, point,
+													  point = this->applyPoint(ctx, x, y)));
+		reti |= innerExtrema(ctx, xi, yi);
+		reti &= (this->getCodomain() | TCU_NAN);
+
+		return ctx.format.convert(reti);
+	}
+
+	virtual Interval	innerExtrema	(const EvalContext&,
+										 const Interval&,
+										 const Interval&) const
+	{
+		return Interval(); // empty interval, i.e. no extrema
+	}
+
+	virtual Interval	applyPoint		(const EvalContext&	ctx,
+										 double 			x,
+										 double 			y) const
+	{
+		const double exact	= this->applyExact(x, y);
+		const double prec	= this->precision(ctx, exact, x, y);
+
+		return exact + Interval(-prec, prec);
+	}
+
+	virtual double		applyExact		(double, double) const
+	{
+		TCU_THROW(InternalError, "Cannot apply");
+	}
+
+	virtual Interval	getCodomain		(void) const
+	{
+		return Interval::unbounded(true);
+	}
+
+	virtual double		precision		(const EvalContext&	ctx,
+										 double				ret,
+										 double				x,
+										 double				y) const = 0;
+};
+
+class CFloatFunc2 : public FloatFunc2
+{
+public:
+					CFloatFunc2	(const string&	name,
+								 DoubleFunc2&	func)
+						: m_name(name)
+						, m_func(func)
+	{
+	}
+
+	string			getName		(void) const						{ return m_name; }
+
+protected:
+	double			applyExact	(double x, double y) const			{ return m_func(x, y); }
+
+	const string	m_name;
+	DoubleFunc2&	m_func;
+};
+
+class InfixOperator : public FloatFunc2
+{
+protected:
+	virtual string	getSymbol		(void) const = 0;
+
+	void			doPrint			(ostream& os, const BaseArgExprs& args) const
+	{
+		os << "(" << *args[0] << " " << getSymbol() << " " << *args[1] << ")";
+	}
+
+	Interval		applyPoint		(const EvalContext&	ctx,
+									 double 			x,
+									 double 			y) const
+	{
+		const double exact	= this->applyExact(x, y);
+
+		// Allow either representable number on both sides of the exact value,
+		// but require exactly representable values to be preserved.
+		return ctx.format.roundOut(exact);
+	}
+
+	double			precision		(const EvalContext&, double, double, double) const
+	{
+		return 0.0;
+	}
+};
+
+class FloatFunc3 : public PrimitiveFunc<Signature<float, float, float, float> >
+{
+protected:
+	Interval			doApply			(const EvalContext&	ctx, const IArgs& iargs) const
+	{
+		return this->applyMonotone(ctx, iargs.a, iargs.b, iargs.c);
+	}
+
+	Interval			applyMonotone	(const EvalContext&	ctx,
+										 const Interval&	xi,
+										 const Interval&	yi,
+										 const Interval&	zi) const
+	{
+		Interval reti;
+		TCU_INTERVAL_APPLY_MONOTONE3(reti, x, xi, y, yi, z, zi, ret,
+									 TCU_SET_INTERVAL(ret, point,
+													  point = this->applyPoint(ctx, x, y, z)));
+		return ctx.format.convert(reti);
+	}
+
+	virtual Interval	applyPoint		(const EvalContext&	ctx,
+										 double 			x,
+										 double 			y,
+										 double 			z) const
+	{
+		const double exact	= this->applyExact(x, y, z);
+		const double prec	= this->precision(ctx, exact, x, y, z);
+		return exact + Interval(-prec, prec);
+	}
+
+	virtual double		applyExact		(double, double, double) const
+	{
+		TCU_THROW(InternalError, "Cannot apply");
+	}
+
+	virtual double		precision		(const EvalContext&	ctx,
+										 double				result,
+										 double				x,
+										 double				y,
+										 double				z) const = 0;
+};
+
+// We define syntactic sugar functions for expression constructors. Since
+// these have the same names as ordinary mathematical operations (sin, log
+// etc.), it's better to give them a dedicated namespace.
+namespace Functions
+{
+
+using namespace tcu;
+
+class Add : public InfixOperator
+{
+public:
+	string		getName		(void) const 						{ return "add"; }
+	string		getSymbol	(void) const 						{ return "+"; }
+
+	Interval	doApply		(const EvalContext&	ctx,
+							 const IArgs&		iargs) const
+	{
+		// Fast-path for common case
+		if (iargs.a.isOrdinary() && iargs.b.isOrdinary())
+		{
+			Interval ret;
+			TCU_SET_INTERVAL_BOUNDS(ret, sum,
+									sum = iargs.a.lo() + iargs.b.lo(),
+									sum = iargs.a.hi() + iargs.b.hi());
+			return ctx.format.convert(ctx.format.roundOut(ret));
+		}
+		return this->applyMonotone(ctx, iargs.a, iargs.b);
+	}
+
+protected:
+	double		applyExact	(double x, double y) const 			{ return x + y; }
+};
+
+class Mul : public InfixOperator
+{
+public:
+	string		getName		(void) const 									{ return "mul"; }
+	string		getSymbol	(void) const 									{ return "*"; }
+
+	Interval	doApply		(const EvalContext&	ctx, const IArgs& iargs) const
+	{
+		Interval a = iargs.a;
+		Interval b = iargs.b;
+
+		// Fast-path for common case
+		if (a.isOrdinary() && b.isOrdinary())
+		{
+			Interval ret;
+			if (a.hi() < 0)
+			{
+				a = -a;
+				b = -b;
+			}
+			if (a.lo() >= 0 && b.lo() >= 0)
+			{
+				TCU_SET_INTERVAL_BOUNDS(ret, prod,
+										prod = iargs.a.lo() * iargs.b.lo(),
+										prod = iargs.a.hi() * iargs.b.hi());
+				return ctx.format.convert(ctx.format.roundOut(ret));
+			}
+			if (a.lo() >= 0 && b.hi() <= 0)
+			{
+				TCU_SET_INTERVAL_BOUNDS(ret, prod,
+										prod = iargs.a.hi() * iargs.b.lo(),
+										prod = iargs.a.lo() * iargs.b.hi());
+				return ctx.format.convert(ctx.format.roundOut(ret));
+			}
+		}
+		return this->applyMonotone(ctx, iargs.a, iargs.b);
+	}
+
+protected:
+	double		applyExact	(double x, double y) const						{ return x * y; }
+
+	Interval	innerExtrema(const EvalContext&, const Interval& xi, const Interval& yi) const
+	{
+		if (((xi.contains(-TCU_INFINITY) || xi.contains(TCU_INFINITY)) && yi.contains(0.0)) ||
+			((yi.contains(-TCU_INFINITY) || yi.contains(TCU_INFINITY)) && xi.contains(0.0)))
+			return Interval(TCU_NAN);
+
+		return Interval();
+	}
+};
+
+class Sub : public InfixOperator
+{
+public:
+	string		getName		(void) const 				{ return "sub"; }
+	string		getSymbol	(void) const 				{ return "-"; }
+
+	Interval	doApply		(const EvalContext&	ctx, const IArgs& iargs) const
+	{
+		// Fast-path for common case
+		if (iargs.a.isOrdinary() && iargs.b.isOrdinary())
+		{
+			Interval ret;
+
+			TCU_SET_INTERVAL_BOUNDS(ret, diff,
+									diff = iargs.a.lo() - iargs.b.hi(),
+									diff = iargs.a.hi() - iargs.b.lo());
+			return ctx.format.convert(ctx.format.roundOut(ret));
+
+		}
+		else
+		{
+			return this->applyMonotone(ctx, iargs.a, iargs.b);
+		}
+	}
+
+protected:
+	double		applyExact	(double x, double y) const	{ return x - y; }
+};
+
+class Negate : public FloatFunc1
+{
+public:
+	string	getName		(void) const									{ return "_negate"; }
+	void	doPrint		(ostream& os, const BaseArgExprs& args) const	{ os << "-" << *args[0]; }
+
+protected:
+	double	precision	(const EvalContext&, double, double) const		{ return 0.0; }
+	double	applyExact	(double x) const								{ return -x; }
+};
+
+class Div : public InfixOperator
+{
+public:
+	string		getName			(void) const 						{ return "div"; }
+
+protected:
+	string		getSymbol		(void) const 						{ return "/"; }
+
+	Interval	innerExtrema	(const EvalContext&,
+								 const Interval&		nom,
+								 const Interval&		den) const
+	{
+		Interval ret;
+
+		if (den.contains(0.0))
+		{
+			if (nom.contains(0.0))
+				ret |= TCU_NAN;
+
+			if (nom.lo() < 0.0 || nom.hi() > 0.0)
+				ret |= Interval::unbounded();
+		}
+
+		return ret;
+	}
+
+protected:
+
+	double		applyExact	(double x, double y) const { return x / y; }
+
+	Interval	applyPoint	(const EvalContext&	ctx, double x, double y) const
+	{
+		return FloatFunc2::applyPoint(ctx, x, y);
+	}
+
+	double		precision	(const EvalContext& ctx, double ret, double, double den) const
+	{
+		const FloatFormat&	fmt		= ctx.format;
+
+		// \todo [2014-03-05 lauri] Check that the limits in GLSL 3.10 are actually correct.
+		// For now, we assume that division's precision is 2.5 ULP when the value is within
+		// [2^MINEXP, 2^MAXEXP-1]
+
+		if (den == 0.0)
+			return 0.0; // Result must be exactly inf
+		else if (de::inBounds(deAbs(den),
+							  deLdExp(1.0, fmt.getMinExp()),
+							  deLdExp(1.0, fmt.getMaxExp() - 1)))
+			return fmt.ulp(ret, 2.5);
+		else
+			return TCU_INFINITY; // Can be any number, but must be a number.
+	}
+};
+
+class InverseSqrt : public FloatFunc1
+{
+public:
+	string		getName		(void) const							{ return "inversesqrt"; }
+
+protected:
+	double		applyExact	(double x) const						{ return 1.0 / deSqrt(x); }
+
+	double		precision	(const EvalContext& ctx, double ret, double x) const
+	{
+		return x <= 0 ? TCU_NAN : ctx.format.ulp(ret, 2.0);
+	}
+
+	Interval	getCodomain	(void) const
+	{
+		return Interval(0.0, TCU_INFINITY);
+	}
+};
+
+class ExpFunc : public CFloatFunc1
+{
+public:
+				ExpFunc		(const string& name, DoubleFunc1& func)
+					: CFloatFunc1(name, func) {}
+protected:
+	double		precision	(const EvalContext& ctx, double ret, double x) const
+	{
+		switch (ctx.floatPrecision)
+		{
+			case glu::PRECISION_HIGHP:
+				return ctx.format.ulp(ret, 3.0 + 2.0 * deAbs(x));
+			case glu::PRECISION_MEDIUMP:
+				return ctx.format.ulp(ret, 2.0 + 2.0 * deAbs(x));
+			case glu::PRECISION_LOWP:
+				return ctx.format.ulp(ret, 2.0);
+			default:
+				DE_ASSERT(!"Impossible");
+		}
+		return 0;
+	}
+
+	Interval	getCodomain	(void) const
+	{
+		return Interval(0.0, TCU_INFINITY);
+	}
+};
+
+class Exp2	: public ExpFunc	{ public: Exp2 (void)	: ExpFunc("exp2", deExp2) {} };
+class Exp	: public ExpFunc	{ public: Exp (void)	: ExpFunc("exp", deExp) {} };
+
+ExprP<float> exp2	(const ExprP<float>& x)	{ return app<Exp2>(x); }
+ExprP<float> exp	(const ExprP<float>& x)	{ return app<Exp>(x); }
+
+class LogFunc : public CFloatFunc1
+{
+public:
+				LogFunc		(const string& name, DoubleFunc1& func)
+					: CFloatFunc1(name, func) {}
+
+protected:
+	double		precision	(const EvalContext& ctx, double ret, double x) const
+	{
+		if (x <= 0)
+			return TCU_NAN;
+
+		switch (ctx.floatPrecision)
+		{
+			case glu::PRECISION_HIGHP:
+				return (0.5 <= x && x <= 2.0) ? deLdExp(1.0, -21) : ctx.format.ulp(ret, 3.0);
+			case glu::PRECISION_MEDIUMP:
+				return (0.5 <= x && x <= 2.0) ? deLdExp(1.0, -7) : ctx.format.ulp(ret, 2.0);
+			case glu::PRECISION_LOWP:
+				return ctx.format.ulp(ret, 2.0);
+			default:
+				DE_ASSERT(!"Impossible");
+		}
+
+		return 0;
+	}
+};
+
+class Log2	: public LogFunc		{ public: Log2	(void) : LogFunc("log2", deLog2) {} };
+class Log	: public LogFunc		{ public: Log	(void) : LogFunc("log", deLog) {} };
+
+ExprP<float> log2	(const ExprP<float>& x)	{ return app<Log2>(x); }
+ExprP<float> log	(const ExprP<float>& x)	{ return app<Log>(x); }
+
+#define DEFINE_CONSTRUCTOR1(CLASS, TRET, NAME, T0) \
+ExprP<TRET> NAME (const ExprP<T0>& arg0) { return app<CLASS>(arg0); }
+
+#define DEFINE_DERIVED1(CLASS, TRET, NAME, T0, ARG0, EXPANSION)			\
+class CLASS : public DerivedFunc<Signature<TRET, T0> >		 			\
+{																		\
+public:																	\
+	string			getName		(void) const		{ return #NAME; }	\
+																		\
+protected:																\
+	ExprP<TRET>		doExpand		(ExpandContext&,					\
+									 const CLASS::ArgExprs& args_) const \
+	{																	\
+		const ExprP<float>& ARG0 = args_.a;								\
+		return EXPANSION;												\
+	}																	\
+};																		\
+DEFINE_CONSTRUCTOR1(CLASS, TRET, NAME, T0)
+
+#define DEFINE_DERIVED_FLOAT1(CLASS, NAME, ARG0, EXPANSION) \
+	DEFINE_DERIVED1(CLASS, float, NAME, float, ARG0, EXPANSION)
+
+#define DEFINE_CONSTRUCTOR2(CLASS, TRET, NAME, T0, T1)				\
+ExprP<TRET> NAME (const ExprP<T0>& arg0, const ExprP<T1>& arg1)		\
+{																	\
+	return app<CLASS>(arg0, arg1);									\
+}
+
+#define DEFINE_DERIVED2(CLASS, TRET, NAME, T0, Arg0, T1, Arg1, EXPANSION) \
+class CLASS : public DerivedFunc<Signature<TRET, T0, T1> >		 		\
+{																		\
+public:																	\
+	string			getName		(void) const		{ return #NAME; }	\
+																		\
+protected:																\
+	ExprP<TRET>		doExpand	(ExpandContext&, const ArgExprs& args_) const \
+	{																	\
+		const ExprP<T0>& Arg0 = args_.a;								\
+		const ExprP<T1>& Arg1 = args_.b;								\
+		return EXPANSION;												\
+	}																	\
+};																		\
+DEFINE_CONSTRUCTOR2(CLASS, TRET, NAME, T0, T1)
+
+#define DEFINE_DERIVED_FLOAT2(CLASS, NAME, Arg0, Arg1, EXPANSION)		\
+	DEFINE_DERIVED2(CLASS, float, NAME, float, Arg0, float, Arg1, EXPANSION)
+
+#define DEFINE_CONSTRUCTOR3(CLASS, TRET, NAME, T0, T1, T2)				\
+ExprP<TRET> NAME (const ExprP<T0>& arg0, const ExprP<T1>& arg1, const ExprP<T2>& arg2) \
+{																		\
+	return app<CLASS>(arg0, arg1, arg2);								\
+}
+
+#define DEFINE_DERIVED3(CLASS, TRET, NAME, T0, ARG0, T1, ARG1, T2, ARG2, EXPANSION) \
+class CLASS : public DerivedFunc<Signature<TRET, T0, T1, T2> >					\
+{																				\
+public:																			\
+	string			getName		(void) const	{ return #NAME; }				\
+																				\
+protected:																		\
+	ExprP<TRET>		doExpand	(ExpandContext&, const ArgExprs& args_) const	\
+	{																			\
+		const ExprP<T0>& ARG0 = args_.a;										\
+		const ExprP<T1>& ARG1 = args_.b;										\
+		const ExprP<T2>& ARG2 = args_.c;										\
+		return EXPANSION;														\
+	}																			\
+};																				\
+DEFINE_CONSTRUCTOR3(CLASS, TRET, NAME, T0, T1, T2)
+
+#define DEFINE_DERIVED_FLOAT3(CLASS, NAME, ARG0, ARG1, ARG2, EXPANSION)			\
+	DEFINE_DERIVED3(CLASS, float, NAME, float, ARG0, float, ARG1, float, ARG2, EXPANSION)
+
+#define DEFINE_CONSTRUCTOR4(CLASS, TRET, NAME, T0, T1, T2, T3)			\
+ExprP<TRET> NAME (const ExprP<T0>& arg0, const ExprP<T1>& arg1,			\
+				  const ExprP<T2>& arg2, const ExprP<T3>& arg3)			\
+{																		\
+	return app<CLASS>(arg0, arg1, arg2, arg3);							\
+}
+
+DEFINE_DERIVED_FLOAT1(Sqrt,		sqrt,		x, 		constant(1.0f) / app<InverseSqrt>(x));
+DEFINE_DERIVED_FLOAT2(Pow,		pow,		x,	y,	exp2(y * log2(x)));
+DEFINE_DERIVED_FLOAT1(Radians,	radians,	d, 		(constant(DE_PI) / constant(180.0f)) * d);
+DEFINE_DERIVED_FLOAT1(Degrees,	degrees,	r,	 	(constant(180.0f) / constant(DE_PI)) * r);
+
+class TrigFunc : public CFloatFunc1
+{
+public:
+					TrigFunc		(const string&		name,
+									 DoubleFunc1&		func,
+									 const Interval&	loEx,
+									 const Interval&	hiEx)
+						: CFloatFunc1	(name, func)
+						, m_loExtremum	(loEx)
+						, m_hiExtremum	(hiEx) {}
+
+protected:
+	Interval		innerExtrema	(const EvalContext&, const Interval& angle) const
+	{
+		const double		lo		= angle.lo();
+		const double		hi		= angle.hi();
+		const int			loSlope	= doGetSlope(lo);
+		const int			hiSlope	= doGetSlope(hi);
+
+		// Detect the high and low values the function can take between the
+		// interval endpoints.
+		if (angle.length() >= 2.0 * DE_PI_DOUBLE)
+		{
+			// The interval is longer than a full cycle, so it must get all possible values.
+			return m_hiExtremum | m_loExtremum;
+		}
+		else if (loSlope == 1 && hiSlope == -1)
+		{
+			// The slope can change from positive to negative only at the maximum value.
+			return m_hiExtremum;
+		}
+		else if (loSlope == -1 && hiSlope == 1)
+		{
+			// The slope can change from negative to positive only at the maximum value.
+			return m_loExtremum;
+		}
+		else if (loSlope == hiSlope &&
+				 deIntSign(applyExact(hi) - applyExact(lo)) * loSlope == -1)
+		{
+			// The slope has changed twice between the endpoints, so both extrema are included.
+			return m_hiExtremum | m_loExtremum;
+		}
+
+		return Interval();
+	}
+
+	Interval	getCodomain			(void) const
+	{
+		// Ensure that result is always within [-1, 1], or NaN (for +-inf)
+		return Interval(-1.0, 1.0) | TCU_NAN;
+	}
+
+	double		precision			(const EvalContext& ctx, double ret, double arg) const
+	{
+		if (ctx.floatPrecision == glu::PRECISION_HIGHP)
+		{
+			// Use precision from OpenCL fast relaxed math
+			if (-DE_PI_DOUBLE <= arg && arg <= DE_PI_DOUBLE)
+			{
+				return deLdExp(1.0, -11);
+			}
+			else
+			{
+				// "larger otherwise", let's pick |x| * 2^-12 , which is slightly over
+				// 2^-11 at x == pi.
+				return deLdExp(deAbs(arg), -12);
+			}
+		}
+		else
+		{
+			// from OpenCL half-float extension specification
+			return ctx.format.ulp(ret, 2.0);
+		}
+	}
+
+	virtual int		doGetSlope		(double angle) const = 0;
+
+	Interval		m_loExtremum;
+	Interval		m_hiExtremum;
+};
+
+class Sin : public TrigFunc
+{
+public:
+				Sin			(void) : TrigFunc("sin", deSin, -1.0, 1.0) {}
+
+protected:
+	int			doGetSlope	(double angle) const { return deIntSign(deCos(angle)); }
+};
+
+ExprP<float> sin (const ExprP<float>& x) { return app<Sin>(x); }
+
+class Cos : public TrigFunc
+{
+public:
+				Cos			(void) : TrigFunc("cos", deCos, -1.0, 1.0) {}
+
+protected:
+	int			doGetSlope	(double angle) const { return -deIntSign(deSin(angle)); }
+};
+
+ExprP<float> cos (const ExprP<float>& x) { return app<Cos>(x); }
+
+DEFINE_DERIVED_FLOAT1(Tan, tan, x, sin(x) * (constant(1.0f) / cos(x)));
+
+class ArcTrigFunc : public CFloatFunc1
+{
+public:
+					ArcTrigFunc	(const string&		name,
+								 DoubleFunc1&		func,
+								 double				precisionULPs,
+								 const Interval&	domain,
+								 const Interval&	codomain)
+						: CFloatFunc1		(name, func)
+						, m_precision		(precisionULPs)
+						, m_domain			(domain)
+						, m_codomain		(codomain) {}
+
+protected:
+	double			precision	(const EvalContext& ctx, double ret, double x) const
+	{
+		if (!m_domain.contains(x))
+			return TCU_NAN;
+
+		if (ctx.floatPrecision == glu::PRECISION_HIGHP)
+		{
+			// Use OpenCL's precision
+			return ctx.format.ulp(ret, m_precision);
+		}
+		else
+		{
+			// Use OpenCL half-float spec
+			return ctx.format.ulp(ret, 2.0);
+		}
+	}
+
+	// We could implement getCodomain with m_codomain, but choose not to,
+	// because it seems too strict with trascendental constants like pi.
+
+	const double	m_precision;
+	const Interval	m_domain;
+	const Interval	m_codomain;
+};
+
+class ASin : public ArcTrigFunc
+{
+public:
+	ASin (void) : ArcTrigFunc("asin", deAsin, 4.0,
+							  Interval(-1.0, 1.0),
+							  Interval(-DE_PI_DOUBLE * 0.5, DE_PI_DOUBLE * 0.5)) {}
+};
+
+class ACos : public ArcTrigFunc
+{
+public:
+	ACos (void) : ArcTrigFunc("acos", deAcos, 4.0,
+							  Interval(-1.0, 1.0),
+							  Interval(0.0, DE_PI_DOUBLE)) {}
+};
+
+class ATan : public ArcTrigFunc
+{
+public:
+	ATan (void) : ArcTrigFunc("atan", deAtanOver, 5.0,
+							  Interval::unbounded(),
+							  Interval(-DE_PI_DOUBLE * 0.5, DE_PI_DOUBLE * 0.5)) {}
+};
+
+class ATan2 : public CFloatFunc2
+{
+public:
+				ATan2			(void) : CFloatFunc2 ("atan", deAtan2) {}
+
+protected:
+	Interval	innerExtrema	(const EvalContext&,
+								 const Interval&		yi,
+								 const Interval& 		xi) const
+	{
+		Interval ret;
+
+		if (yi.contains(0.0))
+		{
+			if (xi.contains(0.0))
+				ret |= TCU_NAN;
+			if (xi.intersects(Interval(-TCU_INFINITY, 0.0)))
+				ret |= Interval(-DE_PI_DOUBLE, DE_PI_DOUBLE);
+		}
+
+		return ret;
+	}
+
+	double		precision		(const EvalContext& ctx, double ret, double, double) const
+	{
+		if (ctx.floatPrecision == glu::PRECISION_HIGHP)
+			return ctx.format.ulp(ret, 6.0);
+		else
+			return ctx.format.ulp(ret, 2.0);
+	}
+
+	// Codomain could be [-pi, pi], but that would probably be too strict.
+};
+
+DEFINE_DERIVED_FLOAT1(Sinh, sinh, x, (exp(x) - exp(-x)) / constant(2.0f));
+DEFINE_DERIVED_FLOAT1(Cosh, cosh, x, (exp(x) + exp(-x)) / constant(2.0f));
+DEFINE_DERIVED_FLOAT1(Tanh, tanh, x, sinh(x) / cosh(x));
+
+// These are not defined as derived forms in the GLSL ES spec, but
+// that gives us a reasonable precision.
+DEFINE_DERIVED_FLOAT1(ASinh, asinh, x, log(x + sqrt(x * x + constant(1.0f))));
+DEFINE_DERIVED_FLOAT1(ACosh, acosh, x, log(x + sqrt((x + constant(1.0f)) *
+													(x - constant(1.0f)))));
+DEFINE_DERIVED_FLOAT1(ATanh, atanh, x, constant(0.5f) * log((constant(1.0f) + x) /
+															(constant(1.0f) - x)));
+
+template <typename T>
+class GetComponent : public PrimitiveFunc<Signature<typename T::Element, T, int> >
+{
+public:
+	typedef		typename GetComponent::IRet	IRet;
+
+	string		getName		(void) const { return "_getComponent"; }
+
+	void		print		(ostream&				os,
+							 const BaseArgExprs&	args) const
+	{
+		os << *args[0] << "[" << *args[1] << "]";
+	}
+
+protected:
+	IRet		doApply 	(const EvalContext&,
+							 const typename GetComponent::IArgs& iargs) const
+	{
+		IRet ret;
+
+		for (int compNdx = 0; compNdx < T::SIZE; ++compNdx)
+		{
+			if (iargs.b.contains(compNdx))
+				ret = unionIVal<typename T::Element>(ret, iargs.a[compNdx]);
+		}
+
+		return ret;
+	}
+
+};
+
+template <typename T>
+ExprP<typename T::Element> getComponent (const ExprP<T>& container, int ndx)
+{
+	DE_ASSERT(0 <= ndx && ndx < T::SIZE);
+	return app<GetComponent<T> >(container, constant(ndx));
+}
+
+template <typename T>	string	vecNamePrefix			(void);
+template <>				string	vecNamePrefix<float>	(void) { return ""; }
+template <>				string	vecNamePrefix<int>		(void) { return "i"; }
+template <>				string	vecNamePrefix<bool>		(void) { return "b"; }
+
+template <typename T, int Size>
+string vecName (void) { return vecNamePrefix<T>() + "vec" + de::toString(Size); }
+
+template <typename T, int Size> class GenVec;
+
+template <typename T>
+class GenVec<T, 1> : public DerivedFunc<Signature<T, T> >
+{
+public:
+	typedef typename GenVec<T, 1>::ArgExprs ArgExprs;
+
+	string		getName		(void) const
+	{
+		return "_" + vecName<T, 1>();
+	}
+
+protected:
+
+	ExprP<T>	doExpand	(ExpandContext&, const ArgExprs& args) const { return args.a; }
+};
+
+template <typename T>
+class GenVec<T, 2> : public PrimitiveFunc<Signature<Vector<T, 2>, T, T> >
+{
+public:
+	typedef typename GenVec::IRet	IRet;
+	typedef typename GenVec::IArgs	IArgs;
+
+	string		getName		(void) const
+	{
+		return vecName<T, 2>();
+	}
+
+protected:
+	IRet		doApply		(const EvalContext&, const IArgs& iargs) const
+	{
+		return IRet(iargs.a, iargs.b);
+	}
+};
+
+template <typename T>
+class GenVec<T, 3> : public PrimitiveFunc<Signature<Vector<T, 3>, T, T, T> >
+{
+public:
+	typedef typename GenVec::IRet	IRet;
+	typedef typename GenVec::IArgs	IArgs;
+
+	string	getName		(void) const
+	{
+		return vecName<T, 3>();
+	}
+
+protected:
+	IRet	doApply		(const EvalContext&, const IArgs& iargs) const
+	{
+		return IRet(iargs.a, iargs.b, iargs.c);
+	}
+};
+
+template <typename T>
+class GenVec<T, 4> : public PrimitiveFunc<Signature<Vector<T, 4>, T, T, T, T> >
+{
+public:
+	typedef typename GenVec::IRet	IRet;
+	typedef typename GenVec::IArgs	IArgs;
+
+	string		getName		(void) const { return vecName<T, 4>(); }
+
+protected:
+	IRet		doApply		(const EvalContext&, const IArgs& iargs) const
+	{
+		return IRet(iargs.a, iargs.b, iargs.c, iargs.d);
+	}
+};
+
+
+
+template <typename T, int Rows, int Columns>
+class GenMat;
+
+template <typename T, int Rows>
+class GenMat<T, Rows, 2> : public PrimitiveFunc<
+	Signature<Matrix<T, Rows, 2>, Vector<T, Rows>, Vector<T, Rows> > >
+{
+public:
+	typedef typename GenMat::Ret	Ret;
+	typedef typename GenMat::IRet	IRet;
+	typedef typename GenMat::IArgs	IArgs;
+
+	string		getName		(void) const
+	{
+		return dataTypeNameOf<Ret>();
+	}
+
+protected:
+
+	IRet		doApply		(const EvalContext&, const IArgs& iargs) const
+	{
+		IRet	ret;
+		ret[0] = iargs.a;
+		ret[1] = iargs.b;
+		return ret;
+	}
+};
+
+template <typename T, int Rows>
+class GenMat<T, Rows, 3> : public PrimitiveFunc<
+	Signature<Matrix<T, Rows, 3>, Vector<T, Rows>, Vector<T, Rows>, Vector<T, Rows> > >
+{
+public:
+	typedef typename GenMat::Ret	Ret;
+	typedef typename GenMat::IRet	IRet;
+	typedef typename GenMat::IArgs	IArgs;
+
+	string	getName	(void) const
+	{
+		return dataTypeNameOf<Ret>();
+	}
+
+protected:
+
+	IRet	doApply	(const EvalContext&, const IArgs& iargs) const
+	{
+		IRet	ret;
+		ret[0] = iargs.a;
+		ret[1] = iargs.b;
+		ret[2] = iargs.c;
+		return ret;
+	}
+};
+
+template <typename T, int Rows>
+class GenMat<T, Rows, 4> : public PrimitiveFunc<
+	Signature<Matrix<T, Rows, 4>,
+			  Vector<T, Rows>, Vector<T, Rows>, Vector<T, Rows>, Vector<T, Rows> > >
+{
+public:
+	typedef typename GenMat::Ret	Ret;
+	typedef typename GenMat::IRet	IRet;
+	typedef typename GenMat::IArgs	IArgs;
+
+	string	getName	(void) const
+	{
+		return dataTypeNameOf<Ret>();
+	}
+
+protected:
+	IRet	doApply	(const EvalContext&, const IArgs& iargs) const
+	{
+		IRet	ret;
+		ret[0] = iargs.a;
+		ret[1] = iargs.b;
+		ret[2] = iargs.c;
+		ret[3] = iargs.d;
+		return ret;
+	}
+};
+
+template <typename T, int Rows>
+ExprP<Matrix<T, Rows, 2> > mat2 (const ExprP<Vector<T, Rows> >& arg0,
+								 const ExprP<Vector<T, Rows> >& arg1)
+{
+	return app<GenMat<T, Rows, 2> >(arg0, arg1);
+}
+
+template <typename T, int Rows>
+ExprP<Matrix<T, Rows, 3> > mat3 (const ExprP<Vector<T, Rows> >& arg0,
+								 const ExprP<Vector<T, Rows> >& arg1,
+								 const ExprP<Vector<T, Rows> >& arg2)
+{
+	return app<GenMat<T, Rows, 3> >(arg0, arg1, arg2);
+}
+
+template <typename T, int Rows>
+ExprP<Matrix<T, Rows, 4> > mat4 (const ExprP<Vector<T, Rows> >& arg0,
+								 const ExprP<Vector<T, Rows> >& arg1,
+								 const ExprP<Vector<T, Rows> >& arg2,
+								 const ExprP<Vector<T, Rows> >& arg3)
+{
+	return app<GenMat<T, Rows, 4> >(arg0, arg1, arg2, arg3);
+}
+
+
+template <int Rows, int Cols>
+class MatNeg : public PrimitiveFunc<Signature<Matrix<float, Rows, Cols>,
+											  Matrix<float, Rows, Cols> > >
+{
+public:
+	typedef typename MatNeg::IRet		IRet;
+	typedef typename MatNeg::IArgs		IArgs;
+
+	string	getName	(void) const
+	{
+		return "_matNeg";
+	}
+
+protected:
+	void	doPrint	(ostream& os, const BaseArgExprs& args) const
+	{
+		os << "-(" << *args[0] << ")";
+	}
+
+	IRet	doApply	(const EvalContext&, const IArgs& iargs)			const
+	{
+		IRet	ret;
+
+		for (int col = 0; col < Cols; ++col)
+		{
+			for (int row = 0; row < Rows; ++row)
+				ret[col][row] = -iargs.a[col][row];
+		}
+
+		return ret;
+	}
+};
+
+template <typename T, typename Sig>
+class CompWiseFunc : public PrimitiveFunc<Sig>
+{
+public:
+	typedef Func<Signature<T, T, T> >	ScalarFunc;
+
+	string				getName			(void)									const
+	{
+		return doGetScalarFunc().getName();
+	}
+protected:
+	void				doPrint			(ostream&				os,
+										 const BaseArgExprs&	args)			const
+	{
+		doGetScalarFunc().print(os, args);
+	}
+
+	virtual
+	const ScalarFunc&	doGetScalarFunc	(void)									const = 0;
+};
+
+template <int Rows, int Cols>
+class CompMatFuncBase : public CompWiseFunc<float, Signature<Matrix<float, Rows, Cols>,
+															 Matrix<float, Rows, Cols>,
+															 Matrix<float, Rows, Cols> > >
+{
+public:
+	typedef typename CompMatFuncBase::IRet		IRet;
+	typedef typename CompMatFuncBase::IArgs		IArgs;
+
+protected:
+
+	IRet	doApply	(const EvalContext& ctx, const IArgs& iargs) const
+	{
+		IRet			ret;
+
+		for (int col = 0; col < Cols; ++col)
+		{
+			for (int row = 0; row < Rows; ++row)
+				ret[col][row] = this->doGetScalarFunc().apply(ctx,
+															  iargs.a[col][row],
+															  iargs.b[col][row]);
+		}
+
+		return ret;
+	}
+};
+
+template <typename F, int Rows, int Cols>
+class CompMatFunc : public CompMatFuncBase<Rows, Cols>
+{
+protected:
+	const typename CompMatFunc::ScalarFunc&	doGetScalarFunc	(void) const
+	{
+		return instance<F>();
+	}
+};
+
+class ScalarMatrixCompMult : public Mul
+{
+public:
+	string	getName	(void) const
+	{
+		return "matrixCompMult";
+	}
+
+	void	doPrint	(ostream& os, const BaseArgExprs& args) const
+	{
+		Func<Sig>::doPrint(os, args);
+	}
+};
+
+template <int Rows, int Cols>
+class MatrixCompMult : public CompMatFunc<ScalarMatrixCompMult, Rows, Cols>
+{
+};
+
+template <int Rows, int Cols>
+class ScalarMatFuncBase : public CompWiseFunc<float, Signature<Matrix<float, Rows, Cols>,
+															   Matrix<float, Rows, Cols>,
+															   float> >
+{
+public:
+	typedef typename ScalarMatFuncBase::IRet	IRet;
+	typedef typename ScalarMatFuncBase::IArgs	IArgs;
+
+protected:
+
+	IRet	doApply	(const EvalContext& ctx, const IArgs& iargs) const
+	{
+		IRet	ret;
+
+		for (int col = 0; col < Cols; ++col)
+		{
+			for (int row = 0; row < Rows; ++row)
+				ret[col][row] = this->doGetScalarFunc().apply(ctx, iargs.a[col][row], iargs.b);
+		}
+
+		return ret;
+	}
+};
+
+template <typename F, int Rows, int Cols>
+class ScalarMatFunc : public ScalarMatFuncBase<Rows, Cols>
+{
+protected:
+	const typename ScalarMatFunc::ScalarFunc&	doGetScalarFunc	(void)	const
+	{
+		return instance<F>();
+	}
+};
+
+template<typename T, int Size> struct GenXType;
+
+template<typename T>
+struct GenXType<T, 1>
+{
+	static ExprP<T>	genXType	(const ExprP<T>& x) { return x; }
+};
+
+template<typename T>
+struct GenXType<T, 2>
+{
+	static ExprP<Vector<T, 2> >	genXType	(const ExprP<T>& x)
+	{
+		return app<GenVec<T, 2> >(x, x);
+	}
+};
+
+template<typename T>
+struct GenXType<T, 3>
+{
+	static ExprP<Vector<T, 3> >	genXType	(const ExprP<T>& x)
+	{
+		return app<GenVec<T, 3> >(x, x, x);
+	}
+};
+
+template<typename T>
+struct GenXType<T, 4>
+{
+	static ExprP<Vector<T, 4> >	genXType	(const ExprP<T>& x)
+	{
+		return app<GenVec<T, 4> >(x, x, x, x);
+	}
+};
+
+//! Returns an expression of vector of size `Size` (or scalar if Size == 1),
+//! with each element initialized with the expression `x`.
+template<typename T, int Size>
+ExprP<typename ContainerOf<T, Size>::Container> genXType (const ExprP<T>& x)
+{
+	return GenXType<T, Size>::genXType(x);
+}
+
+typedef GenVec<float, 2> FloatVec2;
+DEFINE_CONSTRUCTOR2(FloatVec2, Vec2, vec2, float, float)
+
+typedef GenVec<float, 3> FloatVec3;
+DEFINE_CONSTRUCTOR3(FloatVec3, Vec3, vec3, float, float, float)
+
+typedef GenVec<float, 4> FloatVec4;
+DEFINE_CONSTRUCTOR4(FloatVec4, Vec4, vec4, float, float, float, float)
+
+template <int Size>
+class Dot : public DerivedFunc<Signature<float, Vector<float, Size>, Vector<float, Size> > >
+{
+public:
+	typedef typename Dot::ArgExprs ArgExprs;
+
+	string			getName		(void) const
+	{
+		return "dot";
+	}
+
+protected:
+	ExprP<float>	doExpand 	(ExpandContext&, const ArgExprs& args) const
+	{
+		ExprP<float> val = args.a[0] * args.b[0];
+
+		for (int ndx = 1; ndx < Size; ++ndx)
+			val = val + args.a[ndx] * args.b[ndx];
+
+		return val;
+	}
+};
+
+template <>
+class Dot<1> : public DerivedFunc<Signature<float, float, float> >
+{
+public:
+	string			getName		(void) const
+	{
+		return "dot";
+	}
+
+	ExprP<float>	doExpand 	(ExpandContext&, const ArgExprs& args) const
+	{
+		return args.a * args.b;
+	}
+};
+
+template <int Size>
+ExprP<float> dot (const ExprP<Vector<float, Size> >& x, const ExprP<Vector<float, Size> >& y)
+{
+	return app<Dot<Size> >(x, y);
+}
+
+ExprP<float> dot (const ExprP<float>& x, const ExprP<float>& y)
+{
+	return app<Dot<1> >(x, y);
+}
+
+template <int Size>
+class Length : public DerivedFunc<
+	Signature<float, typename ContainerOf<float, Size>::Container> >
+{
+public:
+	typedef typename Length::ArgExprs ArgExprs;
+
+	string			getName		(void) const
+	{
+		return "length";
+	}
+
+protected:
+	ExprP<float>	doExpand	(ExpandContext&, const ArgExprs& args) const
+	{
+		return sqrt(dot(args.a, args.a));
+	}
+};
+
+template <int Size>
+ExprP<float> length (const ExprP<typename ContainerOf<float, Size>::Container>& x)
+{
+	return app<Length<Size> >(x);
+}
+
+template <int Size>
+class Distance : public DerivedFunc<
+	Signature<float,
+			  typename ContainerOf<float, Size>::Container,
+			  typename ContainerOf<float, Size>::Container> >
+{
+public:
+	typedef typename	Distance::Ret		Ret;
+	typedef typename	Distance::ArgExprs	ArgExprs;
+
+	string		getName		(void) const
+	{
+		return "distance";
+	}
+
+protected:
+	ExprP<Ret>	doExpand 	(ExpandContext&, const ArgExprs& args) const
+	{
+		return length<Size>(args.a - args.b);
+	}
+};
+
+// cross
+
+class Cross : public DerivedFunc<Signature<Vec3, Vec3, Vec3> >
+{
+public:
+	string			getName		(void) const
+	{
+		return "cross";
+	}
+
+protected:
+	ExprP<Vec3>		doExpand 	(ExpandContext&, const ArgExprs& x) const
+	{
+		return vec3(x.a[1] * x.b[2] - x.b[1] * x.a[2],
+					x.a[2] * x.b[0] - x.b[2] * x.a[0],
+					x.a[0] * x.b[1] - x.b[0] * x.a[1]);
+	}
+};
+
+DEFINE_CONSTRUCTOR2(Cross, Vec3, cross, Vec3, Vec3)
+
+template<int Size>
+class Normalize : public DerivedFunc<
+	Signature<typename ContainerOf<float, Size>::Container,
+			  typename ContainerOf<float, Size>::Container> >
+{
+public:
+	typedef typename	Normalize::Ret		Ret;
+	typedef typename	Normalize::ArgExprs	ArgExprs;
+
+	string		getName		(void) const
+	{
+		return "normalize";
+	}
+
+protected:
+	ExprP<Ret>	doExpand	(ExpandContext&, const ArgExprs& args) const
+	{
+		return args.a / length<Size>(args.a);
+	}
+};
+
+template <int Size>
+class FaceForward : public DerivedFunc<
+	Signature<typename ContainerOf<float, Size>::Container,
+			  typename ContainerOf<float, Size>::Container,
+			  typename ContainerOf<float, Size>::Container,
+			  typename ContainerOf<float, Size>::Container> >
+{
+public:
+	typedef typename	FaceForward::Ret		Ret;
+	typedef typename	FaceForward::ArgExprs	ArgExprs;
+
+	string		getName		(void) const
+	{
+		return "faceforward";
+	}
+
+protected:
+
+
+	ExprP<Ret>	doExpand	(ExpandContext&, const ArgExprs& args) const
+	{
+		return cond(dot(args.c, args.b) < constant(0.0f), args.a, -args.a);
+	}
+};
+
+template <int Size>
+class Reflect : public DerivedFunc<
+	Signature<typename ContainerOf<float, Size>::Container,
+			  typename ContainerOf<float, Size>::Container,
+			  typename ContainerOf<float, Size>::Container> >
+{
+public:
+	typedef typename	Reflect::Ret		Ret;
+	typedef typename	Reflect::ArgExprs	ArgExprs;
+
+	string		getName		(void) const
+	{
+		return "reflect";
+	}
+
+protected:
+	ExprP<Ret>	doExpand	(ExpandContext&, const ArgExprs& args) const
+	{
+		return args.a - (args.b * dot(args.b, args.a) * constant(2.0f));
+	}
+};
+
+template <int Size>
+class Refract : public DerivedFunc<
+	Signature<typename ContainerOf<float, Size>::Container,
+			  typename ContainerOf<float, Size>::Container,
+			  typename ContainerOf<float, Size>::Container,
+			  float> >
+{
+public:
+	typedef typename	Refract::Ret		Ret;
+	typedef typename	Refract::Arg0		Arg0;
+	typedef typename	Refract::Arg1		Arg1;
+	typedef typename	Refract::ArgExprs	ArgExprs;
+
+	string		getName		(void) const
+	{
+		return "refract";
+	}
+
+protected:
+	ExprP<Ret>	doExpand	(ExpandContext&	ctx, const ArgExprs& args) const
+	{
+		const ExprP<Arg0>&	i		= args.a;
+		const ExprP<Arg1>&	n		= args.b;
+		const ExprP<float>&	eta		= args.c;
+		const ExprP<float>	dotNI	= bindExpression("dotNI", ctx, dot(n, i));
+		const ExprP<float>	k		= bindExpression("k", ctx, constant(1.0f) - eta * eta *
+												 (constant(1.0f) - dotNI * dotNI));
+
+		return cond(k < constant(0.0f),
+					genXType<float, Size>(constant(0.0f)),
+					i * eta - n * (eta * dotNI + sqrt(k)));
+	}
+};
+
+class PreciseFunc1 : public CFloatFunc1
+{
+public:
+			PreciseFunc1	(const string& name, DoubleFunc1& func) : CFloatFunc1(name, func) {}
+protected:
+	double	precision		(const EvalContext&, double, double) const	{ return 0.0; }
+};
+
+class Abs : public PreciseFunc1
+{
+public:
+	Abs (void) : PreciseFunc1("abs", deAbs) {}
+};
+
+class Sign : public PreciseFunc1
+{
+public:
+	Sign (void) : PreciseFunc1("sign", deSign) {}
+};
+
+class Floor : public PreciseFunc1
+{
+public:
+	Floor (void) : PreciseFunc1("floor", deFloor) {}
+};
+
+class Trunc : public PreciseFunc1
+{
+public:
+	Trunc (void) : PreciseFunc1("trunc", deTrunc) {}
+};
+
+class Round : public FloatFunc1
+{
+public:
+	string		getName		(void) const								{ return "round"; }
+
+protected:
+	Interval	applyPoint	(const EvalContext&, double x) const
+	{
+		double			truncated	= 0.0;
+		const double	fract		= deModf(x, &truncated);
+		Interval		ret;
+
+		if (fabs(fract) <= 0.5)
+			ret |= truncated;
+		if (fabs(fract) >= 0.5)
+			ret |= truncated + deSign(fract);
+
+		return ret;
+	}
+
+	double		precision	(const EvalContext&, double, double) const	{ return 0.0; }
+};
+
+class RoundEven : public PreciseFunc1
+{
+public:
+	RoundEven (void) : PreciseFunc1("roundEven", deRoundEven) {}
+};
+
+class Ceil : public PreciseFunc1
+{
+public:
+	Ceil (void) : PreciseFunc1("ceil", deCeil) {}
+};
+
+DEFINE_DERIVED_FLOAT1(Fract, fract, x, x - app<Floor>(x));
+
+class PreciseFunc2 : public CFloatFunc2
+{
+public:
+			PreciseFunc2	(const string& name, DoubleFunc2& func) : CFloatFunc2(name, func) {}
+protected:
+	double	precision		(const EvalContext&, double, double, double) const { return 0.0; }
+};
+
+DEFINE_DERIVED_FLOAT2(Mod, mod, x, y, x - y * app<Floor>(x / y));
+
+class Modf : public PrimitiveFunc<Signature<float, float, float> >
+{
+public:
+	string	getName				(void) const
+	{
+		return "modf";
+	}
+
+protected:
+	IRet	doApply				(const EvalContext&, const IArgs& iargs) const
+	{
+		Interval	fracIV;
+		Interval&	wholeIV		= const_cast<Interval&>(iargs.b);
+		double		intPart		= 0;
+
+		TCU_INTERVAL_APPLY_MONOTONE1(fracIV, x, iargs.a, frac, frac = deModf(x, &intPart));
+		TCU_INTERVAL_APPLY_MONOTONE1(wholeIV, x, iargs.a, whole,
+									 deModf(x, &intPart); whole = intPart);
+		return fracIV;
+	}
+
+	int		getOutParamIndex	(void) const
+	{
+		return 1;
+	}
+};
+
+class Min : public PreciseFunc2 { public: Min (void) : PreciseFunc2("min", deMin) {} };
+class Max : public PreciseFunc2 { public: Max (void) : PreciseFunc2("max", deMax) {} };
+
+class Clamp : public FloatFunc3
+{
+public:
+	string	getName		(void) const { return "clamp"; }
+
+	double	applyExact	(double x, double minVal, double maxVal) const
+	{
+		return de::min(de::max(x, minVal), maxVal);
+	}
+
+	double	precision	(const EvalContext&, double, double, double minVal, double maxVal) const
+	{
+		return minVal > maxVal ? TCU_NAN : 0.0;
+	}
+};
+
+ExprP<float> clamp(const ExprP<float>& x, const ExprP<float>& minVal, const ExprP<float>& maxVal)
+{
+	return app<Clamp>(x, minVal, maxVal);
+}
+
+DEFINE_DERIVED_FLOAT3(Mix, mix, x, y, a, (x * (constant(1.0f) - a)) + y * a);
+
+static double step (double edge, double x)
+{
+	return x < edge ? 0.0 : 1.0;
+}
+
+class Step : public PreciseFunc2 { public: Step (void) : PreciseFunc2("step", step) {} };
+
+class SmoothStep : public DerivedFunc<Signature<float, float, float, float> >
+{
+public:
+	string		getName		(void) const
+	{
+		return "smoothstep";
+	}
+
+protected:
+
+	ExprP<Ret>	doExpand 	(ExpandContext& ctx, const ArgExprs& args) const
+	{
+		const ExprP<float>&		edge0	= args.a;
+		const ExprP<float>&		edge1	= args.b;
+		const ExprP<float>&		x		= args.c;
+		const ExprP<float>		tExpr	= clamp((x - edge0) / (edge1 - edge0),
+											constant(0.0f), constant(1.0f));
+		const ExprP<float>		t		= bindExpression("t", ctx, tExpr);
+
+		return (t * t * (constant(3.0f) - constant(2.0f) * t));
+	}
+};
+
+class FrExp : public PrimitiveFunc<Signature<float, float, int> >
+{
+public:
+	string	getName			(void) const
+	{
+		return "frexp";
+	}
+
+protected:
+	IRet	doApply			(const EvalContext&, const IArgs& iargs) const
+	{
+		IRet			ret;
+		const IArg0&	x			= iargs.a;
+		IArg1&			exponent	= const_cast<IArg1&>(iargs.b);
+
+		if (x.hasNaN() || x.contains(TCU_INFINITY) || x.contains(-TCU_INFINITY))
+		{
+			// GLSL (in contrast to IEEE) says that result of applying frexp
+			// to infinity is undefined
+			ret = Interval::unbounded() | TCU_NAN;
+			exponent = Interval(-deLdExp(1.0, 31), deLdExp(1.0, 31)-1);
+		}
+		else if (!x.empty())
+		{
+			int				loExp	= 0;
+			const double	loFrac	= deFrExp(x.lo(), &loExp);
+			int				hiExp	= 0;
+			const double	hiFrac	= deFrExp(x.hi(), &hiExp);
+
+			if (deSign(loFrac) != deSign(hiFrac))
+			{
+				exponent = Interval(-TCU_INFINITY, de::max(loExp, hiExp));
+				ret = Interval();
+				if (deSign(loFrac) < 0)
+					ret |= Interval(-1.0 + DBL_EPSILON*0.5, 0.0);
+				if (deSign(hiFrac) > 0)
+					ret |= Interval(0.0, 1.0 - DBL_EPSILON*0.5);
+			}
+			else
+			{
+				exponent = Interval(loExp, hiExp);
+				if (loExp == hiExp)
+					ret = Interval(loFrac, hiFrac);
+				else
+					ret = deSign(loFrac) * Interval(0.5, 1.0 - DBL_EPSILON*0.5);
+			}
+		}
+
+		return ret;
+	}
+
+	int	getOutParamIndex	(void) const
+	{
+		return 1;
+	}
+};
+
+class LdExp : public PrimitiveFunc<Signature<float, float, int> >
+{
+public:
+	string		getName			(void) const
+	{
+		return "ldexp";
+	}
+
+protected:
+	Interval	doApply			(const EvalContext& ctx, const IArgs& iargs) const
+	{
+		Interval	ret = call<Exp2>(ctx, iargs.b);
+		// Khronos bug 11180 consensus: if exp2(exponent) cannot be represented,
+		// the result is undefined.
+
+		if (ret.contains(TCU_INFINITY) | ret.contains(-TCU_INFINITY))
+			ret |= TCU_NAN;
+
+		return call<Mul>(ctx, iargs.a, ret);
+	}
+};
+
+template<int Rows, int Columns>
+class Transpose : public PrimitiveFunc<Signature<Matrix<float, Rows, Columns>,
+												 Matrix<float, Columns, Rows> > >
+{
+public:
+	typedef typename Transpose::IRet	IRet;
+	typedef typename Transpose::IArgs	IArgs;
+
+	string		getName		(void) const
+	{
+		return "transpose";
+	}
+
+protected:
+	IRet		doApply		(const EvalContext&, const IArgs& iargs) const
+	{
+		IRet ret;
+
+		for (int rowNdx = 0; rowNdx < Rows; ++rowNdx)
+		{
+			for (int colNdx = 0; colNdx < Columns; ++colNdx)
+				ret(rowNdx, colNdx) = iargs.a(colNdx, rowNdx);
+		}
+
+		return ret;
+	}
+};
+
+template<typename Ret, typename Arg0, typename Arg1>
+class MulFunc : public PrimitiveFunc<Signature<Ret, Arg0, Arg1> >
+{
+public:
+	string	getName	(void) const 									{ return "mul"; }
+
+protected:
+	void	doPrint	(ostream& os, const BaseArgExprs& args) const
+	{
+		os << "(" << *args[0] << " * " << *args[1] << ")";
+	}
+};
+
+template<int LeftRows, int Middle, int RightCols>
+class MatMul : public MulFunc<Matrix<float, LeftRows, RightCols>,
+							  Matrix<float, LeftRows, Middle>,
+							  Matrix<float, Middle, RightCols> >
+{
+protected:
+	typedef typename MatMul::IRet	IRet;
+	typedef typename MatMul::IArgs	IArgs;
+	typedef typename MatMul::IArg0	IArg0;
+	typedef typename MatMul::IArg1	IArg1;
+
+	IRet	doApply	(const EvalContext&	ctx, const IArgs& iargs) const
+	{
+		const IArg0&	left	= iargs.a;
+		const IArg1&	right	= iargs.b;
+		IRet			ret;
+
+		for (int row = 0; row < LeftRows; ++row)
+		{
+			for (int col = 0; col < RightCols; ++col)
+			{
+				Interval	element	(0.0);
+
+				for (int ndx = 0; ndx < Middle; ++ndx)
+					element = call<Add>(ctx, element,
+										call<Mul>(ctx, left[ndx][row], right[col][ndx]));
+
+				ret[col][row] = element;
+			}
+		}
+
+		return ret;
+	}
+};
+
+template<int Rows, int Cols>
+class VecMatMul : public MulFunc<Vector<float, Cols>,
+								 Vector<float, Rows>,
+								 Matrix<float, Rows, Cols> >
+{
+public:
+	typedef typename VecMatMul::IRet	IRet;
+	typedef typename VecMatMul::IArgs	IArgs;
+	typedef typename VecMatMul::IArg0	IArg0;
+	typedef typename VecMatMul::IArg1	IArg1;
+
+protected:
+	IRet	doApply	(const EvalContext& ctx, const IArgs& iargs) const
+	{
+		const IArg0&	left	= iargs.a;
+		const IArg1&	right	= iargs.b;
+		IRet			ret;
+
+		for (int col = 0; col < Cols; ++col)
+		{
+			Interval	element	(0.0);
+
+			for (int row = 0; row < Rows; ++row)
+				element = call<Add>(ctx, element, call<Mul>(ctx, left[row], right[col][row]));
+
+			ret[col] = element;
+		}
+
+		return ret;
+	}
+};
+
+template<int Rows, int Cols>
+class MatVecMul : public MulFunc<Vector<float, Rows>,
+								 Matrix<float, Rows, Cols>,
+								 Vector<float, Cols> >
+{
+public:
+	typedef typename MatVecMul::IRet	IRet;
+	typedef typename MatVecMul::IArgs	IArgs;
+	typedef typename MatVecMul::IArg0	IArg0;
+	typedef typename MatVecMul::IArg1	IArg1;
+
+protected:
+	IRet	doApply	(const EvalContext& ctx, const IArgs& iargs) const
+	{
+		const IArg0&	left	= iargs.a;
+		const IArg1&	right	= iargs.b;
+
+		return call<VecMatMul<Cols, Rows> >(ctx, right,
+											call<Transpose<Rows, Cols> >(ctx, left));
+	}
+};
+
+template<int Rows, int Cols>
+class OuterProduct : public PrimitiveFunc<Signature<Matrix<float, Rows, Cols>,
+													Vector<float, Rows>,
+													Vector<float, Cols> > >
+{
+public:
+	typedef typename OuterProduct::IRet		IRet;
+	typedef typename OuterProduct::IArgs	IArgs;
+
+	string	getName	(void) const
+	{
+		return "outerProduct";
+	}
+
+protected:
+	IRet	doApply	(const EvalContext& ctx, const IArgs& iargs) const
+	{
+		IRet	ret;
+
+		for (int row = 0; row < Rows; ++row)
+		{
+			for (int col = 0; col < Cols; ++col)
+				ret[col][row] = call<Mul>(ctx, iargs.a[row], iargs.b[col]);
+		}
+
+		return ret;
+	}
+};
+
+template<int Rows, int Cols>
+ExprP<Matrix<float, Rows, Cols> > outerProduct (const ExprP<Vector<float, Rows> >& left,
+												const ExprP<Vector<float, Cols> >& right)
+{
+	return app<OuterProduct<Rows, Cols> >(left, right);
+}
+
+template<int Size>
+class DeterminantBase : public DerivedFunc<Signature<float, Matrix<float, Size, Size> > >
+{
+public:
+	string	getName	(void) const { return "determinant"; }
+};
+
+template<int Size>
+class Determinant;
+
+template<int Size>
+ExprP<float> determinant (ExprP<Matrix<float, Size, Size> > mat)
+{
+	return app<Determinant<Size> >(mat);
+}
+
+template<>
+class Determinant<2> : public DeterminantBase<2>
+{
+protected:
+	ExprP<Ret>	doExpand (ExpandContext&, const ArgExprs& args)	const
+	{
+		ExprP<Mat2>	mat	= args.a;
+
+		return mat[0][0] * mat[1][1] - mat[1][0] * mat[0][1];
+	}
+};
+
+template<>
+class Determinant<3> : public DeterminantBase<3>
+{
+protected:
+	ExprP<Ret> doExpand (ExpandContext&, const ArgExprs& args) const
+	{
+		ExprP<Mat3>	mat	= args.a;
+
+		return (mat[0][0] * (mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]) +
+				mat[0][1] * (mat[1][2] * mat[2][0] - mat[1][0] * mat[2][2]) +
+				mat[0][2] * (mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]));
+	}
+};
+
+template<>
+class Determinant<4> : public DeterminantBase<4>
+{
+protected:
+	 ExprP<Ret>	doExpand	(ExpandContext& ctx, const ArgExprs& args) const
+	{
+		ExprP<Mat4>	mat	= args.a;
+		ExprP<Mat3>	minors[4];
+
+		for (int ndx = 0; ndx < 4; ++ndx)
+		{
+			ExprP<Vec4>		minorColumns[3];
+			ExprP<Vec3>		columns[3];
+
+			for (int col = 0; col < 3; ++col)
+				minorColumns[col] = mat[col < ndx ? col : col + 1];
+
+			for (int col = 0; col < 3; ++col)
+				columns[col] = vec3(minorColumns[0][col+1],
+									minorColumns[1][col+1],
+									minorColumns[2][col+1]);
+
+			minors[ndx] = bindExpression("minor", ctx,
+										 mat3(columns[0], columns[1], columns[2]));
+		}
+
+		return (mat[0][0] * determinant(minors[0]) -
+				mat[1][0] * determinant(minors[1]) +
+				mat[2][0] * determinant(minors[2]) -
+				mat[3][0] * determinant(minors[3]));
+	}
+};
+
+template<int Size> class Inverse;
+
+template <int Size>
+ExprP<Matrix<float, Size, Size> > inverse (ExprP<Matrix<float, Size, Size> > mat)
+{
+	return app<Inverse<Size> >(mat);
+}
+
+template<>
+class Inverse<2> : public DerivedFunc<Signature<Mat2, Mat2> >
+{
+public:
+	string		getName	(void) const
+	{
+		return "inverse";
+	}
+
+protected:
+	ExprP<Ret>	doExpand (ExpandContext& ctx, const ArgExprs& args) const
+	{
+		ExprP<Mat2>		mat = args.a;
+		ExprP<float>	det	= bindExpression("det", ctx, determinant(mat));
+
+		return mat2(vec2(mat[1][1] / det, -mat[0][1] / det),
+					vec2(-mat[1][0] / det, mat[0][0] / det));
+	}
+};
+
+template<>
+class Inverse<3> : public DerivedFunc<Signature<Mat3, Mat3> >
+{
+public:
+	string		getName		(void) const
+	{
+		return "inverse";
+	}
+
+protected:
+	ExprP<Ret>	doExpand 	(ExpandContext& ctx, const ArgExprs& args)			const
+	{
+		ExprP<Mat3>		mat		= args.a;
+		ExprP<Mat2>		invA	= bindExpression("invA", ctx,
+												 inverse(mat2(vec2(mat[0][0], mat[0][1]),
+															  vec2(mat[1][0], mat[1][1]))));
+
+		ExprP<Vec2>		matB	= bindExpression("matB", ctx, vec2(mat[2][0], mat[2][1]));
+		ExprP<Vec2>		matC	= bindExpression("matC", ctx, vec2(mat[0][2], mat[1][2]));
+		ExprP<float>	matD	= bindExpression("matD", ctx, mat[2][2]);
+
+		ExprP<float>	schur	= bindExpression("schur", ctx,
+												 constant(1.0f) /
+												 (matD - dot(matC * invA, matB)));
+
+		ExprP<Vec2>		t1		= invA * matB;
+		ExprP<Vec2>		t2		= t1 * schur;
+		ExprP<Mat2>		t3		= outerProduct(t2, matC);
+		ExprP<Mat2>		t4		= t3 * invA;
+		ExprP<Mat2>		t5		= invA + t4;
+		ExprP<Mat2>		blockA	= bindExpression("blockA", ctx, t5);
+		ExprP<Vec2>		blockB	= bindExpression("blockB", ctx,
+												 (invA * matB) * -schur);
+		ExprP<Vec2>		blockC	= bindExpression("blockC", ctx,
+												 (matC * invA) * -schur);
+
+		return mat3(vec3(blockA[0][0], blockA[0][1], blockC[0]),
+					vec3(blockA[1][0], blockA[1][1], blockC[1]),
+					vec3(blockB[0], blockB[1], schur));
+	}
+};
+
+template<>
+class Inverse<4> : public DerivedFunc<Signature<Mat4, Mat4> >
+{
+public:
+	string		getName		(void) const { return "inverse"; }
+
+protected:
+	ExprP<Ret>			doExpand 			(ExpandContext&		ctx,
+											 const ArgExprs&	args)			const
+	{
+		ExprP<Mat4>	mat		= args.a;
+		ExprP<Mat2>	invA	= bindExpression("invA", ctx,
+											 inverse(mat2(vec2(mat[0][0], mat[0][1]),
+														  vec2(mat[1][0], mat[1][1]))));
+		ExprP<Mat2>	matB	= bindExpression("matB", ctx,
+											 mat2(vec2(mat[2][0], mat[2][1]),
+												  vec2(mat[3][0], mat[3][1])));
+		ExprP<Mat2>	matC	= bindExpression("matC", ctx,
+											 mat2(vec2(mat[0][2], mat[0][3]),
+												  vec2(mat[1][2], mat[1][3])));
+		ExprP<Mat2>	matD	= bindExpression("matD", ctx,
+											 mat2(vec2(mat[2][2], mat[2][3]),
+												  vec2(mat[3][2], mat[3][3])));
+		ExprP<Mat2>	schur	= bindExpression("schur", ctx,
+											 inverse(matD + -(matC * invA * matB)));
+		ExprP<Mat2>	blockA	= bindExpression("blockA", ctx,
+											 invA + (invA * matB * schur * matC * invA));
+		ExprP<Mat2>	blockB	= bindExpression("blockB", ctx,
+											 (-invA) * matB * schur);
+		ExprP<Mat2>	blockC	= bindExpression("blockC", ctx,
+											 (-schur) * matC * invA);
+
+		return mat4(vec4(blockA[0][0], blockA[0][1], blockC[0][0], blockC[0][1]),
+					vec4(blockA[1][0], blockA[1][1], blockC[1][0], blockC[1][1]),
+					vec4(blockB[0][0], blockB[0][1], schur[0][0], schur[0][1]),
+					vec4(blockB[1][0], blockB[1][1], schur[1][0], schur[1][1]));
+	}
+};
+
+class Fma : public DerivedFunc<Signature<float, float, float, float> >
+{
+public:
+	string			getName					(void) const
+	{
+		return "fma";
+	}
+
+	string			getRequiredExtension	(void) const
+	{
+		return "GL_EXT_gpu_ shader5";
+	}
+
+protected:
+	ExprP<float>	doExpand 				(ExpandContext&, const ArgExprs& x) const
+	{
+		return x.a * x.b + x.c;
+	}
+};
+
+} // Functions
+
+using namespace Functions;
+
+template <typename T>
+ExprP<typename T::Element> ContainerExprPBase<T>::operator[] (int i) const
+{
+	return Functions::getComponent(exprP<T>(*this), i);
+}
+
+ExprP<float> operator+ (const ExprP<float>& arg0, const ExprP<float>& arg1)
+{
+	return app<Add>(arg0, arg1);
+}
+
+ExprP<float> operator- (const ExprP<float>& arg0, const ExprP<float>& arg1)
+{
+	return app<Sub>(arg0, arg1);
+}
+
+ExprP<float> operator- (const ExprP<float>& arg0)
+{
+	return app<Negate>(arg0);
+}
+
+ExprP<float> operator* (const ExprP<float>& arg0, const ExprP<float>& arg1)
+{
+	return app<Mul>(arg0, arg1);
+}
+
+ExprP<float> operator/ (const ExprP<float>& arg0, const ExprP<float>& arg1)
+{
+	return app<Div>(arg0, arg1);
+}
+
+template <typename Sig_, int Size>
+class GenFunc : public PrimitiveFunc<Signature<
+	typename ContainerOf<typename Sig_::Ret, Size>::Container,
+	typename ContainerOf<typename Sig_::Arg0, Size>::Container,
+	typename ContainerOf<typename Sig_::Arg1, Size>::Container,
+	typename ContainerOf<typename Sig_::Arg2, Size>::Container,
+	typename ContainerOf<typename Sig_::Arg3, Size>::Container> >
+{
+public:
+	typedef typename GenFunc::IArgs		IArgs;
+	typedef typename GenFunc::IRet		IRet;
+
+			GenFunc					(const Func<Sig_>&	scalarFunc) : m_func (scalarFunc) {}
+
+	string	getName					(void) const
+	{
+		return m_func.getName();
+	}
+
+	int		getOutParamIndex		(void) const
+	{
+		return m_func.getOutParamIndex();
+	}
+
+	string	getRequiredExtension	(void) const
+	{
+		return m_func.getRequiredExtension();
+	}
+
+protected:
+	void	doPrint					(ostream& os, const BaseArgExprs& args) const
+	{
+		m_func.print(os, args);
+	}
+
+	IRet	doApply					(const EvalContext& ctx, const IArgs& iargs) const
+	{
+		IRet ret;
+
+		for (int ndx = 0; ndx < Size; ++ndx)
+		{
+			ret[ndx] =
+				m_func.apply(ctx, iargs.a[ndx], iargs.b[ndx], iargs.c[ndx], iargs.d[ndx]);
+		}
+
+		return ret;
+	}
+
+	void	doGetUsedFuncs			(FuncSet& dst) const
+	{
+		m_func.getUsedFuncs(dst);
+	}
+
+	const Func<Sig_>&	m_func;
+};
+
+template <typename F, int Size>
+class VectorizedFunc : public GenFunc<typename F::Sig, Size>
+{
+public:
+	VectorizedFunc	(void) : GenFunc<typename F::Sig, Size>(instance<F>()) {}
+};
+
+
+
+template <typename Sig_, int Size>
+class FixedGenFunc : public PrimitiveFunc <Signature<
+	typename ContainerOf<typename Sig_::Ret, Size>::Container,
+	typename ContainerOf<typename Sig_::Arg0, Size>::Container,
+	typename Sig_::Arg1,
+	typename ContainerOf<typename Sig_::Arg2, Size>::Container,
+	typename ContainerOf<typename Sig_::Arg3, Size>::Container> >
+{
+public:
+	typedef typename FixedGenFunc::IArgs		IArgs;
+	typedef typename FixedGenFunc::IRet			IRet;
+
+	string						getName			(void) const
+	{
+		return this->doGetScalarFunc().getName();
+	}
+
+protected:
+	void						doPrint			(ostream& os, const BaseArgExprs& args) const
+	{
+		this->doGetScalarFunc().print(os, args);
+	}
+
+	IRet						doApply			(const EvalContext& ctx,
+												 const IArgs&		iargs) const
+	{
+		IRet				ret;
+		const Func<Sig_>&	func	= this->doGetScalarFunc();
+
+		for (int ndx = 0; ndx < Size; ++ndx)
+			ret[ndx] = func.apply(ctx, iargs.a[ndx], iargs.b, iargs.c[ndx], iargs.d[ndx]);
+
+		return ret;
+	}
+
+	virtual const Func<Sig_>&	doGetScalarFunc	(void) const = 0;
+};
+
+template <typename F, int Size>
+class FixedVecFunc : public FixedGenFunc<typename F::Sig, Size>
+{
+protected:
+	const Func<typename F::Sig>& doGetScalarFunc	(void) const { return instance<F>(); }
+};
+
+template<typename Sig>
+struct GenFuncs
+{
+	GenFuncs (const Func<Sig>&			func_,
+			  const GenFunc<Sig, 2>&	func2_,
+			  const GenFunc<Sig, 3>&	func3_,
+			  const GenFunc<Sig, 4>&	func4_)
+		: func	(func_)
+		, func2	(func2_)
+		, func3	(func3_)
+		, func4	(func4_)
+	{}
+
+	const Func<Sig>&		func;
+	const GenFunc<Sig, 2>&	func2;
+	const GenFunc<Sig, 3>&	func3;
+	const GenFunc<Sig, 4>&	func4;
+};
+
+template<typename F>
+GenFuncs<typename F::Sig> makeVectorizedFuncs (void)
+{
+	return GenFuncs<typename F::Sig>(instance<F>(),
+									 instance<VectorizedFunc<F, 2> >(),
+									 instance<VectorizedFunc<F, 3> >(),
+									 instance<VectorizedFunc<F, 4> >());
+}
+
+template<int Size>
+ExprP<Vector<float, Size> > operator*(const ExprP<Vector<float, Size> >& arg0,
+									  const ExprP<Vector<float, Size> >& arg1)
+{
+	return app<VectorizedFunc<Mul, Size> >(arg0, arg1);
+}
+
+template<int Size>
+ExprP<Vector<float, Size> > operator*(const ExprP<Vector<float, Size> >&	arg0,
+									  const ExprP<float>&					arg1)
+{
+	return app<FixedVecFunc<Mul, Size> >(arg0, arg1);
+}
+
+template<int Size>
+ExprP<Vector<float, Size> > operator/(const ExprP<Vector<float, Size> >&	arg0,
+									  const ExprP<float>&					arg1)
+{
+	return app<FixedVecFunc<Div, Size> >(arg0, arg1);
+}
+
+template<int Size>
+ExprP<Vector<float, Size> > operator-(const ExprP<Vector<float, Size> >& arg0)
+{
+	return app<VectorizedFunc<Negate, Size> >(arg0);
+}
+
+template<int Size>
+ExprP<Vector<float, Size> > operator-(const ExprP<Vector<float, Size> >& arg0,
+									  const ExprP<Vector<float, Size> >& arg1)
+{
+	return app<VectorizedFunc<Sub, Size> >(arg0, arg1);
+}
+
+template<int LeftRows, int Middle, int RightCols>
+ExprP<Matrix<float, LeftRows, RightCols> >
+operator* (const ExprP<Matrix<float, LeftRows, Middle> >&	left,
+		   const ExprP<Matrix<float, Middle, RightCols> >&	right)
+{
+	return app<MatMul<LeftRows, Middle, RightCols> >(left, right);
+}
+
+template<int Rows, int Cols>
+ExprP<Vector<float, Rows> > operator* (const ExprP<Vector<float, Cols> >&		left,
+									   const ExprP<Matrix<float, Rows, Cols> >&	right)
+{
+	return app<VecMatMul<Rows, Cols> >(left, right);
+}
+
+template<int Rows, int Cols>
+ExprP<Vector<float, Cols> > operator* (const ExprP<Matrix<float, Rows, Cols> >&	left,
+									   const ExprP<Vector<float, Rows> >&		right)
+{
+	return app<MatVecMul<Rows, Cols> >(left, right);
+}
+
+template<int Rows, int Cols>
+ExprP<Matrix<float, Rows, Cols> > operator* (const ExprP<Matrix<float, Rows, Cols> >&	left,
+											 const ExprP<float>&						right)
+{
+	return app<ScalarMatFunc<Mul, Rows, Cols> >(left, right);
+}
+
+template<int Rows, int Cols>
+ExprP<Matrix<float, Rows, Cols> > operator+ (const ExprP<Matrix<float, Rows, Cols> >&	left,
+											 const ExprP<Matrix<float, Rows, Cols> >&	right)
+{
+	return app<CompMatFunc<Add, Rows, Cols> >(left, right);
+}
+
+template<int Rows, int Cols>
+ExprP<Matrix<float, Rows, Cols> > operator- (const ExprP<Matrix<float, Rows, Cols> >&	mat)
+{
+	return app<MatNeg<Rows, Cols> >(mat);
+}
+
+template <typename T>
+class Sampling
+{
+public:
+	virtual void	genFixeds	(const FloatFormat&, vector<T>&)	const {}
+	virtual T		genRandom	(const FloatFormat&, Random&)		const { return T(); }
+	virtual double	getWeight	(void)								const { return 0.0; }
+};
+
+template <>
+class DefaultSampling<Void> : public Sampling<Void>
+{
+public:
+	void	genFixeds	(const FloatFormat&, vector<Void>& dst) const { dst.push_back(Void()); }
+};
+
+template <>
+class DefaultSampling<bool> : public Sampling<bool>
+{
+public:
+	void	genFixeds	(const FloatFormat&, vector<bool>& dst) const
+	{
+		dst.push_back(true);
+		dst.push_back(false);
+	}
+};
+
+template <>
+class DefaultSampling<int> : public Sampling<int>
+{
+public:
+	int		genRandom	(const FloatFormat&, Random& rnd) const
+	{
+		const int	exp		= rnd.getInt(0, 30);
+		const int	sign	= rnd.getBool() ? -1 : 1;
+
+		return sign * rnd.getInt(0, 1L << exp);
+	}
+
+	void	genFixeds	(const FloatFormat&, vector<int>& dst) const
+	{
+		dst.push_back(0);
+		dst.push_back(-1);
+		dst.push_back(1);
+	}
+	double	getWeight	(void) const { return 1.0; }
+};
+
+template <>
+class DefaultSampling<float> : public Sampling<float>
+{
+public:
+	float	genRandom	(const FloatFormat& format, Random& rnd) const;
+	void	genFixeds	(const FloatFormat& format, vector<float>& dst) const;
+	double	getWeight	(void) const { return 1.0; }
+};
+
+//! Generate a random float from a reasonable general-purpose distribution.
+float DefaultSampling<float>::genRandom (const FloatFormat& format,
+										 Random&			rnd) const
+{
+	const int		minExp			= format.getMinExp();
+	const int		maxExp			= format.getMaxExp();
+	const bool		haveSubnormal	= format.hasSubnormal() != tcu::NO;
+
+	// Choose exponent so that the cumulative distribution is cubic.
+	// This makes the probability distribution quadratic, with the peak centered on zero.
+	const double	minRoot			= deCbrt(minExp - 0.5 - (haveSubnormal ? 1.0 : 0.0));
+	const double	maxRoot			= deCbrt(maxExp + 0.5);
+	const int		fractionBits	= format.getFractionBits();
+	const int		exp				= int(deRoundEven(dePow(rnd.getDouble(minRoot, maxRoot),
+															3.0)));
+	float			base			= 0.0f; // integral power of two
+	float			quantum			= 0.0f; // smallest representable difference in the binade
+	float			significand		= 0.0f; // Significand.
+
+	DE_ASSERT(fractionBits < std::numeric_limits<float>::digits);
+
+	// Generate some occasional special numbers
+	switch (rnd.getInt(0, 64))
+	{
+		case 0: 	return 0;
+		case 1:		return TCU_INFINITY;
+		case 2:		return -TCU_INFINITY;
+		case 3:		return TCU_NAN;
+		default:	break;
+	}
+
+	if (exp >= minExp)
+	{
+		// Normal number
+		base = deFloatLdExp(1.0f, exp);
+		quantum = deFloatLdExp(1.0f, exp - fractionBits);
+	}
+	else
+	{
+		// Subnormal
+		base = 0.0f;
+		quantum = deFloatLdExp(1.0f, minExp - fractionBits);
+	}
+
+	switch (rnd.getInt(0, 16))
+	{
+		case 0: // The highest number in this binade, significand is all bits one.
+			significand = base - quantum;
+			break;
+		case 1: // Significand is one.
+			significand = quantum;
+			break;
+		case 2: // Significand is zero.
+			significand = 0.0;
+			break;
+		default: // Random (evenly distributed) significand.
+		{
+			deUint64 intFraction = rnd.getUint64() & ((1 << fractionBits) - 1);
+			significand = float(intFraction) * quantum;
+		}
+	}
+
+	// Produce positive numbers more often than negative.
+	return (rnd.getInt(0,3) == 0 ? -1.0f : 1.0f) * (base + significand);
+}
+
+//! Generate a standard set of floats that should always be tested.
+void DefaultSampling<float>::genFixeds (const FloatFormat& format, vector<float>& dst) const
+{
+	const int			minExp			= format.getMinExp();
+	const int			maxExp			= format.getMaxExp();
+	const int			fractionBits	= format.getFractionBits();
+	const float			minQuantum		= deFloatLdExp(1.0f, minExp - fractionBits);
+	const float			minNormalized	= deFloatLdExp(1.0f, minExp);
+	const float			maxQuantum		= deFloatLdExp(1.0f, maxExp - fractionBits);
+
+	// NaN
+	dst.push_back(TCU_NAN);
+	// Zero
+	dst.push_back(0.0f);
+
+	for (int sign = -1; sign <= 1; sign += 2)
+	{
+		// Smallest subnormal
+		dst.push_back(sign * minQuantum);
+
+		// Largest subnormal
+		dst.push_back(sign * (minNormalized - minQuantum));
+
+		// Smallest normalized
+		dst.push_back(sign * minNormalized);
+
+		// Next smallest normalized
+		dst.push_back(sign * (minNormalized + minQuantum));
+
+		dst.push_back(sign * 0.5f);
+		dst.push_back(sign * 1.0f);
+		dst.push_back(sign * 2.0f);
+
+		// Largest number
+		dst.push_back(sign * (deFloatLdExp(1.0f, maxExp) +
+							  (deFloatLdExp(1.0f, maxExp) - maxQuantum)));
+
+		dst.push_back(sign * TCU_INFINITY);
+	}
+}
+
+template <typename T, int Size>
+class DefaultSampling<Vector<T, Size> > : public Sampling<Vector<T, Size> >
+{
+public:
+	typedef Vector<T, Size>		Value;
+
+	Value	genRandom	(const FloatFormat& fmt, Random& rnd) const
+	{
+		Value ret;
+
+		for (int ndx = 0; ndx < Size; ++ndx)
+			ret[ndx] = instance<DefaultSampling<T> >().genRandom(fmt, rnd);
+
+		return ret;
+	}
+
+	void	genFixeds	(const FloatFormat& fmt, vector<Value>& dst) const
+	{
+		vector<T> scalars;
+
+		instance<DefaultSampling<T> >().genFixeds(fmt, scalars);
+
+		for (size_t scalarNdx = 0; scalarNdx < scalars.size(); ++scalarNdx)
+			dst.push_back(Value(scalars[scalarNdx]));
+	}
+
+	double	getWeight	(void) const
+	{
+		return dePow(instance<DefaultSampling<T> >().getWeight(), Size);
+	}
+};
+
+template <typename T, int Rows, int Columns>
+class DefaultSampling<Matrix<T, Rows, Columns> > : public Sampling<Matrix<T, Rows, Columns> >
+{
+public:
+	typedef Matrix<T, Rows, Columns>		Value;
+
+	Value	genRandom	(const FloatFormat& fmt, Random& rnd) const
+	{
+		Value ret;
+
+		for (int rowNdx = 0; rowNdx < Rows; ++rowNdx)
+			for (int colNdx = 0; colNdx < Columns; ++colNdx)
+				ret(rowNdx, colNdx) = instance<DefaultSampling<T> >().genRandom(fmt, rnd);
+
+		return ret;
+	}
+
+	void	genFixeds	(const FloatFormat& fmt, vector<Value>& dst) const
+	{
+		vector<T> scalars;
+
+		instance<DefaultSampling<T> >().genFixeds(fmt, scalars);
+
+		for (size_t scalarNdx = 0; scalarNdx < scalars.size(); ++scalarNdx)
+			dst.push_back(Value(scalars[scalarNdx]));
+
+		if (Columns == Rows)
+		{
+			Value	mat	(0.0);
+			T		x	= T(1.0f);
+			mat[0][0] = x;
+			for (int ndx = 0; ndx < Columns; ++ndx)
+			{
+				mat[Columns-1-ndx][ndx] = x;
+				x *= T(2.0f);
+			}
+			dst.push_back(mat);
+		}
+	}
+
+	double	getWeight	(void) const
+	{
+		return dePow(instance<DefaultSampling<T> >().getWeight(), Rows * Columns);
+	}
+};
+
+struct Context
+{
+	Context		(const string&		name_,
+				 TestContext&		testContext_,
+				 RenderContext&		renderContext_,
+				 const FloatFormat&	floatFormat_,
+				 const FloatFormat&	highpFormat_,
+				 Precision			precision_,
+				 ShaderType			shaderType_,
+				 size_t				numRandoms_)
+		: name				(name_)
+		, testContext		(testContext_)
+		, renderContext		(renderContext_)
+		, floatFormat		(floatFormat_)
+		, highpFormat		(highpFormat_)
+		, precision			(precision_)
+		, shaderType		(shaderType_)
+		, numRandoms		(numRandoms_) {}
+
+	string				name;
+	TestContext&		testContext;
+	RenderContext&		renderContext;
+	FloatFormat			floatFormat;
+	FloatFormat			highpFormat;
+	Precision			precision;
+	ShaderType			shaderType;
+	size_t				numRandoms;
+};
+
+template<typename In0_ = Void, typename In1_ = Void, typename In2_ = Void, typename In3_ = Void>
+struct InTypes
+{
+	typedef	In0_	In0;
+	typedef	In1_	In1;
+	typedef	In2_	In2;
+	typedef	In3_	In3;
+};
+
+template <typename In>
+int numInputs (void)
+{
+	return (!isTypeValid<typename In::In0>() ? 0 :
+			!isTypeValid<typename In::In1>() ? 1 :
+			!isTypeValid<typename In::In2>() ? 2 :
+			!isTypeValid<typename In::In3>() ? 3 :
+			4);
+}
+
+template<typename Out0_, typename Out1_ = Void>
+struct OutTypes
+{
+	typedef	Out0_	Out0;
+	typedef	Out1_	Out1;
+};
+
+template <typename Out>
+int numOutputs (void)
+{
+	return (!isTypeValid<typename Out::Out0>() ? 0 :
+			!isTypeValid<typename Out::Out1>() ? 1 :
+			2);
+}
+
+template<typename In>
+struct Inputs
+{
+	vector<typename In::In0>	in0;
+	vector<typename In::In1>	in1;
+	vector<typename In::In2>	in2;
+	vector<typename In::In3>	in3;
+};
+
+template<typename Out>
+struct Outputs
+{
+	Outputs	(size_t size) : out0(size), out1(size) {}
+
+	vector<typename Out::Out0>	out0;
+	vector<typename Out::Out1>	out1;
+};
+
+template<typename In, typename Out>
+struct Variables
+{
+	VariableP<typename In::In0>		in0;
+	VariableP<typename In::In1>		in1;
+	VariableP<typename In::In2>		in2;
+	VariableP<typename In::In3>		in3;
+	VariableP<typename Out::Out0>	out0;
+	VariableP<typename Out::Out1>	out1;
+};
+
+template<typename In>
+struct Samplings
+{
+	Samplings	(const Sampling<typename In::In0>&	in0_,
+				 const Sampling<typename In::In1>&	in1_,
+				 const Sampling<typename In::In2>&	in2_,
+				 const Sampling<typename In::In3>&	in3_)
+		: in0 (in0_), in1 (in1_), in2 (in2_), in3 (in3_) {}
+
+	const Sampling<typename In::In0>&	in0;
+	const Sampling<typename In::In1>&	in1;
+	const Sampling<typename In::In2>&	in2;
+	const Sampling<typename In::In3>&	in3;
+};
+
+template<typename In>
+struct DefaultSamplings : Samplings<In>
+{
+	DefaultSamplings	(void)
+		: Samplings<In>(instance<DefaultSampling<typename In::In0> >(),
+						instance<DefaultSampling<typename In::In1> >(),
+						instance<DefaultSampling<typename In::In2> >(),
+						instance<DefaultSampling<typename In::In3> >()) {}
+};
+
+class PrecisionCase : public TestCase
+{
+public:
+	IterateResult		iterate			(void);
+
+protected:
+						PrecisionCase	(const Context&		context,
+										 const string&		name)
+							: TestCase	(context.testContext,
+										 name.c_str(),
+										 name.c_str())
+							, m_ctx		(context)
+							, m_status	()
+							, m_rnd		(0xdeadbeefu +
+										 context.testContext.getCommandLine().getBaseSeed())
+	{
+	}
+
+	RenderContext&		getRenderContext(void) const 			{ return m_ctx.renderContext; }
+
+	const FloatFormat&	getFormat		(void) const 			{ return m_ctx.floatFormat; }
+
+	TestLog&			log				(void) const 			{ return m_testCtx.getLog(); }
+
+	virtual void		runTest			(void) = 0;
+
+	template <typename In, typename Out>
+	void				testStatement	(const Variables<In, Out>&	variables,
+										 const Inputs<In>&			inputs,
+										 const Statement&			stmt);
+
+	template<typename T>
+	Symbol				makeSymbol		(const Variable<T>& variable)
+	{
+		return Symbol(variable.getName(), getVarTypeOf<T>(m_ctx.precision));
+	}
+
+	Context				m_ctx;
+	ResultCollector		m_status;
+	Random				m_rnd;
+};
+
+IterateResult PrecisionCase::iterate (void)
+{
+	runTest();
+	m_status.setTestContextResult(m_testCtx);
+	return STOP;
+}
+
+template <typename In, typename Out>
+void PrecisionCase::testStatement (const Variables<In, Out>&	variables,
+								   const Inputs<In>&			inputs,
+								   const Statement&				stmt)
+{
+	using namespace ShaderExecUtil;
+
+	typedef typename 	In::In0		In0;
+	typedef typename 	In::In1		In1;
+	typedef typename 	In::In2		In2;
+	typedef typename 	In::In3		In3;
+	typedef typename 	Out::Out0	Out0;
+	typedef typename 	Out::Out1	Out1;
+
+	const FloatFormat&	fmt			= getFormat();
+	const int			inCount		= numInputs<In>();
+	const int			outCount	= numOutputs<Out>();
+	const size_t		numValues	= (inCount > 0) ? inputs.in0.size() : 1;
+	Outputs<Out>		outputs		(numValues);
+	ShaderSpec			spec;
+	const FloatFormat	highpFmt	= m_ctx.highpFormat;
+	const int			maxMsgs		= 100;
+	int					numErrors	= 0;
+	Environment			env; 		// Hoisted out of the inner loop for optimization.
+
+	switch (inCount)
+	{
+		case 4: DE_ASSERT(inputs.in3.size() == numValues);
+		case 3: DE_ASSERT(inputs.in2.size() == numValues);
+		case 2: DE_ASSERT(inputs.in1.size() == numValues);
+		case 1: DE_ASSERT(inputs.in0.size() == numValues);
+		default: break;
+	}
+
+	// Print out the statement and its definitions
+	log() << TestLog::Message << "Statement: " << stmt << TestLog::EndMessage;
+	{
+		ostringstream	oss;
+		FuncSet			funcs;
+
+		stmt.getUsedFuncs(funcs);
+		for (FuncSet::const_iterator it = funcs.begin(); it != funcs.end(); ++it)
+		{
+			(*it)->printDefinition(oss);
+		}
+		if (!funcs.empty())
+			log() << TestLog::Message << "Reference definitions:\n" << oss.str()
+				  << TestLog::EndMessage;
+	}
+
+	// Initialize ShaderSpec from precision, variables and statement.
+	{
+		ostringstream os;
+		os << "precision " << glu::getPrecisionName(m_ctx.precision) << " float;\n";
+		spec.globalDeclarations = os.str();
+	}
+
+	spec.version = getContextTypeGLSLVersion(getRenderContext().getType());
+	spec.inputs.resize(inCount);
+
+	switch (inCount)
+	{
+		case 4: spec.inputs[3] = makeSymbol(*variables.in3);
+		case 3:	spec.inputs[2] = makeSymbol(*variables.in2);
+		case 2:	spec.inputs[1] = makeSymbol(*variables.in1);
+		case 1:	spec.inputs[0] = makeSymbol(*variables.in0);
+		default: break;
+	}
+
+	spec.outputs.resize(outCount);
+
+	switch (outCount)
+	{
+		case 2:	spec.outputs[1] = makeSymbol(*variables.out1);
+		case 1:	spec.outputs[0] = makeSymbol(*variables.out0);
+		default: break;
+	}
+
+	spec.source = de::toString(stmt);
+
+	// Run the shader with inputs.
+	{
+		UniquePtr<ShaderExecutor>	executor		(createExecutor(getRenderContext(),
+																	m_ctx.shaderType,
+																	spec));
+		const void*					inputArr[]		=
+		{
+			&inputs.in0.front(), &inputs.in1.front(), &inputs.in2.front(), &inputs.in3.front(),
+		};
+		void*						outputArr[]		=
+		{
+			&outputs.out0.front(), &outputs.out1.front(),
+		};
+
+		executor->log(log());
+		if (!executor->isOk())
+			TCU_FAIL("Shader compilation failed");
+
+		executor->useProgram();
+		executor->execute(int(numValues), inputArr, outputArr);
+	}
+
+	// Initialize environment with dummy values so we don't need to bind in inner loop.
+	{
+		const typename Traits<In0>::IVal		in0;
+		const typename Traits<In1>::IVal		in1;
+		const typename Traits<In2>::IVal		in2;
+		const typename Traits<In3>::IVal		in3;
+		const typename Traits<Out0>::IVal		reference0;
+		const typename Traits<Out1>::IVal		reference1;
+
+		env.bind(*variables.in0, in0);
+		env.bind(*variables.in1, in1);
+		env.bind(*variables.in2, in2);
+		env.bind(*variables.in3, in3);
+		env.bind(*variables.out0, reference0);
+		env.bind(*variables.out1, reference1);
+	}
+
+	// For each input tuple, compute output reference interval and compare
+	// shader output to the reference.
+	for (size_t valueNdx = 0; valueNdx < numValues; valueNdx++)
+	{
+		bool						result		= true;
+		typename Traits<Out0>::IVal	reference0;
+		typename Traits<Out1>::IVal	reference1;
+
+		env.lookup(*variables.in0) = convert<In0>(fmt, round(fmt, inputs.in0[valueNdx]));
+		env.lookup(*variables.in1) = convert<In1>(fmt, round(fmt, inputs.in1[valueNdx]));
+		env.lookup(*variables.in2) = convert<In2>(fmt, round(fmt, inputs.in2[valueNdx]));
+		env.lookup(*variables.in3) = convert<In3>(fmt, round(fmt, inputs.in3[valueNdx]));
+
+		{
+			EvalContext	ctx (fmt, m_ctx.precision, env);
+			stmt.execute(ctx);
+		}
+
+		switch (outCount)
+		{
+			case 2:
+				reference1 = convert<Out1>(highpFmt, env.lookup(*variables.out1));
+				if (!m_status.check(contains(reference1, outputs.out1[valueNdx]),
+									"Shader output 1 is outside acceptable range"))
+					result = false;
+			case 1:
+				reference0 = convert<Out0>(highpFmt, env.lookup(*variables.out0));
+				if (!m_status.check(contains(reference0, outputs.out0[valueNdx]),
+									"Shader output 0 is outside acceptable range"))
+					result = false;
+			default: break;
+		}
+
+		if (!result)
+			++numErrors;
+
+		if ((!result && numErrors <= maxMsgs) || GLS_LOG_ALL_RESULTS)
+		{
+			MessageBuilder	builder	= log().message();
+
+			builder << (result ? "Passed" : "Failed") << " sample:\n";
+
+			if (inCount > 0)
+			{
+				builder << "\t" << variables.in0->getName() << " = "
+						<< valueToString(highpFmt, inputs.in0[valueNdx]) << "\n";
+			}
+
+			if (inCount > 1)
+			{
+				builder << "\t" << variables.in1->getName() << " = "
+						<< valueToString(highpFmt, inputs.in1[valueNdx]) << "\n";
+			}
+
+			if (inCount > 2)
+			{
+				builder << "\t" << variables.in2->getName() << " = "
+						<< valueToString(highpFmt, inputs.in2[valueNdx]) << "\n";
+			}
+
+			if (inCount > 3)
+			{
+				builder << "\t" << variables.in3->getName() << " = "
+						<< valueToString(highpFmt, inputs.in3[valueNdx]) << "\n";
+			}
+
+			if (outCount > 0)
+			{
+				builder << "\t" << variables.out0->getName() << " = "
+						<< valueToString(highpFmt, outputs.out0[valueNdx]) << "\n"
+						<< "\tExpected range: "
+						<< intervalToString<typename Out::Out0>(highpFmt, reference0) << "\n";
+			}
+
+			if (outCount > 1)
+			{
+				builder << "\t" << variables.out1->getName() << " = "
+						<< valueToString(highpFmt, outputs.out1[valueNdx]) << "\n"
+						<< "\tExpected range: "
+						<< intervalToString<typename Out::Out1>(highpFmt, reference1) << "\n";
+			}
+
+			builder << TestLog::EndMessage;
+		}
+	}
+
+	if (numErrors > maxMsgs)
+	{
+		log() << TestLog::Message << "(Skipped " << (numErrors - maxMsgs) << " messages.)"
+			  << TestLog::EndMessage;
+	}
+
+	if (numErrors == 0)
+	{
+		log() << TestLog::Message << "All " << numValues << " inputs passed."
+			  << TestLog::EndMessage;
+	}
+	else
+	{
+		log() << TestLog::Message << numErrors << "/" << numValues << " inputs failed."
+			  << TestLog::EndMessage;
+	}
+}
+
+
+
+template <typename T>
+struct InputLess
+{
+	bool operator() (const T& val1, const T& val2) const
+	{
+		return val1 < val2;
+	}
+};
+
+template <typename T>
+bool inputLess (const T& val1, const T& val2)
+{
+	return InputLess<T>()(val1, val2);
+}
+
+template <>
+struct InputLess<float>
+{
+	bool operator() (const float& val1, const float& val2) const
+	{
+		if (deIsNaN(val1))
+			return false;
+		if (deIsNaN(val2))
+			return true;
+		return val1 < val2;
+	}
+};
+
+template <typename T, int Size>
+struct InputLess<Vector<T, Size> >
+{
+	bool operator() (const Vector<T, Size>& vec1, const Vector<T, Size>& vec2) const
+	{
+		for (int ndx = 0; ndx < Size; ++ndx)
+		{
+			if (inputLess(vec1[ndx], vec2[ndx]))
+				return true;
+			if (inputLess(vec2[ndx], vec1[ndx]))
+				return false;
+		}
+
+		return false;
+	}
+};
+
+template <typename T, int Rows, int Cols>
+struct InputLess<Matrix<T, Rows, Cols> >
+{
+	bool operator() (const Matrix<T, Rows, Cols>& mat1,
+					 const Matrix<T, Rows, Cols>& mat2) const
+	{
+		for (int col = 0; col < Cols; ++col)
+		{
+			if (inputLess(mat1[col], mat2[col]))
+				return true;
+			if (inputLess(mat2[col], mat1[col]))
+				return false;
+		}
+
+		return false;
+	}
+};
+
+template <typename In>
+struct InTuple :
+	public Tuple4<typename In::In0, typename In::In1, typename In::In2, typename In::In3>
+{
+	InTuple	(const typename In::In0& in0,
+			 const typename In::In1& in1,
+			 const typename In::In2& in2,
+			 const typename In::In3& in3)
+		: Tuple4<typename In::In0, typename In::In1, typename In::In2, typename In::In3>
+		  (in0, in1, in2, in3) {}
+};
+
+template <typename In>
+struct InputLess<InTuple<In> >
+{
+	bool operator() (const InTuple<In>& in1, const InTuple<In>& in2) const
+	{
+		if (inputLess(in1.a, in2.a))
+			return true;
+		if (inputLess(in2.a, in1.a))
+			return false;
+		if (inputLess(in1.b, in2.b))
+			return true;
+		if (inputLess(in2.b, in1.b))
+			return false;
+		if (inputLess(in1.c, in2.c))
+			return true;
+		if (inputLess(in2.c, in1.c))
+			return false;
+		if (inputLess(in1.d, in2.d))
+			return true;
+		return false;
+	};
+};
+
+template<typename In>
+Inputs<In> generateInputs (const Samplings<In>&	samplings,
+						   const FloatFormat&	floatFormat,
+						   size_t				numSamples,
+						   Random&				rnd)
+{
+	Inputs<In>									ret;
+	Inputs<In>									fixedInputs;
+	set<InTuple<In>, InputLess<InTuple<In> > >	seenInputs;
+
+	samplings.in0.genFixeds(floatFormat, fixedInputs.in0);
+	samplings.in1.genFixeds(floatFormat, fixedInputs.in1);
+	samplings.in2.genFixeds(floatFormat, fixedInputs.in2);
+	samplings.in3.genFixeds(floatFormat, fixedInputs.in3);
+
+	for (size_t ndx0 = 0; ndx0 < fixedInputs.in0.size(); ++ndx0)
+	{
+		for (size_t ndx1 = 0; ndx1 < fixedInputs.in1.size(); ++ndx1)
+		{
+			for (size_t ndx2 = 0; ndx2 < fixedInputs.in2.size(); ++ndx2)
+			{
+				for (size_t ndx3 = 0; ndx3 < fixedInputs.in3.size(); ++ndx3)
+				{
+					const InTuple<In>	tuple	(fixedInputs.in0[ndx0],
+												 fixedInputs.in1[ndx1],
+												 fixedInputs.in2[ndx2],
+												 fixedInputs.in3[ndx3]);
+
+					seenInputs.insert(tuple);
+					ret.in0.push_back(tuple.a);
+					ret.in1.push_back(tuple.b);
+					ret.in2.push_back(tuple.c);
+					ret.in3.push_back(tuple.d);
+				}
+			}
+		}
+	}
+
+	for (size_t ndx = 0; ndx < numSamples; ++ndx)
+	{
+		const typename In::In0	in0		= samplings.in0.genRandom(floatFormat, rnd);
+		const typename In::In1	in1		= samplings.in1.genRandom(floatFormat, rnd);
+		const typename In::In2	in2		= samplings.in2.genRandom(floatFormat, rnd);
+		const typename In::In3	in3		= samplings.in3.genRandom(floatFormat, rnd);
+		const InTuple<In>		tuple	(in0, in1, in2, in3);
+
+		if (de::contains(seenInputs, tuple))
+			continue;
+
+		seenInputs.insert(tuple);
+		ret.in0.push_back(in0);
+		ret.in1.push_back(in1);
+		ret.in2.push_back(in2);
+		ret.in3.push_back(in3);
+	}
+
+	return ret;
+}
+
+class FuncCaseBase : public PrecisionCase
+{
+public:
+	IterateResult	iterate			(void);
+
+protected:
+					FuncCaseBase	(const Context&		context,
+									 const string&		name,
+									 const FuncBase&	func)
+						: PrecisionCase	(context, name)
+						, m_extension	(func.getRequiredExtension()) {}
+
+	const string	m_extension;
+};
+
+IterateResult FuncCaseBase::iterate (void)
+{
+	MovePtr<ContextInfo>	info	(ContextInfo::create(getRenderContext()));
+
+	if (!m_extension.empty() && !info->isExtensionSupported(m_extension.c_str()))
+		throw NotSupportedError("Unsupported extension: " + m_extension);
+
+	runTest();
+
+	m_status.setTestContextResult(m_testCtx);
+	return STOP;
+}
+
+template <typename Sig>
+class FuncCase : public FuncCaseBase
+{
+public:
+	typedef Func<Sig>						CaseFunc;
+	typedef typename Sig::Ret				Ret;
+	typedef typename Sig::Arg0				Arg0;
+	typedef typename Sig::Arg1				Arg1;
+	typedef typename Sig::Arg2				Arg2;
+	typedef typename Sig::Arg3				Arg3;
+	typedef InTypes<Arg0, Arg1, Arg2, Arg3>	In;
+	typedef OutTypes<Ret>					Out;
+
+					FuncCase	(const Context&		context,
+								 const string&		name,
+								 const CaseFunc&	func)
+						: FuncCaseBase	(context, name, func)
+						, m_func		(func) {}
+
+protected:
+	void				runTest		(void);
+
+	virtual const Samplings<In>&	getSamplings	(void)
+	{
+		return instance<DefaultSamplings<In> >();
+	}
+
+private:
+	const CaseFunc&			m_func;
+};
+
+template <typename Sig>
+void FuncCase<Sig>::runTest (void)
+{
+	const Inputs<In>	inputs	(generateInputs(getSamplings(),
+												m_ctx.floatFormat,
+												m_ctx.numRandoms,
+												m_rnd));
+	Variables<In, Out>	variables;
+
+	variables.out0	= variable<Ret>("out0");
+	variables.out1	= variable<Void>("out1");
+	variables.in0	= variable<Arg0>("in0");
+	variables.in1	= variable<Arg1>("in1");
+	variables.in2	= variable<Arg2>("in2");
+	variables.in3	= variable<Arg3>("in3");
+
+	{
+		ExprP<Ret>	expr	= applyVar(m_func,
+									   variables.in0, variables.in1,
+									   variables.in2, variables.in3);
+		StatementP	stmt	= variableAssignment(variables.out0, expr);
+
+		this->testStatement(variables, inputs, *stmt);
+	}
+}
+
+template <typename Sig>
+class InOutFuncCase : public FuncCaseBase
+{
+public:
+	typedef Func<Sig>						CaseFunc;
+	typedef typename Sig::Ret				Ret;
+	typedef typename Sig::Arg0				Arg0;
+	typedef typename Sig::Arg1				Arg1;
+	typedef typename Sig::Arg2				Arg2;
+	typedef typename Sig::Arg3				Arg3;
+	typedef InTypes<Arg0, Arg2, Arg3>		In;
+	typedef OutTypes<Ret, Arg1>				Out;
+
+					InOutFuncCase	(const Context&		context,
+									 const string&		name,
+									 const CaseFunc&	func)
+						: FuncCaseBase	(context, name, func)
+						, m_func		(func) {}
+
+protected:
+	void				runTest		(void);
+
+	virtual const Samplings<In>&	getSamplings	(void)
+	{
+		return instance<DefaultSamplings<In> >();
+	}
+
+private:
+	const CaseFunc&			m_func;
+};
+
+template <typename Sig>
+void InOutFuncCase<Sig>::runTest (void)
+{
+	const Inputs<In>	inputs	(generateInputs(getSamplings(),
+												m_ctx.floatFormat,
+												m_ctx.numRandoms,
+												m_rnd));
+	Variables<In, Out>	variables;
+
+	variables.out0	= variable<Ret>("out0");
+	variables.out1	= variable<Arg1>("out1");
+	variables.in0	= variable<Arg0>("in0");
+	variables.in1	= variable<Arg2>("in1");
+	variables.in2	= variable<Arg3>("in2");
+	variables.in3	= variable<Void>("in3");
+
+	{
+		ExprP<Ret>	expr	= applyVar(m_func,
+									   variables.in0, variables.out1,
+									   variables.in1, variables.in2);
+		StatementP	stmt	= variableAssignment(variables.out0, expr);
+
+		this->testStatement(variables, inputs, *stmt);
+	}
+}
+
+template <typename Sig>
+PrecisionCase* createFuncCase (const Context&	context,
+							   const string&	name,
+							   const Func<Sig>&	func)
+{
+	switch (func.getOutParamIndex())
+	{
+		case -1:
+			return new FuncCase<Sig>(context, name, func);
+		case 1:
+			return new InOutFuncCase<Sig>(context, name, func);
+		default:
+			DE_ASSERT(!"Impossible");
+	}
+	return DE_NULL;
+}
+
+class CaseFactory
+{
+public:
+	virtual						~CaseFactory	(void) {}
+	virtual MovePtr<TestNode>	createCase		(const Context& ctx) const = 0;
+	virtual string				getName			(void) const = 0;
+	virtual string				getDesc			(void) const = 0;
+};
+
+class FuncCaseFactory : public CaseFactory
+{
+public:
+	virtual const FuncBase&	getFunc		(void) const = 0;
+
+	string					getName		(void) const
+	{
+		return de::toLower(getFunc().getName());
+	}
+
+	string					getDesc		(void) const
+	{
+		return "Function '" + getFunc().getName() + "'";
+	}
+};
+
+template <typename Sig>
+class GenFuncCaseFactory : public CaseFactory
+{
+public:
+
+						GenFuncCaseFactory	(const GenFuncs<Sig>&	funcs,
+											 const string&			name)
+							: m_funcs	(funcs)
+							, m_name	(de::toLower(name)) {}
+
+	MovePtr<TestNode>	createCase			(const Context& ctx) const
+	{
+		TestCaseGroup*	group = new TestCaseGroup(ctx.testContext,
+												  ctx.name.c_str(), ctx.name.c_str());
+
+		group->addChild(createFuncCase(ctx, "scalar", m_funcs.func));
+		group->addChild(createFuncCase(ctx, "vec2", m_funcs.func2));
+		group->addChild(createFuncCase(ctx, "vec3", m_funcs.func3));
+		group->addChild(createFuncCase(ctx, "vec4", m_funcs.func4));
+
+		return MovePtr<TestNode>(group);
+	}
+
+	string				getName				(void) const
+	{
+		return m_name;
+	}
+
+	string				getDesc				(void) const
+	{
+		return "Function '" + m_funcs.func.getName() + "'";
+	}
+
+private:
+	const GenFuncs<Sig>	m_funcs;
+	string				m_name;
+};
+
+template <template <int> class GenF>
+class TemplateFuncCaseFactory : public FuncCaseFactory
+{
+public:
+	MovePtr<TestNode>	createCase		(const Context& ctx) const
+	{
+		TestCaseGroup*	group = new TestCaseGroup(ctx.testContext,
+							  ctx.name.c_str(), ctx.name.c_str());
+		group->addChild(createFuncCase(ctx, "scalar", instance<GenF<1> >()));
+		group->addChild(createFuncCase(ctx, "vec2", instance<GenF<2> >()));
+		group->addChild(createFuncCase(ctx, "vec3", instance<GenF<3> >()));
+		group->addChild(createFuncCase(ctx, "vec4", instance<GenF<4> >()));
+
+		return MovePtr<TestNode>(group);
+	}
+
+	const FuncBase&		getFunc			(void) const { return instance<GenF<1> >(); }
+};
+
+template <template <int> class GenF>
+class SquareMatrixFuncCaseFactory : public FuncCaseFactory
+{
+public:
+	MovePtr<TestNode>	createCase		(const Context& ctx) const
+	{
+		TestCaseGroup*	group = new TestCaseGroup(ctx.testContext,
+							  ctx.name.c_str(), ctx.name.c_str());
+		group->addChild(createFuncCase(ctx, "mat2", instance<GenF<2> >()));
+#if 0
+		// disabled until we get reasonable results
+		group->addChild(createFuncCase(ctx, "mat3", instance<GenF<3> >()));
+		group->addChild(createFuncCase(ctx, "mat4", instance<GenF<4> >()));
+#endif
+
+		return MovePtr<TestNode>(group);
+	}
+
+	const FuncBase&		getFunc			(void) const { return instance<GenF<2> >(); }
+};
+
+template <template <int, int> class GenF>
+class MatrixFuncCaseFactory : public FuncCaseFactory
+{
+public:
+	MovePtr<TestNode>	createCase		(const Context& ctx) const
+	{
+		TestCaseGroup*	const group = new TestCaseGroup(ctx.testContext,
+														ctx.name.c_str(), ctx.name.c_str());
+
+		this->addCase<2, 2>(ctx, group);
+		this->addCase<3, 2>(ctx, group);
+		this->addCase<4, 2>(ctx, group);
+		this->addCase<2, 3>(ctx, group);
+		this->addCase<3, 3>(ctx, group);
+		this->addCase<4, 3>(ctx, group);
+		this->addCase<2, 4>(ctx, group);
+		this->addCase<3, 4>(ctx, group);
+		this->addCase<4, 4>(ctx, group);
+
+		return MovePtr<TestNode>(group);
+	}
+
+	const FuncBase&		getFunc			(void) const { return instance<GenF<2,2> >(); }
+
+private:
+	template <int Rows, int Cols>
+	void				addCase			(const Context& ctx, TestCaseGroup* group) const
+	{
+		const char*	const name = dataTypeNameOf<Matrix<float, Rows, Cols> >();
+
+		group->addChild(createFuncCase(ctx, name, instance<GenF<Rows, Cols> >()));
+	}
+};
+
+template <typename Sig>
+class SimpleFuncCaseFactory : public CaseFactory
+{
+public:
+						SimpleFuncCaseFactory	(const Func<Sig>& func) : m_func(func) {}
+
+	MovePtr<TestNode>	createCase		(const Context& ctx) const
+	{
+		return MovePtr<TestNode>(createFuncCase(ctx, ctx.name.c_str(), m_func));
+	}
+
+	string				getName					(void) const
+	{
+		return de::toLower(m_func.getName());
+	}
+
+	string				getDesc					(void) const
+	{
+		return "Function '" + getName() + "'";
+	}
+
+private:
+	const Func<Sig>&	m_func;
+};
+
+template <typename F>
+SharedPtr<SimpleFuncCaseFactory<typename F::Sig> > createSimpleFuncCaseFactory (void)
+{
+	return SharedPtr<SimpleFuncCaseFactory<typename F::Sig> >(
+		new SimpleFuncCaseFactory<typename F::Sig>(instance<F>()));
+}
+
+class BuiltinFuncs : public CaseFactories
+{
+public:
+	const vector<const CaseFactory*>	getFactories	(void) const
+	{
+		vector<const CaseFactory*> ret;
+
+		for (size_t ndx = 0; ndx < m_factories.size(); ++ndx)
+			ret.push_back(m_factories[ndx].get());
+
+		return ret;
+	}
+
+	void								addFactory		(SharedPtr<const CaseFactory> fact)
+	{
+		m_factories.push_back(fact);
+	}
+
+private:
+	vector<SharedPtr<const CaseFactory> >			m_factories;
+};
+
+template <typename F>
+void addScalarFactory(BuiltinFuncs& funcs, string name = "")
+{
+	if (name.empty())
+		name = instance<F>().getName();
+
+	funcs.addFactory(SharedPtr<const CaseFactory>(new GenFuncCaseFactory<typename F::Sig>(
+													  makeVectorizedFuncs<F>(), name)));
+}
+
+MovePtr<const CaseFactories> createES3BuiltinCases (void)
+{
+	MovePtr<BuiltinFuncs>	funcs	(new BuiltinFuncs());
+
+	addScalarFactory<Add>(*funcs);
+	addScalarFactory<Sub>(*funcs);
+	addScalarFactory<Mul>(*funcs);
+	addScalarFactory<Div>(*funcs);
+
+	addScalarFactory<Radians>(*funcs);
+	addScalarFactory<Degrees>(*funcs);
+	addScalarFactory<Sin>(*funcs);
+	addScalarFactory<Cos>(*funcs);
+	addScalarFactory<Tan>(*funcs);
+	addScalarFactory<ASin>(*funcs);
+	addScalarFactory<ACos>(*funcs);
+	addScalarFactory<ATan2>(*funcs, "atan2");
+	addScalarFactory<ATan>(*funcs);
+	addScalarFactory<Sinh>(*funcs);
+	addScalarFactory<Cosh>(*funcs);
+	addScalarFactory<Tanh>(*funcs);
+	addScalarFactory<ASinh>(*funcs);
+	addScalarFactory<ACosh>(*funcs);
+	addScalarFactory<ATanh>(*funcs);
+
+	addScalarFactory<Pow>(*funcs);
+	addScalarFactory<Exp>(*funcs);
+	addScalarFactory<Log>(*funcs);
+	addScalarFactory<Exp2>(*funcs);
+	addScalarFactory<Log2>(*funcs);
+	addScalarFactory<Sqrt>(*funcs);
+	addScalarFactory<InverseSqrt>(*funcs);
+
+	addScalarFactory<Abs>(*funcs);
+	addScalarFactory<Sign>(*funcs);
+	addScalarFactory<Floor>(*funcs);
+	addScalarFactory<Trunc>(*funcs);
+	addScalarFactory<Round>(*funcs);
+	addScalarFactory<RoundEven>(*funcs);
+	addScalarFactory<Ceil>(*funcs);
+	addScalarFactory<Fract>(*funcs);
+	addScalarFactory<Mod>(*funcs);
+	funcs->addFactory(createSimpleFuncCaseFactory<Modf>());
+	addScalarFactory<Min>(*funcs);
+	addScalarFactory<Max>(*funcs);
+	addScalarFactory<Clamp>(*funcs);
+	addScalarFactory<Mix>(*funcs);
+	addScalarFactory<Step>(*funcs);
+	addScalarFactory<SmoothStep>(*funcs);
+
+	funcs->addFactory(SharedPtr<const CaseFactory>(new TemplateFuncCaseFactory<Length>()));
+	funcs->addFactory(SharedPtr<const CaseFactory>(new TemplateFuncCaseFactory<Distance>()));
+	funcs->addFactory(SharedPtr<const CaseFactory>(new TemplateFuncCaseFactory<Dot>()));
+	funcs->addFactory(createSimpleFuncCaseFactory<Cross>());
+	funcs->addFactory(SharedPtr<const CaseFactory>(new TemplateFuncCaseFactory<Normalize>()));
+	funcs->addFactory(SharedPtr<const CaseFactory>(new TemplateFuncCaseFactory<FaceForward>()));
+	funcs->addFactory(SharedPtr<const CaseFactory>(new TemplateFuncCaseFactory<Reflect>()));
+	funcs->addFactory(SharedPtr<const CaseFactory>(new TemplateFuncCaseFactory<Refract>()));
+
+
+	funcs->addFactory(SharedPtr<const CaseFactory>(new MatrixFuncCaseFactory<MatrixCompMult>()));
+	funcs->addFactory(SharedPtr<const CaseFactory>(new MatrixFuncCaseFactory<OuterProduct>()));
+	funcs->addFactory(SharedPtr<const CaseFactory>(new MatrixFuncCaseFactory<Transpose>()));
+	funcs->addFactory(SharedPtr<const CaseFactory>(new SquareMatrixFuncCaseFactory<Determinant>()));
+	funcs->addFactory(SharedPtr<const CaseFactory>(new SquareMatrixFuncCaseFactory<Inverse>()));
+
+	return MovePtr<const CaseFactories>(funcs.release());
+}
+
+MovePtr<const CaseFactories> createES31BuiltinCases (void)
+{
+	MovePtr<BuiltinFuncs>	funcs	(new BuiltinFuncs());
+
+	addScalarFactory<FrExp>(*funcs);
+	addScalarFactory<LdExp>(*funcs);
+	addScalarFactory<Fma>(*funcs);
+
+	return MovePtr<const CaseFactories>(funcs.release());
+}
+
+struct PrecisionTestContext
+{
+	PrecisionTestContext	(TestContext&				testCtx_,
+							 RenderContext&				renderCtx_,
+							 const FloatFormat&			highp_,
+							 const FloatFormat&			mediump_,
+							 const FloatFormat&			lowp_,
+							 const vector<ShaderType>&	shaderTypes_,
+							 int						numRandoms_)
+		: testCtx		(testCtx_)
+		, renderCtx		(renderCtx_)
+		, shaderTypes	(shaderTypes_)
+		, numRandoms	(numRandoms_)
+	{
+		formats[glu::PRECISION_HIGHP]	= &highp_;
+		formats[glu::PRECISION_MEDIUMP]	= &mediump_;
+		formats[glu::PRECISION_LOWP]	= &lowp_;
+	}
+
+	TestContext&			testCtx;
+	RenderContext&			renderCtx;
+	const FloatFormat*		formats[glu::PRECISION_LAST];
+	vector<ShaderType>		shaderTypes;
+	int						numRandoms;
+};
+
+TestCaseGroup* createFuncGroup (const PrecisionTestContext&	ctx,
+								const CaseFactory&			factory)
+{
+	TestCaseGroup* const 	group	= new TestCaseGroup(ctx.testCtx,
+														factory.getName().c_str(),
+														factory.getDesc().c_str());
+
+	for (int precNdx = 0; precNdx < glu::PRECISION_LAST; ++precNdx)
+	{
+		const Precision		precision	= Precision(precNdx);
+		const string		precName	(glu::getPrecisionName(precision));
+		const FloatFormat&	fmt			= *de::getSizedArrayElement(ctx.formats, precNdx);
+		const FloatFormat&	highpFmt	= *de::getSizedArrayElement(ctx.formats,
+																	glu::PRECISION_HIGHP);
+
+		for (size_t shaderNdx = 0; shaderNdx < ctx.shaderTypes.size(); ++shaderNdx)
+		{
+			const ShaderType	shaderType	= ctx.shaderTypes[shaderNdx];
+			const string 		shaderName	(glu::getShaderTypeName(shaderType));
+			const string		name		= precName + "_" + shaderName;
+			const Context		caseCtx		(name, ctx.testCtx, ctx.renderCtx, fmt, highpFmt,
+											 precision, shaderType, ctx.numRandoms);
+
+			group->addChild(factory.createCase(caseCtx).release());
+		}
+	}
+
+	return group;
+}
+
+void addBuiltinPrecisionTests (TestContext&					testCtx,
+							   RenderContext&				renderCtx,
+							   const CaseFactories&			cases,
+							   const vector<ShaderType>&	shaderTypes,
+							   TestCaseGroup&				dstGroup)
+{
+	const FloatFormat				highp		(-126, 127, 23, true,
+												 tcu::MAYBE,	// subnormals
+												 tcu::YES,		// infinities
+												 tcu::MAYBE);	// NaN
+	// \todo [2014-04-01 lauri] Check these once Khronos bug 11840 is resolved.
+	const FloatFormat				mediump		(-13, 13, 9, false);
+	// A fixed-point format is just a floating point format with a fixed
+	// exponent and support for subnormals.
+	const FloatFormat				lowp		(0, 0, 7, false, tcu::YES);
+	const PrecisionTestContext		ctx			(testCtx, renderCtx, highp, mediump, lowp,
+												 shaderTypes, 16384);
+
+	for (size_t ndx = 0; ndx < cases.getFactories().size(); ++ndx)
+		dstGroup.addChild(createFuncGroup(ctx, *cases.getFactories()[ndx]));
+}
+
+} // BuiltinPrecisionTests
+} // gls
+} // deqp
diff --git a/modules/glshared/glsBuiltinPrecisionTests.hpp b/modules/glshared/glsBuiltinPrecisionTests.hpp
new file mode 100644
index 0000000..2088f7b
--- /dev/null
+++ b/modules/glshared/glsBuiltinPrecisionTests.hpp
@@ -0,0 +1,67 @@
+#ifndef _GLSBUILTINPRECISIONTESTS_HPP
+#define _GLSBUILTINPRECISIONTESTS_HPP
+
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Tests for precision and range of GLSL builtins and types.
+ *//*--------------------------------------------------------------------*/
+
+#include "deUniquePtr.hpp"
+#include "tcuTestCase.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderUtil.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gls
+{
+namespace BuiltinPrecisionTests
+{
+
+class CaseFactory;
+
+class CaseFactories
+{
+public:
+	virtual											~CaseFactories	(void) {}
+	virtual const std::vector<const CaseFactory*>	getFactories	(void) const = 0;
+};
+
+de::MovePtr<const CaseFactories>		createES3BuiltinCases		(void);
+de::MovePtr<const CaseFactories>		createES31BuiltinCases		(void);
+
+void							addBuiltinPrecisionTests (
+	tcu::TestContext&					testCtx,
+	glu::RenderContext&					renderCtx,
+	const CaseFactories&				cases,
+	const std::vector<glu::ShaderType>&	shaderTypes,
+	tcu::TestCaseGroup&					dstGroup);
+
+} // BuiltinPrecisionTests
+
+using BuiltinPrecisionTests::addBuiltinPrecisionTests;
+
+} // gls
+} // deqp
+
+#endif // _GLSBUILTINPRECISIONTESTS_HPP
diff --git a/modules/glshared/glsCalibration.cpp b/modules/glshared/glsCalibration.cpp
new file mode 100644
index 0000000..e204774
--- /dev/null
+++ b/modules/glshared/glsCalibration.cpp
@@ -0,0 +1,332 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Calibration tools.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsCalibration.hpp"
+#include "tcuTestLog.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+#include "deClock.h"
+
+#include <algorithm>
+#include <limits>
+
+using std::string;
+using std::vector;
+using tcu::Vec2;
+using tcu::TestLog;
+using tcu::TestNode;
+using namespace glu;
+
+namespace deqp
+{
+namespace gls
+{
+
+LineParameters theilSenEstimator (const std::vector<tcu::Vec2>& dataPoints)
+{
+	const float		epsilon					= 1e-6f;
+
+	const int		numDataPoints			= (int)dataPoints.size();
+	vector<float>	pairwiseCoefficients;
+	vector<float>	pointwiseOffsets;
+	LineParameters	result					(0.0f, 0.0f);
+
+	// Compute the pairwise coefficients.
+	for (int i = 0; i < numDataPoints; i++)
+	{
+		const Vec2& ptA = dataPoints[i];
+
+		for (int j = 0; j < i; j++)
+		{
+			const Vec2& ptB = dataPoints[j];
+
+			if (de::abs(ptA.x() - ptB.x()) > epsilon)
+				pairwiseCoefficients.push_back((ptA.y() - ptB.y()) / (ptA.x() - ptB.x()));
+		}
+	}
+
+	// Find the median of the pairwise coefficients.
+	// \note If there are no data point pairs with differing x values, the coefficient variable will stay zero as initialized.
+	std::sort(pairwiseCoefficients.begin(), pairwiseCoefficients.end());
+	int numCoeffs = (int)pairwiseCoefficients.size();
+	if (numCoeffs > 0)
+		result.coefficient = numCoeffs % 2 == 0
+						   ? (pairwiseCoefficients[numCoeffs/2 - 1] + pairwiseCoefficients[numCoeffs/2]) / 2
+						   : pairwiseCoefficients[numCoeffs/2];
+
+	// Compute the offsets corresponding to the median coefficient, for all data points.
+	for (int i = 0; i < numDataPoints; i++)
+		pointwiseOffsets.push_back(dataPoints[i].y() - result.coefficient*dataPoints[i].x());
+
+	// Find the median of the offsets.
+	// \note If there are no data points, the offset variable will stay zero as initialized.
+	std::sort(pointwiseOffsets.begin(), pointwiseOffsets.end());
+	int numOffsets = (int)pointwiseOffsets.size();
+	if (numOffsets > 0)
+		result.offset = numOffsets % 2 == 0
+					  ? (pointwiseOffsets[numOffsets/2 - 1] + pointwiseOffsets[numOffsets/2]) / 2
+					  : pointwiseOffsets[numOffsets/2];
+
+	return result;
+}
+
+bool MeasureState::isDone (void) const
+{
+	return (int)frameTimes.size() >= maxNumFrames || (frameTimes.size() >= 2 &&
+													  frameTimes[frameTimes.size()-2] >= (deUint64)frameShortcutTime &&
+													  frameTimes[frameTimes.size()-1] >= (deUint64)frameShortcutTime);
+}
+
+deUint64 MeasureState::getTotalTime (void) const
+{
+	deUint64 time = 0;
+	for (int i = 0; i < (int)frameTimes.size(); i++)
+		time += frameTimes[i];
+	return time;
+}
+
+void MeasureState::clear (void)
+{
+	maxNumFrames		= 0;
+	frameShortcutTime	= std::numeric_limits<float>::infinity();
+	numDrawCalls		= 0;
+	frameTimes.clear();
+}
+
+void MeasureState::start (int maxNumFrames_, float frameShortcutTime_, int numDrawCalls_)
+{
+	frameTimes.clear();
+	frameTimes.reserve(maxNumFrames_);
+	maxNumFrames		= maxNumFrames_;
+	frameShortcutTime	= frameShortcutTime_;
+	numDrawCalls		= numDrawCalls_;
+}
+
+TheilSenCalibrator::TheilSenCalibrator (void)
+	: m_params	(1 /* initial calls */, 10 /* calibrate iter frames */, 2000.0f /* calibrate iter shortcut threshold */, 31 /* max calibration iterations */,
+				 1000.0f/30.0f /* target frame time */, 1000.0f/60.0f /* frame time cap */, 1000.0f /* target measure duration */)
+	, m_state	(INTERNALSTATE_LAST)
+{
+	clear();
+}
+
+TheilSenCalibrator::TheilSenCalibrator (const CalibratorParameters& params)
+	: m_params	(params)
+	, m_state	(INTERNALSTATE_LAST)
+{
+	clear();
+}
+
+TheilSenCalibrator::~TheilSenCalibrator()
+{
+}
+
+void TheilSenCalibrator::clear (void)
+{
+	m_measureState.clear();
+	m_calibrateIterations.clear();
+	m_state = INTERNALSTATE_CALIBRATING;
+}
+
+void TheilSenCalibrator::clear (const CalibratorParameters& params)
+{
+	m_params = params;
+	clear();
+}
+
+TheilSenCalibrator::State TheilSenCalibrator::getState (void) const
+{
+	if (m_state == INTERNALSTATE_FINISHED)
+		return STATE_FINISHED;
+	else
+	{
+		DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING || !m_measureState.isDone());
+		return m_measureState.isDone() ? STATE_RECOMPUTE_PARAMS : STATE_MEASURE;
+	}
+}
+
+void TheilSenCalibrator::recordIteration (deUint64 iterationTime)
+{
+	DE_ASSERT((m_state == INTERNALSTATE_CALIBRATING || m_state == INTERNALSTATE_RUNNING) && !m_measureState.isDone());
+	m_measureState.frameTimes.push_back(iterationTime);
+
+	if (m_state == INTERNALSTATE_RUNNING && m_measureState.isDone())
+		m_state = INTERNALSTATE_FINISHED;
+}
+
+void TheilSenCalibrator::recomputeParameters (void)
+{
+	DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING);
+	DE_ASSERT(m_measureState.isDone());
+
+	// Minimum and maximum acceptable frame times.
+	const float		minGoodFrameTimeUs	= m_params.targetFrameTimeUs * 0.95f;
+	const float		maxGoodFrameTimeUs	= m_params.targetFrameTimeUs * 1.15f;
+
+	const int		numIterations		= (int)m_calibrateIterations.size();
+
+	// Record frame time.
+	if (numIterations > 0)
+	{
+		m_calibrateIterations.back().frameTime = (float)((double)m_measureState.getTotalTime() / (double)m_measureState.frameTimes.size());
+
+		// Check if we're good enough to stop calibrating.
+		{
+			bool endCalibration = false;
+
+			// Is the maximum calibration iteration limit reached?
+			endCalibration = endCalibration || (int)m_calibrateIterations.size() >= m_params.maxCalibrateIterations;
+
+			// Do a few past iterations have frame time in acceptable range?
+			{
+				const int numRelevantPastIterations = 2;
+
+				if (!endCalibration && (int)m_calibrateIterations.size() >= numRelevantPastIterations)
+				{
+					const CalibrateIteration* const		past			= &m_calibrateIterations[m_calibrateIterations.size() - numRelevantPastIterations];
+					bool								allInGoodRange	= true;
+
+					for (int i = 0; i < numRelevantPastIterations && allInGoodRange; i++)
+					{
+						const float frameTimeUs = past[i].frameTime;
+						if (!de::inRange(frameTimeUs, minGoodFrameTimeUs, maxGoodFrameTimeUs))
+							allInGoodRange = false;
+					}
+
+					endCalibration = endCalibration || allInGoodRange;
+				}
+			}
+
+			// Do a few past iterations have similar-enough call counts?
+			{
+				const int numRelevantPastIterations = 3;
+				if (!endCalibration && (int)m_calibrateIterations.size() >= numRelevantPastIterations)
+				{
+					const CalibrateIteration* const		past			= &m_calibrateIterations[m_calibrateIterations.size() - numRelevantPastIterations];
+					int									minCallCount	= std::numeric_limits<int>::max();
+					int									maxCallCount	= std::numeric_limits<int>::min();
+
+					for (int i = 0; i < numRelevantPastIterations; i++)
+					{
+						minCallCount = de::min(minCallCount, past[i].numDrawCalls);
+						maxCallCount = de::max(maxCallCount, past[i].numDrawCalls);
+					}
+
+					if ((float)(maxCallCount - minCallCount) <= (float)minCallCount * 0.1f)
+						endCalibration = true;
+				}
+			}
+
+			// Is call count just 1, and frame time still way too high?
+			endCalibration = endCalibration || (m_calibrateIterations.back().numDrawCalls == 1 && m_calibrateIterations.back().frameTime > m_params.targetFrameTimeUs*2.0f);
+
+			if (endCalibration)
+			{
+				const int	minFrames			= 10;
+				const int	maxFrames			= 60;
+				int			numMeasureFrames	= deClamp32(deRoundFloatToInt32(m_params.targetMeasureDurationUs / m_calibrateIterations.back().frameTime), minFrames, maxFrames);
+
+				m_state = INTERNALSTATE_RUNNING;
+				m_measureState.start(numMeasureFrames, m_params.calibrateIterationShortcutThreshold, m_calibrateIterations.back().numDrawCalls);
+				return;
+			}
+		}
+	}
+
+	DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING);
+
+	// Estimate new call count.
+	{
+		int newCallCount;
+
+		if (numIterations == 0)
+			newCallCount = m_params.numInitialCalls;
+		else
+		{
+			vector<Vec2> dataPoints;
+			for (int i = 0; i < numIterations; i++)
+			{
+				if (m_calibrateIterations[i].numDrawCalls == 1 || m_calibrateIterations[i].frameTime > m_params.frameTimeCapUs*1.05f) // Only account for measurements not too near the cap.
+					dataPoints.push_back(Vec2((float)m_calibrateIterations[i].numDrawCalls, m_calibrateIterations[i].frameTime));
+			}
+
+			if (numIterations == 1)
+				dataPoints.push_back(Vec2(0.0f, 0.0f)); // If there's just one measurement so far, this will help in getting the next estimate.
+
+			{
+				const float				targetFrameTimeUs	= m_params.targetFrameTimeUs;
+				const float				coeffEpsilon		= 0.001f; // Coefficient must be large enough (and positive) to be considered sensible.
+
+				const LineParameters	estimatorLine		= theilSenEstimator(dataPoints);
+
+				int						prevMaxCalls		= 0;
+
+				// Find the maximum of the past call counts.
+				for (int i = 0; i < numIterations; i++)
+					prevMaxCalls = de::max(prevMaxCalls, m_calibrateIterations[i].numDrawCalls);
+
+				if (estimatorLine.coefficient < coeffEpsilon) // Coefficient not good for sensible estimation; increase call count enough to get a reasonably different value.
+					newCallCount = 2*prevMaxCalls;
+				else
+				{
+					// Solve newCallCount such that approximately targetFrameTime = offset + coefficient*newCallCount.
+					newCallCount = (int)((targetFrameTimeUs - estimatorLine.offset) / estimatorLine.coefficient + 0.5f);
+
+					// We should generally prefer FPS counts below the target rather than above (i.e. higher frame times rather than lower).
+					if (estimatorLine.offset + estimatorLine.coefficient*(float)newCallCount < minGoodFrameTimeUs)
+						newCallCount++;
+				}
+
+				// Make sure we have at least minimum amount of calls, and don't allow increasing call count too much in one iteration.
+				newCallCount = de::clamp(newCallCount, 1, prevMaxCalls*10);
+			}
+		}
+
+		m_measureState.start(m_params.maxCalibrateIterationFrames, m_params.calibrateIterationShortcutThreshold, newCallCount);
+		m_calibrateIterations.push_back(CalibrateIteration(newCallCount, 0.0f));
+	}
+}
+
+void logCalibrationInfo (tcu::TestLog& log, const TheilSenCalibrator& calibrator)
+{
+	const CalibratorParameters&				params				= calibrator.getParameters();
+	const std::vector<CalibrateIteration>&	calibrateIterations	= calibrator.getCalibrationInfo();
+
+	// Write out default calibration info.
+
+	log << TestLog::Section("CalibrationInfo", "Calibration Info")
+		<< TestLog::Message  << "Target frame time: " << params.targetFrameTimeUs << " us (" << 1000000 / params.targetFrameTimeUs << " fps)" << TestLog::EndMessage;
+
+	for (int iterNdx = 0; iterNdx < (int)calibrateIterations.size(); iterNdx++)
+	{
+		log << TestLog::Message << "  iteration " << iterNdx << ": " << calibrateIterations[iterNdx].numDrawCalls << " calls => "
+								<< de::floatToString(calibrateIterations[iterNdx].frameTime, 2) << " us ("
+								<< de::floatToString(1000000.0f / calibrateIterations[iterNdx].frameTime, 2) << " fps)" << TestLog::EndMessage;
+	}
+	log << TestLog::Integer("CallCount",	"Calibrated call count",	"",	QP_KEY_TAG_NONE, calibrator.getMeasureState().numDrawCalls)
+		<< TestLog::Integer("FrameCount",	"Calibrated frame count",	"", QP_KEY_TAG_NONE, (int)calibrator.getMeasureState().frameTimes.size());
+	log << TestLog::EndSection;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsCalibration.hpp b/modules/glshared/glsCalibration.hpp
new file mode 100644
index 0000000..c4b89c6
--- /dev/null
+++ b/modules/glshared/glsCalibration.hpp
@@ -0,0 +1,171 @@
+#ifndef _GLSCALIBRATION_HPP
+#define _GLSCALIBRATION_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Calibration tools.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVector.hpp"
+#include "gluRenderContext.hpp"
+
+#include <limits>
+
+namespace deqp
+{
+namespace gls
+{
+
+struct LineParameters
+{
+	float offset;
+	float coefficient;
+
+	LineParameters (float offset_, float coefficient_) : offset(offset_), coefficient(coefficient_) {}
+};
+
+LineParameters theilSenEstimator (const std::vector<tcu::Vec2>& dataPoints);
+
+struct MeasureState
+{
+	MeasureState (void)
+		: maxNumFrames			(0)
+		, frameShortcutTime		(std::numeric_limits<float>::infinity())
+		, numDrawCalls			(0)
+	{
+	}
+
+	void		clear				(void);
+	void		start				(int maxNumFrames, float frameShortcutTime, int numDrawCalls);
+
+	bool		isDone				(void) const;
+	deUint64	getTotalTime		(void) const;
+
+	int						maxNumFrames;
+	float					frameShortcutTime;
+	int						numDrawCalls;
+	std::vector<deUint64>	frameTimes;
+};
+
+struct CalibrateIteration
+{
+	CalibrateIteration (int numDrawCalls_, float frameTime_)
+		: numDrawCalls	(numDrawCalls_)
+		, frameTime		(frameTime_)
+	{
+	}
+
+	CalibrateIteration (void)
+		: numDrawCalls	(0)
+		, frameTime		(0.0f)
+	{
+	}
+
+	int		numDrawCalls;
+	float	frameTime;
+};
+
+struct CalibratorParameters
+{
+	CalibratorParameters (int		numInitialCalls_,
+						  int		maxCalibrateIterationFrames_,			//!< Maximum (and default) number of frames per one calibrate iteration.
+						  float		calibrateIterationShortcutThresholdMs_,	//!< If the times of two consecutive frames exceed this, stop the iteration even if maxCalibrateIterationFrames isn't reached.
+						  int		maxCalibrateIterations_,
+						  float		targetFrameTimeMs_,
+						  float		frameTimeCapMs_,
+						  float		targetMeasureDurationMs_)
+		: numInitialCalls						(numInitialCalls_)
+		, maxCalibrateIterationFrames			(maxCalibrateIterationFrames_)
+		, calibrateIterationShortcutThreshold	(1000.0f*calibrateIterationShortcutThresholdMs_)
+		, maxCalibrateIterations				(maxCalibrateIterations_)
+		, targetFrameTimeUs						(1000.0f*targetFrameTimeMs_)
+		, frameTimeCapUs						(1000.0f*frameTimeCapMs_)
+		, targetMeasureDurationUs				(1000.0f*targetMeasureDurationMs_)
+	{
+	}
+
+	int		numInitialCalls;
+	int		maxCalibrateIterationFrames;
+	float	calibrateIterationShortcutThreshold;
+	int		maxCalibrateIterations;
+	float	targetFrameTimeUs;
+	float	frameTimeCapUs;
+	float	targetMeasureDurationUs;
+};
+
+class TheilSenCalibrator
+{
+public:
+	enum State
+	{
+		STATE_RECOMPUTE_PARAMS = 0,
+		STATE_MEASURE,
+		STATE_FINISHED,
+
+		STATE_LAST
+	};
+
+											TheilSenCalibrator		(void);
+											TheilSenCalibrator		(const CalibratorParameters& params);
+											~TheilSenCalibrator		(void);
+
+	void									clear					(void);
+	void									clear					(const CalibratorParameters& params);
+
+	State									getState				(void) const;
+	int										getCallCount			(void) const { return m_measureState.numDrawCalls;	}
+
+	// Should be called when getState() returns STATE_RECOMPUTE_PARAMS
+	void									recomputeParameters		(void);
+
+	// Should be called when getState() returns STATE_MEASURE
+	void									recordIteration			(deUint64 frameTime);
+
+	const CalibratorParameters&				getParameters			(void) const { return m_params;					}
+	const MeasureState&						getMeasureState			(void) const { return m_measureState;			}
+	const std::vector<CalibrateIteration>&	getCalibrationInfo		(void) const { return m_calibrateIterations;	}
+
+private:
+	enum InternalState
+	{
+		INTERNALSTATE_CALIBRATING = 0,
+		INTERNALSTATE_RUNNING,
+		INTERNALSTATE_FINISHED,
+
+		INTERNALSTATE_LAST
+	};
+
+	CalibratorParameters					m_params;
+
+	InternalState							m_state;
+	MeasureState							m_measureState;
+
+	std::vector<CalibrateIteration>			m_calibrateIterations;
+};
+
+void logCalibrationInfo (tcu::TestLog& log, const TheilSenCalibrator& calibrator);
+
+} // gls
+} // deqp
+
+#endif // _GLSCALIBRATION_HPP
diff --git a/modules/glshared/glsDrawTest.cpp b/modules/glshared/glsDrawTest.cpp
new file mode 100644
index 0000000..64baf81
--- /dev/null
+++ b/modules/glshared/glsDrawTest.cpp
@@ -0,0 +1,3919 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Draw tests
+ *//*--------------------------------------------------------------------*/
+
+#include "glsDrawTest.hpp"
+
+#include "deRandom.h"
+#include "deRandom.hpp"
+#include "deMath.h"
+#include "deStringUtil.hpp"
+#include "deFloat16.h"
+#include "deUniquePtr.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuPixelFormat.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuSurface.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuFloat.hpp"
+#include "tcuTextureUtil.hpp"
+
+#include "gluPixelTransfer.hpp"
+#include "gluCallLogWrapper.hpp"
+
+#include "sglrContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "sglrGLContext.hpp"
+
+#include "rrGenericVector.hpp"
+
+#include <cstring>
+#include <cmath>
+#include <vector>
+#include <sstream>
+#include <limits>
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+
+namespace deqp
+{
+namespace gls
+{
+namespace
+{
+
+using tcu::TestLog;
+using namespace glw; // GL types
+
+const int MAX_RENDER_TARGET_SIZE = 512;
+
+// Utils
+
+static GLenum targetToGL (DrawTestSpec::Target target)
+{
+	DE_ASSERT(target < DrawTestSpec::TARGET_LAST);
+
+	static const GLenum targets[] =
+	{
+		GL_ELEMENT_ARRAY_BUFFER,	// TARGET_ELEMENT_ARRAY = 0,
+		GL_ARRAY_BUFFER				// TARGET_ARRAY,
+	};
+
+	return targets[(int)target];
+}
+
+static GLenum usageToGL (DrawTestSpec::Usage usage)
+{
+	DE_ASSERT(usage < DrawTestSpec::USAGE_LAST);
+
+	static const GLenum usages[] =
+	{
+		GL_DYNAMIC_DRAW,	// USAGE_DYNAMIC_DRAW = 0,
+		GL_STATIC_DRAW,		// USAGE_STATIC_DRAW,
+		GL_STREAM_DRAW,		// USAGE_STREAM_DRAW,
+
+		GL_STREAM_READ,		// USAGE_STREAM_READ,
+		GL_STREAM_COPY,		// USAGE_STREAM_COPY,
+
+		GL_STATIC_READ,		// USAGE_STATIC_READ,
+		GL_STATIC_COPY,		// USAGE_STATIC_COPY,
+
+		GL_DYNAMIC_READ,	// USAGE_DYNAMIC_READ,
+		GL_DYNAMIC_COPY		// USAGE_DYNAMIC_COPY,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(usages) == DrawTestSpec::USAGE_LAST);
+
+	return usages[(int)usage];
+}
+
+static GLenum inputTypeToGL (DrawTestSpec::InputType type)
+{
+	DE_ASSERT(type < DrawTestSpec::INPUTTYPE_LAST);
+
+	static const GLenum types[] =
+	{
+		GL_FLOAT,				// INPUTTYPE_FLOAT = 0,
+		GL_FIXED,				// INPUTTYPE_FIXED,
+		GL_DOUBLE,				// INPUTTYPE_DOUBLE
+		GL_BYTE,				// INPUTTYPE_BYTE,
+		GL_SHORT,				// INPUTTYPE_SHORT,
+		GL_UNSIGNED_BYTE,		// INPUTTYPE_UNSIGNED_BYTE,
+		GL_UNSIGNED_SHORT,		// INPUTTYPE_UNSIGNED_SHORT,
+
+		GL_INT,					// INPUTTYPE_INT,
+		GL_UNSIGNED_INT,		// INPUTTYPE_UNSIGNED_INT,
+		GL_HALF_FLOAT,			// INPUTTYPE_HALF,
+		GL_UNSIGNED_INT_2_10_10_10_REV, // INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		GL_INT_2_10_10_10_REV			// INPUTTYPE_INT_2_10_10_10,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == DrawTestSpec::INPUTTYPE_LAST);
+
+	return types[(int)type];
+}
+
+static std::string outputTypeToGLType (DrawTestSpec::OutputType type)
+{
+	DE_ASSERT(type < DrawTestSpec::OUTPUTTYPE_LAST);
+
+	static const char* types[] =
+	{
+		"float",		// OUTPUTTYPE_FLOAT = 0,
+		"vec2",			// OUTPUTTYPE_VEC2,
+		"vec3",			// OUTPUTTYPE_VEC3,
+		"vec4",			// OUTPUTTYPE_VEC4,
+
+		"int",			// OUTPUTTYPE_INT,
+		"uint",			// OUTPUTTYPE_UINT,
+
+		"ivec2",		// OUTPUTTYPE_IVEC2,
+		"ivec3",		// OUTPUTTYPE_IVEC3,
+		"ivec4",		// OUTPUTTYPE_IVEC4,
+
+		"uvec2",		// OUTPUTTYPE_UVEC2,
+		"uvec3",		// OUTPUTTYPE_UVEC3,
+		"uvec4",		// OUTPUTTYPE_UVEC4,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == DrawTestSpec::OUTPUTTYPE_LAST);
+
+	return types[type];
+}
+
+static GLenum primitiveToGL (DrawTestSpec::Primitive primitive)
+{
+	GLenum primitives[] =
+	{
+		GL_POINTS,						// PRIMITIVE_POINTS = 0,
+		GL_TRIANGLES,					// PRIMITIVE_TRIANGLES,
+		GL_TRIANGLE_FAN,				// PRIMITIVE_TRIANGLE_FAN,
+		GL_TRIANGLE_STRIP,				// PRIMITIVE_TRIANGLE_STRIP,
+		GL_LINES,						// PRIMITIVE_LINES
+		GL_LINE_STRIP,					// PRIMITIVE_LINE_STRIP
+		GL_LINE_LOOP,					// PRIMITIVE_LINE_LOOP
+		GL_LINES_ADJACENCY,				// PRIMITIVE_LINES_ADJACENCY
+		GL_LINE_STRIP_ADJACENCY,		// PRIMITIVE_LINE_STRIP_ADJACENCY
+		GL_TRIANGLES_ADJACENCY,			// PRIMITIVE_TRIANGLES_ADJACENCY
+		GL_TRIANGLE_STRIP_ADJACENCY,	// PRIMITIVE_TRIANGLE_STRIP_ADJACENCY
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(primitives) == DrawTestSpec::PRIMITIVE_LAST);
+
+	return primitives[(int)primitive];
+}
+
+static deUint32 indexTypeToGL (DrawTestSpec::IndexType indexType)
+{
+	GLenum indexTypes[] =
+	{
+		GL_UNSIGNED_BYTE,	// INDEXTYPE_BYTE = 0,
+		GL_UNSIGNED_SHORT,	// INDEXTYPE_SHORT,
+		GL_UNSIGNED_INT,	// INDEXTYPE_INT,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(indexTypes) == DrawTestSpec::INDEXTYPE_LAST);
+
+	return indexTypes[(int)indexType];
+}
+
+static bool inputTypeIsFloatType (DrawTestSpec::InputType type)
+{
+	if (type == DrawTestSpec::INPUTTYPE_FLOAT)
+		return true;
+	if (type == DrawTestSpec::INPUTTYPE_FIXED)
+		return true;
+	if (type == DrawTestSpec::INPUTTYPE_HALF)
+		return true;
+	if (type == DrawTestSpec::INPUTTYPE_DOUBLE)
+		return true;
+	return false;
+}
+
+static bool outputTypeIsFloatType (DrawTestSpec::OutputType type)
+{
+	if (type == DrawTestSpec::OUTPUTTYPE_FLOAT
+		|| type == DrawTestSpec::OUTPUTTYPE_VEC2
+		|| type == DrawTestSpec::OUTPUTTYPE_VEC3
+		|| type == DrawTestSpec::OUTPUTTYPE_VEC4)
+		return true;
+
+	return false;
+}
+
+static bool outputTypeIsIntType (DrawTestSpec::OutputType type)
+{
+	if (type == DrawTestSpec::OUTPUTTYPE_INT
+		|| type == DrawTestSpec::OUTPUTTYPE_IVEC2
+		|| type == DrawTestSpec::OUTPUTTYPE_IVEC3
+		|| type == DrawTestSpec::OUTPUTTYPE_IVEC4)
+		return true;
+
+	return false;
+}
+
+static bool outputTypeIsUintType (DrawTestSpec::OutputType type)
+{
+	if (type == DrawTestSpec::OUTPUTTYPE_UINT
+		|| type == DrawTestSpec::OUTPUTTYPE_UVEC2
+		|| type == DrawTestSpec::OUTPUTTYPE_UVEC3
+		|| type == DrawTestSpec::OUTPUTTYPE_UVEC4)
+		return true;
+
+	return false;
+}
+
+static size_t getElementCount (DrawTestSpec::Primitive primitive, size_t primitiveCount)
+{
+	switch (primitive)
+	{
+		case DrawTestSpec::PRIMITIVE_POINTS:						return primitiveCount;
+		case DrawTestSpec::PRIMITIVE_TRIANGLES:						return primitiveCount * 3;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN:					return primitiveCount + 2;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP:				return primitiveCount + 2;
+		case DrawTestSpec::PRIMITIVE_LINES:							return primitiveCount * 2;
+		case DrawTestSpec::PRIMITIVE_LINE_STRIP:					return primitiveCount + 1;
+		case DrawTestSpec::PRIMITIVE_LINE_LOOP:						return (primitiveCount==1) ? (2) : (primitiveCount);
+		case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY:				return primitiveCount * 4;
+		case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY:			return primitiveCount + 3;
+		case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY:			return primitiveCount * 6;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY:		return primitiveCount * 2 + 4;
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+struct MethodInfo
+{
+	bool indexed;
+	bool instanced;
+	bool ranged;
+	bool first;
+	bool baseVertex;
+	bool indirect;
+};
+
+static MethodInfo getMethodInfo (gls::DrawTestSpec::DrawMethod method)
+{
+	static const MethodInfo infos[] =
+	{
+		//	indexed		instanced	ranged		first		baseVertex	indirect
+		{	false,		false,		false,		true,		false,		false	}, //!< DRAWMETHOD_DRAWARRAYS,
+		{	false,		true,		false,		true,		false,		false	}, //!< DRAWMETHOD_DRAWARRAYS_INSTANCED,
+		{	false,		true,		false,		true,		false,		true	}, //!< DRAWMETHOD_DRAWARRAYS_INDIRECT,
+		{	true,		false,		false,		false,		false,		false	}, //!< DRAWMETHOD_DRAWELEMENTS,
+		{	true,		false,		true,		false,		false,		false	}, //!< DRAWMETHOD_DRAWELEMENTS_RANGED,
+		{	true,		true,		false,		false,		false,		false	}, //!< DRAWMETHOD_DRAWELEMENTS_INSTANCED,
+		{	true,		true,		false,		false,		true,		true	}, //!< DRAWMETHOD_DRAWELEMENTS_INDIRECT,
+		{	true,		false,		false,		false,		true,		false	}, //!< DRAWMETHOD_DRAWELEMENTS_BASEVERTEX,
+		{	true,		true,		false,		false,		true,		false	}, //!< DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX,
+		{	true,		false,		true,		false,		true,		false	}, //!< DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX,
+	};
+
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(infos) == DrawTestSpec::DRAWMETHOD_LAST);
+	DE_ASSERT((int)method < DE_LENGTH_OF_ARRAY(infos));
+	return infos[(int)method];
+}
+
+template<class T>
+inline static void alignmentSafeAssignment (char* dst, T val)
+{
+	std::memcpy(dst, &val, sizeof(T));
+}
+
+static bool checkSpecsShaderCompatible (const DrawTestSpec& a, const DrawTestSpec& b)
+{
+	// Only the attributes matter
+	if (a.attribs.size() != b.attribs.size())
+		return false;
+
+	for (size_t ndx = 0; ndx < a.attribs.size(); ++ndx)
+	{
+		// Only the output type (== shader input type) matters and the usage in the shader.
+
+		if (a.attribs[ndx].additionalPositionAttribute != b.attribs[ndx].additionalPositionAttribute)
+			return false;
+
+		// component counts need not to match
+		if (outputTypeIsFloatType(a.attribs[ndx].outputType) && outputTypeIsFloatType(b.attribs[ndx].outputType))
+			continue;
+		if (outputTypeIsIntType(a.attribs[ndx].outputType) && outputTypeIsIntType(b.attribs[ndx].outputType))
+			continue;
+		if (outputTypeIsUintType(a.attribs[ndx].outputType) && outputTypeIsUintType(b.attribs[ndx].outputType))
+			continue;
+
+		return false;
+	}
+
+	return true;
+}
+
+// generate random vectors in a way that does not depend on argument evaluation order
+
+tcu::Vec4 generateRandomVec4 (de::Random& random)
+{
+	tcu::Vec4 retVal;
+
+	for (int i = 0; i < 4; ++i)
+		retVal[i] = random.getFloat();
+
+	return retVal;
+}
+
+tcu::IVec4 generateRandomIVec4 (de::Random& random)
+{
+	tcu::IVec4 retVal;
+
+	for (int i = 0; i < 4; ++i)
+		retVal[i] = random.getUint32();
+
+	return retVal;
+}
+
+tcu::UVec4 generateRandomUVec4 (de::Random& random)
+{
+	tcu::UVec4 retVal;
+
+	for (int i = 0; i < 4; ++i)
+		retVal[i] = random.getUint32();
+
+	return retVal;
+}
+
+// IterationLogSectionEmitter
+
+class IterationLogSectionEmitter
+{
+public:
+								IterationLogSectionEmitter		(tcu::TestLog& log, size_t testIteration, size_t testIterations, const std::string& description, bool enabled);
+								~IterationLogSectionEmitter		(void);
+private:
+								IterationLogSectionEmitter		(const IterationLogSectionEmitter&); // delete
+	IterationLogSectionEmitter&	operator=						(const IterationLogSectionEmitter&); // delete
+
+	tcu::TestLog&				m_log;
+	bool						m_enabled;
+};
+
+IterationLogSectionEmitter::IterationLogSectionEmitter (tcu::TestLog& log, size_t testIteration, size_t testIterations, const std::string& description, bool enabled)
+	: m_log		(log)
+	, m_enabled	(enabled)
+{
+	if (m_enabled)
+	{
+		std::ostringstream buf;
+		buf << "Iteration " << (testIteration+1) << "/" << testIterations;
+
+		if (!description.empty())
+			buf << " - " << description;
+
+		m_log << tcu::TestLog::Section(buf.str(), buf.str());
+	}
+}
+
+IterationLogSectionEmitter::~IterationLogSectionEmitter (void)
+{
+	if (m_enabled)
+		m_log << tcu::TestLog::EndSection;
+}
+
+// GLValue
+
+class GLValue
+{
+public:
+
+	template<class Type>
+	class WrappedType
+	{
+	public:
+		static WrappedType<Type>	create			(Type value)							{ WrappedType<Type> v; v.m_value = value; return v; }
+		inline Type					getValue		(void) const							{ return m_value; }
+
+		inline WrappedType<Type>	operator+		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value + other.getValue()); }
+		inline WrappedType<Type>	operator*		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value * other.getValue()); }
+		inline WrappedType<Type>	operator/		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value / other.getValue()); }
+		inline WrappedType<Type>	operator-		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value - other.getValue()); }
+
+		inline WrappedType<Type>&	operator+=		(const WrappedType<Type>& other)		{ m_value += other.getValue(); return *this; }
+		inline WrappedType<Type>&	operator*=		(const WrappedType<Type>& other)		{ m_value *= other.getValue(); return *this; }
+		inline WrappedType<Type>&	operator/=		(const WrappedType<Type>& other)		{ m_value /= other.getValue(); return *this; }
+		inline WrappedType<Type>&	operator-=		(const WrappedType<Type>& other)		{ m_value -= other.getValue(); return *this; }
+
+		inline bool					operator==		(const WrappedType<Type>& other) const	{ return m_value == other.m_value; }
+		inline bool					operator!=		(const WrappedType<Type>& other) const	{ return m_value != other.m_value; }
+		inline bool					operator<		(const WrappedType<Type>& other) const	{ return m_value < other.m_value; }
+		inline bool					operator>		(const WrappedType<Type>& other) const	{ return m_value > other.m_value; }
+		inline bool					operator<=		(const WrappedType<Type>& other) const	{ return m_value <= other.m_value; }
+		inline bool					operator>=		(const WrappedType<Type>& other) const	{ return m_value >= other.m_value; }
+
+		inline 						operator Type	(void) const							{ return m_value; }
+		template<class T>
+		inline T					to				(void) const							{ return (T)m_value; }
+	private:
+		Type	m_value;
+	};
+
+	typedef WrappedType<deInt16>	Short;
+	typedef WrappedType<deUint16>	Ushort;
+
+	typedef WrappedType<deInt8>		Byte;
+	typedef WrappedType<deUint8>	Ubyte;
+
+	typedef WrappedType<float>		Float;
+	typedef WrappedType<double>		Double;
+
+	typedef WrappedType<deInt32>	Int;
+	typedef WrappedType<deUint32>	Uint;
+
+	class Half
+	{
+	public:
+		static Half			create			(float value)				{ Half h; h.m_value = floatToHalf(value); return h; }
+		inline deFloat16	getValue		(void) const				{ return m_value; }
+
+		inline Half			operator+		(const Half& other) const	{ return create(halfToFloat(m_value) + halfToFloat(other.getValue())); }
+		inline Half			operator*		(const Half& other) const	{ return create(halfToFloat(m_value) * halfToFloat(other.getValue())); }
+		inline Half			operator/		(const Half& other) const	{ return create(halfToFloat(m_value) / halfToFloat(other.getValue())); }
+		inline Half			operator-		(const Half& other) const	{ return create(halfToFloat(m_value) - halfToFloat(other.getValue())); }
+
+		inline Half&		operator+=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) + halfToFloat(m_value)); return *this; }
+		inline Half&		operator*=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) * halfToFloat(m_value)); return *this; }
+		inline Half&		operator/=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) / halfToFloat(m_value)); return *this; }
+		inline Half&		operator-=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) - halfToFloat(m_value)); return *this; }
+
+		inline bool			operator==		(const Half& other) const	{ return m_value == other.m_value; }
+		inline bool			operator!=		(const Half& other) const	{ return m_value != other.m_value; }
+		inline bool			operator<		(const Half& other) const	{ return halfToFloat(m_value) < halfToFloat(other.m_value); }
+		inline bool			operator>		(const Half& other) const	{ return halfToFloat(m_value) > halfToFloat(other.m_value); }
+		inline bool			operator<=		(const Half& other) const	{ return halfToFloat(m_value) <= halfToFloat(other.m_value); }
+		inline bool			operator>=		(const Half& other) const	{ return halfToFloat(m_value) >= halfToFloat(other.m_value); }
+
+		template<class T>
+		inline T			to				(void) const				{ return (T)halfToFloat(m_value); }
+
+		inline static deFloat16	floatToHalf		(float f);
+		inline static float		halfToFloat		(deFloat16 h);
+	private:
+		deFloat16 m_value;
+	};
+
+	class Fixed
+	{
+	public:
+		static Fixed		create			(deInt32 value)				{ Fixed v; v.m_value = value; return v; }
+		inline deInt32		getValue		(void) const				{ return m_value; }
+
+		inline Fixed		operator+		(const Fixed& other) const	{ return create(m_value + other.getValue()); }
+		inline Fixed		operator*		(const Fixed& other) const	{ return create(m_value * other.getValue()); }
+		inline Fixed		operator/		(const Fixed& other) const	{ return create(m_value / other.getValue()); }
+		inline Fixed		operator-		(const Fixed& other) const	{ return create(m_value - other.getValue()); }
+
+		inline Fixed&		operator+=		(const Fixed& other)		{ m_value += other.getValue(); return *this; }
+		inline Fixed&		operator*=		(const Fixed& other)		{ m_value *= other.getValue(); return *this; }
+		inline Fixed&		operator/=		(const Fixed& other)		{ m_value /= other.getValue(); return *this; }
+		inline Fixed&		operator-=		(const Fixed& other)		{ m_value -= other.getValue(); return *this; }
+
+		inline bool			operator==		(const Fixed& other) const	{ return m_value == other.m_value; }
+		inline bool			operator!=		(const Fixed& other) const	{ return m_value != other.m_value; }
+		inline bool			operator<		(const Fixed& other) const	{ return m_value < other.m_value; }
+		inline bool			operator>		(const Fixed& other) const	{ return m_value > other.m_value; }
+		inline bool			operator<=		(const Fixed& other) const	{ return m_value <= other.m_value; }
+		inline bool			operator>=		(const Fixed& other) const	{ return m_value >= other.m_value; }
+
+		inline 				operator deInt32 (void) const				{ return m_value; }
+		template<class T>
+		inline T			to				(void) const				{ return (T)m_value; }
+	private:
+		deInt32				m_value;
+	};
+
+	// \todo [mika] This is pretty messy
+						GLValue			(void)			: type(DrawTestSpec::INPUTTYPE_LAST) {}
+	explicit			GLValue			(Float value)	: type(DrawTestSpec::INPUTTYPE_FLOAT),				fl(value)	{}
+	explicit			GLValue			(Fixed value)	: type(DrawTestSpec::INPUTTYPE_FIXED),				fi(value)	{}
+	explicit			GLValue			(Byte value)	: type(DrawTestSpec::INPUTTYPE_BYTE),				b(value)	{}
+	explicit			GLValue			(Ubyte value)	: type(DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE),		ub(value)	{}
+	explicit			GLValue			(Short value)	: type(DrawTestSpec::INPUTTYPE_SHORT),				s(value)	{}
+	explicit			GLValue			(Ushort value)	: type(DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT),		us(value)	{}
+	explicit			GLValue			(Int value)		: type(DrawTestSpec::INPUTTYPE_INT),				i(value)	{}
+	explicit			GLValue			(Uint value)	: type(DrawTestSpec::INPUTTYPE_UNSIGNED_INT),		ui(value)	{}
+	explicit			GLValue			(Half value)	: type(DrawTestSpec::INPUTTYPE_HALF),				h(value)	{}
+	explicit			GLValue			(Double value)	: type(DrawTestSpec::INPUTTYPE_DOUBLE),				d(value)	{}
+
+	float				toFloat			(void) const;
+
+	static GLValue		getMaxValue		(DrawTestSpec::InputType type);
+	static GLValue		getMinValue		(DrawTestSpec::InputType type);
+
+	DrawTestSpec::InputType	type;
+
+	union
+	{
+		Float		fl;
+		Fixed		fi;
+		Double		d;
+		Byte		b;
+		Ubyte		ub;
+		Short		s;
+		Ushort		us;
+		Int			i;
+		Uint		ui;
+		Half		h;
+	};
+};
+
+inline deFloat16 GLValue::Half::floatToHalf (float f)
+{
+	// No denorm support.
+	tcu::Float<deUint16, 5, 10, 15, tcu::FLOAT_HAS_SIGN> v(f);
+	DE_ASSERT(!v.isNaN() && !v.isInf());
+	return v.bits();
+}
+
+inline float GLValue::Half::halfToFloat (deFloat16 h)
+{
+	return tcu::Float16((deUint16)h).asFloat();
+}
+
+float GLValue::toFloat (void) const
+{
+	switch (type)
+	{
+		case DrawTestSpec::INPUTTYPE_FLOAT:
+			return fl.getValue();
+			break;
+
+		case DrawTestSpec::INPUTTYPE_BYTE:
+			return b.getValue();
+			break;
+
+		case DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE:
+			return ub.getValue();
+			break;
+
+		case DrawTestSpec::INPUTTYPE_SHORT:
+			return s.getValue();
+			break;
+
+		case DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT:
+			return us.getValue();
+			break;
+
+		case DrawTestSpec::INPUTTYPE_FIXED:
+		{
+			int maxValue = 65536;
+			return (float)(double(2 * fi.getValue() + 1) / (maxValue - 1));
+
+			break;
+		}
+
+		case DrawTestSpec::INPUTTYPE_UNSIGNED_INT:
+			return (float)ui.getValue();
+			break;
+
+		case DrawTestSpec::INPUTTYPE_INT:
+			return (float)i.getValue();
+			break;
+
+		case DrawTestSpec::INPUTTYPE_HALF:
+			return h.to<float>();
+			break;
+
+		case DrawTestSpec::INPUTTYPE_DOUBLE:
+			return d.to<float>();
+			break;
+
+		default:
+			DE_ASSERT(false);
+			return 0.0f;
+			break;
+	};
+}
+
+GLValue GLValue::getMaxValue (DrawTestSpec::InputType type)
+{
+	GLValue rangesHi[(int)DrawTestSpec::INPUTTYPE_LAST];
+
+	rangesHi[(int)DrawTestSpec::INPUTTYPE_FLOAT]			= GLValue(Float::create(127.0f));
+	rangesHi[(int)DrawTestSpec::INPUTTYPE_DOUBLE]			= GLValue(Double::create(127.0f));
+	rangesHi[(int)DrawTestSpec::INPUTTYPE_BYTE]				= GLValue(Byte::create(127));
+	rangesHi[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE]	= GLValue(Ubyte::create(255));
+	rangesHi[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT]	= GLValue(Ushort::create(65530));
+	rangesHi[(int)DrawTestSpec::INPUTTYPE_SHORT]			= GLValue(Short::create(32760));
+	rangesHi[(int)DrawTestSpec::INPUTTYPE_FIXED]			= GLValue(Fixed::create(32760));
+	rangesHi[(int)DrawTestSpec::INPUTTYPE_INT]				= GLValue(Int::create(2147483647));
+	rangesHi[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_INT]		= GLValue(Uint::create(4294967295u));
+	rangesHi[(int)DrawTestSpec::INPUTTYPE_HALF]				= GLValue(Half::create(256.0f));
+
+	return rangesHi[(int)type];
+}
+
+GLValue GLValue::getMinValue (DrawTestSpec::InputType type)
+{
+	GLValue rangesLo[(int)DrawTestSpec::INPUTTYPE_LAST];
+
+	rangesLo[(int)DrawTestSpec::INPUTTYPE_FLOAT]			= GLValue(Float::create(-127.0f));
+	rangesLo[(int)DrawTestSpec::INPUTTYPE_DOUBLE]			= GLValue(Double::create(-127.0f));
+	rangesLo[(int)DrawTestSpec::INPUTTYPE_BYTE]				= GLValue(Byte::create(-127));
+	rangesLo[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE]	= GLValue(Ubyte::create(0));
+	rangesLo[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT]	= GLValue(Ushort::create(0));
+	rangesLo[(int)DrawTestSpec::INPUTTYPE_SHORT]			= GLValue(Short::create(-32760));
+	rangesLo[(int)DrawTestSpec::INPUTTYPE_FIXED]			= GLValue(Fixed::create(-32760));
+	rangesLo[(int)DrawTestSpec::INPUTTYPE_INT]				= GLValue(Int::create(-2147483647));
+	rangesLo[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_INT]		= GLValue(Uint::create(0));
+	rangesLo[(int)DrawTestSpec::INPUTTYPE_HALF]				= GLValue(Half::create(-256.0f));
+
+	return rangesLo[(int)type];
+}
+
+template<typename T>
+struct GLValueTypeTraits;
+
+template<> struct GLValueTypeTraits<GLValue::Float>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_FLOAT;			};
+template<> struct GLValueTypeTraits<GLValue::Double> { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_DOUBLE;			};
+template<> struct GLValueTypeTraits<GLValue::Byte>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_BYTE;			};
+template<> struct GLValueTypeTraits<GLValue::Ubyte>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE;	};
+template<> struct GLValueTypeTraits<GLValue::Ushort> { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT;	};
+template<> struct GLValueTypeTraits<GLValue::Short>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_SHORT;			};
+template<> struct GLValueTypeTraits<GLValue::Fixed>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_FIXED;			};
+template<> struct GLValueTypeTraits<GLValue::Int>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_INT;			};
+template<> struct GLValueTypeTraits<GLValue::Uint>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_UNSIGNED_INT;	};
+template<> struct GLValueTypeTraits<GLValue::Half>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_HALF;			};
+
+template<typename T>
+inline T extractGLValue (const GLValue& v);
+
+template<> GLValue::Float	inline extractGLValue<GLValue::Float>		(const GLValue& v) { return v.fl; };
+template<> GLValue::Double	inline extractGLValue<GLValue::Double>		(const GLValue& v) { return v.d; };
+template<> GLValue::Byte	inline extractGLValue<GLValue::Byte>		(const GLValue& v) { return v.b; };
+template<> GLValue::Ubyte	inline extractGLValue<GLValue::Ubyte>		(const GLValue& v) { return v.ub; };
+template<> GLValue::Ushort	inline extractGLValue<GLValue::Ushort>		(const GLValue& v) { return v.us; };
+template<> GLValue::Short	inline extractGLValue<GLValue::Short>		(const GLValue& v) { return v.s; };
+template<> GLValue::Fixed	inline extractGLValue<GLValue::Fixed>		(const GLValue& v) { return v.fi; };
+template<> GLValue::Int		inline extractGLValue<GLValue::Int>			(const GLValue& v) { return v.i; };
+template<> GLValue::Uint	inline extractGLValue<GLValue::Uint>		(const GLValue& v) { return v.ui; };
+template<> GLValue::Half	inline extractGLValue<GLValue::Half>		(const GLValue& v) { return v.h; };
+
+template<class T>
+inline T getRandom (deRandom& rnd, T min, T max);
+
+template<>
+inline GLValue::Float getRandom (deRandom& rnd, GLValue::Float min, GLValue::Float max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Float::create(min + deRandom_getFloat(&rnd) * (max.to<float>() - min.to<float>()));
+}
+
+template<>
+inline GLValue::Double getRandom (deRandom& rnd, GLValue::Double min, GLValue::Double max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Double::create(min + deRandom_getFloat(&rnd) * (max.to<float>() - min.to<float>()));
+}
+
+template<>
+inline GLValue::Short getRandom (deRandom& rnd, GLValue::Short min, GLValue::Short max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Short::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
+}
+
+template<>
+inline GLValue::Ushort getRandom (deRandom& rnd, GLValue::Ushort min, GLValue::Ushort max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Ushort::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
+}
+
+template<>
+inline GLValue::Byte getRandom (deRandom& rnd, GLValue::Byte min, GLValue::Byte max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Byte::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
+}
+
+template<>
+inline GLValue::Ubyte getRandom (deRandom& rnd, GLValue::Ubyte min, GLValue::Ubyte max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Ubyte::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
+}
+
+template<>
+inline GLValue::Fixed getRandom (deRandom& rnd, GLValue::Fixed min, GLValue::Fixed max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Fixed::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
+}
+
+template<>
+inline GLValue::Half getRandom (deRandom& rnd, GLValue::Half min, GLValue::Half max)
+{
+	if (max < min)
+		return min;
+
+	float fMax = max.to<float>();
+	float fMin = min.to<float>();
+	GLValue::Half h = GLValue::Half::create(fMin + deRandom_getFloat(&rnd) * (fMax - fMin));
+	return h;
+}
+
+template<>
+inline GLValue::Int getRandom (deRandom& rnd, GLValue::Int min, GLValue::Int max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Int::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
+}
+
+template<>
+inline GLValue::Uint getRandom (deRandom& rnd, GLValue::Uint min, GLValue::Uint max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Uint::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
+}
+
+// Minimum difference required between coordinates
+template<class T>
+inline T minValue (void);
+
+template<>
+inline GLValue::Float minValue (void)
+{
+	return GLValue::Float::create(4 * 1.0f);
+}
+
+template<>
+inline GLValue::Double minValue (void)
+{
+	return GLValue::Double::create(4 * 1.0f);
+}
+
+template<>
+inline GLValue::Short minValue (void)
+{
+	return GLValue::Short::create(4 * 256);
+}
+
+template<>
+inline GLValue::Ushort minValue (void)
+{
+	return GLValue::Ushort::create(4 * 256);
+}
+
+template<>
+inline GLValue::Byte minValue (void)
+{
+	return GLValue::Byte::create(4 * 1);
+}
+
+template<>
+inline GLValue::Ubyte minValue (void)
+{
+	return GLValue::Ubyte::create(4 * 2);
+}
+
+template<>
+inline GLValue::Fixed minValue (void)
+{
+	return GLValue::Fixed::create(4 * 1);
+}
+
+template<>
+inline GLValue::Int minValue (void)
+{
+	return GLValue::Int::create(4 * 16777216);
+}
+
+template<>
+inline GLValue::Uint minValue (void)
+{
+	return GLValue::Uint::create(4 * 16777216);
+}
+
+template<>
+inline GLValue::Half minValue (void)
+{
+	return GLValue::Half::create(4 * 1.0f);
+}
+
+template<class T>
+inline T abs (T val);
+
+template<>
+inline GLValue::Fixed abs (GLValue::Fixed val)
+{
+	return GLValue::Fixed::create(0x7FFFu & val.getValue());
+}
+
+template<>
+inline GLValue::Ubyte abs (GLValue::Ubyte val)
+{
+	return val;
+}
+
+template<>
+inline GLValue::Byte abs (GLValue::Byte val)
+{
+	return GLValue::Byte::create(0x7Fu & val.getValue());
+}
+
+template<>
+inline GLValue::Ushort abs (GLValue::Ushort val)
+{
+	return val;
+}
+
+template<>
+inline GLValue::Short abs (GLValue::Short val)
+{
+	return GLValue::Short::create(0x7FFFu & val.getValue());
+}
+
+template<>
+inline GLValue::Float abs (GLValue::Float val)
+{
+	return GLValue::Float::create(std::fabs(val.to<float>()));
+}
+
+template<>
+inline GLValue::Double abs (GLValue::Double val)
+{
+	return GLValue::Double::create(std::fabs(val.to<float>()));
+}
+
+template<>
+inline GLValue::Uint abs (GLValue::Uint val)
+{
+	return val;
+}
+
+template<>
+inline GLValue::Int abs (GLValue::Int val)
+{
+	return GLValue::Int::create(0x7FFFFFFFu & val.getValue());
+}
+
+template<>
+inline GLValue::Half abs (GLValue::Half val)
+{
+	return GLValue::Half::create(std::fabs(val.to<float>()));
+}
+
+// AttriuteArray
+
+class AttributeArray
+{
+public:
+								AttributeArray		(DrawTestSpec::Storage storage, sglr::Context& context);
+								~AttributeArray		(void);
+
+	void						data				(DrawTestSpec::Target target, size_t size, const char* data, DrawTestSpec::Usage usage);
+	void						subdata				(DrawTestSpec::Target target, int offset, int size, const char* data);
+	void						setupArray			(bool bound, int offset, int size, DrawTestSpec::InputType inType, DrawTestSpec::OutputType outType, bool normalized, int stride, int instanceDivisor, const rr::GenericVec4& defaultAttrib, bool isPositionAttr, bool bgraComponentOrder);
+	void						bindAttribute		(deUint32 loc);
+	void						bindIndexArray		(DrawTestSpec::Target storage);
+
+	int							getComponentCount	(void) const { return m_componentCount; }
+	DrawTestSpec::Target		getTarget			(void) const { return m_target; }
+	DrawTestSpec::InputType		getInputType		(void) const { return m_inputType; }
+	DrawTestSpec::OutputType	getOutputType		(void) const { return m_outputType; }
+	DrawTestSpec::Storage		getStorageType		(void) const { return m_storage; }
+	bool						getNormalized		(void) const { return m_normalize; }
+	int							getStride			(void) const { return m_stride; }
+	bool						isBound				(void) const { return m_bound; }
+	bool						isPositionAttribute	(void) const { return m_isPositionAttr; }
+
+private:
+	DrawTestSpec::Storage		m_storage;
+	sglr::Context&				m_ctx;
+	deUint32					m_glBuffer;
+
+	int							m_size;
+	char*						m_data;
+	int							m_componentCount;
+	bool						m_bound;
+	DrawTestSpec::Target		m_target;
+	DrawTestSpec::InputType		m_inputType;
+	DrawTestSpec::OutputType	m_outputType;
+	bool						m_normalize;
+	int							m_stride;
+	int							m_offset;
+	rr::GenericVec4				m_defaultAttrib;
+	int							m_instanceDivisor;
+	bool						m_isPositionAttr;
+	bool						m_bgraOrder;
+};
+
+AttributeArray::AttributeArray (DrawTestSpec::Storage storage, sglr::Context& context)
+	: m_storage			(storage)
+	, m_ctx				(context)
+	, m_glBuffer		(0)
+	, m_size			(0)
+	, m_data			(DE_NULL)
+	, m_componentCount	(1)
+	, m_bound			(false)
+	, m_target			(DrawTestSpec::TARGET_ARRAY)
+	, m_inputType		(DrawTestSpec::INPUTTYPE_FLOAT)
+	, m_outputType		(DrawTestSpec::OUTPUTTYPE_VEC4)
+	, m_normalize		(false)
+	, m_stride			(0)
+	, m_offset			(0)
+	, m_instanceDivisor	(0)
+	, m_isPositionAttr	(false)
+	, m_bgraOrder		(false)
+{
+	if (m_storage == DrawTestSpec::STORAGE_BUFFER)
+	{
+		m_ctx.genBuffers(1, &m_glBuffer);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glGenBuffers()");
+	}
+}
+
+AttributeArray::~AttributeArray	(void)
+{
+	if (m_storage == DrawTestSpec::STORAGE_BUFFER)
+	{
+		m_ctx.deleteBuffers(1, &m_glBuffer);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDeleteBuffers()");
+	}
+	else if (m_storage == DrawTestSpec::STORAGE_USER)
+		delete[] m_data;
+	else
+		DE_ASSERT(false);
+}
+
+void AttributeArray::data (DrawTestSpec::Target target, size_t size, const char* ptr, DrawTestSpec::Usage usage)
+{
+	m_size = (int)size;
+	m_target = target;
+
+	if (m_storage == DrawTestSpec::STORAGE_BUFFER)
+	{
+		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");
+
+		m_ctx.bufferData(targetToGL(target), size, ptr, usageToGL(usage));
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBufferData()");
+	}
+	else if (m_storage == DrawTestSpec::STORAGE_USER)
+	{
+		if (m_data)
+			delete[] m_data;
+
+		m_data = new char[size];
+		std::memcpy(m_data, ptr, size);
+	}
+	else
+		DE_ASSERT(false);
+}
+
+void AttributeArray::subdata (DrawTestSpec::Target target, int offset, int size, const char* ptr)
+{
+	m_target = target;
+
+	if (m_storage == DrawTestSpec::STORAGE_BUFFER)
+	{
+		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");
+
+		m_ctx.bufferSubData(targetToGL(target), offset, size, ptr);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBufferSubData()");
+	}
+	else if (m_storage == DrawTestSpec::STORAGE_USER)
+		std::memcpy(m_data + offset, ptr, size);
+	else
+		DE_ASSERT(false);
+}
+
+void AttributeArray::setupArray (bool bound, int offset, int size, DrawTestSpec::InputType inputType, DrawTestSpec::OutputType outType, bool normalized, int stride, int instanceDivisor, const rr::GenericVec4& defaultAttrib, bool isPositionAttr, bool bgraComponentOrder)
+{
+	m_componentCount	= size;
+	m_bound				= bound;
+	m_inputType			= inputType;
+	m_outputType		= outType;
+	m_normalize			= normalized;
+	m_stride			= stride;
+	m_offset			= offset;
+	m_defaultAttrib		= defaultAttrib;
+	m_instanceDivisor	= instanceDivisor;
+	m_isPositionAttr	= isPositionAttr;
+	m_bgraOrder			= bgraComponentOrder;
+}
+
+void AttributeArray::bindAttribute (deUint32 loc)
+{
+	if (!isBound())
+	{
+		switch (m_inputType)
+		{
+			case DrawTestSpec::INPUTTYPE_FLOAT:
+			{
+				tcu::Vec4 attr = m_defaultAttrib.get<float>();
+
+				switch (m_componentCount)
+				{
+					case 1: m_ctx.vertexAttrib1f(loc, attr.x()); break;
+					case 2: m_ctx.vertexAttrib2f(loc, attr.x(), attr.y()); break;
+					case 3: m_ctx.vertexAttrib3f(loc, attr.x(), attr.y(), attr.z()); break;
+					case 4: m_ctx.vertexAttrib4f(loc, attr.x(), attr.y(), attr.z(), attr.w()); break;
+					default: DE_ASSERT(DE_FALSE); break;
+				}
+				break;
+			}
+			case DrawTestSpec::INPUTTYPE_INT:
+			{
+				tcu::IVec4 attr = m_defaultAttrib.get<deInt32>();
+				m_ctx.vertexAttribI4i(loc, attr.x(), attr.y(), attr.z(), attr.w());
+				break;
+			}
+			case DrawTestSpec::INPUTTYPE_UNSIGNED_INT:
+			{
+				tcu::UVec4 attr = m_defaultAttrib.get<deUint32>();
+				m_ctx.vertexAttribI4ui(loc, attr.x(), attr.y(), attr.z(), attr.w());
+				break;
+			}
+			default:
+				DE_ASSERT(DE_FALSE);
+				break;
+		}
+	}
+	else
+	{
+		const deUint8* basePtr = DE_NULL;
+
+		if (m_storage == DrawTestSpec::STORAGE_BUFFER)
+		{
+			m_ctx.bindBuffer(targetToGL(m_target), m_glBuffer);
+			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");
+
+			basePtr = DE_NULL;
+		}
+		else if (m_storage == DrawTestSpec::STORAGE_USER)
+		{
+			m_ctx.bindBuffer(targetToGL(m_target), 0);
+			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");
+
+			basePtr = (const deUint8*)m_data;
+		}
+		else
+			DE_ASSERT(DE_FALSE);
+
+		if (!inputTypeIsFloatType(m_inputType))
+		{
+			// Input is not float type
+
+			if (outputTypeIsFloatType(m_outputType))
+			{
+				const int size = (m_bgraOrder) ? (GL_BGRA) : (m_componentCount);
+
+				DE_ASSERT(!(m_bgraOrder && m_componentCount != 4));
+
+				// Output type is float type
+				m_ctx.vertexAttribPointer(loc, size, inputTypeToGL(m_inputType), m_normalize, m_stride, basePtr + m_offset);
+				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
+			}
+			else
+			{
+				// Output type is int type
+				m_ctx.vertexAttribIPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_stride, basePtr + m_offset);
+				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribIPointer()");
+			}
+		}
+		else
+		{
+			// Input type is float type
+
+			// Output type must be float type
+			DE_ASSERT(outputTypeIsFloatType(m_outputType));
+
+			m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, basePtr + m_offset);
+			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
+		}
+
+		if (m_instanceDivisor)
+			m_ctx.vertexAttribDivisor(loc, m_instanceDivisor);
+	}
+}
+
+void AttributeArray::bindIndexArray (DrawTestSpec::Target target)
+{
+	if (m_storage == DrawTestSpec::STORAGE_USER)
+	{
+	}
+	else if (m_storage == DrawTestSpec::STORAGE_BUFFER)
+	{
+		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
+	}
+}
+
+// DrawTestShaderProgram
+
+class DrawTestShaderProgram : public sglr::ShaderProgram
+{
+public:
+												DrawTestShaderProgram		(const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays);
+
+	void										shadeVertices				(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void										shadeFragments				(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+private:
+	static std::string							genVertexSource				(const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays);
+	static std::string							genFragmentSource			(const glu::RenderContext& ctx);
+	static void									generateShaderParams		(std::map<std::string, std::string>& params, glu::ContextType type);
+	static rr::GenericVecType					mapOutputType				(const DrawTestSpec::OutputType& type);
+	static int									getComponentCount			(const DrawTestSpec::OutputType& type);
+
+	static sglr::pdec::ShaderProgramDeclaration createProgramDeclaration	(const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays);
+
+	std::vector<int>							m_componentCount;
+	std::vector<bool>							m_isCoord;
+	std::vector<rr::GenericVecType>				m_attrType;
+};
+
+DrawTestShaderProgram::DrawTestShaderProgram (const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays)
+	: sglr::ShaderProgram	(createProgramDeclaration(ctx, arrays))
+	, m_componentCount		(arrays.size())
+	, m_isCoord				(arrays.size())
+	, m_attrType			(arrays.size())
+{
+	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
+	{
+		m_componentCount[arrayNdx]	= getComponentCount(arrays[arrayNdx]->getOutputType());
+		m_isCoord[arrayNdx]			= arrays[arrayNdx]->isPositionAttribute();
+		m_attrType[arrayNdx]		= mapOutputType(arrays[arrayNdx]->getOutputType());
+	}
+}
+
+template <typename T>
+void calcShaderColorCoord (tcu::Vec2& coord, tcu::Vec3& color, const tcu::Vector<T, 4>& attribValue, bool isCoordinate, int numComponents)
+{
+	if (isCoordinate)
+		switch (numComponents)
+		{
+			case 1:	coord += tcu::Vec2((float)attribValue.x(),						(float)attribValue.x());					break;
+			case 2:	coord += tcu::Vec2((float)attribValue.x(),						(float)attribValue.y());					break;
+			case 3:	coord += tcu::Vec2((float)attribValue.x() + attribValue.z(),	(float)attribValue.y());					break;
+			case 4:	coord += tcu::Vec2((float)attribValue.x() + attribValue.z(),	(float)attribValue.y() + attribValue.w());	break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	else
+	{
+		switch (numComponents)
+		{
+			case 1:
+				color = color * (float)attribValue.x();
+				break;
+
+			case 2:
+				color.x() = color.x() * attribValue.x();
+				color.y() = color.y() * attribValue.y();
+				break;
+
+			case 3:
+				color.x() = color.x() * attribValue.x();
+				color.y() = color.y() * attribValue.y();
+				color.z() = color.z() * attribValue.z();
+				break;
+
+			case 4:
+				color.x() = color.x() * attribValue.x() * attribValue.w();
+				color.y() = color.y() * attribValue.y() * attribValue.w();
+				color.z() = color.z() * attribValue.z() * attribValue.w();
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+}
+
+void DrawTestShaderProgram::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	const float	u_coordScale = getUniformByName("u_coordScale").value.f;
+	const float u_colorScale = getUniformByName("u_colorScale").value.f;
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const size_t varyingLocColor = 0;
+
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		// Calc output color
+		tcu::Vec2 coord = tcu::Vec2(0.0, 0.0);
+		tcu::Vec3 color = tcu::Vec3(1.0, 1.0, 1.0);
+
+		for (int attribNdx = 0; attribNdx < (int)m_attrType.size(); attribNdx++)
+		{
+			const int	numComponents	= m_componentCount[attribNdx];
+			const bool	isCoord			= m_isCoord[attribNdx];
+
+			switch (m_attrType[attribNdx])
+			{
+				case rr::GENERICVECTYPE_FLOAT:	calcShaderColorCoord(coord, color, rr::readVertexAttribFloat(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), isCoord, numComponents);	break;
+				case rr::GENERICVECTYPE_INT32:	calcShaderColorCoord(coord, color, rr::readVertexAttribInt	(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), isCoord, numComponents);	break;
+				case rr::GENERICVECTYPE_UINT32:	calcShaderColorCoord(coord, color, rr::readVertexAttribUint	(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), isCoord, numComponents);	break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+
+		// Transform position
+		{
+			packet.position = tcu::Vec4(u_coordScale * coord.x(), u_coordScale * coord.y(), 1.0f, 1.0f);
+			packet.pointSize = 1.0f;
+		}
+
+		// Pass color to FS
+		{
+			packet.outputs[varyingLocColor] = tcu::Vec4(u_colorScale * color.x(), u_colorScale * color.y(), u_colorScale * color.z(), 1.0f) * 0.5f + tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f);
+		}
+	}
+}
+
+void DrawTestShaderProgram::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const size_t varyingLocColor = 0;
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		rr::FragmentPacket& packet = packets[packetNdx];
+
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, varyingLocColor, fragNdx));
+	}
+}
+
+std::string DrawTestShaderProgram::genVertexSource (const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays)
+{
+	std::map<std::string, std::string>	params;
+	std::stringstream					vertexShaderTmpl;
+
+	generateShaderParams(params, ctx.getType());
+
+	vertexShaderTmpl << "${VTX_HDR}";
+
+	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
+	{
+		vertexShaderTmpl
+			<< "${VTX_IN} highp " << outputTypeToGLType(arrays[arrayNdx]->getOutputType()) << " a_" << arrayNdx << ";\n";
+	}
+
+	vertexShaderTmpl <<
+		"uniform highp float u_coordScale;\n"
+		"uniform highp float u_colorScale;\n"
+		"${VTX_OUT} ${COL_PRECISION} vec4 v_color;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tgl_PointSize = 1.0;\n"
+		"\thighp vec2 coord = vec2(0.0, 0.0);\n"
+		"\thighp vec3 color = vec3(1.0, 1.0, 1.0);\n";
+
+	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
+	{
+		const bool isPositionAttr = arrays[arrayNdx]->isPositionAttribute();
+
+		if (isPositionAttr)
+		{
+			switch (arrays[arrayNdx]->getOutputType())
+			{
+				case (DrawTestSpec::OUTPUTTYPE_FLOAT):
+				case (DrawTestSpec::OUTPUTTYPE_INT):
+				case (DrawTestSpec::OUTPUTTYPE_UINT):
+					vertexShaderTmpl <<
+						"\tcoord += vec2(float(a_" << arrayNdx << "), float(a_" << arrayNdx << "));\n";
+					break;
+
+				case (DrawTestSpec::OUTPUTTYPE_VEC2):
+				case (DrawTestSpec::OUTPUTTYPE_IVEC2):
+				case (DrawTestSpec::OUTPUTTYPE_UVEC2):
+					vertexShaderTmpl <<
+						"\tcoord += vec2(a_" << arrayNdx << ".xy);\n";
+					break;
+
+				case (DrawTestSpec::OUTPUTTYPE_VEC3):
+				case (DrawTestSpec::OUTPUTTYPE_IVEC3):
+				case (DrawTestSpec::OUTPUTTYPE_UVEC3):
+					vertexShaderTmpl <<
+						"\tcoord += vec2(a_" << arrayNdx << ".xy);\n"
+						"\tcoord.x += float(a_" << arrayNdx << ".z);\n";
+					break;
+
+				case (DrawTestSpec::OUTPUTTYPE_VEC4):
+				case (DrawTestSpec::OUTPUTTYPE_IVEC4):
+				case (DrawTestSpec::OUTPUTTYPE_UVEC4):
+					vertexShaderTmpl <<
+						"\tcoord += vec2(a_" << arrayNdx << ".xy);\n"
+						"\tcoord += vec2(a_" << arrayNdx << ".zw);\n";
+					break;
+
+				default:
+					DE_ASSERT(false);
+					break;
+			}
+		}
+		else
+		{
+			switch (arrays[arrayNdx]->getOutputType())
+			{
+				case (DrawTestSpec::OUTPUTTYPE_FLOAT):
+				case (DrawTestSpec::OUTPUTTYPE_INT):
+				case (DrawTestSpec::OUTPUTTYPE_UINT):
+					vertexShaderTmpl <<
+						"\tcolor = color * float(a_" << arrayNdx << ");\n";
+					break;
+
+				case (DrawTestSpec::OUTPUTTYPE_VEC2):
+				case (DrawTestSpec::OUTPUTTYPE_IVEC2):
+				case (DrawTestSpec::OUTPUTTYPE_UVEC2):
+					vertexShaderTmpl <<
+						"\tcolor.rg = color.rg * vec2(a_" << arrayNdx << ".xy);\n";
+					break;
+
+				case (DrawTestSpec::OUTPUTTYPE_VEC3):
+				case (DrawTestSpec::OUTPUTTYPE_IVEC3):
+				case (DrawTestSpec::OUTPUTTYPE_UVEC3):
+					vertexShaderTmpl <<
+						"\tcolor = color.rgb * vec3(a_" << arrayNdx << ".xyz);\n";
+					break;
+
+				case (DrawTestSpec::OUTPUTTYPE_VEC4):
+				case (DrawTestSpec::OUTPUTTYPE_IVEC4):
+				case (DrawTestSpec::OUTPUTTYPE_UVEC4):
+					vertexShaderTmpl <<
+						"\tcolor = color.rgb * vec3(a_" << arrayNdx << ".xyz) * float(a_" << arrayNdx << ".w);\n";
+					break;
+
+				default:
+					DE_ASSERT(false);
+					break;
+			}
+		}
+	}
+
+	vertexShaderTmpl <<
+		"\tv_color = vec4(u_colorScale * color, 1.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5);\n"
+		"\tgl_Position = vec4(u_coordScale * coord, 1.0, 1.0);\n"
+		"}\n";
+
+	return tcu::StringTemplate(vertexShaderTmpl.str().c_str()).specialize(params);
+}
+
+std::string DrawTestShaderProgram::genFragmentSource (const glu::RenderContext& ctx)
+{
+	std::map<std::string, std::string> params;
+
+	generateShaderParams(params, ctx.getType());
+
+	static const char* fragmentShaderTmpl =
+		"${FRAG_HDR}"
+		"${FRAG_IN} ${COL_PRECISION} vec4 v_color;\n"
+		"void main(void)\n"
+		"{\n"
+		"\t${FRAG_COLOR} = v_color;\n"
+		"}\n";
+
+	return tcu::StringTemplate(fragmentShaderTmpl).specialize(params);
+}
+
+void DrawTestShaderProgram::generateShaderParams (std::map<std::string, std::string>& params, glu::ContextType type)
+{
+	if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_300_ES))
+	{
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "dEQP_FragColor";
+		params["VTX_HDR"]		= "#version 300 es\n";
+		params["FRAG_HDR"]		= "#version 300 es\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+		params["COL_PRECISION"]	= "mediump";
+	}
+	else if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_100_ES))
+	{
+		params["VTX_IN"]		= "attribute";
+		params["VTX_OUT"]		= "varying";
+		params["FRAG_IN"]		= "varying";
+		params["FRAG_COLOR"]	= "gl_FragColor";
+		params["VTX_HDR"]		= "";
+		params["FRAG_HDR"]		= "";
+		params["COL_PRECISION"]	= "mediump";
+	}
+	else if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_430))
+	{
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "dEQP_FragColor";
+		params["VTX_HDR"]		= "#version 430\n";
+		params["FRAG_HDR"]		= "#version 430\nlayout(location = 0) out highp vec4 dEQP_FragColor;\n";
+		params["COL_PRECISION"]	= "highp";
+	}
+	else if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_330))
+	{
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "dEQP_FragColor";
+		params["VTX_HDR"]		= "#version 330\n";
+		params["FRAG_HDR"]		= "#version 330\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+		params["COL_PRECISION"]	= "mediump";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+}
+
+rr::GenericVecType DrawTestShaderProgram::mapOutputType (const DrawTestSpec::OutputType& type)
+{
+	switch (type)
+	{
+		case (DrawTestSpec::OUTPUTTYPE_FLOAT):
+		case (DrawTestSpec::OUTPUTTYPE_VEC2):
+		case (DrawTestSpec::OUTPUTTYPE_VEC3):
+		case (DrawTestSpec::OUTPUTTYPE_VEC4):
+			return rr::GENERICVECTYPE_FLOAT;
+
+		case (DrawTestSpec::OUTPUTTYPE_INT):
+		case (DrawTestSpec::OUTPUTTYPE_IVEC2):
+		case (DrawTestSpec::OUTPUTTYPE_IVEC3):
+		case (DrawTestSpec::OUTPUTTYPE_IVEC4):
+			return rr::GENERICVECTYPE_INT32;
+
+		case (DrawTestSpec::OUTPUTTYPE_UINT):
+		case (DrawTestSpec::OUTPUTTYPE_UVEC2):
+		case (DrawTestSpec::OUTPUTTYPE_UVEC3):
+		case (DrawTestSpec::OUTPUTTYPE_UVEC4):
+			return rr::GENERICVECTYPE_UINT32;
+
+		default:
+			DE_ASSERT(false);
+			return rr::GENERICVECTYPE_LAST;
+	}
+}
+
+int DrawTestShaderProgram::getComponentCount (const DrawTestSpec::OutputType& type)
+{
+	switch (type)
+	{
+		case (DrawTestSpec::OUTPUTTYPE_FLOAT):
+		case (DrawTestSpec::OUTPUTTYPE_INT):
+		case (DrawTestSpec::OUTPUTTYPE_UINT):
+			return 1;
+
+		case (DrawTestSpec::OUTPUTTYPE_VEC2):
+		case (DrawTestSpec::OUTPUTTYPE_IVEC2):
+		case (DrawTestSpec::OUTPUTTYPE_UVEC2):
+			return 2;
+
+		case (DrawTestSpec::OUTPUTTYPE_VEC3):
+		case (DrawTestSpec::OUTPUTTYPE_IVEC3):
+		case (DrawTestSpec::OUTPUTTYPE_UVEC3):
+			return 3;
+
+		case (DrawTestSpec::OUTPUTTYPE_VEC4):
+		case (DrawTestSpec::OUTPUTTYPE_IVEC4):
+		case (DrawTestSpec::OUTPUTTYPE_UVEC4):
+			return 4;
+
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+sglr::pdec::ShaderProgramDeclaration DrawTestShaderProgram::createProgramDeclaration (const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays)
+{
+	sglr::pdec::ShaderProgramDeclaration decl;
+
+	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
+		decl << sglr::pdec::VertexAttribute(std::string("a_") + de::toString(arrayNdx), mapOutputType(arrays[arrayNdx]->getOutputType()));
+
+	decl << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT);
+
+	decl << sglr::pdec::VertexSource(genVertexSource(ctx, arrays));
+	decl << sglr::pdec::FragmentSource(genFragmentSource(ctx));
+
+	decl << sglr::pdec::Uniform("u_coordScale", glu::TYPE_FLOAT);
+	decl << sglr::pdec::Uniform("u_colorScale", glu::TYPE_FLOAT);
+
+	return decl;
+}
+
+class RandomArrayGenerator
+{
+public:
+	static char*			generateArray			(int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type);
+	static char*			generateIndices			(int seed, int elementCount, DrawTestSpec::IndexType type, int offset, int min, int max, int indexBase);
+	static rr::GenericVec4	generateAttributeValue	(int seed, DrawTestSpec::InputType type);
+
+private:
+	template<typename T>
+	static char*			createIndices			(int seed, int elementCount, int offset, int min, int max, int indexBase);
+	static void				setData					(char* data, DrawTestSpec::InputType type, deRandom& rnd, GLValue min, GLValue max);
+
+	static char*			generateBasicArray		(int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type);
+	template<typename T, typename GLType>
+	static char*			createBasicArray		(int seed, int elementCount, int componentCount, int offset, int stride);
+	static char*			generatePackedArray		(int seed, int elementCount, int componentCount, int offset, int stride);
+};
+
+void RandomArrayGenerator::setData (char* data, DrawTestSpec::InputType type, deRandom& rnd, GLValue min, GLValue max)
+{
+	switch (type)
+	{
+		case DrawTestSpec::INPUTTYPE_FLOAT:
+		{
+			alignmentSafeAssignment<float>(data, getRandom<GLValue::Float>(rnd, min.fl, max.fl));
+			break;
+		}
+
+		case DrawTestSpec::INPUTTYPE_SHORT:
+		{
+			alignmentSafeAssignment<deInt16>(data, getRandom<GLValue::Short>(rnd, min.s, max.s));
+			break;
+		}
+
+		case DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT:
+		{
+			alignmentSafeAssignment<deUint16>(data, getRandom<GLValue::Ushort>(rnd, min.us, max.us));
+			break;
+		}
+
+		case DrawTestSpec::INPUTTYPE_BYTE:
+		{
+			alignmentSafeAssignment<deInt8>(data, getRandom<GLValue::Byte>(rnd, min.b, max.b));
+			break;
+		}
+
+		case DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE:
+		{
+			alignmentSafeAssignment<deUint8>(data, getRandom<GLValue::Ubyte>(rnd, min.ub, max.ub));
+			break;
+		}
+
+		case DrawTestSpec::INPUTTYPE_FIXED:
+		{
+			alignmentSafeAssignment<deInt32>(data, getRandom<GLValue::Fixed>(rnd, min.fi, max.fi));
+			break;
+		}
+
+		case DrawTestSpec::INPUTTYPE_INT:
+		{
+			alignmentSafeAssignment<deInt32>(data, getRandom<GLValue::Int>(rnd, min.i, max.i));
+			break;
+		}
+
+		case DrawTestSpec::INPUTTYPE_UNSIGNED_INT:
+		{
+			alignmentSafeAssignment<deUint32>(data, getRandom<GLValue::Uint>(rnd, min.ui, max.ui));
+			break;
+		}
+
+		case DrawTestSpec::INPUTTYPE_HALF:
+		{
+			alignmentSafeAssignment<deFloat16>(data, getRandom<GLValue::Half>(rnd, min.h, max.h).getValue());
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+}
+
+char* RandomArrayGenerator::generateArray (int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type)
+{
+	if (type == DrawTestSpec::INPUTTYPE_INT_2_10_10_10 || type == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10)
+		return generatePackedArray(seed, elementCount, componentCount, offset, stride);
+	else
+		return generateBasicArray(seed, elementCount, componentCount, offset, stride, type);
+}
+
+char* RandomArrayGenerator::generateBasicArray (int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type)
+{
+	switch (type)
+	{
+		case DrawTestSpec::INPUTTYPE_FLOAT:				return createBasicArray<float,		GLValue::Float>	(seed, elementCount, componentCount, offset, stride);
+		case DrawTestSpec::INPUTTYPE_DOUBLE:			return createBasicArray<double,		GLValue::Double>(seed, elementCount, componentCount, offset, stride);
+		case DrawTestSpec::INPUTTYPE_SHORT:				return createBasicArray<deInt16,	GLValue::Short>	(seed, elementCount, componentCount, offset, stride);
+		case DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT:	return createBasicArray<deUint16,	GLValue::Ushort>(seed, elementCount, componentCount, offset, stride);
+		case DrawTestSpec::INPUTTYPE_BYTE:				return createBasicArray<deInt8,		GLValue::Byte>	(seed, elementCount, componentCount, offset, stride);
+		case DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE:		return createBasicArray<deUint8,	GLValue::Ubyte>	(seed, elementCount, componentCount, offset, stride);
+		case DrawTestSpec::INPUTTYPE_FIXED:				return createBasicArray<deInt32,	GLValue::Fixed>	(seed, elementCount, componentCount, offset, stride);
+		case DrawTestSpec::INPUTTYPE_INT:				return createBasicArray<deInt32,	GLValue::Int>	(seed, elementCount, componentCount, offset, stride);
+		case DrawTestSpec::INPUTTYPE_UNSIGNED_INT:		return createBasicArray<deUint32,	GLValue::Uint>	(seed, elementCount, componentCount, offset, stride);
+		case DrawTestSpec::INPUTTYPE_HALF:				return createBasicArray<deFloat16,	GLValue::Half>	(seed, elementCount, componentCount, offset, stride);
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+	return DE_NULL;
+}
+
+template<typename T, typename GLType>
+char* RandomArrayGenerator::createBasicArray (int seed, int elementCount, int componentCount, int offset, int stride)
+{
+	const GLType min = extractGLValue<GLType>(GLValue::getMinValue(GLValueTypeTraits<GLType>::Type));
+	const GLType max = extractGLValue<GLType>(GLValue::getMaxValue(GLValueTypeTraits<GLType>::Type));
+
+	const size_t componentSize	= sizeof(T);
+	const size_t elementSize	= componentSize * componentCount;
+	const size_t bufferSize		= offset + (elementCount - 1) * stride + elementSize;
+
+	char* data = new char[bufferSize];
+	char* writePtr = data + offset;
+
+	GLType previousComponents[4];
+
+	deRandom rnd;
+	deRandom_init(&rnd, seed);
+
+	for (int vertexNdx = 0; vertexNdx < elementCount; vertexNdx++)
+	{
+		GLType components[4];
+
+		for (int componentNdx = 0; componentNdx < componentCount; componentNdx++)
+		{
+			components[componentNdx] = getRandom<GLType>(rnd, min, max);
+
+			// Try to not create vertex near previous
+			if (vertexNdx != 0 && abs(components[componentNdx] - previousComponents[componentNdx]) < minValue<GLType>())
+			{
+				// Too close, try again (but only once)
+				components[componentNdx] = getRandom<GLType>(rnd, min, max);
+			}
+		}
+
+		for (int componentNdx = 0; componentNdx < componentCount; componentNdx++)
+			previousComponents[componentNdx] = components[componentNdx];
+
+		for (int componentNdx = 0; componentNdx < componentCount; componentNdx++)
+			alignmentSafeAssignment(writePtr + componentNdx*componentSize, components[componentNdx].getValue());
+
+		writePtr += stride;
+	}
+
+	return data;
+}
+
+char* RandomArrayGenerator::generatePackedArray (int seed, int elementCount, int componentCount, int offset, int stride)
+{
+	DE_UNREF(componentCount);
+
+	const deUint32 limit10		= (1 << 10);
+	const deUint32 limit2		= (1 << 2);
+	const size_t elementSize	= 4;
+	const size_t bufferSize		= offset + (elementCount - 1) * stride + elementSize;
+
+	char* data = new char[bufferSize];
+	char* writePtr = data + offset;
+
+	deRandom rnd;
+	deRandom_init(&rnd, seed);
+
+	for (int vertexNdx = 0; vertexNdx < elementCount; vertexNdx++)
+	{
+		const deUint32 x			= deRandom_getUint32(&rnd) % limit10;
+		const deUint32 y			= deRandom_getUint32(&rnd) % limit10;
+		const deUint32 z			= deRandom_getUint32(&rnd) % limit10;
+		const deUint32 w			= deRandom_getUint32(&rnd) % limit2;
+		const deUint32 packedValue	= (w << 30) | (z << 20) | (y << 10) | (x);
+
+		alignmentSafeAssignment(writePtr, packedValue);
+		writePtr += stride;
+	}
+
+	return data;
+}
+
+char* RandomArrayGenerator::generateIndices (int seed, int elementCount, DrawTestSpec::IndexType type, int offset, int min, int max, int indexBase)
+{
+	char* data = DE_NULL;
+
+	switch (type)
+	{
+		case DrawTestSpec::INDEXTYPE_BYTE:
+			data = createIndices<deUint8>(seed, elementCount, offset, min, max, indexBase);
+			break;
+
+		case DrawTestSpec::INDEXTYPE_SHORT:
+			data = createIndices<deUint16>(seed, elementCount, offset, min, max, indexBase);
+			break;
+
+		case DrawTestSpec::INDEXTYPE_INT:
+			data = createIndices<deUint32>(seed, elementCount, offset, min, max, indexBase);
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	return data;
+}
+
+template<typename T>
+char* RandomArrayGenerator::createIndices (int seed, int elementCount, int offset, int min, int max, int indexBase)
+{
+	const size_t elementSize	= sizeof(T);
+	const size_t bufferSize		= offset + elementCount * elementSize;
+
+	char* data = new char[bufferSize];
+	char* writePtr = data + offset;
+
+	deUint32 oldNdx1 = deUint32(-1);
+	deUint32 oldNdx2 = deUint32(-1);
+
+	deRandom rnd;
+	deRandom_init(&rnd, seed);
+
+	DE_ASSERT(indexBase >= 0); // watch for underflows
+
+	if (min < 0 || (size_t)min > std::numeric_limits<T>::max() ||
+		max < 0 || (size_t)max > std::numeric_limits<T>::max() ||
+		min > max)
+		DE_ASSERT(!"Invalid range");
+
+	for (int elementNdx = 0; elementNdx < elementCount; ++elementNdx)
+	{
+		deUint32 ndx = getRandom(rnd, GLValue::Uint::create(min), GLValue::Uint::create(max)).getValue();
+
+		// Try not to generate same index as any of previous two. This prevents
+		// generation of degenerate triangles and lines. If [min, max] is too
+		// small this cannot be guaranteed.
+
+		if (ndx == oldNdx1)			++ndx;
+		if (ndx > (deUint32)max)	ndx = min;
+		if (ndx == oldNdx2)			++ndx;
+		if (ndx > (deUint32)max)	ndx = min;
+		if (ndx == oldNdx1)			++ndx;
+		if (ndx > (deUint32)max)	ndx = min;
+
+		oldNdx2 = oldNdx1;
+		oldNdx1 = ndx;
+
+		ndx += indexBase;
+
+		alignmentSafeAssignment<T>(writePtr + elementSize * elementNdx, T(ndx));
+	}
+
+	return data;
+}
+
+rr::GenericVec4	RandomArrayGenerator::generateAttributeValue (int seed, DrawTestSpec::InputType type)
+{
+	de::Random random(seed);
+
+	switch (type)
+	{
+		case DrawTestSpec::INPUTTYPE_FLOAT:
+			return rr::GenericVec4(generateRandomVec4(random));
+
+		case DrawTestSpec::INPUTTYPE_INT:
+			return rr::GenericVec4(generateRandomIVec4(random));
+
+		case DrawTestSpec::INPUTTYPE_UNSIGNED_INT:
+			return rr::GenericVec4(generateRandomUVec4(random));
+
+		default:
+			DE_ASSERT(false);
+			return rr::GenericVec4(tcu::Vec4(1, 1, 1, 1));
+	}
+}
+
+} // anonymous
+
+// AttributePack
+
+class AttributePack
+{
+public:
+
+								AttributePack		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, sglr::Context& drawContext, const tcu::UVec2& screenSize, bool useVao, bool logEnabled);
+								~AttributePack		(void);
+
+	AttributeArray*				getArray			(int i);
+	int							getArrayCount		(void);
+
+	void						newArray			(DrawTestSpec::Storage storage);
+	void						clearArrays			(void);
+	void 						updateProgram		(void);
+
+	void						render 				(DrawTestSpec::Primitive primitive, DrawTestSpec::DrawMethod drawMethod, int firstVertex, int vertexCount, DrawTestSpec::IndexType indexType, const void* indexOffset, int rangeStart, int rangeEnd, int instanceCount, int indirectOffset, int baseVertex, float coordScale, float colorScale, AttributeArray* indexArray);
+
+	const tcu::Surface&			getSurface			(void) const { return m_screen; }
+private:
+	tcu::TestContext&			m_testCtx;
+	glu::RenderContext&			m_renderCtx;
+	sglr::Context&				m_ctx;
+
+	std::vector<AttributeArray*>m_arrays;
+	sglr::ShaderProgram*		m_program;
+	tcu::Surface				m_screen;
+	const bool					m_useVao;
+	const bool					m_logEnabled;
+	deUint32					m_programID;
+	deUint32					m_vaoID;
+};
+
+AttributePack::AttributePack (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, sglr::Context& drawContext, const tcu::UVec2& screenSize, bool useVao, bool logEnabled)
+	: m_testCtx		(testCtx)
+	, m_renderCtx	(renderCtx)
+	, m_ctx			(drawContext)
+	, m_program		(DE_NULL)
+	, m_screen		(screenSize.x(), screenSize.y())
+	, m_useVao		(useVao)
+	, m_logEnabled	(logEnabled)
+	, m_programID	(0)
+	, m_vaoID		(0)
+{
+	if (m_useVao)
+		m_ctx.genVertexArrays(1, &m_vaoID);
+}
+
+AttributePack::~AttributePack (void)
+{
+	clearArrays();
+
+	if (m_programID)
+		m_ctx.deleteProgram(m_programID);
+
+	if (m_program)
+		delete m_program;
+
+	if (m_useVao)
+		m_ctx.deleteVertexArrays(1, &m_vaoID);
+}
+
+AttributeArray* AttributePack::getArray (int i)
+{
+	return m_arrays.at(i);
+}
+
+int AttributePack::getArrayCount (void)
+{
+	return (int)m_arrays.size();
+}
+
+void AttributePack::newArray (DrawTestSpec::Storage storage)
+{
+	m_arrays.push_back(new AttributeArray(storage, m_ctx));
+}
+
+void AttributePack::clearArrays (void)
+{
+	for (std::vector<AttributeArray*>::iterator itr = m_arrays.begin(); itr != m_arrays.end(); itr++)
+		delete *itr;
+	m_arrays.clear();
+}
+
+void AttributePack::updateProgram (void)
+{
+	if (m_programID)
+		m_ctx.deleteProgram(m_programID);
+	if (m_program)
+		delete m_program;
+
+	m_program = new DrawTestShaderProgram(m_renderCtx, m_arrays);
+	m_programID = m_ctx.createProgram(m_program);
+}
+
+void AttributePack::render (DrawTestSpec::Primitive primitive, DrawTestSpec::DrawMethod drawMethod, int firstVertex, int vertexCount, DrawTestSpec::IndexType indexType, const void* indexOffset, int rangeStart, int rangeEnd, int instanceCount, int indirectOffset, int baseVertex, float coordScale, float colorScale, AttributeArray* indexArray)
+{
+	DE_ASSERT(m_program != DE_NULL);
+	DE_ASSERT(m_programID != 0);
+
+	m_ctx.viewport(0, 0, m_screen.getWidth(), m_screen.getHeight());
+	m_ctx.clearColor(0.0, 0.0, 0.0, 1.0);
+	m_ctx.clear(GL_COLOR_BUFFER_BIT);
+
+	m_ctx.useProgram(m_programID);
+	GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glUseProgram()");
+
+	m_ctx.uniform1f(m_ctx.getUniformLocation(m_programID, "u_coordScale"), coordScale);
+	m_ctx.uniform1f(m_ctx.getUniformLocation(m_programID, "u_colorScale"), colorScale);
+
+	if (m_useVao)
+		m_ctx.bindVertexArray(m_vaoID);
+
+	if (indexArray)
+		indexArray->bindIndexArray(DrawTestSpec::TARGET_ELEMENT_ARRAY);
+
+	for (int arrayNdx = 0; arrayNdx < (int)m_arrays.size(); arrayNdx++)
+	{
+		std::stringstream attribName;
+		attribName << "a_" << arrayNdx;
+
+		deUint32 loc = m_ctx.getAttribLocation(m_programID, attribName.str().c_str());
+
+		if (m_arrays[arrayNdx]->isBound())
+		{
+			m_ctx.enableVertexAttribArray(loc);
+			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glEnableVertexAttribArray()");
+		}
+
+		m_arrays[arrayNdx]->bindAttribute(loc);
+	}
+
+	if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWARRAYS)
+	{
+		m_ctx.drawArrays(primitiveToGL(primitive), firstVertex, vertexCount);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArrays()");
+	}
+	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INSTANCED)
+	{
+		m_ctx.drawArraysInstanced(primitiveToGL(primitive), firstVertex, vertexCount, instanceCount);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArraysInstanced()");
+	}
+	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS)
+	{
+		m_ctx.drawElements(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElements()");
+	}
+	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED)
+	{
+		m_ctx.drawRangeElements(primitiveToGL(primitive), rangeStart, rangeEnd, vertexCount, indexTypeToGL(indexType), indexOffset);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawRangeElements()");
+	}
+	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED)
+	{
+		m_ctx.drawElementsInstanced(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset, instanceCount);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElementsInstanced()");
+	}
+	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT)
+	{
+		struct DrawCommand
+		{
+			GLuint count;
+			GLuint primCount;
+			GLuint first;
+			GLuint reservedMustBeZero;
+		};
+		deUint8* buffer = new deUint8[sizeof(DrawCommand) + indirectOffset];
+
+		{
+			DrawCommand command;
+
+			command.count				= vertexCount;
+			command.primCount			= instanceCount;
+			command.first				= firstVertex;
+			command.reservedMustBeZero	= 0;
+
+			memcpy(buffer + indirectOffset, &command, sizeof(command));
+
+			if (m_logEnabled)
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "DrawArraysIndirectCommand:\n"
+					<< "\tcount: " << command.count << "\n"
+					<< "\tprimCount: " << command.primCount << "\n"
+					<< "\tfirst: " << command.first << "\n"
+					<< "\treservedMustBeZero: " << command.reservedMustBeZero << "\n"
+					<< tcu::TestLog::EndMessage;
+		}
+
+		GLuint indirectBuf = 0;
+		m_ctx.genBuffers(1, &indirectBuf);
+		m_ctx.bindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuf);
+		m_ctx.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(DrawCommand) + indirectOffset, buffer, GL_STATIC_DRAW);
+		delete [] buffer;
+
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "Setup draw indirect buffer");
+
+		m_ctx.drawArraysIndirect(primitiveToGL(primitive), (const deInt8*)DE_NULL + indirectOffset);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArraysIndirect()");
+
+		m_ctx.deleteBuffers(1, &indirectBuf);
+	}
+	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT)
+	{
+		struct DrawCommand
+		{
+			GLuint count;
+			GLuint primCount;
+			GLuint firstIndex;
+			GLint  baseVertex;
+			GLuint reservedMustBeZero;
+		};
+		deUint8* buffer = new deUint8[sizeof(DrawCommand) + indirectOffset];
+
+		{
+			DrawCommand command;
+
+			// index offset must be converted to firstIndex by dividing with the index element size
+			DE_ASSERT(((const deUint8*)indexOffset - (const deUint8*)DE_NULL) % gls::DrawTestSpec::indexTypeSize(indexType) == 0); // \note This is checked in spec validation
+
+			command.count				= vertexCount;
+			command.primCount			= instanceCount;
+			command.firstIndex			= (glw::GLuint)(((const deUint8*)indexOffset - (const deUint8*)DE_NULL) / gls::DrawTestSpec::indexTypeSize(indexType));
+			command.baseVertex			= baseVertex;
+			command.reservedMustBeZero	= 0;
+
+			memcpy(buffer + indirectOffset, &command, sizeof(command));
+
+			if (m_logEnabled)
+				m_testCtx.getLog()
+					<< tcu::TestLog::Message
+					<< "DrawElementsIndirectCommand:\n"
+					<< "\tcount: " << command.count << "\n"
+					<< "\tprimCount: " << command.primCount << "\n"
+					<< "\tfirstIndex: " << command.firstIndex << "\n"
+					<< "\tbaseVertex: " << command.baseVertex << "\n"
+					<< "\treservedMustBeZero: " << command.reservedMustBeZero << "\n"
+					<< tcu::TestLog::EndMessage;
+		}
+
+		GLuint indirectBuf = 0;
+		m_ctx.genBuffers(1, &indirectBuf);
+		m_ctx.bindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuf);
+		m_ctx.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(DrawCommand) + indirectOffset, buffer, GL_STATIC_DRAW);
+		delete [] buffer;
+
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "Setup draw indirect buffer");
+
+		m_ctx.drawElementsIndirect(primitiveToGL(primitive), indexTypeToGL(indexType), (const deInt8*)DE_NULL + indirectOffset);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArraysIndirect()");
+
+		m_ctx.deleteBuffers(1, &indirectBuf);
+	}
+	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_BASEVERTEX)
+	{
+		m_ctx.drawElementsBaseVertex(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset, baseVertex);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElementsBaseVertex()");
+	}
+	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX)
+	{
+		m_ctx.drawElementsInstancedBaseVertex(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset, instanceCount, baseVertex);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElementsInstancedBaseVertex()");
+	}
+	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX)
+	{
+		m_ctx.drawRangeElementsBaseVertex(primitiveToGL(primitive), rangeStart, rangeEnd, vertexCount, indexTypeToGL(indexType), indexOffset, baseVertex);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawRangeElementsBaseVertex()");
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	for (int arrayNdx = 0; arrayNdx < (int)m_arrays.size(); arrayNdx++)
+	{
+		if (m_arrays[arrayNdx]->isBound())
+		{
+			std::stringstream attribName;
+			attribName << "a_" << arrayNdx;
+
+			deUint32 loc = m_ctx.getAttribLocation(m_programID, attribName.str().c_str());
+
+			m_ctx.disableVertexAttribArray(loc);
+			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDisableVertexAttribArray()");
+		}
+	}
+
+	if (m_useVao)
+		m_ctx.bindVertexArray(0);
+
+	m_ctx.useProgram(0);
+	m_ctx.readPixels(m_screen, 0, 0, m_screen.getWidth(), m_screen.getHeight());
+}
+
+// DrawTestSpec
+
+DrawTestSpec::AttributeSpec	DrawTestSpec::AttributeSpec::createAttributeArray (InputType inputType, OutputType outputType, Storage storage, Usage usage, int componentCount, int offset, int stride, bool normalize, int instanceDivisor)
+{
+	DrawTestSpec::AttributeSpec spec;
+
+	spec.inputType			= inputType;
+	spec.outputType			= outputType;
+	spec.storage			= storage;
+	spec.usage				= usage;
+	spec.componentCount		= componentCount;
+	spec.offset				= offset;
+	spec.stride				= stride;
+	spec.normalize			= normalize;
+	spec.instanceDivisor	= instanceDivisor;
+
+	spec.useDefaultAttribute= false;
+
+	return spec;
+}
+
+DrawTestSpec::AttributeSpec	DrawTestSpec::AttributeSpec::createDefaultAttribute (InputType inputType, OutputType outputType, int componentCount)
+{
+	DE_ASSERT(inputType == INPUTTYPE_INT || inputType == INPUTTYPE_UNSIGNED_INT || inputType == INPUTTYPE_FLOAT);
+	DE_ASSERT(inputType == INPUTTYPE_FLOAT || componentCount == 4);
+
+	DrawTestSpec::AttributeSpec spec;
+
+	spec.inputType				= inputType;
+	spec.outputType				= outputType;
+	spec.storage				= DrawTestSpec::STORAGE_LAST;
+	spec.usage					= DrawTestSpec::USAGE_LAST;
+	spec.componentCount			= componentCount;
+	spec.offset					= 0;
+	spec.stride					= 0;
+	spec.normalize				= 0;
+	spec.instanceDivisor		= 0;
+
+	spec.useDefaultAttribute	= true;
+
+	return spec;
+}
+
+DrawTestSpec::AttributeSpec::AttributeSpec (void)
+{
+	inputType					= DrawTestSpec::INPUTTYPE_LAST;
+	outputType					= DrawTestSpec::OUTPUTTYPE_LAST;
+	storage						= DrawTestSpec::STORAGE_LAST;
+	usage						= DrawTestSpec::USAGE_LAST;
+	componentCount				= 0;
+	offset						= 0;
+	stride						= 0;
+	normalize					= false;
+	instanceDivisor				= 0;
+	useDefaultAttribute			= false;
+	additionalPositionAttribute = false;
+	bgraComponentOrder			= false;
+}
+
+int DrawTestSpec::AttributeSpec::hash (void) const
+{
+	if (useDefaultAttribute)
+	{
+		return 1 * int(inputType) + 7 * int(outputType) + 13 * componentCount;
+	}
+	else
+	{
+		return 1 * int(inputType) + 2 * int(outputType) + 3 * int(storage) + 5 * int(usage) + 7 * componentCount + 11 * offset + 13 * stride + 17 * (normalize ? 0 : 1) + 19 * instanceDivisor;
+	}
+}
+
+bool DrawTestSpec::AttributeSpec::valid (glu::ApiType ctxType) const
+{
+	const bool inputTypeFloat				= inputType == DrawTestSpec::INPUTTYPE_FLOAT || inputType  == DrawTestSpec::INPUTTYPE_FIXED || inputType == DrawTestSpec::INPUTTYPE_HALF;
+	const bool inputTypeUnsignedInteger		= inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE || inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT || inputType  == DrawTestSpec::INPUTTYPE_UNSIGNED_INT || inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10;
+	const bool inputTypeSignedInteger		= inputType == DrawTestSpec::INPUTTYPE_BYTE  || inputType == DrawTestSpec::INPUTTYPE_SHORT || inputType == DrawTestSpec::INPUTTYPE_INT || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10;
+	const bool inputTypePacked				= inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10;
+
+	const bool outputTypeFloat				= outputType == DrawTestSpec::OUTPUTTYPE_FLOAT || outputType == DrawTestSpec::OUTPUTTYPE_VEC2  || outputType == DrawTestSpec::OUTPUTTYPE_VEC3  || outputType == DrawTestSpec::OUTPUTTYPE_VEC4;
+	const bool outputTypeSignedInteger		= outputType == DrawTestSpec::OUTPUTTYPE_INT   || outputType == DrawTestSpec::OUTPUTTYPE_IVEC2 || outputType == DrawTestSpec::OUTPUTTYPE_IVEC3 || outputType == DrawTestSpec::OUTPUTTYPE_IVEC4;
+	const bool outputTypeUnsignedInteger	= outputType == DrawTestSpec::OUTPUTTYPE_UINT  || outputType == DrawTestSpec::OUTPUTTYPE_UVEC2 || outputType == DrawTestSpec::OUTPUTTYPE_UVEC3 || outputType == DrawTestSpec::OUTPUTTYPE_UVEC4;
+
+	if (useDefaultAttribute)
+	{
+		if (inputType != DrawTestSpec::INPUTTYPE_INT && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_INT && inputType != DrawTestSpec::INPUTTYPE_FLOAT)
+			return false;
+
+		if (inputType != DrawTestSpec::INPUTTYPE_FLOAT && componentCount != 4)
+			return false;
+
+		// no casting allowed (undefined results)
+		if (inputType == DrawTestSpec::INPUTTYPE_INT && !outputTypeSignedInteger)
+			return false;
+		if (inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT && !outputTypeUnsignedInteger)
+			return false;
+	}
+
+	if (inputTypePacked && componentCount != 4)
+		return false;
+
+	// Invalid conversions:
+
+	// float -> [u]int
+	if (inputTypeFloat && !outputTypeFloat)
+		return false;
+
+	// uint -> int		(undefined results)
+	if (inputTypeUnsignedInteger && outputTypeSignedInteger)
+		return false;
+
+	// int -> uint		(undefined results)
+	if (inputTypeSignedInteger && outputTypeUnsignedInteger)
+		return false;
+
+	// packed -> non-float (packed formats are converted to floats)
+	if (inputTypePacked && !outputTypeFloat)
+		return false;
+
+	// Invalid normalize. Normalize is only valid if output type is float
+	if (normalize && !outputTypeFloat)
+		return false;
+
+	// Allow reverse order (GL_BGRA) only for packed and 4-component ubyte
+	if (bgraComponentOrder && componentCount != 4)
+		return false;
+	if (bgraComponentOrder && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 && inputType != DrawTestSpec::INPUTTYPE_INT_2_10_10_10 && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE)
+		return false;
+	if (bgraComponentOrder && normalize != true)
+		return false;
+
+	// GLES2 limits
+	if (ctxType == glu::ApiType::es(2,0))
+	{
+		if (inputType != DrawTestSpec::INPUTTYPE_FLOAT && inputType != DrawTestSpec::INPUTTYPE_FIXED &&
+			inputType != DrawTestSpec::INPUTTYPE_BYTE  && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE &&
+			inputType != DrawTestSpec::INPUTTYPE_SHORT && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT)
+			return false;
+
+		if (!outputTypeFloat)
+			return false;
+
+		if (bgraComponentOrder)
+			return false;
+	}
+
+	// GLES3 limits
+	if (ctxType.getProfile() == glu::PROFILE_ES && ctxType.getMajorVersion() == 3)
+	{
+		if (bgraComponentOrder)
+			return false;
+	}
+
+	// No user pointers in GL core
+	if (ctxType.getProfile() == glu::PROFILE_CORE)
+	{
+		if (!useDefaultAttribute && storage == DrawTestSpec::STORAGE_USER)
+			return false;
+	}
+
+	return true;
+}
+
+bool DrawTestSpec::AttributeSpec::isBufferAligned (void) const
+{
+	const bool inputTypePacked = inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10;
+
+	// Buffer alignment, offset is a multiple of underlying data type size?
+	if (storage == STORAGE_BUFFER)
+	{
+		int dataTypeSize = gls::DrawTestSpec::inputTypeSize(inputType);
+		if (inputTypePacked)
+			dataTypeSize = 4;
+
+		if (offset % dataTypeSize != 0)
+			return false;
+	}
+
+	return true;
+}
+
+bool DrawTestSpec::AttributeSpec::isBufferStrideAligned (void) const
+{
+	const bool inputTypePacked = inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10;
+
+	// Buffer alignment, offset is a multiple of underlying data type size?
+	if (storage == STORAGE_BUFFER)
+	{
+		int dataTypeSize = gls::DrawTestSpec::inputTypeSize(inputType);
+		if (inputTypePacked)
+			dataTypeSize = 4;
+
+		if (stride % dataTypeSize != 0)
+			return false;
+	}
+
+	return true;
+}
+
+std::string DrawTestSpec::targetToString(Target target)
+{
+	DE_ASSERT(target < TARGET_LAST);
+
+	static const char* targets[] =
+	{
+		"element_array",	// TARGET_ELEMENT_ARRAY = 0,
+		"array"				// TARGET_ARRAY,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(targets) == DrawTestSpec::TARGET_LAST);
+
+	return targets[(int)target];
+}
+
+std::string DrawTestSpec::inputTypeToString(InputType type)
+{
+	DE_ASSERT(type < INPUTTYPE_LAST);
+
+	static const char* types[] =
+	{
+		"float",			// INPUTTYPE_FLOAT = 0,
+		"fixed",			// INPUTTYPE_FIXED,
+		"double",			// INPUTTYPE_DOUBLE
+
+		"byte",				// INPUTTYPE_BYTE,
+		"short",			// INPUTTYPE_SHORT,
+
+		"unsigned_byte",	// INPUTTYPE_UNSIGNED_BYTE,
+		"unsigned_short",	// INPUTTYPE_UNSIGNED_SHORT,
+
+		"int",						// INPUTTYPE_INT,
+		"unsigned_int",				// INPUTTYPE_UNSIGNED_INT,
+		"half",						// INPUTTYPE_HALF,
+		"unsigned_int2_10_10_10",	// INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		"int2_10_10_10"				// INPUTTYPE_INT_2_10_10_10,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == DrawTestSpec::INPUTTYPE_LAST);
+
+	return types[(int)type];
+}
+
+std::string DrawTestSpec::outputTypeToString(OutputType type)
+{
+	DE_ASSERT(type < OUTPUTTYPE_LAST);
+
+	static const char* types[] =
+	{
+		"float",		// OUTPUTTYPE_FLOAT = 0,
+		"vec2",			// OUTPUTTYPE_VEC2,
+		"vec3",			// OUTPUTTYPE_VEC3,
+		"vec4",			// OUTPUTTYPE_VEC4,
+
+		"int",			// OUTPUTTYPE_INT,
+		"uint",			// OUTPUTTYPE_UINT,
+
+		"ivec2",		// OUTPUTTYPE_IVEC2,
+		"ivec3",		// OUTPUTTYPE_IVEC3,
+		"ivec4",		// OUTPUTTYPE_IVEC4,
+
+		"uvec2",		// OUTPUTTYPE_UVEC2,
+		"uvec3",		// OUTPUTTYPE_UVEC3,
+		"uvec4",		// OUTPUTTYPE_UVEC4,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == DrawTestSpec::OUTPUTTYPE_LAST);
+
+	return types[(int)type];
+}
+
+std::string DrawTestSpec::usageTypeToString(Usage usage)
+{
+	DE_ASSERT(usage < USAGE_LAST);
+
+	static const char* usages[] =
+	{
+		"dynamic_draw",	// USAGE_DYNAMIC_DRAW = 0,
+		"static_draw",	// USAGE_STATIC_DRAW,
+		"stream_draw",	// USAGE_STREAM_DRAW,
+
+		"stream_read",	// USAGE_STREAM_READ,
+		"stream_copy",	// USAGE_STREAM_COPY,
+
+		"static_read",	// USAGE_STATIC_READ,
+		"static_copy",	// USAGE_STATIC_COPY,
+
+		"dynamic_read",	// USAGE_DYNAMIC_READ,
+		"dynamic_copy",	// USAGE_DYNAMIC_COPY,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(usages) == DrawTestSpec::USAGE_LAST);
+
+	return usages[(int)usage];
+}
+
+std::string	DrawTestSpec::storageToString (Storage storage)
+{
+	DE_ASSERT(storage < STORAGE_LAST);
+
+	static const char* storages[] =
+	{
+		"user_ptr",	// STORAGE_USER = 0,
+		"buffer"	// STORAGE_BUFFER,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(storages) == DrawTestSpec::STORAGE_LAST);
+
+	return storages[(int)storage];
+}
+
+std::string DrawTestSpec::primitiveToString (Primitive primitive)
+{
+	DE_ASSERT(primitive < PRIMITIVE_LAST);
+
+	static const char* primitives[] =
+	{
+		"points",					// PRIMITIVE_POINTS ,
+		"triangles",				// PRIMITIVE_TRIANGLES,
+		"triangle_fan",				// PRIMITIVE_TRIANGLE_FAN,
+		"triangle_strip",			// PRIMITIVE_TRIANGLE_STRIP,
+		"lines",					// PRIMITIVE_LINES
+		"line_strip",				// PRIMITIVE_LINE_STRIP
+		"line_loop",				// PRIMITIVE_LINE_LOOP
+		"lines_adjacency",			// PRIMITIVE_LINES_ADJACENCY
+		"line_strip_adjacency",		// PRIMITIVE_LINE_STRIP_ADJACENCY
+		"triangles_adjacency",		// PRIMITIVE_TRIANGLES_ADJACENCY
+		"triangle_strip_adjacency",	// PRIMITIVE_TRIANGLE_STRIP_ADJACENCY
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(primitives) == DrawTestSpec::PRIMITIVE_LAST);
+
+	return primitives[(int)primitive];
+}
+
+std::string DrawTestSpec::indexTypeToString (IndexType type)
+{
+	DE_ASSERT(type < DrawTestSpec::INDEXTYPE_LAST);
+
+	static const char* indexTypes[] =
+	{
+		"byte",		// INDEXTYPE_BYTE = 0,
+		"short",	// INDEXTYPE_SHORT,
+		"int",		// INDEXTYPE_INT,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(indexTypes) == DrawTestSpec::INDEXTYPE_LAST);
+
+	return indexTypes[(int)type];
+}
+
+std::string DrawTestSpec::drawMethodToString (DrawTestSpec::DrawMethod method)
+{
+	DE_ASSERT(method < DrawTestSpec::DRAWMETHOD_LAST);
+
+	static const char* methods[] =
+	{
+		"draw_arrays",							//!< DRAWMETHOD_DRAWARRAYS
+		"draw_arrays_instanced",				//!< DRAWMETHOD_DRAWARRAYS_INSTANCED
+		"draw_arrays_indirect",					//!< DRAWMETHOD_DRAWARRAYS_INDIRECT
+		"draw_elements",						//!< DRAWMETHOD_DRAWELEMENTS
+		"draw_range_elements",					//!< DRAWMETHOD_DRAWELEMENTS_RANGED
+		"draw_elements_instanced",				//!< DRAWMETHOD_DRAWELEMENTS_INSTANCED
+		"draw_elements_indirect",				//!< DRAWMETHOD_DRAWELEMENTS_INDIRECT
+		"draw_elements_base_vertex",			//!< DRAWMETHOD_DRAWELEMENTS_BASEVERTEX,
+		"draw_elements_instanced_base_vertex",	//!< DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX,
+		"draw_range_elements_base_vertex",		//!< DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(methods) == DrawTestSpec::DRAWMETHOD_LAST);
+
+	return methods[(int)method];
+}
+
+int DrawTestSpec::inputTypeSize (InputType type)
+{
+	DE_ASSERT(type < INPUTTYPE_LAST);
+
+	static const int size[] =
+	{
+		sizeof(float),		// INPUTTYPE_FLOAT = 0,
+		sizeof(deInt32),	// INPUTTYPE_FIXED,
+		sizeof(double),		// INPUTTYPE_DOUBLE
+
+		sizeof(deInt8),		// INPUTTYPE_BYTE,
+		sizeof(deInt16),	// INPUTTYPE_SHORT,
+
+		sizeof(deUint8),	// INPUTTYPE_UNSIGNED_BYTE,
+		sizeof(deUint16),	// INPUTTYPE_UNSIGNED_SHORT,
+
+		sizeof(deInt32),		// INPUTTYPE_INT,
+		sizeof(deUint32),		// INPUTTYPE_UNSIGNED_INT,
+		sizeof(deFloat16),		// INPUTTYPE_HALF,
+		sizeof(deUint32) / 4,		// INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		sizeof(deUint32) / 4		// INPUTTYPE_INT_2_10_10_10,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(size) == DrawTestSpec::INPUTTYPE_LAST);
+
+	return size[(int)type];
+}
+
+int DrawTestSpec::indexTypeSize (IndexType type)
+{
+	DE_ASSERT(type < INDEXTYPE_LAST);
+
+	static const int size[] =
+	{
+		sizeof(deUint8),	// INDEXTYPE_BYTE,
+		sizeof(deUint16),	// INDEXTYPE_SHORT,
+		sizeof(deUint32),	// INDEXTYPE_INT,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(size) == DrawTestSpec::INDEXTYPE_LAST);
+
+	return size[(int)type];
+}
+
+std::string DrawTestSpec::getName (void) const
+{
+	const MethodInfo	methodInfo	= getMethodInfo(drawMethod);
+	const bool			hasFirst	= methodInfo.first;
+	const bool			instanced	= methodInfo.instanced;
+	const bool			ranged		= methodInfo.ranged;
+	const bool			indexed		= methodInfo.indexed;
+
+	std::stringstream name;
+
+	for (size_t ndx = 0; ndx < attribs.size(); ++ndx)
+	{
+		const AttributeSpec& attrib = attribs[ndx];
+
+		if (attribs.size() > 1)
+			name << "attrib" << ndx << "_";
+
+		if (ndx == 0|| attrib.additionalPositionAttribute)
+			name << "pos_";
+		else
+			name << "col_";
+
+		if (attrib.useDefaultAttribute)
+		{
+			name
+				<< "non_array_"
+				<< DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << "_"
+				<< attrib.componentCount << "_"
+				<< DrawTestSpec::outputTypeToString(attrib.outputType) << "_";
+		}
+		else
+		{
+			name
+				<< DrawTestSpec::storageToString(attrib.storage) << "_"
+				<< attrib.offset << "_"
+				<< attrib.stride << "_"
+				<< DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType);
+			if (attrib.inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 && attrib.inputType != DrawTestSpec::INPUTTYPE_INT_2_10_10_10)
+				name << attrib.componentCount;
+			name
+				<< "_"
+				<< (attrib.normalize ? "normalized_" : "")
+				<< DrawTestSpec::outputTypeToString(attrib.outputType) << "_"
+				<< DrawTestSpec::usageTypeToString(attrib.usage) << "_"
+				<< attrib.instanceDivisor << "_";
+		}
+	}
+
+	if (indexed)
+		name
+			<< "index_" << DrawTestSpec::indexTypeToString(indexType) << "_"
+			<< DrawTestSpec::storageToString(indexStorage) << "_"
+			<< "offset" << indexPointerOffset << "_";
+	if (hasFirst)
+		name << "first" << first << "_";
+	if (ranged)
+		name << "ranged_" << indexMin << "_" << indexMax << "_";
+	if (instanced)
+		name << "instances" << instanceCount << "_";
+
+	switch (primitive)
+	{
+		case DrawTestSpec::PRIMITIVE_POINTS:
+			name << "points_";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLES:
+			name << "triangles_";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN:
+			name << "triangle_fan_";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP:
+			name << "triangle_strip_";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINES:
+			name << "lines_";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINE_STRIP:
+			name << "line_strip_";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINE_LOOP:
+			name << "line_loop_";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY:
+			name << "line_adjancency";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY:
+			name << "line_strip_adjancency";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY:
+			name << "triangles_adjancency";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY:
+			name << "triangle_strip_adjancency";
+			break;
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	name << primitiveCount;
+
+	return name.str();
+}
+
+std::string DrawTestSpec::getDesc (void) const
+{
+	std::stringstream desc;
+
+	for (size_t ndx = 0; ndx < attribs.size(); ++ndx)
+	{
+		const AttributeSpec& attrib = attribs[ndx];
+
+		if (attrib.useDefaultAttribute)
+		{
+			desc
+				<< "Attribute " << ndx << ": default, " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position ,") : ("color ,"))
+				<< "input datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << ", "
+				<< "input component count " << attrib.componentCount << ", "
+				<< "used as " << DrawTestSpec::outputTypeToString(attrib.outputType) << ", ";
+		}
+		else
+		{
+			desc
+				<< "Attribute " << ndx << ": " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position ,") : ("color ,"))
+				<< "Storage in " << DrawTestSpec::storageToString(attrib.storage) << ", "
+				<< "stride " << attrib.stride << ", "
+				<< "input datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << ", "
+				<< "input component count " << attrib.componentCount << ", "
+				<< (attrib.normalize ? "normalized, " : "")
+				<< "used as " << DrawTestSpec::outputTypeToString(attrib.outputType) << ", "
+				<< "instance divisor " << attrib.instanceDivisor << ", ";
+		}
+	}
+
+	if (drawMethod == DRAWMETHOD_DRAWARRAYS)
+	{
+		desc
+			<< "drawArrays(), "
+			<< "first " << first << ", ";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INSTANCED)
+	{
+		desc
+			<< "drawArraysInstanced(), "
+			<< "first " << first << ", "
+			<< "instance count " << instanceCount << ", ";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS)
+	{
+		desc
+			<< "drawElements(), "
+			<< "index type " << DrawTestSpec::indexTypeToString(indexType) << ", "
+			<< "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", "
+			<< "index offset " << indexPointerOffset << ", ";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_RANGED)
+	{
+		desc
+			<< "drawElementsRanged(), "
+			<< "index type " << DrawTestSpec::indexTypeToString(indexType) << ", "
+			<< "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", "
+			<< "index offset " << indexPointerOffset << ", "
+			<< "range start " << indexMin << ", "
+			<< "range end " << indexMax << ", ";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INSTANCED)
+	{
+		desc
+			<< "drawElementsInstanced(), "
+			<< "index type " << DrawTestSpec::indexTypeToString(indexType) << ", "
+			<< "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", "
+			<< "index offset " << indexPointerOffset << ", "
+			<< "instance count " << instanceCount << ", ";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INDIRECT)
+	{
+		desc
+			<< "drawArraysIndirect(), "
+			<< "first " << first << ", "
+			<< "instance count " << instanceCount << ", "
+			<< "indirect offset " << indirectOffset << ", ";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INDIRECT)
+	{
+		desc
+			<< "drawElementsIndirect(), "
+			<< "index type " << DrawTestSpec::indexTypeToString(indexType) << ", "
+			<< "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", "
+			<< "index offset " << indexPointerOffset << ", "
+			<< "instance count " << instanceCount << ", "
+			<< "indirect offset " << indirectOffset << ", "
+			<< "base vertex " << baseVertex << ", ";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	desc << primitiveCount;
+
+	switch (primitive)
+	{
+		case DrawTestSpec::PRIMITIVE_POINTS:
+			desc << "points";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLES:
+			desc << "triangles";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN:
+			desc << "triangles (fan)";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP:
+			desc << "triangles (strip)";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINES:
+			desc << "lines";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINE_STRIP:
+			desc << "lines (strip)";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINE_LOOP:
+			desc << "lines (loop)";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY:
+			desc << "lines (adjancency)";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY:
+			desc << "lines (strip, adjancency)";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY:
+			desc << "triangles (adjancency)";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY:
+			desc << "triangles (strip, adjancency)";
+			break;
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	return desc.str();
+}
+
+std::string DrawTestSpec::getMultilineDesc (void) const
+{
+	std::stringstream desc;
+
+	for (size_t ndx = 0; ndx < attribs.size(); ++ndx)
+	{
+		const AttributeSpec& attrib = attribs[ndx];
+
+		if (attrib.useDefaultAttribute)
+		{
+			desc
+				<< "Attribute " << ndx << ": default, " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position\n") : ("color\n"))
+				<< "\tinput datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << "\n"
+				<< "\tinput component count " << attrib.componentCount << "\n"
+				<< "\tused as " << DrawTestSpec::outputTypeToString(attrib.outputType) << "\n";
+		}
+		else
+		{
+			desc
+				<< "Attribute " << ndx << ": " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position\n") : ("color\n"))
+				<< "\tStorage in " << DrawTestSpec::storageToString(attrib.storage) << "\n"
+				<< "\tstride " << attrib.stride << "\n"
+				<< "\tinput datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << "\n"
+				<< "\tinput component count " << attrib.componentCount << "\n"
+				<< (attrib.normalize ? "\tnormalized\n" : "")
+				<< "\tused as " << DrawTestSpec::outputTypeToString(attrib.outputType) << "\n"
+				<< "\tinstance divisor " << attrib.instanceDivisor << "\n";
+		}
+	}
+
+	if (drawMethod == DRAWMETHOD_DRAWARRAYS)
+	{
+		desc
+			<< "drawArrays()\n"
+			<< "\tfirst " << first << "\n";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INSTANCED)
+	{
+		desc
+			<< "drawArraysInstanced()\n"
+			<< "\tfirst " << first << "\n"
+			<< "\tinstance count " << instanceCount << "\n";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS)
+	{
+		desc
+			<< "drawElements()\n"
+			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
+			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
+			<< "\tindex offset " << indexPointerOffset << "\n";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_RANGED)
+	{
+		desc
+			<< "drawElementsRanged()\n"
+			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
+			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
+			<< "\tindex offset " << indexPointerOffset << "\n"
+			<< "\trange start " << indexMin << "\n"
+			<< "\trange end " << indexMax << "\n";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INSTANCED)
+	{
+		desc
+			<< "drawElementsInstanced()\n"
+			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
+			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
+			<< "\tindex offset " << indexPointerOffset << "\n"
+			<< "\tinstance count " << instanceCount << "\n";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INDIRECT)
+	{
+		desc
+			<< "drawArraysIndirect()\n"
+			<< "\tfirst " << first << "\n"
+			<< "\tinstance count " << instanceCount << "\n"
+			<< "\tindirect offset " << indirectOffset << "\n";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INDIRECT)
+	{
+		desc
+			<< "drawElementsIndirect()\n"
+			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
+			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
+			<< "\tindex offset " << indexPointerOffset << "\n"
+			<< "\tinstance count " << instanceCount << "\n"
+			<< "\tindirect offset " << indirectOffset << "\n"
+			<< "\tbase vertex " << baseVertex << "\n";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_BASEVERTEX)
+	{
+		desc
+			<< "drawElementsBaseVertex()\n"
+			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
+			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
+			<< "\tindex offset " << indexPointerOffset << "\n"
+			<< "\tbase vertex " << baseVertex << "\n";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX)
+	{
+		desc
+			<< "drawElementsInstancedBaseVertex()\n"
+			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
+			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
+			<< "\tindex offset " << indexPointerOffset << "\n"
+			<< "\tinstance count " << instanceCount << "\n"
+			<< "\tbase vertex " << baseVertex << "\n";
+	}
+	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX)
+	{
+		desc
+			<< "drawRangeElementsBaseVertex()\n"
+			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
+			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
+			<< "\tindex offset " << indexPointerOffset << "\n"
+			<< "\tbase vertex " << baseVertex << "\n"
+			<< "\trange start " << indexMin << "\n"
+			<< "\trange end " << indexMax << "\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	desc << "\t" << primitiveCount << " ";
+
+	switch (primitive)
+	{
+		case DrawTestSpec::PRIMITIVE_POINTS:
+			desc << "points";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLES:
+			desc << "triangles";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN:
+			desc << "triangles (fan)";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP:
+			desc << "triangles (strip)";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINES:
+			desc << "lines";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINE_STRIP:
+			desc << "lines (strip)";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINE_LOOP:
+			desc << "lines (loop)";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY:
+			desc << "lines (adjancency)";
+			break;
+		case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY:
+			desc << "lines (strip, adjancency)";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY:
+			desc << "triangles (adjancency)";
+			break;
+		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY:
+			desc << "triangles (strip, adjancency)";
+			break;
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	desc << "\n";
+
+	return desc.str();
+}
+
+DrawTestSpec::DrawTestSpec (void)
+{
+	primitive			= PRIMITIVE_LAST;
+	primitiveCount		= 0;
+	drawMethod			= DRAWMETHOD_LAST;
+	indexType			= INDEXTYPE_LAST;
+	indexPointerOffset	= 0;
+	indexStorage		= STORAGE_LAST;
+	first				= 0;
+	indexMin			= 0;
+	indexMax			= 0;
+	instanceCount		= 0;
+	indirectOffset		= 0;
+	baseVertex			= 0;
+}
+
+int DrawTestSpec::hash (void) const
+{
+	// Use only drawmode-relevant values in "hashing" as the unrelevant values might not be set (causing non-deterministic behavior).
+	const MethodInfo	methodInfo		= getMethodInfo(drawMethod);
+	const bool			arrayed			= methodInfo.first;
+	const bool			instanced		= methodInfo.instanced;
+	const bool			ranged			= methodInfo.ranged;
+	const bool			indexed			= methodInfo.indexed;
+	const bool			indirect		= methodInfo.indirect;
+	const bool			hasBaseVtx		= methodInfo.baseVertex;
+
+	const int			indexHash		= (!indexed)	? (0) : (int(indexType) + 10 * indexPointerOffset + 100 * int(indexStorage));
+	const int			arrayHash		= (!arrayed)	? (0) : (first);
+	const int			indexRangeHash	= (!ranged)		? (0) : (indexMin + 10 * indexMax);
+	const int			instanceHash	= (!instanced)	? (0) : (instanceCount);
+	const int			indirectHash	= (!indirect)	? (0) : (indirectOffset);
+	const int			baseVtxHash		= (!hasBaseVtx)	? (0) : (baseVertex);
+	const int			basicHash		= int(primitive) + 10 * primitiveCount + 100 * int(drawMethod);
+
+	return indexHash + 3 * arrayHash + 5 * indexRangeHash + 7 * instanceHash + 13 * basicHash + 17 * (int)attribs.size() + 19 * primitiveCount + 23 * indirectHash + 27 * baseVtxHash;
+}
+
+bool DrawTestSpec::valid (void) const
+{
+	DE_ASSERT(apiType.getProfile() != glu::PROFILE_LAST);
+	DE_ASSERT(primitive != PRIMITIVE_LAST);
+	DE_ASSERT(drawMethod != DRAWMETHOD_LAST);
+
+	const MethodInfo methodInfo = getMethodInfo(drawMethod);
+
+	for (int ndx = 0; ndx < (int)attribs.size(); ++ndx)
+		if (!attribs[ndx].valid(apiType))
+			return false;
+
+	if (methodInfo.ranged)
+	{
+		deUint32 maxIndexValue = 0;
+		if (indexType == INDEXTYPE_BYTE)
+			maxIndexValue = GLValue::getMaxValue(INPUTTYPE_UNSIGNED_BYTE).ub.getValue();
+		else if (indexType == INDEXTYPE_SHORT)
+			maxIndexValue = GLValue::getMaxValue(INPUTTYPE_UNSIGNED_SHORT).us.getValue();
+		else if (indexType == INDEXTYPE_INT)
+			maxIndexValue = GLValue::getMaxValue(INPUTTYPE_UNSIGNED_INT).ui.getValue();
+		else
+			DE_ASSERT(DE_FALSE);
+
+		if (indexMin > indexMax)
+			return false;
+		if (indexMin < 0 || indexMax < 0)
+			return false;
+		if ((deUint32)indexMin > maxIndexValue || (deUint32)indexMax > maxIndexValue)
+			return false;
+	}
+
+	if (methodInfo.first && first < 0)
+		return false;
+
+	// GLES2 limits
+	if (apiType == glu::ApiType::es(2,0))
+	{
+		if (drawMethod != gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS && drawMethod != gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS)
+			return false;
+		if (drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS && (indexType != INDEXTYPE_BYTE && indexType != INDEXTYPE_SHORT))
+			return false;
+	}
+
+	// Indirect limitations
+	if (methodInfo.indirect)
+	{
+		// Indirect offset alignment
+		if (indirectOffset % 4 != 0)
+			return false;
+
+		// All attribute arrays must be stored in a buffer
+		for (int ndx = 0; ndx < (int)attribs.size(); ++ndx)
+			if (!attribs[ndx].useDefaultAttribute && attribs[ndx].storage == gls::DrawTestSpec::STORAGE_USER)
+				return false;
+	}
+	if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INDIRECT)
+	{
+		// index offset must be convertable to firstIndex
+		if (indexPointerOffset % gls::DrawTestSpec::indexTypeSize(indexType) != 0)
+			return false;
+
+		// Indices must be in a buffer
+		if (indexStorage != STORAGE_BUFFER)
+			return false;
+	}
+
+	// Do not allow user pointer in GL core
+	if (apiType.getProfile() == glu::PROFILE_CORE)
+	{
+		if (methodInfo.indexed && indexStorage == DrawTestSpec::STORAGE_USER)
+			return false;
+	}
+
+	return true;
+}
+
+DrawTestSpec::CompatibilityTestType DrawTestSpec::isCompatibilityTest (void) const
+{
+	const MethodInfo methodInfo = getMethodInfo(drawMethod);
+
+	bool bufferAlignmentBad = false;
+	bool strideAlignmentBad = false;
+
+	// Attribute buffer alignment
+	for (int ndx = 0; ndx < (int)attribs.size(); ++ndx)
+		if (!attribs[ndx].isBufferAligned())
+			bufferAlignmentBad = true;
+
+	// Attribute stride alignment
+	for (int ndx = 0; ndx < (int)attribs.size(); ++ndx)
+		if (!attribs[ndx].isBufferStrideAligned())
+			strideAlignmentBad = true;
+
+	// Index buffer alignment
+	if (methodInfo.indexed)
+	{
+		if (indexStorage == STORAGE_BUFFER)
+		{
+			int indexSize = 0;
+			if (indexType == INDEXTYPE_BYTE)
+				indexSize = 1;
+			else if (indexType == INDEXTYPE_SHORT)
+				indexSize = 2;
+			else if (indexType == INDEXTYPE_INT)
+				indexSize = 4;
+			else
+				DE_ASSERT(DE_FALSE);
+
+			if (indexPointerOffset % indexSize != 0)
+				bufferAlignmentBad = true;
+		}
+	}
+
+	// \note combination bad alignment & stride is treated as bad offset
+	if (bufferAlignmentBad)
+		return COMPATIBILITY_UNALIGNED_OFFSET;
+	else if (strideAlignmentBad)
+		return COMPATIBILITY_UNALIGNED_STRIDE;
+	else
+		return COMPATIBILITY_NONE;
+}
+
+// DrawTest
+
+DrawTest::DrawTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const DrawTestSpec& spec, const char* name, const char* desc)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_refBuffers		(DE_NULL)
+	, m_refContext		(DE_NULL)
+	, m_glesContext		(DE_NULL)
+	, m_glArrayPack		(DE_NULL)
+	, m_rrArrayPack		(DE_NULL)
+	, m_maxDiffRed		(-1)
+	, m_maxDiffGreen	(-1)
+	, m_maxDiffBlue		(-1)
+	, m_iteration		(0)
+	, m_result			()	// \note no per-iteration result logging (only one iteration)
+{
+	addIteration(spec);
+}
+
+DrawTest::DrawTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_refBuffers		(DE_NULL)
+	, m_refContext		(DE_NULL)
+	, m_glesContext		(DE_NULL)
+	, m_glArrayPack		(DE_NULL)
+	, m_rrArrayPack		(DE_NULL)
+	, m_maxDiffRed		(-1)
+	, m_maxDiffGreen	(-1)
+	, m_maxDiffBlue		(-1)
+	, m_iteration		(0)
+	, m_result			(testCtx.getLog(), "Iteration result: ")
+{
+}
+
+DrawTest::~DrawTest	(void)
+{
+	deinit();
+}
+
+void DrawTest::addIteration (const DrawTestSpec& spec, const char* description)
+{
+	// Validate spec
+	const bool validSpec = spec.valid();
+	DE_ASSERT(validSpec);
+
+	if (!validSpec)
+		return;
+
+	// Check the context type is the same with other iterations
+	if (!m_specs.empty())
+	{
+		const bool validContext = m_specs[0].apiType == spec.apiType;
+		DE_ASSERT(validContext);
+
+		if (!validContext)
+			return;
+	}
+
+	m_specs.push_back(spec);
+
+	if (description)
+		m_iteration_descriptions.push_back(std::string(description));
+	else
+		m_iteration_descriptions.push_back(std::string());
+}
+
+void DrawTest::init (void)
+{
+	const int						renderTargetWidth	= de::min(MAX_RENDER_TARGET_SIZE, m_renderCtx.getRenderTarget().getWidth());
+	const int						renderTargetHeight	= de::min(MAX_RENDER_TARGET_SIZE, m_renderCtx.getRenderTarget().getHeight());
+	sglr::ReferenceContextLimits	limits				(m_renderCtx);
+	bool							useVao				= false;
+
+	m_glesContext = new sglr::GLContext(m_renderCtx, m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS | sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(0, 0, renderTargetWidth, renderTargetHeight));
+
+	if (m_renderCtx.getType().getAPI() == glu::ApiType::es(2,0) || m_renderCtx.getType().getAPI() == glu::ApiType::es(3,0))
+		useVao = false;
+	else if (contextSupports(m_renderCtx.getType(), glu::ApiType::es(3,1)) || glu::isContextTypeGLCore(m_renderCtx.getType()))
+		useVao = true;
+	else
+		DE_ASSERT(!"Unknown context type");
+
+	DE_ASSERT(!m_specs.empty());
+	DE_ASSERT(contextSupports(m_renderCtx.getType(), m_specs[0].apiType));
+
+	m_refBuffers	= new sglr::ReferenceContextBuffers(m_renderCtx.getRenderTarget().getPixelFormat(), 0, 0, renderTargetWidth, renderTargetHeight);
+	m_refContext	= new sglr::ReferenceContext(limits, m_refBuffers->getColorbuffer(), m_refBuffers->getDepthbuffer(), m_refBuffers->getStencilbuffer());
+
+	m_glArrayPack	= new AttributePack(m_testCtx, m_renderCtx, *m_glesContext, tcu::UVec2(renderTargetWidth, renderTargetHeight), useVao, true);
+	m_rrArrayPack	= new AttributePack(m_testCtx, m_renderCtx, *m_refContext,  tcu::UVec2(renderTargetWidth, renderTargetHeight), useVao, false);
+
+	m_maxDiffRed	= deCeilFloatToInt32(256.0f * (6.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().redBits)));
+	m_maxDiffGreen	= deCeilFloatToInt32(256.0f * (6.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().greenBits)));
+	m_maxDiffBlue	= deCeilFloatToInt32(256.0f * (6.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().blueBits)));
+}
+
+void DrawTest::deinit (void)
+{
+	delete m_glArrayPack;
+	delete m_rrArrayPack;
+	delete m_refBuffers;
+	delete m_refContext;
+	delete m_glesContext;
+
+	m_glArrayPack	= DE_NULL;
+	m_rrArrayPack	= DE_NULL;
+	m_refBuffers	= DE_NULL;
+	m_refContext	= DE_NULL;
+	m_glesContext	= DE_NULL;
+}
+
+DrawTest::IterateResult DrawTest::iterate (void)
+{
+	const int					specNdx			= (m_iteration / 2);
+	const bool					drawStep		= (m_iteration % 2) == 0;
+	const bool					compareStep		= (m_iteration % 2) == 1;
+	const IterateResult			iterateResult	= ((size_t)m_iteration + 1 == m_specs.size()*2) ? (STOP) : (CONTINUE);
+	const DrawTestSpec&			spec			= m_specs[specNdx];
+	const bool					updateProgram	= (m_iteration == 0) || (drawStep && !checkSpecsShaderCompatible(m_specs[specNdx], m_specs[specNdx-1])); // try to use the same shader in all iterations
+	IterationLogSectionEmitter	sectionEmitter	(m_testCtx.getLog(), specNdx, m_specs.size(), m_iteration_descriptions[specNdx], drawStep && m_specs.size()!=1);
+
+	if (drawStep)
+	{
+		const MethodInfo	methodInfo				= getMethodInfo(spec.drawMethod);
+		const bool			indexed					= methodInfo.indexed;
+		const bool			instanced				= methodInfo.instanced;
+		const bool			ranged					= methodInfo.ranged;
+		const bool			hasFirst				= methodInfo.first;
+		const bool			hasBaseVtx				= methodInfo.baseVertex;
+
+		const size_t		primitiveElementCount	= getElementCount(spec.primitive, spec.primitiveCount);						// !< elements to be drawn
+		const int			indexMin				= (ranged) ? (spec.indexMin) : (0);
+		const int			firstAddition			= (hasFirst) ? (spec.first) : (0);
+		const int			baseVertexAddition		= (hasBaseVtx && spec.baseVertex > 0) ? ( spec.baseVertex) : (0);			// spec.baseVertex > 0 => Create bigger attribute buffer
+		const int			indexBase				= (hasBaseVtx && spec.baseVertex < 0) ? (-spec.baseVertex) : (0);			// spec.baseVertex < 0 => Create bigger indices
+		const size_t		elementCount			= primitiveElementCount + indexMin + firstAddition + baseVertexAddition;	// !< elements in buffer (buffer should have at least primitiveElementCount ACCESSIBLE (index range, first) elements)
+		const int			maxElementIndex			= (int)primitiveElementCount + indexMin + firstAddition - 1;
+		const int			indexMax				= de::max(0, (ranged) ? (de::clamp<int>(spec.indexMax, 0, maxElementIndex)) : (maxElementIndex));
+		float				coordScale				= getCoordScale(spec);
+		float				colorScale				= getColorScale(spec);
+
+		rr::GenericVec4		nullAttribValue;
+
+		// Log info
+		m_testCtx.getLog() << TestLog::Message << spec.getMultilineDesc() << TestLog::EndMessage;
+		m_testCtx.getLog() << TestLog::Message << TestLog::EndMessage; // extra line for clarity
+
+		// Data
+
+		m_glArrayPack->clearArrays();
+		m_rrArrayPack->clearArrays();
+
+		for (int attribNdx = 0; attribNdx < (int)spec.attribs.size(); attribNdx++)
+		{
+			DrawTestSpec::AttributeSpec attribSpec		= spec.attribs[attribNdx];
+			const bool					isPositionAttr	= (attribNdx == 0) || (attribSpec.additionalPositionAttribute);
+
+			if (attribSpec.useDefaultAttribute)
+			{
+				const int		seed		= 10 * attribSpec.hash() + 100 * spec.hash() + attribNdx;
+				rr::GenericVec4 attribValue = RandomArrayGenerator::generateAttributeValue(seed, attribSpec.inputType);
+
+				m_glArrayPack->newArray(DrawTestSpec::STORAGE_USER);
+				m_rrArrayPack->newArray(DrawTestSpec::STORAGE_USER);
+
+				m_glArrayPack->getArray(attribNdx)->setupArray(false, 0, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, false, 0, 0, attribValue, isPositionAttr, false);
+				m_rrArrayPack->getArray(attribNdx)->setupArray(false, 0, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, false, 0, 0, attribValue, isPositionAttr, false);
+			}
+			else
+			{
+				const int					seed					= attribSpec.hash() + 100 * spec.hash() + attribNdx;
+				const size_t				elementSize				= attribSpec.componentCount * DrawTestSpec::inputTypeSize(attribSpec.inputType);
+				const size_t				stride					= (attribSpec.stride == 0) ? (elementSize) : (attribSpec.stride);
+				const size_t				evaluatedElementCount	= (instanced && attribSpec.instanceDivisor > 0) ? (spec.instanceCount / attribSpec.instanceDivisor + 1) : (elementCount);
+				const size_t				referencedElementCount	= (ranged) ? (de::max<size_t>(evaluatedElementCount, spec.indexMax + 1)) : (evaluatedElementCount);
+				const size_t				bufferSize				= attribSpec.offset + stride * (referencedElementCount - 1) + elementSize;
+				const char*					data					= RandomArrayGenerator::generateArray(seed, (int)referencedElementCount, attribSpec.componentCount, attribSpec.offset, (int)stride, attribSpec.inputType);
+
+				try
+				{
+					m_glArrayPack->newArray(attribSpec.storage);
+					m_rrArrayPack->newArray(attribSpec.storage);
+
+					m_glArrayPack->getArray(attribNdx)->data(DrawTestSpec::TARGET_ARRAY, bufferSize, data, attribSpec.usage);
+					m_rrArrayPack->getArray(attribNdx)->data(DrawTestSpec::TARGET_ARRAY, bufferSize, data, attribSpec.usage);
+
+					m_glArrayPack->getArray(attribNdx)->setupArray(true, attribSpec.offset, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, attribSpec.normalize, attribSpec.stride, attribSpec.instanceDivisor, nullAttribValue, isPositionAttr, attribSpec.bgraComponentOrder);
+					m_rrArrayPack->getArray(attribNdx)->setupArray(true, attribSpec.offset, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, attribSpec.normalize, attribSpec.stride, attribSpec.instanceDivisor, nullAttribValue, isPositionAttr, attribSpec.bgraComponentOrder);
+
+					delete [] data;
+					data = NULL;
+				}
+				catch (...)
+				{
+					delete [] data;
+					throw;
+				}
+			}
+		}
+
+		// Shader program
+		if (updateProgram)
+		{
+			m_glArrayPack->updateProgram();
+			m_rrArrayPack->updateProgram();
+		}
+
+		// Draw
+		try
+		{
+			// indices
+			if (indexed)
+			{
+				const int		seed				= spec.hash();
+				const size_t	indexElementSize	= DrawTestSpec::indexTypeSize(spec.indexType);
+				const size_t	indexArraySize		= spec.indexPointerOffset + indexElementSize * elementCount;
+				const char*		indexArray			= RandomArrayGenerator::generateIndices(seed, (int)elementCount, spec.indexType, spec.indexPointerOffset, indexMin, indexMax, indexBase);
+				const char*		indexPointerBase	= (spec.indexStorage == DrawTestSpec::STORAGE_USER) ? (indexArray) : ((char*)DE_NULL);
+				const char*		indexPointer		= indexPointerBase + spec.indexPointerOffset;
+
+				de::UniquePtr<AttributeArray> glArray	(new AttributeArray(spec.indexStorage, *m_glesContext));
+				de::UniquePtr<AttributeArray> rrArray	(new AttributeArray(spec.indexStorage, *m_refContext));
+
+				try
+				{
+					glArray->data(DrawTestSpec::TARGET_ELEMENT_ARRAY, indexArraySize, indexArray, DrawTestSpec::USAGE_STATIC_DRAW);
+					rrArray->data(DrawTestSpec::TARGET_ELEMENT_ARRAY, indexArraySize, indexArray, DrawTestSpec::USAGE_STATIC_DRAW);
+
+					m_glArrayPack->render(spec.primitive, spec.drawMethod, 0, (int)primitiveElementCount, spec.indexType, indexPointer, spec.indexMin, spec.indexMax, spec.instanceCount, spec.indirectOffset, spec.baseVertex, coordScale, colorScale, glArray.get());
+					m_rrArrayPack->render(spec.primitive, spec.drawMethod, 0, (int)primitiveElementCount, spec.indexType, indexPointer, spec.indexMin, spec.indexMax, spec.instanceCount, spec.indirectOffset, spec.baseVertex, coordScale, colorScale, rrArray.get());
+
+					delete [] indexArray;
+					indexArray = NULL;
+				}
+				catch (...)
+				{
+					delete [] indexArray;
+					throw;
+				}
+			}
+			else
+			{
+				m_glArrayPack->render(spec.primitive, spec.drawMethod, spec.first, (int)primitiveElementCount, DrawTestSpec::INDEXTYPE_LAST, DE_NULL, 0, 0, spec.instanceCount, spec.indirectOffset, 0, coordScale, colorScale, DE_NULL);
+				m_rrArrayPack->render(spec.primitive, spec.drawMethod, spec.first, (int)primitiveElementCount, DrawTestSpec::INDEXTYPE_LAST, DE_NULL, 0, 0, spec.instanceCount, spec.indirectOffset, 0, coordScale, colorScale, DE_NULL);
+			}
+		}
+		catch (glu::Error& err)
+		{
+			// GL Errors are ok if the mode is not properly aligned
+
+			const DrawTestSpec::CompatibilityTestType ctype = spec.isCompatibilityTest();
+
+			m_testCtx.getLog() << TestLog::Message << "Got error: " << err.what() << TestLog::EndMessage;
+
+			if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET)
+				m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned buffers.");
+			else if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+				m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned stride.");
+			else
+				throw;
+		}
+	}
+	else if (compareStep)
+	{
+		if (!compare(spec.primitive))
+		{
+			const DrawTestSpec::CompatibilityTestType ctype = spec.isCompatibilityTest();
+
+			if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET)
+				m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned buffers.");
+			else if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
+				m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned stride.");
+			else
+				m_result.addResult(QP_TEST_RESULT_FAIL, "Image comparison failed.");
+		}
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return STOP;
+	}
+
+	m_result.setTestContextResult(m_testCtx);
+
+	m_iteration++;
+	return iterateResult;
+}
+
+enum PrimitiveClass
+{
+	PRIMITIVECLASS_POINT = 0,
+	PRIMITIVECLASS_LINE,
+	PRIMITIVECLASS_TRIANGLE,
+
+	PRIMITIVECLASS_LAST
+};
+
+static PrimitiveClass getDrawPrimitiveClass (gls::DrawTestSpec::Primitive primitiveType)
+{
+	switch (primitiveType)
+	{
+		case gls::DrawTestSpec::PRIMITIVE_POINTS:
+			return PRIMITIVECLASS_POINT;
+
+		case gls::DrawTestSpec::PRIMITIVE_LINES:
+		case gls::DrawTestSpec::PRIMITIVE_LINE_STRIP:
+		case gls::DrawTestSpec::PRIMITIVE_LINE_LOOP:
+		case gls::DrawTestSpec::PRIMITIVE_LINES_ADJACENCY:
+		case gls::DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY:
+			return PRIMITIVECLASS_LINE;
+
+		case gls::DrawTestSpec::PRIMITIVE_TRIANGLES:
+		case gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN:
+		case gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP:
+		case gls::DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY:
+		case gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY:
+			return PRIMITIVECLASS_TRIANGLE;
+
+		default:
+			DE_ASSERT(false);
+			return PRIMITIVECLASS_LAST;
+	}
+}
+
+static bool isBlack (const tcu::RGBA& c)
+{
+	// ignore alpha channel
+	return c.getRed() == 0 && c.getGreen() == 0 && c.getBlue() == 0;
+}
+
+static bool isEdgeTripletComponent (int c1, int c2, int c3, int renderTargetDifference)
+{
+	const int	roundingDifference	= 2 * renderTargetDifference; // src and dst pixels rounded to different directions
+	const int	d1					= c2 - c1;
+	const int	d2					= c3 - c2;
+	const int	rampDiff			= de::abs(d2 - d1);
+
+	return rampDiff > roundingDifference;
+}
+
+static bool isEdgeTriplet (const tcu::RGBA& c1, const tcu::RGBA& c2, const tcu::RGBA& c3, const tcu::IVec3& renderTargetThreshold)
+{
+	// black (background color) and non-black is always an edge
+	{
+		const bool b1 = isBlack(c1);
+		const bool b2 = isBlack(c2);
+		const bool b3 = isBlack(c3);
+
+		// both pixels with coverage and pixels without coverage
+		if ((b1 && b2 && b3) == false && (b1 || b2 || b3) == true)
+			return true;
+		// all black
+		if (b1 && b2 && b3)
+			return false;
+		// all with coverage
+		DE_ASSERT(!b1 && !b2 && !b3);
+	}
+
+	// Color is always linearly interpolated => component values change nearly linearly
+	// in any constant direction on triangle hull. (df/dx ~= C).
+
+	// Edge detection (this function) is run against the reference image
+	// => no dithering to worry about
+
+	return	isEdgeTripletComponent(c1.getRed(),		c2.getRed(),	c3.getRed(),	renderTargetThreshold.x())	||
+			isEdgeTripletComponent(c1.getGreen(),	c2.getGreen(),	c3.getGreen(),	renderTargetThreshold.y())	||
+			isEdgeTripletComponent(c1.getBlue(),	c2.getBlue(),	c3.getBlue(),	renderTargetThreshold.z());
+}
+
+static bool pixelNearEdge (int x, int y, const tcu::Surface& ref, const tcu::IVec3& renderTargetThreshold)
+{
+	// should not be called for edge pixels
+	DE_ASSERT(x >= 1 && x <= ref.getWidth()-2);
+	DE_ASSERT(y >= 1 && y <= ref.getHeight()-2);
+
+	// horizontal
+
+	for (int dy = -1; dy < 2; ++dy)
+	{
+		const tcu::RGBA c1 = ref.getPixel(x-1, y+dy);
+		const tcu::RGBA c2 = ref.getPixel(x,   y+dy);
+		const tcu::RGBA c3 = ref.getPixel(x+1, y+dy);
+		if (isEdgeTriplet(c1, c2, c3, renderTargetThreshold))
+			return true;
+	}
+
+	// vertical
+
+	for (int dx = -1; dx < 2; ++dx)
+	{
+		const tcu::RGBA c1 = ref.getPixel(x+dx, y-1);
+		const tcu::RGBA c2 = ref.getPixel(x+dx, y);
+		const tcu::RGBA c3 = ref.getPixel(x+dx, y+1);
+		if (isEdgeTriplet(c1, c2, c3, renderTargetThreshold))
+			return true;
+	}
+
+	return false;
+}
+
+static deUint32 getVisualizationGrayscaleColor (const tcu::RGBA& c)
+{
+	// make triangle coverage and error pixels obvious by converting coverage to grayscale
+	if (isBlack(c))
+		return 0;
+	else
+		return 50u + (deUint32)(c.getRed() + c.getBlue() + c.getGreen()) / 8u;
+}
+
+static bool pixelNearLineIntersection (int x, int y, const tcu::Surface& target)
+{
+	// should not be called for edge pixels
+	DE_ASSERT(x >= 1 && x <= target.getWidth()-2);
+	DE_ASSERT(y >= 1 && y <= target.getHeight()-2);
+
+	int coveredPixels = 0;
+
+	for (int dy = -1; dy < 2; dy++)
+	for (int dx = -1; dx < 2; dx++)
+	{
+		const bool targetCoverage = !isBlack(target.getPixel(x+dx, y+dy));
+		if (targetCoverage)
+		{
+			++coveredPixels;
+
+			// A single thin line cannot have more than 3 covered pixels in a 3x3 area
+			if (coveredPixels >= 4)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+// search 3x3 are for matching color
+static bool pixelNeighborhoodContainsColor (const tcu::Surface& target, int x, int y, const tcu::RGBA& color, const tcu::IVec3& compareThreshold)
+{
+	// should not be called for edge pixels
+	DE_ASSERT(x >= 1 && x <= target.getWidth()-2);
+	DE_ASSERT(y >= 1 && y <= target.getHeight()-2);
+
+	for (int dy = -1; dy < 2; dy++)
+	for (int dx = -1; dx < 2; dx++)
+	{
+		const tcu::RGBA	targetCmpPixel	= target.getPixel(x+dx, y+dy);
+		const int		r				= deAbs32(color.getRed()	- targetCmpPixel.getRed());
+		const int		g				= deAbs32(color.getGreen()	- targetCmpPixel.getGreen());
+		const int		b				= deAbs32(color.getBlue()	- targetCmpPixel.getBlue());
+
+		if (r <= compareThreshold.x() && g <= compareThreshold.y() && b <= compareThreshold.z())
+			return true;
+	}
+
+	return false;
+}
+
+// search 3x3 are for matching coverage (coverage == (color != background color))
+static bool pixelNeighborhoodContainsCoverage (const tcu::Surface& target, int x, int y, bool coverage)
+{
+	// should not be called for edge pixels
+	DE_ASSERT(x >= 1 && x <= target.getWidth()-2);
+	DE_ASSERT(y >= 1 && y <= target.getHeight()-2);
+
+	for (int dy = -1; dy < 2; dy++)
+	for (int dx = -1; dx < 2; dx++)
+	{
+		const bool targetCmpCoverage = !isBlack(target.getPixel(x+dx, y+dy));
+		if (targetCmpCoverage == coverage)
+			return true;
+	}
+
+	return false;
+}
+
+static bool edgeRelaxedImageCompare (tcu::TestLog& log, const char* imageSetName, const char* imageSetDesc, const tcu::Surface& reference, const tcu::Surface& result, const tcu::IVec3& compareThreshold, const tcu::IVec3& renderTargetThreshold, int maxAllowedInvalidPixels)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+
+	const tcu::IVec4	green						(0, 255, 0, 255);
+	const tcu::IVec4	errorColor					(255, 0, 0, 255);
+	const int			width						= reference.getWidth();
+	const int			height						= reference.getHeight();
+	tcu::TextureLevel	errorMask					(tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), width, height);
+	int					numFailingPixels			= 0;
+
+	// clear errormask edges which would otherwise be transparent
+
+	tcu::clear(tcu::getSubregion(errorMask.getAccess(), 0,			0,			width,	1),			green);
+	tcu::clear(tcu::getSubregion(errorMask.getAccess(), 0,			height-1,	width,	1),			green);
+	tcu::clear(tcu::getSubregion(errorMask.getAccess(), 0,			0,			1,		height),	green);
+	tcu::clear(tcu::getSubregion(errorMask.getAccess(), width-1,	0,			1,		height),	green);
+
+	// skip edge pixels since coverage on edge cannot be verified
+
+	for (int y = 1; y < height - 1; ++y)
+	for (int x = 1; x < width - 1; ++x)
+	{
+		const tcu::RGBA	refPixel			= reference.getPixel(x, y);
+		const tcu::RGBA	screenPixel			= result.getPixel(x, y);
+		const bool		isOkReferencePixel	= pixelNeighborhoodContainsColor(result, x, y, refPixel, compareThreshold);			// screen image has a matching pixel nearby (~= If something is drawn on reference, it must be drawn to screen too.)
+		const bool		isOkScreenPixel		= pixelNeighborhoodContainsColor(reference, x, y, screenPixel, compareThreshold);	// reference image has a matching pixel nearby (~= If something is drawn on screen, it must be drawn to reference too.)
+
+		if (isOkScreenPixel && isOkReferencePixel)
+		{
+			// pixel valid, write greenish pixels to make the result image easier to read
+			const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel);
+			errorMask.getAccess().setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y);
+		}
+		else if (!pixelNearEdge(x, y, reference, renderTargetThreshold))
+		{
+			// non-edge pixel values must be within threshold of the reference values
+			errorMask.getAccess().setPixel(errorColor, x, y);
+			++numFailingPixels;
+		}
+		else
+		{
+			// we are on/near an edge, verify only coverage (coverage == not background colored)
+			const bool	referenceCoverage		= !isBlack(refPixel);
+			const bool	screenCoverage			= !isBlack(screenPixel);
+			const bool	isOkReferenceCoverage	= pixelNeighborhoodContainsCoverage(result, x, y, referenceCoverage);	// Check reference pixel against screen pixel
+			const bool	isOkScreenCoverage		= pixelNeighborhoodContainsCoverage(reference, x, y, screenCoverage);	// Check screen pixels against reference pixel
+
+			if (isOkScreenCoverage && isOkReferenceCoverage)
+			{
+				// pixel valid, write greenish pixels to make the result image easier to read
+				const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel);
+				errorMask.getAccess().setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y);
+			}
+			else
+			{
+				// coverage does not match
+				errorMask.getAccess().setPixel(errorColor, x, y);
+				++numFailingPixels;
+			}
+		}
+	}
+
+	log	<< TestLog::Message
+		<< "Comparing images:\n"
+		<< "\tallowed deviation in pixel positions = 1\n"
+		<< "\tnumber of allowed invalid pixels = " << maxAllowedInvalidPixels << "\n"
+		<< "\tnumber of invalid pixels = " << numFailingPixels
+		<< TestLog::EndMessage;
+
+	if (numFailingPixels > maxAllowedInvalidPixels)
+	{
+		log << TestLog::Message
+			<< "Image comparison failed. Color threshold = (" << compareThreshold.x() << ", " << compareThreshold.y() << ", " << compareThreshold.z() << ")"
+			<< TestLog::EndMessage
+			<< TestLog::ImageSet(imageSetName, imageSetDesc)
+			<< TestLog::Image("Result",		"Result",		result)
+			<< TestLog::Image("Reference",	"Reference",	reference)
+			<< TestLog::Image("ErrorMask",	"Error mask",	errorMask)
+			<< TestLog::EndImageSet;
+
+		return false;
+	}
+	else
+	{
+		log << TestLog::ImageSet(imageSetName, imageSetDesc)
+			<< TestLog::Image("Result", "Result", result)
+			<< TestLog::EndImageSet;
+
+		return true;
+	}
+}
+
+static bool intersectionRelaxedLineImageCompare (tcu::TestLog& log, const char* imageSetName, const char* imageSetDesc, const tcu::Surface& reference, const tcu::Surface& result, const tcu::IVec3& compareThreshold, int maxAllowedInvalidPixels)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+
+	const tcu::IVec4	green						(0, 255, 0, 255);
+	const tcu::IVec4	errorColor					(255, 0, 0, 255);
+	const int			width						= reference.getWidth();
+	const int			height						= reference.getHeight();
+	tcu::TextureLevel	errorMask					(tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), width, height);
+	int					numFailingPixels			= 0;
+
+	// clear errormask edges which would otherwise be transparent
+
+	tcu::clear(tcu::getSubregion(errorMask.getAccess(), 0,			0,			width,	1),			green);
+	tcu::clear(tcu::getSubregion(errorMask.getAccess(), 0,			height-1,	width,	1),			green);
+	tcu::clear(tcu::getSubregion(errorMask.getAccess(), 0,			0,			1,		height),	green);
+	tcu::clear(tcu::getSubregion(errorMask.getAccess(), width-1,	0,			1,		height),	green);
+
+	// skip edge pixels since coverage on edge cannot be verified
+
+	for (int y = 1; y < height - 1; ++y)
+	for (int x = 1; x < width - 1; ++x)
+	{
+		const tcu::RGBA	refPixel			= reference.getPixel(x, y);
+		const tcu::RGBA	screenPixel			= result.getPixel(x, y);
+		const bool		isOkScreenPixel		= pixelNeighborhoodContainsColor(reference, x, y, screenPixel, compareThreshold);	// reference image has a matching pixel nearby (~= If something is drawn on screen, it must be drawn to reference too.)
+		const bool		isOkReferencePixel	= pixelNeighborhoodContainsColor(result, x, y, refPixel, compareThreshold);			// screen image has a matching pixel nearby (~= If something is drawn on reference, it must be drawn to screen too.)
+
+		if (isOkScreenPixel && isOkReferencePixel)
+		{
+			// pixel valid, write greenish pixels to make the result image easier to read
+			const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel);
+			errorMask.getAccess().setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y);
+		}
+		else if (!pixelNearLineIntersection(x, y, reference) &&
+				 !pixelNearLineIntersection(x, y, result))
+		{
+			// non-intersection pixel values must be within threshold of the reference values
+			errorMask.getAccess().setPixel(errorColor, x, y);
+			++numFailingPixels;
+		}
+		else
+		{
+			// pixel is near a line intersection
+			// we are on/near an edge, verify only coverage (coverage == not background colored)
+			const bool	referenceCoverage		= !isBlack(refPixel);
+			const bool	screenCoverage			= !isBlack(screenPixel);
+			const bool	isOkScreenCoverage		= pixelNeighborhoodContainsCoverage(reference, x, y, screenCoverage);	// Check screen pixels against reference pixel
+			const bool	isOkReferenceCoverage	= pixelNeighborhoodContainsCoverage(result, x, y, referenceCoverage);	// Check reference pixel against screen pixel
+
+			if (isOkScreenCoverage && isOkReferenceCoverage)
+			{
+				// pixel valid, write greenish pixels to make the result image easier to read
+				const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel);
+				errorMask.getAccess().setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y);
+			}
+			else
+			{
+				// coverage does not match
+				errorMask.getAccess().setPixel(errorColor, x, y);
+				++numFailingPixels;
+			}
+		}
+	}
+
+	log	<< TestLog::Message
+		<< "Comparing images:\n"
+		<< "\tallowed deviation in pixel positions = 1\n"
+		<< "\tnumber of allowed invalid pixels = " << maxAllowedInvalidPixels << "\n"
+		<< "\tnumber of invalid pixels = " << numFailingPixels
+		<< TestLog::EndMessage;
+
+	if (numFailingPixels > maxAllowedInvalidPixels)
+	{
+		log << TestLog::Message
+			<< "Image comparison failed. Color threshold = (" << compareThreshold.x() << ", " << compareThreshold.y() << ", " << compareThreshold.z() << ")"
+			<< TestLog::EndMessage
+			<< TestLog::ImageSet(imageSetName, imageSetDesc)
+			<< TestLog::Image("Result",		"Result",		result)
+			<< TestLog::Image("Reference",	"Reference",	reference)
+			<< TestLog::Image("ErrorMask",	"Error mask",	errorMask)
+			<< TestLog::EndImageSet;
+
+		return false;
+	}
+	else
+	{
+		log << TestLog::ImageSet(imageSetName, imageSetDesc)
+			<< TestLog::Image("Result", "Result", result)
+			<< TestLog::EndImageSet;
+
+		return true;
+	}
+}
+
+bool DrawTest::compare (gls::DrawTestSpec::Primitive primitiveType)
+{
+	const tcu::Surface&	ref		= m_rrArrayPack->getSurface();
+	const tcu::Surface&	screen	= m_glArrayPack->getSurface();
+
+	if (m_renderCtx.getRenderTarget().getNumSamples() > 1)
+	{
+		// \todo [mika] Improve compare when using multisampling
+		m_testCtx.getLog() << tcu::TestLog::Message << "Warning: Comparision of result from multisample render targets are not as stricts as without multisampling. Might produce false positives!" << tcu::TestLog::EndMessage;
+		return tcu::fuzzyCompare(m_testCtx.getLog(), "Compare Results", "Compare Results", ref.getAccess(), screen.getAccess(), 0.3f, tcu::COMPARE_LOG_RESULT);
+	}
+	else
+	{
+		const PrimitiveClass primitiveClass = getDrawPrimitiveClass(primitiveType);
+
+		switch (primitiveClass)
+		{
+			case PRIMITIVECLASS_POINT:
+			{
+				// Point are extremely unlikely to have overlapping regions, don't allow any no extra / missing pixels
+				const int maxAllowedInvalidPixelsWithPoints = 0;
+				return tcu::intThresholdPositionDeviationErrorThresholdCompare(m_testCtx.getLog(),
+																			   "CompareResult",
+																			   "Result of rendering",
+																			   ref.getAccess(),
+																			   screen.getAccess(),
+																			   tcu::UVec4(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue, 256),
+																			   tcu::IVec3(1, 1, 0),					//!< 3x3 search kernel
+																			   true,								//!< relax comparison on the image boundary
+																			   maxAllowedInvalidPixelsWithPoints,	//!< error threshold
+																			   tcu::COMPARE_LOG_RESULT);
+			}
+
+			case PRIMITIVECLASS_LINE:
+			{
+				// Lines can potentially have a large number of overlapping pixels. Pixel comparison may potentially produce
+				// false negatives in such pixels if for example the pixel in question is overdrawn by another line in the
+				// reference image but not in the resultin image. Relax comparison near line intersection points (areas) and
+				// compare only coverage, not color, in such pixels
+				const int maxAllowedInvalidPixelsWithLines = 5; // line are allowed to have a few bad pixels
+				return intersectionRelaxedLineImageCompare(m_testCtx.getLog(),
+														   "CompareResult",
+														   "Result of rendering",
+														   ref,
+														   screen,
+														   tcu::IVec3(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue),
+														   maxAllowedInvalidPixelsWithLines);
+			}
+
+			case PRIMITIVECLASS_TRIANGLE:
+			{
+				// Triangles are likely to partially or fully overlap. Pixel difference comparison is fragile in pixels
+				// where there could be potential overlapping since the  pixels might be covered by one triangle in the
+				// reference image and by the other in the result image. Relax comparsion near primitive edges and
+				// compare only coverage, not color, in such pixels.
+				const int			maxAllowedInvalidPixelsWithTriangles	= 10;
+				const tcu::IVec3	renderTargetThreshold					= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold().toIVec().xyz();
+
+				return edgeRelaxedImageCompare(m_testCtx.getLog(),
+											   "CompareResult",
+											   "Result of rendering",
+											   ref,
+											   screen,
+											   tcu::IVec3(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue),
+											   renderTargetThreshold,
+											   maxAllowedInvalidPixelsWithTriangles);
+			}
+
+			default:
+				DE_ASSERT(false);
+				return false;
+		}
+	}
+}
+
+float DrawTest::getCoordScale (const DrawTestSpec& spec) const
+{
+	float maxValue = 1.0f;
+
+	for (int arrayNdx = 0; arrayNdx < (int)spec.attribs.size(); arrayNdx++)
+	{
+		DrawTestSpec::AttributeSpec attribSpec		= spec.attribs[arrayNdx];
+		const bool					isPositionAttr	= (arrayNdx == 0) || (attribSpec.additionalPositionAttribute);
+		float						attrMaxValue	= 0;
+
+		if (!isPositionAttr)
+			continue;
+
+		if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10)
+		{
+			if (attribSpec.normalize)
+				attrMaxValue += 1.0f;
+			else
+				attrMaxValue += 1024.0;
+		}
+		else if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10)
+		{
+			if (attribSpec.normalize)
+				attrMaxValue += 1.0f;
+			else
+				attrMaxValue += 512.0;
+		}
+		else
+		{
+			const float max = GLValue::getMaxValue(attribSpec.inputType).toFloat();
+
+			attrMaxValue += (attribSpec.normalize && !inputTypeIsFloatType(attribSpec.inputType)) ? (1.0f) : (max * 1.1f);
+		}
+
+		if (attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_VEC3 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_VEC4
+			|| attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_IVEC3 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_IVEC4
+			|| attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_UVEC3 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_UVEC4)
+				attrMaxValue *= 2;
+
+		maxValue += attrMaxValue;
+	}
+
+	return 1.0f / maxValue;
+}
+
+float DrawTest::getColorScale (const DrawTestSpec& spec) const
+{
+	float colorScale = 1.0f;
+
+	for (int arrayNdx = 1; arrayNdx < (int)spec.attribs.size(); arrayNdx++)
+	{
+		DrawTestSpec::AttributeSpec attribSpec		= spec.attribs[arrayNdx];
+		const bool					isPositionAttr	= (arrayNdx == 0) || (attribSpec.additionalPositionAttribute);
+
+		if (isPositionAttr)
+			continue;
+
+		if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10)
+		{
+			if (!attribSpec.normalize)
+				colorScale *= 1.0 / 1024.0;
+		}
+		else if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10)
+		{
+			if (!attribSpec.normalize)
+				colorScale *= 1.0 / 512.0;
+		}
+		else
+		{
+			const float max = GLValue::getMaxValue(attribSpec.inputType).toFloat();
+
+			colorScale *= (attribSpec.normalize && !inputTypeIsFloatType(attribSpec.inputType) ? 1.0f : float(1.0 / double(max)));
+			if (attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_VEC4 ||
+				attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_UVEC4 ||
+				attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_IVEC4)
+				colorScale *= (attribSpec.normalize && !inputTypeIsFloatType(attribSpec.inputType) ? 1.0f : float(1.0 / double(max)));
+		}
+	}
+
+	return colorScale;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsDrawTest.hpp b/modules/glshared/glsDrawTest.hpp
new file mode 100644
index 0000000..a129a6b
--- /dev/null
+++ b/modules/glshared/glsDrawTest.hpp
@@ -0,0 +1,281 @@
+#ifndef _GLSDRAWTEST_HPP
+#define _GLSDRAWTEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Draw tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuTestCase.hpp"
+#include "gluRenderContext.hpp"
+
+namespace sglr
+{
+
+class ReferenceContextBuffers;
+class ReferenceContext;
+class Context;
+
+} // sglr
+
+namespace deqp
+{
+namespace gls
+{
+
+class AttributePack;
+
+struct DrawTestSpec
+{
+	enum Target
+	{
+		TARGET_ELEMENT_ARRAY = 0,
+		TARGET_ARRAY,
+
+		TARGET_LAST
+	};
+
+	enum InputType
+	{
+		INPUTTYPE_FLOAT = 0,
+		INPUTTYPE_FIXED,
+		INPUTTYPE_DOUBLE,
+
+		INPUTTYPE_BYTE,
+		INPUTTYPE_SHORT,
+
+		INPUTTYPE_UNSIGNED_BYTE,
+		INPUTTYPE_UNSIGNED_SHORT,
+
+		INPUTTYPE_INT,
+		INPUTTYPE_UNSIGNED_INT,
+		INPUTTYPE_HALF,
+		INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		INPUTTYPE_INT_2_10_10_10,
+
+		INPUTTYPE_LAST
+	};
+
+	enum OutputType
+	{
+		OUTPUTTYPE_FLOAT = 0,
+		OUTPUTTYPE_VEC2,
+		OUTPUTTYPE_VEC3,
+		OUTPUTTYPE_VEC4,
+
+		OUTPUTTYPE_INT,
+		OUTPUTTYPE_UINT,
+
+		OUTPUTTYPE_IVEC2,
+		OUTPUTTYPE_IVEC3,
+		OUTPUTTYPE_IVEC4,
+
+		OUTPUTTYPE_UVEC2,
+		OUTPUTTYPE_UVEC3,
+		OUTPUTTYPE_UVEC4,
+
+		OUTPUTTYPE_LAST
+	};
+
+	enum Usage
+	{
+		USAGE_DYNAMIC_DRAW = 0,
+		USAGE_STATIC_DRAW,
+		USAGE_STREAM_DRAW,
+
+		USAGE_STREAM_READ,
+		USAGE_STREAM_COPY,
+
+		USAGE_STATIC_READ,
+		USAGE_STATIC_COPY,
+
+		USAGE_DYNAMIC_READ,
+		USAGE_DYNAMIC_COPY,
+
+		USAGE_LAST
+	};
+
+	enum Storage
+	{
+		STORAGE_USER = 0,
+		STORAGE_BUFFER,
+
+		STORAGE_LAST
+	};
+
+	enum Primitive
+	{
+		PRIMITIVE_POINTS = 0,
+		PRIMITIVE_TRIANGLES,
+		PRIMITIVE_TRIANGLE_FAN,
+		PRIMITIVE_TRIANGLE_STRIP,
+		PRIMITIVE_LINES,
+		PRIMITIVE_LINE_STRIP,
+		PRIMITIVE_LINE_LOOP,
+
+		PRIMITIVE_LINES_ADJACENCY,
+		PRIMITIVE_LINE_STRIP_ADJACENCY,
+		PRIMITIVE_TRIANGLES_ADJACENCY,
+		PRIMITIVE_TRIANGLE_STRIP_ADJACENCY,
+
+		PRIMITIVE_LAST
+	};
+
+	enum IndexType
+	{
+		INDEXTYPE_BYTE = 0,
+		INDEXTYPE_SHORT,
+		INDEXTYPE_INT,
+
+		INDEXTYPE_LAST
+	};
+
+	enum DrawMethod
+	{
+		DRAWMETHOD_DRAWARRAYS = 0,
+		DRAWMETHOD_DRAWARRAYS_INSTANCED,
+		DRAWMETHOD_DRAWARRAYS_INDIRECT,
+		DRAWMETHOD_DRAWELEMENTS,
+		DRAWMETHOD_DRAWELEMENTS_RANGED,
+		DRAWMETHOD_DRAWELEMENTS_INSTANCED,
+		DRAWMETHOD_DRAWELEMENTS_INDIRECT,
+		DRAWMETHOD_DRAWELEMENTS_BASEVERTEX,
+		DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX,
+		DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX,
+
+		DRAWMETHOD_LAST
+	};
+
+	enum CompatibilityTestType
+	{
+		COMPATIBILITY_NONE = 0,
+		COMPATIBILITY_UNALIGNED_OFFSET,
+		COMPATIBILITY_UNALIGNED_STRIDE,
+
+		COMPATIBILITY_LAST
+	};
+
+	static std::string			targetToString		(Target target);
+	static std::string			inputTypeToString	(InputType type);
+	static std::string			outputTypeToString	(OutputType type);
+	static std::string			usageTypeToString	(Usage usage);
+	static std::string			storageToString		(Storage storage);
+	static std::string			primitiveToString	(Primitive primitive);
+	static std::string			indexTypeToString	(IndexType type);
+	static std::string			drawMethodToString	(DrawMethod method);
+	static int					inputTypeSize		(InputType type);
+	static int					indexTypeSize		(IndexType type);
+
+	struct AttributeSpec
+	{
+		static AttributeSpec	createAttributeArray	(InputType inputType, OutputType outputType, Storage storage, Usage usage, int componentCount, int offset, int stride, bool normalize, int instanceDivisor);
+		static AttributeSpec	createDefaultAttribute	(InputType inputType, OutputType outputType, int componentCount);	//!< allowed inputType values: INPUTTYPE_INT, INPUTTYPE_UNSIGNED_INT, INPUTTYPE_FLOAT
+
+		InputType				inputType;
+		OutputType				outputType;
+		Storage					storage;
+		Usage					usage;
+		int 					componentCount;
+		int						offset;
+		int						stride;
+		bool					normalize;
+		int						instanceDivisor;				//!< used only if drawMethod = Draw*Instanced
+		bool					useDefaultAttribute;
+
+		bool					additionalPositionAttribute;	//!< treat this attribute as position attribute. Attribute at index 0 is alway treated as such. False by default
+		bool					bgraComponentOrder;				//!< component order of this attribute is bgra, valid only for 4-component targets. False by default.
+
+								AttributeSpec			(void);
+
+		int						hash					(void) const;
+		bool					valid					(glu::ApiType apiType) const;
+		bool					isBufferAligned			(void) const;
+		bool					isBufferStrideAligned	(void) const;
+	};
+
+	std::string 				getName				(void) const;
+	std::string					getDesc				(void) const;
+	std::string					getMultilineDesc	(void) const;
+
+	glu::ApiType				apiType;			//!< needed in spec validation
+	Primitive					primitive;
+	int							primitiveCount;		//!< number of primitives to draw (per instance)
+
+	DrawMethod					drawMethod;
+	IndexType					indexType;			//!< used only if drawMethod = DrawElements*
+	int							indexPointerOffset;	//!< used only if drawMethod = DrawElements*
+	Storage						indexStorage;		//!< used only if drawMethod = DrawElements*
+	int							first;				//!< used only if drawMethod = DrawArrays*
+	int							indexMin;			//!< used only if drawMethod = Draw*Ranged
+	int							indexMax;			//!< used only if drawMethod = Draw*Ranged
+	int							instanceCount;		//!< used only if drawMethod = Draw*Instanced or Draw*Indirect
+	int							indirectOffset;		//!< used only if drawMethod = Draw*Indirect
+	int							baseVertex;			//!< used only if drawMethod = DrawElementsIndirect or *BaseVertex
+
+	std::vector<AttributeSpec>	attribs;
+
+								DrawTestSpec		(void);
+
+	int							hash				(void) const;
+	bool						valid				(void) const;
+	CompatibilityTestType		isCompatibilityTest	(void) const;
+};
+
+class DrawTest : public tcu::TestCase
+{
+public:
+									DrawTest				(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const DrawTestSpec& spec, const char* name, const char* desc);
+									DrawTest				(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc);
+	virtual							~DrawTest				(void);
+
+	void							addIteration			(const DrawTestSpec& spec, const char* description = DE_NULL);
+
+private:
+	void							init					(void);
+	void							deinit					(void);
+	IterateResult					iterate					(void);
+
+	bool							compare					(gls::DrawTestSpec::Primitive primitiveType);
+	float							getCoordScale			(const DrawTestSpec& spec) const;
+	float							getColorScale			(const DrawTestSpec& spec) const;
+
+	glu::RenderContext&				m_renderCtx;
+
+	sglr::ReferenceContextBuffers*	m_refBuffers;
+	sglr::ReferenceContext*			m_refContext;
+	sglr::Context*					m_glesContext;
+
+	AttributePack*					m_glArrayPack;
+	AttributePack*					m_rrArrayPack;
+
+	int								m_maxDiffRed;
+	int								m_maxDiffGreen;
+	int								m_maxDiffBlue;
+
+	std::vector<DrawTestSpec>		m_specs;
+	std::vector<std::string>		m_iteration_descriptions;
+	int								m_iteration;
+	tcu::ResultCollector			m_result;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSDRAWTEST_HPP
diff --git a/modules/glshared/glsFboCompletenessTests.cpp b/modules/glshared/glsFboCompletenessTests.cpp
new file mode 100644
index 0000000..498f1fd
--- /dev/null
+++ b/modules/glshared/glsFboCompletenessTests.cpp
@@ -0,0 +1,841 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Framebuffer completeness tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsFboCompletenessTests.hpp"
+
+#include "gluStrUtil.hpp"
+#include "gluObjectWrapper.hpp"
+#include "deStringUtil.hpp"
+
+#include <cctype>
+#include <iterator>
+#include <algorithm>
+
+using namespace glw;
+using glu::RenderContext;
+using glu::getFramebufferStatusName;
+using glu::getPixelFormatName;
+using glu::getTypeName;
+using glu::getErrorName;
+using glu::Framebuffer;
+using tcu::TestCase;
+using tcu::TestCaseGroup;
+using tcu::TestLog;
+using tcu::MessageBuilder;
+using tcu::TestNode;
+using std::string;
+using de::toString;
+using namespace deqp::gls::FboUtil;
+using namespace deqp::gls::FboUtil::config;
+typedef TestCase::IterateResult IterateResult;
+
+namespace deqp
+{
+namespace gls
+{
+namespace fboc
+{
+
+namespace details
+{
+// \todo [2013-12-04 lauri] Place in deStrUtil.hpp?
+
+string toLower (const string& str)
+{
+	string ret;
+	std::transform(str.begin(), str.end(), std::inserter(ret, ret.begin()), ::tolower);
+	return ret;
+}
+
+// The following extensions are applicable both to ES2 and ES3.
+
+// GL_OES_depth_texture
+static const FormatKey s_oesDepthTextureFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_DEPTH_COMPONENT,	GL_UNSIGNED_SHORT),
+	GLS_UNSIZED_FORMATKEY(GL_DEPTH_COMPONENT,	GL_UNSIGNED_INT),
+};
+
+// GL_OES_packed_depth_stencil
+static const FormatKey s_oesPackedDepthStencilSizedFormats[] =
+{
+	GL_DEPTH24_STENCIL8,
+};
+
+static const FormatKey s_oesPackedDepthStencilTexFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8),
+};
+
+// GL_OES_required_internalformat
+static const FormatKey s_oesRequiredInternalFormatColorFormats[] =
+{
+	// Same as ES2 RBO formats, plus RGBA8 (even without OES_rgb8_rgba8)
+	GL_RGB5_A1, GL_RGBA8, GL_RGBA4, GL_RGB565
+};
+
+static const FormatKey s_oesRequiredInternalFormatDepthFormats[] =
+{
+	GL_DEPTH_COMPONENT16,
+};
+
+// GL_EXT_color_buffer_half_float
+static const FormatKey s_extColorBufferHalfFloatFormats[] =
+{
+	GL_RGBA16F, GL_RGB16F, GL_RG16F, GL_R16F,
+};
+
+static const FormatKey s_oesDepth24SizedFormats[] =
+{
+	GL_DEPTH_COMPONENT24
+};
+
+static const FormatKey s_oesDepth32SizedFormats[] =
+{
+	GL_DEPTH_COMPONENT32
+};
+
+static const FormatKey s_oesRgb8Rgba8RboFormats[] =
+{
+	GL_RGB8,
+	GL_RGBA8,
+};
+
+static const FormatKey s_oesRequiredInternalFormatRgb8ColorFormat[] =
+{
+	GL_RGB8,
+};
+
+static const FormatKey s_extTextureType2101010RevFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_UNSIGNED_INT_2_10_10_10_REV),
+	GLS_UNSIZED_FORMATKEY(GL_RGB,	GL_UNSIGNED_INT_2_10_10_10_REV),
+};
+
+static const FormatKey s_oesRequiredInternalFormat10bitColorFormats[] =
+{
+	GL_RGB10_A2, GL_RGB10,
+};
+
+static const FormatKey s_extTextureRgRboFormats[] =
+{
+	GL_R8, GL_RG8,
+};
+
+static const FormatKey s_extTextureRgTexFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_RED,	GL_UNSIGNED_BYTE),
+	GLS_UNSIZED_FORMATKEY(GL_RG,	GL_UNSIGNED_BYTE),
+};
+
+static const FormatKey s_extTextureRgFloatTexFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_RED,	GL_FLOAT),
+	GLS_UNSIZED_FORMATKEY(GL_RG,	GL_FLOAT),
+};
+
+static const FormatKey s_extTextureRgHalfFloatTexFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_RED,	GL_HALF_FLOAT_OES),
+	GLS_UNSIZED_FORMATKEY(GL_RG,	GL_HALF_FLOAT_OES),
+};
+
+static const FormatKey s_nvPackedFloatRboFormats[] =
+{
+	GL_R11F_G11F_B10F,
+};
+
+static const FormatKey s_nvPackedFloatTexFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_RGB,	GL_UNSIGNED_INT_10F_11F_11F_REV),
+};
+
+static const FormatKey s_extSrgbRboFormats[] =
+{
+	GL_SRGB8_ALPHA8,
+};
+
+static const FormatKey s_extSrgbTexFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_SRGB,			GL_UNSIGNED_BYTE),
+	GLS_UNSIZED_FORMATKEY(GL_SRGB_ALPHA,	GL_UNSIGNED_BYTE),
+};
+
+static const FormatKey s_oesRgb8Rgba8TexFormats[] =
+{
+	GLS_UNSIZED_FORMATKEY(GL_RGB,		GL_UNSIGNED_BYTE),
+	GLS_UNSIZED_FORMATKEY(GL_RGBA,		GL_UNSIGNED_BYTE),
+};
+
+static const FormatExtEntry s_esExtFormats[] =
+{
+	{
+		"GL_OES_depth_texture",
+		REQUIRED_RENDERABLE | DEPTH_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesDepthTextureFormats),
+	},
+	{
+		"GL_OES_packed_depth_stencil",
+		REQUIRED_RENDERABLE | DEPTH_RENDERABLE | STENCIL_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_oesPackedDepthStencilSizedFormats)
+	},
+	{
+		"GL_OES_packed_depth_stencil GL_OES_required_internalformat",
+		TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesPackedDepthStencilSizedFormats)
+	},
+	{
+		"GL_OES_packed_depth_stencil",
+		DEPTH_RENDERABLE | STENCIL_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesPackedDepthStencilTexFormats)
+	},
+	// \todo [2013-12-10 lauri] Find out if OES_texture_half_float is really a
+	// requirement on ES3 also. Or is color_buffer_half_float applicatble at
+	// all on ES3, since there's also EXT_color_buffer_float?
+	{
+		"GL_OES_texture_half_float GL_EXT_color_buffer_half_float",
+		REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_extColorBufferHalfFloatFormats)
+	},
+
+	// OES_required_internalformat doesn't actually specify that these are renderable,
+	// since it was written against ES 1.1.
+	{
+		"GL_OES_required_internalformat",
+		 // Allow but don't require RGBA8 to be color-renderable if
+		 // OES_rgb8_rgba8 is not present.
+		COLOR_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesRequiredInternalFormatColorFormats)
+	},
+	{
+		"GL_OES_required_internalformat",
+		DEPTH_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesRequiredInternalFormatDepthFormats)
+	},
+	{
+		"GL_EXT_texture_rg",
+		REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_extTextureRgRboFormats)
+	},
+	// These are not specified to be color-renderable, but the wording is
+	// exactly as ambiguous as the wording in the ES2 spec.
+	{
+		"GL_EXT_texture_rg",
+		COLOR_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_extTextureRgTexFormats)
+	},
+	{
+		"GL_EXT_texture_rg GL_OES_texture_float",
+		COLOR_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_extTextureRgFloatTexFormats)
+	},
+	{
+		"GL_EXT_texture_rg GL_OES_texture_half_float",
+		COLOR_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_extTextureRgHalfFloatTexFormats)
+	},
+
+	{
+		"GL_NV_packed_float",
+		COLOR_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_nvPackedFloatTexFormats)
+	},
+	{
+		"GL_NV_packed_float GL_EXT_color_buffer_half_float",
+		REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_nvPackedFloatRboFormats)
+	},
+
+	// Some Tegra drivers report GL_EXT_packed_float even for ES. Treat it as
+	// a synonym for the NV_ version.
+	{
+		"GL_EXT_packed_float",
+		COLOR_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_nvPackedFloatTexFormats)
+	},
+	{
+		"GL_EXT_packed_float GL_EXT_color_buffer_half_float",
+		REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_nvPackedFloatRboFormats)
+	},
+
+	{
+		"GL_EXT_sRGB",
+		COLOR_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_extSrgbTexFormats)
+	},
+	{
+		"GL_EXT_sRGB",
+		REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_extSrgbRboFormats)
+	},
+
+	 // In Khronos bug 7333 discussion, the consensus is that these texture
+	 // formats, at least, should be color-renderable. Still, that cannot be
+	 // found in any extension specs, so only allow it, not require it.
+	{
+		"GL_OES_rgb8_rgba8",
+		COLOR_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesRgb8Rgba8TexFormats)
+	},
+	{
+		"GL_OES_rgb8_rgba8",
+		REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_oesRgb8Rgba8RboFormats)
+	},
+	{
+		"GL_OES_rgb8_rgba8 GL_OES_required_internalformat",
+		TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesRequiredInternalFormatRgb8ColorFormat)
+	},
+
+	// The depth-renderability of the depth RBO formats is not explicitly
+	// spelled out, but all renderbuffer formats are meant to be renderable.
+	{
+		"GL_OES_depth24",
+		REQUIRED_RENDERABLE | DEPTH_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_oesDepth24SizedFormats)
+	},
+	{
+		"GL_OES_depth24 GL_OES_required_internalformat GL_OES_depth_texture",
+		TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesDepth24SizedFormats)
+	},
+
+	{
+		"GL_OES_depth32",
+		REQUIRED_RENDERABLE | DEPTH_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_oesDepth32SizedFormats)
+	},
+	{
+		"GL_OES_depth32 GL_OES_required_internalformat GL_OES_depth_texture",
+		TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_oesDepth32SizedFormats)
+	},
+
+	{
+		"GL_EXT_texture_type_2_10_10_10_REV",
+		TEXTURE_VALID, // explicitly unrenderable
+		GLS_ARRAY_RANGE(s_extTextureType2101010RevFormats)
+	},
+	{
+		"GL_EXT_texture_type_2_10_10_10_REV GL_OES_required_internalformat",
+		TEXTURE_VALID, // explicitly unrenderable
+		GLS_ARRAY_RANGE(s_oesRequiredInternalFormat10bitColorFormats)
+	},
+};
+
+Context::Context (TestContext& testCtx,
+				  RenderContext& renderCtx,
+				  CheckerFactory& factory)
+	: m_testCtx				(testCtx)
+	, m_renderCtx			(renderCtx)
+	, m_verifier			(m_ctxFormats, factory)
+	, m_haveMultiColorAtts	(false)
+{
+	FormatExtEntries extRange = GLS_ARRAY_RANGE(s_esExtFormats);
+	addExtFormats(extRange);
+}
+
+void Context::addFormats (FormatEntries fmtRange)
+{
+	FboUtil::addFormats(m_minFormats, fmtRange);
+	FboUtil::addFormats(m_ctxFormats, fmtRange);
+	FboUtil::addFormats(m_maxFormats, fmtRange);
+}
+
+void Context::addExtFormats (FormatExtEntries extRange)
+{
+	FboUtil::addExtFormats(m_ctxFormats, extRange, &m_renderCtx);
+	FboUtil::addExtFormats(m_maxFormats, extRange, DE_NULL);
+}
+
+void TestBase::pass (void)
+{
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+}
+
+void TestBase::qualityWarning (const char* msg)
+{
+	m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, msg);
+}
+
+void TestBase::fail (const char* msg)
+{
+	m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, msg);
+}
+
+static string statusName (GLenum status)
+{
+	const char* errorName = getErrorName(status);
+	if (status != GL_NO_ERROR && errorName != DE_NULL)
+		return string(errorName) + " (during FBO initialization)";
+
+	const char* fbStatusName = getFramebufferStatusName(status);
+	if (fbStatusName != DE_NULL)
+		return fbStatusName;
+
+	return "unknown value (" + toString(status) + ")";
+}
+
+const glw::Functions& gl (const TestBase& test)
+{
+	return test.getContext().getRenderContext().getFunctions();
+}
+
+IterateResult TestBase::iterate (void)
+{
+	glu::Framebuffer fbo(m_ctx.getRenderContext());
+	FboBuilder builder(*fbo, GL_FRAMEBUFFER, gl(*this));
+	const IterateResult ret = build(builder);
+	const StatusCodes statuses = m_ctx.getVerifier().validStatusCodes(builder);
+
+	GLenum glStatus = builder.getError();
+	if (glStatus == GL_NO_ERROR)
+		glStatus = gl(*this).checkFramebufferStatus(GL_FRAMEBUFFER);
+
+	// \todo [2013-12-04 lauri] Check if drawing operations succeed.
+
+	StatusCodes::const_iterator it = statuses.begin();
+	GLenum err = *it++;
+	logFramebufferConfig(builder, m_testCtx.getLog());
+
+	MessageBuilder msg(&m_testCtx.getLog());
+
+	msg << "Expected ";
+	if (it != statuses.end())
+	{
+		msg << "one of ";
+		while (it != statuses.end())
+		{
+			msg << statusName(err);
+			err = *it++;
+			msg << (it == statuses.end() ? " or " : ", ");
+		}
+	}
+	msg << statusName(err) << "." << TestLog::EndMessage;
+	m_testCtx.getLog() << TestLog::Message << "Received " << statusName(glStatus)
+			 << "." << TestLog::EndMessage;
+
+	if (!contains(statuses, glStatus))
+	{
+		// The returned status value was not acceptable.
+		if (glStatus == GL_FRAMEBUFFER_COMPLETE)
+			fail("Framebuffer checked as complete, expected incomplete");
+		else if (statuses.size() == 1 && contains(statuses, GL_FRAMEBUFFER_COMPLETE))
+			fail("Framebuffer checked is incomplete, expected complete");
+		else
+			// An incomplete status is allowed, but not _this_ incomplete status.
+			fail("Framebuffer checked as incomplete, but with wrong status");
+	}
+	else if (glStatus != GL_FRAMEBUFFER_COMPLETE &&
+			 contains(statuses, GL_FRAMEBUFFER_COMPLETE))
+	{
+		qualityWarning("Framebuffer object could have checked as complete but did not.");
+	}
+	else
+		pass();
+
+	return ret;
+}
+
+IterateResult TestBase::build (FboBuilder& builder)
+{
+	DE_UNREF(builder);
+	return STOP;
+}
+
+ImageFormat TestBase::getDefaultFormat (GLenum attPoint, GLenum bufType) const
+{
+	if (bufType == GL_NONE)
+	{
+		return ImageFormat::none();
+	}
+
+	// Prefer a standard format, if there is one, but if not, use a format
+	// provided by an extension.
+	Formats formats = m_ctx.getMinFormats().getFormats(formatFlag(attPoint) |
+														 formatFlag(bufType));
+	Formats::const_iterator it = formats.begin();
+	if (it == formats.end())
+	{
+		formats = m_ctx.getCtxFormats().getFormats(formatFlag(attPoint) |
+													 formatFlag(bufType));
+		it = formats.begin();
+	}
+	if (it == formats.end())
+		throw tcu::NotSupportedError("Unsupported attachment kind for attachment point",
+									 "", __FILE__, __LINE__);
+	return *it;
+};
+
+Image* makeImage (GLenum bufType, ImageFormat format,
+				  GLsizei width, GLsizei height, FboBuilder& builder)
+{
+	Image* image = DE_NULL;
+	switch (bufType)
+	{
+		case GL_NONE:
+			return DE_NULL;
+		case GL_RENDERBUFFER:
+			image = &builder.makeConfig<Renderbuffer>();
+			break;
+		case GL_TEXTURE:
+			image = &builder.makeConfig<Texture2D>();
+			break;
+		default:
+			DE_ASSERT(!"Impossible case");
+	}
+	image->internalFormat = format;
+	image->width = width;
+	image->height = height;
+	return image;
+}
+
+Attachment* makeAttachment (GLenum bufType, ImageFormat format,
+							GLsizei width, GLsizei height, FboBuilder& builder)
+{
+	Image* const imgCfg = makeImage (bufType, format, width, height, builder);
+	Attachment* att = DE_NULL;
+	GLuint img = 0;
+
+	if (Renderbuffer* rboCfg = dynamic_cast<Renderbuffer*>(imgCfg))
+	{
+		img = builder.glCreateRbo(*rboCfg);
+		att = &builder.makeConfig<RenderbufferAttachment>();
+	}
+	else if (Texture2D* texCfg = dynamic_cast<Texture2D*>(imgCfg))
+	{
+		img = builder.glCreateTexture(*texCfg);
+		TextureFlatAttachment& texAtt = builder.makeConfig<TextureFlatAttachment>();
+		texAtt.texTarget = GL_TEXTURE_2D;
+		att = &texAtt;
+	}
+	else
+	{
+		DE_ASSERT(imgCfg == DE_NULL);
+		return DE_NULL;
+	}
+	att->imageName = img;
+	return att;
+}
+
+void TestBase::attachTargetToNew (GLenum target, GLenum bufType, ImageFormat format,
+								  GLsizei width, GLsizei height, FboBuilder& builder)
+{
+	ImageFormat imgFmt = format;
+	if (imgFmt.format == GL_NONE)
+		imgFmt = getDefaultFormat(target, bufType);
+
+	const Attachment* const att = makeAttachment(bufType, imgFmt, width, height, builder);
+	builder.glAttach(target, att);
+}
+
+static string formatName (ImageFormat format)
+{
+	const string s = getPixelFormatName(format.format);
+	const string fmtStr = toLower(s.substr(3));
+
+	if (format.unsizedType != GL_NONE)
+	{
+		const string typeStr = getTypeName(format.unsizedType);
+		return fmtStr + "_" + toLower(typeStr.substr(3));
+	}
+
+	return fmtStr;
+}
+
+static string formatDesc (ImageFormat format)
+{
+	const string fmtStr = getPixelFormatName(format.format);
+
+	if (format.unsizedType != GL_NONE)
+	{
+		const string typeStr = getTypeName(format.unsizedType);
+		return fmtStr + " with type " + typeStr;
+	}
+
+	return fmtStr;
+}
+
+struct RenderableParams
+{
+	GLenum				attPoint;
+	GLenum				bufType;
+	ImageFormat 		format;
+	static string		getName				(const RenderableParams& params)
+	{
+		return formatName(params.format);
+	}
+	static string		getDescription		(const RenderableParams& params)
+	{
+		return formatDesc(params.format);
+	}
+};
+
+class RenderableTest : public ParamTest<RenderableParams>
+{
+public:
+					RenderableTest		(Context& group, const Params& params)
+						: ParamTest<RenderableParams> (group, params) {}
+	IterateResult	build				(FboBuilder& builder);
+};
+
+IterateResult RenderableTest::build (FboBuilder& builder)
+{
+	attachTargetToNew(m_params.attPoint, m_params.bufType, m_params.format, 64, 64, builder);
+	return STOP;
+}
+
+string attTypeName (GLenum bufType)
+{
+	switch (bufType)
+	{
+		case GL_NONE:
+			return "none";
+		case GL_RENDERBUFFER:
+			return "rbo";
+		case GL_TEXTURE:
+			return "tex";
+		default:
+			DE_ASSERT(!"Impossible case");
+	}
+	return ""; // Shut up compiler
+}
+
+struct AttachmentParams
+{
+	GLenum						color0Kind;
+	GLenum						colornKind;
+	GLenum						depthKind;
+	GLenum						stencilKind;
+
+	static string		getName			(const AttachmentParams& params);
+	static string		getDescription	(const AttachmentParams& params)
+	{
+		return getName(params);
+	}
+};
+
+string AttachmentParams::getName (const AttachmentParams& params)
+{
+	return (attTypeName(params.color0Kind) + "_" +
+			attTypeName(params.colornKind) + "_" +
+			attTypeName(params.depthKind) + "_" +
+			attTypeName(params.stencilKind));
+}
+
+//! Test for combinations of different kinds of attachments
+class AttachmentTest : public ParamTest<AttachmentParams>
+{
+public:
+					AttachmentTest		(Context& group, Params& params)
+						: ParamTest<AttachmentParams> (group, params) {}
+
+protected:
+	IterateResult 	build				(FboBuilder& builder);
+	void			makeDepthAndStencil	(FboBuilder& builder);
+};
+
+
+void AttachmentTest::makeDepthAndStencil (FboBuilder& builder)
+{
+	if (m_params.stencilKind == m_params.depthKind)
+	{
+		// If there is a common stencil+depth -format, try to use a common
+		// image for both attachments.
+		const FormatFlags flags =
+			DEPTH_RENDERABLE | STENCIL_RENDERABLE | formatFlag(m_params.stencilKind);
+		const Formats& formats = m_ctx.getMinFormats().getFormats(flags);
+		Formats::const_iterator it = formats.begin();
+		if (it != formats.end())
+		{
+			const ImageFormat format = *it;
+			Attachment* att = makeAttachment(m_params.depthKind, format, 64, 64, builder);
+			builder.glAttach(GL_DEPTH_ATTACHMENT, att);
+			builder.glAttach(GL_STENCIL_ATTACHMENT, att);
+			return;
+		}
+	}
+	// Either the kinds were separate, or a suitable format was not found.
+	// Create separate images.
+	attachTargetToNew(GL_STENCIL_ATTACHMENT, m_params.stencilKind, ImageFormat::none(),
+					  64, 64, builder);
+	attachTargetToNew(GL_DEPTH_ATTACHMENT, m_params.depthKind, ImageFormat::none(),
+					  64, 64, builder);
+}
+
+IterateResult AttachmentTest::build (FboBuilder& builder)
+{
+	attachTargetToNew(GL_COLOR_ATTACHMENT0, m_params.color0Kind, ImageFormat::none(),
+					  64, 64, builder);
+
+	if (m_params.colornKind != GL_NONE)
+	{
+		TCU_CHECK_AND_THROW(NotSupportedError, m_ctx.haveMultiColorAtts(),
+							"Multiple attachments not supported");
+		GLint maxAttachments = 1;
+		gl(*this).getIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxAttachments);
+		GLU_EXPECT_NO_ERROR(
+			gl(*this).getError(), "Couldn't read GL_MAX_COLOR_ATTACHMENTS");
+
+		for (int i = 1; i < maxAttachments; i++)
+		{
+			attachTargetToNew(GL_COLOR_ATTACHMENT0 + i, m_params.colornKind,
+							  ImageFormat::none(), 64, 64, builder);
+		}
+	}
+
+	makeDepthAndStencil(builder);
+
+	return STOP;
+}
+
+class EmptyImageTest : public TestBase
+{
+public:
+					EmptyImageTest	(Context& group,
+									 const char* name, const char* desc)
+						: TestBase	(group, name, desc) {}
+
+	IterateResult	build			(FboBuilder& builder);
+};
+
+IterateResult EmptyImageTest::build (FboBuilder& builder)
+{
+	attachTargetToNew(GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, ImageFormat::none(),
+					  0, 0, builder);
+	return STOP;
+}
+
+
+class DistinctSizeTest : public TestBase
+{
+public:
+					DistinctSizeTest	(Context& group,
+										 const char* name, const char* desc)
+						: TestBase		(group, name, desc) {}
+
+	IterateResult	build				(FboBuilder& builder);
+};
+
+IterateResult DistinctSizeTest::build (FboBuilder& builder)
+{
+	attachTargetToNew(GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, ImageFormat::none(),
+					  64, 64, builder);
+	attachTargetToNew(GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, ImageFormat::none(),
+					  128, 128, builder);
+	return STOP;
+}
+
+TestCaseGroup* Context::createRenderableTests (void)
+{
+	TestCaseGroup* const renderableTests = new TestCaseGroup(
+		m_testCtx, "renderable", "Tests for support of renderable image formats");
+
+	TestCaseGroup* const rbRenderableTests = new TestCaseGroup(
+		m_testCtx, "renderbuffer", "Tests for renderbuffer formats");
+
+	TestCaseGroup* const texRenderableTests = new TestCaseGroup(
+		m_testCtx, "texture", "Tests for texture formats");
+
+	static const struct AttPoint {
+		GLenum 			attPoint;
+		const char* 	name;
+		const char* 	desc;
+	} attPoints[] =
+	{
+		{ GL_COLOR_ATTACHMENT0,		"color0",	"Tests for color attachments"	},
+		{ GL_STENCIL_ATTACHMENT,	"stencil",	"Tests for stencil attachments" },
+		{ GL_DEPTH_ATTACHMENT,		"depth",	"Tests for depth attachments"	},
+	};
+
+	// At each attachment point, iterate through all the possible formats to
+	// detect both false positives and false negatives.
+	const Formats rboFmts = m_maxFormats.getFormats(ANY_FORMAT);
+	const Formats texFmts = m_maxFormats.getFormats(ANY_FORMAT);
+
+	for (const AttPoint* it = DE_ARRAY_BEGIN(attPoints); it != DE_ARRAY_END(attPoints); it++)
+	{
+		TestCaseGroup* const rbAttTests = new TestCaseGroup(m_testCtx, it->name, it->desc);
+		TestCaseGroup* const texAttTests = new TestCaseGroup(m_testCtx, it->name, it->desc);
+
+		for (Formats::const_iterator it2 = rboFmts.begin(); it2 != rboFmts.end(); it2++)
+		{
+			const RenderableParams params = { it->attPoint, GL_RENDERBUFFER, *it2 };
+			rbAttTests->addChild(new RenderableTest(*this, params));
+		}
+		rbRenderableTests->addChild(rbAttTests);
+
+		for (Formats::const_iterator it2 = texFmts.begin(); it2 != texFmts.end(); it2++)
+		{
+			const RenderableParams params = { it->attPoint, GL_TEXTURE, *it2 };
+			texAttTests->addChild(new RenderableTest(*this, params));
+		}
+		texRenderableTests->addChild(texAttTests);
+	}
+	renderableTests->addChild(rbRenderableTests);
+	renderableTests->addChild(texRenderableTests);
+
+	return renderableTests;
+}
+
+TestCaseGroup* Context::createAttachmentTests (void)
+{
+	TestCaseGroup* const attCombTests = new TestCaseGroup(
+		m_testCtx, "attachment_combinations", "Tests for attachment combinations");
+
+	static const GLenum s_bufTypes[] = { GL_NONE, GL_RENDERBUFFER, GL_TEXTURE };
+	static const Range<GLenum> s_kinds = GLS_ARRAY_RANGE(s_bufTypes);
+
+	for (const GLenum* col0 = s_kinds.begin(); col0 != s_kinds.end(); ++col0)
+		for (const GLenum* coln = s_kinds.begin(); coln != s_kinds.end(); ++coln)
+			for (const GLenum* dep = s_kinds.begin(); dep != s_kinds.end(); ++dep)
+				for (const GLenum* stc = s_kinds.begin(); stc != s_kinds.end(); ++stc)
+				{
+					AttachmentParams params = { *col0, *coln, *dep, *stc };
+					attCombTests->addChild(new AttachmentTest(*this, params));
+				}
+
+	return attCombTests;
+}
+
+TestCaseGroup* Context::createSizeTests (void)
+{
+	TestCaseGroup* const sizeTests = new TestCaseGroup(
+		m_testCtx, "size", "Tests for attachment sizes");
+	sizeTests->addChild(new EmptyImageTest(
+							*this, "zero",
+							"Test for zero-sized image attachment"));
+	sizeTests->addChild(new DistinctSizeTest(
+							*this, "distinct",
+							"Test for attachments with different sizes"));
+
+	return sizeTests;
+}
+
+} // details
+
+} // fboc
+} // gls
+} // deqp
diff --git a/modules/glshared/glsFboCompletenessTests.hpp b/modules/glshared/glsFboCompletenessTests.hpp
new file mode 100644
index 0000000..b284731
--- /dev/null
+++ b/modules/glshared/glsFboCompletenessTests.hpp
@@ -0,0 +1,147 @@
+#ifndef _GLSFBOCOMPLETENESSTESTS_HPP
+#define _GLSFBOCOMPLETENESSTESTS_HPP
+
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Common parts for ES2/3 framebuffer completeness tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuTestCase.hpp"
+#include "gluRenderContext.hpp"
+#include "glsFboUtil.hpp"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuTestLog.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+namespace fboc
+{
+
+namespace details
+{
+
+using glu::RenderContext;
+using tcu::TestCase;
+using tcu::TestContext;
+typedef TestCase::IterateResult IterateResult;
+using tcu::TestCaseGroup;
+using tcu::TestLog;
+using std::string;
+
+using namespace glw;
+using namespace deqp::gls::FboUtil;
+using namespace deqp::gls::FboUtil::config;
+
+class Context
+{
+public:
+							Context					(TestContext& testCtx,
+													 RenderContext& renderCtx,
+													 CheckerFactory& factory);
+	RenderContext&			getRenderContext		(void) const { return m_renderCtx; }
+	TestContext&			getTestContext			(void) const { return m_testCtx; }
+	const FboVerifier&		getVerifier				(void) const { return m_verifier; }
+	const FormatDB&			getMinFormats			(void) const { return m_minFormats; }
+	const FormatDB&			getCtxFormats			(void) const { return m_ctxFormats; }
+	bool					haveMultiColorAtts		(void) const { return m_haveMultiColorAtts; }
+	void					setHaveMulticolorAtts	(bool have) { m_haveMultiColorAtts = have; }
+	void					addFormats				(FormatEntries fmtRange);
+	void					addExtFormats			(FormatExtEntries extRange);
+	TestCaseGroup*			createRenderableTests	(void);
+	TestCaseGroup*			createAttachmentTests	(void);
+	TestCaseGroup*			createSizeTests			(void);
+private:
+	TestContext&			m_testCtx;
+	RenderContext&			m_renderCtx;
+	FormatDB				m_minFormats;
+	FormatDB				m_ctxFormats;
+	FormatDB				m_maxFormats;
+	FboVerifier				m_verifier;
+	bool					m_haveMultiColorAtts;
+};
+
+class TestBase : public TestCase
+{
+public:
+	Context&				getContext			(void) const { return m_ctx; }
+
+protected:
+
+							TestBase			(Context& ctx,
+												 const string& name, const string& desc)
+								: TestCase		(ctx.getTestContext(),
+												 name.c_str(), desc.c_str())
+								, m_ctx			(ctx) {}
+	void					fail				(const char* msg);
+	void					qualityWarning		(const char* msg);
+	void					pass				(void);
+	void					checkFbo			(FboBuilder& builder);
+	ImageFormat				getDefaultFormat	(GLenum attPoint, GLenum bufType) const;
+
+	IterateResult			iterate				(void);
+
+	virtual IterateResult	build				(FboBuilder& builder);
+
+	void					attachTargetToNew	(GLenum target, GLenum bufType,
+												 ImageFormat format,
+												 GLsizei width, GLsizei height,
+												 FboBuilder& builder);
+	Context&				m_ctx;
+};
+
+	// Utilities for building
+Image* 				makeImage			(GLenum bufType, ImageFormat format,
+										 GLsizei width, GLsizei height, FboBuilder& builder);
+Attachment*			makeAttachment		(GLenum bufType, ImageFormat format,
+										 GLsizei width, GLsizei height, FboBuilder& builder);
+
+template <typename P>
+class ParamTest : public TestBase
+{
+public:
+	typedef P	Params;
+				ParamTest		(Context& ctx, const Params& params)
+					: TestBase	(ctx, Params::getName(params), Params::getDescription(params))
+					, m_params	(params) {}
+
+protected:
+	Params		m_params;
+};
+
+// Shorthand utility
+const glw::Functions& gl (const TestBase& test);
+
+} // details
+
+using details::Context;
+using details::TestBase;
+using details::ParamTest;
+using details::gl;
+
+} // fboc
+} // gls
+} // deqp
+
+#endif // _GLSFBOCOMPLETENESSTESTS_HPP
diff --git a/modules/glshared/glsFboUtil.cpp b/modules/glshared/glsFboUtil.cpp
new file mode 100644
index 0000000..9d312a0
--- /dev/null
+++ b/modules/glshared/glsFboUtil.cpp
@@ -0,0 +1,635 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Utilities for framebuffer objects.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsFboUtil.hpp"
+
+#include "glwEnums.hpp"
+#include "deUniquePtr.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "deStringUtil.hpp"
+#include <sstream>
+
+using namespace glw;
+using tcu::TestLog;
+using tcu::TextureFormat;
+using tcu::NotSupportedError;
+using glu::TransferFormat;
+using glu::mapGLInternalFormat;
+using glu::mapGLTransferFormat;
+using glu::getPixelFormatName;
+using glu::getTypeName;
+using glu::getFramebufferTargetName;
+using glu::getFramebufferAttachmentName;
+using glu::getFramebufferAttachmentTypeName;
+using glu::getTextureTargetName;
+using glu::getTransferFormat;
+using glu::ContextInfo;
+using glu::ContextType;
+using glu::RenderContext;
+using de::UniquePtr;
+using de::toString;
+using std::set;
+using std::vector;
+using std::string;
+using std::istringstream;
+using std::istream_iterator;
+
+namespace deqp
+{
+namespace gls
+{
+
+namespace FboUtil
+{
+
+
+void FormatDB::addFormat (ImageFormat format, FormatFlags newFlags)
+{
+	FormatFlags& flags = m_map[format];
+	flags = FormatFlags(flags | newFlags);
+}
+
+// Not too fast at the moment, might consider indexing?
+Formats FormatDB::getFormats (FormatFlags requirements) const
+{
+	Formats ret;
+	for (FormatMap::const_iterator it = m_map.begin(); it != m_map.end(); it++)
+	{
+		if ((it->second & requirements) == requirements)
+			ret.insert(it->first);
+	}
+	return ret;
+}
+
+FormatFlags FormatDB::getFormatInfo (ImageFormat format, FormatFlags fallback) const
+{
+	return lookupDefault(m_map, format, fallback);
+}
+
+void addFormats (FormatDB& db, FormatEntries stdFmts)
+{
+	for (const FormatEntry* it = stdFmts.begin(); it != stdFmts.end(); it++)
+	{
+		for (const FormatKey* it2 = it->second.begin(); it2 != it->second.end(); it2++)
+			db.addFormat(formatKeyInfo(*it2), it->first);
+	}
+}
+
+void addExtFormats (FormatDB& db, FormatExtEntries extFmts, const RenderContext* ctx)
+{
+	const UniquePtr<ContextInfo> ctxInfo(ctx != DE_NULL ? ContextInfo::create(*ctx) : DE_NULL);
+	for (const FormatExtEntry* it = extFmts.begin(); it != extFmts.end(); it++)
+	{
+		bool supported = true;
+		if (ctxInfo)
+		{
+			istringstream tokenStream(string(it->extensions));
+			istream_iterator<string> tokens((tokenStream)), end;
+
+			while (tokens != end)
+			{
+				if (!ctxInfo->isExtensionSupported(tokens->c_str()))
+				{
+					supported = false;
+					break;
+				}
+				++tokens;
+			}
+		}
+		if (supported)
+			for (const FormatKey* i2 = it->formats.begin(); i2 != it->formats.end(); i2++)
+				db.addFormat(formatKeyInfo(*i2), FormatFlags(it->flags));
+	}
+}
+
+FormatFlags formatFlag (GLenum context)
+{
+	switch (context)
+	{
+		case GL_NONE:
+			return FormatFlags(0);
+		case GL_RENDERBUFFER:
+			return RENDERBUFFER_VALID;
+		case GL_TEXTURE:
+			return TEXTURE_VALID;
+		case GL_STENCIL_ATTACHMENT:
+			return STENCIL_RENDERABLE;
+		case GL_DEPTH_ATTACHMENT:
+			return DEPTH_RENDERABLE;
+		default:
+			DE_ASSERT(context >= GL_COLOR_ATTACHMENT0 && context <= GL_COLOR_ATTACHMENT15);
+			return COLOR_RENDERABLE;
+	}
+}
+
+namespace config {
+
+GLsizei	imageNumSamples	(const Image& img)
+{
+	if (const Renderbuffer* rbo = dynamic_cast<const Renderbuffer*>(&img))
+		return rbo->numSamples;
+	return 0;
+}
+
+static GLenum glTarget (const Image& img)
+{
+	if (dynamic_cast<const Renderbuffer*>(&img) != DE_NULL)
+		return GL_RENDERBUFFER;
+	if (dynamic_cast<const Texture2D*>(&img) != DE_NULL)
+		return GL_TEXTURE_2D;
+	if (dynamic_cast<const TextureCubeMap*>(&img) != DE_NULL)
+		return GL_TEXTURE_CUBE_MAP;
+	if (dynamic_cast<const Texture3D*>(&img) != DE_NULL)
+		return GL_TEXTURE_3D;
+	if (dynamic_cast<const Texture2DArray*>(&img) != DE_NULL)
+		return GL_TEXTURE_2D_ARRAY;
+
+	DE_ASSERT(!"Impossible image type");
+	return GL_NONE;
+}
+
+static void glInitFlat (const TextureFlat& cfg, GLenum target, const glw::Functions& gl)
+{
+	const TransferFormat format = transferImageFormat(cfg.internalFormat);
+	GLint w = cfg.width;
+	GLint h = cfg.height;
+	for (GLint level = 0; level < cfg.numLevels; level++)
+	{
+		gl.texImage2D(target, level, cfg.internalFormat.format, w, h, 0,
+					  format.format, format.dataType, DE_NULL);
+		w = de::max(1, w / 2);
+		h = de::max(1, h / 2);
+	}
+}
+
+static void glInitLayered (const TextureLayered& cfg,
+						   GLint depth_divider, const glw::Functions& gl)
+{
+	const TransferFormat format = transferImageFormat(cfg.internalFormat);
+	GLint w = cfg.width;
+	GLint h = cfg.height;
+	GLint depth = cfg.numLayers;
+	for (GLint level = 0; level < cfg.numLevels; level++)
+	{
+		gl.texImage3D(glTarget(cfg), level, cfg.internalFormat.format, w, h, depth, 0,
+					  format.format, format.dataType, DE_NULL);
+		w = de::max(1, w / 2);
+		h = de::max(1, h / 2);
+		depth = de::max(1, depth / depth_divider);
+	}
+}
+
+static void glInit (const Texture& cfg, const glw::Functions& gl)
+{
+	if (const Texture2D* t2d = dynamic_cast<const Texture2D*>(&cfg))
+		glInitFlat(*t2d, glTarget(*t2d), gl);
+	else if (const TextureCubeMap* tcm = dynamic_cast<const TextureCubeMap*>(&cfg))
+	{
+		// \todo [2013-12-05 lauri]
+		// move this to glu or someplace sensible (this array is already
+		// present in duplicates)
+		static const GLenum s_cubeMapFaces[] =
+			{
+				GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+				GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+				GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+				GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+			};
+		const Range<GLenum> range = GLS_ARRAY_RANGE(s_cubeMapFaces);
+		for (const GLenum* it = range.begin(); it != range.end(); it++)
+			glInitFlat(*tcm, *it, gl);
+	}
+	else if (const Texture3D* t3d = dynamic_cast<const Texture3D*>(&cfg))
+		glInitLayered(*t3d, 2, gl);
+	else if (const Texture2DArray* t2a = dynamic_cast<const Texture2DArray*>(&cfg))
+		glInitLayered(*t2a, 1, gl);
+}
+
+static GLuint glCreate (const Image& cfg, const glw::Functions& gl)
+{
+	GLuint ret = 0;
+	if (const Renderbuffer* const rbo = dynamic_cast<const Renderbuffer*>(&cfg))
+	{
+		gl.genRenderbuffers(1, &ret);
+		gl.bindRenderbuffer(GL_RENDERBUFFER, ret);
+
+		if (rbo->numSamples == 0)
+			gl.renderbufferStorage(GL_RENDERBUFFER, rbo->internalFormat.format,
+								   rbo->width, rbo->height);
+		else
+			gl.renderbufferStorageMultisample(
+				GL_RENDERBUFFER, rbo->numSamples, rbo->internalFormat.format,
+				rbo->width, rbo->height);
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
+	}
+	else if (const Texture* const tex = dynamic_cast<const Texture*>(&cfg))
+	{
+		gl.genTextures(1, &ret);
+		gl.bindTexture(glTarget(*tex), ret);
+		glInit(*tex, gl);
+		gl.bindTexture(glTarget(*tex), 0);
+	}
+	else
+		DE_ASSERT(!"Impossible image type");
+	return ret;
+}
+
+static void glDelete (const Image& cfg, GLuint img, const glw::Functions& gl)
+{
+	if (dynamic_cast<const Renderbuffer*>(&cfg) != DE_NULL)
+		gl.deleteRenderbuffers(1, &img);
+	else if (dynamic_cast<const Texture*>(&cfg) != DE_NULL)
+		gl.deleteTextures(1, &img);
+	else
+		DE_ASSERT(!"Impossible image type");
+}
+
+static void attachAttachment (const Attachment& att, GLenum attPoint,
+							  const glw::Functions& gl)
+{
+	if (const RenderbufferAttachment* const rAtt =
+		dynamic_cast<const RenderbufferAttachment*>(&att))
+		gl.framebufferRenderbuffer(rAtt->target, attPoint,
+								   rAtt->renderbufferTarget, rAtt->imageName);
+	else if (const TextureFlatAttachment* const fAtt =
+			 dynamic_cast<const TextureFlatAttachment*>(&att))
+		gl.framebufferTexture2D(fAtt->target, attPoint,
+								fAtt->texTarget, fAtt->imageName, fAtt->level);
+	else if (const TextureLayerAttachment* const lAtt =
+			 dynamic_cast<const TextureLayerAttachment*>(&att))
+		gl.framebufferTextureLayer(lAtt->target, attPoint,
+								   lAtt->imageName, lAtt->level, lAtt->layer);
+	else
+		DE_ASSERT(!"Impossible attachment type");
+}
+
+GLenum attachmentType (const Attachment& att)
+{
+	if (dynamic_cast<const RenderbufferAttachment*>(&att) != DE_NULL)
+		return GL_RENDERBUFFER;
+	else if (dynamic_cast<const TextureAttachment*>(&att) != DE_NULL)
+		return GL_TEXTURE;
+
+	DE_ASSERT(!"Impossible attachment type");
+	return GL_NONE;
+}
+
+static GLsizei textureLayer (const TextureAttachment& tAtt)
+{
+	if (dynamic_cast<const TextureFlatAttachment*>(&tAtt) != DE_NULL)
+		return 0;
+	else if (const TextureLayerAttachment* const lAtt =
+			 dynamic_cast<const TextureLayerAttachment*>(&tAtt))
+		return lAtt->layer;
+
+	DE_ASSERT(!"Impossible attachment type");
+	return 0;
+}
+
+static void checkAttachmentCompleteness (Checker& cctx, const Attachment& attachment,
+										 GLenum attPoint, const Image* image,
+										 const FormatDB& db)
+{
+	// GLES2 4.4.5 / GLES3 4.4.4, "Framebuffer attachment completeness"
+
+	if (const TextureAttachment* const texAtt =
+		dynamic_cast<const TextureAttachment*>(&attachment))
+		if (const TextureLayered* const ltex = dynamic_cast<const TextureLayered*>(image))
+		{
+			// GLES3: "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is
+			// TEXTURE and the value of FRAMEBUFFER_ATTACHMENT_OBJECT_NAME names a
+			// three-dimensional texture, then the value of
+			// FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER must be smaller than the depth
+			// of the texture.
+			//
+			// GLES3: "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is
+			// TEXTURE and the value of FRAMEBUFFER_ATTACHMENT_OBJECT_NAME names a
+			// two-dimensional array texture, then the value of
+			// FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER must be smaller than the
+			// number of layers in the texture.
+
+			cctx.require(textureLayer(*texAtt) < ltex->numLayers,
+						 GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
+		}
+
+	// "The width and height of image are non-zero."
+	cctx.require(image->width > 0 && image->height > 0, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
+
+	// Check for renderability
+	FormatFlags flags = db.getFormatInfo(image->internalFormat, ANY_FORMAT);
+	// If the format does not have the proper renderability flag, the
+	// completeness check _must_ fail.
+	cctx.require((flags & formatFlag(attPoint)) != 0, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
+	// If the format is only optionally renderable, the completeness check _can_ fail.
+	cctx.canRequire((flags & REQUIRED_RENDERABLE) != 0,
+					GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
+}
+
+} // namespace config
+
+using namespace config;
+
+void Checker::require (bool condition, GLenum error)
+{
+	if (!condition)
+	{
+		m_statusCodes.erase(GL_FRAMEBUFFER_COMPLETE);
+		m_statusCodes.insert(error);
+	}
+}
+
+void Checker::canRequire (bool condition, GLenum error)
+{
+	if (!condition)
+		m_statusCodes.insert(error);
+}
+
+FboVerifier::FboVerifier (const FormatDB& formats, CheckerFactory& factory)
+	: m_formats				(formats)
+	, m_factory				(factory)
+{
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Return acceptable framebuffer status codes.
+ *
+ * This function examines the framebuffer configuration descriptor `fboConfig`
+ * and returns the set of status codes that `glCheckFramebufferStatus` is
+ * allowed to return on a conforming implementation when given a framebuffer
+ * whose configuration adheres to `fboConfig`.
+ *
+ * The returned set is guaranteed to be non-empty, but it may contain multiple
+ * INCOMPLETE statuses (if there are multiple errors in the spec), or or a mix
+ * of COMPLETE and INCOMPLETE statuses (if supporting a FBO with this spec is
+ * optional). Furthermore, the statuses may contain GL error codes, which
+ * indicate that trying to create a framebuffer configuration like this could
+ * have failed with an error (if one was checked for) even before
+ * `glCheckFramebufferStatus` was ever called.
+ *
+ *//*--------------------------------------------------------------------*/
+StatusCodes FboVerifier::validStatusCodes (const Framebuffer& fboConfig) const
+{
+	const AttachmentMap& atts = fboConfig.attachments;
+	const UniquePtr<Checker> cctx(m_factory.createChecker());
+
+	for (TextureMap::const_iterator it = fboConfig.textures.begin();
+		 it != fboConfig.textures.end(); it++)
+	{
+		const FormatFlags flags =
+			m_formats.getFormatInfo(it->second->internalFormat, ANY_FORMAT);
+		cctx->require((flags & TEXTURE_VALID) != 0, GL_INVALID_ENUM);
+		cctx->require((flags & TEXTURE_VALID) != 0, GL_INVALID_OPERATION);
+		cctx->require((flags & TEXTURE_VALID) != 0, GL_INVALID_VALUE);
+	}
+
+	for (RboMap::const_iterator it = fboConfig.rbos.begin(); it != fboConfig.rbos.end(); it++)
+	{
+		const FormatFlags flags =
+			m_formats.getFormatInfo(it->second->internalFormat, ANY_FORMAT);
+		cctx->require((flags & RENDERBUFFER_VALID) != 0, GL_INVALID_ENUM);
+	}
+
+	// "There is at least one image attached to the framebuffer."
+	// TODO: support XXX_framebuffer_no_attachments
+	cctx->require(!atts.empty(), GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
+
+	for (AttachmentMap::const_iterator it = atts.begin(); it != atts.end(); it++)
+	{
+		const GLenum attPoint = it->first;
+		const Attachment& att = *it->second;
+		const Image* const image = fboConfig.getImage(attachmentType(att), att.imageName);
+		checkAttachmentCompleteness(*cctx, att, attPoint, image, m_formats);
+		cctx->check(it->first, *it->second, image);
+	}
+
+	return cctx->getStatusCodes();
+}
+
+
+void Framebuffer::attach (glw::GLenum attPoint, const Attachment* att)
+{
+	if (att == DE_NULL)
+		attachments.erase(attPoint);
+	else
+		attachments[attPoint] = att;
+}
+
+const Image* Framebuffer::getImage (GLenum type, glw::GLuint imgName) const
+{
+	switch (type)
+	{
+		case GL_TEXTURE:
+			return lookupDefault(textures, imgName, DE_NULL);
+		case GL_RENDERBUFFER:
+			return lookupDefault(rbos, imgName, DE_NULL);
+		default:
+			DE_ASSERT(!"Bad image type");
+	}
+	return DE_NULL; // shut up compiler warning
+}
+
+void Framebuffer::setTexture (glw::GLuint texName, const Texture& texCfg)
+{
+	textures[texName] = &texCfg;
+}
+
+void Framebuffer::setRbo (glw::GLuint rbName, const Renderbuffer& rbCfg)
+{
+	rbos[rbName] = &rbCfg;
+}
+
+static void logField (TestLog& log, const string& field, const string& value)
+{
+	log << TestLog::Message << field << ": " << value << TestLog::EndMessage;
+}
+
+static void logImage (const Image& img, TestLog& log, bool useType)
+{
+	const GLenum type = img.internalFormat.unsizedType;
+	logField(log, "Internal format",	getPixelFormatName(img.internalFormat.format));
+	if (useType && type != GL_NONE)
+		logField(log, "Format type",	getTypeName(type));
+	logField(log, "Width", 				toString(img.width));
+	logField(log, "Height",				toString(img.height));
+}
+
+static void logRenderbuffer (const Renderbuffer& rbo, TestLog& log)
+{
+	logImage(rbo, log, false);
+	logField(log, "Samples",			toString(rbo.numSamples));
+}
+
+static void logTexture (const Texture& tex, TestLog& log)
+{
+	logField(log, "Type",				glu::getTextureTargetName(glTarget(tex)));
+	logImage(tex, log, true);
+	logField(log, "Levels",				toString(tex.numLevels));
+	if (const TextureLayered* const lTex = dynamic_cast<const TextureLayered*>(&tex))
+		logField(log, "Layers",				toString(lTex->numLayers));
+}
+
+static void logAttachment (const Attachment& att, TestLog& log)
+{
+	logField(log, "Target",				getFramebufferTargetName(att.target));
+	logField(log, "Type",				getFramebufferAttachmentTypeName(attachmentType(att)));
+	logField(log, "Image Name",			toString(att.imageName));
+	if (const RenderbufferAttachment* const rAtt
+		= dynamic_cast<const RenderbufferAttachment*>(&att))
+	{
+		DE_UNREF(rAtt); // To shut up compiler during optimized builds.
+		DE_ASSERT(rAtt->renderbufferTarget == GL_RENDERBUFFER);
+		logField(log, "Renderbuffer Target",	"GL_RENDERBUFFER");
+	}
+	else if (const TextureAttachment* const tAtt = dynamic_cast<const TextureAttachment*>(&att))
+	{
+		logField(log, "Mipmap Level",		toString(tAtt->level));
+		if (const TextureFlatAttachment* const fAtt =
+			dynamic_cast<const TextureFlatAttachment*>(tAtt))
+			logField(log, "Texture Target",		getTextureTargetName(fAtt->texTarget));
+		else if (const TextureLayerAttachment* const lAtt =
+			dynamic_cast<const TextureLayerAttachment*>(tAtt))
+			logField(log, "Layer",				toString(lAtt->level));
+	}
+}
+
+void logFramebufferConfig (const Framebuffer& cfg, TestLog& log)
+{
+	log << TestLog::Section("Framebuffer", "Framebuffer configuration");
+
+	const string rboDesc = cfg.rbos.empty()
+		? "No renderbuffers were created"
+		: "Renderbuffers created";
+	log << TestLog::Section("Renderbuffers", rboDesc);
+	for (RboMap::const_iterator it = cfg.rbos.begin(); it != cfg.rbos.end(); ++it)
+	{
+		const string num = toString(it->first);
+		log << TestLog::Section(num, "Renderbuffer " + num);
+		logRenderbuffer(*it->second, log);
+		log << TestLog::EndSection;
+	}
+	log << TestLog::EndSection; // Renderbuffers
+
+	const string texDesc = cfg.textures.empty()
+		? "No textures were created"
+		: "Textures created";
+	log << TestLog::Section("Textures", texDesc);
+	for (TextureMap::const_iterator it = cfg.textures.begin();
+		 it != cfg.textures.end(); ++it)
+	{
+		const string num = toString(it->first);
+		log << TestLog::Section(num, "Texture " + num);
+		logTexture(*it->second, log);
+		log << TestLog::EndSection;
+	}
+	log << TestLog::EndSection; // Textures
+
+	const string attDesc = cfg.attachments.empty()
+		? "Framebuffer has no attachments"
+		: "Framebuffer attachments";
+	log << TestLog::Section("Attachments", attDesc);
+	for (AttachmentMap::const_iterator it = cfg.attachments.begin();
+		 it != cfg.attachments.end(); it++)
+	{
+		const string attPointName = getFramebufferAttachmentName(it->first);
+		log << TestLog::Section(attPointName, "Attachment point " + attPointName);
+		logAttachment(*it->second, log);
+		log << TestLog::EndSection;
+	}
+	log << TestLog::EndSection; // Attachments
+
+	log << TestLog::EndSection; // Framebuffer
+}
+
+FboBuilder::FboBuilder (GLuint fbo, GLenum target, const glw::Functions& gl)
+	: m_error	(GL_NO_ERROR)
+	, m_target	(target)
+	, m_gl		(gl)
+{
+	m_gl.bindFramebuffer(m_target, fbo);
+}
+
+FboBuilder::~FboBuilder (void)
+{
+	for (TextureMap::const_iterator it = textures.begin(); it != textures.end(); it++)
+	{
+		glDelete(*it->second, it->first, m_gl);
+	}
+	for (RboMap::const_iterator it = rbos.begin(); it != rbos.end(); it++)
+	{
+		glDelete(*it->second, it->first, m_gl);
+	}
+	m_gl.bindFramebuffer(m_target, 0);
+	for (Configs::const_iterator it = m_configs.begin(); it != m_configs.end(); it++)
+	{
+		delete *it;
+	}
+}
+
+void FboBuilder::checkError (void)
+{
+	const GLenum error = m_gl.getError();
+	if (error != GL_NO_ERROR && m_error == GL_NO_ERROR)
+		m_error = error;
+}
+
+void FboBuilder::glAttach (GLenum attPoint, const Attachment* att)
+{
+	if (att == NULL)
+		m_gl.framebufferRenderbuffer(m_target, attPoint, GL_RENDERBUFFER, 0);
+	else
+		attachAttachment(*att, attPoint, m_gl);
+	checkError();
+	attach(attPoint, att);
+}
+
+GLuint FboBuilder::glCreateTexture (const Texture& texCfg)
+{
+	const GLuint texName = glCreate(texCfg, m_gl);
+	checkError();
+	setTexture(texName, texCfg);
+	return texName;
+}
+
+GLuint FboBuilder::glCreateRbo (const Renderbuffer& rbCfg)
+{
+	const GLuint rbName = glCreate(rbCfg, m_gl);
+	checkError();
+	setRbo(rbName, rbCfg);
+	return rbName;
+}
+
+TransferFormat transferImageFormat (const ImageFormat& imgFormat)
+{
+	if (imgFormat.unsizedType == GL_NONE)
+		return getTransferFormat(mapGLInternalFormat(imgFormat.format));
+	else
+		return TransferFormat(imgFormat.format, imgFormat.unsizedType);
+}
+
+} // FboUtil
+} // gls
+} // deqp
diff --git a/modules/glshared/glsFboUtil.hpp b/modules/glshared/glsFboUtil.hpp
new file mode 100644
index 0000000..00887a8
--- /dev/null
+++ b/modules/glshared/glsFboUtil.hpp
@@ -0,0 +1,452 @@
+#ifndef _GLSFBOUTIL_HPP
+#define _GLSFBOUTIL_HPP
+
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Utilities for framebuffer objects.
+ *//*--------------------------------------------------------------------*/
+
+#include "gluRenderContext.hpp"
+#include "gluContextInfo.hpp"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuDefs.hpp"
+
+#include <map>
+#include <set>
+#include <vector>
+#include <algorithm>
+#include <iterator>
+
+namespace deqp
+{
+namespace gls
+{
+
+// Utilities for standard containers. \todo [2013-12-10 lauri] Move to decpp?
+
+//! A pair of iterators to present a range.
+//! \note This must be POD to allow static initialization.
+//! \todo [2013-12-03 lauri] Move this to decpp?
+template <typename T>
+struct Range
+{
+	typedef const T*	const_iterator;
+
+	const T*	m_begin;
+	const T*	m_end;
+
+	const T*	begin		(void) const { return m_begin; }
+	const T*	end			(void) const { return m_end; }
+};
+
+#define GLS_ARRAY_RANGE(ARR) { DE_ARRAY_BEGIN(ARR), DE_ARRAY_END(ARR) }
+
+#define GLS_NULL_RANGE { DE_NULL, DE_NULL }
+
+
+//! A pair type that, unlike stl::pair, is POD so it can be statically initialized.
+template <typename T1, typename T2>
+struct Pair
+{
+	typedef	T1	first_type;
+	typedef T2	second_type;
+	T1			first;
+	T2			second;
+};
+
+template<typename C>
+C intersection(const C& s1, const C& s2)
+{
+	C ret;
+	std::set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(),
+						  std::insert_iterator<C>(ret, ret.begin()));
+	return ret;
+}
+
+// \todo [2013-12-03 lauri] move to decpp?
+template<typename C>
+inline bool isMember (const typename C::key_type& key, const C& container)
+{
+	typename C::const_iterator it = container.find(key);
+	return (it != container.end());
+}
+
+template <typename M> inline
+const typename M::mapped_type* lookupMaybe (const M& map,
+											const typename M::key_type& key)
+{
+	typename M::const_iterator it = map.find(key);
+	if (it == map.end())
+		return DE_NULL;
+	return &it->second;
+}
+
+template<typename M> inline
+const typename M::mapped_type& lookupDefault (const M& map,
+											  const typename M::key_type& key,
+											  const typename M::mapped_type& fallback)
+{
+	const typename M::mapped_type* ptr = lookupMaybe(map, key);
+	return ptr == DE_NULL ? fallback : *ptr;
+}
+
+
+template<typename M>
+const typename M::mapped_type& lookup (const M& map,
+									   const typename M::key_type& key)
+{
+	const typename M::mapped_type* ptr = lookupMaybe(map, key);
+	if (ptr == DE_NULL)
+		throw std::out_of_range("key not found in map");
+	return *ptr;
+}
+
+template<typename C>
+inline bool contains (const C& container, const typename C::value_type& item)
+{
+	const typename C::const_iterator it = container.find(item);
+	return (it != container.end());
+}
+
+
+template<typename M> static inline
+bool insert(const typename M::key_type& key, const typename M::mapped_type& value, M& map)
+{
+	typename M::value_type entry(key, value);
+	std::pair<typename M::iterator,bool> ret = map.insert(entry);
+	return ret.second;
+}
+
+std::vector<std::string> splitString(const std::string& s);
+
+namespace FboUtil
+{
+
+//! Configurations for framebuffer objects and their attachments.
+
+class FboVerifier;
+class FboBuilder;
+
+typedef deUint32		FormatKey;
+
+#define GLS_UNSIZED_FORMATKEY(FORMAT, TYPE) \
+	(deUint32(TYPE) << 16 | deUint32(FORMAT))
+
+typedef Range<FormatKey>	FormatKeys;
+
+struct ImageFormat
+{
+	glw::GLenum				format;
+
+	//! Type if format is unsized, GL_NONE if sized.
+	glw::GLenum				unsizedType;
+
+	bool 					operator<		(const ImageFormat& other) const
+	{
+		return (format < other.format ||
+				(format == other.format && unsizedType < other.unsizedType));
+	}
+
+	static ImageFormat		none			(void)
+	{
+		ImageFormat fmt = { GL_NONE, GL_NONE };
+		return fmt;
+	}
+};
+
+static inline ImageFormat formatKeyInfo(FormatKey key)
+{
+	ImageFormat fmt = { key & 0xffff, key >> 16 };
+	return fmt;
+}
+
+enum FormatFlags
+{
+	ANY_FORMAT			= 0,
+	COLOR_RENDERABLE	= 1 << 0,
+	DEPTH_RENDERABLE	= 1 << 1,
+	STENCIL_RENDERABLE	= 1 << 2,
+	RENDERBUFFER_VALID	= 1 << 3,
+	TEXTURE_VALID		= 1 << 4,
+	REQUIRED_RENDERABLE	= 1 << 5, //< Without this, renderability is allowed, not required.
+};
+
+static inline FormatFlags operator|(FormatFlags f1, FormatFlags f2)
+{
+	return FormatFlags(deUint32(f1) | deUint32(f2));
+}
+
+FormatFlags formatFlag(glw::GLenum context);
+
+typedef std::set<ImageFormat> Formats;
+
+class FormatDB
+{
+public:
+	void							addFormat		(ImageFormat format, FormatFlags flags);
+	Formats							getFormats		(FormatFlags requirements) const;
+	FormatFlags						getFormatInfo	(ImageFormat format,
+													 FormatFlags fallback) const;
+
+private:
+	typedef std::map<ImageFormat, FormatFlags>		FormatMap;
+
+	FormatMap						m_map;
+};
+
+typedef Pair<FormatFlags, FormatKeys>				FormatEntry;
+typedef Range<FormatEntry>							FormatEntries;
+
+// \todo [2013-12-20 lauri] It turns out that format properties in extensions
+// are actually far too fine-grained for this bundling to be reasonable,
+// especially given the syntactic cumbersomeness of static arrays. It's better
+// to list each entry separately.
+
+struct FormatExtEntry
+{
+	const char*									extensions;
+	deUint32									flags;
+	Range<FormatKey>							formats;
+};
+
+typedef Range<FormatExtEntry>						FormatExtEntries;
+
+void				addFormats			(FormatDB& db, FormatEntries stdFmts);
+void 				addExtFormats		(FormatDB& db, FormatExtEntries extFmts,
+										 const glu::RenderContext* ctx);
+glu::TransferFormat	transferImageFormat	(const ImageFormat& imgFormat);
+
+namespace config
+{
+
+struct Config
+{
+	virtual 					~Config			(void) {};
+};
+
+struct Image : public Config
+{
+	ImageFormat					internalFormat;
+	glw::GLsizei				width;
+	glw::GLsizei				height;
+
+protected:
+								Image			(void)
+									: internalFormat	(ImageFormat::none())
+									, width				(0)
+									, height			(0) {}
+};
+
+struct Renderbuffer : public Image
+{
+						Renderbuffer	(void) : numSamples(0) {}
+
+	glw::GLsizei		numSamples;
+};
+
+struct Texture : public Image
+{
+							Texture			(void) : numLevels(1) {}
+
+	glw::GLint 				numLevels;
+};
+
+struct TextureFlat : public Texture
+{
+};
+
+struct Texture2D : public TextureFlat
+{
+};
+
+struct TextureCubeMap : public TextureFlat
+{
+};
+
+struct TextureLayered : public Texture
+{
+							TextureLayered	(void) : numLayers(1) {}
+	glw::GLsizei			numLayers;
+};
+
+struct Texture3D : public TextureLayered
+{
+};
+
+struct Texture2DArray : public TextureLayered
+{
+};
+
+struct Attachment : public Config
+{
+							Attachment		(void) : target(GL_FRAMEBUFFER), imageName(0) {}
+
+	glw::GLenum 			target;
+	glw::GLuint 			imageName;
+
+	//! Returns `true` iff this attachment is "framebuffer attachment
+	//! complete" when bound to attachment point `attPoint`, and the current
+	//! image with name `imageName` is `image`, using `vfr` to check format
+	//! renderability.
+	bool					isComplete		(glw::GLenum attPoint, const Image* image,
+											 const FboVerifier& vfr) const;
+};
+
+struct RenderbufferAttachment : public Attachment
+{
+				RenderbufferAttachment	(void)
+				: renderbufferTarget(GL_RENDERBUFFER) {}
+
+	glw::GLenum renderbufferTarget;
+};
+
+struct TextureAttachment : public Attachment
+{
+							TextureAttachment	(void) : level(0) {}
+
+	glw::GLint				level;
+};
+
+struct TextureFlatAttachment : public TextureAttachment
+{
+							TextureFlatAttachment (void) : texTarget(GL_NONE) {}
+
+	glw::GLenum				texTarget;
+};
+
+struct TextureLayerAttachment : public TextureAttachment
+{
+							TextureLayerAttachment (void) : layer(0) {}
+
+	glw::GLsizei			layer;
+};
+
+glw::GLenum		attachmentType	(const Attachment& att);
+glw::GLsizei	imageNumSamples	(const Image& img);
+
+//! Mapping from attachment points to attachment configurations.
+typedef std::map<glw::GLenum, const Attachment*> AttachmentMap;
+
+//! Mapping from object names to texture configurations.
+typedef std::map<glw::GLuint, const Texture*> TextureMap;
+
+//! Mapping from object names to renderbuffer configurations.
+typedef std::map<glw::GLuint, const Renderbuffer*> RboMap;
+
+//! A framebuffer configuration.
+struct Framebuffer
+{
+	AttachmentMap			attachments;
+	TextureMap				textures;
+	RboMap					rbos;
+
+	void					attach			(glw::GLenum attPoint, const Attachment* att);
+	void					setTexture		(glw::GLuint texName, const Texture& texCfg);
+	void					setRbo			(glw::GLuint rbName, const Renderbuffer& rbCfg);
+	const Image*			getImage		(glw::GLenum type, glw::GLuint imgName) const;
+};
+
+} // config
+
+void logFramebufferConfig(const config::Framebuffer& cfg, tcu::TestLog& log);
+
+class FboBuilder : public config::Framebuffer
+{
+public:
+	void						glAttach		(glw::GLenum attPoint,
+												 const config::Attachment* att);
+	glw::GLuint					glCreateTexture	(const config::Texture& texCfg);
+	glw::GLuint					glCreateRbo		(const config::Renderbuffer& rbCfg);
+								FboBuilder		(glw::GLuint fbo, glw::GLenum target,
+												 const glw::Functions& gl);
+								~FboBuilder		(void);
+	glw::GLenum					getError		(void) { return m_error; }
+
+	//! Allocate a new configuration of type `Config` (which must be a
+	//! subclass of `config::Config`), and return a referenc to it. The newly
+	//! allocated object will be freed when this builder object is destroyed.
+	template<typename Config>
+	Config&						makeConfig		(void)
+	{
+		Config* cfg = new Config();
+		m_configs.insert(cfg);
+		return *cfg;
+	}
+
+private:
+	typedef std::set<config::Config*> Configs;
+
+	void						checkError		(void);
+
+	glw::GLenum					m_error;		//< The first GL error encountered.
+	glw::GLenum					m_target;
+	const glw::Functions&		m_gl;
+	Configs						m_configs;
+};
+
+typedef std::set<glw::GLenum> StatusCodes;
+
+class Checker
+{
+public:
+					Checker			(void) { m_statusCodes.insert(GL_FRAMEBUFFER_COMPLETE); }
+	virtual			~Checker		(void) {}
+	void			require			(bool condition, glw::GLenum error);
+	void			canRequire		(bool condition, glw::GLenum error);
+	StatusCodes		getStatusCodes	(void) { return m_statusCodes; }
+	virtual void	check			(glw::GLenum attPoint, const config::Attachment& att,
+									 const config::Image* image) = 0;
+private:
+
+	StatusCodes		m_statusCodes;	//< Allowed return values for glCheckFramebufferStatus.
+};
+
+class CheckerFactory
+{
+public:
+	virtual Checker*	createChecker	(void) = 0;
+};
+
+typedef std::set<glw::GLenum> AttachmentPoints;
+typedef std::set<ImageFormat> Formats;
+
+class FboVerifier
+{
+public:
+								FboVerifier				(const FormatDB& formats,
+														 CheckerFactory& factory);
+
+	StatusCodes					validStatusCodes		(const config::Framebuffer& cfg) const;
+
+private:
+	const FormatDB&				m_formats;
+	CheckerFactory&				m_factory;
+};
+
+} // FboUtil
+} // gls
+} // deqp
+
+#endif // _GLSFBOUTIL_HPP
diff --git a/modules/glshared/glsFragOpInteractionCase.cpp b/modules/glshared/glsFragOpInteractionCase.cpp
new file mode 100644
index 0000000..0d68d16
--- /dev/null
+++ b/modules/glshared/glsFragOpInteractionCase.cpp
@@ -0,0 +1,578 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader - render state interaction case.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsFragOpInteractionCase.hpp"
+
+#include "glsRandomShaderProgram.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "glsInteractionTestUtil.hpp"
+
+#include "gluRenderContext.hpp"
+#include "gluContextInfo.hpp"
+
+#include "rsgShader.hpp"
+#include "rsgProgramGenerator.hpp"
+#include "rsgUtils.hpp"
+
+#include "sglrContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "sglrGLContext.hpp"
+#include "sglrContextUtil.hpp"
+
+#include "tcuRenderTarget.hpp"
+#include "tcuImageCompare.hpp"
+
+#include "deRandom.hpp"
+#include "deString.h"
+#include "deStringUtil.hpp"
+
+#include "glwEnums.hpp"
+
+#include "gluDrawUtil.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+
+using std::vector;
+using std::string;
+using tcu::Vec2;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec4;
+
+using gls::InteractionTestUtil::RenderState;
+using gls::InteractionTestUtil::StencilState;
+
+enum
+{
+	NUM_ITERATIONS					= 5,
+	NUM_COMMANDS_PER_ITERATION		= 5,
+	VIEWPORT_WIDTH					= 64,
+	VIEWPORT_HEIGHT					= 64
+};
+
+namespace
+{
+
+static void computeVertexLayout (const vector<rsg::ShaderInput*>& attributes, int numVertices, vector<glu::VertexArrayBinding>* layout, int* stride)
+{
+	DE_ASSERT(layout->empty());
+
+	int curOffset = 0;
+
+	for (vector<rsg::ShaderInput*>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
+	{
+		const rsg::ShaderInput*		attrib		= *iter;
+		const rsg::Variable*		var			= attrib->getVariable();
+		const rsg::VariableType&	type		= var->getType();
+		const int					numComps	= type.getNumElements();
+
+		TCU_CHECK_INTERNAL(type.getBaseType() == rsg::VariableType::TYPE_FLOAT && de::inRange(type.getNumElements(), 1, 4));
+
+		layout->push_back(glu::va::Float(var->getName(), numComps, numVertices, 0 /* computed later */, (const float*)(deUintptr)curOffset));
+
+		curOffset += numComps * (int)sizeof(float);
+	}
+
+	for (vector<glu::VertexArrayBinding>::iterator vaIter = layout->begin(); vaIter != layout->end(); ++vaIter)
+		vaIter->pointer.stride = curOffset;
+
+	*stride = curOffset;
+}
+
+class VertexDataStorage
+{
+public:
+												VertexDataStorage	(const vector<rsg::ShaderInput*>& attributes, int numVertices);
+
+	int											getDataSize			(void) const	{ return (int)m_data.size();					}
+	void*										getBasePtr			(void)			{ return m_data.empty() ? DE_NULL : &m_data[0]; }
+	const void*									getBasePtr			(void) const	{ return m_data.empty() ? DE_NULL : &m_data[0]; }
+
+	const std::vector<glu::VertexArrayBinding>&	getLayout			(void) const	{ return m_layout; }
+
+	int											getNumEntries		(void) const	{ return (int)m_layout.size();	}
+	const glu::VertexArrayBinding&				getLayoutEntry		(int ndx) const	{ return m_layout[ndx];			}
+
+private:
+	std::vector<deUint8>						m_data;
+	std::vector<glu::VertexArrayBinding>		m_layout;
+};
+
+VertexDataStorage::VertexDataStorage (const vector<rsg::ShaderInput*>& attributes, int numVertices)
+{
+	int stride = 0;
+	computeVertexLayout(attributes, numVertices, &m_layout, &stride);
+	m_data.resize(stride * numVertices);
+}
+
+static inline glu::VertexArrayBinding getEntryWithPointer (const VertexDataStorage& data, int ndx)
+{
+	const glu::VertexArrayBinding& entry = data.getLayoutEntry(ndx);
+	return glu::VertexArrayBinding(entry.binding, glu::VertexArrayPointer(entry.pointer.componentType,
+																		  entry.pointer.convert,
+																		  entry.pointer.numComponents,
+																		  entry.pointer.numElements,
+																		  entry.pointer.stride,
+																		  (const void*)((deUintptr)entry.pointer.data+(deUintptr)data.getBasePtr())));
+}
+
+template<int Size>
+static void setVertex (const glu::VertexArrayPointer& pointer, int vertexNdx, const tcu::Vector<float, Size>& value)
+{
+	// \todo [2013-12-14 pyry] Implement other modes.
+	DE_ASSERT(pointer.componentType == glu::VTX_COMP_FLOAT && pointer.convert == glu::VTX_COMP_CONVERT_NONE);
+	DE_ASSERT(pointer.numComponents == Size);
+	DE_ASSERT(de::inBounds(vertexNdx, 0, pointer.numElements));
+
+	float* dst = (float*)((deUint8*)pointer.data + pointer.stride*vertexNdx);
+
+	for (int ndx = 0; ndx < Size; ndx++)
+		dst[ndx] = value[ndx];
+}
+
+template<int Size>
+static tcu::Vector<float, Size> interpolateRange (const rsg::ConstValueRangeAccess& range, const tcu::Vector<float, Size>& t)
+{
+	tcu::Vector<float, Size> result;
+
+	for (int ndx = 0; ndx < Size; ndx++)
+		result[ndx] = range.getMin().component(ndx).asFloat()*(1.0f - t[ndx]) + range.getMax().component(ndx).asFloat()*t[ndx];
+
+	return result;
+}
+
+struct Quad
+{
+	tcu::IVec2	posA;
+	tcu::IVec2	posB;
+};
+
+struct RenderCommand
+{
+	Quad		quad;
+	float		depth;
+	RenderState	state;
+
+	RenderCommand (void) : depth(0.0f) {}
+};
+
+static Quad getRandomQuad (de::Random& rnd, int targetW, int targetH)
+{
+	// \note In viewport coordinates.
+	// \todo [2012-12-18 pyry] Out-of-bounds values.
+	const int		maxOutOfBounds	= 0;
+	const float		minSize			= 0.5f;
+
+	const int		minW			= deCeilFloatToInt32(minSize*targetW);
+	const int		minH			= deCeilFloatToInt32(minSize*targetH);
+	const int		maxW			= targetW + 2*maxOutOfBounds;
+	const int		maxH			= targetH + 2*maxOutOfBounds;
+
+	const int		width			= rnd.getInt(minW, maxW);
+	const int		height			= rnd.getInt(minH, maxH);
+	const int		x				= rnd.getInt(-maxOutOfBounds, targetW+maxOutOfBounds-width);
+	const int		y				= rnd.getInt(-maxOutOfBounds, targetH+maxOutOfBounds-height);
+
+	const bool		flipX			= rnd.getBool();
+	const bool		flipY			= rnd.getBool();
+
+	Quad			quad;
+
+	quad.posA	= tcu::IVec2(flipX ? (x+width-1) : x, flipY ? (y+height-1) : y);
+	quad.posB	= tcu::IVec2(flipX ? x : (x+width-1), flipY ? y : (y+height-1));
+
+	return quad;
+}
+
+static float getRandomDepth (de::Random& rnd)
+{
+	// \note Not using depth 1.0 since clearing with 1.0 and rendering with 1.0 may not be same value.
+	static const float depthValues[] = { 0.0f, 0.2f, 0.4f, 0.5f, 0.51f, 0.6f, 0.8f, 0.95f };
+	return rnd.choose<float>(DE_ARRAY_BEGIN(depthValues), DE_ARRAY_END(depthValues));
+}
+
+static void computeRandomRenderCommand (de::Random& rnd, RenderCommand& command, glu::ApiType apiType, int targetW, int targetH)
+{
+	command.quad	= getRandomQuad(rnd, targetW, targetH);
+	command.depth	= getRandomDepth(rnd);
+	gls::InteractionTestUtil::computeRandomRenderState(rnd, command.state, apiType, targetW, targetH);
+}
+
+static void setRenderState (sglr::Context& ctx, const RenderState& state)
+{
+	if (state.scissorTestEnabled)
+	{
+		ctx.enable(GL_SCISSOR_TEST);
+		ctx.scissor(state.scissorRectangle.left, state.scissorRectangle.bottom,
+					state.scissorRectangle.width, state.scissorRectangle.height);
+	}
+	else
+		ctx.disable(GL_SCISSOR_TEST);
+
+	if (state.stencilTestEnabled)
+	{
+		ctx.enable(GL_STENCIL_TEST);
+
+		for (int face = 0; face < rr::FACETYPE_LAST; face++)
+		{
+			deUint32				glFace	= face == rr::FACETYPE_BACK ? GL_BACK : GL_FRONT;
+			const StencilState&		sParams	= state.stencil[face];
+
+			ctx.stencilFuncSeparate(glFace, sParams.function, sParams.reference, sParams.compareMask);
+			ctx.stencilOpSeparate(glFace, sParams.stencilFailOp, sParams.depthFailOp, sParams.depthPassOp);
+			ctx.stencilMaskSeparate(glFace, sParams.writeMask);
+		}
+	}
+	else
+		ctx.disable(GL_STENCIL_TEST);
+
+	if (state.depthTestEnabled)
+	{
+		ctx.enable(GL_DEPTH_TEST);
+		ctx.depthFunc(state.depthFunc);
+		ctx.depthMask(state.depthWriteMask ? GL_TRUE : GL_FALSE);
+	}
+	else
+		ctx.disable(GL_DEPTH_TEST);
+
+	if (state.blendEnabled)
+	{
+		ctx.enable(GL_BLEND);
+		ctx.blendEquationSeparate(state.blendRGBState.equation, state.blendAState.equation);
+		ctx.blendFuncSeparate(state.blendRGBState.srcFunc, state.blendRGBState.dstFunc, state.blendAState.srcFunc, state.blendAState.dstFunc);
+		ctx.blendColor(state.blendColor.x(), state.blendColor.y(), state.blendColor.z(), state.blendColor.w());
+	}
+	else
+		ctx.disable(GL_BLEND);
+
+	if (state.ditherEnabled)
+		ctx.enable(GL_DITHER);
+	else
+		ctx.disable(GL_DITHER);
+
+	ctx.colorMask(state.colorMask[0] ? GL_TRUE : GL_FALSE,
+				  state.colorMask[1] ? GL_TRUE : GL_FALSE,
+				  state.colorMask[2] ? GL_TRUE : GL_FALSE,
+				  state.colorMask[3] ? GL_TRUE : GL_FALSE);
+}
+
+static void renderQuad (sglr::Context& ctx, const glu::VertexArrayPointer& posPtr, const Quad& quad, const float depth)
+{
+	const deUint16	indices[]	= { 0, 1, 2, 2, 1, 3 };
+
+	const bool		flipX		= quad.posB.x() < quad.posA.x();
+	const bool		flipY		= quad.posB.y() < quad.posA.y();
+	const int		viewportX	= de::min(quad.posA.x(), quad.posB.x());
+	const int		viewportY	= de::min(quad.posA.y(), quad.posB.y());
+	const int		viewportW	= de::abs(quad.posA.x()-quad.posB.x())+1;
+	const int		viewportH	= de::abs(quad.posA.y()-quad.posB.y())+1;
+
+	const Vec2		pA			(flipX ? 1.0f : -1.0f, flipY ? 1.0f : -1.0f);
+	const Vec2		pB			(flipX ? -1.0f : 1.0f, flipY ? -1.0f : 1.0f);
+
+	setVertex(posPtr, 0, Vec4(pA.x(), pA.y(), depth, 1.0f));
+	setVertex(posPtr, 1, Vec4(pB.x(), pA.y(), depth, 1.0f));
+	setVertex(posPtr, 2, Vec4(pA.x(), pB.y(), depth, 1.0f));
+	setVertex(posPtr, 3, Vec4(pB.x(), pB.y(), depth, 1.0f));
+
+	ctx.viewport(viewportX, viewportY, viewportW, viewportH);
+	ctx.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_SHORT, &indices[0]);
+}
+
+static void render (sglr::Context& ctx, const glu::VertexArrayPointer& posPtr, const RenderCommand& cmd)
+{
+	setRenderState(ctx, cmd.state);
+	renderQuad(ctx, posPtr, cmd.quad, cmd.depth);
+}
+
+static void setupAttributes (sglr::Context& ctx, const VertexDataStorage& vertexData, deUint32 program)
+{
+	for (int attribNdx = 0; attribNdx < vertexData.getNumEntries(); ++attribNdx)
+	{
+		const glu::VertexArrayBinding	bindingPtr	= getEntryWithPointer(vertexData, attribNdx);
+		const int						attribLoc	= bindingPtr.binding.type == glu::BindingPoint::TYPE_NAME ? ctx.getAttribLocation(program, bindingPtr.binding.name.c_str()) : bindingPtr.binding.location;
+
+		DE_ASSERT(bindingPtr.pointer.componentType == glu::VTX_COMP_FLOAT);
+
+		if (attribLoc >= 0)
+		{
+			ctx.enableVertexAttribArray(attribLoc);
+			ctx.vertexAttribPointer(attribLoc, bindingPtr.pointer.numComponents, GL_FLOAT, GL_FALSE, bindingPtr.pointer.stride, bindingPtr.pointer.data);
+		}
+	}
+}
+
+void setUniformValue (sglr::Context& ctx, int location, rsg::ConstValueAccess value)
+{
+	DE_STATIC_ASSERT(sizeof(rsg::Scalar) == sizeof(float));
+	DE_STATIC_ASSERT(sizeof(rsg::Scalar) == sizeof(int));
+
+	switch (value.getType().getBaseType())
+	{
+		case rsg::VariableType::TYPE_FLOAT:
+			switch (value.getType().getNumElements())
+			{
+				case 1:		ctx.uniform1fv(location, 1, (float*)value.value().getValuePtr());	break;
+				case 2:		ctx.uniform2fv(location, 1, (float*)value.value().getValuePtr());	break;
+				case 3:		ctx.uniform3fv(location, 1, (float*)value.value().getValuePtr());	break;
+				case 4:		ctx.uniform4fv(location, 1, (float*)value.value().getValuePtr());	break;
+				default:	TCU_FAIL("Unsupported type");										break;
+			}
+			break;
+
+		case rsg::VariableType::TYPE_INT:
+		case rsg::VariableType::TYPE_BOOL:
+		case rsg::VariableType::TYPE_SAMPLER_2D:
+		case rsg::VariableType::TYPE_SAMPLER_CUBE:
+			switch (value.getType().getNumElements())
+			{
+				case 1:		ctx.uniform1iv(location, 1, (int*)value.value().getValuePtr());		break;
+				case 2:		ctx.uniform2iv(location, 1, (int*)value.value().getValuePtr());		break;
+				case 3:		ctx.uniform3iv(location, 1, (int*)value.value().getValuePtr());		break;
+				case 4:		ctx.uniform4iv(location, 1, (int*)value.value().getValuePtr());		break;
+				default:	TCU_FAIL("Unsupported type");										break;
+			}
+			break;
+
+		default:
+			throw tcu::InternalError("Unsupported type", "", __FILE__, __LINE__);
+	}
+}
+
+static int findShaderInputIndex (const vector<rsg::ShaderInput*>& vars, const char* name)
+{
+	for (int ndx = 0; ndx < (int)vars.size(); ++ndx)
+	{
+		if (deStringEqual(vars[ndx]->getVariable()->getName(), name))
+			return ndx;
+	}
+
+	throw tcu::InternalError(string(name) + " not found in shader inputs");
+}
+
+} // anonymous
+
+struct FragOpInteractionCase::ReferenceContext
+{
+	const sglr::ReferenceContextLimits	limits;
+	sglr::ReferenceContextBuffers		buffers;
+	sglr::ReferenceContext				context;
+
+	ReferenceContext (glu::RenderContext& renderCtx, int width, int height)
+		: limits	(renderCtx)
+		, buffers	(renderCtx.getRenderTarget().getPixelFormat(), renderCtx.getRenderTarget().getDepthBits(), renderCtx.getRenderTarget().getStencilBits(), width, height)
+		, context	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer())
+	{
+	}
+};
+
+FragOpInteractionCase::FragOpInteractionCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const rsg::ProgramParameters& params)
+	: TestCase			(testCtx, name, "")
+	, m_renderCtx		(renderCtx)
+	, m_ctxInfo			(ctxInfo)
+	, m_params			(params)
+	, m_vertexShader	(rsg::Shader::TYPE_VERTEX)
+	, m_fragmentShader	(rsg::Shader::TYPE_FRAGMENT)
+	, m_program			(DE_NULL)
+	, m_glCtx			(DE_NULL)
+	, m_referenceCtx	(DE_NULL)
+	, m_glProgram		(0)
+	, m_refProgram		(0)
+	, m_iterNdx			(0)
+{
+}
+
+FragOpInteractionCase::~FragOpInteractionCase (void)
+{
+	FragOpInteractionCase::deinit();
+}
+
+void FragOpInteractionCase::init (void)
+{
+	de::Random				rnd				(m_params.seed ^ 0x232faac);
+	const int				viewportW		= de::min<int>(m_renderCtx.getRenderTarget().getWidth(),	VIEWPORT_WIDTH);
+	const int				viewportH		= de::min<int>(m_renderCtx.getRenderTarget().getHeight(),	VIEWPORT_HEIGHT);
+	const int				viewportX		= rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth()	- viewportW);
+	const int				viewportY		= rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight()	- viewportH);
+
+	rsg::ProgramGenerator	generator;
+
+	generator.generate(m_params, m_vertexShader, m_fragmentShader);
+	rsg::computeUnifiedUniforms(m_vertexShader, m_fragmentShader, m_unifiedUniforms);
+
+	try
+	{
+		DE_ASSERT(!m_program);
+		m_program = new gls::RandomShaderProgram(m_vertexShader, m_fragmentShader, (int)m_unifiedUniforms.size(), m_unifiedUniforms.empty() ? DE_NULL : &m_unifiedUniforms[0]);
+
+		DE_ASSERT(!m_referenceCtx);
+		m_referenceCtx = new ReferenceContext(m_renderCtx, viewportW, viewportH);
+
+		DE_ASSERT(!m_glCtx);
+		m_glCtx = new sglr::GLContext(m_renderCtx, m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS|sglr::GLCONTEXT_LOG_PROGRAMS, IVec4(viewportX, viewportY, viewportW, viewportH));
+
+		m_refProgram	= m_referenceCtx->context.createProgram(m_program);
+		m_glProgram		= m_glCtx->createProgram(m_program);
+
+		m_viewportSize	= tcu::IVec2(viewportW, viewportH);
+		m_iterNdx		= 0;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+	catch (...)
+	{
+		// Save some memory by cleaning up stuff.
+		FragOpInteractionCase::deinit();
+		throw;
+	}
+}
+
+void FragOpInteractionCase::deinit (void)
+{
+	delete m_referenceCtx;
+	m_referenceCtx = DE_NULL;
+
+	delete m_glCtx;
+	m_glCtx = DE_NULL;
+
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+FragOpInteractionCase::IterateResult FragOpInteractionCase::iterate (void)
+{
+	de::Random							rnd					(m_params.seed ^ deInt32Hash(m_iterNdx));
+	const tcu::ScopedLogSection			section				(m_testCtx.getLog(), string("Iter") + de::toString(m_iterNdx), string("Iteration ") + de::toString(m_iterNdx));
+
+	const int							positionNdx			= findShaderInputIndex(m_vertexShader.getInputs(), "dEQP_Position");
+
+	const int							numVertices			= 4;
+	VertexDataStorage					vertexData			(m_vertexShader.getInputs(), numVertices);
+	std::vector<rsg::VariableValue>		uniformValues;
+	std::vector<RenderCommand>			renderCmds			(NUM_COMMANDS_PER_ITERATION);
+
+	tcu::Surface						rendered			(m_viewportSize.x(), m_viewportSize.y());
+	tcu::Surface						reference			(m_viewportSize.x(), m_viewportSize.y());
+
+	const tcu::Vec4						vtxInterpFactors[]	=
+	{
+		tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f),
+		tcu::Vec4(1.0f, 0.0f, 0.5f, 0.5f),
+		tcu::Vec4(0.0f, 1.0f, 0.5f, 0.5f),
+		tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f)
+	};
+
+	rsg::computeUniformValues(rnd, uniformValues, m_unifiedUniforms);
+
+	for (int attribNdx = 0; attribNdx < (int)m_vertexShader.getInputs().size(); ++attribNdx)
+	{
+		if (attribNdx == positionNdx)
+			continue;
+
+		const rsg::ShaderInput*				shaderIn		= m_vertexShader.getInputs()[attribNdx];
+		const rsg::VariableType&			varType			= shaderIn->getVariable()->getType();
+		const rsg::ConstValueRangeAccess	valueRange		= shaderIn->getValueRange();
+		const int							numComponents	= varType.getNumElements();
+		const glu::VertexArrayBinding		layoutEntry		= getEntryWithPointer(vertexData, attribNdx);
+
+		DE_ASSERT(varType.getBaseType() == rsg::VariableType::TYPE_FLOAT);
+
+		for (int vtxNdx = 0; vtxNdx < 4; vtxNdx++)
+		{
+			const int			fNdx	= (attribNdx+vtxNdx+m_iterNdx)%DE_LENGTH_OF_ARRAY(vtxInterpFactors);
+			const tcu::Vec4&	f		= vtxInterpFactors[fNdx];
+
+			switch (numComponents)
+			{
+				case 1:	setVertex(layoutEntry.pointer, vtxNdx, interpolateRange(valueRange, f.toWidth<1>()));	break;
+				case 2:	setVertex(layoutEntry.pointer, vtxNdx, interpolateRange(valueRange, f.toWidth<2>()));	break;
+				case 3:	setVertex(layoutEntry.pointer, vtxNdx, interpolateRange(valueRange, f.toWidth<3>()));	break;
+				case 4:	setVertex(layoutEntry.pointer, vtxNdx, interpolateRange(valueRange, f.toWidth<4>()));	break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+	}
+
+	for (vector<RenderCommand>::iterator cmdIter = renderCmds.begin(); cmdIter != renderCmds.end(); ++cmdIter)
+		computeRandomRenderCommand(rnd, *cmdIter, m_renderCtx.getType().getAPI(), m_viewportSize.x(), m_viewportSize.y());
+
+	// Workaround for inaccurate barycentric/depth computation in current reference renderer:
+	// Small bias is added to the draw call depths, in increasing order, to avoid accuracy issues in depth comparison.
+	for (int cmdNdx = 0; cmdNdx < (int)renderCmds.size(); cmdNdx++)
+		renderCmds[cmdNdx].depth += 0.0231725f * float(cmdNdx);
+
+	{
+		const glu::VertexArrayPointer		posPtr			= getEntryWithPointer(vertexData, positionNdx).pointer;
+
+		sglr::Context* const				contexts[]		= { m_glCtx, &m_referenceCtx->context };
+		const deUint32						programs[]		= { m_glProgram, m_refProgram };
+		tcu::PixelBufferAccess				readDst[]		= { rendered.getAccess(), reference.getAccess() };
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(contexts); ndx++)
+		{
+			sglr::Context&	ctx			= *contexts[ndx];
+			const deUint32	program		= programs[ndx];
+
+			setupAttributes(ctx, vertexData, program);
+
+			ctx.disable		(GL_SCISSOR_TEST);
+			ctx.colorMask	(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+			ctx.depthMask	(GL_TRUE);
+			ctx.stencilMask	(~0u);
+			ctx.clearColor	(0.0f, 0.25f, 0.5f, 1.0f);
+			ctx.clear		(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+			ctx.useProgram	(program);
+
+			for (vector<rsg::VariableValue>::const_iterator uniformIter = uniformValues.begin(); uniformIter != uniformValues.end(); ++uniformIter)
+				setUniformValue(ctx, ctx.getUniformLocation(program, uniformIter->getVariable()->getName()), uniformIter->getValue());
+
+			for (vector<RenderCommand>::const_iterator cmdIter = renderCmds.begin(); cmdIter != renderCmds.end(); ++cmdIter)
+				render(ctx, posPtr, *cmdIter);
+
+			GLU_EXPECT_NO_ERROR(ctx.getError(), "Rendering failed");
+
+			ctx.readPixels(0, 0, m_viewportSize.x(), m_viewportSize.y(), GL_RGBA, GL_UNSIGNED_BYTE, readDst[ndx].getDataPtr());
+		}
+	}
+
+	{
+		const tcu::RGBA		threshold		= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold()+tcu::RGBA(3,3,3,3);
+		const bool			compareOk		= tcu::bilinearCompare(m_testCtx.getLog(), "CompareResult", "Image comparison result", reference.getAccess(), rendered.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
+
+		if (!compareOk)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+			return STOP;
+		}
+	}
+
+	m_iterNdx += 1;
+	return (m_iterNdx < NUM_ITERATIONS) ? CONTINUE : STOP;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsFragOpInteractionCase.hpp b/modules/glshared/glsFragOpInteractionCase.hpp
new file mode 100644
index 0000000..b75724d
--- /dev/null
+++ b/modules/glshared/glsFragOpInteractionCase.hpp
@@ -0,0 +1,90 @@
+#ifndef _GLSFRAGOPINTERACTIONCASE_HPP
+#define _GLSFRAGOPINTERACTIONCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader - render state interaction case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "rsgShader.hpp"
+#include "rsgParameters.hpp"
+
+namespace glu
+{
+class RenderContext;
+class ContextInfo;
+}
+
+namespace sglr
+{
+class GLContext;
+}
+
+namespace deqp
+{
+namespace gls
+{
+
+class RandomShaderProgram;
+
+class FragOpInteractionCase : public tcu::TestCase
+{
+public:
+											FragOpInteractionCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const rsg::ProgramParameters& params);
+											~FragOpInteractionCase	(void);
+
+	void									init					(void);
+	void									deinit					(void);
+	IterateResult							iterate					(void);
+
+private:
+											FragOpInteractionCase	(const FragOpInteractionCase&);
+	FragOpInteractionCase&					operator=				(const FragOpInteractionCase&);
+
+	struct ReferenceContext;
+
+	glu::RenderContext&						m_renderCtx;
+	const glu::ContextInfo&					m_ctxInfo;
+
+	rsg::ProgramParameters					m_params;
+
+	rsg::Shader								m_vertexShader;
+	rsg::Shader								m_fragmentShader;
+	std::vector<const rsg::ShaderInput*>	m_unifiedUniforms;
+
+	gls::RandomShaderProgram*				m_program;
+
+	sglr::GLContext*						m_glCtx;
+	ReferenceContext*						m_referenceCtx;
+
+	deUint32								m_glProgram;
+	deUint32								m_refProgram;
+
+	tcu::IVec2								m_viewportSize;
+
+	int										m_iterNdx;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSFRAGOPINTERACTIONCASE_HPP
diff --git a/modules/glshared/glsFragmentOpUtil.cpp b/modules/glshared/glsFragmentOpUtil.cpp
new file mode 100644
index 0000000..c147c7a
--- /dev/null
+++ b/modules/glshared/glsFragmentOpUtil.cpp
@@ -0,0 +1,301 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Fragment operation test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsFragmentOpUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluDrawUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+namespace FragmentOpUtil
+{
+
+template<typename T>
+inline T triQuadInterpolate (const T values[4], float xFactor, float yFactor)
+{
+	if (xFactor + yFactor < 1.0f)
+		return values[0] + (values[2]-values[0])*xFactor		+ (values[1]-values[0])*yFactor;
+	else
+		return values[3] + (values[1]-values[3])*(1.0f-xFactor)	+ (values[2]-values[3])*(1.0f-yFactor);
+}
+
+// GLSL ES 1.0 shaders
+static const char* s_glsl1VertSrc =
+	"attribute highp vec4 a_position;\n"
+	"attribute mediump vec4 a_color;\n"
+	"varying mediump vec4 v_color;\n"
+	"void main()\n"
+	"{\n"
+	"	gl_Position = a_position;\n"
+	"	v_color = a_color;\n"
+	"}\n";
+static const char* s_glsl1FragSrc =
+	"varying mediump vec4 v_color;\n"
+	"void main()\n"
+	"{\n"
+	"	gl_FragColor = v_color;\n"
+	"}\n";
+
+// GLSL ES 3.0 shaders
+static const char* s_glsl3VertSrc =
+	"#version 300 es\n"
+	"in highp vec4 a_position;\n"
+	"in mediump vec4 a_color;\n"
+	"out mediump vec4 v_color;\n"
+	"void main()\n"
+	"{\n"
+	"	gl_Position = a_position;\n"
+	"	v_color = a_color;\n"
+	"}\n";
+static const char* s_glsl3FragSrc =
+	"#version 300 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";
+
+// GLSL 3.3 shaders
+static const char* s_glsl33VertSrc =
+	"#version 330 core\n"
+	"in vec4 a_position;\n"
+	"in vec4 a_color;\n"
+	"in vec4 a_color1;\n"
+	"out vec4 v_color;\n"
+	"out vec4 v_color1;\n"
+	"void main()\n"
+	"{\n"
+	"	gl_Position	= a_position;\n"
+	"	v_color		= a_color;\n"
+	"	v_color1	= a_color1;\n"
+	"}\n";
+static const char* s_glsl33FragSrc =
+	"#version 330 core\n"
+	"in vec4 v_color;\n"
+	"in vec4 v_color1;\n"
+	"layout(location = 0, index = 0) out vec4 o_color;\n"
+	"layout(location = 0, index = 1) out vec4 o_color1;\n"
+	"void main()\n"
+	"{\n"
+	"	o_color  = v_color;\n"
+	"	o_color1 = v_color1;\n"
+	"}\n";
+
+static const char* getVertSrc (glu::GLSLVersion glslVersion)
+{
+	if (glslVersion == glu::GLSL_VERSION_100_ES)
+		return s_glsl1VertSrc;
+	else if (glslVersion == glu::GLSL_VERSION_300_ES)
+		return s_glsl3VertSrc;
+	else if (glslVersion == glu::GLSL_VERSION_330)
+		return s_glsl33VertSrc;
+
+	DE_ASSERT(DE_FALSE);
+	return 0;
+}
+
+static const char* getFragSrc (glu::GLSLVersion glslVersion)
+{
+	if (glslVersion == glu::GLSL_VERSION_100_ES)
+		return s_glsl1FragSrc;
+	else if (glslVersion == glu::GLSL_VERSION_300_ES)
+		return s_glsl3FragSrc;
+	else if (glslVersion == glu::GLSL_VERSION_330)
+		return s_glsl33FragSrc;
+
+	DE_ASSERT(DE_FALSE);
+	return 0;
+}
+
+QuadRenderer::QuadRenderer (const glu::RenderContext& context, glu::GLSLVersion glslVersion)
+	: m_context			(context)
+	, m_program			(DE_NULL)
+	, m_positionLoc		(0)
+	, m_colorLoc		(-1)
+	, m_color1Loc		(-1)
+	, m_blendFuncExt	(!glu::glslVersionIsES(glslVersion) && (glslVersion >= glu::GLSL_VERSION_330))
+{
+	DE_ASSERT(glslVersion == glu::GLSL_VERSION_100_ES ||
+			  glslVersion == glu::GLSL_VERSION_300_ES ||
+			  glslVersion == glu::GLSL_VERSION_330);
+
+	const glw::Functions&	gl		= context.getFunctions();
+	const char*				vertSrc	= getVertSrc(glslVersion);
+	const char*				fragSrc	= getFragSrc(glslVersion);
+
+	m_program = new glu::ShaderProgram(m_context, glu::makeVtxFragSources(vertSrc, fragSrc));
+	if (!m_program->isOk())
+	{
+		delete m_program;
+		throw tcu::TestError("Failed to compile program", DE_NULL, __FILE__, __LINE__);
+	}
+
+	m_positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_position");
+	m_colorLoc		= gl.getAttribLocation(m_program->getProgram(), "a_color");
+
+	if (m_blendFuncExt)
+		m_color1Loc = gl.getAttribLocation(m_program->getProgram(), "a_color1");
+
+	if (m_positionLoc < 0 || m_colorLoc < 0 || (m_blendFuncExt && m_color1Loc < 0))
+	{
+		delete m_program;
+		throw tcu::TestError("Invalid attribute locations", DE_NULL, __FILE__, __LINE__);
+	}
+}
+
+QuadRenderer::~QuadRenderer (void)
+{
+	delete m_program;
+}
+
+void QuadRenderer::render (const Quad& quad) const
+{
+	const float position[] =
+	{
+		quad.posA.x(), quad.posA.y(), quad.depth[0], 1.0f,
+		quad.posA.x(), quad.posB.y(), quad.depth[1], 1.0f,
+		quad.posB.x(), quad.posA.y(), quad.depth[2], 1.0f,
+		quad.posB.x(), quad.posB.y(), quad.depth[3], 1.0f
+	};
+	const deUint8 indices[] = { 0, 2, 1, 1, 2, 3 };
+
+	DE_STATIC_ASSERT(sizeof(tcu::Vec4) == sizeof(float)*4);
+	DE_STATIC_ASSERT(sizeof(quad.color) == sizeof(float)*4*4);
+	DE_STATIC_ASSERT(sizeof(quad.color1) == sizeof(float)*4*4);
+
+	std::vector<glu::VertexArrayBinding> vertexArrays;
+
+	vertexArrays.push_back(glu::va::Float(m_positionLoc,	4, 4, 0, &position[0]));
+	vertexArrays.push_back(glu::va::Float(m_colorLoc,		4, 4, 0, (const float*)&quad.color[0]));
+
+	if (m_blendFuncExt)
+		vertexArrays.push_back(glu::va::Float(m_color1Loc,	4, 4, 0, (const float*)&quad.color1[0]));
+
+	m_context.getFunctions().useProgram(m_program->getProgram());
+	glu::draw(m_context, m_program->getProgram(),
+			  (int)vertexArrays.size(), &vertexArrays[0],
+			  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+}
+
+ReferenceQuadRenderer::ReferenceQuadRenderer (void)
+	: m_fragmentBufferSize(0)
+{
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(m_fragmentDepths); i++)
+		m_fragmentDepths[i] = 0.0f;
+}
+
+void ReferenceQuadRenderer::flushFragmentBuffer (const rr::MultisamplePixelBufferAccess&	colorBuffer,
+												 const rr::MultisamplePixelBufferAccess&	depthBuffer,
+												 const rr::MultisamplePixelBufferAccess&	stencilBuffer,
+												 rr::FaceType								faceType,
+												 const rr::FragmentOperationState&			state)
+{
+	m_fragmentProcessor.render(colorBuffer, depthBuffer, stencilBuffer, &m_fragmentBuffer[0], m_fragmentBufferSize, faceType, state);
+	m_fragmentBufferSize = 0;
+}
+
+void ReferenceQuadRenderer::render (const tcu::PixelBufferAccess&			colorBuffer,
+									const tcu::PixelBufferAccess&			depthBuffer,
+									const tcu::PixelBufferAccess&			stencilBuffer,
+									const IntegerQuad&						quad,
+									const rr::FragmentOperationState&		state)
+{
+	bool			flipX			= quad.posA.x() > quad.posB.x();
+	bool			flipY			= quad.posA.y() > quad.posB.y();
+	rr::FaceType	faceType		= flipX == flipY ? rr::FACETYPE_FRONT : rr::FACETYPE_BACK;
+	int				xFirst			= flipX ? quad.posB.x() : quad.posA.x();
+	int				xLast			= flipX ? quad.posA.x() : quad.posB.x();
+	int				yFirst			= flipY ? quad.posB.y() : quad.posA.y();
+	int				yLast			= flipY ? quad.posA.y() : quad.posB.y();
+	float			width			= (float)(xLast - xFirst + 1);
+	float			height			= (float)(yLast - yFirst + 1);
+
+	for (int y = yFirst; y <= yLast; y++)
+	{
+		// Interpolation factor for y.
+		float yRatio = (0.5f + (float)(y - yFirst)) / height;
+		if (flipY)
+			yRatio = 1.0f - yRatio;
+
+		for (int x = xFirst; x <= xLast; x++)
+		{
+			// Interpolation factor for x.
+			float xRatio = (0.5f + (float)(x - xFirst)) / width;
+			if (flipX)
+				xRatio = 1.0f - xRatio;
+
+			tcu::Vec4	color	= triQuadInterpolate(quad.color, xRatio, yRatio);
+			tcu::Vec4	color1	= triQuadInterpolate(quad.color1, xRatio, yRatio);
+			float		depth	= triQuadInterpolate(quad.depth, xRatio, yRatio);
+
+			// Interpolated color and depth.
+
+			DE_STATIC_ASSERT(MAX_FRAGMENT_BUFFER_SIZE == DE_LENGTH_OF_ARRAY(m_fragmentBuffer));
+
+			if (m_fragmentBufferSize >= MAX_FRAGMENT_BUFFER_SIZE)
+				flushFragmentBuffer(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer),
+									rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthBuffer),
+									rr::MultisamplePixelBufferAccess::fromMultisampleAccess(stencilBuffer), faceType, state);
+
+			m_fragmentDepths[m_fragmentBufferSize] = depth;
+			m_fragmentBuffer[m_fragmentBufferSize] = rr::Fragment(tcu::IVec2(x, y), rr::GenericVec4(color), rr::GenericVec4(color1), 1u /* coverage mask */, &m_fragmentDepths[m_fragmentBufferSize]);
+			m_fragmentBufferSize++;
+		}
+	}
+
+	flushFragmentBuffer(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer),
+						rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthBuffer),
+						rr::MultisamplePixelBufferAccess::fromMultisampleAccess(stencilBuffer), faceType, state);
+}
+
+tcu::PixelBufferAccess getMultisampleAccess(const tcu::PixelBufferAccess& original)
+{
+	return tcu::PixelBufferAccess(original.getFormat(),
+								  1,
+								  original.getWidth(),
+								  original.getHeight(),
+								  original.getFormat().getPixelSize(),
+								  original.getRowPitch(),
+								  original.getDataPtr());
+}
+
+tcu::ConstPixelBufferAccess getMultisampleAccess(const tcu::ConstPixelBufferAccess& original)
+{
+	return tcu::ConstPixelBufferAccess(original.getFormat(),
+									   1,
+									   original.getWidth(),
+									   original.getHeight(),
+									   original.getFormat().getPixelSize(),
+									   original.getRowPitch(),
+									   original.getDataPtr());
+}
+
+} // FragmentOpUtil
+} // gls
+} // deqp
diff --git a/modules/glshared/glsFragmentOpUtil.hpp b/modules/glshared/glsFragmentOpUtil.hpp
new file mode 100644
index 0000000..7a98d83
--- /dev/null
+++ b/modules/glshared/glsFragmentOpUtil.hpp
@@ -0,0 +1,153 @@
+#ifndef _GLSFRAGMENTOPUTIL_HPP
+#define _GLSFRAGMENTOPUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Fragment operation test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuTexture.hpp"
+#include "rrFragmentOperations.hpp"
+
+namespace glu
+{
+class ShaderProgram;
+class RenderContext;
+}
+
+namespace deqp
+{
+namespace gls
+{
+namespace FragmentOpUtil
+{
+
+struct Quad
+{
+	tcu::Vec2		posA;
+	tcu::Vec2		posB;
+
+	// Normalized device coordinates (range [-1, 1]).
+	// In order (A.x, A.y), (A.x, B.y), (B.x, A.y), (B.x, B.y)
+	tcu::Vec4		color[4];
+	tcu::Vec4		color1[4];
+	float			depth[4];
+
+	Quad (void)
+		: posA(-1.0f, -1.0f)
+		, posB( 1.0f,  1.0f)
+	{
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(depth); i++)
+			depth[i] = 0.0f;
+	}
+};
+
+class QuadRenderer
+{
+public:
+								QuadRenderer				(const glu::RenderContext& context, glu::GLSLVersion glslVersion);
+								~QuadRenderer				(void);
+
+	void						render						(const Quad& quad) const;
+
+private:
+								QuadRenderer				(const QuadRenderer& other); // Not allowed!
+	QuadRenderer&				operator=					(const QuadRenderer& other); // Not allowed!
+
+	const glu::RenderContext&	m_context;
+	glu::ShaderProgram*			m_program;
+	int							m_positionLoc;
+	int							m_colorLoc;
+	int							m_color1Loc;
+	const bool					m_blendFuncExt;
+};
+
+struct IntegerQuad
+{
+	tcu::IVec2	posA;
+	tcu::IVec2	posB;
+
+	// Viewport coordinates (depth in range [0, 1]).
+	// In order (A.x, A.y), (A.x, B.y), (B.x, A.y), (B.x, B.y)
+	tcu::Vec4	color[4];
+	tcu::Vec4	color1[4];
+	float		depth[4];
+
+	IntegerQuad (int windowWidth, int windowHeight)
+		: posA(0,				0)
+		, posB(windowWidth-1,	windowHeight-1)
+	{
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(depth); i++)
+			depth[i] = 0.0f;
+	}
+
+	IntegerQuad (void)
+		: posA(0, 0)
+		, posB(1, 1)
+	{
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(depth); i++)
+			depth[i] = 0.0f;
+	}
+};
+
+class ReferenceQuadRenderer
+{
+public:
+								ReferenceQuadRenderer	(void);
+
+	void						render					(const tcu::PixelBufferAccess&			colorBuffer,
+														 const tcu::PixelBufferAccess&			depthBuffer,
+														 const tcu::PixelBufferAccess&			stencilBuffer,
+														 const IntegerQuad&						quad,
+														 const rr::FragmentOperationState&		state);
+
+private:
+	enum
+	{
+		MAX_FRAGMENT_BUFFER_SIZE = 1024
+	};
+
+	void						flushFragmentBuffer (const rr::MultisamplePixelBufferAccess&	colorBuffer,
+													 const rr::MultisamplePixelBufferAccess&	depthBuffer,
+													 const rr::MultisamplePixelBufferAccess&	stencilBuffer,
+													 rr::FaceType								faceType,
+													 const rr::FragmentOperationState&			state);
+
+	rr::Fragment				m_fragmentBuffer[MAX_FRAGMENT_BUFFER_SIZE];
+	float						m_fragmentDepths[MAX_FRAGMENT_BUFFER_SIZE];
+	int							m_fragmentBufferSize;
+
+	rr::FragmentProcessor		m_fragmentProcessor;
+};
+
+
+// These functions take a normally-indexed 2d pixel buffer and return a pixel buffer access
+// that indexes the same memory area, but using the multisample indexing convention.
+tcu::PixelBufferAccess					getMultisampleAccess(const tcu::PixelBufferAccess&			original);
+tcu::ConstPixelBufferAccess				getMultisampleAccess(const tcu::ConstPixelBufferAccess&	original);
+
+} // FragmentOpUtil
+} // gls
+} // deqp
+
+#endif // _GLSFRAGMENTOPUTIL_HPP
diff --git a/modules/glshared/glsInteractionTestUtil.cpp b/modules/glshared/glsInteractionTestUtil.cpp
new file mode 100644
index 0000000..2c8f126
--- /dev/null
+++ b/modules/glshared/glsInteractionTestUtil.cpp
@@ -0,0 +1,268 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Interaction test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsInteractionTestUtil.hpp"
+
+#include "tcuVector.hpp"
+
+#include "deRandom.hpp"
+#include "deMath.h"
+
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+namespace InteractionTestUtil
+{
+
+using tcu::Vec4;
+using tcu::IVec2;
+using std::vector;
+
+static Vec4 getRandomColor (de::Random& rnd)
+{
+	static const float components[] = { 0.0f, 0.2f, 0.4f, 0.5f, 0.6f, 0.8f, 1.0f };
+	float r = rnd.choose<float>(DE_ARRAY_BEGIN(components), DE_ARRAY_END(components));
+	float g = rnd.choose<float>(DE_ARRAY_BEGIN(components), DE_ARRAY_END(components));
+	float b = rnd.choose<float>(DE_ARRAY_BEGIN(components), DE_ARRAY_END(components));
+	float a = rnd.choose<float>(DE_ARRAY_BEGIN(components), DE_ARRAY_END(components));
+	return Vec4(r, g, b, a);
+}
+
+void computeRandomRenderState (de::Random& rnd, RenderState& state, glu::ApiType apiType, int targetWidth, int targetHeight)
+{
+	// Constants governing randomization.
+	const float		scissorTestProbability		= 0.2f;
+	const float		stencilTestProbability		= 0.4f;
+	const float		depthTestProbability		= 0.6f;
+	const float		blendProbability			= 0.4f;
+	const float		ditherProbability			= 0.5f;
+
+	const float		depthWriteProbability		= 0.7f;
+	const float		colorWriteProbability		= 0.7f;
+
+	const int		minStencilVal				= -3;
+	const int		maxStencilVal				= 260;
+
+	const int		maxScissorOutOfBounds		= 10;
+	const float		minScissorSize				= 0.7f;
+
+	static const deUint32 compareFuncs[] =
+	{
+		GL_NEVER,
+		GL_ALWAYS,
+		GL_LESS,
+		GL_LEQUAL,
+		GL_EQUAL,
+		GL_GEQUAL,
+		GL_GREATER,
+		GL_NOTEQUAL
+	};
+
+	static const deUint32 stencilOps[] =
+	{
+		GL_KEEP,
+		GL_ZERO,
+		GL_REPLACE,
+		GL_INCR,
+		GL_DECR,
+		GL_INVERT,
+		GL_INCR_WRAP,
+		GL_DECR_WRAP
+	};
+
+	static const deUint32 blendEquations[] =
+	{
+		GL_FUNC_ADD,
+		GL_FUNC_SUBTRACT,
+		GL_FUNC_REVERSE_SUBTRACT,
+		GL_MIN,
+		GL_MAX
+	};
+
+	static const deUint32 blendFuncs[] =
+	{
+		GL_ZERO,
+		GL_ONE,
+		GL_SRC_COLOR,
+		GL_ONE_MINUS_SRC_COLOR,
+		GL_DST_COLOR,
+		GL_ONE_MINUS_DST_COLOR,
+		GL_SRC_ALPHA,
+		GL_ONE_MINUS_SRC_ALPHA,
+		GL_DST_ALPHA,
+		GL_ONE_MINUS_DST_ALPHA,
+		GL_CONSTANT_COLOR,
+		GL_ONE_MINUS_CONSTANT_COLOR,
+		GL_CONSTANT_ALPHA,
+		GL_ONE_MINUS_CONSTANT_ALPHA,
+		GL_SRC_ALPHA_SATURATE
+	};
+
+	static const deUint32 blendEquationsES2[] =
+	{
+		GL_FUNC_ADD,
+		GL_FUNC_SUBTRACT,
+		GL_FUNC_REVERSE_SUBTRACT
+	};
+
+	static const deUint32 blendFuncsDstES2[] =
+	{
+		GL_ZERO,
+		GL_ONE,
+		GL_SRC_COLOR,
+		GL_ONE_MINUS_SRC_COLOR,
+		GL_DST_COLOR,
+		GL_ONE_MINUS_DST_COLOR,
+		GL_SRC_ALPHA,
+		GL_ONE_MINUS_SRC_ALPHA,
+		GL_DST_ALPHA,
+		GL_ONE_MINUS_DST_ALPHA,
+		GL_CONSTANT_COLOR,
+		GL_ONE_MINUS_CONSTANT_COLOR,
+		GL_CONSTANT_ALPHA,
+		GL_ONE_MINUS_CONSTANT_ALPHA
+	};
+
+	state.scissorTestEnabled	= rnd.getFloat() < scissorTestProbability;
+	state.stencilTestEnabled	= rnd.getFloat() < stencilTestProbability;
+	state.depthTestEnabled		= rnd.getFloat() < depthTestProbability;
+	state.blendEnabled			= rnd.getFloat() < blendProbability;
+	state.ditherEnabled			= rnd.getFloat() < ditherProbability;
+
+	if (state.scissorTestEnabled)
+	{
+		int minScissorW		= deCeilFloatToInt32(minScissorSize*targetWidth);
+		int minScissorH		= deCeilFloatToInt32(minScissorSize*targetHeight);
+		int maxScissorW		= targetWidth + 2*maxScissorOutOfBounds;
+		int maxScissorH		= targetHeight + 2*maxScissorOutOfBounds;
+
+		int scissorW		= rnd.getInt(minScissorW, maxScissorW);
+		int	scissorH		= rnd.getInt(minScissorH, maxScissorH);
+		int scissorX		= rnd.getInt(-maxScissorOutOfBounds, targetWidth+maxScissorOutOfBounds-scissorW);
+		int scissorY		= rnd.getInt(-maxScissorOutOfBounds, targetHeight+maxScissorOutOfBounds-scissorH);
+
+		state.scissorRectangle = rr::WindowRectangle(scissorX, scissorY, scissorW, scissorH);
+	}
+
+	if (state.stencilTestEnabled)
+	{
+		for (int ndx = 0; ndx < 2; ndx++)
+		{
+			state.stencil[ndx].function			= rnd.choose<deUint32>(DE_ARRAY_BEGIN(compareFuncs), DE_ARRAY_END(compareFuncs));
+			state.stencil[ndx].reference		= rnd.getInt(minStencilVal, maxStencilVal);
+			state.stencil[ndx].compareMask		= rnd.getUint32();
+			state.stencil[ndx].stencilFailOp	= rnd.choose<deUint32>(DE_ARRAY_BEGIN(stencilOps), DE_ARRAY_END(stencilOps));
+			state.stencil[ndx].depthFailOp		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(stencilOps), DE_ARRAY_END(stencilOps));
+			state.stencil[ndx].depthPassOp		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(stencilOps), DE_ARRAY_END(stencilOps));
+			state.stencil[ndx].writeMask		= rnd.getUint32();
+		}
+	}
+
+	if (state.depthTestEnabled)
+	{
+		state.depthFunc			= rnd.choose<deUint32>(DE_ARRAY_BEGIN(compareFuncs), DE_ARRAY_END(compareFuncs));
+		state.depthWriteMask	= rnd.getFloat() < depthWriteProbability;
+	}
+
+	if (state.blendEnabled)
+	{
+		if (apiType == glu::ApiType::es(2,0))
+		{
+			state.blendRGBState.equation	= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendEquationsES2), DE_ARRAY_END(blendEquationsES2));
+			state.blendRGBState.srcFunc		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendFuncs), DE_ARRAY_END(blendFuncs));
+			state.blendRGBState.dstFunc		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendFuncsDstES2), DE_ARRAY_END(blendFuncsDstES2));
+
+			state.blendAState.equation		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendEquationsES2), DE_ARRAY_END(blendEquationsES2));
+			state.blendAState.srcFunc		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendFuncs), DE_ARRAY_END(blendFuncs));
+			state.blendAState.dstFunc		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendFuncsDstES2), DE_ARRAY_END(blendFuncsDstES2));
+		}
+		else
+		{
+			state.blendRGBState.equation	= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendEquations), DE_ARRAY_END(blendEquations));
+			state.blendRGBState.srcFunc		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendFuncs), DE_ARRAY_END(blendFuncs));
+			state.blendRGBState.dstFunc		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendFuncs), DE_ARRAY_END(blendFuncs));
+
+			state.blendAState.equation		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendEquations), DE_ARRAY_END(blendEquations));
+			state.blendAState.srcFunc		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendFuncs), DE_ARRAY_END(blendFuncs));
+			state.blendAState.dstFunc		= rnd.choose<deUint32>(DE_ARRAY_BEGIN(blendFuncs), DE_ARRAY_END(blendFuncs));
+		}
+
+		state.blendColor				= getRandomColor(rnd);
+	}
+
+	for (int ndx = 0; ndx < 4; ndx++)
+		state.colorMask[ndx] = rnd.getFloat() < colorWriteProbability;
+}
+
+void computeRandomQuad (de::Random& rnd, gls::FragmentOpUtil::IntegerQuad& quad, int targetWidth, int targetHeight)
+{
+	// \note In viewport coordinates.
+	// \todo [2012-12-18 pyry] Out-of-bounds values.
+	// \note Not using depth 1.0 since clearing with 1.0 and rendering with 1.0 may not be same value.
+	static const float depthValues[] = { 0.0f, 0.2f, 0.4f, 0.5f, 0.51f, 0.6f, 0.8f, 0.95f };
+
+	const int		maxOutOfBounds		= 0;
+	const float		minSize				= 0.5f;
+
+	int minW		= deCeilFloatToInt32(minSize*targetWidth);
+	int minH		= deCeilFloatToInt32(minSize*targetHeight);
+	int maxW		= targetWidth + 2*maxOutOfBounds;
+	int maxH		= targetHeight + 2*maxOutOfBounds;
+
+	int width		= rnd.getInt(minW, maxW);
+	int	height		= rnd.getInt(minH, maxH);
+	int x			= rnd.getInt(-maxOutOfBounds, targetWidth+maxOutOfBounds-width);
+	int y			= rnd.getInt(-maxOutOfBounds, targetHeight+maxOutOfBounds-height);
+
+	bool flipX		= rnd.getBool();
+	bool flipY		= rnd.getBool();
+
+	float depth		= rnd.choose<float>(DE_ARRAY_BEGIN(depthValues), DE_ARRAY_END(depthValues));
+
+	quad.posA	= IVec2(flipX ? (x+width-1) : x, flipY ? (y+height-1) : y);
+	quad.posB	= IVec2(flipX ? x : (x+width-1), flipY ? y : (y+height-1));
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(quad.color); ndx++)
+		quad.color[ndx] = getRandomColor(rnd);
+
+	std::fill(DE_ARRAY_BEGIN(quad.depth), DE_ARRAY_END(quad.depth), depth);
+}
+
+void computeRandomRenderCommands (de::Random& rnd, glu::ApiType apiType, int numCommands, int targetW, int targetH, vector<RenderCommand>& dst)
+{
+	DE_ASSERT(dst.empty());
+
+	dst.resize(numCommands);
+	for (vector<RenderCommand>::iterator cmd = dst.begin(); cmd != dst.end(); cmd++)
+	{
+		computeRandomRenderState(rnd, cmd->state, apiType, targetW, targetH);
+		computeRandomQuad(rnd, cmd->quad, targetW, targetH);
+	}
+}
+
+} // InteractionTestUtil
+} // gls
+} // deqp
diff --git a/modules/glshared/glsInteractionTestUtil.hpp b/modules/glshared/glsInteractionTestUtil.hpp
new file mode 100644
index 0000000..849d460
--- /dev/null
+++ b/modules/glshared/glsInteractionTestUtil.hpp
@@ -0,0 +1,129 @@
+#ifndef _GLSINTERACTIONTESTUTIL_HPP
+#define _GLSINTERACTIONTESTUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Interaction test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "glsFragmentOpUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "rrRenderState.hpp"
+
+namespace de
+{
+class Random;
+}
+
+namespace deqp
+{
+namespace gls
+{
+namespace InteractionTestUtil
+{
+
+struct BlendState
+{
+	deUint32	equation;
+	deUint32	srcFunc;
+	deUint32	dstFunc;
+
+	BlendState (void)
+		: equation	(0)
+		, srcFunc	(0)
+		, dstFunc	(0)
+	{
+	}
+};
+
+struct StencilState
+{
+	deUint32	function;
+	int			reference;
+	deUint32	compareMask;
+
+	deUint32	stencilFailOp;
+	deUint32	depthFailOp;
+	deUint32	depthPassOp;
+
+	deUint32	writeMask;
+
+	StencilState (void)
+		: function		(0)
+		, reference		(0)
+		, compareMask	(0)
+		, stencilFailOp	(0)
+		, depthFailOp	(0)
+		, depthPassOp	(0)
+		, writeMask		(0)
+	{
+	}
+};
+
+struct RenderState
+{
+	bool				scissorTestEnabled;
+	rr::WindowRectangle	scissorRectangle;
+
+	bool				stencilTestEnabled;
+	StencilState		stencil[rr::FACETYPE_LAST];
+
+	bool				depthTestEnabled;
+	deUint32			depthFunc;
+	bool				depthWriteMask;
+
+	bool				blendEnabled;
+	BlendState			blendRGBState;
+	BlendState			blendAState;
+	tcu::Vec4			blendColor;
+
+	bool				ditherEnabled;
+
+	tcu::BVec4			colorMask;
+
+	RenderState (void)
+		: scissorTestEnabled	(false)
+		, scissorRectangle		(0, 0, 0, 0)
+		, stencilTestEnabled	(false)
+		, depthTestEnabled		(false)
+		, depthFunc				(0)
+		, depthWriteMask		(false)
+		, blendEnabled			(false)
+		, ditherEnabled			(false)
+	{
+	}
+};
+
+struct RenderCommand
+{
+	gls::FragmentOpUtil::IntegerQuad	quad;
+	RenderState							state;
+};
+
+void		computeRandomRenderState		(de::Random& rnd, RenderState& state, glu::ApiType apiType, int targetWidth, int targetHeight);
+void		computeRandomQuad				(de::Random& rnd, gls::FragmentOpUtil::IntegerQuad& quad, int targetWidth, int targetHeight);
+void		computeRandomRenderCommands		(de::Random& rnd, glu::ApiType apiType, int numCommands, int targetW, int targetH, std::vector<RenderCommand>& dst);
+
+} // InteractionTestUtil
+} // gls
+} // deqp
+
+#endif // _GLSINTERACTIONTESTUTIL_HPP
diff --git a/modules/glshared/glsLifetimeTests.cpp b/modules/glshared/glsLifetimeTests.cpp
new file mode 100644
index 0000000..e1c59ad
--- /dev/null
+++ b/modules/glshared/glsLifetimeTests.cpp
@@ -0,0 +1,1248 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Common object lifetime tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsLifetimeTests.hpp"
+
+#include "deString.h"
+#include "deRandom.hpp"
+#include "deSTLUtil.hpp"
+#include "deStringUtil.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuTestLog.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluDefs.hpp"
+#include "glwFunctions.hpp"
+
+#include <vector>
+#include <map>
+#include <algorithm>
+#include <sstream>
+
+namespace deqp
+{
+namespace gls
+{
+namespace LifetimeTests
+{
+namespace details
+{
+
+using std::map;
+using std::string;
+using std::ostringstream;
+using de::Random;
+using tcu::RenderTarget;
+using tcu::RGBA;
+using tcu::StringTemplate;
+using tcu::TestCase;
+typedef TestCase::IterateResult IterateResult;
+using tcu::TestLog;
+using tcu::ScopedLogSection;
+using glu::Program;
+using glu::Shader;
+using glu::Framebuffer;
+using glu::SHADERTYPE_VERTEX;
+using glu::SHADERTYPE_FRAGMENT;
+using namespace glw;
+
+enum { VIEWPORT_SIZE = 128, FRAMEBUFFER_SIZE = 128 };
+
+GLint getInteger (ContextWrapper& gl, GLenum queryParam)
+{
+	GLint ret = 0;
+	GLU_CHECK_CALL_ERROR(
+		gl.glGetIntegerv(queryParam, &ret),
+		gl.glGetError());
+	gl.log() << TestLog::Message << "// Single integer output: " << ret << TestLog::EndMessage;
+	return ret;
+}
+
+#define GLSL100_SRC(BODY) ("#version 100\n" #BODY "\n")
+
+static const char* const s_vertexShaderSrc = GLSL100_SRC(
+	attribute vec2 pos;
+	void main()
+	{
+		gl_Position = vec4(pos.xy, 0.0, 1.0);
+	}
+	);
+
+static const char* const s_fragmentShaderSrc = GLSL100_SRC(
+	void main()
+	{
+		gl_FragColor = vec4(1.0);
+	}
+	);
+
+class CheckedShader : public Shader
+{
+public:
+	CheckedShader (const RenderContext& renderCtx, glu::ShaderType type, const string& src)
+		: Shader (renderCtx, type)
+	{
+		const char* const srcStr = src.c_str();
+		setSources(1, &srcStr, DE_NULL);
+		compile();
+		TCU_CHECK(getCompileStatus());
+	}
+};
+
+class CheckedProgram : public Program
+{
+public:
+	CheckedProgram	(const RenderContext& renderCtx, GLuint vtxShader, GLuint fragShader)
+		: Program	(renderCtx)
+	{
+		attachShader(vtxShader);
+		attachShader(fragShader);
+		link();
+		TCU_CHECK(getLinkStatus());
+	}
+};
+
+ContextWrapper::ContextWrapper (const Context& ctx)
+	: CallLogWrapper	(ctx.gl(), ctx.log())
+	, m_ctx				(ctx)
+{
+	enableLogging(true);
+}
+
+void SimpleBinder::bind (GLuint name)
+{
+	(this->*m_bindFunc)(m_bindTarget, name);
+}
+
+GLuint SimpleBinder::getBinding (void)
+{
+	return getInteger(*this, m_bindingParam);
+}
+
+GLuint SimpleType::gen (void)
+{
+	GLuint ret;
+	(this->*m_genFunc)(1, &ret);
+	return ret;
+}
+
+class VertexArrayBinder : public SimpleBinder
+{
+public:
+						VertexArrayBinder	(Context& ctx)
+							: SimpleBinder	(ctx, 0, GL_NONE, GL_VERTEX_ARRAY_BINDING, true) {}
+	void				bind				(GLuint name) { glBindVertexArray(name); }
+};
+
+class QueryBinder : public Binder
+{
+public:
+						QueryBinder		(Context& ctx) : Binder(ctx) {}
+	void				bind			(GLuint name)
+	{
+		if (name != 0)
+			glBeginQuery(GL_ANY_SAMPLES_PASSED, name);
+		else
+			glEndQuery(GL_ANY_SAMPLES_PASSED);
+	}
+	GLuint				getBinding		(void) { return 0; }
+};
+
+bool ProgramType::isDeleteFlagged (GLuint name)
+{
+	GLint deleteFlagged = 0;
+	glGetProgramiv(name, GL_DELETE_STATUS, &deleteFlagged);
+	return deleteFlagged != 0;
+}
+
+bool ShaderType::isDeleteFlagged (GLuint name)
+{
+	GLint deleteFlagged = 0;
+	glGetShaderiv(name, GL_DELETE_STATUS, &deleteFlagged);
+	return deleteFlagged != 0;
+}
+
+void setupFbo (const Context& ctx, GLuint seed, GLuint fbo)
+{
+	const Functions& gl = ctx.getRenderContext().getFunctions();
+
+	GLU_CHECK_CALL_ERROR(gl.bindFramebuffer(GL_FRAMEBUFFER, fbo),
+						 gl.getError());
+
+	if (seed == 0)
+	{
+		gl.clearColor(0.0, 0.0, 0.0, 1.0);
+		GLU_CHECK_CALL_ERROR(gl.clear(GL_COLOR_BUFFER_BIT), gl.getError());
+	}
+	else
+	{
+		Random			rnd		(seed);
+		const GLsizei	width	= rnd.getInt(0, FRAMEBUFFER_SIZE);
+		const GLsizei	height	= rnd.getInt(0, FRAMEBUFFER_SIZE);
+		const GLint		x		= rnd.getInt(0, FRAMEBUFFER_SIZE - width);
+		const GLint		y		= rnd.getInt(0, FRAMEBUFFER_SIZE - height);
+		const GLfloat	r1		= rnd.getFloat();
+		const GLfloat	g1		= rnd.getFloat();
+		const GLfloat	b1		= rnd.getFloat();
+		const GLfloat	a1		= rnd.getFloat();
+		const GLfloat	r2		= rnd.getFloat();
+		const GLfloat	g2		= rnd.getFloat();
+		const GLfloat	b2		= rnd.getFloat();
+		const GLfloat	a2		= rnd.getFloat();
+
+		GLU_CHECK_CALL_ERROR(gl.clearColor(r1, g1, b1, a1), gl.getError());
+		GLU_CHECK_CALL_ERROR(gl.clear(GL_COLOR_BUFFER_BIT), gl.getError());
+		gl.scissor(x, y, width, height);
+		gl.enable(GL_SCISSOR_TEST);
+		gl.clearColor(r2, g2, b2, a2);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		gl.disable(GL_SCISSOR_TEST);
+	}
+
+	gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	GLU_CHECK_ERROR(gl.getError());
+}
+
+void drawFbo (const Context& ctx, GLuint fbo, Surface& dst)
+{
+	const RenderContext& renderCtx = ctx.getRenderContext();
+	const Functions& gl = renderCtx.getFunctions();
+
+	GLU_CHECK_CALL_ERROR(
+		gl.bindFramebuffer(GL_FRAMEBUFFER, fbo),
+		gl.getError());
+
+	dst.setSize(FRAMEBUFFER_SIZE, FRAMEBUFFER_SIZE);
+	glu::readPixels(renderCtx, 0, 0, dst.getAccess());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels from framebuffer");
+
+	GLU_CHECK_CALL_ERROR(
+		gl.bindFramebuffer(GL_FRAMEBUFFER, 0),
+		gl.getError());
+}
+
+GLuint getFboAttachment (const Functions& gl, GLuint fbo, GLenum requiredType)
+{
+	GLint type = 0, name = 0;
+	gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
+	GLU_CHECK_CALL_ERROR(
+		gl.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+											   GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
+											   &type),
+		gl.getError());
+	GLU_CHECK_CALL_ERROR(
+		gl.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+											   GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
+											   &name),
+		gl.getError());
+	gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+	GLU_CHECK_ERROR(gl.getError());
+
+	GLuint ret = GLenum(type) == requiredType ? name : 0;
+	return ret;
+}
+
+void FboAttacher::initAttachment (GLuint seed, GLuint element)
+{
+	Binder& binder = *getElementType().binder();
+	Framebuffer fbo(getRenderContext());
+
+	enableLogging(false);
+
+	binder.enableLogging(false);
+	binder.bind(element);
+	initStorage();
+	binder.bind(0);
+	binder.enableLogging(true);
+
+	attach(element, *fbo);
+	setupFbo(getContext(), seed, *fbo);
+	detach(element, *fbo);
+
+	enableLogging(true);
+
+	log() << TestLog::Message
+		  << "// Drew to " << getElementType().getName() << " " << element
+		  << " with seed " << seed << "."
+		  << TestLog::EndMessage;
+}
+
+void FboInputAttacher::drawContainer (GLuint fbo, Surface& dst)
+{
+	drawFbo(getContext(), fbo, dst);
+	log() << TestLog::Message
+		  << "// Read pixels from framebuffer " << fbo << " to output image."
+		  << TestLog::EndMessage;
+}
+
+void FboOutputAttacher::setupContainer (GLuint seed, GLuint fbo)
+{
+	setupFbo(getContext(), seed, fbo);
+	log() << TestLog::Message
+		  << "// Drew to framebuffer " << fbo << " with seed " << seed << "."
+		  << TestLog::EndMessage;
+}
+
+void FboOutputAttacher::drawAttachment (GLuint element, Surface& dst)
+{
+	Framebuffer fbo(getRenderContext());
+	m_attacher.enableLogging(false);
+	m_attacher.attach(element, *fbo);
+	drawFbo(getContext(), *fbo, dst);
+	m_attacher.detach(element, *fbo);
+	m_attacher.enableLogging(true);
+	log() << TestLog::Message
+		  << "// Read pixels from " << m_attacher.getElementType().getName() << " " << element
+		  << " to output image."
+		  << TestLog::EndMessage;
+	GLU_CHECK_ERROR(gl().getError());
+}
+
+void TextureFboAttacher::attach (GLuint texture, GLuint fbo)
+{
+	GLU_CHECK_CALL_ERROR(
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo),
+		gl().getError());
+	GLU_CHECK_CALL_ERROR(
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+								  GL_TEXTURE_2D, texture, 0),
+		gl().getError());
+	GLU_CHECK_CALL_ERROR(
+		glBindFramebuffer(GL_FRAMEBUFFER, 0),
+		gl().getError());
+}
+
+void TextureFboAttacher::detach (GLuint texture, GLuint fbo)
+{
+	DE_UNREF(texture);
+	GLU_CHECK_CALL_ERROR(
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo),
+		gl().getError());
+	GLU_CHECK_CALL_ERROR(
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0),
+		gl().getError());
+	GLU_CHECK_CALL_ERROR(
+		glBindFramebuffer(GL_FRAMEBUFFER, 0),
+		gl().getError());
+}
+
+GLuint TextureFboAttacher::getAttachment (GLuint fbo)
+{
+	return getFboAttachment(gl(), fbo, GL_TEXTURE);
+}
+
+void TextureFboAttacher::initStorage (void)
+{
+	GLU_CHECK_CALL_ERROR(
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FRAMEBUFFER_SIZE, FRAMEBUFFER_SIZE, 0,
+					 GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, DE_NULL),
+		gl().getError());
+}
+
+void RboFboAttacher::initStorage (void)
+{
+	GLU_CHECK_CALL_ERROR(
+		glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, FRAMEBUFFER_SIZE, FRAMEBUFFER_SIZE),
+		gl().getError());
+}
+
+void RboFboAttacher::attach (GLuint rbo, GLuint fbo)
+{
+	GLU_CHECK_CALL_ERROR(
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo),
+		gl().getError());
+	GLU_CHECK_CALL_ERROR(
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo),
+		gl().getError());
+	GLU_CHECK_CALL_ERROR(
+		glBindFramebuffer(GL_FRAMEBUFFER, 0),
+		gl().getError());
+}
+
+void RboFboAttacher::detach (GLuint rbo, GLuint fbo)
+{
+	DE_UNREF(rbo);
+	GLU_CHECK_CALL_ERROR(
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo),
+		gl().getError());
+	GLU_CHECK_CALL_ERROR(
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0),
+		gl().getError());
+	GLU_CHECK_CALL_ERROR(
+		glBindFramebuffer(GL_FRAMEBUFFER, 0),
+		gl().getError());
+}
+
+GLuint RboFboAttacher::getAttachment (GLuint fbo)
+{
+	return getFboAttachment(gl(), fbo, GL_RENDERBUFFER);
+}
+
+static const char* const s_fragmentShaderTemplate = GLSL100_SRC(
+	void main()
+	{
+		gl_FragColor = vec4(${RED}, ${GREEN}, ${BLUE}, 1.0);
+	}
+	);
+
+void ShaderProgramAttacher::initAttachment (GLuint seed, GLuint shader)
+{
+	using					de::insert;
+	using					de::floatToString;
+
+	Random					rnd(seed);
+	map<string, string>		params;
+	const StringTemplate	sourceTmpl	(s_fragmentShaderTemplate);
+
+	insert(params, "RED",	floatToString(rnd.getFloat(), 4));
+	insert(params, "GREEN",	floatToString(rnd.getFloat(), 4));
+	insert(params, "BLUE",	floatToString(rnd.getFloat(), 4));
+
+	{
+		const string			source		= sourceTmpl.specialize(params);
+		const char* const		sourceStr	= source.c_str();
+
+		GLU_CHECK_CALL_ERROR(glShaderSource(shader, 1, &sourceStr, DE_NULL), gl().getError());
+		GLU_CHECK_CALL_ERROR(glCompileShader(shader), gl().getError());
+
+		{
+			GLint compileStatus = 0;
+			gl().getShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
+			TCU_CHECK_MSG(compileStatus != 0, sourceStr);
+		}
+	}
+}
+
+void ShaderProgramAttacher::attach (GLuint shader, GLuint program)
+{
+	GLU_CHECK_CALL_ERROR(
+		glAttachShader(program, shader),
+		gl().getError());
+}
+
+void ShaderProgramAttacher::detach (GLuint shader, GLuint program)
+{
+	GLU_CHECK_CALL_ERROR(
+		glDetachShader(program, shader),
+		gl().getError());
+}
+
+GLuint ShaderProgramAttacher::getAttachment (GLuint program)
+{
+	GLuint			shaders[2]	= { 0, 0 };
+	const GLsizei	shadersLen	= DE_LENGTH_OF_ARRAY(shaders);
+	GLsizei			numShaders	= 0;
+	GLuint			ret			= 0;
+
+	gl().getAttachedShaders(program, shadersLen, &numShaders, shaders);
+
+	// There should ever be at most one attached shader in normal use, but if
+	// something is wrong, the temporary vertex shader might not have been
+	// detached properly, so let's find the fragment shader explicitly.
+	for (int ndx = 0; ndx < de::min<GLsizei>(shadersLen, numShaders); ++ndx)
+	{
+		GLint shaderType = GL_NONE;
+		gl().getShaderiv(shaders[ndx], GL_SHADER_TYPE, &shaderType);
+
+		if (shaderType == GL_FRAGMENT_SHADER)
+		{
+			ret = shaders[ndx];
+			break;
+		}
+	}
+
+	return ret;
+}
+
+void setViewport (const RenderContext& renderCtx, const Rectangle& rect)
+{
+	renderCtx.getFunctions().viewport(rect.x, rect.y, rect.width, rect.height);
+}
+
+void readRectangle (const RenderContext& renderCtx, const Rectangle& rect, Surface& dst)
+{
+	dst.setSize(rect.width, rect.height);
+	glu::readPixels(renderCtx, rect.x, rect.y, dst.getAccess());
+}
+
+Rectangle randomViewport (const RenderContext& ctx, GLint maxWidth, GLint maxHeight,
+						  Random& rnd)
+{
+	const RenderTarget&	target	= ctx.getRenderTarget();
+	const GLint			width	= de::min(target.getWidth(), maxWidth);
+	const GLint			xOff	= rnd.getInt(0, width - maxWidth);
+	const GLint			height	= de::min(target.getHeight(), maxHeight);
+	const GLint			yOff	= rnd.getInt(0, height - maxHeight);
+
+	return Rectangle(xOff, yOff, width, height);
+}
+
+void ShaderProgramInputAttacher::drawContainer (GLuint program, Surface& dst)
+{
+	static const float	s_vertices[6]	= { -1.0, 0.0, 1.0, 1.0, 0.0, -1.0 };
+	Random				rnd				(program);
+	CheckedShader		vtxShader		(getRenderContext(),
+										 SHADERTYPE_VERTEX, s_vertexShaderSrc);
+	const Rectangle		viewport		= randomViewport(getRenderContext(),
+														 VIEWPORT_SIZE, VIEWPORT_SIZE, rnd);
+
+	gl().attachShader(program, vtxShader.getShader());
+	gl().linkProgram(program);
+
+	{
+		GLint linkStatus = 0;
+		gl().getProgramiv(program, GL_LINK_STATUS, &linkStatus);
+		TCU_CHECK(linkStatus != 0);
+	}
+
+	log() << TestLog::Message
+		  << "// Attached a temporary vertex shader and linked program " << program
+		  << TestLog::EndMessage;
+
+	setViewport(getRenderContext(), viewport);
+	log() << TestLog::Message << "// Positioned viewport randomly" << TestLog::EndMessage;
+
+	glUseProgram(program);
+	{
+		GLint posLoc = gl().getAttribLocation(program, "pos");
+		TCU_CHECK(posLoc >= 0);
+
+		gl().enableVertexAttribArray(posLoc);
+
+		gl().clearColor(0, 0, 0, 1);
+		gl().clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		gl().vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, s_vertices);
+		gl().drawArrays(GL_TRIANGLES, 0, 3);
+
+		gl().disableVertexAttribArray(posLoc);
+		log () << TestLog::Message << "// Drew a fixed triangle" << TestLog::EndMessage;
+	}
+	glUseProgram(0);
+
+	readRectangle(getRenderContext(), viewport, dst);
+	log() << TestLog::Message << "// Copied viewport to output image" << TestLog::EndMessage;
+
+	gl().detachShader(program, vtxShader.getShader());
+	log() << TestLog::Message << "// Removed temporary vertex shader" << TestLog::EndMessage;
+}
+
+ES2Types::ES2Types (const Context& ctx)
+	: Types			(ctx)
+	, m_bufferBind	(ctx, &CallLogWrapper::glBindBuffer,
+					 GL_ARRAY_BUFFER, GL_ARRAY_BUFFER_BINDING)
+	, m_bufferType	(ctx, "buffer", &CallLogWrapper::glGenBuffers,
+					 &CallLogWrapper::glDeleteBuffers,
+					 &CallLogWrapper::glIsBuffer, &m_bufferBind)
+	, m_textureBind	(ctx, &CallLogWrapper::glBindTexture, GL_TEXTURE_2D, GL_TEXTURE_BINDING_2D)
+	, m_textureType	(ctx, "texture", &CallLogWrapper::glGenTextures,
+					 &CallLogWrapper::glDeleteTextures,
+					 &CallLogWrapper::glIsTexture, &m_textureBind)
+	, m_rboBind		(ctx, &CallLogWrapper::glBindRenderbuffer,
+					 GL_RENDERBUFFER, GL_RENDERBUFFER_BINDING)
+	, m_rboType		(ctx, "renderbuffer",
+					 &CallLogWrapper::glGenRenderbuffers,
+					 &CallLogWrapper::glDeleteRenderbuffers,
+					 &CallLogWrapper::glIsRenderbuffer, &m_rboBind)
+	, m_fboBind		(ctx, &CallLogWrapper::glBindFramebuffer,
+					 GL_FRAMEBUFFER, GL_FRAMEBUFFER_BINDING)
+	, m_fboType		(ctx, "framebuffer",
+					 &CallLogWrapper::glGenFramebuffers,
+					 &CallLogWrapper::glDeleteFramebuffers,
+					 &CallLogWrapper::glIsFramebuffer, &m_fboBind)
+	, m_shaderType	(ctx)
+	, m_programType	(ctx)
+	, m_texFboAtt	(ctx, m_textureType, m_fboType)
+	, m_texFboInAtt	(m_texFboAtt)
+	, m_texFboOutAtt(m_texFboAtt)
+	, m_rboFboAtt	(ctx, m_rboType, m_fboType)
+	, m_rboFboInAtt	(m_rboFboAtt)
+	, m_rboFboOutAtt(m_rboFboAtt)
+	, m_shaderAtt	(ctx, m_shaderType, m_programType)
+	, m_shaderInAtt	(m_shaderAtt)
+{
+	Type* const types[] =
+	{
+		&m_bufferType, &m_textureType, &m_rboType, &m_fboType, &m_shaderType, &m_programType
+	};
+	m_types.insert(m_types.end(), DE_ARRAY_BEGIN(types), DE_ARRAY_END(types));
+
+	m_attachers.push_back(&m_texFboAtt);
+	m_attachers.push_back(&m_rboFboAtt);
+	m_attachers.push_back(&m_shaderAtt);
+
+	m_inAttachers.push_back(&m_texFboInAtt);
+	m_inAttachers.push_back(&m_rboFboInAtt);
+	m_inAttachers.push_back(&m_shaderInAtt);
+
+	m_outAttachers.push_back(&m_texFboOutAtt);
+	m_outAttachers.push_back(&m_rboFboOutAtt);
+}
+
+class Name
+{
+public:
+				Name		(Type& type) : m_type(type), m_name(type.gen()) {}
+				Name		(Type& type, GLuint name) : m_type(type), m_name(name) {}
+				~Name		(void) { m_type.release(m_name); }
+	GLuint		operator*	(void) const { return m_name; }
+
+private:
+	Type&			m_type;
+	const GLuint	m_name;
+};
+
+class ResultCollector
+{
+public:
+					ResultCollector		(TestContext& testCtx);
+	bool			check				(bool cond, const char* msg);
+	void			fail				(const char* msg);
+	void			warn				(const char* msg);
+					~ResultCollector	(void);
+
+private:
+	void			addResult			(qpTestResult result, const char* msg);
+
+	TestContext&	m_testCtx;
+	TestLog&		m_log;
+	qpTestResult	m_result;
+	const char*		m_message;
+};
+
+ResultCollector::ResultCollector (TestContext& testCtx)
+	: m_testCtx		(testCtx)
+	, m_log			(testCtx.getLog())
+	, m_result		(QP_TEST_RESULT_PASS)
+	, m_message		("Pass")
+{
+}
+
+bool ResultCollector::check (bool cond, const char* msg)
+{
+	if (!cond)
+		fail(msg);
+	return cond;
+}
+
+void ResultCollector::addResult (qpTestResult result, const char* msg)
+{
+	m_log << TestLog::Message << "// Fail: " << msg << TestLog::EndMessage;
+	if (m_result == QP_TEST_RESULT_PASS)
+	{
+		m_result = result;
+		m_message = msg;
+	}
+	else
+	{
+		if (result == QP_TEST_RESULT_FAIL)
+			m_result = result;
+		m_message = "Multiple problems, see log for details";
+	}
+}
+
+void ResultCollector::fail (const char* msg)
+{
+	addResult(QP_TEST_RESULT_FAIL, msg);
+}
+
+void ResultCollector::warn (const char* msg)
+{
+	addResult(QP_TEST_RESULT_QUALITY_WARNING, msg);
+}
+
+ResultCollector::~ResultCollector (void)
+{
+	m_testCtx.setTestResult(m_result, m_message);
+}
+
+class TestBase : public TestCase, protected CallLogWrapper
+{
+protected:
+							TestBase			(const char*	name,
+												 const char*	description,
+												 const Context&	ctx);
+
+	// Copy ContextWrapper since MI (except for CallLogWrapper) is a no-no.
+	const Context&			getContext			(void) const { return m_ctx; }
+	const RenderContext&	getRenderContext	(void) const { return m_ctx.getRenderContext(); }
+	const Functions&		gl					(void) const { return m_ctx.gl(); }
+	TestLog&				log					(void) const { return m_ctx.log(); }
+	void					init				(void);
+
+	Context					m_ctx;
+	Random					m_rnd;
+};
+
+TestBase::TestBase (const char* name, const char* description, const Context& ctx)
+	: TestCase			(ctx.getTestContext(), name, description)
+	, CallLogWrapper	(ctx.gl(), ctx.log())
+	, m_ctx				(ctx)
+	, m_rnd				(deStringHash(name))
+{
+	enableLogging(true);
+}
+
+void TestBase::init (void)
+{
+	m_rnd = Random(deStringHash(getName()));
+}
+
+class LifeTest : public TestBase
+{
+public:
+	typedef void			(LifeTest::*TestFunction)	(void);
+
+							LifeTest					(const char*	name,
+														 const char*	description,
+														 Type&			type,
+														 TestFunction 	test)
+								: TestBase		(name, description, type.getContext())
+								, m_type		(type)
+								, m_test		(test) {}
+
+	IterateResult			iterate						(void);
+
+	void					testGen						(void);
+	void					testDelete					(void);
+	void					testBind					(void);
+	void					testDeleteBound				(void);
+	void					testBindNoGen				(void);
+	void					testDeleteUsed				(void);
+
+private:
+	Binder&					binder						(void) { return *m_type.binder(); }
+
+	Type&					m_type;
+	TestFunction			m_test;
+};
+
+IterateResult LifeTest::iterate (void)
+{
+	(this->*m_test)();
+	return STOP;
+}
+
+void LifeTest::testGen (void)
+{
+	ResultCollector	errors	(getTestContext());
+	Name			name	(m_type);
+
+	if (m_type.genCreates())
+		errors.check(m_type.exists(*name), "Gen* should have created an object, but didn't");
+	else
+		errors.check(!m_type.exists(*name), "Gen* should not have created an object, but did");
+}
+
+void LifeTest::testDelete (void)
+{
+	ResultCollector	errors	(getTestContext());
+	GLuint			name	= m_type.gen();
+
+	m_type.release(name);
+	errors.check(!m_type.exists(name), "Object still exists after deletion");
+}
+
+void LifeTest::testBind (void)
+{
+	ResultCollector	errors	(getTestContext());
+	Name			name	(m_type);
+
+	binder().bind(*name);
+	GLU_EXPECT_NO_ERROR(gl().getError(), "Bind failed");
+	errors.check(m_type.exists(*name), "Object does not exist after binding");
+	binder().bind(0);
+}
+
+void LifeTest::testDeleteBound (void)
+{
+	const GLuint	id		= m_type.gen();
+	ResultCollector	errors	(getTestContext());
+
+	binder().bind(id);
+	m_type.release(id);
+
+	if (m_type.nameLingers())
+	{
+		errors.check(gl().getError() == GL_NO_ERROR, "Deleting bound object failed");
+		errors.check(binder().getBinding() == id,
+					 "Deleting bound object did not retain binding");
+		errors.check(m_type.exists(id),
+					 "Deleting bound object made its name invalid");
+		errors.check(m_type.isDeleteFlagged(id),
+					 "Deleting bound object did not flag the object for deletion");
+		binder().bind(0);
+	}
+	else
+	{
+		errors.check(gl().getError() == GL_NO_ERROR, "Deleting bound object failed");
+		errors.check(binder().getBinding() == 0,
+					 "Deleting bound object did not remove binding");
+		errors.check(!m_type.exists(id),
+					 "Deleting bound object did not make its name invalid");
+		binder().bind(0);
+	}
+
+	errors.check(binder().getBinding() == 0, "Unbinding didn't remove binding");
+	errors.check(!m_type.exists(id), "Name is still valid after deleting and unbinding");
+}
+
+void LifeTest::testBindNoGen (void)
+{
+	ResultCollector	errors	(getTestContext());
+	const GLuint	id		= m_rnd.getUint32();
+
+	if (!errors.check(!m_type.exists(id), "Randomly chosen identifier already exists"))
+		return;
+
+	Name			name	(m_type, id);
+	binder().bind(*name);
+
+	if (binder().genRequired())
+	{
+		errors.check(glGetError() == GL_INVALID_OPERATION,
+					 "Did not fail when binding a name not generated by Gen* call");
+		errors.check(!m_type.exists(*name),
+					 "Bind* created an object for a name not generated by a Gen* call");
+	}
+	else
+	{
+		errors.check(glGetError() == GL_NO_ERROR,
+					 "Failed when binding a name not generated by Gen* call");
+		errors.check(m_type.exists(*name),
+					 "Object was not created by the Bind* call");
+	}
+}
+
+void LifeTest::testDeleteUsed (void)
+{
+	ResultCollector	errors(getTestContext());
+	GLuint			programId = 0;
+
+	{
+		CheckedShader	vtxShader	(getRenderContext(),
+									 SHADERTYPE_VERTEX, s_vertexShaderSrc);
+		CheckedShader	fragShader	(getRenderContext(),
+									 SHADERTYPE_FRAGMENT, s_fragmentShaderSrc);
+		CheckedProgram	program		(getRenderContext(),
+									 vtxShader.getShader(), fragShader.getShader());
+
+		programId = program.getProgram();
+
+		log() << TestLog::Message << "// Created and linked program " << programId
+			  << TestLog::EndMessage;
+		GLU_CHECK_CALL_ERROR(glUseProgram(programId), gl().getError());
+
+		log() << TestLog::Message << "// Deleted program " << programId
+			  << TestLog::EndMessage;
+	}
+	TCU_CHECK(glIsProgram(programId));
+	{
+		GLint deleteFlagged = 0;
+		glGetProgramiv(programId, GL_DELETE_STATUS, &deleteFlagged);
+		errors.check(deleteFlagged != 0, "Program object was not flagged as deleted");
+	}
+	GLU_CHECK_CALL_ERROR(glUseProgram(0), gl().getError());
+	errors.check(!gl().isProgram(programId),
+				 "Deleted program name still valid after being made non-current");
+}
+
+class AttachmentTest : public TestBase
+{
+public:
+	typedef void			(AttachmentTest::*TestFunction)	(void);
+							AttachmentTest					(const char*	name,
+															 const char*	description,
+															 Attacher&		attacher,
+															 TestFunction	test)
+								: TestBase		(name, description, attacher.getContext())
+								, m_attacher	(attacher)
+								, m_test		(test) {}
+	IterateResult			iterate							(void);
+
+	void					testDeletedNames				(void);
+	void					testDeletedBinding				(void);
+	void					testDeletedReattach				(void);
+
+private:
+	Attacher&				m_attacher;
+	const TestFunction		m_test;
+};
+
+IterateResult AttachmentTest::iterate (void)
+{
+	(this->*m_test)();
+	return STOP;
+}
+
+GLuint getAttachment (Attacher& attacher, GLuint container)
+{
+	const GLuint queriedAttachment = attacher.getAttachment(container);
+	attacher.log() << TestLog::Message
+				   << "// Result of query for " << attacher.getElementType().getName()
+				   << " attached to " << attacher.getContainerType().getName() << " "
+				   << container << ": " << queriedAttachment << "."
+				   << TestLog::EndMessage;
+	return queriedAttachment;
+}
+
+void AttachmentTest::testDeletedNames (void)
+{
+	Type&			elemType		= m_attacher.getElementType();
+	Type&			containerType	= m_attacher.getContainerType();
+	Name			container		(containerType);
+	ResultCollector	errors			(getTestContext());
+	GLuint			elementId		= 0;
+
+	{
+		Name element(elemType);
+		elementId = *element;
+		m_attacher.initAttachment(0, *element);
+		m_attacher.attach(*element, *container);
+		errors.check(getAttachment(m_attacher, *container) == elementId,
+					 "Attachment name not returned by query even before deletion.");
+	}
+
+	// "Such a container or other context may continue using the object, and
+	// may still contain state identifying its name as being currently bound"
+	//
+	// We here interpret "may" to mean that whenever the container has a
+	// deleted object attached to it, a query will return that object's former
+	// name.
+	errors.check(getAttachment(m_attacher, *container) == elementId,
+				 "Attachment name not returned by query after attachment was deleted.");
+
+	if (elemType.nameLingers())
+		errors.check(elemType.exists(elementId),
+					 "Attached object name no longer valid after deletion.");
+	else
+		errors.check(!elemType.exists(elementId),
+					 "Attached object name still valid after deletion.");
+
+	m_attacher.detach(elementId, *container);
+	errors.check(getAttachment(m_attacher, *container) == 0,
+				 "Attachment name returned by query even after detachment.");
+	errors.check(!elemType.exists(elementId),
+				 "Deleted attached object name still usable after detachment.");
+};
+
+class InputAttachmentTest : public TestBase
+{
+public:
+					InputAttachmentTest	(const char*	name,
+										 const char*	description,
+										 InputAttacher&	inputAttacher)
+						: TestBase			(name, description, inputAttacher.getContext())
+						, m_inputAttacher	(inputAttacher) {}
+
+	IterateResult	iterate				(void);
+
+private:
+	InputAttacher&	m_inputAttacher;
+};
+
+GLuint replaceName (Type& type, GLuint oldName, TestLog& log)
+{
+	const Binder* const	binder		= type.binder();
+	const bool			genRequired	= binder == DE_NULL || binder->genRequired();
+
+	if (genRequired)
+		return type.gen();
+
+	log << TestLog::Message
+		<< "// Type does not require Gen* for binding, reusing old id " << oldName << "."
+		<< TestLog::EndMessage;
+
+	return oldName;
+}
+
+IterateResult InputAttachmentTest::iterate (void)
+{
+	Attacher&		attacher		= m_inputAttacher.getAttacher();
+	Type&			containerType	= attacher.getContainerType();
+	Type&			elementType		= attacher.getElementType();
+	Name			container		(containerType);
+	GLuint			elementId		= 0;
+	const GLuint	refSeed			= m_rnd.getUint32();
+	const GLuint	newSeed			= m_rnd.getUint32();
+	ResultCollector	errors			(getTestContext());
+
+	Surface			refSurface;		// Surface from drawing with refSeed-seeded attachment
+	Surface			delSurface;		// Surface from drawing with deleted refSeed attachment
+	Surface			newSurface;		// Surface from drawing with newSeed-seeded attachment
+
+	log() << TestLog::Message
+		  << "Testing if writing to a newly created object modifies a deleted attachment"
+		  << TestLog::EndMessage;
+
+	{
+		ScopedLogSection	section	(log(),
+									 "Write to original", "Writing to an original attachment");
+		const Name			element	(elementType);
+
+		elementId = *element;
+		attacher.initAttachment(refSeed, elementId);
+		attacher.attach(elementId, *container);
+		m_inputAttacher.drawContainer(*container, refSurface);
+		// element gets deleted here
+		log() << TestLog::Message << "// Deleting attachment";
+	}
+	{
+		ScopedLogSection section	(log(), "Write to new",
+									 "Writing to a new attachment after deleting the original");
+		const GLuint	newId		= replaceName(elementType, elementId, log());
+		const Name		newElement	(elementType, newId);
+
+		attacher.initAttachment(newSeed, newId);
+
+		m_inputAttacher.drawContainer(*container, delSurface);
+		attacher.detach(elementId, *container);
+
+		attacher.attach(newId, *container);
+		m_inputAttacher.drawContainer(*container, newSurface);
+		attacher.detach(newId, *container);
+	}
+	{
+		const bool surfacesMatch = tcu::pixelThresholdCompare(
+			log(), "Reading from deleted",
+			"Comparison result from reading from a container with a deleted attachment "
+			"before and after writing to a fresh object.",
+			refSurface, delSurface, RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);
+
+		errors.check(
+			surfacesMatch,
+			"Writing to a fresh object modified the container with a deleted attachment.");
+
+		if (!surfacesMatch)
+			log() << TestLog::Image("New attachment",
+									"Container state after attached to the fresh object",
+									newSurface);
+	}
+
+	return STOP;
+}
+
+class OutputAttachmentTest : public TestBase
+{
+public:
+						OutputAttachmentTest			(const char*		name,
+														 const char*		description,
+														 OutputAttacher&	outputAttacher)
+							: TestBase			(name, description,
+												 outputAttacher.getContext())
+							, m_outputAttacher	(outputAttacher) {}
+	IterateResult		iterate							(void);
+
+private:
+	OutputAttacher&		m_outputAttacher;
+};
+
+IterateResult OutputAttachmentTest::iterate (void)
+{
+	Attacher&		attacher		= m_outputAttacher.getAttacher();
+	Type&			containerType	= attacher.getContainerType();
+	Type&			elementType		= attacher.getElementType();
+	Name			container		(containerType);
+	GLuint			elementId		= 0;
+	const GLuint	refSeed			= m_rnd.getUint32();
+	const GLuint	newSeed			= m_rnd.getUint32();
+	ResultCollector	errors			(getTestContext());
+	Surface			refSurface;		// Surface drawn from attachment to refSeed container
+	Surface			newSurface;		// Surface drawn from attachment to newSeed container
+	Surface			delSurface;		// Like newSurface, after writing to a deleted attachment
+
+	log() << TestLog::Message
+		  << "Testing if writing to a container with a deleted attachment "
+		  << "modifies a newly created object"
+		  << TestLog::EndMessage;
+
+	{
+		ScopedLogSection	section	(log(), "Write to existing",
+									 "Writing to a container with an existing attachment");
+		const Name			element	(elementType);
+
+		elementId = *element;
+		attacher.initAttachment(0, elementId);
+		attacher.attach(elementId, *container);
+
+		// For reference purposes, make note of what refSeed looks like.
+		m_outputAttacher.setupContainer(refSeed, *container);
+		m_outputAttacher.drawAttachment(elementId, refSurface);
+	}
+	{
+		ScopedLogSection	section		(log(), "Write to deleted",
+										 "Writing to a container after deletion of attachment");
+		const GLuint		newId		= replaceName(elementType, elementId, log());
+		const Name			newElement	(elementType, newId);
+
+		log() << TestLog::Message
+			  << "Creating a new object " << newId
+			  << TestLog::EndMessage;
+
+		log() << TestLog::Message
+			  << "Recording state of new object before writing to container"
+			  << TestLog::EndMessage;
+		attacher.initAttachment(newSeed, newId);
+		m_outputAttacher.drawAttachment(newId, newSurface);
+
+		log() << TestLog::Message
+			  << "Writing to container"
+			  << TestLog::EndMessage;
+
+		// Now re-write refSeed to the container.
+		m_outputAttacher.setupContainer(refSeed, *container);
+		// Does it affect the newly created attachment object?
+		m_outputAttacher.drawAttachment(newId, delSurface);
+	}
+	attacher.detach(elementId, *container);
+
+	const bool surfacesMatch = tcu::pixelThresholdCompare(
+		log(), "Writing to deleted",
+		"Comparison result from reading from a fresh object before and after "
+		"writing to a container with a deleted attachment",
+		newSurface, delSurface, RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);
+
+	errors.check(surfacesMatch,
+				 "Writing to container with deleted attachment modified a new object.");
+
+	if (!surfacesMatch)
+		log() << TestLog::Image(
+			"Original attachment",
+			"Result of container modification on original attachment before deletion.",
+			refSurface);
+	return STOP;
+};
+
+struct LifeTestSpec
+{
+	const char*				name;
+	LifeTest::TestFunction	func;
+	bool					needBind;
+};
+
+MovePtr<TestCaseGroup> createLifeTestGroup (TestContext& testCtx,
+											const LifeTestSpec& spec,
+											const vector<Type*>& types)
+{
+	MovePtr<TestCaseGroup> group(new TestCaseGroup(testCtx, spec.name, spec.name));
+
+	for (vector<Type*>::const_iterator it = types.begin(); it != types.end(); ++it)
+	{
+		Type& type = **it;
+		const char* name = type.getName();
+		if (!spec.needBind || type.binder() != DE_NULL)
+			group->addChild(new LifeTest(name, name, type, spec.func));
+	}
+
+	return group;
+}
+
+static const LifeTestSpec s_lifeTests[] =
+{
+	{ "gen",			&LifeTest::testGen,			false	},
+	{ "delete",			&LifeTest::testDelete,		false	},
+	{ "bind",			&LifeTest::testBind,		true	},
+	{ "delete_bound",	&LifeTest::testDeleteBound,	true	},
+	{ "bind_no_gen",	&LifeTest::testBindNoGen,	true	},
+};
+
+string attacherName (Attacher& attacher)
+{
+	ostringstream os;
+	os << attacher.getElementType().getName() << "_" <<  attacher.getContainerType().getName();
+	return os.str();
+}
+
+void addTestCases (TestCaseGroup& group, Types& types)
+{
+	TestContext& testCtx = types.getTestContext();
+
+	for (const LifeTestSpec* it = DE_ARRAY_BEGIN(s_lifeTests);
+		 it != DE_ARRAY_END(s_lifeTests); ++it)
+		group.addChild(createLifeTestGroup(testCtx, *it, types.getTypes()).release());
+
+	{
+		TestCaseGroup* const delUsedGroup =
+			new TestCaseGroup(testCtx, "delete_used", "Delete current program");
+		group.addChild(delUsedGroup);
+
+		delUsedGroup->addChild(
+			new LifeTest("program", "program", types.getProgramType(),
+						 &LifeTest::testDeleteUsed));
+	}
+
+	{
+		TestCaseGroup* const	attGroup	= new TestCaseGroup(
+			testCtx, "attach", "Attachment tests");
+		group.addChild(attGroup);
+
+		{
+			TestCaseGroup* const	nameGroup	= new TestCaseGroup(
+				testCtx, "deleted_name", "Name of deleted attachment");
+			attGroup->addChild(nameGroup);
+
+			const vector<Attacher*>& atts = types.getAttachers();
+			for (vector<Attacher*>::const_iterator it = atts.begin(); it != atts.end(); ++it)
+			{
+				const string name = attacherName(**it);
+				nameGroup->addChild(new AttachmentTest(name.c_str(), name.c_str(), **it,
+													   &AttachmentTest::testDeletedNames));
+			}
+		}
+		{
+			TestCaseGroup* inputGroup = new TestCaseGroup(
+				testCtx, "deleted_input", "Input from deleted attachment");
+			attGroup->addChild(inputGroup);
+
+			const vector<InputAttacher*>& inAtts = types.getInputAttachers();
+			for (vector<InputAttacher*>::const_iterator it = inAtts.begin();
+				 it != inAtts.end(); ++it)
+			{
+				const string name = attacherName((*it)->getAttacher());
+				inputGroup->addChild(new InputAttachmentTest(name.c_str(), name.c_str(), **it));
+			}
+		}
+		{
+			TestCaseGroup* outputGroup = new TestCaseGroup(
+				testCtx, "deleted_output", "Output to deleted attachment");
+			attGroup->addChild(outputGroup);
+
+			const vector<OutputAttacher*>& outAtts = types.getOutputAttachers();
+			for (vector<OutputAttacher*>::const_iterator it = outAtts.begin();
+				 it != outAtts.end(); ++it)
+			{
+				string name = attacherName((*it)->getAttacher());
+				outputGroup->addChild(new OutputAttachmentTest(name.c_str(), name.c_str(),
+															   **it));
+			}
+		}
+	}
+}
+
+} // details
+} // LifetimeTests
+} // gls
+} // deqp
diff --git a/modules/glshared/glsLifetimeTests.hpp b/modules/glshared/glsLifetimeTests.hpp
new file mode 100644
index 0000000..32a9734
--- /dev/null
+++ b/modules/glshared/glsLifetimeTests.hpp
@@ -0,0 +1,432 @@
+#ifndef _GLSLIFETIMETESTS_HPP
+#define _GLSLIFETIMETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Common object lifetime tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "deRandom.hpp"
+#include "deUniquePtr.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuTestContext.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "gluRenderContext.hpp"
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gls
+{
+namespace LifetimeTests
+{
+namespace details
+{
+
+using std::vector;
+using de::MovePtr;
+using de::Random;
+using tcu::Surface;
+using tcu::TestCaseGroup;
+using tcu::TestContext;
+using tcu::TestLog;
+using glu::CallLogWrapper;
+using glu::RenderContext;
+using namespace glw;
+
+typedef void		(CallLogWrapper::*BindFunc)		(GLenum target, GLuint name);
+typedef void		(CallLogWrapper::*GenFunc)		(GLsizei n, GLuint* names);
+typedef void		(CallLogWrapper::*DeleteFunc)	(GLsizei n, const GLuint* names);
+typedef GLboolean	(CallLogWrapper::*ExistsFunc)	(GLuint name);
+
+class Context
+{
+public:
+							Context				(const RenderContext& renderCtx,
+												 TestContext& testCtx)
+								: m_renderCtx	(renderCtx)
+								, m_testCtx		(testCtx) {}
+	const RenderContext&	getRenderContext	(void) const { return m_renderCtx; }
+	TestContext&			getTestContext		(void) const { return m_testCtx; }
+	const Functions&		gl					(void) const { return m_renderCtx.getFunctions(); }
+	TestLog&				log					(void) const { return m_testCtx.getLog(); }
+
+private:
+	const RenderContext&	m_renderCtx;
+	TestContext&			m_testCtx;
+};
+
+class ContextWrapper : public CallLogWrapper
+{
+public:
+	const Context&			getContext			(void) const { return m_ctx; }
+	const RenderContext&	getRenderContext	(void) const { return m_ctx.getRenderContext(); }
+	TestContext&			getTestContext		(void) const { return m_ctx.getTestContext(); }
+	const Functions&		gl					(void) const { return m_ctx.gl(); }
+	TestLog&				log					(void) const { return m_ctx.log(); }
+	void					enableLogging		(bool enable)
+	{
+		CallLogWrapper::enableLogging(enable);
+	}
+
+protected:
+							ContextWrapper				(const Context& ctx);
+	const Context			m_ctx;
+};
+
+class Binder : public ContextWrapper
+{
+public:
+	virtual				~Binder			(void) {}
+	virtual void		bind			(GLuint name) = 0;
+	virtual GLuint		getBinding		(void) = 0;
+	virtual bool		genRequired		(void) const { return true; }
+
+protected:
+						Binder			(const Context& ctx) : ContextWrapper(ctx) {}
+};
+
+class SimpleBinder : public Binder
+{
+public:
+						SimpleBinder	(const Context& ctx,
+										 BindFunc bindFunc,
+										 GLenum bindTarget,
+										 GLenum bindingParam,
+										 bool genRequired_ = false)
+							: Binder			(ctx)
+							, m_bindFunc		(bindFunc)
+							, m_bindTarget		(bindTarget)
+							, m_bindingParam	(bindingParam)
+							, m_genRequired		(genRequired_) {}
+
+	void				bind			(GLuint name);
+	GLuint				getBinding		(void);
+	bool				genRequired		(void) const { return m_genRequired; }
+
+private:
+	const BindFunc		m_bindFunc;
+	const GLenum		m_bindTarget;
+	const GLenum		m_bindingParam;
+	const bool			m_genRequired;
+};
+
+class Type : public ContextWrapper
+{
+public:
+	virtual					~Type			(void) {}
+	virtual GLuint			gen				(void) = 0;
+	virtual void			release			(GLuint name) = 0;
+	virtual bool			exists			(GLuint name) = 0;
+	virtual bool			isDeleteFlagged	(GLuint name) { DE_UNREF(name); return false; }
+	virtual Binder*			binder			(void) const { return DE_NULL; }
+	virtual const char*		getName			(void) const = 0;
+	virtual bool			nameLingers		(void) const { return false; }
+	virtual bool			genCreates		(void) const { return false; }
+
+protected:
+							Type			(const Context& ctx) : ContextWrapper(ctx) {}
+};
+
+class SimpleType : public Type
+{
+public:
+				SimpleType	(const Context& ctx, const char* name,
+							 GenFunc genFunc, DeleteFunc deleteFunc, ExistsFunc existsFunc,
+							 Binder* binder_ = DE_NULL, bool genCreates_ = false)
+						: Type			(ctx)
+						, m_getName		(name)
+						, m_genFunc		(genFunc)
+						, m_deleteFunc	(deleteFunc)
+						, m_existsFunc	(existsFunc)
+						, m_binder		(binder_)
+						, m_genCreates	(genCreates_) {}
+
+	GLuint			gen 		(void);
+	void			release		(GLuint name)		{ (this->*m_deleteFunc)(1, &name); }
+	bool			exists		(GLuint name)		{ return (this->*m_existsFunc)(name) != GL_FALSE; }
+	Binder*			binder		(void) const		{ return m_binder; }
+	const char*		getName		(void) const		{ return m_getName; }
+	bool			nameLingers	(void) const		{ return false; }
+	bool			genCreates	(void) const		{ return m_genCreates; }
+
+private:
+	const char* const	m_getName;
+	const GenFunc		m_genFunc;
+	const DeleteFunc	m_deleteFunc;
+	const ExistsFunc	m_existsFunc;
+	Binder* const		m_binder;
+	const bool			m_genCreates;
+};
+
+class ProgramType : public Type
+{
+public:
+					ProgramType		(const Context& ctx) : Type(ctx) {}
+	bool			nameLingers		(void) const	{ return true; }
+	bool			genCreates		(void) const	{ return true; }
+	const char*		getName			(void) const	{ return "program"; }
+	GLuint			gen				(void)			{ return glCreateProgram(); }
+	void			release			(GLuint name)	{ glDeleteProgram(name); }
+	bool			exists			(GLuint name)	{ return glIsProgram(name) != GL_FALSE; }
+	bool			isDeleteFlagged	(GLuint name);
+};
+
+class ShaderType : public Type
+{
+public:
+					ShaderType		(const Context& ctx) : Type(ctx) {}
+	bool			nameLingers		(void) const { return true; }
+	bool			genCreates		(void) const { return true; }
+	const char*		getName			(void) const { return "shader"; }
+	GLuint			gen				(void) { return glCreateShader(GL_FRAGMENT_SHADER); }
+	void			release			(GLuint name) { glDeleteShader(name); }
+	bool			exists			(GLuint name) { return glIsShader(name) != GL_FALSE; }
+	bool			isDeleteFlagged	(GLuint name);
+};
+
+class Attacher : public ContextWrapper
+{
+public:
+	virtual void		initAttachment			(GLuint seed, GLuint attachment) = 0;
+	virtual void		attach					(GLuint element, GLuint container) = 0;
+	virtual void		detach					(GLuint element, GLuint container) = 0;
+	virtual GLuint		getAttachment			(GLuint container) = 0;
+	virtual bool		canAttachDeleted		(void) const { return true; }
+
+	Type&				getElementType			(void) const { return m_elementType; }
+	Type&				getContainerType		(void) const { return m_containerType; }
+	virtual				~Attacher				(void) {}
+
+protected:
+						Attacher				(const Context& ctx,
+												 Type& elementType, Type& containerType)
+							: ContextWrapper	(ctx)
+							, m_elementType		(elementType)
+							, m_containerType	(containerType) {}
+
+private:
+	Type&				m_elementType;
+	Type&				m_containerType;
+};
+
+class InputAttacher : public ContextWrapper
+{
+public:
+	Attacher&			getAttacher				(void) const { return m_attacher; }
+	virtual void		drawContainer			(GLuint container, Surface& dst) = 0;
+protected:
+						InputAttacher			(Attacher& attacher)
+							: ContextWrapper	(attacher.getContext())
+							, m_attacher		(attacher) {}
+	Attacher&			m_attacher;
+};
+
+class OutputAttacher : public ContextWrapper
+{
+public:
+	Attacher&			getAttacher				(void) const { return m_attacher; }
+	virtual void		setupContainer			(GLuint seed, GLuint container) = 0;
+	virtual void		drawAttachment			(GLuint attachment, Surface& dst) = 0;
+protected:
+						OutputAttacher			(Attacher& attacher)
+							: ContextWrapper	(attacher.getContext())
+							, m_attacher		(attacher) {}
+	Attacher&			m_attacher;
+};
+
+class Types : public ContextWrapper
+{
+public:
+									Types				(const Context& ctx)
+										: ContextWrapper(ctx) {}
+	virtual Type&					getProgramType		(void) = 0;
+	const vector<Type*>&			getTypes			(void) { return m_types; }
+	const vector<Attacher*>&		getAttachers		(void) { return m_attachers; }
+	const vector<InputAttacher*>&	getInputAttachers	(void) { return m_inAttachers; }
+	const vector<OutputAttacher*>&	getOutputAttachers	(void) { return m_outAttachers; }
+	virtual							~Types				(void) {}
+
+protected:
+	vector<Type*>					m_types;
+	vector<Attacher*>				m_attachers;
+	vector<InputAttacher*>			m_inAttachers;
+	vector<OutputAttacher*>			m_outAttachers;
+};
+
+class FboAttacher : public Attacher
+{
+public:
+	void			initAttachment		(GLuint seed, GLuint element);
+
+protected:
+					FboAttacher			(const Context& ctx,
+										 Type& elementType, Type& containerType)
+						: Attacher 		(ctx, elementType, containerType) {}
+	virtual void	initStorage			(void) = 0;
+};
+
+class FboInputAttacher : public InputAttacher
+{
+public:
+			FboInputAttacher		(FboAttacher& attacher)
+				: InputAttacher 	(attacher) {}
+	void	drawContainer			(GLuint container, Surface& dst);
+};
+
+class FboOutputAttacher : public OutputAttacher
+{
+public:
+			FboOutputAttacher			(FboAttacher& attacher)
+				: OutputAttacher 		(attacher) {}
+	void	setupContainer				(GLuint seed, GLuint container);
+	void	drawAttachment				(GLuint attachment, Surface& dst);
+};
+
+class TextureFboAttacher : public FboAttacher
+{
+public:
+			TextureFboAttacher	(const Context& ctx, Type& elementType, Type& containerType)
+				: FboAttacher	(ctx, elementType, containerType) {}
+
+	void	initStorage			(void);
+	void	attach				(GLuint element, GLuint container);
+	void	detach				(GLuint element, GLuint container);
+	GLuint	getAttachment		(GLuint container);
+};
+
+class RboFboAttacher : public FboAttacher
+{
+public:
+			RboFboAttacher		(const Context& ctx, Type& elementType, Type& containerType)
+				: FboAttacher	(ctx, elementType, containerType) {}
+
+	void	initStorage			(void);
+	void	attach				(GLuint element, GLuint container);
+	void	detach				(GLuint element, GLuint container);
+	GLuint	getAttachment		(GLuint container);
+};
+
+class ShaderProgramAttacher : public Attacher
+{
+public:
+			ShaderProgramAttacher	(const Context& ctx,
+									 Type& elementType, Type& containerType)
+				: Attacher			(ctx, elementType, containerType) {}
+
+	void	initAttachment		(GLuint seed, GLuint element);
+	void	attach				(GLuint element, GLuint container);
+	void	detach				(GLuint element, GLuint container);
+	GLuint	getAttachment		(GLuint container);
+};
+
+class ShaderProgramInputAttacher : public InputAttacher
+{
+public:
+			ShaderProgramInputAttacher	(Attacher& attacher)
+				: InputAttacher			(attacher) {}
+
+	void	drawContainer				(GLuint container, Surface& dst);
+};
+
+class ES2Types : public Types
+{
+public:
+								ES2Types		(const Context& ctx);
+	Type&						getProgramType	(void) { return m_programType; }
+
+protected:
+	SimpleBinder				m_bufferBind;
+	SimpleType					m_bufferType;
+	SimpleBinder				m_textureBind;
+	SimpleType					m_textureType;
+	SimpleBinder				m_rboBind;
+	SimpleType					m_rboType;
+	SimpleBinder				m_fboBind;
+	SimpleType					m_fboType;
+	ShaderType					m_shaderType;
+	ProgramType					m_programType;
+	TextureFboAttacher			m_texFboAtt;
+	FboInputAttacher			m_texFboInAtt;
+	FboOutputAttacher			m_texFboOutAtt;
+	RboFboAttacher				m_rboFboAtt;
+	FboInputAttacher			m_rboFboInAtt;
+	FboOutputAttacher			m_rboFboOutAtt;
+	ShaderProgramAttacher		m_shaderAtt;
+	ShaderProgramInputAttacher	m_shaderInAtt;
+};
+
+MovePtr<TestCaseGroup>	createGroup		(TestContext& testCtx, Type& type);
+void					addTestCases	(TestCaseGroup& group, Types& types);
+
+struct Rectangle
+{
+			Rectangle	(GLint x_, GLint y_, GLint width_, GLint height_)
+				: x			(x_)
+				, y			(y_)
+				, width		(width_)
+				, height	(height_) {}
+	GLint	x;
+	GLint	y;
+	GLint	width;
+	GLint	height;
+};
+
+Rectangle	randomViewport	(const RenderContext& ctx, GLint maxWidth, GLint maxHeight,
+							 Random& rnd);
+void		setViewport		(const RenderContext& renderCtx, const Rectangle& rect);
+void		readRectangle	(const RenderContext& renderCtx, const Rectangle& rect,
+							 Surface& dst);
+
+} // details
+
+using details::BindFunc;
+using details::GenFunc;
+using details::DeleteFunc;
+using details::ExistsFunc;
+
+using details::Context;
+using details::Binder;
+using details::SimpleBinder;
+using details::Type;
+using details::SimpleType;
+using details::Attacher;
+using details::InputAttacher;
+using details::OutputAttacher;
+using details::Types;
+using details::ES2Types;
+
+using details::createGroup;
+using details::addTestCases;
+
+using details::Rectangle;
+using details::randomViewport;
+using details::setViewport;
+using details::readRectangle;
+
+} // LifetimeTests
+} // gls
+} // deqp
+
+#endif // _GLSLIFETIMETESTS_HPP
diff --git a/modules/glshared/glsLongStressCase.cpp b/modules/glshared/glsLongStressCase.cpp
new file mode 100644
index 0000000..7f751ce
--- /dev/null
+++ b/modules/glshared/glsLongStressCase.cpp
@@ -0,0 +1,1586 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Parametrized, long-running stress case.
+ *
+ * \todo [2013-06-27 nuutti] Do certain things in a cleaner and less
+ *							 confusing way, such as the "redundant buffer
+ *							 factor" thing in LongStressCase.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsLongStressCase.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuCommandLine.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuVectorUtil.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+#include "tcuStringTemplate.hpp"
+#include "gluStrUtil.hpp"
+#include "gluShaderProgram.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deString.h"
+#include "deSharedPtr.hpp"
+#include "deClock.h"
+
+#include "glw.h"
+
+#include <limits>
+#include <vector>
+#include <iomanip>
+#include <map>
+#include <iomanip>
+
+using tcu::TestLog;
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::IVec2;
+using tcu::IVec3;
+using tcu::IVec4;
+using tcu::TextureLevel;
+using tcu::TextureFormat;
+using tcu::ConstPixelBufferAccess;
+using tcu::CubeFace;
+using de::SharedPtr;
+using de::Random;
+using de::toString;
+
+using std::vector;
+using std::string;
+using std::map;
+
+namespace deqp
+{
+namespace gls
+{
+
+using TextureTestUtil::TextureType;
+using TextureTestUtil::TEXTURETYPE_2D;
+using TextureTestUtil::TEXTURETYPE_CUBE;
+
+static const float Mi = (float)(1<<20);
+
+static const deUint32 bufferUsages[] =
+{
+	GL_STATIC_DRAW,
+	GL_STREAM_DRAW,
+	GL_DYNAMIC_DRAW,
+
+	GL_STATIC_READ,
+	GL_STREAM_READ,
+	GL_DYNAMIC_READ,
+
+	GL_STATIC_COPY,
+	GL_STREAM_COPY,
+	GL_DYNAMIC_COPY
+};
+
+static const deUint32 bufferUsagesGLES2[] =
+{
+	GL_STATIC_DRAW,
+	GL_DYNAMIC_DRAW,
+	GL_STREAM_DRAW
+};
+
+static const deUint32 bufferTargets[] =
+{
+	GL_ARRAY_BUFFER,
+	GL_ELEMENT_ARRAY_BUFFER,
+
+	GL_COPY_READ_BUFFER,
+	GL_COPY_WRITE_BUFFER,
+	GL_PIXEL_PACK_BUFFER,
+	GL_PIXEL_UNPACK_BUFFER,
+	GL_TRANSFORM_FEEDBACK_BUFFER,
+	GL_UNIFORM_BUFFER
+};
+
+static const deUint32 bufferTargetsGLES2[] =
+{
+	GL_ARRAY_BUFFER,
+	GL_ELEMENT_ARRAY_BUFFER
+};
+
+static inline int computePixelStore (const TextureFormat& format)
+{
+	const int pixelSize = format.getPixelSize();
+	if (deIsPowerOfTwo32(pixelSize))
+		return de::min(pixelSize, 8);
+	else
+		return 1;
+}
+
+static inline int getNumIterations (const tcu::TestContext& testCtx, const int defaultNumIterations)
+{
+	const int cmdLineVal = testCtx.getCommandLine().getTestIterationCount();
+	return cmdLineVal == 0 ? defaultNumIterations : cmdLineVal;
+}
+
+static inline float triangleArea (const Vec2& a, const Vec2& b, const Vec2& c)
+{
+	const Vec2 ab = b-a;
+	const Vec2 ac = c-a;
+	return 0.5f * tcu::length(ab.x()*ac.y() - ab.y()*ac.x());
+}
+
+static inline string mangleShaderNames (const string& source, const string& manglingSuffix)
+{
+	map<string, string> m;
+	m["NS"] = manglingSuffix;
+	return tcu::StringTemplate(source.c_str()).specialize(m);
+}
+
+template <typename T, int N>
+static inline T randomChoose (Random& rnd, const T (&arr)[N])
+{
+	return rnd.choose<T>(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr));
+}
+
+static inline int nextDivisible (const int x, const int div)
+{
+	DE_ASSERT(x >= 0);
+	DE_ASSERT(div >= 1);
+	return x == 0 ? 0 : x-1 + div - (x-1) % div;
+}
+
+static inline string getTimeStr (const deUint64 seconds)
+{
+	const deUint64		m = seconds / 60;
+	const deUint64		h = m / 60;
+	const deUint64		d = h / 24;
+	std::ostringstream	res;
+
+	res << d << "d " << h%24 << "h " << m%60 << "m " << seconds%60 << "s";
+	return res.str();
+}
+
+static inline string probabilityStr (const float prob)
+{
+	return prob == 0.0f ? "never"	:
+		   prob == 1.0f ? "ALWAYS"	:
+		   de::floatToString(prob*100.0f, 0) + "%";
+}
+
+static inline deUint32 randomBufferTarget (Random& rnd, const bool isGLES3)
+{
+	return isGLES3 ? randomChoose(rnd, bufferTargets) : randomChoose(rnd, bufferTargetsGLES2);
+}
+
+static inline deUint32 randomBufferUsage (Random& rnd, const bool isGLES3)
+{
+	return isGLES3 ? randomChoose(rnd, bufferUsages) : randomChoose(rnd, bufferUsagesGLES2);
+}
+
+static inline deUint32 cubeFaceToGLFace (tcu::CubeFace face)
+{
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
+		case tcu::CUBEFACE_POSITIVE_X: return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
+		case tcu::CUBEFACE_NEGATIVE_Y: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
+		case tcu::CUBEFACE_POSITIVE_Y: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
+		case tcu::CUBEFACE_NEGATIVE_Z: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
+		case tcu::CUBEFACE_POSITIVE_Z: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
+		default:
+			DE_ASSERT(false);
+			return GL_NONE;
+	}
+}
+
+#if defined(DE_DEBUG)
+static inline bool isMatchingGLInternalFormat (const deUint32 internalFormat, const TextureFormat& texFormat)
+{
+	switch (internalFormat)
+	{
+		// Unsized formats.
+
+		case GL_RGBA:				return texFormat.order == TextureFormat::RGBA &&
+											   (texFormat.type == TextureFormat::UNORM_INT8			||
+												texFormat.type == TextureFormat::UNORM_SHORT_4444	||
+												texFormat.type == TextureFormat::UNORM_SHORT_5551);
+
+		case GL_RGB:				return texFormat.order == TextureFormat::RGB &&
+											   (texFormat.type == TextureFormat::UNORM_INT8			||
+												texFormat.type == TextureFormat::UNORM_SHORT_565);
+
+		case GL_LUMINANCE_ALPHA:	return texFormat.order == TextureFormat::LA && texFormat.type == TextureFormat::UNORM_INT8;
+		case GL_LUMINANCE:			return texFormat.order == TextureFormat::L && texFormat.type == TextureFormat::UNORM_INT8;
+		case GL_ALPHA:				return texFormat.order == TextureFormat::A && texFormat.type == TextureFormat::UNORM_INT8;
+
+		// Sized formats.
+
+		default:					return glu::mapGLInternalFormat(internalFormat) == texFormat;
+	}
+}
+#endif // DE_DEBUG
+
+static inline bool compileShader (const deUint32 shaderGL)
+{
+	glCompileShader(shaderGL);
+
+	int success = GL_FALSE;
+	glGetShaderiv(shaderGL, GL_COMPILE_STATUS, &success);
+
+	return success == GL_TRUE;
+}
+
+static inline bool linkProgram (const deUint32 programGL)
+{
+	glLinkProgram(programGL);
+
+	int success = GL_FALSE;
+	glGetProgramiv(programGL, GL_LINK_STATUS, &success);
+
+	return success == GL_TRUE;
+}
+
+static inline string getShaderInfoLog (const deUint32 shaderGL)
+{
+	int				infoLogLen = 0;
+	vector<char>	infoLog;
+	glGetShaderiv(shaderGL, GL_INFO_LOG_LENGTH, &infoLogLen);
+	infoLog.resize(infoLogLen+1);
+	glGetShaderInfoLog(shaderGL, (int)infoLog.size(), DE_NULL, &infoLog[0]);
+	return &infoLog[0];
+}
+
+static inline string getProgramInfoLog (const deUint32 programGL)
+{
+	int				infoLogLen = 0;
+	vector<char>	infoLog;
+	glGetProgramiv(programGL, GL_INFO_LOG_LENGTH, &infoLogLen);
+	infoLog.resize(infoLogLen+1);
+	glGetProgramInfoLog(programGL, (int)infoLog.size(), DE_NULL, &infoLog[0]);
+	return &infoLog[0];
+}
+
+namespace LongStressCaseInternal
+{
+
+// A hacky-ish class for drawing text on screen as GL quads.
+class DebugInfoRenderer
+{
+public:
+								DebugInfoRenderer		(const glu::RenderContext& ctx);
+								~DebugInfoRenderer		(void) { delete m_prog; }
+
+	void						drawInfo				(deUint64 secondsElapsed, int texMem, int maxTexMem, int bufMem, int maxBufMem, int iterNdx);
+
+private:
+								DebugInfoRenderer		(const DebugInfoRenderer&);
+	DebugInfoRenderer&			operator=				(const DebugInfoRenderer&);
+
+	void						render					(void);
+	void						addTextToBuffer			(const string& text, int yOffset);
+
+	const glu::RenderContext&	m_ctx;
+	const glu::ShaderProgram*	m_prog;
+	vector<float>				m_posBuf;
+	vector<deUint16>			m_ndxBuf;
+};
+
+void DebugInfoRenderer::drawInfo (const deUint64 secondsElapsed, const int texMem, const int maxTexMem, const int bufMem, const int maxBufMem, const int iterNdx)
+{
+	const deUint64 m = secondsElapsed / 60;
+	const deUint64 h = m / 60;
+	const deUint64 d = h / 24;
+
+	{
+		std::ostringstream text;
+
+		text << std::setw(2) << std::setfill('0') << d << ":"
+			 << std::setw(2) << std::setfill('0') << h % 24 << ":"
+			 << std::setw(2) << std::setfill('0') << m % 60 << ":"
+			 << std::setw(2) << std::setfill('0') << secondsElapsed % 60;
+		addTextToBuffer(text.str(), 0);
+		text.str("");
+
+		text << std::fixed << std::setprecision(2) << (float)texMem/Mi << "/" << (float)maxTexMem/Mi;
+		addTextToBuffer(text.str(), 1);
+		text.str("");
+
+		text << std::fixed << std::setprecision(2) << (float)bufMem/Mi << "/" << (float)maxBufMem/Mi;
+		addTextToBuffer(text.str(), 2);
+		text.str("");
+
+		text << std::setw(0) << iterNdx;
+		addTextToBuffer(text.str(), 3);
+	}
+
+	render();
+}
+
+DebugInfoRenderer::DebugInfoRenderer (const glu::RenderContext& ctx)
+	: m_ctx			(ctx)
+	, m_prog		(DE_NULL)
+{
+	DE_ASSERT(!m_prog);
+	m_prog = new glu::ShaderProgram(ctx, glu::makeVtxFragSources(
+		"attribute highp vec2 a_pos;\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = vec4(a_pos, -1.0, 1.0);\n"
+		"}\n",
+
+		"void main(void)\n"
+		"{\n"
+		"	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+		"}\n"));
+}
+
+void DebugInfoRenderer::addTextToBuffer (const string& text, const int yOffset)
+{
+	static const char		characters[]	= "0123456789.:/";
+	const int				numCharacters	= DE_LENGTH_OF_ARRAY(characters)-1; // \note -1 for null byte.
+	const int				charWid			= 6;
+	const int				charHei			= 6;
+	static const string		charsStr		(characters);
+
+	static const char font[numCharacters*charWid*charHei + 1]=
+		" #### ""   #  "" #### ""##### ""   #  ""######"" #####""######"" #### "" #### ""      ""  ##  ""     #"
+		"#    #""  ##  ""#    #""     #""  #   ""#     ""#     ""    # ""#    #""#    #""      ""  ##  ""    # "
+		"#    #""   #  ""    # ""  ### "" #  # "" #### ""# ### ""   #  "" #### "" #####""      ""      ""   #  "
+		"#    #""   #  ""   #  ""     #""######""     #""##   #""  #   ""#    #""     #""      ""  ##  ""  #   "
+		"#    #""   #  ""  #   ""#    #""    # ""#    #""#    #"" #    ""#    #""   ## ""  ##  ""  ##  "" #    "
+		" #### ""  ### ""######"" #### ""    # "" #### "" #### ""#     "" #### ""###   ""  ##  ""      ""#     ";
+
+	for (int ndxInText = 0; ndxInText < (int)text.size(); ndxInText++)
+	{
+		const int ndxInCharset	= (int)charsStr.find(text[ndxInText]);
+		DE_ASSERT(ndxInCharset < numCharacters);
+		const int fontXStart	= ndxInCharset*charWid;
+
+		for (int y = 0; y < charHei; y++)
+		{
+			float ay = -1.0f + (float)(y + 0 + yOffset*(charHei+2))*0.1f/(float)(charHei+2);
+			float by = -1.0f + (float)(y + 1 + yOffset*(charHei+2))*0.1f/(float)(charHei+2);
+			for (int x = 0; x < charWid; x++)
+			{
+				// \note Text is mirrored in x direction since on most(?) mobile devices the image is mirrored(?).
+				float ax = 1.0f - (float)(x + 0 + ndxInText*(charWid+2))*0.1f/(float)(charWid+2);
+				float bx = 1.0f - (float)(x + 1 + ndxInText*(charWid+2))*0.1f/(float)(charWid+2);
+
+				if (font[y*numCharacters*charWid + fontXStart + x] != ' ')
+				{
+					const int vtxNdx = (int)m_posBuf.size()/2;
+
+					m_ndxBuf.push_back(vtxNdx+0);
+					m_ndxBuf.push_back(vtxNdx+1);
+					m_ndxBuf.push_back(vtxNdx+2);
+
+					m_ndxBuf.push_back(vtxNdx+2);
+					m_ndxBuf.push_back(vtxNdx+1);
+					m_ndxBuf.push_back(vtxNdx+3);
+
+					m_posBuf.push_back(ax);
+					m_posBuf.push_back(ay);
+
+					m_posBuf.push_back(bx);
+					m_posBuf.push_back(ay);
+
+					m_posBuf.push_back(ax);
+					m_posBuf.push_back(by);
+
+					m_posBuf.push_back(bx);
+					m_posBuf.push_back(by);
+				}
+			}
+		}
+	}
+}
+
+void DebugInfoRenderer::render (void)
+{
+	const int prog		= m_prog->getProgram();
+	const int posloc	= glGetAttribLocation(prog, "a_pos");
+
+	glUseProgram(prog);
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+	glEnableVertexAttribArray(posloc);
+	glVertexAttribPointer(posloc, 2, GL_FLOAT, 0, 0, &m_posBuf[0]);
+	glDrawElements(GL_TRIANGLES, (int)m_ndxBuf.size(), GL_UNSIGNED_SHORT, &m_ndxBuf[0]);
+	glDisableVertexAttribArray(posloc);
+
+	m_posBuf.clear();
+	m_ndxBuf.clear();
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Texture object helper class
+ *
+ * Each Texture owns a GL texture object that is created when the Texture
+ * is constructed and deleted when it's destructed. The class provides some
+ * convenience interface functions to e.g. upload texture data to the GL.
+ *
+ * In addition, the class tracks the approximate amount of GL memory likely
+ * used by the corresponding GL texture object; get this with
+ * getApproxMemUsage(). Also, getApproxMemUsageDiff() returns N-M, where N
+ * is the value that getApproxMemUsage() would return after a call to
+ * setData() with arguments corresponding to those given to
+ * getApproxMemUsageDiff(), and M is the value currently returned by
+ * getApproxMemUsage(). This can be used to check if we need to free some
+ * other memory before performing the setData() call, in case we have an
+ * upper limit on the amount of memory we want to use.
+ *//*--------------------------------------------------------------------*/
+class Texture
+{
+public:
+						Texture					(TextureType type);
+						~Texture				(void);
+
+	// Functions that may change the value returned by getApproxMemUsage().
+	void				setData					(const ConstPixelBufferAccess& src, int width, int height, deUint32 internalFormat, bool useMipmap);
+
+	// Functions that don't change the value returned by getApproxMemUsage().
+	void				setSubData				(const ConstPixelBufferAccess& src, int xOff, int yOff, int width, int height) const;
+	void				toUnit					(int unit) const;
+	void				setFilter				(deUint32 min, deUint32 mag) const;
+	void				setWrap					(deUint32 s, deUint32 t) const;
+
+	int					getApproxMemUsage		(void) const { return m_dataSizeApprox; }
+	int					getApproxMemUsageDiff	(int width, int height, deUint32 internalFormat, bool useMipmap) const;
+
+private:
+						Texture					(const Texture&); // Not allowed.
+	Texture&			operator=				(const Texture&); // Not allowed.
+
+	static deUint32		genTexture				(void) { deUint32 tex = 0; glGenTextures(1, &tex); return tex; }
+
+	deUint32			getGLBindTarget			(void) const { DE_ASSERT(m_type == TEXTURETYPE_2D || m_type == TEXTURETYPE_CUBE); return m_type == TEXTURETYPE_2D ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP; }
+
+	const TextureType	m_type;
+	const deUint32		m_textureGL;
+
+	int					m_numMipLevels;
+	deUint32			m_internalFormat;
+	int					m_dataSizeApprox;
+};
+
+Texture::Texture (const TextureType type)
+	: m_type			(type)
+	, m_textureGL		(genTexture())
+	, m_numMipLevels	(0)
+	, m_internalFormat	(0)
+	, m_dataSizeApprox	(0)
+{
+}
+
+Texture::~Texture (void)
+{
+	glDeleteTextures(1, &m_textureGL);
+}
+
+int Texture::getApproxMemUsageDiff (const int width, const int height, const deUint32 internalFormat, const bool useMipmap) const
+{
+	const int	numLevels				= useMipmap ? deLog2Floor32(de::max(width, height))+1 : 1;
+	const int	pixelSize				= internalFormat == GL_RGBA		? 4
+										: internalFormat == GL_RGB		? 3
+										: internalFormat == GL_ALPHA	? 1
+										: glu::mapGLInternalFormat(internalFormat).getPixelSize();
+	int			memUsageApproxAfter		= 0;
+
+	for (int level = 0; level < numLevels; level++)
+		memUsageApproxAfter += de::max(1, width>>level) * de::max(1, height>>level) * pixelSize * (m_type == TEXTURETYPE_CUBE ? 6 : 1);
+
+	return memUsageApproxAfter - getApproxMemUsage();
+}
+
+void Texture::setData (const ConstPixelBufferAccess& src, const int width, const int height, const deUint32 internalFormat, const bool useMipmap)
+{
+	DE_ASSERT(m_type != TEXTURETYPE_CUBE || width == height);
+	DE_ASSERT(!useMipmap || (deIsPowerOfTwo32(width) && deIsPowerOfTwo32(height)));
+
+	const TextureFormat&		format		= src.getFormat();
+	const glu::TransferFormat	transfer	= glu::getTransferFormat(format);
+
+	m_numMipLevels = useMipmap ? deLog2Floor32(de::max(width, height))+1 : 1;
+
+	m_internalFormat = internalFormat;
+	m_dataSizeApprox = width * height * format.getPixelSize() * (m_type == TEXTURETYPE_CUBE ? 6 : 1);
+
+	DE_ASSERT(src.getRowPitch() == format.getPixelSize()*src.getWidth());
+	DE_ASSERT(isMatchingGLInternalFormat(internalFormat, format));
+	DE_ASSERT(width <= src.getWidth() && height <= src.getHeight());
+
+	glPixelStorei(GL_UNPACK_ALIGNMENT, computePixelStore(format));
+
+	if (m_type == TEXTURETYPE_2D)
+	{
+		m_dataSizeApprox = 0;
+
+		glBindTexture(GL_TEXTURE_2D, m_textureGL);
+		for (int level = 0; level < m_numMipLevels; level++)
+		{
+			const int levelWid = de::max(1, width>>level);
+			const int levelHei = de::max(1, height>>level);
+			m_dataSizeApprox += levelWid * levelHei * format.getPixelSize();
+			glTexImage2D(GL_TEXTURE_2D, level, internalFormat, levelWid, levelHei, 0, transfer.format, transfer.dataType, src.getDataPtr());
+		}
+	}
+	else if (m_type == TEXTURETYPE_CUBE)
+	{
+		m_dataSizeApprox = 0;
+
+		glBindTexture(GL_TEXTURE_CUBE_MAP, m_textureGL);
+		for (int level = 0; level < m_numMipLevels; level++)
+		{
+			const int levelWid = de::max(1, width>>level);
+			const int levelHei = de::max(1, height>>level);
+			m_dataSizeApprox += 6 * levelWid * levelHei * format.getPixelSize();
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+				glTexImage2D(cubeFaceToGLFace((CubeFace)face), level, internalFormat, levelWid, levelHei, 0, transfer.format, transfer.dataType, src.getDataPtr());
+		}
+	}
+	else
+		DE_ASSERT(false);
+}
+
+void Texture::setSubData (const ConstPixelBufferAccess& src, const int xOff, const int yOff, const int width, const int height) const
+{
+	const TextureFormat&		format		= src.getFormat();
+	const glu::TransferFormat	transfer	= glu::getTransferFormat(format);
+
+	DE_ASSERT(src.getRowPitch() == format.getPixelSize()*src.getWidth());
+	DE_ASSERT(isMatchingGLInternalFormat(m_internalFormat, format));
+	DE_ASSERT(width <= src.getWidth() && height <= src.getHeight());
+
+	glPixelStorei(GL_UNPACK_ALIGNMENT, computePixelStore(format));
+
+	if (m_type == TEXTURETYPE_2D)
+	{
+		glBindTexture(GL_TEXTURE_2D, m_textureGL);
+		for (int level = 0; level < m_numMipLevels; level++)
+			glTexSubImage2D(GL_TEXTURE_2D, level, xOff>>level, yOff>>level, de::max(1, width>>level), de::max(1, height>>level), transfer.format, transfer.dataType, src.getDataPtr());
+	}
+	else if (m_type == TEXTURETYPE_CUBE)
+	{
+		glBindTexture(GL_TEXTURE_CUBE_MAP, m_textureGL);
+		for (int level = 0; level < m_numMipLevels; level++)
+		{
+			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+				glTexSubImage2D(cubeFaceToGLFace((CubeFace)face), level, xOff>>level, yOff>>level, de::max(1, width>>level), de::max(1, height>>level), transfer.format, transfer.dataType, src.getDataPtr());
+		}
+	}
+	else
+		DE_ASSERT(false);
+}
+
+void Texture::setFilter (const deUint32 min, const deUint32 mag) const
+{
+	glBindTexture(getGLBindTarget(), m_textureGL);
+	glTexParameteri(getGLBindTarget(), GL_TEXTURE_MIN_FILTER, min);
+	glTexParameteri(getGLBindTarget(), GL_TEXTURE_MAG_FILTER, mag);
+}
+
+void Texture::setWrap (const deUint32 s, const deUint32 t) const
+{
+	glBindTexture(getGLBindTarget(), m_textureGL);
+	glTexParameteri(getGLBindTarget(), GL_TEXTURE_WRAP_S, s);
+	glTexParameteri(getGLBindTarget(), GL_TEXTURE_WRAP_T, t);
+}
+
+void Texture::toUnit (const int unit) const
+{
+	glActiveTexture(GL_TEXTURE0 + unit);
+	glBindTexture(getGLBindTarget(), m_textureGL);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Buffer object helper class
+ *
+ * Each Buffer owns a GL buffer object that is created when the Buffer
+ * is constructed and deleted when it's destructed. The class provides some
+ * convenience interface functions to e.g. upload buffer data to the GL.
+ *
+ * In addition, the class tracks the approximate amount of GL memory,
+ * similarly to the Texture class (see above). The getApproxMemUsageDiff()
+ * is also analoguous.
+ *//*--------------------------------------------------------------------*/
+class Buffer
+{
+public:
+						Buffer					(void);
+						~Buffer					(void);
+
+	// Functions that may change the value returned by getApproxMemUsage().
+	template <typename T>
+	void				setData					(const vector<T>& src, const deUint32 target, const deUint32 usage) { setData(&src[0], (int)(src.size()*sizeof(T)), target, usage); }
+	void				setData					(const void* src, int size, deUint32 target, deUint32 usage);
+
+	// Functions that don't change the value returned by getApproxMemUsage().
+	template <typename T>
+	void				setSubData				(const vector<T>& src, const int offsetElems, const int numElems, const deUint32 target) { setSubData(&src[offsetElems], offsetElems*(int)sizeof(T), numElems*(int)sizeof(T), target); }
+	void				setSubData				(const void* src, int offsetBytes, int sizeBytes, deUint32 target) const;
+	void				bind					(const deUint32 target) const { glBindBuffer(target, m_bufferGL); }
+
+	int					getApproxMemUsage		(void) const { return m_dataSizeApprox; }
+	template <typename T>
+	int					getApproxMemUsageDiff	(const vector<T>& src) const { return getApproxMemUsageDiff((int)(src.size()*sizeof(T))); }
+	int					getApproxMemUsageDiff	(const int sizeBytes) const { return sizeBytes - getApproxMemUsage(); }
+
+private:
+						Buffer					(const Buffer&); // Not allowed.
+	Buffer&				operator=				(const Buffer&); // Not allowed.
+
+	static deUint32		genBuffer				(void) { deUint32 buf = 0; glGenBuffers(1, &buf); return buf; }
+
+	const deUint32		m_bufferGL;
+	int					m_dataSizeApprox;
+};
+
+Buffer::Buffer (void)
+	: m_bufferGL		(genBuffer())
+	, m_dataSizeApprox	(0)
+{
+}
+
+Buffer::~Buffer (void)
+{
+	glDeleteBuffers(1, &m_bufferGL);
+}
+
+void Buffer::setData (const void* const src, const int size, const deUint32 target, const deUint32 usage)
+{
+	bind(target);
+	glBufferData(target, size, src, usage);
+	glBindBuffer(target, 0);
+
+	m_dataSizeApprox = size;
+}
+
+void Buffer::setSubData (const void* const src, const int offsetBytes, const int sizeBytes, const deUint32 target) const
+{
+	bind(target);
+	glBufferSubData(target, offsetBytes, sizeBytes, src);
+	glBindBuffer(target, 0);
+}
+
+class Program
+{
+public:
+						Program					(void);
+						~Program				(void);
+
+	void				setSources				(const string& vertSource, const string& fragSource);
+	void				build					(TestLog& log);
+	void				use						(void) const { DE_ASSERT(m_isBuilt); glUseProgram(m_programGL); }
+	void				setRandomUniforms		(const vector<VarSpec>& uniforms, const string& shaderNameManglingSuffix, Random& rnd) const;
+	void				setAttribute			(const Buffer& attrBuf, int attrBufOffset, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const;
+	void				setAttributeClientMem	(const void* attrData, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const;
+	void				disableAttributeArray	(const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const;
+
+private:
+						Program				(const Program&); // Not allowed.
+	Program&			operator=			(const Program&); // Not allowed.
+
+	string				m_vertSource;
+	string				m_fragSource;
+
+	const deUint32		m_vertShaderGL;
+	const deUint32		m_fragShaderGL;
+	const deUint32		m_programGL;
+	bool				m_hasSources;
+	bool				m_isBuilt;
+};
+
+Program::Program (void)
+	: m_vertShaderGL	(glCreateShader(GL_VERTEX_SHADER))
+	, m_fragShaderGL	(glCreateShader(GL_FRAGMENT_SHADER))
+	, m_programGL		(glCreateProgram())
+	, m_hasSources		(false)
+	, m_isBuilt			(false)
+{
+	glAttachShader(m_programGL, m_vertShaderGL);
+	glAttachShader(m_programGL, m_fragShaderGL);
+}
+
+Program::~Program (void)
+{
+	glDeleteShader(m_vertShaderGL);
+	glDeleteShader(m_fragShaderGL);
+	glDeleteProgram(m_programGL);
+}
+
+void Program::setSources (const string& vertSource, const string& fragSource)
+{
+	const char* const vertSourceCstr = vertSource.c_str();
+	const char* const fragSourceCstr = fragSource.c_str();
+
+	m_vertSource = vertSource;
+	m_fragSource = fragSource;
+
+	// \note In GLES2 api the source parameter type lacks one const.
+	glShaderSource(m_vertShaderGL, 1, (const char**)&vertSourceCstr, DE_NULL);
+	glShaderSource(m_fragShaderGL, 1, (const char**)&fragSourceCstr, DE_NULL);
+
+	m_hasSources = true;
+}
+
+void Program::build (TestLog& log)
+{
+	DE_ASSERT(m_hasSources);
+
+	const bool vertCompileOk	= compileShader(m_vertShaderGL);
+	const bool fragCompileOk	= compileShader(m_fragShaderGL);
+	const bool attemptLink		= vertCompileOk && fragCompileOk;
+	const bool linkOk			= attemptLink && linkProgram(m_programGL);
+
+	if (!(vertCompileOk && fragCompileOk && linkOk))
+	{
+		log << TestLog::ShaderProgram(linkOk, attemptLink ? getProgramInfoLog(m_programGL) : string(""))
+			<< TestLog::Shader(QP_SHADER_TYPE_VERTEX, m_vertSource, vertCompileOk, getShaderInfoLog(m_vertShaderGL))
+			<< TestLog::Shader(QP_SHADER_TYPE_FRAGMENT, m_fragSource, fragCompileOk, getShaderInfoLog(m_fragShaderGL))
+			<< TestLog::EndShaderProgram;
+
+		throw tcu::TestError("Program build failed");
+	}
+
+	m_isBuilt = true;
+}
+
+void Program::setRandomUniforms (const vector<VarSpec>& uniforms, const string& shaderNameManglingSuffix, Random& rnd) const
+{
+	use();
+
+	for (int unifNdx = 0; unifNdx < (int)uniforms.size(); unifNdx++)
+	{
+		const VarSpec&	spec			= uniforms[unifNdx];
+		const int		typeScalarSize	= glu::getDataTypeScalarSize(spec.type);
+		const int		location		= glGetUniformLocation(m_programGL, mangleShaderNames(spec.name, shaderNameManglingSuffix).c_str());
+		if (location < 0)
+			continue;
+
+		if (glu::isDataTypeFloatOrVec(spec.type))
+		{
+			float val[4];
+			for (int i = 0; i < typeScalarSize; i++)
+				val[i] = rnd.getFloat(spec.minValue.f[i], spec.maxValue.f[i]);
+
+			switch (spec.type)
+			{
+				case glu::TYPE_FLOAT:		glUniform1f(location, val[0]);							break;
+				case glu::TYPE_FLOAT_VEC2:	glUniform2f(location, val[0], val[1]);					break;
+				case glu::TYPE_FLOAT_VEC3:	glUniform3f(location, val[0], val[1], val[2]);			break;
+				case glu::TYPE_FLOAT_VEC4:	glUniform4f(location, val[0], val[1], val[2], val[3]);	break;
+				default: DE_ASSERT(false);
+			}
+		}
+		else if (glu::isDataTypeMatrix(spec.type))
+		{
+			float val[4*4];
+			for (int i = 0; i < typeScalarSize; i++)
+				val[i] = rnd.getFloat(spec.minValue.f[i], spec.maxValue.f[i]);
+
+			switch (spec.type)
+			{
+				case glu::TYPE_FLOAT_MAT2:		glUniformMatrix2fv		(location, 1, GL_FALSE, &val[0]); break;
+				case glu::TYPE_FLOAT_MAT3:		glUniformMatrix3fv		(location, 1, GL_FALSE, &val[0]); break;
+				case glu::TYPE_FLOAT_MAT4:		glUniformMatrix4fv		(location, 1, GL_FALSE, &val[0]); break;
+				case glu::TYPE_FLOAT_MAT2X3:	glUniformMatrix2x3fv	(location, 1, GL_FALSE, &val[0]); break;
+				case glu::TYPE_FLOAT_MAT2X4:	glUniformMatrix2x4fv	(location, 1, GL_FALSE, &val[0]); break;
+				case glu::TYPE_FLOAT_MAT3X2:	glUniformMatrix3x2fv	(location, 1, GL_FALSE, &val[0]); break;
+				case glu::TYPE_FLOAT_MAT3X4:	glUniformMatrix3x4fv	(location, 1, GL_FALSE, &val[0]); break;
+				case glu::TYPE_FLOAT_MAT4X2:	glUniformMatrix4x2fv	(location, 1, GL_FALSE, &val[0]); break;
+				case glu::TYPE_FLOAT_MAT4X3:	glUniformMatrix4x3fv	(location, 1, GL_FALSE, &val[0]); break;
+				default: DE_ASSERT(false);
+			}
+		}
+		else if (glu::isDataTypeIntOrIVec(spec.type))
+		{
+			int val[4];
+			for (int i = 0; i < typeScalarSize; i++)
+				val[i] = rnd.getInt(spec.minValue.i[i], spec.maxValue.i[i]);
+
+			switch (spec.type)
+			{
+				case glu::TYPE_INT:			glUniform1i(location, val[0]);							break;
+				case glu::TYPE_INT_VEC2:	glUniform2i(location, val[0], val[1]);					break;
+				case glu::TYPE_INT_VEC3:	glUniform3i(location, val[0], val[1], val[2]);			break;
+				case glu::TYPE_INT_VEC4:	glUniform4i(location, val[0], val[1], val[2], val[3]);	break;
+				default: DE_ASSERT(false);
+			}
+		}
+		else if (glu::isDataTypeUintOrUVec(spec.type))
+		{
+			deUint32 val[4];
+			for (int i = 0; i < typeScalarSize; i++)
+			{
+				DE_ASSERT(spec.minValue.i[i] >= 0 && spec.maxValue.i[i] >= 0);
+				val[i] = (deUint32)rnd.getInt(spec.minValue.i[i], spec.maxValue.i[i]);
+			}
+
+			switch (spec.type)
+			{
+				case glu::TYPE_UINT:		glUniform1ui(location, val[0]);							break;
+				case glu::TYPE_UINT_VEC2:	glUniform2ui(location, val[0], val[1]);					break;
+				case glu::TYPE_UINT_VEC3:	glUniform3ui(location, val[0], val[1], val[2]);			break;
+				case glu::TYPE_UINT_VEC4:	glUniform4ui(location, val[0], val[1], val[2], val[3]);	break;
+				default: DE_ASSERT(false);
+			}
+		}
+		else
+			DE_ASSERT(false);
+	}
+}
+
+void Program::setAttribute (const Buffer& attrBuf, const int attrBufOffset, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const
+{
+	const int attrLoc = glGetAttribLocation(m_programGL, mangleShaderNames(attrSpec.name, shaderNameManglingSuffix).c_str());
+
+	glEnableVertexAttribArray(attrLoc);
+	attrBuf.bind(GL_ARRAY_BUFFER);
+
+	if (glu::isDataTypeFloatOrVec(attrSpec.type))
+		glVertexAttribPointer(attrLoc, glu::getDataTypeScalarSize(attrSpec.type), GL_FLOAT, GL_FALSE, 0, (GLvoid*)(deIntptr)attrBufOffset);
+	else
+		DE_ASSERT(false);
+}
+
+void Program::setAttributeClientMem (const void* const attrData, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const
+{
+	const int attrLoc = glGetAttribLocation(m_programGL, mangleShaderNames(attrSpec.name, shaderNameManglingSuffix).c_str());
+
+	glEnableVertexAttribArray(attrLoc);
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+	if (glu::isDataTypeFloatOrVec(attrSpec.type))
+		glVertexAttribPointer(attrLoc, glu::getDataTypeScalarSize(attrSpec.type), GL_FLOAT, GL_FALSE, 0, attrData);
+	else
+		DE_ASSERT(false);
+}
+
+void Program::disableAttributeArray (const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const
+{
+	const int attrLoc = glGetAttribLocation(m_programGL, mangleShaderNames(attrSpec.name, shaderNameManglingSuffix).c_str());
+
+	glDisableVertexAttribArray(attrLoc);
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Container class for managing GL objects
+ *
+ * GLObjectManager can be used for objects of class Program, Buffer or
+ * Texture. In the manager, each such object is associated with a name that
+ * is used to access it.
+ *
+ * In addition to the making, getting and removing functions, the manager
+ * supports marking objects as "garbage", meaning they're not yet
+ * destroyed, but can be later destroyed with removeRandomGarbage(). The
+ * idea is that if we want to stress test with high memory usage, we can
+ * continuously move objects to garbage after using them, and when a memory
+ * limit is reached, we can call removeGarbageUntilUnder(limit, rnd). This
+ * way we can approximately keep our memory usage at just under the wanted
+ * limit.
+ *
+ * The manager also supports querying the approximate amount of GL memory
+ * used by its objects.
+ *
+ * \note The memory usage related functions are not currently supported
+ *		 for Program objects.
+ *//*--------------------------------------------------------------------*/
+template <typename T>
+class GLObjectManager
+{
+public:
+	void						make						(const string& name)								{ DE_ASSERT(!has(name)); m_objects[name] = SharedPtr<T>(new T); }
+	void						make						(const string& name, gls::TextureType texType)		{ DE_ASSERT(!has(name)); m_objects[name] = SharedPtr<T>(new T(texType)); }
+	bool						has							(const string& name) const	{ return m_objects.find(name) != m_objects.end(); }
+	const T&					get							(const string& name) const;
+	T&							get							(const string& name)		{ return const_cast<T&>(((const GLObjectManager<T>*)this)->get(name)); }
+	void						remove						(const string& name)		{ const int removed = (int)m_objects.erase(name); DE_ASSERT(removed); DE_UNREF(removed); }
+	int							computeApproxMemUsage		(void) const;
+	void						markAsGarbage				(const string& name);
+	int							removeRandomGarbage			(Random& rnd);
+	void						removeGarbageUntilUnder		(int limit, Random& rnd);
+
+private:
+	static const char*			objTypeName					(void);
+
+	map<string, SharedPtr<T> >	m_objects;
+	vector<SharedPtr<T> >		m_garbageObjects;
+};
+
+template <> const char* GLObjectManager<Buffer>::objTypeName	(void) { return "buffer"; }
+template <> const char* GLObjectManager<Texture>::objTypeName	(void) { return "texture"; }
+template <> const char* GLObjectManager<Program>::objTypeName	(void) { return "program"; }
+
+template <typename T>
+const T& GLObjectManager<T>::get (const string& name) const
+{
+	const typename map<string, SharedPtr<T> >::const_iterator it = m_objects.find(name);
+	DE_ASSERT(it != m_objects.end());
+	return *it->second;
+}
+
+template <typename T>
+int GLObjectManager<T>::computeApproxMemUsage (void) const
+{
+	int result = 0;
+
+	for (typename map<string, SharedPtr<T> >::const_iterator it = m_objects.begin(); it != m_objects.end(); ++it)
+		result += it->second->getApproxMemUsage();
+
+	for (typename vector<SharedPtr<T> >::const_iterator it = m_garbageObjects.begin(); it != m_garbageObjects.end(); ++it)
+		result += (*it)->getApproxMemUsage();
+
+	return result;
+}
+
+template <typename T>
+void GLObjectManager<T>::markAsGarbage (const string& name)
+{
+	const typename map<string, SharedPtr<T> >::iterator it = m_objects.find(name);
+	DE_ASSERT(it != m_objects.end());
+	m_garbageObjects.push_back(it->second);
+	m_objects.erase(it);
+}
+
+template <typename T>
+int GLObjectManager<T>::removeRandomGarbage (Random& rnd)
+{
+	if (m_garbageObjects.empty())
+		return -1;
+
+	const int removeNdx		= rnd.getInt(0, (int)m_garbageObjects.size()-1);
+	const int memoryFreed	= m_garbageObjects[removeNdx]->getApproxMemUsage();
+	m_garbageObjects.erase(m_garbageObjects.begin() + removeNdx);
+	return memoryFreed;
+}
+
+template <typename T>
+void GLObjectManager<T>::removeGarbageUntilUnder (const int limit, Random& rnd)
+{
+	int memUsage = computeApproxMemUsage();
+
+	while (memUsage > limit)
+	{
+		const int memReleased = removeRandomGarbage(rnd);
+		if (memReleased < 0)
+			throw tcu::InternalError(string("") + "Given " + objTypeName() + " memory usage limit exceeded, and no unneeded " + objTypeName() + " resources available to release");
+		memUsage -= memReleased;
+		DE_ASSERT(memUsage == computeApproxMemUsage());
+	}
+}
+
+} // LongStressCaseInternal
+
+using namespace LongStressCaseInternal;
+
+static int generateRandomAttribData (vector<deUint8>& attrDataBuf, int& dataSizeBytesDst, const VarSpec& attrSpec, const int numVertices, Random& rnd)
+{
+	const bool	isFloat			= glu::isDataTypeFloatOrVec(attrSpec.type);
+	const int	numComponents	= glu::getDataTypeScalarSize(attrSpec.type);
+	const int	componentSize	= (int)(isFloat ? sizeof(GLfloat) : sizeof(GLint));
+	const int	offsetInBuf		= nextDivisible((int)attrDataBuf.size(), componentSize); // Round up for alignment.
+
+	DE_STATIC_ASSERT(sizeof(GLint) == sizeof(int));
+	DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(float));
+
+	dataSizeBytesDst = numComponents*componentSize*numVertices;
+
+	attrDataBuf.resize(offsetInBuf + dataSizeBytesDst);
+
+	if (isFloat)
+	{
+		float* const data = (float*)&attrDataBuf[offsetInBuf];
+
+		for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx++)
+			for (int compNdx = 0; compNdx < numComponents; compNdx++)
+				data[vtxNdx*numComponents + compNdx] = rnd.getFloat(attrSpec.minValue.f[compNdx], attrSpec.maxValue.f[compNdx]);
+	}
+	else
+	{
+		DE_ASSERT(glu::isDataTypeIntOrIVec(attrSpec.type));
+
+		int* const data = (int*)&attrDataBuf[offsetInBuf];
+
+		for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx++)
+			for (int compNdx = 0; compNdx < numComponents; compNdx++)
+				data[vtxNdx*numComponents + compNdx] = rnd.getInt(attrSpec.minValue.i[compNdx], attrSpec.maxValue.i[compNdx]);
+	}
+
+	return offsetInBuf;
+}
+
+static int generateRandomPositionAttribData (vector<deUint8>& attrDataBuf, int& dataSizeBytesDst, const VarSpec& attrSpec, const int numVertices, Random& rnd)
+{
+	DE_ASSERT(glu::isDataTypeFloatOrVec(attrSpec.type));
+
+	const int numComponents = glu::getDataTypeScalarSize(attrSpec.type);
+	DE_ASSERT(numComponents >= 2);
+	const int offsetInBuf = generateRandomAttribData(attrDataBuf, dataSizeBytesDst, attrSpec, numVertices, rnd);
+
+	if (numComponents > 2)
+	{
+		float* const data = (float*)&attrDataBuf[offsetInBuf];
+
+		for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx++)
+			data[vtxNdx*numComponents + 2] = -1.0f;
+
+		for (int triNdx = 0; triNdx < numVertices-2; triNdx++)
+		{
+			float* const	vtxAComps	= &data[(triNdx+0)*numComponents];
+			float* const	vtxBComps	= &data[(triNdx+1)*numComponents];
+			float* const	vtxCComps	= &data[(triNdx+2)*numComponents];
+
+			const float		triArea		= triangleArea(Vec2(vtxAComps[0], vtxAComps[1]),
+													   Vec2(vtxBComps[0], vtxBComps[1]),
+													   Vec2(vtxCComps[0], vtxCComps[1]));
+			const float		t			= triArea / (triArea + 1.0f);
+			const float		z			= (1.0f-t)*attrSpec.minValue.f[2] + t*attrSpec.maxValue.f[2];
+
+			vtxAComps[2] = de::max(vtxAComps[2], z);
+			vtxBComps[2] = de::max(vtxBComps[2], z);
+			vtxCComps[2] = de::max(vtxCComps[2], z);
+		}
+	}
+
+	return offsetInBuf;
+}
+
+static void generateAttribs (vector<deUint8>& attrDataBuf, vector<int>& attrDataOffsets, vector<int>& attrDataSizes, const vector<VarSpec>& attrSpecs, const string& posAttrName, const int numVertices, Random& rnd)
+{
+	attrDataBuf.clear();
+	attrDataOffsets.clear();
+	attrDataSizes.resize(attrSpecs.size());
+
+	for (int i = 0; i < (int)attrSpecs.size(); i++)
+	{
+		if (attrSpecs[i].name == posAttrName)
+			attrDataOffsets.push_back(generateRandomPositionAttribData(attrDataBuf, attrDataSizes[i], attrSpecs[i], numVertices, rnd));
+		else
+			attrDataOffsets.push_back(generateRandomAttribData(attrDataBuf, attrDataSizes[i], attrSpecs[i], numVertices, rnd));
+	}
+}
+
+LongStressCase::LongStressCase (tcu::TestContext&				testCtx,
+								const glu::RenderContext&		renderCtx,
+								const char* const				name,
+								const char* const				desc,
+								const int						maxTexMemoryUsageBytes,
+								const int						maxBufMemoryUsageBytes,
+								const int						numDrawCallsPerIteration,
+								const int						numTrianglesPerDrawCall,
+								const vector<ProgramContext>&	programContexts,
+								const FeatureProbabilities&		probabilities,
+								const deUint32					indexBufferUsage,
+								const deUint32					attrBufferUsage,
+								const int						redundantBufferFactor,
+								const bool						showDebugInfo)
+	: tcu::TestCase					(testCtx, name, desc)
+	, m_renderCtx					(renderCtx)
+	, m_maxTexMemoryUsageBytes		(maxTexMemoryUsageBytes)
+	, m_maxBufMemoryUsageBytes		(maxBufMemoryUsageBytes)
+	, m_numDrawCallsPerIteration	(numDrawCallsPerIteration)
+	, m_numTrianglesPerDrawCall		(numTrianglesPerDrawCall)
+	, m_numVerticesPerDrawCall		(numTrianglesPerDrawCall+2) // \note Triangle strips are used.
+	, m_programContexts				(programContexts)
+	, m_probabilities				(probabilities)
+	, m_indexBufferUsage			(indexBufferUsage)
+	, m_attrBufferUsage				(attrBufferUsage)
+	, m_redundantBufferFactor		(redundantBufferFactor)
+	, m_showDebugInfo				(showDebugInfo)
+	, m_numIterations				(getNumIterations(testCtx, 5))
+	, m_isGLES3						(contextSupports(renderCtx.getType(), glu::ApiType::es(3,0)))
+	, m_currentIteration			(0)
+	, m_startTimeSeconds			((deUint64)-1)
+	, m_lastLogTime					((deUint64)-1)
+	, m_lastLogIteration			(0)
+	, m_currentLogEntryNdx			(0)
+	, m_rnd							(deStringHash(getName()) ^ testCtx.getCommandLine().getBaseSeed())
+	, m_programs					(DE_NULL)
+	, m_buffers						(DE_NULL)
+	, m_textures					(DE_NULL)
+	, m_debugInfoRenderer			(DE_NULL)
+{
+	DE_ASSERT(m_numVerticesPerDrawCall <= (int)std::numeric_limits<deUint16>::max()+1); // \note Vertices are referred to with 16-bit indices.
+	DE_ASSERT(m_redundantBufferFactor > 0);
+}
+
+LongStressCase::~LongStressCase (void)
+{
+	LongStressCase::deinit();
+}
+
+void LongStressCase::init (void)
+{
+	// Generate dummy texture data for each texture spec in m_programContexts.
+
+	DE_ASSERT(!m_programContexts.empty());
+	DE_ASSERT(m_programResources.empty());
+	m_programResources.resize(m_programContexts.size());
+
+	for (int progCtxNdx = 0; progCtxNdx < (int)m_programContexts.size(); progCtxNdx++)
+	{
+		const ProgramContext&	progCtx = m_programContexts[progCtxNdx];
+		ProgramResources&		progRes = m_programResources[progCtxNdx];
+
+		for (int texSpecNdx = 0; texSpecNdx < (int)progCtx.textureSpecs.size(); texSpecNdx++)
+		{
+			const TextureSpec&		spec	= progCtx.textureSpecs[texSpecNdx];
+			const TextureFormat		format	= glu::mapGLTransferFormat(spec.format, spec.dataType);
+
+			// If texture data with the same format has already been generated, re-use that (don't care much about contents).
+
+			SharedPtr<TextureLevel> dummyTex;
+
+			for (int prevProgCtxNdx = 0; prevProgCtxNdx < (int)m_programResources.size(); prevProgCtxNdx++)
+			{
+				const vector<SharedPtr<TextureLevel> >& prevProgCtxTextures = m_programResources[prevProgCtxNdx].dummyTextures;
+
+				for (int texNdx = 0; texNdx < (int)prevProgCtxTextures.size(); texNdx++)
+				{
+					if (prevProgCtxTextures[texNdx]->getFormat() == format)
+					{
+						dummyTex = prevProgCtxTextures[texNdx];
+						break;
+					}
+				}
+			}
+
+			if (!dummyTex)
+				dummyTex = SharedPtr<TextureLevel>(new TextureLevel(format));
+
+			if (dummyTex->getWidth() < spec.width || dummyTex->getHeight() < spec.height)
+			{
+				dummyTex->setSize(spec.width, spec.height);
+				tcu::fillWithComponentGradients(dummyTex->getAccess(), spec.minValue, spec.maxValue);
+			}
+
+			progRes.dummyTextures.push_back(dummyTex);
+		}
+	}
+
+	m_vertexIndices.clear();
+	for (int i = 0; i < m_numVerticesPerDrawCall; i++)
+		m_vertexIndices.push_back((deUint16)i);
+	m_rnd.shuffle(m_vertexIndices.begin(), m_vertexIndices.end());
+
+	DE_ASSERT(!m_programs && !m_buffers && !m_textures);
+	m_programs = new GLObjectManager<Program>;
+	m_buffers = new GLObjectManager<Buffer>;
+	m_textures = new GLObjectManager<Texture>;
+
+	m_currentIteration = 0;
+
+	{
+		TestLog& log = m_testCtx.getLog();
+
+		log << TestLog::Message << "Number of iterations: "										<< (m_numIterations > 0 ? toString(m_numIterations) : "infinite")				<< TestLog::EndMessage
+			<< TestLog::Message << "Number of draw calls per iteration: "						<< m_numDrawCallsPerIteration													<< TestLog::EndMessage
+			<< TestLog::Message << "Number of triangles per draw call: "						<< m_numTrianglesPerDrawCall													<< TestLog::EndMessage
+			<< TestLog::Message << "Using triangle strips"																														<< TestLog::EndMessage
+			<< TestLog::Message << "Approximate texture memory usage limit: "					<< de::floatToString((float)m_maxTexMemoryUsageBytes / Mi, 2) << " MiB"			<< TestLog::EndMessage
+			<< TestLog::Message << "Approximate buffer memory usage limit: "					<< de::floatToString((float)m_maxBufMemoryUsageBytes / Mi, 2) << " MiB"			<< TestLog::EndMessage
+			<< TestLog::Message << "Default vertex attribute data buffer usage parameter: "		<< glu::getUsageName(m_attrBufferUsage)											<< TestLog::EndMessage
+			<< TestLog::Message << "Default vertex index data buffer usage parameter: "			<< glu::getUsageName(m_indexBufferUsage)										<< TestLog::EndMessage
+
+			<< TestLog::Section("ProbabilityParams", "Per-iteration probability parameters")
+			<< TestLog::Message << "Program re-build: "															<< probabilityStr(m_probabilities.rebuildProgram)				<< TestLog::EndMessage
+			<< TestLog::Message << "Texture re-upload: "														<< probabilityStr(m_probabilities.reuploadTexture)				<< TestLog::EndMessage
+			<< TestLog::Message << "Buffer re-upload: "															<< probabilityStr(m_probabilities.reuploadBuffer)				<< TestLog::EndMessage
+			<< TestLog::Message << "Use glTexImage* instead of glTexSubImage* when uploading texture: "			<< probabilityStr(m_probabilities.reuploadWithTexImage)			<< TestLog::EndMessage
+			<< TestLog::Message << "Use glBufferData* instead of glBufferSubData* when uploading buffer: "		<< probabilityStr(m_probabilities.reuploadWithBufferData)		<< TestLog::EndMessage
+			<< TestLog::Message << "Delete texture after using it, even if could re-use it: "					<< probabilityStr(m_probabilities.deleteTexture)				<< TestLog::EndMessage
+			<< TestLog::Message << "Delete buffer after using it, even if could re-use it: "					<< probabilityStr(m_probabilities.deleteBuffer)					<< TestLog::EndMessage
+			<< TestLog::Message << "Don't re-use texture, and only delete if memory limit is hit: "				<< probabilityStr(m_probabilities.wastefulTextureMemoryUsage)	<< TestLog::EndMessage
+			<< TestLog::Message << "Don't re-use buffer, and only delete if memory limit is hit: "				<< probabilityStr(m_probabilities.wastefulBufferMemoryUsage)	<< TestLog::EndMessage
+			<< TestLog::Message << "Use client memory (instead of GL buffers) for vertex attribute data: "		<< probabilityStr(m_probabilities.clientMemoryAttributeData)	<< TestLog::EndMessage
+			<< TestLog::Message << "Use client memory (instead of GL buffers) for vertex index data: "			<< probabilityStr(m_probabilities.clientMemoryIndexData)		<< TestLog::EndMessage
+			<< TestLog::Message << "Use random target parameter when uploading buffer data: "					<< probabilityStr(m_probabilities.randomBufferUploadTarget)		<< TestLog::EndMessage
+			<< TestLog::Message << "Use random usage parameter when uploading buffer data: "					<< probabilityStr(m_probabilities.randomBufferUsage)			<< TestLog::EndMessage
+			<< TestLog::Message << "Use glDrawArrays instead of glDrawElements: "								<< probabilityStr(m_probabilities.useDrawArrays)				<< TestLog::EndMessage
+			<< TestLog::Message << "Use separate buffers for each attribute, instead of one array for all: "	<< probabilityStr(m_probabilities.separateAttributeBuffers)		<< TestLog::EndMessage
+			<< TestLog::EndSection
+			<< TestLog::Message << "Using " << m_programContexts.size() << " program(s)" << TestLog::EndMessage;
+
+		bool anyProgramsFailed = false;
+		for (int progCtxNdx = 0; progCtxNdx < (int)m_programContexts.size(); progCtxNdx++)
+		{
+			const ProgramContext& progCtx = m_programContexts[progCtxNdx];
+			glu::ShaderProgram prog(m_renderCtx, glu::makeVtxFragSources(mangleShaderNames(progCtx.vertexSource, ""), mangleShaderNames(progCtx.fragmentSource, "")));
+			log << TestLog::Section("ShaderProgram" + toString(progCtxNdx), "Shader program " + toString(progCtxNdx)) << prog << TestLog::EndSection;
+			if (!prog.isOk())
+				anyProgramsFailed = true;
+		}
+
+		if (anyProgramsFailed)
+			throw tcu::TestError("One or more shader programs failed to compile");
+	}
+
+	DE_ASSERT(!m_debugInfoRenderer);
+	if (m_showDebugInfo)
+		m_debugInfoRenderer = new DebugInfoRenderer(m_renderCtx);
+}
+
+void LongStressCase::deinit (void)
+{
+	m_programResources.clear();
+
+	delete m_programs;
+	m_programs = DE_NULL;
+
+	delete m_buffers;
+	m_buffers = DE_NULL;
+
+	delete m_textures;
+	m_textures = DE_NULL;
+
+	delete m_debugInfoRenderer;
+	m_debugInfoRenderer = DE_NULL;
+}
+
+LongStressCase::IterateResult LongStressCase::iterate (void)
+{
+	TestLog&					log							= m_testCtx.getLog();
+	const int					renderWidth					= m_renderCtx.getRenderTarget().getWidth();
+	const int					renderHeight				= m_renderCtx.getRenderTarget().getHeight();
+	const bool					useClientMemoryIndexData	= m_rnd.getFloat() < m_probabilities.clientMemoryIndexData;
+	const bool					useDrawArrays				= m_rnd.getFloat() < m_probabilities.useDrawArrays;
+	const bool					separateAttributeBuffers	= m_rnd.getFloat() < m_probabilities.separateAttributeBuffers;
+	const int					progContextNdx				= m_rnd.getInt(0, (int)m_programContexts.size()-1);
+	const ProgramContext&		programContext				= m_programContexts[progContextNdx];
+	ProgramResources&			programResources			= m_programResources[progContextNdx];
+	const string				programName					= "prog" + toString(progContextNdx);
+	const string				textureNamePrefix			= "tex" + toString(progContextNdx) + "_";
+	const string				unitedAttrBufferNamePrefix	= "attrBuf" + toString(progContextNdx) + "_";
+	const string				indexBufferName				= "indexBuf" + toString(progContextNdx);
+	const string				separateAttrBufNamePrefix	= "attrBuf" + toString(progContextNdx) + "_";
+
+	if (m_currentIteration == 0)
+		m_lastLogTime = m_startTimeSeconds = deGetTime();
+
+	// Make or re-compile programs.
+	{
+		const bool hadProgram = m_programs->has(programName);
+
+		if (!hadProgram)
+			m_programs->make(programName);
+
+		Program& prog = m_programs->get(programName);
+
+		if (!hadProgram || m_rnd.getFloat() < m_probabilities.rebuildProgram)
+		{
+			programResources.shaderNameManglingSuffix = toString((deUint16)deUint64Hash((deUint64)m_currentIteration ^ deGetTime()));
+
+			prog.setSources(mangleShaderNames(programContext.vertexSource, programResources.shaderNameManglingSuffix),
+							mangleShaderNames(programContext.fragmentSource, programResources.shaderNameManglingSuffix));
+
+			prog.build(log);
+		}
+
+		prog.use();
+	}
+
+	Program& program = m_programs->get(programName);
+
+	// Make or re-upload textures.
+
+	for (int texNdx = 0; texNdx < (int)programContext.textureSpecs.size(); texNdx++)
+	{
+		const string		texName		= textureNamePrefix + toString(texNdx);
+		const bool			hadTexture	= m_textures->has(texName);
+		const TextureSpec&	spec		= programContext.textureSpecs[texNdx];
+
+		if (!hadTexture)
+			m_textures->make(texName, spec.textureType);
+
+		if (!hadTexture || m_rnd.getFloat() < m_probabilities.reuploadTexture)
+		{
+			Texture& texture = m_textures->get(texName);
+
+			m_textures->removeGarbageUntilUnder(m_maxTexMemoryUsageBytes - texture.getApproxMemUsageDiff(spec.width, spec.height, spec.internalFormat, spec.useMipmap), m_rnd);
+
+			if (!hadTexture || m_rnd.getFloat() < m_probabilities.reuploadWithTexImage)
+				texture.setData(programResources.dummyTextures[texNdx]->getAccess(), spec.width, spec.height, spec.internalFormat, spec.useMipmap);
+			else
+				texture.setSubData(programResources.dummyTextures[texNdx]->getAccess(), 0, 0, spec.width, spec.height);
+
+			texture.toUnit(0);
+			texture.setWrap(spec.sWrap, spec.tWrap);
+			texture.setFilter(spec.minFilter, spec.magFilter);
+		}
+	}
+
+	// Bind textures to units, in random order (because when multiple texture specs have same unit, we want to pick one randomly).
+
+	{
+		vector<int> texSpecIndices(programContext.textureSpecs.size());
+		for (int i = 0; i < (int)texSpecIndices.size(); i++)
+			texSpecIndices[i] = i;
+		m_rnd.shuffle(texSpecIndices.begin(), texSpecIndices.end());
+		for (int i = 0; i < (int)texSpecIndices.size(); i++)
+			m_textures->get(textureNamePrefix + toString(texSpecIndices[i])).toUnit(programContext.textureSpecs[i].textureUnit);
+	}
+
+	// Make or re-upload index buffer.
+
+	if (!useDrawArrays)
+	{
+		m_rnd.shuffle(m_vertexIndices.begin(), m_vertexIndices.end());
+
+		if (!useClientMemoryIndexData)
+		{
+			const bool hadIndexBuffer = m_buffers->has(indexBufferName);
+
+			if (!hadIndexBuffer)
+				m_buffers->make(indexBufferName);
+
+			Buffer& indexBuf = m_buffers->get(indexBufferName);
+
+			if (!hadIndexBuffer || m_rnd.getFloat() < m_probabilities.reuploadBuffer)
+			{
+				m_buffers->removeGarbageUntilUnder(m_maxBufMemoryUsageBytes - indexBuf.getApproxMemUsageDiff(m_vertexIndices), m_rnd);
+				const deUint32 target = m_rnd.getFloat() < m_probabilities.randomBufferUploadTarget ? randomBufferTarget(m_rnd, m_isGLES3) : GL_ELEMENT_ARRAY_BUFFER;
+
+				if (!hadIndexBuffer || m_rnd.getFloat() < m_probabilities.reuploadWithBufferData)
+					indexBuf.setData(m_vertexIndices, target, m_rnd.getFloat() < m_probabilities.randomBufferUsage ? randomBufferUsage(m_rnd, m_isGLES3) : m_indexBufferUsage);
+				else
+					indexBuf.setSubData(m_vertexIndices, 0, m_numVerticesPerDrawCall, target);
+			}
+		}
+	}
+
+	// Set vertex attributes. If not using client-memory data, make or re-upload attribute buffers.
+
+	generateAttribs(programResources.attrDataBuf, programResources.attrDataOffsets, programResources.attrDataSizes,
+					programContext.attributes, programContext.positionAttrName, m_numVerticesPerDrawCall, m_rnd);
+
+	if (!(m_rnd.getFloat() < m_probabilities.clientMemoryAttributeData))
+	{
+		if (separateAttributeBuffers)
+		{
+			for (int attrNdx = 0; attrNdx < (int)programContext.attributes.size(); attrNdx++)
+			{
+				const int usedRedundantBufferNdx = m_rnd.getInt(0, m_redundantBufferFactor-1);
+
+				for (int redundantBufferNdx = 0; redundantBufferNdx < m_redundantBufferFactor; redundantBufferNdx++)
+				{
+					const string	curAttrBufName		= separateAttrBufNamePrefix + toString(attrNdx) + "_" + toString(redundantBufferNdx);
+					const bool		hadCurAttrBuffer	= m_buffers->has(curAttrBufName);
+
+					if (!hadCurAttrBuffer)
+						m_buffers->make(curAttrBufName);
+
+					Buffer& curAttrBuf = m_buffers->get(curAttrBufName);
+
+					if (!hadCurAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadBuffer)
+					{
+						m_buffers->removeGarbageUntilUnder(m_maxBufMemoryUsageBytes - curAttrBuf.getApproxMemUsageDiff(programResources.attrDataSizes[attrNdx]), m_rnd);
+						const deUint32 target = m_rnd.getFloat() < m_probabilities.randomBufferUploadTarget ? randomBufferTarget(m_rnd, m_isGLES3) : GL_ARRAY_BUFFER;
+
+						if (!hadCurAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadWithBufferData)
+							curAttrBuf.setData(&programResources.attrDataBuf[programResources.attrDataOffsets[attrNdx]], programResources.attrDataSizes[attrNdx], target,
+											   m_rnd.getFloat() < m_probabilities.randomBufferUsage ? randomBufferUsage(m_rnd, m_isGLES3) : m_attrBufferUsage);
+						else
+							curAttrBuf.setSubData(&programResources.attrDataBuf[programResources.attrDataOffsets[attrNdx]], 0, programResources.attrDataSizes[attrNdx], target);
+					}
+
+					if (redundantBufferNdx == usedRedundantBufferNdx)
+						program.setAttribute(curAttrBuf, 0, programContext.attributes[attrNdx], programResources.shaderNameManglingSuffix);
+				}
+			}
+		}
+		else
+		{
+			const int usedRedundantBufferNdx = m_rnd.getInt(0, m_redundantBufferFactor-1);
+
+			for (int redundantBufferNdx = 0; redundantBufferNdx < m_redundantBufferFactor; redundantBufferNdx++)
+			{
+				const string	attrBufName		= unitedAttrBufferNamePrefix + toString(redundantBufferNdx);
+				const bool		hadAttrBuffer	= m_buffers->has(attrBufName);
+
+				if (!hadAttrBuffer)
+					m_buffers->make(attrBufName);
+
+				Buffer& attrBuf = m_buffers->get(attrBufName);
+
+				if (!hadAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadBuffer)
+				{
+					m_buffers->removeGarbageUntilUnder(m_maxBufMemoryUsageBytes - attrBuf.getApproxMemUsageDiff(programResources.attrDataBuf), m_rnd);
+					const deUint32 target = m_rnd.getFloat() < m_probabilities.randomBufferUploadTarget ? randomBufferTarget(m_rnd, m_isGLES3) : GL_ARRAY_BUFFER;
+
+					if (!hadAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadWithBufferData)
+						attrBuf.setData(programResources.attrDataBuf, target, m_rnd.getFloat() < m_probabilities.randomBufferUsage ? randomBufferUsage(m_rnd, m_isGLES3) : m_attrBufferUsage);
+					else
+						attrBuf.setSubData(programResources.attrDataBuf, 0, (int)programResources.attrDataBuf.size(), target);
+				}
+
+				if (redundantBufferNdx == usedRedundantBufferNdx)
+				{
+					for (int i = 0; i < (int)programContext.attributes.size(); i++)
+						program.setAttribute(attrBuf, programResources.attrDataOffsets[i], programContext.attributes[i], programResources.shaderNameManglingSuffix);
+				}
+			}
+		}
+	}
+	else
+	{
+		for (int i = 0; i < (int)programContext.attributes.size(); i++)
+			program.setAttributeClientMem(&programResources.attrDataBuf[programResources.attrDataOffsets[i]], programContext.attributes[i], programResources.shaderNameManglingSuffix);
+	}
+
+	// Draw.
+
+	glViewport(0, 0, renderWidth, renderHeight);
+
+	glClearDepthf(1.0f);
+	glClear(GL_DEPTH_BUFFER_BIT);
+	glEnable(GL_DEPTH_TEST);
+
+	for (int i = 0; i < m_numDrawCallsPerIteration; i++)
+	{
+		program.use();
+		program.setRandomUniforms(programContext.uniforms, programResources.shaderNameManglingSuffix, m_rnd);
+
+		if (useDrawArrays)
+			glDrawArrays(GL_TRIANGLE_STRIP, 0, m_numVerticesPerDrawCall);
+		else
+		{
+			if (useClientMemoryIndexData)
+			{
+				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+				glDrawElements(GL_TRIANGLE_STRIP, m_numVerticesPerDrawCall, GL_UNSIGNED_SHORT, &m_vertexIndices[0]);
+			}
+			else
+			{
+				m_buffers->get(indexBufferName).bind(GL_ELEMENT_ARRAY_BUFFER);
+				glDrawElements(GL_TRIANGLE_STRIP, m_numVerticesPerDrawCall, GL_UNSIGNED_SHORT, DE_NULL);
+			}
+		}
+	}
+
+	for(int i = 0; i < (int)programContext.attributes.size(); i++)
+		program.disableAttributeArray(programContext.attributes[i], programResources.shaderNameManglingSuffix);
+
+	if (m_showDebugInfo)
+		m_debugInfoRenderer->drawInfo(deGetTime()-m_startTimeSeconds, m_textures->computeApproxMemUsage(), m_maxTexMemoryUsageBytes, m_buffers->computeApproxMemUsage(), m_maxBufMemoryUsageBytes, m_currentIteration);
+
+	if (m_currentIteration > 0)
+	{
+		// Log if a certain amount of time has passed since last log entry (or if this is the last iteration).
+
+		const deUint64	loggingIntervalSeconds	= 10;
+		const deUint64	time					= deGetTime();
+		const deUint64	timeDiff				= time - m_lastLogTime;
+		const int		iterDiff				= m_currentIteration - m_lastLogIteration;
+
+		if (timeDiff >= loggingIntervalSeconds || m_currentIteration == m_numIterations-1)
+		{
+			log << TestLog::Section("LogEntry" + toString(m_currentLogEntryNdx), "Log entry " + toString(m_currentLogEntryNdx))
+				<< TestLog::Message << "Time elapsed: " << getTimeStr(time - m_startTimeSeconds) << TestLog::EndMessage
+				<< TestLog::Message << "Frame number: " << m_currentIteration << TestLog::EndMessage
+				<< TestLog::Message << "Time since last log entry: " << timeDiff << "s" << TestLog::EndMessage
+				<< TestLog::Message << "Frames since last log entry: " << iterDiff << TestLog::EndMessage
+				<< TestLog::Message << "Average frame time since last log entry: " << de::floatToString((float)timeDiff / iterDiff, 2) << "s" << TestLog::EndMessage
+				<< TestLog::Message << "Approximate texture memory usage: "
+									<< de::floatToString((float)m_textures->computeApproxMemUsage() / Mi, 2) << " MiB / "
+									<< de::floatToString((float)m_maxTexMemoryUsageBytes / Mi, 2) << " MiB"
+									<< TestLog::EndMessage
+				<< TestLog::Message << "Approximate buffer memory usage: "
+										<< de::floatToString((float)m_buffers->computeApproxMemUsage() / Mi, 2) << " MiB / "
+										<< de::floatToString((float)m_maxBufMemoryUsageBytes / Mi, 2) << " MiB"
+										<< TestLog::EndMessage
+				<< TestLog::EndSection;
+
+			m_lastLogTime		= time;
+			m_lastLogIteration	= m_currentIteration;
+			m_currentLogEntryNdx++;
+		}
+	}
+
+	// Possibly remove or set-as-garbage some objects, depending on given probabilities.
+
+	for (int texNdx = 0; texNdx < (int)programContext.textureSpecs.size(); texNdx++)
+	{
+		const string texName = textureNamePrefix + toString(texNdx);
+		if (m_rnd.getFloat() < m_probabilities.deleteTexture)
+			m_textures->remove(texName);
+		else if (m_rnd.getFloat() < m_probabilities.wastefulTextureMemoryUsage)
+			m_textures->markAsGarbage(texName);
+
+	}
+
+	if (m_buffers->has(indexBufferName))
+	{
+		if (m_rnd.getFloat() < m_probabilities.deleteBuffer)
+			m_buffers->remove(indexBufferName);
+		else if (m_rnd.getFloat() < m_probabilities.wastefulBufferMemoryUsage)
+			m_buffers->markAsGarbage(indexBufferName);
+
+	}
+
+	if (separateAttributeBuffers)
+	{
+		for (int attrNdx = 0; attrNdx < (int)programContext.attributes.size(); attrNdx++)
+		{
+			const string curAttrBufNamePrefix = separateAttrBufNamePrefix + toString(attrNdx) + "_";
+
+			if (m_buffers->has(curAttrBufNamePrefix + "0"))
+			{
+				if (m_rnd.getFloat() < m_probabilities.deleteBuffer)
+				{
+					for (int i = 0; i < m_redundantBufferFactor; i++)
+						m_buffers->remove(curAttrBufNamePrefix + toString(i));
+				}
+				else if (m_rnd.getFloat() < m_probabilities.wastefulBufferMemoryUsage)
+				{
+					for (int i = 0; i < m_redundantBufferFactor; i++)
+						m_buffers->markAsGarbage(curAttrBufNamePrefix + toString(i));
+				}
+			}
+		}
+	}
+	else
+	{
+		if (m_buffers->has(unitedAttrBufferNamePrefix + "0"))
+		{
+			if (m_rnd.getFloat() < m_probabilities.deleteBuffer)
+			{
+				for (int i = 0; i < m_redundantBufferFactor; i++)
+					m_buffers->remove(unitedAttrBufferNamePrefix + toString(i));
+			}
+			else if (m_rnd.getFloat() < m_probabilities.wastefulBufferMemoryUsage)
+			{
+				for (int i = 0; i < m_redundantBufferFactor; i++)
+					m_buffers->markAsGarbage(unitedAttrBufferNamePrefix + toString(i));
+			}
+		}
+	}
+
+	GLU_CHECK_MSG("End of LongStressCase::iterate()");
+
+	m_currentIteration++;
+	if (m_currentIteration == m_numIterations)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Passed");
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsLongStressCase.hpp b/modules/glshared/glsLongStressCase.hpp
new file mode 100644
index 0000000..9df6991
--- /dev/null
+++ b/modules/glshared/glsLongStressCase.hpp
@@ -0,0 +1,347 @@
+#ifndef _GLSLONGSTRESSCASE_HPP
+#define _GLSLONGSTRESSCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Parametrized, long-running stress case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuTexture.hpp"
+#include "tcuMatrix.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderUtil.hpp"
+#include "glsTextureTestUtil.hpp"
+#include "deRandom.hpp"
+#include "deSharedPtr.hpp"
+
+#include <string>
+#include <vector>
+#include <map>
+
+namespace deqp
+{
+namespace gls
+{
+
+namespace LongStressCaseInternal
+{
+
+template <typename T> class GLObjectManager;
+class Program;
+class Buffer;
+class Texture;
+class DebugInfoRenderer;
+
+}
+
+struct VarSpec
+{
+	union Value
+	{
+		float	f[4*4]; // \note Matrices are stored in column major order.
+		int		i[4];
+	};
+
+	std::string		name;
+	glu::DataType	type;
+	Value			minValue;
+	Value			maxValue;
+
+	template <typename T>
+	VarSpec (const std::string& name_, const T& minValue_, const T& maxValue_)	: name(name_) { set(minValue_, maxValue_); }
+
+	template <typename T>
+	VarSpec (const std::string& name_, const T& value)							: name(name_) { set(value, value); }
+
+	void set (float minValue_, float maxValue_)
+	{
+		type			= glu::TYPE_FLOAT;
+		minValue.f[0]	= minValue_;
+		maxValue.f[0]	= maxValue_;
+	}
+
+	template <int ValSize>
+	void set (const tcu::Vector<float, ValSize>& minValue_, const tcu::Vector<float, ValSize>& maxValue_)
+	{
+		type = glu::getDataTypeFloatVec(ValSize);
+		vecToArr(minValue_, minValue.f);
+		vecToArr(maxValue_, maxValue.f);
+	}
+
+	template <int ValRows, int ValCols>
+	void set (const tcu::Matrix<float, ValRows, ValCols>& minValue_, const tcu::Matrix<float, ValRows, ValCols>& maxValue_)
+	{
+		type = glu::getDataTypeMatrix(ValCols, ValRows);
+		matToArr(minValue_, minValue.f);
+		matToArr(maxValue_, maxValue.f);
+	}
+
+	void set (int minValue_, int maxValue_)
+	{
+		type			= glu::TYPE_INT;
+		minValue.i[0]	= minValue_;
+		maxValue.i[0]	= maxValue_;
+	}
+
+	template <int ValSize>
+	void set (const tcu::Vector<int, ValSize>& minValue_, const tcu::Vector<int, ValSize>& maxValue_)
+	{
+		type = glu::getDataTypeVector(glu::TYPE_INT, ValSize);
+		vecToArr(minValue_, minValue.i);
+		vecToArr(maxValue_, maxValue.i);
+	}
+
+private:
+	template <typename T, int SrcSize, int DstSize>
+	static inline void vecToArr (const tcu::Vector<T, SrcSize>& src, T (&dst)[DstSize])
+	{
+		DE_STATIC_ASSERT(DstSize >= SrcSize);
+		for (int i = 0; i < SrcSize; i++)
+			dst[i] = src[i];
+	}
+
+	template <int ValRows, int ValCols, int DstSize>
+	static inline void matToArr (const tcu::Matrix<float, ValRows, ValCols>& src, float (&dst)[DstSize])
+	{
+		DE_STATIC_ASSERT(DstSize >= ValRows*ValCols);
+		tcu::Array<float, ValRows*ValCols> data = src.getColumnMajorData();
+		for (int i = 0; i < ValRows*ValCols; i++)
+			dst[i] = data[i];
+	}
+};
+
+struct TextureSpec
+{
+	gls::TextureTestUtil::TextureType	textureType;
+	deUint32							textureUnit;
+	int									width;
+	int									height;
+	deUint32							format;
+	deUint32							dataType;
+	deUint32							internalFormat;
+	bool								useMipmap;
+	deUint32							minFilter;
+	deUint32							magFilter;
+	deUint32							sWrap;
+	deUint32							tWrap;
+	tcu::Vec4							minValue;
+	tcu::Vec4							maxValue;
+
+	TextureSpec (const gls::TextureTestUtil::TextureType	texType,
+				 const deUint32								unit,
+				 const int									width_,
+				 const int									height_,
+				 const deUint32								format_,
+				 const deUint32								dataType_,
+				 const deUint32								internalFormat_,
+				 const bool									useMipmap_,
+				 const deUint32								minFilter_,
+				 const deUint32								magFilter_,
+				 const deUint32								sWrap_,
+				 const deUint32								tWrap_,
+				 const tcu::Vec4&							minValue_,
+				 const tcu::Vec4&							maxValue_)
+		: textureType		(texType)
+		, textureUnit		(unit)
+		, width				(width_)
+		, height			(height_)
+		, format			(format_)
+		, dataType			(dataType_)
+		, internalFormat	(internalFormat_)
+		, useMipmap			(useMipmap_)
+		, minFilter			(minFilter_)
+		, magFilter			(magFilter_)
+		, sWrap				(sWrap_)
+		, tWrap				(tWrap_)
+		, minValue			(minValue_)
+		, maxValue			(maxValue_)
+	{
+	}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Struct for a shader program sources and related data
+ *
+ * A ProgramContext holds a program's vertex and fragment shader sources
+ * as well as specifications of its attributes, uniforms, and textures.
+ * When given to a StressCase, the string ${NS} is replaced by a magic
+ * number that varies between different compilations of the same program;
+ * the same replacement is done in attributes' and uniforms' names. This
+ * can be used to avoid shader caching by the GL, by e.g. suffixing each
+ * attribute, uniform and varying name with ${NS} in the shader source.
+ *//*--------------------------------------------------------------------*/
+struct ProgramContext
+{
+	std::string							vertexSource;
+	std::string							fragmentSource;
+	std::vector<VarSpec>				attributes;
+	std::vector<VarSpec>				uniforms;
+
+	std::vector<TextureSpec>			textureSpecs;		//!< \note If multiple textures have same unit, one of them is picked randomly.
+
+	std::string							positionAttrName;	//!< \note Position attribute may get a bit more careful handling than just complete random.
+
+	ProgramContext (const char* const vtxShaderSource_,
+					const char* const fragShaderSource_,
+					const char* const positionAttrName_)
+		: vertexSource		(vtxShaderSource_)
+		, fragmentSource	(fragShaderSource_)
+		, positionAttrName	(positionAttrName_)
+	{
+	}
+};
+
+class LongStressCase : public tcu::TestCase
+{
+public:
+	//! Probabilities for actions that may be taken on each iteration. \note The texture and buffer specific actions are randomized per texture or buffer.
+	struct FeatureProbabilities
+	{
+		float rebuildProgram;				//!< Rebuild program, with variable name-mangling.
+		float reuploadTexture;				//!< Reupload texture, even if it already exists and has been uploaded.
+		float reuploadBuffer;				//!< Reupload buffer, even if it already exists and has been uploaded.
+		float reuploadWithTexImage;			//!< Use glTexImage*() when re-uploading texture, not glTexSubImage*().
+		float reuploadWithBufferData;		//!< Use glBufferData() when re-uploading buffer, not glBufferSubData().
+		float deleteTexture;				//!< Delete texture at end of iteration, even if we could re-use it.
+		float deleteBuffer;					//!< Delete buffer at end of iteration, even if we could re-use it.
+		float wastefulTextureMemoryUsage;	//!< Don't re-use a texture, and don't delete it until given memory limit is hit.
+		float wastefulBufferMemoryUsage;	//!< Don't re-use a buffer, and don't delete it until given memory limit is hit.
+		float clientMemoryAttributeData;	//!< Use client memory for vertex attribute data when drawing (instead of GL buffers).
+		float clientMemoryIndexData;		//!< Use client memory for vertex indices when drawing (instead of GL buffers).
+		float randomBufferUploadTarget;		//!< Use a random target when setting buffer data (i.e. not necessarily the one it'll be ultimately bound to).
+		float randomBufferUsage;			//!< Use a random buffer usage parameter with glBufferData(), instead of the ones specified as params for the case.
+		float useDrawArrays;				//!< Use glDrawArrays() instead of glDrawElements().
+		float separateAttributeBuffers;		//!< Give each vertex attribute its own buffer.
+
+		// Named parameter idiom: helpers that can be used when making temporaries, e.g. FeatureProbabilities().pReuploadTexture(1.0f).pReuploadWithTexImage(1.0f)
+		FeatureProbabilities& pRebuildProgram				(const float prob) { rebuildProgram					= prob; return *this; }
+		FeatureProbabilities& pReuploadTexture				(const float prob) { reuploadTexture				= prob; return *this; }
+		FeatureProbabilities& pReuploadBuffer				(const float prob) { reuploadBuffer					= prob; return *this; }
+		FeatureProbabilities& pReuploadWithTexImage			(const float prob) { reuploadWithTexImage			= prob; return *this; }
+		FeatureProbabilities& pReuploadWithBufferData		(const float prob) { reuploadWithBufferData			= prob; return *this; }
+		FeatureProbabilities& pDeleteTexture				(const float prob) { deleteTexture					= prob; return *this; }
+		FeatureProbabilities& pDeleteBuffer					(const float prob) { deleteBuffer					= prob; return *this; }
+		FeatureProbabilities& pWastefulTextureMemoryUsage	(const float prob) { wastefulTextureMemoryUsage		= prob; return *this; }
+		FeatureProbabilities& pWastefulBufferMemoryUsage	(const float prob) { wastefulBufferMemoryUsage		= prob; return *this; }
+		FeatureProbabilities& pClientMemoryAttributeData	(const float prob) { clientMemoryAttributeData		= prob; return *this; }
+		FeatureProbabilities& pClientMemoryIndexData		(const float prob) { clientMemoryIndexData			= prob; return *this; }
+		FeatureProbabilities& pRandomBufferUploadTarget		(const float prob) { randomBufferUploadTarget		= prob; return *this; }
+		FeatureProbabilities& pRandomBufferUsage			(const float prob) { randomBufferUsage				= prob; return *this; }
+		FeatureProbabilities& pUseDrawArrays				(const float prob) { useDrawArrays					= prob; return *this; }
+		FeatureProbabilities& pSeparateAttribBuffers		(const float prob) { separateAttributeBuffers		= prob; return *this; }
+
+		FeatureProbabilities (void)
+			: rebuildProgram				(0.0f)
+			, reuploadTexture				(0.0f)
+			, reuploadBuffer				(0.0f)
+			, reuploadWithTexImage			(0.0f)
+			, reuploadWithBufferData		(0.0f)
+			, deleteTexture					(0.0f)
+			, deleteBuffer					(0.0f)
+			, wastefulTextureMemoryUsage	(0.0f)
+			, wastefulBufferMemoryUsage		(0.0f)
+			, clientMemoryAttributeData		(0.0f)
+			, clientMemoryIndexData			(0.0f)
+			, randomBufferUploadTarget		(0.0f)
+			, randomBufferUsage				(0.0f)
+			, useDrawArrays					(0.0f)
+			, separateAttributeBuffers		(0.0f)
+		{
+		}
+	};
+
+															LongStressCase						(tcu::TestContext&						testCtx,
+																								 const glu::RenderContext&				renderCtx,
+																								 const char*							name,
+																								 const char*							desc,
+																								 int									maxTexMemoryUsageBytes, //!< Approximate upper bound on GL texture memory usage.
+																								 int									maxBufMemoryUsageBytes, //!< Approximate upper bound on GL buffer memory usage.
+																								 int									numDrawCallsPerIteration,
+																								 int									numTrianglesPerDrawCall,
+																								 const std::vector<ProgramContext>&		programContexts,
+																								 const FeatureProbabilities&			probabilities,
+																								 deUint32								indexBufferUsage,
+																								 deUint32								attrBufferUsage,
+																								 int									redundantBufferFactor = 1,
+																								 bool									showDebugInfo = false);
+
+															~LongStressCase						(void);
+
+	void													init								(void);
+	void													deinit								(void);
+
+	IterateResult											iterate								(void);
+
+private:
+															LongStressCase						(const LongStressCase&);
+	LongStressCase&											operator=							(const LongStressCase&);
+
+	const glu::RenderContext&								m_renderCtx;
+	const int												m_maxTexMemoryUsageBytes;
+	const int												m_maxBufMemoryUsageBytes;
+	const int												m_numDrawCallsPerIteration;
+	const int												m_numTrianglesPerDrawCall;
+	const int												m_numVerticesPerDrawCall;
+	const std::vector<ProgramContext>						m_programContexts;
+	const FeatureProbabilities								m_probabilities;
+	const deUint32											m_indexBufferUsage;
+	const deUint32											m_attrBufferUsage;
+	const int												m_redundantBufferFactor; //!< By what factor we allocate redundant buffers. Default is 1, i.e. no redundancy.
+	const bool												m_showDebugInfo;
+
+	const int												m_numIterations;
+	const bool												m_isGLES3;
+
+	int														m_currentIteration;
+	deUint64												m_startTimeSeconds; //!< Set at beginning of first iteration.
+	deUint64												m_lastLogTime;
+	int														m_lastLogIteration;
+	int														m_currentLogEntryNdx;
+
+	de::Random												m_rnd;
+	LongStressCaseInternal::GLObjectManager<
+		LongStressCaseInternal::Program>*					m_programs;
+	LongStressCaseInternal::GLObjectManager<
+		LongStressCaseInternal::Buffer>*					m_buffers;
+	LongStressCaseInternal::GLObjectManager<
+		LongStressCaseInternal::Texture>*					m_textures;
+	std::vector<deUint16>									m_vertexIndices;
+
+	struct ProgramResources
+	{
+		std::vector<deUint8>							attrDataBuf;
+		std::vector<int>								attrDataOffsets;
+		std::vector<int>								attrDataSizes;
+		std::vector<de::SharedPtr<tcu::TextureLevel> >	dummyTextures;
+		std::string										shaderNameManglingSuffix;
+	};
+
+	std::vector<ProgramResources>							m_programResources;
+
+	LongStressCaseInternal::DebugInfoRenderer*				m_debugInfoRenderer;
+};
+
+
+} // gls
+} // deqp
+
+#endif // _GLSLONGSTRESSCASE_HPP
diff --git a/modules/glshared/glsLongStressTestUtil.cpp b/modules/glshared/glsLongStressTestUtil.cpp
new file mode 100644
index 0000000..39af38e
--- /dev/null
+++ b/modules/glshared/glsLongStressTestUtil.cpp
@@ -0,0 +1,592 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Utilities for tests with gls::LongStressCase.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsLongStressTestUtil.hpp"
+#include "tcuStringTemplate.hpp"
+#include "deStringUtil.hpp"
+
+#include "glw.h"
+
+using tcu::Vec2;
+using tcu::Vec3;
+using tcu::Vec4;
+using tcu::Mat2;
+using tcu::Mat3;
+using tcu::Mat4;
+using de::toString;
+using std::map;
+using std::string;
+
+namespace deqp
+{
+namespace gls
+{
+namespace LongStressTestUtil
+{
+
+template <int Size>
+static tcu::Matrix<float, Size, Size> translationMat (const float v)
+{
+	tcu::Matrix<float, Size, Size>	res(1.0f);
+	tcu::Vector<float, Size>		col(v);
+	col[Size-1] = 1.0f;
+	res.setColumn(Size-1, col);
+	return res;
+}
+
+// Specializes certain template patterns in templ for GLSL version m_glslVersion; params in additionalParams (optional) are also included in the substitution.
+string ProgramLibrary::substitute (const string& templ, const map<string, string>& additionalParams) const
+{
+	const bool				isGLSL3 = m_glslVersion == glu::GLSL_VERSION_300_ES;
+	map<string, string>		params;
+
+	params["FRAG_HEADER"]		= isGLSL3 ? "#version 300 es\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n" : "";
+	params["VTX_HEADER"]		= isGLSL3 ? "#version 300 es\n"	: "";
+	params["VTX_IN"]			= isGLSL3 ? "in"				: "attribute";
+	params["VTX_OUT"]			= isGLSL3 ? "out"				: "varying";
+	params["FRAG_IN"]			= isGLSL3 ? "in"				: "varying";
+	params["FRAG_COLOR"]		= isGLSL3 ? "dEQP_FragColor"	: "gl_FragColor";
+	params["TEXTURE_2D_FUNC"]	= isGLSL3 ? "texture"			: "texture2D";
+	params["NS"]				= "${NS}"; // \note Keep these as-is, they're handled by StressCase.
+
+	params.insert(additionalParams.begin(), additionalParams.end());
+
+	return tcu::StringTemplate(templ.c_str()).specialize(params);
+}
+
+string ProgramLibrary::substitute (const std::string& templ) const
+{
+	return substitute(templ, map<string, string>());
+}
+
+ProgramLibrary::ProgramLibrary (const glu::GLSLVersion glslVersion)
+	: m_glslVersion (glslVersion)
+{
+	DE_ASSERT(glslVersion == glu::GLSL_VERSION_100_ES || glslVersion == glu::GLSL_VERSION_300_ES);
+}
+
+gls::ProgramContext ProgramLibrary::generateBufferContext (const int numDummyAttributes) const
+{
+	static const char* const vertexTemplate =
+		"${VTX_HEADER}"
+		"${VTX_IN} highp vec3 a_position;\n"
+		"${VTX_DUMMY_INPUTS}"
+		"${VTX_OUT} mediump vec4 v_color;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 1.0);\n"
+		"	v_color = ${VTX_COLOR_EXPRESSION};\n"
+		"}\n";
+
+	static const char* const fragmentTemplate =
+		"${FRAG_HEADER}"
+		"${FRAG_IN} mediump vec4 v_color;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	${FRAG_COLOR} = v_color;\n"
+		"}\n";
+
+	map<string, string> firstLevelParams;
+
+	{
+		string vtxDummyInputs;
+		string vtxColorExpr;
+		for (int i = 0; i < numDummyAttributes; i++)
+		{
+			vtxDummyInputs	+= "${VTX_IN} mediump vec4 a_in" + toString(i) + ";\n";
+			vtxColorExpr	+= string() + (i > 0 ? " + " : "") + "a_in" + toString(i);
+		}
+
+		firstLevelParams["VTX_DUMMY_INPUTS"]		= substitute(vtxDummyInputs);
+		firstLevelParams["VTX_COLOR_EXPRESSION"]	= vtxColorExpr;
+	}
+
+	gls::ProgramContext context(substitute(vertexTemplate, firstLevelParams).c_str(), substitute(fragmentTemplate).c_str(), "a_position");
+
+	context.attributes.push_back(gls::VarSpec("a_position", Vec3(-0.1f), Vec3(0.1f)));
+
+	for (int i = 0; i < numDummyAttributes; i++)
+		context.attributes.push_back(gls::VarSpec("a_in" + de::toString(i), Vec4(0.0f), Vec4(1.0f / (float)numDummyAttributes)));
+
+	return context;
+}
+
+gls::ProgramContext ProgramLibrary::generateTextureContext (const int numTextures, const int texWid, const int texHei, const float positionFactor) const
+{
+	static const char* const vertexTemplate =
+		"${VTX_HEADER}"
+		"${VTX_IN} highp vec3 a_position;\n"
+		"${VTX_IN} mediump vec2 a_texCoord;\n"
+		"${VTX_OUT} mediump vec2 v_texCoord;\n"
+		"uniform mediump mat4 u_posTrans;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = u_posTrans * vec4(a_position, 1.0);\n"
+		"	v_texCoord = a_texCoord;\n"
+		"}\n";
+
+	static const char* const fragmentTemplate =
+		"${FRAG_HEADER}"
+		"${FRAG_IN} mediump vec2 v_texCoord;\n"
+		"uniform mediump sampler2D u_sampler;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	${FRAG_COLOR} = ${TEXTURE_2D_FUNC}(u_sampler, v_texCoord);\n"
+		"}\n";
+
+	gls::ProgramContext context(substitute(vertexTemplate).c_str(), substitute(fragmentTemplate).c_str(), "a_position");
+
+	context.attributes.push_back(gls::VarSpec("a_position",		Vec3(-positionFactor),						Vec3(positionFactor)));
+	context.attributes.push_back(gls::VarSpec("a_texCoord",		Vec2(0.0f),									Vec2(1.0f)));
+
+	context.uniforms.push_back(gls::VarSpec("u_sampler",		0));
+	context.uniforms.push_back(gls::VarSpec("u_posTrans",		translationMat<4>(positionFactor-1.0f),		translationMat<4>(1.0f-positionFactor)));
+
+	for (int i = 0; i < numTextures; i++)
+		context.textureSpecs.push_back(gls::TextureSpec(gls::TextureTestUtil::TEXTURETYPE_2D, 0,
+														texWid, texHei, GL_RGBA, GL_UNSIGNED_BYTE, GL_RGBA, true,
+														GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT,
+														Vec4(0.0f), Vec4(1.0f)));
+
+	return context;
+}
+
+gls::ProgramContext ProgramLibrary::generateBufferAndTextureContext (const int numTextures, const int texWid, const int texHei) const
+{
+	static const char* const vertexTemplate =
+		"${VTX_HEADER}"
+		"${VTX_IN} highp vec3 a_position;\n"
+		"${VTX_TEX_COORD_INPUTS}"
+		"${VTX_TEX_COORD_OUTPUTS}"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = vec4(a_position, 1.0);\n"
+		"${VTX_TEX_COORD_WRITES}"
+		"}\n";
+
+	static const char* const fragmentTemplate =
+		"${FRAG_HEADER}"
+		"${FRAG_TEX_COORD_INPUTS}"
+		"${FRAG_SAMPLERS}"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	${FRAG_COLOR} =${FRAG_COLOR_EXPRESSION};\n"
+		"}\n";
+
+	map<string, string> firstLevelParams;
+
+	{
+		string vtxTexCoordInputs;
+		string vtxTexCoordOutputs;
+		string vtxTexCoordWrites;
+		string fragTexCoordInputs;
+		string fragSamplers;
+		string fragColorExpression;
+
+		for (int i = 0; i < numTextures; i++)
+		{
+			vtxTexCoordInputs		+= "${VTX_IN} mediump vec2 a_texCoord" + toString(i) + ";\n";
+			vtxTexCoordOutputs		+= "${VTX_OUT} mediump vec2 v_texCoord" + toString(i) + ";\n";
+			vtxTexCoordWrites		+= "\tv_texCoord" + toString(i) + " = " + "a_texCoord" + toString(i) + ";\n";
+			fragTexCoordInputs		+= "${FRAG_IN} mediump vec2 v_texCoord" + toString(i) + ";\n";
+			fragSamplers			+= "uniform mediump sampler2D u_sampler" + toString(i) + ";\n";
+			fragColorExpression		+= string() + (i > 0 ? " +" : "") + "\n\t\t${TEXTURE_2D_FUNC}(u_sampler" + toString(i) + ", v_texCoord" + toString(i) + ")";
+		}
+
+		firstLevelParams["VTX_TEX_COORD_INPUTS"]	= substitute(vtxTexCoordInputs);
+		firstLevelParams["VTX_TEX_COORD_OUTPUTS"]	= substitute(vtxTexCoordOutputs);
+		firstLevelParams["VTX_TEX_COORD_WRITES"]	= vtxTexCoordWrites;
+		firstLevelParams["FRAG_TEX_COORD_INPUTS"]	= substitute(fragTexCoordInputs);
+		firstLevelParams["FRAG_SAMPLERS"]			= fragSamplers;
+		firstLevelParams["FRAG_COLOR_EXPRESSION"]	= substitute(fragColorExpression);
+	}
+
+	gls::ProgramContext context(substitute(vertexTemplate, firstLevelParams).c_str(), substitute(fragmentTemplate, firstLevelParams).c_str(), "a_position");
+
+	context.attributes.push_back(gls::VarSpec("a_position", Vec3(-0.1f), Vec3(0.1f)));
+
+	for (int i = 0; i < numTextures; i++)
+	{
+		context.attributes.push_back(gls::VarSpec("a_texCoord" + de::toString(i), Vec2(0.0f), Vec2(1.0f)));
+		context.uniforms.push_back(gls::VarSpec("u_sampler" + de::toString(i), i));
+		context.textureSpecs.push_back(gls::TextureSpec(gls::TextureTestUtil::TEXTURETYPE_2D, i,
+														texWid, texHei, GL_RGBA, GL_UNSIGNED_BYTE, GL_RGBA, true,
+														GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT,
+														Vec4(0.0f), Vec4(1.0f / (float)numTextures)));
+	}
+
+	return context;
+}
+
+gls::ProgramContext ProgramLibrary::generateFragmentPointLightContext (const int texWid, const int texHei) const
+{
+	static const char* const vertexTemplate =
+		"${VTX_HEADER}"
+		"struct Material\n"
+		"{\n"
+		"	mediump vec3	ambientColor;\n"
+		"	mediump vec4	diffuseColor;\n"
+		"	mediump vec3	emissiveColor;\n"
+		"	mediump vec3	specularColor;\n"
+		"	mediump float	shininess;\n"
+		"};\n"
+		"\n"
+		"struct Light\n"
+		"{\n"
+		"	mediump vec3	color;\n"
+		"	mediump vec4	position;\n"
+		"	mediump vec3	direction;\n"
+		"	mediump float	constantAttenuation;\n"
+		"	mediump float	linearAttenuation;\n"
+		"	mediump float	quadraticAttenuation;\n"
+		"};\n"
+		"\n"
+		"${VTX_IN} highp vec4	a_position${NS};\n"
+		"${VTX_IN} mediump vec3	a_normal${NS};\n"
+		"${VTX_IN} mediump vec3	a_color${NS};\n"
+		"${VTX_IN} mediump vec4	a_texCoord0${NS};\n"
+		"\n"
+		"uniform Material		u_material${NS};\n"
+		"uniform Light			u_light${NS}[1];\n"
+		"uniform highp mat4		u_mvpMatrix${NS};\n"
+		"uniform mediump mat4	u_modelViewMatrix${NS};\n"
+		"uniform mediump mat3	u_normalMatrix${NS};\n"
+		"uniform mediump mat4	u_texCoordMatrix0${NS};\n"
+		"\n"
+		"${VTX_OUT} mediump vec4	v_baseColor${NS};\n"
+		"${VTX_OUT} mediump vec2	v_texCoord0${NS};\n"
+		"\n"
+		"${VTX_OUT} mediump vec3	v_eyeNormal${NS};\n"
+		"${VTX_OUT} mediump vec3	v_directionToLight${NS}[1];\n"
+		"${VTX_OUT} mediump float	v_distanceToLight${NS}[1];\n"
+		"\n"
+		"vec3 direction (vec4 from, vec4 to)\n"
+		"{\n"
+		"	return vec3(to.xyz * from.w - from.xyz * to.w);\n"
+		"}\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = u_mvpMatrix${NS} * a_position${NS};\n"
+		"	v_texCoord0${NS} = (u_texCoordMatrix0${NS} * a_texCoord0${NS}).xy;\n"
+		"\n"
+		"	mediump vec4 eyePosition	= u_modelViewMatrix${NS} * a_position${NS};\n"
+		"	mediump vec3 eyeNormal		= normalize(u_normalMatrix${NS} * a_normal${NS});\n"
+		"\n"
+		"	vec4 color	 = vec4(0.0, 0.0, 0.0, 1.0);\n"
+		"	color.rgb	+= u_material${NS}.emissiveColor;\n"
+		"\n"
+		"	color.a		*= u_material${NS}.diffuseColor.a;\n"
+		"\n"
+		"	v_baseColor${NS} = color;\n"
+		"\n"
+		"	v_distanceToLight${NS}[0]	= distance(eyePosition, u_light${NS}[0].position);\n"
+		"	v_directionToLight${NS}[0]	= normalize(direction(eyePosition, u_light${NS}[0].position));\n"
+		"\n"
+		"	v_eyeNormal${NS}			= eyeNormal;\n"
+		"}\n";
+
+	static const char* const fragmentTemplate =
+		"${FRAG_HEADER}"
+		"struct Light\n"
+		"{\n"
+		"	mediump vec3	color;\n"
+		"	mediump vec4	position;\n"
+		"	mediump vec3	direction;\n"
+		"	mediump float	constantAttenuation;\n"
+		"	mediump float	linearAttenuation;\n"
+		"	mediump float	quadraticAttenuation;\n"
+		"};\n"
+		"\n"
+		"struct Material\n"
+		"{\n"
+		"	mediump vec3	ambientColor;\n"
+		"	mediump vec4	diffuseColor;\n"
+		"	mediump vec3	emissiveColor;\n"
+		"	mediump vec3	specularColor;\n"
+		"	mediump float	shininess;\n"
+		"};\n"
+		"\n"
+		"uniform sampler2D		u_sampler0${NS};\n"
+		"uniform Light			u_light${NS}[1];\n"
+		"uniform Material		u_material${NS};\n"
+		"\n"
+		"${FRAG_IN} mediump vec4	v_baseColor${NS};\n"
+		"${FRAG_IN} mediump vec2	v_texCoord0${NS};\n"
+		"\n"
+		"${FRAG_IN} mediump vec3	v_eyeNormal${NS};\n"
+		"${FRAG_IN} mediump vec3	v_directionToLight${NS}[1];\n"
+		"${FRAG_IN} mediump float	v_distanceToLight${NS}[1];\n"
+		"\n"
+		"mediump vec3 computeLighting (Light light, mediump vec3 directionToLight, mediump vec3 vertexEyeNormal)\n"
+		"{\n"
+		"	mediump float	normalDotDirection	= max(dot(vertexEyeNormal, directionToLight), 0.0);\n"
+		"	mediump	vec3	color				= normalDotDirection * u_material${NS}.diffuseColor.rgb * light.color;\n"
+		"\n"
+		"	if (normalDotDirection != 0.0)\n"
+		"	{\n"
+		"		mediump vec3 h = normalize(directionToLight + vec3(0.0, 0.0, 1.0));\n"
+		"		color.rgb += pow(max(dot(vertexEyeNormal, h), 0.0), u_material${NS}.shininess) * u_material${NS}.specularColor * light.color;\n"
+		"	}\n"
+		"\n"
+		"	return color;\n"
+		"}\n"
+		"\n"
+		"mediump float computePointLightAttenuation (Light light, mediump float distanceToLight)\n"
+		"{\n"
+		"	mediump float	constantAttenuation		= light.constantAttenuation;\n"
+		"	mediump float	linearAttenuation		= light.linearAttenuation * distanceToLight;\n"
+		"	mediump float	quadraticAttenuation	= light.quadraticAttenuation * distanceToLight * distanceToLight;\n"
+		"\n"
+		"	return 1.0 / (constantAttenuation + linearAttenuation + quadraticAttenuation);\n"
+		"}\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	mediump vec3 eyeNormal	= normalize(v_eyeNormal${NS});\n"
+		"	mediump vec4 color		= v_baseColor${NS};\n"
+		"\n"
+		"	color.rgb += computePointLightAttenuation(u_light${NS}[0], v_distanceToLight${NS}[0]) * computeLighting(u_light${NS}[0], normalize(v_directionToLight${NS}[0]), eyeNormal);\n"
+		"\n"
+		"	color *= ${TEXTURE_2D_FUNC}(u_sampler0${NS}, v_texCoord0${NS});\n"
+		"\n"
+		"	${FRAG_COLOR} = color;\n"
+		"}\n";
+
+	gls::ProgramContext context(substitute(vertexTemplate).c_str(), substitute(fragmentTemplate).c_str(), "a_position${NS}");
+
+	context.attributes.push_back(gls::VarSpec("a_position${NS}",						Vec4(-1.0f),				Vec4(1.0f)));
+	context.attributes.push_back(gls::VarSpec("a_normal${NS}",							Vec3(-1.0f),				Vec3(1.0f)));
+	context.attributes.push_back(gls::VarSpec("a_texCoord0${NS}",						Vec4(-1.0f),				Vec4(1.0f)));
+
+	context.uniforms.push_back(gls::VarSpec("u_material${NS}.ambientColor",				Vec3(0.0f),					Vec3(1.0f)));
+	context.uniforms.push_back(gls::VarSpec("u_material${NS}.diffuseColor",				Vec4(0.0f),					Vec4(1.0f)));
+	context.uniforms.push_back(gls::VarSpec("u_material${NS}.emissiveColor",			Vec3(0.0f),					Vec3(1.0f)));
+	context.uniforms.push_back(gls::VarSpec("u_material${NS}.specularColor",			Vec3(0.0f),					Vec3(1.0f)));
+	context.uniforms.push_back(gls::VarSpec("u_material${NS}.shininess",				0.0f,						1.0f));
+
+	context.uniforms.push_back(gls::VarSpec("u_light${NS}[0].color",					Vec3(0.0f),					Vec3(1.0f)));
+	context.uniforms.push_back(gls::VarSpec("u_light${NS}[0].position",					Vec4(-1.0f),				Vec4(1.0f)));
+	context.uniforms.push_back(gls::VarSpec("u_light${NS}[0].direction",				Vec3(-1.0f),				Vec3(1.0f)));
+	context.uniforms.push_back(gls::VarSpec("u_light${NS}[0].constantAttenuation",		0.1f,						1.0f));
+	context.uniforms.push_back(gls::VarSpec("u_light${NS}[0].linearAttenuation",		0.1f,						1.0f));
+	context.uniforms.push_back(gls::VarSpec("u_light${NS}[0].quadraticAttenuation",		0.1f,						1.0f));
+
+	context.uniforms.push_back(gls::VarSpec("u_mvpMatrix${NS}",							translationMat<4>(-0.2f),	translationMat<4>(0.2f)));
+	context.uniforms.push_back(gls::VarSpec("u_modelViewMatrix${NS}",					translationMat<4>(-0.2f),	translationMat<4>(0.2f)));
+	context.uniforms.push_back(gls::VarSpec("u_normalMatrix${NS}",						translationMat<3>(-0.2f),	translationMat<3>(0.2f)));
+	context.uniforms.push_back(gls::VarSpec("u_texCoordMatrix0${NS}",					translationMat<4>(-0.2f),	translationMat<4>(0.2f)));
+
+	context.uniforms.push_back(gls::VarSpec("u_sampler0${NS}", 0));
+
+	context.textureSpecs.push_back(gls::TextureSpec(gls::TextureTestUtil::TEXTURETYPE_2D, 0,
+													texWid, texHei, GL_RGBA, GL_UNSIGNED_BYTE, GL_RGBA,
+													true, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT,
+													Vec4(0.0f), Vec4(1.0f)));
+
+	return context;
+}
+
+gls::ProgramContext ProgramLibrary::generateVertexUniformLoopLightContext (const int texWid, const int texHei) const
+{
+	static const char* const vertexTemplate =
+		"${VTX_HEADER}"
+		"struct Material {\n"
+		"	mediump vec3 ambientColor;\n"
+		"	mediump vec4 diffuseColor;\n"
+		"	mediump vec3 emissiveColor;\n"
+		"	mediump vec3 specularColor;\n"
+		"	mediump float shininess;\n"
+		"};\n"
+		"struct Light {\n"
+		"	mediump vec3 color;\n"
+		"	mediump vec4 position;\n"
+		"	mediump vec3 direction;\n"
+		"	mediump float constantAttenuation;\n"
+		"	mediump float linearAttenuation;\n"
+		"	mediump float quadraticAttenuation;\n"
+		"	mediump float spotExponent;\n"
+		"	mediump float spotCutoff;\n"
+		"};\n"
+		"${VTX_IN} highp vec4 a_position${NS};\n"
+		"${VTX_IN} mediump vec3 a_normal${NS};\n"
+		"${VTX_IN} mediump vec4 a_texCoord0${NS};\n"
+		"uniform Material u_material${NS};\n"
+		"uniform Light u_directionalLight${NS}[1];\n"
+		"uniform mediump int u_directionalLightCount${NS};\n"
+		"uniform Light u_spotLight${NS}[4];\n"
+		"uniform mediump int u_spotLightCount${NS};\n"
+		"uniform highp mat4 u_mvpMatrix${NS};\n"
+		"uniform highp mat4 u_modelViewMatrix${NS};\n"
+		"uniform mediump mat3 u_normalMatrix${NS};\n"
+		"uniform mediump mat4 u_texCoordMatrix0${NS};\n"
+		"${VTX_OUT} mediump vec4 v_color${NS};\n"
+		"${VTX_OUT} mediump vec2 v_texCoord0${NS};\n"
+		"mediump vec3 direction (mediump vec4 from, mediump vec4 to)\n"
+		"{\n"
+		"	return vec3(to.xyz * from.w - from.xyz * to.w);\n"
+		"}\n"
+		"\n"
+		"mediump vec3 computeLighting (\n"
+		"	mediump vec3 directionToLight,\n"
+		"	mediump vec3 halfVector,\n"
+		"	mediump vec3 normal,\n"
+		"	mediump vec3 lightColor,\n"
+		"	mediump vec3 diffuseColor,\n"
+		"	mediump vec3 specularColor,\n"
+		"	mediump float shininess)\n"
+		"{\n"
+		"	mediump float normalDotDirection  = max(dot(normal, directionToLight), 0.0);\n"
+		"	mediump vec3  color               = normalDotDirection * diffuseColor * lightColor;\n"
+		"\n"
+		"	if (normalDotDirection != 0.0)\n"
+		"		color += pow(max(dot(normal, halfVector), 0.0), shininess) * specularColor * lightColor;\n"
+		"\n"
+		"	return color;\n"
+		"}\n"
+		"\n"
+		"mediump float computeDistanceAttenuation (mediump float distToLight, mediump float constAtt, mediump float linearAtt, mediump float quadraticAtt)\n"
+		"{\n"
+		"	return 1.0 / (constAtt + linearAtt * distToLight + quadraticAtt * distToLight * distToLight);\n"
+		"}\n"
+		"\n"
+		"mediump float computeSpotAttenuation (\n"
+		"	mediump vec3  directionToLight,\n"
+		"	mediump vec3  lightDir,\n"
+		"	mediump float spotExponent,\n"
+		"	mediump float spotCutoff)\n"
+		"{\n"
+		"	mediump float spotEffect = dot(lightDir, normalize(-directionToLight));\n"
+		"\n"
+		"	if (spotEffect < spotCutoff)\n"
+		"		spotEffect = 0.0;\n"
+		"\n"
+		"	spotEffect = pow(spotEffect, spotExponent);\n"
+		"	return spotEffect;\n"
+		"}\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	highp vec4 position = a_position${NS};\n"
+		"	highp vec3 normal = a_normal${NS};\n"
+		"	gl_Position = u_mvpMatrix${NS} * position;\n"
+		"	v_texCoord0${NS} = (u_texCoordMatrix0${NS} * a_texCoord0${NS}).xy;\n"
+		"	mediump vec4 color = vec4(u_material${NS}.emissiveColor, u_material${NS}.diffuseColor.a);\n"
+		"\n"
+		"	highp vec4 eyePosition = u_modelViewMatrix${NS} * position;\n"
+		"	mediump vec3 eyeNormal = normalize(u_normalMatrix${NS} * normal);\n"
+		"	for (int i = 0; i < u_directionalLightCount${NS}; i++)\n"
+		"	{\n"
+		"		mediump vec3 directionToLight = -u_directionalLight${NS}[i].direction;\n"
+		"		mediump vec3 halfVector = normalize(directionToLight + vec3(0.0, 0.0, 1.0));\n"
+		"		color.rgb += computeLighting(directionToLight, halfVector, eyeNormal, u_directionalLight${NS}[i].color, u_material${NS}.diffuseColor.rgb, u_material${NS}.specularColor, u_material${NS}.shininess);\n"
+		"	}\n"
+		"\n"
+		"	for (int i = 0; i < u_spotLightCount${NS}; i++)\n"
+		"	{\n"
+		"		mediump float distanceToLight = distance(eyePosition, u_spotLight${NS}[i].position);\n"
+		"		mediump vec3 directionToLight = normalize(direction(eyePosition, u_spotLight${NS}[i].position));\n"
+		"		mediump vec3 halfVector = normalize(directionToLight + vec3(0.0, 0.0, 1.0));\n"
+		"		color.rgb += computeLighting(directionToLight, halfVector, eyeNormal, u_spotLight${NS}[i].color, u_material${NS}.diffuseColor.rgb, u_material${NS}.specularColor, u_material${NS}.shininess) * computeDistanceAttenuation(distanceToLight, u_spotLight${NS}[i].constantAttenuation, u_spotLight${NS}[i].linearAttenuation, u_spotLight${NS}[i].quadraticAttenuation) * computeSpotAttenuation(directionToLight, u_spotLight${NS}[i].direction, u_spotLight${NS}[i].spotExponent, u_spotLight${NS}[i].spotCutoff);\n"
+		"	}\n"
+		"\n"
+		"\n"
+		"	v_color${NS} = color;\n"
+		"}\n";
+
+	static const char* const fragmentTemplate =
+		"${FRAG_HEADER}"
+		"uniform sampler2D u_sampler0${NS};\n"
+		"${FRAG_IN} mediump vec4 v_color${NS};\n"
+		"${FRAG_IN} mediump vec2 v_texCoord0${NS};\n"
+		"void main (void)\n"
+		"{\n"
+		"	mediump vec2 texCoord0 = v_texCoord0${NS};\n"
+		"	mediump vec4 color = v_color${NS};\n"
+		"	color *= ${TEXTURE_2D_FUNC}(u_sampler0${NS}, texCoord0);\n"
+		"	${FRAG_COLOR} = color;\n"
+		"}\n";
+
+	gls::ProgramContext context(substitute(vertexTemplate).c_str(), substitute(fragmentTemplate).c_str(), "a_position${NS}");
+
+	context.attributes.push_back	(gls::VarSpec("a_position${NS}",									Vec4(-1.0f),				Vec4(1.0f)));
+	context.attributes.push_back	(gls::VarSpec("a_normal${NS}",										Vec3(-1.0f),				Vec3(1.0f)));
+	context.attributes.push_back	(gls::VarSpec("a_texCoord0${NS}",									Vec4(-1.0f),				Vec4(1.0f)));
+
+	context.uniforms.push_back		(gls::VarSpec("u_material${NS}.ambientColor",						Vec3(0.0f),					Vec3(1.0f)));
+	context.uniforms.push_back		(gls::VarSpec("u_material${NS}.diffuseColor",						Vec4(0.0f),					Vec4(1.0f)));
+	context.uniforms.push_back		(gls::VarSpec("u_material${NS}.emissiveColor",						Vec3(0.0f),					Vec3(1.0f)));
+	context.uniforms.push_back		(gls::VarSpec("u_material${NS}.specularColor",						Vec3(0.0f),					Vec3(1.0f)));
+	context.uniforms.push_back		(gls::VarSpec("u_material${NS}.shininess",							0.0f,						1.0f));
+
+	context.uniforms.push_back		(gls::VarSpec("u_directionalLight${NS}[0].color",					Vec3(0.0f),					Vec3(1.0f)));
+	context.uniforms.push_back		(gls::VarSpec("u_directionalLight${NS}[0].position",				Vec4(-1.0f),				Vec4(1.0f)));
+	context.uniforms.push_back		(gls::VarSpec("u_directionalLight${NS}[0].direction",				Vec3(-1.0f),				Vec3(1.0f)));
+	context.uniforms.push_back		(gls::VarSpec("u_directionalLight${NS}[0].constantAttenuation",		0.1f,						1.0f));
+	context.uniforms.push_back		(gls::VarSpec("u_directionalLight${NS}[0].linearAttenuation",		0.1f,						1.0f));
+	context.uniforms.push_back		(gls::VarSpec("u_directionalLight${NS}[0].quadraticAttenuation",	0.1f,						1.0f));
+	context.uniforms.push_back		(gls::VarSpec("u_directionalLight${NS}[0].spotExponent",			0.1f,						1.0f));
+	context.uniforms.push_back		(gls::VarSpec("u_directionalLight${NS}[0].spotCutoff",				0.1f,						1.0f));
+
+	context.uniforms.push_back		(gls::VarSpec("u_directionalLightCount${NS}",						1));
+
+	for (int i = 0; i < 4; i++)
+	{
+		const std::string ndxStr = de::toString(i);
+
+		context.uniforms.push_back(gls::VarSpec("u_spotLight${NS}["+ndxStr+"].color",					Vec3(0.0f),					Vec3(1.0f)));
+		context.uniforms.push_back(gls::VarSpec("u_spotLight${NS}["+ndxStr+"].position",				Vec4(-1.0f),				Vec4(1.0f)));
+		context.uniforms.push_back(gls::VarSpec("u_spotLight${NS}["+ndxStr+"].direction",				Vec3(-1.0f),				Vec3(1.0f)));
+		context.uniforms.push_back(gls::VarSpec("u_spotLight${NS}["+ndxStr+"].constantAttenuation",		0.1f,						1.0f));
+		context.uniforms.push_back(gls::VarSpec("u_spotLight${NS}["+ndxStr+"].linearAttenuation",		0.1f,						1.0f));
+		context.uniforms.push_back(gls::VarSpec("u_spotLight${NS}["+ndxStr+"].quadraticAttenuation",	0.1f,						1.0f));
+		context.uniforms.push_back(gls::VarSpec("u_spotLight${NS}["+ndxStr+"].spotExponent",			0.1f,						1.0f));
+		context.uniforms.push_back(gls::VarSpec("u_spotLight${NS}["+ndxStr+"].spotCutoff",				0.1f,						1.0f));
+	}
+
+	context.uniforms.push_back		(gls::VarSpec("u_spotLightCount${NS}",								4));
+
+	context.uniforms.push_back		(gls::VarSpec("u_mvpMatrix${NS}",									translationMat<4>(-0.2f),	translationMat<4>(0.2f)));
+	context.uniforms.push_back		(gls::VarSpec("u_modelViewMatrix${NS}",								translationMat<4>(-0.2f),	translationMat<4>(0.2f)));
+	context.uniforms.push_back		(gls::VarSpec("u_normalMatrix${NS}",								translationMat<3>(-0.2f),	translationMat<3>(0.2f)));
+	context.uniforms.push_back		(gls::VarSpec("u_texCoordMatrix0${NS}",								translationMat<4>(-0.2f),	translationMat<4>(0.2f)));
+
+	context.uniforms.push_back		(gls::VarSpec("u_sampler0${NS}",									0));
+
+	context.textureSpecs.push_back	(gls::TextureSpec(gls::TextureTestUtil::TEXTURETYPE_2D, 0,
+													  texWid, texHei, GL_RGBA, GL_UNSIGNED_BYTE, GL_RGBA,
+													  true, GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT,
+													  Vec4(0.0f), Vec4(1.0f)));
+
+	return context;
+}
+
+} // StressTestUtil
+} // gls
+} // deqp
diff --git a/modules/glshared/glsLongStressTestUtil.hpp b/modules/glshared/glsLongStressTestUtil.hpp
new file mode 100644
index 0000000..baa12f5
--- /dev/null
+++ b/modules/glshared/glsLongStressTestUtil.hpp
@@ -0,0 +1,61 @@
+#ifndef _GLSLONGSTRESSTESTUTIL_HPP
+#define _GLSLONGSTRESSTESTUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Utilities for tests with gls::LongStressCase.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsLongStressCase.hpp"
+#include "gluShaderUtil.hpp"
+
+#include <map>
+#include <string>
+
+namespace deqp
+{
+namespace gls
+{
+namespace LongStressTestUtil
+{
+
+class ProgramLibrary
+{
+public:
+									ProgramLibrary							(glu::GLSLVersion glslVersion);
+
+	gls::ProgramContext				generateBufferContext					(int numDummyAttributes) const;
+	gls::ProgramContext				generateTextureContext					(int numTextureObjects, int texWid, int texHei, float positionFactor) const;
+	gls::ProgramContext				generateBufferAndTextureContext			(int numTextures, int texWid, int texHei) const;
+	gls::ProgramContext				generateFragmentPointLightContext		(int texWid, int texHei) const;
+	gls::ProgramContext				generateVertexUniformLoopLightContext	(int texWid, int texHei) const;
+
+private:
+	std::string						substitute								(const std::string&) const;
+	std::string						substitute								(const std::string&, const std::map<std::string, std::string>&) const;
+
+	glu::GLSLVersion				m_glslVersion;
+};
+
+} // StressTestUtil
+} // gls
+} // deqp
+
+#endif // _GLSLONGSTRESSTESTUTIL_HPP
diff --git a/modules/glshared/glsMemoryStressCase.cpp b/modules/glshared/glsMemoryStressCase.cpp
new file mode 100644
index 0000000..cff05bf
--- /dev/null
+++ b/modules/glshared/glsMemoryStressCase.cpp
@@ -0,0 +1,945 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES Module
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Memory object stress test
+ *//*--------------------------------------------------------------------*/
+
+#include "glsMemoryStressCase.hpp"
+#include "gluShaderProgram.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuCommandLine.hpp"
+#include "deRandom.hpp"
+#include "deClock.h"
+#include "deString.h"
+
+#include "glw.h"
+
+#include <vector>
+#include <iostream>
+
+using std::vector;
+using tcu::TestLog;
+
+namespace deqp
+{
+namespace gls
+{
+
+static const char* glErrorToString (deUint32 error)
+{
+	switch (error)
+	{
+		case GL_OUT_OF_MEMORY:
+			return "GL_OUT_OF_MEMORY";
+			break;
+
+		case GL_INVALID_ENUM:
+			return "GL_INVALID_ENUM";
+			break;
+
+		case GL_INVALID_FRAMEBUFFER_OPERATION:
+			return "GL_INVALID_FRAMEBUFFER_OPERATION";
+			break;
+
+		case GL_INVALID_OPERATION:
+			return "GL_INVALID_OPERATION";
+			break;
+
+		case GL_INVALID_VALUE:
+			return "GL_INVALID_VALUE";
+			break;
+
+		case 0:
+			return "<none>";
+			break;
+
+		default:
+			// \todo [mika] Handle uknown errors?
+			DE_ASSERT(false);
+			return NULL;
+			break;
+	}
+}
+
+static const float s_quadCoords[] =
+{
+	-1.0f, -1.0f,
+	 1.0f, -1.0f,
+	 1.0f,  1.0f,
+	-1.0f,  1.0f
+};
+
+static const GLubyte s_quadIndices[] =
+{
+	0, 1, 2,
+	2, 3, 0
+};
+
+class TextureRenderer
+{
+public:
+			TextureRenderer		(tcu::TestLog& log, glu::RenderContext& renderContext);
+			~TextureRenderer	(void);
+	void	render				(deUint32 texture);
+
+private:
+	glu::ShaderProgram*	m_program;
+	glu::RenderContext&	m_renderCtx;
+
+	deUint32			m_coordBuffer;
+	deUint32			m_indexBuffer;
+	deUint32			m_vao;
+
+	static const char*	s_vertexShaderGLES2;
+	static const char*	s_fragmentShaderGLES2;
+
+	static const char*	s_vertexShaderGLES3;
+	static const char*	s_fragmentShaderGLES3;
+
+	static const char*	s_vertexShaderGL3;
+	static const char*	s_fragmentShaderGL3;
+};
+
+const char* TextureRenderer::s_vertexShaderGLES2 =
+"attribute mediump vec2 a_coord;\n"
+"varying mediump vec2 v_texCoord;\n"
+"void main (void)\n"
+"{\n"
+"\tv_texCoord = 0.5 * (a_coord + vec2(1.0));\n"
+"\tgl_Position = vec4(a_coord, 0.0, 1.0);\n"
+"}\n";
+
+const char* TextureRenderer::s_fragmentShaderGLES2 =
+"varying mediump vec2 v_texCoord;\n"
+"uniform sampler2D u_texture;\n"
+"void main (void)\n"
+"{\n"
+"\tgl_FragColor = texture2D(u_texture, v_texCoord);\n"
+"}\n";
+
+const char* TextureRenderer::s_vertexShaderGLES3 =
+"#version 300 es\n"
+"in mediump vec2 a_coord;\n"
+"out mediump vec2 v_texCoord;\n"
+"void main (void)\n"
+"{\n"
+"\tv_texCoord = 0.5 * (a_coord + vec2(1.0));\n"
+"\tgl_Position = vec4(a_coord, 0.0, 1.0);\n"
+"}\n";
+
+const char* TextureRenderer::s_fragmentShaderGLES3 =
+"#version 300 es\n"
+"in mediump vec2 v_texCoord;\n"
+"uniform sampler2D u_texture;\n"
+"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+"void main (void)\n"
+"{\n"
+"\tdEQP_FragColor = texture(u_texture, v_texCoord);\n"
+"}\n";
+
+const char* TextureRenderer::s_vertexShaderGL3 =
+"#version 330\n"
+"in mediump vec2 a_coord;\n"
+"out mediump vec2 v_texCoord;\n"
+"void main (void)\n"
+"{\n"
+"\tv_texCoord = 0.5 * (a_coord + vec2(1.0));\n"
+"\tgl_Position = vec4(a_coord, 0.0, 1.0);\n"
+"}\n";
+
+const char* TextureRenderer::s_fragmentShaderGL3 =
+"#version 330\n"
+"in mediump vec2 v_texCoord;\n"
+"uniform sampler2D u_texture;\n"
+"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+"void main (void)\n"
+"{\n"
+"\tdEQP_FragColor = texture(u_texture, v_texCoord);\n"
+"}\n";
+
+TextureRenderer::TextureRenderer (tcu::TestLog& log, glu::RenderContext& renderContext)
+	: m_program		(NULL)
+	, m_renderCtx	(renderContext)
+	, m_coordBuffer	(0)
+	, m_indexBuffer	(0)
+	, m_vao			(0)
+{
+	const glu::ContextType ctxType = renderContext.getType();
+
+	if (glu::isGLSLVersionSupported(ctxType, glu::GLSL_VERSION_300_ES))
+		m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(s_vertexShaderGLES3, s_fragmentShaderGLES3));
+	else if (glu::isGLSLVersionSupported(ctxType, glu::GLSL_VERSION_100_ES))
+		m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(s_vertexShaderGLES2, s_fragmentShaderGLES2));
+	else if (glu::isGLSLVersionSupported(ctxType, glu::GLSL_VERSION_330))
+		m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(s_vertexShaderGL3, s_fragmentShaderGL3));
+	else
+		DE_ASSERT(false);
+
+	if (ctxType.getProfile() == glu::PROFILE_CORE)
+		GLU_CHECK_CALL(glGenVertexArrays(1, &m_vao));
+
+	GLU_CHECK_CALL(glGenBuffers(1, &m_coordBuffer));
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_coordBuffer));
+	GLU_CHECK_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(s_quadCoords), s_quadCoords, GL_STATIC_DRAW));
+
+	GLU_CHECK_CALL(glGenBuffers(1, &m_indexBuffer));
+	GLU_CHECK_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer));
+	GLU_CHECK_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(s_quadIndices), s_quadIndices, GL_STATIC_DRAW));
+
+	if (!m_program->isOk())
+	{
+		log << *m_program;
+		TCU_CHECK_MSG(m_program->isOk(), "Shader compilation failed");
+	}
+}
+
+TextureRenderer::~TextureRenderer (void)
+{
+	delete m_program;
+	glDeleteBuffers(1, &m_coordBuffer);
+	glDeleteBuffers(1, &m_indexBuffer);
+}
+
+void TextureRenderer::render (deUint32 texture)
+{
+	deUint32 coordLoc = -1;
+	deUint32 texLoc	= -1;
+
+	GLU_CHECK_CALL(glUseProgram(m_program->getProgram()));
+
+	coordLoc = glGetAttribLocation(m_program->getProgram(), "a_coord");
+	GLU_CHECK();
+	TCU_CHECK(coordLoc != (deUint32)-1);
+
+	if (m_vao != 0)
+		GLU_CHECK_CALL(glBindVertexArray(m_vao));
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(coordLoc));
+
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_coordBuffer));
+	GLU_CHECK_CALL(glVertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL));
+
+	GLU_CHECK_CALL(glActiveTexture(GL_TEXTURE0));
+	GLU_CHECK_CALL(glBindTexture(GL_TEXTURE_2D, texture));
+
+	texLoc = glGetUniformLocation(m_program->getProgram(), "u_texture");
+	GLU_CHECK();
+	TCU_CHECK(texLoc != (deUint32)-1);
+
+	GLU_CHECK_CALL(glUniform1i(texLoc, 0));
+
+	GLU_CHECK_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer));
+	GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, NULL));
+
+	GLU_CHECK_CALL(glDisableVertexAttribArray(coordLoc));
+
+	if (m_vao != 0)
+		GLU_CHECK_CALL(glBindVertexArray(0));
+}
+
+class BufferRenderer
+{
+public:
+			BufferRenderer	(tcu::TestLog& log, glu::RenderContext& renderContext);
+			~BufferRenderer	(void);
+	void	render			(deUint32 buffer, int size);
+
+private:
+	glu::ShaderProgram*	m_program;
+	glu::RenderContext&	m_renderCtx;
+
+	deUint32			m_coordBuffer;
+	deUint32			m_indexBuffer;
+	deUint32			m_vao;
+
+	static const char*	s_vertexShaderGLES2;
+	static const char*	s_fragmentShaderGLES2;
+
+	static const char*	s_vertexShaderGLES3;
+	static const char*	s_fragmentShaderGLES3;
+
+	static const char*	s_vertexShaderGL3;
+	static const char*	s_fragmentShaderGL3;
+};
+
+const char* BufferRenderer::s_vertexShaderGLES2 =
+"attribute mediump vec2 a_coord;\n"
+"attribute mediump vec4 a_buffer;\n"
+"varying mediump vec4 v_buffer;\n"
+"void main (void)\n"
+"{\n"
+"\tv_buffer = a_buffer;\n"
+"\tgl_Position = vec4(a_coord, 0.0, 1.0);\n"
+"}\n";
+
+const char* BufferRenderer::s_fragmentShaderGLES2 =
+"varying mediump vec4 v_buffer;\n"
+"void main (void)\n"
+"{\n"
+"\tgl_FragColor = v_buffer;\n"
+"}\n";
+
+const char* BufferRenderer::s_vertexShaderGLES3 =
+"#version 300 es\n"
+"in mediump vec2 a_coord;\n"
+"in mediump vec4 a_buffer;\n"
+"out mediump vec4 v_buffer;\n"
+"void main (void)\n"
+"{\n"
+"\tv_buffer = a_buffer;\n"
+"\tgl_Position = vec4(a_coord, 0.0, 1.0);\n"
+"}\n";
+
+const char* BufferRenderer::s_fragmentShaderGLES3 =
+"#version 300 es\n"
+"in mediump vec4 v_buffer;\n"
+"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+"void main (void)\n"
+"{\n"
+"\tdEQP_FragColor = v_buffer;\n"
+"}\n";
+
+const char* BufferRenderer::s_vertexShaderGL3 =
+"#version 330\n"
+"in mediump vec2 a_coord;\n"
+"in mediump vec4 a_buffer;\n"
+"out mediump vec4 v_buffer;\n"
+"void main (void)\n"
+"{\n"
+"\tv_buffer = a_buffer;\n"
+"\tgl_Position = vec4(a_coord, 0.0, 1.0);\n"
+"}\n";
+
+const char* BufferRenderer::s_fragmentShaderGL3 =
+"#version 330\n"
+"in mediump vec4 v_buffer;\n"
+"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
+"void main (void)\n"
+"{\n"
+"\tdEQP_FragColor = v_buffer;\n"
+"}\n";
+
+BufferRenderer::BufferRenderer (tcu::TestLog& log, glu::RenderContext& renderContext)
+	: m_program		(NULL)
+	, m_renderCtx	(renderContext)
+	, m_coordBuffer	(0)
+	, m_indexBuffer	(0)
+	, m_vao			(0)
+{
+	const glu::ContextType ctxType = renderContext.getType();
+
+	if (glu::isGLSLVersionSupported(ctxType, glu::GLSL_VERSION_300_ES))
+		m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(s_vertexShaderGLES3, s_fragmentShaderGLES3));
+	else if (glu::isGLSLVersionSupported(ctxType, glu::GLSL_VERSION_100_ES))
+		m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(s_vertexShaderGLES2, s_fragmentShaderGLES2));
+	else if (glu::isGLSLVersionSupported(ctxType, glu::GLSL_VERSION_330))
+		m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(s_vertexShaderGL3, s_fragmentShaderGL3));
+	else
+		DE_ASSERT(false);
+
+	if (ctxType.getProfile() == glu::PROFILE_CORE)
+		GLU_CHECK_CALL(glGenVertexArrays(1, &m_vao));
+
+	GLU_CHECK_CALL(glGenBuffers(1, &m_coordBuffer));
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_coordBuffer));
+	GLU_CHECK_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(s_quadCoords), s_quadCoords, GL_STATIC_DRAW));
+
+	GLU_CHECK_CALL(glGenBuffers(1, &m_indexBuffer));
+	GLU_CHECK_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer));
+	GLU_CHECK_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(s_quadIndices), s_quadIndices, GL_STATIC_DRAW));
+
+	if (!m_program->isOk())
+	{
+		log << *m_program;
+		TCU_CHECK_MSG(m_program->isOk(), "Shader compilation failed");
+	}
+}
+
+BufferRenderer::~BufferRenderer (void)
+{
+	delete m_program;
+	glDeleteBuffers(1, &m_coordBuffer);
+	glDeleteBuffers(1, &m_indexBuffer);
+}
+
+void BufferRenderer::render (deUint32 buffer, int size)
+{
+	DE_UNREF(size);
+	DE_ASSERT((size_t)size >= sizeof(GLubyte) * 4 * 6);
+	GLU_CHECK_CALL(glUseProgram(m_program->getProgram()));
+
+	deUint32 bufferLoc = glGetAttribLocation(m_program->getProgram(), "a_buffer");
+	TCU_CHECK(bufferLoc != (deUint32)-1);
+
+	deUint32 coordLoc = glGetAttribLocation(m_program->getProgram(), "a_coord");
+	TCU_CHECK(coordLoc != (deUint32)-1);
+
+	if (m_vao != 0)
+		GLU_CHECK_CALL(glBindVertexArray(m_vao));
+
+	GLU_CHECK_CALL(glEnableVertexAttribArray(bufferLoc));
+	GLU_CHECK_CALL(glEnableVertexAttribArray(coordLoc));
+
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_coordBuffer));
+	GLU_CHECK_CALL(glVertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL));
+
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, buffer));
+	GLU_CHECK_CALL(glVertexAttribPointer(bufferLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0));
+	GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
+
+	GLU_CHECK_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer));
+	GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, NULL));
+
+	GLU_CHECK_CALL(glDisableVertexAttribArray(bufferLoc));
+	GLU_CHECK_CALL(glDisableVertexAttribArray(coordLoc));
+
+	if (m_vao != 0)
+		GLU_CHECK_CALL(glBindVertexArray(0));
+}
+
+class MemObjectAllocator
+{
+public:
+	enum Result
+	{
+		RESULT_GOT_BAD_ALLOC = 0,
+		RESULT_GEN_TEXTURES_FAILED,
+		RESULT_GEN_BUFFERS_FAILED,
+		RESULT_BUFFER_DATA_FAILED,
+		RESULT_BUFFER_SUB_DATA_FAILED,
+		RESULT_TEXTURE_IMAGE_FAILED,
+		RESULT_TEXTURE_SUB_IMAGE_FAILED,
+		RESULT_BIND_TEXTURE_FAILED,
+		RESULT_BIND_BUFFER_FAILED,
+		RESULT_DELETE_TEXTURES_FAILED,
+		RESULT_DELETE_BUFFERS_FAILED,
+		RESULT_RENDER_FAILED,
+
+		RESULT_LAST
+	};
+
+						MemObjectAllocator	(tcu::TestLog& log, glu::RenderContext& renderContext, MemObjectType objectTypes, const MemObjectConfig& config, int seed);
+						~MemObjectAllocator	(void);
+	bool				allocUntilFailure	(void);
+	void				clearObjects		(void);
+	Result				getResult			(void) const { return m_result; }
+	deUint32			getGLError			(void) const { return m_glError; }
+	int					getObjectCount		(void) const { return m_objectCount; }
+	deUint32			getBytes			(void) const { return m_bytesRequired; }
+
+	static const char*	resultToString		(Result result);
+
+private:
+
+	void				allocateTexture		(de::Random& rnd);
+	void				allocateBuffer		(de::Random& rnd);
+
+	vector<deUint32>	m_buffers;
+	vector<deUint32>	m_textures;
+	int					m_seed;
+	int					m_objectCount;
+	deUint32			m_bytesRequired;
+	MemObjectType		m_objectTypes;
+	Result				m_result;
+	MemObjectConfig		m_config;
+	deUint32			m_glError;
+	vector<deUint8>		m_dummyData;
+	BufferRenderer		m_bufferRenderer;
+	TextureRenderer		m_textureRenderer;
+};
+
+MemObjectAllocator::MemObjectAllocator (tcu::TestLog& log, glu::RenderContext& renderContext, MemObjectType objectTypes, const MemObjectConfig& config, int seed)
+	: m_seed			(seed)
+	, m_objectCount		(0)
+	, m_bytesRequired	(0)
+	, m_objectTypes		(objectTypes)
+	, m_result			(RESULT_LAST)
+	, m_config			(config)
+	, m_glError			(0)
+	, m_bufferRenderer	(log, renderContext)
+	, m_textureRenderer	(log, renderContext)
+{
+	DE_UNREF(renderContext);
+
+	if (m_config.useDummyData)
+	{
+		int dummySize = deMax32(m_config.maxBufferSize, m_config.maxTextureSize*m_config.maxTextureSize*4);
+		m_dummyData = vector<deUint8>(dummySize);
+	}
+	else if (m_config.write)
+		m_dummyData = vector<deUint8>(128);
+}
+
+MemObjectAllocator::~MemObjectAllocator (void)
+{
+}
+
+bool MemObjectAllocator::allocUntilFailure (void)
+{
+	de::Random rnd(m_seed);
+	GLU_CHECK_MSG("Error in init");
+	try
+	{
+		const deUint64	timeoutUs	= 10000000; // 10s
+		deUint64		beginTimeUs	= deGetMicroseconds();
+		deUint64		currentTimeUs;
+
+		do
+		{
+			GLU_CHECK_MSG("Unkown Error");
+			switch (m_objectTypes)
+			{
+				case MEMOBJECTTYPE_TEXTURE:
+					allocateTexture(rnd);
+					break;
+
+				case MEMOBJECTTYPE_BUFFER:
+					allocateBuffer(rnd);
+					break;
+
+				default:
+				{
+					if (rnd.getBool())
+						allocateBuffer(rnd);
+					else
+						allocateTexture(rnd);
+					break;
+				}
+			}
+
+			if (m_result != RESULT_LAST)
+			{
+				glFinish();
+				return true;
+			}
+
+			currentTimeUs = deGetMicroseconds();
+		} while (currentTimeUs - beginTimeUs < timeoutUs);
+
+		// Timeout
+		if (currentTimeUs - beginTimeUs >= timeoutUs)
+			return false;
+		else
+			return true;
+	}
+	catch (const std::bad_alloc&)
+	{
+		m_result = RESULT_GOT_BAD_ALLOC;
+		return true;
+	}
+}
+
+void MemObjectAllocator::clearObjects (void)
+{
+	deUint32 error = 0;
+
+	if (!m_textures.empty())
+	{
+		glDeleteTextures((GLsizei)m_textures.size(), &(m_textures[0]));
+		error = glGetError();
+		if (error != 0)
+		{
+			m_result	= RESULT_DELETE_TEXTURES_FAILED;
+			m_glError	= error;
+		}
+
+		m_textures.clear();
+	}
+
+	if (!m_buffers.empty())
+	{
+		glDeleteBuffers((GLsizei)m_buffers.size(), &(m_buffers[0]));
+		error = glGetError();
+		if (error != 0)
+		{
+			m_result	= RESULT_DELETE_BUFFERS_FAILED;
+			m_glError	= error;
+		}
+
+		m_buffers.clear();
+	}
+}
+
+void MemObjectAllocator::allocateTexture (de::Random& rnd)
+{
+	const int	vectorBlockSize = 128;
+	deUint32	tex		= 0;
+	deUint32	error	= 0;
+	int			width	= rnd.getInt(m_config.minTextureSize, m_config.maxTextureSize);
+	int			height	= rnd.getInt(m_config.minTextureSize, m_config.maxTextureSize);
+
+	glGenTextures(1, &tex);
+	error = glGetError();
+	if (error != 0)
+	{
+		m_result	= RESULT_GEN_TEXTURES_FAILED;
+		m_glError	= error;
+		return;
+	}
+
+	if (m_textures.size() % vectorBlockSize == 0)
+		m_textures.reserve(m_textures.size() + vectorBlockSize);
+
+	m_textures.push_back(tex);
+
+	glBindTexture(GL_TEXTURE_2D, tex);
+	error = glGetError();
+	if (error != 0)
+	{
+		m_result	= RESULT_BIND_TEXTURE_FAILED;
+		m_glError	= error;
+		return;
+	}
+
+	if (m_config.useDummyData)
+	{
+		DE_ASSERT((int)m_dummyData.size() >= width*height*4);
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, &(m_dummyData[0]));
+	}
+	else
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+	error = glGetError();
+	if (error != 0)
+	{
+		m_result	= RESULT_TEXTURE_IMAGE_FAILED;
+		m_glError	= error;
+		return;
+	}
+
+	if (m_config.write)
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &(m_dummyData[0]));
+
+	error = glGetError();
+	if (error != 0)
+	{
+		m_result	= RESULT_TEXTURE_SUB_IMAGE_FAILED;
+		m_glError	= error;
+		return;
+	}
+
+	if (m_config.use)
+	{
+		try
+		{
+			m_textureRenderer.render(tex);
+		}
+		catch (const glu::Error& err)
+		{
+			m_result	= RESULT_RENDER_FAILED;
+			m_glError	= err.getError();
+			return;
+		}
+		catch (const glu::OutOfMemoryError&)
+		{
+			m_result	= RESULT_RENDER_FAILED;
+			m_glError	= GL_OUT_OF_MEMORY;
+			return;
+		}
+	}
+
+	glBindTexture(GL_TEXTURE_2D, 0);
+	error = glGetError();
+	if (error != 0)
+	{
+		m_result	= RESULT_BIND_TEXTURE_FAILED;
+		m_glError	= error;
+		return;
+	}
+
+	m_objectCount++;
+	m_bytesRequired += width*height*4;
+}
+
+void MemObjectAllocator::allocateBuffer (de::Random& rnd)
+{
+	const int	vectorBlockSize = 128;
+	deUint32	buffer			= 0;
+	deUint32	error			= 0;
+	int			size			= rnd.getInt(m_config.minBufferSize, m_config.maxBufferSize);
+
+	glGenBuffers(1, &buffer);
+	error = glGetError();
+	if (error != 0)
+	{
+		m_result	= RESULT_GEN_BUFFERS_FAILED;
+		m_glError	= error;
+		return;
+	}
+
+	glBindBuffer(GL_ARRAY_BUFFER, buffer);
+	error = glGetError();
+	if (error != 0)
+	{
+		m_result	= RESULT_BIND_BUFFER_FAILED;
+		m_glError	= error;
+		return;
+	}
+
+	if (m_buffers.size() % vectorBlockSize == 0)
+		m_buffers.reserve(m_buffers.size() + vectorBlockSize);
+
+	m_buffers.push_back(buffer);
+
+	if (m_config.useDummyData)
+	{
+		DE_ASSERT((int)m_dummyData.size() >= size);
+		glBufferData(GL_ARRAY_BUFFER, size, &(m_dummyData[0]), GL_DYNAMIC_DRAW);
+	}
+	else
+		glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_DRAW);
+
+	error = glGetError();
+	if (error != 0)
+	{
+		m_result	= RESULT_BUFFER_DATA_FAILED;
+		m_glError	= error;
+		return;
+	}
+
+	if (m_config.write)
+		glBufferSubData(GL_ARRAY_BUFFER, 0, 1, &(m_dummyData[0]));
+
+	error = glGetError();
+	if (error != 0)
+	{
+		m_result	= RESULT_BUFFER_SUB_DATA_FAILED;
+		m_glError	= error;
+		return;
+	}
+
+	if (m_config.use)
+	{
+		try
+		{
+			m_bufferRenderer.render(buffer, size);
+		}
+		catch (const glu::Error& err)
+		{
+			m_result	= RESULT_RENDER_FAILED;
+			m_glError	= err.getError();
+			return;
+		}
+		catch (const glu::OutOfMemoryError&)
+		{
+			m_result	= RESULT_RENDER_FAILED;
+			m_glError	= GL_OUT_OF_MEMORY;
+			return;
+		}
+	}
+
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
+	error = glGetError();
+	if (error != 0)
+	{
+		m_result	= RESULT_BIND_BUFFER_FAILED;
+		m_glError	= error;
+		return;
+	}
+
+	m_objectCount++;
+	m_bytesRequired += size;
+}
+
+const char* MemObjectAllocator::resultToString (Result result)
+{
+	switch (result)
+	{
+		case RESULT_GOT_BAD_ALLOC:
+			return "Caught std::bad_alloc";
+			break;
+
+		case RESULT_GEN_TEXTURES_FAILED:
+			return "glGenTextures failed";
+			break;
+
+		case RESULT_GEN_BUFFERS_FAILED:
+			return "glGenBuffers failed";
+			break;
+
+		case RESULT_BUFFER_DATA_FAILED:
+			return "glBufferData failed";
+			break;
+
+		case RESULT_BUFFER_SUB_DATA_FAILED:
+			return "glBufferSubData failed";
+			break;
+
+		case RESULT_TEXTURE_IMAGE_FAILED:
+			return "glTexImage2D failed";
+			break;
+
+		case RESULT_TEXTURE_SUB_IMAGE_FAILED:
+			return "glTexSubImage2D failed";
+			break;
+
+		case RESULT_BIND_TEXTURE_FAILED:
+			return "glBindTexture failed";
+			break;
+
+		case RESULT_BIND_BUFFER_FAILED:
+			return "glBindBuffer failed";
+			break;
+
+		case RESULT_DELETE_TEXTURES_FAILED:
+			return "glDeleteTextures failed";
+			break;
+
+		case RESULT_DELETE_BUFFERS_FAILED:
+			return "glDeleteBuffers failed";
+			break;
+
+		case RESULT_RENDER_FAILED:
+			return "Rendering result failed";
+			break;
+
+		default:
+			DE_ASSERT(false);
+			return NULL;
+	}
+}
+
+MemoryStressCase::MemoryStressCase (tcu::TestContext& ctx, glu::RenderContext& renderContext, deUint32 objectTypes, int minTextureSize, int maxTextureSize, int minBufferSize, int maxBufferSize, bool write, bool use, bool useDummyData, bool clearAfterOOM, const char* name, const char* desc)
+	: tcu::TestCase					(ctx, name, desc)
+	, m_iteration					(0)
+	, m_iterationCount				(5)
+	, m_objectTypes					((MemObjectType)objectTypes)
+	, m_zeroAlloc					(false)
+	, m_clearAfterOOM				(clearAfterOOM)
+	, m_renderCtx					(renderContext)
+{
+	m_allocated.reserve(m_iterationCount);
+	m_config.maxTextureSize = maxTextureSize;
+	m_config.minTextureSize = minTextureSize;
+	m_config.maxBufferSize	= maxBufferSize;
+	m_config.minBufferSize	= minBufferSize;
+	m_config.useDummyData	= useDummyData;
+	m_config.write			= write;
+	m_config.use			= use;
+}
+
+MemoryStressCase::~MemoryStressCase (void)
+{
+}
+
+void MemoryStressCase::init (void)
+{
+	if (!m_testCtx.getCommandLine().isOutOfMemoryTestEnabled())
+	{
+		m_testCtx.getLog() << TestLog::Message << "Tests that exhaust memory are disabled, use --deqp-test-oom=enable command line option to enable." << TestLog::EndMessage;
+		throw tcu::NotSupportedError("OOM tests disabled");
+	}
+}
+
+void MemoryStressCase::deinit (void)
+{
+	TCU_CHECK(!m_zeroAlloc);
+}
+
+tcu::TestCase::IterateResult MemoryStressCase::iterate (void)
+{
+	bool			end		= false;
+	tcu::TestLog&	log		= m_testCtx.getLog();
+
+	MemObjectAllocator allocator(log, m_renderCtx, m_objectTypes, m_config, deStringHash(getName()));
+
+	if (!allocator.allocUntilFailure())
+	{
+		// Allocation timed out
+		allocator.clearObjects();
+
+		log << TestLog::Message << "Timeout. Couldn't exhaust memory in timelimit. Allocated " << allocator.getObjectCount() << " objects." << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+	// Try to cancel rendering operations
+	if (m_clearAfterOOM)
+		GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+
+	allocator.clearObjects();
+
+	m_allocated.push_back(allocator.getObjectCount());
+
+	if (m_iteration != 0  && allocator.getObjectCount() == 0)
+		m_zeroAlloc = true;
+
+	log << TestLog::Message << "Got error when allocation object count: " << allocator.getObjectCount() << " bytes: " << allocator.getBytes() << TestLog::EndMessage;
+
+	if ((allocator.getGLError() == 0) && (allocator.getResult() == MemObjectAllocator::RESULT_GOT_BAD_ALLOC))
+	{
+		log << TestLog::Message << "std::bad_alloc" << TestLog::EndMessage;
+		end = true;
+		m_testCtx.setTestResult(QP_TEST_RESULT_RESOURCE_ERROR, "Memory allocation failed");
+	}
+	else if (allocator.getGLError() != GL_OUT_OF_MEMORY)
+	{
+		log << TestLog::Message << "Invalid Error " << MemObjectAllocator::resultToString(allocator.getResult())
+			<< " GLError: " << glErrorToString(allocator.getGLError()) <<
+		TestLog::EndMessage;
+
+		end = true;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+	}
+
+	if ((m_iteration+1) == m_iterationCount)
+	{
+		int min = m_allocated[0];
+		int max = m_allocated[0];
+
+		float threshold = 50.0f;
+
+		for (int allocNdx = 0; allocNdx < (int)m_allocated.size(); allocNdx++)
+		{
+			min = deMin32(m_allocated[allocNdx], min);
+			max = deMax32(m_allocated[allocNdx], max);
+		}
+
+		if (min == 0 && max != 0)
+		{
+			log << TestLog::Message << "Allocation count zero" << TestLog::EndMessage;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		}
+		else
+		{
+			const float change = (min - max) / ((float)(max));
+			if (change > threshold)
+			{
+				log << TestLog::Message << "Allocated objects max: " << max << ", min: " << min << ", difference: " << change << "% threshold: " << threshold << "%" << TestLog::EndMessage;
+				m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Allocation count variation");
+			}
+			else
+				m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		}
+		end = true;
+	}
+
+	GLU_CHECK_CALL(glFinish());
+
+	m_iteration++;
+	if (end)
+		return STOP;
+	else
+		return CONTINUE;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsMemoryStressCase.hpp b/modules/glshared/glsMemoryStressCase.hpp
new file mode 100644
index 0000000..2aacec9
--- /dev/null
+++ b/modules/glshared/glsMemoryStressCase.hpp
@@ -0,0 +1,86 @@
+#ifndef _GLSMEMORYSTRESSCASE_HPP
+#define _GLSMEMORYSTRESSCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES Module
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Memory object stress test
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "gluRenderContext.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gls
+{
+
+enum MemObjectType
+{
+	MEMOBJECTTYPE_TEXTURE = (1 << 0),
+	MEMOBJECTTYPE_BUFFER = (1 << 1),
+
+	MEMOBJECTTYPE_LAST
+};
+
+struct MemObjectConfig
+{
+	int minBufferSize;
+	int maxBufferSize;
+
+	int minTextureSize;
+	int maxTextureSize;
+
+	bool useDummyData;
+	bool write;
+	bool use;
+};
+
+class MemoryStressCase : public tcu::TestCase
+{
+public:
+							MemoryStressCase		(tcu::TestContext& testCtx, glu::RenderContext& renderContext, deUint32 objectTypes, int minTextureSize, int maxTextureSize, int minBufferSize, int maxBufferSize, bool write, bool use, bool useDummyData, bool clearAfterOOM, const char* name, const char* desc);
+							~MemoryStressCase		(void);
+
+	void					init					(void);
+	void					deinit					(void);
+	IterateResult			iterate					(void);
+
+private:
+	int						m_iteration;
+	int						m_iterationCount;
+	std::vector<int>		m_allocated;
+	MemObjectType			m_objectTypes;
+	MemObjectConfig			m_config;
+	bool					m_zeroAlloc;
+	bool					m_clearAfterOOM;
+	glu::RenderContext&		m_renderCtx;
+
+							MemoryStressCase	(const MemoryStressCase&);
+	MemoryStressCase&		operator=			(const MemoryStressCase&);
+};
+
+
+} // gls
+} // deqp
+
+#endif // _GLSMEMORYSTRESSCASE_HPP
diff --git a/modules/glshared/glsRandomShaderCase.cpp b/modules/glshared/glsRandomShaderCase.cpp
new file mode 100644
index 0000000..8f92bbd
--- /dev/null
+++ b/modules/glshared/glsRandomShaderCase.cpp
@@ -0,0 +1,517 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Random shader test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsRandomShaderCase.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluTextureUtil.hpp"
+
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "deRandom.hpp"
+
+#include "rsgProgramGenerator.hpp"
+#include "rsgProgramExecutor.hpp"
+#include "rsgUtils.hpp"
+
+#include "tcuTextureUtil.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "glw.h"
+
+using std::vector;
+using std::string;
+using std::pair;
+using std::map;
+
+namespace deqp
+{
+namespace gls
+{
+
+enum
+{
+	VIEWPORT_WIDTH			= 64,
+	VIEWPORT_HEIGHT			= 64,
+
+	TEXTURE_2D_WIDTH		= 64,
+	TEXTURE_2D_HEIGHT		= 64,
+	TEXTURE_2D_FORMAT		= GL_RGBA,
+	TEXTURE_2D_DATA_TYPE	= GL_UNSIGNED_BYTE,
+
+	TEXTURE_CUBE_SIZE		= 16,
+	TEXTURE_CUBE_FORMAT		= GL_RGBA,
+	TEXTURE_CUBE_DATA_TYPE	= GL_UNSIGNED_BYTE,
+
+	TEXTURE_WRAP_S			= GL_CLAMP_TO_EDGE,
+	TEXTURE_WRAP_T			= GL_CLAMP_TO_EDGE,
+
+	TEXTURE_MIN_FILTER		= GL_LINEAR,
+	TEXTURE_MAG_FILTER		= GL_LINEAR
+};
+
+VertexArray::VertexArray (const rsg::ShaderInput* input, int numVertices)
+	: m_input			(input)
+	, m_vertices		(input->getVariable()->getType().getNumElements() * numVertices)
+{
+}
+
+TextureManager::TextureManager (void)
+{
+}
+
+TextureManager::~TextureManager (void)
+{
+}
+
+void TextureManager::bindTexture (int unit, const glu::Texture2D* tex2D)
+{
+	m_tex2D[unit] = tex2D;
+}
+
+void TextureManager::bindTexture (int unit, const glu::TextureCube* texCube)
+{
+	m_texCube[unit] = texCube;
+}
+
+inline vector<pair<int, const glu::Texture2D*> > TextureManager::getBindings2D (void) const
+{
+	vector<pair<int, const glu::Texture2D*> > bindings;
+	for (map<int, const glu::Texture2D*>::const_iterator i = m_tex2D.begin(); i != m_tex2D.end(); i++)
+		bindings.push_back(*i);
+	return bindings;
+}
+
+inline vector<pair<int, const glu::TextureCube*> > TextureManager::getBindingsCube (void) const
+{
+	vector<pair<int, const glu::TextureCube*> > bindings;
+	for (map<int, const glu::TextureCube*>::const_iterator i = m_texCube.begin(); i != m_texCube.end(); i++)
+		bindings.push_back(*i);
+	return bindings;
+}
+
+RandomShaderCase::RandomShaderCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, const rsg::ProgramParameters& params)
+	: tcu::TestCase		(testCtx, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_parameters		(params)
+	, m_gridWidth		(1)
+	, m_gridHeight		(1)
+	, m_vertexShader	(rsg::Shader::TYPE_VERTEX)
+	, m_fragmentShader	(rsg::Shader::TYPE_FRAGMENT)
+	, m_tex2D			(DE_NULL)
+	, m_texCube			(DE_NULL)
+{
+}
+
+RandomShaderCase::~RandomShaderCase (void)
+{
+	delete m_tex2D;
+	delete m_texCube;
+}
+
+void RandomShaderCase::init (void)
+{
+	// Generate shaders
+	rsg::ProgramGenerator programGenerator;
+	programGenerator.generate(m_parameters, m_vertexShader, m_fragmentShader);
+
+	// Compute uniform values
+	std::vector<const rsg::ShaderInput*>	unifiedUniforms;
+	de::Random								rnd(m_parameters.seed);
+	rsg::computeUnifiedUniforms(m_vertexShader, m_fragmentShader, unifiedUniforms);
+	rsg::computeUniformValues(rnd, m_uniforms, unifiedUniforms);
+
+	// Generate vertices
+	const vector<rsg::ShaderInput*>&	inputs		= m_vertexShader.getInputs();
+	int									numVertices	= (m_gridWidth+1)*(m_gridHeight+1);
+
+	for (vector<rsg::ShaderInput*>::const_iterator i = inputs.begin(); i != inputs.end(); i++)
+	{
+		const rsg::ShaderInput*			input			= *i;
+		rsg::ConstValueRangeAccess		valueRange		= input->getValueRange();
+		int								numComponents	= input->getVariable()->getType().getNumElements();
+		VertexArray						vtxArray(input, numVertices);
+		bool							isPosition		= string(input->getVariable()->getName()) == "dEQP_Position";
+
+		TCU_CHECK(input->getVariable()->getType().getBaseType() == rsg::VariableType::TYPE_FLOAT);
+
+		for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx++)
+		{
+			int		y	= vtxNdx / (m_gridWidth+1);
+			int		x	= vtxNdx - y*(m_gridWidth+1);
+			float	xf	= (float)x / (float)m_gridWidth;
+			float	yf	= (float)y / (float)m_gridHeight;
+			float*	dst	= &vtxArray.getVertices()[vtxNdx*numComponents];
+
+			if (isPosition)
+			{
+				// Position attribute gets special interpolation handling.
+				DE_ASSERT(numComponents == 4);
+				dst[0] = -1.0f + xf *  2.0f;
+				dst[1] =  1.0f + yf * -2.0f;
+				dst[2] = 0.0f;
+				dst[3] = 1.0f;
+			}
+			else
+			{
+				for (int compNdx = 0; compNdx < numComponents; compNdx++)
+				{
+					float	minVal	= valueRange.getMin().component(compNdx).asFloat();
+					float	maxVal	= valueRange.getMax().component(compNdx).asFloat();
+					float	xd, yd;
+
+					rsg::getVertexInterpolationCoords(xd, yd, xf, yf, compNdx);
+
+					float	f		= (xd+yd) / 2.0f;
+
+					dst[compNdx] = minVal + f * (maxVal-minVal);
+				}
+			}
+		}
+
+		m_vertexArrays.push_back(vtxArray);
+	}
+
+	// Generate indices
+	int numQuads	= m_gridWidth*m_gridHeight;
+	int numIndices	= numQuads*6;
+	m_indices.resize(numIndices);
+	for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
+	{
+		int	quadY	= quadNdx / (m_gridWidth);
+		int quadX	= quadNdx - quadY*m_gridWidth;
+
+		m_indices[quadNdx*6+0] = quadX + quadY*(m_gridWidth+1);
+		m_indices[quadNdx*6+1] = quadX + (quadY+1)*(m_gridWidth+1);
+		m_indices[quadNdx*6+2] = quadX + quadY*(m_gridWidth+1) + 1;
+		m_indices[quadNdx*6+3] = m_indices[quadNdx*6+2];
+		m_indices[quadNdx*6+4] = m_indices[quadNdx*6+1];
+		m_indices[quadNdx*6+5] = quadX + (quadY+1)*(m_gridWidth+1) + 1;
+	}
+
+	// Create textures.
+	for (vector<rsg::VariableValue>::const_iterator uniformIter = m_uniforms.begin(); uniformIter != m_uniforms.end(); uniformIter++)
+	{
+		const rsg::VariableType& type = uniformIter->getVariable()->getType();
+
+		if (!type.isSampler())
+			continue;
+
+		int unitNdx = uniformIter->getValue().asInt(0);
+
+		if (type == rsg::VariableType(rsg::VariableType::TYPE_SAMPLER_2D, 1))
+			m_texManager.bindTexture(unitNdx, getTex2D());
+		else if (type == rsg::VariableType(rsg::VariableType::TYPE_SAMPLER_CUBE, 1))
+			m_texManager.bindTexture(unitNdx, getTexCube());
+		else
+			DE_ASSERT(DE_FALSE);
+	}
+}
+
+const glu::Texture2D* RandomShaderCase::getTex2D (void)
+{
+	if (!m_tex2D)
+	{
+		m_tex2D = new glu::Texture2D(m_renderCtx, TEXTURE_2D_FORMAT, TEXTURE_2D_DATA_TYPE, TEXTURE_2D_WIDTH, TEXTURE_2D_HEIGHT);
+
+		m_tex2D->getRefTexture().allocLevel(0);
+		tcu::fillWithComponentGradients(m_tex2D->getRefTexture().getLevel(0), tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f));
+		m_tex2D->upload();
+
+		// Setup parameters.
+		glBindTexture(GL_TEXTURE_2D, m_tex2D->getGLTexture());
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		TEXTURE_WRAP_S);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		TEXTURE_WRAP_T);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	TEXTURE_MIN_FILTER);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	TEXTURE_MAG_FILTER);
+
+		GLU_CHECK();
+	}
+
+	return m_tex2D;
+}
+
+const glu::TextureCube* RandomShaderCase::getTexCube (void)
+{
+	if (!m_texCube)
+	{
+		m_texCube = new glu::TextureCube(m_renderCtx, TEXTURE_CUBE_FORMAT, TEXTURE_CUBE_DATA_TYPE, TEXTURE_CUBE_SIZE);
+
+		static const tcu::Vec4 gradients[tcu::CUBEFACE_LAST][2] =
+		{
+			{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
+			{ tcu::Vec4( 0.0f, -1.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
+			{ tcu::Vec4(-1.0f,  0.0f, -1.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
+			{ tcu::Vec4(-1.0f, -1.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
+			{ tcu::Vec4(-1.0f, -1.0f, -1.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
+			{ tcu::Vec4( 0.0f,  0.0f,  0.0f, 2.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
+		};
+
+		// Fill level 0.
+		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		{
+			m_texCube->getRefTexture().allocLevel((tcu::CubeFace)face, 0);
+			tcu::fillWithComponentGradients(m_texCube->getRefTexture().getLevelFace(0, (tcu::CubeFace)face), gradients[face][0], gradients[face][1]);
+		}
+
+		m_texCube->upload();
+
+		// Setup parameters.
+		glBindTexture(GL_TEXTURE_CUBE_MAP, m_texCube->getGLTexture());
+		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		TEXTURE_WRAP_S);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		TEXTURE_WRAP_T);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	TEXTURE_MIN_FILTER);
+		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	TEXTURE_MAG_FILTER);
+
+		GLU_CHECK();
+	}
+
+	return m_texCube;
+}
+
+void RandomShaderCase::deinit (void)
+{
+	delete m_tex2D;
+	delete m_texCube;
+
+	m_tex2D		= DE_NULL;
+	m_texCube	= DE_NULL;
+
+	// Free up memory
+	m_vertexArrays.clear();
+	m_indices.clear();
+}
+
+namespace
+{
+
+void setUniformValue (int location, rsg::ConstValueAccess value)
+{
+	DE_STATIC_ASSERT(sizeof(rsg::Scalar) == sizeof(float));
+	DE_STATIC_ASSERT(sizeof(rsg::Scalar) == sizeof(int));
+
+	switch (value.getType().getBaseType())
+	{
+		case rsg::VariableType::TYPE_FLOAT:
+			switch (value.getType().getNumElements())
+			{
+				case 1:		glUniform1fv(location, 1, (float*)value.value().getValuePtr());		break;
+				case 2:		glUniform2fv(location, 1, (float*)value.value().getValuePtr());		break;
+				case 3:		glUniform3fv(location, 1, (float*)value.value().getValuePtr());		break;
+				case 4:		glUniform4fv(location, 1, (float*)value.value().getValuePtr());		break;
+				default:	TCU_FAIL("Unsupported type");										break;
+			}
+			break;
+
+		case rsg::VariableType::TYPE_INT:
+		case rsg::VariableType::TYPE_BOOL:
+		case rsg::VariableType::TYPE_SAMPLER_2D:
+		case rsg::VariableType::TYPE_SAMPLER_CUBE:
+			switch (value.getType().getNumElements())
+			{
+				case 1:		glUniform1iv(location, 1, (int*)value.value().getValuePtr());		break;
+				case 2:		glUniform2iv(location, 1, (int*)value.value().getValuePtr());		break;
+				case 3:		glUniform3iv(location, 1, (int*)value.value().getValuePtr());		break;
+				case 4:		glUniform4iv(location, 1, (int*)value.value().getValuePtr());		break;
+				default:	TCU_FAIL("Unsupported type");										break;
+			}
+			break;
+
+		default:
+			TCU_FAIL("Unsupported type");
+	}
+}
+
+tcu::MessageBuilder& operator<< (tcu::MessageBuilder& message, rsg::ConstValueAccess value)
+{
+	const char*	scalarType	= DE_NULL;
+	const char* vecType		= DE_NULL;
+
+	switch (value.getType().getBaseType())
+	{
+		case rsg::VariableType::TYPE_FLOAT:			scalarType = "float";	vecType	= "vec";	break;
+		case rsg::VariableType::TYPE_INT:			scalarType = "int";		vecType = "ivec";	break;
+		case rsg::VariableType::TYPE_BOOL:			scalarType = "bool";	vecType = "bvec";	break;
+		case rsg::VariableType::TYPE_SAMPLER_2D:	scalarType = "sampler2D";					break;
+		case rsg::VariableType::TYPE_SAMPLER_CUBE:	scalarType = "samplerCube";					break;
+		default:
+			TCU_FAIL("Unsupported type.");
+	}
+
+	int numElements = value.getType().getNumElements();
+	if (numElements == 1)
+		message << scalarType << "(";
+	else
+		message << vecType << numElements << "(";
+
+	for (int elementNdx = 0; elementNdx < numElements; elementNdx++)
+	{
+		if (elementNdx > 0)
+			message << ", ";
+
+		switch (value.getType().getBaseType())
+		{
+			case rsg::VariableType::TYPE_FLOAT:			message << value.component(elementNdx).asFloat();						break;
+			case rsg::VariableType::TYPE_INT:			message << value.component(elementNdx).asInt();							break;
+			case rsg::VariableType::TYPE_BOOL:			message << (value.component(elementNdx).asBool() ? "true" : "false");	break;
+			case rsg::VariableType::TYPE_SAMPLER_2D:	message << value.component(elementNdx).asInt();							break;
+			case rsg::VariableType::TYPE_SAMPLER_CUBE:	message << value.component(elementNdx).asInt();							break;
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+
+	message << ")";
+
+	return message;
+}
+
+tcu::MessageBuilder& operator<< (tcu::MessageBuilder& message, rsg::ConstValueRangeAccess valueRange)
+{
+	return message << valueRange.getMin() << " -> " << valueRange.getMax();
+}
+
+} // anonymous
+
+RandomShaderCase::IterateResult RandomShaderCase::iterate (void)
+{
+	tcu::TestLog& log = m_testCtx.getLog();
+
+	// Compile program
+	glu::ShaderProgram program(m_renderCtx, glu::makeVtxFragSources(m_vertexShader.getSource(), m_fragmentShader.getSource()));
+	log << program;
+
+	if (!program.isOk())
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to compile shader");
+		return STOP;
+	}
+
+	// Compute random viewport
+	de::Random				rnd				(m_parameters.seed);
+	int						viewportWidth	= de::min<int>(VIEWPORT_WIDTH,	m_renderCtx.getRenderTarget().getWidth());
+	int						viewportHeight	= de::min<int>(VIEWPORT_HEIGHT,	m_renderCtx.getRenderTarget().getHeight());
+	int						viewportX		= rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth()	- viewportWidth);
+	int						viewportY		= rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight()	- viewportHeight);
+	bool					hasAlpha		= m_renderCtx.getRenderTarget().getPixelFormat().alphaBits > 0;
+	tcu::TextureLevel		rendered		(tcu::TextureFormat(hasAlpha ? tcu::TextureFormat::RGBA : tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), viewportWidth, viewportHeight);
+	tcu::TextureLevel		reference		(tcu::TextureFormat(hasAlpha ? tcu::TextureFormat::RGBA : tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), viewportWidth, viewportHeight);
+
+	// Reference program executor.
+	rsg::ProgramExecutor	executor		(reference.getAccess(), m_gridWidth, m_gridHeight);
+
+	GLU_CHECK_CALL(glUseProgram(program.getProgram()));
+
+	// Set up attributes
+	for (vector<VertexArray>::const_iterator attribIter = m_vertexArrays.begin(); attribIter != m_vertexArrays.end(); attribIter++)
+	{
+		GLint location = glGetAttribLocation(program.getProgram(), attribIter->getName());
+
+		// Print to log.
+		log << tcu::TestLog::Message << "attribute[" << location << "]: " << attribIter->getName() << " = " << attribIter->getValueRange() << tcu::TestLog::EndMessage;
+
+		if (location >= 0)
+		{
+			glVertexAttribPointer(location, attribIter->getNumComponents(), GL_FLOAT, GL_FALSE, 0, &attribIter->getVertices()[0]);
+			glEnableVertexAttribArray(location);
+		}
+	}
+	GLU_CHECK_MSG("After attribute setup");
+
+	// Uniforms
+	for (vector<rsg::VariableValue>::const_iterator uniformIter = m_uniforms.begin(); uniformIter != m_uniforms.end(); uniformIter++)
+	{
+		GLint location = glGetUniformLocation(program.getProgram(), uniformIter->getVariable()->getName());
+
+		log << tcu::TestLog::Message << "uniform[" << location << "]: " << uniformIter->getVariable()->getName() << " = " << uniformIter->getValue() << tcu::TestLog::EndMessage;
+
+		if (location >= 0)
+			setUniformValue(location, uniformIter->getValue());
+	}
+	GLU_CHECK_MSG("After uniform setup");
+
+	// Textures
+	vector<pair<int, const glu::Texture2D*> >	tex2DBindings		= m_texManager.getBindings2D();
+	vector<pair<int, const glu::TextureCube*> >	texCubeBindings		= m_texManager.getBindingsCube();
+
+	for (vector<pair<int, const glu::Texture2D*> >::const_iterator i = tex2DBindings.begin(); i != tex2DBindings.end(); i++)
+	{
+		int						unitNdx		= i->first;
+		const glu::Texture2D*	texture		= i->second;
+
+		glActiveTexture(GL_TEXTURE0 + unitNdx);
+		glBindTexture(GL_TEXTURE_2D, texture->getGLTexture());
+
+		executor.setTexture(unitNdx, &texture->getRefTexture(), glu::mapGLSampler(TEXTURE_WRAP_S, TEXTURE_WRAP_T, TEXTURE_MIN_FILTER, TEXTURE_MAG_FILTER));
+	}
+	GLU_CHECK_MSG("After 2D texture setup");
+
+	for (vector<pair<int, const glu::TextureCube*> >::const_iterator i = texCubeBindings.begin(); i != texCubeBindings.end(); i++)
+	{
+		int						unitNdx		= i->first;
+		const glu::TextureCube*	texture		= i->second;
+
+		glActiveTexture(GL_TEXTURE0 + unitNdx);
+		glBindTexture(GL_TEXTURE_CUBE_MAP, texture->getGLTexture());
+
+		executor.setTexture(unitNdx, &texture->getRefTexture(), glu::mapGLSampler(TEXTURE_WRAP_S, TEXTURE_WRAP_T, TEXTURE_MIN_FILTER, TEXTURE_MAG_FILTER));
+	}
+	GLU_CHECK_MSG("After cubemap setup");
+
+	// Draw and read
+	glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
+	glDrawElements(GL_TRIANGLES, (GLsizei)m_indices.size(), GL_UNSIGNED_SHORT, &m_indices[0]);
+	glFlush();
+	GLU_CHECK_MSG("Draw");
+
+	// Render reference while GPU is doing work
+	executor.execute(m_vertexShader, m_fragmentShader, m_uniforms);
+
+	if (rendered.getFormat().order != tcu::TextureFormat::RGBA || rendered.getFormat().type != tcu::TextureFormat::UNORM_INT8)
+	{
+		// Read as GL_RGBA8
+		tcu::TextureLevel readBuf(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), rendered.getWidth(), rendered.getHeight());
+		glu::readPixels(m_renderCtx, viewportX, viewportY, readBuf.getAccess());
+		GLU_CHECK_MSG("Read pixels");
+		tcu::copy(rendered, readBuf);
+	}
+	else
+		glu::readPixels(m_renderCtx, viewportX, viewportY, rendered.getAccess());
+
+	// Compare
+	{
+		float	threshold	= 0.02f;
+		bool	imagesOk	= tcu::fuzzyCompare(log, "Result", "Result images", reference.getAccess(), rendered.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
+
+		if (imagesOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+	}
+
+	return STOP;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsRandomShaderCase.hpp b/modules/glshared/glsRandomShaderCase.hpp
new file mode 100644
index 0000000..08b59d3
--- /dev/null
+++ b/modules/glshared/glsRandomShaderCase.hpp
@@ -0,0 +1,114 @@
+#ifndef _GLSRANDOMSHADERCASE_HPP
+#define _GLSRANDOMSHADERCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Random shader test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "gluRenderContext.hpp"
+#include "rsgParameters.hpp"
+#include "tcuSurface.hpp"
+#include "rsgShader.hpp"
+#include "rsgVariableValue.hpp"
+#include "gluTexture.hpp"
+
+#include <string>
+#include <vector>
+#include <map>
+
+namespace deqp
+{
+namespace gls
+{
+
+class VertexArray
+{
+public:
+								VertexArray			(const rsg::ShaderInput* input, int numVertices);
+								~VertexArray		(void) {}
+
+	const std::vector<float>&	getVertices			(void) const	{ return m_vertices;		}
+	std::vector<float>&			getVertices			(void)			{ return m_vertices;		}
+	const char*					getName				(void) const	{ return m_input->getVariable()->getName();						}
+	int							getNumComponents	(void) const	{ return m_input->getVariable()->getType().getNumElements();	}
+	rsg::ConstValueRangeAccess	getValueRange		(void) const	{ return m_input->getValueRange();								}
+
+private:
+	const rsg::ShaderInput*		m_input;
+	std::vector<float>			m_vertices;
+};
+
+class TextureManager
+{
+public:
+															TextureManager		(void);
+															~TextureManager		(void);
+
+	void													bindTexture			(int unit, const glu::Texture2D* tex2D);
+	void													bindTexture			(int unit, const glu::TextureCube* texCube);
+
+	std::vector<std::pair<int, const glu::Texture2D*> >		getBindings2D		(void) const;
+	std::vector<std::pair<int, const glu::TextureCube*> >	getBindingsCube		(void) const;
+
+private:
+	std::map<int, const glu::Texture2D*>					m_tex2D;
+	std::map<int, const glu::TextureCube*>					m_texCube;
+};
+
+class RandomShaderCase : public tcu::TestCase
+{
+public:
+									RandomShaderCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, const rsg::ProgramParameters& params);
+	virtual							~RandomShaderCase		(void);
+
+	virtual void					init					(void);
+	virtual void					deinit					(void);
+	virtual IterateResult			iterate					(void);
+
+protected:
+	glu::RenderContext&				m_renderCtx;
+
+	// \todo [2011-12-21 pyry] Multiple textures!
+	const glu::Texture2D*			getTex2D				(void);
+	const glu::TextureCube*			getTexCube				(void);
+
+	rsg::ProgramParameters			m_parameters;
+	int								m_gridWidth;
+	int								m_gridHeight;
+
+	rsg::Shader						m_vertexShader;
+	rsg::Shader						m_fragmentShader;
+	std::vector<rsg::VariableValue>	m_uniforms;
+
+	std::vector<VertexArray>		m_vertexArrays;
+	std::vector<deUint16>			m_indices;
+
+	TextureManager					m_texManager;
+	glu::Texture2D*					m_tex2D;
+	glu::TextureCube*				m_texCube;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSRANDOMSHADERCASE_HPP
diff --git a/modules/glshared/glsRandomShaderProgram.cpp b/modules/glshared/glsRandomShaderProgram.cpp
new file mode 100644
index 0000000..79da301
--- /dev/null
+++ b/modules/glshared/glsRandomShaderProgram.cpp
@@ -0,0 +1,308 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief sglr-rsg adaptation.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsRandomShaderProgram.hpp"
+#include "rsgShader.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+
+using std::vector;
+
+static rr::GenericVecType mapToGenericVecType (const rsg::VariableType& varType)
+{
+	if (varType.isFloatOrVec())
+		return rr::GENERICVECTYPE_FLOAT;
+	else if (varType.isIntOrVec())
+		return rr::GENERICVECTYPE_INT32;
+	else
+	{
+		DE_ASSERT(false);
+		return rr::GENERICVECTYPE_LAST;
+	}
+}
+
+static glu::DataType mapToBasicType (const rsg::VariableType& varType)
+{
+	if (varType.isFloatOrVec() || varType.isIntOrVec() || varType.isBoolOrVec())
+	{
+		const glu::DataType		scalarType		= varType.isFloatOrVec()	? glu::TYPE_FLOAT	:
+												  varType.isIntOrVec()		? glu::TYPE_INT		:
+												  varType.isBoolOrVec()		? glu::TYPE_BOOL	: glu::TYPE_LAST;
+		const int				numComps		= varType.getNumElements();
+
+		DE_ASSERT(de::inRange(numComps, 1, 4));
+		return glu::DataType(scalarType + numComps - 1);
+	}
+	else if (varType.getBaseType() == rsg::VariableType::TYPE_SAMPLER_2D)
+		return glu::TYPE_SAMPLER_2D;
+	else if (varType.getBaseType() == rsg::VariableType::TYPE_SAMPLER_CUBE)
+		return glu::TYPE_SAMPLER_CUBE;
+	else
+	{
+		DE_ASSERT(false);
+		return glu::TYPE_LAST;
+	}
+}
+
+static void generateProgramDeclaration (sglr::pdec::ShaderProgramDeclaration& decl, const rsg::Shader& vertexShader, const rsg::Shader& fragmentShader, int numUnifiedUniforms, const rsg::ShaderInput* const* unifiedUniforms)
+{
+	decl << sglr::pdec::VertexSource(vertexShader.getSource())
+		 << sglr::pdec::FragmentSource(fragmentShader.getSource());
+
+	for (vector<rsg::ShaderInput*>::const_iterator vtxInIter = vertexShader.getInputs().begin(); vtxInIter != vertexShader.getInputs().end(); ++vtxInIter)
+	{
+		const rsg::ShaderInput*	vertexInput	= *vtxInIter;
+		decl << sglr::pdec::VertexAttribute(vertexInput->getVariable()->getName(), mapToGenericVecType(vertexInput->getVariable()->getType()));
+	}
+
+	for (vector<rsg::ShaderInput*>::const_iterator fragInIter = fragmentShader.getInputs().begin(); fragInIter != fragmentShader.getInputs().end(); ++fragInIter)
+	{
+		const rsg::ShaderInput*	fragInput	= *fragInIter;
+		decl << sglr::pdec::VertexToFragmentVarying(mapToGenericVecType(fragInput->getVariable()->getType()));
+	}
+
+	for (int uniformNdx = 0; uniformNdx < numUnifiedUniforms; uniformNdx++)
+	{
+		const rsg::ShaderInput*	uniform	= unifiedUniforms[uniformNdx];
+		decl << sglr::pdec::Uniform(uniform->getVariable()->getName(), mapToBasicType(uniform->getVariable()->getType()));
+	}
+
+	decl << sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT);
+}
+
+static sglr::pdec::ShaderProgramDeclaration generateProgramDeclaration (const rsg::Shader& vertexShader, const rsg::Shader& fragmentShader, int numUnifiedUniforms, const rsg::ShaderInput* const* unifiedUniforms)
+{
+	sglr::pdec::ShaderProgramDeclaration decl;
+	generateProgramDeclaration(decl, vertexShader, fragmentShader, numUnifiedUniforms, unifiedUniforms);
+	return decl;
+}
+
+static const rsg::Variable* findShaderOutputByName (const rsg::Shader& shader, const char* name)
+{
+	vector<const rsg::Variable*> outputs;
+	shader.getOutputs(outputs);
+
+	for (vector<const rsg::Variable*>::const_iterator iter = outputs.begin(); iter != outputs.end(); ++iter)
+	{
+		if (deStringEqual((*iter)->getName(), name))
+			return *iter;
+	}
+
+	return DE_NULL;
+}
+
+static const rsg::Variable* findShaderOutputByLocation (const rsg::Shader& shader, int location)
+{
+	vector<const rsg::Variable*> outputs;
+	shader.getOutputs(outputs);
+
+	for (vector<const rsg::Variable*>::const_iterator iter = outputs.begin(); iter != outputs.end(); iter++)
+	{
+		if ((*iter)->getLayoutLocation() == location)
+			return *iter;
+	}
+
+	return DE_NULL;
+}
+
+RandomShaderProgram::RandomShaderProgram (const rsg::Shader& vertexShader, const rsg::Shader& fragmentShader, int numUnifiedUniforms, const rsg::ShaderInput* const* unifiedUniforms)
+	: sglr::ShaderProgram	(generateProgramDeclaration(vertexShader, fragmentShader, numUnifiedUniforms, unifiedUniforms))
+	, m_vertexShader		(vertexShader)
+	, m_fragmentShader		(fragmentShader)
+	, m_numUnifiedUniforms	(numUnifiedUniforms)
+	, m_unifiedUniforms		(unifiedUniforms)
+	, m_positionVar			(findShaderOutputByName(vertexShader, "gl_Position"))
+	, m_fragColorVar		(findShaderOutputByLocation(fragmentShader, 0))
+	, m_execCtx				(m_sampler2DMap, m_samplerCubeMap)
+{
+	TCU_CHECK_INTERNAL(m_positionVar && m_positionVar->getType().getBaseType() == rsg::VariableType::TYPE_FLOAT && m_positionVar->getType().getNumElements() == 4);
+	TCU_CHECK_INTERNAL(m_fragColorVar && m_fragColorVar->getType().getBaseType() == rsg::VariableType::TYPE_FLOAT && m_fragColorVar->getType().getNumElements() == 4);
+
+	// Build list of vertex outputs.
+	for (vector<rsg::ShaderInput*>::const_iterator fragInIter = fragmentShader.getInputs().begin(); fragInIter != fragmentShader.getInputs().end(); ++fragInIter)
+	{
+		const rsg::ShaderInput*	fragInput		= *fragInIter;
+		const rsg::Variable*	vertexOutput	= findShaderOutputByName(vertexShader, fragInput->getVariable()->getName());
+
+		TCU_CHECK_INTERNAL(vertexOutput);
+		m_vertexOutputs.push_back(vertexOutput);
+	}
+}
+
+void RandomShaderProgram::refreshUniforms (void) const
+{
+	DE_ASSERT(m_numUnifiedUniforms == (int)m_uniforms.size());
+
+	for (int uniformNdx = 0; uniformNdx < m_numUnifiedUniforms; uniformNdx++)
+	{
+		const rsg::Variable*		uniformVar	= m_unifiedUniforms[uniformNdx]->getVariable();
+		const rsg::VariableType&	uniformType	= uniformVar->getType();
+		const sglr::UniformSlot&	uniformSlot	= m_uniforms[uniformNdx];
+
+		m_execCtx.getValue(uniformVar) = rsg::ConstValueAccess(uniformType, (const rsg::Scalar*)&uniformSlot.value).value();
+	}
+}
+
+void RandomShaderProgram::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	// \todo [2013-12-13 pyry] Do only when necessary.
+	refreshUniforms();
+
+	int packetOffset = 0;
+
+	while (packetOffset < numPackets)
+	{
+		const int	numToExecute	= de::min(numPackets-packetOffset, (int)rsg::EXEC_VEC_WIDTH);
+
+		// Fetch attributes.
+		for (int attribNdx = 0; attribNdx < (int)m_vertexShader.getInputs().size(); ++attribNdx)
+		{
+			const rsg::Variable*		attribVar		= m_vertexShader.getInputs()[attribNdx]->getVariable();
+			const rsg::VariableType&	attribType		= attribVar->getType();
+			const int					numComponents	= attribType.getNumElements();
+			rsg::ExecValueAccess		access			= m_execCtx.getValue(attribVar);
+
+			DE_ASSERT(attribType.isFloatOrVec() && de::inRange(numComponents, 1, 4));
+
+			for (int ndx = 0; ndx < numToExecute; ndx++)
+			{
+				const int				packetNdx	= ndx+packetOffset;
+				const rr::VertexPacket*	packet		= packets[packetNdx];
+				const tcu::Vec4			attribValue	= rr::readVertexAttribFloat(inputs[attribNdx], packet->instanceNdx, packet->vertexNdx);
+
+										access.component(0).asFloat(ndx) = attribValue[0];
+				if (numComponents >= 2)	access.component(1).asFloat(ndx) = attribValue[1];
+				if (numComponents >= 3)	access.component(2).asFloat(ndx) = attribValue[2];
+				if (numComponents >= 4)	access.component(3).asFloat(ndx) = attribValue[3];
+			}
+		}
+
+		m_vertexShader.execute(m_execCtx);
+
+		// Store position
+		{
+			const rsg::ExecConstValueAccess	access	= m_execCtx.getValue(m_positionVar);
+
+			for (int ndx = 0; ndx < numToExecute; ndx++)
+			{
+				const int			packetNdx	= ndx+packetOffset;
+				rr::VertexPacket*	packet		= packets[packetNdx];
+
+				packet->position[0] = access.component(0).asFloat(ndx);
+				packet->position[1] = access.component(1).asFloat(ndx);
+				packet->position[2] = access.component(2).asFloat(ndx);
+				packet->position[3] = access.component(3).asFloat(ndx);
+			}
+		}
+
+		// Other varyings
+		for (int varNdx = 0; varNdx < (int)m_vertexOutputs.size(); varNdx++)
+		{
+			const rsg::Variable*			var				= m_vertexOutputs[varNdx];
+			const rsg::VariableType&		varType			= var->getType();
+			const int						numComponents	= varType.getNumElements();
+			const rsg::ExecConstValueAccess	access			= m_execCtx.getValue(var);
+
+			DE_ASSERT(varType.isFloatOrVec() && de::inRange(numComponents, 1, 4));
+
+			for (int ndx = 0; ndx < numToExecute; ndx++)
+			{
+				const int				packetNdx	= ndx+packetOffset;
+				rr::VertexPacket* const	packet		= packets[packetNdx];
+				float* const			dst			= packet->outputs[varNdx].getAccess<float>();
+
+										dst[0] = access.component(0).asFloat(ndx);
+				if (numComponents >= 2) dst[1] = access.component(1).asFloat(ndx);
+				if (numComponents >= 3) dst[2] = access.component(2).asFloat(ndx);
+				if (numComponents >= 4) dst[3] = access.component(3).asFloat(ndx);
+			}
+		}
+
+		packetOffset += numToExecute;
+	}
+}
+
+void RandomShaderProgram::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const rsg::ExecConstValueAccess	fragColorAccess	= m_execCtx.getValue(m_fragColorVar);
+	int								packetOffset	= 0;
+
+	DE_STATIC_ASSERT(rsg::EXEC_VEC_WIDTH % rr::NUM_FRAGMENTS_PER_PACKET == 0);
+
+	while (packetOffset < numPackets)
+	{
+		const int	numPacketsToExecute	= de::min(numPackets-packetOffset, (int)rsg::EXEC_VEC_WIDTH / (int)rr::NUM_FRAGMENTS_PER_PACKET);
+
+		// Interpolate varyings.
+		for (int varNdx = 0; varNdx < (int)m_fragmentShader.getInputs().size(); ++varNdx)
+		{
+			const rsg::Variable*		var				= m_fragmentShader.getInputs()[varNdx]->getVariable();
+			const rsg::VariableType&	varType			= var->getType();
+			const int					numComponents	= varType.getNumElements();
+			rsg::ExecValueAccess		access			= m_execCtx.getValue(var);
+
+			DE_ASSERT(varType.isFloatOrVec() && de::inRange(numComponents, 1, 4));
+
+			for (int packetNdx = 0; packetNdx < numPacketsToExecute; packetNdx++)
+			{
+				const rr::FragmentPacket&	packet		= packets[packetOffset+packetNdx];
+
+				for (int fragNdx = 0; fragNdx < rr::NUM_FRAGMENTS_PER_PACKET; fragNdx++)
+				{
+					const tcu::Vec4		varValue	= rr::readVarying<float>(packet, context, varNdx, fragNdx);
+					const int			dstNdx		= packetNdx*rr::NUM_FRAGMENTS_PER_PACKET + fragNdx;
+
+											access.component(0).asFloat(dstNdx) = varValue[0];
+					if (numComponents >= 2)	access.component(1).asFloat(dstNdx) = varValue[1];
+					if (numComponents >= 3)	access.component(2).asFloat(dstNdx) = varValue[2];
+					if (numComponents >= 4)	access.component(3).asFloat(dstNdx) = varValue[3];
+				}
+			}
+		}
+
+		m_fragmentShader.execute(m_execCtx);
+
+		// Store color
+		for (int packetNdx = 0; packetNdx < numPacketsToExecute; packetNdx++)
+		{
+			for (int fragNdx = 0; fragNdx < rr::NUM_FRAGMENTS_PER_PACKET; fragNdx++)
+			{
+				const int		srcNdx	= packetNdx*rr::NUM_FRAGMENTS_PER_PACKET + fragNdx;
+				const tcu::Vec4 color	(fragColorAccess.component(0).asFloat(srcNdx),
+										 fragColorAccess.component(1).asFloat(srcNdx),
+										 fragColorAccess.component(2).asFloat(srcNdx),
+										 fragColorAccess.component(3).asFloat(srcNdx));
+
+				rr::writeFragmentOutput(context, packetOffset+packetNdx, fragNdx, 0, color);
+			}
+		}
+
+		packetOffset += numPacketsToExecute;
+	}
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsRandomShaderProgram.hpp b/modules/glshared/glsRandomShaderProgram.hpp
new file mode 100644
index 0000000..3de574d
--- /dev/null
+++ b/modules/glshared/glsRandomShaderProgram.hpp
@@ -0,0 +1,69 @@
+#ifndef _GLSRANDOMSHADERPROGRAM_HPP
+#define _GLSRANDOMSHADERPROGRAM_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief sglr-rsg adaptation.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "sglrContext.hpp"
+#include "rsgExecutionContext.hpp"
+
+namespace rsg
+{
+class Shader;
+class ShaderInput;
+}
+
+namespace deqp
+{
+namespace gls
+{
+
+class RandomShaderProgram : public sglr::ShaderProgram
+{
+public:
+										RandomShaderProgram			(const rsg::Shader& vertexShader, const rsg::Shader& fragmentShader, int numUnifiedUniforms, const rsg::ShaderInput* const* unifiedUniforms);
+
+private:
+	virtual void						shadeVertices				(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	virtual void						shadeFragments				(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+	void								refreshUniforms				(void) const;
+
+	const rsg::Shader&					m_vertexShader;
+	const rsg::Shader&					m_fragmentShader;
+	const int							m_numUnifiedUniforms;
+	const rsg::ShaderInput* const*		m_unifiedUniforms;
+
+	const rsg::Variable*				m_positionVar;
+	std::vector<const rsg::Variable*>	m_vertexOutputs;			//!< Other vertex outputs in the order they are passed to fragment shader.
+	const rsg::Variable*				m_fragColorVar;
+
+	rsg::Sampler2DMap					m_sampler2DMap;
+	rsg::SamplerCubeMap					m_samplerCubeMap;
+	mutable rsg::ExecutionContext		m_execCtx;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSRANDOMSHADERPROGRAM_HPP
diff --git a/modules/glshared/glsRandomUniformBlockCase.cpp b/modules/glshared/glsRandomUniformBlockCase.cpp
new file mode 100644
index 0000000..f1df39b
--- /dev/null
+++ b/modules/glshared/glsRandomUniformBlockCase.cpp
@@ -0,0 +1,248 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Random uniform block layout case.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsRandomUniformBlockCase.hpp"
+#include "tcuCommandLine.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+
+using std::string;
+using std::vector;
+
+namespace deqp
+{
+namespace gls
+{
+
+using namespace gls::ub;
+
+RandomUniformBlockCase::RandomUniformBlockCase (tcu::TestContext&	testCtx,
+												glu::RenderContext&	renderCtx,
+												glu::GLSLVersion	glslVersion,
+												const char*			name,
+												const char*			description,
+												BufferMode			bufferMode,
+												deUint32			features,
+												deUint32			seed)
+	: UniformBlockCase		(testCtx, renderCtx, name, description, glslVersion, bufferMode)
+	, m_features			(features)
+	, m_maxVertexBlocks		((features & FEATURE_VERTEX_BLOCKS)		? 4 : 0)
+	, m_maxFragmentBlocks	((features & FEATURE_FRAGMENT_BLOCKS)	? 4 : 0)
+	, m_maxSharedBlocks		((features & FEATURE_SHARED_BLOCKS)		? 4 : 0)
+	, m_maxInstances		((features & FEATURE_INSTANCE_ARRAYS)	? 3 : 0)
+	, m_maxArrayLength		((features & FEATURE_ARRAYS)			? 8 : 0)
+	, m_maxStructDepth		((features & FEATURE_STRUCTS)			? 2 : 0)
+	, m_maxBlockMembers		(5)
+	, m_maxStructMembers	(4)
+	, m_seed				(seed)
+	, m_blockNdx			(1)
+	, m_uniformNdx			(1)
+	, m_structNdx			(1)
+{
+}
+
+void RandomUniformBlockCase::init (void)
+{
+	de::Random rnd(m_seed);
+
+	int numShared		= m_maxSharedBlocks				> 0	? rnd.getInt(1, m_maxSharedBlocks)				: 0;
+	int numVtxBlocks	= m_maxVertexBlocks-numShared	> 0	? rnd.getInt(1, m_maxVertexBlocks-numShared)	: 0;
+	int	numFragBlocks	= m_maxFragmentBlocks-numShared	> 0 ? rnd.getInt(1, m_maxFragmentBlocks-numShared)	: 0;
+
+	for (int ndx = 0; ndx < numShared; ndx++)
+		generateBlock(rnd, DECLARE_VERTEX|DECLARE_FRAGMENT);
+
+	for (int ndx = 0; ndx < numVtxBlocks; ndx++)
+		generateBlock(rnd, DECLARE_VERTEX);
+
+	for (int ndx = 0; ndx < numFragBlocks; ndx++)
+		generateBlock(rnd, DECLARE_FRAGMENT);
+}
+
+void RandomUniformBlockCase::generateBlock (de::Random& rnd, deUint32 layoutFlags)
+{
+	DE_ASSERT(m_blockNdx <= 'z' - 'a');
+
+	const float		instanceArrayWeight	= 0.3f;
+	UniformBlock&	block				= m_interface.allocBlock((string("Block") + (char)('A' + m_blockNdx)).c_str());
+	int				numInstances		= (m_maxInstances > 0 && rnd.getFloat() < instanceArrayWeight) ? rnd.getInt(0, m_maxInstances) : 0;
+	int				numUniforms			= rnd.getInt(1, m_maxBlockMembers);
+
+	if (numInstances > 0)
+		block.setArraySize(numInstances);
+
+	if (numInstances > 0 || rnd.getBool())
+		block.setInstanceName((string("block") + (char)('A' + m_blockNdx)).c_str());
+
+	// Layout flag candidates.
+	vector<deUint32> layoutFlagCandidates;
+	layoutFlagCandidates.push_back(0);
+	if (m_features & FEATURE_PACKED_LAYOUT)
+		layoutFlagCandidates.push_back(LAYOUT_SHARED);
+	if ((m_features & FEATURE_SHARED_LAYOUT) && ((layoutFlags & DECLARE_BOTH) != DECLARE_BOTH))
+		layoutFlagCandidates.push_back(LAYOUT_PACKED); // \note packed layout can only be used in a single shader stage.
+	if (m_features & FEATURE_STD140_LAYOUT)
+		layoutFlagCandidates.push_back(LAYOUT_STD140);
+
+	layoutFlags |= rnd.choose<deUint32>(layoutFlagCandidates.begin(), layoutFlagCandidates.end());
+
+	if (m_features & FEATURE_MATRIX_LAYOUT)
+	{
+		static const deUint32 matrixCandidates[] = { 0, LAYOUT_ROW_MAJOR, LAYOUT_COLUMN_MAJOR };
+		layoutFlags |= rnd.choose<deUint32>(&matrixCandidates[0], &matrixCandidates[DE_LENGTH_OF_ARRAY(matrixCandidates)]);
+	}
+
+	block.setFlags(layoutFlags);
+
+	for (int ndx = 0; ndx < numUniforms; ndx++)
+		generateUniform(rnd, block);
+
+	m_blockNdx += 1;
+}
+
+static std::string genName (char first, char last, int ndx)
+{
+	std::string	str			= "";
+	int			alphabetLen	= last - first + 1;
+
+	while (ndx > alphabetLen)
+	{
+		str.insert(str.begin(), (char)(first + ((ndx-1)%alphabetLen)));
+		ndx = ((ndx-1) / alphabetLen);
+	}
+
+	str.insert(str.begin(), (char)(first + (ndx%(alphabetLen+1)) - 1));
+
+	return str;
+}
+
+void RandomUniformBlockCase::generateUniform (de::Random& rnd, UniformBlock& block)
+{
+	const float		unusedVtxWeight		= 0.15f;
+	const float		unusedFragWeight	= 0.15f;
+	bool			unusedOk			= (m_features & FEATURE_UNUSED_UNIFORMS) != 0;
+	deUint32		flags				= 0;
+	std::string		name				= genName('a', 'z', m_uniformNdx);
+	VarType			type				= generateType(rnd, 0, true);
+
+	flags |= (unusedOk && rnd.getFloat() < unusedVtxWeight)		? UNUSED_VERTEX		: 0;
+	flags |= (unusedOk && rnd.getFloat() < unusedFragWeight)	? UNUSED_FRAGMENT	: 0;
+
+	block.addUniform(Uniform(name.c_str(), type, flags));
+
+	m_uniformNdx += 1;
+}
+
+VarType RandomUniformBlockCase::generateType (de::Random& rnd, int typeDepth, bool arrayOk)
+{
+	const float structWeight	= 0.1f;
+	const float arrayWeight		= 0.1f;
+
+	if (typeDepth < m_maxStructDepth && rnd.getFloat() < structWeight)
+	{
+		const float		unusedVtxWeight		= 0.15f;
+		const float		unusedFragWeight	= 0.15f;
+		bool			unusedOk			= (m_features & FEATURE_UNUSED_MEMBERS) != 0;
+		vector<VarType>	memberTypes;
+		int				numMembers = rnd.getInt(1, m_maxStructMembers);
+
+		// Generate members first so nested struct declarations are in correct order.
+		for (int ndx = 0; ndx < numMembers; ndx++)
+			memberTypes.push_back(generateType(rnd, typeDepth+1, true));
+
+		StructType& structType = m_interface.allocStruct((string("s") + genName('A', 'Z', m_structNdx)).c_str());
+		m_structNdx += 1;
+
+		DE_ASSERT(numMembers <= 'Z' - 'A');
+		for (int ndx = 0; ndx < numMembers; ndx++)
+		{
+			deUint32 flags = 0;
+
+			flags |= (unusedOk && rnd.getFloat() < unusedVtxWeight)		? UNUSED_VERTEX		: 0;
+			flags |= (unusedOk && rnd.getFloat() < unusedFragWeight)	? UNUSED_FRAGMENT	: 0;
+
+			structType.addMember((string("m") + (char)('A' + ndx)).c_str(), memberTypes[ndx], flags);
+		}
+
+		return VarType(&structType);
+	}
+	else if (m_maxArrayLength > 0 && arrayOk && rnd.getFloat() < arrayWeight)
+	{
+		const bool	arraysOfArraysOk	= (m_features & FEATURE_ARRAYS_OF_ARRAYS) != 0;
+		const int	arrayLength			= rnd.getInt(1, m_maxArrayLength);
+		VarType	elementType	= generateType(rnd, typeDepth, arraysOfArraysOk);
+		return VarType(elementType, arrayLength);
+	}
+	else
+	{
+		vector<glu::DataType> typeCandidates;
+
+		typeCandidates.push_back(glu::TYPE_FLOAT);
+		typeCandidates.push_back(glu::TYPE_INT);
+		typeCandidates.push_back(glu::TYPE_UINT);
+		typeCandidates.push_back(glu::TYPE_BOOL);
+
+		if (m_features & FEATURE_VECTORS)
+		{
+			typeCandidates.push_back(glu::TYPE_FLOAT_VEC2);
+			typeCandidates.push_back(glu::TYPE_FLOAT_VEC3);
+			typeCandidates.push_back(glu::TYPE_FLOAT_VEC4);
+			typeCandidates.push_back(glu::TYPE_INT_VEC2);
+			typeCandidates.push_back(glu::TYPE_INT_VEC3);
+			typeCandidates.push_back(glu::TYPE_INT_VEC4);
+			typeCandidates.push_back(glu::TYPE_UINT_VEC2);
+			typeCandidates.push_back(glu::TYPE_UINT_VEC3);
+			typeCandidates.push_back(glu::TYPE_UINT_VEC4);
+			typeCandidates.push_back(glu::TYPE_BOOL_VEC2);
+			typeCandidates.push_back(glu::TYPE_BOOL_VEC3);
+			typeCandidates.push_back(glu::TYPE_BOOL_VEC4);
+		}
+
+		if (m_features & FEATURE_MATRICES)
+		{
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT2);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT2X3);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT3X2);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT3);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT3X4);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT4X2);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT4X3);
+			typeCandidates.push_back(glu::TYPE_FLOAT_MAT4);
+		}
+
+		glu::DataType	type	= rnd.choose<glu::DataType>(typeCandidates.begin(), typeCandidates.end());
+		deUint32		flags	= 0;
+
+		if (!glu::isDataTypeBoolOrBVec(type))
+		{
+			// Precision.
+			static const deUint32 precisionCandidates[] = { PRECISION_LOW, PRECISION_MEDIUM, PRECISION_HIGH };
+			flags |= rnd.choose<deUint32>(&precisionCandidates[0], &precisionCandidates[DE_LENGTH_OF_ARRAY(precisionCandidates)]);
+		}
+
+		return VarType(type, flags);
+	}
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsRandomUniformBlockCase.hpp b/modules/glshared/glsRandomUniformBlockCase.hpp
new file mode 100644
index 0000000..b1b41aa
--- /dev/null
+++ b/modules/glshared/glsRandomUniformBlockCase.hpp
@@ -0,0 +1,103 @@
+#ifndef _GLSRANDOMUNIFORMBLOCKCASE_HPP
+#define _GLSRANDOMUNIFORMBLOCKCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Random uniform block layout case.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "glsUniformBlockCase.hpp"
+
+namespace de
+{
+class Random;
+} // de
+
+namespace deqp
+{
+namespace gls
+{
+
+namespace ub
+{
+
+enum FeatureBits
+{
+	FEATURE_VECTORS				= (1<<0),
+	FEATURE_MATRICES			= (1<<1),
+	FEATURE_ARRAYS				= (1<<2),
+	FEATURE_STRUCTS				= (1<<3),
+	FEATURE_NESTED_STRUCTS		= (1<<4),
+	FEATURE_INSTANCE_ARRAYS		= (1<<5),
+	FEATURE_VERTEX_BLOCKS		= (1<<6),
+	FEATURE_FRAGMENT_BLOCKS		= (1<<7),
+	FEATURE_SHARED_BLOCKS		= (1<<8),
+	FEATURE_UNUSED_UNIFORMS		= (1<<9),
+	FEATURE_UNUSED_MEMBERS		= (1<<10),
+	FEATURE_PACKED_LAYOUT		= (1<<11),
+	FEATURE_SHARED_LAYOUT		= (1<<12),
+	FEATURE_STD140_LAYOUT		= (1<<13),
+	FEATURE_MATRIX_LAYOUT		= (1<<14),	//!< Matrix layout flags.
+	FEATURE_ARRAYS_OF_ARRAYS	= (1<<15)
+};
+
+} // ub
+
+class RandomUniformBlockCase : public UniformBlockCase
+{
+public:
+							RandomUniformBlockCase		(tcu::TestContext&		testCtx,
+														 glu::RenderContext&	renderCtx,
+														 glu::GLSLVersion		glslVersion,
+														 const char*			name,
+														 const char*			description,
+														 BufferMode				bufferMode,
+														 deUint32				features,
+														 deUint32				seed);
+
+	void					init						(void);
+
+private:
+	void					generateBlock				(de::Random& rnd, deUint32 layoutFlags);
+	void					generateUniform				(de::Random& rnd, ub::UniformBlock& block);
+	ub::VarType				generateType				(de::Random& rnd, int typeDepth, bool arrayOk);
+
+	const deUint32			m_features;
+	const int				m_maxVertexBlocks;
+	const int				m_maxFragmentBlocks;
+	const int				m_maxSharedBlocks;
+	const int				m_maxInstances;
+	const int				m_maxArrayLength;
+	const int				m_maxStructDepth;
+	const int				m_maxBlockMembers;
+	const int				m_maxStructMembers;
+	const deUint32			m_seed;
+
+	int						m_blockNdx;
+	int						m_uniformNdx;
+	int						m_structNdx;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSRANDOMUNIFORMBLOCKCASE_HPP
diff --git a/modules/glshared/glsRasterizationTestUtil.cpp b/modules/glshared/glsRasterizationTestUtil.cpp
new file mode 100644
index 0000000..fd83f2b
--- /dev/null
+++ b/modules/glshared/glsRasterizationTestUtil.cpp
@@ -0,0 +1,1929 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief rasterization test utils.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsRasterizationTestUtil.hpp"
+#include "tcuVector.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuFloat.hpp"
+#include "deMath.h"
+
+#include "rrRasterizer.hpp"
+
+#include <limits>
+
+namespace deqp
+{
+namespace gls
+{
+namespace RasterizationTestUtil
+{
+namespace
+{
+
+bool lineLineIntersect (const tcu::Vector<deInt64, 2>& line0Beg, const tcu::Vector<deInt64, 2>& line0End, const tcu::Vector<deInt64, 2>& line1Beg, const tcu::Vector<deInt64, 2>& line1End)
+{
+	typedef tcu::Vector<deInt64, 2> I64Vec2;
+
+	// Lines do not intersect if the other line's endpoints are on the same side
+	// otherwise, the do intersect
+
+	// Test line 0
+	{
+		const I64Vec2 line			= line0End - line0Beg;
+		const I64Vec2 v0			= line1Beg - line0Beg;
+		const I64Vec2 v1			= line1End - line0Beg;
+		const deInt64 crossProduct0	= (line.x() * v0.y() - line.y() * v0.x());
+		const deInt64 crossProduct1	= (line.x() * v1.y() - line.y() * v1.x());
+
+		// check signs
+		if ((crossProduct0 < 0 && crossProduct1 < 0) ||
+			(crossProduct0 > 0 && crossProduct1 > 0))
+			return false;
+	}
+
+	// Test line 1
+	{
+		const I64Vec2 line			= line1End - line1Beg;
+		const I64Vec2 v0			= line0Beg - line1Beg;
+		const I64Vec2 v1			= line0End - line1Beg;
+		const deInt64 crossProduct0	= (line.x() * v0.y() - line.y() * v0.x());
+		const deInt64 crossProduct1	= (line.x() * v1.y() - line.y() * v1.x());
+
+		// check signs
+		if ((crossProduct0 < 0 && crossProduct1 < 0) ||
+			(crossProduct0 > 0 && crossProduct1 > 0))
+			return false;
+	}
+
+	return true;
+}
+
+bool isTriangleClockwise (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2)
+{
+	const tcu::Vec2	u				(p1.x() / p1.w() - p0.x() / p0.w(), p1.y() / p1.w() - p0.y() / p0.w());
+	const tcu::Vec2	v				(p2.x() / p2.w() - p0.x() / p0.w(), p2.y() / p2.w() - p0.y() / p0.w());
+	const float		crossProduct	= (u.x() * v.y() - u.y() * v.x());
+
+	return crossProduct > 0.0f;
+}
+
+bool compareColors (const tcu::RGBA& colorA, const tcu::RGBA& colorB, int redBits, int greenBits, int blueBits)
+{
+	const int thresholdRed		= 1 << (8 - redBits);
+	const int thresholdGreen	= 1 << (8 - greenBits);
+	const int thresholdBlue		= 1 << (8 - blueBits);
+
+	return	deAbs32(colorA.getRed()   - colorB.getRed())   <= thresholdRed   &&
+			deAbs32(colorA.getGreen() - colorB.getGreen()) <= thresholdGreen &&
+			deAbs32(colorA.getBlue()  - colorB.getBlue())  <= thresholdBlue;
+}
+
+bool pixelNearLineSegment (const tcu::IVec2& pixel, const tcu::Vec2& p0, const tcu::Vec2& p1)
+{
+	const tcu::Vec2 pixelCenterPosition = tcu::Vec2(pixel.x() + 0.5f, pixel.y() + 0.5f);
+
+	// "Near" = Distance from the line to the pixel is less than 2 * pixel_max_radius. (pixel_max_radius = sqrt(2) / 2)
+	const float maxPixelDistance		= 1.414f;
+	const float maxPixelDistanceSquared	= 2.0f;
+
+	// Near the line
+	{
+		const tcu::Vec2	line			= p1                  - p0;
+		const tcu::Vec2	v				= pixelCenterPosition - p0;
+		const float		crossProduct	= (line.x() * v.y() - line.y() * v.x());
+
+		// distance to line: (line x v) / |line|
+		//     |(line x v) / |line|| > maxPixelDistance
+		// ==> (line x v)^2 / |line|^2 > maxPixelDistance^2
+		// ==> (line x v)^2 > maxPixelDistance^2 * |line|^2
+
+		if (crossProduct * crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(line))
+			return false;
+	}
+
+	// Between the endpoints
+	{
+		// distance from line endpoint 1 to pixel is less than line length + maxPixelDistance
+		const float maxDistance = tcu::length(p1 - p0) + maxPixelDistance;
+
+		if (tcu::length(pixelCenterPosition - p0) > maxDistance)
+			return false;
+		if (tcu::length(pixelCenterPosition - p1) > maxDistance)
+			return false;
+	}
+
+	return true;
+}
+
+bool pixelOnlyOnASharedEdge (const tcu::IVec2& pixel, const TriangleSceneSpec::SceneTriangle& triangle, const tcu::IVec2& viewportSize)
+{
+	if (triangle.sharedEdge[0] || triangle.sharedEdge[1] || triangle.sharedEdge[2])
+	{
+		const tcu::Vec2 triangleNormalizedDeviceSpace[3] =
+		{
+			tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(), triangle.positions[0].y() / triangle.positions[0].w()),
+			tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(), triangle.positions[1].y() / triangle.positions[1].w()),
+			tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(), triangle.positions[2].y() / triangle.positions[2].w()),
+		};
+		const tcu::Vec2 triangleScreenSpace[3] =
+		{
+			(triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
+			(triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
+			(triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
+		};
+
+		const bool pixelOnEdge0 = pixelNearLineSegment(pixel, triangleScreenSpace[0], triangleScreenSpace[1]);
+		const bool pixelOnEdge1 = pixelNearLineSegment(pixel, triangleScreenSpace[1], triangleScreenSpace[2]);
+		const bool pixelOnEdge2 = pixelNearLineSegment(pixel, triangleScreenSpace[2], triangleScreenSpace[0]);
+
+		// If the pixel is on a multiple edges return false
+
+		if (pixelOnEdge0 && !pixelOnEdge1 && !pixelOnEdge2)
+			return triangle.sharedEdge[0];
+		if (!pixelOnEdge0 && pixelOnEdge1 && !pixelOnEdge2)
+			return triangle.sharedEdge[1];
+		if (!pixelOnEdge0 && !pixelOnEdge1 && pixelOnEdge2)
+			return triangle.sharedEdge[2];
+	}
+
+	return false;
+}
+
+float triangleArea (const tcu::Vec2& s0, const tcu::Vec2& s1, const tcu::Vec2& s2)
+{
+	const tcu::Vec2	u				(s1.x() - s0.x(), s1.y() - s0.y());
+	const tcu::Vec2	v				(s2.x() - s0.x(), s2.y() - s0.y());
+	const float		crossProduct	= (u.x() * v.y() - u.y() * v.x());
+
+	return crossProduct / 2.0f;
+}
+
+tcu::IVec4 getTriangleAABB (const TriangleSceneSpec::SceneTriangle& triangle, const tcu::IVec2& viewportSize)
+{
+	const tcu::Vec2 normalizedDeviceSpace[3] =
+	{
+		tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(), triangle.positions[0].y() / triangle.positions[0].w()),
+		tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(), triangle.positions[1].y() / triangle.positions[1].w()),
+		tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(), triangle.positions[2].y() / triangle.positions[2].w()),
+	};
+	const tcu::Vec2 screenSpace[3] =
+	{
+		(normalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
+		(normalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
+		(normalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
+	};
+
+	tcu::IVec4 aabb;
+
+	aabb.x() = (int)deFloatFloor(de::min(de::min(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x()));
+	aabb.y() = (int)deFloatFloor(de::min(de::min(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y()));
+	aabb.z() = (int)deFloatCeil (de::max(de::max(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x()));
+	aabb.w() = (int)deFloatCeil (de::max(de::max(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y()));
+
+	return aabb;
+}
+
+float getExponentEpsilonFromULP (int valueExponent, deUint32 ulp)
+{
+	DE_ASSERT(ulp < (1u<<10));
+
+	// assume mediump precision, using ulp as ulps in a 10 bit mantissa
+	return tcu::Float32::construct(+1, valueExponent, (1u<<23) + (ulp << (23 - 10))).asFloat() - tcu::Float32::construct(+1, valueExponent, (1u<<23)).asFloat();
+}
+
+float getValueEpsilonFromULP (float value, deUint32 ulp)
+{
+	DE_ASSERT(value != std::numeric_limits<float>::infinity() && value != -std::numeric_limits<float>::infinity());
+
+	const int exponent = tcu::Float32(value).exponent();
+	return getExponentEpsilonFromULP(exponent, ulp);
+}
+
+float getMaxValueWithinError (float value, deUint32 ulp)
+{
+	if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity())
+		return value;
+
+	return value + getValueEpsilonFromULP(value, ulp);
+}
+
+float getMinValueWithinError (float value, deUint32 ulp)
+{
+	if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity())
+		return value;
+
+	return value - getValueEpsilonFromULP(value, ulp);
+}
+
+float getMinFlushToZero (float value)
+{
+	// flush to zero if that decreases the value
+	// assume mediump precision
+	if (value > 0.0f && value < tcu::Float32::construct(+1, -14, 1u<<23).asFloat())
+		return 0.0f;
+	return value;
+}
+
+float getMaxFlushToZero (float value)
+{
+	// flush to zero if that increases the value
+	// assume mediump precision
+	if (value < 0.0f && value > tcu::Float32::construct(-1, -14, 1u<<23).asFloat())
+		return 0.0f;
+	return value;
+}
+
+tcu::IVec3 convertRGB8ToNativeFormat (const tcu::RGBA& color, const RasterizationArguments& args)
+{
+	tcu::IVec3 pixelNativeColor;
+
+	for (int channelNdx = 0; channelNdx < 3; ++channelNdx)
+	{
+		const int channelBitCount	= (channelNdx == 0) ? (args.redBits) : (channelNdx == 1) ? (args.greenBits) : (args.blueBits);
+		const int channelPixelValue	= (channelNdx == 0) ? (color.getRed()) : (channelNdx == 1) ? (color.getGreen()) : (color.getBlue());
+
+		if (channelBitCount <= 8)
+			pixelNativeColor[channelNdx] = channelPixelValue >> (8 - channelBitCount);
+		else if (channelBitCount == 8)
+			pixelNativeColor[channelNdx] = channelPixelValue;
+		else
+		{
+			// just in case someone comes up with 8+ bits framebuffers pixel formats. But as
+			// we can only read in rgba8, we have to guess the trailing bits. Guessing 0.
+			pixelNativeColor[channelNdx] = channelPixelValue << (channelBitCount - 8);
+		}
+	}
+
+	return pixelNativeColor;
+}
+
+/*--------------------------------------------------------------------*//*!
+ * Returns the maximum value of x / y, where x c [minDividend, maxDividend]
+ * and y c [minDivisor, maxDivisor]
+ *//*--------------------------------------------------------------------*/
+float maximalRangeDivision (float minDividend, float maxDividend, float minDivisor, float maxDivisor)
+{
+	DE_ASSERT(minDividend <= maxDividend);
+	DE_ASSERT(minDivisor <= maxDivisor);
+
+	// special cases
+	if (minDividend == 0.0f && maxDividend == 0.0f)
+		return 0.0f;
+	if (minDivisor <= 0.0f && maxDivisor >= 0.0f)
+		return std::numeric_limits<float>::infinity();
+
+	return de::max(de::max(minDividend / minDivisor, minDividend / maxDivisor), de::max(maxDividend / minDivisor, maxDividend / maxDivisor));
+}
+
+/*--------------------------------------------------------------------*//*!
+ * Returns the minimum value of x / y, where x c [minDividend, maxDividend]
+ * and y c [minDivisor, maxDivisor]
+ *//*--------------------------------------------------------------------*/
+float minimalRangeDivision (float minDividend, float maxDividend, float minDivisor, float maxDivisor)
+{
+	DE_ASSERT(minDividend <= maxDividend);
+	DE_ASSERT(minDivisor <= maxDivisor);
+
+	// special cases
+	if (minDividend == 0.0f && maxDividend == 0.0f)
+		return 0.0f;
+	if (minDivisor <= 0.0f && maxDivisor >= 0.0f)
+		return -std::numeric_limits<float>::infinity();
+
+	return de::min(de::min(minDividend / minDivisor, minDividend / maxDivisor), de::min(maxDividend / minDivisor, maxDividend / maxDivisor));
+}
+
+struct InterpolationRange
+{
+	tcu::Vec3 max;
+	tcu::Vec3 min;
+};
+
+struct LineInterpolationRange
+{
+	tcu::Vec2 max;
+	tcu::Vec2 min;
+};
+
+InterpolationRange calcTriangleInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2, const tcu::Vec2& ndpixel)
+{
+	const int roundError		= 1;
+	const int barycentricError	= 3;
+	const int divError			= 8;
+
+	const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w();
+	const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w();
+	const tcu::Vec2 nd2 = p2.swizzle(0, 1) / p2.w();
+
+	const float ka = triangleArea(ndpixel, nd1, nd2);
+	const float kb = triangleArea(ndpixel, nd2, nd0);
+	const float kc = triangleArea(ndpixel, nd0, nd1);
+
+	const float kaMax = getMaxFlushToZero(getMaxValueWithinError(ka, barycentricError));
+	const float kbMax = getMaxFlushToZero(getMaxValueWithinError(kb, barycentricError));
+	const float kcMax = getMaxFlushToZero(getMaxValueWithinError(kc, barycentricError));
+	const float kaMin = getMinFlushToZero(getMinValueWithinError(ka, barycentricError));
+	const float kbMin = getMinFlushToZero(getMinValueWithinError(kb, barycentricError));
+	const float kcMin = getMinFlushToZero(getMinValueWithinError(kc, barycentricError));
+	DE_ASSERT(kaMin <= kaMax);
+	DE_ASSERT(kbMin <= kbMax);
+	DE_ASSERT(kcMin <= kcMax);
+
+	// calculate weights: vec3(ka / p0.w, kb / p1.w, kc / p2.w) / (ka / p0.w + kb / p1.w + kc / p2.w)
+	const float maxPreDivisionValues[3] =
+	{
+		getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kaMax / p0.w()), divError)),
+		getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kbMax / p1.w()), divError)),
+		getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kcMax / p2.w()), divError)),
+	};
+	const float minPreDivisionValues[3] =
+	{
+		getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kaMin / p0.w()), divError)),
+		getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kbMin / p1.w()), divError)),
+		getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kcMin / p2.w()), divError)),
+	};
+	DE_ASSERT(minPreDivisionValues[0] <= maxPreDivisionValues[0]);
+	DE_ASSERT(minPreDivisionValues[1] <= maxPreDivisionValues[1]);
+	DE_ASSERT(minPreDivisionValues[2] <= maxPreDivisionValues[2]);
+
+	const float maxDivisor = getMaxFlushToZero(getMaxValueWithinError(maxPreDivisionValues[0] + maxPreDivisionValues[1] + maxPreDivisionValues[2], 2*roundError));
+	const float minDivisor = getMinFlushToZero(getMinValueWithinError(minPreDivisionValues[0] + minPreDivisionValues[1] + minPreDivisionValues[2], 2*roundError));
+	DE_ASSERT(minDivisor <= maxDivisor);
+
+	InterpolationRange returnValue;
+
+	returnValue.max.x() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0], minDivisor, maxDivisor)), divError));
+	returnValue.max.y() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1], minDivisor, maxDivisor)), divError));
+	returnValue.max.z() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2], minDivisor, maxDivisor)), divError));
+	returnValue.min.x() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0], minDivisor, maxDivisor)), divError));
+	returnValue.min.y() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1], minDivisor, maxDivisor)), divError));
+	returnValue.min.z() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2], minDivisor, maxDivisor)), divError));
+
+	DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
+	DE_ASSERT(returnValue.min.y() <= returnValue.max.y());
+	DE_ASSERT(returnValue.min.z() <= returnValue.max.z());
+
+	return returnValue;
+}
+
+LineInterpolationRange calcSingleSampleLineInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec2& ndpoint)
+{
+	const int divError = 3;
+
+	const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w();
+	const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w();
+
+	// project p to the line along the minor direction
+
+	const bool		xMajor		= (de::abs(nd0.x() - nd1.x()) >= de::abs(nd0.y() - nd1.y()));
+	const tcu::Vec2	minorDir	= (xMajor) ? (tcu::Vec2(0.0f, 1.0f)) : (tcu::Vec2(1.0f, 0.0f));
+	const tcu::Vec2	lineDir		(nd1 - nd0);
+	const tcu::Vec2	d			(ndpoint - nd0);
+
+	// calculate factors: vec2((1-t) / p0.w, t / p1.w) / ((1-t) / p0.w + t / p1.w)
+
+	const float		tFactorMax				= getMaxValueWithinError(-(1.0f / (minorDir.x()*lineDir.y() - lineDir.x()*minorDir.y())), divError);
+	const float		tFactorMin				= getMinValueWithinError(-(1.0f / (minorDir.x()*lineDir.y() - lineDir.x()*minorDir.y())), divError);
+	DE_ASSERT(tFactorMin <= tFactorMax);
+
+	const float		tResult1				= tFactorMax * (minorDir.y()*d.x() - minorDir.x()*d.y());
+	const float		tResult2				= tFactorMin * (minorDir.y()*d.x() - minorDir.x()*d.y());
+	const float		tMax					= de::max(tResult1, tResult2);
+	const float		tMin					= de::min(tResult1, tResult2);
+	DE_ASSERT(tMin <= tMax);
+
+	const float		perspectiveTMax			= getMaxValueWithinError(maximalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError);
+	const float		perspectiveTMin			= getMinValueWithinError(minimalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError);
+	DE_ASSERT(perspectiveTMin <= perspectiveTMax);
+
+	const float		perspectiveInvTMax		= getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), p0.w(), p0.w()), divError);
+	const float		perspectiveInvTMin		= getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), p0.w(), p0.w()), divError);
+	DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax);
+
+	const float		perspectiveDivisorMax	= perspectiveTMax + perspectiveInvTMax;
+	const float		perspectiveDivisorMin	= perspectiveTMin + perspectiveInvTMin;
+	DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax);
+
+	LineInterpolationRange returnValue;
+	returnValue.max.x() = getMaxValueWithinError(maximalRangeDivision(perspectiveInvTMin,	perspectiveInvTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
+	returnValue.max.y() = getMaxValueWithinError(maximalRangeDivision(perspectiveTMin,		perspectiveTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
+	returnValue.min.x() = getMinValueWithinError(minimalRangeDivision(perspectiveInvTMin,	perspectiveInvTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
+	returnValue.min.y() = getMinValueWithinError(minimalRangeDivision(perspectiveTMin,		perspectiveTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
+
+	DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
+	DE_ASSERT(returnValue.min.y() <= returnValue.max.y());
+
+	return returnValue;
+}
+
+LineInterpolationRange calcMultiSampleLineInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec2& ndpoint)
+{
+	const int divError = 3;
+
+	// calc weights: vec2((1-t) / p0.w, t / p1.w) / ((1-t) / p0.w + t / p1.w)
+
+	// highp vertex shader
+	const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w();
+	const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w();
+
+	// Allow 1 ULP
+	const float		dividend	= tcu::dot(ndpoint - nd0, nd1 - nd0);
+	const float		dividendMax	= getMaxValueWithinError(dividend, 1);
+	const float		dividendMin	= getMaxValueWithinError(dividend, 1);
+	DE_ASSERT(dividendMin <= dividendMax);
+
+	// Assuming lengthSquared will not be implemented as sqrt(x)^2, allow 1 ULP
+	const float		divisor		= tcu::lengthSquared(nd1 - nd0);
+	const float		divisorMax	= getMaxValueWithinError(divisor, 1);
+	const float		divisorMin	= getMaxValueWithinError(divisor, 1);
+	DE_ASSERT(divisorMin <= divisorMax);
+
+	// Allow 3 ULP precision for division
+	const float		tMax		= getMaxValueWithinError(maximalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
+	const float		tMin		= getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
+	DE_ASSERT(tMin <= tMax);
+
+	const float		perspectiveTMax			= getMaxValueWithinError(maximalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError);
+	const float		perspectiveTMin			= getMinValueWithinError(minimalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError);
+	DE_ASSERT(perspectiveTMin <= perspectiveTMax);
+
+	const float		perspectiveInvTMax		= getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), p0.w(), p0.w()), divError);
+	const float		perspectiveInvTMin		= getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), p0.w(), p0.w()), divError);
+	DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax);
+
+	const float		perspectiveDivisorMax	= perspectiveTMax + perspectiveInvTMax;
+	const float		perspectiveDivisorMin	= perspectiveTMin + perspectiveInvTMin;
+	DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax);
+
+	LineInterpolationRange returnValue;
+	returnValue.max.x() = getMaxValueWithinError(maximalRangeDivision(perspectiveInvTMin,	perspectiveInvTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
+	returnValue.max.y() = getMaxValueWithinError(maximalRangeDivision(perspectiveTMin,		perspectiveTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
+	returnValue.min.x() = getMinValueWithinError(minimalRangeDivision(perspectiveInvTMin,	perspectiveInvTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
+	returnValue.min.y() = getMinValueWithinError(minimalRangeDivision(perspectiveTMin,		perspectiveTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
+
+	DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
+	DE_ASSERT(returnValue.min.y() <= returnValue.max.y());
+
+	return returnValue;
+}
+
+LineInterpolationRange calcSingleSampleLineInterpolationRange (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::IVec2& pixel, const tcu::IVec2& viewportSize, int subpixelBits)
+{
+	// allow interpolation weights anywhere in the central subpixels
+	const float testSquareSize = (2.0f / (1UL << subpixelBits));
+	const float testSquarePos  = (0.5f - testSquareSize / 2);
+	const tcu::Vec2 corners[4] =
+	{
+		tcu::Vec2((pixel.x() + testSquarePos + 0.0f)           / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + 0.0f          ) / viewportSize.y() * 2.0f - 1.0f),
+		tcu::Vec2((pixel.x() + testSquarePos + 0.0f)           / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + testSquareSize) / viewportSize.y() * 2.0f - 1.0f),
+		tcu::Vec2((pixel.x() + testSquarePos + testSquareSize) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + testSquareSize) / viewportSize.y() * 2.0f - 1.0f),
+		tcu::Vec2((pixel.x() + testSquarePos + testSquareSize) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + 0.0f          ) / viewportSize.y() * 2.0f - 1.0f),
+	};
+
+	// calculate interpolation as a line
+	const LineInterpolationRange weights[4] =
+	{
+		calcSingleSampleLineInterpolationWeights(p0, p1, corners[0]),
+		calcSingleSampleLineInterpolationWeights(p0, p1, corners[1]),
+		calcSingleSampleLineInterpolationWeights(p0, p1, corners[2]),
+		calcSingleSampleLineInterpolationWeights(p0, p1, corners[3]),
+	};
+
+	const tcu::Vec2 minWeights = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
+	const tcu::Vec2 maxWeights = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));
+
+	// convert to three-component form. For all triangles, the vertex 0 is always emitted by the line starting point, and vertex 2 by the ending point
+	LineInterpolationRange result;
+	result.min = minWeights;
+	result.max = maxWeights;
+	return result;
+}
+
+struct TriangleInterpolator
+{
+	const TriangleSceneSpec& scene;
+
+	TriangleInterpolator (const TriangleSceneSpec& scene_)
+		: scene(scene_)
+	{
+	}
+
+	InterpolationRange interpolate (int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize, bool multisample, int subpixelBits) const
+	{
+		// allow anywhere in the pixel area in multisample
+		// allow only in the center subpixels (4 subpixels) in singlesample
+		const float testSquareSize = (multisample) ? (1.0f) : (2.0f / (1UL << subpixelBits));
+		const float testSquarePos  = (multisample) ? (0.0f) : (0.5f - testSquareSize / 2);
+		const tcu::Vec2 corners[4] =
+		{
+			tcu::Vec2((pixel.x() + testSquarePos + 0.0f)           / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + 0.0f          ) / viewportSize.y() * 2.0f - 1.0f),
+			tcu::Vec2((pixel.x() + testSquarePos + 0.0f)           / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + testSquareSize) / viewportSize.y() * 2.0f - 1.0f),
+			tcu::Vec2((pixel.x() + testSquarePos + testSquareSize) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + testSquareSize) / viewportSize.y() * 2.0f - 1.0f),
+			tcu::Vec2((pixel.x() + testSquarePos + testSquareSize) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + 0.0f          ) / viewportSize.y() * 2.0f - 1.0f),
+		};
+		const InterpolationRange weights[4] =
+		{
+			calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[0]),
+			calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[1]),
+			calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[2]),
+			calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[3]),
+		};
+
+		InterpolationRange result;
+		result.min = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
+		result.max = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));
+		return result;
+	}
+};
+
+/*--------------------------------------------------------------------*//*!
+ * Used only by verifyMultisampleLineGroupInterpolation to calculate
+ * correct line interpolations for the triangulated lines.
+ *//*--------------------------------------------------------------------*/
+struct MultisampleLineInterpolator
+{
+	const LineSceneSpec& scene;
+
+	MultisampleLineInterpolator (const LineSceneSpec& scene_)
+		: scene(scene_)
+	{
+	}
+
+	InterpolationRange interpolate (int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize, bool multisample, int subpixelBits) const
+	{
+		DE_ASSERT(multisample);
+		DE_UNREF(multisample);
+		DE_UNREF(subpixelBits);
+
+		// in triangulation, one line emits two triangles
+		const int		lineNdx		= primitiveNdx / 2;
+
+		// allow interpolation weights anywhere in the pixel
+		const tcu::Vec2 corners[4] =
+		{
+			tcu::Vec2((pixel.x() + 0.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 0.0f) / viewportSize.y() * 2.0f - 1.0f),
+			tcu::Vec2((pixel.x() + 0.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 1.0f) / viewportSize.y() * 2.0f - 1.0f),
+			tcu::Vec2((pixel.x() + 1.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 1.0f) / viewportSize.y() * 2.0f - 1.0f),
+			tcu::Vec2((pixel.x() + 1.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 0.0f) / viewportSize.y() * 2.0f - 1.0f),
+		};
+
+		// calculate interpolation as a line
+		const LineInterpolationRange weights[4] =
+		{
+			calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[0]),
+			calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[1]),
+			calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[2]),
+			calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[3]),
+		};
+
+		const tcu::Vec2 minWeights = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
+		const tcu::Vec2 maxWeights = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));
+
+		// convert to three-component form. For all triangles, the vertex 0 is always emitted by the line starting point, and vertex 2 by the ending point
+		InterpolationRange result;
+		result.min = tcu::Vec3(minWeights.x(), 0.0f, minWeights.y());
+		result.max = tcu::Vec3(maxWeights.x(), 0.0f, maxWeights.y());
+		return result;
+	}
+};
+
+template <typename Interpolator>
+bool verifyTriangleGroupInterpolationWithInterpolator (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, const Interpolator& interpolator)
+{
+	const tcu::RGBA		invalidPixelColor	= tcu::RGBA(255, 0, 0, 255);
+	const bool			multisampled		= (args.numSamples != 0);
+	const tcu::IVec2	viewportSize		= tcu::IVec2(surface.getWidth(), surface.getHeight());
+	const int			errorFloodThreshold	= 4;
+	int					errorCount			= 0;
+	int					invalidPixels		= 0;
+	int					subPixelBits		= args.subpixelBits;
+	tcu::Surface		errorMask			(surface.getWidth(), surface.getHeight());
+
+	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	// log format
+
+	log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage;
+	if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
+		log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage;
+
+	// subpixel bits in in a valid range?
+
+	if (subPixelBits < 0)
+	{
+		log << tcu::TestLog::Message << "Invalid subpixel count (" << subPixelBits << "), assuming 0" << tcu::TestLog::EndMessage;
+		subPixelBits = 0;
+	}
+	else if (subPixelBits > 16)
+	{
+		// At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict
+		log << tcu::TestLog::Message << "Subpixel count is greater than 16 (" << subPixelBits << "). Checking results using less strict 16 bit requirements. This may produce false positives." << tcu::TestLog::EndMessage;
+		subPixelBits = 16;
+	}
+
+	// check pixels
+
+	for (int y = 0; y < surface.getHeight(); ++y)
+	for (int x = 0; x < surface.getWidth();  ++x)
+	{
+		const tcu::RGBA		color				= surface.getPixel(x, y);
+		bool				stackBottomFound	= false;
+		int					stackSize			= 0;
+		tcu::Vec4			colorStackMin;
+		tcu::Vec4			colorStackMax;
+
+		// Iterate triangle coverage front to back, find the stack of pontentially contributing fragments
+		for (int triNdx = (int)scene.triangles.size() - 1; triNdx >= 0; --triNdx)
+		{
+			const CoverageType coverage = calculateTriangleCoverage(scene.triangles[triNdx].positions[0],
+																	scene.triangles[triNdx].positions[1],
+																	scene.triangles[triNdx].positions[2],
+																	tcu::IVec2(x, y),
+																	viewportSize,
+																	subPixelBits,
+																	multisampled);
+
+			if (coverage == COVERAGE_FULL || coverage == COVERAGE_PARTIAL)
+			{
+				// potentially contributes to the result fragment's value
+				const InterpolationRange weights = interpolator.interpolate(triNdx, tcu::IVec2(x, y), viewportSize, multisampled, subPixelBits);
+
+				const tcu::Vec4 fragmentColorMax =	de::clamp(weights.max.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] +
+													de::clamp(weights.max.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] +
+													de::clamp(weights.max.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2];
+				const tcu::Vec4 fragmentColorMin =	de::clamp(weights.min.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] +
+													de::clamp(weights.min.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] +
+													de::clamp(weights.min.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2];
+
+				if (stackSize++ == 0)
+				{
+					// first triangle, set the values properly
+					colorStackMin = fragmentColorMin;
+					colorStackMax = fragmentColorMax;
+				}
+				else
+				{
+					// contributing triangle
+					colorStackMin = tcu::min(colorStackMin, fragmentColorMin);
+					colorStackMax = tcu::max(colorStackMax, fragmentColorMax);
+				}
+
+				if (coverage == COVERAGE_FULL)
+				{
+					// loop terminates, this is the bottommost fragment
+					stackBottomFound = true;
+					break;
+				}
+			}
+		}
+
+		// Partial coverage == background may be visible
+		if (stackSize != 0 && !stackBottomFound)
+		{
+			stackSize++;
+			colorStackMin = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
+		}
+
+		// Is the result image color in the valid range.
+		if (stackSize == 0)
+		{
+			// No coverage, allow only background (black, value=0)
+			const tcu::IVec3	pixelNativeColor	= convertRGB8ToNativeFormat(color, args);
+			const int			threshold			= 1;
+
+			if (pixelNativeColor.x() > threshold ||
+				pixelNativeColor.y() > threshold ||
+				pixelNativeColor.z() > threshold)
+			{
+				++errorCount;
+
+				// don't fill the logs with too much data
+				if (errorCount < errorFloodThreshold)
+				{
+					log << tcu::TestLog::Message
+						<< "Found an invalid pixel at (" << x << "," << y << ")\n"
+						<< "\tPixel color:\t\t" << color << "\n"
+						<< "\tExpected background color.\n"
+						<< tcu::TestLog::EndMessage;
+				}
+
+				++invalidPixels;
+				errorMask.setPixel(x, y, invalidPixelColor);
+			}
+		}
+		else
+		{
+			DE_ASSERT(stackSize);
+
+			// Each additional step in the stack may cause conversion error of 1 bit due to undefined rounding direction
+			const int			thresholdRed	= stackSize - 1;
+			const int			thresholdGreen	= stackSize - 1;
+			const int			thresholdBlue	= stackSize - 1;
+
+			const tcu::Vec3		valueRangeMin	= tcu::Vec3(colorStackMin.xyz());
+			const tcu::Vec3		valueRangeMax	= tcu::Vec3(colorStackMax.xyz());
+
+			const tcu::IVec3	formatLimit		((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);
+			const tcu::Vec3		colorMinF		(de::clamp(valueRangeMin.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()),
+												 de::clamp(valueRangeMin.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()),
+												 de::clamp(valueRangeMin.z() * formatLimit.z(), 0.0f, (float)formatLimit.z()));
+			const tcu::Vec3		colorMaxF		(de::clamp(valueRangeMax.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()),
+												 de::clamp(valueRangeMax.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()),
+												 de::clamp(valueRangeMax.z() * formatLimit.z(), 0.0f, (float)formatLimit.z()));
+			const tcu::IVec3	colorMin		((int)deFloatFloor(colorMinF.x()),
+												 (int)deFloatFloor(colorMinF.y()),
+												 (int)deFloatFloor(colorMinF.z()));
+			const tcu::IVec3	colorMax		((int)deFloatCeil (colorMaxF.x()),
+												 (int)deFloatCeil (colorMaxF.y()),
+												 (int)deFloatCeil (colorMaxF.z()));
+
+			// Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
+			const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args);
+
+			// Validity check
+			if (pixelNativeColor.x() < colorMin.x() - thresholdRed   ||
+				pixelNativeColor.y() < colorMin.y() - thresholdGreen ||
+				pixelNativeColor.z() < colorMin.z() - thresholdBlue  ||
+				pixelNativeColor.x() > colorMax.x() + thresholdRed   ||
+				pixelNativeColor.y() > colorMax.y() + thresholdGreen ||
+				pixelNativeColor.z() > colorMax.z() + thresholdBlue)
+			{
+				++errorCount;
+
+				// don't fill the logs with too much data
+				if (errorCount <= errorFloodThreshold)
+				{
+					log << tcu::TestLog::Message
+						<< "Found an invalid pixel at (" << x << "," << y << ")\n"
+						<< "\tPixel color:\t\t" << color << "\n"
+						<< "\tNative color:\t\t" << pixelNativeColor << "\n"
+						<< "\tAllowed error:\t\t" << tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue) << "\n"
+						<< "\tReference native color min: " << tcu::clamp(colorMin - tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue), tcu::IVec3(0,0,0), formatLimit) << "\n"
+						<< "\tReference native color max: " << tcu::clamp(colorMax + tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue), tcu::IVec3(0,0,0), formatLimit) << "\n"
+						<< "\tReference native float min: " << tcu::clamp(colorMinF - tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue).cast<float>(), tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
+						<< "\tReference native float max: " << tcu::clamp(colorMaxF + tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue).cast<float>(), tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
+						<< "\tFmin:\t" << tcu::clamp(valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
+						<< "\tFmax:\t" << tcu::clamp(valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
+						<< tcu::TestLog::EndMessage;
+				}
+
+				++invalidPixels;
+				errorMask.setPixel(x, y, invalidPixelColor);
+			}
+		}
+	}
+
+	// don't just hide failures
+	if (errorCount > errorFloodThreshold)
+		log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage;
+
+	// report result
+	if (invalidPixels)
+	{
+		log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage;
+		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
+			<< tcu::TestLog::Image("Result", "Result",			surface)
+			<< tcu::TestLog::Image("ErrorMask", "ErrorMask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+
+		return false;
+	}
+	else
+	{
+		log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
+		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
+			<< tcu::TestLog::Image("Result", "Result", surface)
+			<< tcu::TestLog::EndImageSet;
+
+		return true;
+	}
+}
+
+bool verifyMultisampleLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+{
+	// Multisampled line == 2 triangles
+
+	const tcu::Vec2		viewportSize	= tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
+	const float			halfLineWidth	= scene.lineWidth * 0.5f;
+	TriangleSceneSpec	triangleScene;
+
+	triangleScene.triangles.resize(2 * scene.lines.size());
+	for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
+	{
+		// Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
+		const tcu::Vec2 lineNormalizedDeviceSpace[2] =
+		{
+			tcu::Vec2(scene.lines[lineNdx].positions[0].x() / scene.lines[lineNdx].positions[0].w(), scene.lines[lineNdx].positions[0].y() / scene.lines[lineNdx].positions[0].w()),
+			tcu::Vec2(scene.lines[lineNdx].positions[1].x() / scene.lines[lineNdx].positions[1].w(), scene.lines[lineNdx].positions[1].y() / scene.lines[lineNdx].positions[1].w()),
+		};
+		const tcu::Vec2 lineScreenSpace[2] =
+		{
+			(lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
+			(lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
+		};
+
+		const tcu::Vec2 lineDir			= tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]);
+		const tcu::Vec2 lineNormalDir	= tcu::Vec2(lineDir.y(), -lineDir.x());
+
+		const tcu::Vec2 lineQuadScreenSpace[4] =
+		{
+			lineScreenSpace[0] + lineNormalDir * halfLineWidth,
+			lineScreenSpace[0] - lineNormalDir * halfLineWidth,
+			lineScreenSpace[1] - lineNormalDir * halfLineWidth,
+			lineScreenSpace[1] + lineNormalDir * halfLineWidth,
+		};
+		const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] =
+		{
+			lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+			lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+			lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+			lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+		};
+
+		triangleScene.triangles[lineNdx*2 + 0].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 0].sharedEdge[0] = false;
+		triangleScene.triangles[lineNdx*2 + 0].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 0].sharedEdge[1] = false;
+		triangleScene.triangles[lineNdx*2 + 0].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 0].sharedEdge[2] = true;
+
+		triangleScene.triangles[lineNdx*2 + 1].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 1].sharedEdge[0] = true;
+		triangleScene.triangles[lineNdx*2 + 1].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 1].sharedEdge[1] = false;
+		triangleScene.triangles[lineNdx*2 + 1].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 1].sharedEdge[2] = false;
+	}
+
+	return verifyTriangleGroupRasterization(surface, triangleScene, args, log);
+}
+
+bool verifyMultisampleLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+{
+	// Multisampled line == 2 triangles
+
+	const tcu::Vec2		viewportSize	= tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
+	const float			halfLineWidth	= scene.lineWidth * 0.5f;
+	TriangleSceneSpec	triangleScene;
+
+	triangleScene.triangles.resize(2 * scene.lines.size());
+	for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
+	{
+		// Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
+		const tcu::Vec2 lineNormalizedDeviceSpace[2] =
+		{
+			tcu::Vec2(scene.lines[lineNdx].positions[0].x() / scene.lines[lineNdx].positions[0].w(), scene.lines[lineNdx].positions[0].y() / scene.lines[lineNdx].positions[0].w()),
+			tcu::Vec2(scene.lines[lineNdx].positions[1].x() / scene.lines[lineNdx].positions[1].w(), scene.lines[lineNdx].positions[1].y() / scene.lines[lineNdx].positions[1].w()),
+		};
+		const tcu::Vec2 lineScreenSpace[2] =
+		{
+			(lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
+			(lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
+		};
+
+		const tcu::Vec2 lineDir			= tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]);
+		const tcu::Vec2 lineNormalDir	= tcu::Vec2(lineDir.y(), -lineDir.x());
+
+		const tcu::Vec2 lineQuadScreenSpace[4] =
+		{
+			lineScreenSpace[0] + lineNormalDir * halfLineWidth,
+			lineScreenSpace[0] - lineNormalDir * halfLineWidth,
+			lineScreenSpace[1] - lineNormalDir * halfLineWidth,
+			lineScreenSpace[1] + lineNormalDir * halfLineWidth,
+		};
+		const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] =
+		{
+			lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+			lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+			lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+			lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+		};
+
+		triangleScene.triangles[lineNdx*2 + 0].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);
+		triangleScene.triangles[lineNdx*2 + 0].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f);
+		triangleScene.triangles[lineNdx*2 + 0].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);
+
+		triangleScene.triangles[lineNdx*2 + 0].sharedEdge[0] = false;
+		triangleScene.triangles[lineNdx*2 + 0].sharedEdge[1] = false;
+		triangleScene.triangles[lineNdx*2 + 0].sharedEdge[2] = true;
+
+		triangleScene.triangles[lineNdx*2 + 0].colors[0] = scene.lines[lineNdx].colors[0];
+		triangleScene.triangles[lineNdx*2 + 0].colors[1] = scene.lines[lineNdx].colors[0];
+		triangleScene.triangles[lineNdx*2 + 0].colors[2] = scene.lines[lineNdx].colors[1];
+
+		triangleScene.triangles[lineNdx*2 + 1].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);
+		triangleScene.triangles[lineNdx*2 + 1].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);
+		triangleScene.triangles[lineNdx*2 + 1].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f);
+
+		triangleScene.triangles[lineNdx*2 + 1].sharedEdge[0] = true;
+		triangleScene.triangles[lineNdx*2 + 1].sharedEdge[1] = false;
+		triangleScene.triangles[lineNdx*2 + 1].sharedEdge[2] = false;
+
+		triangleScene.triangles[lineNdx*2 + 1].colors[0] = scene.lines[lineNdx].colors[0];
+		triangleScene.triangles[lineNdx*2 + 1].colors[1] = scene.lines[lineNdx].colors[1];
+		triangleScene.triangles[lineNdx*2 + 1].colors[2] = scene.lines[lineNdx].colors[1];
+	}
+
+	return verifyTriangleGroupInterpolationWithInterpolator(surface, triangleScene, args, log, MultisampleLineInterpolator(scene));
+}
+
+bool verifyMultisamplePointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+{
+	// Multisampled point == 2 triangles
+
+	const tcu::Vec2		viewportSize	= tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
+	TriangleSceneSpec	triangleScene;
+
+	triangleScene.triangles.resize(2 * scene.points.size());
+	for (int pointNdx = 0; pointNdx < (int)scene.points.size(); ++pointNdx)
+	{
+		// Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
+		const tcu::Vec2	pointNormalizedDeviceSpace			= tcu::Vec2(scene.points[pointNdx].position.x() / scene.points[pointNdx].position.w(), scene.points[pointNdx].position.y() / scene.points[pointNdx].position.w());
+		const tcu::Vec2	pointScreenSpace					= (pointNormalizedDeviceSpace + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize;
+		const float		offset								= scene.points[pointNdx].pointSize * 0.5f;
+		const tcu::Vec2	lineQuadNormalizedDeviceSpace[4]	=
+		{
+			(pointScreenSpace + tcu::Vec2(-offset, -offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+			(pointScreenSpace + tcu::Vec2(-offset,  offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+			(pointScreenSpace + tcu::Vec2( offset,  offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+			(pointScreenSpace + tcu::Vec2( offset, -offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
+		};
+
+		triangleScene.triangles[pointNdx*2 + 0].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 0].sharedEdge[0] = false;
+		triangleScene.triangles[pointNdx*2 + 0].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 0].sharedEdge[1] = false;
+		triangleScene.triangles[pointNdx*2 + 0].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 0].sharedEdge[2] = true;
+
+		triangleScene.triangles[pointNdx*2 + 1].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 1].sharedEdge[0] = true;
+		triangleScene.triangles[pointNdx*2 + 1].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 1].sharedEdge[1] = false;
+		triangleScene.triangles[pointNdx*2 + 1].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 1].sharedEdge[2] = false;
+	}
+
+	return verifyTriangleGroupRasterization(surface, triangleScene, args, log);
+}
+
+bool verifySinglesampleLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+{
+	DE_ASSERT(deFloatFrac(scene.lineWidth) != 0.5f); // rounding direction is not defined, disallow undefined cases
+	DE_ASSERT(scene.lines.size() < 255); // indices are stored as unsigned 8-bit ints
+
+	bool				allOK				= true;
+	bool				overdrawInReference	= false;
+	int					referenceFragments	= 0;
+	int					resultFragments		= 0;
+	int					lineWidth			= deFloorFloatToInt32(scene.lineWidth + 0.5f);
+	bool				imageShown			= false;
+	std::vector<bool>	lineIsXMajor		(scene.lines.size());
+
+	// Reference renderer produces correct fragments using the diamond-rule. Make 2D int array, each cell contains the highest index (first index = 1) of the overlapping lines or 0 if no line intersects the pixel
+	tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());
+	tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));
+
+	for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
+	{
+		rr::SingleSampleLineRasterizer rasterizer(tcu::IVec4(0, 0, surface.getWidth(), surface.getHeight()));
+
+		const tcu::Vec2 lineNormalizedDeviceSpace[2] =
+		{
+			tcu::Vec2(scene.lines[lineNdx].positions[0].x() / scene.lines[lineNdx].positions[0].w(), scene.lines[lineNdx].positions[0].y() / scene.lines[lineNdx].positions[0].w()),
+			tcu::Vec2(scene.lines[lineNdx].positions[1].x() / scene.lines[lineNdx].positions[1].w(), scene.lines[lineNdx].positions[1].y() / scene.lines[lineNdx].positions[1].w()),
+		};
+		const tcu::Vec4 lineScreenSpace[2] =
+		{
+			tcu::Vec4((lineNormalizedDeviceSpace[0].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[0].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f),
+			tcu::Vec4((lineNormalizedDeviceSpace[1].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[1].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f),
+		};
+
+		rasterizer.init(lineScreenSpace[0], lineScreenSpace[1], scene.lineWidth);
+
+		// calculate majority of later use
+		lineIsXMajor[lineNdx] = de::abs(lineScreenSpace[1].x() - lineScreenSpace[0].x()) >= de::abs(lineScreenSpace[1].y() - lineScreenSpace[0].y());
+
+		for (;;)
+		{
+			const int			maxPackets			= 32;
+			int					numRasterized		= 0;
+			rr::FragmentPacket	packets[maxPackets];
+
+			rasterizer.rasterize(packets, DE_NULL, maxPackets, numRasterized);
+
+			for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
+			{
+				for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+				{
+					if ((deUint32)packets[packetNdx].coverage & (1 << fragNdx))
+					{
+						const tcu::IVec2 fragPos = packets[packetNdx].position + tcu::IVec2(fragNdx%2, fragNdx/2);
+
+						// Check for overdraw
+						if (!overdrawInReference)
+							overdrawInReference = referenceLineMap.getAccess().getPixelInt(fragPos.x(), fragPos.y()).x() != 0;
+
+						// Output pixel
+						referenceLineMap.getAccess().setPixel(tcu::IVec4(lineNdx + 1, 0, 0, 0), fragPos.x(), fragPos.y());
+					}
+				}
+			}
+
+			if (numRasterized != maxPackets)
+				break;
+		}
+	}
+
+	// Requirement 1: The coordinates of a fragment produced by the algorithm may not deviate by more than one unit
+	{
+		tcu::Surface	errorMask			(surface.getWidth(), surface.getHeight());
+		bool			missingFragments	= false;
+
+		tcu::clear(errorMask.getAccess(), tcu::IVec4(0, 255, 0, 255));
+
+		log << tcu::TestLog::Message << "Searching for deviating fragments." << tcu::TestLog::EndMessage;
+
+		for (int y = 0; y < referenceLineMap.getHeight(); ++y)
+		for (int x = 0; x < referenceLineMap.getWidth(); ++x)
+		{
+			const bool reference	= referenceLineMap.getAccess().getPixelInt(x, y).x() != 0;
+			const bool result		= compareColors(surface.getPixel(x, y), tcu::RGBA::white, args.redBits, args.greenBits, args.blueBits);
+
+			if (reference)
+				++referenceFragments;
+			if (result)
+				++resultFragments;
+
+			if (reference == result)
+				continue;
+
+			// Reference fragment here, matching result fragment must be nearby
+			if (reference && !result)
+			{
+				bool foundFragment = false;
+
+				if (x == 0 || y == 0 || x == referenceLineMap.getWidth() - 1 || y == referenceLineMap.getHeight() -1)
+				{
+					// image boundary, missing fragment could be over the image edge
+					foundFragment = true;
+				}
+
+				// find nearby fragment
+				for (int dy = -1; dy < 2 && !foundFragment; ++dy)
+				for (int dx = -1; dx < 2 && !foundFragment; ++dx)
+				{
+					if (compareColors(surface.getPixel(x+dx, y+dy), tcu::RGBA::white, args.redBits, args.greenBits, args.blueBits))
+						foundFragment = true;
+				}
+
+				if (!foundFragment)
+				{
+					missingFragments = true;
+					errorMask.setPixel(x, y, tcu::RGBA::red);
+				}
+			}
+		}
+
+		if (missingFragments)
+		{
+			log << tcu::TestLog::Message << "Invalid deviation(s) found." << tcu::TestLog::EndMessage;
+			log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
+				<< tcu::TestLog::Image("Result", "Result",			surface)
+				<< tcu::TestLog::Image("ErrorMask", "ErrorMask",	errorMask)
+				<< tcu::TestLog::EndImageSet;
+
+			imageShown = true;
+			allOK = false;
+		}
+		else
+		{
+			log << tcu::TestLog::Message << "No invalid deviations found." << tcu::TestLog::EndMessage;
+		}
+	}
+
+	// Requirement 2: The total number of fragments produced by the algorithm may differ from
+	//                that produced by the diamond-exit rule by no more than one.
+	{
+		// Check is not valid if the primitives intersect or otherwise share same fragments
+		if (!overdrawInReference)
+		{
+			int allowedDeviation = (int)scene.lines.size() * lineWidth; // one pixel per primitive in the major direction
+
+			log << tcu::TestLog::Message << "Verifying fragment counts:\n"
+				<< "\tDiamond-exit rule: " << referenceFragments << " fragments.\n"
+				<< "\tResult image: " << resultFragments << " fragments.\n"
+				<< "\tAllowing deviation of " << allowedDeviation << " fragments.\n"
+				<< tcu::TestLog::EndMessage;
+
+			if (deAbs32(referenceFragments - resultFragments) > allowedDeviation)
+			{
+				tcu::Surface reference(surface.getWidth(), surface.getHeight());
+
+				// show a helpful reference image
+				tcu::clear(reference.getAccess(), tcu::IVec4(0, 0, 0, 255));
+				for (int y = 0; y < surface.getHeight(); ++y)
+				for (int x = 0; x < surface.getWidth(); ++x)
+					if (referenceLineMap.getAccess().getPixelInt(x, y).x())
+						reference.setPixel(x, y, tcu::RGBA::white);
+
+				log << tcu::TestLog::Message << "Invalid fragment count in result image." << tcu::TestLog::EndMessage;
+				log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
+					<< tcu::TestLog::Image("Reference", "Reference",	reference)
+					<< tcu::TestLog::Image("Result", "Result",			surface)
+					<< tcu::TestLog::EndImageSet;
+
+				allOK = false;
+				imageShown = true;
+			}
+			else
+			{
+				log << tcu::TestLog::Message << "Fragment count is valid." << tcu::TestLog::EndMessage;
+			}
+		}
+		else
+		{
+			log << tcu::TestLog::Message << "Overdraw in scene. Fragment count cannot be verified. Skipping fragment count checks." << tcu::TestLog::EndMessage;
+		}
+	}
+
+	// Requirement 3: Line width must be constant
+	{
+		bool invalidWidthFound = false;
+
+		log << tcu::TestLog::Message << "Verifying line widths of the x-major lines." << tcu::TestLog::EndMessage;
+		for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y)
+		{
+			bool	fullyVisibleLine		= false;
+			bool	previousPixelUndefined	= false;
+			int		currentLine				= 0;
+			int		currentWidth			= 1;
+
+			for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x)
+			{
+				const bool	result	= compareColors(surface.getPixel(x, y), tcu::RGBA::white, args.redBits, args.greenBits, args.blueBits);
+				int			lineID	= 0;
+
+				// Which line does this fragment belong to?
+
+				if (result)
+				{
+					bool multipleNearbyLines = false;
+
+					for (int dy = -1; dy < 2; ++dy)
+					for (int dx = -1; dx < 2; ++dx)
+					{
+						const int nearbyID = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();
+						if (nearbyID)
+						{
+							if (lineID && lineID != nearbyID)
+								multipleNearbyLines = true;
+							lineID = nearbyID;
+						}
+					}
+
+					if (multipleNearbyLines)
+					{
+						// Another line is too close, don't try to calculate width here
+						previousPixelUndefined = true;
+						continue;
+					}
+				}
+
+				// Only line with id of lineID is nearby
+
+				if (previousPixelUndefined)
+				{
+					// The line might have been overdrawn or not
+					currentLine = lineID;
+					currentWidth = 1;
+					fullyVisibleLine = false;
+					previousPixelUndefined = false;
+				}
+				else if (lineID == currentLine)
+				{
+					// Current line continues
+					++currentWidth;
+				}
+				else if (lineID > currentLine)
+				{
+					// Another line was drawn over or the line ends
+					currentLine = lineID;
+					currentWidth = 1;
+					fullyVisibleLine = true;
+				}
+				else
+				{
+					// The line ends
+					if (fullyVisibleLine && !lineIsXMajor[currentLine-1])
+					{
+						// check width
+						if (currentWidth != lineWidth)
+						{
+							log << tcu::TestLog::Message << "\tInvalid line width at (" << x - currentWidth << ", " << y << ") - (" << x - 1 << ", " << y << "). Detected width of " << currentWidth << ", expected " << lineWidth << tcu::TestLog::EndMessage;
+							invalidWidthFound = true;
+						}
+					}
+
+					currentLine = lineID;
+					currentWidth = 1;
+					fullyVisibleLine = false;
+				}
+			}
+		}
+
+		log << tcu::TestLog::Message << "Verifying line widths of the y-major lines." << tcu::TestLog::EndMessage;
+		for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x)
+		{
+			bool	fullyVisibleLine		= false;
+			bool	previousPixelUndefined	= false;
+			int		currentLine				= 0;
+			int		currentWidth			= 1;
+
+			for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y)
+			{
+				const bool	result	= compareColors(surface.getPixel(x, y), tcu::RGBA::white, args.redBits, args.greenBits, args.blueBits);
+				int			lineID	= 0;
+
+				// Which line does this fragment belong to?
+
+				if (result)
+				{
+					bool multipleNearbyLines = false;
+
+					for (int dy = -1; dy < 2; ++dy)
+					for (int dx = -1; dx < 2; ++dx)
+					{
+						const int nearbyID = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();
+						if (nearbyID)
+						{
+							if (lineID && lineID != nearbyID)
+								multipleNearbyLines = true;
+							lineID = nearbyID;
+						}
+					}
+
+					if (multipleNearbyLines)
+					{
+						// Another line is too close, don't try to calculate width here
+						previousPixelUndefined = true;
+						continue;
+					}
+				}
+
+				// Only line with id of lineID is nearby
+
+				if (previousPixelUndefined)
+				{
+					// The line might have been overdrawn or not
+					currentLine = lineID;
+					currentWidth = 1;
+					fullyVisibleLine = false;
+					previousPixelUndefined = false;
+				}
+				else if (lineID == currentLine)
+				{
+					// Current line continues
+					++currentWidth;
+				}
+				else if (lineID > currentLine)
+				{
+					// Another line was drawn over or the line ends
+					currentLine = lineID;
+					currentWidth = 1;
+					fullyVisibleLine = true;
+				}
+				else
+				{
+					// The line ends
+					if (fullyVisibleLine && lineIsXMajor[currentLine-1])
+					{
+						// check width
+						if (currentWidth != lineWidth)
+						{
+							log << tcu::TestLog::Message << "\tInvalid line width at (" << x << ", " << y - currentWidth << ") - (" << x  << ", " << y - 1 << "). Detected width of " << currentWidth << ", expected " << lineWidth << tcu::TestLog::EndMessage;
+							invalidWidthFound = true;
+						}
+					}
+
+					currentLine = lineID;
+					currentWidth = 1;
+					fullyVisibleLine = false;
+				}
+			}
+		}
+
+		if (invalidWidthFound)
+		{
+			log << tcu::TestLog::Message << "Invalid line width found, image is not valid." << tcu::TestLog::EndMessage;
+			allOK = false;
+		}
+		else
+		{
+			log << tcu::TestLog::Message << "Line widths are valid." << tcu::TestLog::EndMessage;
+		}
+	}
+
+	//\todo [2013-10-24 jarkko].
+	//Requirement 4. If two line segments share a common endpoint, and both segments are either
+	//x-major (both left-to-right or both right-to-left) or y-major (both bottom-totop
+	//or both top-to-bottom), then rasterizing both segments may not produce
+	//duplicate fragments, nor may any fragments be omitted so as to interrupt
+	//continuity of the connected segments.
+
+	if (!imageShown)
+	{
+		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
+			<< tcu::TestLog::Image("Result", "Result", surface)
+			<< tcu::TestLog::EndImageSet;
+	}
+
+	return allOK;
+}
+
+struct SingleSampleLineCoverageCandidate
+{
+	int			lineNdx;
+	tcu::IVec3	colorMin;
+	tcu::IVec3	colorMax;
+	tcu::Vec3	colorMinF;
+	tcu::Vec3	colorMaxF;
+	tcu::Vec3	valueRangeMin;
+	tcu::Vec3	valueRangeMax;
+};
+
+bool verifySinglesampleLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+{
+	DE_ASSERT(deFloatFrac(scene.lineWidth) != 0.5f); // rounding direction is not defined, disallow undefined cases
+	DE_ASSERT(scene.lines.size() < 8); // coverage indices are stored as bitmask in a unsigned 8-bit ints
+
+	const tcu::RGBA		invalidPixelColor	= tcu::RGBA(255, 0, 0, 255);
+	const tcu::IVec2	viewportSize		= tcu::IVec2(surface.getWidth(), surface.getHeight());
+	const int			errorFloodThreshold	= 4;
+	int					errorCount			= 0;
+	tcu::Surface		errorMask			(surface.getWidth(), surface.getHeight());
+	int					invalidPixels		= 0;
+
+	// log format
+
+	log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage;
+	if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
+		log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage;
+
+	// Reference renderer produces correct fragments using the diamond-exit-rule. Make 2D int array, store line coverage as a 8-bit bitfield
+	// The map is used to find lines with potential coverage to a given pixel
+	tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());
+	tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));
+
+	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
+	{
+		rr::SingleSampleLineRasterizer rasterizer(tcu::IVec4(0, 0, surface.getWidth(), surface.getHeight()));
+
+		const tcu::Vec2 lineNormalizedDeviceSpace[2] =
+		{
+			tcu::Vec2(scene.lines[lineNdx].positions[0].x() / scene.lines[lineNdx].positions[0].w(), scene.lines[lineNdx].positions[0].y() / scene.lines[lineNdx].positions[0].w()),
+			tcu::Vec2(scene.lines[lineNdx].positions[1].x() / scene.lines[lineNdx].positions[1].w(), scene.lines[lineNdx].positions[1].y() / scene.lines[lineNdx].positions[1].w()),
+		};
+		const tcu::Vec4 lineScreenSpace[2] =
+		{
+			tcu::Vec4((lineNormalizedDeviceSpace[0].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[0].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f),
+			tcu::Vec4((lineNormalizedDeviceSpace[1].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[1].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f),
+		};
+
+		rasterizer.init(lineScreenSpace[0], lineScreenSpace[1], scene.lineWidth);
+
+		// Calculate correct line coverage
+		for (;;)
+		{
+			const int			maxPackets			= 32;
+			int					numRasterized		= 0;
+			rr::FragmentPacket	packets[maxPackets];
+
+			rasterizer.rasterize(packets, DE_NULL, maxPackets, numRasterized);
+
+			for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
+			{
+				for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+				{
+					if ((deUint32)packets[packetNdx].coverage & (1 << fragNdx))
+					{
+						const tcu::IVec2	fragPos			= packets[packetNdx].position + tcu::IVec2(fragNdx%2, fragNdx/2);
+						const int			previousMask	= referenceLineMap.getAccess().getPixelInt(fragPos.x(), fragPos.y()).x();
+						const int			newMask			= (previousMask) | (1UL << lineNdx);
+
+						referenceLineMap.getAccess().setPixel(tcu::IVec4(newMask, 0, 0, 0), fragPos.x(), fragPos.y());
+					}
+				}
+			}
+
+			if (numRasterized != maxPackets)
+				break;
+		}
+	}
+
+	// Find all possible lines with coverage, check pixel color matches one of them
+
+	for (int y = 1; y < surface.getHeight() - 1; ++y)
+	for (int x = 1; x < surface.getWidth()  - 1; ++x)
+	{
+		const tcu::RGBA		color					= surface.getPixel(x, y);
+		const tcu::IVec3	pixelNativeColor		= convertRGB8ToNativeFormat(color, args);	// Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
+		int					lineCoverageSet			= 0;										// !< lines that may cover this fragment
+		int					lineSurroundingCoverage = 0xFFFF;									// !< lines that will cover this fragment
+		bool				matchFound				= false;
+		const tcu::IVec3	formatLimit				((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);
+
+		std::vector<SingleSampleLineCoverageCandidate> candidates;
+
+		// Find lines with possible coverage
+
+		for (int dy = -1; dy < 2; ++dy)
+		for (int dx = -1; dx < 2; ++dx)
+		{
+			const int coverage = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();
+
+			lineCoverageSet			|= coverage;
+			lineSurroundingCoverage	&= coverage;
+		}
+
+		// background color is possible?
+		if (lineSurroundingCoverage == 0 && compareColors(color, tcu::RGBA::black, args.redBits, args.greenBits, args.blueBits))
+			continue;
+
+		// Check those lines
+
+		for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
+		{
+			if (((lineCoverageSet >> lineNdx) & 0x01) != 0)
+			{
+				const LineInterpolationRange range = calcSingleSampleLineInterpolationRange(scene.lines[lineNdx].positions[0],
+																							scene.lines[lineNdx].positions[1],
+																							tcu::IVec2(x, y),
+																							viewportSize,
+																							args.subpixelBits);
+
+				const tcu::Vec4		valueMin		= de::clamp(range.min.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] + de::clamp(range.min.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1];
+				const tcu::Vec4		valueMax		= de::clamp(range.max.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] + de::clamp(range.max.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1];
+
+				const tcu::Vec3		colorMinF		(de::clamp(valueMin.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()),
+													 de::clamp(valueMin.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()),
+													 de::clamp(valueMin.z() * formatLimit.z(), 0.0f, (float)formatLimit.z()));
+				const tcu::Vec3		colorMaxF		(de::clamp(valueMax.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()),
+													 de::clamp(valueMax.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()),
+													 de::clamp(valueMax.z() * formatLimit.z(), 0.0f, (float)formatLimit.z()));
+				const tcu::IVec3	colorMin		((int)deFloatFloor(colorMinF.x()),
+													 (int)deFloatFloor(colorMinF.y()),
+													 (int)deFloatFloor(colorMinF.z()));
+				const tcu::IVec3	colorMax		((int)deFloatCeil (colorMaxF.x()),
+													 (int)deFloatCeil (colorMaxF.y()),
+													 (int)deFloatCeil (colorMaxF.z()));
+
+				// Verify validity
+				if (pixelNativeColor.x() < colorMin.x() ||
+					pixelNativeColor.y() < colorMin.y() ||
+					pixelNativeColor.z() < colorMin.z() ||
+					pixelNativeColor.x() > colorMax.x() ||
+					pixelNativeColor.y() > colorMax.y() ||
+					pixelNativeColor.z() > colorMax.z())
+				{
+					if (errorCount < errorFloodThreshold)
+					{
+						// Store candidate information for logging
+						SingleSampleLineCoverageCandidate candidate;
+
+						candidate.lineNdx		= lineNdx;
+						candidate.colorMin		= colorMin;
+						candidate.colorMax		= colorMax;
+						candidate.colorMinF		= colorMinF;
+						candidate.colorMaxF		= colorMaxF;
+						candidate.valueRangeMin	= valueMin.swizzle(0, 1, 2);
+						candidate.valueRangeMax	= valueMax.swizzle(0, 1, 2);
+
+						candidates.push_back(candidate);
+					}
+				}
+				else
+				{
+					matchFound = true;
+					break;
+				}
+			}
+		}
+
+		if (matchFound)
+			continue;
+
+		// invalid fragment
+		++invalidPixels;
+		errorMask.setPixel(x, y, invalidPixelColor);
+
+		++errorCount;
+
+		// don't fill the logs with too much data
+		if (errorCount < errorFloodThreshold)
+		{
+			log << tcu::TestLog::Message
+				<< "Found an invalid pixel at (" << x << "," << y << "), " << (int)candidates.size() << " candidate reference value(s) found:\n"
+				<< "\tPixel color:\t\t" << color << "\n"
+				<< "\tNative color:\t\t" << pixelNativeColor << "\n"
+				<< tcu::TestLog::EndMessage;
+
+			for (int candidateNdx = 0; candidateNdx < (int)candidates.size(); ++candidateNdx)
+			{
+				const SingleSampleLineCoverageCandidate& candidate = candidates[candidateNdx];
+
+				log << tcu::TestLog::Message << "\tCandidate (line " << candidate.lineNdx << "):\n"
+					<< "\t\tReference native color min: " << tcu::clamp(candidate.colorMin, tcu::IVec3(0,0,0), formatLimit) << "\n"
+					<< "\t\tReference native color max: " << tcu::clamp(candidate.colorMax, tcu::IVec3(0,0,0), formatLimit) << "\n"
+					<< "\t\tReference native float min: " << tcu::clamp(candidate.colorMinF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
+					<< "\t\tReference native float max: " << tcu::clamp(candidate.colorMaxF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
+					<< "\t\tFmin:\t" << tcu::clamp(candidate.valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
+					<< "\t\tFmax:\t" << tcu::clamp(candidate.valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
+					<< tcu::TestLog::EndMessage;
+			}
+		}
+	}
+
+	// don't just hide failures
+	if (errorCount > errorFloodThreshold)
+		log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage;
+
+	// report result
+	if (invalidPixels)
+	{
+		log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage;
+		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
+			<< tcu::TestLog::Image("Result", "Result",			surface)
+			<< tcu::TestLog::Image("ErrorMask", "ErrorMask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+
+		return false;
+	}
+	else
+	{
+		log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
+		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
+			<< tcu::TestLog::Image("Result", "Result", surface)
+			<< tcu::TestLog::EndImageSet;
+
+		return true;
+	}
+}
+
+} // anonymous
+
+CoverageType calculateTriangleCoverage (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2, const tcu::IVec2& pixel, const tcu::IVec2& viewportSize, int subpixelBits, bool multisample)
+{
+	typedef tcu::Vector<deInt64, 2> I64Vec2;
+
+	const deUint64		numSubPixels						= ((deUint64)1) << subpixelBits;
+	const deUint64		pixelHitBoxSize						= (multisample) ? (numSubPixels) : (2+2);	//!< allow 4 central (2x2) for non-multisample pixels. Rounding may move edges 1 subpixel to any direction.
+	const bool			order								= isTriangleClockwise(p0, p1, p2);			//!< clockwise / counter-clockwise
+	const tcu::Vec4&	orderedP0							= p0;										//!< vertices of a clockwise triangle
+	const tcu::Vec4&	orderedP1							= (order) ? (p1) : (p2);
+	const tcu::Vec4&	orderedP2							= (order) ? (p2) : (p1);
+	const tcu::Vec2		triangleNormalizedDeviceSpace[3]	=
+	{
+		tcu::Vec2(orderedP0.x() / orderedP0.w(), orderedP0.y() / orderedP0.w()),
+		tcu::Vec2(orderedP1.x() / orderedP1.w(), orderedP1.y() / orderedP1.w()),
+		tcu::Vec2(orderedP2.x() / orderedP2.w(), orderedP2.y() / orderedP2.w()),
+	};
+	const tcu::Vec2		triangleScreenSpace[3]				=
+	{
+		(triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
+		(triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
+		(triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
+	};
+
+	// Broad bounding box - pixel check
+	{
+		const float minX = de::min(de::min(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
+		const float minY = de::min(de::min(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
+		const float maxX = de::max(de::max(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
+		const float maxY = de::max(de::max(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
+
+		if ((float)pixel.x() > maxX + 1 ||
+			(float)pixel.y() > maxY + 1 ||
+			(float)pixel.x() < minX - 1 ||
+			(float)pixel.y() < minY - 1)
+			return COVERAGE_NONE;
+	}
+
+	// Broad triangle - pixel area intersection
+	{
+		const I64Vec2 pixelCenterPosition = I64Vec2(pixel.x(), pixel.y()) * I64Vec2(numSubPixels, numSubPixels) + I64Vec2(numSubPixels / 2, numSubPixels / 2);
+		const I64Vec2 triangleSubPixelSpaceRound[3] =
+		{
+			I64Vec2(deRoundFloatToInt32(triangleScreenSpace[0].x()*numSubPixels), deRoundFloatToInt32(triangleScreenSpace[0].y()*numSubPixels)),
+			I64Vec2(deRoundFloatToInt32(triangleScreenSpace[1].x()*numSubPixels), deRoundFloatToInt32(triangleScreenSpace[1].y()*numSubPixels)),
+			I64Vec2(deRoundFloatToInt32(triangleScreenSpace[2].x()*numSubPixels), deRoundFloatToInt32(triangleScreenSpace[2].y()*numSubPixels)),
+		};
+
+		// Check (using cross product) if pixel center is
+		// a) too far from any edge
+		// b) fully inside all edges
+		bool insideAllEdges = true;
+		for (int vtxNdx = 0; vtxNdx < 3; ++vtxNdx)
+		{
+			const int		otherVtxNdx				= (vtxNdx + 1) % 3;
+			const deInt64	maxPixelDistanceSquared	= pixelHitBoxSize*pixelHitBoxSize; // Max distance from the pixel center from within the pixel is (sqrt(2) * boxWidth/2). Use 2x value for rounding tolerance
+			const I64Vec2	edge					= triangleSubPixelSpaceRound[otherVtxNdx]	- triangleSubPixelSpaceRound[vtxNdx];
+			const I64Vec2	v						= pixelCenterPosition						- triangleSubPixelSpaceRound[vtxNdx];
+			const deInt64	crossProduct			= (edge.x() * v.y() - edge.y() * v.x());
+
+			// distance from edge: (edge x v) / |edge|
+			//     (edge x v) / |edge| > maxPixelDistance
+			// ==> (edge x v)^2 / edge^2 > maxPixelDistance^2    | edge x v > 0
+			// ==> (edge x v)^2 > maxPixelDistance^2 * edge^2
+			if (crossProduct < 0 && crossProduct*crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(edge))
+				return COVERAGE_NONE;
+			if (crossProduct < 0 || crossProduct*crossProduct < maxPixelDistanceSquared * tcu::lengthSquared(edge))
+				insideAllEdges = false;
+		}
+
+		if (insideAllEdges)
+			return COVERAGE_FULL;
+	}
+
+	// Accurate intersection for edge pixels
+	{
+		//  In multisampling, the sample points can be anywhere in the pixel, and in single sampling only in the center.
+		const I64Vec2 pixelCorners[4] =
+		{
+			I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+0) * numSubPixels),
+			I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+0) * numSubPixels),
+			I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+1) * numSubPixels),
+			I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+1) * numSubPixels),
+		};
+		const I64Vec2 pixelCenterCorners[4] =
+		{
+			I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 0, pixel.y() * numSubPixels + numSubPixels/2 + 0),
+			I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 1, pixel.y() * numSubPixels + numSubPixels/2 + 0),
+			I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 1, pixel.y() * numSubPixels + numSubPixels/2 + 1),
+			I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 0, pixel.y() * numSubPixels + numSubPixels/2 + 1),
+		};
+
+		// both rounding directions
+		const I64Vec2 triangleSubPixelSpaceFloor[3] =
+		{
+			I64Vec2(deFloorFloatToInt32(triangleScreenSpace[0].x()*numSubPixels), deFloorFloatToInt32(triangleScreenSpace[0].y()*numSubPixels)),
+			I64Vec2(deFloorFloatToInt32(triangleScreenSpace[1].x()*numSubPixels), deFloorFloatToInt32(triangleScreenSpace[1].y()*numSubPixels)),
+			I64Vec2(deFloorFloatToInt32(triangleScreenSpace[2].x()*numSubPixels), deFloorFloatToInt32(triangleScreenSpace[2].y()*numSubPixels)),
+		};
+		const I64Vec2 triangleSubPixelSpaceCeil[3] =
+		{
+			I64Vec2(deCeilFloatToInt32(triangleScreenSpace[0].x()*numSubPixels), deCeilFloatToInt32(triangleScreenSpace[0].y()*numSubPixels)),
+			I64Vec2(deCeilFloatToInt32(triangleScreenSpace[1].x()*numSubPixels), deCeilFloatToInt32(triangleScreenSpace[1].y()*numSubPixels)),
+			I64Vec2(deCeilFloatToInt32(triangleScreenSpace[2].x()*numSubPixels), deCeilFloatToInt32(triangleScreenSpace[2].y()*numSubPixels)),
+		};
+		const I64Vec2* const corners = (multisample) ? (pixelCorners) : (pixelCenterCorners);
+
+		// Test if any edge (with any rounding) intersects the pixel (boundary). If it does => Partial. If not => fully inside or outside
+
+		for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
+		for (int startRounding = 0; startRounding < 4; ++startRounding)
+		for (int endRounding = 0; endRounding < 4; ++endRounding)
+		{
+			const int		nextEdgeNdx	= (edgeNdx+1) % 3;
+			const I64Vec2	startPos	((startRounding&0x01)	? (triangleSubPixelSpaceFloor[edgeNdx].x())		: (triangleSubPixelSpaceCeil[edgeNdx].x()),		(startRounding&0x02)	? (triangleSubPixelSpaceFloor[edgeNdx].y())		: (triangleSubPixelSpaceCeil[edgeNdx].y()));
+			const I64Vec2	endPos		((endRounding&0x01)		? (triangleSubPixelSpaceFloor[nextEdgeNdx].x())	: (triangleSubPixelSpaceCeil[nextEdgeNdx].x()),	(endRounding&0x02)		? (triangleSubPixelSpaceFloor[nextEdgeNdx].y())	: (triangleSubPixelSpaceCeil[nextEdgeNdx].y()));
+			const I64Vec2	edge		= endPos - startPos;
+
+			for (int pixelEdgeNdx = 0; pixelEdgeNdx < 4; ++pixelEdgeNdx)
+			{
+				const int pixelEdgeEnd = (pixelEdgeNdx + 1) % 4;
+
+				if (lineLineIntersect(startPos, endPos, corners[pixelEdgeNdx], corners[pixelEdgeEnd]))
+					return COVERAGE_PARTIAL;
+			}
+		}
+
+		// fully inside or outside
+		for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
+		{
+			const int		nextEdgeNdx		= (edgeNdx+1) % 3;
+			const I64Vec2&	startPos		= triangleSubPixelSpaceFloor[edgeNdx];
+			const I64Vec2&	endPos			= triangleSubPixelSpaceFloor[nextEdgeNdx];
+			const I64Vec2	edge			= endPos - startPos;
+			const I64Vec2	v				= corners[0] - endPos;
+			const deInt64	crossProduct	= (edge.x() * v.y() - edge.y() * v.x());
+
+			// a corner of the pixel is outside => "fully inside" option is impossible
+			if (crossProduct < 0)
+				return COVERAGE_NONE;
+		}
+
+		return COVERAGE_FULL;
+	}
+}
+
+bool verifyTriangleGroupRasterization (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, VerificationMode mode)
+{
+	DE_ASSERT(mode < VERIFICATIONMODE_LAST);
+
+	const tcu::RGBA		backGroundColor				= tcu::RGBA(0, 0, 0, 255);
+	const tcu::RGBA		triangleColor				= tcu::RGBA(255, 255, 255, 255);
+	const tcu::RGBA		missingPixelColor			= tcu::RGBA(255, 0, 255, 255);
+	const tcu::RGBA		unexpectedPixelColor		= tcu::RGBA(255, 0, 0, 255);
+	const tcu::RGBA		partialPixelColor			= tcu::RGBA(255, 255, 0, 255);
+	const tcu::RGBA		primitivePixelColor			= tcu::RGBA(30, 30, 30, 255);
+	const int			weakVerificationThreshold	= 10;
+	const bool			multisampled				= (args.numSamples != 0);
+	const tcu::IVec2	viewportSize				= tcu::IVec2(surface.getWidth(), surface.getHeight());
+	int					missingPixels				= 0;
+	int					unexpectedPixels			= 0;
+	int					subPixelBits				= args.subpixelBits;
+	tcu::TextureLevel	coverageMap					(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());
+	tcu::Surface		errorMask					(surface.getWidth(), surface.getHeight());
+
+	// subpixel bits in in a valid range?
+
+	if (subPixelBits < 0)
+	{
+		log << tcu::TestLog::Message << "Invalid subpixel count (" << subPixelBits << "), assuming 0" << tcu::TestLog::EndMessage;
+		subPixelBits = 0;
+	}
+	else if (subPixelBits > 16)
+	{
+		// At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict
+		log << tcu::TestLog::Message << "Subpixel count is greater than 16 (" << subPixelBits << "). Checking results using less strict 16 bit requirements. This may produce false positives." << tcu::TestLog::EndMessage;
+		subPixelBits = 16;
+	}
+
+	// generate coverage map
+
+	tcu::clear(coverageMap.getAccess(), tcu::IVec4(COVERAGE_NONE, 0, 0, 0));
+
+	for (int triNdx = 0; triNdx < (int)scene.triangles.size(); ++triNdx)
+	{
+		const tcu::IVec4 aabb = getTriangleAABB(scene.triangles[triNdx], viewportSize);
+
+		for (int y = de::max(0, aabb.y()); y <= de::min(aabb.w(), coverageMap.getHeight() - 1); ++y)
+		for (int x = de::max(0, aabb.x()); x <= de::min(aabb.z(), coverageMap.getWidth() - 1); ++x)
+		{
+			if (coverageMap.getAccess().getPixelUint(x, y).x() == COVERAGE_FULL)
+				continue;
+
+			const CoverageType coverage = calculateTriangleCoverage(scene.triangles[triNdx].positions[0],
+																	scene.triangles[triNdx].positions[1],
+																	scene.triangles[triNdx].positions[2],
+																	tcu::IVec2(x, y),
+																	viewportSize,
+																	subPixelBits,
+																	multisampled);
+
+			if (coverage == COVERAGE_FULL)
+			{
+				coverageMap.getAccess().setPixel(tcu::IVec4(COVERAGE_FULL, 0, 0, 0), x, y);
+			}
+			else if (coverage == COVERAGE_PARTIAL)
+			{
+				CoverageType resultCoverage = COVERAGE_PARTIAL;
+
+				// Sharing an edge with another triangle?
+				// There should always be such a triangle, but the pixel in the other triangle might be
+				// on multiple edges, some of which are not shared. In these cases the coverage cannot be determined.
+				// Assume full coverage if the pixel is only on a shared edge in shared triangle too.
+				if (pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[triNdx], viewportSize))
+				{
+					bool friendFound = false;
+					for (int friendTriNdx = 0; friendTriNdx < (int)scene.triangles.size(); ++friendTriNdx)
+					{
+						if (friendTriNdx != triNdx && pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[friendTriNdx], viewportSize))
+						{
+							friendFound = true;
+							break;
+						}
+					}
+
+					if (friendFound)
+						resultCoverage = COVERAGE_FULL;
+				}
+
+				coverageMap.getAccess().setPixel(tcu::IVec4(resultCoverage, 0, 0, 0), x, y);
+			}
+		}
+	}
+
+	// check pixels
+
+	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+
+	for (int y = 0; y < surface.getHeight(); ++y)
+	for (int x = 0; x < surface.getWidth(); ++x)
+	{
+		const tcu::RGBA		color				= surface.getPixel(x, y);
+		const bool			imageNoCoverage		= compareColors(color, backGroundColor, args.redBits, args.greenBits, args.blueBits);
+		const bool			imageFullCoverage	= compareColors(color, triangleColor, args.redBits, args.greenBits, args.blueBits);
+		CoverageType		referenceCoverage	= (CoverageType)coverageMap.getAccess().getPixelUint(x, y).x();
+
+		switch (referenceCoverage)
+		{
+			case COVERAGE_NONE:
+				if (!imageNoCoverage)
+				{
+					// coverage where there should not be
+					++unexpectedPixels;
+					errorMask.setPixel(x, y, unexpectedPixelColor);
+				}
+				break;
+
+			case COVERAGE_PARTIAL:
+				// anything goes
+				errorMask.setPixel(x, y, partialPixelColor);
+				break;
+
+			case COVERAGE_FULL:
+				if (!imageFullCoverage)
+				{
+					// no coverage where there should be
+					++missingPixels;
+					errorMask.setPixel(x, y, missingPixelColor);
+				}
+				else
+				{
+					errorMask.setPixel(x, y, primitivePixelColor);
+				}
+				break;
+
+			default:
+				DE_ASSERT(false);
+		};
+	}
+
+	// Output results
+	log << tcu::TestLog::Message << "Verifying rasterization result." << tcu::TestLog::EndMessage;
+
+	if (((mode == VERIFICATIONMODE_STRICT) && (missingPixels + unexpectedPixels > 0)) ||
+		((mode == VERIFICATIONMODE_WEAK)   && (missingPixels + unexpectedPixels > weakVerificationThreshold)))
+	{
+		log << tcu::TestLog::Message << "Invalid pixels found:\n\t"
+			<< missingPixels << " missing pixels. (Marked with purple)\n\t"
+			<< unexpectedPixels << " incorrectly filled pixels. (Marked with red)\n\t"
+			<< "Unknown (subpixel on edge) pixels are marked with yellow."
+			<< tcu::TestLog::EndMessage;
+		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
+			<< tcu::TestLog::Image("Result",	"Result",		surface)
+			<< tcu::TestLog::Image("ErrorMask", "ErrorMask",	errorMask)
+			<< tcu::TestLog::EndImageSet;
+
+		return false;
+	}
+	else
+	{
+		log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
+		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
+			<< tcu::TestLog::Image("Result", "Result", surface)
+			<< tcu::TestLog::EndImageSet;
+
+		return true;
+	}
+}
+
+bool verifyLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+{
+	const bool multisampled = args.numSamples != 0;
+
+	if (multisampled)
+		return verifyMultisampleLineGroupRasterization(surface, scene, args, log);
+	else
+		return verifySinglesampleLineGroupRasterization(surface, scene, args, log);
+}
+
+bool verifyPointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+{
+	// Splitting to triangles is a valid solution in multisampled cases and even in non-multisample cases too.
+	return verifyMultisamplePointGroupRasterization(surface, scene, args, log);
+}
+
+bool verifyTriangleGroupInterpolation (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+{
+	return verifyTriangleGroupInterpolationWithInterpolator(surface, scene, args, log, TriangleInterpolator(scene));
+}
+
+bool verifyLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+{
+	const bool multisampled = args.numSamples != 0;
+
+	if (multisampled)
+		return verifyMultisampleLineGroupInterpolation(surface, scene, args, log);
+	else
+		return verifySinglesampleLineGroupInterpolation(surface, scene, args, log);
+}
+
+} // StateQueryUtil
+} // gls
+} // deqp
diff --git a/modules/glshared/glsRasterizationTestUtil.hpp b/modules/glshared/glsRasterizationTestUtil.hpp
new file mode 100644
index 0000000..7b14ae9
--- /dev/null
+++ b/modules/glshared/glsRasterizationTestUtil.hpp
@@ -0,0 +1,169 @@
+#ifndef _GLSRASTERIZATIONTESTUTIL_HPP
+#define _GLSRASTERIZATIONTESTUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief rasterization test utils.
+ *//*--------------------------------------------------------------------*/
+
+#include "deMath.h"
+#include "tcuDefs.hpp"
+#include "tcuTestLog.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gls
+{
+namespace RasterizationTestUtil
+{
+
+enum CoverageType
+{
+	COVERAGE_FULL = 0,		// !< primitive fully covers the queried area
+	COVERAGE_PARTIAL,		// !< primitive coverage is either partial, or could be full, partial or none depending on rounding and/or fill rules
+	COVERAGE_NONE,			// !< primitive does not cover area at all
+
+	COVERAGE_LAST
+};
+
+enum VerificationMode
+{
+	VERIFICATIONMODE_STRICT = 0,	// !< do not allow even a single bad pixel
+	VERIFICATIONMODE_WEAK,			// !< allow some bad pixels
+
+	VERIFICATIONMODE_LAST
+};
+
+struct TriangleSceneSpec
+{
+	struct SceneTriangle
+	{
+		tcu::Vec4	positions[3];
+		tcu::Vec4	colors[3];
+		bool		sharedEdge[3]; // !< is the edge i -> i+1 shared with another scene triangle
+	};
+
+	std::vector<SceneTriangle> triangles;
+};
+
+struct LineSceneSpec
+{
+	struct SceneLine
+	{
+		tcu::Vec4	positions[2];
+		tcu::Vec4	colors[2];
+	};
+
+	std::vector<SceneLine>	lines;
+	float					lineWidth;
+};
+
+struct PointSceneSpec
+{
+	struct ScenePoint
+	{
+		tcu::Vec4	position;
+		tcu::Vec4	color;
+		float		pointSize;
+	};
+
+	std::vector<ScenePoint> points;
+};
+
+struct RasterizationArguments
+{
+	int numSamples;
+	int subpixelBits;
+	int redBits;
+	int greenBits;
+	int blueBits;
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Calculates triangle coverage at given pixel
+ * Calculates the coverage of a triangle given by three vertices. The
+ * triangle should not be z-clipped. If multisample is false, the pixel
+ * center is compared against the triangle. If multisample is true, the
+ * whole pixel area is compared.
+ *//*--------------------------------------------------------------------*/
+CoverageType calculateTriangleCoverage (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2, const tcu::IVec2& pixel, const tcu::IVec2& viewportSize, int subpixelBits, bool multisample);
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Verify triangle rasterization result
+ * Verifies pixels in the surface are rasterized within the bounds given
+ * by RasterizationArguments. Triangles should not be z-clipped.
+ *
+ * Triangle colors are not used. The triangle is expected to be white.
+ *
+ * Returns false if invalid rasterization is found.
+ *//*--------------------------------------------------------------------*/
+bool verifyTriangleGroupRasterization (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, VerificationMode mode = VERIFICATIONMODE_STRICT);
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Verify line rasterization result
+ * Verifies pixels in the surface are rasterized within the bounds given
+ * by RasterizationArguments. Lines should not be z-clipped.
+ *
+ * Line colors are not used. The line is expected to be white.
+ *
+ * Returns false if invalid rasterization is found.
+ *//*--------------------------------------------------------------------*/
+bool verifyLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log);
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Verify point rasterization result
+ * Verifies points in the surface are rasterized within the bounds given
+ * by RasterizationArguments. Points should not be z-clipped.
+ *
+ * Point colors are not used. The point is expected to be white.
+ *
+ * Returns false if invalid rasterization is found.
+ *//*--------------------------------------------------------------------*/
+bool verifyPointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log);
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Verify triangle color interpolation is valid
+ * Verifies the color of a fragments of a colored triangle is in the
+ * valid range. Triangles should not be z-clipped.
+ *
+ * The background is expected to be black.
+ *
+ * Returns false if invalid rasterization interpolation is found.
+ *//*--------------------------------------------------------------------*/
+bool verifyTriangleGroupInterpolation (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log);
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Verify line color interpolation is valid
+ * Verifies the color of a fragments of a colored line is in the
+ * valid range. Lines should not be z-clipped.
+ *
+ * The background is expected to be black.
+ *
+ * Returns false if invalid rasterization interpolation is found.
+ *//*--------------------------------------------------------------------*/
+bool verifyLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log);
+
+} // StateQueryUtil
+} // gls
+} // deqp
+
+#endif // _GLSRASTERIZATIONTESTUTIL_HPP
diff --git a/modules/glshared/glsSamplerObjectTest.cpp b/modules/glshared/glsSamplerObjectTest.cpp
new file mode 100644
index 0000000..78fa572
--- /dev/null
+++ b/modules/glshared/glsSamplerObjectTest.cpp
@@ -0,0 +1,1176 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sampler object testcases.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsSamplerObjectTest.hpp"
+
+#include "tcuTexture.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuStringTemplate.hpp"
+
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluTextureUtil.hpp"
+
+#include "glwFunctions.hpp"
+
+#include "deRandom.hpp"
+#include "deString.h"
+
+#include "deString.h"
+
+#include <map>
+
+namespace deqp
+{
+namespace gls
+{
+
+namespace
+{
+const int VIEWPORT_WIDTH	= 128;
+const int VIEWPORT_HEIGHT	= 128;
+
+const int TEXTURE2D_WIDTH	= 32;
+const int TEXTURE2D_HEIGHT	= 32;
+
+const int TEXTURE3D_WIDTH	= 32;
+const int TEXTURE3D_HEIGHT	= 32;
+const int TEXTURE3D_DEPTH	= 32;
+
+const int CUBEMAP_SIZE		= 32;
+
+} // anonymous
+
+
+TextureSamplerTest::TextureSamplerTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const TestSpec& spec)
+	: tcu::TestCase		(testCtx, spec.name, spec.desc)
+	, m_renderCtx		(renderCtx)
+	, m_program			(NULL)
+	, m_target			(spec.target)
+	, m_textureState	(spec.textureState)
+	, m_samplerState	(spec.samplerState)
+	, m_random			(deStringHash(spec.name))
+{
+}
+
+void TextureSamplerTest::setTextureState (const glw::Functions& gl, GLenum target, SamplingState state)
+{
+	gl.texParameteri(target, GL_TEXTURE_MIN_FILTER, state.minFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri(target, GL_TEXTURE_MIN_FILTER, state.minFilter)");
+	gl.texParameteri(target, GL_TEXTURE_MAG_FILTER, state.magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri(target, GL_TEXTURE_MAG_FILTER, state.magFilter)");
+	gl.texParameteri(target, GL_TEXTURE_WRAP_S, state.wrapS);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri(target, GL_TEXTURE_WRAP_S, state.wrapS)");
+	gl.texParameteri(target, GL_TEXTURE_WRAP_T, state.wrapT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri(target, GL_TEXTURE_WRAP_T, state.wrapT)");
+	gl.texParameteri(target, GL_TEXTURE_WRAP_R, state.wrapR);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri(target, GL_TEXTURE_WRAP_R, state.wrapR)");
+	gl.texParameterf(target, GL_TEXTURE_MAX_LOD, state.maxLod);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameterf(target, GL_TEXTURE_MAX_LOD, state.maxLod)");
+	gl.texParameterf(target, GL_TEXTURE_MIN_LOD, state.minLod);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameterf(target, GL_TEXTURE_MIN_LOD, state.minLod)");
+}
+
+void TextureSamplerTest::setSamplerState (const glw::Functions& gl, SamplingState state, GLuint sampler)
+{
+	gl.samplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, state.minFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, state.minFilter)");
+	gl.samplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, state.magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, state.magFilter)");
+	gl.samplerParameteri(sampler, GL_TEXTURE_WRAP_S, state.wrapS);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, state.wrapS)");
+	gl.samplerParameteri(sampler, GL_TEXTURE_WRAP_T, state.wrapT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, state.wrapT)");
+	gl.samplerParameteri(sampler, GL_TEXTURE_WRAP_R, state.wrapR);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, state.wrapR)");
+	gl.samplerParameterf(sampler, GL_TEXTURE_MAX_LOD, state.maxLod);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameterf(sampler, GL_TEXTURE_MAX_LOD, state.maxLod)");
+	gl.samplerParameterf(sampler, GL_TEXTURE_MIN_LOD, state.minLod);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, state.minLod)");
+}
+
+const char* TextureSamplerTest::selectVertexShader (GLenum target)
+{
+	switch (target)
+	{
+		case GL_TEXTURE_2D:
+			return
+			"${VTX_HDR}"
+			"${VTX_IN} ${HIGHP} vec2 a_position;\n"
+			"uniform ${HIGHP} float u_posScale;\n"
+			"${VTX_OUT} ${MEDIUMP} vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\tv_texCoord = a_position;\n"
+			"\tgl_Position = vec4(u_posScale * a_position, 0.0, 1.0);\n"
+			"}";
+
+		case GL_TEXTURE_3D:
+			return
+			"${VTX_HDR}"
+			"${VTX_IN} ${HIGHP} vec3 a_position;\n"
+			"uniform ${HIGHP} float u_posScale;\n"
+			"${VTX_OUT} ${MEDIUMP} vec3 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\tv_texCoord = a_position;\n"
+			"\tgl_Position = vec4(u_posScale * a_position.xy, 0.0, 1.0);\n"
+			"}";
+
+		case GL_TEXTURE_CUBE_MAP:
+			return
+			"${VTX_HDR}"
+			"${VTX_IN} ${HIGHP} vec4 a_position;\n"
+			"uniform ${HIGHP} float u_posScale;\n"
+			"${VTX_OUT} ${MEDIUMP} vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\tv_texCoord = a_position.zw;\n"
+			"\tgl_Position = vec4(u_posScale * a_position.xy, 0.0, 1.0);\n"
+			"}";
+
+		default:
+			DE_ASSERT(false);
+			return NULL;
+	}
+}
+
+const char* TextureSamplerTest::selectFragmentShader (GLenum target)
+{
+	switch (target)
+	{
+		case GL_TEXTURE_2D:
+			return
+			"${FRAG_HDR}"
+			"uniform ${LOWP} sampler2D u_sampler;\n"
+			"${FRAG_IN} ${MEDIUMP} vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\t${FRAG_COLOR} = texture(u_sampler, v_texCoord);\n"
+			"}";
+
+		case GL_TEXTURE_3D:
+			return
+			"${FRAG_HDR}"
+			"uniform ${LOWP} sampler3D u_sampler;\n"
+			"${FRAG_IN} ${MEDIUMP} vec3 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\t${FRAG_COLOR} = texture(u_sampler, v_texCoord);\n"
+			"}";
+
+		case GL_TEXTURE_CUBE_MAP:
+			return
+			"${FRAG_HDR}"
+			"uniform ${LOWP} samplerCube u_sampler;\n"
+			"${FRAG_IN} ${MEDIUMP} vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\t${FRAG_COLOR} = texture(u_sampler, vec3(cos(3.14 * v_texCoord.y) * sin(3.14 * v_texCoord.x), sin(3.14 * v_texCoord.y), cos(3.14 * v_texCoord.y) * cos(3.14 * v_texCoord.x)));\n"
+			"}";
+
+		default:
+			DE_ASSERT(false);
+			return NULL;
+	}
+}
+
+void TextureSamplerTest::init (void)
+{
+	const char* vertexShaderTemplate	= selectVertexShader(m_target);
+	const char* fragmentShaderTemplate	= selectFragmentShader(m_target);
+
+	std::map<std::string, std::string>	params;
+
+	if (glu::isGLSLVersionSupported(m_renderCtx.getType(), glu::GLSL_VERSION_300_ES))
+	{
+		params["VTX_HDR"]		= "#version 300 es\n";
+		params["FRAG_HDR"]		= "#version 300 es\nlayout(location = 0) out mediump vec4 o_color;\n";
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "o_color";
+		params["HIGHP"]			= "highp";
+		params["LOWP"]			= "lowp";
+		params["MEDIUMP"]		= "mediump";
+	}
+	else if (glu::isGLSLVersionSupported(m_renderCtx.getType(), glu::GLSL_VERSION_330))
+	{
+		params["VTX_HDR"]		= "#version 330\n";
+		params["FRAG_HDR"]		= "#version 330\nlayout(location = 0) out mediump vec4 o_color;\n";
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "o_color";
+		params["HIGHP"]			= "highp";
+		params["LOWP"]			= "lowp";
+		params["MEDIUMP"]		= "mediump";
+	}
+	else
+		DE_ASSERT(false);
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_renderCtx,
+									   glu::makeVtxFragSources(tcu::StringTemplate(vertexShaderTemplate).specialize(params),
+															   tcu::StringTemplate(fragmentShaderTemplate).specialize(params)));
+
+	if (!m_program->isOk())
+	{
+		tcu::TestLog& log = m_testCtx.getLog();
+		log << *m_program;
+		TCU_FAIL("Failed to compile shaders");
+	}
+}
+
+void TextureSamplerTest::deinit (void)
+{
+	delete m_program;
+	m_program = NULL;
+}
+
+TextureSamplerTest::~TextureSamplerTest (void)
+{
+	deinit();
+}
+
+const float s_positions[] = {
+	-1.0, -1.0,
+	 1.0, -1.0,
+	 1.0,  1.0,
+
+	 1.0,  1.0,
+	-1.0,  1.0,
+	-1.0, -1.0
+};
+
+const float s_positions3D[] = {
+	-1.0f, -1.0f, -1.0f,
+	 1.0f, -1.0f,  1.0f,
+	 1.0f,  1.0f, -1.0f,
+
+	 1.0f,  1.0f, -1.0f,
+	-1.0f,  1.0f,  1.0f,
+	-1.0f, -1.0f, -1.0f
+};
+
+const float s_positionsCube[] = {
+	-1.0f, -1.0f, -1.0f, -0.5f,
+	 1.0f, -1.0f,  1.0f, -0.5f,
+	 1.0f,  1.0f,  1.0f,  0.5f,
+
+	 1.0f,  1.0f,  1.0f,  0.5f,
+	-1.0f,  1.0f, -1.0f,  0.5f,
+	-1.0f, -1.0f, -1.0f, -0.5f
+};
+
+void TextureSamplerTest::render (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	GLuint	samplerLoc	= (GLuint)-1;
+	GLuint	scaleLoc	= (GLuint)-1;
+
+	gl.useProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram(m_program->getProgram())");
+
+	samplerLoc = gl.getUniformLocation(m_program->getProgram(), "u_sampler");
+	TCU_CHECK(samplerLoc != (GLuint)-1);
+
+	scaleLoc = gl.getUniformLocation(m_program->getProgram(), "u_posScale");
+	TCU_CHECK(scaleLoc != (GLuint)-1);
+
+	gl.clearColor(0.5f, 0.5f, 0.5f, 1.0f);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glClearColor(0.5f, 0.5f, 0.5f, 1.0f)");
+
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glClear(GL_COLOR_BUFFER_BIT)");
+
+	gl.uniform1i(samplerLoc, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i(samplerLoc, 0)");
+
+	gl.uniform1f(scaleLoc, 1.0f);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1f(scaleLoc, 1.0f)");
+
+	switch (m_target)
+	{
+		case GL_TEXTURE_2D:
+		{
+			glu::VertexArrayBinding vertexArrays[] =
+			{
+				glu::VertexArrayBinding(glu::BindingPoint("a_position"), glu::VertexArrayPointer(glu::VTX_COMP_FLOAT, glu::VTX_COMP_CONVERT_NONE, 2, 6, 0, s_positions))
+			};
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			gl.uniform1f(scaleLoc, 0.25f);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1f(scaleLoc, 0.25f)");
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			break;
+		}
+
+		case GL_TEXTURE_3D:
+		{
+			glu::VertexArrayBinding vertexArrays[] =
+			{
+				glu::VertexArrayBinding(glu::BindingPoint("a_position"), glu::VertexArrayPointer(glu::VTX_COMP_FLOAT, glu::VTX_COMP_CONVERT_NONE, 3, 6, 0, s_positions3D))
+			};
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			gl.uniform1f(scaleLoc, 0.25f);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1f(scaleLoc, 0.25f)");
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			break;
+		}
+
+		case GL_TEXTURE_CUBE_MAP:
+		{
+			glu::VertexArrayBinding vertexArrays[] =
+			{
+				glu::VertexArrayBinding(glu::BindingPoint("a_position"), glu::VertexArrayPointer(glu::VTX_COMP_FLOAT, glu::VTX_COMP_CONVERT_NONE, 4, 6, 0, s_positionsCube))
+			};
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			gl.uniform1f(scaleLoc, 0.25f);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1f(scaleLoc, 0.25f)");
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+GLuint TextureSamplerTest::createTexture2D (const glw::Functions& gl)
+{
+	GLuint			texture		= (GLuint)-1;
+	tcu::Texture2D	refTexture	(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), TEXTURE2D_WIDTH, TEXTURE2D_HEIGHT);
+
+	refTexture.allocLevel(0);
+	tcu::fillWithComponentGradients(refTexture.getLevel(0), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+	gl.genTextures(1, &texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures(1, &texture)");
+
+	gl.bindTexture(GL_TEXTURE_2D, texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_2D, texture)");
+
+	gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, refTexture.getWidth(), refTexture.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, refTexture.getLevel(0).getDataPtr());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, refTexture.getWidth(), refTexture.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, refTexture.getLevel(0).getDataPtr())");
+
+	gl.generateMipmap(GL_TEXTURE_2D);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenerateMipmap(GL_TEXTURE_2D)");
+
+	gl.bindTexture(GL_TEXTURE_2D, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_2D, texture)");
+
+	return texture;
+}
+
+GLuint TextureSamplerTest::createTexture3D (const glw::Functions& gl)
+{
+	GLuint			texture		= (GLuint)-1;
+	tcu::Texture3D	refTexture	(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), TEXTURE3D_WIDTH, TEXTURE3D_HEIGHT, TEXTURE3D_DEPTH);
+
+	refTexture.allocLevel(0);
+	tcu::fillWithComponentGradients(refTexture.getLevel(0), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+	gl.genTextures(1, &texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures(1, &texture)");
+
+	gl.bindTexture(GL_TEXTURE_3D, texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_3D, texture)");
+
+	gl.texImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, refTexture.getWidth(), refTexture.getHeight(), refTexture.getDepth(), 0, GL_RGBA, GL_UNSIGNED_BYTE, refTexture.getLevel(0).getDataPtr());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, refTexture.getWidth(), refTexture.getHeight(), refTexture.getDepth(), 0, GL_RGBA, GL_UNSIGNED_BYTE, refTexture.getLevel(0).getDataPtr())");
+
+	gl.generateMipmap(GL_TEXTURE_3D);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenerateMipmap(GL_TEXTURE_3D)");
+
+	gl.bindTexture(GL_TEXTURE_3D, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_3D, 0)");
+
+	return texture;
+}
+
+GLuint TextureSamplerTest::createTextureCube (const glw::Functions& gl)
+{
+	GLuint				texture		= (GLuint)-1;
+	tcu::TextureCube	refTexture	(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), CUBEMAP_SIZE);
+
+	refTexture.allocLevel(tcu::CUBEFACE_POSITIVE_X, 0);
+	refTexture.allocLevel(tcu::CUBEFACE_POSITIVE_Y, 0);
+	refTexture.allocLevel(tcu::CUBEFACE_POSITIVE_Z, 0);
+	refTexture.allocLevel(tcu::CUBEFACE_NEGATIVE_X, 0);
+	refTexture.allocLevel(tcu::CUBEFACE_NEGATIVE_Y, 0);
+	refTexture.allocLevel(tcu::CUBEFACE_NEGATIVE_Z, 0);
+
+	tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_POSITIVE_X), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+	tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_POSITIVE_Y), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+	tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_POSITIVE_Z), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+	tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_NEGATIVE_X), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+	tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_NEGATIVE_Y), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+	tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_NEGATIVE_Z), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_CUBE_MAP, texture)");
+
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+	{
+		const deUint32 target = glu::getGLCubeFace((tcu::CubeFace)face);
+		gl.texImage2D(target, 0, GL_RGBA8, refTexture.getSize(), refTexture.getSize(), 0, GL_RGBA, GL_UNSIGNED_BYTE, refTexture.getLevelFace(0, (tcu::CubeFace)face).getDataPtr());
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage2D(GL_TEXTURE_CUBE_MAP_...) failed");
+
+	gl.generateMipmap(GL_TEXTURE_CUBE_MAP);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenerateMipmap(GL_TEXTURE_CUBE_MAP)");
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_CUBE_MAP, texture)");
+
+	return texture;
+}
+
+GLuint TextureSamplerTest::createTexture (const glw::Functions& gl, GLenum target)
+{
+	switch (target)
+	{
+		case GL_TEXTURE_2D:
+			return createTexture2D(gl);
+
+		case GL_TEXTURE_3D:
+			return createTexture3D(gl);
+
+		case GL_TEXTURE_CUBE_MAP:
+			return createTextureCube(gl);
+
+		default:
+			DE_ASSERT(false);
+			return (GLuint)-1;
+	}
+}
+
+void TextureSamplerTest::renderReferences (tcu::Surface& textureRef, tcu::Surface& samplerRef, int x, int y)
+{
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+	GLuint					texture	= createTexture(gl, m_target);
+
+	gl.viewport(x, y, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport(x, y, VIEWPORT_WIDTH, VIEWPORT_HEIGHT)");
+
+	gl.bindTexture(m_target, texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, texture)");
+
+	setTextureState(gl, m_target, m_textureState);
+	render();
+	glu::readPixels(m_renderCtx, x, y, textureRef.getAccess());
+
+	setTextureState(gl, m_target, m_samplerState);
+	render();
+	glu::readPixels(m_renderCtx, x, y, samplerRef.getAccess());
+
+	gl.deleteTextures(1, &texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteTextures(1, &texture)");
+}
+
+void TextureSamplerTest::renderResults (tcu::Surface& textureResult, tcu::Surface& samplerResult, int x, int y)
+{
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+	GLuint					texture	= createTexture(gl, m_target);
+	GLuint					sampler	= -1;
+
+	gl.viewport(x, y, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport(x, y, VIEWPORT_WIDTH, VIEWPORT_HEIGHT)");
+
+	gl.genSamplers(1, &sampler);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenSamplers(1, &sampler)");
+	TCU_CHECK(sampler != (GLuint)-1);
+
+	gl.bindSampler(0, sampler);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindSampler(0, sampler)");
+
+	// First set sampler state
+	setSamplerState(gl, m_samplerState, sampler);
+
+	// Set texture state
+	gl.bindTexture(m_target, texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, texture)");
+
+	setTextureState(gl, m_target, m_textureState);
+
+	// Render using sampler
+	render();
+	glu::readPixels(m_renderCtx, x, y, samplerResult.getAccess());
+
+	// Render without sampler
+	gl.bindSampler(0, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindSampler(0, 0)");
+
+	render();
+	glu::readPixels(m_renderCtx, x, y, textureResult.getAccess());
+
+	gl.deleteSamplers(1, &sampler);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteSamplers(1, &sampler)");
+	gl.deleteTextures(1, &texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteTextures(1, &texture)");
+}
+
+tcu::TestCase::IterateResult TextureSamplerTest::iterate (void)
+{
+	tcu::TestLog&	log = m_testCtx.getLog();
+
+	tcu::Surface	textureRef(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+	tcu::Surface	samplerRef(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+
+	tcu::Surface	textureResult(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+	tcu::Surface	samplerResult(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+
+	int				x = m_random.getInt(0, m_renderCtx.getRenderTarget().getWidth() - VIEWPORT_WIDTH);
+	int				y = m_random.getInt(0, m_renderCtx.getRenderTarget().getHeight() - VIEWPORT_HEIGHT);
+
+	renderReferences(textureRef, samplerRef, x, y);
+	renderResults(textureResult, samplerResult, x, y);
+
+	bool isOk = pixelThresholdCompare (log, "Sampler render result", "Result from rendering with sampler", samplerRef, samplerResult, tcu::RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);
+
+	if (!pixelThresholdCompare (log, "Texture render result", "Result from rendering with texture state", textureRef, textureResult, tcu::RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT))
+		isOk = false;
+
+	if (!isOk)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		return STOP;
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+MultiTextureSamplerTest::MultiTextureSamplerTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const TestSpec& spec)
+	: TestCase			(testCtx, spec.name, spec.desc)
+	, m_renderCtx		(renderCtx)
+	, m_program			(NULL)
+	, m_target			(spec.target)
+	, m_textureState1	(spec.textureState1)
+	, m_textureState2	(spec.textureState2)
+	, m_samplerState	(spec.samplerState)
+	, m_random			(deStringHash(spec.name))
+{
+}
+
+void MultiTextureSamplerTest::setTextureState (const glw::Functions& gl, GLenum target, SamplingState state)
+{
+	gl.texParameteri(target, GL_TEXTURE_MIN_FILTER, state.minFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri(target, GL_TEXTURE_MIN_FILTER, state.minFilter)");
+	gl.texParameteri(target, GL_TEXTURE_MAG_FILTER, state.magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri(target, GL_TEXTURE_MAG_FILTER, state.magFilter)");
+	gl.texParameteri(target, GL_TEXTURE_WRAP_S, state.wrapS);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri(target, GL_TEXTURE_WRAP_S, state.wrapS)");
+	gl.texParameteri(target, GL_TEXTURE_WRAP_T, state.wrapT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri(target, GL_TEXTURE_WRAP_T, state.wrapT)");
+	gl.texParameteri(target, GL_TEXTURE_WRAP_R, state.wrapR);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri(target, GL_TEXTURE_WRAP_R, state.wrapR)");
+	gl.texParameterf(target, GL_TEXTURE_MAX_LOD, state.maxLod);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameterf(target, GL_TEXTURE_MAX_LOD, state.maxLod)");
+	gl.texParameterf(target, GL_TEXTURE_MIN_LOD, state.minLod);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameterf(target, GL_TEXTURE_MIN_LOD, state.minLod)");
+}
+
+void MultiTextureSamplerTest::setSamplerState (const glw::Functions& gl, SamplingState state, GLuint sampler)
+{
+	gl.samplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, state.minFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, state.minFilter)");
+	gl.samplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, state.magFilter);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, state.magFilter)");
+	gl.samplerParameteri(sampler, GL_TEXTURE_WRAP_S, state.wrapS);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, state.wrapS)");
+	gl.samplerParameteri(sampler, GL_TEXTURE_WRAP_T, state.wrapT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, state.wrapT)");
+	gl.samplerParameteri(sampler, GL_TEXTURE_WRAP_R, state.wrapR);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, state.wrapR)");
+	gl.samplerParameterf(sampler, GL_TEXTURE_MAX_LOD, state.maxLod);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameterf(sampler, GL_TEXTURE_MAX_LOD, state.maxLod)");
+	gl.samplerParameterf(sampler, GL_TEXTURE_MIN_LOD, state.minLod);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, state.minLod)");
+}
+
+const char* MultiTextureSamplerTest::selectVertexShader (GLenum target)
+{
+	switch (target)
+	{
+		case GL_TEXTURE_2D:
+			return
+			"${VTX_HDR}"
+			"${VTX_IN} ${HIGHP} vec2 a_position;\n"
+			"uniform ${HIGHP} float u_posScale;\n"
+			"${VTX_OUT} ${MEDIUMP} vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\tv_texCoord = a_position;\n"
+			"\tgl_Position = vec4(u_posScale * a_position, 0.0, 1.0);\n"
+			"}";
+
+		case GL_TEXTURE_3D:
+			return
+			"${VTX_HDR}"
+			"${VTX_IN} ${HIGHP} vec3 a_position;\n"
+			"uniform ${HIGHP} float u_posScale;\n"
+			"${VTX_OUT} ${MEDIUMP} vec3 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\tv_texCoord = a_position;\n"
+			"\tgl_Position = vec4(u_posScale * a_position.xy, 0.0, 1.0);\n"
+			"}";
+
+		case GL_TEXTURE_CUBE_MAP:
+			return
+			"${VTX_HDR}"
+			"${VTX_IN} ${HIGHP} vec4 a_position;\n"
+			"uniform ${HIGHP} float u_posScale;\n"
+			"${VTX_OUT} ${MEDIUMP} vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\tv_texCoord = a_position.zw;\n"
+			"\tgl_Position = vec4(u_posScale * a_position.xy, 0.0, 1.0);\n"
+			"}";
+
+		default:
+			DE_ASSERT(false);
+			return NULL;
+	}
+
+}
+
+const char* MultiTextureSamplerTest::selectFragmentShader (GLenum target)
+{
+	switch (target)
+	{
+		case GL_TEXTURE_2D:
+			return
+			"${FRAG_HDR}"
+			"uniform ${LOWP} sampler2D u_sampler1;\n"
+			"uniform ${LOWP} sampler2D u_sampler2;\n"
+			"${FRAG_IN} ${MEDIUMP} vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\t${FRAG_COLOR} = vec4(0.75, 0.75, 0.75, 1.0) * (texture(u_sampler1, v_texCoord) + texture(u_sampler2, v_texCoord));\n"
+			"}";
+
+			break;
+
+		case GL_TEXTURE_3D:
+			return
+			"${FRAG_HDR}"
+			"uniform ${LOWP} sampler3D u_sampler1;\n"
+			"uniform ${LOWP} sampler3D u_sampler2;\n"
+			"${FRAG_IN} ${MEDIUMP} vec3 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\t${FRAG_COLOR} = vec4(0.75, 0.75, 0.75, 1.0) * (texture(u_sampler1, v_texCoord) + texture(u_sampler2, v_texCoord));\n"
+			"}";
+
+		case GL_TEXTURE_CUBE_MAP:
+			return
+			"${FRAG_HDR}"
+			"uniform ${LOWP} samplerCube u_sampler1;\n"
+			"uniform ${LOWP} samplerCube u_sampler2;\n"
+			"${FRAG_IN} ${MEDIUMP} vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\t${FRAG_COLOR} = vec4(0.5, 0.5, 0.5, 1.0) * (texture(u_sampler1, vec3(cos(3.14 * v_texCoord.y) * sin(3.14 * v_texCoord.x), sin(3.14 * v_texCoord.y), cos(3.14 * v_texCoord.y) * cos(3.14 * v_texCoord.x)))"
+			"+ texture(u_sampler2, vec3(cos(3.14 * v_texCoord.y) * sin(3.14 * v_texCoord.x), sin(3.14 * v_texCoord.y), cos(3.14 * v_texCoord.y) * cos(3.14 * v_texCoord.x))));\n"
+			"}";
+
+		default:
+			DE_ASSERT(false);
+			return NULL;
+	}
+
+}
+
+void MultiTextureSamplerTest::init (void)
+{
+	const char* vertexShaderTemplate	= selectVertexShader(m_target);
+	const char* fragmentShaderTemplate	= selectFragmentShader(m_target);
+
+	std::map<std::string, std::string>	params;
+
+	if (glu::isGLSLVersionSupported(m_renderCtx.getType(), glu::GLSL_VERSION_300_ES))
+	{
+		params["VTX_HDR"]		= "#version 300 es\n";
+		params["FRAG_HDR"]		= "#version 300 es\nlayout(location = 0) out mediump vec4 o_color;\n";
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "o_color";
+		params["HIGHP"]			= "highp";
+		params["LOWP"]			= "lowp";
+		params["MEDIUMP"]		= "mediump";
+	}
+	else if (glu::isGLSLVersionSupported(m_renderCtx.getType(), glu::GLSL_VERSION_330))
+	{
+		params["VTX_HDR"]		= "#version 330\n";
+		params["FRAG_HDR"]		= "#version 330\nlayout(location = 0) out mediump vec4 o_color;\n";
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "o_color";
+		params["HIGHP"]			= "highp";
+		params["LOWP"]			= "lowp";
+		params["MEDIUMP"]		= "mediump";
+	}
+	else
+		DE_ASSERT(false);
+
+	DE_ASSERT(!m_program);
+	m_program = new glu::ShaderProgram(m_renderCtx,
+									   glu::makeVtxFragSources(tcu::StringTemplate(vertexShaderTemplate).specialize(params),
+															   tcu::StringTemplate(fragmentShaderTemplate).specialize(params)));
+	if (!m_program->isOk())
+	{
+		tcu::TestLog& log = m_testCtx.getLog();
+
+		log << *m_program;
+		TCU_FAIL("Failed to compile shaders");
+	}
+}
+
+void MultiTextureSamplerTest::deinit (void)
+{
+	delete m_program;
+	m_program = NULL;
+}
+
+MultiTextureSamplerTest::~MultiTextureSamplerTest (void)
+{
+	deinit();
+}
+
+void MultiTextureSamplerTest::render (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	GLuint	samplerLoc1	= (GLuint)-1;
+	GLuint	samplerLoc2	= (GLuint)-1;
+	GLuint	scaleLoc	= (GLuint)-1;
+
+	gl.useProgram(m_program->getProgram());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram(m_program->getProgram())");
+
+	samplerLoc1 = glGetUniformLocation(m_program->getProgram(), "u_sampler1");
+	TCU_CHECK(samplerLoc1 != (GLuint)-1);
+
+	samplerLoc2 = glGetUniformLocation(m_program->getProgram(), "u_sampler2");
+	TCU_CHECK(samplerLoc2 != (GLuint)-1);
+
+	scaleLoc = glGetUniformLocation(m_program->getProgram(), "u_posScale");
+	TCU_CHECK(scaleLoc != (GLuint)-1);
+
+	gl.clearColor(0.5f, 0.5f, 0.5f, 1.0f);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glClearColor(0.5f, 0.5f, 0.5f, 1.0f)");
+
+	gl.clear(GL_COLOR_BUFFER_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glClear(GL_COLOR_BUFFER_BIT)");
+
+	gl.uniform1i(samplerLoc1, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i(samplerLoc1, 0)");
+
+	gl.uniform1i(samplerLoc2, 1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i(samplerLoc2, 1)");
+
+	gl.uniform1f(scaleLoc, 1.0f);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1f(scaleLoc, 1.0f)");
+
+	switch (m_target)
+	{
+		case GL_TEXTURE_2D:
+		{
+			glu::VertexArrayBinding vertexArrays[] =
+			{
+				glu::VertexArrayBinding(glu::BindingPoint("a_position"), glu::VertexArrayPointer(glu::VTX_COMP_FLOAT, glu::VTX_COMP_CONVERT_NONE, 2, 6, 0, s_positions))
+			};
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			gl.uniform1f(scaleLoc, 0.25f);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1f(scaleLoc, 0.25f)");
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			break;
+		}
+
+		case GL_TEXTURE_3D:
+		{
+			glu::VertexArrayBinding vertexArrays[] =
+			{
+				glu::VertexArrayBinding(glu::BindingPoint("a_position"), glu::VertexArrayPointer(glu::VTX_COMP_FLOAT, glu::VTX_COMP_CONVERT_NONE, 3, 6, 0, s_positions3D))
+			};
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			gl.uniform1f(scaleLoc, 0.25f);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1f(scaleLoc, 0.25f)");
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			break;
+		}
+
+		case GL_TEXTURE_CUBE_MAP:
+		{
+			glu::VertexArrayBinding vertexArrays[] =
+			{
+				glu::VertexArrayBinding(glu::BindingPoint("a_position"), glu::VertexArrayPointer(glu::VTX_COMP_FLOAT, glu::VTX_COMP_CONVERT_NONE, 4, 6, 0, s_positionsCube))
+			};
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			gl.uniform1f(scaleLoc, 0.25f);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1f(scaleLoc, 0.25f)");
+
+			glu::draw(m_renderCtx, m_program->getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), vertexArrays, glu::PrimitiveList(glu::PRIMITIVETYPE_TRIANGLES, 6));
+
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+GLuint MultiTextureSamplerTest::createTexture2D (const glw::Functions& gl, int id)
+{
+	GLuint			texture		= (GLuint)-1;
+	tcu::Texture2D	refTexture	(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), TEXTURE2D_WIDTH, TEXTURE2D_HEIGHT);
+
+	refTexture.allocLevel(0);
+
+	gl.genTextures(1, &texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures(1, &texture)");
+
+	switch (id)
+	{
+		case 0:
+			tcu::fillWithComponentGradients(refTexture.getLevel(0), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 0.5f, 0.5f));
+			break;
+
+		case 1:
+			tcu::fillWithComponentGradients(refTexture.getLevel(0), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(0.5f, 0.5f, 1.0f, 1.0f));
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	gl.bindTexture(GL_TEXTURE_2D, texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_2D, texture)");
+
+	gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, refTexture.getWidth(), refTexture.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, refTexture.getLevel(0).getDataPtr());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, refTexture.getWidth(), refTexture.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, refTexture.getLevel(0).getDataPtr())");
+
+	gl.generateMipmap(GL_TEXTURE_2D);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenerateMipmap(GL_TEXTURE_2D)");
+
+	gl.bindTexture(GL_TEXTURE_2D, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_2D, 0)");
+
+	return texture;
+}
+
+GLuint MultiTextureSamplerTest::createTexture3D (const glw::Functions& gl, int id)
+{
+	GLuint			texture		= (GLuint)-1;
+	tcu::Texture3D	refTexture	(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), TEXTURE3D_WIDTH, TEXTURE3D_HEIGHT, TEXTURE3D_DEPTH);
+
+	refTexture.allocLevel(0);
+
+	gl.genTextures(1, &texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures(1, &texture)");
+
+	switch (id)
+	{
+		case 0:
+			tcu::fillWithComponentGradients(refTexture.getLevel(0), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 0.5f, 0.5f));
+			break;
+
+		case 1:
+			tcu::fillWithComponentGradients(refTexture.getLevel(0), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(0.5f, 0.5f, 1.0f, 1.0f));
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	gl.bindTexture(GL_TEXTURE_3D, texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_3D, texture)");
+
+	gl.texImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, refTexture.getWidth(), refTexture.getHeight(), refTexture.getDepth(), 0, GL_RGBA, GL_UNSIGNED_BYTE, refTexture.getLevel(0).getDataPtr());
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, refTexture.getWidth(), refTexture.getHeight(), refTexture.getDepth(), 0, GL_RGBA, GL_UNSIGNED_BYTE, refTexture.getLevel(0).getDataPtr())");
+
+	gl.generateMipmap(GL_TEXTURE_3D);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenerateMipmap(GL_TEXTURE_3D)");
+
+	gl.bindTexture(GL_TEXTURE_3D, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_3D, 0)");
+
+	return texture;
+}
+
+GLuint MultiTextureSamplerTest::createTextureCube (const glw::Functions& gl, int id)
+{
+	GLuint				texture		= (GLuint)-1;
+	tcu::TextureCube	refTexture	(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), CUBEMAP_SIZE);
+
+	gl.genTextures(1, &texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures(1, &texture)");
+
+	refTexture.allocLevel(tcu::CUBEFACE_POSITIVE_X, 0);
+	refTexture.allocLevel(tcu::CUBEFACE_POSITIVE_Y, 0);
+	refTexture.allocLevel(tcu::CUBEFACE_POSITIVE_Z, 0);
+	refTexture.allocLevel(tcu::CUBEFACE_NEGATIVE_X, 0);
+	refTexture.allocLevel(tcu::CUBEFACE_NEGATIVE_Y, 0);
+	refTexture.allocLevel(tcu::CUBEFACE_NEGATIVE_Z, 0);
+
+	switch (id)
+	{
+		case 0:
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_POSITIVE_X), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f));
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_POSITIVE_Y), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f));
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_POSITIVE_Z), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f));
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_NEGATIVE_X), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f));
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_NEGATIVE_Y), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f));
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_NEGATIVE_Z), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f));
+			break;
+
+		case 1:
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_POSITIVE_X), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_POSITIVE_Y), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_POSITIVE_Z), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_NEGATIVE_X), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_NEGATIVE_Y), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+			tcu::fillWithComponentGradients(refTexture.getLevelFace(0, tcu::CUBEFACE_NEGATIVE_Z), tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+			break;
+
+		default:
+			DE_ASSERT(false);
+	}
+
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, texture);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_CUBE_MAP, texture)");
+
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+	{
+		const deUint32 target = glu::getGLCubeFace((tcu::CubeFace)face);
+		gl.texImage2D(target, 0, GL_RGBA8, refTexture.getSize(), refTexture.getSize(), 0, GL_RGBA, GL_UNSIGNED_BYTE, refTexture.getLevelFace(0, (tcu::CubeFace)face).getDataPtr());
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage2D(GL_TEXTURE_CUBE_MAP_...) failed");
+
+	gl.generateMipmap(GL_TEXTURE_CUBE_MAP);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenerateMipmap(GL_TEXTURE_CUBE_MAP)");
+	gl.bindTexture(GL_TEXTURE_CUBE_MAP, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(GL_TEXTURE_CUBE_MAP, 0)");
+
+	return texture;
+}
+
+GLuint MultiTextureSamplerTest::createTexture (const glw::Functions& gl, GLenum target, int id)
+{
+	switch (target)
+	{
+		case GL_TEXTURE_2D:
+			return createTexture2D(gl, id);
+
+		case GL_TEXTURE_3D:
+			return createTexture3D(gl, id);
+
+		case GL_TEXTURE_CUBE_MAP:
+			return createTextureCube(gl, id);
+
+		default:
+			DE_ASSERT(false);
+			return (GLuint)-1;
+	}
+}
+
+void MultiTextureSamplerTest::renderReferences (tcu::Surface& textureRef, tcu::Surface& samplerRef, int x, int y)
+{
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	GLuint					texture1	= createTexture(gl, m_target, 0);
+	GLuint					texture2	= createTexture(gl, m_target, 1);
+
+	gl.viewport(x, y, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport(x, y, VIEWPORT_WIDTH, VIEWPORT_HEIGHT)");
+
+	// Generate texture rendering reference
+	gl.activeTexture(GL_TEXTURE0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glActiveTexture(GL_TEXTURE0)");
+	gl.bindTexture(m_target, texture1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, texture1)");
+	setTextureState(gl, m_target, m_textureState1);
+
+	gl.activeTexture(GL_TEXTURE1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glActiveTexture(GL_TEXTURE1)");
+	gl.bindTexture(m_target, texture2);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, texture2)");
+	setTextureState(gl, m_target, m_textureState2);
+
+	render();
+	glu::readPixels(m_renderCtx, x, y, textureRef.getAccess());
+
+	// Generate sampler rendering reference
+	gl.activeTexture(GL_TEXTURE0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glActiveTexture(GL_TEXTURE0)");
+	gl.bindTexture(m_target, texture1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, texture1)");
+	setTextureState(gl, m_target, m_samplerState);
+
+	gl.activeTexture(GL_TEXTURE1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glActiveTexture(GL_TEXTURE1)");
+	gl.bindTexture(m_target, texture2);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, texture2)");
+	setTextureState(gl, m_target, m_samplerState);
+
+	render();
+	glu::readPixels(m_renderCtx, x, y, samplerRef.getAccess());
+}
+
+void MultiTextureSamplerTest::renderResults (tcu::Surface& textureResult, tcu::Surface& samplerResult, int x, int y)
+{
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	GLuint					texture1	= createTexture(gl, m_target, 0);
+	GLuint					texture2	= createTexture(gl, m_target, 1);
+	GLuint					sampler		= -1;
+
+	gl.viewport(x, y, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glViewport(x, y, VIEWPORT_WIDTH, VIEWPORT_HEIGHT)");
+
+	gl.genSamplers(1, &sampler);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenSamplers(1, &sampler)");
+	TCU_CHECK(sampler != (GLuint)-1);
+
+	gl.bindSampler(0, sampler);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindSampler(0, sampler)");
+	gl.bindSampler(1, sampler);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindSampler(1, sampler)");
+
+	// First set sampler state
+	setSamplerState(gl, m_samplerState, sampler);
+
+	// Set texture state
+	gl.bindTexture(m_target, texture1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, texture1)");
+	setTextureState(gl, m_target, m_textureState1);
+
+	gl.bindTexture(m_target, texture2);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, texture2)");
+	setTextureState(gl, m_target, m_textureState2);
+
+	gl.activeTexture(GL_TEXTURE0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glActiveTexture(GL_TEXTURE0)");
+	gl.bindTexture(m_target, texture1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, texture1)");
+
+	gl.activeTexture(GL_TEXTURE1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glActiveTexture(GL_TEXTURE1)");
+	gl.bindTexture(m_target, texture2);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, texture2)");
+
+	// Render using sampler
+	render();
+	glu::readPixels(m_renderCtx, x, y, samplerResult.getAccess());
+
+	gl.bindSampler(0, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindSampler(0, 0)");
+	gl.bindSampler(1, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindSampler(1, 0)");
+
+	render();
+	glu::readPixels(m_renderCtx, x, y, textureResult.getAccess());
+
+	gl.activeTexture(GL_TEXTURE0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glActiveTexture(GL_TEXTURE0)");
+	gl.bindTexture(m_target, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, 0)");
+
+	gl.activeTexture(GL_TEXTURE1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glActiveTexture(GL_TEXTURE1)");
+	gl.bindTexture(m_target, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture(m_target, 0)");
+
+	gl.deleteSamplers(1, &sampler);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteSamplers(1, &sampler)");
+	gl.deleteTextures(1, &texture1);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteTextures(1, &texture1)");
+	gl.deleteTextures(1, &texture2);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteTextures(1, &texture2)");
+}
+
+tcu::TestCase::IterateResult MultiTextureSamplerTest::iterate (void)
+{
+	tcu::TestLog&	log = m_testCtx.getLog();
+
+	tcu::Surface	textureRef(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+	tcu::Surface	samplerRef(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+
+	tcu::Surface	textureResult(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+	tcu::Surface	samplerResult(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+
+	int				x = m_random.getInt(0, m_renderCtx.getRenderTarget().getWidth() - VIEWPORT_WIDTH);
+	int				y = m_random.getInt(0, m_renderCtx.getRenderTarget().getHeight() - VIEWPORT_HEIGHT);
+
+	renderReferences(textureRef, samplerRef, x, y);
+	renderResults(textureResult, samplerResult, x, y);
+
+	bool isOk = pixelThresholdCompare (log, "Sampler render result", "Result from rendering with sampler", samplerRef, samplerResult, tcu::RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);
+
+	if (!pixelThresholdCompare (log, "Texture render result", "Result from rendering with texture state", textureRef, textureResult, tcu::RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT))
+		isOk = false;
+
+	if (!isOk)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
+		return STOP;
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsSamplerObjectTest.hpp b/modules/glshared/glsSamplerObjectTest.hpp
new file mode 100644
index 0000000..5c3e8ba
--- /dev/null
+++ b/modules/glshared/glsSamplerObjectTest.hpp
@@ -0,0 +1,162 @@
+#ifndef _GLSSAMPLEROBJECTTEST_HPP
+#define _GLSSAMPLEROBJECTTEST_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 3.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Sampler object testcases.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuTestCase.hpp"
+#include "tcuTestLog.hpp"
+#include "deRandom.hpp"
+#include "tcuSurface.hpp"
+#include "gluRenderContext.hpp"
+#include "glw.h"
+#include "glwEnums.hpp"
+#include "gluShaderProgram.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+
+class TextureSamplerTest : public tcu::TestCase
+{
+public:
+	struct SamplingState
+	{
+		GLenum	minFilter;
+		GLenum	magFilter;
+		GLenum	wrapT;
+		GLenum	wrapS;
+		GLenum	wrapR;
+		GLfloat	minLod;
+		GLfloat	maxLod;
+	};
+
+	struct TestSpec
+	{
+		const char*		name;
+		const char*		desc;
+		GLenum			target;
+		SamplingState	textureState;
+		SamplingState	samplerState;
+	};
+
+						TextureSamplerTest	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const TestSpec& spec);
+						~TextureSamplerTest	(void);
+
+	void				init				(void);
+	void				deinit				(void);
+
+	IterateResult		iterate				(void);
+
+private:
+	void				renderReferences		(tcu::Surface& textureRef, tcu::Surface& samplerRef, int x, int y);
+	void				renderResults			(tcu::Surface& textureResult, tcu::Surface& samplerResult, int x, int y);
+
+	void				render					(void);
+
+	static void			setTextureState			(const glw::Functions& gl, GLenum target, SamplingState state);
+	static void			setSamplerState			(const glw::Functions& gl, SamplingState state, GLuint sampler);
+
+	static GLuint		createTexture2D			(const glw::Functions& gl);
+	static GLuint		createTexture3D			(const glw::Functions& gl);
+	static GLuint		createTextureCube		(const glw::Functions& gl);
+	static GLuint		createTexture			(const glw::Functions& gl, GLenum target);
+
+	static const char*	selectVertexShader		(GLenum target);
+	static const char*	selectFragmentShader	(GLenum target);
+
+	glu::RenderContext& m_renderCtx;
+	glu::ShaderProgram*	m_program;
+
+	GLenum				m_target;
+	SamplingState		m_textureState;
+	SamplingState		m_samplerState;
+
+	de::Random			m_random;
+};
+
+class MultiTextureSamplerTest : public tcu::TestCase
+{
+public:
+	struct SamplingState
+	{
+		GLenum	minFilter;
+		GLenum	magFilter;
+		GLenum	wrapT;
+		GLenum	wrapS;
+		GLenum	wrapR;
+		GLfloat	minLod;
+		GLfloat	maxLod;
+	};
+
+	struct TestSpec
+	{
+		const char*		name;
+		const char*		desc;
+		GLenum			target;
+		SamplingState	textureState1;
+		SamplingState	textureState2;
+		SamplingState	samplerState;
+	};
+
+						MultiTextureSamplerTest		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const TestSpec& spec);
+						~MultiTextureSamplerTest	(void);
+
+	void				init						(void);
+	void				deinit						(void);
+
+	IterateResult		iterate						(void);
+
+private:
+	void				renderReferences			(tcu::Surface& textureRef, tcu::Surface& samplerRef, int x, int y);
+	void				renderResults				(tcu::Surface& textureResult, tcu::Surface& samplerResult, int x, int y);
+
+	void				render						(void);
+
+	static void			setTextureState				(const glw::Functions& gl, GLenum target, SamplingState state);
+	static void			setSamplerState				(const glw::Functions& gl, SamplingState state, GLuint sampler);
+
+	static GLuint		createTexture2D				(const glw::Functions& gl, int id);
+	static GLuint		createTexture3D				(const glw::Functions& gl, int id);
+	static GLuint		createTextureCube			(const glw::Functions& gl, int id);
+	static GLuint		createTexture				(const glw::Functions& gl, GLenum target, int id);
+
+	static const char*	selectVertexShader			(GLenum target);
+	static const char*	selectFragmentShader		(GLenum target);
+
+	glu::RenderContext& m_renderCtx;
+	glu::ShaderProgram*	m_program;
+
+	GLenum				m_target;
+	SamplingState		m_textureState1;
+	SamplingState		m_textureState2;
+	SamplingState		m_samplerState;
+
+	de::Random			m_random;
+};
+
+
+} // gls
+} // deqp
+
+#endif // _GLSSAMPLEROBJECTTEST_HPP
diff --git a/modules/glshared/glsScissorTests.cpp b/modules/glshared/glsScissorTests.cpp
new file mode 100644
index 0000000..315301f
--- /dev/null
+++ b/modules/glshared/glsScissorTests.cpp
@@ -0,0 +1,568 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief GLES Scissor tests
+ *//*--------------------------------------------------------------------*/
+
+#include "glsScissorTests.hpp"
+#include "glsTextureTestUtil.hpp"
+
+#include "deMath.h"
+
+#include "tcuTestCase.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuVector.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTexture.hpp"
+
+#include "sglrReferenceContext.hpp"
+#include "sglrContextUtil.hpp"
+
+#include "gluStrUtil.hpp"
+#include "gluDrawUtil.hpp"
+
+#include "glwEnums.hpp"
+
+#include "deRandom.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+namespace Functional
+{
+
+using std::vector;
+
+ScissorTestShader::ScissorTestShader (void)
+	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
+							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
+							<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
+							<< sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
+														"void main (void)\n"
+														"{\n"
+														"	gl_Position = a_position;\n"
+														"}\n")
+							<< sglr::pdec::FragmentSource("uniform mediump vec4 u_color;\n"
+														  "void main (void)\n"
+														  "{\n"
+														  "	gl_FragColor = u_color;\n"
+														  "}\n"))
+	, u_color			(getUniformByName("u_color"))
+{
+}
+
+void ScissorTestShader::setColor (sglr::Context& ctx, deUint32 programID, const tcu::Vec4& color)
+{
+	ctx.useProgram(programID);
+	ctx.uniform4fv(ctx.getUniformLocation(programID, "u_color"), 1, color.getPtr());
+}
+
+void ScissorTestShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		packets[packetNdx]->position = rr::readVertexAttribFloat(inputs[0], packets[packetNdx]->instanceNdx, packets[packetNdx]->vertexNdx);
+}
+
+void ScissorTestShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const tcu::Vec4 color(u_color.value.f4);
+
+	DE_UNREF(packets);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+}
+
+namespace
+{
+
+void drawPrimitives (sglr::Context& ctx, deUint32 program, const deUint32 type, const float *vertPos, int numIndices, const deUint16 *indices)
+{
+	const deInt32	posLoc	= ctx.getAttribLocation(program, "a_position");
+
+	TCU_CHECK(posLoc >= 0);
+
+	ctx.useProgram(program);
+	ctx.enableVertexAttribArray(posLoc);
+	ctx.vertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, vertPos);
+
+	ctx.drawElements(type, numIndices, GL_UNSIGNED_SHORT, indices);
+	ctx.disableVertexAttribArray(posLoc);
+}
+
+// Mark pixel pairs with a large color difference starting with the given points and moving by advance count times
+vector<deUint8> findBorderPairs (const tcu::ConstPixelBufferAccess& image, const tcu::IVec2& start0, const tcu::IVec2& start1, const tcu::IVec2& advance, int count)
+{
+	using tcu::Vec4;
+	using tcu::IVec2;
+
+	const Vec4		threshold	(0.1f, 0.1f, 0.1f, 0.1f);
+	IVec2			p0			= start0;
+	IVec2			p1			= start1;
+	vector<deUint8>	res;
+
+	res.resize(count);
+
+	for (int ndx = 0; ndx < count; ndx++)
+	{
+		const Vec4	diff		= abs(image.getPixel(p0.x(), p0.y()) - image.getPixel(p1.x(), p1.y()));
+
+		res[ndx] = !boolAll(lessThanEqual(diff, threshold));
+		p0  += advance;
+		p1  += advance;
+	}
+	return res;
+}
+
+// make all elements within range of a 'true' element 'true' as well
+vector<deUint8> fuzz (const vector<deUint8>& ref, int range)
+{
+	vector<deUint8> res;
+
+	res.resize(ref.size());
+
+	for (int ndx = 0; ndx < int(ref.size()); ndx++)
+	{
+		if (ref[ndx])
+		{
+			const int begin = de::max(0, ndx-range);
+			const int end	= de::min(ndx+range, int(ref.size())-1);
+
+			for (int i = begin; i <= end; i++)
+				res[i] = 1;
+		}
+	}
+
+	return res;
+}
+
+bool bordersEquivalent (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& reference, const tcu::ConstPixelBufferAccess& result, const tcu::IVec2& start0, const tcu::IVec2& start1, const tcu::IVec2& advance, int count)
+{
+	const vector<deUint8>	refBorders		= fuzz(findBorderPairs(reference,	start0, start1, advance, count), 1);
+	const vector<deUint8>	resBorders		= findBorderPairs(result,			start0, start1, advance, count);
+
+	// Helps deal with primitives that are within 1px of the scissor edge and thus may (not) create an edge for findBorderPairs. This number is largely resolution-independent since the typical  triggers are points rather than edges.
+	const int				errorThreshold  = 2;
+	const int				floodThreshold	= 8;
+	int						messageCount	= 0;
+
+	for (int ndx = 0; ndx < int(resBorders.size()); ndx++)
+	{
+		if (resBorders[ndx] && !refBorders[ndx])
+		{
+			messageCount++;
+
+			if (messageCount <= floodThreshold)
+			{
+				const tcu::IVec2 coord = start0 + advance*ndx;
+				log << tcu::TestLog::Message << "No matching border near " << coord << tcu::TestLog::EndMessage;
+			}
+		}
+	}
+
+	if (messageCount > floodThreshold)
+		log << tcu::TestLog::Message << "Omitted " << messageCount - floodThreshold << " more errors" << tcu::TestLog::EndMessage;
+
+	return messageCount <= errorThreshold;
+}
+
+// Try to find a clear border between [area.xy, area.zw) and the rest of the image, check that the reference and result images have a roughly matching number of border pixel pairs
+bool compareBorders (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& reference, const tcu::ConstPixelBufferAccess& result, const tcu::IVec4& area)
+{
+	using tcu::IVec2;
+	using tcu::IVec4;
+
+	const IVec4			testableArea	(0, 0, reference.getWidth(), reference.getHeight());
+	const tcu::BVec4	testableEdges	(area.x()>testableArea.x() && area.x()<testableArea.z(),
+										 area.y()>testableArea.y() && area.y()<testableArea.w(),
+										 area.z()<testableArea.z() && area.z()>testableArea.x(),
+										 area.w()<testableArea.w() && area.w()>testableArea.y());
+	const IVec4			testArea		(std::max(area.x(), testableArea.x()), std::max(area.y(), testableArea.y()), std::min(area.z(), testableArea.z()), std::min(area.w(), testableArea.w()) );
+
+	if (testArea.x() > testArea.z() || testArea.y() > testArea.w()) // invalid area
+		return true;
+
+	if (testableEdges.x() &&
+		!bordersEquivalent(log,
+						   reference,
+						   result,
+						   IVec2(testArea.x(), testArea.y()),
+						   IVec2(testArea.x()-1, testArea.y()),
+						   IVec2(0, 1),
+						   testArea.w()-testArea.y()))
+		return false;
+
+	if (testableEdges.z() &&
+		!bordersEquivalent(log,
+						   reference,
+						   result,
+						   IVec2(testArea.z(), testArea.y()),
+						   IVec2(testArea.z()-1, testArea.y()),
+						   IVec2(0, 1),
+						   testArea.w()-testArea.y()))
+		return false;
+
+	if (testableEdges.y() &&
+		!bordersEquivalent(log,
+						   reference,
+						   result,
+						   IVec2(testArea.x(), testArea.y()),
+						   IVec2(testArea.x(), testArea.y()-1),
+						   IVec2(1, 0),
+						   testArea.z()-testArea.x()))
+		return false;
+
+	if (testableEdges.w() &&
+		!bordersEquivalent(log,
+						   reference,
+						   result,
+						   IVec2(testArea.x(), testArea.w()),
+						   IVec2(testArea.x(), testArea.w()-1),
+						   IVec2(1, 0),
+						   testArea.z()-testArea.x()))
+		return false;
+
+	return true;
+}
+
+} // anonymous
+
+ScissorCase::ScissorCase (glu::RenderContext& context, tcu::TestContext& testContext, const tcu::Vec4& scissorArea, const char* name, const char* description)
+	: TestCase			(testContext, name, description)
+	, m_renderContext	(context)
+	, m_scissorArea		(scissorArea)
+{
+}
+
+ScissorCase::IterateResult ScissorCase::iterate (void)
+{
+	using TextureTestUtil::RandomViewport;
+
+	const tcu::Vec4				clearColor				(0.0f, 0.0f, 0.0f, 1.0f);
+	glu::RenderContext&			renderCtx				= m_renderContext;
+	const tcu::RenderTarget&	renderTarget			= renderCtx.getRenderTarget();
+	tcu::TestLog&				log						= m_testCtx.getLog();
+
+	const RandomViewport		viewport				(renderTarget, 256, 256, deStringHash(getName()));
+
+	tcu::Surface				glesFrame				(viewport.width, viewport.height);
+	tcu::Surface				refFrame				(viewport.width, viewport.height);
+	deUint32					glesError;
+
+	// Render using GLES
+	{
+		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(0, 0, renderTarget.getWidth(), renderTarget.getHeight()));
+
+		context.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		render(context, tcu::IVec4(viewport.x, viewport.y, viewport.width, viewport.height)); // Call actual render func
+		context.readPixels(glesFrame, viewport.x, viewport.y, viewport.width, viewport.height);
+		glesError = context.getError();
+	}
+
+	// Render reference image
+	{
+		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0),
+												 renderTarget.getDepthBits(),
+												 renderTarget.getStencilBits(),
+												 renderTarget.getWidth(),
+												 renderTarget.getHeight());
+		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
+
+		context.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
+
+		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+		render(context, tcu::IVec4(viewport.x, viewport.y, viewport.width, viewport.height));
+		context.readPixels(refFrame, viewport.x, viewport.y, viewport.width, viewport.height);
+		DE_ASSERT(context.getError() == GL_NO_ERROR);
+	}
+
+	if (glesError != GL_NO_ERROR)
+	{
+		log << tcu::TestLog::Message << "Unexpected error: got " << glu::getErrorStr(glesError) << tcu::TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected error");
+	}
+	else
+	{
+		// Compare images
+		const float			threshold	= 0.02f;
+		const tcu::IVec4	scissorArea	(int(m_scissorArea.x()*viewport.width ),
+										 int(m_scissorArea.y()*viewport.height),
+										 int(m_scissorArea.x()*viewport.width ) + int(m_scissorArea.z()*viewport.width ),
+										 int(m_scissorArea.y()*viewport.height) + int(m_scissorArea.w()*viewport.height));
+		const bool			bordersOk	= compareBorders(log, refFrame.getAccess(), glesFrame.getAccess(), scissorArea);
+		const bool			imagesOk	= tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, glesFrame, threshold, tcu::COMPARE_LOG_RESULT);
+
+		if (!imagesOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+		else if (!bordersOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Scissor area border mismatch");
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	}
+
+	return STOP;
+}
+
+// Tests scissoring with multiple primitive types
+class ScissorPrimitiveCase : public ScissorCase
+{
+public:
+								ScissorPrimitiveCase	(glu::RenderContext&	context,
+														 tcu::TestContext&		testContext,
+														 const tcu::Vec4&		scissorArea,
+														 const tcu::Vec4&		renderArea,
+														 PrimitiveType			type,
+														 int					primitiveCount,
+														 const char*			name,
+														 const char*			description);
+	virtual						~ScissorPrimitiveCase	(void){}
+
+protected:
+	virtual void				render					(sglr::Context& context, const tcu::IVec4& viewport);
+
+private:
+	const tcu::Vec4				m_renderArea;
+	const PrimitiveType			m_primitiveType;
+	const int					m_primitiveCount;
+};
+
+ScissorPrimitiveCase::ScissorPrimitiveCase	(glu::RenderContext&	context,
+											 tcu::TestContext&		testContext,
+											 const tcu::Vec4&		scissorArea,
+											 const tcu::Vec4&		renderArea,
+											 PrimitiveType			type,
+											 int					primitiveCount,
+											 const char*			name,
+											 const char*			description)
+	: ScissorCase		(context, testContext, scissorArea, name, description)
+	, m_renderArea		(renderArea)
+	, m_primitiveType	(type)
+	, m_primitiveCount	(primitiveCount)
+{
+}
+
+void ScissorPrimitiveCase::render (sglr::Context& context, const tcu::IVec4& viewport)
+{
+	const tcu::Vec4				red				(0.6f, 0.1f, 0.1f, 1.0);
+
+	ScissorTestShader			shader;
+	const int					width			= viewport.w();
+	const int					height			= viewport.z();
+	const deUint32				shaderID		= context.createProgram(&shader);
+	const tcu::Vec4				primitiveArea	(m_renderArea.x()*2.0f-1.0f,
+												 m_renderArea.x()*2.0f-1.0f,
+												 m_renderArea.z()*2.0f,
+												 m_renderArea.w()*2.0f);
+	const tcu::IVec4			scissorArea		(int(m_scissorArea.x()*width) + viewport.x(),
+												 int(m_scissorArea.y()*height) + viewport.y(),
+												 int(m_scissorArea.z()*width),
+												 int(m_scissorArea.w()*height));
+
+	static const float quadPositions[] =
+	{
+		 0.0f,  1.0f,
+		 0.0f,  0.0f,
+		 1.0f,  1.0f,
+		 1.0f,  0.0f
+	};
+	static const float triPositions[] =
+	{
+		 0.0f,  0.0f,
+		 1.0f,  0.0f,
+		 0.5f,  1.0f,
+	};
+	static const float linePositions[] =
+	{
+		 0.0f,  0.0f,
+		 1.0f,  1.0f
+	};
+	static const float pointPosition[] =
+	{
+		 0.5f,  0.5f
+	};
+
+	const float*				positionSet[]	= { pointPosition, linePositions, triPositions, quadPositions };
+	const int					vertexCountSet[]= { 1, 2, 3, 4 };
+	const int					indexCountSet[]	= { 1, 2, 3, 6 };
+
+	const deUint16				baseIndices[]	= { 0, 1, 2, 2, 1, 3 };
+	const float*				basePositions	= positionSet[m_primitiveType];
+	const int					vertexCount		= vertexCountSet[m_primitiveType];
+	const int					indexCount		= indexCountSet[m_primitiveType];
+
+	const float					scale			= 1.44f/deFloatSqrt(float(m_primitiveCount)*2.0f);
+	std::vector<float>			positions		(4*vertexCount*m_primitiveCount);
+	std::vector<deUint16>		indices			(indexCount*m_primitiveCount);
+	de::Random					rng				(1234);
+
+	for (int primNdx = 0; primNdx < m_primitiveCount; primNdx++)
+	{
+		const float dx = m_primitiveCount>1 ? rng.getFloat() : 0.0f;
+		const float dy = m_primitiveCount>1 ? rng.getFloat() : 0.0f;
+
+		for (int vertNdx = 0; vertNdx<vertexCount; vertNdx++)
+		{
+			const int ndx = primNdx*4*vertexCount + vertNdx*4;
+			positions[ndx+0] = (basePositions[vertNdx*2 + 0]*scale + dx)*primitiveArea.z() + primitiveArea.x();
+			positions[ndx+1] = (basePositions[vertNdx*2 + 1]*scale + dy)*primitiveArea.w() + primitiveArea.y();
+			positions[ndx+2] = 0.2f;
+			positions[ndx+3] = 1.0f;
+		}
+
+		for (int ndx = 0; ndx < indexCount; ndx++)
+			indices[primNdx*indexCount + ndx] = baseIndices[ndx] + primNdx*vertexCount;
+	}
+
+	// Enable scissor test.
+	context.enable(GL_SCISSOR_TEST);
+	context.scissor(scissorArea.x(), scissorArea.y(), scissorArea.z(), scissorArea.w());
+
+	shader.setColor(context, shaderID, red);
+
+	switch (m_primitiveType)
+	{
+		case QUAD:			// Fall-through, no real quads...
+		case TRIANGLE:		drawPrimitives(context, shaderID, GL_TRIANGLES, &positions[0], indexCount*m_primitiveCount, &indices[0]);		break;
+		case LINE:			drawPrimitives(context, shaderID, GL_LINES,		&positions[0], indexCount*m_primitiveCount, &indices[0]);		break;
+		case POINT:			drawPrimitives(context, shaderID, GL_POINTS,	&positions[0], indexCount*m_primitiveCount, &indices[0]);		break;
+		default:			DE_ASSERT(false);																								break;
+	}
+
+	context.disable(GL_SCISSOR_TEST);
+}
+
+class ScissorClearCase : public ScissorCase
+{
+public:
+								ScissorClearCase		(glu::RenderContext&	context,
+														 tcu::TestContext&		testContext,
+														 const tcu::Vec4&		scissorArea,
+														 deUint32				clearMode,
+														 const char*			name,
+														 const char*			description);
+	virtual						~ScissorClearCase		(void) {}
+
+	virtual void				init					(void);
+
+protected:
+	virtual void				render					(sglr::Context& context, const tcu::IVec4& viewport);
+
+private:
+	const deUint32				m_clearMode; //!< Combination of the flags accepted by glClear
+};
+
+ScissorClearCase::ScissorClearCase	(glu::RenderContext&	context,
+									 tcu::TestContext&		testContext,
+									 const tcu::Vec4&		scissorArea,
+									 deUint32				clearMode,
+									 const char*			name,
+									 const char*			description)
+	: ScissorCase	(context, testContext, scissorArea, name, description)
+	, m_clearMode	(clearMode)
+{
+}
+
+void ScissorClearCase::init (void)
+{
+	if ((m_clearMode & GL_DEPTH_BUFFER_BIT) && m_renderContext.getRenderTarget().getDepthBits()==0)
+		throw tcu::NotSupportedError("Cannot clear depth; no depth buffer present", "", __FILE__, __LINE__);
+	else if ((m_clearMode & GL_STENCIL_BUFFER_BIT) && m_renderContext.getRenderTarget().getStencilBits()==0)
+		throw tcu::NotSupportedError("Cannot clear stencil; no stencil buffer present", "", __FILE__, __LINE__);
+}
+
+void ScissorClearCase::render (sglr::Context& context, const tcu::IVec4& viewport)
+{
+	ScissorTestShader			shader;
+	const deUint32				shaderID		= context.createProgram(&shader);
+	const int					width			= viewport.z();
+	const int					height			= viewport.w();
+	const tcu::Vec4				green			(0.1f, 0.6f, 0.1f, 1.0);
+	const tcu::IVec4			scissorArea		(int(m_scissorArea.x()*width) + viewport.x(),
+												 int(m_scissorArea.y()*height) + viewport.y(),
+												 int(m_scissorArea.z()*width),
+												 int(m_scissorArea.w()*height));
+
+	context.clearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	context.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+	context.clearColor(0.6f, 0.1f, 0.1f, 1.0);
+
+	context.enable(GL_SCISSOR_TEST);
+	context.scissor(scissorArea.x(), scissorArea.y(), scissorArea.z(), scissorArea.w());
+
+	context.clearDepthf(0.0f);
+
+	if (m_clearMode & GL_DEPTH_BUFFER_BIT)
+	{
+		context.enable(GL_DEPTH_TEST);
+		context.depthFunc(GL_GREATER);
+	}
+
+	if (m_clearMode & GL_STENCIL_BUFFER_BIT)
+	{
+		context.clearStencil(123);
+		context.enable(GL_STENCIL_TEST);
+		context.stencilFunc(GL_EQUAL, 123, ~0u);
+	}
+
+	if (m_clearMode & GL_COLOR_BUFFER_BIT)
+		context.clearColor(0.1f, 0.6f, 0.1f, 1.0);
+
+	context.clear(m_clearMode);
+	context.disable(GL_SCISSOR_TEST);
+
+	shader.setColor(context, shaderID, green);
+
+	if (!(m_clearMode & GL_COLOR_BUFFER_BIT))
+		sglr::drawQuad(context, shaderID, tcu::Vec3(-1.0f, -1.0f, 0.5f), tcu::Vec3(1.0f, 1.0f, 0.5f));
+
+	context.disable(GL_DEPTH_TEST);
+	context.disable(GL_STENCIL_TEST);
+}
+
+tcu::TestNode* ScissorCase::createPrimitiveTest (glu::RenderContext&	context,
+												 tcu::TestContext&		testContext,
+												 const tcu::Vec4&		scissorArea,
+												 const tcu::Vec4&		renderArea,
+												 PrimitiveType			type,
+												 int					primitiveCount,
+												 const char*			name,
+												 const char*			description)
+{
+	return new Functional::ScissorPrimitiveCase(context, testContext, scissorArea, renderArea, type, primitiveCount, name, description);
+}
+
+tcu::TestNode* ScissorCase::createClearTest (glu::RenderContext& context, tcu::TestContext& testContext, const tcu::Vec4& scissorArea, deUint32 clearMode, const char* name, const char* description)
+{
+	return new Functional::ScissorClearCase(context, testContext, scissorArea, clearMode, name, description);
+}
+
+} // Functional
+} // gls
+} // deqp
diff --git a/modules/glshared/glsScissorTests.hpp b/modules/glshared/glsScissorTests.hpp
new file mode 100644
index 0000000..e3b853c
--- /dev/null
+++ b/modules/glshared/glsScissorTests.hpp
@@ -0,0 +1,109 @@
+#ifndef _GLSSCISSORTESTS_HPP
+#define _GLSSCISSORTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Common parts for ES2/3 scissor tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuVectorType.hpp"
+#include "tcuVector.hpp"
+#include "sglrGLContext.hpp"
+
+namespace glu
+{
+class RenderContext;
+} // glu
+
+namespace sglr
+{
+class Context;
+} // sglr
+
+namespace deqp
+{
+namespace gls
+{
+namespace Functional
+{
+
+// Wrapper class, provides iterator & reporting logic
+class ScissorCase : public tcu::TestCase
+{
+public:
+	enum PrimitiveType
+	{
+		POINT = 0,
+		LINE,
+		TRIANGLE,
+		QUAD,
+
+		PRIMITIVETYPE_LAST
+	};
+
+								ScissorCase				(glu::RenderContext& context, tcu::TestContext& testContext, const tcu::Vec4& scissorArea, const char* name, const char* description);
+	virtual						~ScissorCase			(void) {}
+
+	virtual IterateResult		iterate					(void);
+
+	// Areas are of the form (x,y,widht,height) in the range [0,1]. Vertex counts 1-3 result in single point/line/tri, higher ones result in the indicated number of quads in pseudorandom locations.
+	static tcu::TestNode*		createPrimitiveTest		(glu::RenderContext&	context,
+														 tcu::TestContext&		testContext,
+														 const tcu::Vec4&		scissorArea,
+														 const tcu::Vec4&		renderArea,
+														 PrimitiveType			type,
+														 int					primitiveCount,
+														 const char*			name,
+														 const char*			description);
+	static tcu::TestNode*		createClearTest			(glu::RenderContext&	context,
+														 tcu::TestContext&		testContext,
+														 const tcu::Vec4&		scissorArea,
+														 deUint32				clearMode,
+														 const char*			name,
+														 const char*			description);
+
+protected:
+	virtual void				render					(sglr::Context& context, const tcu::IVec4& viewport) = 0;
+
+	glu::RenderContext&			m_renderContext;
+	const tcu::Vec4				m_scissorArea;
+};
+
+class ScissorTestShader : public sglr::ShaderProgram
+{
+public:
+								ScissorTestShader	(void);
+
+	void						setColor			(sglr::Context& ctx, deUint32 programID, const tcu::Vec4& color);
+
+private:
+	void						shadeVertices		(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void						shadeFragments		(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+	const sglr::UniformSlot&	u_color;
+};
+
+} // Functional
+} // gls
+} // deqp
+
+#endif // _GLSSCISSORTESTS_HPP
diff --git a/modules/glshared/glsShaderConstExprTests.cpp b/modules/glshared/glsShaderConstExprTests.cpp
new file mode 100644
index 0000000..f15fa74
--- /dev/null
+++ b/modules/glshared/glsShaderConstExprTests.cpp
@@ -0,0 +1,220 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL Shared Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shared shader constant expression test components
+ *//*--------------------------------------------------------------------*/
+
+#include "glsShaderConstExprTests.hpp"
+#include "glsShaderLibrary.hpp"
+#include "glsShaderLibraryCase.hpp"
+
+#include "tcuStringTemplate.hpp"
+
+#include "deStringUtil.hpp"
+#include "deMath.h"
+
+namespace deqp
+{
+namespace gls
+{
+namespace ShaderConstExpr
+{
+
+std::vector<tcu::TestNode*> createTests (tcu::TestContext&			testContext,
+										 glu::RenderContext&		renderContext,
+										 const glu::ContextInfo&	contextInfo,
+										 const TestParams*			cases,
+										 int						numCases,
+										 glu::GLSLVersion			version,
+										 TestShaderStage			testStage)
+{
+	using std::string;
+	using std::vector;
+	using gls::sl::ShaderCase;
+
+	// Needed for autogenerating shader code for increased component counts
+	DE_STATIC_ASSERT(glu::TYPE_FLOAT+1 == glu::TYPE_FLOAT_VEC2);
+	DE_STATIC_ASSERT(glu::TYPE_FLOAT+2 == glu::TYPE_FLOAT_VEC3);
+	DE_STATIC_ASSERT(glu::TYPE_FLOAT+3 == glu::TYPE_FLOAT_VEC4);
+
+	DE_STATIC_ASSERT(glu::TYPE_INT+1 == glu::TYPE_INT_VEC2);
+	DE_STATIC_ASSERT(glu::TYPE_INT+2 == glu::TYPE_INT_VEC3);
+	DE_STATIC_ASSERT(glu::TYPE_INT+3 == glu::TYPE_INT_VEC4);
+
+	DE_STATIC_ASSERT(glu::TYPE_UINT+1 == glu::TYPE_UINT_VEC2);
+	DE_STATIC_ASSERT(glu::TYPE_UINT+2 == glu::TYPE_UINT_VEC3);
+	DE_STATIC_ASSERT(glu::TYPE_UINT+3 == glu::TYPE_UINT_VEC4);
+
+	DE_STATIC_ASSERT(glu::TYPE_BOOL+1 == glu::TYPE_BOOL_VEC2);
+	DE_STATIC_ASSERT(glu::TYPE_BOOL+2 == glu::TYPE_BOOL_VEC3);
+	DE_STATIC_ASSERT(glu::TYPE_BOOL+3 == glu::TYPE_BOOL_VEC4);
+
+	DE_ASSERT(testStage);
+
+	const char* shaderTemplateSrc =
+		"#version ${GLES_VERSION}\n"
+		"precision highp float;\n"
+		"precision highp int;\n"
+		"${DECLARATIONS}\n"
+		"void main()\n"
+		"{\n"
+		"	const ${CASE_BASE_TYPE} cval = ${CASE_EXPRESSION};\n"
+		"	out0 = cval;\n"
+		"	${OUTPUT}\n"
+		"}\n";
+
+	const tcu::StringTemplate		shaderTemplate	(shaderTemplateSrc);
+	vector<tcu::TestNode*>			ret;
+	vector<ShaderCase::ValueBlock>	shaderOutput	(1);
+
+	shaderOutput[0].arrayLength = 1;
+	shaderOutput[0].values.push_back(ShaderCase::Value());
+	shaderOutput[0].values[0].storageType		= ShaderCase::Value::STORAGE_OUTPUT;
+	shaderOutput[0].values[0].valueName			= "out0";
+	shaderOutput[0].values[0].dataType			= glu::TYPE_FLOAT;
+	shaderOutput[0].values[0].arrayLength		= 1;
+	shaderOutput[0].values[0].elements.push_back(ShaderCase::Value::Element());
+
+	for (int caseNdx = 0; caseNdx < numCases; caseNdx++)
+	{
+		std::map<string, string>	shaderTemplateParams;
+		const int					minComponents	= cases[caseNdx].minComponents;
+		const int					maxComponents	= cases[caseNdx].maxComponents;
+		const DataType				inType			= cases[caseNdx].inType;
+		const DataType				outType			= cases[caseNdx].outType;
+		const string				expression		= cases[caseNdx].expression;
+		// Check for presence of func(vec, scalar) style specialization, use as gatekeeper for applying said specialization
+		const bool					alwaysScalar	= expression.find("${MT}")!=string::npos;
+		ShaderCase::Value&			expected		= shaderOutput[0].values[0];
+
+		switch (outType)
+		{
+			case glu::TYPE_INT:
+				expected.elements[0].int32 = (int)cases[caseNdx].output;
+				break;
+
+			case glu::TYPE_UINT:
+				expected.elements[0].int32 = (unsigned int)cases[caseNdx].output;
+				break;
+
+			case glu::TYPE_BOOL:
+				expected.elements[0].bool32 = cases[caseNdx].output!=0.0f;
+				break;
+
+			case glu::TYPE_FLOAT:
+				expected.elements[0].float32 = cases[caseNdx].output;
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+
+		expected.dataType = outType;
+
+		shaderTemplateParams["GLES_VERSION"]	= version == glu::GLSL_VERSION_300_ES ? "300 es" : "100";
+		shaderTemplateParams["CASE_BASE_TYPE"]	= glu::getDataTypeName(outType);
+		shaderTemplateParams["DECLARATIONS"]	= "${DECLARATIONS}";
+		shaderTemplateParams["OUTPUT"]			= "${OUTPUT}";
+
+		for (int compCount = minComponents-1; compCount < maxComponents; compCount++)
+		{
+			vector<tcu::TestNode*>		children;
+			std::map<string, string>	expressionTemplateParams;
+			string						typeName			= glu::getDataTypeName((glu::DataType)(inType + compCount)); // results in float, vec2, vec3, vec4 progression (same for other primitive types)
+			const char*					componentAccess[]	= {"", ".y", ".z", ".w"};
+			const tcu::StringTemplate	expressionTemplate	(expression);
+			// Add type to case name if we are generating multiple versions
+			const string				caseName			= string(cases[caseNdx].name) + (minComponents==maxComponents ? "" : ("_" + typeName));
+
+			// ${T} => final type, ${MT} => final type but with scalar version usable even when T is a vector
+			expressionTemplateParams["T"]			= typeName;
+			expressionTemplateParams["MT"]			= typeName;
+
+			shaderTemplateParams["CASE_EXPRESSION"]	= expressionTemplate.specialize(expressionTemplateParams) + componentAccess[compCount]; // Add vector access to expression as needed
+
+			{
+				const string mapped = shaderTemplate.specialize(shaderTemplateParams);
+
+				if (testStage & SHADER_VERTEX)
+					ret.push_back(new ShaderCase(testContext,
+												 renderContext,
+												 contextInfo,
+												 (caseName + "_vertex").c_str(),
+												 "",
+												 ShaderCase::ShaderCaseSpecification::generateSharedSourceVertexCase(ShaderCase::EXPECT_PASS,
+																													 version,
+																													 shaderOutput,
+																													 mapped)));
+
+				if (testStage & SHADER_FRAGMENT)
+					ret.push_back(new ShaderCase(testContext,
+												 renderContext,
+												 contextInfo,
+												 (caseName + "_fragment").c_str(),
+												 "",
+												 ShaderCase::ShaderCaseSpecification::generateSharedSourceFragmentCase(ShaderCase::EXPECT_PASS,
+																													   version,
+																													   shaderOutput,
+																													   mapped)));
+			}
+
+			// Deal with functions that allways accept one ore more scalar parameters even when others are vectors
+			if (alwaysScalar && compCount > 0)
+			{
+				const string	scalarCaseName	= string(cases[caseNdx].name) + "_" + typeName + "_" + glu::getDataTypeName(inType);
+
+				expressionTemplateParams["MT"] = glu::getDataTypeName(inType);
+				shaderTemplateParams["CASE_EXPRESSION"]	= expressionTemplate.specialize(expressionTemplateParams) + componentAccess[compCount];
+
+				{
+					const string mapped = shaderTemplate.specialize(shaderTemplateParams);
+
+					if (testStage & SHADER_VERTEX)
+						ret.push_back(new ShaderCase(testContext,
+													 renderContext,
+													 contextInfo,
+													 (scalarCaseName + "_vertex").c_str(),
+													 "",
+													 ShaderCase::ShaderCaseSpecification::generateSharedSourceVertexCase(ShaderCase::EXPECT_PASS,
+																														 version,
+																														 shaderOutput,
+																														 mapped)));
+
+					if (testStage & SHADER_FRAGMENT)
+						ret.push_back(new ShaderCase(testContext,
+													 renderContext,
+													 contextInfo,
+													 (scalarCaseName + "_fragment").c_str(),
+													 "",
+													 ShaderCase::ShaderCaseSpecification::generateSharedSourceFragmentCase(ShaderCase::EXPECT_PASS,
+																														   version,
+																														   shaderOutput,
+																														   mapped)));
+				}
+			}
+		}
+	}
+
+	return ret;
+}
+
+} // ShaderConstExpr
+} // gls
+} // deqp
diff --git a/modules/glshared/glsShaderConstExprTests.hpp b/modules/glshared/glsShaderConstExprTests.hpp
new file mode 100644
index 0000000..f27874d
--- /dev/null
+++ b/modules/glshared/glsShaderConstExprTests.hpp
@@ -0,0 +1,82 @@
+#ifndef _GLSSHADERCONSTEXPRTESTS_HPP
+#define _GLSSHADERCONSTEXPRTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL Shared Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shared shader constant expression test components
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "gluShaderUtil.hpp"
+
+#include <vector>
+
+namespace tcu
+{
+class TestNode;
+class TestContext;
+}
+
+namespace glu
+{
+class RenderContext;
+}
+
+namespace deqp
+{
+namespace gls
+{
+namespace ShaderConstExpr
+{
+using glu::DataType;
+
+struct TestParams
+{
+	const char*		name;
+	const char*		expression;
+
+	DataType		inType;
+	int				minComponents;
+	int				maxComponents;
+
+	DataType		outType;
+	float			output;
+};
+
+enum TestShaderStage
+{
+	SHADER_VERTEX = 1<<0,
+	SHADER_FRAGMENT = 1<<2,
+	SHADER_BOTH = SHADER_VERTEX | SHADER_FRAGMENT,
+};
+
+std::vector<tcu::TestNode*>		createTests		(tcu::TestContext&			testContext,
+												 glu::RenderContext&		renderContext,
+												 const glu::ContextInfo&	contextInfo,
+												 const TestParams*			cases,
+												 int						numCases,
+												 glu::GLSLVersion			version,
+												 TestShaderStage			testStage = SHADER_BOTH);
+
+} // ShaderConstExpr
+} // gls
+} // deqp
+
+#endif // _GLSSHADERCONSTEXPRTESTS_HPP
diff --git a/modules/glshared/glsShaderExecUtil.cpp b/modules/glshared/glsShaderExecUtil.cpp
new file mode 100644
index 0000000..fd6ce61
--- /dev/null
+++ b/modules/glshared/glsShaderExecUtil.cpp
@@ -0,0 +1,1503 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader execution utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsShaderExecUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluObjectWrapper.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluProgramInterfaceQuery.hpp"
+#include "gluPixelTransfer.hpp"
+#include "tcuTestLog.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "deSTLUtil.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+#include "deMemory.h"
+
+#include <map>
+
+namespace deqp
+{
+namespace gls
+{
+
+namespace ShaderExecUtil
+{
+
+using std::vector;
+
+static bool isExtensionSupported (const glu::RenderContext& renderCtx, const std::string& extension)
+{
+	const glw::Functions&	gl		= renderCtx.getFunctions();
+	int						numExts	= 0;
+
+	gl.getIntegerv(GL_NUM_EXTENSIONS, &numExts);
+
+	for (int ndx = 0; ndx < numExts; ndx++)
+	{
+		const char* curExt = (const char*)gl.getStringi(GL_EXTENSIONS, ndx);
+
+		if (extension == curExt)
+			return true;
+	}
+
+	return false;
+}
+
+static void checkExtension (const glu::RenderContext& renderCtx, const std::string& extension)
+{
+	if (!isExtensionSupported(renderCtx, extension))
+		throw tcu::NotSupportedError(extension + " is not supported");
+}
+
+// Shader utilities
+
+static std::string generateVertexShader (const ShaderSpec& shaderSpec)
+{
+	const bool			usesInout	= glu::glslVersionUsesInOutQualifiers(shaderSpec.version);
+	const char*			in			= usesInout ? "in"		: "attribute";
+	const char*			out			= usesInout ? "out"		: "varying";
+	std::ostringstream	src;
+
+	src << glu::getGLSLVersionDeclaration(shaderSpec.version) << "\n";
+
+	if (!shaderSpec.globalDeclarations.empty())
+		src << shaderSpec.globalDeclarations << "\n";
+
+	for (vector<Symbol>::const_iterator input = shaderSpec.inputs.begin(); input != shaderSpec.inputs.end(); ++input)
+		src << in << " " << glu::declare(input->varType, input->name) << ";\n";
+
+	for (vector<Symbol>::const_iterator output = shaderSpec.outputs.begin(); output != shaderSpec.outputs.end(); ++output)
+	{
+		DE_ASSERT(output->varType.isBasicType());
+
+		if (glu::isDataTypeBoolOrBVec(output->varType.getBasicType()))
+		{
+			const int				vecSize		= glu::getDataTypeScalarSize(output->varType.getBasicType());
+			const glu::DataType		intBaseType	= vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
+			const glu::VarType		intType		(intBaseType, glu::PRECISION_HIGHP);
+
+			src << "flat " << out << " " << glu::declare(intType, "o_" + output->name) << ";\n";
+		}
+		else
+			src << "flat " << out << " " << glu::declare(output->varType, output->name) << ";\n";
+	}
+
+	src << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	gl_Position = vec4(0.0);\n"
+		<< "	gl_PointSize = 1.0;\n\n";
+
+	// Declare necessary output variables (bools).
+	for (vector<Symbol>::const_iterator output = shaderSpec.outputs.begin(); output != shaderSpec.outputs.end(); ++output)
+	{
+		if (glu::isDataTypeBoolOrBVec(output->varType.getBasicType()))
+			src << "\t" << glu::declare(output->varType, output->name) << ";\n";
+	}
+
+	// Operation - indented to correct level.
+	{
+		std::istringstream	opSrc	(shaderSpec.source);
+		std::string			line;
+
+		while (std::getline(opSrc, line))
+			src << "\t" << line << "\n";
+	}
+
+	// Assignments to outputs.
+	for (vector<Symbol>::const_iterator output = shaderSpec.outputs.begin(); output != shaderSpec.outputs.end(); ++output)
+	{
+		if (glu::isDataTypeBoolOrBVec(output->varType.getBasicType()))
+		{
+			const int				vecSize		= glu::getDataTypeScalarSize(output->varType.getBasicType());
+			const glu::DataType		intBaseType	= vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
+
+			src << "\to_" << output->name << " = " << glu::getDataTypeName(intBaseType) << "(" << output->name << ");\n";
+		}
+	}
+
+	src << "}\n";
+
+	return src.str();
+}
+
+static std::string generateGeometryShader (const ShaderSpec& shaderSpec)
+{
+	DE_ASSERT(glu::glslVersionUsesInOutQualifiers(shaderSpec.version));
+
+	std::ostringstream	src;
+
+	src << glu::getGLSLVersionDeclaration(shaderSpec.version) << "\n";
+
+	if (glu::glslVersionIsES(shaderSpec.version) && shaderSpec.version <= glu::GLSL_VERSION_310_ES)
+		src << "#extension GL_EXT_geometry_shader : require\n";
+
+	if (!shaderSpec.globalDeclarations.empty())
+		src << shaderSpec.globalDeclarations << "\n";
+
+	src << "layout(points) in;\n"
+		<< "layout(points, max_vertices = 1) out;\n";
+
+	for (vector<Symbol>::const_iterator input = shaderSpec.inputs.begin(); input != shaderSpec.inputs.end(); ++input)
+		src << "flat in " << glu::declare(input->varType, "geom_" + input->name) << "[];\n";
+
+	for (vector<Symbol>::const_iterator output = shaderSpec.outputs.begin(); output != shaderSpec.outputs.end(); ++output)
+	{
+		DE_ASSERT(output->varType.isBasicType());
+
+		if (glu::isDataTypeBoolOrBVec(output->varType.getBasicType()))
+		{
+			const int				vecSize		= glu::getDataTypeScalarSize(output->varType.getBasicType());
+			const glu::DataType		intBaseType	= vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
+			const glu::VarType		intType		(intBaseType, glu::PRECISION_HIGHP);
+
+			src << "flat out " << glu::declare(intType, "o_" + output->name) << ";\n";
+		}
+		else
+			src << "flat out " << glu::declare(output->varType, output->name) << ";\n";
+	}
+
+	src << "\n"
+		<< "void main (void)\n"
+		<< "{\n"
+		<< "	gl_Position = gl_in[0].gl_Position;\n\n";
+
+	// Fetch input variables
+	for (vector<Symbol>::const_iterator input = shaderSpec.inputs.begin(); input != shaderSpec.inputs.end(); ++input)
+		src << "\t" << glu::declare(input->varType, input->name) << " = geom_" << input->name << "[0];\n";
+
+	// Declare necessary output variables (bools).
+	for (vector<Symbol>::const_iterator output = shaderSpec.outputs.begin(); output != shaderSpec.outputs.end(); ++output)
+	{
+		if (glu::isDataTypeBoolOrBVec(output->varType.getBasicType()))
+			src << "\t" << glu::declare(output->varType, output->name) << ";\n";
+	}
+
+	src << "\n";
+
+	// Operation - indented to correct level.
+	{
+		std::istringstream	opSrc	(shaderSpec.source);
+		std::string			line;
+
+		while (std::getline(opSrc, line))
+			src << "\t" << line << "\n";
+	}
+
+	// Assignments to outputs.
+	for (vector<Symbol>::const_iterator output = shaderSpec.outputs.begin(); output != shaderSpec.outputs.end(); ++output)
+	{
+		if (glu::isDataTypeBoolOrBVec(output->varType.getBasicType()))
+		{
+			const int				vecSize		= glu::getDataTypeScalarSize(output->varType.getBasicType());
+			const glu::DataType		intBaseType	= vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
+
+			src << "\to_" << output->name << " = " << glu::getDataTypeName(intBaseType) << "(" << output->name << ");\n";
+		}
+	}
+
+	src << "	EmitVertex();\n"
+		<< "	EndPrimitive();\n"
+		<< "}\n";
+
+	return src.str();
+}
+
+static std::string generateEmptyFragmentSource (glu::GLSLVersion version)
+{
+	const bool			customOut		= glu::glslVersionUsesInOutQualifiers(version);
+	std::ostringstream	src;
+
+	src << glu::getGLSLVersionDeclaration(version) << "\n";
+
+	// \todo [2013-08-05 pyry] Do we need one dummy output?
+
+	src << "void main (void)\n{\n";
+	if (!customOut)
+		src << "	gl_FragColor = vec4(0.0);\n";
+	src << "}\n";
+
+	return src.str();
+}
+
+static std::string generatePassthroughVertexShader (const ShaderSpec& shaderSpec, const char* inputPrefix, const char* outputPrefix)
+{
+	// flat qualifier is not present in earlier versions?
+	DE_ASSERT(glu::glslVersionUsesInOutQualifiers(shaderSpec.version));
+
+	std::ostringstream src;
+
+	src << glu::getGLSLVersionDeclaration(shaderSpec.version) << "\n"
+		<< "in highp vec4 a_position;\n";
+
+	for (vector<Symbol>::const_iterator input = shaderSpec.inputs.begin(); input != shaderSpec.inputs.end(); ++input)
+	{
+		src << "in " << glu::declare(input->varType, inputPrefix + input->name) << ";\n"
+			<< "flat out " << glu::declare(input->varType, outputPrefix + input->name) << ";\n";
+	}
+
+	src << "\nvoid main (void)\n{\n"
+		<< "	gl_Position = a_position;\n"
+		<< "	gl_PointSize = 1.0;\n";
+
+	for (vector<Symbol>::const_iterator input = shaderSpec.inputs.begin(); input != shaderSpec.inputs.end(); ++input)
+		src << "\t" << outputPrefix << input->name << " = " << inputPrefix << input->name << ";\n";
+
+	src << "}\n";
+
+	return src.str();
+}
+
+static std::string generateFragmentShader (const ShaderSpec& shaderSpec, bool useIntOutputs, const std::map<std::string, int>& outLocationMap)
+{
+	DE_ASSERT(glu::glslVersionUsesInOutQualifiers(shaderSpec.version));
+
+	std::ostringstream	src;
+	src << glu::getGLSLVersionDeclaration(shaderSpec.version) << "\n";
+
+	if (!shaderSpec.globalDeclarations.empty())
+		src << shaderSpec.globalDeclarations << "\n";
+
+	for (vector<Symbol>::const_iterator input = shaderSpec.inputs.begin(); input != shaderSpec.inputs.end(); ++input)
+		src << "flat in " << glu::declare(input->varType, input->name) << ";\n";
+
+	for (int outNdx = 0; outNdx < (int)shaderSpec.outputs.size(); ++outNdx)
+	{
+		const Symbol&				output		= shaderSpec.outputs[outNdx];
+		const int					location	= de::lookup(outLocationMap, output.name);
+		const std::string			outVarName	= "o_" + output.name;
+		glu::VariableDeclaration	decl		(output.varType, outVarName, glu::STORAGE_OUT, glu::INTERPOLATION_LAST, glu::Layout(location));
+
+		TCU_CHECK_INTERNAL(output.varType.isBasicType());
+
+		if (useIntOutputs && glu::isDataTypeFloatOrVec(output.varType.getBasicType()))
+		{
+			const int			vecSize			= glu::getDataTypeScalarSize(output.varType.getBasicType());
+			const glu::DataType	uintBasicType	= vecSize > 1 ? glu::getDataTypeUintVec(vecSize) : glu::TYPE_UINT;
+			const glu::VarType	uintType		(uintBasicType, glu::PRECISION_HIGHP);
+
+			decl.varType = uintType;
+			src << decl << ";\n";
+		}
+		else if (glu::isDataTypeBoolOrBVec(output.varType.getBasicType()))
+		{
+			const int			vecSize			= glu::getDataTypeScalarSize(output.varType.getBasicType());
+			const glu::DataType	intBasicType	= vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
+			const glu::VarType	intType			(intBasicType, glu::PRECISION_HIGHP);
+
+			decl.varType = intType;
+			src << decl << ";\n";
+		}
+		else if (glu::isDataTypeMatrix(output.varType.getBasicType()))
+		{
+			const int			vecSize			= glu::getDataTypeMatrixNumRows(output.varType.getBasicType());
+			const int			numVecs			= glu::getDataTypeMatrixNumColumns(output.varType.getBasicType());
+			const glu::DataType	uintBasicType	= glu::getDataTypeUintVec(vecSize);
+			const glu::VarType	uintType		(uintBasicType, glu::PRECISION_HIGHP);
+
+			decl.varType = uintType;
+			for (int vecNdx = 0; vecNdx < numVecs; ++vecNdx)
+			{
+				decl.name				= outVarName + "_" + de::toString(vecNdx);
+				decl.layout.location	= location + vecNdx;
+				src << decl << ";\n";
+			}
+		}
+		else
+			src << glu::VariableDeclaration(output.varType, output.name, glu::STORAGE_OUT, glu::INTERPOLATION_LAST, location) << ";\n";
+	}
+
+	src << "\nvoid main (void)\n{\n";
+
+	for (vector<Symbol>::const_iterator output = shaderSpec.outputs.begin(); output != shaderSpec.outputs.end(); ++output)
+	{
+		if ((useIntOutputs && glu::isDataTypeFloatOrVec(output->varType.getBasicType())) ||
+			glu::isDataTypeBoolOrBVec(output->varType.getBasicType()) ||
+			glu::isDataTypeMatrix(output->varType.getBasicType()))
+			src << "\t" << glu::declare(output->varType, output->name) << ";\n";
+	}
+
+	// Operation - indented to correct level.
+	{
+		std::istringstream	opSrc	(shaderSpec.source);
+		std::string			line;
+
+		while (std::getline(opSrc, line))
+			src << "\t" << line << "\n";
+	}
+
+	for (vector<Symbol>::const_iterator output = shaderSpec.outputs.begin(); output != shaderSpec.outputs.end(); ++output)
+	{
+		if (useIntOutputs && glu::isDataTypeFloatOrVec(output->varType.getBasicType()))
+			src << "	o_" << output->name << " = floatBitsToUint(" << output->name << ");\n";
+		else if (glu::isDataTypeMatrix(output->varType.getBasicType()))
+		{
+			const int			numVecs			= glu::getDataTypeMatrixNumColumns(output->varType.getBasicType());
+
+			for (int vecNdx = 0; vecNdx < numVecs; ++vecNdx)
+				if (useIntOutputs)
+					src << "\to_" << output->name << "_" << vecNdx << " = floatBitsToUint(" << output->name << "[" << vecNdx << "]);\n";
+				else
+					src << "\to_" << output->name << "_" << vecNdx << " = " << output->name << "[" << vecNdx << "];\n";
+		}
+		else if (glu::isDataTypeBoolOrBVec(output->varType.getBasicType()))
+		{
+			const int				vecSize		= glu::getDataTypeScalarSize(output->varType.getBasicType());
+			const glu::DataType		intBaseType	= vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
+
+			src << "\to_" << output->name << " = " << glu::getDataTypeName(intBaseType) << "(" << output->name << ");\n";
+		}
+	}
+
+	src << "}\n";
+
+	return src.str();
+}
+
+// ShaderExecutor
+
+ShaderExecutor::ShaderExecutor (const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec)
+	: m_renderCtx	(renderCtx)
+	, m_inputs		(shaderSpec.inputs)
+	, m_outputs		(shaderSpec.outputs)
+{
+}
+
+ShaderExecutor::~ShaderExecutor (void)
+{
+}
+
+void ShaderExecutor::useProgram (void)
+{
+	DE_ASSERT(isOk());
+	m_renderCtx.getFunctions().useProgram(getProgram());
+}
+
+// VertexProcessorExecutor (base class for vertex and geometry executors)
+
+class VertexProcessorExecutor : public ShaderExecutor
+{
+public:
+								VertexProcessorExecutor	(const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec, const glu::ProgramSources& sources);
+								~VertexProcessorExecutor(void);
+
+	bool						isOk					(void) const				{ return m_program.isOk();			}
+	void						log						(tcu::TestLog& dst) const	{ dst << m_program;					}
+	deUint32					getProgram				(void) const				{ return m_program.getProgram();	}
+
+	void						execute					(int numValues, const void* const* inputs, void* const* outputs);
+
+protected:
+	glu::ShaderProgram			m_program;
+};
+
+template<typename Iterator>
+struct SymbolNameIterator
+{
+	Iterator symbolIter;
+
+	SymbolNameIterator (Iterator symbolIter_) : symbolIter(symbolIter_) {}
+
+	inline SymbolNameIterator&	operator++	(void)								{ ++symbolIter; return *this;				}
+
+	inline bool					operator==	(const SymbolNameIterator& other)	{ return symbolIter == other.symbolIter;	}
+	inline bool					operator!=	(const SymbolNameIterator& other)	{ return symbolIter != other.symbolIter;	}
+
+	inline std::string operator* (void) const
+	{
+		if (glu::isDataTypeBoolOrBVec(symbolIter->varType.getBasicType()))
+			return "o_" + symbolIter->name;
+		else
+			return symbolIter->name;
+	}
+};
+
+template<typename Iterator>
+inline glu::TransformFeedbackVaryings<SymbolNameIterator<Iterator> > getTFVaryings (Iterator begin, Iterator end)
+{
+	return glu::TransformFeedbackVaryings<SymbolNameIterator<Iterator> >(SymbolNameIterator<Iterator>(begin), SymbolNameIterator<Iterator>(end));
+}
+
+VertexProcessorExecutor::VertexProcessorExecutor (const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec, const glu::ProgramSources& sources)
+	: ShaderExecutor	(renderCtx, shaderSpec)
+	, m_program			(renderCtx,
+						 glu::ProgramSources(sources) << getTFVaryings(shaderSpec.outputs.begin(), shaderSpec.outputs.end())
+													  << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS))
+{
+}
+
+VertexProcessorExecutor::~VertexProcessorExecutor (void)
+{
+}
+
+template<typename Iterator>
+static int computeTotalScalarSize (Iterator begin, Iterator end)
+{
+	int size = 0;
+	for (Iterator cur = begin; cur != end; ++cur)
+		size += cur->varType.getScalarSize();
+	return size;
+}
+
+void VertexProcessorExecutor::execute (int numValues, const void* const* inputs, void* const* outputs)
+{
+	const glw::Functions&					gl					= m_renderCtx.getFunctions();
+	const bool								useTFObject			= isContextTypeES(m_renderCtx.getType()) || (isContextTypeGLCore(m_renderCtx.getType()) && m_renderCtx.getType().getMajorVersion() >= 4);
+	vector<glu::VertexArrayBinding>			vertexArrays;
+	de::UniquePtr<glu::TransformFeedback>	transformFeedback	(useTFObject ? new glu::TransformFeedback(m_renderCtx) : DE_NULL);
+	glu::Buffer								outputBuffer		(m_renderCtx);
+	const int								outputBufferStride	= computeTotalScalarSize(m_outputs.begin(), m_outputs.end())*sizeof(deUint32);
+
+	// Setup inputs.
+	for (int inputNdx = 0; inputNdx < (int)m_inputs.size(); inputNdx++)
+	{
+		const Symbol&		symbol		= m_inputs[inputNdx];
+		const void*			ptr			= inputs[inputNdx];
+		const glu::DataType	basicType	= symbol.varType.getBasicType();
+		const int			vecSize		= glu::getDataTypeScalarSize(basicType);
+
+		if (glu::isDataTypeFloatOrVec(basicType))
+			vertexArrays.push_back(glu::va::Float(symbol.name, vecSize, numValues, 0, (const float*)ptr));
+		else if (glu::isDataTypeIntOrIVec(basicType))
+			vertexArrays.push_back(glu::va::Int32(symbol.name, vecSize, numValues, 0, (const deInt32*)ptr));
+		else if (glu::isDataTypeUintOrUVec(basicType))
+			vertexArrays.push_back(glu::va::Uint32(symbol.name, vecSize, numValues, 0, (const deUint32*)ptr));
+		else if (glu::isDataTypeMatrix(basicType))
+		{
+			int		numRows	= glu::getDataTypeMatrixNumRows(basicType);
+			int		numCols	= glu::getDataTypeMatrixNumColumns(basicType);
+			int		stride	= numRows * numCols * sizeof(float);
+
+			for (int colNdx = 0; colNdx < numCols; ++colNdx)
+				vertexArrays.push_back(glu::va::Float(symbol.name, colNdx, numRows, numValues, stride, ((const float*)ptr) + colNdx * numRows));
+		}
+		else
+			DE_ASSERT(false);
+	}
+
+	// Setup TF outputs.
+	if (useTFObject)
+		gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, **transformFeedback);
+	gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, *outputBuffer);
+	gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, outputBufferStride*numValues, DE_NULL, GL_STREAM_READ);
+	gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, *outputBuffer);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Error in TF setup");
+
+	// Draw with rasterization disabled.
+	gl.beginTransformFeedback(GL_POINTS);
+	gl.enable(GL_RASTERIZER_DISCARD);
+	glu::draw(m_renderCtx, m_program.getProgram(), (int)vertexArrays.size(), vertexArrays.empty() ? DE_NULL : &vertexArrays[0],
+			  glu::pr::Points(numValues));
+	gl.disable(GL_RASTERIZER_DISCARD);
+	gl.endTransformFeedback();
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Error in draw");
+
+	// Read back data.
+	{
+		const void*	srcPtr		= gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, outputBufferStride*numValues, GL_MAP_READ_BIT);
+		int			curOffset	= 0; // Offset in buffer in bytes.
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER)");
+		TCU_CHECK(srcPtr != DE_NULL);
+
+		for (int outputNdx = 0; outputNdx < (int)m_outputs.size(); outputNdx++)
+		{
+			const Symbol&		symbol		= m_outputs[outputNdx];
+			void*				dstPtr		= outputs[outputNdx];
+			const int			scalarSize	= symbol.varType.getScalarSize();
+
+			for (int ndx = 0; ndx < numValues; ndx++)
+				deMemcpy((deUint32*)dstPtr + scalarSize*ndx, (const deUint8*)srcPtr + curOffset + ndx*outputBufferStride, scalarSize*sizeof(deUint32));
+
+			curOffset += scalarSize*sizeof(deUint32);
+		}
+
+		gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
+	}
+
+	if (useTFObject)
+		gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+	gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Restore state");
+}
+
+// VertexShaderExecutor
+
+class VertexShaderExecutor : public VertexProcessorExecutor
+{
+public:
+								VertexShaderExecutor	(const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec);
+};
+
+VertexShaderExecutor::VertexShaderExecutor (const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec)
+	: VertexProcessorExecutor	(renderCtx, shaderSpec,
+								 glu::ProgramSources() << glu::VertexSource(generateVertexShader(shaderSpec))
+													   << glu::FragmentSource(generateEmptyFragmentSource(shaderSpec.version)))
+{
+}
+
+// GeometryShaderExecutor
+
+class GeometryShaderExecutor : public VertexProcessorExecutor
+{
+public:
+								GeometryShaderExecutor	(const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec);
+};
+
+GeometryShaderExecutor::GeometryShaderExecutor (const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec)
+	: VertexProcessorExecutor	(renderCtx, shaderSpec,
+								 glu::ProgramSources() << glu::VertexSource(generatePassthroughVertexShader(shaderSpec, "", "geom_"))
+													   << glu::GeometrySource(generateGeometryShader(shaderSpec))
+													   << glu::FragmentSource(generateEmptyFragmentSource(shaderSpec.version)))
+{
+	if (renderCtx.getType().getAPI() == glu::ApiType::es(3,1))
+		checkExtension(renderCtx, "GL_EXT_geometry_shader");
+}
+
+// FragmentShaderExecutor
+
+class FragmentShaderExecutor : public ShaderExecutor
+{
+public:
+								FragmentShaderExecutor	(const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec);
+								~FragmentShaderExecutor	(void);
+
+	bool						isOk					(void) const				{ return m_program.isOk();			}
+	void						log						(tcu::TestLog& dst) const	{ dst << m_program;					}
+	deUint32					getProgram				(void) const				{ return m_program.getProgram();	}
+
+	void						execute					(int numValues, const void* const* inputs, void* const* outputs);
+
+protected:
+	std::vector<const Symbol*>	m_outLocationSymbols;
+	std::map<std::string, int>	m_outLocationMap;
+	glu::ShaderProgram			m_program;
+};
+
+static std::map<std::string, int> generateLocationMap (const std::vector<Symbol>& symbols, std::vector<const Symbol*>& locationSymbols)
+{
+	std::map<std::string, int>	ret;
+	int							location	= 0;
+
+	locationSymbols.clear();
+
+	for (std::vector<Symbol>::const_iterator it = symbols.begin(); it != symbols.end(); ++it)
+	{
+		const int	numLocations	= glu::getDataTypeNumLocations(it->varType.getBasicType());
+
+		TCU_CHECK_INTERNAL(!de::contains(ret, it->name));
+		de::insert(ret, it->name, location);
+		location += numLocations;
+
+		for (int ndx = 0; ndx < numLocations; ++ndx)
+			locationSymbols.push_back(&*it);
+	}
+
+	return ret;
+}
+
+inline bool hasFloatRenderTargets (const glu::RenderContext& renderCtx)
+{
+	glu::ContextType type = renderCtx.getType();
+	return glu::isContextTypeGLCore(type);
+}
+
+FragmentShaderExecutor::FragmentShaderExecutor (const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec)
+	: ShaderExecutor		(renderCtx, shaderSpec)
+	, m_outLocationSymbols	()
+	, m_outLocationMap		(generateLocationMap(m_outputs, m_outLocationSymbols))
+	, m_program				(renderCtx,
+							 glu::ProgramSources() << glu::VertexSource(generatePassthroughVertexShader(shaderSpec, "a_", ""))
+												   << glu::FragmentSource(generateFragmentShader(shaderSpec, !hasFloatRenderTargets(renderCtx), m_outLocationMap)))
+{
+}
+
+FragmentShaderExecutor::~FragmentShaderExecutor (void)
+{
+}
+
+inline int queryInt (const glw::Functions& gl, deUint32 pname)
+{
+	int value = 0;
+	gl.getIntegerv(pname, &value);
+	return value;
+}
+
+static tcu::TextureFormat getRenderbufferFormatForOutput (const glu::VarType& outputType, bool useIntOutputs)
+{
+	const tcu::TextureFormat::ChannelOrder channelOrderMap[] =
+	{
+		tcu::TextureFormat::R,
+		tcu::TextureFormat::RG,
+		tcu::TextureFormat::RGBA,	// No RGB variants available.
+		tcu::TextureFormat::RGBA
+	};
+
+	const glu::DataType					basicType		= outputType.getBasicType();
+	const int							numComps		= glu::getDataTypeNumComponents(basicType);
+	tcu::TextureFormat::ChannelType		channelType;
+
+	switch (glu::getDataTypeScalarType(basicType))
+	{
+		case glu::TYPE_UINT:	channelType = tcu::TextureFormat::UNSIGNED_INT32;												break;
+		case glu::TYPE_INT:		channelType = tcu::TextureFormat::SIGNED_INT32;													break;
+		case glu::TYPE_BOOL:	channelType = tcu::TextureFormat::SIGNED_INT32;													break;
+		case glu::TYPE_FLOAT:	channelType = useIntOutputs ? tcu::TextureFormat::UNSIGNED_INT32 : tcu::TextureFormat::FLOAT;	break;
+		default:
+			throw tcu::InternalError("Invalid output type");
+	}
+
+	DE_ASSERT(de::inRange<int>(numComps, 1, DE_LENGTH_OF_ARRAY(channelOrderMap)));
+
+	return tcu::TextureFormat(channelOrderMap[numComps-1], channelType);
+}
+
+void FragmentShaderExecutor::execute (int numValues, const void* const* inputs, void* const* outputs)
+{
+	const glw::Functions&			gl					= m_renderCtx.getFunctions();
+	const bool						useIntOutputs		= !hasFloatRenderTargets(m_renderCtx);
+	const int						maxRenderbufferSize	= queryInt(gl, GL_MAX_RENDERBUFFER_SIZE);
+	const int						framebufferW		= de::min(maxRenderbufferSize, numValues);
+	const int						framebufferH		= (numValues / framebufferW) + ((numValues % framebufferW != 0) ? 1 : 0);
+
+	glu::Framebuffer				framebuffer			(m_renderCtx);
+	glu::RenderbufferVector			renderbuffers		(m_renderCtx, m_outLocationSymbols.size());
+
+	vector<glu::VertexArrayBinding>	vertexArrays;
+	vector<tcu::Vec2>				positions			(numValues);
+
+	if (framebufferH > maxRenderbufferSize)
+		throw tcu::NotSupportedError("Value count is too high for maximum supported renderbuffer size");
+
+	// Compute positions - 1px points are used to drive fragment shading.
+	for (int valNdx = 0; valNdx < numValues; valNdx++)
+	{
+		const int		ix		= valNdx % framebufferW;
+		const int		iy		= valNdx / framebufferW;
+		const float		fx		= -1.0f + 2.0f*((float(ix) + 0.5f) / float(framebufferW));
+		const float		fy		= -1.0f + 2.0f*((float(iy) + 0.5f) / float(framebufferH));
+
+		positions[valNdx] = tcu::Vec2(fx, fy);
+	}
+
+	// Vertex inputs.
+	vertexArrays.push_back(glu::va::Float("a_position", 2, numValues, 0, (const float*)&positions[0]));
+
+	for (int inputNdx = 0; inputNdx < (int)m_inputs.size(); inputNdx++)
+	{
+		const Symbol&		symbol		= m_inputs[inputNdx];
+		const std::string	attribName	= "a_" + symbol.name;
+		const void*			ptr			= inputs[inputNdx];
+		const glu::DataType	basicType	= symbol.varType.getBasicType();
+		const int			vecSize		= glu::getDataTypeScalarSize(basicType);
+
+		if (glu::isDataTypeFloatOrVec(basicType))
+			vertexArrays.push_back(glu::va::Float(attribName, vecSize, numValues, 0, (const float*)ptr));
+		else if (glu::isDataTypeIntOrIVec(basicType))
+			vertexArrays.push_back(glu::va::Int32(attribName, vecSize, numValues, 0, (const deInt32*)ptr));
+		else if (glu::isDataTypeUintOrUVec(basicType))
+			vertexArrays.push_back(glu::va::Uint32(attribName, vecSize, numValues, 0, (const deUint32*)ptr));
+		else if (glu::isDataTypeMatrix(basicType))
+		{
+			int		numRows	= glu::getDataTypeMatrixNumRows(basicType);
+			int		numCols	= glu::getDataTypeMatrixNumColumns(basicType);
+			int		stride	= numRows * numCols * sizeof(float);
+
+			for (int colNdx = 0; colNdx < numCols; ++colNdx)
+				vertexArrays.push_back(glu::va::Float(attribName, colNdx, numRows, numValues, stride, ((const float*)ptr) + colNdx * numRows));
+		}
+		else
+			DE_ASSERT(false);
+	}
+
+	// Construct framebuffer.
+	gl.bindFramebuffer(GL_FRAMEBUFFER, *framebuffer);
+
+	for (int outNdx = 0; outNdx < (int)m_outLocationSymbols.size(); ++outNdx)
+	{
+		const Symbol&	output			= *m_outLocationSymbols[outNdx];
+		const deUint32	renderbuffer	= renderbuffers[outNdx];
+		const deUint32	format			= glu::getInternalFormat(getRenderbufferFormatForOutput(output.varType, useIntOutputs));
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+		gl.renderbufferStorage(GL_RENDERBUFFER, format, framebufferW, framebufferH);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+outNdx, GL_RENDERBUFFER, renderbuffer);
+	}
+	gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set up framebuffer object");
+	TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+	{
+		vector<deUint32> drawBuffers(m_outLocationSymbols.size());
+		for (int ndx = 0; ndx < (int)m_outLocationSymbols.size(); ndx++)
+			drawBuffers[ndx] = GL_COLOR_ATTACHMENT0+ndx;
+		gl.drawBuffers((int)drawBuffers.size(), &drawBuffers[0]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glDrawBuffers()");
+	}
+
+	// Render
+	gl.viewport(0, 0, framebufferW, framebufferH);
+	glu::draw(m_renderCtx, m_program.getProgram(), (int)vertexArrays.size(), &vertexArrays[0],
+			  glu::pr::Points(numValues));
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Error in draw");
+
+	// Read back pixels.
+	{
+		tcu::TextureLevel	tmpBuf;
+
+		// \todo [2013-08-07 pyry] Some fast-paths could be added here.
+
+		for (int outNdx = 0; outNdx < (int)m_outputs.size(); ++outNdx)
+		{
+			const Symbol&				output			= m_outputs[outNdx];
+			const int					outSize			= output.varType.getScalarSize();
+			const int					outVecSize		= glu::getDataTypeNumComponents(output.varType.getBasicType());
+			const int					outNumLocs		= glu::getDataTypeNumLocations(output.varType.getBasicType());
+			deUint32*					dstPtrBase		= static_cast<deUint32*>(outputs[outNdx]);
+			const tcu::TextureFormat	format			= getRenderbufferFormatForOutput(output.varType, useIntOutputs);
+			const tcu::TextureFormat	readFormat		(tcu::TextureFormat::RGBA, format.type);
+			const int					outLocation		= de::lookup(m_outLocationMap, output.name);
+
+			tmpBuf.setStorage(readFormat, framebufferW, framebufferH);
+
+			for (int locNdx = 0; locNdx < outNumLocs; ++locNdx)
+			{
+				gl.readBuffer(GL_COLOR_ATTACHMENT0 + outLocation + locNdx);
+				glu::readPixels(m_renderCtx, 0, 0, tmpBuf.getAccess());
+				GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels");
+
+				if (outSize == 4 && outNumLocs == 1)
+					deMemcpy(dstPtrBase, tmpBuf.getAccess().getDataPtr(), numValues*outVecSize*sizeof(deUint32));
+				else
+				{
+					for (int valNdx = 0; valNdx < numValues; valNdx++)
+					{
+						const deUint32* srcPtr = (const deUint32*)tmpBuf.getAccess().getDataPtr() + valNdx*4;
+						deUint32*		dstPtr = &dstPtrBase[outSize*valNdx + outVecSize*locNdx];
+						deMemcpy(dstPtr, srcPtr, outVecSize*sizeof(deUint32));
+					}
+				}
+			}
+		}
+	}
+
+	// \todo [2013-08-07 pyry] Clear draw buffers & viewport?
+	gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+// Shared utilities for compute and tess executors
+
+static deUint32 getVecStd430ByteAlignment (glu::DataType type)
+{
+	switch (glu::getDataTypeScalarSize(type))
+	{
+		case 1:		return 4u;
+		case 2:		return 8u;
+		case 3:		return 16u;
+		case 4:		return 16u;
+		default:
+			DE_ASSERT(false);
+			return 0u;
+	}
+}
+
+class BufferIoExecutor : public ShaderExecutor
+{
+public:
+						BufferIoExecutor	(const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec, const glu::ProgramSources& sources);
+						~BufferIoExecutor	(void);
+
+	bool				isOk				(void) const				{ return m_program.isOk();			}
+	void				log					(tcu::TestLog& dst) const	{ dst << m_program;					}
+	deUint32			getProgram			(void) const				{ return m_program.getProgram();	}
+
+protected:
+	enum
+	{
+		INPUT_BUFFER_BINDING	= 0,
+		OUTPUT_BUFFER_BINDING	= 1,
+	};
+
+	void				initBuffers			(int numValues);
+	deUint32			getInputBuffer		(void) const		{ return *m_inputBuffer;					}
+	deUint32			getOutputBuffer		(void) const		{ return *m_outputBuffer;					}
+	deUint32			getInputStride		(void) const		{ return getLayoutStride(m_inputLayout);	}
+	deUint32			getOutputStride		(void) const		{ return getLayoutStride(m_outputLayout);	}
+
+	void				uploadInputBuffer	(const void* const* inputPtrs, int numValues);
+	void				readOutputBuffer	(void* const* outputPtrs, int numValues);
+
+	static void			declareBufferBlocks	(std::ostream& src, const ShaderSpec& spec);
+	static void			generateExecBufferIo(std::ostream& src, const ShaderSpec& spec, const char* invocationNdxName);
+
+	glu::ShaderProgram	m_program;
+
+private:
+	struct VarLayout
+	{
+		deUint32		offset;
+		deUint32		stride;
+		deUint32		matrixStride;
+
+		VarLayout (void) : offset(0), stride(0), matrixStride(0) {}
+	};
+
+	void				resizeInputBuffer	(int newSize);
+	void				resizeOutputBuffer	(int newSize);
+
+	static void			computeVarLayout	(const std::vector<Symbol>& symbols, std::vector<VarLayout>* layout);
+	static deUint32		getLayoutStride		(const vector<VarLayout>& layout);
+
+	static void			copyToBuffer		(const glu::VarType& varType, const VarLayout& layout, int numValues, const void* srcBasePtr, void* dstBasePtr);
+	static void			copyFromBuffer		(const glu::VarType& varType, const VarLayout& layout, int numValues, const void* srcBasePtr, void* dstBasePtr);
+
+	glu::Buffer			m_inputBuffer;
+	glu::Buffer			m_outputBuffer;
+
+	vector<VarLayout>	m_inputLayout;
+	vector<VarLayout>	m_outputLayout;
+};
+
+BufferIoExecutor::BufferIoExecutor (const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec, const glu::ProgramSources& sources)
+	: ShaderExecutor	(renderCtx, shaderSpec)
+	, m_program			(renderCtx, sources)
+	, m_inputBuffer		(renderCtx)
+	, m_outputBuffer	(renderCtx)
+{
+	computeVarLayout(m_inputs, &m_inputLayout);
+	computeVarLayout(m_outputs, &m_outputLayout);
+}
+
+BufferIoExecutor::~BufferIoExecutor (void)
+{
+}
+
+void BufferIoExecutor::resizeInputBuffer (int newSize)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *m_inputBuffer);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, newSize, DE_NULL, GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to allocate input buffer");
+}
+
+void BufferIoExecutor::resizeOutputBuffer (int newSize)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *m_outputBuffer);
+	gl.bufferData(GL_SHADER_STORAGE_BUFFER, newSize, DE_NULL, GL_STATIC_DRAW);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to allocate output buffer");
+}
+
+void BufferIoExecutor::initBuffers (int numValues)
+{
+	const deUint32		inputStride			= getLayoutStride(m_inputLayout);
+	const deUint32		outputStride		= getLayoutStride(m_outputLayout);
+	const int			inputBufferSize		= numValues * inputStride;
+	const int			outputBufferSize	= numValues * outputStride;
+
+	resizeInputBuffer(inputBufferSize);
+	resizeOutputBuffer(outputBufferSize);
+}
+
+void BufferIoExecutor::computeVarLayout (const std::vector<Symbol>& symbols, std::vector<VarLayout>* layout)
+{
+	deUint32	maxAlignment	= 0;
+	deUint32	curOffset		= 0;
+
+	DE_ASSERT(layout->empty());
+	layout->resize(symbols.size());
+
+	for (size_t varNdx = 0; varNdx < symbols.size(); varNdx++)
+	{
+		const Symbol&		symbol		= symbols[varNdx];
+		const glu::DataType	basicType	= symbol.varType.getBasicType();
+		VarLayout&			layoutEntry	= (*layout)[varNdx];
+
+		if (glu::isDataTypeScalarOrVector(basicType))
+		{
+			const deUint32	alignment	= getVecStd430ByteAlignment(basicType);
+			const deUint32	size		= (deUint32)glu::getDataTypeScalarSize(basicType)*sizeof(deUint32);
+
+			curOffset		= (deUint32)deAlign32((int)curOffset, (int)alignment);
+			maxAlignment	= de::max(maxAlignment, alignment);
+
+			layoutEntry.offset			= curOffset;
+			layoutEntry.matrixStride	= 0;
+
+			curOffset += size;
+		}
+		else if (glu::isDataTypeMatrix(basicType))
+		{
+			const int				numVecs			= glu::getDataTypeMatrixNumColumns(basicType);
+			const glu::DataType		vecType			= glu::getDataTypeFloatVec(glu::getDataTypeMatrixNumRows(basicType));
+			const deUint32			vecAlignment	= getVecStd430ByteAlignment(vecType);
+
+			curOffset		= (deUint32)deAlign32((int)curOffset, (int)vecAlignment);
+			maxAlignment	= de::max(maxAlignment, vecAlignment);
+
+			layoutEntry.offset			= curOffset;
+			layoutEntry.matrixStride	= vecAlignment;
+
+			curOffset += vecAlignment*numVecs;
+		}
+		else
+			DE_ASSERT(false);
+	}
+
+	{
+		const deUint32	totalSize	= (deUint32)deAlign32(curOffset, maxAlignment);
+
+		for (vector<VarLayout>::iterator varIter = layout->begin(); varIter != layout->end(); ++varIter)
+			varIter->stride = totalSize;
+	}
+}
+
+inline deUint32 BufferIoExecutor::getLayoutStride (const vector<VarLayout>& layout)
+{
+	return layout.empty() ? 0 : layout[0].stride;
+}
+
+void BufferIoExecutor::copyToBuffer (const glu::VarType& varType, const VarLayout& layout, int numValues, const void* srcBasePtr, void* dstBasePtr)
+{
+	if (varType.isBasicType())
+	{
+		const glu::DataType		basicType		= varType.getBasicType();
+		const bool				isMatrix		= glu::isDataTypeMatrix(basicType);
+		const int				scalarSize		= glu::getDataTypeScalarSize(basicType);
+		const int				numVecs			= isMatrix ? glu::getDataTypeMatrixNumColumns(basicType) : 1;
+		const int				numComps		= scalarSize / numVecs;
+
+		for (int elemNdx = 0; elemNdx < numValues; elemNdx++)
+		{
+			for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
+			{
+				const int		srcOffset		= sizeof(deUint32)*(elemNdx*scalarSize + vecNdx*numComps);
+				const int		dstOffset		= layout.offset + layout.stride*elemNdx + (isMatrix ? layout.matrixStride*vecNdx : 0);
+				const deUint8*	srcPtr			= (const deUint8*)srcBasePtr + srcOffset;
+				deUint8*		dstPtr			= (deUint8*)dstBasePtr + dstOffset;
+
+				deMemcpy(dstPtr, srcPtr, sizeof(deUint32)*numComps);
+			}
+		}
+	}
+	else
+		throw tcu::InternalError("Unsupported type");
+}
+
+void BufferIoExecutor::copyFromBuffer (const glu::VarType& varType, const VarLayout& layout, int numValues, const void* srcBasePtr, void* dstBasePtr)
+{
+	if (varType.isBasicType())
+	{
+		const glu::DataType		basicType		= varType.getBasicType();
+		const bool				isMatrix		= glu::isDataTypeMatrix(basicType);
+		const int				scalarSize		= glu::getDataTypeScalarSize(basicType);
+		const int				numVecs			= isMatrix ? glu::getDataTypeMatrixNumColumns(basicType) : 1;
+		const int				numComps		= scalarSize / numVecs;
+
+		for (int elemNdx = 0; elemNdx < numValues; elemNdx++)
+		{
+			for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
+			{
+				const int		srcOffset		= layout.offset + layout.stride*elemNdx + (isMatrix ? layout.matrixStride*vecNdx : 0);
+				const int		dstOffset		= sizeof(deUint32)*(elemNdx*scalarSize + vecNdx*numComps);
+				const deUint8*	srcPtr			= (const deUint8*)srcBasePtr + srcOffset;
+				deUint8*		dstPtr			= (deUint8*)dstBasePtr + dstOffset;
+
+				deMemcpy(dstPtr, srcPtr, sizeof(deUint32)*numComps);
+			}
+		}
+	}
+	else
+		throw tcu::InternalError("Unsupported type");
+}
+
+void BufferIoExecutor::uploadInputBuffer (const void* const* inputPtrs, int numValues)
+{
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	const deUint32			buffer			= *m_inputBuffer;
+	const deUint32			inputStride		= getLayoutStride(m_inputLayout);
+	const int				inputBufferSize	= inputStride*numValues;
+
+	if (inputBufferSize == 0)
+		return; // No inputs
+
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
+	void* mapPtr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, inputBufferSize, GL_MAP_WRITE_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+	TCU_CHECK(mapPtr);
+
+	try
+	{
+		DE_ASSERT(m_inputs.size() == m_inputLayout.size());
+		for (size_t inputNdx = 0; inputNdx < m_inputs.size(); ++inputNdx)
+		{
+			const glu::VarType&		varType		= m_inputs[inputNdx].varType;
+			const VarLayout&		layout		= m_inputLayout[inputNdx];
+
+			copyToBuffer(varType, layout, numValues, inputPtrs[inputNdx], mapPtr);
+		}
+	}
+	catch (...)
+	{
+		gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+		throw;
+	}
+
+	gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
+}
+
+void BufferIoExecutor::readOutputBuffer (void* const* outputPtrs, int numValues)
+{
+	const glw::Functions&	gl					= m_renderCtx.getFunctions();
+	const deUint32			buffer				= *m_outputBuffer;
+	const deUint32			outputStride		= getLayoutStride(m_outputLayout);
+	const int				outputBufferSize	= numValues*outputStride;
+
+	DE_ASSERT(outputBufferSize > 0); // At least some outputs are required.
+
+	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
+	void* mapPtr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, outputBufferSize, GL_MAP_READ_BIT);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+	TCU_CHECK(mapPtr);
+
+	try
+	{
+		DE_ASSERT(m_outputs.size() == m_outputLayout.size());
+		for (size_t outputNdx = 0; outputNdx < m_outputs.size(); ++outputNdx)
+		{
+			const glu::VarType&		varType		= m_outputs[outputNdx].varType;
+			const VarLayout&		layout		= m_outputLayout[outputNdx];
+
+			copyFromBuffer(varType, layout, numValues, mapPtr, outputPtrs[outputNdx]);
+		}
+	}
+	catch (...)
+	{
+		gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+		throw;
+	}
+
+	gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
+}
+
+void BufferIoExecutor::declareBufferBlocks (std::ostream& src, const ShaderSpec& spec)
+{
+	// Input struct
+	if (!spec.inputs.empty())
+	{
+		glu::StructType inputStruct("Inputs");
+		for (vector<Symbol>::const_iterator symIter = spec.inputs.begin(); symIter != spec.inputs.end(); ++symIter)
+			inputStruct.addMember(symIter->name.c_str(), symIter->varType);
+		src << glu::declare(&inputStruct) << ";\n";
+	}
+
+	// Output struct
+	{
+		glu::StructType outputStruct("Outputs");
+		for (vector<Symbol>::const_iterator symIter = spec.outputs.begin(); symIter != spec.outputs.end(); ++symIter)
+			outputStruct.addMember(symIter->name.c_str(), symIter->varType);
+		src << glu::declare(&outputStruct) << ";\n";
+	}
+
+	src << "\n";
+
+	if (!spec.inputs.empty())
+	{
+		src	<< "layout(binding = " << int(INPUT_BUFFER_BINDING) << ", std430) buffer InBuffer\n"
+			<< "{\n"
+			<< "	Inputs inputs[];\n"
+			<< "};\n";
+	}
+
+	src	<< "layout(binding = " << int(OUTPUT_BUFFER_BINDING) << ", std430) buffer OutBuffer\n"
+		<< "{\n"
+		<< "	Outputs outputs[];\n"
+		<< "};\n"
+		<< "\n";
+}
+
+void BufferIoExecutor::generateExecBufferIo (std::ostream& src, const ShaderSpec& spec, const char* invocationNdxName)
+{
+	for (vector<Symbol>::const_iterator symIter = spec.inputs.begin(); symIter != spec.inputs.end(); ++symIter)
+		src << "\t" << glu::declare(symIter->varType, symIter->name) << " = inputs[" << invocationNdxName << "]." << symIter->name << ";\n";
+
+	for (vector<Symbol>::const_iterator symIter = spec.outputs.begin(); symIter != spec.outputs.end(); ++symIter)
+		src << "\t" << glu::declare(symIter->varType, symIter->name) << ";\n";
+
+	src << "\n";
+
+	{
+		std::istringstream	opSrc	(spec.source);
+		std::string			line;
+
+		while (std::getline(opSrc, line))
+			src << "\t" << line << "\n";
+	}
+
+	src << "\n";
+	for (vector<Symbol>::const_iterator symIter = spec.outputs.begin(); symIter != spec.outputs.end(); ++symIter)
+		src << "\toutputs[" << invocationNdxName << "]." << symIter->name << " = " << symIter->name << ";\n";
+}
+
+// ComputeShaderExecutor
+
+class ComputeShaderExecutor : public BufferIoExecutor
+{
+public:
+						ComputeShaderExecutor	(const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec);
+						~ComputeShaderExecutor	(void);
+
+	void				execute					(int numValues, const void* const* inputs, void* const* outputs);
+
+protected:
+	static std::string	generateComputeShader	(const ShaderSpec& spec);
+
+	tcu::IVec3			m_maxWorkSize;
+};
+
+std::string ComputeShaderExecutor::generateComputeShader (const ShaderSpec& spec)
+{
+	std::ostringstream src;
+
+	src << glu::getGLSLVersionDeclaration(spec.version) << "\n";
+
+	if (!spec.globalDeclarations.empty())
+		src << spec.globalDeclarations << "\n";
+
+	src << "layout(local_size_x = 1) in;\n"
+		<< "\n";
+
+	declareBufferBlocks(src, spec);
+
+	src << "void main (void)\n"
+		<< "{\n"
+		<< "	uint invocationNdx = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z\n"
+		<< "	                   + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n";
+
+	generateExecBufferIo(src, spec, "invocationNdx");
+
+	src << "}\n";
+
+	return src.str();
+}
+
+ComputeShaderExecutor::ComputeShaderExecutor (const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec)
+	: BufferIoExecutor	(renderCtx, shaderSpec,
+						 glu::ProgramSources() << glu::ComputeSource(generateComputeShader(shaderSpec)))
+{
+	m_maxWorkSize	= tcu::IVec3(128,128,64); // Minimum in 3plus
+}
+
+ComputeShaderExecutor::~ComputeShaderExecutor (void)
+{
+}
+
+void ComputeShaderExecutor::execute (int numValues, const void* const* inputs, void* const* outputs)
+{
+	const glw::Functions&	gl						= m_renderCtx.getFunctions();
+	const int				maxValuesPerInvocation	= m_maxWorkSize[0];
+	const deUint32			inputStride				= getInputStride();
+	const deUint32			outputStride			= getOutputStride();
+
+	initBuffers(numValues);
+
+	// Setup input buffer & copy data
+	uploadInputBuffer(inputs, numValues);
+
+	// Perform compute invocations
+	{
+		int curOffset = 0;
+		while (curOffset < numValues)
+		{
+			const int numToExec = de::min(maxValuesPerInvocation, numValues-curOffset);
+
+			if (inputStride > 0)
+				gl.bindBufferRange(GL_SHADER_STORAGE_BUFFER, INPUT_BUFFER_BINDING, getInputBuffer(), curOffset*inputStride, numToExec*inputStride);
+
+			gl.bindBufferRange(GL_SHADER_STORAGE_BUFFER, OUTPUT_BUFFER_BINDING, getOutputBuffer(), curOffset*outputStride, numToExec*outputStride);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBufferRange(GL_SHADER_STORAGE_BUFFER)");
+
+			gl.dispatchCompute(numToExec, 1, 1);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");
+
+			curOffset += numToExec;
+		}
+	}
+
+	// Read back data
+	readOutputBuffer(outputs, numValues);
+}
+
+// Tessellation utils
+
+static std::string generateVertexShaderForTess (glu::GLSLVersion version)
+{
+	std::ostringstream	src;
+
+	src << glu::getGLSLVersionDeclaration(version) << "\n";
+
+	src << "void main (void)\n{\n"
+		<< "	gl_Position = vec4(gl_VertexID/2, gl_VertexID%2, 0.0, 1.0);\n"
+		<< "}\n";
+
+	return src.str();
+}
+
+// TessControlExecutor
+
+class TessControlExecutor : public BufferIoExecutor
+{
+public:
+						TessControlExecutor			(const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec);
+						~TessControlExecutor		(void);
+
+	void				execute						(int numValues, const void* const* inputs, void* const* outputs);
+
+protected:
+	static std::string	generateTessControlShader	(const ShaderSpec& shaderSpec);
+};
+
+std::string TessControlExecutor::generateTessControlShader (const ShaderSpec& shaderSpec)
+{
+	std::ostringstream src;
+
+	src << glu::getGLSLVersionDeclaration(shaderSpec.version) << "\n";
+
+	if (shaderSpec.version == glu::GLSL_VERSION_310_ES)
+		src << "#extension GL_EXT_tessellation_shader : require\n";
+
+	if (!shaderSpec.globalDeclarations.empty())
+		src << shaderSpec.globalDeclarations << "\n";
+
+	src << "\nlayout(vertices = 1) out;\n\n";
+
+	declareBufferBlocks(src, shaderSpec);
+
+	src << "void main (void)\n{\n";
+
+	for (int ndx = 0; ndx < 2; ndx++)
+		src << "\tgl_TessLevelInner[" << ndx << "] = 1.0;\n";
+
+	for (int ndx = 0; ndx < 4; ndx++)
+		src << "\tgl_TessLevelOuter[" << ndx << "] = 1.0;\n";
+
+	src << "\n"
+		<< "\thighp uint invocationId = uint(gl_PrimitiveID);\n";
+
+	generateExecBufferIo(src, shaderSpec, "invocationId");
+
+	src << "}\n";
+
+	return src.str();
+}
+
+static std::string generateEmptyTessEvalShader (glu::GLSLVersion version)
+{
+	std::ostringstream src;
+
+	src << glu::getGLSLVersionDeclaration(version) << "\n";
+
+	if (version == glu::GLSL_VERSION_310_ES)
+		src << "#extension GL_EXT_tessellation_shader : require\n\n";
+
+	src << "layout(triangles, ccw) in;\n";
+
+	src << "\nvoid main (void)\n{\n"
+		<< "\tgl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n"
+		<< "}\n";
+
+	return src.str();
+}
+
+TessControlExecutor::TessControlExecutor (const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec)
+	: BufferIoExecutor	(renderCtx, shaderSpec, glu::ProgramSources()
+							<< glu::VertexSource(generateVertexShaderForTess(shaderSpec.version))
+							<< glu::TessellationControlSource(generateTessControlShader(shaderSpec))
+							<< glu::TessellationEvaluationSource(generateEmptyTessEvalShader(shaderSpec.version))
+							<< glu::FragmentSource(generateEmptyFragmentSource(shaderSpec.version)))
+{
+	if (renderCtx.getType().getAPI() == glu::ApiType::es(3,1))
+		checkExtension(renderCtx, "GL_EXT_tessellation_shader");
+}
+
+TessControlExecutor::~TessControlExecutor (void)
+{
+}
+
+void TessControlExecutor::execute (int numValues, const void* const* inputs, void* const* outputs)
+{
+	const glw::Functions&	gl	= m_renderCtx.getFunctions();
+
+	initBuffers(numValues);
+
+	// Setup input buffer & copy data
+	uploadInputBuffer(inputs, numValues);
+
+	if (!m_inputs.empty())
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, INPUT_BUFFER_BINDING, getInputBuffer());
+
+	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, OUTPUT_BUFFER_BINDING, getOutputBuffer());
+
+	// Render patches
+	gl.patchParameteri(GL_PATCH_VERTICES, 3);
+	gl.drawArrays(GL_PATCHES, 0, 3*numValues);
+
+	// Read back data
+	readOutputBuffer(outputs, numValues);
+}
+
+// TessEvaluationExecutor
+
+class TessEvaluationExecutor : public BufferIoExecutor
+{
+public:
+						TessEvaluationExecutor	(const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec);
+						~TessEvaluationExecutor	(void);
+
+	void				execute					(int numValues, const void* const* inputs, void* const* outputs);
+
+protected:
+	static std::string	generateTessEvalShader	(const ShaderSpec& shaderSpec);
+};
+
+static std::string generatePassthroughTessControlShader (glu::GLSLVersion version)
+{
+	std::ostringstream src;
+
+	src << glu::getGLSLVersionDeclaration(version) << "\n";
+
+	if (version == glu::GLSL_VERSION_310_ES)
+		src << "#extension GL_EXT_tessellation_shader : require\n\n";
+
+	src << "layout(vertices = 1) out;\n\n";
+
+	src << "void main (void)\n{\n";
+
+	for (int ndx = 0; ndx < 2; ndx++)
+		src << "\tgl_TessLevelInner[" << ndx << "] = 1.0;\n";
+
+	for (int ndx = 0; ndx < 4; ndx++)
+		src << "\tgl_TessLevelOuter[" << ndx << "] = 1.0;\n";
+
+	src << "}\n";
+
+	return src.str();
+}
+
+std::string TessEvaluationExecutor::generateTessEvalShader (const ShaderSpec& shaderSpec)
+{
+	std::ostringstream src;
+
+	src << glu::getGLSLVersionDeclaration(shaderSpec.version) << "\n";
+
+	if (shaderSpec.version == glu::GLSL_VERSION_310_ES)
+		src << "#extension GL_EXT_tessellation_shader : require\n";
+
+	if (!shaderSpec.globalDeclarations.empty())
+		src << shaderSpec.globalDeclarations << "\n";
+
+	src << "\n";
+
+	src << "layout(isolines, equal_spacing) in;\n\n";
+
+	declareBufferBlocks(src, shaderSpec);
+
+	src << "void main (void)\n{\n"
+		<< "\tgl_Position = vec4(gl_TessCoord.x, 0.0, 0.0, 1.0);\n"
+		<< "\thighp uint invocationId = uint(gl_PrimitiveID) + (gl_TessCoord.x > 0.5 ? 1u : 0u);\n";
+
+	generateExecBufferIo(src, shaderSpec, "invocationId");
+
+	src	<< "}\n";
+
+	return src.str();
+}
+
+TessEvaluationExecutor::TessEvaluationExecutor (const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec)
+	: BufferIoExecutor	(renderCtx, shaderSpec, glu::ProgramSources()
+							<< glu::VertexSource(generateVertexShaderForTess(shaderSpec.version))
+							<< glu::TessellationControlSource(generatePassthroughTessControlShader(shaderSpec.version))
+							<< glu::TessellationEvaluationSource(generateTessEvalShader(shaderSpec))
+							<< glu::FragmentSource(generateEmptyFragmentSource(shaderSpec.version)))
+{
+	if (renderCtx.getType().getAPI() == glu::ApiType::es(3,1))
+		checkExtension(renderCtx, "GL_EXT_tessellation_shader");
+}
+
+TessEvaluationExecutor::~TessEvaluationExecutor (void)
+{
+}
+
+void TessEvaluationExecutor::execute (int numValues, const void* const* inputs, void* const* outputs)
+{
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	const int				alignedValues	= deAlign32(numValues, 2);
+
+	// Initialize buffers with aligned value count to make room for padding
+	initBuffers(alignedValues);
+
+	// Setup input buffer & copy data
+	uploadInputBuffer(inputs, numValues);
+
+	// \todo [2014-06-26 pyry] Duplicate last value in the buffer to prevent infinite loops for example?
+
+	if (!m_inputs.empty())
+		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, INPUT_BUFFER_BINDING, getInputBuffer());
+
+	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, OUTPUT_BUFFER_BINDING, getOutputBuffer());
+
+	// Render patches
+	gl.patchParameteri(GL_PATCH_VERTICES, 2);
+	gl.drawArrays(GL_PATCHES, 0, 2*alignedValues);
+
+	// Read back data
+	readOutputBuffer(outputs, numValues);
+}
+
+// Utilities
+
+ShaderExecutor* createExecutor (const glu::RenderContext& renderCtx, glu::ShaderType shaderType, const ShaderSpec& shaderSpec)
+{
+	switch (shaderType)
+	{
+		case glu::SHADERTYPE_VERTEX:					return new VertexShaderExecutor		(renderCtx, shaderSpec);
+		case glu::SHADERTYPE_TESSELLATION_CONTROL:		return new TessControlExecutor		(renderCtx, shaderSpec);
+		case glu::SHADERTYPE_TESSELLATION_EVALUATION:	return new TessEvaluationExecutor	(renderCtx, shaderSpec);
+		case glu::SHADERTYPE_GEOMETRY:					return new GeometryShaderExecutor	(renderCtx, shaderSpec);
+		case glu::SHADERTYPE_FRAGMENT:					return new FragmentShaderExecutor	(renderCtx, shaderSpec);
+		case glu::SHADERTYPE_COMPUTE:					return new ComputeShaderExecutor	(renderCtx, shaderSpec);
+		default:
+			throw tcu::InternalError("Unsupported shader type");
+	}
+}
+
+} // ShaderExecUtil
+} // gls
+} // deqp
diff --git a/modules/glshared/glsShaderExecUtil.hpp b/modules/glshared/glsShaderExecUtil.hpp
new file mode 100644
index 0000000..1d3a5f2
--- /dev/null
+++ b/modules/glshared/glsShaderExecUtil.hpp
@@ -0,0 +1,108 @@
+#ifndef _GLSSHADEREXECUTIL_HPP
+#define _GLSSHADEREXECUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader execution utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "gluVarType.hpp"
+#include "gluShaderUtil.hpp"
+
+namespace tcu
+{
+class TestLog;
+}
+
+namespace glu
+{
+class RenderContext;
+}
+
+namespace deqp
+{
+namespace gls
+{
+namespace ShaderExecUtil
+{
+
+//! Shader input / output variable declaration.
+struct Symbol
+{
+	std::string			name;		//!< Symbol name.
+	glu::VarType		varType;	//!< Symbol type.
+
+	Symbol (void) {}
+	Symbol (const std::string& name_, const glu::VarType& varType_) : name(name_), varType(varType_) {}
+};
+
+//! Complete shader specification.
+struct ShaderSpec
+{
+	glu::GLSLVersion		version;				//!< Shader version.
+	std::vector<Symbol>		inputs;
+	std::vector<Symbol>		outputs;
+	std::string				globalDeclarations;		//!< These are placed into global scope. Can contain uniform declarations for example.
+	std::string				source;					//!< Source snippet to be executed.
+
+	ShaderSpec (void) : version(glu::GLSL_VERSION_300_ES) {}
+};
+
+//! Base class for shader executor.
+class ShaderExecutor
+{
+public:
+	virtual						~ShaderExecutor			(void);
+
+	//! Check if executor can be used.
+	virtual bool				isOk					(void) const = 0;
+
+	//! Log executor details (program etc.).
+	virtual void				log						(tcu::TestLog& log) const = 0;
+
+	//! Get program.
+	virtual deUint32			getProgram				(void) const = 0;
+
+	//! Set this shader program current in context. Must be called before execute().
+	virtual void				useProgram				(void);
+
+	//! Execute active program. useProgram() must be called before this.
+	virtual void				execute					(int numValues, const void* const* inputs, void* const* outputs) = 0;
+
+protected:
+								ShaderExecutor			(const glu::RenderContext& renderCtx, const ShaderSpec& shaderSpec);
+
+	const glu::RenderContext&	m_renderCtx;
+
+	std::vector<Symbol>			m_inputs;
+	std::vector<Symbol>			m_outputs;
+};
+
+inline tcu::TestLog& operator<< (tcu::TestLog& log, const ShaderExecutor* executor) { executor->log(log);	return log; }
+inline tcu::TestLog& operator<< (tcu::TestLog& log, const ShaderExecutor& executor) { executor.log(log);	return log; }
+
+ShaderExecutor* createExecutor (const glu::RenderContext& renderCtx, glu::ShaderType shaderType, const ShaderSpec& shaderSpec);
+
+} // ShaderExecUtil
+} // gls
+} // deqp
+
+#endif // _GLSSHADEREXECUTIL_HPP
diff --git a/modules/glshared/glsShaderLibrary.cpp b/modules/glshared/glsShaderLibrary.cpp
new file mode 100644
index 0000000..386b16f
--- /dev/null
+++ b/modules/glshared/glsShaderLibrary.cpp
@@ -0,0 +1,1448 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Compiler test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsShaderLibrary.hpp"
+#include "glsShaderLibraryCase.hpp"
+#include "gluShaderUtil.hpp"
+#include "tcuResource.hpp"
+#include "glwEnums.hpp"
+
+#include "deInt32.h"
+
+#include <string>
+#include <vector>
+#include <fstream>
+#include <sstream>
+
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+using std::string;
+using std::vector;
+using std::ostringstream;
+
+using namespace glu;
+
+#if 0
+#	define PARSE_DBG(X) printf X
+#else
+#	define PARSE_DBG(X) DE_NULL_STATEMENT
+#endif
+
+namespace deqp
+{
+namespace gls
+{
+namespace sl
+{
+
+static const glu::GLSLVersion DEFAULT_GLSL_VERSION = glu::GLSL_VERSION_100_ES;
+
+DE_INLINE deBool isWhitespace (char c)
+{
+	return (c == ' ') || (c == '\t') || (c == '\r') || (c == '\n');
+}
+
+DE_INLINE deBool isEOL (char c)
+{
+	return (c == '\r') || (c == '\n');
+}
+
+DE_INLINE deBool isNumeric (char c)
+{
+	return deInRange32(c, '0', '9');
+}
+
+DE_INLINE deBool isAlpha (char c)
+{
+	return deInRange32(c, 'a', 'z') || deInRange32(c, 'A', 'Z');
+}
+
+DE_INLINE deBool isCaseNameChar (char c)
+{
+	return deInRange32(c, 'a', 'z') || deInRange32(c, 'A', 'Z') || deInRange32(c, '0', '9') || (c == '_') || (c == '-') || (c == '.');
+}
+
+// \todo [2011-02-11 pyry] Should not depend on Context or TestContext!
+class ShaderParser
+{
+public:
+							ShaderParser			(tcu::TestContext& testCtx, RenderContext& renderCtx, const glu::ContextInfo& contextInfo, const char* currentDir = DE_NULL);
+							~ShaderParser			(void);
+
+	vector<tcu::TestNode*>	parse					(const char* input);
+
+private:
+	enum Token
+	{
+		TOKEN_INVALID = 0,
+		TOKEN_EOF,
+		TOKEN_STRING,
+		TOKEN_SHADER_SOURCE,
+
+		TOKEN_INT_LITERAL,
+		TOKEN_FLOAT_LITERAL,
+
+		// identifiers
+		TOKEN_IDENTIFIER,
+		TOKEN_TRUE,
+		TOKEN_FALSE,
+		TOKEN_DESC,
+		TOKEN_EXPECT,
+		TOKEN_GROUP,
+		TOKEN_CASE,
+		TOKEN_END,
+		TOKEN_VALUES,
+		TOKEN_BOTH,
+		TOKEN_VERTEX,
+		TOKEN_FRAGMENT,
+		TOKEN_UNIFORM,
+		TOKEN_INPUT,
+		TOKEN_OUTPUT,
+		TOKEN_FLOAT,
+		TOKEN_FLOAT_VEC2,
+		TOKEN_FLOAT_VEC3,
+		TOKEN_FLOAT_VEC4,
+		TOKEN_FLOAT_MAT2,
+		TOKEN_FLOAT_MAT2X3,
+		TOKEN_FLOAT_MAT2X4,
+		TOKEN_FLOAT_MAT3X2,
+		TOKEN_FLOAT_MAT3,
+		TOKEN_FLOAT_MAT3X4,
+		TOKEN_FLOAT_MAT4X2,
+		TOKEN_FLOAT_MAT4X3,
+		TOKEN_FLOAT_MAT4,
+		TOKEN_INT,
+		TOKEN_INT_VEC2,
+		TOKEN_INT_VEC3,
+		TOKEN_INT_VEC4,
+		TOKEN_UINT,
+		TOKEN_UINT_VEC2,
+		TOKEN_UINT_VEC3,
+		TOKEN_UINT_VEC4,
+		TOKEN_BOOL,
+		TOKEN_BOOL_VEC2,
+		TOKEN_BOOL_VEC3,
+		TOKEN_BOOL_VEC4,
+		TOKEN_VERSION,
+		TOKEN_TESSELLATION_CONTROL,
+		TOKEN_TESSELLATION_EVALUATION,
+		TOKEN_GEOMETRY,
+		TOKEN_REQUIRE,
+		TOKEN_IN,
+		TOKEN_IMPORT,
+		TOKEN_PIPELINE_PROGRAM,
+		TOKEN_ACTIVE_STAGES,
+
+		// symbols
+		TOKEN_ASSIGN,
+		TOKEN_PLUS,
+		TOKEN_MINUS,
+		TOKEN_COMMA,
+		TOKEN_VERTICAL_BAR,
+		TOKEN_SEMI_COLON,
+		TOKEN_LEFT_PAREN,
+		TOKEN_RIGHT_PAREN,
+		TOKEN_LEFT_BRACKET,
+		TOKEN_RIGHT_BRACKET,
+		TOKEN_LEFT_BRACE,
+		TOKEN_RIGHT_BRACE,
+		TOKEN_GREATER,
+
+		TOKEN_LAST
+	};
+
+	void						parseError					(const std::string& errorStr);
+	float						parseFloatLiteral			(const char* str);
+	int							parseIntLiteral				(const char* str);
+	string						parseStringLiteral			(const char* str);
+	string						parseShaderSource			(const char* str);
+	void						advanceToken				(void);
+	void						advanceToken				(Token assumed);
+	void						assumeToken					(Token token);
+	DataType					mapDataTypeToken			(Token token);
+	const char*					getTokenName				(Token token);
+	deUint32					getShaderStageLiteralFlag	(void);
+	deUint32					getGLEnumFromName			(const std::string& enumName);
+
+	void						parseValueElement			(DataType dataType, ShaderCase::Value& result);
+	void						parseValue					(ShaderCase::ValueBlock& valueBlock);
+	void						parseValueBlock				(ShaderCase::ValueBlock& valueBlock);
+	deUint32					parseShaderStageList		(void);
+	void						parseRequirement			(ShaderCase::CaseRequirement& valueBlock);
+	void						parseExpectResult			(ShaderCase::ExpectResult& expectResult);
+	void						parseGLSLVersion			(glu::GLSLVersion& version);
+	void						parsePipelineProgram		(ShaderCase::PipelineProgram& program);
+	void						parseShaderCase				(vector<tcu::TestNode*>& shaderNodeList);
+	void						parseShaderGroup			(vector<tcu::TestNode*>& shaderNodeList);
+	void						parseImport					(vector<tcu::TestNode*>& shaderNodeList);
+
+	// Member variables.
+	tcu::TestContext&			m_testCtx;
+	RenderContext&				m_renderCtx;
+	const glu::ContextInfo&		m_contextInfo;
+	std::string					m_input;
+	const char*					m_curPtr;
+	Token						m_curToken;
+	std::string					m_curTokenStr;
+	const char* const			m_currentDir;
+};
+
+ShaderParser::ShaderParser (tcu::TestContext& testCtx, RenderContext& renderCtx, const glu::ContextInfo& contextInfo, const char* currentDir)
+	: m_testCtx			(testCtx)
+	, m_renderCtx		(renderCtx)
+	, m_contextInfo		(contextInfo)
+	, m_curPtr			(DE_NULL)
+	, m_curToken		(TOKEN_LAST)
+	, m_currentDir		(currentDir)
+{
+}
+
+ShaderParser::~ShaderParser (void)
+{
+	// nada
+}
+
+void ShaderParser::parseError (const std::string& errorStr)
+{
+	string atStr = string(m_curPtr, 80);
+	throw tcu::InternalError((string("Parser error: ") + errorStr + " near '" + atStr + " ...'").c_str(), "", __FILE__, __LINE__);
+}
+
+float ShaderParser::parseFloatLiteral (const char* str)
+{
+	return (float)atof(str);
+}
+
+int ShaderParser::parseIntLiteral (const char* str)
+{
+	return atoi(str);
+}
+
+string ShaderParser::parseStringLiteral (const char* str)
+{
+	const char*		p		= str;
+	char			endChar = *p++;
+	ostringstream	o;
+
+	while (*p != endChar && *p)
+	{
+		if (*p == '\\')
+		{
+			switch (p[1])
+			{
+				case 0:		DE_ASSERT(DE_FALSE);	break;
+				case 'n':	o << '\n';				break;
+				case 't':	o << '\t';				break;
+				default:	o << p[1];				break;
+			}
+
+			p += 2;
+		}
+		else
+			o << *p++;
+	}
+
+	return o.str();
+}
+
+static string removeExtraIndentation (const string& source)
+{
+	// Detect indentation from first line.
+	int numIndentChars = 0;
+	for (int ndx = 0; ndx < (int)source.length() && isWhitespace(source[ndx]); ndx++)
+		numIndentChars += source[ndx] == '\t' ? 4 : 1;
+
+	// Process all lines and remove preceding indentation.
+	ostringstream processed;
+	{
+		bool	atLineStart			= true;
+		int		indentCharsOmitted	= 0;
+
+		for (int pos = 0; pos < (int)source.length(); pos++)
+		{
+			char c = source[pos];
+
+			if (atLineStart && indentCharsOmitted < numIndentChars && (c == ' ' || c == '\t'))
+			{
+				indentCharsOmitted += c == '\t' ? 4 : 1;
+			}
+			else if (isEOL(c))
+			{
+				if (source[pos] == '\r' && source[pos+1] == '\n')
+				{
+					pos += 1;
+					processed << '\n';
+				}
+				else
+					processed << c;
+
+				atLineStart			= true;
+				indentCharsOmitted	= 0;
+			}
+			else
+			{
+				processed << c;
+				atLineStart = false;
+			}
+		}
+	}
+
+	return processed.str();
+}
+
+string ShaderParser::parseShaderSource (const char* str)
+{
+	const char*		p = str+2;
+	ostringstream	o;
+
+	// Eat first empty line from beginning.
+	while (*p == ' ') p++;
+	if (*p == '\n') p++;
+
+	while ((p[0] != '"') || (p[1] != '"'))
+	{
+		if (*p == '\\')
+		{
+			switch (p[1])
+			{
+				case 0:		DE_ASSERT(DE_FALSE);	break;
+				case 'n':	o << '\n';				break;
+				case 't':	o << '\t';				break;
+				default:	o << p[1];				break;
+			}
+
+			p += 2;
+		}
+		else
+			o << *p++;
+	}
+
+	return removeExtraIndentation(o.str());
+}
+
+void ShaderParser::advanceToken (void)
+{
+	// Skip old token.
+	m_curPtr += m_curTokenStr.length();
+
+	// Reset token (for safety).
+	m_curToken		= TOKEN_INVALID;
+	m_curTokenStr	= "";
+
+	// Eat whitespace & comments while they last.
+	for (;;)
+	{
+		while (isWhitespace(*m_curPtr))
+			m_curPtr++;
+
+		// Check for EOL comment.
+		if (*m_curPtr == '#')
+		{
+			while (*m_curPtr && !isEOL(*m_curPtr))
+				m_curPtr++;
+		}
+		else
+			break;
+	}
+
+	if (!*m_curPtr)
+	{
+		m_curToken = TOKEN_EOF;
+		m_curTokenStr = "<EOF>";
+	}
+	else if (isAlpha(*m_curPtr))
+	{
+		struct Named
+		{
+			const char*		str;
+			Token			token;
+		};
+
+		static const Named s_named[] =
+		{
+			{ "true",						TOKEN_TRUE						},
+			{ "false",						TOKEN_FALSE						},
+			{ "desc",						TOKEN_DESC						},
+			{ "expect",						TOKEN_EXPECT					},
+			{ "group",						TOKEN_GROUP						},
+			{ "case",						TOKEN_CASE						},
+			{ "end",						TOKEN_END						},
+			{ "values",						TOKEN_VALUES					},
+			{ "both",						TOKEN_BOTH						},
+			{ "vertex",						TOKEN_VERTEX					},
+			{ "fragment",					TOKEN_FRAGMENT					},
+			{ "uniform",					TOKEN_UNIFORM					},
+			{ "input",						TOKEN_INPUT						},
+			{ "output",						TOKEN_OUTPUT					},
+			{ "float",						TOKEN_FLOAT						},
+			{ "vec2",						TOKEN_FLOAT_VEC2				},
+			{ "vec3",						TOKEN_FLOAT_VEC3				},
+			{ "vec4",						TOKEN_FLOAT_VEC4				},
+			{ "mat2",						TOKEN_FLOAT_MAT2				},
+			{ "mat2x3",						TOKEN_FLOAT_MAT2X3				},
+			{ "mat2x4",						TOKEN_FLOAT_MAT2X4				},
+			{ "mat3x2",						TOKEN_FLOAT_MAT3X2				},
+			{ "mat3",						TOKEN_FLOAT_MAT3				},
+			{ "mat3x4",						TOKEN_FLOAT_MAT3X4				},
+			{ "mat4x2",						TOKEN_FLOAT_MAT4X2				},
+			{ "mat4x3",						TOKEN_FLOAT_MAT4X3				},
+			{ "mat4",						TOKEN_FLOAT_MAT4				},
+			{ "int",						TOKEN_INT						},
+			{ "ivec2",						TOKEN_INT_VEC2					},
+			{ "ivec3",						TOKEN_INT_VEC3					},
+			{ "ivec4",						TOKEN_INT_VEC4					},
+			{ "uint",						TOKEN_UINT						},
+			{ "uvec2",						TOKEN_UINT_VEC2					},
+			{ "uvec3",						TOKEN_UINT_VEC3					},
+			{ "uvec4",						TOKEN_UINT_VEC4					},
+			{ "bool",						TOKEN_BOOL						},
+			{ "bvec2",						TOKEN_BOOL_VEC2					},
+			{ "bvec3",						TOKEN_BOOL_VEC3					},
+			{ "bvec4",						TOKEN_BOOL_VEC4					},
+			{ "version",					TOKEN_VERSION					},
+			{ "tessellation_control",		TOKEN_TESSELLATION_CONTROL		},
+			{ "tessellation_evaluation",	TOKEN_TESSELLATION_EVALUATION	},
+			{ "geometry",					TOKEN_GEOMETRY					},
+			{ "require",					TOKEN_REQUIRE					},
+			{ "in",							TOKEN_IN						},
+			{ "import",						TOKEN_IMPORT					},
+			{ "pipeline_program",			TOKEN_PIPELINE_PROGRAM			},
+			{ "active_stages",				TOKEN_ACTIVE_STAGES				},
+		};
+
+		const char* end = m_curPtr + 1;
+		while (isCaseNameChar(*end))
+			end++;
+		m_curTokenStr = string(m_curPtr, end - m_curPtr);
+
+		m_curToken = TOKEN_IDENTIFIER;
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_named); ndx++)
+		{
+			if (m_curTokenStr == s_named[ndx].str)
+			{
+				m_curToken = s_named[ndx].token;
+				break;
+			}
+		}
+	}
+	else if (isNumeric(*m_curPtr))
+	{
+		/* \todo [2010-03-31 petri] Hex? */
+		const char* p = m_curPtr;
+		while (isNumeric(*p))
+			p++;
+		if (*p == '.')
+		{
+			p++;
+			while (isNumeric(*p))
+				p++;
+
+			if (*p == 'e' || *p == 'E')
+			{
+				p++;
+				if (*p == '+' || *p == '-')
+					p++;
+				DE_ASSERT(isNumeric(*p));
+				while (isNumeric(*p))
+					p++;
+			}
+
+			m_curToken = TOKEN_FLOAT_LITERAL;
+			m_curTokenStr = string(m_curPtr, p - m_curPtr);
+		}
+		else
+		{
+			m_curToken = TOKEN_INT_LITERAL;
+			m_curTokenStr = string(m_curPtr, p - m_curPtr);
+		}
+	}
+	else if (*m_curPtr == '"' && m_curPtr[1] == '"')
+	{
+		const char*	p = m_curPtr + 2;
+
+		while ((p[0] != '"') || (p[1] != '"'))
+		{
+			DE_ASSERT(*p);
+			if (*p == '\\')
+			{
+				DE_ASSERT(p[1] != 0);
+				p += 2;
+			}
+			else
+				p++;
+		}
+		p += 2;
+
+		m_curToken		= TOKEN_SHADER_SOURCE;
+		m_curTokenStr	= string(m_curPtr, (int)(p - m_curPtr));
+	}
+	else if (*m_curPtr == '"' || *m_curPtr == '\'')
+	{
+		char		endChar = *m_curPtr;
+		const char*	p		= m_curPtr + 1;
+
+		while (*p != endChar)
+		{
+			DE_ASSERT(*p);
+			if (*p == '\\')
+			{
+				DE_ASSERT(p[1] != 0);
+				p += 2;
+			}
+			else
+				p++;
+		}
+		p++;
+
+		m_curToken		= TOKEN_STRING;
+		m_curTokenStr	= string(m_curPtr, (int)(p - m_curPtr));
+	}
+	else
+	{
+		struct SimpleToken
+		{
+			const char*		str;
+			Token			token;
+		};
+
+		static const SimpleToken s_simple[] =
+		{
+			{ "=",			TOKEN_ASSIGN		},
+			{ "+",			TOKEN_PLUS			},
+			{ "-",			TOKEN_MINUS			},
+			{ ",",			TOKEN_COMMA			},
+			{ "|",			TOKEN_VERTICAL_BAR	},
+			{ ";",			TOKEN_SEMI_COLON	},
+			{ "(",			TOKEN_LEFT_PAREN	},
+			{ ")",			TOKEN_RIGHT_PAREN	},
+			{ "[",			TOKEN_LEFT_BRACKET	},
+			{ "]",			TOKEN_RIGHT_BRACKET },
+			{ "{",			TOKEN_LEFT_BRACE	},
+			{ "}",			TOKEN_RIGHT_BRACE	},
+			{ ">",			TOKEN_GREATER		},
+		};
+
+		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_simple); ndx++)
+		{
+			if (strncmp(s_simple[ndx].str, m_curPtr, strlen(s_simple[ndx].str)) == 0)
+			{
+				m_curToken		= s_simple[ndx].token;
+				m_curTokenStr	= s_simple[ndx].str;
+				return;
+			}
+		}
+
+		// Otherwise invalid token.
+		m_curToken = TOKEN_INVALID;
+		m_curTokenStr = *m_curPtr;
+	}
+}
+
+void ShaderParser::advanceToken (Token assumed)
+{
+	assumeToken(assumed);
+	advanceToken();
+}
+
+void ShaderParser::assumeToken (Token token)
+{
+	if (m_curToken != token)
+		parseError((string("unexpected token '") + m_curTokenStr + "', expecting '" + getTokenName(token) + "'").c_str());
+	DE_TEST_ASSERT(m_curToken == token);
+}
+
+DataType ShaderParser::mapDataTypeToken (Token token)
+{
+	switch (token)
+	{
+		case TOKEN_FLOAT:			return TYPE_FLOAT;
+		case TOKEN_FLOAT_VEC2:		return TYPE_FLOAT_VEC2;
+		case TOKEN_FLOAT_VEC3:		return TYPE_FLOAT_VEC3;
+		case TOKEN_FLOAT_VEC4:		return TYPE_FLOAT_VEC4;
+		case TOKEN_FLOAT_MAT2:		return TYPE_FLOAT_MAT2;
+		case TOKEN_FLOAT_MAT2X3:	return TYPE_FLOAT_MAT2X3;
+		case TOKEN_FLOAT_MAT2X4:	return TYPE_FLOAT_MAT2X4;
+		case TOKEN_FLOAT_MAT3X2:	return TYPE_FLOAT_MAT3X2;
+		case TOKEN_FLOAT_MAT3:		return TYPE_FLOAT_MAT3;
+		case TOKEN_FLOAT_MAT3X4:	return TYPE_FLOAT_MAT3X4;
+		case TOKEN_FLOAT_MAT4X2:	return TYPE_FLOAT_MAT4X2;
+		case TOKEN_FLOAT_MAT4X3:	return TYPE_FLOAT_MAT4X3;
+		case TOKEN_FLOAT_MAT4:		return TYPE_FLOAT_MAT4;
+		case TOKEN_INT:				return TYPE_INT;
+		case TOKEN_INT_VEC2:		return TYPE_INT_VEC2;
+		case TOKEN_INT_VEC3:		return TYPE_INT_VEC3;
+		case TOKEN_INT_VEC4:		return TYPE_INT_VEC4;
+		case TOKEN_UINT:			return TYPE_UINT;
+		case TOKEN_UINT_VEC2:		return TYPE_UINT_VEC2;
+		case TOKEN_UINT_VEC3:		return TYPE_UINT_VEC3;
+		case TOKEN_UINT_VEC4:		return TYPE_UINT_VEC4;
+		case TOKEN_BOOL:			return TYPE_BOOL;
+		case TOKEN_BOOL_VEC2:		return TYPE_BOOL_VEC2;
+		case TOKEN_BOOL_VEC3:		return TYPE_BOOL_VEC3;
+		case TOKEN_BOOL_VEC4:		return TYPE_BOOL_VEC4;
+		default:					return TYPE_INVALID;
+	}
+}
+
+const char* ShaderParser::getTokenName (Token token)
+{
+	switch (token)
+	{
+		case TOKEN_INVALID:					return "<invalid>";
+		case TOKEN_EOF:						return "<eof>";
+		case TOKEN_STRING:					return "<string>";
+		case TOKEN_SHADER_SOURCE:			return "source";
+
+		case TOKEN_INT_LITERAL:				return "<int>";
+		case TOKEN_FLOAT_LITERAL:			return "<float>";
+
+		// identifiers
+		case TOKEN_IDENTIFIER:				return "<identifier>";
+		case TOKEN_TRUE:					return "true";
+		case TOKEN_FALSE:					return "false";
+		case TOKEN_DESC:					return "desc";
+		case TOKEN_EXPECT:					return "expect";
+		case TOKEN_GROUP:					return "group";
+		case TOKEN_CASE:					return "case";
+		case TOKEN_END:						return "end";
+		case TOKEN_VALUES:					return "values";
+		case TOKEN_BOTH:					return "both";
+		case TOKEN_VERTEX:					return "vertex";
+		case TOKEN_FRAGMENT:				return "fragment";
+		case TOKEN_TESSELLATION_CONTROL:	return "tessellation_control";
+		case TOKEN_TESSELLATION_EVALUATION:	return "tessellation_evaluation";
+		case TOKEN_GEOMETRY:				return "geometry";
+		case TOKEN_REQUIRE:					return "require";
+		case TOKEN_UNIFORM:					return "uniform";
+		case TOKEN_INPUT:					return "input";
+		case TOKEN_OUTPUT:					return "output";
+		case TOKEN_FLOAT:					return "float";
+		case TOKEN_FLOAT_VEC2:				return "vec2";
+		case TOKEN_FLOAT_VEC3:				return "vec3";
+		case TOKEN_FLOAT_VEC4:				return "vec4";
+		case TOKEN_FLOAT_MAT2:				return "mat2";
+		case TOKEN_FLOAT_MAT2X3:			return "mat2x3";
+		case TOKEN_FLOAT_MAT2X4:			return "mat2x4";
+		case TOKEN_FLOAT_MAT3X2:			return "mat3x2";
+		case TOKEN_FLOAT_MAT3:				return "mat3";
+		case TOKEN_FLOAT_MAT3X4:			return "mat3x4";
+		case TOKEN_FLOAT_MAT4X2:			return "mat4x2";
+		case TOKEN_FLOAT_MAT4X3:			return "mat4x3";
+		case TOKEN_FLOAT_MAT4:				return "mat4";
+		case TOKEN_INT:						return "int";
+		case TOKEN_INT_VEC2:				return "ivec2";
+		case TOKEN_INT_VEC3:				return "ivec3";
+		case TOKEN_INT_VEC4:				return "ivec4";
+		case TOKEN_UINT:					return "uint";
+		case TOKEN_UINT_VEC2:				return "uvec2";
+		case TOKEN_UINT_VEC3:				return "uvec3";
+		case TOKEN_UINT_VEC4:				return "uvec4";
+		case TOKEN_BOOL:					return "bool";
+		case TOKEN_BOOL_VEC2:				return "bvec2";
+		case TOKEN_BOOL_VEC3:				return "bvec3";
+		case TOKEN_BOOL_VEC4:				return "bvec4";
+		case TOKEN_IN:						return "in";
+		case TOKEN_IMPORT:					return "import";
+		case TOKEN_PIPELINE_PROGRAM:		return "pipeline_program";
+		case TOKEN_ACTIVE_STAGES:			return "active_stages";
+
+		case TOKEN_ASSIGN:					return "=";
+		case TOKEN_PLUS:					return "+";
+		case TOKEN_MINUS:					return "-";
+		case TOKEN_COMMA:					return ",";
+		case TOKEN_VERTICAL_BAR:			return "|";
+		case TOKEN_SEMI_COLON:				return ";";
+		case TOKEN_LEFT_PAREN:				return "(";
+		case TOKEN_RIGHT_PAREN:				return ")";
+		case TOKEN_LEFT_BRACKET:			return "[";
+		case TOKEN_RIGHT_BRACKET:			return "]";
+		case TOKEN_LEFT_BRACE:				return "{";
+		case TOKEN_RIGHT_BRACE:				return "}";
+		case TOKEN_GREATER:					return ">";
+
+		default:							return "<unknown>";
+	}
+}
+
+deUint32 ShaderParser::getShaderStageLiteralFlag (void)
+{
+	switch (m_curToken)
+	{
+		case TOKEN_VERTEX:					return (1 << glu::SHADERTYPE_VERTEX);
+		case TOKEN_FRAGMENT:				return (1 << glu::SHADERTYPE_FRAGMENT);
+		case TOKEN_GEOMETRY:				return (1 << glu::SHADERTYPE_GEOMETRY);
+		case TOKEN_TESSELLATION_CONTROL:	return (1 << glu::SHADERTYPE_TESSELLATION_CONTROL);
+		case TOKEN_TESSELLATION_EVALUATION:	return (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION);
+
+		default:
+			parseError(std::string() + "invalid shader stage name, got " + m_curTokenStr);
+			return 0;
+	}
+}
+
+deUint32 ShaderParser::getGLEnumFromName (const std::string& enumName)
+{
+	static const struct
+	{
+		const char*	name;
+		deUint32	value;
+	} names[] =
+	{
+		{ "GL_MAX_VERTEX_IMAGE_UNIFORMS",			GL_MAX_VERTEX_IMAGE_UNIFORMS			},
+		{ "GL_MAX_VERTEX_ATOMIC_COUNTERS",			GL_MAX_VERTEX_ATOMIC_COUNTERS			},
+		{ "GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS",	GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS		},
+		{ "GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS",	GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS	},
+	};
+
+	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(names); ++ndx)
+		if (names[ndx].name == enumName)
+			return names[ndx].value;
+
+	parseError(std::string() + "unknown enum name, got " + enumName);
+	return 0;
+}
+
+void ShaderParser::parseValueElement (DataType expectedDataType, ShaderCase::Value& result)
+{
+	DataType	scalarType	= getDataTypeScalarType(expectedDataType);
+	int			scalarSize	= getDataTypeScalarSize(expectedDataType);
+
+	/* \todo [2010-04-19 petri] Support arrays. */
+	ShaderCase::Value::Element elems[16];
+
+	if (scalarSize > 1)
+	{
+		DE_ASSERT(mapDataTypeToken(m_curToken) == expectedDataType);
+		advanceToken(); // data type (float, vec2, etc.)
+		advanceToken(TOKEN_LEFT_PAREN);
+	}
+
+	for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
+	{
+		if (scalarType == TYPE_FLOAT)
+		{
+			float signMult = 1.0f;
+			if (m_curToken == TOKEN_MINUS)
+			{
+				signMult = -1.0f;
+				advanceToken();
+			}
+
+			assumeToken(TOKEN_FLOAT_LITERAL);
+			elems[scalarNdx].float32 = signMult * parseFloatLiteral(m_curTokenStr.c_str());
+			advanceToken(TOKEN_FLOAT_LITERAL);
+		}
+		else if (scalarType == TYPE_INT || scalarType == TYPE_UINT)
+		{
+			int signMult = 1;
+			if (m_curToken == TOKEN_MINUS)
+			{
+				signMult = -1;
+				advanceToken();
+			}
+
+			assumeToken(TOKEN_INT_LITERAL);
+			elems[scalarNdx].int32 = signMult * parseIntLiteral(m_curTokenStr.c_str());
+			advanceToken(TOKEN_INT_LITERAL);
+		}
+		else
+		{
+			DE_ASSERT(scalarType == TYPE_BOOL);
+			elems[scalarNdx].bool32 = (m_curToken == TOKEN_TRUE);
+			if (m_curToken != TOKEN_TRUE && m_curToken != TOKEN_FALSE)
+				parseError(string("unexpected token, expecting bool: " + m_curTokenStr));
+			advanceToken(); // true/false
+		}
+
+		if (scalarNdx != (scalarSize - 1))
+			advanceToken(TOKEN_COMMA);
+	}
+
+	if (scalarSize > 1)
+		advanceToken(TOKEN_RIGHT_PAREN);
+
+	// Store results.
+	for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
+		result.elements.push_back(elems[scalarNdx]);
+}
+
+void ShaderParser::parseValue (ShaderCase::ValueBlock& valueBlock)
+{
+	PARSE_DBG(("      parseValue()\n"));
+
+	// Parsed results.
+	ShaderCase::Value result;
+
+	// Parse storage.
+	if (m_curToken == TOKEN_UNIFORM)
+		result.storageType = ShaderCase::Value::STORAGE_UNIFORM;
+	else if (m_curToken == TOKEN_INPUT)
+		result.storageType = ShaderCase::Value::STORAGE_INPUT;
+	else if (m_curToken == TOKEN_OUTPUT)
+		result.storageType = ShaderCase::Value::STORAGE_OUTPUT;
+	else
+		parseError(string("unexpected token encountered when parsing value classifier"));
+	advanceToken();
+
+	// Parse data type.
+	result.dataType = mapDataTypeToken(m_curToken);
+	if (result.dataType == TYPE_INVALID)
+		parseError(string("unexpected token when parsing value data type: " + m_curTokenStr));
+	advanceToken();
+
+	// Parse value name.
+	if (m_curToken == TOKEN_IDENTIFIER || m_curToken == TOKEN_STRING)
+	{
+		if (m_curToken == TOKEN_IDENTIFIER)
+			result.valueName = m_curTokenStr;
+		else
+			result.valueName = parseStringLiteral(m_curTokenStr.c_str());
+	}
+	else
+		parseError(string("unexpected token when parsing value name: " + m_curTokenStr));
+	advanceToken();
+
+	// Parse assignment operator.
+	advanceToken(TOKEN_ASSIGN);
+
+	// Parse actual value.
+	if (m_curToken == TOKEN_LEFT_BRACKET) // value list
+	{
+		advanceToken(TOKEN_LEFT_BRACKET);
+		result.arrayLength = 0;
+
+		for (;;)
+		{
+			parseValueElement(result.dataType, result);
+			result.arrayLength++;
+
+			if (m_curToken == TOKEN_RIGHT_BRACKET)
+				break;
+			else if (m_curToken == TOKEN_VERTICAL_BAR)
+			{
+				advanceToken();
+				continue;
+			}
+			else
+				parseError(string("unexpected token in value element array: " + m_curTokenStr));
+		}
+
+		advanceToken(TOKEN_RIGHT_BRACKET);
+	}
+	else // arrays, single elements
+	{
+		parseValueElement(result.dataType, result);
+		result.arrayLength = 1;
+	}
+
+	advanceToken(TOKEN_SEMI_COLON); // end of declaration
+
+	valueBlock.values.push_back(result);
+}
+
+void ShaderParser::parseValueBlock (ShaderCase::ValueBlock& valueBlock)
+{
+	PARSE_DBG(("    parseValueBlock()\n"));
+	advanceToken(TOKEN_VALUES);
+	advanceToken(TOKEN_LEFT_BRACE);
+
+	for (;;)
+	{
+		if (m_curToken == TOKEN_UNIFORM || m_curToken == TOKEN_INPUT || m_curToken == TOKEN_OUTPUT)
+			parseValue(valueBlock);
+		else if (m_curToken == TOKEN_RIGHT_BRACE)
+			break;
+		else
+			parseError(string("unexpected token when parsing a value block: " + m_curTokenStr));
+	}
+
+	advanceToken(TOKEN_RIGHT_BRACE);
+
+	// Compute combined array length of value block.
+	int arrayLength = 1;
+	for (int valueNdx = 0; valueNdx < (int)valueBlock.values.size(); valueNdx++)
+	{
+		const ShaderCase::Value& val = valueBlock.values[valueNdx];
+		if (val.arrayLength > 1)
+		{
+			DE_ASSERT(arrayLength == 1 || arrayLength == val.arrayLength);
+			arrayLength = val.arrayLength;
+		}
+	}
+	valueBlock.arrayLength = arrayLength;
+}
+
+deUint32 ShaderParser::parseShaderStageList (void)
+{
+	deUint32 mask = 0;
+
+	assumeToken(TOKEN_LEFT_BRACE);
+
+	// don't allow 0-sized lists
+	advanceToken();
+	mask |= getShaderStageLiteralFlag();
+	advanceToken();
+
+	for (;;)
+	{
+		if (m_curToken == TOKEN_RIGHT_BRACE)
+			break;
+		else if (m_curToken == TOKEN_COMMA)
+		{
+			deUint32 stageFlag;
+			advanceToken();
+
+			stageFlag = getShaderStageLiteralFlag();
+			if (stageFlag & mask)
+				parseError(string("stage already set in the shader stage set: " + m_curTokenStr));
+
+			mask |= stageFlag;
+			advanceToken();
+		}
+		else
+			parseError(string("invalid shader stage set token: " + m_curTokenStr));
+	}
+	advanceToken(TOKEN_RIGHT_BRACE);
+
+	return mask;
+}
+
+void ShaderParser::parseRequirement (ShaderCase::CaseRequirement& valueBlock)
+{
+	PARSE_DBG(("    parseRequirement()\n"));
+
+	advanceToken();
+	assumeToken(TOKEN_IDENTIFIER);
+
+	if (m_curTokenStr == "extension")
+	{
+		std::vector<std::string>	anyExtensionStringList;
+		deUint32					affectedCasesFlags		= -1; // by default all stages
+
+		advanceToken();
+		assumeToken(TOKEN_LEFT_BRACE);
+
+		advanceToken();
+		assumeToken(TOKEN_STRING);
+
+		anyExtensionStringList.push_back(parseStringLiteral(m_curTokenStr.c_str()));
+		advanceToken();
+
+		for (;;)
+		{
+			if (m_curToken == TOKEN_RIGHT_BRACE)
+				break;
+			else if (m_curToken == TOKEN_VERTICAL_BAR)
+			{
+				advanceToken();
+				assumeToken(TOKEN_STRING);
+
+				anyExtensionStringList.push_back(parseStringLiteral(m_curTokenStr.c_str()));
+				advanceToken();
+			}
+			else
+				parseError(string("invalid extension list token: " + m_curTokenStr));
+		}
+		advanceToken(TOKEN_RIGHT_BRACE);
+
+		if (m_curToken == TOKEN_IN)
+		{
+			advanceToken();
+			affectedCasesFlags = parseShaderStageList();
+		}
+
+		valueBlock = ShaderCase::CaseRequirement::createAnyExtensionRequirement(anyExtensionStringList, affectedCasesFlags);
+	}
+	else if (m_curTokenStr == "limit")
+	{
+		deUint32	limitEnum;
+		int			limitValue;
+
+		advanceToken();
+
+		assumeToken(TOKEN_STRING);
+		limitEnum = getGLEnumFromName(parseStringLiteral(m_curTokenStr.c_str()));
+		advanceToken();
+
+		assumeToken(TOKEN_GREATER);
+		advanceToken();
+
+		assumeToken(TOKEN_INT_LITERAL);
+		limitValue = parseIntLiteral(m_curTokenStr.c_str());
+		advanceToken();
+
+		valueBlock = ShaderCase::CaseRequirement::createLimitRequirement(limitEnum, limitValue);
+	}
+	else
+		parseError(string("invalid requirement value: " + m_curTokenStr));
+}
+
+void ShaderParser::parseExpectResult (ShaderCase::ExpectResult& expectResult)
+{
+	assumeToken(TOKEN_IDENTIFIER);
+
+	if (m_curTokenStr == "pass")
+		expectResult = ShaderCase::EXPECT_PASS;
+	else if (m_curTokenStr == "compile_fail")
+		expectResult = ShaderCase::EXPECT_COMPILE_FAIL;
+	else if (m_curTokenStr == "link_fail")
+		expectResult = ShaderCase::EXPECT_LINK_FAIL;
+	else if (m_curTokenStr == "compile_or_link_fail")
+		expectResult = ShaderCase::EXPECT_COMPILE_LINK_FAIL;
+	else if (m_curTokenStr == "validation_fail")
+		expectResult = ShaderCase::EXPECT_VALIDATION_FAIL;
+	else
+		parseError(string("invalid expected result value: " + m_curTokenStr));
+
+	advanceToken();
+}
+
+void ShaderParser::parseGLSLVersion (glu::GLSLVersion& version)
+{
+	int			versionNum		= 0;
+	std::string	postfix			= "";
+
+	assumeToken(TOKEN_INT_LITERAL);
+	versionNum = parseIntLiteral(m_curTokenStr.c_str());
+	advanceToken();
+
+	if (m_curToken == TOKEN_IDENTIFIER)
+	{
+		postfix = m_curTokenStr;
+		advanceToken();
+	}
+
+	if		(versionNum == 100 && postfix == "es")	version = glu::GLSL_VERSION_100_ES;
+	else if (versionNum == 300 && postfix == "es")	version = glu::GLSL_VERSION_300_ES;
+	else if (versionNum == 310 && postfix == "es")	version = glu::GLSL_VERSION_310_ES;
+	else if (versionNum == 130)						version = glu::GLSL_VERSION_130;
+	else if (versionNum == 140)						version = glu::GLSL_VERSION_140;
+	else if (versionNum == 150)						version = glu::GLSL_VERSION_150;
+	else if (versionNum == 330)						version = glu::GLSL_VERSION_330;
+	else if (versionNum == 400)						version = glu::GLSL_VERSION_400;
+	else if (versionNum == 410)						version = glu::GLSL_VERSION_410;
+	else if (versionNum == 420)						version = glu::GLSL_VERSION_420;
+	else if (versionNum == 430)						version = glu::GLSL_VERSION_430;
+	else
+		parseError("Unknown GLSL version");
+}
+
+void ShaderParser::parsePipelineProgram (ShaderCase::PipelineProgram& program)
+{
+	deUint32							activeStages			= 0;
+	vector<string>						vertexSources;
+	vector<string>						fragmentSources;
+	vector<string>						tessellationCtrlSources;
+	vector<string>						tessellationEvalSources;
+	vector<string>						geometrySources;
+	vector<ShaderCase::CaseRequirement>	requirements;
+
+	advanceToken(TOKEN_PIPELINE_PROGRAM);
+
+	for (;;)
+	{
+		if (m_curToken == TOKEN_END)
+			break;
+		else if (m_curToken == TOKEN_ACTIVE_STAGES)
+		{
+			advanceToken();
+			activeStages = parseShaderStageList();
+		}
+		else if (m_curToken == TOKEN_REQUIRE)
+		{
+			ShaderCase::CaseRequirement requirement;
+			parseRequirement(requirement);
+			requirements.push_back(requirement);
+		}
+		else if (m_curToken == TOKEN_VERTEX						||
+				 m_curToken == TOKEN_FRAGMENT					||
+				 m_curToken == TOKEN_TESSELLATION_CONTROL		||
+				 m_curToken == TOKEN_TESSELLATION_EVALUATION	||
+				 m_curToken == TOKEN_GEOMETRY)
+		{
+			const Token	token = m_curToken;
+			string		source;
+
+			advanceToken();
+			assumeToken(TOKEN_SHADER_SOURCE);
+			source = parseShaderSource(m_curTokenStr.c_str());
+			advanceToken();
+
+			switch (token)
+			{
+				case TOKEN_VERTEX:					vertexSources.push_back(source);			break;
+				case TOKEN_FRAGMENT:				fragmentSources.push_back(source);			break;
+				case TOKEN_TESSELLATION_CONTROL:	tessellationCtrlSources.push_back(source);	break;
+				case TOKEN_TESSELLATION_EVALUATION:	tessellationEvalSources.push_back(source);	break;
+				case TOKEN_GEOMETRY:				geometrySources.push_back(source);			break;
+				default:
+					parseError(DE_FALSE);
+			}
+		}
+		else
+			parseError(string("invalid pipeline program value: " + m_curTokenStr));
+	}
+	advanceToken(TOKEN_END);
+
+	if (activeStages == 0)
+		parseError("program pipeline object must have active stages");
+
+	// return pipeline part
+	program.activeStageBits = activeStages;
+	program.requirements.swap(requirements);
+	program.vertexSources.swap(vertexSources);
+	program.fragmentSources.swap(fragmentSources);
+	program.tessCtrlSources.swap(tessellationCtrlSources);
+	program.tessEvalSources.swap(tessellationEvalSources);
+	program.geometrySources.swap(geometrySources);
+}
+
+void ShaderParser::parseShaderCase (vector<tcu::TestNode*>& shaderNodeList)
+{
+	// Parse 'case'.
+	PARSE_DBG(("  parseShaderCase()\n"));
+	advanceToken(TOKEN_CASE);
+
+	// Parse case name.
+	string caseName = m_curTokenStr;
+	advanceToken(); // \note [pyry] All token types are allowed here.
+
+	// Setup case.
+	GLSLVersion							version			= DEFAULT_GLSL_VERSION;
+	ShaderCase::ExpectResult			expectResult	= ShaderCase::EXPECT_PASS;
+	string								description;
+	string								bothSource;
+	vector<string>						vertexSources;
+	vector<string>						fragmentSources;
+	vector<string>						tessellationCtrlSources;
+	vector<string>						tessellationEvalSources;
+	vector<string>						geometrySources;
+	vector<ShaderCase::ValueBlock>		valueBlockList;
+	vector<ShaderCase::CaseRequirement>	requirements;
+	vector<ShaderCase::PipelineProgram>	pipelinePrograms;
+
+	for (;;)
+	{
+		if (m_curToken == TOKEN_END)
+			break;
+		else if (m_curToken == TOKEN_DESC)
+		{
+			advanceToken();
+			assumeToken(TOKEN_STRING);
+			description = parseStringLiteral(m_curTokenStr.c_str());
+			advanceToken();
+		}
+		else if (m_curToken == TOKEN_EXPECT)
+		{
+			advanceToken();
+			parseExpectResult(expectResult);
+		}
+		else if (m_curToken == TOKEN_VALUES)
+		{
+			ShaderCase::ValueBlock block;
+			parseValueBlock(block);
+			valueBlockList.push_back(block);
+		}
+		else if (m_curToken == TOKEN_BOTH						||
+				 m_curToken == TOKEN_VERTEX						||
+				 m_curToken == TOKEN_FRAGMENT					||
+				 m_curToken == TOKEN_TESSELLATION_CONTROL		||
+				 m_curToken == TOKEN_TESSELLATION_EVALUATION	||
+				 m_curToken == TOKEN_GEOMETRY)
+		{
+			const Token	token = m_curToken;
+			string		source;
+
+			advanceToken();
+			assumeToken(TOKEN_SHADER_SOURCE);
+			source = parseShaderSource(m_curTokenStr.c_str());
+			advanceToken();
+
+			switch (token)
+			{
+				case TOKEN_VERTEX:					vertexSources.push_back(source);			break;
+				case TOKEN_FRAGMENT:				fragmentSources.push_back(source);			break;
+				case TOKEN_TESSELLATION_CONTROL:	tessellationCtrlSources.push_back(source);	break;
+				case TOKEN_TESSELLATION_EVALUATION:	tessellationEvalSources.push_back(source);	break;
+				case TOKEN_GEOMETRY:				geometrySources.push_back(source);			break;
+				case TOKEN_BOTH:
+				{
+					if (!bothSource.empty())
+						parseError("multiple 'both' blocks");
+					bothSource = source;
+					break;
+				}
+
+				default:
+					parseError(DE_FALSE);
+			}
+		}
+		else if (m_curToken == TOKEN_VERSION)
+		{
+			advanceToken();
+			parseGLSLVersion(version);
+		}
+		else if (m_curToken == TOKEN_REQUIRE)
+		{
+			ShaderCase::CaseRequirement requirement;
+			parseRequirement(requirement);
+			requirements.push_back(requirement);
+		}
+		else if (m_curToken == TOKEN_PIPELINE_PROGRAM)
+		{
+			ShaderCase::PipelineProgram pipelineProgram;
+			parsePipelineProgram(pipelineProgram);
+			pipelinePrograms.push_back(pipelineProgram);
+		}
+		else
+			parseError(string("unexpected token while parsing shader case: " + m_curTokenStr));
+	}
+
+	advanceToken(TOKEN_END); // case end
+
+	if (!bothSource.empty())
+	{
+		if (!vertexSources.empty()				||
+			!fragmentSources.empty()			||
+			!tessellationCtrlSources.empty()	||
+			!tessellationEvalSources.empty()	||
+			!geometrySources.empty()			||
+			!pipelinePrograms.empty())
+		{
+			parseError("'both' cannot be mixed with other shader stages");
+		}
+
+		// vertex
+		{
+			ShaderCase::ShaderCaseSpecification spec = ShaderCase::ShaderCaseSpecification::generateSharedSourceVertexCase(expectResult, version, valueBlockList, bothSource);
+			spec.requirements = requirements;
+
+			shaderNodeList.push_back(new ShaderCase(m_testCtx, m_renderCtx, m_contextInfo, (caseName + "_vertex").c_str(), description.c_str(), spec));
+		}
+
+		// fragment
+		{
+			ShaderCase::ShaderCaseSpecification spec = ShaderCase::ShaderCaseSpecification::generateSharedSourceFragmentCase(expectResult, version, valueBlockList, bothSource);
+			spec.requirements = requirements;
+
+			shaderNodeList.push_back(new ShaderCase(m_testCtx, m_renderCtx, m_contextInfo, (caseName + "_fragment").c_str(), description.c_str(), spec));
+		}
+	}
+	else if (pipelinePrograms.empty())
+	{
+		ShaderCase::ShaderCaseSpecification spec;
+
+		spec.expectResult	= expectResult;
+		spec.caseType		= ShaderCase::CASETYPE_COMPLETE;
+		spec.targetVersion	= version;
+		spec.requirements.swap(requirements);
+		spec.valueBlocks.swap(valueBlockList);
+		spec.vertexSources.swap(vertexSources);
+		spec.fragmentSources.swap(fragmentSources);
+		spec.tessCtrlSources.swap(tessellationCtrlSources);
+		spec.tessEvalSources.swap(tessellationEvalSources);
+		spec.geometrySources.swap(geometrySources);
+
+		shaderNodeList.push_back(new ShaderCase(m_testCtx, m_renderCtx, m_contextInfo, caseName.c_str(), description.c_str(), spec));
+	}
+	else
+	{
+		if (!vertexSources.empty()				||
+			!fragmentSources.empty()			||
+			!tessellationCtrlSources.empty()	||
+			!tessellationEvalSources.empty()	||
+			!geometrySources.empty())
+		{
+			parseError("pipeline programs cannot be mixed with complete programs");
+		}
+
+		// Pipeline case, multiple programs
+		{
+			ShaderCase::PipelineCaseSpecification spec;
+
+			spec.expectResult	= expectResult;
+			spec.caseType		= ShaderCase::CASETYPE_COMPLETE;
+			spec.targetVersion	= version;
+			spec.valueBlocks.swap(valueBlockList);
+			spec.programs.swap(pipelinePrograms);
+
+			shaderNodeList.push_back(new ShaderCase(m_testCtx, m_renderCtx, m_contextInfo, caseName.c_str(), description.c_str(), spec));
+		}
+	}
+}
+
+void ShaderParser::parseShaderGroup (vector<tcu::TestNode*>& shaderNodeList)
+{
+	// Parse 'case'.
+	PARSE_DBG(("  parseShaderGroup()\n"));
+	advanceToken(TOKEN_GROUP);
+
+	// Parse case name.
+	string name = m_curTokenStr;
+	advanceToken(); // \note [pyry] We don't want to check token type here (for instance to allow "uniform") group.
+
+	// Parse description.
+	assumeToken(TOKEN_STRING);
+	string description = parseStringLiteral(m_curTokenStr.c_str());
+	advanceToken(TOKEN_STRING);
+
+	std::vector<tcu::TestNode*> children;
+
+	// Parse group children.
+	for (;;)
+	{
+		if (m_curToken == TOKEN_END)
+			break;
+		else if (m_curToken == TOKEN_GROUP)
+			parseShaderGroup(children);
+		else if (m_curToken == TOKEN_CASE)
+			parseShaderCase(children);
+		else if (m_curToken == TOKEN_IMPORT)
+			parseImport(children);
+		else
+			parseError(string("unexpected token while parsing shader group: " + m_curTokenStr));
+	}
+
+	advanceToken(TOKEN_END); // group end
+
+	// Create group node.
+	tcu::TestCaseGroup* groupNode = new tcu::TestCaseGroup(m_testCtx, name.c_str(), description.c_str(), children);
+	shaderNodeList.push_back(groupNode);
+}
+
+void ShaderParser::parseImport (vector<tcu::TestNode*>& shaderNodeList)
+{
+	ShaderLibrary			subLibrary		(m_testCtx, m_renderCtx, m_contextInfo);
+	vector<tcu::TestNode*>	importedCases;
+	std::string				filename;
+
+	if (!m_currentDir)
+		parseError(string("cannot use import in inline shader source"));
+
+	advanceToken(TOKEN_IMPORT);
+
+	assumeToken(TOKEN_STRING);
+	filename = m_currentDir + parseStringLiteral(m_curTokenStr.c_str());
+	advanceToken(TOKEN_STRING);
+
+	importedCases = subLibrary.loadShaderFile(filename.c_str());
+	shaderNodeList.insert(shaderNodeList.end(), importedCases.begin(), importedCases.end());
+}
+
+vector<tcu::TestNode*> ShaderParser::parse (const char* input)
+{
+	// Initialize parser.
+	m_input			= input;
+	m_curPtr		= m_input.c_str();
+	m_curToken		= TOKEN_INVALID;
+	m_curTokenStr	= "";
+	advanceToken();
+
+	vector<tcu::TestNode*> nodeList;
+
+	// Parse all cases.
+	PARSE_DBG(("parse()\n"));
+	for (;;)
+	{
+		if (m_curToken == TOKEN_CASE)
+			parseShaderCase(nodeList);
+		else if (m_curToken == TOKEN_GROUP)
+			parseShaderGroup(nodeList);
+		else if (m_curToken == TOKEN_IMPORT)
+			parseImport(nodeList);
+		else if (m_curToken == TOKEN_EOF)
+			break;
+		else
+			parseError(string("invalid token encountered at main level: '") + m_curTokenStr + "'");
+	}
+
+	assumeToken(TOKEN_EOF);
+//	printf("  parsed %d test cases.\n", caseList.size());
+	return nodeList;
+}
+
+} // sl
+
+static std::string getFileDirectory (const std::string& filePath)
+{
+	const std::string::size_type lastDelim = filePath.find_last_of('/');
+
+	if (lastDelim == std::string::npos)
+		return "";
+	else
+		return filePath.substr(0, lastDelim+1);
+}
+
+ShaderLibrary::ShaderLibrary (tcu::TestContext& testCtx, RenderContext& renderCtx, const glu::ContextInfo& contextInfo)
+	: m_testCtx			(testCtx)
+	, m_renderCtx		(renderCtx)
+	, m_contextInfo		(contextInfo)
+{
+}
+
+ShaderLibrary::~ShaderLibrary (void)
+{
+}
+
+vector<tcu::TestNode*> ShaderLibrary::loadShaderFile (const char* fileName)
+{
+	tcu::Resource*		resource		= m_testCtx.getArchive().getResource(fileName);
+	std::string			fileDirectory	= getFileDirectory(fileName);
+	std::vector<char>	buf;
+
+/*	printf("  loading '%s'\n", fileName);*/
+
+	try
+	{
+		int size = resource->getSize();
+		buf.resize(size + 1);
+		resource->read((deUint8*)&buf[0], size);
+		buf[size] = '\0';
+	}
+	catch (const std::exception&)
+	{
+		delete resource;
+		throw;
+	}
+
+	delete resource;
+
+	sl::ShaderParser parser(m_testCtx, m_renderCtx, m_contextInfo, fileDirectory.c_str());
+	vector<tcu::TestNode*> nodes = parser.parse(&buf[0]);
+
+	return nodes;
+}
+
+vector<tcu::TestNode*> ShaderLibrary::parseShader (const char* shaderSource)
+{
+	sl::ShaderParser parser(m_testCtx, m_renderCtx, m_contextInfo);
+	vector<tcu::TestNode*> nodes = parser.parse(shaderSource);
+
+	return nodes;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsShaderLibrary.hpp b/modules/glshared/glsShaderLibrary.hpp
new file mode 100644
index 0000000..3639394
--- /dev/null
+++ b/modules/glshared/glsShaderLibrary.hpp
@@ -0,0 +1,60 @@
+#ifndef _GLSSHADERLIBRARY_HPP
+#define _GLSSHADERLIBRARY_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader case library.
+ *//*--------------------------------------------------------------------*/
+
+#include "gluDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "gluRenderContext.hpp"
+#include "gluContextInfo.hpp"
+
+#include <vector>
+
+namespace deqp
+{
+namespace gls
+{
+
+class ShaderLibrary
+{
+public:
+								ShaderLibrary		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& contextInfo);
+								~ShaderLibrary		(void);
+
+	std::vector<tcu::TestNode*>	loadShaderFile		(const char* fileName);
+	std::vector<tcu::TestNode*>	parseShader			(const char* shaderSource);
+
+private:
+								ShaderLibrary		(const ShaderLibrary&);		// not allowed!
+	ShaderLibrary&				operator=			(const ShaderLibrary&);		// not allowed!
+
+	// Member variables.
+	tcu::TestContext&			m_testCtx;
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_contextInfo;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSSHADERLIBRARY_HPP
diff --git a/modules/glshared/glsShaderLibraryCase.cpp b/modules/glshared/glsShaderLibraryCase.cpp
new file mode 100644
index 0000000..c313058
--- /dev/null
+++ b/modules/glshared/glsShaderLibraryCase.cpp
@@ -0,0 +1,1719 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Compiler test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsShaderLibraryCase.hpp"
+
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "tcuStringTemplate.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluContextInfo.hpp"
+#include "gluStrUtil.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "deRandom.hpp"
+#include "deInt32.h"
+#include "deMath.h"
+#include "deString.h"
+#include "deStringUtil.hpp"
+#include "deSharedPtr.hpp"
+
+#include <map>
+#include <vector>
+#include <string>
+#include <sstream>
+
+using namespace std;
+using namespace tcu;
+using namespace glu;
+
+namespace deqp
+{
+namespace gls
+{
+namespace sl
+{
+
+enum
+{
+	VIEWPORT_WIDTH		= 128,
+	VIEWPORT_HEIGHT		= 128
+};
+
+static inline bool usesShaderInoutQualifiers (glu::GLSLVersion version)
+{
+	switch (version)
+	{
+		case glu::GLSL_VERSION_100_ES:
+		case glu::GLSL_VERSION_130:
+		case glu::GLSL_VERSION_140:
+		case glu::GLSL_VERSION_150:
+			return false;
+
+		default:
+			return true;
+	}
+}
+
+static inline bool supportsFragmentHighp (glu::GLSLVersion version)
+{
+	return version != glu::GLSL_VERSION_100_ES;
+}
+
+ShaderCase::ValueBlock::ValueBlock (void)
+	: arrayLength(0)
+{
+}
+
+ShaderCase::CaseRequirement::CaseRequirement (void)
+	: m_type						(REQUIREMENTTYPE_LAST)
+	, m_supportedExtensionNdx		(-1)
+	, m_effectiveShaderStageFlags	(-1)
+	, m_enumName					(-1)
+	, m_referenceValue				(-1)
+{
+}
+
+ShaderCase::CaseRequirement ShaderCase::CaseRequirement::createAnyExtensionRequirement (const std::vector<std::string>& requirements, deUint32 effectiveShaderStageFlags)
+{
+	CaseRequirement retVal;
+
+	retVal.m_type = REQUIREMENTTYPE_EXTENSION;
+	retVal.m_extensions = requirements;
+	retVal.m_effectiveShaderStageFlags = effectiveShaderStageFlags;
+
+	return retVal;
+}
+
+ShaderCase::CaseRequirement ShaderCase::CaseRequirement::createLimitRequirement (deUint32 enumName, int ref)
+{
+	CaseRequirement retVal;
+
+	retVal.m_type = REQUIREMENTTYPE_IMPLEMENTATION_LIMIT;
+	retVal.m_enumName = enumName;
+	retVal.m_referenceValue = ref;
+
+	return retVal;
+}
+
+void ShaderCase::CaseRequirement::checkRequirements (glu::RenderContext& renderCtx, const glu::ContextInfo& contextInfo)
+{
+	DE_UNREF(renderCtx);
+
+	switch (m_type)
+	{
+		case REQUIREMENTTYPE_EXTENSION:
+		{
+			for (int ndx = 0; ndx < (int)m_extensions.size(); ++ndx)
+			{
+				if (contextInfo.isExtensionSupported(m_extensions[ndx].c_str()))
+				{
+					m_supportedExtensionNdx = ndx;
+					return;
+				}
+			}
+
+			// no extension(s). Make a nice output
+			{
+				std::ostringstream extensionList;
+
+				for (int ndx = 0; ndx < (int)m_extensions.size(); ++ndx)
+				{
+					if (!extensionList.str().empty())
+						extensionList << ", ";
+					extensionList << m_extensions[ndx];
+				}
+
+				if (m_extensions.size() == 1)
+					throw tcu::NotSupportedError("Test requires extension " + extensionList.str());
+				else
+					throw tcu::NotSupportedError("Test requires any extension of " + extensionList.str());
+			}
+
+			// cannot be reached
+		}
+
+		case REQUIREMENTTYPE_IMPLEMENTATION_LIMIT:
+		{
+			const glw::Functions&	gl		= renderCtx.getFunctions();
+			glw::GLint				value	= 0;
+			glw::GLenum				error;
+
+			gl.getIntegerv(m_enumName, &value);
+			error = gl.getError();
+
+			if (error != GL_NO_ERROR)
+				throw tcu::TestError("Query for " + de::toString(glu::getGettableStateStr(m_enumName)) +  " generated " + de::toString(glu::getErrorStr(error)));
+
+			if (!(value > m_referenceValue))
+				throw tcu::NotSupportedError("Test requires " + de::toString(glu::getGettableStateStr(m_enumName)) + " (" + de::toString(value) + ") > " + de::toString(m_referenceValue));
+
+			return;
+		}
+
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+ShaderCase::ShaderCaseSpecification::ShaderCaseSpecification (void)
+	: expectResult	(EXPECT_LAST)
+	, targetVersion	(glu::GLSL_VERSION_LAST)
+	, caseType		(CASETYPE_COMPLETE)
+{
+}
+
+ShaderCase::ShaderCaseSpecification ShaderCase::ShaderCaseSpecification::generateSharedSourceVertexCase (ExpectResult expectResult_, glu::GLSLVersion targetVersion_, const std::vector<ValueBlock>& values, const std::string& sharedSource)
+{
+	ShaderCaseSpecification retVal;
+	retVal.expectResult		= expectResult_;
+	retVal.targetVersion	= targetVersion_;
+	retVal.caseType			= CASETYPE_VERTEX_ONLY;
+	retVal.valueBlocks		= values;
+	retVal.vertexSources.push_back(sharedSource);
+	return retVal;
+}
+
+ShaderCase::ShaderCaseSpecification ShaderCase::ShaderCaseSpecification::generateSharedSourceFragmentCase (ExpectResult expectResult_, glu::GLSLVersion targetVersion_, const std::vector<ValueBlock>& values, const std::string& sharedSource)
+{
+	ShaderCaseSpecification retVal;
+	retVal.expectResult		= expectResult_;
+	retVal.targetVersion	= targetVersion_;
+	retVal.caseType			= CASETYPE_FRAGMENT_ONLY;
+	retVal.valueBlocks		= values;
+	retVal.fragmentSources.push_back(sharedSource);
+	return retVal;
+}
+
+class BeforeDrawValidator : public glu::DrawUtilCallback
+{
+public:
+	enum TargetType
+	{
+		TARGETTYPE_PROGRAM = 0,
+		TARGETTYPE_PIPELINE,
+
+		TARGETTYPE_LAST
+	};
+
+							BeforeDrawValidator	(const glw::Functions& gl, glw::GLuint target, TargetType targetType);
+
+	void					beforeDrawCall		(void);
+
+	const std::string&		getInfoLog			(void) const;
+	glw::GLint				getValidateStatus	(void) const;
+
+private:
+	const glw::Functions&	m_gl;
+	const glw::GLuint		m_target;
+	const TargetType		m_targetType;
+
+	glw::GLint				m_validateStatus;
+	std::string				m_logMessage;
+};
+
+BeforeDrawValidator::BeforeDrawValidator (const glw::Functions& gl, glw::GLuint target, TargetType targetType)
+	: m_gl				(gl)
+	, m_target			(target)
+	, m_targetType		(targetType)
+	, m_validateStatus	(-1)
+{
+	DE_ASSERT(targetType < TARGETTYPE_LAST);
+}
+
+void BeforeDrawValidator::beforeDrawCall (void)
+{
+	glw::GLint					bytesWritten	= 0;
+	glw::GLint					infoLogLength;
+	std::vector<glw::GLchar>	logBuffer;
+	int							stringLength;
+
+	// validate
+	if (m_targetType == TARGETTYPE_PROGRAM)
+		m_gl.validateProgram(m_target);
+	else if (m_targetType == TARGETTYPE_PIPELINE)
+		m_gl.validateProgramPipeline(m_target);
+	else
+		DE_ASSERT(false);
+
+	GLU_EXPECT_NO_ERROR(m_gl.getError(), "validate");
+
+	// check status
+	m_validateStatus = -1;
+
+	if (m_targetType == TARGETTYPE_PROGRAM)
+		m_gl.getProgramiv(m_target, GL_VALIDATE_STATUS, &m_validateStatus);
+	else if (m_targetType == TARGETTYPE_PIPELINE)
+		m_gl.getProgramPipelineiv(m_target, GL_VALIDATE_STATUS, &m_validateStatus);
+	else
+		DE_ASSERT(false);
+
+	GLU_EXPECT_NO_ERROR(m_gl.getError(), "get validate status");
+	TCU_CHECK(m_validateStatus == GL_TRUE || m_validateStatus == GL_FALSE);
+
+	// read log
+
+	infoLogLength = 0;
+
+	if (m_targetType == TARGETTYPE_PROGRAM)
+		m_gl.getProgramiv(m_target, GL_INFO_LOG_LENGTH, &infoLogLength);
+	else if (m_targetType == TARGETTYPE_PIPELINE)
+		m_gl.getProgramPipelineiv(m_target, GL_INFO_LOG_LENGTH, &infoLogLength);
+	else
+		DE_ASSERT(false);
+
+	GLU_EXPECT_NO_ERROR(m_gl.getError(), "get info log length");
+
+	if (infoLogLength <= 0)
+	{
+		m_logMessage.clear();
+		return;
+	}
+
+	logBuffer.resize(infoLogLength + 2, '0'); // +1 for zero terminator (infoLogLength should include it, but better play it safe), +1 to make sure buffer is always larger
+
+	if (m_targetType == TARGETTYPE_PROGRAM)
+		m_gl.getProgramInfoLog(m_target, infoLogLength + 1, &bytesWritten, &logBuffer[0]);
+	else if (m_targetType == TARGETTYPE_PIPELINE)
+		m_gl.getProgramPipelineInfoLog(m_target, infoLogLength + 1, &bytesWritten, &logBuffer[0]);
+	else
+		DE_ASSERT(false);
+
+	// just ignore bytesWritten to be safe, find the null terminator
+	stringLength = (int)(std::find(logBuffer.begin(), logBuffer.end(), '0') - logBuffer.begin());
+	m_logMessage.assign(&logBuffer[0], stringLength);
+}
+
+const std::string& BeforeDrawValidator::getInfoLog (void) const
+{
+	return m_logMessage;
+}
+
+glw::GLint BeforeDrawValidator::getValidateStatus (void) const
+{
+	return m_validateStatus;
+}
+
+// ShaderCase.
+
+ShaderCase::ShaderCase (tcu::TestContext& testCtx, RenderContext& renderCtx, const glu::ContextInfo& contextInfo, const char* name, const char* description, const ShaderCaseSpecification& specification)
+	: tcu::TestCase				(testCtx, name, description)
+	, m_renderCtx				(renderCtx)
+	, m_contextInfo				(contextInfo)
+	, m_caseType				(specification.caseType)
+	, m_expectResult			(specification.expectResult)
+	, m_targetVersion			(specification.targetVersion)
+	, m_separatePrograms		(false)
+	, m_valueBlocks				(specification.valueBlocks)
+{
+	if (m_caseType == CASETYPE_VERTEX_ONLY)
+	{
+		// case generated from "both" target, vertex case
+		DE_ASSERT(specification.vertexSources.size() == 1);
+		DE_ASSERT(specification.fragmentSources.empty());
+		DE_ASSERT(specification.tessCtrlSources.empty());
+		DE_ASSERT(specification.tessEvalSources.empty());
+		DE_ASSERT(specification.geometrySources.empty());
+	}
+	else if (m_caseType == CASETYPE_FRAGMENT_ONLY)
+	{
+		// case generated from "both" target, fragment case
+		DE_ASSERT(specification.vertexSources.empty());
+		DE_ASSERT(specification.fragmentSources.size() == 1);
+		DE_ASSERT(specification.tessCtrlSources.empty());
+		DE_ASSERT(specification.tessEvalSources.empty());
+		DE_ASSERT(specification.geometrySources.empty());
+	}
+
+	// single program object
+	{
+		ProgramObject program;
+		program.spec.requirements		= specification.requirements;
+		program.spec.vertexSources		= specification.vertexSources;
+		program.spec.fragmentSources	= specification.fragmentSources;
+		program.spec.tessCtrlSources	= specification.tessCtrlSources;
+		program.spec.tessEvalSources	= specification.tessEvalSources;
+		program.spec.geometrySources	= specification.geometrySources;
+
+		m_programs.push_back(program);
+	}
+}
+
+ShaderCase::ShaderCase (tcu::TestContext& testCtx, RenderContext& renderCtx, const glu::ContextInfo& contextInfo, const char* name, const char* description, const PipelineCaseSpecification& specification)
+	: tcu::TestCase				(testCtx, name, description)
+	, m_renderCtx				(renderCtx)
+	, m_contextInfo				(contextInfo)
+	, m_caseType				(specification.caseType)
+	, m_expectResult			(specification.expectResult)
+	, m_targetVersion			(specification.targetVersion)
+	, m_separatePrograms		(true)
+	, m_valueBlocks				(specification.valueBlocks)
+{
+	deUint32 totalActiveMask = 0;
+
+	DE_ASSERT(m_caseType == CASETYPE_COMPLETE);
+
+	// validate
+
+	for (int pipelineProgramNdx = 0; pipelineProgramNdx < (int)specification.programs.size(); ++pipelineProgramNdx)
+	{
+		// program with an active stage must contain executable code for that stage
+		DE_ASSERT(((specification.programs[pipelineProgramNdx].activeStageBits & (1 << glu::SHADERTYPE_VERTEX))						== 0) || !specification.programs[pipelineProgramNdx].vertexSources.empty());
+		DE_ASSERT(((specification.programs[pipelineProgramNdx].activeStageBits & (1 << glu::SHADERTYPE_FRAGMENT))					== 0) || !specification.programs[pipelineProgramNdx].fragmentSources.empty());
+		DE_ASSERT(((specification.programs[pipelineProgramNdx].activeStageBits & (1 << glu::SHADERTYPE_TESSELLATION_CONTROL))		== 0) || !specification.programs[pipelineProgramNdx].tessCtrlSources.empty());
+		DE_ASSERT(((specification.programs[pipelineProgramNdx].activeStageBits & (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION))	== 0) || !specification.programs[pipelineProgramNdx].tessEvalSources.empty());
+		DE_ASSERT(((specification.programs[pipelineProgramNdx].activeStageBits & (1 << glu::SHADERTYPE_GEOMETRY))					== 0) || !specification.programs[pipelineProgramNdx].geometrySources.empty());
+
+		// no two programs with with the same stage active
+		DE_ASSERT((totalActiveMask & specification.programs[pipelineProgramNdx].activeStageBits) == 0);
+		totalActiveMask |= specification.programs[pipelineProgramNdx].activeStageBits;
+	}
+
+	// create ProgramObjects
+
+	for (int pipelineProgramNdx = 0; pipelineProgramNdx < (int)specification.programs.size(); ++pipelineProgramNdx)
+	{
+		ProgramObject program;
+		program.spec = specification.programs[pipelineProgramNdx];
+		m_programs.push_back(program);
+	}
+}
+
+ShaderCase::~ShaderCase (void)
+{
+}
+
+void ShaderCase::init (void)
+{
+	// If no value blocks given, use an empty one.
+	if (m_valueBlocks.empty())
+		m_valueBlocks.push_back(ValueBlock());
+
+	// Use first value block to specialize shaders.
+	const ValueBlock& valueBlock = m_valueBlocks[0];
+
+	// \todo [2010-04-01 petri] Check that all value blocks have matching values.
+
+	// prepare programs
+	for (int programNdx = 0; programNdx < (int)m_programs.size(); ++programNdx)
+	{
+		// Check requirements
+		for (int ndx = 0; ndx < (int)m_programs[programNdx].spec.requirements.size(); ++ndx)
+			m_programs[programNdx].spec.requirements[ndx].checkRequirements(m_renderCtx, m_contextInfo);
+
+		// Generate specialized shader sources.
+		if (m_caseType == CASETYPE_COMPLETE)
+		{
+			// all shaders specified separately
+			specializeVertexShaders		(m_programs[programNdx].programSources,	m_programs[programNdx].spec.vertexSources,		valueBlock,	m_programs[programNdx].spec.requirements);
+			specializeFragmentShaders	(m_programs[programNdx].programSources,	m_programs[programNdx].spec.fragmentSources,	valueBlock,	m_programs[programNdx].spec.requirements);
+			specializeGeometryShaders	(m_programs[programNdx].programSources,	m_programs[programNdx].spec.geometrySources,	valueBlock,	m_programs[programNdx].spec.requirements);
+			specializeTessControlShaders(m_programs[programNdx].programSources,	m_programs[programNdx].spec.tessCtrlSources,	valueBlock,	m_programs[programNdx].spec.requirements);
+			specializeTessEvalShaders	(m_programs[programNdx].programSources,	m_programs[programNdx].spec.tessEvalSources,	valueBlock,	m_programs[programNdx].spec.requirements);
+		}
+		else if (m_caseType == CASETYPE_VERTEX_ONLY)
+		{
+			DE_ASSERT(m_programs.size() == 1);
+			DE_ASSERT(!m_separatePrograms);
+
+			// case generated from "both" target, vertex case
+			m_programs[0].programSources << glu::VertexSource(specializeVertexShader(m_programs[0].spec.vertexSources[0].c_str(), valueBlock));
+			m_programs[0].programSources << glu::FragmentSource(genFragmentShader(valueBlock));
+		}
+		else if (m_caseType == CASETYPE_FRAGMENT_ONLY)
+		{
+			DE_ASSERT(m_programs.size() == 1);
+			DE_ASSERT(!m_separatePrograms);
+
+			// case generated from "both" target, fragment case
+			m_programs[0].programSources << glu::VertexSource(genVertexShader(valueBlock));
+			m_programs[0].programSources << glu::FragmentSource(specializeFragmentShader(m_programs[0].spec.fragmentSources[0].c_str(), valueBlock));
+		}
+
+		m_programs[programNdx].programSources << glu::ProgramSeparable(m_separatePrograms);
+	}
+
+	// log the expected result
+	switch (m_expectResult)
+	{
+		case EXPECT_PASS:
+			// Don't write anything
+			break;
+
+		case EXPECT_COMPILE_FAIL:
+			m_testCtx.getLog() << tcu::TestLog::Message << "Expecting shader compilation to fail." << tcu::TestLog::EndMessage;
+			break;
+
+		case EXPECT_LINK_FAIL:
+			m_testCtx.getLog() << tcu::TestLog::Message << "Expecting program linking to fail." << tcu::TestLog::EndMessage;
+			break;
+
+		case EXPECT_COMPILE_LINK_FAIL:
+			m_testCtx.getLog() << tcu::TestLog::Message << "Expecting either shader compilation or program linking to fail." << tcu::TestLog::EndMessage;
+			break;
+
+		case EXPECT_VALIDATION_FAIL:
+			m_testCtx.getLog() << tcu::TestLog::Message << "Expecting program validation to fail." << tcu::TestLog::EndMessage;
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+}
+
+static void setUniformValue (const glw::Functions& gl, const std::vector<deUint32>& pipelinePrograms, const std::string& name, const ShaderCase::Value& val, int arrayNdx, tcu::TestLog& log)
+{
+	bool foundAnyMatch = false;
+
+	for (int programNdx = 0; programNdx < (int)pipelinePrograms.size(); ++programNdx)
+	{
+		const int scalarSize	= getDataTypeScalarSize(val.dataType);
+		const int loc			= gl.getUniformLocation(pipelinePrograms[programNdx], name.c_str());
+		const int elemNdx		= (val.arrayLength == 1) ? (0) : (arrayNdx * scalarSize);
+
+		if (loc == -1)
+			continue;
+
+		foundAnyMatch = true;
+
+		DE_STATIC_ASSERT(sizeof(ShaderCase::Value::Element) == sizeof(glw::GLfloat));
+		DE_STATIC_ASSERT(sizeof(ShaderCase::Value::Element) == sizeof(glw::GLint));
+
+		gl.useProgram(pipelinePrograms[programNdx]);
+
+		switch (val.dataType)
+		{
+			case TYPE_FLOAT:		gl.uniform1fv(loc, 1, &val.elements[elemNdx].float32);						break;
+			case TYPE_FLOAT_VEC2:	gl.uniform2fv(loc, 1, &val.elements[elemNdx].float32);						break;
+			case TYPE_FLOAT_VEC3:	gl.uniform3fv(loc, 1, &val.elements[elemNdx].float32);						break;
+			case TYPE_FLOAT_VEC4:	gl.uniform4fv(loc, 1, &val.elements[elemNdx].float32);						break;
+			case TYPE_FLOAT_MAT2:	gl.uniformMatrix2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);		break;
+			case TYPE_FLOAT_MAT3:	gl.uniformMatrix3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);		break;
+			case TYPE_FLOAT_MAT4:	gl.uniformMatrix4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);		break;
+			case TYPE_INT:			gl.uniform1iv(loc, 1, &val.elements[elemNdx].int32);						break;
+			case TYPE_INT_VEC2:		gl.uniform2iv(loc, 1, &val.elements[elemNdx].int32);						break;
+			case TYPE_INT_VEC3:		gl.uniform3iv(loc, 1, &val.elements[elemNdx].int32);						break;
+			case TYPE_INT_VEC4:		gl.uniform4iv(loc, 1, &val.elements[elemNdx].int32);						break;
+			case TYPE_BOOL:			gl.uniform1iv(loc, 1, &val.elements[elemNdx].int32);						break;
+			case TYPE_BOOL_VEC2:	gl.uniform2iv(loc, 1, &val.elements[elemNdx].int32);						break;
+			case TYPE_BOOL_VEC3:	gl.uniform3iv(loc, 1, &val.elements[elemNdx].int32);						break;
+			case TYPE_BOOL_VEC4:	gl.uniform4iv(loc, 1, &val.elements[elemNdx].int32);						break;
+			case TYPE_UINT:			gl.uniform1uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);		break;
+			case TYPE_UINT_VEC2:	gl.uniform2uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);		break;
+			case TYPE_UINT_VEC3:	gl.uniform3uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);		break;
+			case TYPE_UINT_VEC4:	gl.uniform4uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);		break;
+			case TYPE_FLOAT_MAT2X3:	gl.uniformMatrix2x3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
+			case TYPE_FLOAT_MAT2X4:	gl.uniformMatrix2x4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
+			case TYPE_FLOAT_MAT3X2:	gl.uniformMatrix3x2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
+			case TYPE_FLOAT_MAT3X4:	gl.uniformMatrix3x4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
+			case TYPE_FLOAT_MAT4X2:	gl.uniformMatrix4x2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
+			case TYPE_FLOAT_MAT4X3:	gl.uniformMatrix4x3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
+
+			case TYPE_SAMPLER_2D:
+			case TYPE_SAMPLER_CUBE:
+				DE_ASSERT(!"implement!");
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+
+	if (!foundAnyMatch)
+		log << tcu::TestLog::Message << "WARNING // Uniform \"" << name << "\" location is not valid, location = -1. Cannot set value to the uniform." << tcu::TestLog::EndMessage;
+}
+
+bool ShaderCase::isTessellationPresent (void) const
+{
+	if (m_separatePrograms)
+	{
+		const deUint32 tessellationBits =	(1 << glu::SHADERTYPE_TESSELLATION_CONTROL)		|
+											(1 << glu::SHADERTYPE_TESSELLATION_EVALUATION);
+
+		for (int programNdx = 0; programNdx < (int)m_programs.size(); ++programNdx)
+			if (m_programs[programNdx].spec.activeStageBits & tessellationBits)
+				return true;
+		return false;
+	}
+	else
+		return	!m_programs[0].programSources.sources[glu::SHADERTYPE_TESSELLATION_CONTROL].empty() ||
+				!m_programs[0].programSources.sources[glu::SHADERTYPE_TESSELLATION_EVALUATION].empty();
+}
+
+bool ShaderCase::checkPixels (Surface& surface, int minX, int maxX, int minY, int maxY)
+{
+	TestLog&	log				= m_testCtx.getLog();
+	bool		allWhite		= true;
+	bool		allBlack		= true;
+	bool		anyUnexpected	= false;
+
+	DE_ASSERT((maxX > minX) && (maxY > minY));
+
+	for (int y = minY; y <= maxY; y++)
+	{
+		for (int x = minX; x <= maxX; x++)
+		{
+			RGBA		pixel		 = surface.getPixel(x, y);
+			// Note: we really do not want to involve alpha in the check comparison
+			// \todo [2010-09-22 kalle] Do we know that alpha would be one? If yes, could use color constants white and black.
+			bool		isWhite		 = (pixel.getRed() == 255) && (pixel.getGreen() == 255) && (pixel.getBlue() == 255);
+			bool		isBlack		 = (pixel.getRed() == 0) && (pixel.getGreen() == 0) && (pixel.getBlue() == 0);
+
+			allWhite		= allWhite && isWhite;
+			allBlack		= allBlack && isBlack;
+			anyUnexpected	= anyUnexpected || (!isWhite && !isBlack);
+		}
+	}
+
+	if (!allWhite)
+	{
+		if (anyUnexpected)
+			log << TestLog::Message << "WARNING: expecting all rendered pixels to be white or black, but got other colors as well!" << TestLog::EndMessage;
+		else if (!allBlack)
+			log << TestLog::Message << "WARNING: got inconsistent results over the image, when all pixels should be the same color!" << TestLog::EndMessage;
+
+		return false;
+	}
+	return true;
+}
+
+bool ShaderCase::execute (void)
+{
+	const float										quadSize			= 1.0f;
+	static const float								s_positions[4*4]	=
+	{
+		-quadSize, -quadSize, 0.0f, 1.0f,
+		-quadSize, +quadSize, 0.0f, 1.0f,
+		+quadSize, -quadSize, 0.0f, 1.0f,
+		+quadSize, +quadSize, 0.0f, 1.0f
+	};
+
+	static const deUint16							s_indices[2*3]		=
+	{
+		0, 1, 2,
+		1, 3, 2
+	};
+
+	TestLog&										log					= m_testCtx.getLog();
+	const glw::Functions&							gl					= m_renderCtx.getFunctions();
+
+	// Compute viewport.
+	const tcu::RenderTarget&						renderTarget		= m_renderCtx.getRenderTarget();
+	de::Random										rnd					(deStringHash(getName()));
+	const int										width				= deMin32(renderTarget.getWidth(),	VIEWPORT_WIDTH);
+	const int										height				= deMin32(renderTarget.getHeight(),	VIEWPORT_HEIGHT);
+	const int										viewportX			= rnd.getInt(0, renderTarget.getWidth()  - width);
+	const int										viewportY			= rnd.getInt(0, renderTarget.getHeight() - height);
+	const int										numVerticesPerDraw	= 4;
+	const bool										tessellationPresent	= isTessellationPresent();
+
+	bool											allCompilesOk		= true;
+	bool											allLinksOk			= true;
+	const char*										failReason			= DE_NULL;
+
+	deUint32										vertexProgramID		= -1;
+	std::vector<deUint32>							pipelineProgramIDs;
+	std::vector<de::SharedPtr<glu::ShaderProgram> >	programs;
+	de::SharedPtr<glu::ProgramPipeline>				programPipeline;
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderCase::execute(): start");
+
+	if (!m_separatePrograms)
+	{
+		de::SharedPtr<glu::ShaderProgram>	program		(new glu::ShaderProgram(m_renderCtx, m_programs[0].programSources));
+
+		vertexProgramID = program->getProgram();
+		pipelineProgramIDs.push_back(program->getProgram());
+		programs.push_back(program);
+
+		// Check that compile/link results are what we expect.
+
+		DE_STATIC_ASSERT(glu::SHADERTYPE_VERTEX == 0);
+		for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage)
+			if (program->hasShader((glu::ShaderType)stage) && !program->getShaderInfo((glu::ShaderType)stage).compileOk)
+				allCompilesOk = false;
+
+		if (!program->getProgramInfo().linkOk)
+			allLinksOk = false;
+
+		log << *program;
+	}
+	else
+	{
+		// Separate programs
+		for (int programNdx = 0; programNdx < (int)m_programs.size(); ++programNdx)
+		{
+			de::SharedPtr<glu::ShaderProgram> program(new glu::ShaderProgram(m_renderCtx, m_programs[programNdx].programSources));
+
+			if (m_programs[programNdx].spec.activeStageBits & (1 << glu::SHADERTYPE_VERTEX))
+				vertexProgramID = program->getProgram();
+
+			pipelineProgramIDs.push_back(program->getProgram());
+			programs.push_back(program);
+
+			// Check that compile/link results are what we expect.
+
+			DE_STATIC_ASSERT(glu::SHADERTYPE_VERTEX == 0);
+			for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage)
+				if (program->hasShader((glu::ShaderType)stage) && !program->getShaderInfo((glu::ShaderType)stage).compileOk)
+					allCompilesOk = false;
+
+			if (!program->getProgramInfo().linkOk)
+				allLinksOk = false;
+
+			// Log program and active stages
+			{
+				const tcu::ScopedLogSection	section		(log, "Program", "Program " + de::toString(programNdx+1));
+				tcu::MessageBuilder			builder		(&log);
+				bool						firstStage	= true;
+
+				builder << "Pipeline uses stages: ";
+				for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage)
+				{
+					if (m_programs[programNdx].spec.activeStageBits & (1 << stage))
+					{
+						if (!firstStage)
+							builder << ", ";
+						builder << glu::getShaderTypeName((glu::ShaderType)stage);
+						firstStage = true;
+					}
+				}
+				builder << tcu::TestLog::EndMessage;
+
+				log << *program;
+			}
+		}
+	}
+
+	switch (m_expectResult)
+	{
+		case EXPECT_PASS:
+		case EXPECT_VALIDATION_FAIL:
+			if (!allCompilesOk)
+				failReason = "expected shaders to compile and link properly, but failed to compile.";
+			else if (!allLinksOk)
+				failReason = "expected shaders to compile and link properly, but failed to link.";
+			break;
+
+		case EXPECT_COMPILE_FAIL:
+			if (allCompilesOk && !allLinksOk)
+				failReason = "expected compilation to fail, but shaders compiled and link failed.";
+			else if (allCompilesOk)
+				failReason = "expected compilation to fail, but shaders compiled correctly.";
+			break;
+
+		case EXPECT_LINK_FAIL:
+			if (!allCompilesOk)
+				failReason = "expected linking to fail, but unable to compile.";
+			else if (allLinksOk)
+				failReason = "expected linking to fail, but passed.";
+			break;
+
+		case EXPECT_COMPILE_LINK_FAIL:
+			if (allCompilesOk && allLinksOk)
+				failReason = "expected compile or link to fail, but passed.";
+			break;
+
+		default:
+			DE_ASSERT(false);
+			return false;
+	}
+
+	if (failReason != DE_NULL)
+	{
+		// \todo [2010-06-07 petri] These should be handled in the test case?
+		log << TestLog::Message << "ERROR: " << failReason << TestLog::EndMessage;
+
+		// If implementation parses shader at link time, report it as quality warning.
+		if (m_expectResult == EXPECT_COMPILE_FAIL && allCompilesOk && !allLinksOk)
+			m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, failReason);
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, failReason);
+		return false;
+	}
+
+	// Return if compile/link expected to fail.
+	if (m_expectResult == EXPECT_COMPILE_FAIL		||
+		m_expectResult == EXPECT_COMPILE_LINK_FAIL	||
+		m_expectResult == EXPECT_LINK_FAIL)
+		return (failReason == DE_NULL);
+
+	// Setup viewport.
+	gl.viewport(viewportX, viewportY, width, height);
+
+	if (m_separatePrograms)
+	{
+		programPipeline = de::SharedPtr<glu::ProgramPipeline>(new glu::ProgramPipeline(m_renderCtx));
+
+		// Setup pipeline
+		gl.bindProgramPipeline(programPipeline->getPipeline());
+		for (int programNdx = 0; programNdx < (int)m_programs.size(); ++programNdx)
+		{
+			deUint32 shaderFlags = 0;
+			for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage)
+				if (m_programs[programNdx].spec.activeStageBits & (1 << stage))
+					shaderFlags |= glu::getGLShaderTypeBit((glu::ShaderType)stage);
+
+			programPipeline->useProgramStages(shaderFlags, pipelineProgramIDs[programNdx]);
+		}
+
+		programPipeline->activeShaderProgram(vertexProgramID);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "setup pipeline");
+	}
+	else
+	{
+		// Start using program
+		gl.useProgram(vertexProgramID);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+	}
+
+	// Fetch location for positions positions.
+	int positionLoc = gl.getAttribLocation(vertexProgramID, "dEQP_Position");
+	if (positionLoc == -1)
+	{
+		string errStr = string("no location found for attribute 'dEQP_Position'");
+		TCU_FAIL(errStr.c_str());
+	}
+
+	// Iterate all value blocks.
+	for (int blockNdx = 0; blockNdx < (int)m_valueBlocks.size(); blockNdx++)
+	{
+		const ValueBlock&	valueBlock		= m_valueBlocks[blockNdx];
+
+		// always render at least one pass even if there is no input/output data
+		const int			numRenderPasses	= (valueBlock.arrayLength == 0) ? (1) : (valueBlock.arrayLength);
+
+		// Iterate all array sub-cases.
+		for (int arrayNdx = 0; arrayNdx < numRenderPasses; arrayNdx++)
+		{
+			int							numValues			= (int)valueBlock.values.size();
+			vector<VertexArrayBinding>	vertexArrays;
+			int							attribValueNdx		= 0;
+			vector<vector<float> >		attribValues		(numValues);
+			glw::GLenum					postDrawError;
+			BeforeDrawValidator			beforeDrawValidator	(gl,
+															 (m_separatePrograms) ? (programPipeline->getPipeline())			: (vertexProgramID),
+															 (m_separatePrograms) ? (BeforeDrawValidator::TARGETTYPE_PIPELINE)	: (BeforeDrawValidator::TARGETTYPE_PROGRAM));
+
+			vertexArrays.push_back(va::Float(positionLoc, 4, numVerticesPerDraw, 0, &s_positions[0]));
+
+			// Collect VA pointer for inputs
+			for (int valNdx = 0; valNdx < numValues; valNdx++)
+			{
+				const ShaderCase::Value&	val			= valueBlock.values[valNdx];
+				const char* const			valueName	= val.valueName.c_str();
+				const DataType				dataType	= val.dataType;
+				const int					scalarSize	= getDataTypeScalarSize(val.dataType);
+
+				if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
+				{
+					// Replicate values four times.
+					std::vector<float>& scalars = attribValues[attribValueNdx++];
+					scalars.resize(numVerticesPerDraw * scalarSize);
+					if (isDataTypeFloatOrVec(dataType) || isDataTypeMatrix(dataType))
+					{
+						for (int repNdx = 0; repNdx < numVerticesPerDraw; repNdx++)
+							for (int ndx = 0; ndx < scalarSize; ndx++)
+								scalars[repNdx*scalarSize + ndx] = val.elements[arrayNdx*scalarSize + ndx].float32;
+					}
+					else
+					{
+						// convert to floats.
+						for (int repNdx = 0; repNdx < numVerticesPerDraw; repNdx++)
+						{
+							for (int ndx = 0; ndx < scalarSize; ndx++)
+							{
+								float v = (float)val.elements[arrayNdx*scalarSize + ndx].int32;
+								DE_ASSERT(val.elements[arrayNdx*scalarSize + ndx].int32 == (int)v);
+								scalars[repNdx*scalarSize + ndx] = v;
+							}
+						}
+					}
+
+					// Attribute name prefix.
+					string attribPrefix = "";
+					// \todo [2010-05-27 petri] Should latter condition only apply for vertex cases (or actually non-fragment cases)?
+					if ((m_caseType == CASETYPE_FRAGMENT_ONLY) || (getDataTypeScalarType(dataType) != TYPE_FLOAT))
+						attribPrefix = "a_";
+
+					// Input always given as attribute.
+					string attribName = attribPrefix + valueName;
+					int attribLoc = gl.getAttribLocation(vertexProgramID, attribName.c_str());
+					if (attribLoc == -1)
+					{
+						log << TestLog::Message << "Warning: no location found for attribute '" << attribName << "'" << TestLog::EndMessage;
+						continue;
+					}
+
+					if (isDataTypeMatrix(dataType))
+					{
+						int numCols = getDataTypeMatrixNumColumns(dataType);
+						int numRows = getDataTypeMatrixNumRows(dataType);
+						DE_ASSERT(scalarSize == numCols*numRows);
+
+						for (int i = 0; i < numCols; i++)
+							vertexArrays.push_back(va::Float(attribLoc + i, numRows, numVerticesPerDraw, scalarSize*sizeof(float), &scalars[i * numRows]));
+					}
+					else
+					{
+						DE_ASSERT(isDataTypeFloatOrVec(dataType) || isDataTypeIntOrIVec(dataType) || isDataTypeUintOrUVec(dataType) || isDataTypeBoolOrBVec(dataType));
+						vertexArrays.push_back(va::Float(attribLoc, scalarSize, numVerticesPerDraw, 0, &scalars[0]));
+					}
+
+					GLU_EXPECT_NO_ERROR(gl.getError(), "set vertex attrib array");
+				}
+			}
+
+			GLU_EXPECT_NO_ERROR(gl.getError(), "before set uniforms");
+
+			// set uniform values for outputs (refs).
+			for (int valNdx = 0; valNdx < numValues; valNdx++)
+			{
+				const ShaderCase::Value&	val			= valueBlock.values[valNdx];
+				const char* const			valueName	= val.valueName.c_str();
+
+				if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
+				{
+					// Set reference value.
+					string refName = string("ref_") + valueName;
+					setUniformValue(gl, pipelineProgramIDs, refName, val, arrayNdx, m_testCtx.getLog());
+					GLU_EXPECT_NO_ERROR(gl.getError(), "set reference uniforms");
+				}
+				else if (val.storageType == ShaderCase::Value::STORAGE_UNIFORM)
+				{
+					setUniformValue(gl, pipelineProgramIDs, valueName, val, arrayNdx, m_testCtx.getLog());
+					GLU_EXPECT_NO_ERROR(gl.getError(), "set uniforms");
+				}
+			}
+
+			// Clear.
+			gl.clearColor(0.125f, 0.25f, 0.5f, 1.0f);
+			gl.clear(GL_COLOR_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "clear buffer");
+
+			// Use program or pipeline
+			if (m_separatePrograms)
+				gl.useProgram(0);
+			else
+				gl.useProgram(vertexProgramID);
+
+			// Draw.
+			if (tessellationPresent)
+			{
+				gl.patchParameteri(GL_PATCH_VERTICES, 3);
+				GLU_EXPECT_NO_ERROR(gl.getError(), "set patchParameteri(PATCH_VERTICES, 3)");
+			}
+
+			draw(m_renderCtx,
+				 vertexProgramID,
+				 (int)vertexArrays.size(),
+				 &vertexArrays[0],
+				 (tessellationPresent) ?
+					(pr::Patches(DE_LENGTH_OF_ARRAY(s_indices), &s_indices[0])) :
+					(pr::Triangles(DE_LENGTH_OF_ARRAY(s_indices), &s_indices[0])),
+				 (m_expectResult == EXPECT_VALIDATION_FAIL) ?
+					(&beforeDrawValidator) :
+					(DE_NULL));
+
+			postDrawError = gl.getError();
+
+			if (m_expectResult == EXPECT_PASS)
+			{
+				// Read back results.
+				Surface			surface			(width, height);
+				const float		w				= s_positions[3];
+				const int		minY			= deCeilFloatToInt32 (((-quadSize / w) * 0.5f + 0.5f) * height + 1.0f);
+				const int		maxY			= deFloorFloatToInt32(((+quadSize / w) * 0.5f + 0.5f) * height - 0.5f);
+				const int		minX			= deCeilFloatToInt32 (((-quadSize / w) * 0.5f + 0.5f) * width + 1.0f);
+				const int		maxX			= deFloorFloatToInt32(((+quadSize / w) * 0.5f + 0.5f) * width - 0.5f);
+
+				GLU_EXPECT_NO_ERROR(postDrawError, "draw");
+
+				glu::readPixels(m_renderCtx, viewportX, viewportY, surface.getAccess());
+				GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
+
+				if (!checkPixels(surface, minX, maxX, minY, maxY))
+				{
+					log << TestLog::Message << "INCORRECT RESULT for (value block " << (blockNdx+1) << " of " <<  (int)m_valueBlocks.size()
+											<< ", sub-case " << arrayNdx+1 << " of " << valueBlock.arrayLength << "):"
+						<< TestLog::EndMessage;
+
+					log << TestLog::Message << "Failing shader input/output values:" << TestLog::EndMessage;
+					dumpValues(valueBlock, arrayNdx);
+
+					// Dump image on failure.
+					log << TestLog::Image("Result", "Rendered result image", surface);
+
+					gl.useProgram(0);
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
+					return false;
+				}
+			}
+			else if (m_expectResult == EXPECT_VALIDATION_FAIL)
+			{
+				log	<< TestLog::Message
+					<< "Draw call generated error: "
+					<< glu::getErrorStr(postDrawError) << " "
+					<< ((postDrawError == GL_INVALID_OPERATION) ? ("(expected)") : ("(unexpected)")) << "\n"
+					<< "Validate status: "
+					<< glu::getBooleanStr(beforeDrawValidator.getValidateStatus()) << " "
+					<< ((beforeDrawValidator.getValidateStatus() == GL_FALSE) ? ("(expected)") : ("(unexpected)")) << "\n"
+					<< "Info log: "
+					<< ((beforeDrawValidator.getInfoLog().empty()) ? ("[empty string]") : (beforeDrawValidator.getInfoLog())) << "\n"
+					<< TestLog::EndMessage;
+
+				// test result
+
+				if (postDrawError != GL_NO_ERROR && postDrawError != GL_INVALID_OPERATION)
+				{
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, ("Draw: got unexpected error: " + de::toString(glu::getErrorStr(postDrawError))).c_str());
+					return false;
+				}
+
+				if (beforeDrawValidator.getValidateStatus() == GL_TRUE)
+				{
+					if (postDrawError == GL_NO_ERROR)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "expected validation and rendering to fail but validation and rendering succeeded");
+					else if (postDrawError == GL_INVALID_OPERATION)
+						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "expected validation and rendering to fail but validation succeeded (rendering failed as expected)");
+					else
+						DE_ASSERT(false);
+					return false;
+				}
+				else if (beforeDrawValidator.getValidateStatus() == GL_FALSE && postDrawError == GL_NO_ERROR)
+				{
+					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "expected validation and rendering to fail but rendering succeeded (validation failed as expected)");
+					return false;
+				}
+				else if (beforeDrawValidator.getValidateStatus() == GL_FALSE && postDrawError == GL_INVALID_OPERATION)
+				{
+					// Validation does not depend on input values, no need to test all values
+					return true;
+				}
+				else
+					DE_ASSERT(false);
+			}
+			else
+				DE_ASSERT(false);
+		}
+	}
+
+	gl.useProgram(0);
+	if (m_separatePrograms)
+		gl.bindProgramPipeline(0);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderCase::execute(): end");
+	return true;
+}
+
+TestCase::IterateResult ShaderCase::iterate (void)
+{
+	// Initialize state to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	bool executeOk = execute();
+
+	DE_ASSERT(executeOk ? m_testCtx.getTestResult() == QP_TEST_RESULT_PASS : m_testCtx.getTestResult() != QP_TEST_RESULT_PASS);
+	DE_UNREF(executeOk);
+	return TestCase::STOP;
+}
+
+static void generateExtensionStatements (std::ostringstream& buf, const std::vector<ShaderCase::CaseRequirement>& requirements, glu::ShaderType type)
+{
+	for (int ndx = 0; ndx < (int)requirements.size(); ++ndx)
+		if (requirements[ndx].getType() == ShaderCase::CaseRequirement::REQUIREMENTTYPE_EXTENSION &&
+			(requirements[ndx].getAffectedExtensionStageFlags() & (1 << (deUint32)type)) != 0)
+			buf << "#extension " << requirements[ndx].getSupportedExtension() << " : require\n";
+}
+
+// Injects #extension XXX : require lines after the last preprocessor directive in the shader code. Does not support line continuations
+static std::string injectExtensionRequirements (const std::string& baseCode, glu::ShaderType shaderType, const std::vector<ShaderCase::CaseRequirement>& requirements)
+{
+	std::istringstream	baseCodeBuf(baseCode);
+	std::ostringstream	resultBuf;
+	std::string			line;
+	bool				firstNonPreprocessorLine = true;
+	std::ostringstream	extensions;
+
+	generateExtensionStatements(extensions, requirements, shaderType);
+
+	// skip if no requirements
+	if (extensions.str().empty())
+		return baseCode;
+
+	while (std::getline(baseCodeBuf, line))
+	{
+		// begins with '#'?
+		const std::string::size_type	firstNonWhitespace		= line.find_first_not_of("\t ");
+		const bool						isPreprocessorDirective	= (firstNonWhitespace != std::string::npos && line.at(firstNonWhitespace) == '#');
+
+		// Inject #extensions
+		if (!isPreprocessorDirective && firstNonPreprocessorLine)
+		{
+			firstNonPreprocessorLine = false;
+			resultBuf << extensions.str();
+		}
+
+		resultBuf << line << "\n";
+	}
+
+	return resultBuf.str();
+}
+
+// This functions builds a matching vertex shader for a 'both' case, when
+// the fragment shader is being tested.
+// We need to build attributes and varyings for each 'input'.
+string ShaderCase::genVertexShader (const ValueBlock& valueBlock) const
+{
+	ostringstream	res;
+	const bool		usesInout	= usesShaderInoutQualifiers(m_targetVersion);
+	const char*		vtxIn		= usesInout ? "in"	: "attribute";
+	const char*		vtxOut		= usesInout ? "out"	: "varying";
+
+	res << glu::getGLSLVersionDeclaration(m_targetVersion) << "\n";
+
+	// Declarations (position + attribute/varying for each input).
+	res << "precision highp float;\n";
+	res << "precision highp int;\n";
+	res << "\n";
+	res << vtxIn << " highp vec4 dEQP_Position;\n";
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value& val = valueBlock.values[ndx];
+		if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
+		{
+			DataType	floatType	= getDataTypeFloatScalars(val.dataType);
+			const char*	typeStr		= getDataTypeName(floatType);
+			res << vtxIn << " " << typeStr << " a_" << val.valueName << ";\n";
+
+			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
+				res << vtxOut << " " << typeStr << " " << val.valueName << ";\n";
+			else
+				res << vtxOut << " " << typeStr << " v_" << val.valueName << ";\n";
+		}
+	}
+	res << "\n";
+
+	// Main function.
+	// - gl_Position = dEQP_Position;
+	// - for each input: write attribute directly to varying
+	res << "void main()\n";
+	res << "{\n";
+	res << "	gl_Position = dEQP_Position;\n";
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value& val = valueBlock.values[ndx];
+		if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
+		{
+			const string& name = val.valueName;
+			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
+				res << "	" << name << " = a_" << name << ";\n";
+			else
+				res << "	v_" << name << " = a_" << name << ";\n";
+		}
+	}
+
+	res << "}\n";
+	return res.str();
+}
+
+static void genCompareFunctions (ostringstream& stream, const ShaderCase::ValueBlock& valueBlock, bool useFloatTypes)
+{
+	bool cmpTypeFound[TYPE_LAST];
+	for (int i = 0; i < TYPE_LAST; i++)
+		cmpTypeFound[i] = false;
+
+	for (int valueNdx = 0; valueNdx < (int)valueBlock.values.size(); valueNdx++)
+	{
+		const ShaderCase::Value& val = valueBlock.values[valueNdx];
+		if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
+			cmpTypeFound[(int)val.dataType] = true;
+	}
+
+	if (useFloatTypes)
+	{
+		if (cmpTypeFound[TYPE_BOOL])		stream << "bool isOk (float a, bool b) { return ((a > 0.5) == b); }\n";
+		if (cmpTypeFound[TYPE_BOOL_VEC2])	stream << "bool isOk (vec2 a, bvec2 b) { return (greaterThan(a, vec2(0.5)) == b); }\n";
+		if (cmpTypeFound[TYPE_BOOL_VEC3])	stream << "bool isOk (vec3 a, bvec3 b) { return (greaterThan(a, vec3(0.5)) == b); }\n";
+		if (cmpTypeFound[TYPE_BOOL_VEC4])	stream << "bool isOk (vec4 a, bvec4 b) { return (greaterThan(a, vec4(0.5)) == b); }\n";
+		if (cmpTypeFound[TYPE_INT])			stream << "bool isOk (float a, int b)  { float atemp = a+0.5; return (float(b) <= atemp && atemp <= float(b+1)); }\n";
+		if (cmpTypeFound[TYPE_INT_VEC2])	stream << "bool isOk (vec2 a, ivec2 b) { return (ivec2(floor(a + 0.5)) == b); }\n";
+		if (cmpTypeFound[TYPE_INT_VEC3])	stream << "bool isOk (vec3 a, ivec3 b) { return (ivec3(floor(a + 0.5)) == b); }\n";
+		if (cmpTypeFound[TYPE_INT_VEC4])	stream << "bool isOk (vec4 a, ivec4 b) { return (ivec4(floor(a + 0.5)) == b); }\n";
+		if (cmpTypeFound[TYPE_UINT])		stream << "bool isOk (float a, uint b) { float atemp = a+0.5; return (float(b) <= atemp && atemp <= float(b+1u)); }\n";
+		if (cmpTypeFound[TYPE_UINT_VEC2])	stream << "bool isOk (vec2 a, uvec2 b) { return (uvec2(floor(a + 0.5)) == b); }\n";
+		if (cmpTypeFound[TYPE_UINT_VEC3])	stream << "bool isOk (vec3 a, uvec3 b) { return (uvec3(floor(a + 0.5)) == b); }\n";
+		if (cmpTypeFound[TYPE_UINT_VEC4])	stream << "bool isOk (vec4 a, uvec4 b) { return (uvec4(floor(a + 0.5)) == b); }\n";
+	}
+	else
+	{
+		if (cmpTypeFound[TYPE_BOOL])		stream << "bool isOk (bool a, bool b)   { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_BOOL_VEC2])	stream << "bool isOk (bvec2 a, bvec2 b) { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_BOOL_VEC3])	stream << "bool isOk (bvec3 a, bvec3 b) { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_BOOL_VEC4])	stream << "bool isOk (bvec4 a, bvec4 b) { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_INT])			stream << "bool isOk (int a, int b)     { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_INT_VEC2])	stream << "bool isOk (ivec2 a, ivec2 b) { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_INT_VEC3])	stream << "bool isOk (ivec3 a, ivec3 b) { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_INT_VEC4])	stream << "bool isOk (ivec4 a, ivec4 b) { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_UINT])		stream << "bool isOk (uint a, uint b)   { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_UINT_VEC2])	stream << "bool isOk (uvec2 a, uvec2 b) { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_UINT_VEC3])	stream << "bool isOk (uvec3 a, uvec3 b) { return (a == b); }\n";
+		if (cmpTypeFound[TYPE_UINT_VEC4])	stream << "bool isOk (uvec4 a, uvec4 b) { return (a == b); }\n";
+	}
+
+	if (cmpTypeFound[TYPE_FLOAT])		stream << "bool isOk (float a, float b, float eps) { return (abs(a-b) <= (eps*abs(b) + eps)); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_VEC2])	stream << "bool isOk (vec2 a, vec2 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_VEC3])	stream << "bool isOk (vec3 a, vec3 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_VEC4])	stream << "bool isOk (vec4 a, vec4 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n";
+
+	if (cmpTypeFound[TYPE_FLOAT_MAT2])		stream << "bool isOk (mat2 a, mat2 b, float eps) { vec2 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return all(lessThanEqual(diff, vec2(eps))); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_MAT2X3])	stream << "bool isOk (mat2x3 a, mat2x3 b, float eps) { vec3 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return all(lessThanEqual(diff, vec3(eps))); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_MAT2X4])	stream << "bool isOk (mat2x4 a, mat2x4 b, float eps) { vec4 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return all(lessThanEqual(diff, vec4(eps))); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_MAT3X2])	stream << "bool isOk (mat3x2 a, mat3x2 b, float eps) { vec2 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), abs(a[2]-b[2])); return all(lessThanEqual(diff, vec2(eps))); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_MAT3])		stream << "bool isOk (mat3 a, mat3 b, float eps) { vec3 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), abs(a[2]-b[2])); return all(lessThanEqual(diff, vec3(eps))); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_MAT3X4])	stream << "bool isOk (mat3x4 a, mat3x4 b, float eps) { vec4 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), abs(a[2]-b[2])); return all(lessThanEqual(diff, vec4(eps))); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_MAT4X2])	stream << "bool isOk (mat4x2 a, mat4x2 b, float eps) { vec2 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec2(eps))); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_MAT4X3])	stream << "bool isOk (mat4x3 a, mat4x3 b, float eps) { vec3 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec3(eps))); }\n";
+	if (cmpTypeFound[TYPE_FLOAT_MAT4])		stream << "bool isOk (mat4 a, mat4 b, float eps) { vec4 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec4(eps))); }\n";
+}
+
+static void genCompareOp (ostringstream& output, const char* dstVec4Var, const ShaderCase::ValueBlock& valueBlock, const char* nonFloatNamePrefix, const char* checkVarName)
+{
+	bool isFirstOutput = true;
+
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value&	val			= valueBlock.values[ndx];
+		const char*					valueName	= val.valueName.c_str();
+
+		if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
+		{
+			// Check if we're only interested in one variable (then skip if not the right one).
+			if (checkVarName && !deStringEqual(valueName, checkVarName))
+				continue;
+
+			// Prefix.
+			if (isFirstOutput)
+			{
+				output << "bool RES = ";
+				isFirstOutput = false;
+			}
+			else
+				output << "RES = RES && ";
+
+			// Generate actual comparison.
+			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
+				output << "isOk(" << valueName << ", ref_" << valueName << ", 0.05);\n";
+			else
+				output << "isOk(" << nonFloatNamePrefix << valueName << ", ref_" << valueName << ");\n";
+		}
+		// \note Uniforms are already declared in shader.
+	}
+
+	if (isFirstOutput)
+		output << dstVec4Var << " = vec4(1.0);\n";	// \todo [petri] Should we give warning if not expect-failure case?
+	else
+		output << dstVec4Var << " = vec4(RES, RES, RES, 1.0);\n";
+}
+
+string ShaderCase::genFragmentShader (const ValueBlock& valueBlock) const
+{
+	ostringstream	shader;
+	const bool		usesInout		= usesShaderInoutQualifiers(m_targetVersion);
+	const bool		customColorOut	= usesInout;
+	const char*		fragIn			= usesInout ? "in" : "varying";
+	const char*		prec			= supportsFragmentHighp(m_targetVersion) ? "highp" : "mediump";
+
+	shader << glu::getGLSLVersionDeclaration(m_targetVersion) << "\n";
+
+	shader << "precision " << prec << " float;\n";
+	shader << "precision " << prec << " int;\n";
+	shader << "\n";
+
+	if (customColorOut)
+	{
+		shader << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+		shader << "\n";
+	}
+
+	genCompareFunctions(shader, valueBlock, true);
+	shader << "\n";
+
+	// Declarations (varying, reference for each output).
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value& val = valueBlock.values[ndx];
+		DataType	floatType		= getDataTypeFloatScalars(val.dataType);
+		const char*	floatTypeStr	= getDataTypeName(floatType);
+		const char*	refTypeStr		= getDataTypeName(val.dataType);
+
+		if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
+		{
+			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
+				shader << fragIn << " " << floatTypeStr << " " << val.valueName << ";\n";
+			else
+				shader << fragIn << " " << floatTypeStr << " v_" << val.valueName << ";\n";
+
+			shader << "uniform " << refTypeStr << " ref_" << val.valueName << ";\n";
+		}
+	}
+
+	shader << "\n";
+	shader << "void main()\n";
+	shader << "{\n";
+
+	shader << "	";
+	genCompareOp(shader, customColorOut ? "dEQP_FragColor" : "gl_FragColor", valueBlock, "v_", DE_NULL);
+
+	shader << "}\n";
+	return shader.str();
+}
+
+// Specialize a shader for the vertex shader test case.
+string ShaderCase::specializeVertexShader (const char* src, const ValueBlock& valueBlock) const
+{
+	ostringstream	decl;
+	ostringstream	setup;
+	ostringstream	output;
+	const bool		usesInout	= usesShaderInoutQualifiers(m_targetVersion);
+	const char*		vtxIn		= usesInout ? "in"	: "attribute";
+	const char*		vtxOut		= usesInout ? "out"	: "varying";
+
+	// generated from "both" case
+	DE_ASSERT(m_caseType == CASETYPE_VERTEX_ONLY);
+
+	// Output (write out position).
+	output << "gl_Position = dEQP_Position;\n";
+
+	// Declarations (position + attribute for each input, varying for each output).
+	decl << vtxIn << " highp vec4 dEQP_Position;\n";
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value& val = valueBlock.values[ndx];
+		const char*	valueName		= val.valueName.c_str();
+		DataType	floatType		= getDataTypeFloatScalars(val.dataType);
+		const char*	floatTypeStr	= getDataTypeName(floatType);
+		const char*	refTypeStr		= getDataTypeName(val.dataType);
+
+		if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
+		{
+			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
+			{
+				decl << vtxIn << " " << floatTypeStr << " " << valueName << ";\n";
+			}
+			else
+			{
+				decl << vtxIn << " " << floatTypeStr << " a_" << valueName << ";\n";
+				setup << refTypeStr << " " << valueName << " = " << refTypeStr << "(a_" << valueName << ");\n";
+			}
+		}
+		else if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
+		{
+			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
+				decl << vtxOut << " " << floatTypeStr << " " << valueName << ";\n";
+			else
+			{
+				decl << vtxOut << " " << floatTypeStr << " v_" << valueName << ";\n";
+				decl << refTypeStr << " " << valueName << ";\n";
+
+				output << "v_" << valueName << " = " << floatTypeStr << "(" << valueName << ");\n";
+			}
+		}
+	}
+
+	// Shader specialization.
+	map<string, string> params;
+	params.insert(pair<string, string>("DECLARATIONS", decl.str()));
+	params.insert(pair<string, string>("SETUP", setup.str()));
+	params.insert(pair<string, string>("OUTPUT", output.str()));
+	params.insert(pair<string, string>("POSITION_FRAG_COLOR", "gl_Position"));
+
+	StringTemplate	tmpl	(src);
+	const string	baseSrc	= tmpl.specialize(params);
+	const string	withExt	= injectExtensionRequirements(baseSrc, SHADERTYPE_VERTEX, m_programs[0].spec.requirements);
+
+	return withExt;
+}
+
+// Specialize a shader for the fragment shader test case.
+string ShaderCase::specializeFragmentShader (const char* src, const ValueBlock& valueBlock) const
+{
+	ostringstream	decl;
+	ostringstream	setup;
+	ostringstream	output;
+
+	const bool		usesInout		= usesShaderInoutQualifiers(m_targetVersion);
+	const bool		customColorOut	= usesInout;
+	const char*		fragIn			= usesInout			? "in"				: "varying";
+	const char*		fragColor		= customColorOut	? "dEQP_FragColor"	: "gl_FragColor";
+
+	// generated from "both" case
+	DE_ASSERT(m_caseType == CASETYPE_FRAGMENT_ONLY);
+
+	genCompareFunctions(decl, valueBlock, false);
+	genCompareOp(output, fragColor, valueBlock, "", DE_NULL);
+
+	if (customColorOut)
+		decl << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value&	val				= valueBlock.values[ndx];
+		const char*					valueName		= val.valueName.c_str();
+		DataType					floatType		= getDataTypeFloatScalars(val.dataType);
+		const char*					floatTypeStr	= getDataTypeName(floatType);
+		const char*					refTypeStr		= getDataTypeName(val.dataType);
+
+		if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
+		{
+			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
+				decl << fragIn << " " << floatTypeStr << " " << valueName << ";\n";
+			else
+			{
+				decl << fragIn << " " << floatTypeStr << " v_" << valueName << ";\n";
+				std::string offset = isDataTypeIntOrIVec(val.dataType) ? " * 1.0025" : ""; // \todo [petri] bit of a hack to avoid errors in chop() due to varying interpolation
+				setup << refTypeStr << " " << valueName << " = " << refTypeStr << "(v_" << valueName << offset << ");\n";
+			}
+		}
+		else if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
+		{
+			decl << "uniform " << refTypeStr << " ref_" << valueName << ";\n";
+			decl << refTypeStr << " " << valueName << ";\n";
+		}
+	}
+
+	/* \todo [2010-04-01 petri] Check all outputs. */
+
+	// Shader specialization.
+	map<string, string> params;
+	params.insert(pair<string, string>("DECLARATIONS", decl.str()));
+	params.insert(pair<string, string>("SETUP", setup.str()));
+	params.insert(pair<string, string>("OUTPUT", output.str()));
+	params.insert(pair<string, string>("POSITION_FRAG_COLOR", fragColor));
+
+	StringTemplate	tmpl	(src);
+	const string	baseSrc	= tmpl.specialize(params);
+	const string	withExt	= injectExtensionRequirements(baseSrc, SHADERTYPE_FRAGMENT, m_programs[0].spec.requirements);
+
+	return withExt;
+}
+
+static map<string, string> generateVertexSpecialization (glu::GLSLVersion targetVersion, const ShaderCase::ValueBlock& valueBlock)
+{
+	const bool				usesInout	= usesShaderInoutQualifiers(targetVersion);
+	const char*				vtxIn		= usesInout ? "in" : "attribute";
+	ostringstream			decl;
+	ostringstream			setup;
+	map<string, string>		params;
+
+	decl << vtxIn << " highp vec4 dEQP_Position;\n";
+
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value&	val		= valueBlock.values[ndx];
+		const char*					typeStr	= getDataTypeName(val.dataType);
+
+		if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
+		{
+			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
+			{
+				decl << vtxIn << " " << typeStr << " " << val.valueName << ";\n";
+			}
+			else
+			{
+				DataType	floatType		= getDataTypeFloatScalars(val.dataType);
+				const char*	floatTypeStr	= getDataTypeName(floatType);
+
+				decl << vtxIn << " " << floatTypeStr << " a_" << val.valueName << ";\n";
+				setup << typeStr << " " << val.valueName << " = " << typeStr << "(a_" << val.valueName << ");\n";
+			}
+		}
+		else if (val.storageType == ShaderCase::Value::STORAGE_UNIFORM &&
+					val.valueName.find('.') == string::npos)
+			decl << "uniform " << typeStr << " " << val.valueName << ";\n";
+	}
+
+	params.insert(pair<string, string>("VERTEX_DECLARATIONS",		decl.str()));
+	params.insert(pair<string, string>("VERTEX_SETUP",				setup.str()));
+	params.insert(pair<string, string>("VERTEX_OUTPUT",				string("gl_Position = dEQP_Position;\n")));
+	return params;
+}
+
+static map<string, string> generateFragmentSpecialization (glu::GLSLVersion targetVersion, const ShaderCase::ValueBlock& valueBlock)
+{
+	const bool			usesInout		= usesShaderInoutQualifiers(targetVersion);
+	const bool			customColorOut	= usesInout;
+	const char*			fragColor		= customColorOut ? "dEQP_FragColor"	: "gl_FragColor";
+	ostringstream		decl;
+	ostringstream		output;
+	map<string, string>	params;
+
+	genCompareFunctions(decl, valueBlock, false);
+	genCompareOp(output, fragColor, valueBlock, "", DE_NULL);
+
+	if (customColorOut)
+		decl << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value&	val				= valueBlock.values[ndx];
+		const char*					valueName		= val.valueName.c_str();
+		const char*					refTypeStr		= getDataTypeName(val.dataType);
+
+		if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
+		{
+			decl << "uniform " << refTypeStr << " ref_" << valueName << ";\n";
+			decl << refTypeStr << " " << valueName << ";\n";
+		}
+		else if (val.storageType == ShaderCase::Value::STORAGE_UNIFORM &&
+					val.valueName.find('.') == string::npos)
+		{
+			decl << "uniform " << refTypeStr << " " << valueName << ";\n";
+		}
+	}
+
+	params.insert(pair<string, string>("FRAGMENT_DECLARATIONS",		decl.str()));
+	params.insert(pair<string, string>("FRAGMENT_OUTPUT",			output.str()));
+	params.insert(pair<string, string>("FRAG_COLOR",				fragColor));
+	return params;
+}
+
+static map<string, string> generateGeometrySpecialization (glu::GLSLVersion targetVersion, const ShaderCase::ValueBlock& valueBlock)
+{
+	ostringstream		decl;
+	map<string, string>	params;
+
+	DE_UNREF(targetVersion);
+
+	decl << "layout (triangles) in;\n";
+	decl << "layout (triangle_strip, max_vertices=3) out;\n";
+	decl << "\n";
+
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value&	val				= valueBlock.values[ndx];
+		const char*					valueName		= val.valueName.c_str();
+		const char*					refTypeStr		= getDataTypeName(val.dataType);
+
+		if (val.storageType == ShaderCase::Value::STORAGE_UNIFORM &&
+			val.valueName.find('.') == string::npos)
+		{
+			decl << "uniform " << refTypeStr << " " << valueName << ";\n";
+		}
+	}
+
+	params.insert(pair<string, string>("GEOMETRY_DECLARATIONS",		decl.str()));
+	return params;
+}
+
+static map<string, string> generateTessControlSpecialization (glu::GLSLVersion targetVersion, const ShaderCase::ValueBlock& valueBlock)
+{
+	ostringstream		decl;
+	ostringstream		output;
+	map<string, string>	params;
+
+	DE_UNREF(targetVersion);
+
+	decl << "layout (vertices=3) out;\n";
+	decl << "\n";
+
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value&	val				= valueBlock.values[ndx];
+		const char*					valueName		= val.valueName.c_str();
+		const char*					refTypeStr		= getDataTypeName(val.dataType);
+
+		if (val.storageType == ShaderCase::Value::STORAGE_UNIFORM &&
+			val.valueName.find('.') == string::npos)
+		{
+			decl << "uniform " << refTypeStr << " " << valueName << ";\n";
+		}
+	}
+
+	output <<	"gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
+				"gl_TessLevelInner[0] = 2.0;\n"
+				"gl_TessLevelInner[1] = 2.0;\n"
+				"gl_TessLevelOuter[0] = 2.0;\n"
+				"gl_TessLevelOuter[1] = 2.0;\n"
+				"gl_TessLevelOuter[2] = 2.0;\n"
+				"gl_TessLevelOuter[3] = 2.0;";
+
+	params.insert(pair<string, string>("TESSELLATION_CONTROL_DECLARATIONS",	decl.str()));
+	params.insert(pair<string, string>("TESSELLATION_CONTROL_OUTPUT",		output.str()));
+	return params;
+}
+
+static map<string, string> generateTessEvalSpecialization (glu::GLSLVersion targetVersion, const ShaderCase::ValueBlock& valueBlock)
+{
+	ostringstream		decl;
+	ostringstream		output;
+	map<string, string>	params;
+
+	DE_UNREF(targetVersion);
+
+	decl << "layout (triangles) in;\n";
+	decl << "\n";
+
+	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
+	{
+		const ShaderCase::Value&	val				= valueBlock.values[ndx];
+		const char*					valueName		= val.valueName.c_str();
+		const char*					refTypeStr		= getDataTypeName(val.dataType);
+
+		if (val.storageType == ShaderCase::Value::STORAGE_UNIFORM &&
+			val.valueName.find('.') == string::npos)
+		{
+			decl << "uniform " << refTypeStr << " " << valueName << ";\n";
+		}
+	}
+
+	output <<	"gl_Position = gl_TessCoord[0] * gl_in[0].gl_Position + gl_TessCoord[1] * gl_in[1].gl_Position + gl_TessCoord[2] * gl_in[2].gl_Position;\n";
+
+	params.insert(pair<string, string>("TESSELLATION_EVALUATION_DECLARATIONS",	decl.str()));
+	params.insert(pair<string, string>("TESSELLATION_EVALUATION_OUTPUT",		output.str()));
+	return params;
+}
+
+static void specializeShaders (glu::ProgramSources& dst, glu::ShaderType shaderType, const std::vector<std::string>& sources, const ShaderCase::ValueBlock& valueBlock, glu::GLSLVersion targetVersion, const std::vector<ShaderCase::CaseRequirement>& requirements, std::map<std::string, std::string> (*specializationGenerator)(glu::GLSLVersion, const ShaderCase::ValueBlock&))
+{
+	if (!sources.empty())
+	{
+		const std::map<std::string, std::string> specializationParams = specializationGenerator(targetVersion, valueBlock);
+
+		for (int ndx = 0; ndx < (int)sources.size(); ++ndx)
+		{
+			const StringTemplate	tmpl			(sources[ndx]);
+			const std::string		baseGLSLCode	= tmpl.specialize(specializationParams);
+			const std::string		glslSource		= injectExtensionRequirements(baseGLSLCode, shaderType, requirements);
+
+			dst << glu::ShaderSource(shaderType, glslSource);
+		}
+	}
+}
+
+void ShaderCase::specializeVertexShaders (glu::ProgramSources& dst, const std::vector<std::string>& sources, const ValueBlock& valueBlock, const std::vector<ShaderCase::CaseRequirement>& requirements) const
+{
+	specializeShaders(dst, glu::SHADERTYPE_VERTEX, sources, valueBlock, m_targetVersion, requirements, generateVertexSpecialization);
+}
+
+void ShaderCase::specializeFragmentShaders (glu::ProgramSources& dst, const std::vector<std::string>& sources, const ValueBlock& valueBlock, const std::vector<ShaderCase::CaseRequirement>& requirements) const
+{
+	specializeShaders(dst, glu::SHADERTYPE_FRAGMENT, sources, valueBlock, m_targetVersion, requirements, generateFragmentSpecialization);
+}
+
+void ShaderCase::specializeGeometryShaders (glu::ProgramSources& dst, const std::vector<std::string>& sources, const ValueBlock& valueBlock, const std::vector<ShaderCase::CaseRequirement>& requirements) const
+{
+	specializeShaders(dst, glu::SHADERTYPE_GEOMETRY, sources, valueBlock, m_targetVersion, requirements, generateGeometrySpecialization);
+}
+
+void ShaderCase::specializeTessControlShaders (glu::ProgramSources& dst, const std::vector<std::string>& sources, const ValueBlock& valueBlock, const std::vector<ShaderCase::CaseRequirement>& requirements) const
+{
+	specializeShaders(dst, glu::SHADERTYPE_TESSELLATION_CONTROL, sources, valueBlock, m_targetVersion, requirements, generateTessControlSpecialization);
+}
+
+void ShaderCase::specializeTessEvalShaders (glu::ProgramSources& dst, const std::vector<std::string>& sources, const ValueBlock& valueBlock, const std::vector<ShaderCase::CaseRequirement>& requirements) const
+{
+	specializeShaders(dst, glu::SHADERTYPE_TESSELLATION_EVALUATION, sources, valueBlock, m_targetVersion, requirements, generateTessEvalSpecialization);
+}
+
+void ShaderCase::dumpValues (const ValueBlock& valueBlock, int arrayNdx)
+{
+	int numValues = (int)valueBlock.values.size();
+	for (int valNdx = 0; valNdx < numValues; valNdx++)
+	{
+		const ShaderCase::Value&	val				= valueBlock.values[valNdx];
+		const char*					valueName		= val.valueName.c_str();
+		DataType					dataType		= val.dataType;
+		int							scalarSize		= getDataTypeScalarSize(val.dataType);
+		ostringstream				result;
+
+		result << "    ";
+		if (val.storageType == Value::STORAGE_INPUT)
+			result << "input ";
+		else if (val.storageType == Value::STORAGE_UNIFORM)
+			result << "uniform ";
+		else if (val.storageType == Value::STORAGE_OUTPUT)
+			result << "expected ";
+
+		result << getDataTypeName(dataType) << " " << valueName << ":";
+
+		if (isDataTypeScalar(dataType))
+			result << " ";
+		if (isDataTypeVector(dataType))
+			result << " [ ";
+		else if (isDataTypeMatrix(dataType))
+			result << "\n";
+
+		if (isDataTypeScalarOrVector(dataType))
+		{
+			for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
+			{
+				int						elemNdx	= (val.arrayLength == 1) ? 0 : arrayNdx;
+				const Value::Element&	e		= val.elements[elemNdx*scalarSize + scalarNdx];
+				result << ((scalarNdx != 0) ? ", " : "");
+
+				if (isDataTypeFloatOrVec(dataType))
+					result << e.float32;
+				else if (isDataTypeIntOrIVec(dataType))
+					result << e.int32;
+				else if (isDataTypeUintOrUVec(dataType))
+					result << (deUint32)e.int32;
+				else if (isDataTypeBoolOrBVec(dataType))
+					result << (e.bool32 ? "true" : "false");
+			}
+		}
+		else if (isDataTypeMatrix(dataType))
+		{
+			int numRows = getDataTypeMatrixNumRows(dataType);
+			int numCols = getDataTypeMatrixNumColumns(dataType);
+			for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
+			{
+				result << "       [ ";
+				for (int colNdx = 0; colNdx < numCols; colNdx++)
+				{
+					int		elemNdx = (val.arrayLength == 1) ? 0 : arrayNdx;
+					float	v		= val.elements[elemNdx*scalarSize + rowNdx*numCols + colNdx].float32;
+					result << ((colNdx==0) ? "" : ", ") << v;
+				}
+				result << " ]\n";
+			}
+		}
+
+		if (isDataTypeScalar(dataType))
+			result << "\n";
+		else if (isDataTypeVector(dataType))
+			result << " ]\n";
+
+		m_testCtx.getLog() << TestLog::Message << result.str() << TestLog::EndMessage;
+	}
+}
+
+} // sl
+} // gls
+} // deqp
diff --git a/modules/glshared/glsShaderLibraryCase.hpp b/modules/glshared/glsShaderLibraryCase.hpp
new file mode 100644
index 0000000..1057560
--- /dev/null
+++ b/modules/glshared/glsShaderLibraryCase.hpp
@@ -0,0 +1,234 @@
+#ifndef _GLSSHADERLIBRARYCASE_HPP
+#define _GLSSHADERLIBRARYCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader test case.
+ *//*--------------------------------------------------------------------*/
+
+#include "gluDefs.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuSurface.hpp"
+
+#include <string>
+#include <vector>
+
+namespace deqp
+{
+namespace gls
+{
+namespace sl
+{
+
+// ShaderCase node.
+
+class ShaderCase : public tcu::TestCase
+{
+public:
+	enum CaseType
+	{
+		CASETYPE_COMPLETE = 0,		//!< Has all shaders specified separately.
+		CASETYPE_VERTEX_ONLY,		//!< "Both" case, vertex shader sub case.
+		CASETYPE_FRAGMENT_ONLY,		//!< "Both" case, fragment shader sub case.
+
+		CASETYPE_LAST
+	};
+
+	enum ExpectResult
+	{
+		EXPECT_PASS = 0,
+		EXPECT_COMPILE_FAIL,
+		EXPECT_LINK_FAIL,
+		EXPECT_COMPILE_LINK_FAIL,
+		EXPECT_VALIDATION_FAIL,
+
+		EXPECT_LAST
+	};
+
+	struct Value
+	{
+		enum StorageType
+		{
+			STORAGE_UNIFORM,
+			STORAGE_INPUT,
+			STORAGE_OUTPUT,
+
+			STORAGE_LAST
+		};
+
+		/* \todo [2010-03-31 petri] Replace with another vector to allow a) arrays, b) compact representation */
+		union Element
+		{
+			float		float32;
+			deInt32		int32;
+			deInt32		bool32;
+		};
+
+		StorageType				storageType;
+		std::string				valueName;
+		glu::DataType			dataType;
+		int						arrayLength;	// Number of elements in array (currently always 1).
+		std::vector<Element>	elements;		// Scalar values (length dataType.scalarSize * arrayLength).
+	};
+
+	struct ValueBlock
+	{
+							ValueBlock (void);
+
+		int					arrayLength;		// Combined array length of each value (lengths must be same, or one).
+		std::vector<Value>	values;
+	};
+
+	class CaseRequirement
+	{
+	public:
+		enum RequirementType
+		{
+			REQUIREMENTTYPE_EXTENSION = 0,
+			REQUIREMENTTYPE_IMPLEMENTATION_LIMIT,
+
+			REQUIREMENTTYPE_LAST
+		};
+
+									CaseRequirement					(void);
+
+		static CaseRequirement		createAnyExtensionRequirement	(const std::vector<std::string>& requirements, deUint32 effectiveShaderStageFlags);
+		static CaseRequirement		createLimitRequirement			(deUint32 enumName, int ref);
+		void						checkRequirements				(glu::RenderContext& renderCtx, const glu::ContextInfo& contextInfo);
+
+		RequirementType				getType							(void) const { return m_type; };
+		std::string					getSupportedExtension			(void) const { DE_ASSERT(m_type == REQUIREMENTTYPE_EXTENSION); DE_ASSERT(m_supportedExtensionNdx >= 0); return m_extensions[m_supportedExtensionNdx]; }
+		deUint32					getAffectedExtensionStageFlags	(void) const { DE_ASSERT(m_type == REQUIREMENTTYPE_EXTENSION); return m_effectiveShaderStageFlags; }
+
+	private:
+		RequirementType				m_type;
+
+		// REQUIREMENTTYPE_EXTENSION:
+		std::vector<std::string>	m_extensions;
+		int							m_supportedExtensionNdx;
+		deUint32					m_effectiveShaderStageFlags;
+
+		// REQUIREMENTTYPE_IMPLEMENTATION_LIMIT:
+		deUint32					m_enumName;
+		int							m_referenceValue;
+	};
+
+	struct ShaderCaseSpecification
+	{
+										ShaderCaseSpecification				(void);
+
+		static ShaderCaseSpecification	generateSharedSourceVertexCase		(ExpectResult expectResult_, glu::GLSLVersion targetVersion_, const std::vector<ValueBlock>& values, const std::string& sharedSource);
+		static ShaderCaseSpecification	generateSharedSourceFragmentCase	(ExpectResult expectResult_, glu::GLSLVersion targetVersion_, const std::vector<ValueBlock>& values, const std::string& sharedSource);
+
+		ExpectResult					expectResult;
+		glu::GLSLVersion				targetVersion;
+		CaseType						caseType;
+		std::vector<CaseRequirement>	requirements;
+		std::vector<ValueBlock>			valueBlocks;
+		std::vector<std::string>		vertexSources;
+		std::vector<std::string>		fragmentSources;
+		std::vector<std::string>		tessCtrlSources;
+		std::vector<std::string>		tessEvalSources;
+		std::vector<std::string>		geometrySources;
+	};
+
+	struct PipelineProgram
+	{
+		deUint32						activeStageBits;
+		std::vector<CaseRequirement>	requirements;
+		std::vector<std::string>		vertexSources;
+		std::vector<std::string>		fragmentSources;
+		std::vector<std::string>		tessCtrlSources;
+		std::vector<std::string>		tessEvalSources;
+		std::vector<std::string>		geometrySources;
+	};
+
+	struct PipelineCaseSpecification
+	{
+		ExpectResult					expectResult;
+		glu::GLSLVersion				targetVersion;
+		CaseType						caseType;
+		std::vector<ValueBlock>			valueBlocks;
+		std::vector<PipelineProgram>	programs;
+	};
+
+	// Methods.
+									ShaderCase						(tcu::TestContext&				testCtx,
+																	 glu::RenderContext&			renderCtx,
+																	 const glu::ContextInfo&		contextInfo,
+																	 const char*					caseName,
+																	 const char*					description,
+																	 const ShaderCaseSpecification&	specification);
+									ShaderCase						(tcu::TestContext&					testCtx,
+																	 glu::RenderContext&				renderCtx,
+																	 const glu::ContextInfo&			contextInfo,
+																	 const char*						caseName,
+																	 const char*						description,
+																	 const PipelineCaseSpecification&	specification);
+	virtual							~ShaderCase						(void);
+
+private:
+	void							init							(void);
+	bool							execute							(void);
+	IterateResult					iterate							(void);
+
+									ShaderCase						(const ShaderCase&);		// not allowed!
+	ShaderCase&						operator=						(const ShaderCase&);		// not allowed!
+
+	std::string						genVertexShader					(const ValueBlock& valueBlock) const;
+	std::string						genFragmentShader				(const ValueBlock& valueBlock) const;
+	std::string						specializeVertexShader			(const char* src, const ValueBlock& valueBlock) const;
+	std::string						specializeFragmentShader		(const char* src, const ValueBlock& valueBlock) const;
+	void							specializeVertexShaders			(glu::ProgramSources& dst, const std::vector<std::string>& sources, const ValueBlock& valueBlock, const std::vector<ShaderCase::CaseRequirement>& requirements) const;
+	void							specializeFragmentShaders		(glu::ProgramSources& dst, const std::vector<std::string>& sources, const ValueBlock& valueBlock, const std::vector<ShaderCase::CaseRequirement>& requirements) const;
+	void							specializeGeometryShaders		(glu::ProgramSources& dst, const std::vector<std::string>& sources, const ValueBlock& valueBlock, const std::vector<ShaderCase::CaseRequirement>& requirements) const;
+	void							specializeTessControlShaders	(glu::ProgramSources& dst, const std::vector<std::string>& sources, const ValueBlock& valueBlock, const std::vector<ShaderCase::CaseRequirement>& requirements) const;
+	void							specializeTessEvalShaders		(glu::ProgramSources& dst, const std::vector<std::string>& sources, const ValueBlock& valueBlock, const std::vector<ShaderCase::CaseRequirement>& requirements) const;
+	bool							isTessellationPresent			(void) const;
+
+	void							dumpValues						(const ValueBlock& valueBlock, int arrayNdx);
+
+	bool 							checkPixels						(tcu::Surface& surface, int minX, int maxX, int minY, int maxY);
+
+	struct ProgramObject
+	{
+		glu::ProgramSources		programSources;
+		PipelineProgram			spec;
+	};
+
+	// Member variables.
+	glu::RenderContext&				m_renderCtx;
+	const glu::ContextInfo&			m_contextInfo;
+	const CaseType					m_caseType;
+	const ExpectResult				m_expectResult;
+	const glu::GLSLVersion			m_targetVersion;
+	const bool						m_separatePrograms;
+	std::vector<ValueBlock>			m_valueBlocks;
+	std::vector<ProgramObject>		m_programs;
+};
+
+} // sl
+} // gls
+} // deqp
+
+#endif // _GLSSHADERLIBRARYCASE_HPP
diff --git a/modules/glshared/glsShaderPerformanceCase.cpp b/modules/glshared/glsShaderPerformanceCase.cpp
new file mode 100644
index 0000000..77cdc5a
--- /dev/null
+++ b/modules/glshared/glsShaderPerformanceCase.cpp
@@ -0,0 +1,233 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Single-program test case wrapper for ShaderPerformanceMeasurer.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsShaderPerformanceCase.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+using tcu::Vec4;
+using tcu::TestLog;
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gls
+{
+
+ShaderPerformanceCase::ShaderPerformanceCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, PerfCaseType caseType)
+	: tcu::TestCase		(testCtx, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_caseType		(caseType)
+	, m_program			(DE_NULL)
+	, m_measurer		(renderCtx, caseType)
+{
+}
+
+ShaderPerformanceCase::~ShaderPerformanceCase (void)
+{
+	ShaderPerformanceCase::deinit();
+}
+
+void ShaderPerformanceCase::setGridSize (int gridW, int gridH)
+{
+	m_measurer.setGridSize(gridW, gridH);
+}
+
+void ShaderPerformanceCase::setViewportSize (int width, int height)
+{
+	m_measurer.setViewportSize(width, height);
+}
+
+void ShaderPerformanceCase::setVertexFragmentRatio (float fragmentsPerVertices)
+{
+	const float	eps			= 0.01f;
+	int			gridW		= 255;
+	int			gridH		= 255;
+	int			viewportW	= m_renderCtx.getRenderTarget().getWidth();
+	int			viewportH	= m_renderCtx.getRenderTarget().getHeight();
+
+	for (int i = 0; i < 10; i++)
+	{
+		int		numVert	= (gridW+1)*(gridH+1);
+		int		numFrag	= viewportW*viewportH;
+		float	ratio	= (float)numFrag / (float)numVert;
+
+		if (de::abs(ratio - fragmentsPerVertices) < eps)
+			break;
+		else if (ratio < fragmentsPerVertices)
+		{
+			// Not enough fragments.
+			numVert = deRoundFloatToInt32((float)numFrag / fragmentsPerVertices);
+
+			while ((gridW+1)*(gridH+1) > numVert)
+			{
+				if (gridW > gridH)
+					gridW -= 1;
+				else
+					gridH -= 1;
+			}
+		}
+		else
+		{
+			// Not enough vertices.
+			numFrag = deRoundFloatToInt32((float)numVert * fragmentsPerVertices);
+
+			while (viewportW*viewportH > numFrag)
+			{
+				if (viewportW > viewportH)
+					viewportW -= 1;
+				else
+					viewportH -= 1;
+			}
+		}
+	}
+
+	float finalRatio = (float)(viewportW*viewportH) / (float)((gridW+1)*(gridH+1));
+	m_testCtx.getLog() << TestLog::Message << "Requested fragment/vertex-ratio: " << de::floatToString(fragmentsPerVertices, 2) << "\n"
+										   << "Computed fragment/vertex-ratio: " << de::floatToString(finalRatio, 2)
+					   << TestLog::EndMessage;
+
+	setGridSize(gridW, gridH);
+	setViewportSize(viewportW, viewportH);
+}
+
+static void logRenderTargetInfo (TestLog& log, const tcu::RenderTarget& renderTarget)
+{
+	log << TestLog::Section("RenderTarget", "Render target")
+		<< TestLog::Message << "size: " << renderTarget.getWidth() << "x" << renderTarget.getHeight() << TestLog::EndMessage
+		<< TestLog::Message << "bits:"
+							<< " R" << renderTarget.getPixelFormat().redBits
+							<< " G" << renderTarget.getPixelFormat().greenBits
+							<< " B" << renderTarget.getPixelFormat().blueBits
+							<< " A" << renderTarget.getPixelFormat().alphaBits
+							<< " D" << renderTarget.getDepthBits()
+							<< " S" << renderTarget.getStencilBits()
+							<< TestLog::EndMessage;
+
+	if (renderTarget.getNumSamples() != 0)
+		log << TestLog::Message << renderTarget.getNumSamples() << "x MSAA" << TestLog::EndMessage;
+	else
+		log << TestLog::Message << "No MSAA" << TestLog::EndMessage;
+
+	log << TestLog::EndSection;
+}
+
+void ShaderPerformanceCase::init (void)
+{
+	tcu::TestLog& log = m_testCtx.getLog();
+
+	m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(m_vertShaderSource, m_fragShaderSource));
+
+	if (m_program->isOk())
+	{
+		const int initialCallCount = m_initialCalibration ? m_initialCalibration->initialNumCalls : 1;
+		logRenderTargetInfo(log, m_renderCtx.getRenderTarget());
+		m_measurer.init(m_program->getProgram(), m_attributes, initialCallCount);
+		m_measurer.logParameters(log);
+		log << *m_program;
+	}
+	else
+	{
+		log << *m_program;
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		return; // Skip rest of init.
+	}
+
+	setupProgram(m_program->getProgram());
+	setupRenderState();
+}
+
+void ShaderPerformanceCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+
+	m_measurer.deinit();
+}
+
+void ShaderPerformanceCase::setupProgram (deUint32 program)
+{
+	DE_UNREF(program);
+}
+
+void ShaderPerformanceCase::setupRenderState (void)
+{
+}
+
+ShaderPerformanceCase::IterateResult ShaderPerformanceCase::iterate (void)
+{
+	DE_ASSERT(m_program);
+
+	if (!m_program->isOk()) // This happens when compilation failed in init().
+		return STOP;
+
+	m_measurer.iterate();
+
+	if (m_measurer.isFinished())
+	{
+		m_measurer.logMeasurementInfo(m_testCtx.getLog());
+
+		if (m_initialCalibration)
+			m_initialCalibration->initialNumCalls = de::max(1, m_measurer.getFinalCallCount());
+
+		const ShaderPerformanceMeasurer::Result result = m_measurer.getResult();
+		reportResult(result.megaVertPerSec, result.megaFragPerSec);
+		return STOP;
+	}
+	else
+		return CONTINUE;
+}
+
+void ShaderPerformanceCase::reportResult (float mvertPerSecond, float mfragPerSecond)
+{
+	float result = 0.0f;
+	switch (m_caseType)
+	{
+		case CASETYPE_VERTEX:	result = mvertPerSecond;	break;
+		case CASETYPE_FRAGMENT:	result = mfragPerSecond;	break;
+		case CASETYPE_BALANCED:	result = mfragPerSecond;	break;
+		default:
+			DE_ASSERT(false);
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(result, 2).c_str());
+}
+
+ShaderPerformanceCaseGroup::ShaderPerformanceCaseGroup (tcu::TestContext& testCtx, const char* name, const char* description)
+	: TestCaseGroup					(testCtx, name, description)
+	, m_initialCalibrationStorage	(new ShaderPerformanceCase::InitialCalibration)
+{
+}
+
+void ShaderPerformanceCaseGroup::addChild (ShaderPerformanceCase* perfCase)
+{
+	perfCase->setCalibrationInitialParamStorage(m_initialCalibrationStorage);
+	TestCaseGroup::addChild(perfCase);
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsShaderPerformanceCase.hpp b/modules/glshared/glsShaderPerformanceCase.hpp
new file mode 100644
index 0000000..2298306
--- /dev/null
+++ b/modules/glshared/glsShaderPerformanceCase.hpp
@@ -0,0 +1,100 @@
+#ifndef _GLSSHADERPERFORMANCECASE_HPP
+#define _GLSSHADERPERFORMANCECASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Single-program test case wrapper for ShaderPerformanceMeasurer.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "glsShaderPerformanceMeasurer.hpp"
+#include "deSharedPtr.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+
+class ShaderPerformanceCase : public tcu::TestCase
+{
+public:
+	struct InitialCalibration
+	{
+		int initialNumCalls;
+		InitialCalibration (void) : initialNumCalls(1) {}
+	};
+
+										ShaderPerformanceCase				(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, PerfCaseType caseType);
+										~ShaderPerformanceCase				(void);
+
+	void								setCalibrationInitialParamStorage	(const de::SharedPtr<InitialCalibration>& storage) { m_initialCalibration = storage; }
+
+	void								init								(void);
+	void								deinit								(void);
+
+	IterateResult						iterate								(void);
+
+protected:
+	virtual void						setupProgram						(deUint32 program);
+	virtual void						setupRenderState					(void);
+
+	void								setGridSize							(int gridW, int gridH);
+	void								setViewportSize						(int width, int height);
+	void								setVertexFragmentRatio				(float fragmentsPerVertices);
+
+	int									getGridWidth						(void) const { return m_measurer.getGridWidth();		}
+	int									getGridHeight						(void) const { return m_measurer.getGridHeight();		}
+	int									getViewportWidth					(void) const { return m_measurer.getViewportWidth();	}
+	int									getViewportHeight					(void) const { return m_measurer.getViewportHeight();	}
+
+	virtual void						reportResult						(float mvertPerSecond, float mfragPerSecond);
+
+	glu::RenderContext&					m_renderCtx;
+
+	PerfCaseType						m_caseType;
+
+	std::string							m_vertShaderSource;
+	std::string							m_fragShaderSource;
+	std::vector<AttribSpec>				m_attributes;
+
+private:
+	glu::ShaderProgram*					m_program;
+	ShaderPerformanceMeasurer			m_measurer;
+
+	de::SharedPtr<InitialCalibration>	m_initialCalibration;
+};
+
+class ShaderPerformanceCaseGroup : public tcu::TestCaseGroup
+{
+public:
+																ShaderPerformanceCaseGroup	(tcu::TestContext& testCtx, const char* name, const char* description);
+	void														addChild					(ShaderPerformanceCase*);
+
+private:
+	de::SharedPtr<ShaderPerformanceCase::InitialCalibration>	m_initialCalibrationStorage;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSSHADERPERFORMANCECASE_HPP
diff --git a/modules/glshared/glsShaderPerformanceMeasurer.cpp b/modules/glshared/glsShaderPerformanceMeasurer.cpp
new file mode 100644
index 0000000..53e0059
--- /dev/null
+++ b/modules/glshared/glsShaderPerformanceMeasurer.cpp
@@ -0,0 +1,357 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader performance measurer; handles calibration and measurement
+ *//*--------------------------------------------------------------------*/
+
+#include "glsShaderPerformanceMeasurer.hpp"
+#include "gluDefs.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deStringUtil.hpp"
+#include "deMath.h"
+#include "deClock.h"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include <algorithm>
+
+using tcu::Vec4;
+using std::string;
+using std::vector;
+using tcu::TestLog;
+using namespace glw; // GL types
+
+namespace deqp
+{
+namespace gls
+{
+
+static inline float triangleInterpolate (float v0, float v1, float v2, float x, float y)
+{
+	return v0 + (v2-v0)*x + (v1-v0)*y;
+}
+
+static inline float triQuadInterpolate (float x, float y, const tcu::Vec4& quad)
+{
+	// \note Top left fill rule.
+	if (x + y < 1.0f)
+		return triangleInterpolate(quad.x(), quad.y(), quad.z(), x, y);
+	else
+		return triangleInterpolate(quad.w(), quad.z(), quad.y(), 1.0f-x, 1.0f-y);
+}
+
+static inline int getNumVertices (int gridSizeX, int gridSizeY)
+{
+	return (gridSizeX + 1) * (gridSizeY + 1);
+}
+
+static inline int getNumIndices (int gridSizeX, int gridSizeY)
+{
+	return gridSizeX*gridSizeY*6;
+}
+
+static inline deUint16 getVtxIndex (int x, int y, int gridSizeX)
+{
+	return (deUint16)(y*(gridSizeX+1) + x);
+}
+
+static void generateVertices (std::vector<float>& dst, int gridSizeX, int gridSizeY, const AttribSpec& spec)
+{
+	const int numComponents = 4;
+
+	DE_ASSERT((gridSizeX + 1)*(gridSizeY + 1) <= (1<<16)); // Must fit into 16-bit indices.
+	DE_ASSERT(gridSizeX >= 1 && gridSizeY >= 1);
+	dst.resize((gridSizeX + 1) * (gridSizeY + 1) * 4);
+
+	for (int y = 0; y <= gridSizeY; y++)
+	{
+		for (int x = 0; x <= gridSizeX; x++)
+		{
+			float	xf	= (float)x / (float)gridSizeX;
+			float	yf	= (float)y / (float)gridSizeY;
+
+			for (int compNdx = 0; compNdx < numComponents; compNdx++)
+				dst[getVtxIndex(x, y, gridSizeX)*numComponents + compNdx] = triQuadInterpolate(xf, yf, tcu::Vec4(spec.p00[compNdx], spec.p01[compNdx], spec.p10[compNdx], spec.p11[compNdx]));
+		}
+	}
+}
+
+static void generateIndices (std::vector<deUint16>& dst, int gridSizeX, int gridSizeY)
+{
+	const int	numIndicesPerQuad	= 6;
+	int			numIndices			= gridSizeX * gridSizeY * numIndicesPerQuad;
+	dst.resize(numIndices);
+
+	for (int y = 0; y < gridSizeY; y++)
+	{
+		for (int x = 0; x < gridSizeX; x++)
+		{
+			int quadNdx = y*gridSizeX + x;
+
+			dst[quadNdx*numIndicesPerQuad + 0] = getVtxIndex(x+0, y+0, gridSizeX);
+			dst[quadNdx*numIndicesPerQuad + 1] = getVtxIndex(x+1, y+0, gridSizeX);
+			dst[quadNdx*numIndicesPerQuad + 2] = getVtxIndex(x+0, y+1, gridSizeX);
+
+			dst[quadNdx*numIndicesPerQuad + 3] = getVtxIndex(x+0, y+1, gridSizeX);
+			dst[quadNdx*numIndicesPerQuad + 4] = getVtxIndex(x+1, y+0, gridSizeX);
+			dst[quadNdx*numIndicesPerQuad + 5] = getVtxIndex(x+1, y+1, gridSizeX);
+		}
+	}
+}
+
+ShaderPerformanceMeasurer::ShaderPerformanceMeasurer (const glu::RenderContext& renderCtx, PerfCaseType measureType)
+	: m_renderCtx		(renderCtx)
+	, m_gridSizeX		(measureType == CASETYPE_FRAGMENT	? 1		: 255)
+	, m_gridSizeY		(measureType == CASETYPE_FRAGMENT	? 1		: 255)
+	, m_viewportWidth	(measureType == CASETYPE_VERTEX		? 32	: renderCtx.getRenderTarget().getWidth())
+	, m_viewportHeight	(measureType == CASETYPE_VERTEX		? 32	: renderCtx.getRenderTarget().getHeight())
+	, m_state			(STATE_UNINITIALIZED)
+	, m_result			(-1.0f, -1.0f)
+	, m_indexBuffer		(0)
+	, m_vao				(0)
+{
+}
+
+void ShaderPerformanceMeasurer::logParameters (TestLog& log) const
+{
+	log << TestLog::Message << "Grid size: " << m_gridSizeX << "x" << m_gridSizeY << TestLog::EndMessage
+		<< TestLog::Message << "Viewport: " << m_viewportWidth << "x" << m_viewportHeight << TestLog::EndMessage;
+}
+
+void ShaderPerformanceMeasurer::init (deUint32 program, const vector<AttribSpec>& attributes, int calibratorInitialNumCalls)
+{
+	DE_ASSERT(m_state == STATE_UNINITIALIZED);
+
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+	const bool				useVAO	= glu::isContextTypeGLCore(m_renderCtx.getType());
+
+	if (useVAO)
+	{
+		DE_ASSERT(!m_vao);
+		gl.genVertexArrays(1, &m_vao);
+		gl.bindVertexArray(m_vao);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Create VAO");
+	}
+
+	// Validate that we have sane grid and viewport setup.
+
+	DE_ASSERT(de::inBounds(m_gridSizeX, 1, 256) && de::inBounds(m_gridSizeY, 1, 256));
+
+	{
+		bool widthTooSmall		= m_renderCtx.getRenderTarget().getWidth() < m_viewportWidth;
+		bool heightTooSmall		= m_renderCtx.getRenderTarget().getHeight() < m_viewportHeight;
+
+		if (widthTooSmall || heightTooSmall)
+			throw tcu::NotSupportedError("Render target too small (" +
+											 (widthTooSmall  ?									   "width must be at least "  + de::toString(m_viewportWidth)  : "") +
+											 (heightTooSmall ? string(widthTooSmall ? ", " : "") + "height must be at least " + de::toString(m_viewportHeight) : "") +
+											 ")");
+	}
+
+	TCU_CHECK_INTERNAL(de::inRange(m_viewportWidth,		1, m_renderCtx.getRenderTarget().getWidth()) &&
+					   de::inRange(m_viewportHeight,	1, m_renderCtx.getRenderTarget().getHeight()));
+
+	// Insert a_position to attributes.
+	m_attributes = attributes;
+	m_attributes.push_back(AttribSpec("a_position",
+									  Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
+									  Vec4( 1.0f, -1.0f, 0.0f, 1.0f),
+									  Vec4(-1.0f,  1.0f, 0.0f, 1.0f),
+									  Vec4( 1.0f,  1.0f, 0.0f, 1.0f)));
+
+	// Generate indices.
+	{
+		std::vector<deUint16> indices;
+		generateIndices(indices, m_gridSizeX, m_gridSizeY);
+
+		gl.genBuffers(1, &m_indexBuffer);
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(indices.size()*sizeof(deUint16)), &indices[0], GL_STATIC_DRAW);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Upload index data");
+	}
+
+	// Generate vertices.
+	m_attribBuffers.resize(m_attributes.size(), 0);
+	gl.genBuffers((GLsizei)m_attribBuffers.size(), &m_attribBuffers[0]);
+
+	for (int attribNdx = 0; attribNdx < (int)m_attributes.size(); attribNdx++)
+	{
+		std::vector<float> vertices;
+		generateVertices(vertices, m_gridSizeX, m_gridSizeY, m_attributes[attribNdx]);
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, m_attribBuffers[attribNdx]);
+		gl.bufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(vertices.size()*sizeof(float)), &vertices[0], GL_STATIC_DRAW);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Upload vertex data");
+
+	// Setup attribute bindings.
+	for (int attribNdx = 0; attribNdx < (int)m_attributes.size(); attribNdx++)
+	{
+		int location = gl.getAttribLocation(program, m_attributes[attribNdx].name.c_str());
+
+		if (location >= 0)
+		{
+			gl.enableVertexAttribArray(location);
+			gl.bindBuffer(GL_ARRAY_BUFFER, m_attribBuffers[attribNdx]);
+			gl.vertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup vertex attribute state");
+	}
+
+	gl.useProgram(program);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
+
+	m_state = STATE_MEASURING;
+	m_isFirstIteration = true;
+
+	m_calibrator.clear(CalibratorParameters(calibratorInitialNumCalls, 10 /* calibrate iteration frames */, 2000.0f /* calibrate iteration shortcut threshold (ms) */, 16 /* max calibrate iterations */,
+											1000.0f/30.0f /* frame time (ms) */, 1000.0f/60.0f /* frame time cap (ms) */, 1000.0f /* target measure duration (ms) */));
+}
+
+void ShaderPerformanceMeasurer::deinit (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if (m_indexBuffer)
+	{
+		gl.deleteBuffers(1, &m_indexBuffer);
+		m_indexBuffer = 0;
+	}
+
+	if (m_vao)
+	{
+		gl.deleteVertexArrays(1, &m_vao);
+		m_vao = 0;
+	}
+
+	if (!m_attribBuffers.empty())
+	{
+		gl.deleteBuffers((GLsizei)m_attribBuffers.size(), &m_attribBuffers[0]);
+		m_attribBuffers.clear();
+	}
+
+	m_state = STATE_UNINITIALIZED;
+}
+
+void ShaderPerformanceMeasurer::render (int numDrawCalls)
+{
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	GLsizei					numIndices	= (GLsizei)getNumIndices(m_gridSizeX, m_gridSizeY);
+
+	gl.viewport(0, 0, m_viewportWidth, m_viewportHeight);
+
+	for (int callNdx = 0; callNdx < numDrawCalls; callNdx++)
+		gl.drawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, DE_NULL);
+}
+
+void ShaderPerformanceMeasurer::iterate (void)
+{
+	DE_ASSERT(m_state == STATE_MEASURING);
+
+	deUint64 renderStartTime = deGetMicroseconds();
+	render(m_calibrator.getCallCount()); // Always render. This gives more stable performance behavior.
+
+	TheilSenCalibrator::State calibratorState = m_calibrator.getState();
+
+	if (calibratorState == TheilSenCalibrator::STATE_RECOMPUTE_PARAMS)
+	{
+		m_calibrator.recomputeParameters();
+
+		m_isFirstIteration = true;
+		m_prevRenderStartTime = renderStartTime;
+	}
+	else if (calibratorState == TheilSenCalibrator::STATE_MEASURE)
+	{
+		if (!m_isFirstIteration)
+			m_calibrator.recordIteration(renderStartTime - m_prevRenderStartTime);
+
+		m_isFirstIteration = false;
+		m_prevRenderStartTime = renderStartTime;
+	}
+	else
+	{
+		DE_ASSERT(calibratorState == TheilSenCalibrator::STATE_FINISHED);
+
+		GLU_EXPECT_NO_ERROR(m_renderCtx.getFunctions().getError(), "End of rendering");
+
+		const MeasureState& measureState = m_calibrator.getMeasureState();
+
+		// Compute result.
+		deUint64	totalTime			= measureState.getTotalTime();
+		int			numFrames			= (int)measureState.frameTimes.size();
+		deInt64		numQuadGrids		= measureState.numDrawCalls * numFrames;
+		deInt64		numPixels			= (deInt64)m_viewportWidth * (deInt64)m_viewportHeight * numQuadGrids;
+		deInt64		numVertices			= (deInt64)getNumVertices(m_gridSizeX, m_gridSizeY) * numQuadGrids;
+		double		mfragPerSecond		= (double)numPixels / (double)totalTime;
+		double		mvertPerSecond		= (double)numVertices / (double)totalTime;
+
+		m_result = Result((float)mvertPerSecond, (float)mfragPerSecond);
+		m_state = STATE_FINISHED;
+	}
+}
+
+void ShaderPerformanceMeasurer::logMeasurementInfo (TestLog& log) const
+{
+	DE_ASSERT(m_state == STATE_FINISHED);
+
+	const MeasureState& measureState(m_calibrator.getMeasureState());
+
+	// Compute totals.
+	deUint64	totalTime			= measureState.getTotalTime();
+	int			numFrames			= (int)measureState.frameTimes.size();
+	deInt64		numQuadGrids		= measureState.numDrawCalls * numFrames;
+	deInt64		numPixels			= (deInt64)m_viewportWidth * (deInt64)m_viewportHeight * numQuadGrids;
+	deInt64		numVertices			= (deInt64)getNumVertices(m_gridSizeX, m_gridSizeY) * numQuadGrids;
+	double		mfragPerSecond		= (double)numPixels / (double)totalTime;
+	double		mvertPerSecond		= (double)numVertices / (double)totalTime;
+	double		framesPerSecond		= (double)numFrames / ((double)totalTime / 1000000.0);
+
+	logCalibrationInfo(log, m_calibrator);
+
+	log << TestLog::Float("FramesPerSecond",		"Frames per second in measurement",	"Frames/s",				QP_KEY_TAG_PERFORMANCE,	(float)framesPerSecond)
+		<< TestLog::Float("FragmentsPerVertices",	"Vertex-fragment ratio",			"Fragments/Vertices",	QP_KEY_TAG_NONE,		(float)numPixels / (float)numVertices)
+		<< TestLog::Float("FragmentPerf",			"Fragment performance",				"MPix/s",				QP_KEY_TAG_PERFORMANCE, (float)mfragPerSecond)
+		<< TestLog::Float("VertexPerf",				"Vertex performance",				"MVert/s",				QP_KEY_TAG_PERFORMANCE, (float)mvertPerSecond);
+}
+
+void ShaderPerformanceMeasurer::setGridSize (int gridW, int gridH)
+{
+	DE_ASSERT(m_state == STATE_UNINITIALIZED);
+	DE_ASSERT(de::inBounds(gridW, 1, 256) && de::inBounds(gridH, 1, 256));
+	m_gridSizeX		= gridW;
+	m_gridSizeY		= gridH;
+}
+
+void ShaderPerformanceMeasurer::setViewportSize (int width, int height)
+{
+	DE_ASSERT(m_state == STATE_UNINITIALIZED);
+	DE_ASSERT(de::inRange(width,	1, m_renderCtx.getRenderTarget().getWidth()) &&
+			  de::inRange(height,	1, m_renderCtx.getRenderTarget().getHeight()));
+	m_viewportWidth		= width;
+	m_viewportHeight	= height;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsShaderPerformanceMeasurer.hpp b/modules/glshared/glsShaderPerformanceMeasurer.hpp
new file mode 100644
index 0000000..d2443bc
--- /dev/null
+++ b/modules/glshared/glsShaderPerformanceMeasurer.hpp
@@ -0,0 +1,131 @@
+#ifndef _GLSSHADERPERFORMANCEMEASURER_HPP
+#define _GLSSHADERPERFORMANCEMEASURER_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader performance measurer; handles calibration and measurement
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuVector.hpp"
+#include "gluRenderContext.hpp"
+#include "glsCalibration.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+
+enum PerfCaseType
+{
+	CASETYPE_VERTEX = 0,
+	CASETYPE_FRAGMENT,
+	CASETYPE_BALANCED,
+
+	CASETYPE_LAST
+};
+
+struct AttribSpec
+{
+	AttribSpec (const char* name_, const tcu::Vec4& p00_, const tcu::Vec4& p01_, const tcu::Vec4& p10_, const tcu::Vec4& p11_)
+		: name		(name_)
+		, p00		(p00_)
+		, p01		(p01_)
+		, p10		(p10_)
+		, p11		(p11_)
+	{
+	}
+
+	AttribSpec (void) {}
+
+	std::string		name;
+	tcu::Vec4		p00;	//!< Bottom left.
+	tcu::Vec4		p01;	//!< Bottom right.
+	tcu::Vec4		p10;	//!< Top left.
+	tcu::Vec4		p11;	//!< Top right.
+};
+
+class ShaderPerformanceMeasurer
+{
+public:
+	struct Result
+	{
+		float megaVertPerSec;
+		float megaFragPerSec;
+
+		Result (float megaVertPerSec_, float megaFragPerSec_) : megaVertPerSec(megaVertPerSec_), megaFragPerSec(megaFragPerSec_) {}
+	};
+
+										ShaderPerformanceMeasurer	(const glu::RenderContext& renderCtx, PerfCaseType measureType);
+										~ShaderPerformanceMeasurer	(void) { deinit(); }
+
+	void								init						(deUint32 program, const std::vector<AttribSpec>& attributes, int calibratorInitialNumCalls);
+	void								deinit						(void);
+	void								iterate						(void);
+
+	void								logParameters				(tcu::TestLog& log)		const;
+	bool								isFinished					(void)					const { return m_state == STATE_FINISHED; }
+	Result								getResult					(void)					const { DE_ASSERT(m_state == STATE_FINISHED); return m_result; }
+	void								logMeasurementInfo			(tcu::TestLog& log)		const;
+
+	void								setGridSize					(int gridW, int gridH);
+	void								setViewportSize				(int width, int height);
+
+	int									getGridWidth				(void) const { return m_gridSizeX;		}
+	int									getGridHeight				(void) const { return m_gridSizeY;		}
+	int									getViewportWidth			(void) const { return m_viewportWidth;	}
+	int									getViewportHeight			(void) const { return m_viewportHeight;	}
+
+	int									getFinalCallCount			(void) const { DE_ASSERT(m_state == STATE_FINISHED); return m_calibrator.getCallCount(); }
+
+private:
+	enum State
+	{
+		STATE_UNINITIALIZED = 0,
+		STATE_MEASURING,
+		STATE_FINISHED,
+
+		STATE_LAST
+	};
+
+	void								render						(int numDrawCalls);
+
+	const glu::RenderContext&			m_renderCtx;
+	int									m_gridSizeX;
+	int									m_gridSizeY;
+	int									m_viewportWidth;
+	int									m_viewportHeight;
+
+	State								m_state;
+	bool								m_isFirstIteration;
+	deUint64							m_prevRenderStartTime;
+	Result								m_result;
+	TheilSenCalibrator					m_calibrator;
+	deUint32							m_indexBuffer;
+	std::vector<AttribSpec>				m_attributes;
+	std::vector<deUint32>				m_attribBuffers;
+	deUint32							m_vao;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSSHADERPERFORMANCEMEASURER_HPP
diff --git a/modules/glshared/glsShaderRenderCase.cpp b/modules/glshared/glsShaderRenderCase.cpp
new file mode 100644
index 0000000..e7dddf9
--- /dev/null
+++ b/modules/glshared/glsShaderRenderCase.cpp
@@ -0,0 +1,1038 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader execute test.
+ *
+ * \todo [petri] Multiple grid with differing constants/uniforms.
+ * \todo [petri]
+ *//*--------------------------------------------------------------------*/
+
+#include "glsShaderRenderCase.hpp"
+
+#include "tcuSurface.hpp"
+#include "tcuVector.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+
+#include "gluPixelTransfer.hpp"
+#include "gluTexture.hpp"
+#include "gluTextureUtil.hpp"
+#include "gluDrawUtil.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "deRandom.hpp"
+#include "deMemory.h"
+#include "deString.h"
+#include "deMath.h"
+#include "deStringUtil.hpp"
+
+#include <stdio.h>
+#include <vector>
+#include <string>
+
+namespace deqp
+{
+namespace gls
+{
+
+using namespace std;
+using namespace tcu;
+using namespace glu;
+
+static const int			GRID_SIZE				= 64;
+static const int			MAX_RENDER_WIDTH		= 128;
+static const int			MAX_RENDER_HEIGHT		= 112;
+static const tcu::Vec4		DEFAULT_CLEAR_COLOR		= tcu::Vec4(0.125f, 0.25f, 0.5f, 1.0f);
+
+inline RGBA toRGBA (const Vec4& a)
+{
+	return RGBA(deClamp32(deRoundFloatToInt32(a.x() * 255.0f), 0, 255),
+				deClamp32(deRoundFloatToInt32(a.y() * 255.0f), 0, 255),
+				deClamp32(deRoundFloatToInt32(a.z() * 255.0f), 0, 255),
+				deClamp32(deRoundFloatToInt32(a.w() * 255.0f), 0, 255));
+}
+
+inline tcu::Vec4 toVec (const RGBA& c)
+{
+	return tcu::Vec4(c.getRed()		/ 255.0f,
+					 c.getGreen()	/ 255.0f,
+					 c.getBlue()	/ 255.0f,
+					 c.getAlpha()	/ 255.0f);
+}
+
+// TextureBinding
+
+TextureBinding::TextureBinding (const glu::Texture2D* tex2D, const tcu::Sampler& sampler)
+	: m_type	(TYPE_2D)
+	, m_sampler	(sampler)
+{
+	m_binding.tex2D = tex2D;
+}
+
+TextureBinding::TextureBinding (const glu::TextureCube* texCube, const tcu::Sampler& sampler)
+	: m_type	(TYPE_CUBE_MAP)
+	, m_sampler	(sampler)
+{
+	m_binding.texCube = texCube;
+}
+
+TextureBinding::TextureBinding (const glu::Texture2DArray* tex2DArray, const tcu::Sampler& sampler)
+	: m_type	(TYPE_2D_ARRAY)
+	, m_sampler	(sampler)
+{
+	m_binding.tex2DArray = tex2DArray;
+}
+
+TextureBinding::TextureBinding (const glu::Texture3D* tex3D, const tcu::Sampler& sampler)
+	: m_type	(TYPE_3D)
+	, m_sampler	(sampler)
+{
+	m_binding.tex3D = tex3D;
+}
+
+TextureBinding::TextureBinding (void)
+	: m_type	(TYPE_NONE)
+{
+	m_binding.tex2D = DE_NULL;
+}
+
+void TextureBinding::setSampler (const tcu::Sampler& sampler)
+{
+	m_sampler = sampler;
+}
+
+void TextureBinding::setTexture (const glu::Texture2D* tex2D)
+{
+	m_type			= TYPE_2D;
+	m_binding.tex2D	= tex2D;
+}
+
+void TextureBinding::setTexture (const glu::TextureCube* texCube)
+{
+	m_type				= TYPE_CUBE_MAP;
+	m_binding.texCube	= texCube;
+}
+
+void TextureBinding::setTexture (const glu::Texture2DArray* tex2DArray)
+{
+	m_type					= TYPE_2D_ARRAY;
+	m_binding.tex2DArray	= tex2DArray;
+}
+
+void TextureBinding::setTexture (const glu::Texture3D* tex3D)
+{
+	m_type			= TYPE_3D;
+	m_binding.tex3D	= tex3D;
+}
+
+// QuadGrid.
+
+class QuadGrid
+{
+public:
+							QuadGrid				(int gridSize, int screenWidth, int screenHeight, const Vec4& constCoords, const vector<Mat4>& userAttribTransforms, const vector<TextureBinding>& textures);
+							~QuadGrid				(void);
+
+	int						getGridSize				(void) const { return m_gridSize; }
+	int						getNumVertices			(void) const { return m_numVertices; }
+	int						getNumTriangles			(void) const { return m_numTriangles; }
+	const Vec4&				getConstCoords			(void) const { return m_constCoords; }
+	const vector<Mat4>		getUserAttribTransforms	(void) const { return m_userAttribTransforms; }
+	const vector<TextureBinding>&	getTextures		(void) const { return m_textures; }
+
+	const Vec4*				getPositions			(void) const { return &m_positions[0]; }
+	const float*			getAttribOne			(void) const { return &m_attribOne[0]; }
+	const Vec4*				getCoords				(void) const { return &m_coords[0]; }
+	const Vec4*				getUnitCoords			(void) const { return &m_unitCoords[0]; }
+	const Vec4*				getUserAttrib			(int attribNdx) const { return &m_userAttribs[attribNdx][0]; }
+	const deUint16*			getIndices				(void) const { return &m_indices[0]; }
+
+	Vec4					getCoords				(float sx, float sy) const;
+	Vec4					getUnitCoords			(float sx, float sy) const;
+
+	int						getNumUserAttribs		(void) const { return (int)m_userAttribTransforms.size(); }
+	Vec4					getUserAttrib			(int attribNdx, float sx, float sy) const;
+
+private:
+	int						m_gridSize;
+	int						m_numVertices;
+	int						m_numTriangles;
+	Vec4					m_constCoords;
+	vector<Mat4>			m_userAttribTransforms;
+	vector<TextureBinding>	m_textures;
+
+	vector<Vec4>			m_screenPos;
+	vector<Vec4>			m_positions;
+	vector<Vec4>			m_coords;			//!< Near-unit coordinates, roughly [-2.0 .. 2.0].
+	vector<Vec4>			m_unitCoords;		//!< Positive-only coordinates [0.0 .. 1.5].
+	vector<float>			m_attribOne;
+	vector<Vec4>			m_userAttribs[ShaderEvalContext::MAX_TEXTURES];
+	vector<deUint16>		m_indices;
+};
+
+QuadGrid::QuadGrid (int gridSize, int width, int height, const Vec4& constCoords, const vector<Mat4>& userAttribTransforms, const vector<TextureBinding>& textures)
+	: m_gridSize				(gridSize)
+	, m_numVertices				((gridSize + 1) * (gridSize + 1))
+	, m_numTriangles			(gridSize * gridSize * 2)
+	, m_constCoords				(constCoords)
+	, m_userAttribTransforms	(userAttribTransforms)
+	, m_textures				(textures)
+{
+	Vec4 viewportScale = Vec4((float)width, (float)height, 0.0f, 0.0f);
+
+	// Compute vertices.
+	m_positions.resize(m_numVertices);
+	m_coords.resize(m_numVertices);
+	m_unitCoords.resize(m_numVertices);
+	m_attribOne.resize(m_numVertices);
+	m_screenPos.resize(m_numVertices);
+
+	// User attributes.
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(m_userAttribs); i++)
+		m_userAttribs[i].resize(m_numVertices);
+
+	for (int y = 0; y < gridSize+1; y++)
+	for (int x = 0; x < gridSize+1; x++)
+	{
+		float				sx			= x / (float)gridSize;
+		float				sy			= y / (float)gridSize;
+		float				fx			= 2.0f * sx - 1.0f;
+		float				fy			= 2.0f * sy - 1.0f;
+		int					vtxNdx		= ((y * (gridSize+1)) + x);
+
+		m_positions[vtxNdx]		= Vec4(fx, fy, 0.0f, 1.0f);
+		m_attribOne[vtxNdx]		= 1.0f;
+		m_screenPos[vtxNdx]		= Vec4(sx, sy, 0.0f, 1.0f) * viewportScale;
+		m_coords[vtxNdx]		= getCoords(sx, sy);
+		m_unitCoords[vtxNdx]	= getUnitCoords(sx, sy);
+
+		for (int attribNdx = 0; attribNdx < getNumUserAttribs(); attribNdx++)
+			m_userAttribs[attribNdx][vtxNdx] = getUserAttrib(attribNdx, sx, sy);
+	}
+
+	// Compute indices.
+	m_indices.resize(3 * m_numTriangles);
+	for (int y = 0; y < gridSize; y++)
+	for (int x = 0; x < gridSize; x++)
+	{
+		int stride = gridSize + 1;
+		int v00 = (y * stride) + x;
+		int v01 = (y * stride) + x + 1;
+		int v10 = ((y+1) * stride) + x;
+		int v11 = ((y+1) * stride) + x + 1;
+
+		int baseNdx = ((y * gridSize) + x) * 6;
+		m_indices[baseNdx + 0] = v10;
+		m_indices[baseNdx + 1] = v00;
+		m_indices[baseNdx + 2] = v01;
+
+		m_indices[baseNdx + 3] = v10;
+		m_indices[baseNdx + 4] = v01;
+		m_indices[baseNdx + 5] = v11;
+	}
+}
+
+QuadGrid::~QuadGrid (void)
+{
+}
+
+inline Vec4 QuadGrid::getCoords (float sx, float sy) const
+{
+	float fx = 2.0f * sx - 1.0f;
+	float fy = 2.0f * sy - 1.0f;
+	return Vec4(fx, fy, -fx + 0.33f*fy, -0.275f*fx - fy);
+}
+
+inline Vec4 QuadGrid::getUnitCoords (float sx, float sy) const
+{
+	return Vec4(sx, sy, 0.33f*sx + 0.5f*sy, 0.5f*sx + 0.25f*sy);
+}
+
+inline Vec4 QuadGrid::getUserAttrib (int attribNdx, float sx, float sy) const
+{
+	// homogeneous normalized screen-space coordinates
+	return m_userAttribTransforms[attribNdx] * Vec4(sx, sy, 0.0f, 1.0f);
+}
+
+// ShaderEvalContext.
+
+ShaderEvalContext::ShaderEvalContext (const QuadGrid& quadGrid_)
+	: constCoords	(quadGrid_.getConstCoords())
+	, isDiscarded	(false)
+	, quadGrid		(quadGrid_)
+{
+	const vector<TextureBinding>& bindings = quadGrid.getTextures();
+	DE_ASSERT((int)bindings.size() <= MAX_TEXTURES);
+
+	// Fill in texture array.
+	for (int ndx = 0; ndx < (int)bindings.size(); ndx++)
+	{
+		const TextureBinding& binding = bindings[ndx];
+
+		if (binding.getType() == TextureBinding::TYPE_NONE)
+			continue;
+
+		textures[ndx].sampler = binding.getSampler();
+
+		switch (binding.getType())
+		{
+			case TextureBinding::TYPE_2D:		textures[ndx].tex2D			= &binding.get2D()->getRefTexture();		break;
+			case TextureBinding::TYPE_CUBE_MAP:	textures[ndx].texCube		= &binding.getCube()->getRefTexture();		break;
+			case TextureBinding::TYPE_2D_ARRAY:	textures[ndx].tex2DArray	= &binding.get2DArray()->getRefTexture();	break;
+			case TextureBinding::TYPE_3D:		textures[ndx].tex3D			= &binding.get3D()->getRefTexture();		break;
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+	}
+}
+
+ShaderEvalContext::~ShaderEvalContext (void)
+{
+}
+
+void ShaderEvalContext::reset (float sx, float sy)
+{
+	// Clear old values
+	color		= Vec4(0.0f, 0.0f, 0.0f, 1.0f);
+	isDiscarded	= false;
+
+	// Compute coords
+	coords		= quadGrid.getCoords(sx, sy);
+	unitCoords	= quadGrid.getUnitCoords(sx, sy);
+
+	// Compute user attributes.
+	int numAttribs = quadGrid.getNumUserAttribs();
+	DE_ASSERT(numAttribs <= MAX_USER_ATTRIBS);
+	for (int attribNdx = 0; attribNdx < numAttribs; attribNdx++)
+		in[attribNdx] = quadGrid.getUserAttrib(attribNdx, sx, sy);
+}
+
+tcu::Vec4 ShaderEvalContext::texture2D (int unitNdx, const tcu::Vec2& texCoords)
+{
+	if (textures[unitNdx].tex2D)
+		return textures[unitNdx].tex2D->sample(textures[unitNdx].sampler, texCoords.x(), texCoords.y(), 0.0f);
+	else
+		return tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
+}
+
+// ShaderEvaluator
+
+ShaderEvaluator::ShaderEvaluator (void)
+	: m_evalFunc(DE_NULL)
+{
+}
+
+ShaderEvaluator::ShaderEvaluator (ShaderEvalFunc evalFunc)
+	: m_evalFunc(evalFunc)
+{
+}
+
+ShaderEvaluator::~ShaderEvaluator (void)
+{
+}
+
+void ShaderEvaluator::evaluate (ShaderEvalContext& ctx)
+{
+	DE_ASSERT(m_evalFunc);
+	m_evalFunc(ctx);
+}
+
+// ShaderRenderCase.
+
+ShaderRenderCase::ShaderRenderCase (TestContext& testCtx, RenderContext& renderCtx, const ContextInfo& ctxInfo, const char* name, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc)
+	: TestCase				(testCtx, name, description)
+	, m_renderCtx			(renderCtx)
+	, m_ctxInfo				(ctxInfo)
+	, m_isVertexCase		(isVertexCase)
+	, m_defaultEvaluator	(evalFunc)
+	, m_evaluator			(m_defaultEvaluator)
+	, m_clearColor			(DEFAULT_CLEAR_COLOR)
+	, m_program				(DE_NULL)
+{
+}
+
+ShaderRenderCase::ShaderRenderCase (TestContext& testCtx, RenderContext& renderCtx, const ContextInfo& ctxInfo, const char* name, const char* description, bool isVertexCase, ShaderEvaluator& evaluator)
+	: TestCase				(testCtx, name, description)
+	, m_renderCtx			(renderCtx)
+	, m_ctxInfo				(ctxInfo)
+	, m_isVertexCase		(isVertexCase)
+	, m_defaultEvaluator	(DE_NULL)
+	, m_evaluator			(evaluator)
+	, m_clearColor			(DEFAULT_CLEAR_COLOR)
+	, m_program				(DE_NULL)
+{
+}
+
+ShaderRenderCase::~ShaderRenderCase (void)
+{
+	ShaderRenderCase::deinit();
+}
+
+void ShaderRenderCase::init (void)
+{
+	TestLog&				log		= m_testCtx.getLog();
+	const glw::Functions&	gl		= m_renderCtx.getFunctions();
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderRenderCase::init() begin");
+
+	if (m_vertShaderSource.empty() || m_fragShaderSource.empty())
+	{
+		DE_ASSERT(m_vertShaderSource.empty() && m_fragShaderSource.empty());
+		setupShaderData();
+	}
+
+	DE_ASSERT(!m_program);
+	m_program = new ShaderProgram(m_renderCtx, makeVtxFragSources(m_vertShaderSource, m_fragShaderSource));
+
+	try
+	{
+		log << *m_program; // Always log shader program.
+
+		if (!m_program->isOk())
+			throw CompileFailed(__FILE__, __LINE__);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderRenderCase::init() end");
+	}
+	catch (const std::exception&)
+	{
+		// Clean up.
+		ShaderRenderCase::deinit();
+		throw;
+	}
+}
+
+void ShaderRenderCase::deinit (void)
+{
+	delete m_program;
+	m_program = DE_NULL;
+}
+
+tcu::IVec2 ShaderRenderCase::getViewportSize (void) const
+{
+	return tcu::IVec2(de::min(m_renderCtx.getRenderTarget().getWidth(), MAX_RENDER_WIDTH),
+					  de::min(m_renderCtx.getRenderTarget().getHeight(), MAX_RENDER_HEIGHT));
+}
+
+TestNode::IterateResult ShaderRenderCase::iterate (void)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderRenderCase::iterate() begin");
+
+	DE_ASSERT(m_program);
+	deUint32 programID = m_program->getProgram();
+	gl.useProgram(programID);
+
+	// Create quad grid.
+	IVec2	viewportSize	= getViewportSize();
+	int		width			= viewportSize.x();
+	int		height			= viewportSize.y();
+
+	// \todo [petri] Better handling of constCoords (render in multiple chunks, vary coords).
+	QuadGrid quadGrid(m_isVertexCase ? GRID_SIZE : 4, width, height, Vec4(0.125f, 0.25f, 0.5f, 1.0f), m_userAttribTransforms, m_textures);
+
+	// Render result.
+	Surface resImage(width, height);
+	render(resImage, programID, quadGrid);
+
+	// Compute reference.
+	Surface refImage (width, height);
+	if (m_isVertexCase)
+		computeVertexReference(refImage, quadGrid);
+	else
+		computeFragmentReference(refImage, quadGrid);
+
+	// Compare.
+	bool testOk = compareImages(resImage, refImage, 0.05f);
+
+	// De-initialize.
+	gl.useProgram(0);
+
+	m_testCtx.setTestResult(testOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+							testOk ? "Pass"					: "Fail");
+	return TestNode::STOP;
+}
+
+void ShaderRenderCase::setupShaderData (void)
+{
+}
+
+void ShaderRenderCase::setup (int programID)
+{
+	DE_UNREF(programID);
+}
+
+void ShaderRenderCase::setupUniforms (int programID, const Vec4& constCoords)
+{
+	DE_UNREF(programID);
+	DE_UNREF(constCoords);
+}
+
+void ShaderRenderCase::setupDefaultInputs (int programID)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	// SETUP UNIFORMS.
+
+	setupDefaultUniforms(m_renderCtx, programID);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "post uniform setup");
+
+	// SETUP TEXTURES.
+
+	for (int ndx = 0; ndx < (int)m_textures.size(); ndx++)
+	{
+		const TextureBinding&	tex			= m_textures[ndx];
+		const tcu::Sampler&		sampler		= tex.getSampler();
+		deUint32				texTarget	= GL_NONE;
+		deUint32				texObj		= 0;
+
+		if (tex.getType() == TextureBinding::TYPE_NONE)
+			continue;
+
+		// Feature check.
+		if (m_renderCtx.getType().getAPI() == glu::ApiType::es(2,0))
+		{
+			if (tex.getType() == TextureBinding::TYPE_2D_ARRAY)
+				throw tcu::NotSupportedError("2D array texture binding is not supported");
+
+			if (tex.getType() == TextureBinding::TYPE_3D)
+				throw tcu::NotSupportedError("3D texture binding is not supported");
+
+			if (sampler.compare != tcu::Sampler::COMPAREMODE_NONE)
+				throw tcu::NotSupportedError("Shadow lookups are not supported");
+		}
+
+		switch (tex.getType())
+		{
+			case TextureBinding::TYPE_2D:		texTarget = GL_TEXTURE_2D;			texObj = tex.get2D()->getGLTexture();		break;
+			case TextureBinding::TYPE_CUBE_MAP:	texTarget = GL_TEXTURE_CUBE_MAP;	texObj = tex.getCube()->getGLTexture();		break;
+			case TextureBinding::TYPE_2D_ARRAY:	texTarget = GL_TEXTURE_2D_ARRAY;	texObj = tex.get2DArray()->getGLTexture();	break;
+			case TextureBinding::TYPE_3D:		texTarget = GL_TEXTURE_3D;			texObj = tex.get3D()->getGLTexture();		break;
+			default:
+				DE_ASSERT(DE_FALSE);
+		}
+
+		gl.activeTexture(GL_TEXTURE0+ndx);
+		gl.bindTexture(texTarget, texObj);
+		gl.texParameteri(texTarget, GL_TEXTURE_WRAP_S,		glu::getGLWrapMode(sampler.wrapS));
+		gl.texParameteri(texTarget, GL_TEXTURE_WRAP_T,		glu::getGLWrapMode(sampler.wrapT));
+		gl.texParameteri(texTarget, GL_TEXTURE_MIN_FILTER,	glu::getGLFilterMode(sampler.minFilter));
+		gl.texParameteri(texTarget, GL_TEXTURE_MAG_FILTER,	glu::getGLFilterMode(sampler.magFilter));
+
+		if (texTarget == GL_TEXTURE_3D)
+			gl.texParameteri(texTarget, GL_TEXTURE_WRAP_R, glu::getGLWrapMode(sampler.wrapR));
+
+		if (sampler.compare != tcu::Sampler::COMPAREMODE_NONE)
+		{
+			gl.texParameteri(texTarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+			gl.texParameteri(texTarget, GL_TEXTURE_COMPARE_FUNC, glu::getGLCompareFunc(sampler.compare));
+		}
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "texture sampler setup");
+}
+
+static void getDefaultVertexArrays (const glw::Functions& gl, const QuadGrid& quadGrid, deUint32 program, vector<VertexArrayBinding>& vertexArrays)
+{
+	const int numElements = quadGrid.getNumVertices();
+
+	vertexArrays.push_back(va::Float("a_position",		4, numElements, 0, (const float*)quadGrid.getPositions()));
+	vertexArrays.push_back(va::Float("a_coords",		4, numElements, 0, (const float*)quadGrid.getCoords()));
+	vertexArrays.push_back(va::Float("a_unitCoords",	4, numElements, 0, (const float*)quadGrid.getUnitCoords()));
+	vertexArrays.push_back(va::Float("a_one",			1, numElements, 0, quadGrid.getAttribOne()));
+
+	// a_inN.
+	for (int userNdx = 0; userNdx < quadGrid.getNumUserAttribs(); userNdx++)
+	{
+		string name = string("a_in") + de::toString(userNdx);
+		vertexArrays.push_back(va::Float(name, 4, numElements, 0, (const float*)quadGrid.getUserAttrib(userNdx)));
+	}
+
+	// Matrix attributes - these are set by location
+	static const struct
+	{
+		const char*	name;
+		int			numCols;
+		int			numRows;
+	} matrices[] =
+	{
+		{ "a_mat2",		2, 2 },
+		{ "a_mat2x3",	2, 3 },
+		{ "a_mat2x4",	2, 4 },
+		{ "a_mat3x2",	3, 2 },
+		{ "a_mat3",		3, 3 },
+		{ "a_mat3x4",	3, 4 },
+		{ "a_mat4x2",	4, 2 },
+		{ "a_mat4x3",	4, 3 },
+		{ "a_mat4",		4, 4 }
+	};
+
+	for (int matNdx = 0; matNdx < DE_LENGTH_OF_ARRAY(matrices); matNdx++)
+	{
+		int loc = gl.getAttribLocation(program, matrices[matNdx].name);
+
+		if (loc < 0)
+			continue; // Not used in shader.
+
+		int numRows	= matrices[matNdx].numRows;
+		int numCols	= matrices[matNdx].numCols;
+
+		for (int colNdx = 0; colNdx < numCols; colNdx++)
+			vertexArrays.push_back(va::Float(loc+colNdx, numRows, numElements, 4*(int)sizeof(float), (const float*)quadGrid.getUserAttrib(colNdx)));
+	}
+}
+
+void ShaderRenderCase::render (Surface& result, int programID, const QuadGrid& quadGrid)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "pre render");
+
+	// Buffer info.
+	int				width		= result.getWidth();
+	int				height		= result.getHeight();
+
+	int				xOffsetMax	= m_renderCtx.getRenderTarget().getWidth() - width;
+	int				yOffsetMax	= m_renderCtx.getRenderTarget().getHeight() - height;
+
+	deUint32		hash		= deStringHash(m_vertShaderSource.c_str()) + deStringHash(m_fragShaderSource.c_str());
+	de::Random		rnd			(hash);
+
+	int				xOffset		= rnd.getInt(0, xOffsetMax);
+	int				yOffset		= rnd.getInt(0, yOffsetMax);
+
+	gl.viewport(xOffset, yOffset, width, height);
+
+	// Setup program.
+	setupUniforms(programID, quadGrid.getConstCoords());
+	setupDefaultInputs(programID);
+
+	// Clear.
+	gl.clearColor(m_clearColor.x(), m_clearColor.y(), m_clearColor.z(), m_clearColor.w());
+	gl.clear(GL_COLOR_BUFFER_BIT);
+
+	// Draw.
+	{
+		std::vector<VertexArrayBinding>	vertexArrays;
+		const int						numElements		= quadGrid.getNumTriangles()*3;
+
+		getDefaultVertexArrays(gl, quadGrid, programID, vertexArrays);
+		draw(m_renderCtx, programID, (int)vertexArrays.size(), &vertexArrays[0], pr::Triangles(numElements, quadGrid.getIndices()));
+	}
+	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
+
+	// Read back results.
+	glu::readPixels(m_renderCtx, xOffset, yOffset, result.getAccess());
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "post render");
+}
+
+void ShaderRenderCase::computeVertexReference (Surface& result, const QuadGrid& quadGrid)
+{
+	// Buffer info.
+	int					width		= result.getWidth();
+	int					height		= result.getHeight();
+	int					gridSize	= quadGrid.getGridSize();
+	int					stride		= gridSize + 1;
+	bool				hasAlpha	= m_renderCtx.getRenderTarget().getPixelFormat().alphaBits > 0;
+	ShaderEvalContext	evalCtx		(quadGrid);
+
+	// Evaluate color for each vertex.
+	vector<Vec4> colors((gridSize+1)*(gridSize+1));
+	for (int y = 0; y < gridSize+1; y++)
+	for (int x = 0; x < gridSize+1; x++)
+	{
+		float				sx			= x / (float)gridSize;
+		float				sy			= y / (float)gridSize;
+		int					vtxNdx		= ((y * (gridSize+1)) + x);
+
+		evalCtx.reset(sx, sy);
+		m_evaluator.evaluate(evalCtx);
+		DE_ASSERT(!evalCtx.isDiscarded); // Discard is not available in vertex shader.
+		Vec4 color = evalCtx.color;
+
+		if (!hasAlpha)
+			color.w() = 1.0f;
+
+		colors[vtxNdx] = color;
+	}
+
+	// Render quads.
+	for (int y = 0; y < gridSize; y++)
+	for (int x = 0; x < gridSize; x++)
+	{
+		float x0 = x / (float)gridSize;
+		float x1 = (x + 1) / (float)gridSize;
+		float y0 = y / (float)gridSize;
+		float y1 = (y + 1) / (float)gridSize;
+
+		float sx0 = x0 * (float)width;
+		float sx1 = x1 * (float)width;
+		float sy0 = y0 * (float)height;
+		float sy1 = y1 * (float)height;
+		float oosx = 1.0f / (sx1 - sx0);
+		float oosy = 1.0f / (sy1 - sy0);
+
+		int ix0 = deCeilFloatToInt32(sx0 - 0.5f);
+		int ix1 = deCeilFloatToInt32(sx1 - 0.5f);
+		int iy0 = deCeilFloatToInt32(sy0 - 0.5f);
+		int iy1 = deCeilFloatToInt32(sy1 - 0.5f);
+
+		int		v00 = (y * stride) + x;
+		int		v01 = (y * stride) + x + 1;
+		int		v10 = ((y + 1) * stride) + x;
+		int		v11 = ((y + 1) * stride) + x + 1;
+		Vec4	c00 = colors[v00];
+		Vec4	c01 = colors[v01];
+		Vec4	c10 = colors[v10];
+		Vec4	c11 = colors[v11];
+
+		//printf("(%d,%d) -> (%f..%f, %f..%f) (%d..%d, %d..%d)\n", x, y, sx0, sx1, sy0, sy1, ix0, ix1, iy0, iy1);
+
+		for (int iy = iy0; iy < iy1; iy++)
+		for (int ix = ix0; ix < ix1; ix++)
+		{
+			DE_ASSERT(deInBounds32(ix, 0, width));
+			DE_ASSERT(deInBounds32(iy, 0, height));
+
+			float		sfx		= (float)ix + 0.5f;
+			float		sfy		= (float)iy + 0.5f;
+			float		fx1		= deFloatClamp((sfx - sx0) * oosx, 0.0f, 1.0f);
+			float		fy1		= deFloatClamp((sfy - sy0) * oosy, 0.0f, 1.0f);
+
+			// Triangle quad interpolation.
+			bool		tri		= fx1 + fy1 <= 1.0f;
+			float		tx		= tri ? fx1 : (1.0f-fx1);
+			float		ty		= tri ? fy1 : (1.0f-fy1);
+			const Vec4&	t0		= tri ? c00 : c11;
+			const Vec4&	t1		= tri ? c01 : c10;
+			const Vec4&	t2		= tri ? c10 : c01;
+			Vec4		color	= t0 + (t1-t0)*tx + (t2-t0)*ty;
+
+			result.setPixel(ix, iy, toRGBA(color));
+		}
+	}
+}
+
+void ShaderRenderCase::computeFragmentReference (Surface& result, const QuadGrid& quadGrid)
+{
+	// Buffer info.
+	int					width		= result.getWidth();
+	int					height		= result.getHeight();
+	bool				hasAlpha	= m_renderCtx.getRenderTarget().getPixelFormat().alphaBits > 0;
+	ShaderEvalContext	evalCtx		(quadGrid);
+
+	// Render.
+	for (int y = 0; y < height; y++)
+	for (int x = 0; x < width; x++)
+	{
+		float sx = ((float)x + 0.5f) / (float)width;
+		float sy = ((float)y + 0.5f) / (float)height;
+
+		evalCtx.reset(sx, sy);
+		m_evaluator.evaluate(evalCtx);
+		// Select either clear color or computed color based on discarded bit.
+		Vec4 color = evalCtx.isDiscarded ? m_clearColor : evalCtx.color;
+
+		if (!hasAlpha)
+			color.w() = 1.0f;
+
+		result.setPixel(x, y, toRGBA(color));
+	}
+}
+
+bool ShaderRenderCase::compareImages (const Surface& resImage, const Surface& refImage, float errorThreshold)
+{
+	return tcu::fuzzyCompare(m_testCtx.getLog(), "ComparisonResult", "Image comparison result", refImage, resImage, errorThreshold, tcu::COMPARE_LOG_RESULT);
+}
+
+// Uniform name helpers.
+
+const char* getIntUniformName (int number)
+{
+	switch (number)
+	{
+		case 0:		return "ui_zero";
+		case 1:		return "ui_one";
+		case 2:		return "ui_two";
+		case 3:		return "ui_three";
+		case 4:		return "ui_four";
+		case 5:		return "ui_five";
+		case 6:		return "ui_six";
+		case 7:		return "ui_seven";
+		case 8:		return "ui_eight";
+		case 101:	return "ui_oneHundredOne";
+		default:
+			DE_ASSERT(false);
+			return "";
+	}
+}
+
+const char* getFloatUniformName (int number)
+{
+	switch (number)
+	{
+		case 0:	return "uf_zero";
+		case 1: return "uf_one";
+		case 2: return "uf_two";
+		case 3: return "uf_three";
+		case 4: return "uf_four";
+		case 5: return "uf_five";
+		case 6: return "uf_six";
+		case 7: return "uf_seven";
+		case 8: return "uf_eight";
+		default:
+			DE_ASSERT(false);
+			return "";
+	}
+}
+
+const char* getFloatFractionUniformName (int number)
+{
+	switch (number)
+	{
+		case 1: return "uf_one";
+		case 2: return "uf_half";
+		case 3: return "uf_third";
+		case 4: return "uf_fourth";
+		case 5: return "uf_fifth";
+		case 6: return "uf_sixth";
+		case 7: return "uf_seventh";
+		case 8: return "uf_eighth";
+		default:
+			DE_ASSERT(false);
+			return "";
+	}
+}
+
+void setupDefaultUniforms (const glu::RenderContext& context, deUint32 programID)
+{
+	const glw::Functions& gl = context.getFunctions();
+
+	// Bool.
+	struct BoolUniform { const char* name; bool value; };
+	static const BoolUniform s_boolUniforms[] =
+	{
+		{ "ub_true",	true },
+		{ "ub_false",	false },
+	};
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_boolUniforms); i++)
+	{
+		int uniLoc = gl.getUniformLocation(programID, s_boolUniforms[i].name);
+		if (uniLoc != -1)
+			gl.uniform1i(uniLoc, s_boolUniforms[i].value);
+	}
+
+	// BVec4.
+	struct BVec4Uniform { const char* name; BVec4 value; };
+	static const BVec4Uniform s_bvec4Uniforms[] =
+	{
+		{ "ub4_true",	BVec4(true) },
+		{ "ub4_false",	BVec4(false) },
+	};
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_bvec4Uniforms); i++)
+	{
+		const BVec4Uniform& uni = s_bvec4Uniforms[i];
+		int arr[4];
+		arr[0] = (int)uni.value.x();
+		arr[1] = (int)uni.value.y();
+		arr[2] = (int)uni.value.z();
+		arr[3] = (int)uni.value.w();
+		int uniLoc = gl.getUniformLocation(programID, uni.name);
+		if (uniLoc != -1)
+			gl.uniform4iv(uniLoc, 1, &arr[0]);
+	}
+
+	// Int.
+	struct IntUniform { const char* name; int value; };
+	static const IntUniform s_intUniforms[] =
+	{
+		{ "ui_minusOne",		-1 },
+		{ "ui_zero",			0 },
+		{ "ui_one",				1 },
+		{ "ui_two",				2 },
+		{ "ui_three",			3 },
+		{ "ui_four",			4 },
+		{ "ui_five",			5 },
+		{ "ui_six",				6 },
+		{ "ui_seven",			7 },
+		{ "ui_eight",			8 },
+		{ "ui_oneHundredOne",	101 }
+	};
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_intUniforms); i++)
+	{
+		int uniLoc = gl.getUniformLocation(programID, s_intUniforms[i].name);
+		if (uniLoc != -1)
+			gl.uniform1i(uniLoc, s_intUniforms[i].value);
+	}
+
+	// IVec2.
+	struct IVec2Uniform { const char* name; IVec2 value; };
+	static const IVec2Uniform s_ivec2Uniforms[] =
+	{
+		{ "ui2_minusOne",	IVec2(-1) },
+		{ "ui2_zero",		IVec2(0) },
+		{ "ui2_one",		IVec2(1) },
+		{ "ui2_two",		IVec2(2) },
+		{ "ui2_four",		IVec2(4) },
+		{ "ui2_five",		IVec2(5) }
+	};
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_ivec2Uniforms); i++)
+	{
+		int uniLoc = gl.getUniformLocation(programID, s_ivec2Uniforms[i].name);
+		if (uniLoc != -1)
+			gl.uniform2iv(uniLoc, 1, s_ivec2Uniforms[i].value.getPtr());
+	}
+
+	// IVec3.
+	struct IVec3Uniform { const char* name; IVec3 value; };
+	static const IVec3Uniform s_ivec3Uniforms[] =
+	{
+		{ "ui3_minusOne",	IVec3(-1) },
+		{ "ui3_zero",		IVec3(0) },
+		{ "ui3_one",		IVec3(1) },
+		{ "ui3_two",		IVec3(2) },
+		{ "ui3_four",		IVec3(4) },
+		{ "ui3_five",		IVec3(5) }
+	};
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_ivec3Uniforms); i++)
+	{
+		int uniLoc = gl.getUniformLocation(programID, s_ivec3Uniforms[i].name);
+		if (uniLoc != -1)
+			gl.uniform3iv(uniLoc, 1, s_ivec3Uniforms[i].value.getPtr());
+	}
+
+	// IVec4.
+	struct IVec4Uniform { const char* name; IVec4 value; };
+	static const IVec4Uniform s_ivec4Uniforms[] =
+	{
+		{ "ui4_minusOne",	IVec4(-1) },
+		{ "ui4_zero",		IVec4(0) },
+		{ "ui4_one",		IVec4(1) },
+		{ "ui4_two",		IVec4(2) },
+		{ "ui4_four",		IVec4(4) },
+		{ "ui4_five",		IVec4(5) }
+	};
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_ivec4Uniforms); i++)
+	{
+		int uniLoc = gl.getUniformLocation(programID, s_ivec4Uniforms[i].name);
+		if (uniLoc != -1)
+			gl.uniform4iv(uniLoc, 1, s_ivec4Uniforms[i].value.getPtr());
+	}
+
+	// Float.
+	struct FloatUniform { const char* name; float value; };
+	static const FloatUniform s_floatUniforms[] =
+	{
+		{ "uf_zero",	0.0f },
+		{ "uf_one",		1.0f },
+		{ "uf_two",		2.0f },
+		{ "uf_three",	3.0f },
+		{ "uf_four",	4.0f },
+		{ "uf_five",	5.0f },
+		{ "uf_six",		6.0f },
+		{ "uf_seven",	7.0f },
+		{ "uf_eight",	8.0f },
+		{ "uf_half",	1.0f / 2.0f },
+		{ "uf_third",	1.0f / 3.0f },
+		{ "uf_fourth",	1.0f / 4.0f },
+		{ "uf_fifth",	1.0f / 5.0f },
+		{ "uf_sixth",	1.0f / 6.0f },
+		{ "uf_seventh",	1.0f / 7.0f },
+		{ "uf_eighth",	1.0f / 8.0f }
+	};
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_floatUniforms); i++)
+	{
+		int uniLoc = gl.getUniformLocation(programID, s_floatUniforms[i].name);
+		if (uniLoc != -1)
+			gl.uniform1f(uniLoc, s_floatUniforms[i].value);
+	}
+
+	// Vec2.
+	struct Vec2Uniform { const char* name; Vec2 value; };
+	static const Vec2Uniform s_vec2Uniforms[] =
+	{
+		{ "uv2_minusOne",	Vec2(-1.0f) },
+		{ "uv2_zero",		Vec2(0.0f) },
+		{ "uv2_half",		Vec2(0.5f) },
+		{ "uv2_one",		Vec2(1.0f) },
+		{ "uv2_two",		Vec2(2.0f) },
+	};
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_vec2Uniforms); i++)
+	{
+		int uniLoc = gl.getUniformLocation(programID, s_vec2Uniforms[i].name);
+		if (uniLoc != -1)
+			gl.uniform2fv(uniLoc, 1, s_vec2Uniforms[i].value.getPtr());
+	}
+
+	// Vec3.
+	struct Vec3Uniform { const char* name; Vec3 value; };
+	static const Vec3Uniform s_vec3Uniforms[] =
+	{
+		{ "uv3_minusOne",	Vec3(-1.0f) },
+		{ "uv3_zero",		Vec3(0.0f) },
+		{ "uv3_half",		Vec3(0.5f) },
+		{ "uv3_one",		Vec3(1.0f) },
+		{ "uv3_two",		Vec3(2.0f) },
+	};
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_vec3Uniforms); i++)
+	{
+		int uniLoc = gl.getUniformLocation(programID, s_vec3Uniforms[i].name);
+		if (uniLoc != -1)
+			gl.uniform3fv(uniLoc, 1, s_vec3Uniforms[i].value.getPtr());
+	}
+
+	// Vec4.
+	struct Vec4Uniform { const char* name; Vec4 value; };
+	static const Vec4Uniform s_vec4Uniforms[] =
+	{
+		{ "uv4_minusOne",	Vec4(-1.0f) },
+		{ "uv4_zero",		Vec4(0.0f) },
+		{ "uv4_half",		Vec4(0.5f) },
+		{ "uv4_one",		Vec4(1.0f) },
+		{ "uv4_two",		Vec4(2.0f) },
+		{ "uv4_black",		Vec4(0.0f, 0.0f, 0.0f, 1.0f) },
+		{ "uv4_gray",		Vec4(0.5f, 0.5f, 0.5f, 1.0f) },
+		{ "uv4_white",		Vec4(1.0f, 1.0f, 1.0f, 1.0f) },
+	};
+
+	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_vec4Uniforms); i++)
+	{
+		int uniLoc = gl.getUniformLocation(programID, s_vec4Uniforms[i].name);
+		if (uniLoc != -1)
+			gl.uniform4fv(uniLoc, 1, s_vec4Uniforms[i].value.getPtr());
+	}
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsShaderRenderCase.hpp b/modules/glshared/glsShaderRenderCase.hpp
new file mode 100644
index 0000000..e8299c8
--- /dev/null
+++ b/modules/glshared/glsShaderRenderCase.hpp
@@ -0,0 +1,269 @@
+#ifndef _GLSSHADERRENDERCASE_HPP
+#define _GLSSHADERRENDERCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Shader execute test.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "tcuVector.hpp"
+#include "tcuMatrix.hpp"
+#include "tcuTexture.hpp"
+#include "tcuSurface.hpp"
+#include "gluRenderContext.hpp"
+#include "gluContextInfo.hpp"
+#include "gluShaderProgram.hpp"
+
+#include <sstream>
+#include <string>
+
+namespace glu
+{
+class RenderContext;
+class Texture2D;
+class TextureCube;
+class Texture2DArray;
+class Texture3D;
+} // glu
+
+namespace deqp
+{
+namespace gls
+{
+
+// LineStream \todo [2011-10-17 pyry] Move to proper place!
+
+class LineStream
+{
+public:
+						LineStream		(int indent = 0)	{ m_indent = indent; }
+						~LineStream		(void)				{}
+
+	const char*			str				(void) const		{ m_string = m_stream.str(); return m_string.c_str(); }
+	LineStream&			operator<<		(const char* line)	{ for (int i = 0; i < m_indent; i++) { m_stream << "\t"; } m_stream << line << "\n"; return *this; }
+
+private:
+	int					m_indent;
+	std::ostringstream	m_stream;
+	mutable std::string	m_string;
+};
+
+class QuadGrid;
+
+// TextureBinding
+
+class TextureBinding
+{
+public:
+	enum Type
+	{
+		TYPE_NONE = 0,
+		TYPE_2D,
+		TYPE_CUBE_MAP,
+		TYPE_2D_ARRAY,
+		TYPE_3D,
+
+		TYPE_LAST
+	};
+
+								TextureBinding		(const glu::Texture2D* tex2D, const tcu::Sampler& sampler);
+								TextureBinding		(const glu::TextureCube* texCube, const tcu::Sampler& sampler);
+								TextureBinding		(const glu::Texture2DArray* tex2DArray, const tcu::Sampler& sampler);
+								TextureBinding		(const glu::Texture3D* tex3D, const tcu::Sampler& sampler);
+								TextureBinding		(void);
+
+	void						setSampler			(const tcu::Sampler& sampler);
+	void						setTexture			(const glu::Texture2D* tex2D);
+	void						setTexture			(const glu::TextureCube* texCube);
+	void						setTexture			(const glu::Texture2DArray* tex2DArray);
+	void						setTexture			(const glu::Texture3D* tex3D);
+
+	Type						getType				(void) const { return m_type;		}
+	const tcu::Sampler&			getSampler			(void) const { return m_sampler;	}
+	const glu::Texture2D*		get2D				(void) const { DE_ASSERT(getType() == TYPE_2D);			return m_binding.tex2D;		}
+	const glu::TextureCube*		getCube				(void) const { DE_ASSERT(getType() == TYPE_CUBE_MAP);	return m_binding.texCube;	}
+	const glu::Texture2DArray*	get2DArray			(void) const { DE_ASSERT(getType() == TYPE_2D_ARRAY);	return m_binding.tex2DArray;}
+	const glu::Texture3D*		get3D				(void) const { DE_ASSERT(getType() == TYPE_3D);			return m_binding.tex3D;		}
+
+private:
+	Type					m_type;
+	tcu::Sampler			m_sampler;
+	union
+	{
+		const glu::Texture2D*		tex2D;
+		const glu::TextureCube*		texCube;
+		const glu::Texture2DArray*	tex2DArray;
+		const glu::Texture3D*		tex3D;
+	} m_binding;
+};
+
+// ShaderEvalContext.
+
+class ShaderEvalContext
+{
+public:
+	// Limits.
+	enum
+	{
+		MAX_USER_ATTRIBS	= 4,
+		MAX_TEXTURES		= 4,
+	};
+
+	struct ShaderSampler
+	{
+		tcu::Sampler				sampler;
+		const tcu::Texture2D*		tex2D;
+		const tcu::TextureCube*		texCube;
+		const tcu::Texture2DArray*	tex2DArray;
+		const tcu::Texture3D*		tex3D;
+
+		inline ShaderSampler (void)
+			: tex2D		(DE_NULL)
+			, texCube	(DE_NULL)
+			, tex2DArray(DE_NULL)
+			, tex3D		(DE_NULL)
+		{
+		}
+	};
+
+							ShaderEvalContext		(const QuadGrid& quadGrid);
+							~ShaderEvalContext		(void);
+
+	void					reset					(float sx, float sy);
+
+	// Inputs.
+	tcu::Vec4				coords;
+	tcu::Vec4				unitCoords;
+	tcu::Vec4				constCoords;
+
+	tcu::Vec4				in[MAX_USER_ATTRIBS];
+	ShaderSampler			textures[MAX_TEXTURES];
+
+	// Output.
+	tcu::Vec4				color;
+	bool					isDiscarded;
+
+	// Functions.
+	inline void				discard					(void)	{ isDiscarded = true; }
+	tcu::Vec4				texture2D				(int unitNdx, const tcu::Vec2& coords);
+
+private:
+	const QuadGrid&			quadGrid;
+};
+
+// ShaderEvalFunc.
+
+typedef void (*ShaderEvalFunc) (ShaderEvalContext& c);
+
+inline void evalCoordsPassthroughX		(ShaderEvalContext& c) { c.color.x() = c.coords.x(); }
+inline void evalCoordsPassthroughXY		(ShaderEvalContext& c) { c.color.xy() = c.coords.swizzle(0,1); }
+inline void evalCoordsPassthroughXYZ	(ShaderEvalContext& c) { c.color.xyz() = c.coords.swizzle(0,1,2); }
+inline void evalCoordsPassthrough		(ShaderEvalContext& c) { c.color = c.coords; }
+inline void evalCoordsSwizzleWZYX		(ShaderEvalContext& c) { c.color = c.coords.swizzle(3,2,1,0); }
+
+// ShaderEvaluator
+// Either inherit a class with overridden evaluate() or just pass in an evalFunc.
+
+class ShaderEvaluator
+{
+public:
+						ShaderEvaluator			(void);
+						ShaderEvaluator			(ShaderEvalFunc evalFunc);
+	virtual				~ShaderEvaluator		(void);
+
+	virtual void		evaluate				(ShaderEvalContext& ctx);
+
+private:
+						ShaderEvaluator			(const ShaderEvaluator&);	// not allowed!
+	ShaderEvaluator&	operator=				(const ShaderEvaluator&);	// not allowed!
+
+	ShaderEvalFunc		m_evalFunc;
+};
+
+// ShaderRenderCase.
+
+class ShaderRenderCase : public tcu::TestCase
+{
+public:
+								ShaderRenderCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc);
+								ShaderRenderCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const char* description, bool isVertexCase, ShaderEvaluator& evaluator);
+	virtual						~ShaderRenderCase		(void);
+
+	void						init					(void);
+	void						deinit					(void);
+
+	IterateResult				iterate					(void);
+
+protected:
+	virtual void				setupShaderData			(void);
+	virtual void				setup					(int programID);
+	virtual void				setupUniforms			(int programID, const tcu::Vec4& constCoords);
+
+	tcu::IVec2					getViewportSize			(void) const;
+
+	class CompileFailed : public tcu::TestError
+	{
+	public:
+		inline CompileFailed (const char* file, int line) : tcu::TestError("Failed to compile shader program", DE_NULL, file, line) {}
+	};
+
+private:
+								ShaderRenderCase		(const ShaderRenderCase&);		// not allowed!
+	ShaderRenderCase&			operator=				(const ShaderRenderCase&);		// not allowed!
+
+	void						setupDefaultInputs		(int programID);
+
+	void						render					(tcu::Surface& result, int programID, const QuadGrid& quadGrid);
+	void						computeVertexReference	(tcu::Surface& result, const QuadGrid& quadGrid);
+	void						computeFragmentReference(tcu::Surface& result, const QuadGrid& quadGrid);
+	bool						compareImages			(const tcu::Surface& resImage, const tcu::Surface& refImage, float errorThreshold);
+
+protected:
+	glu::RenderContext&			m_renderCtx;
+	const glu::ContextInfo&		m_ctxInfo;
+
+	bool						m_isVertexCase;
+	ShaderEvaluator				m_defaultEvaluator;
+	ShaderEvaluator&			m_evaluator;
+	std::string					m_vertShaderSource;
+	std::string					m_fragShaderSource;
+	tcu::Vec4					m_clearColor;
+
+	std::vector<tcu::Mat4>		m_userAttribTransforms;
+	std::vector<TextureBinding>	m_textures;
+
+	glu::ShaderProgram*			m_program;
+};
+
+// Helpers.
+// \todo [2012-04-10 pyry] Move these to separate utility?
+
+const char*		getIntUniformName			(int number);
+const char*		getFloatUniformName			(int number);
+const char*		getFloatFractionUniformName	(int number);
+
+void			setupDefaultUniforms		(const glu::RenderContext& context, deUint32 programID);
+
+} // gls
+} // deqp
+
+#endif // _GLSSHADERRENDERCASE_HPP
diff --git a/modules/glshared/glsStateChangePerfTestCases.cpp b/modules/glshared/glsStateChangePerfTestCases.cpp
new file mode 100644
index 0000000..aacb05e
--- /dev/null
+++ b/modules/glshared/glsStateChangePerfTestCases.cpp
@@ -0,0 +1,729 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State change performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsStateChangePerfTestCases.hpp"
+
+#include "tcuTestLog.hpp"
+
+#include "gluDefs.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+
+#include "deStringUtil.hpp"
+
+#include "deClock.h"
+
+#include <vector>
+#include <algorithm>
+
+using std::vector;
+using std::string;
+using tcu::TestLog;
+using namespace glw;
+
+namespace deqp
+{
+namespace gls
+{
+
+namespace
+{
+
+struct ResultStats
+{
+	double		median;
+	double 		mean;
+	double		variance;
+
+	deUint64	min;
+	deUint64	max;
+};
+
+ResultStats calculateStats (const vector<deUint64>& values)
+{
+	ResultStats result = { 0.0, 0.0, 0.0, 0xFFFFFFFFFFFFFFFFu, 0 };
+
+	deUint64 sum = 0;
+
+	for (int i = 0; i < (int)values.size(); i++)
+		sum += values[i];
+
+	result.mean = ((double)sum) / values.size();
+
+	for (int i = 0; i < (int)values.size(); i++)
+	{
+		const double val = (double)values[i];
+		result.variance += (val - result.mean) * (val - result.mean);
+	}
+
+	result.variance /= values.size();
+
+	{
+		const int n = (int)(values.size()/2);
+
+		vector<deUint64> sortedValues = values;
+
+		std::sort(sortedValues.begin(), sortedValues.end());
+
+		result.median = (double)sortedValues[n];
+	}
+
+	for (int i = 0; i < (int)values.size(); i++)
+	{
+		result.min = std::min(result.min, values[i]);
+		result.max = std::max(result.max, values[i]);
+	}
+
+	return result;
+}
+
+
+void genIndices (vector<GLushort>& indices, int triangleCount)
+{
+	indices.reserve(triangleCount*3);
+
+	for (int triangleNdx = 0; triangleNdx < triangleCount; triangleNdx++)
+	{
+		indices.push_back((GLushort)triangleNdx*3);
+		indices.push_back((GLushort)triangleNdx*3+1);
+		indices.push_back((GLushort)triangleNdx*3+2);
+	}
+}
+
+void genCoords (vector<GLfloat>& coords, int triangleCount)
+{
+	coords.reserve(triangleCount * 3 * 2);
+
+	for (int triangleNdx = 0; triangleNdx < triangleCount; triangleNdx++)
+	{
+		if ((triangleNdx % 2) == 0)
+		{
+			// CW
+			coords.push_back(-1.0f);
+			coords.push_back(-1.0f);
+
+			coords.push_back( 1.0f);
+			coords.push_back(-1.0f);
+
+			coords.push_back( 1.0f);
+			coords.push_back( 1.0f);
+		}
+		else
+		{
+			// CCW
+			coords.push_back(-1.0f);
+			coords.push_back(-1.0f);
+
+			coords.push_back(-1.0f);
+			coords.push_back( 1.0f);
+
+			coords.push_back( 1.0f);
+			coords.push_back( 1.0f);
+		}
+	}
+}
+
+void genTextureData (vector<deUint8>& data, int width, int height)
+{
+	data.clear();
+	data.reserve(width*height*4);
+
+	for (int x = 0; x < width; x++)
+	{
+		for (int y = 0; y < height; y++)
+		{
+			data.push_back((deUint8)((255*x)/width));
+			data.push_back((deUint8)((255*y)/width));
+			data.push_back((deUint8)((255*x*y)/(width*height)));
+			data.push_back(255);
+		}
+	}
+}
+
+double calculateVariance (const vector<deUint64>& values, double avg)
+{
+	double sum = 0.0;
+
+	for (int valueNdx = 0; valueNdx < (int)values.size(); valueNdx++)
+	{
+		double value = (double)values[valueNdx];
+		sum += (value - avg) * (value - avg);
+	}
+
+	return sum / values.size();
+}
+
+deUint64 findMin (const vector<deUint64>& values)
+{
+	deUint64 min = ~0ull;
+
+	for (int valueNdx = 0; valueNdx < (int)values.size(); valueNdx++)
+		min = std::min(values[valueNdx], min);
+
+	return min;
+}
+
+deUint64 findMax (const vector<deUint64>& values)
+{
+	deUint64 max = 0;
+
+	for (int valueNdx = 0; valueNdx < (int)values.size(); valueNdx++)
+		max = std::max(values[valueNdx], max);
+
+	return max;
+}
+
+deUint64 findMedian (const vector<deUint64>& v)
+{
+	vector<deUint64> values = v;
+	size_t n = values.size() / 2;
+
+	std::nth_element(values.begin(), values.begin() + n, values.end());
+
+	return values[n];
+}
+
+} // anonymous
+
+StateChangePerformanceCase::StateChangePerformanceCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, DrawType drawType, int drawCallCount, int triangleCount)
+	: tcu::TestCase		(testCtx, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_drawType		(drawType)
+	, m_iterationCount	(100)
+	, m_callCount		(drawCallCount)
+	, m_triangleCount	(triangleCount)
+{
+}
+
+StateChangePerformanceCase::~StateChangePerformanceCase (void)
+{
+	StateChangePerformanceCase::deinit();
+}
+
+void StateChangePerformanceCase::init (void)
+{
+	if (m_drawType == DRAWTYPE_INDEXED_USER_PTR)
+		genIndices(m_indices, m_triangleCount);
+}
+
+void StateChangePerformanceCase::requireIndexBuffers (int count)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if ((int)m_indexBuffers.size() >= count)
+		return;
+
+	m_indexBuffers.reserve(count);
+
+	vector<GLushort> indices;
+	genIndices(indices, m_triangleCount);
+
+	while ((int)m_indexBuffers.size() < count)
+	{
+		GLuint buffer;
+
+		gl.genBuffers(1, &buffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenBuffers()");
+
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(indices.size() * sizeof(GLushort)), &(indices[0]), GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBufferData()");
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+
+		m_indexBuffers.push_back(buffer);
+	}
+}
+
+void StateChangePerformanceCase::requireCoordBuffers (int count)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if ((int)m_coordBuffers.size() >= count)
+		return;
+
+	m_coordBuffers.reserve(count);
+
+	vector<GLfloat> coords;
+	genCoords(coords, m_triangleCount);
+
+	while ((int)m_coordBuffers.size() < count)
+	{
+		GLuint buffer;
+
+		gl.genBuffers(1, &buffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenBuffers()");
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, buffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+		gl.bufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(coords.size() * sizeof(GLfloat)), &(coords[0]), GL_STATIC_DRAW);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBufferData()");
+		gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
+
+		m_coordBuffers.push_back(buffer);
+	}
+}
+
+void StateChangePerformanceCase::requirePrograms (int count)
+{
+	if ((int)m_programs.size() >= count)
+		return;
+
+	m_programs.reserve(count);
+
+	while ((int)m_programs.size() < count)
+	{
+		string vertexShaderSource =
+			"attribute mediump vec2 a_coord;\n"
+			"varying mediump vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\tv_texCoord = vec2(0.5) + 0.5" + de::toString(m_programs.size()) + " * a_coord.xy;\n"
+			"\tgl_Position = vec4(a_coord, 0.5, 1.0);\n"
+			"}";
+
+		string fragmentShaderSource =
+			"uniform sampler2D u_sampler;\n"
+			"varying mediump vec2 v_texCoord;\n"
+			"void main (void)\n"
+			"{\n"
+			"\tgl_FragColor = vec4(1.0" + de::toString(m_programs.size()) + " * texture2D(u_sampler, v_texCoord).xyz, 1.0);\n"
+			"}";
+
+		glu::ShaderProgram* program = new glu::ShaderProgram(m_renderCtx, glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(fragmentShaderSource));
+
+		if (!program->isOk())
+		{
+			m_testCtx.getLog() << *program;
+			delete program;
+			TCU_FAIL("Compile failed");
+		}
+
+		m_programs.push_back(program);
+	}
+}
+
+void StateChangePerformanceCase::requireTextures (int count)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	const int textureWidth	= 64;
+	const int textureHeight	= 64;
+
+	if ((int)m_textures.size() >= count)
+		return;
+
+	m_textures.reserve(count);
+
+	vector<deUint8> textureData;
+	genTextureData(textureData, textureWidth, textureHeight);
+
+	DE_ASSERT(textureData.size() == textureWidth * textureHeight * 4);
+
+	while ((int)m_textures.size() < count)
+	{
+		GLuint texture;
+
+		gl.genTextures(1, &texture);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures()");
+
+		gl.bindTexture(GL_TEXTURE_2D, texture);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+		gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, &(textureData[0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage2D()");
+
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri()");
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri()");
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		GL_REPEAT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri()");
+		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		GL_REPEAT);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri()");
+
+		gl.bindTexture(GL_TEXTURE_2D, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture()");
+
+		m_textures.push_back(texture);
+	}
+}
+
+void StateChangePerformanceCase::requireFramebuffers (int count)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if ((int)m_framebuffers.size() >= count)
+		return;
+
+	m_framebuffers.reserve(count);
+
+	requireRenderbuffers(count);
+
+	while ((int)m_framebuffers.size() < count)
+	{
+		GLuint framebuffer;
+
+		gl.genFramebuffers(1, &framebuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenFramebuffers()");
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()");
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, m_renderbuffers[m_framebuffers.size()]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer()");
+
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffers[m_framebuffers.size()]);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glFramebufferRenderbuffer()");
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer()");
+
+		gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()");
+
+		m_framebuffers.push_back(framebuffer);
+	}
+}
+
+void StateChangePerformanceCase::requireRenderbuffers (int count)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if ((int)m_renderbuffers.size() >= count)
+		return;
+
+	m_renderbuffers.reserve(count);
+
+	while ((int)m_renderbuffers.size() < count)
+	{
+		GLuint renderbuffer;
+
+		gl.genRenderbuffers(1, &renderbuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenRenderbuffers()");
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer()");
+
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGB565, 24, 24);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glRenderbufferStorage()");
+
+		gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer()");
+
+		m_renderbuffers.push_back(renderbuffer);
+	}
+}
+
+void StateChangePerformanceCase::requireSamplers (int count)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if ((int)m_samplers.size() >= count)
+		return;
+
+	m_samplers.reserve(count);
+
+	while ((int)m_samplers.size() < count)
+	{
+		GLuint sampler;
+		gl.genSamplers(1, &sampler);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenSamplers()");
+		m_samplers.push_back(sampler);
+	}
+}
+
+void StateChangePerformanceCase::requireVertexArrays (int count)
+{
+	const glw::Functions& gl = m_renderCtx.getFunctions();
+
+	if ((int)m_vertexArrays.size() >= count)
+		return;
+
+	m_vertexArrays.reserve(count);
+
+	while ((int)m_vertexArrays.size() < count)
+	{
+		GLuint vertexArray;
+		gl.genVertexArrays(1, &vertexArray);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenVertexArrays()");
+		m_vertexArrays.push_back(vertexArray);
+	}
+}
+
+void StateChangePerformanceCase::deinit (void)
+{
+	m_indices.clear();
+	m_interleavedResults.clear();
+	m_batchedResults.clear();
+
+	{
+		const glw::Functions& gl = m_renderCtx.getFunctions();
+
+		if (!m_indexBuffers.empty())
+		{
+			gl.deleteBuffers((GLsizei)m_indexBuffers.size(), &(m_indexBuffers[0]));
+			m_indexBuffers.clear();
+		}
+
+		if (!m_coordBuffers.empty())
+		{
+			gl.deleteBuffers((GLsizei)m_coordBuffers.size(), &(m_coordBuffers[0]));
+			m_coordBuffers.clear();
+		}
+
+		if (!m_textures.empty())
+		{
+			gl.deleteTextures((GLsizei)m_textures.size(), &(m_textures[0]));
+			m_textures.clear();
+		}
+
+		if (!m_framebuffers.empty())
+		{
+			gl.deleteFramebuffers((GLsizei)m_framebuffers.size(), &(m_framebuffers[0]));
+			m_framebuffers.clear();
+		}
+
+		if (!m_renderbuffers.empty())
+		{
+			gl.deleteRenderbuffers((GLsizei)m_renderbuffers.size(), &(m_renderbuffers[0]));
+			m_renderbuffers.clear();
+		}
+
+		if (!m_samplers.empty())
+		{
+			gl.deleteSamplers((GLsizei)m_samplers.size(), &m_samplers[0]);
+			m_samplers.clear();
+		}
+
+		if (!m_vertexArrays.empty())
+		{
+			gl.deleteVertexArrays((GLsizei)m_vertexArrays.size(), &m_vertexArrays[0]);
+			m_vertexArrays.clear();
+		}
+
+		for (int programNdx = 0; programNdx < (int)m_programs.size(); programNdx++)
+		{
+			delete m_programs[programNdx];
+			m_programs[programNdx] = NULL;
+		}
+		m_programs.clear();
+	}
+}
+
+void StateChangePerformanceCase::logAndSetTestResult (void)
+{
+	TestLog&	log			= m_testCtx.getLog();
+
+	ResultStats interleaved	= calculateStats(m_interleavedResults);
+	ResultStats batched		= calculateStats(m_batchedResults);
+
+	log << TestLog::Message << "Interleaved mean: "					<< interleaved.mean						<< TestLog::EndMessage;
+	log << TestLog::Message << "Interleaved median: "				<< interleaved.median					<< TestLog::EndMessage;
+	log << TestLog::Message << "Interleaved variance: "				<< interleaved.variance					<< TestLog::EndMessage;
+	log << TestLog::Message << "Interleaved min: "					<< interleaved.min						<< TestLog::EndMessage;
+	log << TestLog::Message << "Interleaved max: "					<< interleaved.max						<< TestLog::EndMessage;
+
+	log << TestLog::Message << "Batched mean: "						<< batched.mean							<< TestLog::EndMessage;
+	log << TestLog::Message << "Batched median: "					<< batched.median						<< TestLog::EndMessage;
+	log << TestLog::Message << "Batched variance: "					<< batched.variance						<< TestLog::EndMessage;
+	log << TestLog::Message << "Batched min: "						<< batched.min							<< TestLog::EndMessage;
+	log << TestLog::Message << "Batched max: "						<< batched.max							<< TestLog::EndMessage;
+
+	log << TestLog::Message << "Batched/Interleaved mean ratio: "	<< (interleaved.mean/batched.mean)		<< TestLog::EndMessage;
+	log << TestLog::Message << "Batched/Interleaved median ratio: "	<< (interleaved.median/batched.median)	<< TestLog::EndMessage;
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)(((double)interleaved.median) / batched.median), 2).c_str());
+}
+
+tcu::TestCase::IterateResult StateChangePerformanceCase::iterate (void)
+{
+	if (m_interleavedResults.empty() && m_batchedResults.empty())
+	{
+		TestLog& log = m_testCtx.getLog();
+
+		log << TestLog::Message << "Draw call count: " << m_callCount << TestLog::EndMessage;
+		log << TestLog::Message << "Per call triangle count: " << m_triangleCount << TestLog::EndMessage;
+	}
+
+	// \note [mika] Interleave sampling to balance effects of powerstate etc.
+	if ((int)m_interleavedResults.size() < m_iterationCount && m_batchedResults.size() >= m_interleavedResults.size())
+	{
+		const glw::Functions&	gl			= m_renderCtx.getFunctions();
+		deUint64				resBeginUs	= 0;
+		deUint64				resEndUs	= 0;
+
+		setupInitialState(gl);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish()");
+
+		// Render result
+		resBeginUs = deGetMicroseconds();
+
+		renderTest(gl);
+
+		gl.finish();
+		resEndUs = deGetMicroseconds();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish()");
+
+		m_interleavedResults.push_back(resEndUs - resBeginUs);
+
+		return CONTINUE;
+	}
+	else if ((int)m_batchedResults.size() < m_iterationCount)
+	{
+		const glw::Functions&	gl			= m_renderCtx.getFunctions();
+		deUint64				refBeginUs	= 0;
+		deUint64				refEndUs	= 0;
+
+		setupInitialState(gl);
+		gl.finish();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish()");
+
+		// Render reference
+		refBeginUs = deGetMicroseconds();
+
+		renderReference(gl);
+
+		gl.finish();
+		refEndUs = deGetMicroseconds();
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish()");
+
+		m_batchedResults.push_back(refEndUs - refBeginUs);
+
+		return CONTINUE;
+	}
+	else
+	{
+		logAndSetTestResult();
+		return STOP;
+	}
+}
+
+void StateChangePerformanceCase::callDraw (const glw::Functions& gl)
+{
+	switch (m_drawType)
+	{
+		case DRAWTYPE_NOT_INDEXED:		gl.drawArrays(GL_TRIANGLES, 0, m_triangleCount * 3);									break;
+		case DRAWTYPE_INDEXED_USER_PTR:	gl.drawElements(GL_TRIANGLES, m_triangleCount * 3, GL_UNSIGNED_SHORT, &m_indices[0]);	break;
+		case DRAWTYPE_INDEXED_BUFFER:	gl.drawElements(GL_TRIANGLES, m_triangleCount * 3, GL_UNSIGNED_SHORT, NULL);			break;
+		default:
+			DE_ASSERT(false);
+	}
+}
+
+// StateChangeCallPerformanceCase
+
+StateChangeCallPerformanceCase::StateChangeCallPerformanceCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description)
+	: tcu::TestCase 	(testCtx, tcu::NODETYPE_PERFORMANCE, name, description)
+	, m_renderCtx		(renderCtx)
+	, m_iterationCount	(100)
+	, m_callCount		(1000)
+{
+}
+
+StateChangeCallPerformanceCase::~StateChangeCallPerformanceCase (void)
+{
+}
+
+void StateChangeCallPerformanceCase::executeTest (void)
+{
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	deUint64				beginTimeUs		= 0;
+	deUint64				endTimeUs		= 0;
+
+	beginTimeUs = deGetMicroseconds();
+
+	execCalls(gl, (int)m_results.size(), m_callCount);
+
+	endTimeUs = deGetMicroseconds();
+
+	m_results.push_back(endTimeUs - beginTimeUs);
+}
+
+void StateChangeCallPerformanceCase::logTestCase (void)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	log << TestLog::Message << "Iteration count: " << m_iterationCount << TestLog::EndMessage;
+	log << TestLog::Message << "Per iteration call count: " << m_callCount << TestLog::EndMessage;
+}
+
+double calculateAverage (const vector<deUint64>& values)
+{
+	deUint64 sum = 0;
+
+	for (int valueNdx = 0; valueNdx < (int)values.size(); valueNdx++)
+		sum += values[valueNdx];
+
+	return ((double)sum) / values.size();
+}
+
+void StateChangeCallPerformanceCase::logAndSetTestResult (void)
+{
+	TestLog&	log				= m_testCtx.getLog();
+
+	deUint64	minUs			= findMin(m_results);
+	deUint64	maxUs			= findMax(m_results);
+	deUint64	medianUs		= findMedian(m_results);
+	double		avgIterationUs	= calculateAverage(m_results);
+	double		avgCallUs		= avgIterationUs / m_callCount;
+	double		varIteration	= calculateVariance(m_results, avgIterationUs);
+	double		avgMedianCallUs	= ((double)medianUs)/m_callCount;
+
+	log << TestLog::Message << "Min iteration time: "						<< minUs << "us" << TestLog::EndMessage;
+	log << TestLog::Message << "Max iteration time: "						<< maxUs << "us" << TestLog::EndMessage;
+	log << TestLog::Message << "Average iteration time: "					<< avgIterationUs << "us" << TestLog::EndMessage;
+	log << TestLog::Message << "Iteration variance time: "					<< varIteration << TestLog::EndMessage;
+	log << TestLog::Message << "Median iteration time: "					<< medianUs << "us" << TestLog::EndMessage;
+	log << TestLog::Message << "Average call time: "						<< avgCallUs << "us" << TestLog::EndMessage;
+	log << TestLog::Message << "Average call time for median iteration: "	<< avgMedianCallUs << "us" << TestLog::EndMessage;
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)avgMedianCallUs, 3).c_str());
+}
+
+tcu::TestCase::IterateResult StateChangeCallPerformanceCase::iterate (void)
+{
+	if (m_results.empty())
+		logTestCase();
+
+	if ((int)m_results.size() < m_iterationCount)
+	{
+		executeTest();
+		GLU_EXPECT_NO_ERROR(m_renderCtx.getFunctions().getError(), "Unexpected error");
+		return CONTINUE;
+	}
+	else
+	{
+		logAndSetTestResult();
+		return STOP;
+	}
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsStateChangePerfTestCases.hpp b/modules/glshared/glsStateChangePerfTestCases.hpp
new file mode 100644
index 0000000..67fa90a
--- /dev/null
+++ b/modules/glshared/glsStateChangePerfTestCases.hpp
@@ -0,0 +1,136 @@
+#ifndef _GLSSTATECHANGEPERFTESTCASES_HPP
+#define _GLSSTATECHANGEPERFTESTCASES_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State change performance tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+
+namespace glu
+{
+class ShaderProgram;
+class RenderContext;
+}
+
+namespace glw
+{
+class Functions;
+}
+
+namespace deqp
+{
+namespace gls
+{
+
+class StateChangePerformanceCase : public tcu::TestCase
+{
+public:
+	enum DrawType
+	{
+		DRAWTYPE_NOT_INDEXED		= 0,	//!< glDrawArrays()
+		DRAWTYPE_INDEXED_USER_PTR,			//!< glDrawElements(), indices from user pointer.
+		DRAWTYPE_INDEXED_BUFFER,			//!< glDrawElements(), indices in buffer.
+	};
+
+										StateChangePerformanceCase		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, DrawType drawType, int drawCallCount, int triangleCount);
+										~StateChangePerformanceCase		(void);
+
+	void								init							(void);
+	void								deinit							(void);
+
+	IterateResult						iterate							(void);
+
+protected:
+	void								requireIndexBuffers				(int count);
+	void								requireCoordBuffers				(int count);
+	void								requirePrograms					(int count);
+	void								requireTextures					(int count);
+	void								requireFramebuffers				(int count);
+	void								requireRenderbuffers			(int count);
+	void								requireSamplers					(int count);
+	void								requireVertexArrays				(int count);
+
+	virtual void						setupInitialState				(const glw::Functions& gl) = 0;
+	virtual void						renderTest						(const glw::Functions& gl) = 0;
+	virtual void						renderReference					(const glw::Functions& gl) = 0;
+
+	void								callDraw						(const glw::Functions& gl);
+
+	void								logAndSetTestResult				(void);
+
+protected:
+	glu::RenderContext&					m_renderCtx;
+
+	const DrawType						m_drawType;
+	const int							m_iterationCount;
+	const int							m_callCount;
+	const int							m_triangleCount;
+
+	std::vector<deUint32>				m_indexBuffers;
+	std::vector<deUint32>				m_coordBuffers;
+	std::vector<deUint32>				m_textures;
+	std::vector<glu::ShaderProgram*>	m_programs;
+	std::vector<deUint32>				m_framebuffers;
+	std::vector<deUint32>				m_renderbuffers;
+	std::vector<deUint32>				m_samplers;
+	std::vector<deUint32>				m_vertexArrays;
+
+private:
+										StateChangePerformanceCase		(const StateChangePerformanceCase&);
+	StateChangePerformanceCase&			operator=						(const StateChangePerformanceCase&);
+
+	std::vector<deUint16>				m_indices;
+
+	std::vector<deUint64>				m_interleavedResults;
+	std::vector<deUint64>				m_batchedResults;
+};
+
+class StateChangeCallPerformanceCase : public tcu::TestCase
+{
+public:
+
+							StateChangeCallPerformanceCase	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description);
+							~StateChangeCallPerformanceCase	(void);
+
+	IterateResult			iterate							(void);
+
+	virtual void			execCalls						(const glw::Functions& gl, int iterNdx, int callCount) = 0;
+
+private:
+	void					executeTest						(void);
+	void					logTestCase						(void);
+
+	void					logAndSetTestResult				(void);
+
+	glu::RenderContext&		m_renderCtx;
+
+	const int				m_iterationCount;
+	const int				m_callCount;
+
+	std::vector<deUint64>	m_results;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSSTATECHANGEPERFTESTCASES_HPP
diff --git a/modules/glshared/glsStateQueryUtil.hpp b/modules/glshared/glsStateQueryUtil.hpp
new file mode 100644
index 0000000..e7a2a20
--- /dev/null
+++ b/modules/glshared/glsStateQueryUtil.hpp
@@ -0,0 +1,179 @@
+#ifndef _GLSSTATEQUERYUTIL_HPP
+#define _GLSSTATEQUERYUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief State Query test utils.
+ *//*--------------------------------------------------------------------*/
+
+#include "deMath.h"
+#include "tcuDefs.hpp"
+#include "tcuTestLog.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+namespace StateQueryUtil
+{
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Rounds given float to the nearest integer (half up).
+ *
+ * Returns the nearest integer for a float argument. In the case that there
+ * are two nearest integers at the equal distance (aka. the argument is of
+ * form x.5), the integer with the higher value is chosen. (x.5 rounds to x+1)
+ *//*--------------------------------------------------------------------*/
+template <typename T>
+T roundGLfloatToNearestIntegerHalfUp (float val)
+{
+	return (T)(deFloatFloor(val + 0.5f));
+}
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Rounds given float to the nearest integer (half down).
+ *
+ * Returns the nearest integer for a float argument. In the case that there
+ * are two nearest integers at the equal distance (aka. the argument is of
+ * form x.5), the integer with the higher value is chosen. (x.5 rounds to x)
+ *//*--------------------------------------------------------------------*/
+template <typename T>
+T roundGLfloatToNearestIntegerHalfDown (float val)
+{
+	return (T)(deFloatCeil(val - 0.5f));
+}
+
+template <typename T>
+class StateQueryMemoryWriteGuard
+{
+public:
+					StateQueryMemoryWriteGuard	(void);
+
+					operator T&					(void);
+	T*				operator &					(void);
+
+	bool			isUndefined					(void) const;
+	bool			isMemoryContaminated		(void) const;
+	bool			verifyValidity				(tcu::TestContext& testCtx) const;
+
+	const T&		get							(void) const { return m_value; }
+
+private:
+	enum
+	{
+		GUARD_VALUE = 0xDEDEADCD
+	};
+	enum
+	{
+		WRITE_GUARD_VALUE = 0xDE
+	};
+
+	deInt32			m_preguard;
+	union
+	{
+		T			m_value;
+		deUint8		m_isWrittenToGuard[sizeof(T)];
+	};
+	deInt32			m_postguard; // \note guards are not const qualified since the GL implementation might modify them
+};
+
+template <typename T>
+StateQueryMemoryWriteGuard<T>::StateQueryMemoryWriteGuard (void)
+	: m_preguard	((deInt32)(GUARD_VALUE))
+	, m_postguard	((deInt32)(GUARD_VALUE))
+{
+	for (size_t i = 0; i < DE_LENGTH_OF_ARRAY(m_isWrittenToGuard); ++i)
+		m_isWrittenToGuard[i] = (deUint8)WRITE_GUARD_VALUE;
+}
+
+template <typename T>
+StateQueryMemoryWriteGuard<T>::operator T& (void)
+{
+	return m_value;
+}
+
+template <typename T>
+T* StateQueryMemoryWriteGuard<T>::operator & (void)
+{
+	return &m_value;
+}
+
+template <typename T>
+bool StateQueryMemoryWriteGuard<T>::isUndefined () const
+{
+	for (size_t i = 0; i < DE_LENGTH_OF_ARRAY(m_isWrittenToGuard); ++i)
+		if (m_isWrittenToGuard[i] != (deUint8)WRITE_GUARD_VALUE)
+			return false;
+	return true;
+}
+
+template <typename T>
+bool StateQueryMemoryWriteGuard<T>::isMemoryContaminated () const
+{
+	return (m_preguard != (deInt32)(GUARD_VALUE)) || (m_postguard != (deInt32)(GUARD_VALUE));
+}
+
+template <typename T>
+bool StateQueryMemoryWriteGuard<T>::verifyValidity (tcu::TestContext& testCtx) const
+{
+	using tcu::TestLog;
+
+	if (m_preguard != (deInt32)(GUARD_VALUE))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Pre-guard value was modified " << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS ||
+			testCtx.getTestResult() == QP_TEST_RESULT_LAST)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Get* did an illegal memory write");
+
+		return false;
+	}
+	else if (m_postguard != (deInt32)(GUARD_VALUE))
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Post-guard value was modified " << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS ||
+			testCtx.getTestResult() == QP_TEST_RESULT_LAST)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Get* did an illegal memory write");
+
+		return false;
+	}
+	else if (isUndefined())
+	{
+		testCtx.getLog() << TestLog::Message << "// ERROR: Get* did not return a value" << TestLog::EndMessage;
+		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS ||
+			testCtx.getTestResult() == QP_TEST_RESULT_LAST)
+			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Get* did not return a value");
+
+		return false;
+	}
+
+	return true;
+}
+
+template<typename T>
+std::ostream& operator<< (std::ostream& str, const StateQueryMemoryWriteGuard<T>& guard)
+{
+	return str << guard.get();
+}
+
+} // StateQueryUtil
+} // gls
+} // deqp
+
+#endif // _GLSSTATEQUERYUTIL_HPP
diff --git a/modules/glshared/glsTextureBufferCase.cpp b/modules/glshared/glsTextureBufferCase.cpp
new file mode 100644
index 0000000..a58357a
--- /dev/null
+++ b/modules/glshared/glsTextureBufferCase.cpp
@@ -0,0 +1,1031 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture buffer test case
+ *//*--------------------------------------------------------------------*/
+
+#include "glsTextureBufferCase.hpp"
+
+#include "tcuFormatUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+
+#include "rrRenderer.hpp"
+#include "rrShaders.hpp"
+
+#include "gluObjectWrapper.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluShaderUtil.hpp"
+#include "gluStrUtil.hpp"
+#include "gluTexture.hpp"
+
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deUniquePtr.hpp"
+
+#include "deMemory.h"
+#include "deString.h"
+#include "deMath.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+using tcu::TestLog;
+
+using std::map;
+using std::string;
+using std::vector;
+
+using namespace deqp::gls::TextureBufferCaseUtil;
+
+namespace deqp
+{
+namespace gls
+{
+namespace
+{
+
+enum
+{
+	MAX_VIEWPORT_WIDTH	= 256,
+	MAX_VIEWPORT_HEIGHT	= 256,
+	MIN_VIEWPORT_WIDTH	= 64,
+	MIN_VIEWPORT_HEIGHT	= 64,
+};
+
+deUint8 extend2BitsToByte (deUint8 bits)
+{
+	deUint8 x = 0;
+
+	DE_ASSERT((bits & (~0x03u)) == 0);
+
+	x |= bits << 6;
+	x |= bits << 4;
+	x |= bits << 2;
+	x |= bits;
+
+	return x;
+}
+
+void genRandomCoords (de::Random rng, vector<deUint8>& coords, size_t offset, size_t size)
+{
+	const deUint8 bits		= 2;
+	const deUint8 bitMask	= deUint8((0x1u << bits) - 1);
+
+	coords.resize(size);
+
+	for (int i = 0; i < (int)size; i++)
+	{
+		const deUint8 xBits = deUint8(rng.getUint32() & bitMask);
+		coords[i] = extend2BitsToByte(xBits);
+	}
+
+	// Fill indices with nice quad
+	{
+		const deUint8 indices[] =
+		{
+			extend2BitsToByte(0x0u),
+			extend2BitsToByte(0x1u),
+			extend2BitsToByte(0x2u),
+			extend2BitsToByte(0x3u)
+		};
+
+		for (int i = 0; i < DE_LENGTH_OF_ARRAY(indices); i++)
+		{
+			const deUint8	index	= indices[i];
+			const size_t	posX	= (size_t(index) * 2) + 0;
+			const size_t	posY	= (size_t(index) * 2) + 1;
+
+			if (posX >= offset && posX < offset+size)
+				coords[posX - offset] = ((i % 2) == 0 ? extend2BitsToByte(0x0u) : extend2BitsToByte(0x3u));
+
+			if (posY >= offset && posY < offset+size)
+				coords[posY - offset] = ((i / 2) == 1 ? extend2BitsToByte(0x3u) : extend2BitsToByte(0x0u));
+		}
+	}
+
+	// Fill beginning of buffer
+	{
+		const deUint8 indices[] =
+		{
+			extend2BitsToByte(0x0u),
+			extend2BitsToByte(0x3u),
+			extend2BitsToByte(0x1u),
+
+			extend2BitsToByte(0x1u),
+			extend2BitsToByte(0x2u),
+			extend2BitsToByte(0x0u),
+
+			extend2BitsToByte(0x0u),
+			extend2BitsToByte(0x2u),
+			extend2BitsToByte(0x1u),
+
+			extend2BitsToByte(0x1u),
+			extend2BitsToByte(0x3u),
+			extend2BitsToByte(0x0u)
+		};
+
+		for (int i = (int)offset; i < DE_LENGTH_OF_ARRAY(indices) && i < (int)(offset + size); i++)
+			coords[i-offset] = indices[i];
+	}
+}
+
+class CoordVertexShader : public rr::VertexShader
+{
+public:
+	CoordVertexShader (void)
+		: rr::VertexShader(1, 1)
+	{
+		m_inputs[0].type	= rr::GENERICVECTYPE_FLOAT;
+		m_outputs[0].type	= rr::GENERICVECTYPE_FLOAT;
+	}
+
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; packetNdx++)
+		{
+			rr::VertexPacket* const		packet		= packets[packetNdx];
+			tcu::Vec4					position;
+
+			readVertexAttrib(position, inputs[0], packet->instanceNdx, packet->vertexNdx);
+
+			packet->outputs[0]	= tcu::Vec4(1.0f);
+			packet->position	= tcu::Vec4(2.0f * (position.x() - 0.5f), 2.0f * (position.y() - 0.5f), 0.0f, 1.0f);
+		}
+	}
+};
+
+class TextureVertexShader : public rr::VertexShader
+{
+public:
+	TextureVertexShader (const tcu::ConstPixelBufferAccess& texture)
+		: rr::VertexShader	(1, 1)
+		, m_texture			(texture)
+	{
+		m_inputs[0].type	= rr::GENERICVECTYPE_FLOAT;
+		m_outputs[0].type	= rr::GENERICVECTYPE_FLOAT;
+	}
+
+	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; packetNdx++)
+		{
+			rr::VertexPacket* const		packet		= packets[packetNdx];
+			tcu::Vec4					position;
+			tcu::Vec4					texelValue;
+
+			readVertexAttrib(position, inputs[0], packet->instanceNdx, packet->vertexNdx);
+
+			texelValue	= tcu::Vec4(m_texture.getPixel(de::clamp<int>((deRoundFloatToInt32(position.x() * 4) + 4) * (deRoundFloatToInt32(position.y() * 4) + 4), 0, m_texture.getWidth()-1), 0));
+
+			packet->outputs[0]	= texelValue;
+			packet->position	= tcu::Vec4(2.0f * (position.x() - 0.5f), 2.0f * (position.y() - 0.5f), 0.0f, 1.0f);
+		}
+	}
+
+private:
+	const tcu::ConstPixelBufferAccess& m_texture;
+};
+
+class CoordFragmentShader : public rr::FragmentShader
+{
+public:
+	CoordFragmentShader (void)
+		: rr::FragmentShader (1, 1)
+	{
+		m_inputs[0].type	= rr::GENERICVECTYPE_FLOAT;
+		m_outputs[0].type	= rr::GENERICVECTYPE_FLOAT;
+	}
+
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; packetNdx++)
+		{
+			rr::FragmentPacket&	packet		= packets[packetNdx];
+
+			const tcu::Vec4		vtxColor0	= rr::readVarying<float>(packet, context, 0, 0);
+			const tcu::Vec4		vtxColor1	= rr::readVarying<float>(packet, context, 0, 1);
+			const tcu::Vec4		vtxColor2	= rr::readVarying<float>(packet, context, 0, 2);
+			const tcu::Vec4		vtxColor3	= rr::readVarying<float>(packet, context, 0, 3);
+
+			const tcu::Vec4		color0		= vtxColor0;
+			const tcu::Vec4		color1		= vtxColor1;
+			const tcu::Vec4		color2		= vtxColor2;
+			const tcu::Vec4		color3		= vtxColor3;
+
+			rr::writeFragmentOutput(context, packetNdx, 0, 0, tcu::Vec4(color0.x() * color0.w(), color0.y() * color0.w(), color0.z() * color0.w(), 1.0f));
+			rr::writeFragmentOutput(context, packetNdx, 1, 0, tcu::Vec4(color1.x() * color1.w(), color1.y() * color1.w(), color1.z() * color1.w(), 1.0f));
+			rr::writeFragmentOutput(context, packetNdx, 2, 0, tcu::Vec4(color2.x() * color2.w(), color2.y() * color2.w(), color2.z() * color2.w(), 1.0f));
+			rr::writeFragmentOutput(context, packetNdx, 3, 0, tcu::Vec4(color3.x() * color3.w(), color3.y() * color3.w(), color3.z() * color3.w(), 1.0f));
+		}
+	}
+};
+
+class TextureFragmentShader : public rr::FragmentShader
+{
+public:
+	TextureFragmentShader (const tcu::ConstPixelBufferAccess& texture)
+		: rr::FragmentShader	(1, 1)
+		, m_texture				(texture)
+	{
+		m_inputs[0].type	= rr::GENERICVECTYPE_FLOAT;
+		m_outputs[0].type	= rr::GENERICVECTYPE_FLOAT;
+	}
+
+	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+	{
+		for (int packetNdx = 0; packetNdx < numPackets; packetNdx++)
+		{
+			rr::FragmentPacket&	packet		= packets[packetNdx];
+
+			const tcu::IVec2	position	= packet.position;
+
+			const tcu::IVec2	position0	= packet.position + tcu::IVec2(0, 0);
+			const tcu::IVec2	position1	= packet.position + tcu::IVec2(1, 0);
+			const tcu::IVec2	position2	= packet.position + tcu::IVec2(0, 1);
+			const tcu::IVec2	position3	= packet.position + tcu::IVec2(1, 1);
+
+			const tcu::Vec4		texColor0	= m_texture.getPixel(de::clamp((position0.x() * position0.y()), 0, m_texture.getWidth()-1), 0);
+			const tcu::Vec4		texColor1	= m_texture.getPixel(de::clamp((position1.x() * position1.y()), 0, m_texture.getWidth()-1), 0);
+			const tcu::Vec4		texColor2	= m_texture.getPixel(de::clamp((position2.x() * position2.y()), 0, m_texture.getWidth()-1), 0);
+			const tcu::Vec4		texColor3	= m_texture.getPixel(de::clamp((position3.x() * position3.y()), 0, m_texture.getWidth()-1), 0);
+
+			const tcu::Vec4		vtxColor0	= rr::readVarying<float>(packet, context, 0, 0);
+			const tcu::Vec4		vtxColor1	= rr::readVarying<float>(packet, context, 0, 1);
+			const tcu::Vec4		vtxColor2	= rr::readVarying<float>(packet, context, 0, 2);
+			const tcu::Vec4		vtxColor3	= rr::readVarying<float>(packet, context, 0, 3);
+
+			const tcu::Vec4		color0		= 0.5f * (vtxColor0 + texColor0);
+			const tcu::Vec4		color1		= 0.5f * (vtxColor1 + texColor1);
+			const tcu::Vec4		color2		= 0.5f * (vtxColor2 + texColor2);
+			const tcu::Vec4		color3		= 0.5f * (vtxColor3 + texColor3);
+
+			rr::writeFragmentOutput(context, packetNdx, 0, 0, tcu::Vec4(color0.x() * color0.w(), color0.y() * color0.w(), color0.z() * color0.w(), 1.0f));
+			rr::writeFragmentOutput(context, packetNdx, 1, 0, tcu::Vec4(color1.x() * color1.w(), color1.y() * color1.w(), color1.z() * color1.w(), 1.0f));
+			rr::writeFragmentOutput(context, packetNdx, 2, 0, tcu::Vec4(color2.x() * color2.w(), color2.y() * color2.w(), color2.z() * color2.w(), 1.0f));
+			rr::writeFragmentOutput(context, packetNdx, 3, 0, tcu::Vec4(color3.x() * color3.w(), color3.y() * color3.w(), color3.z() * color3.w(), 1.0f));
+		}
+	}
+
+private:
+	const tcu::ConstPixelBufferAccess& m_texture;
+};
+
+string generateVertexShaderTemplate (RenderBits renderBits)
+{
+	std::ostringstream stream;
+
+	stream <<
+		"${VERSION_HEADER}\n";
+
+	if (renderBits & RENDERBITS_AS_VERTEX_TEXTURE)
+		stream << "${TEXTURE_BUFFER_EXT}";
+
+	stream <<
+		"${VTX_INPUT} layout(location = 0) ${HIGHP} vec2 i_coord;\n"
+		"${VTX_OUTPUT} ${HIGHP} vec4 v_color;\n";
+
+	if (renderBits & RENDERBITS_AS_VERTEX_TEXTURE)
+	{
+		stream <<
+			"uniform ${HIGHP} samplerBuffer u_vtxSampler;\n";
+	}
+
+	stream <<
+		"\n"
+		"void main (void)\n"
+		"{\n";
+
+	if (renderBits & RENDERBITS_AS_VERTEX_TEXTURE)
+		stream << "\tv_color = texelFetch(u_vtxSampler, clamp((int(round(i_coord.x * 4.0)) + 4) * (int(round(i_coord.y * 4.0)) + 4), 0, textureSize(u_vtxSampler)-1));\n";
+	else
+		stream << "\tv_color = vec4(1.0);\n";
+
+	stream <<
+		"\tgl_Position = vec4(2.0 * (i_coord - vec2(0.5)), 0.0, 1.0);\n"
+		"}\n";
+
+	return stream.str();
+}
+
+string generateFragmentShaderTemplate (RenderBits renderBits)
+{
+	std::ostringstream stream;
+
+	stream <<
+		"${VERSION_HEADER}\n";
+
+	if (renderBits & RENDERBITS_AS_FRAGMENT_TEXTURE)
+		stream << "${TEXTURE_BUFFER_EXT}";
+
+	stream <<
+		"${FRAG_OUTPUT} layout(location = 0) ${HIGHP} vec4 dEQP_FragColor;\n"
+		"${FRAG_INPUT} ${HIGHP} vec4 v_color;\n";
+
+	if (renderBits & RENDERBITS_AS_FRAGMENT_TEXTURE)
+		stream << "uniform ${HIGHP} samplerBuffer u_fragSampler;\n";
+
+	stream <<
+		"\n"
+		"void main (void)\n"
+		"{\n";
+
+	if (renderBits & RENDERBITS_AS_FRAGMENT_TEXTURE)
+		stream << "\t${HIGHP} vec4 color = 0.5 * (v_color + texelFetch(u_fragSampler, clamp(int(gl_FragCoord.x) * int(gl_FragCoord.y), 0, textureSize(u_fragSampler)-1)));\n";
+	else
+		stream << "\t${HIGHP} vec4 color = v_color;\n";
+
+	stream <<
+		"\tdEQP_FragColor = vec4(color.xyz * color.w, 1.0);\n"
+		"}\n";
+
+	return stream.str();
+}
+
+string specializeShader (const string& shaderTemplateString, glu::GLSLVersion glslVersion)
+{
+	const tcu::StringTemplate	shaderTemplate(shaderTemplateString);
+	map<string, string>			parameters;
+
+	parameters["VERSION_HEADER"]		= glu::getGLSLVersionDeclaration(glslVersion);
+	parameters["VTX_OUTPUT"]			= "out";
+	parameters["VTX_INPUT"]				= "in";
+	parameters["FRAG_INPUT"]			= "in";
+	parameters["FRAG_OUTPUT"]			= "out";
+	parameters["HIGHP"]					= (glslVersion == glu::GLSL_VERSION_330 ? "" : "highp");
+	parameters["TEXTURE_BUFFER_EXT"]	= (glslVersion == glu::GLSL_VERSION_330 ? "" : "#extension GL_EXT_texture_buffer : enable\n");
+
+	return shaderTemplate.specialize(parameters);
+}
+
+glu::ShaderProgram* createRenderProgram (glu::RenderContext&	renderContext,
+										 RenderBits				renderBits)
+{
+	const string				vertexShaderTemplate	= generateVertexShaderTemplate(renderBits);
+	const string				fragmentShaderTemplate	= generateFragmentShaderTemplate(renderBits);
+
+	const glu::GLSLVersion		glslVersion				= glu::getContextTypeGLSLVersion(renderContext.getType());
+
+	const string				vertexShaderSource		= specializeShader(vertexShaderTemplate, glslVersion);
+	const string				fragmentShaderSource	= specializeShader(fragmentShaderTemplate, glslVersion);
+
+	glu::ShaderProgram* const	program					= new glu::ShaderProgram(renderContext, glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));
+
+	return program;
+}
+
+void logModifications (TestLog& log, ModifyBits modifyBits)
+{
+	tcu::ScopedLogSection section(log, "Modify Operations", "Modify Operations");
+
+	const struct
+	{
+		ModifyBits	bit;
+		const char*	str;
+	} bitInfos[] =
+	{
+		{ MODIFYBITS_BUFFERDATA,			"Recreate buffer data with glBufferData()."			},
+		{ MODIFYBITS_BUFFERSUBDATA,			"Modify texture buffer with glBufferSubData()."		},
+		{ MODIFYBITS_MAPBUFFER_WRITE,		"Map buffer write-only and rewrite data."			},
+		{ MODIFYBITS_MAPBUFFER_READWRITE,	"Map buffer readw-write check and rewrite data."	}
+	};
+
+	DE_ASSERT(modifyBits != 0);
+
+	for (int infoNdx = 0; infoNdx < DE_LENGTH_OF_ARRAY(bitInfos); infoNdx++)
+	{
+		if (modifyBits & bitInfos[infoNdx].bit)
+			log << TestLog::Message << bitInfos[infoNdx].str << TestLog::EndMessage;
+	}
+}
+
+void modifyBufferData (TestLog&				log,
+					   de::Random&			rng,
+					   glu::TextureBuffer&	texture)
+{
+	vector<deUint8> data;
+
+	genRandomCoords(rng, data, 0, texture.getBufferSize());
+
+	log << TestLog::Message << "BufferData, Size: " << data.size() << TestLog::EndMessage;
+
+	texture.bufferData(&(data[0]), data.size());
+	texture.upload();
+}
+
+void modifyBufferSubData (TestLog&				log,
+						  de::Random&			rng,
+						  const glw::Functions&	gl,
+						  glu::TextureBuffer&	texture)
+{
+	const size_t				minSize		= 4*16;
+	const size_t				size		= de::max<size_t>(minSize, size_t((texture.getSize() != 0 ? texture.getSize() : texture.getBufferSize()) * (0.7 + 0.3 * rng.getFloat())));
+	const size_t				minOffset	= texture.getOffset();
+	const size_t				offset		= minOffset + (rng.getUint32() % (texture.getBufferSize() - (size + minOffset)));
+	vector<deUint8>				data;
+
+	genRandomCoords(rng, data, offset, size);
+
+	log << TestLog::Message << "BufferSubData, Offset: " << offset << ", Size: " << size << TestLog::EndMessage;
+
+	gl.bindBuffer(GL_TEXTURE_BUFFER, texture.getGLBuffer());
+	gl.bufferSubData(GL_TEXTURE_BUFFER, (glw::GLsizei)offset, (glw::GLsizei)data.size(), &(data[0]));
+	gl.bindBuffer(GL_TEXTURE_BUFFER, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to update data with glBufferSubData()");
+
+	deMemcpy(texture.getRefBuffer() + offset, &(data[0]), int(data.size()));
+}
+
+void modifyMapWrite (TestLog&				log,
+					 de::Random&			rng,
+					 const glw::Functions&	gl,
+					 glu::TextureBuffer&	texture)
+{
+	const size_t				minSize		= 4*16;
+	const size_t				size		= de::max<size_t>(minSize, size_t((texture.getSize() != 0 ? texture.getSize() : texture.getBufferSize()) * (0.7 + 0.3 * rng.getFloat())));
+	const size_t				minOffset	= texture.getOffset();
+	const size_t				offset		= minOffset + (rng.getUint32() % (texture.getBufferSize() - (size + minOffset)));
+	vector<deUint8>				data;
+
+	genRandomCoords(rng, data, offset, size);
+
+	log << TestLog::Message << "glMapBufferRange, Write Only, Offset: " << offset << ", Size: " << size << TestLog::EndMessage;
+
+	gl.bindBuffer(GL_TEXTURE_BUFFER, texture.getGLBuffer());
+	{
+		deUint8* ptr = (deUint8*)gl.mapBufferRange(GL_TEXTURE_BUFFER, (glw::GLsizei)offset, (glw::GLsizei)size, GL_MAP_WRITE_BIT);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+		TCU_CHECK(ptr);
+
+		for (int i = 0; i < (int)data.size(); i++)
+			ptr[i] = data[i];
+
+		TCU_CHECK(gl.unmapBuffer(GL_TEXTURE_BUFFER));
+	}
+	gl.bindBuffer(GL_TEXTURE_BUFFER, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to update data with glMapBufferRange()");
+
+	deMemcpy(texture.getRefBuffer()+offset, &(data[0]), int(data.size()));
+}
+
+void modifyMapReadWrite (TestLog&				log,
+						 tcu::ResultCollector&	resultCollector,
+						 de::Random&			rng,
+						 const glw::Functions&	gl,
+						 glu::TextureBuffer&	texture)
+{
+	const size_t				minSize		= 4*16;
+	const size_t				size		= de::max<size_t>(minSize, size_t((texture.getSize() != 0 ? texture.getSize() : texture.getBufferSize()) * (0.7 + 0.3 * rng.getFloat())));
+	const size_t				minOffset	= texture.getOffset();
+	const size_t				offset		= minOffset + (rng.getUint32() % (texture.getBufferSize() - (size + minOffset)));
+	vector<deUint8>				data;
+
+	genRandomCoords(rng, data, offset, size);
+
+	log << TestLog::Message << "glMapBufferRange, Read Write, Offset: " << offset << ", Size: " << size << TestLog::EndMessage;
+
+	gl.bindBuffer(GL_TEXTURE_BUFFER, texture.getGLBuffer());
+	{
+		size_t			invalidBytes	= 0;
+		deUint8* const	ptr				= (deUint8*)gl.mapBufferRange(GL_TEXTURE_BUFFER, (glw::GLsizei)offset, (glw::GLsizei)size, GL_MAP_WRITE_BIT|GL_MAP_READ_BIT);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
+		TCU_CHECK(ptr);
+
+		for (int i = 0; i < (int)data.size(); i++)
+		{
+			if (ptr[i] != texture.getRefBuffer()[offset+i])
+			{
+				if (invalidBytes < 24)
+					log << TestLog::Message << "Invalid byte in mapped buffer. " << tcu::Format::Hex<2>(data[i]).toString() << " at " << i << ", expected " << tcu::Format::Hex<2>(texture.getRefBuffer()[i]).toString() << TestLog::EndMessage;
+
+				invalidBytes++;
+			}
+
+			ptr[i] = data[i];
+		}
+
+		TCU_CHECK(gl.unmapBuffer(GL_TEXTURE_BUFFER));
+
+		if (invalidBytes > 0)
+		{
+			log << TestLog::Message << "Total of " << invalidBytes << " invalid bytes." << TestLog::EndMessage;
+			resultCollector.fail("Invalid data in mapped buffer");
+		}
+	}
+
+	gl.bindBuffer(GL_TEXTURE_BUFFER, 0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to update data with glMapBufferRange()");
+
+	for (int i = 0; i < (int)data.size(); i++)
+		texture.getRefBuffer()[offset+i] = data[i];
+}
+
+void modify (TestLog&						log,
+			 tcu::ResultCollector&			resultCollector,
+			 glu::RenderContext&			renderContext,
+			 ModifyBits						modifyBits,
+			 de::Random&					rng,
+			 glu::TextureBuffer&			texture)
+{
+	const tcu::ScopedLogSection modifySection(log, "Modifying Texture buffer", "Modifying Texture Buffer");
+
+	logModifications(log, modifyBits);
+
+	if (modifyBits & MODIFYBITS_BUFFERDATA)
+		modifyBufferData(log, rng, texture);
+
+	if (modifyBits & MODIFYBITS_BUFFERSUBDATA)
+		modifyBufferSubData(log, rng, renderContext.getFunctions(), texture);
+
+	if (modifyBits & MODIFYBITS_MAPBUFFER_WRITE)
+		modifyMapWrite(log, rng, renderContext.getFunctions(), texture);
+
+	if (modifyBits & MODIFYBITS_MAPBUFFER_READWRITE)
+		modifyMapReadWrite(log, resultCollector, rng, renderContext.getFunctions(), texture);
+}
+
+void renderGL (glu::RenderContext&		renderContext,
+			   RenderBits				renderBits,
+			   deUint32					coordSeed,
+			   int						triangleCount,
+			   glu::ShaderProgram&		program,
+			   glu::TextureBuffer&		texture)
+{
+	const glw::Functions&	gl			= renderContext.getFunctions();
+	const glu::VertexArray	vao			(renderContext);
+	const glu::Buffer		coordBuffer	(renderContext);
+
+	gl.useProgram(program.getProgram());
+	gl.bindVertexArray(*vao);
+
+	gl.enableVertexAttribArray(0);
+
+	if (renderBits & RENDERBITS_AS_VERTEX_ARRAY)
+	{
+		gl.bindBuffer(GL_ARRAY_BUFFER, texture.getGLBuffer());
+		gl.vertexAttribPointer(0, 2, GL_UNSIGNED_BYTE, true, 0, DE_NULL);
+	}
+	else
+	{
+		de::Random		rng(coordSeed);
+		vector<deUint8> coords;
+
+		genRandomCoords(rng, coords, 0, 256*2);
+
+		gl.bindBuffer(GL_ARRAY_BUFFER, *coordBuffer);
+		gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizei)coords.size(), &(coords[0]), GL_STREAM_DRAW);
+		gl.vertexAttribPointer(0, 2, GL_UNSIGNED_BYTE, true, 0, DE_NULL);
+	}
+
+	if (renderBits & RENDERBITS_AS_VERTEX_TEXTURE)
+	{
+		const deInt32 location = gl.getUniformLocation(program.getProgram(), "u_vtxSampler");
+
+		gl.activeTexture(GL_TEXTURE0);
+		gl.bindTexture(GL_TEXTURE_BUFFER, texture.getGLTexture());
+		gl.uniform1i(location, 0);
+	}
+
+	if (renderBits & RENDERBITS_AS_FRAGMENT_TEXTURE)
+	{
+		const deInt32 location = gl.getUniformLocation(program.getProgram(), "u_fragSampler");
+
+		gl.activeTexture(GL_TEXTURE1);
+		gl.bindTexture(GL_TEXTURE_BUFFER, texture.getGLTexture());
+		gl.uniform1i(location, 1);
+		gl.activeTexture(GL_TEXTURE0);
+	}
+
+	if (renderBits & RENDERBITS_AS_INDEX_ARRAY)
+	{
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, texture.getGLBuffer());
+		gl.drawElements(GL_TRIANGLES, triangleCount * 3, GL_UNSIGNED_BYTE, DE_NULL);
+		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+	}
+	else
+		gl.drawArrays(GL_TRIANGLES, 0, triangleCount * 3);
+
+	if (renderBits & RENDERBITS_AS_FRAGMENT_TEXTURE)
+	{
+		gl.activeTexture(GL_TEXTURE1);
+		gl.bindTexture(GL_TEXTURE_BUFFER, 0);
+	}
+
+	if (renderBits & RENDERBITS_AS_VERTEX_TEXTURE)
+	{
+		gl.activeTexture(GL_TEXTURE0);
+		gl.bindTexture(GL_TEXTURE_BUFFER, 0);
+	}
+
+	gl.bindBuffer(GL_ARRAY_BUFFER, 0);
+	gl.disableVertexAttribArray(0);
+
+	gl.bindVertexArray(0);
+	gl.useProgram(0);
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Rendering failed");
+}
+
+void renderReference (RenderBits					renderBits,
+					  deUint32						coordSeed,
+					  int							triangleCount,
+					  glu::TextureBuffer&			texture,
+					  const tcu::PixelBufferAccess&	target)
+{
+	const CoordVertexShader			coordVertexShader;
+	const TextureVertexShader		textureVertexShader		(texture.getRefTexture());
+	const rr::VertexShader* const	vertexShader			= (renderBits & RENDERBITS_AS_VERTEX_TEXTURE ? static_cast<const rr::VertexShader*>(&textureVertexShader) : &coordVertexShader);
+
+	const CoordFragmentShader		coordFragmmentShader;
+	const TextureFragmentShader		textureFragmentShader	(texture.getRefTexture());
+	const rr::FragmentShader* const	fragmentShader			= (renderBits & RENDERBITS_AS_FRAGMENT_TEXTURE ? static_cast<const rr::FragmentShader*>(&textureFragmentShader) : &coordFragmmentShader);
+
+	const rr::Renderer				renderer;
+	const rr::RenderState			renderState(rr::ViewportState(rr::WindowRectangle(0, 0, target.getWidth(), target.getHeight())));
+	const rr::RenderTarget			renderTarget(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(target));
+
+	const rr::Program				program(vertexShader, fragmentShader);
+
+	rr::VertexAttrib				vertexAttribs[1];
+	vector<deUint8>					coords;
+
+	if (renderBits & RENDERBITS_AS_VERTEX_ARRAY)
+	{
+		vertexAttribs[0].type			= rr::VERTEXATTRIBTYPE_NONPURE_UNORM8;
+		vertexAttribs[0].size			= 2;
+		vertexAttribs[0].pointer		= texture.getRefBuffer();
+	}
+	else
+	{
+		de::Random rng(coordSeed);
+
+		genRandomCoords(rng, coords, 0, 256*2);
+
+		vertexAttribs[0].type			= rr::VERTEXATTRIBTYPE_NONPURE_UNORM8;
+		vertexAttribs[0].size			= 2;
+		vertexAttribs[0].pointer		= &(coords[0]);
+	}
+
+	if (renderBits & RENDERBITS_AS_INDEX_ARRAY)
+	{
+		const rr::PrimitiveList	primitives(rr::PRIMITIVETYPE_TRIANGLES, triangleCount * 3, rr::DrawIndices((const void*)texture.getRefBuffer(), rr::INDEXTYPE_UINT8));
+		const rr::DrawCommand	cmd(renderState, renderTarget, program, 1, vertexAttribs, primitives);
+
+		renderer.draw(cmd);
+	}
+	else
+	{
+		const rr::PrimitiveList	primitives(rr::PRIMITIVETYPE_TRIANGLES, triangleCount * 3, 0);
+		const rr::DrawCommand	cmd(renderState, renderTarget, program, 1, vertexAttribs, primitives);
+
+		renderer.draw(cmd);
+	}
+}
+
+void logRendering (TestLog& log, RenderBits renderBits)
+{
+	const struct
+	{
+		RenderBits	bit;
+		const char*	str;
+	} bitInfos[] =
+	{
+		{ RENDERBITS_AS_VERTEX_ARRAY,		"vertex array"		},
+		{ RENDERBITS_AS_INDEX_ARRAY,		"index array"		},
+		{ RENDERBITS_AS_VERTEX_TEXTURE,		"vertex texture"	},
+		{ RENDERBITS_AS_FRAGMENT_TEXTURE,	"fragment texture"	}
+	};
+
+	std::ostringstream	stream;
+	vector<const char*> usedAs;
+
+	DE_ASSERT(renderBits != 0);
+
+	for (int infoNdx = 0; infoNdx < DE_LENGTH_OF_ARRAY(bitInfos); infoNdx++)
+	{
+		if (renderBits & bitInfos[infoNdx].bit)
+			usedAs.push_back(bitInfos[infoNdx].str);
+	}
+
+	stream << "Render using texture buffer as ";
+
+	for (int asNdx = 0; asNdx < (int)usedAs.size(); asNdx++)
+	{
+		if (asNdx+1 == (int)usedAs.size() && (int)usedAs.size() > 1)
+			stream << " and ";
+		else if (asNdx > 0)
+			stream << ", ";
+
+		stream << usedAs[asNdx];
+	}
+
+	stream << ".";
+
+	log << TestLog::Message << stream.str() << TestLog::EndMessage;
+}
+
+void render (TestLog&						log,
+			 glu::RenderContext&			renderContext,
+			 RenderBits						renderBits,
+			 de::Random&					rng,
+			 glu::ShaderProgram&			program,
+			 glu::TextureBuffer&			texture,
+			 const tcu::PixelBufferAccess&	target)
+{
+	const tcu::ScopedLogSection	renderSection	(log, "Render Texture buffer", "Render Texture Buffer");
+	const int					triangleCount	= 8;
+	const deUint32				coordSeed		= rng.getUint32();
+
+	logRendering(log, renderBits);
+
+	renderGL(renderContext, renderBits, coordSeed, triangleCount, program, texture);
+	renderReference(renderBits, coordSeed, triangleCount, texture, target);
+}
+
+void verifyScreen (TestLog&								log,
+				   tcu::ResultCollector&				resultCollector,
+				   glu::RenderContext&					renderContext,
+				   const tcu::ConstPixelBufferAccess&	referenceTarget)
+{
+	const tcu::ScopedLogSection	verifySection	(log, "Verify screen contents", "Verify screen contents");
+	tcu::Surface				screen			(referenceTarget.getWidth(), referenceTarget.getHeight());
+
+	glu::readPixels(renderContext, 0, 0, screen.getAccess());
+
+	if (!tcu::fuzzyCompare(log, "Result of rendering", "Result of rendering", referenceTarget, screen.getAccess(), 0.05f, tcu::COMPARE_LOG_RESULT))
+		resultCollector.fail("Rendering failed");
+}
+
+void logImplementationInfo (TestLog& log, glu::RenderContext& renderContext)
+{
+	const tcu::ScopedLogSection		section	(log, "Implementation Values", "Implementation Values");
+	de::UniquePtr<glu::ContextInfo>	info	(glu::ContextInfo::create(renderContext));
+	const glw::Functions&			gl		= renderContext.getFunctions();
+
+	if (glu::contextSupports(renderContext.getType(), glu::ApiType(3, 3, glu::PROFILE_CORE)))
+	{
+		deInt32 maxTextureSize;
+
+		gl.getIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTextureSize);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE)");
+
+		log << TestLog::Message << "GL_MAX_TEXTURE_BUFFER_SIZE : " <<  maxTextureSize << TestLog::EndMessage;
+	}
+	else if (glu::contextSupports(renderContext.getType(), glu::ApiType(3, 1, glu::PROFILE_ES)) && info->isExtensionSupported("GL_EXT_texture_buffer"))
+	{
+		{
+			deInt32 maxTextureSize;
+
+			gl.getIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTextureSize);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE_EXT)");
+
+			log << TestLog::Message << "GL_MAX_TEXTURE_BUFFER_SIZE_EXT : " <<  maxTextureSize << TestLog::EndMessage;
+		}
+
+		{
+			deInt32 textureBufferAlignment;
+
+			gl.getIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &textureBufferAlignment);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT_EXT)");
+
+			log << TestLog::Message << "GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT_EXT : " <<  textureBufferAlignment << TestLog::EndMessage;
+		}
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+}
+
+void logTextureInfo (TestLog&	log,
+					 deUint32	format,
+					 size_t		bufferSize,
+					 size_t		offset,
+					 size_t		size)
+{
+	const tcu::ScopedLogSection	section(log, "Texture Info", "Texture Info");
+
+	log << TestLog::Message << "Texture format : " << glu::getPixelFormatStr(format) << TestLog::EndMessage;
+	log << TestLog::Message << "Buffer size : " << bufferSize << TestLog::EndMessage;
+
+	if (offset != 0 || size != 0)
+	{
+		log << TestLog::Message << "Buffer range offset: " << offset << TestLog::EndMessage;
+		log << TestLog::Message << "Buffer range size: " << size << TestLog::EndMessage;
+	}
+}
+
+void runTests (tcu::TestContext&	testCtx,
+			   glu::RenderContext&	renderContext,
+			   de::Random&			rng,
+			   deUint32				format,
+			   size_t				bufferSize,
+			   size_t				offset,
+			   size_t				size,
+			   RenderBits			preRender,
+			   glu::ShaderProgram*	preRenderProgram,
+			   ModifyBits			modifyType,
+			   RenderBits			postRender,
+			   glu::ShaderProgram*	postRenderProgram)
+{
+	const tcu::RenderTarget	renderTarget	(renderContext.getRenderTarget());
+	const glw::Functions&	gl				= renderContext.getFunctions();
+
+	const int				width			= de::min<int>(renderTarget.getWidth(), MAX_VIEWPORT_WIDTH);
+	const int				height			= de::min<int>(renderTarget.getHeight(), MAX_VIEWPORT_HEIGHT);
+	const tcu::Vec4			clearColor		(0.25f, 0.5f, 0.75f, 1.0f);
+
+	TestLog&				log				= testCtx.getLog();
+	tcu::ResultCollector	resultCollector	(log);
+
+	logImplementationInfo(log, renderContext);
+	logTextureInfo(log, format, bufferSize, offset, size);
+
+	{
+		tcu::Surface			referenceTarget	(width, height);
+		vector<deUint8>			bufferData;
+
+		genRandomCoords(rng, bufferData, 0, bufferSize);
+
+		for (deUint8 i = 0; i < 4; i++)
+		{
+			const deUint8 val = extend2BitsToByte(i);
+
+			if (val >= offset && val < offset + size)
+			{
+				bufferData[val*2 + 0] = (i / 2 == 0 ? extend2BitsToByte(0x2u) : extend2BitsToByte(0x01u));
+				bufferData[val*2 + 1] = (i % 2 == 0 ? extend2BitsToByte(0x2u) : extend2BitsToByte(0x01u));
+			}
+		}
+
+		{
+			glu::TextureBuffer texture (renderContext, format, bufferSize, offset, size, &(bufferData[0]));
+
+			TCU_CHECK_MSG(width >= MIN_VIEWPORT_WIDTH || height >= MIN_VIEWPORT_HEIGHT, "Too small viewport");
+
+			DE_ASSERT(preRender == 0 || preRenderProgram);
+			DE_ASSERT(postRender == 0 || postRenderProgram);
+
+			gl.viewport(0, 0, width, height);
+			gl.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+			gl.clear(GL_COLOR_BUFFER_BIT);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Screen setup failed");
+
+			tcu::clear(referenceTarget.getAccess(), clearColor);
+
+			texture.upload();
+
+			if (preRender != 0)
+				render(log, renderContext, preRender, rng, *preRenderProgram, texture, referenceTarget.getAccess());
+
+			if (modifyType != 0)
+				modify(log, resultCollector, renderContext, modifyType, rng, texture);
+
+			if (postRender != 0)
+				render(log, renderContext, postRender, rng, *postRenderProgram, texture, referenceTarget.getAccess());
+		}
+
+		verifyScreen(log, resultCollector, renderContext, referenceTarget.getAccess());
+
+		resultCollector.setTestContextResult(testCtx);
+	}
+}
+
+} // anonymous
+
+TextureBufferCase::TextureBufferCase (tcu::TestContext&		testCtx,
+									  glu::RenderContext&	renderCtx,
+									  deUint32				format,
+									  size_t				bufferSize,
+									  size_t				offset,
+									  size_t				size,
+									  RenderBits			preRender,
+									  ModifyBits			modify,
+									  RenderBits			postRender,
+									  const char*			name,
+									  const char*			description)
+	: tcu::TestCase				(testCtx, name, description)
+	, m_renderCtx				(renderCtx)
+	, m_format					(format)
+	, m_bufferSize				(bufferSize)
+	, m_offset					(offset)
+	, m_size					(size)
+
+	, m_preRender				(preRender)
+	, m_modify					(modify)
+	, m_postRender				(postRender)
+
+	, m_preRenderProgram		(DE_NULL)
+	, m_postRenderProgram		(DE_NULL)
+{
+}
+
+TextureBufferCase::~TextureBufferCase (void)
+{
+	TextureBufferCase::deinit();
+}
+
+void TextureBufferCase::init (void)
+{
+	de::UniquePtr<glu::ContextInfo> info (glu::ContextInfo::create(m_renderCtx));
+
+	if (!glu::contextSupports(m_renderCtx.getType(), glu::ApiType(3, 3, glu::PROFILE_CORE))
+		&& !(glu::contextSupports(m_renderCtx.getType(), glu::ApiType(3, 1, glu::PROFILE_ES)) && info->isExtensionSupported("GL_EXT_texture_buffer")))
+		throw tcu::NotSupportedError("Texture buffers not supported", "", __FILE__, __LINE__);
+
+	if (m_preRender != 0)
+	{
+		TestLog&					log			= m_testCtx.getLog();
+		const char* const			sectionName	= (m_postRender != 0 ? "Primary render program" : "Render program");
+		const tcu::ScopedLogSection	section		(log, sectionName, sectionName);
+
+		m_preRenderProgram = createRenderProgram(m_renderCtx, m_preRender);
+		m_testCtx.getLog() << (*m_preRenderProgram);
+
+		TCU_CHECK(m_preRenderProgram->isOk());
+	}
+
+	if (m_postRender != 0)
+	{
+		// Reusing program
+		if (m_preRender == m_postRender)
+		{
+			m_postRenderProgram = m_preRenderProgram;
+		}
+		else
+		{
+			TestLog&					log			= m_testCtx.getLog();
+			const char* const			sectionName	= (m_preRender!= 0 ? "Secondary render program" : "Render program");
+			const tcu::ScopedLogSection	section		(log, sectionName, sectionName);
+
+			m_postRenderProgram = createRenderProgram(m_renderCtx, m_postRender);
+			m_testCtx.getLog() << (*m_postRenderProgram);
+
+			TCU_CHECK(m_postRenderProgram->isOk());
+		}
+	}
+}
+
+void TextureBufferCase::deinit (void)
+{
+	if (m_preRenderProgram == m_postRenderProgram)
+		m_postRenderProgram = DE_NULL;
+
+	delete m_preRenderProgram;
+	m_preRenderProgram = DE_NULL;
+
+	delete m_postRenderProgram;
+	m_postRenderProgram = DE_NULL;
+}
+
+tcu::TestCase::IterateResult TextureBufferCase::iterate (void)
+{
+	de::Random	rng		(deInt32Hash(deStringHash(getName())));
+	size_t		offset;
+
+	if (m_offset != 0)
+	{
+		const glw::Functions&	gl			= m_renderCtx.getFunctions();
+		deInt32					alignment	= 0;
+
+		gl.getIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &alignment);
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT)");
+
+		offset = m_offset * alignment;
+	}
+	else
+		offset = 0;
+
+	runTests(m_testCtx, m_renderCtx, rng, m_format, m_bufferSize, offset, m_size, m_preRender, m_preRenderProgram, m_modify, m_postRender, m_postRenderProgram);
+
+	return STOP;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsTextureBufferCase.hpp b/modules/glshared/glsTextureBufferCase.hpp
new file mode 100644
index 0000000..7416b53
--- /dev/null
+++ b/modules/glshared/glsTextureBufferCase.hpp
@@ -0,0 +1,101 @@
+#ifndef _GLSTEXTUREBUFFERCASE_HPP
+#define _GLSTEXTUREBUFFERCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture buffer test case
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+
+namespace glu
+{
+	class RenderContext;
+	class ShaderProgram;
+} // glu
+
+namespace deqp
+{
+namespace gls
+{
+
+namespace TextureBufferCaseUtil
+{
+
+enum ModifyBits
+{
+	MODIFYBITS_NONE					= 0,
+	MODIFYBITS_BUFFERDATA			= (0x1<<0),
+	MODIFYBITS_BUFFERSUBDATA		= (0x1<<1),
+	MODIFYBITS_MAPBUFFER_WRITE		= (0x1<<2),
+	MODIFYBITS_MAPBUFFER_READWRITE	= (0x1<<3),
+};
+
+enum RenderBits
+{
+	RENDERBITS_NONE					= 0,
+	RENDERBITS_AS_VERTEX_ARRAY		= (0x1<<0),
+	RENDERBITS_AS_INDEX_ARRAY		= (0x1<<1),
+	RENDERBITS_AS_VERTEX_TEXTURE	= (0x1<<2),
+	RENDERBITS_AS_FRAGMENT_TEXTURE	= (0x1<<3)
+};
+
+} // TextureBufferCaseUtil
+
+class TextureBufferCase : public tcu::TestCase
+{
+public:
+					TextureBufferCase	(tcu::TestContext&					testCtx,
+										 glu::RenderContext&				renderCtx,
+										 deUint32							format,
+										 size_t								bufferSize,
+										 size_t								offset,
+										 size_t								size,
+										 TextureBufferCaseUtil::RenderBits	preRender,
+										 TextureBufferCaseUtil::ModifyBits	modify,
+										 TextureBufferCaseUtil::RenderBits	postRender,
+										 const char*						name,
+										 const char*						description);
+
+					~TextureBufferCase	(void);
+
+	void			init				(void);
+	void			deinit				(void);
+	IterateResult	iterate				(void);
+
+private:
+	glu::RenderContext&						m_renderCtx;
+	const deUint32							m_format;
+	const size_t							m_bufferSize;
+	const size_t							m_offset;
+	const size_t							m_size;
+	const TextureBufferCaseUtil::RenderBits	m_preRender;
+	const TextureBufferCaseUtil::ModifyBits	m_modify;
+	const TextureBufferCaseUtil::RenderBits	m_postRender;
+
+	glu::ShaderProgram*						m_preRenderProgram;
+	glu::ShaderProgram*						m_postRenderProgram;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSTEXTUREBUFFERCASE_HPP
diff --git a/modules/glshared/glsTextureTestUtil.cpp b/modules/glshared/glsTextureTestUtil.cpp
new file mode 100644
index 0000000..914008b
--- /dev/null
+++ b/modules/glshared/glsTextureTestUtil.cpp
@@ -0,0 +1,3199 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture test utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsTextureTestUtil.hpp"
+#include "gluDefs.hpp"
+#include "gluDrawUtil.hpp"
+#include "gluRenderContext.hpp"
+#include "deRandom.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuVectorUtil.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuTexLookupVerifier.hpp"
+#include "tcuTexCompareVerifier.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+#include "qpWatchDog.h"
+#include "deStringUtil.hpp"
+
+using tcu::TestLog;
+using std::vector;
+using std::string;
+using std::map;
+
+namespace deqp
+{
+namespace gls
+{
+namespace TextureTestUtil
+{
+
+SamplerType getSamplerType (tcu::TextureFormat format)
+{
+	using tcu::TextureFormat;
+
+	switch (format.type)
+	{
+		case TextureFormat::SIGNED_INT8:
+		case TextureFormat::SIGNED_INT16:
+		case TextureFormat::SIGNED_INT32:
+			return SAMPLERTYPE_INT;
+
+		case TextureFormat::UNSIGNED_INT8:
+		case TextureFormat::UNSIGNED_INT32:
+		case TextureFormat::UNSIGNED_INT_1010102_REV:
+			return SAMPLERTYPE_UINT;
+
+		// Texture formats used in depth/stencil textures.
+		case TextureFormat::UNSIGNED_INT16:
+		case TextureFormat::UNSIGNED_INT_24_8:
+			return (format.order == TextureFormat::D || format.order == TextureFormat::DS) ? SAMPLERTYPE_FLOAT : SAMPLERTYPE_UINT;
+
+		default:
+			return SAMPLERTYPE_FLOAT;
+	}
+}
+
+SamplerType getFetchSamplerType (tcu::TextureFormat format)
+{
+	using tcu::TextureFormat;
+
+	switch (format.type)
+	{
+		case TextureFormat::SIGNED_INT8:
+		case TextureFormat::SIGNED_INT16:
+		case TextureFormat::SIGNED_INT32:
+			return SAMPLERTYPE_FETCH_INT;
+
+		case TextureFormat::UNSIGNED_INT8:
+		case TextureFormat::UNSIGNED_INT32:
+		case TextureFormat::UNSIGNED_INT_1010102_REV:
+			return SAMPLERTYPE_FETCH_UINT;
+
+		// Texture formats used in depth/stencil textures.
+		case TextureFormat::UNSIGNED_INT16:
+		case TextureFormat::UNSIGNED_INT_24_8:
+			return (format.order == TextureFormat::D || format.order == TextureFormat::DS) ? SAMPLERTYPE_FETCH_FLOAT : SAMPLERTYPE_FETCH_UINT;
+
+		default:
+			return SAMPLERTYPE_FETCH_FLOAT;
+	}
+}
+
+static tcu::Texture1DView getSubView (const tcu::Texture1DView& view, int baseLevel, int maxLevel)
+{
+	const int	clampedBase	= de::clamp(baseLevel, 0, view.getNumLevels()-1);
+	const int	clampedMax	= de::clamp(maxLevel, clampedBase, view.getNumLevels()-1);
+	const int	numLevels	= de::min(clampedMax-clampedBase+1, view.getNumLevels()-clampedBase);
+	return tcu::Texture1DView(numLevels, view.getLevels()+clampedBase);
+}
+
+static tcu::Texture2DView getSubView (const tcu::Texture2DView& view, int baseLevel, int maxLevel)
+{
+	const int	clampedBase	= de::clamp(baseLevel, 0, view.getNumLevels()-1);
+	const int	clampedMax	= de::clamp(maxLevel, clampedBase, view.getNumLevels()-1);
+	const int	numLevels	= de::min(clampedMax-clampedBase+1, view.getNumLevels()-clampedBase);
+	return tcu::Texture2DView(numLevels, view.getLevels()+clampedBase);
+}
+
+static tcu::TextureCubeView getSubView (const tcu::TextureCubeView& view, int baseLevel, int maxLevel)
+{
+	const int							clampedBase	= de::clamp(baseLevel, 0, view.getNumLevels()-1);
+	const int							clampedMax	= de::clamp(maxLevel, clampedBase, view.getNumLevels()-1);
+	const int							numLevels	= de::min(clampedMax-clampedBase+1, view.getNumLevels()-clampedBase);
+	const tcu::ConstPixelBufferAccess*	levels[tcu::CUBEFACE_LAST];
+
+	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
+		levels[face] = view.getFaceLevels((tcu::CubeFace)face) + clampedBase;
+
+	return tcu::TextureCubeView(numLevels, levels);
+}
+
+static tcu::Texture3DView getSubView (const tcu::Texture3DView& view, int baseLevel, int maxLevel)
+{
+	const int	clampedBase	= de::clamp(baseLevel, 0, view.getNumLevels()-1);
+	const int	clampedMax	= de::clamp(maxLevel, clampedBase, view.getNumLevels()-1);
+	const int	numLevels	= de::min(clampedMax-clampedBase+1, view.getNumLevels()-clampedBase);
+	return tcu::Texture3DView(numLevels, view.getLevels()+clampedBase);
+}
+
+inline float linearInterpolate (float t, float minVal, float maxVal)
+{
+	return minVal + (maxVal - minVal) * t;
+}
+
+inline tcu::Vec4 linearInterpolate (float t, const tcu::Vec4& a, const tcu::Vec4& b)
+{
+	return a + (b - a) * t;
+}
+
+inline float bilinearInterpolate (float x, float y, const tcu::Vec4& quad)
+{
+	float w00 = (1.0f-x)*(1.0f-y);
+	float w01 = (1.0f-x)*y;
+	float w10 = x*(1.0f-y);
+	float w11 = x*y;
+	return quad.x()*w00 + quad.y()*w10 + quad.z()*w01 + quad.w()*w11;
+}
+
+inline float triangleInterpolate (float v0, float v1, float v2, float x, float y)
+{
+	return v0 + (v2-v0)*x + (v1-v0)*y;
+}
+
+inline float triangleInterpolate (const tcu::Vec3& v, float x, float y)
+{
+	return triangleInterpolate(v.x(), v.y(), v.z(), x, y);
+}
+
+inline float triQuadInterpolate (float x, float y, const tcu::Vec4& quad)
+{
+	// \note Top left fill rule.
+	if (x + y < 1.0f)
+		return triangleInterpolate(quad.x(), quad.y(), quad.z(), x, y);
+	else
+		return triangleInterpolate(quad.w(), quad.z(), quad.y(), 1.0f-x, 1.0f-y);
+}
+
+SurfaceAccess::SurfaceAccess (tcu::Surface& surface, const tcu::PixelFormat& colorFmt, int x, int y, int width, int height)
+	: m_surface		(&surface)
+	, m_colorMask	(getColorMask(colorFmt))
+	, m_x			(x)
+	, m_y			(y)
+	, m_width		(width)
+	, m_height		(height)
+{
+}
+
+SurfaceAccess::SurfaceAccess (tcu::Surface& surface, const tcu::PixelFormat& colorFmt)
+	: m_surface		(&surface)
+	, m_colorMask	(getColorMask(colorFmt))
+	, m_x			(0)
+	, m_y			(0)
+	, m_width		(surface.getWidth())
+	, m_height		(surface.getHeight())
+{
+}
+
+SurfaceAccess::SurfaceAccess (const SurfaceAccess& parent, int x, int y, int width, int height)
+	: m_surface			(parent.m_surface)
+	, m_colorMask		(parent.m_colorMask)
+	, m_x				(parent.m_x + x)
+	, m_y				(parent.m_y + y)
+	, m_width			(width)
+	, m_height			(height)
+{
+}
+
+// 1D lookup LOD computation.
+
+inline float computeLodFromDerivates (LodMode mode, float dudx, float dudy)
+{
+	float p = 0.0f;
+	switch (mode)
+	{
+		// \note [mika] Min and max bounds equal to exact with 1D textures
+		case LODMODE_EXACT:
+		case LODMODE_MIN_BOUND:
+		case LODMODE_MAX_BOUND:
+			p = de::max(deFloatAbs(dudx), deFloatAbs(dudy));
+			break;
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return deFloatLog2(p);
+}
+
+static float computeNonProjectedTriLod (LodMode mode, const tcu::IVec2& dstSize, deInt32 srcSize, const tcu::Vec3& sq)
+{
+	float dux	= (sq.z() - sq.x()) * (float)srcSize;
+	float duy	= (sq.y() - sq.x()) * (float)srcSize;
+	float dx	= (float)dstSize.x();
+	float dy	= (float)dstSize.y();
+
+	return computeLodFromDerivates(mode, dux/dx, duy/dy);
+}
+
+// 2D lookup LOD computation.
+
+inline float computeLodFromDerivates (LodMode mode, float dudx, float dvdx, float dudy, float dvdy)
+{
+	float p = 0.0f;
+	switch (mode)
+	{
+		case LODMODE_EXACT:
+			p = de::max(deFloatSqrt(dudx*dudx + dvdx*dvdx), deFloatSqrt(dudy*dudy + dvdy*dvdy));
+			break;
+
+		case LODMODE_MIN_BOUND:
+		case LODMODE_MAX_BOUND:
+		{
+			float mu = de::max(deFloatAbs(dudx), deFloatAbs(dudy));
+			float mv = de::max(deFloatAbs(dvdx), deFloatAbs(dvdy));
+
+			p = mode == LODMODE_MIN_BOUND ? de::max(mu, mv) : mu + mv;
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return deFloatLog2(p);
+}
+
+static float computeNonProjectedTriLod (LodMode mode, const tcu::IVec2& dstSize, const tcu::IVec2& srcSize, const tcu::Vec3& sq, const tcu::Vec3& tq)
+{
+	float dux	= (sq.z() - sq.x()) * (float)srcSize.x();
+	float duy	= (sq.y() - sq.x()) * (float)srcSize.x();
+	float dvx	= (tq.z() - tq.x()) * (float)srcSize.y();
+	float dvy	= (tq.y() - tq.x()) * (float)srcSize.y();
+	float dx	= (float)dstSize.x();
+	float dy	= (float)dstSize.y();
+
+	return computeLodFromDerivates(mode, dux/dx, dvx/dx, duy/dy, dvy/dy);
+}
+
+// 3D lookup LOD computation.
+
+inline float computeLodFromDerivates (LodMode mode, float dudx, float dvdx, float dwdx, float dudy, float dvdy, float dwdy)
+{
+	float p = 0.0f;
+	switch (mode)
+	{
+		case LODMODE_EXACT:
+			p = de::max(deFloatSqrt(dudx*dudx + dvdx*dvdx + dwdx*dwdx), deFloatSqrt(dudy*dudy + dvdy*dvdy + dwdy*dwdy));
+			break;
+
+		case LODMODE_MIN_BOUND:
+		case LODMODE_MAX_BOUND:
+		{
+			float mu = de::max(deFloatAbs(dudx), deFloatAbs(dudy));
+			float mv = de::max(deFloatAbs(dvdx), deFloatAbs(dvdy));
+			float mw = de::max(deFloatAbs(dwdx), deFloatAbs(dwdy));
+
+			p = mode == LODMODE_MIN_BOUND ? de::max(de::max(mu, mv), mw) : (mu + mv + mw);
+			break;
+		}
+
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	return deFloatLog2(p);
+}
+
+static float computeNonProjectedTriLod (LodMode mode, const tcu::IVec2& dstSize, const tcu::IVec3& srcSize, const tcu::Vec3& sq, const tcu::Vec3& tq, const tcu::Vec3& rq)
+{
+	float dux	= (sq.z() - sq.x()) * (float)srcSize.x();
+	float duy	= (sq.y() - sq.x()) * (float)srcSize.x();
+	float dvx	= (tq.z() - tq.x()) * (float)srcSize.y();
+	float dvy	= (tq.y() - tq.x()) * (float)srcSize.y();
+	float dwx	= (rq.z() - rq.x()) * (float)srcSize.z();
+	float dwy	= (rq.y() - rq.x()) * (float)srcSize.z();
+	float dx	= (float)dstSize.x();
+	float dy	= (float)dstSize.y();
+
+	return computeLodFromDerivates(mode, dux/dx, dvx/dx, dwx/dx, duy/dy, dvy/dy, dwy/dy);
+}
+
+static inline float projectedTriInterpolate (const tcu::Vec3& s, const tcu::Vec3& w, float nx, float ny)
+{
+	return (s[0]*(1.0f-nx-ny)/w[0] + s[1]*ny/w[1] + s[2]*nx/w[2]) / ((1.0f-nx-ny)/w[0] + ny/w[1] + nx/w[2]);
+}
+
+static inline float triDerivateX (const tcu::Vec3& s, const tcu::Vec3& w, float wx, float width, float ny)
+{
+	float d = w[1]*w[2]*(width*(ny - 1.0f) + wx) - w[0]*(w[2]*width*ny + w[1]*wx);
+	return (w[0]*w[1]*w[2]*width * (w[1]*(s[0] - s[2])*(ny - 1.0f) + ny*(w[2]*(s[1] - s[0]) + w[0]*(s[2] - s[1])))) / (d*d);
+}
+
+static inline float triDerivateY (const tcu::Vec3& s, const tcu::Vec3& w, float wy, float height, float nx)
+{
+	float d = w[1]*w[2]*(height*(nx - 1.0f) + wy) - w[0]*(w[1]*height*nx + w[2]*wy);
+	return (w[0]*w[1]*w[2]*height * (w[2]*(s[0] - s[1])*(nx - 1.0f) + nx*(w[0]*(s[1] - s[2]) + w[1]*(s[2] - s[0])))) / (d*d);
+}
+
+// 1D lookup LOD.
+static float computeProjectedTriLod (LodMode mode, const tcu::Vec3& u, const tcu::Vec3& projection, float wx, float wy, float width, float height)
+{
+	// Exact derivatives.
+	float dudx	= triDerivateX(u, projection, wx, width, wy/height);
+	float dudy	= triDerivateY(u, projection, wy, height, wx/width);
+
+	return computeLodFromDerivates(mode, dudx, dudy);
+}
+
+// 2D lookup LOD.
+static float computeProjectedTriLod (LodMode mode, const tcu::Vec3& u, const tcu::Vec3& v, const tcu::Vec3& projection, float wx, float wy, float width, float height)
+{
+	// Exact derivatives.
+	float dudx	= triDerivateX(u, projection, wx, width, wy/height);
+	float dvdx	= triDerivateX(v, projection, wx, width, wy/height);
+	float dudy	= triDerivateY(u, projection, wy, height, wx/width);
+	float dvdy	= triDerivateY(v, projection, wy, height, wx/width);
+
+	return computeLodFromDerivates(mode, dudx, dvdx, dudy, dvdy);
+}
+
+// 3D lookup LOD.
+static float computeProjectedTriLod (LodMode mode, const tcu::Vec3& u, const tcu::Vec3& v, const tcu::Vec3& w, const tcu::Vec3& projection, float wx, float wy, float width, float height)
+{
+	// Exact derivatives.
+	float dudx	= triDerivateX(u, projection, wx, width, wy/height);
+	float dvdx	= triDerivateX(v, projection, wx, width, wy/height);
+	float dwdx	= triDerivateX(w, projection, wx, width, wy/height);
+	float dudy	= triDerivateY(u, projection, wy, height, wx/width);
+	float dvdy	= triDerivateY(v, projection, wy, height, wx/width);
+	float dwdy	= triDerivateY(w, projection, wy, height, wx/width);
+
+	return computeLodFromDerivates(mode, dudx, dvdx, dwdx, dudy, dvdy, dwdy);
+}
+
+static inline tcu::Vec4 execSample (const tcu::Texture1DView& src, const ReferenceParams& params, float s, float lod)
+{
+	if (params.samplerType == SAMPLERTYPE_SHADOW)
+		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, lod), 0.0, 0.0, 1.0f);
+	else
+		return src.sample(params.sampler, s, lod);
+}
+
+static inline tcu::Vec4 execSample (const tcu::Texture2DView& src, const ReferenceParams& params, float s, float t, float lod)
+{
+	if (params.samplerType == SAMPLERTYPE_SHADOW)
+		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, t, lod), 0.0, 0.0, 1.0f);
+	else
+		return src.sample(params.sampler, s, t, lod);
+}
+
+static inline tcu::Vec4 execSample (const tcu::TextureCubeView& src, const ReferenceParams& params, float s, float t, float r, float lod)
+{
+	if (params.samplerType == SAMPLERTYPE_SHADOW)
+		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, t, r, lod), 0.0, 0.0, 1.0f);
+	else
+		return src.sample(params.sampler, s, t, r, lod);
+}
+
+static inline tcu::Vec4 execSample (const tcu::Texture2DArrayView& src, const ReferenceParams& params, float s, float t, float r, float lod)
+{
+	if (params.samplerType == SAMPLERTYPE_SHADOW)
+		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, t, r, lod), 0.0, 0.0, 1.0f);
+	else
+		return src.sample(params.sampler, s, t, r, lod);
+}
+
+static inline tcu::Vec4 execSample (const tcu::TextureCubeArrayView& src, const ReferenceParams& params, float s, float t, float r, float q, float lod)
+{
+	if (params.samplerType == SAMPLERTYPE_SHADOW)
+		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, t, r, q, lod), 0.0, 0.0, 1.0f);
+	else
+		return src.sample(params.sampler, s, t, r, q, lod);
+}
+
+static inline tcu::Vec4 execSample (const tcu::Texture1DArrayView& src, const ReferenceParams& params, float s, float t, float lod)
+{
+	if (params.samplerType == SAMPLERTYPE_SHADOW)
+		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, t, lod), 0.0, 0.0, 1.0f);
+	else
+		return src.sample(params.sampler, s, t, lod);
+}
+
+static void sampleTextureNonProjected (const SurfaceAccess& dst, const tcu::Texture1DView& src, const tcu::Vec4& sq, const ReferenceParams& params)
+{
+	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
+
+	tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
+	int			srcSize		= src.getWidth();
+
+	// Coordinates and lod per triangle.
+	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	float		triLod[2]	= { de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[0]) + lodBias, params.minLod, params.maxLod),
+								de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[1]) + lodBias, params.minLod, params.maxLod) };
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	{
+		for (int x = 0; x < dst.getWidth(); x++)
+		{
+			float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
+			float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();
+
+			int		triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
+			float	triX	= triNdx ? 1.0f-xf : xf;
+			float	triY	= triNdx ? 1.0f-yf : yf;
+
+			float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
+			float	lod		= triLod[triNdx];
+
+			dst.setPixel(execSample(src, params, s, lod) * params.colorScale + params.colorBias, x, y);
+		}
+	}
+}
+
+static void sampleTextureNonProjected (const SurfaceAccess& dst, const tcu::Texture2DView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const ReferenceParams& params)
+{
+	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
+
+	tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
+	tcu::IVec2	srcSize		= tcu::IVec2(src.getWidth(), src.getHeight());
+
+	// Coordinates and lod per triangle.
+	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	float		triLod[2]	= { de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[0], triT[0]) + lodBias, params.minLod, params.maxLod),
+								de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[1], triT[1]) + lodBias, params.minLod, params.maxLod) };
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	{
+		for (int x = 0; x < dst.getWidth(); x++)
+		{
+			float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
+			float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();
+
+			int		triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
+			float	triX	= triNdx ? 1.0f-xf : xf;
+			float	triY	= triNdx ? 1.0f-yf : yf;
+
+			float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
+			float	t		= triangleInterpolate(triT[triNdx].x(), triT[triNdx].y(), triT[triNdx].z(), triX, triY);
+			float	lod		= triLod[triNdx];
+
+			dst.setPixel(execSample(src, params, s, t, lod) * params.colorScale + params.colorBias, x, y);
+		}
+	}
+}
+
+static void sampleTextureProjected (const SurfaceAccess& dst, const tcu::Texture1DView& src, const tcu::Vec4& sq, const ReferenceParams& params)
+{
+	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
+	float		dstW		= (float)dst.getWidth();
+	float		dstH		= (float)dst.getHeight();
+
+	tcu::Vec4	uq			= sq * (float)src.getWidth();
+
+	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	tcu::Vec3	triU[2]		= { uq.swizzle(0, 1, 2), uq.swizzle(3, 2, 1) };
+	tcu::Vec3	triW[2]		= { params.w.swizzle(0, 1, 2), params.w.swizzle(3, 2, 1) };
+
+	for (int py = 0; py < dst.getHeight(); py++)
+	{
+		for (int px = 0; px < dst.getWidth(); px++)
+		{
+			float	wx		= (float)px + 0.5f;
+			float	wy		= (float)py + 0.5f;
+			float	nx		= wx / dstW;
+			float	ny		= wy / dstH;
+
+			int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+			float	triWx	= triNdx ? dstW - wx : wx;
+			float	triWy	= triNdx ? dstH - wy : wy;
+			float	triNx	= triNdx ? 1.0f - nx : nx;
+			float	triNy	= triNdx ? 1.0f - ny : ny;
+
+			float	s		= projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy);
+			float	lod		= computeProjectedTriLod(params.lodMode, triU[triNdx], triW[triNdx], triWx, triWy, (float)dst.getWidth(), (float)dst.getHeight())
+							+ lodBias;
+
+			dst.setPixel(execSample(src, params, s, lod) * params.colorScale + params.colorBias, px, py);
+		}
+	}
+}
+
+static void sampleTextureProjected (const SurfaceAccess& dst, const tcu::Texture2DView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const ReferenceParams& params)
+{
+	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
+	float		dstW		= (float)dst.getWidth();
+	float		dstH		= (float)dst.getHeight();
+
+	tcu::Vec4	uq			= sq * (float)src.getWidth();
+	tcu::Vec4	vq			= tq * (float)src.getHeight();
+
+	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	tcu::Vec3	triU[2]		= { uq.swizzle(0, 1, 2), uq.swizzle(3, 2, 1) };
+	tcu::Vec3	triV[2]		= { vq.swizzle(0, 1, 2), vq.swizzle(3, 2, 1) };
+	tcu::Vec3	triW[2]		= { params.w.swizzle(0, 1, 2), params.w.swizzle(3, 2, 1) };
+
+	for (int py = 0; py < dst.getHeight(); py++)
+	{
+		for (int px = 0; px < dst.getWidth(); px++)
+		{
+			float	wx		= (float)px + 0.5f;
+			float	wy		= (float)py + 0.5f;
+			float	nx		= wx / dstW;
+			float	ny		= wy / dstH;
+
+			int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+			float	triWx	= triNdx ? dstW - wx : wx;
+			float	triWy	= triNdx ? dstH - wy : wy;
+			float	triNx	= triNdx ? 1.0f - nx : nx;
+			float	triNy	= triNdx ? 1.0f - ny : ny;
+
+			float	s		= projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy);
+			float	t		= projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy);
+			float	lod		= computeProjectedTriLod(params.lodMode, triU[triNdx], triV[triNdx], triW[triNdx], triWx, triWy, (float)dst.getWidth(), (float)dst.getHeight())
+							+ lodBias;
+
+			dst.setPixel(execSample(src, params, s, t, lod) * params.colorScale + params.colorBias, px, py);
+		}
+	}
+}
+
+void sampleTexture (const SurfaceAccess& dst, const tcu::Texture2DView& src, const float* texCoord, const ReferenceParams& params)
+{
+	const tcu::Texture2DView	view	= getSubView(src, params.baseLevel, params.maxLevel);
+	const tcu::Vec4				sq		= tcu::Vec4(texCoord[0+0], texCoord[2+0], texCoord[4+0], texCoord[6+0]);
+	const tcu::Vec4				tq		= tcu::Vec4(texCoord[0+1], texCoord[2+1], texCoord[4+1], texCoord[6+1]);
+
+	if (params.flags & ReferenceParams::PROJECTED)
+		sampleTextureProjected(dst, view, sq, tq, params);
+	else
+		sampleTextureNonProjected(dst, view, sq, tq, params);
+}
+
+void sampleTexture (const SurfaceAccess& dst, const tcu::Texture1DView& src, const float* texCoord, const ReferenceParams& params)
+{
+	const tcu::Texture1DView	view	= getSubView(src, params.baseLevel, params.maxLevel);
+	const tcu::Vec4				sq		= tcu::Vec4(texCoord[0], texCoord[1], texCoord[2], texCoord[3]);
+
+	if (params.flags & ReferenceParams::PROJECTED)
+		sampleTextureProjected(dst, view, sq, params);
+	else
+		sampleTextureNonProjected(dst, view, sq, params);
+}
+
+static float computeCubeLodFromDerivates (LodMode lodMode, const tcu::Vec3& coord, const tcu::Vec3& coordDx, const tcu::Vec3& coordDy, const int faceSize)
+{
+	const tcu::CubeFace	face	= tcu::selectCubeFace(coord);
+	int					maNdx	= 0;
+	int					sNdx	= 0;
+	int					tNdx	= 0;
+
+	// \note Derivate signs don't matter when computing lod
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X:
+		case tcu::CUBEFACE_POSITIVE_X: maNdx = 0; sNdx = 2; tNdx = 1; break;
+		case tcu::CUBEFACE_NEGATIVE_Y:
+		case tcu::CUBEFACE_POSITIVE_Y: maNdx = 1; sNdx = 0; tNdx = 2; break;
+		case tcu::CUBEFACE_NEGATIVE_Z:
+		case tcu::CUBEFACE_POSITIVE_Z: maNdx = 2; sNdx = 0; tNdx = 1; break;
+		default:
+			DE_ASSERT(DE_FALSE);
+	}
+
+	{
+		const float		sc		= coord[sNdx];
+		const float		tc		= coord[tNdx];
+		const float		ma		= de::abs(coord[maNdx]);
+		const float		scdx	= coordDx[sNdx];
+		const float		tcdx	= coordDx[tNdx];
+		const float		madx	= de::abs(coordDx[maNdx]);
+		const float		scdy	= coordDy[sNdx];
+		const float		tcdy	= coordDy[tNdx];
+		const float		mady	= de::abs(coordDy[maNdx]);
+		const float		dudx	= float(faceSize) * 0.5f * (scdx*ma - sc*madx) / (ma*ma);
+		const float		dvdx	= float(faceSize) * 0.5f * (tcdx*ma - tc*madx) / (ma*ma);
+		const float		dudy	= float(faceSize) * 0.5f * (scdy*ma - sc*mady) / (ma*ma);
+		const float		dvdy	= float(faceSize) * 0.5f * (tcdy*ma - tc*mady) / (ma*ma);
+
+		return computeLodFromDerivates(lodMode, dudx, dvdx, dudy, dvdy);
+	}
+}
+
+static void sampleTexture (const SurfaceAccess& dst, const tcu::TextureCubeView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const tcu::Vec4& rq, const ReferenceParams& params)
+{
+	const tcu::IVec2	dstSize			= tcu::IVec2(dst.getWidth(), dst.getHeight());
+	const float			dstW			= float(dstSize.x());
+	const float			dstH			= float(dstSize.y());
+	const int			srcSize			= src.getSize();
+
+	// Coordinates per triangle.
+	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triW[2]			= { params.w.swizzle(0, 1, 2), params.w.swizzle(3, 2, 1) };
+
+	const float			lodBias			((params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f);
+
+	for (int py = 0; py < dst.getHeight(); py++)
+	{
+		for (int px = 0; px < dst.getWidth(); px++)
+		{
+			const float		wx		= (float)px + 0.5f;
+			const float		wy		= (float)py + 0.5f;
+			const float		nx		= wx / dstW;
+			const float		ny		= wy / dstH;
+
+			const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+			const float		triNx	= triNdx ? 1.0f - nx : nx;
+			const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+			const tcu::Vec3	coord		(triangleInterpolate(triS[triNdx], triNx, triNy),
+										 triangleInterpolate(triT[triNdx], triNx, triNy),
+										 triangleInterpolate(triR[triNdx], triNx, triNy));
+			const tcu::Vec3	coordDx		(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
+										 triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
+										 triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy));
+			const tcu::Vec3	coordDy		(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
+										 triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
+										 triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx));
+
+			const float		lod			= de::clamp(computeCubeLodFromDerivates(params.lodMode, coord, coordDx, coordDy, srcSize) + lodBias, params.minLod, params.maxLod);
+
+			dst.setPixel(execSample(src, params, coord.x(), coord.y(), coord.z(), lod) * params.colorScale + params.colorBias, px, py);
+		}
+	}
+}
+
+void sampleTexture (const SurfaceAccess& dst, const tcu::TextureCubeView& src, const float* texCoord, const ReferenceParams& params)
+{
+	const tcu::TextureCubeView	view	= getSubView(src, params.baseLevel, params.maxLevel);
+	const tcu::Vec4				sq		= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
+	const tcu::Vec4				tq		= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
+	const tcu::Vec4				rq		= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);
+
+	return sampleTexture(dst, view, sq, tq, rq, params);
+}
+
+// \todo [2013-07-17 pyry] Remove this!
+void sampleTextureMultiFace (const SurfaceAccess& dst, const tcu::TextureCubeView& src, const float* texCoord, const ReferenceParams& params)
+{
+	return sampleTexture(dst, src, texCoord, params);
+}
+
+static void sampleTextureNonProjected (const SurfaceAccess& dst, const tcu::Texture2DArrayView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const tcu::Vec4& rq, const ReferenceParams& params)
+{
+	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
+
+	tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
+	tcu::IVec2	srcSize		= tcu::IVec2(src.getWidth(), src.getHeight());
+
+	// Coordinates and lod per triangle.
+	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	tcu::Vec3	triR[2]		= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
+	float		triLod[2]	= { computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[0], triT[0]) + lodBias,
+								computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[1], triT[1]) + lodBias};
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	{
+		for (int x = 0; x < dst.getWidth(); x++)
+		{
+			float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
+			float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();
+
+			int		triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
+			float	triX	= triNdx ? 1.0f-xf : xf;
+			float	triY	= triNdx ? 1.0f-yf : yf;
+
+			float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
+			float	t		= triangleInterpolate(triT[triNdx].x(), triT[triNdx].y(), triT[triNdx].z(), triX, triY);
+			float	r		= triangleInterpolate(triR[triNdx].x(), triR[triNdx].y(), triR[triNdx].z(), triX, triY);
+			float	lod		= triLod[triNdx];
+
+			dst.setPixel(execSample(src, params, s, t, r, lod) * params.colorScale + params.colorBias, x, y);
+		}
+	}
+}
+
+void sampleTexture (const SurfaceAccess& dst, const tcu::Texture2DArrayView& src, const float* texCoord, const ReferenceParams& params)
+{
+	tcu::Vec4 sq = tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
+	tcu::Vec4 tq = tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
+	tcu::Vec4 rq = tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);
+
+	DE_ASSERT(!(params.flags & ReferenceParams::PROJECTED)); // \todo [2012-02-17 pyry] Support projected lookups.
+	sampleTextureNonProjected(dst, src, sq, tq, rq, params);
+}
+
+static void sampleTextureNonProjected (const SurfaceAccess& dst, const tcu::Texture1DArrayView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const ReferenceParams& params)
+{
+	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
+
+	tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
+	deInt32		srcSize		= src.getWidth();
+
+	// Coordinates and lod per triangle.
+	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	float		triLod[2]	= { computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[0]) + lodBias,
+								computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[1]) + lodBias};
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	{
+		for (int x = 0; x < dst.getWidth(); x++)
+		{
+			float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
+			float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();
+
+			int		triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
+			float	triX	= triNdx ? 1.0f-xf : xf;
+			float	triY	= triNdx ? 1.0f-yf : yf;
+
+			float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
+			float	t		= triangleInterpolate(triT[triNdx].x(), triT[triNdx].y(), triT[triNdx].z(), triX, triY);
+			float	lod		= triLod[triNdx];
+
+			dst.setPixel(execSample(src, params, s, t, lod) * params.colorScale + params.colorBias, x, y);
+		}
+	}
+}
+
+void sampleTexture (const SurfaceAccess& dst, const tcu::Texture1DArrayView& src, const float* texCoord, const ReferenceParams& params)
+{
+	tcu::Vec4 sq = tcu::Vec4(texCoord[0+0], texCoord[2+0], texCoord[4+0], texCoord[6+0]);
+	tcu::Vec4 tq = tcu::Vec4(texCoord[0+1], texCoord[2+1], texCoord[4+1], texCoord[6+1]);
+
+	DE_ASSERT(!(params.flags & ReferenceParams::PROJECTED)); // \todo [2014-06-09 mika] Support projected lookups.
+	sampleTextureNonProjected(dst, src, sq, tq, params);
+}
+
+static void sampleTextureNonProjected (const SurfaceAccess& dst, const tcu::Texture3DView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const tcu::Vec4& rq, const ReferenceParams& params)
+{
+	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
+
+	tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
+	tcu::IVec3	srcSize		= tcu::IVec3(src.getWidth(), src.getHeight(), src.getDepth());
+
+	// Coordinates and lod per triangle.
+	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	tcu::Vec3	triR[2]		= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
+	float		triLod[2]	= { de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[0], triT[0], triR[0]) + lodBias, params.minLod, params.maxLod),
+								de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[1], triT[1], triR[1]) + lodBias, params.minLod, params.maxLod) };
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	{
+		for (int x = 0; x < dst.getWidth(); x++)
+		{
+			float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
+			float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();
+
+			int		triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
+			float	triX	= triNdx ? 1.0f-xf : xf;
+			float	triY	= triNdx ? 1.0f-yf : yf;
+
+			float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
+			float	t		= triangleInterpolate(triT[triNdx].x(), triT[triNdx].y(), triT[triNdx].z(), triX, triY);
+			float	r		= triangleInterpolate(triR[triNdx].x(), triR[triNdx].y(), triR[triNdx].z(), triX, triY);
+			float	lod		= triLod[triNdx];
+
+			dst.setPixel(src.sample(params.sampler, s, t, r, lod) * params.colorScale + params.colorBias, x, y);
+		}
+	}
+}
+
+static void sampleTextureProjected (const SurfaceAccess& dst, const tcu::Texture3DView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const tcu::Vec4& rq, const ReferenceParams& params)
+{
+	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
+	float		dstW		= (float)dst.getWidth();
+	float		dstH		= (float)dst.getHeight();
+
+	tcu::Vec4	uq			= sq * (float)src.getWidth();
+	tcu::Vec4	vq			= tq * (float)src.getHeight();
+	tcu::Vec4	wq			= rq * (float)src.getDepth();
+
+	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	tcu::Vec3	triR[2]		= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
+	tcu::Vec3	triU[2]		= { uq.swizzle(0, 1, 2), uq.swizzle(3, 2, 1) };
+	tcu::Vec3	triV[2]		= { vq.swizzle(0, 1, 2), vq.swizzle(3, 2, 1) };
+	tcu::Vec3	triW[2]		= { wq.swizzle(0, 1, 2), wq.swizzle(3, 2, 1) };
+	tcu::Vec3	triP[2]		= { params.w.swizzle(0, 1, 2), params.w.swizzle(3, 2, 1) };
+
+	for (int py = 0; py < dst.getHeight(); py++)
+	{
+		for (int px = 0; px < dst.getWidth(); px++)
+		{
+			float	wx		= (float)px + 0.5f;
+			float	wy		= (float)py + 0.5f;
+			float	nx		= wx / dstW;
+			float	ny		= wy / dstH;
+
+			int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+			float	triWx	= triNdx ? dstW - wx : wx;
+			float	triWy	= triNdx ? dstH - wy : wy;
+			float	triNx	= triNdx ? 1.0f - nx : nx;
+			float	triNy	= triNdx ? 1.0f - ny : ny;
+
+			float	s		= projectedTriInterpolate(triS[triNdx], triP[triNdx], triNx, triNy);
+			float	t		= projectedTriInterpolate(triT[triNdx], triP[triNdx], triNx, triNy);
+			float	r		= projectedTriInterpolate(triR[triNdx], triP[triNdx], triNx, triNy);
+			float	lod		= computeProjectedTriLod(params.lodMode, triU[triNdx], triV[triNdx], triW[triNdx], triP[triNdx], triWx, triWy, (float)dst.getWidth(), (float)dst.getHeight())
+							+ lodBias;
+
+			dst.setPixel(src.sample(params.sampler, s, t, r, lod) * params.colorScale + params.colorBias, px, py);
+		}
+	}
+}
+
+void sampleTexture (const SurfaceAccess& dst, const tcu::Texture3DView& src, const float* texCoord, const ReferenceParams& params)
+{
+	const tcu::Texture3DView	view	= getSubView(src, params.baseLevel, params.maxLevel);
+	const tcu::Vec4				sq		= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
+	const tcu::Vec4				tq		= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
+	const tcu::Vec4				rq		= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);
+
+	if (params.flags & ReferenceParams::PROJECTED)
+		sampleTextureProjected(dst, view, sq, tq, rq, params);
+	else
+		sampleTextureNonProjected(dst, view, sq, tq, rq, params);
+}
+
+static void sampleTexture (const SurfaceAccess& dst, const tcu::TextureCubeArrayView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const tcu::Vec4& rq, const tcu::Vec4& qq, const ReferenceParams& params)
+{
+	const float		dstW	= (float)dst.getWidth();
+	const float		dstH	= (float)dst.getHeight();
+
+	// Coordinates per triangle.
+	tcu::Vec3		triS[2]	= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	tcu::Vec3		triT[2]	= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	tcu::Vec3		triR[2]	= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
+	tcu::Vec3		triQ[2]	= { qq.swizzle(0, 1, 2), qq.swizzle(3, 2, 1) };
+	const tcu::Vec3	triW[2]	= { params.w.swizzle(0, 1, 2), params.w.swizzle(3, 2, 1) };
+
+	const float		lodBias	= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
+
+	for (int py = 0; py < dst.getHeight(); py++)
+	{
+		for (int px = 0; px < dst.getWidth(); px++)
+		{
+			const float		wx		= (float)px + 0.5f;
+			const float		wy		= (float)py + 0.5f;
+			const float		nx		= wx / dstW;
+			const float		ny		= wy / dstH;
+
+			const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+			const float		triNx	= triNdx ? 1.0f - nx : nx;
+			const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+			const tcu::Vec3	coord	(triangleInterpolate(triS[triNdx], triNx, triNy),
+									 triangleInterpolate(triT[triNdx], triNx, triNy),
+									 triangleInterpolate(triR[triNdx], triNx, triNy));
+
+			const float		coordQ	= triangleInterpolate(triQ[triNdx], triNx, triNy);
+
+			const tcu::Vec3	coordDx	(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
+									 triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
+									 triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy));
+			const tcu::Vec3	coordDy	(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
+									 triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
+									 triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx));
+
+			const float		lod			= de::clamp(computeCubeLodFromDerivates(params.lodMode, coord, coordDx, coordDy, src.getWidth()) + lodBias, params.minLod, params.maxLod);
+
+			dst.setPixel(execSample(src, params, coord.x(), coord.y(), coord.z(), coordQ, lod) * params.colorScale + params.colorBias, px, py);
+		}
+	}
+}
+
+void sampleTexture (const SurfaceAccess& dst, const tcu::TextureCubeArrayView& src, const float* texCoord, const ReferenceParams& params)
+{
+	tcu::Vec4 sq = tcu::Vec4(texCoord[0+0], texCoord[4+0], texCoord[8+0], texCoord[12+0]);
+	tcu::Vec4 tq = tcu::Vec4(texCoord[0+1], texCoord[4+1], texCoord[8+1], texCoord[12+1]);
+	tcu::Vec4 rq = tcu::Vec4(texCoord[0+2], texCoord[4+2], texCoord[8+2], texCoord[12+2]);
+	tcu::Vec4 qq = tcu::Vec4(texCoord[0+3], texCoord[4+3], texCoord[8+3], texCoord[12+3]);
+
+	sampleTexture(dst, src, sq, tq, rq, qq, params);
+}
+
+void fetchTexture (const SurfaceAccess& dst, const tcu::ConstPixelBufferAccess& src, const float* texCoord, const tcu::Vec4& colorScale, const tcu::Vec4& colorBias)
+{
+	const tcu::Vec4		sq			= tcu::Vec4(texCoord[0], texCoord[1], texCoord[2], texCoord[3]);
+	const tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
+	const tcu::Vec3		triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	{
+		for (int x = 0; x < dst.getWidth(); x++)
+		{
+			const float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
+			const float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();
+
+			const int	triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
+			const float	triX	= triNdx ? 1.0f-xf : xf;
+			const float	triY	= triNdx ? 1.0f-yf : yf;
+
+			const float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
+
+			dst.setPixel(src.getPixel((int)s, 0) * colorScale + colorBias, x, y);
+		}
+	}
+}
+
+void clear (const SurfaceAccess& dst, const tcu::Vec4& color)
+{
+	for (int y = 0; y < dst.getHeight(); y++)
+		for (int x = 0; x < dst.getWidth(); x++)
+			dst.setPixel(color, x, y);
+}
+
+bool compareImages (TestLog& log, const tcu::Surface& reference, const tcu::Surface& rendered, tcu::RGBA threshold)
+{
+	return tcu::pixelThresholdCompare(log, "Result", "Image comparison result", reference, rendered, threshold, tcu::COMPARE_LOG_RESULT);
+}
+
+bool compareImages (TestLog& log, const char* name, const char* desc, const tcu::Surface& reference, const tcu::Surface& rendered, tcu::RGBA threshold)
+{
+	return tcu::pixelThresholdCompare(log, name, desc, reference, rendered, threshold, tcu::COMPARE_LOG_RESULT);
+}
+
+int measureAccuracy (tcu::TestLog& log, const tcu::Surface& reference, const tcu::Surface& rendered, int bestScoreDiff, int worstScoreDiff)
+{
+	return tcu::measurePixelDiffAccuracy(log, "Result", "Image comparison result", reference, rendered, bestScoreDiff, worstScoreDiff, tcu::COMPARE_LOG_EVERYTHING);
+}
+
+inline int rangeDiff (int x, int a, int b)
+{
+	if (x < a)
+		return a-x;
+	else if (x > b)
+		return x-b;
+	else
+		return 0;
+}
+
+inline tcu::RGBA rangeDiff (tcu::RGBA p, tcu::RGBA a, tcu::RGBA b)
+{
+	int rMin = de::min(a.getRed(),		b.getRed());
+	int rMax = de::max(a.getRed(),		b.getRed());
+	int gMin = de::min(a.getGreen(),	b.getGreen());
+	int gMax = de::max(a.getGreen(),	b.getGreen());
+	int bMin = de::min(a.getBlue(),		b.getBlue());
+	int bMax = de::max(a.getBlue(),		b.getBlue());
+	int aMin = de::min(a.getAlpha(),	b.getAlpha());
+	int aMax = de::max(a.getAlpha(),	b.getAlpha());
+
+	return tcu::RGBA(rangeDiff(p.getRed(),		rMin, rMax),
+					 rangeDiff(p.getGreen(),	gMin, gMax),
+					 rangeDiff(p.getBlue(),		bMin, bMax),
+					 rangeDiff(p.getAlpha(),	aMin, aMax));
+}
+
+inline bool rangeCompare (tcu::RGBA p, tcu::RGBA a, tcu::RGBA b, tcu::RGBA threshold)
+{
+	tcu::RGBA diff = rangeDiff(p, a, b);
+	return diff.getRed()	<= threshold.getRed() &&
+		   diff.getGreen()	<= threshold.getGreen() &&
+		   diff.getBlue()	<= threshold.getBlue() &&
+		   diff.getAlpha()	<= threshold.getAlpha();
+}
+
+RandomViewport::RandomViewport (const tcu::RenderTarget& renderTarget, int preferredWidth, int preferredHeight, deUint32 seed)
+	: x			(0)
+	, y			(0)
+	, width		(deMin32(preferredWidth, renderTarget.getWidth()))
+	, height	(deMin32(preferredHeight, renderTarget.getHeight()))
+{
+	de::Random rnd(seed);
+	x = rnd.getInt(0, renderTarget.getWidth()	- width);
+	y = rnd.getInt(0, renderTarget.getHeight()	- height);
+}
+
+ProgramLibrary::ProgramLibrary (const glu::RenderContext& context, tcu::TestContext& testCtx, glu::GLSLVersion glslVersion, glu::Precision texCoordPrecision)
+	: m_context				(context)
+	, m_testCtx				(testCtx)
+	, m_glslVersion			(glslVersion)
+	, m_texCoordPrecision	(texCoordPrecision)
+{
+}
+
+ProgramLibrary::~ProgramLibrary (void)
+{
+	clear();
+}
+
+void ProgramLibrary::clear (void)
+{
+	for (map<Program, glu::ShaderProgram*>::iterator i = m_programs.begin(); i != m_programs.end(); i++)
+	{
+		delete i->second;
+		i->second = DE_NULL;
+	}
+	m_programs.clear();
+}
+
+glu::ShaderProgram* ProgramLibrary::getProgram (Program program)
+{
+	TestLog& log = m_testCtx.getLog();
+
+	if (m_programs.find(program) != m_programs.end())
+		return m_programs[program]; // Return from cache.
+
+	static const char* vertShaderTemplate =
+		"${VTX_HEADER}"
+		"${VTX_IN} highp vec4 a_position;\n"
+		"${VTX_IN} ${PRECISION} ${TEXCOORD_TYPE} a_texCoord;\n"
+		"${VTX_OUT} ${PRECISION} ${TEXCOORD_TYPE} v_texCoord;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	gl_Position = a_position;\n"
+		"	v_texCoord = a_texCoord;\n"
+		"}\n";
+	static const char* fragShaderTemplate =
+		"${FRAG_HEADER}"
+		"${FRAG_IN} ${PRECISION} ${TEXCOORD_TYPE} v_texCoord;\n"
+		"uniform ${PRECISION} float u_bias;\n"
+		"uniform ${PRECISION} float u_ref;\n"
+		"uniform ${PRECISION} vec4 u_colorScale;\n"
+		"uniform ${PRECISION} vec4 u_colorBias;\n"
+		"uniform ${PRECISION} ${SAMPLER_TYPE} u_sampler;\n"
+		"\n"
+		"void main (void)\n"
+		"{\n"
+		"	${FRAG_COLOR} = ${LOOKUP} * u_colorScale + u_colorBias;\n"
+		"}\n";
+
+	map<string, string> params;
+
+	bool	isCube		= de::inRange<int>(program, PROGRAM_CUBE_FLOAT, PROGRAM_CUBE_SHADOW_BIAS);
+	bool	isArray		= de::inRange<int>(program, PROGRAM_2D_ARRAY_FLOAT, PROGRAM_2D_ARRAY_SHADOW)
+							|| de::inRange<int>(program, PROGRAM_1D_ARRAY_FLOAT, PROGRAM_1D_ARRAY_SHADOW);
+
+	bool	is1D		= de::inRange<int>(program, PROGRAM_1D_FLOAT, PROGRAM_1D_UINT_BIAS)
+							|| de::inRange<int>(program, PROGRAM_1D_ARRAY_FLOAT, PROGRAM_1D_ARRAY_SHADOW)
+							|| de::inRange<int>(program, PROGRAM_BUFFER_FLOAT, PROGRAM_BUFFER_UINT);
+
+	bool	is2D		= de::inRange<int>(program, PROGRAM_2D_FLOAT, PROGRAM_2D_UINT_BIAS)
+							|| de::inRange<int>(program, PROGRAM_2D_ARRAY_FLOAT, PROGRAM_2D_ARRAY_SHADOW);
+
+	bool	is3D		= de::inRange<int>(program, PROGRAM_3D_FLOAT, PROGRAM_3D_UINT_BIAS);
+	bool	isCubeArray	= de::inRange<int>(program, PROGRAM_CUBE_ARRAY_FLOAT, PROGRAM_CUBE_ARRAY_SHADOW);
+
+	if (m_glslVersion == glu::GLSL_VERSION_100_ES)
+	{
+		params["FRAG_HEADER"]	= "";
+		params["VTX_HEADER"]	= "";
+		params["VTX_IN"]		= "attribute";
+		params["VTX_OUT"]		= "varying";
+		params["FRAG_IN"]		= "varying";
+		params["FRAG_COLOR"]	= "gl_FragColor";
+	}
+	else if (m_glslVersion == glu::GLSL_VERSION_300_ES || m_glslVersion == glu::GLSL_VERSION_310_ES || m_glslVersion == glu::GLSL_VERSION_330)
+	{
+		const string version	= glu::getGLSLVersionDeclaration(m_glslVersion);
+		const string ext		= (isCubeArray && glu::glslVersionIsES(m_glslVersion)) ? "\n#extension GL_EXT_texture_cube_map_array : require" : "";
+
+		params["FRAG_HEADER"]	= version + ext + "\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+		params["VTX_HEADER"]	= version + ext + "\n";
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "dEQP_FragColor";
+	}
+	else
+		DE_ASSERT(!"Unsupported version");
+
+	params["PRECISION"]		= glu::getPrecisionName(m_texCoordPrecision);
+
+	if (isCubeArray)
+		params["TEXCOORD_TYPE"]	= "vec4";
+	else if (isCube || (is2D && isArray) || is3D)
+		params["TEXCOORD_TYPE"]	= "vec3";
+	else if ((is1D && isArray) || is2D)
+		params["TEXCOORD_TYPE"]	= "vec2";
+	else if (is1D)
+		params["TEXCOORD_TYPE"]	= "float";
+	else
+		DE_ASSERT(DE_FALSE);
+
+	const char*	sampler	= DE_NULL;
+	const char*	lookup	= DE_NULL;
+
+	if (m_glslVersion == glu::GLSL_VERSION_300_ES || m_glslVersion == glu::GLSL_VERSION_310_ES || m_glslVersion == glu::GLSL_VERSION_330)
+	{
+		switch (program)
+		{
+			case PROGRAM_2D_FLOAT:			sampler = "sampler2D";				lookup = "texture(u_sampler, v_texCoord)";												break;
+			case PROGRAM_2D_INT:			sampler = "isampler2D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_2D_UINT:			sampler = "usampler2D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_2D_SHADOW:			sampler = "sampler2DShadow";		lookup = "vec4(texture(u_sampler, vec3(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
+			case PROGRAM_2D_FLOAT_BIAS:		sampler = "sampler2D";				lookup = "texture(u_sampler, v_texCoord, u_bias)";										break;
+			case PROGRAM_2D_INT_BIAS:		sampler = "isampler2D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
+			case PROGRAM_2D_UINT_BIAS:		sampler = "usampler2D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
+			case PROGRAM_2D_SHADOW_BIAS:	sampler = "sampler2DShadow";		lookup = "vec4(texture(u_sampler, vec3(v_texCoord, u_ref), u_bias), 0.0, 0.0, 1.0)";	break;
+			case PROGRAM_1D_FLOAT:			sampler = "sampler1D";				lookup = "texture(u_sampler, v_texCoord)";												break;
+			case PROGRAM_1D_INT:			sampler = "isampler1D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_1D_UINT:			sampler = "usampler1D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_1D_SHADOW:			sampler = "sampler1DShadow";		lookup = "vec4(texture(u_sampler, vec3(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
+			case PROGRAM_1D_FLOAT_BIAS:		sampler = "sampler1D";				lookup = "texture(u_sampler, v_texCoord, u_bias)";										break;
+			case PROGRAM_1D_INT_BIAS:		sampler = "isampler1D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
+			case PROGRAM_1D_UINT_BIAS:		sampler = "usampler1D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
+			case PROGRAM_1D_SHADOW_BIAS:	sampler = "sampler1DShadow";		lookup = "vec4(texture(u_sampler, vec3(v_texCoord, u_ref), u_bias), 0.0, 0.0, 1.0)";	break;
+			case PROGRAM_CUBE_FLOAT:		sampler = "samplerCube";			lookup = "texture(u_sampler, v_texCoord)";												break;
+			case PROGRAM_CUBE_INT:			sampler = "isamplerCube";			lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_CUBE_UINT:			sampler = "usamplerCube";			lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_CUBE_SHADOW:		sampler = "samplerCubeShadow";		lookup = "vec4(texture(u_sampler, vec4(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
+			case PROGRAM_CUBE_FLOAT_BIAS:	sampler = "samplerCube";			lookup = "texture(u_sampler, v_texCoord, u_bias)";										break;
+			case PROGRAM_CUBE_INT_BIAS:		sampler = "isamplerCube";			lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
+			case PROGRAM_CUBE_UINT_BIAS:	sampler = "usamplerCube";			lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
+			case PROGRAM_CUBE_SHADOW_BIAS:	sampler = "samplerCubeShadow";		lookup = "vec4(texture(u_sampler, vec4(v_texCoord, u_ref), u_bias), 0.0, 0.0, 1.0)";	break;
+			case PROGRAM_2D_ARRAY_FLOAT:	sampler = "sampler2DArray";			lookup = "texture(u_sampler, v_texCoord)";												break;
+			case PROGRAM_2D_ARRAY_INT:		sampler = "isampler2DArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_2D_ARRAY_UINT:		sampler = "usampler2DArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_2D_ARRAY_SHADOW:	sampler = "sampler2DArrayShadow";	lookup = "vec4(texture(u_sampler, vec4(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
+			case PROGRAM_3D_FLOAT:			sampler = "sampler3D";				lookup = "texture(u_sampler, v_texCoord)";												break;
+			case PROGRAM_3D_INT:			sampler = "isampler3D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_3D_UINT:			sampler =" usampler3D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_3D_FLOAT_BIAS:		sampler = "sampler3D";				lookup = "texture(u_sampler, v_texCoord, u_bias)";										break;
+			case PROGRAM_3D_INT_BIAS:		sampler = "isampler3D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
+			case PROGRAM_3D_UINT_BIAS:		sampler =" usampler3D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
+			case PROGRAM_CUBE_ARRAY_FLOAT:	sampler = "samplerCubeArray";		lookup = "texture(u_sampler, v_texCoord)";												break;
+			case PROGRAM_CUBE_ARRAY_INT:	sampler = "isamplerCubeArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_CUBE_ARRAY_UINT:	sampler = "usamplerCubeArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_CUBE_ARRAY_SHADOW:	sampler = "samplerCubeArrayShadow";	lookup = "vec4(texture(u_sampler, vec4(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
+			case PROGRAM_1D_ARRAY_FLOAT:	sampler = "sampler1DArray";			lookup = "texture(u_sampler, v_texCoord)";												break;
+			case PROGRAM_1D_ARRAY_INT:		sampler = "isampler1DArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_1D_ARRAY_UINT:		sampler = "usampler1DArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
+			case PROGRAM_1D_ARRAY_SHADOW:	sampler = "sampler1DArrayShadow";	lookup = "vec4(texture(u_sampler, vec4(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
+			case PROGRAM_BUFFER_FLOAT:		sampler = "samplerBuffer";			lookup = "texelFetch(u_sampler, int(v_texCoord))";										break;
+			case PROGRAM_BUFFER_INT:		sampler = "isamplerBuffer";			lookup = "vec4(texelFetch(u_sampler, int(v_texCoord)))";								break;
+			case PROGRAM_BUFFER_UINT:		sampler = "usamplerBuffer";			lookup = "vec4(texelFetch(u_sampler, int(v_texCoord)))";								break;
+			default:
+				DE_ASSERT(false);
+		}
+	}
+	else if (m_glslVersion == glu::GLSL_VERSION_100_ES)
+	{
+		sampler = isCube ? "samplerCube" : "sampler2D";
+
+		switch (program)
+		{
+			case PROGRAM_2D_FLOAT:			lookup = "texture2D(u_sampler, v_texCoord)";			break;
+			case PROGRAM_2D_FLOAT_BIAS:		lookup = "texture2D(u_sampler, v_texCoord, u_bias)";	break;
+			case PROGRAM_CUBE_FLOAT:		lookup = "textureCube(u_sampler, v_texCoord)";			break;
+			case PROGRAM_CUBE_FLOAT_BIAS:	lookup = "textureCube(u_sampler, v_texCoord, u_bias)";	break;
+			default:
+				DE_ASSERT(false);
+		}
+	}
+	else
+		DE_ASSERT(!"Unsupported version");
+
+	params["SAMPLER_TYPE"]	= sampler;
+	params["LOOKUP"]		= lookup;
+
+	std::string vertSrc = tcu::StringTemplate(vertShaderTemplate).specialize(params);
+	std::string fragSrc = tcu::StringTemplate(fragShaderTemplate).specialize(params);
+
+	glu::ShaderProgram* progObj = new glu::ShaderProgram(m_context, glu::makeVtxFragSources(vertSrc, fragSrc));
+	if (!progObj->isOk())
+	{
+		log << *progObj;
+		delete progObj;
+		TCU_FAIL("Failed to compile shader program");
+	}
+
+	try
+	{
+		m_programs[program] = progObj;
+	}
+	catch (...)
+	{
+		delete progObj;
+		throw;
+	}
+
+	return progObj;
+}
+
+TextureRenderer::TextureRenderer (const glu::RenderContext& context, tcu::TestContext& testCtx, glu::GLSLVersion glslVersion, glu::Precision texCoordPrecision)
+	: m_renderCtx		(context)
+	, m_testCtx			(testCtx)
+	, m_programLibrary	(context, testCtx, glslVersion, texCoordPrecision)
+{
+}
+
+TextureRenderer::~TextureRenderer (void)
+{
+	clear();
+}
+
+void TextureRenderer::clear (void)
+{
+	m_programLibrary.clear();
+}
+
+void TextureRenderer::renderQuad (int texUnit, const float* texCoord, TextureType texType)
+{
+	renderQuad(texUnit, texCoord, RenderParams(texType));
+}
+
+void TextureRenderer::renderQuad (int texUnit, const float* texCoord, const RenderParams& params)
+{
+	const glw::Functions&	gl			= m_renderCtx.getFunctions();
+	tcu::Vec4				wCoord		= params.flags & RenderParams::PROJECTED ? params.w : tcu::Vec4(1.0f);
+	bool					useBias		= !!(params.flags & RenderParams::USE_BIAS);
+	TestLog&				log			= m_testCtx.getLog();
+	bool					logUniforms	= !!(params.flags & RenderParams::LOG_UNIFORMS);
+
+	// Render quad with texture.
+	float position[] =
+	{
+		-1.0f*wCoord.x(), -1.0f*wCoord.x(), 0.0f, wCoord.x(),
+		-1.0f*wCoord.y(), +1.0f*wCoord.y(), 0.0f, wCoord.y(),
+		+1.0f*wCoord.z(), -1.0f*wCoord.z(), 0.0f, wCoord.z(),
+		+1.0f*wCoord.w(), +1.0f*wCoord.w(), 0.0f, wCoord.w()
+	};
+	static const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+	Program progSpec	= PROGRAM_LAST;
+	int		numComps	= 0;
+	if (params.texType == TEXTURETYPE_2D)
+	{
+		numComps = 2;
+
+		switch (params.samplerType)
+		{
+			case SAMPLERTYPE_FLOAT:		progSpec = useBias ? PROGRAM_2D_FLOAT_BIAS	: PROGRAM_2D_FLOAT;		break;
+			case SAMPLERTYPE_INT:		progSpec = useBias ? PROGRAM_2D_INT_BIAS	: PROGRAM_2D_INT;		break;
+			case SAMPLERTYPE_UINT:		progSpec = useBias ? PROGRAM_2D_UINT_BIAS	: PROGRAM_2D_UINT;		break;
+			case SAMPLERTYPE_SHADOW:	progSpec = useBias ? PROGRAM_2D_SHADOW_BIAS	: PROGRAM_2D_SHADOW;	break;
+			default:					DE_ASSERT(false);
+		}
+	}
+	else if (params.texType == TEXTURETYPE_1D)
+	{
+		numComps = 1;
+
+		switch (params.samplerType)
+		{
+			case SAMPLERTYPE_FLOAT:		progSpec = useBias ? PROGRAM_1D_FLOAT_BIAS	: PROGRAM_1D_FLOAT;		break;
+			case SAMPLERTYPE_INT:		progSpec = useBias ? PROGRAM_1D_INT_BIAS	: PROGRAM_1D_INT;		break;
+			case SAMPLERTYPE_UINT:		progSpec = useBias ? PROGRAM_1D_UINT_BIAS	: PROGRAM_1D_UINT;		break;
+			case SAMPLERTYPE_SHADOW:	progSpec = useBias ? PROGRAM_1D_SHADOW_BIAS	: PROGRAM_1D_SHADOW;	break;
+			default:					DE_ASSERT(false);
+		}
+	}
+	else if (params.texType == TEXTURETYPE_CUBE)
+	{
+		numComps = 3;
+
+		switch (params.samplerType)
+		{
+			case SAMPLERTYPE_FLOAT:		progSpec = useBias ? PROGRAM_CUBE_FLOAT_BIAS	: PROGRAM_CUBE_FLOAT;	break;
+			case SAMPLERTYPE_INT:		progSpec = useBias ? PROGRAM_CUBE_INT_BIAS		: PROGRAM_CUBE_INT;		break;
+			case SAMPLERTYPE_UINT:		progSpec = useBias ? PROGRAM_CUBE_UINT_BIAS		: PROGRAM_CUBE_UINT;	break;
+			case SAMPLERTYPE_SHADOW:	progSpec = useBias ? PROGRAM_CUBE_SHADOW_BIAS	: PROGRAM_CUBE_SHADOW;	break;
+			default:					DE_ASSERT(false);
+		}
+	}
+	else if (params.texType == TEXTURETYPE_3D)
+	{
+		numComps = 3;
+
+		switch (params.samplerType)
+		{
+			case SAMPLERTYPE_FLOAT:		progSpec = useBias ? PROGRAM_3D_FLOAT_BIAS	: PROGRAM_3D_FLOAT;		break;
+			case SAMPLERTYPE_INT:		progSpec = useBias ? PROGRAM_3D_INT_BIAS	: PROGRAM_3D_INT;		break;
+			case SAMPLERTYPE_UINT:		progSpec = useBias ? PROGRAM_3D_UINT_BIAS	: PROGRAM_3D_UINT;		break;
+			default:					DE_ASSERT(false);
+		}
+	}
+	else if (params.texType == TEXTURETYPE_2D_ARRAY)
+	{
+		DE_ASSERT(!useBias); // \todo [2012-02-17 pyry] Support bias.
+
+		numComps = 3;
+
+		switch (params.samplerType)
+		{
+			case SAMPLERTYPE_FLOAT:		progSpec = PROGRAM_2D_ARRAY_FLOAT;	break;
+			case SAMPLERTYPE_INT:		progSpec = PROGRAM_2D_ARRAY_INT;	break;
+			case SAMPLERTYPE_UINT:		progSpec = PROGRAM_2D_ARRAY_UINT;	break;
+			case SAMPLERTYPE_SHADOW:	progSpec = PROGRAM_2D_ARRAY_SHADOW;	break;
+			default:					DE_ASSERT(false);
+		}
+	}
+	else if (params.texType == TEXTURETYPE_CUBE_ARRAY)
+	{
+		DE_ASSERT(!useBias);
+
+		numComps = 4;
+
+		switch (params.samplerType)
+		{
+			case SAMPLERTYPE_FLOAT:		progSpec = PROGRAM_CUBE_ARRAY_FLOAT;	break;
+			case SAMPLERTYPE_INT:		progSpec = PROGRAM_CUBE_ARRAY_INT;		break;
+			case SAMPLERTYPE_UINT:		progSpec = PROGRAM_CUBE_ARRAY_UINT;		break;
+			case SAMPLERTYPE_SHADOW:	progSpec = PROGRAM_CUBE_ARRAY_SHADOW;	break;
+			default:					DE_ASSERT(false);
+		}
+	}
+	else if (params.texType == TEXTURETYPE_1D_ARRAY)
+	{
+		DE_ASSERT(!useBias); // \todo [2012-02-17 pyry] Support bias.
+
+		numComps = 2;
+
+		switch (params.samplerType)
+		{
+			case SAMPLERTYPE_FLOAT:		progSpec = PROGRAM_1D_ARRAY_FLOAT;	break;
+			case SAMPLERTYPE_INT:		progSpec = PROGRAM_1D_ARRAY_INT;	break;
+			case SAMPLERTYPE_UINT:		progSpec = PROGRAM_1D_ARRAY_UINT;	break;
+			case SAMPLERTYPE_SHADOW:	progSpec = PROGRAM_1D_ARRAY_SHADOW;	break;
+			default:					DE_ASSERT(false);
+		}
+	}
+	else if (params.texType == TEXTURETYPE_BUFFER)
+	{
+		numComps = 1;
+
+		switch (params.samplerType)
+		{
+			case SAMPLERTYPE_FETCH_FLOAT:	progSpec = PROGRAM_BUFFER_FLOAT;	break;
+			case SAMPLERTYPE_FETCH_INT:		progSpec = PROGRAM_BUFFER_INT;		break;
+			case SAMPLERTYPE_FETCH_UINT:	progSpec = PROGRAM_BUFFER_UINT;		break;
+			default:						DE_ASSERT(false);
+		}
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	glu::ShaderProgram* program = m_programLibrary.getProgram(progSpec);
+
+	// \todo [2012-09-26 pyry] Move to ProgramLibrary and log unique programs only(?)
+	if (params.flags & RenderParams::LOG_PROGRAMS)
+		log << *program;
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set vertex attributes");
+
+	// Program and uniforms.
+	deUint32 prog = program->getProgram();
+	gl.useProgram(prog);
+
+	gl.uniform1i(gl.getUniformLocation(prog, "u_sampler"), texUnit);
+	if (logUniforms)
+		log << TestLog::Message << "u_sampler = " << texUnit << TestLog::EndMessage;
+
+	if (useBias)
+	{
+		gl.uniform1f(gl.getUniformLocation(prog, "u_bias"), params.bias);
+		if (logUniforms)
+			log << TestLog::Message << "u_bias = " << params.bias << TestLog::EndMessage;
+	}
+
+	if (params.samplerType == SAMPLERTYPE_SHADOW)
+	{
+		gl.uniform1f(gl.getUniformLocation(prog, "u_ref"), params.ref);
+		if (logUniforms)
+			log << TestLog::Message << "u_ref = " << params.ref << TestLog::EndMessage;
+	}
+
+	gl.uniform4fv(gl.getUniformLocation(prog, "u_colorScale"),	1, params.colorScale.getPtr());
+	gl.uniform4fv(gl.getUniformLocation(prog, "u_colorBias"),	1, params.colorBias.getPtr());
+
+	if (logUniforms)
+	{
+		log << TestLog::Message << "u_colorScale = " << params.colorScale << TestLog::EndMessage;
+		log << TestLog::Message << "u_colorBias = " << params.colorBias << TestLog::EndMessage;
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Set program state");
+
+	{
+		const glu::VertexArrayBinding vertexArrays[] =
+		{
+			glu::va::Float("a_position",	4,			4, 0, &position[0]),
+			glu::va::Float("a_texCoord",	numComps,	4, 0, texCoord)
+		};
+		glu::draw(m_renderCtx, prog, DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
+				  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+	}
+}
+
+void computeQuadTexCoord1D (std::vector<float>& dst, float left, float right)
+{
+	dst.resize(4);
+
+	dst[0] = left;
+	dst[1] = left;
+	dst[2] = right;
+	dst[3] = right;
+}
+
+void computeQuadTexCoord1DArray (std::vector<float>& dst, int layerNdx, float left, float right)
+{
+	dst.resize(4*2);
+
+	dst[0] = left;	dst[1] = (float)layerNdx;
+	dst[2] = left;	dst[3] = (float)layerNdx;
+	dst[4] = right;	dst[5] = (float)layerNdx;
+	dst[6] = right;	dst[7] = (float)layerNdx;
+}
+
+void computeQuadTexCoord2D (std::vector<float>& dst, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight)
+{
+	dst.resize(4*2);
+
+	dst[0] = bottomLeft.x();	dst[1] = bottomLeft.y();
+	dst[2] = bottomLeft.x();	dst[3] = topRight.y();
+	dst[4] = topRight.x();		dst[5] = bottomLeft.y();
+	dst[6] = topRight.x();		dst[7] = topRight.y();
+}
+
+void computeQuadTexCoord2DArray (std::vector<float>& dst, int layerNdx, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight)
+{
+	dst.resize(4*3);
+
+	dst[0] = bottomLeft.x();	dst[ 1] = bottomLeft.y();	dst[ 2] = (float)layerNdx;
+	dst[3] = bottomLeft.x();	dst[ 4] = topRight.y();		dst[ 5] = (float)layerNdx;
+	dst[6] = topRight.x();		dst[ 7] = bottomLeft.y();	dst[ 8] = (float)layerNdx;
+	dst[9] = topRight.x();		dst[10] = topRight.y();		dst[11] = (float)layerNdx;
+}
+
+void computeQuadTexCoord3D (std::vector<float>& dst, const tcu::Vec3& p0, const tcu::Vec3& p1, const tcu::IVec3& dirSwz)
+{
+	tcu::Vec3 f0 = tcu::Vec3(0.0f, 0.0f, 0.0f).swizzle(dirSwz[0], dirSwz[1], dirSwz[2]);
+	tcu::Vec3 f1 = tcu::Vec3(0.0f, 1.0f, 0.0f).swizzle(dirSwz[0], dirSwz[1], dirSwz[2]);
+	tcu::Vec3 f2 = tcu::Vec3(1.0f, 0.0f, 0.0f).swizzle(dirSwz[0], dirSwz[1], dirSwz[2]);
+	tcu::Vec3 f3 = tcu::Vec3(1.0f, 1.0f, 0.0f).swizzle(dirSwz[0], dirSwz[1], dirSwz[2]);
+
+	tcu::Vec3 v0 = p0 + (p1-p0)*f0;
+	tcu::Vec3 v1 = p0 + (p1-p0)*f1;
+	tcu::Vec3 v2 = p0 + (p1-p0)*f2;
+	tcu::Vec3 v3 = p0 + (p1-p0)*f3;
+
+	dst.resize(4*3);
+
+	dst[0] = v0.x(); dst[ 1] = v0.y(); dst[ 2] = v0.z();
+	dst[3] = v1.x(); dst[ 4] = v1.y(); dst[ 5] = v1.z();
+	dst[6] = v2.x(); dst[ 7] = v2.y(); dst[ 8] = v2.z();
+	dst[9] = v3.x(); dst[10] = v3.y(); dst[11] = v3.z();
+}
+
+void computeQuadTexCoordCube (std::vector<float>& dst, tcu::CubeFace face)
+{
+	static const float texCoordNegX[] =
+	{
+		-1.0f,  1.0f, -1.0f,
+		-1.0f, -1.0f, -1.0f,
+		-1.0f,  1.0f,  1.0f,
+		-1.0f, -1.0f,  1.0f
+	};
+	static const float texCoordPosX[] =
+	{
+		+1.0f,  1.0f,  1.0f,
+		+1.0f, -1.0f,  1.0f,
+		+1.0f,  1.0f, -1.0f,
+		+1.0f, -1.0f, -1.0f
+	};
+	static const float texCoordNegY[] =
+	{
+		-1.0f, -1.0f,  1.0f,
+		-1.0f, -1.0f, -1.0f,
+		 1.0f, -1.0f,  1.0f,
+		 1.0f, -1.0f, -1.0f
+	};
+	static const float texCoordPosY[] =
+	{
+		-1.0f, +1.0f, -1.0f,
+		-1.0f, +1.0f,  1.0f,
+		 1.0f, +1.0f, -1.0f,
+		 1.0f, +1.0f,  1.0f
+	};
+	static const float texCoordNegZ[] =
+	{
+		 1.0f,  1.0f, -1.0f,
+		 1.0f, -1.0f, -1.0f,
+		-1.0f,  1.0f, -1.0f,
+		-1.0f, -1.0f, -1.0f
+	};
+	static const float texCoordPosZ[] =
+	{
+		-1.0f,  1.0f, +1.0f,
+		-1.0f, -1.0f, +1.0f,
+		 1.0f,  1.0f, +1.0f,
+		 1.0f, -1.0f, +1.0f
+	};
+
+	const float*	texCoord		= DE_NULL;
+	int				texCoordSize	= DE_LENGTH_OF_ARRAY(texCoordNegX);
+
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X: texCoord = texCoordNegX; break;
+		case tcu::CUBEFACE_POSITIVE_X: texCoord = texCoordPosX; break;
+		case tcu::CUBEFACE_NEGATIVE_Y: texCoord = texCoordNegY; break;
+		case tcu::CUBEFACE_POSITIVE_Y: texCoord = texCoordPosY; break;
+		case tcu::CUBEFACE_NEGATIVE_Z: texCoord = texCoordNegZ; break;
+		case tcu::CUBEFACE_POSITIVE_Z: texCoord = texCoordPosZ; break;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return;
+	}
+
+	dst.resize(texCoordSize);
+	std::copy(texCoord, texCoord+texCoordSize, dst.begin());
+}
+
+void computeQuadTexCoordCube (std::vector<float>& dst, tcu::CubeFace face, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight)
+{
+	int		sRow		= 0;
+	int		tRow		= 0;
+	int		mRow		= 0;
+	float	sSign		= 1.0f;
+	float	tSign		= 1.0f;
+	float	mSign		= 1.0f;
+
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X: mRow = 0; sRow = 2; tRow = 1; mSign = -1.0f;				   tSign = -1.0f;	break;
+		case tcu::CUBEFACE_POSITIVE_X: mRow = 0; sRow = 2; tRow = 1;				sSign = -1.0f; tSign = -1.0f;	break;
+		case tcu::CUBEFACE_NEGATIVE_Y: mRow = 1; sRow = 0; tRow = 2; mSign = -1.0f;				   tSign = -1.0f;	break;
+		case tcu::CUBEFACE_POSITIVE_Y: mRow = 1; sRow = 0; tRow = 2;												break;
+		case tcu::CUBEFACE_NEGATIVE_Z: mRow = 2; sRow = 0; tRow = 1; mSign = -1.0f; sSign = -1.0f; tSign = -1.0f;	break;
+		case tcu::CUBEFACE_POSITIVE_Z: mRow = 2; sRow = 0; tRow = 1;							   tSign = -1.0f;	break;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return;
+	}
+
+	dst.resize(3*4);
+
+	dst[0+mRow] = mSign;
+	dst[3+mRow] = mSign;
+	dst[6+mRow] = mSign;
+	dst[9+mRow] = mSign;
+
+	dst[0+sRow] = sSign * bottomLeft.x();
+	dst[3+sRow] = sSign * bottomLeft.x();
+	dst[6+sRow] = sSign * topRight.x();
+	dst[9+sRow] = sSign * topRight.x();
+
+	dst[0+tRow] = tSign * bottomLeft.y();
+	dst[3+tRow] = tSign * topRight.y();
+	dst[6+tRow] = tSign * bottomLeft.y();
+	dst[9+tRow] = tSign * topRight.y();
+}
+
+void computeQuadTexCoordCubeArray (std::vector<float>& dst, int sliceNdx, tcu::CubeFace face, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight)
+{
+	int			sRow	= 0;
+	int			tRow	= 0;
+	int			mRow	= 0;
+	const int	qRow	= 3;
+	float		sSign	= 1.0f;
+	float		tSign	= 1.0f;
+	float		mSign	= 1.0f;
+	const float	q		= (float)sliceNdx;
+
+	switch (face)
+	{
+		case tcu::CUBEFACE_NEGATIVE_X: mRow = 0; sRow = 2; tRow = 1; mSign = -1.0f;				   tSign = -1.0f;	break;
+		case tcu::CUBEFACE_POSITIVE_X: mRow = 0; sRow = 2; tRow = 1;				sSign = -1.0f; tSign = -1.0f;	break;
+		case tcu::CUBEFACE_NEGATIVE_Y: mRow = 1; sRow = 0; tRow = 2; mSign = -1.0f;				   tSign = -1.0f;	break;
+		case tcu::CUBEFACE_POSITIVE_Y: mRow = 1; sRow = 0; tRow = 2;												break;
+		case tcu::CUBEFACE_NEGATIVE_Z: mRow = 2; sRow = 0; tRow = 1; mSign = -1.0f; sSign = -1.0f; tSign = -1.0f;	break;
+		case tcu::CUBEFACE_POSITIVE_Z: mRow = 2; sRow = 0; tRow = 1;							   tSign = -1.0f;	break;
+		default:
+			DE_ASSERT(DE_FALSE);
+			return;
+	}
+
+	dst.resize(4*4);
+
+	dst[ 0+mRow] = mSign;
+	dst[ 4+mRow] = mSign;
+	dst[ 8+mRow] = mSign;
+	dst[12+mRow] = mSign;
+
+	dst[ 0+sRow] = sSign * bottomLeft.x();
+	dst[ 4+sRow] = sSign * bottomLeft.x();
+	dst[ 8+sRow] = sSign * topRight.x();
+	dst[12+sRow] = sSign * topRight.x();
+
+	dst[ 0+tRow] = tSign * bottomLeft.y();
+	dst[ 4+tRow] = tSign * topRight.y();
+	dst[ 8+tRow] = tSign * bottomLeft.y();
+	dst[12+tRow] = tSign * topRight.y();
+
+	dst[ 0+qRow] = q;
+	dst[ 4+qRow] = q;
+	dst[ 8+qRow] = q;
+	dst[12+qRow] = q;
+}
+
+// Texture result verification
+
+//! Verifies texture lookup results and returns number of failed pixels.
+int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
+							  const tcu::ConstPixelBufferAccess&	reference,
+							  const tcu::PixelBufferAccess&			errorMask,
+							  const tcu::Texture1DView&				baseView,
+							  const float*							texCoord,
+							  const ReferenceParams&				sampleParams,
+							  const tcu::LookupPrecision&			lookupPrec,
+							  const tcu::LodPrecision&				lodPrec,
+							  qpWatchDog*							watchDog)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());
+
+	const tcu::Texture1DView	src		= getSubView(baseView, sampleParams.baseLevel, sampleParams.maxLevel);
+
+	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0], texCoord[1], texCoord[2], texCoord[3]);
+
+	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
+	const float			dstW			= float(dstSize.x());
+	const float			dstH			= float(dstSize.y());
+	const int			srcSize			= src.getWidth();
+
+	// Coordinates and lod per triangle.
+	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };
+
+	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);
+
+	int					numFailed		= 0;
+
+	const tcu::Vec2 lodOffsets[] =
+	{
+		tcu::Vec2(-1,  0),
+		tcu::Vec2(+1,  0),
+		tcu::Vec2( 0, -1),
+		tcu::Vec2( 0, +1),
+	};
+
+	tcu::clear(errorMask, tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < result.getHeight(); py++)
+	{
+		// Ugly hack, validation can take way too long at the moment.
+		if (watchDog)
+			qpWatchDog_touch(watchDog);
+
+		for (int px = 0; px < result.getWidth(); px++)
+		{
+			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
+			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;
+
+			// Try comparison to ideal reference first, and if that fails use slower verificator.
+			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
+			{
+				const float		wx		= (float)px + 0.5f;
+				const float		wy		= (float)py + 0.5f;
+				const float		nx		= wx / dstW;
+				const float		ny		= wy / dstH;
+
+				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+				const float		triWx	= triNdx ? dstW - wx : wx;
+				const float		triWy	= triNdx ? dstH - wy : wy;
+				const float		triNx	= triNdx ? 1.0f - nx : nx;
+				const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+				const float		coord		= projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy);
+				const float		coordDx		= triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy) * float(srcSize);
+				const float 	coordDy		= triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx) * float(srcSize);
+
+				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx, coordDy, lodPrec);
+
+				// Compute lod bounds across lodOffsets range.
+				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
+				{
+					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
+					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
+					const float		nxo		= wxo/dstW;
+					const float		nyo		= wyo/dstH;
+
+					const float	coordDxo	= triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo) * float(srcSize);
+					const float	coordDyo	= triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo) * float(srcSize);
+					const tcu::Vec2	lodO	= tcu::computeLodBoundsFromDerivates(coordDxo, coordDyo, lodPrec);
+
+					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
+					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
+				}
+
+				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
+				const bool		isOk		= tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix);
+
+				if (!isOk)
+				{
+					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+					numFailed += 1;
+				}
+			}
+		}
+	}
+
+	return numFailed;
+}
+
+int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
+							  const tcu::ConstPixelBufferAccess&	reference,
+							  const tcu::PixelBufferAccess&			errorMask,
+							  const tcu::Texture2DView&				baseView,
+							  const float*							texCoord,
+							  const ReferenceParams&					sampleParams,
+							  const tcu::LookupPrecision&			lookupPrec,
+							  const tcu::LodPrecision&				lodPrec,
+							  qpWatchDog*							watchDog)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());
+
+	const tcu::Texture2DView	src		= getSubView(baseView, sampleParams.baseLevel, sampleParams.maxLevel);
+
+	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[2+0], texCoord[4+0], texCoord[6+0]);
+	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[2+1], texCoord[4+1], texCoord[6+1]);
+
+	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
+	const float			dstW			= float(dstSize.x());
+	const float			dstH			= float(dstSize.y());
+	const tcu::IVec2	srcSize			= tcu::IVec2(src.getWidth(), src.getHeight());
+
+	// Coordinates and lod per triangle.
+	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };
+
+	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);
+
+	int					numFailed		= 0;
+
+	const tcu::Vec2 lodOffsets[] =
+	{
+		tcu::Vec2(-1,  0),
+		tcu::Vec2(+1,  0),
+		tcu::Vec2( 0, -1),
+		tcu::Vec2( 0, +1),
+	};
+
+	tcu::clear(errorMask, tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < result.getHeight(); py++)
+	{
+		// Ugly hack, validation can take way too long at the moment.
+		if (watchDog)
+			qpWatchDog_touch(watchDog);
+
+		for (int px = 0; px < result.getWidth(); px++)
+		{
+			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
+			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;
+
+			// Try comparison to ideal reference first, and if that fails use slower verificator.
+			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
+			{
+				const float		wx		= (float)px + 0.5f;
+				const float		wy		= (float)py + 0.5f;
+				const float		nx		= wx / dstW;
+				const float		ny		= wy / dstH;
+
+				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+				const float		triWx	= triNdx ? dstW - wx : wx;
+				const float		triWy	= triNdx ? dstH - wy : wy;
+				const float		triNx	= triNdx ? 1.0f - nx : nx;
+				const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+				const tcu::Vec2	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
+											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy));
+				const tcu::Vec2	coordDx		= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
+														triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy)) * srcSize.asFloat();
+				const tcu::Vec2	coordDy		= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
+														triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx)) * srcSize.asFloat();
+
+				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDy.x(), coordDy.y(), lodPrec);
+
+				// Compute lod bounds across lodOffsets range.
+				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
+				{
+					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
+					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
+					const float		nxo		= wxo/dstW;
+					const float		nyo		= wyo/dstH;
+
+					const tcu::Vec2	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
+												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo));
+					const tcu::Vec2	coordDxo	= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
+															triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo)) * srcSize.asFloat();
+					const tcu::Vec2	coordDyo	= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
+															triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo)) * srcSize.asFloat();
+					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo.x(), coordDxo.y(), coordDyo.x(), coordDyo.y(), lodPrec);
+
+					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
+					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
+				}
+
+				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
+				const bool		isOk		= tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix);
+
+				if (!isOk)
+				{
+					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+					numFailed += 1;
+				}
+			}
+		}
+	}
+
+	return numFailed;
+}
+
+bool verifyTextureResult (tcu::TestContext&						testCtx,
+						  const tcu::ConstPixelBufferAccess&	result,
+						  const tcu::Texture1DView&				src,
+						  const float*							texCoord,
+						  const ReferenceParams&				sampleParams,
+						  const tcu::LookupPrecision&			lookupPrec,
+						  const tcu::LodPrecision&				lodPrec,
+						  const tcu::PixelFormat&				pixelFormat)
+{
+	tcu::TestLog&	log				= testCtx.getLog();
+	tcu::Surface	reference		(result.getWidth(), result.getHeight());
+	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
+	int				numFailedPixels;
+
+	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);
+
+	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
+	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());
+
+	if (numFailedPixels > 0)
+		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+	log << TestLog::ImageSet("VerifyResult", "Verification result")
+		<< TestLog::Image("Rendered", "Rendered image", result);
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Image("Reference", "Ideal reference image", reference)
+			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+	}
+
+	log << TestLog::EndImageSet;
+
+	return numFailedPixels == 0;
+}
+
+bool verifyTextureResult (tcu::TestContext&						testCtx,
+						  const tcu::ConstPixelBufferAccess&	result,
+						  const tcu::Texture2DView&				src,
+						  const float*							texCoord,
+						  const ReferenceParams&				sampleParams,
+						  const tcu::LookupPrecision&			lookupPrec,
+						  const tcu::LodPrecision&				lodPrec,
+						  const tcu::PixelFormat&				pixelFormat)
+{
+	tcu::TestLog&	log				= testCtx.getLog();
+	tcu::Surface	reference		(result.getWidth(), result.getHeight());
+	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
+	int				numFailedPixels;
+
+	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);
+
+	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
+	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());
+
+	if (numFailedPixels > 0)
+		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+	log << TestLog::ImageSet("VerifyResult", "Verification result")
+		<< TestLog::Image("Rendered", "Rendered image", result);
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Image("Reference", "Ideal reference image", reference)
+			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+	}
+
+	log << TestLog::EndImageSet;
+
+	return numFailedPixels == 0;
+}
+
+//! Verifies texture lookup results and returns number of failed pixels.
+int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
+							  const tcu::ConstPixelBufferAccess&	reference,
+							  const tcu::PixelBufferAccess&			errorMask,
+							  const tcu::TextureCubeView&			baseView,
+							  const float*							texCoord,
+							  const ReferenceParams&				sampleParams,
+							  const tcu::LookupPrecision&			lookupPrec,
+							  const tcu::LodPrecision&				lodPrec,
+							  qpWatchDog*							watchDog)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());
+
+	const tcu::TextureCubeView	src		= getSubView(baseView, sampleParams.baseLevel, sampleParams.maxLevel);
+
+	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
+	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
+	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);
+
+	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
+	const float			dstW			= float(dstSize.x());
+	const float			dstH			= float(dstSize.y());
+	const int			srcSize			= src.getSize();
+
+	// Coordinates per triangle.
+	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };
+
+	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);
+
+	const float			posEps			= 1.0f / float((1<<4) + 1); // ES3 requires at least 4 subpixel bits.
+
+	int					numFailed		= 0;
+
+	const tcu::Vec2 lodOffsets[] =
+	{
+		tcu::Vec2(-1,  0),
+		tcu::Vec2(+1,  0),
+		tcu::Vec2( 0, -1),
+		tcu::Vec2( 0, +1),
+
+		// \note Not strictly allowed by spec, but implementations do this in practice.
+		tcu::Vec2(-1, -1),
+		tcu::Vec2(-1, +1),
+		tcu::Vec2(+1, -1),
+		tcu::Vec2(+1, -1),
+	};
+
+	tcu::clear(errorMask, tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < result.getHeight(); py++)
+	{
+		// Ugly hack, validation can take way too long at the moment.
+		if (watchDog)
+			qpWatchDog_touch(watchDog);
+
+		for (int px = 0; px < result.getWidth(); px++)
+		{
+			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
+			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;
+
+			// Try comparison to ideal reference first, and if that fails use slower verificator.
+			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
+			{
+				const float		wx		= (float)px + 0.5f;
+				const float		wy		= (float)py + 0.5f;
+				const float		nx		= wx / dstW;
+				const float		ny		= wy / dstH;
+
+				const bool		tri0	= nx + ny - posEps <= 1.0f;
+				const bool		tri1	= nx + ny + posEps >= 1.0f;
+
+				bool			isOk	= false;
+
+				DE_ASSERT(tri0 || tri1);
+
+				// Pixel can belong to either of the triangles if it lies close enough to the edge.
+				for (int triNdx = (tri0?0:1); triNdx <= (tri1?1:0); triNdx++)
+				{
+					const float		triWx	= triNdx ? dstW - wx : wx;
+					const float		triWy	= triNdx ? dstH - wy : wy;
+					const float		triNx	= triNdx ? 1.0f - nx : nx;
+					const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+					const tcu::Vec3	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
+												 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
+												 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy));
+					const tcu::Vec3	coordDx		(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
+												 triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
+												 triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy));
+					const tcu::Vec3	coordDy		(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
+												 triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
+												 triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx));
+
+					tcu::Vec2		lodBounds	= tcu::computeCubeLodBoundsFromDerivates(coord, coordDx, coordDy, srcSize, lodPrec);
+
+					// Compute lod bounds across lodOffsets range.
+					for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
+					{
+						const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
+						const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
+						const float		nxo		= wxo/dstW;
+						const float		nyo		= wyo/dstH;
+
+						const tcu::Vec3	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
+													 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo),
+													 projectedTriInterpolate(triR[triNdx], triW[triNdx], nxo, nyo));
+						const tcu::Vec3	coordDxo	(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
+													 triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo),
+													 triDerivateX(triR[triNdx], triW[triNdx], wxo, dstW, nyo));
+						const tcu::Vec3	coordDyo	(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
+													 triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo),
+													 triDerivateY(triR[triNdx], triW[triNdx], wyo, dstH, nxo));
+						const tcu::Vec2	lodO		= tcu::computeCubeLodBoundsFromDerivates(coordO, coordDxo, coordDyo, srcSize, lodPrec);
+
+						lodBounds.x() = de::min(lodBounds.x(), lodO.x());
+						lodBounds.y() = de::max(lodBounds.y(), lodO.y());
+					}
+
+					const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
+
+					if (tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix))
+					{
+						isOk = true;
+						break;
+					}
+				}
+
+				if (!isOk)
+				{
+					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+					numFailed += 1;
+				}
+			}
+		}
+	}
+
+	return numFailed;
+}
+
+bool verifyTextureResult (tcu::TestContext&						testCtx,
+						  const tcu::ConstPixelBufferAccess&	result,
+						  const tcu::TextureCubeView&			src,
+						  const float*							texCoord,
+						  const ReferenceParams&				sampleParams,
+						  const tcu::LookupPrecision&			lookupPrec,
+						  const tcu::LodPrecision&				lodPrec,
+						  const tcu::PixelFormat&				pixelFormat)
+{
+	tcu::TestLog&	log				= testCtx.getLog();
+	tcu::Surface	reference		(result.getWidth(), result.getHeight());
+	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
+	int				numFailedPixels;
+
+	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);
+
+	sampleTextureMultiFace(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
+	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());
+
+	if (numFailedPixels > 0)
+		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+	log << TestLog::ImageSet("VerifyResult", "Verification result")
+		<< TestLog::Image("Rendered", "Rendered image", result);
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Image("Reference", "Ideal reference image", reference)
+			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+	}
+
+	log << TestLog::EndImageSet;
+
+	return numFailedPixels == 0;
+}
+
+//! Verifies texture lookup results and returns number of failed pixels.
+int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
+							  const tcu::ConstPixelBufferAccess&	reference,
+							  const tcu::PixelBufferAccess&			errorMask,
+							  const tcu::Texture3DView&				baseView,
+							  const float*							texCoord,
+							  const ReferenceParams&				sampleParams,
+							  const tcu::LookupPrecision&			lookupPrec,
+							  const tcu::LodPrecision&				lodPrec,
+							  qpWatchDog*							watchDog)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());
+
+	const tcu::Texture3DView	src		= getSubView(baseView, sampleParams.baseLevel, sampleParams.maxLevel);
+
+	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
+	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
+	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);
+
+	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
+	const float			dstW			= float(dstSize.x());
+	const float			dstH			= float(dstSize.y());
+	const tcu::IVec3	srcSize			= tcu::IVec3(src.getWidth(), src.getHeight(), src.getDepth());
+
+	// Coordinates and lod per triangle.
+	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };
+
+	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);
+
+	int					numFailed		= 0;
+
+	const tcu::Vec2 lodOffsets[] =
+	{
+		tcu::Vec2(-1,  0),
+		tcu::Vec2(+1,  0),
+		tcu::Vec2( 0, -1),
+		tcu::Vec2( 0, +1),
+	};
+
+	tcu::clear(errorMask, tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < result.getHeight(); py++)
+	{
+		// Ugly hack, validation can take way too long at the moment.
+		if (watchDog)
+			qpWatchDog_touch(watchDog);
+
+		for (int px = 0; px < result.getWidth(); px++)
+		{
+			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
+			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;
+
+			// Try comparison to ideal reference first, and if that fails use slower verificator.
+			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
+			{
+				const float		wx		= (float)px + 0.5f;
+				const float		wy		= (float)py + 0.5f;
+				const float		nx		= wx / dstW;
+				const float		ny		= wy / dstH;
+
+				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+				const float		triWx	= triNdx ? dstW - wx : wx;
+				const float		triWy	= triNdx ? dstH - wy : wy;
+				const float		triNx	= triNdx ? 1.0f - nx : nx;
+				const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+				const tcu::Vec3	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
+											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
+											 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy));
+				const tcu::Vec3	coordDx		= tcu::Vec3(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
+														triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
+														triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy)) * srcSize.asFloat();
+				const tcu::Vec3	coordDy		= tcu::Vec3(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
+														triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
+														triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx)) * srcSize.asFloat();
+
+				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDx.z(), coordDy.x(), coordDy.y(), coordDy.z(), lodPrec);
+
+				// Compute lod bounds across lodOffsets range.
+				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
+				{
+					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
+					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
+					const float		nxo		= wxo/dstW;
+					const float		nyo		= wyo/dstH;
+
+					const tcu::Vec3	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
+												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo),
+												 projectedTriInterpolate(triR[triNdx], triW[triNdx], nxo, nyo));
+					const tcu::Vec3	coordDxo	= tcu::Vec3(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
+															triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo),
+															triDerivateX(triR[triNdx], triW[triNdx], wxo, dstW, nyo)) * srcSize.asFloat();
+					const tcu::Vec3	coordDyo	= tcu::Vec3(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
+															triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo),
+															triDerivateY(triR[triNdx], triW[triNdx], wyo, dstH, nxo)) * srcSize.asFloat();
+					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo.x(), coordDxo.y(), coordDxo.z(), coordDyo.x(), coordDyo.y(), coordDyo.z(), lodPrec);
+
+					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
+					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
+				}
+
+				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
+				const bool		isOk		= tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix);
+
+				if (!isOk)
+				{
+					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+					numFailed += 1;
+				}
+			}
+		}
+	}
+
+	return numFailed;
+}
+
+bool verifyTextureResult (tcu::TestContext&						testCtx,
+						  const tcu::ConstPixelBufferAccess&	result,
+						  const tcu::Texture3DView&				src,
+						  const float*							texCoord,
+						  const ReferenceParams&				sampleParams,
+						  const tcu::LookupPrecision&			lookupPrec,
+						  const tcu::LodPrecision&				lodPrec,
+						  const tcu::PixelFormat&				pixelFormat)
+{
+	tcu::TestLog&	log				= testCtx.getLog();
+	tcu::Surface	reference		(result.getWidth(), result.getHeight());
+	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
+	int				numFailedPixels;
+
+	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);
+
+	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
+	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());
+
+	if (numFailedPixels > 0)
+		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+	log << TestLog::ImageSet("VerifyResult", "Verification result")
+		<< TestLog::Image("Rendered", "Rendered image", result);
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Image("Reference", "Ideal reference image", reference)
+			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+	}
+
+	log << TestLog::EndImageSet;
+
+	return numFailedPixels == 0;
+}
+
+//! Verifies texture lookup results and returns number of failed pixels.
+int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
+							  const tcu::ConstPixelBufferAccess&	reference,
+							  const tcu::PixelBufferAccess&			errorMask,
+							  const tcu::Texture1DArrayView&		src,
+							  const float*							texCoord,
+							  const ReferenceParams&				sampleParams,
+							  const tcu::LookupPrecision&			lookupPrec,
+							  const tcu::LodPrecision&				lodPrec,
+							  qpWatchDog*							watchDog)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());
+
+	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[2+0], texCoord[4+0], texCoord[6+0]);
+	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[2+1], texCoord[4+1], texCoord[6+1]);
+
+	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
+	const float			dstW			= float(dstSize.x());
+	const float			dstH			= float(dstSize.y());
+	const float			srcSize			= float(src.getWidth()); // For lod computation, thus #layers is ignored.
+
+	// Coordinates and lod per triangle.
+	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };
+
+	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);
+
+	int					numFailed		= 0;
+
+	const tcu::Vec2 lodOffsets[] =
+	{
+		tcu::Vec2(-1,  0),
+		tcu::Vec2(+1,  0),
+		tcu::Vec2( 0, -1),
+		tcu::Vec2( 0, +1),
+	};
+
+	tcu::clear(errorMask, tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < result.getHeight(); py++)
+	{
+		// Ugly hack, validation can take way too long at the moment.
+		if (watchDog)
+			qpWatchDog_touch(watchDog);
+
+		for (int px = 0; px < result.getWidth(); px++)
+		{
+			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
+			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;
+
+			// Try comparison to ideal reference first, and if that fails use slower verificator.
+			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
+			{
+				const float		wx		= (float)px + 0.5f;
+				const float		wy		= (float)py + 0.5f;
+				const float		nx		= wx / dstW;
+				const float		ny		= wy / dstH;
+
+				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+				const float		triWx	= triNdx ? dstW - wx : wx;
+				const float		triWy	= triNdx ? dstH - wy : wy;
+				const float		triNx	= triNdx ? 1.0f - nx : nx;
+				const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+				const tcu::Vec2	coord	(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
+										 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy));
+				const float	coordDx		= triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy) * srcSize;
+				const float	coordDy		= triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx) * srcSize;
+
+				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx, coordDy, lodPrec);
+
+				// Compute lod bounds across lodOffsets range.
+				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
+				{
+					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
+					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
+					const float		nxo		= wxo/dstW;
+					const float		nyo		= wyo/dstH;
+
+					const tcu::Vec2	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
+												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo));
+					const float	coordDxo		= triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo) * srcSize;
+					const float	coordDyo		= triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo) * srcSize;
+					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo, coordDyo, lodPrec);
+
+					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
+					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
+				}
+
+				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
+				const bool		isOk		= tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix);
+
+				if (!isOk)
+				{
+					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+					numFailed += 1;
+				}
+			}
+		}
+	}
+
+	return numFailed;
+}
+
+//! Verifies texture lookup results and returns number of failed pixels.
+int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
+							  const tcu::ConstPixelBufferAccess&	reference,
+							  const tcu::PixelBufferAccess&			errorMask,
+							  const tcu::Texture2DArrayView&		src,
+							  const float*							texCoord,
+							  const ReferenceParams&				sampleParams,
+							  const tcu::LookupPrecision&			lookupPrec,
+							  const tcu::LodPrecision&				lodPrec,
+							  qpWatchDog*							watchDog)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());
+
+	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
+	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
+	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);
+
+	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
+	const float			dstW			= float(dstSize.x());
+	const float			dstH			= float(dstSize.y());
+	const tcu::Vec2		srcSize			= tcu::IVec2(src.getWidth(), src.getHeight()).asFloat(); // For lod computation, thus #layers is ignored.
+
+	// Coordinates and lod per triangle.
+	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };
+
+	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);
+
+	int					numFailed		= 0;
+
+	const tcu::Vec2 lodOffsets[] =
+	{
+		tcu::Vec2(-1,  0),
+		tcu::Vec2(+1,  0),
+		tcu::Vec2( 0, -1),
+		tcu::Vec2( 0, +1),
+	};
+
+	tcu::clear(errorMask, tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < result.getHeight(); py++)
+	{
+		// Ugly hack, validation can take way too long at the moment.
+		if (watchDog)
+			qpWatchDog_touch(watchDog);
+
+		for (int px = 0; px < result.getWidth(); px++)
+		{
+			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
+			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;
+
+			// Try comparison to ideal reference first, and if that fails use slower verificator.
+			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
+			{
+				const float		wx		= (float)px + 0.5f;
+				const float		wy		= (float)py + 0.5f;
+				const float		nx		= wx / dstW;
+				const float		ny		= wy / dstH;
+
+				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+				const float		triWx	= triNdx ? dstW - wx : wx;
+				const float		triWy	= triNdx ? dstH - wy : wy;
+				const float		triNx	= triNdx ? 1.0f - nx : nx;
+				const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+				const tcu::Vec3	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
+											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
+											 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy));
+				const tcu::Vec2	coordDx		= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
+														triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy)) * srcSize;
+				const tcu::Vec2	coordDy		= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
+														triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx)) * srcSize;
+
+				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDy.x(), coordDy.y(), lodPrec);
+
+				// Compute lod bounds across lodOffsets range.
+				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
+				{
+					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
+					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
+					const float		nxo		= wxo/dstW;
+					const float		nyo		= wyo/dstH;
+
+					const tcu::Vec3	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
+												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo),
+												 projectedTriInterpolate(triR[triNdx], triW[triNdx], nxo, nyo));
+					const tcu::Vec2	coordDxo	= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
+															triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo)) * srcSize;
+					const tcu::Vec2	coordDyo	= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
+															triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo)) * srcSize;
+					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo.x(), coordDxo.y(), coordDyo.x(), coordDyo.y(), lodPrec);
+
+					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
+					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
+				}
+
+				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
+				const bool		isOk		= tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix);
+
+				if (!isOk)
+				{
+					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+					numFailed += 1;
+				}
+			}
+		}
+	}
+
+	return numFailed;
+}
+
+bool verifyTextureResult (tcu::TestContext&						testCtx,
+						  const tcu::ConstPixelBufferAccess&	result,
+						  const tcu::Texture1DArrayView&		src,
+						  const float*							texCoord,
+						  const ReferenceParams&				sampleParams,
+						  const tcu::LookupPrecision&			lookupPrec,
+						  const tcu::LodPrecision&				lodPrec,
+						  const tcu::PixelFormat&				pixelFormat)
+{
+	tcu::TestLog&	log				= testCtx.getLog();
+	tcu::Surface	reference		(result.getWidth(), result.getHeight());
+	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
+	int				numFailedPixels;
+
+	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);
+
+	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
+	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());
+
+	if (numFailedPixels > 0)
+		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+	log << TestLog::ImageSet("VerifyResult", "Verification result")
+		<< TestLog::Image("Rendered", "Rendered image", result);
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Image("Reference", "Ideal reference image", reference)
+			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+	}
+
+	log << TestLog::EndImageSet;
+
+	return numFailedPixels == 0;
+}
+
+bool verifyTextureResult (tcu::TestContext&						testCtx,
+						  const tcu::ConstPixelBufferAccess&	result,
+						  const tcu::Texture2DArrayView&		src,
+						  const float*							texCoord,
+						  const ReferenceParams&				sampleParams,
+						  const tcu::LookupPrecision&			lookupPrec,
+						  const tcu::LodPrecision&				lodPrec,
+						  const tcu::PixelFormat&				pixelFormat)
+{
+	tcu::TestLog&	log				= testCtx.getLog();
+	tcu::Surface	reference		(result.getWidth(), result.getHeight());
+	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
+	int				numFailedPixels;
+
+	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);
+
+	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
+	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());
+
+	if (numFailedPixels > 0)
+		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;
+
+	log << TestLog::ImageSet("VerifyResult", "Verification result")
+		<< TestLog::Image("Rendered", "Rendered image", result);
+
+	if (numFailedPixels > 0)
+	{
+		log << TestLog::Image("Reference", "Ideal reference image", reference)
+			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
+	}
+
+	log << TestLog::EndImageSet;
+
+	return numFailedPixels == 0;
+}
+
+// Shadow lookup verification
+
+int computeTextureCompareDiff (const tcu::ConstPixelBufferAccess&	result,
+							   const tcu::ConstPixelBufferAccess&	reference,
+							   const tcu::PixelBufferAccess&		errorMask,
+							   const tcu::Texture2DView&			src,
+							   const float*							texCoord,
+							   const ReferenceParams&				sampleParams,
+							   const tcu::TexComparePrecision&		comparePrec,
+							   const tcu::LodPrecision&				lodPrec,
+							   const tcu::Vec3&						nonShadowThreshold)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());
+
+	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[2+0], texCoord[4+0], texCoord[6+0]);
+	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[2+1], texCoord[4+1], texCoord[6+1]);
+
+	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
+	const float			dstW			= float(dstSize.x());
+	const float			dstH			= float(dstSize.y());
+	const tcu::IVec2	srcSize			= tcu::IVec2(src.getWidth(), src.getHeight());
+
+	// Coordinates and lod per triangle.
+	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };
+
+	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);
+
+	int					numFailed		= 0;
+
+	const tcu::Vec2 lodOffsets[] =
+	{
+		tcu::Vec2(-1,  0),
+		tcu::Vec2(+1,  0),
+		tcu::Vec2( 0, -1),
+		tcu::Vec2( 0, +1),
+	};
+
+	tcu::clear(errorMask, tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < result.getHeight(); py++)
+	{
+		for (int px = 0; px < result.getWidth(); px++)
+		{
+			const tcu::Vec4	resPix	= result.getPixel(px, py);
+			const tcu::Vec4	refPix	= reference.getPixel(px, py);
+
+			// Other channels should trivially match to reference.
+			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(refPix.swizzle(1,2,3) - resPix.swizzle(1,2,3)), nonShadowThreshold)))
+			{
+				errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+				numFailed += 1;
+				continue;
+			}
+
+			{
+				const float		wx		= (float)px + 0.5f;
+				const float		wy		= (float)py + 0.5f;
+				const float		nx		= wx / dstW;
+				const float		ny		= wy / dstH;
+
+				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+				const float		triWx	= triNdx ? dstW - wx : wx;
+				const float		triWy	= triNdx ? dstH - wy : wy;
+				const float		triNx	= triNdx ? 1.0f - nx : nx;
+				const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+				const tcu::Vec2	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
+											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy));
+				const tcu::Vec2	coordDx		= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
+														triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy)) * srcSize.asFloat();
+				const tcu::Vec2	coordDy		= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
+														triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx)) * srcSize.asFloat();
+
+				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDy.x(), coordDy.y(), lodPrec);
+
+				// Compute lod bounds across lodOffsets range.
+				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
+				{
+					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
+					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
+					const float		nxo		= wxo/dstW;
+					const float		nyo		= wyo/dstH;
+
+					const tcu::Vec2	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
+												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo));
+					const tcu::Vec2	coordDxo	= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
+															triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo)) * srcSize.asFloat();
+					const tcu::Vec2	coordDyo	= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
+															triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo)) * srcSize.asFloat();
+					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo.x(), coordDxo.y(), coordDyo.x(), coordDyo.y(), lodPrec);
+
+					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
+					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
+				}
+
+				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
+				const bool		isOk		= tcu::isTexCompareResultValid(src, sampleParams.sampler, comparePrec, coord, clampedLod, sampleParams.ref, resPix.x());
+
+				if (!isOk)
+				{
+					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+					numFailed += 1;
+				}
+			}
+		}
+	}
+
+	return numFailed;
+}
+
+int computeTextureCompareDiff (const tcu::ConstPixelBufferAccess&	result,
+							   const tcu::ConstPixelBufferAccess&	reference,
+							   const tcu::PixelBufferAccess&		errorMask,
+							   const tcu::TextureCubeView&			src,
+							   const float*							texCoord,
+							   const ReferenceParams&				sampleParams,
+							   const tcu::TexComparePrecision&		comparePrec,
+							   const tcu::LodPrecision&				lodPrec,
+							   const tcu::Vec3&						nonShadowThreshold)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());
+
+	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
+	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
+	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);
+
+	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
+	const float			dstW			= float(dstSize.x());
+	const float			dstH			= float(dstSize.y());
+	const int			srcSize			= src.getSize();
+
+	// Coordinates per triangle.
+	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };
+
+	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);
+
+	int					numFailed		= 0;
+
+	const tcu::Vec2 lodOffsets[] =
+	{
+		tcu::Vec2(-1,  0),
+		tcu::Vec2(+1,  0),
+		tcu::Vec2( 0, -1),
+		tcu::Vec2( 0, +1),
+	};
+
+	tcu::clear(errorMask, tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < result.getHeight(); py++)
+	{
+		for (int px = 0; px < result.getWidth(); px++)
+		{
+			const tcu::Vec4	resPix	= result.getPixel(px, py);
+			const tcu::Vec4	refPix	= reference.getPixel(px, py);
+
+			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(refPix.swizzle(1,2,3) - resPix.swizzle(1,2,3)), nonShadowThreshold)))
+			{
+				errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+				numFailed += 1;
+				continue;
+			}
+
+			{
+				const float		wx		= (float)px + 0.5f;
+				const float		wy		= (float)py + 0.5f;
+				const float		nx		= wx / dstW;
+				const float		ny		= wy / dstH;
+
+				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+				const float		triWx	= triNdx ? dstW - wx : wx;
+				const float		triWy	= triNdx ? dstH - wy : wy;
+				const float		triNx	= triNdx ? 1.0f - nx : nx;
+				const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+				const tcu::Vec3	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
+											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
+											 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy));
+				const tcu::Vec3	coordDx		(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
+											 triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
+											 triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy));
+				const tcu::Vec3	coordDy		(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
+											 triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
+											 triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx));
+
+				tcu::Vec2		lodBounds	= tcu::computeCubeLodBoundsFromDerivates(coord, coordDx, coordDy, srcSize, lodPrec);
+
+				// Compute lod bounds across lodOffsets range.
+				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
+				{
+					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
+					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
+					const float		nxo		= wxo/dstW;
+					const float		nyo		= wyo/dstH;
+
+					const tcu::Vec3	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
+												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo),
+												 projectedTriInterpolate(triR[triNdx], triW[triNdx], nxo, nyo));
+					const tcu::Vec3	coordDxo	(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
+												 triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo),
+												 triDerivateX(triR[triNdx], triW[triNdx], wxo, dstW, nyo));
+					const tcu::Vec3	coordDyo	(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
+												 triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo),
+												 triDerivateY(triR[triNdx], triW[triNdx], wyo, dstH, nxo));
+					const tcu::Vec2	lodO		= tcu::computeCubeLodBoundsFromDerivates(coordO, coordDxo, coordDyo, srcSize, lodPrec);
+
+					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
+					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
+				}
+
+				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
+				const bool		isOk		= tcu::isTexCompareResultValid(src, sampleParams.sampler, comparePrec, coord, clampedLod, sampleParams.ref, resPix.x());
+
+				if (!isOk)
+				{
+					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+					numFailed += 1;
+				}
+			}
+		}
+	}
+
+	return numFailed;
+}
+
+int computeTextureCompareDiff (const tcu::ConstPixelBufferAccess&	result,
+							   const tcu::ConstPixelBufferAccess&	reference,
+							   const tcu::PixelBufferAccess&		errorMask,
+							   const tcu::Texture2DArrayView&		src,
+							   const float*							texCoord,
+							   const ReferenceParams&				sampleParams,
+							   const tcu::TexComparePrecision&		comparePrec,
+							   const tcu::LodPrecision&				lodPrec,
+							   const tcu::Vec3&						nonShadowThreshold)
+{
+	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
+	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());
+
+	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
+	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
+	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);
+
+	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
+	const float			dstW			= float(dstSize.x());
+	const float			dstH			= float(dstSize.y());
+	const tcu::IVec2	srcSize			= tcu::IVec2(src.getWidth(), src.getHeight());
+
+	// Coordinates and lod per triangle.
+	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
+	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };
+
+	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);
+
+	int					numFailed		= 0;
+
+	const tcu::Vec2 lodOffsets[] =
+	{
+		tcu::Vec2(-1,  0),
+		tcu::Vec2(+1,  0),
+		tcu::Vec2( 0, -1),
+		tcu::Vec2( 0, +1),
+	};
+
+	tcu::clear(errorMask, tcu::RGBA::green.toVec());
+
+	for (int py = 0; py < result.getHeight(); py++)
+	{
+		for (int px = 0; px < result.getWidth(); px++)
+		{
+			const tcu::Vec4	resPix	= result.getPixel(px, py);
+			const tcu::Vec4	refPix	= reference.getPixel(px, py);
+
+			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(refPix.swizzle(1,2,3) - resPix.swizzle(1,2,3)), nonShadowThreshold)))
+			{
+				errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+				numFailed += 1;
+				continue;
+			}
+
+			{
+				const float		wx		= (float)px + 0.5f;
+				const float		wy		= (float)py + 0.5f;
+				const float		nx		= wx / dstW;
+				const float		ny		= wy / dstH;
+
+				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
+				const float		triWx	= triNdx ? dstW - wx : wx;
+				const float		triWy	= triNdx ? dstH - wy : wy;
+				const float		triNx	= triNdx ? 1.0f - nx : nx;
+				const float		triNy	= triNdx ? 1.0f - ny : ny;
+
+				const tcu::Vec3	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
+											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
+											 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy));
+				const tcu::Vec2	coordDx		= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
+														triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy)) * srcSize.asFloat();
+				const tcu::Vec2	coordDy		= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
+														triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx)) * srcSize.asFloat();
+
+				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDy.x(), coordDy.y(), lodPrec);
+
+				// Compute lod bounds across lodOffsets range.
+				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
+				{
+					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
+					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
+					const float		nxo		= wxo/dstW;
+					const float		nyo		= wyo/dstH;
+
+					const tcu::Vec2	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
+												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo));
+					const tcu::Vec2	coordDxo	= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
+															triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo)) * srcSize.asFloat();
+					const tcu::Vec2	coordDyo	= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
+															triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo)) * srcSize.asFloat();
+					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo.x(), coordDxo.y(), coordDyo.x(), coordDyo.y(), lodPrec);
+
+					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
+					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
+				}
+
+				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
+				const bool		isOk		= tcu::isTexCompareResultValid(src, sampleParams.sampler, comparePrec, coord, clampedLod, sampleParams.ref, resPix.x());
+
+				if (!isOk)
+				{
+					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
+					numFailed += 1;
+				}
+			}
+		}
+	}
+
+	return numFailed;
+}
+
+// Mipmap generation comparison.
+
+static int compareGenMipmapBilinear (const tcu::ConstPixelBufferAccess& dst, const tcu::ConstPixelBufferAccess& src, const tcu::PixelBufferAccess& errorMask, const GenMipmapPrecision& precision)
+{
+	DE_ASSERT(dst.getDepth() == 1 && src.getDepth() == 1); // \todo [2013-10-29 pyry] 3D textures.
+
+	const float		dstW		= float(dst.getWidth());
+	const float		dstH		= float(dst.getHeight());
+	const float		srcW		= float(src.getWidth());
+	const float		srcH		= float(src.getHeight());
+	int				numFailed	= 0;
+
+	// Translation to lookup verification parameters.
+	const tcu::Sampler		sampler		(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
+										 tcu::Sampler::LINEAR, tcu::Sampler::LINEAR, 0.0f, false /* non-normalized coords */);
+	tcu::LookupPrecision	lookupPrec;
+
+	lookupPrec.colorThreshold	= precision.colorThreshold;
+	lookupPrec.colorMask		= precision.colorMask;
+	lookupPrec.coordBits		= tcu::IVec3(22);
+	lookupPrec.uvwBits			= precision.filterBits;
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	for (int x = 0; x < dst.getWidth(); x++)
+	{
+		const tcu::Vec4	result	= dst.getPixel(x, y);
+		const float		cx		= (float(x)+0.5f) / dstW * srcW;
+		const float		cy		= (float(y)+0.5f) / dstH * srcH;
+		const bool		isOk	= tcu::isLinearSampleResultValid(src, sampler, lookupPrec, tcu::Vec2(cx, cy), 0, result);
+
+		errorMask.setPixel(isOk ? tcu::RGBA::green.toVec() : tcu::RGBA::red.toVec(), x, y);
+		if (!isOk)
+			numFailed += 1;
+	}
+
+	return numFailed;
+}
+
+static int compareGenMipmapBox (const tcu::ConstPixelBufferAccess& dst, const tcu::ConstPixelBufferAccess& src, const tcu::PixelBufferAccess& errorMask, const GenMipmapPrecision& precision)
+{
+	DE_ASSERT(dst.getDepth() == 1 && src.getDepth() == 1); // \todo [2013-10-29 pyry] 3D textures.
+
+	const float		dstW		= float(dst.getWidth());
+	const float		dstH		= float(dst.getHeight());
+	const float		srcW		= float(src.getWidth());
+	const float		srcH		= float(src.getHeight());
+	int				numFailed	= 0;
+
+	// Translation to lookup verification parameters.
+	const tcu::Sampler		sampler		(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
+										 tcu::Sampler::LINEAR, tcu::Sampler::LINEAR, 0.0f, false /* non-normalized coords */);
+	tcu::LookupPrecision	lookupPrec;
+
+	lookupPrec.colorThreshold	= precision.colorThreshold;
+	lookupPrec.colorMask		= precision.colorMask;
+	lookupPrec.coordBits		= tcu::IVec3(22);
+	lookupPrec.uvwBits			= precision.filterBits;
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	for (int x = 0; x < dst.getWidth(); x++)
+	{
+		const tcu::Vec4	result	= dst.getPixel(x, y);
+		const float		cx		= deFloatFloor(float(x) / dstW * srcW) + 1.0f;
+		const float		cy		= deFloatFloor(float(y) / dstH * srcH) + 1.0f;
+		const bool		isOk	= tcu::isLinearSampleResultValid(src, sampler, lookupPrec, tcu::Vec2(cx, cy), 0, result);
+
+		errorMask.setPixel(isOk ? tcu::RGBA::green.toVec() : tcu::RGBA::red.toVec(), x, y);
+		if (!isOk)
+			numFailed += 1;
+	}
+
+	return numFailed;
+}
+
+static int compareGenMipmapVeryLenient (const tcu::ConstPixelBufferAccess& dst, const tcu::ConstPixelBufferAccess& src, const tcu::PixelBufferAccess& errorMask, const GenMipmapPrecision& precision)
+{
+	DE_ASSERT(dst.getDepth() == 1 && src.getDepth() == 1); // \todo [2013-10-29 pyry] 3D textures.
+	DE_UNREF(precision);
+
+	const float		dstW		= float(dst.getWidth());
+	const float		dstH		= float(dst.getHeight());
+	const float		srcW		= float(src.getWidth());
+	const float		srcH		= float(src.getHeight());
+	int				numFailed	= 0;
+
+	for (int y = 0; y < dst.getHeight(); y++)
+	for (int x = 0; x < dst.getWidth(); x++)
+	{
+		const tcu::Vec4	result	= dst.getPixel(x, y);
+		const int		minX		= deFloorFloatToInt32(float(x-0.5f) / dstW * srcW);
+		const int		minY		= deFloorFloatToInt32(float(y-0.5f) / dstH * srcH);
+		const int		maxX		= deCeilFloatToInt32(float(x+1.5f) / dstW * srcW);
+		const int		maxY		= deCeilFloatToInt32(float(y+1.5f) / dstH * srcH);
+		tcu::Vec4		minVal, maxVal;
+		bool			isOk;
+
+		DE_ASSERT(minX < maxX && minY < maxY);
+
+		for (int ky = minY; ky <= maxY; ky++)
+		{
+			for (int kx = minX; kx <= maxX; kx++)
+			{
+				const int		sx		= de::clamp(kx, 0, src.getWidth()-1);
+				const int		sy		= de::clamp(ky, 0, src.getHeight()-1);
+				const tcu::Vec4	sample	= src.getPixel(sx, sy);
+
+				if (ky == minY && kx == minX)
+				{
+					minVal = sample;
+					maxVal = sample;
+				}
+				else
+				{
+					minVal = min(sample, minVal);
+					maxVal = max(sample, maxVal);
+				}
+			}
+		}
+
+		isOk = boolAll(logicalAnd(lessThanEqual(minVal, result), lessThanEqual(result, maxVal)));
+
+		errorMask.setPixel(isOk ? tcu::RGBA::green.toVec() : tcu::RGBA::red.toVec(), x, y);
+		if (!isOk)
+			numFailed += 1;
+	}
+
+	return numFailed;
+}
+
+qpTestResult compareGenMipmapResult (tcu::TestLog& log, const tcu::Texture2D& resultTexture, const tcu::Texture2D& level0Reference, const GenMipmapPrecision& precision)
+{
+	qpTestResult result = QP_TEST_RESULT_PASS;
+
+	// Special comparison for level 0.
+	{
+		const tcu::Vec4		threshold	= select(precision.colorThreshold, tcu::Vec4(1.0f), precision.colorMask);
+		const bool			level0Ok	= tcu::floatThresholdCompare(log, "Level0", "Level 0", level0Reference.getLevel(0), resultTexture.getLevel(0), threshold, tcu::COMPARE_LOG_RESULT);
+
+		if (!level0Ok)
+		{
+			log << TestLog::Message << "ERROR: Level 0 comparison failed!" << TestLog::EndMessage;
+			result = QP_TEST_RESULT_FAIL;
+		}
+	}
+
+	for (int levelNdx = 1; levelNdx < resultTexture.getNumLevels(); levelNdx++)
+	{
+		const tcu::ConstPixelBufferAccess	src			= resultTexture.getLevel(levelNdx-1);
+		const tcu::ConstPixelBufferAccess	dst			= resultTexture.getLevel(levelNdx);
+		tcu::Surface						errorMask	(dst.getWidth(), dst.getHeight());
+		bool								levelOk		= false;
+
+		// Try different comparisons in quality order.
+
+		if (!levelOk)
+		{
+			const int numFailed = compareGenMipmapBilinear(dst, src, errorMask.getAccess(), precision);
+			if (numFailed == 0)
+				levelOk = true;
+			else
+				log << TestLog::Message << "WARNING: Level " << levelNdx << " comparison to bilinear method failed, found " << numFailed << " invalid pixels." << TestLog::EndMessage;
+		}
+
+		if (!levelOk)
+		{
+			const int numFailed = compareGenMipmapBox(dst, src, errorMask.getAccess(), precision);
+			if (numFailed == 0)
+				levelOk = true;
+			else
+				log << TestLog::Message << "WARNING: Level " << levelNdx << " comparison to box method failed, found " << numFailed << " invalid pixels." << TestLog::EndMessage;
+		}
+
+		// At this point all high-quality methods have been used.
+		if (!levelOk && result == QP_TEST_RESULT_PASS)
+			result = QP_TEST_RESULT_QUALITY_WARNING;
+
+		if (!levelOk)
+		{
+			const int numFailed = compareGenMipmapVeryLenient(dst, src, errorMask.getAccess(), precision);
+			if (numFailed == 0)
+				levelOk = true;
+			else
+				log << TestLog::Message << "ERROR: Level " << levelNdx << " appears to contain " << numFailed << " completely wrong pixels, failing case!" << TestLog::EndMessage;
+		}
+
+		if (!levelOk)
+			result = QP_TEST_RESULT_FAIL;
+
+		log << TestLog::ImageSet(string("Level") + de::toString(levelNdx), string("Level ") + de::toString(levelNdx) + " result")
+			<< TestLog::Image("Result", "Result", dst);
+
+		if (!levelOk)
+			log << TestLog::Image("ErrorMask", "Error mask", errorMask);
+
+		log << TestLog::EndImageSet;
+	}
+
+	return result;
+}
+
+qpTestResult compareGenMipmapResult (tcu::TestLog& log, const tcu::TextureCube& resultTexture, const tcu::TextureCube& level0Reference, const GenMipmapPrecision& precision)
+{
+	qpTestResult result = QP_TEST_RESULT_PASS;
+
+	static const char* s_faceNames[] = { "-X", "+X", "-Y", "+Y", "-Z", "+Z" };
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_faceNames) == tcu::CUBEFACE_LAST);
+
+	// Special comparison for level 0.
+	for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+	{
+		const tcu::CubeFace	face		= tcu::CubeFace(faceNdx);
+		const tcu::Vec4		threshold	= select(precision.colorThreshold, tcu::Vec4(1.0f), precision.colorMask);
+		const bool			level0Ok	= tcu::floatThresholdCompare(log,
+																	 ("Level0Face" + de::toString(faceNdx)).c_str(),
+																	 (string("Level 0, face ") + s_faceNames[face]).c_str(),
+																	 level0Reference.getLevelFace(0, face),
+																	 resultTexture.getLevelFace(0, face),
+																	 threshold, tcu::COMPARE_LOG_RESULT);
+
+		if (!level0Ok)
+		{
+			log << TestLog::Message << "ERROR: Level 0, face " << s_faceNames[face] << " comparison failed!" << TestLog::EndMessage;
+			result = QP_TEST_RESULT_FAIL;
+		}
+	}
+
+	for (int levelNdx = 1; levelNdx < resultTexture.getNumLevels(); levelNdx++)
+	{
+		for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
+		{
+			const tcu::CubeFace					face		= tcu::CubeFace(faceNdx);
+			const char*							faceName	= s_faceNames[face];
+			const tcu::ConstPixelBufferAccess	src			= resultTexture.getLevelFace(levelNdx-1,	face);
+			const tcu::ConstPixelBufferAccess	dst			= resultTexture.getLevelFace(levelNdx,		face);
+			tcu::Surface						errorMask	(dst.getWidth(), dst.getHeight());
+			bool								levelOk		= false;
+
+			// Try different comparisons in quality order.
+
+			if (!levelOk)
+			{
+				const int numFailed = compareGenMipmapBilinear(dst, src, errorMask.getAccess(), precision);
+				if (numFailed == 0)
+					levelOk = true;
+				else
+					log << TestLog::Message << "WARNING: Level " << levelNdx << ", face " << faceName << " comparison to bilinear method failed, found " << numFailed << " invalid pixels." << TestLog::EndMessage;
+			}
+
+			if (!levelOk)
+			{
+				const int numFailed = compareGenMipmapBox(dst, src, errorMask.getAccess(), precision);
+				if (numFailed == 0)
+					levelOk = true;
+				else
+					log << TestLog::Message << "WARNING: Level " << levelNdx << ", face " << faceName <<" comparison to box method failed, found " << numFailed << " invalid pixels." << TestLog::EndMessage;
+			}
+
+			// At this point all high-quality methods have been used.
+			if (!levelOk && result == QP_TEST_RESULT_PASS)
+				result = QP_TEST_RESULT_QUALITY_WARNING;
+
+			if (!levelOk)
+			{
+				const int numFailed = compareGenMipmapVeryLenient(dst, src, errorMask.getAccess(), precision);
+				if (numFailed == 0)
+					levelOk = true;
+				else
+					log << TestLog::Message << "ERROR: Level " << levelNdx << ", face " << faceName << " appears to contain " << numFailed << " completely wrong pixels, failing case!" << TestLog::EndMessage;
+			}
+
+			if (!levelOk)
+				result = QP_TEST_RESULT_FAIL;
+
+			log << TestLog::ImageSet(string("Level") + de::toString(levelNdx) + "Face" + de::toString(faceNdx), string("Level ") + de::toString(levelNdx) + ", face " + string(faceName) + " result")
+				<< TestLog::Image("Result", "Result", dst);
+
+			if (!levelOk)
+				log << TestLog::Image("ErrorMask", "Error mask", errorMask);
+
+			log << TestLog::EndImageSet;
+		}
+	}
+
+	return result;
+}
+
+// Logging utilities.
+
+std::ostream& operator<< (std::ostream& str, const LogGradientFmt& fmt)
+{
+	return str << "(R: " << fmt.valueMin->x() << " -> " << fmt.valueMax->x() << ", "
+			   <<  "G: " << fmt.valueMin->y() << " -> " << fmt.valueMax->y() << ", "
+			   <<  "B: " << fmt.valueMin->z() << " -> " << fmt.valueMax->z() << ", "
+			   <<  "A: " << fmt.valueMin->w() << " -> " << fmt.valueMax->w() << ")";
+}
+
+} // TextureTestUtil
+} // gls
+} // deqp
diff --git a/modules/glshared/glsTextureTestUtil.hpp b/modules/glshared/glsTextureTestUtil.hpp
new file mode 100644
index 0000000..45ea65f
--- /dev/null
+++ b/modules/glshared/glsTextureTestUtil.hpp
@@ -0,0 +1,554 @@
+#ifndef _GLSTEXTURETESTUTIL_HPP
+#define _GLSTEXTURETESTUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Texture test utilities.
+ *
+ * About coordinates:
+ *  + Quads consist of 2 triangles, rendered using explicit indices.
+ *  + All TextureTestUtil functions and classes expect texture coordinates
+ *    for quads to be specified in order (-1, -1), (-1, 1), (1, -1), (1, 1).
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTexture.hpp"
+#include "tcuSurface.hpp"
+#include "tcuPixelFormat.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuTestContext.hpp"
+#include "deMath.h"
+#include "deInt32.h"
+#include "gluShaderProgram.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuCompressedTexture.hpp"
+#include "gluShaderUtil.hpp"
+
+#include <map>
+
+namespace tcu
+{
+struct LookupPrecision;
+struct LodPrecision;
+struct TexComparePrecision;
+}
+
+namespace deqp
+{
+namespace gls
+{
+namespace TextureTestUtil
+{
+
+enum TextureType
+{
+	TEXTURETYPE_2D = 0,
+	TEXTURETYPE_CUBE,
+	TEXTURETYPE_2D_ARRAY,
+	TEXTURETYPE_3D,
+	TEXTURETYPE_CUBE_ARRAY,
+	TEXTURETYPE_1D,
+	TEXTURETYPE_1D_ARRAY,
+	TEXTURETYPE_BUFFER,
+
+	TEXTURETYPE_LAST
+};
+
+enum SamplerType
+{
+	SAMPLERTYPE_FLOAT,
+	SAMPLERTYPE_INT,
+	SAMPLERTYPE_UINT,
+	SAMPLERTYPE_SHADOW,
+
+	SAMPLERTYPE_FETCH_FLOAT,
+	SAMPLERTYPE_FETCH_INT,
+	SAMPLERTYPE_FETCH_UINT,
+
+	SAMPLERTYPE_LAST
+};
+
+SamplerType		getSamplerType		(tcu::TextureFormat format);
+SamplerType		getFetchSamplerType	(tcu::TextureFormat format);
+
+struct RenderParams
+{
+	enum Flags
+	{
+		PROJECTED		= (1<<0),
+		USE_BIAS		= (1<<1),
+		LOG_PROGRAMS	= (1<<2),
+		LOG_UNIFORMS	= (1<<3),
+
+		LOG_ALL			= LOG_PROGRAMS|LOG_UNIFORMS
+	};
+
+	RenderParams (TextureType texType_)
+		: texType		(texType_)
+		, samplerType	(SAMPLERTYPE_FLOAT)
+		, flags			(0)
+		, w				(1.0f)
+		, bias			(0.0f)
+		, ref			(0.0f)
+		, colorScale	(1.0f)
+		, colorBias		(0.0f)
+	{
+	}
+
+	TextureType		texType;		//!< Texture type.
+	SamplerType		samplerType;	//!< Sampler type.
+	deUint32		flags;			//!< Feature flags.
+	tcu::Vec4		w;				//!< w coordinates for quad vertices.
+	float			bias;			//!< User-supplied bias.
+	float			ref;			//!< Reference value for shadow lookups.
+
+	// color = lookup() * scale + bias
+	tcu::Vec4		colorScale;		//!< Scale for texture color values.
+	tcu::Vec4		colorBias;		//!< Bias for texture color values.
+};
+
+enum Program
+{
+	PROGRAM_2D_FLOAT = 0,
+	PROGRAM_2D_INT,
+	PROGRAM_2D_UINT,
+	PROGRAM_2D_SHADOW,
+
+	PROGRAM_2D_FLOAT_BIAS,
+	PROGRAM_2D_INT_BIAS,
+	PROGRAM_2D_UINT_BIAS,
+	PROGRAM_2D_SHADOW_BIAS,
+
+	PROGRAM_1D_FLOAT,
+	PROGRAM_1D_INT,
+	PROGRAM_1D_UINT,
+	PROGRAM_1D_SHADOW,
+
+	PROGRAM_1D_FLOAT_BIAS,
+	PROGRAM_1D_INT_BIAS,
+	PROGRAM_1D_UINT_BIAS,
+	PROGRAM_1D_SHADOW_BIAS,
+
+	PROGRAM_CUBE_FLOAT,
+	PROGRAM_CUBE_INT,
+	PROGRAM_CUBE_UINT,
+	PROGRAM_CUBE_SHADOW,
+
+	PROGRAM_CUBE_FLOAT_BIAS,
+	PROGRAM_CUBE_INT_BIAS,
+	PROGRAM_CUBE_UINT_BIAS,
+	PROGRAM_CUBE_SHADOW_BIAS,
+
+	PROGRAM_1D_ARRAY_FLOAT,
+	PROGRAM_1D_ARRAY_INT,
+	PROGRAM_1D_ARRAY_UINT,
+	PROGRAM_1D_ARRAY_SHADOW,
+
+	PROGRAM_2D_ARRAY_FLOAT,
+	PROGRAM_2D_ARRAY_INT,
+	PROGRAM_2D_ARRAY_UINT,
+	PROGRAM_2D_ARRAY_SHADOW,
+
+	PROGRAM_3D_FLOAT,
+	PROGRAM_3D_INT,
+	PROGRAM_3D_UINT,
+
+	PROGRAM_3D_FLOAT_BIAS,
+	PROGRAM_3D_INT_BIAS,
+	PROGRAM_3D_UINT_BIAS,
+
+	PROGRAM_CUBE_ARRAY_FLOAT,
+	PROGRAM_CUBE_ARRAY_INT,
+	PROGRAM_CUBE_ARRAY_UINT,
+	PROGRAM_CUBE_ARRAY_SHADOW,
+
+	PROGRAM_BUFFER_FLOAT,
+	PROGRAM_BUFFER_INT,
+	PROGRAM_BUFFER_UINT,
+
+	PROGRAM_LAST
+};
+
+class ProgramLibrary
+{
+public:
+											ProgramLibrary			(const glu::RenderContext& context, tcu::TestContext& testCtx, glu::GLSLVersion glslVersion, glu::Precision texCoordPrecision);
+											~ProgramLibrary			(void);
+
+	glu::ShaderProgram*						getProgram				(Program program);
+	void									clear					(void);
+
+private:
+											ProgramLibrary			(const ProgramLibrary& other);
+	ProgramLibrary&							operator=				(const ProgramLibrary& other);
+
+	const glu::RenderContext&				m_context;
+	tcu::TestContext&						m_testCtx;
+	glu::GLSLVersion						m_glslVersion;
+	glu::Precision							m_texCoordPrecision;
+	std::map<Program, glu::ShaderProgram*>	m_programs;
+};
+
+class TextureRenderer
+{
+public:
+								TextureRenderer			(const glu::RenderContext& context, tcu::TestContext& testCtx, glu::GLSLVersion glslVersion, glu::Precision texCoordPrecision);
+								~TextureRenderer		(void);
+
+	void						clear					(void); //!< Frees allocated resources. Destructor will call clear() as well.
+
+	void						renderQuad				(int texUnit, const float* texCoord, TextureType texType);
+	void						renderQuad				(int texUnit, const float* texCoord, const RenderParams& params);
+
+private:
+								TextureRenderer			(const TextureRenderer& other);
+	TextureRenderer&			operator=				(const TextureRenderer& other);
+
+	const glu::RenderContext&	m_renderCtx;
+	tcu::TestContext&			m_testCtx;
+	ProgramLibrary				m_programLibrary;
+};
+
+class RandomViewport
+{
+public:
+	int		x;
+	int		y;
+	int		width;
+	int		height;
+
+	RandomViewport (const tcu::RenderTarget& renderTarget, int preferredWidth, int preferredHeight, deUint32 seed);
+};
+
+inline tcu::RGBA toRGBA (const tcu::Vec4& v)
+{
+	// \todo [2011-10-24 pyry] Rounding mode?
+	return tcu::RGBA(deClamp32(deRoundFloatToInt32(v.x()*255.0f), 0, 255),
+					 deClamp32(deRoundFloatToInt32(v.y()*255.0f), 0, 255),
+					 deClamp32(deRoundFloatToInt32(v.z()*255.0f), 0, 255),
+					 deClamp32(deRoundFloatToInt32(v.w()*255.0f), 0, 255));
+}
+
+inline tcu::RGBA toRGBAMasked (const tcu::Vec4& v, deUint8 mask)
+{
+	// \todo [2011-10-24 pyry] Rounding mode?
+	return tcu::RGBA((mask&tcu::RGBA::RED_MASK)		? deClamp32(deRoundFloatToInt32(v.x()*255.0f), 0, 255) : 0,
+					 (mask&tcu::RGBA::GREEN_MASK)	? deClamp32(deRoundFloatToInt32(v.y()*255.0f), 0, 255) : 0,
+					 (mask&tcu::RGBA::BLUE_MASK)	? deClamp32(deRoundFloatToInt32(v.z()*255.0f), 0, 255) : 0,
+					 (mask&tcu::RGBA::ALPHA_MASK)	? deClamp32(deRoundFloatToInt32(v.w()*255.0f), 0, 255) : 0xff);
+}
+
+inline tcu::Vec4 toVec4 (const tcu::RGBA& c)
+{
+	return tcu::Vec4(c.getRed()		/ 255.0f,
+					 c.getGreen()	/ 255.0f,
+					 c.getBlue()	/ 255.0f,
+					 c.getAlpha()	/ 255.0f);
+}
+
+inline deUint8 getColorMask (const tcu::PixelFormat& format)
+{
+	return (format.redBits		? tcu::RGBA::RED_MASK	: 0) |
+		   (format.greenBits	? tcu::RGBA::GREEN_MASK	: 0) |
+		   (format.blueBits		? tcu::RGBA::BLUE_MASK	: 0) |
+		   (format.alphaBits	? tcu::RGBA::ALPHA_MASK	: 0);
+}
+
+inline tcu::IVec4 getBitsVec (const tcu::PixelFormat& format)
+{
+	return tcu::IVec4(format.redBits, format.greenBits, format.blueBits, format.alphaBits);
+}
+
+inline tcu::BVec4 getCompareMask (const tcu::PixelFormat& format)
+{
+	return tcu::BVec4(format.redBits	> 0,
+					  format.greenBits	> 0,
+					  format.blueBits	> 0,
+					  format.alphaBits	> 0);
+}
+
+// \todo [2012-02-09 pyry] Move to tcuSurfaceAccess?
+class SurfaceAccess
+{
+public:
+							SurfaceAccess		(tcu::Surface& surface, const tcu::PixelFormat& colorFmt);
+							SurfaceAccess		(tcu::Surface& surface, const tcu::PixelFormat& colorFmt, int x, int y, int width, int height);
+							SurfaceAccess		(const SurfaceAccess& parent, int x, int y, int width, int height);
+
+	int						getWidth			(void) const	{ return m_width;	}
+	int						getHeight			(void) const	{ return m_height;	}
+
+	void					setPixel			(const tcu::Vec4& color, int x, int y) const;
+
+private:
+	mutable tcu::Surface*	m_surface;
+	deUint8					m_colorMask;
+	int						m_x;
+	int						m_y;
+	int						m_width;
+	int						m_height;
+};
+
+inline void SurfaceAccess::setPixel (const tcu::Vec4& color, int x, int y) const
+{
+	DE_ASSERT(de::inBounds(x, 0, m_width) && de::inBounds(y, 0, m_height));
+	m_surface->setPixel(m_x+x, m_y+y, toRGBAMasked(color, m_colorMask));
+}
+
+enum LodMode
+{
+	LODMODE_EXACT = 0,		//!< Ideal lod computation.
+	LODMODE_MIN_BOUND,		//!< Use estimation range minimum bound.
+	LODMODE_MAX_BOUND,		//!< Use estimation range maximum bound.
+
+	LODMODE_LAST
+};
+
+struct ReferenceParams : public RenderParams
+{
+	ReferenceParams (TextureType texType_)
+		: RenderParams	(texType_)
+		, sampler		()
+		, lodMode		(LODMODE_EXACT)
+		, minLod		(-1000.0f)
+		, maxLod		(1000.0f)
+		, baseLevel		(0)
+		, maxLevel		(1000)
+	{
+	}
+
+	ReferenceParams (TextureType texType_, const tcu::Sampler& sampler_, LodMode lodMode_ = LODMODE_EXACT)
+		: RenderParams	(texType_)
+		, sampler		(sampler_)
+		, lodMode		(lodMode_)
+		, minLod		(-1000.0f)
+		, maxLod		(1000.0f)
+		, baseLevel		(0)
+		, maxLevel		(1000)
+	{
+	}
+
+	tcu::Sampler		sampler;
+	LodMode				lodMode;
+	float				minLod;
+	float				maxLod;
+	int					baseLevel;
+	int					maxLevel;
+};
+
+void			clear						(const SurfaceAccess& dst, const tcu::Vec4& color);
+
+// Similar to sampleTexture() except uses texelFetch.
+void			fetchTexture				(const SurfaceAccess& dst, const tcu::ConstPixelBufferAccess& src, const float* texCoord, const tcu::Vec4& colorScale, const tcu::Vec4& colorBias);
+
+void			sampleTexture				(const SurfaceAccess& dst, const tcu::Texture2DView&		src, const float* texCoord, const ReferenceParams& params);
+void			sampleTexture				(const SurfaceAccess& dst, const tcu::TextureCubeView&		src, const float* texCoord, const ReferenceParams& params);
+void			sampleTextureMultiFace		(const SurfaceAccess& dst, const tcu::TextureCubeView&		src, const float* texCoord, const ReferenceParams& params);
+void			sampleTexture				(const SurfaceAccess& dst, const tcu::Texture2DArrayView&	src, const float* texCoord, const ReferenceParams& params);
+void			sampleTexture				(const SurfaceAccess& dst, const tcu::Texture3DView&		src, const float* texCoord, const ReferenceParams& params);
+void			sampleTexture				(const SurfaceAccess& dst, const tcu::TextureCubeArrayView&	src, const float* texCoord, const ReferenceParams& params);
+void			sampleTexture				(const SurfaceAccess& dst, const tcu::Texture1DView&		src, const float* texCoord, const ReferenceParams& params);
+void			sampleTexture				(const SurfaceAccess& dst, const tcu::Texture1DArrayView&	src, const float* texCoord, const ReferenceParams& params);
+
+void			computeQuadTexCoord1D			(std::vector<float>& dst, float left, float right);
+void			computeQuadTexCoord1DArray		(std::vector<float>& dst, int layerNdx, float left, float right);
+void			computeQuadTexCoord2D			(std::vector<float>& dst, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight);
+void			computeQuadTexCoord2DArray		(std::vector<float>& dst, int layerNdx, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight);
+void			computeQuadTexCoord3D			(std::vector<float>& dst, const tcu::Vec3& p0, const tcu::Vec3& p1, const tcu::IVec3& dirSwz);
+void			computeQuadTexCoordCube			(std::vector<float>& dst, tcu::CubeFace face);
+void			computeQuadTexCoordCube			(std::vector<float>& dst, tcu::CubeFace face, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight);
+void			computeQuadTexCoordCubeArray	(std::vector<float>& dst, int sliceNdx, tcu::CubeFace face, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight);
+
+bool			compareImages				(tcu::TestLog& log, const char* name, const char* desc, const tcu::Surface& reference, const tcu::Surface& rendered, tcu::RGBA threshold);
+bool			compareImages				(tcu::TestLog& log, const tcu::Surface& reference, const tcu::Surface& rendered, tcu::RGBA threshold);
+int				measureAccuracy				(tcu::TestLog& log, const tcu::Surface& reference, const tcu::Surface& rendered, int bestScoreDiff, int worstScoreDiff);
+
+int				computeTextureLookupDiff	(const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::ConstPixelBufferAccess&	reference,
+											 const tcu::PixelBufferAccess&		errorMask,
+											 const tcu::Texture1DView&			src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 qpWatchDog*						watchDog);
+
+int				computeTextureLookupDiff	(const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::ConstPixelBufferAccess&	reference,
+											 const tcu::PixelBufferAccess&		errorMask,
+											 const tcu::Texture2DView&			src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 qpWatchDog*						watchDog);
+
+int				computeTextureLookupDiff	(const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::ConstPixelBufferAccess&	reference,
+											 const tcu::PixelBufferAccess&		errorMask,
+											 const tcu::TextureCubeView&		src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 qpWatchDog*						watchDog);
+
+int				computeTextureLookupDiff	(const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::ConstPixelBufferAccess&	reference,
+											 const tcu::PixelBufferAccess&		errorMask,
+											 const tcu::Texture1DArrayView&		src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 qpWatchDog*						watchDog);
+
+int				computeTextureLookupDiff	(const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::ConstPixelBufferAccess&	reference,
+											 const tcu::PixelBufferAccess&		errorMask,
+											 const tcu::Texture2DArrayView&		src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 qpWatchDog*						watchDog);
+
+int				computeTextureLookupDiff	(const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::ConstPixelBufferAccess&	reference,
+											 const tcu::PixelBufferAccess&		errorMask,
+											 const tcu::Texture3DView&			src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 qpWatchDog*						watchDog);
+
+bool			verifyTextureResult			(tcu::TestContext&					testCtx,
+											 const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::Texture1DView&			src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 const tcu::PixelFormat&			pixelFormat);
+
+bool			verifyTextureResult			(tcu::TestContext&					testCtx,
+											 const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::Texture2DView&			src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 const tcu::PixelFormat&			pixelFormat);
+
+bool			verifyTextureResult			(tcu::TestContext&					testCtx,
+											 const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::TextureCubeView&		src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 const tcu::PixelFormat&			pixelFormat);
+
+bool			verifyTextureResult			(tcu::TestContext&					testCtx,
+											 const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::Texture1DArrayView&		src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 const tcu::PixelFormat&			pixelFormat);
+
+bool			verifyTextureResult			(tcu::TestContext&					testCtx,
+											 const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::Texture2DArrayView&		src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 const tcu::PixelFormat&			pixelFormat);
+
+bool			verifyTextureResult			(tcu::TestContext&					testCtx,
+											 const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::Texture3DView&			src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::LookupPrecision&		lookupPrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 const tcu::PixelFormat&			pixelFormat);
+
+int				computeTextureCompareDiff	(const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::ConstPixelBufferAccess&	reference,
+											 const tcu::PixelBufferAccess&		errorMask,
+											 const tcu::Texture2DView&			src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::TexComparePrecision&	comparePrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 const tcu::Vec3&					nonShadowThreshold);
+
+int				computeTextureCompareDiff	(const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::ConstPixelBufferAccess&	reference,
+											 const tcu::PixelBufferAccess&		errorMask,
+											 const tcu::TextureCubeView&		src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::TexComparePrecision&	comparePrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 const tcu::Vec3&					nonShadowThreshold);
+
+int				computeTextureCompareDiff	(const tcu::ConstPixelBufferAccess&	result,
+											 const tcu::ConstPixelBufferAccess&	reference,
+											 const tcu::PixelBufferAccess&		errorMask,
+											 const tcu::Texture2DArrayView&		src,
+											 const float*						texCoord,
+											 const ReferenceParams&				sampleParams,
+											 const tcu::TexComparePrecision&	comparePrec,
+											 const tcu::LodPrecision&			lodPrec,
+											 const tcu::Vec3&					nonShadowThreshold);
+
+// Mipmap generation comparison.
+
+struct GenMipmapPrecision
+{
+	tcu::IVec3			filterBits;			//!< Bits in filtering parameters (fixed-point).
+	tcu::Vec4			colorThreshold;		//!< Threshold for color value comparison.
+	tcu::BVec4			colorMask;			//!< Color channel comparison mask.
+};
+
+qpTestResult	compareGenMipmapResult		(tcu::TestLog& log, const tcu::Texture2D& resultTexture, const tcu::Texture2D& level0Reference, const GenMipmapPrecision& precision);
+qpTestResult	compareGenMipmapResult		(tcu::TestLog& log, const tcu::TextureCube& resultTexture, const tcu::TextureCube& level0Reference, const GenMipmapPrecision& precision);
+
+// Utility for logging texture gradient ranges.
+struct LogGradientFmt
+{
+	LogGradientFmt (const tcu::Vec4* min_, const tcu::Vec4* max_) : valueMin(min_), valueMax(max_) {}
+	const tcu::Vec4* valueMin;
+	const tcu::Vec4* valueMax;
+};
+
+std::ostream&			operator<<		(std::ostream& str, const LogGradientFmt& fmt);
+inline LogGradientFmt	formatGradient	(const tcu::Vec4* minVal, const tcu::Vec4* maxVal) { return LogGradientFmt(minVal, maxVal); }
+
+} // TextureTestUtil
+} // gls
+} // deqp
+
+#endif // _GLSTEXTURETESTUTIL_HPP
diff --git a/modules/glshared/glsUniformBlockCase.cpp b/modules/glshared/glsUniformBlockCase.cpp
new file mode 100644
index 0000000..d2d4760
--- /dev/null
+++ b/modules/glshared/glsUniformBlockCase.cpp
@@ -0,0 +1,1952 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Uniform block case.
+ *//*--------------------------------------------------------------------*/
+
+#include "glsUniformBlockCase.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+#include "gluPixelTransfer.hpp"
+#include "gluContextInfo.hpp"
+#include "gluRenderContext.hpp"
+#include "gluDrawUtil.hpp"
+#include "glwFunctions.hpp"
+#include "glwEnums.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuRenderTarget.hpp"
+#include "deRandom.hpp"
+#include "deStringUtil.hpp"
+#include "deMemory.h"
+#include "deString.h"
+
+#include <algorithm>
+#include <map>
+
+using tcu::TestLog;
+using std::string;
+using std::vector;
+using std::map;
+
+namespace deqp
+{
+namespace gls
+{
+namespace ub
+{
+
+static bool isSupportedGLSLVersion (glu::GLSLVersion version)
+{
+	return version >= (glslVersionIsES(version) ? glu::GLSL_VERSION_300_ES : glu::GLSL_VERSION_330);
+}
+
+struct PrecisionFlagsFmt
+{
+	deUint32 flags;
+	PrecisionFlagsFmt (deUint32 flags_) : flags(flags_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const PrecisionFlagsFmt& fmt)
+{
+	// Precision.
+	DE_ASSERT(dePop32(fmt.flags & (PRECISION_LOW|PRECISION_MEDIUM|PRECISION_HIGH)) <= 1);
+	str << (fmt.flags & PRECISION_LOW		? "lowp"	:
+			fmt.flags & PRECISION_MEDIUM	? "mediump"	:
+			fmt.flags & PRECISION_HIGH		? "highp"	: "");
+	return str;
+}
+
+struct LayoutFlagsFmt
+{
+	deUint32 flags;
+	LayoutFlagsFmt (deUint32 flags_) : flags(flags_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const LayoutFlagsFmt& fmt)
+{
+	static const struct
+	{
+		deUint32	bit;
+		const char*	token;
+	} bitDesc[] =
+	{
+		{ LAYOUT_SHARED,		"shared"		},
+		{ LAYOUT_PACKED,		"packed"		},
+		{ LAYOUT_STD140,		"std140"		},
+		{ LAYOUT_ROW_MAJOR,		"row_major"		},
+		{ LAYOUT_COLUMN_MAJOR,	"column_major"	}
+	};
+
+	deUint32 remBits = fmt.flags;
+	for (int descNdx = 0; descNdx < DE_LENGTH_OF_ARRAY(bitDesc); descNdx++)
+	{
+		if (remBits & bitDesc[descNdx].bit)
+		{
+			if (remBits != fmt.flags)
+				str << ", ";
+			str << bitDesc[descNdx].token;
+			remBits &= ~bitDesc[descNdx].bit;
+		}
+	}
+	DE_ASSERT(remBits == 0);
+	return str;
+}
+
+// VarType implementation.
+
+VarType::VarType (void)
+	: m_type	(TYPE_LAST)
+	, m_flags	(0)
+{
+}
+
+VarType::VarType (const VarType& other)
+	: m_type	(TYPE_LAST)
+	, m_flags	(0)
+{
+	*this = other;
+}
+
+VarType::VarType (glu::DataType basicType, deUint32 flags)
+	: m_type	(TYPE_BASIC)
+	, m_flags	(flags)
+{
+	m_data.basicType = basicType;
+}
+
+VarType::VarType (const VarType& elementType, int arraySize)
+	: m_type	(TYPE_ARRAY)
+	, m_flags	(0)
+{
+	m_data.array.size			= arraySize;
+	m_data.array.elementType	= new VarType(elementType);
+}
+
+VarType::VarType (const StructType* structPtr)
+	: m_type	(TYPE_STRUCT)
+	, m_flags	(0)
+{
+	m_data.structPtr = structPtr;
+}
+
+VarType::~VarType (void)
+{
+	if (m_type == TYPE_ARRAY)
+		delete m_data.array.elementType;
+}
+
+VarType& VarType::operator= (const VarType& other)
+{
+	if (this == &other)
+		return *this; // Self-assignment.
+
+	if (m_type == TYPE_ARRAY)
+		delete m_data.array.elementType;
+
+	m_type	= other.m_type;
+	m_flags	= other.m_flags;
+	m_data	= Data();
+
+	if (m_type == TYPE_ARRAY)
+	{
+		m_data.array.elementType	= new VarType(*other.m_data.array.elementType);
+		m_data.array.size			= other.m_data.array.size;
+	}
+	else
+		m_data = other.m_data;
+
+	return *this;
+}
+
+// StructType implementation.
+
+void StructType::addMember (const char* name, const VarType& type, deUint32 flags)
+{
+	m_members.push_back(StructMember(name, type, flags));
+}
+
+// Uniform implementation.
+
+Uniform::Uniform (const char* name, const VarType& type, deUint32 flags)
+	: m_name	(name)
+	, m_type	(type)
+	, m_flags	(flags)
+{
+}
+
+// UniformBlock implementation.
+
+UniformBlock::UniformBlock (const char* blockName)
+	: m_blockName	(blockName)
+	, m_arraySize	(0)
+	, m_flags		(0)
+{
+}
+
+struct BlockLayoutEntry
+{
+	BlockLayoutEntry (void)
+		: size(0)
+	{
+	}
+
+	std::string			name;
+	int					size;
+	std::vector<int>	activeUniformIndices;
+};
+
+std::ostream& operator<< (std::ostream& stream, const BlockLayoutEntry& entry)
+{
+	stream << entry.name << " { name = " << entry.name
+		   << ", size = " << entry.size
+		   << ", activeUniformIndices = [";
+
+	for (vector<int>::const_iterator i = entry.activeUniformIndices.begin(); i != entry.activeUniformIndices.end(); i++)
+	{
+		if (i != entry.activeUniformIndices.begin())
+			stream << ", ";
+		stream << *i;
+	}
+
+	stream << "] }";
+	return stream;
+}
+
+struct UniformLayoutEntry
+{
+	UniformLayoutEntry (void)
+		: type			(glu::TYPE_LAST)
+		, size			(0)
+		, blockNdx		(-1)
+		, offset		(-1)
+		, arrayStride	(-1)
+		, matrixStride	(-1)
+		, isRowMajor	(false)
+	{
+	}
+
+	std::string			name;
+	glu::DataType		type;
+	int					size;
+	int					blockNdx;
+	int					offset;
+	int					arrayStride;
+	int					matrixStride;
+	bool				isRowMajor;
+};
+
+std::ostream& operator<< (std::ostream& stream, const UniformLayoutEntry& entry)
+{
+	stream << entry.name << " { type = " << glu::getDataTypeName(entry.type)
+		   << ", size = " << entry.size
+		   << ", blockNdx = " << entry.blockNdx
+		   << ", offset = " << entry.offset
+		   << ", arrayStride = " << entry.arrayStride
+		   << ", matrixStride = " << entry.matrixStride
+		   << ", isRowMajor = " << (entry.isRowMajor ? "true" : "false")
+		   << " }";
+	return stream;
+}
+
+class UniformLayout
+{
+public:
+	std::vector<BlockLayoutEntry>		blocks;
+	std::vector<UniformLayoutEntry>		uniforms;
+
+	int									getUniformIndex			(const char* name) const;
+	int									getBlockIndex			(const char* name) const;
+};
+
+// \todo [2012-01-24 pyry] Speed up lookups using hash.
+
+int UniformLayout::getUniformIndex (const char* name) const
+{
+	for (int ndx = 0; ndx < (int)uniforms.size(); ndx++)
+	{
+		if (uniforms[ndx].name == name)
+			return ndx;
+	}
+	return -1;
+}
+
+int UniformLayout::getBlockIndex (const char* name) const
+{
+	for (int ndx = 0; ndx < (int)blocks.size(); ndx++)
+	{
+		if (blocks[ndx].name == name)
+			return ndx;
+	}
+	return -1;
+}
+
+// ShaderInterface implementation.
+
+ShaderInterface::ShaderInterface (void)
+{
+}
+
+ShaderInterface::~ShaderInterface (void)
+{
+	for (std::vector<StructType*>::iterator i = m_structs.begin(); i != m_structs.end(); i++)
+		delete *i;
+
+	for (std::vector<UniformBlock*>::iterator i = m_uniformBlocks.begin(); i != m_uniformBlocks.end(); i++)
+		delete *i;
+}
+
+StructType& ShaderInterface::allocStruct (const char* name)
+{
+	m_structs.reserve(m_structs.size()+1);
+	m_structs.push_back(new StructType(name));
+	return *m_structs.back();
+}
+
+struct StructNameEquals
+{
+	std::string name;
+
+	StructNameEquals (const char* name_) : name(name_) {}
+
+	bool operator() (const StructType* type) const
+	{
+		return type->getTypeName() && name == type->getTypeName();
+	}
+};
+
+const StructType* ShaderInterface::findStruct (const char* name) const
+{
+	std::vector<StructType*>::const_iterator pos = std::find_if(m_structs.begin(), m_structs.end(), StructNameEquals(name));
+	return pos != m_structs.end() ? *pos : DE_NULL;
+}
+
+void ShaderInterface::getNamedStructs (std::vector<const StructType*>& structs) const
+{
+	for (std::vector<StructType*>::const_iterator i = m_structs.begin(); i != m_structs.end(); i++)
+	{
+		if ((*i)->getTypeName() != DE_NULL)
+			structs.push_back(*i);
+	}
+}
+
+UniformBlock& ShaderInterface::allocBlock (const char* name)
+{
+	m_uniformBlocks.reserve(m_uniformBlocks.size()+1);
+	m_uniformBlocks.push_back(new UniformBlock(name));
+	return *m_uniformBlocks.back();
+}
+
+namespace // Utilities
+{
+
+// Layout computation.
+
+int getDataTypeByteSize (glu::DataType type)
+{
+	return glu::getDataTypeScalarSize(type)*sizeof(deUint32);
+}
+
+int getDataTypeByteAlignment (glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_FLOAT:
+		case glu::TYPE_INT:
+		case glu::TYPE_UINT:
+		case glu::TYPE_BOOL:		return 1*sizeof(deUint32);
+
+		case glu::TYPE_FLOAT_VEC2:
+		case glu::TYPE_INT_VEC2:
+		case glu::TYPE_UINT_VEC2:
+		case glu::TYPE_BOOL_VEC2:	return 2*sizeof(deUint32);
+
+		case glu::TYPE_FLOAT_VEC3:
+		case glu::TYPE_INT_VEC3:
+		case glu::TYPE_UINT_VEC3:
+		case glu::TYPE_BOOL_VEC3:	// Fall-through to vec4
+
+		case glu::TYPE_FLOAT_VEC4:
+		case glu::TYPE_INT_VEC4:
+		case glu::TYPE_UINT_VEC4:
+		case glu::TYPE_BOOL_VEC4:	return 4*sizeof(deUint32);
+
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+int getDataTypeArrayStride (glu::DataType type)
+{
+	DE_ASSERT(!glu::isDataTypeMatrix(type));
+
+	int baseStride		= getDataTypeByteSize(type);
+	int vec4Alignment	= sizeof(deUint32)*4;
+
+	DE_ASSERT(baseStride <= vec4Alignment);
+	return de::max(baseStride, vec4Alignment); // Really? See rule 4.
+}
+
+static inline int deRoundUp32 (int a, int b)
+{
+	int d = a/b;
+	return d*b == a ? a : (d+1)*b;
+}
+
+int computeStd140BaseAlignment (const VarType& type)
+{
+	const int vec4Alignment = sizeof(deUint32)*4;
+
+	if (type.isBasicType())
+	{
+		glu::DataType basicType = type.getBasicType();
+
+		if (glu::isDataTypeMatrix(basicType))
+		{
+			bool	isRowMajor	= !!(type.getFlags() & LAYOUT_ROW_MAJOR);
+			int		vecSize		= isRowMajor ? glu::getDataTypeMatrixNumColumns(basicType)
+											 : glu::getDataTypeMatrixNumRows(basicType);
+
+			return getDataTypeArrayStride(glu::getDataTypeFloatVec(vecSize));
+		}
+		else
+			return getDataTypeByteAlignment(basicType);
+	}
+	else if (type.isArrayType())
+	{
+		int elemAlignment = computeStd140BaseAlignment(type.getElementType());
+
+		// Round up to alignment of vec4
+		return deRoundUp32(elemAlignment, vec4Alignment);
+	}
+	else
+	{
+		DE_ASSERT(type.isStructType());
+
+		int maxBaseAlignment = 0;
+
+		for (StructType::ConstIterator memberIter = type.getStruct().begin(); memberIter != type.getStruct().end(); memberIter++)
+			maxBaseAlignment = de::max(maxBaseAlignment, computeStd140BaseAlignment(memberIter->getType()));
+
+		return deRoundUp32(maxBaseAlignment, vec4Alignment);
+	}
+}
+
+inline deUint32 mergeLayoutFlags (deUint32 prevFlags, deUint32 newFlags)
+{
+	const deUint32	packingMask		= LAYOUT_PACKED|LAYOUT_SHARED|LAYOUT_STD140;
+	const deUint32	matrixMask		= LAYOUT_ROW_MAJOR|LAYOUT_COLUMN_MAJOR;
+
+	deUint32 mergedFlags = 0;
+
+	mergedFlags |= ((newFlags & packingMask)	? newFlags : prevFlags) & packingMask;
+	mergedFlags |= ((newFlags & matrixMask)		? newFlags : prevFlags) & matrixMask;
+
+	return mergedFlags;
+}
+
+void computeStd140Layout (UniformLayout& layout, int& curOffset, int curBlockNdx, const std::string& curPrefix, const VarType& type, deUint32 layoutFlags)
+{
+	int baseAlignment = computeStd140BaseAlignment(type);
+
+	curOffset = deAlign32(curOffset, baseAlignment);
+
+	if (type.isBasicType())
+	{
+		glu::DataType		basicType	= type.getBasicType();
+		UniformLayoutEntry	entry;
+
+		entry.name			= curPrefix;
+		entry.type			= basicType;
+		entry.size			= 1;
+		entry.arrayStride	= 0;
+		entry.matrixStride	= 0;
+		entry.blockNdx		= curBlockNdx;
+
+		if (glu::isDataTypeMatrix(basicType))
+		{
+			// Array of vectors as specified in rules 5 & 7.
+			bool	isRowMajor	= !!(layoutFlags & LAYOUT_ROW_MAJOR);
+			int		vecSize		= isRowMajor ? glu::getDataTypeMatrixNumColumns(basicType)
+											 : glu::getDataTypeMatrixNumRows(basicType);
+			int		numVecs		= isRowMajor ? glu::getDataTypeMatrixNumRows(basicType)
+											 : glu::getDataTypeMatrixNumColumns(basicType);
+			int		stride		= getDataTypeArrayStride(glu::getDataTypeFloatVec(vecSize));
+
+			entry.offset		= curOffset;
+			entry.matrixStride	= stride;
+			entry.isRowMajor	= isRowMajor;
+
+			curOffset += numVecs*stride;
+		}
+		else
+		{
+			// Scalar or vector.
+			entry.offset = curOffset;
+
+			curOffset += getDataTypeByteSize(basicType);
+		}
+
+		layout.uniforms.push_back(entry);
+	}
+	else if (type.isArrayType())
+	{
+		const VarType&	elemType	= type.getElementType();
+
+		if (elemType.isBasicType() && !glu::isDataTypeMatrix(elemType.getBasicType()))
+		{
+			// Array of scalars or vectors.
+			glu::DataType		elemBasicType	= elemType.getBasicType();
+			UniformLayoutEntry	entry;
+			int					stride			= getDataTypeArrayStride(elemBasicType);
+
+			entry.name			= curPrefix + "[0]"; // Array uniforms are always postfixed with [0]
+			entry.type			= elemBasicType;
+			entry.blockNdx		= curBlockNdx;
+			entry.offset		= curOffset;
+			entry.size			= type.getArraySize();
+			entry.arrayStride	= stride;
+			entry.matrixStride	= 0;
+
+			curOffset += stride*type.getArraySize();
+
+			layout.uniforms.push_back(entry);
+		}
+		else if (elemType.isBasicType() && glu::isDataTypeMatrix(elemType.getBasicType()))
+		{
+			// Array of matrices.
+			glu::DataType		elemBasicType	= elemType.getBasicType();
+			bool				isRowMajor		= !!(layoutFlags & LAYOUT_ROW_MAJOR);
+			int					vecSize			= isRowMajor ? glu::getDataTypeMatrixNumColumns(elemBasicType)
+															 : glu::getDataTypeMatrixNumRows(elemBasicType);
+			int					numVecs			= isRowMajor ? glu::getDataTypeMatrixNumRows(elemBasicType)
+															 : glu::getDataTypeMatrixNumColumns(elemBasicType);
+			int					stride			= getDataTypeArrayStride(glu::getDataTypeFloatVec(vecSize));
+			UniformLayoutEntry	entry;
+
+			entry.name			= curPrefix + "[0]"; // Array uniforms are always postfixed with [0]
+			entry.type			= elemBasicType;
+			entry.blockNdx		= curBlockNdx;
+			entry.offset		= curOffset;
+			entry.size			= type.getArraySize();
+			entry.arrayStride	= stride*numVecs;
+			entry.matrixStride	= stride;
+			entry.isRowMajor	= isRowMajor;
+
+			curOffset += numVecs*type.getArraySize()*stride;
+
+			layout.uniforms.push_back(entry);
+		}
+		else
+		{
+			DE_ASSERT(elemType.isStructType() || elemType.isArrayType());
+
+			for (int elemNdx = 0; elemNdx < type.getArraySize(); elemNdx++)
+				computeStd140Layout(layout, curOffset, curBlockNdx, curPrefix + "[" + de::toString(elemNdx) + "]", type.getElementType(), layoutFlags);
+		}
+	}
+	else
+	{
+		DE_ASSERT(type.isStructType());
+
+		for (StructType::ConstIterator memberIter = type.getStruct().begin(); memberIter != type.getStruct().end(); memberIter++)
+			computeStd140Layout(layout, curOffset, curBlockNdx, curPrefix + "." + memberIter->getName(), memberIter->getType(), layoutFlags);
+
+		curOffset = deAlign32(curOffset, baseAlignment);
+	}
+}
+
+void computeStd140Layout (UniformLayout& layout, const ShaderInterface& interface)
+{
+	// \todo [2012-01-23 pyry] Uniforms in default block.
+
+	int numUniformBlocks = interface.getNumUniformBlocks();
+
+	for (int blockNdx = 0; blockNdx < numUniformBlocks; blockNdx++)
+	{
+		const UniformBlock&	block			= interface.getUniformBlock(blockNdx);
+		bool				hasInstanceName	= block.getInstanceName() != DE_NULL;
+		std::string			blockPrefix		= hasInstanceName ? (std::string(block.getBlockName()) + ".") : std::string("");
+		int					curOffset		= 0;
+		int					activeBlockNdx	= (int)layout.blocks.size();
+		int					firstUniformNdx	= (int)layout.uniforms.size();
+
+		for (UniformBlock::ConstIterator uniformIter = block.begin(); uniformIter != block.end(); uniformIter++)
+		{
+			const Uniform& uniform = *uniformIter;
+			computeStd140Layout(layout, curOffset, activeBlockNdx, blockPrefix + uniform.getName(), uniform.getType(), mergeLayoutFlags(block.getFlags(), uniform.getFlags()));
+		}
+
+		int	uniformIndicesEnd	= (int)layout.uniforms.size();
+		int	blockSize			= curOffset;
+		int	numInstances		= block.isArray() ? block.getArraySize() : 1;
+
+		// Create block layout entries for each instance.
+		for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		{
+			// Allocate entry for instance.
+			layout.blocks.push_back(BlockLayoutEntry());
+			BlockLayoutEntry& blockEntry = layout.blocks.back();
+
+			blockEntry.name = block.getBlockName();
+			blockEntry.size = blockSize;
+
+			// Compute active uniform set for block.
+			for (int uniformNdx = firstUniformNdx; uniformNdx < uniformIndicesEnd; uniformNdx++)
+				blockEntry.activeUniformIndices.push_back(uniformNdx);
+
+			if (block.isArray())
+				blockEntry.name += "[" + de::toString(instanceNdx) + "]";
+		}
+	}
+}
+
+// Value generator.
+
+void generateValue (const UniformLayoutEntry& entry, void* basePtr, de::Random& rnd)
+{
+	glu::DataType	scalarType		= glu::getDataTypeScalarType(entry.type);
+	int				scalarSize		= glu::getDataTypeScalarSize(entry.type);
+	bool			isMatrix		= glu::isDataTypeMatrix(entry.type);
+	int				numVecs			= isMatrix ? (entry.isRowMajor ? glu::getDataTypeMatrixNumRows(entry.type) : glu::getDataTypeMatrixNumColumns(entry.type)) : 1;
+	int				vecSize			= scalarSize / numVecs;
+	bool			isArray			= entry.size > 1;
+	const int		compSize		= sizeof(deUint32);
+
+	DE_ASSERT(scalarSize%numVecs == 0);
+
+	for (int elemNdx = 0; elemNdx < entry.size; elemNdx++)
+	{
+		deUint8* elemPtr = (deUint8*)basePtr + entry.offset + (isArray ? elemNdx*entry.arrayStride : 0);
+
+		for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
+		{
+			deUint8* vecPtr = elemPtr + (isMatrix ? vecNdx*entry.matrixStride : 0);
+
+			for (int compNdx = 0; compNdx < vecSize; compNdx++)
+			{
+				deUint8* compPtr = vecPtr + compSize*compNdx;
+
+				switch (scalarType)
+				{
+					case glu::TYPE_FLOAT:	*((float*)compPtr)		= (float)rnd.getInt(-9, 9);						break;
+					case glu::TYPE_INT:		*((int*)compPtr)		= rnd.getInt(-9, 9);							break;
+					case glu::TYPE_UINT:	*((deUint32*)compPtr)	= (deUint32)rnd.getInt(0, 9);					break;
+					// \note Random bit pattern is used for true values. Spec states that all non-zero values are
+					//       interpreted as true but some implementations fail this.
+					case glu::TYPE_BOOL:	*((deUint32*)compPtr)	= rnd.getBool() ? rnd.getUint32()|1u : 0u;		break;
+					default:
+						DE_ASSERT(false);
+				}
+			}
+		}
+	}
+}
+
+void generateValues (const UniformLayout& layout, const std::map<int, void*>& blockPointers, deUint32 seed)
+{
+	de::Random	rnd			(seed);
+	int			numBlocks	= (int)layout.blocks.size();
+
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		void*	basePtr		= blockPointers.find(blockNdx)->second;
+		int		numEntries	= (int)layout.blocks[blockNdx].activeUniformIndices.size();
+
+		for (int entryNdx = 0; entryNdx < numEntries; entryNdx++)
+		{
+			const UniformLayoutEntry& entry = layout.uniforms[layout.blocks[blockNdx].activeUniformIndices[entryNdx]];
+			generateValue(entry, basePtr, rnd);
+		}
+	}
+}
+
+// Shader generator.
+
+const char* getCompareFuncForType (glu::DataType type)
+{
+	switch (type)
+	{
+		case glu::TYPE_FLOAT:			return "mediump float compare_float    (highp float a, highp float b)  { return abs(a - b) < 0.05 ? 1.0 : 0.0; }\n";
+		case glu::TYPE_FLOAT_VEC2:		return "mediump float compare_vec2     (highp vec2 a, highp vec2 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y); }\n";
+		case glu::TYPE_FLOAT_VEC3:		return "mediump float compare_vec3     (highp vec3 a, highp vec3 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y)*compare_float(a.z, b.z); }\n";
+		case glu::TYPE_FLOAT_VEC4:		return "mediump float compare_vec4     (highp vec4 a, highp vec4 b)    { return compare_float(a.x, b.x)*compare_float(a.y, b.y)*compare_float(a.z, b.z)*compare_float(a.w, b.w); }\n";
+		case glu::TYPE_FLOAT_MAT2:		return "mediump float compare_mat2     (highp mat2 a, highp mat2 b)    { return compare_vec2(a[0], b[0])*compare_vec2(a[1], b[1]); }\n";
+		case glu::TYPE_FLOAT_MAT2X3:	return "mediump float compare_mat2x3   (highp mat2x3 a, highp mat2x3 b){ return compare_vec3(a[0], b[0])*compare_vec3(a[1], b[1]); }\n";
+		case glu::TYPE_FLOAT_MAT2X4:	return "mediump float compare_mat2x4   (highp mat2x4 a, highp mat2x4 b){ return compare_vec4(a[0], b[0])*compare_vec4(a[1], b[1]); }\n";
+		case glu::TYPE_FLOAT_MAT3X2:	return "mediump float compare_mat3x2   (highp mat3x2 a, highp mat3x2 b){ return compare_vec2(a[0], b[0])*compare_vec2(a[1], b[1])*compare_vec2(a[2], b[2]); }\n";
+		case glu::TYPE_FLOAT_MAT3:		return "mediump float compare_mat3     (highp mat3 a, highp mat3 b)    { return compare_vec3(a[0], b[0])*compare_vec3(a[1], b[1])*compare_vec3(a[2], b[2]); }\n";
+		case glu::TYPE_FLOAT_MAT3X4:	return "mediump float compare_mat3x4   (highp mat3x4 a, highp mat3x4 b){ return compare_vec4(a[0], b[0])*compare_vec4(a[1], b[1])*compare_vec4(a[2], b[2]); }\n";
+		case glu::TYPE_FLOAT_MAT4X2:	return "mediump float compare_mat4x2   (highp mat4x2 a, highp mat4x2 b){ return compare_vec2(a[0], b[0])*compare_vec2(a[1], b[1])*compare_vec2(a[2], b[2])*compare_vec2(a[3], b[3]); }\n";
+		case glu::TYPE_FLOAT_MAT4X3:	return "mediump float compare_mat4x3   (highp mat4x3 a, highp mat4x3 b){ return compare_vec3(a[0], b[0])*compare_vec3(a[1], b[1])*compare_vec3(a[2], b[2])*compare_vec3(a[3], b[3]); }\n";
+		case glu::TYPE_FLOAT_MAT4:		return "mediump float compare_mat4     (highp mat4 a, highp mat4 b)    { return compare_vec4(a[0], b[0])*compare_vec4(a[1], b[1])*compare_vec4(a[2], b[2])*compare_vec4(a[3], b[3]); }\n";
+		case glu::TYPE_INT:				return "mediump float compare_int      (highp int a, highp int b)      { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_INT_VEC2:		return "mediump float compare_ivec2    (highp ivec2 a, highp ivec2 b)  { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_INT_VEC3:		return "mediump float compare_ivec3    (highp ivec3 a, highp ivec3 b)  { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_INT_VEC4:		return "mediump float compare_ivec4    (highp ivec4 a, highp ivec4 b)  { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_UINT:			return "mediump float compare_uint     (highp uint a, highp uint b)    { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_UINT_VEC2:		return "mediump float compare_uvec2    (highp uvec2 a, highp uvec2 b)  { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_UINT_VEC3:		return "mediump float compare_uvec3    (highp uvec3 a, highp uvec3 b)  { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_UINT_VEC4:		return "mediump float compare_uvec4    (highp uvec4 a, highp uvec4 b)  { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_BOOL:			return "mediump float compare_bool     (bool a, bool b)                { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_BOOL_VEC2:		return "mediump float compare_bvec2    (bvec2 a, bvec2 b)              { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_BOOL_VEC3:		return "mediump float compare_bvec3    (bvec3 a, bvec3 b)              { return a == b ? 1.0 : 0.0; }\n";
+		case glu::TYPE_BOOL_VEC4:		return "mediump float compare_bvec4    (bvec4 a, bvec4 b)              { return a == b ? 1.0 : 0.0; }\n";
+		default:
+			DE_ASSERT(false);
+			return DE_NULL;
+	}
+}
+
+void getCompareDependencies (std::set<glu::DataType>& compareFuncs, glu::DataType basicType)
+{
+	switch (basicType)
+	{
+		case glu::TYPE_FLOAT_VEC2:
+		case glu::TYPE_FLOAT_VEC3:
+		case glu::TYPE_FLOAT_VEC4:
+			compareFuncs.insert(glu::TYPE_FLOAT);
+			compareFuncs.insert(basicType);
+			break;
+
+		case glu::TYPE_FLOAT_MAT2:
+		case glu::TYPE_FLOAT_MAT2X3:
+		case glu::TYPE_FLOAT_MAT2X4:
+		case glu::TYPE_FLOAT_MAT3X2:
+		case glu::TYPE_FLOAT_MAT3:
+		case glu::TYPE_FLOAT_MAT3X4:
+		case glu::TYPE_FLOAT_MAT4X2:
+		case glu::TYPE_FLOAT_MAT4X3:
+		case glu::TYPE_FLOAT_MAT4:
+			compareFuncs.insert(glu::TYPE_FLOAT);
+			compareFuncs.insert(glu::getDataTypeFloatVec(glu::getDataTypeMatrixNumRows(basicType)));
+			compareFuncs.insert(basicType);
+			break;
+
+		default:
+			compareFuncs.insert(basicType);
+			break;
+	}
+}
+
+void collectUniqueBasicTypes (std::set<glu::DataType>& basicTypes, const VarType& type)
+{
+	if (type.isStructType())
+	{
+		for (StructType::ConstIterator iter = type.getStruct().begin(); iter != type.getStruct().end(); ++iter)
+			collectUniqueBasicTypes(basicTypes, iter->getType());
+	}
+	else if (type.isArrayType())
+		collectUniqueBasicTypes(basicTypes, type.getElementType());
+	else
+	{
+		DE_ASSERT(type.isBasicType());
+		basicTypes.insert(type.getBasicType());
+	}
+}
+
+void collectUniqueBasicTypes (std::set<glu::DataType>& basicTypes, const UniformBlock& uniformBlock)
+{
+	for (UniformBlock::ConstIterator iter = uniformBlock.begin(); iter != uniformBlock.end(); ++iter)
+		collectUniqueBasicTypes(basicTypes, iter->getType());
+}
+
+void collectUniqueBasicTypes (std::set<glu::DataType>& basicTypes, const ShaderInterface& interface)
+{
+	for (int ndx = 0; ndx < interface.getNumUniformBlocks(); ++ndx)
+		collectUniqueBasicTypes(basicTypes, interface.getUniformBlock(ndx));
+}
+
+void generateCompareFuncs (std::ostream& str, const ShaderInterface& interface)
+{
+	std::set<glu::DataType> types;
+	std::set<glu::DataType> compareFuncs;
+
+	// Collect unique basic types
+	collectUniqueBasicTypes(types, interface);
+
+	// Set of compare functions required
+	for (std::set<glu::DataType>::const_iterator iter = types.begin(); iter != types.end(); ++iter)
+	{
+		getCompareDependencies(compareFuncs, *iter);
+	}
+
+	for (int type = 0; type < glu::TYPE_LAST; ++type)
+	{
+		if (compareFuncs.find(glu::DataType(type)) != compareFuncs.end())
+			str << getCompareFuncForType(glu::DataType(type));
+	}
+}
+
+struct Indent
+{
+	int level;
+	Indent (int level_) : level(level_) {}
+};
+
+std::ostream& operator<< (std::ostream& str, const Indent& indent)
+{
+	for (int i = 0; i < indent.level; i++)
+		str << "\t";
+	return str;
+}
+
+void		generateDeclaration			(std::ostringstream& src, const VarType& type, const char* name, int indentLevel, deUint32 unusedHints);
+void		generateDeclaration			(std::ostringstream& src, const Uniform& uniform, int indentLevel);
+void		generateDeclaration			(std::ostringstream& src, const StructType& structType, int indentLevel);
+
+void		generateLocalDeclaration	(std::ostringstream& src, const StructType& structType, int indentLevel);
+void		generateFullDeclaration		(std::ostringstream& src, const StructType& structType, int indentLevel);
+
+void generateDeclaration (std::ostringstream& src, const StructType& structType, int indentLevel)
+{
+	DE_ASSERT(structType.getTypeName() != DE_NULL);
+	generateFullDeclaration(src, structType, indentLevel);
+	src << ";\n";
+}
+
+void generateFullDeclaration (std::ostringstream& src, const StructType& structType, int indentLevel)
+{
+	src << "struct";
+	if (structType.getTypeName())
+		src << " " << structType.getTypeName();
+	src << "\n" << Indent(indentLevel) << "{\n";
+
+	for (StructType::ConstIterator memberIter = structType.begin(); memberIter != structType.end(); memberIter++)
+	{
+		src << Indent(indentLevel+1);
+		generateDeclaration(src, memberIter->getType(), memberIter->getName(), indentLevel+1, memberIter->getFlags() & UNUSED_BOTH);
+	}
+
+	src << Indent(indentLevel) << "}";
+}
+
+void generateLocalDeclaration (std::ostringstream& src, const StructType& structType, int indentLevel)
+{
+	if (structType.getTypeName() == DE_NULL)
+		generateFullDeclaration(src, structType, indentLevel);
+	else
+		src << structType.getTypeName();
+}
+
+void generateDeclaration (std::ostringstream& src, const VarType& type, const char* name, int indentLevel, deUint32 unusedHints)
+{
+	deUint32 flags = type.getFlags();
+
+	if ((flags & LAYOUT_MASK) != 0)
+		src << "layout(" << LayoutFlagsFmt(flags & LAYOUT_MASK) << ") ";
+
+	if ((flags & PRECISION_MASK) != 0)
+		src << PrecisionFlagsFmt(flags & PRECISION_MASK) << " ";
+
+	if (type.isBasicType())
+		src << glu::getDataTypeName(type.getBasicType()) << " " << name;
+	else if (type.isArrayType())
+	{
+		std::vector<int>	arraySizes;
+		const VarType*		curType		= &type;
+		while (curType->isArrayType())
+		{
+			arraySizes.push_back(curType->getArraySize());
+			curType = &curType->getElementType();
+		}
+
+		if (curType->isBasicType())
+		{
+			if ((curType->getFlags() & PRECISION_MASK) != 0)
+				src << PrecisionFlagsFmt(curType->getFlags() & PRECISION_MASK) << " ";
+			src << glu::getDataTypeName(curType->getBasicType());
+		}
+		else
+		{
+			DE_ASSERT(curType->isStructType());
+			generateLocalDeclaration(src, curType->getStruct(), indentLevel+1);
+		}
+
+		src << " " << name;
+
+		for (std::vector<int>::const_iterator sizeIter = arraySizes.begin(); sizeIter != arraySizes.end(); sizeIter++)
+			src << "[" << *sizeIter << "]";
+	}
+	else
+	{
+		generateLocalDeclaration(src, type.getStruct(), indentLevel+1);
+		src << " " << name;
+	}
+
+	src << ";";
+
+	// Print out unused hints.
+	if (unusedHints != 0)
+		src << " // unused in " << (unusedHints == UNUSED_BOTH		? "both shaders"	:
+									unusedHints == UNUSED_VERTEX	? "vertex shader"	:
+									unusedHints == UNUSED_FRAGMENT	? "fragment shader" : "???");
+
+	src << "\n";
+}
+
+void generateDeclaration (std::ostringstream& src, const Uniform& uniform, int indentLevel)
+{
+	if ((uniform.getFlags() & LAYOUT_MASK) != 0)
+		src << "layout(" << LayoutFlagsFmt(uniform.getFlags() & LAYOUT_MASK) << ") ";
+
+	generateDeclaration(src, uniform.getType(), uniform.getName(), indentLevel, uniform.getFlags() & UNUSED_BOTH);
+}
+
+void generateDeclaration (std::ostringstream& src, const UniformBlock& block)
+{
+	if ((block.getFlags() & LAYOUT_MASK) != 0)
+		src << "layout(" << LayoutFlagsFmt(block.getFlags() & LAYOUT_MASK) << ") ";
+
+	src << "uniform " << block.getBlockName();
+	src << "\n{\n";
+
+	for (UniformBlock::ConstIterator uniformIter = block.begin(); uniformIter != block.end(); uniformIter++)
+	{
+		src << Indent(1);
+		generateDeclaration(src, *uniformIter, 1 /* indent level */);
+	}
+
+	src << "}";
+
+	if (block.getInstanceName() != DE_NULL)
+	{
+		src << " " << block.getInstanceName();
+		if (block.isArray())
+			src << "[" << block.getArraySize() << "]";
+	}
+	else
+		DE_ASSERT(!block.isArray());
+
+	src << ";\n";
+}
+
+void generateValueSrc (std::ostringstream& src, const UniformLayoutEntry& entry, const void* basePtr, int elementNdx)
+{
+	glu::DataType	scalarType		= glu::getDataTypeScalarType(entry.type);
+	int				scalarSize		= glu::getDataTypeScalarSize(entry.type);
+	bool			isArray			= entry.size > 1;
+	const deUint8*	elemPtr			= (const deUint8*)basePtr + entry.offset + (isArray ? elementNdx*entry.arrayStride : 0);
+	const int		compSize		= sizeof(deUint32);
+
+	if (scalarSize > 1)
+		src << glu::getDataTypeName(entry.type) << "(";
+
+	if (glu::isDataTypeMatrix(entry.type))
+	{
+		int	numRows	= glu::getDataTypeMatrixNumRows(entry.type);
+		int	numCols	= glu::getDataTypeMatrixNumColumns(entry.type);
+
+		DE_ASSERT(scalarType == glu::TYPE_FLOAT);
+
+		// Constructed in column-wise order.
+		for (int colNdx = 0; colNdx < numCols; colNdx++)
+		{
+			for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
+			{
+				const deUint8*	compPtr	= elemPtr + (entry.isRowMajor ? rowNdx*entry.matrixStride + colNdx*compSize
+																	  : colNdx*entry.matrixStride + rowNdx*compSize);
+
+				if (colNdx > 0 || rowNdx > 0)
+					src << ", ";
+
+				src << de::floatToString(*((const float*)compPtr), 1);
+			}
+		}
+	}
+	else
+	{
+		for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
+		{
+			const deUint8* compPtr = elemPtr + scalarNdx*compSize;
+
+			if (scalarNdx > 0)
+				src << ", ";
+
+			switch (scalarType)
+			{
+				case glu::TYPE_FLOAT:	src << de::floatToString(*((const float*)compPtr), 1);			break;
+				case glu::TYPE_INT:		src << *((const int*)compPtr);									break;
+				case glu::TYPE_UINT:	src << *((const deUint32*)compPtr) << "u";						break;
+				case glu::TYPE_BOOL:	src << (*((const deUint32*)compPtr) != 0u ? "true" : "false");	break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+	}
+
+	if (scalarSize > 1)
+		src << ")";
+}
+
+void generateCompareSrc (std::ostringstream& src, const char* resultVar, const VarType& type, const char* srcName, const char* apiName, const UniformLayout& layout, const void* basePtr, deUint32 unusedMask)
+{
+	if (type.isBasicType() || (type.isArrayType() && type.getElementType().isBasicType()))
+	{
+		// Basic type or array of basic types.
+		bool						isArray			= type.isArrayType();
+		glu::DataType				elementType		= isArray ? type.getElementType().getBasicType() : type.getBasicType();
+		const char*					typeName		= glu::getDataTypeName(elementType);
+		std::string					fullApiName		= string(apiName) + (isArray ? "[0]" : ""); // Arrays are always postfixed with [0]
+		int							uniformNdx		= layout.getUniformIndex(fullApiName.c_str());
+		const UniformLayoutEntry&	entry			= layout.uniforms[uniformNdx];
+
+		if (isArray)
+		{
+			for (int elemNdx = 0; elemNdx < type.getArraySize(); elemNdx++)
+			{
+				src << "\tresult *= compare_" << typeName << "(" << srcName << "[" << elemNdx << "], ";
+				generateValueSrc(src, entry, basePtr, elemNdx);
+				src << ");\n";
+			}
+		}
+		else
+		{
+			src << "\tresult *= compare_" << typeName << "(" << srcName << ", ";
+			generateValueSrc(src, entry, basePtr, 0);
+			src << ");\n";
+		}
+	}
+	else if (type.isArrayType())
+	{
+		const VarType& elementType = type.getElementType();
+
+		for (int elementNdx = 0; elementNdx < type.getArraySize(); elementNdx++)
+		{
+			std::string op = string("[") + de::toString(elementNdx) + "]";
+			generateCompareSrc(src, resultVar, elementType, (string(srcName) + op).c_str(), (string(apiName) + op).c_str(), layout, basePtr, unusedMask);
+		}
+	}
+	else
+	{
+		DE_ASSERT(type.isStructType());
+
+		for (StructType::ConstIterator memberIter = type.getStruct().begin(); memberIter != type.getStruct().end(); memberIter++)
+		{
+			if (memberIter->getFlags() & unusedMask)
+				continue; // Skip member.
+
+			string op = string(".") + memberIter->getName();
+			generateCompareSrc(src, resultVar, memberIter->getType(), (string(srcName) + op).c_str(), (string(apiName) + op).c_str(), layout, basePtr, unusedMask);
+		}
+	}
+}
+
+void generateCompareSrc (std::ostringstream& src, const char* resultVar, const ShaderInterface& interface, const UniformLayout& layout, const std::map<int, void*>& blockPointers, bool isVertex)
+{
+	deUint32 unusedMask = isVertex ? UNUSED_VERTEX : UNUSED_FRAGMENT;
+
+	for (int blockNdx = 0; blockNdx < interface.getNumUniformBlocks(); blockNdx++)
+	{
+		const UniformBlock& block = interface.getUniformBlock(blockNdx);
+
+		if ((block.getFlags() & (isVertex ? DECLARE_VERTEX : DECLARE_FRAGMENT)) == 0)
+			continue; // Skip.
+
+		bool			hasInstanceName	= block.getInstanceName() != DE_NULL;
+		bool			isArray			= block.isArray();
+		int				numInstances	= isArray ? block.getArraySize() : 1;
+		std::string		apiPrefix		= hasInstanceName ? string(block.getBlockName()) + "." : string("");
+
+		DE_ASSERT(!isArray || hasInstanceName);
+
+		for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		{
+			std::string		instancePostfix		= isArray ? string("[") + de::toString(instanceNdx) + "]" : string("");
+			std::string		blockInstanceName	= block.getBlockName() + instancePostfix;
+			std::string		srcPrefix			= hasInstanceName ? string(block.getInstanceName()) + instancePostfix + "." : string("");
+			int				activeBlockNdx		= layout.getBlockIndex(blockInstanceName.c_str());
+			void*			basePtr				= blockPointers.find(activeBlockNdx)->second;
+
+			for (UniformBlock::ConstIterator uniformIter = block.begin(); uniformIter != block.end(); uniformIter++)
+			{
+				const Uniform& uniform = *uniformIter;
+
+				if (uniform.getFlags() & unusedMask)
+					continue; // Don't read from that uniform.
+
+				generateCompareSrc(src, resultVar, uniform.getType(), (srcPrefix + uniform.getName()).c_str(), (apiPrefix + uniform.getName()).c_str(), layout, basePtr, unusedMask);
+			}
+		}
+	}
+}
+
+void generateVertexShader (std::ostringstream& src, glu::GLSLVersion glslVersion, const ShaderInterface& interface, const UniformLayout& layout, const std::map<int, void*>& blockPointers)
+{
+	DE_ASSERT(isSupportedGLSLVersion(glslVersion));
+
+	src << glu::getGLSLVersionDeclaration(glslVersion) << "\n";
+	src << "in highp vec4 a_position;\n";
+	src << "out mediump float v_vtxResult;\n";
+	src << "\n";
+
+	std::vector<const StructType*> namedStructs;
+	interface.getNamedStructs(namedStructs);
+	for (std::vector<const StructType*>::const_iterator structIter = namedStructs.begin(); structIter != namedStructs.end(); structIter++)
+		generateDeclaration(src, **structIter, 0);
+
+	for (int blockNdx = 0; blockNdx < interface.getNumUniformBlocks(); blockNdx++)
+	{
+		const UniformBlock& block = interface.getUniformBlock(blockNdx);
+		if (block.getFlags() & DECLARE_VERTEX)
+			generateDeclaration(src, block);
+	}
+
+	// Comparison utilities.
+	src << "\n";
+	generateCompareFuncs(src, interface);
+
+	src << "\n"
+		   "void main (void)\n"
+		   "{\n"
+		   "	gl_Position = a_position;\n"
+		   "	mediump float result = 1.0;\n";
+
+	// Value compare.
+	generateCompareSrc(src, "result", interface, layout, blockPointers, true);
+
+	src << "	v_vtxResult = result;\n"
+		   "}\n";
+}
+
+void generateFragmentShader (std::ostringstream& src, glu::GLSLVersion glslVersion, const ShaderInterface& interface, const UniformLayout& layout, const std::map<int, void*>& blockPointers)
+{
+	DE_ASSERT(isSupportedGLSLVersion(glslVersion));
+
+	src << glu::getGLSLVersionDeclaration(glslVersion) << "\n";
+	src << "in mediump float v_vtxResult;\n";
+	src << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+	src << "\n";
+
+	std::vector<const StructType*> namedStructs;
+	interface.getNamedStructs(namedStructs);
+	for (std::vector<const StructType*>::const_iterator structIter = namedStructs.begin(); structIter != namedStructs.end(); structIter++)
+		generateDeclaration(src, **structIter, 0);
+
+	for (int blockNdx = 0; blockNdx < interface.getNumUniformBlocks(); blockNdx++)
+	{
+		const UniformBlock& block = interface.getUniformBlock(blockNdx);
+		if (block.getFlags() & DECLARE_FRAGMENT)
+			generateDeclaration(src, block);
+	}
+
+	// Comparison utilities.
+	src << "\n";
+	generateCompareFuncs(src, interface);
+
+	src << "\n"
+		   "void main (void)\n"
+		   "{\n"
+		   "	mediump float result = 1.0;\n";
+
+	// Value compare.
+	generateCompareSrc(src, "result", interface, layout, blockPointers, false);
+
+	src << "	dEQP_FragColor = vec4(1.0, v_vtxResult, result, 1.0);\n"
+		   "}\n";
+}
+
+void getGLUniformLayout (const glw::Functions& gl, UniformLayout& layout, deUint32 program)
+{
+	int		numActiveUniforms	= 0;
+	int		numActiveBlocks		= 0;
+
+	gl.getProgramiv(program, GL_ACTIVE_UNIFORMS,		&numActiveUniforms);
+	gl.getProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS,	&numActiveBlocks);
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get number of uniforms and uniform blocks");
+
+	// Block entries.
+	layout.blocks.resize(numActiveBlocks);
+	for (int blockNdx = 0; blockNdx < numActiveBlocks; blockNdx++)
+	{
+		BlockLayoutEntry&	entry				= layout.blocks[blockNdx];
+		int					size;
+		int					nameLen;
+		int					numBlockUniforms;
+
+		gl.getActiveUniformBlockiv(program, (deUint32)blockNdx, GL_UNIFORM_BLOCK_DATA_SIZE,			&size);
+		gl.getActiveUniformBlockiv(program, (deUint32)blockNdx, GL_UNIFORM_BLOCK_NAME_LENGTH,		&nameLen);
+		gl.getActiveUniformBlockiv(program, (deUint32)blockNdx, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS,	&numBlockUniforms);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform block query failed");
+
+		// \note Some implementations incorrectly return 0 as name length even though the length should include null terminator.
+		std::vector<char> nameBuf(nameLen > 0 ? nameLen : 1);
+		gl.getActiveUniformBlockName(program, (deUint32)blockNdx, (glw::GLsizei)nameBuf.size(), DE_NULL, &nameBuf[0]);
+
+		entry.name	= std::string(&nameBuf[0]);
+		entry.size	= size;
+		entry.activeUniformIndices.resize(numBlockUniforms);
+
+		if (numBlockUniforms > 0)
+			gl.getActiveUniformBlockiv(program, (deUint32)blockNdx, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, &entry.activeUniformIndices[0]);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform block query failed");
+	}
+
+	if (numActiveUniforms > 0)
+	{
+		// Uniform entries.
+		std::vector<deUint32> uniformIndices(numActiveUniforms);
+		for (int i = 0; i < numActiveUniforms; i++)
+			uniformIndices[i] = (deUint32)i;
+
+		std::vector<int>		types			(numActiveUniforms);
+		std::vector<int>		sizes			(numActiveUniforms);
+		std::vector<int>		nameLengths		(numActiveUniforms);
+		std::vector<int>		blockIndices	(numActiveUniforms);
+		std::vector<int>		offsets			(numActiveUniforms);
+		std::vector<int>		arrayStrides	(numActiveUniforms);
+		std::vector<int>		matrixStrides	(numActiveUniforms);
+		std::vector<int>		rowMajorFlags	(numActiveUniforms);
+
+		// Execute queries.
+		gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_TYPE,			&types[0]);
+		gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_SIZE,			&sizes[0]);
+		gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_NAME_LENGTH,	&nameLengths[0]);
+		gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_BLOCK_INDEX,	&blockIndices[0]);
+		gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_OFFSET,			&offsets[0]);
+		gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_ARRAY_STRIDE,	&arrayStrides[0]);
+		gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_MATRIX_STRIDE,	&matrixStrides[0]);
+		gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_IS_ROW_MAJOR,	&rowMajorFlags[0]);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Active uniform query failed");
+
+		// Translate to LayoutEntries
+		layout.uniforms.resize(numActiveUniforms);
+		for (int uniformNdx = 0; uniformNdx < numActiveUniforms; uniformNdx++)
+		{
+			UniformLayoutEntry&	entry		= layout.uniforms[uniformNdx];
+			std::vector<char>	nameBuf		(nameLengths[uniformNdx]);
+			glw::GLsizei		nameLen		= 0;
+			int					size		= 0;
+			deUint32			type		= GL_NONE;
+
+			gl.getActiveUniform(program, (deUint32)uniformNdx, (glw::GLsizei)nameBuf.size(), &nameLen, &size, &type, &nameBuf[0]);
+
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform name query failed");
+
+			// \note glGetActiveUniform() returns length without \0 and glGetActiveUniformsiv() with \0
+			if (nameLen+1	!= nameLengths[uniformNdx]	||
+				size		!= sizes[uniformNdx]		||
+				type		!= (deUint32)types[uniformNdx])
+				TCU_FAIL("Values returned by glGetActiveUniform() don't match with values queried with glGetActiveUniformsiv().");
+
+			entry.name			= std::string(&nameBuf[0]);
+			entry.type			= glu::getDataTypeFromGLType(types[uniformNdx]);
+			entry.size			= sizes[uniformNdx];
+			entry.blockNdx		= blockIndices[uniformNdx];
+			entry.offset		= offsets[uniformNdx];
+			entry.arrayStride	= arrayStrides[uniformNdx];
+			entry.matrixStride	= matrixStrides[uniformNdx];
+			entry.isRowMajor	= rowMajorFlags[uniformNdx] != GL_FALSE;
+		}
+	}
+}
+
+void copyUniformData (const UniformLayoutEntry& dstEntry, void* dstBlockPtr, const UniformLayoutEntry& srcEntry, const void* srcBlockPtr)
+{
+	deUint8*					dstBasePtr	= (deUint8*)dstBlockPtr + dstEntry.offset;
+	const deUint8*				srcBasePtr	= (const deUint8*)srcBlockPtr + srcEntry.offset;
+
+	DE_ASSERT(dstEntry.size <= srcEntry.size);
+	DE_ASSERT(dstEntry.type == srcEntry.type);
+
+	int							scalarSize	= glu::getDataTypeScalarSize(dstEntry.type);
+	bool						isMatrix	= glu::isDataTypeMatrix(dstEntry.type);
+	const int					compSize	= sizeof(deUint32);
+
+	for (int elementNdx = 0; elementNdx < dstEntry.size; elementNdx++)
+	{
+		deUint8*		dstElemPtr	= dstBasePtr + elementNdx*dstEntry.arrayStride;
+		const deUint8*	srcElemPtr	= srcBasePtr + elementNdx*srcEntry.arrayStride;
+
+		if (isMatrix)
+		{
+			int	numRows	= glu::getDataTypeMatrixNumRows(dstEntry.type);
+			int	numCols	= glu::getDataTypeMatrixNumColumns(dstEntry.type);
+
+			for (int colNdx = 0; colNdx < numCols; colNdx++)
+			{
+				for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
+				{
+					deUint8*		dstCompPtr	= dstElemPtr + (dstEntry.isRowMajor ? rowNdx*dstEntry.matrixStride + colNdx*compSize
+																					: colNdx*dstEntry.matrixStride + rowNdx*compSize);
+					const deUint8*	srcCompPtr	= srcElemPtr + (srcEntry.isRowMajor ? rowNdx*srcEntry.matrixStride + colNdx*compSize
+																					: colNdx*srcEntry.matrixStride + rowNdx*compSize);
+					deMemcpy(dstCompPtr, srcCompPtr, compSize);
+				}
+			}
+		}
+		else
+			deMemcpy(dstElemPtr, srcElemPtr, scalarSize*compSize);
+	}
+}
+
+void copyUniformData (const UniformLayout& dstLayout, const std::map<int, void*>& dstBlockPointers, const UniformLayout& srcLayout, const std::map<int, void*>& srcBlockPointers)
+{
+	// \note Src layout is used as reference in case of activeUniforms happens to be incorrect in dstLayout blocks.
+	int numBlocks = (int)srcLayout.blocks.size();
+
+	for (int srcBlockNdx = 0; srcBlockNdx < numBlocks; srcBlockNdx++)
+	{
+		const BlockLayoutEntry&		srcBlock	= srcLayout.blocks[srcBlockNdx];
+		const void*					srcBlockPtr	= srcBlockPointers.find(srcBlockNdx)->second;
+		int							dstBlockNdx	= dstLayout.getBlockIndex(srcBlock.name.c_str());
+		void*						dstBlockPtr	= dstBlockNdx >= 0 ? dstBlockPointers.find(dstBlockNdx)->second : DE_NULL;
+
+		if (dstBlockNdx < 0)
+			continue;
+
+		for (vector<int>::const_iterator srcUniformNdxIter = srcBlock.activeUniformIndices.begin(); srcUniformNdxIter != srcBlock.activeUniformIndices.end(); srcUniformNdxIter++)
+		{
+			const UniformLayoutEntry&	srcEntry		= srcLayout.uniforms[*srcUniformNdxIter];
+			int							dstUniformNdx	= dstLayout.getUniformIndex(srcEntry.name.c_str());
+
+			if (dstUniformNdx < 0)
+				continue;
+
+			copyUniformData(dstLayout.uniforms[dstUniformNdx], dstBlockPtr, srcEntry, srcBlockPtr);
+		}
+	}
+}
+
+} // anonymous (utilities)
+
+class UniformBufferManager
+{
+public:
+								UniformBufferManager	(const glu::RenderContext& renderCtx);
+								~UniformBufferManager	(void);
+
+	deUint32					allocBuffer				(void);
+
+private:
+								UniformBufferManager	(const UniformBufferManager& other);
+	UniformBufferManager&		operator=				(const UniformBufferManager& other);
+
+	const glu::RenderContext&	m_renderCtx;
+	std::vector<deUint32>		m_buffers;
+};
+
+UniformBufferManager::UniformBufferManager (const glu::RenderContext& renderCtx)
+	: m_renderCtx(renderCtx)
+{
+}
+
+UniformBufferManager::~UniformBufferManager (void)
+{
+	if (!m_buffers.empty())
+		m_renderCtx.getFunctions().deleteBuffers((glw::GLsizei)m_buffers.size(), &m_buffers[0]);
+}
+
+deUint32 UniformBufferManager::allocBuffer (void)
+{
+	deUint32 buf = 0;
+
+	m_buffers.reserve(m_buffers.size()+1);
+	m_renderCtx.getFunctions().genBuffers(1, &buf);
+	GLU_EXPECT_NO_ERROR(m_renderCtx.getFunctions().getError(), "Failed to allocate uniform buffer");
+	m_buffers.push_back(buf);
+
+	return buf;
+}
+
+} // ub
+
+using namespace ub;
+
+// UniformBlockCase.
+
+UniformBlockCase::UniformBlockCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, glu::GLSLVersion glslVersion, BufferMode bufferMode)
+	: TestCase		(testCtx, name, description)
+	, m_renderCtx	(renderCtx)
+	, m_glslVersion	(glslVersion)
+	, m_bufferMode	(bufferMode)
+{
+	TCU_CHECK_INTERNAL(isSupportedGLSLVersion(glslVersion));
+}
+
+UniformBlockCase::~UniformBlockCase (void)
+{
+}
+
+UniformBlockCase::IterateResult UniformBlockCase::iterate (void)
+{
+	TestLog&				log				= m_testCtx.getLog();
+	const glw::Functions&	gl				= m_renderCtx.getFunctions();
+	UniformLayout			refLayout;		//!< std140 layout.
+	vector<deUint8>			data;			//!< Data.
+	map<int, void*>			blockPointers;	//!< Reference block pointers.
+
+	// Initialize result to pass.
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+	// Compute reference layout.
+	computeStd140Layout(refLayout, m_interface);
+
+	// Assign storage for reference values.
+	{
+		int totalSize = 0;
+		for (vector<BlockLayoutEntry>::const_iterator blockIter = refLayout.blocks.begin(); blockIter != refLayout.blocks.end(); blockIter++)
+			totalSize += blockIter->size;
+		data.resize(totalSize);
+
+		// Pointers for each block.
+		int curOffset = 0;
+		for (int blockNdx = 0; blockNdx < (int)refLayout.blocks.size(); blockNdx++)
+		{
+			blockPointers[blockNdx] = &data[0] + curOffset;
+			curOffset += refLayout.blocks[blockNdx].size;
+		}
+	}
+
+	// Generate values.
+	generateValues(refLayout, blockPointers, 1 /* seed */);
+
+	// Generate shaders and build program.
+	std::ostringstream vtxSrc;
+	std::ostringstream fragSrc;
+
+	generateVertexShader(vtxSrc, m_glslVersion, m_interface, refLayout, blockPointers);
+	generateFragmentShader(fragSrc, m_glslVersion, m_interface, refLayout, blockPointers);
+
+	glu::ShaderProgram program(m_renderCtx, glu::makeVtxFragSources(vtxSrc.str(), fragSrc.str()));
+	log << program;
+
+	if (!program.isOk())
+	{
+		// Compile failed.
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
+		return STOP;
+	}
+
+	// Query layout from GL.
+	UniformLayout glLayout;
+	getGLUniformLayout(gl, glLayout, program.getProgram());
+
+	// Print layout to log.
+	log << TestLog::Section("ActiveUniformBlocks", "Active Uniform Blocks");
+	for (int blockNdx = 0; blockNdx < (int)glLayout.blocks.size(); blockNdx++)
+		log << TestLog::Message << blockNdx << ": " << glLayout.blocks[blockNdx] << TestLog::EndMessage;
+	log << TestLog::EndSection;
+
+	log << TestLog::Section("ActiveUniforms", "Active Uniforms");
+	for (int uniformNdx = 0; uniformNdx < (int)glLayout.uniforms.size(); uniformNdx++)
+		log << TestLog::Message << uniformNdx << ": " << glLayout.uniforms[uniformNdx] << TestLog::EndMessage;
+	log << TestLog::EndSection;
+
+	// Check that we can even try rendering with given layout.
+	if (!checkLayoutIndices(glLayout) || !checkLayoutBounds(glLayout) || !compareTypes(refLayout, glLayout))
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid layout");
+		return STOP; // It is not safe to use the given layout.
+	}
+
+	// Verify all std140 blocks.
+	if (!compareStd140Blocks(refLayout, glLayout))
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid std140 layout");
+
+	// Verify all shared blocks - all uniforms should be active, and certain properties match.
+	if (!compareSharedBlocks(refLayout, glLayout))
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid shared layout");
+
+	// Check consistency with index queries
+	if (!checkIndexQueries(program.getProgram(), glLayout))
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Inconsintent block index query results");
+
+	// Use program.
+	gl.useProgram(program.getProgram());
+
+	// Assign binding points to all active uniform blocks.
+	for (int blockNdx = 0; blockNdx < (int)glLayout.blocks.size(); blockNdx++)
+	{
+		deUint32 binding = (deUint32)blockNdx; // \todo [2012-01-25 pyry] Randomize order?
+		gl.uniformBlockBinding(program.getProgram(), (deUint32)blockNdx, binding);
+	}
+
+	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set uniform block bindings");
+
+	// Allocate buffers, write data and bind to targets.
+	UniformBufferManager bufferManager(m_renderCtx);
+	if (m_bufferMode == BUFFERMODE_PER_BLOCK)
+	{
+		int							numBlocks			= (int)glLayout.blocks.size();
+		vector<vector<deUint8> >	glData				(numBlocks);
+		map<int, void*>				glBlockPointers;
+
+		for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+		{
+			glData[blockNdx].resize(glLayout.blocks[blockNdx].size);
+			glBlockPointers[blockNdx] = &glData[blockNdx][0];
+		}
+
+		copyUniformData(glLayout, glBlockPointers, refLayout, blockPointers);
+
+		for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+		{
+			deUint32	buffer	= bufferManager.allocBuffer();
+			deUint32	binding	= (deUint32)blockNdx;
+
+			gl.bindBuffer(GL_UNIFORM_BUFFER, buffer);
+			gl.bufferData(GL_UNIFORM_BUFFER, (glw::GLsizeiptr)glData[blockNdx].size(), &glData[blockNdx][0], GL_STATIC_DRAW);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to upload uniform buffer data");
+
+			gl.bindBufferBase(GL_UNIFORM_BUFFER, binding, buffer);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBufferBase(GL_UNIFORM_BUFFER) failed");
+		}
+	}
+	else
+	{
+		DE_ASSERT(m_bufferMode == BUFFERMODE_SINGLE);
+
+		int				totalSize			= 0;
+		int				curOffset			= 0;
+		int				numBlocks			= (int)glLayout.blocks.size();
+		int				bindingAlignment	= 0;
+		map<int, int>	glBlockOffsets;
+
+		gl.getIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &bindingAlignment);
+
+		// Compute total size and offsets.
+		curOffset = 0;
+		for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+		{
+			if (bindingAlignment > 0)
+				curOffset = deRoundUp32(curOffset, bindingAlignment);
+			glBlockOffsets[blockNdx] = curOffset;
+			curOffset += glLayout.blocks[blockNdx].size;
+		}
+		totalSize = curOffset;
+
+		// Assign block pointers.
+		vector<deUint8>	glData(totalSize);
+		map<int, void*>	glBlockPointers;
+
+		for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+			glBlockPointers[blockNdx] = &glData[glBlockOffsets[blockNdx]];
+
+		// Copy to gl format.
+		copyUniformData(glLayout, glBlockPointers, refLayout, blockPointers);
+
+		// Allocate buffer and upload data.
+		deUint32 buffer = bufferManager.allocBuffer();
+		gl.bindBuffer(GL_UNIFORM_BUFFER, buffer);
+		if (!glData.empty())
+			gl.bufferData(GL_UNIFORM_BUFFER, (glw::GLsizeiptr)glData.size(), &glData[0], GL_STATIC_DRAW);
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to upload uniform buffer data");
+
+		// Bind ranges to binding points.
+		for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+		{
+			deUint32 binding = (deUint32)blockNdx;
+			gl.bindBufferRange(GL_UNIFORM_BUFFER, binding, buffer, (glw::GLintptr)glBlockOffsets[blockNdx], (glw::GLsizeiptr)glLayout.blocks[blockNdx].size);
+			GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBufferRange(GL_UNIFORM_BUFFER) failed");
+		}
+	}
+
+	bool renderOk = render(program.getProgram());
+	if (!renderOk)
+		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image compare failed");
+
+	return STOP;
+}
+
+bool UniformBlockCase::compareStd140Blocks (const UniformLayout& refLayout, const UniformLayout& cmpLayout) const
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		isOk		= true;
+	int			numBlocks	= m_interface.getNumUniformBlocks();
+
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		const UniformBlock&		block			= m_interface.getUniformBlock(blockNdx);
+		bool					isArray			= block.isArray();
+		std::string				instanceName	= string(block.getBlockName()) + (isArray ? "[0]" : "");
+		int						refBlockNdx		= refLayout.getBlockIndex(instanceName.c_str());
+		int						cmpBlockNdx		= cmpLayout.getBlockIndex(instanceName.c_str());
+		bool					isUsed			= (block.getFlags() & (DECLARE_VERTEX|DECLARE_FRAGMENT)) != 0;
+
+		if ((block.getFlags() & LAYOUT_STD140) == 0)
+			continue; // Not std140 layout.
+
+		DE_ASSERT(refBlockNdx >= 0);
+
+		if (cmpBlockNdx < 0)
+		{
+			// Not found, should it?
+			if (isUsed)
+			{
+				log << TestLog::Message << "Error: Uniform block '" << instanceName << "' not found" << TestLog::EndMessage;
+				isOk = false;
+			}
+
+			continue; // Skip block.
+		}
+
+		const BlockLayoutEntry&		refBlockLayout	= refLayout.blocks[refBlockNdx];
+		const BlockLayoutEntry&		cmpBlockLayout	= cmpLayout.blocks[cmpBlockNdx];
+
+		// \todo [2012-01-24 pyry] Verify that activeUniformIndices is correct.
+		// \todo [2012-01-24 pyry] Verify all instances.
+		if (refBlockLayout.activeUniformIndices.size() != cmpBlockLayout.activeUniformIndices.size())
+		{
+			log << TestLog::Message << "Error: Number of active uniforms differ in block '" << instanceName
+				<< "' (expected " << refBlockLayout.activeUniformIndices.size()
+				<< ", got " << cmpBlockLayout.activeUniformIndices.size()
+				<< ")" << TestLog::EndMessage;
+			isOk = false;
+		}
+
+		for (vector<int>::const_iterator ndxIter = refBlockLayout.activeUniformIndices.begin(); ndxIter != refBlockLayout.activeUniformIndices.end(); ndxIter++)
+		{
+			const UniformLayoutEntry&	refEntry	= refLayout.uniforms[*ndxIter];
+			int							cmpEntryNdx	= cmpLayout.getUniformIndex(refEntry.name.c_str());
+
+			if (cmpEntryNdx < 0)
+			{
+				log << TestLog::Message << "Error: Uniform '" << refEntry.name << "' not found" << TestLog::EndMessage;
+				isOk = false;
+				continue;
+			}
+
+			const UniformLayoutEntry&	cmpEntry	= cmpLayout.uniforms[cmpEntryNdx];
+
+			if (refEntry.type			!= cmpEntry.type			||
+				refEntry.size			!= cmpEntry.size			||
+				refEntry.offset			!= cmpEntry.offset			||
+				refEntry.arrayStride	!= cmpEntry.arrayStride		||
+				refEntry.matrixStride	!= cmpEntry.matrixStride	||
+				refEntry.isRowMajor		!= cmpEntry.isRowMajor)
+			{
+				log << TestLog::Message << "Error: Layout mismatch in '" << refEntry.name << "':\n"
+					<< "  expected: type = " << glu::getDataTypeName(refEntry.type) << ", size = " << refEntry.size << ", offset = " << refEntry.offset << ", array stride = "<< refEntry.arrayStride << ", matrix stride = " << refEntry.matrixStride << ", row major = " << (refEntry.isRowMajor ? "true" : "false") << "\n"
+					<< "  got: type = " << glu::getDataTypeName(cmpEntry.type) << ", size = " << cmpEntry.size << ", offset = " << cmpEntry.offset << ", array stride = "<< cmpEntry.arrayStride << ", matrix stride = " << cmpEntry.matrixStride << ", row major = " << (cmpEntry.isRowMajor ? "true" : "false")
+					<< TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+	}
+
+	return isOk;
+}
+
+bool UniformBlockCase::compareSharedBlocks (const UniformLayout& refLayout, const UniformLayout& cmpLayout) const
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		isOk		= true;
+	int			numBlocks	= m_interface.getNumUniformBlocks();
+
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		const UniformBlock&		block			= m_interface.getUniformBlock(blockNdx);
+		bool					isArray			= block.isArray();
+		std::string				instanceName	= string(block.getBlockName()) + (isArray ? "[0]" : "");
+		int						refBlockNdx		= refLayout.getBlockIndex(instanceName.c_str());
+		int						cmpBlockNdx		= cmpLayout.getBlockIndex(instanceName.c_str());
+		bool					isUsed			= (block.getFlags() & (DECLARE_VERTEX|DECLARE_FRAGMENT)) != 0;
+
+		if ((block.getFlags() & LAYOUT_SHARED) == 0)
+			continue; // Not shared layout.
+
+		DE_ASSERT(refBlockNdx >= 0);
+
+		if (cmpBlockNdx < 0)
+		{
+			// Not found, should it?
+			if (isUsed)
+			{
+				log << TestLog::Message << "Error: Uniform block '" << instanceName << "' not found" << TestLog::EndMessage;
+				isOk = false;
+			}
+
+			continue; // Skip block.
+		}
+
+		const BlockLayoutEntry&		refBlockLayout	= refLayout.blocks[refBlockNdx];
+		const BlockLayoutEntry&		cmpBlockLayout	= cmpLayout.blocks[cmpBlockNdx];
+
+		if (refBlockLayout.activeUniformIndices.size() != cmpBlockLayout.activeUniformIndices.size())
+		{
+			log << TestLog::Message << "Error: Number of active uniforms differ in block '" << instanceName
+				<< "' (expected " << refBlockLayout.activeUniformIndices.size()
+				<< ", got " << cmpBlockLayout.activeUniformIndices.size()
+				<< ")" << TestLog::EndMessage;
+			isOk = false;
+		}
+
+		for (vector<int>::const_iterator ndxIter = refBlockLayout.activeUniformIndices.begin(); ndxIter != refBlockLayout.activeUniformIndices.end(); ndxIter++)
+		{
+			const UniformLayoutEntry&	refEntry	= refLayout.uniforms[*ndxIter];
+			int							cmpEntryNdx	= cmpLayout.getUniformIndex(refEntry.name.c_str());
+
+			if (cmpEntryNdx < 0)
+			{
+				log << TestLog::Message << "Error: Uniform '" << refEntry.name << "' not found" << TestLog::EndMessage;
+				isOk = false;
+				continue;
+			}
+
+			const UniformLayoutEntry&	cmpEntry	= cmpLayout.uniforms[cmpEntryNdx];
+
+			if (refEntry.type		!= cmpEntry.type	||
+				refEntry.size		!= cmpEntry.size	||
+				refEntry.isRowMajor	!= cmpEntry.isRowMajor)
+			{
+				log << TestLog::Message << "Error: Layout mismatch in '" << refEntry.name << "':\n"
+					<< "  expected: type = " << glu::getDataTypeName(refEntry.type) << ", size = " << refEntry.size << ", row major = " << (refEntry.isRowMajor ? "true" : "false") << "\n"
+					<< "  got: type = " << glu::getDataTypeName(cmpEntry.type) << ", size = " << cmpEntry.size << ", row major = " << (cmpEntry.isRowMajor ? "true" : "false")
+					<< TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+	}
+
+	return isOk;
+}
+
+bool UniformBlockCase::compareTypes (const UniformLayout& refLayout, const UniformLayout& cmpLayout) const
+{
+	TestLog&	log			= m_testCtx.getLog();
+	bool		isOk		= true;
+	int			numBlocks	= m_interface.getNumUniformBlocks();
+
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		const UniformBlock&		block			= m_interface.getUniformBlock(blockNdx);
+		bool					isArray			= block.isArray();
+		int						numInstances	= isArray ? block.getArraySize() : 1;
+
+		for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
+		{
+			std::ostringstream instanceName;
+
+			instanceName << block.getBlockName();
+			if (isArray)
+				instanceName << "[" << instanceNdx << "]";
+
+			int cmpBlockNdx = cmpLayout.getBlockIndex(instanceName.str().c_str());
+
+			if (cmpBlockNdx < 0)
+				continue;
+
+			const BlockLayoutEntry& cmpBlockLayout = cmpLayout.blocks[cmpBlockNdx];
+
+			for (vector<int>::const_iterator ndxIter = cmpBlockLayout.activeUniformIndices.begin(); ndxIter != cmpBlockLayout.activeUniformIndices.end(); ndxIter++)
+			{
+				const UniformLayoutEntry&	cmpEntry	= cmpLayout.uniforms[*ndxIter];
+				int							refEntryNdx	= refLayout.getUniformIndex(cmpEntry.name.c_str());
+
+				if (refEntryNdx < 0)
+				{
+					log << TestLog::Message << "Error: Uniform '" << cmpEntry.name << "' not found in reference layout" << TestLog::EndMessage;
+					isOk = false;
+					continue;
+				}
+
+				const UniformLayoutEntry&	refEntry	= refLayout.uniforms[refEntryNdx];
+
+				// \todo [2012-11-26 pyry] Should we check other properties as well?
+				if (refEntry.type != cmpEntry.type)
+				{
+					log << TestLog::Message << "Error: Uniform type mismatch in '" << refEntry.name << "':\n"
+						<< "  expected: " << glu::getDataTypeName(refEntry.type) << "\n"
+						<< "  got: " << glu::getDataTypeName(cmpEntry.type)
+						<< TestLog::EndMessage;
+					isOk = false;
+				}
+			}
+		}
+	}
+
+	return isOk;
+}
+
+bool UniformBlockCase::checkLayoutIndices (const UniformLayout& layout) const
+{
+	TestLog&	log			= m_testCtx.getLog();
+	int			numUniforms	= (int)layout.uniforms.size();
+	int			numBlocks	= (int)layout.blocks.size();
+	bool		isOk		= true;
+
+	// Check uniform block indices.
+	for (int uniformNdx = 0; uniformNdx < numUniforms; uniformNdx++)
+	{
+		const UniformLayoutEntry& uniform = layout.uniforms[uniformNdx];
+
+		if (uniform.blockNdx < 0 || !deInBounds32(uniform.blockNdx, 0, numBlocks))
+		{
+			log << TestLog::Message << "Error: Invalid block index in uniform '" << uniform.name << "'" << TestLog::EndMessage;
+			isOk = false;
+		}
+	}
+
+	// Check active uniforms.
+	for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+	{
+		const BlockLayoutEntry& block = layout.blocks[blockNdx];
+
+		for (vector<int>::const_iterator uniformIter = block.activeUniformIndices.begin(); uniformIter != block.activeUniformIndices.end(); uniformIter++)
+		{
+			if (!deInBounds32(*uniformIter, 0, numUniforms))
+			{
+				log << TestLog::Message << "Error: Invalid active uniform index " << *uniformIter << " in block '" << block.name << "'" << TestLog::EndMessage;
+				isOk = false;
+			}
+		}
+	}
+
+	return isOk;
+}
+
+bool UniformBlockCase::checkLayoutBounds (const UniformLayout& layout) const
+{
+	TestLog&	log			= m_testCtx.getLog();
+	int			numUniforms	= (int)layout.uniforms.size();
+	bool		isOk		= true;
+
+	for (int uniformNdx = 0; uniformNdx < numUniforms; uniformNdx++)
+	{
+		const UniformLayoutEntry& uniform = layout.uniforms[uniformNdx];
+
+		if (uniform.blockNdx < 0)
+			continue;
+
+		const BlockLayoutEntry&		block			= layout.blocks[uniform.blockNdx];
+		bool						isMatrix		= glu::isDataTypeMatrix(uniform.type);
+		int							numVecs			= isMatrix ? (uniform.isRowMajor ? glu::getDataTypeMatrixNumRows(uniform.type) : glu::getDataTypeMatrixNumColumns(uniform.type)) : 1;
+		int							numComps		= isMatrix ? (uniform.isRowMajor ? glu::getDataTypeMatrixNumColumns(uniform.type) : glu::getDataTypeMatrixNumRows(uniform.type)) : glu::getDataTypeScalarSize(uniform.type);
+		int							numElements		= uniform.size;
+		const int					compSize		= sizeof(deUint32);
+		int							vecSize			= numComps*compSize;
+
+		int							minOffset		= 0;
+		int							maxOffset		= 0;
+
+		// For negative strides.
+		minOffset	= de::min(minOffset, (numVecs-1)*uniform.matrixStride);
+		minOffset	= de::min(minOffset, (numElements-1)*uniform.arrayStride);
+		minOffset	= de::min(minOffset, (numElements-1)*uniform.arrayStride + (numVecs-1)*uniform.matrixStride);
+
+		maxOffset	= de::max(maxOffset, vecSize);
+		maxOffset	= de::max(maxOffset, (numVecs-1)*uniform.matrixStride + vecSize);
+		maxOffset	= de::max(maxOffset, (numElements-1)*uniform.arrayStride + vecSize);
+		maxOffset	= de::max(maxOffset, (numElements-1)*uniform.arrayStride + (numVecs-1)*uniform.matrixStride + vecSize);
+
+		if (uniform.offset+minOffset < 0 || uniform.offset+maxOffset > block.size)
+		{
+			log << TestLog::Message << "Error: Uniform '" << uniform.name << "' out of block bounds" << TestLog::EndMessage;
+			isOk = false;
+		}
+	}
+
+	return isOk;
+}
+
+bool UniformBlockCase::checkIndexQueries (deUint32 program, const UniformLayout& layout) const
+{
+	tcu::TestLog&				log			= m_testCtx.getLog();
+	const glw::Functions&		gl			= m_renderCtx.getFunctions();
+	bool						allOk		= true;
+
+	// \note Spec mandates that uniform blocks are assigned consecutive locations from 0
+	//		 to ACTIVE_UNIFORM_BLOCKS. BlockLayoutEntries are stored in that order in UniformLayout.
+	for (int blockNdx = 0; blockNdx < (int)layout.blocks.size(); blockNdx++)
+	{
+		const BlockLayoutEntry&		block		= layout.blocks[blockNdx];
+		const int					queriedNdx	= gl.getUniformBlockIndex(program, block.name.c_str());
+
+		if (queriedNdx != blockNdx)
+		{
+			log << TestLog::Message << "ERROR: glGetUniformBlockIndex(" << block.name << ") returned " << queriedNdx << ", expected " << blockNdx << "!" << TestLog::EndMessage;
+			allOk = false;
+		}
+
+		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformBlockIndex()");
+	}
+
+	return allOk;
+}
+
+bool UniformBlockCase::render (deUint32 program) const
+{
+	tcu::TestLog&				log				= m_testCtx.getLog();
+	const glw::Functions&		gl				= m_renderCtx.getFunctions();
+	de::Random					rnd				(deStringHash(getName()));
+	const tcu::RenderTarget&	renderTarget	= m_renderCtx.getRenderTarget();
+	const int					viewportW		= de::min(renderTarget.getWidth(),	128);
+	const int					viewportH		= de::min(renderTarget.getHeight(),	128);
+	const int					viewportX		= rnd.getInt(0, renderTarget.getWidth()		- viewportW);
+	const int					viewportY		= rnd.getInt(0, renderTarget.getHeight()	- viewportH);
+
+	gl.clearColor(0.125f, 0.25f, 0.5f, 1.0f);
+	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+	// Draw
+	{
+		const float position[] =
+		{
+			-1.0f, -1.0f, 0.0f, 1.0f,
+			-1.0f, +1.0f, 0.0f, 1.0f,
+			+1.0f, -1.0f, 0.0f, 1.0f,
+			+1.0f, +1.0f, 0.0f, 1.0f
+		};
+		const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };
+
+		gl.viewport(viewportX, viewportY, viewportW, viewportH);
+
+		glu::VertexArrayBinding posArray = glu::va::Float("a_position", 4, 4, 0, &position[0]);
+		glu::draw(m_renderCtx, program, 1, &posArray,
+				  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
+	}
+
+	// Verify that all pixels are white.
+	{
+		tcu::Surface	pixels			(viewportW, viewportH);
+		int				numFailedPixels = 0;
+
+		glu::readPixels(m_renderCtx, viewportX, viewportY, pixels.getAccess());
+		GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");
+
+		for (int y = 0; y < pixels.getHeight(); y++)
+		{
+			for (int x = 0; x < pixels.getWidth(); x++)
+			{
+				if (pixels.getPixel(x, y) != tcu::RGBA::white)
+					numFailedPixels += 1;
+			}
+		}
+
+		if (numFailedPixels > 0)
+		{
+			log << TestLog::Image("Image", "Rendered image", pixels);
+			log << TestLog::Message << "Image comparison failed, got " << numFailedPixels << " non-white pixels" << TestLog::EndMessage;
+		}
+
+		return numFailedPixels == 0;
+	}
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsUniformBlockCase.hpp b/modules/glshared/glsUniformBlockCase.hpp
new file mode 100644
index 0000000..db47d79
--- /dev/null
+++ b/modules/glshared/glsUniformBlockCase.hpp
@@ -0,0 +1,270 @@
+#ifndef _GLSUNIFORMBLOCKCASE_HPP
+#define _GLSUNIFORMBLOCKCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Uniform block tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+#include "gluShaderUtil.hpp"
+
+namespace glu
+{
+class RenderContext;
+}
+
+namespace deqp
+{
+namespace gls
+{
+
+// Uniform block details.
+namespace ub
+{
+
+enum UniformFlags
+{
+	PRECISION_LOW		= (1<<0),
+	PRECISION_MEDIUM	= (1<<1),
+	PRECISION_HIGH		= (1<<2),
+	PRECISION_MASK		= PRECISION_LOW|PRECISION_MEDIUM|PRECISION_HIGH,
+
+	LAYOUT_SHARED		= (1<<3),
+	LAYOUT_PACKED		= (1<<4),
+	LAYOUT_STD140		= (1<<5),
+	LAYOUT_ROW_MAJOR	= (1<<6),
+	LAYOUT_COLUMN_MAJOR	= (1<<7),	//!< \note Lack of both flags means column-major matrix.
+	LAYOUT_MASK			= LAYOUT_SHARED|LAYOUT_PACKED|LAYOUT_STD140|LAYOUT_ROW_MAJOR|LAYOUT_COLUMN_MAJOR,
+
+	DECLARE_VERTEX		= (1<<8),
+	DECLARE_FRAGMENT	= (1<<9),
+	DECLARE_BOTH		= DECLARE_VERTEX|DECLARE_FRAGMENT,
+
+	UNUSED_VERTEX		= (1<<10),	//!< Uniform or struct member is not read in vertex shader.
+	UNUSED_FRAGMENT		= (1<<11),	//!< Uniform or struct member is not read in fragment shader.
+	UNUSED_BOTH			= UNUSED_VERTEX|UNUSED_FRAGMENT
+};
+
+// \todo [2012-07-25 pyry] Use glu::VarType.
+
+class StructType;
+
+class VarType
+{
+public:
+						VarType			(void);
+						VarType			(const VarType& other);
+						VarType			(glu::DataType basicType, deUint32 flags);
+						VarType			(const VarType& elementType, int arraySize);
+	explicit			VarType			(const StructType* structPtr);
+						~VarType		(void);
+
+	bool				isBasicType		(void) const	{ return m_type == TYPE_BASIC;	}
+	bool				isArrayType		(void) const	{ return m_type == TYPE_ARRAY;	}
+	bool				isStructType	(void) const	{ return m_type == TYPE_STRUCT;	}
+
+	deUint32			getFlags		(void) const	{ return m_flags;					}
+	glu::DataType		getBasicType	(void) const	{ return m_data.basicType;			}
+
+	const VarType&		getElementType	(void) const	{ return *m_data.array.elementType;	}
+	int					getArraySize	(void) const	{ return m_data.array.size;			}
+
+	const StructType&	getStruct		(void) const	{ return *m_data.structPtr;			}
+
+	VarType&			operator=		(const VarType& other);
+
+private:
+	enum Type
+	{
+		TYPE_BASIC,
+		TYPE_ARRAY,
+		TYPE_STRUCT,
+
+		TYPE_LAST
+	};
+
+	Type				m_type;
+	deUint32			m_flags;
+	union Data
+	{
+		glu::DataType		basicType;
+		struct
+		{
+			VarType*		elementType;
+			int				size;
+		} array;
+		const StructType*	structPtr;
+
+		Data (void)
+		{
+			array.elementType	= DE_NULL;
+			array.size			= 0;
+		};
+	} m_data;
+};
+
+class StructMember
+{
+public:
+						StructMember	(const char* name, const VarType& type, deUint32 flags) : m_name(name), m_type(type), m_flags(flags) {}
+						StructMember	(void) : m_flags(0) {}
+
+	const char*			getName			(void) const { return m_name.c_str();	}
+	const VarType&		getType			(void) const { return m_type;			}
+	deUint32			getFlags		(void) const { return m_flags;			}
+
+private:
+	std::string			m_name;
+	VarType				m_type;
+	deUint32			m_flags;
+};
+
+class StructType
+{
+public:
+	typedef std::vector<StructMember>::iterator			Iterator;
+	typedef std::vector<StructMember>::const_iterator	ConstIterator;
+
+								StructType		(const char* typeName) : m_typeName(typeName) {}
+								~StructType		(void) {}
+
+	const char*					getTypeName		(void) const	{ return m_typeName.empty() ? DE_NULL : m_typeName.c_str();	}
+
+	inline Iterator				begin			(void)			{ return m_members.begin();		}
+	inline ConstIterator		begin			(void) const	{ return m_members.begin();		}
+	inline Iterator				end				(void)			{ return m_members.end();		}
+	inline ConstIterator		end				(void) const	{ return m_members.end();		}
+
+	void						addMember		(const char* name, const VarType& type, deUint32 flags = 0);
+
+private:
+	std::string					m_typeName;
+	std::vector<StructMember>	m_members;
+};
+
+class Uniform
+{
+public:
+					Uniform			(const char* name, const VarType& type, deUint32 flags = 0);
+
+	const char*		getName			(void) const { return m_name.c_str();	}
+	const VarType&	getType			(void) const { return m_type;			}
+	deUint32		getFlags		(void) const { return m_flags;			}
+
+private:
+	std::string		m_name;
+	VarType			m_type;
+	deUint32		m_flags;
+};
+
+class UniformBlock
+{
+public:
+	typedef std::vector<Uniform>::iterator			Iterator;
+	typedef std::vector<Uniform>::const_iterator	ConstIterator;
+
+							UniformBlock		(const char* blockName);
+
+	const char*				getBlockName		(void) const { return m_blockName.c_str();		}
+	const char*				getInstanceName		(void) const { return m_instanceName.empty() ? DE_NULL : m_instanceName.c_str();	}
+	bool					isArray				(void) const { return m_arraySize > 0;			}
+	int						getArraySize		(void) const { return m_arraySize;				}
+	deUint32				getFlags			(void) const { return m_flags;					}
+
+	void					setInstanceName		(const char* name)			{ m_instanceName = name;			}
+	void					setFlags			(deUint32 flags)			{ m_flags = flags;					}
+	void					setArraySize		(int arraySize)				{ m_arraySize = arraySize;			}
+	void					addUniform			(const Uniform& uniform)	{ m_uniforms.push_back(uniform);	}
+
+	inline Iterator			begin				(void)			{ return m_uniforms.begin();	}
+	inline ConstIterator	begin				(void) const	{ return m_uniforms.begin();	}
+	inline Iterator			end					(void)			{ return m_uniforms.end();		}
+	inline ConstIterator	end					(void) const	{ return m_uniforms.end();		}
+
+private:
+	std::string				m_blockName;
+	std::string				m_instanceName;
+	std::vector<Uniform>	m_uniforms;
+	int						m_arraySize;	//!< Array size or 0 if not interface block array.
+	deUint32				m_flags;
+};
+
+class ShaderInterface
+{
+public:
+								ShaderInterface			(void);
+								~ShaderInterface		(void);
+
+	StructType&					allocStruct				(const char* name);
+	const StructType*			findStruct				(const char* name) const;
+	void						getNamedStructs			(std::vector<const StructType*>& structs) const;
+
+	UniformBlock&				allocBlock				(const char* name);
+
+	int							getNumUniformBlocks		(void) const	{ return (int)m_uniformBlocks.size();	}
+	const UniformBlock&			getUniformBlock			(int ndx) const	{ return *m_uniformBlocks[ndx];			}
+
+private:
+	std::vector<StructType*>	m_structs;
+	std::vector<UniformBlock*>	m_uniformBlocks;
+};
+
+class UniformLayout;
+
+} // ub
+
+class UniformBlockCase : public tcu::TestCase
+{
+public:
+	enum BufferMode
+	{
+		BUFFERMODE_SINGLE = 0,	//!< Single buffer shared between uniform blocks.
+		BUFFERMODE_PER_BLOCK,	//!< Per-block buffers
+
+		BUFFERMODE_LAST
+	};
+
+								UniformBlockCase			(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, glu::GLSLVersion glslVersion, BufferMode bufferMode);
+								~UniformBlockCase			(void);
+
+	IterateResult				iterate						(void);
+
+protected:
+	bool						compareStd140Blocks			(const ub::UniformLayout& refLayout, const ub::UniformLayout& cmpLayout) const;
+	bool						compareSharedBlocks			(const ub::UniformLayout& refLayout, const ub::UniformLayout& cmpLayout) const;
+	bool						compareTypes				(const ub::UniformLayout& refLayout, const ub::UniformLayout& cmpLayout) const;
+	bool						checkLayoutIndices			(const ub::UniformLayout& layout) const;
+	bool						checkLayoutBounds			(const ub::UniformLayout& layout) const;
+	bool						checkIndexQueries			(deUint32 program, const ub::UniformLayout& layout) const;
+
+	bool						render						(deUint32 program) const;
+
+	glu::RenderContext&			m_renderCtx;
+	glu::GLSLVersion			m_glslVersion;
+	BufferMode					m_bufferMode;
+	ub::ShaderInterface			m_interface;
+};
+
+} // gls
+} // deqp
+
+#endif // _GLSUNIFORMBLOCKCASE_HPP
diff --git a/modules/glshared/glsVertexArrayTests.cpp b/modules/glshared/glsVertexArrayTests.cpp
new file mode 100644
index 0000000..4c9c19b
--- /dev/null
+++ b/modules/glshared/glsVertexArrayTests.cpp
@@ -0,0 +1,2233 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array and buffer tests
+ *//*--------------------------------------------------------------------*/
+
+#include "glsVertexArrayTests.hpp"
+
+#include "deRandom.h"
+
+#include "tcuTestLog.hpp"
+#include "tcuPixelFormat.hpp"
+#include "tcuRGBA.hpp"
+#include "tcuSurface.hpp"
+#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuRenderTarget.hpp"
+#include "tcuStringTemplate.hpp"
+#include "tcuImageCompare.hpp"
+
+#include "gluPixelTransfer.hpp"
+#include "gluCallLogWrapper.hpp"
+
+#include "sglrContext.hpp"
+#include "sglrReferenceContext.hpp"
+#include "sglrGLContext.hpp"
+
+#include "deMath.h"
+#include "deStringUtil.hpp"
+
+#include <cstring>
+#include <cmath>
+#include <vector>
+#include <sstream>
+#include <limits>
+#include <algorithm>
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+
+namespace deqp
+{
+namespace gls
+{
+
+using tcu::TestLog;
+using namespace glw; // GL types
+
+std::string Array::targetToString(Target target)
+{
+	DE_ASSERT(target < TARGET_LAST);
+
+	static const char* targets[] =
+	{
+		"element_array",	// TARGET_ELEMENT_ARRAY = 0,
+		"array"				// TARGET_ARRAY,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(targets) == Array::TARGET_LAST);
+
+	return targets[(int)target];
+}
+
+std::string Array::inputTypeToString(InputType type)
+{
+	DE_ASSERT(type < INPUTTYPE_LAST);
+
+	static const char* types[] =
+	{
+		"float",			// INPUTTYPE_FLOAT = 0,
+		"fixed",			// INPUTTYPE_FIXED,
+		"double",			// INPUTTYPE_DOUBLE
+
+		"byte",				// INPUTTYPE_BYTE,
+		"short",			// INPUTTYPE_SHORT,
+
+		"unsigned_byte",	// INPUTTYPE_UNSIGNED_BYTE,
+		"unsigned_short",	// INPUTTYPE_UNSIGNED_SHORT,
+
+		"int",						// INPUTTYPE_INT,
+		"unsigned_int",				// INPUTTYPE_UNSIGNED_INT,
+		"half",						// INPUTTYPE_HALF,
+		"usigned_int2_10_10_10",	// INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		"int2_10_10_10"				// INPUTTYPE_INT_2_10_10_10,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == Array::INPUTTYPE_LAST);
+
+	return types[(int)type];
+}
+
+std::string Array::outputTypeToString(OutputType type)
+{
+	DE_ASSERT(type < OUTPUTTYPE_LAST);
+
+	static const char* types[] =
+	{
+		"float",		// OUTPUTTYPE_FLOAT = 0,
+		"vec2",			// OUTPUTTYPE_VEC2,
+		"vec3",			// OUTPUTTYPE_VEC3,
+		"vec4",			// OUTPUTTYPE_VEC4,
+
+		"int",			// OUTPUTTYPE_INT,
+		"uint",			// OUTPUTTYPE_UINT,
+
+		"ivec2",		// OUTPUTTYPE_IVEC2,
+		"ivec3",		// OUTPUTTYPE_IVEC3,
+		"ivec4",		// OUTPUTTYPE_IVEC4,
+
+		"uvec2",		// OUTPUTTYPE_UVEC2,
+		"uvec3",		// OUTPUTTYPE_UVEC3,
+		"uvec4",		// OUTPUTTYPE_UVEC4,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == Array::OUTPUTTYPE_LAST);
+
+	return types[(int)type];
+}
+
+std::string Array::usageTypeToString(Usage usage)
+{
+	DE_ASSERT(usage < USAGE_LAST);
+
+	static const char* usages[] =
+	{
+		"dynamic_draw",	// USAGE_DYNAMIC_DRAW = 0,
+		"static_draw",	// USAGE_STATIC_DRAW,
+		"stream_draw",	// USAGE_STREAM_DRAW,
+
+		"stream_read",	// USAGE_STREAM_READ,
+		"stream_copy",	// USAGE_STREAM_COPY,
+
+		"static_read",	// USAGE_STATIC_READ,
+		"static_copy",	// USAGE_STATIC_COPY,
+
+		"dynamic_read",	// USAGE_DYNAMIC_READ,
+		"dynamic_copy",	// USAGE_DYNAMIC_COPY,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(usages) == Array::USAGE_LAST);
+
+	return usages[(int)usage];
+}
+
+std::string	Array::storageToString (Storage storage)
+{
+	DE_ASSERT(storage < STORAGE_LAST);
+
+	static const char* storages[] =
+	{
+		"user_ptr",	// STORAGE_USER = 0,
+		"buffer"	// STORAGE_BUFFER,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(storages) == Array::STORAGE_LAST);
+
+	return storages[(int)storage];
+}
+
+std::string Array::primitiveToString (Primitive primitive)
+{
+	DE_ASSERT(primitive < PRIMITIVE_LAST);
+
+	static const char* primitives[] =
+	{
+		"points",			// PRIMITIVE_POINTS ,
+		"triangles",		// PRIMITIVE_TRIANGLES,
+		"triangle_fan",		// PRIMITIVE_TRIANGLE_FAN,
+		"triangle_strip"	// PRIMITIVE_TRIANGLE_STRIP,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(primitives) == Array::PRIMITIVE_LAST);
+
+	return primitives[(int)primitive];
+}
+
+int Array::inputTypeSize (InputType type)
+{
+	DE_ASSERT(type < INPUTTYPE_LAST);
+
+	static const int size[] =
+	{
+		sizeof(float),		// INPUTTYPE_FLOAT = 0,
+		sizeof(deInt32),	// INPUTTYPE_FIXED,
+		sizeof(double),		// INPUTTYPE_DOUBLE
+
+		sizeof(deInt8),		// INPUTTYPE_BYTE,
+		sizeof(deInt16),	// INPUTTYPE_SHORT,
+
+		sizeof(deUint8),	// INPUTTYPE_UNSIGNED_BYTE,
+		sizeof(deUint16),	// INPUTTYPE_UNSIGNED_SHORT,
+
+		sizeof(deInt32),		// INPUTTYPE_INT,
+		sizeof(deUint32),		// INPUTTYPE_UNSIGNED_INT,
+		sizeof(deFloat16),		// INPUTTYPE_HALF,
+		sizeof(deUint32) / 4,		// INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		sizeof(deUint32) / 4		// INPUTTYPE_INT_2_10_10_10,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(size) == Array::INPUTTYPE_LAST);
+
+	return size[(int)type];
+}
+
+static bool inputTypeIsFloatType (Array::InputType type)
+{
+	if (type == Array::INPUTTYPE_FLOAT)
+		return true;
+	if (type == Array::INPUTTYPE_FIXED)
+		return true;
+	if (type == Array::INPUTTYPE_DOUBLE)
+		return true;
+	if (type == Array::INPUTTYPE_HALF)
+		return true;
+	return false;
+}
+
+static bool outputTypeIsFloatType (Array::OutputType type)
+{
+	if (type == Array::OUTPUTTYPE_FLOAT
+		|| type == Array::OUTPUTTYPE_VEC2
+		|| type == Array::OUTPUTTYPE_VEC3
+		|| type == Array::OUTPUTTYPE_VEC4)
+		return true;
+
+	return false;
+}
+
+template<class T>
+inline T getRandom (deRandom& rnd, T min, T max);
+
+template<>
+inline GLValue::Float getRandom (deRandom& rnd, GLValue::Float min, GLValue::Float max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Float::create(min + deRandom_getFloat(&rnd) * (max.to<float>() - min.to<float>()));
+}
+
+template<>
+inline GLValue::Short getRandom (deRandom& rnd, GLValue::Short min, GLValue::Short max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Short::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
+}
+
+template<>
+inline GLValue::Ushort getRandom (deRandom& rnd, GLValue::Ushort min, GLValue::Ushort max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Ushort::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
+}
+
+template<>
+inline GLValue::Byte getRandom (deRandom& rnd, GLValue::Byte min, GLValue::Byte max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Byte::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
+}
+
+template<>
+inline GLValue::Ubyte getRandom (deRandom& rnd, GLValue::Ubyte min, GLValue::Ubyte max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Ubyte::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
+}
+
+template<>
+inline GLValue::Fixed getRandom (deRandom& rnd, GLValue::Fixed min, GLValue::Fixed max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Fixed::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
+}
+
+template<>
+inline GLValue::Half getRandom (deRandom& rnd, GLValue::Half min, GLValue::Half max)
+{
+	if (max < min)
+		return min;
+
+	float fMax = max.to<float>();
+	float fMin = min.to<float>();
+	GLValue::Half h = GLValue::Half::create(fMin + deRandom_getFloat(&rnd) * (fMax - fMin));
+	return h;
+}
+
+template<>
+inline GLValue::Int getRandom (deRandom& rnd, GLValue::Int min, GLValue::Int max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Int::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
+}
+
+template<>
+inline GLValue::Uint getRandom (deRandom& rnd, GLValue::Uint min, GLValue::Uint max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Uint::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
+}
+
+template<>
+inline GLValue::Double getRandom (deRandom& rnd, GLValue::Double min, GLValue::Double max)
+{
+	if (max < min)
+		return min;
+
+	return GLValue::Double::create(min + deRandom_getFloat(&rnd) * (max.to<float>() - min.to<float>()));
+}
+
+// Minimum difference required between coordinates
+template<class T>
+inline T minValue (void);
+
+template<>
+inline GLValue::Float minValue (void)
+{
+	return GLValue::Float::create(4 * 1.0f);
+}
+
+template<>
+inline GLValue::Short minValue (void)
+{
+	return GLValue::Short::create(4 * 256);
+}
+
+template<>
+inline GLValue::Ushort minValue (void)
+{
+	return GLValue::Ushort::create(4 * 256);
+}
+
+template<>
+inline GLValue::Byte minValue (void)
+{
+	return GLValue::Byte::create(4 * 1);
+}
+
+template<>
+inline GLValue::Ubyte minValue (void)
+{
+	return GLValue::Ubyte::create(4 * 2);
+}
+
+template<>
+inline GLValue::Fixed minValue (void)
+{
+	return GLValue::Fixed::create(4 * 512);
+}
+
+template<>
+inline GLValue::Int minValue (void)
+{
+	return GLValue::Int::create(4 * 16777216);
+}
+
+template<>
+inline GLValue::Uint minValue (void)
+{
+	return GLValue::Uint::create(4 * 16777216);
+}
+
+template<>
+inline GLValue::Half minValue (void)
+{
+	return GLValue::Half::create(4 * 1.0f);
+}
+
+template<>
+inline GLValue::Double minValue (void)
+{
+	return GLValue::Double::create(4 * 1.0f);
+}
+
+template<class T>
+inline T abs (T val);
+
+template<>
+inline GLValue::Fixed abs (GLValue::Fixed val)
+{
+	return GLValue::Fixed::create(0x7FFFu & val.getValue());
+}
+
+template<>
+inline GLValue::Ubyte abs (GLValue::Ubyte val)
+{
+	return val;
+}
+
+template<>
+inline GLValue::Byte abs (GLValue::Byte val)
+{
+	return GLValue::Byte::create(0x7Fu & val.getValue());
+}
+
+template<>
+inline GLValue::Ushort abs (GLValue::Ushort val)
+{
+	return val;
+}
+
+template<>
+inline GLValue::Short abs (GLValue::Short val)
+{
+	return GLValue::Short::create(0x7FFFu & val.getValue());
+}
+
+template<>
+inline GLValue::Float abs (GLValue::Float val)
+{
+	return GLValue::Float::create(std::fabs(val.to<float>()));
+}
+
+template<>
+inline GLValue::Uint abs (GLValue::Uint val)
+{
+	return val;
+}
+
+template<>
+inline GLValue::Int abs (GLValue::Int val)
+{
+	return GLValue::Int::create(0x7FFFFFFFu & val.getValue());
+}
+
+template<>
+inline GLValue::Half abs (GLValue::Half val)
+{
+	return GLValue::Half::create(std::fabs(val.to<float>()));
+}
+
+template<>
+inline GLValue::Double abs (GLValue::Double val)
+{
+	return GLValue::Double::create(std::fabs(val.to<float>()));
+}
+
+template<class T>
+inline static void alignmentSafeAssignment (char* dst, T val)
+{
+	std::memcpy(dst, &val, sizeof(T));
+}
+
+ContextArray::ContextArray (Storage storage, sglr::Context& context)
+	: m_storage			(storage)
+	, m_ctx				(context)
+	, m_glBuffer		(0)
+	, m_bound			(false)
+	, m_attribNdx		(0)
+	, m_size			(0)
+	, m_data			(DE_NULL)
+	, m_componentCount	(1)
+	, m_target			(Array::TARGET_ARRAY)
+	, m_inputType		(Array::INPUTTYPE_FLOAT)
+	, m_outputType		(Array::OUTPUTTYPE_VEC4)
+	, m_normalize		(false)
+	, m_stride			(0)
+	, m_offset			(0)
+{
+	if (m_storage == STORAGE_BUFFER)
+	{
+		m_ctx.genBuffers(1, &m_glBuffer);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glGenBuffers()");
+	}
+}
+
+ContextArray::~ContextArray	(void)
+{
+	if (m_storage == STORAGE_BUFFER)
+	{
+		m_ctx.deleteBuffers(1, &m_glBuffer);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDeleteBuffers()");
+	}
+	else if (m_storage == STORAGE_USER)
+		delete[] m_data;
+	else
+		DE_ASSERT(false);
+}
+
+Array* ContextArrayPack::getArray (int i)
+{
+	return m_arrays.at(i);
+}
+
+void ContextArray::data (Target target, int size, const char* ptr, Usage usage)
+{
+	m_size = size;
+	m_target = target;
+
+	if (m_storage == STORAGE_BUFFER)
+	{
+		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");
+
+		m_ctx.bufferData(targetToGL(target), size, ptr, usageToGL(usage));
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBufferData()");
+	}
+	else if (m_storage == STORAGE_USER)
+	{
+		if (m_data)
+			delete[] m_data;
+
+		m_data = new char[size];
+		std::memcpy(m_data, ptr, size);
+	}
+	else
+		DE_ASSERT(false);
+}
+
+void ContextArray::subdata (Target target, int offset, int size, const char* ptr)
+{
+	m_target = target;
+
+	if (m_storage == STORAGE_BUFFER)
+	{
+		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");
+
+		m_ctx.bufferSubData(targetToGL(target), offset, size, ptr);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBufferSubData()");
+	}
+	else if (m_storage == STORAGE_USER)
+		std::memcpy(m_data + offset, ptr, size);
+	else
+		DE_ASSERT(false);
+}
+
+void ContextArray::bind (int attribNdx, int offset, int size, InputType inputType, OutputType outType, bool normalized, int stride)
+{
+	m_attribNdx			= attribNdx;
+	m_bound				= true;
+	m_componentCount	= size;
+	m_inputType			= inputType;
+	m_outputType		= outType;
+	m_normalize			= normalized;
+	m_stride			= stride;
+	m_offset			= offset;
+}
+
+void ContextArray::bindIndexArray (Array::Target target)
+{
+	if (m_storage == STORAGE_USER)
+	{
+	}
+	else if (m_storage == STORAGE_BUFFER)
+	{
+		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
+	}
+}
+
+void ContextArray::glBind (deUint32 loc)
+{
+	if (m_storage == STORAGE_BUFFER)
+	{
+		m_ctx.bindBuffer(targetToGL(m_target), m_glBuffer);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");
+
+		if (!inputTypeIsFloatType(m_inputType))
+		{
+			// Input is not float type
+
+			if (outputTypeIsFloatType(m_outputType))
+			{
+				// Output type is float type
+				m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, (GLvoid*)((GLintptr)m_offset));
+				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
+			}
+			else
+			{
+				// Output type is int type
+				m_ctx.vertexAttribIPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_stride, (GLvoid*)((GLintptr)m_offset));
+				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribIPointer()");
+			}
+		}
+		else
+		{
+			// Input type is float type
+
+			// Output type must be float type
+			DE_ASSERT(m_outputType == OUTPUTTYPE_FLOAT || m_outputType == OUTPUTTYPE_VEC2 || m_outputType == OUTPUTTYPE_VEC3 || m_outputType == OUTPUTTYPE_VEC4);
+
+			m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, (GLvoid*)((GLintptr)m_offset));
+			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
+		}
+
+		m_ctx.bindBuffer(targetToGL(m_target), 0);
+	}
+	else if (m_storage == STORAGE_USER)
+	{
+		m_ctx.bindBuffer(targetToGL(m_target), 0);
+		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");
+
+		if (!inputTypeIsFloatType(m_inputType))
+		{
+			// Input is not float type
+
+			if (outputTypeIsFloatType(m_outputType))
+			{
+				// Output type is float type
+				m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, m_data + m_offset);
+				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
+			}
+			else
+			{
+				// Output type is int type
+				m_ctx.vertexAttribIPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_stride, m_data + m_offset);
+				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribIPointer()");
+			}
+		}
+		else
+		{
+			// Input type is float type
+
+			// Output type must be float type
+			DE_ASSERT(m_outputType == OUTPUTTYPE_FLOAT || m_outputType == OUTPUTTYPE_VEC2 || m_outputType == OUTPUTTYPE_VEC3 || m_outputType == OUTPUTTYPE_VEC4);
+
+			m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, m_data + m_offset);
+			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
+		}
+	}
+	else
+		DE_ASSERT(false);
+}
+
+GLenum ContextArray::targetToGL (Array::Target target)
+{
+	DE_ASSERT(target < TARGET_LAST);
+
+	static const GLenum targets[] =
+	{
+		GL_ELEMENT_ARRAY_BUFFER,	// TARGET_ELEMENT_ARRAY = 0,
+		GL_ARRAY_BUFFER				// TARGET_ARRAY,
+	};
+
+	return targets[(int)target];
+}
+
+GLenum ContextArray::usageToGL (Array::Usage usage)
+{
+	DE_ASSERT(usage < USAGE_LAST);
+
+	static const GLenum usages[] =
+	{
+		GL_DYNAMIC_DRAW,	// USAGE_DYNAMIC_DRAW = 0,
+		GL_STATIC_DRAW,		// USAGE_STATIC_DRAW,
+		GL_STREAM_DRAW,		// USAGE_STREAM_DRAW,
+
+		GL_STREAM_READ,		// USAGE_STREAM_READ,
+		GL_STREAM_COPY,		// USAGE_STREAM_COPY,
+
+		GL_STATIC_READ,		// USAGE_STATIC_READ,
+		GL_STATIC_COPY,		// USAGE_STATIC_COPY,
+
+		GL_DYNAMIC_READ,	// USAGE_DYNAMIC_READ,
+		GL_DYNAMIC_COPY		// USAGE_DYNAMIC_COPY,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(usages) == Array::USAGE_LAST);
+
+	return usages[(int)usage];
+}
+
+GLenum ContextArray::inputTypeToGL (Array::InputType type)
+{
+	DE_ASSERT(type < INPUTTYPE_LAST);
+
+	static const GLenum types[] =
+	{
+		GL_FLOAT,				// INPUTTYPE_FLOAT = 0,
+		GL_FIXED,				// INPUTTYPE_FIXED,
+		GL_DOUBLE,				// INPUTTYPE_DOUBLE
+		GL_BYTE,				// INPUTTYPE_BYTE,
+		GL_SHORT,				// INPUTTYPE_SHORT,
+		GL_UNSIGNED_BYTE,		// INPUTTYPE_UNSIGNED_BYTE,
+		GL_UNSIGNED_SHORT,		// INPUTTYPE_UNSIGNED_SHORT,
+
+		GL_INT,					// INPUTTYPE_INT,
+		GL_UNSIGNED_INT,		// INPUTTYPE_UNSIGNED_INT,
+		GL_HALF_FLOAT,			// INPUTTYPE_HALF,
+		GL_UNSIGNED_INT_2_10_10_10_REV, // INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		GL_INT_2_10_10_10_REV			// INPUTTYPE_INT_2_10_10_10,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == Array::INPUTTYPE_LAST);
+
+	return types[(int)type];
+}
+
+std::string ContextArray::outputTypeToGLType (Array::OutputType type)
+{
+	DE_ASSERT(type < OUTPUTTYPE_LAST);
+
+	static const char* types[] =
+	{
+		"float",		// OUTPUTTYPE_FLOAT = 0,
+		"vec2",			// OUTPUTTYPE_VEC2,
+		"vec3",			// OUTPUTTYPE_VEC3,
+		"vec4",			// OUTPUTTYPE_VEC4,
+
+		"int",			// OUTPUTTYPE_INT,
+		"uint",			// OUTPUTTYPE_UINT,
+
+		"ivec2",		// OUTPUTTYPE_IVEC2,
+		"ivec3",		// OUTPUTTYPE_IVEC3,
+		"ivec4",		// OUTPUTTYPE_IVEC4,
+
+		"uvec2",		// OUTPUTTYPE_UVEC2,
+		"uvec3",		// OUTPUTTYPE_UVEC3,
+		"uvec4",		// OUTPUTTYPE_UVEC4,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == Array::OUTPUTTYPE_LAST);
+
+	return types[type];
+}
+
+GLenum ContextArray::primitiveToGL (Array::Primitive primitive)
+{
+	GLenum primitives[] =
+	{
+		GL_POINTS,			// PRIMITIVE_POINTS = 0,
+		GL_TRIANGLES,		// PRIMITIVE_TRIANGLES,
+		GL_TRIANGLE_FAN,	// PRIMITIVE_TRIANGLE_FAN,
+		GL_TRIANGLE_STRIP	// PRIMITIVE_TRIANGLE_STRIP,
+	};
+	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(primitives) == Array::PRIMITIVE_LAST);
+
+	return primitives[(int)primitive];
+}
+
+ContextArrayPack::ContextArrayPack (glu::RenderContext& renderCtx, sglr::Context& drawContext)
+	: m_renderCtx	(renderCtx)
+	, m_ctx			(drawContext)
+	, m_program		(DE_NULL)
+	, m_screen		(std::min(512, renderCtx.getRenderTarget().getWidth()), std::min(512, renderCtx.getRenderTarget().getHeight()))
+{
+}
+
+ContextArrayPack::~ContextArrayPack (void)
+{
+	for (std::vector<ContextArray*>::iterator itr = m_arrays.begin(); itr != m_arrays.end(); itr++)
+		delete *itr;
+
+	delete m_program;
+}
+
+int ContextArrayPack::getArrayCount (void)
+{
+	return (int)m_arrays.size();
+}
+
+void ContextArrayPack::newArray (Array::Storage storage)
+{
+	m_arrays.push_back(new ContextArray(storage, m_ctx));
+}
+
+class ContextShaderProgram : public sglr::ShaderProgram
+{
+public:
+												ContextShaderProgram		(const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays);
+
+	void										shadeVertices				(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
+	void										shadeFragments				(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
+
+private:
+	static std::string							genVertexSource				(const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays);
+	static std::string							genFragmentSource			(const glu::RenderContext& ctx);
+	static rr::GenericVecType					mapOutputType				(const Array::OutputType& type);
+	static int									getComponentCount			(const Array::OutputType& type);
+
+	static sglr::pdec::ShaderProgramDeclaration createProgramDeclaration	(const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays);
+
+	std::vector<int>							m_componentCount;
+	std::vector<rr::GenericVecType>				m_attrType;
+};
+
+ContextShaderProgram::ContextShaderProgram (const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays)
+	: sglr::ShaderProgram	(createProgramDeclaration(ctx, arrays))
+	, m_componentCount		(arrays.size())
+	, m_attrType			(arrays.size())
+{
+	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
+	{
+		m_componentCount[arrayNdx]	= getComponentCount(arrays[arrayNdx]->getOutputType());
+		m_attrType[arrayNdx]		= mapOutputType(arrays[arrayNdx]->getOutputType());
+	}
+}
+
+template <typename T>
+void calcShaderColorCoord (tcu::Vec2& coord, tcu::Vec3& color, const tcu::Vector<T, 4>& attribValue, bool isCoordinate, int numComponents)
+{
+	if (isCoordinate)
+		switch (numComponents)
+		{
+			case 1:	coord = tcu::Vec2((float)attribValue.x(),					(float)attribValue.x());					break;
+			case 2:	coord = tcu::Vec2((float)attribValue.x(),					(float)attribValue.y());					break;
+			case 3:	coord = tcu::Vec2((float)attribValue.x() + attribValue.z(),	(float)attribValue.y());					break;
+			case 4:	coord = tcu::Vec2((float)attribValue.x() + attribValue.z(),	(float)attribValue.y() + attribValue.w());	break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	else
+	{
+		switch (numComponents)
+		{
+			case 1:
+				color = color * (float)attribValue.x();
+				break;
+
+			case 2:
+				color.x() = color.x() * attribValue.x();
+				color.y() = color.y() * attribValue.y();
+				break;
+
+			case 3:
+				color.x() = color.x() * attribValue.x();
+				color.y() = color.y() * attribValue.y();
+				color.z() = color.z() * attribValue.z();
+				break;
+
+			case 4:
+				color.x() = color.x() * attribValue.x() * attribValue.w();
+				color.y() = color.y() * attribValue.y() * attribValue.w();
+				color.z() = color.z() * attribValue.z() * attribValue.w();
+				break;
+
+			default:
+				DE_ASSERT(false);
+		}
+	}
+}
+
+void ContextShaderProgram::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
+{
+	const float	u_coordScale = getUniformByName("u_coordScale").value.f;
+	const float u_colorScale = getUniformByName("u_colorScale").value.f;
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+	{
+		const size_t varyingLocColor = 0;
+
+		rr::VertexPacket& packet = *packets[packetNdx];
+
+		// Calc output color
+		tcu::Vec2 coord = tcu::Vec2(1.0, 1.0);
+		tcu::Vec3 color = tcu::Vec3(1.0, 1.0, 1.0);
+
+		for (int attribNdx = 0; attribNdx < (int)m_attrType.size(); attribNdx++)
+		{
+			const int numComponents = m_componentCount[attribNdx];
+
+			switch (m_attrType[attribNdx])
+			{
+				case rr::GENERICVECTYPE_FLOAT:	calcShaderColorCoord(coord, color, rr::readVertexAttribFloat(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), attribNdx == 0, numComponents);	break;
+				case rr::GENERICVECTYPE_INT32:	calcShaderColorCoord(coord, color, rr::readVertexAttribInt	(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), attribNdx == 0, numComponents);	break;
+				case rr::GENERICVECTYPE_UINT32:	calcShaderColorCoord(coord, color, rr::readVertexAttribUint	(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), attribNdx == 0, numComponents);	break;
+				default:
+					DE_ASSERT(false);
+			}
+		}
+
+		// Transform position
+		{
+			packet.position = tcu::Vec4(u_coordScale * coord.x(), u_coordScale * coord.y(), 1.0f, 1.0f);
+		}
+
+		// Pass color to FS
+		{
+			packet.outputs[varyingLocColor] = tcu::Vec4(u_colorScale * color.x(), u_colorScale * color.y(), u_colorScale * color.z(), 1.0f);
+		}
+	}
+}
+
+void ContextShaderProgram::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
+{
+	const size_t varyingLocColor = 0;
+
+	// Triangles are flashaded
+	tcu::Vec4 color = rr::readTriangleVarying<float>(packets[0], context, varyingLocColor, 0);
+
+	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
+		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
+			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
+}
+
+std::string ContextShaderProgram::genVertexSource (const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays)
+{
+	std::stringstream vertexShaderTmpl;
+	std::map<std::string, std::string> params;
+
+	if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_300_ES))
+	{
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "dEQP_FragColor";
+		params["VTX_HDR"]		= "#version 300 es\n";
+		params["FRAG_HDR"]		= "#version 300 es\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+	}
+	else if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_100_ES))
+	{
+		params["VTX_IN"]		= "attribute";
+		params["VTX_OUT"]		= "varying";
+		params["FRAG_IN"]		= "varying";
+		params["FRAG_COLOR"]	= "gl_FragColor";
+		params["VTX_HDR"]		= "";
+		params["FRAG_HDR"]		= "";
+	}
+	else if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_330))
+	{
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "dEQP_FragColor";
+		params["VTX_HDR"]		= "#version 330\n";
+		params["FRAG_HDR"]		= "#version 330\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	vertexShaderTmpl << "${VTX_HDR}";
+
+	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
+	{
+		vertexShaderTmpl
+			<< "${VTX_IN} highp " <<  ContextArray::outputTypeToGLType(arrays[arrayNdx]->getOutputType()) << " a_" << arrays[arrayNdx]->getAttribNdx() << ";\n";
+	}
+
+	vertexShaderTmpl <<
+		"uniform highp float u_coordScale;\n"
+		"uniform highp float u_colorScale;\n"
+		"${VTX_OUT} mediump vec4 v_color;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tgl_PointSize = 1.0;\n"
+		"\thighp vec2 coord = vec2(1.0, 1.0);\n"
+		"\thighp vec3 color = vec3(1.0, 1.0, 1.0);\n";
+
+	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
+	{
+		if (arrays[arrayNdx]->getAttribNdx() == 0)
+		{
+			switch (arrays[arrayNdx]->getOutputType())
+			{
+				case (Array::OUTPUTTYPE_FLOAT):
+					vertexShaderTmpl <<
+						"\tcoord = vec2(a_0);\n";
+					break;
+
+				case (Array::OUTPUTTYPE_VEC2):
+					vertexShaderTmpl <<
+						"\tcoord = a_0.xy;\n";
+					break;
+
+				case (Array::OUTPUTTYPE_VEC3):
+					vertexShaderTmpl <<
+						"\tcoord = a_0.xy;\n"
+						"\tcoord.x = coord.x + a_0.z;\n";
+					break;
+
+				case (Array::OUTPUTTYPE_VEC4):
+					vertexShaderTmpl <<
+						"\tcoord = a_0.xy;\n"
+						"\tcoord += a_0.zw;\n";
+					break;
+
+				case (Array::OUTPUTTYPE_IVEC2):
+				case (Array::OUTPUTTYPE_UVEC2):
+					vertexShaderTmpl <<
+						"\tcoord = vec2(a_0.xy);\n";
+					break;
+
+				case (Array::OUTPUTTYPE_IVEC3):
+				case (Array::OUTPUTTYPE_UVEC3):
+					vertexShaderTmpl <<
+						"\tcoord = vec2(a_0.xy);\n"
+						"\tcoord.x = coord.x + float(a_0.z);\n";
+					break;
+
+				case (Array::OUTPUTTYPE_IVEC4):
+				case (Array::OUTPUTTYPE_UVEC4):
+					vertexShaderTmpl <<
+						"\tcoord = vec2(a_0.xy);\n"
+						"\tcoord += vec2(a_0.zw);\n";
+					break;
+
+				default:
+					DE_ASSERT(false);
+					break;
+			}
+			continue;
+		}
+
+		switch (arrays[arrayNdx]->getOutputType())
+		{
+			case (Array::OUTPUTTYPE_FLOAT):
+				vertexShaderTmpl <<
+					"\tcolor = color * a_" << arrays[arrayNdx]->getAttribNdx() << ";\n";
+				break;
+
+			case (Array::OUTPUTTYPE_VEC2):
+				vertexShaderTmpl <<
+					"\tcolor.rg = color.rg * a_" << arrays[arrayNdx]->getAttribNdx() << ".xy;\n";
+				break;
+
+			case (Array::OUTPUTTYPE_VEC3):
+				vertexShaderTmpl <<
+					"\tcolor = color.rgb * a_" << arrays[arrayNdx]->getAttribNdx() << ".xyz;\n";
+				break;
+
+			case (Array::OUTPUTTYPE_VEC4):
+				vertexShaderTmpl <<
+					"\tcolor = color.rgb * a_" << arrays[arrayNdx]->getAttribNdx() << ".xyz * a_" << arrays[arrayNdx]->getAttribNdx() << ".w;\n";
+				break;
+
+			default:
+				DE_ASSERT(false);
+				break;
+		}
+	}
+
+	vertexShaderTmpl <<
+		"\tv_color = vec4(u_colorScale * color, 1.0);\n"
+		"\tgl_Position = vec4(u_coordScale * coord, 1.0, 1.0);\n"
+		"}\n";
+
+	return tcu::StringTemplate(vertexShaderTmpl.str().c_str()).specialize(params);
+}
+
+std::string ContextShaderProgram::genFragmentSource (const glu::RenderContext& ctx)
+{
+	std::map<std::string, std::string> params;
+
+	if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_300_ES))
+	{
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "dEQP_FragColor";
+		params["VTX_HDR"]		= "#version 300 es\n";
+		params["FRAG_HDR"]		= "#version 300 es\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+	}
+	else if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_100_ES))
+	{
+		params["VTX_IN"]		= "attribute";
+		params["VTX_OUT"]		= "varying";
+		params["FRAG_IN"]		= "varying";
+		params["FRAG_COLOR"]	= "gl_FragColor";
+		params["VTX_HDR"]		= "";
+		params["FRAG_HDR"]		= "";
+	}
+	else if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_330))
+	{
+		params["VTX_IN"]		= "in";
+		params["VTX_OUT"]		= "out";
+		params["FRAG_IN"]		= "in";
+		params["FRAG_COLOR"]	= "dEQP_FragColor";
+		params["VTX_HDR"]		= "#version 330\n";
+		params["FRAG_HDR"]		= "#version 330\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
+	}
+	else
+		DE_ASSERT(DE_FALSE);
+
+	static const char* fragmentShaderTmpl =
+		"${FRAG_HDR}"
+		"${FRAG_IN} mediump vec4 v_color;\n"
+		"void main(void)\n"
+		"{\n"
+		"\t${FRAG_COLOR} = v_color;\n"
+		"}\n";
+
+	return tcu::StringTemplate(fragmentShaderTmpl).specialize(params);
+}
+
+rr::GenericVecType ContextShaderProgram::mapOutputType (const Array::OutputType& type)
+{
+	switch (type)
+	{
+		case (Array::OUTPUTTYPE_FLOAT):
+		case (Array::OUTPUTTYPE_VEC2):
+		case (Array::OUTPUTTYPE_VEC3):
+		case (Array::OUTPUTTYPE_VEC4):
+			return rr::GENERICVECTYPE_FLOAT;
+
+		case (Array::OUTPUTTYPE_INT):
+		case (Array::OUTPUTTYPE_IVEC2):
+		case (Array::OUTPUTTYPE_IVEC3):
+		case (Array::OUTPUTTYPE_IVEC4):
+			return rr::GENERICVECTYPE_INT32;
+
+		case (Array::OUTPUTTYPE_UINT):
+		case (Array::OUTPUTTYPE_UVEC2):
+		case (Array::OUTPUTTYPE_UVEC3):
+		case (Array::OUTPUTTYPE_UVEC4):
+			return rr::GENERICVECTYPE_UINT32;
+
+		default:
+			DE_ASSERT(false);
+			return rr::GENERICVECTYPE_LAST;
+	}
+}
+
+int ContextShaderProgram::getComponentCount (const Array::OutputType& type)
+{
+	switch (type)
+	{
+		case (Array::OUTPUTTYPE_FLOAT):
+		case (Array::OUTPUTTYPE_INT):
+		case (Array::OUTPUTTYPE_UINT):
+			return 1;
+
+		case (Array::OUTPUTTYPE_VEC2):
+		case (Array::OUTPUTTYPE_IVEC2):
+		case (Array::OUTPUTTYPE_UVEC2):
+			return 2;
+
+		case (Array::OUTPUTTYPE_VEC3):
+		case (Array::OUTPUTTYPE_IVEC3):
+		case (Array::OUTPUTTYPE_UVEC3):
+			return 3;
+
+		case (Array::OUTPUTTYPE_VEC4):
+		case (Array::OUTPUTTYPE_IVEC4):
+		case (Array::OUTPUTTYPE_UVEC4):
+			return 4;
+
+		default:
+			DE_ASSERT(false);
+			return 0;
+	}
+}
+
+sglr::pdec::ShaderProgramDeclaration ContextShaderProgram::createProgramDeclaration (const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays)
+{
+	sglr::pdec::ShaderProgramDeclaration decl;
+
+	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
+		decl << sglr::pdec::VertexAttribute(std::string("a_") + de::toString(arrayNdx), mapOutputType(arrays[arrayNdx]->getOutputType()));
+
+	decl << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT);
+	decl << sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT);
+
+	decl << sglr::pdec::VertexSource(genVertexSource(ctx, arrays));
+	decl << sglr::pdec::FragmentSource(genFragmentSource(ctx));
+
+	decl << sglr::pdec::Uniform("u_coordScale", glu::TYPE_FLOAT);
+	decl << sglr::pdec::Uniform("u_colorScale", glu::TYPE_FLOAT);
+
+	return decl;
+}
+
+void ContextArrayPack::updateProgram (void)
+{
+	delete m_program;
+	m_program = new ContextShaderProgram(m_renderCtx, m_arrays);
+}
+
+void ContextArrayPack::render (Array::Primitive primitive, int firstVertex, int vertexCount, bool useVao, float coordScale, float colorScale)
+{
+	deUint32 program = 0;
+	deUint32 vaoId = 0;
+
+	updateProgram();
+
+	m_ctx.viewport(0, 0, m_screen.getWidth(), m_screen.getHeight());
+	m_ctx.clearColor(0.0, 0.0, 0.0, 1.0);
+	m_ctx.clear(GL_COLOR_BUFFER_BIT);
+
+	program = m_ctx.createProgram(m_program);
+
+	m_ctx.useProgram(program);
+	GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glUseProgram()");
+
+	m_ctx.uniform1f(m_ctx.getUniformLocation(program, "u_coordScale"), coordScale);
+	m_ctx.uniform1f(m_ctx.getUniformLocation(program, "u_colorScale"), colorScale);
+
+	if (useVao)
+	{
+		m_ctx.genVertexArrays(1, &vaoId);
+		m_ctx.bindVertexArray(vaoId);
+	}
+
+	for (int arrayNdx = 0; arrayNdx < (int)m_arrays.size(); arrayNdx++)
+	{
+		if (m_arrays[arrayNdx]->isBound())
+		{
+			std::stringstream attribName;
+			attribName << "a_" << m_arrays[arrayNdx]->getAttribNdx();
+
+			deUint32 loc = m_ctx.getAttribLocation(program, attribName.str().c_str());
+			m_ctx.enableVertexAttribArray(loc);
+			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glEnableVertexAttribArray()");
+
+			m_arrays[arrayNdx]->glBind(loc);
+		}
+	}
+
+	DE_ASSERT((firstVertex % 6) == 0);
+	m_ctx.drawArrays(ContextArray::primitiveToGL(primitive), firstVertex, vertexCount - firstVertex);
+	GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArrays()");
+
+	for (int arrayNdx = 0; arrayNdx < (int)m_arrays.size(); arrayNdx++)
+	{
+		if (m_arrays[arrayNdx]->isBound())
+		{
+			std::stringstream attribName;
+			attribName << "a_" << m_arrays[arrayNdx]->getAttribNdx();
+
+			deUint32 loc = m_ctx.getAttribLocation(program, attribName.str().c_str());
+
+			m_ctx.disableVertexAttribArray(loc);
+			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDisableVertexAttribArray()");
+		}
+	}
+
+	if (useVao)
+		m_ctx.deleteVertexArrays(1, &vaoId);
+
+	m_ctx.deleteProgram(program);
+	m_ctx.useProgram(0);
+	m_ctx.readPixels(m_screen, 0, 0, m_screen.getWidth(), m_screen.getHeight());
+}
+
+// GLValue
+
+GLValue GLValue::getMaxValue (Array::InputType type)
+{
+	GLValue rangesHi[(int)Array::INPUTTYPE_LAST];
+
+	rangesHi[(int)Array::INPUTTYPE_FLOAT]			= GLValue(Float::create(127.0f));
+	rangesHi[(int)Array::INPUTTYPE_DOUBLE]			= GLValue(Double::create(127.0f));
+	rangesHi[(int)Array::INPUTTYPE_BYTE]			= GLValue(Byte::create(127));
+	rangesHi[(int)Array::INPUTTYPE_UNSIGNED_BYTE]	= GLValue(Ubyte::create(255));
+	rangesHi[(int)Array::INPUTTYPE_UNSIGNED_SHORT]	= GLValue(Ushort::create(65530));
+	rangesHi[(int)Array::INPUTTYPE_SHORT]			= GLValue(Short::create(32760));
+	rangesHi[(int)Array::INPUTTYPE_FIXED]			= GLValue(Fixed::create(32760));
+	rangesHi[(int)Array::INPUTTYPE_INT]				= GLValue(Int::create(2147483647));
+	rangesHi[(int)Array::INPUTTYPE_UNSIGNED_INT]	= GLValue(Uint::create(4294967295u));
+	rangesHi[(int)Array::INPUTTYPE_HALF]			= GLValue(Half::create(256.0f));
+
+	return rangesHi[(int)type];
+}
+
+GLValue GLValue::getMinValue (Array::InputType type)
+{
+	GLValue rangesLo[(int)Array::INPUTTYPE_LAST];
+
+	rangesLo[(int)Array::INPUTTYPE_FLOAT]			= GLValue(Float::create(-127.0f));
+	rangesLo[(int)Array::INPUTTYPE_DOUBLE]			= GLValue(Double::create(-127.0f));
+	rangesLo[(int)Array::INPUTTYPE_BYTE]			= GLValue(Byte::create(-127));
+	rangesLo[(int)Array::INPUTTYPE_UNSIGNED_BYTE]	= GLValue(Ubyte::create(0));
+	rangesLo[(int)Array::INPUTTYPE_UNSIGNED_SHORT]	= GLValue(Ushort::create(0));
+	rangesLo[(int)Array::INPUTTYPE_SHORT]			= GLValue(Short::create(-32760));
+	rangesLo[(int)Array::INPUTTYPE_FIXED]			= GLValue(Fixed::create(-32760));
+	rangesLo[(int)Array::INPUTTYPE_INT]				= GLValue(Int::create(-2147483647));
+	rangesLo[(int)Array::INPUTTYPE_UNSIGNED_INT]	= GLValue(Uint::create(0));
+	rangesLo[(int)Array::INPUTTYPE_HALF]			= GLValue(Half::create(-256.0f));
+
+	return rangesLo[(int)type];
+}
+
+float GLValue::toFloat (void) const
+{
+	switch (type)
+	{
+		case Array::INPUTTYPE_FLOAT:
+			return fl.getValue();
+			break;
+
+		case Array::INPUTTYPE_BYTE:
+			return b.getValue();
+			break;
+
+		case Array::INPUTTYPE_UNSIGNED_BYTE:
+			return ub.getValue();
+			break;
+
+		case Array::INPUTTYPE_SHORT:
+			return s.getValue();
+			break;
+
+		case Array::INPUTTYPE_UNSIGNED_SHORT:
+			return us.getValue();
+			break;
+
+		case Array::INPUTTYPE_FIXED:
+		{
+			int maxValue = 65536;
+			return (float)(double(2 * fi.getValue() + 1) / (maxValue - 1));
+
+			break;
+		}
+
+		case Array::INPUTTYPE_UNSIGNED_INT:
+			return (float)ui.getValue();
+			break;
+
+		case Array::INPUTTYPE_INT:
+			return (float)i.getValue();
+			break;
+
+		case Array::INPUTTYPE_HALF:
+			return h.to<float>();
+			break;
+
+		case Array::INPUTTYPE_DOUBLE:
+			return (float)d.getValue();
+			break;
+
+		default:
+			DE_ASSERT(false);
+			return 0.0f;
+			break;
+	};
+}
+
+class RandomArrayGenerator
+{
+public:
+	static char*	generateArray			(int seed, GLValue min, GLValue max, int count, int componentCount, int stride, Array::InputType type);
+	static char*	generateQuads			(int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive, Array::InputType type, GLValue min, GLValue max);
+	static char*	generatePerQuad			(int seed, int count, int componentCount, int stride, Array::Primitive primitive, Array::InputType type, GLValue min, GLValue max);
+
+private:
+	template<typename T>
+	static char*	createQuads		(int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive, T min, T max);
+	template<typename T>
+	static char*	createPerQuads	(int seed, int count, int componentCount, int stride, Array::Primitive primitive, T min, T max);
+	static char*	createQuadsPacked (int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive);
+	static void		setData			(char* data, Array::InputType type, deRandom& rnd, GLValue min, GLValue max);
+};
+
+void RandomArrayGenerator::setData (char* data, Array::InputType type, deRandom& rnd, GLValue min, GLValue max)
+{
+	switch (type)
+	{
+		case Array::INPUTTYPE_FLOAT:
+		{
+			alignmentSafeAssignment<float>(data, getRandom<GLValue::Float>(rnd, min.fl, max.fl));
+			break;
+		}
+
+		case Array::INPUTTYPE_DOUBLE:
+		{
+			alignmentSafeAssignment<double>(data, getRandom<GLValue::Float>(rnd, min.fl, max.fl));
+			break;
+		}
+
+		case Array::INPUTTYPE_SHORT:
+		{
+			alignmentSafeAssignment<deInt16>(data, getRandom<GLValue::Short>(rnd, min.s, max.s));
+			break;
+		}
+
+		case Array::INPUTTYPE_UNSIGNED_SHORT:
+		{
+			alignmentSafeAssignment<deUint16>(data, getRandom<GLValue::Ushort>(rnd, min.us, max.us));
+			break;
+		}
+
+		case Array::INPUTTYPE_BYTE:
+		{
+			alignmentSafeAssignment<deInt8>(data, getRandom<GLValue::Byte>(rnd, min.b, max.b));
+			break;
+		}
+
+		case Array::INPUTTYPE_UNSIGNED_BYTE:
+		{
+			alignmentSafeAssignment<deUint8>(data, getRandom<GLValue::Ubyte>(rnd, min.ub, max.ub));
+			break;
+		}
+
+		case Array::INPUTTYPE_FIXED:
+		{
+			alignmentSafeAssignment<deInt32>(data, getRandom<GLValue::Fixed>(rnd, min.fi, max.fi));
+			break;
+		}
+
+		case Array::INPUTTYPE_INT:
+		{
+			alignmentSafeAssignment<deInt32>(data, getRandom<GLValue::Int>(rnd, min.i, max.i));
+			break;
+		}
+
+		case Array::INPUTTYPE_UNSIGNED_INT:
+		{
+			alignmentSafeAssignment<deUint32>(data, getRandom<GLValue::Uint>(rnd, min.ui, max.ui));
+			break;
+		}
+
+		case Array::INPUTTYPE_HALF:
+		{
+			alignmentSafeAssignment<deFloat16>(data, getRandom<GLValue::Half>(rnd, min.h, max.h).getValue());
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+}
+
+char* RandomArrayGenerator::generateArray (int seed, GLValue min, GLValue max, int count, int componentCount, int stride, Array::InputType type)
+{
+	char* data = NULL;
+
+	deRandom rnd;
+	deRandom_init(&rnd, seed);
+
+	if (stride == 0)
+		stride = componentCount * Array::inputTypeSize(type);
+
+	data = new char[stride * count];
+
+	for (int vertexNdx = 0; vertexNdx < count; vertexNdx++)
+	{
+		for (int componentNdx = 0; componentNdx < componentCount; componentNdx++)
+		{
+			setData(&(data[vertexNdx * stride + Array::inputTypeSize(type) * componentNdx]), type, rnd, min, max);
+		}
+	}
+
+	return data;
+}
+
+char* RandomArrayGenerator::generateQuads (int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive, Array::InputType type, GLValue min, GLValue max)
+{
+	char* data = DE_NULL;
+
+	switch (type)
+	{
+		case Array::INPUTTYPE_FLOAT:
+			data = createQuads<GLValue::Float>(seed, count, componentCount, offset, stride, primitive, min.fl, max.fl);
+			break;
+
+		case Array::INPUTTYPE_FIXED:
+			data = createQuads<GLValue::Fixed>(seed, count, componentCount, offset, stride, primitive, min.fi, max.fi);
+			break;
+
+		case Array::INPUTTYPE_DOUBLE:
+			data = createQuads<GLValue::Double>(seed, count, componentCount, offset, stride, primitive, min.d, max.d);
+			break;
+
+		case Array::INPUTTYPE_BYTE:
+			data = createQuads<GLValue::Byte>(seed, count, componentCount, offset, stride, primitive, min.b, max.b);
+			break;
+
+		case Array::INPUTTYPE_SHORT:
+			data = createQuads<GLValue::Short>(seed, count, componentCount, offset, stride, primitive, min.s, max.s);
+			break;
+
+		case Array::INPUTTYPE_UNSIGNED_BYTE:
+			data = createQuads<GLValue::Ubyte>(seed, count, componentCount, offset, stride, primitive, min.ub, max.ub);
+			break;
+
+		case Array::INPUTTYPE_UNSIGNED_SHORT:
+			data = createQuads<GLValue::Ushort>(seed, count, componentCount, offset, stride, primitive, min.us, max.us);
+			break;
+
+		case Array::INPUTTYPE_UNSIGNED_INT:
+			data = createQuads<GLValue::Uint>(seed, count, componentCount, offset, stride, primitive, min.ui, max.ui);
+			break;
+
+		case Array::INPUTTYPE_INT:
+			data = createQuads<GLValue::Int>(seed, count, componentCount, offset, stride, primitive, min.i, max.i);
+			break;
+
+		case Array::INPUTTYPE_HALF:
+			data = createQuads<GLValue::Half>(seed, count, componentCount, offset, stride, primitive, min.h, max.h);
+			break;
+
+		case Array::INPUTTYPE_INT_2_10_10_10:
+		case Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10:
+			data = createQuadsPacked(seed, count, componentCount, offset, stride, primitive);
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	return data;
+}
+
+char* RandomArrayGenerator::createQuadsPacked (int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive)
+{
+	DE_ASSERT(componentCount == 4);
+	DE_UNREF(componentCount);
+	int quadStride = 0;
+
+	if (stride == 0)
+		stride = sizeof(deUint32);
+
+	switch (primitive)
+	{
+		case Array::PRIMITIVE_TRIANGLES:
+			quadStride = stride * 6;
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	char* const _data		= new char[offset + quadStride * (count - 1) + stride * 5 + componentCount * Array::inputTypeSize(Array::INPUTTYPE_INT_2_10_10_10)]; // last element must be fully in the array
+	char* const resultData	= _data + offset;
+
+	const deUint32 max		= 1024;
+	const deUint32 min		= 10;
+	const deUint32 max2		= 4;
+
+	deRandom rnd;
+	deRandom_init(&rnd,  seed);
+
+	switch (primitive)
+	{
+		case Array::PRIMITIVE_TRIANGLES:
+		{
+			for (int quadNdx = 0; quadNdx < count; quadNdx++)
+			{
+				deUint32 x1	= min + deRandom_getUint32(&rnd) % (max - min);
+				deUint32 x2	= min + deRandom_getUint32(&rnd) % (max - x1);
+
+				deUint32 y1	= min + deRandom_getUint32(&rnd) % (max - min);
+				deUint32 y2	= min + deRandom_getUint32(&rnd) % (max - y1);
+
+				deUint32 z	= min + deRandom_getUint32(&rnd) % (max - min);
+				deUint32 w	= deRandom_getUint32(&rnd) % max2;
+
+				deUint32 val1 = (w << 30) | (z << 20) | (y1 << 10) | x1;
+				deUint32 val2 = (w << 30) | (z << 20) | (y1 << 10) | x2;
+				deUint32 val3 = (w << 30) | (z << 20) | (y2 << 10) | x1;
+
+				deUint32 val4 = (w << 30) | (z << 20) | (y2 << 10) | x1;
+				deUint32 val5 = (w << 30) | (z << 20) | (y1 << 10) | x2;
+				deUint32 val6 = (w << 30) | (z << 20) | (y2 << 10) | x2;
+
+				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 0]), val1);
+				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 1]), val2);
+				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 2]), val3);
+				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 3]), val4);
+				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 4]), val5);
+				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 5]), val6);
+			}
+
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	return _data;
+}
+
+template<typename T>
+char* RandomArrayGenerator::createQuads (int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive, T min, T max)
+{
+	int componentStride = sizeof(T);
+	int quadStride = 0;
+
+	if (stride == 0)
+		stride = componentCount * componentStride;
+	DE_ASSERT(stride >= componentCount * componentStride);
+
+	switch (primitive)
+	{
+		case Array::PRIMITIVE_TRIANGLES:
+			quadStride = stride * 6;
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	char* resultData = new char[offset + quadStride * count];
+	char* _data = resultData;
+	resultData = resultData + offset;
+
+	deRandom rnd;
+	deRandom_init(&rnd,  seed);
+
+	switch (primitive)
+	{
+		case Array::PRIMITIVE_TRIANGLES:
+		{
+			for (int quadNdx = 0; quadNdx < count; ++quadNdx)
+			{
+				T x1, x2;
+				T y1, y2;
+				T z, w;
+
+				// attempt to find a good (i.e not extremely small) quad
+				for (int attemptNdx = 0; attemptNdx < 4; ++attemptNdx)
+				{
+					x1 = getRandom<T>(rnd, min, max);
+					x2 = getRandom<T>(rnd, minValue<T>(), abs<T>(max - x1));
+
+					y1 = getRandom<T>(rnd, min, max);
+					y2 = getRandom<T>(rnd, minValue<T>(), abs<T>(max - y1));
+
+					z = (componentCount > 2) ? (getRandom<T>(rnd, min, max)) : (T::create(0));
+					w = (componentCount > 3) ? (getRandom<T>(rnd, min, max)) : (T::create(1));
+
+					// no additional components, all is good
+					if (componentCount <= 2)
+						break;
+
+					// The result quad is too thin?
+					if ((deFloatAbs(x2.template to<float>() + z.template to<float>()) < minValue<T>().template to<float>()) ||
+						(deFloatAbs(y2.template to<float>() + w.template to<float>()) < minValue<T>().template to<float>()))
+						continue;
+
+					// all ok
+					break;
+				}
+
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride]), x1);
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + componentStride]), y1);
+
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride]), x1 + x2);
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride + componentStride]), y1);
+
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 2]), x1);
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 2 + componentStride]), y1 + y2);
+
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 3]), x1);
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 3 + componentStride]), y1 + y2);
+
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 4]), x1 + x2);
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 4 + componentStride]), y1);
+
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 5]), x1 + x2);
+				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 5 + componentStride]), y1 + y2);
+
+				if (componentCount > 2)
+				{
+					for (int i = 0; i < 6; i++)
+						alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * i + componentStride * 2]), z);
+				}
+
+				if (componentCount > 3)
+				{
+					for (int i = 0; i < 6; i++)
+						alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * i + componentStride * 3]), w);
+				}
+			}
+
+			break;
+		}
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	return _data;
+}
+
+char* RandomArrayGenerator::generatePerQuad (int seed, int count, int componentCount, int stride, Array::Primitive primitive, Array::InputType type, GLValue min, GLValue max)
+{
+	char* data = DE_NULL;
+
+	switch (type)
+	{
+		case Array::INPUTTYPE_FLOAT:
+			data = createPerQuads<GLValue::Float>(seed, count, componentCount, stride, primitive, min.fl, max.fl);
+			break;
+
+		case Array::INPUTTYPE_FIXED:
+			data = createPerQuads<GLValue::Fixed>(seed, count, componentCount, stride, primitive, min.fi, max.fi);
+			break;
+
+		case Array::INPUTTYPE_DOUBLE:
+			data = createPerQuads<GLValue::Double>(seed, count, componentCount, stride, primitive, min.d, max.d);
+			break;
+
+		case Array::INPUTTYPE_BYTE:
+			data = createPerQuads<GLValue::Byte>(seed, count, componentCount, stride, primitive, min.b, max.b);
+			break;
+
+		case Array::INPUTTYPE_SHORT:
+			data = createPerQuads<GLValue::Short>(seed, count, componentCount, stride, primitive, min.s, max.s);
+			break;
+
+		case Array::INPUTTYPE_UNSIGNED_BYTE:
+			data = createPerQuads<GLValue::Ubyte>(seed, count, componentCount, stride, primitive, min.ub, max.ub);
+			break;
+
+		case Array::INPUTTYPE_UNSIGNED_SHORT:
+			data = createPerQuads<GLValue::Ushort>(seed, count, componentCount, stride, primitive, min.us, max.us);
+			break;
+
+		case Array::INPUTTYPE_UNSIGNED_INT:
+			data = createPerQuads<GLValue::Uint>(seed, count, componentCount, stride, primitive, min.ui, max.ui);
+			break;
+
+		case Array::INPUTTYPE_INT:
+			data = createPerQuads<GLValue::Int>(seed, count, componentCount, stride, primitive, min.i, max.i);
+			break;
+
+		case Array::INPUTTYPE_HALF:
+			data = createPerQuads<GLValue::Half>(seed, count, componentCount, stride, primitive, min.h, max.h);
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	return data;
+}
+
+template<typename T>
+char* RandomArrayGenerator::createPerQuads (int seed, int count, int componentCount, int stride, Array::Primitive primitive, T min, T max)
+{
+	deRandom rnd;
+	deRandom_init(&rnd, seed);
+
+	int componentStride = sizeof(T);
+
+	if (stride == 0)
+		stride = componentStride * componentCount;
+
+	int quadStride = 0;
+
+	switch (primitive)
+	{
+		case Array::PRIMITIVE_TRIANGLES:
+			quadStride = stride * 6;
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	char* data = new char[count * quadStride];
+
+	for (int quadNdx = 0; quadNdx < count; quadNdx++)
+	{
+		for (int componentNdx = 0; componentNdx < componentCount; componentNdx++)
+		{
+			T val = getRandom<T>(rnd, min, max);
+
+			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 0 + componentStride * componentNdx, val);
+			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 1 + componentStride * componentNdx, val);
+			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 2 + componentStride * componentNdx, val);
+			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 3 + componentStride * componentNdx, val);
+			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 4 + componentStride * componentNdx, val);
+			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 5 + componentStride * componentNdx, val);
+		}
+	}
+
+	return data;
+}
+
+// VertexArrayTest
+
+VertexArrayTest::VertexArrayTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name ,const char* desc)
+	: TestCase			(testCtx, name, desc)
+	, m_renderCtx		(renderCtx)
+	, m_refBuffers		(DE_NULL)
+	, m_refContext		(DE_NULL)
+	, m_glesContext		(DE_NULL)
+	, m_glArrayPack		(DE_NULL)
+	, m_rrArrayPack		(DE_NULL)
+	, m_isOk			(false)
+	, m_maxDiffRed		(deCeilFloatToInt32(256.0f * (2.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().redBits))))
+	, m_maxDiffGreen	(deCeilFloatToInt32(256.0f * (2.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().greenBits))))
+	, m_maxDiffBlue		(deCeilFloatToInt32(256.0f * (2.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().blueBits))))
+{
+}
+
+VertexArrayTest::~VertexArrayTest (void)
+{
+	deinit();
+}
+
+void VertexArrayTest::init (void)
+{
+	const int						renderTargetWidth	= de::min(512, m_renderCtx.getRenderTarget().getWidth());
+	const int						renderTargetHeight	= de::min(512, m_renderCtx.getRenderTarget().getHeight());
+	sglr::ReferenceContextLimits	limits				(m_renderCtx);
+
+	m_glesContext		= new sglr::GLContext(m_renderCtx, m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS | sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(0, 0, renderTargetWidth, renderTargetHeight));
+
+	m_refBuffers		= new sglr::ReferenceContextBuffers(m_renderCtx.getRenderTarget().getPixelFormat(), 0, 0, renderTargetWidth, renderTargetHeight);
+	m_refContext		= new sglr::ReferenceContext(limits, m_refBuffers->getColorbuffer(), m_refBuffers->getDepthbuffer(), m_refBuffers->getStencilbuffer());
+
+	m_glArrayPack		= new ContextArrayPack(m_renderCtx, *m_glesContext);
+	m_rrArrayPack		= new ContextArrayPack(m_renderCtx, *m_refContext);
+}
+
+void VertexArrayTest::deinit (void)
+{
+	delete m_glArrayPack;
+	delete m_rrArrayPack;
+	delete m_refBuffers;
+	delete m_refContext;
+	delete m_glesContext;
+
+	m_glArrayPack	= DE_NULL;
+	m_rrArrayPack	= DE_NULL;
+	m_refBuffers	= DE_NULL;
+	m_refContext	= DE_NULL;
+	m_glesContext	= DE_NULL;
+}
+
+void VertexArrayTest::compare (void)
+{
+	const tcu::Surface&	ref		= m_rrArrayPack->getSurface();
+	const tcu::Surface&	screen	= m_glArrayPack->getSurface();
+
+	if (m_renderCtx.getRenderTarget().getNumSamples() > 1)
+	{
+		// \todo [mika] Improve compare when using multisampling
+		m_testCtx.getLog() << tcu::TestLog::Message << "Warning: Comparision of result from multisample render targets are not as stricts as without multisampling. Might produce false positives!" << tcu::TestLog::EndMessage;
+		m_isOk = tcu::fuzzyCompare(m_testCtx.getLog(), "Compare Results", "Compare Results", ref.getAccess(), screen.getAccess(), 1.5f, tcu::COMPARE_LOG_RESULT);
+	}
+	else
+	{
+		tcu::RGBA		threshold	(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue, 255);
+		tcu::Surface	error		(ref.getWidth(), ref.getHeight());
+
+		m_isOk = true;
+
+		for (int y = 1; y < ref.getHeight()-1; y++)
+		{
+			for (int x = 1; x < ref.getWidth()-1; x++)
+			{
+				tcu::RGBA	refPixel		= ref.getPixel(x, y);
+				tcu::RGBA	screenPixel		= screen.getPixel(x, y);
+				bool		isOkPixel		= false;
+
+				// Don't do comparisons for this pixel if it belongs to a one-pixel-thin part (i.e. it doesn't have similar-color neighbors in both x and y directions) in both result and reference.
+				// This fixes some false negatives.
+				bool		refThin			= (!tcu::compareThreshold(refPixel, ref.getPixel(x-1, y  ), threshold) && !tcu::compareThreshold(refPixel, ref.getPixel(x+1, y  ), threshold)) ||
+											  (!tcu::compareThreshold(refPixel, ref.getPixel(x  , y-1), threshold) && !tcu::compareThreshold(refPixel, ref.getPixel(x  , y+1), threshold));
+				bool		screenThin		= (!tcu::compareThreshold(screenPixel, screen.getPixel(x-1, y  ), threshold) && !tcu::compareThreshold(screenPixel, screen.getPixel(x+1, y  ), threshold)) ||
+											  (!tcu::compareThreshold(screenPixel, screen.getPixel(x  , y-1), threshold) && !tcu::compareThreshold(screenPixel, screen.getPixel(x  , y+1), threshold));
+
+				if (refThin && screenThin)
+					isOkPixel = true;
+				else
+				{
+					for (int dy = -1; dy < 2 && !isOkPixel; dy++)
+					{
+						for (int dx = -1; dx < 2 && !isOkPixel; dx++)
+						{
+							// Check reference pixel against screen pixel
+							{
+								tcu::RGBA	screenCmpPixel	= screen.getPixel(x+dx, y+dy);
+								deUint8		r				= deAbs32(refPixel.getRed()		- screenCmpPixel.getRed());
+								deUint8		g				= deAbs32(refPixel.getGreen()	- screenCmpPixel.getGreen());
+								deUint8		b				= deAbs32(refPixel.getBlue()	- screenCmpPixel.getBlue());
+
+								if (r <= m_maxDiffRed && g <= m_maxDiffGreen && b <= m_maxDiffBlue)
+									isOkPixel = true;
+							}
+
+							// Check screen pixels against reference pixel
+							{
+								tcu::RGBA	refCmpPixel		= ref.getPixel(x+dx, y+dy);
+								deUint8		r				= deAbs32(refCmpPixel.getRed()		- screenPixel.getRed());
+								deUint8		g				= deAbs32(refCmpPixel.getGreen()	- screenPixel.getGreen());
+								deUint8		b				= deAbs32(refCmpPixel.getBlue()		- screenPixel.getBlue());
+
+								if (r <= m_maxDiffRed && g <= m_maxDiffGreen && b <= m_maxDiffBlue)
+									isOkPixel = true;
+							}
+						}
+					}
+				}
+
+				if (isOkPixel)
+					error.setPixel(x, y, tcu::RGBA(screen.getPixel(x, y).getRed(), (screen.getPixel(x, y).getGreen() + 255) / 2, screen.getPixel(x, y).getBlue(), 255));
+				else
+				{
+					error.setPixel(x, y, tcu::RGBA(255, 0, 0, 255));
+					m_isOk = false;
+				}
+			}
+		}
+
+		tcu::TestLog& log = m_testCtx.getLog();
+		if (!m_isOk)
+		{
+			log << TestLog::Message << "Image comparison failed, threshold = (" << m_maxDiffRed << ", " << m_maxDiffGreen << ", " << m_maxDiffBlue << ")" << TestLog::EndMessage;
+			log << TestLog::ImageSet("Compare result", "Result of rendering")
+				<< TestLog::Image("Result",		"Result",		screen)
+				<< TestLog::Image("Reference",	"Reference",	ref)
+				<< TestLog::Image("ErrorMask",	"Error mask",	error)
+				<< TestLog::EndImageSet;
+		}
+		else
+		{
+			log << TestLog::ImageSet("Compare result", "Result of rendering")
+				<< TestLog::Image("Result", "Result", screen)
+				<< TestLog::EndImageSet;
+		}
+	}
+}
+
+// MultiVertexArrayTest
+
+MultiVertexArrayTest::Spec::ArraySpec::ArraySpec(Array::InputType inputType_, Array::OutputType outputType_, Array::Storage storage_, Array::Usage usage_, int componentCount_, int offset_, int stride_, bool normalize_, GLValue min_, GLValue max_)
+	: inputType		(inputType_)
+	, outputType	(outputType_)
+	, storage		(storage_)
+	, usage			(usage_)
+	, componentCount(componentCount_)
+	, offset		(offset_)
+	, stride		(stride_)
+	, normalize		(normalize_)
+	, min			(min_)
+	, max			(max_)
+{
+}
+
+std::string MultiVertexArrayTest::Spec::getName (void) const
+{
+	std::stringstream name;
+
+	for (size_t ndx = 0; ndx < arrays.size(); ++ndx)
+	{
+		const ArraySpec& array = arrays[ndx];
+
+		if (arrays.size() > 1)
+			name << "array" << ndx << "_";
+
+		name
+			<< Array::storageToString(array.storage) << "_"
+			<< array.offset << "_"
+			<< array.stride << "_"
+			<< Array::inputTypeToString((Array::InputType)array.inputType);
+		if (array.inputType != Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 && array.inputType != Array::INPUTTYPE_INT_2_10_10_10)
+			name << array.componentCount;
+		name
+			<< "_"
+			<< (array.normalize ? "normalized_" : "")
+			<< Array::outputTypeToString(array.outputType) << "_"
+			<< Array::usageTypeToString(array.usage) << "_";
+	}
+
+	if (first)
+		name << "first" << first << "_";
+
+	switch (primitive)
+	{
+		case Array::PRIMITIVE_TRIANGLES:
+			name << "quads_";
+			break;
+		case Array::PRIMITIVE_POINTS:
+			name << "points_";
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+	name << drawCount;
+
+	return name.str();
+}
+
+std::string MultiVertexArrayTest::Spec::getDesc (void) const
+{
+	std::stringstream desc;
+
+	for (size_t ndx = 0; ndx < arrays.size(); ++ndx)
+	{
+		const ArraySpec& array = arrays[ndx];
+
+		desc
+			<< "Array " << ndx << ": "
+			<< "Storage in " << Array::storageToString(array.storage) << ", "
+			<< "stride " << array.stride << ", "
+			<< "input datatype " << Array::inputTypeToString((Array::InputType)array.inputType) << ", "
+			<< "input component count " << array.componentCount << ", "
+			<< (array.normalize ? "normalized, " : "")
+			<< "used as " << Array::outputTypeToString(array.outputType) << ", ";
+	}
+
+	desc
+		<< "drawArrays(), "
+		<< "first " << first << ", "
+		<< drawCount;
+
+	switch (primitive)
+	{
+		case Array::PRIMITIVE_TRIANGLES:
+			desc << "quads ";
+			break;
+		case Array::PRIMITIVE_POINTS:
+			desc << "points";
+			break;
+
+		default:
+			DE_ASSERT(false);
+			break;
+	}
+
+
+	return desc.str();
+}
+
+MultiVertexArrayTest::MultiVertexArrayTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const Spec& spec, const char* name, const char* desc)
+	: VertexArrayTest	(testCtx, renderCtx, name, desc)
+	, m_spec			(spec)
+	, m_iteration		(0)
+{
+}
+
+MultiVertexArrayTest::~MultiVertexArrayTest	(void)
+{
+}
+
+MultiVertexArrayTest::IterateResult MultiVertexArrayTest::iterate (void)
+{
+	if (m_iteration == 0)
+	{
+		const size_t	primitiveSize		= (m_spec.primitive == Array::PRIMITIVE_TRIANGLES) ? (6) : (1); // in non-indexed draw Triangles means rectangles
+		float			coordScale			= 1.0f;
+		float			colorScale			= 1.0f;
+		const bool		useVao				= m_renderCtx.getType().getProfile() == glu::PROFILE_CORE;
+
+		// Log info
+		m_testCtx.getLog() << TestLog::Message << m_spec.getDesc() << TestLog::EndMessage;
+
+		// Color and Coord scale
+		{
+			// First array is always position
+			{
+				Spec::ArraySpec arraySpec = m_spec.arrays[0];
+				if (arraySpec.inputType == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10)
+				{
+					if (arraySpec.normalize)
+						coordScale = 1.0f;
+					else
+						coordScale = 1.0 / 1024.0;
+				}
+				else if (arraySpec.inputType == Array::INPUTTYPE_INT_2_10_10_10)
+				{
+					if (arraySpec.normalize)
+						coordScale = 1.0f;
+					else
+						coordScale = 1.0 / 512.0;
+				}
+				else
+					coordScale = (arraySpec.normalize && !inputTypeIsFloatType(arraySpec.inputType) ? 1.0f : float(0.9 / double(arraySpec.max.toFloat())));
+
+				if (arraySpec.outputType == Array::OUTPUTTYPE_VEC3 || arraySpec.outputType == Array::OUTPUTTYPE_VEC4
+					|| arraySpec.outputType == Array::OUTPUTTYPE_IVEC3 || arraySpec.outputType == Array::OUTPUTTYPE_IVEC4
+					|| arraySpec.outputType == Array::OUTPUTTYPE_UVEC3 || arraySpec.outputType == Array::OUTPUTTYPE_UVEC4)
+						coordScale = coordScale * 0.5f;
+			}
+
+			// And other arrays are color-like
+			for (int arrayNdx = 1; arrayNdx < (int)m_spec.arrays.size(); arrayNdx++)
+			{
+				Spec::ArraySpec arraySpec	= m_spec.arrays[arrayNdx];
+
+				colorScale *= (arraySpec.normalize && !inputTypeIsFloatType(arraySpec.inputType) ? 1.0f : float(1.0 / double(arraySpec.max.toFloat())));
+				if (arraySpec.outputType == Array::OUTPUTTYPE_VEC4)
+					colorScale *= (arraySpec.normalize && !inputTypeIsFloatType(arraySpec.inputType) ? 1.0f : float(1.0 / double(arraySpec.max.toFloat())));
+			}
+		}
+
+		// Data
+		for (int arrayNdx = 0; arrayNdx < (int)m_spec.arrays.size(); arrayNdx++)
+		{
+			Spec::ArraySpec arraySpec		= m_spec.arrays[arrayNdx];
+			const int		seed			= int(arraySpec.inputType) + 10 * int(arraySpec.outputType) + 100 * int(arraySpec.storage) + 1000 * int(m_spec.primitive) + 10000 * int(arraySpec.usage) + int(m_spec.drawCount) + 12 * int(arraySpec.componentCount) + int(arraySpec.stride) + int(arraySpec.normalize);
+			const char*		data			= DE_NULL;
+			const size_t	stride			= (arraySpec.stride == 0) ? (arraySpec.componentCount * Array::inputTypeSize(arraySpec.inputType)) : (arraySpec.stride);
+			const size_t	bufferSize		= arraySpec.offset + stride * (m_spec.drawCount * primitiveSize - 1) + arraySpec.componentCount  * Array::inputTypeSize(arraySpec.inputType);
+
+			switch (m_spec.primitive)
+			{
+	//			case Array::PRIMITIVE_POINTS:
+	//				data = RandomArrayGenerator::generateArray(seed, arraySpec.min, arraySpec.max, arraySpec.count, arraySpec.componentCount, arraySpec.stride, arraySpec.inputType);
+	//				break;
+				case Array::PRIMITIVE_TRIANGLES:
+					if (arrayNdx == 0)
+					{
+						data = RandomArrayGenerator::generateQuads(seed, m_spec.drawCount, arraySpec.componentCount, arraySpec.offset, arraySpec.stride, m_spec.primitive, arraySpec.inputType, arraySpec.min, arraySpec.max);
+					}
+					else
+					{
+						DE_ASSERT(arraySpec.offset == 0); // \note [jarkko] it just hasn't been implemented
+						data = RandomArrayGenerator::generatePerQuad(seed, m_spec.drawCount, arraySpec.componentCount, arraySpec.stride, m_spec.primitive, arraySpec.inputType, arraySpec.min, arraySpec.max);
+					}
+					break;
+
+				default:
+					DE_ASSERT(false);
+					break;
+			}
+
+			m_glArrayPack->newArray(arraySpec.storage);
+			m_rrArrayPack->newArray(arraySpec.storage);
+
+			m_glArrayPack->getArray(arrayNdx)->data(Array::TARGET_ARRAY, (int)bufferSize, data, arraySpec.usage);
+			m_rrArrayPack->getArray(arrayNdx)->data(Array::TARGET_ARRAY, (int)bufferSize, data, arraySpec.usage);
+
+			m_glArrayPack->getArray(arrayNdx)->bind(arrayNdx, arraySpec.offset, arraySpec.componentCount, arraySpec.inputType, arraySpec.outputType, arraySpec.normalize, arraySpec.stride);
+			m_rrArrayPack->getArray(arrayNdx)->bind(arrayNdx, arraySpec.offset, arraySpec.componentCount, arraySpec.inputType, arraySpec.outputType, arraySpec.normalize, arraySpec.stride);
+
+			delete [] data;
+		}
+
+		try
+		{
+			m_glArrayPack->render(m_spec.primitive, m_spec.first, m_spec.drawCount * (int)primitiveSize, useVao, coordScale, colorScale);
+			m_rrArrayPack->render(m_spec.primitive, m_spec.first, m_spec.drawCount * (int)primitiveSize, useVao, coordScale, colorScale);
+		}
+		catch (glu::Error& err)
+		{
+			// GL Errors are ok if the mode is not properly aligned
+
+			m_testCtx.getLog() << TestLog::Message << "Got error: " << err.what() << TestLog::EndMessage;
+
+			if (isUnalignedBufferOffsetTest())
+				m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned buffers.");
+			else if (isUnalignedBufferStrideTest())
+				m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned stride.");
+			else
+				throw;
+
+			return STOP;
+		}
+
+		m_iteration++;
+		return CONTINUE;
+	}
+	else if (m_iteration == 1)
+	{
+		compare();
+
+		if (m_isOk)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		}
+		else
+		{
+			if (isUnalignedBufferOffsetTest())
+				m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned buffers.");
+			else if (isUnalignedBufferStrideTest())
+				m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned stride.");
+			else
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed.");
+		}
+
+		m_iteration++;
+		return STOP;
+	}
+	else
+	{
+		DE_ASSERT(false);
+		return STOP;
+	}
+}
+
+bool MultiVertexArrayTest::isUnalignedBufferOffsetTest (void) const
+{
+	// Buffer offsets should be data type size aligned
+	for (size_t i = 0; i < m_spec.arrays.size(); ++i)
+	{
+		if (m_spec.arrays[i].storage == Array::STORAGE_BUFFER)
+		{
+			const bool inputTypePacked = m_spec.arrays[i].inputType == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_spec.arrays[i].inputType == Array::INPUTTYPE_INT_2_10_10_10;
+
+			int dataTypeSize = Array::inputTypeSize(m_spec.arrays[i].inputType);
+			if (inputTypePacked)
+				dataTypeSize = 4;
+
+			if (m_spec.arrays[i].offset % dataTypeSize != 0)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+bool MultiVertexArrayTest::isUnalignedBufferStrideTest (void) const
+{
+	// Buffer strides should be data type size aligned
+	for (size_t i = 0; i < m_spec.arrays.size(); ++i)
+	{
+		if (m_spec.arrays[i].storage == Array::STORAGE_BUFFER)
+		{
+			const bool inputTypePacked = m_spec.arrays[i].inputType == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_spec.arrays[i].inputType == Array::INPUTTYPE_INT_2_10_10_10;
+
+			int dataTypeSize = Array::inputTypeSize(m_spec.arrays[i].inputType);
+			if (inputTypePacked)
+				dataTypeSize = 4;
+
+			if (m_spec.arrays[i].stride % dataTypeSize != 0)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+} // gls
+} // deqp
diff --git a/modules/glshared/glsVertexArrayTests.hpp b/modules/glshared/glsVertexArrayTests.hpp
new file mode 100644
index 0000000..155bb6d
--- /dev/null
+++ b/modules/glshared/glsVertexArrayTests.hpp
@@ -0,0 +1,470 @@
+#ifndef _GLSVERTEXARRAYTESTS_HPP
+#define _GLSVERTEXARRAYTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL (ES) Module
+ * -----------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Vertex array and buffer tests
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuTestCase.hpp"
+#include "tcuVector.hpp"
+#include "tcuSurface.hpp"
+#include "gluRenderContext.hpp"
+#include "gluCallLogWrapper.hpp"
+#include "tcuTestLog.hpp"
+#include "gluShaderProgram.hpp"
+#include "deFloat16.h"
+#include "tcuFloat.hpp"
+#include "tcuPixelFormat.hpp"
+#include "sglrContext.hpp"
+
+namespace sglr
+{
+
+class ReferenceContextBuffers;
+class ReferenceContext;
+class Context;
+
+} // sglr
+
+namespace deqp
+{
+namespace gls
+{
+
+class Array
+{
+public:
+	enum Target
+	{
+		// \note [mika] Are these actualy used somewhere?
+		TARGET_ELEMENT_ARRAY = 0,
+		TARGET_ARRAY,
+
+		TARGET_LAST
+	};
+
+	enum InputType
+	{
+		INPUTTYPE_FLOAT = 0,
+		INPUTTYPE_FIXED,
+		INPUTTYPE_DOUBLE,
+
+		INPUTTYPE_BYTE,
+		INPUTTYPE_SHORT,
+
+		INPUTTYPE_UNSIGNED_BYTE,
+		INPUTTYPE_UNSIGNED_SHORT,
+
+		INPUTTYPE_INT,
+		INPUTTYPE_UNSIGNED_INT,
+		INPUTTYPE_HALF,
+		INPUTTYPE_UNSIGNED_INT_2_10_10_10,
+		INPUTTYPE_INT_2_10_10_10,
+
+		INPUTTYPE_LAST
+	};
+
+	enum OutputType
+	{
+		OUTPUTTYPE_FLOAT = 0,
+		OUTPUTTYPE_VEC2,
+		OUTPUTTYPE_VEC3,
+		OUTPUTTYPE_VEC4,
+
+		OUTPUTTYPE_INT,
+		OUTPUTTYPE_UINT,
+
+		OUTPUTTYPE_IVEC2,
+		OUTPUTTYPE_IVEC3,
+		OUTPUTTYPE_IVEC4,
+
+		OUTPUTTYPE_UVEC2,
+		OUTPUTTYPE_UVEC3,
+		OUTPUTTYPE_UVEC4,
+
+		OUTPUTTYPE_LAST
+	};
+
+	enum Usage
+	{
+		USAGE_DYNAMIC_DRAW = 0,
+		USAGE_STATIC_DRAW,
+		USAGE_STREAM_DRAW,
+
+		USAGE_STREAM_READ,
+		USAGE_STREAM_COPY,
+
+		USAGE_STATIC_READ,
+		USAGE_STATIC_COPY,
+
+		USAGE_DYNAMIC_READ,
+		USAGE_DYNAMIC_COPY,
+
+		USAGE_LAST
+	};
+
+	enum Storage
+	{
+		STORAGE_USER = 0,
+		STORAGE_BUFFER,
+
+		STORAGE_LAST
+	};
+
+	enum Primitive
+	{
+		PRIMITIVE_POINTS = 0,
+		PRIMITIVE_TRIANGLES,
+		PRIMITIVE_TRIANGLE_FAN,
+		PRIMITIVE_TRIANGLE_STRIP,
+
+		PRIMITIVE_LAST
+	};
+
+	static std::string	targetToString		(Target target);
+	static std::string	inputTypeToString	(InputType type);
+	static std::string	outputTypeToString	(OutputType type);
+	static std::string	usageTypeToString	(Usage usage);
+	static std::string	storageToString		(Storage storage);
+	static std::string	primitiveToString	(Primitive primitive);
+	static int			inputTypeSize		(InputType type);
+
+	virtual				~Array				(void) {}
+	virtual void		data				(Target target, int size, const char* data, Usage usage) = 0;
+	virtual void		subdata				(Target target, int offset, int size, const char* data) = 0;
+	virtual void		bind				(int attribNdx, int offset, int size, InputType inType, OutputType outType, bool normalized, int stride) = 0;
+	virtual void		unBind				(void) = 0;
+
+	virtual bool		isBound				(void) const = 0;
+	virtual int			getComponentCount	(void) const = 0;
+	virtual Target		getTarget			(void) const = 0;
+	virtual InputType	getInputType		(void) const = 0;
+	virtual OutputType	getOutputType		(void) const = 0;
+	virtual Storage		getStorageType		(void) const = 0;
+	virtual bool		getNormalized		(void) const = 0;
+	virtual int			getStride			(void) const = 0;
+	virtual int			getAttribNdx		(void) const = 0;
+	virtual void		setAttribNdx		(int attribNdx) = 0;
+};
+
+class ContextArray : public Array
+{
+public:
+								ContextArray		(Storage storage, sglr::Context& context);
+	virtual						~ContextArray		(void);
+	virtual void				data				(Target target, int size, const char* data, Usage usage);
+	virtual void				subdata				(Target target, int offset, int size, const char* data);
+	virtual void				bind				(int attribNdx, int offset, int size, InputType inType, OutputType outType, bool normalized, int stride);
+	virtual void				bindIndexArray		(Array::Target storage);
+	virtual void				unBind				(void) { m_bound = false; }
+	virtual bool				isBound				(void) const { return m_bound; }
+
+	virtual int					getComponentCount	(void) const { return m_componentCount; }
+	virtual Array::Target		getTarget			(void) const { return m_target; }
+	virtual Array::InputType	getInputType		(void) const { return m_inputType; }
+	virtual Array::OutputType	getOutputType		(void) const { return m_outputType; }
+	virtual Array::Storage		getStorageType		(void) const { return m_storage; }
+	virtual bool				getNormalized		(void) const { return m_normalize; }
+	virtual int					getStride			(void) const { return m_stride; }
+	virtual int					getAttribNdx		(void) const { return m_attribNdx; }
+	virtual void				setAttribNdx		(int attribNdx) { m_attribNdx = attribNdx; }
+
+	void						glBind				(deUint32 loc);
+	static deUint32				targetToGL			(Array::Target target);
+	static deUint32				usageToGL			(Array::Usage usage);
+	static deUint32				inputTypeToGL		(Array::InputType type);
+	static std::string			outputTypeToGLType	(Array::OutputType type);
+	static deUint32				primitiveToGL		(Array::Primitive primitive);
+
+private:
+	Storage						m_storage;
+	sglr::Context&				m_ctx;
+	deUint32					m_glBuffer;
+
+	bool						m_bound;
+	int							m_attribNdx;
+	int							m_size;
+	char*						m_data;
+	int							m_componentCount;
+	Array::Target				m_target;
+	Array::InputType			m_inputType;
+	Array::OutputType			m_outputType;
+	bool						m_normalize;
+	int							m_stride;
+	int							m_offset;
+};
+
+class ContextArrayPack
+{
+public:
+								ContextArrayPack	(glu::RenderContext& renderCtx, sglr::Context& drawContext);
+	virtual						~ContextArrayPack	(void);
+	virtual Array*				getArray			(int i);
+	virtual int					getArrayCount		(void);
+	virtual	void				newArray			(Array::Storage storage);
+	virtual void				render 				(Array::Primitive primitive, int firstVertex, int vertexCount, bool useVao, float coordScale, float colorScale);
+
+	const tcu::Surface&			getSurface			(void) const { return m_screen; }
+private:
+	void 						updateProgram		(void);
+	glu::RenderContext&			m_renderCtx;
+	sglr::Context&				m_ctx;
+
+	std::vector<ContextArray*>	m_arrays;
+	sglr::ShaderProgram*		m_program;
+	tcu::Surface				m_screen;
+};
+
+class GLValue
+{
+public:
+
+	template<class Type>
+	class WrappedType
+	{
+	public:
+		static WrappedType<Type>	create			(Type value)							{ WrappedType<Type> v; v.m_value = value; return v; }
+		inline Type					getValue		(void) const							{ return m_value; }
+
+		inline WrappedType<Type>	operator+		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value + other.getValue()); }
+		inline WrappedType<Type>	operator*		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value * other.getValue()); }
+		inline WrappedType<Type>	operator/		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value / other.getValue()); }
+		inline WrappedType<Type>	operator-		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value - other.getValue()); }
+
+		inline WrappedType<Type>&	operator+=		(const WrappedType<Type>& other)		{ m_value += other.getValue(); return *this; }
+		inline WrappedType<Type>&	operator*=		(const WrappedType<Type>& other)		{ m_value *= other.getValue(); return *this; }
+		inline WrappedType<Type>&	operator/=		(const WrappedType<Type>& other)		{ m_value /= other.getValue(); return *this; }
+		inline WrappedType<Type>&	operator-=		(const WrappedType<Type>& other)		{ m_value -= other.getValue(); return *this; }
+
+		inline bool					operator==		(const WrappedType<Type>& other) const	{ return m_value == other.m_value; }
+		inline bool					operator!=		(const WrappedType<Type>& other) const	{ return m_value != other.m_value; }
+		inline bool					operator<		(const WrappedType<Type>& other) const	{ return m_value < other.m_value; }
+		inline bool					operator>		(const WrappedType<Type>& other) const	{ return m_value > other.m_value; }
+		inline bool					operator<=		(const WrappedType<Type>& other) const	{ return m_value <= other.m_value; }
+		inline bool					operator>=		(const WrappedType<Type>& other) const	{ return m_value >= other.m_value; }
+
+		inline 						operator Type	(void) const							{ return m_value; }
+		template<class T>
+		inline T					to				(void) const							{ return (T)m_value; }
+	private:
+		Type	m_value;
+	};
+
+	typedef WrappedType<deInt16>	Short;
+	typedef WrappedType<deUint16>	Ushort;
+
+	typedef WrappedType<deInt8>		Byte;
+	typedef WrappedType<deUint8>	Ubyte;
+
+	typedef WrappedType<float>		Float;
+	typedef WrappedType<double>		Double;
+
+	typedef WrappedType<deInt32>	Int;
+	typedef WrappedType<deUint32>	Uint;
+
+	class Half
+	{
+	public:
+		static Half			create			(float value)				{ Half h; h.m_value = floatToHalf(value); return h; }
+		inline deFloat16	getValue		(void) const				{ return m_value; }
+
+		inline Half			operator+		(const Half& other) const	{ return create(halfToFloat(m_value) + halfToFloat(other.getValue())); }
+		inline Half			operator*		(const Half& other) const	{ return create(halfToFloat(m_value) * halfToFloat(other.getValue())); }
+		inline Half			operator/		(const Half& other) const	{ return create(halfToFloat(m_value) / halfToFloat(other.getValue())); }
+		inline Half			operator-		(const Half& other) const	{ return create(halfToFloat(m_value) - halfToFloat(other.getValue())); }
+
+		inline Half&		operator+=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) + halfToFloat(m_value)); return *this; }
+		inline Half&		operator*=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) * halfToFloat(m_value)); return *this; }
+		inline Half&		operator/=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) / halfToFloat(m_value)); return *this; }
+		inline Half&		operator-=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) - halfToFloat(m_value)); return *this; }
+
+		inline bool			operator==		(const Half& other) const	{ return m_value == other.m_value; }
+		inline bool			operator!=		(const Half& other) const	{ return m_value != other.m_value; }
+		inline bool			operator<		(const Half& other) const	{ return halfToFloat(m_value) < halfToFloat(other.m_value); }
+		inline bool			operator>		(const Half& other) const	{ return halfToFloat(m_value) > halfToFloat(other.m_value); }
+		inline bool			operator<=		(const Half& other) const	{ return halfToFloat(m_value) <= halfToFloat(other.m_value); }
+		inline bool			operator>=		(const Half& other) const	{ return halfToFloat(m_value) >= halfToFloat(other.m_value); }
+
+		template<class T>
+		inline T			to				(void) const				{ return (T)halfToFloat(m_value); }
+
+		inline static deFloat16	floatToHalf		(float f);
+		inline static float		halfToFloat		(deFloat16 h);
+	private:
+		deFloat16 m_value;
+	};
+
+	class Fixed
+	{
+	public:
+		static Fixed		create			(deInt32 value)				{ Fixed v; v.m_value = value; return v; }
+		inline deInt32		getValue		(void) const				{ return m_value; }
+
+		inline Fixed		operator+		(const Fixed& other) const	{ return create(m_value + other.getValue()); }
+		inline Fixed		operator*		(const Fixed& other) const	{ return create(m_value * other.getValue()); }
+		inline Fixed		operator/		(const Fixed& other) const	{ return create(m_value / other.getValue()); }
+		inline Fixed		operator-		(const Fixed& other) const	{ return create(m_value - other.getValue()); }
+
+		inline Fixed&		operator+=		(const Fixed& other)		{ m_value += other.getValue(); return *this; }
+		inline Fixed&		operator*=		(const Fixed& other)		{ m_value *= other.getValue(); return *this; }
+		inline Fixed&		operator/=		(const Fixed& other)		{ m_value /= other.getValue(); return *this; }
+		inline Fixed&		operator-=		(const Fixed& other)		{ m_value -= other.getValue(); return *this; }
+
+		inline bool			operator==		(const Fixed& other) const	{ return m_value == other.m_value; }
+		inline bool			operator!=		(const Fixed& other) const	{ return m_value != other.m_value; }
+		inline bool			operator<		(const Fixed& other) const	{ return m_value < other.m_value; }
+		inline bool			operator>		(const Fixed& other) const	{ return m_value > other.m_value; }
+		inline bool			operator<=		(const Fixed& other) const	{ return m_value <= other.m_value; }
+		inline bool			operator>=		(const Fixed& other) const	{ return m_value >= other.m_value; }
+
+		inline 				operator deInt32 (void) const				{ return m_value; }
+		template<class T>
+		inline T			to				(void) const				{ return (T)m_value; }
+	private:
+		deInt32				m_value;
+	};
+
+	// \todo [mika] This is pretty messy
+						GLValue			(void)			: type(Array::INPUTTYPE_LAST) {}
+	explicit			GLValue			(Float value)	: type(Array::INPUTTYPE_FLOAT),				fl(value)	{}
+	explicit			GLValue			(Fixed value)	: type(Array::INPUTTYPE_FIXED),				fi(value)	{}
+	explicit			GLValue			(Byte value)	: type(Array::INPUTTYPE_BYTE),				b(value)	{}
+	explicit			GLValue			(Ubyte value)	: type(Array::INPUTTYPE_UNSIGNED_BYTE),		ub(value)	{}
+	explicit			GLValue			(Short value)	: type(Array::INPUTTYPE_SHORT),				s(value)	{}
+	explicit			GLValue			(Ushort value)	: type(Array::INPUTTYPE_UNSIGNED_SHORT),	us(value)	{}
+	explicit			GLValue			(Int value)		: type(Array::INPUTTYPE_INT),				i(value)	{}
+	explicit			GLValue			(Uint value)	: type(Array::INPUTTYPE_UNSIGNED_INT),		ui(value)	{}
+	explicit			GLValue			(Half value)	: type(Array::INPUTTYPE_HALF),				h(value)	{}
+	explicit			GLValue			(Double value)	: type(Array::INPUTTYPE_DOUBLE),			d(value)	{}
+
+	float				toFloat			(void) const;
+
+	static GLValue		getMaxValue		(Array::InputType type);
+	static GLValue		getMinValue		(Array::InputType type);
+
+	Array::InputType	type;
+
+	union
+	{
+		Float		fl;
+		Fixed		fi;
+		Double		d;
+		Byte		b;
+		Ubyte		ub;
+		Short		s;
+		Ushort		us;
+		Int			i;
+		Uint		ui;
+		Half		h;
+	};
+};
+
+class VertexArrayTest : public tcu::TestCase
+{
+public:
+									VertexArrayTest		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name ,const char* desc);
+	virtual							~VertexArrayTest	(void);
+	virtual void					init				(void);
+	virtual void					deinit				(void);
+
+protected:
+									VertexArrayTest		(const VertexArrayTest& other);
+	VertexArrayTest&				operator=			(const VertexArrayTest& other);
+
+	void							compare				(void);
+
+	glu::RenderContext&				m_renderCtx;
+
+	sglr::ReferenceContextBuffers*	m_refBuffers;
+	sglr::ReferenceContext*			m_refContext;
+	sglr::Context*					m_glesContext;
+
+	ContextArrayPack*				m_glArrayPack;
+	ContextArrayPack*				m_rrArrayPack;
+	bool							m_isOk;
+
+	int								m_maxDiffRed;
+	int								m_maxDiffGreen;
+	int								m_maxDiffBlue;
+};
+
+class MultiVertexArrayTest : public VertexArrayTest
+{
+public:
+	class Spec
+	{
+	public:
+		class ArraySpec
+		{
+		public:
+								ArraySpec	(Array::InputType inputType, Array::OutputType outputType, Array::Storage storage, Array::Usage usage, int componetCount, int offset, int stride, bool normalize, GLValue min, GLValue max);
+
+			Array::InputType	inputType;
+			Array::OutputType	outputType;
+			Array::Storage		storage;
+			Array::Usage		usage;
+			int 				componentCount;
+			int					offset;
+			int					stride;
+			bool				normalize;
+			GLValue				min;
+			GLValue				max;
+		};
+
+		std::string 			getName		(void) const;
+		std::string				getDesc		(void) const;
+
+		Array::Primitive		primitive;
+		int						drawCount;			//!<Number of primitives to draw
+		int						first;
+
+		std::vector<ArraySpec>	arrays;
+	};
+
+							MultiVertexArrayTest	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const Spec& spec, const char* name, const char* desc);
+	virtual					~MultiVertexArrayTest	(void);
+	virtual IterateResult	iterate					(void);
+
+private:
+	bool					isUnalignedBufferOffsetTest		(void) const;
+	bool					isUnalignedBufferStrideTest		(void) const;
+
+	Spec					m_spec;
+	int						m_iteration;
+};
+
+inline deFloat16 GLValue::Half::floatToHalf (float f)
+{
+	// No denorm support.
+	tcu::Float<deUint16, 5, 10, 15, tcu::FLOAT_HAS_SIGN> v(f);
+	DE_ASSERT(!v.isNaN() && !v.isInf());
+	return v.bits();
+}
+
+inline float GLValue::Half::halfToFloat (deFloat16 h)
+{
+	return tcu::Float16((deUint16)h).asFloat();
+}
+
+} // gls
+} // deqp
+
+#endif // _GLSVERTEXARRAYTESTS_HPP
diff --git a/modules/glshared/glshared.cmake b/modules/glshared/glshared.cmake
new file mode 100644
index 0000000..783f37d
--- /dev/null
+++ b/modules/glshared/glshared.cmake
@@ -0,0 +1,3 @@
+if (DEQP_SUPPORT_GLES2 OR DEQP_SUPPORT_GLES3 OR DEQP_SUPPORT_OPENGL)
+	add_subdirectory(glshared)
+endif ()
diff --git a/modules/internal/CMakeLists.txt b/modules/internal/CMakeLists.txt
new file mode 100644
index 0000000..3dd9ad0
--- /dev/null
+++ b/modules/internal/CMakeLists.txt
@@ -0,0 +1,28 @@
+# drawElements internal tests
+
+set(DE_INTERNAL_TESTS_SRCS
+	ditBuildInfoTests.cpp
+	ditBuildInfoTests.hpp
+	ditDelibsTests.cpp
+	ditDelibsTests.hpp
+	ditFrameworkTests.cpp
+	ditFrameworkTests.hpp
+	ditImageCompareTests.cpp
+	ditImageCompareTests.hpp
+	ditImageIOTests.cpp
+	ditImageIOTests.hpp
+	ditTestCase.cpp
+	ditTestCase.hpp
+	ditTestLogTests.cpp
+	ditTestLogTests.hpp
+	ditTestPackage.cpp
+	ditTestPackage.hpp
+	)
+
+set(DE_INTERNAL_TESTS_LIBS
+	tcutil
+	)
+
+add_deqp_module(de-internal-tests "${DE_INTERNAL_TESTS_SRCS}" "${DE_INTERNAL_TESTS_LIBS}" ditTestPackageEntry.cpp)
+
+add_data_dir(de-internal-tests data	internal/data)
diff --git a/modules/internal/data/imagecompare/2_units_2d_cmp.png b/modules/internal/data/imagecompare/2_units_2d_cmp.png
new file mode 100644
index 0000000..4413deb
--- /dev/null
+++ b/modules/internal/data/imagecompare/2_units_2d_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/2_units_2d_ref.png b/modules/internal/data/imagecompare/2_units_2d_ref.png
new file mode 100644
index 0000000..8076f40
--- /dev/null
+++ b/modules/internal/data/imagecompare/2_units_2d_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/4_units_cube_cmp.png b/modules/internal/data/imagecompare/4_units_cube_cmp.png
new file mode 100644
index 0000000..f0d7ba5
--- /dev/null
+++ b/modules/internal/data/imagecompare/4_units_cube_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/4_units_cube_ref.png b/modules/internal/data/imagecompare/4_units_cube_ref.png
new file mode 100644
index 0000000..6498da5
--- /dev/null
+++ b/modules/internal/data/imagecompare/4_units_cube_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_2_cmp.png b/modules/internal/data/imagecompare/cube_2_cmp.png
new file mode 100644
index 0000000..934a1f2
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_2_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_2_ref.png b/modules/internal/data/imagecompare/cube_2_ref.png
new file mode 100644
index 0000000..57938f1
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_2_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_cmp.png b/modules/internal/data/imagecompare/cube_cmp.png
new file mode 100644
index 0000000..5f1cd40
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_nmap_2_cmp.png b/modules/internal/data/imagecompare/cube_nmap_2_cmp.png
new file mode 100644
index 0000000..5168055
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_nmap_2_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_nmap_2_ref.png b/modules/internal/data/imagecompare/cube_nmap_2_ref.png
new file mode 100644
index 0000000..b594398
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_nmap_2_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_nmap_cmp.png b/modules/internal/data/imagecompare/cube_nmap_cmp.png
new file mode 100644
index 0000000..9d801da
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_nmap_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_nmap_ref.png b/modules/internal/data/imagecompare/cube_nmap_ref.png
new file mode 100644
index 0000000..60715b0
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_nmap_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_ref.png b/modules/internal/data/imagecompare/cube_ref.png
new file mode 100644
index 0000000..4f7941d
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_sphere_2_cmp.png b/modules/internal/data/imagecompare/cube_sphere_2_cmp.png
new file mode 100644
index 0000000..09cb155
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_sphere_2_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_sphere_2_ref.png b/modules/internal/data/imagecompare/cube_sphere_2_ref.png
new file mode 100644
index 0000000..826028c
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_sphere_2_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_sphere_cmp.png b/modules/internal/data/imagecompare/cube_sphere_cmp.png
new file mode 100644
index 0000000..2ac78f1
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_sphere_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/cube_sphere_ref.png b/modules/internal/data/imagecompare/cube_sphere_ref.png
new file mode 100644
index 0000000..0bddd90
--- /dev/null
+++ b/modules/internal/data/imagecompare/cube_sphere_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/earth_diffuse_cmp.png b/modules/internal/data/imagecompare/earth_diffuse_cmp.png
new file mode 100644
index 0000000..a8a0363
--- /dev/null
+++ b/modules/internal/data/imagecompare/earth_diffuse_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/earth_diffuse_ref.png b/modules/internal/data/imagecompare/earth_diffuse_ref.png
new file mode 100644
index 0000000..526d72b
--- /dev/null
+++ b/modules/internal/data/imagecompare/earth_diffuse_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/earth_light_cmp.png b/modules/internal/data/imagecompare/earth_light_cmp.png
new file mode 100644
index 0000000..a1045ce
--- /dev/null
+++ b/modules/internal/data/imagecompare/earth_light_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/earth_light_ref.png b/modules/internal/data/imagecompare/earth_light_ref.png
new file mode 100644
index 0000000..cd548b5
--- /dev/null
+++ b/modules/internal/data/imagecompare/earth_light_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/earth_spot_cmp.png b/modules/internal/data/imagecompare/earth_spot_cmp.png
new file mode 100644
index 0000000..3de3803
--- /dev/null
+++ b/modules/internal/data/imagecompare/earth_spot_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/earth_spot_ref.png b/modules/internal/data/imagecompare/earth_spot_ref.png
new file mode 100644
index 0000000..f7c9cbe
--- /dev/null
+++ b/modules/internal/data/imagecompare/earth_spot_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/earth_texture_cmp.png b/modules/internal/data/imagecompare/earth_texture_cmp.png
new file mode 100644
index 0000000..48ced61
--- /dev/null
+++ b/modules/internal/data/imagecompare/earth_texture_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/earth_texture_ref.png b/modules/internal/data/imagecompare/earth_texture_ref.png
new file mode 100644
index 0000000..5ef1e91
--- /dev/null
+++ b/modules/internal/data/imagecompare/earth_texture_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/empty_256x256.png b/modules/internal/data/imagecompare/empty_256x256.png
new file mode 100644
index 0000000..ef020ee
--- /dev/null
+++ b/modules/internal/data/imagecompare/empty_256x256.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/lessThan0-reference.png b/modules/internal/data/imagecompare/lessThan0-reference.png
new file mode 100644
index 0000000..a4d5e29
--- /dev/null
+++ b/modules/internal/data/imagecompare/lessThan0-reference.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/lessThan0-result.png b/modules/internal/data/imagecompare/lessThan0-result.png
new file mode 100644
index 0000000..4832c79
--- /dev/null
+++ b/modules/internal/data/imagecompare/lessThan0-result.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/lessthan_vtx_cmp.png b/modules/internal/data/imagecompare/lessthan_vtx_cmp.png
new file mode 100644
index 0000000..0d2f9c5
--- /dev/null
+++ b/modules/internal/data/imagecompare/lessthan_vtx_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/lessthan_vtx_ref.png b/modules/internal/data/imagecompare/lessthan_vtx_ref.png
new file mode 100644
index 0000000..0d2f9c5
--- /dev/null
+++ b/modules/internal/data/imagecompare/lessthan_vtx_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/readpixels_msaa.png b/modules/internal/data/imagecompare/readpixels_msaa.png
new file mode 100644
index 0000000..519a083
--- /dev/null
+++ b/modules/internal/data/imagecompare/readpixels_msaa.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/readpixels_ref.png b/modules/internal/data/imagecompare/readpixels_ref.png
new file mode 100644
index 0000000..a67e4d6
--- /dev/null
+++ b/modules/internal/data/imagecompare/readpixels_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/refract_frag_cmp.png b/modules/internal/data/imagecompare/refract_frag_cmp.png
new file mode 100644
index 0000000..53f926f
--- /dev/null
+++ b/modules/internal/data/imagecompare/refract_frag_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/refract_frag_ref.png b/modules/internal/data/imagecompare/refract_frag_ref.png
new file mode 100644
index 0000000..1f0f052
--- /dev/null
+++ b/modules/internal/data/imagecompare/refract_frag_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/refract_vtx_cmp.png b/modules/internal/data/imagecompare/refract_vtx_cmp.png
new file mode 100644
index 0000000..f2e59a5
--- /dev/null
+++ b/modules/internal/data/imagecompare/refract_vtx_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/refract_vtx_ref.png b/modules/internal/data/imagecompare/refract_vtx_ref.png
new file mode 100644
index 0000000..f343ed3
--- /dev/null
+++ b/modules/internal/data/imagecompare/refract_vtx_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/texfilter_cmp.png b/modules/internal/data/imagecompare/texfilter_cmp.png
new file mode 100644
index 0000000..e8a5cd3
--- /dev/null
+++ b/modules/internal/data/imagecompare/texfilter_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/texfilter_ref.png b/modules/internal/data/imagecompare/texfilter_ref.png
new file mode 100644
index 0000000..1090550
--- /dev/null
+++ b/modules/internal/data/imagecompare/texfilter_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/texfilter_vtx_linear_cmp.png b/modules/internal/data/imagecompare/texfilter_vtx_linear_cmp.png
new file mode 100644
index 0000000..450f25f
--- /dev/null
+++ b/modules/internal/data/imagecompare/texfilter_vtx_linear_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/texfilter_vtx_linear_ref.png b/modules/internal/data/imagecompare/texfilter_vtx_linear_ref.png
new file mode 100644
index 0000000..200d8e1
--- /dev/null
+++ b/modules/internal/data/imagecompare/texfilter_vtx_linear_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/texfilter_vtx_nearest_cmp.png b/modules/internal/data/imagecompare/texfilter_vtx_nearest_cmp.png
new file mode 100644
index 0000000..450f25f
--- /dev/null
+++ b/modules/internal/data/imagecompare/texfilter_vtx_nearest_cmp.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/texfilter_vtx_nearest_ref.png b/modules/internal/data/imagecompare/texfilter_vtx_nearest_ref.png
new file mode 100644
index 0000000..3f49b2f
--- /dev/null
+++ b/modules/internal/data/imagecompare/texfilter_vtx_nearest_ref.png
Binary files differ
diff --git a/modules/internal/data/imagecompare/white_256x256.png b/modules/internal/data/imagecompare/white_256x256.png
new file mode 100644
index 0000000..8920fa7
--- /dev/null
+++ b/modules/internal/data/imagecompare/white_256x256.png
Binary files differ
diff --git a/modules/internal/data/imageio/rgb24_209x181.png b/modules/internal/data/imageio/rgb24_209x181.png
new file mode 100644
index 0000000..f195934
--- /dev/null
+++ b/modules/internal/data/imageio/rgb24_209x181.png
Binary files differ
diff --git a/modules/internal/data/imageio/rgb24_256x256.png b/modules/internal/data/imageio/rgb24_256x256.png
new file mode 100644
index 0000000..61a632d
--- /dev/null
+++ b/modules/internal/data/imageio/rgb24_256x256.png
Binary files differ
diff --git a/modules/internal/data/imageio/rgba32_207x219.png b/modules/internal/data/imageio/rgba32_207x219.png
new file mode 100644
index 0000000..9294bc5
--- /dev/null
+++ b/modules/internal/data/imageio/rgba32_207x219.png
Binary files differ
diff --git a/modules/internal/data/imageio/rgba32_256x256.png b/modules/internal/data/imageio/rgba32_256x256.png
new file mode 100644
index 0000000..6533a91
--- /dev/null
+++ b/modules/internal/data/imageio/rgba32_256x256.png
Binary files differ
diff --git a/modules/internal/ditBuildInfoTests.cpp b/modules/internal/ditBuildInfoTests.cpp
new file mode 100644
index 0000000..1cbd4c5
--- /dev/null
+++ b/modules/internal/ditBuildInfoTests.cpp
@@ -0,0 +1,201 @@
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Build information tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "ditBuildInfoTests.hpp"
+#include "tcuTestLog.hpp"
+#include "deStringUtil.hpp"
+
+using tcu::TestLog;
+
+namespace dit
+{
+
+static const char* getOsName (int os)
+{
+	switch (os)
+	{
+		case DE_OS_VANILLA:		return "DE_OS_VANILLA";
+		case DE_OS_WIN32:		return "DE_OS_WIN32";
+		case DE_OS_UNIX:		return "DE_OS_UNIX";
+		case DE_OS_WINCE:		return "DE_OS_WINCE";
+		case DE_OS_OSX:			return "DE_OS_OSX";
+		case DE_OS_ANDROID:		return "DE_OS_ANDROID";
+		case DE_OS_SYMBIAN:		return "DE_OS_SYMBIAN";
+		case DE_OS_IOS:			return "DE_OS_IOS";
+		default:
+			return DE_NULL;
+	}
+}
+
+static const char* getCompilerName (int compiler)
+{
+	switch (compiler)
+	{
+		case DE_COMPILER_VANILLA:	return "DE_COMPILER_VANILLA";
+		case DE_COMPILER_MSC:		return "DE_COMPILER_MSC";
+		case DE_COMPILER_GCC:		return "DE_COMPILER_GCC";
+		case DE_COMPILER_CLANG:		return "DE_COMPILER_CLANG";
+		default:
+			return DE_NULL;
+	}
+}
+
+static const char* getCpuName (int cpu)
+{
+	switch (cpu)
+	{
+		case DE_CPU_VANILLA:	return "DE_CPU_VANILLA";
+		case DE_CPU_ARM:		return "DE_CPU_ARM";
+		case DE_CPU_X86:		return "DE_CPU_X86";
+		case DE_CPU_X86_64:		return "DE_CPU_X86_64";
+		case DE_CPU_ARM_64:		return "DE_CPU_ARM_64";
+		case DE_CPU_MIPS:		return "DE_CPU_MIPS";
+		case DE_CPU_MIPS_64:	return "DE_CPU_MIPS_64";
+		default:
+			return DE_NULL;
+	}
+}
+
+static const char* getEndiannessName (int endianness)
+{
+	switch (endianness)
+	{
+		case DE_BIG_ENDIAN:		return "DE_BIG_ENDIAN";
+		case DE_LITTLE_ENDIAN:	return "DE_LITTLE_ENDIAN";
+		default:
+			return DE_NULL;
+	}
+}
+
+class BuildInfoStringCase : public tcu::TestCase
+{
+public:
+	BuildInfoStringCase (tcu::TestContext& testCtx, const char* name, const char* valueName, const char* value)
+		: tcu::TestCase	(testCtx, name, valueName)
+		, m_valueName	(valueName)
+		, m_value		(value)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << m_valueName << " = " << m_value << TestLog::EndMessage;
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+
+private:
+	std::string	m_valueName;
+	std::string	m_value;
+};
+
+class BuildEnumCase : public tcu::TestCase
+{
+public:
+	typedef const char* (*GetStringFunc) (int value);
+
+	BuildEnumCase (tcu::TestContext& testCtx, const char* name, const char* varName, int value, GetStringFunc getString)
+		: tcu::TestCase	(testCtx, name, varName)
+		, m_varName		(varName)
+		, m_value		(value)
+		, m_getString	(getString)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const char*	valueName	= m_getString(m_value);
+		const bool	isOk		= valueName != DE_NULL;
+		std::string	logValue	= valueName ? std::string(valueName) : de::toString(m_value);
+
+		m_testCtx.getLog() << TestLog::Message << m_varName << " = " << logValue << TestLog::EndMessage;
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "No enum name found");
+		return STOP;
+	}
+
+private:
+	std::string		m_varName;
+	int				m_value;
+	GetStringFunc	m_getString;
+};
+
+class EndiannessConsistencyCase : public tcu::TestCase
+{
+public:
+	EndiannessConsistencyCase (tcu::TestContext& context, const char* name, const char* description)
+		: tcu::TestCase(context, name, description)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		const deUint16	multiByte	= (deUint16)0x0102;
+
+#if DE_ENDIANNESS == DE_BIG_ENDIAN
+		const bool		isOk		= *((const deUint8*)&multiByte) == (deUint8)0x01;
+#elif DE_ENDIANNESS == DE_LITTLE_ENDIAN
+		const bool		isOk		= *((const deUint8*)&multiByte) == (deUint8)0x02;
+#endif
+
+		m_testCtx.getLog()
+			<< TestLog::Message
+			<< "Verifying DE_ENDIANNESS matches actual behavior"
+			<< TestLog::EndMessage;
+
+		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+								isOk ? "Pass"				: "Configured endianness inconsistent");
+		return STOP;
+	}
+};
+
+BuildInfoTests::BuildInfoTests (tcu::TestContext& testCtx)
+	: tcu::TestCaseGroup(testCtx, "build_info", "Build Info Tests")
+{
+}
+
+BuildInfoTests::~BuildInfoTests (void)
+{
+}
+
+void BuildInfoTests::init (void)
+{
+#if defined(DE_DEBUG)
+	const bool isDebug = true;
+#else
+	const bool isDebug = false;
+#endif
+
+	addChild(new BuildInfoStringCase		(m_testCtx, "date",						"__DATE__",			__DATE__));
+	addChild(new BuildInfoStringCase		(m_testCtx, "time",						"__TIME__",			__TIME__));
+	addChild(new BuildInfoStringCase		(m_testCtx, "de_debug",					"DE_DEBUG",			isDebug ? "1" : "not defined"));
+	addChild(new BuildEnumCase				(m_testCtx, "de_os",					"DE_OS",			DE_OS,									getOsName));
+	addChild(new BuildEnumCase				(m_testCtx, "de_cpu",					"DE_CPU",			DE_CPU,									getCpuName));
+	addChild(new BuildEnumCase				(m_testCtx, "de_compiler",				"DE_COMPILER",		DE_COMPILER,							getCompilerName));
+	addChild(new BuildInfoStringCase		(m_testCtx, "de_ptr_size",				"DE_PTR_SIZE",		de::toString(DE_PTR_SIZE).c_str()));
+	addChild(new BuildEnumCase				(m_testCtx, "de_endianness",			"DE_ENDIANNESS",	DE_ENDIANNESS,							getEndiannessName));
+	addChild(new EndiannessConsistencyCase	(m_testCtx, "de_endianness_consitent", 	"DE_ENDIANNESS"));
+}
+
+} // dit
diff --git a/modules/internal/ditBuildInfoTests.hpp b/modules/internal/ditBuildInfoTests.hpp
new file mode 100644
index 0000000..0c19b4c
--- /dev/null
+++ b/modules/internal/ditBuildInfoTests.hpp
@@ -0,0 +1,43 @@
+#ifndef _DITBUILDINFOTESTS_HPP
+#define _DITBUILDINFOTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Build information tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+
+namespace dit
+{
+
+class BuildInfoTests : public tcu::TestCaseGroup
+{
+public:
+					BuildInfoTests		(tcu::TestContext& testCtx);
+					~BuildInfoTests		(void);
+
+	void			init				(void);
+};
+
+} // dit
+
+#endif // _DITBUILDINFOTESTS_HPP
diff --git a/modules/internal/ditDelibsTests.cpp b/modules/internal/ditDelibsTests.cpp
new file mode 100644
index 0000000..5e95336
--- /dev/null
+++ b/modules/internal/ditDelibsTests.cpp
@@ -0,0 +1,150 @@
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief delibs self-tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "ditDelibsTests.hpp"
+
+// depool
+#include "dePoolArray.h"
+#include "dePoolHeap.h"
+#include "dePoolHash.h"
+#include "dePoolSet.h"
+#include "dePoolHashSet.h"
+#include "dePoolHashArray.h"
+#include "dePoolMultiSet.h"
+
+// dethread
+#include "deThreadTest.h"
+
+// deutil
+#include "deTimerTest.h"
+
+// decpp
+#include "deBlockBuffer.hpp"
+#include "deFilePath.hpp"
+#include "dePoolArray.hpp"
+#include "deRingBuffer.hpp"
+#include "deSharedPtr.hpp"
+#include "deThreadSafeRingBuffer.hpp"
+#include "deUniquePtr.hpp"
+#include "deRandom.hpp"
+#include "deCommandLine.hpp"
+#include "deArrayBuffer.hpp"
+#include "deStringUtil.hpp"
+
+namespace dit
+{
+
+class DepoolTests : public tcu::TestCaseGroup
+{
+public:
+	DepoolTests (tcu::TestContext& testCtx)
+		: tcu::TestCaseGroup(testCtx, "depool", "depool self-tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new SelfCheckCase(m_testCtx, "array",		"dePoolArray_selfTest()",		dePoolArray_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "heap",		"dePoolHeap_selfTest()",		dePoolHeap_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "hash",		"dePoolHash_selfTest()",		dePoolHash_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "set",		"dePoolSet_selfTest()",			dePoolSet_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "hash_set",	"dePoolHashSet_selfTest()",		dePoolHashSet_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "hash_array",	"dePoolHashArray_selfTest()",	dePoolHashArray_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "multi_set",	"dePoolMultiSet_selfTest()",	dePoolMultiSet_selfTest));
+	}
+};
+
+class DethreadTests : public tcu::TestCaseGroup
+{
+public:
+	DethreadTests (tcu::TestContext& testCtx)
+		: tcu::TestCaseGroup(testCtx, "dethread", "dethread self-tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new SelfCheckCase(m_testCtx, "thread",		"deThread_selfTest()",		deThread_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "mutex",		"deMutex_selfTest()",		deMutex_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "semaphore",	"deSemaphore_selfTest()",	deSemaphore_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "atomic",		"deAtomic_selfTest()",		deAtomic_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "singleton",	"deSingleton_selfTest()",	deSingleton_selfTest));
+	}
+};
+
+class DeutilTests : public tcu::TestCaseGroup
+{
+public:
+	DeutilTests (tcu::TestContext& testCtx)
+		: tcu::TestCaseGroup(testCtx, "deutil", "deutil self-tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new SelfCheckCase(m_testCtx, "timer",	"deTimer_selfTest()",	deTimer_selfTest));
+	}
+};
+
+class DecppTests : public tcu::TestCaseGroup
+{
+public:
+	DecppTests (tcu::TestContext& testCtx)
+		: tcu::TestCaseGroup(testCtx, "decpp", "decpp self-tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new SelfCheckCase(m_testCtx, "block_buffer",				"de::BlockBuffer_selfTest()",			de::BlockBuffer_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "file_path",					"de::FilePath_selfTest()",				de::FilePath_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "pool_array",					"de::PoolArray_selfTest()",				de::PoolArray_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "ring_buffer",				"de::RingBuffer_selfTest()",			de::RingBuffer_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "shared_ptr",					"de::SharedPtr_selfTest()",				de::SharedPtr_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "thread_safe_ring_buffer",	"de::ThreadSafeRingBuffer_selfTest()",	de::ThreadSafeRingBuffer_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "unique_ptr",					"de::UniquePtr_selfTest()",				de::UniquePtr_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "random",						"de::Random_selfTest()",				de::Random_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "commandline",				"de::cmdline::selfTest()",				de::cmdline::selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "array_buffer",				"de::ArrayBuffer_selfTest()",			de::ArrayBuffer_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "string_util",				"de::StringUtil_selfTest()",			de::StringUtil_selfTest));
+	}
+};
+
+DelibsTests::DelibsTests (tcu::TestContext& testCtx)
+	: tcu::TestCaseGroup(testCtx, "delibs", "delibs Tests")
+{
+}
+
+DelibsTests::~DelibsTests (void)
+{
+}
+
+void DelibsTests::init (void)
+{
+	addChild(new DepoolTests	(m_testCtx));
+	addChild(new DethreadTests	(m_testCtx));
+	addChild(new DeutilTests	(m_testCtx));
+	addChild(new DecppTests		(m_testCtx));
+}
+
+} // dit
diff --git a/modules/internal/ditDelibsTests.hpp b/modules/internal/ditDelibsTests.hpp
new file mode 100644
index 0000000..209d4e7
--- /dev/null
+++ b/modules/internal/ditDelibsTests.hpp
@@ -0,0 +1,43 @@
+#ifndef _DITDELIBSTESTS_HPP
+#define _DITDELIBSTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief delibs self-tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "ditTestCase.hpp"
+
+namespace dit
+{
+
+class DelibsTests : public tcu::TestCaseGroup
+{
+public:
+					DelibsTests			(tcu::TestContext& testCtx);
+					~DelibsTests		(void);
+
+	void			init				(void);
+};
+
+} // dit
+
+#endif // _DITDELIBSTESTS_HPP
diff --git a/modules/internal/ditFrameworkTests.cpp b/modules/internal/ditFrameworkTests.cpp
new file mode 100644
index 0000000..3882c45
--- /dev/null
+++ b/modules/internal/ditFrameworkTests.cpp
@@ -0,0 +1,60 @@
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Miscellaneous framework tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "ditFrameworkTests.hpp"
+
+#include "tcuFloatFormat.hpp"
+
+namespace dit
+{
+
+class CommonFrameworkTests : public tcu::TestCaseGroup
+{
+public:
+	CommonFrameworkTests (tcu::TestContext& testCtx)
+		: tcu::TestCaseGroup(testCtx, "common", "Tests for the common utility framework")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new SelfCheckCase(m_testCtx, "float_format","tcu::FloatFormat_selfTest()",
+								   tcu::FloatFormat_selfTest));
+	}
+};
+
+FrameworkTests::FrameworkTests (tcu::TestContext& testCtx)
+	: tcu::TestCaseGroup(testCtx, "framework", "Miscellaneous framework tests")
+{
+}
+
+FrameworkTests::~FrameworkTests (void)
+{
+}
+
+void FrameworkTests::init (void)
+{
+	addChild(new CommonFrameworkTests(m_testCtx));
+}
+
+}
diff --git a/modules/internal/ditFrameworkTests.hpp b/modules/internal/ditFrameworkTests.hpp
new file mode 100644
index 0000000..e5e0ff6
--- /dev/null
+++ b/modules/internal/ditFrameworkTests.hpp
@@ -0,0 +1,43 @@
+#ifndef _DITFRAMEWORKTESTS_HPP
+#define _DITFRAMEWORKTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Miscellaneous framework tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "ditTestCase.hpp"
+
+namespace dit
+{
+
+class FrameworkTests : public tcu::TestCaseGroup
+{
+public:
+					FrameworkTests			(tcu::TestContext& testCtx);
+					~FrameworkTests			(void);
+
+	void			init				(void);
+};
+
+} // dit
+
+#endif // _DITFRAMEWORKTESTS_HPP
diff --git a/modules/internal/ditImageCompareTests.cpp b/modules/internal/ditImageCompareTests.cpp
new file mode 100644
index 0000000..96e0a5f
--- /dev/null
+++ b/modules/internal/ditImageCompareTests.cpp
@@ -0,0 +1,231 @@
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Image comparison tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "ditImageCompareTests.hpp"
+#include "tcuResource.hpp"
+#include "tcuImageCompare.hpp"
+#include "tcuFuzzyImageCompare.hpp"
+#include "tcuImageIO.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuTextureUtil.hpp"
+#include "tcuRGBA.hpp"
+#include "deFilePath.hpp"
+#include "deClock.h"
+
+namespace dit
+{
+
+using tcu::TestLog;
+
+static const char*	BASE_DIR			= "data/imagecompare";
+
+static void loadImageRGBA8 (tcu::TextureLevel& dst, const tcu::Archive& archive, const char* path)
+{
+	tcu::TextureLevel tmp;
+	tcu::ImageIO::loadImage(tmp, archive, path);
+
+	dst.setStorage(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), tmp.getWidth(), tmp.getHeight());
+	tcu::copy(dst, tmp);
+}
+
+class FuzzyComparisonMetricCase : public tcu::TestCase
+{
+public:
+	FuzzyComparisonMetricCase (tcu::TestContext& testCtx, const char* name, const char* refImg, const char* cmpImg, const float minBound, const float maxBound)
+		: tcu::TestCase	(testCtx, name, "")
+		, m_refImg		(refImg)
+		, m_cmpImg		(cmpImg)
+		, m_minBound	(minBound)
+		, m_maxBound	(maxBound)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		tcu::TextureLevel		refImg;
+		tcu::TextureLevel		cmpImg;
+		tcu::TextureLevel		errorMask;
+		tcu::FuzzyCompareParams	params;
+		float					result		= 0.0f;
+		deUint64				compareTime	= 0;
+
+		params.maxSampleSkip = 0;
+
+		tcu::ImageIO::loadImage(refImg, m_testCtx.getArchive(), de::FilePath::join(BASE_DIR, m_refImg).getPath());
+		tcu::ImageIO::loadImage(cmpImg, m_testCtx.getArchive(), de::FilePath::join(BASE_DIR, m_cmpImg).getPath());
+
+		errorMask.setStorage(refImg.getFormat(), refImg.getWidth(), refImg.getHeight(), refImg.getDepth());
+
+		{
+			const deUint64 startTime = deGetMicroseconds();
+			result = tcu::fuzzyCompare(params, refImg, cmpImg, errorMask);
+			compareTime = deGetMicroseconds()-startTime;
+		}
+
+		m_testCtx.getLog() << TestLog::Float("Result", "Result metric", "", QP_KEY_TAG_NONE, result)
+						   << TestLog::Float("MinBound", "Minimum bound", "", QP_KEY_TAG_NONE, m_minBound)
+						   << TestLog::Float("MaxBound", "Maximum bound", "", QP_KEY_TAG_NONE, m_maxBound)
+						   << TestLog::Integer("CompareTime", "Comparison time", "us", QP_KEY_TAG_TIME, compareTime);
+
+		{
+			const bool isOk = de::inRange(result, m_minBound, m_maxBound);
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Metric out of bounds");
+		}
+
+		return STOP;
+	}
+
+private:
+	const std::string	m_refImg;
+	const std::string	m_cmpImg;
+	const float			m_minBound;
+	const float			m_maxBound;
+};
+
+class BilinearCompareCase : public tcu::TestCase
+{
+public:
+	BilinearCompareCase (tcu::TestContext& testCtx, const char* name, const char* refImg, const char* cmpImg, const tcu::RGBA& threshold, bool expectedResult)
+		: tcu::TestCase		(testCtx, name, "")
+		, m_refImg			(refImg)
+		, m_cmpImg			(cmpImg)
+		, m_threshold		(threshold)
+		, m_expectedResult	(expectedResult)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		tcu::TextureLevel		refImg;
+		tcu::TextureLevel		cmpImg;
+		bool					result;
+		deUint64				compareTime	= 0;
+
+		loadImageRGBA8(refImg, m_testCtx.getArchive(), de::FilePath::join(BASE_DIR, m_refImg).getPath());
+		loadImageRGBA8(cmpImg, m_testCtx.getArchive(), de::FilePath::join(BASE_DIR, m_cmpImg).getPath());
+
+		{
+			const deUint64 startTime = deGetMicroseconds();
+			result = tcu::bilinearCompare(m_testCtx.getLog(), "CompareResult", "Image comparison result", refImg, cmpImg, m_threshold, tcu::COMPARE_LOG_EVERYTHING);
+			compareTime = deGetMicroseconds()-startTime;
+		}
+
+		m_testCtx.getLog() << TestLog::Integer("CompareTime", "Comparison time", "us", QP_KEY_TAG_TIME, compareTime);
+
+		{
+			const bool isOk = result == m_expectedResult;
+			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
+									isOk ? "Pass"				: "Wrong comparison result");
+		}
+
+		return STOP;
+	}
+
+private:
+	const std::string		m_refImg;
+	const std::string		m_cmpImg;
+	const tcu::RGBA			m_threshold;
+	const bool				m_expectedResult;
+};
+
+class FuzzyComparisonMetricTests : public tcu::TestCaseGroup
+{
+public:
+	FuzzyComparisonMetricTests (tcu::TestContext& testCtx)
+		: tcu::TestCaseGroup(testCtx, "fuzzy_metric", "Fuzzy comparison metrics")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "identical",		"cube_ref.png",				"cube_ref.png",				0.0f,			0.000001f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "cube",			"cube_ref.png",				"cube_cmp.png",				0.0029f,		0.0031f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "cube_2",			"cube_2_ref.png",			"cube_2_cmp.png",			0.0134f,		0.0140f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "cube_sphere",	"cube_sphere_ref.png",		"cube_sphere_cmp.png",		0.0730f,		0.0801f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "cube_nmap",		"cube_nmap_ref.png",		"cube_nmap_cmp.png",		0.0024f,		0.0025f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "cube_nmap_2",	"cube_nmap_2_ref.png",		"cube_nmap_2_cmp.png",		0.0172f,		0.0189f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "earth_diffuse",	"earth_diffuse_ref.png",	"earth_diffuse_cmp.png",	0.0f,			0.00002f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "eath_texture",	"earth_texture_ref.png",	"earth_texture_cmp.png",	0.0002f,		0.0003f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "earth_spot",		"earth_spot_ref.png",		"earth_spot_cmp.png",		0.0015f,		0.0018f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "earth_light",	"earth_light_ref.png",		"earth_light_cmp.png",		1.7050f,		1.7070f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "lessThan0",		"lessThan0-reference.png",	"lessThan0-result.png",		0.0003f,		0.0004f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "cube_sphere_2",	"cube_sphere_2_ref.png",	"cube_sphere_2_cmp.png",	0.0207f,		0.0230f));
+		addChild(new FuzzyComparisonMetricCase(m_testCtx, "earth_to_empty",	"earth_spot_ref.png",		"empty_256x256.png",		77074.0f,		77076.0f));
+	}
+};
+
+class BilinearCompareTests : public tcu::TestCaseGroup
+{
+public:
+	BilinearCompareTests (tcu::TestContext& testCtx)
+		: tcu::TestCaseGroup(testCtx, "bilinear_compare", "Bilinear Image Comparison Tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new BilinearCompareCase(m_testCtx, "identical",				"cube_ref.png",						"cube_ref.png",						tcu::RGBA(0,0,0,0),			true));
+		addChild(new BilinearCompareCase(m_testCtx, "empty_to_white",			"empty_256x256.png",				"white_256x256.png",				tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "white_to_empty",			"white_256x256.png",				"empty_256x256.png",				tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "cube",						"cube_ref.png",						"cube_cmp.png",						tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "cube_2",					"cube_2_ref.png",					"cube_2_cmp.png",					tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "cube_sphere",				"cube_sphere_ref.png",				"cube_sphere_cmp.png",				tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "cube_nmap",				"cube_nmap_ref.png",				"cube_nmap_cmp.png",				tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "cube_nmap_2",				"cube_nmap_2_ref.png",				"cube_nmap_2_cmp.png",				tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "earth_diffuse",			"earth_diffuse_ref.png",			"earth_diffuse_cmp.png",			tcu::RGBA(20,20,20,2),		true));
+		addChild(new BilinearCompareCase(m_testCtx, "eath_texture",				"earth_texture_ref.png",			"earth_texture_cmp.png",			tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "earth_spot",				"earth_spot_ref.png",				"earth_spot_cmp.png",				tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "earth_light",				"earth_light_ref.png",				"earth_light_cmp.png",				tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "lessThan0",				"lessThan0-reference.png",			"lessThan0-result.png",				tcu::RGBA(36,36,36,2),		true));
+		addChild(new BilinearCompareCase(m_testCtx, "cube_sphere_2",			"cube_sphere_2_ref.png",			"cube_sphere_2_cmp.png",			tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "earth_to_empty",			"earth_spot_ref.png",				"empty_256x256.png",				tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "texfilter",				"texfilter_ref.png",				"texfilter_cmp.png",				tcu::RGBA(7,7,7,2),			true));
+		addChild(new BilinearCompareCase(m_testCtx, "refract_vtx",				"refract_vtx_ref.png",				"refract_vtx_cmp.png",				tcu::RGBA(7,7,7,2),			true));
+		addChild(new BilinearCompareCase(m_testCtx, "refract_frag",				"refract_frag_ref.png",				"refract_frag_cmp.png",				tcu::RGBA(7,7,7,2),			true));
+		addChild(new BilinearCompareCase(m_testCtx, "lessthan_vtx",				"lessthan_vtx_ref.png",				"lessthan_vtx_cmp.png",				tcu::RGBA(7,7,7,2),			true));
+		addChild(new BilinearCompareCase(m_testCtx, "2_units_2d",				"2_units_2d_ref.png",				"2_units_2d_cmp.png",				tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "4_units_cube_vtx",			"4_units_cube_ref.png",				"4_units_cube_cmp.png",				tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "texfilter_vtx_nearest",	"texfilter_vtx_nearest_ref.png",	"texfilter_vtx_nearest_cmp.png",	tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "texfilter_vtx_linear",		"texfilter_vtx_linear_ref.png",		"texfilter_vtx_linear_cmp.png",		tcu::RGBA(7,7,7,2),			false));
+		addChild(new BilinearCompareCase(m_testCtx, "readpixels_msaa",			"readpixels_ref.png",				"readpixels_msaa.png",				tcu::RGBA(1,1,1,1),			true));
+	}
+};
+
+ImageCompareTests::ImageCompareTests (tcu::TestContext& testCtx)
+	: tcu::TestCaseGroup(testCtx, "image_compare", "Image comparison tests")
+{
+}
+
+ImageCompareTests::~ImageCompareTests (void)
+{
+}
+
+void ImageCompareTests::init (void)
+{
+	addChild(new FuzzyComparisonMetricTests	(m_testCtx));
+	addChild(new BilinearCompareTests		(m_testCtx));
+}
+
+} // dit
diff --git a/modules/internal/ditImageCompareTests.hpp b/modules/internal/ditImageCompareTests.hpp
new file mode 100644
index 0000000..d43b10c
--- /dev/null
+++ b/modules/internal/ditImageCompareTests.hpp
@@ -0,0 +1,43 @@
+#ifndef _DITIMAGECOMPARETESTS_HPP
+#define _DITIMAGECOMPARETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Image comparison tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+
+namespace dit
+{
+
+class ImageCompareTests : public tcu::TestCaseGroup
+{
+public:
+					ImageCompareTests		(tcu::TestContext& testCtx);
+					~ImageCompareTests		(void);
+
+	void			init					(void);
+};
+
+} // dit
+
+#endif // _DITIMAGECOMPARETESTS_HPP
diff --git a/modules/internal/ditImageIOTests.cpp b/modules/internal/ditImageIOTests.cpp
new file mode 100644
index 0000000..fde67f3
--- /dev/null
+++ b/modules/internal/ditImageIOTests.cpp
@@ -0,0 +1,114 @@
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Image IO tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "ditImageIOTests.hpp"
+#include "tcuResource.hpp"
+#include "tcuImageIO.hpp"
+#include "tcuTexture.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuFormatUtil.hpp"
+#include "deUniquePtr.hpp"
+#include "deString.h"
+
+namespace dit
+{
+
+using tcu::TestLog;
+
+// \todo [2013-05-28 pyry] Image output cases!
+
+class ImageReadCase : public tcu::TestCase
+{
+public:
+	ImageReadCase (tcu::TestContext& testCtx, const char* name, const char* filename, deUint32 expectedHash)
+		: TestCase			(testCtx, name, filename)
+		, m_filename		(filename)
+		, m_expectedHash	(expectedHash)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		m_testCtx.getLog() << TestLog::Message << "Loading image from file '" << m_filename << "'" << TestLog::EndMessage;
+
+		tcu::TextureLevel texture;
+		tcu::ImageIO::loadImage(texture, m_testCtx.getArchive(), m_filename.c_str());
+
+		m_testCtx.getLog() << TestLog::Message << "Loaded " << texture.getWidth() << "x" << texture.getHeight() << "x" << texture.getDepth() << " image with format " << texture.getFormat() << TestLog::EndMessage;
+
+		// Check that layout is as expected
+		TCU_CHECK(texture.getAccess().getRowPitch() == texture.getWidth()*texture.getFormat().getPixelSize());
+		TCU_CHECK(texture.getAccess().getSlicePitch() == texture.getAccess().getRowPitch()*texture.getAccess().getHeight());
+
+		const int		imageSize	= texture.getAccess().getSlicePitch()*texture.getDepth();
+		const deUint32	hash		= deMemoryHash(texture.getAccess().getDataPtr(), imageSize);
+
+		if (hash != m_expectedHash)
+		{
+			m_testCtx.getLog() << TestLog::Message << "ERROR: expected hash " << tcu::toHex(m_expectedHash) << ", got " << tcu::toHex(hash) << TestLog::EndMessage;
+			m_testCtx.getLog() << TestLog::Image("Image", "Loaded image", texture.getAccess());
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Hash check failed");
+		}
+		else
+			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+
+		return STOP;
+	}
+
+private:
+	const std::string		m_filename;
+	const deUint32			m_expectedHash;
+};
+
+class ImageReadTests : public tcu::TestCaseGroup
+{
+public:
+	ImageReadTests (tcu::TestContext& testCtx)
+		: TestCaseGroup(testCtx, "read", "Image read tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new ImageReadCase(m_testCtx, "rgb24_256x256",	"data/imageio/rgb24_256x256.png",	0x6efad777));
+		addChild(new ImageReadCase(m_testCtx, "rgb24_209x181",	"data/imageio/rgb24_209x181.png",	0xfd6ea668));
+		addChild(new ImageReadCase(m_testCtx, "rgba32_256x256",	"data/imageio/rgba32_256x256.png",	0xcf4883da));
+		addChild(new ImageReadCase(m_testCtx, "rgba32_207x219",	"data/imageio/rgba32_207x219.png",	0x404ba06b));
+	}
+};
+
+ImageIOTests::ImageIOTests(tcu::TestContext& testCtx)
+	: TestCaseGroup(testCtx, "image_io", "Image read and write tests")
+{
+}
+
+ImageIOTests::~ImageIOTests (void)
+{
+}
+
+void ImageIOTests::init (void)
+{
+	addChild(new ImageReadTests(m_testCtx));
+}
+
+} // dit
diff --git a/modules/internal/ditImageIOTests.hpp b/modules/internal/ditImageIOTests.hpp
new file mode 100644
index 0000000..8253bc3
--- /dev/null
+++ b/modules/internal/ditImageIOTests.hpp
@@ -0,0 +1,43 @@
+#ifndef _DITIMAGEIOTESTS_HPP
+#define _DITIMAGEIOTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Image IO tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+
+namespace dit
+{
+
+class ImageIOTests : public tcu::TestCaseGroup
+{
+public:
+					ImageIOTests		(tcu::TestContext& testCtx);
+					~ImageIOTests		(void);
+
+	void			init				(void);
+};
+
+} // dit
+
+#endif // _DITIMAGEIOTESTS_HPP
diff --git a/modules/internal/ditTestCase.cpp b/modules/internal/ditTestCase.cpp
new file mode 100644
index 0000000..eb05f9e
--- /dev/null
+++ b/modules/internal/ditTestCase.cpp
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test case classes for internal tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.hpp"
+
+DE_EMPTY_CPP_FILE
diff --git a/modules/internal/ditTestCase.hpp b/modules/internal/ditTestCase.hpp
new file mode 100644
index 0000000..b751489
--- /dev/null
+++ b/modules/internal/ditTestCase.hpp
@@ -0,0 +1,56 @@
+#ifndef _DITTESTCASE_HPP
+#define _DITTESTCASE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test case classes for internal tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+
+namespace dit
+{
+
+class SelfCheckCase : public tcu::TestCase
+{
+public:
+	typedef void (*Function) (void);
+
+	SelfCheckCase (tcu::TestContext& testCtx, const char* name, const char* desc, Function func)
+		: tcu::TestCase	(testCtx, name, desc)
+		, m_function	(func)
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		m_function();
+		return STOP;
+	}
+
+private:
+	Function m_function;
+};
+
+} // dit
+
+#endif // _DITTESTCASE_HPP
diff --git a/modules/internal/ditTestLogTests.cpp b/modules/internal/ditTestLogTests.cpp
new file mode 100644
index 0000000..4660613
--- /dev/null
+++ b/modules/internal/ditTestLogTests.cpp
@@ -0,0 +1,86 @@
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test log output tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "ditTestLogTests.hpp"
+#include "tcuTestLog.hpp"
+
+#include <limits>
+
+namespace dit
+{
+
+using tcu::TestLog;
+
+// \todo [2014-02-25 pyry] Extend with:
+//  - output of all element types
+//  - nested element cases (sections, image sets)
+//  - parse results and verify
+
+class BasicSampleListCase : public tcu::TestCase
+{
+public:
+	BasicSampleListCase (tcu::TestContext& testCtx)
+		: TestCase(testCtx, "sample_list", "Basic sample list usage")
+	{
+	}
+
+	IterateResult iterate (void)
+	{
+		TestLog& log = m_testCtx.getLog();
+
+		log << TestLog::SampleList("TestSamples", "Test Sample List")
+			<< TestLog::SampleInfo
+			<< TestLog::ValueInfo("NumDrawCalls",	"Number of draw calls",		"",		QP_SAMPLE_VALUE_TAG_PREDICTOR)
+			<< TestLog::ValueInfo("NumOps",			"Number of ops in shader",	"op",	QP_SAMPLE_VALUE_TAG_PREDICTOR)
+			<< TestLog::ValueInfo("RenderTime",		"Rendering time",			"ms",	QP_SAMPLE_VALUE_TAG_RESPONSE)
+			<< TestLog::EndSampleInfo;
+
+		log << TestLog::Sample << 1 << 2 << 2.3 << TestLog::EndSample
+			<< TestLog::Sample << 0 << 0 << 0 << TestLog::EndSample
+			<< TestLog::Sample << 421 << -23 << 0.00001 << TestLog::EndSample
+			<< TestLog::Sample << 2 << 9 << -1e9 << TestLog::EndSample
+			<< TestLog::Sample << std::numeric_limits<deInt64>::max() << std::numeric_limits<deInt64>::min() << -0.0f << TestLog::EndSample
+			<< TestLog::Sample << 0x3355 << 0xf24 << std::numeric_limits<double>::max() << TestLog::EndSample;
+
+		log << TestLog::EndSampleList;
+
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		return STOP;
+	}
+};
+
+TestLogTests::TestLogTests (tcu::TestContext& testCtx)
+	: TestCaseGroup(testCtx, "testlog", "Test Log Tests")
+{
+}
+
+TestLogTests::~TestLogTests (void)
+{
+}
+
+void TestLogTests::init (void)
+{
+	addChild(new BasicSampleListCase(m_testCtx));
+}
+
+} // dit
diff --git a/modules/internal/ditTestLogTests.hpp b/modules/internal/ditTestLogTests.hpp
new file mode 100644
index 0000000..e49a249
--- /dev/null
+++ b/modules/internal/ditTestLogTests.hpp
@@ -0,0 +1,43 @@
+#ifndef _DITTESTLOGTESTS_HPP
+#define _DITTESTLOGTESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test log output tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestCase.hpp"
+
+namespace dit
+{
+
+class TestLogTests : public tcu::TestCaseGroup
+{
+public:
+					TestLogTests		(tcu::TestContext& testCtx);
+					~TestLogTests		(void);
+
+	void			init				(void);
+};
+
+} // dit
+
+#endif // _DITTESTLOGTESTS_HPP
diff --git a/modules/internal/ditTestPackage.cpp b/modules/internal/ditTestPackage.cpp
new file mode 100644
index 0000000..6af0657
--- /dev/null
+++ b/modules/internal/ditTestPackage.cpp
@@ -0,0 +1,75 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program OpenGL ES 2.0 Module
+ * -------------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief drawElements Internal Test Package
+ *//*--------------------------------------------------------------------*/
+
+#include "ditTestPackage.hpp"
+#include "ditBuildInfoTests.hpp"
+#include "ditDelibsTests.hpp"
+#include "ditFrameworkTests.hpp"
+#include "ditImageIOTests.hpp"
+#include "ditImageCompareTests.hpp"
+#include "ditTestLogTests.hpp"
+
+namespace dit
+{
+
+class DeqpTests : public tcu::TestCaseGroup
+{
+public:
+	DeqpTests (tcu::TestContext& testCtx)
+		: tcu::TestCaseGroup(testCtx, "deqp", "dEQP Test Framework Self-tests")
+	{
+	}
+
+	void init (void)
+	{
+		addChild(new TestLogTests		(m_testCtx));
+		addChild(new ImageIOTests		(m_testCtx));
+		addChild(new ImageCompareTests	(m_testCtx));
+	}
+};
+
+TestPackage::TestPackage (tcu::TestContext& testCtx)
+	: tcu::TestPackage	(testCtx, "dE-IT", "drawElements Internal Tests")
+	, m_wrapper			(testCtx)
+	, m_archive			(testCtx.getRootArchive(), "internal/")
+{
+}
+
+TestPackage::~TestPackage (void)
+{
+}
+
+void TestPackage::init (void)
+{
+	addChild(new BuildInfoTests	(m_testCtx));
+	addChild(new DelibsTests	(m_testCtx));
+	addChild(new FrameworkTests	(m_testCtx));
+	addChild(new DeqpTests		(m_testCtx));
+}
+
+void TestPackage::deinit (void)
+{
+	TestNode::deinit();
+}
+
+} // dit
diff --git a/modules/internal/ditTestPackage.hpp b/modules/internal/ditTestPackage.hpp
new file mode 100644
index 0000000..0d08a06
--- /dev/null
+++ b/modules/internal/ditTestPackage.hpp
@@ -0,0 +1,52 @@
+#ifndef _DITTESTPACKAGE_HPP
+#define _DITTESTPACKAGE_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief drawElements Internal Test Package
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "tcuTestPackage.hpp"
+#include "tcuResource.hpp"
+
+namespace dit
+{
+
+class TestPackage : public tcu::TestPackage
+{
+public:
+									TestPackage				(tcu::TestContext& testCtx);
+	virtual							~TestPackage			(void);
+
+	virtual void					init					(void);
+	virtual void					deinit					(void);
+
+	tcu::TestCaseWrapper&			getTestCaseWrapper		(void) { return m_wrapper; }
+	tcu::Archive&					getArchive				(void) { return m_archive; }
+
+private:
+	tcu::TestCaseWrapper			m_wrapper;
+	tcu::ResourcePrefix				m_archive;
+};
+
+} // dit
+
+#endif // _DITTESTPACKAGE_HPP
diff --git a/modules/internal/ditTestPackageEntry.cpp b/modules/internal/ditTestPackageEntry.cpp
new file mode 100644
index 0000000..54df640
--- /dev/null
+++ b/modules/internal/ditTestPackageEntry.cpp
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ * drawElements Internal Test Module
+ * ---------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief dE-IT module entry point.
+ *//*--------------------------------------------------------------------*/
+
+#include "ditTestPackage.hpp"
+
+// Register package to test executor.
+
+static tcu::TestPackage* createTestPackage (tcu::TestContext& testCtx)
+{
+	return new dit::TestPackage(testCtx);
+}
+
+tcu::TestPackageDescriptor g_ditPackageDescriptor("dE-IT", createTestPackage);
diff --git a/modules/internal/internal.cmake b/modules/internal/internal.cmake
new file mode 100644
index 0000000..105d974
--- /dev/null
+++ b/modules/internal/internal.cmake
@@ -0,0 +1 @@
+add_subdirectory(internal)